From 2365fe663bd087b6e3784f66645cf0357025b9bb Mon Sep 17 00:00:00 2001 From: Falco Girgis Date: Sun, 16 Mar 2025 10:23:55 -0500 Subject: [PATCH] Working saving, not loading. --- .gitignore | 65 +- .gitlab-ci.yml | 133 +- .vscode/settings.json | 8 +- CMakeLists.txt | 100 - CODING_STYLE.md | 107 - README.md | 4 +- 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/Debug/gta3.txt | 0 codewarrior/Release/gta3.txt | 0 codewarrior/re3.mcp.xml | 15378 ---------------- conanfile.py | 135 - dreamcast/common.mk | 396 - dreamcast/gen-mesh-variants.py | 114 - dreamcast/save.ico | Bin 1526 -> 0 bytes dreamcast/settings.ico | Bin 766 -> 0 bytes gamefiles/{ => liberty}/TEXT/JAPANESE.gxt | Bin gamefiles/{ => liberty}/TEXT/american.gxt | Bin gamefiles/{ => liberty}/TEXT/english.gxt | Bin gamefiles/{ => liberty}/TEXT/french.gxt | Bin gamefiles/{ => liberty}/TEXT/german.gxt | Bin gamefiles/{ => liberty}/TEXT/italian.gxt | Bin gamefiles/{ => liberty}/TEXT/polish.gxt | Bin gamefiles/{ => liberty}/TEXT/russian.gxt | Bin gamefiles/{ => liberty}/TEXT/spanish.gxt | Bin gamefiles/{ => liberty}/data/PARTICLE.CFG | 0 gamefiles/{ => liberty}/data/main_d.scm | Bin .../{ => liberty}/data/main_freeroam.scm | Bin gamefiles/{ => liberty}/gamecontrollerdb.txt | 0 gamefiles/{ => liberty}/models/fonts_j.txd | Bin gamefiles/{ => liberty}/models/fonts_p.txd | Bin gamefiles/{ => liberty}/models/fonts_r.txd | Bin .../{ => liberty}/models/frontend_ds3.txd | Bin .../{ => liberty}/models/frontend_ds4.txd | Bin .../{ => liberty}/models/frontend_nsw.txd | Bin .../{ => liberty}/models/frontend_x360.txd | Bin .../{ => liberty}/models/frontend_xone.txd | Bin gamefiles/{ => liberty}/models/generic.txd | Bin gamefiles/{ => liberty}/models/menu.txd | Bin gamefiles/{ => liberty}/models/nswbtns.txd | Bin gamefiles/{ => liberty}/models/particle.txd | Bin gamefiles/{ => liberty}/models/ps3btns.txd | Bin gamefiles/{ => liberty}/models/x360btns.txd | Bin .../{ => liberty}/neo/carTweakingTable.dat | 0 gamefiles/{ => liberty}/neo/neo.txd | Bin .../{ => liberty}/neo/rimTweakingTable.dat | 0 .../{ => liberty}/neo/worldTweakingTable.dat | 0 gamefiles/miami/TEXT/american.gxt | Bin 0 -> 424134 bytes gamefiles/miami/TEXT/french.gxt | Bin 0 -> 470132 bytes gamefiles/miami/TEXT/german.gxt | Bin 0 -> 462030 bytes gamefiles/miami/TEXT/italian.gxt | Bin 0 -> 459086 bytes gamefiles/miami/TEXT/russian.gxt | Bin 0 -> 420604 bytes gamefiles/miami/TEXT/spanish.gxt | Bin 0 -> 455414 bytes gamefiles/miami/data/freeroam_miami.scm | Bin 0 -> 38197 bytes gamefiles/miami/gamecontrollerdb.txt | 943 + gamefiles/miami/models/fonts_r.txd | Bin 0 -> 524584 bytes gamefiles/miami/models/frontend_ds2.txd | Bin 0 -> 329976 bytes gamefiles/miami/models/frontend_ds3.txd | Bin 0 -> 524968 bytes gamefiles/miami/models/frontend_ds4.txd | Bin 0 -> 524968 bytes gamefiles/miami/models/frontend_nsw.txd | Bin 0 -> 2360104 bytes gamefiles/miami/models/frontend_x360.txd | Bin 0 -> 524968 bytes gamefiles/miami/models/frontend_xone.txd | Bin 0 -> 524968 bytes gamefiles/miami/models/generic.txd | Bin 0 -> 1168376 bytes gamefiles/miami/models/nswbtns.txd | Bin 0 -> 528424 bytes gamefiles/miami/models/particle.txd | Bin 0 -> 2668072 bytes gamefiles/miami/models/ps3btns.txd | Bin 0 -> 528424 bytes gamefiles/miami/models/x360btns.txd | Bin 0 -> 528424 bytes gamefiles/miami/neo/carTweakingTable.dat | 104 + gamefiles/miami/neo/neo.txd | Bin 0 -> 28968 bytes gamefiles/miami/neo/rimTweakingTable.dat | 130 + gamefiles/miami/neo/worldTweakingTable.dat | 30 + {utils/gxt => gxt/liberty}/american.txt | 0 {utils/gxt => gxt/liberty}/build.bat | 0 {utils/gxt => gxt/liberty}/english.txt | 0 {utils/gxt => gxt/liberty}/french.txt | 0 {utils/gxt => gxt/liberty}/german.txt | 0 {utils/gxt => gxt/liberty}/gxt.exe | Bin {utils/gxt => gxt/liberty}/italian.txt | 0 {utils/gxt => gxt/liberty}/polish.txt | 0 {utils/gxt => gxt/liberty}/russian.txt | 0 {utils/gxt => gxt/liberty}/spanish.txt | 0 gxt/miami/american.txt | 14438 +++++++++++++++ gxt/miami/build.bat | 5 + gxt/miami/french.txt | 14726 +++++++++++++++ gxt/miami/german.txt | 14726 +++++++++++++++ gxt/miami/gxt.exe | Bin 0 -> 307200 bytes gxt/miami/italian.txt | 14735 +++++++++++++++ gxt/miami/spanish.txt | 14422 +++++++++++++++ {dreamcast => liberty}/Makefile | 264 +- {dreamcast => liberty/assets}/GTA3SF8.b | Bin liberty/assets/dc_ps2d.png | Bin 0 -> 90082 bytes liberty/assets/dc_ps2f.png | Bin 0 -> 93428 bytes liberty/assets/dc_xboxd.png | Bin 0 -> 89065 bytes liberty/assets/dc_xboxf.png | Bin 0 -> 91910 bytes liberty/assets/ps4_d.png | Bin 0 -> 6437 bytes liberty/assets/ps4_f.png | Bin 0 -> 78895 bytes liberty/assets/xbox_d.png | Bin 0 -> 6437 bytes liberty/assets/xbox_f.png | Bin 0 -> 80524 bytes liberty/common.mk | 401 + liberty/cuts.mk | 286 + {dreamcast => liberty}/gta3files.mk | 3 +- liberty/ip.txt | 9 + {dreamcast => liberty}/modlist.mk | 0 {dreamcast => liberty}/mp3list.mk | 0 {dreamcast => liberty}/sfxlist.mk | 0 {dreamcast => liberty}/sfxlooplist.mk | 2 +- {dreamcast => liberty}/sim.mk | 29 +- {dreamcast => liberty}/texlist.mk | 0 {dreamcast => liberty}/wavlist.mk | 0 miami/GTAVCSF8.b | Bin 0 -> 201828 bytes miami/Makefile | 668 + miami/common.mk | 416 + miami/cuts.mk | 151 + miami/gta3files.mk | 210 + miami/imgmisc.mk | 61 + {dreamcast => miami}/ip.txt | 6 +- miami/modlist.mk | 4614 +++++ miami/mp3list.mk | 102 + miami/sfxlist.mk | 9943 ++++++++++ miami/sfxlooplist.mk | 34 + miami/sim.mk | 50 + miami/texlist.mk | 1362 ++ miami/wavlist.mk | 1121 ++ premake-vs2015.cmd | 1 - premake-vs2017.cmd | 1 - premake-vs2019.cmd | 1 - premake5.exe | Bin 1395712 -> 0 bytes premake5.lua | 477 - premake5Linux | Bin 2035312 -> 0 bytes printHash.bat | 26 - printHash.sh | 14 - public/index.html | 102 - src/animation/AnimBlendNode.cpp | 163 - src/animation/AnimBlendSequence.h | 166 - src/{ => common}/prof/profiler.cpp | 0 src/{ => common}/prof/profiler.h | 0 src/{ => common}/vmu/vmu.cpp | 4 +- src/{ => common}/vmu/vmu.h | 0 src/{ => liberty}/CMakeLists.txt | 0 .../animation/AnimBlendAssocGroup.cpp | 0 .../animation/AnimBlendAssocGroup.h | 0 .../animation/AnimBlendAssociation.cpp | 2 + .../animation/AnimBlendAssociation.h | 0 .../animation/AnimBlendClumpData.cpp | 0 .../animation/AnimBlendClumpData.h | 0 src/liberty/animation/AnimBlendHierarchy.cpp | 57 + .../animation/AnimBlendHierarchy.h | 0 src/{ => liberty}/animation/AnimBlendList.h | 0 src/liberty/animation/AnimBlendNode.cpp | 189 + src/{ => liberty}/animation/AnimBlendNode.h | 3 +- .../animation/AnimBlendSequence.cpp | 39 - src/liberty/animation/AnimBlendSequence.h | 402 + src/{ => liberty}/animation/AnimManager.cpp | 164 +- src/{ => liberty}/animation/AnimManager.h | 0 src/{ => liberty}/animation/AnimationId.h | 0 src/{ => liberty}/animation/Bones.cpp | 0 src/{ => liberty}/animation/Bones.h | 0 src/{ => liberty}/animation/CutsceneMgr.cpp | 2 +- src/{ => liberty}/animation/CutsceneMgr.h | 0 src/{ => liberty}/animation/FrameUpdate.cpp | 0 src/{ => liberty}/animation/RpAnimBlend.cpp | 0 src/{ => liberty}/animation/RpAnimBlend.h | 0 src/{ => liberty}/audio/AudioCollision.cpp | 0 src/{ => liberty}/audio/AudioCollision.h | 0 src/{ => liberty}/audio/AudioLogic.cpp | 6 +- src/{ => liberty}/audio/AudioManager.cpp | 0 src/{ => liberty}/audio/AudioManager.h | 0 src/{ => liberty}/audio/AudioSamples.h | 0 src/{ => liberty}/audio/AudioScriptObject.cpp | 0 src/{ => liberty}/audio/AudioScriptObject.h | 0 src/{ => liberty}/audio/DMAudio.cpp | 0 src/{ => liberty}/audio/DMAudio.h | 0 src/{ => liberty}/audio/MusicManager.cpp | 0 src/{ => liberty}/audio/MusicManager.h | 0 src/{ => liberty}/audio/PolRadio.cpp | 0 src/{ => liberty}/audio/PolRadio.h | 0 src/{ => liberty}/audio/audio_enums.h | 0 src/{ => liberty}/audio/eax/eax-util.cpp | 0 src/{ => liberty}/audio/eax/eax-util.h | 0 src/{ => liberty}/audio/eax/eax.h | 0 src/{ => liberty}/audio/oal/aldlist.cpp | 0 src/{ => liberty}/audio/oal/aldlist.h | 0 src/{ => liberty}/audio/oal/channel.cpp | 0 src/{ => liberty}/audio/oal/channel.h | 0 src/{ => liberty}/audio/oal/oal_utils.cpp | 0 src/{ => liberty}/audio/oal/oal_utils.h | 0 src/{ => liberty}/audio/oal/stream.cpp | 0 src/{ => liberty}/audio/oal/stream.h | 0 src/{ => liberty}/audio/sampman.h | 205 +- src/{ => liberty}/audio/sampman_dc.cpp | 108 +- src/liberty/audio/sampman_dc_streams.h | 200 + src/{ => liberty}/audio/sampman_miles.cpp | 0 src/{ => liberty}/audio/sampman_null.cpp | 0 src/{ => liberty}/audio/sampman_oal.cpp | 0 src/{ => liberty}/audio/soundlist.h | 0 src/{ => liberty}/buildings/Building.cpp | 0 src/{ => liberty}/buildings/Building.h | 0 src/{ => liberty}/buildings/Solid.h | 0 src/{ => liberty}/buildings/Treadable.cpp | 0 src/{ => liberty}/buildings/Treadable.h | 0 src/{ => liberty}/collision/ColBox.cpp | 0 src/{ => liberty}/collision/ColBox.h | 0 src/{ => liberty}/collision/ColLine.cpp | 0 src/{ => liberty}/collision/ColLine.h | 0 src/{ => liberty}/collision/ColModel.cpp | 0 src/{ => liberty}/collision/ColModel.h | 0 src/{ => liberty}/collision/ColPoint.cpp | 0 src/{ => liberty}/collision/ColPoint.h | 0 src/{ => liberty}/collision/ColSphere.cpp | 0 src/{ => liberty}/collision/ColSphere.h | 0 src/{ => liberty}/collision/ColTriangle.cpp | 2 +- src/{ => liberty}/collision/ColTriangle.h | 2 +- src/{ => liberty}/collision/Collision.cpp | 0 src/{ => liberty}/collision/Collision.h | 0 .../collision/CompressedVector.h | 2 +- src/{ => liberty}/collision/TempColModels.cpp | 0 src/{ => liberty}/collision/TempColModels.h | 0 src/{ => liberty}/collision/VuCollision.cpp | 0 src/{ => liberty}/collision/VuCollision.h | 0 src/{ => liberty}/collision/vu0Collision.dsm | 0 src/{ => liberty}/collision/vu0Collision_1.s | 0 src/{ => liberty}/collision/vu0Collision_2.s | 0 src/{ => liberty}/control/AutoPilot.cpp | 0 src/{ => liberty}/control/AutoPilot.h | 0 src/{ => liberty}/control/Bridge.cpp | 0 src/{ => liberty}/control/Bridge.h | 0 src/{ => liberty}/control/CarAI.cpp | 0 src/{ => liberty}/control/CarAI.h | 0 src/{ => liberty}/control/CarCtrl.cpp | 0 src/{ => liberty}/control/CarCtrl.h | 0 src/{ => liberty}/control/Curves.cpp | 0 src/{ => liberty}/control/Curves.h | 0 src/{ => liberty}/control/Darkel.cpp | 0 src/{ => liberty}/control/Darkel.h | 0 src/{ => liberty}/control/GameLogic.cpp | 0 src/{ => liberty}/control/GameLogic.h | 0 src/{ => liberty}/control/Garages.cpp | 0 src/{ => liberty}/control/Garages.h | 0 src/{ => liberty}/control/NameGrid.cpp | 0 src/{ => liberty}/control/NameGrid.h | 0 src/{ => liberty}/control/OnscreenTimer.cpp | 0 src/{ => liberty}/control/OnscreenTimer.h | 0 src/{ => liberty}/control/PathFind.cpp | 0 src/{ => liberty}/control/PathFind.h | 0 src/{ => liberty}/control/Phones.cpp | 0 src/{ => liberty}/control/Phones.h | 0 src/{ => liberty}/control/Pickups.cpp | 0 src/{ => liberty}/control/Pickups.h | 0 src/{ => liberty}/control/PowerPoints.cpp | 0 src/{ => liberty}/control/PowerPoints.h | 0 src/{ => liberty}/control/Record.cpp | 0 src/{ => liberty}/control/Record.h | 0 src/{ => liberty}/control/Remote.cpp | 0 src/{ => liberty}/control/Remote.h | 0 src/{ => liberty}/control/Replay.cpp | 0 src/{ => liberty}/control/Replay.h | 0 src/{ => liberty}/control/Restart.cpp | 0 src/{ => liberty}/control/Restart.h | 0 src/{ => liberty}/control/RoadBlocks.cpp | 0 src/{ => liberty}/control/RoadBlocks.h | 0 src/{ => liberty}/control/SceneEdit.cpp | 0 src/{ => liberty}/control/SceneEdit.h | 0 src/{ => liberty}/control/Script.cpp | 0 src/{ => liberty}/control/Script.h | 0 src/{ => liberty}/control/Script2.cpp | 0 src/{ => liberty}/control/Script3.cpp | 0 src/{ => liberty}/control/Script4.cpp | 4 +- src/{ => liberty}/control/Script5.cpp | 0 src/{ => liberty}/control/Script6.cpp | 0 src/{ => liberty}/control/ScriptCommands.h | 0 src/{ => liberty}/control/ScriptDebug.cpp | 0 src/{ => liberty}/control/TrafficLights.cpp | 0 src/{ => liberty}/control/TrafficLights.h | 0 src/{ => liberty}/core/Accident.cpp | 0 src/{ => liberty}/core/Accident.h | 0 src/{ => liberty}/core/AnimViewer.cpp | 0 src/{ => liberty}/core/AnimViewer.h | 0 src/{ => liberty}/core/Cam.cpp | 0 src/{ => liberty}/core/Camera.cpp | 0 src/{ => liberty}/core/Camera.h | 0 src/{ => liberty}/core/CdStream.cpp | 0 src/{ => liberty}/core/CdStream.h | 10 +- src/{ => liberty}/core/CdStreamDC.cpp | 23 +- src/{ => liberty}/core/CdStreamPosix.cpp | 0 src/{ => liberty}/core/Clock.cpp | 0 src/{ => liberty}/core/Clock.h | 0 src/{ => liberty}/core/ControllerConfig.cpp | 380 +- src/{ => liberty}/core/ControllerConfig.h | 0 src/{ => liberty}/core/Crime.h | 0 src/{ => liberty}/core/Debug.cpp | 0 src/{ => liberty}/core/Debug.h | 0 src/{ => liberty}/core/Directory.cpp | 0 src/{ => liberty}/core/Directory.h | 0 src/{ => liberty}/core/EventList.cpp | 0 src/{ => liberty}/core/EventList.h | 0 src/{ => liberty}/core/FileLoader.cpp | 19 +- src/{ => liberty}/core/FileLoader.h | 0 src/{ => liberty}/core/FileMgr.cpp | 0 src/{ => liberty}/core/FileMgr.h | 0 src/{ => liberty}/core/Fire.cpp | 0 src/{ => liberty}/core/Fire.h | 0 src/{ => liberty}/core/FrontEndControls.cpp | 0 src/{ => liberty}/core/FrontEndControls.h | 0 src/{ => liberty}/core/Frontend.cpp | 476 +- src/{ => liberty}/core/Frontend.h | 11 + src/{ => liberty}/core/FrontendTriggers.h | 0 src/{ => liberty}/core/Frontend_PS2.cpp | 0 src/{ => liberty}/core/Frontend_PS2.h | 0 src/{ => liberty}/core/Game.cpp | 0 src/{ => liberty}/core/Game.h | 0 src/{ => liberty}/core/General.h | 0 src/{ => liberty}/core/IniFile.cpp | 0 src/{ => liberty}/core/IniFile.h | 0 src/{ => liberty}/core/Lists.cpp | 0 src/{ => liberty}/core/Lists.h | 0 src/{ => liberty}/core/MenuScreens.cpp | 0 src/{ => liberty}/core/MenuScreensCustom.cpp | 2 +- src/{ => liberty}/core/Pad.cpp | 13 +- src/{ => liberty}/core/Pad.h | 0 src/{ => liberty}/core/Placeable.cpp | 0 src/{ => liberty}/core/Placeable.h | 0 src/{ => liberty}/core/PlayerInfo.cpp | 0 src/{ => liberty}/core/PlayerInfo.h | 0 src/{ => liberty}/core/Pools.cpp | 0 src/{ => liberty}/core/Pools.h | 0 src/{ => liberty}/core/Profile.cpp | 0 src/{ => liberty}/core/Profile.h | 0 src/{ => liberty}/core/Radar.cpp | 32 +- src/{ => liberty}/core/Radar.h | 0 src/{ => liberty}/core/Range2D.cpp | 0 src/{ => liberty}/core/Range2D.h | 0 src/{ => liberty}/core/Range3D.cpp | 0 src/{ => liberty}/core/Range3D.h | 0 src/{ => liberty}/core/References.cpp | 0 src/{ => liberty}/core/References.h | 0 src/{ => liberty}/core/Stats.cpp | 0 src/{ => liberty}/core/Stats.h | 0 src/{ => liberty}/core/Streaming.cpp | 6 + src/{ => liberty}/core/Streaming.h | 0 src/{ => liberty}/core/SurfaceTable.cpp | 0 src/{ => liberty}/core/SurfaceTable.h | 0 src/{ => liberty}/core/TimeStep.cpp | 0 src/{ => liberty}/core/TimeStep.h | 0 src/{ => liberty}/core/Timer.cpp | 0 src/{ => liberty}/core/Timer.h | 0 src/{ => liberty}/core/User.cpp | 0 src/{ => liberty}/core/User.h | 0 src/{ => liberty}/core/Wanted.cpp | 0 src/{ => liberty}/core/Wanted.h | 0 src/{ => liberty}/core/World.cpp | 0 src/{ => liberty}/core/World.h | 0 src/{ => liberty}/core/ZoneCull.cpp | 0 src/{ => liberty}/core/ZoneCull.h | 0 src/{ => liberty}/core/Zones.cpp | 0 src/{ => liberty}/core/Zones.h | 0 src/{ => liberty}/core/common.h | 6 +- src/{ => liberty}/core/config.h | 6 +- src/{ => liberty}/core/main.cpp | 50 +- src/{ => liberty}/core/main.h | 0 src/{ => liberty}/core/obrstr.cpp | 0 src/{ => liberty}/core/obrstr.h | 0 src/{ => liberty}/core/re3.cpp | 2 +- src/{ => liberty}/core/templates.h | 0 src/{ => liberty}/core/timebars.cpp | 0 src/{ => liberty}/core/timebars.h | 0 src/{ => liberty}/entities/Dummy.cpp | 0 src/{ => liberty}/entities/Dummy.h | 0 src/{ => liberty}/entities/Entity.cpp | 5 + src/{ => liberty}/entities/Entity.h | 1 + src/{ => liberty}/entities/Physical.cpp | 10 +- src/{ => liberty}/entities/Physical.h | 0 src/{ => liberty}/extras/GitSHA1.cpp.in | 0 src/{ => liberty}/extras/GitSHA1.h | 0 src/{ => liberty}/extras/arrow.inc | 0 src/{ => liberty}/extras/cursor.inc | 0 src/{ => liberty}/extras/custompipes.cpp | 0 src/{ => liberty}/extras/custompipes.h | 0 src/{ => liberty}/extras/custompipes_d3d9.cpp | 0 src/{ => liberty}/extras/custompipes_gl.cpp | 0 src/{ => liberty}/extras/debugmenu.cpp | 0 src/{ => liberty}/extras/debugmenu.h | 0 src/{ => liberty}/extras/frontendoption.cpp | 0 src/{ => liberty}/extras/frontendoption.h | 0 src/liberty/extras/ini.h | 857 + src/{ => liberty}/extras/postfx.cpp | 0 src/{ => liberty}/extras/postfx.h | 0 src/{ => liberty}/extras/re3_inttypes.h | 0 src/{ => liberty}/extras/screendroplets.cpp | 0 src/{ => liberty}/extras/screendroplets.h | 0 .../extras/shaders/colourfilterIII.frag | 0 .../extras/shaders/colourfilterIII_PS.hlsl | 0 .../extras/shaders/contrast.frag | 0 .../extras/shaders/contrastPS.hlsl | 0 .../extras/shaders/default_UV2.vert | 0 .../extras/shaders/default_UV2_VS.hlsl | 0 src/{ => liberty}/extras/shaders/im2d.vert | 0 .../extras/shaders/im2d_UV2.vert | 0 src/{ => liberty}/extras/shaders/lighting.h | 0 src/{ => liberty}/extras/shaders/make_glsl.sh | 0 .../extras/shaders/make_hlsl.cmd | 0 .../extras/shaders/makeinc_glsl.sh | 0 .../extras/shaders/makeinc_hlsl.sh | 0 .../extras/shaders/neoGloss.frag | 0 .../extras/shaders/neoGloss.vert | 0 .../extras/shaders/neoGloss_PS.hlsl | 0 .../extras/shaders/neoGloss_VS.hlsl | 0 src/{ => liberty}/extras/shaders/neoRim.vert | 0 .../extras/shaders/neoRimSkin.vert | 0 .../extras/shaders/neoRimSkin_VS.hlsl | 0 .../extras/shaders/neoRim_VS.hlsl | 0 .../extras/shaders/neoVehicle.frag | 0 .../extras/shaders/neoVehicle.vert | 0 .../extras/shaders/neoVehicle_PS.hlsl | 0 .../extras/shaders/neoVehicle_VS.hlsl | 0 .../extras/shaders/neoWorldIII.frag | 0 .../extras/shaders/neoWorldIII_PS.hlsl | 0 .../extras/shaders/obj/colourfilterIII_PS.cso | Bin .../extras/shaders/obj/colourfilterIII_PS.inc | 0 .../shaders/obj/colourfilterIII_frag.inc | 0 .../extras/shaders/obj/contrastPS.cso | Bin .../extras/shaders/obj/contrastPS.inc | 0 .../extras/shaders/obj/contrast_frag.inc | 0 .../extras/shaders/obj/default_UV2_VS.cso | Bin .../extras/shaders/obj/default_UV2_VS.inc | 0 .../extras/shaders/obj/default_UV2_vert.inc | 0 .../extras/shaders/obj/im2d_UV2_vert.inc | 0 .../extras/shaders/obj/im2d_vert.inc | 0 .../extras/shaders/obj/neoGloss_PS.cso | Bin .../extras/shaders/obj/neoGloss_PS.inc | 0 .../extras/shaders/obj/neoGloss_VS.cso | Bin .../extras/shaders/obj/neoGloss_VS.inc | 0 .../extras/shaders/obj/neoGloss_frag.inc | 0 .../extras/shaders/obj/neoGloss_vert.inc | 0 .../extras/shaders/obj/neoRimSkin_VS.cso | Bin .../extras/shaders/obj/neoRimSkin_VS.inc | 0 .../extras/shaders/obj/neoRimSkin_vert.inc | 0 .../extras/shaders/obj/neoRim_VS.cso | Bin .../extras/shaders/obj/neoRim_VS.inc | 0 .../extras/shaders/obj/neoRim_vert.inc | 0 .../extras/shaders/obj/neoVehicle_PS.cso | Bin .../extras/shaders/obj/neoVehicle_PS.inc | 0 .../extras/shaders/obj/neoVehicle_VS.cso | Bin .../extras/shaders/obj/neoVehicle_VS.inc | 0 .../extras/shaders/obj/neoVehicle_frag.inc | 0 .../extras/shaders/obj/neoVehicle_vert.inc | 0 .../extras/shaders/obj/neoWorldIII_PS.cso | Bin .../extras/shaders/obj/neoWorldIII_PS.inc | 0 .../extras/shaders/obj/neoWorldIII_frag.inc | 0 .../extras/shaders/obj/screenDroplet_PS.cso | Bin .../extras/shaders/obj/screenDroplet_PS.inc | 0 .../extras/shaders/obj/screenDroplet_frag.inc | 0 .../extras/shaders/obj/simple_frag.inc | 0 .../extras/shaders/screenDroplet.frag | 0 .../extras/shaders/screenDroplet_PS.hlsl | 0 src/{ => liberty}/extras/shaders/simple.frag | 0 .../extras/shaders/standardConstants.h | 0 src/{ => liberty}/fakerw/fake.cpp | 0 src/{ => liberty}/fakerw/rpanisot.h | 0 src/{ => liberty}/fakerw/rphanim.h | 0 src/{ => liberty}/fakerw/rpmatfx.h | 0 src/{ => liberty}/fakerw/rpskin.h | 0 src/{ => liberty}/fakerw/rpworld.h | 0 src/{ => liberty}/fakerw/rtbmp.h | 0 src/{ => liberty}/fakerw/rtcharse.h | 0 src/{ => liberty}/fakerw/rtpng.h | 0 src/{ => liberty}/fakerw/rtquat.h | 0 src/liberty/fakerw/rwcore.h | 423 + src/{ => liberty}/fakerw/rwplcore.h | 0 src/{ => liberty}/math/Matrix.cpp | 23 +- src/{ => liberty}/math/Matrix.h | 0 src/{ => liberty}/math/Quaternion.cpp | 0 src/{ => liberty}/math/Quaternion.h | 0 src/{ => liberty}/math/Rect.cpp | 0 src/{ => liberty}/math/Rect.h | 0 src/{ => liberty}/math/Vector.cpp | 0 src/{ => liberty}/math/Vector.h | 0 src/{ => liberty}/math/Vector2D.h | 0 src/{ => liberty}/math/VuVector.h | 0 src/{ => liberty}/math/math.cpp | 0 src/{ => liberty}/math/maths.h | 2 +- src/{ => liberty}/modelinfo/BaseModelInfo.cpp | 0 src/{ => liberty}/modelinfo/BaseModelInfo.h | 0 .../modelinfo/ClumpModelInfo.cpp | 0 src/{ => liberty}/modelinfo/ClumpModelInfo.h | 0 src/{ => liberty}/modelinfo/MloModelInfo.cpp | 0 src/{ => liberty}/modelinfo/MloModelInfo.h | 0 src/{ => liberty}/modelinfo/ModelIndices.cpp | 0 src/{ => liberty}/modelinfo/ModelIndices.h | 0 src/{ => liberty}/modelinfo/ModelInfo.cpp | 0 src/{ => liberty}/modelinfo/ModelInfo.h | 0 src/{ => liberty}/modelinfo/PedModelInfo.cpp | 0 src/{ => liberty}/modelinfo/PedModelInfo.h | 0 .../modelinfo/SimpleModelInfo.cpp | 0 src/{ => liberty}/modelinfo/SimpleModelInfo.h | 0 src/{ => liberty}/modelinfo/TimeModelInfo.cpp | 0 src/{ => liberty}/modelinfo/TimeModelInfo.h | 0 .../modelinfo/VehicleModelInfo.cpp | 0 .../modelinfo/VehicleModelInfo.h | 0 .../modelinfo/XtraCompsModelInfo.h | 0 src/{ => liberty}/objects/CutsceneHead.cpp | 0 src/{ => liberty}/objects/CutsceneHead.h | 0 src/{ => liberty}/objects/CutsceneObject.cpp | 0 src/{ => liberty}/objects/CutsceneObject.h | 0 src/{ => liberty}/objects/DummyObject.cpp | 0 src/{ => liberty}/objects/DummyObject.h | 0 src/{ => liberty}/objects/Object.cpp | 0 src/{ => liberty}/objects/Object.h | 0 src/{ => liberty}/objects/ObjectData.cpp | 0 src/{ => liberty}/objects/ObjectData.h | 0 src/{ => liberty}/objects/ParticleObject.cpp | 0 src/{ => liberty}/objects/ParticleObject.h | 0 src/{ => liberty}/objects/Projectile.cpp | 0 src/{ => liberty}/objects/Projectile.h | 0 src/{ => liberty}/peds/CivilianPed.cpp | 0 src/{ => liberty}/peds/CivilianPed.h | 0 src/{ => liberty}/peds/CopPed.cpp | 0 src/{ => liberty}/peds/CopPed.h | 0 src/{ => liberty}/peds/DummyPed.h | 0 src/{ => liberty}/peds/EmergencyPed.cpp | 0 src/{ => liberty}/peds/EmergencyPed.h | 0 src/{ => liberty}/peds/Gangs.cpp | 0 src/{ => liberty}/peds/Gangs.h | 0 src/{ => liberty}/peds/Ped.cpp | 0 src/{ => liberty}/peds/Ped.h | 0 src/{ => liberty}/peds/PedAI.cpp | 12 +- src/{ => liberty}/peds/PedChat.cpp | 0 src/{ => liberty}/peds/PedDebug.cpp | 0 src/{ => liberty}/peds/PedFight.cpp | 0 src/{ => liberty}/peds/PedIK.cpp | 0 src/{ => liberty}/peds/PedIK.h | 0 src/{ => liberty}/peds/PedPlacement.cpp | 0 src/{ => liberty}/peds/PedPlacement.h | 0 src/{ => liberty}/peds/PedRoutes.cpp | 0 src/{ => liberty}/peds/PedRoutes.h | 0 src/{ => liberty}/peds/PedType.cpp | 0 src/{ => liberty}/peds/PedType.h | 0 src/{ => liberty}/peds/PlayerPed.cpp | 0 src/{ => liberty}/peds/PlayerPed.h | 0 src/{ => liberty}/peds/Population.cpp | 0 src/{ => liberty}/peds/Population.h | 0 src/{ => liberty}/renderer/2dEffect.h | 0 src/{ => liberty}/renderer/Antennas.cpp | 0 src/{ => liberty}/renderer/Antennas.h | 0 src/{ => liberty}/renderer/Clouds.cpp | 2 +- src/{ => liberty}/renderer/Clouds.h | 0 src/{ => liberty}/renderer/Console.cpp | 0 src/{ => liberty}/renderer/Console.h | 0 src/{ => liberty}/renderer/Coronas.cpp | 0 src/{ => liberty}/renderer/Coronas.h | 0 src/{ => liberty}/renderer/Credits.cpp | 0 src/{ => liberty}/renderer/Credits.h | 0 src/{ => liberty}/renderer/Draw.cpp | 0 src/{ => liberty}/renderer/Draw.h | 0 src/{ => liberty}/renderer/Fluff.cpp | 0 src/{ => liberty}/renderer/Fluff.h | 0 src/{ => liberty}/renderer/Font.cpp | 0 src/{ => liberty}/renderer/Font.h | 0 src/{ => liberty}/renderer/Glass.cpp | 0 src/{ => liberty}/renderer/Glass.h | 0 src/{ => liberty}/renderer/Hud.cpp | 0 src/{ => liberty}/renderer/Hud.h | 0 src/{ => liberty}/renderer/Instance.cpp | 0 src/{ => liberty}/renderer/Instance.h | 0 src/{ => liberty}/renderer/Lines.cpp | 0 src/{ => liberty}/renderer/Lines.h | 0 src/{ => liberty}/renderer/MBlur.cpp | 0 src/{ => liberty}/renderer/MBlur.h | 0 src/{ => liberty}/renderer/Particle.cpp | 0 src/{ => liberty}/renderer/Particle.h | 0 src/{ => liberty}/renderer/ParticleMgr.cpp | 0 src/{ => liberty}/renderer/ParticleMgr.h | 0 src/{ => liberty}/renderer/ParticleType.h | 0 src/{ => liberty}/renderer/PlayerSkin.cpp | 0 src/{ => liberty}/renderer/PlayerSkin.h | 0 src/{ => liberty}/renderer/PointLights.cpp | 0 src/{ => liberty}/renderer/PointLights.h | 0 src/{ => liberty}/renderer/RenderBuffer.cpp | 0 src/{ => liberty}/renderer/RenderBuffer.h | 0 src/{ => liberty}/renderer/Renderer.cpp | 0 src/{ => liberty}/renderer/Renderer.h | 0 src/{ => liberty}/renderer/Rubbish.cpp | 0 src/{ => liberty}/renderer/Rubbish.h | 0 src/{ => liberty}/renderer/Shadows.cpp | 0 src/{ => liberty}/renderer/Shadows.h | 0 src/{ => liberty}/renderer/Skidmarks.cpp | 0 src/{ => liberty}/renderer/Skidmarks.h | 0 src/{ => liberty}/renderer/SpecialFX.cpp | 0 src/{ => liberty}/renderer/SpecialFX.h | 0 src/{ => liberty}/renderer/Sprite.cpp | 0 src/{ => liberty}/renderer/Sprite.h | 0 src/{ => liberty}/renderer/Sprite2d.cpp | 11 +- src/{ => liberty}/renderer/Sprite2d.h | 0 src/{ => liberty}/renderer/TexList.cpp | 0 src/{ => liberty}/renderer/TexList.h | 0 src/{ => liberty}/renderer/Timecycle.cpp | 0 src/{ => liberty}/renderer/Timecycle.h | 0 src/{ => liberty}/renderer/WaterCannon.cpp | 0 src/{ => liberty}/renderer/WaterCannon.h | 0 src/{ => liberty}/renderer/WaterLevel.cpp | 3 +- src/{ => liberty}/renderer/WaterLevel.h | 0 src/{ => liberty}/renderer/Weather.cpp | 0 src/{ => liberty}/renderer/Weather.h | 0 src/{ => liberty}/rw/ClumpRead.cpp | 0 src/{ => liberty}/rw/Lights.cpp | 0 src/{ => liberty}/rw/Lights.h | 0 src/{ => liberty}/rw/MemoryHeap.cpp | 0 src/{ => liberty}/rw/MemoryHeap.h | 0 src/{ => liberty}/rw/MemoryMgr.cpp | 0 src/{ => liberty}/rw/MemoryMgr.h | 0 src/{ => liberty}/rw/NodeName.cpp | 0 src/{ => liberty}/rw/NodeName.h | 0 src/{ => liberty}/rw/RwHelper.cpp | 0 src/{ => liberty}/rw/RwHelper.h | 0 src/{ => liberty}/rw/RwMatFX.cpp | 0 src/{ => liberty}/rw/RwPS2AlphaTest.cpp | 0 src/{ => liberty}/rw/TexRead.cpp | 0 src/{ => liberty}/rw/TexturePools.cpp | 0 src/{ => liberty}/rw/TexturePools.h | 0 src/{ => liberty}/rw/TxdStore.cpp | 0 src/{ => liberty}/rw/TxdStore.h | 0 src/{ => liberty}/rw/VisibilityPlugins.cpp | 0 src/{ => liberty}/rw/VisibilityPlugins.h | 0 src/{ => liberty}/save/Date.cpp | 0 src/{ => liberty}/save/Date.h | 0 src/{ => liberty}/save/GenericGameStorage.cpp | 44 +- src/{ => liberty}/save/GenericGameStorage.h | 0 src/{ => liberty}/save/MemoryCard.cpp | 0 src/{ => liberty}/save/MemoryCard.h | 0 src/{ => liberty}/save/PCSave.cpp | 47 +- src/{ => liberty}/save/PCSave.h | 0 src/{ => liberty}/save/SaveBuf.h | 0 src/{ => liberty}/skel/crossplatform.cpp | 10 +- src/{ => liberty}/skel/crossplatform.h | 0 src/{ => liberty}/skel/dc/dc.cpp | 63 +- src/{ => liberty}/skel/dc/dc.h | 0 src/{ => liberty}/skel/events.cpp | 0 src/{ => liberty}/skel/events.h | 0 src/{ => liberty}/skel/glfw/glfw.cpp | 0 src/{ => liberty}/skel/platform.h | 0 src/{ => liberty}/skel/skeleton.cpp | 0 src/{ => liberty}/skel/skeleton.h | 0 src/{ => liberty}/skel/win/gta3.ico | Bin src/{ => liberty}/skel/win/resource.h | 0 src/{ => liberty}/skel/win/win.cpp | 0 src/{ => liberty}/skel/win/win.h | 0 src/{ => liberty}/skel/win/win.rc | 0 src/{ => liberty}/text/Messages.cpp | 0 src/{ => liberty}/text/Messages.h | 0 src/{ => liberty}/text/Pager.cpp | 0 src/{ => liberty}/text/Pager.h | 0 src/{ => liberty}/text/Text.cpp | 0 src/{ => liberty}/text/Text.h | 0 src/{ => liberty}/vehicles/Automobile.cpp | 0 src/{ => liberty}/vehicles/Automobile.h | 0 src/{ => liberty}/vehicles/Bike.h | 0 src/{ => liberty}/vehicles/Boat.cpp | 0 src/{ => liberty}/vehicles/Boat.h | 0 src/{ => liberty}/vehicles/CarGen.cpp | 0 src/{ => liberty}/vehicles/CarGen.h | 0 src/{ => liberty}/vehicles/Cranes.cpp | 0 src/{ => liberty}/vehicles/Cranes.h | 0 src/{ => liberty}/vehicles/DamageManager.cpp | 0 src/{ => liberty}/vehicles/DamageManager.h | 0 src/{ => liberty}/vehicles/Door.cpp | 0 src/{ => liberty}/vehicles/Door.h | 0 src/{ => liberty}/vehicles/Floater.cpp | 0 src/{ => liberty}/vehicles/Floater.h | 0 src/{ => liberty}/vehicles/HandlingMgr.cpp | 0 src/{ => liberty}/vehicles/HandlingMgr.h | 0 src/{ => liberty}/vehicles/Heli.cpp | 0 src/{ => liberty}/vehicles/Heli.h | 0 src/{ => liberty}/vehicles/Plane.cpp | 0 src/{ => liberty}/vehicles/Plane.h | 0 src/{ => liberty}/vehicles/Train.cpp | 0 src/{ => liberty}/vehicles/Train.h | 0 src/{ => liberty}/vehicles/Transmission.cpp | 0 src/{ => liberty}/vehicles/Transmission.h | 0 src/{ => liberty}/vehicles/Vehicle.cpp | 0 src/{ => liberty}/vehicles/Vehicle.h | 0 src/{ => liberty}/weapons/BulletInfo.cpp | 47 +- src/{ => liberty}/weapons/BulletInfo.h | 0 src/{ => liberty}/weapons/Explosion.cpp | 0 src/{ => liberty}/weapons/Explosion.h | 0 src/{ => liberty}/weapons/ProjectileInfo.cpp | 0 src/{ => liberty}/weapons/ProjectileInfo.h | 0 src/{ => liberty}/weapons/ShotInfo.cpp | 0 src/{ => liberty}/weapons/ShotInfo.h | 0 src/{ => liberty}/weapons/Weapon.cpp | 0 src/{ => liberty}/weapons/Weapon.h | 0 src/{ => liberty}/weapons/WeaponEffects.cpp | 0 src/{ => liberty}/weapons/WeaponEffects.h | 0 src/{ => liberty}/weapons/WeaponInfo.cpp | 0 src/{ => liberty}/weapons/WeaponInfo.h | 0 src/{ => liberty}/weapons/WeaponType.h | 0 src/miami/CMakeLists.txt | 170 + src/miami/animation/AnimBlendAssocGroup.cpp | 187 + src/miami/animation/AnimBlendAssocGroup.h | 24 + src/miami/animation/AnimBlendAssociation.cpp | 233 + src/miami/animation/AnimBlendAssociation.h | 93 + src/miami/animation/AnimBlendClumpData.cpp | 36 + src/miami/animation/AnimBlendClumpData.h | 44 + .../animation/AnimBlendHierarchy.cpp | 21 +- src/miami/animation/AnimBlendHierarchy.h | 30 + src/miami/animation/AnimBlendList.h | 28 + src/miami/animation/AnimBlendNode.cpp | 190 + src/miami/animation/AnimBlendNode.h | 32 + src/miami/animation/AnimBlendSequence.cpp | 46 + src/miami/animation/AnimBlendSequence.h | 398 + src/miami/animation/AnimManager.cpp | 1338 ++ src/miami/animation/AnimManager.h | 143 + src/miami/animation/AnimationId.h | 287 + src/miami/animation/Bones.cpp | 59 + src/miami/animation/Bones.h | 30 + src/miami/animation/CutsceneMgr.cpp | 669 + src/miami/animation/CutsceneMgr.h | 61 + src/miami/animation/FrameUpdate.cpp | 446 + src/miami/animation/RpAnimBlend.cpp | 518 + src/miami/animation/RpAnimBlend.h | 45 + src/miami/audio/AudioCollision.cpp | 405 + src/miami/audio/AudioCollision.h | 57 + src/miami/audio/AudioLogic.cpp | 10279 +++++++++++ src/miami/audio/AudioManager.cpp | 1434 ++ src/miami/audio/AudioManager.h | 649 + src/miami/audio/AudioSamples.h | 10130 ++++++++++ src/miami/audio/AudioScriptObject.cpp | 103 + src/miami/audio/AudioScriptObject.h | 26 + src/miami/audio/DMAudio.cpp | 408 + src/miami/audio/DMAudio.h | 107 + src/miami/audio/MusicManager.cpp | 1423 ++ src/miami/audio/MusicManager.h | 113 + src/miami/audio/PolRadio.cpp | 750 + src/miami/audio/PolRadio.h | 67 + src/miami/audio/audio_enums.h | 1336 ++ src/miami/audio/eax/eax-util.cpp | 706 + src/miami/audio/eax/eax-util.h | 765 + src/miami/audio/eax/eax.h | 536 + src/miami/audio/oal/aldlist.cpp | 291 + src/miami/audio/oal/aldlist.h | 82 + src/miami/audio/oal/channel.cpp | 295 + src/miami/audio/oal/channel.h | 55 + src/miami/audio/oal/oal_utils.cpp | 181 + src/miami/audio/oal/oal_utils.h | 54 + src/miami/audio/oal/stream.cpp | 1831 ++ src/miami/audio/oal/stream.h | 192 + src/miami/audio/sampman.h | 2730 +++ src/miami/audio/sampman_dc.cpp | 1479 ++ src/miami/audio/sampman_dc_streams.h | 1228 ++ src/miami/audio/sampman_miles.cpp | 2599 +++ src/miami/audio/sampman_null.cpp | 403 + src/miami/audio/sampman_oal.cpp | 2082 +++ src/miami/audio/soundlist.h | 282 + src/miami/buildings/Building.cpp | 44 + src/miami/buildings/Building.h | 20 + src/miami/buildings/Solid.h | 12 + src/miami/buildings/Treadable.cpp | 8 + src/miami/buildings/Treadable.h | 12 + src/miami/collision/ColBox.cpp | 21 + src/miami/collision/ColBox.h | 22 + src/miami/collision/ColLine.cpp | 9 + src/miami/collision/ColLine.h | 14 + src/miami/collision/ColModel.cpp | 206 + src/miami/collision/ColModel.h | 39 + src/miami/collision/ColPoint.cpp | 16 + src/miami/collision/ColPoint.h | 34 + src/miami/collision/ColSphere.cpp | 27 + src/miami/collision/ColSphere.h | 21 + src/miami/collision/ColStore.cpp | 255 + src/miami/collision/ColStore.h | 43 + src/miami/collision/ColTriangle.cpp | 32 + src/miami/collision/ColTriangle.h | 77 + src/miami/collision/Collision.cpp | 2590 +++ src/miami/collision/Collision.h | 68 + src/miami/collision/CompressedVector.h | 36 + src/miami/collision/TempColModels.cpp | 306 + src/miami/collision/TempColModels.h | 24 + src/miami/collision/VuCollision.cpp | 282 + src/miami/collision/VuCollision.h | 32 + src/miami/collision/vu0Collision.dsm | 21 + src/miami/collision/vu0Collision_1.s | 610 + src/miami/collision/vu0Collision_2.s | 191 + src/miami/control/AutoPilot.cpp | 136 + src/miami/control/AutoPilot.h | 137 + src/miami/control/Bridge.cpp | 163 + src/miami/control/Bridge.h | 28 + src/miami/control/CarAI.cpp | 802 + src/miami/control/CarAI.h | 31 + src/miami/control/CarCtrl.cpp | 3370 ++++ src/miami/control/CarCtrl.h | 174 + src/miami/control/Curves.cpp | 55 + src/miami/control/Curves.h | 9 + src/miami/control/Darkel.cpp | 439 + src/miami/control/Darkel.h | 55 + src/miami/control/GameLogic.cpp | 632 + src/miami/control/GameLogic.h | 51 + src/miami/control/Garages.cpp | 2495 +++ src/miami/control/Garages.h | 303 + src/miami/control/NameGrid.cpp | 87 + src/miami/control/NameGrid.h | 53 + src/miami/control/OnscreenTimer.cpp | 165 + src/miami/control/OnscreenTimer.h | 57 + src/miami/control/PathFind.cpp | 1989 ++ src/miami/control/PathFind.h | 304 + src/miami/control/Phones.cpp | 404 + src/miami/control/Phones.h | 69 + src/miami/control/Pickups.cpp | 1742 ++ src/miami/control/Pickups.h | 170 + src/miami/control/PowerPoints.cpp | 22 + src/miami/control/PowerPoints.h | 26 + src/miami/control/Record.cpp | 107 + src/miami/control/Record.h | 104 + src/miami/control/Remote.cpp | 58 + src/miami/control/Remote.h | 8 + src/miami/control/Replay.cpp | 1911 ++ src/miami/control/Replay.h | 417 + src/miami/control/Restart.cpp | 250 + src/miami/control/Restart.h | 36 + src/miami/control/RoadBlocks.cpp | 298 + src/miami/control/RoadBlocks.h | 30 + src/miami/control/SceneEdit.cpp | 1127 ++ src/miami/control/SceneEdit.h | 96 + src/miami/control/Script.cpp | 3265 ++++ src/miami/control/Script.h | 639 + src/miami/control/Script2.cpp | 1557 ++ src/miami/control/Script3.cpp | 2417 +++ src/miami/control/Script4.cpp | 2348 +++ src/miami/control/Script5.cpp | 2810 +++ src/miami/control/Script6.cpp | 1814 ++ src/miami/control/Script7.cpp | 1404 ++ src/miami/control/Script8.cpp | 618 + src/miami/control/ScriptCommands.h | 1508 ++ src/miami/control/ScriptDebug.cpp | 1765 ++ src/miami/control/SetPieces.cpp | 325 + src/miami/control/SetPieces.h | 48 + src/miami/control/TrafficLights.cpp | 536 + src/miami/control/TrafficLights.h | 32 + src/miami/core/Accident.cpp | 128 + src/miami/core/Accident.h | 31 + src/miami/core/AnimViewer.cpp | 437 + src/miami/core/AnimViewer.h | 12 + src/miami/core/Cam.cpp | 5422 ++++++ src/miami/core/Camera.cpp | 4143 +++++ src/miami/core/Camera.h | 645 + src/miami/core/CdStream.cpp | 538 + src/miami/core/CdStream.h | 59 + src/miami/core/CdStreamDC.cpp | 738 + src/miami/core/CdStreamPosix.cpp | 604 + src/miami/core/Clock.cpp | 136 + src/miami/core/Clock.h | 31 + src/miami/core/ControllerConfig.cpp | 2918 +++ src/miami/core/ControllerConfig.h | 229 + src/miami/core/Crime.h | 39 + src/miami/core/Debug.cpp | 170 + src/miami/core/Debug.h | 45 + src/miami/core/Directory.cpp | 74 + src/miami/core/Directory.h | 23 + src/miami/core/EventList.cpp | 251 + src/miami/core/EventList.h | 68 + src/miami/core/FileLoader.cpp | 1769 ++ src/miami/core/FileLoader.h | 48 + src/miami/core/FileMgr.cpp | 313 + src/miami/core/FileMgr.h | 23 + src/miami/core/Fire.cpp | 500 + src/miami/core/Fire.h | 53 + src/miami/core/FrontEndControls.cpp | 1887 ++ src/miami/core/FrontEndControls.h | 750 + src/miami/core/Frontend.cpp | 6755 +++++++ src/miami/core/Frontend.h | 874 + src/miami/core/FrontendTriggers.h | 1393 ++ src/miami/core/Frontend_PS2.cpp | 3020 +++ src/miami/core/Frontend_PS2.h | 247 + src/miami/core/Game.cpp | 1352 ++ src/miami/core/Game.h | 81 + src/miami/core/General.h | 180 + src/miami/core/IniFile.cpp | 29 + src/miami/core/IniFile.h | 14 + src/miami/core/Lists.cpp | 26 + src/miami/core/Lists.h | 130 + src/miami/core/MenuScreens.cpp | 353 + src/miami/core/MenuScreensCustom.cpp | 819 + src/miami/core/Pad.cpp | 4752 +++++ src/miami/core/Pad.h | 569 + src/miami/core/Placeable.cpp | 62 + src/miami/core/Placeable.h | 36 + src/miami/core/PlayerInfo.cpp | 880 + src/miami/core/PlayerInfo.h | 124 + src/miami/core/Pools.cpp | 647 + src/miami/core/Pools.h | 64 + src/miami/core/Profile.cpp | 71 + src/miami/core/Profile.h | 28 + src/miami/core/Radar.cpp | 1831 ++ src/miami/core/Radar.h | 294 + src/miami/core/Range2D.cpp | 28 + src/miami/core/Range2D.h | 11 + src/miami/core/Range3D.cpp | 30 + src/miami/core/Range3D.h | 11 + src/miami/core/References.cpp | 143 + src/miami/core/References.h | 20 + src/miami/core/Ropes.cpp | 176 + src/miami/core/Ropes.h | 31 + src/miami/core/Stats.cpp | 1443 ++ src/miami/core/Stats.h | 152 + src/miami/core/Streaming.cpp | 3278 ++++ src/miami/core/Streaming.h | 221 + src/miami/core/SurfaceTable.cpp | 156 + src/miami/core/SurfaceTable.h | 101 + src/miami/core/TimeStep.cpp | 5 + src/miami/core/TimeStep.h | 10 + src/miami/core/Timer.cpp | 331 + src/miami/core/Timer.h | 71 + src/miami/core/User.cpp | 135 + src/miami/core/User.h | 42 + src/miami/core/Wanted.cpp | 510 + src/miami/core/Wanted.h | 65 + src/miami/core/World.cpp | 2312 +++ src/miami/core/World.h | 176 + src/miami/core/ZoneCull.cpp | 172 + src/miami/core/ZoneCull.h | 65 + src/miami/core/Zones.cpp | 800 + src/miami/core/Zones.h | 114 + src/miami/core/common.h | 420 + src/miami/core/config.h | 528 + src/miami/core/main.cpp | 2447 +++ src/miami/core/main.h | 76 + src/miami/core/obrstr.cpp | 119 + src/miami/core/obrstr.h | 9 + src/miami/core/re3.cpp | 1329 ++ src/miami/core/templates.h | 270 + src/miami/core/timebars.cpp | 121 + src/miami/core/timebars.h | 13 + src/miami/entities/Dummy.cpp | 67 + src/miami/entities/Dummy.h | 19 + src/miami/entities/Entity.cpp | 906 + src/miami/entities/Entity.h | 184 + src/miami/entities/Physical.cpp | 2295 +++ src/miami/entities/Physical.h | 175 + src/miami/extras/GitSHA1.cpp.in | 2 + src/miami/extras/GitSHA1.h | 1 + src/miami/extras/arrow.inc | 16 + src/miami/extras/cursor.inc | 16 + src/miami/extras/custompipes.cpp | 543 + src/miami/extras/custompipes.h | 145 + src/miami/extras/custompipes_d3d9.cpp | 724 + src/miami/extras/custompipes_gl.cpp | 738 + src/miami/extras/debugmenu.cpp | 1312 ++ src/miami/extras/debugmenu.h | 204 + src/miami/extras/frontendoption.cpp | 170 + src/miami/extras/frontendoption.h | 88 + src/{ => miami}/extras/ini.h | 94 +- src/miami/extras/postfx.cpp | 494 + src/miami/extras/postfx.h | 46 + src/miami/extras/re3_inttypes.h | 216 + src/miami/extras/screendroplets.cpp | 817 + src/miami/extras/screendroplets.h | 78 + src/miami/extras/shaders/colourfilterVC.frag | 27 + .../extras/shaders/colourfilterVC_PS.hlsl | 23 + src/miami/extras/shaders/contrast.frag | 19 + src/miami/extras/shaders/contrastPS.hlsl | 21 + src/miami/extras/shaders/default_UV2.vert | 25 + src/miami/extras/shaders/default_UV2_VS.hlsl | 54 + src/miami/extras/shaders/im2d.vert | 19 + src/miami/extras/shaders/im2d_UV2.vert | 21 + src/miami/extras/shaders/lighting.h | 44 + src/miami/extras/shaders/make_glsl.sh | 9 + src/miami/extras/shaders/make_hlsl.cmd | 3 + src/miami/extras/shaders/makeinc_glsl.sh | 6 + src/miami/extras/shaders/makeinc_hlsl.sh | 6 + src/miami/extras/shaders/neoGloss.frag | 26 + src/miami/extras/shaders/neoGloss.vert | 25 + src/miami/extras/shaders/neoGloss_PS.hlsl | 20 + src/miami/extras/shaders/neoGloss_VS.hlsl | 35 + src/miami/extras/shaders/neoRim.vert | 34 + src/miami/extras/shaders/neoRimSkin.vert | 43 + src/miami/extras/shaders/neoRimSkin_VS.hlsl | 73 + src/miami/extras/shaders/neoRim_VS.hlsl | 61 + src/miami/extras/shaders/neoVehicle.frag | 29 + src/miami/extras/shaders/neoVehicle.vert | 51 + src/miami/extras/shaders/neoVehicle_PS.hlsl | 34 + src/miami/extras/shaders/neoVehicle_VS.hlsl | 64 + src/miami/extras/shaders/neoWorldVC.frag | 26 + src/miami/extras/shaders/neoWorldVC_PS.hlsl | 25 + src/miami/extras/shaders/screenDroplet.frag | 18 + .../extras/shaders/screenDroplet_PS.hlsl | 17 + src/miami/extras/shaders/simple.frag | 17 + src/miami/extras/shaders/standardConstants.h | 28 + src/miami/fakerw/fake.cpp | 1014 + src/miami/fakerw/rpanisot.h | 6 + src/miami/fakerw/rphanim.h | 72 + src/miami/fakerw/rpmatfx.h | 43 + src/miami/fakerw/rpskin.h | 26 + src/miami/fakerw/rpworld.h | 336 + src/miami/fakerw/rtbmp.h | 4 + src/miami/fakerw/rtcharse.h | 14 + src/miami/fakerw/rtpng.h | 4 + src/miami/fakerw/rtquat.h | 15 + src/{ => miami}/fakerw/rwcore.h | 0 src/miami/fakerw/rwplcore.h | 498 + src/miami/math/Matrix.cpp | 531 + src/miami/math/Matrix.h | 154 + src/miami/math/Quaternion.cpp | 177 + src/miami/math/Quaternion.h | 95 + src/miami/math/Rect.cpp | 17 + src/miami/math/Rect.h | 71 + src/miami/math/Vector.cpp | 46 + src/miami/math/Vector.h | 129 + src/miami/math/Vector2D.h | 104 + src/miami/math/VuVector.h | 32 + src/miami/math/math.cpp | 118 + src/miami/math/maths.h | 19 + src/miami/modelinfo/BaseModelInfo.cpp | 103 + src/miami/modelinfo/BaseModelInfo.h | 74 + src/miami/modelinfo/ClumpModelInfo.cpp | 208 + src/miami/modelinfo/ClumpModelInfo.h | 60 + src/miami/modelinfo/MloModelInfo.cpp | 41 + src/miami/modelinfo/MloModelInfo.h | 14 + src/miami/modelinfo/ModelIndices.cpp | 34 + src/miami/modelinfo/ModelIndices.h | 621 + src/miami/modelinfo/ModelInfo.cpp | 258 + src/miami/modelinfo/ModelInfo.h | 52 + src/miami/modelinfo/PedModelInfo.cpp | 168 + src/miami/modelinfo/PedModelInfo.h | 52 + src/miami/modelinfo/SimpleModelInfo.cpp | 194 + src/miami/modelinfo/SimpleModelInfo.h | 64 + src/miami/modelinfo/TimeModelInfo.cpp | 33 + src/miami/modelinfo/TimeModelInfo.h | 20 + src/miami/modelinfo/VehicleModelInfo.cpp | 1193 ++ src/miami/modelinfo/VehicleModelInfo.h | 166 + src/miami/modelinfo/WeaponModelInfo.cpp | 53 + src/miami/modelinfo/WeaponModelInfo.h | 23 + src/miami/objects/CutsceneObject.cpp | 182 + src/miami/objects/CutsceneObject.h | 24 + src/miami/objects/DummyObject.cpp | 14 + src/miami/objects/DummyObject.h | 12 + src/miami/objects/Object.cpp | 858 + src/miami/objects/Object.h | 116 + src/miami/objects/ObjectData.cpp | 161 + src/miami/objects/ObjectData.h | 27 + src/miami/objects/ParticleObject.cpp | 1345 ++ src/miami/objects/ParticleObject.h | 112 + src/miami/objects/Projectile.cpp | 15 + src/miami/objects/Projectile.h | 9 + src/miami/objects/Stinger.cpp | 259 + src/miami/objects/Stinger.h | 40 + src/miami/peds/CivilianPed.cpp | 610 + src/miami/peds/CivilianPed.h | 26 + src/miami/peds/CopPed.cpp | 874 + src/miami/peds/CopPed.h | 51 + src/miami/peds/DummyPed.h | 10 + src/miami/peds/EmergencyPed.cpp | 412 + src/miami/peds/EmergencyPed.h | 39 + src/miami/peds/Gangs.cpp | 125 + src/miami/peds/Gangs.h | 57 + src/miami/peds/Ped.cpp | 9662 ++++++++++ src/miami/peds/Ped.h | 1192 ++ src/miami/peds/PedAI.cpp | 6926 +++++++ src/miami/peds/PedAttractor.cpp | 802 + src/miami/peds/PedAttractor.h | 196 + src/miami/peds/PedChat.cpp | 153 + src/miami/peds/PedDebug.cpp | 349 + src/miami/peds/PedFight.cpp | 4203 +++++ src/miami/peds/PedIK.cpp | 375 + src/miami/peds/PedIK.h | 66 + src/miami/peds/PedPlacement.cpp | 58 + src/miami/peds/PedPlacement.h | 8 + src/miami/peds/PedRoutes.cpp | 79 + src/miami/peds/PedRoutes.h | 15 + src/miami/peds/PedType.cpp | 317 + src/miami/peds/PedType.h | 178 + src/miami/peds/PlayerPed.cpp | 2267 +++ src/miami/peds/PlayerPed.h | 120 + src/miami/peds/Population.cpp | 1815 ++ src/miami/peds/Population.h | 88 + src/miami/prof/profiler.cpp | 376 + src/miami/prof/profiler.h | 27 + src/miami/renderer/2dEffect.h | 99 + src/miami/renderer/Antennas.cpp | 129 + src/miami/renderer/Antennas.h | 25 + src/miami/renderer/Clouds.cpp | 473 + src/miami/renderer/Clouds.h | 23 + src/miami/renderer/Console.cpp | 96 + src/miami/renderer/Console.h | 27 + src/miami/renderer/Coronas.cpp | 959 + src/miami/renderer/Coronas.h | 105 + src/miami/renderer/Credits.cpp | 820 + src/miami/renderer/Credits.h | 15 + src/miami/renderer/CutsceneShadow.cpp | 269 + src/miami/renderer/CutsceneShadow.h | 52 + src/miami/renderer/Draw.cpp | 113 + src/miami/renderer/Draw.h | 70 + src/miami/renderer/Fluff.cpp | 1364 ++ src/miami/renderer/Fluff.h | 205 + src/miami/renderer/Font.cpp | 1938 ++ src/miami/renderer/Font.h | 217 + src/miami/renderer/Glass.cpp | 1027 ++ src/miami/renderer/Glass.h | 59 + src/miami/renderer/Hud.cpp | 2091 +++ src/miami/renderer/Hud.h | 139 + src/miami/renderer/Instance.cpp | 9 + src/miami/renderer/Instance.h | 14 + src/miami/renderer/Lines.cpp | 74 + src/miami/renderer/Lines.h | 7 + src/miami/renderer/MBlur.cpp | 802 + src/miami/renderer/MBlur.h | 39 + src/miami/renderer/Occlusion.cpp | 530 + src/miami/renderer/Occlusion.h | 62 + src/miami/renderer/Particle.cpp | 2534 +++ src/miami/renderer/Particle.h | 108 + src/miami/renderer/ParticleMgr.cpp | 255 + src/miami/renderer/ParticleMgr.h | 138 + src/miami/renderer/ParticleType.h | 92 + src/miami/renderer/PlayerSkin.cpp | 166 + src/miami/renderer/PlayerSkin.h | 15 + src/miami/renderer/PointLights.cpp | 330 + src/miami/renderer/PointLights.h | 50 + src/miami/renderer/RenderBuffer.cpp | 52 + src/miami/renderer/RenderBuffer.h | 25 + src/miami/renderer/Renderer.cpp | 1687 ++ src/miami/renderer/Renderer.h | 105 + src/miami/renderer/Rubbish.cpp | 423 + src/miami/renderer/Rubbish.h | 57 + src/miami/renderer/ShadowCamera.cpp | 549 + src/miami/renderer/ShadowCamera.h | 54 + src/miami/renderer/Shadows.cpp | 2515 +++ src/miami/renderer/Shadows.h | 210 + src/miami/renderer/Skidmarks.cpp | 259 + src/miami/renderer/Skidmarks.h | 41 + src/miami/renderer/SpecialFX.cpp | 1493 ++ src/miami/renderer/SpecialFX.h | 249 + src/miami/renderer/Sprite.cpp | 603 + src/miami/renderer/Sprite.h | 31 + src/miami/renderer/Sprite2d.cpp | 470 + src/miami/renderer/Sprite2d.h | 64 + src/miami/renderer/TexList.cpp | 41 + src/miami/renderer/TexList.h | 14 + src/miami/renderer/Timecycle.cpp | 526 + src/miami/renderer/Timecycle.h | 201 + src/miami/renderer/VarConsole.cpp | 786 + src/miami/renderer/VarConsole.h | 92 + src/miami/renderer/WaterCannon.cpp | 317 + src/miami/renderer/WaterCannon.h | 39 + src/miami/renderer/WaterCreatures.cpp | 275 + src/miami/renderer/WaterCreatures.h | 49 + src/miami/renderer/WaterLevel.cpp | 3382 ++++ src/miami/renderer/WaterLevel.h | 184 + src/miami/renderer/Weather.cpp | 662 + src/miami/renderer/Weather.h | 73 + src/miami/renderer/WindModifiers.cpp | 52 + src/miami/renderer/WindModifiers.h | 11 + src/miami/rw/ClumpRead.cpp | 223 + src/miami/rw/Lights.cpp | 389 + src/miami/rw/Lights.h | 27 + src/miami/rw/MemoryHeap.cpp | 499 + src/miami/rw/MemoryHeap.h | 204 + src/miami/rw/MemoryMgr.cpp | 130 + src/miami/rw/MemoryMgr.h | 12 + src/miami/rw/NodeName.cpp | 77 + src/miami/rw/NodeName.h | 4 + src/miami/rw/RwHelper.cpp | 829 + src/miami/rw/RwHelper.h | 65 + src/miami/rw/RwMatFX.cpp | 312 + src/miami/rw/RwPS2AlphaTest.cpp | 247 + src/miami/rw/TexRead.cpp | 515 + src/miami/rw/TexturePools.cpp | 221 + src/miami/rw/TexturePools.h | 42 + src/miami/rw/TxdStore.cpp | 211 + src/miami/rw/TxdStore.h | 44 + src/miami/rw/VisibilityPlugins.cpp | 1045 ++ src/miami/rw/VisibilityPlugins.h | 152 + src/miami/save/Date.cpp | 91 + src/miami/save/Date.h | 18 + src/miami/save/GenericGameStorage.cpp | 1273 ++ src/miami/save/GenericGameStorage.h | 66 + src/miami/save/MemoryCard.cpp | 3084 ++++ src/miami/save/MemoryCard.h | 197 + src/miami/save/PCSave.cpp | 252 + src/miami/save/PCSave.h | 41 + src/miami/save/SaveBuf.h | 123 + src/miami/skel/crossplatform.cpp | 453 + src/miami/skel/crossplatform.h | 207 + src/miami/skel/dc/dc.cpp | 2879 +++ src/miami/skel/dc/dc.h | 7 + src/miami/skel/events.cpp | 831 + src/miami/skel/events.h | 7 + src/miami/skel/glfw/glfw.cpp | 2609 +++ src/miami/skel/platform.h | 61 + src/miami/skel/skeleton.cpp | 428 + src/miami/skel/skeleton.h | 290 + src/miami/skel/win/gtavc.ico | Bin 0 -> 81817 bytes src/miami/skel/win/resource.h | 21 + src/miami/skel/win/win.cpp | 3462 ++++ src/miami/skel/win/win.h | 91 + src/miami/skel/win/win.rc | 47 + src/miami/text/Messages.cpp | 815 + src/miami/text/Messages.h | 69 + src/miami/text/Pager.cpp | 184 + src/miami/text/Pager.h | 28 + src/miami/text/Text.cpp | 530 + src/miami/text/Text.h | 99 + src/miami/vehicles/Automobile.cpp | 5900 ++++++ src/miami/vehicles/Automobile.h | 175 + src/miami/vehicles/Bike.cpp | 2962 +++ src/miami/vehicles/Bike.h | 172 + src/miami/vehicles/Boat.cpp | 1489 ++ src/miami/vehicles/Boat.h | 85 + src/miami/vehicles/CarGen.cpp | 309 + src/miami/vehicles/CarGen.h | 51 + src/miami/vehicles/Cranes.cpp | 740 + src/miami/vehicles/Cranes.h | 96 + src/miami/vehicles/DamageManager.cpp | 233 + src/miami/vehicles/DamageManager.h | 115 + src/miami/vehicles/Door.cpp | 170 + src/miami/vehicles/Door.h | 69 + src/miami/vehicles/Floater.cpp | 324 + src/miami/vehicles/Floater.h | 47 + src/miami/vehicles/HandlingMgr.cpp | 434 + src/miami/vehicles/HandlingMgr.h | 279 + src/miami/vehicles/Heli.cpp | 1049 ++ src/miami/vehicles/Heli.h | 101 + src/miami/vehicles/Plane.cpp | 1069 ++ src/miami/vehicles/Plane.h | 81 + src/miami/vehicles/Train.cpp | 766 + src/miami/vehicles/Train.h | 84 + src/miami/vehicles/Transmission.cpp | 130 + src/miami/vehicles/Transmission.h | 28 + src/miami/vehicles/Vehicle.cpp | 2518 +++ src/miami/vehicles/Vehicle.h | 451 + src/miami/weapons/BulletInfo.cpp | 341 + src/miami/weapons/BulletInfo.h | 25 + src/miami/weapons/Explosion.cpp | 514 + src/miami/weapons/Explosion.h | 58 + src/miami/weapons/ProjectileInfo.cpp | 437 + src/miami/weapons/ProjectileInfo.h | 34 + src/miami/weapons/ShotInfo.cpp | 149 + src/miami/weapons/ShotInfo.h | 24 + src/miami/weapons/Weapon.cpp | 3384 ++++ src/miami/weapons/Weapon.h | 90 + src/miami/weapons/WeaponEffects.cpp | 133 + src/miami/weapons/WeaponEffects.h | 26 + src/miami/weapons/WeaponInfo.cpp | 295 + src/miami/weapons/WeaponInfo.h | 74 + src/miami/weapons/WeaponType.h | 86 + src/tools/adf2mp3.cpp | 50 + {dreamcast => src/tools}/analyze-profile.cpp | 0 src/tools/animtool.cpp | 1029 ++ {dreamcast => src/tools}/aud2adpcm.c | 5 +- src/tools/coltool.cpp | 162 + {dreamcast => src/tools}/extract-sfx.cpp | 0 {dreamcast => src/tools}/imgtool.cpp | 8 +- {dreamcast => src/tools}/pack-sfx.cpp | 0 src/tools/streamheaderpack.cpp | 236 + {dreamcast => src/tools}/texconv.cpp | 79 +- vendor/dc-load-ser/loader.elf | Bin 0 -> 17280 bytes vendor/dca3-kos | 2 +- vendor/emu/refsw/refsw_lists.cpp | 11 +- vendor/koshle/hlekos.cpp | 4 - vendor/koshle/hlematrix3d.cpp | 2 +- vendor/koshle/hlepvr_scene.cpp | 13 +- vendor/librw/src/camera.cpp | 44 + .../librw/src}/common_defines.h | 0 vendor/librw/src/dc/rwdc.cpp | 667 +- vendor/librw/src/dc/rwdc.h | 2 +- vendor/librw/src/engine.cpp | 9 +- {src/skel => vendor/librw/src}/fcaseopen.h | 0 {src/math => vendor/librw/src}/float16.h | 0 vendor/librw/src/rwobjects.h | 6 +- vendor/librw/src/texture.cpp | 6 +- {dreamcast => vendor}/pvrtex/.gitignore | 0 {dreamcast => vendor}/pvrtex/Makefile | 0 {dreamcast => vendor}/pvrtex/README | 0 {dreamcast => vendor}/pvrtex/README.md | 0 {dreamcast => vendor}/pvrtex/avstring.c | 0 {dreamcast => vendor}/pvrtex/bprint.c | 0 {dreamcast => vendor}/pvrtex/compat/va_copy.h | 0 {dreamcast => vendor}/pvrtex/config.h | 0 {dreamcast => vendor}/pvrtex/crc.c | 0 {dreamcast => vendor}/pvrtex/dither.cpp | 0 {dreamcast => vendor}/pvrtex/elbg.c | 0 {dreamcast => vendor}/pvrtex/elbg.h | 0 {dreamcast => vendor}/pvrtex/file_common.c | 0 {dreamcast => vendor}/pvrtex/file_common.h | 0 {dreamcast => vendor}/pvrtex/file_dctex.c | 0 {dreamcast => vendor}/pvrtex/file_dctex.h | 0 {dreamcast => vendor}/pvrtex/file_pvr.c | 0 {dreamcast => vendor}/pvrtex/file_pvr.h | 0 {dreamcast => vendor}/pvrtex/file_tex.c | 0 {dreamcast => vendor}/pvrtex/file_tex.h | 0 {dreamcast => vendor}/pvrtex/lfg.c | 0 .../pvrtex/libavcodec/elbg.h | 0 .../pvrtex/libavutil/attributes.h | 0 .../pvrtex/libavutil/attributes_internal.h | 0 .../pvrtex/libavutil/avassert.h | 0 .../pvrtex/libavutil/avconfig.h | 0 .../pvrtex/libavutil/avstring.h | 0 .../pvrtex/libavutil/avutil.h | 0 .../pvrtex/libavutil/bprint.h | 0 .../pvrtex/libavutil/bswap.h | 0 .../pvrtex/libavutil/common.h | 0 {dreamcast => vendor}/pvrtex/libavutil/crc.h | 0 .../pvrtex/libavutil/dynarray.h | 0 .../pvrtex/libavutil/error.h | 0 .../pvrtex/libavutil/internal.h | 0 .../pvrtex/libavutil/intfloat.h | 0 .../pvrtex/libavutil/intreadwrite.h | 0 {dreamcast => vendor}/pvrtex/libavutil/lfg.c | 0 {dreamcast => vendor}/pvrtex/libavutil/lfg.h | 0 {dreamcast => vendor}/pvrtex/libavutil/libm.h | 0 {dreamcast => vendor}/pvrtex/libavutil/log.h | 0 .../pvrtex/libavutil/macros.h | 0 .../pvrtex/libavutil/mathematics.c | 0 .../pvrtex/libavutil/mathematics.h | 0 {dreamcast => vendor}/pvrtex/libavutil/md5.h | 0 {dreamcast => vendor}/pvrtex/libavutil/mem.c | 0 {dreamcast => vendor}/pvrtex/libavutil/mem.h | 0 .../pvrtex/libavutil/pixfmt.h | 0 .../pvrtex/libavutil/rational.c | 0 .../pvrtex/libavutil/rational.h | 0 .../pvrtex/libavutil/thread.h | 0 .../pvrtex/libavutil/version.h | 0 {dreamcast => vendor}/pvrtex/log.c | 0 {dreamcast => vendor}/pvrtex/main.c | 0 {dreamcast => vendor}/pvrtex/md5.c | 0 {dreamcast => vendor}/pvrtex/mem.c | 0 {dreamcast => vendor}/pvrtex/mycommon.c | 0 {dreamcast => vendor}/pvrtex/mycommon.h | 0 {dreamcast => vendor}/pvrtex/nvmath.h | 0 {dreamcast => vendor}/pvrtex/optparse.h | 0 {dreamcast => vendor}/pvrtex/optparse_impl.c | 0 {dreamcast => vendor}/pvrtex/pixel.h | 0 {dreamcast => vendor}/pvrtex/pvr_texture.c | 0 {dreamcast => vendor}/pvrtex/pvr_texture.h | 0 .../pvrtex/pvr_texture_encoder.c | 0 .../pvrtex/pvr_texture_encoder.h | 0 .../pvrtex/readme_unformatted.txt | 0 {dreamcast => vendor}/pvrtex/stb_image.h | 0 {dreamcast => vendor}/pvrtex/stb_image_impl.c | 0 .../pvrtex/stb_image_resize.h | 0 .../pvrtex/stb_image_resize_impl.c | 0 .../pvrtex/stb_image_write.h | 0 .../pvrtex/stb_image_write_impl.c | 0 {dreamcast => vendor}/pvrtex/tddither.c | 0 {dreamcast => vendor}/pvrtex/tddither.h | 0 {dreamcast => vendor}/pvrtex/vqcompress.c | 0 {dreamcast => vendor}/pvrtex/vqcompress.h | 0 1353 files changed, 386994 insertions(+), 19272 deletions(-) delete mode 100644 CMakeLists.txt delete mode 100644 CODING_STYLE.md delete mode 100644 autoconf/LICENSE.txt delete mode 100644 autoconf/api.lua delete mode 100644 autoconf/autoconf.lua delete mode 100644 autoconf/clang.lua delete mode 100644 autoconf/gcc.lua delete mode 100644 autoconf/msc.lua delete mode 100644 cmake/FindMilesSDK.cmake delete mode 100644 cmake/FindSndFile.cmake delete mode 100644 cmake/Findmpg123.cmake delete mode 100644 cmake/Findopusfile.cmake delete mode 100644 cmake/GetGitRevisionDescription.cmake delete mode 100644 cmake/GetGitRevisionDescription.cmake.in delete mode 100644 cmake/nx/NXFunctions.cmake delete mode 100644 codewarrior/Debug/gta3.txt delete mode 100644 codewarrior/Release/gta3.txt delete mode 100644 codewarrior/re3.mcp.xml delete mode 100644 conanfile.py delete mode 100644 dreamcast/common.mk delete mode 100644 dreamcast/gen-mesh-variants.py delete mode 100644 dreamcast/save.ico delete mode 100644 dreamcast/settings.ico rename gamefiles/{ => liberty}/TEXT/JAPANESE.gxt (100%) rename gamefiles/{ => liberty}/TEXT/american.gxt (100%) rename gamefiles/{ => liberty}/TEXT/english.gxt (100%) rename gamefiles/{ => liberty}/TEXT/french.gxt (100%) rename gamefiles/{ => liberty}/TEXT/german.gxt (100%) rename gamefiles/{ => liberty}/TEXT/italian.gxt (100%) rename gamefiles/{ => liberty}/TEXT/polish.gxt (100%) rename gamefiles/{ => liberty}/TEXT/russian.gxt (100%) rename gamefiles/{ => liberty}/TEXT/spanish.gxt (100%) rename gamefiles/{ => liberty}/data/PARTICLE.CFG (100%) rename gamefiles/{ => liberty}/data/main_d.scm (100%) rename gamefiles/{ => liberty}/data/main_freeroam.scm (100%) rename gamefiles/{ => liberty}/gamecontrollerdb.txt (100%) rename gamefiles/{ => liberty}/models/fonts_j.txd (100%) rename gamefiles/{ => liberty}/models/fonts_p.txd (100%) rename gamefiles/{ => liberty}/models/fonts_r.txd (100%) rename gamefiles/{ => liberty}/models/frontend_ds3.txd (100%) rename gamefiles/{ => liberty}/models/frontend_ds4.txd (100%) rename gamefiles/{ => liberty}/models/frontend_nsw.txd (100%) rename gamefiles/{ => liberty}/models/frontend_x360.txd (100%) rename gamefiles/{ => liberty}/models/frontend_xone.txd (100%) rename gamefiles/{ => liberty}/models/generic.txd (100%) rename gamefiles/{ => liberty}/models/menu.txd (100%) rename gamefiles/{ => liberty}/models/nswbtns.txd (100%) rename gamefiles/{ => liberty}/models/particle.txd (100%) rename gamefiles/{ => liberty}/models/ps3btns.txd (100%) rename gamefiles/{ => liberty}/models/x360btns.txd (100%) rename gamefiles/{ => liberty}/neo/carTweakingTable.dat (100%) rename gamefiles/{ => liberty}/neo/neo.txd (100%) rename gamefiles/{ => liberty}/neo/rimTweakingTable.dat (100%) rename gamefiles/{ => liberty}/neo/worldTweakingTable.dat (100%) create mode 100644 gamefiles/miami/TEXT/american.gxt create mode 100644 gamefiles/miami/TEXT/french.gxt create mode 100644 gamefiles/miami/TEXT/german.gxt create mode 100644 gamefiles/miami/TEXT/italian.gxt create mode 100644 gamefiles/miami/TEXT/russian.gxt create mode 100644 gamefiles/miami/TEXT/spanish.gxt create mode 100644 gamefiles/miami/data/freeroam_miami.scm create mode 100644 gamefiles/miami/gamecontrollerdb.txt create mode 100644 gamefiles/miami/models/fonts_r.txd create mode 100644 gamefiles/miami/models/frontend_ds2.txd create mode 100644 gamefiles/miami/models/frontend_ds3.txd create mode 100644 gamefiles/miami/models/frontend_ds4.txd create mode 100644 gamefiles/miami/models/frontend_nsw.txd create mode 100644 gamefiles/miami/models/frontend_x360.txd create mode 100644 gamefiles/miami/models/frontend_xone.txd create mode 100644 gamefiles/miami/models/generic.txd create mode 100644 gamefiles/miami/models/nswbtns.txd create mode 100644 gamefiles/miami/models/particle.txd create mode 100644 gamefiles/miami/models/ps3btns.txd create mode 100644 gamefiles/miami/models/x360btns.txd create mode 100644 gamefiles/miami/neo/carTweakingTable.dat create mode 100644 gamefiles/miami/neo/neo.txd create mode 100644 gamefiles/miami/neo/rimTweakingTable.dat create mode 100644 gamefiles/miami/neo/worldTweakingTable.dat rename {utils/gxt => gxt/liberty}/american.txt (100%) rename {utils/gxt => gxt/liberty}/build.bat (100%) rename {utils/gxt => gxt/liberty}/english.txt (100%) rename {utils/gxt => gxt/liberty}/french.txt (100%) rename {utils/gxt => gxt/liberty}/german.txt (100%) rename {utils/gxt => gxt/liberty}/gxt.exe (100%) rename {utils/gxt => gxt/liberty}/italian.txt (100%) rename {utils/gxt => gxt/liberty}/polish.txt (100%) mode change 100755 => 100644 rename {utils/gxt => gxt/liberty}/russian.txt (100%) rename {utils/gxt => gxt/liberty}/spanish.txt (100%) create mode 100644 gxt/miami/american.txt create mode 100644 gxt/miami/build.bat create mode 100644 gxt/miami/french.txt create mode 100644 gxt/miami/german.txt create mode 100644 gxt/miami/gxt.exe create mode 100644 gxt/miami/italian.txt create mode 100644 gxt/miami/spanish.txt rename {dreamcast => liberty}/Makefile (64%) rename {dreamcast => liberty/assets}/GTA3SF8.b (100%) create mode 100644 liberty/assets/dc_ps2d.png create mode 100644 liberty/assets/dc_ps2f.png create mode 100644 liberty/assets/dc_xboxd.png create mode 100644 liberty/assets/dc_xboxf.png create mode 100644 liberty/assets/ps4_d.png create mode 100644 liberty/assets/ps4_f.png create mode 100644 liberty/assets/xbox_d.png create mode 100644 liberty/assets/xbox_f.png create mode 100644 liberty/common.mk create mode 100644 liberty/cuts.mk rename {dreamcast => liberty}/gta3files.mk (99%) create mode 100644 liberty/ip.txt rename {dreamcast => liberty}/modlist.mk (100%) rename {dreamcast => liberty}/mp3list.mk (100%) rename {dreamcast => liberty}/sfxlist.mk (100%) rename {dreamcast => liberty}/sfxlooplist.mk (91%) rename {dreamcast => liberty}/sim.mk (57%) rename {dreamcast => liberty}/texlist.mk (100%) rename {dreamcast => liberty}/wavlist.mk (100%) create mode 100644 miami/GTAVCSF8.b create mode 100644 miami/Makefile create mode 100644 miami/common.mk create mode 100644 miami/cuts.mk create mode 100644 miami/gta3files.mk create mode 100644 miami/imgmisc.mk rename {dreamcast => miami}/ip.txt (65%) create mode 100644 miami/modlist.mk create mode 100644 miami/mp3list.mk create mode 100644 miami/sfxlist.mk create mode 100644 miami/sfxlooplist.mk create mode 100644 miami/sim.mk create mode 100644 miami/texlist.mk create mode 100644 miami/wavlist.mk delete mode 100644 premake-vs2015.cmd delete mode 100644 premake-vs2017.cmd delete mode 100644 premake-vs2019.cmd delete mode 100644 premake5.exe delete mode 100644 premake5.lua delete mode 100755 premake5Linux delete mode 100644 printHash.bat delete mode 100755 printHash.sh delete mode 100644 public/index.html delete mode 100644 src/animation/AnimBlendNode.cpp delete mode 100644 src/animation/AnimBlendSequence.h rename src/{ => common}/prof/profiler.cpp (100%) rename src/{ => common}/prof/profiler.h (100%) rename src/{ => common}/vmu/vmu.cpp (98%) rename src/{ => common}/vmu/vmu.h (100%) rename src/{ => liberty}/CMakeLists.txt (100%) rename src/{ => liberty}/animation/AnimBlendAssocGroup.cpp (100%) rename src/{ => liberty}/animation/AnimBlendAssocGroup.h (100%) rename src/{ => liberty}/animation/AnimBlendAssociation.cpp (98%) rename src/{ => liberty}/animation/AnimBlendAssociation.h (100%) rename src/{ => liberty}/animation/AnimBlendClumpData.cpp (100%) rename src/{ => liberty}/animation/AnimBlendClumpData.h (100%) create mode 100644 src/liberty/animation/AnimBlendHierarchy.cpp rename src/{ => liberty}/animation/AnimBlendHierarchy.h (100%) rename src/{ => liberty}/animation/AnimBlendList.h (100%) create mode 100644 src/liberty/animation/AnimBlendNode.cpp rename src/{ => liberty}/animation/AnimBlendNode.h (94%) rename src/{ => liberty}/animation/AnimBlendSequence.cpp (53%) create mode 100644 src/liberty/animation/AnimBlendSequence.h rename src/{ => liberty}/animation/AnimManager.cpp (87%) rename src/{ => liberty}/animation/AnimManager.h (100%) rename src/{ => liberty}/animation/AnimationId.h (100%) rename src/{ => liberty}/animation/Bones.cpp (100%) rename src/{ => liberty}/animation/Bones.h (100%) rename src/{ => liberty}/animation/CutsceneMgr.cpp (99%) rename src/{ => liberty}/animation/CutsceneMgr.h (100%) rename src/{ => liberty}/animation/FrameUpdate.cpp (100%) rename src/{ => liberty}/animation/RpAnimBlend.cpp (100%) rename src/{ => liberty}/animation/RpAnimBlend.h (100%) rename src/{ => liberty}/audio/AudioCollision.cpp (100%) rename src/{ => liberty}/audio/AudioCollision.h (100%) rename src/{ => liberty}/audio/AudioLogic.cpp (99%) rename src/{ => liberty}/audio/AudioManager.cpp (100%) rename src/{ => liberty}/audio/AudioManager.h (100%) rename src/{ => liberty}/audio/AudioSamples.h (100%) rename src/{ => liberty}/audio/AudioScriptObject.cpp (100%) rename src/{ => liberty}/audio/AudioScriptObject.h (100%) rename src/{ => liberty}/audio/DMAudio.cpp (100%) rename src/{ => liberty}/audio/DMAudio.h (100%) rename src/{ => liberty}/audio/MusicManager.cpp (100%) rename src/{ => liberty}/audio/MusicManager.h (100%) rename src/{ => liberty}/audio/PolRadio.cpp (100%) rename src/{ => liberty}/audio/PolRadio.h (100%) rename src/{ => liberty}/audio/audio_enums.h (100%) rename src/{ => liberty}/audio/eax/eax-util.cpp (100%) rename src/{ => liberty}/audio/eax/eax-util.h (100%) rename src/{ => liberty}/audio/eax/eax.h (100%) rename src/{ => liberty}/audio/oal/aldlist.cpp (100%) rename src/{ => liberty}/audio/oal/aldlist.h (100%) rename src/{ => liberty}/audio/oal/channel.cpp (100%) rename src/{ => liberty}/audio/oal/channel.h (100%) rename src/{ => liberty}/audio/oal/oal_utils.cpp (100%) rename src/{ => liberty}/audio/oal/oal_utils.h (100%) rename src/{ => liberty}/audio/oal/stream.cpp (100%) rename src/{ => liberty}/audio/oal/stream.h (100%) rename src/{ => liberty}/audio/sampman.h (83%) rename src/{ => liberty}/audio/sampman_dc.cpp (93%) create mode 100644 src/liberty/audio/sampman_dc_streams.h rename src/{ => liberty}/audio/sampman_miles.cpp (100%) rename src/{ => liberty}/audio/sampman_null.cpp (100%) rename src/{ => liberty}/audio/sampman_oal.cpp (100%) rename src/{ => liberty}/audio/soundlist.h (100%) rename src/{ => liberty}/buildings/Building.cpp (100%) rename src/{ => liberty}/buildings/Building.h (100%) rename src/{ => liberty}/buildings/Solid.h (100%) rename src/{ => liberty}/buildings/Treadable.cpp (100%) rename src/{ => liberty}/buildings/Treadable.h (100%) rename src/{ => liberty}/collision/ColBox.cpp (100%) rename src/{ => liberty}/collision/ColBox.h (100%) rename src/{ => liberty}/collision/ColLine.cpp (100%) rename src/{ => liberty}/collision/ColLine.h (100%) rename src/{ => liberty}/collision/ColModel.cpp (100%) rename src/{ => liberty}/collision/ColModel.h (100%) rename src/{ => liberty}/collision/ColPoint.cpp (100%) rename src/{ => liberty}/collision/ColPoint.h (100%) rename src/{ => liberty}/collision/ColSphere.cpp (100%) rename src/{ => liberty}/collision/ColSphere.h (100%) rename src/{ => liberty}/collision/ColTriangle.cpp (92%) rename src/{ => liberty}/collision/ColTriangle.h (94%) rename src/{ => liberty}/collision/Collision.cpp (100%) rename src/{ => liberty}/collision/Collision.h (100%) rename src/{ => liberty}/collision/CompressedVector.h (85%) rename src/{ => liberty}/collision/TempColModels.cpp (100%) rename src/{ => liberty}/collision/TempColModels.h (100%) rename src/{ => liberty}/collision/VuCollision.cpp (100%) rename src/{ => liberty}/collision/VuCollision.h (100%) rename src/{ => liberty}/collision/vu0Collision.dsm (100%) rename src/{ => liberty}/collision/vu0Collision_1.s (100%) rename src/{ => liberty}/collision/vu0Collision_2.s (100%) rename src/{ => liberty}/control/AutoPilot.cpp (100%) rename src/{ => liberty}/control/AutoPilot.h (100%) rename src/{ => liberty}/control/Bridge.cpp (100%) rename src/{ => liberty}/control/Bridge.h (100%) rename src/{ => liberty}/control/CarAI.cpp (100%) rename src/{ => liberty}/control/CarAI.h (100%) rename src/{ => liberty}/control/CarCtrl.cpp (100%) rename src/{ => liberty}/control/CarCtrl.h (100%) rename src/{ => liberty}/control/Curves.cpp (100%) rename src/{ => liberty}/control/Curves.h (100%) rename src/{ => liberty}/control/Darkel.cpp (100%) rename src/{ => liberty}/control/Darkel.h (100%) rename src/{ => liberty}/control/GameLogic.cpp (100%) rename src/{ => liberty}/control/GameLogic.h (100%) rename src/{ => liberty}/control/Garages.cpp (100%) rename src/{ => liberty}/control/Garages.h (100%) rename src/{ => liberty}/control/NameGrid.cpp (100%) rename src/{ => liberty}/control/NameGrid.h (100%) rename src/{ => liberty}/control/OnscreenTimer.cpp (100%) rename src/{ => liberty}/control/OnscreenTimer.h (100%) rename src/{ => liberty}/control/PathFind.cpp (100%) rename src/{ => liberty}/control/PathFind.h (100%) rename src/{ => liberty}/control/Phones.cpp (100%) rename src/{ => liberty}/control/Phones.h (100%) rename src/{ => liberty}/control/Pickups.cpp (100%) rename src/{ => liberty}/control/Pickups.h (100%) rename src/{ => liberty}/control/PowerPoints.cpp (100%) rename src/{ => liberty}/control/PowerPoints.h (100%) rename src/{ => liberty}/control/Record.cpp (100%) rename src/{ => liberty}/control/Record.h (100%) rename src/{ => liberty}/control/Remote.cpp (100%) rename src/{ => liberty}/control/Remote.h (100%) rename src/{ => liberty}/control/Replay.cpp (100%) rename src/{ => liberty}/control/Replay.h (100%) rename src/{ => liberty}/control/Restart.cpp (100%) rename src/{ => liberty}/control/Restart.h (100%) rename src/{ => liberty}/control/RoadBlocks.cpp (100%) rename src/{ => liberty}/control/RoadBlocks.h (100%) rename src/{ => liberty}/control/SceneEdit.cpp (100%) rename src/{ => liberty}/control/SceneEdit.h (100%) rename src/{ => liberty}/control/Script.cpp (100%) rename src/{ => liberty}/control/Script.h (100%) rename src/{ => liberty}/control/Script2.cpp (100%) rename src/{ => liberty}/control/Script3.cpp (100%) rename src/{ => liberty}/control/Script4.cpp (99%) rename src/{ => liberty}/control/Script5.cpp (100%) rename src/{ => liberty}/control/Script6.cpp (100%) rename src/{ => liberty}/control/ScriptCommands.h (100%) rename src/{ => liberty}/control/ScriptDebug.cpp (100%) rename src/{ => liberty}/control/TrafficLights.cpp (100%) rename src/{ => liberty}/control/TrafficLights.h (100%) rename src/{ => liberty}/core/Accident.cpp (100%) rename src/{ => liberty}/core/Accident.h (100%) rename src/{ => liberty}/core/AnimViewer.cpp (100%) rename src/{ => liberty}/core/AnimViewer.h (100%) rename src/{ => liberty}/core/Cam.cpp (100%) rename src/{ => liberty}/core/Camera.cpp (100%) rename src/{ => liberty}/core/Camera.h (100%) rename src/{ => liberty}/core/CdStream.cpp (100%) rename src/{ => liberty}/core/CdStream.h (86%) rename src/{ => liberty}/core/CdStreamDC.cpp (98%) rename src/{ => liberty}/core/CdStreamPosix.cpp (100%) rename src/{ => liberty}/core/Clock.cpp (100%) rename src/{ => liberty}/core/Clock.h (100%) rename src/{ => liberty}/core/ControllerConfig.cpp (87%) mode change 100644 => 100755 rename src/{ => liberty}/core/ControllerConfig.h (100%) rename src/{ => liberty}/core/Crime.h (100%) rename src/{ => liberty}/core/Debug.cpp (100%) rename src/{ => liberty}/core/Debug.h (100%) rename src/{ => liberty}/core/Directory.cpp (100%) rename src/{ => liberty}/core/Directory.h (100%) rename src/{ => liberty}/core/EventList.cpp (100%) rename src/{ => liberty}/core/EventList.h (100%) rename src/{ => liberty}/core/FileLoader.cpp (98%) rename src/{ => liberty}/core/FileLoader.h (100%) rename src/{ => liberty}/core/FileMgr.cpp (100%) rename src/{ => liberty}/core/FileMgr.h (100%) rename src/{ => liberty}/core/Fire.cpp (100%) rename src/{ => liberty}/core/Fire.h (100%) rename src/{ => liberty}/core/FrontEndControls.cpp (100%) rename src/{ => liberty}/core/FrontEndControls.h (100%) rename src/{ => liberty}/core/Frontend.cpp (90%) rename src/{ => liberty}/core/Frontend.h (99%) rename src/{ => liberty}/core/FrontendTriggers.h (100%) rename src/{ => liberty}/core/Frontend_PS2.cpp (100%) rename src/{ => liberty}/core/Frontend_PS2.h (100%) rename src/{ => liberty}/core/Game.cpp (100%) rename src/{ => liberty}/core/Game.h (100%) rename src/{ => liberty}/core/General.h (100%) rename src/{ => liberty}/core/IniFile.cpp (100%) rename src/{ => liberty}/core/IniFile.h (100%) rename src/{ => liberty}/core/Lists.cpp (100%) rename src/{ => liberty}/core/Lists.h (100%) rename src/{ => liberty}/core/MenuScreens.cpp (100%) rename src/{ => liberty}/core/MenuScreensCustom.cpp (100%) rename src/{ => liberty}/core/Pad.cpp (99%) rename src/{ => liberty}/core/Pad.h (100%) rename src/{ => liberty}/core/Placeable.cpp (100%) rename src/{ => liberty}/core/Placeable.h (100%) rename src/{ => liberty}/core/PlayerInfo.cpp (100%) rename src/{ => liberty}/core/PlayerInfo.h (100%) rename src/{ => liberty}/core/Pools.cpp (100%) rename src/{ => liberty}/core/Pools.h (100%) rename src/{ => liberty}/core/Profile.cpp (100%) rename src/{ => liberty}/core/Profile.h (100%) rename src/{ => liberty}/core/Radar.cpp (97%) rename src/{ => liberty}/core/Radar.h (100%) rename src/{ => liberty}/core/Range2D.cpp (100%) rename src/{ => liberty}/core/Range2D.h (100%) rename src/{ => liberty}/core/Range3D.cpp (100%) rename src/{ => liberty}/core/Range3D.h (100%) rename src/{ => liberty}/core/References.cpp (100%) rename src/{ => liberty}/core/References.h (100%) rename src/{ => liberty}/core/Stats.cpp (100%) rename src/{ => liberty}/core/Stats.h (100%) rename src/{ => liberty}/core/Streaming.cpp (99%) rename src/{ => liberty}/core/Streaming.h (100%) rename src/{ => liberty}/core/SurfaceTable.cpp (100%) rename src/{ => liberty}/core/SurfaceTable.h (100%) rename src/{ => liberty}/core/TimeStep.cpp (100%) rename src/{ => liberty}/core/TimeStep.h (100%) rename src/{ => liberty}/core/Timer.cpp (100%) rename src/{ => liberty}/core/Timer.h (100%) rename src/{ => liberty}/core/User.cpp (100%) rename src/{ => liberty}/core/User.h (100%) rename src/{ => liberty}/core/Wanted.cpp (100%) rename src/{ => liberty}/core/Wanted.h (100%) rename src/{ => liberty}/core/World.cpp (100%) rename src/{ => liberty}/core/World.h (100%) rename src/{ => liberty}/core/ZoneCull.cpp (100%) rename src/{ => liberty}/core/ZoneCull.h (100%) rename src/{ => liberty}/core/Zones.cpp (100%) rename src/{ => liberty}/core/Zones.h (100%) rename src/{ => liberty}/core/common.h (98%) rename src/{ => liberty}/core/config.h (98%) rename src/{ => liberty}/core/main.cpp (99%) rename src/{ => liberty}/core/main.h (100%) rename src/{ => liberty}/core/obrstr.cpp (100%) rename src/{ => liberty}/core/obrstr.h (100%) rename src/{ => liberty}/core/re3.cpp (99%) rename src/{ => liberty}/core/templates.h (100%) rename src/{ => liberty}/core/timebars.cpp (100%) rename src/{ => liberty}/core/timebars.h (100%) rename src/{ => liberty}/entities/Dummy.cpp (100%) rename src/{ => liberty}/entities/Dummy.h (100%) rename src/{ => liberty}/entities/Entity.cpp (99%) rename src/{ => liberty}/entities/Entity.h (99%) rename src/{ => liberty}/entities/Physical.cpp (99%) rename src/{ => liberty}/entities/Physical.h (100%) rename src/{ => liberty}/extras/GitSHA1.cpp.in (100%) rename src/{ => liberty}/extras/GitSHA1.h (100%) rename src/{ => liberty}/extras/arrow.inc (100%) rename src/{ => liberty}/extras/cursor.inc (100%) rename src/{ => liberty}/extras/custompipes.cpp (100%) rename src/{ => liberty}/extras/custompipes.h (100%) rename src/{ => liberty}/extras/custompipes_d3d9.cpp (100%) rename src/{ => liberty}/extras/custompipes_gl.cpp (100%) rename src/{ => liberty}/extras/debugmenu.cpp (100%) rename src/{ => liberty}/extras/debugmenu.h (100%) rename src/{ => liberty}/extras/frontendoption.cpp (100%) rename src/{ => liberty}/extras/frontendoption.h (100%) create mode 100644 src/liberty/extras/ini.h rename src/{ => liberty}/extras/postfx.cpp (100%) rename src/{ => liberty}/extras/postfx.h (100%) rename src/{ => liberty}/extras/re3_inttypes.h (100%) rename src/{ => liberty}/extras/screendroplets.cpp (100%) rename src/{ => liberty}/extras/screendroplets.h (100%) rename src/{ => liberty}/extras/shaders/colourfilterIII.frag (100%) rename src/{ => liberty}/extras/shaders/colourfilterIII_PS.hlsl (100%) rename src/{ => liberty}/extras/shaders/contrast.frag (100%) rename src/{ => liberty}/extras/shaders/contrastPS.hlsl (100%) rename src/{ => liberty}/extras/shaders/default_UV2.vert (100%) rename src/{ => liberty}/extras/shaders/default_UV2_VS.hlsl (100%) rename src/{ => liberty}/extras/shaders/im2d.vert (100%) rename src/{ => liberty}/extras/shaders/im2d_UV2.vert (100%) rename src/{ => liberty}/extras/shaders/lighting.h (100%) rename src/{ => liberty}/extras/shaders/make_glsl.sh (100%) rename src/{ => liberty}/extras/shaders/make_hlsl.cmd (100%) rename src/{ => liberty}/extras/shaders/makeinc_glsl.sh (100%) rename src/{ => liberty}/extras/shaders/makeinc_hlsl.sh (100%) rename src/{ => liberty}/extras/shaders/neoGloss.frag (100%) rename src/{ => liberty}/extras/shaders/neoGloss.vert (100%) rename src/{ => liberty}/extras/shaders/neoGloss_PS.hlsl (100%) rename src/{ => liberty}/extras/shaders/neoGloss_VS.hlsl (100%) rename src/{ => liberty}/extras/shaders/neoRim.vert (100%) rename src/{ => liberty}/extras/shaders/neoRimSkin.vert (100%) rename src/{ => liberty}/extras/shaders/neoRimSkin_VS.hlsl (100%) rename src/{ => liberty}/extras/shaders/neoRim_VS.hlsl (100%) rename src/{ => liberty}/extras/shaders/neoVehicle.frag (100%) rename src/{ => liberty}/extras/shaders/neoVehicle.vert (100%) rename src/{ => liberty}/extras/shaders/neoVehicle_PS.hlsl (100%) rename src/{ => liberty}/extras/shaders/neoVehicle_VS.hlsl (100%) rename src/{ => liberty}/extras/shaders/neoWorldIII.frag (100%) rename src/{ => liberty}/extras/shaders/neoWorldIII_PS.hlsl (100%) rename src/{ => liberty}/extras/shaders/obj/colourfilterIII_PS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/colourfilterIII_PS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/colourfilterIII_frag.inc (100%) rename src/{ => liberty}/extras/shaders/obj/contrastPS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/contrastPS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/contrast_frag.inc (100%) rename src/{ => liberty}/extras/shaders/obj/default_UV2_VS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/default_UV2_VS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/default_UV2_vert.inc (100%) rename src/{ => liberty}/extras/shaders/obj/im2d_UV2_vert.inc (100%) rename src/{ => liberty}/extras/shaders/obj/im2d_vert.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoGloss_PS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/neoGloss_PS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoGloss_VS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/neoGloss_VS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoGloss_frag.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoGloss_vert.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoRimSkin_VS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/neoRimSkin_VS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoRimSkin_vert.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoRim_VS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/neoRim_VS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoRim_vert.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoVehicle_PS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/neoVehicle_PS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoVehicle_VS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/neoVehicle_VS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoVehicle_frag.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoVehicle_vert.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoWorldIII_PS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/neoWorldIII_PS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/neoWorldIII_frag.inc (100%) rename src/{ => liberty}/extras/shaders/obj/screenDroplet_PS.cso (100%) rename src/{ => liberty}/extras/shaders/obj/screenDroplet_PS.inc (100%) rename src/{ => liberty}/extras/shaders/obj/screenDroplet_frag.inc (100%) rename src/{ => liberty}/extras/shaders/obj/simple_frag.inc (100%) rename src/{ => liberty}/extras/shaders/screenDroplet.frag (100%) rename src/{ => liberty}/extras/shaders/screenDroplet_PS.hlsl (100%) rename src/{ => liberty}/extras/shaders/simple.frag (100%) rename src/{ => liberty}/extras/shaders/standardConstants.h (100%) rename src/{ => liberty}/fakerw/fake.cpp (100%) rename src/{ => liberty}/fakerw/rpanisot.h (100%) rename src/{ => liberty}/fakerw/rphanim.h (100%) rename src/{ => liberty}/fakerw/rpmatfx.h (100%) rename src/{ => liberty}/fakerw/rpskin.h (100%) rename src/{ => liberty}/fakerw/rpworld.h (100%) rename src/{ => liberty}/fakerw/rtbmp.h (100%) rename src/{ => liberty}/fakerw/rtcharse.h (100%) rename src/{ => liberty}/fakerw/rtpng.h (100%) rename src/{ => liberty}/fakerw/rtquat.h (100%) create mode 100644 src/liberty/fakerw/rwcore.h rename src/{ => liberty}/fakerw/rwplcore.h (100%) rename src/{ => liberty}/math/Matrix.cpp (93%) rename src/{ => liberty}/math/Matrix.h (100%) rename src/{ => liberty}/math/Quaternion.cpp (100%) rename src/{ => liberty}/math/Quaternion.h (100%) rename src/{ => liberty}/math/Rect.cpp (100%) rename src/{ => liberty}/math/Rect.h (100%) rename src/{ => liberty}/math/Vector.cpp (100%) rename src/{ => liberty}/math/Vector.h (100%) rename src/{ => liberty}/math/Vector2D.h (100%) rename src/{ => liberty}/math/VuVector.h (100%) rename src/{ => liberty}/math/math.cpp (100%) rename src/{ => liberty}/math/maths.h (98%) rename src/{ => liberty}/modelinfo/BaseModelInfo.cpp (100%) rename src/{ => liberty}/modelinfo/BaseModelInfo.h (100%) rename src/{ => liberty}/modelinfo/ClumpModelInfo.cpp (100%) rename src/{ => liberty}/modelinfo/ClumpModelInfo.h (100%) rename src/{ => liberty}/modelinfo/MloModelInfo.cpp (100%) rename src/{ => liberty}/modelinfo/MloModelInfo.h (100%) rename src/{ => liberty}/modelinfo/ModelIndices.cpp (100%) rename src/{ => liberty}/modelinfo/ModelIndices.h (100%) rename src/{ => liberty}/modelinfo/ModelInfo.cpp (100%) rename src/{ => liberty}/modelinfo/ModelInfo.h (100%) rename src/{ => liberty}/modelinfo/PedModelInfo.cpp (100%) rename src/{ => liberty}/modelinfo/PedModelInfo.h (100%) rename src/{ => liberty}/modelinfo/SimpleModelInfo.cpp (100%) rename src/{ => liberty}/modelinfo/SimpleModelInfo.h (100%) rename src/{ => liberty}/modelinfo/TimeModelInfo.cpp (100%) rename src/{ => liberty}/modelinfo/TimeModelInfo.h (100%) rename src/{ => liberty}/modelinfo/VehicleModelInfo.cpp (100%) rename src/{ => liberty}/modelinfo/VehicleModelInfo.h (100%) rename src/{ => liberty}/modelinfo/XtraCompsModelInfo.h (100%) rename src/{ => liberty}/objects/CutsceneHead.cpp (100%) rename src/{ => liberty}/objects/CutsceneHead.h (100%) rename src/{ => liberty}/objects/CutsceneObject.cpp (100%) rename src/{ => liberty}/objects/CutsceneObject.h (100%) rename src/{ => liberty}/objects/DummyObject.cpp (100%) rename src/{ => liberty}/objects/DummyObject.h (100%) rename src/{ => liberty}/objects/Object.cpp (100%) rename src/{ => liberty}/objects/Object.h (100%) rename src/{ => liberty}/objects/ObjectData.cpp (100%) rename src/{ => liberty}/objects/ObjectData.h (100%) rename src/{ => liberty}/objects/ParticleObject.cpp (100%) rename src/{ => liberty}/objects/ParticleObject.h (100%) rename src/{ => liberty}/objects/Projectile.cpp (100%) rename src/{ => liberty}/objects/Projectile.h (100%) rename src/{ => liberty}/peds/CivilianPed.cpp (100%) rename src/{ => liberty}/peds/CivilianPed.h (100%) rename src/{ => liberty}/peds/CopPed.cpp (100%) rename src/{ => liberty}/peds/CopPed.h (100%) rename src/{ => liberty}/peds/DummyPed.h (100%) rename src/{ => liberty}/peds/EmergencyPed.cpp (100%) rename src/{ => liberty}/peds/EmergencyPed.h (100%) rename src/{ => liberty}/peds/Gangs.cpp (100%) rename src/{ => liberty}/peds/Gangs.h (100%) rename src/{ => liberty}/peds/Ped.cpp (100%) rename src/{ => liberty}/peds/Ped.h (100%) rename src/{ => liberty}/peds/PedAI.cpp (99%) rename src/{ => liberty}/peds/PedChat.cpp (100%) rename src/{ => liberty}/peds/PedDebug.cpp (100%) rename src/{ => liberty}/peds/PedFight.cpp (100%) rename src/{ => liberty}/peds/PedIK.cpp (100%) rename src/{ => liberty}/peds/PedIK.h (100%) rename src/{ => liberty}/peds/PedPlacement.cpp (100%) rename src/{ => liberty}/peds/PedPlacement.h (100%) rename src/{ => liberty}/peds/PedRoutes.cpp (100%) rename src/{ => liberty}/peds/PedRoutes.h (100%) rename src/{ => liberty}/peds/PedType.cpp (100%) rename src/{ => liberty}/peds/PedType.h (100%) rename src/{ => liberty}/peds/PlayerPed.cpp (100%) rename src/{ => liberty}/peds/PlayerPed.h (100%) rename src/{ => liberty}/peds/Population.cpp (100%) rename src/{ => liberty}/peds/Population.h (100%) rename src/{ => liberty}/renderer/2dEffect.h (100%) rename src/{ => liberty}/renderer/Antennas.cpp (100%) rename src/{ => liberty}/renderer/Antennas.h (100%) rename src/{ => liberty}/renderer/Clouds.cpp (99%) rename src/{ => liberty}/renderer/Clouds.h (100%) rename src/{ => liberty}/renderer/Console.cpp (100%) rename src/{ => liberty}/renderer/Console.h (100%) rename src/{ => liberty}/renderer/Coronas.cpp (100%) rename src/{ => liberty}/renderer/Coronas.h (100%) rename src/{ => liberty}/renderer/Credits.cpp (100%) rename src/{ => liberty}/renderer/Credits.h (100%) rename src/{ => liberty}/renderer/Draw.cpp (100%) rename src/{ => liberty}/renderer/Draw.h (100%) rename src/{ => liberty}/renderer/Fluff.cpp (100%) rename src/{ => liberty}/renderer/Fluff.h (100%) rename src/{ => liberty}/renderer/Font.cpp (100%) rename src/{ => liberty}/renderer/Font.h (100%) rename src/{ => liberty}/renderer/Glass.cpp (100%) rename src/{ => liberty}/renderer/Glass.h (100%) rename src/{ => liberty}/renderer/Hud.cpp (100%) rename src/{ => liberty}/renderer/Hud.h (100%) rename src/{ => liberty}/renderer/Instance.cpp (100%) rename src/{ => liberty}/renderer/Instance.h (100%) rename src/{ => liberty}/renderer/Lines.cpp (100%) rename src/{ => liberty}/renderer/Lines.h (100%) rename src/{ => liberty}/renderer/MBlur.cpp (100%) rename src/{ => liberty}/renderer/MBlur.h (100%) rename src/{ => liberty}/renderer/Particle.cpp (100%) rename src/{ => liberty}/renderer/Particle.h (100%) rename src/{ => liberty}/renderer/ParticleMgr.cpp (100%) rename src/{ => liberty}/renderer/ParticleMgr.h (100%) rename src/{ => liberty}/renderer/ParticleType.h (100%) rename src/{ => liberty}/renderer/PlayerSkin.cpp (100%) rename src/{ => liberty}/renderer/PlayerSkin.h (100%) rename src/{ => liberty}/renderer/PointLights.cpp (100%) rename src/{ => liberty}/renderer/PointLights.h (100%) rename src/{ => liberty}/renderer/RenderBuffer.cpp (100%) rename src/{ => liberty}/renderer/RenderBuffer.h (100%) rename src/{ => liberty}/renderer/Renderer.cpp (100%) rename src/{ => liberty}/renderer/Renderer.h (100%) rename src/{ => liberty}/renderer/Rubbish.cpp (100%) rename src/{ => liberty}/renderer/Rubbish.h (100%) rename src/{ => liberty}/renderer/Shadows.cpp (100%) rename src/{ => liberty}/renderer/Shadows.h (100%) rename src/{ => liberty}/renderer/Skidmarks.cpp (100%) rename src/{ => liberty}/renderer/Skidmarks.h (100%) rename src/{ => liberty}/renderer/SpecialFX.cpp (100%) rename src/{ => liberty}/renderer/SpecialFX.h (100%) rename src/{ => liberty}/renderer/Sprite.cpp (100%) rename src/{ => liberty}/renderer/Sprite.h (100%) rename src/{ => liberty}/renderer/Sprite2d.cpp (98%) rename src/{ => liberty}/renderer/Sprite2d.h (100%) rename src/{ => liberty}/renderer/TexList.cpp (100%) rename src/{ => liberty}/renderer/TexList.h (100%) rename src/{ => liberty}/renderer/Timecycle.cpp (100%) rename src/{ => liberty}/renderer/Timecycle.h (100%) rename src/{ => liberty}/renderer/WaterCannon.cpp (100%) rename src/{ => liberty}/renderer/WaterCannon.h (100%) rename src/{ => liberty}/renderer/WaterLevel.cpp (99%) rename src/{ => liberty}/renderer/WaterLevel.h (100%) rename src/{ => liberty}/renderer/Weather.cpp (100%) rename src/{ => liberty}/renderer/Weather.h (100%) rename src/{ => liberty}/rw/ClumpRead.cpp (100%) rename src/{ => liberty}/rw/Lights.cpp (100%) rename src/{ => liberty}/rw/Lights.h (100%) rename src/{ => liberty}/rw/MemoryHeap.cpp (100%) rename src/{ => liberty}/rw/MemoryHeap.h (100%) rename src/{ => liberty}/rw/MemoryMgr.cpp (100%) rename src/{ => liberty}/rw/MemoryMgr.h (100%) rename src/{ => liberty}/rw/NodeName.cpp (100%) rename src/{ => liberty}/rw/NodeName.h (100%) rename src/{ => liberty}/rw/RwHelper.cpp (100%) rename src/{ => liberty}/rw/RwHelper.h (100%) rename src/{ => liberty}/rw/RwMatFX.cpp (100%) rename src/{ => liberty}/rw/RwPS2AlphaTest.cpp (100%) rename src/{ => liberty}/rw/TexRead.cpp (100%) rename src/{ => liberty}/rw/TexturePools.cpp (100%) rename src/{ => liberty}/rw/TexturePools.h (100%) rename src/{ => liberty}/rw/TxdStore.cpp (100%) rename src/{ => liberty}/rw/TxdStore.h (100%) rename src/{ => liberty}/rw/VisibilityPlugins.cpp (100%) rename src/{ => liberty}/rw/VisibilityPlugins.h (100%) rename src/{ => liberty}/save/Date.cpp (100%) rename src/{ => liberty}/save/Date.h (100%) rename src/{ => liberty}/save/GenericGameStorage.cpp (96%) rename src/{ => liberty}/save/GenericGameStorage.h (100%) rename src/{ => liberty}/save/MemoryCard.cpp (100%) rename src/{ => liberty}/save/MemoryCard.h (100%) rename src/{ => liberty}/save/PCSave.cpp (80%) rename src/{ => liberty}/save/PCSave.h (100%) rename src/{ => liberty}/save/SaveBuf.h (100%) rename src/{ => liberty}/skel/crossplatform.cpp (98%) rename src/{ => liberty}/skel/crossplatform.h (100%) rename src/{ => liberty}/skel/dc/dc.cpp (97%) rename src/{ => liberty}/skel/dc/dc.h (100%) rename src/{ => liberty}/skel/events.cpp (100%) rename src/{ => liberty}/skel/events.h (100%) rename src/{ => liberty}/skel/glfw/glfw.cpp (100%) rename src/{ => liberty}/skel/platform.h (100%) rename src/{ => liberty}/skel/skeleton.cpp (100%) rename src/{ => liberty}/skel/skeleton.h (100%) rename src/{ => liberty}/skel/win/gta3.ico (100%) rename src/{ => liberty}/skel/win/resource.h (100%) rename src/{ => liberty}/skel/win/win.cpp (100%) rename src/{ => liberty}/skel/win/win.h (100%) rename src/{ => liberty}/skel/win/win.rc (100%) rename src/{ => liberty}/text/Messages.cpp (100%) rename src/{ => liberty}/text/Messages.h (100%) rename src/{ => liberty}/text/Pager.cpp (100%) rename src/{ => liberty}/text/Pager.h (100%) rename src/{ => liberty}/text/Text.cpp (100%) rename src/{ => liberty}/text/Text.h (100%) rename src/{ => liberty}/vehicles/Automobile.cpp (100%) rename src/{ => liberty}/vehicles/Automobile.h (100%) rename src/{ => liberty}/vehicles/Bike.h (100%) rename src/{ => liberty}/vehicles/Boat.cpp (100%) rename src/{ => liberty}/vehicles/Boat.h (100%) rename src/{ => liberty}/vehicles/CarGen.cpp (100%) rename src/{ => liberty}/vehicles/CarGen.h (100%) rename src/{ => liberty}/vehicles/Cranes.cpp (100%) rename src/{ => liberty}/vehicles/Cranes.h (100%) rename src/{ => liberty}/vehicles/DamageManager.cpp (100%) rename src/{ => liberty}/vehicles/DamageManager.h (100%) rename src/{ => liberty}/vehicles/Door.cpp (100%) rename src/{ => liberty}/vehicles/Door.h (100%) rename src/{ => liberty}/vehicles/Floater.cpp (100%) rename src/{ => liberty}/vehicles/Floater.h (100%) rename src/{ => liberty}/vehicles/HandlingMgr.cpp (100%) rename src/{ => liberty}/vehicles/HandlingMgr.h (100%) rename src/{ => liberty}/vehicles/Heli.cpp (100%) rename src/{ => liberty}/vehicles/Heli.h (100%) rename src/{ => liberty}/vehicles/Plane.cpp (100%) rename src/{ => liberty}/vehicles/Plane.h (100%) rename src/{ => liberty}/vehicles/Train.cpp (100%) rename src/{ => liberty}/vehicles/Train.h (100%) rename src/{ => liberty}/vehicles/Transmission.cpp (100%) rename src/{ => liberty}/vehicles/Transmission.h (100%) rename src/{ => liberty}/vehicles/Vehicle.cpp (100%) rename src/{ => liberty}/vehicles/Vehicle.h (100%) rename src/{ => liberty}/weapons/BulletInfo.cpp (89%) rename src/{ => liberty}/weapons/BulletInfo.h (100%) rename src/{ => liberty}/weapons/Explosion.cpp (100%) rename src/{ => liberty}/weapons/Explosion.h (100%) rename src/{ => liberty}/weapons/ProjectileInfo.cpp (100%) rename src/{ => liberty}/weapons/ProjectileInfo.h (100%) rename src/{ => liberty}/weapons/ShotInfo.cpp (100%) rename src/{ => liberty}/weapons/ShotInfo.h (100%) rename src/{ => liberty}/weapons/Weapon.cpp (100%) rename src/{ => liberty}/weapons/Weapon.h (100%) rename src/{ => liberty}/weapons/WeaponEffects.cpp (100%) rename src/{ => liberty}/weapons/WeaponEffects.h (100%) rename src/{ => liberty}/weapons/WeaponInfo.cpp (100%) rename src/{ => liberty}/weapons/WeaponInfo.h (100%) rename src/{ => liberty}/weapons/WeaponType.h (100%) create mode 100644 src/miami/CMakeLists.txt create mode 100644 src/miami/animation/AnimBlendAssocGroup.cpp create mode 100644 src/miami/animation/AnimBlendAssocGroup.h create mode 100644 src/miami/animation/AnimBlendAssociation.cpp create mode 100644 src/miami/animation/AnimBlendAssociation.h create mode 100644 src/miami/animation/AnimBlendClumpData.cpp create mode 100644 src/miami/animation/AnimBlendClumpData.h rename src/{ => miami}/animation/AnimBlendHierarchy.cpp (75%) create mode 100644 src/miami/animation/AnimBlendHierarchy.h create mode 100644 src/miami/animation/AnimBlendList.h create mode 100644 src/miami/animation/AnimBlendNode.cpp create mode 100644 src/miami/animation/AnimBlendNode.h create mode 100644 src/miami/animation/AnimBlendSequence.cpp create mode 100644 src/miami/animation/AnimBlendSequence.h create mode 100644 src/miami/animation/AnimManager.cpp create mode 100644 src/miami/animation/AnimManager.h create mode 100644 src/miami/animation/AnimationId.h create mode 100644 src/miami/animation/Bones.cpp create mode 100644 src/miami/animation/Bones.h create mode 100644 src/miami/animation/CutsceneMgr.cpp create mode 100644 src/miami/animation/CutsceneMgr.h create mode 100644 src/miami/animation/FrameUpdate.cpp create mode 100644 src/miami/animation/RpAnimBlend.cpp create mode 100644 src/miami/animation/RpAnimBlend.h create mode 100644 src/miami/audio/AudioCollision.cpp create mode 100644 src/miami/audio/AudioCollision.h create mode 100644 src/miami/audio/AudioLogic.cpp create mode 100644 src/miami/audio/AudioManager.cpp create mode 100644 src/miami/audio/AudioManager.h create mode 100644 src/miami/audio/AudioSamples.h create mode 100644 src/miami/audio/AudioScriptObject.cpp create mode 100644 src/miami/audio/AudioScriptObject.h create mode 100644 src/miami/audio/DMAudio.cpp create mode 100644 src/miami/audio/DMAudio.h create mode 100644 src/miami/audio/MusicManager.cpp create mode 100644 src/miami/audio/MusicManager.h create mode 100644 src/miami/audio/PolRadio.cpp create mode 100644 src/miami/audio/PolRadio.h create mode 100644 src/miami/audio/audio_enums.h create mode 100644 src/miami/audio/eax/eax-util.cpp create mode 100644 src/miami/audio/eax/eax-util.h create mode 100644 src/miami/audio/eax/eax.h create mode 100644 src/miami/audio/oal/aldlist.cpp create mode 100644 src/miami/audio/oal/aldlist.h create mode 100644 src/miami/audio/oal/channel.cpp create mode 100644 src/miami/audio/oal/channel.h create mode 100644 src/miami/audio/oal/oal_utils.cpp create mode 100644 src/miami/audio/oal/oal_utils.h create mode 100644 src/miami/audio/oal/stream.cpp create mode 100644 src/miami/audio/oal/stream.h create mode 100644 src/miami/audio/sampman.h create mode 100644 src/miami/audio/sampman_dc.cpp create mode 100644 src/miami/audio/sampman_dc_streams.h create mode 100644 src/miami/audio/sampman_miles.cpp create mode 100644 src/miami/audio/sampman_null.cpp create mode 100644 src/miami/audio/sampman_oal.cpp create mode 100644 src/miami/audio/soundlist.h create mode 100644 src/miami/buildings/Building.cpp create mode 100644 src/miami/buildings/Building.h create mode 100644 src/miami/buildings/Solid.h create mode 100644 src/miami/buildings/Treadable.cpp create mode 100644 src/miami/buildings/Treadable.h create mode 100644 src/miami/collision/ColBox.cpp create mode 100644 src/miami/collision/ColBox.h create mode 100644 src/miami/collision/ColLine.cpp create mode 100644 src/miami/collision/ColLine.h create mode 100644 src/miami/collision/ColModel.cpp create mode 100644 src/miami/collision/ColModel.h create mode 100644 src/miami/collision/ColPoint.cpp create mode 100644 src/miami/collision/ColPoint.h create mode 100644 src/miami/collision/ColSphere.cpp create mode 100644 src/miami/collision/ColSphere.h create mode 100644 src/miami/collision/ColStore.cpp create mode 100644 src/miami/collision/ColStore.h create mode 100644 src/miami/collision/ColTriangle.cpp create mode 100644 src/miami/collision/ColTriangle.h create mode 100644 src/miami/collision/Collision.cpp create mode 100644 src/miami/collision/Collision.h create mode 100644 src/miami/collision/CompressedVector.h create mode 100644 src/miami/collision/TempColModels.cpp create mode 100644 src/miami/collision/TempColModels.h create mode 100644 src/miami/collision/VuCollision.cpp create mode 100644 src/miami/collision/VuCollision.h create mode 100644 src/miami/collision/vu0Collision.dsm create mode 100644 src/miami/collision/vu0Collision_1.s create mode 100644 src/miami/collision/vu0Collision_2.s create mode 100644 src/miami/control/AutoPilot.cpp create mode 100644 src/miami/control/AutoPilot.h create mode 100644 src/miami/control/Bridge.cpp create mode 100644 src/miami/control/Bridge.h create mode 100644 src/miami/control/CarAI.cpp create mode 100644 src/miami/control/CarAI.h create mode 100644 src/miami/control/CarCtrl.cpp create mode 100644 src/miami/control/CarCtrl.h create mode 100644 src/miami/control/Curves.cpp create mode 100644 src/miami/control/Curves.h create mode 100644 src/miami/control/Darkel.cpp create mode 100644 src/miami/control/Darkel.h create mode 100644 src/miami/control/GameLogic.cpp create mode 100644 src/miami/control/GameLogic.h create mode 100644 src/miami/control/Garages.cpp create mode 100644 src/miami/control/Garages.h create mode 100644 src/miami/control/NameGrid.cpp create mode 100644 src/miami/control/NameGrid.h create mode 100644 src/miami/control/OnscreenTimer.cpp create mode 100644 src/miami/control/OnscreenTimer.h create mode 100644 src/miami/control/PathFind.cpp create mode 100644 src/miami/control/PathFind.h create mode 100644 src/miami/control/Phones.cpp create mode 100644 src/miami/control/Phones.h create mode 100644 src/miami/control/Pickups.cpp create mode 100644 src/miami/control/Pickups.h create mode 100644 src/miami/control/PowerPoints.cpp create mode 100644 src/miami/control/PowerPoints.h create mode 100644 src/miami/control/Record.cpp create mode 100644 src/miami/control/Record.h create mode 100644 src/miami/control/Remote.cpp create mode 100644 src/miami/control/Remote.h create mode 100644 src/miami/control/Replay.cpp create mode 100644 src/miami/control/Replay.h create mode 100644 src/miami/control/Restart.cpp create mode 100644 src/miami/control/Restart.h create mode 100644 src/miami/control/RoadBlocks.cpp create mode 100644 src/miami/control/RoadBlocks.h create mode 100644 src/miami/control/SceneEdit.cpp create mode 100644 src/miami/control/SceneEdit.h create mode 100644 src/miami/control/Script.cpp create mode 100644 src/miami/control/Script.h create mode 100644 src/miami/control/Script2.cpp create mode 100644 src/miami/control/Script3.cpp create mode 100644 src/miami/control/Script4.cpp create mode 100644 src/miami/control/Script5.cpp create mode 100644 src/miami/control/Script6.cpp create mode 100644 src/miami/control/Script7.cpp create mode 100644 src/miami/control/Script8.cpp create mode 100644 src/miami/control/ScriptCommands.h create mode 100644 src/miami/control/ScriptDebug.cpp create mode 100644 src/miami/control/SetPieces.cpp create mode 100644 src/miami/control/SetPieces.h create mode 100644 src/miami/control/TrafficLights.cpp create mode 100644 src/miami/control/TrafficLights.h create mode 100644 src/miami/core/Accident.cpp create mode 100644 src/miami/core/Accident.h create mode 100644 src/miami/core/AnimViewer.cpp create mode 100644 src/miami/core/AnimViewer.h create mode 100644 src/miami/core/Cam.cpp create mode 100644 src/miami/core/Camera.cpp create mode 100644 src/miami/core/Camera.h create mode 100644 src/miami/core/CdStream.cpp create mode 100644 src/miami/core/CdStream.h create mode 100644 src/miami/core/CdStreamDC.cpp create mode 100644 src/miami/core/CdStreamPosix.cpp create mode 100644 src/miami/core/Clock.cpp create mode 100644 src/miami/core/Clock.h create mode 100644 src/miami/core/ControllerConfig.cpp create mode 100644 src/miami/core/ControllerConfig.h create mode 100644 src/miami/core/Crime.h create mode 100644 src/miami/core/Debug.cpp create mode 100644 src/miami/core/Debug.h create mode 100644 src/miami/core/Directory.cpp create mode 100644 src/miami/core/Directory.h create mode 100644 src/miami/core/EventList.cpp create mode 100644 src/miami/core/EventList.h create mode 100644 src/miami/core/FileLoader.cpp create mode 100644 src/miami/core/FileLoader.h create mode 100644 src/miami/core/FileMgr.cpp create mode 100644 src/miami/core/FileMgr.h create mode 100644 src/miami/core/Fire.cpp create mode 100644 src/miami/core/Fire.h create mode 100644 src/miami/core/FrontEndControls.cpp create mode 100644 src/miami/core/FrontEndControls.h create mode 100644 src/miami/core/Frontend.cpp create mode 100644 src/miami/core/Frontend.h create mode 100644 src/miami/core/FrontendTriggers.h create mode 100644 src/miami/core/Frontend_PS2.cpp create mode 100644 src/miami/core/Frontend_PS2.h create mode 100644 src/miami/core/Game.cpp create mode 100644 src/miami/core/Game.h create mode 100644 src/miami/core/General.h create mode 100644 src/miami/core/IniFile.cpp create mode 100644 src/miami/core/IniFile.h create mode 100644 src/miami/core/Lists.cpp create mode 100644 src/miami/core/Lists.h create mode 100644 src/miami/core/MenuScreens.cpp create mode 100644 src/miami/core/MenuScreensCustom.cpp create mode 100644 src/miami/core/Pad.cpp create mode 100644 src/miami/core/Pad.h create mode 100644 src/miami/core/Placeable.cpp create mode 100644 src/miami/core/Placeable.h create mode 100644 src/miami/core/PlayerInfo.cpp create mode 100644 src/miami/core/PlayerInfo.h create mode 100644 src/miami/core/Pools.cpp create mode 100644 src/miami/core/Pools.h create mode 100644 src/miami/core/Profile.cpp create mode 100644 src/miami/core/Profile.h create mode 100644 src/miami/core/Radar.cpp create mode 100644 src/miami/core/Radar.h create mode 100644 src/miami/core/Range2D.cpp create mode 100644 src/miami/core/Range2D.h create mode 100644 src/miami/core/Range3D.cpp create mode 100644 src/miami/core/Range3D.h create mode 100644 src/miami/core/References.cpp create mode 100644 src/miami/core/References.h create mode 100644 src/miami/core/Ropes.cpp create mode 100644 src/miami/core/Ropes.h create mode 100644 src/miami/core/Stats.cpp create mode 100644 src/miami/core/Stats.h create mode 100644 src/miami/core/Streaming.cpp create mode 100644 src/miami/core/Streaming.h create mode 100644 src/miami/core/SurfaceTable.cpp create mode 100644 src/miami/core/SurfaceTable.h create mode 100644 src/miami/core/TimeStep.cpp create mode 100644 src/miami/core/TimeStep.h create mode 100644 src/miami/core/Timer.cpp create mode 100644 src/miami/core/Timer.h create mode 100644 src/miami/core/User.cpp create mode 100644 src/miami/core/User.h create mode 100644 src/miami/core/Wanted.cpp create mode 100644 src/miami/core/Wanted.h create mode 100644 src/miami/core/World.cpp create mode 100644 src/miami/core/World.h create mode 100644 src/miami/core/ZoneCull.cpp create mode 100644 src/miami/core/ZoneCull.h create mode 100644 src/miami/core/Zones.cpp create mode 100644 src/miami/core/Zones.h create mode 100644 src/miami/core/common.h create mode 100644 src/miami/core/config.h create mode 100644 src/miami/core/main.cpp create mode 100644 src/miami/core/main.h create mode 100644 src/miami/core/obrstr.cpp create mode 100644 src/miami/core/obrstr.h create mode 100644 src/miami/core/re3.cpp create mode 100644 src/miami/core/templates.h create mode 100644 src/miami/core/timebars.cpp create mode 100644 src/miami/core/timebars.h create mode 100644 src/miami/entities/Dummy.cpp create mode 100644 src/miami/entities/Dummy.h create mode 100644 src/miami/entities/Entity.cpp create mode 100644 src/miami/entities/Entity.h create mode 100644 src/miami/entities/Physical.cpp create mode 100644 src/miami/entities/Physical.h create mode 100644 src/miami/extras/GitSHA1.cpp.in create mode 100644 src/miami/extras/GitSHA1.h create mode 100644 src/miami/extras/arrow.inc create mode 100644 src/miami/extras/cursor.inc create mode 100644 src/miami/extras/custompipes.cpp create mode 100644 src/miami/extras/custompipes.h create mode 100644 src/miami/extras/custompipes_d3d9.cpp create mode 100644 src/miami/extras/custompipes_gl.cpp create mode 100644 src/miami/extras/debugmenu.cpp create mode 100644 src/miami/extras/debugmenu.h create mode 100644 src/miami/extras/frontendoption.cpp create mode 100644 src/miami/extras/frontendoption.h rename src/{ => miami}/extras/ini.h (91%) create mode 100644 src/miami/extras/postfx.cpp create mode 100644 src/miami/extras/postfx.h create mode 100644 src/miami/extras/re3_inttypes.h create mode 100644 src/miami/extras/screendroplets.cpp create mode 100644 src/miami/extras/screendroplets.h create mode 100644 src/miami/extras/shaders/colourfilterVC.frag create mode 100644 src/miami/extras/shaders/colourfilterVC_PS.hlsl create mode 100644 src/miami/extras/shaders/contrast.frag create mode 100644 src/miami/extras/shaders/contrastPS.hlsl create mode 100644 src/miami/extras/shaders/default_UV2.vert create mode 100644 src/miami/extras/shaders/default_UV2_VS.hlsl create mode 100644 src/miami/extras/shaders/im2d.vert create mode 100644 src/miami/extras/shaders/im2d_UV2.vert create mode 100644 src/miami/extras/shaders/lighting.h create mode 100644 src/miami/extras/shaders/make_glsl.sh create mode 100644 src/miami/extras/shaders/make_hlsl.cmd create mode 100644 src/miami/extras/shaders/makeinc_glsl.sh create mode 100644 src/miami/extras/shaders/makeinc_hlsl.sh create mode 100644 src/miami/extras/shaders/neoGloss.frag create mode 100644 src/miami/extras/shaders/neoGloss.vert create mode 100644 src/miami/extras/shaders/neoGloss_PS.hlsl create mode 100644 src/miami/extras/shaders/neoGloss_VS.hlsl create mode 100644 src/miami/extras/shaders/neoRim.vert create mode 100644 src/miami/extras/shaders/neoRimSkin.vert create mode 100644 src/miami/extras/shaders/neoRimSkin_VS.hlsl create mode 100644 src/miami/extras/shaders/neoRim_VS.hlsl create mode 100644 src/miami/extras/shaders/neoVehicle.frag create mode 100644 src/miami/extras/shaders/neoVehicle.vert create mode 100644 src/miami/extras/shaders/neoVehicle_PS.hlsl create mode 100644 src/miami/extras/shaders/neoVehicle_VS.hlsl create mode 100644 src/miami/extras/shaders/neoWorldVC.frag create mode 100644 src/miami/extras/shaders/neoWorldVC_PS.hlsl create mode 100644 src/miami/extras/shaders/screenDroplet.frag create mode 100644 src/miami/extras/shaders/screenDroplet_PS.hlsl create mode 100644 src/miami/extras/shaders/simple.frag create mode 100644 src/miami/extras/shaders/standardConstants.h create mode 100644 src/miami/fakerw/fake.cpp create mode 100644 src/miami/fakerw/rpanisot.h create mode 100644 src/miami/fakerw/rphanim.h create mode 100644 src/miami/fakerw/rpmatfx.h create mode 100644 src/miami/fakerw/rpskin.h create mode 100644 src/miami/fakerw/rpworld.h create mode 100644 src/miami/fakerw/rtbmp.h create mode 100644 src/miami/fakerw/rtcharse.h create mode 100644 src/miami/fakerw/rtpng.h create mode 100644 src/miami/fakerw/rtquat.h rename src/{ => miami}/fakerw/rwcore.h (100%) create mode 100644 src/miami/fakerw/rwplcore.h create mode 100644 src/miami/math/Matrix.cpp create mode 100644 src/miami/math/Matrix.h create mode 100644 src/miami/math/Quaternion.cpp create mode 100644 src/miami/math/Quaternion.h create mode 100644 src/miami/math/Rect.cpp create mode 100644 src/miami/math/Rect.h create mode 100644 src/miami/math/Vector.cpp create mode 100644 src/miami/math/Vector.h create mode 100644 src/miami/math/Vector2D.h create mode 100644 src/miami/math/VuVector.h create mode 100644 src/miami/math/math.cpp create mode 100644 src/miami/math/maths.h create mode 100644 src/miami/modelinfo/BaseModelInfo.cpp create mode 100644 src/miami/modelinfo/BaseModelInfo.h create mode 100644 src/miami/modelinfo/ClumpModelInfo.cpp create mode 100644 src/miami/modelinfo/ClumpModelInfo.h create mode 100644 src/miami/modelinfo/MloModelInfo.cpp create mode 100644 src/miami/modelinfo/MloModelInfo.h create mode 100644 src/miami/modelinfo/ModelIndices.cpp create mode 100644 src/miami/modelinfo/ModelIndices.h create mode 100644 src/miami/modelinfo/ModelInfo.cpp create mode 100644 src/miami/modelinfo/ModelInfo.h create mode 100644 src/miami/modelinfo/PedModelInfo.cpp create mode 100644 src/miami/modelinfo/PedModelInfo.h create mode 100644 src/miami/modelinfo/SimpleModelInfo.cpp create mode 100644 src/miami/modelinfo/SimpleModelInfo.h create mode 100644 src/miami/modelinfo/TimeModelInfo.cpp create mode 100644 src/miami/modelinfo/TimeModelInfo.h create mode 100644 src/miami/modelinfo/VehicleModelInfo.cpp create mode 100644 src/miami/modelinfo/VehicleModelInfo.h create mode 100644 src/miami/modelinfo/WeaponModelInfo.cpp create mode 100644 src/miami/modelinfo/WeaponModelInfo.h create mode 100644 src/miami/objects/CutsceneObject.cpp create mode 100644 src/miami/objects/CutsceneObject.h create mode 100644 src/miami/objects/DummyObject.cpp create mode 100644 src/miami/objects/DummyObject.h create mode 100644 src/miami/objects/Object.cpp create mode 100644 src/miami/objects/Object.h create mode 100644 src/miami/objects/ObjectData.cpp create mode 100644 src/miami/objects/ObjectData.h create mode 100644 src/miami/objects/ParticleObject.cpp create mode 100644 src/miami/objects/ParticleObject.h create mode 100644 src/miami/objects/Projectile.cpp create mode 100644 src/miami/objects/Projectile.h create mode 100644 src/miami/objects/Stinger.cpp create mode 100644 src/miami/objects/Stinger.h create mode 100644 src/miami/peds/CivilianPed.cpp create mode 100644 src/miami/peds/CivilianPed.h create mode 100644 src/miami/peds/CopPed.cpp create mode 100644 src/miami/peds/CopPed.h create mode 100644 src/miami/peds/DummyPed.h create mode 100644 src/miami/peds/EmergencyPed.cpp create mode 100644 src/miami/peds/EmergencyPed.h create mode 100644 src/miami/peds/Gangs.cpp create mode 100644 src/miami/peds/Gangs.h create mode 100644 src/miami/peds/Ped.cpp create mode 100644 src/miami/peds/Ped.h create mode 100644 src/miami/peds/PedAI.cpp create mode 100644 src/miami/peds/PedAttractor.cpp create mode 100644 src/miami/peds/PedAttractor.h create mode 100644 src/miami/peds/PedChat.cpp create mode 100644 src/miami/peds/PedDebug.cpp create mode 100644 src/miami/peds/PedFight.cpp create mode 100644 src/miami/peds/PedIK.cpp create mode 100644 src/miami/peds/PedIK.h create mode 100644 src/miami/peds/PedPlacement.cpp create mode 100644 src/miami/peds/PedPlacement.h create mode 100644 src/miami/peds/PedRoutes.cpp create mode 100644 src/miami/peds/PedRoutes.h create mode 100644 src/miami/peds/PedType.cpp create mode 100644 src/miami/peds/PedType.h create mode 100644 src/miami/peds/PlayerPed.cpp create mode 100644 src/miami/peds/PlayerPed.h create mode 100644 src/miami/peds/Population.cpp create mode 100644 src/miami/peds/Population.h create mode 100644 src/miami/prof/profiler.cpp create mode 100644 src/miami/prof/profiler.h create mode 100644 src/miami/renderer/2dEffect.h create mode 100644 src/miami/renderer/Antennas.cpp create mode 100644 src/miami/renderer/Antennas.h create mode 100644 src/miami/renderer/Clouds.cpp create mode 100644 src/miami/renderer/Clouds.h create mode 100644 src/miami/renderer/Console.cpp create mode 100644 src/miami/renderer/Console.h create mode 100644 src/miami/renderer/Coronas.cpp create mode 100644 src/miami/renderer/Coronas.h create mode 100644 src/miami/renderer/Credits.cpp create mode 100644 src/miami/renderer/Credits.h create mode 100644 src/miami/renderer/CutsceneShadow.cpp create mode 100644 src/miami/renderer/CutsceneShadow.h create mode 100644 src/miami/renderer/Draw.cpp create mode 100644 src/miami/renderer/Draw.h create mode 100644 src/miami/renderer/Fluff.cpp create mode 100644 src/miami/renderer/Fluff.h create mode 100644 src/miami/renderer/Font.cpp create mode 100644 src/miami/renderer/Font.h create mode 100644 src/miami/renderer/Glass.cpp create mode 100644 src/miami/renderer/Glass.h create mode 100644 src/miami/renderer/Hud.cpp create mode 100644 src/miami/renderer/Hud.h create mode 100644 src/miami/renderer/Instance.cpp create mode 100644 src/miami/renderer/Instance.h create mode 100644 src/miami/renderer/Lines.cpp create mode 100644 src/miami/renderer/Lines.h create mode 100644 src/miami/renderer/MBlur.cpp create mode 100644 src/miami/renderer/MBlur.h create mode 100644 src/miami/renderer/Occlusion.cpp create mode 100644 src/miami/renderer/Occlusion.h create mode 100644 src/miami/renderer/Particle.cpp create mode 100644 src/miami/renderer/Particle.h create mode 100644 src/miami/renderer/ParticleMgr.cpp create mode 100644 src/miami/renderer/ParticleMgr.h create mode 100644 src/miami/renderer/ParticleType.h create mode 100644 src/miami/renderer/PlayerSkin.cpp create mode 100644 src/miami/renderer/PlayerSkin.h create mode 100644 src/miami/renderer/PointLights.cpp create mode 100644 src/miami/renderer/PointLights.h create mode 100644 src/miami/renderer/RenderBuffer.cpp create mode 100644 src/miami/renderer/RenderBuffer.h create mode 100644 src/miami/renderer/Renderer.cpp create mode 100644 src/miami/renderer/Renderer.h create mode 100644 src/miami/renderer/Rubbish.cpp create mode 100644 src/miami/renderer/Rubbish.h create mode 100644 src/miami/renderer/ShadowCamera.cpp create mode 100644 src/miami/renderer/ShadowCamera.h create mode 100644 src/miami/renderer/Shadows.cpp create mode 100644 src/miami/renderer/Shadows.h create mode 100644 src/miami/renderer/Skidmarks.cpp create mode 100644 src/miami/renderer/Skidmarks.h create mode 100644 src/miami/renderer/SpecialFX.cpp create mode 100644 src/miami/renderer/SpecialFX.h create mode 100644 src/miami/renderer/Sprite.cpp create mode 100644 src/miami/renderer/Sprite.h create mode 100644 src/miami/renderer/Sprite2d.cpp create mode 100644 src/miami/renderer/Sprite2d.h create mode 100644 src/miami/renderer/TexList.cpp create mode 100644 src/miami/renderer/TexList.h create mode 100644 src/miami/renderer/Timecycle.cpp create mode 100644 src/miami/renderer/Timecycle.h create mode 100644 src/miami/renderer/VarConsole.cpp create mode 100644 src/miami/renderer/VarConsole.h create mode 100644 src/miami/renderer/WaterCannon.cpp create mode 100644 src/miami/renderer/WaterCannon.h create mode 100644 src/miami/renderer/WaterCreatures.cpp create mode 100644 src/miami/renderer/WaterCreatures.h create mode 100644 src/miami/renderer/WaterLevel.cpp create mode 100644 src/miami/renderer/WaterLevel.h create mode 100644 src/miami/renderer/Weather.cpp create mode 100644 src/miami/renderer/Weather.h create mode 100644 src/miami/renderer/WindModifiers.cpp create mode 100644 src/miami/renderer/WindModifiers.h create mode 100644 src/miami/rw/ClumpRead.cpp create mode 100644 src/miami/rw/Lights.cpp create mode 100644 src/miami/rw/Lights.h create mode 100644 src/miami/rw/MemoryHeap.cpp create mode 100644 src/miami/rw/MemoryHeap.h create mode 100644 src/miami/rw/MemoryMgr.cpp create mode 100644 src/miami/rw/MemoryMgr.h create mode 100644 src/miami/rw/NodeName.cpp create mode 100644 src/miami/rw/NodeName.h create mode 100644 src/miami/rw/RwHelper.cpp create mode 100644 src/miami/rw/RwHelper.h create mode 100644 src/miami/rw/RwMatFX.cpp create mode 100644 src/miami/rw/RwPS2AlphaTest.cpp create mode 100644 src/miami/rw/TexRead.cpp create mode 100644 src/miami/rw/TexturePools.cpp create mode 100644 src/miami/rw/TexturePools.h create mode 100644 src/miami/rw/TxdStore.cpp create mode 100644 src/miami/rw/TxdStore.h create mode 100644 src/miami/rw/VisibilityPlugins.cpp create mode 100644 src/miami/rw/VisibilityPlugins.h create mode 100644 src/miami/save/Date.cpp create mode 100644 src/miami/save/Date.h create mode 100644 src/miami/save/GenericGameStorage.cpp create mode 100644 src/miami/save/GenericGameStorage.h create mode 100644 src/miami/save/MemoryCard.cpp create mode 100644 src/miami/save/MemoryCard.h create mode 100644 src/miami/save/PCSave.cpp create mode 100644 src/miami/save/PCSave.h create mode 100644 src/miami/save/SaveBuf.h create mode 100644 src/miami/skel/crossplatform.cpp create mode 100644 src/miami/skel/crossplatform.h create mode 100644 src/miami/skel/dc/dc.cpp create mode 100644 src/miami/skel/dc/dc.h create mode 100644 src/miami/skel/events.cpp create mode 100644 src/miami/skel/events.h create mode 100644 src/miami/skel/glfw/glfw.cpp create mode 100644 src/miami/skel/platform.h create mode 100644 src/miami/skel/skeleton.cpp create mode 100644 src/miami/skel/skeleton.h create mode 100644 src/miami/skel/win/gtavc.ico create mode 100644 src/miami/skel/win/resource.h create mode 100644 src/miami/skel/win/win.cpp create mode 100644 src/miami/skel/win/win.h create mode 100644 src/miami/skel/win/win.rc create mode 100644 src/miami/text/Messages.cpp create mode 100644 src/miami/text/Messages.h create mode 100644 src/miami/text/Pager.cpp create mode 100644 src/miami/text/Pager.h create mode 100644 src/miami/text/Text.cpp create mode 100644 src/miami/text/Text.h create mode 100644 src/miami/vehicles/Automobile.cpp create mode 100644 src/miami/vehicles/Automobile.h create mode 100644 src/miami/vehicles/Bike.cpp create mode 100644 src/miami/vehicles/Bike.h create mode 100644 src/miami/vehicles/Boat.cpp create mode 100644 src/miami/vehicles/Boat.h create mode 100644 src/miami/vehicles/CarGen.cpp create mode 100644 src/miami/vehicles/CarGen.h create mode 100644 src/miami/vehicles/Cranes.cpp create mode 100644 src/miami/vehicles/Cranes.h create mode 100644 src/miami/vehicles/DamageManager.cpp create mode 100644 src/miami/vehicles/DamageManager.h create mode 100644 src/miami/vehicles/Door.cpp create mode 100644 src/miami/vehicles/Door.h create mode 100644 src/miami/vehicles/Floater.cpp create mode 100644 src/miami/vehicles/Floater.h create mode 100644 src/miami/vehicles/HandlingMgr.cpp create mode 100644 src/miami/vehicles/HandlingMgr.h create mode 100644 src/miami/vehicles/Heli.cpp create mode 100644 src/miami/vehicles/Heli.h create mode 100644 src/miami/vehicles/Plane.cpp create mode 100644 src/miami/vehicles/Plane.h create mode 100644 src/miami/vehicles/Train.cpp create mode 100644 src/miami/vehicles/Train.h create mode 100644 src/miami/vehicles/Transmission.cpp create mode 100644 src/miami/vehicles/Transmission.h create mode 100644 src/miami/vehicles/Vehicle.cpp create mode 100644 src/miami/vehicles/Vehicle.h create mode 100644 src/miami/weapons/BulletInfo.cpp create mode 100644 src/miami/weapons/BulletInfo.h create mode 100644 src/miami/weapons/Explosion.cpp create mode 100644 src/miami/weapons/Explosion.h create mode 100644 src/miami/weapons/ProjectileInfo.cpp create mode 100644 src/miami/weapons/ProjectileInfo.h create mode 100644 src/miami/weapons/ShotInfo.cpp create mode 100644 src/miami/weapons/ShotInfo.h create mode 100644 src/miami/weapons/Weapon.cpp create mode 100644 src/miami/weapons/Weapon.h create mode 100644 src/miami/weapons/WeaponEffects.cpp create mode 100644 src/miami/weapons/WeaponEffects.h create mode 100644 src/miami/weapons/WeaponInfo.cpp create mode 100644 src/miami/weapons/WeaponInfo.h create mode 100644 src/miami/weapons/WeaponType.h create mode 100644 src/tools/adf2mp3.cpp rename {dreamcast => src/tools}/analyze-profile.cpp (100%) create mode 100644 src/tools/animtool.cpp rename {dreamcast => src/tools}/aud2adpcm.c (98%) create mode 100644 src/tools/coltool.cpp rename {dreamcast => src/tools}/extract-sfx.cpp (100%) rename {dreamcast => src/tools}/imgtool.cpp (93%) rename {dreamcast => src/tools}/pack-sfx.cpp (100%) create mode 100644 src/tools/streamheaderpack.cpp rename {dreamcast => src/tools}/texconv.cpp (79%) create mode 100755 vendor/dc-load-ser/loader.elf rename {src/core => vendor/librw/src}/common_defines.h (100%) rename {src/skel => vendor/librw/src}/fcaseopen.h (100%) rename {src/math => vendor/librw/src}/float16.h (100%) rename {dreamcast => vendor}/pvrtex/.gitignore (100%) rename {dreamcast => vendor}/pvrtex/Makefile (100%) rename {dreamcast => vendor}/pvrtex/README (100%) rename {dreamcast => vendor}/pvrtex/README.md (100%) rename {dreamcast => vendor}/pvrtex/avstring.c (100%) rename {dreamcast => vendor}/pvrtex/bprint.c (100%) rename {dreamcast => vendor}/pvrtex/compat/va_copy.h (100%) rename {dreamcast => vendor}/pvrtex/config.h (100%) rename {dreamcast => vendor}/pvrtex/crc.c (100%) rename {dreamcast => vendor}/pvrtex/dither.cpp (100%) rename {dreamcast => vendor}/pvrtex/elbg.c (100%) rename {dreamcast => vendor}/pvrtex/elbg.h (100%) rename {dreamcast => vendor}/pvrtex/file_common.c (100%) rename {dreamcast => vendor}/pvrtex/file_common.h (100%) rename {dreamcast => vendor}/pvrtex/file_dctex.c (100%) rename {dreamcast => vendor}/pvrtex/file_dctex.h (100%) rename {dreamcast => vendor}/pvrtex/file_pvr.c (100%) rename {dreamcast => vendor}/pvrtex/file_pvr.h (100%) rename {dreamcast => vendor}/pvrtex/file_tex.c (100%) rename {dreamcast => vendor}/pvrtex/file_tex.h (100%) rename {dreamcast => vendor}/pvrtex/lfg.c (100%) rename {dreamcast => vendor}/pvrtex/libavcodec/elbg.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/attributes.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/attributes_internal.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/avassert.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/avconfig.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/avstring.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/avutil.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/bprint.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/bswap.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/common.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/crc.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/dynarray.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/error.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/internal.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/intfloat.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/intreadwrite.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/lfg.c (100%) rename {dreamcast => vendor}/pvrtex/libavutil/lfg.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/libm.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/log.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/macros.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/mathematics.c (100%) rename {dreamcast => vendor}/pvrtex/libavutil/mathematics.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/md5.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/mem.c (100%) rename {dreamcast => vendor}/pvrtex/libavutil/mem.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/pixfmt.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/rational.c (100%) rename {dreamcast => vendor}/pvrtex/libavutil/rational.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/thread.h (100%) rename {dreamcast => vendor}/pvrtex/libavutil/version.h (100%) rename {dreamcast => vendor}/pvrtex/log.c (100%) rename {dreamcast => vendor}/pvrtex/main.c (100%) rename {dreamcast => vendor}/pvrtex/md5.c (100%) rename {dreamcast => vendor}/pvrtex/mem.c (100%) rename {dreamcast => vendor}/pvrtex/mycommon.c (100%) rename {dreamcast => vendor}/pvrtex/mycommon.h (100%) rename {dreamcast => vendor}/pvrtex/nvmath.h (100%) rename {dreamcast => vendor}/pvrtex/optparse.h (100%) rename {dreamcast => vendor}/pvrtex/optparse_impl.c (100%) rename {dreamcast => vendor}/pvrtex/pixel.h (100%) rename {dreamcast => vendor}/pvrtex/pvr_texture.c (100%) rename {dreamcast => vendor}/pvrtex/pvr_texture.h (100%) rename {dreamcast => vendor}/pvrtex/pvr_texture_encoder.c (100%) rename {dreamcast => vendor}/pvrtex/pvr_texture_encoder.h (100%) rename {dreamcast => vendor}/pvrtex/readme_unformatted.txt (100%) rename {dreamcast => vendor}/pvrtex/stb_image.h (100%) rename {dreamcast => vendor}/pvrtex/stb_image_impl.c (100%) rename {dreamcast => vendor}/pvrtex/stb_image_resize.h (100%) rename {dreamcast => vendor}/pvrtex/stb_image_resize_impl.c (100%) rename {dreamcast => vendor}/pvrtex/stb_image_write.h (100%) rename {dreamcast => vendor}/pvrtex/stb_image_write_impl.c (100%) rename {dreamcast => vendor}/pvrtex/tddither.c (100%) rename {dreamcast => vendor}/pvrtex/tddither.h (100%) rename {dreamcast => vendor}/pvrtex/vqcompress.c (100%) rename {dreamcast => vendor}/pvrtex/vqcompress.h (100%) diff --git a/.gitignore b/.gitignore index 6e7ad76b..32ef11d6 100644 --- a/.gitignore +++ b/.gitignore @@ -366,27 +366,46 @@ src/extras/GitSHA1.cpp *.d *.sim.o *.sim.o3 -dreamcast/1ST_READ.BIN -dreamcast/IP.BIN -dreamcast/re3.cdi -dreamcast/re3.iso -dreamcast/re3.elf -dreamcast/re3.elf.bin -dreamcast/re3-sim.elf -dreamcast/dca3.cdi -dreamcast/dca3.iso -dreamcast/dca3.elf -dreamcast/dca3.elf.bin -dreamcast/dca3-sim.elf -dreamcast/texconv* -dreamcast/imgtool* -dreamcast/extract-sfx* -dreamcast/pack-sfx* -dreamcast/analyze-profile* -dreamcast/aud2adpcm* -dreamcast/repack-data -dreamcast/output.map -dreamcast/dca3.ds.iso -dreamcast/git-version.h -dreamcast/git-version.tmp + +liberty/1ST_READ.BIN +liberty/IP.BIN +liberty/*.cdi +liberty/*.iso +liberty/dca-liberty.elf +liberty/dca-liberty.elf.bin +liberty/dca-liberty-sim.elf +liberty/animtool* +liberty/texconv* +liberty/imgtool* +liberty/coltool* +liberty/streamheaderpack* +liberty/extract-sfx* +liberty/pack-sfx* +liberty/analyze-profile* +liberty/aud2adpcm* +liberty/repack-data +liberty/output.map +liberty/git-version + +miami/1ST_READ.BIN +miami/IP.BIN +miami/*.cdi +miami/*.iso +miami/dca-miami.elf +miami/dca-miami.elf.bin +miami/dca-miami-sim.elf +miami/animtool* +miami/texconv* +miami/imgtool* +miami/coltool* +miami/streamheaderpack* +miami/extract-sfx* +miami/pack-sfx* +miami/analyze-profile* +miami/aud2adpcm* +miami/adf2mp3* +miami/repack-data +miami/output.map +miami/git-version + .DS_Store diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index deb86975..71854ac9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ image: name: ghcr.io/kos-builds/kos-dc:latest-14.1.0 entrypoint: ["/bin/sh", "-c", 'echo gitlab command: "$@" && /bin/bash -c "$@"'] -build-dreamcast: +build-dreamcast-liberty: variables: CCACHE_BASEDIR: $CI_PROJECT_DIR CCACHE_DIR: $CI_PROJECT_DIR/ccache @@ -23,13 +23,13 @@ build-dreamcast: - make -C /opt/toolchains/dc/kos/kernel/arch/dreamcast/sound/arm - make -C /opt/toolchains/dc/kos -j $(nproc) script: - - cd dreamcast + - cd liberty - make -j $(nproc) artifacts: paths: - - dreamcast/dca3.elf + - liberty/dca-liberty.elf -build-dreamcast-ide-32mb: +build-dreamcast-miami: variables: CCACHE_BASEDIR: $CI_PROJECT_DIR CCACHE_DIR: $CI_PROJECT_DIR/ccache @@ -47,13 +47,13 @@ build-dreamcast-ide-32mb: - make -C /opt/toolchains/dc/kos/kernel/arch/dreamcast/sound/arm - make -C /opt/toolchains/dc/kos -j $(nproc) script: - - cd dreamcast - - make -j $(nproc) WITH_IDE=1 WITH_32MB=1 TARGET=dca3-ide-32mb.elf + - cd miami + - make -j $(nproc) artifacts: paths: - - dreamcast/dca3-ide-32mb.elf + - miami/dca-miami.elf -build-texconv: +build-texconv-liberty: variables: CCACHE_BASEDIR: $CI_PROJECT_DIR CCACHE_DIR: $CI_PROJECT_DIR/ccache @@ -68,13 +68,13 @@ build-texconv: - update-ccache-symlinks script: - export PATH="/usr/lib/ccache:$PATH" - - cd dreamcast + - cd liberty - make texconv -j $(nproc) artifacts: paths: - - dreamcast/texconv + - liberty/texconv -build-texconv-clang: +build-texconv-liberty-clang: variables: CCACHE_BASEDIR: $CI_PROJECT_DIR CCACHE_DIR: $CI_PROJECT_DIR/ccache @@ -89,14 +89,14 @@ build-texconv-clang: - update-ccache-symlinks script: - export PATH="/usr/lib/ccache:$PATH" - - cd dreamcast + - cd liberty - CC=clang CXX=clang++ make texconv -j $(nproc) - mv texconv texconv-clang artifacts: paths: - - dreamcast/texconv-clang + - liberty/texconv-clang -build-sim: +build-texconv-miami: variables: CCACHE_BASEDIR: $CI_PROJECT_DIR CCACHE_DIR: $CI_PROJECT_DIR/ccache @@ -106,19 +106,61 @@ build-sim: - $CCACHE_DIR stage: build before_script: - - dpkg --add-architecture i386 - apt update - - apt install -y build-essential gcc g++ gcc-multilib g++-multilib libx11-dev:i386 ccache + - apt install -y build-essential gcc g++ ccache - update-ccache-symlinks script: - export PATH="/usr/lib/ccache:$PATH" - - cd dreamcast + - cd miami + - make texconv -j $(nproc) + artifacts: + paths: + - miami/texconv + +build-texconv-miami-clang: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - apt update + - apt install -y build-essential gcc g++ clang ccache + - update-ccache-symlinks + script: + - export PATH="/usr/lib/ccache:$PATH" + - cd miami + - CC=clang CXX=clang++ make texconv -j $(nproc) + - mv texconv texconv-clang + artifacts: + paths: + - miami/texconv-clang + +build-sim-liberty: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - apt update + - apt install -y build-essential gcc g++ libx11-dev ccache + - update-ccache-symlinks + script: + - export PATH="/usr/lib/ccache:$PATH" + - cd liberty - make -f sim.mk -j $(nproc) artifacts: paths: - - dreamcast/dca3-sim.elf + - liberty/dca-liberty-sim.elf -build-sim-clang: +build-sim-liberty-clang: variables: CCACHE_BASEDIR: $CI_PROJECT_DIR CCACHE_DIR: $CI_PROJECT_DIR/ccache @@ -128,24 +170,55 @@ build-sim-clang: - $CCACHE_DIR stage: build before_script: - - dpkg --add-architecture i386 - apt update - - apt install -y build-essential gcc g++ gcc-multilib g++-multilib libx11-dev:i386 clang ccache + - apt install -y build-essential gcc g++ libx11-dev clang ccache - update-ccache-symlinks script: - export PATH="/usr/lib/ccache:$PATH" - - cd dreamcast - - CC=clang CXX=clang++ make -f sim.mk -j $(nproc) TARGET=dca3-sim-clang.elf + - cd liberty + - CC=clang CXX=clang++ make -f sim.mk -j $(nproc) TARGET=dca-liberty-sim-clang.elf artifacts: paths: - - dreamcast/dca3-sim-clang.elf + - liberty/dca-liberty-sim-clang.elf -pages: - stage: deploy +build-sim-miami: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - apt update + - apt install -y build-essential gcc g++ libx11-dev ccache + - update-ccache-symlinks script: - - echo "The site will be deployed to $CI_PAGES_URL" + - export PATH="/usr/lib/ccache:$PATH" + - cd miami + - make -f sim.mk -j $(nproc) artifacts: paths: - - public - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH \ No newline at end of file + - miami/dca-liberty-sim.elf + +build-sim-miami-clang: + variables: + CCACHE_BASEDIR: $CI_PROJECT_DIR + CCACHE_DIR: $CI_PROJECT_DIR/ccache + cache: + - key: ccache-$CI_JOB_NAME + paths: + - $CCACHE_DIR + stage: build + before_script: + - apt update + - apt install -y build-essential gcc g++ libx11-dev clang ccache + - update-ccache-symlinks + script: + - export PATH="/usr/lib/ccache:$PATH" + - cd miami + - CC=clang CXX=clang++ make -f sim.mk -j $(nproc) TARGET=dca-miami-sim-clang.elf + artifacts: + paths: + - miami/dca-miami-sim-clang.elf \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 82b6d2fe..5aa93a55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -140,6 +140,12 @@ "svd": "cpp", "print": "cpp", "strstream": "cpp", - "regex": "cpp" + "regex": "cpp", + "cassert": "cpp", + "barrier": "cpp", + "coroutine": "cpp", + "future": "cpp", + "latch": "cpp", + "syncstream": "cpp" } } diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index fedf86aa..00000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,100 +0,0 @@ -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 deleted file mode 100644 index b8be02bc..00000000 --- a/CODING_STYLE.md +++ /dev/null @@ -1,107 +0,0 @@ -# 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.md b/README.md index 35ab7f09..1f696bf0 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,12 @@ You will also need the following tools installed ### Cloning the dca3-game repo and downloading the prebuilt elf - Open dreamsdk shell -- type `git clone https://gitlab.com/skmp/dca3-game.git` (and press enter) +- type `git clone --branch alpha https://gitlab.com/skmp/dca3-game.git` (and press enter) - It should take a moment and successfully clone the repo - type `cd dca3-game/dreamcast` (and press enter) - type `explorer .` (and press enter). - A folder named dreamcast with some files should be open. Keep it on the side. -- Download the artifacts from https://gitlab.com/skmp/dca3-game/-/jobs/8725216645 +- Download the Alpha Prebuilt Elf from https://gitlab.com/skmp/dca3-game/-/jobs/8725216645 - Open artifacts.zip and extract dca3.elf to the folder that was kept open before. - Close the folder and dreamsdk shell diff --git a/autoconf/LICENSE.txt b/autoconf/LICENSE.txt deleted file mode 100644 index eb1b1720..00000000 --- a/autoconf/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 064ea795..00000000 --- a/autoconf/api.lua +++ /dev/null @@ -1,305 +0,0 @@ ---- --- 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 deleted file mode 100644 index 6c99f9da..00000000 --- a/autoconf/autoconf.lua +++ /dev/null @@ -1,18 +0,0 @@ ---- --- 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 deleted file mode 100644 index fdb5f405..00000000 --- a/autoconf/clang.lua +++ /dev/null @@ -1,27 +0,0 @@ ---- --- 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 deleted file mode 100644 index 34520139..00000000 --- a/autoconf/gcc.lua +++ /dev/null @@ -1,27 +0,0 @@ ---- --- 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 deleted file mode 100644 index b96a82ec..00000000 --- a/autoconf/msc.lua +++ /dev/null @@ -1,62 +0,0 @@ ---- --- 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 deleted file mode 100644 index dcf4da33..00000000 --- a/cmake/FindMilesSDK.cmake +++ /dev/null @@ -1,34 +0,0 @@ -# - 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 deleted file mode 100644 index 5381af48..00000000 --- a/cmake/FindSndFile.cmake +++ /dev/null @@ -1,67 +0,0 @@ -# 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 deleted file mode 100644 index aa59ad82..00000000 --- a/cmake/Findmpg123.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# - 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 deleted file mode 100644 index faae7645..00000000 --- a/cmake/Findopusfile.cmake +++ /dev/null @@ -1,64 +0,0 @@ -# - 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 deleted file mode 100644 index 87f691ad..00000000 --- a/cmake/GetGitRevisionDescription.cmake +++ /dev/null @@ -1,284 +0,0 @@ -# - 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 deleted file mode 100644 index 116efc4e..00000000 --- a/cmake/GetGitRevisionDescription.cmake.in +++ /dev/null @@ -1,43 +0,0 @@ -# -# 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 deleted file mode 100644 index cf3f974b..00000000 --- a/cmake/nx/NXFunctions.cmake +++ /dev/null @@ -1,38 +0,0 @@ -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/Debug/gta3.txt b/codewarrior/Debug/gta3.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/codewarrior/Release/gta3.txt b/codewarrior/Release/gta3.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/codewarrior/re3.mcp.xml b/codewarrior/re3.mcp.xml deleted file mode 100644 index 9a41471b..00000000 --- a/codewarrior/re3.mcp.xml +++ /dev/null @@ -1,15378 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]> - - - - - 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 deleted file mode 100644 index b6424eb2..00000000 --- a/conanfile.py +++ /dev/null @@ -1,135 +0,0 @@ -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/dreamcast/common.mk b/dreamcast/common.mk deleted file mode 100644 index 2a0365eb..00000000 --- a/dreamcast/common.mk +++ /dev/null @@ -1,396 +0,0 @@ -GIT_VERSION := $(shell git describe --always --tags --long --dirty 2>/dev/null || echo "NO_GIT") -CI_JOB_ID ?= 00000000 - - -git-version.tmp: - @echo "Generating git-version.tmp with GIT_VERSION = \"$(GIT_VERSION)\"" - @echo "#pragma once" > git-version.tmp - @echo "#ifndef VERSION_H" >> git-version.tmp - @echo "#define VERSION_H" >> git-version.tmp - @echo "#define GIT_VERSION \"$(GIT_VERSION)\"" >> git-version.tmp - @echo "#define CI_JOB_ID \"$(CI_JOB_ID)\"" >> git-version.tmp - @echo "#endif // VERSION_H" >> git-version.tmp - -git-version.h: git-version.tmp - @if [ ! -f git-version.h ] || ! cmp -s git-version.tmp git-version.h; then \ - echo "Updating git-version.h"; \ - cp git-version.tmp git-version.h; \ - else \ - echo "git-version.h is up to date. No change."; \ - fi - -.PHONY: git-version.tmp - -../src/skel/dc/dc.cpp: git-version.h - - -# List all of your C files here, but change the extension to ".o" -# Include "romdisk.o" if you want a rom disk. -RE3_OBJS = \ - ../src/animation/AnimBlendAssocGroup.o \ - ../src/animation/AnimBlendAssociation.o \ - ../src/animation/AnimBlendClumpData.o \ - ../src/animation/AnimBlendHierarchy.o \ - ../src/animation/AnimBlendNode.o \ - ../src/animation/AnimBlendSequence.o \ - ../src/animation/AnimManager.o \ - ../src/animation/Bones.o \ - ../src/animation/CutsceneMgr.o \ - ../src/animation/FrameUpdate.o \ - ../src/animation/RpAnimBlend.o \ - \ - ../src/buildings/Building.o \ - ../src/buildings/Treadable.o \ - \ - ../src/collision/ColBox.o \ - ../src/collision/ColLine.o \ - ../src/collision/Collision.o \ - ../src/collision/ColModel.o \ - ../src/collision/ColPoint.o \ - ../src/collision/ColSphere.o \ - ../src/collision/ColTriangle.o \ - ../src/collision/TempColModels.o \ - ../src/collision/VuCollision.o \ - \ - ../src/control/AutoPilot.o \ - ../src/control/Bridge.o \ - ../src/control/CarAI.o \ - ../src/control/CarCtrl.o \ - ../src/control/Curves.o \ - ../src/control/Darkel.o \ - ../src/control/GameLogic.o \ - ../src/control/Garages.o \ - ../src/control/NameGrid.o \ - ../src/control/OnscreenTimer.o \ - ../src/control/PathFind.o \ - ../src/control/Phones.o \ - ../src/control/Pickups.o \ - ../src/control/PowerPoints.o \ - ../src/control/Record.o \ - ../src/control/Remote.o \ - ../src/control/Replay.o \ - ../src/control/Restart.o \ - ../src/control/RoadBlocks.o \ - ../src/control/SceneEdit.o \ - ../src/control/Script.o \ - ../src/control/Script2.o \ - ../src/control/Script3.o \ - ../src/control/Script4.o \ - ../src/control/Script5.o \ - ../src/control/Script6.o \ - ../src/control/ScriptDebug.o \ - ../src/control/TrafficLights.o \ - \ - ../src/core/Accident.o \ - ../src/core/Cam.o \ - ../src/core/Camera.o \ - ../src/core/CdStreamDC.o \ - ../src/core/Clock.o \ - ../src/core/ControllerConfig.o \ - ../src/core/Debug.o \ - ../src/core/Directory.o \ - ../src/core/EventList.o \ - ../src/core/FileLoader.o \ - ../src/core/FileMgr.o \ - ../src/core/Fire.o \ - ../src/core/Frontend.o \ - ../src/core/FrontEndControls.o \ - ../src/core/Frontend_PS2.o \ - ../src/core/Game.o \ - ../src/core/IniFile.o \ - ../src/core/Lists.o \ - ../src/core/main.o \ - ../src/core/MenuScreens.o \ - ../src/core/MenuScreensCustom.o \ - ../src/core/obrstr.o \ - ../src/core/Pad.o \ - ../src/core/Placeable.o \ - ../src/core/PlayerInfo.o \ - ../src/core/Pools.o \ - ../src/core/Profile.o \ - ../src/core/Radar.o \ - ../src/core/Range2D.o \ - ../src/core/Range3D.o \ - ../src/core/re3.o \ - ../src/core/References.o \ - ../src/core/Stats.o \ - ../src/core/Streaming.o \ - ../src/core/SurfaceTable.o \ - ../src/core/timebars.o \ - ../src/core/Timer.o \ - ../src/core/TimeStep.o \ - ../src/core/User.o \ - ../src/core/Wanted.o \ - ../src/core/World.o \ - ../src/core/ZoneCull.o \ - ../src/core/Zones.o \ - \ - ../src/entities/Dummy.o \ - ../src/entities/Entity.o \ - ../src/entities/Physical.o \ - \ - ../src/fakerw/fake.o \ - \ - ../src/math/math.o \ - ../src/math/Matrix.o \ - ../src/math/Quaternion.o \ - ../src/math/Rect.o \ - ../src/math/Vector.o \ - \ - ../src/modelinfo/BaseModelInfo.o \ - ../src/modelinfo/ClumpModelInfo.o \ - ../src/modelinfo/MloModelInfo.o \ - ../src/modelinfo/ModelIndices.o \ - ../src/modelinfo/ModelInfo.o \ - ../src/modelinfo/PedModelInfo.o \ - ../src/modelinfo/SimpleModelInfo.o \ - ../src/modelinfo/TimeModelInfo.o \ - ../src/modelinfo/VehicleModelInfo.o \ - \ - ../src/objects/CutsceneHead.o \ - ../src/objects/CutsceneObject.o \ - ../src/objects/DummyObject.o \ - ../src/objects/Object.o \ - ../src/objects/ObjectData.o \ - ../src/objects/ParticleObject.o \ - ../src/objects/Projectile.o \ - \ - ../src/peds/CivilianPed.o \ - ../src/peds/CopPed.o \ - ../src/peds/EmergencyPed.o \ - ../src/peds/Gangs.o \ - ../src/peds/Ped.o \ - ../src/peds/PedAI.o \ - ../src/peds/PedChat.o \ - ../src/peds/PedDebug.o \ - ../src/peds/PedFight.o \ - ../src/peds/PedIK.o \ - ../src/peds/PedPlacement.o \ - ../src/peds/PedRoutes.o \ - ../src/peds/PedType.o \ - ../src/peds/PlayerPed.o \ - ../src/peds/Population.o \ - \ - ../src/renderer/Antennas.o \ - ../src/renderer/Clouds.o \ - ../src/renderer/Console.o \ - ../src/renderer/Coronas.o \ - ../src/renderer/Credits.o \ - ../src/renderer/Draw.o \ - ../src/renderer/Fluff.o \ - ../src/renderer/Font.o \ - ../src/renderer/Glass.o \ - ../src/renderer/Hud.o \ - ../src/renderer/Instance.o \ - ../src/renderer/Lines.o \ - ../src/renderer/MBlur.o \ - ../src/renderer/Particle.o \ - ../src/renderer/ParticleMgr.o \ - ../src/renderer/PlayerSkin.o \ - ../src/renderer/PointLights.o \ - ../src/renderer/RenderBuffer.o \ - ../src/renderer/Renderer.o \ - ../src/renderer/Rubbish.o \ - ../src/renderer/Shadows.o \ - ../src/renderer/Skidmarks.o \ - ../src/renderer/SpecialFX.o \ - ../src/renderer/Sprite.o \ - ../src/renderer/Sprite2d.o \ - ../src/renderer/TexList.o \ - ../src/renderer/Timecycle.o \ - ../src/renderer/WaterCannon.o \ - ../src/renderer/WaterLevel.o \ - ../src/renderer/Weather.o \ - \ - ../src/rw/ClumpRead.o \ - ../src/rw/Lights.o \ - ../src/rw/MemoryHeap.o \ - ../src/rw/MemoryMgr.o \ - ../src/rw/NodeName.o \ - ../src/rw/RwHelper.o \ - ../src/rw/RwMatFX.o \ - ../src/rw/RwPS2AlphaTest.o \ - ../src/rw/TexRead.o \ - ../src/rw/TexturePools.o \ - ../src/rw/TxdStore.o \ - ../src/rw/VisibilityPlugins.o \ - \ - ../src/skel/crossplatform.o \ - ../src/skel/events.o \ - ../src/skel/skeleton.o \ - ../src/skel/dc/dc.o \ - \ - ../src/text/Messages.o \ - ../src/text/Pager.o \ - ../src/text/Text.o \ - \ - ../src/vehicles/Automobile.o \ - ../src/vehicles/Boat.o \ - ../src/vehicles/CarGen.o \ - ../src/vehicles/Cranes.o \ - ../src/vehicles/DamageManager.o \ - ../src/vehicles/Door.o \ - ../src/vehicles/Floater.o \ - ../src/vehicles/HandlingMgr.o \ - ../src/vehicles/Heli.o \ - ../src/vehicles/Plane.o \ - ../src/vehicles/Train.o \ - ../src/vehicles/Transmission.o \ - ../src/vehicles/Vehicle.o \ - \ - ../src/weapons/BulletInfo.o \ - ../src/weapons/Explosion.o \ - ../src/weapons/ProjectileInfo.o \ - ../src/weapons/ShotInfo.o \ - ../src/weapons/Weapon.o \ - ../src/weapons/WeaponEffects.o \ - ../src/weapons/WeaponInfo.o \ - \ - ../src/audio/AudioCollision.o \ - ../src/audio/AudioLogic.o \ - ../src/audio/AudioManager.o \ - ../src/audio/AudioScriptObject.o \ - ../src/audio/DMAudio.o \ - ../src/audio/MusicManager.o \ - ../src/audio/PolRadio.o \ - ../src/audio/sampman_miles.o \ - ../src/audio/sampman_oal.o \ - \ - ../src/save/Date.o \ - ../src/save/GenericGameStorage.o \ - ../src/save/MemoryCard.o \ - ../src/save/PCSave.o \ - \ - ../src/extras/debugmenu.o \ - ../src/extras/frontendoption.o \ - ../src/extras/postfx.o \ - ../src/extras/screendroplets.o \ - \ - ../src/vmu/vmu.o \ - ../vendor/miniLZO/minilzo.o \ - \ - -# Excluded \ - ../src/extras/custompipes.o \ - ../src/extras/custompipes_d3d9.o \ - ../src/extras/custompipes_gl.o \ - ../src/core/CdStream.o \ - ../src/core/CdStreamPosix.o \ - ../src/extras \ - ../src/extras/GitSHA1.cpp.in \ - ../src/core/AnimViewer.o \ - -RW_OBJS = \ - ../vendor/librw/src/anim.o \ - ../vendor/librw/src/base.o \ - ../vendor/librw/src/camera.o \ - ../vendor/librw/src/charset.o \ - ../vendor/librw/src/clump.o \ - ../vendor/librw/src/engine.o \ - ../vendor/librw/src/error.o \ - ../vendor/librw/src/frame.o \ - ../vendor/librw/src/geometry.o \ - ../vendor/librw/src/geoplg.o \ - ../vendor/librw/src/hanim.o \ - ../vendor/librw/src/image.o \ - ../vendor/librw/src/light.o \ - ../vendor/librw/src/matfx.o \ - ../vendor/librw/src/pipeline.o \ - ../vendor/librw/src/plg.o \ - ../vendor/librw/src/prim.o \ - ../vendor/librw/src/raster.o \ - ../vendor/librw/src/render.o \ - ../vendor/librw/src/skin.o \ - ../vendor/librw/src/texture.o \ - ../vendor/librw/src/tristrip.o \ - ../vendor/librw/src/userdata.o \ - ../vendor/librw/src/uvanim.o \ - ../vendor/librw/src/world.o \ - \ - ../vendor/librw/src/dc/rwdc.o \ - ../vendor/librw/src/dc/alloc.o - -# Excluded \ - ../vendor/librw/src/d3d-x/d3d.o \ - ../vendor/librw/src/d3d-x/d3d8.o \ - ../vendor/librw/src/d3d-x/d3d8render.o \ - ../vendor/librw/src/d3d/d3d8.o \ - ../vendor/librw/src/d3d/d3d8matfx.o \ - ../vendor/librw/src/d3d/d3d8render.o \ - ../vendor/librw/src/d3d/d3d8skin.o \ - ../vendor/librw/src/d3d/d3d9.o \ - ../vendor/librw/src/d3d/d3d9matfx.o \ - ../vendor/librw/src/d3d/d3d9render.o \ - ../vendor/librw/src/d3d/d3d9skin.o \ - ../vendor/librw/src/d3d/d3d.o \ - ../vendor/librw/src/d3d/d3ddevice.o \ - ../vendor/librw/src/d3d/d3dimmed.o \ - ../vendor/librw/src/d3d/d3drender.o \ - ../vendor/librw/src/d3d/xbox.o \ - ../vendor/librw/src/d3d/xboxmatfx.o \ - ../vendor/librw/src/d3d/xboxskin.o \ - ../vendor/librw/src/d3d/xboxvfmt.o \ - \ - ../vendor/librw/src/gl/gl3.o \ - ../vendor/librw/src/gl/gl3device.o \ - ../vendor/librw/src/gl/gl3immed.o \ - ../vendor/librw/src/gl/gl3matfx.o \ - ../vendor/librw/src/gl/gl3pipe.o \ - ../vendor/librw/src/gl/gl3raster.o \ - ../vendor/librw/src/gl/gl3render.o \ - ../vendor/librw/src/gl/gl3shader.o \ - ../vendor/librw/src/gl/gl3skin.o \ - ../vendor/librw/src/gl/wdgl.o \ - ../vendor/librw/src/gl/glad/glad.cXXX \ - \ - ../vendor/librw/src/ps2/pds.o \ - ../vendor/librw/src/ps2/ps2.o \ - ../vendor/librw/src/ps2/ps2device.o \ - ../vendor/librw/src/ps2/ps2matfx.o \ - ../vendor/librw/src/ps2/ps2raster.o \ - ../vendor/librw/src/ps2/ps2skin.o \ - -INCLUDE = \ --I../src/animation \ --I../src/audio \ --I../src/buildings \ --I../src/collision \ --I../src/control \ --I../src/core \ --I../src/entities \ --I../src/extras \ --I../src/fakerw \ --I../src/math \ --I../src/modelinfo \ --I../src/objects \ --I../src/peds \ --I../src/renderer \ --I../src/rw \ --I../src/save \ --I../src/skel \ --I../src/text \ --I../src/vehicles \ --I../src/weapons \ --I../src/audio/eax \ --I../src/audio/oal \ --I../src/extras/shaders \ --I../src/extras/shaders/obj \ --I../src/skel/glfw \ --I../src/skel/win \ -\ --I../vendor/librw \ -\ --I../vendor/miniLZO - -DEFINES = -DRW_DC -DLIBRW $(if $(WITH_LOGGING),-DWITH_LOGGING) $(if $(WITH_DCLOAD),-DDC_CHDIR=/pc) \ - $(if $(WITH_BEEPS),-DWITH_BEEPS) -FLAGS = -fpermissive -Wno-sign-compare -Wno-parentheses -Wno-maybe-uninitialized \ - -Wno-format -Wno-strict-aliasing -Wno-unused-variable \ - -Wno-unused-but-set-variable -Wno-write-strings \ - -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion \ - -Wno-multichar -Wno-unused-value -Wno-char-subscripts -Wno-reorder \ - -Wno-unused-function -Wno-class-memaccess -fno-permissive - -CPPFLAGS += $(INCLUDE) $(DEFINES) $(FLAGS) -CFLAGS += -std=gnu17 $(CPPFLAGS) -CXXFLAGS += -std=gnu++20 $(CPPFLAGS) \ No newline at end of file diff --git a/dreamcast/gen-mesh-variants.py b/dreamcast/gen-mesh-variants.py deleted file mode 100644 index 9668b6a6..00000000 --- a/dreamcast/gen-mesh-variants.py +++ /dev/null @@ -1,114 +0,0 @@ - -def variant(small_xyz, pad_xyz, small_uv): - return f""" - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - 0, - 0, - 0, - 0, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - 0, - 0, - 0, - 0, - &submitMesh, - &submitMesh, - &submitMesh, - &submitMesh, - - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - 0, - 0, - 0, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - 0, - 0, - 0, - 0, - &submitMesh, - 0, - &submitMesh, - - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - 0, - 0, - 0, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - &submitMesh, - 0, - 0, - 0, - 0, - 0, - &submitMesh, - 0, - &submitMesh, -""" - -str = "" -str += variant(False, False, False) -str += variant(False, False, True) -str += variant(False, True, False) -str += variant(False, True, True) -str += variant(True, False, False) -str += variant(True, False, True) -str += variant(True, True, False) -str += variant(True, True, True) - -print(str) \ No newline at end of file diff --git a/dreamcast/save.ico b/dreamcast/save.ico deleted file mode 100644 index 64f7c6745e51c8842c7279f8ba754eda2b1e2dd3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1526 zcmZQzU}RuoP*4zHU}Runc)`TLpvJ&}!slaVV9)@GF)%PVFo4vvFyI4>j9d(y%tZ_u z8k!9Jobwp;Ej<|g{K6Tmd@>l4JNGlpShs~?<(01t|NsBTFh)s9(NIuKP*7MbO;}9O z#YIsN0)!3Igbfp2ToiYM!A3zdF=1md5f?`=clSnPGcjR9F%dx+UpOr-(b!N#+);Wr z_ipapg2riSg2IA=6;2SouyLB0h@hlkBSKzG#Brja2u#0mB8V@E#7`7a6r6?37t)ek z=;8=*o}h4IqLAP!5f>LHCBvx*NDj-Q07mNf!X8}^uIRgU|10w?)18RDoOH`V7ad8M>;ACK6 z<($dN$xvPn%2yz-vIfj#4VX|~?(hx_9xz05vW9Z9mluG!?;eCka>@mXiioDdqo z&QQSj4g?uOXU=3`Wnkwj0rB|Wv4+m%WM??Quo@xH$-;AhVFTQ}37jlE3f%{b_kz=Av7R>fnh^A z7`V7_hO)A9GBAK-%gf7MTpT6@1WaJy1j~X9aN!K)<%KG6ap2m`!oYL)!gH{K5*LQm8(a>Y z;k&>9;+MEMaqVVtIdFghVt|W_!RiezE(dtd-n|QQFj%*X3j@#Dy9^)=Al)gDd^!*+G042e52;c>&lE7Z(?IknQE=V08mX003ot Ba037U diff --git a/dreamcast/settings.ico b/dreamcast/settings.ico deleted file mode 100644 index 5230be86eb1657d75fc63aab39c391fe0e821995..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 766 zcmZQzU}RuqP*4zHU}Runc)`TLAjZJJpuxbvpuoVu;J^TqXJNnxLJ1^67qr=ylVOwYP)+G3if_t`2YVuhA~>JAYhS_l9G~^lG3VGt3WIz zrA4chv=%8XTD3}P6-aQ^qD89~f%r;FtCSXLX=y1z_)1zR{8d`3K;j^IuzrwXs}^a2 zj9j&9)gq7$N-!Hhf?7&SFc+)>*#&k0#6_!CX{|!xf=q@u1*!n70mN2X1=74~mDVC9 z2BlStv=%9WG=uC@VwkC zixjwb@7|WZd-o#k-Mcp_?cTj%_wL=>cJJQ3352zGgL&I_Z`i$S_r~3uw0BE`WI_Cm zyLav0wQ6BElph)jSHEHRhTR(&7`S)u-T=b8x9r}sd&BPS<>k9K8yj=)hVgfA-@Vy- zw>8LyExUK`-oAS`h`)O`NQL+A-R0H0cN-h;-o1g0IQq5# diff --git a/gamefiles/TEXT/JAPANESE.gxt b/gamefiles/liberty/TEXT/JAPANESE.gxt similarity index 100% rename from gamefiles/TEXT/JAPANESE.gxt rename to gamefiles/liberty/TEXT/JAPANESE.gxt diff --git a/gamefiles/TEXT/american.gxt b/gamefiles/liberty/TEXT/american.gxt similarity index 100% rename from gamefiles/TEXT/american.gxt rename to gamefiles/liberty/TEXT/american.gxt diff --git a/gamefiles/TEXT/english.gxt b/gamefiles/liberty/TEXT/english.gxt similarity index 100% rename from gamefiles/TEXT/english.gxt rename to gamefiles/liberty/TEXT/english.gxt diff --git a/gamefiles/TEXT/french.gxt b/gamefiles/liberty/TEXT/french.gxt similarity index 100% rename from gamefiles/TEXT/french.gxt rename to gamefiles/liberty/TEXT/french.gxt diff --git a/gamefiles/TEXT/german.gxt b/gamefiles/liberty/TEXT/german.gxt similarity index 100% rename from gamefiles/TEXT/german.gxt rename to gamefiles/liberty/TEXT/german.gxt diff --git a/gamefiles/TEXT/italian.gxt b/gamefiles/liberty/TEXT/italian.gxt similarity index 100% rename from gamefiles/TEXT/italian.gxt rename to gamefiles/liberty/TEXT/italian.gxt diff --git a/gamefiles/TEXT/polish.gxt b/gamefiles/liberty/TEXT/polish.gxt similarity index 100% rename from gamefiles/TEXT/polish.gxt rename to gamefiles/liberty/TEXT/polish.gxt diff --git a/gamefiles/TEXT/russian.gxt b/gamefiles/liberty/TEXT/russian.gxt similarity index 100% rename from gamefiles/TEXT/russian.gxt rename to gamefiles/liberty/TEXT/russian.gxt diff --git a/gamefiles/TEXT/spanish.gxt b/gamefiles/liberty/TEXT/spanish.gxt similarity index 100% rename from gamefiles/TEXT/spanish.gxt rename to gamefiles/liberty/TEXT/spanish.gxt diff --git a/gamefiles/data/PARTICLE.CFG b/gamefiles/liberty/data/PARTICLE.CFG similarity index 100% rename from gamefiles/data/PARTICLE.CFG rename to gamefiles/liberty/data/PARTICLE.CFG diff --git a/gamefiles/data/main_d.scm b/gamefiles/liberty/data/main_d.scm similarity index 100% rename from gamefiles/data/main_d.scm rename to gamefiles/liberty/data/main_d.scm diff --git a/gamefiles/data/main_freeroam.scm b/gamefiles/liberty/data/main_freeroam.scm similarity index 100% rename from gamefiles/data/main_freeroam.scm rename to gamefiles/liberty/data/main_freeroam.scm diff --git a/gamefiles/gamecontrollerdb.txt b/gamefiles/liberty/gamecontrollerdb.txt similarity index 100% rename from gamefiles/gamecontrollerdb.txt rename to gamefiles/liberty/gamecontrollerdb.txt diff --git a/gamefiles/models/fonts_j.txd b/gamefiles/liberty/models/fonts_j.txd similarity index 100% rename from gamefiles/models/fonts_j.txd rename to gamefiles/liberty/models/fonts_j.txd diff --git a/gamefiles/models/fonts_p.txd b/gamefiles/liberty/models/fonts_p.txd similarity index 100% rename from gamefiles/models/fonts_p.txd rename to gamefiles/liberty/models/fonts_p.txd diff --git a/gamefiles/models/fonts_r.txd b/gamefiles/liberty/models/fonts_r.txd similarity index 100% rename from gamefiles/models/fonts_r.txd rename to gamefiles/liberty/models/fonts_r.txd diff --git a/gamefiles/models/frontend_ds3.txd b/gamefiles/liberty/models/frontend_ds3.txd similarity index 100% rename from gamefiles/models/frontend_ds3.txd rename to gamefiles/liberty/models/frontend_ds3.txd diff --git a/gamefiles/models/frontend_ds4.txd b/gamefiles/liberty/models/frontend_ds4.txd similarity index 100% rename from gamefiles/models/frontend_ds4.txd rename to gamefiles/liberty/models/frontend_ds4.txd diff --git a/gamefiles/models/frontend_nsw.txd b/gamefiles/liberty/models/frontend_nsw.txd similarity index 100% rename from gamefiles/models/frontend_nsw.txd rename to gamefiles/liberty/models/frontend_nsw.txd diff --git a/gamefiles/models/frontend_x360.txd b/gamefiles/liberty/models/frontend_x360.txd similarity index 100% rename from gamefiles/models/frontend_x360.txd rename to gamefiles/liberty/models/frontend_x360.txd diff --git a/gamefiles/models/frontend_xone.txd b/gamefiles/liberty/models/frontend_xone.txd similarity index 100% rename from gamefiles/models/frontend_xone.txd rename to gamefiles/liberty/models/frontend_xone.txd diff --git a/gamefiles/models/generic.txd b/gamefiles/liberty/models/generic.txd similarity index 100% rename from gamefiles/models/generic.txd rename to gamefiles/liberty/models/generic.txd diff --git a/gamefiles/models/menu.txd b/gamefiles/liberty/models/menu.txd similarity index 100% rename from gamefiles/models/menu.txd rename to gamefiles/liberty/models/menu.txd diff --git a/gamefiles/models/nswbtns.txd b/gamefiles/liberty/models/nswbtns.txd similarity index 100% rename from gamefiles/models/nswbtns.txd rename to gamefiles/liberty/models/nswbtns.txd diff --git a/gamefiles/models/particle.txd b/gamefiles/liberty/models/particle.txd similarity index 100% rename from gamefiles/models/particle.txd rename to gamefiles/liberty/models/particle.txd diff --git a/gamefiles/models/ps3btns.txd b/gamefiles/liberty/models/ps3btns.txd similarity index 100% rename from gamefiles/models/ps3btns.txd rename to gamefiles/liberty/models/ps3btns.txd diff --git a/gamefiles/models/x360btns.txd b/gamefiles/liberty/models/x360btns.txd similarity index 100% rename from gamefiles/models/x360btns.txd rename to gamefiles/liberty/models/x360btns.txd diff --git a/gamefiles/neo/carTweakingTable.dat b/gamefiles/liberty/neo/carTweakingTable.dat similarity index 100% rename from gamefiles/neo/carTweakingTable.dat rename to gamefiles/liberty/neo/carTweakingTable.dat diff --git a/gamefiles/neo/neo.txd b/gamefiles/liberty/neo/neo.txd similarity index 100% rename from gamefiles/neo/neo.txd rename to gamefiles/liberty/neo/neo.txd diff --git a/gamefiles/neo/rimTweakingTable.dat b/gamefiles/liberty/neo/rimTweakingTable.dat similarity index 100% rename from gamefiles/neo/rimTweakingTable.dat rename to gamefiles/liberty/neo/rimTweakingTable.dat diff --git a/gamefiles/neo/worldTweakingTable.dat b/gamefiles/liberty/neo/worldTweakingTable.dat similarity index 100% rename from gamefiles/neo/worldTweakingTable.dat rename to gamefiles/liberty/neo/worldTweakingTable.dat diff --git a/gamefiles/miami/TEXT/american.gxt b/gamefiles/miami/TEXT/american.gxt new file mode 100644 index 0000000000000000000000000000000000000000..ca03b34c1c0906e32d1a960bfb542cc152bb05ab GIT binary patch literal 424134 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�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%0D1Kfl3Qd&IYxhK%oms380z;W(p{+fLsbIEkG#= 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
Z}* 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%0D1BD7CtPZ^Ho1WVnIvmjw0NK;Z`} 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/TEXT/french.gxt b/gamefiles/miami/TEXT/french.gxt new file mode 100644 index 0000000000000000000000000000000000000000..ece0d8ed4383144c80cd3291b7b49809a2305805 GIT binary patch literal 470132 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%0AA?}iXUV- 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�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

JEg5dX*hU5n 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

hh(g-L=fYJ=4Jpu9=%w&ivNH{{;EQrzx6f&Sx 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

P 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

fYxq;+z&DXl-@yk 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%0AfSp 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/TEXT/italian.gxt b/gamefiles/miami/TEXT/italian.gxt new file mode 100644 index 0000000000000000000000000000000000000000..8b6d317c843fcd492b09fa68b5b5bfb374ffba10 GIT binary patch literal 459086 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�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

l--A5fbD)Gq;r3#g0&(V!J< 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/TEXT/russian.gxt b/gamefiles/miami/TEXT/russian.gxt new file mode 100644 index 0000000000000000000000000000000000000000..375323ba4d9e460495c6a4e569062e0bd9dfd445 GIT binary patch literal 420604 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

(<1i1;+HUh0= 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�V2NVJLvJO&K_tVB%&B@8E261_@@ExCKKPoNdVf zx}g4_M)8J*;C*}$wh;qp zbP2*XX4u9BQ*XlX3C=cUSjG(#H)BZSfw9dQ4#C+L3^BYgaZ83e7~9Bz;S!u}$nXKq zHezt#gQ_<&W|#nHn=pKWvrWOHNMN@anK88S!_=EIOn|d382&)m;JI-w0k9hI+_)c{ zZOl*sXPYqWfwN5+Yy@HI%@}UL+2#y6LNIX)1}0$`+may;#x^uymGyH?IO&DTCVdAC?>)>oNh9_{gIfIuNOpOIY9-M8-a0$jXGGK@ihpI6$WVi=s z8!^~Oz{HIiY9Q=*Cqo8?OE9((1A~hsSWUc>F$2RE7#noMpcGWxl!0LejBO4Xp}Qvq zYa?S@T?Hy{LA|bd3`@Y}1E_ol&EbLC(x4G1&`LK@iw9OhgGva<%nqpB0+j-wau8G^ zKw7<^T#l&CKxG`LBmlLXKr8A&qjZpR3DgGyjgl`1_r^eC3&Hk-$|YpgkX;Or9yny= z9n|KT4&A2!DwjcZBuGD~tb(|FHUnrz4OFtPWB|#6+ze{}AnSzG=OEwC2G5p4`re?H z7^v(9^|V2C0cds=(g%j91oiSkts9VAK{YO@HU_n5AmX5PyPz>8&|Dp;jRGoFLE#H& zcY)joDiINFL(rT7x(^_wEu`Ipdfox3L7+9sf0 z5U92Soi+rj#UN{bK94j>;PdQYI%D#RzC z*;J4pmV#HBg2EisV}SHnLG2#UDhkkC2xJc!D0V<2O`vqbzyKPB0M)jj&<6G9L8$|B z{v4=ga%KSC!3x=T(!sDAd_xpS1+2FPaxLf#2vA!VQs04UQP8LwY*iB|EI{cTG`|Ll zXGkc4+yFT@1+@196lS0h1m!JID+kn$0@;Gd8z8r|F@Rza)D8#v5Y*0tga;@WECjE4 z1%(=H+!drB=1NeD0FnbiBeQeCyDuSgd7ye4qyuCtC13Cd5Pyox+03@Q&H_qKuBdXO{;@)@Xw3Q9Gg9v&!W zQo&~zL&_vj?uN|Bf=V3pbI?F>0*W`#sfD1D3v}Z*s6+ylUZDI1N*SQg2bJZZmLw>C zL2Wir9}zUFk_aBBgp>}*xdD>CKt6}G^dW6HP?-v87lPInfMN+WLksH9fm{yq6Qphc z^*BMjLP&}O#WAF%4k{f${XAH>g8U5`8v&Iapi&7`qd?*Wl9xbz0Z?88mEVvvJr;u3 zfPhvhLrj?uJ);DYOF=0BRIY-~mIIX^u#~C80J?<&U2HE4z!K`IH7o0mmDIAnS zK;a281D1jyr3WabRDf6AgJKb+4m67fqCq`-Pzr{ndPs>2>h(bU2kLo%W}-oE28C2B z1IW*i+y<)8K%oyxi=eV~A^4tFkejim50JYdBWa+1B`AM@)I$19p!@(TS3q$Ky73-V zj)P(V#0SMSWL5^0raK|%EnD2;=9*`PDMK_k1M8VEG@3`*nc7(n}8K=B4jiJ-8pU`S;E?fwJR#;{Zh ziY1U;pw0j06QK1o&ES*tn;1an1B3E3tbB!x z;DPcR$Q7^{2H5~gosjYpBm-JK2pWL}t&fMf59D@GE&`QNsHqL)Vo(f&!T?mtf#xkh zJAXl~4v;QTtbyurP)`-)E6^+>$OoV>1o;Nkih=Y^L3YDj1q`49OLs)CVdhL17OX{Q;Q|N`Ig-7?c-4AqkpM1N9U^>(D@M0o600a(5N@d}q)N zq@bBLP^^MQG2{fZPJ|2Pi*7+R&)(hNM_f%puYnsFeg7)dP)}fNTV{ zDj|0}L)KTJx&*`nl|Y~o7f^bGjTwVVJxHkvSyu`Q8PFU#C|*IK3My+M{SMISB#>MJ zs@Xv?1u8{Bp#*F1KtdLhR#U<07?k!vCc@e~pt29-&qM}Lh{EzdtS$wW$e?+Ke5hWK z*{Ip2>pmGf)3vv^rJcgA$FjCpu+j$Ji`Jga>e2h<)!ZCgS58W3NC{05nyfSi~N8es$FEKsWjl13rx z20>!8z_Of%=Ic^FciyP@V*x zx&~P<3(C2mln1gAl0&Yglv zQqasjs4Wl5%OJm3GOS@(01h>T`#~;*trCEhlb|*PD9RJFO_+oC`%D3Bkptr!5s5~QpG)q|k@d7%6; z7u+KTH%JgCeB)p($kxR3#4#yarXRv^aW~j zgJKG@&J|Q@!fXMB1*kU+>S3<}=X+2Y2r2tPtq(}q0-DoY2;B<`86N`0GQ>Tgc}CEF zGSCK9Jrfg6gL0Vm) zx(Jj`U~}p)pMz4|4DcEhNIXJP1;}1ddII?$l%`;&gW9B^c!IbavW^W>CxF5Z6q2x> z7sz~2xjzS-Iv_aVbe-0kEBDsJ6h`ub|Kdm9HSbfKnbP+(4r!h;|_8WCX-G z0;I(Vavvy6L3tOnA_6p{14+$m7$9rdoxx{%L(YT(*#Qb6P%MH5C z!lVa1F$$CqVI?Lg6EX$>vLCYc2h{cg@j$)= zjUX%ruYd&k2sEn>2|tj_KxqL~W`Ig>&IjD9jLVkXssQnMg_@&$|?{aqyki?fzk#je}Z;_f?UqP z;K~51H$dqdHT@yy7?4jueR$9e87MxM!D}l}3IMgJA*BUqRC5t{p9LtzRWht+0F9f1 zMmRvdMTii;d+$Jb8e(b> zc#kG*-A)}t0Ye=FqHhgR1MwXwoY8Ful?0$N6uI<(s0O7ENWO!V!3Y~cHwhumdIr@a zkQFbcJo3^t79lI#Q$z@YdRU-UC7^N?M1$6)fbtp0c2G?R8z*q{3~~(#@&@1dHvuxK%m5M#2=Zs> z0L>mlZ}tPvB*(i(gn0UaPnmU#cXRagF=P+{%_M`_MhqrkHt5DbLxvzQJ2(V%2jvks zJA}cEiGd;5Gsx91-VnSe0>U<8C}L({2o4BxjEpyA*u?^4n}E;qXJTVuAbj5(C>KNS zwVMqd3xxD+KsN+}@)oFl4DmatoP(qcP)sB03Q*YqYFC5e8eau=xPgp|RE^aSO17jnkHigt+GZ7+e|R89W&L8G;!67(n+=LP{1;cnogWfmY}f>)!wdR|Xe` zcm_8HPlg}{SB7u~S8y8l2m2XRra^oSX@BC14^RsjR5OF(1Jr*4-FC1Xyp{}45Xuk)c5?_guG|^>JdjKP#WKjg0sySR>9fk;QMGH>Ma;-m|^NI!8h82#SIM@&cVbD4Z-Ku zLBx$1tXQDx4UHKZ;A|6yUvRc5LkTNPjTyr`INO3DiVY@i$*=^*HZlO8`2aTG$dDm| z9jeC2h+zf?jBN~F+XPnQ$iSe&2^Du@U;v#-2Nri`U^oF2cVS?70b{!|Fx=vTs&`{x zXyAsj-5D5Oz}OxP41PRNaZd(@BQUlXWKB#MFJ%4+)T#iLo}e?9LHz?zj~LP_0=0rb zJs?Q_0nK`(G88a?dcvSK52$R-hL7McW`N``Q2KzBo%qWVkUCJ_)L}p_OF-oUXssTo z#s!UBKuUN>yAm`;0xBOs^(Uwwx*Tjbs1GvgkHHonkoGlb%pWu!02(U+jaNbHThN^F zLI%(%0;o?1qCssO&^mQc{Q|0kLG3J1?+(;H2eo`a@{m(NKx6xmkvh;Q(>exFpA6C? z1oiGfYnwoGFWKNR(+21{qoCQqxzN!}P~UO}14swRE>KGoRLg+e1zP3K8B2bK-Ok}#u4U& z=UYI&MU26L?0~5Q^`Jmzf^yV6aBhO+bV#ockphO}8Dz$e&)!V}cT z2gMYq9|+m!1M72I-e@u#e18q7Z2)P7gJKobIslmr%Il!ECbi2H&{!p8 zL<*EkK&}PV<|EN&NS_Bi|AEp5XxAxd> zIbERD2e45-(2N0SyaSXLL8p6w`ZS&4KDO zkUu~p_9mLRI7q)hV@1u=HYV_sI)+>A3}!a(+yG!TK$s`E*Bu>D8x4)KY&VTP(K)yLm=eR`e?{ z^nuUc0QnI#1_#lr!eGc?z+k`tYWIVBsGzYGP~8r58K{g&1>c(qDnmi58zH3&B)mcO z2c*9bvJ+G`fMOCniZf2`tn>{+bOQ;eo_2XqDA$@CXzB zJ8?mA2}#4CG7?l5fa)`lC@5`!QatDeLC7t|5SKvGGiW^`DBXknjh=EraS94=P=6Y< z`ga~UHbAQfK&xs%p$eKU0r?bE4?^a4K%oQ57oaczg&)YLpb-L4T!F?HA+xF=lc$4s z=YncakbD6HXuJ}11_{J0P?}%KFbi7NLt0y)+747kLP88=2Fxd*`EpQ+017iunuGZb zl2RaH3#!vV@}Lp}G|vTMgUki-LG?Rm^hJRIwC@X)5<&Ghq%{X}4`k&Ds70B`fEcF* z)#@w3>j*$27Lf60Q0jx6q6~@$P!0x#5opy0Z2TV7S_GvE(25IC{D5j_PzZqH3^e); z8drgs0;=WF%Rq?PAisj@M9|nAXk;AJ3kBsjP>Bd?3xh%*G?D_Fp-KhkD~P{9?g!Ze zsxd)#nuA&#AhSSyVUT%{v42p!gW>=*LkDwZG6N_)K`9b4Hvqb3LH600~==yFn>_0rb=^*z6oA9zf@SLF#AN7zw2O2kp@X&2rBN z&wg}*^D?O92hBu+TmdO-K3Ug2z0Ik&lg$1a*fw(1+0n}at zg&}00JgAfajfH{QOQ3cFB$a?dXD;~eF_0KYH>jjUwFeXipf~`9B4ix}WR7n-c$XL` zkAVCG3VF~RB*+J#oCF#j1l_L!T3xu9VGeZOe<65ZEhyE1ViYu^1JXAae10h;)E6;8 z)_Q~TF=!2%79Tz@P?W8#6E%!Pq7Y42xiFQwGQ_Um*2njtt(>(>`DBr+@mmmH9i22y8$@-(Qu0J-fNlxkBM7Bhg#JJ75pDAj}d ziJR@5LC87Rsw-S3Uv1d$ghwYNl@5;%3{#EIFKl; zZvhHT(3)9L?gG`BiQqj3pgac3lc1ae%9Eg#2B10tw0{QFmjIRcp!QfId?tJ@1E?jl z4!pw@7Hg0d*`N{t6sI89f&2+7|3H2Qm5QJh0-(GJN)r%upc1DSyqf@YO2J$P$XSG_ zJu%R}VUW*Z_w0cBN}x6fsJsP*lmY{&w+C`J$gPmp3MkfAGJtwwpdA&Ub_!@MFsRJ{ zDyc!W;9>?)%Nmp-Kw$-%UkCN4KyeIOQ37%;DE1&N9^}&!KxRSeWKgLD8m9)e?pA^C zGzQI0gKP%XDxk4^P#OZ&G@x=5ly*U79VnN8VgzC`XdiR|cA`< z7$D~WfJS^k>v=(K3Q$>!-aiA44}os5hPAdq=?D}LpcOEnFbAzH0+|F_nE)!$K|6v# zwKF7dg4&9p`UsMiK{*@b0?_UDAR3fvKxHhVWd-s9B9=h%ka=WKN&@9{P{@N?M4&tf z@(0Kzv*CMjL16;godDWv3Q7Txx&Rb0p&GNo&(+b3ff1olmV1RKr7}! zu?9Lf4rDhhk1Rymg#l_=fbtl~MUXTI2_aC}f%JlU5};HCav6H-6yyWYY!*mAQjNj2eLK@l&?VH4RQx0TtT@3)P8}ehqNw0 zvLIU_H7=-hgPpqr>P>-K0I2mX$b3k@5i&mo8fTdco{2?Gk;uJ1)U*aFk3i)PXzep( z#uqd~0?PU5sbwLwJ_dydD7CBux6mQB_k&X>C{1Ci0{IpcyP$S0C>}v&CTKhd;dWFP zfl3(Ap0oMjJ_4vL1oh@Xc?Z-g1;raEw}V=qkh&dI5`xA*Ah8R{C)iyADlt$~E2u35 zTJ-{H*MQOxB>h5c0;NaL`7V$U0hKBs*C6j+0EGo8bwFYT!~(UwAoUO^wsjdGHh@Y1 zPmeWDOo_d1*8g8x`F1@L2d(uA}CfsVF9b7Ky5rw zuMre3pfm#FgVI_mxZeUQ*Fg0l$bL}xgX$@mTURkG1)nbkItvukzX#RRAR9nyJwPUa zYzL)bkd5;hAgghaYbj8<2wCF`%14k|1~Li<^E1fJi0}r59mwyHS{<@F6xN!CggPiK zLh43H_=7?a(w_u{Gic2lDEvAZAhMtk1BD>S7Kk~ZQ~)Y-L9q{NMMH8YDBd7x2^6NN z`2tkdfZPRXrGm-;Q0o(vlVN2EB0WLc%Am3mcCrpAgg|2~ppqK2eic-sg31yQAGB^8 zQU`&;9+C$@=7W3!%RQh{7CZys8sumUZjUlRcbvPs`Wb`Tpmq#kdEC9gtZW<9K5Wh6ixAC2S_fINrp7;U8r7 z#W>!?kl_$y_Qg2f#Du{OGUH+#Z(_=D0?syLxB+LIGdzQ{Eg1g5*_I44kXaeycvA+3 z5;)s{fsGd$FQ$eJNpQ9icqcm8{ienYui)Y)3|V|I^`;Cb;A}I7IDVM8Il}`u+k#<^ z08HGHAwm$!He+De1ZNvC_zA(p4H;g+*+vX)!Z2}T1|AU@+XTF89PBnTQ-&N-n7A2( zj2Mh<&M*hYHfLav6NidhFfgoxu`L-GWF(;CjtmT8Ft!r|!yFjfnStRAjP1g}kRS*)cfmTR>ZupdP~la18^}1gGx@2duA~ppUem;$zWL1VU{mc&d3Q2hZq;}A4z0PE3!Mi@XPGN?p^ zv_e5WL6Eya>#IOzD5x%gsfL*g@(V}}EQ~=TbFj89^7$%|(|kbXI!HCBR|JYpkk3Kk z2(lN{G6%T_(r$o^c7fcn7~Goy^(J3 zsP(lBytWDyS0EQbT(%J00tEROR5pNO3Y3;WdD$B*0~%F^%rbz|9w@JY!U+@upb>gR z$b#Yr5=Nlh1S&s4?K{x804NthT9cqr0MOhgsJsQ0iLj7>rA|<)0Hp;`t9=I8tsozR zVjSe_N@z%f*4cq_6=WwVB($=@BZr{81sW*;^_oFt38*&;$>ESR0cr<=TGF63a-bLl z`5Kh3Rzl}NLHQ2mCXiX6aD#;~WL_FH3Jl7PAX!NJ64IA~g&QaxRUo+&v8Ec-z6Q;d zfbuuQU7&D*v^_y?1%*1uO`!M!`36?jg6bTQji7u5(g7<65iS7LZXjQS`hAc)@gV6H zG>-*~3y?1%aRJf~i3gDH5WYp|0o^Q906jkn)K`GS0jMtxTC)do3n&~x@dV2Kkno3v z639$YT!H$?pdJb+MhV*p>e<2kJ9O(7P`ZSLCnP3eHB2GYME~sY)8U=uaE2M4(^==?`fyx|E9D&vtAjU}{a*$n( zpfm!Cv8B-d9ApF%R7S#P9ziN#c?aYUP+WsXUqJ0CkT_&@5ok^nR7-<&fmZ)O+yI#? z1eI8zlnQbQD7Aom3t4RlQbnDWcQAWFCC+g01ETH#mCT@?Jg7W?v?d_245~X}H4mu0 z3MubF@Xjv%`|LG=slgg;Q5 z5Rqa*E`YQgLAO3Y#w8IW0-#&~YG;93xS)^$nFuP?88CJPAZnvT@Op00xC1B+gIowJ zmGYtUMj+pUdwCrkai@*?Q_B7iJ;Jiv}YkT2WZU@Xq;pQ187ek#Ql)dh(SIC)gy>@ z3+NtmP+UXKl>v#nGITLaW@ut)1J|gKybiJ#R7ZeHnMDksRuU-mL2d`x4e<|Xb`Lb7 zyAs?+2i0ewk{F~8mi|F4BT!ii$t8%C1sbyf^&Al=tS<$h+XQkKa*BkURsgXL6w07} z6Qm>u#XiV2$axTP4iTty29@?8-^~U0x2F;m+T6>`K z1k}=p*a#~3KxdGGb{K=^-9aG=8dC(73+OAhK`9Ybf2N|WVui$QHrO7>E&xb<1ga}g zSF-vr_%k>&cr(N^gfKWV1TnZXxFW3)2c;oM`UQn9NH=Ij4yyY=sTkDPg4AoExB%r` z)Gz?;Wdfa^2I|p(N)m|shLYPsEe=Rrf>H(~JwirVL2Wxw3lo6i7CS0K~T(+s4YIPk6N4hBbtW`;@Nb`WSp2-d!bE-a6oM)P&oqS2&tP}><~5@;M2q8@Zs3ZxGN3MWVl5Y!F; zwU$BcR8UV76pEk}0jl9aZBfu#SCF4TDFf8X0rgHmrh`lbwc$WwpfO}nc?XL9eCVhm zsJ#Tz2OA>>se{Z_fc%@vpbNgS2Eqodc?9hm0;L>~??5glZR8kvd=*sUgVGeJ9ROxn_5K9IdoAbH4|LKE;D z?i=WuLT7&;6GHQePMYCl&?W~2jT)y4GnVld~oRq$(^9G6;v{SN(xxJ4%BZ1)#{)*22d#iDdRw) z1yTtrLqKi@jaq>G4Ke}b@8t}M;Pr?Q-4MS+?wSFO?|^E*W#HZ;s15?%J_A}UrU2Ce zTEhye3m~U)gF+Bg>Vax3kbhxy11PQ_Yn?%5ALu3+&}lQE@(N@+NDNXQf?NPx1-P;CPF z7L=Be*Gqxo2c{F`CQv&Gaxyg}#z1ioN;{w#eo#IJr8>~5LZEz&*bjiZf(bPSKy^GQ zMnQ1^s;xos0osKHnq>mbn1R})pnMC8$?4#cSx^fBG@1%Z|Dd=9r9n{L4;dc4H93VaavHX669LQ zct0f8A^us65gw3PA&C1yX#=DWbbd9cRzV(Z2bE5sl4v2gRSIdnK|%~Q{ejAL^e_aK zf1pwcG;;|`T_6{MN?Fk8FNBY5I>arIump`_HZrs_G=lF(1ew#t;KbTqC&u0V=~l?t_$|pb`R9u7gIGL25ug0M!k!wZ9;hAfJNDN!ZvnsO|;L2*Laf z+LHlV{|;Fh1zJ}CDp^4;291G((iEs}2hH$7MxR0FQA0{~P&k6-Xh5L@awX`VX~@Z? z3JjpL#X)=iAY!1A+_~V>??L;`VCNTtau+DQLF|L1e8_4@P#A&Q$spT7YiU5K4HVj- z@d3y_Z>Vbkv2uR%l zid{$>1(X{hZU?Qw0hM(iaZr7a2s=>844T~l%{+lhU64JXGzanrWJNxtO$5r5pwtD* zb)fMPP`rThF33bk-Ht!CLfnDy-5l_alT-#!N(H4IP@aY40#Ka}3tdo+3o2Pbwm`~R zNNEqM1wkvjLF=I)yImnOMF{gD=^fGvoei#eKrsz*1@fAB(3&q$Jr7DDpz$h*|3N2# zf=U&Lov=7Ym_U4ZBH{ovGYYc_5(l7?01*?QasZUJL8TSQZ=m)+L>;IM2ZcMtr=Z#d zbT(Hbcs3W(do~64o;M(R&!&#?h71{y-m|G=yb(hioNdgY0_icEI>wtYw87b?3}@hM zGw@k7&i+28j`8LU+aNt@Q^$A<1|~@F+0-fCkRb-nHez@MXB#t2fb^bCo#IUx_Q2Vu z41SOvv#C?O8N&}a+Z@(=Hg$@(U+l=81jBUigzy|3Vn}SwbgU+P^`8VE#fguVeZqC3k0min3 z-UjdQV+y*s$qdp2Xm! zpcAb@?OjOf1N9L=^%JOF531KdkZN>WK>ef$X#ZgexcvuO)e72E3hG;dQZA@14CxPm@-4`2kf{(igL2FY za6SXI@Id_wkPkp>MguYjlWykT^&uC|n@>2|<2@wFE%z3y9A_^$ujO z6)0|IgKJ+<`3_pimkJ(XgoFlY)DSc~3n_mT81H6cmG? z@)6WB0;vYI+CjUi85o)wKxWNmK;HudQUzLh2U@2KYAb_m2aUTx>JrqR5NLiJRI)<) zUa)a#Q2he3w-UU90@9Lz^bbI5!a%2Lf@(pKe?V@B>^%gfc1X-4Mw1{u0>u_6h9I>u zC>4O}OHjCjXju4y>;}0U)T3GjjR8=d1X@)Bs`F9ftAYVkFF?W<)Gh&~M-UIRR_{o*M0=Wj%8V1$upw=d+=7yY#3~DQYQphs+*|DIo0=c;Wysy_E9Dks7 zP$1WXQUb_Npm8oxDh0U=#_md(gC#@K)DjM-T`DDsBQxF_(5?7s*@q-5rJkW5Nl^ZEAl{f8ORP$D;YF92^zBj z&4z;7(;zue%LUYL2E`UA_fLoS<3YAS#+_%wcb9@p0;PLUC_=`TK{*YQ8bGIkr!s)< zK?Ak$LFo*X1ACx*qCu$`WFoA50JZ!<_JhI&)OrQ^7j)|Z$S zpy>cPv_YjOs60kKTVXob2e5msL3d_?Tm`ZpRCssgzbl!8E|Dk!%&gKtj&iGorMC`Y2}Wnkz4-~J345e4}O)Ybx-iCrAj zUq<8`P_6~-bp_QypcWXYH3TZF5d8*Fi4E}+sI3SpA3>=Cs1=Q#@*zD4h@G&}AJB>(P;CcsA4E55y$JFzC@xU-WrI@!D3yUy6{u|u z@-wK+2e}*6e}b(A0F6O_QXQ(PE5YL`pil#qO`v|?Z19K$$miH>1J$aaR^&?X>>g}{ z7gXPY$~RDI2IV|Z-*XALPXyZO0Xo$ul_43tZUD3b4l=F`(gPYH0r5cPJ*f8t3PI4Q zq5=b|D-q(Lln+WJkh^g}E`-hSg33{l>p&xKppXQOs(?}qs1$>ZzQWQasDxl(XaJv! z3yL97=?5x#ATb8o#|+AWptu0l|BJynela+GfyOKsGJrw{lutmp4pa}$2k$uoxdwDg zF(~DMQc5Ct&pk*Ta!LoufK<$f&R&Cbp~fjFt%Gs|$mO7R1t^3-p^L5))nt%4kRAu9 z?+B{5K)DE1-hzCEt(OH#4Y0C(9RsL_0o4$oIX6%^flBVB;5HVhPYbF`Af^X^*UW)R z29SF|r53161=X^UauPOP3o#K?9)ZFUHP1o9AJU5g^~F$SA)y5-KS1L|puQ?-9lbNk z`P|5{4e~31v(gpG>sD4Da8PtOU)sY~dKtcqPB0%{B5;~x;M2$^Q$q33rkk|nE z0W|UsQUhw?K}r=+?F(zKfodO6h=XP=fm$Mf z4rJFb$d@2IA3S~o2`N|#Az>viZ zRqw*Ua0AA6WnfTYfr`5^FqFX9?hFi|Iasj09t;e6tWY(c3=D2Cwig4#1sL0#fgyzr zs>X+b;RcND%fR5o4i)!fU|0cT`!g^+fw2P^7-Tr0Y62M;(qQZ$28MGmb}$1&7$;Os z2m|=GX0Y2r85j(>pyFW+3_)OaNW7sVWG`k97c8%U=0qU9Ay5ej+S3NAw`YUzvW4tV z0hMc@avW5?fXXvaid+D0`+-In=YiXQpj8~Obl$))i2*b^2U-mX8gmDg9iZ_^P<{Zl zg<$0XD0MwSP)TcCC(NDS240i`q$8`O`1oNovzc|f@s zlp8_q7f9L!)oGxT8q~)Ewag*=xIy^>)KdcGg4xjC0>n+Aas*@wC^VLVdn}+93hEsh zpn3q*q6PJtAi6+40hLmqyaGCZ8RUM<5g$l?1)Wv{x=9K&^MM%~5MO~@3UUFcw+$-i zK|TPrXc8G9@}Sudkh?%G1^EFM2cWP8l}Ml*3W_gK=>v*8Si2TfOM*rMVP->I5Ari2 z9zc8lK;<)WGhfw1og2&B^UO610XX% zu1y7}Pf(fxxeZj?gK8{DtbxjSkk3G+9LR21xd^IrLFGNj3{Z%J+L0jLARmKj*9G8G z6x8ztwT(ex2|AGs)SHF$^gw+w&|EL5-Uhi4)b0S)RG<`zT^Fbh0>u$1pMmD)A#*5@ z)*xu4736+UT!CB!at&KN6JD{}>AURMT0F8}6W)wiD>3~!~ zYEV#b3g#oo$s(ZEBWOkx)Pe)8ngG>=AiqJz6JX&A;z3#nkhBMy*U1O3><5J+Bt{`2 z1Ug*|bmL|s_})!OYZvXVW1KRw6XydTaa3G6?9$| zBmx;h0Qn5k3IVm?L16}R1;|#=SQ)6d0ty#UJ_7BW2F(zG^n&s-DE}d5tRblbwn`l`JsV zfaaP&HiA+Ns0|GoX$PfbknNz75tN!BZAC=c0vZParFf9fK&_+s44{~W+!G4ogF+P4 z#saN&0Oc-7nwtq8VE~Ck>;uVwTDPE@4mAD^s{26m=%7(1NKYG4w}5)0pppY54=NL9 zGC;>IL;T5&LvNWhO)vfQ~bRb^t-f!9e;z=?)ab)4^k5 zAhjSZTnI`RAfJH33{FqmULHNdIE+$+@$@ePvKzzzEtG z_yg()fZO~b;4>Se;A{hiGjO&c0~5H-4^m^qpaf$ZF)+lz*v1SD>tJjX28LZQwkZRH z9=QDO@e@0;}smW`o)>pt1v0 zD}nO(0*2`fu+j=tTY>6Bkn2FB4u};9F%rIxfxW-fy!P`jt0qr(g8vx zD9#`?BPcXM^D3Y+5EQ?Vw1`--1qx-5pFwpBs5S$s0hR9{vp{7$sHFo6AJB|5qyz-T zIAnYUwyFqZ2PjTKz61FcRExrDKnNc)9t?^{P_GX(7YHg_AnglKXoLDSAYUM-F3>nP ztUdsx2T&~us>LAtTp{&5qq4u;vxf#8j0rf#4@r2sK0gVBGN)b>$6EwyKN_CLF3MhZT z;t^8!LQWxuoMs9cmjsn=Al;x)0mUFJMj`166m}rjfqF@xbP1}LLG3lr*dQo{fpmdJ z0}*cXX8@T7Y5_ya0L-)wY8QakYJqNB1NAr{r6(w_f%+z(Iv3#si20y0dOrB>Mo7GX zS~Z})9w_C3>SfR@2E+xRF$_@Y2x`xPQaq^a1?65)+ZmK=K{_D4Sdc40rX%|q)Mf*j zgDXr{f>(!t>U(2oeg6br-}{2k#{=D^=i%sU9B;(HumfD*`-0D%1D)#(7B^vFr~udH zzTh+5{=wMh3=BEoy4=?oRDbV)u^ky0KEc>d3=DGMy4=?|-kE{H0>*Y>V2FaTT^Se- zz}RjK3~b;!-q$$Z9X=*#9Ph!vkOC9;WMH@gV|y_$NP+8kU*mXh258;yYaH(b8FxFv z3Tq`nde)${0NUpUYBz&Q%#{qF88%QVgp|0+44~V*K_l3pGysZIQ11rRZUw0Z&9b7l zt3l}tRDy!m*MeH9pfMXzE(Y~*K&cT_#=&d=r9DWw1&VRd>JQ3^aho? zpc`mFd&WRv1#%l`T@)xKg8J_ub)Y&3RQ5t#207srlutqR4aDsre}d94sKpK{e-Q46 z)IF%-j#_Sj+zBc>kbMKGVL-VNWDm$*P+Y*=1hC6N{BRwG7`=IA< zgGvWbp9<6?2etk|euKmXXzUIYZ=l;bKsrJgATf(*A3(YuIe7hdlt^fvxdoXd(JQEvKO%MaaAs9QDfuV>U zDjveXumr{qh16>o*g-1=kz)^$t3e^S5WG?VlqW&q42u7y43HK-BE=SfcN&9IHAn|& z*8u38c}P18a_=@|oCa1VfJ$IQx&!3^P+J6&@?hx{RC>;50QE;etvXP;2IXAX3N=tZ z0j>T5xoZ{!=$2|oNdT%fK=}k>CTJfNs0Rn?Lx6G=BppHQ0g<3|2J)2x!wm2VH=x=Z z#0I5#P|FH3;sNs~>aFmQvKrE}1+Az6^>slugW9;DbPDMkfx-ip8$fOa`2%D#DE)){ z0~(J9g&QR8!pdooJ3#pWG`nPVUwAa_Ch2XZZ_lmWG(A-!{mjUe}d z@;Rth1=Rpu(3{I(K8A%6$aK)?2B=>L3K38n7_`?9 zL8GgnHVJ54LxBNQUxQA?2i0&O3_7nI;#Sa_g&7QxehSCOa|Elb0a7XgX&*U310yoX9vX~C_RA2pK5}*_XnXd))^FcO)YGQ~Dh_C^L8>k%vYAb@qRzRkMY8p@+fKn7F7l2#=X}N*& z9H?ypDI-DSJeXkvawjMsg4zk7JOIjP%R5l3Gp|i?FPxIAoqf9b%K>MAe#_1A}C#Oe3JM8Wxd2Inko*Tx0m)OK9sIf;lu|%$1(mU&wh*LV0_8DCZwumjNKFLFvmiCFlne?5kY7RZ1KD+r9&?Mq zvrv%q21?bivN^FS#MG+GOaLG%;{(t(IYkdEo#77@sFNZ5hK1VDFz zL0YJu&aTcuu8>=U;cXmeXYl%*9#Fdm%r;;+0B0LAe1Wr#7&Jib8n7B;h8#HCgkcq& zZOTvqYS)0(m@#O8+A?6aIfEaZZNbn3XIp~%<(?olMhpyJKBOx}ze)E@=)-rB(T=Yi@+~4gIC6ZR-S>%Vo-Vp)sLXu462Jj zF$O80L2&>%Lm4z01WHGs5)6bv{Y=p8qb>ueRs+d`;u6xgYXr~GgYrM9PX$_k0ID(S z7!1KTri0Q~0RyPLgSfqgalNdnz_c|CrZUyb0gT)l6eF({cpj5tuA&~(zdI{>GfXWI`+X>_< zNDP5m2cUM|ba1YKv|B*o3L0SrwJagM15o^dX5>LG2bEn43@Qwe&<2%@UEp#HRHK8+ z8BqHNHg*WgTlwH|T2KuPsVPA|hm^RWQ4>%Z2Wnq|OkD}yzW`df3K~^~`G&Z3*U11{ zncoQCPYWA21(^@(>wtU=8fgLXL9qbpt3t{mkPK+vG8>#?Kk%}1gq#~7 zp@J=LtQZiXjbAs&ZLo9?(glejSU7^tCk3q`2h~^E43P0$kSjs)1#%6@m7p{NsdYj5 z5_vowaz+}iF>DV8e}*6iKZZK!NoSyP7E;fHQUoY&K_~650j~#v#5O1`fJ$^wSr2M0 zp!yt9Zh+zg)RzXWp#!yEVd)X%SCB72^G2X@AJkR{DhNU_XALKJoeFYka2Bl|E zIRdJuL2YQz2qP$mfaF2zu0dlepc)CYoB@@3kiAc!90Ce8P>r;Z0n$2yq-M~lKWG*J zl$Jm~LOzEd6dsTm2F)UYT3Mj_6V!)-j4#8~f?7DB&;r%8kkkWm4`}8C zxPjsf6f*M}Mn+13>|KQ9Z%}H4#3>|qtAp1!f=ZSG26b=`15$o~)Dd3>K|+@Nk_gml z0j0`;D2YHR9pZk_{zFhc19Xbb=(0?JpAe6SF_0tYlE0-F5>)lZ-> zTn=vIgGwCGxD9A@1(e?*r5ULF0)-H$)CSF;LvlN)?t+wRpxfm@bu{E$HrQx6qErN> zTF^KIXiO7SE`s_mpmGtkjulj5fyz)&=>eL(2eqgmt>j(?M}`J)JII>>GzSlAA0gT( zptcC;^Z*6$ifGX8e~=nTy9u;Tx`Sad14vCf11z_b(_+C@OM^lZ<|WqZ!mT!19)W+*sd@JhA>!9C7glb1dJWQz`zIV zsYEg`gW??4>jaHlfL1zy z>OfF#gp3@4+GC(KZ=kiapmrLh-3!V^ppp#Kiv*>1P%m;Jc0m|{9aSD*xpz#z?DFMl$pgJ9t z*FhuypgbMG02;A^v@9Xx1pN%4J9!}MvLWlJK%*`T86Yh$hzMw%Bxp_zl*2*05kVy+ zDBNHty@UAE89?n0WD!uW4zx!WwDum<%LBOqR3d@OuBG5vw@zr^9ps)=hNaLup+O@r zv%x!}K;<9A1)y30G}a1o0YnruGO-SPnj$EsIvGH17|=W%r0)nS(_r%~pmqqzZJ@pb zWONQR#{+8pfm&mrRePWk3)FUk%u6BM=MUb=hbs<1V{4#Oe;mQ*Cbcj?=DQHSTng^R zfI=PA+X2M_sFa=!9^nUt1IUe_@gC3$Hc+|(wcd+XvZ(2pMMvNE;cF{z2t5sC=$q0F76I zVh)t6K`9zk?}5?>XwMv|=9~*2O@`$MNNo<8M}gE>Ad^6CRL}?;Xl@sjfUxdhas2bB;Y6F?)Zpb!Sd1ZeFWvKq(;3#4pl2A``28;=K_ zUqsp{k*8lskUw-4&=%+|nVx>UofQcLs(~R+W}O! zgVY2wFdTugLl_uTKqHkP@lXbaG8j7yKI>{2AI`v_1RB8vsfmEx<_|h3la}c&M$d7+uY|tt%KCoW!Dz7>i+l7H)AB^qF!0-sh zc4J`RI%fx$%ps@I2sp$o?LWnfqaWBV~M zY=f{(K<<17V>>Z0@CkzTnt)cL<-yo43=D^0Y*z+`doZ>e14E1uRJ}U`Llcbc!N70~ z#`a`j&=ZEL@nT?zfw8?A7$(8kJ`4<7U~FFo1`ZLZdOrpRF$g=z-;jaf6pU@e!0-gd zHfCUu5(TRd@;8CZ{x^vt&;EnP1VQszpgO;j0aW`!N_7Q>2!>Q>?EorSLG?4JoCDP& zp!x??-$CkUQ27Shg#%fO1}Z~Atv*oc1UXd|q60Es3lhx+_p3qm8fexTR6By^#X$8A zUzk<|( z%1_jjLP32+(0CiDKF8js2GyvrS{&4O1(^>jTM;XwKq_FifZRR{zD5jU7pQIqjaY(4 zxj?lM$TyG{0I0MU+K&z=Bu?4B0W`WNl0ND)-Nl<$OGHV7}tq2*^50922HdM}`m2Zc5yc0u|;qk?n5?MhG^7c{??$^aU>1NjQpPk{7VKs6(%tp#d% zf%@!_xp2h%7${AE_9lW-0;s(QN)3n*gZTs&!jO~&G7q%=3A9cZ)Y^s2_^xCCtuX_o zNsyhOR0J9s1dU;W+AAPFXnYmaf&|3|D1<>ZHAufBcoiFD-RErZJOQX*25NtS!Uoi% zNd&J{0kzFQ@vFeFh5=*-WM=?qtv={3mwd1(pkCfQ2GFRxGx+X5Q0oko-w{g|Z;klD=z45WTkj(&Fg$~)%1+r%~1E}o?YT1E&1ZfF^TnO?jD7HcS070!&*vQUeh63DtjQM2FNCu??B@TAU}Zo1KKqWD)&Hl zM1XQN=o}VE2?EJSpgAu{Is>H|P%Z)GqvZ^s5*8H7p!OqlqBjgUTaF41!7wP<;mSAt*0`WI^jxK=wn*9*}Dx zBc`D61o0p8r7K1016#Y4Er;H zR_V+GmxiF;4QTcZWF9EBf=Xe~Y#6BY2AKtl<8=&>aSI0UC@m;NL8TE$2grx(z^AA~ z(i6z7h&6PeRtcz91oaUiXL^BF6M{->&>R_P%@OEk6Hr>gmTo{JS|GC^^h^fO%oHfZ zK&1j?%nD*QC>5<{&}CQ+ZpnbkCy=W^W&Rv+{Rc`5pmGQlN|1aEsmYc>?=At2j)8Q5 zVjmPkp!5UEaUeY)aZn6_#w}p8iJhA}Fju zu>&fPApQdB2e}w=avgI12k8UlEm+$GX2IDaid(6|X`wi7ao4RQ}?TpJYkpgai5zo0w^atY|>OwbrBD7-*v1k{29 z#Vo|n$hLs|3>mAO0}cm}4p9DsoK*oyJD{`#8Q}u462bRFfNTYo&mglPHzYyI2atb3 zH4JD(5p;4Qs1yUG9gsSR&ph*S-#y+AGlxfFDk2&lyfavLbLQ^EQ`W5S@c z1(}5dwe3MM2P(xtsT<^G(6|@qTpUntg|r1gp$%&5fKoQ7^aGt3577_0Lj>d!P#XiJ z3UoIRs3r&539<>~OHdesP9^}^4_avr$_0twvII1C3F=LN!W_0b6cpB=bde0Mp$r)e z7z`Ldx0Qix0o6pH+ZI9kKy@Z)RRhQ`pg9Og`wEnjVQq6ryBaiN2+BR6-ZiNA21;ij zy`Z=QwSggPraY0Gj1PFspCIhu(Kz%WYILK6xpF!o|46uJd zAqh(Hpfg@TF#u}eP6wX~0?M3}BZDpjhyC4%q31dReCx0FCF36Q-Y zvmx~ZBBy~y?H7P=ivX#Bq%u&63<@2P{h$&Gw2~GS3!v68s0IOvL3%5Yx)2oZp!5zZ zg+OTy6ndccAgHAT%1xlu3M+>}B@)PdP)s7~I8e(Ra+)b9e}Kw>h2XO;Ksg9hc0yV# zAQvuW0IeK?lr*4P8x|IjumP!uxe4S)NZSYGZ}jnaQ0@V_5tK3@AqQ#^gM0|;4}$8J z+2A=Q$c*4@aBmk@s?LYD+Ck}H2DrTgDx*QQA!wEY;(JgyfL7*1+U1}!5VArZ2FYa0P_{DBeI}4yqME zdO)rM)smoo03_7%8I~}B<|H6x=L`l=I}8+VAk!f^05-}3X^%t7!a3j*2|=L+8UMgu zF2QUCr5w;K2WWN=cC^2l)mxt^!I$ zpj-sXub}f1K)wa}0aONqQYpk2pu7euvq7a6sKf!a!9g`CDDFXd6EwdH@-=9N2y$*L zVudlt<)H8c`398cshfA%85n99q3RtO z7>>c%P7DlBU~JIdX(p%|7Y2rVFt#fLgAg-R+>L=D1;%z~VE6!IdoVC)u`n?B#s@eC z2m3JuurM$LxduCf&V8(dv5goQZo$~b3=CYXP&J@)TwrWd28IMSsJIye!y_2moPi;R z9V%`C8KJIWhpn&aU|0>lIS7VxV3Us09HUp#hDafbt5cGy>%W z&>SzQ?gWX$YB13G*Pyx%R6>JVaiIJKY72nMF;K|~SqTlZ71TQbwVXgDBBZSk+M5AM zYoPKFly^b8TEXX-fO6dohE?Fw6J|mpc>g?PJr$@F0ky~GfX4tqwKhT($V^0!2UIsM zg`Ru@(F-ckL25uFBA|Q^axbW!fb|wYWe>$j-dcfq_37Mk=jS+x)LZFii zK&x&+^)9G(1(l1Sk_A+gLuy{wcsi&@2}w?NY*w{R%>;tvMAmb09G7Qvq1=Vb@o%o=3Kd5#D z#X8KLpnlq1@W?o51R8QeDk3gGEget`1*8Mg#|F6pvQq`rQh~LLKrK#?O`!egiy2_` zB&ZGo`4ci818SFn`r4rQ0+|PCy@C2)AeVziR+fWTkAPeV>dApdt3Y)fhz5lQsD=R5 z2Oyt;&cFbTPJ`q@sS)NIP`&`2qYt|KW;(-qhIQZ@!9l46RF><4cR7OG018=9oegTK zL0W~Nu?~>m7J}F5focrUs1eBZpp*~FTcCC-$Xrm01lbD;1yHRFD#u{?29_5=y?0PN zfZ7x=F-TZ}d=JTyAU`ez=Maz>D93<&1oB-7xLpRaAJj$#(EdYE$^?zLgTevQ zMufQp6i%Q}gpBONL_y;}pfU)gAJTr9#Q=&=NP7&FYeA!ppxzgxUP82+L1UYs(h1aN z1o;@!G62={u+}mtWFW0xP+Ji+wg9TjL8$`N8-au}s6_`Vmq6_tkZ(YF2~>lxWB`RZ zsI>-~Speln$Q=)mwgRX&p2MIE&T|OYg7P$^6$n!WO3$EF1L{SB&d&zv1GRQQ?FmpT z5;EHb%RQiwhU7rVJOgM{3sg44(kiGt0gbGH_GN?afCIJOA-NkAE0Fa@5EDV63d`-F zIUSH6Abtkrsp;S~Tc9)qN3&W=qAD>HwurNY4V43PEKgET%!NZpgkANDU3j$B3F7Sr()NRN@sdEC=r*0@a3~ z6a~t6pqw)sJYorI8-Uukkhq78;X&Fepxzd!i~*Hspp*(qU!WQTlHMVy0@)uRaZt|% z*1rSEEyJ(_vf~HhVo)0bWGYBiBKWR1P;LRWpg?5{D90na1SALQKh1@Yw}HyDeDK&Z zC>$Z`K`Y#y!KXHWMw3#(cSnL;25QrRd<}9HEaX8t8W})))KI_EKq(rcB7(u6p@pFtd{0{&!&(N!?jkGbJ}^+qAziNll5UV+Kx+y? zc?i`0oP)GS3EeNCeQ_XvgVF}b*P#7yusIb_>VoW51MN}<%~62bD4-YwV>BLJ=x(DqRUJ1_mkQjxu${;xaWGg6tfZ`2QoXpoFM5OG!s<70C5Q< zUqDU`0@Veu{Y0R41gHfA>6L?00CE}wm8BrRf@+ev&~ygs(}LDDf(KFkFGLy%`upIG}2L7#NyhY+nY3S1`681Ne4vu-p6@7!Ghk z)dw&z@Nq%efeZ{+VC*0Uh9YjLcrXLQ8W=l-fuW2CDjv$funfWu0QEu-!Py23QoLX_ z0iYTKvSINO9Fgb$|Pl%WXDHe;9oXPYxDgR?CdF2LEA3?}?g zy+#ZSU2wJmgN6W1+>l`coNdJL2F^BSNDzdnF=1$fvrQQ|gka)k3`%geIYR@SZNcyk z&bDMw5{9ZbW?)zaXB#l6iNM4S89d-@BZf6_wlTv2INOBb5u9zxkR%GzYsRnu&NgRY z5`&3bFhs!FmJG{aY!e2CM{u?QgMc_xy@?@12AplgPzPrlGdzK_O&CNZVCqd7lHhDJ zh8J+QIYW>nOpOIY3!H7qa16#aWneIof~qk!VDNym4H+iD*+vZe;A~@tH*mHIgPt^0 zuNebF5{zxmz_19$wqRfok%6kQgp5`s$Ux2-0oAIYellcC0aSlO=7B)#c0p}2$WF3e zaBC7&dxGvpnG4-_3ak4-r7)<31@*B(Ee=TA7*bL%1fOIADx)EF0;pvR8cP7RJ3*tV zp#9RT8Rj!Uc142jt^)NtV68+@84BswgI1b=N=MMyRG@V|pwdtlaBmVa*MVM}fpme)gv=j;WFe(IW{nS;4Ok2wV}_mg4)P(Wln2#SpuX@zj2&d4 zz9wi!2jp8+6G8TZTF;;m0`-1DGb4~OdQiIoR0o4<0Z=LK{b zKyCqkiOk?=$tjEw+rd9g3=?X9*5Na3&C|bC_Z7~ z1qy%A92BT;3|eQ2eD^sh%s{CC6ds@+Jt&2O`X8Y74XE7+3Nb_v6Vy`zoplIGmyn(q zrg@P+9@4mILJph-)FfPGtbij)2AwKz;$`4N%-e`f;F^#0+qc z5!62d^?M-VFkgXsL7*^(tYTOIZYe=}u^@GzaT`!i5|YXxc?uL)p!PUyyxEF@fdS+y zP_Gp<%QPFh4g?gRpp*`3oq$phsJ#SoBghS)lmJS>pw_J0KrKy3YYx=f0@(-hHOR;F89*n#flL7LK=X&7-U)1M0^~a6@(C20puQ@o z{{@K|NGStaI|j-pptKHJD+U|U0oeq~y`Xplr8Q9c1hw2j@eLXc0F`8@{c+HY{apA+ z+d^{mt?;Ny(7IGR6BrG5$IVeqn`~VtX1hrBj zBR`Pb2P)%1eFRAD02vJh*$QfRYYWf_2`F4a zy;o3(g2q)qIzaIUiWg9gx&XY!2^1TkSb)SbBwfw~m%gA8W>7wZj9-BK04jeVX$q8o zL9PJFgGyIO$p#r=f#evFPeAbrTKxwq8$jlRaw;f~K->$eAkXgv31jrUh2>_ar0p%D_IDqm5 zD2zZY3(!3`pj8W?btfPnf=mJBVGtXXA0am|gX*Z+;IbOzGf;^EDw#n(1%)vvPbn}M zGFUR0GgvS{+y@c^nFR?wSjvUD6%rPZ@B_ISwggpE|fMqAc_+fks~DwSo3Lj9;S^(Z>3`+YD|AG2Bpcn<2 z2uTZ|S{dYHP>TZ;gA5FyGcJ&C{e_eTpjHfM>?jes{u7j%KrV%}mqGS}M(RN|KPVT0 z%1cn{1mz7#9tG88pppyZV~}cyYe8-Y^*2EJK&2(f&8ZBKm6o8HK#>0+H4tRYA*eliToWe<*HP#YJN9zppP)Ki7%1o>el zc=s--jEA&T5aAB;1IXo&6b*7C%tw$mE@Y--B?D-t17tQR=0Nx4f3gg zkdOhbj$>c|l`pV73o;Wj{{XTNmXkm&V^Dtq)anGyra*LqQZ%;J+mJi~YZrr31+3k) z5WLO^R40LE`atbn(45G8=h6&)B0d!jmsO$jc1W=j*wLw6w3s4IR6j#XNkha?b zaD4|Vogl3x&@K~D8yu9!AhYqH+7h(F50(QVqxBHoATuHP0c0yEjX_c=XeJ0$$3t3X zpl}D(39i>bZ4gjB3o1oHsQRLs^0DE1o|<}&CqG=uvvARp^8Fff431m$W_ z`*0Zps3ZcZ1f@5S2&hf~`5Kg>KzV#V1IQPkG6}iQ56XogTR~+pWUK}hS|ER-p5g)u zA<*15XuTRJZb2mqs7J97yz2>+8fJm#OF;exxeJu8Kxqk7c0@2h#vwrY7IanxWIP`v zgSu)Pl%qi91t`CQTG4BuGaw)zfo_=u&69#$2Z=dQSpunRLF++5H7zKYLDC##CLcLY zAZ-Fr?10KTM4h1vKAi%z9u73;3~~V|)y*d+s5jKZgO-{sryM0iACP zatmbjJ17o8;Q-2ypb`(X=3@~9=;Tn){01mhf=uoOue_^dPzTooptu305KsvR@hvC@ zK{kS74>EoNDP2Hi*>v!HB_wx%)PPbmDE>fU2C*4b;)2Wr#T2MyQD6YY7o_B#0bYdz z$#bBX1L=V9Kqcc`a5{&iNst>rxdDg{LFEl(MLoz@3&E>vKy4;S zu7r$?fpRJ2{A*AS0I37*pqLA`33O&sDtOKrH4T8w1%*7wuG!EY8>mzVrA<)Cf>QQ; zhIQZ_N04>opp*&9N1*Z>)Ixyl?gX{CK=Xs3(JIJj4yc_6av^9Z9;j3RNRzWEn3UcLQ=(-nBeFicEG)fBc8%Ql^_02-? z%7pn0pxgyo{RlD#l=}0*r3!3a38e1}(gQLF6l$Op0ID74f?Ko&44{<&kljq6aTrjW z3KZt43|72|3InY}0Qnpw4%+7cibv2YWRM#`v+AH46Ldxts00V?nFf`L zko1+v0P4kqQY$DlK_wPwGz`R7U;y2O3UUL;evk>EFay+?2iT28=%mGgfL`n4=C4w;s=x$K`8-}?`JT8+O?pT1gL}u zwO~-k1VFZd>Jd;q11hOOZ4ZzyApK~NIS^NX7A+ZbcJIF5BS~k!*LZH$d)PexTAFSOCX$^wbtAN@wpt2hjLa7X(k_MFW zK_x1vl!w>_%4d+8As;+a2-<-F@;Au;pwtICHwVlwu$zZNU^h!V_4QS;!C|`hD&Y;#0Xhk`wT>xr1f!Y9wl{%mr9@6Im zwVgoyamaWfD3yT18k7f;!F3d54K^sWK)D(c7oZg-pp*!57bJucqbr~uB`BOg=^7Nn zpdA>X5lhIvsQr9ygH zpm+eK2GGe5pq3*j|A0y$*qATKY|yA8XuJwEBLNz}0ksQ2?giyvP-_BIvZ{mUEI=g? zWF7;g7BqVSX$wF~2T*PS*$2`KvJ=w&2aVK#;vSJ}A-iKhH6tutgU+G=wUR-h0V=OS zttC)@1JovfjF*9Y3`*f3UxCio1%)=uK2YrmimiNj7_I{EQ39z2g&L^N1%)Xn{6W1M zPx#fLB)gR?^! z#31VljGW@b7~0_MaE4n@wsX8AY@LCTbG#G79LU-NBj1VPG(Uu}v8m7QxtN3=AxgwE{-*<_ruWFt#HD z0|R7DfKj{?1H&8`+nIsk3XJW-!0-abc4dIA4={>%V_;YXSs!2oy3I`pvJL=zmQewW z?a9FK0mk-XV32^U12BsBW?(Rau#G`$LH5AeMhqp8`F`VgLt_RD$h^KWDBRA#*{0xC zg*h^yyDLFuGicWVsKo}#@t{^SD2IY_Ire)JK`Yf<7~&ax82lNW8N3WJ5l08sZ{Q*$HY%fcihvp}Q1%z&BTq zh7)qkjfNBX=?T=@g`DC}TwWt3O@mqrp!PMW-3elY>T^&X466S@tt3$0jmv&W4glS8 znhicP9+uasZ9k}ul?|SE1NARJ{UcER^ahXhEeH3U?HM2=sIakqNXY;>n+4RmSOy-W z0;NHa|K>8R0MC0t`dtuJkTwixEC)2R1!_yd+FT&}Agx!39iUzYsLD0QI~;svzxpP)iFm1_lZ{ke@(pb;uYBXv_^V z^9~9}$h&8&|Eag-Jsq!q=gBZ#{-SKfch$+ zUI)lkAUi-MKd6-mGoOJ0v=SMl7t}|D%m$&x5@=n`JaGRWMuK_(kPtwRW6+*_P#l3` zZZ1-588p5E>ivOQ{velu&fEcwzJq)L>W87rfy6;!hEOvWGFGXaGyD20Je4Fknc zG6QJFcouk<9G}K=A~bQ2?zx z0L2_AokPq5jVXXa))jmL87QtHc`uOxwDud+Qw7BzddPxwAW}DIb_X&(hk9!_tTzTS z0o11em0EKcKhpn89waY-+zTpWAoIHm89@C# zP%MH@yFjP}jXpr`Yz6htK;a4L-Gg#5BtL;pKLE{qgU&Sog&8OvLspoB^5sef&?pgT zZUxet2c>CH+60X&fbth8|AF?Wfckx)9up|XfWjK&H&C2`ViD3Jfvh8g=!MMQrGooy zpz;k=dVt~#;v!H8fcoT+k_luI#Mhu6GGx3K6i=YAhm}X5aVt1*Lh=*a9fb=P^LWMj@jMppq0cHUQEK>YajAg4_lw2S7dnjb(z?YJyTN zs80zR=LOjXG8;4&0vhE9>$V-P{{+C3qj?9OhF%00ND?U7f_iFqCs#P~HZ`EhrCw z;tZq{QUZZwK&1*OoIo;&xe8F~0iAvT$}gZ)37XRag)^xB0-eqYS_=U34JZy~gGan) zfO9dZya)LjV03=_7OaRqepz(OfECMVyf$~47Bmnsx zHi81myP%O4P`&_-mw;*jQ0T(+%mJ^tgZK$l`hh|el(!)B8<0JmAa{etfk1OOAT=O+ zK(m{m7=+manPmdi#gH5e8pQ^c5unrnO2v@y0gYYHf$vcSrEHK-Kz?iimsXH-BVld> z`2|$sfW{$V_s_!QK<);)0Tj-lIYCg)0r?S8zJtm?P%fPXzUv3%4p3Nt_S=ERW%I#t z0LrzXHHe_H3pCme$*rK#XHdEUm2)6b*a{LzO$!QL&>hSmSA$l`g330KFF<|+jbXxC zps-p9vQiN=0}FDIE(5G&1C=P*&{-m929RGsGYp`xh4~Sbt3i4|Baon22bG-3;N8C< z7ASRs>Qqq80NJ$-YSkfTR6z45pj-~ABSEeKg*T+M1;sGPMo?)0ayuw@g3c%2t^xH%7czj-Dr7A&Xs!k{hYX5ESZIJ^6V!4Al?#w{u(13CiV;w|4YY?AG=e)9 zy22C`bD-J}ww@cL8#D$x8)-#2Xe<$wRw1imK&C)a0jSP^l$DUxfpd12{1(hbCvEqf`QE8C9pgI=hPEh@UUk9l4L7mSAnG71Ih1Dsb{0XZ2K=mbP zyb+cHL9-j690_VefIPvxA4yaB6g%u=(U~L#s83{_4pxg^fDWKLZNGHSvApfI>J1BjE%1ub`XC8RJ8z}TZ zCV|@9pz&r%=rw`oU_tZC`OsSdVEJVsc>f`&go31UNE!v@3XtibdIU7TfIPAe@)yXL zpjmO4?VwgCC@+InXM$!kASoH760yPyWHKm3o5AN6E(EXY2h9P1YI;Z;1~i)nazD&e zP%eatq527u#vr*BWJ@-9RRXB?1hwBl^$BYDg6swLIzW3nApHT*YEV!+1=LCar3+9P zg4&}HmxFkqQW4}Q5DltVLG>x zOIS#QasVi%6c|7zF9Y`@AT3}}e-fk$60e{VAJkF=iNMMfMEe_-QeZv=m53mhgKUS? z>97{8GXtd52c=q&A3*B{A*;ecp#jQmpppX8?}nI)dLApRO#!;`1T^0WaSv!sG01PA zl{X8(yOTj>5+XK1af?5XfW{O-J48S!4U|6SGE4`L1AuZcDE))n3Cppt-XAEoL4E-F z5McwTy#zW<8KxT4Gg|^aBMp@1K>Y=fK9HG^6pFpI3<@7d@al8WDiK865u^_ks*sf- zpjsT(0zw^60Ob}?*$Ud944GL1m7S233<+0AF98zbpfU@Tzd z69KiK0xBujf%iMjWq_>V28A^!%wcBZFBuXUKx;ifAp{B&*g6W(36`Lo3M#)r%~`?b4AJ=zouCs{K_w(e7L@uSC*UDg<%8@2)eNA1HRc!$NEawYgXXS4?Q~G< z8PcNyiGoBxITUoK56Cu9sso+w1zKGQN`0%4%4tw(0qOzG2A`7)G8g1K5D(Ob0=29d z7+k?8YJ>7SC_F*E97wwnmQo=p0CHvlqz?va4S?JPiS@LKgHL7@st51?=XxfoPmgF+4z%Aj%{RL6mOQIJ&+kkSZpUJJ+% zpws{>zdYB7V-Bd9k5YRiB^AC#(z*%4mOPzN3dg2c;o@LjpEbL>GO z3hLv6!W5KlK&2BX9fMp8s@XxI0cu@>M$SWbC&Qy!r){b0DMJpn4J%hM+Ow4DcBQAQysK zO8IDeJ0beB!Q--^nIljO8Ptbd%m7+}3~~viH4Ms&5EDVM0BRF~$}dpf0=Wp(S_Ab3 zASnP;W`b&^nc#2(#W5(RF#Qd36JlK<$Q+Q5Kz4%adu+Z(xD(_Gh}&VesKQ3QgBb!C zf*3p*{J`^Cpb!AXAS4xFx&ajSpmp3Jn?Y#_)Mfy+m_U7LP>BOuISHE40)-zaY(TyQ zg%ze7!Wdi`JQzF~oEdx=Tp8jST*0nzWe5VZBN#j}+yrTTfzk}f6{*lMYEW+z;yXyr z1+7B^*#;V40EH8%Hwj96iD=PzPfh zF)&=m-jTsp1AnOKrJ zhhS`P1_l|(+`nuy1FDfgV*sFd1*K-lIuy`|5~QTV zp36XG_$+X57_?p!H1Z5;>BHs^KxG!Fe+_EUgI36ZM)yHw%0lqiCP)@C764f}2Fc-| zkt&c~pjI|7xjpz&qU91<*#;3@$?c?NPe45)nz$^W2r zd!U{UC=Y>F*MQsunjx9Puo&F>L6{Gk`v8Ruq-;!N0J#xlGidAxQs;w8cvwyYm1!V% z%!TfahQt@hH00UAcYJO1fr2^dd2CahznK+99RIh+q51{b_PMz)V2VPjDY&qpxB3m1gK1htW^h%hB7dKM&3YWJE&%Y zmHD8SAjpTHmUAmZB6#I4XdHAd_$(WBXpa)qJ_5xn=sX!v4;Ew!hz~LeloDov_b!6M z2b6w6szEJi*vJNGXA>x1K&2BTC4*8BBxWFGA;|rp@By_^K|5nWJKI3>1RxVZVLB7s zs{q9vs09X64GK|EUkOxGf?6%0{uZdt28H$l@GK2zGz_8|vJwka)`NVBK3WZG|A0aP zGTRm>VjI)pu7cY0fR~fQ0W5lCuC$1bV@d8oCGp9 z2D@VsHu3=)O#r21wHHZ&tHG|SEsGJ0)3eX4>q-70C3!qjxD2GDw z8N~OX)*vJ;gF*`3e31R1xPhr%%mA9P1Brmj8c<&Wk{dy-P)P3$)TaW;gW8CooDLfy zgq5ivInW+RP#Oe4hk8>STJbh4^)zYdTSsbfa(X3381_W%AKG*zYsdk2Xco$^hPt#I2CL} z9I}=H6f2-S2pWX~g%qTA1H}m_-a+GKkhoa|ABO?O38YpAg(xUhfa*%nSQV&*0>u=_ zc2F20Yy!CfG`|K)(~yyVP$~l12T}(SSqkp=fJzNe*$L`tLu`ShMNn!0#XqF(0F}oe z9;j{vwWC431CWW35*IXMH=kiK1E_Qc*$f)Z0F69@QZYysBosg`+}YrrY#{%GLJj0& zNV>u_2NWle5i*c(L9qcE&jjUeNc$b615~a+ax)|pKw$zY`#|Lfk6+_<1+#E?0R5q(CKtAwj%?>G)OPc1k_WLgY@RWH)6WL z*e(nVAuzTp149Rl?FP9;-N!N91hk?z1k&R(0rk|>AU!)1P){ui#`a=hP=NI8Oh7Ao zLttzl1_nP!FV7_2mx194j19U49@4WjiT7uKt^78L4`5(0f%NQ5;sY5N(qQZ$NYCyL zH)hWcG-|#IY1}9uoL3<=A*i1ODw{xc9;n|0$}yn26I6DBaxtW>1!@z3+O4481Sq$H zYDtj(xeSojAY?BvB7cL@3#d#1m0R<{tF0h;24u?&hB*uc43IrmklW1{fKw@`{sgI? z#jpVER?sLM$OVwn7nD{(wJWGp1LZ=HU7&IoRAPWexj=ahlp8^1BdE0jDlI|hHG=x0 zp!pO~DutAHpgn7#{0}M*=Q4oiu3)_tP}vBY?T4rX>4vm}K;oc$9KZnD=?7{}gGQD? z{sZM=m}-!XAoqj92iB$sl{gC-mVn!vpz&2u{RZmQf=X;quNicjLn3%p)dB{P|3Trp zjseuefrKlh#Rr;oTn4_A6I9oNd=2VzWrJJmpm+k6m!Q=Ypx6P;AArmPwS!C;AZ`QI z_@HtYqy`kCkh%%d5(K#fF^>fD1EfU)s;fcnfP@dk9gvnMs2>li((m{Cy*0zAuWuQ6< zl)53lUJQg1LYo2ZUfD*gW@05D~Gg% zK`kf53@U;LatrL9Ku{QgYO@N4x!|=3*lH$FNP+SaC_GC*1xtDq$y zDC|HkUI1?c!fJBZY$B)(Ol1J&QP4aIC~v{i9w-Ha=IKD|3qa`{v||_KOHc@b`~;f8 zg18V=LP7cnkUAaY2Tph$$c!fco5^It0Xq#1Y6gP#FMPp$+m8D0VR0 zw~$m1>VJUB7?53%)C3B1kbgkuV1d@AfmDE8266?c9s>36K(>Qy0f~dkKv)d`DjPv7 zHb5tCL3)LtPynTEP#p*gO;9L++JK;$JJ75;Bz9oqagdY@G8uGJE#yvIP|gJT4N?+- z)I(H(TKS-L#nTx;{sh$^3gDATK<)>v_k*m8WMDwFM?vLUKlqMV$Y>D6H=x!W%x}vW zAU7d_(m$xy1Ena?s2^wqHW54)3sMQ{|H5QI{sZmG2g!oUeB@plsD}+2IfLxb1=Z7_ z{sZW=8&I1P6r-T=U{Gz52p%y7g2D^b(*dzTrO6zm{0?#M1CV|RpP;LgrJ|usE z(k>{4fZ`lvCa4|;txyN8US7!nas{XafaDEGZ42oMf=mLXOGtYKbT$E~rw3{=gM6`) z0aUYt$}Er?P_6=%il81bD7S-JA0StOdMKcr4r=>=;sccCKz;?4d7zpLG{OQ3M^Ih@ z#V08Lf$}pbpMy#~&}lHBa0B^hCU|ZXGRgrm+n-@N1E`-5as#9^0GR^HYoJ^S@+~B! zK(#YyZVfX22g+Zd7zLF#ptj;Xus@J}1u8LMYC&UQpn3{6Lk#i*EG)E2ZPe#QgF!zs+mB!9u#`8J{)N552OlG ze?h_olp{f@4iv7SdI6N0Vf}khY)yxbK!MIe0`*EkwKoHUucxE0XFTMzZyV52-wX^4 zkoyb_8G_(!V}@M}3=APA5hn45jtr9+VQeP`1|}HW8N4%4j0v>z5R~pf^#y3u*^r@* z0W|&u@+W8w2<+TBSPD)Bx1T`a0!nY7x%U7DX9h0@Gw@A=>fq69*vKZR6#_~ZkeUNj zQ>Zhbss^=rl=H!<5tOz-IUkZ{LE#ChFF`p4A_r>yK-#6CvI!JFpc0~v z0pxyA$^xA&20Aew^QHt)N`RE`pj=bOP{~loV8mboJ;xnV3W07(1f^n7yn^O*KrsbM zNuW7ZQ2GRg6li=NTh9k#KB&F|hNO2;yh7|ltXu=7VNgy*ZTExR)Xf0e(b&$= z%+SUV!r%&bAEf-B!2qf)K<#YM3<)T z1b|07U||NC$p(c4C~SRw85oX8Fo1jL70{ICC~ZRW zKd5X6)jas)4LyWFWizN30-B|Ogb-}^0;ntjr4C3MN5m-NJ_AtwirUu()iIDWDdvLn z2&jbyYCnR~IH)a-%@j}?hqM(yqa~0O21>agmx9tXsN@3G{-8DuDD8tvS4h19DrZ3D z8z{ekY6eh_1eH`ERiGRKabGj|6m3ZEf}8-fl3_V`-z9=qf6M@{A%Tn{pqd0qL(brR!yyd*3~L!cw_SkdLpm888745a zF?2CZg5Gxl%11p6kg=mK27iV|h7JZ-hAsvW8>9nq7Y5?a3s8FtRG)xG=o1-WvwxJB z0-6H^+3n8&vU5HIXmvcOosQ}&WEUWplAsy^R6Bv@9zn4I3OmHiBSIGgg9`keBSn9QIEEV_>!s!yh=?m|-4hEd^NI zgy9mLZOZTs&NgFUVPatL0IM-)u!FNL7-HaTONJ~M+t7fa1=v)V| z`9?+zr{Lno4By~v69y$#s5wTa41RF78AA=6ZO*U<&bDA+V1uc*WZ;6ajSU#&;A}$% zBRJcL!41wfW+;QRO~7T-5jI%rgPipQY6*gJJg9XDtM3gNjKKTXKrIMRT?X1~1=)!P z+8F>!FR<}2m<~`$0IGXHWg6(*R8V;UG6Pg588d)Ve=0N=f>Iu&9z?WAKqUtxmm|_3 zsBH#nYk)>Krh`uvgV+T!8B~^l`i`Jg(V!9wl;%No7pT1sG7IDv&}nO+du%|pB1jBW zE0SV2sH}pNGq5rgGRp$78`Sfd4Ib41*$rx?LrPQ7_#NJ(C7%LtpI9~gZvA!4c61k2Zt|cJ|9w=g2Ehj ztLsW|$pzZO44RPwtzQ6@9-y{4sD?sbsSWZC#Mhvj0Z|TEF(B^UX#}@m(d8kvGU&7y zNKEI0?L?>rtwI2~7qkuobY^=O17Z6?Z5v1$Oa!mFfY_l2Es;PW0jhgI=^1j54amQs zbc33TK&>j!7z!wjg2EV7M}kUwNb3U9B7)Reps2h1&_JXwn1AOofO=V=u$cqiw*k862-MF7>4LeA7`28Bx(uM3?uk)rM1opl64XLM z1=$s_G6Cd2P+lT7t;2kQPrm|EO45VpTtt3B)ajtG0pOlA*z{AtW5Y%h*c8&Z1ZSMzocp8G*98C-i3=p;v0~;fZZOq^X zXPYo|!Pp)F44^feVDsV~88Vo_>U|93ofyjCY-e!W0;0x+L4X;i#+4xg&URy11!uc6 zFtNbYcrX;e*`5pyaJCo27dYFSA&3>G-iP54ob3y)<01C?f$Mb$+aFw)XTa-nNN)pF zFM#UIh44GUK;x63eE^`c7BrFoD$_vyPEZdH)XN3Uh=I)ncH&8lt`ejm(2iBLLY1x)TF5Y6B{}L96#bszE&hP^}26oj_xv zFqeVGK42|T(6}h1p9FIOWb6SnZv{Hf4m27Mkw7bAR zjpBns4bpc3#R14RP;Uxk1E@U&YP~>OACR#{NR0uCr-k5q`9LlN*#N3zKw*uTYXtcd zG|~X_2WUnRavBiGeo)H?6q2B^Jdi3-%?gQiQ0WNS`vj^5L2dxK4%8;xWaXC<3mksVELDqeP!WDFqEMz|psO8_n(8kaSZt}+a?>*K{2*wI4Agdlj6#CMA>jZzzZWzX1MAg-)PYQ!2i{Yr zzyNU-$d8~IAkeH1=%h-}I0vYQs|((v3mKt>s08^E)XoK&1DQzx-RlAy+XIc+fW{m_ zYX(8HtdJ4p2Jp<-Lh$N-P)dM|IL}78Ul!ueInZ-yLF2HX7z6F|1(^xzQ-Rj9gVGDA z6#z*0kjVdGR^`S9|g5>LE~wlywVH4kw%wc z4Fd>+%tc>qhVV1QuOPEQIS4euHy`XDSnCxsS_%qT&0_x*}TArYG87S64p$-bArC@tO{sZNIQ2PwzC&E&|FOpt1*)+d+8(H1Y^a4WRf1xdJl(4Jx@nvvr_VOrVg2=?0}@kd2`6OGpU? znW;mz0n`%)g~}}Od=@A_!Fm#)G!KaI|pb`<3uOMXrCDP;CrfdR6H5@ZKx7E2IdTH za5iL24PvGxg94;31)sy$0cRV6?_LC}F*IWM1s6AFu!7X3;B)vEz}cn@w;*gEqj*OK z257wsIuH-EVg~G|cxQ%1aB&xge{i-dgB2U3P6i!_*9T|2GqAA3#61`);A~F@CJvam z7lRm_?aiPCVaGcL8#08!*+$?xd>sd@_HhQU0Ry!ML1_ zcF-P7NU8>{q6C$pptd7urVEsB zVC@FTIwFt_pgDSw%u)u>_#h}ngZ2u5N)AYW6_k$=B^W65Vg3f04eE)2Ru_Q$4Ql0p zN@CDV8z@vk`4D1vDtNUXq_qPoO(A#sfaVcE`4&{ifYgFY9*{mzUmcX|L33rGb^s`k z=QDu9547(M(yxcu0ct~m(gMh}pb!GB2>`7M*JWq|?@&zy&p|-k1Uk0@6k@QsJ5Zhn z@jpXCG7PHaK&cJX>jBkdpjrh~3U)wCK!~}z44_&QQYJ#qGXmKJ;UnY_EQkzb zl@27{KqKg&n{h!a3_z`BP~HWF4QMt8GI9!<%?6$Qu@XEN2Wba^@&ssP8^i;ZkFR5cJ2K=}^TPJz^5kkyr-kOkF@ zpw->fL4fs+zCo=pwI`kKxQz2@+D*h6H=amYG+W34Ybw*qIw>5RTIenAb)g& zTV~bU%*wMnhIpAi_omeD@eA#6f05PC)?a0i_*~OCWv+<=j+o zxdbWeL2Vz{K6Qv+LB5#{KHmo9A_fM31`w^s&;nl50x1VT>uEr#8B{(&%mIZ3q!gP8 zUfG8TzgY~RJO*hSfyyM1UQpQ$iaC%Oppy_lJuyfOLhJ&KGlAr1Fo5zC2t!JB&|Nv8 zvIFEVgwJ)s_mqIzg9;3=G6ym%1qumJFA~%<1H~%HK2UB0*`><>3J1_GYM7m%Se*|p z9U%D+R1$)60;moKnGRA1xrJaR1E`z;mGX(;GG`rh=Q+rKkTve&F#s3_3k!Xe(SkqywT=DHe%oawfMknQ-)n| zwi!bKsKo~sH)mJ}XIn75fU_+bEI=(gkQzfnh8j5Ah+!6-ZOm{3&Ncy`vJG~Fp(#TG zsKp1?YsT;l&NgSL1GVhH;uZ`G;A~5VJ21A90i?wTQe$MukN|4&f!RjjQ?|k88yPb^ zgNvJhZ|a7Kn=*ufT7qEpW(0mC&o+mOMFlYs$j zrm+!29h_~g4{*U+k+2z5P;VcUzCbNEP;Uy9*XJ@Uh1TDQ z@?jy;C}}K)poR2t8;l2eirp;!99@ms6>aY+`EKt2Ms`9Q7(?PGzI@t_a@)pejy0kxk%?MP6|1k@4(wd_HsNPx;d zP~Q&Z9*~Pcr3@(lK}KO9yVybNHz4QUf@Y`P8Qh_J?}Hga8Jrk=89W(+8G;!+8T=Xi z7=pp)6FQD`a8aV*9 zKtN%QPbWkU6xxtEY>?dx89*~YkXi}dZ=jY0L=UK41gc#?qe~!{fZ8Y^S@ixFC^vvo zJSZ;kg&N2Ve{lK&)oP&p14(C)+y(gUdN0m=uUGzwaA3F$L{MwLK08PZ1pxgQj7pnSR<+`9qgQBX?|(kBC@e~?*_ z&@BMB;*it%D(Fpku(fTVk^+>HLF)oRc^foN2yzFgtqMxRkhTbD6bn?Vg2s?Qwj%kc5nAfNH%t;I-|L6?LE-18M0% zS|p&f4iW>UAxL@$r4dkX8DtNny$2EjnE{Fw&}t)48wt{vgxCaWX+h#0)LsXbQlPYj z973R!4C--!Oaz4vdK`n?4~jKVy9m^h292D7aumW0kb6Pl401836$h$~L1hKVRUo&5 zTDb_*LFG6oKGDM(#7B)ym<=Eq(0CkZqhygS&4az03HVUX50JTP7sQ@%82P&;Vy>8Gr94IY-T5F*AhqT&3E(Vo>pfVj~ zJ}l)f29F|xPPPKI`a$um3vO2-$2Q0fh#nirY|xkjEH^;H6_hVi8Hf!fb_yZAq1LHhV%+i%>wnpLAf22 zzd&Y#+Ge0s25LKk)_Q_c4=A@l;sI0#fmGqP9dzm?sMLeyPf)CYVi+`@4cf7eNLPp) z5Ar?89!O|{{0rJU0Wty9vj?4T0rDlNW`NWLkTv0;`~%BTkQjiK29PtYK;ewNBm(&W zlyg8K1Tr1OM^3#UU!s}?>iZ+d7i3Huls`aa2B?h<%Dt#F4~P*3*cd;^+yd}e2B;>6 z*b2*0pgsf043IBjEeKHE)Xe}X4M4ReDAj;^@*s7fk^++QO{#ped)c}0xWI{ zK6wWsZpOd^>Pvvd%^8f~Yzu}c7~9BzVGf*a$gm5}He%2K^(#Pnjf@$Pvvsm^(4Fz}Suq z43l7NCkBRnFt#%TgAk~{0aEY6z%T>Gc7>eg1NOTc1H%iLxH|)b6E_0`Slokwp#;YE zWMBa8_XLZ3F)%!WiF-3JXz)PQ`!Fyhz}UVF3@>18KL!R7UZ@&>1_mb>JAi><4U8Sg zz;Fb{4q{+%;e)CVW?+~DV}~#>yn(SpA$w3&_+fn~#BHmflieUG9ChXgRA+}Fh0iGW_F zqS^v#FM`I|LG=+R3_xwbxeOrJfyyh88xa14tmy{17E}g6&N~750n*NZ)GDBsEvUQ) z)hHlWE(VVXgUU?Msq-M;f@Xt3H6-W^63}QQ`g}LY&!7?)R9k`k1S+vXWe{l311KCo zXPJS@JV>~JMr}a8gp7@XMwvloHZx3SSj*7J;18aE0M&4yat_pj1IsmWK;tE# zumiR6|EC^KMf$m_2h00>^==M_ZXc)*>pq3G+j0N=}K%;1&`gtC> z^^NWBYy4xNp!qpa%L~*B0;MaExii4Mwq*>f!2M#7+aYx~@(3R&)quu*LFW>HY8Fr_ zj_gBF7=YTJpg2R6%%Jdxv>ZUKIY`dUiutBL8k|IE+fL7Fi=06bW0TRBTeg$Nd52O>M5;SiP>3gB3 z4N%Sj^-~g|BafiIJSaXB!E;3*UxLy&s8(xmK<9RV+Apv?3tH;|N@<{WEokHv6uzL=63BO; z_8F`#2)V-;lomkc!gK~mih_&?gGx?N=z&TPP#X>uW7!P444`ryGUoyssR5-f(8v|2 z7X_J#f}|kiREeD1K)Z`TDIH`NsD%oO4bZt@pcWux9H$w4ix{YF1adVbf6QY5&HklA z_sf7%3d9y{ZAoHEcbINa-bS?f=YmTohGs3ZWj z{Xr!PX#Ngl1ITZnIs?%g0hO$v6W>9ipx6P$8EAGElz&0<)sUVBBz$1^!-IIBkOB4n zK{Wv=MnNeIWCJKgBk~GJ6>46AgaM>Y3dskc(H_vL)*#n|=6oRQjX-r&A_Is7g&br& z5mdH=@+&A+LdqD>>Kst~fmE&pr%#ZNLFbNvN)gE16UbE{9-?OjsTo0~7bqoz%21F` zAhiT2f6j!KTMHRL?t#=8h_V;tLr^S%>M;--)Efc03Y5k`p$7^PP+o(asSN9V!}1Gg zCK!~*Kyyi;oR!Z2njwMA^?^z~PzeIk19B0_)u7TBG(!RM6Uau`nt#yf79`FDB|4-g1(}Kn3sC5Qav>-mF9zSM1-hviv~nDjra+6y!H#SUjcL;E+~{hAqFbT zLE~AV-V5l=2}sVH!vN~Tf%cAq+yp8QAZICpd;;3vWQT0l6M@ zu4^Uu!~xLAA}CdbFiZl^n?qs(76YI@Q7W{32C@aY#Q`$gk-?k6pP`AtpFx44pTU(O z0DLYDD3yT16V{4?)I5-0BBb5|^=Lt<2c#0xegOFo)Di>D(u3j@bUPqqzujDLFV!1- zKNBc4K<)?i%0R6YP#X!9F40d$hn4uCmLMdpLG}fL$~ef$JD`@|46wwshqPb|2^tXdl|GkQSXIbk-P@ zUsuA}pnRdgfGCeavY@gERBJ+RdIqIZ(8_Sg`btn<1cd}-ogys7gKSZNnh9#3K*|G< zTS0CHwG=?^MfDq~Tueop_lATfs5S@1J*3|TX;Xs2bs2b^1JoJ-mGmIJpgam1uK?9D zpix*zc?nAIptJu#aSzJL3mH~1%wzz~6@yM&m=C_E3N+@4h)IxHkQNDg?n3w#w4w?U z>Yx$^bo)C9Lqr%DU^99Ucfv|x5D(;D&ppgm8ZngrBG0@YS4!Q(HWb5kMr zF+to38ifSqfkXyS{shfLfKvW^@H$73yFob+l#)Ry1QhZxcYwxcU@HI>p!2>io<1(1 zT_P%wF&Xe`z!Pw`5km%KOa{C%Py#aEVia#+0=_Ldz(2?+-oTV$A57elf#DU5?Zm)P z0vUTT0-Z$62N`cMig#gP$bqq485lOe*lrB4@fPrnwJDHs74VIOEChSZ{-JL`!O)oz}Wr_3~ylU00ssh$he9T=zOar2;0vn z-V!`7%frb4E^$FE4A6`Ws6GYFGl9}BsGS2UNkKD2kX99Fqz+U%fbu7(#{sFQK`{yH z>4M?|)ZYY^J)p7>w2}dIW(lbF2c=}tDJzhg5w_|avbGddUM4fF2cMe(s+&PE4x%9` z6SQgu)S80W3mWkU)dnDUf>INxE&=67Q2v6r5me8BYyq`xLH!+2P5_mfpmH5lIz#%$ zpc(^Isz7QNP@4*rb3mqn)_8$j0J0OL4|0zJB&S1qRiKm&YLi299VmoAGkhRA8F~&E zL}m&2ei=|{vJ$0F3Cao6!95I+8$s>^=_PCiXap2gCxFsFs8j{H&{{LlsW+f@5h!F){R|l=f%q9zN<+dE)Pn+*jmW73QvM`D?BxmIVdbarR)rbW#B#%$PFNOg4_qm6_EBhNIxiTfJPV~y;IQo zJ5W4=@(Eln**n0+ga){Q;0W zL8T`ommr4>q`wJ@zuDlCKv3^u4FhP#3luY;m6o9J1dW}5+z(3oAl)GKGZ;W)Y#{f- z(k-ZD2aUpj$_ZGwtp=L~%G;n7Rv@2)bbx#UI{6H;2L*I42x!y_jFY%fMPL#VI>1-B^0Q3fP@w(1oOcwZ$Y^i6kZVbg5m&lb0DOK0Ihigg$$^@2hsISWk0L3RLjzKjMWMl!+BKlI z5Gduq^ehIq%OGtPka?i+1gTR8@8I`lfV8dK859_r7}^+`z^$?n@a^FK3?NaEd=Enh z!z2dKSs;k@WT3nS8s`J0DbPvGpcRIT!L2+{iwtDb0`PiSP})Gm)EWjvDGj>E1$2rY zD9?h%??AB*I!PN8>!2PoG9Oe5Kvq10WFa$tptcRjE>Nn3u|ci^g*vF52DM>8p|}p* zng*SA4iW*SGtl0G+2HsF8n?e0OP}vVE3qj(b*-cna3e1DOLVQ$gl}d<=6P>RDB=l^KZg5M(OE|Dco(>B~WGf`ZhPuogY2 zzXQrkpwO8I9_K9p*Kv^BdK(xZXP?1rh13Wjzk*@_G^PRy4UoG)>xKxQFasL}0_EIQ z;L&7|Yd~`#pfUiIE!TApQWQ08ku*QVS>-f=W=xP7_Ef z1;q!b4QUMBMWTV2<1+^BBJqK=8;#=)j2JG!*~SbdkT#?-sND!UQ#HUp$Qaaa)Pb}W zjX^s~Cc)X}3}@hM3x+aC8`3!5z>+}$(pEH%H#A`IfV2sX<3aO%khY(3yrB`p2RPf9 zp#ajxGX|a6`T@>1h3w`4xxvtkVF#qWXB=;6&hQ7$wq!7Z%$*s>8yPTcgR>18KEc^W z3|)}?<9H(z1~VAjk%6HC#&%+0=z_7G85lmn*e(nVHIO+n<9JsF1|G=# z7x-olD;V3IfuRD%_F!O`24j0NFfc*p!oW9k{D85&85qj=!EjcHpln|T1_c<~ zkAWc!#`b4m*aBk*FffP-Le&Q{Fr>iPK@1H0VC-N91{NWxnh*vC2N*k)fngVn9mc@$ z2F8wn%+o0dgGQY}Yx_XGX;4oN(dq%EH&9v!-ID=ok%00|CHMwTP#FQqAE1#IX%@V<5 zilEuTx!}<*P#y*4O;CP7b_1w44yso{BkmxR=0Znokxw%N`2jSd3TjWKG9X4vAu9kt zy=uhTZjd^Ve;~6~pppeNiVCVpK&3ON-3TfDLB0T$0H78hsP+Q&u3`5Kg4`Iv0NEjo zT8e>sh7dDBH4SJC5fl=jxf4iz5Aq>M9%LV=9tZ8o0FCc}N@q|j12op<2rk(`_X9&p zLy$U9%?JuXP|Fn5(g2mgpxA|&2Qvk<@(mOgpjH4(70ey5QW2pRRO*6iVo)syD&HaH z6~w)unF9!GIs<6GAgC0B)C8b88pzlWs3eBi1ab+eH3I6LLDsr}%6O3cO7OTJr1c8w z{ea8?wIg962`W)R^(CYv1+@u4ZME6pl^-Dgf?CO-dK*-(7J%K(~;MhEp9l1-UT9 zGq^E$G6XTWGK4d@GB|>FKl(v$*Mp=EUj?NeP)P<#DWLW*Xx|oTE5ku4c(~b* zTt9-^+n`aLxzKtM)I$J`)q_@#f>zWa>L*YNht&C?StL*k0MyTijgo@uSWrHM_1Zu+ z45;r48Y=+RMW9|aY~B&X2DPUVW`jyo~opK`{&}@geiw3&E$` zf%+4mJ?L{8K;!Hn8$ms1P%Z|wqCux&gW5iz@gn3_K1dGI!Uolr5c3fFKrI%KE>Mff z6}>L;BHk3C1I+mW0G3Xw3#_+#b|->j$q(hRnS| zYHm=U1k`E)jj)3Hf}nmbBy2$O2Vr5hm>_Kq$UGQG$0~3g3u?cfFTY9Tl+LfSl_mLMoq%mI(DAoeJNb|!-2r3<=7 z1ms3gPZ|_%AhSTDz92tATEEDvV?b#fl=?ua1Qc(eu`SSD>yTNw3h-^(pf)>bk%d>#xmh6BJ@=v4HEyLk`uH>2sGOa$`zn+0F5z$!XGp;IRo620j}zm3`lPb)K3EK=>p|bQ2qnm?*_^-pfnFkU!af%)rz3}ISYIT7^GJL zQV$wq1Dz)bTI~et*MNG8pfxR^m;%KLq~8V_aRT+FKq((o%7c0cAeTeNuMjhop!}G~ z0NT|H@;7Lm5M*@%C|7{O7&3wiO81~qNKh#N3QbTbg2od;eut<6m7$O_7^D`IK0vWP z7kslfq{aY+G^F$el|CRJf=)pI*Ax>NHS_1Eqh^_^JtX zd={+4Y62Ql?Srum4H$mG*@g@~knvR$(4jkDU~DG_1{uisD)`Q-8W`J!fnghr?aIJ# z4#sw4VBmp_yMphm%7U>y7#NPh*q#gwevolj6VROg85rA}fk6l|)@lN}UDyc5_J!|j zFag~z>;M^q1>ae<48{(C+)Nw*3b#N8h6Ko1t4Vwi1H%y*JD7ptAB-Ksz+eCwg9V@P zlL2Q#PWVycMYO^}D{n!$6BOg1+6gpf2|7C+d0i+denF`NvMv#0_g|!gFrPjsC)vQg8(WCL3V>u zFUSr^?*>$QAeWmEGeKh`kdT4oJ5UJ%3z-1$Y5fY&^ZFrv0Ht&Y3%!2_DqBIW0_8A} ztsqxIOarz5K=~2m0#M!rxes*0C?r>bYA)nZ0hO1a+&!0J33x^iRPTXm0#I!PiVIN7 zJ)Z%zLITt(0Lg-E0ga*-FjPQuJj8{d6=R^%1yovrTm%Xe(0xmw*hiE!AQNEbK}u$j z9*|m4UjR}kgGyh}C~_i01$Zt9Vm@SzH>jop-OL3`TcC0eWCN&p{zd2y#2jHc&ma0KEGJw7UY4id(_G98muNq#6_lpmKdS`1}=+ z`$1s}%8RftfV9~_?m(nu5D(PG1*I2|y)Zw6$_G%M1<8SY3b7S3k}w-QM+~YfLF)-Y zEmL&&gKR}E0YLRGsC5ML5y&)1>HyWeFu#F%myotBs5F3;eE3o+#8>m7eQr=1T}oCO z1-Sq+;)?7tP^yNdBS@<01lP}?oB?qwsEn8mzF%h*cy|sY<)F@`fKmm>mylFE6WqH2 zmUGRSsNC`0CIL8rAeGB`4{gUtq|EQo5zo>owM3$#`R6w7nLXMcg( z1fbXlm7Ab8FsM8Rg*M0qAU-Iaf&2uj{XsKEpq1~S{00&Om8_ul4JfUH!V%OTU;yu{ z2AKxwjX`{|4178iC`Zp=Sj+%v;lXCrLH$vC@R|b1x=he~8mL_aYI%d&s0<9S79uDm zfKm*o-T>7fp!PguJveBc2uK9fJ_ET1GW${h9!mht{2*qq6&M=8%FY@SxOL$pEqmRM&&@1}H>9p$|$~ zptA;$QxV8?i2b0Lm<^9lNL+*R1t?BIZHn0ppqK>pc0s9r3Ik+SJE&X+m93z11~ke7 zIsX83-$M(7BSQy64?{cjz6($r8q{6}m3p9*392C=;R^}_P%Q>(r-MpXP^%c!iv_Jn z2e}V9oFJ(lBnt8&q)Y*oe6W-T@*`+P52&3D$`6nd2-H>p#Tukn0_w|yX6Qhr2gvS) z;B#z1u?2AtC{=(?%K)VTP^tlyL63(^I0$r1+8XaHz00>~|p z^+>4ecM;}*dP<;`njpQPHD#c-9mwaPv}uN3CJ&?b`U6KLwp12rGerSG=c?c zi-1OIK_v(%4T91IWW5fk)dq=u$aG}Pw3U9u7L6bXio|#KZ1IOpxc((7#bKRF@Wx20gd#w!|#;=-Tetlg`mDb6Zj-M z(0V>lj)9#0wSWQCeg&z3v=TsN0mzM@)-5PELUJ{zu7Zp*fl4U__)RdNR0V4Fxv=nRy&w&%-{p+ zxq{fBmFzIK69YpPsJ99dcV=Mt0b{!`Fi3%Vs~~Y#28L-cwi^S(B^cYCfx!pVa|NmK zWMKFMV|y_$WPp0BAaQR71_n@16vXynV6cF(eHj=Iz}S9}6*ND%LHi|b4x41WeupD1C=kJv<2$%facOcBe9^g2P)SfGY6n`zo53u9B`cvD%~OL zZb4-(Xf+FH{0`D?0ks%F`4BSNi|$5{dtoi(Z0If-P zfuvASi317)Q2z_GVg#lNRB}L8Wq|stpw&lp43KlWV6(A~;1jt(bHSk11py2m;4`{F zx1)hd0?ZjY%UCK`_e>zJ2el+X z^)@KZK)wUTJ!tnlXnY=&`ypjKDCS_gL2Uz2T>=UL(5O5p1%T>Oh-y$-&C$M>6(3z^!!7GmuBd?&n z*^t%}D6N3Z1C^nmQAAKH9u%vfkOj4eL7@O@(;=$^xdqgp289Hy>;mZlrB6@~91^au z-E1J$ppkk|{RuhI2hSmHN(bmLFGHB4urJ)Kw$w|tpN&oP)imxngj9?s3Zm11==A3vJJG0733n2IH(2z zwZ1^E1H}X=enE1OFb3smkO)i{s7?O;FARg$l?eklYE1FVK7+sK!PwgFrq9rAJ5_2esuuH8Uu6fLs7- zy?|0SXoMBy5|Axx!Mzj6h=VHws80?X_W+e9pqvOAQv>y03K&3bQBV#Cts@4tzd$|& z&GkWaBdZ6EC4tTugzUbDsE4S6q)5;_IwW>MX$_XskV6+#j)B^&AX7o%0?IF_;RY!Q zLFpBg20`jTegpXv6l2uNJ0O>0ALYPy`XMNNBDaVkX%sU01Ik^nw2SIfP?&&Pr6Ap) z{0!0sNu{8+8Yo9W#s)y~2I^me(gP@NL8gF8TM!!(!b)E)u#SU_x0*#+u< zfy@Qv2Uz+9*@>9d1o;Z&5|H_jo)xHl4vI^VUqIvYpq;uP7l6zKsRZR+&(Bt-RBl6ZC`dgh z&Okl}jZUtD?gId&G*D{`R7ZncJr~@{1Emj8N`SN$A$x-$r`ChUP(gNuz(+43>QKu# z)cAmuKAWb*6T}9k3edV4P`p9v1yE^I06r@e)Ix`q z#h|(yWEyCE8PvA|rF&3nh4i{1BUji;cu;(R`d6U-6X?`t&>bD1^VI@^{J}eRCxB)K z!E7UjFL1UoLlLBZ1>UiH49+%XP=oZfz;}80z}e;uQ{Ze11`kO8$_O+|xCF)q%@V@d zh75X;{uTIY4nG*%iGg7XjP1+-xg!hYHWvm4@SPnXwkrce8Ke&ex|)Mw8I0`?>E~=? zg^fG+Fn~fGR#zid(!+8KsNDhT8$-&TUhw*S(9TUz8vrz>Gm8OqiaRL%f#P8u1E@U& zDwQDXbYSHOB+fu>GEn&j$`hd0E-1f)YPeRvJIM{0PRo%wVFV4E07!vaV6}WWl$LmDvv>J zS&)xFr30w!hm_=?))}Z=0gZBi>M~HhHXrFEcaZ&{QCLu`6jTmC>LgG(395@hDHAl) z1qlgI>V>!zlR&u{GA;|rzo0%8WPKMXc0nsgQ=xT7D)=l8koln8 z1j>;h3~EP$;sR9S%>>^=49fo?RiH2dc`;cTf!g>UDwIW1zYQd#*y5?F#PSf#MZpHpEm=y9wqd zP^f}Nyg{uDh}$8l43vf;H5SO%kae&iQ=J(=Z7a}dB&ciz<#15D4RmS)C_X`92r3gm zwLQp%kh5(;YfeC=C?u>vxoA3gMHfgXr1S@c+$``~ACMYInE`SuD7-)+OQ>D~xeU}Y z0=XLG6Odm)>#!hgKZuWDxfwJg11bYR=7R7#=xqa#6b?#jpb`O;azN$4Jn#$;$b3+H z0hCTby?&5i=Yi{RkdIMYA|Rchm;<#$K=~e&hd?0-%2%M3H=vXNG7praL3tfil7Uha zq$LcQAFGwwA9B*jJFagFkGJuUB8pj(MGE_lE4UOZCj2HwUBZc7of#6%FK;|2n zFsy-zJ2Ei*gR>18xFDm3#_>)Jkb4F|>YW)FDq!L+3=E*#roeu5Wnj>Nj3$Ej3Z}r= z?$9$pL3%wH7}y}AjK=Yv3=B;$wig4#85rA}fk6&3$_U;o*al#YLs z{sNf;%CVr+hC%HE(8wF)ya*0FQox`c|M8$pQw@m>;rzpm+eS z{lLC@2C~-yauOe?_J^EV1Ue-ER0@M)3pCOM8gBvBsG!mfq5~3|pn3$4ljAqCxQi8eu}TAwjxlgZKL@FyN8_wNODR9n^*d)wUp0K`XUDEtZ80kdrS# zX%VH_ByEk0l6CFGEiF>6xX1z289!Z4_e6v@&hDrVOCk9@N$b-HQ%d1rE9s7NiH%js)3)t^(BC z1?>QZlzAXugL)01ele&`1X?)(In4}|Zb0cC6q=wIg`A}hI&Bs)#sXS90E#(Cj4lMP z+g%PGDaZzwVxW8v+BrD~-2Mic4H+?pln$V{T>##11xf{=(sd5l1kkz;Q0@lha!?ru zDtSOAf%GI(5YJ>pMrWkpjJAlq=uB6pp*Xay3e zrUSXHjRE9akUv3l{g7G))Vc=s|5kukAb`>bhzCg%i460>^*F?ppt1lowgu{U!^ZVM zCc;{z5c5Fk22`VidJ(qwHZL^ zZVtFt1ByG4t)O%PigQqCf%-$Rd;%I7goHXMeS!M;AQhk-0SX7mPP*yfQ4P>a1dtf0 z^auG1G9m(-s{@rriy5*RKz$L=>H|m)1f_kDtMT=@Kq(azHlT2TtbhUS<_Gn3K=B3f z9cZK+)CK~bB@1ya$UabQ2MT{&sSFefkdhJd8azCg(1DOKqPlH;4pjZXv z5=cmad03_9c#y3GJ8RR-pKMz!f zgK9>Qt3fq8Y^)5_N&ux=kb6PnISUv-`4m(~gXZr*{shf~gIcnnatgU^1S#7gE&|ml zptu30BT%@4{0RyPkeg>fM^r&E1q!cZ@L1h!s6C)C0F9J@*2#ck8MJN+c{Ul;egNel zP>l>p%b*ktDhcO;^E}8zPzeK*1LbE>-xd^;`3#^E0kVDv;u=tH1Nf_%Bc&$BP*abFlh7xQd@z13o3y@BUqp|9VD%RW}QH*%pk6r0WbYP zu?5Nxpk6r0$DolWP)vYg64dqriFGl6c0_`57)S8Ojfb>J;Aa<{W zh6QMh5#(l29SK@X0BS3M${mP*K|2Bq7(ioTkPrd685AOm!Ta1mVGP=j2TEbE-VVs$ zpmYl>JEw!km_Th-P#Oh|n}A#i+MNR#Wq_3qAbp@Pgt!`%Ye3}=B*s8(VNk0Xwtf&) zcfev1{4GBf}&H&`t}`NoJrm zoNF067(n-gJA-dH0Nu0J=7DZi0EMM9gFiz9ctu$w1L%%O(3&yOin7V@o*@2y z4kW}8CE9fGYE?uI2hALTau%%g1*HK{-2rN2g2F(Bp^gD`78R)P02!$Qg%>E5fbuY` z+yRwaGZ{cB1K;{FP-HE^~mLl>NF#;^&_HfMMMXIn7vFu}~XWH5uV4GkC);A}&N zE;!qWVGEpX%zW|_Sx~zSbzM_1LjXe%_>7bgoTpoW z#;75q50G*Il;#&QWP|s|fKnyy)l}%VhB3G@crbV}I79C}bY<{k2w`w#0IjKVWr$$# z1fPlmawW(=us!=A`ynY1GC$IUbc!S-ErNE(gUSU^z5=xZSAln8K*mQwB^<2YTEzhB z1%u|!A*~L`3etHDGZ-L!TF@#LP;Ct=GeEn07BGO$0|w=9)I1F;y%#fpWtxW}vzsq{on99s_YI zjfhrhOteZ9uu9N*$e^+Y6at`H1a!&-s3ib$AH*+u(2@pJTY+K%Unq1j=re#$FovnY z=X;o1$eCfFm;;T-gXV!ir64GTL7@$D59F>!P`L(4`JnO-(xw2lqG9;}(#nL{4C(7& zk0S%9k07T(6YG0OYXFpXA^kVl%m||G2XYt0HK0=_7c$@)cLepWU?Upy89+C;AyR6^naln+3oku$+<7-Q&aa3d&f2&F-7RY>gx%I%PKcc7dHTWbjF zxkJnYl^dXQN0@Lq06$^eZkfW`zty#pf# z18^%76yBiR4eINGN@d8JVu)_g=oKPep|2zcg%GIq4N9SqJ`boQhs+N`aw8-sLgYa0 zNl?0lv?D<&4p&VGs$Hp zL2(VKuV5(?6nh|_&j;@v1jQ#PKY~O-=@}G9ka`6aV-UYUQfYvXpDQGl+Q3t3fRACk z2}2w>HGXPJZAEs*#E)tI0j3n&$$mU^HX2$W7i zr|tSPbTN1{fZC#D20O3E9G_sIfL5kkklOlPu)R=@rDcx zW#AMYWEgM6z;Fu2Heq0R17n*pFn~@#2yzZGj5lXs*aA-VLCzq}dZ1RgGl*>rE{{Pc zB2X*UgLY4Y(&Y^B=r(990e15za*YS7wGpuet2II82dIvl1-*F;l>0&T6sS)Qs+mFc zD5%E;%2BYslo7Zjh0Jt7N)%Ae1J%l)u`kHz52WM()yXrEN@P%f8dd|-E`?y)0va_3 z)$5d}5Axy+RQ@Y4poSZ$tX5zEm0#$w1iD4lnE|we71U1$)xnkUzBFk34m4H^s&zqS zGN|1L3u|!c?C$bLzqBMLiTkHa*j8)WH4Z0VDR;KiZ?KKWLO4gJ26}WrC%ps zP%>o@0i{_m+kn9b&NgBItJ!xB)s2D42Wq(HqzFx#9V1;#dKVAum= zTQD#@fw3(a7@9!6NRWC*28IPNwi5&R=2Ng;&I}ATpxz}&jSBR`1ThFbBr=U|{$HV|y|%Sg}LZcrh@9!Pwpm41F-R4+FzB7~7YDfrkUC-j9Jn z561RqV3-GE2QV;baYEGuGB9Mo*g*^odtmHf1_ll;sG1N4hA0?2l!2iN#tvg(=!3Dt z85lIUq3S`a3}Ea?28KB>b`%4{FA&?`+c4hH5qzs@5QuHWz)-})zyM|&GcdHk*rp5& zD`0Fh28LTOwmAcX2`^N=1p`A5jBN>7>j=8>6f}zk8rcHX$)Gs_NDBe7o()tNg3=vm z*B9vg3sA`dIzJiImj$h%1IdHR4^T@H)M5gyE(D2zYFp5mG@#M~(oP4N3hECp1J6o> z`llcf&@2sT#1B-aK~_P4TmUiwG$stHw_&p?ptW|884^$*0aV|B`Wv9}e9&wsXyz0) zHUO)KLGyi}G7mJu3UZqQ1Ll2$kdgw@cLJ4vpgaolBcu)j&E!B%3Iz2dAagmO`~5)v z0IjM8)kUCo1E{A~0Pa1&%8h&mQ2%!}c)bI}ZqTS5sLh6YVZY8#L!Xsi@ePJ==lQBTYT&s9MB#2_1&f_obCq2UVYX@GiJAlHHV5ujE&q<@7R z79cl5MxGJ16>K#hC^dlEfS^7OsIN8?e9jaoeS-R%pq3%XOb|T-YAeX)sH=dH?NVR> zg}@w!MDW}LD4k)N1-ri&;eL?KpjrXciUQ4>g3JJ=JxIS7(#OCaYoPRl=;4Fv0Z2N< zj7iYAJg7$iN|_LKAfJFz2Pn6KX1UP&;h?xj_zjf8K<)zBh?-(SG9Vv-(gVoFGL=NlP7?gHgikSu660aT)aN|Naepb`+&{{qzrpz;({&VkC# zIpCBLzyPryRF8miHmG+FS}_hv382*!3&C?HpxG!;Uk^0f0!r~9w}LPzhk8KrVyKxP!tIw5tUa&LC4j?JtlokV9uB*ma<|0=1|?E(6Ul43IU! zkkdX^!_UnG`4*JNKt2JP1}dFFH6&;p0pw;-=z!)CA@fh5JvXz!5n%%2LDCwi zl%EBzOF=OQnr{W|z5>||n)w6GK7&Ga2HL59kkvACqR4y+7krw3j=sH7$^onDHt?In+jfi4{;x;zX@7zvyK6@!yB?@9#pSD;v8lw zC>}vM7?k@#X&&NxP%Z(*IcygeXuTk4MgihekZVEXEufi6&~4_Bvlu}$9SguGq=Cdi zGi@Mupw=Cby#^q=klRF{GzYrH2UNd+OaS?E7Wh6%&}<|qJR#*iD93}$1MO7>rCP|k z69^BK+CcdRRx*HM12p3baw}|1YBqR;0#w(|V*u@81La7Nogkm$TQLDjd8obvmF1vy zoS+Z`?X8*(Zhe64LiZa)4ALF|)##8A1epjc9Y80oz+4YX%b?mD9b%R%`bRGxxHf)??LD-C_r66er z7WbGofb0d;)u6D0j2wW@Tv?*zm@AQyn@bo81FR9}KpA;?D{SAo=k*7TvS z(gf8RsYv%_g2EK!I#4V_T!6@ppi`M4WeX%1<%4IzK&>ZGnFiU>04j$-^)jgc3o;87 z7NC(m(2jP{2q(yAsAUVRe1()mpuKa*EeBBgNAI_Q>Jm^X3@U3u@eOHPg7#d2%mBF_ zcYO@X%b=DB=teG(*^pWpQX?xcfZ_@@Rzc|lWGd*6KTv7~@j-D7s>47nI?%{E$d8b7 z(LuQuv?38yH-O4BQ2c{J5mfSnd=4tNL9G{1d}M=LszLtF-mv|7pxJ29kXAr&q_ao7 zA$VK{!Zw18Z8`ZGfyTK&Gu=TTwh;qE6l4s*DBhTXp$En`VPLocW1BKSW~V{w%^4VK zAmaf>@fHjWOpv}lcpsh@jP1n0kOX5pGcfeP*e(nVOpq}EBhZaQH88dt1H(BO+ns?S z3DWNe-@v&A#s=Lu1nKJ=fllizg0a0B7|y`hJ`4_AlkRE(iqe)OJ)G&4p0uC z4~`Y=sz5mrw4ZD)Qhx{3r=1O+*#(sXpj-p#&w<8)K&>IYJTmjO&Sb=7{L8F_=4=7iHO0WX(j5#Q8LRtr)8Ud8|7lLnb z0mTfmi$Q8Yu>=ZzP#HEGJWB&=ouJzXG9Oeof!4Bt%mS6Upl|}Ma|eYxq&)&sMQvB0 zmbBRFe^3tr6e6G&Bd8vNonH&8r$Az$k{J|E z2zxu-VV1C@xN8XYui4!KbWCN^i4rZepdA6Awk0T^ zf>O+K2GCwt(5xY3pA)ElMuaLPCO|D1NT~{OIVhY!X%G}TAU}aZ2UNd<;sMkW1Gyd) z>!5QvAf{%6%WqIh0olC(z8(S8_6Mbf`6%~>V^5i|`~zzLfx-etV^>fX3uNbrkNF5h%ri%4S$y0b0QbvJE*OGcYuO&pQB#gVGqR4*;u2LH-1_eLyJ( zG~WjaBgohXq^t(T0xTDSLJVXQC=5U=l_0elXeBymgc!7s9aLk1QZ9%OnJ)vm64G8n zxC>+kEcQUEL9qea83dXG2IWgg4#rlWgW?O+76#>7&}aduR9*-k{{)S{f%+(*yb4NX zpp*gX>4U^TW6?BRP$mOuy zf*hNmHYBJV1(h72SOn!b&}wLq3835$s$oDb0mUkMn;+yCNVtRgs-U_LltMu(KR|I0 z3P1Gx1?uy!1lQ@HmMClw?hfe8RFIReF{s7q2AP2}2F1K)-(ceI3=9#F7N>E%2Lr=iWj1S)qxaP%8v9jtV(76;!5!_Rr#S8z`hf zZEHwB0=>=w^>&c^51?5HT%|GS{;Em_(E1@qhCYTihPB`xFr=^1z~Il&$Q7XX!uppqDr zHWn~|Vg*$1gIY_Vn1Z#3VEQ2Q0U(n>p-WsWpr#^-OF&~Ypz<6P>X6n2EH%xAwx~d< z4dfD-YeDlupm8ElYZ%mihOAp7&IQOmNA>~8WvDeNf(Po2p^v*DWFa92s$W1MmI&`x zf?72YKY>yUV$>H@*Mi#Opq2?JltF0~)XoR>As}ruNKFSyk)W0)D93=(HmDB(@-1jg z22{5~!VDB95E1OT8Dtxz#R1U^n*9LvwLpDY)IK75j|kMh1l8K0)BqZf1obySHi23c zpi%_1FAcOd8kEaHp@-Vu1EmR=&p>@BP~8d}Plwd>pwWHMSSP5w0ga)7ViUv$t+521 zVFT$CfNFQps&r6G7PRIO6xtB|pt%;1$*JIT^Fa22@-Jwf2;?`=DczuS11bd|y=I6m znY)3Fz=7Nk8CeIF#h|nTi3d=egT^30 zDFjrefI>e6e3uxcw-0J9fno%1A({{0KatWRNrtTVVxqH^}cGb3tig4s_ogD5M}gU{E-L>IhI? z1;ro4H6V2$eW2O^lnWto0NQ5{nj-*>`hwJg^npSH(T)a{1E6pO$%5FR-94Z_3@Dd_ z>KI5(333(4$Dmaup!0?xE9BA3A5fnSR8BzjfP4s=eFyEuht#N`PzUt|Kqu0H;tS++ zP?&;x5RiG|N``gdU8#`N3@YPv89?rbltG~M52_g;CWF!^C{$qK0ZIw8z+-2in3w_I z%LkgL0QFcQE2|wDbQusBWCN%?0hs~vJ*3|WG8t43gZdPp)C4-S0Tz-7y~yUk*pNAL zNUj0t1jQmK4S_}sLFR*MaFFXkWAdOF0=XYn27p4R0=$D9RAz%p9*{pkAwQD=HckjK z50raBZIPwm87Ww7g2EA$e<9%oTGImwVNi^NN=s0U3F_~HN=w9u>jLnp1M{KhiGfZ; zgY5qS#T&>j&@FwSUe-eJSpc9OHYjXBcFu#JeS{o~2p52Mf11NuiLK0N&!~6v+ zML_NZiGxa6P@MoO1wo}Jq%939|3Ll&-Aaw$$WCi6JP?}%Iune3l5WYel;RDs7 zpjA+yav7u=(xL?AMocA+(1MEBu^&(24~_it7TeS%THOpzsF8Bq&vb!hIIFIA~Qh zBrk%>N{}oljeznxxP;N()FlA^Z)R4F;trh;Kk?0aQYR+H@d4g7PTH zR!~b4GKDk2quX0kx-VNj@m@;S`+pd1crT~~nb0RpwHK(z)! zAEdql`3hARr0hes8+6W&0(kc~EWaXB3Wx=%V?pg@m_I3i(o#7#RX{A95jmp zawDi+4#_W&G5{17v%z@>v<3r~=g`M2LGcaB2cQ}i6oQaA2i+|N8p&ABkO)4V7qX8Y zlrC04ML?+zbn*-+bU~#V$jzYe2GQttfYJe|1O&AgK`{X-V?pkN+$M(z2~bHej{%XU zKqf-Q;X$oUP~RI=ih^{4QamISK%?TI8V}STbY%zupZW|ju@iifG^iH_@+GK-2DOtQ zF$U`wfm+9)@(fh}LS#Xq06J*_Bm>%E49ZWS5&~oftR4fgL9q`C7f^2)lt)2389=w@ zf$Cyi@To`h8RjFo22zhgViTkvRGNa~4wT+OEdr3=5v3rgz10KmnSxRbsAUBzX+djj z6B$6YC8#e68jH>bhZZc4f@(0xIX4Ty=T;(@nxJwEH1-cN2hN(In6s)ZXO4G1jHz;LbmSmtj1IlHq7<9pX z3W#cu&7k-Jm7}1t9n?0#Hd+RX3*^(%L8$;Tt^kUWMGUZd5)@CMb(pE(op&G?gIY15 zItCOTARjGY0QFE485V+XOaPS%pb`pHCW6K|Ky?eK1_P}mUI#w45ac4%avhY9K_-Gs z!ZZ;y-;oL~>p*6LTmWioAyI zmf>f*mw#kb%Jz(Y8g=34XQaHc@&b$KP3(~ zP%8_PW+5dvC>MkJ7O-)4kSjqk396|P{)VWiJP;AX+0PVj4)!?9123qe3%BP_I4k)KWTn?$96X9oRg8IZD`yhE0 z(pHCr5vXPX`3@BJAd?q>$K$)eXEK9QHE0YBc7h(rd`Q{>m3Oc{(PE@}2a(qkk9~)T7r7C5Wjv1pOqdJ4KU{$UQ8R#C3(5hYo)~0BIH){<#5Sx)0ICl`;lBo42ZBmc zQ2GYt9pn`epf)QgWr6CKx#084K(0ZqQ$TGcNIw)5XOKB~(E15b=t5!=)LR9`CB#3V zRn*8MH;~i^Nd<}EUcd}!j}zikP;La(z##X6QWYeQKt6=T2Xg#^TsxBi6doX(7l1<> zR7))c-zfx2W1zAICXi7cB#? zy$8i4D25?x=0K`JEp|{k1FeFF^cW$t`yiWxU4z_VyEZ|qFF~s`LtG;a;|)OfkT5WS z*@g@Zzd&aZfy50B7*rUc;zkS%O)$1G1H&yC+YGWY6Rg*qfkBE1s@{SDd=52O+>(Jo zgBdFB$iR>WV>>Z0=&(S=of*J4)`8W#Kz3?^*{%!>Z(wTN7#Olxq3Yck82-W79t;dB zY*2Ae28L-cwig4#KN#Dafx(0ws>TPhBNJ@CF9U-O2UOgTfuRV-_J{0H6ypTV3_^A< zO=noo0BXO1%34soLh{Od=t+m5x&@RDK`XmKqp6@$2X+nzs8f(HRp8jC=o;l@;WY1xe7`rpq?YBG=-TB%Ege`MaVtHuydJ_&n*MZxq!*4ZY_t_9wvHvlQYVZk;pmQfde=8?Oh`KsBsUwpHWSnyoPl)uASmqCFo0YL zS%nDlFQ~)^^?5M84x8VsGfqT0EG>x_X)}kpn4e;9-#6Q6eghB0@V9}+}Ab_+&=<^66o9` zQ0ofP^97|fkS>tBQW=&qK=KNx#S2PpuofbyB!|_eAYXuT#A0x`fYL80lt4WgQ0@V_ z4HU+6!1Ds2H~_UfLG2}wouJk@Y>oi5$`UdL0m;K4ch7*|Ukpm;pl}4OtDnm-2Yee3 z!UYH$L1Tvu44^t1G=>TqQ3m+{G|E*8ovi`62vVbgVjGkfK`sE*XrQsFYzELA8YJa_ z+8Q$$Aai4o6GTDfI%I7kC^SKF3aV=$Z61&uD9k~A0@(#J5jO4t3V%=tECAO?=&f{6 zY6qnO(1Jvy>MT&73N$kTs{29t3{;N6){uhQKA?~T=>v^8g4*7oT~>1$ z)WQA$jn07L7L+4E`4g0*LH2?|0l7yB3UQD-KrJ+N2AC8t1@&q{p$0MwR^}pH4+;a&{3@iE4%&?aYV&~F;-H)h z(gRsl0U8?sg*7N%K;Z;Qksv!kc?MK6f~s2L48$Fc!A0ikXf)80EIcI-v`nKO5YHFfkqEOp#w@A zpn4Bf9)ff)1J@i289;p*$f<9jyb6jtNGSoTc|a#8fcp6$AArhDi2pz}5XcRX&{_!X z+kkY#(hw+>!_MMg#{e651jPs>9YA*cgIbfYJwH!CO-V?r5xfrvbdFevYlIQ#HjhUz zanMRg7~6$`Aq~=k1n>f2 zut8dl@kTC?7GVvD&j1QnP^kqp4KZTTm_nt>psEY=Ckpq?QAfk&spbDEEQl7<7IsD1Cs~Aa{Vo zK`U`Ud{B&o>Qcy>F;FQ3%7q{ogYMS=r4CTN1R5D;U~psr^`RlXL{R#k3BDZx#0T}U zLG>iaRjU|gf!io^!0Ruv!F3~~eFrLyLG?2vmq5x@P)PwQi9n^nGH^ZwmCGQNAUA;a zQ9yc(pj->`JLv3SSnh=7R?uiUXp{<+BS9-yKw$z3dr-*(T8#j58R%XgkR40GJB&az zKd6oYm9QXPpuC$19$N>MyO1@Hppp~hU(jg+AT_BBpxu8UT_71qo?8fB=MEABNioDZ@dd)1W#VH1Y~6c|mz_7I>U_2E$yid**^$ zU7*kg_2@w{4oYE=v%Mg5Sg@1^D#<{p0MyO{wfsP-5tOPxId?XA6%J^W8We_*Iu0~j z0V?@HH3g_10EI9}J;=47dID6RS1^E1g8+@JLDCqg#W$Y;Gy=5{+|~uPGC;KxXuS(4 z#6h(gC=G$k1=S{?QVO)k0oL*Wg(pZHH1_}s9Y`q!8tYoZ0NMuu8e;?H4an^gpfH#P z?t_Ee1@kAUb_0zdg3=l&oq$G@K_wMvv>vkF6jJVk?%xK*8KmWt4-Qq3dQhqW%~F77 z6F@NmYU_Z?XGqT&QU-!dg@q!>*Pz-HWY$~; zkh?%V2L|xT9H7{Ml)<32Z6F;W)u8+hn#)=Uo`(ae0gZ8javn$oBm;5-D4l^uSV5@~ zGRF;Z39gt#Enz_I7Dx_84o%n`7N|`KiA9hsEQi5t1H~CAeS%63&<*IIoCk6@#FwDi z8&De)G>Qt+4N4IZ+d!oYsFnhyMNoSRl4t1>l~?TyVYx z^?)FI5mC?W>0khz*8$QCDsSd9fZCFvuxVsyV*src4q=!KE<<7Q0E%%?egyT)K=})i z=0Uw8h%F#Df?5`!eI20u4)Gr(mQZ~L$-ki104UZ$BaNW8>q-Vl_(IY%s0Rxw%a(%g z69Lr}kQ4|@)1cf7={JIACFg<1p%Cr>_4+_1JE+GB3LVhtiI5P5<$Or_2Wkz1(gLU_ z3~?!H+6U!iP|XEOcc6JTP>T)JasiFugTfhhps92Qn2>Dnk5+Fbj0j zAjCXSc?xUOf?7nNvH@ZrsN@9od_bmw`~xW&K%oNSAxd>}-2ghdW1!ps8kqsrKcMqL zAZ`GyOC&!WKzmX^B`m0R2DN)Z;|8D_8M4*@v=#=`P6VYFP@I5hP&p5Z8%R7N=B`j< z5mw9BF@VmTH(@};0Vric^g`kZl(IlJ&H(Q#PX&+Ofzmsujso4V4l0u&>Os4dKs7!n zy@P5mNUa2kb5L4e!myYDwDJ{xV>W4(|+&IK6P@8)WxQ~mP@*(|b z(AX*{H!TFOGXd2cpnf~ZY>*kC8XlCkK=nT;E@r@24MNfuC`=o{v!NjOLt5{k)Cn3% z1)b{!%2^OKkoja#KLa!>4jRJ*wOBxHAJFJ3C{;shZBVafA$YD3(gJ|&A_CnB1FE$^ ztCv76bwn70d=IK)LB0g_`$6&51zw*B%1@x3%Ao!Ws67tqKf&S()VG_?uo!9&s0{!b zbpo{~K&>n6@rzvEfNTWKB7xfYpl|}Up+N0TP^y8HHWlDmi2w$W3Q(O3>ia`l3Xs+@ zsKkTW2Qm%RT7%dCY5jp(hqJ(UX(5L+YRL#nJD^*SKp_e0kAQp%iZRgYJ5b61r9V)b zo(FEl&w%!gU@dS^E3gteIu4t8fW#GUTR=SsP%ML1#DP{@fcy>W34lsGPLJ0OA>Kx;HyQSP3A zw6Y-S1rp|^n*?f$f#MN#x_=^g^aj)}0;M!i+61kn0hM#0(FIUB2TGBkJPYfAgGLxYb045m z1Jox2mByeP4zdNZiVu?FA#MTHU!Zk3pt=lF+Jb5UNJxPE0IGBI!KEcAULg4#fIp7sSAeVt^ z5yXfiL=PxsB3z8_JLLKi6eq~_BgkK%`7e-fK&H+CmrF3;fNTeqO^X>IAp|OAAf;{q zgDZmzLp%fSdksMC9gs^Pu?gx$gXXY6F$Jn;L9Gf<*dd1yB>Z6hfXuF-=5$0TfcDyg zLe!rDRMvv>GNhe^2s22?f_y+u_(5EaoJO#x4p8{fI$a>!OkC_Ew~nyyu|{qqLe>?5 z+J;NO_ohPplnQRSfm%+W+zD!xLPi#0@dIih0_62sXf zv=CsWv=4(T1EgkweLi`OH z=Yq^RgKoTpv@}5}2$FMQt^}1_5W7I}37WNm=*I0n(D)`OOd+~RaUsa{pf)JP22$Mz zi6>A=2P(mcb)PqQ1Ru1j4AdKf=_b`~P}qQGA3=Txt%CsN7EoUfl;T07;h=DZ_zD)f z_;VmA{1E3rfMy^dDG9XK0AwCytb7*3Yz9zE2vn;=`dpAO2e}547eV%t+7^Jg6SVpO zR3^h(1i0D*5F0@$8Po#@*#${SAoU<$P@|1AczXtzwGE*<8x%LlEo%@TRDXiT7+Dy}|(2ZJMnAA<`6a@!A-azQ;_P?*!lRfJm-F!vzW#se8XpmG5bK8XAUT6F*# zuK?vEP)ie(Yv+RJERb7Vpg9}R-U3kR4C*0**7kwwP0)N6B=3V%fYvXA;u5r80#ph> zN+r;`9!Nb2%J(2K*xm?`C@8OhdNUw>p!o=d8}ZdkkQyGenh{jfKBF{>Nk)ZL47;OI3vhBQ22mW zo`Z4@Xp9OJG9VS8nhDe|2dz2)%_oCK7eFD-0G`){=>)a7K%oVz7ePJ%?a%5#EO#Ojnp z2FUDsDg$VJ2`Gnv#(F?00O4EEN=Q%|gv1jfWr0!wsQduQfpme!89^ZiaSJG}K)dNc z<3@;b3*tLah=9sI(5!VL18CLBN^ls1><7*07l8NjfZPocN6j~owF)5fL3P5))M1gn?lWjBU!m zz`zU<2d!w!gR()h%$J~S69xtb4;F|Tkn%H7wiyEhgA*%6+?;`dVH=cf!N9;E#0C+! zWME(@gR(*QJom6MFnIdK2le#-fkA{HDsI8RFb&3ro_)t80GfpXwXl#+M^(3lzI=3G$QM}fhc z0aS{DPHs_W0M)IF!TW|lEYMg1=!_@O*&LAC8Dtjd7IM%G3&22_SAk0OeIs>j1J638I2{vq7_0F4oV<}N|?8|Xd<%6tNf2T<#0DFY~ zp9wyh1ynYY?@!WPK+Ra}W|+&c6ufc?v;q@U*FoZlnsy+ke$WYJps=3Ku$Tdn=Ba6$ zIs?cKkZ(XSg|Gv}L(Ny@$Cp0?sEsxooL)e+AE@R5wWL973qd&=lo}x6h-qsB_#P*6 zax=(Z$TooTE~u6Txdqa82G!)Cutc_-x+zti0kq;1vYzK)LF@29t9?MN7Ela;#@p60Ku#M`f!}@( zNiD>e%EYIBP;5X>)B^S2Aft4k79(i=CaC2~PA&ko;S0d0C4gcC)Z>Dr6*K|^+NS{O#e(|c=)m@-d6BHPn8N3<%83Mp(Du7mLg3dVtnFv~s18b{;Y*Yu|GzQwe z1?s<4g3H@E44_popz#Vw4->Le3pW1&@(ZZ%1Ik;FlnCO1LJZV?0rdhwyP7~@0*X&i z{|FS?pjHZ~#RKY3g8GjjaZrC1v?2hMQb9BKkTC>MpBUsPfAFjzsQnKz2NKedz89#i zKL^~t2ZbQy1iSg*F?>)t3u%*sdK93&0-)IqkbaQ8AUA^I2sE<-u?y1QhNKFRzd$7w zD9%A`eUK|bWA&i28loDc4s{I*q;CpJQJ^vw)Q1J-B+w`Wh!0yC2l6+_r4T_D_OF z8WuAw0iQGia*a2`YKG|ybMU%?{8U0}`w27bDG32}@E+X7;PGvcUqNXWRQ7<@N`g*h z1g&3JV3@+NfB{sNf!eR2xggZ`6z;T4PMnb5>p)G3l!ON9cH{OtWK|C&)e>I<;wxtf z*##Q;aSjb(0NpbVyH_m4F~T|Ck%0j;QUhTdg6|mzvz-_iKqp&(+0G0M^FZtSLF!!? z7(gROU~yLl20zeVLXfx{1H&;G+ns?y1hm#4B<{h$;0I%SGB9Mp*j@|_$6#!428LfS zwhse?8fXsyNUtvgLkEoQ$G~t2#`b4mU<2&|0I3OJU`Va;N9~e87fuRP*4r5@DgNzh8$A>d8WWm@G3=CUf>_`TNe=v3wWG%Y^2LpI~ z1T?w~>Ro|qcF<@WXdDz&(?eSRAX(7L7Ep~2O81BvrDX65Oi;}RY9pYIL_=~FsAQc5 z?ze$zaZqaml!`&4svx^Sd*47g4>HCKDz8AT1yDHv>UV)!t{_)|>;&zE1)YxoY8OD} zu|T$h>IKMYE}$F@I*}T5_8(~c2h_d<<#tfb4mukGGI9-S14CLV2;eIGr(q! zKqJ|pb`EGH2ePURG&2ga12h5xG6ghlt2?J0&9~4s{|3TU) zpx!Mg{6IT#5PL^KEm}|u5z;yZooxVGk31WCM-k|}K#&QbJ`%`Buuuc7g>nY3Hv#n$ zK=BP4*9W!aK;zD!QG3Yz1SEAp=6^u`0=XO1dWNkx1Jy>LSv`;)^BEwkwLs|ubXNjs zW*&5N5=ag)+Xb>268osJ1WMl^lUG4kvV-P5K&^OP2FN+GAb)~lXaRU1C8$>cnuP$( zuR!K&K^at8u2b#+Q#SKUtbjmX*N38^>BG7#YkiHJce2}YQs{lbM2!ugt5)@mY{0#CD z=&Tt~{|nRy0ND?+0~F7*z&%9J2oB5!NPPl|K~PH$WCo~*n92Y$2^5#0JPGPigYqn> zI-t}ADsw^hPe(fU2bAMM zVF5~Cu#;*M!RLH|X2(F~8l-drm71WQA|z}<{sHA?Q0WQ@&4tiY+~$JMBY>nQb?_`M zXooS#EYSVl3JkDwK@jR;>4HOdx3N0yKXKnaPFJZjinzq=uTo0I9n`;SY*WP)>x{0CFKH-GFwPfMh_W zBFNN*;JsR)SOvA}L3>9)E(Dnd3T;r@0{H+Gm!L8d;&M>vfYK+-ESNh$BPBDyu>#7c zkTM;=Ng!8&TmcGWP_6*wN>KWMtVe~&fJS~mAqp}H)Qg79eS`9u0=T{htVa1H&E|+mwNU1JeEq2Wd`#vyB*@!P&+P0+4oA zxM92rLl~TG#sIn_bsjTl%n>w31S*3;CkTMj1}J<%buFYc0?pS#ZrA|Tu%PiJ$f*gS zIKrm`G};47tB}$Ma#|ZG9fQ)qO7K};pqvM3EzJSnMg*Fj2b~W9N>!jyDNwovmFJ+n z&7fQWF$uKyw;Ou0$z1TL3n*4Wv(lh;4rI)r06cpSI+?+dVIISLhSdz9QW>;564cHC zr72LV1C5@5>WWHmY2CyCs@*`VB0%*eWc@6p^aIs~pfwku6bd>A6%-DjH9Vm911M!p z0ryxzEh0!=u!sRvHi3GupwKD+?+XI$?*i4+pz&c)$^qp)P&)(^N}&25BnLU^0hFd7 zJdnMx`X4kZ1@a@Pb^?u*fp*40PD}!|4M3+Ff=mUK_AowZjSMI!f@(-mtb#%nl)peF z9B4in#0JGOC|!eG0qL87#^*pPK=lZuF2)t!pd1Yv=L3a4_V5Pf9Z>oLjoLIafYv=g z+8z*dA$2II)Bw2{ly^WT!P>JR7sJXZQ0og6XEVUq2Xhd`t4pmq_crGee8 z5Z{4PIW~Dvyn)Ie&>8ZCRDxR3AeSIkI)K_7p!Opuze7eNSAtg*K|&J}>!1;CP?`dT zI5ro6%1KcB9ewl{{QAXhE~-(?HRg`nDe0r;c_$Z3qAlW9Tgw?MTIs3ronK0xUe5&j@upw=rW zmw-|)D25dnAZz$QsS_lRs5e0Q02DirQ@B8P7l6_WsI-NRp@7mPD6fEe0HF3OXnhN! zUVyayL8TF>l?qA?pcDeC(-jyX?LtVMgjo`SN)Ax&1jQ|ETosf*Kxr0a9;i(ZI!^(l z4&+NvEtL)4VF{`aLFE9XF9PusD8xWD4Jfa7f=?C$rC`vQ2xdD5R4zc=0}2aJn-Jn= zgik@TkP|*Zrhr@pN>!kg4C;S?#9(O(Id6bkilA9v$Xaet?m+e%sC@6w^Z4BEvE z@-zON3^EgvwvEAQI|q@r!KV?-gQRWnsQ};;2pl88_f4un(mD9P$!##UDFeeV2s<34 zAqkrPK^m4p*zu0Rh77mhY$I^(2fAkxG~Ncux1d}I@-3)L1jVf(gE6?g0ksK0=?2tt z0>u<4+(5Y%G(HasRZwXRlJjN&WLK0*qq&*7iCxcp>ka}kp_%uvVzY9{Q zf!bc6Tn4fqlx{$I0u(}^Q;U z0F@yT44^y%YKee!fyz2i&Ii?eAa{dOH>iaLDxDyyV+I3gr54C7AUz;=KvrsF-XN98 z06O&tlyes|=rX|0{(_Wsp#80&+BT5^bMzEsFUU8L-X%yqsPqPjK*DD}yuSkpJ;*9A zP;Cr3X%BV|ETn`1g&brY1yqZG%m&2*WE~gigdb2{3yMWZ4+GST2ZahGMT2|>Dai`J zC-H%Ff^q|D-hqS*q?H1SS5O-j*2)K^1JKw8q}KvU`LK1fp!No={_6pkzmU3XHUsRm z0Z`rp#RI5ygIW%O`~<3LW`NhEgfKWVcr!RN_%n1fI5Gqbn@B4%)sF5=;_D6z`(GFnSsI4*D2J;(UoC`C^Lg&aImMJAp^q;NhsThfq_L9 z$~I_c9 z>;T6gLxzy=%nZ){0nUy=3=A`Vg4m&cA%+YL4u7F+BL;>CTr3REp-zq<_iqq~vW*xR z-l##@#taNkOrUHN28IvzEDUa*evUq_3=9$>Aa;;zNRT(fl42GHcUM2xAV))nGgUCQ z5kpQFjBU)YV;YQY!tiAqjBUywagv3>!_gDuGzOI$5H=_rJf1?>pm1n-$HL&@AL8o6 zz`zjmjfKI}+0{A7)sR7jffdXS4Pju=;bmp;^a}~{XJBAhBg)F)?F#lT!y0{725(Pi z@8FOS1`A781|P@pNY@}k1_?VD+lV118OAndn9u@an=ph-W@Yg8boBL%H)If4#LD37 z3rcMa3}=?GGWdr%`UH8pGyGW2$`Ih;=>v+R2OA)4ko$M+WMv5OjEZsux#tKgLx6t} zD3}>yEx!aB2esgQK%^XpkcV!?}|T42~|oo-5pF22hNGUTgIQj*Ly9O~Z zROB-;I0iU6d$=+%JThfua100nX=QMbU|?_z^7Ri5VqnM#XJl{;hD7Qf2-^^psg^{7 z#SP<)7#N(Qplo9XhAS|(2?GP$CaAa*1H(KR+nIr3Lo`_27-Y_j1Q^?ZK_v#tHez6y z0%IF9FdU18ikmPnxWz%)rVI?{U~Dr6hO~I7xH$vE6&Txsfx!W0z9j<#QzBT63CP}G zX)v}SLsB}7ZOrfp&Ng9q1XFLsz>u~cs>aBK;aU=uZOp*%A{ojyVPG&xg|bZ<7}8*D zGX{nSFt#}ZgBHwQ3kHT&Ft#NF!xTu^f}F;X00|#Z&@yyD*`RcF3*wh}Q2KYu1ltvF z%D}*oU|?_z2@UZNVqkdl zkrB!^W?(X7WN-|12d6g=kl({xgCZFi7}&%a7#!mR{DVRm7*@;&u|qt4T^Sf$&M+`I zIl8+$y1OzcWif*5Adu!5C>xa5ra;-C{1}oAQ3DFMH?3=Er2 zfXxr~aCK$)0A>dTIeP~)%#i?z2fI2shA=!5Wnge}b#(SHWMKFOV;eCr$cRD3jTsoE z4l^=1x%vhB`!O(tfXs38^z(9NVE9$S2(BMN;owmUVS~b<1j+`5!vZK96b{oA7{Ka5 z{=EQW8!<3^gRzYn7|wvg(972--pG)l2NZ^0zTkY?lF!KC-Q$q$Xh+XluaD#4t|_!j5+|V%VVxV+S*Y z$Uxc73=9_1P__>P!wHyrKL&;kpnT--7y`;izhL443=GF$>O&Y9d{%(%jdzU*U@!pX z1%D@J#}Ebvy)2NKK0f{t3=AP=AT@rWpn9JJln(tP!aRL^7}$gu7@UGUoj?uBHQ7uc zwoAOT4+95`?H|B!YC8jiQ*cOdNGJnCk}Ly*Q>Y6#9hO1apmaD5$_C}BHjw&IpLjRV zAchrCwufg30|%(Q3JnVK4Dn~U0&*wVH4F@s*g<9nhq$^hFu0vzWN-?NbaxDLXV7>8 zV+S+LI}K(BJBGP3gq&nxaCUTZ@^ocjs5u2LB8N{tOH|pyDA63=B2<85o>hT!VuAgBUzOc6m5@`UN{OG%aReaQ1Kvc4c5- zSO;SpGBB7eg^C**Fx0`>h79ZAY$Jv{aJDf6%QBdH69zXp+mxXO&NgE>1!tQx{DHGA z7-W{i^jb0;fVshlfx!(XZe+kv0%sdCT!N`FW?=XNW1BEA=q-Vo1FGL)Y%>OiSunOa zsND-yW5K|{0dv1414Ek$BZIStt0Snvx&+2HVqiD`V;eIt+=H=A7#NsLq3TT;7%X6H zGX{nT7~7nI;TtG^L1y~9F`R+3{TP-V2f4x3HN??_!9jxwWN%PVWB|hlCJ-AO6$}A} zj1116e$I}*3=D517#N&=T*E+ZLKcvEAAb*5p8$p_*BBX`ef&c}b<>^OFt!1M#C0g! zh=HL8#x`bPu)6^jH(_A717n*qFy!5Yii674TTr$+1H&#D+k$~13}(J11H%z7CI)Ap zka!OtBZjDUMh0iU5F=2+&4ICv7#QZk*v1SDO`!De2kQ12GBn6BfYgJOGpvKML3!2% zl#l!!ojn*B7%EOPFgW`=28XyaFf^QjvV9#HN}L!Ooc+BG$GjTme^VQP#S zBH(OGhBBC5GX{oLFt#}Z!zCEof`Q>5jBUxlAOUlar6a=;h?`B~!xK z?aWYfAF9TZf#DfM+&JEmfkCMmEN&d{#K2GjV>>f2{DZMw7#PA@plVzh7&gJ!ZVU_} ztx$1y28MSKHaIR++Q8x_@rGs$OJL$g3=9P@aU)ZP6EJa828K@-ObpKcKBn=8<_vb0 zFt!Cl0i12gunopGGGLIff~qkxWJrLsjTqEG=^tc{DFed+h+U?jcFR8)+k%1NoHQtG zeZXP+3d#nBtqM47eL%^a!3)X;^(hLVY*3%#9XOBpx`N8~EutWEeB-11{TTQxK>iJg z2lWV_9fq=v7#RM**v1SDaU~$}P(P5~Nien{1H&;G+lYaIZw1KAct;-}28M6OAocP7 zey$7*5w{r_oP%5p3=AC^n&50FhD|WGks|}!U5FY(V@C!BINOQA2F`Y7_y%KJI5L>r zgQ~Z5WblKtofuX@*+xc=40qscCx%~ewljm-eTZHoV@C!*INOOK4aPQcWT<-pQDbcA z$gl{`c49aJXFD@IgR@;2SRTUEyD~Vy*hY>FZ(wY5M+S~ZP&MXG3<_|zGs8P5+r-e3 zLF6$+jftTXg940gJPkzvV8h`5D`Bf|kW+lk>0obAl;2F`Y25O@Vs z@5(R@#x`?g(0L6Nw{T=|fwP?$!r*LYh8#HCg`o}3c4b%vXS*@%fwSEivfe<$y#^MSLS7&72&XNER7+l65fobAeR0M2$}xCUptGkk%wJs5<3!OZt$ zFoUzb7{cIeZ-yc`+lS!*jBV)15b_(U*U*XK8H{b@$j~Rm2+I3Lh8B(tOWxq_NJ3|6;6 zZg2$^Gz|0LY$JvTaJDhSKRDZjLE#Qey(xngoNdOC1ZSHw)WF#m3`yQ#yIc$mEE%T2 z#0?D?Ho)123}@hMBZe1nwlPBxxa@LuF)%b?$bz#?8QS1%GloTQwmHKNINO3j0bF*0 z^jb1x!PrIy43pq&Lxz2Dwh;p#xEynJF)%V_SOjOAFzkb~O&RXN*=7vC;B0dS0dScH z(rdw>0cTq>xWU-Q1`J7XwjqNYxO{VUF)%h_h=H?>8EW8c6NUwFwkg97INOZj0-SBm z-~uiKLFQO66v5e+3_UQmi2=hBINOln3Y=}kU;!>CU0n=Jj2V*PY!ik$INOwA9-M8) zZ~)FWXZQtYTQG=#%UF;(mJC`jwy6O_0Gw^ekOOBMF|@(i#tie|Y!ikZaJDJK1vuM` zAq-p&ySf;dnlp64*%k~(;A~3SADKz>o!J8#464*+vXY;A~@t9dNb@!#OzH zlz{_WhJ(y8V~~Th%^5=AYzu}GINOq87L09fz`*hXnlH=^8MNSRBL+7(+nAvM&NgA_ zfwN5+R>9e349DPXbA~5ywgm$dxV~_8F)+7eP=T>63>fU-Y(s`1INOLJ1I{*PXoIs& z7<9n(i>r%)g(*V@oNdN%3eGlX5Chj2AT<^Yx8Q6`1_5w=;p$>wX~19vXB#s3!P!O( z^Wbb_h9hvc3Bx@&+mt~HT&IA{F=McRv&|X&;A{(q18}w_!!0P=(7=G<4V-Pr!0`?e z=7t7F403R`F@qVLZNlIOXPYu)!P#aE9dNce!#p_Kg5e#UZOIS>uH#%?3=It!p269M z3`!rM_8J;7M8Vm{40GUY6NYPWwkZSON0@puh5$I*oS_ZQwqQ61XInA|eS)etGGOq5 zvke&<;A|s?O>nj`!v{FqghB2zOs^?J7@Td!FaypuXE+CETQIPEfvLA-@PV<74H%l> zY(s{1aJCV{JviH#K?7XBySf+}n=pic>tYbwl%WaEHezpGgAFrG z+>GH7oNdm~!2%PvV31;ku`LU1VZopRXInDZ!Pu4t3=wd)Aww0MZNxAG&NgP)1ZSHtT!OPr89u<-W()!X zP`fP68I0g;3kEMZ+may($~H1EV5oqz4H;&^*+vXo;A~@t3vjjx!#g18vfykZhFNg7F~bo!+l1i> zoNda$AOZD@sTqSDoNdlv24`C^_`%th3_D;A{(qOK`R&!#5b)*nq)81FF~9kRb)mHexse zXB#tog0oE+8Z=?*O&NH!U~DsnNpQ9~gM>Cr+=5{foNdYAq5~B-F<`g>XB#qP=)%N} z7$U&q0In{^CMFDhaJDIffj&%)8H1VujBU1i;yr3@I?SsR2V3oNdT30nRpJ zSO;etGaQ4nO&IRM*`^GC;A}Gn8PHrzkgJQasX2ocoNd9-0UbjyHnn6}0ArgOFzkb~ z4H;g**+vXJK9Dg7V>4q0B{WONJjXwz&a= zNC8yb+>k*B&NgCjg0qboBH(Njh8#HCl%WOAHe;9rXPYx@fwL_bF2LEA4DVoU3j+qB zLa4nKh74DBf!eN~zCn%*3@xBJyCCrJHbc^TMh0i-Iunr(Ft!ncMHM4x{T_Jkh66MQ z1X|0-z`*dM5-bj0*Czo{03p6->O_2d) zKG+cgN)R?^j9m}P2F+jiLD`@&b}f)!Lj6KObGQ$tGBG%Zg~YoC1u@9qeB!7#Un#ok2_noedyy zmw4wOPX;ZpxSI=T4FA_@kho81gg*mA#U7CQ-~iVkM}|otaZf*Yk4OfFB!5N*7tf#& z&^nNB3qb7P5GPM>28)F-wl71%c?Jd-&)^Uze@BKTaJDbQi`fu0?*2XuE^}aPUxq{H zAnH9_eHetG>OEY288XBe7+m~8D`^-QtWJX1F8&}79GMSc$Ge3voC2u{@ecqJj5i7nV9;t~0<(=>JQyxP#Z7_(7=BEHh&#FjFnAqnhNy84abf7Pg0NkJeHqTofT(u~31;{KHPh8Gh(Ty3MBL5SfI$t;He|4X zvyB*B;A~@t2sqn>AqCDhWtg)7W{w#{9$eg!56Z(7Hz`J197qVHZ?=uv-K}05lAP1H2jb zK-GZK>LpW<`A(oHWssQza)Wccv6%tGo>dGCZm!Ppj!r=g%T`0#&dv-*wGcIq&OQu$ zy->DaFvGKzP&FYT3|f;xW;(|^IXW}gK=nEWIWmMn^*RN4Gca{R^g0CxFf>5*f+onD zpz56+!x?tK*v@VYZB-EU&d$CJVQV0EIlBZfuuO&6KDckF&SdMYmg_y37GnjAciC65Wl!agfX0(1Yv{L=RAPA z)6LJBK?-V@n|~045;R@81voS4K*P{2094pP%y$a_b>8Ro$-2^!9xVF3&)U~0m`7lnh z@M|W-OfMH#1}3Q6ynF)~bYSBCE(~SOP&NLM3;{6lfFOn&(D3&P^<`**rcWP7ABF~K z82UIdFl3cO^!hj%GL*sDMhq=*wlPDWF(gcUoJ<&ALEYfvPH`cHwFio+x&ePis0-ZhG{UpK@1GrV0wex8G2xL1%)s;z}y_{ z#t;K@XQ&IqKbRjw{TNQc)Q1Kz%z>#7cVU-E@CD8eVi1D5!8hEA;R!U2`G&hO1i{!5{tPj2b_m0?6_B** z=g7dY4eB00A8&>n7~9v6AqAS3{er_87C^(tFC>T|38p60m%#~YFK9*H189Eq3wLEu zX@ta!zgIBB3TT-4dwVlXX@H9R1~WK7%?B+GsDY+`e?K<{JE)odK|u^@(6k!h=)#}? z&A$PT!3+zaVHMyQ%CH5R_5xg87-XPnAiyn%K@IB80CyJ#2{=2HVHZq|R{+B|XxSU! zL2ix=Auzq3?hIbg@+v6Un4t+;Ujzj^Glam*33g*Jfw4pU z7(}4?A}G|4VFgS)+=XEdv|bK&3t_kdwJX@in;{HpX0Wd@Lk2YNfehf)1kaQ9p62x!->gM23Cx%8R+*}x{p!p)i&4*zFoE^ka0ap{spas+G?!q7d zwKv4ajlm1X4svHGgVtps!M+S1p#BXB4rY*phI2@$GXo1W{fC76Gfab~|B%Q4hC5L4 zP_JNy5E$Dxm>~`7o=`t01`}x5h6Xq?T!E&CFjo%-A!uC_<{Hc}3!0C@Je?TkK*K8B zF^GW$>YnfbKZY&P@CgrcW=MgCZIq`ULmiCmAHrbN4+cSuet_NM>eVu$5a$sz?2!?r3 zy}q6S3>8puKUaT-9H?CZjvfpzpzaLt4`!%?x-%fim%$8bevpe3!ya(G>=Ga3;>#cb z7Y|~P0GG)w@jETHBGdAcwJ!Px-}9P1(e0(JJBpneQ?4q~W*v!fUU zVC;Y(h8PSfr^Lw_%dif)rb4J zFsMuig_}pbW2g(mjinGaD2@c6;?Dkl3`S74i)S!{3zY5S=*PeWWBWTYynyQUbqrw0 zm;j1jk9gkz4+c9Z8&r=3LDdJlJ2LD6_cc7?gM%FzUVzJUkNDsaM}`&Pvce-i%r}6+ z;yB1Xp5S)-Di}M&g~5yoWWHy-Q%Dd)6hh8lY-|TtgT-pzaBFc4KgXstNY=Wmt6tlzx2UgMADc?!nnc3_?d?;>HYa zaJC6U6`XC#unx{PV|WB-n=^=k`|=<&Ef}_#fa2FTJ~+gcVFffC!h)R{I-qWdaEf3k zfx0u&HJD*v4@kWqIGwKp=LLUozN>?>L3!;Slnu&jLg0R}e>^C!IYHS0K8_66W`W!v z04__PLD~KRAq+pD?7&b@h7Kqjl!o^}*})->40GUYcZQN~kb45-gM(ZdLcneVvBMeu zoduOk!67crz6^(8Y&Tzqe-|L)@lH+*YB095GeaFzO}vXUg9DuH#!v-QytI|NoA74PiGz+(*x&)}$dpI`>BB~W&tGegc{ zsG2|*hGQ`Gfj$fe;Nt!aYDZvdf*9U_$3}vq;sc`??7-!1a8!JdFGCBs90-mAl>=KK z=EOTY`Y~*R*cA`jgsCdIgSZp(q#J`9f*gWVtR670;t1!cSW1~b%w+nK@fKF;n85>WL%{*DYYpziT= zcVuvbstI;>XXt?173>3~3z;#4OymN>rgB!RG1+je??7-2w>UdJ>40yz-41dyt`Wj!yRzAg~WRVxiE070EJ0N zypKBrLlx9sUr%=i85lb_gdqcJFDM_~0_TO0cu?P=44m&m;z47o^Pu8^?hFj~z-_;f zc+eha1#n!4#0U8XGaQ1t4b)EVfwTP?c7V&bkoaJC28K`Iwo^!aaEK>E2sqwB>|h2z zaNdmf1-bKFJ0oZ>IcUI_;hro5hz%O5WH{Fa76)wwVBqM6vJD*>g5Yc?hDk8Ckt4$e zINOPVs|Tvy$eAGt#x`bP;Oc;iJ2EhY!Prg=4AWq2X9k8hP_~g11A|Z}M7@zS149~= z4I1md0cC^6x+S3Q1g$4egR;#S7#OxJgy?lLV_;}1Vq$Rf4081e^I;GHjk|h6_RDKn zfYb!Jf)<+l`GMJ>p+SZ?&^WD+V~CTh3quY_+&?rZ#Djt14M;r5)ivBPlHmp;6N6iD zNW70*5W^xU+t)3K;nrJ51~=#&!Mt}cwh@EGDFz02M?ZI`&>)7Y10Xgym>HrLK-dNh z3=FfNY|#G7Cr~zMoS$VOL=DLAtDtPK-xoo|L4HqwvO#{I1!Y?@Ffd$z+6CJGe#MZH z!QBzG+mM0b1w@Sz$X*Q-h&aez5r}$_RSXPg!QvpR7y=+{kW~zeAZ(CT4ByUy)tG@s zQq3T2bI_n_*LkqGIY^ud;ui~$_$@m|&>02B@rK3>efBW6Im0m++sKf?!~rU9WWq23 z&NgE>2WOizXgEUE7#lKl!P!O(D`4iEFff!s*sh>HsnTf%26rD;)i2zi;hG5(m<^iCI3^Aj_j7b{Wti}Zk-uU_!i?K%nB<|}A z+E>-02x5b$8W#U0fKV7?{9pP>Oc3hKR=-Ix_r&vz-`rz|C)?9gJ&f#DX6ZN|W0 z^c5;@&cKid^{s6J2LEmy2mu$g+ZhYqQ=aCf#DF0 zZOFjz0U9P|@s12fo+PO{AC{R$S{c+#&%(lcmYvk5%0)w>;sJL z#L%S;vCA^vkzvXgh`43E6NAh_h+fNhXNH0mVDWgD;1Grdr$ORDzMw-xtiWjiG*ipK zu;V%dgNIje2xzY{%Pkn&jlt>$j2+I<1``i=V|Zl2$l&1>4{E|QctF^mp#8{3mSFJ! zkVFqeJOCuI$Oofz28g3JsF^7M0Ou=9tg@pETj$UDKn;1TK< z0xBC{J!WL^h;;S`ozL)voe9haZ9+c524(v&FvP*cLl_vQOkia2bapn3_h(=TsRglJ zL0TEk$uoe?x&ak+3~J&`U^b{;cqR&AgZ3|TG=SBE`=o7R5OGkwu;ds6gQtt52WTQ$ zjf;W7(-(9Gg(1TfQ3eK2KNs-cx1!@9wwr5^UoboW`njHf~pb*PrrE3P#lBRV@3wg5GPmA=@+L=85z7B!9mWDp~S%8jHYM}vvM+cDJ5+0lt1?F>jf)Xmq?kAba(5qvHX$iYTXHYg1!$ufY&LH=slz`)?` z9t_@tpOwi7K064cz6Qz$ZJ@mbXPYqeErh5sjJIUa0J*`>(+#xw^M?r&gO8)1vunH| zgNrGQZNyLlXB#stfwN5*dX6!G)VlBYO%OKtEQq+x zU~%w1O%^*C+mOKr&NgDW2R1+4B;L@J!N3-(#*Bd>2F5mLU}%A{Ef^S%z}S`y3@*|j zH-v+Ngki%puwGEr%CHZ{c4AxYEK0w)^Mj?wb#2ipVPz1^b zHU9LVY*4$@3Bm?d=?qa&Hpt#AC>vz&EGQdf?vz&7bqKKFOv$` z4Td0l<)Cbky+#l=xZUdlWrOUEfU-gM)td*?vdpz59B6qF6J_ZyTAvR6k9Y>qL=-V7)kWbYg(8)WYqC>vz&J}4Vx z?-M8+WbXk`8us*a1X%5dZ`BZIG_ zo2R355JL}G+{e=?lz~A?3Z%vdd^T6gaS+=NTt2J!x}I<%r(f!4XE^CU@$OaV(@hha*j7L zW5|HB%^7aN*%k}}<}fvu40bTKu>r#dINOln1)OcfU}FJQZ(_iZ1!o&G^uXCB442?+ zQ-&X=Q1zw^3|>qib}*>>#h`GEfx*`ooRt|aSurs9`a2mK#XB;@S;N>)4A0yEo=m~JPGs6-%+lAo?obAdG;s8_c z#_$i$c4vrigo%4FtbnsU85*2m;$93M&M>w&!#OzHhe5*yChp5{4bJvs2yump`!hU% zvjZ5ectONLZ8-^V7~6?q37qZBa0SkGVVL0qQ{&3;0?G!p>kND$;%4zq3=`mNX9f;G zn79kWEI8Yh;Rl@U#$e|UQ{&FC0M7Pc&WP3>+~qwll*nINOCmBo-#_%5VkFc4O#*>vd<~34^KeV6cO;JsEW3VB%g3d*I^U z40UjEABMbem>OS(DR8zQ14{%<+@GNd&JJMciG+y@d^Pll=vn79{%PA81*&5#FY`!GC%vwazS zx?pPj816vXpftwS4G{;WF%dZ1nPCT4B+tWB37QyEF9k!o)ooR`r3| zrl2s?>W8tN7$(5k&J1hdY!?QO2{1LT3@&iC8^bg>+ns@LB20}3gBg?!>bnKO*-i{S zaJDnU1324-!DA9cuSvWsLlcw@ilYT^wiCkvINO=QVKPKLsQ;A#XS*_dg0tNiCQN~- z0fpfcINOQAZYoUNnPCN-?ZRL(4JPi&a1PFPV@R706L)7=2W5l$YhU1OCx)^a5H;rU z&I}LWY!?QTnGkVMKh6oxc4A0^vz-}E!PzbhPP1U@T^SC+*=`I8vti=y4BOyr4~B10 zHmH2im;+G*Dj!zB+0G1Hb7A5x3@LE7E5j)`+l}FnGFVMK=$wirDloPa!#z0LnW0P- zDsJS+a0SkGVu(|Ni90jgg0YPq8GO{C;>J!4Ti|SG1~mOaEgc!Yz}Zd=JjPJ(7>5N%N(NK(7=UZ9-QsUz+?dxH*{oZfwP?$4#C;Z49hHGYFrqOz}c=0 z>mcolctb-shBP;*8WTr`4RE#-!vc4hxHE%}2aN5)Fb~dlWw7vsiMuf@fV15hp1|22 z3@(uRI^NL4lVMdP6R15CZ)oPo@Bz+tVn~UCi90iNMZ?%G3?4BswkyLLINOcEDi$X0 z&QJkkn>#Xe#6iW)ofx*j+0G1?;$h-03`Y`RY&V8GaJDnSm()#&%(dg0o#2cEQ$~HD|WN?GCofz`qY-fgoEQoqz0~ZFZY#7^> zVHTY2#^8_x6*qQdxB_Q8F-*vXi90hS<-yo43Pv+nGV59Hz#FVF{e=%D_D}V%!9L?7!jo}QO?auHZ9H!ob!6gF5_GCzcmdR$uUJSE*pyFnZ43FS!Ck8WL zsJOW!!wfjviQ&|7sGBVu8Kh3Y*iH;9;B04x7jU)mwiCl8Xc=e@ zipL;`UFJqk3=Y9Cwll*yINOE6C_J24!C zvz-~{r9sUxc46pAhp}B5cEH(g3>z|F;_eJ{GGS~FhFx&BCj(O!Ox%khWj-WrSei00 zyx9x!qot`Mg91$4jDev9CT`})aAzJ=y*UHJ3>e#jfk6hQ#=?;y1SW3Dz%XGC)J#i9 z2CrQpy?!B%5ey8kK;tL={(err3=A!xahL$ms1O6g9FTaZ4`|4f;onvU2Hyy0#~>F5 zhK_A8wl9MljP1q1a1PG)W#HQmQsd_18Nk5c1!jAN`UWsCXs|Od_{Kx_g?WM50gl1J z3=C0^85#VXeEmRUN;)h|U^Zw>iGvx+_F-U{1!IRWFgz$^V(@bfclP%SVfgld5yXxU za&=~q(PLup^Msrg|7#}$gP*S_`269VeGCkK{?6dNW0%-KY+rvr*GPswkQ)DB(DGFV zF&{<-KY!2(=nM?Uz~Z5Po^A{bQf46WFz`67!9*qoKmYguXJ;3N6C4Z-exbfjpuya8 zTfyvLk1z%XlhX_g{?4wBexA+@El(I3{GC0(1O1;knZRt2zq&Y}Y##;&JD7L~1A~wm zNIhs|pMl}UJ_ZK=09QYdt8-2=GWdsvxP=%pFc_SIu?-lW?{*+vWwa5csZ z6)?R<3=CZ`wlM?4Dj3^@f#Dd8ZOXuK1;#dGV0Z#!n=>%5z|6E@U=V|`Eg2XNoMd1K zaCG(tg+mG#i0$s`WN66n=OB!2%wTf}#x`Mib`8RgH!xr@Qea{TaCCPCjSx4%*+vZK z;A~?C7e$yF6NY7Qwkdk6hcMJ|;m<44A`-d>xsb&P<5dcaY|DbHp7_34KL>x2*>!ZK`Rs$NPD}k|% z7#Plh+~eWz>gO53Fz+HGLx3mb41bYpP__XBgWM$u8)QxklnoloWw;Cx2Mw8)K-r*N z%W(xF4jRktgR((mxhz*9;-InIDNr_O4B1Vbfg!*%IK0(T;oj`8r)!P4+b+(IQW2;1corwoM2!G@b>|4KD>2^5ybX&3}ax} zCIuFc_XuQQm;{O=e?Rb`Y6vK-{DTbR4U8G`_JGs}8O9r0GDtwgK~ceA24#c7vkA%u zg~I|U8x#)vplnb$Fw}z00hb{g#6b22`GFP|T#Jj3Wp$97srqw2DKdw z3;{uqeXLD_Obh`*exM`g8L|YRY##=OusTMFxSv16H<)+`1H-z_Aie$ppq-$rL?CRC z`(;F-Y$FDSS5+Xr{@}vm0obmPAkaFHJ334Z0ip5EZiWmD0iPKmY-0w7PcXI#1H+Z& z3?Ok|P;`WW(m-U02k4x3nVk#_V6TKQFkJFwWPsc-!N4Hm2VsN4Uk=I!g}(-r4GMoF zC>xXxKS0@TjtmUndLVj1^T=$yP&VisQ8?RzAqmDdGGLekXB#qXf$5DmVb}p@n=> zFzh?SzyR7v>){&2&?W_DyMrpld6z)!5I@kR8ZDP$Yy*ZqINOk67MyLwunx{PX4nU3 zn=rh9vrQTP!P#aETvuRbnlnhi*%l0HaJD7GA($JC7#K`o;zkAx9&olHLkyg4#E=7L z8#A=P*(MAV;A~Td6>zp0!#+6MoZ$kTZNcya&bDND26F@G+-aD&u>k|eRjB)o4H?AX zY$FCcINO*Z0nRpI$b+*@8LHrHGlniW+niwyoNd9d56-q^_yKc+2?N6=n7D}n1Isn2 z8%zutMBr>A1|2xtn86OtHerZ>vrQSY;A}I78aUgWp$E>kV3-DHTQcy$;?9(TVF66s z)PP|foNdUk4bC=VIHm_mharCMrp636;A|6yH*mHo!!J17jDbTRrrw-E4rZ4b1A_;Q zZO*_D2V+|>FqFX9mJAFv>lqn>oO}&I>${#*fZ5=)h_foeY-7-c93_T~3_;GIoXo(` zbCQ7}$l1x!&n1N6&nXBy$ls5l$%~00$l2M^FM@%gX$wfb2l)Puea0Yh5ZjPpA1MDi zdw}m)vFL-UF=b%r+YDu!F)#$c^jb17aP))31AIV}-!tHBBL1`ik;bjJsb4O){1ic`=T;D)XY^WbbZhDTuYy$$0{7#PC#fb8-% zjyE@ASOAJ&e_vNn+o?;Ofgvaqw2Om*p#dZw>I>RM%+Mmjzz`GCT zB<>s<x zVPH~XVhDEi3yBD3VBj(XsSf}}JA)b6oFI2lCfv3gq&~zD)EK=X&cG1t;UD7eXUOmh z&NgCT0)-n`+?c@)6mA~=@j;G$4C_uYFa&#hf){Q!J!NDF_73$0^{3A9GlAKl_RJhU zDBFjD!3-uI!oV;CWWJwg04RPX&M+_p2RMR)oFN1hzX6UxLH^+k4j}UbTwPsUgBW&g z2dN2ijP&$lxCe@_z)(jYe+Gs;5Ie-t2Q>fgA;!P}$_b!0*p?NH48b98A)t1?!b%w1 zh#?2gHfA^kXPYp{t%9jBWvGC&%@{7h+2#ydt6^#^7)s!5ONI+DwxI!o!WyU=LqmoH zINONfjXo2^J%+{%5e6`}3BwsU+myl15GHQM@a;a#9CHSj2Qan;!yGuXfO&K4oD9i)Er_1zsg2X{=1BL@|wjsj}INOMU#|x&$n86Ls zHepDEvrQS;Kxr~0ASm95fk6VsHfCV3fU!a6{KMF$3=DNJwiyG%A{g78fx!W0uLT1` z8jNkpzz}pCXxM-uZX+m6f;>UDg={(lW(R=Mvl7_9K>?uj{744m z{-A((9|ndbCMes_pP@+>$_`;*Sd<51#~X!vxG+4*g|S^3HY@;{84tQrkilys$liFz z&=7xySM$K)prbVyG`51pT^xNK8E$NWu!F+=82&)mZjQko4AbU-)w?-{1u=-tg|WjJ zeu464NW5E!r!T`bh&X7uKEt$4U~}9ZgPa&<%m=G?2Q?yBz}ZF&d*Ezih8J+Q34;K{ z&F+5grVKW4wi!bToNdm~0cTq<%z)VI0XjD43dCN}6?+VqApZ67cl2YhgP89V;uOT- z1X1JbALhxh3Zf>!!#{+f2qF%;_KsoMGO(Kif(#59w!zs(4ENw{V}?I)wh05FoDac_<$f#KNJ$b0ibeK1R4%O{s9a@5b+>KKLdsoINOk+0?syK z=z_D28D_xQCJgJ~Y*U6kaJCu4AvoKd!3q*zpkkAO1CkDdL)`rs(jaL#IKP6^!l1z;MNf5p-{$ z5vYyv4aPQQU{LXeii6fA!q}jD*-kKk)Pt%8hJ6PiY$FB+hDEht^`4+S-X;$b2ep5u zz}Q9%47*@#V+IB$NhYv4pmN}tIF#+f!0^i)Vh(5z&b$UF+s~gNMFOfOgn@xa9n22) z2nu0nfw(yw)QFq|ac4NFVR6G7tQWM!oFNNhFUUCzS>g-~A+FA#;S+`ue@2E7*9eeS z28BgnwsE{A149)jNKHgYL}V1h3y@w{$X%a83Sev6_LV_HFJ$xA$w(S71A!8IqAajEJ!DC-6*BKZ> zf}Qr{YQFlVQeFYgn5h%A(26@0sa9D%b@IFA4i5GI!p}UVbKtO2AxX` z457hZ;C4=hDIc@~E&cF~F1x^kOT1rd|Va|Twu_LRej0|DU0iX+V z82<2r!~?+NCwq9HY##=O7?^kn1H&gV28J+CXID^qcy0K zi5oNcz}Y4YRdBW`!x}i-jN#Qaka{0ye_vOIZCe-_Km~=5fgwZ0Rv6oe;TN23%#g7S zCT_xT1H;1%?!cQidX^=^&RTGvqTA zF_bXmgLM=zq=MDwgUtrH3S<_@1(^(~47p(2LGFXN4rD&aM2I;cv0?_0EhP-840;R< z48CC3Ktco*HVO=N3>ge{3|S2M43!MU3?&Si49N`H;PA==huJcQbg&pGY(O>_F{Cmi zGh{I2Gt@DZGt@CCFk~_c6%>K}4>Bc@ z0cJKVEQ%R285F>&4l}KR(i13NK;Z@PEhtuCsRk4l0pL^#iZKO-N{loIN=Z2kAX!i< zgV_QJtvZHmhB}5ih5!av1{a2S1|J5024@CuhIob$21kY<26qNmh7fRUL0pRNzeI34 z1*LCL2tm{+Fo04bD7Ay)OaUCPX$(2w+yqJq3Jjp|1En#LI#9|1rDBlVioj`Yc=^T^ zoI60V4$8ZrcuoiBc$i2EIG2EUh2T5}%b|JDoDV7=6u>$_xet`iAz@ey4$mZpQifuN zWN5Ad<$nc+95^orZ1y6CL~yt|Gn7J03rH!I3r*eV?gEt+kQ9~y`@kf|VFLu4`;N}zcMl+Qus zA;fnezvhC=K}b0V3pH3;OJsoMxjbh1WkC$C49*Px48Gu!z>guA!I1$}#v6k3z6t{aLmY!IgC|2U zgC|2jgCBzegByb*gAaobgC~OvgCn>;L{|~a5DGReh`|x62Hiwguzrxaeqc5JP;)>f z56DHq;8Mti0V3|k;Li}m5Ctxm6c|DnJQ-ZU?)7I-U^fKRO}Lj*$z z1IT<|uzE)ZUj|nOKZXznR}xL~V+dt%V(<9KG#0~~DKl_2h1LOzL{;WC%P+T!EID^B-oxzd8j{y{N3JhTkt_&Uw-3-nQq2Tge zj{yV~7&I6Hz#@>i_F*t&P+$lE>juR=$Q(ZgX9iFlf#O4x0c18P-XK<$69SRt(i!a86rfHG)|f=V%vyFhJMkPARA0hN-FFaWiH zK`mL3|3R&SOlaK&ig`#q18OIMVjQFb5jr3qs73(!8dS!D{0eGMgW89g&{_&)2FPYm zDgng~hz|*Mh)zhG1ytTbN=-*_+Y}PFl!O^91R(Z7>K0Ib1Em9yuZ!WS72-DpAC&7r zbpfbs2BmpKjR8qnq{cxaLn*j!0r@nQ0aR~6+MS8uo&ls~3kn%fD51v>L=Py$VKoLQ zu0VbQ)fk}u4M+~ehlDAJ2g@CxG!5c|bU<1lpf*0JegydyREj{_A)wFzbst^=t7nF8?#C~iQ#P*5)p`3*u8yzJi1qD1SpzH>jL|m2IHj3#i6*We8$WU;vfspt{WyT+4?tfLbK5R0N7) zQ2Py(MnOJ>nSjeLNertPior1fYPCYj4oLifN@h*I=&2JlngJ5eWthhR zN|B(L2k{~46*I*rg4^;Se}YU0^+!Q1Pf*DV5(DLVP)Y!$43J5nkOj4(LGmCINlRa# zR02tXAiWCU+ySu_R9@vXfJD>5J_WHr=>bx*fN~`whohz{*oY2H6cS#J;1)G#^a12| zh#fFLKx}~3ov67JXq$X-aj1S%bg?`47VEvQZbnFNX@P!)1+p;<+&%;4JWzUv#4)H02IUM`jst}UBz=JLAE?a$ zF##l21nz;tN>E5h!NLIKVo3WBlrBJi1J!}Bn0I8zWyobHWzc2tV*u5!p!NqSP9bK1 zLK~J3K<)&EGbmI*E-V6%4T4NZ?aM$)D^UA82;55nl~$mWU?4F7aup~PKw$v#Ln%WV zxR(#A7eHybj$%H1IbY!yFqG^(?=NCMwv}~k##aK_%nb~0jRc7U_dq{96Y*_0Uk?82alVA;tg2^!W{t& z`CxS=3=9lDV3(vZC@}bd#j+WS!R>NTyS4}%8^sJ63!V1StgDIp=| zfJ*WbaF1Vs!Gj?Y+=l|CR*)U&av**t*zcfpR1R)^pxWTf018!5ctgq?P$)q1G^luZ+N1*;0 zC}u!CCq0G$20aEx20eyg22h!1$N-wHL{|yR*`RjOI`9|}DCL62F+n5Ppb`~i9>`{p zJ!-y9SVpw=Tq9F&(qu7lInzI0z51KCljZi2s zfJT!*r6y?X5|mm%F$!vZfl?u;RpZQ%!%)hQ#Gt?cas#M+mc+0MJO=@?3sz3RV#x(u z*H$rr##2Bo5s+OlIZ&w$8g&Qx0hD4eZ2;wKP~3r9hoG?wNG=1Vbx>#_=O<9?f_i5l z^FXczxfE0)gTfz_+d(FQ><8s+SMZz>DE2^p1BEkaJOmUrpk68{b`;<#78J7}yAq*u zGBCG;+zW~cP^|@W4=DY?as)^{q}BrELXb;9xdu|=gHj#Lj4%dgh5!c8R+&(6c>(DO zfM!TQ=@eAzrZeQjXR<({1rmqVg8AUG3{-Z2@)g8PkX?=pp!5f-c|myqRDbz^N1Q?R z5GbdBatEkH0Hpy?nGLFCKqV`P5AqGjg`hCcg7#fNB^D^3fJzmRYuv!|TA(mS)d^~` z!SXyPrGs(;s8w489zO!b9w=Nurhwc9YCC~cL*~{%Wdvl#2&4|xTu_<^ zLnA{T11N++t_Ia)h;|Rim7s73l>~7N!3?Pkpc119+}kc@SOleEqM(u1I0n%8Ey#r+ zlR&8o6gHrI0OEt%ETA!G5F6AE1LYo2nGDhi31v|F2KBH(A)Ep3A?AX|?LcW2ln&Jy zEEqt0Mj&Yrq!*N8L1_@yTL86?K=nMx4WQaF4cdYR z0&)?ebqxw@P|XWz$$;DlO0}Sr334N-Z;L4LKqUw$ZXvU;pqK`Q2FRx%3`#wakcQ=j zV)*FHdInJ66BIX~Gz|)MP&*2gcR_KI$Y9R^3mHgGMc4@md5~K`H9u%X0-_719@I(% zg$+mrC>=x02bFjrlR@zaiWyKE1^EeNE2#Gi3NHo*NAN5L$TcuOgVHLf9hArbY6FAP zFen#5N|Qu}b>N%{2|rN3A{kt^f^sOx#~`~vZUgB6l`){c4Jajo><6_1T%hBrp!NtT zywVszeJ)U10<~g5B{QgW0Oe;`Y5;{ds6+(S2%vd9P}qaigHD zkg8yYM21WTPcCRI6P<+0o3AxnGXs9P_6@oGbo<(!D}597`zxjy;M++0i`-n&k9uYg32aPP6V-& z8J01C!USX!s09Y5Tm+JZ z_!d-~gF+V6mjabCpb`O;NM&4!4+?Wo&Piuj1#UfoLJt&1Ip8uFl;=S?0hC%mDnYdXXv7E9-vEUzC|!X1 z3m{*Easeo_)0U2Ke^-w^u0&+dXtsu96%&CBn zA%VgOxy4)x4jX6iN)?dZpxP3Y@<94QDX5fT9(ZjFh!091pb!M91Jx&>H7Jl$2ILZu z3eapf$jy+ki!^Xw719a-=>dgBF}SS*X&HfH8PvZ5^=U!of=mIm=^;54)M5-~C#3Ch8s z5o=J&hPWCsQUNNNKqWWGO~|u!;RmXhK)D)ZE+{@g z{Vq`4fy#4`eo%S>g;p^*{2?nJAn6Ab)1Z)nl`A>mQVC=asAUH-50oN6g#}V z0;uc<#daBZY#fsIKxTtxz(H<>q#c;+KxHr}20>vAYQcb3)PP1sK=}<+mY0B6YJtKG z5=Ic+knjPOg!v3r@RAZ#5<^;HAa_9W6J*Q^!iU)jDtAC;GeAehL3tV!>!6k*WR({v zH-mBxs0Ih66;PfA&D28D7$_Zsd<;rwkWmU44>Vrm#h?yOr?8R^K{*tZdSUq;#74A{L1_f!1JHOL!e^iy4DxFc zxZa1@4a!ZB)>9$_Bz3`J802RK@E8%O6ab|vQ0au+ssXhXK)owaKM@kvpi&(<-@rl{ zW&$X!fzlYrx1cl(k%#ySlzu=ZKd9vbvJI3HKLu=w)_3I9K_C$Il^U0V3yvbb@>gnkN9Q z-URt%9(bK3$p0W2NGO9`hUz;|o`#hbpfClM9iUhSwQ@k=2+IGkl!5RE$b3+%0pv@N zc_5vTQWz4JpgI{8lAw|slxjh-3Mw%`xecTiR5yd_LJ%7i8=(9KYL$WXfa-Tp`v7DI zh!3&{qz+VmfLsF#ffDd~LQq;xWXJ~h;Sli!awQ~Y5%~nQT?Wd-pmsIH22ec*aw90b zK_xZF?V#2es2>3eJxKh3VhZE~P>unmLr_S9Yy!nAsN?~KHpq975CO?RSfDru`3B?z zP#Y9fuY+hvo&?3)dhpmMDBXhkRUlV@>_PYvR7!$c@E~_V$`(*51M(ZFl@BThL1hZa z9#E))MmRvGBGLv(Kd2T}V1TIy`K1h8r-0g{pfClw4&*aX?8Eq=5)0&qRA|`-=}$rI z1LXx!t^$Rd3j=6e3=~qJkOZwqf|R`8&@ow1O9RsCK*SNK+=P`KDbN{NNIC$894Jgd zy%<?c1#$;yj05C4kV{~02e}ATGJ!$|mS;if6jXD8 z^niK{5PyTxI;>O#+ zr4CAOAoqbnBo*8b0{H?owgO3+AoU=BfZ`40{`CwQ;F+Ra@W?>`cpd~a5&$s+(w2h6 z7|4yFI0e-Upm;{CodmVZQW>hCZ6H|aLi!^`(A86jxB%rOQ0ocQQUJA1av3VYIw5ss zF$1Y#2TDij(6%rroIr61ayO`q0M%Wfng$exm~~n{xYP!XL4raBl;5h5!WfjwAnk2X ztU*FNp8@1bP>6y;0+hNzZB$Uo0i`-n-wl+KK;a6}4aq+s{fN{6awDi*2B`prBc!$l zg)V5s0F=Wac7a+~pu7xedm!wCgfB!5C`W>92E_>|{ei~WK`9oIQXruLasepCg34(S z8??R+)ISHg9Og1mssQmp=?|m^6n=>O0_vxMN?_3VD#(qX^Z{zsfP4W8VUUj?YfnIF z8#Imws*gdUAUA?aS%_Jnx*e1QK(PVx6G#-+szxnsL1_dOl8_Vu3Lj8i2r>zj%RzM` z#2p})flLIo86hTubU{iuP?&^)*RrL8(-=q>NH?h51@-wsErNMq8Bp69!~>NIpfE#K z0pfwmX;9e!D#>-gGYha4Zy@yu8PFO$PUmfHdkQk`s0=WQ$LG53V-#{%7kjp?W3IvZfRe?)In2TWX3yF15 zX@wYN2Dw*(0aX_)#y~j+6g!|jHy|@WWqdwEIs>R&$L-4X;MF9M{tC!HpmG~j{(#aD zEJi`T1*H{8egNfTkpDmBZe19D0L zg*3=S5DjuiF2f?QE3wx^AbUaKmdKC>t|uU6A*6l+g$$^?1%)#xL_qNc8sWe;lMM-f zP{=@ZfJzpS%Rr?!sILTa4XjN9$%`Pjfmo3IhN#UzBRHV;ASg~ip$#) z_&TUgbpnqh=YjJHs6Ph^15mjY$pFdgAax)Yg4Sn))}@2inuFGSgIZR|BSMIBALLGu zzd^AHD%C-?3@G$L?Q2A90^~c;916$`(1<0Z^$ZI=kefkmB#;>(+aan!p_2z5IRK?W zkWWBq0FpjHbCIBS5J(214n%@{3Q_}#8IYSG{T@)rfks0h`2!R-pcDbB0U`MVRzd9)P>urS5J=w()B*yf4^W(g{0ORF5o1y7!Q}|dZb%ymv?>JDivZOUc^ud4k?7&Am%_?o1i{5DE1(;P_TX_sB{2@KBTMwwV*(09@N(Y)li^#1;rUC ztRZfPg&HW{G8tg;11jl3>&8L(2-Z6RsRa246ow%Gg3JK91Y`%Oyg@b_)Ki6|P!J2` z7ElbWhxUU&szH5Th>58694G~Vd~jU}Vu5N-h$}#82P6V2abYDGWQ-YP0wfk8 zB`m1a289b~4-lomia0#d_)df6b~K=MDxWgvfn(jg>|g63aAeL2urFLFwSq-NB# z3aYt5ZUUubNLdOBUF0-_+(JNP1glbUvhrI3`6f2;d21!LAJs=TKIDqtl z(ieyZ)pek_1^ECJ5|D6)q)(8WKs`rLID_UQKS&$0I3@|9hA?;vL?E`8{g3>soi1|o?1cv1ohHEX%6JS6!1J(5O@V8s8tAR0YG9ERJtNY zH6bM@BHe?^P0$!2sEmQQ97KX@k7Dqc3@BxS+Df2Q0@4LygK`~c76GIZQO2T%EXaMJ z)-t3w4@!%WQw>1*F&}KQ3j=7k1t{(MgIhA78Er@|1NjwF$3uJoX=Q`*3UWFDr4f+d zKA!!nlvOq3@ z@Dss%utB{ONa#c4LAe!{%0RgeHgf{2;UTIZ;S2H!C>O#)9W?3<8CL?WuAKqiLjejK zn0z9*M+&MbL17DW1tP`6>LG}FP>xCkw|qgR2*gh08Bb8X1`F|gh79N`Ezr&$(EJ=I zErVo1E6+eZ8ju@7DHP&&NZ5jM45ZBrOE-|(5z^*>)C(Zjfy(b?;B`tM-y%!~?WYFC z7%0Xexd!5LP%itB6h5f!Lx{^k zsSeVjfb_0G>Ogwa89<|sps)kQBS;j|(uJr6wSYi9Hb^W4Lr*t>n1JvFNC&Jn1Mzn; zcpV3*9EF4?#GjzmLZI>;Cn#w1+`0LQq}+#WTn?pqK;2Ehrykg7?!x+B+bZ zfI`Bb0WzWmORFH4LrN`BD-Gml5C+xKptcsMB@YP!Q27pN`9Z=DSwASgL7@P02WXB4 z6dxdSBbrOa-|Fw2}o&b!YTF{Cn(0X=IjzEL}C~QD(1l7DC z+d*SDAl;zwLM;nGxe8=9$lOHmie6A|1NE#x^#G_QhlB&jA0V}$dIe-6s3ro13@A^4 zYH671AaRhbpb!D&B}n{%+Mkfr3$h24!a*qqBnm3OioxwNSegWlTY^TELAHTh21*y8 zI0S_|sQv)C1k@e|t&sw?>JYQ5kn#kSn<}Awd{7Gv#0TLNa0w0SKY=jJT_AUWLIvb* zkc}W3lyV@Y5~ytnn!U%B%OSc!wt(UkWG<}L3~3jG(lR8CgIoZraY3~MD5Zd04zdfB zmqD!&SX&C>2T&@2lmVdB1=0tiLAHTH1Cnk)u>dOFAiY13{h%@cR2m{?U_p5uHp2{w zEy&1w3AptP>UDxz;h+$O$bwpP5W7KP0kR9!<^|O|ptK7zA2corDS<&T2XX_XRt4D) zs;MF7Kyn~x?g=y+4T@*bxosq;I<^F7YRxipf&?&oDO6ssP73f2jmV=>mQPa^1-7;A>chBpt>6p>!7j$ z6eFPcf`~?f*F}TE6;$&=#6UZFK;3M_d0Z9AuS|?3Q#Tpm3$ymVI>P_#T_WsAj&F`J3;GYL8%i| z-hg}ys?9<94dOoZvPQHLakXG?2Ik zrALsxpxv7wHYjcpqr9LN2&n!Ajn#nkgWM0wxu6gR2i=1#sD5M1??gPwW2^}2Pm&V#-%`GE+E%~ zQUt^%P@M!SZ6I|z#B@md0)+_3Paqe9(g>&&0QuJ)d>Ry}E(N(4GRhCCw?OSGP+TL@ zGbl%cOn{AvgGye|dTNjxB`?;L176xuLcyhkX!`{ z7nmPFr5?mJpxg~Atw1RrRX50fkjbFb3o6M$@db(#SjmL!R#1)wnTN=gpjDb67b9|} zH+Z)qq9%isWu**|mCKND0i_F&OF(^MP+A4K0TNoEkbtETg#SQeKcIFLC^drIhaAhG z5CEx!;*D!iyWtrcmS0Epiu`|)L#dMG9>I^CW6ujXf6QM)_|0Fh}Z$8V@T+MVgr<_ zK)OJ&1BwHXTR{E?h%@t|@6lpa836sSG|=>TC+2!cu_kn2G9fyy}0csMAe zQc!w&pz@@eUJ}eB^4;lA^8L}s{smmkSjnT z4>A*!XF#a}q$3}^IshaKG7r>t1(gGk5+9VRKy@i7Rv|6`wbwxD7*rmC!Ut3qfzD|F znFwl6fcEEsY9vJbf$T<(bx7U=AD z&}wUtt3YuL$~A}<0LZT9A7|JuOg)1JR9b)i1~dNInL| zB`E!Z^n+3_gbj)#RM&#S1hj()5}T0F1GPjzp$tk3pb{1m29U4^#U98H>uaXcQc@Dgx9F0W~}+T6qLXMG&`uLJ<_YkWv`rM$niCDCa_QG{{|`_5>(Y zKFR%D12t z0!dkr6ohOi$ZSyB0AWzB2IYHDTMpC%1LYpb3_7-0goGr-taOGo;MN!@UxRD{^$0<} zL(UnXQWn(b0hK|Bb{?p`2`a-N^C6%Tz6d-D3#zL@DnTppA+ZfASwQ6nDE2_10n!C( zbwEZ1K{)|rBh2?8)gV8CLI_kxf%+A&atab#Am2iAV;*>n4U~gH@d^;a9NL&j=AxZ>_E@YkPv~4 zIf3E^6sDlrL)6#@`5EGBP+0*g`#|LY8UWb+cpj-nfGaw-a8hrJf8b(+tR z1780N>W?Hcfbt5cwgZ_1N(0C>BE;t)8Bn?g92wOf*PWTRye4Q2igk+sq;ZD1(^eKDaa?F)fu2xAE*rkTJ;agVW8RvYs04z9FsP*k$_pSFklCOX z5GX}KQYa#viotow5xiFvRN_J62~@g)@&P2AKrR6BAT2ylx&!$el#W3yT3AX0#S=&$ zBv)5~%|)$mA@w;Z93U|bYVm_i0Es}%2gL_yoC;(U=qyIi=nkk}fv5t7G$glzbb!i7 zkS>txP}3r=x)fCv%q~y}f#L-czMzzZ=n;U@1SpSyTnY-sRp8x#IpEeFA~%3?Gpv7) zSrS6ZXi%(y!UVac1d4BvsUX`yW`Oz}ppXT{0Vu3M?Imn(hRn}FTnfq=p!y6FLXZ*@ zR2PA2LQp*gDKA0c1c^&fegwrJsOJMJ$3QhOtc3?^okHqDP?-Q}4S`yRpxz89m%vIw zP>6s^8Bl2hDm5V~5#kF-sDe^3sP4~W0J#lTB7)97hsZ-}a!|;_Tn4fOahM?S31dVG@YA*oqV+Oen)DwjG1T-cGsv$vP18N^YYC2F@ zfNDF0ZIBcTDsw<$pfVfc3Xlk-W(VbEP@f3mGDy6Fbb)*ci4{;A0#qA*L{$n_u$N_QYPfY_j%2TB{DoD3RgL*xTcI6`6>l&(PTg~chT zl!wexfXaMO41!z*8IK0#P>@cL&7gJ{X#NY-)&Y$UKvaU}hCye*K0ZKg}lOS#dr3grl0gVSi)&haz3t}cD3?OL;(t-q)C7`wmXrvqD zH&ASWM(07P1Qb5lTaBo32`WoLr3Oeps0{%M9Z*_^h=Iy>P?`kQwxBQu$*hOgK#+bd zsBQ$s8EACbAAC|JC^dn64DuJqMWDC`o(Y zjuT{mC8#}tIDZ*ZGeX+ukn{t}BcNIblma2LklF*(9s|V_$Pb{nL*x~ZTZ_T>A%MZ>Lb1(y_&y3tx2aULbVg!`#VEf`h>sUa28T9rxsKo+mnJt3uvIFH# zP%Z+MWS}?%*$3)9fZ8P>{~~%ypppla#zC{-kdT772Q<=%SOWqJS&;idF#zL(cB?|} zcL3Q2G93{nptKGOHCX9_hz~^nvjBVoA*k*Fg&`<>L1QtXGy}>ppzwpOYfFW$sX_NS zBsGD20GfvY`4d#1gYq4Ivtjl@deES>2U+_M>N9{!A<(QCqy_+`I#_su!VaVslr}-- zAtdcVT6K_FR*)V9Vl?4Zvb!)joBAq+}I$g4ylc7XcYkdzL} z(V$&KpfVLS(gN};$bX=`16qp$>feD;MH!Rbcvj6LApUPi>U@w zih%MvDD*)g0tx|8nuU~0AbqGc5@MDQIWL038l)4o+3*a0E!t@%WAXWhb zD6~Q89K-{KE67yH$|-~hB(;NflYw#ps2vJQy&%6sbb#CnaW^P0fYK`HOfpbNf?@`A zf)>aRpfn3=8-d&oYPrGE8^qlppMmljs6GdUB*-L)E>JB7$}yn!DM$tq#-K79w1N(z z9^`+>>Sa)x!t8sXrb1B70hzT0*$DD6sC)zElO%92ALLR{%?R=#C@+K3GDr;MM~Lk( zH-q97w6-523(Gah3^N!YBgCK)7|038AeV!34kS)Nu0&5$klsuH1E`LI^sMs1cZq@e zm7w?n<#1352XPTd1k{2Cg*`+RKMl0I3AI6c!?&c`3;198hTo8dpMY zlY&}|kdZlvEU5kll}?~u4XEY-nF@-35F1q6!csR#AE-nHnFmT6pzwsu*MM5VpjINJ z#zzlhkh!3^1NjnEc7tLCIPH>_A}yDhEO3F{pKd+5!gY0F}+4bO)(pKyC!3 zUQiDZVk;=TK`sWREYMg3D2_n27AST=sSLDp4HiD2x))OGf^t2mjEA({K|TV_`hi;I zAag(|6y!Qk*#oi>AXzB&!S4dpIMnFL+57Z6-xg11;bc0$Ypd19s z4WPJ&q$^M^gRPZg$yXP5p5Jung#h5M=v?1(hhE&;^ARXv7BOV#wGlq-O&PQ{oTyU=!lrliIE~qsG@+opj3@M`!tLGr4Bkr`5 z2t5TK5|%j(Gr(tggZu#!!ImdMwG*h^1f_XUY=hK-QUIt;2TEBGKZ5)P5{0A*Sj`9v zIfySoX%`|7N^2mqAh`upmx4?IwbWtd1t?8{*dP_4wj!wHhoxDV8K`6IuvLPPT!G6s zsHp@J1EBN(%Cn#}3`>QedI{9K0JQ-?X#zA`1*%a%?gXu=0_9*xIR)y^fW{F(=@g^` zRKI{?8k8=P&tC%h7cwRYN-q#|ATe&npa3q$l)TV-z{~%FNc>!wg zfaW4VHK_uFBXqA2DAhq~X;3)>YO8>32H6No$Dp=9$Y&rnDE>fcHyM1t39PpdDv?2E zz+x0K!wCP{3V| zfOgb?d<0qJ0SXOJDFxC4iV;xi0=W&u28A*xmO*^fx)9_VP>u(UN`XQGR04oP6O^J0 z7(jh7P!0#B2w0eb%mbCLAbaA_b`pVFU#RI8)FJ?x1@kLN7bsVNQY9>>fI<-zJD|J= z3OVF73gN>_V^A9#GL`}gBao{==?T=X0GW#j0S0hcfoR=>&dma)WzdWk$YzjgQ0hZY zp%52bs!{eA$b?n zb^z6sptcgog`iRcysLd!#_6A|#!IasnuQLBbYf2eMyLV+0Y#ppp^PW(4gS0qq9@`395+ zK=a42v<7Mqp{qlL1$u83WCAD^gJ@7257asZu|X{tQ27W7JxCb=@-IjwsDuK!0TjQm za01y4DhVMm2dT?J`2jgyKum?00E$ykPZyN;LFz!M0HhyzZVzT3q$LT`4a@xy)u6M1 zK>h;FZGgf65=x*_7nD*Ur3c7tP}?1J@&qVNLinK21BE@r1c*C8F^z~{5D(&O(7X!r z2^^pipepdm<)D-bD#K7!fWjA)o*;Pv)TaiGS3z6^%8jV?J0yHSB_gPm4=U9_eLhe- z9b`IalpEAmRUk)+~1gf7Iz#|ICbuuL9fb0N`d4R^< zL2Qr>$f`hXMUY9LZ~~QR>!G)|fYJcSHc)y4*#_c+QV^(y0fi>0ZUBjc_@Gb+Ma4AKYcVS!Q(B-MfBK&3J$MnP&ID& zf${@(6F_+z6yKl_0qKFI7*J^m3KxWrKs-<>09l0sDGQKmO6;uwSlEGDqM#NVD2Ic} z8Ay!+3QLerL1i^0XQGA~$la)E3sgdb(jW3{6=qnYnu5v$`7#eWt^!H{h_nYvJ)k}X zNCo=IWsr~u*$&DXMc}y~NUTE2WKfw2YAb=_6;xBe>Q7Lc26ToMNG+u91eGVCas=cz zQ0#!h6x8DcrDIU32n!j|SS6^p3K4_lH&~tkjn{!(4+>9ET@T5nAXk9uL{LbBWFfH& zYAu6Ukd=ps9yG{gkPARQ0JZTz;R<4d(gUb|2k}8U4mRQkDq}%yHPG4@kiS4{dO@iI zWE03opf(KTj2_VF6UbGdzJ3n4j|1{QWKS801uE4br3+|v1gQT4nu`M21&T|^84V!} z{tTdf0thoeI#6{(@*}DWSc(Jr0u)z}m;=QNsN@BO45aKp4PQ{n0BZk$>;RQgptcmq z|B!MK6hEL946wEssQ-xY4RX&P6waW#{Xnq{N=Kl6FNh6F;h^#p6keb>gVb`cmL8;^ z1@Z@IJ^|DQ1GN-EDIcUBQLBPt43v97q7Z+%f$vuVl_8+m1=S{?eX+3G0@Mt)u zHHZ(<3+i)$!Ut5Yfouo438D^`xD;a$^-OT29yRt zEAAk@H&AYOh3--SrJPmZ5k^oxg|y^AszI$a(8-F3vZjKo@&j^K9C|7Q#QYrn9ogmkO)Isb5`5!f3LfV5M5@aeUFGErlq7DGL3giY*Y=S}sl8Qho zLFR(;6sY6`#XiUnptu6r2`Xzq{cFfbC#a_iOXaJ;H{*iR1SpIksT7pgL46#M382tN zPnVD$BgigLjzk`X0=Wv*q5`=P6k4EG0tkcBJ?IQ@P|U(w#W0_M#2~H#m6V`%9i(*w z@j0k;1Jz8BRsa@dEM_E9RD2qTUVPz4>2cR?u%7LH|2E{uh1RyL>djaHP z&^o+42H383kQ~SaNS+0qb_%i))Gq_M5EQ2%mwEWgHi=3Wt-e#23h?;Xy(O zW(O$Og34l0eg%amsGJ6+2vF+)R62lcK@S5^EeSfa2{bRV zNa+Jxoeqj!P(L4(H$h_ppqK}Vfzl5ow}VnPtQG^6Mj-tllRk3qm0?9jGPV0ub z3skCrLK_yFAblYH2={`*2;?r1j}RjUAQM5k88X5EDhnWeXOK#W|3Eb)$gQCC36ck; zU{F|s_#jot=>pWQ0l5TJYrtFv8l#7~9aJ8J!Ua-0gZv6PK^0O@gHCA$wF5vi1E9D7 z`4o~8A#Mh#2c;^IDWDM)NGk&rmLNBRdNz>K7nF)2c??mOfK-6|3W+PwI3DaQeT4f# zzJY}y%=PH;1xf>;QU}yW2ZaDA?Lg+qKqUz%)FGu1Xbl8N9jI>!3IR||gIes6ybL<8 z5K`A;wlE zbz<00q=4lcP#A$)@{n)TKKp_DtH9)eUx)Ic}2Bj8I*$45%dInIh2-J^&j4puA z`oJ79fyqH~J)|WA>pg<(frJUDyoJ;-kn#wWav-6AT6%#_e*pOyRNjC>3>2@RRuw3m zKz;z#TOe11@&IV00>lTE1=!pL(hDhVG8sUpz=PC5Qaq^L0E%UhzhR{hXoWbaCk-+g z6vLqYDyXdnYNdeA(FXMwK>bXJe?a97NF^vALPkwMK7zyysNPA0o>&R;Bg7_H4FU=i zn5#j#WEuDdMNsI2Vi~jt5n&Rj6au9TkWb>kXCZ^^1ceKz-vP?epgI?1BBZ1Sxi5|Z zc~l%x>Vf2tWk59vWDOO_)sVVuJp<_GKv2p6)e<1PKw}D^G6@uSpn3u1Hjtk{xdM__ zLGcIj4=7eZvWO8XQ0Ri%OrZ51pt2QoN&;xlEl4G(KMiVMfcAcXayTf5f$Rg(pp^)a zbOhpoQUhdF5-5*?>;$DsP)P++4@xH>w}93pL)zsa*CX5m3OSHZA#MSMFvxaLng`_` zP%a1g4AhGS#V@EN0+oZHTnf^O2t803gW3|XngAq=FEl~&sJbDgE~pI#3R_UTf!qz6 zGXtF*0x3&Cwt(UQ)a!+{WOnmPPzZoVYe4x4BnOEVP>BjES3q$F zN&B$Sh1516S3vRss1$~b4xx^+J*&yAJ{F4t~`-cbJx*;4>{kkSjrH3{?ApY9VBkK`sZ4Ab@fmD8+(m zwnfl8L?N{wYPf(x6I5=2;vJF>KD77O$^clOADa12+Cccut9_vs0@LW0wC9bT2_$K50Z*N zVFD_xK=y*_0gy^iJ_Cg@sNIauHz4((@B_sgD6Bx?2C6?m@d4`hg7PkoJQI!T z0#LlbT1AlX1eGnIyaGvMAbC*g1H}T!ry$>h+5_p}aVAh&1jQ~SFM!+*s&El(#{yDPaKR7)U%p>T*cC z8030TjR0!X&I9l41epW!7i8rxXx9d)&jRu@$ZfE)4wQO8z5tbzkn>_ea{!=J1j=Wi zbP0-Cko#cngp|Ud77fTvAag-y?89Ob6mp=l6;$Se@--p{&jXKcLvl8#MFOhxK=~IW z0=YoPTUus#zg zR6xBOP0r?QJ#uLN?xe8RbgVHakg$*(lavCqF^ahEdrW{b{ zgGw++oPkV$i9kl3Kq=Ut0d$Hmvdcg*0kQ>T8z_IFmM);R9>}c(P>zO#Bq&cqS~-x= zgoPiZoJVd$fKnJJjzFy$L_UPf&p`H4fyRYksRUF9gVH@{KMBak&fxRfP)A{5^GlGt z0jcp|_Jdr5oUcJ`6p$Yw?gFJQkWHYGeNfp2vH`LJ7vy44{sPH@;u}&LfLsEq`#>Wm zpp*mZDS`Slknu#&SU5;6sH6a;SI}4|D4&4Z*dUWJTM3}m4 z7CNBN0F|I1IgrahIRli7L8SpGc0sm)M!P_%7}g#D)n=g70ovUJ+CQ1e02<*1jenu8 z_5-C!OcBs3C6I1V`T@lniU*ZhpwS5i@NEF2W+1{to1u=ukfDwNR3<@YH$iC!)awPE%M04= z3>wP-#TY2vK*qg6B_JsLLHCzq*9$5OLcwJ=XeJd>{zB3mC{{qR3u_sJ(lw~11WI|J zc!8xTNJ|Q2H)!=2sGI}sdH|&Y*h!YKaSBK}0p(9niU;L7&{_~seFJLAfl@Bahbl;I z1d#hdtw&I60Hg+_59C8ost1)Mso<6^tULtODIis#(z^(Jo;t{fP7E-2p}HKDt3fUW zmB^s{3JOIulM8Bjk2l(RtnHPAQ-WDWyVZoqD}LWDIakAqwZs*6E&1;~G(@(Vvm5s?|aD5Xje%QVtaApx6M7Fv52Af@&a8-UZc)pxO%-av=A?@^L=64+yF;K%ox` zNl?iI3Kz&+DaZ_vUqB=6pf&_3)IctPv}Zu>1gQnZ1xOV{FUT~I3?#Nd;vhdkOoF%( zq#l%pK`sHA1{#4tPDh~96QmEM2BHt-b_fro2P6kds~}&4+8-bpkeMKHP-ubFfLbyj zUxG%iK)OL82yzD~-+<~G$aoQm1xlBYRyHUlfYKVsr}^M??g+k*4b(P+#XZO_Q2GJI z3}_4$WG1L~V_@)PK(u*5X@P;k5qzUBq&)-^0gcmu+=CugkX8X`cg73`(5-!-ysf}s z4n6xE6pxS?2e|`O4}$y&atCNk0+iZ8Iw3t~P`HC!2XZNK`jBljLb4GtZUxFmpf)k6 zel23aZ5M7GAq-&*khlQF49Hy|H-gG0P^=k4Z3X!VBo1oTf#xznp#WJ=2+A9vwhW{% z0E=Nzz6R+B#R90s6vn{70CFp6%oa301adRTJcv9f+(0!usC)#)0VsrEHln)^`k^s~v1C{rnlmW>_pwVs6oG_>c2c3WinneMXsznT|z~^#9 zN(OXWdfO45X_(t1BU^d9DK@6_on{q-JTp9crLKqwwg1~bA44{z+PX^GvMg9za44w>5 z3_c9544}~`(EbO|35K9r0F+8Wy>*bypc)nw`XDwa-Gasls=zfL$j_ja2cXaf)odU) zg7O8ZRRao7P-=#hxAsTh)*L1u%_{RQ==K{`-XgUUlty9w6ff~@BS^<6+=1ZsDK z^g_RDJT134T(=^j+dgZvHB>kQr9333m}K2RwFs+AzS(jcOc zdIQuu0p%i)uMpvcKQ=(?13>j5By2z?fb4|LC_~%@D|0~M19BCp#RG~-M+Q)z2$VNK zu7I5F1Um5s6f(#@g|IW_ViJ^UK(>NrKtMZ-Ks6jFe}hZ~mF=K>2Feqlum#x) zqA|nR4ZH#ll3PJxirWTcl_2v#WdbOzfO3E%11$Z5WU$+W?o&t{f!3mc`t*<*2$XU` z!+zk$Z{K)WdBF%&Z}Fn~f9WLGi+gzpHxF#^=O%VPkw zmmqEc?S}!y6QuM2rFzhqPYweprGiq+Drij#>Ro}_tDw^yK_iHuvJsRsLFXwZGk{v^ zpq2&5UQoIPu|Z?*ko4ur0J_hOfdQLL0D})SR6zTPKqWe8M+s;Y02Gt3QWrAH2&#WU z<*pTj6@v>y5<@xz=maKET>&cZKq(Z`TL*;)sLu=0`;G7Xf!Ag%@NK?Ie>piv?QhIED`22`Jb(jur=464gO;Rib9D+hd|CL|Ss zQWj{w8DtvBJqUM$>;SD;1@+!RDIHXzxr0y81^FJmHw7w>KxqsTMld}L49Ge_c?VS9 zLdqymzZH~oK&3QnOcoM4pph3)+bHYZ z0w@K6{0-{;KvD%rHz>Y9<5&n+fXZEvJ=oQPLJL&df^ruqq(F5csBQ!G#6hEdpqkLPoGac0$ICK`wxp0dgJW)O1kn!(t3n zdxFv&sOq-xF~3>3FHcp zK2T``8+!qn0V_j6eubn!kV=pXKx0fG6_79ir4G=D45+UKTB8dK0d)p-22lA5O4lG) zg7}ab3sCw7nFvaCpp(BK;fq|ufNG8k22d&oH#G$?<9#(6;L2jUh`iUEZt zs0;_$1qyXY8ynd~kXl0e-5EgZ(?K;3C^jIzMQ)$Ldup}LID&TpnavFR0dkf4oZcf`U+G=!SXtKi2=$N zsHFoekAhk&pq>w?&IP##l!8Iw1d36Rzd^Qvaw%wqF341fZ6H~YO3=7FD6N6gD`-3$ z6rZ3v3&e-G3Dk!Gg(1icP`rcMBan3qpf~~L3Q%}~dOM)>4(h9dQZq;n6e6IQ2c;|I zI7QF3{amObY>B#jsm4BkSHjvgG>Uo071D06n6pOdzWGU1KAH+=?2OfpmYn0 zM^IjZw4gzwVxX7zJ^AFx6wT5i)WED#t-(11R@`aw@1C1la(JGf=pI)>wgBf}s8aq`wX8?So1a z1@LNVP>U48!)5{~{eb#Wpi&i72SQXrQa7Y60O@go%281IgRHa!rD;$ufRsKU)u0js zZOBn1Vj`RI-pt?6ppYy0ccJdRP%vM1IdHdQGx1mkQ+fc4Ag&g1+NAGr3+B4 zki@VG+)o6BF{pI}3Jp*i2DNHHc?*;qLHQiyTaf!fCV|qG8^a6+Q0fMSGsq^0JZSAU z$W5SF0);UoUXb%YC^lg2JV-4FvN@4q9dvgt#2i@2fXo1`uLHF-Ks{+tP6LHCs5Zjg zzlV&{fm%hN7BeWsKt4j;cm^t2K%oYrK_-J*7oZvn6pElx4p99IatFkHpb!DYDX6Xi ziGpf=kdHyJ1)6sN)zBdGLFa^m`WBG51H~~UC4ovLka~z-P+0>DQw;{t%p7ReA5?ey zFc^c&aF7_NtOKQN1qSSDKsUgHY6wUw1*I5JIRUB(At@519}-reULc4Catp{rkY7P_ zf^`f<40Q~kIdhP|AoU}toB_oRD1CwA98^+*`~j+gL49>lNJ7rHieqqs-rMfR;K|_3 z;K<<2;K|U>;0GSHbY)Or@L~uB&%}XbJsBYPfP?G}VF0Ztsbk1sr~|M32K87|80r|x z!E?J1TR`a$)brJ#}%)G`8%lYndn^-n>u z0E!ck9#BpJ#XH1qP)Y%r4jJc$^)o@W6)atV;sBH?V0MGr6`s!|!!7(lDC92r2TXXi6mF@W|K zfl4G$oH#RpTAP*N(hXD_gIo!!;X!2+s0IP8!3NDKAkq+|wF(-g2gN@q#eiljK|LZ+ zsSk=v(C7rH)C9$7F}U3r$6&}{z+k|jz@WkaGYJ$2pfPXIX&4aSfJ8v;P)K?!0^jWm zT0;-=bs~6%I*tLfpBmKq1NAu|ISphUC@wP@Kx0aany~=I52%L-x-Apr zdPt6~W5{NxV}OwX46Y0=4Dk%0G5|D>5zgSs;K&dFE*U_oKzSNuCd9Q66CkM^CJHKp zK&n8l0>w9IUnj`_AT}hgBDdHAA{ zUIpb4kZ(b$8C2gu!U|G5LB`NPbvh_lgX(Kg=!5(W5`(!6lp{eo8x)qHbOovdL92d1 zsRC3!fl>vC2B`4Rb$R4akfASfQOr%z1PkU3IN z9D_^%r5jMb0;L*|Z6I+-+=E(7AoD@4f`knultDE;NEb*B6v`lZQ0o{}>VZ-a$lah4 z2BZd*qCvVqD>*=A0%!+4XxtK{53~*fWCkeZfoM<&fII6v>pl|`L4*|Ie;yRdp zkUdMFx&#zUxMB|!TE)=&5I}VmsErHilY-iIm}WxC1dt0r;ReE>JOC;aK{Y&R9uO3V zGr;8+w%7yh>j1?RCh8QO$W=jkoG>Pqy>c@B!7X@87QPdr7K=Z1I@){%pI(-ME7UUC9 zo1%^(9o&}j1DAcEk{2}A2pa1Lr7KWv0gG)&j|x;1fKorGzNiDQDg&7k$Kc4|4xWDv zVsM1690IifL9?%*zN05Y5Q8&AD1#>h=#&!lvIpdI&>mk<+D!$Q=%Af!p!5K0i-X#P zAR9s91scl$`2vy-K(Pl3Jy4i~+6SQA0x|;>x{zBZK(jcYI0W@nLF4}*)u1tZPt1DOTV3F1R^gVGsjlo7N~52PBj8V3}opb=J(pFu0< zQQMZFm0F15|&3;uF*YcVlp3 z@MKV62nXMs2|D8e6i%SJ8Z;LMn%w}EQ=oM#Apb#TF(5etWC}a-BbTZVt4*7?jsR?QPK5F=$Q}lxslezJpQ= zs8zL$0d!{&$nBtba$$hP87MX&?Jt-MLl_`o2&x4@d*4AR0hC`sX%JK@fO>MU5D8)c z-HZhqy@j|9l>R~WE9e#nX9iFj1{JK47vwsSA3)^<$oHW3F)XKmYGhEW2;@J| zXksvU^#dsVgIo*>O;COdhR#BO%JOpXyd-QyUIE-A1EoFCh%_kmfJ8t!78C=Z@CBt1 zkUmgrE*ZR*1C)Y5tqqVHLGA^m0Z94=%~^m->k~PLw0J#>_egM_uDGVv# zw2mBRpmGxA1CU*yRHeY+1|Ay*gP46uzMOT~NOo5I zfKn@{4+aYP3~;!BVjGbbK`k~&eGak()IR~GebA~jP#l5EC`i5q)s3LM4)PJGMF(pC zfl3I_xB)2aKzSF`-UjtPD;Pjw1KOVm>gj>f9*7S~BOqTZFt{>+=HNg!6@%y8L1QP7 zy+9z}fXX+}=|rG19a7t+f>Rx63=T4G1d2ykj__sxt!GICmzkgv0eMsclt)12D5z8g z?Hhv38h}zBD6fL-K^F;PK!iLfJ-|vpbaBwQIB2FGz$3Z1O z$R<$8gJK>OE1*;W%9Wrp3gRx%S;e6I2#Q}&xyHZ%iY<^Fs3iyrA&?&+;Rq^`AniL) zUIDcqG#Eg66O{WwJ_L7naEAl3JBTp_*#)>_0OEE~=z-!9wD%fOic=Dvgv}-1 zb)cFDG+PeJL&Sv+ws3{G2;D4@E0EU`KxQC7Ju67Mfan6Xo!L$Pb|Y49Ip++a5Fq56Z2O zo-0HZsBD7Nvba?_f%n*gY|sVYwgd7>Is<5z56E;#Sb%&B8aD>n3)(Ra*(VQkGic@z z)S`j78B+d%$^eiUsQd@j!l2$MEI%MrKsO3Z!gAgbK`cpzwyw27zXAAvFNVe2_muX$@2ZgVcaxs{~x%Alf6K zb|fT~f$D2e>56aE6Ex}(!~iO_9l@(L{TZAXK>Y{>hRI+)Xp9up%kX7DmyZOmat6&R z`Z9plNx6bofx0j_g4dZL_R)gM29TRUHGCcesC@uRaiEa`P^^P`p`bJaYQKTv8`LU+ zw2MJ80IKOhqoW}IfMNxdqCxQrDhVK`P(fOUVGJ`EAaMW*RcCO^5fq<@5QW4v$RtpE z599_=dINLCAt z%5qSif%JGmwt`{=RG))t8&LZTqypwj&^R)v_X;rqvS8DRMok|tp`gY?4Kpnf+f{y=U7l|3LEK(#ig4FZY>$Zg)Bx)&sh zz9I?IDgup4fZAuUPy(3`ay=;KAi6+pBhVfxh&iB;bO)a)30lbjiU-h~3&a&5mqYsR z`QRBykZ(Y`LFE?2hoF)Z6b7K249Zua(hbzN1K9>r0a|AaDh)s_N6-ywpjIQOY=fy`XdpiXqSrWQdp_1E@xT z3*<6TdQ1hM6ArQ!puRC^jd>V&r8#J| zCTJZ4sFVSXdia6MCD1I6BSSEFBpos`0vgu_rFc+Z8&tl6_R@joZeU};Ak#d+b3mZg z>G;QjLFot-FCh1UN^it0Kd2WCDsez*2vn+oR;Pl-GC+KgA3^0ksD6O75kOO0J@C;)E5Bt4ib@kg{%TLVh%}%2vb0)g8H_yg5V zptu615>y#bz5~T1sFnrgDo`2*AwjFtLH2-R5N0|^ z92D-Tqf-zQKshLg!4vFyPkq&pwbt#PZKhK1ZsJ@GQiqFpmGOfiwi?2+#FC?VKW)DTLu)z zAeVsj_%MLhYJo;0L83kkpgCyJSPrNJi~{q5ak>&zYJmI!+HKGCMN(GJwWK{29W* zIS{n_2h>7zVSt4Q$oHTy1^EDy!W|hr8Qj6TJQ;i#f*C;NFUYOo3?M$puMkr~xfm1< zAXhjucrbwS1}Hy(!Utp`Xub|44;o?i1-Di~^Y_7EABTfU(5gg`SrEU4Gl1e3)T)K( z@?!93@Bq*GdoY091o9cE{taeu2HOQHSwJVJfOO$EDS*L|A(R1>I=mS|89?p}X9#BS zX7B`uGblVj=k@u6*A#%(5l;xPa58J2*5!c>)xpAeUkbRnUyOD}x6^H+b#?l&3(U z0*ZMbhDflj3gA=$iF=USA+~{1EzE9g=7QoHlpl~y2E_p=je_)fGQi>rMB`2ypnA%c z0g(zoF$J1~2DM^9VFro^P!0q2uON8<T2QmlL zXM^ZxVDJX#T2M%V@`w{Sy@PTABv*p`4RQ%cHK=p~^&R~g{25%pa}}uN8%P|K$3QUz zDhXW~K;Zx~)fb!xJQzS}3X+aM`awAnfZPOH zeFGW|0;O_i25)dm0J#npPVNjr42|Gfdr+u=`V62Q^Pp55!T^c`P!ALo53uqVq#k4g zsJsH@Wsv!<3~u1O07}9B4CpFA;y4=98{=?JtE3bYOiWIt*N4hnxiu**Sd4-~VZ;Bprf51^0=2Io{rJcDX3P-+8- zBVqtj?}2KpPzF$ZgZ7t#?17{oP&x$hK&cmG12&UDF@vrjVHPY_K>h%QEU4@Qg$u;b z=<$xtHK5pwWbg%-6`=GB3Jniv3P6Ms#1$Y@L1io`)H~p4gjSWP<;tX4IrO^au38z zP-ub5ZcqvW)p>plkX!~Thw4ML8A>I z-$BY9X9iH*1%Xo*D6TEEkRDLHAbbTXkv$o_!Lv?=b#V*tR2D!lrTpNMh2wL|Cs-Zz+F`&8zWEQCX z0n+CPE+s+f3}hl?L=3dA8C1q1w~9bz45)lTE$2aX3&?+<_y+j`)c*qA1_COd{26eY z2Fg*ewoM3lFEyw%fs_w)S>NL+$y7Em7?T+gqWdNmjQ0#$fbx^s1Y#+#0 zkUgNVhSW45o4pu7DFNa?P#8ecJR)>JX$+JuAmu(RpM%T=wRS<~fNFtm@Teuor;xPl z1zuy~25upM%6U-fi_Jfv@&^<@p!^A`Wk4w$}V(2X#l_y@J_Kt2c6u;@JtNG=BL zpa;bVu95&^sv84nejcO_6b_*D1vxJ!2%K|4?f}(PpxPMJ0|v!CsJsCA6%=M5*FfqQ ze+Ez-gL0iKxNQh(vw+4_Kp_lC#KZE=NNv9x_ zK;;p{Tu@km+C!jH8q^{LrDIV415|c{@)w8?YVU!{A&^}V^Few+Elg19?9Biw(Likt z(6~3O)eI^z$-P%9r)GJ@LPpfVohTTojJR2o2XY!G<8r#C|oLnyec0_8A} zJ3+2<1@{mj=OuvpaG+8dlx{&O9+VeAwF4-IKz%7lsDN@cNCuQ2ASnlAGjeMYl&(N+ zY*4BL<$qA6u8y{`4E&FLA4oV^$^GeP@MzHN1!|bY3pOp@t`~mN>vb-f^rHdJV2!c zC>C58K&3V)mcto9P!C)NgIozp2cUMd9(czRNIl3VQ0Rlyg)``ZQ#&Z^K&dl?A(8>6 z5)|X0*aw9P!e&r=4!g}DQBcVPGCPtX6kIzYQn?QUD2>3v7SX-|AhSVX2r?B^ zj)Ur1P>lj=ql4NrpxPZ2hL9Qxlr}&jpp|*P(2+lPaH|=V3P3Rg%4eY5h%guAYf#Aq zDl0&t3o6|~X&98>K_(;eAtF9OE<@xsSl&`#0QE3Hz5wNKkPV637oA z{h<5^O2?p3LyV<>MDUjzAlHEEXh`T`_a&_E1la+qksv7wRI0$*{4f!0Ap)@hltv+G zTY44|46REne4?x3;@)!m@-8kE~XafWDTfP4>%NoVjV4XV3feJW50gT~LX znE)EU1epO!Z=jwEXg>-lWkbKPc~m`Z}N* z8`RGA1>d>_N{gV}3QEDCwx$AuE(55Y3MzR(dkjJ2<)BgmRN6sm3y>_RUI4`_s2&Hk zz#+O|Ju6V!1f@LCDtb^E7y@o}g6cg;$>9gK8PmcPIsB8ws4=9y^N^ekZhxA=QWe2E~ zgro^j7=YXiDpw(H0hM@=7y-rWqJQ8GOMbDInFL+7F@*)RF*|^&l64)Ph0~ z)Di^MV4#r!NU8#rIUsvMApq$wxPW61qzhDrK|&T3exTF}>REvDLI?vW-XZNSP^tly z93UN_903XuP#pmpaRa4TP$>v1Q9nn_1&1hTR2bCO2KDe^=7I7s$ShE9g`{NA z8f}o9L8%Z_=7aJcq(1=C0~%8R`4&Wjay%%9g3<}7^$T(Xs6GPKOCYy^;tJwRh^s*D zERf55!F6^31BmU+&<`H{1;r9XEvQ8UO0^(gfkFfnqoDE^RCa(u7F2hEatcTsGGYx1 zYgjCRd<@DfpwbTHa!{=fDw82*gVGQrq(Sb7q%2ULhopW`DgyCfYGE@TQQ&zBP%i;g zB7=MjYN3LB7{$;C9&-WZ8c^tf!VFaEgWMMYKA990>n`B_5~%D1iG$SPuT?=~d!TrQ zxC4~8KqWjVS3z_K@4R1bkt1*lvBg)FH30xAnYBd(w^7tqKG$V5;$gZQ9uhRpgxS_GgR2dcM0vq7L# z1u7#!sTNcUfLbk}`VrKc1BElly`YgoP>&2$t3YBA)YtW9aAfcRmk^+_Es(20aRdq% zNU8*t9-y%_P%Oe?2sAbdvJI3ILA?!7=z>a8)E)vv2P_>!YIIPU2eKdJ?jZ0jwV+%I z%FT!#C#VeuDj%c3@}Rr|3R{p*Ks7ta*APCa9Y{+zav-D8+(w!0Kq^_A#h-0Lg<&AdqfQDGg(TauqCQL1BV9$^r^kP$`2N77(9+ z(gtXx92B~keL_$h3R@Y0D{Mh_!$uDvp#aLs#DzP^KcL(K2?J1{8^i;(u|RzR&p0h+ejU!V;A4K%orEbD&ZLG!+dw9O(zrVVsJ=$Xf<_-;wLHX~ zpm>1gTu_e}6z8Bg0kw@m@dYXeLB0c}B~bYWG7D6ig6smt53JM#)li<`kc71`K_Lw) zML_*rQ2KzWgqQ$oD}YphN-R*>2?`%{8IT?$vYAO<}!3t1m1y+CRfPznIWCCDs98wFBM zg8JbQcY<<1s7?gcGN5n)_02&!0G5YAt_FoF$i*OgKx#mCfKo~%gD!aN8kEZ+xf7%s zR6c_Gxu6mpG$Rqp03kso6+#tggdUPUKvs9b=k0gXU| zQaQ*4pmG_sf(tbJ3UM8%)c|UFfL08F(itS(gW4pZum$M>g*GUjK(>Iw2^4N1lR;vh z;666UU!e2`asg=m0VLxCj!O^=T@5I7Kz;+IUBt*I#6(bn z+7TcZfy&Py@O}Z%7#B#V8+@(}R2o4h@cxPoF16vv>w2B01v#D}2V2l5Tb&7d|G zq(=tIB_Q1(Sx^ZES}z1jBcRp=NCjxt3zUmM?NgBZLE#M=`3LnDLA4M>FUUMlssp7m zP`&_#0*DW4LqJr3awKToDadS)86f?TmOipPD9%CU5~z&^vMr3kpTUzM0vxg+*FeU= zLA_&8*$T>8ps{sOc!N}fLJs6_7jSzEWDcm*0JRuE&wTm-oaqzlycL!?nis}mF=klG2< zLIs5lNOcIpG6do_Q0@Zd1CT2ez#}J+o)oMm2E`#LS35F*RDxUwiW!KDKyeAm-4Gd2 z4hEILpcVwEc7}vKNIz(-8KMSM3xQk!DVafXpi~a&2|;obq>O@;Z=i4n`2-{esvkfq zK=nV!m!PpNkSqKc)WPe5L8F17(gM;`1dY@}WI=8O)xeLX? zD!o80Zx9=jr$MryS`d`NLF192@e@$42ZayFZ=f^wgTbRnkP;eF8iLX*$n~I92y0D% zdRL&h2eqd_y=PF`i3G<#vJ7Y)3WyI08&H1(lxsodB`9Tq;s<0isEh)YG$7xBY6s9r zGsq91QU_GuB1XSJ@e5J`YE^?uUQjs%YB?ctE66O645&tilnS8z?2s`ai20z<1H~|? z7Y)jdpg4xqOAvX`+G>kbY1}3(BjYo((9(Ksg39Lk@B=#AZ;f3knBNY=Y7cD93}!bWnWBcNQerMYapQkavdnmfqVyQ9f5K@Bv(a(N83Q71t8NvX&aPFK;r}; zGeCX?AfF@F2ZHp2!U&YdL7@oJ4Jvg&Vj%lL zr3-P zWiBWTK;;T(%mtQqK=BF5PoS^`TXIwgn0aP&orxH3iDSpdC0M(?IDHqz}}N3jm*7 z6arr_1jUkqSYh zx5%w;NSg&zBS2CNWPBAAmXO&gkWHX|FGK`1mjO}-N@I`|4asMqz5pntfYJm=1QdIq z@&e>?P$&g~calIvK`lMdyeX)31f@7o=z?4UntK8H7!-DpaV$_cfWin=?}7A!S`(mh z7UVKeNQ2TTsQiKCP{{lzqUQ)o^$;B%4DHa9r$C_%O4T6yKqU@{4=MpbY*0-JT73tq z+d=Xm+d;7cT2%n@Jt)V3QXOd34k*@r!L45(@Qxu+-vS~Bs-Gco4007HOh9cOQ27B$ z2O#%=astRs$S5%={ep4|s4NAA7HIqvR04oZ1GPjzV*;QO5R{fdC9e;7W*roUpcn_` zXizI3RCa+v3N$td$}6CeH_*5^DD^@s46XbS~4?$*wS}~ycaZn2z;UZ881gcv=@d~mPRNuq; zPM{bBrFc+j0ZI#?8UYkm5IZ65d&mg0FT-T$tR^V^LEHn;1uB(5?FdlZfzlD^92Q8+ z6*PAUYQuwijUX3*W_CcX0JQ@_wG^mK29-geQ4ml)1 zZlH1#l=eWW0;C4y5>W02mENG(1hqv#^%kgw2g+lh)*qylfz21eOa<8v$}f<70*Y5q zY6sODpp*%+2b2y#y+V-BKxHB%6hN+t0`IB-tzIX^e2}|9F##&mK$x(0ChU6atj9AbB4YhM@KUD8+-^3&~NSR0axhP&*Uk zGfl-eLO+aNcBNS@rJ7^HLu z~nUKy5dWouE;9cLvbxH)uBss8j>#2ki|7rA2#Om} zt^k=0D~mwoI;ivll@6ft6x1I9)y|+C4vGm78{{gG9LOCYUxLC9RI-4~28A=soe=Xu zCnAE{@u1KLTTc9`smEoWkCaAUn`NRdh1{_rCf!qozUqQBj;sBI;L2d=*R8VY#Qkf%k zJvOLM2PzpME&z>Ff_w-X{Q=dopmqI0;E{Jw>Hx`r)PYhzsMH4K2vC@U%!cF!$b1jT zg`n^Tg#;+wfKn4^W)KulAiF?r0>vJv#sjsTK<0q*Bq;ttJ_OZNpt=)OeuC71YJP+o zP}&8hCXlT#9Uw7KOoM72%$1>_6c!9lAE2=mkPV=IGAJ!U$_&uX7f{$BmlL2=4=TYx zt4$y+4N&ZZ;tZq@q#v>p4HQzyegV}EAblX0fZPsBjgU1XptcRj3{d?9iUCOIfYL8y zMF&JDq;vt<3QE18lm-fQP%Z+skwHB^Q0WNCcOajDW+y=I1H}(0EJ3*%)Yk;nDj+`0 zU!c+u0=XUJR#+_yieL0yDzH!nwdFwZ4oYz#*MZ6<(8xc?9iTn| zD4s$6ZBPjf@&{<{5|mOw0Wujhw*abBKrRF2Uyv!F5(bpAK&c93 z1IPr(yaFg)f$}IQeZzEv(ip640@)8rU!c$h$%EVm8eId019EE^22%sd2cYp# z(Ee^eXulQY3Q)<2NP8fgK`{necL2)AAPlO5Kq(be(}UXbUf{DcKq(Sdhl6r4sHOnr z$9C|(3{c$wYQcj-5;RI5#Q>_0Kq1Z~7cy!9aib}C*DWO6K)pgxsRoHZb#S`?G@cCV zoq$3GB(BZ?sMi5TNiwKfFlEFtR9q7Kw$xjM^G%_3I|ZS1-TQHt3Y`l zAC1~=&W1EBl|DIq|i3EBfe zi`W9?M@X&*wf;bBsX%T4m5-pjj7agIHWCQyf={u4)C3^cfof7n8Ud9Fp!5k60rjy! zqlBP-8Nv)uc!E+fD3(F-0;*F%Z8}hm11jG^eHPGq08r|5V}Qk#E(3CifNBrWs3FKU z&`c~Sk73V8$axghiw3zCR4#(r2%z!+RK|h)2`VokA5q41P6r>syFQ9%GL={K{sHOsqjDvg!O7*a|JIF*(jScF3fzk|U3=3oe zXv`fn+TqUtvJa#i)G7m|a?tr6pw=yD-5;ow2IVnOEd|QMpcaxV1IXu~bqki2S!jPUjNCwop289=BEW`=i+X0ozpcwK1+W^Y# zpxOk~8UdLGYGs2)PeG{xR5yXrK@bCK`UCZkL489|nE)~y;$u+#3yNn@Isw@X3TaRZ z0QG7>V#s}AP<;lfJ3;&QK>h=z8c=&09?)nPq(=a9IjBVnidB#dC`3W=4(cO; z%mkHh5I!hwK`KD$4m4%~X+eU@5KxZ?l&3*srXbrOB^yWvQuBk#Q&5Tk=?0aHkWd2c zz5=BR&}ki@(LjiwKxqS{4qJW%iG#{)P<;%t4^&!#S_`1u1sVYdwGcq@0-5JSZySPi zgGwP#J^|%>P-uZvgWC8YIY^H62Jb=ut-S$>fz(3M3@H3TWgRF7fZ_zw&jF3q!fXbW zeW3ITauukZ1}e!we0qi(sJw;P4@$p~(gczk=oxOH5*IXr1IpW&;TFc=4BnYdo3a=Z zi=b4ET<(HW9B7OiRI-6mD5O4s)UCP;=cfAGoqptJ?5(?RV~P;CXW8&t1@@*=3j1GN%B=@H}> zkS*txR_U z@5ut04XW!vH7sQQ9g=oItwE59pk4>4%mL+9P`e6L2Ze)mf?Npk7bt&#S_vRMpxOtv z_8+p+22^i@@*}9+1g)C_g*<5Y38-}q8qEOJF`)7iWG|@X0QI0iE&{m>R4;&93!qQ} z<#$N$7L9%aIZ*8g3Nuj20@$$rpqN8mCkj#xs(nEI1GyN|#sQTppqmarVF3#zNVtM*fs`_swI67N7vytD9D;fp zpc)Dk1IRTHs5bzLX^7iE9KgJKiZb^@g%#MmClg|OTMiVaY>B9|hN z5jRln1nd2P;uT~Yq>lh;4I!I?s@4m9Dg|UM7f2PTBn9OONDCQ~fQK;(4(bY8NPI!$kYzx#5}^7N6c?zgj6kUq zWD}^Z0I~^WKBznZxdjsEpq)vOG6xiE5OIhvLGxXpRwQV>5~Q?6)ZL)?1Ep0+9RN5lj@Zf)O+a016X`n?NA~QUhu&fl5e3pAIC7+(Q8QqY>Q42dzhfl*u3$ zfy!N2>kXs_)ba$S8&Llmly^WQ!;rEBA`8lOpcX2qui(J|>K7x{(t-4VQWGd9L8TdF zhc_gjgLH#REJzy-WIrf`L172V;gGp=P_D)tordVdzHSbE^Z+z63`z^wTDuT)K$(VLJpD_ zKsg-Jf`;|}KxHs!)B%#>K=L3Rptdli9t4F4BAb*zW|C&NM1p&CqQR^!XF=Gp4 z7p&b1>i>db91;eg5CWCgApI~IP+EYPk6aRiRDj|i6yu=QKd8I})x3~3lpq;Ue*siW zfm{xXQAFr~Qa>m~gK`3BoDh`LL3soeh7fZ>IRjK?fYL6=R*)#D1psSLfXoH?4`d@u z7wAMmNbH4x`$eF52E_xYUICShpn4HhPJ(<2av!Mt0rlKKV+5cO2Gt&*UIM6o1GV2l zu7iy9fkFkQ8xsDYdej-b#tPH|0?oaEa-t>p)C16HDab5PS_QcqrWTZvKsg;G2WmTm zS`8q3LH!L-Sqn;uAbC(J2hs;h8=x{Dl+Qu=8dPpVY!3$S$_A|>28Ago&q7w1Lu>+- zfuOL1)I*>i8z>Y|QzIyRL9=Y2(h4LCiYw5$yP)y{Z><5#&c~VF~gVD8GR6D`?eiBSSEBwieX$hv+37qOcSNsue&Z z$q;=Y*C1p-Ej~~g0CE>71w+(e3pq$`0+l78niUl4k>Hd4K)DoDkAl((D2IUR5YX5I zXubkc8+hF}#$UYTN{Rx_t0hLFfkpfV9gw#r)HZLrn&j8KJ~10~#~c$Zepof!zTK zDqVaTGQl?+pt~J(PYLLDAIK^Bpt1{cSFS$;oJ;0W^CFN-dzcN(JA;2eJ+16VPn|pm+y` z5y;h`ln#;u*$O%f9&*k#$Q_{D89?C*y6FRSGCjyWp!>l<{tjV)+;Rc319bBaD6fE0 z49LBpoQ9BvxC#{GAe*ttfzICs-D?E8ZwlF5h)&QAc%U2wy88rVJ2n$Qr__V)YXY6R z4$%d=#{hIsBj|nvNFK&C8FZpMD6d0K;m4E*`3ZC%9woWZb&0dyk+=%xV%26gZ~D-q!HNI)q`odF~UIxz;+ngg|B zKrL%fs}&*xici?;e$bv6kZRD(N-zUR9wduBwqYX+kopdkvOz3R`w29EiJTV5F#|N7 z3u<+N`mUh0T8`kETFOlYwR=IezAM$uqm_$6twPWVjG&T%Iw6Z}BCT8vn!9ae0FAMN zb|F&F?U?2g_5tKJ6;Ry-YF9z-c>~>d0=gLkR9`?uK{pJ|W2gY%t_`{o9n_Kq)lZ;O z7go!IYCzDfuAtj?LHQ7*8@lw(0{ ztr-lUmKvy5MBKrPS|`JF!`uxj0U$Pk+M=NQLqKx+eu$6yisa{V)?i zwka}zN@q|@0CFD+sCGlNB0;_a)$^d54Rk9qs3rut10)XeHK-m1-3$Z@3Do=7KyxXe zwlJs_4eDuudY_QiH)x~`)RzVI13={hsD%y6ld#$abn_AD7EsXL;Gmn1Aa@Oe?%V<0 za}8=+fLsG|0qpK!h#aIP0;(54>OePJK<;~jwCO-$47za=6ptX6gIZdU8<;?02nn%3 z@GZNb)(7ZjU5H5_AA{O-hH)PQK{wSvTmfo%fbO>`2DjIJ7)lvH zZ6(lMHJ}zTsP#fDAL1ro22cxEfdSNJ0@Z||xQ5*_0x}WOXMorZsVP9W$$;9r5O*TG z02J4t)<42c#EF2~G@yIlKyCo}6cmP_Fah014Dk`<&JvJ~i1rSySO>LpKy?o&HGukR zpgYuZ8FImWm@o#=EnA>gX9>6^4{FngF@SDs1Njhkdpqc6=Te3l44~8pN}ZtiE(PCF z4{D)-+JT_fSRy#jl`w#A0tVeh9mfEI1+{NMx>6ZHHyeZU0Vozh=7IWDAX6az zgvfx>3rG&sE&;XmAZ=Mt%Nt@lNFI@EK{p+N>TA%Ap`aMc1E&N~iUFlb$i1MTa9_^= zx-S^i*8-UYy0r^*0}SN8Fvz{2pqrCneZ~+5kZw>r5Y*ZRxdxOnL41g7A#QL1-?v)@ zzNI;i0Th}bUxM-sD8I}Dhgc;8C@+9+kp$h@H4oe?0hL0a*n{0(4KoMiB2do}6u%%h z$1!+7dwP)D>p}hlwMZc01PRpw29S$D{sP^PRRq3c71?Yb22c!uZgB&Z$FP0_=&mi0 zTS2`fNce)v2~fWj(WAX_1^1WGNSQXP`kLFo`84oWYedI=&9Dvv>R6{ze7-Kd%m z&Oab?L9qbR1u_RzLx4(EkS(Cl0EHIB&!AWamEoZA5m5dp2KNxwgL4DuekF)XNLm4@ z1J#iblR#w}NH?g22E_|V4W#4%-CdW@04iBQH3KLPK`{iXfkA9gO##veDGNa%3`*~i zTOJ{91Id8W3&^#gumPz7lqnz=fn-27fnpNUIsx5x z3+hvYZs-E}5;U3%^8+Z=LQ*oM!~wYj6wV;ugKqT#*#QbqNZf z2GH$pkX!`{Uyy&j89;debdxzK4dgSVgU3%mF#;;FK=+q{LJm|r#W8^DCsaE@CV>18 ziF=5@L2U|9n8C^)SnUe35tM#FdLVbmgVHNVWgLSa1IX17{h&M!^Esr%2dP=l0IJbJ zcRqnyY@n2r$dJLXhymnEP@fp&E|3kN+yF{xkh}*8UyvLqhk@c8ltMru4jMfN#TMjd zX;A3IF*q_*GC*#lgOrb<44^yr+11OF_WeG?G)QbU`gGlor(?D?wYiU9J0m{FiSt5{+K%)|k;QP}+GpeA`G0-?L zX!ZuQq7<|e4q_{${RU|Vf!qSJ0~BwN8V}TpN(I*yApd|;FQ|PE%1W-@@(B1$(g;^AjiqA<`4b zMi&N9oPu&1$Uh+0g6=#AnGa%v`~VuO0L>18RD*8i1f|+c@O`78k_8lBAUA;6pp*#e zSAg1rAQM2TFA-eJf!ZgiVm{A$Uaco0);iGCIZY0?Nx8iGmCeW{=|%%D|$pqT{hY*6??>;>&I zfv}7iU@5{7&H}Y*KSm@e{It`*o^&`cD>4$|}*AnAtm5b^a;L9qyG%Yt$;sJsQmE-17? zJqplB9i*lMg%oH`0y0VpTCV^)V-PY|2^!A?%{hZs5`kv=K|7p4YdRpRV08ncbqO-L zfMFRp43T{Xx(6N-2ar?%as{O10_gy?v_Rz>s5Ax1z}je_)&!^~L8MMd`_F>`M1#`l zGH^=>)XD^naDmoIfYKMpWKj7CD%*0w{TL7r)ItU2Uzn*NU7-3Dlo~-bHl$R8$b#|& zsNDt81G#}0RC^SG^8>^updJsXR0Ej}Q44ByLBbj2KG57gh{o)J`fG+Pe5z~ zl`SB5nlOOMbP`g)kSwT=1{#5blunRb2#Ir04-7Qk05S)ZFG2nU zuHyJs`6{=7QV;5(C+VI${lp2~d3pN;jan zp(+MM%>)WB*jzoRJ_hv~Abnp@eFaK!3JeJIV7UXddK0uF0+Mr);{a54g2Eh>Vj$%= z$kr+bkVzmGDBM6H4N`*~FQ9T8loCL;f>K=`!!mGB5mM_x>OcqyN`s(M6I7}~LJ(Ba zg5m{aM+yUIR2mlQpqdI)W`cZa%>dC4s*~~=Kq(aw3ZOOssQ(4h3kpeC?m~nJC)}mw?hR zYRrRlf$|h6rXglS@)Icb(NhF0r9@1#%6jWB}!BkUB^l;?75)x+I4Ilxp)BKy@xCuYzhgh(2@|f!bA|9y`c9 zPzkNT0IKUiH4Z3@C^ZXIW0G$cs80ba6CmjYlr}&+B0;qUtlW14w?06jhw4+%m^8>o zpjZR>5;isq3VTQyhsZsM_=lJb@;j(x1@)gm`6UrNMh?rH2-UD$04tSXaSutE*m5DH zyaUymkk&uKUoh8$$}LdJ1C^(c5*^e^2bE!{eqwD@A1`QpgavK zZ9qL~kQ+dDfNTZLr9s3%;R>1=0F@*l6`D+(6|F=E)bO4L46ufSqI9!=q+kcsR6ND z0lZQL)PDq(*Ps#(;`%)BN)%9u0-4Q%#0RJr2lZExOCr!Z98mcQ8fOKiGuWyl(5%8T zaL)u({(wRNQUEg=1=3~AsoHBj3LWFI82DlmZZ1t^?A`4QoMP#*|X zRzO4`^%KbdpjZUekf2dAP>BG_MW7Y?pc(@-dH{+iP`w9PRRd{DK<3Utssk86>9B|a zR8QuCd;XxZ2GVwcwBA7F1gN$H)eR7{LE}MH;IbbS53n&?(2P1LC8dF54&+)$ixQ*{ zl)^!|5axDB$V0}*ATEN9?154$C@etwiFE}i<$&T0l)_>80v48#5GnxASb<^-l0tB~ zg_yhy>Ln4Q4$_B0O$#8`KxWWjc?(oqgW5QVmJBFmfY!D`$_Xd%s;FcJ(Aq{&x`6l( zRBwRd1UZ+1;t7%dAh81RF{so4l?jkA0r?#ik04t?DI1i^L1il>#L??-P-_=t3#h$S z1fGe6%|L+6M%Wj?0P+ba<{;?@y)=O3c~C9|r5}*%L1iB(L~#pddLCWdN1kpz$G4-ybwq04hg8xd&zvV$>Uw3P7bnDYzB^#Vn`| z3Q`R!OF^NFNKvp}38+Lsv_C-O9iR{emCm4E9B4)!q!-kR2E_u%H6VY3(gKq8=YMyM=u1J722#`!_15K=OMQUb^?pb&$M@}j!}RFZ&P z2`a-}7@QbD`#~YLf%@s7vLCe48q}Uk0hel+uJ&gDiGkbAb}#akt~MF*PehJ_MnwFO8AXk}AAxQr}8>Su#Stsu5} zgL_)(3?SdZ;sBCMKimDAUi?n6jBQiQ=&q~qEJgzNL~en6(}Wu zVlfR|)+16sC@n$iRFGRh@q{QnL8%B-Qi1XzD3>XKYjV(t0H~e^iGp$#hz8|SP@N3Y z0coXx`~iwjND9aUmyIAXkPQ$OkWvM-?gLT=fLsWY1<{ZeKWOz5sD%a@jfaFJ$bXR1 zI}Mz-VPOW!gP;}&D2>5#3n<5cLIBif2BkHS{h)FjRMtU498@O4@s0F`H;d;!W^ zpwbF7<_RhpK&3IrhoF8xXzc^Y43Iid3lEh3LE#9?10Xkm>;laafzl{Q9+c}r=7363 zkR6~nhJ_xeZ3GDoNNxa`1zNY9ij)ID`2)0OA7nPf)eshFoE0?A07{>r(1V37XygYJ zHn3U=W*01`KrI^3T53@2!AdAl`370Gn$d zbu=ii%>%E51H}=@SD@Y#Xzf@jgD!&~18D73Cb&cd<#A9BhL{Di3uG^5jDXT1WcEG< zDRqI?PJ-MBD%U`{02JR4lR%{tB8(9AqYtg`m4&yJ96C(Dt{qkClGfcT4s>|j`AlHN9 zC=Q%nA#GexngOLUP?-vW)pp{af@*d=SPNP`rZ7faFbx&p>=wIR?@R%19pmrXJ2Gwk!S^yM>AbCjZ8)h;noq%#Gq>eCXKD)6W_a=8OCALL79mx0>cpt1&3+kjjG$y1=7Pb$(L z1(>Ojm7tIjWKb-Fbbx#f8Z7{g{(#0=VWEq>TL3g}4l2z+ZFW#k1lOn)Xx}ENegXLi zRCa^PSdbWK{U2ywIPP&Q(0mza2MDO-2kk%xwJ1TmFCnIYW*D)DD1LpQJ~X7?@5li1 z7pScPid#r|24aEIBFIOmEgnz^gYpun9tX8EKzRvN0)fOq=>wDsAY~Lt1QPp@v;s+G zpfVD)4gh2)D5c^~5%}GY=-Y$Va-imHP|kp~u0gGCP)vf-9w_EPr39o50i_s-Um0zA9kfyjA_8$QXdNyn{y}~OxfQgM2;?)6$)H{*B!ob1evpqqp$N*KpdK-(On`(0 zs1yP9NI-L>pzr~e8lbWV6pkQOkh} z5(88wfXV<+84Yq3s3i(=Er<=O5kRg6*#L0|NEFl#1hvaRA|SVeN_NnSDo~t&ax?mz zJ!td|6xSfVkX#OG^?||>6dIrvoS+^y#NUv54U`i=WgJWw#6OTc1sW-am7k#U5WCw! zGw~qPy%<1a9w4(p{za@v0P)sC`){D|1f^Dx`#@zmh+V`0@g*dF5xpGPYB!Lnpi&AH zYKRgMG>Qirr-hUOp#A{Jb)Z%#XtWl@2Bip4X#y&FK;;1_6hPqzT7?cOwIQW2q|FMc z`y9b5AQmw|YDJJ+K^USZlmV2sK&3w@9zeB71w#RNeiT${fp!6a`~*ptAlpH%0QIgw z;RZ^fAX!kE59&*S*dW(ILK!qO4l)DebC6x@8C<}loDda|)CL+?0QK~9!7F({qcEWQ z8xl95R0T?-uy`P5btGuS1hJ<8;(L62P|60acLT*PD3yYA;Bu!211ML5+9{P_*TykG zRtbXYZIF*aGN2YWWZf4epMc^96#t-n1`0VyZUE&?(D)9>cGx;AP|F+?10WZG=A%IC zi$HT95Pl?hH77_ORHA_7K(P-BRhTP4?QBpl2b7{9E`Y4!gryWv+2Bm~s1 z1epeM4W#!ETVV$(U0^b>ngx`M}%nhr~W;ZUN+Skn0dOLt06o@hp%lK=~~WJemSB31lLuP5_nskg_F{0aWgQ z!UVEj71YB5#Vg3%M21!1wY#8xDQGP#C{2OV3T!kIlwUz97?cJ;^$930LAF40Fk&?( z$aGK-0+jMWF@W$#F~cJ88eUM`fmom&%Aha;?R^2cd_8#W2B_=>jkAI5gY{P-J^_^) zpqvQm;X+CX4~Bg3m@h~-XcQ3Cb^^_wf^rThq(Qy}$$|KwJO*lCLd*h%1E^Gom4u*@ z7UW}4X@*!g4XG7Dr3J`NkV%NL4YV>Imr9Tvs7DGKF940Ig2ESaqZ8!jEKpp5YC2G_ z6R`#!R{nx)119cPzr_R5zs0%P&rk?0E$V_dNoikg7_Ac&p>jZ zatjnspjr>PuZPI7Q4-KPI9zT7$wT}NT4xM8D+(e4 z8Os2zq63X#fcB_?&OL#dg6sl_8qk?VptYEw^_93?i%l)amBgA0YCncS`?sLf1<4VJ z902N{!PfMEat>mI36@_#r8guOfXZ1=tploaL86fK14_#fc~Dw{m<-ARAd^A47gWQ7 zN@P%q1cemgu!Zy$KqnZ0W(z>~T7cHRf%*cV`#Dg34mzy{bRH*Y-#F+DKWrh1sRlCy zG1Y)V4$^bLel7^8jRG41hJ*oV78T@wP#y*4F;E@_g(9dW1IxdlFb1_iK&@bqE=U;x z%AKGb4AKFKAy6I%wQxatAw5=5i2};WkTs;Fha@C@g3=}Eyc$@45VSS~)P4Y!i=es; zLF0e4b)Nwl_#JY0%k9$ zjsw;15MO~>X|OU96e^(f1PT+F>7dYrl~|w}5)v=?(lscYL1ibXOar+XRI-C=BFMNh z$TbL;xHEv}wo0M3D`?IFRQrN-f&2+7(LwbbXe|~fWI*nNm<}qrL9T(NNc7SS6i2XL zEF?9f#sO%B63i8-V}KC69+azKYnecEW1xN(XqPCYtU$D6Kp_ZPl?L)3s8s?<10a8aaz-L} z2R)?J0HqL6n1fnL5P48K1*I{N2xMhEEc78IEUJq@c^IEOXr2cqj~;uVb&H_$+Ce8N z!p9}Oe4r5nP!0rz26C!`_zT%A zP&mU}jVX)iXJozjeGEG51Xo>-IDre-SPVi1rVBwSA2bdG8q)`zV+LCJ3_1%ER11P; zML}!4K{v5Qfp3il)tsR54p1owDHB0y29zs6H4CVx02(a>l?b5FLLR3AogIUz6O>;- zZC}v*Kd3DaF%48sgT^;NaX_3IAeErf7*vvg$|q2(5LBW-QZZ=#EvQ`zT9*Pco3NQM zAEhzOU;y>4K>Zq!4HYwbXF1<22!o+GHX0fiLk zbP8fj0)-c(1)j-}#ITA8yFofYWi6wK}cEy)$^b<1aT{tU}v zpwI%P8<2lMCP7w}fl@m}J??%!WKIth!=NyNv}8eLEUb46*=+)RgY|AJB$D0Cnx08!q6@&l-U2%1R&#L z4GA6C+#@KCKqf#&KR`T?YgR$s4w;Pswf{ls8IUqNn$ga`7uL7>tXQs#q76;Q7pl=?ufMfd@v8&rOP&N+hE1~LoeMo@@>ay}@e zK;gNbAr(5Giah2HD%nA0Iw<5o@eZnmL1ix}&w+Fy)Pi{EDnMx!R5OE02hjRCkZzFs zAZ`Ga{GikT@+YXS$YcP`?SaZskSNGJL=1pNf*aVtg0F_)IQ$aRE zO0F;l&?+TR4FL)dP>5!NV;xkEfKn@H&K$Jw08&oCd=Bv^Bqf1Vfy!CP+!w+=m>WRz zXdpG9S^yMVpmGahC#W0(l_<#!p!2jq@d_!?LGccn8Gw}Jptu3$uQF&m7Lr~dDnX%A z06wb$V*7dqPzr_QyA<$TqAPe702J?#^a%+oP%j#^<_45YL1sfrF_2B5b#ax@vJe#K zpz;ASf&&U&P}>gV7l?mBF%I$(sEo)3pO^-+2jqH?C}>6sl;=TZ9%yYk$Rtp%b7X*& zAs}^-vJe#4pfm?cUl4zQW*9-C3ko5SD5ReV%2S}c0a{ZA$tRHd5H#`u8hHSXPk`E@ zkTD0)xG`u%9%Lu->K8=c6tRaKG|mj$c@ElRje24>qO}8RiGX@upm{KC=72_%L8EY> z(+NSN(x4Lx5$YiQcT&wp>{Z9*3xo>D_$X-X5EK)j6*QpP8qnA;=p;>$%V4<;6lWkd zC>B8Tf1nl&D270>3(9k#G7OZni=cb3K&b~*&p=9UP}+gzLdf_ds@q_ro1k(ZluJSJ z2r>2bl~iQ$cMukSic1B*Z76n1!%F@e5&rVj0519@h{#WcPr|29PU2 zxdoD+LFp4YUO*)%>MAFQDX_5?(0m5S1W*cuw4^~U2c2S!IGX^}qJxZPf$kRoof{7d zZ%|r<-A@Br@dLW^36$PJr4VT63n(u@!X3l{#RzER7!*d3woVX(GdMRw+yko3VI!!Z z5((rFP>u(+_(44nNEp{KfX*NX#XczJLFo|UHqba6=w2LHngE^Q30jK?Ndq7sfmkrJ zK=}@oN|1MUgW80kI0Vt4)-fVvLH+^xAfF)z$-Rj9N0fdbdm!!v*#?RaP@N4C!JZZn z^Ti+?AosyS5R@N5?R?O@wh{PjcF5jzNDTxEP0+|KWcMJ%Y+R}w89-~YL3Mg5cy1h0 zTY^r#2CYj2pt>7$^B2gMpfVQZeo(pr z)i0pD1gg(Ka-jGEu_18KUlb3~G6R+yUxYgVtn$l>~kAOlCBnO&bhwMl%Vt|A^B*lYjMNrs*L_Ha* zz^!FS$brHGbQ=YzM+NFbf@*t^Ngy4dQWs>i0t2Y!3>wn}$$(nlpmq)@gc0JPov0wY zAnHN>2jv3LcnZkQAO=vo9n@w9rFPIP9Au3as8j{n3d*e@mw^0|$N;)~1~hH~8a;uS zm&|~;qXHB{pwt73S&-SFHUOx#0IL0AVxaw0ppp=gWa~Hw z6B7TR{hEk$1#%N19guJZ`2u7b$aR?UhrXi_bY?y59t22h6qM2+u?-3XNPL1;XTe+w zvIP{rpppU6=!4XQ4XPe3^q z(rd)j11ga~w~K)8%>nrYR6l|01<<Cbkd?`v44~OTP&x;#PDadQVyXtI1Kpj% z0KPW>RNg5tgff8IRG?d&KsURC+CiX@Kz1w0C!kpdP>Mv33&?6V(0Wo(On~mx2A%f| zs;xnDUm$maZm02N&}IPL=m1JrnJk~2b)GhzVAf%d_GR$#a= zbTfcXl7p1jkohN&|3NJ{P*{O#QCNKmX+we1AZQj4GK&Q|?Hkh41g%{L&BB6i7YCj3 z19CZNW)-xHA7nbHj0d?KbQ>S& zf?8mZyap=a2&)H`03Z`V)i}sPqB3jIer;A7CXl z$QPhe1`^7kn;}3ak%8hEmWL2EEF`Q@Ys_Hq{xy*Opx6hMRVCoQ6zGgfP`L}bjSAHF z1@(GBBafgEh17$f)rE*s6J#SK&OmiJ$R<$yL*fKiop_m&jzT~32N`a>IzW$LauR9QvoD|Ksf`Hk3lyELfS>3b`QvQP`ef+0!rzi z+yOeJ5p2*kP5D&Kq(cueh1|% z&{#YuM?gwR)OGNj@6Q1F4iY+`5nRy7Ib@U!R6i7ecLjj#0ND@n0Vu_T z$|p$rfTdDU`v?@?pt*ZcUkJO+PT+6`r7lpthwd&=FCq^-Lx($8!`d^T(F;(%gXIj6 z%Rw;%%CjKfgUm-&3voX&c7WUmxiJV7?x1!8s78miB|$j?RL_EHM^H)w)y|+EKBx}> z>p3B6d{C=9harstbQ2LMcY)F+D6BQ1H^e|vLFBtfQu>SkD2fl6nPO`!e$#o+S^VEr0c ze-u)(fpRjc=@1h^K8Lk#Aub2C8$hWCWDckv1)U}jDjA)?`^zCQ3Tl6WY{lX{b3kbyBnld30i}0ze}Z~t*i8YA zeuM0Tga*iTQ0fJ>e?cJw8Z!d5;X&F@De*d626?{)3Gbk!lhsS3%N1F@qihC6f8&InSqy`kS5IIn}15yo1S+Fxl z8IbyMAe#^>Ag%-T93g2mg#mQhA;e5jSVPPK%^`x)4k(mBsUK8AU{eP=%LddR0<~;G z;Rdodp8-_9z)C5QpFwRlP^|%KkAdnJP^%eI?m^n>Ae#^_1la|OMNrQLG*$wNHBgEG zjU|G5S)kkrNok1E5VZOlRN{ivLE5LFbPJ0;m~TL_Ls^-JY7!`ZkC}Pfx-=x5#Udp!#_k18Dpj<|5E& z7f3CL4@$R?x)`DhRIY(s07@&M+z4x{fKuW*a61sD8l)0r3Md7D(jus20gXg}NknsV~$N)qg=o}qT`2eapLG=vC zmmoVqyAD&qegmmP4r@@_0+n;1m;jA@KxW=CZ3ERmAibaz2vS2kn?UgfO6?%qKsf-k zh7*)SAamv9`Ute!36zdNr-OiUC8+%kn!`#3*UO-pHc;;mR9b<;A5uEN!U>d?LHGJ}caTjG_kip}wFi`1LH#CB>m1b1 z2K75YrsMQN;Pl8TvfZRX{Ic;5+0aQN1$_-HK8q_;M6-758hyh}PD^xG29SX|9 zuv`r)sX+A=sGfBMuQq_x^q~G@0C)!-XkHu?pP;%2lwUzL9%yC_lrKT?2x|9(+R-3B zsI?DDZJ-(*q7T$_1+|$#=7Zt~)Y=Bc3n*=Yd;kg^PqXRU+kH&9&($`hd0XFDOJnH3leNk<%L}%s?RmN-dyL1*8|W_YO3&3#u(aDUFa_pgjdJdmuX@ zLFupv><;400>vPxd<3Ou%)8kjzJmB4Qi79WD#+KM(MwRu2DQLJISdj{pivOe`Z7@X zK;i{dGgHfKP@4@@zL07%NFOMcKy6h}cz}ElqCqRbKx5&kcgcZrn==C`ZU)6LD0PEI zPe8E_${U~)B_VkcRHA`;5IH2r9>`qe_MHoOBnOmpK{BMe5LE7f;u?~#L1h@Iq=cmr zNI3!u8FI}A*pvwo(tSOQeYM^=t)J6iuI;if2Y8`;m z6{uze)f;Z)wsPzgfB|xzO8dU(b zbwD)-D2;(!j9l7)&esC%BLLOzpu7kweLyn+pt2mK4pc59=1>vs5sBg$Tot3WvjZ1H~_B_YA1qg|v@Arh&$LL16&W ziRg)d@+!y%P+9=_64ZMJ)whtk0Mr5p#SJK&L3$CkfaVe*V-}$HAE;Fa+FJ{nLxQ9m zNKArq1M)sMP;U#8IzjatDC8lnXHckscDO;@25M!2Yy!Cl)Q*9ug|u1|89@CwNZ$&5 zM;K@&5+n_RYzMg)6w(m)fN}t+v;vhSkoiwgOCS}T*Fb3>RJVar6lCN860#82K=eaG z5V5xaWCN((391P|dSLkpQGQm6EuzDw4;o7bl}w<9T0l;%OYK{0B~kjwx&l>^jf2letmX$6#SASQy!1CYx=t06!& z4=7E5)~!KG1k|-4uo?ywdY~A?*6IPp0H{?53RjSOK(Pz*9i%=3`2bQcfJ%Ib3lVOC ztv79_M1!RKdSt_#CBKsgH(hoF1_ z8uRHglz(i?ZM{kL1(`RW+oc#sNEix&53@}Pb@WKMxpw?NDV&Fg{O z06Iko`FwUt%>}hmL2YnQ3k}rof{apu`V{!vW|Z3N13s~%1iYUObYc%^MFgmq4cV2k zo&k1eJ7}i>C^kXy0K0((vUd#BP6G91iomOXA@{_CdbglH7ic#vq}>7vZBVNil41}u zJD`=$AhSUuJfIi{^@KsO3Q`AIGoHyX4}3ZssFe+Ab%R!YfZ9c%umYtv&f%@s7 zU2UL|Oi=F$)H{Uq5}E|UQ?5(3J(pl}C;EGW!DV5vT+Nr8kIQ^1yRxkp41ACCFc(912QtARVyz z3&;v@$n0n$IE{cxMo`)Z?ZSohl0p6iVbpO3@9amdSRfH z1sbOV`8Wl4MlKq8V0K{&rwYWufqnFPavOzY6#G6H;@nk znGW$Oq&E$^zbYuDz|=r|2pV|+jVXcV z7$GZ`JsCiy4`{_EX#FW@9s;s59I}ENv_=!Oy9spC5NNd&D3^f7L_p;cDD6Pf0%TST zG_nl|Lr_S7N-IWazQLss}U|06Kpk)V6}eJ!sqslyX3$ zNT9q13S(Fr1)ahP8fgNhGLRb3TCWg>b_UR{DSz;q3D6o)Se<~_TML;#2IWf7st=GF z(0n|o%mLNd#M_T*AEfqnVF0Zk2d&otnTu{Ws2u_s=Le-m$gWn6r>;2Zi2-nq$~sFT+kRbq>TWn%|SUI z)K&qx0aBxa@*T)FQ0Whv^Fp)$L2GJ2B|FIN#o#)AJp=4a5m5aOIx7TJ*Fajqpmrgs z{)dgRg6dyTxdJ=K0Mfz&xd-H0M0)_lgOocUSAcv4Dvd$)6{yYwt#gEg;Ccp72!ZT{ z$S-05?JWnb$b`5YlBPj=AT1C`7=ZiSIt(0JPRKp8=HLlEJkw zXeJS27AOWlDIFAV5Y-?N$U1w(TDKeq(CQcLZB>vfG8kqsfbuuUFCbTgZjVN!J&+2} z=o@I?Dd@IJ5F3<=K{XC&OcAtW7PR{Vw0i=S2SN2XNKG&UXh#8Pj}2^p6l5$MvZ4Z1 ztAf%YD8@joK#)H{wIm|6Kt6%gr=YR`v?>X7ItHkI0Hq30-2jPSNJ|FP)<8aQ9u!xQ zGzqE4LG?T+kATdA#0IDifyFjtTo^KX2WnA)PFn-D^*|*DXiYAty}Al~0x&53LQ)4P zC4g3cLCQT)$_KR>LFR+(0@V(nS{t-R2jotWK2RQngaK&h5F~$s(hMkNL)?V$0Vv0V zQUYjJ8B`{L{EMigKsrEq6jWn_YzCPEDycwe0h9tk=edL0=Aax0ssll7ZcxaALYuhx zR){-6x0*ppU`Q_vG}{2`vw+M2*_8usyMcN>koW`TThw*-pzr|sA5^-4LK~FMA!R5i z7o>tq0!M}v=#EQJ3V^s5)IJ2Y9$+~HR91lU8Kmb8nYn?a8`N)%A4{F1MQVu9?K|7g2buB1& zg5s}^Ap<;%ihXVrH0}?oZNnK{89-|TLAek#=UNV)RRy)Nf*C-2+(2On*|!Kf2iYHd zT1Fg0BXm6(C>2xJZcr`)ogf05aR;rH1eH{vP=nnj04gmZJrvY53o5rksSMP^1f^t9 zD1*`uByE85C#b9km205!DbT##XqZwnOc8YeD1CxTPf!{H^|L{INGyQz1*nb$mC2x5 z2vn|v>Uz*hbWps*_Jx9Uzk0r8r16CpTA89;pnLbiZfO61xCDyKpHbx<7*8o2=Z7Sz@N_1{6} z(#CE`3j-92kiGt(Gy)p+fz$+q>;%OI>HdY-397%+prfgfREs!C475fW)XRp%94Kvp z;=q6b)c2x|n;I7lx{%j4HPP%8Ws{Gppy_m^$VoVBjgHDjFB5MAbTOP0m>_& z@efc+rClnA#0RK{2Xb{VbWI1Sq$)?bH5Jj~0=4QuYX(5M8#L|#8ixawP!J!3+z(2@ zpnM8S)sP+#B#wi@=QkCB_lFiUWH2Z&crp}&%P&y94as#7_ke1;JaAhFVlt>jgx+!m z^}C9}_myCC1*A<2sV5=jET~lk3P+GzKsg_@_7Sue95k{CsvnW<_h10EYd|3nN`HtJ z4Dq2rp8YW2gHj#x3QW-GG~_-f5DOGjgwh})cY@B$0F}<5QWoTYNIW5zdrshc%RwbA zsO-k&22h(1R6>Aagm~LwwF_viEvRM$xd1t@gX$SjpAZxypq2`xZwrbeP!AUr4xo}A zR3ky=qlhc#K{awdxO@SvhzeyW0Hp=ZvQ0))ue~=QAko*j)aY5+@QvN~Q0xBaweKUkx zK)WabFHrkAAF0Pqy30Vh1eCf#y*yBAfw=}$Iw3{^V4~RWTn3HcgGRA%r+-k$ zB99V~;!1buP0|n-K+*>&Bx#fOaLsCw;$l$C6qL^)Ei^Ugd_x*T8UrZrLgJoK=>ZB$ zQ2PaxUO;{(KHYDroirl=47(-63uV%{_oxa-h-xwG;uBQ;3mEQ27Yz zIY4gz0);=Q1p*p70?pJ^f#;M!EpgC@A1L>MOb4j~)l-lWXHX15Mj$~U0%^yAassFp z1J#9~7BWZ&NGHfOptB4?t$0v>8q)3t?Jxo5N>J|sG&Tcjt%E`p6egf{5omk`w8j_I ziwEUgQ22vvg~SFZ?SSG8)T;rR3-S%9WQC*$b-K zL3Imc1`ZVepn3-+vI^Yi0mTU@+(EvA)m|VwL2di@dw!tnt_DH87P)Own1hRVezBD0J-rBBm;^iPI} zwB|YlyeA5hmmuK-sa+wXHy{KH&{W3bU*Q2c}Dd_b*cQ0PI@7AVD`s)NN9BnFDX zEfa{^I0hH+%mT=KkO{DRBtY$c5FZqq5TAi!1>!1LDnkw}P#IIlP{~jS?zez?Xb`(V zc>|KGAojuXF+^I2n=S4zHfy5r9B?rn8ptOlN`4g1ZKs6m?R2Ov4Ijp<@ zr5Ko>KrIx|SU7TsgVH%9XMo0NL3tMvb0EKi!U22uz-$J^9B8Z`G|mCqr2tCBAYGsm z4ithA--1RuLGup244}Ocehi?oO_={cIS$mehMd0(2`k9V8fXLpRN{e3G|-A0P`enE zgFw005xiO+G=~NX3s5=%#S$Wqfx-h67odIo<>2#JvcY$6g31w4$bfqHp!^Hk3j}fr zXstPDTp2W91F5G$VFq#=sP+Nna!8H@t-{J<0JU);H7aPV22^gKkBXy@bVG6m$Q(%M zdN5Ri$Kyfe3A&q5SGTJ$z``1|*90-54=RB`B{V43L9qs^b3kjQK;%O-m_O*OQP3#969Z_iFzD7s#E3s6&4F?ZXdewIK0sw5s6`0sse5hPG(;`ak0?m)XoK)fJ^c!nqje+JMAi17@d^AE9w6v!@6{s5H}RSck&PM{q% zkkT9GPEee}MnoYsDROEC*#${8*lqR)pF)YvZcuIqjdVdm5|YnAZDvTFirv4UGMSv2 zGekZDwR=H5IFPL%3~Eh*+J2xEoCvq6*3Tde$!T{7i0Ik=B_#aj$BU}wi)3A9

3VPH+pWtHDd{C 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%0D2Vf!qxWe{}O9 z`3KZK1dS4c%m%fxK&cE?R)BPY;u17>12G?F<~)WR@O>$u))go}fXWR-sSEQD=p;r^ z8iu$4WFI6(L1QDJ^@fmefRwDD)(a?CfZ8db)A2xg59BXUh=JNvkkOVB2GHH+kkkq? z6;x(`@+8cKAloAfI=FSx`cb z9~2Uh{053+$h;TGMYvNDNI!D<2wLw7NinGptJ}II}jh#+ACrJ`4|%Wkn{pG2bBLH zsvzScpfVN22jyu{DGX|Lg35JJ96{17$b3-jf^>pf(4bNlk}D8>29QdS8$fXgN|~UP zn20v30}4%09SU<5NDs^ppix0c?G17*Xv7P&wht5^pw%j%F$hr1gKB-yoE)e|0Ij5f zrE^eS1Tr0jLE#2-aS_8Z23TtrZje6ENDA_qmmt$2Wo90@{S1n2NDc$JA2f3f$w?5iK&>EH zA05;~2K7lmu?z}rP)LFN2#Oa}HK4I;PznX5Gf>=s#$_RPK*kOsHi7hGJ0S-WUZ8LX z?L7p!A9T_P`kVFutWAAatZ^* zETm5knNbF*1JzTI6L}!H57g=cr6ojaMWkJjouIS_NsFL-2g*et)gYIFY=_*C2}&8D zlm+RXf?N)%mqG3ZmD3=1fYt|qQV}Qx#W8?l8?-+Rq+%ZUEH+FrScrjUyg@oaV=tg| z2`b}3_CvxMmn$ItLY}pNq#u|rP|gLFO`!A$+2P>qALQz5GIqKnp3s z_bG$fMhvGw{VFiqm>~nyuL8487*4?1rVL)7{u5Z-jDZc(&oGKNFlPXtfdLk`U`PP< z`M_*ThAtS}(14);(ibrTt)CTviGwy;g8EM&^-hKiO>njmOXKlQ@Ex>y%PH=-(xq;FYsHFm`*+KOjq{R#>(LpUA zNIn9M+(KI7pppwzkAhmKkoDb=^a2WLY^4z*twYira*BnNx5($}f#MgG4ng@O6?{7( zD9wP<1Ef_7DuqE~`JlXk%SKR|1Eq&zaJmDfP0$Dd$d@2dNP3326x3n?&7#4|NRWR) zEnejDA5aam2;BDpt%w4(-9UK+)FuGYpt=lZDu@lLpFsHzF=7l#$Doxke@+03FI$O-hz~RpfCo72;{Udh?_y-0m=`UZGT8ifm){!lR)kPwTD4w zgY-h&3GxppEFh{ut!a=yAT1zJo`=O3?p6q>X9;mN$fuy19z=u6=TvZ;4P-B*9gS)u z$Q_`Qh(Plx5d9$gAmtLoHHi$M{sE}`0EIQE)CKL$K)C) z^e#}D0}3Hf%z%6gYKKAkWuOoNwLw6(gZk;9`3{h4K`9v2Vgl{k1Ff|Kt$YTh9Z=jO z(hR7EO=SSJvOsk`$Ye-GOf?NP%F$T&@pcDWKCqym)rASat1f@5SuR->} z!VFZSg7}CKfVl)>1}KGrTBjiOkl2QV8FEVnk!CPUGSJQkNSXoVK~PN$DuY0&9^_6) z2!KZ5K`9uNIw1CdQW0q62a*y|O$EiQKLg0kpwIxVu>+MVpz;$m9}4S-AlJo^kzLGE z6ru(cZlE>}$P`fcf&2$bo1jtvRCj`Gf`kRWTnXv1LuS1|{(;2^_B;e}3n&M|YD!3+ zgv>*LLI@P!kdhbXN{ES|7BOf{03;78H;`|U1;qozuaGtf#1>G#Lev`&pMd-a@)vsR z6=X7~d;^`O3+kss;s}xcAS=Rf+Y4&9LE;$XT2z}sVF?Ojkgq`D1j;iI--G%IkWvAZ z&p>4^$X<|cQ0fHr)FCZ(P|W~I?}*R_`2yr8P`e0}zCj}kAl;CB1(E@^nIWM9I^h-) z!ys{xU7$J$=1<&V0`d#U<)DxO?J)&~8YsRXqYDu8LG5cq-v#6<+~$F72d%CGPau-M)NDZun0hx&?gFrbN zlDa@~59_yp#+hJkPtbS^$d3?xFcU$2dr-*>D!(Cd2XQgPcF>ASLh%PG&mrjuWF{n? zf${^$y`b_3g??$Sg8wAA=8rC-~+o(EJ@N4j^j-VXYgGS)hCbI$0dV2H6LS3sCu% zh;-X1s7wOo2T;lcwJ|_(0~%ET`4ALFpqvH@6_7emt7-*HlVpfkpDnC z_CTZOtHAweP7-qrQrVNMRY%>NSP=5xj#+;!8&bDAM0rgeD;+72S zU~EGJh7wSJ1|)81$S?=aHiGP51B)9oNWk1?Xu@CxXM<*z7#N&EY8)9DL_mEW5Zj4? zVIGX_%)sys#&%&~&;a#+Kx$kWAamRxwi^S(0+_fv0|O7JUj!2OU|?7QV|y|%+<~#Z z7#KuAeI$??Zw3ZC7~6+|p#jGBWnlOOWBV~Mc!2s*AoczX3|C<600xE>P#+Q`9>~D3 z55^8+V0Z^(2Qx58f%=mmH6aWPQ(){+28M4ib{GSL2rQ1m86bU5kopM7S`a^8(8w@o z^cPgBKz7E0T9mHP^)#5R7*J~hw7vs!`V~kQD91w5G^m${7`Fq7LsUajFJ$Kn=p6S| z;Fdk8B?THE1?57BT2LDR)OvxnEkS|~%hbX5Yy-rX)05cV`o(@zV zf=YH!*#fCAK=y%ZT2N?$T0$WIz*K4&)1vO^{FlmBgU%N2Eze z{D4Y9(C!t;xDLofNKAuL4`?n8qzAOl71B2WwbDSYg6IN`W`jm?*D-+7F>H(xl&)uh zd*{9ki@;?yNEawPKrM4nISQ$XK&?DTdIaSj(4Hlbd62jTk)U=kNEf7jfaD=i4Gl^Y zAh&~RZ&1r46+A};>eYeL2FSOd78Iz22dy;##Sh4}keCFe4v-jV^%Z;2@y_IlCL=22k4p6q=ybB>2q& z`4M!BUIx@3u#f=d0Z@8CxCUe%q!fVo2vlp9GC*7eQU%IQpxIzhIDpa%B7cKi4@(Qk z${kSY0LpD3UxIQkC?A4+fk>+$vq5NHS@ zfkFUM;~;7h2p<$ipqdV(8dU0ld=D}MPJ-47@ZL2h+o0O!69YiD2CWwbH0}-~cwIMLmV0i` z6Omqmzqryk?jPDfZ_>MzClVSP^tpC2Ba^K0kpOr zq#IPDfZPlkJ%Y?&fZPb0xd-Jc&?prs)Ii}4>X}1w45&8_@dduL0LqD=lmHqPg5(!a zegNfuPzZx;fwk&D{cBK52IhNEtbs~vP<;vsZ%AJOM<9H^sOpwb;A530XGBX!tjM?h`>g&Jtb0w|q>%1n?uAY}|FEka6f zkbl5^PDelJT;K*q1_lL4pAI}1xD3uVg6tCubux-KFlNYu^y!S^4NMp=!P%w^a*%!; zc>WJ`_m4AJy(Pmkn7E-K!zVb~h#?Hp*E5PYG-fygXPbcU4*=^mG-Z&3^v8|j4b2!P z!P({vVvzp0QM{oALjjy^$*>N_HZo*jf%MId;*E?L7QorY3~7+Qxlz243BwmS8{P*8 zUrJB_>1!K-E+cvbV>?6c83Mb_g@K_0($_WuE$9TVAqV-_je+44Ox&G;!41;a246~0 z1!H?MFsy;Gy%-?-_dt5R85r~+eQl$79|nd+Ft#rP1Lz!Bu)Tf^3`=0{@rU$_LFeXx z#_vF7V&JdqBAhn+}j4ASFFW7E~^R zN^ejf3Q=Ez`~g`ZhwV-ske?ug2n(qUd z0t+#aJg97ggcnE!XjMOG92`_?g6exvc>p?>8x$v?9yTb>A-!Xe8$cx!qQ-@_ia;R% zN(UhQknsml2@SFrRCa^fq>x?{qp}4b^EIeF z3~C*wGJwVzKsJKPdQkla@*BvN5P!k;9fINkG*1ii7bv}hLIZxg8Fx$))OdXAz_c)azanBpilzE zH^@d%j)kO0P%9B611k?eaRNzSpz(EVDISz(V0$D%tw@k=kS>UiL8TgK^$chX88p)Z zS-lC$HK0B-C>>$C0pw4Rsi2So&5}Uk8IlV@t_AG_1ogBbaRxeb91#+rcn7VZg4nCT z5X1l~4I%Lk@&}}D292me;tb?c&=LCSGZ+Y{6h1K9y`C#byu$?cGND@gtW-7tj9c940Xv;>MdkY7OI0&*Eh zC!`Gq8u0{`ETB`2Kr*0|hMKNGCm+CaILItWo&dQTls{9!qh6r!f$XCMsf6^rAbx<9 z>WSd{5I`vqR3kt_8P>l7g#&1e5R}S5r7WW41oehMJtWA?0;oI$wbVd00w|q;OajS* zTn)=_AhS?wBanYUbBCZ12H5}#M^G6BD&Z0P0zsmnat>q`q+S5o2D;xGw2#l3!I2?| z!JWYoeAWd>1~i@oie-@Lpj-wTe*nb+$Xy^YkS-tSER!Px2qNo8)ZQSqApJ1cfkFnt z2DL6>?OxCtVo+KGiGh3#@((EQfYKKzXCcdjd;)SEWHcSLrvnt%$fYzS?SfkOkaP)& zQ;mwU0OhLCup4?B-|jr1Eo$-2!Q%XAQ!;$G$`~yu@6eQptypB4I)2+?iqyWMOO(i z0aOD(N;i<-LE#UIF^C+Zp9V4&;#<%uNT9RH&z-~iY@qOhxDB+X6Lf0;sFejdc?*(r zAn5^;KR{zUpfH4_K3IB$_!*lULH-1lGoVrnz>opy+Zux|eA@D!8@u0oPE{-94 zI2jnw`%?-GkRA~zPC={PL3K2!vGpg07r_5+RJfKJGU#Q!1&m^`RF1GT0>r3k3p z1?5gq9)#rxkQ`{`JS4S)`c9D1bx_KMtV95*1myuxc>^i|K&cB<$AfG~)C8au4blUd z;RLm$KrR907f?wA$#Ed}gVGyFCrA|3kARpCDU(1YGicurNDhS29 zcToNWjYWZ4b08T|xeUr#h)@IdPay4dkS{@}xPo#cE*n9%Ai@)559n5JP}>PKe+&s< z)Lt)YTN30`P&)?UH&AH6TICS`fl4aGSU#v62Duhgj^}~%GU#RlQ2PaBD?~SlgSMu&@P%9fS>YAxJIA#UK}f+GC(J29g8W z0*O~pp8>M=6O>*dbrdM2f>IYKKY+}HowNW-U!WKRwG~0FN=SfVh&;t$iJXC0_8K1Du_OaZJ-_y zD1;zo9H`U)*#c?#fl4M&PJqQCD9%9rS4ioIECWgnAb){U7$|*1)(b<{XMp(VW~DC|HjOpqHurohqxYKp?G3KG7cIC2KxWD5#wSeSy!s#NgV z_Mi{|$$;Ds3LQ|Z2POte?JymnaRX5Jf=-wKgYp?9#2_w)v|AwUCQuHBwEiGBZh%$| zg31F>>H*~~kgGs30HQ(Z094+9!WERlL1K_}45|}BJV>~M`e2~Z8<07mI0BV_AoU>M zf&2im4|$9S(gH)*2N4Ci7UTm^I6};at!0O#R**Pqi3zd;G&c&14KjmilDH8*h^mfQ~`V%jt>Lq#uiW?7_?>*RFlHiGv$L%g#(ROL)uuN z*a4+T& z(K7+fd$2*;wkF{AuK>6`YXWNXg3rzmbut0%dpZJFW5MtM&bDMwfV728K=%gd!P$lk zE^xLH!xA{#n1KN@Uttn&Xu_ZeXPYv_!P#aEOW*FVP(KCC zc4J_0gY*MT;@ue-_QBX53=9sCc@+~-KVSij?Zv<#1!?b_fcgR8a}h!Q@?l^&2NU;Y zU@(ES{Y^mq0MNaEV7vSo7}mkW0~i>1ApHQ7_&|97zy#C}fc6he;)59&+93S{llTw@ zhBYvDCN?lMZ7*gMW>;u(7 zpt=Io0tD56p#BkPHVZOh4|5Hu9|anX2e}O9Qb^wmqz|MAG|G#piy%D<1RoRz5Z{B+ z4X6zcN)y;cA$r3YW`K9{gX$uP`Rf@x89-;KgM180MW8qUmAjy|feH-k86a*3g$$_e z1{x&+septy$Zw$325KR|+H|1Uh4~OvH-hH)VId9jF=#{ul-?j^HzbXM(mE(sK(jBP zv0IQ$ptyvD3}nO}ZSdM-3iBjzU%)q2;@G{ zcory*VBrBuCm=V2#6Wv`LAeAJ=PnG8_yG9;vtWUBm!N=b&~0#C@R9 z1?6#2-UGFbLH#309zhR3P|4uT0NT3+N?V}#0);fl?;z7axe?ON0hJ=4@qJK!1jQ04 zWq`y$EAjsZb7z#_#oRsDFNM` zkaz}-(R+YVCv4E+`j)%72hZB?Cxr5(9_^r5M!o07@Z<6b^DHC|n_~2c>sV9)`8w zkn1Z*iUXBkpuRWA22ia8YH>nh7!+op)xwao_#rtDlv_aY3CZao7HIAOQVM|9BY;v5 zq#Od(qoA+=m3A=SgXW|`ZiBVJAub24WCxu!Pz+uh3~9Z>cBz0$3{aU43t5mWL173= z6`(Q-=kaP}837}jGss|zQ0!mS!@(&U!Cg4*mK`sR45>QG5#RACfkU26? zZ31e6gJ#`8HiOKAl{%nM0kv&Gx<1 zAmuA4TtTq`3S&?@MwIK2vlHSN{K0tuzgp3>3?t))#Cx z7UTwy7$_z{w^D#o1ISGv`$6FfDn~$Og3=PGKMgA1AU*}H`URN}>gj+&2^7a5S0#ei zmV;s*$`*6+MtvTTHga2e*(D(6pE1e z0G+K1N>88^2`eQ)=^UgR)J_7mQ$cYJniH=AuXO~)94OWx;R`B@L17On>mZ|VAoC%m zA1GzR{0@mz$n58O21s8XWG}>QP`H3{Iw-Ax>Qm5)R#4dtN=u-&0I0SC#S)?v2Bk2_ zS!AGbPmq5gH9aVtU?DLNdX^fb+=QGS4r=Xz+BhKpf@&I&pJ638sI~)zIi#Ed*$FZY zQWrqV0MI$_kkA2*oPf%GNJ$GR%|T{?Tmu?|0=1byc7Xf`ifu^V1J%VK9;i10$}ym{ z1(Jcp55%3QqfsCifLsnT7vxJ&D1pvX0<|L`u?&hwP+tL5qQgW$^K76Jq6ECw0J%(q z)Xbo?2P#ouu>;C=pd1ehV^D5{?9E4Z74c&=5F0@84e~o^oh!&T5F3(jK_LT5(V(=C z@G-X0AHrs!#vmxag7Pt_j03e(ASoY~n?Y-fAaM%X!w>3Bg4_g(-9_M2{XsDa5&@-3 zP}qS~fNCv-J3(`)pt28?Cm?AA6n~%?0_g>{79imXi)B#h1@bAVT!p0-PznddJjftt2NF-9G92VvP~8eLw-kJ%EM%So5{jU_g6Ivyd<}|O zbdy1LgIeT}oC*ptSUUog!a(V19{83nNNNJ5SCEY$f1=+A1By$KDv*hwkOZY4P@4sG z$1{ix%ZVTtfYg9$R*3Hrp$chH5N9K(q=)zxbPhEvOhMrS%26P{!cs9Tyg=fhJP*l} zkkE&u&?E-PU1T8l!$J#DVt{r8Lt1MPc~Gc8R`P+;5Xf&JKY{XnDR>+lloCL8LR2G% z87Tfhu?7lT$UUi`G6qyOffPsw#DsE`V;00$JG5mnDjTxq~!qk{BT!XN| z>${)9*+vXLY+yCu_1*K}Y!e0oo?A8^^)fCJbxfY*PjkL6{mdhBI)sIYXWhOx%Lu1DtKi z5Frc|H#A_F1ZNvEe1o%%7$%6o)EF~dg0oE+d_-a5rVKrBwi&}FINO}TMhvFLf}sG; zwq#%thl(2+Ff4$x4H=9iVB$s$>)>o-h93}iyptgVLzE;~O}vv41H&yC+n9kNLkcQx z!octX#x`YOD3FGVn?uG$MP$%-twQ=EpfVj)0)pD=kQ)X-D;YrLIB3KL)LMe|H<5dK zFgB=n0h$p7)s&!;3^FPLDyI=5AT^=To#m)9pi`_s?P1XRTi6avSlI|_KY;G12DQ~e z^*U^~5U9)o)jFV72&jaIw1v=BgU*@|e2z?M1s4Wk%A7m=11q{*&ss}+eIml-qH-TCbpq3v<9#-Fg%3)AR z3h^081;`Fi%?&9}LA`8{D5#7Fm8qZ_98|}G>;>5c@-f5=P@M#-ML{-z!W-mfP`L~; z57~CmnnFmu4KfRKw+Lj$5jK|zD&Ilvc2MmIssBOk2~Z6JYg2&S3+e@d><9T8lwv^V zs)F1FsXIV@XHeT5l%61Vf$CCF%N1lYBrZT>C?JzSqmv+YppXLbAtr!YpPkdqrh=?m231MO-C^@cz-6ewgs;|`#f6GSayR0U)k zCNGO9`0E#_Odk5q?kiSrG zz5>aD%m;-hB-}y$3Q)X&(g~=Y4H;7d)%T!$1iIxER7Znaf1p$bic!eeE-1Z$Tmnj4 z2zPTIF8x&WNGzp4ZP-ud}8I&VHhif$R!^{P8U&>zP`Uxl6@XR-f~&`3FJda zT7-y#Vg{5BA>jfk#Xvn-(2N;qM-eClK`9cF|3Ig!Bi}jgqRNsYfzYgayF>!0r4TZ1lczre}Zn(0gX)fGx#z%F@VPHK;;Z%E*VtP zfcyZ9bI^&qAd^7(1C+l&=@V4SK+YKfmEf?Ecu-jcD&;|84k`&j=?N4MpxPH?8Yopl zOhA;xurvaa2l*6~nm{28Dj7gN1BD~Vc2Juj2Yk9CC?$c~5+GHe8U~b3LAe4n_74&V zrBTo*0VH>UVkwUSbh->IjY7g2REL22rJ(Qup6Xb4CEeuNIpqK@@38Wk3 zK9G5!@gI=wpuCSk52BIRrh#GtG@1;G9Z(tu)qEhc zAT=K-RzSB%K*|)*oD8V^fTVuNNGZsrpx6L~F(@5?awuq&A7mH8t)R37YIlIl1I0I} z7mjc(DBXhcI4EC$LK_q|p!5b(Sp_~X0%RJj<_Dz<(253741-2xAmgH-JcCG&kbDKI zg+Oir#UiA202(RI1CJ0uOaR#e3K5X`2-kvmpmGNk%b*wmmBXO9K!`b@lmtp2so>cL z&}bkikAwUUihYnCSXd@9fYv5}(i6yakobUv6DU=J>Kafh4b)x)nG8A&0Ad@cyoBTo zQ27iR)d1CGpm+tB6#Etl=?xb4ALqF*#kQL4&+WyS_H)lsvAM~ z8=#&Z4@r-pS!htH0xGXSEwDWBJR=Q4O*I6mlRH zpc5xRWjLaB42mU4`39=BL2dRp@QEC#sRk69ptKAsFF^GsDEvX{L4JgsSpBJ` zk)W{)RQMaAokI#~9f3>$g%&98K`j%AEXb{( z(jOG>s6GR^29$3gt^tKO=mblU{gAK%xq^6CgIouy!y)M!a=JgnZ=lczjlO_VD2NT} zm4I>xs7yd^ErVPLN?V{Ad&qibP^k}!r&RFz2atM@-$5-O(D^BlIt*0Of$|8*UQjy% zRKi1gCZMta6yu-}2bHd%m%ptEKxGCf zEr7xalomkw60}bO6jz`W2pZ*B#sJa>D#<|lKq^79p!^6j71YuJ)e6XRu$~%ZgauR= zfOJFr0BUc8{0;Iy$Xt+nKrV#D3CI^9w?kIdfaE}BG$;*2@;Ibb4l0j9c7W`Ko+Es67H=LwwD^0P+jsY$1?c5LJ+QHc*~W2e-UIIR)e* zP>Tn`RtL9=9Fb0*1%)3dbwg$`A^8F_Q-<6kgv1D_WPpSYD0hJT4RJfDyaClvknn8BZ#-20hD4u7?g@2?Q>A+4>1$sW{_zh-+}T*Is>TWg^hcF^nmgk zNEW0PREi;D15_e{@&+isg4&&+P=)1BPz-_M5|lq7z5=-hW)oyh9mGUP9D-60C`MuB zA1EimYC(jLKsJC%CFC*zWG-Y35!6!vg*7OKL3te%6OeKN6dNEHfzmX{)u5CQDiJ^> z7^G|hxf4-3!p2fTsS1?pKrsYLPavN_+OH53KxTr(K(2?m4K$Vu8r6WT4~E1mB3*!7 z3(^JhJH$Rv$_L#N1zLLs=^2C6K*}Z%A9VTzDE>hC3M2z^2Wb5V#2pZKgH%II1i1uc zB51uJD5Zi{_<_hUYK=l_W*MQ<0WD;m(0My%t z+>#8L#RS#)ps@@{+<{a>QZ%Ucfb2s9iGgAY*7}|YZb8G=#DZ)Exds&epzwo~C!pE| z(pCcHKTs$^(i)@<1DSzEtOx*&`he0CsFnxC5ya=9kO0m1g33)$PJ)CFU~*ArE}ZGU(h5SXh8ccu>0<7FwWG1}f!2 zVG6My6y6{=g4!Te;QN_DB@rxDfZ_q!evnE~z5?Y!5E~R)kXAe>ra-A16iTq10ylXCV7PV;rD58)O0~Y(Xo!Ksf?5pAIR1aQPkNKFF$4kP65M2E^}m49N^2 z46+jx&aja(kUEI(A+Z9QmB(}kNEIlZf^R_&#f%A<&p<0~Ks_Z; z?1IbyVTijx;S3o&L3R-&wZVKcj{)Qpkb00C-8_R_LxQ})cR^2JU|^^L&y@$c1_b#t z#DV7Ap?5wn0JB{qLOlH#%s}(z@otWuK86h7IeIV~vM0q2d>6DKLjhP#a0uwGa?qX9 zZt);?2ty1b14FQ9kgH$3A;TLu+laxBm4P8RAjmN?-VnTx9KtpM-!as{23o)620kqS z)P@FyP6fCX18U`#GJx_iWTh5t%_n5X8fg3gRQrQ+5-3-IQXnFvL8%@j18RxD^nvz} zfm9)vkf1&VXjBtas(|7M6vmMJ4+=w2e;H(g8$&3AD}y@&=(GUPzI;e32nM%yLG4P& zsq&!n*Fp1PFjs)=hn3}^9G<}dx>K_he2*ijEeGn4gKBF?ItBR~lm|feD!})G!|Dmx zP1m3j3Y1GhWjAOA2B=jA2`7*!B>zBC2PiB-X#jEZ6le|wq#o3M0+oRfn?NA~s>eV* zS(t60vKL-YWEg`hg9n2rgENB|LG>Uc9zZ2%9>XFAP~3uI7L+Q9NzWkNpfm}pAwcO8 zG9N-p`UTZ_ptJ=_=b%)Fo+3a#h2%C|X&I7s5osC}28j9^vUU-an?X4M6uKZkf@&j3 zc@Nsx4mvvlR1VfL;7e_844&YT`)~$V21kYf27d-n$^)fQP}&2vJRl(sssTYQF_5c4 z7-lahR@Z^YTVeSCvTqb*Gsx%oZ4Y4xWe5V>9|BJOZVVytbPpPR0J#I7>yh;bF?ce# zGkBnwfN(!3#6T?vP?`eeV^BH+l^n43Vxu7jif>Rm0u;NT*dr8T?yi2WL5_ytda{9$ zf#C}$1B1IOXbqYn!wD`J+X#GL1w`DK0aR~6*rp8sm|<$n81z_RY;%SdINO3@7aL66 zlHnCh+|YnQj}6<*`^E{yfASy1~zsW+k&AE&bDN@ z24fo;FtBhy)fgEv6u{X=3@tEojEuqewS)cT$iN`S09EhAz#s#2zcT}a0ZiP5fx!#L zc4c6QV1%l7V_*g4fbRT4tb;Cs5A_ zWF90ZL&jDiy&aIPAoD@v%b+qJG)@Cj2@(Uj7Q}|Q0^}x8>k?E`KxV%|r57moLuxRX z+d=&<(D(;PC!~CU^co-|50LqNP~8CP)r0CKNbLi z5*BEr1=LpnwNfDA4eFhMLL1b2fb<(dCV@tBL174LRfEC+q!#9TPU8+{134W*(V^kK>BYWeV`r_C^kST5j0{u z1Kjoog)k&eL4F7IVqqy5q!!{%P`?~xE+`B@?PySpfN~uu?m=~YDA*)We;ks}K&>2* zt6-r|NFKCHDFb>NEaJonNQ)X2TA-FIsPzP@)j{O}XjKp_Wr9K$6bqoV1zP0-YWslP z2?{|_{#*o}5d@_=P%eVB<3KGs(C8z`Jcw@*rsja>s6it`5|L0l60xgAg&$$TVnd0}}5LTVS(*kdYXWddOHf$Tgr;1F{Ds3JPhE zyCGo;ie1q7B*YBR7!#z22=ON<7D2HB3MJ4;EogKB)TaT>8|O2CN*9O^KyeQ`B^=Vv z0);!MFAbWF0)-(cSAp7gptytdHbHGc$SgWY4x|PYZXnlz_W6P4#z18X$W53o0_8Q3 z-$7{)6pkQWpuPtv^gt$oTFapF0+a(l?giy3kh(YqP`kH?0cJAl4c4IA9;6x+-k=l; zIt>Jr24Q&)Gy(;gzX$mha;q~a6@%OjQU@vtKxqeL2gL87F-}N38FWS?$Yr4T1Epq= zpJ8zday@$K0`fN`HbG?osAUTYD@X|qaz7+>fqaD+!vyg_ArCs!19U!}6Sx%#G7Hv! zMYji34}k0k#WJX*0JWK6{bf*10t!ip?VxfRBmz?H#Q@nw4N5tnv;|86ppXLjE*ZS< z3t}&5Gz)a1G)Nz)ghR-~bYaejgJJ-bT0rF{#9ok2PznasuLyI489-;tg2EMKI;g|~ z=}cwFK7=y+ze@jgT}}}JdhtjW<%0DDCL22KBSI<=!CcwrVHU-kbgnF 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 I1cM&~0G->3UH||9 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/gamecontrollerdb.txt b/gamefiles/miami/gamecontrollerdb.txt new file mode 100644 index 00000000..728fddc2 --- /dev/null +++ b/gamefiles/miami/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/miami/models/fonts_r.txd b/gamefiles/miami/models/fonts_r.txd new file mode 100644 index 0000000000000000000000000000000000000000..b6d50ac29fcd0f4c08e048130d298252922dc6b2 GIT binary patch literal 524584 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/models/frontend_nsw.txd b/gamefiles/miami/models/frontend_nsw.txd new file mode 100644 index 0000000000000000000000000000000000000000..cbd0afc79de7e498630eccba68935b2e56505d39 GIT binary patch literal 2360104 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

>VTzs%t=PY;<{g@Ih%1WIxCqAPkBh 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 W7!85Z5Eu;sW@r%n|IaLd%mx5DPgkG- literal 0 HcmV?d00001 diff --git a/gamefiles/miami/models/frontend_x360.txd b/gamefiles/miami/models/frontend_x360.txd new file mode 100644 index 0000000000000000000000000000000000000000..acb330998aeba28b18a6219925846727f6e05eb2 GIT binary patch literal 524968 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 z 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 beG6(&eEI)<|F8O=GAFYi3UD@`u7=1m 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 z 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

    0deEAAgPVjPabA!eOxPDdr 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%0D3Wiv%1R7#K1HlmrABguv-JAU-r)eSvC& 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⁡t@$;FoZj6*_*ZA{LCx@(75g;wZTLAA3Cz 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

    VXw*Z$Y9k1yR@vUJzGO{+lWeK!<13$mwBm4yW~ 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/models/nswbtns.txd b/gamefiles/miami/models/nswbtns.txd new file mode 100644 index 0000000000000000000000000000000000000000..9831ce549a9ab2adf0caaeb5bf713c83cc9d7308 GIT binary patch literal 528424 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

    Q_SQ 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/models/particle.txd b/gamefiles/miami/models/particle.txd new file mode 100644 index 0000000000000000000000000000000000000000..b0c585f47cf6c871e996f876139846aeeee8e5d8 GIT binary patch literal 2668072 zcmWe*U|^8hror(4KeGTM0|NsK0|S^{!oa{F%D})-;sD`;vH&Xs1ISzl1_uTOMivGJu=)QP{{J7vqhTZ~&=;VUQWf7-T*&Ms^P_fAakQ|DPHdoB7E0 zBfAU4Ms_!d4f7L>Mh^>^IE)7Afnkt&$QapPWOw26C*S}7|H;MJ%tW>m#71@xh>h%4 zm>*y?%zq#n76u?T48zocXk>Fh=E5+@P8ddaA2xpq{Qv(ShC%W$HZe3vJvv6W6Xq5W z4Ra%iMs`2QKQIjQD>@C6N7e^22ZllB!!WWN(A|gbFQNbc|AR0{97Gd~LF&;lx?RZb z0I^~21JN*dBl`#DM-UD2KZu55WOXpTAR1&A2!qUrVPtopyAj04=0}nL|Nj$JR1E{I0957{lq?#1OtvH$=7lY+6CgKQ77 z{UA2XZ6J5TFw74yKOy@QgFoGeG8nFvxrmhS>|ELGA!yWVfRGRpI~t|CC^4^N{TU*$cuT zcYrX+Z7>XSF9?I&55ge7fH25^APn*^$p0W3gh65;3{nHaAUz-qG6RHR=7Q`2VUWGZ zZb5bo*1;WT~M)#lA|NsAq!RY!xd}MP$ z_JA;$<1hGFglxfOIJ|BnyD)PZPZb71Df>;ust z`#~7w9+100801b62DusJeh>!v2jnjh2Kf`@Uzq*g84unB!L3%*?K^SBf$XpNx z*#p8LJ3;otFvxAlZUwm=gkkqz0rGqz{BaW`N8CnG3=odti2g z><3|(n_zAPxf|qu5Qh2F;{X5uFl_bz|9^B0k^_l>Xc&g+1(^fFAah}M!0ZIs55h1v zf!qgjC&w2ZTZX1Njr=Ul0a`0f+{Pf#g7HK<3|xTR`ptxew%4n7cuK0Qn7u9sd9SPb`M%0hs~9Ff&2sgX{s>2eKE0L2dxK z2jngg2DuaDUXa^C7~~I-e?S=IKaf8`{ss9Pgh6bO7)TC;LFzzyKzc#ufH25Rkl7%6 zKp12v$Zn83K<)v#3*<(STR|A)ewbgK{{R0E!!G~-|0f2+)WggGnFlf#ghBRz>;u^g zvLECQkb6Mx0%4FlLGA^)8-zjr0Qm>xFAxU#5#(Qxzd;zp24RpmNFJmXqz9xIWDW>} z%mkSYvIk@z$X<~BAa{V=19BJ0jUWtiGtB)kzqndkiS6~Bo0yoQVY@p z!XW)1vp{Bo%*N+;ko!Px1-TpKeh^0Xr`P}g|6$nY|NsB!7$yg!VR}L4fH25hkoh2c zaD@TLeIR#&+zWCy$o(J;@(ajcApe2<3Gy$p|3P9PIglEVT96(P2AKge3uGq9Y*09Z z!WN z4v>35?gF_F|NlQW43me^FufqNKxTq4 z$b67JAPlk_Yx5C-`hhCzH#*ns3g>OguxdO>D@ z%mSH-%kLn!fZPRgAIPmB401cnAIN?U`TzevF&L%?W(LSSkhvfXvIk@z$X-ymBZmVh zZb0q?xfkSako!UY0Qm>xFOdI0{sj3KghBoX(I7F997qjFEl3YYFUSm#Ss*h(W`n{S z6y_j1L3V@O0CEe+O(3^{+zE0s%>6LGhW-ElA00>h|NkF^LE5_JiC3au3K|AoqdX334yU-5~da`~mV0$X_7;f&2;bFUa2@3}S=CKyn~8 zAhjSpAibdc1TqU`Cdh1%9U!|vc7p5&xdY@Lkh?(c17VPxL2d_Om|sBtgW;(E|NrB{ zFf}k5q#tAs2!qT8nGdoHghBR#><766Tx*$J{6 z+hWP{8 zzj6Qn|0jfz)x*qynF%r z1@a%rpCJE&{0&M2AR5F6iG$=pYC(EHVF$`fAphg?J18zd80039+d%Gwxf$jUT>d0R z9A*a0Opy5?dqDPq?8TN2Kyd_eFUZ{>_k;Wa@(;*gApe2<3Gy$<-yjTPgTz2`pfCc( zJxC8M?m=dO%mk$`P+9|}IZ)aI*$r|7$Soi@f!qdjC&=Bz_>WjMAoE~mgX{s>2eKDr zKgb;*_ki35av#W@AoqgOEGP^>{s8$0h^z7vyhr|AWE^6jmTTps)j(0rEe{ zOi&&J*#WW(WGBdOkQ+d50l5j}K9D;>m{@-is}5u)41??e*$1*0WIxCqAoqaW1#%zA zognvu+zoO+$R8m8fcypWAF}^JVE_sXkQgXzKw$(5E07*g*n!Ldg(b*LP}qXZ2iXO( z6J$5Y4IsCG+yrwUv3?>)9n21peIR>5_JiC3au3K|pfCWr6XafyyFu;;`2*x1kiS6w z1Njr=Uy#3%{SOKoP#A&23Zxzsb|5oAVF@x5vr!$Q)3Z zg3JZk0Sar7ouIG>VURn>^#i5)Kz4)N0CEq=Z6FMCC&;}ZcZ1vy@(0L2Ab)}U2l6Mf z|3PU16eggs0m*~Z!QvjI9~71#^FUz>G9TIhAp0rx110)F_QU)Sau>*bAa{b?3vxHe z{h%}e@(;*gApe2<5ArX_-=H)AqCtF+|3PU46jmVhAbp@P1epU0Q;@kJ^Fj82>;%~j z!j!n5QoSJiK^Wv7SQ-Gi6XafyyFu;;`2*x1kiS6w1Njr=Uy#2+{s*N6P?&(i1|$zs z2TC)bumkA_g(b*5nBPJ6fb0X=OR4**p&#T9kb6Mx0>urw|3Ur$`3K}LkpDpb1o;=_ zZ*>2I(g;W$NIgg&C=5aV2bnok{10*`D84}M2E`x9A0YpL`~~tKA^(HY3P?RjA1?oc z!W@LD5r5P$1LPK18UXnp6kj0!gWM1D2P_Yu`yZ4CKzRX_9zfv&k_Y)8q#hJ@ApIb7 zK<0tW1*I{NJs|r)m>O=Uh8ZBYfZPR2{~&jQ+zUzrAopYQKXMs>%l{yKApIb7K<0tW zM)p57+)XVrVEzZW59CgedqM67xgQjdp!fvED=2h*w3*3i06LF=(D?&7 ziMa*w#Tkhy@njxAK;82PKx?;2#h|q=#9&g*1*wPaC7?A8vWr+uj9&?Z%!b*Gi>786;IacGj?I0z{DSUhTzqW$vDpI>1GxbgrbZaxG8-fgvL78| zb2~PFg4(w*j7=UShHe(RT_8Tl9r!RMVSvkAkiFP2x_d!>K*lit;-it(gV^Y1g810% z#^qLO`yZR#ATf};kTJ;Z$Qaqrp!N|uMwSP$k@bSu$Yz7sAUn}9HNyaxS?G3x_#pSd zFvzXQ7~~gZjP7q_KC(Ix8`&HX8)gTL2HA~{ak-n+bb!qqbh|)&m|I{p$el0@@&gPb z`!nVL|NrP1SstbbM#Ibl(J*^JG{}Bz7@ONk@jo{G*z5th0U3kb2g5M8gJ@*GA^W?4 zgm8eF3!;(j#O5w+?x(i@k=+1dgWLteApas`nBUR;4e~oWhN*$+0nx~2fy{?tkex70 zY#4yTg-|$v)SzQ@^I`VFXqcNoG|asqx5F^ZU&#Kg`Tzev3?qw!*f8}l{U91f1}edc@PcL2Qvpm!^{WS z1;a3RfM}50kTJUdVSHTS0Fy_ikVqqd|T~#xQXZjjRvEhM5VXVRpgn2Dt;|9uNk(59C%5 z2DuxALH>YYbiZQb!_z^2jngg2DuaDW)KGX0fw>p6_*%H4~Pbt1HvG4K^SBQ$UYDT z*$s0G%xy5Yf@qlgVKm5ZFifofLFT|P%nq2HAp1e?0AY~3K<)!!kb6NGb z?gL?vdtn&n4-gIVH!_BW0kRlK9WsWQ12PwcVRnJ+1=$b6AoqaW2ErhBf-uP4AdJml zATeaW!`L8kWDL>|!yq$37-R>?J`e`k4RQy}O(6GyFwD&$8rd(%{sgf>{s+;>7+F1t z4Kf3SLFR%m%r20%mrbP9U%Ka z7-Tod9Uu&I7s!nu401CFgZu!)$o>Se3HcwSAA~{XfiTPtkbNKwvLECQkefgl--H3^Na8HVA|40@(|~AUA;g55k}@0Qnt+ zL4Jo}nBPG(%VUXKE801zEhPfZvPauE8Fh2jo^nz%RSs)BD8-zi2 zf$Rm@55gd~fZPSbAUA?Avio6dbbsRF!_*_wATvQ2WCsX?>;%~jat8>5+yrtP2!q@T z!XUSUFfRYY!U0Ajs{`o+VURf>3^EslLH2+!$WD;`Aa{T;$W0*k!7#GhL2O)p#U&2X zgN$M3g6sidkewj=LGA!ykefj617VPRK^WwI5JvVF%&*vJWOX2YAPh1GghA$lFvuQ| zeIN|7ALI@Y2Du65Mr3z`*tq;iNF1aWhGAxd>;Ykrogn)`7~~d^yFeJ^Mv!|!803B! zM)n&azk}4m^nuI)VUW2X46*}cAIM%12Dt;|9uS7P5$0wPO~{Y<!UR5C)kC!XWcO7-Sd7 zUXcAD3~~?5Z6F%tRv0GaM||=yGhj5xY!C+72f`q`LGA!ykefhm1Gy7~LGA`&m|tLi zgVE&rA7l^6J`e`k4{`?xgWLteFt@^JeEuUw4rUIB2AL1TAUi?!gD}V~Aa{ZM55lzY zKge7V2H69$4}?K>gWLgg6Nm=66NZWLA0ag`vtTsH4iE;}3&J2bfZPId7YKvg2=YG& zgW?~Av84fA@eflAqCt8==72EBTo4A?0kRKdF9?I&0CEoq!`uj?3Hgl}d6-!+8e|U$ zgX{%ikQ+em0l5o=L2d-O7lc9X2Vsz3Kp2+xK{T@eL3|hnsRLnqWH-niAPjO7$bBGpf-uOZb7b)t<@jomKKz@f|kl#TV+5aFmNDPEw>OkQJ!XPt1;R(W^@CAi4D7--! z6#k%i0AXbJk>W36)gqe@vJZqo_Jc6UEg*M+FvyJ{w}RXa!XQ6@Fw9>dKY}pG&oB() z!!S%8NDoLq2!qT6nG3=oJ3#h<>;++vJ3tubK0^Lk{r~@e5Jpx9;)5`<7&bP{4iF8p z6NEu-0J#U`E)WK}5#(NwyFnP_4-kg=3+7Ll-$67?3`B#}fz*RANIwXJ%mbMVG9QFN z_JQmLVUQa@?tx)!?nV{^`4bmLR)>uZvjaqf>;++v8$cN3CXm}e?gY6Pgh6fx`2&PO z{sLi;A3+%8Zx{ygVHl(aq#lGp`a$M^Fvwhx`5=2h7-T2Neh>z^2ZqtzjmxjZh@+bY z;=}9#(IC4)?f_wsn?UXZxf6szZU(s@Jdn8{^FbJ77sy@^hPeaeE@X_$Kjerbn+s#Z>;<_2gh6fs zxeeq_5C*v!h(?kl$c_1<~024^juhAblYHAPh1KWG2XbkUby_vJ>V85Djw| zj3&pQxb(oxM5aM@f-uM(APjO7$bBFTax2KqAh(130m2|Zf&34`Aisk!G5!ac1HvFP zL1u&O0oez_Ap1cW*;c&a!XW!W7};GQHaUJIqz7a!ItJMd!XUSR+y%lQH-g*> zayQ8RAPn*g$WI{ufiTR!p!kQeK{QAXqzMy1NMZ zgo;QbA=?9D z!|VsS2ZTXx1Gy6vS0D^>JID{9xCHqN+>OuNI7-R;>EReY% z^Fj82FwAa{JCHFk{vcNkvOO?1%pD*%f!qgjCkTVw401cj4H zFo+Ej2VsyJkb00lkbV#bnFTTvWIo6q5QfT z%^SFvx6>Juo{#G_qSL@fWdrk?jMqL2dwHkh?$_-$59}28n~@K^UYKqz|MYWDdwYkhvfXvIArv2!rfL#>D!8Qgz67g4_YZ zAa{W<$c-TPg4_*qKgb^-4Du7mZy~2KThoP1jxfd^9^nU;TeKNAzKY#voKX~w9I>>Fv;?%SoWHuu@?#=xC`SWCGd{bmMC_F!W_|X3L?c3>p|NiX+@p0i_zkW?Vd-m+%7cX8c z{{R1f`~Uy{DYpCFyLavH-n|3)1st}x?1qXre*E}xH^?1OK5`t9%m(GNr%#{G{PX8e zFR^|B*?s5EovENWqr?rM^!E1c+y0+Fe}dwilyGc(|Ni~lckkZejVpX^1KIuN&6{pe z8v6V9FV=jGPd_NUo;-On^ZWPjpfZZ2a0l59O6#C91)7FP(hqf4IulVzkK;}?(5gDJ3;0WPz!d`vuDqG-@ktk%4eWFPD-4E z^<%N4|IM2>Q$b}B7C96>pFVwRef8?q?EnA&gX~6;BcvCf{NKNSyT5(=HW3sKpt!}R z7L>=IJb3~tSBc5vB)biihChD%IQjnl`;(E~0Ahp6Hc(pq^XCsqX$6}<2#B?V>Ij${ zKy}2^r%(Goefrc-t#X=x{h)B`1i1lJK0SW?c532?2hz+y*C^Pvw{K4d$%E9<0)x`|-Me?EfBpIuR4<{2dHc_wKd0Zkd2==`+yJs0 zRF{1J{vFiT$5od!|NQxL=A%cCrh?jR)CvQT-H#qUnh47KSlVph^a!)-@87@8j~+c* zeCN)c`P6U&s7wRd{pr&uP??Gz<|uZ9!u`gL8`EFEe!cM5uV1r4?M6!60BW~9fBt+r zv|Pb!H>e!CbLY-XQ22xT6QF(tNFTXw0NMTG#f!;5e*6HH=XmQdkRM;aem(KepFbeA zp!@&}mqv2j0J8h@=g-}L|NaH_PjIC*upQ8}0qPUsYTFU*29Q~xy8GF)XOsW_{Y$Vs z1+@o|(gwaT0=wbGix<;>{``qm9mwt{Po7K!wRisi{}1vvR&}s+4x&Nrfp_oT5lojL zwV-zIuV23=K7IOhA}GF~V)Y<NK}bmGKRSlVfz*L8NFO=|iNi2VJu(fGgV8WKTz0|a(P@}|Tr|3R7#~?Z z%R+iJ`BUeK=Q<5m>v)fGY3S2_#g~22V^hEjUWti zGYEs&AaRg9NFB((6%`f#L3qN13I9PD#0KG%loW7yz{~~d0qKQd5Fca)NFHPkNIfzJ ziGk!mYCsso28n^xfXs(s5FaECl1IiMF&Ku4!}uUQ$QaoykT^&k$WD-3L4F4L9fUz_ zkQhh~q-Mp675@($IPm}3vu7Ci%9Sf%IZ&8@%mldsqz{Hce2^I+yFqGTW`WEDu|Z-W zIgnnEIuHi2LE<1aFbq;#$f zkJcKPXH z3=#+F2Vs~zNDT;s)PXQa93&4?Pm2FRdO+rbFi0;5gV-Q(n3*6xOdKSJY#&SxCJ)jF zk_TasT97;lgTz7ZMaLj;UmWYCw8HW`WEE`3r*gC4TuKuLE<2JkpDn&kInxeF_1h+El3~84iF8p z6J`cT4u(PEAbAjmxgX>vkUkI%l85O7*$vYNqCs*XeK55k{V+aA41_^ufM^f~$$`{> zFiadogUkV$hm1jXg5*H@Ks1OCvL94Ff&2&ZCn&B#c^_8xgV-Q(kUYpNkbNNiAR43} zBnHADb3hox28n~*15yLRAT~%32!rf}xecTqWH*QgVVGW!9++B?UKk%F2Ergd$Q%$G zM1$0TFo+EkL$(8CHVA{A0}LGmEAAU((!BnFa0RtxeM zNIys)41>f#7+D=M8>9|o4#+$Z2H63kL2iSo1JNKc#wl65|K^W$Lkban3 zKs3k>kQm4u7zXh{ZUf1K>;Ta)KFD1l4AKWv4{{p_!}uUMkUo$)5RI-EBn}b-sRNk} z;)5_qEr^C;kUYpOAagQwO3!av=YM(l@%_Vg3in5%NFCTo?w~3vwUGZ6FNd zgXBOoh!1ifNFF2xvL6|P#6WT&w}I4w)PwYaFo+MrAUO~pq!vVj)PTg1F-#4}Y>>ZU z7^DZL9z=u0L4E_pF)sgu;8__YyJeU|r4ahE#dXPSlevmyN8iYY&AUTjaWQ?o_qy~mT zYC&=!3}S=CVdj9?ATf{}vRNQ8kQ_)I41@SEbs%|=ILPlHjII0wxfvt}qCsjw`au|^ z7afDl1!0&tNF4}+^nuJk#^~mO)PXQaEl3U>gT#?BE_sj{Fbq?VOD#wo<|b@@2i1Qd zH6S^VIuIL#Vdf&!FguZHkUU5|2!r&4%zVURwM7)T9>4Z|?=k!g^BkugXPrXHjpgkffZ*f2SC8YG8|(d9vWWO*1H zB#sN?QUenQ(IB_MFwFnR;vhCMMz;&(Uu29d57P%TAI2w^Mm7t?M%RbWT$p-f8pg(D z4#+%E7=UPWjIIY{79otT2gHY&3u5EKq?n6t9wEKh?eq!yrBg z!{lK!HZzgySC~4GUStds2k8Z=1=Y!*b^(Y6@j)0QhmJvdKp12W$Xy`6!u$?mgXBPZ zK^R#thz~LwWCsj`_#kl*2C>n_LHa@FfiQ>-QwLHD!XQ3243dMH1)@P}K>9)EfH27I zAQ~hFk^`v+waGy3K~Vb)qz5F1j6q@`3=#wB1DS#Be^9)G#6W65=72DW4N?!nAU4Qs zP`eX^LGmCwLE<0`VuQqBav(8~+h7>PN0tYf1rh_9jgE1d1#&mY-!Kf~gUko12iXg1 z?}KQN*&s2H`5+8p!|Vphfy6*&p!**r22u-ZH-j)tEl52`9ApMa9Niv}97rw5ZkQZM z4CXJ8evmv&FR~ht90((;1?h#U1(^xrgZv5dFER#+f%JjoVd_CN$S#l=2!q%lcZ2MN z$$|8M{0j3wDDFWR7XKhSLFz&JKxTk2$Q+nBNDgKeNDhQSYC&QkIgmP#7|35B3}S=i zL2}3#BnFZLsX@jdyI|@-W`fj!+zj$BA^(H)fy@P&0m2}+fW$x;Bo49%WCqBu*!&N& z2c`#PH^>YS2I&RyLFT|PNF7KHgh6a{age(~7$lFb7o--X2P6kl19BG(gTz4cFbv{@ z4g7_dgkY7RWL&hL6kbY$QLGA)!m>D28 zNDW9WEbc*NJqUyR4a1=H0Fno(g_#F(JIH>J889(qb3yVT43h)VAoUIUw`VF-RPw zAEXbY2j+i}e?b^j_k(B}9Aag)&1z`{$AuKgTerYLE<1YL25zfg4iItK{SXD5(n`?>Ot`j@*^m&LH-6|P#OSX5E~>1 zQUg*8(ht%LG6RG`e2_UH_kh?SIb;lz1GxvJ2E<2}$HxY#1?dA}kX~f9AUTj241?5w zFvyJ{3}VCF19B&_dSr2seo)-P(f}+BKz;{dkpDqx10)YhTOe~_?m#yeBnJ{hmIs*+ zatjE9*f6_cYVgq@wIDq(y~t`oc7f!OF-RQl2KgVvhxs3*7v>I-88CN%%mB%O#9;Cuvq54oIgnbI97r9A50Zyr5FexmSq(@I zrXMB_qCxtRF-RW>gUo=b2k~KQVdlWX2BZ!o2f`pWNF3xxP+Wuj3-UV%!~72l8&Em{ znGLcRqz^=+n+viNBo4wLb3y7s7^Ds)2hs<^AT~%0BnPq^gh6bO7)UQj4G6=`Ko;v&Z{sZ|F6b3NA!~72t1BDML4}jEy+y%lgb3x)D z4B~_2K^Vk_*#}~S)Pvjt!XSB&97qo`28n^>Kx#l3#0KdFVUT{1TR|8khYcgE2eCnV zKs3l)kpDm!WFLqR5(mkHFvxt696tYp1*w6V53&n}L1G{| z5C-XmVGtiA2U7>5L1G{bk|!4<+W|5IWF8EI_%L~pA3$*r!XUqcFvw3J43dMXL#9FE zAbDgA5(B9LnGM1)yFfIE50V3^fng9Igh6s3HOLqw2ErhD5QgbNra|JwVvt$L7^V+o z9>@K$bnTE-s(=at4v(Yh34w;6jL3bO750ZnaMYjjU zhshB`gUmw5Fnu7iVE#s?LE?lkF7+_8VKgptVDjj01My*UFulljf!Hu{VrZCI$TYG( zka_qpv1*akVPhlPfh>>gE*KkG9$6nc8(AHU4N?PR6QV(CX@NoZ5;7O0h7cxX2C?$U z_7JOwp6Uj>`{)@KBh9YSxF6WP_Za67Ffby{AAqu9=MSXkC+2`p3?P?)ov=-?pOKMqNS#FlaxVJQqgUkhCm|e*BgV=*PyoO8s zgTfhxLGb{J4-f{$4=Bz+acg5^i!q)7D(eyw62avjEdAr7LFz#m); z2eKE0LGFOL2}TdLv@n$89~Pz{8Wh%`Fb9P{2!rAR6fdCo0by7igX%bHV^DelnGdoD zghBR#><766ghB2Cxew${7#=L?U?|2vEDS+3C|p6|3kqjYn1jL|6bGO@3W^tM$2BqL zf$Rm@4{`^{Js@|1+y`%jy3JP0L*$WD1P?&?l zo=_Zt)PV9UNIfWyNW~z1ApIb7h_MT#9%Ki|jUe}e+zoO+$R8m8fbc-2gP{=rpzr}< zPSFvxt6 zJs|sVr3sK6NG(Vo$PAEuApd~;1@a#V(>omu_V@>d2?&G22^3zSa07)OC>%lIi7l)_ z;-I(ysR5}4#T7^&2!qT3VUT&Ca3>do^n=WSVUXD%dqDPq>;>5matAK=fz*K1g7ksR z0GS7J3&^h^Os{k>SmPfQ1|SRy6HwTI!U+^!pm4(%uAqDfQU{6)kRIf)1(^lIAhW4~ zVdjBokUK&4f$Rm@4{`^{y`Z!}Y&rm$19KNW(!gMgf0*Av;Q$LyP*{S(5nH^2#6WQl zk_Ux1DC|LT0MZLG1B78_BGb6S7Fi848=HD$d1N-oPLTZ|cYxdjazDszAa{b?i=Gxh zVlelE^nmn&%z&AR&2DOm4W{^q`4{AOP&k0X4i;|M!VDCiAbC*ug4BZapobmEO!Tk< ziQ&Q^yRl(h>Otbj=7adi7-Sa6O(3^{+zE0o$lahgNB0v*93&6(3rH`_EMz;V$sR26 z5A!E1tU%!a$`km)2^5~7a0TfBg%t?H%s~z#m>7%(sYAvf{U8i73m*ol0byjlAU4cg z7!9)<#s|?Lbs)VU^Fi(g`41G|=xG4tUy#2+@*uS!Js|xc3^SLS^k9g8kl#QU9U%LVF}iy|YCvj1dO&7@>;lC(dO864A7mfM4v>0~KA5>MGpRujWc;m}(WH-z$AQ~1|AT|htqs!sr z!`upTH!MAX`~r$skQ+d50l5j}HV}rn8N>!*kUU5oNDs)ZATwZQf@qkzAUTjakb00l z5QdpW2|W<;4{|#QgZu*W6UcA){0$0AkXn4<2$BPt1yTz#4>>J?;tv+L*l3VEwJ>^| zf#Mb9E^O`xg#ieI+zyflsROA;c00&C5C(-0$X_5eAhjTUAWTWzgY?on{y}aAVUQm{ zegXLjO+T96(P2Dur8L2M9)g&i#3VP=8i6I)z^#6WT&jBFpQ zJO}9q*^Q1t;vfui8wkVng3Q5%L25v1L1uyc0&*kBFCaIA+>VYx;vjjDI*=ZaUXU3u z46+{-Ubw;p6fUGKHz2cM800==3=;>b1!0grkUK%{2e}z!F35ar7$gT$ z15yvt3o;kvc90)n{(;dTF_0X{K9G8lK9GJG2H6L)8)gPfA1G|d!Ss%QkpDn#2l)Zy z7nr|5G{~J%2Z;u`0jzQued5|ALdO-R?W`X13LXi)hJ5(oJih%Q3CO=N_k(C+@;yv1$gi-l#1`+!X2HS)7A_!nfY=}mG7lsV z!XP)1ib487W`N9sVUYRA7$go-12P*FcQAj$;veLG5E~Q*AU}cp22u;sgKQ3n4KfR4 zCrAy*k03oT3`zqa^O5xpmiPy`9fUz{1NjXU1|Yw}+zw)c`~;E%sR5}4VUYVlGzf#l zLGm!YpfCf49SDQiAhST`faF2p0178ic!Bi7Fo+K_2P6kF3#1NYFGvq4{6Jw!EC#6u z=>zEpnFBHpWG={jkUby_vlm2z_#iouI*=Zaevq3%Zio2+#D@6+qz%d8kinX7!reNng2m<0l5j}Hjo>U-3?-c#6WT&H6XPh401Dw24Rpm zNFJmP=g>*n`ADW`M#Cq!wf^NFNBp%mbN83I^!|=?7tu zc_4E^=7a13*#~kr$Zn7uU>GC@k^{LFq#mRXWDYV0nFo>snFCS}(+i_PVjww?8jyaF zdJqP&L1G{|kQ$JBP&g8UX%+t3% z3}S=CVD19>0i+M4ABI7EkQpE~AhjTU$QUFBk^`xM=?Ccnu|a+X*$c7QgXBSe1*wJk z7es@^KxTu&0u&!0^&oxd@dy$FVURosgTfDnLHb}ACI+HG>R=e87u`${A0!S^3v(mL zEy!+!`48kykRM=S1JVb>AUi<%VR}J&kTJ+SkQ_)2$S#n25C)kAG7}^ZQx6JPe3(}8 z4{`&{jUczdFtQs#@da`x$bBFhghBd1egOFmqz4uzAR5F6iG$1qse|bU(I7rZ968=W z@qvtCW`N8A#V^P#5C-XiVUXSE7$ygzLFz#m_2a0cyJ3)Q~ zVGtYSW{@064aiL(JunR7gTz680;vP(fnj95Aa{V&!Z5lXP}t(b)Q*3U8$s>^`4<#k zpfG^B4a5eCf#g7HKx#p51JNK1@+Zi@AU}Zgz`_DV!}NmWLF!=oK{Uub^l(Iv50H5v zGeBm8+yGJ!G7F>^WF9&OnTsw4)9kbW2j=>>^{R}jU2FM*Cb3yt*YC#yp28n^>Kx#nhL1Byw zQ#1ZSc7ZU+t)TD$`5WXW5C*vm-7hdVf%Jgf3i2mN4M;sK3_vtUFUU_Id63yK^&mYU ze}d#d`5B}ZIqX4vkXax(khvf?!OQ^Z1!0g`q+*ah5C*vgWIxDUkQ+c4WEaRzZ0>-$ z0i+M4A7(Fz4{{es9^_Y$yI~l_2Z@8^k@bM|qU!_M3&Nmq2e}C(4^juhu&_p_sU80y zH-g*>ax=*7AoqYUE;oVP2GRoxM^N~I)PnpD3O^7H;)BcrnThNl5E~>0k^|WTQVY@p z!XP$CA1E$B@}RH)nGezfvI}G<2&0DuDSVKA5C)kC!XUFj?g9A$WGBc?AUA;A1H&M9 zf!qXh4@e)#3=oFt2k8ar1F3~!kXayeL4H8i1JegG4-~#2eIN`{4>AYj9+-Muexp|W zgWLkL3*<(STS0CBxdntlY>*g84x|R87UUif4Z?sJ{X3j8;}~1S{R1e2{I4lZ%}xEFo+Ej1GxvJ2BaRFpQ#!DAh#jAALI@Y z289Jk93+qI4iFn81`01&Si!;xrVk_sQUg*8!XP%tERdNXHK6c8)&nvJ*z1UG&BvdqMUiyBFjikiS6w zgJF;ux_d!-LHa;`2g!ry52I+^X2bl}P=wU&v_y@TGIc`Aq zgD@ygu*C<+E$IFPg$u~9Abl`*gUp4gMK&L#4x}EW7L>+7>OuYonF-T_?pJ#ALGA{* z1?Fy0e8Aidaytlv#6WIGb~i{5D9k|OAbF4;7zX(j=1-9MFpRDT6fPhP3V)FOAbF5l z^e~`S{Da&Nau>*bAbUX=#0H6h!TC zkXn%0Aisjl2KgPN7KD+_hp7dbKUguyFCce4EtPM1%MsagaPn9SDQiATf{`AhSSbBKv8uvO(?y`3X6^ zVE%{s3lxqpKY`SP*vMf3QV+tQutWAgNG%A%)Puqw6izS@BzZ(RNX`32-hkhvf=AhjSi$ZsI?LGmCoVSX73G|0WMFaY@h zOty27-lZW zERdP7u%a~nLFR$n1~MOH4+w+UAU}ZQknIAoL1G~FAh&`10rEFU56CYdJs|gk)PZOa zA7mCx9f$_$1NjZ)N01tjT9{vk3Jr2IEF3`o2KgTp4j>F-gZu}Q2dM*LkUv3T0aFVK z2M`8@1t=_$)q>Q6_#kmm*n!LgVUSvMb3x%nZv2Dn0bx))fXoG%53&b@LGA>JgWL^L z1F{E1gD^-Oq#mRW>^_+znC#!XP$C45SVeE+7nIgTz2#fowL&{Go=C{RoOD zP&k162%Of*3IZ$|j zFo+FuKTI9S?L!@d{0fQ-P?&)H2J$1wub^-Ng(E0zLGmzlAiu-H1*8UqL178f4+=Ao zIv57=LE<2DK<0ulhz&9mBnMIl3O`EYALJg88$jlRFvuSuJ7Dex*#WW(ghB2H$%E8^ z+z!GZy&!kO+yv4G(hpJxl1B~?WOX2OhXV%r7ZfhAa0K}mgh62h5`%>cDEvTdP#A#1 z3=}pX3{nRQE07r=H82b^7Z#2nH6RSqhi)dh@eeW|WGBdOkl8Q{;)BFN@*uZ>;s`{8 z_#pRy(jds)Fbv{@#6j{Pb?APCg$2x9kQ$Jg!v%x<1M)j4UO|2Yg(Jw%pzuKt8<09s zSb@Yr@-PhIgTz7dAoD;N#0JF!D84~v!Z64VkT^&lq>h^L4@+Ml^Fd~VFesitGh}W1t?5F{sv)Ccq027 zIeb9sKw$*J=;}b`fiNh%K>9)QAoU;&3R{p}AhSSfD2abingf{!G8cqF=@4We$X<}y zpg022APlk-=0A{|K^WvNWc467fy@NOH^~1W3^EsF2S^+w4>EJOVNm#h!VDA^pfCja z8-ziA2Zb3Z>_BQj7^V&scAzi>VQlI^YC+)z3OA6MAT=NiVuQp$av(M2#6L(MNI%TI zAag+&Ii5gjK=A~kL25yM0Lg>Yf!qwj=xzb21DOf(8_5443}S=A0OW6wSs*iq8wU9c z_A}%^FJsIK;a7t zXHZ;%)PQJYb)fhMVNlqD;u<6mQU}5yHnLifxghgEVGIgukQ#F1A7n1b9FTd)=7PjQ z=7KONo4&)kqz0rH;>5m!XP%X8$fOaVRZL`^nvt)(h(@`K^SB%$PSQx zkU1dphARg74;BU>KZE=Z3j=~{$V?ChnGecSAp0nZe`GU3Y*74x%mmpB zizg5pQ3Fw8uVIh4ddD6T-}fiTESQ2c?+1lbFUBM=S3Ah(0u z22uxdD+q(!j;tP}55xzR3m|`kFo+G(HK$r6h9y}APi!I)Pdy4jek&G1BxGzc_0ij6XYh4nIOAi@eksI z>;lPy)PdXz!XQ3K93&4?528W(L1u#d4ZL2WH%tY6WQM&HYhAW zVFFS!oZ}wkH;^Aeeg*j%T7gVca%5FZqVpzs8#L629EIuJ%y3sM8Z zAbq3pKd^VZgW?+NBo&P~@0JSwhZUeay*{@j>Yl z5C*9Sg#{=~Kx&3-yumYxFFi1T}A4os4nJ}}58x0BrP`rTr4DvfD3_xK83R{pkC=5Utqz)8TpfCip5LFz!^0a6DlD?#c&7{msNf#g7HKw$); zL1u!&6=WtTJVBTm@efLmAag+GfiNiE(CtTd14s_!Mvz-U7{msN!Q2aSE67ZcJ3;a= zzk}=mxgX>Yka@!ugZvK)3s9JV!Up7j5C(-UNDL$gQUk&uHb@Ml4ir`(3}S=CK;Z;Z z0}3Y)4O0t>ACOrf3<^_FScBA(oBu)jLFRzW17VQ;pmYin2bl}98|DTOALKTW8$fOa zVVHW5`#@?z7{msd36cl-9YllD14u8(43HX-nZpr-{08zrC=5Yi1M)uzgV@OaM^*<4 zcTgCCFeq$c`2?f}6h;c(_>}HVLL40KOAayV^LH-9}kUE%qL1ux>9BvroFIaej;t>>|AislXP#A&24x|Qz zL16?6GnhJ%8c-O))Pdwd>OdI82I&RKfz*I7C>?;p6J#Gq4Y~0TG8^U&khvfXauY}l zBnL7Z6i*-;ghB2A*$Yw!ax(~n+yaVkm|H;lK>9)EfaGCe0OEtxf#gAEfy^CF8005V z_`u=`Ol1bNI%FN zkUDbW9~Ac3%!b7i%xsW45C+);5(C*w$lV}yAisdz3JL=d2I&Ebf#g7Dfy^8(7~~&N zSc1X^6i=Y|1o;_+LH-7bgXBT#Ko}Iqps)ai4@fNt!_*;%4@fPD4~lOgjZ`~*@1 zGIywBkUv0i28ugSxPbf#@-GO3!U);FAhjUB!{QU97KB0SKzc!O0a63QAag-tATvN} zU>K$rBo9(cN&Lgy14@4|^FeHoe_-ar;s|6HOg$)_gWL|nAblV=g4_yH1HvG6ATf{} zC@er2#0I$?o7pfsh8hj>E6Cp4G$ZsIGgD}WlAUA^C3NjDmCJ+tc!_>jT z1m=EFn1R%R%pIy27k0YQ2xZ`KTtS<)PXQ^Sb+Qw3I~uLP#A*3 z5~dd9e`NKbcmSyfVUSvoeIR>5YCssIp4|8c=>?eqvKtgnxafQ!2gNJM zT_AseFvxw#egWwLnGF&H$$`|s{0#~-m|sBVfz*P`9|{=cR*;)P{(^-8$X_4~@*^l5 zLE!^Z15*nM2atc!!vUlpSuM!_AUz=UAoD@t1PWsi2AK;I1IdBZfYgF8rST7PCn!yU z+yb%(hCzIEw}I>f*$r|7vVTDS2I+(80m*~ZfiTEykXn#DD2zZDq#ooSkT^&l-ZUFfS9&;Agc%2F&Ht--ypYw{0Qh`(1E~jLkbgm9ps)b>5u^^;zaTl7 zdXPV1dO%?WG7qE&ghAm2vKM4OvU-pn5T-Q#L1w`02Dyn?yJ31j?uF%RkRL%9o0~yy z2l)Zy7mz$i9Vk3N801e-IDzCqYCvHJG9QFN?i&aUvK!=PkXu0E0&*M7pCC7bFvt%e zF;EzQ)PeMXFi0QBA0Ry-zk$@j)PwwvtRCbybUh$7AblVViVu+eF!Mp`kukO6AKA_5 z_M^KOWIwu_L2d>46NEu*CpNfYgKh2r>&~F35b4Js^L9XqX;Q7=YA)Fi1ZrJkZsH z^uRDR;vZxd%nhKhC)N$fdO-dHxf$e75C*Y9VxVvU`3M_!@>gOJ{ShM6FKfd`al?_7g--j zEl3|IJV0iF=>z#46doXRL16>31Ede6AA~{nQv-v{0%4HZ zAUi;If$Rmj8x$9~+y-(NNIgg&vVM>_NH53_FufoeWEMycqz9xP8H2XKgev5n?P;>#TT+$K<)wg2_z1Z2dM+82VoE!wFaZ&nQC$SjbXKxTm43epe5AU;SOBo9&x z(+8qKe2{q{b3x{V)PgXG4H5&%fz*K1(j)#sc7W_c_YcTTAPkBFPfm zVUQY-T96))UJwSE1u_$4Hpm`y3=#*a0jUM)2e}vIR}coVLH2>n1GxvJ7UWJC2Ju1S zAUi?oKzfidNDQPGWIjkO45RA-#S1P>&HN9t1Li(b-G;0WmS+1&9XmLE<1k zV+%`=7)UQDtntMO%q)<3Aa$Vd1DOlLAidZyOb$eY(ilh|NI%Fd^f(9cL2@89AhSSj z0)+=CzG40Zu|e(y`2}PqNIl3b7zXh{;vjjDUYI@@4H5&H3sM783&SA4pz8s}3ocCU z_y^eoawE)tApe8H0p>ms8zctuJ4g*kEl3XtgV-R9?l)MNfM}38Fh7F)4^j&=2c#Fo z2AK^C1CTwSxB}?`=>^3LHVl#jsUZe~)M7IOBnFZLnE|pBWH-nSAh&?r1acb)gV-Q9 zgWL|12l)l02c#EHn(1K9^s55wsCKzayaTE;)f?I1tkaw|w2Bo9&t!Z5dj*dPp& z1BC;~U!bspg%OMf`5Pn$G6xjiAoZZ|0qKQd5FaECG6$p%6hEMF1DOZX55q7s@udln z8kl+z4a(;r`$1-c%mvI}G<%zh9J;)BFNZUm_V=>h2nnTL!)ZUV`{%mL{IVNke$ zXqY+3W`N8AVUV9d;vjoKYC#yJj}WF+{DbTTxdG&6kiS7;1o9uqtuPGoD=s&K^nx(R zKcMgcsRikWg%>EEKx~j%pm+e84>Aj67syVKUStds17VOn$UKl*5C-W3VUYQtFa((e zvIiZ5?Qw=ira0H2i%mJwbnE}Hfy&$te_JHhz=>zE{glQT7AiF_s0l5j}FOVBSZiTrUM1%YS zk^}h-q!xrh`al@O24RpKNDW9oD1JctKw$?<10Xg?3>3B?H6XRHumIT&G6NZd#9$a? zHVA{%gD^-hE)0?fsRdz}eq?iDY>*sC4M;u6ERdT(ZUeay5N zc!s$bMuWsaeg~-q=>eGmG7E%3{s++@F_4*{@ByiXnFZ1dVuQp$av(Jz^&kw=ODv{! z{Da&9auW!H+z4_j$juA`Nz96$fVGJ@KWDdwYN-@YBka-{sG8<$M$Xy^iL3V@O0KyKx&Zng7g!M=@I`Rx4_&7 zax2KqAh(130P+hAgTz2#2vP&`J4_EqKM2G4AbD7LgY>}60fj9noIx1G24RrdAbC)H zfYgG_2iXtO4>AXaLG}@gVR}GjfXoA7kl7$RKz4!b1lbM3Ah&=phz$}4$%E8_^nmn( z%!2s~*^eN%faE~xK9%##0H6h`~gyftRG|sDVSdI4{{UCogg=Z+z#>s$S)v2 z!7xY+bOp!zegnB3?ps>eT$n79IL3V@O0CEe+O(3^{ z+z9hK$n78uzEpnFYfjGeP1Yd5}7oK9Cud;DLyLkb7b72l)l$Cy?Jj zeuViKM1%OSyaNkEm_Cph$QULL3rCQ8kh!4n2bm2DPmp;ab3vF|=>TLF2!qT9*#UDe z$Zn7uK>h={3Fba@H-qFrYCvj1ZUyNFnFTTvhCzIgA3$b;)M7IW*<4cC0~P-uH^Te@ z@)O8!AU}fo!Z3fsXi&I<=Rv z8l)G5L3V*K$UG1R*$cuTJFsDp90-Hdf-p!g2!qT5VUYVlcEId|(b(JsQU_8G(g!jN zWFN?X=>7$T1ISL0Js|bi%tSVy9QHuRKgi86e}TdUUzmWx2RTeYYC+)w(g!mGM1#yk z#vn0d3{sDbL1ut3$UJ-)qy~hM^@7;QW`o$c>;|a?=?B>Xig!@lg8T#W7cvH=1yGs* zsR5}4=>wSy!pLS*gFP7HALI{^-#{4TS5O#%(mX7DK;eNNKA>;|$-}}3qz9xIWCk(@ znT?K-<_r! zc91;C-5@<6{UCE-<`U~Ja@7sS_=oug76zd30EG)Ed_ds@3NKK&p@$`i4-yCEF_0RN zT2NX5=>x?H$P5q$nFqr#J4m5ndO9)9 z4Z%lINhsVw@c;^YkUEfh7zXJF#UTiT%)^F3axe^1 z57P^xLFR$X1(}ZvgXBTwKS(`DKgbS{`$7Hy`3L4VkU!~-hf4gz!UYyqpl}0)9|(iO z5)?n6umy!NF>wNmD^T2kFerWqVURiyhUvvegVce{1Gxv}E|B{MQ@q2%bEw5X%-yhb z0HR@O0hA6v7!=l^Fb9P_DQN*;d=euEigS>CAbUafgWQ2D&Oz>m`DGyKp&I|NFaw1n zGDc4epzsI91t?CCF({ru`4bdp#KbL34M;smA4os4c_22(9*}*YGzh|=vI*oK5Jq+* z%4TX8qCw_@FvuPd z2H6X;9~mRNb1<=oQ~V=`D{|O_*q}H;$Dp_ZVNl!=i$Ur^7~L!oA7l?QMt09&We?Z* zM-Fe~IKjpS#T~I2n|@?5WP67Ko1FLu`FAMzZPbl~@4h4_{*m>JvWHIyfZDjA_APST z7Q_aL4If{Q`UF=9fZ`d1L2(Xi4};hsF=-)G%OXfPnx1nFT}{7#KAfq{WZkbxn+ zG_N=}KReYJqJ?yl6{1CfL4iSmk%fUl0L=eSjXoSRGc#4pA=fRSu_b!B0c4ksj?RBo zRn`AOLPG!L<>mi}goJ=;a@|jiK9K)xY;3@Gf$Rri5Fds??jptqFUV~m^FZN8 zjQt>WAiW?AG6#e~Y>>UMFb3HRG8bew2!q%l46+|22eJ>O59D@`UJxH-F0uB5^n&!k zFvv`h{UGx}{si$s_QEi-I7l8O4zd&3eh`~j`$2j^c7xmjG7n@v%zlvF=-~ho1E~S2 z1=$S>3y>M0@CTU#Vv}k=$Q+P9khvfX;)Cn~*$qnnAR2~2@*wwt><3|xyFq$E_9L^2 z@jplnNH54fkY144APf=%*#WW>>3Fo+MbAEXzg9z=umg8YPRJ~3>N z8$fXe!XPt1W`XPnnGdoDgh6(LFh~r9L2Qs3kiTK}gY*+)A2u~GcY|n4Fbv{@Fvwn*I*_}unMVQv7q1LR*A4T>ue2FZimK(0Nw^nt<>0ns1~QiID3O2uJe015|?J75^Z2g!llNU0qoNk7K;g9Fm|1C$LLe@H6L z$w@8A$SeVmLV(8(NF!Jo7(nA14h#;E@dpP6hMt}trcpc^2BT?UG!2ZVfzdQDng&MG zz-Ss6O#`E8U^ER3)-=%H-_J5w-A0MKK>nICWeOXJ24QrwU}7*DU4AI>VSa$oAb*4K z?Afz9kZF)Sh>wgxVnfNV$ZiF>9p)Dp8$^TrJ$v?So<)lm@xk!y*|WKz7%mRtgVZCN zIh5ES_k;WirNRCKg$GD%(V|5Ht5>fULBc{H_LePMB(`kXA_iiE!Um*%DES%WR*;*a ze&hrBWA*CQAioKL{0tHYiEY`kMQYEUJu-Xt?2&@9VYD1nOl+w79~9qEKZ_vy5gM;T zAbQW9J#t5m98o~VAb*3@gTe!(W{CSA?Dw;0&#Hhh$e&QZfzvlAorCyCjvP@sd-kjb zhz9u|#`5zSLAb*3{pmYK9J17i5YC&NF zDjPr;WCqAAP+2k*{0(v=D4sy>29rgh6ajcz`f096)NIajyW<1M@$qjsUe0 zhEm*v+zN^}kUv22d;R)#4UoUzzI|&3qCx%#iGj)jSQtS4t^~>tAbp@R0~CHBb3kDU zGIOxUJIIZoxB|HuS)&i*mr2`NR%MT#^&@d2! z`F}8k0jLZDxdVh@@d5KYD84}$oC8A{0~h7@NxjEMhlb|Kx(111~e=b zKw$|=PoOXW*#k;nFnj4ugZvD#ABwqPe2^PKbs5Od(0J4U`2#85ExvyJYV-B$SF5*g z-&%oa5C+LZ{SQh9dZ6$Dg%v0qK<0qL5@a?kO@U}wSb*%NxBo%m53(PGVQvAr59CfX zziWZ~0rKPT-@hIH|Nrmw|Nnm%5Dnu0{{7nygkk;#sRgAAP#A&22NWi-d;o19i9u;_ zTL@IPfZRaOFaX&FvJ+Guf&2_}4>YZV>TVDQwQoUj4~k0*kbnRG|L^tx|9?Ly_JQ&} zKyo1eg2DmB{{R2K8;Ayl4=7zg(?6&_0QHSf`UfC8LH2{(Lyt57@;fO1f$RgtJE&X& zrFW1!K=BSM!(r(Flzu^R5AqMx-(mm%{}1{9|9`;$|Nn!aVxVvV`56>8ATf|U2)lv8 z1zJvm%0^h2g7OHcTmbP^Kz4!Rf|mY=m32_R3qbu3?#qDECn%4C(kjTmAR5F6VNiO8 z#w*BwAV0Kfzlf&?SbMSR2GBWO6_m}nF%r*=5Hts z9;X4tJ(B-TKz;_f6YEyP$Fhl=net0F>52ZiD(Axf_y_qD#0QCk!UdEbKw==dy#N3IgVe#qKxx7Ul!rj|0SJTA02CX6%0`g? zL1_`>K2W^`axX0~EDb=*J#ak)vKy5Cp#BG?bx?eR{0@p+kl#TVh>y6O^_= z{sz%7@hqsnVKhiBC>%gI1Qf2IaukF?VE_tiQ2GajJ;)89_yD;JR6c>yE6o4MY?v51 zG{_uK8UTeaD4ap|fy#c6{UE=C@&L$P(7X;R>p*EAgkfm`l#W4h3i2-ugVF%Ve=t9% zpoIZQ9E3sYK<2oB@&GgpEI@XG><766is54;A{&_Cfv!*$J{A6@fb2txd(gPJ3@q+JZUkXMej!I56sDm3kK}(fX#9iP zBcM77RQ@@E;vWgyLGuGB3_$+%0fi9=L&ZU91SAK-u(SY57a;$F_#h18gD}|tpfCs3 zyP!M>!XUqc;)PcJMh+{G9U!|v=?#?jKxGiLE;0e7e`vY~r%_Nk1^E+{9zfv%@-L{2 z1hrY9;bsC72dM$61*!Z0|34@kK=}oRL40tyf!Y)xbs)VUyP>Lz4&(vl4g z0|BHk0F7xGfZPW4ixnu2K;Z)NFDP$-*wA#V268*J>;UTnl^;+H9t!}O0SXsTc!9zM zWCln-$Q+QlApM}W2Phpv^E?kIZPL=u=w^e$8`S0kxdT)dK;snD*8`2~fyTALeLs+& zL4E_p8B(5=0i|D1`2nIq{sDytj1N)^(+AZH>g#~}lpwV*e}nuEN{gWQKzARl`JnKI zmIa`BD=CoMLH>hcP@fDujtvSIkl$eb1i2rSZb4xNs}VPoO#`E8U^ESkrh(BkFq#HN)4*U&1Eickker{BlUbaZ4><*qw4(>Ws9$^ z5*x}G<{ywBKx`O2Y0@MH5S}$_76S-_*w`>g{ZPf&`~wODkT}TCix)3ufMFPa_3G7d zF_1ct`l053kefknhxrLa!!Sr3@W%>cq6H6T8ShN&H@{s*}qcAoqj(0`ecopCG@3Fv#y9J`988VHnx}=wSddcQDue zFh7Ih9o_FBKZD{O;c&e!yq?- zFvwjXcY@pt!yx~F`~mXM*RNj-K=|+9zZD=Fgh62e!XSB&IuHi2VPYWtuyh2<10Xv< zGzf#(APjN`J;MNG7syVSpFwT`VRXNP!T{uckbgja{r~@e{r~^}+o2f5u7`yM$iE;A zQU?&vCBeOwbAp1dXp{2h;X2bjrvJ2#Y zP}&384T>9(J3#J&VGtjLL2d{60pu5`zZd@h{~v_GeuRpF*s!nx$-!_tNFOL2fWieg|QYzd?3^XpsLwc7xmj!XP(++y=rR_rfs9{m{4t z`D^3<|NmG1|NkGB4nTefu|XIl50V365Fdn5!T{N9kbNNkgW>?>CXoAR>3@))LH58f zvj0JDfw>LjUr@Y(@;i)%`WNIcZ2kxN8-zjfJOBUx4^ji-gD@xz>Op3L(h(?3VfKJ% zP`HEgAjnN1_t7d0K<0wX2iXJiHwc651%*8bgW>_?Hjq0({)WakC{2SfD2_pX0r?Ar zLH-2sLE<3)f%qT{k~{eS|9?;zfcPK`QdbZ2JID@@zhM|;FUTDr_ki37!t_W3Ap1b} zf-tiGq45p#Gl&LZkUv5G0QnE(Cy<|E7$y$#Ge{mrBeVNK;R^CQG6tnNQ2qnC0puP~ z+|b+qFu#M+A;@hYcY?wIgrVsGRL_CJ0OVhg-$DLG#>oDJu|Z*gj6v$bbp&!c0NDw$ zALM@!hJ^trt`2c-c}7$Er{mIgq6 z0{I)n2ZalW28qG^3u41COdZ_+AbUVz4)Z@O3_$S$iXRZBRs4g(7vz7Cogn)`?f}IF z$W0&&iW?9H`5lBo{s+;}@(|`%kbgmW0+u&G;-EN&`58om{0+h&agchD8gM-TvJYf8 zC=NjG0AY}uKyCy19fWD&e~_6VJ3w}U>;$<1=6{fzK>i2$7a4=X0>p=g0m#4LvK(0) zNe<*kkY7O<#D-xIAB4gF2jw+%zr*4lnq;)B>A3{nenJBS8hkUAI!$%8P+3{bd$Fo+G)529gy2iXlu zhah)=(j>C`=)uMp1|a{y{08z5h!1i%NDLHSAPfrw5Dmg0IS>Z1L26<8Kzc#qFbvWQ z^Eb@zAh*!N&**l5!X4xWP+Y*=2ctp$fU!X|h!1i*G6v~G_6vv&!XR;wI+z|1ABI8l zAUi>JgYZE49TpZK`$6u3xeu8J`2m>^Vk3)z{DSNUkXaxM5<|xzJ75^3282OwfYBiK zKw*#@K<CJti5FidQ)(IEGM`~>nl41@R} zab%3_Zjd-Kf2gxT{sH+Jgh62dqCsLq-Cv}*ALL(&U|@)WvN;$S7?=bZ z7|Js;OH#oH36M#!Li7nR2rwuxvM?}!&Hq0NMnhmU1V(5GV3a=&NaYWd4J&_&Qqyu$ zlS?x5^9&3jB?*ZnwES^kaDbFQ4h#&sy1M_hw6y-~=;-{{(9rmAY;64B(9rO|xw-j& zQ&ZFb1_lOTzNV(;e-KtuQu?o|stVSludn|fq#vXnWCjRpYis`pVUT%xdV2rO%*?=M zSzBBGx3#tXZ)0Qg-@(D*znz`ke`jZBuo#F9vJWKh<>mE1BO~K~Q&ZFb{{DV2Zf$M- zUszc9KRi7Azq`9TSU*TD$bOJ}L2d-Gjf{-`gX{#cLGA*HTUuIz?Fk47_#Y7w0mh-B zq5nblSXo(t{SDFsQVViFNIi&cVPOIGKgb-Ac`!eL{H3n0{$EW^4ICbzFacpun1IAV zSVctz94;WWFmpie1%&~~e2{-Zd{8(zIy(Nhx3>p}3rGya4+#nRpP!%qzoMeze^F7< z|D2p0u)Dpzy}{~0VF)q<6t*TNCjUY11^FEm79jV6_@KB1#S2Ib9R~A0!9zC&KN3PNN`q zgTeu12gn~FHpso8xBy|0dXQR>I#3z`g@K}?;(rhg@;^u%qz_~_$R1F*gVH@n56B%L zvp{|ag$u}kATbahBoB%wS65eX9s#)xlukkU50v&mc7gl|@-NKqAR6ROke$dh$bOLd zpzsBSBghVrTR<4(2R}c*|GvJy|3Q3zfB*j=F;IR1iGkFE+zWCO2!rIJqN4ui=H`O) zX=Y|7IBzB%ik z1?dC18{~eFS#ol6|3P6ZFE0-+t3YC)FagCk2!r&1^n>((*dYBN8e|8^evsWDKY{!f z7#R2;gkxi4|ATOJbTl{&Ky*w@%>R^>l>Z3{3I9QU1*JbwdIZrR@!Hzj{}U!m_&;OD zjQ?}y%=tfk`t<)TEiGX6Ah*K&4{{sGFCe=?{so0I$nN0a;Qt`=K=y;gKyCts0Z1Jv zJ%HQ~(g#Ya^GBW?Aq@?~!OG|^(7buN^${tWU2gNFNKov$Mf|1eG%d1qJ^>@dzqkLH+~byu3Vc9D~@&$;tmgVFJ<% ziub<0zW*y$toXle+qVB3H*N&8dwP1nYCvuQxeJs=LGA|m1r*PqI0yL~l&3*q3NsI6 zCMe877!)?3I0CsJ6mOt33`*mmv?MDl3-&W896)@K9iX%TVuQjN6jq=x1BD;R-=MVU z>FEiMQ;?k?dq8e4D=Yh7Q&aQ5zP|o{RaMph#>U3~4Gj(dOG--q$Hm2g!v^GMP?-y= zhd}8XBn}Fj$jHe5si~>|LG{s+B}@Kq-n{w$`t|GoPnw}RXSiW5*g zfZPI#Z;*bFJ3!$NG9MJ~pn45NXJuu9?T3W{C>}xPfx-|JmY}o_D$78AgoO<#?m=>( zFap^Hk^_Yu$Uabd1Lb9qognvv;uGZW_V#vg+6B286lb7x+||_u4hN8bLGA*@Hz*81 z`2rMYpfm~!OHdqw(kv+LbaZt5pE`9aSRW{iK=BNU4+#m0|Db#ds((Q72Z|4nJ3!`u z+yU|@$j>0R)YaAf2blv3PY@dv4xn@ZvICT6K>i2$3zVinG^p$Z$$`=jC@+HS1la=$ z8<0FG?}5^fkB`rPke#471KA0RGf-U$vIpefdGqG|U$9`o|Cuvqg7Yva96tASh3Q{15UwC=5X5GpK$7#XqS0 z0i|h>TR`ax6c3=V2Bp7s>(>20c<|u=!-o(52e}E97D`J?!R0Z?{UCcmX&q!2$WD+s zp!^FecR}t3`3n>$urvU&4-`+JGyu{MvJ(`SpfnBA*Vfhsj&o4iwPC}C|EpK82DdLk z?y0P-{11xHw6wJUpzsH!F;KjL+z+w?miDaSw_Ikl(LgzyAO7<;(x~@81tDM?m2N%6}mDgTfwU z1}J@j><5WeS674mFm2j2aQO_14^Wu{vL94efXW+CT>?t~p!fsjVUV9d_JYbfP+WuT zUc7iQINgEDKv39#!U$wOs9Xm58&qC_><5KADEvWT3-UK8FM|9F^EXHgBo9jKAU}iL z0!sTJHb@-g7Eru_!W85$Q2c`OAt>*G{D0-jmH(G6UHZRs=T2}s1i1yI4&)Y)UXcGl z{s-9u%6p(N1cd=e9mt)acmUM}pgaJw6QmwgPJq;c`~b2SlutnVkn>(|Z}0!^?(YAf zbPft5kUEfkpn4Ld7nF}cVF|MvWHt;m}_6c(T~3d*Y>J)pD!3L8*dfXZ!9+=0YF zaRDleKxHbZ>;ttQCQX_I&VRdi@BY7Q*RKDdI02O+yFv0GzktFX)ZYR56NEwe8&nU1>N`-JgWL`(<3Q;Y zp|s<%L9f$Rsl5fsm$ya=K}?gqILgh74<`4MCv$Xt*)pg0DZ z53(0z2gvUrd64;_cmU-uP@ICw4N!dqvJ)f^G6&>Fkh!2R0);Qgeoz_!m93yS0AY}S zLHPlc2SMoqRJMc49Z>v&(jv$_kUv3w1Ep~g2Du634wxH3=?xS|APjOdsQnGH8{{8Q z_<`~Q$PJ*p3`!56_yCy=3L8*50qFr@ka?i83uG6l{05a}Ap1c62l)r&FOXRvb3pz9 z`5jbWg3=O*2Duv~4@!R^^&od4`yUiOpl}CaP&xpWGoW$_)OG-cAE=xL#SbXHKyCxM z9aNry{0j0j$Soi@f-p!NWHtzc;sS(0vrUNDZh?0Qnn~r$O-s@;9hF z1&sRg35hR8UmFgAUA{RNl^O*lxCZooBxC25F`h(2b2au z{s+Ye$W0*oKw$<-)1WW_r2`lZ@-GO3{0uT56n-ExKyCx+1;r)EJdj&pW`g_(D&s(Y z0htTJp!fvo1Njvs4+?vb{h+i1ayv*IqzB}0P?~~;7bu;8{0+jOzJ;KmAh_)U;)Co3 zg&`1gN~@rJyKv#c|DgICRF}55w}Z=3kh?+d1f?Ai4Jt=K?gF_Fl%_#p z0K%ZM4diZ6nFEV!kX{f5r8AJ7Ah(0;2gMJ_ED#$c4pIXOFHo5Z3QLf?K=~MCF32p9 zJje`?|3MgJ4k!#j7!(d5zk}i(ltw^)1jRKdje}?r5fO0wgYr5koq_BGg(t`lpmYl= zn?U}bJbCi}?c2BiKXm92xDNp84}sDz$PSQuK^SB<$i1L+3<^I`o&otAghBZb!5VMYSk)m zoxODFQgHtXl%_#`0l6I%exSSqax+K_lvY9M8{}?~|3UEwawjNFg4_i1H^?rKUqNLp zDE)x+gWLf!3*=5vSc2RE(hG`nke@;2Cn!EZbxCn?F}VE(N*5qIL2(VjAiF_n4Mc;| zJ}4|e_QCuQvJ;d}Kp12nNDkz87!48!g(1i+P#A#f4Nw~$R9A!g60mkNC?A9JI!Gh)x15jE4nGH%GAoqgA zL4JU_4I~E2PoQuEg+It#kQpF*KxqzC?t$_j$ZU|gAiW?r!@>ZR20;2iWgEy{AU}Y@ z0>lT|36ck?1*JpKxCV#@`5hDnptuM59pp|>S^>o)D4aoY0IEkpbq}mf2Z{$!S^(9d zpfCoh1^F4&o&w2%+FPLhDyXjy$}^y}42lC#nFw+ps9XTWIVg>Q!UW_`nEOC9NDM@S z)Pmds@&_m%gX{u@2Z#;w6DX`eVFrqS5C)|mQ22t(2g!rX1z}M5g3JS{1(iRbya&Rd z{xitmApe8(gVF{lOhN7hVh`ghA$k z>;uIW$p0WWgTfpX9x!u2=?+vjgUSPtzd&IFieFGzfciV2z9p!yaqQT!|C=^#`VXqF zKzSGxPM|mexgTU6D4l`K1o;`{e~=qN?go{)AbF5lko!S$pzr`;5F6w^P#lBuGRWT` z3`@fx|AEplC`>?VKK*|Bf&YAbeE)@ng#L?(iTwwK2`C+a!U*IJ zkRL(m4CHrE+5?G$@);d|APma$ptc9d{~!#?1F$pzG8Yu~Aa{ZM55l1M0Hq6% znIQW>aRw?6Kv0n!J``=Gc7l>?x31M)w}k0Aep(mcq$Ape7Cklmm(0rER2t%Kqe3mG_`>5#%pWnFz}3AUzWQ01xgbj|AW#iDBXba0w^3nX$F)&Kx#p05)|GbJ3)O0P?&@41f@Gr z_<_;~DBXkX0fiqZ%s}RW`~4wHZL|2hf-dD2za51IV2q`#^5t;^O)b!XW>H;u7R{kU60E z1KAIvLE#Bf5Ar7{jzN9~`5%Nq?g7OeD1U;?2IWOiIRqMi1C8T>!T^-+Kz;=I7vyh{ zK2Ul9Z2}4#ko#fj22>`1(izA< zptKK)S5WwX{0Z^{D6B#Dfx-o(4`e4uJxo6=&w$DdklnDb0=XUJcTk@j=sZw zg2D+jz6zQzx_9s1|7Xvh{Rh!ow{8W`^MlL;`2%DJD7--WL1hHUf1of0`5Q!o#9(0o zDn~$R3#1+tFChJ(aDZWu86a_x9#Gl=xg8X*pt2s+W(1WBpt2Fvmjk6k(0C-MUjrJO z1=X7%|AEpF$W0)#Kw%E5TR{00lz%~W3n*WK!V#2@Kw$#P4_C(P}%^Q1M)M-ji59I(huT;!V46C zAU}ZeHK@D=wSPhG2hAaX!U7a8pmYz?2l59~% zr6o|>0Oc8w9#EPEwQoRf0Qm)EJ}6#6egpX%ls`avL2(PpQ=m8jg)vAB6h0s|AU}Y@ z5@a{Xt)O%ZO6ws1g5m^(L2d%s1E0htTpgY<$hD7-=9AU}Y@8dO$*(io^L0Hq<2Ss*h&Wf#m}ptKJvpFwp2sC)y( z14tf}UP1l_rF)RMF#m)62QnL!MnL`tm6M=$5@>!Hlpa9t1jPv`EJ1DpnFn$w$SLO5`1d4l5J^+;mptu700c1YNT_E#7aRMr@K5P9U`)eIR>5;Q;eDs5}6f1rh^=E67feT_AfwX&V$LpfCW1D<}>@ zoCCMXO*`4FTQr4iFy{cOX8nN?V|G4~kb%*n;c@sRy|kMH4#S|d05S&@jv#YD;R?ba^Fd~S)PpccFDNWQ?gIH2 zJ0Vte6@e1=Fh!2Wi5C(-C$V^Zifb0d?1+o+5E|4Br zSq@SQ3KNhUKyo1Wg5m|F59DT0+=J39sICLmw;&9v+d%OM@)yWVkXle2fM^f~r6W){ zg3JP8P~3pb2e}8t2Vs~xke#6L2ZbNV9U%XM+y;s_5C-`PgkfJ(6W59Dr;-JrM!g)PWzkbV#!6pkQwfZPPaAb)`3 zACzuE^#iEv0+nH)`U_M~fXX*edH{t3$WNd!28A^!+(7z3_JhI&WH!j1p!^3)n;;z$u7$|Lk>;btAWEUu2Kr~1UWIiaoKw$^Mps)g^ z8Bo}O;sN9akUEfiK<)zR1H~!Ge;|Lt#v(!C0+IuT14tYcHlQ#9=>f$LsDA)zOYPgY z54l5YyjmGP`d_%LG>FbAA#HtG8g1t&^Rb)3<8ulLH-Ao1E6{qq!$!spzs5Q1;~9M zd64}ecYx9a$Uh)+Kzc!A7$9>%7?cJ;Vjv8%1LS{@UqI;%WG*Ppg4_*SBL`}KgWAL( zb3yW;Z~~PjAPmY+AU?<(P&x&<8x#*9_kikfQ2h-W^8xw)z<~q*L1_x)e^8wUN*^!` zs_Q`NLFGLtAAr&!$UUGjC(yhA2!q@Wif2%Gf$|opod_ydKr|>lf!qxW8&En0xfPTr zLGA&$8RR~YTS0LR8fOOi7vydbhJ^(vO@QJNl>R{Z9n=p3M01#RaGi2blrtAAr`HfiS3#32NJd;veK+P+9|F5Fca@$X}qi z1(kiE^a=7mKR^F}(0DM&4p7u%mTmiKWL1uvL1m!7^I#3vZ+ycr+APmY! zAa{e@42p9Q8x$rW8k9yrVGeRX$nCKF3yL35Sb)M6RJMV_6XY&XxPi<8VNh6t%mJAX z3LB6)AhjSpApd~M08pHR$`nw1!7wO)fciQhcf)9qdQd(9g*PZ4gTfSKHpuUwxCglh z6sDkb1?sd88Up17kpDsEfZ`1lw;*?e(k%#s%mwj5X#?bM zkoh1CDw{!OfXZ%AoPg{E*q}HBnGM1qH-o|gBo7K}P@V>b9Y{YY&4JtmD(gXR0>wM1zXckv0FBvz%4g7= zCTLC%)K>t_fr88g1!(L6RHmFcbLRh%BS*mH zG^oA?g%K!>L1uyS5y&kd|AE{F!k{z-ax2K+pzr{N3n;CC;vE#vAUi;Q2h~-e^|+vP z3+ms3+znC#%2%MW4i^6)zk|X8WG2XbP#OWL0mT`}FQBvx@()NaC@q1?K9C)tdJq%_ zApe8h3rZ^>^&tO({0?#xs4fMuLFpN!7ZhF~y`X*`s9XlcJE&d(tsMpR6G3SMqy`k8 zpmG4DACwnCVE~FZSU7;(56Z`&cm$GYEss1?5eU`$2Uh$nT)?0i*`xH&EUI`5iR22U^<&s;@!$3N*e0DsMq*K>h-S zA1MAoVE{@;ps)n_9b_)Z{~$Mm`~q?V$ju_IdLgVG@g zgUkSh0Vpki+zP@Vagg6YV;CU+gT`AxYjQzryg+p}D9%9X6;wZi+ywG3C?A2s4`dF= z-5?A~`!M%{_#n4}^nlz6iXYIJ9B3XMRAzzNgP?Q_DsMn>21@%Nvq63Z`5%Nq;RrGl z#0JqI3`z?iKY+{zg)JzqL3V)r4N5~Gzrrvm96;p?D7-*rD@YwkJxCsu&p>VkVNlqD z^n=_1s#9V92aP3x#y3Ik1jQ3b4Jf^U>N!vv0J#@rHmF?>D#t8dIqTj#Q`W@Kz;_*v!L_@@;AsnP#l58Kr{%0>;;7bNDnC7 zKw$x*L3V=DJV+cwgTe>oW>ES9l`|kefWjMOCn#J%VE{4<oAK;;}P?St|+ zsILI>52!u@`3GbUD4l}L1cevK4&*QZsRxxOAoqasFv#y9w}b2k`5ojZke@;E1FCaC zc@m@!6h9!hg5n=k&VbSd$UPwSAiH4YCCCg=9s=<}=^UgN}p!yNib_AIV%CDgG0a6dDw?X~^#Vg1i zAoqaW1+ouh2FOhyGePwnsLlhm8$kAhl;u&NnsD1;b6;Rt2ly5-o9#A?2xdjvkAp2o<7^xw}8w7VNiU4(gH{=NDnBCK^Wvl5C*9M z#U&_wKzRd{M?ifi(D(zWjSp%sfYKzWYy*WU$W0(Wg3CXi(gM z`o-vaL3%)XLF%VYovH!~50HA87>EyA*8$UYDo6kaerAR2~YdO$SD z43L{Zav=AE^nmn&%mMLX;vgD?VPYV?$Qa~KkRFg;5F6wskQy4uFh~y!gY<&T1&M<& z$X=MgL4Jo}kbamQAoU=9APfpGkQ_)a$j>1AVESQs21JAOfYiavfQ1E!28qM;g7kwh z$ju;qFbt9hnE}EeGeGu&*dTjBe2|?WeK7aJ^ucJ1@`r&DasCpN4Lkp&I5$5#wWKI9 zGl#@;PhiTSFFuXA+C2L%OzFo+Ej1E~SgAUz-qVuQr0f`Wp|f`Wodf`WoFf`WqLf`Wnqf`Wp) zf`WoPf`Wn^f`Wpqf`Wo9f`WqVpkg37AE+7^G&LqcK|wk}K|xwUK|yLztP~U!q!1Jo zBoh=ABoP!8BoY)9#1|A4#1<43#1a$~#0144HVAVB1qFdHOdcc#Q-e%{;)C886c@-C z6hANwiZ2)j#UC;T#VI;Qk8fo4Aag+GE)NO{0%2V74~jz&2E`)`gZQBMZ4L?w0%2Hu zgTz2=kUR*3(o6+3?u&wgf^vd_f)av)g2I9zzz-VVHbFr_X3#VMN)Mp;hhb0}ae}4| zP#OWnzX3E2XduPE9G3V8#W8x^g7~0#2g!jjhz$}0VdS_6vFRQ6Ap4Nx9>fO45i$nF z8w?}ICpsIH7C>S!wICX14#-?soP*+haZpgu!l0m_xzPOI8x$1O35{D&oPy#J6!)M! z42nk(28n^>K=BX4AT|ht;vIxR>S6gFq`n|1C@2eB27uB)6eI}*f$}*h-^1b`6xYac z?-mpk1QLU(!59BhNco=!n#a-O6~qU{IVkQy7{msNfiNh}VHgw_1A#$tgN|YG2BJan zh>T%z45DEeBo9&x!Z5Qy@eRVr@ePV|P?-w$=%pde5g0Lt^AybUS`KyeMq^DqpGe~>&# z4wOcWf`WpO^S??^P!Op6N6-Jr@sAw0AT|ht;vR&N;~ykG*yA4*r=a-7hCy;542pB) zxCZ5K7zV{T2!rAr86(GkKT`aI>UU7wqQ@&JjzQ@H6!#zuQUl^6s{^GI5QfD+DDFWR zl>cLbf`UNt4{8g5;v5veAPkCcP?-S2pfmvDgYrH|4p;nx>V8oC!}7lnwEhRhFDOny z7#6P}HVA{{KzJy`KPWyy@rsN=@eksIXb=XmVHiD+gW?+${~#JY&Oz}G!l1lA6Dj|L z%70MYgD@;^L1j89PlGTh{z3U2B##{bpfmuI1E~SUJt*IU>U~hz4~qK~EcqW_{Dazm zptuKNQ2c|+f4!ifAXwcGihEGI4}?K&KQW~CA1Ho7aSg)g@ehi37zWA1FihP*l>Hz( zL2-?aLGcO_1JT$pDE>k6AdH;vLGca3ptuKN5E~Z%pg0F%P`rci^q`<15S|ou zpte0I-eDLN*PyrsmHVK$1<@ceP~3wsNF9g|!l3vE$%En_l=so&AC~`NZ2;sr2Big1 zo(JW9kT{4B%Kxyq2bKMxwm+!c2jzKKoP**Wgh6e8Q2r-W{=?!PM8o19L=VRJ2gNHW z-eEL|jgCQbptuKNP+WsBD86AB#s|eY2!rArl;1%Z6z?Dm>ifa!eo*{_;u0By;vW{b zAR5F6#XSha;vZE0gUShF;vZE0hd}!PpgJDZ1^~q~D6fMsNDLJBp#C9992EDUz8|RH z2a0=8e1qazASfsZgh6o+!XP%N|A#IAgW?wy&!BjRVGtjNLGmCB%8LVmLGg%=L2(Te zLuSLoKyi+Y(aV2Oo(H7^5DkiZ^f(9Qc@PH0J#yX$#V;}j#Vsh_L2(PBL2}4(58@-M z0hJT5{158;BbWWK`X3bkpt>Fu*PyZ;7RR7=9wgYvJ)1k*l6UqM~-h88x-##42o+I2E{iF zgW?^8VQ~-2>!3IXVNkq-Xi(gP#(qF?3d;AOd<=?DWEvFrpmH4){~$S#I#|4e_#m|) zHK4c$se{%3pt>K_?nf^BVfi1_2LP4zpg0G`FDQS5%6L#(faQIV9H`z0iG$kvp!f&n zbx=HG!=Un?*!V|I1E9DEVNhHQ1jZKc$YSVe03?SV|DduSghBBQihmFd!mu(Q7Wbfd z2gNxIBlr73aS4iJP+WrI6qygo)1deT$%DjU7{mu*kQ@ku)FIdXptuK(|AFd#Slok# z0YLqIQ2qzSHz@8w@e86s@eRuRAdDXW$mKsQ{$X(rigyqOmH)7~1;r^0gYr5^92tYe zKp3WGAmScmCn!F#VNkqd!^mo3=>Qb>u>22;cUasb*YlwGhG9^?2VqdX530*SaSn<@ z5C)|I7zXh{@e0b@AT~%G6z3os#0SZN@;nG5$2~0X!{Q!&3=mY$gW?#J|3UE&qCx2a zRQ`i7NDh=9K=~h3{)5{6p#C1L4FHRKSe)aFTTmQ>;u@P8kT^&lghBB^Z;TwDFg7UO zL2Y~xjgDbzKyeQ$`#~66{KMiMl=orz9TeZ7ejW^i;vQ7?gYq*dK0$E|icb&?id&Ev zhz9Y|F)Y48Gzf$Gd!RZWR_97BJqXh~?m_m!;uD!hk8>CwSq&)u zLGcd4u)Gf{`$7307Wc3^9+c-{aSw`fP#+$ILHQn3pM&x zLGcc%|6%zbRQ}_Nf8_E27QZkW6z?Dmslip!kO6cTjr|)VGJxu>22;V^BH(<#$lN2eti?`+dlL zepnk2U;Km800@KP8iYad45DEe#7D-kxChbnjC+t>p!mdwL2-^Q4vKdW2E{vazK6v- za$6tN)`R7Fb{`0X#6a;3D&Il*9~A$fI0vNx5C(~Z@;$7+hxPM7 z@eeBhLG?d!9K+%kBnCNq@;xZ-L1jEBzCm$~j6wMxmajo^49feU_yxr!C@+IBC>?;(1V|1< zgXBQ*4l3J0G$_77@eaZuHmscwD(6A*42pM9JcG)1P`M6E1EBUFh!2ZvP<(^R0uUcW zgX(-xz6ZrSD86AB#0Oz)bpR-?LGcd5AU-mN#XpD!#XAfm$32Kmueb-<1BzFC7!>E| z7&-o7bv`WaLG3+I-Uqe$KxICtoQK6dD6Ww)sLTgpP~3y^HYlz^@e7JeP~3trC=Gzb zKyeRB8z4SN92CzW8WiU+4B~^*1*qHy<$F+EgW??&ryvX}+d=68l=ndx6#pPGP`(Gn zIfw?O15jB2s_$WW9Te9f3`zqaF%U*h1F-l;r(tmqif?obi+d0aigRQPiWhoed~pwx z!x#Us{11wMP~5}vKB(*m#Xl(TgYrBmo7aSw`LP~3vz5rjc(Q2c_#K^T@c zK;oeIM#dm9P#YiAzJuj&^!NwGIVfF#(gBDKihoer0L49s2E{+9J^;1#KyeI;b6DJi z_@Md#gptz#NFG!lfG{i#fZ`rK&XM_`c!y!+xCgQ6755-}U~!C(#uopeG62^01C{-t zHa{rtL3tn6-UqewK=nN+&x7I}l*d8w4Z4UZRKyeI;Ur_vm;ueHKY>*fTgX#njMvi}w8c^N`_4Pn;42o}1 z{KGIP&OvnoC~bh`K^Ue6lm!`_ z6!##EEB;}59~A$fHXbOi!{Qo5gD@!GK^RuAgUWVL{DR^Z)L#R|BWO+zM1%MsagaO+ zgV><>M#iAH2c;EIy$>tN)sSCkQxvM#Xoxdg5nt@hKxaCFpM7eAU-|f9%Kh{9HX<5`v9Q$ z2gNxEgW7%|8e9B>>U~iB!`gkI_=n|nP+TKpP`(FYP~3y+cu?Ge@-!&lgUWDFJc7~y zC|*Hv4k{Z!7^DUi_nT602KEi4B~^-f#g7O4=VqW^FAmZVR4Kc_aHV*97KcE zfiNgOKp2?~;?okN$2p7-ihB@-@zLWS6!#zus{cWCKdAhNmHnW0KPc~m%6?eg56bhP zxCg~I41?NxAPkCo5C+9RC|+Ul2#SADe1g&gNF0_1K=F@^LGcg5Fn!4J4~kb%o(5r1 z{DR^e6z?DmVuR#C;vfuC18Vn!;usXKpm+zxD+q)5p!f#Kf#M#7L3~i$gD^-9uJ{MV zH!=prIShm18iZ*X$N0>K#V;}qigyr37Q+_*pte6O{$X`LDDQ*XeW3Vc4|9C@p}>0FWH0900{R41@R} zagaPnA1MAodO+ngD@!WK^PRzAdDWD*u>D)P>YW( z-jT)7;~y0FAPma;u(BW2??=x2u(ls8|AXQmR0n{{08qSxXi$0p(I7r34S>o6P#OTm zFDNg=;t~}9pt1lIzo0T6R6l^!fG~&+5`*P?kXlf@gUWtb8y`9TL2(Deu($`sKL~^P zptuKNkQz|jg5n*7L3y9l_y@%^41?kxgkgM8+=DPEol{Er;husBDiL2(blpm+vh^ti+(hOUNMeB?OC zHs=q@{~!!Y1E9PQihB?S#XqR+j~@S^wjU_|L2(Z%|3Q5KP#lBe9u&_YHYnaf?E(-D z!XPQd>T^&U0F?nCIZ*u%i+@m>0HqC(JV+fV4S@1KDE?u2 zAC#{_aR-VoP<{r{AU>!}0AWyk!{Q#K79<9ehhdOay42okA#uksb#IdQRR17)(L2OVN0KyOe5(A|PSp38E zf#Mj1VP=5L0m*^HKyeSkp!f%2Q2c{1a{dR!I}C&37=*FKA1-lhYAF>1#W6Al#XSsz z#6j^7!pP-6EdG)6J}mxWWdNxB2i5fQU zgZlTN`W=)8KyeO=dk_Y(LGcZWdypDXTLGjFR0e?JAEX9U27uZDu>22-e^8u);t~{Z zAPkB#5FaEC!l3vD(J&0+!{Q&5PC)Sti(^pSg5nuw4#+GJ8zv8u1E~SUJqTlqe^C5_ zFt)e@iGwgUIa-K;;uwZu`#E7WC>_A$KxqI}4}j`@SlJKD_n>|s2!rxJsP6|W|3Ud5 zgh6EhsC);-FDULo@dk=NP+kX>1E9DEVURp1{y}O$@}ROFlm!_D44T^t| zJShKz>VHuDgW3R~IsnuLfVBZZbvr1|VKj&h!=N;P9PgmG2VrdS4~kb%{DR^d6z?z$ z;)5_q4unDK(DOeiZb2AVJc8sv7?(V?#X)fkihB@7kAD~+BnMIlDg!|A4{Ga~by#T_nv;c~KQ2qzSH#!F8aZtR1 z;uD5J@e2}z#XXD$iGk!m>OgT1!pLnuP~3tru6P8=fiNz4YKw#76%@a)xCZ5Y5Ju*M z*dTSV{11zNP}>g_@31rgihuMv06qRe@eC^Wk>ei}=b$nGM1#r$kUS{1;vQC~gW?^; z24UoQ1;s50gT!F*4x>S0*y5jWs1FE>e^46$l>b5T49efII0nTxC{2Jca{PnjLGceN13>W&ig!>uAA~_{kQgZL zLFoX*2Bi&9{DayFuyO#D24L+0P&xo%P+0(~6JTioR3?DZ0H_>*<$q9ogW?{9VQf&m zg5n&6L3tWPgT!F*4x?dl50VF|L(co4GysZQ5QfDyHkyz;Ha+Buf#MYw_b?h1-=Me$ z(I5;G2dP8P|Dd>sVNm>oFenYc;vcyW0E&N5KOYqTpg0G`FDM;=;v6~dL1G{bk_TZ> z8UU36pg0D_JqRPmKPU}=VOacw>Htuk4~lnm z42o-5oPy#N#0H6h;vJ+GM1%OC_y=K-JV+e~Bc}mS+=4K^_#z~aPcNx*pm+sgP#l9W zD87*~NDL$oihJbtKdk--#W@Uv;vQ7~gW@0527tvssO|@~|3LX4)CL5_IVcT)Fo+Gq zuy_ZR{h+u9(I5L3tlJ{y}8`a-Ijp zKPW9A$2l?^7XP4p4~u^g8x-%b_y_S}a-g&T!k{z&D+56N0Z{zI%6(9rgD@!GL3KYU z?m_iGDF1`vAA~`155llGMUGDp8x+ULaS!4nV~`q9+=DQ3{1b|+frx)lJR@V|xJSKHc^_2X!!RiCgD|KK04nD}@r@kMAU3G% z2Vs~TjK&rJu(BUH?m_iF2;+)>P#lBe6&9Z$8iYaV0)|2I$QUFK!l1YZVN&9sQ2L;z zJaU|a*r2?Qj6rb^!pQj_)cyy>Kd26Xl>wl*hn4-Hybo&ofyRE2;~!N2gX#cK8bB@s zKyeSkuy}{jptuKNQ2fKn08sqG@;P$+Bc}mS+=J=?Q27t)3xL{yu>22-e^40!D*His zAC&JwaSzJ(pte5rN&_Go#0Ozex&ZM(;-I*Pwf{hIkFEYkj(;xG(S2g0PpKPc{D7!)`3z@RoAC|*Gr#D-xIA030_K=BXC|H$)yp!Po~{*lXn zP#OT$0ig09l>b5b9)tK=BVM z|B>S#RQ7}7A5`{(Feu)UF)03FWdJOGLGcN~AU-VaK{SXDi+30eQU}6AA^t(}3c{ec z1z`{yIqqS65Dk)tmHn{t9~A!}42yqI-w(MB0E&NDyo1sJ2*dI}DBpuHdOHv~?m=u& zdH~f4pgI5)|DbpVmGhu90OEt<9#%Gh#9-w=sQ!ng0a*Mam;Io)N6!1OGysc#P#OTm zF(_U^@d=_q@eL9KVGtXHLE^|5q!t$U$aOy`f8)y6AUPPurH0z#p!fvED>6oodsrO+ zk_V|pFZ*HjKPdju%K+p$02Kc)j2{26xCg~EG6v;)5FZrxAR5#L0F?otJ|8IlLHQhn zL2OXmgTz4?lomi~03-+N|AE>8p!kQi{Xlsi)b<0#Jt(eW7!>~?F%S(BLyvz@yn^DJ zP~3ym!QvlQ?-Pn!Lh{s(dysjc_=I6l{DLqju0a@<4nS!D#0RN^mHnW4A3g4|#Xr6_ zAgKNa<$d&e9~94^I0s=+yn|>E2IYTP{KMiHR0e=Bh!2W;Q278#3!pLqBo2#zSosf% ze^~tws{>#(C{2LM0}zI#0p$3H9u&tQ42o}1yo1sJhz9XNWdN)l z2x}BWE(c*yyn-++9e`+1o(IJ{EdD`!kT^&^dj1E+KYIR$wE;nC08|cu(g6sg zrvXqI04nQ2@ePY(5Dnsk;vOUi!XQ2<{*mVcKyeSkptJyrb5Q()FsM8LiNV?dpf&)k z3;>M-g8BilbO0+4Kz#yOS^&jAsQgEce^5MwFsSSYVNm>oFf86dG$`I-7!>~?j2!>y z@ebn;MBIby1m$HI2E{7~!_omLzk@I+-a#}d?m==OHK4X1t~3D4|DgB>l>s1(oCaWJ z0jNCy8V>^X|3GB`D1Jfl4vJ$C4T^hE-2lTNK1dFf|3PsKieFINgX(!uUI+0(@eacv zK1d!^27u}TP#FM91F&)clny``lomi~0EA&_08|E`=YLS#gW?+$_aF>ngT#>I9TeZl z7?c)3d_rje6c+=5VeyDeBc}&Yd?Uv@Ebd|IL2UpK2Gs!|3@QUa@ek_*fa(EIIsjo% zI)Ie}pfmuAe^5ODY6pPg9~8f!GysZY5F3=|VQ~+lL1LhE0m}cd_y@&3D360MD6T0Hp&^n-GLyWdSJuK^W8)0JQ;N^*<=CK^PS8Fbv{@ z;vW>pAPkCUbPN**#XmBJ<$D-C5OEK(6BKvo7!Ff1J)rvX?y08|Db*8!k3fFAdt_y>uB5Qdcj$Y}r+_nwub})5VuRuwR0e?JACwkAd{8+6!l1GOqz;r`U}XR({$cGtSX_hR z8^nfTSQ!AS2VikeDE>k53yN=88UV3j7$gV7uy`0K8We9J3`z%}cm%~YD9%CsKG57C zsP6~jgT#r6e^9)G;u~Zph=yU1I4Es^;vZK2!^!|q8UU38pfms~13>W&!k|2loE~8D z56b_bG9DJEpgax2p!fxef#Mw*!_ojKeSqR0l>b5P08l#+R0qKFKPa9-aStl*K{SXD zN&_G^2!qN1P#OTmJ*eyl)%~FO1;r~YK9OmVI66j_2eAh#-eLBF;vI%T@e7J;Slol= zgh6Ql)c*sC!|DWN8l(qQW`N=w6vrS8igOSjhCzIgI4J+a%70iI0QCbv?Ep|30JQ~R z^#CXxfZ7Bg3}S=Q0Vqv?(f};}LGcQTb5MQ;VNl$H%6pJFEdD`kP?~_nJxCoa?qThJ zP#XZ$_5+pouy{sJ1E6#O;)CKIgoji7gYrBKgW?s`&&L-3AaPI`0Ky=3AR2~2X#f=0 zpm+vh5F2DBhz4Ph7%2Wh`5zSjpfUhd2Y~v5p!f%2P#OT00ie17lmLQSp0+XItYW} z9)w}yq{Kfce}nQrtc(D$K^P>CjA40kAZSqD1;rJJ2E{8Vox|U08|ga(f};JVQ~-QgUSUE4NC{0 zvH;Wu0M+rJybX$5P#OSXP+WuZJS_i%C)42pMTj9dmF*8!lo2jz26 z{KCq4er1P&$IeIVkUgFo+L|dr-bd#-KC+YX`vcKP>)1aSLkaf!h8c8pH>s2~hll zFo+Gx|DZMitPco^e^A_l;uRFXAU>!L0L4E@4J-}7$_bDfQ2c}H0Z{&jmHnW$J}ACH z@eYfBSQ!8+2S99)9CG^)6rUiB9-lBiNDdjp#L?-2hG=N+NfXaDL zo(IJ{D1U=6C>?;}8x-%L_y@^@_@MYl9tQ@+Eht_=7#9DaxCgZpK^T+ey3r2$Y} zgW`C5P*4yE?+gkGLdUzIVjy`?8UU3Epfm(Z10b_NWdX=sP+b6uYfya;qCs&F!XP$! z{Day6pfUhdzJuZy6!)Nh9;lrMifd3g0E&AM2FZic1xOy020-lqP#F&@13>W#qCxo| z6#pQ35DkidWHre7AJqN>)&Hpu7%>Ur^kGFeoj6$^=l{gD@y9z|sIn4wk<`WdJDd zK^PSOpmYI>dk_Y(VeyaN4uq8fpn3pQ4uIMLptuKNP`MAopfrFS|DZ9T;S&F#cmu^h zC@w*93Q7ks4B~_08WiWq7$gRg1L*vr*R1SdJ0I;|Q#XYDz0L4Fu28m-U z13+m2RL+Cq7ZkV1@ehi3Z1E4u`E(1Vy zI4BN5@d=7oWDJUHP~0QaAT~%1D2;&hfYJac4S~`Us1E?j_n>$N#WyJL!!UXofE@pz zHUM(`g5nsI=Rt7~!XP;i28n^v04R-s>Htta2gNTaPC;=Ci+@o055l1M2gN-ojleKS zEvy^>r2$Y~0ICO&;~!KGfG{i#fZ7AF_8%K^F54>tY#qN9%MHx{y}8` zD9%867ZiV>cm%~OD1Jd06z}L|Ka3BO1E~e20gygW`T?aQ^!NwGIVk=?G$=j5(gv&_ z2^L2{t@2c->=JP3o> zurvT04*JgW?tz&!BccItH}|LGs9P4@y6vv;;C67SA9W6!#zuihmFW#XqbK2%8H6^#Nh= z56a)5cn8HX2!r^re2**+i+@lX0F=i;`5qL{u=oeXJtz%;>H?5BNFF2yqCpsx4nS!D zR1bjiKPdh|aSp2YK^Vk_VNm>o%71dpeo$Hf#XSfQblijN2gMPn3;^YS#VZVh#Ia$JJSgl314fR2P@I9{4-}W6{0xd$P`raMC>?+>hz-La zKB#;Er4>+l4@y5EGeB$*4Jub)aSozE?LiO?ihB?S#Xqd<2gN_A{0GH9sQw3~0Z?5J zif{BZ0E&AUhQ&Xu4*-g1P#FMj^KPdl$;vdup z1jRq7><7g;41>}Fhz%MC0@eSZu^;3z06o4zX#<3j#RpsbgYrKpohhRQH3*e^~zy zR0e?70e~=a8UVEgLGccvL1_X+gVF%3YyhPJQ2P(m2SAR0P@IF}9)v+`Q2c}99Tevv z49fE$42pYD-4BX;Q2mb{|DZe%ig!?X07?^}bO5UZU}*r94nPsQV-G#ifa&t#XpD!nG1@4P`(GX_h1+l z_aF=^|3PU0RtLcPfS_^!gh6Ql7T=)w2eDxoISqj70$3jqIsQTM4T^gh2Ju1h56bT# z46E~DaSy8dLGcf&|B>@QEdD`aAPi~?g3<%19ssoiKxqKf7XV>UoFijcS^&jAEbc)x zsO%jK7!?1Ycmu^DEdPVbe^9;##V;uCL2MX?iGgU47%0tv;u>TIItJA%ptuL+eNfzk zXb=Xmk>ej$27vNE2!rxJ2!rAu6wjcz2jzDV8#(@A;-IJ+aSMuLQ2c{vkT?j##6dKO4@x&6{h+u9(J%}$7nJuw@ePV=5C*Yf z7?uWL`5)8f#@ej)Xp!f&HHF7=&<#$k=gT!HJ z16KCK%6(Y;gUWqS{Da~d6yKnH4=U$D7{msZ{h<60!l3vEiGkuDgokSU!{QAT|DduQ z6sMrL1;sfi?m-yD2g!jjOddpo_@FccigTDbAT|iY@;)e@L2W%y{DauY@edLQ)d8^l zk1hT|aStlrLHQe0&x7I`6z8CH0KzbN~?466GL25Fdm=a_DIQlny|30H~Y?<$qB8!!RhmLHQnrLGqw< z0g?x$0Z=&)%HyDV9v1(g_y)y2NFG#Az|?@`L1_Tg1_Y%6P#plO<3aI^9KWD=2Ju1h z4w3`WpfUg?29krN0a*Nl$^lUPgD@!mK^PYQpl}~77#9DaJPV3DP#l8d5`;nV3c?^U z7>0?1(g!kzsRNk-ihq!~pt2qm*PyrsVNg7SFeuJJ;vfuD0}=zN0hI%w{vas+L1h3a z4S>pdSp0(G7{mtUdr;W`N&_G^avA`|FDSo*@;4|AfG{ZjLHQme2g0DV07@gE_(!e> zKxqJ04}kJHD1JfZJP3p09E3sf4-yB(KS&&gL3IQu9l+86DDFWR6!#!JSmPfQSD^M9 zDBeKn0E9vD2#RYE2C+f$528VQP+WsBEdD`kkT^&UNH54NSlbU2ub?;w#VrVf*f0!| z1Jws03`z?iaZpi|G;4~t(=9D~{gp!^OJ2c->A z{Daa4NDNd4fZBl|3`+xpHU5$FKPdh{c^DLzptuLcD-46;8HPde4#OaMkXn#FP~3ye z1I0h6{s+Y^D2_pK4U2zJ+=4JH4S>=J2!qst)PUqb7*qy;+Jc~VASjMODDFXV3Th94;v1CrL3IGA{0GUy(gjF8NFKxo zl>wkOA#yzc%HN>)2E{8V?m_tGAU;S9NFJmP7XO2_3;@Ly2!rAc7XRpZ z9~9>x42o-P7$gUZe~>4kQm^gVGGB41m=GpmqQ#enD{#idzr{#WP3@6!#zuVuR`jkT^&lgkfm_ zlny}oA65qpmT~|TN1*sZkAF~Hg5ngEx6v^uzCjq27Cix1yKBh@;WF^VHi~BgW?`UgVF@3TmWHEoWsoXpk644wOD% z@egVT!qNaJ&OsQI9zZmx96*nMP<;Txp!f&X37|9pssli60QC3=mGQ9n2c-d6{DaB_ z4D0(3lS>?m>AT6tBn_6vrS85(CMD;vR%SY>+r8{y`X&20-~A zghA~9P&xo%P~3wsDE>it9aQdv@;C^C(gG;%K^RmQfaE}F02KeAHUKRCL3tX4LGceN z??G{ood2=KKd9~p#XSfQ#`p)t8wi8q5EP#v42okI4Pt}xItYWIu9#rmw@;xX{LGcX3ATbyQ)d`?@2VsyHhz6+x zr2!Czr2$wv0HpyK2BiZ~{Dbm8D2_pK55k~44~lzGx&YCj^Z<%~SQ`K|CJf5&pmH6Q zr^$_fP~8v0pnMO*pnljuVNkq*Fen~DaSDoCWDJUP7>0?1)Pd9^V~`o3I7h~y{Er^@ zpn3p?L1_RM|Dg6BDDFXd9~PgWI0a!)S^&j4NFLTE1gQgw;fsG*Jpd~QKxqIJ|DZAe z6#t+&29@!kI0nTvh!4XcK1du?|AWeZSo(pm+x5br22W!!W2U0EvV0 zKXUwo;uV$#K=~Pz20-x)i+@o20K^8R0gyPdT9A75{11zNP~3wss7(NB|AF#6D2_p8 zJSd()7{msNf!N4xe^42ZT(5)JpgJEU4oU-{J^-jbfR+EC^Z`mIpt1r~{(~^6{0GH7 z2!rAshC%T#P#6@4APkCAQ2c^0EWXickQ_1w=>h2l#Xk%qrvc>n2gN-I!{Q!BgW@07 z?t_*8u=oe%dr*9W;vd8Y#WN`0K^PSG=;}afam76-&XF;w3;>n$p!^T2-$8ABbPOu< zVQ~wKS5TaS>HtvugW?<{4@v_dHmE#+sRPA7NF7K$DDFWRR`-MAAA|=({Da~UgkkZD z9_JuFNDi3>u|XIl4$_OB20&>6lny|30W6P$;u#kIpgIABL1_Ra25JL>$^cN?A2|(x z;v5wBpm+t*FbvA`APhGZ&cos#wDtprLFGLt zUO{mR%GaRy2E``~gW?$!=O8s88dMg5)Plr8YGLt@od1#I9~So@dZ6+?$X-zVf#MMq zpQK`tJ`e`QKL~^39fm=CP#S>6F)02)X#o`PAPg!SKr~Dq)(!xT3Bc+CQ2c`892Ebc zd<}|Q5C+9Fhz}A6#XpD!@j>FCvK|({$Z-#fe-H-6KTI5y9zgXysJw@z0Z^Vtj(b>q z!r~o7gW?vHCO~lxi+d28l=vSk`5zR2APkFpWHw9;MuX&F7?f_XVNkroFs}Fq#XSgv z;va-T=>k*^fYJb{E`XHuBFi0L$Mu5@)Xj}+X4uIPFpgJBFx3K&Tia$_1gW?;M zK0p|z1|$x`u=oY#aZtP?$3H0EL1utxkT|Hn4=M*hbpfc|2dV==e8-_aF?4 zUyvLq?qL{|29V<)q>h;QALujyic=5<#V=?)A0Gy(0jUMWF$ja=7(|1_U>L*)VUQds z{$XtZQ2c`885H-RGy!6R#6TFt24N5zlmw(e$D38P99~8f!dH@vX zptuKNkUWSF5(i;WUk?<=u(${1b5MN`;)CKHghBG4^Z`o)p#A`;3_y>6SQ-GuJBSU6 zXHfb8wf#UC#D>K`NDV0NLHQnpLF50Rd=J8dvHl0eDGbBn9yYHFkT@s}!0G@{{DR^hhGFpxifd53gXBRN#0H6h>H$z00BQ?>`g@@E9w@(q;vR%S zaSV!cP~3wsh>afqpg4xbEhvwJ(g27LN&}#H2g!rjATf{}s0;wr1E4wp6sMqgM#i8x z24RpKDBeN!1c(OlLE<2_ptuL+dr-WCFeu((7!>~?49fG!cp%GvP`rUKEbg$;xa2|N zptJ(Qu(iUV_2M8lNDPz)Ky3j~8UU38pfVqXLHQpP$Dnc^6wjcv01^XXu=oen1IY0Y zVuRuxTl|B{2T<7#id#^egT{A3`5F|zpm+wwH>fUvVGtjr4wOb<^*=05LG?T+Er8-1 zlrCTx#0TYnkQ^v|fZ`vN_hES+)W*YxLGca3pg0H7APiy;6b8jFI)=qJvN%W#6rac# zJ?=q!LXj~@9;6SH20$3pE&!DWptuIbJ1CA}c^{N6K;p3Y2g!rV2T=UO+5w~?8iYaOAax+UF!Mld0#ICo@;oS? zgW?>-2c-)T4XOh`X#y1gp!5NXe^~hsD)T{c3&Nnd2E{!n|HI-QBo9g(u(m%aZb5Ml zihmFWu|aVSigyqO$%D!SSlolufZ`Wari0=Xl&?W-P+kYcJqUy3L3~jBgXBQ!KzScM z{z37I4Wq{?NDaCeJ^8SBL8n1+h7E(_6B)ze9~9Rh42pMT3=#vW0htA|15^%x;u)0B zK^Vjar3nxQwG%+?15nxkr2$a+4_X5T>IZ`28Wgvnc0F?ZgTz7kACw+I@eh&%mHnW) z9TdNy{12+rK^Vja#Xl(TgD^-Q#0RATP~L~dKPXRw@;!(K#Wg6-LFEF729^IHF;LzI zsi8Ff=^5A9?1IG$C~lB3NF0Vi@eN{wXlxi1zo2+V#;`O15(CMDFvtw__y@HKKw=;a zDg!`#P?-Qq8=$-oYx~3cexSS!iepfG!{Qz}|AXW}YC!Q1%Hy!S4T@h-{Da~eBo2yu z5DklekQ`{f2Nb`cd=0{&cn0x7`5uHp@sC{g!{Q&*CxngnfXe^D*v`Wi_aHG)oFKfD9%A; zJBWtGJt*Em@eksI#6j|){0}N0VELa={DZ=JFk)CdgW?5*VSE^k9M{NfP<(a$d>AhI9~RfxXi$71V^AIj#Um(QK^Qr%LGca4uybQTYLPK0o?&qeif2%q zgW@05H-L$Q>V8n$AJ*;z?;mG7Xu z4vJ?`e8VuPoJYo>I0nTpC{Kgp6&C-X@&F_Ui+@nugD^}TDE>ihKT!D(>idEE{-E+7 zxvdXs-y!3{Rt8{;b96CS{Da~igkkYcDDFY>AibdY1(^xb3!*`B4k`mc7{ms}J%|R? z`=Gc7mHVJ}A80NZ7Pp{y1z}kHgUWr794P)l7{ms}KS&;w|3T$HsEr4z|3T$GDDFXV z4vJF{2E{21!{QzkpP(`w6#pPOkU9_s$%8N`?m=>(Gy+lw>K}mO9)w}>4;tse7yqEN zFqqN+C_b=ZP`ts!LGcFSgJ@7*2E{8ZpW~uIYC!5iaSy83K{U)f5Fb>>gD@!GL3|Jm zihoeKk1hT|WdNw`N5-JI2IYHDS^!~K{Daznpz&Q${3F)^urz?4r(tmlihoetg7P;g z{$c3_#D~?^FSEH29@=o_y^ITxCdbnAJirQ_5DG8 zKT!P-D+6G03n~LZ?K{x;4u}TDHAoIU{z3AfG5|Ej2dW1^?EvI*0F(w`c^Vd{392Vc8 zxP)O)+=Aj57XQfNAUO~QiGlQk%mn3aP@fKz20(mJngH1cig!?c2gNx^9;6PG20-GV zb^&M%0F(}3WdW?M2g?7j@*R}NL3KT-8~~LMAR3fDKxqP`1|*N124Hmos67D6+n_iF zmFb{32gN5S{y`YT2Z@8?9i$F~L26;?1(XJ0>Oo~c2!rxJX#5Y920-x)!k~Bt(dZZ? zK3L)(6fdCo0AX1CfbuA|xCg~62!rAngh6bOIuHi2VHm`R#WN^gK^SBY$b1k6u|e?; zihmFW#XC$5hz&{uAaPJx02%`Xl?9;o04NQB+WMgQ1;r}}gW?#(2gN-I!{Q%QHh|Iq zNDZhh2&xM}ZVNiU5@+>IsK=FuN#)H_P_(sMcF_1ctdJx7J z|DbpU*#W|!cn0NtP~HZW@gN!$-yk(0IZ#>v@nLE}@sC^&fcku(HXkUSL2(SCK^PSG zAPlM>K;odd2eko^;~&%(0L4G3O$cfWfZ`t%pP)Df<$n+c#VKe^2ZTZK3c?^ZNE{UJ zAPhg$2(0Z^R}!?3sq<$Vyw zmj*!f0H{3(sslh}J19Ou@rs@fKyeR>TM!MxptuIbJ4iixu=oejAU+7g*r2okiffP_5C)|OkY1QLC=Gz(9z=uGfiS2H z0L3>f{z3gcP&|X;9F+G#7{msp5s)}2eSp+}+5n(B05lg2>Jxz40-!cOs4M`b0Zs0=avFfu0kARvxefrO0Z`oz!l1YXr3Fy@g7Q2l-a%u#p!yw@7C?PIP`raM zhz&{;AaPh+gW?;+24N5zgkkZItqvFrH%0e0ICaMeF9LQ57h1l)eE3B z0E&N5{DRu{urvUQb6DJi)}*8!k30BQ@s;u+Kz0Hpy?SpZ4{ zAPnMz+JKjAMY7>C^hM=+l6#t;Q0F=)`@e7J~P<(wl702KeAIsi2O2a0o0{DUwk&x81&HUOv}0E%-^+=9w^5C-LSP<(^Z04V-J zG^iW^sR7A>$^cM30ICB(`5zX?p!ft~eDM#8TjVqVi+@nugD^-Qgkf5kQ$IaP+9?*14?h8JPwLmP`(DmD+q(y0U!*D zZx9Bh2N(wNL1_S127t-|P#OT$1+X*#%IhEui+>Osl*d7BJy5)Z;uVBJY>*fz-a+yp z8a@6&WdNu>2x#XSgv^n&6aWG-?V0L48hUO^bdhNTG*4N4ENGysZ! zkUS{vkufL@!0G`|z6W7g*$;|aP+WuJ9fU#g3Zg-I9n=N@$%8P64Js2r`5#sXz~UcN z*Mssr2!rAs#0SMa2!q(5Gy#fhP`tu0D6U~?07Qc@NDL;A9Ph|%dZz(UoFilO_y@%m zEdD`p2Z}=w2E```qo)Iq7zo3}L2QtEP}%|M2VszTAbUXL{;)CtRIY_yxr?h!2W)7zXh{;vkG1=g91Vh<{LC z1f>B`oWL+B-a!}?S1=5UI}irNB`8kOF(|HK7$yf&3&J3MAPh1KWG=`)(0CuHt_S67 zP`rZT9)v;h3Bn*YNE{UJpfmuYL41%La{R;60Bl?UR3Cu)g`hG3)bXmen2U{GLW zVPJ3o^Zzq2Fbo(L5D@sUp`q~~L=U7p2Fh-bzeoK)8vmelFi>f5Ang?s6Z>y!YWg2U z52QN=%5IRqNBuu?<6l-*_P@Qo{eKWWP-$==?FIQeBqU_i|FW_J)&?7m|AA}&Dk&-b z2c>@yJ&^G*PGzP0mGniS#%5jSOqo=>dS&Ks4Wkw*I^h`#=|fuy`y7T{3DCQ z)Faa{^FZcH1ri{Wx7$S=r#gZUR04j>v7J}`_Ph9EvDY(W?n*61|MOi;us-w}9LSaw{^1`3XdW{E3VQvg`-h4e~e4{~-QI!=wHm z+4UbNpM%OZ5Ixe;KFGfye~Tgi}21*Yj4TJnW>i_Kjpl}#zaS!q@s2vB2 ze-I7gk2DNwyMp`=qDPwFLH-5#dnEfG)NcXx*+4WXJVqJ@`Fqs=A^$<)Fw){4X&B`1QU8zJ{a>KDNYEGvh#qNaALL(1LX4ucJJQJFodwg?%liXcJJPu0L36S zNNgzifn2wO{0Z`R>+apVXF@TE4H5&%k!$Wy&Op>o`5QT&LE<0`@-xWqAT=QVVD}F(?gRM)^*dPoO2eCnNAQ~hF!=Nw&=^xDg z!{;`T+d+N-`3u>9Ab+8YgVcb^e0=5&H93%fK>h;x9fU!C289U-gV-Q3kUWSD5*up% zBjjF#-Me>#{0H+V%#Wb>hl!*69i#>nZXlYF`GZ{^_K^Wvun0rBNkQfMq*dTF` zpFwPN3^HS=h6TtkAU}dIhz;^52!q(5_y=JS8{|h28>9{$gTz7dAbmp(gZu{b4~PwN zKPWCiY>>Y|Gzf#(Aax)b#0Oyz8zct8AU;SRNdHj6Aa{e@55gce$nBuG1o;hwL1G{b zk_YiYav*<#FsST6mjme^N*LyTkpDnzklR5th!4UbKZ7uc4H5_W8>9w?LE!5ugfY>0ng8T*Y zD~J#BHwc5+AaRg92!q%lH6S?uvgVcij z4ZgqrAparz8>D`y`yJ#bke^|G0r?M?|3Pvf|AN$lFo+G( z3o-+jp1~{*ayQ8RAb)`T17d^3aJhN7`5)OopfCXOarqr24hkDk7=hG+!V82!Y>*g8 zFGwDwZYW@oTS4wd_5(;tI<(I7s^3>e0zml|>)dq8%A?1s4kQQ`2mGW`g_=vKNLyYC#y8PpMn5=?D1{W)HRf4zmwtKFBZV@d&aX zB!;X8#0KdF$${7)eIObnMoo-tKBayKnFaC(2!q(j?gY6VWIhOk*dRHO8jxBLALKuf z90-HhATeY)m>5VOwJ^wjkUb!?k?jSElVS%*JvO(3#9`)ua``G6QBFHE57|Fgrjr$WCN86Ke;uImqrsHVt7}V}odz{U9|U4B~^tK^Vja ziGj=@)=h-efy@G#NvZkB=77vX#vpft!UpCR5E~?hj6rSyVUQfC`~$IJ@*p+{gXBQ= zz|6r$gXBT#h*b|#3$h2~UXUJ;er)FA69ef1>4ljCqCtER2C>o22B`7sw0{ z4KfEL4#FTdNDPEQY>@vz;^=0A#6a>Ob?D}xtHs7g*AFrSghA$k%mra|yFq-AJWL&k zkIig+Vjw**GeG8m>;bt2rU%3ZiNVx>XpkO|JV*}22FZgkNDgEMhz9XN@*s7DFi0Ip zJxCu+KZp&&AU+I(_#kyK4C2G&Kw=25hhO7?6 z2iXBL7i1QQ4>AuV2U81TgTz7dAhjSG8H41|^}zJvqS4I+nE?_*RuAHX%mvATFvw1j z{m2+34pN7%4fNTp{oU%50XPR6U0YX3(^M?1Id9fhz-&U!XPOuNJ z=78)4(J*sBG)N9agVe#y0cJo7)%X_2C0L|gUm+8AoU)}-T#)%73}Pc=kQm4fAQ~i&%`TALAaM`| zv0-iiu|ax3dO;Y(hG7sN*<6rXm_85<;)B$})ZtSLQcnp6*#W{J`#^aeM1%MsJJB&n z93&6I=>A1^1G4*JdO%_zH6R+KA0!SkA4G%r=;ndcfy6*^AT=S0AUTja zeCB}UKx#m0VR}JqkT?j#P+Wt`0}uwWL4F5? z4c+`s#`yzA)I5J+|Ni|9bi{o7_wTpazkh!a6oc3xF?zaTAnam;`giI6{rmT#VUQRo zJV0s)xeXLvz5Dm?2VsyNLh1*DJScwapnhJzfB*g*Xjp*U4`PGFKw$!6<8v=aJxCqM z@1QUPu|eU1PtQQhf#MX|?;2S0n$-UN`w9666gD9Lf@qMRK;~@UzkmPC{rmTW>;Q=k zMhtQrD84{$2ZaMJH-h{HQUjtvYCvHE(g*S%ENozMAbAi5@sag_#0DD%`5)wOLhgj6 zZID`!A3*6Eqz9BYK;Z!LE66O691O$sfaKBTU}^>f4e}GnFCahQau3WeAhjShApd~k z9+bvGX&oenj6r1zC|%%^M^-z~Y>=M_`5zQEAT=PhF!#dJ04N+l`2i*lqG9Sm;RfOl zW(;y8$ZsHjfZ`3CTR~|Uqz2??WO-0{fW(pYg3<#h-a#0|Mpi!<*&sK9@-fICps+x9 zBPdK@X&F79LHPg{*B~0E7i12|JY+GL++d+WZU^}X9xr9}E~27a(_o+zN6V$lW0Kf!qp`1Nj%^P7n?A zKa3BO2l*AG9%Kdx!^{Dh1qvUK8jyMz2B`tbf%L(|28ssx2Nb^`_kjEj@-xVvl(+{J z-=OdSg#*ZJkR8b8f%u@b0?1*`PQ8xdTK~+by7Q0AXZ5!NL|K4hkPon4_l; zWc9RUgX{&l0px#b`yJWcp!f!1kQmJWAU4QOklonQ2uPlm7~~FE8U)2XE$ssN9b_K} z!`LwUK=}j|1|S+HMh_a~XOO+1um`cJWhcmwAPh4P#s<+Kzk@JL4n%|aAUA;A0!j}c zF(8iV2+WFIIDu(=5& z2EydJ1DoIRnFT5fL3%*pfKMGY1vgW78#c@PHi(bW*c2c>0@-#})7>;Z*2hz9u|6b>MaY!ApyAT`98 zO^F(ie?V>m*^eIgAU?<)FnJJ7uA4yq2l*9b2FTwqH-p4MaR9;~`#>0!-aveq8d}mI zw}9LPiW`tyknIAcNst`K-5`I1*dTStYDi(j%mc+U$V^i7(n>AN|DbpVg+0t35E~>0 z@-xWqAaRhJL3~jBfcT_hm|dVS1eryuep;vp`3>YJP@Ew93&aMAgTero7GdrO=^@80 zF#VuBH(2}+atp|x=>CU^gWL(r7a)Iw)PlrdVM0nc!1RO60ObWx7?Yx(7HUE198~s! z+=T2N5E~>0@+-{$$ZA1;A=Nz~b3poF_JP72lqSdtOH%E|r5@xaP#OpM6%^+%bs%w& z-;u>Z?u4lYxf`aIR2mevpfUhtHVA{vM7En$^C(phN{b+Op!*439OQ0LoPpF*!@bC9 z0c0k~K9C)h`iE5gAa{W52c=7x{h+vp#Xm?MWF{=mK<)(b@nKN<0bzVlP0l5Jr2J$l~ZGhMycf#ax(V%z-r6Z78APnN;Qj07O@-NIzkX;})vOF!>AiF?z zg4_$U2NcgRagZE{4U@x0gZv2!N91?}=>z2(Pm#F&ds4aiS0 zH{!AjyR#%2!8{~$S#KS4A) z2Duev2FQOP_2}{-J}AyX{sO53sRxN6V`O<;Zb4R0EjGxm{m}JC$ZiL*L2iQi8AfBX z7gRQa`~?b2Z1Ny6kUbzZAhjSqItGb@d3bP94SC@x`Qps)a8 zm>y&rq#mXgTfBnQg7o531F{cf7btyz!W^WBo*3jlkl#V+0OSr32Dt@B}eub_MZN)y=J1WLcS;uoX`*#41 z$bXgZu=`_nA2?3ZxDcJ|MN| z7@|Nnpd|NsBj|NsBL{r~^}AU+I( z_#knReE4{{I4U$3F@4)P~R4Bc-aK1dvt4q#yb;^T8C$e*Ba0_iyoH6KKS#6WWR z^wL8PodNPMC=5Vyps)wYQQKd{nuqLvV%eq~kRELD2U7zw8^lKU4@e9o2f`pWx;(x3 zAh(130P+hcy@L3J>;}0POp!zW`N8Bg(JuwkQ~SikUU5XBnMIl69?G^5(8ln8>E(27!;Qv zH-X#;^E=2bAbUX=Wge(J1%*3E9Hth;28n^xfZPWPH<%nXXprAQeudcq z^EX*4j?v64ipZc@BsN86hFvvFg29WAoD=x4y4~f?g#l3h$>5F4L=K<)&kPmte1G>DH+J+X2iyFuj) zz5NbyKPaq0_JT0TPoVe(xd$YN?tYLs$ekbzVx!BG%LmyB3R6(o55nY{L#jTIA3**F z)$yS67Q}|7ZBST%724R?5V(cMS4ai=QxuAFexd~(s$Q__~BGwF2)Pdp? zWDh9agZv5V=OF6`*$c8Cqz)N_#6WIER!c25DEvWT019)E+d%FDg*(Vjkb9_Q56B#l zpFnoN{137NBtFm>6t0YroNpzxuF`P482p}hj`4c9OPJ`q?Y6fFo0L25yZJ;m!`3dAV zboZn4LGcbsGoX9}!XUkb^dW})k^_Y`D9wN{hz%0MrJkPRAU}cp015|Cn!sl_NG-_EpfmtV(;&Zq_@HzE z@)IaLap?!if$RXO2k8Ta87{T-5C?@n$PFOBgZu(=8+zXb-5oIhgZu#UF9?J5g3wQ@56Jx>zrw^oVE{4@ z6sEZR1JVQ13&J2aE_Kuv2e}jEE|8rd401Eb&mebzXprAQ{=;W4NIys|vOhuL1u_d1 zCdlHr%mLXAvJ-?sY+ULn5l8nc$R3azLFpWX(b*t>f%M?B6C@AP2l6KfgV-RygY<&p z9OQRG=^xn)YWW{zCddsSKf*A`Z6JSwFo+GpAT|ht*f4bgV-Q3Qr&{A9%ettJdnFUW`g_)!XPAh#p?9pqP#JWLLx9+w>;^FV4r;RM1UHYo4lQctWn%>N)afY`+9 z!=?`8ACMnF=78J>%0D1KgVGf!3_yMdse$PQsRhY{>;|bvmV>E*(J-~tjC+vTAh&|- z2VoE!B!&%x+y-(3NDVFw@&m}v$QUFBQV-IL9A6;wKyn~^L16=u$7U}`45k(o?;s4) zhin!|4Y?TPHjo=Y7{tbA4#<8`+=1K)ic541(hCv;$%Fh3av#Vnke@($v6%^y1E~k; z1*ylS4x|^P7L-n?9rrM^3AqDgKS&KI>_P4Y`4t(1#6a>Oe}L42@+!z|To~k6kXo2N zkRC#6L3V>MNFTN09%LsWx4`5U6Rve_W}KyC&39Yllp$nvDJVc`j~3!ix)bs)Py;SI7Igh62i5+~J8 zLh3>GfZPH~)5!h@*#)u_WH$(d*u=&$E<0g%f&33L4`c^6wV*yEsLTPW1I0baeIUKq z)KV%2as$XMAUi6O>2blpf3uHbBgX{#^4H8E;0~9tOwV-?t!XP#_ zb?ExY;e*TtxdY@LkpDsUf&2|}BgkHmTgWjNn;wu|ATvSsfXoNcAU-xbKw=;{kb6Mt zKyJilCbh&s_JQmGl>;CQVuRdEE%RaKfXoD$4GM2i7=SQ{4H5&n0j3T_gVH@n9uyxS zH6Xnp@!^0$eg(xZC~iT11koTr!omUMSCBl&@1SxBBnJwQ;ox_eA3%Nt`4{9jkpE$6 z9mEG=nBPG(C@etgVS0xX4N42h;Q$H?5C-`hls1O5-;v`H6u+Qw0F^&54B}JT_Fy38 z{DGvL{QQ)H{QMkpPawc@{(zKJ9>XBREK*W=98ywwLQ+zBI#3K^gTz2`gU!9vau3LF zAb*1V>>(wUmn9{YHxY_KY>*g8j%Sd?7d8A3id&H1K>h^zd9##M-bE>?yw^|+VuQp$ za-eVkg%dUG9SmlG;vMF9kUv3w{wgJv_ftwL?>`iS*dQ^G94H)up!s7kxStX?fcy@M zOHjOn{0{OX%+I*!feZtXdqJ37KZEpw(mDu({0)nDP@EFucUYK^5(XeUL1hLAgX|^O z?I8Ui_ku8pO^P`nH^BT0%D0l0(NJzk=+AVUT?=H-Pkl;u?fO{s#F0l(%7iB9#W|2bl>n zA7n4GTR?n}ILO~1yFeJE7UXu2+mO}bW5dh==>^#b!XP%tY>*sGFNh8EE65HIMvrHZ zUQjv*VM_cB3n!2nAb)}U2(laI77z_`56F!mx5DfNg*!+suDAo~L&u=70{I1`7i10y zgV-Q3kQ~S!kli4=KxTvN1IfcM$bBF^Ah*KYO-&jUmLR)8VGVKzvim{)1i2IDCJ+tc zgWQd*4j&ujKbYS@_JA}s=0=W(3MpFDoidvA_pl}9-Hz?c( z!oS$;1jPd=AArIDo0uj zKyeOA`ylf`Wi2S2L3Rz4Kk?ZMiW5+}1i29u29%})kbY2l0+|JhcaXUt^YOW1Fvx-2 z2ucSaw}avjU;N^d1I0JU-ym~9{syJ3!5Z)AVGc?MAoqge4CD`7e#9jX(gREDps)n_ z8)PQBJ%gDKayKaMNKFIiejm(!#OF3pe36<4K=~b%=0WKl6n^;39}03HKM!iG$=p>Oks2`Usgl6y!nv0Qm>xFOdI0{=|hr z@*s5}^&ovi!GDC@2=WKWKOldB{D;f$AaRg9NF7K$NFO1yhk`uFA0YpL`~~tK$e*|{ zNFJmPq#mSiDEN<%8$sn5s5}FeYoPKCm)}9+!_EJs)_=pz|DZGsO3R=$4e}c(j`3lT z8jxC$9*|x_X=|{{lals9;R5nMtj#;r{ZDGz2l*e=?gjN1Ky5~l{=pu<_}oWo+=I$S zP#Fm-D?xhknLXI$Kye3(Lr`3T;uI9OApe2VHwc5+ATeYN5+5#kACz}NWf;gmAb)`{ zC~iS~5C*Y9V#C${p!5q$$1s0_Xi!=Jg$W3Q*dVdt=zsLI4@&DGzk{o2;q3VBZGkO2DuFsKOnb) z(l;o*g4iH`;&LlU4x|PYzv$%whz}A6$%E8^!VF{%hz4O0d$3@TdtiPCxfztELGcDk z2cU4l$|#S=&^%^kLGDA($At8dBM%B&P~HN? z3n+f571!wIAp0L=Cpm7Vg&vSwko^yGCny|17{o^R2Pu4zevmmZ42yq|owV=^sb+%G z7bt(h(m%+ZAUA^20tkcHpz;bNj*dZo!xa`FIgmb(evmmZ46+B5r$P3T>Q-v22jwqp zeh0Y`lpa7BlvhD~nER1wkl#RIf*uASF_0WcA4osQ9FU(u7-ZjIiF;5P1acq9tspmp z;ta$FiGk!maRj14ZU(s>8H4-;QU?kTP&|Y5gD{8<5(CMB^nvt)%zSGYkUEe#AU}gJhz%0Mr)RLpf!qmlGbqkLaRF-gZwfW+(s`qfZ`0~XAlOl>1FSzJyhDaPs;fN>7{w;sSFeVEawkU zU>dnvSr`}?K$wq#fdPc27#J9|kT8f35(lXv*N(xe59BA1-#{4TPmsUu7#J8pI0Qh$>bbo{VoX5n(oX5bxn#aQpWIgwoXn??!_R?#KgRqfq|iufq`MTgaOFkAPn+5hQE1ugh2k@%f!Tdk%55$ zhW9csFo41VTNr@M8jSIa%}pS;g8Ypf=kWOE;St))%E|`v^Fsy(hWAJq#K#r}Aag+G zfy~8b-(V60xe?@6kelIY9hAPAn3(r6FtA=^WRwN@6Xfro3=9m&7$gRgLk@!wXx#uZ z4`eRL{K4d3WVe9a267|Ftzf@1F)??7{LaWIvX_xj6XZ9LA3+%8@Ba)849FNH29g7* z8){*I>~~Op2gMtxJOkxttkli3W z5dH?)39=g$41f?sG9U!|v{s-AT z5cL7f|5{8;%piAkva+&);sxY)kQ+hnMe{qz{~(O&H&A&9@juM(AT=-y3O5ku0h#w6 z)Mo;R{?YY>*hl|DgUN$p5UYYy+A9VSWdNJ1G5u+_D(j zuS4@Y$n7A%z%a}|AaRi2kugY2^FITF2N)y545UW{6#t;G1@)UjX$@p2$Zn7uK=A-_ z6RqPOWCsX?>;u`09QTWPc!WS{AJlFGwO#%*GJ^bL2KEccPpJL@`3WQs^FN3U5`(D^ z1?xl7^OKd84eWnV8v`l+LGA##2jnhV`5$C92!rea*$47BDC|M$9uyCtxZlgk$qj0& zA^9EIKOWFL07?fSaS#Ui8H7P$0HR@W5Ar(*qv-j`!y^Rq|6*ue3$h>N7Lc1j?xThO zLFR(|4ZB24Gn;zz@-I4u7URL+Ais9c7((;)R7x?ezX3BnM6LBas!XBb9TkFE~ne^8zRVIFW8fWn@Y zl?_zig3 z@;5R@&eNcDfZgw)bb}gBurL6%5g=gzDsMsYfi!LmN(Uf!gWL~F3n0IMFvwpZj4vEO z@*uS!eV}*;`yFIH$loBlpkuY5_y%DVzeCzOAh&_s3G)-o-wFR27+`Ej+=BdviD7DB zG(;^T96)A+>;Pd%UI5hzpfP1=7=X$pkh`&k1IT|Mj4KR4a$vuM%2iNX9%L>jCpW09 z0i|;g#^P^K+=Kcju(m5GjgsPjm|tNuD6Eh%NF0`CK=}m}1|U1Y7(6}z3Ih=l2~ar< zsuw_YBe;FU%F2cw4j?~b^FK%&6!)NX1JVOZ^Ps#1^E=4jAdD2}5I=+B9T`K?KYBe5 z^AE@`*f2~U7Y)*bKMX+QB%pAB#uv!#;BesN!~6zfBV(92AsVCyIX|GM15p0}JWc{ir?C0~ zG}i*EuVCdP$dBk@0P;IX3=}?~aDv4>$PAEKDDe*QH^`63_JhIz7A_z;eEtXd6XX{d z2Jwl-Fg>8OfSeaV_Jh(RtR4o_Ah(1307?rWKY{!P@*~KvAV0&>0Ei6=7f`tg3MWu~ zf+Ox>@eA@h41@d&vmYjpp8sL)#zqtCcbGbu8K5u#)gK^t!0ZRb0UU$k4HO0-zkvJ% z@*BvHAiu)G0OWTN8x$^}@BxJrD7--72GWm|?m>P7rFW2hFbwiL2!reg*$Yww;uGtC zn4d_ck;4F#4n(kw9fI5gP6r^rfcymV8_16!zk>V>@;eBF*dQ@b_<+I*6keckL-Ie! zU-LFnOsT*a%cB9Rwf#MG22asPteggRo?gAHiiQVZ&3V$;u#e0Fu#Ms8x&3;agZ8V8o(9?5Vtchfcykw zgJ=++6b#Y>$_F5KfY=~6U|^6xKz_oO20;D?#XTrpfYJv@4M;6W57_UZx(&6=1Nj|< zG1DAE9mM~jxWSBjPo6%@aqcm~BcDBeLB zl=eYlAUTj4kXmTmBg{a=Ey(X6jLrWbaZvbx%4Gz`OamY{!!XS6AQ~hF@)M~T;(tU~ zBis#%KX5q+idRtlg8T~dKgi!8|AW{dF}(gqvxcTkXjH8^FPQ<7=8!EB`8i|{s)zDpt6o+|09P1hz$)}kon;97gqLz{EUoI;~(T+ zLKx&Pd>BQgXBSB1qw4P801G#`iEfDGysyLhyP*W z1M@qmoWUFSFn@sj1@a%rpCJFj@&Jei@j>Dsd5}7o9yA){N08q^{zOj$AUTjYX8eQF zCn68P+>eb0`3WYDEB;~fpm>M)9aJ`>#s${+2l)$>20&#Xs0;x485H+q_#YGo)b>Bf z?;s5GABe^l1|V^m-$7vws&CNUGWh(DNCP1EqhpZ2VHjBs-TxptnBPHhPqF_&`Jbft z2c>x{(xZ+A05NwKs0g~z}k8+zf;BkpgaJ|3k32%$e$oT zLef4c3}6@(CLj!{|3K*x+LnGeGt`{4eE`4{9@bPV!4tn35%9Te{%|HJYCC|p2lK>9%Dpq2f|{s;LP8N<>9 zNDSm2klT>`4+#T^-$8Q77?M^Xa`@OFJs`6|7{rEQ5FhM+kelCw<|ROL1E6&Rpt*7o z2C+fof}pW%(7JQboCb8QJ(2w%klz{5`oSQ3K^Wv`7zXh{800pX|B=%!$bTR?kQx{U z@o{088gv?2JxC5R_6PGfWUVoH9TKRX1C4`%#sEPyczhhXUI!HSApe7CP+9=x2|Q^4 zmi9q@1=$I51Bgb)AaM{TKA4D=Qnw&mg~p`u`vrmIpxLg&qc=bOoB*1I;^u#@l~F=Ji4LqGOnwKs3z% zptKLdpt1`&{z3l1hSALg@sZVm_#pE^7{mt8iGutO%iAEogW?n9Cy+nUG049le}gdc z_!me$s9XSr0mv*^SpX^zKy!|ub%^gli5omIJv#>;$%DcRlmCH6nm`K+e13<81K2NE(gMie$n`3S5A#2m2KygWuEX*? zTDcDM7tEjdXpmY^c!9zI6n3z10EGnzgTezH!@~zu-h#>=NILX*2u%~nVSy!oAp0K{ z1~3}rKZxH!?J5|Cv@bw%pt(;GP~2Yx*$r9`gywgUUqF7s=Vx>|kb00lP#AzPC>*e1 zP}qPla=3u%6;S;G!l3m57eVO~l0Rf&;eaCyV15SCFbs-o2!{C`L__=!n)?R(9kd1k zlt)4F5CW~!K<)uVq8MrXW4iJWg4QQ?$B`l!n5j6LP6b|5W5>!SY zg#*mL$TT>9L2V7BItS!ua2*e-gFxjl%#l!fIBP#7R#P#KPlL1`Pj?jPh&c)bHz1I`L+-#tXi^PoHl zvX7d6#}_Wha>(HW3I`B|g#{=+K;};&;Q%d{Ky9(bkbPjX;5~4V zumIJe;QAC)r(R@a5hx$Q>XIauaxMI4lf6>vkdOfCsb}5Qf2ZDyUwCVDLUzm>)s@1m$l~_<`aYhC%5I zl;#JU-(leb@;?ZJ!T{t>kb6OH2l)#er_eMEVuQqxG04v#wIB@AhmJw!A^RI-56oVY zXj0A}NXp62Pbn_QNh~oU$qu+;tmhAayh3-(!@!_r#K52xz`$S%qCtEX)ME(f?k4Cq zSDM=k@+-*CRSXPP%NZD~rZO;C1wg|CBu6iI41_%(|AG7t^7Bas2HW=x3|5a3;b032 z2auY9a2u`d1o;c(zo`riMkg5rwVEUMW!4xDmm4RVch5?e_!G8S- z@w+3)|Nj{nOhI7)O$)Zm85kTv;RMn{>o}o@`5=FQ{4*6A&p#O$Orib;$3MvbP;3PX zm&XhYj>9Dkko;~0O5>n-hWQm52B5G2VXOa$G~xJu7=!_m-}N3dFc=~E-3S^6Ab*1} z$nT)M0LQ->7;Hi5VCoRf3oySgXJ7#NUGFyo1IW+F802pdM)x}?Pk`}H1_noH7`TD* z56Db<_P{r;YT zK?~}4bbo{59pp!3e}mW{F&GB>9~2(YFaVW_gSu`+^1I%9)HnzE7Ze5{8iYZ91Nj$( z-TpH$fM}S%VKhh$2!r^J&@ceiji#Wq1+t6MbV^PAAoqaW1xoWseg~y}n7?5($WI{u zqGOOhabd7pXqjlboPogzWEaRzYWjg(vq0_uxd-ZZSegg<9fU#g4Z|S+f#Ms6!TtiJ zaXc8N21MKbgtV6jO&B2gUHLr&gYthwo(K6IInH5z_xaDj0K%X!0EvMxC{2K97zX(j zhC%8;7{<5#4G9CIfldcVe%FGQb+EWc_czGxAU}Zo9SHS5NDSm(kl#TV#0H7MFibs2 z4+z7=K{O}~K<#LwsSFHyp#BuS`_3TyK=xuu^Mw2k^FPRcAU}ir4#FTd$ZsGFk^}iU z>^}no2!r&1#9$bt2Gmvt^_lcQc@Gp9LdR%Fn};fA4m*_L3%;zO+jVtas~!vkXt}*BF8VJ>H*mU zvJYf0jyw;FPmn)A?u5A+mPX5Fdm=@}PJJ`5lBoY>*g84-A9Uf&KrVfdO3hPGw+F2IWam-Xz6O#HuCI?;tmU z+y-(d$ju-Oaz6-T^FK%}2!p}^M8ouf)PnS2^FK%(2!qlYsGiaK$-tlt%8Q`50lAY{ zKM|`A6sDlCg_d`q_=aIvyo1~U@;k^)Ah&`14Z<*gfcyetgD^-OKVokl#V}fcy`#6XXVvd*ERJiYri@5gWhw z)WQ4?jeC$i$QWcd%4 zL3|Jf$$`{i^FPR5kXt}*0@Vjvptu61S5VyHi(5i+ApIb7py?iF2Z)AYkl#TV;qv~7=YXWatjE9+^j`zoe1+gsE_p?GF}Zb z6CK0C03;5w8{`%c2DuI7Mvz-UZbru-ab%1vkM3u5K1eM{FDQ&b_JHgIVNe)=FvuMs z_khwTDBY6WHUy;=P?|yVJIqWF4Z!J9Rk&gEYubAitrfb96o|?oTo>fYJ~sjzQ)jV^A0%W0)LB z3={_B`XA&sY<@--1E~RFSQvo90c0O2+(8&*Kgd0B|NmrQkUq)4APp+VL4HH`FESq# zKA>=d#y!aI=on-+$e$p)Kp4aZiNP?)evmlGT_882W02cH7?(Im4%xrxY>+$%BkKp* z1q%a^{UEo%{r{hVLHa!dgETQ|02Dr;a6*cEWdDQA1^E|b2gr{g8pH>Q!!U>sk^`kp zWH%DS2Kfty(ftkM!{pJ;LkA{&4?u3g@ISPVh_f8PlJ-I7fH1P( zL16&mgD^-AmOnsj5C(~Z{10*mDgFn!9fU#tfMJ+FLH>qem>4<@(*v>-RAv5u|Z-WIhg;kg#pNq$Qa~q z5Fg}j5C*Y9Vjzs}M|^ycdJqQb2iXsD6FA+2{0+m_>`wy`PY|8<2ls z800Sy2Kfz!L41%r$Q}>|u|Z-Wd64}ezr!%dUoZ@EA2y6E5Ap{H6XRcyI%Eto8)PrY zJsAE6g#)NAsA6DH2BiUz|3TpZt@FVB1}yD+WdDKu2*V&dKo}$s!XR-_`T*IFj*vJ>WikQ+c477idb41?T=j*;b%{e{o}=w`t9AUi?s#N~fbdk7=`q2)fzFW6|1 znJ|nl4-&`bf0)0JX_z~aX^=cJA6FP4izBOtu|ej5@*l{3c>NDb^T_ER>VJ?wKzczK zREB~uhz-IZIT!}KAa~>SKd7CD z!~Y<^fcyqB1BO9-5C+M?Fo+MsFfmyA2e||09uN(}=l>gYe30EBcYxf44TIbZ!pMGuu|eX<7$y#)k^K)U2S9v~ILr>R{140jpms1Q z4G`gfm_N~JkiTIV=6__r;A4aQ0mA5NKw`)k-2N}cC9f$_G1LQ7{`;graVuQp$7$gtEAT}`= zq!;98nBPHskbaQAK^SBw$ZilO=zmbz4;mW>rFl^K2lM|_1_na@NA@#_4YCgzgWLgf z56EpGe}UWyax(~n+z;Y|#6j}N7$ydy(J@FJNDQPGhCyN=yI>gE4Iux6;tUjrp!i3@ zptdh4?}PjbO7kH9gD|KJ0JR68^Bf?(FbpyaghA$l{0zb%`(Sp1Xb>NSk^K&HGsy3t zzAK0Zg#kz$qz)Sf`3r{8gy&xK9Cx`}Nkli46fiTFOFt?-l9W+l4@;_)y9J&7k z@;@vMfXV=9{}<$E7zUXMG8<$M$UYE8b{~unavKPP+zE>-ke@+*2ZaHM4Uz-t1?d5q z2g4vf2!rH67{o@F1Brn!vN%W{#s|3%gh6fv`5)Qua6TyRL4BV9r2Zes{~!!Y14v;2 zG7E%3=7Q`3VUT?wcVNRHzk@KykA(aWG7Drb$b1k6u|XIljt_&>z%a5pm>9?`xPL)$ z4qcN5p0@_2?<(kC8&JO&hCzIE3{!_J4pI-}qsxQjF#P|UfdMoZqzCgm$e+mmhOt3m0xB2L z#=k*!f$RZcnEzok$S=r#2eCnZ1YwYWK^PPUAPf=%VUQk>Ss-&k802PT3=%`e$a2VR zTx!t$ul$&SK?}B44wv6yVE~c`m4&b{fc6PMc7fas!XUqcFvuSu|Dt1%AJH)=96(|q zwIF>U{UEbI7-Tj+f5FNjWE!R(T`x!tivK}*9=0|JkV~{^V7$y#4gD{8>(gV^D!yxm}{Q&bDhz9uwgh76SVGtiAhm1jD zFbvM?pt2rX*MrCVKz_pKXLLD`dQe_~h6AX70N%3&+Lr)bvkmeOItKX@gwe%8d~ANl z=YNoYV17oYL2?-W2dzZ}&3l8&IZ!*Y0Md&T4vr@o7{L2;eljqC z{Dh9t{SNXoNDVB$k!hIUam7E#kLdnJ=7Z!xV|Ji0%ke@+r24Q^uB2^A#9>{D^*n;c=?Mpeyz~BbL?->|;K;Zzzj-Y%3+Rp}E z-wN_83}f>@C=5Ut=67(Mg3>cK3{B&D$bJU73FJ1A8%gySsp>&?fb0ULFOa>^um;E~vN_XzwdBUe3S(-sb`{2OABt6J$4~exR0qP#*#v zgWQN5E-*GIT&frtz-!mB`3)on(hKq*E=(17lXCt*aY<2XYBu~d0n!<)5G@W24h#y6 zEDQ_|U_O>J{vc=Fp`L9_o){w|<9}S3nVA`<983>2+(1t=VD3hyLH?u!gUq6*AIPx_ zXzfiTE@FpTVWkbgNjIsd~jH#hfxd>Eu2hLO#I zu|al#>_x{QcM)-{{jL6U^a{f zi6LW{0*W({sv)?J3#3b14B=jF0Bg2Kf_)#l^+J{)W*oab#LlRP;Y`Sb)rhg#*Yw z5Jpc6)D8oX- z0qF-}- zWd6&^$zjC`3JP#>kU9_s=|c|-SiT@N49N9A$loxG?st%%VHo6ZklT>`4e~PxgZwNn zFaICpMX%@%p{S?`4l__#fXqTp3n04)g#jquKz@f|V*QWqcaS;A zeg~B~={1!0ii zL2d&19ppZc-$8K)ayuwKL4F2dU0vP(AZ%b@@ZZqT5G&Tx)B6v?u&_W52T&Nu%F2Sv z5s-bLFaYH@^mG7nBgpR{401Qf-!M!}J^-mf_d9yJ2l*R>VSWd>2j+KBe1Xz1$nT)I z1o;`{Cy>95jg9}Cn3()GH8uTjZf=f&LGngMM*l%-L3%*?L170n3lyH9FaU)wD2;*a zgoOdfEuc67#SJJOz`_9Je~@2@^*_k(APmw2!l1Z^`5lzDK<0z&0oe!gJIH>JJJ9_O ziZ_tkL4E-F1>`4?pDisd|65yI|F^ZZ{cmq?55{(OcK=}*ByM40@!!nM>_144zP>&< z&EN_HP@V(10h9(nZUVUt|ZZ0um8TjzW)OQ1OEpF1%WY$4ZVD9C_RAE5PBG(=L3+tKyd?dBeMU|(*Vei*wO$<92Dm;jO=$%{DaH| zweLXofcy@NdqRE(`NPf4?LWxhAU}qLg#3?)i1;596Z1bVF7AJPd^{M(#>V~+4-fwz z92^V|A5gjgr3Fx)0EHuZ7=YRlurPq70g(Sebuugsfcy@^Aise81xo)QOsfAuX&>Z& zkl&Hx9#kH~(mkj?2c>&Zo&}X-pm=w1Z~(`px3@RgUm*X1{G6Ja`adHh<9~K`_W#`6 z-2XW_IsY>=Gyj9cLE!;X7Z4Ee9~K6nG6Pg@g3I~`yl&3en*abP@4vnMnQ28@;k`Q zpfV1Wr$O3)ByT#XmOxBgZ`` z&x6V`P~3ykG$?(8{NCEy`hU`-N&n~1pZ|ZwiWUD?uU-wNLHyp{-v12^4gbr_%m0JY z0Z3nHXy|`X{sDz2s4N7f0Z>?j@*8>{0Od`PJ3;=3r2$YJg5sZ8|AXp$kXlgugZvK5 z`yg{+{zp#xpu7mm_prDJrBhIu2a0=8`URzXP`)lGC;l{10*u%>SUY4=T4o z{s)!ipmH5l)`8MBD9wZ78RYk~XV3n>apT7STeoigzk2oR|6|9F{ok}{)Bibh=KKex z5m4CxN&}$s5>!uu(i12Tfa(HJ+XB`e0J#C#|DZN8D6T+mM$QAEI0g9+RQ`kfik$yJ ze2^Rn!~73QH=wYC`5#oag8UB(XPEy%bv`H^g34u(|3PIm%>SUW8C&bDd-m-4zhuc0aJf)ZQ}aJBFYiC78~}wQ z$UIPeLdgH1G)k)fLFFIF?;uR7|3UEp^FPRKF#mgcdj1FHeUSS>{;#U40_Xcxt5$*2 zJSg5let+@e#s5c-9)Z)q{{8#^gYw42i4*_V*Vq5g&(83@_$E1$N#x==Yq>RP=3F7@gg`JKz=`b`0)Sr z>(~FEH*em5kUCI$0kw@l`q9%rD80e_4{HN}{11vBP(B646)69M@-GMzD*r%X080BH zj2!=jTj>4=#R1I!pt2cMN5T9LYVSrxMg0er<3&Y9;QAd@*MssnsH_9! zcTm~Cd-v}D8#iwJzhJ=vaC@M!u@T%p0@axy{h;z6RQ`ebiJ&mX<$sXdK>i20msI~F z$3G|xKz;{dP#Fpe15lXa@;|6O0xFY1{s*;DK;<;3%>rusfZ8yyG60kYK=nJQjsv9w zP&k0{JBSAHL3Jaj?1#mDa&j`bPXICpRHuOIR*)Sa|AX=!DE(oJe~{ln7!>y)3@ZCU z7!<$gg2gN(c@1S-tNPTp4G`OD#s!Ks}4+>jQ8w(WPpmYcFKdAl(jfcS6f1vmW z`5hE*Aism!F)+Vl^FOE_#O8mH9%TQ6(iF&SP#OdIA7m%UZjc*5aRJK%p!5lfCr}#* zlm=jJKu~`Vi1nyP)zH6c@;802BtG{vXKQptu92Ur-qiice6Q2IXr|IDq^LY8Qeqhz}|! zL16^)JE+bB<$I8RP*{TE9#rmw+M1yL7AV|7;SXy2fcy`N7m(jU{S;*XgVHg`FCc$` zFuLDCVF2oFv$NPGePFV$^ej^ps)wU0mv;NH-Yj1$c>&w$i}^nv^iDpNt}9%Mc!oI&9Y zvKQokP&|OjewhD3?gU{_yusq0T>pc@4&;AOUILj(N*X{815mpK)P4um;h;DLg###U zgZhOa|AN~1AU??7etv%7aUxK82l6{C-Gjmv7WbfbC#>89#RJUmp!fl~6XafG|AXQb zgwe}BP~5{XDE>hhqz)AKAPnI=R~`U`DadT(Gyn>B zSX&5`9zpp4lr}+W6qHs$^#LdhKo}MdAb)|v0fa$*1+^7G7!=gI6&nrnKZpi}8O;A6b3pzFVNm#j!Wk6apz;Qk_CRg{2a0!)-5_^>+yinK zD1JbB9@+08e-Pt$Sa^W^4#J?Yg83grgUkf^A7lqC4S?(hg#oDD0df;4KZ3#lR5pR! z3vxH8tOof3P&%kQA_j#_aK3mcgKK{Rq0Am;;YVE~E~Y~cX%19Dh^!UKju;e(7p zeg>%pwQE2aS2&Ov4sODEC^wcpFtQDcA)qMVUSs%c0I`7APn<2h=%zc zR6c>+3Bw@2gW?X^Po%OzeurU@xgfv8FnSmu=Lc9gz`_EQ7SJ)sk01>4CkVsjLH>nd zkbY!;gVGwv&mau)Hwc6L4Z^ccHV9-H*;j_9HSI zT^-EKf%GG>cEj9CEDiE2Of51SCI+I3wR3MSdqHjlxfz5(Y>++>4Z|RRaQ*-PpXdMo|9of| zqz8mS@*p{(|NsAkFi0&(926!X^9C~pxfA4G5C*Y9{sm!>+d&v4F7p5Xf2sff|113e z|6d7;<^KQwF9!85NM7du|Nkog|NjTkps;|MF@Tn1JL#e2~1x|Ns9%dO%?b3ctbVe~`aH?gRM~ z6tAFo1H~&SPGK122ar6>Z&v^R|9ANR|G(Y;|NlX3GiX?V{0+i3P&rW8fXslUfx+f~ zP`rcEG$_77X%*ymP`rWMt@Ho?e-H-6HOLR3umQ0_eslZ(|G(G&|NlMy|Nrmu|Nnod z|NsAk#6aNzk_U-{Fo+E@1Ed#}en9pOrZ51x2NbU$|AEpk$gMCxSp5J09~7^kumH({ z;@$x2SHJ)N{|Eg4{~tvA{Qv(Sgh70epFv>(3L8*(fcy^%Pf)mm(g4W5!Q_8Xo&=?B zko!P>1Epn<+d=*T`2`fWpzr{N0Vr%hG{}D-d60jD{{R0U_W%F?C@2PrgXBSe2hkw^ zgTfFLexPt3jQ$6eaUlPL+zE0w$UhPP|NjU14dfq?7${so@d&~oHpp+Fa001``~Uxc z(*OVeK{QA`s9XTagD{8xepXqApa!%|NlP?>ZcfJIDpat$lsta0%4F^ zP`H4?D&_zG{~7=P{|D)V`5z&}nkl#TVlqW!9pmYKXE0F(@;~%6JghA>+{s*NmQ2Gbux51MCL3t5WCWFd8P~Hc{ zCn&x_{wewY|9{Q@|NlWWC|*Hn9po1n2B`&=eIP%B{0>S3AT~%0qy`j5FbpyqWCy7H z1J$*I#s46;fbu@bZJ=@u|lz2kb6Mx0@Z7vat-7skUt8bes6|`1I%Bb zFaW6ml@TDngVF^k?m_+sse$DakXfMo1Zw+%+EE}oKxrS;#sa0e!4m(V^ayJ6!2A!Y z&p`eu`~Uxc?c4@yHI|AW#$ za{LcA|AXQJR3?Gk2`bY;aS!qbDDM0J|Njs2`(!l#gZvIE7eVC%D6Bw!2I&EX0Vpg$ zWh2NePy*S^?<;#XpD!r5{ip0;K^^`v?>U$Y}s%FUSobx6nKOLGcdqGblfT z;sccKVQ~*?!+`t_YUhLe3aax!=^i=ngW?|41_t>Z#0FuI9#9?t(V#E@r5{ijfWi}$ zpFrUNY6FAXfS^18^FJv6(;^H&W@0lNBnB#bKy@4_PC$7Rlr}*$$gLo^gVHm|f1vgb zsJsXHA5{Ki2S1)y>eq!)xiY>-)?v;YcISbhN21t9-}(jF+^ zfzkjd4}#o9&2#`V4`endu3_ea!USX|DF4CII;fli#SaLB%5RXnLH+@yZBW_=`56@V zApe8%0f-F>6A%W44=4?Q(myD@fM}3jkQfMq_@HosKAismc6l5kS3}F5TiNVS_P@V+&7ZyjL^a@J5AUA{3J*eCR<#&*uL1iB(|AR1$ z4WdC|0179N-$5AUcTjo&=>^pjAQ}`7AUTjdpfmvTKPbzG9r7KV#1DOj70}uw;1M)klTn5EGC|!d5 z4N9XRH-h{E@;As|p!i+*|NsA$|NsAA52ZnV28n^_<?-kP@Vv#6Hpj| z%mImk>;R=NkpDsD49L$Q45|-67{msJ2dF#-rB6`Yf&2pU6Db&!zF~d_`5TnBKxTr{ z8p!XUat`KqP<(*m9fU#d1H~PvYy-I+6yIzA|Np=F|Ns9x|NsBL`~Uy{+yDRnzw!V7 z|Er;40ir=+0V*Fr@eI-nqCxQv3LlUeAhST>0;(54bpgo#pfVR!27)liJ)rymqG5bc zSb*FP3IkA@CD#8SwV=2M=>_>4l(#_X4AkxfrFl>|BjG-1(aSuX&>Z&P+kJXJ1DP%>;hp>TNIQBLGcapGsrEV_yCE4;vbf8LGgAF z>gT8b|Nnpf|NsBH&~Ug7r9t5UN*k~|u@{<7E<^Q!!V4r1N+X~$6I2g?>;Q#3$WD-- zLH2{(0m2|RfZ7Nk43Y&a*LHPkhgYp~*gV>-j zfTc}PT!Grjpm@a>|FF0Rm3g4@4ixtw|AX>ADD8v%4hn0KxiC9Hc^(w^pfm#VGsyoS8iYaq2jzKCS_1hI zRJVibA&{M*bPlo~gh6>8BnEN^$ZeqV3Y4DjL*pIdlmGugeuL!$P#A#x4~kz9289bK zjlk5xXiz!;r3+B{0EGdltN?{CD4ap|fiS4P0kyF}X%FNMP}&6fA5=$!{10lgfc%Lq z4S>Q1ls-WD9^_|G+=J2qhz9u|l=ngT9b_iR@1QUS*$J`>6y~6M8`QP~VNkvYrF~Et z4vTx3KR`4nJ%i#Ckdl&{hK1d4xJ=9w@zl>P%4E2~_ui%0EyV0F@=6auAdUK;aIG15kW}{0xd0P<(*x z|AYJh!l1MaN&~R8kFE|E9~5pNJ3;;j`5jc=fG{ZDL4F5?1IYiNvt6KKzRUL834)ypl|}kKeGQp^&iL_P#XXgrl9f;M1%L2(Zf$K_viagaVxS_AnX9%O4@yIzIsg=&pfVLy*MagLEbW8RJtz!7 z?KBV@l=ea8JIH^?egm;V{s)PJXmr03!w2aDg)u1XL2&@e10W1akD#ysmH#03f$}~m zzCdmV`2iH4pm+uO50u~0;~$ghgUqSu{ z)pejW0IKUiWiqH-0=Wwm9-y`gC_jMWnpnRPs}2;VpfU#(_MmYEP+A0)#h`ou%KxA? z8YrK_{0~Y4Aisdh0Z`h;=6_I~2uc^QJOD~BpfnHaTY}p4pmHBn_kr3#pt>LAe^A(i z(jh1eK>ZU?dH{t5C=5V-eNbKir2%67MXWlI|3T>son;~(UISQ!9HBcL<`%I_d^KmjLAH;@*0Vs|@X%&>-vH1-o4#S{yjZF=@7)Twc`~;;fP(1*$4}?Mf2e}KB_Ca|R z)E@)oe^4BP+CZTA2c>CH84t?q=>7+Zfx-n;KY-djAU&Wo1jmm2dr+MZ%J-l!29>ig|AWE+l>R{e2jzcIT!7pIavLa(g6cs~7=YXj^Ajlk zKw_YJ5`;l&kmX_Ou+boOAiW^}gUVWvogn)`ZUV&*DD8vd43u_3{s-k>Q2GazeV}q3 zQ$KkL1iu|3_#%y$^)Rd2Vt21L1h3a z|AXp55C+9D$UmTX2KgCQSAu9zT87Dk)ZoJ){h;^<=>g?0kUgL@2&(%*X%rOqpm+n- zS0KNG;vbavL4E?&ZJ;y`@;eA4`yZ4ZKw$&(KS&QK3}OBU<$aLm$p4`DM^FEt{0~Y4Ape8>4Z@)K2l*LP7lHU7_kh@-vN&iqfYJcS|DbdaO6wpD@-v7J z!=SPlBnB!6Kw$uKBM5`i3aAVLr6Ev$1*rk084w1A3rIaE96&Fet1*VFt1nOkcN$e$p; zgVGYHOaZY$W`V*06fU520ir?q0@Qv3sR#K96n-E-g8U1z3&aMAf$DjXI*{2Q8kC+u zbv>w!18U=e;vLl10mVJ2PKWu66dINfKgUWkY+=J3GDgH)Q3o0`};Ri|wp!5VPQ$cP6*$)Z>Q2c`22L z?giDqAaRfzL2(G;gTz7p1Gy2DhCulf#0J%^pg04SQ=oDNlxIQd1EdCI4~Pco1DOp< zTcB_RnFsPONH53?kbaPPAibb?2i5T)4C>>-;vSTik^Mvp8>AN$exULY6rLb=g5nY6 z22l8e+y?R=C_RHPNE{>vawjNmKw$-PD=6K7+zav_$iE=}f!Y_Kd7#i76zd911L{{ z>;#25$StILN&qKZE2!YC-7~0K>9&uf!qvZgZPwUka-|?g4_lQV~~9y4DtiWO&~YI z!UjZx+yshWP`&|SkUK%*p!5mS2TGeD3^EgBHoE&j;vlm@7-T2NFCYvO2iXs@6BG_G z^FVUwW?(ZHT@5LGka@@$WG9FZaudieAa{W<$h{yw2!s3z!yq}3T97^%2Ifwo$>$GbWaea+kbLq0L<1{?a$rE5KS1hv38U)ib1ubb zIFE+&Xc`zgX#jNAC^n2Q4ngPsz%WQJjEzsvV3Y&79UBInX9dDAIhg-oe2^Rn!}!QF zOf85WjDCi>3E9n{Gt593cHSE{Hs~Bc7zUm92Ra83M1wF$9Hti(E+7o@KM2F@8!R-) zO(6flFv#s7zrfCS1f6#YJKqz;2A%l{qCsa~g2X{*oPuZ&2C+dHqy}^bCP*E~9FUo? z@BrBZvJ)AD!XBAVudo5x39=jHCXo9;?gY6Rgkk;zons3-&lYsHFX*ga&^g8+Hs}mw z*g4Ojvz|d1Bo8|W7^Dvr9v}=d4-^)lumzI@26PgUvUSb)MC6c!*i zfWiWVL2g102Wo`@$lovwvICaZL4F6tH7Jfi@dfiQ$bX>w96)}7-Qxke=K^%^2k2fb z*gYShyOBWm!GP{T0b$tvC$M|IK=*}!!Ul9-0w`^O!U7bwAiF^Jg7Oy#gWLg18=$ZN zxeeq-5C*xK8esr32Ndt1umz=cP`HEa1^FA4Z$ar86jz{l1Kr&L@-yh(8rc0NpgVj( z{sj3CbdMY89wgA+MxZ-yKzF-=?*0MY0|dJN2gC>67YVux2c!pdKMLqh6i~Q zpnDQPVGYVxp!5dHA0YQ2rwLFvfG{i^Kz<-6EI|4|eg~NYN@Fm;gZvH3$Ds5LaueuI z6Hpw3?r{OdAIPtudwxLv1^ESZ?-R&hpgWI1_d9{^w*uXX2)g$Pbk8T~zEcnlx+4xG z4!SQB6ds`afk1cTfYJvjJVADV!Wfh;Kw*v?4xlsv%8Q`<0g5M3dH|ITAh(130mGz( z1G?Wq`45!-U~vv|2k71wP&|R$3A(=s6tAHBoj~_qg5nkA-}Ll!@ZD#i`wT&T1l@rK zy6X&d2U<%@3-~VFuCA{CZEbDfyH7##pzr|Qi3JKfP&k6_(gWE63S&@S0;Mz1y*i*c z0NuL-iW5+|3knBV7=Ypolny{?7v_HuO>7u|^nvt)!V?s>p!5cccTgGx<#ABj1l?H# z@;B&?H;|t}=^1p77bqS<{srCf1;U{FA3=AVf$ls5`5AN%An3kZ(A{q!47&#yBnG-4 z7<5k_=#FHN8KCFELAc?=4JsZ*!^pEYaN|7p{v{hu;r3i$3zkT@u9fbNC^=>z2xP@Vv# zCr~(p$`4Q;1C^nmd!;~e0m_Gl4T^ivJ$ImV54r~w z6wjbI2l;>I%$eYOZ$W&}-H0Fz$`4IVP2hV_LE#9>3n2f4%1Dr%pgY7s_acMx9xN|_ z>R3=c0m`GGGyqDourvUQOHdeq{0_qS{11``sfEQosEh^0Jt(h%(ifNTfzk))u3(V= zLFz%_2}%Q?as(9Cp!5a`d(i!1u(A=`QXWG|>p29@`q zG9HxvL16$&2Oz(LXpnnB7?gHFa-eVmm5rb<0F?`%Gy=-wpg0EQcTl+q3KtL`Bo2}X z=>?eq(hDjFLH-AYJ1qV|WdNvd1i1_3K9K)G@elGpsJ;UE9~6%uKY`*E6bAU>9wZO) zJ1F0P@(-xo1C^_wv=7Srp!f%syP&cURL6trXi&KiD*s>@;d^7RQ`eLV^A6d z#RupfLzo*uG>8uh8;~3*u0ZYvl@}lyRDOffFsSSR#V4p;1PVJ)xPaK8umjPc@B*m; z`5R;&$S#nbpga%4pt28?7C~(gP`rTdnghi@D84}P4@&=6{eMvH2e)4#J>x z0!sIwIu8_vAhSUE2~_8U{137Rl-EFQQc$`Bwf8`70l5p5CqOjJouKj=BnL_>pm+kg z7nEK>^*0EE;tv#$pl|_UP<{f14G4q64#WqU1yTc|L3%*?L1uyM2KgPXkQhh~6!)OC0@4H03-Ujx+y~`-P}v9aKPZiX%34r5 z0ND+y3t(XZN)sRqavz8WVNhBDVNe`_FevOm7*ubAFvt&}Z~=u8C@eu?2J$Be!^A-H z$Yz1;1KAD2Ah&?}1|au=;vVEyPWhKG8H8bF0Vo_m?Ew%5g$2w_pz;WWLGA;k8Bm)EG)Pl?a#Sy5C0+=eG)N9)E(n9n1la?kLFRzW2AK=; zKd9{kiZ@XF!~74+|JeKu6NC956#t+)19V3b$P7^W2jwYH7=YXjDhEI`2!recVUQR| z4G4qSAhjU>gWLqdpfm)M1EnL7dJrF^2E+zokQ_)K$R8jK3loqZL2OXCg8T|H6T}D6 zAax);Ape8(gUkeBQ2c}34NAYT^bhkt$d54pqSGKbQ2c|`g5n=k{(;I6P#OT43vxRM zgWL`2`fGpu7#j=>CTBVg3iDe^7e? z6n3CI1TqU`Cdh0M2DusJeh>z^9hBZc@}O`6$-yv83`T?0fx;7nLFp4zwt~zC(I9hR z>Okgz)PgWb97KaK$XpN~gh6(LFep4hav&PyHjo&|Y>*wG^b1PIu>245I|#%4i%x^e z22dD)>I#tmL3%;;KPVl6%mld$ZPgWLtG zlR<6+(I5;8XAp+TBhw%@$P5q$xdmhn$SoiYV}tAgxe3Mx=>e$)nF;bc$S z(ftkMgZvM|AhjS2N;e>Pg3JQB6J{>R-ykuN`5IZ%2!rebiGkb%!Z0>S4rB(1hQ$ZSTo4;% z2S^``4RR+)4n%|GKr{@4#6jvnG{`KFnIJnr?gG&;J3(xa7)UM53{aW|`3;o5vH2S& z29gJ289y{gXCcT1Njvc1|SS_6Nm;!_5VM$e$qpf-uPcAQ~hFk^^CwT9A7| zY!D4H1B8*;AU-yX?mrM8=6(fO8gD{8;};wIb;m;KS&OSL28E@hPfRZJ=FX{u6swa-^mTLQGG*a4d-ZFjmFhz8W>FjqiJ9? z4UDFN(KIlc21e7sXc`zz1EXnRG!2ZVfzdQD9Mb^F=MNY|&JZAr96f*F|8Pu0qkbAK z8%NW?Xc`zz1EXnRG!2ZVfzdQD_|pJvE`OxP6+ZuiowVgD^;LC}EJBVSa$oAb*3_D}vUq!f22fNFGFk#6TFt z28n^p7)pMIxfSGgkUu~e#s<+Ke}mSygVyhZ_8WjOXdO2Q!^A;yAU;U_Q1d^?y&w#7 zKgd5I8iqk(0TKi4qX6yG0b$U763|`~5DnUE1KM)~VuQi}lm>gJ_UH zL3|(@j-hn zLHjO2G|2xTzk_IyJk0+fGeH<;$6%&G&!XWlw#h|zc#T&?fAU}cp4ceCu+EWekKPb*Y z{syIcP`Uv59TWy2wIDs9bOXX5GeBm+@;}JmFg#fO4vHsGoPo+YP#OmL4YaQuw7(qW ze~=$RVF1D)Hpt%~H6S)9OhDxYsC)pK0rER?+z(biBfAUaW{~?q@d%1rke@;434qQO z0EGj{uOJNaFDMK^YCvHCN(&%8F#m(<2v8dVg% zfM}5aL1Lh?02T%yzk|X86!#!~pga!>Kjioy%zg&B4dic7T!GvSazDuLpm+u4dywBj z=S+a|Kgi!83`!HAZ~&=6_CF}UfWiQj_d)pwlm89L0NDddUm$mYFwA~>(V*}L*$=`Xx4`(IvIUc7o5@1MxxUOM&D-X#x}uAU5cnG7t?4A5gjgnFk63P+17F0~E$E4B~_A2Dyh; z{)L$f%4ZG2e|{}XHb3x(IEGMXi)tIihGcMKz;|Evj#e640OKS z#EBFCgTz4L0P-^^TtH$Vc@PGPfx-n;PJ+?_$UIP90Oco`Js>fVU7)ytxsw(&sH_9! zGmzgwc7gm4Dvv;Z2gMD@T_87t;u}PR_#g~Q&!Bh(`2}>o-ty(k|1Vs)5PsGkNDSm> zP_CF|Hfzm$6 z{~&un{sxu1pmGP4{z3TiF!mu;|D)(Ui2iXlue<1&Z+y;toSUCprI|zf)ILIF$|AG7jihGd1K{QN!@7}%O zc!$v-wV-eSg&8QEK;a51M?n~54=Aib=^vE;KyCoJ2jnhL8UUqNm_L!(FfnpykU5~V z1qxqK`UlwuD*It^56S}|cY(@zP?-hFv!HYfOADZM42n~be_Kz z-$678gVGAf98lPT@&G6dK?^*Bh3Gxv z#XTqvK<)sgMOfT}{0_o|{6daAC`>{5ALf5p{Da&Bs{26sALL(<8(|m}79cTD7=YXl z!k{n$VURc|jez7pc?6V3Kz;?K3y^<7d{FuT@j)1*7i2CduYu}akiS6~`5lBo=^x}DkiS4_6of(k1cd>}-5_yL7=hX>pnL?XP~HHsLFojPWDHKxTl%LFRz+ z8%RIM-=K5|%JZ-?k5>N0W-chaL16%L2PhAM`~vbb$c-Ss!1y3PgZQ901Lavzc!A;# zlzu_w2Z#pw2NWJKK1eN0A4o6AESNkBQ3>XWdX!3IX zVNg7P+(Jvg;xZd#FUWq7`$6sl`3K}i7#rkvkQj^)VuQp$e2_Yj-$C&QDpNr9D~JY} z2NDBekpDpCEr5WWFkzS*L16%j9}ot`7090;H-q8`6t5r*iZhUU5DgLs zv9b9DmpC@HgFy`BUXWiwaSHM`$p4`5z~{%o;BIQV0XYmneg}mID4s!>n)VEKvq0eh zi+2!vr1>8dKgj+Du|Z-Z4UbfRkF z>fr!#Kgd5Ie}nFa1YyvSZ&4zMr)g$D?O_#g~o55}+oxebItet`K21|YY9{Ev)5ZUp%kkPW*8iYZ75C(+_EDeD4!7#{77zTv_D7-=T zf#`wqJIr2?J3#&fxew%CP#l8%0ir?S0P;J?Z=k!qL3ezE{0j;T5F2#II|zg1Kw_Y< z0qF(l1DOTFAT}sWKx`NdvJVyxFn0_T4N3zb_khwW$j_j7h4}--2Kfu*XDchK{~*7D z?tTYhS6A2n9v&WG47&FoBo7J)kRDK&!Tb$kgD{8>!=SJL**O^EALIs*TR?FGif532 zL4E*XkiS9k48kD4gZv2cYjAMz|FE#I{{aC3a2gaAAUTj8P#S>w85E{4HpmVT289DG z3_#%z!XS5mFo;c$v;eaQWGBe~Aa{cN42pYDIDlwSe8T(;@*4<){GFVf{68%%?SEoo zBAAYgi-U&)D4alH2+|J<2asPuW`q0wS3Yy!m-EUkmmE-21HX&L1A*49>V8VC7% z*|KH-w`|!0_WPkjhyEWqa^ydV2BiT|nkX+X2bYPUbOR~_L17BA2joYPy&yM$Fo+ML zLH>o&AT|ht+)4`!vJ(^spmYdw6U^VBI0wZysO$py8I*oO`5aWPb#!!q{SJz65Z<_P zBRI}M{yu*E_1Ef$}?u4NCVQKc6~v3hsZ99LWEmx&f4L zKyeSU2V^J6@36QB`J0yh#AY_gPLTZ|zk|{{C{Kdo9OOoj-$7{|6yKn@2Bl$8Sq}0G zEWLyBI0%E%J*d2cg#jqtgD@!ngYpGPA1LpG(mp6Zf$RhM9TfH;8iYY^!RAIl7ix>X~VNiMp`5EMQ5C*Y9aSx(F;Q(WU)PceZlzu>A z2}=90xCi+igh74>(e(B|$nT&$2=X^5zCr07R4#+cI8Z(X`5lzrySlo-b=tgn^ZtYU z1Bzo%yn-;uuOJNaH^|Q*Iaq!G#XG1S0P;J?EcCdC`5WeUdio#acThZl(mE(kKxrM+ zjsy7{RK|hwD#-7kJP%6upg088TXcn zgZv1hL4E}J9Te9fKFF`2@&Tk4#0KdD`5S~mVF@bpK=y#_1C=o#H-Ov%3Ih;LPYeok zkl#UN5Gaqs{0&NL)b;vSR+LG5=?yny@-OY5LG2e}^v|3Pe!I4IshY*6|D#XHE~pfUthzJc-*s2l;s zI|zf)J*bQU`5k0GJ^hPp7bwqx;vQ7bf$||JUO@2!azDt=Fu#LnP?`q$6BNfFzk>V^ z;)C)6Odg~LlvY4x8_18KFa@P47zV{T2!r?_xq(dgpl|@CJ5YNIi2C3n* z4JiCT@eT@0khvhgfx;N%HxLctgVH-l4unDWgTz7d1A#$ygWLg%D^OkqxgX?zP+1L% ze^7jb{0pLy{SJzAP`L0`dkT@vKfoPEZ*woNd4CEG& z|3P^Ylx{)s2=WuC>;uI+D6T>N2VqcJ0EG+4-=Ojx6z?DmD(gVy8z>Ed%mSH-&kvw< zfKQ&Da-cW>eqtO0*HqB9V8F(Hz>b=+Do8x5Ar*xP6Op> zO5HS9nD0EGd_KcKP@)LsFV>!5G|(I9_=!Ut4Of-p!ONIj^32+|8G=jrWs zYS;%#udp-#stZ7V0;OkA{DRUsC>%ib8b}Nj&!Dmb6xX1(E2yjkr5#W?I+*e`IpG2- zlR@DC3ImY;Ky?BrzCjoi*C2m`;u(}yKx#qZ2BL?$ztO`A z&*a8iH$o@ni#2mCpL^m)zN2-@n|@YhVy6|7)=ACX<#%BjHZFn zG=MD)kbM4tA>=3lvdGc%2S(2ypws?3Y-xQ|Y_tpq z|0^gc{0D^r2!qlBNE{Rnuy7)lhRx@JXb>h=AHCIq+y+YLAiv7W%Kiu4R|2~C0u~M+ zaghH>Ndq9YATvSwK;Z|%ATeaWf$T-*4+I;}05M8hyXv#2Qt@-N6ApfnD0 z4+w+&2l6+_jUfNR?*0LV0SJS{KxqM_2jm}++X=Y|=6{e~Aag;SPrVjy#1G)NstJ*EC5q#u+XL1_^b4Ogr4m4Dt_19Y`ERgW?tB9}ot~f#MjncLC%#5bp2qN7=gy+T#I|2ZaTQ4WdEn zKz4xA4#;hwG6ht&g3168CKSJfeu80;pFwT_@j-rq@j*1m z-!OlH`~%{H{0nk7C|*Gr6t5sT(B2Kuz9kSFgh70eKSBFcLHFZ=?!*PjgZLmeXnzw( z9mpO~oPf#!P#l5kOi-Q$xt);z@X3SH56B!)-Up>CQ22tv85Fl5zk=cnL?dI6|3Mfe z2l6M#eW1N5pnW+Y8st|H4dR3R24aK!3F5>22I7OnLH-2!`{2QY;Qja@df&c%|3Nh9 zu3(UQkoh3{LFEW2Za{GaD+4I;KgjPO{h+iDD)&L@3RK2`!Wra$SbT%hG01*o4DvfD zor@-N75pnHu${sF}s$n79MfcyrELy$N~48#YqL1G{b5(AyXaP{id|2J;j_APf=*VNe)=;veKbP&|X&yLPf!?u{0_?JApe5=4H5^@AU?>iAbC(YfWiP279c&KGzE))koh3HK>i1{ z&p_@4`5%;jLH-~YgVF#j{e#LsP}u?s0}uxJ50n-_801F~4Z_<*3pgaM>AU}h`0TfOkKB$ZUg(oQggVG_$ z4Ip=c>;?HB6mKB+lk0boK9GK7|AWj1`3>YR5C+9F2!reeVNm%4!mv01xdX%og#kz& zP`H5PK=}m}4xsP=iG#ulqz05Gki!X-ra<)$$V^bW1KAC7 zBPjiY%07@ED8V5AgTfKi_JNfFAb*3x0AvryUmy(fBPe}SVF z52);-#P2Y@Ape8R1BEHbpCCVg`~qWx_@HU7zk~b%lLz@3CI^xSu|a7A6gHrI1S~w2IU)=UQk*9sR7X-y`V4y#T5vH%1%%mg8U6C zJ3xK_iG$)4l&?Wy0`en>4^j&%M?hjAzk}EyJ+L$f3SW>N)JXfVa08hI@(ajMApe2v z0Qngd79chVgTz53_)Q4QV-G#!k{n&#T&>EAb*3x1|$Y5 zQ$YR&`5WYCP}qRffiNg+KzvZz0jU9nB}fk_4T9VOvJd8VYS5rM1Y{=6FCagF(lm?) zg*V85AayVdvKJHvAU;SRC|p1o6b2x@AUz;l!LFR${0g6u$4e|%bzc38qgX{(Q7i1?g4U$8~ zAU&XP1GyE(2BiZKhKYg1LGcOlGYEsyHpt%~F%Sm%7Zg^YGzj8@!V45XAh&_+rv(Q2 z9TW~A8e|WM2KgC;L2Qs5y1gJVke@&pYTgh6ajc!1&$#0P~H$V?C$low$B z2Kg7{PY?#FMb-ym!@?Zh4Yc6H>;m}@m)}A5g5*Gc2Kfm_!`LAIf!qzlpfCWbfyE;z z3_$(_`5TlrK=L59AR2^0`av{E45SuhAH6Xs96){qg#$LbL2@9sfzk_z2Kf!-e;5t& z1Beg8Aisk!NF7KY$V`wvkQz`pfaG8pq#vXX#0Rml*-Z~InEydEHv2&S0Qmt#gD}X= zAoqf37zW9MFpLlKH?mnEIZ*h()pcR!`>1Njl;S5O#GYSvKJ4+?{!?EX== zkFHT2O#`E8U^ESkrh(BkFq#HN)4*sN7*1(`l=BDjQp<}A5(`p`428bocwb&5R=pr`T$o(D@aZGf?;x{adO&Ps401P!528UB#Kwj}av+RL zZf%_Ze{2|=dP>D$ej%0y*#}b#Vxwbh{z4Z6@nINUKQ@2E#6Wt`)l7)4=0=bh2qUWj@j)0S2BKm9#76f9c>PCL57I*^M)nInHnKf1b;x4KZUeDl7^DW7 z4dR0^vKWYs>}QZ)VSHpZNDQQx+8AU%2!qUp`EBx)b^pV?H)F+lIm`cpFgAH~ea^NA zQOtnx(dovz>0os*agfX^r z8021%TS?IaQcFl5%-^8+2l)-8hZGDlALd7N8s=Y+I5L~={?A#CGps>j3JL>IoRi{j zm|9}|5A!>W#^!&JIrzdKBoA^c%wA$?eEL9gpm+!QAH>FHCdf^oJb_IPBnHw4ieGFP z=3kH;j18he{s);0qCprW2E!mTv0;#Ua{V74n1?g|VgAQvCrAtwHXt=1e}eev800RH z|FB`0pNOGBWhBThbn{_+kUjX~0j3U}2Duxb|3P+wFi0J`UJxH-Cq8+QyFhLy$NwPn zVHjjT$SfEiod(H);vbYxL2d%E(bd5CAhjSjgZvF*gJ_r>G7Yj5qz;5Z=7IRg@*p-y z43`{89%Meu@33%y(I9bTHi(amL1u%@AO?f%0i{D^j7uL(Ey!OW^&lFXT9Ex9IT!}< zv8lr+2C@s}M-WE#8!>EB>;u^Y@(T=u?8BuW6yGpE!DwuL0O^OxgUrXKhL9LYAIJ=l zyFhLNVRXNP#9$aC2V#S0ko_PGG6RNj*$0vb*#W~KGeCN=sYh1}avv^rAURNYVUq`m zf$RWb5E~l?cARDC|J;$QWcF$S*Lzf!H7n;)7_ISs?pCZUvQRAaM}J zW(PfSJWG;xN1*W#&LFR$n3-UXt-vsgl$WI{ufiQ@Tj$z>d z6G!Hg>SvJMAUz-&gkfwDjSYj`ODYDL3&J3MAQ~Bi#6TG2caVQT{s+~aAU>#%1u9oy z;vgD^Ve&9ONFRs|qLJN=iw|-G$X;X&62pc;=E1~a>Op)M8yAhP1{)tFh8*{xI0yL+ zhCyKfVuSn!!yrBg!{k8W1@b2hgWL;ZgJ^6RWG5~+Ba4I7p<`ruY;2f%Y;wqAAax)< zhz9uu-ESZh|{n7@(PAiF?zQUk-xg3&NDL3|K~@j*0Y>-}%88E-Y=uI&?;PFt9ebm6n{(!Mz=7RX>7$ygz zL2}46$c>;h4~i#{+d+N+`2`tc^D{^cghA?HG>8o{17sG+T##KL46=_}7-SwYMmHB( z4xJ5C3layhK{Uv%FgJr}kRPyNkRM?fBoAVPXb^^l8Hfg%2l6|}d}Iu=3&e-fAT~J| zq#uSs`mwP=Vjwpl(;zksBkKdX7luLp2l)ZyCm06tK^Vpd`5S~md}MJLA7&Q1T`)UA zGzf#_K{N>CVx!A}_#g~YPY8qj55gdIAR2^0Y-9`)gSi((gYp5$Paqn^N5-JA0r?w* zL4F1Ck>x>pKzd=~AbVhLC6=6tG7p0#vpfsFvx!(8svVE-#{1?&&U`gh7E(%fW%=m%q(PkKx|^&jjj%vkIV*{NeKqU z56I6T4DvT9-as_6UqEbh4D%;Q9L9#xATgN#L1B)|y~yG)HjIY(37roT$0i06Cl`bK z4)Qn1%`h6|2N;I=3q*s$2BZ!~!_*+tFgXy7?j}my4$}*x(ak284~iog2KgU^L4HK` z6FM8@HxNb_2k}ATFf%|j$ZU|?Kr|@5f!LH_YWW{z4hVzX0&*7&gWL&nHwc5m0puT$ zzd#t&4+7)NCh%N3NDLW+)PnROV~}~sW`pbl^)*0tgD{9s2?m)@s(z6DFgIbNLFR${ z4#FV6fM`(v7L->&7$gRwK^T|+L3%(KoB1#?kUKyaWFN5@In0p#j?Bhp7DyguFGwE7 z#zlkFz~UO@Ul<0B&49uJgh65;HJ~s6#XZQs$QYN|$l@S-KzxwCL3R;?VQSH7biE*X zP&|RmhhdN#Kp2$vKyC%aA2J4s!7xZ2$ZsGTWEL(AvKOQdL?i13u|f8L^n=(Sdq8X$ zhPequgWL%+A7lmyBeOw#WDJr6VURwMI65091~L=oE|3_=4KNIH3n2_s3rgo8b3hnm zCJ2M_KZpj2fiO%h$V?C$MB~FCHOS!rG6Q5DvR^=M2Jt~O41?5x#PDH|+dvqk7KA~1 zLH2<#J~x5nU}}(Q5F2C;2!q@R!yrBggXCctWCk%9rXECt+y^oTgpvIP5=X}%^&pHK zCm?+wH-Y>N!yrCL9E3sYiE$IU8jwC12AKoGAah|D#0O!R8W@dk4k12FAIL0_*`P20 z`5Q!oFpLeNL1ux}!)Op2qz{BadO;Wz1|S+F29k$im>v*K2*dQiXpmVj3^E&pVSG?r zz|_HLLgs_yL26+%hz-IZb3uLvVNm`C(I9`rFi1TJgVcg(5C*Y97~~$1esuqX)R2ln z=D{$?To?wK4`Rb;5F3PHYC$xqc7fD`^nuI)VUYVl802>tM)yBR4-A9!!Z1uf%zZF% zkXjHMMB~FCH82c|SCE-73^EslVSEq`QV*j+Y!Jq02f7?cJq&})2KgO?VQl0uK-UZ7 z!}NpP0n-a&!^Dx*!PvNHm>v)fG6RM|?m@>eIk4KC<^Pds5E~nY=|QJqW`o=h!yqvj z8x#)M+>C4o$V`wP5C*YfaR^ca(+gw6XplIFjf`RXKxTlrAR2^W zY!Ho%L2@7r5(Cj73}O?DVfMplus&!$1DOTGU~y32G_wi5Mgv7pW)nDXg4DzO3!_0| zAhjTWgJ=*&HV?)Isl$gs>Ogvt%>nU2;xLR&KS&NH4q{`&Fmpg=gD{K_@&mehkQ_`6 zhz*hlnGd327?}@JgRUN=4kQkuK^UZ-5C*9OnS+c$W`HnAj#?Pmd=MLEK8S{4kT?i~ z__*u^$-^*69Wn-qgZMB%gUShP7$y&*LH2<3fb^nckT?v3)Deq8dO&JnG|V2D7_vPe zHOPDr8zct8Aoa)?B!`SaYG4>94pI-Y51YSXVjy`C2BkxgewbcdG)N6D46+Y|LGs8n zhz-IZdtewFA0!6DATw~e0bLxV7KD+-L424W@zL1agX{*7IUtN|HaZ(557R@4hS^6f z4Kfd8J_w`Ri_QnB0b!6h$b68$K{N=1*tjrA9)v-CeHaGu(J{;|AoD@?fcT_fka?hV zhKxaDQOJB`Hileg6L`-KNE~E82*ce1-6I4NLpB4M4YMCagTewX2d!H`e3027d2~$B zpCJ2T=HQ?~X2R@1UP}fN1C7lgi-Ft$;v69gtAWrBo4C!qz=Z0(a2&THb^~)24N5zWCzGT5Fdm=Y>*g84#WpxkQfMq*vRI> z)PTf5@*uy%Fib5-4#q~NL2MWXnFCV?5(Du;ZUA8r8zzQqHb@K^!}KH5Fufo#5C*A- z(J(g1OmuZ1zrfUiXb=X8f%L#INDm0Z_#hgj2IOWC2I&D|kQhh~hCyQJY>+uHaTtxP z4w)av&Ne4&#Hw zKx_~O$$|JF3}S=wJ*+GQv0)gThGBUU)V_wYLGmzjKr}iAnF+%n|A5>NQUel0ra^2F z28n|(h>eWV#X#;Q27~k=t3&4_%YoP+d61t#VF1D)HVA|K50V4XAPiz7i-Y)tFwA^x z>d@uT`RIDcJAc5CYUdBUeD&%-GXDAN*MATOiGgSsM&^UqAPkcuh6bs}he2lGlc%NJ z<}F+QPn|aBKZwSLkDomG|IWR8|3NfJ{?e7pa6U{7Bo1POFi0LmgD{8P0GSKIAhTf@WTv{7COAKU+yPSuaw|*?NF1aOghB2FiG##IdO_+y zY!C*?!`uWj2P6hk55pk0gVe%kkbaOFbaO%KL1ux}fy@G7kbN*3Bu6X;nE|o~WG~1r zkl8Q{k^}i4WHv}1rVhl1sR8K$VUSu74RR}p2FZim0OEtp0MQ^ZkUEe#AT=Ni5`$ro zdXPOJ^FcI79f%E52htCsL25zjU>GD1@;3~F_%JzQXqY~b*&qz!gX{&-AU+6#>;$<7 zBnFbh#s;YYsRj8JCXl41??gVGti=KZu6u1-T#OMv$2xHcTF*9wY`*2V#TdKx_~O zsfY1Fav;Bg_#nL?3}S=Sfb0SBLGPg&{D4gkml(`UbiE*P5JuMr5`$ro9E^=lgZzh#LGtM8VSJD}WO)!D zgh640jA3eE`apaT2Jt~*4$2oG3`+l?v<0HkF-RVSLFR+>!Stj1A0&qjgVcjCx*W(n zkQ=aJWc466NDa(gAa{Z=NE~D)$iE0GSD5gJ_U{U^Ix04TIFeFib7TT$oxA8zc@>1EN8Ekefj6fVmOmc9KwphLPn#Y-9{F7lc9jVCq3^bPSRM=>w?+ zVGtiggZvJ{$Z{ZYWDJr+$H?*^HcTF5FNh7JL2@t*vJ>QQP+Wj8hz-Ipd5}IB2I+@k zWO-yZNG*&Hqd{UYedshu4p|+D4e~n(qpOASL2d?NWIl|IPJ`58!^rADZb0UP*f2dH zHVnhWkZF({G9Q@@k^^C6FYW!!XE=FgCd~$P8pmiaFTy!Nicw0f`~= zk=eN9kj0VNAibb;0K=g43BoWwj0S}nNE{S?FfkAf;)BXIY;quZkoh2akbV#bsRN0_ zFi0Nc9&~Y#9uNk}fz-nEqVqxeKw=7r#*#n|sav*sS2Ju1SAU4Q9AR43=M1wF$4#Wr1AU+I( z%197~@nJN`43HmTdO&=T`$2q=dXS$$e2{t&A7m~FgY0`Wm=K{QM)Odg~j z#0J&ZAUO~X;)BFNdO>0!HcSk}2AKn*L1G{bvIj(i#6TFN4@86P1kuQHAh(0~AUz;C z5JqQ%)WGzDXpns%Js@*oY>+-=KFACZ2Dulc2gC-c0fi5U55ge5AbF5D2*cFC*dRMV zY!D5S1DOwU3y2TGAUTjY%nXp(AT~%0BoCrNYC!rxG{`=XI0%FIAPkcOsRfCHXmkux z2a*F}ko_P#Kw=;oBnDCsVuQp$d=MK%gZLmehz9XNY><5*cYyp4!ytEqFo+MLL3V)H zAR49?Bn}b-@nL3x_#g~Y2cki8AT~%Xj0Ukm;xKs-4HAQ4bUsKNn;6JEkT^O9sRv<@ zIEV&e5E~i8#E{K^xd|kPj6rIU`N(XLnIJwW3_&zDjI1A|9)xcm+xwps3^IdQ^&s<+ z{Q`0uES^DZ5C)kCaw|w3G6v}dVUQd;2I)b^Ahj?tWc4sH5Dl{r#0IGW=~+?~^&c7U zYAyUvEC%UAR|^t@VUYRA7-T2N4agW-9@+mOwJ>*rXk-kN1Brpm0?{CIU~CW#QUk&u zIS?O&k=YIaR%uWyu!XS5mFo+Fu1Bea6AU;Sv zEDS(wd>Eu2gh6J2Xnblxav*gedtewO2Eyq6z~?t~wa9#!pOI;h`5=2i?gP=t7$gV6 zATf|U2!r^@{s*}gBo3lM7{rEQWPKnuNH0td7maKNOb@ajKx~lzv0;!L45Q1##F70D zVuRd-j$v*C*$-la+yuiQF=YRP6^sv3i|l`p8(|n^7Kja_VdjDKfG|iL zL?esC*f1Jd9%dHE-N+c1->`|p#E|vF{0|ZXxd~(sGDda-hz-LaH-gk7`yV6+5(i-r z8y$np1&M)ZkX{fQgh6~{jI19d4pIYB55gdKB4e1JU^Gk)#z&`N^4MsQ`5^m17-T1i z24RqUKr}K2$%E7*`yVC`5(lwCYOrCDd4$v;>jS9=nFrE??rv=ULl?uxM>Y>+KC->Y zY>*p3d}JD=9@+mOd5}9n7$gVcgD{AXjzRM1V#sPiY#0Wq1E~jLWcMPo@%a&-9I_c8 z^FelD!^my{$%FVH^~nB*g$+m!hOvo(Ny z2BZh%W{}@O7{tcsPeO7a{m2+(KFB^02H6L*7sLi(kT^01sR#KTgh6Z&2B`yK7#l=` zFh~qtFESs-2H6Q?gJ_sO5DhX1RQH1Vs38A?FtYn$Y;=Ev#IcEi#6cKj4hVzH2ib#; zLGD1tAbAiUq#opV5C*Y9Vjv8X2VrD3j1Ln7sRxPS!yvUV3{nrmpfMB>2DumHc34<| z{09<4=7Ypw;RRyDFi0QB43Jt78$^TnFt>p00bvjyWqz2@77zXiS7^W9QW5XbI zAaRhnAR2~Yd=QO{LFz#m@jwegpXx#D`&!I7khM2Fb%POdg#ExdA!- z!Pqbw<{pqb5F6wknBR$^k=29PAal_%tiKI1A0`K4gX{$90r>@lVg7>o0pxddF_1b? z{DQfSPghBoRxf$jMkRM?fBoD$Mzr!$y4_X@l(*vTBF-RU{KFBU)46+xQ4`Rc_Kg zVHhR`5(lwC;vgD?L1G{b5(CjN46*}+LH3|ykh{<^NDZ?8LGA`&kT{5jVPrnY3=kiL zL2P6-Ffoug2!rH6Gzf#_Kp5s$5Djua2!q%l3}VADh!4XcKZC?UG$;>%#6f&e-T~1t zKFseRdqH*~V~`j+2B}B(KS(`D3`C=2kROos<5CCH2T}{t1H&NoAPjRW$lb{92eCmI zBo4wLHVA{*AUz;9hz4Ph7zl&JKp4aZVUXP*J3urD!|aC9FgJl{5C*A5_CH7rqz^>H zFo+MrFg}O|@j-e)G%^OsfiOr6M1$-F(I5;`55gdKBD)#HCl(|79h)AIz33QZH%JVb z2C+dH!yr8%HaZOw1E~dJ z5F3O+YG4?|$A&?6!PKC;ADeoR8jv`M24Rq15DgN8VGtjLL3|JmauY}$45RZw>R=e8 z2IMA?-$59}hN*|qAT=P2jSo@}k|Ts+dOX=FK=9*`IaqpOASLFzzZ2BSgZAoUf#W`HnA3`8SikQhh}2*cPQ8l(m!2E!mR z5QgzVG>8wvAU3*skQg$Cse_4u)PwvE!XP$C9)@9RV0;)25(lwC>OmO9hG7sNhCyO5 zwICW<99a&;24RpIWDF8R)&r9RiG$e4G>i>W4`Rb;5E~nY=?AF;`5hgD#Ia$JSuk-B z8ijx z4^j)lAob{eCzcP=3o-*_H#Q8D1JNK1asvp1*u-FvT96$e8fHGaKA1R&2FZccgJ@hB zWEQd4&KYu|XK57DU4^j1Qtg7^EKA{~$4R46_$R!!Sr62*cFE z#6dJRF_1XO9*`aw28n^#AQ~G6$$>Ds9I_aUjZP!$L6(EDVe%k8$Xz2HW1K%Az`*eT zKeGTM0|P@0l+D4wz`!KPz>t$#Qj%K4Kt5oFXjNcPU{GLWVPFtoU|2e5>HmSo*REar zPb-YeZ_l4U|G#(bRNz(A6UIVdjDK!!Ss1kfGv#5C*Yf z7^D_tK1dz186Z2b;rH*~|Ns8|`+sb5_{8QcJqETLWWR)B&wmgGiGj>JxAz>}Um!C; zYCtr|jpvRX1KS019|(io2onRzgVdp8kUYp=$QY!yI4bBr2!q&Q`+Iu8_JaHmQVVhm z$Se>aUYPlWXiyp;H~b0N3yO0X#^nxD?Z;*(ES!<; zfZ2;oBg@0s`0Piv3&w`og-pZDLZ*@Jg|U(4VQiTFF!NzFsJsEOLFERB4ZsEpzs4>SXhGCpfCo-3y6)Z1{99S>Ok@!`#>1kPGmMV`$2vH*$*-g6sO2` zfy6=jK{UvG7zVisBnHw0qCtF+UXWUR7?=I9_yD;D8wU9a9pkeHmmJJ~Y<8fFQNuoT zcYy2%VPv~N_JG116o#Pi1%)FB!`L7>SQvxEKx|NWfoKpPgwgFm=Y#A3VUXP*3}Pc= zkR7171lbER7es?F$R3a!$PN%jmIH}{)PUTLZXb*fvjgT|kR2fVVe+)1VfKP(nB5@1 z!0ZQ!gD|!22bl@O$o7Kl24QsjVQOJCx$c4K2hlKlK{UutWDK$&gh6uHFi1bl4ajVm z7&aQD4up}-L$(u{4YC`AL1Lh?3q->(vVIsFWG*2LQwySDW`Sr>S_P$1kUcOvK{P16 zg2Z4Lq!z{o(I7rD2APYDLGD53BdbMb!}11<2FZc!24Rpr#A1*>axf^aK<hz`$04)J;2h&C_OyNczp2)if>RG5L5<$ zXnbl%)fYL0AXC}M#Tqb+=Jp5RBnUv zFQ^X(YR7^4aUdFmL2QsXNDZiM4@wiD^Z<%?5C-uTj$!c*qCtFg3{nr$2htDH2TDVu@jW=> z8$Dh?@ehhK7zV{9hz*KQP(0&`bC4VigY>}EgTz33KxqLa4l*Cz&Qborrwve94a%pW zF*{H`2gM((J_pgDI0VHdAq-Lrif531Y-&N~faF2;jmG=H$2%+zKzSS#?;s4ytDv|8 z#T5*L_{3n4UQqmkXng8H@}M>*%>Ge&;L-weyC0PQLGc1g4pREbMW`1E{P9#W^TVfZ`SuXSm{*l=uggEui!OG8g8C zQF`Fg0Vw`qZ2(Z5z~UJePoTI0#V<%4lny|3JhB`}9+WOX^626qIdlxt55gclp!78w z_wP<(^hN+&QrNQ@W^(g#upG81O^C_QlL0F>WBaRG`G5C+93C|*JF4$AkS zc!!CD+%L7+Ecd4Kf>K7swn?9fX`7N7Dpt(*&rz2ZcQ-9zbyiidPUH zlpa7B#D>X%Crwc^eegps)wg zpfCre2T-_!@;s=H2E`9Z9vOqgU>Fod57T>;{<$N}nLN zfG~&;avR8vqnP~k04mdw^A>WLg7O+D>|t?>FaBU^Kr|?Rs2Tq-dqHNy+zN_+kUbz8 z+0CPAfw;5)%HN={1BD?d4IqabC_F*ppg06!Soq_kLFzyl6h|<<_|%gk2a0o$TS4^! zOdp615(C)$;Rwp}uy7_sgW?<=gW?2~4hZQb zRvu(N$gLnZgUTdi^FiVuF_62_{etYTQ8pw!fWiqBjv$O2ULZC|3>4PH#tTRtNIfVo zf@lyQgpuP1#3vSm(hSTT5Dki3m>7r;G6!TA$b3+ogWL;Bs~|NXwIIKZ#xo?Iv4sOD zd_Zj|P}qUu8>R*%1`0n=ID*2MSPTk#kUmhHAnO5%k!lu5Kgb-AnIH^fgUkW(L1u!| z01U&_gVF*h9f0IvdO-e!`E8Vjqz6zOgD@!Wk;4a67l6VSBo3lM;SCB~bPNi2m>4LW zLE#Pxb7XOtJV+d*283Z^_-K%w$mW39ATwa@1-T2RAEXu}4#FTcAhjSlkX;}*!7!+O zGaC1hxCey;D9%A~3<@_8AA~_=07wiJW}q+zg)Iof!W0y)AU;SAgh6~zn1l4d_#iQK z3{nHr2V)o9fXoEh3(|w^Pmo_nItGOY zC>&wo0LmYrFa)IwSlEHW55xwAA&3o94+>*Yc!I=1>OpA*CJ$1B4TIFf)PQJEe8Dh? zk4+Cq9ApPbFNg-IM^*<)3ot&&4Im6s2ckiK1Em8H4dR2;f-r~=^Y2KfLGg?n2B5G3 z#V;tlVBrc1GY|&F1xOC02NVyWcn7Hmg*QwsJ{qJ4WCpq#kQgX_K^Vk_$-~sc>;~~c zYCsrdCrBKo7DR*G1B!1@8V0!wrVd#gi!V?*T!X6Z6 zuyg@Z2MSB%_yWZT3?u7>vGK`))PT$a#SI99>;v&ZZb9aQ%z%l3#6cLO4x}H1LHa;$ z1C>vpI0exlK1dJ9?I1o#9F#^t^e9HO1wi2h3SUqdg2D?Fo*)bgV^Fw(!Vkm;(J-|j zJ_v*KA!CpjItIlP2&0R`?1RaHXpkKs3~~pE528VQkXt}(5C-u<7$gS5AayXaL2d!L z5f;Co_y(m{5Qd43(ulYR<$X~2fiNhagTfaSmY^_%#XTsDL3|Jml7nGTScBpaghBd2 zd=Q4ogJ=*RBnQGEJ~{^J1(^%77sLk1gVciJ7ldKrAUO~QiJ@bVeh>z!1=$BO1H=aT z2NeIK@r{UY^soSh8z{U$`2!TLFgB>JhlL@C4GMdZ91Mf_AiW?AVuLU!-eB?|8WtBI z8iYY&xG+dR$Se>Bv0>^!Y>>M^ZULDAqCsg9gh6USav(N{M#muaApIb}q5FH34>^ww z6waV<1cf1p4WdE$1|$Z`{~$JUyn@0M6bGQN2GKA+vKo*%APka&(b(icaxe@s3uHIQ zT#(%$42nCLJV-x?528Wl!qkKGfYgHYf#Mm)2eCo!2Z_VfgZQKI4k-&j)nKy2iA0hviI2AKtN2PnUS(gKK$>>ikX$mWCOL2d-81<^2f zg7~ob2KgBz4l)xaHd1L&T!X>^6xN`y1BDYT+(2@mumZ_}#6fJ390-HL4}?K_K=BQV z3lJNm78V~MaTuEt8e}%eEugXrlomktgX{u{gD}WW5Qf_BM(7S^Ef17VOHNG(Vn#0H6h;uWM9#0H6j!X73D zV}tlGHjD;|k%~d)!{QL;Hjv#Qb3k(FZUBjc;vM8xkh?+hF#RJnzR~>+if2$5fx-ra zLE#7rTaY-255u5v1BD|2VsyOLHb591 zqCprWPYVpP1B5|o4ivv28iYY^1H~mMUO{dL*$r|BNDW8~M1%B!+zRp!NDQQYq+?LN z0p)L4IDoG@646+AgJ_v)@u($-d z5hMn3H!NO3=>a4Lasx;m$o(LHfYJ&G!_>k2JxW8$0Z>?i@(L&{L2OVsf@n}UB4bea z!!Rg(L2Qsbj0Ul3fkAN!!ytP>801!vn?ddb#Vv>irE5@G24ch91d;=#0T3RIe@NXA zOZT8K0fiNa28Az(4T^hEn1R9=JuX3E3c?^cka`dsq!z?RS4#^%$R3d0APln?lny{{ z2Big%7>EY>50n-_{(-pz#0H6h^%L17P52a~5Y4RR06UXWWr7~~d^UqJaA=5~<3K=BNsL1G|xfoPD}D2Bv8sH_Cl zk)W^x#XTroL3~)8g2D?V1`0b628BHgqsxKhiNP@a=xRZ62(lMMgXCcG3Sy&UkQpGg zAPiCuqCx6GZUyCWP@IFrKyC!_LGcZuLFog;9>u8f4~lb88UTe4C=5a23*v(?D14E_ zA0`J<2NMU0!Ptaoka?iE!ln)+4l);}2F3=d#byUgJ%|RyHwc5+ptKB%W03nnVlX~P zY!pM{9u^Lud;yAoP+0)VFQBjo@nLLG*uyZg9I_a;xPi%|(;&5=I6@bP@j-Tg?1rfU z#UG3fQv;$w=77|KFesfM>jBAw_#i)m{0EZ<`4J>Gic!-5C_G?!1r+xnHYi*{;RwT^ zH~^Kapm2qWf#Lv!VSEq`;)B$J*w`>g9^Gtobs+tqJPUFQhz-J^bO6$etQN!vnFYci zd62t7?t_Vg#6WI_`41!qN)I6ZNXN+G019VNSb=B|2C(G4WRIYiG#uzgkgLT z4dR3H8Yn(Md_owc9;60DgW?#(hGCEypfmuACs4eC+yGJo5{IdQ(IEXGdqHYIZiM*( zq!xrhe2_Vyv;Z>`#2?8R7XP3y0fj3lj6vZ9VuQjJ6s8~=6rP}R6GVgfFbq-$!Z1FF z28BNegV@+GNDdTFFgavChz&CjW;aM3$X%d#0=XCD29O#M2FbxND2_pTKxTk2$PQ3E zgZu;HgD{8cVlY0)Kcjg11WOdfXo@m7!(Jf@Bq=Eum*)82!p~56kedP0m*^HU>Fn^AU(+HVPYT} zrVbYkQUfv{6lcgZhz*J_kT^&$$X<{=AU=ox5;LH2{(3(^ac17VOD%pV{* zkb00jh&_rS^*$&}Kw%1s15mhv_^_}6g*`|N6c?cI1hGMCVQdf|ghAm95(lw~!65TM z_JJ@cjzN5onIJPj7-SwOoi4`if52MSiKISL3%)HVSXB=G1CAjd_ds_ z3R_tCfWi_KAE2-Y#SbWqK{QAmNDP!NKp4aa#RE(&NRC(x(htHQGeG8p^nu&};)7_A z7|3i;IsnOm_#k(|wQj4nW}v5(lx7^B^d%fy6*@1yT!&2asB7V34_>cn8HJ z$XpN`=JNDibIBnR`)C`~y2k;4lV*Pw6&g*zyG zK{P1rLGca}2ZcQ-K0x6O!XPnfVUW3?_ygGivKz(*xd9Z&z^3MWwfgTf6~7J$ML6t1AK28n~zfWjOkP6`IaAtCjk_y^eq zier#k5Fg|on0+8N$PFMqC=GzrfiQ>*EON$_TLGcb!1Bz=92Fb(tAoqd9K>9&` z0I3D(1&NPh{AmCbUZ8vgqCw#X3VTqV2Z@2g7$gn~Ll7GzM+yeTFUTwq4Z`SpL1G{b z5(mXG$PQ2$4x&L~AR3hJKxTm0AUTjakUL@V4T^IR8{|fiI7khMJ&N(iJ1l%a;R}jm z5C(-aC_X@82nuhI97r7~Er8;KR17i;6ki}3mtK$@2!rH7`59y{$bJwTBo5+(;uppS zxdr4-kb6PmAhjS2ihGb-LG)umr^cD11SA3KWhYIZ)Vw;t(W9Dh9KE0qg2H6Qx17d?Ph!4{XVuRcPk_XWsJ}7;F+y`j=2?|3{c!J^p6z-&AkXfKO0;L^7@ei^CWG9FQsRhL^NIwX}>;<_CBn~nUq!wg0 zNDSmoP+A4y(R@#Oyo16Ily^X34WdC9BnHBu@P>syDKso@Kr|>$iO~zP9~7@3wIF>U zyFp?g3=#vS3s8K6_B-57UrOECk2Dz41_`P0>hyA!e<6Z9%LUV?SRr9NIytDC|*JF4a(ynH6SsN8kjst z9OhP#A7JhurAbQz=-~wlb5K~5ib3%WiW?9OiYHK+!ad{7xM8qcK1Gqx}T)d8TeCnm0t)qvsy6i>+U4B~?@Obnz3Sr0lJ zBo9&xic=5`N(&%1NFB%=kh?%}4q}7EKyn~IfZ`wIZV-DEQ5Dyw07LE<1bNDO2KDBpr; zkY12Dhz4PhIEV&`f#MXz2dM+`L2Qt@AbAiQW+q4;6z3rIATbah6!)X`KjrZc%3H87 z1+hV458{I`EKWddWDJTUP<(*m0VWR82NDNikUo%EAQ~hN!XULE3}VCL7DR*00jUGY zfoKp0u|ej7)PiUbA7m~lt%A%2iGk7?DE*FN^3wpQZ3PQsP-j2Jt}{BnAp+5F3U;@dnC+uyPn#9Hbu< zCm=PTxC5C5VuNTH2FZiefy6;-Kx~j1AiF>q#0JTMXpkJb{UC9W-Jo(Gq#s0&#y@%S z4+=X_ScAeD6sDlK0r5d$42la72C-on6o;UE3W|T2I+z?No3sM8ZATbaI ziGeUk9%Ls-3`B!4NF3w_m>D2_Aax)X2FZcq5EN%H4ATRWgQ)}2AaM{4G6RG`Y*0D_ z=?BSy>;{R0%mJ}MVjwl>7$gsJKgb`Wn7lLqic3%#01AIl{DZ<6#0H6j;sC@4VNh6u z#6jTzdW zaR!Q05QecqG)Nsx9z=uqAaR&FkQgZLLFR+RL2OXo2GO9j05Tt>2ZljvKo}$ka@Qy( zKmI{s2ntJ3T!6{~P`e%!&Y(B}VNlqE!WR@DAax)ym^>(cK=BXagD^-QhC$|m7!y04};7C$%D)S zsRv;Y8)QGoEg%{s56bf(GeL473}S;Ytc?m%JBmq92cU2Tg*kFKg7Q8n+(F?Bi%U@4 zgZLn|pzsG_5F3O+YCss74bl(dgW`@@3`z?i3^EI3KFCf`ngp2x!l3d1M1wF$4Tz8I z?$P)sE&f4a2Z~cr`wuznKye2W2Zb>RgTfgk1`2nOI4EvFGzf#lKx$w#hz$}4r7I8{ zTik>6gTz5`4Z_H55FaEDG6Q5L$Za6|L25v50I@;xAUO~l+1;aT(&HbL29U!I6sDkf z1BEY$MhBnFcMu|XIl z2QmjF4~jEv7^DXjhafo+8-zh@kQj^&G6Q5D$WD-(L2Qtjp!x|!gWLuZAH~#(e^7XW z;tmwfAPmZDAPkBVP~3pzKyeSkpzsH&0mT&vgZMBEQU}5yK1d%r2AK!KAblV?5F3O+ zY!C+N17VQ)ptuLIL2(XpA4nZY4CF2le>Co?6ZfES1hut5VGGK4p!frYIVcW5@dUyk zF;KXJEqSAoD4G4qM42TBBH;4ws8!{Va z4vYrHHAoGN52Hb1kTfuT0w^7vbf4A_GMmADT0h855RA;GT{uAO8ky#U@*60QKyd-0 zLHQ08-=Mev(I7r3u0V1iJ_v*29YljLNE{UZAT~%HEPi1$NDL$o;-j-c=78)4#s5rr zyu;!gMvu&Rp=X+e#T_WWgW?-RgW?~AL3IEq?m%$}!k{<=#TyKR;vdF_#3d*{LokdF z(+4sKWEVtyT0cY|qz(XyL-IZ--jVTW{L?xufZ`k!{vaAuCxGG|lmQ}Kia$_Xf@n~D!SX$b55k~01<8Zr6&Zunfy6=O2#5_*2QnK} z--F5$kls4?Y5gFK8vn2|U^E?o!k?ZPlfNTgVGB~Ehw*VcAwU-0gZ1E`!=}U2Pp@z$3G|?jHUs4#y`w1P~3yqAQ}{Z zpt1lIpCAn4gD@!WK^R-y!{Q&5jzHp3NF4}+(g3Wi2bBjfK9}3nJ_fg`{ophJ zqrrUwJDxgVGBq{y}93NSy_^><8UT zh^Pm+K=BR1$Z4PtoEG3|02cQ!dNls&75||204NSY@eabU@&FXypg0AU10Xg!hKYmH z3aESs<$Dn30*~`e>j&j`XugNUH6#sy#Th{50ZQD1;vIxXkagD^-QM8hzMPZS~5gUWb#zMs;|;5NAzQVxLP9fYSM$^w*f z0OSXdpFs3T#UTH~FenUQY!HpiM`wfhlwr_#04U$U(g3IoK#zM+z6ZrUNDfrzgD^PH zgW?e!@1Xn*!k~BtV^G|~FsSSYu|YJbE`X#1kX|^Z%nu;<4^507zaT!bn7UyG%G01U z0E%bi_y_SpA4~lAL-IN}4M6k$iA>umjxidqG4G337i+;VGAlNAz_3r4^xNIj-o95 zK=BW%-$C&Wi+@<&2houD2ibw-c5wL*ig!@lg5n;W=Rxt_&EPr-gnPhg0hS&h?E+AJ z&D z07?rW8WjJaF+N1zf~Qevy^a+Bka+KAaGi*TCw1el6JY%UWd9(uspCh4`{`vK%nu+M zghBR#!wwOzC~*$XORzA-rUsNIpm_#r7D^cavjZeY8V2Qm&{zN{4S>pfP`rc2h(LT8 z8xnpXJHUQ`*83ngPln`kP@NBt_X%ALt{{w@4#4#Sa(w_Q2S9!T`4vP%{RXuIp4Lh8 z3&fp+MGul+KyeN76Nm<5P}spSwCx1SOXwI>9)Q$==&6vh0AvmrgZv4y1DOV~NyDJ{ z2bJ-lb|5Gnz~USv21*AYF;M*v#$dOD+yZhF$gR-$M~-`!{w@ZW{!RuE4NeEhX#rX{ zfYJ-RT>vr*WG1XE0J$IJFLVqNN5&v|Wd2}agWQCMAz=neIM)7=POWM1xj~d|A69b z3aGw^XY(pfmu&;Bg~J zp23KFs9Ug#gXE}$nHeD9|9@ryQ3eKv5(Y*F5Sx*KfguLU=3ro8U=n0tNKed7%}Gs6 zGcb%dGl6I$jbvqD0GZps(7>R;$ie{PS$4NG40l|+aTCLwD>webaQd8?!`{!sJx)pS zAA0L25T6wNqiTnKT7ktqh#rm4 zp&y^5#^IbRH(+hR(YD_(Y4d~1aTq3)9zbnAP#OTygw&4853ce78pr?B=gb7-po-%E z*f3O{0i+%|4nh3djhp_1;vR&N=#sSpwt^6y)bh}<9u+%IV?V4@dL}dpg04?D~JYR5StJN zwFO}LA6YFdy@1RCl`k+mN9nu=qo!VRFbc$P8HggUSPt z9I_r58)P;py^Y5I;D~=vJb>Z@gh6QmM1wFg8^lM#;PM?5pRhOv(a7-+ienH4iG$Q3 zV~`jKgXBSWkK)0T4nT1MYWss|5QfD$5*r-%ptu9YAq<1~pt1v&|3Q4194PKVY!D5@ zFh0n9klCYn;L`voE|4&I{09_IFbs-Q5C*9M<#iAnCI_NHVxT?%sO$&HfiOrM6z4ED zh!4UbJ`97*8;yVZ$3H0CK^R)jgX0?%C!l-`5{F?>+<@3H8pH;v0jUMWB?yDqATdz< zgV>-rhhdOBhz+7)7^DV-L3%*;gUlYq^h*PvI0S_|h(?YFC?9411|)_JgY+ZECvyG= z#WymBse{olJ3)Mq*`sk!zqkj5H7MUfF{rOdHz4n#xEgV#NyaZTH}Mh;(C{DR^E6d#~?1m#JXIEW33V~{v92E`dj9Ew442QM=~ z<`9cP_JYg;(IE3dY$yhmDWjN%Yl=W|3<`5t*n{F4mNr0g(0G9N5kYEUVxV{ju|YIQ zABYVyhuC;WRtM4t!cg-WK<0tWg}Pxh4bUJBfWjCQ-XIKOqs2QY4#4ewkUS`EKp4aZ z#WM&);|JFM1H}(6u028MGi|OIg~sPigyqO z#S4rEu|a77ia};k6#t-f0E&N*`7jK!1B5|i(@?j8>;a{1 z3wLxH8VBI`g~@@$pm7iC|H0dTpg4rZC5R>!gY-kq2E{cz9YV!HVjv7ozaV#yV&cjI zQ2c_z6+OJ5=>Z(KpfHBTFN_B10f~b!vKUAl#s`@VVuRF>8sE6ogUo}v4HVz-^o1;r z?w3(MW?BG+At-)8GDk9I`m541mr5fz-h;u6RWj2dPEo zgX}=&gV-Q7Ah(0u3Zg-FgVcd&5Fg!7qkKdf0EG`ShJ_7C3>0smaD;|0yqpDzfx;F< z!!SqgXBSOhhn&! zL426sMrla=gTe$E!@>nB25Ps%+xVa`28Ass9zo#_VuLV99E3q)(EJAubC4JeL*+o` zp~&HjcVsz`IUu{BcB7Q_NOD-kp?(3&!TdK$W5z#nm_XwZ9`~TIhs7a?hVfx+q<8_x zFGwB~FE9*J2T}vVAT|iY)F8(*G8?29T@6SKq!(%qxV{JJK@x|@Gbr9+`av{I53)Z; zIvW&5ptwd32M`}54hm;bxPmY!>_8YKhAfZF2Zb><4AKuO_hI@$d=N%f3*v*~5gP`{ zfz*O%5C*9M@nIMyhUPBxbO25(Aoqg&hV0*w%tj9j5FZqdAPfp$P*}s_4HkA#HK4K_ z9L6AVP#l2NW5X~xm^mOm2!qT9VGtXn4`vQX41_`A=oq99gptKT`aohJcY!b{K0!3H zI7keHLH>feV{~i)%h&)Yd|=@Q3S$@>M1#T&(g$LLFo+MrAT}}viGkFCXcz|ZL3V)L35shF2C+dHqy|KTFi3n9 zL+XFzZ~%o3G@L-=Cg6G;IZUB(4w3`sdyraCoPcO#4AKL_ATf|U2!r_0xB-tLz~UPu z2T}*ZAT~@KC?Dh&kek5ng^GjuAbE8Ep^G8Qk7PC|TtMLu4L@+0 zg4obF2eku17!Igok~2B`zZF9@TvLG?aJ92EB;3}S=yz%VSnL41&25C*Yf;vjc{ zFiadI2V#T7uwan;;OPLW4kR{O)?+E_k<&XYUSRPJ3TIgOLgNh-?;s4$zo4*%VUS*s z8W;xgVR9fECJv)PW}{O3?t{7=Bn~zY#79p1qwIEY zxPZbD6c->G6hp1&JY>0WxP4L;8Qv@Bzg$C@#U}B`CgO;-D}D@u3(l4-X;c7fY)F!#aC0mU&k46+a8R;YTA`@s4~;~ypdL2(bq(69o$y z-UsoaYLVSGS`I+_f}k)0g%v0aK^PQvPz=iNU<{H6sRM;22!rH7VG5#=F)R&$=ExW%4vSA14H84fFmZGm zWF8Dd%>>OsFo5*J_#nT4+>EXcBnDLja{Flhhvt7!c!4k|j*&2^tOTbGkQgW|L171q zXAlkIgV^X8Bo2xTP`rU?7zULAAPmwA69ds8bubL#gD}Vp5C*Y97?(Uq4G4qGgvKo> zEuh3LNF1aFgpt*O#6f-@#fUZlD4s##0*zBx+=IdsT;GGlpgsXn2D7$QYDnz-0s|Y(Z*3@dJuy5DkknkT?i~)PTfc7^Dt_LFzylB#x{G#0KdD zVGtW6kBmWLFbq-y!=ShY`31xW`31y>VUReCJxW8;04R(=;etei^E`+T!k};jg)=A} zAYpLagXCZs6gMDsAPi!IFerXu@*p-y41_^qAR2~2@ejfvK1>cogD}WE5Dmg0Hb@OJ z4HE~^APkZN`3DrgAQ~hFqCxyo42g5}Z~($50W}?I{*~-AbAiC z^5ZDRod!U02~7jwumpu0R16gFAPkOsP#DAHKx_~O@j*0le1pV5`av{E4iujt8pMai zHAoDGL1G{bRReB|g4iGo(gRZm;-h1bJSdJq7^Ds+58{L397Kcs24at5)HDDJ7f?Jw zX;8R>;u@Uqq2UVR!{Z(l#~}3}IZ!ym*dRVGHb@V0ynyt9_@MX((a3y|7>EywGf-TC zXpq?;3=)T_2eDxoCI?juPro2>kQkCV;Bo=PAH|SyAW)oPi+fPm!om(D4~jz&hN=gp z2^0(y2dRN!X!yh1i6Awg_yb{(9LNrIHYol<7$gq07nCMp7;Gm<4unDaK^VjanTLc? z)WiHS8s`{!AC%9*c^ejXpg01BDJ;xDaRB0j;uKj8j13b9=>xGraRH)17+DQS9Ap+q z48#VR3F3ojP<(^*gV-<(5(B9Nse#2C$W0)5m|Bn;ka;k1C>xwcKw?k~=flhyrQ5-A z4hmZkhJ_m}TtVRm3L}slC>}u=qy{7p!XP#X!^B~15Fdm=d}MW?I0Ip*IJkTU@j)15 z2Z)BoHN30L)Plr8=7TUy3=0h&&xgh}Jbi%T4I~D_PyRq zbub#lhGCFCP~3qqD4#;(0=%vd#s=wwVw5xh5`(G%rw5Q3ATgL5K{N=1+zX;XaxnEE zK1dvdL2M8Pu}3ka{s)BzC~jaF#D|3qDDFYxpg4rlpsVuaBATbaIsR7X- zy&xKdL3&{E3XM;cdLCpiNFB&b5F4fz#0Fug9#H&(%L0(uAUA{LK>9#z7zT;K*rPP= zxQEW=!1DGG14s^}4;=R(agaI?MrMQbfy6*GNE{TWAPh1#&M)4M-1+4Kfp? z2gD!6#N~bDxCez9hz|-w5Dmkia0bO82!rIH7?ejrc?_HuU~(`Tss>ahgE1)1Kw=;o zqy`jMu(*e@L2(I^2dM+u31WjVh>a`;QwMS{NDjnDW`o2){83C?8bA&wSa`$Y9wY}6 z1H~CM4nXmVGwzYqf%L&Jw2lGie~=nboFT_QavA{1LG1$B1JVO7e_&!DGhu9y`#|vx z)dNxwPLs&;AT^_ya9RL`BM9S)dr-K8f-pK8#D`(19?;p3pm+phkQ^+& zVQBy+1`>zHB`BSNFuY9%vKyoh?L~iG$(_7WW_;WEMya8H3cp+yfGau|YHx zgVF~G!`%f^2XZ?|9_9}ae-sl=1IS?p3Oi`{gYy_D4IqUfc+3tI&oB&93u1$45C*Yf z7^D{z{~!!vgW?E;L2(X?dk`ChL3&|!gVccdAbUajKx~j3k!Vo80E&MQhNl6L8jv0s z4PuXC!toCccX(L}3TKcQDC}Y32NDCt6D)2)G)N3agV-Pp;)5`V4bl&adl&}AD+q(k zL5_bA8zc_W1ByEk4dR3J!}u^ZNE|8#b}x(#awjqy#2>|k;~o@dNZ|zTqk+Ug;SCc9 zr3Fx&g5nKC!!RhGkTFOc#s}$##XE=w@j>z+Ge8&==O7w}L40W3g7Y-Y94HNnYmhhy zgV`XrLEQ=BgE34DM2}*^agQ8Eps)nR1&9X4H;4wwgV?Zm1JT$pG>$;^1sH?)p!kMi zSp0*`N5-Hy2VszUkbN*VG#!BA6cpbeGeK-{+YaO&kXn!)ko%x)usn!Aib;!iSa^Zr z3KXWG@C9L*I4HhhaRy=|V~~0f28n_6BV$;cgJ=*2$-&G4#WOk^6pt`55F3;RkTJ+j zAaRg=Fd8HVawn7zZU=z)qnNZb01ZEIK7)lRC=8)&j5Z%AzCjoi7cd&c2E{Q5L*ttP zM1wF$45SVgzo7UBVGtiggD@zrLH5DeFgJnB1*wC{gJ=*&k{^wCBF1|`@retA!W|l( z@UR5&L2-r)gW?4w2cto848qX(hQ~dK55k~01H~gKevvV<7|2c#2FZhHkRBKfWrNcw zlnrw$sE&ZKM`<$B0w|2p(*`VFL2(Sjptu6Dk>y})q__vg5q#VS6!#zu(g)HH!l3wt z(I7S`K0z2H2679C4H8FAmmo2iT_75!7Nj1;2hr$$80C{z55U3;6z-t-0%2&_!pHhS zVxYK2r$KQH5(i-r8-&r>pg04?D+nXAL3~jBg2X@=WIu=oVW|D!_8mwK2!qT8(I9!4 zn@4H#;u<~NK=}w1=AifmVNh6uXb=XmVR9fE#)rip_aSOtra0kU9hz+7a z7{taV4i&?QXHZ%K#W^|##T^WT;uRK;ATbadgh6Z=2AL022NDCh6J$0_9mo$LF%Uh9 z$u9#yVF(LTQ22tv9Te}N_=B-Qd}KD151xwwsRLn<7>Gu~C}{&E4vI$@hQ%$&eh?cZ z4sr*`9uN)V!{k8XAhjSjg5*K$(Re36-a%mp4MXra4=7wgaR|boxB}&SWHFF941@H7 z*dTQv3}b`TgZRi8CI=D&$%En*hCy))asvp1*dTFG+=BFg*dPqz!{k6|0Ax3a2FZcg zqnP|O01I~z4T>93xP#&e6uuxa5F3PH;vgCn-pFb}Vjv8<>kp(B9fRTzWFAZ$q!$!- zATdx|4ivW_yFeJE79p!fmBC6td+cf-U%dO>WES`dcm!97BBM5`+1C;|Hc~ClpihS6H)lLv`G;~G@f!|DMr8)PoXPACno2S9948iJY!POBidjbifB04T0O;R%WdP#A*3 z8^i{MGboNgG)x@ChhdN!5F4Z)MB~CB{h+u4#XBgTK^VjbiNi2R41_^uf$Rs#gJ=+j ziGl3Hf=A<@3gtgE{=s1hiW5*cgW?m!2E{!HgYr2@41|&7G0J_A9*`Pj42n-s{DRT| zNH2&D5(BA)VUQR|4ix7g4CRCBfAAOsln*Kwz!;V;KyDkwBu9u#k&_(sN{G6%#5$%8OR4~T|ha9IRpgW?-r27t^0*$t9|(qOe9 zcaCDx(*P{YK;aLHFA$9!_n|!jW8PKhf$im_=m+YESzEC2nu767$`15G!%pK zBF=grqz0rO8N=cnBnM)H;uJ=M(f}wPL1G|z5C-u<7~~F+8=!JvdqHdv2FZiuKp3QM z6q6VCpl}0)At>BI7{rHRAR2^0aSw`95C-v)F-R|n50wMQEtCyz3xMR1FjyYh?W1hc z(f~9pQNj@x*GO>-E`LFBgN%{n!EHxmIZz(~#s{eb*#nAq5DmgeaSDo8a9#$9gD^}z zR1GNK!`6R+*&sC_407jae3KU6$l(Rb=g52z8zcq_eAG8-laQV(T=(is?o_#k(J?1j|>ApR((UfhGi5){6$_yy6R zcmu@;D4t+)AaN*0NgE)w*f2~#$P5q$sR4P&`263{)O~F-Qy~55gcdp!Pn94N`}WLE@k|28lz} zg3}F148#Y?gUkc5(J?4)L2d$Js2Xsc58{L5Mlto`9u$_KaD`z|{D3etPQdXFN*kc~ zg3_S83984za-jGHVURqCM#eC6Kyo1SKyn}&8mFMT28_}9Ah&?r1;QXU2!q(8n7Z+f z9-gqc0mU0Au0i1rqLDF34JbZAVxahhvcY*9#7Do6D11Tr5QL#|0?yx{xB$g9lm_Ql5Fd&`aSH15gD@-&AhW^hk>VAk9*jY84zdeG zL+t~r1=#~~%P6LOe4~dkC{CgA0j_VMe0Vwl#TO{9KzSTQBV(8th(^XB^I#aH9+Vb9 z@}PJInFo>siGyg69LP>|ca8GtlNLa6f{tPF0ir?i1j3;B1JTGBDu$Bxp<>|t4vkw- z9tWi@a9IJ0b0iFISAgsvjc@wIH@0v_4u23E6z?z^q#ncv#Vf4s4@(13KF;_DsRP9| zNI!B~0Es~{xSWB?gVM+-rs6msDDKF`pf~}g0g!%BJc8mMBnDz5VNl$IFgS06)W9%I z4Je*LY>-(XHk1a%JE&X$VGw&X-Ums%gW?;6LGc8mLGcHRdr(is&L3iRKA}7c%hRBE zhG9^ABV$n9gVcd&LgtOi56vgVB6H%nsv#XAxPjrW1e0FWF=%_tt4bvrTf1}y_X z^8p|Xig!@_gV%KYodcLr{E!FfnR}yBcN& z2>Ab>SwNJ5fuY2KfdRy3WME*3fwDOm7#NrY85olC6HAKY4J;r!$RJr67(nJaFgP$M zFtRW(IFMn+sFu+X7!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R z7!85Z5Eu=C(GVC7fzc2c4S~@R7!3iG5a{XY=|;yW3Whlc-R~g&F!wTw_dxzd$0!Ph zIS1YEApS7-GK%*={zbkOz55%VyMmBdSv&jvQq3ma5w~zXta0noW*KlJK_C4+E zwSf%Nk?enB{0>qxknuxHyFq?~VOrWV7|n+H8%7UCH`3BgFn_~nTG}-j&4&3KMh`|e z(#lORKO@t$vTHD!i|lV0doa3_7H)$17aL6r`v#kt*!&F>8*FZ+wtHay9HmD?U^E0q zLtr!nAR$1;`2*$HX zL)tTBI3C&WAT}9B3|TE8|Dxj|>lrc}j_!95pA2J$tQL@e(eaS=42cd$_cx4BqA^2O zIn2N4^pN!s2@Xg1GdiCHqlTnXbpK-G4@r*^>1b^JMHeH|s3E5e-JkgQL(X$VI2fOQ zVR9g2Kx_~_oG{2w#9)x0Vd_Ba;eug)B1D7y3sVDP4;Kvc6CoPrUzj==e<;&1zYs&i z{0UPF;}2yT<`-gUm_L!#!o-FujqDd<*f4)0tA&XTH5%Cu#Ij+2L{<+I8)`JNABbhc z{D`a`CN|V)WIqtghWQa)JxqM4(CF?bmJjnEHuW&Mp+IAEKQUr3zhP4glN$;&Hun=F z2J;&(wJ`a?OyhDpG2+Pn!loWsez3B!xtkO*n7?r8fyob68kf6?6^Ho?mwK4|V5D)m znG|uDzwqgSsTpiEJ~tC92lEp?^)NMqjmGC@V&!0dBBUOsZm`gV+)J!H%s<4ahp8P5 zG%@ZaMh&uG2&qR_JJ4)GZY5S8*&oE{K~_IdY+~F=iW-zo4AbV+n zVSXUhY?wZJ(WJVMSoJXX6RRGk4yKP@G|XOV(=hjwV=hcDt!Z-HMv5Mg`$D6g@?KWDQht2P_wsWwU zkInBOvBBa#TDk+|Pkfk`b`P}K`23A5H_+~+h5g9>BZf^2Hw=`S#Q1kq&BzS_GR_|` zBRjMP>G=a>`)5?+Xb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kin zXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD zjE2By2n;|7w6wG|BjW*Z!hl+d>~|1*KpisxmV^9^j0eC8G_?}uH(Zv$!Zn-=0D+3%q6 zq{ROq{UA(=86fq@7+o*296i|R_LIWL=RREK;L|hEa=6@#OMIZ+J5cr`hsQvX!zi;X=HZlb51*!+wv zMo+iU+b(2(jjg8kbpvMI4u3(A|ei9U*aaa|Q#SkejI~KN$Rr z?gnc5j~KJi?H_19F>a=&8g&1nn~g4yj}Oy}PYpIXn7+YCV{)&2W|j z`5lCZvmbH!4diba#-(Psio^VkOb=JTV)GZWzhP`_>V~5j%)i*^;pksK= zHf)F~ME55?{t)vTQO*VV7o-qG6T%>M!wJLu3-S|;O^61m9WEH=SCD^TY(g|h?Qp;_ zf5Q9%;}fD`>W4ZF^C!$7Fg`IfOwUlIk^Kns1F{$)HcZb@q>=pwb3d{eF>IK=p-3b9 z59WSkF=E*;y+e&g_8-jc$YR8@VS0xWjqW#?+tI~|;luO}6&jnLU~b1IM~oOu|4^W@ z`3L51Y;wei!SoMy8kb*S?#3lgNF1hru+!N5f$U~%^2CZEn==^MxcmWgD=v9r#bM?Q zRvMQ-VD7~wPpml1oWV%r^8?Ji_|y<92QzE1(fHgCb1ObI#LB_U8cZ}Hx5L~@NF6cq zFtY{=O^Ta|QA>=P!Qyw2I|#WKqy~lwsU=n(X5K*4#JCsPjl`%YMh&u=1IZ@Et;DD$ zMa@9^8)P@JZUw0YVPe%2s}5x5Kw?teNvwKO)D4vX$#EkodWcm!>VNu&0I_icQwMV) zj8APEWN*)a3zL6hTdkh{pyM~WVh9kj-z zxCdD+$X&>MTChQO&=QmDW|*7E)laNGm_4+jiFF6MI+%OV#c9EZ*+p%d61T$KLWvo~ z>V?@wZJJp7v8jW(1)ChT#b9<(o2JB_Ft<=*1}S=B_EC!_#cphBVeY^tM+-5Sebk_- z;Xar>FtTy^1Ljs-^2Cb6%o(gSE`Pw>i%XtZahN%Sk;dl-n0xW5Ayy7%)?lOYxgX|M zd}@f5gPAp$XhLp>xs{MQV&q|F4HlXdHxr|l7&U{%?;v*&axX{?3=>jItUS!Tfu@OZ zFR~knQBRB-WHSenO^REIQA>)Nf%G@XZeraEQVYVwswY+*$jpJnq`H$>^`xj9DF2h= zMpE<;t9I1?^bY}I;|8V<=0X^s+BD33deNl1ALcGn^%1KcWzS4(2XoF>115 z=F@{F$K4=zk)w|kJs>-1jY)A2vRaV4komM=gY2LsCfCg{H<7EKSbZ>iXhjq24s>-e z_n?c@f)BHc+B79@g}H?iGl^OmBD5!#-mC zjjVsQ z{SIRfg?vjbH^TgkOjFD3pZCFu&rW zsbR-pH3OHQk;MnA+o|C$WPjpfQ^TIYXa+9-ViO;X?x)mEAb*0)0MUdnNZoM4F#m%5 z1Y;ATL28E!hWQob9~heu4N^NCFwCDYzrgr}Xqft;PQ&~O^9PJi3=PvWRB2>C!u)_N zMu-j5GZblLzroy(EJh3)rf(?H$o_-5A6bl8HcanOqmlgwb33vav22*$p+uwm4d!-q zabox|y+eh@<|mljvB?o52Gc(jXl(w0xf`1tF=8gz}$*Uo>+01IfIqPWNWM!`wxxK4R6w%%=xUtecS4!Q6!` zMol)%e0tF2xEtgya`cg+2V@7WF)8jrRts_$GM^S~kR7zdC@MXVVxJ1L=wwTn`9l(>-=dMWh}z4cQ& z4Cw8CO6{Yz|3T(a>Lz;Y2f3NrnBIOEDEsh*+d#RAp7!GNKS++AZlSkbApa4A>Fq9h z*hh@Nk=4+{P4uz@*mW012z>S5y5)zwuXK1h5hV)Xot z%m?`m=1&;y<>loCW5Z~Wo}uV>kegwCK&L@|2kkjP7Y_;w3c@BnRMP;u|6qKOA3+$# zhtatF4@v{c$;n9^NB|VRgB`>Cg-(O~4#Mc-xcm=O2TC^}4B`)Vzk{L*4jB{ce~=j|`4NUed>96)LB}9< zvv`~emQhX4PW1sE9^7-FDo4h9AWCP4;a&mI8I*Xm1oefzD6B84YrKP2XkB^TJ zx*SNIxVX63h7B9mfBEv|GYEss0htF<%g@ixmzbECaQX7(i~s-s{|{0JQV-GxG8e=K z=>cJw9UwMH9SDQ;Jbd`@KFDqj4Gj&D*&wq(=7aQt_#knRdB_-K21qZ+Zjd`*axgnV z;@I@T)Pd{+*$?B7(t|Gq$S8k|$@ax)`9pTl4TQ!BC?Lmfkk-$iKYs+3A0Rdw?&g%L~)MuWr%`46NP6b{(@0P-IQgVf-|Aa{Z=Obr?3j}c5S$@FOX zLvoOjq5;&N0JSAR7`eRwG6U3RfVEA~E#pl0K+W!KT;}#Yc7CbyWJfJWEmEFF+zCIu^5Fa0g=?x1D3q#fq;=g?P^7+=S zTQ|eR(aizr1=#~q2eK1c9wgV_-{1S~+qbVvmo8ll;v>t0)Pv-)=|xrtGXMAQ-#<^E zK7A6|jDmuK0+2XJ9$7s$Hc0QkfB*i1>;uWe^uqWcF+vz-223xAPe>24Jjm@J_k!F3 z!XR^y<%wZ~^nmn&;sj(KF?vST46P8rD1QWy#($t}*!WLsML|x!ff@N8WQAx|U{HXJ z{|JEj{}~t;_?0K?_LOz{3FIM%tw-g#949v0>VT2h3j(`$6H( zCgS*?P0R}wi#USghdTae`VaC8IIS`B{AUDVP?}@r0EaoqZcdq)|Ll^%a10Vdq*F@L z0LY&_V0%FE48q7^4YHF{KJ7mk%fx`g03-$q6UzOLup4A2$V`yGKp12P$j@MV<vh4?zb4;vY|AYJiG7Drk$XpNx z*$)y2VVE4uZWs+xL!SK%;4}~N7sz}NhS>pf0|9UwjkgWLtP8$^TDkZwPu zoQK&B3V)bAAiF?j!Z6HEki9T55F2DSNDibA8yyHVT&Di2^~qlhD(x*JhG zf&32kvzQk=PlNa%3~~#|evlm?3}S=bB%k&l#0OzyH-O|p{s++@OiUPn%2tp)AblXS zK{Uv2kX{f5nGf$971t`?0=BmFbvWQ!XQ4#ZczFHVGtXJ z!S=|+fXiYK8-!tUAiof@8!XSn3NGuB!vLfggh6(LFw9(#{UA0n2Kf`253(P`2k8Tw zPq3W{Dfd8O4fZcI{ebKTg(Zvz*#qK(+zA&$^E+4#v~B{aBhG$MS_IYcpnL$S_t4z| zvJd1RkeMJ1atGKhB>UlfP#zHTA}Z};bssc7KyCnGkbUSFY!}F%Q2SwgkUK#bq#mo; zM8pLsJ%Zu_WCqB7WDFAng#|neK<*34_80`KK+yZh3hz4O=xdY@+kli2}WFNPB#eW#))oJ}NU^ETP2dSZk8$kAeFvu(r z4YCu2LFR*KkbT%NNDicj5)88s6rM2qko^ksub|oD|H3w#|AS}{2C+fz0jUS+r^J4c z86Z1A7~}>}T!Yvk^I;ffCy0h&kQ_)oNIxa^gUkTg2g4w9L3V&>7>3D-xt#hhANCoH zL2QsZO5*_Jepq;e>;r`@2!qT9iG#ue=0=d+dL{q=gD{8b9Ax`J zZUFfk1!0i?L3ShC1F{?Be-H+-LH-0`WV=CZko_PvAah7{ z1IS)b+<|D2{UCpU%mUd5!Z0?-4v<|S`#~7Q2VoE!qz)7|Aah8uA7m#;4=CJW803GL zogi~T7-R>?E^PLL3ghA$mFo+GqAU+6#+yN3pb~8vnA^SmT1>|3l-5`u? zKgbOr`$1-d!W~3|>_EmKd64}eF_=7v28n_66JtNfogjN*7{mv;38Wt+2C^Fz=O7wn zHVlL81la?^AT|iYLE!+xFgDD5kl7$NfG~&+vJZwqYCvHD z;)B>AGYQ!ZlLz?|WIqUl>;$hWGYcCHatF+Q5F3O+>OlHH`a%AHnN2K>>;{m2kXaxMG7p52#X$Cf><7^>46`4k z4&)bN?L}7y(hIT=hC${bV~{u~+(8&-H;7Fw`$6FViU*LLAR6Qz7zXh{;vhGG>;u^g z!XP#XgTf0W4^ofrFH-p+cOdHl@j>Q-+yKKM`#>0GH;4^#8%Q0pUTUyG_Jc6UY#0W) z17sh_UJwScLH-A+qlTT>%mA4U!ytEn+yKHLHV9+WGb)BT1~8CeLi+dv#?oBkha!gP z_ydce>3?>a5HJR@k>d-*2gxCeQJW1ipU+_8e-H+-L3V=d0MQ^m2!q%lF+2 z7>0?1)PXQH-2k#5gkkoAXb={0*z;dD^dklqcfSaZBTC%>vJ>PN7zX(jWGBq8Aba$y z{{IJI^$Z9NV}sOz^i$#nki8&(!Z65wkewj2K^WwBkbTJ5wB!GOkXt}LBZ2Dt^K4y2b7 z`(geE*#$BWWEY4A*$czyc7yB(xdo&a6bIzk5Ar)mA1MAnegN48G7lZY>;~BfvKNLy ze2^T-evla;^N39cAhjSpAisk!vfq*I2bm8FXOLa!7$go-3$h;+hakTYYd^?tkRFh~ zVKln`aoG*B7lc7}gY1!N}3 z9*}*=7$gS5AiF>qqz;5ZY!C+75AqWs`$1uk?0=ZuAR1&p$Q+P)AiI&%9EcCIA7n2G zgVcg(P}qa?gW{GH`$6sn>4RaA{h;&&G8fqmAiF^rWEuFvv_82Kf(!L2d!r39=i6L2Q`)ApIb} zgUlnwZkQT;ZUC7H!ytEo+yJr<3|x9*|ko!XP(+><5_*vIidqsROB}mfaw8K>h)li;h9=LB=3?YTFI6 zA7m!T4Y)AKUXUC}Ev+!f4KPeAJ4TARq>n$4zx|zIh>kxD*0jJTV)Y+{2f}?EvXTE` zm>zBb*&$@R`#-m4JsgAhq5;4DgJ_UjXb}z|vq5&~cK`eDeDME&5G|km?>`8G*cL1P z{|8|Z8{{@>x&h>8nB4)_|Nn3J|NnpH|Nmfjfb0ah2Sg)dkb9}&22gl{%mQI#zk=*U z#vp%#Fh~sKHV~#pIDq^OvKxd!Y>*pZG|aysJ7H{C_<(4TT1vwJWEV09nF+!mzrySU z(NP~D@d0uR$nT&y0*Qgtfx?g+cYxv@q#tBHh=#cX#D=*6WFJT#+!Z7{mr) zkT?i~?1%9|@*q9P_7iJ2$bB&TL2d?NkU1c;K^SBY$ZsG!K=y*fKo}$r!XP$C9%Mg^ z2Kj{?`$6#r!XW!W_JPa>*#R;a#0S|A!ys|w_y@5;>Op!zegOH46buS?kh?+pK^SBw z$UKleAR2~2d>97V3Bw>UklR4+2bo2R-5|BFJOFY72!q@J@-xU@kl7%6Kx~j5Fbv{@ z#6cLu2Dure7Zjf$Gs(puw}8w5#UUsxLGA!yP#nWBh!4Ubw}I>ixd|i(vY%XgvFQW3 z1?CQr*`V+RVUS%Q`$6`C{0$NVxfSGYZ01lZ266|;Y?xa>@dI)Lhz8k9srjVn2iXU* z7lc7!0&){6`YBZl3I~uIKz5Up)+x0gq#tB22!q(8nDn(3q>n$4fBebijfCAPlnGY}FsQ7)YKL?%)zr0o&(!=HLIy|Ns9_ z{Qn;#1?$Sx2Kat}xh#0S{}@*~I|5F2C{hz9XN7$gVcgWOE6 z{UAF*W`HorPLSOo3}S=qfMJk42&3~sVjwq@YCkNm!~6uIL3V=d2E_%453>v8PZ%3S z!}uUIAPmw&j{Tst3(^a-59AjR8x$rWw}8wBiGeW4UStdsgQ)@O0hJ%5xB=#WP}qUo z05S_?Cd_USA7noWgT#^T2C+fnAhjS2(obp{M7JMg9>`pfT_FF!><95d7$gS@cMt}# zLGmDVFbwhsC_IS`17!O_dO`jGnG2#p{sEZ_audjI5C+MC>;_>F8)QF7El3TB4bnr2 z{UG~5_JGU-nG52BXb>M{AIM&03=#vW2Vsy}Qu83X|3PNJ>;lmsKY_$RG{_E+eaQC1 z*dVo_@FUfJP&|Rs3`j31>_GN_%mUFM46+l3L3Sf!nB5>6q!wfbsp%hNKL~@|4YD5; z)*!P$Y!HoXA50G9K9D%bUJwTHLFz$ffXpHngVGHMgUVwN4KfepcaU8mJ3#&e*#mM1 zvN$MyKysjPB-d_aeV{Y}!XS5m+yJr@WHyKmvIp62kQ_)1B#&$cHP|3GAY+jIpl|?T zP~6bUUR-Vm#Rn_FqER^}pZl|NoW!CX#=~6~S|;5NQKsuUYZg|M6G;{-5;sKiKYq zpAZ`49$JPW$b745f53Lb?BD(W|9_DEAh&?nps)bBk=pJ6*$uK2W*3MCVUV358ic?6 z|NkF^LE%F!`vs+yz;O%mH^`45^O5ZYu|eV>3}S=iL2PQI2aw$$zktjG`45C)Y><0E zVGZKLFvxBg28oHQnNXA_L1`Nl<{+~`800^Yy&yNi>;{Q}Fw90J#gq2e}J`LGs9UgV-Q7l-dun3uF&A46_$RgD}VqFbuLEAvgL3V-c1i1;u2JvC`gJ_UA zNG-_!pfo^k7=Y{txgTT}$UYDa!Z15P_JA;m55h2afW$x;;thu?gG&;yD9ZM%*~*90EGjnJOY^sG9QFNZi4Ya_JZ62 z5(mW*NG(i1wP<8_fWicZL2dxK1r**O8YE6FJJHR7xd9X&*f6?!TJk||p{4nwW+U2t zqhT?8!vbUc!2#+13n&|Q|3yJ!QAuWUPHJ&>W(x6#J|L-t-v8ym-~hS*0&M<&o&W#A z*zEuR|4#q^{}1^8|9{H=|Nl$=|Nme2|NsAHC5(la4|NsC0 z?EnA&Fa7`j|5_*pu|XIl4w46%1JVPs1Ee2>L415>fXqTS1Ee0C86fjP_8?=B+d%3; zc7gl{@+Zi@Fu#Ln5FaECk_VX!QV%i*hptsr@jSs)DJgTz5qe5FaEC zk_VX!QV%i*f&-43z`2a*G+ zLB=3+L2@8+`4i+{kiTIV#0QCk^J;<#fy)Xh4~vsgTz2`AoU>sf&2;bFU;>C8pH>QgXBT#K8up2g!rn22u|)3#1o@L41&ZLGmEKgD{8> zQVUW8vJ>P_kbgn`hG7sNBo2}XnE_HyD>FdmgX}@ZAh&_+1i2UFKaf8`{ss9PhCzIg zI7lAkHjsLdSs=YI4B~_Q3z7$!1;QXcNG(VW$WD+yLH>pL9YllpAaRg9$PAErTA2Yd zA7l?Q2DuGnC&;}Z|AG7o@-NKqAR5F6iG$=pZUdA*^huUT!y9HziG6uO1WFN?HAU}fq3i2~D28n^>Kx#m0 zL3%)Dfcyo+AU-YJ4l)yDAIKh17=gkH6lSpS1JNKpNE{>&a|g)JAPjOdhz7YEW){di z7zT-hQ;2jBWh={5#&~w-$68p4-yB-lVb+5 zevmyN`#|=B><76Q9fQPS7$yhu8wi8^3Gy!plj47nyJ7ay%l{yM!!RuTVEzaB58ZDd z|G~syG)N4DLH-6|kiS6~;qvC8zcq_caS{Deh>!n zLE<1ap!fy36Xa%4yu&bv4-yB-gUkWxA;t`lJ`jfK1=#`953(ENR*;+VVUWLI801e7 z2KgI?L3|JfsR5Y-G85!Bke@(yf$Rif5E~>$tlMFJ2hqrW0GUIK86ZEyFeuzW7^D~E z29Wze?gU|wn?Y{Jhe7^>VVJ*RG)x?%A7mEDOpy5?dqDPq{0_n(Hb{&Zw}bo(@;3~F z_#nT){D5u-x_uyZ=os04RUj7-DSgUlF6|AWE=6uzLa24Rr@K{N=H z>vv>-;$ja5|AXQamIgp!0P-&kgZQAZ0m*~Zfzl@^o_zOA0!S^1Bx?HItQh9SlS2CAU;SOBo8tNqz9xIghBR! zXb=Y33z9>|ATf{}NF4}++z4_j2*cb?$d532n0gou5{I!t=7G!x*#WW>0avMk<DsH6Z_i{0Z_e$lovw;)BFN@*s0SdO&(Xb|GVs7&?Z@gWLwf zAa{Z=%-tYA;KLwwAbl{5t`{T^!XR;w8jyV;|AG7jax2JRAPi!I#6WHb$-~?U;)BFN zYC!%2`4i+{nBPG(hz}A6$%D)R=>h2n*+mG0+yufP_kl3TtuTzs-ynIA9+26{_JG7d zW`HnA4KfCaf#gAYK>h>y3FKCgzd#to28n^(4w46%3&J2iNF1aF0G7}^ZG7p47e2_Ru4L1M7{0@=>sR5}4=?B>Z!XP_A7-T;- z400PX#^!gBK9E@;3^E($CXihq4B~^_3X+G}g={8>4Kf2H2T}tv1Ede+XONpg7{msN zfy@HQgUkbA5FaECQUmfMD4s!nM#dm9kQ~Sikb00lkUb##K=vYIkXx`}bbo`?!1RI4 z0htFf7i2!jJs`V4ZUWIDK1duS4^jtG57G-V1BO9-kT^&l+<=Wn_BTu(qz8sUW`N8BVUW8( zZUNZ?vJZwqe2~2$_kz@c^nmok%mdLNK1duSkIfvA86dMk=78iuW`Qt>4-yB-!~72l z0~iL0f!qO-2dM?=0oehv3uGqhG7sNBo2}X`46NXqz_~k2!q%lF_0Y~d5~Ek3}S=CLGmDVFgsy1 zNDL$gQUg*8G9P3Q2!rf|*$?t7ItIyu)PXQaA2J4sfiO%A&awkYV%q)<85E~>0vIAre41?5y#6WT&bs!A06U2t; z1E~X<4Z^gh6bO7|5?6d1MR{1IdBZAY+giNDibHWG)DU>;TyXvJ-}p z{SFcXsR5}4=>uVqyFuoFFo+Ej17VOF5C*Y9;voA#_9Ocp#0I$^BnMIhQV%j8-3=f< zNE{>&G7qF4WEMy-2!q%lF_0TT^5_^O4#FTcAPi!I>;tI-nF+!m^Fj82{0PIyeh0~c z)PXQaAIJ=lSs)CvAH)WUgXEDh$S#l^NFB(}$Qa~)kQ_)2$ZTxx0ND)^2g!rX1*r#_ z1=0(`AT~%0W;aM341?5zFvxru2JvC?Aag+&WCzUOAR6X(5E~>9QV-GxvIArn$WD;m zFbv{@Fi0L`4@fNxgT!EZko^u~gY<*sKzcxSfb2qd3y2RA2g!rX1gQtv1=0(`AT~%0 zWDiIlq>c~(+AoqdX334yY53qU^M1#aYa-c8(`4^-f&QU@{*p{8pH>QgXBT#VCI4B0%4GTkQhh~qz0rGqz8mS`mte<*~l2A2BaQ_L1uu= z0+|Ui8)Og2%^-im{EXg*1j&Qcf!srD|AX8FvIm4gY>+!a?ghCUghBoQ`3V^#`xnLr z$%E7)V~~Cj2AKyk7i2!jUm*X%{EzH+PE(ZrJs^8QZiD#&6mKB+f-p!7ghBEk3{nfiAT|txh*w7lc86f?*IJMH4w8qN z1F{RGAB54(0I7pvkhvi9LFz$n#O8mHe?a~qghA?%F({lsW`Z!te2_gL_krw$*$<;Z zVjy{tTS00;?gn8HA0!Tv2dM@56XaizzhM}}2Z@8^LFR+h<1+)K4u(PI!qkJ@iOc^W zf8p{!NDdu?{0=e;WG2iXAh&_+1KAC7FARhDAaRg9$iMj94$=eiFU;>C8pH>QgUkV$ z0rL;YUJwSE2@(U@0aAmEL1G{|kewj6g4_&pJIJr-ejJ`e`+LGA;|!}Nmmf&2}_AU?=EkUYo?ka~P(fYiY-$Xu9u zkb6Py2Du;P4`Td_tOle9q!(lc2!qT6`2l1;$R3cLAa{b?3&S8j2!rH6ZU^}npW8ud zL3&|+2hkutNE~Jc$Ub~#fYiY-$Xzh?AoqgY4e|rXFUWo)#0KdBVUT_h2AKu&JIFsE ze}U`)*$Hwd$i2uIBnFZLse`!%q!)xi`axpo`a%ANVUSs*m;tg2hC$|n%m;-R$ju

    591w=NALK8PeIPf2+zP@Vw}WU928n~@LF!;`0qF(d!R&ufID!0$ z3xnJb@(;*gApe2<3Gy$dNbGeCNT#7Hp%qz9WB zAoD@?AY+iZAoD@)1^Er+N048U{SRV;#6WT&H6XPhJ3#tCW`bxC28n^>U>L*)iG%C_ z*$MI|$iEs5(mkH%mV4b=5}hB0Wu$C4>AUcf!qjkGstfsKZ5)U@-ry@K{SXD z5(mkH)PdXx(gV^9!yrCL93&6JAU;T(8g8eC86dYJ`yIpviGkb*ax=((Ab*1Vt3%59 z1IdX+#RWNu#ToGiCP=3W5YL3(|A>74!06c)1ADfGj*bpR=Y$RQ_-k)(|36s5fK+#Y z)PvkVkZ}NV1GVf2`Gr*TNL5cQw}Q+8*$r|th&_r2wj3J`|IzRt?r8v2-Vp0YklMh& zz+qLlgUVWvzlaF~kl#V-iBU@rHN?7`9&Vwx9U#9Crnn&1|3lT?#QKjIb)YaHM%}2I z(fW5Z4UDD%O47hcFKa+`pGuWVM9qd0V-c` zm9e0<1THy3ZU^b5MthEsJs^2}ejpTwl!P6~Zd@4D1_OlwE_rlukRL#9K^Lc#4{`&@ zEtHx=YyF^f1_}dO+cRqZ$gYz>`GH#H5-4A3Yikck`xKN{KzRmNdIpsxAT{XnAUA`; z09~9MKFAzU`AUvnO7-CKAIw~k-|>YHOdUQNq#tAsJ~hKx4&--GI1gt(4h6pr$9Mph zL&TPypnOf%OZEJU%RX9(gX%s|*-NVVps)dj5vlsfRSzm3$#Ewk z`#@%b%qFC6pyfgC1jQ90cYxxBT5W$qb`3^(P+StKM?v!H>gq!_?}G9jD4wx}KQZZq zm@vX-HnCzLw-Ks4h*5*fK1%fBG80*xm@q_EkIu#yKQOiU)ZmkY>8B+PN*|ywAjS?_ z#04$PB*rh~s2S+EBFD|6dWOgNASjQ5@+!9Y1ho}F^4R2tix@sXg8YsvY^dRXP#Hyy zb{5DSLUkoI+>2}mHQGNQH-p@cY%V!$T;(SyZb9xO$4o+cK3W)rfTSb18fUt--s zi8@?yONp7tdO>NB(D)CsdSck1a0jJ5V$>2+1IiyDHxg1ut~|(mP#BY|ADcc<-T;LG zHaTjEfy}2?7?bKIP?%Ap+$PmtWcBn2V`Te~*`V|Y3Ik-ZQTE_z4}#)`()0;RUqdxc z38ha^c?Sw3eEy4%kN-cE!vLS(Kz;{>5k57Nil{qqJsxYCviUse{Q6l{5#+7p|_Z1n2BPYCvjX{v$+>rn$k>_aPKlC~xH{}U1t2)5aXF>5fY8R_xc($Yfl*)pSHNO>3z#=HS@6R7!k)86doWq5MvfdEiC=uQcsK;TzZkkL2d$>g)Bx18{}?m=7GdPX%O9P7Z;cR z*u;_5f%IZC6Il(pY>>Gi^U2kZt`B4$C`{4Caq%~9-1xtwqy*lc2e}1g2C{k(pA<8Z z^$r#`XnryvAOP-9T<#*(ub}=AG3Mg3k6z*+H-g+s4f{am!1{n7b3tKC4Rh&b2DUU# zirt`f1)#ZgWIf2~2APel2AK^CKae|+#puN*#(l)N1v&0Peu0&v$ZTBZfYK1i9k}EN zi#WEphPe&o2T&Nm_~|1C=?1>;;w8pfn6h z6QD34$338M1NjlA7u03~*$v|l6irOJ1C?PQ_Y>TDt z4kVk@I0mH$^soWNE2v*UPk#^-kHokKn}6`>!xvtl^bT?tKKonnlwM$Y1{B61d(hPo!v}>MDEzRg1({1qI>u%OJ~5ElAUp7>!6gTBGsx|@`=4>JyL~KFDT+%m9T4ve}ffLFpS;{vyY0kXaxz zk@bT5J1}=+lZUAVsR8*B#z&_?@}TfQ7pEp4UwQz!4de!V_JPt8$P8R+@X3SxM#y|z z=2A->6nCIF!e$=GZ6G%y%YoG6^DnYmYO_K1g2I#9=8|F_wcS8W8iTnH6!y5{3Z{mh zG{`-m@Ibbc6#szqVM}8maav(e_<`I_uKCFNL4HS1kNEt9te0H-Db)uWs{*xE$uS31 zug#b-gX}eLps)gk898>-N)IS45bGvT*bzI{1xg2?dXQ3ggUVlG-A9gHAhSX7LXKX1 zdO+m>D81lQLyR1#PXUS}V$@PY4Jf=paYw3IpfCi5C8_#|RSzmVh)sLMnu$#v$nT)I z!zPDM4CHrO#uF$lf$|?dH_$>3nFA$Ne(QR;S(Js@)k*+oxzke@*Df@~*<4-!WfBbE))M~okd zRY$D7)Ko_(p76QafZFq#HN)4*sNAoKhI zL*fq|KvIe0`~gi(&Hrw0ZeXmasEBnfy@Z4WR&iRTVM^`e;NbWV@(YM2#*H9#AdF2d zXs#R-J|G&KJhjC@_E2gcEKWgYQR+UBTdJ$8!Rdoivxw0j6&3X#gh75FMm;t))O0g7 z&BEqZWHC^Df&8AHo=$RpL(VhQbTct#A={1YA7a?ZdWo?Mo0*_+1L+0PAbAj*+%${q z9%9S_*$J|bob-yW7i11O?gr^AE-r@WQ4k;99CUwy{DzJ}egNqO(J(m>AK7e}IE=<7 zFCZWQE`LEZOdU2FT`xKxn;vXp==#w4Apd~GKr}Y}FfouCbiZN~gQ*A6pg2SiYmgX7 zABYC2!G%Hc$a+C~(Cx>i7g-!#A0a-nUSu}9K9C$XF_1V6gXBSe0Qm)3j2M5Q>jm+N z(TAR{(Df1G!`z8ZgVG1cA0Qeg2jZjq4<-kqvB_f-1F1#F=;}aaKL{h+kFEzK2hsw&S6)q&gwiZ>93$z!9D z^}yuO#nAa6bs#^1XkswPEg&;MZAcIeiW3lxO)tp3APiCqlLxUu>OnM23>ytn12Y?$ z4O5HF&DhkTi=p#j=7DHXI~SX|AUPOD)(`Rnhz|-Am^_RI$$|V1!Z0xyjcf-p8zv8; zLFOX+AJpyviGk!mW`o!u8pMZTkQhu4E-`FsU}{1B0LjDZLlBMbK9D@J9q95fv(VMS z#F1%``$6hJG_p8|4HAdh19BIPjV=e`!!WWQTw)-*K;}8I&?lr41}?XV-v#_XF)+h;Qj^3 z{~!#q9~34avtbxy9!Lx%4->;igVcfagJ^8>ATf{}C|$$+1rmc{kQ_`uNFK%p$s@~w z_%Mu39Ge(SJ&Xp02MB}w1+oX^H&Fb8_#nGs>R@6ZahMn`8l)Da7ewPy1Cj@s1)|Z_ zfb@aHK^UY4gh6bOIuMP_2Z@0&HgRlXAhj@z%^sNlL172NFtxbM0)-t+4Ty%B38F!K zWQ?p9mw7PtAhj?xApe5g1(Jh_foKpPhCzIgIuMOd3?>JnL25yIK>h?_5F6xf5C-u< z@*s5}3}S=iVeSBlfz*M-K{SXDk^`v$=>@qTBnHDEH6VEq4H5(C1E~ekAU-w>k^`v$ zVVFF~3=khA2NMI)AU?V{$S#mLhz6-a#~?Y79*}!M7{mst1z`{$BoEUAvlm3e#6a>O zJuop44dR0^Obv|2r52gF&IS?CUKDs^-ADcLOJiyEW(J(zAzk)DKKR*A1 z><8%uVVHiH7&;%M28Pk)(D^WR$TUn1h=z%S*dTYp>?9-(lE)PWFtb2@2bl+=L1Os) z50VF&3Bn*Y$Soim8y_SF!yq-pszuiWavQoB$UK-Bhz9YI%?F8rFvu?;IS>uvBV&*_ zNDs(-P#D3~fY=~85QeD-u|aANF9g<@j+~m8W0WQgD{AVE=G3vKiRqK>h$>kQ_)Yh{ngqrUoPi!svQIVjzr593%${XOKLI2J!J> zkUC_yfz+alq4Pm%U>KVmNDLJJAR43&L?hdQ&c~((BnHCR)Z!Bd$$>C(8VB)lVUYRA zZUw1B7enWR)W9${Igl8-f069~$s_YYW?>T}M;xSw6pYPYbTM>3viaEL(8UPxLFS>W z0f~VyK5^tQ2B{?nV>2IJ3=~(ex&+-0bhRLPbTM>3s0;>)foPC=5C-u<_JC**ABI7E zQp92Uh^0Yg1ju|44buZ+!^A*t1o<6=Ve-f{vRarpx){t4AaR%)^!y2ugV_mF1ENV4 z2k9XNV{;p_7&051x!B~<#c=rn**tVTFga4iVfu)rvAGQ<2C^H*2GQ8e1f>s<9EgU= zgZRjC2{H$n4^xLsqw9gmktz<;2cpr_7)T5q!^{V{5he!{1JNK1ayzm-OdX6yw+CGh zIv=JFnZ~6C6s{n zL1G{pnGX_!VVF6@sDbGL(fHI8BM(vo!XR_d{f8_DVxzkq6t*C75DhW|9fRbM%>;!Z z2&1b9@j+@~G_oFaGtv1VefZSllLv)4$WD-%Aa(fs2a-p(2PB5B2V@ouBg@0=0*S%o zVKhh#W)3nNCXbKCrXFM$Ob<4BWI0%TfYhP;52Oxe7DyiEMvyoNgT%1u0m*?dx;)G* zkQ_2bR)@?+R)dR;t{xPhATbaP(gR}SawEv^AUTj)eClwSLyjJhS?Czq9Aq|39jhwKF&GA^ z1IdBZf-r~;QV)|yra|K9@`Usdl82d(tPaKo(I9zj_JYJ=X#!myqz~jT5DiiXqCsjv zG>DIkLFzz$0?C0ejE$@Y#>OU(O$??UL=%z+sR3bRb;xXVIS?O&L25v5hhb!Q!{k6T zNG%A%!V1Jj#-MNknE_G@!Z0?n8W92M`-s4>}*F z4<-)MhpwI+|AX|Pn}d%JatF)}AR5F+#~^W#86b=<2jauh7fc<924RqT5DiiTqLKL^ zF%X7{gXBOM#D|H4`~u^H#BgDd8f1)a4n97}Opsn=H6Sr$4DugH9)v+`bUBcIkT{42 zsR7X-3}b`Tqw_&xAdIXJSv^b~M1#~3!Z3B{_Q3ccvq5S=VG6<^Hq1Phz}FT#Ri#$EC=EvV~|@w@*oUi!{m_JFfl?j$PAc!Kx#o|fcPLkBV&*p45RCT zi6he>b3p1qG_p8I3}ilt24QS$ka}b}5FZ;xHyfK8LSi6$K>h^LFts3aK^P@3=(I9moHnLeDF&GA^0bv-Q z92%q_WCw^w*N-d)V#6@HIuIWu2XZHf24NT*T@J(tVUQXSjgCR$=z8(F52PN1(aj;2 z57G-VAB2(hfW%=KBo1PO)PQIZA030lVe-gqkU9__hOzNMVlWI+2QvpGhRz3x!7xZ2 z2!qtX#IVu$ya9mpII4Z<)sObtj3nT;-wOAe$Dq!xr> zYG5=>4n)Jm&}ony45Q1z_#m@E7^Ds+24W+Nq4QztU^GY`86)e3se#cTb-3iwOpca3^N;@2FYX7i>wZ$28LncAU?7_bUsWSG7VA#;)7^py&y4Uj87gU2g5Ku=zL^x zeD;9Uf%Fnm15*d0VHjN>Iv=JE8%;9fQPS7+sE3KFAz=dO-5Tx|^Ejfb7L*9=+s1?idLF)60K^>=~~9C*}Ns z^2CzVqT+(0#7bkNLj_1+;y8bRP}q;kkDik;ng&MGz-Ss6O#`E8U^ESkrh(BkFq#HN z)4*sN7)=ACX<#%BjHZFnG%%V5M$^D(8W>FjqiJ9?4UDFN(KIlc21e7sXc`zz1EXnR zG!2ZVfzdQDng&MGz-Ss6O#`E8VC1F&(0zZPd;dmi8enB*{SOL*k?MbJ{tgTb#JD3K zBqk#xL-PGQ*vud7Vxas0N)w><%`mru{Ep0ri4RvAGh6)>7*@i9#^1Gm*AWr`~I5_+VrF~p}Mh{P1>V}FqC_F%E z0-t+9@*s5}HR$095*w@-6qg{k<8vP@;j*fL>C`yd{9~dg#pN2pmGuv1|T*JgW?fa z9RTt>a#+Ij3>F&X7m%N*=^jv6fWicqnV|Fn3r}2X=p_zHpP(=R*#~kLF>!@W9Vnhb zW`WEBnE_G*V#6@Vzo2vk;)CQtf4Vkegs`0kJ`1Fg}b1iGk!`d=QOI z3?vT2Ape5G0LBN=pzuOB2P95Q46+jz--PS|`3V+hgw!I-gUU2e*@i4OSlA%HQzO5F z!VwmCptKGOE0`Zae#e*pVd@404RR;Qy@cF?t`6jPn4dv3sLUf|1}=G!evo;%Kw&_vUQoIQ=|NWy3JZ`Nx;Q>Qx*kxtgT(OZp_d#e zor2Ohx_zLq!sbtq7`AW$sYezEl{29717t2PJs`P(#^`yKT=#(T7AOp0`ax+N6b3N< zC{6iX0(s{T7?E)30Fu(t^9RT(XGhaH<>`Dh3`fIoG!2ZVfzdQDng&MGz-Ss6O#`E8 zU^ESkrh(BkFq#HN)4*sN7)=ACX<#%BjHZFnG%%V5M$^D(8W>FjqiJ9?4UDFN(KIlc z21e7sXc`zz1EXnRG!2ZVfzdQDng&MGz-Ss6O#{O(4Ty+{{5Lf<9dT=PLH=fEXaCR1 z$%%LW{cuh5AivXl?+m@d610axQ&SV3rg?aH{v-40<&J@}hlPa&<;)$B-&0dl;o*Vo zE+r);a2`MwgRw#70w|xr_`{I~g$pPQKz;+21)#8kT=GLr9KEbVcYjk;6W01&PfzbZ zNDf^+j1Ni!pu7(XUr?BVXqenkqCsUCD2&kE3=#u{0lGMh4@yIzd<0Sl$`c?OCO2GY zke@*19X9`f!UkXZz@}#)#XxBk6i@iv0P+vWUobVGGz|&^7#}1LigOSjSDOJ8UdVbt zY6k-bm1&?bfVl;f?m_;6@j*1m@A%vf@;fNcgVf@}ApM|l!zDjh#6j)^g#ogAK<);K zBa0DYgVdv&Lr4$3;51BOV43Xs4YJ%0daUzpzUK5F0a8$Tb7)6qB`O#`E8U^ESkrh(Bk zFq#HN)4*sN7)=ACX<#%BjHZFnG%%V5M$^D(8W>FjqiJ9?4UDFN(KIlc21e7sXc`zz z1EXnRG!2ZVfzdQDng&MGz-Ss6O#`E8U^ESkrh(BkFq#HN)4*sN7)=ACX<#^|0d8*Y z{|X8UBX%9Wii*nrwj!%8MLp;+S+^M6A7ia`6S#Kgo<{O{=K_#a&i zTi6YDF_7P}`Gc32_doL8w&?x<=>h45iG$dnu!Ql4A`L40K=~i!c935{VE|$yi-Y*s zFet2G>aocW1u>A{!^6W-(mBZ8xWqwnFgzUMALbWgX;8Qf)w)7MLxb#k4%9vX@;kA90_g+kA4>imif+X?e?WkN;s1YT0Y(M}h8QTDgMop8Nsxh|xF9F7I3vEa zz=(u12auIQ&mT}=P+(ACWMNnF%pP1cC|;252kAlPgY<#I3lx4J zIS`*14AKK~1G-v}zZx1E;AsKf|LE#LW`M#B-JR&_@bN)rfb0Re2^78{^FUz>>WhQo z0_0zi|B%f9=>ds@_rlBvrA3e$ zkT?v3>y8$}=D_kQ_)2h)pjHO6TZlj$U?;+C$&A?r3XS**!~aXIi*HMM*kZd8>t@FAb$x72~pi#V&fKMH^^_qs;5*P$Sz{qRiOC; zP`}K{$%(3A1Bydp(h^!3AN4GJe% zT!Cni*`Tlm=>g?EkXn#`k<%Ko9*{f?BkKq0fr*3M4l@fThK~mM4P+lkA4nZYFNhCf zgD}W!8yg#NoPf$FkXu1+2Du&N29O$RV33_4x8O4i)PnSY^pX<~Abp^_nm}g3%mnd4W`gG2SXo*B zgVZ2nkQhh~qz0rGT`x#2F>V3r1Zq!*&uO{zd?Oa(A*z343Yz>0jUM) z0qF&q0Wu3@CP*BY8$jwoZU(6Z`2(a5q#q;(G8cs9<>mh)%Y)RwFh~zb4x|R87KA~1 zL1uu|z~n(k$;)C>o z{DW*BOdezgGKPu6XpkO|UXU3evtZ&N8pH?L31WjVC_F%FL1`1j28n?%$nBuC1HvHp zg7kpQ1<7Haj`*e2B`(P6%>{r8pH>g1@Z^TT#y=M3=#v$fz*K1g7hHk1*yelA3kxA zK9HH9c!Ze=3SVp(W*lPk5FZ(X^nmn&+65qUh%FyLYC-xy`e7Jk zF31iT28n~zfG~&+!yt7q3=;#g35pj`ID*uJ)PXRF4H5&%fz*K1g7hHk1*wDC2V%o8$UW%xg7m@s017XV z*&s7P^No3VdH-u_YOrFE97qjFEl3YYFUUP0vp{Bo#L?XVQUh`WNIl5SAhjTWfZPX? z1Brpm1=$HQ8^i~h2{H#6gTz2`AT=PhAUz;H$PAEKATvQ`gTz2~f%JmJL2M8PiG$n+ z5(CMB)Pu|bVUQe13?vU?gZu)bK^Vq{(I7P-wIDf=UJwSE1rh_94Uz}Rf&2{OgJ_Vu zK;j@ZFbwhs2!qss#6W65c7SM@pFrX;4DtiW43OC%3=#+F1(^ZDAU+I()Pc+a$%E1k z$Xt+_FnJIS3PX?_hz-Kn)PTg%F-Q+cEleE52bl%qgJ_UlAT=QSL1_=f2GJln7)I9v z@)HPy^upAj)3C4vsY8}SHw$DJ$WD+N7#rPOP`rV{2&4vtLE#J1LkNTHhQ&8XAIJ=l zSs*qHgX{vC1M@#f4Jb}v@*o<-2e})h4wSY*V#pX|1~NvL1F=DRKzc!DfXo7k!7zvq zvJ+%KD1JcU268t@93%!(2cki0KyC+NkXax#Aag--AT`JsBnFZLsR5}4=>cJo86Y{3 zS`ZDg1EdCICn)Se_JhPh;R_N6sfUFhNF2lliG$KG$UI^&NG(VYNH53?kli3Q$ZU`} z$S#mL$Zn7uKp4aaVUYVk?gyy_`3IyHq@NrN(hD*JWERLwkl7$RKp12v$bMutj1Lk6 zsRg+eqz^=c_#m@D{s5VatQN!uiGk!mYCvj1dXV*k%miVOT2R=7Xb>OdFAxn91EnXB zIUu)z#6WT&bs#fAet?+?G8Y#HsR5}4=>h2lnE^5jWF{^?R?J3wkdVlWI+0}==6g@qGH z4CG!A2I&Q<1+hW?Sh;c~IR25@FfouFAhjSpAoU;&G7BUIG8<$E$S#-|NF1aGqz=S} zVURiyhPB^7dO+z8M1#zRiNn-`!W?7{NDatcAaRfykQpEh;=?e=E|6LnAEX|nAI1mK zAbF5`L2M8P`43$UNE{u5`~^}A69<_AG7H8B(I9g`c7oU-401C_ABYXZ=z2hEKzcxW zVQSE6P`rY|2c#BR4&5w}UC3&%nF~rk$aaCu2I&F$4Ic)n0mTo@evla;vtWD>4H5&{ z31WjV$bFzV0kL5iqz0r9WERM7m^h3E$s=Q!IE)7A0qF&q0a6DO2hkwAKz4%IAPfo* zP`H882`IimVxY7G(g#ut!k~N%G7n@fNDib18H2)1u_F94$=ow2jYYDf&2o)AhVG%OdLdm)PnSY^n%O)nFZp5%m zvI``J4TH=C#SKU;$RGIBV$+W-2GR>M17sG+Opw{g^0?T@;RMnLax=(ZAhSUJ0GSK& z8%P}pgV-Q3kQ_)2NG(VYOfM)rKi_H3t^2=w_in5hBnMIhQVY@p(o1eUf%**~GeB)wkhvh-+uQp;GBWZ%s854#E=UX{ z2T}u43&J41ATvN_p{pmv2kDzLXU_j6OO}A`1gQu43uIfQk1>+M-qw4|r354;v0VD?sA5a(|+k0ZfXoHSp_>B|1IdBZfYgHYfH24mkQ};tV)&r)17;>D z4TJI%$Uh(&WHvGeiNWMRYC-M*=>?fVj9sH@=u@AJhVy7RkEVgqGyqEjq>n$4d-&w& z_ya7=hYJnb?>CTs8EE_M?d=D2Jxh6c`GDHLeEISLwg33>Qu;CiC-=g$vl_`iSu ze$@T}ihoeq0}6i-2C)aS9vHQIv^_hT21e7sXc`zz1EXnRXr%#C#vh6jGxJi4G7FN- zOyX0>*?_^yzyP|}6Z!tl|Ns9p3_;8^fNloa&GP^Me-KUYuw(i4>lfGm|Nq$rz<#jZ zTefTw17SMb54XFpuuzGCfx!WULGF0@@+A+A-2#edHjq6?b|)|}Fu*X_Eg(5i+<@E! zVuNUq9tzSa$X-x717Qx3c_2SSG0bjs8srvaF$a)7GTZ>O8J5C-vaVURow!`uVXOJqI)g%cad&mi;v|NqYcvLD6=iGkQq zagZDcgVPB}9ZVdg282QG0ND+qp>Y86AFg-+$${+W0NDe=Q1d|MgT+8*LdDSS1*yTn zAoU=-K^UqJWCu3;LE>P$L3%)TgJ`H8kh$P=0Ob>7H^?lIK5+g4nFrMi5(n9Z?go$; z3rHOZgX{)jXqbY{f$As5ZkT!)jqG-q{UH00-2mdVfx-!dp?1UU0_g#n1>=L**f6p@ zNDqh)!XULU8YB+FAp6kmX93v_N-v-=K=V6D56B#B7-Tjw2B`&MkXn#9hz9YIF<$#Y z?m)61W(LR{bPSS1#vplQdq8}UJhB*wjn#gTzd`;7VGtV@=ScAZj!O_9gh6URGzdf0 zg6spCkBqU2q1z9N3nq|xAiF^r6b2wZ%xn-1!XPnp47D5NPi%H!6T@ad$PM5;0J0y3 zVeUY;6T}C(2ZXWM4>BKwLE(VST_8D-eb_MA4Iq1AZUE6BJ`98GhGCHXAPjN?lt%V1 zh)t^fFgJnS4zdSkFFFm91KA0~AU?<)(6Sb!A7noagZMCv&;Ky{vC$xXAPh1GWG={W zU<`8u)P9g0Obv_;;)5_qKQ^<85d-N3nE}Hnc7xmiwI3#rPJ{G=Ffn#vQv=cq!XR_O zc0=uhu|a+XVURpF4AP6uTvEh9=7Q{i*$JXSd=Li7;lk9g8{|%y`5^m17{*7ZLH2<# zNE}3i^il(Z%mSH-4};Xe>_TS4#HdMw%ty!6G@oW>ku&~4?(hR>|HUvGe}Kg|avG#r zJR$WRK=#7y1m!n^>Xzd1&9U!|v_9B-%AU;SOgh6Vk=?;*Y$QWc7&-d@&1(5Oo z|NlW_0Wdeg)RXEKka`$~nE|6g_JS}6$lmSSx62{pd-v{%fy9yA0dot;JYvEFqz+~m zIt{iLR@Z>+YHDiIK*lh)faH+e0Sgn5*@WDHZYPWnvm0a<2*dr4Y(I<*atFwM7)A;Y zd~Sf*1)@RrfiNiiKo}m^NM`YX{0XuP8H3ya;)CQs_Je2;2C<>-d2C?-au>*65Js^V zWFJ&7$lo9gat{o{-377(WG@JV*dRS13}VB?KxW}^1IS(&2H6k7FguWGkT?t@i-Xv3 zw?ORy^I`Ub+yHV9H2r|=LJkKIABI8ZgUkbAkbhwq#0Oy*A4G%rAblV`APnMz#Nim( zeh?e#Hjr9Sn8VxvG6ROedO>alVUQjW4Z|Ql41>g={sEZ>!eINM;&5|7en(ChP(2{~ zVD^LD0^+lQ^uo*s>4ULh;vg{?2FW4$2V_48gToyv4tEbo56BHjdO-G}+s^@#hp7dr z2eCmIBo4wLHVlK@gk%rMZV(393Bw>h2*b?N{MaJMX0=uwa zU>GD0Vk4zBn43WMBijpOW3?aT4v_sI3~~bq!|VsKVHhR`vKPb#(P;LA!XAWi*$)nX zkUbzfv0;!LE(~)Aj0U*@$$pTX`0NIS0Sm~@AoH==jY|xq9vy?+fMh=?+(Gt(Ft&IH z*$u`Z_k#4|vWr-8kbAJ$kJoN!`<4l$52T-1yRfMP`2iG`(6mR;-%z{3VF0S1L3$~1 z1IT_*Sc1$Va?BlOH!=-w-+;^_#VsJUpfH4%iNu6CvYlAjAisdjgkg}IKp12f2*ct4 z#0H6j)I-Ogu$qS!-lVDlOtWON>?DafZM{LGz0ZF zsGJ4KlVTpFYC&#+VM@)StA29EACk<-yaR&{=T9;+GQ!v7BD1NzE(c^bXkQ~C3{po; zyFuoGFw9=i{ziNlrVgZs8t#CZhtE!QIhYJa4WeQ8!~6>}7luLhz%a;s5E~>1QVYW%K1duS55gcf;7bG8{11u~ka-}p zkTEtfP#OWL2hku5QcsHgAUz;IgZvJ{AU4Q6kU7X0WH&AhG6RM|YC!rxeg|Pf{>K*n z*z5<6g<*$cxUdyz3Q z_Jhm-*$1;5M1#TuBn~nQ=3V&RF2c=2$v`38nAajxJ z1=$TU2P6*iGt3PjKC=BFcYrV`%wZVa@96e}%mSH%jCLH2+! z%wABOg4i%QkUE$ieD-0JgSi1@9>@-mSs-&k7$k;{VRnPmf-p!O$gLpzL3**-K}Zbb z22h-V!UAR=$ZT{BlY`M9d61nTyFnPlCuA2cd5{}m_JjNcG8Y+x#6TEi2h4tuJk0MP z8l)eW`NW9B!T@9k$SxQL*#pAJe2{%0Kf^Fc9^?mN?7^o7<_1tagD}XiF!zA$K(`lU zKS-QXzazT|Xzfb0R;55pin2!qss^dOr-4K~P35Jq+fC_F&!0b!86 zAUTj4YS@R%43POCd$3`U94>v-76;jd4O815T;@^3ZgR#S$Q^zFt$zS*+d$rbfy-}% z#IgAuS&Wc=Qst4&W@cvoj}3#;KMaH9L3&8F2VFhL43L?id9EL&e0J#&S9yu(~!vthM$XsIF05S(;KDxcg ze2_d0gWLeJ9~1^4dtn$94zMsF)_#y1L1rP_h0ccA3!*`GgD@xzK=y;&4zeFc!|Vsq zAhU__Kgc|EJ7Ii~oiGei2f`q`L2d?Nn7uF>WG@U8vL7T5@&m|xn0?4JNF0PgcET{o zZV(394bls<7sf`81CToi`5$B!vYp6mWV=D?U>IgM$WBn$gD}V)Fg~&yKz_vL29P)i zgUm#>6UK(w4U$L3AiH50**=*4AR6Qzm>WQL;Ibd224ptOPGlO{ewckQ8l(q=L41&X zAag)=g4iGoVuQp$;Sb6OAdKz~kQhiUC|;56g|R{QgX{odkUhv4BnHADF_0M`yFhlL zV~{w=4Im7%4>=q_Vj%t0upeeG$St5a0htS;L424SK=vbt0Z1HVH^`qbKO@s1agaRB zpCI#)G0YBRyFp?g^)P)P^&okWT_F2m{s*}OBo0ysvIp5-5E~>0!XSB&nIH@@3xq*_ zf?<#upl}4yAUT-bAblV;<_2#0JTM>;Tz^jzQue3^E61H^@$qJunP1 z6NW)zAoDD2-FuP%9q0=CFkUEf^AblVV zV#DkQ*$u+T_Jia=>OgjZ>_o;OF_0Y043PQgc7pgIe}gc{ERb3dhUo*j2c!>VFDUL| zc7tdT2FbzHgY3g*KgbM_*&sVW_Q5cS57H0AAT=A@A#0O!JJjg5<2Ju1WBV&+TKx#oW2!qss%m!svFx_#iou8$o7+%me8~ z#~`yn@-Q042B`;OboC%HkXmBw2e||029SD?J`e_(0mC3ZG6vZXk^|Wb@-s39iGk!m zYC!%1nNKVRsRy|MhCyb4FvxC@nIH^egD}V*SUkaKkUL;%KxTpbMXcQ*bs+U1eIWZm z_JT0TE|8hX7-SE~PFNZRu|Z-WH-XfH%mA4MGLuvc(g)HHa|6gc5Jq+j$Ud0eFndAn z1E~S&1(`vry~yf8`au}v24oB}ALJI0oiGft7bFIf1L;9Fhf+4kJP-!?3FHot9U!|v zc7ia74H5&%fz(lIA2$6Ub72_S9UwbF7$gRh$EKf}VlaEqX;SRK7=LhJVEF%^Spc+8 zA_mIlU|?Wi5@cY2od1wtK<4TP`280S3=Rwmj4Y5{^f=DX0)->!zFH7X$Um~Ovj0J6 z0fJ~kYAKNixd)WaK{O@i5Th3~o(C#FKr|usAUEmi>i!4Ogw#?Z4?4>nWCw_*#2jMu zg8WUa|3T_NaSx)2F`Ha9AoJ;A56FEW|AWFBM1%PFFepA?7@s;y+$!6Xb7@ zeIOdd$A>{`KzR~G<5Nee94PLMjg9|6QLLw!#-|RK9LV4F@;}J$pnL|Rak+<7age_$ z@joc-Pn$OFKZqvPZc^2Q;vM9F5KXFHTZ{s;A?LH-BP zL2(bFv8f079h9~}G&Xrs z#AxAvP~3y+QxHvxTVQHIVF!wP5DgPUrYS97kj(=5AC&$>M_aR;KwF$WZG z_`;8x{s-kVkpDq6Ic~+K2jp*%`$04|c|u~Kd;`)8qOqw3`5zQUAR3!IDPkahgW?`U zlcEowT9Dg7{s+;Z{W%m1Kq2IPMbjm<7{#3=PYAv;0-2jvYAO-L;vc~Cro;vPg3QVWtNrp^JS zeUNz|8l;EX7?d_Z@eiUYH5(LHptuLogvLJ$up!$^BZAxS}gW>?> ze-MocMmB$-{0}N8 zLH-BP$nHUB)7t-_v=54Z5RGm(E%~6d4+?t_jn5vC|3Ud5MB`IOt{lkkpt=P_lWPV! z`ap3H3Ud&RPd}~mKgbOr|AT0J?x40DDDFXF4x;gy4e~!I?m;v@b>zx{+yJUuK{UB$ zkfRUeZ%~+nXnguXZUgxpMB`IOt{f=tL1u$!a?Kz|A1K{}{12k>=?A$B#C>{-i(KIkJ(}0eS&i_$78U`aZ z4S@R2qu3aHrp0JDjJ$BLv9b9-ibum>G!2Z*G~nju_J0(QhQVkW7@27xBqZejC>{-i z(KIkJ(*W{4AERu{yT(StWHd~Ma~eobPyauPN5f#Grh)wY{QskPGz>;+8Yn9(`#*|D z!(gPQfyTzh|D$*`3`S}i=;`VCKZ-}gU^ESkrh$=|24>Ej`F|9ThQVkW7@28c-MV%E zNAYMFjHZE+nFdCBxj$0-y)b`|)Oin>zeoDIuaUa%0p{5g^%JRgL)FN^YA7Ef$U}azcnXAB{z@Wg$!oVQFzz`G~#1Iu4 z1)>FmLW2aOLZiTRP-u`+RA`hEhz4OO+c+pR$T%uA3QQAVqw58mi)22`Ua;Lr;vhF5 z>qWQAB`7q=B`P!uOk-iAs|T9}bw7$5K>kLv7bK6Y9`09|eNZ|yC^RTEDl`gABeCJ` zf~te*M>Z4WKaf3e408k2jj(VB`vYnY$ggnwp!TK(g$AWXg+{?>47WhT24)V3hPeZ7 zH&UEK!QD7R04RQxa4M;7_4Inc>W`WEE z`3+_d*nW`zq2(#eZjc&~T2Q!w{0{O5D4s#)g3QNeFW4PW_kh%Z)Pmdq3NujHVRHk> z&tMF*A6BkF(=W(wkY11(AhST`!tI9Ig={Y}8>AMN9zbapY(KP&0QnE(SCId~_Cw*xtf$|#ET%6$!^*7jEAUA`` zg{S~{nN$uf&p>v7+yJ&8n)X0$fQLV{90$1Ln_ za9IuV6Uc8M`$2Yt>;u^gO9LQ2NDib1q!twZpm+nh0hE?O=7Hk?Y6nI*Ah`pi4x|TW zKgcg||3kwXW-p8exd{|5pg00ykRDLF1=$ZW2V@?|TyXfJy8+p5Xm~-=9>nh;v*7k4 zw+le!4>S#e><3|(8$jt5qz~o>ka>5b0w85=NN~vir~s7m59Brw289#Itspmp!mlGL z0Nh3Z`3Yn;D2ze&fc%YI{(|@*agZ92S`bEe1IWxXQ32pO2V@T@oM{FUXxB zb+CK~@&nlKA;BSFyOF{lTnEGIKWMlhy8)ICKxV+g0c1AF4Iq2K7+N=g*s$^jZa2*D zNbL!b`5?c7(jJQa(7XVW2e}g#4ybN`mAl|J z8Ki9iG9PR=)Zd_X7}j_I>w&Z{L2iJ>1uS2H;&vu9Z^76gJ3$zv7NiFhhoJlf@(;*f zP#y!>4e~$C?_m2u=@9CFkQ_`cNFOX5KxVHYkXV?go&0nA<^a0GkPoPh`8&(8^YjI+#8X4KfFWVdjI}fNU=^8>R-N9_D6{ z`$6WwXps3}3~e(YvtepLG)zB;2H6QS8*U${4+e?@5QfGBayWqWfb@dhf|NhuZb249 zVuRH~(-X)HxV>ohfcl=GekjOJWH*8MFg-B+U^Ag*C72C0AB7DQgXS%mUJwm36K)68 z9bo%GeQ+oSs{zTuFvuJb2IUVljAAdE9LPQxhMS2LzHoan#vdGz#vh<;*!V+Iequ>^ zVs@$#nZpmD@duDr41+o`}>FsBbeIR?0G006Iw}CLstuPwo zevm&v8005p4DvfN2FanT1Mxv-z{~^L0kRK-VQv7qiJll{FNg-Y1B5~Dg1HgoUKj?s zABI8xL-sF-?d9c#^XwIndJqQb2bl*lA7l^6J`e`k55x5GKg>Q54RQkrgWLqdAoqdX z3Bw?FgD}VsFbwiD41@e05fSkpM5AMn8kioC86XTY7i13zgX{%ikUKz_9{vZ}gN#A; zgD}WFAdKvPkb6NG<_C~}U>N3Ckl#Uba3C6nVRA6FApJ0NK>i2W0kR8ZFS1)eY+8o_ z$PRQ2atFvgAa{W<$c-TPf-th%k^KYmBh0@b|HCjy45SVu52BGV$UG1RnGdoLWG@VZ z{0(y#h)pXDvjaxM><76A5Zh*N5MuXf+3k)+K zMuY4IxdG%BkefglCP9mp(@nIN-4_JHgJ z*$u)Vcfi~PqG{oGkeM*EK{Uut5C*ve<768gh62d^E=G{Aon9ez35>83I}Ajg4opbKgfKT-$8bR+yHV9 z$nPK;{Z`2E{!LQN6+;s!Y!VEzZul!O7u{~+^0_JA;hqs-$67e96;`Z z`5)v~P#A#R4#F^hfM`-MNG}M3%mSGWvIpdEkpDq!kli3RfY=}!$4^s;g1F=E=2VsyL2!q5y7^W9QgX{*m0pvE2J3;;jVNkq*Fv!m!e}HIG zFh~zbKgj=}@P*j{qCs|o>_*2RH-a$8jUY83Hpm|!8YBk7AT|ht*f9MdHb@MFL2QsX zh=%C}iGwgm3`T?4Fm)g{$X%eg0fhm`y|6F<`2*x{7!6|M!yt7aKZEqcXpngzvq2c- zFOYp8yFnP84RbR{4&+u?{DIWLFi0GPL26(a#3zJ7`jOSZ_#hhOf0#Q#X#wO`ko!UY z0r?k3gV^{mNDs^$5Dmg0vq5HpXqf*&c7ogjqG5gju|fU^(I5w17U1zkQhimhz7X}N)ZNDU0b_#k!2VlZ)# z`#|vy!XUSU@-GOJ5(Xf(pfCVoP#D0>goOdfUXXntH^49`>_HeLj?e8dIgsB$7-Sa6 z3>Xb#gUkWRgD^}EM1#aZ7{rEQ5Fds?Vj%Z|+zs+S$PeWBAEXDQ7i12|-=OdWVUXD% zJ3#IP`4@&k;vhE29UwP@XplN&4AKMQgXCZs#0QCkFh~qUgD{8ZJg8UD{Aisd(6qddT(IB-Tzk~F`{0|BP5Qg~^WCzHdAp1Zxh!4UbJ`97z zK^Vpd(I7SGVlY0kUqSwXVURu$2Ju1Z2SkJT$QYy-WDdxDkQ+h%2Vsz#LFG8ePlWsp zk_Uwq%A7l>5%^<&nFev^&{s;LBvlGOJsR7vw!XR}Z8YBmzL3|hn@j>Fq800P(A7&OzJu(ea z1LA|s0ND?skugXfgh6UR7-SX*gUp9vPuU9AEqA02hku5vKJ(ejzQ{?F-R>)4M+?`gZLl}VuLV99E3q)ATvQ}8W#W9{15Uw z%-PLgh70eI5Gx_A!Cq#K>i2$ z4}@W1fb4&e7$|*!5WBo4wLJ3%x^El3`OL3|JfiGeV(8kjgp9SDQuK{N=1Kp4b^VVM6x{zdjb$loBfAPmwEvKKkMgTz5%APh1K#0O!JS{NUh28o00 z0*RqxkUkIwnFXR@7^V)!htVK$ka-{)gh6Z=2Jt}{q!wl#G9MIYAUPNY$$|We?tf5v z0O<$$8Kf4Z55xx1AU;SAhCyb)#6UDQJ~sD(#E{j3%me8MVGtW62T}v#gD{8>!XPma z2C-q{AU+7g_#pitw}NPp7zl&h55gcZkX{(Z=6_Ilz~T#}7o--X4`e3D3=j?C!{k6T zNDM@S)PiVqc@Q5Y2g4xqL2d@|L3%;*APi!I>;Rbok^^BF8%Bflf!H8*AoU-``_67)p#6e;pvq3aS z4G4qSAPi!|Fi0&3!^B~15FdnL>Okg$jzQuu43mTLK{Uv25C+M?%mTR+86%6s+y>GM(gVUEc@PG%K^W#XP(A?pALe%u zAEY0IVd_EVz%WP*WG~1J5F3U;d=Q55LF!>{2B`;$gXBRN7aODpgh6VMG0d$nF_3-` z2AKD^%!P@AXb>MH4>AKJ4-x~>ATf|07zWA1Fh~x@2GJlrAag+aKzxuqNFR(1;)CQs zG)xRcgWL*I2NDNi5F3PHYC$we9Y_qM7Q_Z&ke@;69^`*eIRN5=(g8>f$Q+P+V0uA( z5F12;Fo+E@1H=ZY1GyW-M#dmDAPiCmvKxd!Y>*ld4da7on4KW?AoI}Gz{EjvAT~%p z41@H8Fo+M*2V#TFgJBpS?YPd>96agVcifps)ea zAU+6#`~y=DQU}5yF&GA^1!0glh(;F&nE{dmVGtYU7i6^{IgmIAgVeyxfQf^|Ky7Y2f`pZ5C*YfYC$vzgTz4?7r;5(kNa zXb^_6L2@8AG6u=R#6k9ed{Eqg#Bli^qy~gRYC!1% zlvY6cVPYUQNDhQSY>=HG8pH>&L3%(mh!4UbeIN{CgD^-f2!q%l3{nf?!_mmELGcCSgXCZsq!;9F z5REPdGY@12NDjmYsRLmU8-zi6VPOEGLGcWe2k8UZ3DOHkU9{Cu|YIQKS(`@hN(rSLE<0`69eginE?_5=>_pY{s75=#6apn z7{mw3fiQ>-QVXI%{)Ux-AblXUAUTkmK^SBP41?5zq7`p#q zc7fzU7^DW-9FYAWH6U>i4N?QbAT~%08H2<@7$gV6AT|ht*dTQv8X1GcVDcbwWH!iN z5C(~XFv$NPK1dx1gY<&@4ZeUvYLVj~BnL7Jgh6a%b3km686XUj z2Vsy~Kzxu|7zXh{7$gV7$Zmy+f%Jp)fM}3<5Dmg0HVA{%foPBzhz-IZJ_v)@AdDOi zAhSRiq#lGpY!C*qL25x5#0H6hFvve3|AR0n3_yICI7l7H9uN%@gV8WHh!2v7VUS*s zIUqjBO&}Vi24p9Q56ep+aZp%*Fi0Mx4}?MHf-r~;!XP=2T96os4Zl2C+eEK^UYSqz;5(d=L%7ATbyQsRQW) zVPrXwI0(bkf$RohkQfYu)PwYa_%Jz;7>p0{1G+d&4&)D*97qg=L3%;{0MQ^m$ZQZF zhGF6$8YBn9FnMBWkbV$`=?Bpuy&y4=y&w!@gZvM2C&;}Z43h)NgD^-hNDQP7L?iP- zVjv7shmJvNLFR%mNDibAhCyx_C=7E0j0U+AhCzM-u|aa^?t}ROBo1ODV~~0f2FbxN zNFHP!2!q(@@-TJ8(irCtFt8xbKZLSj=MR97J}gR2%SlZx$;{6q{TKpP(CGsVphFND z!RH?`uz&>q6YJLDppKK1^FJ3C*MArdibqjV(f<+>5@4*NqViusLE*oKhQ@!;8J*hN z+W$>VO#T}h8iHwSYwQ0Y?Ck9P-@(D*zqhwH9EXL4f$wsTj*k8h!)a-0|3Nq_E9-x5 zZtnl`^78*BB_;o>s;a=asj2Bd41?}}m^5k9e|&hsf(8GP@y3lC|AX-1!-xNa@R1`& z{$IOx4UV5aefl4SfB*jdAB6uC03Sbo{Ev(;U%vbw8H4Nq;T0=Z{0CuVw}9AFr%wHk zjM4ql($exDhCyKf3lA6#3nx&RrKF_%kBp4`54!&|G&J--410KZ{0CuJ9Dw4%*47pr zFQ%rZ|BZ}{{_E-K{RhPzC>~{HW&bNFDS_h`7SEt~2Vrh*?*AMd9RES|a7f>z_yLp@ zNKro=)FS77P#FNq`yebWE&U&aL3tlk27oZO{0~ZJAPh=-Anfn&56}NWK|%k+!^7cu zACzuEX&982Vd)x_&Ovv*fzmz*gZPB+9?PBjdKVHk9%VmiIw4%>SVB zu(Y)FKL~@u34}pmmXeYJ&ikM;0E9vL9~9mo49owZIDqAUP`rThKBx=;<$X{Y0LuTM zybr>lybmf1Kr{%C=KudAs_qAsU!XjXTn2#B04ez&RQH20EZu?Xe^7b^(dhL*a{dR^ z{h)LWO6#CJ4@&zW3}S=iKr|?SgYpY928n_40StrG*45ShhvDAd-v7uLR35n_y50t{~nA%b-?G(pZ|mKojZ5Hd4A8HJ^w*?(V|8FK^Qcj2g9H? zN?~E)e;6((DEJQ=Cxp=> zghBZp6po<04`PF8P~HcHIVcWb7!)TU49feUG60nKL1h3a|AX>8EdPV@JqUy19fVgVG)--@`Dr z`X7{rL3tOHra@^Nl+I!GKZp&=_aJc?2Hk^PU0wYjhKq}f|AX|yFi0IN?}KRMybof- z@;->hmH$Cy04V>1$^lTm2jS<>pZ_N&|AX=_GKS@QP#FNiuzU}rLGDGy85tS>kuk`B zuy6p;uy6wT7Zi4&ybsIwAR3hKLHQqqLE#UI2N(v$4Jhw}Fev|nFeo08^FN3Uif0f8 z<$qASAC&h&G$?(HBs@~{KWN+rR1Wa+^1{zPgry5m-UpQdAdD;jgVLFci_3pUM@Mkm zKOi6gOoQrwQ2GSrgT%x{aJ?846Z0P#Bd2{38>9}DmSGr_#$#h+|ATN+QWBU3u|YUB zHT6HJd;pahpu7*mpt=>*F92cCz6214wb9qCSp)7Lz-UlA{@}rb|3PH{2;aJO>;HoX z5B^`hdKD~o=FFM@2M!$gziQPgFrGDQ7WiBgQ2h_8vx|y~{)5(MfH25S2?+`CemSUa z29<%Juz_Jv9>s@2`5YO8%tOYoumhP3!XP_fVFfc6l>b3(e^9)D#%e(0ouIe_<$X;} z&HwW9^8Z2c2`b}37?k%xZ9WhN<@?e6&oE;80kFIeigQpI0ICB(=>dj8=>%2=fYK1C z4GBt9$m4&Yd=J8)G!Co#K{Tiw0Ofrc2IXx~`URz17!4a2gwY_ip!5vV3&Nm01;U{G z1;U^_k6eGk@;rzJ<$DkY<$DmGF=NJmSl(a1e*J$C28{`T@V0H+{(~?m|DQN<;y(z( z@;@l=gD|Kp0OfxWhLr)USFiq$Tn5zE*8WE>13>(gBDDVNl)&VNluur6FwjAC%TW zc^^~;fYLZJ2G#$d^bX1cFg9q;6ox@*6@)=@APh>!APh_Mpu7*mp!^TZ`=Gut2!rxH z2!rxH41@AM2!rxJsJ{-Y`(gPXR`-MY^q~9?s{dj6AC~VyG^jiP<$Vwa<^L^Pw)}_X ze^?m+%Ksn?avKc8@;`_M`5Tm9VKgW#K-ksQ^*_kppgao7_nQ~$>FN0&86$@o$UIp72bl|s7f{{@nG1?L5Js>6L2(Rf1Ay{AsQw3~0azIT$`d0A zkHq{Bif72V4FJr8Q9A2Vq#=huz}{O6%wtmiIw4D9wT} zC>?_^D4)PEsJ#Gc^TYBLh{l%xVeNld{s+;p{12=DL1h4_EP&;G5RI+=2jzPh#+CnJ zWdNuT0LuFyjNAqQ<$D;0`2|)6fY#rD@;)p~Kz;{>9|(iW0pz?7Vk75$5F3=gkugX= zItIyu@;-UuwX%19wfcgs{3`)Z=jGS&kc^`y9<7A*Y!}$34 z{~!#?^T=gDPEHPZtR0l+K^Ro`gD_}b0F>`RV}2kEs{270l;@W%TlOD@LHQnpH*DAd zp2OR}e?NGg0I2>4VNjk&#>bBz{}02Uxr6Q7xBp+ecJ2Sgix-3Y0igMOQ2qyD(AXVl zybgqs`|zOl3&>9(4Dw%KVBmic_VxAs4{A4oFsLj9VNjk2AThCyM94a4F9 zq#qfB@;(TI;sB-(cE_reloWW5cQpS)`uig>|AX>8EboJ8P+9<$0ie7OV#D%3DD8mK z5UBnKjVHq}EZrf~pnML(Fg+lBAU>#mgQNq z`UoHl%m1J<0MrKn<^T5f_Wz*#55l1Fe-K8`|FALu)E+=C1AKgZ;AH@)9I&^y{|~~T zJd9ihfY_k6J19(HSXo&aJeCLQ$AiKbhCzBkY!D672g?5-J)n31(b)1oD6Wy~e^5IB zR40xkJQDLiD9^(%sQnMh`ywJD;5GoL41nc-P@55yen4|bAPma?p!y%g2Bj|$4N6-e z4AKLlK^UY5lommGAB17~9z=us3b4EnqG90nqb5tp3NB|3Ud4ghBlUSpEmq{~!#?{~!!213+_`p!^SN1As8B z9028i5C-LaSQ!AKLHQqs(epnn|AO)`48!s~hz7+EDDNX!AD(qCx2igpt`GJs>dU`w< z56Uy3dLO+EfYtw?{Ey!L2jzWG8Gu~>Bj294o^>Twtb%>jTgs0;wnp!|-E_wC#FABI77 zKdAix!poO02lp-J&Yk;z+O%o^`}+F+gW4Y;x75_sfYmbc%~ygjXub}FL1i1r zf1o@MV}tw(Di1&yR2IU*2sBOz8WR9vSU7^l0YLk)VBroc2S9lml>b3_8nhP)hC%TN z!l1a5lamAQ<$&o0sR7A>FeuJP^Z&?O{|n0Vp!^S`LFoY02LR=LP`Uu66;Rqira^2_ z86YMmhLWB@X%Up?K^Rn5fiNiDg7QA7E`z0O(A)qhorCf}D9^*n08pL)~rAC&Jw7*qy;@;#{T2hku5%Ksn?%l|uf?)(qJ zpu7(%1CaCnv17;Jc^^~;fW{1Ac^{PjL3tm9(epp33;@kdg7QBIgYrKJgYrKp@5Ayv z$iE;zg2X^H$nT)A0EG=KtUzPuJ`5F|5 zAT}uOU>KJFLGcK}Aa$U6ACw+IcqG;RAis{p`XAKxhvj_`4a)x@49fc;3`-+08dNTT z(h7(MVOUxMr9V(wM9%x5JP*U5G60sQK{P1ugD@z)gUSF92IU7(9suP9P@Vu`Q2QTS z{ztF>L3KZ<3;^YS5QgP{P~Hb&P~HdO&6_v>2dx7Jl>s0&DDQ(Xdj3Cp^yq)&{12-8 zLHQq)??D(erU=5Iybr^mHUKE^gD|KK0K%aB55l1Q55gcn!SX+d2Kg06gV><(fMHPm z56b^A3<^t7834ktG5{9tAR3haLGc8`pfUiIr(qZrpCC4f2B`tjAPkCckT{42<$F*W z0K%X=F_Q2|%>SS~4=MwY`~RT&ACxXY`5QzdrxlPGdj1EcJy6~U(V#jXhGFRyM1%4? ztPB9wl<4{HB| zFsS|q<$Yuf%JZ=N4=Mv-?Ep~z2bBY$Jdcb)`5uNrYk)y{AA~`9A6EB+@;@l=gD}V) zFbrw~fW`tq`5%Nq`5#oqf&2r?^C16%Xpo;lG|1nuybo#vfY`9S52HbOABI6?04&@= zVGm-1;tGU8c^^cB>U>b%M#rFd1&M+3JctIxDToH;dk|(}VHq)NU}0t4NX-AR@*YNm z@;)dXz-ZXG5Qr8M5(4iJ0M+@Dl9J%IKPcUSFl_t}G~Nfopt%81n-!GjK^T;NLF3z? zwm)cn9|(ik!NI}*K^T-TKo~SO2BJY2w3ZgM{vT8pfG}u15U3mgDM!}33f z4;mwcVbHiAs0{(ap#4OkwMC%v28KcTzq7ORe`{+id_N+{eV{S`G+qfRw~~{SQTFhH z$^eibVKiu+5U3mgjfKK6sO*GcP*}q9KZpjEwd(5X;Qjm{8kFBb7?l4(7?#gL@dt`m zK|#U){QUg?VKhh#6!$P1l=ne=5Dm*0BZVHR`5zYFAR2To3@q=1_@H!wjFIy`hz%M~ zhUIFgYr8vhLr=LG5~}@c^`x`Gc*5#@;?ZJ z@;@l=gYp{)gUSF{{s+;Zybr7UL3tmwkO04)EbmjR&s55lm#52HbO zABI6?01SijJ_v*IKL~^JKPWyxZ7~qWmH%P&K8S|pe^6Y5@;i(Mu|XJ?FGdPI67xSO zZb5O4ocBR|P+9=xdt{6)|AXoqP`-y@Q2qzC{XuyjR{z85eo)>AVOZV=(Xjju%kv-_ zgh6>9BnQg(pfUhd_k%Ec{s-kN^!yLXhp;jLlt+=v08kx{Tn51U0HFL2!k{`IRtA9P z{E_oNNG~YwgD}WkP`@8!7pM#X)%_qgDBpuHXgv~g{SWE`fG{ZkgD@=rgYrBKgZv50 z_pp8dC=6ivA4J3QKWIz^gh6>9hCyWjC_Z5MA5;c_>V6Og#UrQ>0Lu5sZ2(wYgJ>8A zr3FyE07@Gp36IqL&&0&^AG8k`6z8BbKtS;isvAIQ1(a?;7&JZv8cza^F@w?<4D0Lb zgU^)%tp@^OP#*w8&np8FevYX!UmTALHhzh7}UQ4g*ym?;sTWaL34c| z8q`Ju#Tlp{0HQ&02r9!t7*qy;Xizy1%J-mgU=Yp9%K9Ia9!65Whxv6h|FeM4eFT*O zpu7(%13-Bn)aD1J9}ouRe-H+>H9>32KUs6GVc1y~sX%m3(Q04&de>RwP@1YzX-4=V#ea{|a^0BBARSN;c; z0kFIe%Kxx21W?`wVOahL<$D+gwMAeUmj6L{A5;c_FlgQal>b2(l>cFQA4Y@x3d;8| z8kF~87!+=xJdcdg^FJ(3KxF_3gW?Q^LHQrl27u*#5Dm)r$YlU1?}IQbPmCmbH2*{L zKPcaWFsKZG<$qWi0LuHYx*t>qfZCiO49fo?49feUG5~}@^*<=TBjd>)E)rkdl*K}{~$9#7?kfp_B1v&{s&=DIRwi0pfUhN!}30e2JI1s z<$q8g0EA)r9#r>(Fmm1pu|ats6b>L76kedb4{8H|Xb=YFf6%@P5C)aQ$oU^u|0Cyr zP@NCMp!^TQ==mSi1{g{C9_H84{13_hpnQ)n|AWc^P~Hb&o%Ksn?%KIP;%l9A} zl;>d>RQH20a{dR^|DZgNTn2#hJTeC5dl-i0e^9=MVNm`DVNlq^@;`DJ0LuHY_CJUX z%ljZ2l=ndx)b59s0ie7Os{cV4mM=yMJyP>OsNWCj^Mld>hz8a9p!pyW4H`QFwHrZc z2ZTX$OrU%ZYFmP6P~8tocOVSP_n>+RM1wFWU4q&G=;ME|asbvAfbp@>pt1n8CkB=$ zKzSb-!_ET%<$n;asHi|WZyR*3HmEEBVOW0vv~CB6L2G(I7<48W2!rxE41>n|VdDfK zHK2Y!XwMI*9085@fyx)qct5Pn0nwoRj@;)5<#}Wbs`FtOv>phS_d(;Fpt1l|#(~xX z!7#|*ps_#@29=MXIY7|5Ko|y)(D|3MfO2OtcK6Hpw1#sERehz*KU7zX8i z5C+9LEbc*R0W=mgn*SkZ0*r*R4;1I1_y=K7y$?zsu>227H^})Pl%`-9l>b5L4TeE^ z9)w|K04V>1+5_nM8dtstwQJGU!ty_e2IU112IYTHUIEddHUJ32$^lRt8@UWX&i|k? z0JKIBlz(CQAC#X#c^j1XK^T_zL2UvM#+Uy=WdO*0P`(FYP&)*a|6v$8--Fm7w;|_! zQ2qz`15^fp@;?ZJ_#i)m@;``%VNm|}^Ya6r{RPVVpmG2dUbylJ~k@G(&Pk{0X2*b(%SpEm~2SE8BhC%rghGF?1 zG&cw;13(y*|3Mg327oXq|AX>941@ANNE}r6gD^-B2!rxI2!qT9<$Vwa<$q8<$Av-T z6`=eN%J-l$3q-^6KFBYi{12-CLH+|_FgW?=k27vNDs0X2CT4^Z-6i;&w%nj2!rwy2!rxJ2*dI}Xb%nugYrHouY&4-7zVWgKw|-*{13vQ`X83> zK{RN-AGFq=Q2qy*3(ET-49feUK0XYC>U@wG2!qZ8LC^o7JP*Pkzku>SEYHJekUwF0 zAHDtuwF5x;9@O@SVNe`^Ff9Kg*Z-h$0F>`xaSNhB`5zYdAR3hSL1h34kA$`_vL8oc z|DTbO5j^Jyif>Tf2VqdUfYtw?JP%4UAPma$p!y$_=Rp{>*AF(<1R8$=VNl)&r9}`8 zs{cXr{~#KML1h65gUSaG4H5^f6#~&9K8S5+XZIgQBbNmrHmDo`tqlTU(6}Hd&w%nj z2!rkqfRzEDy}=+1>WhQQ0uT)vn+J{AfiSEL0OfxW28{=TFsQx<%?rS2*ghXnSpmYJ zF$5R}o%aFi|AWT;Kp52Shvj<^4ceasqCt5chC%HV(Al6Mcf)8SEPgYrKJ zgZh1-F+kAVFKi4DId6;v_Gtd+2A2h}ybme|K=~hrLGwu<49oi<8kELB7?kcn7?c)4 zc^`y9Y*4;NE(1XM8iYZ49fU#h$QU{AgVcfYJTeB&3Bbw#Q2qzydr)2i(I5=U|DbiY zAPma?pfN#E-Ungi{0}PwKzSLoR}fYPfbu>v2IYSc2IYN_I*@u02AwYqnp*(ndk_ZY zeGmracWn6{mj6L__<%4h?}OY9%ljZ2l3DBF8&`>27vND2!rxIs0;w*eNY)N+WsFI?SEMP4{G~?>V8lf0Ofla z2G#qZ{12i*^*<>8gVGbI?uX@nP&$K^0kC`zqd|Eegh6>6M1%4?41@AFNF0PgY>+sJ zMlJ(D?OSa5A5;#&+5n(@55u6gKd1}<(V#K_RQH20DF1^nXv`jz_hA^6mq8em|3Mhk z1_0%M5Dmhhd=J7PHK6xP{FL3KYU@53;x4FJmjpt=;4_dytz|3P^Yl>cEEG%pXz z`>=8Vl$Svml(%8|ADM>beUKUu2G#kXd=H~x`5$Bt2!rZ=SiT3bLHQq)?_n6^Mi3iB zgD|Yl2jzJX2IYBV49fE$49owZdLM*AWdJB#k@G%?jb8tQ`v0)J528VNo>2Y=l>wvq ze`Mr;Sic{X|3URXh=$euAR3hKL1%t|FlgN$s0;vMP`Uz@1E6{aR2G0}5C-LKP&x$B zAPi%}Xwd#3P#Oi%FbwLi!Z4@~17VO}5C-Lc5QdcjpnL$!`yd)L9tgsqwKOmc%5$K+ z2g0E7HxLGmzkx8QjSd>C2aVZ*FzBpsP`(CX(6~NmT`w{QiNi1~&x7(mDBpuHXlxOL zL1u$6DDT5C=)4k`n?QLTbe18AhGEdy9w^VlFsN(;t%HJL(0C^(@53-?d=xZh3c{eg z4{9fY%2-f1foRY^AW%4i#{OU!6jz{l1H~7J4T?_?294{2Fer{e6_J!^#0r-UrPO!Z0ZBgYrELgYrBKgYrELgZdgE49feUd=F~pgJ_VO zK=~cyE>M04u|YHpBjviY*6_O z!k~BqVNkq*=5j#!9##f`@;)e@L3tjQ|3T>hlrE6-#z@;r17P6{qG91{=sr-;STZOb!7wO|fyw~TnjjDj zD+6FOXe=5TgVca9=xz!S4N?bUgVca%P&oj?pz;DX4hSj(KxrR_L3@Kh7&Jcy%KIP; zT0aBApmQ}q7&Hb5!k{t$)DD2rp!s*$SRiOV0G6*oc^w^t#t1=W1Tu!{1&#BAFvx5W z2CY2+VbB@hFbr}ZsEk6!u)Ge+&oDM9kHg0FK;;_b4a)nVumG*8hG9_o z3MxZE7<3*eC_IrdDDQ*jY(O+99%1u;pm9J@9E0*chz8|*(AY01--Fnr_5aAM|3T>i zgh6QnMuXA~41?MNurdHd!}33j2BkevUPi~DybTfq<#`YrMuXIYFh~xBL3tm9Vfi1_ z27t{2fbswcgYph2|AR0n?}IQX?}PFiDDT5CEdRsuJ}95U+5xb94BDd$Dg&_PeNbB; zIq!q&dyqa*z6W7Yy8>h;2!rxIsP0G3`yltg@;;~?4`PGzJjneZ8iYal9)v;p9Yllj zJTeCP7lc9cprHH@%lDvi02DT$d=D!FVEG?JgYr8JgYrKJgXVET`5sgTfZ`V!gYrHI zkLLf8lkZ_|J6N29%6$+OgG= zm^>)&gW9g3{13vQG!9DZpfUiI4`3LShmi9>tPB9nt-&h834-r zp!@@(L3J#s?+?oVAR5#Tz*h!<@;?ZJ@;-;CQDg!_mlfRHAPnl)gZMBS6c(U-528UB zl>b3_9u%%18WfhG{Em!4WdJDugD|M>2bBS!I0ezLI0nT(DDFWtC_Rkk|B;jbVQ~(k zLE}K6v;j*OAR2V`FD&1K@;;~?07_FJ3@ZmfG${Xr(jN?i(jbV9OoP-RV~`v&2IYO^ zasV_p0K%X=0K%Yh068y#>QdMo8iMep2jyuP29d> zl=ndxmghnF7UX6S2KfP0f5R|H4wl!k(IEdLV_4n?g$Jmg1PUt{28A6c3_%zcH?X`9 zDhEJu2h0DUcm>5V2!rAtR`y0OcVVhUG6%{)b^$8GxMsLG>~y?}IRCOaPSc zVPya)@53;t{)gpzP+K2_LHQq)??LN&K=~b(??G(Pd>$U~gH!SX*SK0p{0mY{e7VOai$ z)%!3S6vv?a4~l0H4a@(d_5aAp|DZe%ihB?ZN(UeslrBIRl~rAC~t)c^;JiLHQm=gYrBw z2IYOw`dyg2KyCzKP~Hb&P~JtxpgfKZgZv1>uzU|Fo0pga!>ClC$7pm+db zP*@`8dl(y*??LejieC^0#rq^;#-O|c zszYHkET4gB(3l%2{~=@0SRAMvfMHNs0LuR$49fqYF#*uJ9pwBD%JVP`s{270q#lGp z`ax!aXpq^kybn5~12i82atAU7weLae7(n?Ll1u|aEg zKr{%0+E6eIQUel$VURk|_$es7VHg(1p!^PsAJASOP+Wm9=*&-0e8S=uM1$fS8N<&1 z0HuqOhCv0uNb@tuzo0x1!k}~j%KIQTES-SzJ%|mWL3tm9L1_s@!_pWizrxs{bO_7) zAR3e&LFpEh7C|&fFD(Cq%mC>H@j)~QgW47#8iYaWL1`R>VPya)-y>tt*cozu1Lb|> zasbp00OeCqo&{l0I{=iAVHlM6k;?#Bz6a4DIZ)n*VUQk>S)jZRGZ&QqLHQnrk=+8S z_d)p|l<#2}l;=TtABI7B8PpyE`4xmgeg%nvFo+Gx`=D|Ggh6}|hRK0wPl;&X=ly8vpKd4>> zl>s0O%6p)-v!F5nhGF>>ly5=#ACz}N7?hVm7}TZ*<#SjWfKG$*K1dx1gY<#S0AY}M zAPmd*p#BGl4RQkrgYrHs&x2@?J3%xo|AS~yen-yxFh0z$AUROJ2bBRJIS>Zrd6+zi zjhy#EY*2WD!XJi_^FDh12gNr!2Ic*cIQIuC>qct+2jz88IsnnI`X5BY@;rhxZ4q}7+31Y)ANDLW+#F5p2 z@;nHG!ViQ&;SLH<7!At*pnMOa`S|$2_qT)M6NEu=j*dt3|Hv8l1C@Co3`zr_ybq&6 zc^`y9X#})J475%JM1wFWzk>2Ej0Ukm=zLDlnY^HJ0T2zspga%4Ahn>eeh>}9Ff&2z4A8wCAR2^0YxzL?ltF8E zKp0e3fyOF9G^h^)8oLA4=O90T@-oOzpf)?GyaUlN|H5cc7=kc}kDSjzVjv9C2MR9` z2B`t%b5OW~Xiyx1;t(B!+IJu^P@IFt`$6#zqCt0of#{Jq_Xo-cqxC<>e^9;$r3(-Z zN++N^55u5z1xiyO49fE`8kF`xd=L#%gG__c8AuG2HbFFUeh2YEXHS7>WN{E1WCkd2 zfG|iehz4O$o&n_@P~Hb&786)R?5F3=HU>KC<&@m|Ag7Pym2BkL;2Blw6-Ui7b(;zl34AKM26Cey? zgZcs>49Yj4d<4o<$YlU1|082i-bXG2KzSIH|6w#J|HCjS|AXp$5C+xxpnMO)p#8cq z8aeNS@;j)%fs8?UAB16fA5`~)*s%N$ax)0S+z-n4Aiu%#K8OtpUyxrxG$@aQ*dQ8& zVQf&igUSF94GMD@4blU{pmG2d?#LLF|3MfO-=KH~#W|?XhtZ(?55l1OaHQdpTK|Lc zKPdiTX#$xB^%+6=6-0ybKPWAM(ibSbfoNFT1Mxv@kXjIiu|eu#7?gHF`5YvVP9wJi zKyok)vIB%cdO;YJ_dytxe?av=dj1FHHBkNs)&C$Gl>b2(RtA99|g$IZR`56>`Fd9?_!16te28A^!{6QELwy=B;(ht&$jFICOR1SdR9X#Kxq+%L1h4ljZA~sFbvAKpfm`gK^PYs31w@0)1eG%| z4C;fx@;rzQTC)SgAa{b+>wz$+3;>k{pt1~+sN2B}5HAT=-yO5dP#3sMiGL1G{bDnmdtDDQ*H08sr2 z%KxDCFQ73s7zWJ+fbu^GgYrKp?}IR?8~~L8pu7(%13(zW2IYC={10j$fbu$w2C+f< zU>KC=VR;{9K4{JXmiIw4DDQ*vJ&cCseGm=G_b?2~{~!#?_n`a_%KIP;3kw(x3M-Jm zK^Vjag$*b_gJ=*2g)2-ghz$xikUAI*QVYxXFd7t}FbvE0FnXls{n7l-`5#vQBaaDz z(hn%Dz-U;SL#9FL4a5i0ptJ}|hcFr>4&uXT5E~hT)FER~KLZ)V@;xa1gD@=rgYrHI zqvwB69Sg#s{13vQybr>l{13vQJPXVFAR5$mhtZ&N0F>WBc^`yfc^*cC>UvoI2blxH zpga%4pmG3&VR;{v-$58O*9W3O`5xph5Dmhhybr4XL3tlk27vND2qT9Dhz;^HC|qF} z6c3=Vgwdd|1gV43Aa$_3528VF2g~;`8YBk8uzU}rNAv&4YWsuAJrD-9`-#c_pmYXG ze=r)9-avd94H8FAk03rs9+?KQabb{J5Qdcjp!^TQpnL$rpt>Ja27oXu|HH}v5Dm&> zFbrx7fbu>v2IW~8hUI(YG60s>VKgYuBV(9eP#XZm2958*Fev|n*7}0-J2D34cVrC8 z`ydSJ`@`}+$dB0aKPX&b7}QP&VNm%DqCw#V3P_%JMu;-f)wAUi>B z0NDY;ATbyQrEd@$M1#hA2qsJF4-eBUOa0aOd(QIsN|6w$U4Z@@O ze`Mx=P&xpmDG&`x2Ot`hPCyuzc0g$fmj7WizPt~T1Epa?G{{~M2H6Qp=P(S?3&WuN z0b+yl2q@o!FsKZG<$Dkf!l1klY6pO55C*lsLHQqsL3tLGcR_g{ghBZq8H4gY41@ac z$ax=B&x7=X%!1Ld{EjW}qvwCnIzHsQ56b@_|9~*ae;^vDp&@o622!rAZnFg^z7^D|DenD&y28lB>GyfmW|06U1!^Qw%X#qxq(h(?)foM?r zf#rK-8dN@j@;EjOO1~g+d^9NSfiNgdgVH*vT>+v&_JQ=lFf5ONXi)wKVNf0d<$F+n z0F<{tdx}B%AA~{qA5`Ch$^Z}s)zhH756Zisybr?2c^||E<#|~C2hpH){Gfafqe1#X zm{7h4<$Vwa<#}Wbs`o({xace-Il+ zgYqwk4@$ov49dT_XpmZvJ+L$lO5-35vJ*su^de(eJ^|6N{13`UpfUi2Vfi0K!}33J z834+=p!^S_K^RmffH0^|2hpJX4-$vbu)GhNy9Z&AI#}KZ(V#XxG6v;&5C-kr0bx)Z z1%^TS9OPDH4Dts^97cou3JNP22896#!@>g=PB0o2Col}k$DnwE(I7oA3=;>1HAo&r zgYtIj@7*pf(4nd;!(zAU4ci&{`f)zaCUZf$}$q z24T<~J!sty2*dmUqCprGJ|G%2_6ee4{s-lMPj$NUk%~w2KO=lR2$WVp=?0X}KzSNQ!^A-}ObjFs!l1GMlvhD)7!69xAU3QF zfaPNt4KfFWLFo~ML3V;@5C)l#jA43U`2>_lK=~d-!^!|qz5`)UJ_MBqpgA^B{zT9J zp!^If2S9lrRIh_Dhz-iqFbpaOKHm z@-v7Hqd{y?ngZojkQgZ6gTz2I2!rH6c^*WA@;!)+iw4Po(j>?%5C)k8J*e&nu|atrRR6maMuXTe3{nRY z2VqdY2Z_VjAURN;1z}K{My5f11rQ%rCV=b$VNiMmVPrN)9dfz{2I$}7la04UER zW8}OG%CqPgSss@EL1Hj7KzSS=2IYBV49fGcybq$0^FF8y0Ofg*n?W=PBl`u!289(U z3_#%rqCsJb?0=9LD6fJrXv_vgW5dX005Th!T9DXC&i63CkLG_!{SV5+p!5gIhaei6 z56X`qJ`988LFo%bgVcl4D@YDTgVZBqP~Hd0gUSmK2Bl|o4AKk2FmaICAT~%0#0KRD zP}u>(pgaS@pgaYtOJR8tl=ndGe^A{F!mu%TP<;<-ufu3iUPs2Td=H~x=D}##SRRZ9 z*#jznKp4aZxdVhjZUNDt@d{8G0Ky;){^FJu=LFopR=0N!v8;zU}L1`D1c0lStX$wY!(k@6H zj0UMg#-Kb8l84cte2t7j`apab4Pt{Z$b1kDavRKEb2( z#0KSk5QgP{P~8s7{~#KKapilESs)C{_aGWHh6l>`APlk-M8n(x%KIQTEZ>8|0F?hh zG$_BrXpo;kY#0sm8;Ayl0Vu4HF?zlSg*_B1kO=!_pdAgD@!VBV&+W5FbRNW0-xQ`~hNv@)8V# z@*Jp*4a%n=49b(BJP*tJAR2^0`5#7u)(wC#DDQ)4kUkIwjn#uND37D_L1Li%55k}} zJjfnU-UqQkc^nzT{0{O5hz+7a7{&&L709n3K8Oa%!TbhdgXEDhD9k|^ly5;a4C5;Y zU~(`TrUyjB;(nAK&HteEfSlGqY)~Ep}ZBj6hCyLP3?9wXq^;#0>HU9DdO=TDpfn1? zpfm_d%b19L2dzIke@*Q0l5-VAU=o<${Qg2K^Vk_VHh8jA3^yI8x0ae$LRSQq!%_$2V$eEN6y7l!!_B-PMuWly#0Sy1FsX4qsvc5KjMo2< za6nF1urvsxLFpQlzCn2xhCzH}aZp+Z$$@B4J_n^;kQxvT5=X`$agco={U8itBV$ln z2iXnF4=^@}4>BKwL1utx5C-Kx5C-K*7!6{B@;HbFVVF3G28konFnN%fAaM{4>a&1o z5QedF(I7n_cYrX+KOh<#hJ^vhKOhY94>lSk2E(ARKn@#p_Gmtaq+fEwVKo0k!VfuJ zfzli(-@?)$EZu@=P=0`=Z4e*EhN%b9FmV_SG6y6MQw!pQ>;u^YqCprN8+;l&x50dUj^FJgUK;UBt5DmlVd{F)ZVNlu!nF+!$Hb@-A2VsyH z$Q~F5iGk!n7$gVcgD}V+AUi-bNG&=I5(8m$adbY&A0V?~G{|g_J{S!W2iXInLE!wAR2_xxkE%ya>!9!$&Ht!j0!m-7vIgtBcene)2#9(|%+&-!ok>*G9 zKO!t(=>n9_@L^C|2BlSW8kB}X7?h?#=@>>Ms{^q?7@zsHl!N(^7BsQ$2KgU3{6OIZ z5`)pW%mI}*AURsNeWaQ>n*T}9|DZGo!pLbDlzu^cQ2s~G`=B%p5{JoyXpmkQ4PsLZ z50*4cihn?118S#$!VMHw#DvRW@#k=G$7ue?%>STx#g>L);-EAN!XP!E^b2BxFpP~Y z|AW#uNDU~hgY>~LNDRg%mj=0OuwrySf!qo5GYG@vKx|mv2hlKb7!9lUL3|J!WX34Q zo^DAEm(l!BS~!BzASk_}(;zVz2Bl%-G5{nF%J(1|gh6^i7?};?6N5>O-@&LJ=1-9S zVHjKZz|?_gP`(GzAPmwo82vRA-87p2Nz4EE;uw@RDaD|)4Kf4S9&|pk+;C!p{E3W* zlivobUqygyje2|e8fDg)?g-$=G=H2+gC|AW#IDD8qUhz-LaJ|R4s_o*8X zu&^Jc@rUbZ{>L9a)J#9HG5};IE;dMR6w@zGMtWF}=Ks;OHq!IJNcR6|{vXZ%BRTz# zbia@0|Iz$E((}Mb_Wx-9AI<+GIsK1xzmMks(fmKs^T0^<|7iXn&Hp1g{f~6NkLLf; z{6Et3z)1H0X#O9~|06m5k95C}=Ks!@)oF^22EUAI<;tN^7*V2bAy8 zF)i&IE@qGB|Iz$E)YCG0zK8LLy1(e*{?YtDn*ZsM&Zun%ERW-&scqkIHE%TkkLLfO zn6^jrJ$2IUX#S^8I1a@;G}PUUs|)~%4|TuM%l)JIe>DHoE3Hx69#B5VhDYyGAwEry z=6~YDirVRS)I7?<9+dY%VF=3aAT|tx#6~f3VLO`ti3_LE^fVey!!Zqv=Ks-jJsi{Z zsGo>S*Q5ELxNsUxPov>99MiyP{vS=(!!cct`iZ!7J(~ZC3#ZZaG#XCBF%697|Iu_k z9MkovpNLD>qxqk>a2icdqv13h)4*u{A5GW8FS*Q5ELxNsUxPov>99MiyP{vS=(!!cct`iZ!7J(~ZC3#ZZa zG#XCBF%697|Iu_k9MkovpNLD>qxqk>a2icdqv13h)4*u{A5GW8FMBIV$H8Msn13==)<-ll~M}*sGSPb2;7|s8XxC7;HWQ;HG z!_GndLRrc2S79kBeT)@AUPNYjTMfjB}iHt4Tqr@4x{;>5#0X= z<#B8nl+TgHL3|hn<$Vx)G~ExqbUzwrkT@I7|B(70l#fCA97dzFk@+As$apj@LDJG_ zI1IgT7|s8X{13|CFbvAuFgA!r=fmoKkT{4vn(l{Qx*v@*NSux4e@OcumX|>^41@AE zj18jE`5-YE9!*b>^fVd{Ln|Cc^FJj2qvvfxd|1AR(WB{pXr=qn_=3dONY4MTyh|vr zqszneA=9Jj36h>h!(nKJ!${8mptOpdhtcynAwIHR5F2FPC>~nrf27CPX#QvX&%(k2 zo&yBsVGu^o_aHH3jIJIgH`3G6sQ-sv**BX1A^m?)9!AgCq?QAt>3`^@|B)VNqxqlo z`X4qY2r4s>@kmcgqy8Uy<=<%jC%^s&<$YM2VKnUzy|h0XXAGe1|9>Qh11JxpV_2R> z7stmR$zeF^_n}+%feL_;9Dn%oG^yplNDfDG{f;gCVP@fzgQ*#%DJy$M^FJfF{ezy? zaP{%g)gkjo(<0%th#X#&ut&q1a99%)Mx*&3d;TXUZ4wjSBUKG7JPFOIz|@gSkJNCb z)Ze4|ACmt;;RvepL73FE2U8C+V-#brmtbK8qhWn35Df}*5N2g%1)s$LQUekP(I5h^6NnuQ`Uq^cW$Cge&V(1uO_#&&tr*5RmA%_o$4a@T|8aZ#n*f2R5ja&wR)POL^ zEJApshB>kR9?k!V{y!|tu%#)O95x!H9#$7&lOM@q$YBFwgYqqMUIwv2n2n7M+z)`! zATeYN69>^SGeG?T5C)kGqCptMM#dvK?1}OFX#R)ff8;cSjZI8gj;cZJOCg6Ntb7EC z!SXdMAA|BVObizdQUk*C4TJ1};n6rjjT7|n7|s8P^}nz*gO5fJzfnH!^aTn> z3h zIW{&Z?}G9)C?6wZY+@jJm^g?AVUQRIgUkWtdvpx56Gl_pf5XYV(fp5k{wFNW;G>6A z{8QU6uy8}plQ1zD4a>X8G>DCyw~^WCa-e(<8W#l7$Yl)7d~}-HejHBbjplzy{g0ep zVQC6RkESQw?ML*y017KuSi;yK8Xbf3Gzf$8FmhhU#RjQ`$$|91Fs?X4w-4Q&qkKr5 zj^=+z{SQhn$mtfu$Aw4J4kYbh3kO)3!NL+m!^B`TNDP#JL3tX6VR;+IN2bx`Kzd23 z|6%rGbMHtO8_D?}mM%c)5rjc}5RF`?AhSX8BR#zk<9|>Xf-oq&U^Ix04I}4s7#lgi zgV-Ppl7rE(ybme|(BlW29mrzDxPK(68O{HY_CInO!N(p+X@e5K;tNBN9D2Tm|I98=8oq7k(K|E^9ViCD#$*VJs>tZMlJ`C*&ubG{0^dF7?k%xVxT+^!pQ9am>P65 zVB*+ln7c+YJ(~YVPW}gt#ewn-EH8la5{OOj^bfL=6b#GLAh&}ss2o71VQi59Kp12; zOb>{S57XQ4gU!Ct{13_hu)KgTt$^ePTl`^j56GP`49W+fJb{d{`4yWvv=oE6j}#h| z&q4kH(I5<~|3T#e2!q5x7}-padSpH}HZA=**v%fz|B(C-N*AC!0;2KZ!5p73ccZ6y zP~L!HQ2v0iVd`P@K=}=3KRz1dZjiq~Gzi1;K8Ov%Fh7CVAag)!K{PRVp!_`;>>bVj zko=FWPQ;dW21A^oy8)X!LHPiLLHPibH$ZF<4ZkR;kpE$9d^A1$IT-C2&Hs@6k1uT?%MHdj1i25E&SCiiln*#KIR1k$sICNI zkQ|6c#vm~e29*OKGe8(*9thLRuOPb!VNlx)5gXHPq&%tQNNYDSsX&saf z2;spNhamUD`~XVtAPmd*Fd98yfcT*N15yj4L2MX?nFpgmcF@w_AoGdAAa^5UklR6g zQ2qzi|1dVlA0RdeBj;AiXdQ%10nJve_^;t^5x&A0G{K6Nm=68RQ=r4RSNcpCB3}4#FUHFdAeo zEU#muLGrZn+h8|$H2-6^|IyP3D1CtNV2ewTn?QLUl~HP{6P!`xfSF#5Dmg0H-c!8+hO7$Hb@+VL2Qt@ zp!^T=3y22s(J?LjG!)Dn&HqC?|AW#rDBZ)-I*bP82^a?Db>w`H%!cKEP(A@+kbW43 znM<#{f^H8oAC_-Hege41eu9! z4=y&$y&yM&+zaA^+zFy#e2^RngZQ944{|ez#$_hR?vaQ`dj1Ec3tVXeoA_W#BQW=Y z(mpJ$gVH+)gYp6hgV><_&dbXSZuf)OLPA3SMMXv7d{7$zgh6Uy7-k-b2AL1Sps)gA zn7yfABJJ#AU@0-kRM=dbedLv8SLhc^!$&W4v66omNs1qATvN1WDX32%tsC{Y;K~K7|d_j zXi%O7xe??p5DjuCh(^XRIgnapGtl`kIcm9ksF^d;^FJ*9$)N{BT!Y*V%9o(@4#J?k z0Ha~~0hae+G$^lw@;|5z0HQ&B7>4D4kY1QMAR1=JV9Nh6H-cyo2KgOCgWL0H(RPxc-AM$P8o*G8;MHz}!G98ssNj z800UIdq6a(Oaie%7{4_zGQA6ofgu$w!Y|FP$PT-{_ z@Gr<7To~jp5C*vigkkDH`5zk_CI+KHeu1$;G(G$?80{Fz`5zP?ptwiJpfnE?LuSLo zkZE*zkeT$v$ZkQ-51>2?$`>FpQ2qyD^fCaH*J1e|R0e?fpfUhC|AWc^m{}n6Kp18x zJ^Tr?3m1(Z{xEgOJ80%^%JG#I^s?(+)m9Ed9Y~bhXrqbC|gxdq8O!gkgCVMuYM# zhz%+iU>H;e@bK{b=jZ1KV^F>aVOUupA|e772dRVU0ht5BAUk08Qp+F2m;KV`NDSHCAU;Svh=yTe+%O!~jOKsp=YLR|0_8~%jSEwnmO$ph>;TcAJPyL3bPmd= zFbom{v0?cbM1$&dQ2qzyZ%`Qk%lEJ{0F?hh7$gT$1JVP+Aag(%WG2Xd5T?{0#Oepx zg$u*-GKdCYbam)r$b1-^Sa%FJb))&8Hu)bt4PxU{l9rIo1La3hz6a4D49lY+8Xbf3 zF)06o@-?Uo0OfI5z6a4D49fc;43Yz3kRA|5HXCL?C4L}9FU&q%G$=!q$Q+Ozpgajm*B}~(k@GxA48{lLVGsu8e-H-c ze-H-cc@T!R13-BnhLQ6>%p8!tAPjN?2$SmvQuTrC!iGWl9)v-1FdD>$VNm$M*dQ9F z21bL#NOj9_Q9qjhQS(12zktTdK=}lOL2OX^fMMh`0}?~d2ZZ=Ay~uh%Y*Nz_NDnAY z!)RE(M5aM*N9Ke41>%FmL4E>ZP#XX=4+xs~mynS755k~)4_Xrh!ytK3SpiZHG6SR^ zM1%Ya!XP)0>V8VpgX|<1gTf4-UP|0GRP~PLe@Om^rD;(524T>7GB6sJW?*Vyd{ElQ z#s-NY%Ypdl7@ItMA0Qf(zCmdnIse1N zL25u4l>R|9HjFL@;)BdY#vn0}UKkBx!!UAsfr+8hAblV=fzmaI2IXgb7+DR-9U%XK zFevYX$^cOQ2Vp|_A7lNi=0P+FgVH?+gT#j*e~=ug3;5T8>>8>0A6xzc zrEL%frE?I5u|eq^hCyPW^bf)yF%S*HpmYV3gRx;WNF6A>foPZ-5F4ZqM1#a&X%Iw% z#6a!?iG$c6bs!AlgJ_T#G6uO7gh6s38X1GcK>9#=97KaKNDQO~RQH3*0FWFg?}IS5 z{14ItQV+r)dqMg^7{mr)kefjml$Yq0u0i$@!XWn`s{#1~6epm#0Lj5PPyeK6TG zn*Uk?}IQb z|AXcOLG?dq9WYEShz6MhvJ+-M$j=})z4H*vK5R57Z-c}^?gFtvZU$kHTR}9){h&Aj z$$@B)S`Z#g`I2648Oiw{ls-W9EGP}Y(gcWxrE^eP0b!6j5JqN$)Pcl6G$>txXplUJ z4WdDOkUWSDqCs|mXb^_U!Pqc0AR2^0;vgD?L2QtEWDF7mVVD@my&w$B`{*<%4};7C z<#P}XQVYT`bs%w2-Ungy{10mXgXBT#K^WOyko_P&C?A0^C=Y=!t;( z3>XH9fz|+l#0JqI400!kM#eBPkbgkpAR43>glX;W!D#+y{%83Q%dapR)b0h90k~*TegLI2 z7!A`05(DW2YYm|3)T_h2-CH2;&H|6zFoM1wFW55X`f&w=*Ug7Q5mUw|+u4HCl0X%nAXP?`nh zR~QDh?Lqkz8H3z_jA3%1{0%AtU~Yx6LFE`o4jsevf&2(k1F{>0(ftH+J4g&vE`jnA zEz>#3d|15?qe1qAFmnEfg&{~B8H2<@e2_n2G{{U4rlq?FtJ$OZpS1iBY7>C+12P8X zHBg=c(I5=U|Hv4W<|xIW^a?WrmiIw4dj6M}m;VpKGBPs%VHlQ=K{P0j!}2}I%^)^N z3>yZi2VrEt!rTGM?;!VpXiz=^VNf0dVOpkjkohnSvKxe9`5xp37#rj^7zT;MFh~sK z2V@L0kCyHptY(ko{12<6LHP)j9zb;*C?9~*J30*#1Lb{C{s!dEC9*FXiypi*$JZ2F({p)iy`wt=^dmOgh6BUAPlk>!5rJ zs`Eh@WIre@Kr}2KL3|J!2u)GgqgTz4T5QIVb7)0a4pmdEa z57Gm|pu7*lAa{W3dQhH+(a2?hqM{;7TL9z+P~HI1APi!|Fvwr9ybq&6?ge4={10;% zC~tw-AWWBKs3mGFbvWIQVXI%W+9gWgE^nl z%59_hACdn-X&;0^X$D4v*dPo_{~!!XUm!V9o`caKF<9OQ(a7pSY>+$*gUSJrIUv2D zGzh|=v3bGGmF9^ffp!^JS7sxLl8WiRr z|G;RF+mJEHERdej{LcW&{{LxJwhU%-L1`6~20&>Dl%GKLKPVr7Feu%?Xb>BOL1`b9 zzCbi6?}PFL2!q(5JOh#g<$VwhQxBp+YCvKjIT!|+0m2|VKr|>VgD@zKf@pjgqz2?( zkY13zAoD>OxeP$A`$6KM`X3pC+yl%1AR1&2G6v;&WDGJNN|H-geCuKW*5N1*%#qCpsx$3YlWCxU2cY3cv?FszQ3m6iPu(gVsrAPnM5NlE<& zl>?ys2BJY2ly*Vo4-A9c21=(e3`+AL3>u#RVUQkBz6F^D%BLW+LHS!nMdd$;2Jt~< z0Ei8uL1h3)9gGICLH+=_0Yrn$0%4eWAbC|)RdC%8VuQ>8VUWEb3~~bqgYpmvgYqKC ztsr+(JDxy!6Evm=!m#jxu|Z=PAPmauAPfpe5C-LckT{42xeJD2`e8I^>=R@@3{%_f zL&3b!{EylGN6!1O{13{--W}1iPOArkT8xTg$`=D|b zhC%rshGBUhM1$H&AosyA$P8o*G8=}e>Gq*w)@c5RKBsL1G{b%BvvtAagQ*Fvwnz8$j*? z<$Vwa)&C$&&HN9_lb|*ahz8|XSiT3bLHQb%?_o43?}O@o5Dju42!qstXi(UJ^ucHl z8-_vV!|+hav($9wX#R)fe^7cr&ikNz55l1Q0m7hs3Zp^!AC%`{G$?O@@-m19VUSu7 zA4Y@Jg7_c|%CjIEgkkcaJcW!wX%-&_sRiXzkXaxM%IBbb55pin2!rw~hz6+zVUXKE zGz=r>f0!7E2IYHDo(It&3}S;Y$h{ylL2g6O|De1NDg&sMHeq=ZWF{zXU>IaCEYBm; zpnMO*Aos!YK8S|pdl(IB_k+q45Dl{jL{rP{L&=;DiKyC!(cWrI${~)ZU zruH9%wY0RrG{|ir49Z`idblxHbT|Db#b!^mcX@+&A#Ko}GjpnQ%@gUSPt7zpFb|DgO1G8cqF zW`Z!xK1$s_6!nkP{11vNP~8to2cWtiRG)z`EdPV@J}9k$@&+jXgYrIzM$Z4Bd+s}T_75y4}?MX!Z65w z5F3=oL2MWXi6LWV8nZhhb3u2VqcK0AxSN9iXrR<#$l{fiQ>- z!k}=a1cUM-$Q+QFpfUwUgYq;eK0q`mEI|1k8H2M{knAsq;AbW9PP##B@1LbiL28n~@L2P6U%Kv6&X8&Orl=ndxJ^#b< z6eyp9!V*MNnh!wv5QLG<2IW^!e1P&bh(^!%Fg`5rgVcfI5r$!AfoM>^2hku5G8e=L z*$2Xux_v0>AL;oYlmE|?FaVRE2+hK&uf17seI2IW&wK8InD90-HtL1yaf>;H#gka-~W_%O_k$TWxz z!bV0$|7~n+{##mF!f8-B0I~;UCa4Sol|P_<2`ImV+5w=v1j_#)Oi3O9`R7?dYL7?cN*F(~hV_%Is8 zM$Y>nJ`97@AY+hupnQjnLG~l70p)#=eh>}9pnQz19-R%62dRhApga%4pga%5urdI| z2jzbd2H6QR3zq*uc^`y9DU>K)1XA5`~)(kv()fG{ZUgJ@7% z0i`1t2IYGg2IW0iz6a4D49cUR{13t)HYjhQV^H1!iGgVJybt1o#PMNJz5}({JSgD^-vGKRSiBnQ$5auY}oD4!!^dwY9u{SPV!K<0ul z$S#ms$oU@>exUpV%KsotZa#qJeOUen9s2>QTK^T-)KzScVgYrHo zt$}Dz{SRt~gYrEHgV?Zq52HbO3LS&;4oD0}gV-Pp;=?dHA101WBjEaps)mCP#ywd zQqwgk?}ISN3=jt8SrCThe^9;$<#P}X!l1YS<$n+cu|fGC6ki~WtqcIgBMgJ;e^CAh zVOSXes{28Fa@12IWssK89gX{)4eWG>8uxR|nO(FbvA8rlzLg@irLS(9rNdNDPEQ z`On0}1S}6@gZLn9Wo7l>+S(c}2a*Sgf$}N{gUkWh3##uy7?j^(;vgDiK1e?ZJ2*J} z_wn)h@8#w7-`Cgozq`9T7~9#|{Ri0v(hn-{KzR`~P68`GL3tLGe_@!EG!DvhAblXS zKp2#VLFU6SsEh@LIcS^`RQ`f!(3&TZ7$`nK7?kHhVxYJJu|Z~l<|tqoBo2yS5Dki7 zP@e^q_hB@M4Zcw=>wMcL1`R>LFopBL1_+zVfi1FHbG?o zDDQ(XDDQ(XD362k9Eb+B?Ljm$AC~_?c@tCyfbuLTAA&G48&ofYFepERFh~xR4?!5j zx3#tX55l0l>*VAFE(bttkU9_snGee2pga$wyWe^C3Kn0x`s_aF=^J794E%C{g43Ug4{g7P*9gYrBm++j2*uY>p?8YB;*LHQnp zLGc93_b?h3w;&o6*Pwh4!k|14qe0>zJ~3_|PHIMC{s-0hurvpvL1_krLFofTgD@x! zf@pLMN_U|A55l0l4@#%Vc^{OoKzR+6=dtB|kT@v+gD@!XgD|Mh2jy1~4a$!o49dHp zybZ#jypD`PV#pYje?b^l27uIp%mZPNIUx6dFf9Lr#6Wg{FsOV1=>_F~P~8v0pt>KF z_dztS{0|B@5C-L6P@V^2P(2R9xbg=`4uoO(9@ORr=?9qu%J(1)s^dX<8iqmP3&S8e zP#FNq?;s3{3y?Yx8-zjmAA~{X!16wbhQ%w42IX^5z6bR&Ks1Oyn*SL_%sBv{^ad&e zK<$4J8=sgkg|4ItHaTP&x$Ve^9yvl>xB)56b%>j4R)R@*uYS56Y*Yybq#5 zc@jBqgV><_48rE-=Kn!7NDLW+@;wZL@-j#b2!qT6VUWGBybof7>;Pd<-Une=-Y2F0 z2Zb95gYqv3gYq>DBj%6xT2slr~^_9+duIY)~Et zv0>>7qz;5(;vgD?LH&LZhLr=Lx*b#wfG~)Sj6wMyBo4x$aspKUgYqac2IXDQI2}r7nFBFw6n7_eB2&1j%Q(E@gIai_JYb75Dl^m)TW1FkX}$-55l3Lq5nfd zLcrL=!{a{)gWLo118D3JK$bG`;}J`yg>p zT7Z=SFt?8sdL-t5P}&2fTTr}%Xiyper58{d1hGNs3>kyQyg*n(L*qZFEC8h;5C*9Q z(a35+{eRFn87RMiFeuM~Xk-k_`yd*W|3UqEP@V)~P@cz?_g!6G|AX=>C{My@7#q}H z2Z@0&NIxj=!!XDVFbpaSK=y$0K1eMHgYrEHgYrHI!}33f2APkZ|3Ud4gh62k!l3Yk zgVclaJqUxs4;1d8d=Dx=K^Tz0p)ub29*OKc^C$%2h9nBFia0Dufy^jh!4thAR2^0`5uHpc^`y9aT~Pi8VNiYp<#|vZ2eCmkD9?lX z;~*M@VR;`U2V#TrEvOv;%m1J}3c|2*z}wp!+%5;@e-Il~_k%D<4XFJOqCpsB4lMtJ z>U|IfxdD{-LGmE%=jZnyROf>*DDQ(Xa{dRo15_S?+WsI6@-Hm!gJ@7Vg7Q14P6y?E z5C-LWkQfMq#9?_Kq#jh(g3N$nP`HBfJTeA_A1L3$XjuLS^sOAPkBpkQm4; z5C-+DkTFOMqy~gR>OgS}!l1kl!mzv#qDM-;hxv0P=6_Ingw_2Z8kF}z7?dVJ7?$2Z zG$>ty(hn#-f-oq5g7PyAgYpDO9Wn-`GY|&le-H+#1?6+l_#Fs?@*pU$gT~%qG>8q# zpCB5P=Rg>i|3Ud2G!7499v}>|2SkIy3>|~?z%VFmkufNKU>Ia3XlxTb|AXW~dO+@nmGhuD2bD)4KY%c( zK7i$mkwTBe{13|WpfUhdAA#Z=mJVR)0+g3QZBkI0f#rWt+Js?H9!18WIu?XM`5dGd zJ?(<>J`98MKS(bq--FURhz-K9ybof7@+b&{>TggP0IH{v^L{`;z<(G9wcSDa92uTK{P0zgYrE(hKYmJz%Zx`0J#yC_d#rs zd7!)x!l1kk%l9xEBnHa+pt1twc6|9Cl+QtJ01yV13m^>2^B@eGzXM_P@B{f58H4PA zVNm{u<#SLtfiP@s1B?dw0UHK|83=>)fXV<+{s&=@{h+)L!l3vC#WM(l;vAI!L1|$$ z|BvkTKcI8~N)w>;0m}QJHUKEyfbu^m4S~`pC|!YQ&>9<1-3-d7APmY!p!@>Le;^EE zgD@z6f@o0vjhx;=`3f0>%tOYoe2a}_=I6l9D~*jfiS2H zfaQHqIWSV{eV9K-V*L+lr-J(bAR2^0=>e1uVEG@EPC$7dl;)B1KPc~mFer_I@;@kl zg7QBoZGzGw2!qDbK=~e&uV5HCZ-UsM_Ig4>!haYB=?9q!!yt1(7&QM5qCt5cM8h!1 z9#FoA;ppgSuvst~l>cEER0e?5f%L#IDBr`%0FWC&c^-yA`atSH{zA_GpnMO)pfUhd zkAm_)hz4O$-iNhqKr|>nB4bbPHkFevYX;tJ$;XK&x0^XEeIpm{UCcl`3mGOP#FNiu>LrV2IYAW2KgU^L2kf@ z3knMUBV&-A$QU^+KT$3lOV4WYHIoq!d+cm zU>c+rWDX32$_HeepP&C986%e+AU3ibhz-hP$QU^+u%$p6R~x$FY5 zL1havuC1;855n#3?O+<@SCF4zxU8%U93C(lWCkezgTf4iL1w}11BET9tO1RwfH0`u z2VqeD2ervS`5zQVptu5+SD^e4T9XZ0mj!CafZ`Vv&mfGR|3T#dtX>=`^hm7#L2J@r zc^^cB(gLUqfTaykI)Pzq`5%0toRSY z8#ZkCkBql%+x8zB@7=riKL}5sJ{{cu0NDw`n>TO%55mZLLF|PK7yd`aFneM2oH=v; z!*EMW3)uZ2Kfv&$Nt6B~W03n>TU)_?2C*wDD!}<4MuYM{2!p~56n41sKd7w&%KIQp zYW@f1eGmr4J1F0SFsM!#Dfu4e&ykq_L3tij2EgY3KxqKf2LP1=pfUhd_k%F5`X7|{ zL3Ir%?}N$!P`(FYP#FNq`(a^W|3Ud4hCyi9R0e?ZKL{h|e^A~BXG|J=EA|6v%E_ix|6{U3%OKYk3(|FALuWG4uN z^8U@6H~*vKlP6F9N5;E%@BWXBw{G1E&;Ouu0OUrHA7B_c?}OMNf5P%Vs4W1(pzr`; zP#FNK`(YSV27vNE2!p~EgpuoiSQ~&){)d(4p!fyFGYEs?9aIK@@;(TU=Kqn={s+Y~ zs0@J7pmYGjpfmx(pu7)DFQ762)P@ALCt(Q2h_WpfUiI zhC%rsl=nep04SY<@;(fM(myCqfXV<+J_O|(P`(G@xpU|K2jwSZ49aiF7?kIcF)ZK1 zXjs07(V+Z~jM4KuIv+XjgV@M3y6*ERuCKH2T;BTVNf3cmiIw4$nT&$4{G~^ zFeq$5bw4QogTf7jk=y>Dutm=Qu(}_V|3PsA!m#!~DBeIA6qlg(KL~^JKPbqVf4Pqxi*<64B{sm!V zwIFpMy!G$jzt@p4h!4Uu|Ni|8k_TasSvF96KxTl<0@;lXgXBPJvB}{R1L*;|7lwcT z{reXggXBSG!!RMUKlPkkXazLAaP_qNDL$gawo_wAUQi|9P>caI!GKO z55gce48!C>c7xb3agaI?2AKAdE{MT|JBs(gU&wNS zL3|KRYYYlQkR6~fh4~BQ4v;@VVGELn`4glDgh65;wIDV4{0Q;`GKQ%I(V(<~EQihp zsR5Y}!XWoxQx6Jn5C*A5*N=@4QU@{*=5`PbiU$yljzM7p!pQQV_=2eeg$alcavMkt z#0H6h>;>5a(hK5)Fi0Mx4kQkuK^UYC8H4->k_TaAHnJL!91MfZKsFa7j;xj%Y>=5C z3~~=BoI!Si%muMQ;vhB%gXBSckT?i~)PXRF4U&VY0r5c?6bCR2;)CRoF~}^Cei#O& zLr}VdVNm)5u|YIQ4uoOmf%JgP1epQD$Yz7sAUA>3f!M@ikXax-p!5K;3&aM6KPa9- zZUNCCF_1bC2C+f%F!Mof0$~^*BnL|~AQ~hN(g!mK#0QCk__V|@J3#J%`3n?2Ah&|V zKw%5AALK_62B`;yI|zf+gVciD3UWILgZvG`ATeYNQVYT$b3y8m<*>0qYCvj1W`Wqa z)ZmiCrXFM#$WI_Y!15)Cj}3$T48t&akUnHKvKcV5k?jSk2VsyKLFoo22jaunFd8HV zG6RG`Y!HUYVWUCnKyCrigw$fwPmCDITv!}{>;bVsVxTw&(I5;m2gCO6-!_@VUT)|xyTsgK9HTD_ywgCkh?(gAhjS2k^^Cwc_2BE9vC0yW{`dm2FZc+fiO%B zNE~D~wK2%8ATvQU$PSR*pzwm(3!*{s0&+7*9;6P2L1`Wq?jZkx)PUR!(hp*TzVjv7k8z35lL25u4rUpcV@-0XVBoD$MyFeIZFGwClgUo5C-`Nqz_~sNDhWUa>(XWlMM<#ki9Uw&}on#KyC#2 z38ogr24Rps5Dmj1bs!AmgJ=*R1euL&A37f-2TB_ty&yI+28ko{ ziD85Efb0digAfL(1LbjCVGWW4#S_RJkXjfQUeN0ka`dX znFTTvgh70eyFqCQrWaWrqy}UbNRFBqUTAR6Rm zkQfYu_#ky48YBjyK^Wvekoh2tEDqv>FtVK>wa9#GutDwznFFFh7-T0XJaJ)AJiyd| z{0a&qY#1a5GXrEU$PQc>rVf`pNE~DaNF9g<@j>YY#s;|uhC$*Wc^C%qLGb}{4+z89 zAR1&I41@H8%mdLN3=#*?ATgL($TUbCgh6ovQU}7I_yma|tAnvY@}RH-r2!BP(g%}+ z(J;LrH6RSq1EWFeL2Qs6F!dlBBo4zM^`LZ%j$!Evq#u^=L2Q_PAajVtAUz-qQx9W< z>;}0F6mB3I#0O!J+dyKV@J3E2AUA=+0VD@=3&<=G2E`eO24RqXka@`ZLE<0`a|6f> zkl7%AW1~T0AdD^#5<|x@H6U>i8{`L=J=kcFJjneZ|A5>Hqd{zB3{nFMdysmNIEWAO z8wi8MKo}$r(hm}YVPrdz{fsP*j}6iT!no9cX$3^XFo+LR1EN9dKyCrK34}rJ24RpG z48!z-)S_dM8ORu9J`9862&NYlpP=|dRtsZ;)PUrWF-Qz152Hcmg4iIlU>Kwa#0IGc zVGtYSMvyuX2Dt%*LE<0`O0&oqfyu+z+J7E}9 zHpApV>OdGI2l6KfgTe?I!^A*w1kwvqi>@DJ2DLEA{U8jo17sI628qG!0jUAWf!qYb zptt~Gkb6NGBnQGEJs=Fzhm8iwgUo|rkUkJbj&p4KkmW&akRFg(=xSkNptu0317T45 z0i_9Kw}Rwh7-SX*gVccX5J){J?ZPlf4&5GHe3-wG-2t)(Sv^P%NFIhk@*p;d2H6F2 z2Z)AYm>kG2APjOJDEvVflvZFE#0SZP^nuKR=>zdWW`g8EG_qSjYCwFDUYLK8X;At^ z=7ZSCVjwm$2Fb%PNF2rn*#W{JcY(xUZUNDtc!6P%`#@qK3}S=CLH-3{kY11($QUFB z!XULE4D%063`B#(Kp3PCM1wF$9271f8YB+FAaM{4!k{<;(J(O(O)LhP3&JonK<0oj zh!64)D84{pgN{LZK=Pot0Od0fAEb{S800sQeIUC*Zh&E!IE;pggJ_U?7!6{BFi0IX z_k!d=VGY6{H6R)k9w2cL2E{iBgW?cfKS(Vo{*f_A9mpJXwJ>pz9Ec6m17d?P$Sx2D z*#)9OW`o!u43dXoP#OhckT|*>xcDGDVR9h9f%qUhKxV?kL1G{_$X*aeW`p=3^&kw= z1H+&&hOt5J0kJ`1AU4c@pm>8}kQhh~**uVXkUo$Y%ncxWKx~j(LE<3)f-opuf@l~< z=i?Fw*$Z+5HuWI8KxHdPFGvjtgWL$hFt@>IkUK%?A4G%XK^Vjag%vsmxf^6QNDT;s z`~$);F_7OtdO%_z3{nrm$ZQZF8H40NdeAXQ9Hb6JqhpXgAUO~Q*$u+T`a$v_|AG9C zE&qeWL3)re$PQ#PC}D%l0=WU786Y(FHpncHpFr{;8e|`c4blfw?&sYP}#$ju-=41>yM7#l=` z)Wa}HKMaGy0~B{4e}UK_43Y!UAaR%;P~3qqD2<@I52OYp4#J=?0;L@o4H5&HgN|Y0 z4zdTPAEXAv2VoE!WFH8_+z%25xf5h2$c^akz{Llt1!0gp$PAF($TY})kUbzZAPiCm z!XR-NAEX}S4v@QG7^DY;LGHnZL2@t*lZWxK(I9g{;xKzbcEj8Xk^^CoIiTExFFI*Vp50Dy= zS`Y@A2f`pWNDhQydSU87=EE>Z3>m}J;-f*~1~L~!gY?480@27~Ab*1LJPd=@m6WG0BlW+q4shGAxc!Vu&p5C*Y9aSp;DHVlKz1Yww7 zWEvJOpm+qa(J?4~Kp2!JK^PQPAPln~M1%M+46+-P#;}DOOby69kRQ?Qg7HD_1F=Es zKz70~NDQVABnPsa7z{EGhCybaV~`xkJWzPS=k^`9s3S*F6xM+}C5C*9MsRz*@wdfcm2eK1{ zL2dwHkUKy$NDgEMhz-J^Gy+lw!yvbS*dQ9&o#<>(7=q#rrWT|ggh6J4>;kbtdO+$x zG)Nst4kV5XgVf=|Aag+W!SsU60@(w?AhSUjqy~gRa-g&d3KtL!!l1B%VGth~gTz5* zgUkS75F2C;F1L^>4l@tkOb{Pr9}L6H1@S?74&*lw2DuN0L41%rDBeK&VR}F`hz}A6 z$-^)xyg=e03}S=CL25wefYJddJ%BJMtUzXfFev;$eB>}fXT#({dSMu52gn|f`5?Ps z7$gS6AbUX=qy{7g(+9E_WIwSOqz{BaX23AW3}i8o{V+M0II{U5Igndm80Hrc4e}!> z+_BLhd2ASF4vYrb53&!I20(0d400F9EKr<->;qvC8zcr|gJ_s}Apaq=L2@9qAPjRS zhz(N%qCt8<{shq=J`9801`@}HL2g2KCx{OchhcPmAaRf#pm+tj3&aQM0b!6@P#A#h z1c`(AAPnMzXpk5PgVcgBIvd0XsROA8nGeID_Ap2cqz8mSY>?gP=@k?vps)nd=yD)F z$b67q5C*vu#D~!!HVlL8AP0l=V>1tA9|(i|1EOJOgZLn~f@lziu|aA;X2CE_J&1;h zfoM=3M^+268^i~RgUo}a0Z>?hFevT7%mA@LVFIH;Y+M-RE>O6F%mBp?$R3c{AU=o& z*#$}yAPlk}M1wF$4yFdA4GC@!XQ4#P7n=q2grROeJ~7? z1GxhogZvKi7l;P=5k$lIAhjSqG6snuW03zq{sFlIW*55MAaRg9$PFO1$oi4lAhSRi zqz2{>7!6VfatFw5$ZU{$m>5VOEF3^=5Qgz#G$>tw+yTQNcY)l8j6v=PiNn-@Xb>M6 zgTz2?1BDsLevn-tF_>Ot^I&#^XmkwI2QnL^ABI8h1j&IgEboKVz{~}SgTeunUP0jk zqCpts77zxx3xq*pFbv{@>_;~b=4O!jAPiCi5`)=AEDh2F(hD*J#0JqY4B~_2Kw$>L zAaRhnATf}8Kw>Zq;)BFNYLGEVJu(K#fy{+r7$2k;6h0sfN*|zbf?-hDz}TR0K@K|* zA0LM4g~bubY*3hj%mbMNk_TasI7kdcgWLe~7l;jtSCD>We;~7AegM%R43h(~K^Vjb z=?Aev7{mwpA0!4cAA~{ng5*FLBoD%%Fafykd=EKw@(;zh<3=;#H4GLe78ORuuAU}dINDLi=+zfIP$ekcJf@pFvvVIU77^DY;L41%I zAPnMz)Plr7Y?ztIVjyvJ3{wvh2eCmIl$Jo@4r0UfgV-Q(5Fg|o5C*vqgkg3e(=c%u zjVuRZgY<*K6of%}3lzp6aaM$Aa{Z=$n79=KzcwJ#D-xIAEX8ugTz1> zqz0rH#0FsyA4G%1Kx|?#Og$*vKo}I(pfmvz2iXZ?gTe}i(Zh=vK1eUfED#2n2THdf zF%SlYHH;4mYY-o#282Q4AU;SR%r78!f!qngAaRg7P~3p{$QUFC;thu{sE;`WDJr6VUQRsjzHl9;)DDO!sz~knFEpo zsRNk}qCtER8-zi85C(~XFo+E@AH)X9gJ=*2v0)e#ULXu}KQ z+1T{M#6apnZUSMLyFhXvzk&P@G84oHsRLn<8$o8m#6fZ(43Yzhf#g7Df$YYXc0uNX z+R`8z8wQz02!rebVM6La?m<=uVxx@q9qy~gR=72CL zjzHod_rm-EVxwbN7=pqC#0FuInILr_F&KvNL1ux(K<=UhgWL+j*vx?050ZoV733!n z2KfnuVetWC!!Sr6$V`|z5Fg}T5C*v!4p5wd;unNL=HSy0k_Y8SkQ-qbq#ncug#pM+5C*A(VUQgl zw}aGS!yxq_H6Z=SX&B@lklR4^!Dtv8BnQGEJFuCFECx~o!pLI8vO(?wVQlI_W`Zy_ zIda5cW`V*AhC$&9@;itIr7su;rBN704l`odptKG06Ua;u28AOi>_KLOXmpHj4@^Bs z9ZU|S7luJ*f-uMqm_I-?$bTRVV}s%y#0O!RS`ZtALE#5d2f`pehz4O~J7IQ%{0w3v z(;zkoBa4Il4bl&CKL~@|2;ze8=mV=2ALxbE7!XUqa!UN`Re0o7@ zKzd*p#0RMdxdG%>klR2Q#0SwJc@PGL3rG)09Hbr{gTz7VKyd_$H;{Wk7?$5bZUkYF z8W0AtLGmCw(9NPIA7mFOE@*8JvYSD6fx;4GHz>?NVFya1Ape5G2bMNLG$^b<7$gs( z31N^rkQp$uLE#9(AUi-9Bo8tdWEY4J!Z1F}Y!DwEBdZ6Q2XZ&a4=@ahUy#3G8023N z8zc@>gG__OVQN6)AoD=x!rTY4ALJf%8YBk7FmVtY8-|$&G7}UQpg0DJfiOrf41@fS zjA80QdO&JHav(O$jUX`)8zu%~gZu%)Ape4B7zXi?F-R>4gY?2MC>&rIqz|MXM5AMn z8gwy2e2`m_?IENdq!y$HghAl|ax1!fK<)sEfiOrOgh62h!l1YVVUQYhyFlt;=7Z7_ z2!r?_wICW~AE+z$o#>Ym37}-paxyTqK56T-Lb3y7rY!C+V(J@FZNDTOpP*iGeUE4nP>hMwbKeK^UYLM1wGh z4N?m-1H{IKLGmDVAPm!oOD(;{LGA%zkQ+ha3d68)1BD$3gVHJ}{E#tvn#RQk&!{pg0G~foPCgkoh3}ATeYNk^^C6F>0_uegI)YX2INt&kZm&Aoqj95*8*P z8We`0Fa=?deIN|-D~JYRkhvflgh6^i7!(i4;vhE64v;z!ALMQr4PxWNATvSz2#{Wo zIr!AlQx4>Qkb6KhDC|HOhR!7#5~5cYBnIMxXk@!U@k_|P=<*=%2FZckgw3xYagaPr4yF%T z3?>H(1CV+chKa-YAQ~nH@;itP3KI|p`4i?xkiS6~qy{7h3R93A$R8jKa|1GsoQ9Fb zLFz#mrXN{uFt9;xgSi9b2avyE7$y$lgWL}ab5K}-Ff3d^VT6o9@d*lNq7l;OhAqa!g87TZfGzf#(pzwpy zpl}7HOAtn8gZSte6wWY=YzBx8!XW)14AKV*Z;)D;Js=u}L2&?b3y6lf5yXeFL2&|N z!!Rk~3i3M)W77wc1KA658_1pL7$gV6AbC*Q0+|5{Q;--owIFrK7^DWq2ZaGBTtH$V z3=#*?pfCc7gD}Y7APh1eBoD&K;-D}9xf$dRVlhZPNFTB4>8%drK9HRtH-a#zegefg zD4ap!ps)a8PH;IgmO~_<`6UaS#m>1DOS~2gC-2IYFtb2%AaM`|$-%_&(I7n_j86^B zUVLhB$$|9ZlA}Z%*<4t90f~Xa0~A&u3<_5m28AOiT+!1eEVd)w~gY?5N$Q)!0 zQVYT`yFg(NvLA#&@ec}L5C*Yf801zE8$`n}zVHC4MaLjBiO~yk8^}#Czk>K64B~^t zLH2^o0kL5;$j>12L2@7rVuSR6Fh~xDL1rL}f!H817zTwChz$!nkR2cl;)B8m6lNeA zgh6(|{0MRf2!re%3>cKgNO1?uA0U5$*dV`v*dPq@Gsv$XF%S*ngD@zpkTEEXK;aBh z1G68b2ZTXlAdK!NP}qVnNDib9T^^keQUg*8$~Q1O(A9wCL1u!~qRR~wAD>%5{s4tJ zC`>^Z6pkPa3tLcn1hGLhD11R_7KA~37zXiSav;4hb71;F>OgE52B`(bEeM0c3x+}N z0J#-}LGD5iFOWF87&bmg9mp&YhM5hbLE_lVMK%KzcOVRMGYEs+3i2O_4WeNf#0TjE znG34#L3%)Z5C*A%=>xH07^DY;L41&U5Dns^V^BDOTrXV&bykHn)FUWr&4DuHU zgX|uP802=4e~|qK;=|Grhz9u+M1wFWe9$o{EJ6A~`atFp8xAnFAR2`6=>w%5P&|Mz zE_*<7ptuLo_{T$>Kz;zZmk+qzgX{yv z69|LC352N`J|Od97@yfNIZ)hz><7gw$PX|#Op)E4da8@APnNeJgJ=*2g##=N!Dvu;f!H7#6n>yEK@I~L8zc@g7sdzKNh}8Gf$0a? z0g5XS2AK!LAU;SAso1$gdzb zqhpX9$p0XDkRF&@U^GY`gwgeZ^nk=cG)xX;28a)$VR9fEghAmC3MUu_)qNlg5{F?> z7=rADVUT-AR2^0Y-CJ|`$$m>3J-kh zL2iJBHOS8}wIDV~9OPCIhJ_`_U&ww%W`p7igh6URaRb62c@PZ}2bl>opBNgX9)>~w z0I`YDM@S9G98ejDtPT_oAb*0~i;h9!APn*sOb;mSg3JM7SlFQJ1E~k;Lly^#fz*RA zNDhQSc7wti6hx>bkX;~g5DiiX!XR-N2Ju01Fbq-)!pLz3VuRum zghBS8V~{uuBkP5Uf%JpKVHjj5%zkRp$mSxmVRnGTK>h@UIZO;jgYpQ>eIOd-W^{id z^Fi?h!XUMvcmw$r#0OyzA7mHIY+`7bdKgWN9$acbW`O(%qCx%zVPv%+_ksM2>>m&t zEOf|K>;qwt8jxC$7`hse7zl&ZA!Cqwm_862B^EI{Uf#6WT& z3=#+NL2@7r(+{FS;e{>+5=X`$Ge8)m7KA}z2(kl&L3%+L#D-yz+d+21!iCr{7)<717d^JgVce-4-`Kj8iYY|APmwE!XWcO;vfuS zgT&A=NIl3b7zU+rkQfMq^nlU~hz-IZw}LQ84RTz8%*Tac=7MOD9*|xT2CK$rWIj3u$svn@_%IBMTM&(m zLFR(gfWiWs8$n_qcfc@+Pb`M%0r>&M2BjNh3=1ES97qi)++kucJ3wI$l7qPuAPXT9}_e?gZHn!XP%tFCaM(8{}?S9s==^%>%g`BnR>% zC~iP%K{QM+NIi%R%da3dNDQPNWDdxEAUi>3fb=1U2|62OHVlL0Kz740NF2llnFBH# zWG0LTu|aVGvKtx0><7t#^n%oZ#0Cong$)dY!VHwoL2iWk3z-JVBV&-CKzwBVAoZYd z17T2jfiOrO*}ot@NE}%V8yjRc$UKl*5C*Y9?gNR#Fh~rBL1G{_wm1gq2e}Cw2FW2~ zn0}C1ATvRH5F48tL1`GI2IMCghN%PbLGqyRgo%M@kQhh~gh6ajdI7OPGzf#t1JNKc zm^?@flzu?&2blrGAaNK5xe+9f4TJmu(htfb=eAh&>Mka@@$OdG2ryvZH2k}8}0`ak7kQ@ku)PXRF4KfpiLFz$fVDkq^9wY~{ z7dOf*3IS>Y!2cnTNNIeLH%mAe!m^(oFKimIJv7 zWIjwBq!(l^hz6+#r9)7@gJBRKq!uIxG8ZHcG6O_|;txcF^n)-+4Tul&Gbmm`VSo*T z^nox;4rU%m4@ewj4oD1yLFR$@ATvSk0AY}SU>GI`qCps>2V@3_2B`(PYoIVFyg+UO z(IB^g!WiUk5E~S(AUTk~Kp5m^5Dmj1HRu?mA0`IU3(^C_Abphh6J##P3=jsn4Mc;) zKp5nH5Fcb0h=$n%a}OxqK;j@ihz-K9xWz?-%mS$c(ICAb3}S=CVCKVUkhvf?fZPHS z1EpV(7>JLILHa@FfWihO4+?8+G)N4FL25v045Sy?ERZ?qe2^F{Y(VZr_8Z7fklR2Q zl>b2(qy}UzNDky?kQ_P&i6h$sVuRcS5=U2yix2V(NDoLa$Sx3ug%?N+gh70e8ju`J zJ%~m&6C?-nBPcvT7?g)VG)x?%AEXCFgY<#qk=29Lg2X`n2KgW4cMwJnCv0qxdXPGp zevla;J3)L94YC77gZu%q8-!tO5Dk(8VURu;2AKyE1GyLER(fNQ-5_^^(hGkIeAa{e@4AKL_Fg3VnkY12JkRA{Q>7m4rFt>rs0hx_%FGw6_A4n~-T_Cj} zK1dwo77)f3&&XmRHMlUyE|7U33^EgjL1Bn22I7P4hGCFCP*{WbFg>8O0>UspG7ZuX z;={rorWd3iWERMMAR2^0Y!C*84TulIF#mwU1>`Om28qMW1koUQkb020APiFnvmYb| zawCX_xdFz9sl`Qu+z-Ma^&kw&=O8mc7-SyEUKkrB4^j&<3q*tXFgAz=@j+@q801$F z2IWDJ86XVO1HvG+APiCu!XPyue}dG2*dPq@GpGyzVNiI0Fo+E@6T}A5AU+6#^ujPm zA4osQ4IuY}`~$)uHVlL8gkg{vNG(i1G7Yi=*}Wh)(*uLt1PTif2DuI0{UCV|2FZgk z$el0@5(l{%L}SCqX$F)QLE#0`3!*`0f#fOiBf6a+w}3FnY>+*mxC7aRYzIglWG_fB zNDW9Hqz;5Zd>97tK^Vq|(IEB67^DVd7RX!}jcy-E4jF^wL2dxiFbt9hr4tYh69cIM ziNP?)d{CMJr9+Syh!4Xc_kr96qhVnH3U`nikT?h4#yEeh>!nLF!=cr3VeN4;F_Y8X1G!4st7u z2FZcgAh&`rh!1lk$Q+RWVHhL_W5eV?Vz@BO98zhJ*&sK<>;i=^2!q5yc7fszghBE! zvq9n@K8OZkP&|VC1HvFSOb*0{VUReC4blU{$YLOUAh*Emfzcqhz%WbQ5CkbBVG2yzch9Ha)O9-RiIJD5K}=7ZQEeJ~6%2ZoW&0kJ{i zAUz-qavunT%m?`gBoAUEW0*Kd4x|=@L3%;vfWi)BCx{QiATf}75C*9O=|MINBnDCk z3J(wl`2$3Q#6WsM>OnL}4~z}sgXBSWf%Jngh!65JC~t!>D7}C%hz-IZHVlKp7-kQQ z2B`tDL1ux>htVK&VSJGNFbwk}NDs_BnE4<%kbCKYL2d)N5o9Mi28n~}4VXA6ykKmQ zn?V?)ALdSw-#~n1bs+U1F?0+PM^*<4dyp7N9mpI|e8bE|ra^KbK8OaX0ns2mAPh1O zM1#aYYCsso2AK`RAU+6#+zzr28H2Of|I^nx%*Er<`oAhj?xAT~@4#0H6jFeto0 z;vlzy^dn#0FuIJ`e_(0W%9(A2J)H2If|fT9_P+jm`fcIglQZUKj@X17s(N2Kf~v55pic zKzcwlNDQO~ghB3xVURf>H6Z&zG)Nr`gVcgB$b66(NFHPd2!s3!!l1ki%Zu1(kUYpP zSUCeS52g>q2H6iX8$^T51<@cr2!recxeFu)k^`9o!Z7_H`#@}%I*?jgVUQa@;Q+#* zbPJ-1#UM9=)Pl@_xevq!VURcsBdY_+fiSY)VCrBrOdeSc%zO|H5(kAFNF6!`nE?|6 zsYS-1G!2pmVNluug)>YH+3z4W$P8q?$ZU`t$bJxpu|aMEu|Z~oFo=(=hg>$u98j78 znGIrtFgCM5{sE;s5Qe2CkUL=dKs1PtO)pFgq!$!dAPfo@Q22l_hz-&U!XR@%7?(R> zW?_@ZB?b})`4L8g%m=vz8H41JF-$GUO)zyJe}gcH50giB4@^JEd>96)g^7b`biafA zj|;=>f|&(M51_CDl?fm*ka-|?fG~&+!XP#%u0ZYrsR5-mkU1cGk!g@QAUTj4kXl+{ zkoh2cK;a0(AiF{Cf$>2!NDT;s*f5N&4rC4pgXBQ!K>E=!NDib1gkfO-@)w8=k^_Yw zNDd@M2!qst^nhp>2I&WhfiOr6n;9T+5C+8qEM7tG1nEJ?Aa&Tx0EvOz3^D^ogV-Pp z(gVUEHPpbMGy}sR^Rbx;lLw_A5C-`NM1$f7CkTVYK^P_uG8@DP$$`=vD9?f1 z2&$Kn&4bwsQV+r)J=ieFT#z`(91sSX1M)W^3{nfy3&J3`fWjSwL2QtHAp1e?0ns4y zL1G{b;)7_AK2Ugq^nlzC3U?R=@j>z+bs+V$z#y|h_G81Ka0Q8j+yin8hz-IZJ~9T0 zA!C>va@fJ-LH>bZO8o;0SCIQ*7-TN8Js^2(c7W7@FwC7GzrolbKZE38=3vv0PYmQ1 zeDb8ofzk^MgUkhCbp6Qkpfn4@ptJ-^e;_}=Xb>CZFLeDdKFn;8UQk$o!UQ?~U}7L~ zkXayeLFEOA24RppU}lj@gX{+BN5>%d!Z64VbPSS%xdoIDL4F5eko~Z@2bm4ygV-QD zkZBMbgkj7Dx=69+((N9)@9hK{QMrMuY4I zi6grgqz@zxqCs*nwWQD>{iLYJrWRxdDE-620mKH0fz*O%klR3M41_^^5C+8yD9wT7 zU^FPqK>h>y2c#E-L25yKP~3ymf%JheNF1aW#0JqIyFuzf7^EI#4#+%^9EcB655gdG zklhDTj}L?Nz%WQ1NG~!5nGNED+=h%n_JT0T&mbD)PLO**Gzf$8BFJ2jndlfK4#UWD zATbahM1wHMZjkvf3^EghLFo<|!^A)|$Zk+rf%Jpi4buy9J1A^GW`g8E_JA;q4WdD6 zKz;#XkY7My3=#*aqZJ0353&#BE)WgFpfVjB8zctuBglO)8sttG8=VHJgJF<57#m~` z2!rfJ#vu0~`xPVyQU}5?H^Iz+(I7RTFbC1FFou}{G7niVhz)WF2!rAfT^-0wkQ@ku z)Pl?e(I9bKzm$${Js!XPyuKfq{E zIDy1L`aohJ8iYY=Kr~1{NFIhkcEQXBg*8YGOb%oZvOG*3u{6j$kbW2j`2%Doj0Ukm z?g6<8sbkUE$ekli3Q$Soim#0RlK>R=d@UT|TMeIN{S zCrCdCgUo>G1&PD_1EN9l$Qa}=kY7L;CeKx~j4OfQIr=>^Fl+Y8bQG8=?JY>-+I4bl%%3u1#X$Xt;5 z^uQpyK=A}hHy}5FXpk6)2Bj}h*n((ew}9k97^EH~4{|fi&mg~nFi0FG24aKMfM^hg zu|fWZVUT$sJ3wZ_#6WBihRK2CDZwCfK>k9;ptJ_UAU}e{VRnOPkUv0f1<@cr2*dn@ z&mADMKxTr>!>10U2Ba2*LH2<1ILv$y4da7o5C(-EvU#Ae0hs~9AiH1~6rV74AT~%H z$n7u;N_QYOObxARko_=>oF719Fh0mVAb-GU5F6xQP`H3Fh>eUvVxV{dVGtXHLGmE| zAPh1agkk1`*dPp&17VPQkX{fQhGF3X;)B8;hCzH72B`t*1EnR9dFU8qAF@7>JV*?r zA7m$p24N5zgh6Z=hUo)|(>hLI=7anJN;e>Pf@lyQlx9F|P#l2LCWsGH528V8K{QA$ z$gLm@N}nJM@&k-UW`p7eqz*=d!Vu(dkQfMq>;PeqnV>KR*#X0#@B@j1?4$&P`~)%w zM1$-C(J(te{sj3KghB3sxeeq_kefk#5Qh1UkUK!;fXv6I4&(-q{UCEec7V(VVdQXw zu|eV>HOS_H`~tEEgkfO>vJ)f@!XULE8rcqz-$3d?c7x>UfkAEn*#n|M801Efn?UXW z(a3yISb)SqX#nJ35C-u8onhhdOD7zUY%j6vd{@Bz^<3^E7C2GJls$W9O& z6z8CDhhdN$NE{g>n~%4%BK?1u3{G_qMBHpmW`eIObn1`0D;V_1BE;sAs}=>jGO z%M&m+fW$#=hlM>zEr<VU~sR5}4VURp1j6hWf&hsgVccB29g7r0SjM{I7l9ZL2@7rvkRF9iG$RFFi0(k2AK!K zFtaG7L1ux%5rjeJ!!U>svJ*tZ#9-kMN{b*2avw+x6m}p!% zpzw#KMHmfo3y2MJ7dj1650VFA7#pSrBnC1U6iy)jgZv1hK^POpcKJ3ts_FEKPoAG(<^vx%WWdO_|(reSPQ_=7MgOhFiA7c9&{X%QWR!UJ1E z8iqmf2V#S07zWt~G9QFtVjvo%4unB$5C)kGQUh}vhz-IZ{U8hqLl7S(528WhAdD^! zk^^ConV|9tM1$-AVUV4~Vvu?e2I&Xc0m2~rKp12`F&Ly4gkfP1VuSnw;={rdM1wFW zykQs=#@H~(?I1US@&JeisRy|a;=hV6Gs;V=?B>lQV$9{bPNk)kb6LG1*yko21pF#9*`Q4n?dS8d=MLCHb_4RgZLmd zAUTj8T49*|Aisg^1i1l3gD}W`kUY$85F3VJVjvo%7a4=}g7`2R#0HrK!ys`GhJ_g@ zEJ6MR@j+}*yn*5mhCyxu*#pC%Gzt^dsJ`BUm0l5c;VeSXfAoU;&a~p_;#SMrJ(vQtvkQlQ6Kzw`{WDW?!%m&e*Fon?| zHVA{v1Yvw;f#gB)1@jN6JOlXwq52FQMp94LN47-S#FT#!DH*&sH^KOlAJ7^Dwfj2u45P7ns! z19B^fhG9?`gXBQsAPjON%np$KAPnPUqd|H=`a%8x(I5;GgQ*4S2Z@3F3ko+78zzTN zgVdsnA@f0cLH2;+7!)TU8iYY?kT?v3#E|`gYzBx8GY6y|*-j7}BnHDEb3tZ-Feu)M z#V~#7G|XHO4RQm>jUWti6D|yshtVK6g2ET1ACwxgh62paw{mD(J@F4hCymU zX%D6!WCjR>%mvv4G6Nlh^nmPyr56wl5<|uyGeLZiImj5K4`e<_4n~9Q24NTZeChJ`Q4J)khdrXM5* zk^}h%#s;|^gh76XVURosBfAU42I&RSlwgqCL4Jf`kli2*3RjREjK*dcHZgR!fYgA@ zfnkt8Kz;>bkXjHQ~4&*0Xc7XJQ+zL_$!yrBg zgUkS7ka-{sG8+`vq+*y}7!5K9hLP3q2Qm|eLFz#m*}Wh+kXjf9g%v1_K{P15K;|N2kT?v3>;b6- z*$YwwQV((q$gRj2qz;5ZeuLQqqCsLXy&!!c3^E5M2689Jy~uF{5(C)}(oYD3)Pd{- zVRW@1Igni-3^EgB2Pj-&G>8qtAh)205y&ie355vTmhfNL0Z7{!q%mT4NVF|(@HY|=o;RH$t*vueR3}g?; zKcF}PVNm>oXpk7lUVP?(+zV0%!XQ1!7$ycY4<-kSZ&EO_er#-zJs>}TFvvZG+>0&` z(gV^9icb&*nFqq4cm>fQ401at%t06w2OxEz_yFkzVRSQLe2_YjUJwS^0TYMOpzsBW zgW?iI!_pSWP7og?4sr)F8zcu31JNM2z%WP+$W1V_anT?gxLWKQ)CR1!{!f=7!1SQ3-UY2Jdi&?7$go71JNM+ zVD^L9Ab*4OBa4B=VSIcv%-tZnKp12`41?rA7-TjG<1+_Y4x3t#dyvfnsexgTeIP%A z!Ux0#sY5mo7aL?IE;*1mC|p2(LdKvp0OEt>@nMi&kUKzXL3%)LgV7*1G6spm;v5?d zQj1F*BnQGEGe8)`hGCH1APjN`KEHz8ipwt`IZ&8_(hdlN%meX3W`byt7|dP}8zc_W zgU>#YT`)I-)PwlQ7$k;_L3V=pAR2^0Y-||h9uNk}!DtX0lunS%$HxY_9i$gz4~zz} zVeUna2ap&@4unDWfG|F@amj(~1nC7~Q27J11C;kcc7SLYM)wOSoI(0P;vfu)0~iLG z1!99}kQ~TAFts3kF!zAuKo}+lqG1@O2Au}UgUkZ?9fm>X!!XEBkUY9RYz zfbn5+AhTd*!rTgy1F=DVgkcaLhGAx4qd|H>7+DTvFUTGc2FZbFkl#S%KMaHTFpR7h zoek0t(gRWpibG@!%P$}?WHv0`VKhh{8NnQHK^T_*LGFO51JR%`2iXn6AURN+fZ_wh2KfVoLFz!}gV-Q(5C*9SiGeUk9E3q^ zkXjIiv0*ew3>kyOL3%*uz%a;eSXhDNKx&Znki!O<0Wu5ZW)Ka+FfmxTf$Rs#gTz60 zfYg96h>Z)w)FR7+%mKNd7z~Odkb00gAdGAdEF3^;kTEPyk!g@TC=G#VWO0xf2!q67 zG&&o^2VrD+5FZ(X`~pfNpzr~?3*=^Gx5B~@Sqz;G(gVUEGeG8n%mL9LJ_v)-8;B25 z0}6K-hM5BjA9Q;_ZUl+L)PVGY%z$B#n_+BhG{{~M2H6EOgIYAmU7$P%!XS5m_#i)n z+>9PaATbaI$&+h8$b1ln=?8@c$R3cnu($=~e~^1Xeuk+7(IEeWFer>b@*oUS12O|7 z55pieAPka2#~`&J43dYbM|KZP44Foj2k8Tu2{Hp2BdaHd4KfpyPLS1t#6aeQ!WU)- zhz}BnsRP-G%m;~s)PQJGFvuKGID#-J>_He7{vb9;9E3q^5C*Y9=>tT=Fi0F7!^{B1 zDX|!)2i+_XA7loI#)pyhgWL!TI}irB6Bfp>FoV&!!U`0YFbp#TM1#x)<$Vwa@j>AS z!Z5WU8e}$z2H6Q>gD}V*klR5t2!rH6`ao_&#vt=ye31DdK8QxfAblYHAakgRLHP)T zL3Y8+1%)XnAA-Ukgh6}|4Z_I&fvKmI2KgOi9}I)+fQ2Qp*&uNc2Duj$7oac!sY6x+ z5(AkBl80fC-#{3d4^jux2MSYAT!Ao%55pinNDjmYu|YJ556hn*^FU^R><3|x90-Hd z5yCL@VCq18m{}lmVHjpENFJmXM1#a&Y!FQj2H6D)BTza6(V#E|#W4uO*f1I<2I7M- zj1Ni+AT|gii-Y(eIbt!gUJx6EL1w^ckU1bW2!q^;EC&*UVURdT9)v;Z10)XeCkTVW z41{4}1rkG#D^S>i!X2azghA%O(hH0R=>w?&(a0F29vOqof|(BrV~|>q7>EX$3&S8c zg4_+FVPYT}qz;tMKxTsYApd~u1@U2ipcV~sC&-N;dthdQ+yn}DTo|Sf7Y*_&E;(G{ zAV0$LKgeEeW`NuX3M-IW5C+8ya+rYFps)jp!!Wvfm^dgrL4F6>1Hv$Of!H7nG7BUI zQv+gy#6a-^at8>5>;hqsI0%E(kb^Ot-Xg)xW*VGtYHtspiCgXBPF!t{b@keMJeKz4)JATf}8sfj^$f-uNzkefm70EH*6 zI0UIBWIxC(LTW(rAp1Zx$d9o656auP^n=_6QVUW8qG4_UsR!{v;RK5_n0X)?q#lN0 z@*ulGVG6<^{U8i-3y4O>Aax)<$Sx2DxdDVh_JQ~yJJHPmnL{iFnE}HfGePMCW(=1#0QCk;s+Un#9zEp{K$Xrl3f-s1UjzQue_ku7eo1YsB(q!xx@>L{T>=7QV;@*Bu4pfCWr z85W+%G)Nr?lWHHxOb`b717sd3e}ecR`#@}1z69}cnGLcR6el44AR6RukQpFx5C(~X z%tFVY@CJ#2%atBBrgh6USd>BSI3&w}J1I7o@AiW^-LHQmOryvXp4;T$%gD{8>!Z0?B28n?% zj1LNXYGIg}AoF1uBnHADH-l&phRK0w5Fdm=Y#2s%4=A2N;-Gj2(J%~(J5X4HFeqG* zF(_O?aS6g8bs)Vk3~~nygY<&bfWj1{AEXb4L2@uQNDib2W;cw6xf?`-++>ADbR*VlcHJedzKae}lwf800pPyOF~d#0H6jFi0J`8Tj}hJ)k@e z@-HZkKp12v2!q%lyFg}w(kX}rnFaDQh=yU1T97)BJ3tu32bl}PAT~%0ghA%PXb>BQ zL3%-KP~3vlgW?io2MB}g1ks={0?{Bo48z1gdO+?6(ICAb8m0#3Mq+7@K9IX%802pl z8%Bfd0=Wf-VSHS6fXqaf1GydKPLSV0=?3{nfy3$hc0 zL2Qs5D9&-|hnWMSL2&>x1Ed#(LH+^R4`RbGObp#jkbZ0!qz;Bb_JG)+_{F9cSq!8O z8H40Nd=QNbgWLuRe-I4|V-OAU0}K-~1KDhldqMsNg(EDUK;;1l!}y>ugRx=iko^a9 z3rG#fEugposRN0F%!Y}B~o!u$bp8!{WjhhdN(K^Vjb*#k-gAPlk> zqzB|jkUR*3*u-FvpFny+=7IbGqG1>o<{B8M9wDIkLGcR`hslH3 zAPhn=N5FexmWEaRCAUA=;kTEEnK^R#K zq#wiw(V(~lVGtV`gTz4Qf-oqIKxV`20*QezNE{i1K2J4~PxIFufoe#D`%JAEXC_L1G{p7Y4~AV^Deng##!oL16^KAT}sH zf-p!9lqNtlDD08-!q^};g2EM~7lc7-K^SBX41>ZOqy}Ub%xxe(NIeXL%mJARqCps> z2ZTZDKp3Wu5Dm%`xYUEhL2d%kAa#VyLYD`*3xq-L0=X6D7Z45NgTz4?WIhO^>&M0i zxdY@TkUmge1epV(L2(13L1u$!5C*Y9VF*$K!XUSRXplHD7-j}YEsPDKVHjj5$Q>XI zibEI;V}sHJ$gLm@k^|8o3}S=Sz%Vv{5)uQM0Wu%vCy>8DX&Zz=Vjv7s1H&M-AU4Q; zP#EGe9~2KD3{wkI4{|5SJdoL-@C1bi%svo}4TIFfFnV49nS-nc#0O!Jx#VJyc_1@D zc7oW*=77XOVjzqxj*AV7UyvL~JxDLe&mauqgJ@8^gTe(AMlcKtBM=73gWLeZAoVZ| zG6%#4iGwgmA1GWv>OuNI=7GW;M1$l&@dJ_r=>gFoJ~9TGfs8?VKp12OvO0WhP&$Cw z2V%qYg4iH&5C*Yf;`q$MCI@m4$c-R>f@n~fB4dy|$ZQzKW(G_Q6h<%%au+B}Kr|?> zKp12eD6C-SgTz1>#wV7B=|QH^*&sg1E>JoE*$Z+9h=yShA030_ki|fJkbgko0ul#> z0nD$&&>;07GeG)5ZUJFf8UwLG;vfuC15*pK2gV19q1ywBYmgck2DuM}LGcHokZPfiOr8M1wHM3`#M`9FUnHjBFONoyg+Y*r2$D zVNe=gB8-_t)0kQ*xLE#DFgD|L!1*rjvgD}WFAPmwEqLDF3J%|re4~q*>*n`Xg z@zLc#VlWIc9~7n_8iqk`!4|JDJ?Jz@Eyz9)2I+;-AU4Q-AQ~hF(g#utqDjFZ{UEo2 z+zDcX!T~wVL1G|tK^VryWj-jc!SsX71gQh*0mT(496@}T*&s0(n-Ut?91t6XL2&}2 zK^SB|D15NdFfouk41>ZH#s;<_46z(877zXh{;vfui6AXj=3^ET!gX{vC3o;9&7sLl)kQj&tVGtXHL2(bF zK^Pk7}*?A^)K$2UkH2*dmWavO*b(~HYIkQp#F$TUbCq#x#15F6xPCP9Y_qs2B`y?2g4van1AunAa$Uy1YwY$LGA(3AU}fG zFd8HV!XUFidO#RtFU)=r4HARVpg0DxK^UYC;OF`$292u|Z~lXi%7dFo;hG!{Qp|XOKRS86Z9=o;|cW(I9;w{V)u(8zc@g2c!-ZexUe)VGti=Hi!>03lt|HHVA{%qhpZS$YLOK zLGgmD52P0x2H6F|Ap1ZV6pt{!fM{aOz-2D7n?P)kn?N+k@33$H@j>>3Fo+GpF!Mm_ zK^Vpdg*^y^{0lMz@UTF(^*Z?FNMt2!q5y;ReDW zHVA{<2%=#a#0OyzAEXY1LH+{y9mEEi1M&+l4AKh<7m#`o2E{ihJV1U&ra|t9VUT)| zc_6bv`av{E4CDq72H6XuL2@7rVuR#C;vlzy{0uT5gh6(KFi0L`AIJ`n*&qzk3lay( zgD}W#AR2^0dO>ROVUT$sj86?VIglO@2HAs*L3V;LNDhRt=|vVpj%$#gL4F761JNK1 zG8;sL@(IYFAPfp45FbQ?Fv!m!8l(>$!|VcygWLtepm0OSAag-tAUA+$5C(~X><3|x z9UvMRql9~egX{*Gf$TmQ8x{s28svYFT96xH z@*s0T?gC+u9EcCX$mW5<1>`o67)TC=L3~ggg3N}|AT}u&rVm7;V^G|~>_VnN?uWSt z4k}b)WX;>8l(rLALLFD4RROAEg%|%(Zc~G29gKa2ckjdfXo273B-nB zkX^8NfaObcH6Z&ya_Dvt!-weu(bzD^Zjc*b?t#%DH-Ov=k^|X~jA4EP$$|U^qCx7B z^@7xZFen^gdO_-7YG5=-A4nWz4hX~4gJ@7%0%1@(L&oU-2FZccgY<*Uf?*IJhCzM- zVUQe13>1&ZX&U4Xkefhmg_#fHqhpX>5Qd3^*f90DXqZ}%8*!;Y7YF$X+3z4WNIwX} z;;((!yvt&xBy|0JWL!$<1!y+CWr><1=$CqVQdf|OksY`4eOp2*dP%?1agI!W_f~ z(fBa38gw>@55pk)VfMgiklmm#17Vo^L2dwH7#~D~^ujQR4^j^@14P5bK{UvJpmGMJ z282OkAUj|fCI+HGc7XCGhz5lTEUm%VAU+7g{0{O5hz&}cAbUXiK<0zY0?{B0vj@Zm ziGwgm9Y_zzED#Nn1Gyb!2FNau9uS`x3^D_RLGHkYk<9>^4Z%mj&p69o;qvCA7m~_56CaXV32trcZ2kTFvvWR888g87ug(;dTi=I>OtZlcfc^nT#$Jn zaS#o{AT|tx^nlcY%mmROK1dFx4w(jp4=hiC{0`!S!U5!0kbYzg3MUW-iGkFB%m$eY z(gU&s7Y3;TVVFEfJxD(&en1#xCM+yK;vjQD801e72Dt?!528Wh$QUFKF(@8DYLNAS^n%QR zg#$<)B!-SbVF(I4SeOz+!_*_wAT~M%xeH_;$Xy`&L3V-C1}L0iG&&pPUQj%OFo+Gq zApIb7K<0rkh!4Ubbs!A#D~N_+kXjH1@j>c97{mtI2Qm{Bh9LLBXk<1>3>FR`KY`dF z8suh>IUo!&3xq*-z%YmpG8<$ENG-@55QdozV}tl0KM;#S`3j^L8H3ye!r06L*$)y2 zVUQk>IuH$VH^{9Zc~E$P*dQ^OIuH$#17VPQ5C+ME)PXRF55u6i1gQsMm>4XLg8UA` zps)bx17T3ufH25BWDHURvJYe)$es8w%r1}`P<(*+AblV<$PFNKVPOg~6J|Du4Z<)# zgV-<(G6TehxgEp?nFGQw^)NHhX^_7_7+oC12Z@9H3t}TW%t06=2T}vl55gcZ z5DhX16xYZ!$UYDTiG$n)!?3Ub`3dAkkh?(`WEKpA>;TyX!XWcO;-LHrG7qE{=0}j3 zAU2E!sfW24M1$0W^nutQ3^EHugD{8}uSK>A^R z!bOAZf?<$)ko_PGiXV`DAoD_+e2_TEUKj@PK^VjbsR6|W zhz98anTZd>)WB$%T68{093%$9AUA?&kQg=$lLx5*xdVhjY>--z90-HNK>h-`8RT{l z8zhE~LHa-#q!%Oy3L6j~#0Ftd7~%?Zba7BvgT!DoC@ewxLH2`akl7#_gkfxu`#|bI zdO#Q?2f`pRkUkIwu|Z&{=kMo=?f$e@)L{=3MUX7WClnM#7EW( z;)5_q56GP$w}3FnJs^LAFo+Ej1Gx?64v?8J3^NzT2iXNO3#1-|VSWag1HvFVkiS6s zU~Ykl!}!QF%zdEv1-S!+LGFTKWVJ9h$UGPZsR!AKY#&S=It@|-QVYT${m6PjVFcoX z`~-3fNDT;s#6W66VFMBe(I7P-8YB-g6BaheG$;%}>R=e=9~ccX6U2s@529fhqz5Dq zqG1?h9t?xr0b+yHfb@Xe4sr*`JuoqlI0z&2LHa=P2ht1D19C4&A9^@}_#kl*2C+dH z6i*;N2!r_O7-SX*gXBSK(dA)$kUYq4Y#1a5!|3uLada_|eIPLq4Uz}h0ir=-ps)b> z6BK@+a3&Rl(ls&$nFqrlb3trS8USIKI#76H!yq?+Fh~xWhM5C03&aNb1LOy67!;-; zd1MUoD@YuKvFU+{f#gA9f}9pWY>-=EG{|irzko2z4v;>O9Uu%7gQXvkIuHh#0mC4F zfH24}AU!bqL3V>MjE_!(4(YVqe1G>F~|-O zhKa+>hw(xF#Dzg_2ZasD&!Dh@u|a%TxWN1Zqe1QkVPyA%_#h0@1EWD~5C)kA!Z0?- zT_83{9msx=+d&v)9teZPVHm^*VGtjr59BwHSs*q{FShUknFkXGv60gPh!4Ubc@Pcq zGYo_HAhjSlbPSRMg%QYZ5C*vggh6Z&2Dt}@LFzzkkRE*Ifz*KP2B|}~4-`HiF_6DO z;SS29AQ~G6sRgNnsli5r#6W65VFz+EEIok4VBrf>1LA`)%v_KfWDF7q$%Fh1G7pA9 z?nU+kG8+^IAb-LzKDEgDklCQHLUsp;4RZ^~Jup9k+y$}+WCzGz5C*Y97$gqDFts2W zWCjR>^nvVvse{=K(hrJH7$2PmsR!u;=?A$9OdG2haeh+Vd_A7Kzx`Shz9u&8H3^qqy{Di3JZ|iK^VpcsR4xt2!s3pN*5p+ zgkkQ5(I9`q*f1I-hO8H4E-3s!VlWI+3o;L67f24|CJ-N_7luJGC@@()NI3?sW4 z76za&!iGU%15yLRAosv9%nT48B!^8eObjFsQV()7%nj%?NFEu3^uY9EqhV@ssRM-< zC|q!1P`H4?2u6d{gD@=4&}o=DkRFgaP&xpKfz*M-Kx_~Ou|XK524pv~7)TsB3_xs< zKhXVv%m=YyVGN?Ng#|HUAa{T;$PFO3p<|GLKydk=Y37zW9MFghQk4unBwfG~&+!XR@%80KCO4bls; z8x;O93mfx-<$gD^HTK$V`wp$PFMdQ2c@H0AWx#fM{A_kRL%9l)gaj zfYBf}Iws^6kb026u(=ha7vwjPA3L1qug{d7$tEVVE3<4N?ojAT|ht z*sw4Ju|XK59u(#vc@P^NgVccJK{O15+z7H8gh62eas!Blg%OAilLPq)gt7UT7%`Au zki9TBfZUIaVQCsfgX{!hko_Pxfy@WdAU=oAX2AINU79kQtyb1-S*}CS(i}L&qTXAblV|f!qgTgUke(0ZK<8d61t$c7nt}801F~ z4U$L4AoF2zAR6RG7zUXIG6zJ%%mA@L=7TWEevmsr7{sSG2AK~ECm06#6&b_q1<@dJ zka}Eh0Lg*WA-f-0El3=c4nY1yjsqAQBo0yoV7>EzTpzsD^ zkQ|5x$q~XJbubLl2eK0uFUT~=k03q>gTe#kUzj_|r9pPV>;|O|7zU*g5F12;Fi0K9 zPEcHd?1f>F7|1;!`$2Lbnivey2QmYs7Dj{E$QYys;Rs&;$)Pmdy3O|rK zkRBKY@j>D+4C15f1L?(wL16&GAisj*0pw_D84GIg8 zzhL165(i-rALLF-Fvxrm2H6R+A33dn{0YJ!d60b|cYrX+UQpP8>;7DR(Eau|U8 z3&J4%AQ}`dAPiCi!=UhhVURhXFa^;tw}9LOVuQp$7-TO8qc#l+KNtqt4RQ+z zgV?z20?C0es7(Z-LGA=$kUK#bmp+gHppy{I0%FE5Q9N#K^UYLgh6bOTR<4*N07Tfc7rfT3?v70 z56G<`d2%q!ERa7zc7rg;e=rP6Cm?%37^D`&2g!ll0mC5sVVE2@;L`*0Cn!8X?gG)E z@C1or(*qKRVUT(d2I)b^ATvOIK{f-#hv@}{1v(q#e{}zX_#l0tv;+*M8{`ILjLklB#6aeQ!UPtMAT~C0KyC#22ZTX>LB=3G zAU=!+sljFjNE{u5{15Ucvfn`I03-&&AoqdPfYJqs28n?%hz-IZJ3-+FvLECJ5Ff+_ z(I7r3{IR(OCI<2sj18kfdO_(6WDdw|kli2}CJti5Fh~v=gWLpiGsr(6cY_-kWko#a5q#h&( z!pQ1D@}Ten>4k}dXpnzEdSDo2H^|*E4AKXa1JNKpOb%oRG6uHy|iNP>P z4@@0Q4CWS)9LPS9+d*js#0FsyABI8Z!Z65e7#kN2GY3S&(kF-p@nLZUqCsNl801cn z+d%FF#TCdt5C-u<;vfuTgXD3!fn0H9yFm7U+ylZOabnB_sROwY9TTG$IWAyfkBbJm z35H=|2;zgn7?iF+G&T$hcM!&9E;eycdVpb&8$lRUFM#Yt_ZKKmK;Z_$AoqYUNFB&d zkUkI_BnHB;Fam`c$Q&3AVuQ>9sYk{jGe8(-21q|h9%L3s41_`QAag(%F9>OeF)28o08gTfy~!^{EE zAU+7g^nk=*egoxsko_(FARg!gY1FvL3V)R3Y2z17{mr)kU9_zQVVh$vK&Y+NFF2y!i4O{<|b0a zK<0xmDSD9Ag4_tggv@}+gJ=*2`4JS>uyDskgVciJ0u;9(dqFfPj6fJ94vI^dUJx5( zCP)qx_8>M$3?@%34Kf!S2HB4dgWL$ipm>F0kUK#3!!XFtxb%a<5F`)M3!*{phhdNy zNFIb?_Q1>m@j+<_6fPhPQUju4^3;$QY(IB&MVVD|_dXPIod{DfD*dPoN2Vs!=K{PT3<$I7gNF7KING}M3_#g~( zJ1)D2k~k=yklhRNCn)Y=;f_p$kL1MTtNFL-i5RHsMZa~K%e}Uo&hCyzHVURmOcET{oEOh-KJ}gau{0+(%AR6R$ zkQ+cW2!qT5nG3Q9qz8sUe2_ROy@1RCv0)fl4U7$opH z?@$s4`2!T5$ZiJlLHOoP%I%zYq#f#MH@L2d!L1LQ6c2APYj55@-R0r?lC z9)v-5fiTF8FdAeI$PQ#T!0ZKyf#gAU!qkAoVCKWvAR43wWG=`aP`ZVw!9|1Y0O^Ba zZ0bPnhhdmHkUp4NkQ~T9WIuxVFboP85C-`bM1wGh4U-4C8Ds{`Y>>GiHpmW`e?c?| zgT!GNrVgYRWCqCJAPh1aMuWtW;~!)vNIl35koow+2&4w2AEX9EBV&*`AaRhJKp3PJ zhC$|n*dTjA@di>0!l3X0nE?_9$%8ORFUTLX#vr?>X$QzWkUv0v1<@dkY(9t&!pLC{ zVuRuySr5n@P#A#12ZTZ41ByRTxPjDy^uRDEkAdVtaSM_M(I7dHI0%E-Fg-9DB!-MZ z;vfvN1Dyuh4{|fiA0W4Z+zO&WZUkYFJje|o`$6hKZUAAB{UGx|YCsrd21p-BJxCo4 zgX{(A2k}AbL3%+L#0QDP^upL6{UCEd_JPtHhz-LqwJ^ItdO&Fm**utjkQ!_lW){qB zn14ay$m+52VQN8cN5>#{z{Eg)24NT<nCF^G>W4`PGNfYBf`L2PskQUkIVM1$0T zXb=XmK^Wu)m>)oX1-TW3LE<0`@)yV*APjR0%npz_APh?XAPlkxhC%)UnGfQFOg)1xdlWcW01Wt`;pnmYGC?6@*wwv%mn!pgh6~z7=SQ{ z4{`&D2Ju01AUA{9pm2oIAT|ht>;S0&nFos-7!9%$7$gS^a}W*UgTz5$1;QXPP&|S3gXBPb5F2CeUv;voA#7$lF5LFU8U0dfn74U2CW8{|F^2B`(v2g4wHK<0t$0AY|F z$QYy^#s{%s801$N2I&WhgUkonhm1kt2I7O{Kp5mcd>CdIhz5l{Hnkx0kTFOd$R2EJ zv5A4q2DusJUl4||LE<2@KxTtz5FdtN>Og8?c>^Q|@)Hb$!UGhpps)bx2blws1Brt$ zG8?1@hC$|l*dTL3{s6@ZItJMbQU@{@gt7SxBnGkrqz)ASAax+~Kx~j(kTFOtG6tyu z=>_QlVUQUh8Xbewfy_bHOHDS&y&&^IG{_zh2JvxWkUEfCK^PQ9uy_LTL2@AXgV><( z1j&QgATea~Kzcxa2g!ldz%a;e5C*9SVUQUxwV*Nu6ki~7Kx#nxL2(R{2dP8GpfmyF zgV-<(5`)4VXr^aE0Zj6v!_7!(d5_ku7;9SDQW1YwX{ zU}k{K1^Ef&50E_|aS#o{AaM{Mq!%O)!XP$C3}gm~53(C%A2J4sfzk{NgZRku=xmTY z$UYbb@zK?w^FeV#j5?S;kXn#mL2d_OWHw9XHfMaLlfU||iTL4EU~UJ=fiOrO6h0t1 zWV1kWATe|dQUkLCqy`ipAR2^0W`X2E7^VgkuOM-dI%EvWA0P~p1JNK1k_XWsvp^Wc z2VsyKkoh1n5C+)+!XW!V{sO53VUSuF2E{Q*4G4qO!7wOJK^P(+8qKX2CEl z9fN3?9LVi3eJ~m%2l4}m4>B9sUSu{%4u(PEAh*EWhK)w{ABYXo1H#zUg2X^_APiy? zgF$LReg(N7hGG5#xfA3*5DiN!pmGzW4&+Y|2KgC=k?jMiM|LmB9%Md9El3Q8L2@t* zvlAo-vK!<+kR6~j1u_>zgVciLKp3PRWF9&O$$>CP9%L3ajIIa92dM?IK{O1*>;#1Y z$XpPH$$`{T1B1*1nG3=oHnQ9CnGbRsD4alX1Hzy<1koTd5C*v!6m}qSP#%J*2hkvV zKxTtzkXjf9rFjq=qz2>;5Dnsk%mtZ&OvChp>;T0N2*cz-aR*Wl!yrCL4(1k+IEW83 z8^i{w0f~dekkx_2Kp12n2!q@Vau*DP^nx&m4{{3#gV-SV!_;$<7gkkXk5(BBjhe2vUW`Hor z4KNxcj;s&F2VrC}d~A?fkXaxMG8cqF{s8$0gh70e8jxE-{sCc-T96+>@*oWIGYEtH z1oA6LJ%|SB1!0hS7zT-hA|ywEX74rC@s9|(ikFbq=*5=Ul(_#l6SXcz|R0p&%QoyauE z4p6#)(I9om7$k?x2dM+aGssSmImmJ#zk$S&#XxLWSisx^qCx6F{s!rRVUW2XIgol# zxPsgb3Ug5U1*r#NkXjH8QVXI%d=Lid1Mxu^flX#z!_6q#vXnghApk z4C2EuOdq*4$Q+PfkeiS(NE~E82!rIYVNjTW!X1WTVGp7~;SZugav(Oy?I1Hj7{mr) zkl7$U2!r?_3}S=yfb@d+=osb>P<(+fC_jQQNIeXL^n);n50V36P+9?DbT)_&QU_8G z!yqvb2DuMpA4nd=hhdNy48z<9QVUBTpl|{C8AO9H$WJf~3S&^30?ETLNDYh)qCw_> zFvx6>A3x$S*Jq;)BFN>R}jUCJ2M<0%2q}$UKmHV17WSL1uu&Kz;(L z1KA71urNTTLH2|AAR49@q#qXsnF+!md60S#2Kf`@2ap*cyFhG^n?d0K3Nw&;kb6LG z2g$+w4x>T#!!SrcNFB&rkRFgdAPh1ChC$*WHb@N!gVcjC$W9O+WCsX?#6WzI8(AX2HZlgO1DOrNAaRfy5F1?_B!`SaYCvYgFo+Ms*u+3`ApIcoK{TltrXS`m5Dmj1 z`#@}v8j$(u7$gsiGf;TL!X6ZVAPf=%VGtXHLFT|PNG}M3%!G-7*dPqzgTfqyL26(a z#0S|2^CKvpKw>a^L2(5VgV7*4ko_=ukQ+d01;)olgVcl6!Q?=8!|Vf@2a*F}m>7tL zxf#R;VUYVl;RbRGDBmHcUyvNgOprLpoggz|YC&um2B`t*2Vs~P$Za5Zf$RtQ1B5|r zkT{5jnFrDXG9QFNY!C*qK^SBg2!q^-4TIbVaxcstkhvi9L3~m$NFT@#APfp05XNR7 zObnzBWH*Qg=|#pMe}Vh}!XUSUXpmn(7!(E|ccIguFoT%~qCxh6+yG+3FpLkPLH2_5 zfG~&+!XP%t4v>8yH6Xh|7^WY@2B`zdgTz2~z%Ymp(gWgyXpmZv7)&jQ4H5^*BV&*l z$V?E1sR7X-J`98S=or}yQrRH4A!Cp|pnL}MKL~@|48kCB5QeD*r4vxxAjcIbE;Sm~BoD$MHVA{_ z4n!lT4Hz4w2Zmu{=rl+k_x^Py&%8Q0>kV^ zwhtE@Q5em>9?{AoE}tBo0ys;)B$I*dQ}NW`Qt>4-yB-qhoY&5Fexm8H2A@2FOm3`5=2>=>}vk41?5w{6tF(ax2IWAiH4p;-W!nKz;z}0r?l12Kf_& zL4E^;3y20`kRD_V@+Zi@ATbaPGzf#lk=29L;=&-cAiW?A(hG`9kQfYu%z?3SnSm}2QV(-Khz6Mn z@&^cm>;d@~M8hyh9;6P0L25u4AX(gQLJqz8mSYGLX?G)OH7gZuz;6Ud$D7~~$1zd`1K%!Of)evp|k z8e|X1K2VwgnF$gH>4ljMG9MX(#s|?LH-pq8V_3L>)PgWb9XbsX!-hfX zK>h+@WHUi-17VPVKr{@)>;kC=VGtjr2E+#G2k}8+1Bxp`=>ujaE*j)Ekbglm2!qUq zsRi*tVUN!qQ2c=00>hvwNFAt*1i1s|Z;<>2GR>s1JVz|AT~%0#0FsyALMS3Js=w7 zW{_J!ZUkWvA0!Ua15*Q{K^P=N7>Es012O{}4HE;Y1=$A+ zN0?hc_JG_66Nk|-H-N$h6t^H6qz6QU!VJU)VGtkW1`r0>0m2|NL25u4BoCrtYC&#* znGJIrhz4PhUSxAXY!C+N0qF;sgN#A$24PTqf&34`ATvNT$UG1Y!yq?-%mHB#8{`I< z94RzNA4oq4gY4mdaxgPMG>nf;TDwFi1Zr{9tJb6qXL2d`p z=zao;fiTEFAT=Nw7Y6x*kQ$gghz8jOax(~n+ySFOZi8Wv8$lT4ZV-)(L17JZ3&?I5 zMm85@CP*Bl7DR(EG8?9!5Dn4;ax(~n*dUBc4@eG#LE#3%AUi>Bf~8Lo8y$n(2~rEg zAUz-q(*rUOW*;bxfWiY5r^py22ErhD5Jr~+nFq2LWG09WG806DFtR?7TafjF_#g~Y z0}=|l2l)wP7RY=M2B`&Mkb6NG zrXHjRBn~nMrWYg!!Z1F_A0Ra#3}S;Y$bBFT(g(5!Kx#o@2~!JVgUkcTgUkcT z!)TBg2!qT3VUU@i^a|1o(g(sIcf#Tl#0H6hZVUS)J2AK~s2gV1{AoqgI1^F9UKgbOr400RDUmy%J4}?KxfG~&+ z@(YL!(n|~msRdzBc;HhHQUh`y2!ref(IB^iXb=XmLGFWLkUK#b6h0stgh6s3H-hW| znFnHn^kT!H^bNuwH88axHVA|CQ-VR}f%JpI3Wh=Eg6sqN10;qmJV5%eVUSrc^&q{- zW`WosJs^3I*`T;Z#-Mlxg%OBG76-9G;vl^sGq7Qh9LR2v94HJydaz+o*n`Xmg+B~~ z#9$aXK4I!W=D_5!u|Z-W46+;K29R4oY>*fTgUkV?Z;%)agTz4?*<6s_pm0IPATbaI zxfP@qWEMys6t*A?G7n@nNFF2yG82YD>OtZl{V)s)8;~B5UJwSk6-0x?K<0wvK{QAo zNIwXJ_%Lyh9q1UO7i1?$928G5K1dA22H61$7mz%NkBmWbAoqjJ2c-)TA0`h)gT!DM#0Oyz zA0!XMp!5S01Gx{`Um!7%KR_6y4-|GVHcT(IXpr3?|A4{;6s{l);)BFNY>>M^VFzL( zV~{)ygWLwfAh&|t4$=<_Cy<#SHaZ5WLB=q7O8f+}8-zjb0GSP;s8m(+?5@#SKWF7!0xt7Js0)0qF

    85F6QSnB5>ZgD^}E#0G^W$PN$&nS+c$>Of|J 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�Fsy8-&r>AU-;V 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®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%0ALH;1*?@@V%|L9==3O95NNehUv16Y`Y;s7fK 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 C2C^#v literal 0 HcmV?d00001 diff --git a/gamefiles/miami/models/ps3btns.txd b/gamefiles/miami/models/ps3btns.txd new file mode 100644 index 0000000000000000000000000000000000000000..6f485f1434e8b318af9373ac8f93fcb3a524053b GIT binary patch literal 528424 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%0AJqQ= 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 Ke*lzvVQc`T%*zn~ literal 0 HcmV?d00001 diff --git a/gamefiles/miami/models/x360btns.txd b/gamefiles/miami/models/x360btns.txd new file mode 100644 index 0000000000000000000000000000000000000000..3c6ac314513a2f6e09e7046cf9ae3d9654effdc0 GIT binary patch literal 528424 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 z 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

    kk3{P_XgUiCuhuV70HATba|GaEUqL2KZC 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 z≫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

    78)*zF+qLb2Ch&|Z2M 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

    f{z42Bmkp8tRO;`x6Njeh>X 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 HKxP8~8ZCRd literal 0 HcmV?d00001 diff --git a/gamefiles/miami/neo/carTweakingTable.dat b/gamefiles/miami/neo/carTweakingTable.dat new file mode 100644 index 00000000..5bdc3596 --- /dev/null +++ b/gamefiles/miami/neo/carTweakingTable.dat @@ -0,0 +1,104 @@ +# Fresnel RO Table +# SUNNY CLOUDY RAINY, FOGGY EXTRASUNNY HURRICANE EXTRACOLOURS +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # Midnight +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 1am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 2am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 3am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 4am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 5am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 6am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 7am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 8am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 9am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 10am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 11am +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # Midday +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 1pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 2pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 3pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 4pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 5pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 6pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 7pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 8pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 9pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 10pm +0.4 0.4 0.4 0.4 0.4 0.4 0.4 # 11pm +# Specular Power Table (ther higher, the tighter the highlite) +# SUNNY CLOUDY RAINY, FOGGY EXTRASUNNY HURRICANE EXTRACOLOURS +25 15 70 10 15 15 18 # Midnight +25 15 70 10 15 15 18 # 1am +25 15 70 10 15 15 18 # 2am +25 15 70 10 15 15 18 # 3am +25 15 70 10 15 15 18 # 4am +25 15 70 10 15 15 18 # 5am +25 15 70 10 15 15 18 # 6am +25 15 70 10 15 15 18 # 7am +25 15 70 10 15 15 18 # 8am +25 15 70 10 15 15 18 # 9am +25 15 70 10 15 15 18 # 10am +25 15 70 10 15 15 18 # 11am +25 15 70 10 15 15 18 # Midday +25 15 70 10 15 15 18 # 1pm +25 15 70 10 15 15 18 # 2pm +25 15 70 10 15 15 18 # 3pm +25 15 70 10 15 15 18 # 4pm +25 15 70 10 15 15 18 # 5pm +25 15 70 10 15 15 18 # 6pm +25 15 70 10 15 15 18 # 7pm +25 15 70 10 15 15 18 # 8pm +25 15 70 10 15 15 18 # 9pm +25 15 70 10 15 15 18 # 10pm +25 15 70 10 15 15 18 # 11pm +# Diffuse Colour Modifier Table (Red,Green,Blue,Amount) +# SUNNY CLOUDY RAINY, FOGGY EXTRASUNNY HURRICANE EXTRACOLOURS +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # Midnight +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 1am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 2am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 3am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 4am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 5am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 6am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 7am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 8am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 9am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 10am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 11am +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # Midday +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 1pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 2pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 3pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 4pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 5pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 6pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 7pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 8pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 9pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 10pm +0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 0, 0, 0, 0 # 11pm +# Specular Colour Table (Red,Green,Blue,Ignored) +# SUNNY CLOUDY RAINY, FOGGY EXTRASUNNY HURRICANE EXTRACOLOURS +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # Midnight +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 1am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 2am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 3am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 4am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 5am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 6am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 7am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 8am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 9am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 10am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 11am +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # Midday +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 1pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 2pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 3pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 4pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 5pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 6pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 7pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 8pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 9pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 10pm +178, 178, 178, 100 140, 140, 150, 50 220, 220, 220, 75 255, 255, 255, 20 178, 178, 178, 100 178, 178, 178, 75 178, 178, 178, 100 # 11pm diff --git a/gamefiles/miami/neo/neo.txd b/gamefiles/miami/neo/neo.txd new file mode 100644 index 0000000000000000000000000000000000000000..b2fa6e66a721a1a10a7b4125a246d67444b37a16 GIT binary patch literal 28968 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 < - + +[FEI_STO] +Stop + +[ST_ICEC] +'Ice Cream' Sold + +[MOB_68A] +Tommy son, have I got a surprise for you + +[MOB_68B] +I'm down at the recording studios with some major artists + +[MOB_68C] +Why don't you pay us a visit? + +[MOB_68D] +You know it makes sense, dontcha? See ya later. + +[OUTFT1] +Street + +[OUTFT2] +Soiree + +[OUTFT3] +Coveralls + +[OUTFT4] +Country Club + +[OUTFT5] +Havana + +[OUTFT6] +Cop + +[OUTFT7] +Bank Job + +[OUTFT8] +Casual + +[OUTFT9] +Mr Vercetti + +[OUTFT10] +Tracksuit + +[OUTFT13] +MC Tommy + +[CAR_AS1] +CAR SHOWROOM ASSET COMPLETED + +[CAR_AS2] +~g~Sunshine Auto's will now generate revenue up to a maximum of $~1~. Make sure you collect it regularly. + +[BUYSAVE] +~g~You can now save your game here when not on a mission. + +[BUYGARG] +~g~You can also store vehicles in this garage. + +[STRPBUY] +Pole Position Club purchased: $~1~ + +[GA_4] +Car bombs are $500 each + +[GA_5] +Your car is already fitted with a bomb. + +[GA_6] { reVC update } +Park it, prime it by pressing the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button and LEG IT! + +[GA_7] { reVC update } +Arm with the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button. Bomb will go off when engine is started. + +[GA_6B] { reVC update } +Park it, prime it by pressing the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button and LEG IT! + +[GA_7B] { reVC update } +Arm with the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button. Bomb will go off when engine is started. + +[MOB_70A] +Tommy, it's me, Colonel Cortez. Look senor, I believe you are a man who can get things done. So please help me. + +[MOB_70B] +You can find me at the boat. + +[PICK2] +.357 delivered to Ocean View Hotel! + +[PICK3] +Chainsaw delivered to Ocean View Hotel! + +[PICK4] +Flame Thrower delivered to Ocean View Hotel! + +[PICK5] +.308 Sniper delivered to Ocean View Hotel! + +[PICK6] +Minigun delivered to Ocean View Hotel! + +[PICK7] +Rocket Launcher delivered to Ocean View Hotel! + +[PICK8] +Sea Sparrow now available from the Mansion on Starfish Island! + +[PICK9] +Tank now available from the Army Barracks! + +[PICK10] +Hunter now available from the Army Barracks! + +[CLOTH1] +Soiree outfit delivered to Rafael's on Ocean Beach. + +[CLOTH2] +Street outfit delivered to Safehouses. + +[CLOTH3] +Coveralls outfit delivered to Tooled Up in The North Point Mall. + +[CLOTH4] +Country Club outfit delivered to The Golf Club in Leaf Links. + +[CLOTH5] +Havana outfit delivered to Little Havana Streetwear in Little Havana. + +[CLOTH6] +Cop outfit delivered to Police Station on Washington Beach. + +[CLOTH7] +Casual outfit delivered to Gash in The North Point Mall. + +[CLOTH8] +Mr Vercetti outfit delivered to Collar & Cuffs on Ocean Beach. + +[CLOTH9] +Tracksuit outfit delivered to Jocksport in Downtown. + +[CLOTH10] +Bank Job outfit delivered to Malibu Club in Vice Point. + +[MOB_62A] +Tommy, is Ricardo Diaz, I want to thank you for looking out for me my man. + +[MOB_62B] +I ask that prick Cortez, he say you the real deal, my friend, why you not come see me. + +[MOB_62C] +I need a guy like you. All I have now is dickheads, + +[MOB_62D] +dickheads everywhere, yo. I make you real rich. + +[GOAWAY2] +~g~Come back when you have finished the Biker gang missions. + +[COL2_9] +You American idiot! They followed you here! + +[LOADCOL] +Loading... + +[STFT_17] +Fastest time on 'PCJ Playground' + +[STFT_18] +Fastest time on 'Trial By Dirt' + +[STFT_19] +Fastest time on 'Test Track' + +[NEW_REC] +~g~New Record Set!! ~w~~1~ minutes ~g~and ~w~~1~ seconds. + +[BMX_HOW] +~g~Do two laps of the dirt track, ~y~passing through ~g~the ~y~CHECKPOINTS ~g~as you go! + +[BMXREW1] +~g~Each time you beat your previous record for the two laps + +[BMXREW2] +~g~a larger ~y~REWARD ~g~will be awarded! + +[BMXRAIN] +~g~Looks like rain... + +[ITBEG] +In the beginning... + +[NBMNBUY] +El Swanko Casa purchased: $~1~ + +[LNKVBUY] +Links View Apartment purchased: $~1~ + +[HYCOBUY] +Hyman Condo purchased: $~1~ + +[BUYGARS] +~g~You can also store vehicles in these garages. + +[OCHEBUY] +Ocean Heights Apartment purchased: $~1~ + +[WASHBUY] +1102 Washington Street purchased: $~1~ + +[VCPTBUY] +3321 Vice Point purchased: $~1~ + +[HELP6_C] +Press the ~h~~k~~VEHICLE_HANDBRAKE~ ~w~button to apply the vehicle's handbrake. + +[HELP2_A] +Press the ~h~~k~~PED_SPRINT~ ~w~button when running to ~h~sprint + +[HELP4_A] +Press the ~h~~k~~VEHICLE_ACCELERATE~ ~w~button to accelerate. + +[HELP5_A] +Press the ~h~~k~~VEHICLE_BRAKE~ ~w~button to brake, or to reverse if the vehicle has stopped. + +[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. + +[PBOAT_1] { reVC update } +Press the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button to fire the boat cannons. + +[SEG3_4] { reVC update } +~g~You can pick up bombs by simply piloting your RC Raider adjacent to each one, to drop a bomb press the ~h~~k~~VEHICLE_FIREWEAPON~ ~g~button. + +[RCR1_3] { reVC update } +~g~If you wish to quit this mission press the ~h~~k~~VEHICLE_FIREWEAPON~ ~g~button to detonate your RC car. + +[HELP32] { reVC update } +Then fire using the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button. + +[HELP33] { reVC update } +Then fire using the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button. + +[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. + +[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. + +[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. + +[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. + +[ATUTOR3] +Press the ~h~~k~~TOGGLE_SUBMISSIONS~ button~w~ to toggle Paramedic missions on or off. + +[GUN_H1] +~w~Press the~h~ ~k~~PED_SPRINT~ ~w~button to buy. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ ~w~button to exit. + +[PU_CF3] { reVC update } +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to replace current weapon in this slot. + +[PU_CF4] { reVC update } +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to replace current weapon in this slot. + +[HELP9_B] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP37] +If you do not want to enter the vehicle while car jacking someone, press the ~h~~k~~PED_SPRINT~ ~w~button. + +[HELP6_A] +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. + +[HELP26] +Press the ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~button to enter or exit a vehicle. + +[HELP27] +Press the ~h~~k~~VEHICLE_TURRETUP~~w~ button and the ~h~~k~~VEHICLE_TURRETDOWN~~w~ button, to shift your weight on a bike. + +[HELP28] +Press the ~h~~k~~VEHICLE_TURRETUP~~w~ button and the ~h~~k~~VEHICLE_TURRETDOWN~~w~ button, to shift your weight on a bike. + +[HELP35] +Press the ~h~~k~~GO_LEFT~~w~, and the ~h~~k~~GO_RIGHT~~w~, to steer the vehicle. + +[HELP36] +Press the ~h~~k~~GO_LEFT~~w~, and the ~h~~k~~GO_RIGHT~~w~, to steer the vehicle. + +[HELP42] +Follow the ~q~pink blip~w~ to find the hotel. + +[HELP19] +Walk into the ~q~pink marker ~w~to continue. + +[HELP1] +Stop in the center of the ~q~pink marker. + +[HELP12] +Walk into the center of the ~q~pink marker~w~ to trigger a mission. + +[SEG3_6] +~g~To successfully hit a target zone, you must drop the bomb in the area represented by the ~q~pink marker~w~. You can drop the bombs in any order. + +[S_PROMP] +When not on a mission you can save your progress by collecting the ~h~cassette tape pickup. + +[HELP16] +Walk through the front door of the ~h~Ocean View~w~ Hotel to enter the building. + +[HELP43] +~g~Goto the ~h~Ocean View~g~ hotel on Ocean Drive. + +[HELI_F1] +~r~Heli Checkpoint mission cancelled! + +[AMMUHLP] +If you need any weapons visit ~h~Ammu-Nation~w~. Follow the ~h~Gun blip~w~ on the radar. + +[HELI_1] +Downtown Chopper Checkpoint + +[HELI_2] +Ocean Beach Chopper Checkpoint + +[HELI_3] +Vice Point Chopper Checkpoint + +[HELI_4] +Little Haiti Chopper Checkpoint + +[FST_MFR] +Most Favorite Radio Station + +[FST_LFR] +Least Favorite Radio Station + +[FEI_HOL] +Hold + +[FEI_ZOO] +Zoom + +[FEI_BTR] +> < - + +[FEI_NA] +N\A + +[MESA] +Mesa Grande + +[STRP_NO] +You cannot buy the Stripclub at this time, come back later. + +[CHSE] +CHASE + +[NBMN_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase El Swanko Casa for $~1~ + +[NBMN_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase El Swanko Casa for $~1~ + +[NBMN_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase El Swanko Casa for $~1~ + +[LNKV_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Links View Apartment for $~1~ + +[LNKV_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Links View Apartment for $~1~ + +[LNKV_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Links View Apartment for $~1~ + +[HYCO_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Hyman Condo for $~1~ + +[HYCO_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Hyman Condo for $~1~ + +[HYCO_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Hyman Condo for $~1~ + +[OCHE_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Ocean Heights Apartment for $~1~ + +[OCHE_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Ocean Heights Apartment for $~1~ + +[OCHE_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Ocean Heights Apartment for $~1~ + +[WASH_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase 1102 Washington Street for $~1~ + +[WASH_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase 1102 Washington Street for $~1~ + +[WASH_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase 1102 Washington Street for $~1~ + +[VCPT_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase 3321 Vice Point for $~1~ + +[VCPT_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase 3321 Vice Point for $~1~ + +[VCPT_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase 3321 Vice Point for $~1~ + +[PRNT_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Print Works for $~1~ + +[PRNT_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Print Works for $~1~ + +[PRNT_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Print Works for $~1~ + +[CAR_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Car Showroom for $~1~ + +[CAR_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Car Showroom for $~1~ + +[CAR_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Car Showroom for $~1~ + +[PORN_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Film Studios for $~1~ + +[PORN_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Film Studios for $~1~ + +[PORN_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Film Studios for $~1~ + +[ICE_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Ice Cream Factory for $~1~ + +[ICE_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Ice Cream Factory for $~1~ + +[ICE_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Ice Cream Factory for $~1~ + +[TAXI_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Taxi Company for $~1~ + +[TAXI_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Taxi Company for $~1~ + +[TAXI_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Taxi Company for $~1~ + +[BANK_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase The Malibu for $~1~ + +[BANK_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase The Malibu for $~1~ + +[BANK_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase The Malibu for $~1~ + +[BOAT_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Boatyard for $~1~ + +[BOAT_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Boatyard for $~1~ + +[BOAT_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Boatyard for $~1~ + +[STRP_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Pole Position Club for $~1~ + +[STRP_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Pole Position Club for $~1~ + +[STRP_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase the Pole Position Club for $~1~ + +[STOCK] +~r~out of stock + +[HELP14] +To find the Lawyer's office, follow the ~h~L blip~w~ on the radar + +[RAMPAGE] +RAMPAGE!! + +[RAMP_F] +RAMPAGE FAILED!! + +[RAMP_P] +RAMPAGE PASSED!! + +[RAMP_A] +ALL RAMPAGES COMPLETED!! + +[PAGE_01] +Kill ~1~ Gang Members in 2 Minutes! + +[PAGE_02] +Destroy ~1~ Vehicles in 2 Minutes! + +[PAGE_03] +Drive-by and Waste ~1~ Gang Members in 2 Minutes! + +[PAGE_04] +Runover and Kill ~1~ Gang Members in 2 Minutes! + +[PAGE_05] +Gun Down ~1~ Gang Members in 2 Minutes! + +[SENTXS] +Sentinel XS + +[MAP_LEG] +Map Legend + +[VCNMAV] +VCN Maverick + +[LG_01] +Player position + +[LG_02] +Avery Carrington + +[LG_03] +Biker Contact + +[LG_04] +Colonel Cortez + +[LG_05] +Ricardo Diaz + +[LG_06] +Kent Paul + +[LG_07] +Lawyer + +[LG_08] +Phil Cassidy + +[LG_09] +Boatyard + +[LG_10] +Malibu Club + +[LG_11] +Cubans + +[LG_12] +Film Studio + +[LG_13] +Ammu-Nation + +[LG_14] +Haitians + +[LG_15] +Hardware Store + +[LG_16] +Safe House + +[LG_17] +Ice Cream + +[LG_18] +Kaufman Cabs + +[LG_19] +Love Fist + +[LG_20] +Print Works + +[LG_21] +Property + +[LG_22] +Pay 'n' Spray + +[LG_23] +Clothes Shop + +[LG_24] +Tommy's Mansion + +[LG_25] +Telephone + +[LG_26] +Wildstyle Radio Station + +[LG_27] +Flash FM Radio Station + +[LG_28] +KChat Radio Station + +[LG_29] +Fever 105 Radio Station + +[LG_30] +VROck Radio Station + +[LG_31] +VCPR Radio Station + +[LG_32] +Espantoso Radio Station + +[LG_33] +Emotion 98.3 Radio Station + +[LG_34] +Wave 103 Radio Station + +[LG_36] +Sun Yard + +[LG_37] +Strip Club + +[MAP_YAH] +YOU ARE HERE + +[TAXSHRT] +~g~You can use this Kaufman Cab to take you to destinations instead of driving. It will cost you $9. + +[MOB_09D] +Maybe Leo's already dead. Maybe I killed Leo and took his phone - you think of that prick? + +[FE_MLG] +MAP LEGEND + +[FED_RDR] +RADAR MODE + +[FED_HUD] +HUD MODE + +[FED_RDB] +BLIPS ONLY + +[FEST_HV] +Highest Vigilante Mission level + +[BRIBE1] +You have just picked up a police bribe, this will reduce your wanted level by one star. + +[CLOHELP] +Clean Clothes!! + +[CRED001] +ROCKSTAR NORTH + +[CRD001A] +STUDIO DIRECTOR + +[CRD001B] +ANDREW SEMPLE + +[CRED002] +PRODUCER + +[CRD002A] +DEVELOPMENT DIRECTOR + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTORS + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED010] +ANDREW DUTHIE + +[CRED011] +CRAIG FILSHIE + +[CRED012] +WILLIAM MILLS + +[CRED013] +CHRIS ROTHWELL + +[CRD013A] +IMRAN SARWAR + +[CRD013B] +JAMES WORRALL + +[CRD013C] +JOHN HAIME + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +LEAD CHARACTER DESIGNER + +[CRD018A] +CHARACTER DESIGNER + +[CRED019] +IAN MCQUE + +[CRD019A] +TOKS SOLARIN + +[CRD019B] +ALAN DAVIDSON + +[CRED020] +LEAD ANIMATOR + +[CRED021] +ALEX HORTON + +[CRD022A] +ANIMATORS + +[CRED022] +LEE MONTGOMERY + +[CRD022B] +DUNCAN SHIELDS + +[CRD022C] +GUS BRAID + +[CRED023] +VEHICLE DESIGNERS + +[CRD023A] +JOLYON ORME + +[CRD023B] +ALAN DUNCAN + +[CRD024A] +LEAD VEHICLE DESIGNER + +[CRED024] +PAUL KUROWSKI + +[CRED025] +MAP DESIGNERS + +[CRED026] +ADAM COCHRANE + +[CRED027] +NIK TAYLOR + +[CRED028] +GARY MCADAM + +[CRED029] +KEIRAN BAILLIE + +[CRED030] +ALISDAIR WOOD + +[CRED031] +ANDREW SOOSAY + +[CRD031A] +STEVEN MULHOLLAND + +[CRD031B] +WAYLAND STANDING + +[CRD031C] +CAMPBELL J. DICK + +[CRD031D] +GRAPHIC DESIGNER + +[CRD031E] +STUART PETRI + +[CRED032] +LEAD PROGRAMMER + +[CRD032A] +PROGRAMMERS + +[CRED033] +ALEXANDER ROGER + +[CRED034] +GRAEME WILLIAMSON + +[CRED035] +BARANE CHAN + +[CRED036] +DEREK PAYNE + +[CRED037] +GORDON YEOMAN + +[CRD037A] +ALAN CAMPBELL + +[CRD037B] +MARK HANLON + +[CRD037C] +ANDRZEJ MADAJCZYK + +[CRED038] +LEAD MUSIC PRODUCER + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +LEAD AUDIO ENGINEER + +[CRED042] +ALLAN WALKER + +[CRD041A] +AUDIO ENGINEER + +[CRD041B] +AUDIO + +[CRD042A] +WILL MORTON + +[CRED043] +AUDIO PROGRAMMER + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD QA + +[CRED048] +NEIL CORBETT + +[CRED049] +KEVIN WONG + +[CRED050] +QA + +[CRED051] +DAVID BEDDOES + +[CRED052] +DAVID WATSON + +[CRED053] +BARRY CLARK + +[CRED054] +ROSS SPARROW + +[CRED055] +JAMES ALLAN + +[CRED056] +NEIL MEIKLE + +[CRD056A] +GEORGE WILLIAMSON + +[CRD056B] +MATT JONES + +[CRD056C] +ROB HARBOUR + +[CRD056D] +TOM WHITTAKER + +[CRED057] +LEAD TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRD057A] +TECHNICAL SUPPORT + +[CRED059] +CHRISTINE CHALMERS + +[CRD060A] +OFFICE SUPPORT + +[CRD060B] +KIM GURNEY + +[CRD060C] +CASSIE OLIVER + +[CRED060] +ROCKSTAR NEW YORK + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +VP OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +CHIEF TECHNOLOGY OFFICER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRD071A] +DIRECTOR OF QUALITY ASSURANCE + +[CRD072A] +JEFF ROSA + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +JERONIMO BARRERA + +[CRED080] +CARLY SLATER + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +RENAUD SEBANNE + +[CRED084] +RICHARD KRUGER + +[CRD084A] +DANIEL EINZIG + +[CRD084B] +JACEN BURROWS + +[CRD084C] +LINN PR + +[CRD084D] +COVER ART + +[CRD084E] +STEPHEN BLISS + +[CRED085] +KENT PAUL'S 80 NOSTALGIA ZONE + +[CRED086] +WRITTEN BY DAN HOUSER + +[CRD086A] +PRODUCED BY ADAM TEDMAN + +[CRED087] +WWW.KENTPAUL.COM AND WWW.VICECITY.COM + +[CRED088] +CREATED BY + +[CRD088A] +ADAM TEDMAN + +[CRD088B] +DAVID YU + +[CRD088C] +JERRY LUNA + +[CRD088D] +STUART PETRI + +[CRD088E] +MICHAEL CARNEVALE + +[CRD088F] +GREG LAU + +[CRD088G] +FUTABA HAYASHI + +[CRED089] +QA MANAGER + +[CRED090] +CRAIG ARBUTHNOTT + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRD092A] +JOE HOWELL + +[CRD092B] +MARC FERNANDEZ + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +ROCKSTAR TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRD098A] +ELIZABETH SATTERWHITE + +[CRD098B] +JAMEEL VEGA + +[CRD098C] +MIKE HONG + +[CRED099] +LEE CUMMINGS + +[CRED100] +STORY + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT SCENES + +[CRED108] +WRITTEN BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER AND NAVID KHONSARI + +[CRED110] +PRODUCED BY JAMIE KING + +[CRD110A] +TALENT PROCUREMENT: JAMIE KING, SEAN MACALUSO + +[CRED111] +CAST + +[CRD111A] +SUPPORTING CHARACTERS + +[CRED112] +TOMMY VERCETTI - RAY LIOTTA + +[CRED113] +KEN ROSENBERG - WILLIAN FICHTNER + +[CRED114] +SONNY FORELLI - TOM SIZEMORE + +[CRED115] +STEVE SCOTT - DENNIS HOPPER + +[CRED116] +AVERY CARRINGTON - BURT REYNOLDS + +[CRED117] +RICARDO DIAZ - LUIS GUZMAN + +[CRED118] +LANCE VANCE - PHILIP MICHAEL THOMAS + +[CRED119] +COLONEL JUAN CORTEZ - ROBERT DAVI + +[CRED120] +UMBERTO ROBINA - DANNY TREJO + +[CRED121] +PHIL CASSIDY - GARY BUSEY + +[CRED122] +MITCH BAKER - LEE MAJORS + +[CRED123] +MERCEDES CORTEZ - FAIRUZA BALK + +[CRED124] +KENT PAUL - DANNY DYER + +[CRED125] +JEZZ TORRENT - KEVIN MCKIDD + +[CRED126] +TAXI CONTROLLER - DEBORAH HARRY + +[CRED127] +CANDY SUXXX - JENNA JAMESON + +[CRED128] +BJ SMITH - LAWRENCE TAYLOR + +[CRED129] +AUNTIE POULET - YOUREE CLEOMILI HARRIS + +[CRED130] +SUPPLIER - ARMANDO RIESCO + +[CRED131] +COUGAR - BLAYNE PERRY + +[CRED132] +HILARY - CHARLES TUCKER + +[CRED133] +CONGRESSMAN ALEX SHRUB - CHRIS LUCAS + +[CRED134] +OLD MAN KELLY - GEORGE DICENZO + +[CRD134A] +CAM JONES - GREG SIMS + +[CRD134B] +PSYCHO - HUNTER PLATIN + +[CRD134C] +MAUDE THE ICE CREAM LADY - JANE GENNARO + +[CRD134D] +JETHRO - JOHN ZURHELLEN + +[CRD134E] +GONZALES - JORGE PUPO + +[CRD134F] +DWAYNE - NAVID KHONSARI + +[CRD134G] +DICK - PETER MCKAY + +[CRD134H] +MIKE THE GOON & PORN GUY - ROBERT CIHRA + +[CRD134I] +PERCY - RUSSELL FOREMAN + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +TECHNICAL DIRECTION BY ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +DIRECTED BY NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +PRODUCED BY JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT PERSPECTIVE STUDIOS, BROOKLYN + +[CRED140] +MOTION CAPTURE ACTORS + +[CRD140A] +BLAYNE PERRY + +[CRD140B] +JONATHON SALE + +[CRD140C] +CHARLES TUCKER + +[CRD140D] +EDDIE MARRERO + +[CRD140E] +WILLIAM MCCALL + +[CRD140F] +JORGE PUPO + +[CRD140G] +ROBERT JACKSON + +[CRD140H] +TARA RADCLIFFE + +[CRD140I] +JENIFER GAMBETESE + +[CRD140J] +KRIS ACHEVARRIA + +[CRD140K] +ALI ORDOUBADI + +[CRD140L] +KAHLEEM POOLE + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRD141A] +WRITTEN BY DAN HOUSER, MARC FERNANDEZ, GILLIAN TELLING AND NAVID KHONSARI + +[CRD141B] +WITH HELP FROM JEREMY POPE, LANCE WILLIAMS, AND JENNY JEMISON + +[CRED142] +WRITTEN BY + +[CRD142A] +DAN HOUSER AND JAMES WORRALL + +[CRED143] +DIRECTED BY DAN HOUSER, CRAIG CONNER, MARC FERNANDEZ, AND ALLAN WALKER + +[CRED144] +PRODUCED BY RENAUD SEBANNE + +[CRED145] +PEDESTRIANS + +[CRED146] +ADAM DAVIDSON, ADAM WATKINS, ALEJANDRO K. BROWN, ALEX ANTHONY SIOUKAS, ALEX GARCIA, + +[CRED147] +ALICE SALTZMAN, ALISON CIHRA, AMY SALIMA, AMY SALZMAN, ANDREA VIDELA, ANTHONY ATTI, + +[CRED148] +ANTHONY RIVERA, BIJAN SHAMS, BLAYNE PERRY, BRETT BISOGNO, BREYE MATA, BRIAN PANEN, + +[CRED149] +BROCK VODER, CAREY BERTINI, CHARISSE LAMBERT, CHRIS DIFAT, CHRIS REISENBERGER, + +[CRED150] +CHRISTOPHER BRODAY, CHRISTOPHER CARRO, CYNTHIA GREENE, DAMARIES LOPEZ, DAN LEE, + +[CRED151] +DAN SCHNEIDER, DAN TOYAMA, DAVID DEAN CHALTFIELD, JR., DAVID HARRISON, DAVID WILEY, + +[CRED152] +DEBORAH COLLINS, DEBRANDA CHANEY-GILES, DEMETRA KOUKOULAS, DENISE ROSADO, + +[CRED153] +DEVIN BENNETT, DEVIN WINTERBOTTOM, DORIS WOO, DOUGLAS HARRISON, DUNCAN COUTTS, + +[CRED154] +DUPE AJAYI, EDWIN AVELLANEDA, ELIZABETH HOWELL, ELIZABETH SATTERWHITE, ERIC NAGLE, + +[CRED155] +ESTEBAN KARPLUS, F. FONT, FUTABA HAYASHI, GENE HILGREEN, GERALD COSGROVE, + +[CRED156] +GERARD LUNA, GILLIAN TELLING, GREGG CARLUCCI, GREGORY CLERVOIX, GREGORY SCHWEIZER, + +[CRED157] +HADLEY TOMICKI, J. ROSSETT, JAMEEL VEGA, JASON JONES, JEFF ROSA, JENNIFER JEMISON, + +[CRED158] +JEREMY TAGGERT, JESSICA RIDER, JOSEPH GREENE,JOSEPH HOWELL, KATE DUKICH, + +[CRED159] +KEL O'NEILL, KEVIN HOPKINS, LADAWN JAMES, LANCE WILLIAMS, LAURA BUBBLES, + +[CRED160] +LAURA PATTERSON, LEE CUMMINGS, LETICIA L. YOUNG, LINDSAY KENNEDY, LISA ORITZ, + +[CRED161] +LORNA JORDAN, LUCIO AMADIO, MARCO FERNANDEZ, MARIKO TANAKA, MARLON MATTHEWS, + +[CRED162] +MARY TELLING, MASAYOSHI MITSUYAMA, MATTHEW CHUNG, MAX ALLSTADT, MAX BOGDANOV, + +[CRED163] +MELISSA ALVAREZ, MICHAEL MAY, MICHAEL ROTHSTEIN, MIGUEL VIDAL, MIKE FEDERLINE, + +[CRED164] +NATALIE DESCALZO, N'GAI MEMBERS, NICOLAS MALLO, NOELLE SADLER, NORBERT MORIVAN, + +[CRED165] +OSWALD GREENE, JR., PETER MCKAY, PETER APPEL, PRESTON SAVARESE, RAFAEL GONZALES, + +[CRED166] +RANDY JOHNSON, REY CONCEPCION, RICHARD KROGER, ROB TIBBS, ROBERT JACKSON, + +[CRED167] +ROBERT SCHULER, ROSS A. MCINTYRE, RUSSELL FOREMAN, RUTH NUNEZ, SALVADORE SUAZO, + +[CRED168] +SAM WHITE, SANTOS GONZALES, SCOTT SMITH, SEYMOUR FRAILMAN, SPELMAN BRAUMAN, + +[CRED169] +STEPHANIE TELLING, STEVE KNEZEVICH, STEVE ROBERT, SUMIKO YASUDA, SUSAN LEWIS, + +[CRED170] +SYLVIA COLACIOS, TOMOKO MIYAZAKI, TRON, VERDEL HALE, YVES MONDESIR, ZENO LEINFELDER, + +[CRED171] +DAVID BEDDOES, CHRISTINE CHALMERS, BARRY CLARK, NEIL CORBETT, KIM GURNEY, NEIL MEIKLE, + +[CRED172] +CASSIE OLIVER, LORRAINE ROY, DAVID WATSON, KEVIN WONG, WILL MORTON + +[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] +MUSIC CONSULTANCY + +[CRD218A] +HEINZ HENN + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER AND LAZLOW + +[CRED223] +PRODUCER FOR ROCKSTAR NORTH + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING + +[CRED228] +WRITTEN BY DAN HOUSER AND LAZLOW + +[CRED229] +FLASH FM + +[CRD229A] +TONI-MARIA CHAMBERS + +[CRD229B] +IMAGING VOICE AND PRODUCTION-JEFF BERLIN + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +TOMMY MOTTOLA, + +[CRED232] +MICHELLE ANTHONY, + +[CRED233] +STEVE BARNETT, + +[CRED234] +CHUCK FLECKENSTEIN, + +[CRED235] +RITA LIBERATOR + +[CRED236] +MARTIN & CLAIRE LOGAN + +[CRED237] +SANDRA HUTTON + +[CRED238] +CHRISTINE DAVIDSON + +[CRED239] +ALAN, RED & BIGFOOT + +[CRED240] +LE T + +[CRED241] +COLIN DONALD + +[CRED242] +KERRY STALLWOOD + +[CRED243] +ALAN MCGREGOR + +[CRED244] +CHRIS MORTON + +[CRED245] +EMIL BUSSE + +[CRED246] +EMILY BAILLIE + +[CRED247] +KEVIN ARCHIBALD + +[CRED248] +MORAG KERR + +[CRED249] +CATH WALKER + +[CRED250] +ISO BAR + +[CRED251] +WATERLINE + +[CRED252] +NEWS CAFE + +[CRD251A] +THE POND + +[CRD252A] +PIVO + +[CRED253] +BUDGET VIDEO RENTALS + +[CRED254] +LORNA'S SCOOTER + +[CRED255] +GARETH MURFIN + +[CRED256] +ADDITIONAL ART + +[CRED257] +TONY PORTER + +[CRED258] +CRAIG MOORE + +[CRED259] +CUT SCENE LIP-SYNC ANIMATION + +[CRED260] +COSGROVE HALL FILMS + +[CRED261] +PRODUCER - OWEN BALLHATCHET + +[CRED262] +SENIOR ANIMATOR - JON TURNER + +[CRED263] +ANIMATORS - RICHARD DRUMM + +[CRED264] +DAVE BROWN + +[CRED265] +MAIR THOMAS + +[CRED266] +PRASHANT PATEL + +[CRED267] +AUDIO TECHNOLOGY CONSULTANT + +[CRED268] +RIK EDE FOR GAMESOUND LTD. + +[CRED269] +DTS INTEGRATION SUPPORT + +[CRED270] +TED LAVERTY FOR DTS + +[CRED271] +CHRIS GREER FOR DTS + +[CRED272] +JASON PAGE FOR SCEE + +[CRED273] +RESEARCH AND ANALYSIS + +[CRED274] +VROCK + +[CRED275] +DJ: LAZLOW AS HIMSELF + +[CRED276] +IMAGING VOICE-JOE KELLY + +[CRED277] +IMAGING PRODUCTION-JONATHAN HANST + +[CRED278] +WAVE 103 + +[CRED279] +DJ: ADAM FIRST-JAMIE CANFIELD + +[CRED280] +IMAGING VOICE-JEN SWEENEY + +[CRED281] +IMAGING PRODUCTION-JONATHAN HANST + +[CRED282] +FEVER 105 + +[CRED283] +DJ: OLIVER 'LADYKILLER' BISCUIT-JULIUS DYSON + +[CRED284] +IMAGING VOICE MALE-ED MCMANN + +[CRED285] +IMAGING VOICE FEMALE-SHAWNEE SMITH + +[CRED286] +IMAGING PRODUCTION- LISTEN KITCHEN + +[CRED287] +EMOTION 98.3 + +[CRED288] +DJ: FERNANDO- FRANK CHAVEZ + +[CRED289] +IMAGING VOICE-JEN SWEENEY + +[CRED290] +IMAGING PRODUCTION-JONATHAN HANST + +[CRED291] +RADIO ESPANTOSO + +[CRED292] +DJ: PEPE-TONY CHILRODES + +[CRED293] +WILDSTYLE + +[CRED294] +DJ: MISTER MAGIC AS HIMSELF + +[CRED295] +IMAGING VOICE-FRANK SILVESTRO + +[CRED296] +IMAGING PRODUCTION-LAZLOW + +[CRED297] +KCHAT + +[CRED298] +WRITTEN BY DAN HOUSER AND LAZLOW + +[CRED299] +PRODUCED AND EDITED BY LAZLOW + +[CRED300] +DJ AMY SHECKENHAUSEN -LEYNA WEBER + +[CRED301] +JEZ TORRENT-KEVIN MCKIDD + +[CRED302] +MANDY -COLLEEN CORBETT + +[CRED303] +MICHELLE CARAPADIS-MARY BIRDSONG + +[CRED304] +MR.ZOO-CARL DOWLING + +[CRED305] +GETHSEMANEE-LYNN LIPTON + +[CRED306] +CLAUDE MAGINOT-JOHN MAUCERI + +[CRED307] +BJ SMITH-LAWRENCE TAYLOR + +[CRED308] +THOR-FRANK FAVA + +[CRED309] +RADIO CALLERS + +[CRED310] +COUZIN ED, JOSH CLARK, JASON BUHRMESTER, JUAN ALLER, WAYNE OLIVER, SUSAN LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, KEITH BROADAS + +[CRED311] +LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, + +[CRED312] +DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, + +[CRED313] +KEITH BROADAS + +[CRED314] +VCPR + +[CRED315] +WRITTEN BY DAN HOUSER AND LAZLOW + +[CRED316] +PRODUCED BY LAZLOW + +[CRED317] +MAURICE CHAVEZ-PHILLIP ANTHONY RODRIGUEZ + +[CRED318] +JONATHAN FREELOADER- PATRICK OLSEN + +[CRED319] +MICHELLE MONTANIUS-KELLY GUEST + +[CRED320] +REP. ALEX SHRUB- CHRIS LUCAS + +[CRED321] +CALLUM CRAYSHAW- SEAN MODICA + +[CRED322] +JOHN F. HICKORY- LJ GANSEN + +[CRED323] +PASTOR RICHARDS- DAVID GREEN + +[CRED324] +JAN BROWN- MAUREEN SILLIMAN + +[CRED325] +BARRY STARK- RENAUD SEBBANE + +[CRED326] +JENNY LOUISE CRAB- MARY BIRDSONG + +[CRED327] +KONSTANTINOS SMITH- KONSTANTINOS.COM + +[CRED328] +JEREMY ROBARD-PETER SILVESTRO + +[CRED329] +RADIO COMMERCIALS + +[CRED330] +WRITTEN BY DAN HOUSER AND LAZLOW + +[CRED331] +PRODUCED BY LAZLOW + +[CRED332] +ADDITIONAL JINGLES PRODUCED BY CRAIG CONNER + +[CRED333] +COMMERCIAL VOICES: + +[CRED334] +ADAM DAVIDSON, ALEX ANTHONY, ALICE SALTZMAN, AMY SALZMAN, KATE DUKICH, + +[CRED335] +ARAN RONICLE, BARB JONES, BEN KRECH, BRIAN THOMAS, BROCK YODER, CHRIS + +[CRED336] +FERRANTE, CRAIG CONNER, DAVE RYAN, DAVID GREEN, DORIS WOO, DOUGLAS + +[CRED337] +HARRISON, ED MCMANN, FRANK CHAVEZ, FRANK FAVA, GENE HILGREEN, GREG + +[CRED338] +SCHWEIZER, HUNTER PLATIN, JAMES FERRANTE, JEFF BERLIN, JEFF ROSA, JOE KELLY, + +[CRED339] +JOHN MAUCERI, JOSH CLARK, JULIE WEMYSS, KEVIN STRALEY, KIM GURNEY, LANCE + +[CRED340] +WILLIAMS, LAURA PATERSON, LAZLOW, LISA ORTIZ, LORNA JORDAN, LUCIEN JONES, + +[CRED341] +MAUREEN SILLIMAN, MIKE FERRANTE JR., PETE GUSTIN, PETER SILVESTRO, RAFF + +[CRED342] +CROLLA, RANDY JOHNSON, RICHARD KRUGER, RON REEVE, SHELLEY MILLER, SKY, TJ ALLARD + +[CRD344A] +AUDIO RECORDED AT DIGITAL ARTS STUDIOS, + +[CRED344] +NYC, TRACK 9 STUDIOS, NYC, + +[CRED345] +WEDDINGTON MULTIMEDIA, LOS ANGELES, + +[CRD345A] +SYNC SOUND, NYC AND RADIO LAZLOW, LONG ISLAND. + +[CRED346] +THANKS TO AXEL ERICSON AND WON LEE AT DIGITAL ARTS, PAUL VASQUEZ AT TRACK 9 STUDIOS, JOHN BOWEN AND JOHN HASSLER AT SYNC SOUND + +[CRED347] +MARK LLOYD + +[CRED348] +TIM BATES + +[CRED349] +KIT BROWN + +[CRED350] +ANDY MASON + +[CRED351] +PHIL DEANE + +[CRED352] +PHIL ALEXANDER + +[CRED353] +MATT HEWITT + +[CRED354] +DENBY GRACE + +[CRED355] +ANTOINE CABROL + +[CRED356] +JONOTHAN STONES + +[CRED357] +MIKE BLACKBURN + +[CRED358] +TIM MCGAFF + +[SUNSHIN] +Sunshine Autos + +[CHERRYP] +Cherry Popper Icecreams + +[KAUFCAB] +Kaufman Cabs + +[BOATYAR] +The Boatyard + +[WANT_L] +Your wanted level has been suspended, if you commit a crime whilst the stars are flashing your full wanted level will be reinstated. + +[PICK1] +Body Armor delivered to Ocean View Hotel! + +[HOTRNG] +HOTRING + +[BLODRNG] +BLOODRING + +[DIRTRNG] +DIRTRING + +[FEC_ABR] +Accelerate, Brake or Reverse + +[FEI_BTU] +; = - + +[FEI_SCR] +Scroll + +[SKUMBUY] +Skumole Shack purchased: $~1~ + +[SKUM_L] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Skumole Shack for $~1~ + +[SKUM_T] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Skumole Shack for $~1~ + +[SKUM_C] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to purchase Skumole Shack for $~1~ + +[LG_35] +Destination + +[BOAT_AS] +~g~The Boatyard will now generate revenue up to a maximum of $~1~. Make sure you collect it regularly. + +[BOAT_A2] +BOATYARD ASSET COMPLETED + +[BOAT_N] +Checkpoint Charlie + +[BOAT_P] +~g~collect the packages before the time runs out. + +[FEI_R1B] +R1 \ R2 button - + +[HELP9_A] +Press the~h~ ~k~~PED_FIREWEAPON~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HELP21] +Press the ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~button to enter or exit a vehicle. + +[CREAM] +Distribution + +[UMBERTO] +Cafe Robina + +[PU_CF1] +Press the ~h~~k~~PED_ANSWER_PHONE~ ~w~button to pick up this weapon. It will replace any weapon you have of the same type. + +[FED_RDM] +MAP & BLIPS + +[FEC_ILU] +Invert Look in 1st Person + +[NITRO] +All taxi's now have a boost jump! Just press the horn button. + +[RATNG53] +Untrustworthy + +[RATNG54] +Embarrassment + +[RATNG55] +Hacker + +[RATNG56] +Cheater + +[RATNG57] +Total Liar + +[STHC_04] +Highest score with Keepie-Uppy beach ball + +[STHC_05] +Hotring Best Result + +[STFT_13] +Fastest time on Downtown Chopper Checkpoint + +[STFT_14] +Fastest time on Ocean Beach Chopper Checkpoint + +[STFT_15] +Fastest time on Vice Point Chopper Checkpoint + +[STFT_16] +Fastest time on Little Haiti Chopper Checkpoint + +[STFT_21] +Fastest time in Hotring + +[STFT_22] +Fastest lap time in Hotring + +[STFT_20] +Fastest time for 'Cone Crazy' + +[HELP44] +Stop in the ~q~pink marker. + +[HELP45] +Press the ~h~~k~~PED_DUCK~~w~ button to duck. This will increase the accuracy of guns you are holding. + +[RCR1_5] +RC Bandit Race + +[RCPL1_7] +RC Baron Race + +[RCH1_11] +RC Raider Pickup + +[FEA_CTD] +Warning! This feature requires DTS compatible hardware to be connected. Proceed? + +[FEM_STE] +USE STEREO + +[GREET] +Greetings from... + +[LANCE_1] +Come on man, drive more careful! + +[LANCE_2] +Hey watch what you're doin! + +[LANCE_3] +Hey where are we goin' now? + +[LANCE_4] +What are we doin' now? + +[LAW4_15] +More money! + +[MERC_5] +Nice car, Mr. Vercetti. + +[MERC_26] +FASTER, FASTER, FASTER! + +[MERC_27] +Careful, Tommy, I only have a nose job last month. + +[MERC_28] +Tommy, drive careful. + +[MERC_29] +Tommy, go slower. + +[MERC_30] +Tommy, please kill someone other than me. + +[MERC_31] +Tommy, baby, don't kill me! + +[MERC_32] +Tommy, I'm glad you stole this car! + +[MERC_40] +I had so much fun. + +[MERC_43] +Adios, angel. + +[MERC_44] +You keep working out, now, you hear. + +[MERC_45] +Ciao, beautiful. + +[COL5_17] +Oh my god they've got a helicopter! + +[COL5_18] +Shoot the helicopter! + +[COL5_19] +Tommy, take that chopper out! + +[COL5_20] +He's coming again! Blow that chopper! + +[COL5_21] +Look at the size of that chopper! + +[COL5_22] +Here he comes again! + +[FEA_DSM] +Warning! This savegame is set to use DTS. This requires DTS compatible hardware to be connected. Please select whether you want to proceed using DTS or STEREO output. + +[STFT_23] +Fastest time for Checkpoint Charlie + +[HELP50] +Press the ~h~~k~~PED_ANSWER_PHONE~~w~ button to set the camera behind you. + +[HELP51] +Press the ~h~~k~~PED_ANSWER_PHONE~~w~ button to set the camera behind you. + +[HELP52] +Press the ~h~~k~~PED_ANSWER_PHONE~~w~ button to set the camera behind you. + +[HELP53] +Press ~h~~k~~PED_CYCLE_WEAPON_LEFT~~w~ button or ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ button to cycle through your available weapons. + +[HELP46] +There are eight different types of weapon. + +[HELP47] +You can carry one of each type of weapon at a time - one type of pistol, one type of shotgun. + +[HELP54] +~w~Cost: $~1~ ~r~Buying this will replace your current weapon. + +[HELP2A2] +Press the ~h~~k~~PED_SPRINT~~w~ button when running to ~h~sprint + +[HLPSN_A] +The sniper rifle allows you to zoom in and fire accurately at targets from a distance. + +[HLPSN_B] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ ~w~button to ~h~target~w~ with the sniper rifle. + +[HLPSN_C] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ ~w~button to ~h~target~w~ with the sniper rifle. + +[HLPSN_D] +Press the ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ 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. + +[HLPSN_E] +Press the ~h~~k~~PED_FIREWEAPON~~w~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HLPSN_F] +Press the ~h~~k~~PED_FIREWEAPON~~w~ button ~w~to ~h~fire~w~ the sniper rifle. + +[HLPSN_G] +Press the ~h~~k~~PED_FIREWEAPON~~w~ button to ~h~fire~w~ the sniper rifle. + +[PLANE_H] +Use the ~h~~k~~VEHICLE_ACCELERATE~~w~ button to accelerate, Left and right to turn. + +[PLANE_4] { reVC update } +{ Use the ~h~~k~~VEHICLE_ACCELERATE~~w~ button to accelerate, Left and right to turn. } +Use the right analog stick to accelerate, pull back on the left analog stick to climb, push forwards to descend. Left and right to turn. + +[HELP55] +Press the ~h~~k~~PED_FIREWEAPON~~w~ button to attack the chef. + +[STPR_8] +Pole Position Club + +[STPR_9] +3321 Vice Point + +[STPR_10] +Links View Apartment + +[STPR_11] +El Swanko Casa + +[STPR_12] +1102 Washington Street + +[STPR_13] +Ocean Heights Apartment + +[STPR_14] +Skumole Shack + +[STPR_15] +Hyman Condo + +[RCCANX] +~r~RC plane cancelled. + +[CLT_HL2] +When a Clothes Pickup is collected, a one star or two star wanted level will be cleared. + +[CRED009] +MISSION DESIGN + +[CRED359] +LEE JOHNSON + +[CRED360] +HENDRIK LESSER + +[CRED361] +PASQUALE STACCHIOTTI + +[CRED362] +ENRIQUE FERNANDEZ + +[CRED363] +PAUL BYERS + +[CRED364] +MIKE EMENY + +[CRED365] +ROB DUNKIN + +[CRED366] +CHARLIE KINLOCH + +[CRED367] +KEVIN HOBSON + +[CRED368] +JIM CREE + +[MOB_66A] +Tommy, Tommy why you coming back here for? + +[MOB_66B] +I tell you we don't want to see you around here no more. + +[MOB_67A] +Tommy, me thinks you should be staying away, you hear? + +[MOB_67B] +The Haitian boys not too 'appy with you. + +[MOB_18A] +Tommy, it's Paulo, how are you? Right mate, anyway, thought I had to drop you a line. + +[MOB_18B] +Oh my good lord, my son, you will not believe the quality of the brass I just encountered. + +[MOB_18C] +Street walker or something, just down in Little Havana, mate. + +[MOB_18D] +Said her name was Mercedes or something. + +[MOB_18E] +Oh my god, mate. You gotta check this bird out. + +[MOB_18F] +Could strip the lead out of a pencil. Said I was the best she ever had and all. + +[MOB_18G] +Keep you potatoes skinned for her. Be seeing you. + +[MOB_72A] +Tommy, it's me, Lance. Keep your mouth shut there Tommy, 'cause I ain't got time to talk. + +[MOB_72B] +I ain't interested in what you got to say. Why should I be? You don't care about me, do you? + +[MOB_72C] +You gotta look after me a bit better. Give me a fair slice. You know... + +[MOB_72D] +Tommy... oh, look, man, I'm sorry. It's just that... + +[MOB_72E] +People patronize me all my life, treat me like a little kid. + +[MOB_72F] +My brother would do that. Please, man, don't do that. + +[MOB_72G] +I gotta go. + +[MOB_63A] +Tommy, it's Earnest. Earnest Kelly. + +[MOB_63B] +How are you? + +[MOB_63C] +I'm doing okay. I'll need a stick to walk, but I should be back at work soon enough. + +[MOB_63D] +Good. + +[MOB_63E] +I heard about Lance. What a little prick, eh? + +[MOB_63F] +Yes. + +[MOB_63G] +Never trust a man who walks the streets in his pajamas. That's what I say. Glad you killed him. I hope it was painful for the prick. + +[MOB_63H] +I think it was. I just didn't think he was like that... + +[MOB_63I] +Tommy, for a raging lunatic, you're pretty naive. I'll be back at work soon, teach you a thing or two about life, you hear. + +[MOB_63J] +Take your time, Earnest. Look after yourself. + +[MOB_16A] +Tommy, Paulo here, que pasa amigo? + +[MOB_16B] +What do you want Paul? I don't want any fake label clothes. + +[MOB_16C] +Very funny, mate, but you know I don't touch bent gear. Nah, I was just calling to see if I get a part in one your movies. + +[MOB_16D] +Back in England I did a lot of blue stuff, mate. I'm packing more heat than you, my son. + +[MOB_16E] +Paul, thanks for the offer, I'll bear it in mind. + +[MOB_16F] +Seriously, don't forget about me, after all I done for you. + +[MOB_16G] +That's what I'm trying to forget about. + +[MOB_17A] +Tommy Vercetti, how's it going, Mr. big shot? I hear all these things about you, some kind of player in town, now eh... + +[MOB_17B] +Paul, you're drunk. + +[MOB_17C] +Nah, you stupid prat, I ain't drunk. I only had a couple and some treats, ain't been to bed for a couple of days, you know. + +[MOB_17D] +Anyway, don't give me that. I ain't a mug. Who set you up in this town? Who? Me. That's who. + +[MOB_17F] +Really? + +[MOB_17G] +Don't give me that. Don't! I introduced you to people. I showed you the ropes, did a lot of stuff for you, and this is how you repay me. + +[MOB_17H] +You ignore me. You won't give me a way in, after all I did for you! What do you think I am? A div or something? + +[MOB_17I] +Paul, take it easy. I've been busy, don't be an idiot. + +[MOB_17J] +I ain't no idiot, mush. That's what they said in borstal. Are you asking for trouble son, because you're going to get it! + +[MOB_17K] +Tommy, mate. Please. You was my big hope! Please, don't laugh at me! + +[MOB_17L] +Paul, get some sleep, seriously. + +[MOB_73A] +Tommy, it's Steve. + +[MOB_73B] +Hey, Steve. + +[MOB_73C] +Hey indeed, genius. You're a marvel! I'm a marvel! They love us. We are re-writing the record books, pal. + +[MOB_73D] +We are talking major awards here. Finally, I can put my dad in a home an tell him to shut up. + +[MOB_73E] +Eeer, that's cool, Steve. + +[MOB_73F] +Cool? It's hot, man. Hot. H. O. T. He never believed in me. Never thought I was an artist, and now I've made it. + +[MOB_73G] +I'm the best damn skin flick director of all time, my friend. I just wanted to say, it's a pleasure to have met you. + +[MOB_73H] +Thanks steve. + +[MOB_73I] +I love you, baby. Don't go changing on me, you hear. + +[MOB_73J] +I hear you. Good bye, Steve. + +[BOLLOX] +Press the ~h~~k~~VEHICLE_HANDBRAKE~ ~w~button to drop a bomb. Press the ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~button to cancel. + +[BRID_OP] +Storm warning over, all bridges to the mainland are now open. + +[BRID_CL] +Storm warning: All bridges to the mainland are closed. + +[LG_38] +Target + +[ASSET_C] +POLE POSITION ASSET COMPLETED! + +[ASSET_D] +~g~The Pole Position Club will now generate revenue up to a maximum of $~1~ per day. Pick up your cash regularly! + +[ST_WHEE] +Longest Wheelie time (secs) + +[ST_STOP] +Longest Stoppie time (secs) + +[ST_2WHE] +Longest 2 wheels time (secs) + +[ST_WHED] +Longest Wheelie distance (m) + +[ST_STOD] +Longest Stoppie distance (m) + +[ST_2WHD] +Longest 2 wheels distance (m) + +[OUTFT11] +Tracksuit + +[OUTFT12] +Frankie + +[RELOAD] +~g~You have won the fast reload ability! + +[APACHE] +Hunter delivered to helipad in Ocean Beach. + +[CRED369] +JOHN MCCARDLE + +[CRED370] +DAVID MURDOCH + +[CRED371] +CHRIS BROWN + +[CRED372] +PAUL GREEN + +[CRED373] +KYLE MILNE + +[CRED374] +KEVIN YUN + +[CRED375] +ERICK COBBS + +[CRED376] +RANDY BLAKE + +[CRED377] +BRANDON LIM + +[CRED378] +BRANDON FENOL + +[CRED379] +MICHAEL MANOLE + +[CRED380] +ALETHEIA SIMONSON + +[CRED381] +JOHN JANSEN + +[CUNTY] +New clothes delivered to the Vercetti Estate! + +[GOODBOY] +$50 Good Citizen Bonus! + +[NEWCONT] +New ~h~Contact Point ~w~opened at the marina in Ocean Beach!! + +[FIRELVL] +Fire Truck Mission level ~1~ + +[FEM_RET] +RETRY + +[FESZ_QR] +Are you sure you want to start a new game? All progress will be lost. Proceed? + +[SEG3_1] +TIME: + +[HELP56] +Press the ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ button to change camera modes. + +[HELP57] +Press the ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ button to change camera modes. + +[HELP58] +While targeting you can press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button or ~k~~PED_CYCLE_TARGET_RIGHT~ ~w~button to cycle through targets. + +[HELP59] +While targeting you can press the ~h~~k~~PED_CYCLE_TARGET_LEFT~ button or ~k~~PED_CYCLE_TARGET_RIGHT~ ~w~button to cycle through targets. + +[HELP60] +If you press the ~w~~k~~PED_SPRINT~ ~w~button while car jacking, you will not enter that vehicle. + +[HELP61] +You now have limitless ammo and double health on all vehicles. + +[FEC_LB1] +Look + +[FEC_LB2] +behind + +[FEC_LB3] +Look behind + +[FEC_R3] +(R3 button) + +[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 + +[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~ + +[FEC_WAR] +Warning + +[FEC_OKK] +O.K. + +[FEC_DLF] +Delete Failed. + +[FEC_SVU] +Save Unsuccessful. + +[FEC_LUN] +Load Unsuccessful. File Corrupted, Please delete. + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FES_CSA] +Select a skin from the list below: + +[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_DSN] +Default Player Skin.bmp + +[FET_RSO] +ORIGINAL SETTING RESTORED + +[FET_RSC] +HARDWARE NOT AVAILABLE - ORIGINAL SETTING RESTORED + +[FEA_3DH] +AUDIO HARDWARE + +[FEA_SPK] +SPEAKERS CONFIGURATION + +[FEM_LOD] +DRAW DISTANCE + +[FEM_VSC] +FRAME SYNC + +[FEM_FRM] +FRAME LIMITER + +[FEM_MM] +MAIN MENU + +[FED_RES] +SCREEN RESOLUTION + +[FET_CTL] +CONTROLLER SETUP + +[FET_OPT] +OPTIONS + +[FEC_MSH] +MOUSE SENSITIVITY + +[FEC_IVV] +INVERT MOUSE VERTICALLY + +[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 + +[FEC_SPC] +SPC + +[WIN_TTL] +Grand Theft Auto VC + +[WIN_95] +Grand Theft Auto VC cannot run on Windows 95 + +[WIN_DX] +Grand Theft Auto VC requires at least DirectX version 8.1 + +[FET_EIG] +CANNOT SET A CONTROL FOR THIS ACTION + +[FET_DAM] +DYNAMIC ACOUSTIC MODELING + +[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? + +[FET_QG] +QUIT GAME + +[FEN_STA] +START GAME + +[FET_PAU] +PAUSE MENU + +[REPLAY] +REPLAY + +[FEC_ANS] +Action + +[CVT_MSG] +Converting textures to optimal format for your video card + +[FEC_SFT] +SHIFT + +[FEH_VMP] +VIEW MAP + +[FES_DEE] +Delete Failed! Please try again. + +[FES_CMP] +Save failed! Please try again. + +[FESZ_WR] +Saving current game. Please wait... + +[FELD_WR] +Loading game. Please wait... + +[FEDL_WR] +Deleting saved game. Please wait... + +[PCRESRT] +Starting new game. Please wait... + +[FET_STI] +Standard Controls + +[FET_CTI] +Classic Controls + +[FET_PS] +PLAYER SKIN SETUP + +[FEH_NA] +OPTION NOT AVAILABLE + +[FEH_MPH] +MOUSE, CURSORS TO MOVE - PGUP, PGDN, MSWHEEL TO ZOOM, L - LEGEND + +[FEA_MP3] +MP3 PLAYER + +[NO_PCCD] +Please insert your GTA Vice City CD, or press ESC to cancel + +[FEH_SSA] +CURSORS TO MOVE - S TO SAVE TO FILE + +[FES_CMI] +LAST MISSION PASSED + +[FET_STS] +STATS SAVED TO 'STATS.HTML' + 'STATS.TXT' + +[WIN_VDM] +Grand Theft Auto VC cannot find enough available video memory + +[FEC_ERI] +Error! One or more control actions are not bound to a key or button. Please check all control actions are set + +[FEC_TFU] +Turret + Lean Up + +[FEC_TFD] +Turret + Lean Down + +[FET_RIG] +SELECT A NEW CONTROL FOR THIS ACTION + +[FEA_NM3] +NO MP3 FILES FOUND + +[FEA_MPB] +MP3 VOLUME BOOST + +[FEA_MUS] +MUSIC VOLUME + +[FEA_SFX] +SFX VOLUME + +[CVT_ERR] +You have run out of disk space. Please make some space on your harddisk before continuing. Press ESC to cancel. + +[FEA_ADP] +AUTO-DETECT HARDWARE + +{=================================== MISSION TABLE AMBULAE ===================================} + +[ATUTOR2:AMBULAE] +~g~Drive the patients to Hospital CAREFULLY. Each bump reduces their chances of survival. + +[A_FULL:AMBULAE] +~r~Ambulance full!! + +[A_FAIL2:AMBULAE] +~r~Your lack of urgency has been fatal to the patient! + +[A_FAIL3:AMBULAE] +~r~The patient is dead!! + +[A_PASS:AMBULAE] +Rescued! + +[A_COMP2:AMBULAE] +You will never get tired! + +[A_CANC:AMBULAE] +~r~Paramedic mission cancelled! + +[A_COMP3:AMBULAE] +Paramedic missions complete! You will never get tired when running! + +[ALEVEL:AMBULAE] +Paramedic Mission Level ~1~ + +[A_FAIL1:AMBULAE] +Paramedic mission ended. + +[A_SAVES:AMBULAE] +PEOPLE SAVED: ~1~ + +[A_COMP1:AMBULAE] +Paramedic missions complete: $~1~ + +{=================================== MISSION TABLE ASSIN1 ===================================} + +[ASM1_5:ASSIN1] +~r~Your target completed his deliveries! + +[ASM1_6:ASSIN1] +Deliveries left: + +[ASM1_7:ASSIN1] +~g~Carl Pearson, Pizza Delivery Man. Kill him before he completes his deliveries. + +[ASM1_A:ASSIN1] +Mr. Teal, your help in eradicating those out-of-towners was invaluable to business. I have more work for you with a more 'hands-on' approach. + +[ASM1_D:ASSIN1] +Mr. Teal, your help in eradicating those out-of-towners was invaluable to business. + +{=================================== MISSION TABLE ASSIN2 ===================================} + +[ASM2_1:ASSIN2] +~g~Mrs. Dawson will be leaving the Jewelry shop in Vice Point soon. Kill her. It must look like a car accident. + +[ASM2_3:ASSIN2] +~g~She's gonna blow! Get out of there! + +[ASM2_4:ASSIN2] +~r~You smashed up her car when she wasn't in it! She won't touch it now! + +[ASM2_5:ASSIN2] +~r~She got away! + +[ASM2_6:ASSIN2] +~r~You were too near the scene of the 'accident'! + +[ASM2_7:ASSIN2] +~r~It's supposed to look like an accident! Try ramming her off the road instead! + +[ASM2_8:ASSIN2] +~g~You must make Mrs. Dawson's death look like an accident. Do not use any weapons. + +[ASM2_9:ASSIN2] +~g~You need some wheels for this job! + +[ASM2_10:ASSIN2] +~g~When her car bursts into flames make your self scarce! + +[ASM2_11:ASSIN2] +Help me! + +[ASM2_12:ASSIN2] +Somebody help me! + +[ASM2_13:ASSIN2] +Oh my god! + +[ASM2_A:ASSIN2] +My compliments on a job well done Mr. Teal. My client was very pleased. + +[ASM2_2:ASSIN2] +health: + +{=================================== MISSION TABLE ASSIN3 ===================================} + +[ASM3_11:ASSIN3] +TIME: + +[ASM3_C:ASSIN3] +A European gang plans to hit a bank in Vice City. My employers would rather this didn't happen. + +[ASM3_D:ASSIN3] +Each member of the gang has a cover while they are here in Vice City. Some have menial jobs, others are on vacation. + +[ASM3_E:ASSIN3] +Each target and their likely whereabouts are taped under the phone. + +[ASM3_14:ASSIN3] +~g~Dick Tanner is working for DBP Security in Ocean Drive. + +[ASM3_15:ASSIN3] +~g~Marcus Hammond and Franco Carter are located near the Jewelry shop in Vice Point. + +[ASM3_16:ASSIN3] +~g~Nick Kong is cruising off Washington Beach. + +[ASM3_18:ASSIN3] +~g~Don't get too close to the target or he may spot you! + +[ASM3_19:ASSIN3] +~g~He's seen you! Waste him! + +[ASM3_20:ASSIN3] +~g~They have seen you! Make sure you waste them both! + +[ASM3_21:ASSIN3] +~r~You did not kill all the gang members in time! + +[ASM3_22:ASSIN3] +~g~Don't get too close to the targets or they may spot you! + +[ASM3_12:ASSIN3] +~g~A selection of weapons have been left for you nearby if you require them. You have ~h~9 MINUTES ~g~to kill all members of the gang. + +[ASM3_13:ASSIN3] +~g~Mike Griffin is working on an advertising board in Washington. + +[ASM3_17:ASSIN3] +~g~Charlie Dilson is riding in Washington. + +{=================================== MISSION TABLE ASSIN4 ===================================} + +[ASM4_10:ASSIN4] +~g~Looks like you're not the only one after the briefcase! Get it to Ammu-Nation, quick time! + +[ASM4_12:ASSIN4] +Distance: + +[ASM4_15:ASSIN4] +~g~Get the sniper rifle to your right. + +[ASM4_16:ASSIN4] +~g~Watch the woman on the balcony, she will walk down the escalators and ask someone the time. + +[ASM4_17:ASSIN4] +~g~Once the conversation has finished kill the person she spoke to, but do not kill her. + +[ASM4_18:ASSIN4] +~g~Once the target is dead retrieve his briefcase and take it to Ammu-Nation in Downtown. + +[ASM4_19:ASSIN4] +~g~Keep your distance from the target! Use the distance bar in the upper right corner of the screen. + +[ASM4_20:ASSIN4] +~g~If it gets full he will see you. + +[ASM4_21:ASSIN4] +~g~Get the briefcase! + +[ASM4_22:ASSIN4] +~g~Take the briefcase to Ammu-Nation in Downtown. + +[ASM4_23:ASSIN4] +~g~He has spotted you! Nail him and get the briefcase! + +[ASM4_25:ASSIN4] +~r~You killed the woman you fool! + +[ASM4_26:ASSIN4] +~r~The target has boarded his flight! + +[ASM4_27:ASSIN4] +~r~The target has seen you! You should have kept your distance! + +[ASM4_28:ASSIN4] +~r~The target heard you firing your weapon! + +[ASM4_29:ASSIN4] +~r~Itchy trigger finger? You killed him too soon! + +[ASM4_A:ASSIN4] +Time to fry bigger fish, Mr. Teal. There's a rifle in the foliage to your right. + +[ASM4_B:ASSIN4] +Watch the woman standing on the balcony above the check-in desks. She will walk through the crowd and ask someone the time. + +[ASM4_C:ASSIN4] +You must kill that person, retrieve his case and take it to the location taped under the phone. + +{=================================== MISSION TABLE ASSIN5 ===================================} + +[ASM5_A:ASSIN5] +There is a valuable exchange taking place on the roof of the Cherry Popper Ice Cream Company. + +[ASM5_B:ASSIN5] +Kill everyone involved, steal the merchandise and take it to the helipad at the airport. + +[ASM5_C:ASSIN5] +There is a gate to your left that leads to the back of the factory. + +[ASM5_1:ASSIN5] +~g~Enter the compound behind the Cherry Popper Ice Cream Company The deal is taking place on the roof. + +[ASM5_2:ASSIN5] +~g~Get the merchandise and take it to the helipad at the airport! + +[ASM5_3:ASSIN5] +~g~Take the merchandise to the helipad at the airport! + +{=================================== MISSION TABLE BANKJ1 ===================================} + +[WANTED1:BANKJ1] +~g~Shake the cops and lose your wanted level! + +[BJM1_A:BANKJ1] +Tommy! Hey, Tommy, look at this, this is great! I've got us this minibar installed! + +[BJM1_B:BANKJ1] +We got a whole bar downstairs, Ken. + +[BJM1_C:BANKJ1] +Yeah, yeah, whatever. Well, I got the chalkboard you asked for. + +[BJM1_D:BANKJ1] +Ah, that's the benefit of a law school education: the ability to follow instructions. + +[BJM1_E:BANKJ1] +Now, I need a safe man. + +[BJM1_F:BANKJ1] +Oh, all right, well, let me think...safe, safe, safe, safe - I got it! This guy will blow you away! + +[BJM1_G:BANKJ1] +Ahh, nah, that schmuck. He's on the inside. + +[BJM1_H:BANKJ1] +Where inside? + +[BJM1_I:BANKJ1] +In a police headquarter cell awaiting transfer. + +[BJM1_J:BANKJ1] +I think he's about to get paroled.... + +[BJM1_1:BANKJ1] +~g~Break Cam Jones out of police custody! + +[BJM1_3:BANKJ1] +~g~You will find something useful in the station's locker room. + +[BJM1_21:BANKJ1] +~g~The key card to the cells can be found upstairs in the station. + +[BNK1_7:BANKJ1] +Cam Jones? + +[BNK1_8:BANKJ1] +I'm busting you out! + +[BNK1_10:BANKJ1] +Yeah, that's me.. + +[BNK1_11:BANKJ1] +Whatever you say! + +[BNK1_13:BANKJ1] +I'm gonna be doing a job and you're my safe cracker. + +[BNK1_14:BANKJ1] +Beats losing my ass in a cell! + +[BJM1_22:BANKJ1] +~g~Get Cam back to his house! + +[BJM1_23:BANKJ1] +~g~You need to get the key card first! + +[BNK1_12:BANKJ1] +Lose the heat and get me back to my place! + +[BJM1_20:BANKJ1] +Put the weapon away or face the consequences! + +[BJM1_5:BANKJ1] +Only authorized personnel beyond this point! + +[BJM1_2:BANKJ1] +~r~You were supposed to bust Cam out, not get him killed! + +[BJM1_4:BANKJ1] +He's armed! Kill him! + +{=================================== MISSION TABLE BANKJ2 ===================================} + +[BJM2_A:BANKJ2] +We need a stick up man. You know one? + +[BJM2_B:BANKJ2] +Hey, Tommy, Tommy, Tommy, this stuff keeps you sharp, man. + +[BJM2_C:BANKJ2] +WoooOOOooo! + +[BJM2_D:BANKJ2] +I could be your stick up man! Stick 'em up! Stick 'em up! + +[BJM2_E:BANKJ2] +You ain't a stick up man, you're an idiot. + +[BJM2_F:BANKJ2] +Now get yourself a drink and shut up. + +[BJM2_G:BANKJ2] +Hey, get outta my way! Yeh yeh yeh - ow ow ow! + +[BJM2_H:BANKJ2] +Cam, what do you think? + +[BJM2_I:BANKJ2] +Well, the best shooter in this town is a guy named Cassidy. + +[BJM2_J:BANKJ2] +Is that so? + +[BJM2_K:BANKJ2] +Yeah. A military guy, or he thinks he is. + +[BJM2_L:BANKJ2] +I doubt he was ever in the army, but he certainly knows how to get a hold of guns. + +[BJM2_M:BANKJ2] +He'll be down at the shooting range. + +[BJM2_2A:BANKJ2] +You Phil Cassidy? + +[BJM2_2B:BANKJ2] +Why? + +[BJM2_2C:BANKJ2] +I'm looking for a man who can handle a gun. From this setup, I'm not too convinced. + +[BJM2_2D:BANKJ2] +Son, I could shoot a fly off your head at 80 feet. + +[BJM2_2E:BANKJ2] +Oh really? + +[BJM2_2F:BANKJ2] +Yeah. I learnt in the army. + +[BJM2_2G:BANKJ2] +Fly shooting real popular in the army? Glad I don't pay tax. + +[BJM2_2H:BANKJ2] +You tryin' to be funny kid? + +[BJM2_2I:BANKJ2] +Ha ha ha ha ha! + +[BJM2_2J:BANKJ2] +Let's shoot. + +[BJM2_1:BANKJ2] +~g~Go to Ammu-Nation in Downtown and talk to Phil Cassidy. + +[BJM2_3:BANKJ2] +HIT RATE: ~1~% + +[BJM2_4:BANKJ2] +SCORE ROUND ONE: ~1~ + +[BJM2_6:BANKJ2] +SCORE ROUND TWO: ~1~ + +[BJM2_7:BANKJ2] +TOTAL SCORE FOR SHOOT: ~1~ + +[BJM2_9:BANKJ2] +~g~Get to round two's starting point. + +[BJM2_11:BANKJ2] +~r~Phil's dead! + +[BJM2_12:BANKJ2] +~r~One of the shooters is dead! + +[BJM2_14:BANKJ2] +~g~Move on to the next area! + +[BJM2_15:BANKJ2] +SCORE: + +[BJM2_17:BANKJ2] +~g~Go and talk to Phil. + +[BJM2_18:BANKJ2] +SCORE TO BEAT: + +[BJM2_19:BANKJ2] +~g~Hit as many targets as you can in the time limit! + +[BJM2_22:BANKJ2] +~r~You have left the shooting range! + +[BJM2_23:BANKJ2] +~g~If you leave the shooting range during the competition, you will fail the mission. + +[BJM2_24:BANKJ2] +~g~The closest target is worth one point. + +[BJM2_25:BANKJ2] +~g~The middle target is worth two points. + +[BJM2_27:BANKJ2] +~g~All targets this round are worth one point. + +[BNK2_2:BANKJ2] +AIM 3-2-1 FIRE! + +[BNK2_3:BANKJ2] +AREA CLEAR! + +[BNK2_4:BANKJ2] +Hoooeee! + +[BNK2_5:BANKJ2] +Couldn't hit a barn door! + +[BNK2_7:BANKJ2] +So you wanna do me a favor, and help me put together a job? + +[BNK2_8:BANKJ2] +Son, after shooting like that, if you asked me to be your wife, I'd say yes. + +[BNK2_9A:BANKJ2] +Son, you better get your fancy talking and big ideas and shove 'em where there ain't no sun. You can't shoot nothin'! + +[BNK2_9B:BANKJ2] +You can't shoot nothin'. + +[BJM2_28:BANKJ2] +SCORE ROUND THREE: ~1~ + +[BJM2_20:BANKJ2] +~g~When you run out of ~w~time ~g~or ~w~ammunition ~g~the round is over! + +[BJM2_26:BANKJ2] +~g~The far target is worth three points. + +[BNK2_1:BANKJ2] +LIVE AMMUNITION + +[RANGE_1:BANKJ2] +SCORE FOR SHOOT: ~1~ + +[BJM2_2:BANKJ2] +~g~To exit the round press the ~h~~k~~PED_JUMPING~ ~g~button. + +[BJM2_N:BANKJ2] +Relax + +{=================================== MISSION TABLE BANKJ3 ===================================} + +[BJM3_A:BANKJ3] +Things are starting to come together nicely here. + +[BJM3_B:BANKJ3] +What's the plan, Tommy? Que pasa, amigo? + +[BJM3_C:BANKJ3] +The plan is you keep doing that like a moron. Anyhow, we need a driver. + +[BJM3_D:BANKJ3] +Tommy, I'll do it. I can drive. + +[BJM3_E:BANKJ3] +You want Hilary, mister. Not some smart-talking law school chump. + +[BJM3_F:BANKJ3] +Hilary's the real deal. You ain't never seen anyone drive so fast. I'll give him a call here. + +[BJM3_G:BANKJ3] +Hey Hil, it's Phil. How's it going? No. don't talk. We'll reminisce later. You want to do me a favor? + +[BJM3_H:BANKJ3] +I got me a guy from up north. No, no, I don't think he was in the service, but he wants a driver. + +[BJM3_I:BANKJ3] +For a bit of action. Okay, I understand. + +[BJM3_J:BANKJ3] +What'd he say? + +[BJM3_K:BANKJ3] +Well, he'll do it, no problem. Well, there might be a little problem - see, he has abandonment issues. + +[BJM3_L:BANKJ3] +Seems he won't work for anyone who can't beat him. Something to do with his momma. + +[BJM3_M:BANKJ3] +Anyway, he wants to race you first, said he'd meet you outside.. + +[BJM3_2A:BANKJ3] +You Tommy? Of course you're Tommy, I mean, + +[BJM3_2B:BANKJ3] +Why else would anyone want to speak to me? + +[BJM3_2C:BANKJ3] +OK. Consider it this way - + +[BJM3_2D:BANKJ3] +I'll drive for you IF, and only IF, you can drive properly. + +[BJM3_2E:BANKJ3] +Leave me alone - and I'll never forgive you. + +[BJM3_2:BANKJ3] +~r~Hilary is dead! + +[BJM3_4:BANKJ3] +~g~You need a car to take part! + +[BNK3_1:BANKJ3] +Ok. I'll drive for you, but please, treat me bad. + +[BNK3_3A:BANKJ3] +Illegal street race in progress at Vice Point. + +[BNK3_3B:BANKJ3] +Calling all officers. + +[BNK3_3C:BANKJ3] +Street racers, this is illegal and forbidden! + +{=================================== MISSION TABLE BANKJ4 ===================================} + +[BNK4_A:BANKJ4] +~w~As you can see, gentlemen, this is going to be the easiest buck we ever made. + +[BNK4_B:BANKJ4] +~w~Tommy, seriously, you gotta consider going into law. + +[BNK4_C:BANKJ4] +~w~What the hell are you smoking, man? This ain't no simple plan! + +[BNK4_D:BANKJ4] +~w~Well, who needs a simple plan anyway? + +[BNK4_E:BANKJ4] +~w~Take communism, now that was a simple plan. Didn't do Russia any favors, huh? + +[BNK4_F:BANKJ4] +~w~Calm down, all right? With a team like this it's going to be no problem. + +[BNK4_G:BANKJ4] +~w~We got Cam on safe. Phil? You and me will handle security, and Hilary'll drive the getaway car. + +[BNK4_H:BANKJ4] +~w~Uh, heh heh, aren't you forgetting somebody? Somebody who helped you to no end in this town? Somebody who... + +[BNK4_I:BANKJ4] +~w~Ken... Ken, that's right. Ken here, he washes the money for us and he keeps the drinks on ice. + +[BNK4_J:BANKJ4] +~w~I don't understand what I am supposed to be doing here. + +[BNK4_K:BANKJ4] +~w~Look, it's easy. Haven't you ever seen a movie? + +[BNK4_L:BANKJ4] +~w~We walk into the bank, we wave the gun around, and leave very rich men. + +[P_DEAD:BANKJ4] +~r~Phil's dead!! + +[C_DEAD:BANKJ4] +~r~Cam's dead!! + +[H_DEAD:BANKJ4] +~r~Hilary's dead!! + +[P_HIND:BANKJ4] +~r~You've lost Phil! + +[C_HIND:BANKJ4] +~r~Cam's been left behind! + +[H_HIND:BANKJ4] +~r~Hilary's been abandoned! + +[GETCAR:BANKJ4] +~g~Get in the getaway car to do the bank job! + +[TRASHED:BANKJ4] +~r~YOU TRASHED THE GETAWAY CAR!! + +[BNK4_1:BANKJ4] +I'll drive. + +[BNK4_2:BANKJ4] +Great. A passenger. Wait 'til I tell the group about this. + +[BNK4_3A:BANKJ4] +Hey, watch the wheels, Tommy! + +[BNK4_3B:BANKJ4] +Tommy, Hilary's taking up too much room! + +[BNK4_3C:BANKJ4] +I am not! + +[BNK4_3D:BANKJ4] +Are too! + +[BNK4_3E:BANKJ4] +Hey, shut up you two, or you can get out and walk. + +[BNK4_3F:BANKJ4] +Yeah - HILARY. + +[BNK4_3I:BANKJ4] +For god's sake, Phil, stop waving that thing around! + +[BNK4_3J:BANKJ4] +Yeah, you'll have somebody's eye out! + +[BNK4_3M:BANKJ4] +The car's ruined. RUINED! + +[BNK4_3O:BANKJ4] +You cling to an illusion of permanence. + +[BNK4_3P:BANKJ4] +What? + +[BNK4_3Q:BANKJ4] +You think all things will last. + +[BNK4_3R:BANKJ4] +Youth, loved ones, pizza, + +[BNK4_3S:BANKJ4] +All will pass or end and you must accept that. + +[BNK4_3T:BANKJ4] +You're right. Thanks, Cam. + +[BNK4_3U:BANKJ4] +Don't mention it. + +[BNK4_3V:BANKJ4] +Hey Tommy, why have we stopped? + +[BNK4_4A:BANKJ4] +~w~Keep driving around the block, OK? + +[BNK4_5:BANKJ4] +~w~Okay, Tommy, Okay. + +[BNK4_6:BANKJ4] +~w~THIS IS A RAID! + +[BNK4_7:BANKJ4] +~w~NOBODY MOVE! + +[BNK4_8:BANKJ4] +~w~EVERYBODY UP AGAINST THAT WALL! + +[BNK4_9:BANKJ4] +Phil, hold down the fort! + +[BNK4_10:BANKJ4] +Wilco roger that! + +[BNK4_11:BANKJ4] +Come on Cam, the vault's upstairs... + +[BK4_12A:BANKJ4] +Damn! It's a Flange 9000! + +[BK4_12B:BANKJ4] +This could take hours to crack, + +[BK4_12C:BANKJ4] +Or five minutes if you could find the manager. + +[BNK4_13:BANKJ4] +I'll go find where he's holed up. + +[BK4_14A:BANKJ4] +Phil, things still sweet? + +[BNK4_15:BANKJ4] +Sure. Everything's reeaal quiet. + +[BNK4_16:BANKJ4] +You - you're coming with me! + +[BNK4_17:BANKJ4] +Ok! Ok! Just don't shoot! + +[BNK4_18:BANKJ4] +I SAID NOBODY MOVE! + +[BK4_19A:BANKJ4] +It's on a time lock, + +[BK4_19B:BANKJ4] +You might as well give up now! + +[BK4_20A:BANKJ4] +Hell, I can bypass the time lock, + +[BK4_20B:BANKJ4] +Then we just need your key code and we're good! + +[BNK4_21:BANKJ4] +Stay here. You try anything and you're dead. + +[BNK422A:BANKJ4] +Cam, how long? + +[BK4_23A:BANKJ4] +Give me 5 minutes! + +[BK4_24A:BANKJ4] +I'm gonna check on Phil, I'll be right back. + +[BK4_24B:BANKJ4] +I told you not to touch that alarm! + +[BNK4_25:BANKJ4] +The SWAT team will be here any minute! + +[BNK4_27:BANKJ4] +I could do with some help here, Tommy! + +[BNK4_28:BANKJ4] +Vice City S.W.A.T! You are completely surrounded! + +[BNK4_29:BANKJ4] +Surrounded? HA HA HA HAAAAAaaa! + +[BNK4_30:BANKJ4] +They're crapping themselves, corrupt bastards! + +[BK4_31A:BANKJ4] +Tommy! The vault's open! + +[BK4_34A:BANKJ4] +Ok, we got the SWAT retirement fund. Let's get out of here! + +[BK4_34B:BANKJ4] +Ok, you asked for it! You've had your last chance! + +[BK4_35A:BANKJ4] +They're storming the place! + +[BK4_35B:BANKJ4] +Take cover! + +[BNK4_94:BANKJ4] +~w~Ok, guys. Nice an easy just as we planned. + +[BM_DEAD:BANKJ4] +~r~You needed the bank manager alive!! + +[ASSET_A:BANKJ4] +BANK HEIST ASSET COMPLETED! + +[ASSET_B:BANKJ4] +~g~The Malibu Club will now generate revenue up to a maximum of $~1~ per day. Pick up your cash regularly! + +[IDIOT:BANKJ4] +~r~ That's right, just wander about dressed like a lunatic and draw attention to yourself, IDIOT! + +{=================================== MISSION TABLE BARON1 ===================================} + +[COK1_A:BARON1] +Come on, baby, go! Yeah! Yeah! Arrrrr! + +[COK1_B:BARON1] +Stupid horse! I'll chop your head off! Grrrrr... + +[COK1_C:BARON1] +Who is this dickhead? + +[COK1_D:BARON1] +Tommy Vercetti. You remember me. + +[COK1_E:BARON1] +Excuse me. I'm a little anxious. Never trust a goddamn horse! + +[COK1_F:BARON1] +You do a good job - you work for me now. + +[COK1_H:BARON1] +As I said, amigo, you work for me now. Shut up. Some Judas has betrayed me. + +[COK1_I:BARON1] +He thinks I don't know how much money I should be making, but stealing 3% is as good as stealing 100%. + +[COK1_J:BARON1] +No one does this to me. NO ONE!! + +[COK1_K:BARON1] +You follow him from his apartment and you see where he goes! Later, we will kill him. + +[COK1_1:BARON1] +Ooh shit! + +[COK1_2:BARON1] +Too slow grandad! + +[COK1_4:BARON1] +Loser. + +[COK1_5:BARON1] +You better keep on running, asshole! + +[COK1_8:BARON1] +~g~Quick! Grab some wheels and follow him! + +[COK1_9:BARON1] +~r~You're supposed to follow him, not kill him! + +[COK1_10:BARON1] +~g~Go to the thief's house and find out where he's stashing the money. + +[COK1_11:BARON1] +~g~Have a look through his window. + +[COK1_7:BARON1] +~g~He's escaped to the roof, keep on his tail but don't kill him! + +[COK1_G:BARON1] +I work for money. + +{=================================== MISSION TABLE BARON2 ===================================} + +[COK2_A:BARON2] +What kind of incompetent fool are you? + +[COK2_B:BARON2] +FOOL! FOOL! FOOL! FOOL! + +[COK2_C:BARON2] +Tommy! + +[COK2_D:BARON2] +What, Ricardo? + +[COK2_E:BARON2] +These idiots - they always trying to screw you. + +[COK2_F:BARON2] +That's the problem with this business. + +[COK2_G:BARON2] +What do you think you're doing? + +[COK2_H:BARON2] +These pricks have failed me miserably, + +[COK2_I:BARON2] +Soon any mom and pop will think they can sell gallo in Vice City. + +[COK2_J:BARON2] +What next, huh? The stinking Mafia?! + +[COK2_K:BARON2] +That gang place is a fortress at ground level, + +[COK2_L:BARON2] +so Quentin here - Quentin! QUENTIN! + +[COK2_M:BARON2] +He'll fly you over the area! + +[COK2_N:BARON2] +Eradicate them! + +[COK2_O:BARON2] +What do you think you're doing? + +[COK2_P:BARON2] +What are you doing here? + +[COK2_Q:BARON2] +Hey, I've been asking around and it's obvious + +[COK2_R:BARON2] +that Diaz jumped the deal and iced my brother. + +[COK2_S:BARON2] +And he'll kill you, too! + +[COK2_T:BARON2] +I can take Diaz! + +[COK2_U:BARON2] +No - listen to me! I'll handle Diaz - + +[COK2_1:BARON2] +One thing puzzling me, What's with 'Quentin!? + +[COK2_2:BARON2] +I dunno, I always kinda liked it...Quentin Vance... + +[COK2_3:BARON2] +Vance? Your name's Lance Vance? + +[COK2_4:BARON2] +Hey! I got enough of that at school! + +[COK2_5:BARON2] +You ever fired one of those from a whirly? + +[COK2_8:BARON2] +Where the hell are we headed anyway? + +[COK2_9:BARON2] +Prawn Island. + +[COK2_13:BARON2] +Lance Vance. Poor bastard. + +[COK2_14:BARON2] +Ok, we're almost there. + +[COK2_15:BARON2] +We'll make a couple of passes. + +[COK2_16:BARON2] +So take out as many guns as you can. + +[COK2_17:BARON2] +Then I'll set you down and you're on your way. + +[COK2_20:BARON2] +Damn! This is a war zone! Take out some of those gunmen! + +[COK2_21:BARON2] +We're taking hits here, man! + +[COK2_22:BARON2] +This thing ain't cheap to fix! Take them out! + +[COK2_23:BARON2] +Ok, you're on your own from here! Good luck, brother! + +[COK2_24:BARON2] +Heli Health: + +[COK2_25:BARON2] +~g~Go and collect the money on the roof. + +[COK2_27:BARON2] +You're on MY turf asshole! + +[COK2_28:BARON2] +You're going down! + +[COK2_6:BARON2] +No. I'll get a bit of practice on the way though. + +[OPEN_B:BARON2] +The road blocks to the mainland have now been removed + +[COK2_V:BARON2] +- he's beginning to trust me. + +{=================================== MISSION TABLE BARON3 ===================================} + +[COK3_A:BARON3] +Not so pleased with your selves NOW, huh! + +[COK3_B:BARON3] +Ahahahahaa, Ahahahahaa. + +[COK3_C:BARON3] +Whoa! Watch where you're waving that thing! + +[COK3_D:BARON3] +No more pigeon shit on MY car, eh Tommy! + +[COK3_E:BARON3] +Guess not. + +[COK3_F:BARON3] +You're damn right. Now listen, + +[COK3_G:BARON3] +you know who owns the fastest boat on the east coast? + +[COK3_H:BARON3] +Not off hand, no. + +[COK3_I:BARON3] +ME. And I want it to stay that way. + +[COK3_J:BARON3] +Every smuggler from here to Caracas has one dream, a faster boat. + +[COK3_K:BARON3] +Rumor has it the boatyard has just completed such a vessel. + +[COK3_L:BARON3] +for some Costa Rican dickhead. + +[COK3_M:BARON3] +And Tommy...I WANT THAT BOAT!!! + +[COK3_N:BARON3] +I think your pigeons are back. + +[COK3_O:BARON3] +Ah! I thought I got you. Where'd you come from? + +[COK3_P:BARON3] +Pigeons! Boom! Aaaaah! + +[COK3_5:BARON3] +~g~Find the switch to lower the boat. + +[COK3_6:BARON3] +~g~Get the boat to the mansion. + +[COK3_7:BARON3] +~r~You destroyed the boat! + +[COK3_8:BARON3] +~g~Go to the boatyard at the docks and steal the fastest boat. + +[COK3_9:BARON3] +~g~Now get into the boat. + +{=================================== MISSION TABLE BARON4 ===================================} + +[COK4_A:BARON4] +Eject! PLASTIC CRAP! + +[COK4_B:BARON4] +You doing this to me? + +[COK4_C:BARON4] +Who do you think you are, you piece of plastic SHIT? Aaarrgh! + +[COK4_D:BARON4] +SCREW YOU! + +[COK4_E:BARON4] +It eats my favorite El burro movie, it die! + +[COK4_F:BARON4] +What else could I do? + +[COK4_G:BARON4] +It's probably not plugged in. + +[COK4_H:BARON4] +What? + +[COK4_I:BARON4] +Damn - no matter, I can buy a hundred more. + +[COK4_J:BARON4] +Now Tommy, + +[COK4_K:BARON4] +each month a freelancer sails into Vice City and moors his yacht. + +[COK4_L:BARON4] +He sells his cargo to the first boat. + +[COK4_M:BARON4] +I want you to take the speedboat + +[COK4_N:BARON4] +and beat all the other shitheads to it, + +[COK4_O:BARON4] +then you bring the cargo here, ok!? + +[COK4_P:BARON4] +Let me guess, you thought I could use a guardian angel. + +[COK4_Q:BARON4] +I'm just saying you need to let me in there, my man. + +[COK4_T:BARON4] +and you're probably gonna wanna kiss me! + +[COK4_U:BARON4] +Wacko. + +[COK4_V:BARON4] +Hahahahahaha! + +[COK4_1:BARON4] +So Tommy, we know it was Diaz busted our deal.. + +[COK4_3:BARON4] +So why the hell are we running errands for him? + +[COK4_4:BARON4] +The more we learn now, the less we have to learn when we take this town over! + +[COK4_5:BARON4] +I like your style, man. Real fresh. + +[COK4_12:BARON4] +Watch yourself, they're coming from all over! + +[COK4_13:BARON4] +Got 'em. Head for Diaz's as fast as you can! + +[COK4_14:BARON4] +You want some of this?! + +[COK4_15:BARON4] +Sleep with the fish! + +[COK4_16:BARON4] +Eat it! EAT IT! + +[COK4_19:BARON4] +More trouble up ahead! + +[COK4_20:BARON4] +There are gunmen on that jetty! + +[COK4_24:BARON4] +Good shooting, my friend. You're a real, proper, grade A lunatic. + +[COK4_25:BARON4] +Well, thank you. + +[COK4_26:BARON4] +See you around, Tommy. + +[COK4_27:BARON4] +Okay, Mr. Lance Vance Dance. + +[COK4_28:BARON4] +~g~Get to the yacht before the other boats do! + +[COK4_31:BARON4] +~g~Go to the fastest boat at the jetty! + +[COK4_32:BARON4] +~r~Too slow! + +[COK4_33:BARON4] +~r~You destroyed the boat! + +[COK4_34:BARON4] +~g~Take those boats out! + +[COK4_35:BARON4] +Boat health: + +[COK4_R:BARON4] +Now you can feed me all this 'lonely tough guy' crap, + +[COK4_S:BARON4] +but I know one day I'm gonna save your ass, + +{=================================== MISSION TABLE BARON5 ===================================} + +[PROP_A:BARON5] +PROPERTY ACQUIRED! + +[COK4_30:BARON5] +~r~Lance is dead! + +[ASS1_A:BARON5] +I got us some cannons in the trunk. + +[ASS1_B:BARON5] +Holy shit! Where'd you get all this stuff? + +[ASS1_C:BARON5] +Been saving it for a rainy day. + +[ASS1_D:BARON5] +You like? + +[ASS1_E:BARON5] +Yeah, I like. + +[ASS1_F:BARON5] +You stupid pricks... + +[ASS1_G:BARON5] +my beautiful house + +[ASS1_H:BARON5] +look what you've done to it! + +[ASS1_I:BARON5] +This is for my brother! + +[ASS1_J:BARON5] +I trusted you, Tommy. + +[ASS1_K:BARON5] +I woulda had you made... + +[ASS1_L:BARON5] +Say goodnight, Mr. Diaz. + +[ASS1_1:BARON5] +This place is going to be crawling with assholes...be careful... + +[ASS1_2:BARON5] +Don't worry Tommy, I'll cover you. + +[ASS1_13:BARON5] +DIAZ?! I've come to take over your business! + +[ASS1_15:BARON5] +~g~Storm the mansion and kill Diaz! + +[ASS1_16:BARON5] +~g~Kill Diaz! + +[ASS1_17:BARON5] +~g~There are multiple routes into the mansion. + +[BUD1:BARON5] +Lances Health: + +[ASS1_18:BARON5] +~g~The door is locked, try another route. + +[ASS1_19:BARON5] +This way! + +[ASS1_14:BARON5] +TOMMY! You betrayed me, you idiot! I'm gonna kill you real soon.. + +[ASS1_20:BARON5] +Tommy, my problem with Quentin, not wit you, man! + +{=================================== MISSION TABLE BIKE1 ===================================} + +[BM1_A:BIKE1] +Where's Baker? + +[BM1_B:BIKE1] +I'm looking for Big Mitch Baker... + +[BM1_C:BIKE1] +Who's lookin'? + +[BM1_D:BIKE1] +Tommy Vercetti. + +[BM1_E:BIKE1] +Vercetti + +[BM1_F:BIKE1] +You don't look like the law, so that's bought you a minute. + +[BM1_G:BIKE1] +You better talk fast. + +[BM1_H:BIKE1] +Kent Paul said you might be interested in pulling security for a gig he's got set up. + +[BM1_I:BIKE1] +Kent Paul? Sheesh! No wonder he sent ya. + +[BM1_J:BIKE1] +The last time he was here he left through the window in nothing but his limey birthday suit. + +[BM1_K:BIKE1] +Are you interested or not? + +[BM1_L:BIKE1] +We only do favors for our own. + +[BM1_M:BIKE1] +How do I join? + +[BM1_N:BIKE1] +This ain't no country club, boy. Can you handle a bike? + +[BM1_O:BIKE1] +Can you sit on a stool and drink? + +[BM1_P:BIKE1] +Cougar, Zeppelin, go see how this girl handles a bike... + +[BM1_3:BIKE1] +~r~The racers have been attacked! + +[BIKE1_1:BIKE1] +All right, fancy clothes. Let's see what you can do. + +[BM1_1:BIKE1] +~g~Get a Freeway or an Angel and get to the starting grid. + +[BM1_2:BIKE1] +~g~You need a Freeway or an Angel to compete! + +{=================================== MISSION TABLE BIKE2 ===================================} + +[BM2_1:BIKE2] +CHAOSMETER: + +[BM2_A:BIKE2] +Ah, got ya again. + +[BM2_B:BIKE2] +Hey Vercetti. + +[BM2_C:BIKE2] +Cougar says you can handle a bike pretty good. + +[BM2_D:BIKE2] +Yeah, how many more errands am I gonna to have to run? + +[BM2_E:BIKE2] +I'm a very busy man. + +[BM2_F:BIKE2] +If it's a fight that's gonna settle this then bring it on. + +[BM2_G:BIKE2] +Being one of us ain't just about brawlin'. It's about being part of a family. + +[BM2_H:BIKE2] +Yeah, I've been part of a family before alright. It didn't work out. + +[BM2_I:BIKE2] +Yeah, right, but this family takes care of its own. + +[BM2_J:BIKE2] +We don't ask a man to do the dirty work and then let him do fifteen years hard time. + +[BM2_K:BIKE2] +Yeah, that's right. I've done my homework. + +[BM2_L:BIKE2] +This here's the biggest family of misfits, outcasts and badasses. + +[BM2_M:BIKE2] +Hell, some of us has even been betrayed by our own country. + +[BM2_N:BIKE2] +I was locked up during 'Nam. Ugly business. + +[BM2_O:BIKE2] +Which is why I'm gonna ask you to go mess with the man. + +[BM2_Q:BIKE2] +So get out there, grab a bike and show this city how pissed you are! + +[BM2_R:BIKE2] +Alright, alright. + +[BM2_3:BIKE2] +~g~This sound indicates that you have filled a part of the meter, continue to do so. + +[BM2_2:BIKE2] +~g~You must fill the Chaos Meter in the time given to show us how much of a badass you are! + +[BM2_4:BIKE2] +~r~You failed to fill the Chaos Meter in time! + +[BM2_P:BIKE2] +This whole damn country needs a kick in the ass, and we're the ones to deliver it. + +{=================================== MISSION TABLE BIKE3 ===================================} + +[BM3_A:BIKE3] +Hey there, Mitch. + +[BM3_B:BIKE3] +Well, if it ain't 'bad ass' Vercetti. + +[BM3_C:BIKE3] +Now I wanna see how good you can fight for your patch. + +[BM3_D:BIKE3] +A local street gang made the mistake of stealing my hog... + +[BM3_E:BIKE3] +probably because of some machismo thing or somethin'. + +[BM3_F:BIKE3] +Me and the boys would go over there and teach them a lesson in respect an'all. + +[BM3_G:BIKE3] +Anyways. + +[BM3_H:BIKE3] +Then I got to thinking - this would make a good initiation for you. + +[BM3_I:BIKE3] +You get my bike back, you can tell Paul he's got his security. + +[BM3_2:BIKE3] +~r~You were supposed to bring the bike back, not destroy it! + +[BM3_3:BIKE3] +~g~Get the bike back to the bar! + +[BM3_4:BIKE3] +~g~Get on the bike! + +[INTRUDE:BIKE3] +~g~You've been spotted! + +[BM3_6:BIKE3] +~g~They are holed up behind Ammu-Nation in the Downtown area. + +[BM3_7:BIKE3] +~g~You will need a fast bike to gain access to the roof. + +[BM3_8:BIKE3] +~g~Use the bike to jump from these stairs to the roof on the far side of the road. + +[BM3_1:BIKE3] +~g~A local gang has stolen Mitch Baker's Angel. Get it back! + +[BM3_9:BIKE3] +~g~Get Mitch's Angel and get out of there! + +{=================================== MISSION TABLE BMX_1 ===================================} + +[GETBIK2:BMX_1] +~r~You have ~w~~1~ seconds ~g~to get on a Dirt Bike! + +{=================================== MISSION TABLE BOATBUY ===================================} + +[DRUG_2:BOATBUY] +Put it out. There's a dude here. + +[DRUG_3:BOATBUY] +Hey suit dude! I guess you're the new owner? + +[DRUG_4:BOATBUY] +Yeah. Which one of the boats is the fastest? + +[DRUG_5:BOATBUY] +It's already in the water, dude, + +[DRUG_6:BOATBUY] +I though you might want to try her out. + +[DRUG_7:BOATBUY] +Dude, she's already running with a 300 horse power engine... + +[DRUG_8:BOATBUY] +...and the fiberglass hull, she just shoots through the waves.! + +[DRUG_9:BOATBUY] +She can do like zero to sixty in four seconds flat dude... + +[DRUG_10:BOATBUY] +and she can hold like twenty bales of the best Jamaican smoke right in the hull! + +[DRUG_11:BOATBUY] +So go ahead dude, she's ready to fly! + +[DRUG_12:BOATBUY] +Yo yo, uh, suit dude, you gotta light? + +[DRUG_1:BOATBUY] +Hello? Hel-lo?! Hello? + +[DRUG_13:BOATBUY] +Dude? + +{=================================== MISSION TABLE CAP_1 ===================================} + +[CAP1_B1:CAP_1] +~g~The Mafia is taxing your businesses. Find them and kill them. + +[CAP1_B2:CAP_1] +~g~The Mafia has taxed the Boatyard! + +[CAP1_B3:CAP_1] +~g~The Mafia has taxed the Ice Cream Factory! + +[CAP1_B4:CAP_1] +~g~The Mafia has taxed the Car Showroom! + +[CAP1_B5:CAP_1] +~g~The Mafia has taxed the Taxi Firm! + +[CAP_01:CAP_1] +Ok, what's the emergency? + +[CAP_02:CAP_1] +WHO? + +[CAP_03:CAP_1] +Tommy...some mob thugs ...said they'd come to take their cut... + +[CAP_04:CAP_1] +...said it was a Mr. Forello's money...I feel like crap. + +[CAP_05:CAP_1] +Forelli? SONNY Forelli? + +[CAP_06:CAP_1] +Yeah, that's the guy...I think...they were very insistent... + +[CAP_07:CAP_1] +Don't you worry, pop, I'm not angry with you. + +[CAP_08:CAP_1] +Get him to the hospital. + +[CAP_09:CAP_1] +Tommy...rip that guy a new asshole for me... + +[CAP_10:CAP_1] +I'm gonna rip him two! + +[CAP1_2:CAP_1] +You know the rules, Vercetti! + +[CAP1_3:CAP_1] +Mr. Forelli sends his regards! + +[CAP1_4:CAP_1] +It's the Harwood Butcher! + +[CAP1_5:CAP_1] +You tell Sonny - stay away! + +[CAP1_6:CAP_1] +Vice City is MINE now, NOT his. + +[CAP1_7:CAP_1] +You think you can take me down, Vercetti? + +[CAP1_8:CAP_1] +We'll keep coming after you until you die, Vercetti. + +[CAP1_9:CAP_1] +You don't stand a chance, you psychotic prick. + +[CAP1_10:CAP_1] +I'll murder you Vercetti. + +[CAP1_11:CAP_1] +You always were a jerk. + +[CAP1_12:CAP_1] +You're going to die, Vercetti. + +[CAP1_B6:CAP_1] +~g~You have found the collector, kill him. + +[CAP1_B7:CAP_1] +~g~You've lost the collector. + +[CAP1_B8:CAP_1] +~r~The collector has taxed all of your businesses. + +[CAP1_B9:CAP_1] +~g~The mafia has taxed The Malibu! + +[CAP1_B0:CAP_1] +~g~The mafia has taxed the film studio! + +[CAP1_C2:CAP_1] +~g~The Mafia has arrived at the Boatyard! + +[CAP1_C3:CAP_1] +~g~The Mafia has arrived at the Ice Cream Factory! + +[CAP1_C4:CAP_1] +~g~The Mafia has arrived at the Car Showroom! + +[CAP1_C5:CAP_1] +~g~The Mafia has arrived at the Taxi Firm! + +[CAP1_C9:CAP_1] +~g~The Mafia has arrived at The Malibu! + +[CAP1_C0:CAP_1] +~g~The Mafia has arrived at the film studio! + +[CAP1_D2:CAP_1] +~g~The Mafia is leaving the Boatyard! + +[CAP1_D3:CAP_1] +~g~The Mafia is leaving the Ice Cream Factory! + +[CAP1_D4:CAP_1] +~g~The Mafia is leaving the Car Showroom! + +[CAP1_D5:CAP_1] +~g~The Mafia is leaving the Taxi Firm! + +[CAP1_D9:CAP_1] +~g~The Mafia is leaving The Malibu! + +[CAP1_D0:CAP_1] +~g~The Mafia is leaving the film studio! + +[CAP1B10:CAP_1] +You've capped the Collectors. More are on their way. + +{=================================== MISSION TABLE CARBUY ===================================} + +[CAR1_1:CARBUY] +B.J. Smith. And you must be Mr. Vercetti. + +[CAR1_2:CARBUY] +Would you like the tour? + +[CAR1_3:CARBUY] +Might as well. + +[CAR1_4:CARBUY] +Well, I'm very sad to be selling the dealership to y'all. + +[CAR1_5:CARBUY] +This was my first investment after I turned pro. + +[CAR1_6:CARBUY] +But now it's time for me to move on. + +[CAR1_7:CARBUY] +You're leaving town? + +[CAR1_8:CARBUY] +Not in too much of a hurry, I hope? + +[CAR1_9:CARBUY] +No. I'm just coming out of retirement, and preparing for my future comeback. + +[CAR1_10:CARBUY] +The business wasn't too strong, + +[CAR1_11:CARBUY] +and my staff took it upon themselves to get a bit more + +[CAR1_12:CARBUY] +creative with the generation of wealth. + +[CAR1_13:CARBUY] +Obviously, I could wind down the business before I hand it over. + +[CAR1_14:CARBUY] +Hell, I could burn the place down if I wanted to. + +[CAR1_15:CARBUY] +This is prime development land. + +[CAR1_16:CARBUY] +Oh, I wouldn't worry about any of that. + +[CAR1_17:CARBUY] +This place seems perfect. + +[CAR1_18:CARBUY] +Yeh it does, So I take it we have a deal? + +{=================================== MISSION TABLE CARPAR1 ===================================} + +[MM_1_A:CARPAR1] +~g~Collect ~y~5 checkpoints~r~ WITHOUT ~g~smashing any ~r~CONES! ~g~You may collect checkpoints in ~y~ANY ORDER. + +[CONE_1:CARPAR1] +~r~You smashed a cone!! + +[MM_1_C:CARPAR1] +~y~PASS THROUGH~g~ a checkpoint to start the timer. ~g~Each checkpoint will credit you with ~y~~1~ SECONDS~g~. + +{=================================== MISSION TABLE COPCAR ===================================} + +[KILLS:COPCAR] +KILLS: + +[C_BREIF:COPCAR] +~g~Suspect last seen in the ~a~ area. + +[COPCART:COPCAR] +~g~You have ~1~ seconds to return to a police vehicle before the mission ends. + +[C_CANC:COPCAR] +~r~Vigilante mission cancelled! + +[C_TIME:COPCAR] +~r~Your time as a law enforcer is over! + +[C_COMP1:COPCAR] +Vigilante mission level 12 complete: Your max Body Armor increased to 150 + +[CLEVEL:COPCAR] +Vigilante Mission Level ~1~ + +[C_PASS:COPCAR] +THREAT ELIMINATED: $ ~1~ + +{=================================== MISSION TABLE COUNT1 ===================================} + +[CM1_A:COUNT1] +Mr. Vercetti? Hey. You bought the old print works? + +[CM1_B:COUNT1] +Yeah, my old man used to work on these. + +[CM1_C:COUNT1] +I was going to follow him in his trade, but...I lived a different life. + +[CM1_D:COUNT1] +You planning on selling the old machinery, breaking it down? + +[CM1_E:COUNT1] +I'm thinking we might print something - a newspaper, a magazine... + +[CM1_F:COUNT1] +Oh, crap, sonny, low grade crap. I've always fancied printing money. It ain't too hard. + +[CM1_G:COUNT1] +You know, I've been doing it on a small scale for years. + +[CM1_H:COUNT1] +Really? + +[CM1_I:COUNT1] +Sure. But we'd need some good quality plates. + +[CM1_J:COUNT1] +Of course! There's a counterfeiting syndicate already operating in Florida. + +[CM1_K:COUNT1] +A syndicate? + +[CM1_L:COUNT1] +Yeah. Just rumors is all I've heard. + +[CM1_M:COUNT1] +I know a man who's good with rumors... + +[CM1_N:COUNT1] +I used to spend the evenings with him, cleaning the rollers... + +[CM1_2A:COUNT1] +Look at the arse on that! + +[CM1_2B:COUNT1] +Awright girl, it's your loss mate init! + +[CM1_2C:COUNT1] +Awright me ol'china, how's it hangin'? + +[CM1_2D:COUNT1] +What do you know about counterfeiting? + +[CM1_2E:COUNT1] +Oh I'm fine Paul, how 'bout you? + +[CM1_2F:COUNT1] +Come 'ere! + +[CM1_2G:COUNT1] +Awright! Awright! Awright!! You're obviously a busy man. + +[CM1_2H:COUNT1] +All I know about dodgy readys is the Triads supply the plates. + +[CM1_2I:COUNT1] +They've got a shipping company down the docks, + +[CM1_2J:COUNT1] +the boss man would know when the plates are coming in next! + +[CM1_2K:COUNT1] +Thanks Paul. + +[CM1_2L:COUNT1] +What's the matter with you, you maniac! + +[CM1_2M:COUNT1] +Give me another drink, lively! + +[CM1_3:COUNT1] +~g~You've been spotted! + +[CM1_5:COUNT1] +~g~Go and meet Kent Paul at the Malibu Club! + +[CNT1_1:COUNT1] +Who are you? Oooof! Aaiieee! Not the face! Not the face! + +[CM1_1:COUNT1] +~g~Go to the Chartered Libertine Lines boat at the docks. + +[CM1_2:COUNT1] +~g~The Shipping Officer will have the information that is required. + +[CNT1_2:COUNT1] +Ok, I talk! I talk! + +[CM1_6:COUNT1] +~g~Get the information back to the Print Works! + +{=================================== MISSION TABLE COUNT2 ===================================} + +[CNT2_B1:COUNT2] +Alright, the courier's moving the plates from the docks today. + +[CNT2_B2:COUNT2] +I'm gonna go intercept them, grab the plates, lose any heat, and make my way back here. + +[CNT2_B3:COUNT2] +Now. Depending how well this goes, + +[CNT2_B4:COUNT2] +we may have five minutes to print the money before the counterfeit syndicate finds us, or we may have all year. + +[CNT2_B5:COUNT2] +Either way, I want green rolling off the presses five minutes after I get back. Got it? + +[CNT2_B6:COUNT2] +Don't you worry Tommy. We'll be ready. + +[CNT2_B7:COUNT2] +Me an'the boys will be around in the neighborhood case you need any heat taken care of. + +[CNT2_B8:COUNT2] +All right, everybody cool? All right. I'll catch you later... + +[CNT2_01:COUNT2] +~g~The counterfeit plates ~y~courier~g~ is arriving at the ~r~docks~g~ in a helicopter any second now. + +[CNT2_02:COUNT2] +~r~The plates courier has fled in the helicopter. + +[CNT2_03:COUNT2] +~r~The courier has arrived at his destination safely, you're too late! + +[CNT2_04:COUNT2] +~r~You destroyed the plates in the explosion! + +[CNT2_05:COUNT2] +~g~You have the counterfeit plates. Take them to the print works. + +[CNT2_06:COUNT2] +~g~The courier has died and dropped the plates, get to them before anyone else. + +[CNT2_07:COUNT2] +~g~One of the guards has picked up the plates, don't let 'em get away. + +[CNT2_08:COUNT2] +~g~The ~r~courier~g~ with the plates has arrived at the docks. + +[CNT2_4:COUNT2] +Private business. You're not welcome! + +[CNT2_09:COUNT2] +PRINT WORKS ASSET COMPLETED + +[CNT2_10:COUNT2] +~g~The print works will now generate revenue up to a maximum of $~1~. Make sure you collect it regularly. + +[CNT2_11:COUNT2] +~r~The plates are at the bottom of the sea! + +{=================================== MISSION TABLE CUBAN1 ===================================} + +[CUB1_A:CUBAN1] +Si, men? + +[CUB1_B:CUBAN1] +Hey, easy Papi, this man's for me. You, you the boy? + +[CUB1_C:CUBAN1] +Oh yeh. You the boy. I think so, you know? + +[CUB1_D:CUBAN1] +No. I don't think I do. + +[CUB1_E:CUBAN1] +Oh yeah? You come here, tough guy. + +[CUB1_F:CUBAN1] +You think you can take me on? + +[CUB1_G:CUBAN1] +You think you can play stupid with me? + +[CUB1_H:CUBAN1] +No, I think you're playing plenty stupid enough for both of us. + +[CUB1_I:CUBAN1] +Hey, he call you dumb, son. + +[CUB1_J:CUBAN1] +And I call him a little girl, Papi. + +[CUB1_K:CUBAN1] +Look at him, all dressed up like that. + +[CUB1_L:CUBAN1] +What is this, ladies night? + +[CUB1_M:CUBAN1] +You some kind of tough guy, you dress like a woman? + +[CUB1_N:CUBAN1] +You got on panties like a woman too, huh? + +[CUB1_O:CUBAN1] +What you got against women? You prefer men, big boy? + +[CUB1_P:CUBAN1] +I like women! I like all women! I love my mother, chico! + +[CUB1_Q:CUBAN1] +Alright, alright, I'll take your word for it. Relax. + +[CUB1_R:CUBAN1] +Can you drive, amigo? + +[CUB1_S:CUBAN1] +Yeah... like a woman. + +[CUB1_T:CUBAN1] +Very funny. I like you, big boy. Maybe you can help. + +[CUB1_U:CUBAN1] +Maybe you can prove you a man. Huh? + +[CUB1_V:CUBAN1] +Take out the boat. + +[CUB1_W:CUBAN1] +Show me you got some big cojones, + +[CUB1_X:CUBAN1] +and not some little bitty chiquita ones. + +[CUB1_02:CUBAN1] +Ok man, treat her like a woman. + +[CUB1_03:CUBAN1] +Not bad, you're a real man. + +[CUB1_04:CUBAN1] +You got real big cojones, amigo. + +[CUB1_05:CUBAN1] +Amigo, you a man, man. + +[CUB1_06:CUBAN1] +Call yourself a man, man? + +[CUB1_07:CUBAN1] +You a little scaredy kitten, baby boy, go cry to your mommy! + +[CUB1_09:CUBAN1] +Man, you the man, man. I like you, man. I like you a lot. + +[CUB1_10:CUBAN1] +Any time, man. 'cause you got cojones. And all my friends have big cojones. + +[CUB1_11:CUBAN1] +~r~You Killed Rico! + +[CUB1_12:CUBAN1] +Go through the first checkpoint to begin the test. + +[CUB1_14:CUBAN1] +Get back in the boat! + +[CUB1_15:CUBAN1] +~r~You are too slow, man. + +[CUB1_01:CUBAN1] +Hey, I'm Rico. You the man with the big cojones? + +[CUB1_13:CUBAN1] +~g~You have three minutes to get round the course. + +[CUB1_08:CUBAN1] +You a big waste of space. Walk like a man, talk like a man, but you drive like an idiot. + +{=================================== MISSION TABLE CUBAN2 ===================================} + +[CUB2_A:CUBAN2] +Un cafecito, por favor, Alberto.. + +[CUB2_N:CUBAN2] +No problema, Tommy. + +[CUB2_B:CUBAN2] +Papi! Una grande problema! + +[CUB2_O:CUBAN2] +Umberto my son, what happened? + +[CUB2_C:CUBAN2] +The Haitians! I hate these Haitians! + +[CUB2_D:CUBAN2] +They mess with me for the last time! + +[CUB2_E:CUBAN2] +These Hai - these Haitians! We take 'em out! + +[CUB2_F:CUBAN2] +Only we need some backup. + +[CUB2_H:CUBAN2] +Amigo, you drive good! + +[CUB2_I:CUBAN2] +For a woman. Right? + +[CUB2_J:CUBAN2] +This is no time for joking! + +[CUB2_K:CUBAN2] +Come on, drive for me again! + +[CUB2_L:CUBAN2] +Take my boys over there, and then we'll take these Haitians down! + +[CUB2_01:CUBAN2] +Not enough room, man, you need a bigger car. + +[CUB2_02:CUBAN2] +We need reinforcements from the cafe! + +[CUB2_03:CUBAN2] +~g~Get a car and pick up the Cubans from outside Robina's Cafe. + +[CUB2_04:CUBAN2] +~g~Go and drop the Cubans off at the fight. + +[CUB2_05:CUBAN2] +Take out that cowardly sniper! + +[CUB2_07:CUBAN2] +They fight like girls! Take cover! + +[CUB2_09:CUBAN2] +Sniper on the roof! + +[CUB2_11:CUBAN2] +~r~You fool, we needed that car. + +[CUB2_12:CUBAN2] +Hey amigo! Good to see you could make it! + +[CUB2_13:CUBAN2] +Stinking nest of Haitians, we gonna kill 'em all! + +[CUB2_14:CUBAN2] +CHAAAAAARGE! + +[CUB2_15:CUBAN2] +Now, my brothers, CHAAARRRGGEE!! + +[CUB2_16:CUBAN2] +Tommy, we have proved our manful bravery! + +[CUB2_17:CUBAN2] +Let us steal the van full of drugs and make good our escape! + +[CUB2_18:CUBAN2] +~g~Get a car and pick up the Cubans + +[CUB2_19:CUBAN2] +We gonna fight like men! + +[CUB2_21:CUBAN2] +Fight like men with huge cojones! + +[CUB2_22:CUBAN2] +~g~ Finish off the rest of the Haitians so the Cubans can move forward. + +[CUB2_23:CUBAN2] +~g~ Little Haiti will be swarming with Haitians trying to even the score with the Cubans. Watch your back. + +[CUB2_24:CUBAN2] +~g~Return to Robina's Cafe with the Van and park it round the back. + +[CUB2_25:CUBAN2] +KILL ALL THE HAITIANS!! + +[CUB2_G:CUBAN2] +I lost a few hermanos already out there. + +[CUB2_M:CUBAN2] +They mess with me, they mess with the biggest boy in town! + +{=================================== MISSION TABLE CUBAN3 ===================================} + +[CUB3_B:CUBAN3] +Poppa, don't serve this snake in the straw. + +[CUB3_C:CUBAN3] +You're two-faced, Tommy! + +[CUB3_E:CUBAN3] +The Haitians, man. They're laughing at me! + +[CUB3_G:CUBAN3] +They're laughing at me, Tommy. At me! + +[CUB3_I:CUBAN3] +Nobody does whatever they like, Umberto, they do what you let them do. + +[CUB3_J:CUBAN3] +What? + +[CUB3_K:CUBAN3] +You want somebody taken care of? + +[CUB3_L:CUBAN3] +I can handle it, but it's gonna cost you. + +[CUB3_M:CUBAN3] +I know we're brothers and all, but this is business. + +[CUB3_N:CUBAN3] +Tommy. You a real man. Businessman, a gentleman. + +[CUB3_O:CUBAN3] +These Haitians. They have a load of product coming in off shore, really good stuff. + +[CUB3_P:CUBAN3] +We take it, and we finish them. + +[CUB3_Q:CUBAN3] +You take it, and I look after you. Like my brother. Like my son. + +[CUB3_R:CUBAN3] +I think I prefer the cash to being bounced on your knee, amigo. + +[CUB3_01:CUBAN3] +Hey Rico. Nice boat, you ready? + +[CUB3_03:CUBAN3] +~g~Collect all the briefcases filled with the drugs and cash. + +[CUB3_04:CUBAN3] +~g~Get the drugs and cash back to Umberto. + +[CUB3_05:CUBAN3] +Si Tommy. Now you be a good shot today, + +[CUB3_06:CUBAN3] +My boat, she no good full of holes, ok? + +[CUB3_07:CUBAN3] +~g~ Go meet Rico. He'll drive you to the meet location. + +[CUB3_02:CUBAN3] +~g~KILL ALL THE HAITIANS ON THE BOATS!! + +[CUB3_08:CUBAN3] +Uh oh.. Pack of Cubans. We under attack! + +[CUB3_A:CUBAN3] +Alberto. Una cafe, senor. + +[CUB3_D:CUBAN3] +You're either two-faced, or you're a wimp, baby boy! + +[CUB3_F:CUBAN3] +Easy, easy. What's your problem? + +[CUB3_H:CUBAN3] +Umberto Robina! They're doing whatever they like! + +{=================================== MISSION TABLE CUBAN4 ===================================} + +[CUB4_A:CUBAN4] +Hey, ladies. You know what I'm gonna do? + +[CUB4_B:CUBAN4] +I'm gonna kill me a Haitian. And then? + +[CUB4_C:CUBAN4] +And then I'm going to make love like a man. + +[CUB4_D:CUBAN4] +You know that, chica? Something like this. + +[CUB4_E:CUBAN4] +Loser! + +[CUB4_F:CUBAN4] +Prick. + +[CUB4_G:CUBAN4] +Hey, baby, I wouldn't touch you with a ten foot pole! + +[CUB4_H:CUBAN4] +Umberto Robina, he likes the ladies! Not some goat in a skirt! + +[CUB4_I:CUBAN4] +Tommy!! Tommy, I love you, I love you! Let's go! + +[CUB4_J:CUBAN4] +Go where? Can't I get a cup of coffee first? + +[CUB4_K:CUBAN4] +No time for coffee! Besides, I just had one. + +[CUB4_L:CUBAN4] +We gonna take out the Haitians. + +[CUB4_M:CUBAN4] +Tommy, how do you take out a snake? + +[CUB4_N:CUBAN4] +You bite him in the ass! Hahaha! + +[CUB4_O:CUBAN4] +Whatever you say, Umberto. + +[CUB4_P:CUBAN4] +Tommy, you go and get us a little Haitian car. + +[CUB4_Q:CUBAN4] +When you get it, come back and pick up my boy. + +[CUB4_R:CUBAN4] +Pepe, and take him out to the Haitians. + +[CUB4_S:CUBAN4] +Then, you go around to the Haitians processing plant, and you use their solvent as an explosive. + +[CUB4_T:CUBAN4] +Boom! Bye bye! + +[CUB4_U:CUBAN4] +Umberto, what about you? + +[CUB4_V:CUBAN4] +Uhh... I'm going to stay behind, and watch over the cafe with Poppa. + +[CUB4_W:CUBAN4] +He not feeling so good. You know? + +[CUB4_02:CUBAN4] +~g~The bombs will be set with a 45 second timer. + +[CUB4_04:CUBAN4] +~r~You've alerted the base, there is no way we will get in now! + +[CUB4_07:CUBAN4] +Oy - the solvent is round the back, amigo. + +[CUB4_08:CUBAN4] +Hola, amigos. + +[CUB4_09:CUBAN4] +Bueno. Haitian Putas. Muerte. + +[CUB4_10:CUBAN4] +Vamos. + +[CUB4_11:CUBAN4] +Vamos indeed. + +[CUB4_12:CUBAN4] +Hey, we need a Haitian gang car! + +[CUB4_13:CUBAN4] +Oye, let's go find our muchachos! + +[CUB4_14:CUBAN4] +Follow my compadres. + +[CUB4_15:CUBAN4] +Ok, in you go... + +[CUB4_16:CUBAN4] +I'm going to plant the bomb, cover me! + +[CUB4_17:CUBAN4] +RUN!! + +[CUB4_18:CUBAN4] +Man, this a nice part of town... + +[CUB4_19:CUBAN4] +This place is a dump, man. + +[CUB4_20:CUBAN4] +I had a beautiful woman... lived around this neighborhood. + +[CUB4_21:CUBAN4] +You know, they do nice pizzas here. + +[CUB4_22:CUBAN4] +Whoah, man. You drive like a crazy bitch! + +[CUB4_23:CUBAN4] +You lost, man? + +[CUB4_24:CUBAN4] +You've left Pepe behind, go and get him. + +[CUB4_03:CUBAN4] +~g~Stay in the car until safely parked inside the compound. + +[CUB4_26:CUBAN4] +~g~Take Pepe, head North into Little Haiti and steal a Voodoo car. + +[CUB4_27:CUBAN4] +~g~Go and meet up with Rico and the other Cubans. + +[CUB4_28:CUBAN4] +~g~Join the other Cubans at the Haitian Drugs Factory. + +[CUB4_29:CUBAN4] +~g~Walk into each of the markers to plant a bomb at that location. + +[CUB4_30:CUBAN4] +~g~After all three bombs are planted, get clear of the factory before it blows. + +[CUB4_31:CUBAN4] +~g~Get clear of the factory!! + +[CUB4_32:CUBAN4] +~g~Park the car at the blip and get out. + +[CUB4_06:CUBAN4] +~r~You did not get far enough away from the base and we had to abort the explosion! + +{=================================== MISSION TABLE FINALE ===================================} + +[FIN1_01:FINALE] +What's going on? + +[FIN1_02:FINALE] +Tommy! Oh good, good. Listen, listen. Uh, listen, + +[FIN1_03:FINALE] +I like fish. I love fish. + +[FIN1_04:FINALE] +I love them as pets in bowls, or as food on a plate, + +[FIN1_05:FINALE] +but as much as I love em, I don't want to sleep with them. + +[FIN1_06:FINALE] +Okay, but right now your Italian brothers are coming from up there to fit me with some cement shoes, and I... + +[FIN1_07:FINALE] +Shut up Ken. Sit down. + +[FIN1_08:FINALE] +Lance, what the hell's going on? + +[FIN1_09:FINALE] +It's your friends up north Tommy. They ain't too happy you capped their man. + +[FIN1_10:FINALE] +They're coming down to see the business today. + +[FIN1_11:FINALE] +They took longer than I thought... + +[FIN1_12:FINALE] +Guys, we gotta make this final we gotta leave no doubt that this is my operation. Mine! + +[FIN1_13:FINALE] +Ken, you get the first run of counterfeit cash and put three mil in briefcases. + +[FIN1_14:FINALE] +Lance, you get the guys together... + +[FIN2_01:FINALE] +Tommy! + +[FIN2_02:FINALE] +What? No big hugs for your old buddy? + +[FIN2_03:FINALE] +I've had fifteen years out of the loop, + +[FIN2_04:FINALE] +I'm a bit rusty on family etiquette. + +[FIN2_05:FINALE] +Always angry, eh Tommy. + +[FIN2_06:FINALE] +Didn't I say your temper would get you into trouble, huh? + +[FIN2_07:FINALE] +There's three mil in the cases... + +[FIN2_08:FINALE] +How many was it? Ten? No, eleven men. + +[FIN2_09:FINALE] +That's how you get to be called the Harwood Butcher! Heh-heh-heh! + +[FIN2_10:FINALE] +You sent me to kill one man, ONE MAN. They knew I was coming Sonny... + +[FIN2_11:FINALE] +Tommy, Tommy, watch your tone. + +[FIN2_12:FINALE] +Anyone would think you blame me for that unfortunate set of circumstances. + +[FIN2_13:FINALE] +Just take the money... + +[FIN2_14:FINALE] +Get the damn cash. + +[FIN2_15:FINALE] +You know, Tommy? I did what I could for you, I pulled strings, called in favors. + +[FIN2_16:FINALE] +I was your friend, Tommy. I hoped you'd see sense, see what's good for business. + +[FIN2_17:FINALE] +I trusted you, Tommy, and you disappointed me. + +[FIN2_18:FINALE] +But at least someone in your chicken shit organization knows how to do business, + +[FIN2_19:FINALE] +Isn't that right, Lance? + +[FIN2_20:FINALE] +I'm sorry Tommy. This is Vice City. This is business. + +[FIN2_21:FINALE] +You sold us out... + +[FIN2_22:FINALE] +No. I sold YOU out, Tommy, I sold YOU out. + +[FIN2_23:FINALE] +The real cash is upstairs in the safe. + +[FIN2_24:FINALE] +Tommy, what was the big plan? + +[FIN2_25:FINALE] +You think I'd just take the fake cash? + +[FIN2_26:FINALE] +Save face and run away with my tail between my legs?! + +[FIN2_27:FINALE] +No. + +[FIN2_28:FINALE] +I just wanted to piss you off before I kill you. + +[FIN3_01:FINALE] +Tommy? + +[FIN3_02:FINALE] +Oh my god, Tommy! What happened? + +[FIN3_03:FINALE] +What does it look like? + +[FIN3_04:FINALE] +It looks like you ruined your suit! + +[FIN3_05:FINALE] +and Tommy, that was a beautiful suit! Tommy, what on earth happened? + +[FIN3_06:FINALE] +I had a disagreement with a business associate, you know how it is. + +[FIN3_07:FINALE] +Tommy, I have a disagreement, I send them an angry letter. + +[FIN3_08:FINALE] +Maybe I pee in their mailbox. I don't start World War III. + +[FIN3_09:FINALE] +You know, maybe you should speak to my shrink... + +[FIN3_10:FINALE] +That stupid prick, Lance... + +[FIN3_11:FINALE] +Tommy. I never liked that guy, okay? + +[FIN3_12:FINALE] +He's neurotic, he's insecure, he's self-centered - the guy's an asshole! + +[FIN3_13:FINALE] +I'm glad you took him out! + +[FIN3_14:FINALE] +I don't think we're gonna be getting any more heat from up north either... + +[FIN3_15:FINALE] +...'cause there ain't no 'up north', anymore. + +[FIN3_16:FINALE] +It's all down south now. + +[FIN3_17:FINALE] +Wait, does that mean what I think it means..? Tommy, baby! + +[FIN3_18:FINALE] +What do you think it means? + +[FIN3_19:FINALE] +That we're in charge... I mean, that you're in charge. Oh, Tommy! + +[FIN3_20:FINALE] +You know, Ken. I think this could be the beginning of a beautiful business relationship.... + +[FIN3_21:FINALE] +After all, you're a conniving, backstabbing, two-bit thief + +[FIN3_22:FINALE] +and I'm a convicted psychotic killer and drug dealer. + +[FIN3_23:FINALE] +I know. Ain't it just beautiful? + +[FIN_B1:FINALE] +~g~Go and kill ~y~Lance Vance~g~ the backstabber. + +[FIN_B2:FINALE] +~g~Kill ~p~Sonny~g~ and finish this once and for all. + +[FIN_B3:FINALE] +~g~The Mafia are trying to steal your money. Defend the safe. + +[FIN_B4:FINALE] +~g~You are close to death, get some ~w~health~g~ from downstairs. + +[FIN_B5:FINALE] +~g~The Mafia is stealing your money, defend the ~c~safe + +[FIN_B7:FINALE] +~r~The mafia has stolen all your money. + +[DEFSAFE:FINALE] +~g~Get back to the safe and defend it. + +{=================================== MISSION TABLE FIRETRK ===================================} + +[F_PASS1:FIRETRK] +Fire extinguished! + +[F_FAIL2:FIRETRK] +~r~You're too late! + +[F_CANC:FIRETRK] +~r~Fire Fighter mission cancelled! + +[F_EXTIN:FIRETRK] +FIRES: + +[F_START:FIRETRK] +~g~Burning vehicle reported in the ~a~ area. Go and extinguish the fire. + +[SIREN_1:FIRETRK] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ ~w~button. + +[SIREN_2:FIRETRK] +To turn on this vehicle's sirens tap the ~h~~k~~VEHICLE_HORN~ ~w~button. + +[FIREPRO:FIRETRK] +Fire Truck Mission level 12 complete. You are now completely fireproof!! + +[F_FAIL1:FIRETRK] +Fire Fighter mission ended. + +[F_STAR1:FIRETRK] +~g~Burning vehicles reported in the ~a~ area. Go and extinguish the fire. + +[SPRAY_4:FIRETRK] { reVC update } +Use the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button to fire the water cannon. Aim using ~h~~k~~VEHICLE_TURRETLEFT~~w~ and ~h~~k~~VEHICLE_TURRETRIGHT~~w~. + +[SPRAY_1:FIRETRK] { reVC update } +Use the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button to fire the water cannon. Aim using ~h~~k~~VEHICLE_TURRETLEFT~~w~ and ~h~~k~~VEHICLE_TURRETRIGHT~~w~. + +{=================================== MISSION TABLE GENERA1 ===================================} + +[GEN1_A:GENERA1] +Mr. Vercetti! + +[GEN1_B:GENERA1] +Colonel. + +[GEN1_D:GENERA1] +No - thanks. + +[GEN1_E:GENERA1] +I'm ashamed to admit that one of the causes of our mutual problem appears to have been the loose tongue of a man I used to trust. + +[GEN1_F:GENERA1] +I've been carrying Gonzalez for years, but now his incompetence reaches new heights! + +[GEN1_G:GENERA1] +It is only right that you kill Gonzalez... + +[GEN1_H:GENERA1] +Did he do it? It's the money that's important to me. + +[GEN1_J:GENERA1] +He will be at his Penthouse, half drunk probably. Use this... + +[GEN1_06:GENERA1] +Eh! He's got a blade! + +[GEN1_07:GENERA1] +Go away from me, you cheap bastard! + +[GEN1_08:GENERA1] +Oh sweet Jesus, I've wasted my life and my looks! + +[GEN1_10:GENERA1] +I'm going to shut that big mouth of yours! + +[GEN1_11:GENERA1] +Stop running you fat slimeball! + +[GEN1_12:GENERA1] +Stand still and I'll make it quick! + +[GEN1_13:GENERA1] +Quit your squealing, no one cares, fatso! + +[GEN1_C:GENERA1] +Thank you for coming. Please sit. Lobster? + +[GEN1_05:GENERA1] +~g~Go and kill Gonzalez! + +[GEN1_09:GENERA1] +I pay you double, Tommy, DOUBLE! + +[GEN1_18:GENERA1] +~r~Gonzalez has made it safely to the Police Station! + +[GEN1_19:GENERA1] +~g~The Vice City Police are on to you! + +[GEN1_20:GENERA1] +~g~Get into a vehicle. + +[GEN1_21:GENERA1] +~g~Get to the~h~ Pay 'N' Spray~g~ in~h~ Vice Point~g~. + +[GEN1_22:GENERA1] +Drive your vehicle into the spray shop to lose your ~h~wanted level, ~h~repair and ~h~respray your vehicle. Cost - ~h~$100. This time it's free. + +[GEN1_01:GENERA1] +When jogging, press and hold the~h~ ~k~~PED_FIREWEAPON~ ~w~button to prepare a melee attack. + +[GEN1_02:GENERA1] +When jogging, press and hold the~h~ ~k~~PED_FIREWEAPON~ ~w~button to prepare a melee attack. + +[GEN1_03:GENERA1] +When jogging, press and hold the~h~ ~k~~PED_FIREWEAPON~ ~w~button to prepare a melee attack. + +[GEN1_14:GENERA1] +Release the~h~ ~k~~PED_FIREWEAPON~ ~w~button to make the attack. + +[GEN1_15:GENERA1] +Release the~h~ ~k~~PED_FIREWEAPON~ ~w~button to make the attack. + +[GEN1_16:GENERA1] +Release the~h~ ~k~~PED_FIREWEAPON~ ~w~button to make the attack. + +[GEN1_I:GENERA1] +For this kindness I'll reward you, and then we will find your money together. + +[GEN1_23:GENERA1] +~g~Go back through the doors to return to the ground floor. + +{=================================== MISSION TABLE GENERA2 ===================================} + +[COL2_B:GENERA2] +This looks delicious, huh? Tapir snout? + +[COL2_C:GENERA2] +Uhhh... no, no. No, thanks. + +[COL2_D:GENERA2] +Tommy, you are like a pampas breeze that has freed me from the stench of corruption, + +[COL2_E:GENERA2] +although, I must appear to mourn his passing and carry on with business as usual. + +[COL2_F:GENERA2] +This isn't getting me any closer to my money... + +[COL2_G:GENERA2] +Tommy, my friend, you are not in Liberty now. Here we do things differently. + +[COL2_H:GENERA2] +I will continue with my enquiries but in the meantime I have a valuable deal to close. + +[COL2_I:GENERA2] +A favor for a friend, Cortez? + +[COL2_J:GENERA2] +You're a good friend, Tommy. I knew you would not let me down. + +[COL2_K:GENERA2] +I need you to meet a courier who has obtained some valuable technology for me... + +[COL2_1:GENERA2] +Ze rain, she is tres wet zis time of the year... + +[COL2_2:GENERA2] +What? + +[COL2_3:GENERA2] +Ah, comment? + +[COL2_4:GENERA2] +Look, Cortez sent me. Just give me the damn chips. + +[COL2_5:GENERA2] +Oh...d'accord. + +[COL2_B1:GENERA2] +~g~Meet the courier at the mall. + +[COL2_B2:GENERA2] +~g~The courier is fleeing with the guidance chips! Don't let him get away! + +[COL2_B3:GENERA2] +~g~Take the guidance chips back to the Colonel. + +[COL2_F1:GENERA2] +~r~You killed the contact! + +[COL2_F2:GENERA2] +~g~The courier is dead. Grab the guidance chips. + +[COL2_6A:GENERA2] +Freeze, imperialist American pig! Zat iz propertay of ze government Francais. 'And eet over! + +[BLIPHLP:GENERA2] +The blip on the radar is a triangle pointing up, this shows that the target is higher than the player. + +[COL2_F3:GENERA2] +~r~The guidance chips are at the bottom of the sea. + +[COL2_F4:GENERA2] +~r~The courier has escaped! You failed to get the guidance chips. + +[COL2_A:GENERA2] +Tommy! Come, join me. + +{=================================== MISSION TABLE GENERA3 ===================================} + +[GEN3_A:GENERA3] +Thomas, I appreciate your coming. + +[GEN3_B:GENERA3] +Forgive me for getting straight to business. + +[GEN3_C:GENERA3] +Diaz has asked me to oversee a minor business transaction. + +[GEN3_D:GENERA3] +Let's hope it goes better than last time, huh? + +[GEN3_E:GENERA3] +Which is why I thought of you, my friend. + +[GEN3_F:GENERA3] +I've dropped some protection at the multistory carpark. + +[GEN3_G:GENERA3] +Pick it up - then go and watch over Diaz's men at the drop off. + +[GEN3_H:GENERA3] +Gracias, amigo. + +[GEN3_1:GENERA3] +Hogging all the action, I see... + +[GEN3_2:GENERA3] +Look, you wanna do something other than just shadowing me everywhere? Why don't you come along and show me if you're any use. + +[GEN3_3:GENERA3] +I might just do that. The name's Lance, by the way. + +[GEN3_5:GENERA3] +You must be Cortez's new gun. + +[GEN3_6:GENERA3] +Until more gainful opportunities arise. + +[GEN3_7:GENERA3] +They'll be here any minute - we both better get a good vantage point... + +[GEN3_8:GENERA3] +OK! I'll take the balcony, you get the roof across the yard. + +[GEN3_9:GENERA3] +I live! Dickheads! And it's all down to you! What is your name? + +[GEN3_10:GENERA3] +Tommy. + +[GEN3_11:GENERA3] +I see you soon, amigo, I think! + +[GEN3_12:GENERA3] +Shit. Where's that guy Lance? + +[GEN3_14:GENERA3] +Tommy! I need some help here! + +[GEN3_15:GENERA3] +Don't worry, I got you covered! + +[GEN3_16:GENERA3] +Diaz's men are getting cut down! + +[GEN3_19:GENERA3] +~g~Haitians! They're busting the deal! Protect Diaz! + +[GEN3_20:GENERA3] +~g~The Colonel has arranged some firepower for you at the multistory carpark. + +[GEN3_22:GENERA3] +Diaz's Health: + +[GEN3_23:GENERA3] +~g~You've left Lance behind! Go and get him! + +[GEN3_25:GENERA3] +~r~Lance died! + +[GEN3_28:GENERA3] +~g~Take the briefcase back to Diaz. + +[GEN3_29:GENERA3] +~g~Collect the briefcase and take it back to Diaz. + +[GEN3_30:GENERA3] +~r~He got away with the money! Diaz will have your balls for this! + +[GEN3_33:GENERA3] +~r~Check your fire!! You're supposed to be watching over Diaz and his men, not shooting them! + +[GEN3_34:GENERA3] +~r~There ain't gonna be a deal if you shoot the Cubans! + +[GEN3_35:GENERA3] +~g~He's stolen Diaz's money! + +[GEN3_36:GENERA3] +~g~Grab the bike, chase him down and get Diaz's money back! + +[GEN3_37:GENERA3] +~g~Here come the Cubans. Watch over the deal making sure Diaz and Lance are safe. + +[GEN3_38:GENERA3] +~r~Diaz died! You failed to protect him! + +[GEN3_39:GENERA3] +~g~Get to your vantage point up the stairs. + +[GEN3_44:GENERA3] +~g~Go with Lance to the drop off and watch over Diaz. + +[GEN3_45:GENERA3] +They'll be here any minute, we both better get a good vantage point. + +[GEN3_40:GENERA3] { reVC update } +To ~h~shoot straight ahead ~w~on a ~h~motorbike ~w~press the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button. + +[GEN3_41:GENERA3] { reVC update } +To ~h~shoot straight ahead ~w~on a ~h~motorbike ~w~press the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button. + +[GEN3_46:GENERA3] +Sheeit! + +[GEN3_47:GENERA3] +Tommy! + +[GEN3_48:GENERA3] +Damn! + +[GEN3_49:GENERA3] +lance's health: + +[GEN3_50:GENERA3] +~r~You lost Diaz's money! Next time try not to reduce the money to ashes! + +[GEN3_51:GENERA3] +More damned Haitians in a shitty van! + +[GEN3_54:GENERA3] +Don't just stand there, you pricks, chase that Haitian dickhead down! + +[GEN3_55:GENERA3] +Tommy! I'll stay here and watch over Diaz! + +[GEN3_18:GENERA3] +~g~Here come the Cubans, keep close to Diaz. Watch over the deal making Diaz and Lance are safe. + +[GEN3_56:GENERA3] +~r~Diaz was ambushed and died! Next time keep him in your sights! + +[GEN3_57:GENERA3] +The Kruger is an assault rifle, which allows you to manually aim in 1st person. + +[GEN3_58:GENERA3] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~~w~ button to ~h~aim~w~ with an assault rifle. + +[GEN3_59:GENERA3] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~~w~ button to ~h~aim~w~ with an assault rifle. + +[GEN3_60:GENERA3] +Press the ~h~~k~~PED_FIREWEAPON~~w~ button to ~h~fire~w~ an assault rifle. + +[GEN3_61:GENERA3] +Press the ~h~~k~~PED_FIREWEAPON~~w~ button to ~h~fire~w~ an assault rifle. + +[GEN3_62:GENERA3] +Press the ~h~~k~~PED_FIREWEAPON~~w~ button to ~h~fire~w~ an assault rifle. + +[GEN3_63:GENERA3] +As well as performing drive-by's,~h~ motorbikes ~w~allow you to ~h~shoot forwards~w~. + +[GEN3_64:GENERA3] { reVC update } +To shoot forwards while on a bike press the ~h~~k~~VEHICLE_FIREWEAPON~~w~ button. + +[GEN3_65:GENERA3] { reVC update } +To shoot forwards while on a bike press the ~h~~k~~VEHICLE_FIREWEAPON~~w~ button. + +[GEN3_66:GENERA3] { reVC update } +To shoot forwards while on a bike press the ~h~~k~~VEHICLE_FIREWEAPON~~w~ button. + +[GEN3_67:GENERA3] +You must have a sub machine gun to shoot forwards on a motorbike. + +[GEN3_53:GENERA3] +MY MONEY! + +[GEN3_52:GENERA3] +These Haitians think they can take RICARDO DIAZ!?! + +{=================================== MISSION TABLE GENERA4 ===================================} + +[DETON:GENERA4] +DETONATION: + +[COL4_3:GENERA4] +CONVOY HALT! + +[COL4_6:GENERA4] +WE'RE TAKING ENEMY FIRE! + +[COL4_7:GENERA4] +Civilian, move away from the tank! + +[COL4_8:GENERA4] +I SAID, move away, IMMEDIATELY! + +[COL4_9:GENERA4] +DEFENSIVE POSITIONS! + +[COL4_11:GENERA4] +Get that civilian out of our way soldier! - Sir, Yes Sir! + +[COL4_12:GENERA4] +Civilian in the TANK! STOP HIM! + +[COL4_13:GENERA4] +This is a military convoy, do not obstruct our route. + +[COL4_14:GENERA4] +Drop him soldier. + +[COL4_15:GENERA4] +Get that civilian vehicle out of our way! - Sir! Moving vehicle Sir! + +[COL4_17:GENERA4] +Ok, PLATOON MOVE IT OUT! + +[COL4_18:GENERA4] +Someone's on the tank Sir! + +[COL4_19:GENERA4] +Go get some doughnuts, soldier! - Sir, Yes Sir! + +[COL4_20:GENERA4] +Target acquired, Sir + +[COL4_21:GENERA4] +SNIPER! + +[COL4_22:GENERA4] +I'm getting out of here. + +[COL4_23:GENERA4] +Objective completed! Platoon dismissed! - Lets go eat some doughnuts. + +[COL4_24:GENERA4] +Security protocol Delta India Echo triggered! Vehicle self destruct initiated! + +[COL4_26:GENERA4] +Prepare to die Communist scum! + +[COL4_B2:GENERA4] +~r~The tank arrived at its destination safely! + +[COL4_B5:GENERA4] +~r~The tank has been destroyed! + +[COL4_01:GENERA4] +Diaz was pleased, and would like to meet you again. + +[COL4_02:GENERA4] +Is that a good thing? + +[COL4_03:GENERA4] +Of course! Although I'm starting to think that Diaz was responsible for our unfortunate loss... + +[COL4_04:GENERA4] +What makes you say that? + +[COL4_05:GENERA4] +One does not wave accusations at a man like Diaz - I'm merely thinking out loud... + +[COL4_06:GENERA4] +No matter. I have a proposal that you could profit from... + +[COL4_07:GENERA4] +I don't have time to run more errands, Cortez. + +[COL4_08:GENERA4] +I would have thought a man with such dangerous debts would be hungry for opportunities. Please, Tommy, at least hear me out. + +[COL4_09:GENERA4] +Go on... + +[COL410:GENERA4] +I have a buyer for a piece of military hardware that is being taken through town. Pick it up for me... + +[COL411:GENERA4] +and once you get it, I want you to call me immediately, then... + +[COL4_B4:GENERA4] +~g~The tank is locked. Find a way to lure out the occupants. + +[COL4_1:GENERA4] +What's up with the Gunner? - Don't know Sir! + +[COL4_4:GENERA4] +Get topside soldier. - Sir, Yes Sir! + +[COL4_B1:GENERA4] +~g~Go and acquire the piece of military hardware that is being taken through town. + +[COL4_B3:GENERA4] +~g~Drop the tank off in the Colonels lockup before it self destructs. + +[COL4_B6:GENERA4] +~g~Find a way to steal the tank! + +[COL4_B7:GENERA4] +~g~Drive the tank into the garage. + +[COL4_B8:GENERA4] +~g~Get out of the tank and walk out of the garage. + +{=================================== MISSION TABLE GENERA5 ===================================} + +[COL5A_1:GENERA5] +Circumstances force a hasty departure, amigo. + +[COL5A_2:GENERA5] +What's the problem? + +[COL5A_3:GENERA5] +Ehh, the French want their missile technology back and after that last incident, + +[COL5A_4:GENERA5] +I feel it is time to find safer harbors. + +[COL5A_5:GENERA5] +Wouldn't it be safer to fly? + +[COL5A_6:GENERA5] +I'd be dead before I reached check-in. Besides, I need to get my merchandise out of the country. + +[COL5A_7:GENERA5] +Need another gun? + +[COL5A_8:GENERA5] +You, my friend, are worth ten guns... + +[COL5B_1:GENERA5] +Thomas, you have protected and served me well. + +[COL5B_2:GENERA5] +But now you must leave us before we reach the open seas. + +[COL5B_4:GENERA5] +Thank you, Colonel. + +[COL5B_5:GENERA5] +One more request. While I'm away, could you keep an eye on Mercedes for me? + +[COL5B_6:GENERA5] +I think she could look after herself, but sure, I'll keep an eye out. + +[COL5B_7:GENERA5] +Gracias, amigo. Hasta luego. + +[COL5B_8:GENERA5] +Adios, amigo. + +[COL5_7:GENERA5] +Stop shooting at me! + +[COL5_9:GENERA5] +Tommy, stop them shooting at me! + +[COL5_10:GENERA5] +I have diplomatic immunity! + +[COL5_11:GENERA5] +Don't shoot, I am a Colonel! + +[COL5_12:GENERA5] +Thomas, kill them, my country will love you. + +[COL5_13:GENERA5] +Tommy, we are being overrun by the French! + +[COL5_14:GENERA5] +Tommy, everywhere I look, there are French men, I hate it! + +[COL5_15:GENERA5] +Tommy, how are you? + +[COL5_16:GENERA5] +This is for Piaf and Gainesbourg and your stupid french bread! + +[COL5_1:GENERA5] +Port side! Port side! + +[COL5_2:GENERA5] +They're attacking from starboard! + +[COL5_3:GENERA5] +The bridge up ahead! + +[COL5_4:GENERA5] +They've got a helicopter! + +[COL5_B1:GENERA5] +~g~Defend the Colonel and his yacht at all costs. + +[COL5_B2:GENERA5] +~g~Get up front and clear the route for the Colonel's yacht. + +[COL5_B3:GENERA5] +~r~The Colonel is dead! + +[COL5_B4:GENERA5] +~g~Shoot the attacking helicopter out of the sky. + +[COL5B_3:GENERA5] +I will lower my personal launch. Keep it, my friend, a token of my gratitude. + +[COL5_B5:GENERA5] +~g~Shoot down the helicopters, do not endanger the yacht. + +[COL5_B6:GENERA5] +~g~You have run out of ammo, get more from the stairs on the top deck. + +[COL5_B7:GENERA5] +~g~You are running low on health, get more from the stairs on the top deck. + +{=================================== MISSION TABLE HAIT1 ===================================} + +[HAM1_A:HAIT1] +Hello? Hello? + +[HAM1_C:HAIT1] +You must be the big bad man me grandaddy been chattin' 'bout. + +[HAM1_D:HAIT1] +Tells me tings about you, you know, when he visits, + +[HAM1_E:HAIT1] +and about the others who wait for you. + +[HAM1_F:HAIT1] +Now, we all dead for long time, but you, + +[HAM1_G:HAIT1] +I wouldn't want to be in your shoes, ha ha ha ha ha! + +[HAM1_H:HAIT1] +I got a message to come here. + +[HAM1_I:HAIT1] +Can you hear dem? + +[HAM1_J:HAIT1] +Dem callin' your name, boy, must want you pretty bad, don't ya tink? + +[HAM1_K:HAIT1] +Now you do old Auntie Poulet a turn, huh, maybe she help you. + +[HAM1_L:HAIT1] +Maybe she can give you a little juju after all of dis. + +[HAM1_M:HAIT1] +Give you some magic to give the law man the stink eye, hmmmmm? + +[HAM1_N:HAIT1] +Look, this is all very, um... give me what? + +[HAM1_O:HAIT1] +I,I, I think I've got the wrong address... + +[HAM1_P:HAIT1] +Do me these tings, Tommy...... + +[HAM1_Q:HAIT1] +The Cubans, nasty proud foofoos, mmm, + +[HAM1_R:HAIT1] +been making my lovely Haitian boys shake de heads. + +[HAM1_S:HAIT1] +Now they told the policeman where me been stashing my powders. + +[HAM1_T:HAIT1] +Dey tink it drugs, them stupid. + +[HAM1_U:HAIT1] +Now be a good boy Tommy and go and get the powders for Auntie Poulet. + +[HAM1_V:HAIT1] +Yeah, yeah, sure, sure. + +[HAM1_1:HAIT1] +~g~The cops are closing in on our stashes. BE quick, and beat dem to it! + +[HAM1_2:HAIT1] +~r~The cops got to the stash first! + +[HAM1_3:HAIT1] +~g~Get this stuff back to the hideout! + +[HAM1_4:HAIT1] +~g~Good! Now get the next one! + +[HAM1_6:HAIT1] +~r~The Stash was destroyed, you idiot! + +[HAM1_7:HAIT1] +~g~The cops have got our stash! Retrieve it before they get away! + +[HAM1_8:HAIT1] +~g~The cops are on the way to pick up the stash, get a move on! + +[HAT_1A:HAIT1] +~g~Don't move a muscle, chump! + +[HAM1_B:HAIT1] +Come in, my dear, and rest your soul. + +{=================================== MISSION TABLE HAIT2 ===================================} + +[HAT2_B1:HAIT2] +~g~Get to the van that contains the flying bombs. + +[HAT2_B2:HAIT2] +Kill the Cubans... + +[HAT2_B4:HAIT2] +...and destroy their boats! + +[HAT2_B5:HAIT2] +~g~The Cubans are making a run for it. Don't let them get away! + +[HAT2_B6:HAIT2] +~r~The RC plane is getting too far out of range! + +[HAT2_B7:HAIT2] +~g~One of the Cubans in escaping in a car. Don't leave any witnesses! + +[HAT2_B8:HAIT2] +~r~You have no RC planes left! + +[HAT2_B9:HAIT2] +RC Planes: + +[HAT2_1:HAIT2] +Oh, sorry, I - I must have the wrong address... + +[HAT2_2:HAIT2] +Well, you might as well come in and rest your soles and have some tea. + +[HAT2_3:HAIT2] +Do you have something there for me, Tommy? + +[HAT2_4:HAIT2] +Yeah... + +[HAT2_5:HAIT2] +This place feels familiar to me, uh - it's - a smell from childhood - a deja vu... + +[HAT2_6:HAIT2] +Now Tommy, I'm going to whisper a lickle errand for you. Hear me well, aye? + +[HAT2_7:HAIT2] +You look like someone I, I... + +[HAT2_8:HAIT2] +The Cubans have fast boats they use to cross the seas with drugs. + +[HAT2_9:HAIT2] +It is their livelihood. + +[HAT2_10:HAIT2] +Me nephew bin making lickle flying bombs to take dem out. + +[HAT2_11:HAIT2] +Blow de boats to coffin wood. + +[HAT2_12:HAIT2] +Thanks for the tea. + +[HAT2_B3:HAIT2] { reVC update } +Press the ~h~~k~~VEHICLE_FIREWEAPON~ ~w~button to drop a bomb. Press the ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~button to cancel. + +{=================================== MISSION TABLE HAIT3 ===================================} + +[HAM3_A:HAIT3] +Hello? Hello - uh..I'm looking for somebody around here... + +[HAM3_B:HAIT3] +You looking hungry, Tommy. + +[HAM3_C:HAIT3] +Do I know you? + +[HAM3_D:HAIT3] +Hush now. + +[HAM3_E:HAIT3] +One more ting an I can let you go, Tommy. + +[HAM3_F:HAIT3] +My boys gone war wit dem Cuban boys. + +[HAM3_G:HAIT3] +But no guns. + +[HAM3_H:HAIT3] +Hmm, but de Cubans have a surprise comin'. + +[HAM3_I:HAIT3] +While they fight in de streets, you take this rifle and kill dem in de hubbub. + +[HAM3_J:HAIT3] +No one sees you, no one hear you. + +[HAM3_K:HAIT3] +Now, Tommy, you do this for me, and you no longer tied to my apron strings. + +[HAM3_1:HAIT3] +~g~We must win this battle. If all the Haitians die we lose. + +[HAM3_3:HAIT3] +~g~I expect the Cubans to cheat so be on your guard. + +[HAM3_4:HAIT3] +~r~You have been spotted! The mission is a failure! + +[HAM3_5:HAIT3] +~g~You must kill the Cubans from a distance. You must not be seen. + +[HAM3_8:HAIT3] +~g~Haitians are dying! Improve your aim. + +[HAM3_7:HAIT3] +~g~Look Out! The Cubans have brought reinforcements. Kill them all!! + +[HAM3_2:HAIT3] +~r~The Haitians have died! + +[HAM3_L:HAIT3] +Kay auntie.. + +{=================================== MISSION TABLE HOTEL ===================================} + +[INTB_A:HOTEL] +Tommy! Tommy, it's been too long. + +[INTB_B:HOTEL] +Hello Sonny. + +[INTB_C:HOTEL] +I know, I know. You're just overwhelmed with emotion. + +[INTB_D:HOTEL] +Fifteen years - seems like only yesterday. + +[INTB_E:HOTEL] +I guess that's a perspective thing. + +[INTB_F:HOTEL] +Hey, doing time for the family is no piece of cake, + +[INTB_G:HOTEL] +but the family looks after its own, ok? + +[INTB_H:HOTEL] +So, how'd the deal go down - you sitting on some white gold? + +[INTB_I:HOTEL] +Look Sonny, we were set up. The deal was an ambush. Harry and Lee are dead. + +[INTB_J:HOTEL] +You better be kidding me Tommy. Tell me you still got the money. + +[INTB_K:HOTEL] +...no Sonny...I don't have the money. + +[INTB_L:HOTEL] +That was my money, Tommy, MY MONEY! + +[INTB_M:HOTEL] +You better not be screwing me Tommy because you know I'm not a man to be screwed with! + +[INTB_N:HOTEL] +Wait Sonny. + +[INTB_O:HOTEL] +You have my personal assurance that I'm going to get your money back and the drugs. + +[INTB_P:HOTEL] +And I'm gonna mail you the dicks of those responsible. + +[INTB_Q:HOTEL] +Hey, I already know that. You're not a fool Tommy, but I warn you, neither am I. + +[INTB_R:HOTEL] +If it was anybody else you'd be DEAD already. + +[INTB_S:HOTEL] +But because it's you, because we got history, I'm gonna let you handle this. + +[INTB_T:HOTEL] +Look, Sonny, you got my word. + +[INTB_U:HOTEL] +I'll be in touch. + +{=================================== MISSION TABLE ICECRE1 ===================================} + +[ICC1_1:ICECRE1] +~g~Use your Ice Cream van distribute drugs around Vice City. + +[ICC1_3:ICECRE1] +~g~You receive money for each transaction you make, but the more transactions you make the more police attention you get. + +[ICC1_4:ICECRE1] +~g~There aren't any customers in this area try another one. + +[ICC1_5:ICECRE1] +Deals done: + +[ICC1_6:ICECRE1] +~g~Use the Mr. Whoopee van to distribute Cherry Poppers product around Vice City. + +[ICC1_7:ICECRE1] +~g~You receive money for each transaction you make, but the more transactions you make the more police attention you get. + +[ICC1_9:ICECRE1] +~g~Local gangs will not appreciate you doing business on their turf so expect hostility if you do so. + +[ICC1_10:ICECRE1] +~g~You made ~1~ deals! + +[ICC1_11:ICECRE1] +~g~You made ~1~ deal! + +[ICC1_12:ICECRE1] +PROPERTY ACQUIRED! + +[ICC1_13:ICECRE1] +~r~You didn't make any deals! + +[ICC1_14:ICECRE1] +ICECREAM ASSET COMPLETED + +[ICC1_15:ICECRE1] +~g~The icecream factory will now generate revenue up to a maximum of $~1~. Make sure you collect it regularly. + +[ICC1_2:ICECRE1] +~g~Park your van and press the ~h~~k~~VEHICLE_HORN~~w~ to play your ice cream jingle to notify customers that your ready for business. + +[ICC1_16:ICECRE1] +~g~Use your Mr. Whoopee van to distribute Cherry Poppers product around Vice City. + +[ICC1_8:ICECRE1] +~g~To make a transaction, ~h~park your van ~g~and press the ~h~~k~~VEHICLE_HORN~ ~g~button to play the ice cream jingle to attract customers. + +[ICE_AT1:ICECRE1] +ICECREAM FACTORY ASSET COMPLETED + +[ICE_AT2:ICECRE1] +~g~The Cherry Popper factory will now generate revenue up to a maximum of $~1~. Make sure you collect it regularly. + +[ICC1_17:ICECRE1] +Distribution mission over + +[ICC1_18:ICECRE1] +Total ice cream sales: $~1~ + +[ICC1_19:ICECRE1] +Total deals done: ~1~ + +{=================================== MISSION TABLE ICECUT ===================================} + +[ICC1_A:ICECUT] +Who are you? + +[ICC1_B:ICECUT] +Your new owner. + +[ICC1_C:ICECUT] +Were you now, or at any time, a child? + +[ICC1_D:ICECUT] +What are you talking about? + +[ICC1_E:ICECUT] +Were you a child? + +[ICC1_F:ICECUT] +Yes! Calm down! What's wrong with you? + +[ICC1_G:ICECUT] +I knew it. A child. + +[ICC1_H:ICECUT] +A dirty, stinking, sniveling, snotting, vile, puking, crying little baby! + +[ICC1_K:ICECUT] +Ow! Calm down. + +[ICC1_L:ICECUT] +I HATE babies, and I hate children. + +[ICC1_N:ICECUT] +Enough already! + +[ICC1_P:ICECUT] +You make soft ice cream, okay? It's purely for kids. + +[ICC1_Q:ICECUT] +What kind of psycho are you? + +[ICC1_R:ICECUT] +Just so I understand this, why make children happy if you hate them? + +[ICC1_S:ICECUT] +Oh, you stupid, sniveling, snotty- + +[ICC1_T:ICECUT] +Shut up! + +[ICC1_U:ICECUT] +- Brat! + +[ICC1_V:ICECUT] +The ice cream is a front. + +[ICC1_W:ICECUT] +We distribute other, non-dairy products. + +[ICC1_X:ICECUT] +And if I see a kid, I put him to good use. + +[ICC1_Y:ICECUT] +Don't I, kiddies? Yes - yes, I do. Mummy doesn't love you. + +[ICC1_Z:ICECUT] +She HATES you! + +[ICC1_ZA:ICECUT] +PROPERTY ACQUIRED! + +[ICC1_M:ICECUT] +They're dirty, sniveling, snotting, vile, puking little.. + +[ICC1_I:ICECUT] +A baby.. an awful, horrible, disgusting little boo hoo. + +[ICC1_J:ICECUT] +Mommy doesn't love you. You little shit! + +{=================================== MISSION TABLE INTRO ===================================} + +[INT1_A:INTRO] +Tommy Vercetti...Huh! shit. + +[INT1_B:INTRO] +Didn't think they'd ever let him out. + +[INT1_C:INTRO] +He kept his head down, helps people forget. + +[INT1_D:INTRO] +People will remember soon enough. + +[INT1_E:INTRO] +When they see him walking down the streets of their neighborhoods. + +[INT1_F:INTRO] +It will be bad for business. + +[INT1_G:INTRO] +Well, what are we gonna do, Sonny? + +[INT1_H:INTRO] +We treat him like an old friend and keep him busy out of town. OK? + +[INT1_I:INTRO] +We been talking about expanding down South, right? + +[INT1_J:INTRO] +Vice City is twenty-four carat gold these days. + +[INT1_K:INTRO] +The Colombians, the Mexicans, hell, + +[INT1_L:INTRO] +even those Cuban refugees are cutting themselves a piece of some nice action. + +[INT1_M:INTRO] +But it's all drugs, Sonny, + +[INT1_N:INTRO] +None of the families will touch that shit! + +[INT1_O:INTRO] +Times are changing. + +[INT1_P:INTRO] +The families can't keep their backs turned while our enemies reap the rewards. + +[INT1_Q:INTRO] +So, we send someone down to do the dirty work for us... + +[INT1_R:INTRO] +and cut ourselves a nice quiet slice. OK? + +[INT1_S:INTRO] +Who's our contact down there? + +[INT1_T:INTRO] +Ken Rosenberg, schmuck of a lawyer. + +[INT1_U:INTRO] +How's he gonna hold Vercetti's leash? + +[INT1_V:INTRO] +We don't need him to. + +[INT1_W:INTRO] +We just set him loose in Vice City, + +[INT1_X:INTRO] +we give him a little cash to get started. OK? + +[INT1_Y:INTRO] +Give it a few months. + +[INT1_Z:INTRO] +Then we go down, + +[INT1_A1:INTRO] +pay him a little visit, right? + +[INT1_A2:INTRO] +see how he's doing. + +[INT2_A:INTRO] +Hey, hey, guys! It's, uh, Ken Rosenberg here! Hey! Heh, heh, hey, great, hey! + +[INT2_B:INTRO] +Well, uh, I'm gonna drive you guys to the meet, okay? + +[INT2_C:INTRO] +Now, I've talked to the suppliers and they are very, huh-ha, + +[INT2_D:INTRO] +keen to start a business relationship, so, uh, + +[INT2_E:INTRO] +if all goes well, we should, uh, + +[INT2_F:INTRO] +be doing very nicely for ourselves, which is, y'know... + +[INT2_G:INTRO] +good.. + +[INT2_H:INTRO] +Okay, so. They're brothers, okay. + +[INT2_I:INTRO] +One operates the uh, the business, + +[INT2_J:INTRO] +and the other one does the flying. + +[INT2_K:INTRO] +Now they operate out of Mexico, + +[INT2_M:INTRO] +They own a farm in Panama. + +[INT2_N:INTRO] +Okay, all right, listen - + +[INT2_O:INTRO] +you guys, when we get there should I stay in the car, + +[INT2_P:INTRO] +or do you guys want me to come in with you guys? + +[INT2_Q:INTRO] +No. Stay in the car. + +[INT2_R:INTRO] +You know what, I thought about it, + +[INT2_S:INTRO] +I'm gonna watch the car. + +[INT3_A:INTRO] +Ok, that's them in the chopper. + +[INT3_B:INTRO] +All right, here's the deal. + +[INT3_C:INTRO] +They want a straight exchange on open ground. + +[INT3_D:INTRO] +All right? Ok. Stay tight, let's go. + +[INT3_E:INTRO] +All right. Take it easy, now. + +[INT3_F:INTRO] +I'm right here. The cars running, baby! + +[INT3_G:INTRO] +Got it? + +[INT3_H:INTRO] +100% pure grade-A Colombian, my friend. + +[INT3_I:INTRO] +The greens? + +[INT3_J:INTRO] +Tens and twenties...used. + +[INT3_L:INTRO] +Go on, get out of here! Drive! + +[INT4_A:INTRO] +Screwed! We're screwed! + +[INT4_B:INTRO] +This is soo typical, + +[INT4_C:INTRO] +I poke my head out of the gutter for one freakin' second, + +[INT4_D:INTRO] +and fate shovels shit in my face! + +[INT4_E:INTRO] +Well, screw you! + +[INT4_F:INTRO] +Shut your face and quit complaining! You're alive, aren't ya'? + +[INT4_G:INTRO] +Drop me right up here. + +[INT4_H:INTRO] +Go dump the car, then go get some sleep. + +[INT4_I:INTRO] +I'll drop by your office tomorrow and we can start sorting this mess out. + +[INT4_J:INTRO] +OK, that's a good idea, I'll get some sleep. + +[INT4_K:INTRO] +What are you gonna do? + +[INT4_L:INTRO] +Make my way back to my hotel, + +[INT4_M:INTRO] +clear my head, and figure this crap out. + +[INT4_N:INTRO] +OK. + +[INTRO1:INTRO] +I poke my head out of the gutter for one freakin' second and fate shovels shit in my face! + +[INTRO2:INTRO] +Go get some sleep. + +[INTRO3:INTRO] +What are you gonna do? + +[INTRO4:INTRO] +I'll drop by your office tomorrow and we can start sorting this mess out. + +[INT3_K:INTRO] +I think we have a deal, my friend. HA HA! + +[INT3_M:INTRO] +Let me see it. + +[INT2_L:INTRO] +no, no, no, wait... + +[INT3_N:INTRO] +Oh Shit! + +{=================================== MISSION TABLE KENT1 ===================================} + +[KPM1_A:KENT1] +Awright mush, I'm gonna save your Vera, mate. + +[KPM1_B:KENT1] +What the hell are you talking about? + +[KPM1_C:KENT1] +You know that wanker Diaz, the Bugle Master. + +[KPM1_D:KENT1] +He's got your boy, Lance. Word is your mate tried to jump him... + +[KPM1_E:KENT1] +didn't jump high enough if you know what I mean. + +[KPM1_F:KENT1] +Where did he take him? In plain English? + +[KPM1_G:KENT1] +Keep your barnet on! They got him across town at the junkyard. + +[KPM1_H:KENT1] +Bloody hell....you nutter! + +[KPM1_2:KENT1] +~r~You were supposed to get Lance out alive! + +[KPM1_3:KENT1] +LANCE'S HEALTH: + +[RESC_1:KENT1] +You ok to use a gun? + +[RESC_2:KENT1] +Sure...I guess...nice to see you, too. + +[RESC_3:KENT1] +Let's get out of here. + +[RESC_4:KENT1] +There goes my careful planning blown to shit, thanks to you. You screwed up real good, Lance! + +[RESC_5:KENT1] +He killed my brother. What do you expect me to do, mow his lawns? + +[RESC_6:KENT1] +We're gonna have to take out that prick Diaz before he takes us out. + +[RESC_7:KENT1] +Get patched up and meet me on the bridge to Star Island, ok? + +[RESC_8:KENT1] +Ok, I got you. + +[KPM1_1:KENT1] +~g~Lance is being held at the junk yard, Go and rescue him! + +[KPM1_4:KENT1] +~g~Get Lance to the hospital! + +[M_PASSN:KENT1] +MISSION PASSED! + +[KPM1_5:KENT1] +~g~Diaz's guys are after you! Get Lance to the hospital. + +{=================================== MISSION TABLE KICKSTT ===================================} + +[KICK1_2:KICKSTT] +~r~You did not get back to the bike quickly enough! + +[KICK1_7:KICKSTT] +~r~You have wrecked the bike! + +[KICK1_8:KICKSTT] +~g~Get on the bike! + +[KICK1_T:KICKSTT] +TIME TAKEN: + +[KICKTM:KICKSTT] +~b~EVENT TIME: ~1~:~1~ + +[KICKTM2:KICKSTT] +~b~EVENT TIME: ~1~:0~1~ + +[GETBIKE:KICKSTT] +~g~You have ~1~ seconds to return to a dirtbike before the mission ends. + +[KICK1_1:KICKSTT] +~g~Complete the course as quickly as possible. + +[KICK1_6:KICKSTT] +~g~Well done! + +[KICK_10:KICKSTT] +~g~Use the Sanchez to complete the course by passing through all of the checkpoints. + +[KICK_12:KICKSTT] +~r~You bottled it! + +[KICK_13:KICKSTT] +~r~You have taken too long! + +[KICK_11:KICKSTT] +~g~To leave the mission stand in the ~q~pink marker~g~ on foot. + +{=================================== MISSION TABLE LAWYER1 ===================================} + +[LAW1_A:LAWYER1] +Go get some sleep, he says - + +[LAW1_B:LAWYER1] +- I have been sitting in this chair all night with the lights off drinking coffee! + +[LAW1_C:LAWYER1] +This is a disaster. We are so screwed, man! + +[LAW1_D:LAWYER1] +These gorillas, listen to me, are gonna come down here and rip my head off. It's ridiculous! + +[LAW1_E:LAWYER1] +I did NOT go to law school for this! Ok, now what the hell are we gonna do? + +[LAW1_F:LAWYER1] +Shut up, sit down, relax. I'll tell you what we're gonna do. + +[LAW1_G:LAWYER1] +You're gonna find out who took our cocaine - and then, I'm gonna kill them. + +[LAW1_H:LAWYER1] +That's a good idea. That's a GREAT idea. Let me think, let me think, let me think. + +[LAW1_I:LAWYER1] +- OH! There's this retired Colonel, Colonel Juan Garcia Cortez. + +[LAW1_J:LAWYER1] +He's the one that helped me set up this deal + +[LAW1_K:LAWYER1] +well away from Vice City's established thugs. Ok? + +[LAW1_L:LAWYER1] +Now, listen. He's holding his party out in the bay on his expensive yacht + +[LAW1_M:LAWYER1] +and all of Vice City's big players are gonna be there. OK? + +[LAW1_N:LAWYER1] +I have an invite, of course I have an invite, + +[LAW1_O:LAWYER1] +but there's no way that I'm going out there, sticking my head out the door - no way! Not gonna happen. + +[LAW1_P:LAWYER1] +I told you, shut up! I'll go myself... + +[LAW1_Q:LAWYER1] +Ho - whoa, whoa! Hey, I like 1978 too, but, y'know, this isn't gonna be a beer and strippers do. + +[LAW1_R:LAWYER1] +I mean, no offense, but I think that you might turn heads on the runway for the wrong reasons. + +[LAW1_S:LAWYER1] +What's wrong with the way I'm dressed? + +[LAW1_T:LAWYER1] +Ok, look, here. Stop by Rafael's, tell him I sent 'ya. He'll make you look respectable. + +[LAW1_U:LAWYER1] +OK, go, c'mon... + +[LAWP_1:LAWYER1] +Buenas noches. + +[LAWP_2:LAWYER1] +I understand you are here on the behalf of Mr. Rosenberg, + +[LAWP_3:LAWYER1] +I hope any recent problems have not affected his health, or uh, + +[LAWP_4:LAWYER1] +mental well being, Mr...uh? + +[LAWP_5:LAWYER1] +Vercetti. He's just got a touch of...agoraphobia. + +[LAWP_6:LAWYER1] +Excellent, excellent. And you? + +[LAWP_7:LAWYER1] +I just want my merchandise. + +[LAWP_8:LAWYER1] +Ah. It's an unfortunate set of circumstances for all involved. + +[LAWP_9:LAWYER1] +Of course I have initiated my own lines of inquiry, + +[LAWP_10:LAWYER1] +but such a delicate matter will take time. + +[LAWP_11:LAWYER1] +Perhaps we will talk later. + +[LAWP_12:LAWYER1] +Meanwhile, let me introduce you to my daughter, + +[LAWP_13:LAWYER1] +Mercedes! + +[LAWP_14:LAWYER1] +Caramia, could you look after our guest while I attend to my necessary obligations? + +[LAWP_15:LAWYER1] +Of course, daddy. + +[LAWP_16:LAWYER1] +Please excuse me. + +[LAWP_17:LAWYER1] +Mercedes!? + +[LAWP_18:LAWYER1] +You try living with it. + +[LAWP_19:LAWYER1] +Anyway, let me point out some of our more distinguished guests... + +[LAWP_20:LAWYER1] +That's our congressman Alex Shrub with rising silicone star Candy Suxxx... + +[LAWP_21:LAWYER1] +And have you met my lovely wife Laura? No? + +[LAWP_22:LAWYER1] +Well, unfortunately she's in Alabama. This is Candy. + +[LAWP_23:LAWYER1] +And over there we have the Vice City Mambas' star tight end, BJ - + +[LAWP_24:LAWYER1] +always the charmer. + +[LAWP_25:LAWYER1] +I blocked down on him and then I put him in a wheelchair! + +[LAWP_26:LAWYER1] +Haha, that is good! + +[LAWP_27:LAWYER1] +Well now, I'm looking at some prime real estate property. + +[LAWP_28:LAWYER1] +And that poolside amphibian is Jezz Torrent, + +[LAWP_29:LAWYER1] +lead singer with Love Fist. + +[LAWP_30:LAWYER1] +Can I tell yous - do you know how they play ping-pong in Thailand? + +[LAWP_31:LAWYER1] +Let me tell you's, + +[LAWP_32:LAWYER1] +it does not involve a paddle, if you know what I mean! + +[LAWP_33:LAWYER1] +Impotent. + +[LAWP_34:LAWYER1] +And the chatty trio. + +[LAWP_35:LAWYER1] +That sleeping sweat gland is Papa's right hand gimp, Gonzalez + +[LAWP_36:LAWYER1] +and the other two are Pastor Richards + +[LAWP_37:LAWYER1] +and pseudo intellectual film director, Steve Scott. + +[LAWP_38:LAWYER1] +...passion with the nympho invaders, + +[LAWP_39:LAWYER1] +when the giant shark comes in and + +[LAWP_40:LAWYER1] +just bites their dicks off! + +[LAWP_41:LAWYER1] +Ha now, you never saw anything like that before, have you? + +[LAWP_42:LAWYER1] +Colonel! + +[LAWP_43:LAWYER1] +your parties as ever are a triumph, hahahaha! + +[LAWP_44:LAWYER1] +I can only apologize for my late arrival. + +[LAWP_45:LAWYER1] +Ah, de nada amigo. How do we find you? + +[LAWP_46:LAWYER1] +Our business is very trying - barbarians at the gates. + +[LAWP_47:LAWYER1] +A time for rewarding one's friends and liquidating one's enemies, amigo. + +[LAWP_48:LAWYER1] +Who's the loudmouth? + +[LAWP_49:LAWYER1] +Ricardo Diaz. He's Mr. Coke. + +[LAWP_50:LAWYER1] +Mercedes! + +[LAWP_51:LAWYER1] +Oh, I was just taking my friend back into town. + +[LAWP_52:LAWYER1] +Another time, Ricardo! + +[LAWP_53:LAWYER1] +Let's get out of here. + +[LAWP_54:LAWYER1] +Actually, take me to the Pole Position club. + +[LAW1_2:LAWYER1] +~g~Get to the Colonel's boat. + +[LAW1_4:LAWYER1] +~r~You killed the Colonel's daughter! + +[LAW1_5:LAWYER1] +Will you be working for my father? + +[LAW1_6:LAWYER1] +Maybe. + +[LAW1_7:LAWYER1] +Do you mind me resting my hand in your lap? + +[LAW1_8:LAWYER1] +Maybe... + +[LAW1_9:LAWYER1] +It's so difficult having a rich and powerful father. Vamos. + +[LAW1_10:LAWYER1] +See you around, handsome! + +[LAW1_11:LAWYER1] +I'm sure you will. + +[LAW1_12:LAWYER1] +Hmmmm...nice bike. + +[LAW1_13:LAWYER1] +No! My Bike! + +[LAW1_3:LAWYER1] +~g~Take the Colonel's daughter to the Pole Position club. + +[HELP20:LAWYER1] +Follow the ~h~T-shirt~w~ blip on the radar to find Rafael's. + +[LAW1_14:LAWYER1] +Wow, I like, really dig your motorcycle. + +[LAW1_15:LAWYER1] +Yeah babe, just picked it up from Howlin' Pete's + +{=================================== MISSION TABLE LAWYER2 ===================================} + +[LAW2_A:LAWYER2] +Ah! Well, I hope you're having a good time. Because I'm going out of my mind with worry here. What did you find out? + +[LAW2_B:LAWYER2] +That there are more criminals in this town than in prison. We need a lead from the streets... + +[LAW2_C:LAWYER2] +Ok, let me think, let me think, let me think - + +[LAW2_D:LAWYER2] +- AH! I've got it! + +[LAW2_E:LAWYER2] +Ok, There's this limey, some music industry slimeball, + +[LAW2_F:LAWYER2] +goes by the name of Kent Paul. + +[LAW2_G:LAWYER2] +Anyway, he's got his nose so far up most of Vice City's ass + +[LAW2_I:LAWYER2] +it's this guy, all right? He's always at The Malibu. + +[LAW2_J:LAWYER2] +I'll go pay him a visit. + +[LAW2B_A:LAWYER2] +Where'd you pop up from? + +[LAW2B_B:LAWYER2] +I've been looking for a bird like you for ages, mate... + +[LAW2B_C:LAWYER2] +Kent Paul, mate. Yeah, I'm the guvnor 'round here. + +[LAW2B_D:LAWYER2] +I'm looking for some English guy... + +[LAW2B_E:LAWYER2] +I sort things out, you know what I mean? + +[LAW2B_F:LAWYER2] +I'll treat you. Whatever you want, I'll get you, girl. + +[LAW2B_G:LAWYER2] +Don't you worry about a thing, mate. + +[LAW2B_H:LAWYER2] +Get lost, honey. + +[LAW2B_I:LAWYER2] +Oi oi oi oi oi! + +[LAW2B_J:LAWYER2] +You Kent Paul? I'm a friend of Rosenberg's... + +[LAW2B_K:LAWYER2] +Rosenberg...Rosenberg...Oh, that bonkers ambulance chaser! + +[LAW2B_L:LAWYER2] +That guy could defend an innocent man all the way to death row! + +[LAW2B_M:LAWYER2] +Give us another drink, bruv. + +[LAW2B_N:LAWYER2] +Everybody's a comedian. + +[LAW2B_O:LAWYER2] +Listen to me, I'm missing twenty keys and a lot of cash... + +[LAW2B_P:LAWYER2] +Drugs, mate? It's a mug's game. + +[LAW2B_Q:LAWYER2] +What do you know about it? + +[LAW2B_R:LAWYER2] +Oi oi! What I was coming to was, + +[LAW2B_S:LAWYER2] +there's some chef-cum-trumpetshifter who deals out kitchen of a hotel on Ocean Drive. + +[LAW2B_T:LAWYER2] +He's been looking real pleased with himself lately. You could go and check him out...?! + +[LAW2B_U:LAWYER2] +I will - and I'll be seeing you around. + +[LAW2B_V:LAWYER2] +Yeah, that's right. Go on - walk away, you mug. I'll knock you spark out! + +[LAW2B_W:LAWYER2] +Give me a drink - and where's that slut! + +[LAW2C_A:LAWYER2] +Oh, way to go, tough guy. Beat him to a pulp. That should make him real chatty. + +[LAW2C_B:LAWYER2] +You want some, too? + +[LAW2C_C:LAWYER2] +Hey, chill. I want what you want, brother. + +[LAW2C_D:LAWYER2] +Oh, yeah? And what's that? + +[LAW2C_E:LAWYER2] +Your green - and my dead brother's white lady. Unfortunately, you just silenced our lead. + +[LAW2C_F:LAWYER2] +Accidents happen. Get lost. + +[LAW2C_G:LAWYER2] +Hey, hey, whoa. No need to go all 'Lone Ranger' on my ass. + +[LAW2C_H:LAWYER2] +The way I see it - we two hombres in a strange town. We need to watch each other's back. + +[LAW2C_I:LAWYER2] +My back's just fine, brother... + +[LAW2C_J:LAWYER2] +You sure about that? Here, take this. + +[LAW2C_K:LAWYER2] +Follow me! + +[LAW2_1:LAWYER2] +Hey, whatchoo lookin' at? + +[LAW2_2:LAWYER2] +You better start talking.. + +[LAW2_3:LAWYER2] +Hey, make me, you prick! + +[LAW2_4:LAWYER2] +This way! + +[LAW2_5:LAWYER2] +I'm going to go see what I can dig up. I'll be watching you, Tommy. + +[LAW2_6:LAWYER2] +~g~Go to the Malibu Club and find Kent Paul. + +[LAW2_7:LAWYER2] +~g~Go and find the chef on Ocean Drive. + +[LAW2_10:LAWYER2] +~g~Drive back to the hotel. + +[LAW2_11:LAWYER2] +~g~Pick up his cell phone. + +[LAW2_12:LAWYER2] +Cell phone acquired! You can now receive phone calls. + +[LAW2_13:LAWYER2] +~g~You've left Lance behind! Go and get him! + +[LAW2_14:LAWYER2] +We gotta get the hell outta here! + +[GUN_2A:LAWYER2] +Hold the ~h~~k~~PED_LOCK_TARGET~ ~w~button to ~h~auto-target~w~, press the ~h~~k~~PED_FIREWEAPON~ ~w~button to ~h~fire! + +[GUN_2C:LAWYER2] +Hold the ~h~~k~~PED_LOCK_TARGET~ ~w~button to ~h~auto-target~w~, press the ~h~~k~~PED_FIREWEAPON~ ~w~button to ~h~fire! + +[GUN_2D:LAWYER2] +Hold the ~h~~k~~PED_LOCK_TARGET~ ~w~button to ~h~auto-target~w~, press the ~h~~k~~PED_FIREWEAPON~ ~w~button to ~h~fire! + +[HELP17:LAWYER2] +Press the ~h~~k~~PED_FIREWEAPON~ ~w~button to attack the chef. + +[HELP18:LAWYER2] +Press the~h~ ~k~~PED_FIREWEAPON~ ~w~button to attack the chef. + +[LAW3_11:LAWYER2] +Stand in the ~q~pink marker~w~ to view the weapons on offer. + +[LAW3_12:LAWYER2] +You can select weapons by pressing ~h~left~w~ or ~h~right~w~ on the ~h~directional button. + +[LAW3_13:LAWYER2] +If you have enough cash you can buy weapons by pressing the ~h~~k~~PED_SPRINT~ ~w~button. + +[LAW3_14:LAWYER2] +You can exit the shop by pressing the ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~button. + +[LAW3_15:LAWYER2] +Follow the ~h~Gun blip~w~ on the radar to find ~h~Ammu-Nation. + +[LAW2_15:LAWYER2] +~g~Go to Ammu-Nation. + +[LAW2_H:LAWYER2] +that if anybody knows the whereabouts of 20 k's of coke, + +[LAW2_K:LAWYER2] +Take it easy now. + +[LAW2_16:LAWYER2] +One thing you gotta realize about this town. You gotta pack some heat. + +[LAW2_17:LAWYER2] +C'mon, the local gun shop's a couple of blocks away. + +[LAW2_18:LAWYER2] +Tommy, every man needs a little R&R once in a while. + +[LAW2_19:LAWYER2] +This here's the Pole Position Strip Club. You might want to drop in some time. + +{=================================== MISSION TABLE LAWYER3 ===================================} + +[LAW3_A:LAWYER3] +Aaah! Oh, for god's sake, it's you! Oh, Jeez - I'm gonna need new pants! + +[LAW3_B:LAWYER3] +Hey, those psychos from up north - they've been on the horn, and they're coming down here soon. + +[LAW3_C:LAWYER3] +Now where is the goddamn money?! + +[LAW3_D:LAWYER3] +Relax, relax. We're not at that part yet. + +[LAW3_E:LAWYER3] +Ohhh... I thought that you were taking care of this, I really did! + +[LAW3_F:LAWYER3] +And now those guidos say we gotta do them a favor. + +[LAW3_G:LAWYER3] +You mean I gotta do 'em a favor. + +[LAW3_H:LAWYER3] +Oh, of course that's what I mean. Do I look like I can intimidate a jury? + +[LAW3_I:LAWYER3] +I couldn't intimidate a child - and believe me, I've tried. + +[LAW3_J:LAWYER3] +Now, look. It's either that, or Forelli's cousin, Giorgio, gets five years for fraud. + +[LAW3_K:LAWYER3] +You gotta take these guys OUT! + +[LAW3_L:LAWYER3] +I understand. Help the jury change their minds. Don't worry about it. + +[LAW3_M:LAWYER3] +No no no no no - NO! I tried that. The jury case didn't go so well, + +[LAW3_N:LAWYER3] +so MAKE them change their minds. + +[LAW3_1:LAWYER3] +Giorgio sends his regards. + +[LAW3_2:LAWYER3] +Remember, guilty is a dirty word. + +[LAW3_3:LAWYER3] +Innocent until I say otherwise. + +[LAW3_4:LAWYER3] +You know he's not guilty. + +[LAW3_5:LAWYER3] +You remember Giorgio? You remember he's innocent. + +[LAW3_6:LAWYER3] +Not guilty, understand? Good. + +[LAW3_8:LAWYER3] +~r~You killed a Juror! + +[LAW3_9:LAWYER3] +~g~Smash up the Juror's car to get him out! + +[HELP40:LAWYER3] +You can smash cars up by using the hammer or a similar weapon. + +[HELP41:LAWYER3] +or you can ram them with a vehicle + +[LAW3_20:LAWYER3] +~g~Smash up the Juror's car! + +[LAW3_21:LAWYER3] +I can't believe this is happening! + +[LAW3_22:LAWYER3] +Unbelievable! + +[LAW3_23:LAWYER3] +Ok! Ok, man! I get the message! + +[LAW3_24:LAWYER3] +~g~That hammer would be useful. + +[LAW3_7:LAWYER3] +~g~Go and intimidate the two jurors, but DON'T kill them! + +[HELP23:LAWYER3] +You can follow the ~h~hammer blip~w~ on the radar if you want to buy melee weapons from the hardware store. + +[LAW3_16:LAWYER3] +Dumb Florida Moron. + +[LAW3_17:LAWYER3] +Get out of the way! + +{=================================== MISSION TABLE LAWYER4 ===================================} + +[LAW4_A:LAWYER4] +Avery, it goes without saying... Tommy! Tommy! Any progress? No, no, no - tell me later, tell me later. + +[LAW4_B:LAWYER4] +Tommy, this is Avery Carrington - I believe you met at the party? + +[LAW4_C:LAWYER4] +Not in person. + +[LAW4_D:LAWYER4] +Howdy. + +[LAW4_E:LAWYER4] +Avery here has a proposition. + +[LAW4_F:LAWYER4] +Haven't we got other things on our mind? + +[LAW4_G:LAWYER4] +I'm trying to keep the wolves from the door, so could you please cut me some slack? + +[LAW4_H:LAWYER4] +I'm stretched like a wire and even if I'm dead by the end of the week, I'd like to think that I didn't die poor. + +[LAW4_I:LAWYER4] +Now just calm down, both of you. + +[LAW4_J:LAWYER4] +Son, you help me and any greaseballs giving you a hard time, I'll see to it they take a long dirt nap. + +[LAW4_K:LAWYER4] +Ok. What could I do for ya'? + +[LAW4_L:LAWYER4] +This delivery company's got its depot on some prime land. They won't sell. + +[LAW4_M:LAWYER4] +They're hanging on like a big old prairie rat, so we gotta go in there and smoke that vermin out. + +[LAW4_N:LAWYER4] +Head on down there and stir up a hornet's nest + +[LAW4_O:LAWYER4] +- the security will have their hands full and then you can sneak in and put 'em out of business. + +[LAW4_P:LAWYER4] +And you could drop by Rafael's for a change of clothes. You might be there a while, but yeah, go for it. + +[LAW4_Q:LAWYER4] +Should be a riot. + +[LAW4_R:LAWYER4] +If the balls drop like they should, stop by my office sometime... + +[LAW4_1:LAWYER4] +Please disperse. The management will discuss any grievances in the appropriate manner! + +[LAW4_2:LAWYER4] +Please disperse. Go back to your homes! + +[LAW4_3:LAWYER4] +Please disperse! This is inappropriate! + +[LAW4_4:LAWYER4] +Please disperse. You will all end up on the streets. + +[LAW4_5:LAWYER4] +Sticks out, boys! Let's crack some commie skulls! + +[LAW4_13:LAWYER4] +~g~Start fighting with at least 4 workers to get a riot started. + +[LAW4_14:LAWYER4] +~g~Destroy the vans in the compound! + +[HELP38:LAWYER4] +If you take out someone who's holding a weapon, they will drop it. + +[HELP39:LAWYER4] +You can target and shoot explosive barrels but keep your distance. + +{=================================== MISSION TABLE MIAMI_1 ===================================} + +[T4X4_1A:MIAMI_1] +~g~You have ~1~ seconds to collect ~y~24~g~ checkpoints. ~g~You may collect them in ~y~ANY ORDER. + +[T4X4_1B:MIAMI_1] +~y~PASS THROUGH~g~ the first checkpoint to start the ~r~TIMER. + +[T4X4_1C:MIAMI_1] +~1~ of 24! + +[GETBIK1:MIAMI_1] +You have ~1~ seconds to get on a PCJ 600! + +[GETBIK3:MIAMI_1] +~r~You need a PCJ 600 to attempt this mission! + +{=================================== MISSION TABLE MM ===================================} + +[BLOD_04:MM] +CAR HEALTH: + +[BLOD_05:MM] +~g~TARGET TIME: ~1~ Minute + +[BLOD_06:MM] +~g~TARGET TIME: ~1~ Minutes + +[BLOD_07:MM] +NEW Best Time: ~1~ Seconds + +[BLOD_08:MM] +Cars Destroyed: ~1~ + +[BLOD_09:MM] +$~1~ + +[BLOD_10:MM] +WINNER!! + +[BLOD_01:MM] +Drive through the checkpoints to increase your overall time. + +[BLOD_02:MM] +You will fail if your overall time reaches zero. + +[BLOD_03:MM] +Get your overall time above the Target Time to win! + +{=================================== MISSION TABLE OVALRIG ===================================} + +[HOTR_01:OVALRIG] +~g~The Race lasts for 12 laps. Only 1st, 2nd and 3rd places qualify for winnings. + +[HOTR_02:OVALRIG] +~g~If your car is destroyed you will be disqualified. + +[HOTR_03:OVALRIG] +~g~When your car is damaged you can get it repaired at the pitstop. + +[HOTR_04:OVALRIG] +~g~This is the way to leave the stadium. + +[HOTR_05:OVALRIG] +Car Health: + +[HOTR_06:OVALRIG] +Laps: + +[HOTR_07:OVALRIG] +New best time: ~1~:0~1~ + +[HOTR_08:OVALRIG] +Time: ~1~:~1~ + +[HOTR_10:OVALRIG] +Race Time: + +[HOTR_09:OVALRIG] +Position: + +[HOTR_12:OVALRIG] +~r~Your car has been destroyed! + +[HOTR_13:OVALRIG] +~r~You didn't win the race! + +[HOTR_14:OVALRIG] +~r~You have been disqualified! + +[HOTR_15:OVALRIG] +Time: ~1~:~1~ + +[HOTR_16:OVALRIG] +Time: ~1~:0~1~ + +[HOTR_17:OVALRIG] +Best Time: ~1~:~1~ + +[HOTR_18:OVALRIG] +Best Time: ~1~:0~1~ + +[HOTR_19:OVALRIG] +Best Time: NA + +[HOTR_20:OVALRIG] +New Best Time: ~1~:~1~ + +[HOTR_21:OVALRIG] +New Best Time: ~1~:0~1~ + +[HOTR_22:OVALRIG] +Best Result: NA + +[HOTR_23:OVALRIG] +Best Result: 1st + +[HOTR_24:OVALRIG] +Best Result: 2nd + +[HOTR_25:OVALRIG] +Best Result: 3rd + +[HOTR_26:OVALRIG] +Best Result: ~1~th + +[HOTR_27:OVALRIG] +Best Lap Time: ~1~.~1~ seconds + +[HOTR_28:OVALRIG] +Best Lap Time: ~1~.0~1~ seconds + +[HOTR_29:OVALRIG] +$~1~ + +[HOTR_30:OVALRIG] +1ST PLACE + +[HOTR_31:OVALRIG] +2ND PLACE + +[HOTR_32:OVALRIG] +3RD PLACE + +[HOTR_33:OVALRIG] +Best Lap Time: NA + +[HOTR_11:OVALRIG] +New best lap time: ~1~.~1~ seconds + +[HOTR_34:OVALRIG] +New best lap time: ~1~.0~1~ seconds + +{=================================== MISSION TABLE PHIL1 ===================================} + +[PHIL1_A:PHIL1] +Phil? + +[PHIL1_B:PHIL1] +RUN! + +[PHIL1_C:PHIL1] +Run + +[PHIL1_E:PHIL1] +Shit Phil, you drink that stuff? + +[PHIL1_F:PHIL1] +Hell, you don't have to drink it + +[PHIL1_G:PHIL1] +- just a good whiff will set you off. Hoowwee! + +[PHIL1_H:PHIL1] +Listen Phil, you said you could fix me up with some firepower... + +[PHIL1_I:PHIL1] +Sure thing. + +[PHIL1_J:PHIL1] +There's some Mexican gun-runner been doing me for business of late. + +[PHIL1_K:PHIL1] +He does his weekly run about now. + +[PHIL1_L:PHIL1] +Ram his hardware off the back of his trucks before he goes to ground. + +[PHIL1_M:PHIL1] +And you'd be doing me a favor while you're at it. + +[PHIL1_N:PHIL1] +Then finish him off. + +[PHI1_01:PHIL1] +~g~Go and knock the arms off the back of the dealers' trucks. + +[PHI1_02:PHIL1] +~g~The arms dealer dropped his load. Smash the crate and pick up the weapon. + +[PHI1_03:PHIL1] +~g~Looks like they have called for back up. + +[PHI1_04:PHIL1] +~g~Now go and finish off the remaining arms dealers. + +[PHI1_HP:PHIL1] +When using Detonator Grenades, throw a grenade then trigger the explosion at any time. + +[PHIL1_O:PHIL1] +Hoooooweeeeee! + +[PHIL1_D:PHIL1] +Never get a naked flame too close to one of Phil Cassidy's Boomshine stills! + +{=================================== MISSION TABLE PHIL2 ===================================} + +[PHIL2_A:PHIL2] +Hey Phil, how's it goin? + +[PHIL2_B:PHIL2] +Heeyyyy, Tommy. Howyadoin'? Ish been too long... + +[PHIL2_C:PHIL2] +I swear you should lay off that boomshine, man - + +[PHIL2_D:PHIL2] +smells like paint stripper. Making my eyes burn... + +[PHIL2_E:PHIL2] +Shshs shhh youshelf Tommy, + +[PHIL2_F:PHIL2] +and come over here because there's someshin' I wanna show you.. someshin. + +[PHIL2_G:PHIL2] +Woof! God! Should I be able to smell that from way over here? I'm feeling woozy. + +[PHIL2_H:PHIL2] +Don'tchaworry about the shmell Tommy, you jush wash thish. + +[PHIL2_I:PHIL2] +Shittycheapbatteriesh or shumin'. There'sh shum more on the bench. + +[PHIL2_J:PHIL2] +TA-DAAA! + +[PHIL2_K:PHIL2] +Aww Damn! + +[PHI2_01:PHIL2] +~g~Quick, get Phil to the hospital. + +[PHI2_03:PHIL2] +~r~Phil Cassidy is dead!!! Now who's gonna supply arms in Vice City? + +[PHI2_05:PHIL2] +Not the hospital, man! Too many cops and Viet Cong! + +[PHI2_06:PHIL2] +There's an ex-army surgeon owes me a few favors and a lawnmower. + +[PHI2_07:PHIL2] +He's got a place down Little Havana - ooo look, a giant fish. + +[PHI2_08:PHIL2] +Watch out! Charlie in the tree line! + +[PHI2_09:PHIL2] +Is it me or are the roads made of jelly? + +[PHI2_10:PHIL2] +Broken Spoon to Mother Hen, you copy? + +[PHI2_11:PHIL2] +Spooney Wooney Woo Woo Woooo! + +[PHI2_12:PHIL2] +He's come for me boy! + +[PHI2_13:PHIL2] +Black feathered wings beating all around... + +[PHI2_14:PHIL2] +It's beautiful, man ... it's beautiful ... but so cold ... + +[PHI2_15:PHIL2] +10-4 we've got a drunk driver. + +[PHI2_04:PHIL2] +PHIL'S HEALTH: + +[PHI_AS1:PHIL2] +PHILS PLACE ASSET COMPLETED + +[PHI_AS2:PHIL2] +~g~New Weapons available to purchase from Phils Place. + +{=================================== MISSION TABLE PIZZA ===================================} + +[PIZ1_01:PIZZA] +~g~Go deliver these pizzas, you must throw the pizza to the customers. Do a drive-by to throw the pizzas. + +[PIZ1_02:PIZZA] +~g~You have thrown all your pizzas, go back and get some more. + +[PIZ1_05:PIZZA] +~g~You have five minutes to deliver the orders before the customers phone another pizza shop. + +[PIZ1_07:PIZZA] +~r~You killed the customer! You're fired. + +[PIZ1_08:PIZZA] +~r~You are out of time. You're fired. + +[PIZ1_09:PIZZA] +~r~You destroyed our bike! You're fired. + +[PIZ1_11:PIZZA] +Hey! Get back on the bike! + +[PIZ1_12:PIZZA] +Pizzas left: + +[PIZ1_06:PIZZA] +Press the~h~ ~k~~TOGGLE_SUBMISSIONS~~w~ when on the bike to cancel the mission. + +[PIZ1_13:PIZZA] +Get these delivered nice and hot. + +[PIZ1_14:PIZZA] +Pal, pizza's for you. + +[PIZ1_15:PIZZA] +Hey, come on Mister, deliver these quick. + +[PIZ1_16:PIZZA] +What are you waiting around for Mister? You got pizza to deliver. + +[PIZ1_17:PIZZA] +I know you didn't want to be a pizza boy, well I don't give a damn. + +[PIZ1_18:PIZZA] +Deliver these. + +[PIZ1_19:PIZZA] +These need delivering. + +[PIZ1_20:PIZZA] +Come on Mister, deliver these things or you're sacked. + +[PIZ1_21:PIZZA] +We got people waiting pal. + +[PIZ1_22:PIZZA] +What are you waiting around for? These need delivering! + +[PIZ1_23:PIZZA] +Deliver the damn food Mister. + +[PIZ1_24:PIZZA] +These need delivering pal. + +[PIZ1_25:PIZZA] +Man, can you take these? + +[PIZ1_26:PIZZA] +Mister, deliver these pronto, avamos amigo. + +[PIZ1_27:PIZZA] +Come on, we're in a rush, deliver these. + +[PIZ1_28:PIZZA] +You again? well deliver these quick pal. + +[PIZ1_29:PIZZA] +No wasting time this time pal. + +[PIZ1_30:PIZZA] +Come on you lazy bastard, deliver this crap on time. + +[PIZ1_31:PIZZA] +You'll never get a promotion unless you move faster this time. + +[PIZ1_32:PIZZA] +~r~Pizza's too hot to handle? + +[PIZ1_33:PIZZA] +~g~Return to the restaurant for more orders. + +[PIZ1_34:PIZZA] +~g~Pizza delivered, here's your cash. + +[PIZ_WON:PIZZA] +Pizza Mission Complete. Your max Health increased to 150. + +{=================================== MISSION TABLE PORN1 ===================================} + +[POR1_A:PORN1] +Action. + +[POR1_B:PORN1] +Whoa! Now that's big. + +[POR1_C:PORN1] +12 inches. That is regulation baby. + +[POR1_D:PORN1] +CUT!! Who IS this idiot? You! YOU! Why are you in my space? WHY? + +[POR1_E:PORN1] +What is all this crap? + +[POR1_F:PORN1] +Aliens? Fishing poles? + +[POR1_G:PORN1] +Who's ever seen a shark that big? + +[POR1_H:PORN1] +All this stuff's gotta go. + +[POR1_I:PORN1] +Why'd you get in this business, ya prick? + +[POR1_J:PORN1] +Huh? + +[POR1_K:PORN1] +For the pussy, that's why! What is this?? + +[POR1_L:PORN1] +This is my art - SECURITY! + +[POR1_M:PORN1] +Look, you pompous asshole, I own you now. I own all of this. + +[POR1_N:PORN1] +We're gonna turn this place around... + +[POR1_O:PORN1] +I'm gonna make you rich. + +[POR1_P:PORN1] +Uh. You're - You - you're Tommy Vercetti? But I thought that you were... + +[POR1_Q:PORN1] +That's right. + +[POR1_R:PORN1] +We're gonna be making some changes around here and start making some real money. + +[POR1_S:PORN1] +Actually, have you ever thought about, umm... + +[POR1_T:PORN1] +But first we're going to need some good-looking broads. + +[POR1_U:PORN1] +Yeh, girls are fine but you... whew! + +[POR1_02:PORN1] +~g~ Go and take out Candy's pimp, then return and pick up Candy. + +[POR1_04:PORN1] +Yo, Candy. I'm looking for movie talent - you interested? + +[POR1_05:PORN1] +Sure! But, you'd have to talk to my agent... + +[POR1_06:PORN1] +The HELL are you doin'? + +[POR1_07:PORN1] +You should have stayed at home today! + +[POR1_7B:PORN1] +Can you believe this asshole? + +[POR1_08:PORN1] +Hey Mercedes! + +[POR1_09:PORN1] +Hey Tommy! You wanna party? + +[POR1_10:PORN1] +Not now sweets. You interested in doing some movies? + +[POR1_11:PORN1] +Of course. As long as it's cheap and sleazy. + +[POR1_13:PORN1] +~g~Take the girls back to the Studio to meet Steve. + +[POR1_17:PORN1] +Whoa, cool shark! + +[POR1_18:PORN1] +~r~Mercedes is dead! + +[POR1_20:PORN1] +Tommy where are you going? Get back here! + +[POR1_21:PORN1] +Where are you going? + +[POR1_22:PORN1] +Tommy, when are we going to spend some time alone together? + +[POR1_01:PORN1] +~g~Candy Suxxx would be perfect for a starring role! + +[POR1_12:PORN1] +~g~Take Candy with you to meet up with Mercedes. + +[POR1_16:PORN1] +Maybe later, babe... + +[POR1_24:PORN1] +~g~Go back and collect Candy. + +[POR1_25:PORN1] +~g~You have left Candy behind, go and get her. + +[POR1_23:PORN1] +~g~Candy will be taking care of business ~h~Downtown~g~. + +[POR1_26:PORN1] +~g~Here's Candy, looks like she has been with Congressman Shrub again. + +[POR1_15:PORN1] +Tommy, you coming in for a warm-up? + +[POR1_14:PORN1] +Heh heh - you're hired! + +[POR1_27:PORN1] +Come on, let's go. + +[POR1_28:PORN1] +Tommy be careful! My implants aren't insured yet! + +[POR1_29:PORN1] +You call that driving? + +[POR1_30:PORN1] +I can't do porno after this! + +[POR1_31:PORN1] +What? Are you trying to kill me? I thought I was the star! + +{=================================== MISSION TABLE PORN2 ===================================} + +[POR2_A:PORN2] +How's filming going, Steve? + +[POR2_B:PORN2] +Well, Candy is a natural and that new girl - she's insatiable! + +[POR2_C:PORN2] +She went through half the cast and crew before I even took a light reading. + +[POR2_D:PORN2] +Anyway, hey, tomorrow we're going on location to shoot the boat scenes - + +[POR2_E:PORN2] +Boat scenes?! What boat scenes? + +[POR2_F:PORN2] +The fishermen are in the throes of passion when this giant shark comes in - + +[POR2_G:PORN2] +What'd I say about the giant shark? + +[POR2_H:PORN2] +I said, 'NO GIANT SHARK', alright? + +[POR2_I:PORN2] +Just keep the cameras pointed at the poontang! + +[POR2_J:PORN2] +Ok ok, hey Tommy, a guy's gotta try, right? + +[POR2_K:PORN2] +Get those flyers printed up? + +[POR2_L:PORN2] +Yeah, but nobody's gonna let us distribute those things, I mean + +[POR2_M:PORN2] +They're just too, uh, they're unimaginative. + +[POR2_N:PORN2] +You don't worry about that. + +[POR2_O:PORN2] +I've got my own ideas for distribution. + +[POR2_P:PORN2] +O.K. Hey, Candy, uh - in my trailer. + +[POR2_01:PORN2] +~g~There is a seaplane that was used as a prop in some old indie film round the back of the studios. + +[POR2_02:PORN2] +~g~Pick one of the checkpoints to start dropping the flyers from. + +[POR2_03:PORN2] +~g~Drop the flyers all the way to the end checkpoint. + +[POR2_04:PORN2] +~r~LOW FUEL!!! + +[POR2_05:PORN2] +~g~Use it to distribute the flyers around town. + +[DILDO:PORN2] +Skimmer Fuel: + +[POR2_Q:PORN2] +Oh, boy. + +[PORN2_9:PORN2] +~g~You have ~1~ seconds to return to a Skimmer before the mission ends. + +{=================================== MISSION TABLE PORN3 ===================================} + +[POR3_A:PORN3] +Ok, what's the problem now? + +[POR3_B:PORN3] +SSShhhh! + +[POR3_C:PORN3] +Well, after his close encounter with the nympho-invaders, + +[POR3_D:PORN3] +our hero finds himself unable to think of anything but this huge phallic mountain - + +[POR3_E:PORN3] +and that's when I want to do the scene with the vat of mashed potatoes, but then we, uh - + +[POR3_F:PORN3] +I don't give a crap about that! + +[POR3_G:PORN3] +J - Just keep going, keep going! + +[POR3_H:PORN3] +Hey Tommy... + +[POR3_I:PORN3] +You mentioned something about some legal problem on the phone? + +[POR3_J:PORN3] +Congressman Alex Shrub has jumped on the pre-election bandwagon, he's going after the puritan vote. + +[POR3_K:PORN3] +Rumors are he's gonna support measures to restrict, shall we say, + +[POR3_L:PORN3] +the more fleshy aspects of this nation's great entertainment industry. + +[POR3_M:PORN3] +Great. + +[POR3_N:PORN3] +Candy! You know Shrub, + +[POR3_O:PORN3] +you guys get up to anything kinky? + +[POR3_P:PORN3] +Oh yeah, oh yeah, oh yeah! Yes yes yes YES OOOoooh! + +[POR3_Q:PORN3] +Please - tell me you got that. + +[POR3_R:PORN3] +Was that part of the, uh... or was she talking to..? + +[POR3_S:PORN3] +Hey, I can never tell. Anyway... + +[POR3_T:PORN3] +You're probably best following her after the shoot, + +[POR3_U:PORN3] +see if she'll lead you to their new love nest. + +[POR3_V:PORN3] +You got a camera? + +[POR3_X:PORN3] +Yeah. Get him a camera. + +[POR3_02:PORN3] +~r~You've killed the Congressman! There's no way you can blackmail him now. + +[POR3_03:PORN3] +~r~You've alerted the Congressman's protection, they will get him out of there immediately. + +[POR3_04:PORN3] +Uh, Candy, could you call me Martha? + +[POR3_05:PORN3] +Oh Alex - I mean Martha. Whatever you say... + +[POR3_06:PORN3] +Martha, someone's watching.. how kinky. + +[POR3_07:PORN3] +You! Give me that camera! + +[POR3_01:PORN3] +~g~Follow Candy's ~h~Stretch~g~. + +[POR3_15:PORN3] +~r~You trashed Candy's Stretch! + +[POR3_17:PORN3] +~g~Get back to the Porn Studios with the film. + +[POR3_19:PORN3] +~r~You ran out of film! + +[POR3_21:PORN3] +~g~You lost Candy's Stretch! + +[POR3_22:PORN3] +~g~The WK Chariot Hotel across from his balcony should provide an ideal photo-grabbing location. + +[POR3_23:PORN3] +~g~There is a side door that will allow you access to the hotel. + +[POR3_08:PORN3] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ ~w~button to ~h~target~w~ with the camera. + +[POR3_09:PORN3] +Press and hold the~h~ ~k~~PED_LOCK_TARGET~ ~w~button to ~h~target ~w~with the camera. + +[POR3_10:PORN3] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ ~w~button to ~h~zoom in ~w~with the camera and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ ~w~button to ~h~zoom out ~w~again. + +[POR3_11:PORN3] +Press the~h~ ~k~~PED_SNIPER_ZOOM_IN~ ~w~button to ~h~zoom in ~w~with the camera and the~h~ ~k~~PED_SNIPER_ZOOM_OUT~ ~w~button to ~h~zoom out ~w~again. + +[POR3_12:PORN3] +Press the~h~ ~k~~PED_FIREWEAPON~ ~w~button to take a picture. + +[POR3_13:PORN3] +Press the~h~ ~k~~PED_FIREWEAPON~ ~w~button to take a picture. + +[POR3_14:PORN3] +Press the~h~ ~k~~PED_FIREWEAPON~ ~w~button to take a picture. + +[POR3_20:PORN3] +~g~If you need transport, use the ~h~Sparrow~g~ round the back. + +[POR3_16:PORN3] +~g~You need three good blackmail photographs of Alex Shrub with Candy. + +[POR3_24:PORN3] +PHOTOS TAKEN: + +{=================================== MISSION TABLE PORN4 ===================================} + +[POR4_A:PORN4] +I'm sorry, but I just can't swallow this right now. + +[POR4_B:PORN4] +Oh COME ON darling! + +[POR4_C:PORN4] +He's hung like a sperm whale for pity's sake, + +[POR4_D:PORN4] +how can you not feel the part?! + +[POR4_E:PORN4] +But Stevie... + +[POR4_F:PORN4] +How's my star director? + +[POR4_G:PORN4] +Oh, man. The struggle between the artistic integrity and + +[POR4_H:PORN4] +the humping, pumping action continues unabated. + +[POR4_I:PORN4] +And before you ask, yes, all four videos will be released by their... + +[POR4_J:PORN4] +Honey, can you PLEASE keep the anaconda in the shot, + +[POR4_K:PORN4] +he costs more per hour than you do! + +[POR4_L:PORN4] +Oh, sorry Steve. + +[POR4_M:PORN4] +I was thinking, we need some kind of big stunt to really promote the launch. + +[POR4_N:PORN4] +Something that will make a real impact on the City - you got any ideas? + +[POR4_O:PORN4] +Well, in the old days they used to have gala events, + +[POR4_P:PORN4] +stars, limos, the night sky crisscrossed with searchlights... + +[POR4_Q:PORN4] +Searchlights! I've got an idea... + +[POR4_R:PORN4] +...yeah, yeah, yeah. The little sequined numbers, and the limos, oh, premieres + +[POR4_S:PORN4] +Oh, yes ma'am, of course ma'am, + +[POR4_T:PORN4] +and the press, and the barrage of lights... + +[POR4_01:PORN4] +~g~Go ~y~Downtown~g~ and adjust the spotlight on top of the building. + +[POR4_02:PORN4] +~g~A fast bike will be needed to jump from roof to roof. The Security Guard usually drives a ~y~PCJ 600~g~ to work... + +[POR4_03:PORN4] +~g~You will need to get onto the roofs of the buildings. There should be a lift into one of the upper offices... + +[POR4_06:PORN4] +~g~Return to the lower office if you need access to the rooftops again. + +[POR4_07:PORN4] +~g~You will need a bike so you can jump from building to building. + +[POR4_08:PORN4] +~g~Smash through the window to start the course. You have until 07:00 before it gets too light to get up there unseen. + +[POR4_09:PORN4] +~g~The pickups will show you which building to jump to next. + +[POR4_10:PORN4] +~r~It's too light to get up there unseen. + +[POR4_11:PORN4] +~g~Return to the ladder if you need access to the rooftops again. + +[POR4_05:PORN4] +~g~These stairs will lead round to a lower office. + +[POR_AS1:PORN4] +FILM STUDIO ASSET COMPLETED + +[POR_AS2:PORN4] +~g~Inter Global Films will now generate revenue up to a maximum of $~1~. Make sure you collect it regularly. + +{=================================== MISSION TABLE PROT1 ===================================} + +[PRO1_B:PROT1] +I can't stand this look. Tommy, whadaya say? Whadaya say we put a bar in... + +[PRO1_D:PROT1] +Listen to me, + +[PRO1_E:PROT1] +The time to take over this town is now. It's all out there waiting for us. + +[PRO1_F:PROT1] +We need to start seizing territory, + +[PRO1_G:PROT1] +let Vice City know we're the new players in town, know what I'm saying? + +[PRO1_I:PROT1] +What you need is a legitimate front Tommy, real estate. It's never done me no harm. + +[PRO1_J:PROT1] +We need to start using some muscle or we can kiss all that hard work goodbye. + +[PRO1_K:PROT1] +Local businesses know Diaz is dead, and they're refusing to pay protection! + +[PRO1_L:PROT1] +Ooh! We could try bribery... + +[PRO1_M:PROT1] +Bribery? Screw bribery! I'll show you how to make 'em scared! + +[PRO1_01:PROT1] +~g~Do a hit and run on the shop fronts and the owners will be begging for protection. + +[PRO1_03:PROT1] +~r~This was supposed to be a hit and run, not a 'hit and have coffee'. + +[PRO1_04:PROT1] +My livelihood, destroyed! + +[PRO1_05:PROT1] +Ruined...RUINED!! + +[PRO1_06:PROT1] +I pay through the ass for protection! + +[PRO1_07:PROT1] +My beautiful window display! + +[PRO1_08:PROT1] +My store. My wonderful store. + +[PRO1_09:PROT1] +Vercetti. Remember the name. + +[PRO1_10:PROT1] +I run this town now. ME! + +[BUYP1:PROT1] +You can now buy property in certain areas of the map. + +[BUYP2:PROT1] +If you see a green house pickup, you can buy that property. + +[PRO1_N:PROT1] +I'll be back here in five minutes... + +[PRO1_11:PROT1] +~g~Get to ~y~The North Point Mall~g~ in ~y~Vice Point~g~. + +[PRO1_12:PROT1] +~g~Smash the panes of glass in each shop front and the owners will be begging for new protection. + +[PRO1_A:PROT1] +Oh, we gotta redecorate this place. We gotta make it look older. + +[PRO1_C:PROT1] +You're my lawyer, Rosenberg, not my interior decorator. Got it? + +[BUYP3:PROT1] +Stand inside the pickup, then press the ~h~~k~~PED_ANSWER_PHONE~~w~ button to purchase that property. + +[PRO1_13:PROT1] +~g~You have five minutes to smash them all. + +{=================================== MISSION TABLE PROT2 ===================================} + +[PRO2_A:PROT2] +What's the problem? + +[PRO2_B:PROT2] +Some bar is refusing to pay. + +[PRO2_C:PROT2] +They reckon they're protected by a local gang of thugs. + +[PRO2_D:PROT2] +But don't worry Tommy, I can handle it. + +[PRO2_E:PROT2] +You call this handling it? + +[PRO2_F:PROT2] +You two, off your asses... + +[PRO2_G:PROT2] +Let's go. + +[PRO2_10:PROT2] +~g~Two more have made a run for it. Track them down and finish this. + +[PRO2_11:PROT2] +Get in the car, useless. + +[PRO2_02:PROT2] +Your protection needs a little more protection. + +[PRO2_03:PROT2] +Aw hell, not again! I don't need this crap! + +[PRO2_04:PROT2] +These idiots operate out of DBP Security around the block. + +[PRO2_05:PROT2] +You guys just sort it out amongst yourselves. + +[PRO2_06:PROT2] +I'll be seeing you later. + +[PRO2_07:PROT2] +Yeah, yeah, whatever. + +[PRO2_09:PROT2] +~g~Go and speak to the Front Page Bar Owner. + +[PRO2_01:PROT2] +~g~Take out the guards protecting the Front Page Bar and find out who supplied them. + +[PRO2_08:PROT2] +~g~DBP Security will know you are on your way, go and get them before they clear out. + +{=================================== MISSION TABLE PROT3 ===================================} + +[PRO3_A:PROT3] +You moron! What were you thinking?! + +[PRO3_B:PROT3] +Do you realize what this means?! + +[PRO3_C:PROT3] +We could all be sunk! + +[PRO3_D:PROT3] +The timer must have got screwed. + +[PRO3_E:PROT3] +That place was wired to go up like a firework factory. + +[PRO3_F:PROT3] +Then somebody tipped off the cops... + +[PRO3_G:PROT3] +What's the problem, fellas? + +[PRO3_H:PROT3] +Mike was supposed to torch some place in the mall, + +[PRO3_I:PROT3] +but he screwed the fuses and now the cops are crawling all over it. + +[PRO3_J:PROT3] +We gotta get our stuff and get out of here! + +[PRO3_K:PROT3] +Relax, both of you, let me think for a second! + +[PRO3_L:PROT3] +Tommy Vercetti just doesn't cut and run. + +[PRO3_M:PROT3] +The cops are gonna be going over that building with a fine toothed comb, right? + +[PRO3_N:PROT3] +But that takes time. + +[PRO3_O:PROT3] +We gotta go in and torch that place ourselves. + +[PRO3_P:PROT3] +Yeah, but... + +[PRO3_Q:PROT3] +No one but a cop could get within a mile of that place! + +[PRO3_R:PROT3] +So we go as cops. + +[PRO3_S:PROT3] +We gotta get uniforms - and we're gonna need a squad car. + +[PRO3_T:PROT3] +All thanks to you Mike. + +[PRO3_U:PROT3] +I'm sorry. + +[PRO3_V:PROT3] +I got it. + +[PRO3_W:PROT3] +What we got to do is lure the cops in with the finger, + +[PRO3_X:PROT3] +put them in a lock-up + +[PRO3_Y:PROT3] +and jump 'em. + +[PRO3_Z:PROT3] +Good plan. Let's go! + +[PRO3_A1:PROT3] +Alright. + +[PRO3_01:PROT3] +Ok Lance, let's get the cops' attention! + +[PRO3_02:PROT3] +~g~ Take a cop car and go and plant the bomb at the Tarbrush Coffee Shop in the Mall. + +[PRO3_03:PROT3] +~g~ You've left Lance behind, go and get him. + +[PRO3_04:PROT3] +~g~ Let's go. + +[PRO3_05:PROT3] +~r~You killed Lance! + +[PRO3_07:PROT3] +~g~ You have blown your cover. Hurry up and plant the bomb! + +[PRO3_09:PROT3] +Tie 'em up and gag 'em! + +[PRO3_10:PROT3] +Ooo. Fits perfectly! + +[PRO3_11:PROT3] +Bit tight around the crotch though... + +[PRO3_12:PROT3] +Oh yeah yeah, mine too. Mine too. + +[PRO3_13:PROT3] +Easy brother! No cop drives this bad! + +[PRO3_14:PROT3] +Remember - smile at the other cops + +[PRO3_15:PROT3] +Hey there officer. Nice badge, nice badge. + +[PRO3_16:PROT3] +Real smooth, Lance. + +[PRO3_17:PROT3] +Ok, timers are set, 5 seconds and ticking. + +[PRO3_18:PROT3] +5 seconds?!! We got to get the hell out of here! + +[PRO3_19:PROT3] +Now that got them really irritated. + +[PRO3_20:PROT3] +~g~ Get two cops to follow you into the garage. + +[PRO3_21:PROT3] +~g~Get a wanted level so the cops will follow you into the lock-up. + +[PRO3_22:PROT3] +~g~The lock-up door is blocked! You need to clear the door so it can close. + +[PRO3_23:PROT3] +~g~Walk into the marker to plant the bomb. + +[PRO3_24:PROT3] +~g~Get clear of the Cafe! + +[PRO_AS1:PROT3] +PROTECTION RING ASSET COMPLETED + +[PRO_AS2:PROT3] +~g~Vercetti Estate will now generate revenue up to a maximum of $~1~. Make sure you collect it regularly. + +[PRO3_08:PROT3] +~g~ Get back to ~h~Vercetti Estate~g~ on ~h~Starfish Island~g~. + +{=================================== MISSION TABLE RACES ===================================} + +[RACES_2:RACES] +~g~You need a vehicle to race, this is not a foot race! + +[RACES_3:RACES] +3..2..1.. GO GO GO! + +[RACES_8:RACES] +~r~You didn't win the race! + +[RACES00:RACES] +Race ~1~: + +[RACES01:RACES] +Terminal Velocity + +[RACES02:RACES] +Ocean Drive + +[RACES03:RACES] +Border Run + +[RACES04:RACES] +Capital Cruise + +[RACES05:RACES] +Tour! + +[RACES06:RACES] +V.C. Endurance + +[RACES07:RACES] +Entrance Fee: $~1~ + +[RACES08:RACES] +Best Time: ~1~:~1~ + +[RACES09:RACES] +Best Result: 1st + +[RACES10:RACES] +Best Result: 2nd + +[RACES11:RACES] +Best Result: 3rd + +[RACES12:RACES] +Best Result: 4th + +[RACES13:RACES] +Track Length: ~1~.~1~ km + +[RACES15:RACES] +Best Time: NA + +[RACES16:RACES] +Best Result: NA + +[RACES19:RACES] +You cannot afford to enter this race. + +[RACES22:RACES] +Best Time: ~1~:0~1~ + +[RACES23:RACES] +Track Length: ~1~.~1~ miles + +[RACES_1:RACES] +~g~Get a fast vehicle and get to the starting grid. + +[RACEHLP:RACES] +~w~Press the~h~ ~k~~PED_SPRINT~ ~w~button to start the selected race. Press the~h~ ~k~~VEHICLE_ENTER_EXIT~ ~w~button to exit. + +{=================================== MISSION TABLE RCHELI1 ===================================} + +[WRECKED:RCHELI1] +~r~The vehicle is wrecked! + +[RCH1_4:RCHELI1] +Checkpoints: + +[RCH1_7:RCHELI1] +~g~There are 20 checkpoints in total. + +[RCH1_12:RCHELI1] +~g~The RC helicopter is getting too far out of range! + +[RCH1_13:RCHELI1] +~r~The RC helicopter went out of range! + +[RCH1_8:RCHELI1] { reVC update } +~g~If you wish to quit this mission press the ~h~~k~~VEHICLE_FIREWEAPON~ ~g~button to detonate your RC Helicopter. + +{=================================== MISSION TABLE RCPLNE1 ===================================} + +[RCPL1_4:RCPLNE1] +~g~Compete in a CHECKPOINT RACE with 3 other RC Plane's + +[RCPL1_5:RCPLNE1] +~g~Fly through the checkpoints scattered throughout Vice City. + +[RCPL1_6:RCPLNE1] { reVC update } +~g~If you wish to quit this mission press the ~h~~k~~VEHICLE_FIREWEAPON~ ~g~button to detonate your RC Plane. + +[RCPL1_8:RCPLNE1] +~g~Your RC Plane is going out of range! + +[RCPL1_9:RCPLNE1] +~r~Your RC Plane went out of range! + +{=================================== MISSION TABLE RCRACE1 ===================================} + +[RCRC1_1:RCRACE1] +~g~Compete in a CHECKPOINT RACE with 3 other RC Bandits over 2 LAPS + +[RCRC1_3:RCRACE1] +~g~Final lap! + +[RCR1_4:RCRACE1] +laps left: + +[RCR1_1:RCRACE1] +~g~Compete in a checkpoint race with 3 other RC Cars. + +[RCR1_2:RCRACE1] +~g~Be the first to complete two laps of the track to win! + +[RCR1_6:RCRACE1] +~g~Your RC Car is going out of range! + +[RCR1_7:RCRACE1] +~r~Your RC Car went out of range! + +{=================================== MISSION TABLE ROCK1 ===================================} + +[RBM1_A:ROCK1] +AllllllllRrrighttt! + +[RBM1_B:ROCK1] +Yessss! Brilliant, bloody brilliant! + +[RBM1_D:ROCK1] +Hey, you ever met Love Fist before? + +[RBM1_E:ROCK1] +No, I haven't but I've always loved your music. + +[RBM1_F:ROCK1] +Let me introduce you to the band. + +[RBM1_G:ROCK1] +This is Percy, Dick, and Willy's in the kaze, and that was Jezz in the booth earlier, + +[RBM1_H:ROCK1] +and guys, I want you to meet a good friend of mine. + +[RBM1_I:ROCK1] +This is Tommy. We go way back. + +[RBM1_J:ROCK1] +All right, pal. + +[RBM1_K:ROCK1] +And eh, what was your name again? + +[RBM1_L:ROCK1] +Leave it out, Jezz you, remember - + +[RBM1_M:ROCK1] +don't be playing them games with me, mate, + +[RBM1_N:ROCK1] +I'm too crafty for that, sunshine! + +[RBM1_O:ROCK1] +You see, the thing is, Tom, the boys need some help. + +[RBM1_P:ROCK1] +They ain't too connected here, they don't have the old 'how's your father?' + +[RBM1_Q:ROCK1] +We need some drugs, pal! + +[RBM1_R:ROCK1] +Gonna get on the old Love Fist fury, you know?! + +[RBM1_S:ROCK1] +Well, this is Vice City, man. What's the problem? + +[RBM1_U:ROCK1] +Love Juice, man! + +[RBM1_V:ROCK1] +Love Juice? + +[RBM1_W:ROCK1] +Aye, two parts boomshine, 1 part trumpet, 5 fizz bombs and a liter of petrol. + +[RBM1_X:ROCK1] +Can you help us out, pal? + +[RBM1_Y:ROCK1] +Aw, it would really mean a lot to the boys. + +[RBM1_Z:ROCK1] +You can do that for the boys, right? + +[RBM1_7:ROCK1] +~r~You did not get the Love Juice in time! + +[RBM1_8:ROCK1] +~r~Mercedes is dead! + +[RBM1_10:ROCK1] +~r~You idiot! You have destroyed the merchandise! + +[RBM1_13:ROCK1] +~g~Get the 'Love Juice' and Mercedes to the band before they are needed on stage. + +[RBM1_15:ROCK1] +~r~You have lost the dealer, our cash and the drugs! + +[RBM1_17:ROCK1] +~g~Kill the dealer and get the drugs! + +[MOB_07A:ROCK1] +Hey mate, the guys could do with some company, if you know what I mean... + +[MOB_07B:ROCK1] +I know just the girl. + +[ROK1_5:ROCK1] +Hey, Mercedes! + +[ROK1_6:ROCK1] +Hiya, Tommy. And how are you? + +[ROK1_7:ROCK1] +Just fine. Listen, you fancy having Love Fist? + +[ROK1_8:ROCK1] +Ok, but just as a favor I expect returned.. + +[RBM1_14:ROCK1] +~g~You need a car or a motorcycle! + +[RBM1_1:ROCK1] +~g~Go and collect Mercedes from her apartment. + +[RBM1_12:ROCK1] +~g~Go and collect the 'Love Juice' ingredients from the dealer. + +[ROK1_2:ROCK1] +NO LONGER NEEDED + +[ROK1_3:ROCK1] +NO LONGER NEEDED + +[MERC_39:ROCK1] +I'll see you later, big boy. + +[RBM1_C:ROCK1] +Hey, Tommy! Glad you could make it. + +[RBM1_9:ROCK1] +~g~Go and collect some love juice from the dealer for Love Fist! + +[ROK1_1A:ROCK1] +Looking for something special? I got what you need! + +[ROK1_9:ROCK1] +Thanks for the money, sucker! + +[RBM1_T:ROCK1] +We need Love Juice, man, you know? + +{=================================== MISSION TABLE ROCK2 ===================================} + +[RBM2_A:ROCK2] +Tommy, man. Am I glad to see you! + +[RBM2_B:ROCK2] +What's going on? + +[RBM2_C:ROCK2] +Bad vibes, Tommy.... + +[RBM2_E:ROCK2] +There's this cat, we hardly know him, but he knows us. + +[RBM2_F:ROCK2] +Like this cat. Knows all about us. + +[RBM2_G:ROCK2] +Knows that Willy likes his ladies' underwear, eh! + +[RBM2_H:ROCK2] +Or that Percy likes Duran Duran! + +[RBM2_K:ROCK2] +Yeah, the love rocket thing, right. But listen, this cat... + +[RBM2_L:ROCK2] +yeh, yeh, the guy, he wants Love Fist dead. + +[RBM2_M:ROCK2] +Dead Tommy. + +[RBM2_N:ROCK2] +Love Fist gone. You know what they say, the good die young. + +[RBM2_O:ROCK2] +but Tommy, you gotta save Love Fist! + +[RBM2_P:ROCK2] +We got a signing in two hours and I think... + +[RBM2_Q:ROCK2] +And the boys think the stalker's gonna try some monkey business there. + +[RBM2_1:ROCK2] +~g~Drive the limo to the signing event and try to draw the psycho out. + +[RBM2_2:ROCK2] +~r~You've wrecked the band's car! + +[RBM2_3:ROCK2] +~g~Get to the signing! + +[RBM2_4:ROCK2] +~g~Get the Psycho! Don't let him escape! + +[RBM2_5:ROCK2] +~r~You lost him, you idiot! + +[RBM2_7:ROCK2] +~r~The fans have been attacked, the psycho won't show! + +[RBM2_8:ROCK2] +~r~The security guards have been attacked, the psycho won't show! + +[PSYCH_1:ROCK2] +I'll see Love Fist burn! + +[PSYCH_2:ROCK2] +Love Fist ruined my life! + +[RBM2_I:ROCK2] +Shut up ye fool. Just 'cause Jezz bangs sheep. + +[RBM2_R:ROCK2] +Oi shut it! + +[RBM2_D:ROCK2] +Aye, I'm not joking, it's heavy stuff man, heavy you know? + +[RBM2_J:ROCK2] +It's a love rocket thing, you know? + +{=================================== MISSION TABLE ROCK3 ===================================} + +[RBM3_A:ROCK3] +Tommy! Tommy! Tommy, man, that psycho's back! + +[RBM3_B:ROCK3] +What's going on? + +[RBM3_C:ROCK3] +That psycho won't leave Love Fist alone! + +[RBM3_D:ROCK3] +You didnae kill him man. And now he's back. + +[RBM3_E:ROCK3] +Yeah, yeah, yeah, and the thing is... + +[RBM3_F:ROCK3] +The thing is, we need someone to drive the limo we can trust, + +[RBM3_G:ROCK3] +cause that nutter keeps making threats! + +[RBM3_I:ROCK3] +We're all bricking ourselves, man. + +[RBM3_J:ROCK3] +Okay guys, calm down, I'll handle this. + +[RBM3_K:ROCK3] +Normally I wouldn't busy myself with driving around a bunch of drunken Scottish bisexuals, + +[RBM3_L:ROCK3] +but, in your case I'll make an exception. + +[RBM3_4:ROCK3] +~r~You've killed Love Fist! + +[RBM3_6:ROCK3] +DETONATION: + +[RBM3_1:ROCK3] +~g~Drive Love Fist to the venue. + +[RBM3_2:ROCK3] +While the bomb is armed if you try to leave the car it will explode... + +[RBM3_3:ROCK3] +If the detonation bar completely fills the bomb will explode. + +[RBM3_8:ROCK3] +The faster you drive the lower the detonation bar will go. + +[RBM3_7:ROCK3] +~g~BOMB DEFUSED! + +[ROK3_62:ROCK3] +so we thought we'd show you our Temple of Rock - + +[ROK3_63:ROCK3] +Get a feel for that Love Fist fury! + +[ROK3_64:ROCK3] +Listen to yourself, man. It's papier-mache and gaffa tape. + +[ROK3_65:ROCK3] +Hey, to the kids, it's a temple and we are the priests! + +[ROK3_66:ROCK3] +Aye, well, if the kids like their priests half cut and tone deaf, + +[ROK3_67:ROCK3] +who am I to argue? + +[ROK3_68:ROCK3] +Oh geez, the tape's getting chewed again. + +[ROK3_69:ROCK3] +At this rate, we'll never get to play live. + +[ROK3_70:ROCK3] +Oohh shite! My bowels... + +[ROK3_73:ROCK3] +Jezz is running the tape, + +[RBM3_9:ROCK3] +If you are stopped or drive slowly the detonation bar will increase. + +[RBM3_H:ROCK3] +I'm shitin' masel' man. I need ma ma! + +[ROK3_71:ROCK3] +We gotta get on with it - thanks again Tommy, Know what I am saying, nice one, bye! + +[ROK3_1:ROCK3] +At last man, time for a well earned drink. The venue's just a hundred yards down the road. + +[ROK3_2:ROCK3] +Better make it a large one then. Hey Tommy, change the tunes, man. + +[ROK3_3:ROCK3] +I get confused if my head ain't banging. Ah look, what's this? Hey Tommy, stick this tape on. + +[ROK3_4:ROCK3] +Love Fist. Your time polluting the airwaves is over. I gave you the chance to be friends. + +[ROK3_5:ROCK3] +Now, I'm giving you the chance to die. Try to slow down and your limousine will explode, along with your BIG, HAIRY ARSES! + +[ROK3_6:ROCK3] +Tommy pal, you gotta save the band! I'm getting bored of this. Just keep the pedal to the metal!! + +[ROK3_7:ROCK3] +We gotta find the bomb! Can't we just drive around all day? Aye, we've got plenty to drink.. + +[ROK3_8:ROCK3] +Won't the bomb not be in the engine? We'll have to stop to get it. We're all going to die! I'm gonna get drunk! + +[ROK3_9:ROCK3] +Hey, there's a queue here pal! The answer ain't in the drinks cabinet! Get out of my way! + +[ROK3_10:ROCK3] +Hey, the vodka bottle's got wires coming out of it! That's not vodka, that's BOOMSHINE! + +[ROK3_11:ROCK3] +WAAAAAAGGGHHHH!!!! And it's wired to blow!! WAAAAAAAAAAAAGGGHHHHHHHH!!!! + +[ROK3_12:ROCK3] +They always said the drink would kill me. I've seen this on the telly. you gotta pull out one of the wires. Which wire? I don't know, man. + +[ROK3_13:ROCK3] +I don't have a clue. Willy, say something. I'm gonna play bass in hell. + +[ROK3_14:ROCK3] +Tommy man, keep driving fast, pal. Somebody do something. Aye, clever! + +[ROK3_15:ROCK3] +'Somebody do something', what kind of crap is that, I've seen braver girls. Okay tough guy, you do something. + +[ROK3_16:ROCK3] +Look, man, I play a musical instrument I don't have a clue about bomb disposal. Willy could just suck the boomshine out with a straw. + +[ROK3_17:ROCK3] +Aye, I've heard that your good at that kind of thing. Hey, I was off my tits that night, as well you know! + +[ROK3_18:ROCK3] +Just pass Willy a straw! A straw?!?! This is the Love Fist Tour Bus! + +[ROK3_19:ROCK3] +Where am I gonna get a straw from, know wot I mean? Which wire, Tommy? The green one. There isn't a green one. + +[ROK3_20:ROCK3] +Or is this one green? Any of these wires look green to you? + +[ROK3_21:ROCK3] +Oh no! Death's on the cards! Everything looks green! I should have dumped you lot when I had the chance man. + +[ROK3_22:ROCK3] +Glory seeker. Capitalist. I've been carrying you for years. Shut up. You're a muppit. + +[ROK3_23:ROCK3] +A big screaming girl. Yeah. Shut up and pull a wire. Which wire? This one.. + +[ROK3_24:ROCK3] +NO! Man, we're okay. We ain't been blown up, pal. + +[ROK3_25:ROCK3] +Tommy, man, nice one. Rock and roll, man. Ain't we got a gig to go to? + +[ROK3_26:ROCK3] +A racket to make? Groupies to abuse? LOVE FIST! + +[ROK3_27:ROCK3] +Have you finished with that bottle? + +{=================================== MISSION TABLE SERG1 ===================================} + +[TEX1_A:SERG1] +Come in and park yourself on the hide, son. + +[TEX1_B:SERG1] +Hell, my daddy used to say, never look a gift horse in the mouth, and by golly, he never did. + +[TEX1_C:SERG1] +Would you like a drop of the old Kentucky? + +[TEX1_D:SERG1] +No thanks. + +[TEX1_E:SERG1] +A clean thinker! I like that. + +[TEX1_F:SERG1] +Now, the property business isn't all about high-falootin' paper pushing. + +[TEX1_G:SERG1] +It's about dirt! And the will to claim that dirt! You with me, son? + +[TEX1_H:SERG1] +Oh yeah. + +[TEX1_K:SERG1] +Persuasion's my forte. + +[TEX1_L:SERG1] +Yeh, he'll be down at the country club, down on the golf course. + +[TEX1_M:SERG1] +They don't allow guns, so his bodyguards won't be packing lawgivers. + +[TEX1_N:SERG1] +Go beat eight tons of crap out of him. + +[TEX1_O:SERG1] +Here now - I got you a membership, and boy you're going to need more appropriate clothing. + +[TEX1_2:SERG1] +~g~Now head to the Leaf Links Golf Club. + +[TEX1_3:SERG1] +Who's this guy? Boys, deal with him. + +[TEX1_6:SERG1] +Nice ass baby! + +[TEX1_7:SERG1] +Is this me? + +[TEX1_I:SERG1] +Well, I need some tenacious bastard to let go of some dirt, + +[TEX1_J:SERG1] +and you look to me like the kind of guy to persuade him. + +[TEX1_0:SERG1] +~g~The target is at the driving range enjoying a game of golf. Make sure it's his last. + +[TEX1_8:SERG1] +Each time you enter a caddy you automatically receive a golf club, providing your melee weapon slot is empty. + +[TEX1_9:SERG1] +Get him! + +[TEX1_10:SERG1] +Kill that Psycho! + +[TEX1_1:SERG1] +~g~Go and pick up some golfing clothes from Jocksports. + +{=================================== MISSION TABLE SERG2 ===================================} + +[TEX_2A:SERG2] +~g~Excellent! They've spotted you! + +[TEX_2B:SERG2] +~r~Fool! People have to WITNESS a Cuban doing the hit! + +[TEX_2C:SERG2] +~g~Go get yourself some Cuban gang colors in Little Havana! + +[TEX_2D:SERG2] +~g~Take out the Haitian Gang Lord at Romero's Funeral Parlor! + +[TEX2_A:SERG2] +Tommy, this is Donald Love. Donald, this here is Tommy Vercetti, + +[TEX2_B:SERG2] +the latest gunslinger to come to these parts. + +[TEX2_C:SERG2] +Yeh...uh... + +[TEX2_D:SERG2] +Donald, you just shut up and listen, and you might learn something. + +[TEX2_E:SERG2] +Now, nothing brings down real estate prices quicker than a good old-fashioned gang war - + +[TEX2_F:SERG2] +'cept maybe a disaster, like a biblical plague or something, + +[TEX2_G:SERG2] +but, that may be going too far in this case. + +[TEX2_H:SERG2] +You getting this down, you four-eyed prick? + +[TEX2_I:SERG2] +Now recently a Haitian gang lord died. Apparently the Cubans did it, nobody's certain. + +[TEX2_J:SERG2] +But let's make them certain! You disguise yourself as a Cuban hombre, + +[TEX2_K:SERG2] +and head on down to crash that funeral. Mix it up, and then high tail it. + +[TEX2_L:SERG2] +You getting this down, Donald? + +[TEX2_M:SERG2] +Well, that ought to put the coyote in the chicken coop, huh? + +[TEX2_N:SERG2] +And then we'll just sit back, and watch the prices tumble. + +[TEXEXIT:SERG2] +~g~Now get out of Little Haiti! + +{=================================== MISSION TABLE SERG3 ===================================} + +[TEX3_A:SERG3] +Now look here, son. I got a problem and I reckon you could help me with it. + +[TEX3_B:SERG3] +I'm no builder. + +[TEX3_C:SERG3] +No, I was thinking more of your demolition skills. + +[TEX3_D:SERG3] +Now this here, this is the development as planned and this, + +[TEX3_E:SERG3] +this is the property that we're looking at. + +[TEX3_F:SERG3] +You're trying to say this new office block is kind of in the way. + +[TEX3_G:SERG3] +You catch on quick. + +[TEX3_H:SERG3] +Now I'm going to head out of town for a while + +[TEX3_I:SERG3] +and if that office development were to face sudden and insurmountable structural problems, then I.. + +[TEX3_J:SERG3] +As a civil minded individual you'd feel obliged to step in and + +[TEX3_K:SERG3] +save the rejuvenation of an important area of the city? + +[TEX3_L:SERG3] +Where can I get more guys like you? + +[TEX3_1:SERG3] +~g~Use the RC helicopter to transport bombs to four demolition points on the building site. + +[TEX3_2:SERG3] +~g~You must place one bomb at each target. You can place bombs in any order. + +[TEX3_3:SERG3] +~g~Maneuver the RC helicopter next to a bomb to pick it up. it can carry one bomb at a time.. + +[TEX3_5:SERG3] +~g~If you place a bomb unsuccessfully you can pick it up and try again. + +[TEX3_7:SERG3] +~g~You must then place the remaining bombs in 7 minutes! + +[TEX3_8:SERG3] +~g~You missed the target! Pick up a bomb and try again! + +[TEX3_10:SERG3] +~g~Drop the bomb at a target. + +[TEX3_11:SERG3] +targets left: + +[TEX3_17:SERG3] +~r~You ran out of time! + +[TEX3_18:SERG3] +~r~Your RC Helicopter has been destroyed! + +[TEX3_19:SERG3] +~r~You dropped your bomb in the water! That ain't no way to fish! + +[TEX3_20:SERG3] +~r~The RC Helicopter is nearly out of range! + +[TEX3_21:SERG3] +~r~The RC Helicopter went out of range! + +[TEX3_24:SERG3] +Press the ~h~~k~~VEHICLE_LOOKLEFT~ ~w~button to rotate the helicopter counter-clockwise. + +[TEX3_25:SERG3] +Press the ~h~~k~~VEHICLE_LOOKRIGHT~ ~w~button to rotate the helicopter clockwise. + +[TEX3_27:SERG3] +~g~A central stairway allows access to all the floors in the building. + +[TEX3_31:SERG3] +~r~You destroyed the TOPFUN van that contained the bombs and RC helicopter! + +[TEX3_32:SERG3] +You can ~h~look behind~w~ by simultaneously pressing the ~h~~k~~VEHICLE_LOOKLEFT~~w~ and the ~h~~k~~VEHICLE_LOOKRIGHT~~w~ buttons. + +[TEX3_4:SERG3] { reVC update } +~g~To drop a bomb press the~h~ ~k~~VEHICLE_FIREWEAPON~ ~g~button. + +[TEX3_29:SERG3] { reVC update } +To drop a bomb press the~h~ ~k~~VEHICLE_FIREWEAPON~ ~w~button. + +[TEX3_26:SERG3] +Pressing the ~h~~k~~VEHICLE_BRAKE~ ~w~button ~w~decreases the rotor speed, causing the helicopter to~h~ descend. + +[TEX3_22:SERG3] +Pressing the ~h~~k~~VEHICLE_ACCELERATE~ ~w~button increases the rotor speed, causing the helicopter to ~h~ascend. + +[TEX3_16:SERG3] +~g~Get to the ~w~TOPFUN ~g~van near the building site to be demolished. + +[TEX3_33:SERG3] +Once you pick up a bomb the radar will show you the position of the target relative to the RC helicopter. + +[TEX3_34:SERG3] +An ~h~upwards pointing triangular blip ~w~indicates the target is ~h~above ~w~the RC helicopter. + +[TEX3_35:SERG3] +A ~h~downward pointing triangular blip ~w~indicates the target is ~h~below ~w~the RC helicopter. + +[TEX3_36:SERG3] +A ~h~square blip ~w~indicates the target is on the ~h~same level ~w~as the RC helicopter + +[TEX3_6:SERG3] +~g~Once you have picked up a bomb for the first time, the detonation timer will start. + +[TEX3_28:SERG3] +To ~h~pick up a bomb~w~, simply maneuver the RC helicopter next to it. The RC Helicopter can carry one bomb at a time. + +[TEX3_30:SERG3] +~g~To pick up a bomb, simply maneuver the RC helicopter next to it. The RC Helicopter can carry one bomb at a time. + +[TEX3_12:SERG3] +~g~Bomb planted! Only 3 more targets to go! Go back and get another bomb. + +[TEX3_13:SERG3] +~g~Bomb planted! Only 2 more targets to go! Go back and get another bomb. + +[TEX3_14:SERG3] +~g~Bomb planted! Only 1 more target to go! Go back and get another bomb. + +[TEX3_15:SERG3] +~r~Detonation timer initiated! ~g~You must plant the ~w~4 bombs ~g~in the remaining time! + +[TEX3_37:SERG3] +Pushing ~h~back on the analog stick ~w~decreases the rotor speed, causing the helicopter to~h~ descend. + +[TEX3_38:SERG3] { reVC update } +{ Pressing the ~h~~k~~VEHICLE_ACCELERATE~ ~w~button increases the rotor speed, causing the helicopter to ~h~ascend. } +Pushing ~h~forward on the analog stick ~w~increases the rotor speed, causing the helicopter to ~h~ascend. + +[TEX3_39:SERG3] +~g~To drop a bomb press the ~h~~k~~VEHICLE_HANDBRAKE~ ~g~button. + +[TEX3_40:SERG3] +To drop a bomb press the ~h~~k~~VEHICLE_HANDBRAKE~ ~w~button. + +[TEX3_23:SERG3] +Press the ~h~~k~~VEHICLE_TURRETUP~~w~ and ~h~~k~~VEHICLE_TURRETDOWN~~w~ buttons to tilt the helicopter in the direction you wish to maneuver it. + +{=================================== MISSION TABLE TAXI1 ===================================} + +[FARES:TAXI1] +FARES: + +[TAXI1:TAXI1] +~g~Look for a fare. + +[TSCORE2:TAXI1] +$~1~ + +[IN_ROW:TAXI1] +~1~ IN A ROW bonus! $~1~ + +[TAXI3:TAXI1] +~r~Your passenger fled in terror! + +[TAXI7:TAXI1] +~r~Your car is trashed, get it repaired. + +[TAXI4:TAXI1] +Fare complete! + +[TAXI5:TAXI1] +SPEED BONUS!! + +[TAXI6:TAXI1] +Taxi mission over + +[TAXIH1:TAXI1] +Stop near a highlighted pedestrian to pick them up then drive them to their destination before the time runs out. + +[FARE1:TAXI1] +~g~Destination ~w~'The Pole Position Club' ~g~in Ocean Beach. + +[FARE3:TAXI1] +~g~Destination ~w~'The Marina' ~g~in Ocean Beach. + +[FARE4:TAXI1] +~g~Destination ~w~'Ammu-Nation' ~g~in Ocean Beach. + +[FARE5:TAXI1] +~g~Destination ~w~'The Hardware store' ~g~in Washington Beach. + +[FARE6:TAXI1] +~g~Destination ~w~'The North Point Mall' ~g~in Vice Point. + +[MFARE1:TAXI1] +~g~Destination ~w~'Ammu-Nation' ~g~in Downtown. + +[MFARE2:TAXI1] +~g~Destination ~w~'the Terminal' ~g~at Escobar International Airport. + +[WFARE3:TAXI1] +~g~Destination ~w~'Sunshine Autos' ~g~in Little Havana. + +[WFARE4:TAXI1] +~g~Destination ~w~'Kaufman Cabs' ~g~in Little Haiti. + +[WFARE5:TAXI1] +~g~Destination ~w~'The Hardware store' ~g~in Little Havana. + +[WFARE6:TAXI1] +~g~Destination ~w~'Howlin Petes Bike Emporium' ~g~in Downtown. + +[FARE7:TAXI1] +~g~Destination ~w~'The Jewelers' ~g~in Vice Point. + +[FARE8:TAXI1] +~g~Destination ~w~'The Beach' ~g~in Ocean Beach. + +[FARE9:TAXI1] +~g~Destination ~w~'The Beach' ~g~in Washington Beach. + +[FARE10:TAXI1] +~g~Destination ~w~'The Beach' ~g~in Vice Point. + +[FARE11:TAXI1] +~g~Destination ~w~'Hospital' ~g~in Ocean Beach. + +[FARE12:TAXI1] +~g~Destination ~w~'Hospital' ~g~in Vice Point. + +[FARE13:TAXI1] +~g~Destination ~w~'Police Station' ~g~in Washington Beach. + +[FARE14:TAXI1] +~g~Destination ~w~'Police Station' ~g~in Vice Point. + +[FARE15:TAXI1] +~g~Destination ~w~'Pizza Restaurant' ~g~in Vice Point. + +[WFARE7:TAXI1] +~g~Destination ~w~'Police Station' ~g~in Little Havana. + +[WFARE8:TAXI1] +~g~Destination ~w~'Police Station' ~g~in Downtown. + +[WFARE9:TAXI1] +~g~Destination ~w~'Hospital' ~g~in Downtown. + +[WFARE10:TAXI1] +~g~Destination ~w~'Hospital' ~g~in Little Havana. + +[WFARE11:TAXI1] +~g~Destination ~w~'The Stadium' ~g~in Downtown. + +[WFARE12:TAXI1] +~g~Destination ~w~'Pizza Restaurant' ~g~in Little Haiti. + +[WFARE13:TAXI1] +~g~Destination ~w~'Pizza Restaurant' ~g~in Downtown. + +[WFARE14:TAXI1] +~g~Destination ~w~'Docks' ~g~in Viceport. + +[WFARE15:TAXI1] +~g~Destination ~w~'Chemist' ~g~in Little Haiti. + +[FARE2:TAXI1] +~g~Destination ~w~'The Malibu club' ~g~in Vice Point. + +{=================================== MISSION TABLE TAXICUT ===================================} + +[TAXC_A:TAXICUT] +Guess you're the new owner. + +[TAXC_B:TAXICUT] +What are you, mob? Cartel? You don't look Mexican... + +[TAXC_C:TAXICUT] +Anyhoo, I guess you better get on with the 'things are gonna change around here' crap, + +[TAXC_D:TAXICUT] +maybe threaten one of the drivers - + +[TAXC_E:TAXICUT] +go steady on Ted over there, he's just had his hernia fixed. + +[TAXC_F:TAXICUT] +Well, yeah. Things are going to change around here, lady. + +[TAXC_G:TAXICUT] +Oh crap, sonny. Might as well leave this to me - + +[TAXC_H:TAXICUT] +I've been doing this for years. + +[TAXC_I:TAXICUT] +Now hear this. + +[TAXC_J:TAXICUT] +We are now under new management and things are going to change around here again. + +[TAXC_K:TAXICUT] +Our new management, the + +[TAXC_L:TAXICUT] +- Which gang are you? + +[TAXC_M:TAXICUT] +Well, I'm not part of any gang actually. + +[TAXC_N:TAXICUT] +What's your goddamned name, kid? + +[TAXC_O:TAXICUT] +Vercetti, Tommy Vercetti. + +[TAXC_P:TAXICUT] +Our new management, the Vercetti Gang, + +[TAXC_Q:TAXICUT] +is gonna make sure we get no trouble. + +[TAXC_R:TAXICUT] +Capiche? Out! + +[TAXC_S:TAXICUT] +Did you like the 'capiche'? I liked the 'capiche'. + +[TAXC_T:TAXICUT] +So this is how it's worked in the past, + +[TAXC_U:TAXICUT] +We run the firm as usual. + +[TAXC_V:TAXICUT] +If we get any trouble from rival firms, you beat the crap out of them. + +[TAXC_W:TAXICUT] +Then they beat the crap out of us, + +[TAXC_X:TAXICUT] +then you beat the crap out of them, + +[TAXC_Y:TAXICUT] +etcetera, etcetera. You got it? + +[TAXC_Z:TAXICUT] +Uh, yeah, I guess... + +[TAXC_A1:TAXICUT] +Just grab a taxi from the garage if you feel like jumping in. + +{=================================== MISSION TABLE TAXIWA1 ===================================} + +[OUTTIME:TAXIWA1] +~r~Too slow, man, too slow! + +[TAX1_1:TAXIWA1] +Ok, we got a high class fare needs picking up from Starfish island - any takers? + +[TAX1_2:TAXIWA1] +Tommy here, I'll take it! + +[TAX1_3:TAXIWA1] +This is my fare, back off asshole! + +[TAX1_4:TAXIWA1] +Come on come on, Get in, quick! + +[TAX1_5:TAXIWA1] +Ok, ok! Just don't hurt me! + +[TAXW1_1:TAXIWA1] +~g~Pick up the V.I.P. on Starfish Island. + +[TAXW1_2:TAXIWA1] +~g~Get the V.I.P back! Take the other car out! + +[TAXW1_3:TAXIWA1] +~r~The V.I.P. is dead! + +[TAXW1_4:TAXIWA1] +~r~The V.I.P. has been dropped off! + +[TAXW1_6:TAXIWA1] +~g~Take the V.I.P. to the airport! + +{=================================== MISSION TABLE TAXIWA2 ===================================} + +[TAX2_1:TAXIWA2] +Calling all cars, we're losing fares all over town. What's with you guys? + +[TAX2_2:TAXIWA2] +VC Cabs keep beating us to it. They've just got too many cars - we can't compete! + +[TAX2_3:TAXIWA2] +Mr. Vercetti, if you're out there listening in, you gotta put some VC Cabs out of action before we go bust! + +[TAXW2_1:TAXIWA2] +~g~Destroy 3 of the rival taxis! + +{=================================== MISSION TABLE TAXIWA3 ===================================} + +[TAX3_1:TAXIWA3] +Car 13, We got a Miss Cortez, asked for you especially. + +[TAX3_2:TAXIWA3] +Ok, I got it. Car 13 out! + +[TAX3_3:TAXIWA3] +Hmmmm, no sign of Mercedes... + +[TAXW3_3:TAXIWA3] +~g~Take out the leader cab! + +[TAXW3_2:TAXIWA3] +~g~Stay alive until the timer runs out. + +[TAX_AS1:TAXIWA3] +TAXI FIRM ASSET COMPLETED + +[TAX_AS2:TAXIWA3] +~g~Kaufman Cabs will now generate revenue up to a maximum of $~1~. Make sure you collect it regularly. + +[TAX3_4:TAXIWA3] +It's time for Kaufman Cab's guardian angel to eat some fender! + +[TAX3_5:TAXIWA3] +Hey boy I'm gonna tan your hide! + +{ reVC 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? + +[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 + +[FEC_IVP] +INVERT PAD VERTICALLY + +[FEM_NON] +NONE + +[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 + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_AUT] { aspect ratio related } +AUTO + +[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/gxt/miami/build.bat b/gxt/miami/build.bat new file mode 100644 index 00000000..7327497f --- /dev/null +++ b/gxt/miami/build.bat @@ -0,0 +1,5 @@ +gxt -g VC -i "american.txt" -o "../../gamefiles/TEXT/american.gxt" +gxt -g VC -i "french.txt" -o "../../gamefiles/TEXT/french.gxt" +gxt -g VC -i "german.txt" -o "../../gamefiles/TEXT/german.gxt" +gxt -g VC -i "italian.txt" -o "../../gamefiles/TEXT/italian.gxt" +gxt -g VC -i "spanish.txt" -o "../../gamefiles/TEXT/spanish.gxt" \ No newline at end of file diff --git a/gxt/miami/french.txt b/gxt/miami/french.txt new file mode 100644 index 00000000..73860f9c --- /dev/null +++ b/gxt/miami/french.txt @@ -0,0 +1,14726 @@ +{ + 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. +} + +[RAMPAGE] +RODEO! + +[RAMP_F] +ECHEC DU RODEO! + +[RAMP_P] +RODEO REUSSI! + +[RAMP_A] +TOUS LES RODEOS REUSSIS! + +[PAGE_01] +Tue ~1~ membres de gang en 2 minutes! + +[PAGE_02] +Détruis ~1~ véhicules en 2 minutes! + +[PAGE_03] +Roule à côte des membres de gang et tue-en ~1~ en 2 minutes! + +[PAGE_04] +Ecrase ~1~ membres de gang en 2 minutes! + +[PAGE_05] +Eclate la tête de ~1~ membres de gang en 2 minutes! + +[FEC_ABR] +Accélérer, freiner ou marche arrière + +[CLOHELP] +Vêtements changés! + +[FE_MLG] +LEGENDE DE LA CARTE + +[FED_RDR] +RADAR + +[FED_HUD] +L'INTERFACE + +[FED_RDL] +GRAND + +[FED_RDB] +BIPS UNIQUEMENT + +[FED_HUF] +FONDU + +[FEI_BTU] +; = - + +[FEI_SCR] +Fait défiler + +[FEST_HV] +Missions d'Autodéfense, niveau maximum + +[LG_01] +Position du joueur + +[LG_02] +Avery Carrington + +[LG_03] +Motards + +[LG_04] +Colonel Cortez + +[LG_05] +Ricardo Diaz + +[LG_06] +Kent Paul + +[LG_07] +Avocat + +[LG_08] +Phil Cassidy + +[LG_09] +Chantier naval + +[LG_10] +Club Malibu + +[LG_11] +Cubains + +[LG_12] +Studio de Cinéma + +[LG_13] +Ammu-Nation + +[LG_14] +Haïtiens + +[LG_15] +Quincaillerie + +[LG_16] +Planque + +[LG_17] +Crèmes glacées + +[LG_18] +Taxi Kaufman + +[LG_19] +Love Fist + +[LG_20] +Imprimerie + +[LG_21] +Propriété + +[LG_22] +Pay 'n' Spray + +[LG_23] +Boutique de fringues + +[LG_24] +Manoir de Tommy + +[LG_25] +Téléphone + +[LG_26] +Station de radio Wildstyle + +[LG_27] +Station de radio Flash FM + +[LG_28] +Station de radio KChat + +[LG_29] +Station de radio Fever 105 + +[LG_30] +Station de radio VRock + +[LG_31] +Station de radio VCPR + +[LG_32] +Station de radio Espantoso + +[LG_33] +Station de radio Emotion 98.3 + +[LG_34] +Station de radio Onde 103 + +[LG_35] +Destination + +[LG_36] +Sun Yard + +[LG_37] +Boîte de striptease + +[MAP_YAH] +VOUS ETES ICI + +[MAP_LEG] +Légende + +[SENTXS] +Sentinelle XS + +[VCNMAV] +VCN Maverick + +[TAXSHRT] +~g~Au lieu de conduire, tu peux te servir de ce taxi Kaufman pour te déplacer. Cela te coûtera $9. + +[BRIBE1] +Tu viens de récupérer un pot-de-vin de la police. Cela réduira ton indice de recherche d'une étoile. + +[SUNSHIN] +Sunshine Autos + +[KAUFCAB] +Taxis Kaufman + +[BOATYAR] +Le chantier naval + +[WANT_L] +Tu as perdu ton indice de recherche. Ne commets pas de délit tant que les étoiles brillent ou tu seras de nouveau très recherché. + +[HOTRNG] +HOTRING + +[BLODRNG] +BLOODRING + +[DIRTRNG] +DIRTRING + +[IN_VEH] +~g~Eh! Remonte dans ta caisse! + +[HEY] +~g~Te la joue pas perso, reste avec tes potes! + +[HELP3] +T'es vite crevé, ton sprint ne dure donc pas longtemps. + +[HELP4_D] +Pousse le ~h~joystick analogique droit ~w~ vers le haut pour ~h~accélérer. + +[HELP5_D] +Tire le ~h~joystick analogique droit~w~ vers toi pour ~h~freiner~w~ ou pour ~h~reculer~w~ si le véhicule est à l'arrêt. + +[HELP7_A] +Maintiens la ~h~ ~k~~PED_LOCK_TARGET~ ~w~ enfoncée pour ~h~viser~w~ avec le fusil à lunette. + +[HELP7_D] +Maintiens la ~h~ ~k~~PED_LOCK_TARGET~ ~w~ enfoncée pour ~h~viser~w~ avec le fusil à lunette. + +[HELP10] +Ce badge t'indique que t'es recherché par la police. + +[HELP11] +Plus t'as de badges, plus ton indice de recherche est important. + +[HELP13] +Tu as parfois intérêt à emprunter des chemins non indiqués sur le radar. + +[TIMER] +Cette mission est en temps limité. Tu dois donc la réussir avant que le compteur n'atteigne zéro. + +[HORN] +~g~Klaxonne! + +[NOMONEY] +~g~T'as pas assez de cash! + +[REWARD] +RECOMPENSE $~1~ + +[M_FAIL] +ECHEC DE LA MISSION! + +[M_PASS] +MISSION ACCOMPLIE! $~1~ + +[DEAD] +T'ES MORT! + +[BUSTED] +CHOPE! + +[WEATHE1] +TEMPS ENSOLEILLE + +[WEATHE2] +TEMPS TRES ENSOLEILLE + +[WEATHE3] +TEMPS NUAGEUX + +[WEATHE4] +TEMPS PLUVIEUX + +[WEATHE5] +TEMPS BRUMEUX + +[WEATHE6] +TEMPS NORMAL + +[NUMBER] +~1~ + +[LOADCAR] +CHARGEMENT DU VEHICULE... (APPUIE SUR F1 POUR ANNULER) + +[CARSOFF] +Trafic désactivé. + +[CARS_ON] +Trafic activé. + +[TEXTXYZ] +Ecriture des coordonnées sur le fichier... + +[CHEATON] +Mode triche activé + +[CHEATOF] +Mode triche désactivé + +[IMPORT1] +Va dehors et attends ton véhicule. + +[PAGEB11] +Lance-flammes livré à la planque + +[WANT_A] +Tu ne seras arrêté que si tu as un ~h~indice de recherche. + +[WANT_B] +La rangée d'étoiles dans le coin supérieur droit de l'écran symbolise ton ~h~indice de recherche~w~. + +[WANT_C] +Ton ~h~indice de recherche~w~ est désormais de un... + +[WANT_D] +deux... + +[WANT_E] +trois... + +[WANT_F] +Plus ton ~h~indice de recherche~w~ augmente, plus les forces de l'ordre t'en veulent. + +[WANT_G] +Quand tu es ~h~'chopé'~w~, tu es amené au poste de police le plus proche. + +[WANT_H] +Les flics se laisseront corrompre contre toutes tes armes et une partie de ton cash. + +[WANT_I] +Toutes les missions en cours échoueront. + +[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'effacer ton indice de recherche. + +[HEAL_B] +Quand tu es ~h~'H.S'~w~, t'es amené à l'hôpital le plus proche. + +[HEAL_C] +On te confisque alors toute ton artillerie et les docteurs prennent ton argent pour te remettre en forme. + +[HEAL_E] +En jouant, tu trouveras d'autres moyens de te soigner ou de te protéger. + +[SAVE1] +Entre dans la corona 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 Ammu-Nation pour acheter une arme. + +[R_TIME] +TEMPS DE COURSE : + +[PROP_1] +Tu n'as pas assez de cash pour acheter cette propriété + +[PROP_2] +Tu ne peux pas acheter une propriété en cours de mission + +[IND_ZON] +Vice City Beach + +[COM_ZON] +Partie centrale de Vice City + +[BEACH1] +Ocean Beach + +[BEACH2] +Washington Beach + +[BEACH3] +Vice Point + +[GOLFC] +Leaf Links + +[STARI] +Starfish Island + +[DOCKS] +Port de Vice + +[HAVANA] +Little Havana + +[HAITI] +Little Haiti + +[PORNI] +Prawn Island + +[DTOWN] +Centre + +[VICE_C] +Vice City + +[A_PORT] +Aéroport Escobar + +[JUNKY] +Décharge + +[PISTOL] +Pistolet + +[PYTHON] +.357 + +[UZI] +Uz-1 + +[TEC9] +Tec 9 + +[M4] +M4 + +[INGRAM] +Mac + +[MP5] +MP + +[RUGER] +Kruger + +[SNIPE] +Fusil à lunette + +[GRENADE] +Grenades + +[SHOTGN1] +Fusil à pompe + +[SHOTGN2] +S.P.A.S. 12 + +[SHOTGN3] +Fusil à pompe Stubby + +[ARMOUR] +Gilet pare-balles + +[LASER] +.308 Lunette + +[BASEBAT] +Batte de baseball + +[HAMMER] +Marteau + +[SCREWD] +Tournevis + +[CLEVER] +Couperet + +[MACHETE] +Machette + +[KNIFE] +Couteau + +[KATANA] +Katana + +[CHAINSA] +Tronçonneuse + +[G_COST] +$~1~ + +[CAR_1] +Ambulance + +[MALIBU] +Malibu Club + +[MANSION] +La résidence de Diaz + +[TMANS] +Chez Tommy + +[STRIP] +'Pole Position Club' + +[MALL1] +Centre commercial de North Point + +[BANKINT] +El Banco Corrupto Grande + +[RANGE] +Champ de tir + +[POL_HQ] +QG de Vice City Police + +[INT_B] +Un vieil ami + +[INTB_1] +~g~Va au bureau de l'avocat. + +[LAW_1] +La Fête + +[LAW_2] +Baston de Rue + +[LAW_3] +Jury sous pression + +[LAW_4] +Emeute + +[COL_1] +Salaud de traître + +[COL_2] +Fusillade + +[COL_3] +Anges gardiens + +[COL_4] +Chef, oui, chef ! + +[COL_5] +Sur le Pont + +[COK_1] +Course poursuite + +[COK_2] +Phnom Penh '86 + +[COK_3] +Speedboat + +[COK_4] +Offre & demande + +[KENT_1] +Couloir de la mort + +[ASS_1] +Liquidation + +[BUD_1] +EXTORSION + +[BUD_2] +Rixe de Bar + +[BUD_3] +CopLand + +[CAP_1] +L'encaisseur + +[FIN_1] +Amis proches + +[BANK_1] +Sans Issue + +[BANK_2] +Le Flingueur + +[BANK_3] +Le Chauffeur + +[BANK_4] +Hold-up + +[CNT_1] +La Mèche est vendue + +[CNT_2] +Halte au Messager + +[PORN_1] +Bout d'essai + +[PORN_2] +Dodo Vibro + +[PORN_3] +Martha + +[PORN_4] +Projecteur-G + +[TAX_1] +Taxis Kaufman + +[TAXI_1] +V.I.P. + +[TAXI_2] +Concurrence Amicale + +[TAXI_3] +Cabmaggedon + +[ICE_1] +Distribution + +[TEX_1] +Quatre fers + +[TEX_2] +Funérailles + +[TEX_3] +Demolition Man + +[PHIL_1] +Trafiquant d'armes + +[PHIL_2] +Boomshine Saigon + +[BIKE_1] +Alliage d'Acier + +[BIKE_2] +Colère + +[BIKE_3] +Bécane + +[ROCK_1] +Love Juice + +[ROCK_2] +Psychopathe + +[ROCK_3] +Promo + +[ROCK_4] +Les Love Fist! + +[HAT_1] +Juju + +[HAT_2] +Bombes! + +[HAT_3] +Raclée + +[CUB_1] +Cascades aquatiques + +[CUB_2] +Chair à Canon + +[CUB_3] +Bataille Navale + +[CUB_4] +Vaudou + +[JOB_1] +Accident de la route + +[JOB_2] +Bute la femme! + +[JOB_3] +Autocide + +[JOB_4] +Enregistrement + +[JOB_5] +Détails + +[ANSWER] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~~w~ pour répondre au téléphone. + +[MOB_01A] +Comment ça va, mon pote? C'est Paul! J'ai peut-être quelque chose d'intéressant pour toi, mais faut que je te parle en personne. + +[MOB_01B] +Je suis en train de me la couler douce au Malibu. + +[MOB_01C] +Tu me devras un ou deux petits services en échange, mon mignon. A tout à l'heure... + +[MOB_02A] +Sniiiiiiiiiiiif! Hé, salut, Tommy! Tommy? + +[MOB_02B] +On a un problème à l'imprimerie. Tu ferais mieux d'aller y jeter un coup d'oeil. + +[MOB_02C] +Quelque chose a merdé. C'est le bordel. Faut que j'te laisse. + +[MOB_03A] +Mr Vercetti? J'ai ici un document signé qui établit formellement + +[MOB_03B] +que vous avez racheté toutes les dettes de BJ's Auto. + +[MOB_03C] +La disparition soudaine de BJ ne me laisse pas d'autre choix + +[MOB_03D] +que de vous tenir pour responsable du passif de cette société. + +[MOB_03E] +Jusqu'à ce que ce compte soit complètement soldé + +[MOB_03F] +vous devriez considérer les rues de Vice City comme très dangereuses. + +[MOB_04A] +Comment va, vieille branche? + +[MOB_04B] +Ecoute, Tommy, j'avais oublié de te dire qu'on va avoir besoin de balaises en plus pour le concert. + +[MOB_04C] +Y'a un gang de motards dirigé par Mitch Baker, ça nous ferai une super publicité. Vraiment Rock'n'roll, Baby. + +[MOB_04D] +Règle ça pour moi et je te laisserai accéder aux coulisses du concert, d'accord? + +[MOB_05A] +Bien joué, Tommy, j'suis content d'avoir récupéré ma bécane. + +[MOB_05B] +Dis à Mr Kent Paul que la sécurité sera assurée pour le concert. + +[MOB_05C] +Tu as ma parole. + +[MOB_05D] +Bon, maintenant, évite de t'attirer des emmerdes. + +[MOB_06A] +Tommy! Ceux d'en bas, ils parlent de toi, mon petit... + +[MOB_06B] +Je me disais que tu te laisserais peut-être tenter par un doux foyer et par une soupe au gombo de tata Poulet? Mmm? + +[MOB_06C] +Fais un saut par ma cuisine un de ces jours, hein, Tommy? + +[MOB_08A] +Salut, Tommy, je me disais que t'aurais peut-être besoin d'un conseil en affaires... + +[MOB_08B] +Dès que tu as une opération en route, il faut que tu passes prendre le fric de la semaine. + +[MOB_08C] +Si tu laisses les mecs penser que ce sont eux qui dirigent l'affaire, ils essayent de ronger tes bénefs! Ok? + +[MOB_08D] +Ca va, Ken, je connais le business... + +[MOB_08E] +Ok, ok, je sais que tu sais. Je sais. C'était... + +[MOB_08F] +C'était juste pour te dire que je sais que tu sais que je sais... + +[MOB_08G] +Ouvre simplement l'oeil! + +[MOB_08H] +T'inquiète, Ken, t'inquiète... + +[MOB_09A] +Salut, Léo! J'ai du boulot pour toi! + +[MOB_09B] +C'est pas Léo. + +[MOB_09C] +Si Léo apprend que t'as son téléphone, il va te buter aussi sec! + +[MOB_09D] +Peut-être que Léo est mort. Peut-être même que c'est moi qui l'ai buté et que j'ai hérité de son téléphone. T'avais pas pensé à ça hein, connard? + +[MOB_09E] +T'as buté Léo? Alors, c'est que t'en as dans le froc! Tu veux bosser pour moi? + +[MOB_09F] +Passe au café de mon père dans Little Havana et on pourra causer entre hommes. + +[MOB_10A] +Tommy! Ecoute, j'ai un service à te demander! + +[MOB_10B] +Steve! Comment ça marche, le cinoche? + +[MOB_10C] +Ca va, ça va. Mais on a besoin d'une scène de poursuite en bagnole et on a pas le budget pour ça. + +[MOB_10D] +J'ai laissé des bagnoles dans les environs. Tu sauras quoi faire. + +[MOB_10E] +OK, Steve, je vais voir ce que je peux faire. A plus tard. + +[MOB_11A] +Salut petit, je me suis dit que j'allais te passer un coup de fil pour te donner un bon conseil. + +[MOB_11B] +Salut Avery, qu'est-ce que tu voulais me dire? + +[MOB_11C] +Les perspectives ne manquent pas dans cette ville, si tu possèdes le bon business... Tu vois ce que je veux dire? + +[MOB_11D] +Je pense que oui... + +[MOB_11E] +Je te conseille d'ouvrir l'oeil, et le bon, si tu veux dégotter un bon business. A plus tard. + +[MOB_11F] +Ciao, Avery. + +[MOB12_A] +Tommy! C'est Avery! Ecoute, je suis débordé de boulot pour le moment, + +[MOB12_B] +et j'ai un de mes réprésentants qui a besoin d'être chaperonné jusqu'aux Gator Keys. + +[MOB12_C] +Je suis sur l'achat d'un terrain là-bas, alors j'envoie un mec graisser quelques pattes. + +[MOB12_D] +J'ai besoin que tu t'assures qu'il arrive bien là-bas. OK? + +[MOB12_E] +OK, pas de problème, Avery! Mais où c'est que je le trouve, ce mec? + +[MOB12_F] +Il boucle une affaire au chantier. Je lui ai dit que tu le prendrais là-bas. + +[MOB12_G] +Ok, Avery. J'y vais. A plus tard. + +[MOB13_A] +Vercetti? VERCETTI! Putain, mec, faut que tu me files un coup de main! + +[MOB13_B] +Mr Moffat? Comment va la famille? + +[MOB13_C] +Merde, mais quel con! Tu m'entends? + +[MOB13_D] +Ca m'a fait plaisir d'avoir des nouvelles... + +[MOB13_E] +Attends! Attends! Vercetti, euh... Tommy. Je peux t'appeler Tommy? + +[MOB13_F] +On est tous les deux dans les affaires, pas vrai? Et tu sais renifler les bonnes, hein? + +[MOB13_G] +J'ai pas le temps de causer, viens-en au fait! + +[MOB13_H] +Du FRIC! Voilà le putain de fait! + +[MOB13_I] +Je me suis encore tiré de taule, mais ça prend jamais longtemps avant qu'ils me retombent dessus. Pour eux, c'est qu'un putain de jeu! + +[MOB13_J] +Je t'appelle d'une cabine quelque part dans le trou du cul du monde! + +[MOB13_K] +Sors-moi de là avant qu'ils me chopent encore et... et... ah... meeeerde... + +[MOB13_L] +Eh bien, c'est que je suis très occupé pour les prochaines- + +[MOB13_M] +Non! Me laisse pas dans cette merde! T'as pas de coeur ou quoi? Putain, j'devrais pas avoir à m'abaisser comme ça! + +[MOB13_N] +Je suis à genoux, Tommy! Et c'est les rotules dans la poussière que je te supplie... + +[MOB13_O] +J'imagine que je pourrais passer dans le coin et voir si je te trouve... + +[MOB13_P] +Oh, merde, les voilà! Putain, magne-toi, Tommy! Vite! + +[MOB_14A] +Salut, Tommy, on va bien s'entendre, toi et moi. + +[MOB_14B] +Une tendre amie est venu me sussurrer dans l'oreille que la division SWAT de Vice City a un coffre dans une grosse banque, + +[MOB_14C] +et ils y gardent les pots-de-vin de toutes ces dernières années. + +[MOB_14D] +C'est une sorte de caisse de retraite pour vieux garçons. + +[MOB_14E] +Evidemment, si cette info pouvait t'aider à ramasser un peu de ce pognon, + +[MOB_14F] +j'imagine que tu te sentirais obligé de m'en refiler un peu, pas vrai, Tommy? + +[MOB_14G] +Je vais garder ça à l'esprit, merci, Kent. + +[MOB_14H] +Je m'appelle Paul, abruti! Et je suis du Kent, près de Londres. + +[MOB_14I] +Pour ce que j'en ai à foutre de la campagne anglaise, depuis l'école... + +[MOB15_A] +Tommy, mon vieux, c'est Paul, du Kent. + +[MOB15_B] +Il y a une ou deux authentiques dames qui ont ton nom plein la bouche, au Malibu. + +[MOB15_C] +De quoi tu parles?! + +[MOB15_D] +De gonzesses, de poulettes, de filles, quoi! Et bien élevées, pas le genre à causer pognon. + +[MOB15_E] +Faut que tu viennes voir ça. + +[MOB16_A] +Tommy, c'est Paulo, que pasa amigo? + +[MOB16_B] +Qu'est-ce que tu m'veux, Paul? J'ai pas besoin de fausses fringues de marque. + +[MOB16_C] +Très drôle, mon vieux, mais je fais pas dans la fringue de tapette, moi. + +[MOB16_D] +Bon, je t'appelle parce que j'me demande si je ne pourrais pas avoir un rôle dans un de tes films. + +[MOB16_E] +J'ai fait pas mal de pornos en Angleterre, mon vieux. + +[MOB16_F] +Au plumard, je suis une mitrailleuse lourde. + +[MOB16_G] +Merci de me proposer tes services, Paul, j'y réfléchirai. + +[MOB16_H] +Sérieusement, pense à moi, après tout ce que j'ai fait pour toi... + +[MOB16_I] +Justement, j'essaie de l'oublier. + +[MOB17_A] +Tommy Vercetti, comment ça va, big boss? + +[MOB17_B] +J'ai entendu tous ces trucs sur toi. Un flambeur en ville maintenant... + +[MOB17_C] +Paul, t'es bourré. + +[MOB17_D] +Naan! Pauvre abruti! J'suis pas saoul! + +[MOB17_E] +J'ai juste pris un ou deux verres et quelques tournées, et ça fait deux jours que j'ai pas fermé l'oeil! + +[MOB17_F] +De toute façon, ne me traite pas comme ça. + +[MOB17_G] +Je suis pas une poire. Qui c'est qui t'a lancé dans cette ville? Hein, qui? Moi! + +[MOB17_H] +Ah ouais? + +[MOB17_I] +Ouais! + +[MOB17_J] +Paul, t'énerve pas. J'étais occupé, sois pas stupide. + +[MOB17_K] +Je suis pas stupide, connard! Ils me l'ont dit en maison de redressement! + +[MOB17_L] +Si tu cherches les emmerdes, mon pote, tu vas les trouver! + +[MOB17_M] +Tommy, s'il te plaît! T'étais mon grand espoir! Te fous pas de moi! + +[MOB17_N] +Paul, va roupiller un bon coup, sérieusement. + +[MOB18_A] +Tommy, c'est Paulo, comment ça va, mon pote, je me suis dit que j'allais te passer un petit coup de fil. + +[MOB18_B] +Ah, mon pote, tu croiras jamais l'incroyable petit lot que je viens de lever... + +[MOB18_C] +Elle se promenait juste en bas dans Little Havana, mon pote. + +[MOB18_D] +Elle m'a dit qu'elle s'appelait Mercedes ou un truc comme ça. Ah, mon pote, faut que t'ailles voir cette fille! + +[MOB18_E] +Elle ferait bander un eunuque! Elle m'a dit que j'étais le meilleur coup de sa vie et tout et tout! + +[MOB18_F] +Vas-y, trouve-la. A plus tard. + +[MOB19_A] +Tommy V, c'est KP! Kent Paul. Le bruit court que des mecs veulent te faire la peau. + +[MOB19_B] +Ouvre l'oeil, mon pote. Et oublie pas... Je t'ai rien dit. + +[MOB_20A] +Salut, Tommy, c'est Paul. Je viens juste d'entendre dire que t'avais été un vilain garçon. + +[MOB_20B] +Il y a quelqu'un qui s'est vexé de te voir jouer les caïds et ça lui a donné de mauvaises idées... + +[MOB_20C] +Bon, dis à personne que je t'ai rencardé. Les grandes gueules finissent toujours par l'avoir dans l'os. + +[MOB_20D] +Enfin, j'ai entendu dire que ta tête a été mise à prix et que quelqu'un va essayer de t'avoir, + +[MOB_20E] +alors fais gaffe, et m'oublie pas, mon pote. + +[MOB71_A] +Tommy, Thomas, c'est Cortez. Que pasa? + +[MOB71_B] +Les affaires tournent... Et toi, comment ça va? + +[MOB71_C] +Tommy, ici, c'est toujours la guerre. Excusez la mauvaise ligne, mais on vient juste d'avoir un nouveau coup d'état manqué. + +[MOB71_D] +Le peuple est la plus exigeante des maîtresses. + +[MOB71_E] +Si je compte bien, y'a eu trois révolutions et quatre coups d'état depuis que je suis rentré de Vice City. + +[MOB71_F] +Heureusement, à chaque fois, j'ai pris du galon. + +[MOB71_G] +Je voulais vous demander quelque chose à propos de Mercedes... + +[MOB71_H] +Ouais? Quoi? + +[MOB71_I] +Voilà, Tommy, Tommy, j'ai entendu toutes ces histoires et je sais pas quoi penser... + +[MOB71_J] +Peut-être que tout le monde essaye de m'humilier... + +[MOB71_K] +Peut-être qu'elle se croit tout permis, mais dites-moi juste la vérité, Tommy, c'est vrai? + +[MOB71_L] +Mais dites-moi juste la vérité, Tommy, c'est vrai? + +[MOB71_M] +Qu'est-ce qui est vrai? + +[MOB71_N] +Ces bruits qui courent... Elle va vraiment devenir avocate? + +[MOB71_O] +Ah, Tommy, c'est une véritable honte! Nous, les Cortez, sommes une honorable famille et nous ne tolèrerons jamais qu'une de nos filles devienne avocate. + +[MOB71_P] +Je vous en prie, dites-moi que ce n'est pas vrai, je ne crois pas que je pourrais le supporter. + +[MOB71_Q] +Colonel, je peux vous assurer que Mercedes ne deviendra jamais avocate. Ne vous inquiètez pas. + +[MOB71_R] +Merci, Tommy, cette honte me serait insupportable. C'est une femme, pas un parasite, vous comprenez. + +[MOB71_S] +Je le sais, colonel. + +[MOB71_T] +Bon, vous allez devoir m'excuser, Tommy, mais le nouveau ministre de l'intérieur vient d'arriver... + +[MOB71_U] +Il y a de nombreuses années, j'ai tué son père au cours d'une tentative de coup d'état ratée et je dois me montrer poli. Adios, amigo. + +[MOB22_A] +Tommy, vous vous révèlez très utile, amigo. + +[MOB22_B] +Merci, Cortez. Et mon affaire? + +[MOB22_C] +Tommy, je travaille inlassablement en ce sens pour aller au fond des choses et de la vérité dans cette terrible histoire. + +[MOB22_D] +Vous avez ma parole, mais en attendant, + +[MOB22_E] +acceptez, je vous en prie, les sincères remerciements de mon peuple pour vos actions bénéfiques. + +[MOB_25A] +Tommy, c'est Cortez. Les Français me font beaucoup d'ennuis... + +[MOB_25B] +Les sales hypocrites! Ils passent des siècles à piller les pays pauvres et ils osent me traiter de voleur! + +[MOB_25C] +Je vais avoir besoin de votre aide aussi vite que possible. + +[MOB_25D] +Vite, Tommy, j'ai vraiment besoin de vous. Je hais ces maudits Français! + +[MOB_26A] +Allo, Tommy? + +[MOB_26B] +Ouais? + +[MOB_26C] +C'est moi, Baker. Je voulais juste te dire que j'ai beaucoup apprécié le spectacle. + +[MOB_26D] +Moi et mes gars, on veut te remercier, et te rappeler + +[MOB_26E] +que tu as tout notre respect. Bon, salut. Et continue comme ça, mon pote. + +[MOB_29A] +Allo? Vous êtes bien M. Tommy Vercetti? + +[MOB_29B] +Ouais. + +[MOB_29C] +Bien. J'ai entendu dire que vous êtes l'homme à appeler quand on est victime d'une invasion de vermine. + +[MOB_29D] +Ca se peut... + +[MOB_29E] +Eh bien, là je suis vraiment envahi par la vermine. Les haïtiens sont partout... + +[MOB_29F] +Je m'appelle Umberto Robina et je vous propose une rencontre au Café Robina aussitôt que vous pourrez. + +[MOB_29G] +Moi j'vous le dis, ces maudits haïtiens sont allés trop loin, cette fois... + +[MOB_29H] +Test + +[MOB_30A] +Tommy, c'est Umberto Robina. + +[MOB_30B] +Quoi de neuf au café? + +[MOB_30C] +Oh, merveilleux, Tommy, vraiment incroyable! Ici, plus de mauviettes, Tommy. Juste de vrais hommes et des belles filles! + +[MOB_30D] +Je voulais te dire que moi et papa, maintenant, on te considère comme cubain. + +[MOB_30E] +T'as fait tes preuves, mon frère et t'en as une sacrée paire dans l'pantalon! + +[MOB_30F] +Euh, merci, Umberto. Personne ne m'a dit ça depuis que je suis sorti de taule. Bon, salut. + +[MOB_33A] +Tommy, c'est Phil! Bon, arrête tes conneries et écoute-moi, tu veux? + +[MOB_33B] +Bon. J'ai du whisky extra fort, presque fermenté, et je me demandais si tu voulais pas venir boire un coup... + +[MOB_33C] +Sans déconner, Tommy, si t'as besoin d'un verre ou de décoller de la peinture, t'es le bienvenu, ce truc fera de toi un homme! + +[MOB_33D] +Ca me fait cet effet, même si je vois plus rien d'un oeil. Je t'attends, t'entends? + +[MOB_34A] +Tommy, ça m'a fait vraiment plaisir de bosser avec toi. Je m'étais pas autant marré depuis la guerre du Vietnam, mon pote. + +[MOB_34B] +Bon, si t'as besoin de quelque chose, tu m'appelles, t'entends? + +[MOB_34C] +Je n'oublie jamais ceux avec qui j'ai servi + +[MOB_34D] +et je suis sûr que je peux t'aider, t'entends? + +[MOB_35A] +Tommy, la blessure cicatrise bien. Le truc drôle, c'est que + +[MOB_35B] +j'ai combattu dans six batailles et que je m'en suis toujours sorti sans une égratignure, et voilà! + +[MOB_35C] +'Phil, le manchot d'un bras.' Enfin, j'ai encore une large selection d'armes de poing alors je serai jamais 'Phil, le désarmé', t'entends? + +[MOB_35D] +Aller, mon pote, laisse tomber les conneries sentimentales et va te boire un verre, t'entends? + +[MOB_36A] +Tommy, c'est Phil. Je voulais te remercier pour le coup de main, mon pote. + +[MOB_36B] +Salopard de Charlie, toujours à tendre des embuscades ici ou là, + +[MOB_36C] +mais la blessure cicatrise bien, et ça veut aussi dire que j'aurai plus à truander le gouvernement pour le contrôle d'invalidité. Merci, mon pote. + +[MOB_40A] +Salut, Tommy, c'est Sonny. Alors ce bronzage? + +[MOB_40B] +J'suis pas bronzé. + +[MOB_40C] +Ouais et t'as pas non plus mon pognon, alors je m'interrogeais... + +[MOB_40D] +Je me disais : 'Qu'est-ce qu'il fout?' Alors, je te le demande, Tommy, qu'est-ce que tu fous? + +[MOB_40E] +Je cherche le pognon, Sonny, t'inquéte pas! + +[MOB_40F] +Je m'inquiéte, Tommy, c'est mon genre, + +[MOB_40G] +parce que j'ai ce problème récurrent dans la vie de tomber sur des gens indignes de confiance... + +[MOB_40H] +Ne sois pas indigne de ma confiance, Tommy, s'il te plaît... + +[MOB_40I] +Rends-nous à tous deux ce service. J'attends de tes nouvelles... + +[MOB_41A] +Tommy, tu te souviens de moi? + +[MOB_41B] +Salut, Sonny. + +[MOB_41C] +Ouais, exactement, Sonny, on est de vieux amis, + +[MOB_41D] +mais tu ne m'écris jamais, tu ne m'appelles jamais. Tu ne veux plus qu'on soit amis? + +[MOB_41E] +J'ai été très occupé à régler des trucs ici et là. Tu ne m'aides pas beaucoup, ici. + +[MOB_41F] +Oh, c'est ma faute? Bon, j'ai entendu que tu étais très occupé, d'accord, + +[MOB_41G] +occupé à liquider des barons de la drogue, occupé à prendre le pouvoir. + +[MOB_41H] +Alors je te préviens simplement de ne pas m'oublier, Tommy, parce que moi, je ne t'oublie pas... + +[MOB_42A] +Tommy. + +[MOB_42B] +Sonny. + +[MOB_42C] +Faut croire que t'es à moitié sourd, alors je vais me répéter : + +[MOB_42D] +Tommy, où est ce putain de pognon? Où est cette putain de marchandise? Et où est ma putain de part sur ta nouvelle affaire? + +[MOB_42E] +Tu te fous vraiment d'ma gueule, Tommy, et ça me fait pas rire! + +[MOB_43A] +Tommy! Tommy! J'ai eu Sonny au téléphone. Tu m'écoutes? + +[MOB_43B] +Je ne sais pas pour toi, mais il y a un mec qui menace de buter toute ma famille, + +[MOB_43C] +et ça me fout vraiment les jetons. Qu'est-ce que tu comptes faire? + +[MOB_43D] +Reste calme, Ken, tout va bien se passer. + +[MOB_43E] +Je suis calme! Calme comme un homme qui craint pour sa propre vie! + +[MOB_43F] +Ken, j'ai d'autres choses en tête pour l'instant, + +[MOB_43G] +on s'occupera de Forelli quand l'heure sera venue. + +[MOB_43H] +Je suis calme. J'ai pas l'air calme? J'sais pas, c'est ma voix qui a changé à cause de la mort qui me pend au nez! + +[MOB45_A] +Tommy, faut qu'on cause sérieusement. + +[MOB45_B] +C'est quoi le problème, Lance? + +[MOB45_C] +C'est toi le problème, mon pote, j'ai l'impression que tu m'files pas une part équitable... + +[MOB45_D] +Mais le pire, c'est que tu m'as foutu la honte devant les mecs. Je supporte pas ça. + +[MOB45_E] +Lance, c'est pas ça du tout. T'as fait des bourdes, c'est tout. + +[MOB45_F] +Tommy, je suis pas ton petit messager et encore moins ton garçon de courses. + +[MOB45_G] +Lance, déconne pas et on aura pas d'emmerdes! Et si c'est moi qui déconne, tu peux me rentrer dedans quand tu veux. + +[MOB45_H] +Tommy, j'ai tout fait pour toi et tu me traites comme un con. Fais pas ça. + +[MOB45_I] +Lance, je ne vais pas t'arnaquer ou te planter un couteau dans le dos, ok? + +[MOB45_J] +T'énerve pas. C'est déjà assez dur comme ça, sans que tu perdes les pédales. + +[MOB45_K] +Fais-moi confiance. Tu m'entends? Tu m'entends? + +[MOB45_L] +Je t'entends, Tommy, mais je peux pas supporter ça plus longtemps. + +[MOB45_M] +Lance, arrête de déconner. Maintenant, je te préviens. + +[MOB45_N] +Tu m'entends? Relax! Prends quelques jours de repos et on en reparle, ok? + +[MOB46_A] +Tommy, c'est Lance. + +[MOB46_B] +Ouais? + +[MOB46_C] +Même pas un 'ça fait plaisir d'avoir des tes nouvelles'? Allez, mec, sois sympa! + +[MOB46_D] +Je suis occupé. Qu'est-ce que tu veux? + +[MOB46_E] +Rien. C'était juste pour te dire... Tu sais, Tommy, on peut faire ce truc. + +[MOB46_F] +Toi et moi, sans problème. Tu vois ce que je veux dire? + +[MOB46_G] +Va bien falloir qu'on le fasse, parce que sinon, on est morts, Lance. + +[MOB46_H] +On peut plus reculer, maintenant, mais merci de ton appel. On se reparle plus tard. + +[MOB_47A] +Tommy, c'est Lance, on a de gros problèmes. Amène-toi ici en vitesse. + +[MOB52_A] +Salut, Léo, je crois qu'on a un acheteur pour la marchandise de Diaz. + +[MOB52_B] +Il faut que tu l'appelles, mec, pour organiser le deal, tu comprends? + +[MOB52_C] +T'es où, là? + +[MOB52_D] +T'es sûr que ça va, Léo? T'as une voix bizarre... + +[MOB52_E] +Dis-moi juste où t'es. + +[MOB52_F] +Qui c'est qui cause? Je veux parler à Léo! + +[MOB52_G] +Léo est parti pour un moment, je le remplace. + +[MOB52_H] +Va te faire foutre, connard! + +[MOB54_A] +Salut, Tommy! + +[MOB54_B] +Salut, Mercedes, quoi de neuf? + +[MOB54_C] +J'ai un nouvel appartement dans Vice Point. + +[MOB54_D] +J'ai pensé que tu voudrais peut-être passer un de ces quatre... + +[MOB54_E] +J'adorerai. A plus tard. + +[MOB55_A] +Tommy, c'est moi. + +[MOB55_B] +Salut, Mercedes. + +[MOB55_C] +Tommy, je m'ennuie tellement! Quand est-ce qu'on va s'amuser? + +[MOB55_D] +Comment ça? + +[MOB55_E] +Bon, je sais que t'es très occupé à te battre, à tuer des gens ou à les corrompre, + +[MOB55_F] +mais moi, j'ai envie de m'amuser un peu! Alors, ne m'oublie pas, Tommy! + +[MOB56_A] +Tommy, j'ai entendu dire que tu avais tué Ricardo Diaz! + +[MOB56_B] +Un feu accidentel s'est déclenché dans sa résidence... + +[MOB56_C] +Je pense qu'il a cramé dans sa chemise en acrylique. + +[MOB56_D] +Tommy, je suis si fière de toi! Je savais que tu étais un vrai macho! + +[MOB56_E] +Ah, mon dur de dur, je suis si fière d'être ton amie! + +[MOB56_F] +Je sais que tu vas être très occupé à essayer de t'emparer de cette ville, + +[MOB56_G] +mais ne m'oublie pas, d'accord? + +[MOB57_A] +C'est Mercedes. Je ne t'aime plus, Tommy! + +[MOB57_B] +C'est fini! C'est vrai! Parce que tu n'es plus gentil avec moi! + +[MOB57_C] +Tu ne me traites plus comme une dame! Tu m'ignores et je te déteste! + +[MOB57_D] +Je veux que tu viennes me voir tout de suite! " + +[MOB58_A] +Tommy. + +[MOB58_B] +Salut, Mercedes. + +[MOB58_C] +Bonjour monsieur le vrai dur! Je suis très en colère contre toi, Tommy! + +[MOB58_D] +Ne m'oblige plus jamais à supporter Jezz Torrent! + +[MOB58_E] +Il est pathétique! En plein milieu il a commencé à pleurer à cause de son toutou + +[MOB58_F] +qui est mort quand il avait 7 ans et parce que sa mère ne l'a jamais aimé... + +[MOB58_G] +En plus, Tommy, il porte une perruque et un soutien-gorge dans l'intimité! + +[MOB58_H] +Je t'en veux vraiment! + +[MOB59_A] +Ooh Tommy, c'est Mercedes. + +[MOB59_B] +Je voulais juste te dire que je m'étais éclatée sur le tournage! + +[MOB59_C] +Si tu as quelque chose d'autre de ce genre, pense à moi! + +[MOB59_D] +Je suis sincère! J'ai toujours voulu être actrice! + +[MOB59_E] +Je crois que j'ai beaucoup appris sur l'art dramatique! + +[MOB59_F] +C'est tellement révélateur! Merci beaucoup! On se voit très bientôt, adios! + +[MOB_99] +Rends-toi à la cabine indiquée. + +[MOB_98] +Rends-toi à la cabine indiquée. + +[MOB_97] +Rends-toi à la cabine indiquée. + +[MOB_96] +Rends-toi à la cabine indiquée. + +[MOB_95] +Rends-toi à la cabine indiquée. + +[A_TIME] ++~1~ secondes + +[F_RANGE] +La radio du camion de pompiers ne capte plus rien. Rapproche-toi d'une caserne! + +[DODO_FT] +Tu as 'volé' pendant ~1~ secondes! + +[GA_8] +Utilise le détonateur pour armer la bombe. + +[GA_10] +Bravo. 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 ne nous intéresse pas! + +[GA_20] +On en a trop des comme ça! Désolé, mec. + +[CHASE] +Attention maximale des médias + +[CHASE1] +Ignoré + +[CHASE2] +Ennuyeux + +[CHASE3] +Plus ou moins intéressant + +[CHASE4] +Journal local page 7 + +[CHASE5] +Journal local La une + +[CHASE6] +Vice Courier Rubrique 1 + +[CHASE7] +Vice Courier La une + +[CHASE8] +Télé locale 3h du mat' + +[CHASE9] +Télé locale Infos + +[CHASE10] +Télé locale Direct + +[CHASE11] +UFA Today page 12 + +[CHASE12] +UFA Today page 4 + +[CHASE13] +Photo dans UFA Today + +[CHASE14] +Télé nationale 4h du mat' + +[CHASE15] +Télé nationale Infos + +[CHASE16] +Télé nationale Direct + +[CHASE17] +Infos internationales + +[CHASE18] +Crise nationale + +[CHASE19] +Crise internationale + +[CHASE20] +Evénement mondial + +[CHASE21] +Truc de légendes + +[CR_1] +La grue ne peut pas soulever ce véhicule. + +[PU_MONY] +T'as pas assez de liquide. + +[CO_ALL] +Tu les as toutes eues. Tiens, voilà pour toi... + +[FEM_ON] +Activé + +[FEM_OFF] +Désactivé + +[FEM_YES] +Oui + +[FEM_NO] +Non + +[FEM_RET] +Réessayer + +[FEC_NA] +NA + +[FEC_CWL] +Faire défiler les armes vers la gauche + +[FEC_CWR] +Faire défiler les armes vers la droite + +[FEC_LOF] +Regarder devant + +[FEC_TAR] +Viser + +[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] +Rergarder à gauche + +[FEC_LB] +Regarder derrière + +[FEC_LR] +Regarder à droite + +[FEC_HOR] +Klaxon + +[FEC_VES] +Commandes du véhicule + +[FEC_BRA] +Freiner ou marche arrière + +[FEC_HAB] +Frein à main + +[FEC_CAW] +Arme du véhicule + +[FEC_ACC] +Accélérateur + +[FEC_CCF] +Configuration : + +[FEC_CF1] +Config. 1 + +[FEC_CF2] +Config. 2 + +[FEC_CF3] +Config. 3 + +[FEC_CF4] +Config. 4 + +[FEC_CDP] +Affichage manette : + +[FEC_ONF] +à pied + +[FEC_INC] +En voiture + +[FEC_VIB] +Vibrations : + +[FEL_ENG] +Anglais + +[FEL_FRE] +Français + +[FEL_GER] +Allemand + +[FEL_ITA] +Italien + +[FEL_SPA] +Espagnol + +[FED_DBG] +Menu Debug + +[FED_RID] +Recharger IDE + +[FED_RIP] +Recharger IPL + +[FED_PAH] +Analyser Saut + +[FED_DFL] +CTheScripts::DbgFlag + +[FED_DLS] +Lumière blanche de debug allumée + +[FED_SPR] +Affiche les groupes de pietons + +[FED_SCR] +Affiche les groupes en voiture + +[FED_SCZ] +Affiche les zones de massacre + +[FED_DSR] +Debug les demandes de répartition par niveaux + +[FED_SCP] +gb Indique les polys de collision + +[PL_STAT] +Stats du joueur + +[PE_WAST] +Personnes tuées par toi + +[PE_WSOT] +Personnes tuées par d'autres + +[TM_BUST] +Nb de fois capturé + +[GNG_WST] +Gangsters tués + +[DED_CRI] +Criminels tués + +[PER_COM] +Pourcentage accompli + +[KGS_EXP] +Kilos d'explosifs utilisés + +[ACCURA] +Précision + +[ST_WEAP] +Budget arsenal + +[ST_PROP] +Budget propriété + +[ST_AUTO] +Budget réparation et peinture voiture + +[ST_PHOT] +Photos prises + +[ST_LOAN] +Visites des usuriers + +[ST_STOR] +Magasins liquidés + +[ST_MOVI] +Cascades de films + +[ST_PIZZ] +Pizza livrées + +[ST_GARB] +Collectes de détritus effectuées + +[ST_ICEC] +Crèmes glacées vendues + +[TOP_SHO] +Meilleur score au champ de tir + +[SHO_RAN] +Position au champ de tir + +[SEAGULL] +Mouettes descendues + +[PROPOWN] +Propriété possédée + +[ST_TIME] +Temps de jeu + +[ST_FTIM] +Heures de vol + +[ST_PRAN] +Classement pilote + +[ST_RAN0] +Apprenti + +[ST_RAN1] +Navigateur + +[ST_RAN2] +Copilote + +[ST_RAN3] +Junior + +[ST_RAN4] +Compétent + +[ST_RAN5] +Senior + +[ST_RAN6] +As + +[ST_RAN7] +Baron rouge + +[ST_DRWN] +Poissons nourris + +[ST_FASH] +Budget fringues + +[ST_DAMA] +Propriété détruite + +[TM_DED] +Visites à l'hôpital + +[DAYSPS] +Jours passés dans le jeu + +[NUMSHV] +Visites dans l'entrepôt + +[MXCARD] +Dist. max. de saut de Cascade dangereuse (ft) + +[MXCARJ] +Hauteur max. de saut de Cascade dangereuse(ft) + +[MXCARDM] +Dist. max. de saut de Cascade dangereuse (m) + +[MXCARJM] +Hauteur max. de saut de Cascade dangereuse (m) + +[MXFLIP] +Saltos max. de saut de Cascade dangereuse + +[MXJUMP] +Rotation max. de saut de Cascade dangereuse + +[BUL_FIR] +Balles tirées + +[BUL_HIT] +Balles ayant atteint la cible + +[SPRAYIN] +Peintures + +[BSTSTU] +Meilleure cacade 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 réalisée + +[NOUNIF] +Sauts uniques accomplis + +[NMISON] +Tentatives de mission + +[PASDRO] +Passagers perdus + +[MONTAX] +Total des courses en taxi + +[DAYPLC] +Dépenses quotidiennes de police + +[CRIMRA] +Taux de criminalité : + +[STPR_1] +Le Malibu + +[STPR_2] +Imprimerie + +[STPR_3] +Studio de cinéma + +[STPR_4] +Usine de crème glacée + +[STPR_5] +Concession automobile + +[STPR_6] +Compagnie de taxis + +[STPR_7] +Chantier naval + +[SET1EN] +Config. 1. Activée + +[GMSAVE] +Sauvegarder partie + +[FEDS_TB] +Retour + +[FEST_OO] +sur + +[FEC_TUC] +Commandes tourelle + +[FEC_RS3] +Faire défiler les stations de radio (touche L3) + +[FEC_HO3] +Klaxonner (touche L3) + +[C_FAIL] +Mission d'autodéfense terminée! + +[C_ESCP] +~r~Le suspect s'est échappé! + +[C_VIGIL] +BONUS AUTODEFENSE! + +[HEAL_A] +Ton niveau de ~h~santé~w~ s'affiche en orange en haut à droite de l'écran. + +[WRONGCD] +Disque invalide. Insère le bon disque. + +[NOCD] +Le compartiment à disque est vide. Insère un disque. + +[OPENCD] +Le compartiment à disque est ouvert. Referme-le. + +[CDERROR] +Erreur au cours de la lecture du DVD de Grand Theft Auto: Vice City + +[RESTART] +Démarrage d'une nouvelle partie + +[GA_3] +Plus de cadeaux. 100$ pour repeindre! + +[GA_1] +Oula! Je ne touche à rien d'aussi chaud, moi! + +[GA_1A] +Reviens quand tu seras moins occupé... + +[HELP9_C] +Appuie sur la~h~ ~k~~PED_FIREWEAPON~ ~w~pour ~h~tirer~w~ avec le fusil à lunette. + +[TAXI2] +~r~Tu n'as plus le temps! + +[PAGEB13] +Santé livrée à la planque + +[PAGEB14] +Adrénaline livrée à la planque + +[FESZ_CA] +Annuler + +[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 les données depuis la dernière partie sauvegardée seront perdues. Continuer? + +[T4X4_1] +'Terrain de jeu PCJ' + +[BMX_1] +'Sélection par la boue' + +[BMX_2] +'Piste d'essai' + +[BMXFAIL] +~r~Tu n'as pas réussi à établir un nouveau record! + +[T4X4_3] +'DANS LA POCHE!' + +[MM_1] +'Cônes en folie' + +[T4X4_F] +~r~Tu t'es barré! Trop dur pour toi? + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinelle + +[RIO] +Rio + +[PATRIOT] +Patriote + +[FIRETRK] +Camion de pompier + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[VOODOO] +Voodoo + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulance + +[FBICAR] +FBI Washington + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[WASHING] +Washington + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[HUNTER] +Hunter + +[POLICAR] +Police + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[CUBAN] +Cuban Hermes + +[HELI] +Hélicoptère + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[ROMERO] +Corbillard de Romero + +[PACKER] +Packer + +[ADMIRAL] +Amiral + +[SQUALO] +Squalo + +[SEASPAR] +Sea Sparrow + +[PIZZABO] +Pizza Boy + +[GANGBUR] +Gang Burrito + +[TROPIC] +Tropic + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[CADDY] +Caddy + +[ZEBRA] +Taxi Zebra + +[TOPFUN] +Top Fun + +[SKIMMER] +Skimmer + +[PCJ600] +PCJ 600 + +[PHOENIX] +Phoenix + +[FAGGIO] +Faggio + +[FREEWAY] +Freeway + +[RCBARON] +RC Baron + +[RCRAIDE] +RC Raider + +[GLENDAL] +Glendale + +[OCEANIC] +Oceanic + +[SANCHEZ] +Sanchez + +[SPARROW] +Sparrow + +[LOVEFIS] +Love Fist + +[COASTG] +Garde-côte + +[DINGHY] +Dinghy + +[HERMES] +Hermes + +[SABRE] +Sabre + +[SABRETU] +Sabre Turbo + +[WALTON] +Walton + +[REGINA] +Regina + +[COMET] +Comet + +[DELUXO] +Deluxo + +[BURRITO] +Burrito + +[SPAND] +Spandex Express + +[MARQUIS] +Marquis + +[BAGGAGE] +Bagagiste + +[KAUFMAN] +Taxi Kaufman + +[COASTMA] +Garde-côte Maverick + +[MAVERIC] +Maverick + +[RANCHER] +Rancher + +[FBIRANC] +FBI Rancher + +[VIRGO] +Virgo + +[GREENWO] +Greenwood + +[HOTRING] +Hotring Racer + +[BLISTAC] +Blista Compact + +[FEST_DF] +Dist. parcourue à pied (miles) + +[FEST_DC] +Dist. parcourue en voiture (miles) + +[FESTDFM] +Distance parcourue à pied (m) + +[FESTDCM] +Distance parcourue en voiture (m) + +[TOT_DIS] +Distance totale parcourue (miles) + +[TOTDISM] +Distance totale parcourue (m) + +[DISTHEL] +Dist. parcourue en hélicoptère (miles) + +[DISTHEM] +Dist. parcourue en hélicoptère (m) + +[DISTBOA] +Dist. parcourue en bateau (miles) + +[DISTBOM] +Dist. parcourue en bateau (m) + +[FEST_LS] +Personnes secourues en ambulance + +[FEST_CC] +Criminels tués lors de la mission d'autodéfense + +[FEST_FE] +Nombre total de feux éteints + +[FEST_RP] +Rodéos réussis + +[FEST_MP] +Missions réussies + +[FEST_BB] +Les fous du volant : + +[FEST_H0] +Nombre maximum de 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 + +[USJ] +BONUS POUR CASCADE UNIQUE! + +[RATNG1] +Citoyen honnête + +[RATNG2] +Quidam + +[RATNG3] +Eboueur + +[RATNG4] +Voleur à l'étalage + +[RATNG5] +Vandale + +[RATNG6] +Escroc + +[RATNG7] +Pickpocket + +[RATNG8] +Cleptomane + +[RATNG9] +Mouchard + +[RATNG10] +Rat + +[RATNG11] +Sangsue + +[RATNG12] +Le roi de l'arnaque + +[RATNG13] +Aigrefin + +[RATNG14] +Grippe-sou + +[RATNG15] +Crapule + +[RATNG16] +Rascal + +[RATNG17] +Racaille + +[RATNG18] +Garnement + +[RATNG19] +Vaurien + +[RATNG20] +Hors-la-loi + +[RATNG21] +Voyou + +[RATNG22] +Lâche + +[RATNG23] +Idiot SA + +[RATNG24] +Idiot + +[RATNG25] +Récidiviste + +[RATNG26] +Ancien taulard + +[RATNG27] +Criminel + +[RATNG28] +Clochard + +[RATNG29] +Sage + +[RATNG30] +Pilote + +[RATNG31] +Gros bras + +[RATNG32] +Tueur à gages + +[RATNG33] +Chasseur de tête + +[RATNG34] +Force de l'ordre + +[RATNG35] +Ronin + +[RATNG36] +Magouilleur + +[RATNG37] +Tueur + +[RATNG38] +Associé + +[RATNG39] +Boucher + +[RATNG40] +Nettoyeur + +[RATNG41] +Assassin + +[RATNG42] +Conseiller + +[RATNG43] +Autodidacte + +[RATNG44] +Bras droit + +[RATNG45] +Exécutant + +[RATNG46] +Lieutenant + +[RATNG47] +Sous-chef + +[RATNG48] +Capo + +[RATNG49] +Patron + +[RATNG50] +Noops + +[RATNG51] +Don + +[RATNG52] +Roi de la putain de ville + +[PAGE_00] +. + +[WELCOME] +BIENVENUE A + +[TSCORE] +REVENUS : ~1~$ + +[PBOAT_2] { reVC update } +Appuie sur la ~h~~k~~VEHICLE_FIREWEAPON~~w~ pour tirer avec les canons du bateau. + +[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! + +[ATUTOR] +Appuie sur la ~h~~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions ambulance. + +[FEST_HA] +Mission ambulance, niveau maximum + +[C_KILLS] +CRIMINELS TUES : ~1~ + +[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! + +[CRED001] +ROCKSTAR NORTH + +[CRD001A] +DIRECTEUR DU STUDIO + +[CRD001B] +ANDREW SEMPLE + +[CRED002] +PRODUCTEUR + +[CRD002A] +DIRECTEUR DU DEVELOPPEMENT + +[CRED003] +LESLIE BENZIES + +[CRED004] +CONCEPTEUR GRAPHIQUE + +[CRED005] +AARON GARBUT + +[CRED006] +DIRECTEURS TECHNIQUES + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED010] +ANDREW DUTHIE + +[CRED011] +CRAIG FILSHIE + +[CRED012] +WILLIAM MILLS + +[CRED013] +CHRIS ROTHWELL + +[CRD013A] +IMRAN SARWAR + +[CRD013B] +JAMES WORRALL + +[CRD013C] +JOHN HAIME + +[CRED014] +ECRIT PAR + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +CONCEPTEUR PRINCIPAL DES PERSONNAGES + +[CRD018A] +CONCEPTEUR DES PERSONNAGES + +[CRED019] +IAN MCQUE + +[CRD019A] +TOKS SOLARIN + +[CRD019B] +ALAN DAVIDSON + +[CRED020] +ANIMATEUR PRINCIPAL + +[CRED021] +ALEX HORTON + +[CRD022A] +ANIMATEURS + +[CRED022] +LEE MONTGOMERY + +[CRD022B] +DUNCAN SHIELDS + +[CRD022C] +GUS BRAID + +[CRED023] +CONCEPTEURS DES VEHICULES + +[CRD023A] +JOLYON ORME + +[CRD023B] +ALAN DUNCAN + +[CRD024A] +CONCEPTEUR PRINCIPAL DES VEHICULES + +[CRED024] +PAUL KUROWSKI + +[CRED025] +CONCEPTEURS DE CARTE + +[CRED026] +ADAM COCHRANE + +[CRED027] +NIK TAYLOR + +[CRED028] +GARY MCADAM + +[CRED029] +KEIRAN BAILLIE + +[CRED030] +ALISDAIR WOOD + +[CRED031] +ANDREW SOOSAY + +[CRD031A] +STEVEN MULHOLLAND + +[CRD031B] +WAYLAND STANDING + +[CRD031C] +CAMPBELL J. DICK + +[CRD031D] +CONCEPTEUR GRAPHIQUE + +[CRD031E] +STUART PETRI + +[CRED032] +PROGRAMMEUR PRINCIPAL + +[CRD032A] +PROGRAMMEURS + +[CRED033] +ALEXANDER ROGER + +[CRED034] +GRAEME WILLIAMSON + +[CRED035] +BARANE CHAN + +[CRED036] +DEREK PAYNE + +[CRED037] +GORDON YEOMAN + +[CRD037A] +ALAN CAMPBELL + +[CRD037B] +MARK HANLON + +[CRD037C] +ANDRZEJ MADAJCZYK + +[CRED038] +PRODUCTEUR MUSIQUE PRINCIPAL + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +INGENIEUR AUDIO PRINCIPAL + +[CRED042] +ALLAN WALKER + +[CRD041A] +INGENIEUR DU SON + +[CRD041B] +AUDIO + +[CRD042A] +WILL MORTON + +[CRED043] +PROGRAMMEUR AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +RESPONSABLE DES TESTS + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +RESPONSABLE CQ + +[CRED048] +NEIL CORBETT + +[CRED049] +KEVIN WONG + +[CRED050] +CQ + +[CRED051] +DAVID BEDDOES + +[CRED052] +DAVID WATSON + +[CRED053] +BARRY CLARK + +[CRED054] +ROSS SPARROW + +[CRED055] +JAMES ALLAN + +[CRED056] +NEIL MEIKLE + +[CRD056A] +GEORGE WILLIAMSON + +[CRD056B] +MATT JONES + +[CRD056C] +ROB HARBOUR + +[CRD056D] +TOM WHITTAKER + +[CRED057] +ASSISTANCE TECHNIQUE PRINCIPALE + +[CRED058] +LORRAINE ROY + +[CRD057A] +ASSISTANCE TECHNIQUE + +[CRED059] +CHRISTINE CHALMERS + +[CRD060A] +SERVICES GENERAUX + +[CRD060B] +KIM GURNEY + +[CRD060C] +CASSIE OLIVER + +[CRED060] +ROCKSTAR NEW YORK + +[CRED061] +PRODUCTEUR EXECUTIF + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCTEUR + +[CRED064] +DAN HOUSER + +[CRED065] +VICE-PRESIDENT DU DEVELOPPEMENT + +[CRED066] +JAMIE KING + +[CRED067] +RESPONSABLE TECHNOLOGIE + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUCTEUR ASSOCIE + +[CRED070] +JEREMY POPE + +[CRD071A] +RESPONSABLE CONTROLE QUALITE + +[CRD072A] +JEFF ROSA + +[CRED071] +RESPONSABLE MUSIQUE + +[CRED072] +TERRY DONOVAN + +[CRED073] +EQUIPE DE PRODUCTION + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +JERONIMO BARRERA + +[CRED080] +CARLY SLATER + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +RENAUD SEBANNE + +[CRED084] +RICHARD KRUGER + +[CRD084A] +DANIEL EINZIG + +[CRD084B] +JACEN BURROWS + +[CRD084C] +LINN PR + +[CRD084D] +COUVERTURE + +[CRD084E] +STEPHEN BLISS + +[CRED085] +KENT PAUL'S 80 NOSTALGIA ZONE + +[CRED086] +ECRIT PAR DAN HOUSER + +[CRD086A] +PRODUIT PAR ADAM TEDMAN + +[CRED087] +WWW.KENTPAUL.COM ET WWW.VICECITY.COM + +[CRED088] +CREES PAR + +[CRD088A] +ADAM TEDMAN + +[CRD088B] +DAVID YU + +[CRD088C] +JERRY LUNA + +[CRD088D] +STUART PETRI + +[CRD088E] +MICHAEL CARNEVALE + +[CRD088F] +GREG LAU + +[CRD088G] +FUTABA HAYASHI + +[CRED089] +RESPONSABLE CQ + +[CRED090] +CRAIG ARBUTHNOTT + +[CRED091] +ANALYSTE PRINCIPAL + +[CRED092] +ADAM DAVIDSON + +[CRD092A] +JOE HOWELL + +[CRD092B] +MARC FERNANDEZ + +[CRED093] +ANALYSTE DE JEU + +[CRED094] +RICHARD HUIE + +[CRED095] +EQUIPE DE TEST CHEZ ROCKSTAR + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRD098A] +ELIZABETH SATTERWHITE + +[CRD098B] +JAMEEL VEGA + +[CRD098C] +MIKE HONG + +[CRED099] +LEE CUMMINGS + +[CRED100] +HISTOIRE + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CINEMATIQUES + +[CRED108] +ECRITES PAR DAN HOUSER ET JAMES WORRALL + +[CRED109] +DIRECTION DE L'AUDIO PAR DAN HOUSER ET NAVID KHONSARI + +[CRED110] +PRODUCTION DE JAMIE KING + +[CRD110A] +CASTING : JAMIE KING, SEAN MACALUSO + +[CRED111] +DISTRIBUTION + +[CRD111A] +PERSONNAGES SECONDAIRES + +[CRED112] +TOMMY VERCETTI : RAY LIOTTA + +[CRED113] +KEN ROSENBERG : WILLIAN FICHTNER + +[CRED114] +SONNY FORELLI : TOM SIZEMORE + +[CRED115] +STEVE SCOTT : DENNIS HOPPER + +[CRED116] +AVERY CARRINGTON : BURT REYNOLDS + +[CRED117] +RICARDO DIAZ : LUIS GUZMAN + +[CRED118] +LANCE VANCE : PHILIP MICHAEL THOMAS + +[CRED119] +COLONEL JUAN CORTEZ : ROBERT DAVI + +[CRED120] +UMBERTO ROBINA : DANNY TREJO + +[CRED121] +PHIL CASSIDY : GARY BUSEY + +[CRED122] +MITCH BAKER : LEE MAJORS + +[CRED123] +MERCEDES CORTEZ : FAIRUZA BALK + +[CRED124] +KENT PAUL : DANNY DYER + +[CRED125] +JEZZ TORRENT : KEVIN MCKIDD + +[CRED126] +REGULATRICE TAXI : DEBORAH HARRY + +[CRED127] +CANDY SUXX : JENNA JAMESON + +[CRED128] +BJ SMITH : LAWRENCE TAYLOR + +[CRED129] +TATA POULET : YOUREE CLEOMILI HARRIS + +[CRED130] +FOURNISSEUR : ARMANDO RIESCO + +[CRED131] +COUGAR : BLAYNE PERRY + +[CRED132] +HILARY : CHARLES TUCKER + +[CRED133] +ALEX SHRUB, REPRESENTANT DU CONGRES : CHRIS LUCAS + +[CRED134] +LE VIEUX KELLY : GEORGE DICENZO + +[CRD134A] +CAM JONES : GREG SIMS + +[CRD134B] +PSYCHOPATHE : HUNTER PLATIN + +[CRD134C] +MAUDE, LA VENDEUSE DE GLACES : JANE GENNARO + +[CRD134D] +JETHRO : JOHN ZURHELLEN + +[CRD134E] +GONZALES : JORGE PUPO + +[CRD134F] +DWAYNE : NAVID KHONSARI + +[CRD134G] +DICK : PETER MCKAY + +[CRD134H] +MIKE, L'ACTEUR DE PORNO : ROBERT CIHRA + +[CRD134I] +PERCY : RUSSELL FOREMAN + +[CRED135] +CAPTURE DE MOUVEMENTS + +[CRED136] +ANIME PAR + +[CRD136A] +DIRECTION TECHNIQUE PAR ALEX HORTON + +[CRED137] +DIRIGE PAR + +[CRD137A] +DIRIGE PAR NAVID KHONSARI + +[CRED138] +PRODUIT PAR + +[CRD138A] +PRODUIT PAR JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +ENREGISTRE AUX PERSPECTIVE STUDIOS, BROOKLYN + +[CRED140] +ACTEURS POUR LA CAPTURE DE MOUVEMENTS + +[CRD140A] +BLAYNE PERRY + +[CRD140B] +JONATHON SALE + +[CRD140C] +CHARLES TUCKER + +[CRD140D] +EDDIE MARRERO + +[CRD140E] +WILLIAM MCCALL + +[CRD140F] +JORGE PUPO + +[CRD140G] +ROBERT JACKSON + +[CRD140H] +TARA RADCLIFFE + +[CRD140I] +JENIFER GAMBETESE + +[CRD140J] +KRIS ACHEVARRIA + +[CRD140K] +ALI ORDOUBADI + +[CRD140L] +KAHLEEM POOLE + +[CRED141] +DIALOGUES DES PIETONS + +[CRD141A] +ECRIT PAR DAN HOUSER, MARC FERNANDEZ, GILLIAN TELLING ET NAVID KHONSARI + +[CRD141B] +AVEC L'AIDE DE JEREMY POPE, LANCE WILLIAMS ET JENNY JEMISON + +[CRED142] +ECRITS PAR + +[CRD142A] +DAN HOUSER ET JAMES WORRALL + +[CRED143] +DIRIGE PAR DAN HOUSER, CRAIG CONNER, MARC FERNANDEZ ET ALLAN WALKER + +[CRED144] +PRODUITS PAR RENAUD SEBANNE + +[CRED145] +PIETONS + +[CRED146] +ADAM DAVIDSON, ADAM WATKINS, ALEJANDRO K. BROWN, ALEX ANTHONY SIOUKAS, ALEX GARCIA, + +[CRED147] +ALICE SALTZMAN, ALISON CIHRA, AMY SALIMA, AMY SALZMAN, ANDREA VIDELA, ANTHONY ATTI, + +[CRED148] +ANTHONY RIVERA, BIJAN SHAMS, BLAYNE PERRY, BRETT BISOGNO, BREYE MATA, BRIAN PANEN, + +[CRED149] +BROCK VODER, CAREY BERTINI, CHARISSE LAMBERT, CHRIS DIFAT, CHRIS REISENBERGER, + +[CRED150] +CHRISTOPHER BRODAY, CHRISTOPHER CARRO, CYNTHIA GREENE, DAMARIES LOPEZ, DAN LEE, + +[CRED151] +DAN SCHNEIDER, DAN TOYAMA, DAVID DEAN CHALTFIELD, JR., DAVID HARRISON, DAVID WILEY, + +[CRED152] +DEBORAH COLLINS, DEBRANDA CHANEY-GILES, DEMETRA KOUKOULAS, DENISE ROSADO, + +[CRED153] +DEVIN BENNETT, DEVIN WINTERBOTTOM, DORIS WOO, DOUGLAS HARRISON, DUNCAN COUTTS, + +[CRED154] +DUPE AJAYI, EDWIN AVELLANEDA, ELIZABETH HOWELL, ELIZABETH SATTERWHITE, ERIC NAGLE, + +[CRED155] +ESTEBAN KARPLUS, F. FONT, FUTABA HAYASHI, GENE HILGREEN, GERALD COSGROVE, + +[CRED156] +GERARD LUNA, GILLIAN TELLING, GREGG CARLUCCI, GREGORY CLERVOIX, GREGORY SCHWEIZER, + +[CRED157] +HADLEY TOMICKI, J. ROSSETT, JAMEEL VEGA, JASON JONES, JEFF ROSA, JENNIFER JEMISON, + +[CRED158] +JEREMY TAGGERT, JESSICA RIDER, JOSEPH GREENE,JOSEPH HOWELL, KATE DUKICH, + +[CRED159] +KEL O'NEILL, KEVIN HOPKINS, LADAWN JAMES, LANCE WILLIAMS, LAURA BUBBLES, + +[CRED160] +LAURA PATTERSON, LEE CUMMINGS, LETICIA L. YOUNG, LINDSAY KENNEDY, LISA ORITZ, + +[CRED161] +LORNA JORDAN, LUCIO AMADIO, MARCO FERNANDEZ, MARIKO TANAKA, MARLON MATTHEWS, + +[CRED162] +MARY TELLING, MASAYOSHI MITSUYAMA, MATTHEW CHUNG, MAX ALLSTADT, MAX BOGDANOV, + +[CRED163] +MELISSA ALVAREZ, MICHAEL MAY, MICHAEL ROTHSTEIN, MIGUEL VIDAL, MIKE FEDERLINE, + +[CRED164] +NATALIE DESCALZO, N'GAI MEMBERS, NICOLAS MALLO, NOELLE SADLER, NORBERT MORIVAN, + +[CRED165] +OSWALD GREENE, JR., PETER MCKAY, PETER APPEL, PRESTON SAVARESE, RAFAEL GONZALES, + +[CRED166] +RANDY JOHNSON, REY CONCEPCION, RICHARD KROGER, ROB TIBBS, ROBERT JACKSON, + +[CRED167] +ROBERT SCHULER, ROSS A. MCINTYRE, RUSSELL FOREMAN, RUTH NUNEZ, SALVADORE SUAZO, + +[CRED168] +SAM WHITE, SANTOS GONZALES, SCOTT SMITH, SEYMOUR FRAILMAN, SPELMAN BRAUMAN, + +[CRED169] +STEPHANIE TELLING, STEVE KNEZEVICH, STEVE ROBERT, SUMIKO YASUDA, SUSAN LEWIS, + +[CRED170] +SYLVIA COLACIOS, TOMOKO MIYAZAKI, TRON, VERDEL HALE, YVES MONDESIR, ZENO LEINFELDER, + +[CRED171] +DAVID BEDDOES, CHRISTINE CHALMERS, BARRY CLARK, NEIL CORBETT, KIM GURNEY, NEIL MEIKLE, + +[CRED172] +CASSIE OLIVER, LORRAINE ROY, DAVID WATSON, KEVIN WONG, WILL MORTON + +[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] +STATIONS DE RADIO ET MUSIQUE + +[CRED218] +CONSULTANT EN MUSIQUE + +[CRD218A] +HEINZ HENN + +[CRD218B] +STUART ROSS + +[CRED219] +COORDINATEUR DE LA BANDE-SON + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCTEUR CHEZ ROCKSTAR GAMES + +[CRED222] +DAN HOUSER ET LAZLOW + +[CRED223] +PRODUCTEUR CHEZ ROCKSTAR NORTH + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER ET IMAGING + +[CRED228] +ECRIT PAR DAN HOUSER ET LAZLOW + +[CRED229] +FLASH FM + +[CRD229A] +TONI-MARIA CHAMBERS + +[CRD229B] +VOIX ET PRODUCTION : JEFF BERLIN + +[CRED230] +REMERCIEMENTS SPECIAUX A + +[CRED231] +TOMMY MOTTOLA, + +[CRED232] +MICHELLE ANTHONY, + +[CRED233] +STEVE BARNETT, + +[CRED234] +CHUCK FLECKENSTEIN, + +[CRED235] +RITA LIBERATOR + +[CRED236] +MARTIN ET CLAIRE LOGAN + +[CRED237] +SANDRA HUTTON + +[CRED238] +CHRISTINE DAVIDSON + +[CRED239] +ALAN, RED ET BIGFOOT + +[CRED240] +LE T + +[CRED241] +COLIN DONALD + +[CRED242] +KERRY STALLWOOD + +[CRED243] +ALAN MCGREGOR + +[CRED244] +CHRIS MORTON + +[CRED245] +EMIL BUSSE + +[CRED246] +EMILY BAILLIE + +[CRED247] +KEVIN ARCHIBALD + +[CRED248] +MORAG KERR + +[CRED249] +CATH WALKER + +[CRED250] +ISO BAR + +[CRED251] +WATERLINE + +[CRED252] +NEWS CAFE + +[CRD251A] +THE POND + +[CRD252A] +PIVO + +[CRED253] +BUDGET VIDEO RENTALS + +[CRED254] +LORNA'S SCOOTER + +[CRED255] +GARETH MURFIN + +[CRED256] +GRAPHISMES ADDITIONNELS + +[CRED257] +TONY PORTER + +[CRED258] +CRAIG MOORE + +[CRED259] +ANIMATION SYNCHRO LABIALE DES CINEMATIQUES + +[CRED260] +COSGROVE HALL FILMS + +[CRED261] +PRODUCTEUR : OWEN BALLHATCHET + +[CRED262] +ANIMATEUR SENIOR : JON TURNER + +[CRED263] +ANIMATEURS : RICHARD DRUMM + +[CRED264] +DAVE BROWN + +[CRED265] +MAIR THOMAS + +[CRED266] +PRASHANT PATEL + +[CRED267] +CONSULTANT TECHNOLOGIE AUDIO + +[CRED268] +RIK EDE POUR GAMESOUND LTD. + +[CRED269] +ASSISTANCE INTEGRATION DTS + +[CRED270] +TED LAVERTY POUR DTS + +[CRED271] +CHRIS GREER POUR DTS + +[CRED272] +JASON PAGE POUR SCEE + +[CRED273] +RECHERCHE ET ANALYSE + +[CRED274] +VROCK + +[CRED275] +DJ : LAZLOW + +[CRED276] +VOIX : JOE KELLY + +[CRED277] +PRODUCTION : JONATHAN HANST + +[CRED278] +ONDE 103 + +[CRED279] +DJ : ADAM FIRST-JAMIE CANFIELD + +[CRED280] +VOIX : JEN SWEENLEY + +[CRED281] +PRODUCTION : JONATHAN HANST + +[CRED282] +FEVER 105 + +[CRED283] +DJ : OLIVER 'LADYKILLER' BISCUIT-JULIUS DYSON + +[CRED284] +VOIX D'HOMME : ED McMANN + +[CRED285] +VOIX DE FEMME : SHWNEE SMITH + +[CRED286] +PRODUCTION : LISTEN KISTEN + +[CRED287] +EMOTION 98.3 + +[CRED288] +DJ : FERNANDO- FRANK CHAVEZ + +[CRED289] +VOIX : JEN SWEENLEY + +[CRED290] +PRODUCTION : JONATHAN HANST + +[CRED291] +RADIO ESPANTOSO + +[CRED292] +DJ : PEPE-TONY CHILRODES + +[CRED293] +WILDSTYLE + +[CRED294] +DJ : MISTER MAGIC + +[CRED295] +VOIX : FRANK SILVESTRO + +[CRED296] +PRODUCTION : LAZLOW + +[CRED297] +KCHAT + +[CRED298] +ECRIT PAR DAN HOUSER ET LAZLOW + +[CRED299] +PRODUIT ET EDITE PAR LAZLOW + +[CRED300] +DJ AMY SHECKENHAUSEN : LEYNA WEBER + +[CRED301] +JEZ TORRENT : KEVIN MCKIDD + +[CRED302] +MANDY : COLLEEN CORBETT + +[CRED303] +MICHELLE CARAPADIS : MARY BIRDSONG + +[CRED304] +MR.ZOO : CARL DOWLING + +[CRED305] +GETHSEMANEE : LYNN LIPTON + +[CRED306] +CLAUDE MAGINOT : JOHN MAUCERI + +[CRED307] +BJ SMITH : LAWRENCE TAYLOR + +[CRED308] +THOR : FRANK FAVA + +[CRED309] +INTERLOCUTEURS + +[CRED310] +COUZIN ED, JOSH CLARK, JASON BUHRMESTER, JUAN ALLER, WAYNE OLIVER, SUSAN LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, KEITH BROADAS + +[CRED311] +LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, + +[CRED312] +DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, + +[CRED313] +KEITH BROADAS + +[CRED314] +VCPR + +[CRED315] +ECRIT PAR DAN HOUSER ET LAZLOW + +[CRED316] +PRODUIT PAR LAZLOW + +[CRED317] +MAURICE CHAVEZ : PHILLIP ANTHONY RODRIGUEZ + +[CRED318] +JONATHAN FREELOADER : PATRICK OLSEN + +[CRED319] +MICHELLE MONTANIUS : KELLY GUEST + +[CRED320] +ALEX SHRUB : CHRIS LUCAS + +[CRED321] +CALLUM CRAYSHAW : SEAN MODICA + +[CRED322] +JOHN F. HICKORY : LJ GANSEN + +[CRED323] +LE PASTEUR RICHARDS : DAVID GREEN + +[CRED324] +JAN BROWN : MAUREEN SILLIMAN + +[CRED325] +BARRY STARK : RENAUD SEBBANE + +[CRED326] +JENNY LOUISE CRAB : MARY BIRDSONG + +[CRED327] +KONSTANTINOS SMITH : KONSTANTINOS.COM + +[CRED328] +JEREMY ROBARD : PETER SILVESTRO + +[CRED329] +PUBLICITES RADIO + +[CRED330] +ECRITES PAR DAN HOUSER ET LAZLOW + +[CRED331] +PRODUITES PAR LAZLOW + +[CRED332] +JINGLES SUPPLEMENTAIRES PRODUITS PAR CRAIG CONNER + +[CRED333] +VOIX DES PUBLICITES : + +[CRED334] +ADAM DAVIDSON, ALEX ANTHONY, ALICE SALTZMAN, AMY SALZMAN, KATE DUKICH, + +[CRED335] +ARAN RONICLE, BARB JONES, BEN KRECH, BRIAN THOMAS, BROCK YODER, CHRIS + +[CRED336] +FERRANTE, CRAIG CONNER, DAVE RYAN, DAVID GREEN, DORIS WOO, DOUGLAS + +[CRED337] +HARRISON, ED MCMANN, FRANK CHAVEZ, FRANK FAVA, GENE HILGREEN, GREG + +[CRED338] +SCHWEIZER, HUNTER PLATIN, JAMES FERRANTE, JEFF BERLIN, JEFF ROSA, JOE KELLY, + +[CRED339] +JOHN MAUCERI, JOSH CLARK, JULIE WEMYSS, KEVIN STRALEY, KIM GURNEY, LANCE + +[CRED340] +WILLIAMS, LAURA PATERSON, LAZLOW, LISA ORTIZ, LORNA JORDAN, LUCIEN JONES, + +[CRED341] +MAUREEN SILLIMAN, MIKE FERRANTE JR., PETE GUSTIN, PETER SILVESTRO, RAFF + +[CRED342] +CROLLA, RANDY JOHNSON, RICHARD KRUGER, RON REEVE, SHELLEY MILLER, SKY, TJ ALLARD + +[CRD344A] +ENREGISTREMENTS AUX DIGITAL ARTS STUDIOS, + +[CRED344] +NYC, TRACK 9 STUDIOS, NYC, + +[CRED345] +WEDDINGTON MULTIMEDIA, LOS ANGELES, + +[CRD345A] +SYNC SOUND, NYC ET RADIO LAZLOW, LONG ISLAND. + +[CRED346] +MERCI A AXEL ERICSON ET WON LEE DE DIGITAL ARTS, PAUL VASQUEZ DETRACK 9 STUDIOS, JOHN BOWEN ET JOHN HASSLER DE SYNC SOUND. + +[CRED347] +MARK LLOYD + +[CRED348] +TIM BATES + +[CRED349] +KIT BROWN + +[CRED350] +ANDY MASON + +[CRED351] +PHIL DEANE + +[CRED352] +PHIL ALEXANDER + +[CRED353] +MATT HEWITT + +[CRED354] +DENBY GRACE + +[CRED355] +ANTOINE CABROL + +[CRED356] +JONATHAN STONES + +[CRED357] +MIKE BLACKBURN + +[CRED358] +TIM MCGAFF + +[CINCAM] +Caméra cinématique + +[RC4] +'RODEO DE RUMPO' + +[LEGAL] +~g~Elimine toute menace criminelle! + +[GA_2] +Nouveau moteur et nouvelle peinture. Les flics ne te reconnaîtront plus! + +[HELP15] +Si tu es à pied, appuie sur la ~h~~k~~PED_LOOKBEHIND~~w~ pour ~h~regarder derrière~w~. Utilise le ~h~joystick analogique droit~w~ pour ~h~regarder autour~w~ de toi. + +[FEC_LB4] +Regarder derrière (touche R3) + +[PERPIC] +Paquets cachés trouvés + +[CO_ONE] +Paquet caché ~1~ sur ~1~ + +[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 + +[USJ_ALL] +TOUTES LES CASCADES ONT ETE ACCOMPLIES! + +[JAN] +Jan + +[FEB] +Fév + +[MAR] +Mar + +[APR] +Avr + +[MAY] +Mai + +[JUN] +Jun + +[JUL] +Jui + +[AUG] +Aoû + +[SEP] +Sept + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Déc + +[DEFDT] +--:---:---- --:--:-- + +[BONUS] +~g~BONUS $~1~ + +[HORN1] +Appuie sur la ~h~~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner. + +[HORN2] +Appuie sur la ~h~~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner. + +[HORN3] +Appuie sur la ~h~~k~~VEHICLE_HORN~ ~w~pour ~h~klaxonner. + +[FEC_EXV] +Monter et descendre d'un véhicule + +[TAXI_M] +'CHAUFFEUR DE TAXI' + +[COP_M] +'AUTODEFENSE' + +[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~ + +[FESZ_LS] +Chargement effectué. + +[HELI_1A] +Teste tes capacités avec le Sparrow et vois en combien de temps tu peux réussir le parcours. + +[HELI_1B] +Parcours réussi! + +[HELIODD] +Petits boulots en hélico + +[INT2_M] +Ils sont proprios d'une ferme au Panama! + +[INT2_N] +Ok, écoutez un peu... + +[INT2_O] +Les gars, quand on sera là-bas, est-ce que je dois rester dans la bagnole, + +[INT2_P] +où bien est-ce que vous voulez absolument que je vienne avec vous? + +[INT2_Q] +Non, Reste dans la bagnole. + +[INT2_R] +Vous savez quoi, j'ai réfléchi à la question, + +[INT2_S] +Je vais surveiller la bagnole. + +[INT4_A] +On est baisés! Baisés! + +[INT4_B] +Putain, c'est vraiment le coup classique! + +[INT4_C] +Je sors la tête d'un caniveau pour une seconde de défonce, + +[INT4_D] +et le destin me chie de la merde plein la gueule! + +[INT4_E] +Allez vous faire mettre! + +[INT4_F] +Ferme-la et arrêter de gueuler! T'es encore vivant, pas vrai? + +[INT4_G] +Laisse-moi là. + +[INT4_H] +Débarrasse-toi de la caisse et va roupiller un bon coup. + +[INT4_I] +Je passerai à ton bureau demain et on essayera de trouver une solution à ce merdier. + +[INT4_J] +Ouais, bonne idée, je vais aller roupiller. + +[INT4_K] +Qu'est-ce que tu vas faire? + +[INT4_L] +Rentrer à mon hôtel. + +[INT4_M] +Et penser à autre chose que ces conneries. + +[INT4_N] +Ok. + +[LAW] +MISSIONS AVOCAT + +[LAW1_1] +~g~Va te procurer de nouvelles fringues à la boutique de Rafael. + +[LAW4_6] +Brûlez la direction! + +[LAW4_7] +Tuez les patrons! + +[LAW4_8] +Allez! Battez-vous! Allez! + +[LAW4_9] +Plus de vacances! Moins de travail! + +[LAW4_11] +Allez! Battez-vous! Allez! + +[LAW4_12] +Viva la revolucion! + +[GENERAL] +MISSIONS DU COLONEL + +[GEN3_4] +Tommy Vercetti. Allons-y... + +[GEN3_13] +C'est quoi ton problème, mec? Monte sur le toit de l'autre côté de la cour avant qu'ils rappliquent! + +[GEN3_17] +Meeeerde! T'essayes de m'buter ou quoi? + +[GEN3_21] +~g~Il a le pognon de Diaz! Attrape-le et récupère le fric! + +[GEN3_24] +~r~Diaz est mort! Tu devais le protéger! + +[GEN3_26] +~r~T'as flingué Diaz! + +[GEN3_27] +~r~T'as descendu les gardes du corps de Diaz! + +[GEN3_31] +~g~Va au rencart et veille sur Diaz. + +[GEN3_32] +~g~Va te mettre en position sur le toit du bâtiment en face de Lance. + +[COKE] +MISSIONS BARON DE LA COKE + +[COK1_3] +J'espère que tu vas te casser la gueule! + +[COK1_6] +J'en ai marre de ces merdeux. + +[COK2_7] +Tu vois ces marqueurs? Essaye de flinguer les lumières! + +[COK2_10] +En tout cas, tu tires mieux que tu causes. + +[COK2_11] +Merci. T'as un certain charme aussi. + +[COK2_12] +Je sais, Tommy. + +[COK2_18] +T'aimes bien Kenny Loggins? + +[COK2_19] +Tu parles, j'adore ce disque ouais! + +[COK2_26] +~r~T'as buté Lance! + +[COK3_1] +Tire pas, mec! + +[COK3_2] +Qu'est-ce que c'est que ça? + +[COK3_3] +Il embarque le bateau! Merde! + +[COK3_4] +A l'aide! Il y a un mec qui nous fauche le bateau! + +[COK4_W] +Bon! C'est le dernier. + +[COK4_X] +Je vais mettre le moteur en route. + +[COK4_Y] +Je crois qu'on a de la visite... + +[COK4_2] +Ouais. + +[COK4_6] +Tu sais où on va? + +[COK4_7] +On est paumés? + +[COK4_8] +On a de la concurrence! + +[COK4_9] +Bousille-les! + +[COK4_9A] +L'heure de la danse de Lance Vance a sonné! + +[COK4_10] +Réduits en miettes! Bons à nourrir la poiscaille! + +[COK4_11] +On a réussi! Les autres bateaux ne sont pas de taille. + +[COK4_17] +Ils sont prêts à tout! + +[COK4_18] +J'ai les pieds mouillés! On prend la flotte! + +[COK4_21] +Pont droit devant! + +[COK4_22] +Ecope, on va chavirer! + +[COK4_23] +Joli coup. + +[COK4_29] +~r~T'as buté Lance! + +[ASS1_6] +Continue, Tommy, ça va aller! + +[ASS1_7] +Prenez ça, enculés d'assassins! + +[ASS1_8] +Je suis coincé! + +[ASS1_9] +Je te couvre, Tommy! + +[ASS1_10] +Hé, c'est chouette toutes ces plantes... + +[ASS1_11] +Hé, Tommy, je peux avoir une chambre avec vue sur la baie? + +[ASS1_12] +Il y a vraiment de superbes plafonds, ici... + +[ASS1_3] +Lance! Couvre-moi! + +[ASS1_5] +Lance! + +[ASS1_15] +~g~Attaque la résidence et tue Diaz! + +[ASS1_17] +~g~Il existe de nombreux accès à la résidence. + +[TAXWAR] +MISSIONS GUERRE DES TAXIS + +[NOTAXI] +~g~T'as besoin d'un taxi Kaufman pour activer cette mission. + +[TAXW1_5] +~g~Faut que tu sois dans un taxi Kaufman! + +[TAX2_4] +Vas-y, Tommy. + +[TAX2_5] +Massacre-lui la tête. + +[TAX2_6] +Il a même pas son permis. + +[TAX2_7] +Putain de limousines! + +[TAXW3_1] +~g~Va prendre Mercedes. + +[RACE1] +~g~3..2..1.. PARTEZ! + +[RACE2] +~g~3 + +[RACE3] +~g~2 + +[RACE4] +~g~1 + +[RACE5] +~g~PARTEZ! + +[FIRST] +~b~1er + +[SECOND] +~b~2e + +[THIRD] +~b~3e + +[FOURTH] +~b~4e + +[RACETM] +~b~TEMPS DE COURSE : ~1~:~1~ + +[RACETM2] +~b~TEMPS DE COURSE : ~1~:0~1~ + +[RACEFA] +~r~Tu n'as pas gagné la course! + +[TEX1_5] +~r~Il s'est tiré! + +[SEG3_1] +TEMPS: + +[SEG3_2] +~g~Va à la camionnette qui contient le bombardier télécommandé et les bombes à retardement. + +[SEG3_3] +~g~Utilise le bombardier télécommandé pour transporter 4 bombes vers 4 zones cibles sur le chantier. + +[SERG3_5] +~g~Tu ne peux transporter qu'une bombe à la fois et tu ne peux récupérer une bombe déjà larguée avec succès. + +[SEG3_7] +~g~Une fois que tu as largué la première bombe sur une zone cible, le compte à rebours s'enclenche. Tu dois larguer toutes les bombes avant la fin de celui-ci. + +[SEG3_8] +~g~Les 4 bombes doivent être larguées sur les 4 zones cibles pour réussir la mission et détruire le bâtiment. + +[SEG3_9] +~g~Tu as touché la cible! Plus que 3! + +[SEG3_10] +~g~Tu as touché la cible! Plus que 2! + +[SEG3_11] +~g~Tu as touché la cible! Plus qu'une! + +[SEG3_12] +~r~Tu as raté la cible! Va récupérer une bombe! + +[SEG3_13] +~g~Largue la bombe sur une zone cible. + +[SEG3_14] +~r~Le temps est écoulé et tu n'as pu détruire le bâtiment. + +[SEG3_15] +~r~Ton bombardier télécommandé a été détruit! Comment tu vas transporter les bombes, maintenant? + +[AVERY] +MISSIONS AVERY + +[ASM] +MISSIONS ASSASSIN + +[ASM_1] +MISSION ASSASSIN 1 + +[ASM1_1] +~g~Ton aide dans l'éradication de ces indésirables fut une excellente affaire. J'ai un autre travail pour toi. Regarde sous le téléphone. + +[ASM1_2] +~g~Rends-toi à la cabine en dehors du centre commercial à Washington. + +[ASM1_3] +~g~Carl Pearson, livreur de pizzas. Il ne doit pas terminer sa tournée. + +[ASM1_4] +~g~Tue le livreur de pizzas avant qu'il le livre toutes ses pizzas. + +[ASM_2] +MISSION ASSASSIN 2 + +[ASM_3] +MISSION ASSASSIN 3 + +[ASM3_A] +Marcus Hammond, Franco Carter, Dick Tanner, Nick Kong et Stuntman Driver appartiennent tous au syndicat européen qui s'apprête à faire un hold-up. + +[ASM3_B] +Ils sont tous en position. Il faut qu'ils soient tous morts avant le commencement. Tu as 9 minutes. J'ai laissé quelques flingues à proximité qui devraient t'être utiles. + +[ASM3_1] +~g~Va récupérer l'arme que M. Black a laissé pour toi. + +[ASM3_2] +~g~Ne t'approche pas trop près de la cible ou tu risques d'être repéré. + +[ASM3_3] +~g~Pour un travail propre et rapide, installe-toi près de leurs positions dans des emplacements d'où tu pourras facilement les liquider, sans être repéré. + +[ASM3_4] +~g~Il t'a vu! Vaudrait mieux le buter en vitesse, maintenant! + +[ASM3_5] +~g~Marcus Hammond est en position près des panneaux publicitaires dans Washington. + +[ASM3_6] +~g~Franco Carter est en position à côté de DBP Security non loin d'Ocean Drive. + +[ASM3_7] +~g~Dick Tanner est en position à côté de la bijouterie dans Vice Point. + +[ASM3_8] +~g~Nick Kong est en position près de Washington Beach. + +[ASM3_9] +~g~Stuntman Driver est en position à Washington. + +[ASM3_10] +~r~Tu n'as pas réussi à tous les descendre. + +[ASM_4] +MISSION ASSASSIN 4 + +[ASM4_1] +~g~Va récupérer le fusil laissé à ton intention dans le feuillage à l'extérieur du terminal de l'aéroport. + +[ASM4_2] +~g~Ne rate pas la cible où tu alerteras ses gardes du corps et garde tes distances pour qu'il ne te repère pas. + +[ASM4_3] +~g~Observe la femme sur le balcon au-dessus des comptoirs d'enregistrements du terminal. NE LA TUE PAS. + +[ASM4_4] +~g~Tue l'homme à qui elle donnera la serviette, mais seulement quand il l'aura récupérée. Prends ensuite la serviette et amène-la à Ammu-Nation dans le centre. + +[ASM4_5] +~g~Récupère la serviette! + +[ASM4_6] +~g~Amène la serviette à Ammu-Nation dans le centre! + +[ASM4_7] +~r~Imbécile! Tu as tué la femme! + +[ASM4_8] +~r~La cible t'a entendu tirer! Le deal est annulé! + +[ASM4_9] +~r~La cible a embarqué à bord de son avion! + +[ASM4_10] +~r~On dirait que tu n'es pas le seul à t'intéresser à cette serviette! Amène-la en vitesse à Ammu-Nation! + +[ASM4_11] +~r~La cible t'a repéré! Le deal est annulé! + +[ASM4_13] +~g~Il t'a repéré et essaye de s'enfuir! Rattrape-le et récupère la serviette! + +[ASM4_14] +~g~La barre de distance dans le coin supérieur droit de l'écran te signale ta proximité avec la cible. Ne la laisse pas devenir pleine ou il te repèrera. + +[ASM_5] +MISSION ASSASSIN 5 + +[KICK] +DEMARRAGE AU PIED + +[KICK1_3] +~g~Nombre de fautes de pied : ~1~ + +[KICK1_4] +~g~Pénalité de temps : ~1~ secondes + +[BANK] +MISSIONS BRAQUAGE + +[BANK1] +MISSION BRAQUAGE 1 + +[BANK2] +MISSION BRAQUAGE 2 + +[BJM2_3] +TAUX DE REUSSITE : ~1~% + +[BJM2_15] +SCORE : + +[BJM2_18] +SCORE A BATTRE : + +[BJM2_19] +~g~Touche autant de cibles que possible dans le temps imparti! + +[BJM2_21] +~g~Touche autant de cibles que possible jusqu'à épuisement de tes munitions. + +[BANK3] +MISSION BRAQUAGE 3 + +[BJM3_1] +~g~Trouve une bagnole qui a des chevaux et rends-toi sur la grille de départ. + +[BNK4_2A] +Les gars au garage ont fait du super boulot sur ce bébé. + +[BNK4_3G] +Oh, merde, maintenant, on a les flics au cul! + +[BNK4_3H] +Et on n'est même pas encore sur place... + +[BNK4_3K] +Va d'abord falloir semer les flics... + +[BNK4_3L] +Merde, Tommy, t'essayes de tous nous tuer? + +[BNK4_3N] +Tout ce que j'aime part en fumée! + +[BNK422A] +Cam, combien de temps? + +[BK4_23A] +Donne-moi 3 minutes! + +[BNK4_26] +Bordel de merde! Les voilà! + +[BNK4_32] +Sers-toi des explosifs pour ouvrir les coffres! + +[BNK4_43] +Je nous couvre, FONCE! + +[BNK4_51] +Je te trouve très bien comme ça. + +[KENT] +MISSIONS KENT PAUL + +[KENT1] +MISSION KENT PAUL 1 + +[COUNT] +MISSIONS CONTREFACON + +[COUNT1] +MISSION CONTREFACON 1 + +[COUNT2] +MISSION CONTREFACON 2 + +[BIKE] +MISSIONS BIKERS + +[BIKE1] +MISSION BIKERS 1 + +[BIKE2] +MISSION BIKERS 2 + +[BIKE3] +MISSION BIKERS 3 + +[GOAWAY1] +Reviens quand tu auras fini les missions du gang haïtien. + +[HAIT] +MISSIONS GANG HAITIEN + +[HAIT1] +MISSION GANG HAITIEN 1 + +[HAIT2] +MISSION GANG HAITIEN 2 + +[HAIT3] +MISSION GANG HAITIEN 3 + +[HAM3_6] +~g~Utilise le fusil à lunette que je t'ai donné pour les tuer! + +[ROCK] +MISSIONS GROUPE DE ROCK + +[ROK1_4] +~g~Ok, je crois que c'est ce que vous vouliez... + +[ROK1_1E] +~g~Ca te coûtera plus que ce que tu as! + +[ROK1_1F] +~g~Reviens quand tu auras le pognon! + +[RBM2_6] +~g~Waou! C'est un mec! Arrête-le! + +[ROCK3] +MISSION GROUPE DE ROCK 3 + +[ROK3_6D] +~r~en même temps que vos sales grosses têtes chevelues! + +[ROK3_40] +Près de la glacière? + +[RBM3_5] +~g~Emmène les Love Fist au concert. + +[CUBANM] +MISSIONS GANG CUBAIN + +[CUBAN1] +MISSION GANG CUBAIN 1 + +[CUBAN2] +MISSION GANG CUBAIN 2 + +[CUB2_10] +~r~T'es supposé tuer des Haïtiens, pas des Cubains! + +[CUBAN3] +MISSION GANG CUBAIN 3 + +[CUBAN4] +MISSION GANG CUBAIN 40 + +[CUB4_25] +Ok, allons-y! + +[PROT] +MISSIONS DE PROTECTION + +[PRO1_H] +Arrêtez de vous exciter, tous les deux. Je commence à peine à voir comment les choses marchent ici. + +[PRO1_02] +~g~Sors du centre commercial. + +[PRO3_06] +~g~Sème les flics. + +[PORN] +MISSIONS PORNO + +[PORN1] +MISSION PORNO 1 + +[POR1_03] +~r~Candy est morte! + +[PORN2] +MISSION PORNO 2 + +[PORN3] +MISSION PORNO 3 + +[POR3_18] +T'as été repéré! + +[PORN4] +MISSION PORNO 4 + +[POR4_04] +~g~Les bureaux se trouvent de l'autre côté de cette porte. + +[PHIL] +MISSIONS PHIL + +[PHIL1] +MISSION PHIL 1 + +[PHI1_06] +Qu'est-ce que tu fous à conduire comme ça? + +[PHI1_07] +Hé! + +[PHIL2] +MISSION PHIL 2 + +[PIZ1_A] +MISSION LIVREUR DE PIZZA + +[PIZ1_03] +~g~Retourne à la pizzeria pour d'autres commandes. + +[PIZ1_04] +~g~Voilà tes nouvelles commandes. + +[PIZ1_10] +Appuie sur la ~h~ touche R3 ~w~ pour annuler les missions pizzas. + +[CNTBUY1] +Imprimerie achetée : $~1~ + +[CARBUY] +Concession achetée : $~1~ + +[PORNBUY] +Studio de cinéma acheté: $~1~ + +[ICEBUY] +Usine de crème glacée achetée: $~1~ + +[TAXIBUY] +Compagnie de taxis achetée: $~1~ + +[BANKBUY] +Club Malibu acheté : $~1~ + +[BOATBUY] +Chantier naval acheté: $~1~ + +[PRNT_NO] +Tu ne peux pas acheter l'imprimerie pour l'instant, reviens plus tard. + +[CAR_NO] +Tu ne peux pas acheter la concession automobile pour l'instant, reviens plus tard. + +[PORN_NO] +Tu ne peux pas acheter le studio de cinéma pour l'instant, reviens plus tard. + +[ICE_NO] +Tu ne peux pas acheter l'usine de crème glacée pour l'instant, reviens plus tard. + +[TAXI_NO] +Tu ne peux pas acheter la compagnie de taxis pour l'instant, reviens plus tard. + +[BANK_NO] +Tu ne peux pas acheter le club Malibu pour l'instant, reviens plus tard. + +[BOAT_NO] +Tu ne peux pas acheter le chantier naval pour l'instant, reviens plus tard. + +[PRNT_R3] +Appuie sur la touche R3 pour acheter l'imprimerie au prix de $~1~ + +[CAR_R3] +Appuie sur la touche R3 pour acheter la concession automobile au prix de $~1~ + +[PORN_R3] +Appuie sur la touche R3 pour acheter le studio de cinéma au prix de $~1~ + +[ICE_R3] +Appuie sur la touche R3 pour acheter l'usine de crème glacée au prix de $~1~ + +[TAXI_R3] +Appuie sur la touche R3 pour acheter la compagnie de taxis au prix de $~1~ + +[BANK_R3] +Appuie sur la touche R3 pour acheter le club Malibu au prix de $~1~ + +[BOAT_R3] +Appuie sur la touche R3 pour acheter le chantier naval au prix de $~1~ + +[COL2_6] +Arrête espèce de porc impérialiste d'Américain! + +[COL2_6B] +Ceci est la propriété du gouvernement français! + +[COL2_6C] +Et c'est terminé. + +[COL3_A] +Thomas, merci d'être venu. + +[COL3_B] +Désolé d'aller droit au but... + +[COL3_C] +Diaz m'a demandé de superviser une petite transaction financière. + +[COL3_D] +Espérons que ça se passera mieux que la dernière fois. + +[COL3_E] +C'est la raison pour laquelle j'ai pensé à vous, mon ami. + +[COL3_F] +J'ai laissé un revolver au parking à niveaux. + +[COL3_G] +Récupèrez-le et allez surveiller les mecs de Diaz au dépôt. + +[COL4_2] +Je sais pas, chef! + +[COL4_5] +Chef, oui, chef! + +[COL4_10] +Allons manger quelques beignets. + +[COL4_16] +Chef, on déplace le véhicule, chef! + +[COL4_25] +Autodestruction du véhicule initialisée! + +[COL5_6] +Mercedes, cette fille aura ma peau. + +[COL5_8] +Maudit cafard! + +[COL5_5] +Crevez, porcs de Français! + +[CNT2_1] +Tuez-le! + +[CNT2_2] +Récupérez les plaques! + +[CNT2_3] +Protégez le coursier! + +[FINKILL] +Ok, les mecs, butez-le! + +[FIN_6] +Sonny est en haut avec le coffre et MON argent... + +[FIN_10] +Sonny? SONNY! Je viens te chercher! + +[RACES_4] +3 + +[RACES_5] +2 + +[RACES_6] +1 + +[RACES_7] +PARTEZ! + +[RACES_9] +Temps : ~1~:~1~ + +[RACES] +TEMPS : + +[RACES17] +Nv temps record : ~1~:~1~ + +[RACES20] +Nv temps record : ~1~:0~1~ + +[RACES21] +Temps : ~1~:0~1~ + +[RCH1_1] +~g~Utilise l'hélicoptère radiocommandé, le RC Raider, pour PASSER les points de passage. + +[RCH1_2] +~g~Les POINTS DE PASSAGE sont disséminés dans l'aéroport. + +[RCH1_3] +~g~Tu as ~c~8 minutes~g~ pour passer les ~c~20! + +[RCH1_5] +Temps + +[RCRC1_1] +~g~Fais une COURSE DE POINTS DE PASSAGE contre deux autres RC Bandits sur 2 TOURS + +[RCRC1_2] +~g~Va sur la grille de départ! + +[RCRC1_3] +~g~Dernier tour! + +[RCRC1_4] +~g~3 + +[RCRC1_5] +~g~2 + +[RCRC1_6] +~g~1 + +[RCRC1_7] +~g~PARTEZ! + +[RCRC1_8] +~g~Temps de course : ~1~ secondes + +[RCPL1_1] +~g~Fais une COURSE DE POINTS DE PASSAGE contre 3 autres RC Baron + +[RCPL1_2] +~g~Tu dois passer par le ~o~CENTRE CORONA ~g~pour valider un point de passage. + +[RCPL1_3] +~g~Va sur la grille de départ maintenant! + +[FEA_2SP] +2 haut-parleurs + +[FEA_4SP] +Plus de 2 haut-parleurs + +[FEA_EAR] +Casque + +[FEA_NAH] +PAS DE MATERIEL AUDIO + +[FET_APP] +APPLIQUER + +[FES_SKN] +NOM DU SKIN + +[FES_DAT] +DATE + +[FES_SET] +Utiliser Skin + +[FET_DEF] +Par défaut + +[FESZ_QZ] +Veux-tu vraiment sauvegarder cette partie? + +[FES_SCG] +Sauvegarder la partie en cours? + +[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] +Courir + +[FEC_HND] +Frein à main + +[FEC_LOL] +Regarder à gauche + +[FEC_LOR] +Regarder à droite + +[FEC_NTR] +Cible suivante + +[FEC_PTT] +Cible précédente + +[FEC_LBA] +Regarder derrière + +[FEC_CEN] +Centrer caméra + +[FET_CCN] +Classique + +[FET_SCN] +Standard + +[FET_CFT] +A PIED + +[FET_CCR] +EN VOITURE + +[FET_CAC] +ACTION + +[FEC_IBT] +- + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +NON LIE + +[FEC_TFL] +Regard gauche+tourelle G + +[FEC_TFR] +Regarde droite+tourelle D + +[FEC_MWF] +VOLANT MS HAUT + +[FEC_MWB] +VOLANT MX BAS + +[FEC_ORR] +ou + +[FEC_NUS] +NON UTILISE + +[FEC_LUD] +Regarder en haut + +[FEC_LDU] +Regarder en bas + +[FEC_CMP] +COMBO : REGARDER G+D + +[LAW_1A] +law_1a + +[LAW_1B] +law_1b + +[LAW_2A] +law_2a + +[LAW_2B] +law_2b + +[FEH_STA] +STATS + +[FEH_LOA] +CHARGER + +[FEH_CON] +COMMANDES + +[FEH_AUD] +AUDIO + +[FEH_DIS] +AFFICHAGE + +[FEH_LAN] +LANGUE + +[FEH_SGA] +LANCER NOUVELLE PARTIE + +[FEO_CON] +Configuration manette + +[FEO_AUD] +Configuration audio + +[FEO_DIS] +Configuration affichage + +[FEO_LAN] +Choix langue + +[FEO_PLA] +Configuration joueur + +[FEA_OUT] +Sortie + +[FEA_ST] +Stéréo + +[FEA_DTS] +DTS + +[FEA_RSS] +Station de radio + +[FEA_NON] +RADIO ETEINTE + +[FEA_FM0] +WILDSTYLE + +[FEA_FM1] +FLASH FM + +[FEA_FM2] +KCHAT + +[FEA_FM3] +FEVER 105 + +[FEA_FM4] +VROCK + +[FEA_FM5] +VCPR + +[FEA_FM7] +EMOTION 98.3 + +[FEA_FM8] +ONDE 103 + +[FED_BRI] +Luminosité + +[FED_TRA] +Rémanences : + +[FED_SUB] +Sous-titres + +[FED_WIS] +Ecran large + +[FED_POS] +Position écran + +[FEP_RES] +Reprendre + +[FEP_STG] +Commencer partie + +[FEP_STA] +Stats + +[FEP_BRI] +Briefings + +[FEP_OPT] +Options + +[FEP_QUI] +Quitter partie + +[FES_LOA] +Charger partie + +[FES_DEL] +Effacer partie + +[FEC_CSU] +Configuration manette + +[FEC_RED] +Réassigner commandes + +[FEC_MOU] +Paramètres souris + +[DISTGOL] +Dist. parcourue en voiturette (miles) + +[DISTGOM] +Distance parcourue en voiturette (m) + +[ST_FAVR] +Station de radio préférée + +[ST_WSTR] +Station de radio la moins écoutée + +[ST_FAVV] +Véhicule préféré + +[ST_STAR] +Total d'étoiles de recherche obtenues + +[ST_HEAD] +Nombre de tirs à la tête + +[ST_GANG] +Gang le moins apprécié + +[ST_STGN] +Total d'étoiles de recherche évitées + +[TYREPOP] +Pneus crevés avec des balles + +[TYRESLA] +Pneus lacérés avec une lame + +[ST_BRK] +Nombre de victimes dans le Bloodring + +[ST_LTBR] +Plus longue durée dans le Bloodring (sec) + +[ST_GNG1] +Cubains + +[ST_GNG2] +Haïtiens + +[ST_GNG3] +Faux voyous + +[ST_GNG4] +Gang de Diaz + +[ST_GNG5] +Agents de sécurité + +[ST_GNG6] +Gang de motards + +[ST_GNG7] +Gang de Vercetti + +[ST_GNG8] +Golfeurs + +[FEA_FM6] +ESPANTOSO + +[ST_ASSI] +Contrats exécutés + +[DISTBIK] +Dist. parcourue à moto (miles) + +[DISTBIM] +Dist. parcourue à moto (m) + +[HOTEL] +Hôtel Ocean View + +[ICC1_1] +~g~Utilise ta camionnette de crème glacée pour distribuer de la drogue à Vice City. + +[ICC1_2] +~g~Gare ta camionnette et appuie sur ~h~~k~~VEHICLE_HORN~~w~ pour jouer le jingle et signaler à tes clients que tu es prêt. + +[ICC1_3] +~g~Tu reçois de l'argent pour chaque transaction faite, mais plus tu fais d'affaires, plus tu attires l'attention de la police. + +[KICK1_9] +POINTS DE PASSAGE RESTANTS : + +[FIN_B6] +Tu n'as pas assez d'argent pour commencer cette mission. + +[TEX3_3] +~g~Pour ramasser une bombe, manoeuvre l'hélico au-dessus d'elle. La bombe est alors automatiquement fixée sous la carlingue. + +[TEX3_9] +~g~Ramasse une bombe en manoeuvrant l'hélico à côté d'elle. + +[HELP22] +Va vers la position du bip de la maison verte sur le radar. + +[FES_FMS] +Formatage terminé. Sélectionne OK pour continuer. + +[FES_SSC] +Sauvegarde terminée. Sélectionne OK pour continuer. + +[FES_DSC] +Suppression terminée. Sélectionne OK pour continuer. + +[FESZ_QC] +Ecraser ce fichier de sauvegarde corrompu? + +[FES_CHE] +Attention! Un ou plusieurs codes ont été activés, ce qui peut affecter votre fichier de sauvegarde. Sauvegarde déconseillée. + +[FET_SG] +SAUVEGARDER + +[FEH_BRI] +BRIEFING + +[FEH_MAP] +CARTE + +[FEM_OK] +OK + +[FEC_CRO] +S'accroupir + +[FEC_CR3] +S'accroupir (touche L3) + +[FEC_SMT] +Sous-mission + +[FEC_SM3] +Sous-mission (touche R3) + +[FEC_RSC] +Stations de radio + +[ST_PR01] +Pilote + +[ST_PR02] +Soldat armée de l'air + +[ST_PR03] +Officier pilote + +[ST_PR04] +Caporal + +[ST_PR05] +Lieutenant + +[ST_PR06] +Sergent + +[ST_PR07] +Capitaine + +[ST_PR08] +Biggs + +[ST_PR09] +Wedge + +[ST_PR10] +Baron rouge + +[ST_PR11] +Goose + +[ST_PR12] +Viper + +[ST_PR13] +Jester + +[ST_PR14] +Chappy + +[ST_PR15] +Iceman + +[ST_PR16] +Maverick + +[ST_PR17] +Noops + +[ST_PR18] +Maréchal de l'air + +[ST_PR19] +As + +[FET_LG] +Charger partie + +[CAR_EXP] +Véhicules routiers détruits + +[BOA_EXP] +Bateaux détruits + +[HEL_DST] +Avions et hélicoptères détruits + +[STFT_01] +Meilleur temps sur 'Alliage d'Acier' + +[STFT_02] +Meilleur temps sur 'Le chauffeur' + +[STFT_03] +Meilleur temps sur Dirt Ring + +[STFT_04] +Meilleur temps course avion télécommandé + +[STFT_05] +Meilleur temps course voiture télécommandée + +[STFT_06] +Meilleur temps hélico télécommandé + +[STFT_07] +Meilleur temps sur 'Terminal Velocity' + +[STFT_08] +Meilleur temps sur 'Ocean Drive' + +[STFT_09] +Meilleur temps sur 'Border Run' + +[STFT_10] +Meilleur temps sur 'Capital Cruise' + +[STFT_11] +Meilleur temps sur 'Virée!' + +[STFT_12] +Meilleur temps sur 'Endurance V.C.' + +[STHC_01] +Meilleur score sur Le Flingueur. + +[STHC_02] +Meilleur pourcentage de tir au but sur le Flingueur. + +[STHC_03] +Nombre de deals de drogue + +[HELP24] +Tu peux maintenant bosser pour le colonel. + +[HELP25] +Tu peux maintenant bosser pour Avery Carrington + +[HELP29] +Tu peux te rendre au magasin de fringues quand tu n'es pas en mission. + +[HELP30] +Quand tu achètes de nouvelles fringues, ton indice de recherche retombe à zéro. + +[BJM2_22] +~r~Tu as quitté le stand de tir! + +[BJM2_23] +~g~Si tu quittes le stand de tir pendant la compétition, la mission est un échec. + +[ASM4_24] +Distance : + +[RBM1_6] +~g~Ramène Mercedes et le Love Juice au groupe, au studio d'enregistrement. + +[RBM1_3] +PLUS NECESSAIRE + +[HAM1_5] +PLUS NECESSAIRE + +[RBM1_11] +PLUS NECESSAIRE + +[HELP31] +Pour réaliser un arrosage latéral, regarde d'abord à droite ou à gauche en utilisant ~k~~VEHICLE_LOOKLEFT~ ou ~k~~VEHICLE_LOOKRIGHT~. + +[HELP34] +Tu dois être armé d'une mitraillette pour réaliser un arrosage latéral. + +[STRIP_1] +~r~T'as pas assez de pognon, pauvre fauché! + +[EXIT_1] +Appuie sur ~k~~PED_SPRINT~ pour sortir. + +[ASM1_A] +M. Teal, votre aide dans l'éradication de ces indésirables fut une excellente affaire. J'ai un autre travail plus en finesse pour vous. + +[ASM1_B] +Regardez sous le téléphone. + +[ASM1_C] +J'ai un autre travail plus en finesse pour vous. + +[SCARF] +Appartement 3c + +[LAW4_10] +Les patrons sont tous des salauds! + +[GEN1_04] +~g~Passe la porte pour accéder au toit de Gonzalez. + +[GEN1_17] +~g~Gonzalez se fait la malle! Suis-le et finis-en avec lui! + +[RCH1_9] +~b~TEMPS TOTAL : ~1~:~1~ + +[RCH1_10] +~b~TEMPS TOTAL : ~1~:0~1~ + +[WHEEL01] +DOUBLE BONUS DEUX ROUES : $ ~1~ Distance : ~1~.~1~m Temps : ~1~ secondes + +[WHEEL02] +DOUBLE BONUS DEUX ROUES : $ ~1~ Distance : ~1~ feet Temps : ~1~ secondes + +[WHEEL03] +BONUS DEUX ROUES : $ ~1~ Temps : ~1~ secondes + +[WHEEL04] +BONUS DEUX ROUES : $ ~1~ Distance : ~1~.~1~m + +[WHEEL05] +BONUS DEUX ROUES : $ ~1~ Distance : ~1~ pieds + +[WHEEL06] +BONUS ROUE ARRIERE : $ ~1~ Distance : ~1~.~1~m Temps : ~1~ secondes + +[WHEEL07] +BONUS ROUE ARRIERE : $ ~1~ Distance : ~1~ feet Temps : ~1~ secondes + +[WHEEL08] +BONUS ROUE ARRIERE : $ ~1~ Temps : ~1~ secondes + +[WHEEL09] +BONUS ROUE ARRIERE : $ ~1~ Distance : ~1~.~1~m + +[WHEEL10] +BONUS ROUE ARRIERE : $ ~1~ Distance : ~1~ pieds + +[WHEEL11] +BONUS ROUE AVANT : $ ~1~ Distance : ~1~.~1~m Temps : ~1~ secondes + +[WHEEL12] +BONUS ROUE AVANT : $ ~1~ Distance : ~1~ pieds Temps : ~1~ secondes + +[WHEEL13] +BONUS ROUE AVANT: $ ~1~ Temps : ~1~ secondes + +[WHEEL14] +BONUS ROUE AVANT : $ ~1~ Distance : ~1~.~1~m + +[WHEEL15] +BONUS ROUE AVANT : $ ~1~ Distance : ~1~ pieds + +[ROK3_72] +Love Fist! + +[POR1_19] +Hé! + +[DESPERA] +Desperado + +[MOB_99A] +Rends-toi à la cabine près du centre commercial à Washington. + +[MOB_98A] +Rends-toi à la cabine dans Vice Point. + +[MOB_96A] +Rends-toi à la cabine du terminal de l'aéroport. + +[MOB_95A] +Rends-toi à la cabine dans Little Havana. + +[BNK1_1] +Je peux vous aider, monsieur? + +[BNK1_2] +Il y a un imposteur! + +[BNK1_3] +Il a pêté les plombs! + +[BNK1_4] +Bon sang, t'es qui, toi? + +[BNK1_5] +Où es ton badge? + +[BNK1_6] +Les voilà! Descendez-les! + +[MOB_24A] +Bonjour, c'est M. Vercetti? + +[MOB_24B] +Oui. + +[MOB_24C] +ici Cortez. Vous étiez à ma fête. + +[MOB_24D] +Oui, je me souviens. + +[MOB_24E] +M. Vercetti, c'est un regrettable incident, ce qui s'est produit avec votre affaire. + +[MOB_24F] +Je sais. + +[MOB_24G] +Je veux que vous sachiez que mes hommes et moi, faisons le maximum pour tirer ça au clair. + +[MOB_24H] +Si vous souhaitez me parler en privé, vous me trouverez sur le bateau. Au revoir, senor. + +[BNK2_2] +VISEZ 3-2-1 FEU! + +[BNK2_3] +ZONE DEGAGEE! + +[BNK2_6] +Ce mec est cinglé! + +[ANGEL] +Angel + +[CUBJET] +Jetmax cubain + +[SANDKIN] +Sandking + +[POLMAV] +Maverick Police + +[BOXVILL] +Boxville + +[BENSON] +Benson + +[HOTRINA] +Hotring Racer + +[HOTRINB] +Hotring Racer + +[BLOODRA] +Bloodring Banger + +[BLOODRB] +Bloodring Banger + +[MAFIACR] +Mafia Cruiser + +[COP_M2] +'VICE SQUAD' + +[COP_M3] +'BROWN THUNDER' + +[BJM2_20] +~g~Quand tu n'as plus de ~w~temps ~g~ou de ~w~munitions ~g~, la manche est terminée! + +[BNK3_2] +Pas question que je conduise pour toi! J'en parlerai pendant la réunion de groupe! + +[FEM_SL1] +Pas de sauvegarde 1 + +[FEM_SL2] +Pas de sauvegarde 2 + +[FEM_SL3] +Pas de sauvegarde 3 + +[FEM_SL4] +Pas de sauvegarde 4 + +[FEM_SL5] +Pas de sauvegarde 5 + +[FEM_SL6] +Pas de sauvegarde 6 + +[FEM_SL7] +Pas de sauvegarde 7 + +[FEM_SL8] +Pas de sauvegarde 8 + +[FEA_CHA] +Passage en mode STEREO. Patiente un instant... + +[FEA_CHD] +Attention! Tu es en train de passer du mode STEREO au mode DTS. Patiente un instant... + +[FEI_SEL] +Sélectionner + +[FEI_BAC] +Retour + +[FEI_RES] +Reprendre + +[FEI_NAV] +Explorer + +[FEI_BTX] +Touche / - + +[FEI_BTT] +Touche " - + +[FEI_STA] +Touche START - + +[FEI_BTD] +; = > < - + +[FEI_STO] +Arrêter + +[MOB_68A] +Tommy, mon fiston, j'ai une surprise pour toi! + +[MOB_68B] +Je suis au studio d'enregistrement avec quelques artistes réputés. + +[MOB_68C] +Pourquoi tu ne viendrais pas faire un tour? + +[MOB_68D] +C'est une bonne idée, non? Allez, à plus tard. C'est plutôt normal, non? Allez, à plus tard. + +[OUTFT1] +Streetwear + +[OUTFT2] +Soirée + +[OUTFT3] +Bleu de travail + +[OUTFT4] +Country club + +[OUTFT5] +Cubano + +[OUTFT6] +Flic + +[OUTFT7] +Braqueur + +[OUTFT8] +Décontracté + +[OUTFT9] +M. Vercetti + +[OUTFT10] +Survêtement + +[OUTFT13] +MC Tommy + +[HOTR_07] +Nouveau record : ~1~:0~1~ + +[HOTR_08] +Temps : ~1~:~1~ + +[GEN3_45] +Ils seront là d'une minute à l'autre. On ferait mieux de se trouver une bonne planque... + +[CAR_AS1] +CONCESSION AUTOMOBILE ACQUISE + +[CAR_AS2] +~g~Sunshine Autos génère dorénavant un revenu de $~1~ maximum. Pense à récupérer le fric régulièrement. + +[BUYSAVE] +~g~Puisque tu n'es pas en mission, tu peux sauvegarder ta partie gratuitement. + +[BUYGARG] +~g~Tu peux aussi garer des véhicules dans ce garage. + +[STRPBUY] +Club Pole Position acquis : $~1~ + +[STRP_R3] +Pour acheter le club Pole Position pour $~1~, appuie sur la touche R3. + +[NBMN_R3] +Pour acheter Elswanko Casa pour $~1~, appuie sur la touche R3. + +[GA_4] +Les bombes pour voiture coûtent $1000 pièce. + +[GA_5] +Ta caisse est déjà équipée d'une bombe. + +[GA_6] { reVC update } +Gare-toi, amorce-la en appuyant sur la ~h~~k~~VEHICLE_FIREWEAPON~~w~ et BARRE-TOI vite! + +[GA_7] { reVC update } +Amorce la bombe en appuyant sur la ~h~~k~~VEHICLE_FIREWEAPON~~w~. Elle explosera au démarrage. + +[GA_6B] { reVC update } +Gare-toi, amorce-la en appuyant sur la ~h~~k~~VEHICLE_FIREWEAPON~~w~ et BARRE-TOI vite! + +[GA_7B] { reVC update } +Amorce la bombe en appuyant sur la ~h~~k~~VEHICLE_FIREWEAPON~~w~. Elle explosera au démarrage. + +[MOB_70A] +Tommy, c'est moi, le colonel Cortez. Ecoutez senor, je parie qu'avec vous, tout est possible. + +[MOB_70B] +Je serai sur le bateau. + +[PICK1] +Gilet pare-balles livré dans l'hôtel Ocean View! + +[PICK2] +.357 livré à la planque! + +[PICK3] +Tronçonneuse livrée à la planque! + +[PICK4] +Lance-flammes livré à la planque! + +[PICK5] +.308 Lunette livré à la planque! + +[PICK6] +Mitrailleuse livrée à la planque! + +[PICK7] +Lance-roquettes livré à la planque! + +[PICK8] +Sea Sparrow désormais disponible dans la résidence! + +[PICK9] +Char désormais disponible dans la caserne de l'armée! + +[PICK10] +Hunter désormais disponible dans la caserne de l'armée! + +[HELP41] +tu peux aussi les défoncer avec un véhicule + +[ICC1_6] +~g~Utilise le Mr. Whopee pour distribuer des produits Cherry Poppers dans Vice City. + +[ICC1_12] +PROPRIETE ACQUISE! + +[CLOTH1] +Tenue de soirée livrée chez Rafael sur Ocean Beach. + +[CLOTH2] +Tenue streetwear livrée aux planques. + +[CLOTH3] +Bleu de travail livré chez Tooled Up dans le centre commercial de North Point. + +[CLOTH4] +Tenue pour le Country Club livrée au Leaf Links Golf Club. + +[CLOTH5] +Tenue Cubano livrée chez Little Havana Streetwear dans Little Havana. + +[CLOTH6] +Uniforme de flic livré au poste de police de Washington Beach. + +[CLOTH7] +Tenue décontractée livrée chez Gash dans le centre commercial de North Point. + +[CLOTH8] +Tenue de M. Vercetti livrée chez Collar & Cuffs sur Ocean Beach. + +[CLOTH9] +Survêtement livré chez Jocksport dans le centre. + +[CLOTH10] +Tenue de braqueur livrée au Malibu de Vice Point. + +[RBM1_9] +~g~Va chez le dealer et rapporte du Love Juice pour les Love Fist! + +[MOB_62A] +Tommy, c'est Ricardo Diaz, je voulais te remercier de m'avoir sauvé. + +[MOB_62B] +J'ai demandé à ce con de Cortez. Il a dit que tu feras l'affaire, mon ami. Viens me voir à l'occasion. + +[MOB_62C] +C'est d'un mec comme toi dont j'ai besoin. J'ai plus que des têtes de bites, + +[MOB_62D] +des têtes de bite partout! Tu vas gagner plein de pognon. + +[GOAWAY2] +~g~Reviens quand tu auras terminé les missions Bikers. + +[COL2_9] +Imbécile de Ricain! Ils vous ont suivi jusqu'ici! + +[LOADCOL] +Chargement... + +[STFT_17] +Meilleur temps sur Terrain de jeu PCJ + +[STFT_18] +Meilleur temps sur 'Sélection par la boue' + +[STFT_19] +Meilleur temps sur Piste d'essai + +[NEW_REC] +Nouveau record! ~1~ minutes et ~1~ secondes. + +[BMX_HOW] +~g~Fais deux tours de circuit, ~y~en passant par ~g~les ~y~POINTS DE PASSAGE~g~! + +[BMXREW1] +~g~Chaque fois que tu inscris un nouveau record pour les deux tours, + +[BMXREW2] +~g~une meilleure ~y~RECOMPENSE ~g~t'est offerte! + +[BMXRAIN] +~g~On dirait qu'il pleut... + +[ITBEG] +AU DEBUT... + +[NBMNBUY] +El Swanko Casa acheté : $ ~1~ + +[LNKVBUY] +Appartement de Links View acheté : $ ~1~ + +[HYCOBUY] +Hyman Condo acheté : $ ~1~ + +[BUYGARS] +~g~Tu peux également stocker des véhicules dans ces garages. + +[OCHEBUY] +Appartement d'Ocean Heights acheté : $ ~1~ + +[WASHBUY] +1102 Washington Street acheté : $ ~1~ + +[VCPTBUY] +3321 Vice Point acheté : $ ~1~ + +[SKUMBUY] +Skumole Terrace achetée : $~1~ + +[HELP6_C] +Appuie sur la ~h~~k~~VEHICLE_HANDBRAKE~~w~ pour actionner le frein à main. + +[HELP2_A] +Appuie sur la ~h~~k~~PED_SPRINT~~w~ lorsque tu cours pour ~h~sprinter. + +[HELP4_A] +Appuie sur la ~h~~k~~VEHICLE_ACCELERATE~~w~ pour accélérer. + +[HELP5_A] +Appuie sur la ~h~~k~~VEHICLE_BRAKE~~w~ pour freiner ou faire marche arrière si le véhicule est à l'arrêt. + +[HELP8_A] +Appuie sur la ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ pour faire un zoom avant avec le fusil et sur la ~x~touche /~w~ pour faire un zoom arrière. + +[PBOAT_1] { reVC update } +Appuie sur la~h~ ~k~~VEHICLE_FIREWEAPON~~w~ pour tirer avec les canons du bateau. + +[SEG3_4] { reVC update } +~g~Tu peux ramasser des bombes en pilotant ton avion radiocommandé à côté. Appuie sur la ~h~~k~~VEHICLE_FIREWEAPON~ ~g~touche. + +[RCR1_3] { reVC update } +~g~Si tu veux quitter cette mission, appuie sur la ~h~~k~~VEHICLE_FIREWEAPON~~g~ pour faire exploser ta voiture radiocommandée. + +[HELP32] { reVC update } +Appuie ensuite sur la ~h~~k~~VEHICLE_FIREWEAPON~~w~ pour tirer. + +[HELP33] { reVC update } +Appuie ensuite sur la ~h~~k~~VEHICLE_FIREWEAPON~~w~ pour tirer. + +[TTUTOR] +Appuie sur la ~h~~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Taxi. + +[TTUTOR2] +Appuie sur la ~h~~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Taxi. + +[FTUTOR] +Appuie sur la ~h~~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Camion de pompiers. + +[FTUTOR2] +Appuie sur la ~h~~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Camion de pompiers. + +[CTUTOR] +Appuie sur la ~h~~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Auto-défense. + +[CTUTOR2] +Appuie sur la ~h~~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Auto-défense. + +[HELP8_B] +Appuie sur la~h~ ~k~~PED_SNIPER_ZOOM_IN~ ~w~pour faire un ~h~zoom avant~w~ avec le fusil et sur la ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ pour faire un ~h~zoom arrière~w~. + +[ATUTOR3] +Appuie sur la ~h~~k~~TOGGLE_SUBMISSIONS~~w~ pour activer ou désactiver les missions Ambulance. + +[GUN_H1] +~w~Appuie sur la~h~ ~k~~PED_SPRINT~~w~ pour acheter. ~w~Appuie sur la~h~ ~k~~VEHICLE_ENTER_EXIT~~w~ pour quitter. + +[PU_CF3] { reVC update } +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~~w~ pour remplacer l'arme actuelle dans cet emplacement. + +[PU_CF4] { reVC update } +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~~w~ pour remplacer l'arme actuelle dans cet emplacement. + +[HELP9_B] +Appuie sur la~h~ ~k~~PED_FIREWEAPON~ ~w~pour ~h~tirer~w~ avec le fusil à lunette. + +[HELP37] +Si tu ne veux pas monter dans un véhicule quand tu braques le conducteur, appuie sur la ~h~~k~~PED_SPRINT~. + +[HELP6_A] +Appuie sur la ~h~~k~~VEHICLE_HANDBRAKE~ ~w~ pour actionner le frein à main. + +[HELP6_D] +Appuie sur la ~h~~k~~VEHICLE_HANDBRAKE~ ~w~ pour actionner le frein à main. + +[HELP26] +Appuie sur la ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~ pour monter ou sortir d'un véhicule. + +[HELP27] +Appuie sur ~h~~k~~VEHICLE_TURRETUP~~w~ ou ~h~~k~~VEHICLE_TURRETDOWN~~w~ pour déplacer ton poids sur une moto. + +[HELP28] +Appuie sur ~h~~k~~VEHICLE_TURRETUP~~w~ ou ~h~~k~~VEHICLE_TURRETDOWN~~w~ pour déplacer ton poids sur une moto. + +[HELP35] +Appuie sur ~h~~k~~GO_LEFT~~w~ ou ~h~~k~~GO_RIGHT~~w~ pour diriger le véhicule. + +[HELP36] +Appuie sur ~h~~k~~GO_LEFT~~w~ ou ~h~~k~~GO_RIGHT~~w~ pour diriger le véhicule. + +[HELP42] +Suis le ~q~point rose~w~ pour trouver l'hôtel. + +[HELP19] +Marche sur le ~q~marqueur rose~w~ pour continuer. + +[HELP1] +Arrête-toi au centre du ~q~marqueur rose. + +[HELP12] +Marche au centre du ~q~marqueur rose~w~ pour lancer une mission. + +[SEG3_6] +~g~Pour toucher ta cible, tu dois larguer la bombe sur la zone indiquée par le ~q~marqueur rose~w~. Tu peux larguer les bombes dans n'importe quel ordre. + +[S_PROMP] +Lorsque tu n'es pas en mission, tu peux sauvegarder la partie en ramassant la ~h~cassette. + +[HELP16] +Franchis la porte d'entrée de l'hôtel ~h~Ocean View~w~ pour y pénétrer. + +[HELP43] +~g~Rends-toi à l'hôtel ~h~Ocean View~g~ sur Ocean Drive. + +[HELI_F1] +~r~Mission Point de passage hélico annulée! + +[AMMUHLP] +Si tu as besoin d'armes, va chez ~h~Ammu-Nation~w~. Suis le ~h~point pistolet~w~ sur le radar. + +[HELI_1] +Point de passage hélico du Centre + +[HELI_2] +Point de passage hélico d'Ocean Beach + +[HELI_3] +Point de passage hélico de Vice Point + +[HELI_4] +Point de passage hélico de Little Haiti + +[FST_MFR] +Station de radio préférée + +[FST_LFR] +Station de radio la moins écoutée + +[FEI_HOL] +Maintenir + +[FEI_ZOO] +Zoom + +[FEI_BTR] +> < - + +[FEI_NA] +Aucun + +[MESA] +Grande table + +[STRP_NO] +Tu ne peux pas acheter la boîte de striptease maintenant. Reviens plus tard. + +[CHSE] +POURSUITE + +[NBMN_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter El Swanko Casa pour $~1~. + +[NBMN_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter El Swanko Casa pour $~1~. + +[NBMN_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter El Swanko Casa pour $~1~. + +[LNKV_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'appartement de Links View pour $~1~. + +[LNKV_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'appartement de Links View pour $~1~. + +[LNKV_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'appartement de Links View pour $~1~. + +[HYCO_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter Hyman Condo pour $~1~. + +[HYCO_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter Hyman Condo pour $~1~. + +[HYCO_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter Hyman Condo pour $~1~. + +[OCHE_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'appartement d'Ocean Heights pour $~1~. + +[OCHE_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'appartement d'Ocean Heights pour $~1~. + +[OCHE_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'appartement d'Ocean Heights pour $~1~. + +[WASH_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le 1102 Washington Street pour $~1~. + +[WASH_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le 1102 Washington Street pour $~1~. + +[WASH_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le 1102 Washington Street pour $~1~. + +[VCPT_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le 3321 Vice Point pour $~1~. + +[VCPT_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le 3321 Vice Point pour $~1~. + +[VCPT_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le 3321 Vice Point pour $~1~. + +[SKUM_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~~w~ pour acheter la Skumole Terrace pour $~1~. + +[SKUM_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~~w~ pour acheter la Skumole Terrace pour $~1~. + +[SKUM_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~~w~ pour acheter la Skumole Terrace pour $~1~. + +[PRNT_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'imprimerie pour $~1~. + +[PRNT_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'imprimerie pour $~1~. + +[PRNT_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'imprimerie pour $~1~. + +[CAR_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter la concession automobile pour $~1~. + +[CAR_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter la concession automobile pour $~1~. + +[CAR_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter la concession automobile pour $~1~. + +[PORN_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le studio de cinéma pour $~1~. + +[PORN_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le studio de cinéma pour $~1~. + +[PORN_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le studio de cinéma pour $~1~. + +[ICE_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'usine de crème glacée pour $~1~. + +[ICE_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'usine de crème glacée pour $~1~. + +[ICE_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter l'usine de crème glacée pour $~1~. + +[TAXI_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter la compagnie de taxis pour $~1~. + +[TAXI_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter la compagnie de taxis pour $~1~. + +[TAXI_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter la compagnie de taxis pour $~1~. + +[BANK_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le Malibu pour $~1~. + +[BANK_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le Malibu pour $~1~. + +[BANK_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le Malibu pour $~1~. + +[BOAT_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le chantier naval pour $~1~. + +[BOAT_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le chantier naval pour $~1~. + +[BOAT_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le chantier naval pour $~1~. + +[STRP_L] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le Pole Position Club pour $~1~. + +[STRP_T] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le Pole Position Club pour $~1~. + +[STRP_C] +Appuie sur la ~h~~k~~PED_ANSWER_PHONE~ ~w~pour acheter le Pole Position Club pour $~1~. + +[STOCK] +~r~stock écoulé + +[HELP14] +Pour trouver le cabinet d'avocats, suis le ~h~point L~w~ sur le radar. + +[BOAT_AS] +~g~Le chantier naval génère dorénavant un revenu de $~1~ maximum. Pense à récupérer le fric régulièrement. + +[BOAT_A2] +CHANTIER NAVAL OK + +[BOAT_N] +Checkpoint Charlie + +[BOAT_P] +~g~Récupère les paquets avant la fin du temps imparti. + +[FEI_R1B] +Touches R1\R2 - + +[HELP9_A] +Appuie sur la touche ~h~~k~~PED_FIREWEAPON~ ~w~pour tirer avec le fusil à lunette. + +[HELP21] +Appuie sur la touche ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~pour monter dans un véhicule ou en sortir. + +[CREAM] +Distribution + +[UMBERTO] +Café Robina + +[PU_CF1] +Appuie sur la touche ~h~~k~~PED_ANSWER_PHONE~ ~w~pour ramasser cette arme. Elle remplacera toute autre arme du même type que tu possèdes. + +[FED_RDM] +CARTE ET POINTS + +[FEC_ILU] +Vue inversée à la 1ere personne : + +[NITRO] +Tous les taxis disposent d'un saut turbo! Appuie simplement sur la touche klaxonner. + +[RATNG53] +Faux cul + +[RATNG54] +Faiseur d'emmerdes + +[RATNG55] +Malhonnête + +[RATNG56] +Tricheur + +[RATNG57] +Mythomane + +[STHC_04] +Meilleur score au beach ball de Keepie-Uppy + +[STHC_05] +Meilleur résultat au Hotring + +[STFT_13] +Meilleur tps point de passage hélico du centre + +[STFT_14] +Meilleur tps point de passage hélico d'Ocean Beach + +[STFT_15] +Meilleur tps point de passage hélico de Vice Point + +[STFT_16] +Meilleur tps point de passage hélico de Little Haiti + +[STFT_21] +Meilleur temps au Hotring + +[STFT_22] +Meilleur temps au tour au Hotring + +[STFT_20] +Meilleur temps au 'Cone Crazy' + +[HELP44] +Arrête-toi sur le ~q~marqueur rose. + +[HELP45] +Appuie sur la ~h~~k~~PED_DUCK~~w~ pour t'accroupir. Ceci augmente la précision des flingues que tu portes. + +[RCR1_5] +Course de Bandit RC + +[RCPL1_7] +Course de Baron RC + +[RCH1_11] +Course de Raider RC + +[FEA_CTD] +Attention! Un équipement matériel compatible DTS est requis pour cette fonction. Continuer? + +[FEM_STE] +STEREO + +[FEM_UDY] +DTS + +[GREET] +Bien le bonjour de... + +[LANCE_1] +Hé, mec, conduis un peu mieux! + +[LANCE_2] +Hé, fais gaffe à ce que tu fais! + +[LANCE_3] +Hé, tu vas où, là? + +[LANCE_4] +On fait quoi maintenant? + +[LAW4_15] +Plus de fric! + +[MERC_5] +Belle voiture, M. Vercetti. + +[MERC_26] +PLUS VITE, PLUS VITE, PLUS VITE! + +[MERC_27] +Attention, Tommy, je me suis fait refaire le nez le mois dernier. + +[MERC_28] +Tommy, conduis prudemment! + +[MERC_29] +Tommy, va moins vite! + +[MERC_30] +Tommy, tu veux bien tuer quelqu'un d'autre que moi? + +[MERC_31] +Tommy, chéri, ne me tue pas! + +[MERC_32] +Tommy, ça me très plaisir que t'aies volé cette caisse! + +[MERC_40] +J'ai vraiment passé un bon moment. + +[MERC_43] +Adios, mon chou. + +[MERC_44] +Continue la muscu, d'accord? + +[MERC_45] +Ciao, mon beau. + +[COL5_17] +Oh mon dieu, ils ont un hélicoptère! + +[COL5_18] +Abattez l'hélico! + +[COL5_19] +Tommy, débarrasse-nous de cet hélico! + +[COL5_20] +Il revient! Détruisez cet hélico! + +[COL5_21] +Visez la taille de cet hélico! + +[COL5_22] +Le revoilà! + +[FEA_DSM] +Attention! Cette sauvegarde est paramétrée pour un son DTS. Du matériel compatible DTS doit être connecté. Choisir entre une sortie audio STEREO ou DTS. + +[STFT_23] +Meilleur temps à Checkpoint Charlie + +[HELP50] +Appuie sur la touche ~h~~k~~PED_ANSWER_PHONE~~w~ pour placer la caméra derrière toi. + +[HELP51] +Appuie sur la touche ~h~~k~~PED_ANSWER_PHONE~~w~ pour placer la caméra derrière toi. + +[HELP52] +Appuie sur la touche ~h~~k~~PED_ANSWER_PHONE~~w~ pour placer la caméra derrière toi. + +[HELP53] +Appuie sur la touche ~h~~k~~PED_CYCLE_WEAPON_LEFT~~w~ ou sur la touche ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ pour faire défiler tes armes disponibles. + +[HELP46] +Il existe huit différents types d'armes. + +[HELP47] +Tu peux porter une arme de chaque type à la fois, un type de pistolet, un type de fusil à pompe, etc. + +[HELP54] +~w~Prix : $~1~ ~r~L'achat de cette arme remplacera celle que tu possèdes déjà. + +[HELP2A2] +Appuie sur la touche ~h~~k~~PED_SPRINT~~w~ quand tu cours, pour ~h~sprinter. + +[HLPSN_A] +Grâce au fusil à lunette, tu peux zoomer de loin sur une cible et la viser avec précision. + +[HLPSN_B] +Appuie sur la touche~h~ ~k~~PED_LOCK_TARGET~ ~w~et maintiens-la enfoncée pour ~h~viser~w~ avec le fusil à lunette. + +[HLPSN_C] +Appuie sur la touche~h~ ~k~~PED_LOCK_TARGET~ ~w~et maintiens-la enfoncée pour ~h~viser~w~ avec le fusil à lunette. + +[HLPSN_D] +Appuie sur la touche ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ pour ~h~faire un zoom avant ~w~avec le fusil à lunette et sur la touche~h~ ~k~~PED_SNIPER_ZOOM_OUT~ ~w~pour ~h~faire un zoom arrière~w~. + +[HLPSN_E] +Appuie sur la touche ~h~~k~~PED_FIREWEAPON~~w~ ~w~pour ~h~tirer~w~ avec le fusil à lunette. + +[HLPSN_F] +Appuie sur la touche ~h~~k~~PED_FIREWEAPON~~w~ ~w~pour ~h~tirer~w~ avec le fusil à lunette. + +[HLPSN_G] +Appuie sur la touche ~h~~k~~PED_FIREWEAPON~~w~ ~w~pour ~h~tirer~w~ avec le fusil à lunette. + +[PLANE_H] +Utilise la touche ~h~~k~~VEHICLE_ACCELERATE~~w~ pour accélérer. Gauche et droite pour tourner. + +[PLANE_4] { reVC update } +{Utilise la touche ~h~~k~~VEHICLE_ACCELERATE~~w~ pour accélérer. Gauche et droite pour tourner.} +Utilise le joystick analogique droit pour accélérer, appuie vers le bas sur le joystick analogique gauche pour monter et vers le haut pour descendre. Gauche et droite pour tourner. + +[HELP55] +Appuie sur la touche ~h~~k~~PED_FIREWEAPON~~w~ pour attaquer le chef. + +[STPR_8] +Pole Position Club + +[STPR_9] +3321 Vice Point + +[STPR_10] +Appartement de Links View + +[STPR_11] +El Swanko Casa + +[STPR_12] +1102 Washington Street + +[STPR_13] +Appartement d'Ocean Heights + +[STPR_14] +Skumole Shack + +[STPR_15] +Hyman Condo + +[RCCANX] +~r~Avion RC annulé. + +[CLT_HL2] +Lorsque tu ramasses des fringues, une ou deux étoiles d'indice de recherche sont enlevées. + +[CRED009] +CONCEPTION DES MISSIONS + +[CRED359] +LEE JOHNSON + +[CRED360] +HENDRIK LESSER + +[CRED361] +PASQUALE STACCHIOTTI + +[CRED362] +ENRIQUE FERNANDEZ + +[CRED363] +PAUL BYERS + +[CRED364] +MIKE EMENY + +[CRED365] +ROB DUNKIN + +[CRED366] +CHARLIE KINLOCH + +[CRED367] +KEVIN HOBSON + +[CRED368] +JIM CREE + +[MOB_66A] +Tommy, Tommy, Tommy, pourquoi t'es revenu ici? + +[MOB_66B] +On t'a déjà dit qu'on voulait plus te revoir. + +[MOB_67A] +Tommy, je crois que tu devrais rester à l'écart, tu saisis? + +[MOB_67B] +Les petits gars Haïtiens t'aiment pas beaucoup. + +[MOB_18A] +Tommy, c'est Paulo. Tu vas bien? Bon, mec, il faut que je te parle. + +[MOB_18B] +Ah, mon pote, tu croiras jamais l'incroyable petit lot que je viens de lever... + +[MOB_18C] +Elle se promenait juste en bas dans Little Havana, mon pote. + +[MOB_18D] +Elle m'a dit qu'elle s'appelait Mercedes ou un truc comme ça. + +[MOB_18E] +Ah, mon pote, faut que t'ailles voir cette fille! + +[MOB_18F] +Elle ferait bander un eunuque! Elle m'a dit que j'étais le meilleur coup de sa vie et tout et tout! + +[MOB_18G] +Vas-y, trouve-la. A plus tard! + +[MOB_72A] +Tommy, c'est moi, Lance. Ferme-la, Tommy, parce que j'ai pas le temps de discuter! + +[MOB_72B] +J'me tape de ce que t'as à me dire. Pourquoi ça m'intéresserait? T'en as rien à foutre de moi, non? + +[MOB_72C] +Il faut que tu sois plus sympa avec moi. Donne-moi une belle part. Tu vois... + +[MOB_72D] +Tommy... Ecoute, mec, je suis désolé. C'est que... + +[MOB_72E] +Les gens arrêtent pas de me materner, de me traiter comme un gamin. + +[MOB_72F] +Mon frère pourrait me faire ça, mais pas toi. S'il te plaît. + +[MOB_72G] +Faut que j'y aille. + +[MOB_63A] +Tommy, c'est Earnest. Earnest Kelly. + +[MOB_63B] +Ca va? + +[MOB_63C] +Bien. Il va me falloir une canne pour marcher, mais je devrais revenir rapidement dans les affaires. + +[MOB_63D] +Bien. + +[MOB_63E] +J'ai appris au sujet de Lance. Quel enculé, hein? + +[MOB_63F] +Ouais. + +[MOB_63G] +Ne jamais faire confiance à un mec qui se trimballe en pyjama. C'est ce que je dis toujours. J'espère bien qu'il en a bavé, ce con. + +[MOB_63H] +Je crois que oui. Je pensais pas qu'il était comme ça... + +[MOB_63I] +Tommy, t'as beau être cinglé, t'es vraiment naïf. Il va falloir que je t'apprenne deux ou trois trucs sur la vie, dès que je serai remis sur pied. + +[MOB_63J] +Prends ton temps, Earnest et surtout, prends soin de toi. + +[MOB_16A] +Tommy, c'est Paulo. Que pasa amigo? + +[MOB_16B] +Qu'est-ce tu m'veux, Paul? J'veux pas de fringues de contrefaçon. + +[MOB_16C] +Très drôle, mon pote, mais tu sais que je fais pas dans la merde. Non, j'appelle juste pour savoir si t'aurais pas un rôle pour moi dans un de tes films. + +[MOB_16D] +J'ai fait pas mal de X en Angleterre, mec. J'en ai plus que toi dans l'pantalon. + +[MOB_16E] +Paul, merci pour la proposition, je vais y réfléchir. + +[MOB_16F] +Sérieux, pense à moi, après tout ce que j'ai fait pour toi. + +[MOB_16G] +C'est ce que j'essaie d'oublier, justement... + +[MOB_17A] +Tommy Vercetti, comment vas-tu? J'ai entendu tous ces trucs sur toi. Un flambeur en ville maintenant... + +[MOB_17B] +Paul, t'es bourré? + +[MOB_17C] +Non, pauv' con, je suis pas bourré! J'ai juste pris deux ou trois verres et quelques tournées, et ça fait deux jours que j'ai pas fermé l'oeil! + +[MOB_17D] +De toute façon, me traite pas comme ça. Je suis pas une poire. Qui c'est qui t'a lancé dans cette ville? Hein, qui? Moi! + +[MOB_17F] +Ah ouais? + +[MOB_17G] +Me traite pas comme ça? Qui c'est qui t'a présenté à des gens? Je t'ai montré tous les trucs, je me suis fait chier pour toi et voilà comment tu m'remercies. + +[MOB_17H] +Tu m'ignores. Tu me laisses de côté après tout ce que j'ai fait pour toi! Qui tu crois que je suis? Une merde ou je sais pas quoi? + +[MOB_17I] +Paul, calme-toi. J'ai été occupé, ne sois pas stupide. + +[MOB_17J] +Je suis pas stupide, connard! Ils me l'ont dit en maison de redressement! Si tu cherches les emmerdes, mon pote, tu vas les trouver! + +[MOB_17K] +Tommy, s'il te plaît! T'étais mon grand espoir! Te fous pas de moi! + +[MOB_17L] +Paul, va roupiller un bon coup, sérieusement. + +[MOB_73A] +Tommy, c'est Steve. + +[MOB_73B] +Salut, Steve. + +[MOB_73C] +Salut le génie. T'es merveilleux! Je suis une merveille! Ils nous adorent. On réécrit le livre des records, mon pote! + +[MOB_73D] +Je parle de putain de récompenses. Enfin je peux envoyer mon père à l'hospice et lui dire de fermer sa gueule. + +[MOB_73E] +Euh... C'est cool, Steve. + +[MOB_73F] +Cool? C'est génial, mec. GENIAL quoi! Il n'a jamais voulu croire en moi, mais regarde ce qu'on a réussi à faire. + +[MOB_73G] +Je suis le plus grand réalisateur de films de cul faits maison de la planète, mon pote. Je voulais juste te dire que je suis heureux de t'avoir rencontré. + +[MOB_73H] +Merci, Steve. + +[MOB_73I] +Je t'aime, bébé. Et reste comme tu es, d'accord? + +[MOB_73J] +D'accord. Salut, Steve. + +[BOLLOX] +Appuie sur la touche ~o~R1 ~w~pour larguer une bombe. Appuie sur la touche ~t~" ~w~pour annuler. + +[BRID_OP] +Avis de tempête terminé : tous les ponts sont maintenant accessibles. + +[BRID_CL] +Avis de tempête : tous les ponts sont fermés. + +[LG_38] +Cible + +[ASSET_C] +Pole Position, OK + +[ASSET_D] +~g~Le club Pole Position va maintenant créer un revenu d'un maximum de $~1~ par jour. Venez retirer votre argent régulièrement. + +[ST_WHEE] +Temps maximum sur roue arrière (secs) + +[ST_STOP] +Temps maximum sur roue avant (secs) + +[ST_2WHE] +Temps maximum sur deux roues (secs) + +[ST_WHED] +Distance maximum sur roue arrière (m) + +[ST_STOD] +Distance maximum sur roue avant (m) + +[ST_2WHD] +Distance maximum sur deux roues (m) + +[OUTFT11] +Survêtement + +[OUTFT12] +Frankie + +[RELOAD] +~g~Tu as gagné la possibilité de recharger ton arme rapidement + +[APACHE] +Hunter livré à l'héliport d'Ocean Beach + +[CRED369] +JOHN MCCARDLE + +[CRED370] +DAVID MURDOCH + +[CRED371] +CHRIS BROWN + +[CRED372] +PAUL GREEN + +[CRED373] +KYLE MILNE + +[CUNTY] +Nouveaux vêtements livrés au Domaine Vercetti + +[GOODBOY] +$50 pour bonne conduite + +[NEWCONT] +Nouveau ~h~point de contact ~w~créé à la marina d'Ocean Beach + +[FIRELVL] +Mission Camion de pompiers niveau ~1~ + +[HELP56] +Appuie sur la touche ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ pour changer le mode de la caméra. + +[HELP57] +Appuie sur la touche ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ pour changer le mode de la caméra. + +[HELP58] +Tout en visant, appuie sur la touche ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ ou ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~ pour faire défiler les cibles. + +[HELP59] +Tout en visant, appuie sur la touche ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ ou ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~ pour faire défiler les cibles. + +[HELP60] +Si tu appuies sur la touche ~h~~k~~PED_SPRINT~ ~w~quand tu essaies de piquer une caisse, tu ne peux pas monter dedans. + +[HELP61] +Tu as désormais des munitions illimitées et tous tes véhicules sont deux fois plus résistants. + +[CRED374] +KEVIN YUN + +[CRED375] +ERICK COBBS + +[CRED376] +RANDY BLAKE + +[CRED377] +BRANDON LIM + +[CRED378] +BRANDON FENOL + +[CRED379] +MICHAEL MANOLE + +[CRED380] +ALETHEIA SIMONSON + +[CRED381] +JOHN JANSEN + +[FEC_LB1] +Regarder + +[FEC_LB2] +derrière + +[FEC_LB3] +Regarder derrière + +[FEC_R3] +(touche R3) + +[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 + +[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 + +[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~ + +[FEC_WAR] +Avertissement + +[FEC_OKK] +O.K. + +[FEC_DLF] +Erreur lors de suppression + +[FEC_SVU] +Erreur lors de la sauvegarde + +[FEC_LUN] +Erreur lors du chargement. Fichier corrompu, veuillez le supprimer. + +[FEC_PAD] +Manette + +[FEC_JOY] +Joystick + +[FES_CSA] +Sélectionnez une apparence dans la liste suivante : + +[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_DSN] +Skin joueur par defaut.bmp + +[FET_RSO] +PARAMETRE D'ORIGINE RETABLI + +[FET_RSC] +MATERIEL INDISPONIBLE - PARAMETRE D'ORIGINE RETABLI + +[FEA_3DH] +CONFIG. CARTE-SON + +[FEA_SPK] +CONFIG. HAUT-PARLEURS + +[FEM_LOD] +DISTANCE MODELES + +[FEM_VSC] +SYNCHRO VIDEO + +[FEM_FRM] +RESTRICTION VIDEO + +[FEM_MM] +MENU PRINCIPAL + +[FED_RES] +RESOLUTION ECRAN + +[FET_CTL] +CONFIG. PERIPHERIQUE + +[FET_OPT] +OPTIONS + +[FEC_MSH] +SENSIBILITE SOURIS + +[FEC_IVV] +INVERSER SOURIS VERTIC. + +[FET_MTI] +CONFIG. SOURIS + +[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 + +[FEC_SPC] +ESP + +[WIN_TTL] +Grand Theft Auto VC + +[WIN_95] +Grand Theft Auto VC n'est pas compatible WINDOWS 95 + +[WIN_DX] +Grand Theft Auto VC requiert la version 8.1 de DirectX minimum. + +[FET_EIG] +IMPOSSIBLE DE PARAMETRER UNE TOUCHE POUR CETTE ACTION + +[FET_DAM] +MODELAGE ACCOUST. DYNAMIQUE + +[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 ? + +[FET_QG] +QUITTER PARTIE + +[FEN_STA] +COMMENCER PARTIE + +[FET_PAU] +MENU PAUSE + +[REPLAY] +RALENTI + +[FET_PS] +CONFIG. JOUEURS + +[FEC_ANS] +Action + +[CVT_MSG] +Conversion des textures vers un format optimal pour votre carte graphique + +[FEC_SFT] +MAJ + +[FEH_VMP] +VOIR CARTE + +[FES_DEE] +Echec de la suppression ! Recommencer. + +[FES_CMP] +Echec de la sauvegarde ! Recommencer. + +[FESZ_WR] +Sauvegarde en cours. Un instant... + +[FELD_WR] +Chargement en cours. Un instant... + +[FEDL_WR] +Suppression en cours. Un instant... + +[PCRESRT] +Lancement d'une nouvelle partie. Un instant... + +[FET_STI] +Commandes standard + +[FET_CTI] +Commandes classiques + +[FEH_NA] +OPTION NON DISPONIBLE + +[FEH_MPH] +SOURIS, CURSEURS POUR SE DEPLACER - PAGE HAUT, PAGE BAS, MOLETTE POUR ZOOMER, L - LEGENDE + +[FEA_MP3] +LECTEUR MP3 + +[NO_PCCD] +Insérer le disque de GTA Vice City ou appuyer sur ECHAP pour annuler + +[FEH_SSA] +CURSEURS POUR SE DEPLACER - S POUR SAUVEGARDER LE FICHIER + +[FES_CMI] +DERNIERE MISSION REUSSIE + +[FET_STS] +STATS SAUVEGARDEES DANS 'STATS.HTML' + 'STATS.TXT' + +[WIN_VDM] +Grand Theft Auto VC ne dispose pas de suffisamment de mémoire graphique. + +[FEC_ERI] +Erreur ! Une ou plusieurs actions ne sont pas assignées à une touche ou à un bouton. Vérifier que toutes les actions sont bien assignées. + +[FEC_TFU] +Tourelle + Orienter haut + +[FEC_TFD] +Tourelle + Orienter bas + +[FET_RIG] +CHOISIR UNE AUTRE COMMANDE POUR CETTE ACTION + +[FEA_NM3] +AUCUN FICHIER MP3 TROUVE + +[FEA_MPB] +VOLUME MP3 A FOND + +[FEA_MUS] +VOLUME MUSIQUE + +[FEA_SFX] +VOLUME EFFETS SONORES + +[CVT_ERR] +Espace disque épuisé. Libérez de la mémoire sur votre disque dur pour continuer. Appuyez sur ECHAP pour annuler. + +[FEA_ADP] +AUTODETECT MATERIEL + +{=================================== MISSION TABLE AMBULAE ===================================} + +[ATUTOR2:AMBULAE] +~g~Conduis les patients à l'hôpital. DOUCEMENT. Chaque secousse réduit leurs chances de survie. + +[A_FULL:AMBULAE] +~r~Ambulance pleine! + +[A_FAIL2:AMBULAE] +~r~Ton manque de rapidité a été fatal pour le patient! + +[A_FAIL3:AMBULAE] +~r~Le patient est mort!! + +[A_PASS:AMBULAE] +Sauvé! + +[A_COMP2:AMBULAE] +Tu ne seras plus jamais essoufflé! + +[A_COMP1:AMBULAE] +Missions Ambulance réussies : $~1~ + +[A_CANC:AMBULAE] +~r~Mission ambulance annulée! + +[A_COMP3:AMBULAE] +Missions ambulance accomplies! Maintenant, tu peux sprinter indéfiniment! + +[ALEVEL:AMBULAE] +Mission ambulance, niveau ~1~ + +[A_FAIL1:AMBULAE] +Mission ambulance achevée. + +[A_SAVES:AMBULAE] +PERSONNES SAUVEES : ~1~ + +{=================================== MISSION TABLE ASSIN1 ===================================} + +[ASM1_5:ASSIN1] +~r~Il a livré toutes ses pizzas! + +[ASM1_6:ASSIN1] +Livraisons restantes : + +[ASM1_7:ASSIN1] +~g~Carl Pearson, livreur de pizzas. Tue-le avant qu'il ne termine ses livraisons. + +[ASM1_D:ASSIN1] +Ton aide dans l'éradication de ces indésirables fut une excellente affaire. + +{=================================== MISSION TABLE ASSIN2 ===================================} + +[ASM2_1:ASSIN2] +~g~Mme Dawson va bientôt quitter la bijouterie à Vice Point. Tue-la. Il faut que ça ressemble à un accident de voiture. + +[ASM2_3:ASSIN2] +~g~Ca va sauter, écarte toi! + +[ASM2_4:ASSIN2] +~r~T'as bousillé sa bagnole alors qu'elle était même pas dedans. Elle est pas près de l'utiliser, maintenant! + +[ASM2_5:ASSIN2] +~r~Elle s'est enfuie! + +[ASM2_6:ASSIN2] +~r~Tu étais trop près de l'accident! + +[ASM2_7:ASSIN2] +~g~N'utilise pas d'armes! Il faut que ça ressemble à un accident! Sors-la plutôt de la route! + +[ASM2_8:ASSIN2] +~g~La mort de madame Dawson doit avoir l'air d'un accident. N'utilise pas d'armes. + +[ASM2_9:ASSIN2] +~g~Il te faut une voiture pour ce boulot! + +[ASM2_10:ASSIN2] +~g~Quand sa bagnole prendra feu, éloigne-toi le plus possible du lieu de l'accident. + +[ASM2_11:ASSIN2] +A l'aide! + +[ASM2_12:ASSIN2] +Que quelqu'un m'aide! + +[ASM2_13:ASSIN2] +Oh non! + +[ASM2_A:ASSIN2] +Mes félicitations pour ce travail bien fait, Mr. Teal. Mon client était ravi. + +[ASM2_2:ASSIN2] +Santé : + +{=================================== MISSION TABLE ASSIN3 ===================================} + +[ASM3_11:ASSIN3] +TEMPS : + +[ASM3_C:ASSIN3] +Un gang européen prépare un braquage de banque à Vice City. Mes employeurs préfèreraient que cela n'arrive pas. + +[ASM3_D:ASSIN3] +Chaque membre du gang dispose d'une couverture à Vice City. Certains ont des petits boulots, d'autres jouent les touristes. + +[ASM3_E:ASSIN3] +Chaque cible et leur position probable sont indiqués sous le téléphone. + +[ASM3_14:ASSIN3] +~g~Dick Tanner est à côté de DBP Security sur Ocean Drive. + +[ASM3_15:ASSIN3] +~g~Marcus Hammond et Franco Carter sont près de la bijouterie de Vice Point. + +[ASM3_16:ASSIN3] +~g~Nick Kong est à côté de Washington Beach. + +[ASM3_18:ASSIN3] +~g~Ne t'approche pas trop près de la cible où elle risque de te repérer et d'essayer de se faire la malle! + +[ASM3_19:ASSIN3] +~g~Il t'a repéré! Bute-le! + +[ASM3_20:ASSIN3] +~g~Ils t'ont repéré! Tue-les vite tous les deux! + +[ASM3_21:ASSIN3] +~r~Tu n'as pas tué tous les membres du gang à temps! + +[ASM3_22:ASSIN3] +~g~Ne t'approche pas trop des cibles ou elles risquent de te repérer et d'essayer de filer. + +[ASM3_12:ASSIN3] +~g~Des armes ont été laissées à ta disposition dans le coin si tu en as besoin. Tu as ~h~9 MINUTES ~g~pour tuer tous les membres du gang. + +[ASM3_13:ASSIN3] +~g~Mike Griffin travaille sur un panneau publicitaire à Washington. + +[ASM3_17:ASSIN3] +~g~Charlie Dilson est à moto sur Washington. + +{=================================== MISSION TABLE ASSIN4 ===================================} + +[ASM4_12:ASSIN4] +Distance : + +[ASM4_15:ASSIN4] +~g~Prends le fusil à lunette sur ta droite. + +[ASM4_16:ASSIN4] +~g~Observe la femme au balcon. Elle va descendre l'escalator et demander l'heure à quelqu'un. + +[ASM4_17:ASSIN4] +~g~Lorsque la conversation est terminée, liquide son interlocuteur sans faire de mal à la femme. + +[ASM4_18:ASSIN4] +~g~Une fois la cible éliminée, récupère la serviette et amène-la à Ammu-Nation dans le centre. + +[ASM4_19:ASSIN4] +~g~Reste à distance de la cible. Sa proximité t'est indiquée par la barre de distance située dans le coin supérieur droit de l'écran. + +[ASM4_20:ASSIN4] +~g~Ne la laisse pas devenir pleine ou tu seras repéré. + +[ASM4_21:ASSIN4] +~g~Récupère la serviette! + +[ASM4_22:ASSIN4] +~g~Ramène la serviette à Ammu-Nation dans le centre. + +[ASM4_23:ASSIN4] +~g~Il t'a repéré et s'enfuit. Rattrape-le et récupère la serviette! + +[ASM4_25:ASSIN4] +~r~T'as buté la femme, imbécile! + +[ASM4_26:ASSIN4] +~r~La cible a embarqué pour son vol! + +[ASM4_27:ASSIN4] +~r~La cible t'a repéré! Tu devais garder tes distances! + +[ASM4_28:ASSIN4] +~r~La cible t'a repéré! Elle t'a entendu tirer! + +[ASM4_29:ASSIN4] +~r~Ne le tue qu'après qu'il ait parlé à la femme! + +[ASM4_A:ASSIN4] +L'heure est venue de s'occuper d'un gros poisson, M. Teal. Il y a un fusil dans le feuillage à votre droite. + +[ASM4_B:ASSIN4] +Surveillez la femme sur le balcon situé au-dessus des guichets d'enregistrement. Elle va avancer à travers la foule et demander l'heure à quelqu'un. + +[ASM4_C:ASSIN4] +Vous devez tuer son interlocuteur, récupérer la mallette et l'apporter à l'emplacement indiqué sous le téléphone. + +{=================================== MISSION TABLE ASSIN5 ===================================} + +[ASM5_A:ASSIN5] +Un deal important se déroule sur le toit de l'usine de crème glacée Cherry Popper. + +[ASM5_B:ASSIN5] +Descendez toutes les personnes impliquées, embarquez la marchandise et amènez-la à la piste pour hélicos de l'aéroport. + +[ASM5_C:ASSIN5] +Il y a une porte sur votre gauche qui mène à l'arrière de l'usine. + +[ASM5_1:ASSIN5] +~g~Entre dans l'enceinte située derrière l'usine de crème glacée Cherry Popper et va jusqu'au toit où se fait le deal. + +[ASM5_2:ASSIN5] +~g~Récupère la marchandise et amène-la à la piste pour hélicos de l'aéroport. + +[ASM5_3:ASSIN5] +~g~Emmène la marchandise à la piste pour hélicos de l'aéroport! + +{=================================== MISSION TABLE BANKJ1 ===================================} + +[WANTED1:BANKJ1] +~g~Largue les flics et perds ton indice de recherche! + +[BJM1_A:BANKJ1] +Tommy! Hé, regarde-ça, c'est génial! J'ai fait installer un minibar! + +[BJM1_B:BANKJ1] +Y'a déjà un vrai bar en bas, Ken. + +[BJM1_C:BANKJ1] +Ouais, je sais, et alors? Bon, j'ai le tableau noir que tu m'as demandé. + +[BJM1_D:BANKJ1] +Ah, c'est là qu'on voit l'intérêt de ton école de droit : t'as appris à suivre les instructions. + +[BJM1_E:BANKJ1] +Bon, il me faut un mec sûr. + +[BJM1_F:BANKJ1] +Euh, d'accord, laisse-moi réfléchir... Sûr... sûr... sûr... Je sais! Je connais un mec qui te plaira! + +[BJM1_G:BANKJ1] +Aaah, non, merde, ce con est au trou. + +[BJM1_H:BANKJ1] +Comment ça au trou? + +[BJM1_I:BANKJ1] +Dans un poste de police. Il attend son transfert. + +[BJM1_J:BANKJ1] +Je crois qu'il va être remis en liberté sur parole... + +[BJM1_1:BANKJ1] +~g~Sors Cam Jones de chez les flics! + +[BJM1_3:BANKJ1] +~g~Tu trouveras quelque chose d'utile dans la salle des casiers. + +[BJM1_21:BANKJ1] +~g~La carte d'accès aux cellules se trouve à l'étage. + +[BNK1_7:BANKJ1] +Cam Jones? + +[BNK1_8:BANKJ1] +Je viens t'aider à mettre les bouts! + +[BNK1_10:BANKJ1] +Ouais, c'est moi... + +[BNK1_11:BANKJ1] +Ca me va! + +[BNK1_13:BANKJ1] +J'ai un boulot à faire et t'es mon perceur de coffre. + +[BNK1_14:BANKJ1] +Marre de perdre mon temps dans une cellule. + +[BJM1_22:BANKJ1] +~g~Ramène Cam chez lui! + +[BJM1_23:BANKJ1] +~g~Tu dois d'abord trouver la carte d'accès! + +[BNK1_12:BANKJ1] +Sème les flics et ramène-moi chez moi! + +[BJM1_20:BANKJ1] +Jette ton flingue ou tu vas en subir les conséquences! + +[BJM1_5:BANKJ1] +Accès réservé au personnel autorisé! + +[BJM1_2:BANKJ1] +~r~T'étais supposé faire sortir Cam, pas le faire tuer! + +[BJM1_4:BANKJ1] +Il est armé! Descendez-le! + +{=================================== MISSION TABLE BANKJ2 ===================================} + +[BJM2_A:BANKJ2] +Il nous faut un braqueur. T'en connais un? + +[BJM2_B:BANKJ2] +Hé, Tommy, Tommy, ce truc ça permet d'rester dans l'coup, mec! + +[BJM2_C:BANKJ2] +WoooOOOooo! + +[BJM2_D:BANKJ2] +J'pourrais être ton braqueur! Haut les mains! Haut les mains! + +[BJM2_E:BANKJ2] +T'es pas un braqueur, t'es un idiot. + +[BJM2_F:BANKJ2] +Va boire un verre et ferme-la. + +[BJM2_G:BANKJ2] +Hé, tire-toi de mon chemin! Ye ye ye ow ow! + +[BJM2_H:BANKJ2] +Cam, qu'est-ce que t'en penses? + +[BJM2_I:BANKJ2] +Ben, le meilleur tireur de cette ville, c'est un mec qui s'appelle Cassidy. + +[BJM2_J:BANKJ2] +Ah ouais? + +[BJM2_K:BANKJ2] +Ouais. Un militaire ou en tout cas c'est ce qu'il croit. + +[BJM2_L:BANKJ2] +Je doute qu'il ait jamais été dans l'armée, mais il sait sûrement où dégotter des flingues. + +[BJM2_M:BANKJ2] +Il devrait être au champ de tir. + +[BJM2_2A:BANKJ2] +C'est toi, Phil Cassidy? + +[BJM2_2B:BANKJ2] +Pourquoi? + +[BJM2_2C:BANKJ2] +Je cherche un mec qui sache se servir d'un flingue. D'après ce que je vois, j'suis pas convaincu... + +[BJM2_2D:BANKJ2] +Mon pote, je peux dégomme une mouche sur ta tête à 25 mètres! + +[BJM2_2E:BANKJ2] +Vraiment? + +[BJM2_2F:BANKJ2] +Ouais. J'ai appris ça à l'armée. + +[BJM2_2G:BANKJ2] +Ils s'amusent souvent à descendre des mouches à l'armée? Heureusement que je paye pas d'impôts! + +[BJM2_2H:BANKJ2] +Tu t'crois marrant peut-être, minus? + +[BJM2_2I:BANKJ2] +Ha ha ha ha ha! + +[BJM2_2J:BANKJ2] +Tirons quelques coups. + +[BJM2_1:BANKJ2] +~g~Va à Ammu-Nation dans le centre et parle à Phil Cassidy. + +[BJM2_4:BANKJ2] +SCORE MANCHE 1 : ~1~ + +[BJM2_6:BANKJ2] +SCORE MANCHE 2 : ~1~ + +[BJM2_7:BANKJ2] +SCORE TOTAL : ~1~ + +[BJM2_9:BANKJ2] +~g~Va au point de départ de la deuxième manche. + +[BJM2_11:BANKJ2] +~r~Phil est mort! + +[BJM2_12:BANKJ2] +~r~Un des tireurs est mort! + +[BJM2_14:BANKJ2] +~g~Continue jusqu'au prochain secteur! + +[BJM2_17:BANKJ2] +~g~Va parler à Phil. + +[BJM2_24:BANKJ2] +~g~La cible la plus proche rapporte un point. + +[BJM2_25:BANKJ2] +~g~La cible intermédiaire rapporte deux points. + +[BJM2_27:BANKJ2] +~g~Toutes les cibles de cette manche rapportent un point. + +[BNK2_4:BANKJ2] +Waou! + +[BNK2_5:BANKJ2] +Il raterait une vache dans un couloir! + +[BNK2_7:BANKJ2] +Bon, tu veux bien me rendre un service et m'assister sur un boulot? + +[BNK2_8:BANKJ2] +Mon gars, vu comment tu tires, si tu me demandes en mariage, j'accepte. + +[BNK2_9A:BANKJ2] +Mon gars, tu ferais mieux de remballer ton baratin et tes grands projets. T'es trop mauvais tireur. + +[BNK2_9B:BANKJ2] +T'es trop mauvais tireur. + +[BJM2_28:BANKJ2] +SCORE MANCHE TROIS : ~1~ + +[BJM2_26:BANKJ2] +~g~La cible éloignée rapporte trois points. + +[BNK2_1:BANKJ2] +BALLES REELLES + +[RANGE_1:BANKJ2] +SCORE POUR LE TIR : ~1~ + +[BJM2_2:BANKJ2] +~g~Pour quitter la manche, appuie sur la ~h~~k~~PED_JUMPING~. + +[BJM2_N:BANKJ2] +Du calme... + +{=================================== MISSION TABLE BANKJ3 ===================================} + +[BJM3_A:BANKJ3] +Les choses commencent à se mettre tranquillement en place, ici. + +[BJM3_B:BANKJ3] +C'est quoi le plan, Tommy? Que pasa, amigo? + +[BJM3_C:BANKJ3] +Le plan, c'est que toi, tu restes ici à glander. Bon, on a besoin d'un chauffeur. + +[BJM3_D:BANKJ3] +Tommy, je m'en charge! Je peux conduire. + +[BJM3_E:BANKJ3] +C'est Hilary que tu veux, mec! Pas un stupide vantard d'école de droit. + +[BJM3_F:BANKJ3] +Hilary, c'est le meilleur. T'as jamais vu quelqu'un conduire aussi vite. Je vais l'appeler. + +[BJM3_G:BANKJ3] +Salut, Hil, c'est Phil. Comment ça va? Non, attends, on causera plus tard, j'ai besoin que tu me rendes un service. + +[BJM3_H:BANKJ3] +J'ai avec moi un mec du nord. Non, je crois pas qu'il était dans l'armée, mais il a besoin d'un conducteur. + +[BJM3_I:BANKJ3] +Pour un peu d'adrénaline. Ok, compris. + +[BJM3_J:BANKJ3] +Qu'est-ce qu'il a dit? + +[BJM3_K:BANKJ3] +Bon, il marche, pas de problème. En fait, y'a juste un détail, tu vois, il pose toujours une condition. + +[BJM3_L:BANKJ3] +Il refuse de bosser pour quelqu'un qui ne peut pas le battre. Un truc à cause de sa mère. + +[BJM3_M:BANKJ3] +Quoi qu'il en soit, il veut t'affronter d'abord et il a dit qu'il te retrouvait dehors... + +[BJM3_2A:BANKJ3] +C'est toi Tommy? Evidemment que c'est toi Tommy, enfin, je veux dire, + +[BJM3_2B:BANKJ3] +pourquoi est-ce que quelqu'un d'autre parlerait avec moi? + +[BJM3_2C:BANKJ3] +Bon. Ecoute-moi. + +[BJM3_2D:BANKJ3] +Je conduis pour toi que SI, et seulement SI, tu peux conduire convenablement. + +[BJM3_2E:BANKJ3] +Abandonne et je ne te pardonnerai jamais. + +[BJM3_2:BANKJ3] +~r~Hilary est mort! + +[BJM3_4:BANKJ3] +~g~T'as besoin d'une voiture! + +[BNK3_1:BANKJ3] +Ok, je piloterai pour toi, mais s'il te plaît, traite-moi mal. + +[BNK3_3A:BANKJ3] +Course de rue illégale dans Vice Point! + +[BNK3_3B:BANKJ3] +Appel à tous les officiers. + +[BNK3_3C:BANKJ3] +Les courses de rue sont formellement interdites! + +{=================================== MISSION TABLE BANKJ4 ===================================} + +[BNK4_A:BANKJ4] +~w~Comme vous pouvez voir, les mecs, ça va être le fric le plus facile qu'on se soit jamais fait! + +[BNK4_B:BANKJ4] +~w~Tommy, sérieusement, va falloir que tu considères la question judiciaire. + +[BNK4_C:BANKJ4] +~w~Merde! Qu'est-ce que tu fumes, mon pote? J'appelle pas ça un plan! + +[BNK4_D:BANKJ4] +~w~Bon, de toute manière, on a pas besoin de plan! + +[BNK4_E:BANKJ4] +~w~Prenez le communisme, ça c'était un plan! Et regarde où en est la Russie, maintenant! + +[BNK4_F:BANKJ4] +~w~Du calme, Ok? Avec une équipe comme ça, il n'y aura pas le moindre problème! + +[BNK4_G:BANKJ4] +~w~On amène Cam au coffre. Phil, toi et moi, on s'occupe de la sécurité et Hilary conduit la bagnole. + +[BNK4_H:BANKJ4] +~w~Hum, euh, t'oublies pas quelqu'un? Quelqu'un qui t'aurait aidé depuis le début dans cette ville? Quelqu'un... + +[BNK4_I:BANKJ4] +~w~Ken... Ken, c'est vrai. Bon, Ken va s'occuper de blanchir le pognon et de garder les boissons au frais! + +[BNK4_J:BANKJ4] +~w~Je comprends pas ce que je suis supposé faire ici. + +[BNK4_K:BANKJ4] +~w~Ecoute, c'est dans la poche. T'as jamais vu de film? + +[BNK4_L:BANKJ4] +~w~On entre dans la banque, on sort nos flingues et on ressort plein aux as! + +[P_DEAD:BANKJ4] +~r~Phil est mort! + +[C_DEAD:BANKJ4] +~r~Cam est mort! + +[H_DEAD:BANKJ4] +~r~Hilary est mort! + +[P_HIND:BANKJ4] +~r~T'as perdu Phil! + +[C_HIND:BANKJ4] +~r~T'as oublié Cam! + +[H_HIND:BANKJ4] +~r~Hilary est resté derrière! + +[GETCAR:BANKJ4] +~g~Monte dans la bagnole pour terminer le boulot! + +[TRASHED:BANKJ4] +~r~T'as détruit la bagnole pour s'enfuir! + +[BNK4_1:BANKJ4] +Je vais conduire. + +[BNK4_2:BANKJ4] +Super. Un passager. Attends que j'en parle à la réunion! + +[BNK4_3A:BANKJ4] +Hé, fais gaffe à la caisse, Tommy! + +[BNK4_3B:BANKJ4] +Tommy, Hilary prend trop de place! + +[BNK4_3C:BANKJ4] +C'est pas vrai! + +[BNK4_3D:BANKJ4] +Toi aussi! + +[BNK4_3E:BANKJ4] +Hé, fermez-la tous les deux ou vous rentrez à pied! + +[BNK4_3F:BANKJ4] +Ouais, Hilary. + +[BNK4_3I:BANKJ4] +Putain, Phil! Arrête d'agiter ce truc devant moi! + +[BNK4_3J:BANKJ4] +Ouais, tu vas éborgner quelqu'un! + +[BNK4_3M:BANKJ4] +Mon bébé! Elle est foutue! + +[BNK4_3O:BANKJ4] +Tu t'accroches à l'illusion de la permanence! + +[BNK4_3P:BANKJ4] +Quoi?! + +[BNK4_3Q:BANKJ4] +Tu crois que toutes les choses dureront! + +[BNK4_3R:BANKJ4] +La jeunesse, les amours, les pizzas... + +[BNK4_3S:BANKJ4] +Tout prend fin un jour, et tu dois l'accepter. + +[BNK4_3T:BANKJ4] +Ouais, t'as raison, merci, Cam. + +[BNK4_3U:BANKJ4] +De rien. + +[BNK4_3V:BANKJ4] +Hé, Tommy, pourquoi on s'arrête? + +[BNK4_4A:BANKJ4] +~w~Hilary, continue à tourner autour du quartier. + +[BNK4_5:BANKJ4] +~w~Ok, Tommy, ok. + +[BNK4_6:BANKJ4] +~w~C'EST UN HOLD-UP! + +[BNK4_7:BANKJ4] +~w~PERSONNE NE BOUGE! + +[BNK4_8:BANKJ4] +~w~TOUT LE MONDE CONTRE LE MUR! + +[BNK4_9:BANKJ4] +Phil! Monte la garde! + +[BNK4_10:BANKJ4] +Bien compris! + +[BNK4_11:BANKJ4] +Amène-toi, Cam, la salle des coffres est en haut... + +[BK4_12A:BANKJ4] +Merde! C'est un Flange 9000! + +[BK4_12B:BANKJ4] +Ca peut prendre des heures à ouvrir, + +[BK4_12C:BANKJ4] +ou cinq minutes si tu peux trouver le directeur... + +[BNK4_13:BANKJ4] +Je vais aller voir où il se planque. + +[BK4_14A:BANKJ4] +Ca roule, Phil? + +[BNK4_15:BANKJ4] +Pas de problème. Tout le monde se tient relax. + +[BNK4_16:BANKJ4] +Toi! Tu viens avec moi! + +[BNK4_17:BANKJ4] +D'accord! D'accord! Ne tirez pas! + +[BNK4_18:BANKJ4] +J'AI DIT PERSONNE NE BOUGE! + +[BK4_19A:BANKJ4] +C'est réglé sur une serrure à horloge, + +[BK4_19B:BANKJ4] +Vous feriez aussi bien d'abandonner tout de suite! + +[BK4_20A:BANKJ4] +Putain, je peux court-circuiter l'horloge, + +[BK4_20B:BANKJ4] +on a juste besoin de ton code secret et c'est bingo! + +[BNK4_21:BANKJ4] +Reste ici. T'essayes quoi que ce soit et t'es mort! Pigé? + +[BK4_24A:BANKJ4] +Je vais voir Phil, je reviens tout de suite. + +[BK4_24B:BANKJ4] +Je t'avais dit de pas toucher à cette alarme! + +[BNK4_25:BANKJ4] +Le commando du SWAT sera là d'une minute à l'autre! + +[BNK4_27:BANKJ4] +J'aurais besoin d'un coup de main, Tommy! + +[BNK4_28:BANKJ4] +Ici le SWAT de Vice City! Vous êtes complètement encerclés! + +[BNK4_29:BANKJ4] +Encerclés? HA HA HA HAAAAAaaa! + +[BNK4_30:BANKJ4] +Ils racontent que des conneries, ces ripoux! + +[BK4_31A:BANKJ4] +Tommy! La salle des coffres est ouverte! + +[BK4_34A:BANKJ4] +Ok, on a la caisse de retraite du SWAT! Tirons-nous de là! + +[BK4_34B:BANKJ4] +Bon, vous l'aurez cherché! C'était votre dernière chance! + +[BK4_35A:BANKJ4] +Ils lancent l'assaut! + +[BK4_35B:BANKJ4] +Planquez-vous! + +[BNK4_36:BANKJ4] +Où est Cam? + +[BNK4_37:BANKJ4] +Du passé... + +[BNK4_38:BANKJ4] +C'était le dernier! On dégage! Allez! + +[BNK_39:BANKJ4] +Merde! Où est Hilary? + +[BK4_40A:BANKJ4] +Il me payera ça! + +[BNK4_42:BANKJ4] +Hé! Les mecs! Montez! Je vous couvre! + +[BNK4_44:BANKJ4] +On a réussi! On est riches! RICHES! + +[BNK4_45:BANKJ4] +Dommage que Cam y soit resté, c'était un mec réglo! + +[BNK4_46:BANKJ4] +Ouais. Enfin... On hérite de sa part! + +[BNK4_47:BANKJ4] +Tu l'as dit! Ouais! + +[BNK4_48:BANKJ4] +Tommy, que dirais-tu d'un massage? + +[BNK4_49:BANKJ4] +Salut, Mercedes! Ouais, pourquoi pas, je me sens un peu tendu... + +[BNK450A:BANKJ4] +Qu'est-ce que je te disais, Tommy? Hein, quoi? Le SWAT a du souci à se faire quand Kent Paul est en ville. + +[BNK450B:BANKJ4] +Allez, allonge une plus grosse part, mon pote, j'ai besoin de nouvelles fringues! + +[BNK4_94:BANKJ4] +~w~Ok, les mecs. Tout roule comme prévu. + +[BM_DEAD:BANKJ4] +~r~T'avais besoin du directeur vivant! + +[ASSET_A:BANKJ4] +BRAQUAGE DE BANQUE OK! + +[ASSET_B:BANKJ4] +~g~Le Malibu Club génère dorénavant un revenu de $~1~ maximum. Pense à récupérer le fric régulièrement. + +[IDIOT:BANKJ4] +~r~T'as raison, trimballe-toi déguisé en malade pour attirer l'attention! Espèce d'abruti! + +{=================================== MISSION TABLE BARON1 ===================================} + +[COK1_A:BARON1] +Allez, sale carne, allez! Allez! Putain... + +[COK1_B:BARON1] +Connard de cheval! Je te ferai décapiter! + +[COK1_C:BARON1] +C'est qui ce con? + +[COK1_D:BARON1] +Tommy Vercetti, vous vous souvenez de moi? + +[COK1_E:BARON1] +Excuse-moi, mais j'suis un peu enervé. Fais jamais confiance à une saloperie de canasson! + +[COK1_F:BARON1] +Tu fais du bon boulot. Maintenant, tu travailles pour moi. + +[COK1_H:BARON1] +Comme je disais, amigo, tu bosses pour moi. Maintenant, la ferme! Un salopard m'a trahi! + +[COK1_I:BARON1] +Il croit que je sais pas combien de pognon je devrais gagner... Mais me faucher 3%, c'est pareil que d'me faucher 100%! + +[COK1_J:BARON1] +Personne ne me fait ça! PERSONNE! + +[COK1_K:BARON1] +Tu vas le suivre depuis son appart' pour voir où il va! On le descendra plus tard. + +[COK1_1:BARON1] +Oh merde! + +[COK1_2:BARON1] +Un peu trop lent, papi! + +[COK1_4:BARON1] +Pauv' mec. + +[COK1_5:BARON1] +Tu ferais mieux de continuer à courir, connard! + +[COK1_8:BARON1] +~g~Vite! Grimpe dans une caisse et suis-le! + +[COK1_9:BARON1] +~r~Tu es supposé le suivre, pas le buter! + +[COK1_10:BARON1] +~r~Va à la maison des voleurs et trouve où il planque le fric! + +[COK1_11:BARON1] +~g~Regarde par cette fenêtre. + +[COK1_7:BARON1] +~g~Il s'est échappé vers le toit. Suis-le mais ne le tue pas! + +[COK1_G:BARON1] +Je travaille pour du fric. + +{=================================== MISSION TABLE BARON2 ===================================} + +[COK2_V:BARON2] +Il commence à me faire confiance... + +[COK2_A:BARON2] +Qui c'est qui m'a foutu un con pareil! + +[COK2_B:BARON2] +CON! CON! CON! CON! + +[COK2_C:BARON2] +Tommy - + +[COK2_D:BARON2] +Quoi, Ricardo? + +[COK2_E:BARON2] +Ces merdeux, faut toujours qu'ils essayent de m'baiser... + +[COK2_F:BARON2] +C'est le problème avec ce genre de biz... + +[COK2_G:BARON2] +Qu'est-ce que tu fous toi? + +[COK2_H:BARON2] +Ces connards m'ont vraiment manqué de respect. + +[COK2_I:BARON2] +Bientôt, le premier connard venu va penser qu'il peut vendre de la dope à Vice City. + +[COK2_J:BARON2] +Et ensuite, ça sera quoi? Ces pourris de la Mafia, hein? + +[COK2_K:BARON2] +Le Q.G. de ce gang est une vrai forteresse au rez-de-chaussée, + +[COK2_L:BARON2] +alors Quentin ici... Quentin! QUENTIN! + +[COK2_M:BARON2] +Il va te faire survoler la zone! + +[COK2_N:BARON2] +Liquide-les! + +[COK2_O:BARON2] +Qu'est-ce que tu fous toi? + +[COK2_P:BARON2] +Qu'est-ce que tu fous ici? + +[COK2_Q:BARON2] +Hé, je me suis un peu renseigné et c'est sûr + +[COK2_R:BARON2] +que c'est Diaz qui a bousillé le deal et refroidi mon frère! + +[COK2_S:BARON2] +Et il va t'buter toi aussi! + +[COK2_T:BARON2] +Je peux me faire Diaz! + +[COK2_U:BARON2] +Non, écoute-moi! JE m'occupe de Diaz! + +[COK2_1:BARON2] +Il y a un truc que j'pige pas, pourquoi 'Quentin'? + +[COK2_2:BARON2] +Je sais pas, ça sonne bien... Quentin Vance... + +[COK2_3:BARON2] +Vance? Tu t'appelles Lance Vance? + +[COK2_4:BARON2] +Ca va, hein! J'en ai déjà eu pour mon compte à l'école! + +[COK2_5:BARON2] +T'as déjà tiré depuis un hélico? + +[COK2_8:BARON2] +On va où, au fait? + +[COK2_9:BARON2] +Prawn Island. + +[COK2_13:BARON2] +Lance Vance. Mon pauv' vieux... + +[COK2_14:BARON2] +Ok, on y est presque. + +[COK2_15:BARON2] +On va faire un ou deux passages, + +[COK2_16:BARON2] +Alors, descends autant de tireurs que tu peux. + +[COK2_17:BARON2] +Et puis je te dépose et tu te débrouilles tout seul. + +[COK2_20:BARON2] +Merde, c'est la guerre en bas! Descends ces tireurs! + +[COK2_21:BARON2] +On s'fait canarder, mec! + +[COK2_22:BARON2] +Ca coûte un max de réparer c'coucou, alors bute-les! + +[COK2_23:BARON2] +Bon, t'es tout seul à partir de là. Bonne chance, mon frère! + +[COK2_24:BARON2] +Etat hélico : + +[COK2_25:BARON2] +~g~Va récupérer le pognon sur le toit. + +[COK2_27:BARON2] +T'es sur MES plates-bandes, connard! + +[COK2_28:BARON2] +Ta fin est proche! + +[COK2_6:BARON2] +Non. Je vais m'entraîner un peu en chemin. + +[OPEN_B:BARON2] +Les barrières sur les routes pour le centre ville ont été retirées. + +{=================================== MISSION TABLE BARON3 ===================================} + +[COK3_A:BARON3] +Tu fais moins le fier maintenant, hein! + +[COK3_B:BARON3] +Ahahahaha, ahahahaha! + +[COK3_C:BARON3] +Ho! Fais gaffe où tu braques ce truc! + +[COK3_D:BARON3] +Plus de merde de pigeon sur ma bagnole, hein, Tommy! + +[COK3_E:BARON3] +Faut croire que non... + +[COK3_F:BARON3] +T'as sacrément raison. Bon, écoute, + +[COK3_G:BARON3] +tu sais qui possède le bateau le plus rapide de la côte est? + +[COK3_H:BARON3] +Je suis pas bien sûr. + +[COK3_I:BARON3] +MOI! Et je voudrais pas que ça change... + +[COK3_J:BARON3] +Tous les contrebandiers d'ici à Caracas ne rêvent que d'un seul truc, un bateau plus rapide que le mien. + +[COK3_K:BARON3] +Et la rumeur prétend que le chantier naval Hull-o-caust vient juste d'en finir un + +[COK3_L:BARON3] +pour le compte de je ne sais quel connard du Costa Rica. + +[COK3_M:BARON3] +Alors, Tommy... JE VEUX CE BATEAU! + +[COK3_N:BARON3] +Je crois que tes pigeons sont de retour... + +[COK3_O:BARON3] +Ah! Je croyais que je t'avais eu! D'où tu sors? + +[COK3_P:BARON3] +Pigeons d'mes deux! Boum! Aaaaah! + +[COK3_5:BARON3] +~g~Trouve l'interrupteur pour abaisser le bateau. + +[COK3_6:BARON3] +~g~Amène le bateau à la résidence. + +[COK3_7:BARON3] +~r~T'as détruit le bateau! + +[COK3_8:BARON3] +~g~Va au chantier naval des docks et fauche le bateau le plus rapide. + +[COK3_9:BARON3] +~g~Monte dans le bateau. + +{=================================== MISSION TABLE BARON4 ===================================} + +[COK4_A:BARON4] +Ejection! Connerie d'engin! + +[COK4_B:BARON4] +Pourquoi tu m'fais ça? + +[COK4_C:BARON4] +Pour qui tu t'prends, putain de magnéto! Grrr! + +[COK4_D:BARON4] +ENCULE! + +[COK4_E:BARON4] +Si il m'bouffe ma cassette préférée d'El burro, il est fini! + +[COK4_F:BARON4] +Qu'est-ce que je peux faire d'autre? + +[COK4_G:BARON4] +Il doit pas être branché, c'est tout... + +[COK4_H:BARON4] +Hein? + +[COK4_I:BARON4] +Merde! Tant pis, j'peux m'en acheter cent si ça me chante. + +[COK4_J:BARON4] +Bon, Tommy, + +[COK4_K:BARON4] +tous les mois, il y a un indépendant qui met le cap sur Vice City pour y amarrer son yatch. + +[COK4_L:BARON4] +Il vend sa cargaison au premier bateau. + +[COK4_M:BARON4] +Je veux que tu prennes le speedboat, + +[COK4_N:BARON4] +que tu largues tous les autres connards intéressés + +[COK4_O:BARON4] +et que tu ramènes la cargaison ici. Ok? + +[COK4_P:BARON4] +Laisse-moi deviner. T'as pensé que je pourrais avoir besoin d'un ange-gardien. + +[COK4_Q:BARON4] +Je dis juste que t'as intérêt à me laisser entrer, mon pote. + +[COK4_R:BARON4] +Tu peux m'sortir tout un tas de conneries sur le dur solitaire, + +[COK4_S:BARON4] +mais je sais qu'un de ces quatre, j'vais t'sauver la mise. + +[COK4_T:BARON4] +et qu'après tu voudras me rouler une pelle! + +[COK4_U:BARON4] +Taré, va! + +[COK4_V:BARON4] +Hahahaha + +[COK4_1:BARON4] +Tommy, on sait que c'est Diaz qui a fait foirer notre deal... + +[COK4_3:BARON4] +Alors pourquoi on bosse pour sa gueule? + +[COK4_4:BARON4] +Plus on en apprend maintenant, plus on en saura quand on s'emparera de cette ville! + +[COK4_5:BARON4] +J'aime bien ton style, mon pote. Vraiment culotté. + +[COK4_12:BARON4] +Fais gaffe, ils arrivent de partout! + +[COK4_13:BARON4] +On les a eu! Fonce chez Diaz aussi vite que tu peux! + +[COK4_14:BARON4] +Tu veux ta part de plancton? + +[COK4_15:BARON4] +Salue la poiscaille de ma part! + +[COK4_16:BARON4] +Mange! Mange! + +[COK4_19:BARON4] +Encore des emmerdes droit devant! + +[COK4_20:BARON4] +Il y a des porte-flingues sur cette jetée! + +[COK4_24:BARON4] +Joli coup, mon pote. T'es vraiment un cinglé de première! + +[COK4_25:BARON4] +Ah... Merci. + +[COK4_26:BARON4] +A la prochaine, Tommy. + +[COK4_27:BARON4] +Okay, M. Lance Vance Danse. + +[COK4_28:BARON4] +~g~Atteins le yatch avant les autres bateaux! + +[COK4_31:BARON4] +~g~Va jusqu'au bateau le plus rapide de la jetée! + +[COK4_32:BARON4] +~r~Trop lent! + +[COK4_33:BARON4] +~r~T'as détruit le bateau! + +[COK4_34:BARON4] +~g~Bousille ces bateaux! + +[COK4_35:BARON4] +Etat du bateau : + +{=================================== MISSION TABLE BARON5 ===================================} + +[PROP_A:BARON5] +PROPRIETE ACHETEE! + +[COK4_30:BARON5] +~r~Lance est mort! + +[ASS1_A:BARON5] +J'ai récupéré des flingues. Ils sont dans l'coffre. + +[ASS1_B:BARON5] +Merde alors! Où c'est que t'as dégoté tout ça? + +[ASS1_C:BARON5] +Je l'gardais au frais pour une grande occasion... + +[ASS1_D:BARON5] +Ca te botte? + +[ASS1_E:BARON5] +Tu parles que ça m'botte. + +[ASS1_F:BARON5] +Espèce de connard... + +[ASS1_G:BARON5] +Ma belle maison... + +[ASS1_H:BARON5] +Regarde ce que t'en as fait! + +[ASS1_I:BARON5] +Ca c'est pour mon frère! + +[ASS1_J:BARON5] +Je te faisais confiance, Tommy... + +[ASS1_K:BARON5] +Je t'aurais aidé à devenir... + +[ASS1_L:BARON5] +Bonne nuit, Diaz. + +[ASS1_1:BARON5] +Cet endroit va bientôt grouiller de salopards... Fais gaffe. + +[ASS1_2:BARON5] +T'en fais pas, Tommy, je te couvre. + +[ASS1_4:BARON5] +Diaz doit être à l'intérieur! + +[ASS1_13:BARON5] +DIAZ! Je suis venu reprendre ton affaire! + +[ASS1_14:BARON5] +TOMMY! Tu me trahis... Idiot! Je vais te buter tout de suite! + +[ASS1_16:BARON5] +~g~Tue Diaz! + +[BUD1:BARON5] +Lance + +[ASS1_18:BARON5] +~g~La porte est verrouillée. Trouve un autre chemin. + +[ASS1_19:BARON5] +Par ici! + +[ASS1_20:BARON5] +Tommy, mon problème c'est Quentin, pas toi, mec! + +{=================================== MISSION TABLE BIKE1 ===================================} + +[BM1_A:BIKE1] +Où est Baker? + +[BM1_B:BIKE1] +Je cherche Big Mitch Baker... + +[BM1_C:BIKE1] +Qui le cherche? + +[BM1_D:BIKE1] +Tommy Vercetti. + +[BM1_E:BIKE1] +Vercetti... + +[BM1_F:BIKE1] +T'as pas l'air d'un flic, ce qui te donne droit à une minute... + +[BM1_G:BIKE1] +T'as intérêt à faire court! + +[BM1_H:BIKE1] +Kent Paul dit que ça pourrait t'intéresser de te charger de la sécurité pour un boulot qu'il prépare. + +[BM1_I:BIKE1] +Kent Paul? Ah! Pas étonnant qu'il t'ai envoyé. + +[BM1_J:BIKE1] +La dernière fois qu'il est passé ici, il en est ressorti par la fenêtre avec rien d'autre que son costume d'anglais. + +[BM1_K:BIKE1] +Bon, t'es intéressé ou pas? + +[BM1_L:BIKE1] +On ne rend des services qu'à ceux qui font partie de la bande. + +[BM1_M:BIKE1] +Comment je me joins à vous? + +[BM1_N:BIKE1] +On est pas un club de loisirs, mon pote! Tu peux conduire une bécane? + +[BM1_O:BIKE1] +Tu peux t'asseoir sur un tabouret de bar et boire? + +[BM1_P:BIKE1] +Cougar, Zeppelin, allez voir comment elle se débrouille sur une bécane... + +[BM1_2:BIKE1] +~g~Il te faut une Freeway ou une Angel pour participer! + +[BM1_3:BIKE1] +~r~Les concurrents ont été attaqués! + +[BIKE1_1:BIKE1] +Bon, chouettes fringues. Voyons ce que tu peux faire. + +[BM1_1:BIKE1] +~g~Enfourche une Freeway ou une Angel et va sur la grille de départ. + +{=================================== MISSION TABLE BIKE2 ===================================} + +[BM2_4:BIKE2] +~r~Tu n'as pas rempli le chaosmètre à temps! + +[BM2_1:BIKE2] +CHAOSMETRE : + +[BM2_A:BIKE2] +Ha ha ha, je t'ai encore eu! + +[BM2_B:BIKE2] +Hé, Vercetti. + +[BM2_C:BIKE2] +Cougar dit que tu te débrouilles plutôt bien sur une bécane. + +[BM2_D:BIKE2] +Ouais, mais combien d'épreuves il va encore falloir que je me tape? + +[BM2_E:BIKE2] +Je suis très occupé, mon pote. + +[BM2_F:BIKE2] +Si c'est une baston qui peut régler la question, alors amène-toi! + +[BM2_G:BIKE2] +Devenir l'un d'entre nous, c'est pas qu'une question de baston. C'est appartenir à une famille. + +[BM2_H:BIKE2] +Ouais, j'ai déjà appartenu à une famille avant et ça n'a pas bien collé. + +[BM2_I:BIKE2] +Peut-être, mais notre famille veille sur les siens. + +[BM2_J:BIKE2] +On demande à personne de faire le sale boulot pour l'abandonner au trou pendant quinze ans... + +[BM2_K:BIKE2] +Et ouais, je me suis renseigné. + +[BM2_L:BIKE2] +Ce que tu vois, c'est la plus grande famille d'inadaptés, de parias et d'ordures qui existe. + +[BM2_M:BIKE2] +Certains ont même été trahis par leur propre pays! + +[BM2_N:BIKE2] +On m'a foutu en taule pendant la guerre du Vietnam! Saloperie... + +[BM2_O:BIKE2] +Voilà pourquoi je vais te demander d'aller foutre le bordel. + +[BM2_P:BIKE2] +Ce pays de merde a vraiment besoin d'un coup de pied au cul et c'est nous qui allons le lui donner! + +[BM2_Q:BIKE2] +Alors sors de là, attrape une bécane et montre à cette ville à quel point t'es énervé! + +[BM2_R:BIKE2] +D'accord. + +[BM2_2:BIKE2] +~g~Tu dois remplir le chaosmètre dans le temps imparti pour nous prouver que t'es un dur de dur! + +[BM2_3:BIKE2] +~g~Ce bruit veut dire qu'une partie du chaosmètre est remplie, continue comme ça. + +{=================================== MISSION TABLE BIKE3 ===================================} + +[BM3_A:BIKE3] +Salut, Mitch. + +[BM3_B:BIKE3] +Hé! Mais c'est ce gros méchant de Vercetti! + +[BM3_C:BIKE3] +Maintenant, je veux voir à quel point tu peux te battre pour ton secteur. + +[BM3_D:BIKE3] +Un gang local a commis l'erreur de me faucher ma bécane... + +[BM3_E:BIKE3] +Probablement un truc de machos ou quelque chose de ce genre. + +[BM3_F:BIKE3] +Moi et mes gars, on pourrait aller leur donner la leçon qu'ils méritent... + +[BM3_G:BIKE3] +Enfin... + +[BM3_H:BIKE3] +Je me suis dit que ça serait une excellente initiation pour toi. + +[BM3_I:BIKE3] +Tu me ramènes ma bécane et tu pourras dire à Paul qu'on assurera la sécurité. + +[BM3_2:BIKE3] +~r~Tu étais supposé ramener la bécane, pas la bousiller! + +[BM3_3:BIKE3] +~g~Ramène la bécane au bar! + +[BM3_4:BIKE3] +~g~Monte sur la bécane! + +[INTRUDE:BIKE3] +~g~T'as été repéré! + +[BM3_6:BIKE3] +~g~Ils se planquent derrière Ammu-Nation dans le centre. + +[BM3_7:BIKE3] +~g~T'auras besoin d'une bécane rapide pour accéder au toit. + +[BM3_8:BIKE3] +~g~Sers-toi de la bécane pour sauter de ces marches jusqu'à l'autre côté de la route. + +[BM3_1:BIKE3] +~g~Un gang local a volé l'Angel de Mitch Baker. Récupère-la! + +[BM3_9:BIKE3] +~g~Récupère l'Angel de Mitch Baker et dégage de là! + +{=================================== MISSION TABLE BMX_1 ===================================} + +[BMX_REC:BMX_1] +~g~Nouveau record établi : ~1~! + +[GETBIK2:BMX_1] +Tu as ~1~ secondes pour enfourcher une bécane! + +{=================================== MISSION TABLE BOATBUY ===================================} + +[DRUG_1:BOATBUY] +Bonjour? Y'a quelqu'un? Y'A QUELQU'UN??? + +[DRUG_2:BOATBUY] +Ferme-la. Il y a un mec, ici. + +[DRUG_3:BOATBUY] +Hé, mon pote! T'es le nouveau proprio? + +[DRUG_4:BOATBUY] +Ouais. Lequel de ces bateaux est le plus rapide? + +[DRUG_5:BOATBUY] +Il est déjà à l'eau, mon pote, + +[DRUG_6:BOATBUY] +je me suis dit que tu voudrais peut-être l'essayer. + +[DRUG_7:BOATBUY] +Mon pote, il est déjà équipé d'un moteur de 300 chevaux... + +[DRUG_8:BOATBUY] +...et avec la coque en fibre de verre, il touche même pas l'eau! + +[DRUG_9:BOATBUY] +Il monte à 60 noeuds en quatre secondes, mon pote... + +[DRUG_10:BOATBUY] +Et il peut embarquer vingt écopes de la meilleure jamaïquaine dans la coque. + +[DRUG_11:BOATBUY] +Alors vas-y, mon pote, il t'attend pour décoler! + +[DRUG_12:BOATBUY] +Ouais, hé, mon pote, t'as du feu? + +[DRUG_13:BOATBUY] +Mec? + +{=================================== MISSION TABLE CAP_1 ===================================} + +[CAP1_B1:CAP_1] +~g~La mafia taxe tes commerces. Trouve-les et bute-les. + +[CAP1_B2:CAP_1] +~g~La mafia taxe le chantier naval! + +[CAP1_B3:CAP_1] +~g~La mafia taxe l'usine de crème glacée! + +[CAP1_B4:CAP_1] +~g~La mafia taxe la concession automobile! + +[CAP1_B5:CAP_1] +~g~La mafia taxe la compagnie de taxis! + +[CAP_01:CAP_1] +Ok, où est l'urgence? + +[CAP_02:CAP_1] +QUI? + +[CAP_03:CAP_1] +Tommy... Des truands de la mafia... Ils ont dit qu'ils venaient prendre leur part... + +[CAP_04:CAP_1] +... Ils ont dit que c'était le pognon de Mr Forello... J'me sens mal... + +[CAP_05:CAP_1] +Forelli? SONNY Forelli? + +[CAP_06:CAP_1] +Ouais, c'est ça... je crois... Ils ont beaucoup insisté... + +[CAP_07:CAP_1] +T'inquiète pas, je suis pas en colère contre toi. + +[CAP_08:CAP_1] +Emmène-le à l'hosto. + +[CAP_09:CAP_1] +Tommy... Taille un nouveau trou du cul à ce salopard de ma part... + +[CAP_10:CAP_1] +Je vais lui en rajouter deux! + +[CAP1_2:CAP_1] +Tu connais la règle, Vercetti! + +[CAP1_3:CAP_1] +Avec les compliments de M. Forelli! + +[CAP1_4:CAP_1] +C'est le boucher d'Harwood! + +[CAP1_5:CAP_1] +Tu diras à Sonny... de dégager! + +[CAP1_6:CAP_1] +Vice City est à moi, désormais, pas à lui! + +[CAP1_7:CAP_1] +Tu crois que tu peux m'avoir, Vercetti? + +[CAP1_8:CAP_1] +On va continuer à s'amener jusqu'à ce que tu sois raide mort, Vercetti! + +[CAP1_9:CAP_1] +T'as pas une chance, connard psychotique! + +[CAP1_10:CAP_1] +Je vais te descendre, Vercetti! + +[CAP1_11:CAP_1] +T'as toujours été un con! + +[CAP1_12:CAP_1] +Tu vas crever, Vercetti! + +[CAP1_B6:CAP_1] +~g~Tu as trouvé l'encaisseur. Tue-le. + +[CAP1_B7:CAP_1] +~g~Tu as perdu l'encaisseur. + +[CAP1_B8:CAP_1] +~r~L'encaisseur a taxé toutes tes affaires. + +[CAP1_B9:CAP_1] +~g~La mafia a taxé le Malibu! + +[CAP1_B0:CAP_1] +~g~La mafia a taxé le studio de cinéma! + +[CAP1_C2:CAP_1] +~g~La mafia est arrivée au chantier naval! + +[CAP1_C3:CAP_1] +~g~La mafia est arrivée à l'usine de crème glacée! + +[CAP1_C4:CAP_1] +~g~La mafia est arrivée à la concession automobile! + +[CAP1_C5:CAP_1] +~g~La mafia est arrivée à la compagnie de taxis! + +[CAP1_C9:CAP_1] +~g~La mafia est arrivée au Malibu! + +[CAP1_C0:CAP_1] +~g~La mafia est arrivée au studio de cinéma! + +[CAP1_D2:CAP_1] +~g~La mafia quitte le chantier naval! + +[CAP1_D3:CAP_1] +~g~La mafia quitte l'usine de crème glacée! + +[CAP1_D4:CAP_1] +~g~La mafia quitte la concession automobile! + +[CAP1_D5:CAP_1] +~g~La mafia quitte la compagnie de taxis! + +[CAP1_D9:CAP_1] +~g~La mafia quitte le Malibu! + +[CAP1_D0:CAP_1] +~g~La mafia quitte le studio de cinéma! + +[CAP1B10:CAP_1] +Tu t'es occupé des taxeurs. Y'en a d'autres qui arrivent. + +{=================================== MISSION TABLE CARBUY ===================================} + +[CAR1_1:CARBUY] +B.J. Smith. Et vous devez être Mr Vercetti. + +[CAR1_2:CARBUY] +Vous voulez visiter? + +[CAR1_3:CARBUY] +Pourquoi pas? + +[CAR1_4:CARBUY] +Vous savez, je suis bien triste de vendre l'affaire... + +[CAR1_5:CARBUY] +C'était mon premier investissement de professionnel. + +[CAR1_6:CARBUY] +Mais l'heure est venue pour moi de continuer ma route. + +[CAR1_7:CARBUY] +Vous quittez la ville? + +[CAR1_8:CARBUY] +Pas trop pressé, j'espère? + +[CAR1_9:CARBUY] +Non, je sors juste de ma retraite et je prépare mon retour dans les affaires. + +[CAR1_10:CARBUY] +Cette affaire était pas trop solide, + +[CAR1_11:CARBUY] +mais mon personnel a pris sur lui de faire un peu plus + +[CAR1_12:CARBUY] +créatif en ce qui concerne les méthodes pour devenir rentable. + +[CAR1_13:CARBUY] +Evidemment, je pourrais démanteler l'affaire avant de la refiler. + +[CAR1_14:CARBUY] +Merde, je pourrais y foutre le feu si ça me chantait. + +[CAR1_15:CARBUY] +C'est un terrain de premier ordre. + +[CAR1_16:CARBUY] +Oh, je ne m'en ferais pas pour ça. + +[CAR1_17:CARBUY] +Cet endroit me semble idéal. + +[CAR1_18:CARBUY] +Ouais, il l'est, alors, marché conclu? + +{=================================== MISSION TABLE CARPAR1 ===================================} + +[MM_1_A:CARPAR1] +~g~Récupère ~y~5 points de passage~r~ SANS ~g~renverser de ~r~CONES! ~g~Tu peux les récupérer dans ~y~N'IMPORTE QUEL ORDRE. + +[CONE_1:CARPAR1] +~r~Tu as renversé un cône! + +[MM_1_C:CARPAR1] +~y~FRANCHIS~g~ un point de passage pour enclencher le compteur. ~g~Chaque point de passage te crédite ~y~~1~ SECONDES~g~. + +{=================================== MISSION TABLE COPCAR ===================================} + +[CLEVEL:COPCAR] +Missions d'autodéfense niveau ~1~ + +[C_COMP1:COPCAR] +Niveau 12 de mission Auto-défense réussi : votre max de gilet pare-balles passe à 150. + +[KILLS:COPCAR] +VICTIMES : + +[C_BREIF:COPCAR] +~g~Suspect aperçu pour la dernière fois dans le secteur ~a~. + +[C_PASS:COPCAR] +MENACE ENRAYEE : $~1~ + +[COPCART:COPCAR] +~g~Tu as ~1~ secondes pour retourner à une voiture de police avant que la mission ne s'achève. + +[C_CANC:COPCAR] +~r~Mission d'autodéfense annulée! + +[C_TIME:COPCAR] +~r~Ta carrière de flic est terminée! + +{=================================== MISSION TABLE COUNT1 ===================================} + +[CM1_A:COUNT1] +Vercetti? Hé, vous avez acheté la vieille imprimerie? + +[CM1_B:COUNT1] +Ouais, mon vieux a travaillé sur ces machines. + +[CM1_C:COUNT1] +J'aurais voulu marcher sur ses traces, mais... J'ai suivi une autre voie. + +[CM1_D:COUNT1] +Vous avez l'intention de vendre les vieilles machines, et de tout casser? + +[CM1_E:COUNT1] +Je m'disais qu'on pourrait peut-être imprimer quelque chose, un journal ou un magazine... + +[CM1_F:COUNT1] +Arrête tes conneries, fiston! J'ai toujours rêvé d'imprimer mon propre pognon! C'est pas très compliqué! + +[CM1_G:COUNT1] +Tu sais, j'ai fait ça sur une petite échelle pendant des années. + +[CM1_H:COUNT1] +Ah ouais? + +[CM1_I:COUNT1] +Ouais. Mais il nous faut des plaques de bonne qualité. + +[CM1_J:COUNT1] +Evidemment! Il y a un syndicat de contrefaçon qui opère déjà en Floride! + +[CM1_K:COUNT1] +Un syndicat? + +[CM1_L:COUNT1] +Ouais. C'est le bruit qui court en tout cas. + +[CM1_M:COUNT1] +Je connais un mec qui en saura sûrement plus... + +[CM1_N:COUNT1] +On avait l'habitude de passer nos soirées ensemble, à nettoyer les rouleaux... + +[CM1_2A:COUNT1] +Mate-moi ce cul! + +[CM1_2B:COUNT1] +Ok, ma belle, c'est l'heure de l'initiation! + +[CM1_2C:COUNT1] +Salut, vieille branche, comment ça va? + +[CM1_2D:COUNT1] +Que sais-tu à propos de la contrefaçon? + +[CM1_2E:COUNT1] +Oh, je vais bien, Paul, et toi? + +[CM1_2F:COUNT1] +Viens ici! + +[CM1_2G:COUNT1] +Bon! Bon! Bon! Tu es visiblement un homme très occupé! + +[CM1_2H:COUNT1] +Tout ce que je sais, c'est que les Triades fournissent les plaques. + +[CM1_2I:COUNT1] +Elles ont une compagnie de navigation sur les quais + +[CM1_2J:COUNT1] +et le patron devrait savoir quand les plaques arrivent! + +[CM1_2K:COUNT1] +Merci, Paul. + +[CM1_2L:COUNT1] +C'est quoi ton problème, pauvre maniaque! + +[CM1_2M:COUNT1] +Un autre verre, en vitesse! + +[CM1_3:COUNT1] +~g~T'as été repéré! + +[CM1_5:COUNT1] +~g~Va retrouver Kent Paul au Malibu! + +[CNT1_1:COUNT1] +T'es qui toi? Aïe! Pas le visage! Pas le visage! + +[CM1_1:COUNT1] +~g~Va au bateau de la Chartered Libertine Lines sur les docks. + +[CM1_2:COUNT1] +~g~L'officier de navigation détient l'information dont tu as besoin. + +[CNT1_2:COUNT1] +Ok, je parle! Je parle! + +[CM1_6:COUNT1] +~g~Ramène l'info à l'imprimerie! + +{=================================== MISSION TABLE COUNT2 ===================================} + +[CNT2_B1:COUNT2] +Bon, le coursier déplace les plaques des docks aujourd'hui. + +[CNT2_B2:COUNT2] +Faut l'intercepter et récupérer ces plaques, semer les flics éventuels et revenir ici. + +[CNT2_B3:COUNT2] +En fonction de comment ça marche, + +[CNT2_B4:COUNT2] +on aura entre cinq minutes et toute l'année pour imprimer le pognon avant que le syndicat de contrefaçon rapplique. + +[CNT2_B5:COUNT2] +Quoi qu'il en soit, je veux voir les premiers billets sortir de la presse cinq minutes après mon retour! C'est compris? + +[CNT2_B6:COUNT2] +T'inquiète pas, Tommy, on sera prêts. + +[CNT2_B7:COUNT2] +Les gars et moi, on sera dans le coin au cas où t'aurais besoin de renforts. + +[CNT2_B8:COUNT2] +Bon, tout le monde a compris? Parfait. A plus tard. + +[CNT2_01:COUNT2] +~g~Le ~r~coursier~g~ qui porte les plaques va bientôt arriver aux ~y~docks~g~ en hélico. + +[CNT2_02:COUNT2] +~r~Le coursier s'est enfui en hélico! + +[CNT2_03:COUNT2] +~r~Le coursier est parvenu à destination sain et sauf, c'est trop tard! + +[CNT2_04:COUNT2] +~r~Tu as détruit les plaques dans l'explosion! + +[CNT2_05:COUNT2] +~g~Tu as les plaques. Emmène-les à l'imprimerie! + +[CNT2_06:COUNT2] +~g~Le coursier est mort et il a jeté les plaques. Récupère-les en premier. + +[CNT2_07:COUNT2] +~g~Un des gardes a ramassé les plaques, ne le laisse pas s'enfuir! + +[CNT2_08:COUNT2] +~g~Le ~r~coursier~g~ avec les plaques est arrivé aux docks. + +[CNT2_4:COUNT2] +Affaire privée! Dégagez! + +[CNT2_09:COUNT2] +IMPRIMERIE ACQUISE + +[CNT2_10:COUNT2] +~g~L'imprimerie va maintenant générer un revenu maximum de $~1~. Veille à relever régulièrement les compteurs. + +[CNT2_11:COUNT2] +~r~Les plaques sont au fond de la mer! + +{=================================== MISSION TABLE CUBAN1 ===================================} + +[CUB1_A:CUBAN1] +Si, mec? + +[CUB1_B:CUBAN1] +Hé, du calme, papa, ce mec est là pour moi. Hé, c'est toi le mec? + +[CUB1_C:CUBAN1] +Oh ouais. C'est toi le mec. Je crois bien, tu sais. + +[CUB1_D:CUBAN1] +Non, je crois pas. + +[CUB1_E:CUBAN1] +Ah ouais? Amène-toi, gros dur! + +[CUB1_F:CUBAN1] +Tu crois que tu peux jouer avec moi? + +[CUB1_G:CUBAN1] +Tu penses que tu peux jouer au con avec moi? + +[CUB1_H:CUBAN1] +Non, je pense que tu joues assez le con pour nous deux. + +[CUB1_I:CUBAN1] +Hé, il t'a traité de con, fiston. + +[CUB1_J:CUBAN1] +Et moi, je le traite de fillette, papa. + +[CUB1_K:CUBAN1] +Regarde-le, tout habillé comme ça. + +[CUB1_L:CUBAN1] +C'est la soirée des pouffiasses, ce soir? + +[CUB1_M:CUBAN1] +Toi qu'est plutôt balaise, pourquoi tu te fringues en nana? + +[CUB1_N:CUBAN1] +Et t'as une petite culotte comme les gonzesses? + +[CUB1_O:CUBAN1] +Qu'est-ce que t'as contre les femmes? Tu préfères les mecs, mon gros? + +[CUB1_P:CUBAN1] +J'aime les femmes! J'aime toutes les femmes! J'aime ma maman, Chico! + +[CUB1_Q:CUBAN1] +Ok, ok, j'te crois sur parole. + +[CUB1_R:CUBAN1] +Tu sais conduire, amigo? + +[CUB1_S:CUBAN1] +Ouais... Comme une gonzesse. + +[CUB1_T:CUBAN1] +Très drôle... Tu me plais, ma grosse, peut-être que tu peux aider. + +[CUB1_U:CUBAN1] +Tu peux peut-être nous montrer que t'es un vrai mec? + +[CUB1_V:CUBAN1] +Sors le bateau. + +[CUB1_W:CUBAN1] +Prouve-moi que t'as des couilles énormes + +[CUB1_X:CUBAN1] +et pas une paire de pois chiches! + +[CUB1_02:CUBAN1] +Ok, mec, traite-la comme une gonzesse. + +[CUB1_03:CUBAN1] +Pas mal, t'es un vrai macho. + +[CUB1_04:CUBAN1] +Merde, t'en a vraiment dans le pantalon, amigo. + +[CUB1_05:CUBAN1] +Amigo, t'es un vrai mec, mec. + +[CUB1_06:CUBAN1] +Tu te prends pour un homme, mec? + +[CUB1_07:CUBAN1] +T'es qu'une petite poule mouillée, petit, c'est ta mère qui te manque? + +[CUB1_08:CUBAN1] +Tu gâches tout! Tu marches comme un mec, tu causes comme un mec, mais tu conduis comme une gonzesse! + +[CUB1_09:CUBAN1] +Mec, t'es un vrai mec de chez mec, mec! Putain, je t'adore, mec! + +[CUB1_10:CUBAN1] +C'est quand tu veux, mec. T'as des couilles de taureau et tous mes potes ont des couilles énormes! + +[CUB1_11:CUBAN1] +~r~T'as tué Rico! + +[CUB1_12:CUBAN1] +Va jusqu'au premier point de contrôle pour commencer le test. + +[CUB1_14:CUBAN1] +Retourne dans le bateau! + +[CUB1_15:CUBAN1] +~r~T'es trop lent, mec! + +[CUB1_01:CUBAN1] +Salut, moi c'est Rico. C'est toi qu'a des couilles de taureau? + +[CUB1_13:CUBAN1] +~g~Tu as trois minutes pour terminer le parcours. + +{=================================== MISSION TABLE CUBAN2 ===================================} + +[CUB2_25:CUBAN2] +BUTE TOUS LES HAITIENS! + +[CUB2_A:CUBAN2] +Un cafecito, por favor, Alberto... + +[CUB2_N:CUBAN2] +Tout de suite, Tommy. + +[CUB2_B:CUBAN2] +Poppa! Un gran problema! + +[CUB2_O:CUBAN2] +Umberto, mon fils, que s'est-il passé? + +[CUB2_C:CUBAN2] +Les Haïtiens! Je hais les Haïtiens! + +[CUB2_D:CUBAN2] +C'est la dernière fois qu'ils me font chier! + +[CUB2_E:CUBAN2] +Ces Haïtiens... On va les liquider! + +[CUB2_F:CUBAN2] +On a juste besoin de renforts... + +[CUB2_G:CUBAN2] +J'ai déjà perdu pas mal de frères là-bas. + +[CUB2_H:CUBAN2] +Amigo, tu conduis pas mal! + +[CUB2_I:CUBAN2] +Pour une gonzesse... Pas vrai? + +[CUB2_J:CUBAN2] +C'est pas l'heure des vannes! + +[CUB2_K:CUBAN2] +Allez, conduis encore pour moi! + +[CUB2_L:CUBAN2] +Récupère mes gars là-bas et puis on s'occupe de ces Haïtiens! + +[CUB2_M:CUBAN2] +Ils m'ont cherché et ils sont tombés sur le mec le plus balaise de la ville! + +[CUB2_01:CUBAN2] +C'est trop petit, mec, il te faut une plus grosse bagnole. + +[CUB2_02:CUBAN2] +On a besoin de renforts du café! + +[CUB2_03:CUBAN2] +~g~Prend une bagnole et va ramasser les Cubains à l'extérieur du café de Robina. + +[CUB2_04:CUBAN2] +~g~Amène les Cubains à l'endroit de la bagarre. + +[CUB2_05:CUBAN2] +Liquide ce lâche de tireur embusqué! + +[CUB2_07:CUBAN2] +Ils se battent comme des filles! A couvert! + +[CUB2_09:CUBAN2] +Un sniper sur le toit! + +[CUB2_11:CUBAN2] +~r~Imbécile, on avait besoin de cette bagnole! + +[CUB2_12:CUBAN2] +Hé, amigo! Content de te voir que t'as réussi! + +[CUB2_13:CUBAN2] +Putains de Haïtiens, on va tous les descendre! + +[CUB2_14:CUBAN2] +CHAAAAAARGEZ! + +[CUB2_15:CUBAN2] +Maintenant, mes frères! Chaaaargez! + +[CUB2_16:CUBAN2] +Tommy, on a fait la preuve de notre courage! + +[CUB2_17:CUBAN2] +Piquons cette camionnette bourrée de drogue et tirons-nous! + +[CUB2_18:CUBAN2] +~g~Trouve une bagnole et va chercher les Cubains. + +[CUB2_19:CUBAN2] +On va se battre comme des hommes! + +[CUB2_21:CUBAN2] +Battez-vous avec vos couilles! + +[CUB2_22:CUBAN2] +~g~Elimine les derniers Haïtiens pour que les Cubains puissent avancer. + +[CUB2_23:CUBAN2] +~g~Little Haiti va grouiller de Haïtiens voulant régler leurs comptes avec les Cubains. Fais gaffe à tes couilles. + +[CUB2_24:CUBAN2] +~g~Retourne au café de Robina avec la camionnette et gare-la derrière. + +{=================================== MISSION TABLE CUBAN3 ===================================} + +[CUB3_A:CUBAN3] +Alberto. Un café, senor. + +[CUB3_B:CUBAN3] +Papa, ne sers pas ce morbac... + +[CUB3_C:CUBAN3] +T'es un hypocrite, Tommy! + +[CUB3_D:CUBAN3] +T'es soit un hypocrite, soit une gonzesse! + +[CUB3_E:CUBAN3] +Les Haïtiens, mec! Ils se foutent de ma gueule! + +[CUB3_F:CUBAN3] +Du calme. C'est quoi ton problème? + +[CUB3_G:CUBAN3] +Ils se foutent ma gueule, Tommy, de ma gueule! + +[CUB3_H:CUBAN3] +Umberto Robina! Ils font comme ça leur chante! + +[CUB3_I:CUBAN3] +Personne fait ce qu'il veut, Umberto, ils font ce que tu les laisses faire. + +[CUB3_J:CUBAN3] +Quoi? + +[CUB3_K:CUBAN3] +Tu veux que quelqu'un s'en occupe? + +[CUB3_L:CUBAN3] +Je peux m'en charger, mais ça va te coûter cher! + +[CUB3_M:CUBAN3] +Je sais qu'on est frères et tout et tout, mais ça, c'est les affaires... + +[CUB3_N:CUBAN3] +Tommy, t'es un vrai mec! Un businessman! Un gentleman! + +[CUB3_O:CUBAN3] +Ces Haïtiens. Ils ont de la marchandise de première qualité qui arrive par la mer. + +[CUB3_P:CUBAN3] +On la prend et on en finit avec eux. + +[CUB3_Q:CUBAN3] +Tu t'en charges et je veillerai sur toi. Comme mon frère. Comme mon fils. + +[CUB3_R:CUBAN3] +J'préfère que tu me files du pognon, plutôt que de me faire sauter sur tes genoux, amigo... + +[CUB3_01:CUBAN3] +Salut, Rico. Chouette bateau. T'es prêt? + +[CUB3_03:CUBAN3] +~g~Récupère toutes les serviettes pleines de drogue et de cash. + +[CUB3_04:CUBAN3] +~g~Ramène la drogue et le cash à Umberto. + +[CUB3_05:CUBAN3] +Si, Tommy, faut que tu tires bien, aujourd'hui. + +[CUB3_06:CUBAN3] +Mon bateau, je voudrais pas qu'il se retrouve plein de trous, ok? + +[CUB3_07:CUBAN3] +~g~Va retrouver Rico. Il te conduira au rendez-vous. + +[CUB3_02:CUBAN3] +~g~TUE TOUS LES HAITIENS EN BATEAU! + +[CUB3_08:CUBAN3] +Oh oh... Les Cubains. On nous attaque! + +{=================================== MISSION TABLE CUBAN4 ===================================} + +[CUB4_A:CUBAN4] +Salut, les filles, vous savez ce que je vais faire? + +[CUB4_B:CUBAN4] +Je vais aller me descendre un Haïtien. Et après, vous savez quoi? + +[CUB4_C:CUBAN4] +Après, je vais me vider les couilles comme un homme! + +[CUB4_D:CUBAN4] +T'as déjà vu ça chica? Un peu comme ça... + +[CUB4_E:CUBAN4] +Pauv' mec! + +[CUB4_F:CUBAN4] +Connard! + +[CUB4_G:CUBAN4] +Hé, ma poule, je te toucherai même pas avec une perche! + +[CUB4_H:CUBAN4] +Umberto Robina, il aime les femmes! Pas les vieilles dindes en jupe! + +[CUB4_I:CUBAN4] +Tommy! Tommy, je t'aime, je t'aime! Allons-y! + +[CUB4_J:CUBAN4] +Où ça? Je peux pas avoir un café d'abord? + +[CUB4_K:CUBAN4] +Pas le temps pour un café! D'ailleurs, je viens d'en prendre un! + +[CUB4_L:CUBAN4] +Faut qu'on liquide les Haïtiens! + +[CUB4_M:CUBAN4] +Tommy, tu sais comment on tue un serpent? + +[CUB4_N:CUBAN4] +Tu le mords au cul! Ahahaha! + +[CUB4_O:CUBAN4] +Si tu le dis, Umberto. + +[CUB4_P:CUBAN4] +Tommy, va nous trouver une petite bagnole haïtienne! + +[CUB4_Q:CUBAN4] +Dès que tu l'as, reviens récupérer mon petit + +[CUB4_R:CUBAN4] +Pepe et emmène-le chez les Haïtiens. + +[CUB4_S:CUBAN4] +Ensuite, tu vas au centre de retraitement des Haïtiens et tu te sers de leurs solvants comme explosif. + +[CUB4_T:CUBAN4] +Boom! Et bye bye! + +[CUB4_U:CUBAN4] +Et toi, Umberto? + +[CUB4_V:CUBAN4] +Oh, je reste en arrière, je surveille le café avec papa. + +[CUB4_W:CUBAN4] +Il ne se sent pas très bien, tu sais. + +[CUB4_02:CUBAN4] +~g~Les bombes ont un retardateur de 45 secondes. + +[CUB4_04:CUBAN4] +~r~Tu as mis leur base en alerte! Plus question d'entrer, maintenant! + +[CUB4_07:CUBAN4] +Les solvants sont à l'arrière, amigo. + +[CUB4_08:CUBAN4] +Hola, amigos. + +[CUB4_09:CUBAN4] +Bueno. Putains d'Haïtiens. Muerte. + +[CUB4_10:CUBAN4] +Vamos. + +[CUB4_11:CUBAN4] +Si, vamos. + +[CUB4_12:CUBAN4] +Hé, on a besoin d'une bagnole haïtienne! + +[CUB4_13:CUBAN4] +Bueno, allons trouver ces muchachos! + +[CUB4_14:CUBAN4] +Suis mes amigos. + +[CUB4_15:CUBAN4] +Ok, tu entres... + +[CUB4_16:CUBAN4] +Je vais poser la bombe, couvrez-moi! + +[CUB4_17:CUBAN4] +COURS! + +[CUB4_18:CUBAN4] +Mec, c'est un quartier cool... + +[CUB4_19:CUBAN4] +Cet endroit est une décharge, mec. + +[CUB4_20:CUBAN4] +J'avais une belle copine... Elle vivait dans le coin. + +[CUB4_21:CUBAN4] +Tu sais, ils font de super pizzas, ici! + +[CUB4_22:CUBAN4] +Hé, mec, tu conduis comme une putain de gonzesse! + +[CUB4_23:CUBAN4] +T'es perdu, mec? + +[CUB4_24:CUBAN4] +T'as laissé Pepe derrière, va le récupérer! + +[CUB4_03:CUBAN4] +~g~Ne sors pas de la bagnole avant qu'elle soit garée en sécurité dans l'enceinte. + +[CUB4_26:CUBAN4] +~g~Emmène Pepe, file au nord dans Little Haïti et fauche une bagnole Vaudou. + +[CUB4_27:CUBAN4] +~g~Va retrouver Rico et les autres Cubains. + +[CUB4_28:CUBAN4] +~g~Va rejoindre les Cubains dans l'usine de dope haïtienne. + +[CUB4_29:CUBAN4] +~g~Marche sur chacun des marqueurs pour y poser une bombe. + +[CUB4_30:CUBAN4] +~g~Une fois les trois bombes posées, sors de l'usine avant qu'elle n'explose. + +[CUB4_31:CUBAN4] +~g~Sors de l'usine! + +[CUB4_32:CUBAN4] +~g~Gare la bagnole à l'endroit du bip et descends. + +[CUB4_06:CUBAN4] +~r~Tu ne t'es pas assez éloigné de la base et on a dû annuler l'explosion! + +{=================================== MISSION TABLE FINALE ===================================} + +[FIN_1A:FINALE] +Amène-toi, putain de salopard de traître! + +[FIN_1B:FINALE] +Tu vas crever, Judas de mes deux! + +[FIN_1C:FINALE] +C'est la dernière danse de Lance Vance! + +[FIN_2B:FINALE] +Ah ouais, c'est ce que tu crois? + +[FIN_2C:FINALE] +J'ai dit que je l'avais assez entendu celle-là à l'école! + +[FIN_3:FINALE] +Plus personne pour couvrir ton cul, hein, Tommy? + +[FIN_4:FINALE] +T'es plus que du passé, Tommy, du passé! + +[FIN_5:FINALE] +T'as choisi le mauvais camp, Lance... + +[FIN_11A:FINALE] +Tu m'as volé quinze ans de ma vie, Sonny... + +[FIN_11B:FINALE] +Et je viens te présenter l'addition! + +[FIN_12A:FINALE] +T'as encore rien compris! + +[FIN_12B:FINALE] +Tu m'appartiens, Tommy... + +[FIN_12C:FINALE] +C'est moi qui devait les faire ces quinze années! + +[FIN_13:FINALE] +Attrapez-le, les mecs, il a jamais rien pigé. + +[FIN1_01:FINALE] +Qu'est-ce qui se passe? + +[FIN1_02:FINALE] +Tommy! Ah, bien, bien. Ecoute, écoute... Euh, écoute. + +[FIN1_03:FINALE] +J'aime bien les poissons. Ouais, j'adore le poisson! + +[FIN1_04:FINALE] +Je l'aime à toutes les sauces, dans le bocal ou dans l'assiette, + +[FIN1_05:FINALE] +mais c'est pas parce que je l'aime que je veux dormir avec! + +[FIN1_06:FINALE] +Et maintenant, il y a tes cousins ritals qui s'amènent pour me coller les pieds dans le béton, et je... + +[FIN1_07:FINALE] +La ferme, Ken. Assis. + +[FIN1_08:FINALE] +Lance, bordel, qu'est-ce qui se passe? + +[FIN1_09:FINALE] +C'est tes amis du nord, Tommy... Ils ont pas aimé ce que t'as fait à leur mec. + +[FIN1_10:FINALE] +Ils s'amènent aujourd'hui pour régler cette affaire. + +[FIN1_11:FINALE] +Ils ont mis plus de temps que je pensais... + +[FIN1_12:FINALE] +Les mecs, faut en finir et faire comprendre à tout le monde que désormais, c'est mon business! LE MIEN! + +[FIN1_13:FINALE] +Ken, prend la première production de faux dollars et mets-en pour vingt millions dans des porte-documents. + +[FIN1_14:FINALE] +Lance, tu rassembles tout le monde... + +[FIN2_01:FINALE] +Tommy! + +[FIN2_02:FINALE] +Quoi? T'embrasses pas ton vieux pote? + +[FIN2_03:FINALE] +Ca fait quinze piges que je suis hors-circuit + +[FIN2_04:FINALE] +et je suis un peu rouillé question étiquette. + +[FIN2_05:FINALE] +Toujours la haine, hein, Tommy! + +[FIN2_06:FINALE] +Je te l'avais pas dit que ton sale caractère te causerait des emmerdes? + +[FIN2_07:FINALE] +Il y a vingt millions là-dedans... + +[FIN2_08:FINALE] +Combien ils étaient déjà? Dix, non onze... + +[FIN2_09:FINALE] +C'est pour ça qu'on t'a surnommé le boucher d'Hartwood! Hé hé hé! + +[FIN2_10:FINALE] +Tu m'avais envoyé buter un mec, un seul mec. Mais ils savaient que je venais, Sonny... + +[FIN2_11:FINALE] +Fais gaffe à ce que tu dis... + +[FIN2_12:FINALE] +Quelqu'un pourrait penser que tu veux me coller sur le dos un malheureux concours de circonstances... + +[FIN2_13:FINALE] +Prends simplement ce pognon... + +[FIN2_14:FINALE] +Prendre le putain de fric? + +[FIN2_15:FINALE] +Tu sais, Tommy, j'ai fait ce que je pouvais pour toi, j'ai tiré des ficelles et demandé des services. + +[FIN2_16:FINALE] +J'étais ton pote, Tommy. J'espérais que tu entendrais raison et comprendrais ce qui est bon pour les affaires. + +[FIN2_17:FINALE] +Je t'ai fait confiance, Tommy et je suis déçu... + +[FIN2_18:FINALE] +Mais il y a au moins un mec dans ton organisation de merde qui s'y connaît en business, + +[FIN2_19:FINALE] +Pas vrai, Lance? + +[FIN2_20:FINALE] +Désolé, Tommy, On est à Vice City. C'est les affaires... + +[FIN2_21:FINALE] +Tu nous as vendus... + +[FIN2_22:FINALE] +Non, juste toi, Tommy, juste TOI. + +[FIN2_23:FINALE] +Le vrai pognon est en haut dans le coffre! + +[FIN2_24:FINALE] +Alors, Tommy, c'était quoi le grand plan? + +[FIN2_25:FINALE] +Tu croyais que j'allais simplement prendre les faux biftons? + +[FIN2_26:FINALE] +Sauver la face et m'enfuir la queue entre les jambes? + +[FIN2_27:FINALE] +Non. + +[FIN2_28:FINALE] +Je voulais juste te faire chier avant de te buter. + +[FIN3_01:FINALE] +Tommy? + +[FIN3_02:FINALE] +Oh, mon Dieu, Tommy! Qu'est-ce qui s'est passé? + +[FIN3_03:FINALE] +A ton avis? + +[FIN3_04:FINALE] +On dirait que t'as niqué ton costard! + +[FIN3_05:FINALE] +Sans compter qu'il était chouette, Tommy! Mais qu'est-ce qui s'est passé? + +[FIN3_06:FINALE] +J'ai eu un désaccord avec un partenaire commercial, tu sais comment c'est. + +[FIN3_07:FINALE] +Tommy, moi, quand j'ai un désaccord avec un partenaire commercial, je lui envoie une lettre enflammée... + +[FIN3_08:FINALE] +A la rigueur, je pisse dans sa boite aux lettres... Mais je commence pas la troisième guerre mondiale! + +[FIN3_09:FINALE] +Tu sais, tu devrais peut-être voir mon psy... + +[FIN3_10:FINALE] +Ce sale con de Lance... + +[FIN3_11:FINALE] +Tommy. J'ai jamais pu blairer ce mec, tu sais? + +[FIN3_12:FINALE] +C'est un névrosé dangereux et égocentrique. Un vrai salopard! + +[FIN3_13:FINALE] +Je suis bien content que tu l'aies liquidé! + +[FIN3_14:FINALE] +Je crois pas qu'on doive s'attendre à plus d'emmerdes en provenance du nord... + +[FIN3_15:FINALE] +... Parce qu'il y a plus de nord... + +[FIN3_16:FINALE] +Maintenant, il n'y a plus que le sud... + +[FIN3_17:FINALE] +Attends, est-ce que ça veut dire ce que je crois que ça veut dire, Tommy? + +[FIN3_18:FINALE] +Qu'est-ce que tu crois que ça veut dire? + +[FIN3_19:FINALE] +Qu'on est au commandement... Enfin, que tu es au commandement. Oh, Tommy... + +[FIN3_20:FINALE] +Tu sais, Ken, c'est peut-être le début d'une grande amitié en affaires... + +[FIN3_21:FINALE] +Après tout, t'es un voleur minable un peu magouilleur sur les bords... + +[FIN3_22:FINALE] +Et moi je suis un tueur psychotique doublé d'un dealer... + +[FIN3_23:FINALE] +Ouais. Si c'est pas grandiose! + +[FIN_B1:FINALE] +~g~Va tuer ~y~Vance~g~ le traître. + +[FIN_B2:FINALE] +~g~Trouve ~p~Sonni~g~ et règle-lui son compte pour de bon. + +[FIN_B3:FINALE] +~g~La mafia essaye de voler ton pognon. Défends le coffre. + +[FIN_B4:FINALE] +~g~Tu es presque mort. Va chercher ~w~de quoi te soigner~g~ en bas. + +[FIN_B5:FINALE] +~g~La mafia te vole ton pognon. Défends le ~c~coffre. + +[FIN_B7:FINALE] +~r~La mafia t'a volé tout ton pognon. + +[DEFSAFE:FINALE] +~g~Retourne au coffre et protège-le. + +{=================================== MISSION TABLE FIRETRK ===================================} + +[F_PASS1:FIRETRK] +Feu éteint! + +[F_FAIL2:FIRETRK] +~r~T'arrives trop tard! + +[F_CANC:FIRETRK] +~r~Mission pompier annulée! + +[F_EXTIN:FIRETRK] +INCENDIES : + +[F_START:FIRETRK] +~g~Véhicule en feu repéré dans le secteur ~a~. Va éteindre le feu. + +[SIREN_1:FIRETRK] +Pour déclencher la sirène de ce véhicule, appuie brièvement sur la ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_2:FIRETRK] +Pour déclencher la sirène de ce véhicule, appuie brièvement sur la ~h~~k~~VEHICLE_HORN~~w~. + +[FIREPRO:FIRETRK] +Mission Camion de pompiers niveau 12 réussie. T'es complètement ignifugé maintenant! + +[F_FAIL1:FIRETRK] +Mission Camion de pompiers achevée. + +[F_STAR1:FIRETRK] +~g~Véhicules en flammes dans la zone ~a~. Va éteindre l'incendie. + +[SPRAY_4:FIRETRK] { reVC update } +Utilise la touche ~h~~k~~VEHICLE_FIREWEAPON~ ~w~pour tirer avec le canon à eau et le ~h~~k~~VEHICLE_TURRETLEFT~~w~ et ~h~~k~~VEHICLE_TURRETRIGHT~~w~ pour viser. + +[SPRAY_1:FIRETRK] { reVC update } +Utilise la touche ~h~~k~~VEHICLE_FIREWEAPON~ ~w~pour tirer avec le canon à eau et le ~h~~k~~VEHICLE_TURRETLEFT~~w~ et ~h~~k~~VEHICLE_TURRETRIGHT~~w~ pour viser. + +{=================================== MISSION TABLE GENERA1 ===================================} + +[GEN1_A:GENERA1] +Mr. Vercetti! + +[GEN1_B:GENERA1] +Colonel. + +[GEN1_D:GENERA1] +Non merci. + +[GEN1_E:GENERA1] +Ca me gêne beaucoup d'admettre que la cause d'un de nos problèmes communs semble être un homme en qui j'avais confiance. + +[GEN1_F:GENERA1] +J'ai supporté Gonzalez pendant des années, mais son incompétence a atteint de nouveaux sommets. + +[GEN1_G:GENERA1] +Vous avez le droit de tuer Gonzalez... + +[GEN1_H:GENERA1] +C'est lui qui m'a entubé? La seule chose qui m'intéresse, c'est le pognon! + +[GEN1_I:GENERA1] +Je vous récompenserai pour ce service et nous chercherons ensuite votre argent ensemble. + +[GEN1_J:GENERA1] +Il sera dans son appartement, probablement à moitié saoul. Servez-vous de ça... + +[GEN1_06:GENERA1] +Hé! Il a une tronçonneuse! + +[GEN1_07:GENERA1] +T'approche pas de moi, connard! + +[GEN1_08:GENERA1] +Oh, mon Dieu, j'ai gâché ma vie et j'suis devenu un laidron! + +[GEN1_10:GENERA1] +Je vais t'faire fermer ta grande gueule une bonne fois pour toutes. + +[GEN1_11:GENERA1] +Essaye pas de t'enfuir, bâtard! + +[GEN1_12:GENERA1] +Laisse-toi faire et ça sera rapide! + +[GEN1_13:GENERA1] +Arrête de gueuler, ça intéresse personne! + +[GEN1_C:GENERA1] +Merci d'être venu. Asseyez-vous. Du homard? + +[GEN1_05:GENERA1] +~g~Va tuer Gonzales! + +[GEN1_09:GENERA1] +Je t'offre le double, Tommy, LE DOUBLE! + +[GEN1_18:GENERA1] +~r~Gonzalez a réussi à atteindre le poste de police vivant! + +[GEN1_19:GENERA1] +~g~Les flics de Vice City te cherchent! + +[GEN1_20:GENERA1] +~g~Monte dans une bagnole. + +[GEN1_21:GENERA1] +~g~Va au~h~ Pay 'N' Spray~g~ à~h~ Vice Point~g~. + +[GEN1_22:GENERA1] +~g~Conduis ton véhicule à l'intérieur de l'atelier de peinture pour perdre ton ~h~indice de recherche~g~, ~h~réparer ~g~et ~h~repeindre ~g~ton véhicule. Coût : ~h~$100~g~. Cette coup-ci, c'est gratuit. + +[GEN1_01:GENERA1] +Quand tu cours, maintiens la~h~ ~k~~PED_FIREWEAPON~~w~ enfoncée pour préparer une attaque au corps à corps. + +[GEN1_02:GENERA1] +Quand tu cours, maintiens la~h~ ~k~~PED_FIREWEAPON~~w~ enfoncée pour préparer une attaque au corps à corps. + +[GEN1_03:GENERA1] +Quand tu cours, maintiens la~h~ ~k~~PED_FIREWEAPON~~w~ enfoncée pour préparer une attaque au corps à corps. + +[GEN1_14:GENERA1] +Relâche la~h~ ~k~~PED_FIREWEAPON~~w~ pour attaquer. + +[GEN1_15:GENERA1] +Relâche la~h~ ~k~~PED_FIREWEAPON~~w~ pour attaquer. + +[GEN1_16:GENERA1] +Relâche la~h~ ~k~~PED_FIREWEAPON~~w~ pour attaquer. + +[GEN1_23:GENERA1] +~g~Franchis à nouveau les portes pour revenir au rez-de-chaussée. + +{=================================== MISSION TABLE GENERA2 ===================================} + +[COL2_A:GENERA2] +Tommy! Venez donc vous joindre à moi! + +[COL2_B:GENERA2] +Ca a l'air bon, hein! Un peu de museau de tapir? + +[COL2_C:GENERA2] +Euh... Non. Non, merci. + +[COL2_D:GENERA2] +Tommy, vous êtes comme le vent de la pampa qui m'a purifié de la puanteur de la corruption. + +[COL2_E:GENERA2] +Je dois cependant montrer que je pleure sa mort et continuer les affaires comme d'habitude. + +[COL2_F:GENERA2] +Ca me rapproche pas beaucoup de mon pognon... + +[COL2_G:GENERA2] +Tommy, mon cher, vous n'êtes pas à Liberty. Ici, ça se passe autrement. + +[COL2_H:GENERA2] +Je vais continuer mon enquête, mais en attendant, j'ai une bonne affaire à conclure. + +[COL2_I:GENERA2] +Un service à rendre à un ami, Cortez. + +[COL2_J:GENERA2] +Vous êtes un ami, Tommy. Je savais que vous ne me laisseriez pas tomber. + +[COL2_K:GENERA2] +Il faut que vous rencontriez un coursier qui m'a obtenu une technologie intéressante... + +[COL2_1:GENERA2] +Ah, la pluie est très dense en cette saison... + +[COL2_2:GENERA2] +Quoi? + +[COL2_3:GENERA2] +Ah, comment? + +[COL2_4:GENERA2] +Ecoute, Cortez m'envoie. File-moi ces putain de processeurs. + +[COL2_5:GENERA2] +Oh... D'accord. + +[COL2_B1:GENERA2] +~g~Retrouve le coursier au centre commercial. + +[COL2_B2:GENERA2] +~g~Le coursier se barre avec les processeurs de guidage. Ne le laisse pas filer! + +[COL2_B3:GENERA2] +~g~Ramène les processeurs de guidage au Colonel. + +[COL2_F1:GENERA2] +~r~T'as buté le contact! + +[COL2_F2:GENERA2] +~r~Le coursier est mort. Récupère les processeurs. + +[COL2_6A:GENERA2] +Arrête, espèce de porc impérialiste d'américain! Ceci est la propriété du gouvernement français! Rendez-le! + +[BLIPHLP:GENERA2] +Le bip sur le radar est un triangle pointant vers le haut, ce qui signifie que la cible est plus élevée que toi. + +[COL2_F3:GENERA2] +~r~Les processeurs de guidage sont au fond de la mer. + +[COL2_F4:GENERA2] +~r~Le coursier s'est fait la malle! Tu n'as pas récupéré les processeurs de guidage! + +{=================================== MISSION TABLE GENERA3 ===================================} + +[GEN3_49:GENERA3] +Santé de Lance: + +[GEN3_A:GENERA3] +Merci d'être venu, Thomas. + +[GEN3_B:GENERA3] +Pardonnez-moi d'en venir directement au fait... + +[GEN3_C:GENERA3] +Diaz m'a demandé de superviser une petite transaction... + +[GEN3_D:GENERA3] +Espérons que ça se passera mieux que l'autre fois, n'est-ce pas? + +[GEN3_E:GENERA3] +C'est pour ça que j'ai pensé à vous, amigo. + +[GEN3_F:GENERA3] +J'ai déposé une arme dans le parking à niveaux. + +[GEN3_G:GENERA3] +Récupèrez-le et allez surveiller les hommes de Diaz au rendez-vous. + +[GEN3_H:GENERA3] +Gracias, amigo... + +[GEN3_1:GENERA3] +On s'accapare toute l'action, à ce que je vois... + +[GEN3_2:GENERA3] +Ecoute, tu veux pas faire autre chose que de me suivre partout comme mon ombre? Pourquoi tu ne viens pas pour me montrer à quoi t'es bon? + +[GEN3_3:GENERA3] +Pourquoi pas... Au fait, moi c'est Lance. + +[GEN3_5:GENERA3] +Tu dois être le nouveau porte-flingues de Cortez. + +[GEN3_6:GENERA3] +Jusqu'à ce que de meilleures opportunités se présentent... + +[GEN3_7:GENERA3] +Ils seront là d'une minute à l'autre. On ferait mieux de se trouver une bonne planque... + +[GEN3_8:GENERA3] +OK! Je vais sur le balcon et toi tu t'mets sur le toit de l'autre côté de la cour. + +[GEN3_9:GENERA3] +J'suis vivant! Têtes de noeuds! Et ça c'est grâce à toi! C'est quoi ton nom? + +[GEN3_10:GENERA3] +Tommy. + +[GEN3_11:GENERA3] +On se reverra bientôt, je pense! + +[GEN3_12:GENERA3] +Bon, où est passé Lance? Merde... + +[GEN3_14:GENERA3] +Tommy! J'ai besoin d'un coup de main! + +[GEN3_15:GENERA3] +T'en fais pas, je te couvre! + +[GEN3_16:GENERA3] +Les hommes de Diaz se font massacrer! + +[GEN3_19:GENERA3] +~g~Les haïtiens! Ils veulent foutre le deal en l'air! Protège Diaz! + +[GEN3_20:GENERA3] +~g~Va au parking à niveaux où tu peux récupérer le flingue que le Colonel a laissé pour toi. + +[GEN3_22:GENERA3] +Santé de Diaz : + +[GEN3_23:GENERA3] +~g~T'as laissé Lance derrière! Va le chercher! + +[GEN3_25:GENERA3] +~r~Lance est mort! + +[GEN3_28:GENERA3] +~g~Ramène la serviette à Diaz. + +[GEN3_29:GENERA3] +~g~Récupère la serviette et ramène-la à Diaz. + +[GEN3_30:GENERA3] +~r~Il s'est barré avec le pognon! Diaz va te couper les couilles! + +[GEN3_33:GENERA3] +~r~Tu es supposé surveiller Diaz et ses hommes, pas les flinguer! + +[GEN3_34:GENERA3] +~r~Ils ne vont pas pouvoir faire affaire si tu descends les cubains! + +[GEN3_35:GENERA3] +~g~Il a volé le pognon de Diaz! + +[GEN3_36:GENERA3] +~g~Saute sur la moto, rattrape-le et récupère le pognon de Diaz! + +[GEN3_37:GENERA3] +~g~Voilà les Cubains. Surveille le deal en veillant à la sécurité de Diaz et de Lance. + +[GEN3_38:GENERA3] +~r~Diaz est mort! Tu devais le protéger! + +[GEN3_39:GENERA3] +~g~Prends position en haut des escaliers. + +[GEN3_44:GENERA3] +~g~Va avec Lance au rendez-vous et protège Diaz. + +[GEN3_40:GENERA3] { reVC update } +Pour ~h~tirer droit devant ~w~lorsque tu es en ~h~moto~w~, appuie sur la ~h~~k~~VEHICLE_FIREWEAPON~. + +[GEN3_41:GENERA3] { reVC update } +Pour ~h~tirer droit devant ~w~lorsque tu es en ~h~moto~w~, appuie sur la ~h~~k~~VEHICLE_FIREWEAPON~. + +[GEN3_46:GENERA3] +Chiiiier! + +[GEN3_47:GENERA3] +Tommy! + +[GEN3_48:GENERA3] +Merde! + +[GEN3_50:GENERA3] +~r~Tu as paumé le fric de Diaz! La prochaine fois, essaie de ne pas cramer le blé! + +[GEN3_51:GENERA3] +Encore des Haïtiens dans un camion merdique! + +[GEN3_54:GENERA3] +s Restez pas plantés là, bande d'abrutis! Butez-moi ce Haïtien de merde! + +[GEN3_55:GENERA3] +Tommy! Je reste là et je m'occupe de Diaz! + +[GEN3_18:GENERA3] +~g~Voilà les Cubains. Reste près de Diaz. Surveille les opérations tout en t'assurant que Diaz et Lance sont en sécurité. + +[GEN3_56:GENERA3] +~r~Diaz a été pris dans une embuscade et s'est fait tuer! La prochaine fois, ne le quitte pas des yeux! + +[GEN3_57:GENERA3] +Le Kruger est un fusil d'assaut permettant de viser manuellement à la 1ere personne. + +[GEN3_58:GENERA3] +Appuie sur la touche~h~ R1~w~ et maintiens-la enfoncée pour ~h~viser~w~ avec un fusil d'assaut. + +[GEN3_59:GENERA3] +Appuie sur la touche~h~ L1~w~ et maintiens-la enfoncée pour ~h~viser~w~ avec un fusil d'assaut. + +[GEN3_60:GENERA3] +Appuie sur la touche ~h~~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ avec un fusil d'assaut. + +[GEN3_61:GENERA3] +Appuie sur la touche ~h~~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ avec un fusil d'assaut. + +[GEN3_62:GENERA3] +Appuie sur la touche ~h~~k~~PED_FIREWEAPON~~w~ pour ~h~tirer~w~ avec un fusil d'assaut. + +[GEN3_63:GENERA3] +Tu peux fusiller des types de côté sur une ~h~ moto ~w~, mais également ~h~tirer droit devant~w~. + +[GEN3_64:GENERA3] { reVC update } +Appuie sur la touche ~h~~k~~VEHICLE_FIREWEAPON~~w~ pour tirer droit devant toi sur une moto. + +[GEN3_65:GENERA3] { reVC update } +Appuie sur la touche ~h~~k~~VEHICLE_FIREWEAPON~~w~ pour tirer droit devant toi sur une moto. + +[GEN3_66:GENERA3] { reVC update } +Appuie sur la touche ~h~~k~~VEHICLE_FIREWEAPON~~w~ pour tirer droit devant toi sur une moto. + +[GEN3_67:GENERA3] +Il te faut une mitrailleuse pour tirer droit devant toi sur une moto. + +[GEN3_53:GENERA3] +Mon argent! + +[GEN3_52:GENERA3] +Ces Haitiens pensent qu'ils peuvent se payer Ricardo Diaz! + +{=================================== MISSION TABLE GENERA4 ===================================} + +[DETON:GENERA4] +DETONATION : + +[COL4_3:GENERA4] +CONVOI, HALTE! + +[COL4_6:GENERA4] +ON EST SOUS LE FEU ENNEMI! + +[COL4_7:GENERA4] +Civil, éloigne-toi du char! + +[COL4_8:GENERA4] +J'ai dit, dégage! IMMEDIATEMENT! + +[COL4_9:GENERA4] +POSITIONS DEFENSIVES! + +[COL4_11:GENERA4] +Dégagez ce civil de notre chemin, soldat! -Chef, oui, chef! + +[COL4_12:GENERA4] +Un civil dans le char! ARRETEZ-LE! + +[COL4_13:GENERA4] +Ceci est un convoi militaire, ne bloquez pas la route! + +[COL4_14:GENERA4] +Descendez-le, soldat. + +[COL4_15:GENERA4] +Dégagez ce véhicule civil de la route! -Chef, on déplace le véhicule, chef! + +[COL4_17:GENERA4] +Ok, peloton, on dégage! + +[COL4_18:GENERA4] +Y'a quelqu'un dans le char, chef! + +[COL4_19:GENERA4] +Trouvez-moi quelques beignets, soldat! -Chef, oui, chef! + +[COL4_20:GENERA4] +Cible verrouillée, chef! + +[COL4_21:GENERA4] +TIREUR D'ELITE! + +[COL4_22:GENERA4] +Je me tire d'ici. + +[COL4_23:GENERA4] +Objectif atteint! Peloton, rompez! -Allons manger quelques beignets! + +[COL4_24:GENERA4] +Protocole de sécurité Delta India Echo déclenché! Autodestruction du véhicule initialisée! + +[COL4_26:GENERA4] +Prépare-toi à crever, ordure communiste! + +[COL4_B2:GENERA4] +~r~Le char est parvenu sans problème à destination! + +[COL4_B5:GENERA4] +~r~Le char est détruit! + +[COL4_01:GENERA4] +Diaz était très content et il veut vous voir. + +[COL4_02:GENERA4] +Et c'est une bonne chose? + +[COL4_03:GENERA4] +Evidemment! Même si je commence à penser que Diaz est responsable de notre malheureuse perte... + +[COL4_04:GENERA4] +Qu'est-ce qui vous fait dire ça? + +[COL4_05:GENERA4] +On n'accuse pas comme ça un homme comme Diaz... Je ne faisais que penser à haute voix... + +[COL4_06:GENERA4] +Quoi qu'il en soit, j'ai une proposition qui devrait vous intéresser... + +[COL4_07:GENERA4] +Je n'ai plus le temps de faire des commissions, Cortez. + +[COL4_08:GENERA4] +J'aurais pensé qu'un homme avec des dettes aussi délicates serait ouvert à toute opportunité. Mais écoutez quand même, Tommy. + +[COL4_09:GENERA4] +Allez-y... + +[COL410:GENERA4] +J'ai un acheteur pour une pièce d'artillerie qui est emmenée à travers la ville. Récupèrez-la pour moi. + +[COL411:GENERA4] +Et une fois que vous l'aurez, je veux que vous m'appeliez de suite, et... + +[COL4_B4:GENERA4] +~g~Le char est verrouillé. Trouve le moyen d'en faire sortir les occupants. + +[COL4_1:GENERA4] +Qu'est-ce qui se passe avec l'artilleur? - Aucune idée, chef! + +[COL4_4:GENERA4] +Montez voir, soldat! - Chef, oui, chef! + +[COL4_B1:GENERA4] +~g~Va faucher la pièce d'artillerie escortée à travers la ville. + +[COL4_B3:GENERA4] +~g~Amène le char dans le garage du colonel avant qu'il ne s'autodétruise. + +[COL4_B6:GENERA4] +~g~Trouve un moyen de piquer le char! + +[COL4_B7:GENERA4] +~g~Conduis le char dans le garage. + +[COL4_B8:GENERA4] +~g~Sors du char et du garage. + +{=================================== MISSION TABLE GENERA5 ===================================} + +[COL5A_1:GENERA5] +Les circonstances me forcent à un départ en hâte, amigo. + +[COL5A_2:GENERA5] +Quel est le problème? + +[COL5A_3:GENERA5] +Les français veulent récupérer leur technologie de missile et après le dernier incident, + +[COL5A_4:GENERA5] +je crois que l'heure est venue de me mettre à l'abri. + +[COL5A_5:GENERA5] +Ca serait pas plus sûr de partir en avion ? + +[COL5A_6:GENERA5] +Je serais mort avant d'avoir atteint l'enregistrement. D'ailleurs, il faut que je fasse sortir la marchandise du pays. + +[COL5A_7:GENERA5] +Besoin d'un autre flingue? + +[COL5A_8:GENERA5] +Vous, amigo, vous en valez dix... hahahaha. + +[COL5B_1:GENERA5] +Tommy, vous m'avez bien protégé et servi. + +[COL5B_2:GENERA5] +Mais maintenant, vous devez nous laisser avant qu'on arrive en pleine mer. + +[COL5B_4:GENERA5] +Merci, Colonel. + +[COL5B_5:GENERA5] +Une dernière chose, pendant que je suis parti, pouvez-vous surveiller Mercedes pour moi? + +[COL5B_6:GENERA5] +Je crois qu'elle peut se surveiller tout seule, mais soyez tranquille, je garderai l'oeil ouvert. + +[COL5B_7:GENERA5] +Merci, mon ami. Jusqu'à mon retour. + +[COL5B_8:GENERA5] +Adios, amigo. + +[COL5_7:GENERA5] +Arrêtez de me tirer dessus! + +[COL5_9:GENERA5] +Tommy, empêchez-les de me tirer dessus! + +[COL5_10:GENERA5] +J'ai l'immunité diplomatique! + +[COL5_11:GENERA5] +Ne tirez pas, je suis un Colonel! + +[COL5_12:GENERA5] +Tommy, descendez-les, mon pays vous en sera reconnaissant. + +[COL5_13:GENERA5] +Tommy, on est submergés par les Français! + +[COL5_14:GENERA5] +Tommy, il y a des Français partout! Je déteste ça! + +[COL5_15:GENERA5] +Tommy, comment ça va? + +[COL5_16:GENERA5] +Ca, c'est pour Piaf et Gainsbourg et votre saloperie de pain français! + +[COL5_1:GENERA5] +A bâbord! A bâbord! + +[COL5_2:GENERA5] +Ils attaquent à tribord! + +[COL5_3:GENERA5] +Le pont devant! + +[COL5_4:GENERA5] +Ils ont un hélico! + +[COL5_B1:GENERA5] +~g~Protège le colonel et son yacht à tout prix. + +[COL5_B2:GENERA5] +~g~Passe devant et dégage la route pour le yacht du colonel. + +[COL5_B3:GENERA5] +~r~Le colonel est mort! + +[COL5_B4:GENERA5] +~g~Abats l'hélicoptère vous tire dessus. + +[COL5B_3:GENERA5] +Je vous laisse ma vedette personnelle. Gardez-la en témoignage de ma gratitude! + +[COL5_B5:GENERA5] +~g~Descends les hélicos et protège bien le yacht. + +[COL5_B6:GENERA5] +~g~Tu n'as plus de munitions, va en chercher d'autres dans les escaliers du pont supérieur. + +[COL5_B7:GENERA5] +~g~Ta santé diminue. Régénère-la dans les escaliers du pont supérieur. + +{=================================== MISSION TABLE HAIT1 ===================================} + +[HAM1_A:HAIT1] +Bonjour? Y'a quelqu'un? + +[HAM1_B:HAIT1] +Entre, mon petit, et repose ton esprit. + +[HAM1_C:HAIT1] +Tu dois être le grand méchant homme dont m'a parlé mon grand-père. + +[HAM1_D:HAIT1] +Il me dit des choses sur toi, tu sais, quand il me rend visite + +[HAM1_E:HAIT1] +et aussi des choses sur les autres qui t'attendent. + +[HAM1_F:HAIT1] +Bon, nous sommes tous morts depuis longtemps, mais toi, + +[HAM1_G:HAIT1] +je ne voudrais pas être à ta place, hé hé hé! + +[HAM1_H:HAIT1] +J'ai reçu un message. De venir ici. + +[HAM1_I:HAIT1] +Tu les entends? + +[HAM1_J:HAIT1] +Ils crient ton nom, mon garçon, ils doivent vraiment te vouloir, tu ne crois pas? + +[HAM1_K:HAIT1] +Maintenant, si tu rends service à tata Poulet, peut-être qu'elle peut t'aider. + +[HAM1_L:HAIT1] +Peut-être qu'elle peut te donner un petit grigri après tout ça. + +[HAM1_M:HAIT1] +Te donner de la magie pour donner le mauvais oeil au policier, mmmm? + +[HAM1_N:HAIT1] +Ecoute, tout ça est très, hum... me donner quoi? + +[HAM1_O:HAIT1] +Je... Je crois que je me suis planté d'adresse... + +[HAM1_P:HAIT1] +Fais-ça pour moi, Tommy... + +[HAM1_Q:HAIT1] +Les Cubains, ces vilains coqs arrogants, hum, + +[HAM1_R:HAIT1] +ont fait des ennuis à mes adorables garçons haïtiens. + +[HAM1_S:HAIT1] +Et maintenant, ils ont dit au policier où je cachais mes poudres! + +[HAM1_T:HAIT1] +Ils pensent que c'est des drogues, les idiots! + +[HAM1_U:HAIT1] +Alors, sois un gentil garçon, Tommy, et va chercher les poudres de tata Poulet. + +[HAM1_V:HAIT1] +Oui, d'accord, d'accord. + +[HAM1_1:HAIT1] +~g~Les policiers se rapprochent des poudres! Va les récupérer avant eux! + +[HAM1_2:HAIT1] +~r~Les policiers ont trouvé la cachette les premiers! + +[HAM1_3:HAIT1] +~g~Ramène tout ça à la planque! + +[HAM1_4:HAIT1] +~g~Bien! Maintenant, la suivante! + +[HAM1_6:HAIT1] +~r~La cachette a été détruite, imbécile! + +[HAM1_7:HAIT1] +~g~Les policiers ont les poudres! Récupère-les avant qu'ils filent! + +[HAM1_8:HAIT1] +~g~Les flics sont en route pour récupérer les poudres, magne-toi! + +[HAT_1A:HAIT1] +~g~Bouge pas d'un poil, connard! + +{=================================== MISSION TABLE HAIT2 ===================================} + +[HAT2_B1:HAIT2] +~g~Va à la camionnette contenant les bombes volantes. + +[HAT2_B2:HAIT2] +Tue les Cubains... + +[HAT2_B4:HAIT2] +... Et détruis leurs bateaux! + +[HAT2_B5:HAIT2] +~g~Les Cubains essayent de s'enfuir! Ne les laisse pas faire! + +[HAT2_B6:HAIT2] +~r~L'avion télécommandé est hors de portée! + +[HAT2_B7:HAIT2] +~g~Un des Cubains s'échappe en voiture. Ne laisse aucun témoin! + +[HAT2_B8:HAIT2] +~r~Tu n'as plus d'avions télécommandés! + +[HAT2_B9:HAIT2] +Avions télécommandés : + +[HAT2_1:HAIT2] +Oh, désolé, j'ai dû me tromper d'adresse... + +[HAT2_2:HAIT2] +Eh bien, entre donc te détendre en buvant un peu de thé. + +[HAT2_3:HAIT2] +Tu as quelque chose pour moi, Tommy? + +[HAT2_4:HAIT2] +Ouais... + +[HAT2_5:HAIT2] +Cet endroit me semble familier. Un vague souvenir d'enfance, un air de déjà vu... + +[HAT2_6:HAIT2] +Tommy, je voudrais te demander de faire une petite commission pour moi... + +[HAT2_7:HAIT2] +Tu me rappelles quelqu'un que... + +[HAT2_8:HAIT2] +Les Cubains ont des bateaux très rapides qui leur servent à traverser les mers avec de la drogue. + +[HAT2_9:HAIT2] +C'est leur gagne-pain. + +[HAT2_10:HAIT2] +Mon neveu a fabriqué quelques bombes volantes pour nous en débarrasser. + +[HAT2_11:HAIT2] +Transforme leurs bateaux en allumettes! + +[HAT2_12:HAIT2] +Bon, merci pour le thé. + +[HAT2_B3:HAIT2] { reVC update } +Appuie sur la touche ~h~~k~~VEHICLE_FIREWEAPON~~w~ pour larguer une bombe ou sur ~h~~k~~VEHICLE_ENTER_EXIT~~w~ pour annuler. + +{=================================== MISSION TABLE HAIT3 ===================================} + +[HAM3_A:HAIT3] +Salut, euh, je... Je cherche quelqu'un dans le coin... + +[HAM3_B:HAIT3] +Tu as l'air d'avoir faim, Tommy. + +[HAM3_C:HAIT3] +On se connait? + +[HAM3_D:HAIT3] +Chut! + +[HAM3_E:HAIT3] +Encore une chose et je te laisse partir, Tommy. + +[HAM3_F:HAIT3] +Mes garçons sont en guerre contre les Cubains. + +[HAM3_G:HAIT3] +Mais pas de revolvers. + +[HAM3_H:HAIT3] +Hum, mais les Cubains ont une suprise qui les attend! + +[HAM3_I:HAIT3] +Pendant qu'ils se battent dans les rues, prends ce fusil et tue-les dans la confusion! + +[HAM3_J:HAIT3] +Personne ne te verra, personne ne t'entendra! + +[HAM3_K:HAIT3] +Si tu fais ça pour tata Poulet, Tommy, tu ne seras plus lié aux cordons de mon tablier! + +[HAM3_1:HAIT3] +~g~On doit gagner cette bataille! Si tous les Haïtiens meurent, c'est foutu! + +[HAM3_3:HAIT3] +~g~Les Cubains risquent de tricher, alors fais attention. + +[HAM3_4:HAIT3] +~r~Tu as été repéré! C'est foutu! + +[HAM3_5:HAIT3] +~g~Tu dois tuer les cubains de loin. Ne te fais pas voir. + +[HAM3_8:HAIT3] +~g~Les Haïtiens se font descendre! Vise mieux! + +[HAM3_7:HAIT3] +~g~Fais gaffe! Les Cubains ont amené des renforts. Liquide-les tous! + +[HAM3_2:HAIT3] +~r~Les Haïtiens sont morts! + +[HAM3_L:HAIT3] +Tata... + +{=================================== MISSION TABLE HOTEL ===================================} + +[INTB_A:HOTEL] +Tommy! Ca fait une paye! + +[INTB_B:HOTEL] +Salut Sonny... + +[INTB_C:HOTEL] +Je sais, je sais, t'es submergé par l'émotion, hein? + +[INTB_D:HOTEL] +Quinze piges et c'est comme si c'était hier! + +[INTB_E:HOTEL] +C'est une façon de voir les choses... + +[INTB_F:HOTEL] +Hé, bosser pour la famille, tu sais, c'est pas de la tarte, + +[INTB_G:HOTEL] +mais la famille veille toujours sur les siens, tu comprends? + +[INTB_H:HOTEL] +Bon, raconte un peu comment l'affaire s'est passée? Ca y est, t'es blindé? + +[INTB_I:HOTEL] +Ecoute, Sonny, on s'est fait piéger. C'était une embuscade. Et ils ont buté Harry et Lee. + +[INTB_J:HOTEL] +J'espère pour toi que tu rigoles, Tommy... Et t'as plutôt intérêt à toujours avoir le fric... + +[INTB_K:HOTEL] +... Non... Sonny... J'ai plus le fric... + +[INTB_L:HOTEL] +C'était mon fric, Tommy! MON FRIC!!! + +[INTB_M:HOTEL] +Vaudrait mieux pas que t'essayes de me baiser Tommy, parce que je suis pas le genre à aimer ça... + +[INTB_N:HOTEL] +Attend Sonny... + +[INTB_O:HOTEL] +Je te donne ma parole d'honneur que je vais récupérer ton fric et la dope. + +[INTB_P:HOTEL] +Et en prime, je t'enverrai les couilles de ceux qui ont fait ça! + +[INTB_Q:HOTEL] +Ca, je le sais déjà... Tommy, t'es pas con, mais je te préviens, moi non plus... + +[INTB_R:HOTEL] +Et si c'était pas toi, tu serais déjà mort... + +[INTB_S:HOTEL] +Mais j'ai un faible pour toi et en souvenir du passé, je vais te laisser t'en charger. + +[INTB_T:HOTEL] +T'en fais pas, Sonny, t'as ma parole! + +[INTB_U:HOTEL] +Je te tiens au courant, ciao. + +{=================================== MISSION TABLE ICECRE1 ===================================} + +[ICC1_4:ICECRE1] +~g~Il n'y a aucun client dans ce secteur. Essaye ailleurs. + +[ICC1_5:ICECRE1] +Deals effectués : + +[ICC1_7:ICECRE1] +~g~Tu reçois de l'argent pour chaque transaction faite, mais plus tu fais d'affaires, plus tu attires l'attention de la police. + +[ICC1_8:ICECRE1] +~g~Pour effectuer une transaction, ~h~gare ta camionnette ~g~et appuie sur la ~h~~k~~VEHICLE_HORN~ ~g~pour jouer le jingle et attirer les clients. + +[ICC1_9:ICECRE1] +~g~Les gangs locaux n'apprécient pas qu'on fasse des affaires sur leur territoire. Attends-toi à des représailles. + +[ICC1_10:ICECRE1] +~g~Tu as fait ~1~ deals! + +[ICC1_11:ICECRE1] +~g~Tu as fait ~1~ deals! + +[ICC1_13:ICECRE1] +~r~T'as fait aucun deal! + +[ICC1_14:ICECRE1] +USINE DE CREME GLACEE OK + +[ICC1_15:ICECRE1] +~g~L'usine de crème glacée génère dorénavant un revenu de $~1~ maximum. Pense à récupérer le fric régulièrement. + +[ICC1_16:ICECRE1] +~g~Utilise la camionnette de Mr Whoopee pour distribuer les produits Cherry Poppers dans Vice City. + +[ICE_AT1:ICECRE1] +USINE DE CREME GLACEE OK + +[ICE_AT2:ICECRE1] +~g~L'usine Cherry Popper génère dorénavant un revenu de $~1~ maximum. Pense à récupérer le fric régulièrement. + +[ICC1_17:ICECRE1] +Mission Distribution terminée + +[ICC1_18:ICECRE1] +Total des ventes de glaces : $~1~ + +[ICC1_19:ICECRE1] +Total de deals réalisés : ~1~ + +{=================================== MISSION TABLE ICECUT ===================================} + +[ICC1_A:ICECUT] +T'es qui toi? + +[ICC1_B:ICECUT] +Le nouveau proprio. + +[ICC1_C:ICECUT] +Est-ce que t'as déjà été enfant? + +[ICC1_D:ICECUT] +Mais de quoi tu parles? + +[ICC1_E:ICECUT] +As-tu été un gosse? + +[ICC1_F:ICECUT] +Ouais! Vas-y, c'est quoi ton problème? + +[ICC1_G:ICECUT] +Je le savais. Un chiard. + +[ICC1_H:ICECUT] +Un putain de gosse pourri gâté, un chialeur, un emmerdeur, un bébé à sa maman! + +[ICC1_I:ICECUT] +Un bébé... Un horrible et dégoûtant petit morveux. Ouin ouin. + +[ICC1_J:ICECUT] +Ta maman t'aime pas. Petite merde! + +[ICC1_K:ICECUT] +Hé! Calme-toi. + +[ICC1_L:ICECUT] +Je HAIS les mouflets et les mômes. + +[ICC1_M:ICECUT] +Ce sont des putains de pourris gâtés, de chialeurs, d'emmerdeurs... + +[ICC1_N:ICECUT] +Ca suffit maintenant! + +[ICC1_O:ICECUT] +C'est quoi ton problème? + +[ICC1_P:ICECUT] +Tu confectionnes des glaces, non? C'est un truc pour les gosses. + +[ICC1_Q:ICECUT] +T'es quoi comme genre de cinglé, toi? + +[ICC1_R:ICECUT] +Que je comprenne... A quoi ça sert de rendre les gosses heureux si tu les déteste? + +[ICC1_S:ICECUT] +Espèce d'abruti, petit curieux, fouinard- + +[ICC1_T:ICECUT] +La ferme! + +[ICC1_U:ICECUT] +-de merde! + +[ICC1_V:ICECUT] +Les glaces c'est une couverture. + +[ICC1_W:ICECUT] +On fait aussi dans autre chose que les produits laitiers. + +[ICC1_X:ICECUT] +Et si je vois un gosse, j'en fait mon affaire. + +[ICC1_Y:ICECUT] +N'est-ce pas, gamin? Ouais, c'est bien ça. Ta maman t'aime pas. + +[ICC1_Z:ICECUT] +Elle te DETESTE! + +[ICC1_ZA:ICECUT] +PROPRIETE ACQUISE! + +{=================================== MISSION TABLE INTRO ===================================} + +[INT1_A:INTRO] +Tommy Vercetti... Oh, merde! + +[INT1_B:INTRO] +J'pensais pas qu'ils le laisseraient sortir un jour... + +[INT1_C:INTRO] +Il a gardé un profil bas, pour se faire oublier... + +[INT1_D:INTRO] +Mais les gens vont vite retrouver la mémoire + +[INT1_E:INTRO] +quand ils vont le voir débouler dans les rues du voisinage. + +[INT1_F:INTRO] +Et ça va pas être bon pour les affaires... + +[INT1_G:INTRO] +Et alors, qu'est-ce qu'on va faire, Sonny? + +[INT1_H:INTRO] +On le traite comme un vieil ami et on lui trouve de quoi s'occuper en-dehors de la ville. OK? + +[INT1_I:INTRO] +On parlait justement de s'étendre vers le sud... Pas vrai? + +[INT1_J:INTRO] +Vice City, c'est du 24 carats, maintenant. + +[INT1_K:INTRO] +Les colombiens, les mexicains... Putain, + +[INT1_L:INTRO] +même les réfugiés cubains se taillent une belle part du gâteau! + +[INT1_M:INTRO] +Mais ils font tous dans la dope, Sonny! + +[INT1_N:INTRO] +Aucune famille ne touche à cette merde! + +[INT1_O:INTRO] +Les temps changent... + +[INT1_P:INTRO] +Les familles ne peuvent pas rester les bras croisés pendant que nos adversaires ramassent le jackpot. + +[INT1_Q:INTRO] +Alors, on envoie un mec là-bas faire le sale boulot + +[INT1_R:INTRO] +et pour nous en tailler une bonne part. OK? + +[INT1_S:INTRO] +C'est qui, notre contact là-bas? + +[INT1_T:INTRO] +Ken Rosenberg, le con d'avocat... + +[INT1_U:INTRO] +Et comment il va faire pour tenir Vercetti en laisse? + +[INT1_V:INTRO] +Il aura pas besoin de faire ça. + +[INT1_W:INTRO] +On le lâche juste dans Vice City + +[INT1_X:INTRO] +et on lui refile un peu de cash pour commencer. OK? + +[INT1_Y:INTRO] +Donne-lui quelques mois... + +[INT1_Z:INTRO] +Et puis, on descendra + +[INT1_A1:INTRO] +lui rendre une petite visite, tu saisis? + +[INT1_A2:INTRO] +Histoire de voir comment il se débrouille... + +[INT2_A:INTRO] +Hey, salut les gars, c'est moi, Ken Rosenberg! Hé ! Super! + +[INT2_B:INTRO] +Bon, euh, je vais vous conduire au rendez-vous, ok? + +[INT2_C:INTRO] +J'ai parlé avec les fournisseurs, ils sont très, hé hé, + +[INT2_D:INTRO] +très impatients de commencer cette affaire, et... euh, + +[INT2_E:INTRO] +si tout se passe sans accroc, on devrait... euh, + +[INT2_F:INTRO] +s'en mettre plein les poches, ce qui est vraiment, vous savez... + +[INT2_G:INTRO] +une bonne chose... + +[INT2_H:INTRO] +Ok. Bon, ils sont frères, ok. + +[INT2_I:INTRO] +Il y en a un qui fait marcher... euh, l'affaire + +[INT2_J:INTRO] +et l'autre qui s'occupe du transport en hélicoptère. + +[INT2_K:INTRO] +Maintenant, ils opèrent du Mexique, + +[INT3_A:INTRO] +Ok, c'est eux, dans l'hélico. + +[INT3_B:INTRO] +Bon, voilà le deal. + +[INT3_C:INTRO] +Ils veulent un échange en terrain découvert. + +[INT3_D:INTRO] +OK? Bon, allons-y et restez groupés. + +[INT3_E:INTRO] +Bon, personne s'énerve, maintenant. + +[INT3_F:INTRO] +Je suis ici! Le moteur tourne! + +[INT3_G:INTRO] +Tu l'as? + +[INT3_H:INTRO] +De la colombienne 100% pure, la meilleure qualité, amigo. + +[INT3_I:INTRO] +Les billets? + +[INT3_J:INTRO] +Des 10 et des 20... usagés. + +[INT3_L:INTRO] +Allez! Sors-nous de là! Roule! + +[INTRO1:INTRO] +Je sors la tête d'un caniveau pour une seconde de défonce, et le destin me chie de la merde plein la gueule! + +[INTRO2:INTRO] +Va roupiller. + +[INTRO3:INTRO] +Qu'est-ce que tu vas faire? + +[INTRO4:INTRO] +Je passerai à ton bureau demain et on essayera de trouver une solution à ce merdier. + +[INT3_K:INTRO] +Je crois que nous allons faire affaire, amigo, hé hé hé! + +[INT3_M:INTRO] +Laisse-moi voir. + +[INT2_L:INTRO] +non, non, non, attends... + +{=================================== MISSION TABLE KENT1 ===================================} + +[KPM1_A:KENT1] +D'accord, mon vieux, je vais sauver ton petit ami. + +[KPM1_B:KENT1] +De quoi tu m'causes? + +[KPM1_C:KENT1] +Tu connais ce branleur, Diaz, la grande gueule... + +[KPM1_D:KENT1] +Il a ton pote, Lance. Il paraît que celui-ci a essayé de lui sauter sur le poil. + +[KPM1_E:KENT1] +Mais il a pas sauté assez haut, si tu vois ce que je veux dire... + +[KPM1_F:KENT1] +Où il l'a emmené? Et tourne pas autour du pot! + +[KPM1_G:KENT1] +T'énerve pas! Ils l'ont emmené de l'autre côté de la ville, à la décharge. + +[KPM1_H:KENT1] +Putain... Pauvre cinglé... + +[KPM1_2:KENT1] +~r~Tu étais supposé sortir Lance vivant de ce guêpier! + +[KPM1_3:KENT1] +SANTE DE LANCE : + +[RESC_1:KENT1] +Tu peux te servir d'un flingue? + +[RESC_2:KENT1] +Ouais... je pense... Content de te voir moi aussi. + +[RESC_3:KENT1] +Sortons de là! + +[RESC_4:KENT1] +Ca fout tous mes plans en l'air, tes conneries. Cette fois-ci, t'as vraiment merdé pour de bon, Lance! + +[RESC_5:KENT1] +Il a descendu mon frère! Tu croyais que j'allais faire quoi? Lui tondre sa pelouse? + +[RESC_6:KENT1] +Va falloir qu'on liquide ce connard de Diaz avant qu'il en fasse autant avec nous. + +[RESC_7:KENT1] +Va récupérer et retrouve-moi sur le pont de Star Island. Ok? + +[RESC_8:KENT1] +OK. + +[KPM1_1:KENT1] +~g~Lance est retenu prisonnier dans la décharge. Va le sauver! + +[KPM1_4:KENT1] +~g~Amène Lance à l'hosto! + +[M_PASSN:KENT1] +MISSION TERMINEE! + +[KPM1_5:KENT1] +~g~Les mecs de Diaz sont après toi, amène Lance à l'hosto. + +{=================================== MISSION TABLE KICKSTT ===================================} + +[KICK1_2:KICKSTT] +~r~Tu n'as pas récupéré la moto assez vite! + +[KICK1_7:KICKSTT] +~r~Tu as niqué la moto! + +[KICK1_8:KICKSTT] +~g~Monte sur la moto! + +[KICK1_T:KICKSTT] +TEMPS PRIS : + +[KICKTM:KICKSTT] +~b~TEMPS DE L'EPREUVE : ~1~:~1~ + +[KICKTM2:KICKSTT] +~b~TEMPS DE L'EPREUVE : ~1~:0~1~ + +[GETBIKE:KICKSTT] +~g~Il te reste ~1~ secondes pour retourner à une moto avant la fin de la mission. + +[KICK1_1:KICKSTT] +~g~Termine le parcours aussi vite que possible. + +[KICK1_6:KICKSTT] +~g~Bien joué! + +[KICK_10:KICKSTT] +~g~Utilise la Sanchez pour terminer le parcours sans oublier aucun point de passage. + +[KICK_12:KICKSTT] +~r~Tu t'es dégonflé! + +[KICK_13:KICKSTT] +~r~T'as mis trop de temps! + +[KICK_11:KICKSTT] +~g~Pour quitter la mission, marche sur le ~q~marqueur rose~g~. + +{=================================== MISSION TABLE LAWYER1 ===================================} + +[LAW1_A:LAWYER1] +Va roupiller, qu'il m'a dit - + +[LAW1_B:LAWYER1] +- et je suis resté toute la nuit le cul vissé sur cette chaise dans le noir à boire du café! + +[LAW1_C:LAWYER1] +C'est la merde. On est baisés jusqu'à l'os, mon pote! + +[LAW1_D:LAWYER1] +Ecoute-moi, ces balaises, ils vont débarquer ici pour m'arracher la tête! C'est pas possible! + +[LAW1_E:LAWYER1] +J'me suis pas tapé l'école de droit pour ça! Putain, qu'est-ce qu'on va faire maintenant? + +[LAW1_F:LAWYER1] +Ferme-la, pose ton cul et détends-toi. Je vais te dire ce qu'on va faire. + +[LAW1_G:LAWYER1] +Faut que tu trouves les types qui nous ont fauché la coke et après je les descends. + +[LAW1_H:LAWYER1] +Bonne idée. Excellente idée. Laisse-moi réfléchir, laisse-moi réfléchir. + +[LAW1_I:LAWYER1] +Je sais! Il y a ce Colonel à la retraite, le Colonel Juan Garcia Cortez. + +[LAW1_J:LAWYER1] +C'est lui qui m'a aidé à organiser l'affaire + +[LAW1_K:LAWYER1] +en-dehors des truands établis de Vice City. Ok? + +[LAW1_L:LAWYER1] +Maintenant, écoute. Il organise une fiesta dans la baie, sur son yatch du luxe + +[LAW1_M:LAWYER1] +et tous les gros pontes de Vice City seront là! + +[LAW1_N:LAWYER1] +J'ai une invitation, bien sûr que j'ai une invitation, + +[LAW1_O:LAWYER1] +mais il est pas question que je sorte d'ici. Non, pas question! + +[LAW1_P:LAWYER1] +Ferme-la, je t'ai dit! Je vais y aller moi-même... + +[LAW1_Q:LAWYER1] +Attends deux secondes! Tu sais, moi aussi, j'aime bien 1978, mais là, c'est pas une soirée bière et striptease... + +[LAW1_R:LAWYER1] +Je veux dire, sans vouloir t'offenser, hein, tu risque de te faire refouler à l'entrée, quoi... + +[LAW1_S:LAWYER1] +Y'a un problème avec mes fringues? + +[LAW1_T:LAWYER1] +Bon, écoute. Fais un saut chez Rafael, dis-lui que tu viens de ma part et il te relookera. + +[LAW1_U:LAWYER1] +Ok, allez, vas-y... + +[LAWP_1:LAWYER1] +Buenas noches. + +[LAWP_2:LAWYER1] +Si je comprends bien, c'est M. Rosenberg qui vous envoie. + +[LAWP_3:LAWYER1] +J'espère que de récents problèmes n'ont pas affecté sa santé, ou ... hum... + +[LAWP_4:LAWYER1] +son équilibre mental, M....? + +[LAWP_5:LAWYER1] +Vercetti. Il fait simplement un peu... d'agoraphobie. + +[LAWP_6:LAWYER1] +Excellent, excellent. Et vous? + +[LAWP_7:LAWYER1] +Je veux juste ma marchandise. + +[LAWP_8:LAWYER1] +Ah. C'est un malheureux concours de circonstances pour toutes les personnes impliquées... + +[LAWP_9:LAWYER1] +Bien entendu, j'ai de mon côté ordonné une enquête... + +[LAWP_10:LAWYER1] +Mais c'est un problème délicat qui nécessite du temps... + +[LAWP_11:LAWYER1] +Peut-être en reparlerons-nous plus tard, voulez-vous? + +[LAWP_12:LAWYER1] +En attendant, laissez-moi vous présenter ma fille... + +[LAWP_13:LAWYER1] +Mercedes! + +[LAWP_14:LAWYER1] +Caramia, pourrais-tu t'occuper de notre invité pendant que je m'occupe des autres convives? + +[LAWP_15:LAWYER1] +D'accord, papa. + +[LAWP_16:LAWYER1] +Veuillez m'excuser... + +[LAWP_17:LAWYER1] +Mercedes? + +[LAWP_18:LAWYER1] +Et il faut vivre avec ça... + +[LAWP_19:LAWYER1] +Quoi qu'il en soit, laissez-moi vous montrer quelques uns de nos distingués invités... + +[LAWP_20:LAWYER1] +Celui-ci est le membre du congrès Alex Shrub accompagné par la star montante siliconée Candy Suxxx... + +[LAWP_21:LAWYER1] +Et connaissez-vous ma charmante femme Laura? Non? + +[LAWP_22:LAWYER1] +Eh bien, malheureusement, elle est en Alabama, voici Candy. + +[LAWP_23:LAWYER1] +Et là-bas, nous avons l'ailier vedette des Mambas de Vice City, BJ. + +[LAWP_24:LAWYER1] +toujours aussi charmeur + +[LAWP_25:LAWYER1] +Mais je l'ai plaqué et mis dans une chaise roulante! + +[LAWP_26:LAWYER1] +Ha ha! Excellent! + +[LAWP_27:LAWYER1] +En ce moment, je cherche une nouvelle propriété de luxe. + +[LAWP_28:LAWYER1] +Et ce molusque c'est Jezz Torrent, + +[LAWP_29:LAWYER1] +Le chanteur principal des Love Fist. + +[LAWP_30:LAWYER1] +Savez-vous... comment ils jouent au ping-pong, en Thaïlande? + +[LAWP_31:LAWYER1] +Je vais vous le dire... + +[LAWP_32:LAWYER1] +Ils n'utilisent pas de raquettes, si vous voyez ce que je veux dire! + +[LAWP_33:LAWYER1] +L'impotent. + +[LAWP_34:LAWYER1] +Et le trio bavard. + +[LAWP_35:LAWYER1] +Cette outre à sueur ronflante est le bras droit handicapé de papa, Gonzales. + +[LAWP_36:LAWYER1] +Les deux autres sont le Pasteur Richards + +[LAWP_37:LAWYER1] +et un réalisateur pseudo intellectuel nommé Steve Scott. + +[LAWP_38:LAWYER1] +...passion avec les envahisseuses nymphomanes, + +[LAWP_39:LAWYER1] +Le requin géant s'amène et + +[LAWP_40:LAWYER1] +il leur bouffe simplement les couilles! + +[LAWP_41:LAWYER1] +Ah, c'est là. Vous n'avez jamais vu un truc pareil avant, hein? + +[LAWP_42:LAWYER1] +Colonel! + +[LAWP_43:LAWYER1] +Vos fêtes sont toujours aussi réussies, hahahahha! + +[LAWP_44:LAWYER1] +Je ne puis que m'excuser de mon arrivée tardive. + +[LAWP_45:LAWYER1] +Ah, de nada amigo. Comment allez-vous? + +[LAWP_46:LAWYER1] +Nos affaires sont très difficile - Les barbares sont aux portes de la ville. + +[LAWP_47:LAWYER1] +L'heure est venue de récompenser les amis et de liquider les ennemis, amigo. + +[LAWP_48:LAWYER1] +C'est qui, la grande gueule? + +[LAWP_49:LAWYER1] +Ricardo Diaz, c'est Mr.Coke. + +[LAWP_50:LAWYER1] +Mercedes! + +[LAWP_51:LAWYER1] +Oh, je raccompagnais justement mon ami en ville. + +[LAWP_52:LAWYER1] +Une autre fois, Ricardo! + +[LAWP_53:LAWYER1] +Partons d'ici! + +[LAWP_54:LAWYER1] +En fait, emmenez-moi plutôt au Pole Position club. + +[LAW1_2:LAWYER1] +~g~Va au yatch du Colonel. + +[LAW1_4:LAWYER1] +~r~Tu as tué la fille du Colonel! + +[LAW1_5:LAWYER1] +Vous allez travailler pour mon père? + +[LAW1_6:LAWYER1] +Peut-être. + +[LAW1_7:LAWYER1] +Ca vous ennuie si je pose ma main sur votre genou? + +[LAW1_8:LAWYER1] +Peut-être... + +[LAW1_9:LAWYER1] +C'est trop dur d'avoir un père riche et puissant! Vamos! + +[LAW1_10:LAWYER1] +A plus tard, mon mignon! + +[LAW1_11:LAWYER1] +Sans aucun doute. + +[LAW1_12:LAWYER1] +Mmmm... Chouette bécane. + +[LAW1_13:LAWYER1] +Non! Ma moto! + +[LAW1_3:LAWYER1] +~g~Emmène la fille du Colonel au Pole Position Club. + +[HELP20:LAWYER1] +Suis le ~h~point T-shirt~w~ sur le radar pour trouver Rafael. + +[LAW1_14:LAWYER1] +Mmmm, j'aime beaucoup ta grosse moto. + +[LAW1_15:LAWYER1] +Ouais, bébé, je viens de piquer ça à ce con + +{=================================== MISSION TABLE LAWYER2 ===================================} + +[LAW2_A:LAWYER2] +Ah! Eh bien, j'espère que tu t'es bien amusé! Parce que moi, je suis mort d'inquiétude! Qu'est-ce que t'as trouvé? + +[LAW2_B:LAWYER2] +Qu'y'a plus de criminels dans cette ville qu'en taule. Faut qu'on trouve une piste dans la rue. + +[LAW2_C:LAWYER2] +Attends, laisse-moi réfléchir, voyons... + +[LAW2_D:LAWYER2] +Ah! J'ai trouvé! + +[LAW2_E:LAWYER2] +Bon, il y a bien cet anglais, une espèce de déchet du biz de la musique, + +[LAW2_F:LAWYER2] +il est connu sous le nom de Kent Paul. + +[LAW2_G:LAWYER2] +Quoi qu'il en soit, il a le nez plongé dans la merde de Vice City depuis un moment + +[LAW2_H:LAWYER2] +et ça, si quelqu'un sait où sont les 20 kilos de coke, + +[LAW2_I:LAWYER2] +c'est bien lui, ok? Il est toujours fourré au Malibu... + +[LAW2_J:LAWYER2] +Je vais aller lui rendre une petite visite... + +[LAW2B_A:LAWYER2] +D'où tu sors, toi? + +[LAW2B_B:LAWYER2] +Je cherche un oiseau dans ton genre depuis une éternité... + +[LAW2B_C:LAWYER2] +Kent Paul, mon pote. Ouais, dans le coin, c'est moi le patron. + +[LAW2B_D:LAWYER2] +Je cherche un Anglais... + +[LAW2B_E:LAWYER2] +Je résouds les problèmes, si tu vois ce que je veux dire? + +[LAW2B_F:LAWYER2] +Je vais m'occuper de toi. Quoi que tu demandes, tu l'auras... + +[LAW2B_G:LAWYER2] +Ne t'inquiéte absolument de rien. + +[LAW2B_H:LAWYER2] +Dégage, chérie! + +[LAW2B_I:LAWYER2] +Hé hé hé hé hé! + +[LAW2B_J:LAWYER2] +C'est toi, Kent Paul? Je suis un pote de Rosenberg... + +[LAW2B_K:LAWYER2] +Rosenberg...Rosenberg... Ah, ce cinglé qui poursuit les ambulances! + +[LAW2B_L:LAWYER2] +Ce mec peut défendre un innocent jusqu'au couloir de la mort! + +[LAW2B_M:LAWYER2] +Donne-nous un autre verre! + +[LAW2B_N:LAWYER2] +On est tous des comédiens. + +[LAW2B_O:LAWYER2] +Ecoute-moi, il me manque vingt kilos et un gros tas de pognon... + +[LAW2B_P:LAWYER2] +Une affaire de drogue? C'est toujours des pièges à con... + +[LAW2B_Q:LAWYER2] +Qu'est-ce que tu sais à ce propos? + +[LAW2B_R:LAWYER2] +Hé! Hé! Là où je voulais en venir, + +[LAW2B_S:LAWYER2] +c'est à un cuisinier-trompettiste qui deale à l'extérieur de la cuisine d'un hôtel sur Ocean Drive. + +[LAW2B_T:LAWYER2] +Il avait l'air très content de lui, ces derniers temps. Tu pourrais peut-être aller voir ça de plus près... + +[LAW2B_U:LAWYER2] +Bon, je vais y aller. A plus tard. + +[LAW2B_V:LAWYER2] +Ouais, c'est ça. Allez, dégage, imbécile ou je te botte le cul! + +[LAW2B_W:LAWYER2] +Donne-moi à boire et putain, où est cette salope? + +[LAW2C_A:LAWYER2] +Ouais, c'est bien, massacre-le, ça devrait le rendre plus bavard. + +[LAW2C_B:LAWYER2] +T'en veux aussi? + +[LAW2C_C:LAWYER2] +Hé, relax, je veux la même chose que toi, mon frère. + +[LAW2C_D:LAWYER2] +Ah ouais? Et c'est quoi? + +[LAW2C_E:LAWYER2] +Ton fric et la blanche de mon regretté frère. Malheureusement, tu viens de refroidir notre piste. + +[LAW2C_F:LAWYER2] +C'était un accident. Dégage. + +[LAW2C_G:LAWYER2] +Hé, hé! Pas la peine de jouer les justiciers solitaires avec moi! + +[LAW2C_H:LAWYER2] +Voilà comment je vois les choses... On est deux mecs dans une ville étrange. Et on a besoin de veiller l'un sur l'autre. + +[LAW2C_I:LAWYER2] +J'ai pas besoin de toi, mon frère... + +[LAW2C_J:LAWYER2] +T'en es sûr? Tiens, attrape! + +[LAW2C_K:LAWYER2] +Suis-moi! + +[LAW2_1:LAWYER2] +Qu'est-ce que tu regardes? + +[LAW2_2:LAWYER2] +Tu ferais mieux de commencer à causer... + +[LAW2_3:LAWYER2] +Suce-moi, connard! + +[LAW2_4:LAWYER2] +Par là! + +[LAW2_5:LAWYER2] +Je vais voir ce que je peux trouver. Je garde un oeil sur toi, Tommy. + +[LAW2_6:LAWYER2] +~g~Va au Malibu Club et trouve Kent Paul. + +[LAW2_7:LAWYER2] +~g~Trouve le cuisinier sur Ocean Drive. + +[LAW2_10:LAWYER2] +~g~Retourne à l'hôtel. + +[LAW2_11:LAWYER2] +~g~Ramasse son portable! + +[LAW2_12:LAWYER2] +Portable acquis! Tu peux maintenant recevoir des coups de fil. + +[LAW2_13:LAWYER2] +~g~Tu as laissé Lance derrière! Va le chercher! + +[LAW2_14:LAWYER2] +On doit foutre le camp d'ici! + +[GUN_2A:LAWYER2] +Maintiens la ~h~~k~~PED_LOCK_TARGET~ ~w~enfoncée pour ~h~viser automatiquement~w~. Appuie sur la~h~ ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer! + +[GUN_2C:LAWYER2] +Maintiens la ~h~~k~~PED_LOCK_TARGET~ ~w~enfoncée pour ~h~viser automatiquement~w~. Appuie sur la~h~ ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer! + +[GUN_2D:LAWYER2] +Maintiens la ~h~~k~~PED_LOCK_TARGET~ ~w~enfoncée pour ~h~viser automatiquement~w~. Appuie sur la~h~ ~k~~PED_FIREWEAPON~~w~ pour ~h~tirer! + +[HELP17:LAWYER2] +Appuie sur la~h~ ~k~~PED_FIREWEAPON~ ~w~pour attaquer le chef. + +[HELP18:LAWYER2] +Appuie sur la~h~ ~k~~PED_FIREWEAPON~~w~ pour attaquer le chef. + +[LAW3_11:LAWYER2] +Place-toi sur le ~q~marqueur rose~w~ pour consulter les armes en vente. + +[LAW3_12:LAWYER2] +Tu peux sélectionner des armes en appuyant sur ~h~gauche~w~ ou ~h~droite~w~ de la ~h~touche directionnelle. + +[LAW3_13:LAWYER2] +Si tu as assez de blé, tu peux acheter des armes en appuyant sur la ~h~~k~~PED_SPRINT~. + +[LAW3_14:LAWYER2] +Tu peux sortir du magasin en appuyant sur la ~h~~k~~VEHICLE_ENTER_EXIT~. + +[LAW3_15:LAWYER2] +Suis le ~h~point pistolet~w~ sur le radar pour trouver ~h~Ammu-Nation + +[LAW2_15:LAWYER2] +~g~Va chez Ammu-Nation. + +[LAW2_K:LAWYER2] +calme-toi, maintenant. + +[LAW2_16:LAWYER2] +Il faut que tu comprennes un truc sur cette ville. Tu dois toujours avoir un flingue sur toi. + +[LAW2_17:LAWYER2] +Allez, l'armurerie du coin n'est qu'à quelques pas d'ici. + +[LAW2_18:LAWYER2] +Tommy, tout le monde a besoin de se la couler douce de temps en temps. + +[LAW2_19:LAWYER2] +Voici le Pole Position Strip Club. Tu pourrais y passer de temps en temps. + +{=================================== MISSION TABLE LAWYER3 ===================================} + +[LAW3_A:LAWYER3] +Aaaaaah! Oh, nom de Dieu, c'est toi! Merde, je vais avoir besoin d'un nouveau pantalon! + +[LAW3_B:LAWYER3] +Putain, ces psychopathes du nord ont téléphoné pour dire qu'ils rappliquaient bientôt... + +[LAW3_C:LAWYER3] +Alors, où est ce sacré pognon? + +[LAW3_D:LAWYER3] +Relax, relax, on n'en est pas encore là. + +[LAW3_E:LAWYER3] +Je croyais vraiment que tu t'en occupais... + +[LAW3_F:LAWYER3] +Et maintenant, ces mecs disent qu'il faut qu'on leur rende un service... + +[LAW3_G:LAWYER3] +Tu veux dire que JE dois leur rendre un service... + +[LAW3_H:LAWYER3] +Ouais, évidemment! Est-ce que j'ai l'air de pouvoir impressionner un jury? + +[LAW3_I:LAWYER3] +Je ne pourrais même pas faire peur à un gosse et crois-moi, j'ai essayé! + +[LAW3_J:LAWYER3] +Maintenant, écoute, c'est ça ou le cousin de Forelli, Georgio, se prend cinq ans pour escroquerie. + +[LAW3_K:LAWYER3] +Faut que tu t'occupes de ces mecs! + +[LAW3_L:LAWYER3] +Je comprends. Demander au jury de changer d'opinion. T'en fais pas. + +[LAW3_M:LAWYER3] +Non non non non! Ca, j'ai déjà essayé! Et l'affaire ne s'est pas arrangée! + +[LAW3_N:LAWYER3] +Tu dois les OBLIGER à changer d'opinion! + +[LAW3_1:LAWYER3] +Avec les compliments de Georgio... + +[LAW3_2:LAWYER3] +N'oublie pas que le mot 'coupable' est proscrit! + +[LAW3_3:LAWYER3] +Il est innocent, jusqu'à ce que je dise le contraire. + +[LAW3_4:LAWYER3] +Tu sais qu'il n'est pas coupable... + +[LAW3_5:LAWYER3] +Tu te souviens de Georgio? Et donc qu'il est innocent? + +[LAW3_6:LAWYER3] +Non coupable. C'est compris... bien. + +[LAW3_8:LAWYER3] +~r~Tu as tué un juré! + +[LAW3_9:LAWYER3] +~g~Bousille la voiture du juré pour le faire sortir! + +[HELP40:LAWYER3] +Tu peux bousiller les voitures avec le marteau ou un outil similaire. + +[LAW3_10:LAWYER3] +~g~Tu peux aller à la ~h~quincaillerie~g~ pour acheter une arme de corps à corps. + +[LAW3_20:LAWYER3] +~g~Bousille la voiture du juré! + +[LAW3_21:LAWYER3] +Je n'arrive pas à le croire! + +[LAW3_22:LAWYER3] +Incroyable! + +[LAW3_23:LAWYER3] +Ok! C'est bon, j'ai compris! + +[LAW3_24:LAWYER3] +~g~Ce marteau pourrait être utile. + +[LAW3_7:LAWYER3] +~g~Va intimider les deux jurés, mais NE LES TUE PAS! + +[HELP23:LAWYER3] +Tu peux suivre le ~h~marteau~w~ sur le radar si tu veux acheter des armes de corps à corps dans la quincaillerie. + +[LAW3_16:LAWYER3] +de Pete de Floride. + +[LAW3_17:LAWYER3] +Casse-toi de là! + +{=================================== MISSION TABLE LAWYER4 ===================================} + +[LAW4_A:LAWYER4] +Avery, cela va sans dire... Ah, Tommy! Des nouvelles? Non, non, tu m'en parleras plus tard. Plus tard. + +[LAW4_B:LAWYER4] +Tommy, voici Avery Carrington. Je crois que vous vous êtes rencontrés à la fête? + +[LAW4_C:LAWYER4] +Pas directement. + +[LAW4_D:LAWYER4] +Salut. + +[LAW4_E:LAWYER4] +Avery a une proposition à nous faire. + +[LAW4_F:LAWYER4] +On a pas déjà du pain sur la planche? + +[LAW4_G:LAWYER4] +J'essaye de maintenir les loups au loin, alors lâche-moi un peu la grappe! + +[LAW4_H:LAWYER4] +Je suis tendu comme un cable et même si je dois crever avant la fin de la semaine, j'ai pas envie de mourir pauvre! + +[LAW4_I:LAWYER4] +Bon, vous énervez pas, tous les deux. + +[LAW4_J:LAWYER4] +Ecoute, petit, tu me files un coup de main et le moindre latino qui te fait chier, je m'occupe de lui. + +[LAW4_K:LAWYER4] +Ok, qu'est-ce que je peux faire pour toi? + +[LAW4_L:LAWYER4] +Cette société de livraison a son dépôt sur un terrain très intéressant. Et ils ne veulent pas vendre. + +[LAW4_M:LAWYER4] +Ils s'accrochent à leur terrain comme des rats entêtés, alors va falloir exploser cette vermine. + +[LAW4_N:LAWYER4] +Va là-bas et mets le feu aux poudres. + +[LAW4_O:LAWYER4] +La sécurité sera débordée et tu pourras te glisser dedans et en finir avec eux... + +[LAW4_P:LAWYER4] +Tu peux aussi passer chez Rafael pour changer de fringues. T'en as sans doute pour un moment, mais bon, fonce! + +[LAW4_Q:LAWYER4] +Ca va être une vraie émeute. + +[LAW4_R:LAWYER4] +Si tout se passe comme sur des roulettes, passe me voir au bureau... + +[LAW4_1:LAWYER4] +Dispersez-vous! La direction n'écoutera vos revendications que dans les conditions appropriées! + +[LAW4_2:LAWYER4] +Dispersez-vous! Retournez chez vous! + +[LAW4_3:LAWYER4] +Dispersez-vous! C'est inapproprié! + +[LAW4_4:LAWYER4] +Dispersez-vous ou vous finirez tous à la rue! + +[LAW4_5:LAWYER4] +Sortez vos bâtons, les mecs, on va briser quelques crânes de cocos! + +[LAW4_13:LAWYER4] +~g~Commence à te battre avec au moins 4 ouvriers pour déclencher une émeute. + +[LAW4_14:LAWYER4] +~g~Détruis les camionnettes du complexe! + +[HELP38:LAWYER4] +Si tu butes quelqu'un qui porte une arme, il la lâche. + +[HELP39:LAWYER4] +Tu peux tirer sur les barils d'explosifs, mais garde tes distances. + +{=================================== MISSION TABLE MIAMI_1 ===================================} + +[T4X4_1A:MIAMI_1] +~g~Tu as ~1~ secondes pour récupérer ~y~24~g~ points de passage. ~g~Fais-le dans ~y~N'IMPORTE QUEL ORDRE. + +[T4X4_1B:MIAMI_1] +~y~Passe A TRAVERS~g~ le premier point de passage pour déclencher le ~r~CHRONOMETRE. + +[T4X4_1C:MIAMI_1] +~1~ sur 24! + +[GETBIK1:MIAMI_1] +Tu as ~1~ secondes pour enfourcher une PCJ 600! + +[GETBIK3:MIAMI_1] +~r~Tu as besoin d'une PCJ 600 pour cette mission! + +{=================================== MISSION TABLE MM ===================================} + +[BLOD_04:MM] +ETAT DE LA VOITURE : + +[BLOD_05:MM] +~g~TEMPS VISE : ~1~ minute + +[BLOD_06:MM] +~g~TEMPS VISE : ~1~ minutes + +[BLOD_07:MM] +NV meilleur tps : ~1~ secondes + +[BLOD_08:MM] +Voitures détruites : ~1~ + +[BLOD_09:MM] +$~1~ + +[BLOD_10:MM] +VAINQUEUR! + +[BLOD_01:MM] +Franchis les points de passage pour augmenter ton temps général. + +[BLOD_02:MM] +Tu as perdu si ton temps général est à zéro. + +[BLOD_03:MM] +Il faut que ton temps général soit supérieur au temps visé pour gagner! + +{=================================== MISSION TABLE OVALRIG ===================================} + +[HOTR_01:OVALRIG] +~g~La course dure 12 tours. Seuls les trois premiers arrivés reçoivent une récompense. + +[HOTR_02:OVALRIG] +~g~Tu es disqualifié si ta bagnole est détruite. + +[HOTR_03:OVALRIG] +~g~Si ta bagnole est bousillée, tu peux la faire réparer aux stands. + +[HOTR_04:OVALRIG] +~g~C'est par là pour sortir du stade. + +[HOTR_05:OVALRIG] +Etat de la voiture : + +[HOTR_06:OVALRIG] +Tours : + +[HOTR_10:OVALRIG] +Temps de course : + +[HOTR_09:OVALRIG] +Position : + +[HOTR_12:OVALRIG] +~r~Ta caisse est bousillée! + +[HOTR_13:OVALRIG] +~r~Tu n'as pas gagné la course! + +[HOTR_14:OVALRIG] +~r~Tu as été disqualifié! + +[HOTR_15:OVALRIG] +Temps : ~1~:~1~ + +[HOTR_16:OVALRIG] +Temps : ~1~:0~1~ + +[HOTR_17:OVALRIG] +Meilleur temps : ~1~:~1~ + +[HOTR_18:OVALRIG] +Meilleur temps : ~1~:0~1~ + +[HOTR_19:OVALRIG] +Meilleur temps : aucun + +[HOTR_20:OVALRIG] +Nv meilleur tps : ~1~:~1~ + +[HOTR_21:OVALRIG] +Nv meilleur tps : ~1~:0~1~ + +[HOTR_22:OVALRIG] +Meilleur résultat : aucun + +[HOTR_23:OVALRIG] +Meilleur résultat : 1er + +[HOTR_24:OVALRIG] +Meilleur résultat : 2e + +[HOTR_25:OVALRIG] +Meilleur résultat : 3e + +[HOTR_26:OVALRIG] +Meilleur résultat : ~1~e + +[HOTR_27:OVALRIG] +Meilleur tps au tr : ~1~.~1~ secondes + +[HOTR_28:OVALRIG] +Meilleur tps au tr : ~1~.0~1~ secondes + +[HOTR_29:OVALRIG] +$~1~ + +[HOTR_30:OVALRIG] +1ERE PLACE + +[HOTR_31:OVALRIG] +2E PLACE + +[HOTR_32:OVALRIG] +3E PLACE + +[HOTR_33:OVALRIG] +Meilleur tps au tr : aucun + +[HOTR_11:OVALRIG] +Nv meilleur tps au tr : ~1~.~1~ secondes + +[HOTR_34:OVALRIG] +Nv meilleur tps au tr : ~1~.0~1~ secondes + +{=================================== MISSION TABLE PHIL1 ===================================} + +[PHI1_HP:PHIL1] +Tu peux lancer une grenade à détonateur quelque part puis l'y faire exploser au moment souhaité. + +[PHIL1_A:PHIL1] +Phil? + +[PHIL1_B:PHIL1] +COURS! + +[PHIL1_C:PHIL1] +Cours. + +[PHIL1_E:PHIL1] +Merde, Phil, tu bois cette saloperie? + +[PHIL1_F:PHIL1] +Putain, t'as pas besoin de la boire! + +[PHIL1_G:PHIL1] +Tu la renifles juste un bon coup et t'exploses direct! + +[PHIL1_H:PHIL1] +Ecoute, Phil, t'avais dit que tu pourrais t'occuper de ma puissance de feu... + +[PHIL1_I:PHIL1] +Pas de problème. + +[PHIL1_J:PHIL1] +Il y a ce mexicain qui trafique des armes, il a pas été réglo avec moi. + +[PHIL1_K:PHIL1] +Il fait sa tournée hebdomadaire, maintenant. + +[PHIL1_L:PHIL1] +Défonce l'arrière de son camion et sers-toi avant qu'il réagisse. + +[PHIL1_M:PHIL1] +Et rends-moi un service au passage tant que tu y es. + +[PHIL1_N:PHIL1] +Ensuite, règle-lui son compte... + +[PHI1_01:PHIL1] +~g~Va piquer les armes à l'arrière du camion du trafiquant. + +[PHI1_02:PHIL1] +~g~Le trafiquant a largué son chargement. Brise la caisse et ramasse l'artillerie. + +[PHI1_03:PHIL1] +~g~On dirait qu'ils ont demandé des renforts... + +[PHI1_04:PHIL1] +~g~Va maintenant finir les trafiquants d'armes survivants. + +[PHIL1_O:PHIL1] +Yeeeeeeehhaaaaa! + +[PHIL1_D:PHIL1] +N'approche jamais une allumette enflammée du boomshine de Phil Cassidy! + +{=================================== MISSION TABLE PHIL2 ===================================} + +[PHIL2_A:PHIL2] +Salut, Phil, ça roule? + +[PHIL2_B:PHIL2] +Saluut Tommyyyy! Comment ça va? Ca fait tellement longtemps... + +[PHIL2_C:PHIL2] +Je te jure que tu devrais arrêter le boomshine... + +[PHIL2_D:PHIL2] +Ca sent comme du décapant à peinture, j'ai les yeux qui brûlent... + +[PHIL2_E:PHIL2] +Ferme-la, Tommy + +[PHIL2_F:PHIL2] +et amène-toi par là. Y'a quelque chose que je veux te montrer... Quelque chose... + +[PHIL2_G:PHIL2] +Waou! Putain! C'est normal que je sente l'odeur d'ici? Je me sens tout drôle... + +[PHIL2_H:PHIL2] +T'inquiète pas de l'odeur, Tommy, regarde juste ça. + +[PHIL2_I:PHIL2] +Putain de saloperies de piles bon marché! Il y en a d'autres sur le banc. + +[PHIL2_J:PHIL2] +TA-DAAA! + +[PHIL2_K:PHIL2] +Oh merde! + +[PHI2_01:PHIL2] +~g~Vite, emmène Phil à l'hosto! + +[PHI2_03:PHIL2] +~r~Phil Cassidy est mort! Maintenant, qui va nous filer des flingues à Liberty? + +[PHI2_05:PHIL2] +Pas à l'hosto, mec, c'est plein de flics et de vietcongs! + +[PHI2_06:PHIL2] +Il y a un ancien chirurgien de l'armée qui me doit quelques services et une tondeuse... + +[PHI2_07:PHIL2] +Il crêche à Little Havana, oh! Regarde! Un poisson géant! + +[PHI2_08:PHIL2] +Fais gaffe! Charlie sur la ligne des arbres! + +[PHI2_09:PHIL2] +C'est moi qui hallucine ou la route est vraiment toute molle? + +[PHI2_10:PHIL2] +Cuillère Brisée à Mère Poule, tu me reçois? + +[PHI2_11:PHIL2] +Spooney Wooney Woo Woo Woooo! + +[PHI2_12:PHIL2] +La mort m'appelle, mon garçon! + +[PHI2_13:PHIL2] +Des ailes noires qui battent tout autour... + +[PHI2_14:PHIL2] +C'est magnifique, mec, magnifique... Mais tellement froid... + +[PHI2_15:PHIL2] +10-4 On a un conducteur en état d'ivresse. + +[PHI2_04:PHIL2] +SANTE DE PHIL : + +[PHI_AS1:PHIL2] +CHEZ PHILS PLACE OK + +[PHI_AS2:PHIL2] +~g~De nouvelles armes sont en vente chez Phils Place. + +{=================================== MISSION TABLE PIZZA ===================================} + +[PIZ1_01:PIZZA] +~g~Va livrer ces pizzas. Tu dois les balancer aux clients. Roule à côté d'eux pour lancer les pizzas. + +[PIZ1_02:PIZZA] +~g~T'as balancé toutes tes pizzas, retourne en chercher d'autres. + +[PIZ1_05:PIZZA] +~g~Tu as 5 minutes pour livrer les commandes avant que que les clients n'appelent une autre pizzeria. + +[PIZ1_07:PIZZA] +~r~T'as tué le client! T'es viré! + +[PIZ1_08:PIZZA] +~r~T'es à la bourre! T'es viré! + +[PIZ1_09:PIZZA] +~r~T'as bousillé notre bécane! T'es viré! + +[PIZ1_11:PIZZA] +Hé! Retourne sur ta bécane! + +[PIZ1_12:PIZZA] +Pizzas restantes : + +[PIZ1_06:PIZZA] +Appuie sur la~h~ ~k~~TOGGLE_SUBMISSIONS~~w~ quand tu es sur la moto pour annuler la mission. + +[PIZ1_13:PIZZA] +Effectue ces livraisons en vitesse. + +[PIZ1_14:PIZZA] +Mon pote, un pizza pour toi! + +[PIZ1_15:PIZZA] +Allez, mon vieux, livre ça en vitesse. + +[PIZ1_16:PIZZA] +Qu'est-ce que t'attends, mon vieux? T'as des pizzas à livrer! + +[PIZ1_17:PIZZA] +Je sais que tu voulais pas faire ce boulot, mais je m'en tape! + +[PIZ1_18:PIZZA] +Va livrer ça. + +[PIZ1_19:PIZZA] +On a besoin de livrer ça. + +[PIZ1_20:PIZZA] +Allez, mon vieux, livre ça ou t'es viré! + +[PIZ1_21:PIZZA] +Il y a des gens qui attendent, mon vieux. + +[PIZ1_22:PIZZA] +Mais qu'est-ce que t'attends? Faut livrer ça! + +[PIZ1_23:PIZZA] +Va livrer cette saloperie de bouffe! + +[PIZ1_24:PIZZA] +Les clients ont les crocs, mon vieux! + +[PIZ1_25:PIZZA] +Tu peux prendre ça, mon vieux? + +[PIZ1_26:PIZZA] +Tiens, va livrer ça en vitesse, amigo. + +[PIZ1_27:PIZZA] +Magne-toi, on est débordés. Va livrer ça! + +[PIZ1_28:PIZZA] +Encore toi? Qu'est-ce que t'attends? Va livrer ça! + +[PIZ1_29:PIZZA] +Perds pas de temps, cette fois-ci. + +[PIZ1_30:PIZZA] +Espèce de bon à rien! Va livrer ça en vitesse! + +[PIZ1_31:PIZZA] +T'auras jamais de promotion si tu traînes encore ce coup-ci. + +[PIZ1_32:PIZZA] +~r~La pizza est trop chaude ou quoi? + +[PIZ1_33:PIZZA] +~g~Retourne au restaurant pour d'autres commandes. + +[PIZ1_34:PIZZA] +~g~Pizza livrée. Voilà ton fric. + +[PIZ_WON:PIZZA] +Mission Pizza terminée. votre max de Santé passe à 150. + +{=================================== MISSION TABLE PORN1 ===================================} + +[POR1_A:PORN1] +Action + +[POR1_B:PORN1] +Ooooh! C'est un sacré morceau! + +[POR1_C:PORN1] +30 centimètres, c'est la taille réglementaire, bébé. + +[POR1_D:PORN1] +COUPEZ! Qui c'est cet abruti? Toi! Toi! T'es dans le champ? POURQUOI? + +[POR1_E:PORN1] +Qu'est-ce que c'est que ces conneries? + +[POR1_F:PORN1] +Des aliens? Des cannes à pêche? + +[POR1_G:PORN1] +J'ai jamais vu un requin aussi gros... + +[POR1_H:PORN1] +Faut que tout ça dégage! + +[POR1_I:PORN1] +Pourquoi t'es dans le métier, connard? + +[POR1_J:PORN1] +Hein? + +[POR1_K:PORN1] +Ben, pour les chattes, évidemment! Alors, qu'est-ce que c'est que ça? + +[POR1_L:PORN1] +Ceci est mon art - SECURITE! + +[POR1_M:PORN1] +Ecoute-moi, trou du cul arrogant, tu m'appartiens maintenant. Tout m'appartient ici. + +[POR1_N:PORN1] +On va révolutionner cet endroit... + +[POR1_O:PORN1] +Je vais faire ta fortune... + +[POR1_P:PORN1] +Oh. Tu es... Tu... es Tommy Vercetti? Mais je croyais que tu étais... + +[POR1_Q:PORN1] +Exactement... + +[POR1_R:PORN1] +Va y avoir quelques changements dans le coin et on va commencer à se faire des ronds. + +[POR1_S:PORN1] +Au fait, as-tu jamais pensé à... + +[POR1_T:PORN1] +Mais d'abord, on va avoir besoin de quelques putes bien foutues... + +[POR1_U:PORN1] +Ouais, les filles c'est bien mais toi... waou! + +[POR1_02:PORN1] +~g~Bute le mac de Candy, puis retourne la récupérer. + +[POR1_04:PORN1] +Salut, Candy. Je cherche des actrices, ça te branche? + +[POR1_05:PORN1] +Et comment! Mais faut causer à mon agent... + +[POR1_06:PORN1] +Putain qu'est-ce que tu fous? + +[POR1_07:PORN1] +Tu aurais mieux fait de rester à la maison, aujourd'hui! + +[POR1_7B:PORN1] +J'arrive pas à croire ce trou du cul! + +[POR1_08:PORN1] +Salut, Mercedes! + +[POR1_09:PORN1] +Salut, Tommy! Tu viens faire la fête? + +[POR1_10:PORN1] +Non, pas maintenant, chérie. Ca te dirait de faire du cinéma? + +[POR1_11:PORN1] +Evidemment! Mais seulement si c'est sordide et bon marché! + +[POR1_13:PORN1] +~g~Emmène les filles au studio y retrouver Steve. + +[POR1_14:PORN1] +T'es engagé! + +[POR1_15:PORN1] +Hé tommy, tu viens pour un bout d'essai? + +[POR1_17:PORN1] +Waou! Sympa le requin! + +[POR1_18:PORN1] +~r~Mercedes est morte! + +[POR1_20:PORN1] +Tommy! Où tu vas? Reviens ici! + +[POR1_21:PORN1] +Où tu vas? + +[POR1_22:PORN1] +Tommy, quand c'est qu'on va pouvoir être un peu seuls ensemble? + +[POR1_01:PORN1] +~g~Candy Suxxx serait parfaite pour un premier rôle! + +[POR1_12:PORN1] +~g~Emmène Candy avec toi pour rencontrer Mercedes. + +[POR1_16:PORN1] +Plus tard, peut-être, mon chou... + +[POR1_24:PORN1] +~g~Retourne chercher Candy. + +[POR1_25:PORN1] +~g~Tu as laissé Candy. Retourne la chercher! + +[POR1_23:PORN1] +~g~Candy va s'occuper de l'affaire dans le ~h~Centre~g~. + +[POR1_26:PORN1] +~g~Voilà Candy. On dirait qu'elle a encore eu rendez-vous avec le représentant du Congrès, Alex Shrub. + +[POR1_27:PORN1] +Allez, partons d'ici! + +[POR1_28:PORN1] +Tommy, sois prudent! Mes implants ne sont pas encore assurés! + +[POR1_29:PORN1] +Tu appelles ça conduire? + +[POR1_30:PORN1] +Je pourrais plus faire du porno après ça! + +[POR1_31:PORN1] +Quoi? T'essaies de me tuer ou quoi? Je croyais que c'était moi, la star! + +{=================================== MISSION TABLE PORN2 ===================================} + +[POR2_A:PORN2] +Comment ça se passe, Steve? + +[POR2_B:PORN2] +Eh ben, Candy est faite pour le rôle et la nouvelle fille est infatigable! + +[POR2_C:PORN2] +Elle s'est tapé la moitié du casting et des techniciens avant même que j'ai fini de régler les éclairages! + +[POR2_D:PORN2] +Enfin, demain, on va en extérieurs pour tourner les scènes du bateau... + +[POR2_E:PORN2] +Les scènes du bateau? Quelles scènes du bateau? + +[POR2_F:PORN2] +Les pêcheurs sont dans les filets de la passion quand ce requin géant arrive... + +[POR2_G:PORN2] +Qu'est-ce que j'avais dit à propos du requin géant? + +[POR2_H:PORN2] +J'avais dit : PAS DE REQUIN GEANT! Ok? + +[POR2_I:PORN2] +Laisse simplement les caméras braquées sur le pieu! + +[POR2_J:PORN2] +Ok ok, Tommy, j'avais bien le droit d'essayer... + +[POR2_K:PORN2] +Les prospectus sont imprimés? + +[POR2_L:PORN2] +Ouais, mais personne va nous laisser distribuer ces trucs, je veux dire... + +[POR2_M:PORN2] +Ils sont un peu trop, hum, enfin, ils manquent un peu de poésie... + +[POR2_N:PORN2] +T'en fais pas pour ça! + +[POR2_O:PORN2] +J'ai mes propres plans pour la distribution... + +[POR2_P:PORN2] +OK. Hé, Candy, amène-toi dans ma caravane! + +[POR2_01:PORN2] +~g~Il y a un hydravion à l'arrière des studios qui servait pour la propagande des vieux films. + +[POR2_02:PORN2] +~g~Atteins l'un des points de contrôle pour commencer à larguer les prospectus. + +[POR2_03:PORN2] +~g~Largue les prospectus tout le long du chemin jusqu'au dernier point de contrôle. + +[POR2_04:PORN2] +~r~NIVEAU DE CARBURANT BAS! + +[POR2_05:PORN2] +Utilise-le pour distribuer les prospectus dans la ville. + +[DILDO:PORN2] +Kérosène : + +[POR2_Q:PORN2] +Oh, merde! + +[PORN2_9:PORN2] +~g~Il te reste ~1~ secondes pour retourner à une Skimmer avant la fin de la mission. + +{=================================== MISSION TABLE PORN3 ===================================} + +[POR3_A:PORN3] +Bon, c'est quoi le problème, maintenant? + +[POR3_B:PORN3] +Chuuuut! + +[POR3_C:PORN3] +Eh bien, après l'intime rencontre avec les envahisseuses nymphomanes, + +[POR3_D:PORN3] +notre jeune héros se retrouve incapable de penser à autre chose que cette énorme montagne phallique + +[POR3_E:PORN3] +et c'est là qu'on veut placer la scène avec la cuve de purée de patates, mais alors on... + +[POR3_F:PORN3] +Ces conneries m'intéressent pas! + +[POR3_G:PORN3] +Continuez simplement le boulot, continuez... + +[POR3_H:PORN3] +Hé, Tommy... + +[POR3_I:PORN3] +T'as évoqué un problème légal au téléphone... + +[POR3_J:PORN3] +Ah ouais! Le représentant du Congrès Alex Shrub a pris le train électoral en marche à la pêche des votes puritains... + +[POR3_K:PORN3] +La rumeur laisse entendre qu'il soutient des mesures de restriction contre... + +[POR3_L:PORN3] +Disons... les formes les plus charnelles de la grande industrie cinématographique de ce pays. + +[POR3_M:PORN3] +Super. + +[POR3_N:PORN3] +Candy! Tu connais Shrub. + +[POR3_O:PORN3] +Vous faites des trucs bizarres tous les deux? + +[POR3_P:PORN3] +Oh oui! Oui! Oh oui! Oui oui oui ouuuuuuuuuuuuuuuuuui! + +[POR3_Q:PORN3] +Dis-moi que t'as eu ça... + +[POR3_R:PORN3] +Est-ce que ça faisait partie du... euh... Ou bien elle parlait à... + +[POR3_S:PORN3] +Impossible à dire... Dans tous les cas... + +[POR3_T:PORN3] +Tu ferais mieux de la suivre après le tournage + +[POR3_U:PORN3] +et voir si elle te mènera pas à leur nouveau nid d'amour. + +[POR3_V:PORN3] +T'as un appareil photo? + +[POR3_X:PORN3] +Hé, file-lui un appareil photo! + +[POR3_02:PORN3] +~r~Tu as tué le représentant du Congrès! T'auras du mal à le faire chanter, maintenant! + +[POR3_03:PORN3] +~r~Tu as alerté la sécurité du représentant du Congrès, ils vont le faire sortir tout de suite. + +[POR3_04:PORN3] +Candy, tu peux m'appeler Martha? + +[POR3_05:PORN3] +Oh Alex, enfin, je veux dire Martha. Comme tu veux... + +[POR3_06:PORN3] +Martha, il y a quelqu'un qui regarde... C'est pervers... + +[POR3_07:PORN3] +Vous là-bas, donnez-moi cet appareil photo! + +[POR3_01:PORN3] +~g~Suis la ~h~Stretch~g~ de Candy. + +[POR3_15:PORN3] +~r~T'as bousillé la Stretch de Candy! + +[POR3_17:PORN3] +~g~Retourne aux Porn Studios avec la pellicule. + +[POR3_19:PORN3] +~r~T'es à court de pellicule! + +[POR3_21:PORN3] +~g~T'as perdu la Stretch de Candy! + +[POR3_22:PORN3] +~g~Le WK Chariot Hotel de l'autre côté de ce balcon devrait être idéal, comme point de vue. + +[POR3_23:PORN3] +~g~Il y a une porte latérale qui te permettra d'accéder à l'hôtel. + +[POR3_08:PORN3] +Appuie sur la~h~ ~k~~PED_LOCK_TARGET~ ~w~et maintiens-la enfoncée pour ~h~cadrer~w~ la photo. + +[POR3_09:PORN3] +Appuie sur la~h~ ~k~~PED_LOCK_TARGET~ ~w~et maintiens-la enfoncée pour ~h~cadrer~w~ la photo. + +[POR3_10:PORN3] +Appuie sur la~h~ ~k~~PED_SNIPER_ZOOM_IN~ ~w~pour faire un ~h~zoom avant~w~ et sur la~h~ ~k~~PED_SNIPER_ZOOM_OUT~ ~w~pour faire un ~h~zoom arrière~w~. + +[POR3_11:PORN3] +Appuie sur la~h~ ~k~~PED_SNIPER_ZOOM_IN~ ~w~pour faire un ~h~zoom avant~w~ et sur la~h~ ~k~~PED_SNIPER_ZOOM_OUT~ ~w~pour faire un ~h~zoom arrière~w~. + +[POR3_12:PORN3] +Appuie sur la~h~ ~k~~PED_FIREWEAPON~ ~w~pour prendre une photo. + +[POR3_13:PORN3] +Appuie sur la~h~ ~k~~PED_FIREWEAPON~ ~w~pour prendre une photo. + +[POR3_14:PORN3] +Appuie sur la~h~ ~k~~PED_FIREWEAPON~ ~w~pour prendre une photo. + +[POR3_20:PORN3] +~g~Si tu as besoin de te déplacer, utilise la ~h~Sparrow~g~ garée derrière. + +[POR3_16:PORN3] +~g~Il te faut trois bons clichés d'Alex Shrub avec Candy pour le faire chanter. + +[POR3_24:PORN3] +PHOTOS PRISES : + +{=================================== MISSION TABLE PORN4 ===================================} + +[POR4_A:PORN4] +Je suis désolée, mais je ne peux pas avaler maintenant! + +[POR4_B:PORN4] +Oh, ALLEZ! Chérie! + +[POR4_C:PORN4] +Il décharge comme une baleine, par pitié + +[POR4_D:PORN4] +comment tu fais pour ne pas saisir le rôle? + +[POR4_E:PORN4] +Mais Stevie... + +[POR4_F:PORN4] +Comment va mon réalisateur vedette? + +[POR4_G:PORN4] +Ah, mon pote, la lutte incessante entre l'intégrité artistique et + +[POR4_H:PORN4] +l'éjaculation sous toutes ses formes est loin d'être terminée. + +[POR4_I:PORN4] +Et avant que tu me poses la question, je t'informe que les quatre vidéos sortiront dans les... + +[POR4_J:PORN4] +Chérie, veux-tu garder l'anaconda dans le champ, S'IL TE PLAIT. + +[POR4_K:PORN4] +Il prend plus de l'heure que toi! + +[POR4_L:PORN4] +Oh, pardon, Steve... + +[POR4_M:PORN4] +Je me disais, faudrait monter un gros coup de pub pour la promo du film. + +[POR4_N:PORN4] +Un truc qui aurait vraiment de l'impact sur la ville... T'as pas une idée? + +[POR4_O:PORN4] +Eh ben, autrefois, on faisait des galas, + +[POR4_P:PORN4] +avec des vedettes, des limousines, la nuit éclairée par les projos... + +[POR4_Q:PORN4] +Des projecteurs? J'ai une idée! + +[POR4_R:PORN4] +...Ouais, ouais, ouais! Le petit numéro de paillettes, les limousines, ah, les premières! + +[POR4_S:PORN4] +Oh oui, chère madaaaame, bien sûr, chère madaaaame... + +[POR4_T:PORN4] +et la presse, et le barrage de lumières... + +[POR4_01:PORN4] +~g~Va dans le ~y~Centre~g~ et ajuste le projecteur sur le toit du bâtiment. + +[POR4_02:PORN4] +~g~Une bécane rapide est nécessaire pour sauter de toit en toit. L'agent de sécurité a l'habitude de venir en ~y~PCJ 600~g~ au boulot... + +[POR4_03:PORN4] +~g~Faut que tu montes sur les toits. Il devrait y avoir un ascenceur dans l'un des immeubles de bureaux. + +[POR4_06:PORN4] +~g~Retourne à l'immeuble de bureaux du bas si tu as besoin d'accéder à nouveau au toit. + +[POR4_07:PORN4] +~g~Tu as besoin d'une bécane pour sauter de bâtiment en bâtiment! + +[POR4_08:PORN4] +~g~Fonce à travers la fenêtre pour commencer. Tu as jusqu'à 07:00 avant que la lumière du jour ne risque de te faire découvrir. + +[POR4_09:PORN4] +~g~Les marqueurs t'indiquent sur quel bâtiment sauter ensuite. + +[POR4_10:PORN4] +~r~Il fait maintenant trop clair pour monter sans te faire voir. + +[POR4_11:PORN4] +Retourne à l'escalier si tu as à nouveau besoin d'accéder au toit. + +[POR4_05:PORN4] +~g~Ces escaliers mènent à un bureau. + +[POR_AS1:PORN4] +STUDIO DE CINEMA OK + +[POR_AS2:PORN4] +~g~Inter Global Films génère dorénavant un revenu de $~1~ maximum. Pense à récupérer le fric régulièrement. + +{=================================== MISSION TABLE PROT1 ===================================} + +[PRO1_B:PROT1] +Je supporte pas ce style. Tommy, qu'est-ce que t'en penses? Qu'est-ce que tu dirais qu'on mette un bar dans... + +[PRO1_D:PROT1] +Ecoutez-moi... + +[PRO1_E:PROT1] +L'heure est venue de mettre la main sur cette ville! Le monde n'attend plus que nous! + +[PRO1_F:PROT1] +Faut qu'on commence à s'emparer de territoires! + +[PRO1_G:PROT1] +Faut qu'on fasse comprendre à tout Vice City qu'on est les nouveaux maîtres du jeu! Tu vois ce que je veux dire? + +[PRO1_I:PROT1] +Ce qu'il te faut, c'est une façade légale, Tommy! Une affaire officielle! Ca m'a jamais rapporté d'emmerdes! + +[PRO1_J:PROT1] +Faut qu'on commence à utiliser des balaises ou autant faire une croix sur tout le boulot qu'on s'est tapé jusque là. + +[PRO1_K:PROT1] +Les commerces locaux savent que Diaz est mort et ils refusent de payer pour leur protection! + +[PRO1_L:PROT1] +Ah. On pourrait essayer la corruption... + +[PRO1_M:PROT1] +La corruption! Mon cul, la corruption! Je vais vous montrer comment on leur fait peur! + +[PRO1_01:PROT1] +~g~Défoule-toi sur les devantures des magasins et les proprios supplieront pour être protégés. + +[PRO1_03:PROT1] +~r~C'était supposé être un raid éclair, pas une pause café. + +[PRO1_04:PROT1] +Mon gagne-pain est détruit! + +[PRO1_05:PROT1] +Tout est en ruines! + +[PRO1_06:PROT1] +Je paye le prix fort pour ma protection! + +[PRO1_07:PROT1] +Ma belle vitrine! + +[PRO1_08:PROT1] +Mon magasin! Mon beau magasin! + +[PRO1_09:PROT1] +Vercetti. Souvenez-vous de mon nom... + +[PRO1_10:PROT1] +Je règne désormais sur cette ville. MOI! + +[BUYP1:PROT1] +Tu peux désormais acheter des propriétés dans certaines zones de la carte. + +[BUYP2:PROT1] +Si tu vois un marqueur vert sur une propriété, c'est que tu peux l'acheter. + +[PRO1_N:PROT1] +Je reviens dans cinq minutes... + +[PRO1_11:PROT1] +~g~Va au ~y~Centre commercial de North Point~g~ dans ~y~Vice Point~g~. + +[PRO1_12:PROT1] +~g~Brise les vitrines de chaque magasin et les proprios supplieront pour une nouvelle protection. + +[PRO1_A:PROT1] +Ah, faut qu'on redécore cet endroit, faut qu'on lui donne un air plus vieux. + +[PRO1_C:PROT1] +T'es mon avocat, Rosenberg. Pas mon décorateur. Compris? + +[BUYP3:PROT1] +Tiens-toi sur le marqueur, puis appuie sur la touche ~h~~k~~PED_ANSWER_PHONE~~w~ pour acheter cette propriété. + +[PRO1_13:PROT1] +~g~Tu as cinq minutes pour tous les avoir. + +{=================================== MISSION TABLE PROT2 ===================================} + +[PRO2_A:PROT2] +C'est quoi l'problème? + +[PRO2_B:PROT2] +Un bar refuse de payer. + +[PRO2_C:PROT2] +Le boss considère qu'il est sous la protection d'un gang de voyous du coin... + +[PRO2_D:PROT2] +Mais t'en fais pas, Tommy, je peux m'en occuper. + +[PRO2_E:PROT2] +C'est ça que t'appelles t'en occuper? + +[PRO2_F:PROT2] +Vous deux, levez vos culs de là... + +[PRO2_G:PROT2] +Allons-y. + +[PRO2_01:PROT2] +~g~Liquide les gardes qui protègent le Front Page Bar et trouve qui les a approvisionnés. + +[PRO2_10:PROT2] +~g~Il y en a deux de plus qui se sont fait la malle. Finis-en avec eux. + +[PRO2_11:PROT2] +Montez dans la caisse, bons à rien. + +[PRO2_02:PROT2] +Ta protection a besoin d'être légèrement renforcée. + +[PRO2_03:PROT2] +Ah, non, pas encore! J'en ai marre! + +[PRO2_04:PROT2] +Ces imbéciles opèrent dans le quartier à partir de DBP Security. + +[PRO2_05:PROT2] +Réglez la question entre vous, les mecs. + +[PRO2_06:PROT2] +Bon, à plus tard. + +[PRO2_07:PROT2] +Ouais, ouais, c'est ça. + +[PRO2_08:PROT2] +~g~Les abrutis du DBP Security sauront que t'y vas, alors fonce et chope-les avant qu'ils décampent! + +[PRO2_09:PROT2] +~g~Va parler au proprio du Front Page Bar. + +{=================================== MISSION TABLE PROT3 ===================================} + +[PRO3_A:PROT3] +Pauvre crétin! A quoi tu pensais? + +[PRO3_B:PROT3] +Tu réalise ce qui aurait pu arriver? + +[PRO3_C:PROT3] +On a failli tous se faire avoir! + +[PRO3_D:PROT3] +Le retardateur a dû déconner... + +[PRO3_E:PROT3] +Cet endroit était prêt à sauter comme une usine de feux d'artifice. + +[PRO3_F:PROT3] +Et pis quelqu'un a rencardé les flics... + +[PRO3_G:PROT3] +C'est quoi le problème, les mecs? + +[PRO3_H:PROT3] +Mike devait foutre le feu quelque part dans le centre commercial, + +[PRO3_I:PROT3] +mais il a foiré son coup et maintenant, les flics grouillent, là-bas. + +[PRO3_J:PROT3] +Faut qu'on récupère notre bordel et qu'on s'tire de là! + +[PRO3_K:PROT3] +Du calme, vous deux, laissez-moi réfléchir une seconde! + +[PRO3_L:PROT3] +Tommy Vercetti a pas l'habitude de mettre les bouts comme ça... + +[PRO3_M:PROT3] +Les flics vont passer ce bâtiment au peigne fin, pas vrai? + +[PRO3_N:PROT3] +Mais ça prend du temps... + +[PRO3_O:PROT3] +On va entrer et foutre le feu nous-mêmes! + +[PRO3_P:PROT3] +Ouais, mais... + +[PRO3_Q:PROT3] +Il y a que les flics qui peuvent approcher à moins d'un kilomètre de là-bas! + +[PRO3_R:PROT3] +Alors, on ira en tant que flics! + +[PRO3_S:PROT3] +On va se dégotter des uniformes et il nous faut aussi une bagnole de patrouille. + +[PRO3_T:PROT3] +Tout ça à cause de toi, Mike. + +[PRO3_U:PROT3] +Je suis désolé. + +[PRO3_V:PROT3] +J'ai trouvé! + +[PRO3_W:PROT3] +Tout ce qu'on a à faire, c'est appâter les flics avec un doigt levé, + +[PRO3_X:PROT3] +de les prendre au piège + +[PRO3_Y:PROT3] +et de leur sauter dessus! + +[PRO3_Z:PROT3] +Super plan! En route! + +[PRO3_A1:PROT3] +OK. + +[PRO3_01:PROT3] +Ok Lance, attirons l'attention des flics! + +[PRO3_02:PROT3] +~g~Prends la bagnole des flics et va poser la bombe au Tarbrush Coffee Shop dans le centre commercial. + +[PRO3_03:PROT3] +~g~T'as laissé Lance derrière, va le récupérer! + +[PRO3_04:PROT3] +~g~Allons-y! + +[PRO3_05:PROT3] +~r~T'as buté Lance! + +[PRO3_07:PROT3] +~g~Tu as grillé ta couverture! Magne-toi de poser la bombe! + +[PRO3_09:PROT3] +Ligotons et bâillonnons-les! + +[PRO3_10:PROT3] +Aaah! Il me va parfaitement! + +[PRO3_11:PROT3] +Mais quand même un peu serré au niveau des couilles... + +[PRO3_12:PROT3] +Ah ouais? Le mien aussi, c'est pareil! + +[PRO3_13:PROT3] +Du calme, mon pote! Les flics conduisent pas aussi mal! + +[PRO3_14:PROT3] +Oublie pas... Faut sourire aux autres flics... + +[PRO3_15:PROT3] +Salut officier. Bel insigne, bel insigne... + +[PRO3_16:PROT3] +Tout doux, Lance. + +[PRO3_17:PROT3] +Ok, les retardateurs sont programmés, 5 secondes et ça saute! + +[PRO3_18:PROT3] +5 secondes? Putain faut dégager d'ici en vitesse! + +[PRO3_19:PROT3] +Maintenant, ils vont vraiment être fâchés... + +[PRO3_20:PROT3] +~g~Fais-toi suivre par deux flics dans le garage. + +[PRO3_21:PROT3] +~g~Tu dois avoir un indice de recherche pour que les flics te suivent dans le piège. + +[PRO3_22:PROT3] +~g~La porte est bloquée! Dégage le passage pour qu'elle puisse être refermée. + +[PRO3_23:PROT3] +~g~Marche sur le marqueur pour poser la bombe. + +[PRO3_24:PROT3] +~g~Tire-toi du café! + +[PRO_AS1:PROT3] +PROTECTION OK + +[PRO_AS2:PROT3] +~g~Vercetti Estate génère désormais un revenu de $~1~ maximum. Pense à récupérer le fric régulièrement. + +[PRO3_08:PROT3] +~g~ Retourne à ~h~Vercetti Estate~g~ sur ~h~Starfish Island~g~. + +{=================================== MISSION TABLE RACES ===================================} + +[RACES_2:RACES] +~g~Il te faut un véhicule, c'est pas une course à pied! + +[RACES_3:RACES] +3... 2... 1... PARTEZ! + +[RACES_8:RACES] +~r~Tu n'as pas gagné la course! + +[RACES00:RACES] +Course ~1~ : + +[RACES01:RACES] +Terminal Velocity + +[RACES02:RACES] +Ocean Drive + +[RACES03:RACES] +Border Run + +[RACES04:RACES] +Capital Cruise + +[RACES05:RACES] +Virée! + +[RACES06:RACES] +Endurance V.C. + +[RACES07:RACES] +Participation : ~1~$ + +[RACES08:RACES] +Meilleur temps : ~1~:~1~ + +[RACES09:RACES] +Meilleure perf : 1er + +[RACES10:RACES] +Meilleure perf : 2e + +[RACES11:RACES] +Meilleure perf : 3e + +[RACES12:RACES] +Meilleure perf : 4e + +[RACES13:RACES] +Longueur circuit : ~1~.~1~ km + +[RACES15:RACES] +Meilleur temps : NA + +[RACES16:RACES] +Meilleure perf : NA + +[RACES18:RACES] +TU AS GAGNE : $~1~ + +[RACES19:RACES] +Tu n'as pas assez de moyens pour participer. + +[RACES22:RACES] +Meilleur temps : ~1~:0~1~ + +[RACES23:RACES] +Longueur circuit : ~1~.~1~ miles + +[RACES_1:RACES] +~g~Trouve un bolide et rends-toi sur la grille de départ. + +[RACEHLP:RACES] +~w~Appuie sur la~h~ ~k~~PED_SPRINT~~w~ pour commencer la course choisie. Appuie sur la~h~ ~k~~VEHICLE_ENTER_EXIT~~w~ pour quitter. + +{=================================== MISSION TABLE RCHELI1 ===================================} + +[WRECKED:RCHELI1] +Ton véhicule est mort! + +[RCH1_4:RCHELI1] +Points de passage restants : + +[RCH1_6:RCHELI1] +~g~Utilise l'hélico télécommandé pour passer les points de passage à travers l'aéroport. + +[RCH1_7:RCHELI1] +~g~Il y a 20 points de passage en tout. + +[RCH1_12:RCHELI1] +~g~L'hélicoptère télécommandé est presque hors de portée! + +[RCH1_13:RCHELI1] +~r~L'hélicoptère radiocommandé est hors de portée! + +[RCH1_8:RCHELI1] { reVC update } +~g~Si tu veux quitter cette mission, appuie sur la ~h~~k~~VEHICLE_FIREWEAPON~~g~ pour faire exploser ton hélicoptère radiocommandé. + +{=================================== MISSION TABLE RCPLNE1 ===================================} + +[RCPL1_4:RCPLNE1] +~g~Fais une COURSE DE POINTS DE PASSAGE avec 3 autres avions télécommandés. + +[RCPL1_5:RCPLNE1] +~g~Passe par les points de passage dispersés dans Vice City. + +[RCPL1_6:RCPLNE1] { reVC update } +~g~Si tu veux quitter cette mission, appuie sur la ~h~~k~~VEHICLE_FIREWEAPON~~g~ pour faire exploser ton avion radiocommandé. + +[RCPL1_8:RCPLNE1] +~g~Ton avion RC va sortir du périmètre! + +[RCPL1_9:RCPLNE1] +~r~Ton avion RC est sorti du périmètre! + +{=================================== MISSION TABLE RCRACE1 ===================================} + +[RCR1_4:RCRACE1] +Tours restants : + +[RCR1_1:RCRACE1] +~g~Fais une course de points de passage contre 3 autres voitures RC. + +[RCR1_2:RCRACE1] +~g~Sois le premier à boucler 2 tours de circuit pour gagner! + +[RCR1_6:RCRACE1] +~g~Ta voiture RC va sortir du périmètre! + +[RCR1_7:RCRACE1] +~r~Ta voiture RC est sortie du périmètre! + +{=================================== MISSION TABLE ROCK1 ===================================} + +[RBM1_A:ROCK1] +Okaaaaaaaaaay! + +[RBM1_B:ROCK1] +Grandiose! Vraiment génial! + +[RBM1_D:ROCK1] +Hé, t'as déjà rencontré les Love Fist? + +[RBM1_E:ROCK1] +Non, mais j'adore votre musique! + +[RBM1_F:ROCK1] +Laisse-moi te présenter au groupe. + +[RBM1_G:ROCK1] +Voici P, Percy, Dick et Willy est aux chiottes et Jezz était dans la cabine tout à l'heure... + +[RBM1_H:ROCK1] +Les mecs, je veux vous présenter un bon ami à moi. + +[RBM1_I:ROCK1] +Voici Tommy. Ca fait un bail qu'on se connait. + +[RBM1_J:ROCK1] +Ok, mon pote. + +[RBM1_K:ROCK1] +Ah, euh... C'est quoi déjà, ton nom? + +[RBM1_L:ROCK1] +Arrête-ça Jezz tu t'en souviens, + +[RBM1_M:ROCK1] +et essaye pas de jouer au plus malin avec moi, + +[RBM1_N:ROCK1] +je suis trop intelligent, mon mignon! + +[RBM1_O:ROCK1] +Tu vois, le problème, Tom, c'est que ces garçons ont besoin d'aide. + +[RBM1_P:ROCK1] +Ils n'ont pas beaucoup de contacts ici et ils savent pas à qui s'adresser. + +[RBM1_Q:ROCK1] +On a besoin de drogue, mon pote! + +[RBM1_R:ROCK1] +Faut qu'on réveille la vieille fureur de Love Fist, tu vois? + +[RBM1_S:ROCK1] +Et bien, ici, c'est Vice City, quel est le problème? + +[RBM1_U:ROCK1] +Du Love Juice, mec! + +[RBM1_V:ROCK1] +Du Love Juice? + +[RBM1_W:ROCK1] +Ouais, deux doses de boomshine, une dose de trompette, 5 bombes fizz et un litre d'essence. + +[RBM1_X:ROCK1] +Tu peux nous aider, mon pote? + +[RBM1_Y:ROCK1] +Ah, ça rendrait tellement service aux garçons! + +[RBM1_Z:ROCK1] +Tu peux faire ça pour les garçons, pas vrai? + +[RBM1_8:ROCK1] +~r~Mercedes est morte! + +[RBM1_10:ROCK1] +~r~Imbécile! Tu as détruit la marchandise! + +[RBM1_13:ROCK1] +~g~Amène le Love Juice et Mercedes au groupe avant qu'ils n'entrent sur scène. + +[RBM1_15:ROCK1] +~r~Tu as perdu le dealer, notre cash et la drogue! + +[RBM1_17:ROCK1] +~g~Tue le dealer et prends la drogue! + +[MOB_07A:ROCK1] +Salut, mon vieux, les gars auraient besoin d'un peu de compagnie, si tu vois ce que je veux dire... + +[MOB_07B:ROCK1] +Je connais justement la fille qu'il faut. + +[ROK1_5:ROCK1] +Salut Mercedes! + +[ROK1_6:ROCK1] +Salut, Tommy. Comment vas-tu? + +[ROK1_7:ROCK1] +Ca roule. Ecoute, les Love Fist, ça te branche? + +[ROK1_8:ROCK1] +Ok, mais c'est un service qu'il faudra me retourner... + +[RBM1_14:ROCK1] +~g~T'as besoin d'une bagnole ou d'une bécane! + +[RBM1_1:ROCK1] +~g~Va prendre Mercedes à son appartement. + +[RBM1_12:ROCK1] +~g~Va récupérer les ingrédients du Love Juice chez le dealer. + +[ROK1_2:ROCK1] +PLUS NECESSAIRE + +[ROK1_3:ROCK1] +PLUS NECESSAIRE + +[MERC_39:ROCK1] +A plus tard, champion. + +[RBM1_C:ROCK1] +Hé, Tommy! Content que t'aies réussi! + +[ROK1_1A:ROCK1] +Tu cherches quelque chose de spécial? J'ai ce qu'il te faut! + +[ROK1_9:ROCK1] +Merci pour le pognon, suce boules! + +[RBM1_T:ROCK1] +On a besoin de Love Juice, mec, tu vois? + +{=================================== MISSION TABLE ROCK2 ===================================} + +[RBM2_A:ROCK2] +Tommy, mon pote, je suis content de te voir! + +[RBM2_B:ROCK2] +Qu'est-ce qui se passe? + +[RBM2_C:ROCK2] +Des mauvaises vibrations, Tommy... + +[RBM2_E:ROCK2] +Il y a ce chat, on le connaît pas, mais lui il nous connaît. + +[RBM2_F:ROCK2] +C'est comme ce chat. Il nous connaît tous. + +[RBM2_G:ROCK2] +Il sait que Willy a un faible pour les culottes de ses femmes, hé hé! + +[RBM2_H:ROCK2] +Ou que Percy aime bien Duran Duran! + +[RBM2_K:ROCK2] +Ouais, le truc de la fusée d'amour, c'est vrai. Mais écoute, ce chat... + +[RBM2_L:ROCK2] +ouais, ouais, ce gars, il veut voir les Love Fist morts! + +[RBM2_M:ROCK2] +Morts, Tommy. + +[RBM2_N:ROCK2] +Les Love Fist disparus. Tu sais ce qu'on dit, les meilleurs meurent jeunes... + +[RBM2_O:ROCK2] +Tommy, faut que tu sauves les Love Fist! + +[RBM2_P:ROCK2] +On a une séance d'autographes dans deux heures et je pense que... + +[RBM2_Q:ROCK2] +Et les garçons pensent que le mec va essayer quelque chose de très vilain là-bas. + +[RBM2_1:ROCK2] +~g~Conduis la limousine à la séance d'autographes et essaye de démasquer le psychopathe . + +[RBM2_2:ROCK2] +~r~T'as bousillé la bagnole du groupe! + +[RBM2_3:ROCK2] +~g~Va à la séance d'autographes! + +[RBM2_4:ROCK2] +~g~Attrape le psychopathe! Ne le laisse pas s'échapper! + +[RBM2_5:ROCK2] +~r~Tu l'as perdu, imbécile! + +[RBM2_7:ROCK2] +~r~Les fans ont été attaqués, le psychopathe ne se montrera pas! + +[RBM2_8:ROCK2] +~r~Les agents de sécurité ont été attaqués, le psychopathe ne se montrera pas! + +[PSYCH_1:ROCK2] +Je veux voir les Love Fist morts! + +[PSYCH_2:ROCK2] +Les Love Fist ont gâché ma vie! + +[RBM2_I:ROCK2] +Ferme-la un peu. C'est pas parce que Jezz encule les moutons... + +[RBM2_R:ROCK2] +Oh, ferme-la! + +[RBM2_D:ROCK2] +Je plaisante pas. C'est pas des conneries, mec, tu piges? + +[RBM2_J:ROCK2] +C'est le truc de la fusée d'amour, tu sais? + +{=================================== MISSION TABLE ROCK3 ===================================} + +[RBM3_A:ROCK3] +Tommy! Le psychopathe est de retour! + +[RBM3_B:ROCK3] +Qu'est-ce qui se passe? + +[RBM3_C:ROCK3] +Ce psychopathe ne laissera jamais les Love Fist tranquilles! + +[RBM3_D:ROCK3] +Tu l'as pas tué, mon pote et il est revenu. + +[RBM3_E:ROCK3] +Ouais, ouais et le truc c'est que... + +[RBM3_F:ROCK3] +Le truc, c'est qu'on a besoin de quelqu'un de confiance pour conduire la limousine, + +[RBM3_G:ROCK3] +parce que ce timbré continue à nous menacer! + +[RBM3_I:ROCK3] +On chie tous dans nos frocs, mec. + +[RBM3_J:ROCK3] +Ok, les gars, du calme, je vais m'en occuper... + +[RBM3_K:ROCK3] +Normalement, j'ai mieux à foutre que de faire le taxi pour une bande d'Ecossais bisexuels bourrés, + +[RBM3_L:ROCK3] +mais dans votre cas, je vais faire une exception. + +[ROK3_03:ROCK3] +Autant pas faire dans la demi-mesure, alors. + +[ROK3_04:ROCK3] +Hé, Tommy, change la musique! + +[ROK3_08:ROCK3] +Ca commence à me faire chier, cette histoire. + +[ROK3_09:ROCK3] +Garde juste le pied au plancher! + +[ROK3_61:ROCK3] +Faut qu'on trouve la bombe! + +[ROK3_28:ROCK3] +Je vais bientôt jouer de la basse en enfer... + +[ROK3_30:ROCK3] +Que quelqu'un fasse quelque chose! + +[ROK3_32:ROCK3] +Ok, toi monsieur le dur à cuire, fais quelque chose! + +[ROK3_34:ROCK3] +Willy pourrait juste sucer le boomshine à la paille. + +[ROK3_37:ROCK3] +Passez une paille à Willy! + +[ROK3_41:ROCK3] +Quel fil, Tommy? + +[ROK3_42:ROCK3] +Le vert. + +[ROK3_43:ROCK3] +Y'en a pas de vert. Ou alors celui-la est vert? + +[ROK3_44:ROCK3] +Est-ce qu'un de ces fils te semble vert? + +[ROK3_49:ROCK3] +Ca fait des années que je te supporte! + +[ROK3_51:ROCK3] +Une grosse poufiasse hurlante... + +[ROK3_52:ROCK3] +Ouais! + +[ROK3_53:ROCK3] +Ferme-la et arrache un fil! + +[ROK3_54:ROCK3] +Lequel? + +[ROK3_55:ROCK3] +Celui-là... + +[ROK3_56:ROCK3] +Non! + +[ROK3_57:ROCK3] +Hé, on est ok, on a pas sauté, les potes! Tommy, mon pote bravo. Et Rock and Roll! + +[ROK3_58:ROCK3] +On a pas un concert où aller? Un racket à faire? Des fans à insulter? + +[ROK3_59:ROCK3] +LOVE FIST! + +[ROK3_60:ROCK3] +T'as fini avec cette bouteille? + +[RBM3_4:ROCK3] +~r~Tu as tué les Love Fist! + +[RBM3_6:ROCK3] +DETONATION : + +[RBM3_1:ROCK3] +~g~Conduis les Love Fist jusqu'au concert. + +[RBM3_2:ROCK3] +Si tu essayes de quitter la voiture tant que la bombe est armée, elle explosera. + +[RBM3_3:ROCK3] +Si la jauge de détonation devient pleine, la bombe explose. + +[RBM3_8:ROCK3] +Plus tu conduis vite, plus la jauge de détonation diminue. + +[RBM3_7:ROCK3] +~g~BOMBE DESAMORCEE! + +[ROK3_6A:ROCK3] +~g~Love Fist. Vous avez fini de polluer les ondes! + +[ROK3_6B:ROCK3] +~g~Je vous ai donné l'occasion de devenir mes amis. Je vais maintenant vous offrir celle de mourir! + +[ROK3_62:ROCK3] +alors on a pensé te montrer notre Temple du rock- + +[ROK3_63:ROCK3] +Que tu te fasses une idée de la fureur des Love Fist! + +[ROK3_64:ROCK3] +Ecoute-toi toi même, mon pote. C'est de la daube! + +[ROK3_65:ROCK3] +Hé, à tous les gosses, c'est un temple et nous sommes les prêtres! + +[ROK3_66:ROCK3] +Ouais, eh ben, si les gosses aiment leurs prêtres à moitié saouls et complètement sourds, + +[ROK3_67:ROCK3] +c'est leur problème. + +[ROK3_68:ROCK3] +Ah, merde, la bande de la cassette est encore bouffée. + +[ROK3_69:ROCK3] +Si ça continue, faudra jouer en live. + +[ROK3_70:ROCK3] +Ooooh merde! Mes intestins... + +[ROK3_74:ROCK3] +Ah, tiens, qu'est-ce que c'est que ça? Tommy, mets cette cassette. + +[ROK3_01:ROCK3] +Mon pote, c'est enfin l'heure d'un verre bien mérité. + +[ROK3_02:ROCK3] +La salle de concert est à une centaine de mètres plus loin. + +[ROK3_05:ROCK3] +Je me sens bizarre quand ma tête ne tambourine pas. + +[ROK3_07:ROCK3] +Tommy, faut que tu sauves le groupe! + +[ROK3_29:ROCK3] +Tommy, continue de foncer, mec! + +[ROK3_31:ROCK3] +Que quelqu'un fasse quelque chose... C'est quoi ces conneries? J'ai déjà vu des minettes plus courageuses. + +[ROK3_33:ROCK3] +Ecoute, mec. Moi, je joue d'un instrument, j'y connais rien aux bombes. + +[ROK3_35:ROCK3] +Ouais, j'ai entendu dire que t'étais doué pour ce genre de trucs! + +[ROK3_38:ROCK3] +Une paille? Hé, ici, c'est le bus de tournée des Love Fist! + +[ROK3_39:ROCK3] +Où c'est que tu veux que je trouve une paille? + +[ROK3_46:ROCK3] +J'aurais dû te larguer quand j'en avais l'occasion. + +[ROK3_47:ROCK3] +Capitaliste! + +[ROK3_48:ROCK3] +Arriviste! + +[ROK3_73:ROCK3] +Jezz écoute la cassette, + +[RBM3_9:ROCK3] +Si tu t'arrêtes ou conduis trop lentement, la jauge de détonation augmente. + +[ROK3_50:ROCK3] +Ferme-la, pauvre abruti! + +[ROK3_36:ROCK3] +Hé, j'étais raide défoncé cette nuit-là, et tu le sais bien! + +[RBM3_H:ROCK3] +Je chie dans mon froc, mec. Je veux ma maman! + +[ROK3_45:ROCK3] +Oh, je vois la mort dans les cartes! Tout est vert! + +[ROK3_6C:ROCK3] +~g~Si tu ralentis, la limousine explosera et les GROS TOCARDS CHEVELUS avec! + +[ROK3_71:ROCK3] +On doit continuer. Merci encore, Tommy! T'as assuré, mec! A plus! + +[ROK3_1:ROCK3] +Enfin, mec, voici l'heure d'un verre bien mérité. La salle est juste à quelques mètres d'ici. + +[ROK3_2:ROCK3] +Tu m'en sers un grand alors. Hé, Tommy, change de musique, mec. + +[ROK3_3:ROCK3] +Je pète les plombs si je secoue pas la tête. Hé, regarde, c'est quoi ça? Tommy, mets cette cassette. + +[ROK3_4:ROCK3] +Love Fist. Vous avez fini de polluer les ondes. Je vous ai donné une chance de devenir mes amis. + +[ROK3_5:ROCK3] +Maintenant vous allez mourir. Essayez de freiner et c'est la fin! Paf, plus de limousine et plus de GROS NASES CHEVELUS non plus. + +[ROK3_6:ROCK3] +Tommy, mec, tu dois sauver le groupe! J'en ai marre de ces conneries. Enlève pas ton pied de la pédale, mec! + +[ROK3_7:ROCK3] +On doit trouver la bombe! On peut pas continuer à rouler toute la journée? Ouais, quoi, on a plein de trucs à boire... + +[ROK3_8:ROCK3] +La bombe, elle serait pas dans le moteur? Va falloir qu'on s'arrête pour la désamorcer! Merde, mec, on va tous crever! J'vais m'bourrer la gueule! + +[ROK3_9:ROCK3] +Hé, on est dans la mouise, mon pote! Tu trouveras pas ta réponse dans la boisson! Tire-toi de là! + +[ROK3_10:ROCK3] +Hé, y'a des fils qui sortent de la bouteille de vodka! C'est pas de la vodka, c'est du BOOMSHINE! + +[ROK3_11:ROCK3] +AAAAAHHHHHH! Elle va exploser! AAAAAAAAAAAHHHHH!!!!!!!!!! + +[ROK3_12:ROCK3] +On m'avait prévenu que l'alcool me perdrait. J'ai déjà vu ça à la télé. Il suffit de tirer un de ces fils, mais lequel? J'en sais foutre rien, mec! + +[ROK3_13:ROCK3] +Putain, j'en sais rien! Willy, dis quelque chose. J'vais jouer de la basse en enfer. + +[ROK3_14:ROCK3] +Tommy, mec, continue de rouler à fond la caisse. Que quelqu'un fasse quelque chose! Ouais, c'est ça. + +[ROK3_15:ROCK3] +'Que quelqu'un fasse quelque chose'. Putain, c'est quoi ces conneries. J'ai connu des tantes plus braves. Vas-y le gros dur, fais quelque chose! + +[ROK3_16:ROCK3] +Ecoute, mec, je suis musicien moi, j'sais pas comment ça fonctionne les bombes. Willy a qu'à sucer le boomshine à la paille. + +[ROK3_17:ROCK3] +Ouais, on m'a dit que ça, tu savais faire. J'étais tout émoustillé ce soir-là, comme tu le sais! + +[ROK3_18:ROCK3] +T'as qu'à filer une paille à Willy! Une paille? C'est le bus de tournée des Love Fist! + +[ROK3_19:ROCK3] +Où est-ce que je vais bien pouvoir trouver une paille? Quel fil, Tommy? Le vert. Y'en a pas de vert. + +[ROK3_20:ROCK3] +Où peut-être qu'il est vert celui-là? T'en vois un vert, toi? + +[ROK3_21:ROCK3] +Oh non! On va crever! Je vois la vie en vert! J'aurais dû me débarrasser de toi qu'en j'en avais l'occasion. + +[ROK3_22:ROCK3] +Chasse la gloire! Capitaliste! Ca fait des années que je te soutiens. Ferme-là, tafiole! + +[ROK3_23:ROCK3] +Une grosse chialeuse, ouais, ferme-là et tire le putain de fil! Lequel? Celui-là... + +[ROK3_24:ROCK3] +NON! On est pas morts. On a pas sauté, mec! + +[ROK3_25:ROCK3] +Tommy, bien joué, mec. Rock and roll, mon pote! On a pas un concert à donner, nous? + +[ROK3_26:ROCK3] +Amasser du blé? Abuser des minettes? LOVE FIST! + +[ROK3_27:ROCK3] +T'as fini avec cette bouteille? + +{=================================== MISSION TABLE SERG1 ===================================} + +[TEX1_A:SERG1] +Entre et pose ton cul sur une des banquettes. + +[TEX1_B:SERG1] +Putain, mon père avait l'habitude de dire qu'à cheval donné, on ne regarde pas les dents et il l'a jamais fait! + +[TEX1_C:SERG1] +Tu veux une larme de bon vieux whisky? + +[TEX1_D:SERG1] +Non merci. + +[TEX1_E:SERG1] +Un mec qui aime avoir les idées claires, c'est bien. + +[TEX1_F:SERG1] +En ce moment, mes affaires tournent pas comme elles le devraient... + +[TEX1_G:SERG1] +C'est la merde! Et je veux nettoyer une partie de cette merde! T'es avec moi? + +[TEX1_H:SERG1] +Euh, ouais. + +[TEX1_I:SERG1] +En fait, j'ai besoin d'un mec obstiné pour me débarrasser d'une merde. + +[TEX1_J:SERG1] +T'as l'air du genre persuasif. + +[TEX1_K:SERG1] +La persuasion c'est ma spécialité. + +[TEX1_L:SERG1] +Bon, ce mec sera au country club du golf. + +[TEX1_M:SERG1] +Ils autorisent pas les flingues, alors ses gardes du corps en auront pas. + +[TEX1_N:SERG1] +Va me massacrer ce salopard. + +[TEX1_O:SERG1] +Tiens, voilà ta carte de membre, mais va falloir te trouver des fringues plus appropriées. + +[TEX1_2:SERG1] +~g~Maintenant, direction le Leaf Links Golf Club. + +[TEX1_0:SERG1] +~g~La cible est à ta portée, en train de jouer au golf. Fais en sorte que ce soit sa dernière partie! + +[TEX1_3:SERG1] +C'est qui ce mec? Occupez-vous de lui! + +[TEX1_6:SERG1] +Quel beau cul! + +[TEX1_7:SERG1] +C'est moi, ça? + +[TEX1_8:SERG1] +Chaque fois que tu ouvres ton caddie, tu reçois automatiquement un club de golf si ton emplacement d'arme de corps à corps est vide. + +[TEX1_9:SERG1] +Attrapez-le! + +[TEX1_10:SERG1] +Tuez-moi ce dingue! + +[TEX1_1:SERG1] +~g~Va chercher des fringues de golfeur chez Jocksport's. + +{=================================== MISSION TABLE SERG2 ===================================} + +[TEXEXIT:SERG2] +~g~Maintenant, casse-toi de Little Haiti! + +[TEX_2A:SERG2] +~g~Parfait! Ils t'ont repéré! + +[TEX_2B:SERG2] +~r~Imbécile! Les gens doivent VOIR un cubain faire le coup! + +[TEX_2C:SERG2] +~g~Va te trouver des fringues de cubain dans Little Havana! + +[TEX_2D:SERG2] +~g~Maintenant, liquide le chef de gang haïtien au salon funéraire Romero! + +[TEX2_A:SERG2] +Tommy, voici Donald Love. Donald, Tommy Vercetti, + +[TEX2_B:SERG2] +la dernière crapule à débarquer ici. + +[TEX2_C:SERG2] +Ouais... Euh... + +[TEX2_D:SERG2] +Donald, ferme-la et écoute, t'apprendras peut-être quelque chose. + +[TEX2_E:SERG2] +Bon. Il y a rien de mieux qu'une bonne guerre des gangs pour faire baisser les prix de l'immobilier. + +[TEX2_F:SERG2] +A part une catastrophe, peut-être, comme une peste biblique ou un truc dans le genre. + +[TEX2_G:SERG2] +Mais ça serait aller un peu trop loin, dans le cas présent. + +[TEX2_H:SERG2] +Tu piges, tête de gland? + +[TEX2_I:SERG2] +Un chef du gang haïtien est mort y'a pas longtemps. C'est apparemment un coup des Cubains, mais personne en est sûr. + +[TEX2_J:SERG2] +Alors on va arranger ça! Tu te déguises en Cubain + +[TEX2_K:SERG2] +et tu fonces foutre le bordel aux funérailles. Tu leur balances la sauce et tu dégages vite fait. + +[TEX2_L:SERG2] +T'as compris, Donald? + +[TEX2_M:SERG2] +C'est comme faire entrer le renard dans le poulailler, hein? + +[TEX2_N:SERG2] +Et puis on attend tranquillement que les prix s'écroulent. + +{=================================== MISSION TABLE SERG3 ===================================} + +[TEX3_A:SERG3] +Regarde un peu, mon pote. J'ai un problème et je compte sur ton aide. + +[TEX3_B:SERG3] +Je suis pas constructeur. + +[TEX3_C:SERG3] +Ben, je pensais surtout à tes talents de démolisseur. + +[TEX3_D:SERG3] +Bon, là, c'est le développement prévu, et ça, + +[TEX3_E:SERG3] +c'est la propriété sur laquelle on a des vues. + +[TEX3_F:SERG3] +T'essayes de me dire que ce nouveau bloc de bureaux est comme qui dirait sur le chemin. + +[TEX3_G:SERG3] +T'as tout compris. + +[TEX3_H:SERG3] +Bon, je vais aller faire un tour en ville pendant un moment + +[TEX3_I:SERG3] +et si la construction de ces bureaux devait faire face à de subits et insurmontables problèmes structurels, alors je... + +[TEX3_J:SERG3] +En tant qu'honnête citoyen, vous vous sentiriez obligé d'intervenir + +[TEX3_K:SERG3] +pour la réhabilitation d'un important secteur de la ville? + +[TEX3_L:SERG3] +Où on peut en trouver d'autres, des mecs dans ton genre? + +[TEX3_1:SERG3] +~g~Utilise l'hélico télécommandé pour transporter des bombes sur les quatre cibles du bâtiment que tu dois détruire. + +[TEX3_2:SERG3] +~g~Tu dois larguer une bombe sur chaque cible, et ce, dans n'importe quel ordre. + +[TEX3_5:SERG3] +~g~Si tu largues une bombe sans succès, tu peux la récupérer pour un nouvel essai. + +[TEX3_7:SERG3] +~g~Tu a alors 7 minutes pour larguer toutes les autres bombes! + +[TEX3_8:SERG3] +~g~Tu as raté la cible! Récupère la bombe et réessaye! + +[TEX3_10:SERG3] +~g~Largue la bombe sur la cible. + +[TEX3_11:SERG3] +Cibles restantes: + +[TEX3_17:SERG3] +~r~Tu as dépassé le temps imparti et le bâtiment n'est pas détruit. + +[TEX3_18:SERG3] +~r~Ton hélico télécommandé a été détruit! Comment vas-tu faire pour transporter les bombes, maintenant? + +[TEX3_19:SERG3] +~r~Tu as largué ta bombe dans l'eau! Tu as besoin des 4 bombes pour détruire le bâtiment! + +[TEX3_20:SERG3] +~g~Ton hélico télécommandé est presque hors de portée! Retourne au bâtiment et termine le travail! + +[TEX3_21:SERG3] +~r~Ton hélico télécommandé est hors de portée! + +[TEX3_24:SERG3] +Appuie sur ~h~~k~~VEHICLE_LOOKLEFT~ ~w~pour faire pivoter l'hélico dans le sens inverse des aiguilles d'une montre. + +[TEX3_25:SERG3] +Appuie sur ~h~~k~~VEHICLE_LOOKLEFT~ ~w~pour faire pivoter l'hélico dans le sens des aiguilles d'une montre. + +[TEX3_27:SERG3] +~g~L'escalier central mène à tous les étages du bâtiment. + +[TEX3_31:SERG3] +~r~Tu as détruit la camionnette qui contenait l'hélico télécommandé et les bombes! + +[TEX3_32:SERG3] +Tu peux ~h~regarder derrière~w~ toi en ~h~appuyant simultanément sur ~k~~VEHICLE_LOOKLEFT~ et ~k~~VEHICLE_LOOKRIGHT~~w~. + +[TEX3_4:SERG3] { reVC update } +~g~Pour larguer une bombe, appuie sur la~h~ ~k~~VEHICLE_FIREWEAPON~~w~. + +[TEX3_29:SERG3] { reVC update } +Pour larguer une bombe, appuie sur la~h~ ~k~~VEHICLE_FIREWEAPON~. + +[TEX3_26:SERG3] +Appuie sur la ~h~~k~~VEHICLE_BRAKE~ ~w~pour réduire la vitesse du rotor et ainsi faire~h~ descendre l'hélicoptère. + +[TEX3_22:SERG3] +Appuie sur la ~h~~k~~VEHICLE_ACCELERATE~ ~w~pour augmenter la vitesse du rotor et ainsi faire~h~ monter l'hélicoptère. + +[TEX3_16:SERG3] +~g~Va jusqu'au camion ~w~TOPFUN ~g~ près du bâtiment à démolir. + +[TEX3_33:SERG3] +Une fois que tu as ramassé une bombe, le radar t'indique la position de la cible par rapport à l'hélico. + +[TEX3_34:SERG3] +Le ~h~point triangulaire vers le haut ~w~indique que la cible se trouve ~h~au-dessus ~w~de l'hélicoptère radiocommandé. + +[TEX3_35:SERG3] +Le ~h~point triangulaire vers le bas ~w~indique que la cible se trouve ~h~en dessous ~w~de l'hélicoptère radiocommandé. + +[TEX3_36:SERG3] +Le ~h~point carré ~w~indique que la cible se trouve ~h~au même niveau ~w~que l'hélicoptère radiocommandé. + +[TEX3_6:SERG3] +~g~Une fois que tu as ramassé une bombe pour la première fois, le compte à rebours s'enclenche. + +[TEX3_28:SERG3] +Pour ~h~ramasser une bombe~w~, manoeuvre l'hélico radiocommandé à côté. Il ne peut transporter qu'une seule bombe à la fois. + +[TEX3_30:SERG3] +~g~Pour ramasser une bombe, manoeuvre l'hélico radiocommandé à côté. Il ne peut transporter qu'une seule bombe à la fois. + +[TEX3_12:SERG3] +~g~Bombe posée! Plus que 3 cibles! Va chercher une autre bombe! + +[TEX3_13:SERG3] +~g~Bombe posée! Plus que 2 cibles! Va chercher une autre bombe! + +[TEX3_14:SERG3] +~g~Bombe posée! Plus qu'une cible! Va chercher une autre bombe! + +[TEX3_15:SERG3] +~r~Compte à rebours enclenché! ~g~Tu dois poser les ~w~4 bombes ~g~avant la fin du temps imparti! + +[TEX3_37:SERG3] +Pousse le ~h~joystick analogique droit vers le haut ~w~pour vitesse la vitesse du rotor et ainsi faire ~h~ monter l'hélicoptère. + +[TEX3_38:SERG3] { reVC update } +{Pousse le ~h~~k~~VEHICLE_ACCELERATE~ ~w~pour augmenter la réduire du rotor et ainsi faire ~h~ descendre l'hélicoptère.} +Pousse le ~h~joystick analogique droit vers la bas ~w~pour augmenter la réduire du rotor et ainsi faire ~h~ descendre l'hélicoptère. + +[TEX3_39:SERG3] +~g~Appuie sur la touche ~h~~k~~VEHICLE_HANDBRAKE~ ~g~pour larguer une bombe. + +[TEX3_40:SERG3] +Appuie sur la touche ~h~~k~~VEHICLE_HANDBRAKE~ ~w~pour larguer une bombe. + +[TEX3_23:SERG3] +Appuyer sur les touches ~h~~k~~VEHICLE_TURRETUP~~w~ et ~h~~k~~VEHICLE_TURRETDOWN~~w~ pour orienter l'hélicoptère dans la direction souhaitée. + +{=================================== MISSION TABLE TAXI1 ===================================} + +[FARES:TAXI1] +CLIENTS: + +[TAXI1:TAXI1] +Cherche une course. + +[TSCORE2:TAXI1] +~1~$ + +[IN_ROW:TAXI1] +~1~ DE SUITE! Bonus : ~1~$ + +[TAXI3:TAXI1] +~r~Ton client est parti terrorisé! + +[TAXI7:TAXI1] +~r~Ta voiture est endommagée. Fais-la réparer. + +[TAXI4:TAXI1] +La course est finie! + +[TAXI5:TAXI1] +BONUS DE VITESSE! + +[TAXI6:TAXI1] +La mission taxi est finie + +[TAXIH1:TAXI1] +Arrête-toi près d'un piéton mis en évidence pour qu'il monte, puis conduis-le à destination avant la fin du temps imparti. + +[FARE1:TAXI1] +~g~Destination ~w~'le Pole Position Club' ~g~à Ocean Beach. + +[FARE3:TAXI1] +~g~Destination ~w~'le Marina' ~g~à Ocean Beach. + +[FARE4:TAXI1] +~g~Destination ~w~'Ammu-Nation' ~g~à Ocean Beach. + +[FARE5:TAXI1] +~g~Destination ~w~'la quincaillerie' ~g~à Washington Beach. + +[FARE6:TAXI1] +~g~Destination ~w~'le centre commercial de North Point' ~g~à Vice Point. + +[MFARE1:TAXI1] +~g~Destination ~w~'Ammu-Nation' ~g~dans le Centre. + +[MFARE2:TAXI1] +~g~Destination ~w~'le Terminal' ~g~à l'aéroport International Escobar. + +[WFARE3:TAXI1] +~g~Destination ~w~'Sunshine Autos' ~g~dans Little Havana. + +[WFARE4:TAXI1] +~g~Destination ~w~'les taxis Kaufman' ~g~dans Little Haiti. + +[WFARE5:TAXI1] +~g~Destination ~w~'la quincaillerie' ~g~dans Little Havana. + +[WFARE6:TAXI1] +~g~Destination ~w~'Howlin Petes Bike Emporium' ~g~dans le Centre. + +[FARE7:TAXI1] +~g~Destination ~w~'Les bijoutiers' ~g~à Vice Point. + +[FARE8:TAXI1] +~g~Destination ~w~'la plage' ~g~à Ocean Beach. + +[FARE9:TAXI1] +~g~Destination ~w~'la plage' ~g~à Washington Beach. + +[FARE10:TAXI1] +~g~Destination ~w~'la plage' ~g~à Vice Point. + +[FARE11:TAXI1] +~g~Destination ~w~'l'hôpital' ~g~à Ocean Beach. + +[FARE12:TAXI1] +~g~Destination ~w~'l'hôpital' ~g~à Vice Point. + +[FARE13:TAXI1] +~g~Destination ~w~'le commissariat de police' ~g~à Washington Beach. + +[FARE14:TAXI1] +~g~Destination ~w~'le commissariat de police' ~g~à Vice Point. + +[FARE15:TAXI1] +~g~Destination ~w~'le restaurant de pizza' ~g~à Vice Point. + +[WFARE7:TAXI1] +~g~Destination ~w~'le commissariat de police' ~g~à Little Havana. + +[WFARE8:TAXI1] +~g~Destination ~w~'le commissariat de police' ~g~à Downtown. + +[WFARE9:TAXI1] +~g~Destination ~w~'l'hôpital' ~g~à Downtown. + +[WFARE10:TAXI1] +~g~Destination ~w~'l'hôpital' ~g~à Little Havana. + +[WFARE11:TAXI1] +~g~Destination ~w~'le stade' ~g~à Downtown. + +[WFARE12:TAXI1] +~g~Destination ~w~'le restaurant de pizza' ~g~à Little Haiti. + +[WFARE13:TAXI1] +~g~Destination ~w~'le restaurant de pizza' ~g~à Downtown. + +[WFARE14:TAXI1] +~g~Destination ~w~'les docks' ~g~à Viceport. + +[WFARE15:TAXI1] +~g~Destination ~w~'la pharmacie' ~g~à Little Haiti. + +[FARE2:TAXI1] +~g~Destination ~w~'le Malibu club' ~g~à Vice Point. + +{=================================== MISSION TABLE TAXICUT ===================================} + +[TAXC_A:TAXICUT] +J'imagine que t'es le nouveau proprio. + +[TAXC_B:TAXICUT] +D'où tu sors? La Mafia? Le Cartel? T'as pas l'air mexicain... + +[TAXC_C:TAXICUT] +Quoi qu'il en soit, tu ferais mieux de continuer avec le barratin comme quoi les choses vont changer, + +[TAXC_D:TAXICUT] +peut-être même menacer des conducteurs... + +[TAXC_E:TAXICUT] +Vas-y mollo avec Ted là-bas, on vient juste de lui soigner son hernie. + +[TAXC_F:TAXICUT] +Eh bien, ouais. Les choses vont changer dans le coin, madame. + +[TAXC_G:TAXICUT] +Arrête tes conneries, petit. Vaut mieux que tu me laisses faire. + +[TAXC_H:TAXICUT] +Ca fait des années que je suis dans ce genre de biz. + +[TAXC_I:TAXICUT] +Ecoutez tous. + +[TAXC_J:TAXICUT] +On a une nouvelle direction et les choses vont encore changer dans le coin. + +[TAXC_K:TAXICUT] +Notre nouvelle direction, la... + +[TAXC_L:TAXICUT] +Tu fais partie de quel gang? + +[TAXC_M:TAXICUT] +En fait, je fais pas partie d'un gang... + +[TAXC_N:TAXICUT] +C'est quoi ton foutu nom, petit? + +[TAXC_O:TAXICUT] +Vercetti, Tommy Vercetti. + +[TAXC_P:TAXICUT] +Notre nouvelle direction, le gang Vercetti, + +[TAXC_Q:TAXICUT] +va veiller à nous éviter les emmerdes. + +[TAXC_R:TAXICUT] +Capiche? Terminé! + +[TAXC_S:TAXICUT] +Qu'est-ce que t'as pensé du 'capiche'? Moi, j'ai bien aimé le 'capiche'. + +[TAXC_T:TAXICUT] +Bon, alors c'est comme ça que ça marchait avant + +[TAXC_U:TAXICUT] +et on va continuer à faire tourner l'entreprise comme ça. + +[TAXC_V:TAXICUT] +Si on a des ennuis avec une compagnie concurrente, tu leur flanques une dérouillée. + +[TAXC_W:TAXICUT] +Puis ils nous flanquent une dérouillée. + +[TAXC_X:TAXICUT] +puis tu leur flanques une dérouillée, + +[TAXC_Y:TAXICUT] +et cetera et cetera. T'as pigé? + +[TAXC_Z:TAXICUT] +Euh, ouais, je crois... + +[TAXC_A1:TAXICUT] +Prends simplement un taxi au garage si tu te sens de bosser. + +{=================================== MISSION TABLE TAXIWA1 ===================================} + +[OUTTIME:TAXIWA1] +~r~T'es trop lent, mec, trop lent! + +[TAX1_1:TAXIWA1] +Ok, on a un client rupin qui a besoin d'être ramassé à Starfish island, ça intéresse quelqu'un? + +[TAX1_2:TAXIWA1] +Ici, Tommy, je prends! + +[TAX1_3:TAXIWA1] +C'est mon client, dégage! + +[TAX1_4:TAXIWA1] +Allez, vite, monte! + +[TAX1_5:TAXIWA1] +Ok, ok, mais ne me faites pas de mal! + +[TAXW1_1:TAXIWA1] +~g~Va prendre le V.I.P. sur Starfish Island. + +[TAXW1_2:TAXIWA1] +~g~Ramène le V.I.P.! Bousille l'autre bagnole! + +[TAXW1_3:TAXIWA1] +~r~Le V.I.P. est mort! + +[TAXW1_4:TAXIWA1] +~r~Le V.I.P. a été déposé! + +[TAXW1_6:TAXIWA1] +~g~Emmène le V.I.P. à l'aéroport! + +{=================================== MISSION TABLE TAXIWA2 ===================================} + +[TAX2_1:TAXIWA2] +Appel à toutes les voitures, on perd des clients dans toute la ville! Qu'est-ce que vous foutez, les gars? + +[TAX2_2:TAXIWA2] +Les taxis VC nous les prennent! Ils ont trop de voitures, on peut pas rivaliser! + +[TAX2_3:TAXIWA2] +Vercetti, si t'es en ville en train d'écouter, va falloir mettre quelques taxis VC hors circuit ou c'est la faillite! + +[TAXW2_1:TAXIWA2] +~g~Détruis 3 taxis concurrents! + +{=================================== MISSION TABLE TAXIWA3 ===================================} + +[TAX3_1:TAXIWA3] +Voiture 13, on a une demoiselle Cortez à prendre dans le centre, elle vous a spécialement demandé. + +[TAX3_2:TAXIWA3] +Ok, je prends. Voiture 13 en course! + +[TAX3_3:TAXIWA3] +Hmmmm, aucun signe de Mercedes... + +[TAXW3_3:TAXIWA3] +~g~Bousille le taxi leader! + +[TAXW3_2:TAXIWA3] +~g~Reste vivant jusqu'à la fin du temps imparti. + +[TAX_AS1:TAXIWA3] +COMPAGNIE DE TAXIS ACQUISE + +[TAX_AS2:TAXIWA3] +~G~Les taxis Kaufman génère dorénavant un revenu de $~1~ maximum. Pense à récupérer le fric régulièrement. + +[TAX3_4:TAXIWA3] +L'heure est venue pour l'ange gardien des taxis Kaufman de froisser de la tôle! + +[TAX3_5:TAXIWA3] +Hé mec, j'vais te bousiller ta caisse! + +{ reVC updates } +{ new languages } +[FEL_JAP] +JAPONAIS + +[FEL_POL] +POLONAIS + +[FEL_RUS] +RUSSE + +{ 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] +REJOUER MISSION + +[FESZ_RM] +REJOUER? + +[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 + +[FEC_IVP] +INVERT PAD VERTICALLY + +[FEM_NON] +NONE + +[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 + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_AUT] { aspect ratio related } +AUTO + +[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/gxt/miami/german.txt b/gxt/miami/german.txt new file mode 100644 index 00000000..27e91def --- /dev/null +++ b/gxt/miami/german.txt @@ -0,0 +1,14726 @@ +{ + 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. +} + +[RAMPAGE] +AMOKLAUF!! + +[RAMP_F] +AMOKLAUF FEHLGESCHLAGEN!! + +[RAMP_P] +AMOKLAUF BESTANDEN!! + +[RAMP_A] +ALLE AMOKLÄUFE BESTANDEN!! + +[PAGE_01] +Dein Job: ~1~ Gang-Mitglieder in 2 Minuten! + +[PAGE_02] +Zerstöre ~1~ Fahrzeuge in 2 Minuten! + +[PAGE_03] +Erledige ~1~ Gang-Mitglieder in 2 Minuten im Vorbeifahren! + +[PAGE_04] +Überfahre ~1~ Gang-Mitglieder in 2 Minuten! + +[PAGE_05] +Dein Job: ~1~ Gang-Mitglieder in 2 Minuten! + +[SENTXS] +Sentinel XS + +[MAP_LEG] +Legende + +[VCNMAV] +VCN Maverick + +[LG_01] +Position des Spielers + +[LG_02] +Avery Carrington + +[LG_03] +Biker-Kontaktpunkt + +[LG_04] +Colonel Cortez + +[LG_05] +Ricardo Diaz + +[LG_06] +Kent Paul + +[LG_07] +Rechtsanwalt + +[LG_08] +Phil Cassidy + +[LG_09] +Bootswerft + +[LG_10] +Malibu Club + +[LG_11] +Kubaner + +[LG_12] +Filmstudio + +[LG_13] +AmmuNation + +[LG_14] +Haitianer + +[LG_15] +Eisenwarenladen + +[LG_16] +Versteck + +[LG_17] +Eiscreme + +[LG_18] +Kaufman-Taxis + +[LG_19] +Love Fist + +[LG_20] +Druckerei + +[LG_21] +Immobilie + +[LG_22] +Pay 'n' Spray + +[LG_23] +Bekleidungsgeschäft + +[LG_24] +Tommys Villa + +[LG_25] +Telefon + +[LG_26] +Radiosender Wildstyle + +[LG_27] +Radiosender Flash FM + +[LG_28] +Radiosender KChat + +[LG_29] +Radiosender Fever 105 + +[LG_30] +Radiosender VRock + +[LG_31] +Polizeifunk-Zentrale + +[LG_32] +Radiosender Espantoso + +[LG_33] +Radiosender Emotion 98.3 + +[LG_34] +Radiosender Wave 103 + +[LG_36] +Sun Yard + +[LG_37] +Stripper-Bar + +[MAP_YAH] +DU BIST HIER + +[TAXSHRT] +~g~Du kannst dieses Kaufman-Taxi nehmen, statt selbst Auto zu fahren. Das kostet dich $9. + +[MOB_09D] +Vielleicht hab ich Leo ja erledigt und mir sein Handy genommen. Hast du schon mal daran gedacht, du Penner? + +[FE_MLG] +KARTENLEGENDE + +[FED_RDR] +RADAR-MODUS + +[FED_HUD] +HUD-MODUS + +[FED_RDL] +GROSS + +[FED_RDB] +NUR SYMBOLE + +[FED_HUF] +INFOS EIN- & AUSBLENDEN + +[FEST_HV] +Höchstes Bürgerwehr-Missions-Level + +[BRIBE1] +Du hast soeben Polizei-Bestechungsgeld aufgenommen, dein Fahndungslevel verringert sich damit um einen Stern. + +[CLOHELP] +Saubere Klamotten!! + +[SUNSHIN] +Sunshine Autos + +[CHERRYP] +Cherry Popper Eiscreme + +[KAUFCAB] +Kaufman-Taxis + +[BOATYAR] +Die Bootswerft + +[WANT_L] +Dein Fahndungslevel ist bis auf weiteres aufgehoben. Solltest du ein Verbrechen begehen, während die Sterne blinken, wird dein volles Fahndungslevel wieder aktiv. + +[PICK1] +Kugelsichere Weste im Ocean View Hotel angeliefert! + +[HOTRNG] +HOTRING + +[BLODRNG] +CHAOS-DERBY + +[DIRTRNG] +DIRTRING + +[FEC_ABR] +Beschleunigen, Bremsen oder Zurücksetzen + +[FEI_BTU] +; = - + +[FEI_SCR] +Scrollen + +[SKUMBUY] +Skumole Shack gekauft: $ ~1~ + +[SKUM_L] +Drücke die ~h~L1-Taste~w~, um Skumole Shack zu kaufen. Preis: $~1~ + +[SKUM_T] +Drücke die ~t~"-Taste~w~, um Skumole Shack zu kaufen. Preis: $~1~ + +[SKUM_C] +Drücke die ~o~|-Taste~w~, um Skumole Shack zu kaufen. Preis: $~1~ + +[LG_35] +Ziel + +[IN_VEH] +~g~Hey! Zurück ins Auto!! + +[HEY] +~g~Keine Alleingänge. Halt die Gang beisammen! + +[HELP3] +Du kannst nur kurze Zeit sprinten, ohne müde zu werden. + +[HELP4_D] +Drücke den~h~ rechten Analog-Stick nach oben, um zu ~h~beschleunigen. + +[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. + +[HELP7_A] +Halte die~h~ ~k~~PED_LOCK_TARGET~ ~w~gedrückt, um mit dem Präzisionsgewehr zu zielen. + +[HELP7_D] +Halte die~h~ ~k~~PED_LOCK_TARGET~ ~w~gedrückt, um mit dem Präzisionsgewehr zu zielen. + +[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. + +[HORN] +~g~Drück auf die Hupe. + +[NOMONEY] +~g~Du brauchst mehr Cash! + +[REWARD] +BELOHNUNG $~1~ + +[M_FAIL] +MISSION FEHLGESCHLAGEN! + +[M_PASS] +MISSION ERFÜLLT! $~1~ + +[DEAD] +AUSSER GEFECHT! + +[BUSTED] +VERHAFTET! + +[WEATHE1] +WETTER AUF SONNIG UMSTELLEN + +[WEATHE2] +WETTER AUF SEHR SONNIG UMSTELLEN + +[WEATHE3] +WETTER AUF BEWÖLKT UMSTELLEN + +[WEATHE4] +WETTER AUF REGNERISCH UMSTELLEN + +[WEATHE5] +WETTER AUF NEBLIG UMSTELLEN + +[WEATHE6] +WETTER NORMAL + +[NUMBER] +~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 + +[IMPORT1] +Geh nach draußen und warte auf dein Fahrzeug. + +[PAGEB11] +Flammenwerfer in 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. + +[SAVE1] +Stell dich in die Markierung. 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 Ammu-Nation, um eine Waffe zu kaufen. + +[R_TIME] +ZEIT: + +[PROP_1] +Du hast nicht genug Cash für dieses Objekt. + +[PROP_2] +Während einer Mission kannst du keine Objekte kaufen. + +[IND_ZON] +Vice City Beach + +[COM_ZON] +Vice City Mainland + +[BEACH1] +Ocean Beach + +[BEACH2] +Washington Beach + +[BEACH3] +Vice Point + +[GOLFC] +Leaf Links Golfclub + +[STARI] +Starfish Island + +[DOCKS] +Viceport + +[HAVANA] +Little Havana + +[HAITI] +Little Haiti + +[PORNI] +Prawn Island + +[DTOWN] +Downtown + +[VICE_C] +Vice City + +[A_PORT] +Escobar Inter. Airport + +[JUNKY] +Schrottplatz + +[PISTOL] +Pistole + +[PYTHON] +.357 + +[UZI] +Uz-1 + +[TEC9] +Tec 9 + +[M4] +M4 + +[INGRAM] +Mac + +[MP5] +MP + +[RUGER] +Kruger + +[SNIPE] +Präzisionsgewehr + +[GRENADE] +Granaten + +[SHOTGN1] +Schrotflinte + +[SHOTGN2] +S.P.A.S. 12 + +[SHOTGN3] +Abgesägte Schrotflinte + +[ARMOUR] +Kugelsichere Weste + +[LASER] +.308 Präzisionsgewehr + +[BASEBAT] +Baseballschläger + +[HAMMER] +Hammer + +[SCREWD] +Schraubenzieher + +[CLEVER] +Fleischerbeil + +[MACHETE] +Machete + +[KNIFE] +Messer + +[KATANA] +Katana + +[CHAINSA] +Kettensäge + +[G_COST] +$~1~ + +[CAR_1] +Krankenwagen + +[MALIBU] +Malibu Club + +[MANSION] +Diaz' Haus + +[TMANS] +Vercetti Estate + +[STRIP] +Pole Position Club + +[MALL1] +North Point Einkaufszentrum + +[BANKINT] +El Banco Corrupto Grande + +[RANGE] +Schießstand + +[POL_HQ] +Vice City Polizeihauptquartier + +[INT_B] +Ein alter Freund + +[INTB_1] +~g~Begib dich in die Anwaltskanzlei. + +[LAW_1] +Die Party + +[LAW_2] +Dunkle Gassen + +[LAW_3] +Die Geschworenen + +[LAW_4] +Aufruhr + +[COL_1] +Der Verräter + +[COL_2] +Kugelhagel im Einkaufszentrum + +[COL_3] +Die Schutzengel + +[COL_4] +Zu Befehl, Sir! + +[COL_5] +Alle Mann an Deck! + +[COK_1] +Die Jagd + +[COK_2] +Phnom Penh '86 + +[COK_3] +Das schnellste Boot + +[COK_4] +Angebot & Nachfrage + +[KENT_1] +Die Befreiungsaktion + +[ASS_1] +Pizza Mortale + +[BUD_1] +Fette Beute + +[BUD_2] +Zoff in der Bar + +[BUD_3] +Cop-Land + +[CAP_1] +Der Eintreiber + +[FIN_1] +Freunde und andere Feinde + +[BANK_1] +Kein Entkommen? + +[BANK_2] +Der Scharfschütze + +[BANK_3] +Der Fahrer + +[BANK_4] +Der Coup + +[CNT_1] +Der Singvogel + +[CNT_2] +Der Kurier + +[PORN_1] +Alle meine Pferdchen + +[PORN_2] +Der Dildo-Jet + +[PORN_3] +Ein Mann namens Martha + +[PORN_4] +Heiße Lightshow + +[TAX_1] +Kaufman-Taxis + +[TAXI_1] +V.I.P + +[TAXI_2] +Konkurrentenjagd + +[TAXI_3] +Das Taxi-Inferno + +[ICE_1] +Eiscreme und andere Leckereien + +[TEX_1] +Schlagende Argumente + +[TEX_2] +Das Begräbnis + +[TEX_3] +Schutt und Asche + +[PHIL_1] +Der Waffenschieber + +[PHIL_2] +TNT-Whiskey + +[BIKE_1] +Wheels of Steel + +[BIKE_2] +Chaos-City + +[BIKE_3] +Big Bakers Bike + +[ROCK_1] +Love Juice + +[ROCK_2] +Der Psychokiller + +[ROCK_3] +Die PR-Tour + +[ROCK_4] +Love Fist! + +[HAT_1] +Mysteriöses Pulver + +[HAT_2] +Fliegende Bomben + +[HAT_3] +Schmutzige Methoden + +[CUB_1] +Stunt-Boot-Action + +[CUB_2] +Kanonenfutter + +[CUB_3] +Die Seeschlacht + +[CUB_4] +Trojanisches Voodoo + +[JOB_1] +Der Pizza-Lieferant + +[JOB_2] +Ein bedauerlicher Unfall + +[JOB_3] +Die rasende Schrottfabrik + +[JOB_4] +Trouble am Check-In + +[JOB_5] +Gefeuert! + +[ANSWER] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um den Anruf auf deinem Handy entgegenzunehmen. + +[MOB_01A] +Hey, mein Alter! Paul hier. Ich hätte da vielleicht was für dich, aber das müssen wir unter vier Augen besprechen. + +[MOB_01B] +Ich bin im Malibu und lass es mir gutgehen. + +[MOB_01C] +Ich denke, ich hab einen gut bei dir, wenn du das hörst, Kumpel. Bis gleich. + +[MOB_02A] +Hey! Hallo, Tommy? Tommy! + +[MOB_02B] +In der Druckerei ist irgendwas im Busch. Fahr mal rüber und kümmere dich darum. + +[MOB_02C] +Es scheint ziemlichen Ärger zu geben. Ich muss Schluss machen. + +[MOB_03A] +Mr. Vercetti? Ich habe hier einen unterschriebenen Wisch, + +[MOB_03B] +der besagt, dass Sie alle Schulden von 'BJ's Autos' übernehmen. + +[MOB_03C] +Nach BJ's plötzlichem Verschwinden hab ich keine andere Wahl, + +[MOB_03D] +als Sie für seine Verbindlichkeiten zur Rechenschaft zu ziehen. + +[MOB_03E] +Bis Sie die volle Summe beglichen haben, + +[MOB_03F] +sollte Ihnen klar sein, dass Vice City ein heißes Pflaster für Sie ist. + +[MOB_04A] +Wie geht's, mein Alter? + +[MOB_04B] +Ich hab vergessen, dir zu sagen, dass wir für das Konzert noch ein paar Ordner brauchen. + +[MOB_04C] +Da gibt's so 'ne Biker-Gang, die wär super für die Publicity. + +[MOB_04D] +Wenn du mir das arrangierst, besorg ich dir 'n VIP-Pass für den Abend. Ok? + +[MOB_05A] +Gut gemacht, Tommy. Schön, den Ofen wiederzuhaben. + +[MOB_05B] +Sag Mr. Kent Paul, dass er seine Ordner für das Konzert kriegt. + +[MOB_05C] +Du hast mein Wort darauf. + +[MOB_05D] +Halt die Ohren steif. + +[MOB_06A] +Tommy, man hört so allerlei über dich, mein Junge. + +[MOB_06B] +Wenn du willst, kocht Tante Poulet dir ein leckeres Süppchen, dann kannst du mal abschalten, hm? + +[MOB_06C] +Komm doch die Tage mal bei mir vorbei, ok, Tommy? + +[MOB_08A] +Tommy, ich dachte mir, du könntest meinen Rat als Geschäftsmann brauchen. + +[MOB_08B] +Wenn du ein Unternehmen am Laufen hast, musst du einmal pro Woche die Einnahmen kassieren gehen. + +[MOB_08C] +Sonst werden deine Leute übermütig und versuchen, in die eigene Tasche zu wirtschaften. Ok? + +[MOB_08D] +Hey, ich weiß selbst, wie man so was macht, Ken, ok? + +[MOB_08E] +Ok, ok. Ich weiß ja, dass du Bescheid weißt. + +[MOB_08F] +Ich meinte ja nur, damit du weißt, dass ich im Zweifelsfall auch Bescheid weiß. + +[MOB_08G] +Nur für alle Fälle, mein Alter! + +[MOB_08H] +Wenn du meinst, Ken... + +[MOB_09A] +Hey, Leo! Ich hab Arbeit für dich! + +[MOB_09B] +Hier ist nicht Leo. + +[MOB_09C] +Hey, wenn Leo erfährt, dass du mit seinem Handy telefonierst, bist du fällig. + +[MOB_09E] +Du hast Leo erledigt? Du musst Mumm haben - willst du für mich arbeiten? + +[MOB_09F] +Komm ins Café von meinem Vater in Little Havana, dann können wir reden. + +[MOB_10A] +Tommy! Du musst mir einen Gefallen tun. + +[MOB_10B] +Steve! Was machen die Dreharbeiten? + +[MOB_10C] +Gut. Ich- äh, WIR brauchen noch eine Autoverfolgungsjagd, aber unser Budget ist knapp. + +[MOB_10D] +Ich hab in der Stadt verteilt ein paar Karren stehen. Du weißt, was zu tun ist. + +[MOB_10E] +Ok, Steve, ich halt die Augen offen. Bis dann. + +[MOB_11A] +Hallo, Söhnchen. Ich wollte dir kurz 'nen kleinen Tipp geben. + +[MOB_11B] +Hi, Avery. Worum geht's denn? + +[MOB_11C] +Man kann in dieser Stadt viel Geld machen, wenn man die richtigen Immobilien hat. Klingelt's? + +[MOB_11D] +Ich denke schon. + +[MOB_11E] +Also, halt die Augen offen, dann könnte sich dir die ideale Gelegenheit bieten. Bis bald. + +[MOB_11F] +Ciao, Avery. + +[MOB12_A] +Hey, Tommy. Avery hier. Hör mal, ich hab gerade alle Hände voll zu tun, + +[MOB12_B] +und ein Bevollmächtigter von mir müsste zu den Gator Keys eskortiert werden. + +[MOB12_C] +Ich will da ein Stück Land kaufen und schick ihn hin, um den Deal abzuschließen. + +[MOB12_D] +Könntest du dafür sorgen, dass er gut dort ankommt? + +[MOB12_E] +Mach ich doch glatt, Avery. Wo soll ich ihn abholen? + +[MOB12_F] +Er hat gerade noch an der Baustelle zu tun. Hol ihn doch am besten dort ab. + +[MOB12_G] +Kein Problem. Bis dann, Avery. + +[MOB13_A] +Vercetti? VERCETTI!! Verdammt, Mann, Sie müssen mir helfen! + +[MOB13_B] +Mr. Moffat? Wie geht's der Familie? + +[MOB13_C] +Zur Hölle mit Ihnen, hören Sie! + +[MOB13_D] +Tja, war nett, mit Ihnen zu plaudern... + +[MOB13_E] +WARTEN SIE! Vercetti - Tommy, kann ich Tommy zu Ihnen sagen? + +[MOB13_F] +Wir sind beide Geschäftsleute. Sie erkennen doch ein gutes Geschäft sofort, oder? + +[MOB13_G] +Ich hab keine Zeit für Palaver. Kommen Sie zum Punkt. + +[MOB13_H] +GELD. Geld ist der Punkt, verdammt! + +[MOB13_I] +Ich bin den Kerlen nochmal entwischt, aber sie sitzen mir im Nacken. Die machen sich einen Spaß daraus! + +[MOB13_J] +Ich bin in einer Telefonzelle, irgendwo in diesem verdammten Loch. + +[MOB13_K] +Holen Sie mich hier raus, bevor sie mich schnappen und-... oh Gott... + +[MOB13_L] +Ich bin leider ausgebucht bis-... + +[MOB13_M] +Nein! Verarschen Sie mich nicht, haben Sie Erbarmen! Kein Mensch hat das verdient! + +[MOB13_N] +Ich flehe Sie an, Tommy, auf den Knien flehe ich Sie an! Bitte... + +[MOB13_O] +Naja, ich könnte 'nen kurzen Abstecher machen. Vielleicht finde ich Sie... + +[MOB13_P] +Oh Gott, sie kommen! Um Himmels Willen, beeilen Sie sich! + +[MOB_14A] +Hey, Tommy, hör zu, das wird dir gefallen. + +[MOB_14B] +Ein Vögelchen hat mir gesteckt, dass das Vice City-Spezialkommando in einem renommierten Finanzinstitut ein Schließfach hat. + +[MOB_14C] +Da sind die Schmiergelder drin, die sie über die Jahre kassiert haben. + +[MOB_14D] +So 'ne Art Altersversorgung für die ganze Mannschaft. + +[MOB_14E] +Sollte dir diese Info je helfen, dir was von dem Kuchen anzueignen, + +[MOB_14F] +würdest du dich sicher verpflichtet fühlen, mir 'n Stück davon abzugeben? + +[MOB_14G] +Ich werd dich nicht vergessen. Danke, Kent. + +[MOB_14H] +'Paul'heiße ich, Scherzkeks. Ich KOMME aus Kent, Nähe London. + +[MOB_14I] +Mein geographisches Wissen über England ist eben nicht mehr, was es war. + +[MOB15_A] +Tommy, Alter. Hier ist Paul aus Kent. + +[MOB15_B] +Unten im Malibu, da sind ein paar Keulen, die haben ein Auge auf dich geworfen. + +[MOB15_C] +Ich verstehe nur Bahnhof. + +[MOB15_D] +Hasen. Miezen. Na, Puppen, eben. Coole Babes, keine Professionellen, oder so. + +[MOB15_E] +Du solltest mal kommen und sie dir ansehen. + +[MOB16_A] +Tommy, hier Paul. Wie geht's, mein Freund? + +[MOB16_B] +Was willst du, Paul? Ich brauch keine getürkten Designer-Klamotten. + +[MOB16_C] +Sehr witzig. Du weißt, dass ich mit getürkter Ware nichts am Hut habe. + +[MOB16_D] +Wollte nur hören, ob ich nicht 'ne Rolle in einem von deinen Filmen kriegen könnte. + +[MOB16_E] +In England habe ich viel einschlägiges Zeug gedreht. + +[MOB16_F] +Ich hab mehr zu bieten als du, mein Alter. + +[MOB16_G] +Paul, danke für das Angebot. Ich komm auf dich zurück. + +[MOB16_H] +Lass mich nicht hängen. Denk dran, was ich alles für dich getan habe. + +[MOB16_I] +Das versuch ich ja grade zu vergessen. + +[MOB17_A] +Tommy Vercetti. Wie geht's, großer Meister? + +[MOB17_B] +Man hört so einiges über dich. Bist jetzt 'ne große Nummer in der Stadt, hä? + +[MOB17_C] +Paul, du bist betrunken. + +[MOB17_D] +Nein, du Trottel, ich bin nicht betrunken. + +[MOB17_E] +Hab mir nur ein paar Ladungen Stoff gegeben, war seit ein paar Tagen nicht im Bett. + +[MOB17_F] +Und du brauchst mich nicht dumm anzureden. + +[MOB17_G] +Ich bin nicht irgendwer. Wer hat dir denn in dieser Stadt den Weg geebnet? Ich! + +[MOB17_H] +Tatsächlich? + +[MOB17_I] +Tatsächlich! + +[MOB17_J] +Paul, reg dich ab. Ich hatte viel zu tun. Sei kein Idiot. + +[MOB17_K] +Ich bin kein Idiot. Das haben sie schon im Jugendknast gesagt. + +[MOB17_L] +Wenn du Ärger haben willst, Freundchen, den kannst du haben! + +[MOB17_M] +Tommy, bitte! Du warst meine große Hoffnung. Bitte, mach dich nicht lustig über mich! + +[MOB17_N] +Paul, schlaf mal 'ne Runde. Im Ernst. + +[MOB18_A] +Tommy, hier Paul. Wie geht's, Alter? Hey, ich dachte mir, das musst du hören... + +[MOB18_B] +Echt der Hammer. Du glaubst nicht, was mir für 'ne Puppe über den Weg gelaufen ist. + +[MOB18_C] +'ne Bordsteinschwalbe, oder sowas. Unten in Little Havana. + +[MOB18_D] +Sagt, sie heißt Mercedes oder so ähnlich. Wahnsinn, Alter. Die Puppe musst du dir geben. + +[MOB18_E] +Da würde 'nen Toter Hormonkoller kriegen. Sie sagt, ich wär der beste, den sie je hatte. + +[MOB18_F] +Halt die Augen nach ihr offen. Bis dann. + +[MOB19_A] +Tommy, hier KP - Kent Paul. Ich hab läuten hören, dass jemand dich leimen will. + +[MOB19_B] +Also, sei wachsam, mein Freund. Und kein Sterbenswörtchen, dass du das von mir weißt. + +[MOB_20A] +Hallo, Tommy. Hier Paul. Ich höre, dass du ein paar Leuten auf den Schlips getreten bist. + +[MOB_20B] +Irgendjemand sieht es anscheinend nicht gern, dass du auf einmal den großen Zampano spielst. + +[MOB_20C] +Also, sag nicht, ich hätte dich nicht gewarnt. Es rächt sich, wenn man's übertreibt. + +[MOB_20D] +Jedenfalls ist angeblich ein Kopfgeld auf dich ausgesetzt und es ist schon jemand hinter dir her. + +[MOB_20E] +Also pass auf dich auf. Und vergiss mich nicht ganz. + +[MOB71_A] +Tommy, Thomas, hier Cortez. Wie geht's? + +[MOB71_B] +Es bleibt spannend. Und selbst? + +[MOB71_C] +Ein ewiger Kampf, Tommy. Entschuldigen Sie die schlechte Verbindung, wir hatten wieder einen Putschversuch. + +[MOB71_D] +Es gibt keine anspruchsvollere Geliebte als das Volk. + +[MOB71_E] +Seit ich aus Vice City zurück bin, gab's drei Revolutionen und vier Staatsstreiche. + +[MOB71_F] +Zum Glück bin ich jedesmal befördert worden. + +[MOB71_G] +Ich wollte Sie wegen Mercedes etwas fragen. + +[MOB71_H] +OK. Was ist mit ihr? + +[MOB71_I] +Ich hör Geschichten über sie und weiß nicht, was ich davon halten soll. + +[MOB71_J] +Vielleicht wollen mich alle demütigen. + +[MOB71_K] +Vielleicht wird sie übermütig, seit ich weg musste, sagen Sie mir nur eins, Tommy. Ist das wahr?. + +[MOB71_L] +sagen Sie mir nur eins, Tommy. Ist das wahr? + +[MOB71_M] +Ist was wahr? + +[MOB71_N] +Die Geschichten, die ich höre? Will sie wirklich Anwältin werden? + +[MOB71_O] +Welche Schande, Tommy. Wir Cortez sind eine stolze Familie und würden nie einer Tochter erlauben, Anwältin zu werden. + +[MOB71_P] +Sagen Sie mir, dass es nicht wahr ist. Das ertrage ich nicht. + +[MOB71_Q] +Colonel, ich versichere Ihnen, dass Mercedes niemals Anwältin wird. Keine Angst. + +[MOB71_R] +Danke, Tommy - das wäre zu viel der Schande. Sie ist eine Dame, keine Schmarotzerin. + +[MOB71_S] +Ich weiß, Colonel + +[MOB71_T] +Tommy, Sie müssen jetzt entschuldigen, gerade kommt der neue Innenminister. + +[MOB71_U] +Ich hab seinen Vater vor Jahren bei einem Putschversuch erledigt. Ich muss mich gut mit ihm stellen. Wiederhören. + +[MOB22_A] +Tommy, Sie erweisen sich als sehr nützlich, mein Freund. + +[MOB22_B] +Danke, Cortez. Was ist mit meinem Deal? + +[MOB22_C] +Tommy, ich mühe mich ohne Unterlass, um diesem Sumpf von elenden Lügen und Intrigen auf den Grund zu gehen. + +[MOB22_D] +Sie haben mein Wort darauf. Einstweilen möchte ich + +[MOB22_E] +Ihnen den Dank meines Volkes aussprechen, für das Sie so viel getan haben. + +[MOB_25A] +Tommy, hier Cortez. Die Franzosen machen mir Ärger. Und wie. + +[MOB_25B] +Verdammte Heuchler! Jahrhundertelang beuten sie arme Länder aus und mich schimpfen sie einen Dieb! + +[MOB_25C] +Ich brauche dingend Ihre Hilfe. + +[MOB_25D] +Beeilen Sie sich. Ich brauche Sie. Ich hasse die Franzosen. + +[MOB_26A] +Hallo, Tommy? + +[MOB_26B] +Ja? + +[MOB_26C] +Baker hier. Wollte dir nur sagen, der Gig hat Spaß gemacht. + +[MOB_26D] +Danke, auch im Namen der Gang. Eins sollst du wissen: + +[MOB_26E] +Du hast unsern Respekt. Halt die Nase im Wind, Junge. + +[MOB_29A] +Hallo? Spreche ich mit Mr. Tommy Vercetti? + +[MOB_29B] +Ja. + +[MOB_29C] +Ich hab mir sagen lassen, du wärst der richtige Mann, wenn man Kroppzeug am Hals hat. + +[MOB_29D] +Vielleicht... + +[MOB_29E] +Tja, ich hab 'ne regelrechte Plage am Hals. Haitianer, überall. + +[MOB_29F] +Mein Name ist Umberto Robina und es wär mir recht, wenn du baldmöglichst ins Café Robina kämst. + +[MOB_29G] +Diesmal haben's die Haitianer nämlich zu weit getrieben. + +[MOB_29H] +Test + +[MOB_30A] +Tommy, hier Umberto Robina. + +[MOB_30B] +Wie läuft das Café? + +[MOB_30C] +Oh, bestens. Sagenhaft, Tommy, sagenhaft. Keine Memmen, Tommy, nur echte Männer. Und wunderschöne Frauen! + +[MOB_30D] +Ich wollte nur sagen, für mich und Paps bist du jetzt einer von uns. Ein Kubaner. + +[MOB_30E] +Du bist ein ganzer Kerl. Du hast Mumm in den Knochen. + +[MOB_30F] +Danke, Umberto. Seit ich aus dem Knast raus bin, hat das keiner zu mir gesagt. Bis dann. + +[MOB_33A] +Tommy, Phil hier. Spar dir die sentimentalen Worte, hör mir einfach zu, ok? + +[MOB_33B] +Also, ich brau hier 'nen extra starken TNT-Whiskey und ich wollte fragen, ob du mal 'nen Schluck probieren willst. + +[MOB_33C] +Im Ernst, Tommy, wenn du 'nen Drink willst, oder 'ne Wand abbeizen musst - das Zeug macht einen Mann aus dir. + +[MOB_33D] +Bei mir hat's jedenfalls gewirkt, auch wenn ich jetzt auf einem Auge nichts mehr sehe. Ich warte auf dich. + +[MOB_34A] +Tommy, war ein Vergnügen, mit dir zu arbeiten. So einen Spaß hatte ich seit Vietnam nicht mehr. + +[MOB_34B] +Also, falls du je irgendwas brauchst, ruf mich an, ok? + +[MOB_34C] +Ich vergesse keinen, mit dem ich in der Schlacht war. + +[MOB_34D] +Und ich kann dir bestimmt irgendwann helfen, ok? + +[MOB_35A] +Tommy, die Wunde verheilt gut. Ist nur komisch: + +[MOB_35B] +da hab ich auf 6 Schlachtfeldern gekämpft und nie 'n Kratzer abgekriegt und dann das! + +[MOB_35C] +Jetzt bin ich der einarmige Phil. Hab aber 'n gutes Arsenal an Handfeuerwaffen, also bin ich auch mit Arm ab nicht arm dran. + +[MOB_35D] +Also hör auf mit der sentimentalen Scheiße und hol dir 'nen Drink, ok! + +[MOB_36A] +Tommy, hier Phil. Wollte mich bedanken, dass du mich da rausgehauen hast. + +[MOB_36B] +Verdammte Vietnamesen. Wo du hinschaust ein Hinterhalt. + +[MOB_36C] +Die Wunde heilt gut. Und jetzt kassiere ich meine Versehrtenrente endlich völlig zurecht. Danke, Kumpel. + +[MOB_40A] +Hey, Tommy, hier Sonny. Was macht die Sonnenbräune? + +[MOB_40B] +Ich hab keine Sonnenbräune. + +[MOB_40C] +Naja, mein Geld hast du jedenfalls auch nicht. Daher frag ich mich, + +[MOB_40D] +was treibst du eigentlich die ganze Zeit, Tommy? Sag doch mal? + +[MOB_40E] +Ich bin auf der Suche nach deinem Geld, Sonny. Keine Sorge. + +[MOB_40F] +Ich mach mir aber Sorgen, Tommy, ich kann nicht anders. + +[MOB_40G] +Ich hab nämlich anscheinend mit zu viel unzuverlässigen Menschen zu tun. + +[MOB_40H] +Sei du bitte kein unzuverlässiger Mensch, Tommy. + +[MOB_40I] +Tu dir und mir einen Gefallen. Ich freu mich, von dir zu hören. + +[MOB_41A] +Tommy, kennst du mich noch? + +[MOB_41B] +Hey, Sonny. + +[MOB_41C] +Ja genau, Sonny. Wir sind doch alte Freunde, + +[MOB_41D] +aber du schreibst nie, rufst nie an. Willst du denn nicht mehr mein Freund sein? + +[MOB_41E] +Ich hab alle Hände voll zu tun, die Sache zu regeln. Und du bist auch keine große Hilfe. + +[MOB_41F] +Ach, ich bin also schuld? Ja, ich hab gehört, dass du zu tun hast... + +[MOB_41G] +Musst Drogenbarone erledigen, ihre Geschäfte übernehmen. + +[MOB_41H] +Vergiss mich nicht, Tommy. Ich verspreche dir nämlich, dass ich dich nicht vergesse... + +[MOB_42A] +Tommy. + +[MOB_42B] +Sonny. + +[MOB_42C] +Anscheinend hörst du neuerdings schlecht, deshalb frag ich dich jetzt nochmal: + +[MOB_42D] +Tommy, wo ist die verdammte Kohle? Wo ist der verdammte Stoff und wo ist mein Anteil an deinem neuen Geschäft? + +[MOB_42E] +Du hältst mich zum Narren, Tommy, ich find's bloß nicht zum Lachen. + +[MOB_43A] +Tommy, Tommy, Tommy, Sonny hat gerade angerufen, dämmert dir was? + +[MOB_43B] +Ich weiß nicht, wie das mit dir ist, aber wenn mir jemand droht, dass er meine Familie umbringt, + +[MOB_43C] +dann macht mir das eine Scheißangst. Was gedenkst du zu unternehmen? + +[MOB_43D] +Nur die Ruhe, Ken, alles wird cool. + +[MOB_43E] +Ich BIN ruhig. So ruhig wie man sein kann, wenn man um sein Leben fürchtet! + +[MOB_43F] +Ken, ich muss mich gerade auf was anderes konzentrieren. + +[MOB_43G] +Wir kümmern uns zu gegebener Zeit um Forelli. + +[MOB_43H] +Ich bin ruhig. Hör ich mich nicht ruhig an? Muss die Todesangst sein, die auf die Stimme schlägt. + +[MOB45_A] +Tommy, wir müssen miteinander reden. + +[MOB45_B] +Wo liegt das Problem, Lance? + +[MOB45_C] +Bei dir, mein Freund: Ich finde, du gibst mir keinen fairen Anteil. + +[MOB45_D] +Noch dazu hast du mich vor den Jungs blamiert. Das kann ich nicht auf mir sitzen lassen. + +[MOB45_E] +Lance, das siehst du falsch. Du hast Fehler gemacht. + +[MOB45_F] +Tommy, ich bin nicht dein Laufbursche. Nicht dein Lakai. + +[MOB45_G] +Lance, alles ist ok, solange du keinen Mist baust. Und sollte ICH Mist bauen, steh ich dafür gerade. + +[MOB45_H] +Tommy, ich hab alles für dich getan und du trittst mich mit Füßen. Mach das doch nicht. + +[MOB45_I] +Lance, ich betrüg dich nicht und falle dir nicht in den Rücken, ok? + +[MOB45_J] +Reiß dich zusammen. Es ist schwer genug, ohne dass du mir die Ohren vollheulst. + +[MOB45_K] +Vertrau mir, hörst du. Hörst du? + +[MOB45_L] +Ja, Tommy, aber ich halt das nicht mehr lange aus. + +[MOB45_M] +Lance, komm mir bloß nicht so, ich warne dich. + +[MOB45_N] +Reg dich ab, mach ein paar Tage frei, dann unterhalten wir uns, ok? + +[MOB46_A] +Tommy - Lance. + +[MOB46_B] +Ja? + +[MOB46_C] +Kein 'Schön, dass du anrufst, Lance!' Kein freundliches Wort? Was soll das? + +[MOB46_D] +Ich habe gerade zu tun, Lance. Was willst du? + +[MOB46_E] +Nichts. Ich wollte dir nur sagen, wir kriegen das hin. + +[MOB46_F] +Du und ich, ohne Probleme. Du verstehst mich? + +[MOB46_G] +Wir müssen es hinkriegen. Sonst sind wir erledigt, Lance. + +[MOB46_H] +Wir stecken schon viel zu tief drin. Aber danke für den Anruf. Du hörst von mir. + +[MOB_47A] +Tommy - Lance. Wir stecken in der Patsche. Du musst sofort kommen. + +[MOB52_A] +Hey, Leo, ich glaube, wir haben einen Käufer für Diaz' Ware. + +[MOB52_B] +Du musst ihn anrufen und den Deal in die Wege leiten, ok? + +[MOB52_C] +Wo bist du gerade? + +[MOB52_D] +Ist irgendwas, Leo? Du hörst dich so komisch an. + +[MOB52_E] +Sag mir, wo du bist. + +[MOB52_F] +Verdammt, wer ist da dran? Gib mir Leo! + +[MOB52_G] +Leo ist 'ne Weile verreist, ich vertrete ihn. + +[MOB52_H] +Zum Teufel mit dir! + +[MOB54_A] +Hi, Tommy! + +[MOB54_B] +Hi, Mercedes. Wie geht's? + +[MOB54_C] +Ich hab 'ne neue Wohnung in Vice Point. + +[MOB54_D] +Vielleicht kommst du mich ja mal besuchen. + +[MOB54_E] +Klar, gern. Also, bis später. + +[MOB55_A] +Tommy, ich bin's. + +[MOB55_B] +Hi, Mercedes. + +[MOB55_C] +Tommy, mir ist so langweilig. Wann hast du denn mal Zeit für mich? + +[MOB55_D] +Was soll denn das heißen? + +[MOB55_E] +Ich weiß, du hast so viel zu tun, musst Leute beseitigen und bestechen. + +[MOB55_F] +Aber ich will mal wieder Spaß haben. Also vergiss mich nicht, hörst du? + +[MOB56_A] +Tommy, es heißt, du hast Ricardo Diaz erledigt. + +[MOB56_B] +In seinem Haus ist ein Feuer ausgebrochen. + +[MOB56_C] +Ich glaube, er hatte ein Acrylhemd an und ist verbrannt. + +[MOB56_D] +Tommy, ich bin so stolz auf dich. Ich wusste, du bist ein echter Mann. + +[MOB56_E] +Und er war ein Mistkerl. Ich bin so stolz, dass du mein Freund bist. + +[MOB56_F] +Ich weiß, du hast zu tun, du musst schließlich die Stadt erobern. + +[MOB56_G] +Aber vergiss mich nicht, hörst du? + +[MOB57_A] +Ich bin's, Mercedes. Ich liebe dich nicht mehr, Tommy. + +[MOB57_B] +Wirklich nicht mehr. Du bist nicht mehr nett zu Mercedes. + +[MOB57_C] +Du behandelst mich nicht mehr wie eine Dame. Du ignorierst mich und ich hasse dich. + +[MOB57_D] +Ich bestehe darauf, dass du sofort zu mir kommst! + +[MOB58_A] +Tommy. + +[MOB58_B] +Hey, Mercedes. + +[MOB58_C] +Selber hey, du toller Hecht. Ich bin sauer auf dich, Tommy. + +[MOB58_D] +Schick mich nie mehr zu diesem Jezz Torrent. + +[MOB58_E] +So ein Jammerlappen. Mittendrin fängt er an zu weinen wegen seinem Hündchen, + +[MOB58_F] +das gestorben ist, als er 7 war, und dass seine Mama ihn nie lieb hatte. + +[MOB58_G] +Und Tommy - privat läuft er in Perücke und BH rum. + +[MOB58_H] +Ich bin nicht gut auf dich zu sprechen! + +[MOB59_A] +Oh, Tommy. Ich bin's, Mercedes. + +[MOB59_B] +Ich wollte dir nur sagen, es ist ganz toll beim Film. + +[MOB59_C] +Wenn du nochmal etwas für mich hast, sag Bescheid. + +[MOB59_D] +Wirklich. Ich wollte immer Schauspielerin werden. + +[MOB59_E] +Ich denke, ich lerne viel über die Dramaturgie. + +[MOB59_F] +Es ist so lehrreich! Vielen, vielen Dank. Bis bald. Adios. + +[MOB_99] +Geh zu der Telefonzelle vor Ort. + +[MOB_98] +Geh zu der Telefonzelle vor Ort. + +[MOB_97] +Geh zu der Telefonzelle vor Ort. + +[MOB_96] +Geh zu der Telefonzelle vor Ort. + +[MOB_95] +Geh zu der Telefonzelle vor Ort. + +[A_TIME] ++~1~ Sekunden + +[F_RANGE] +~g~Du bist außer Reichweite des Feuerwehrfunks. Fahr näher an eine Feuerwache heran! + +[DODO_FT] +Du bist ~1~ Sekunden geflogen! + +[GA_8] +Benutze den Zünder, um die Bombe hochgehen zu lassen. + +[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. + +[CHASE] +Größtes Medieninteresse bisher + +[CHASE1] +Null + +[CHASE2] +Minimal + +[CHASE3] +Vages Interesse + +[CHASE4] +Lokalblatt Seite 7 + +[CHASE5] +Lokalblatt Titelseite + +[CHASE6] +Vice Courier einspaltig + +[CHASE7] +Vice Courier Titelseite + +[CHASE8] +Lokalsender 3-Uhr-Nachrichten + +[CHASE9] +Lokalsender 20-Uhr-Nachrichten + +[CHASE10] +Lokalsender Live-Berichterstattung + +[CHASE11] +UFA Today Seite 12 + +[CHASE12] +UFA Today Seite 4 + +[CHASE13] +Foto in UFA Today + +[CHASE14] +Landesweites TV 4-Uhr-News + +[CHASE15] +Landesweites TV 20-Uhr-News + +[CHASE16] +Landesweite Live-Berichterstattung + +[CHASE17] +Internationale Berichterstattung + +[CHASE18] +Nationale Krise + +[CHASE19] +Internationale Krise + +[CHASE20] +Weltereignis + +[CHASE21] +Stoff aus dem Legenden sind + +[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. + +[FEM_ON] +AN + +[FEM_OFF] +AUS + +[FEM_YES] +Ja + +[FEM_NO] +Nein + +[FEM_RET] +Wiederholen + +[FEC_NA] +Nicht verfügbar + +[FEC_CWL] +Eine Waffe nach links + +[FEC_CWR] +Eine Waffe nach rechts + +[FEC_LOF] +Nach vorne sehen + +[FEC_TAR] +Zielen + +[FEC_MOV] +Bewegung + +[FEC_CAM] +Blickwinkel + +[FEC_PAU] +Pause + +[FEC_ENV] +In Fahrzeug einsteigen + +[FEC_JUM] +Springen + +[FEC_ATT] +Angreifen oder Waffe abfeuern + +[FEC_RUN] +Rennen + +[FEC_FPC] +Subjektive Kamera + +[FEC_LL] +Nach links sehen + +[FEC_LB] +Nach hinten sehen + +[FEC_LR] +Nach rechts sehen + +[FEC_HOR] +Hupe + +[FEC_VES] +Fahrzeugsteuerung + +[FEC_BRA] +Bremsen oder rückwärts fahren + +[FEC_HAB] +Handbremse + +[FEC_CAW] +Fahrzeugwaffe + +[FEC_ACC] +Beschleunigen + +[FEC_CCF] +Konfiguration : + +[FEC_CF1] +Konfig. 1 + +[FEC_CF2] +Konfig. 2 + +[FEC_CF3] +Konfig. 3 + +[FEC_CF4] +Konfig. 4 + +[FEC_CDP] +Controller-Anzeige : + +[FEC_ONF] +Zu Fuß + +[FEC_INC] +Im Auto + +[FEC_VIB] +Vibration : + +[FEL_ENG] +Englisch + +[FEL_FRE] +Französisch + +[FEL_GER] +Deutsch + +[FEL_ITA] +Italienisch + +[FEL_SPA] +Spanisch + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[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 + +[PL_STAT] +Spielerstatistiken + +[PE_WAST] +Von dir abservierte Personen + +[PE_WSOT] +Von anderen abservierte Personen + +[TM_BUST] +Zahl deiner Verhaftungen + +[GNG_WST] +Gang-Mitglieder + +[DED_CRI] +Kriminelle + +[PER_COM] +Absolviert (in Prozent) + +[KGS_EXP] +Sprengstoffverbrauch in kg + +[ACCURA] +Treffsicherheit + +[ST_WEAP] +Waffen-Budget + +[ST_PROP] +Budget für Objekte + +[ST_AUTO] +Budget für Autoreparaturen und Lackierung + +[ST_PHOT] +Von dir gemachte Fotos + +[ST_LOAN] +Besuche von Kredithaien + +[ST_STOR] +Geknackte Läden + +[ST_MOVI] +Film-Stunts + +[ST_PIZZ] +Gelieferte Pizzas + +[ST_GARB] +Getätigte Müllfuhren + +[ST_ICEC] +Verkaufte Eiscreme + +[TOP_SHO] +Beste Schießstand-Punktzahl + +[SHO_RAN] +Schießstand-Rangliste + +[SEAGULL] +Abgeschossene Möwen + +[PROPOWN] +Objekte in deinem Besitz + +[ST_TIME] +Gespielte Zeit + +[ST_FTIM] +Flugstunden + +[ST_PRAN] +Piloten-Rating + +[ST_RAN0] +Anfänger + +[ST_RAN1] +Navigator + +[ST_RAN2] +Co-Pilot + +[ST_RAN3] +Halb-Profi + +[ST_RAN4] +Könner + +[ST_RAN5] +Profi + +[ST_RAN6] +Ass + +[ST_RAN7] +Roter Baron + +[ST_DRWN] +Gefütterte Fische + +[ST_FASH] +Kleidungsbudget + +[ST_DAMA] +Zerstörte Objekte + +[TM_DED] +Krankenhausbesuche + +[DAYSPS] +Im Spiel verstrichene Tage + +[NUMSHV] +Besuche in Versteck + +[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 Salti + +[MXJUMP] +Max. Drehungen im Sprung + +[BUL_FIR] +Verschossene Kugeln + +[BUL_HIT] +Kugeln, die trafen + +[SPRAYIN] +Anzahl Lackierungen + +[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 Irrsinns-Stunts geschafft + +[NOUNIF] +Monster-Stunts geschafft + +[NMISON] +Begonnene Missionen + +[PASDRO] +Ans Ziel beförderte Fahrgäste + +[MONTAX] +Mit Taxi verdientes Geld + +[DAYPLC] +Tagesetat für Polizei + +[CRIMRA] +Rang: + +[STPR_1] +Malibu Club + +[STPR_2] +Druckerei + +[STPR_3] +Filmstudio + +[STPR_4] +Eiscremefabrik + +[STPR_5] +Autohaus + +[STPR_6] +Taxiunternehmen + +[STPR_7] +Bootswerft + +[SET1EN] +Konfig. 1 aktiviert + +[GMSAVE] +Spiel speichern + +[FEDS_TB] +Zurück + +[FEST_OO] +von + +[FEC_TUC] +Geschützsteuerung + +[FEC_RS3] +Radiosender auswählen (L3-Taste) + +[FEC_HO3] +Hupe (L3-Taste) + +[C_FAIL] +Bürgerwehr-Mission beendet! + +[C_ESCP] +~r~Der Verdächtige ist entwischt! + +[C_VIGIL] +BÜRGERWEHR BONUS!! + +[HEAL_A] +Dein ~h~Gesundheitszustand~w~ wird rechts oben auf dem Bildschirm in Orange angezeigt. + +[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 'Grand Theft Auto: Vice City' DVD. + +[RESTART] +Neues Spiel wird gestartet + +[GA_3] +Keine Gratisjobs mehr. Umspritzen kostet $100! + +[GA_1] +Hey, so was heißes rühre ich nicht an! + +[GA_1A] +Komm wieder, wenn du nicht so viel zu tun hast... + +[HELP9_C] +Drücke die~h~ ~k~~PED_FIREWEAPON~~w~, um das Präzisionsgewehr ~h~abzufeuern~w~. + +[TAXI2] +~r~Die Zeit ist um! + +[PAGEB13] +Gesundheits-Powerups in Versteck angeliefert. + +[PAGEB14] +Adrenalin in Versteck vorrätig. + +[FESZ_CA] +Abbrechen + +[FES_NGA] +Neues Spiel + +[FES_CAN] +Abbrechen + +[FESZ_QL] +Alle nicht gespeicherten Daten des aktuellen Spiels werden verloren gehen. 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 verloren gehen. Weiter? + +[T4X4_1] +'PCJ Rallye' + +[BMX_1] +'Krasses Gelände' + +[BMX_2] +'Teststrecke' + +[BMXFAIL] +~r~Du hast es nicht geschafft, einen neuen Rekord aufzustellen! + +[BMX_REC] +~g~Neuer Rekord:~1~ !! + +[T4X4_3] +'CHECKPOINT FIEBER' + +[MM_1] +'PYLONEN-RALLYE' + +[T4X4_F] +~r~Du hast gekniffen! Schon überfordert? + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[RIO] +Rio + +[PATRIOT] +Patriot + +[FIRETRK] +Feuerwehrwagen + +[TRASHM] +Trashmaster + +[STRETCH] +Stretch Limo + +[MANANA] +Manana + +[INFERNS] +Infernus + +[VOODOO] +Voodoo + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Krankenwagen + +[FBICAR] +FBI Washington + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[WASHING] +Washington + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[HUNTER] +Hunter + +[POLICAR] +Polizei + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Bus + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[CUBAN] +Kubanischer Hermes + +[HELI] +Helikopter + +[DODO] +Dodo + +[COACH] +Coach + +[CABBIE] +Cabbie + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +RC Bandit + +[ROMERO] +Romeros Leichenwagen + +[PACKER] +Packer + +[ADMIRAL] +Admiral + +[SQUALO] +Squalo + +[SEASPAR] +Sea Sparrow + +[PIZZABO] +Pizza Boy + +[GANGBUR] +Gang Burrito + +[TROPIC] +Tropic + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[CADDY] +Caddy + +[ZEBRA] +Zebra Cab + +[TOPFUN] +Top Fun + +[SKIMMER] +Skimmer + +[PCJ600] +PCJ 600 + +[PHOENIX] +Phoenix + +[FAGGIO] +Faggio + +[FREEWAY] +Freeway + +[RCBARON] +RC Baron + +[RCRAIDE] +RC Raider + +[GLENDAL] +Glendale + +[OCEANIC] +Oceanic + +[SANCHEZ] +Sanchez + +[SPARROW] +Sparrow + +[LOVEFIS] +Love Fist + +[COASTG] +Küstenwache + +[DINGHY] +Dinghy + +[HERMES] +Hermes + +[SABRE] +Sabre + +[SABRETU] +Sabre Turbo + +[WALTON] +Walton + +[REGINA] +Regina + +[COMET] +Comet + +[DELUXO] +Deluxo + +[BURRITO] +Burrito + +[SPAND] +Spandex Express + +[MARQUIS] +Marquis + +[BAGGAGE] +Baggage Handler + +[KAUFMAN] +Kaufman-Taxi + +[COASTMA] +Küstenwachen-Maverick + +[MAVERIC] +Maverick + +[RANCHER] +Rancher + +[FBIRANC] +FBI Rancher + +[VIRGO] +Virgo + +[GREENWO] +Greenwood + +[HOTRING] +Hotring Racer + +[BLISTAC] +Blista Compact + +[FEST_DF] +Zu Fuß zurückgel. Strecke (Meilen) + +[FEST_DC] +Mit Auto gefahrene Strecke (Meilen) + +[FESTDFM] +Zu Fuß zurückgelegte Strecke (Meter) + +[FESTDCM] +Mit Auto gefahrene Strecke (Meter) + +[TOT_DIS] +Insgesamt zurückgel. Strecke (Meilen) + +[TOTDISM] +Insgesamt zurückgel. Strecke (Meter) + +[DISTHEL] +Mit Helikopter geflogene Strecke (Meilen) + +[DISTHEM] +Mit Helikopter geflogene Strecke (Meter) + +[DISTBOA] +Mit Boot zurückgel. Strecke (Meilen) + +[DISTBOM] +Mit Boot zurückgel. Strecke (Meter) + +[FEST_LS] +Mit Krankenwagen gerettete Menschen + +[FEST_CC] +Kriminelle bei Bürgerwehr-Mission + +[FEST_FE] +Gelöschte Feuer gesamt + +[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 + +[USJ] +MONSTER-STUNT-BONUS! + +[RATNG1] +Unbescholtener Bürger + +[RATNG2] +Durchschnittstyp + +[RATNG3] +Falschparker + +[RATNG4] +Ladendieb + +[RATNG5] +Rowdy + +[RATNG6] +Laufbursche + +[RATNG7] +Taschendieb + +[RATNG8] +Kleptomane + +[RATNG9] +Informant + +[RATNG10] +Spitzel + +[RATNG11] +Verräter + +[RATNG12] +Hochstapler + +[RATNG13] +Betrüger + +[RATNG14] +Schieber + +[RATNG15] +Falschspieler + +[RATNG16] +Schläger + +[RATNG17] +Straßengauner + +[RATNG18] +Gauner + +[RATNG19] +Halunke + +[RATNG20] +Outlaw + +[RATNG21] +Schurke + +[RATNG22] +Kurier + +[RATNG23] +Gangster + +[RATNG24] +Obergangster + +[RATNG25] +Galgenvogel + +[RATNG26] +Ex-Knacki + +[RATNG27] +Schwerer Junge + +[RATNG28] +Profi + +[RATNG29] +Drahtzieher + +[RATNG30] +Fahrer + +[RATNG31] +Soldat + +[RATNG32] +Gorilla + +[RATNG33] +Kopfgeldjäger + +[RATNG34] +Mann fürs Grobe + +[RATNG35] +Ronin + +[RATNG36] +Abräumer + +[RATNG37] +Hit-Man + +[RATNG38] +Partner + +[RATNG39] +Butcher + +[RATNG40] +Troubleshooter + +[RATNG41] +Attentäter + +[RATNG42] +Adjutant + +[RATNG43] +Gemachter Mann + +[RATNG44] +Rechte Hand + +[RATNG45] +Vollstrecker + +[RATNG46] +Leutnant + +[RATNG47] +Vize-Boss + +[RATNG48] +Capo + +[RATNG49] +Boss + +[RATNG50] +Oberboss + +[RATNG51] +Don + +[RATNG52] +King of the City + +[PAGE_00] +.. + +[WELCOME] +WILLKOMMEN IN + +[TSCORE] +EINKÜNFTE: $~1~ + +[PBOAT_2] { reVC update } +Drücke die ~h~~k~~VEHICLE_FIREWEAPON~~w~, um die Bordkanonen abzufeuern. + +[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! + +[ATUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~~w~, um Krankenwagen-Missionen an- oder abzuschalten. + +[FEST_HA] +Höchster Krankenwagen-Missions-Level + +[C_KILLS] +KRIMINELLE: ~1~ + +[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! + +[CRED001] +ROCKSTAR NORTH + +[CRD001A] +STUDIO DIRECTOR + +[CRD001B] +ANDREW SEMPLE + +[CRED002] +PRODUCER + +[CRD002A] +DEVELOPMENT DIRECTOR + +[CRED003] +LESLIE BENZIES + +[CRED004] +ART DIRECTOR + +[CRED005] +AARON GARBUT + +[CRED006] +TECHNICAL DIRECTORS + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED010] +ANDREW DUTHIE + +[CRED011] +CRAIG FILSHIE + +[CRED012] +WILLIAM MILLS + +[CRED013] +CHRIS ROTHWELL + +[CRD013A] +IMRAN SARWAR + +[CRD013B] +JAMES WORRALL + +[CRD013C] +JOHN HAIME + +[CRED014] +WRITTEN BY + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +LEAD CHARACTER DESIGNER + +[CRD018A] +CHARACTER DESIGNER + +[CRED019] +IAN MCQUE + +[CRD019A] +TOKS SOLARIN + +[CRD019B] +ALAN DAVIDSON + +[CRED020] +LEAD ANIMATOR + +[CRED021] +ALEX HORTON + +[CRD022A] +ANIMATORS + +[CRED022] +LEE MONTGOMERY + +[CRD022B] +DUNCAN SHIELDS + +[CRD022C] +GUS BRAID + +[CRED023] +VEHICLE DESIGNERS + +[CRD023A] +JOLYON ORME + +[CRD023B] +ALAN DUNCAN + +[CRD024A] +LEAD VEHICLE DESIGNER + +[CRED024] +PAUL KUROWSKI + +[CRED025] +MAP DESIGNERS + +[CRED026] +ADAM COCHRANE + +[CRED027] +NIK TAYLOR + +[CRED028] +GARY MCADAM + +[CRED029] +KEIRAN BAILLIE + +[CRED030] +ALISDAIR WOOD + +[CRED031] +ANDREW SOOSAY + +[CRD031A] +STEVEN MULHOLLAND + +[CRD031B] +WAYLAND STANDING + +[CRD031C] +CAMPBELL J. DICK + +[CRD031D] +GRAPHIC DESIGNER + +[CRD031E] +STUART PETRI + +[CRED032] +LEAD PROGRAMMER + +[CRD032A] +PROGRAMMERS + +[CRED033] +ALEXANDER ROGER + +[CRED034] +GRAEME WILLIAMSON + +[CRED035] +BARANE CHAN + +[CRED036] +DEREK PAYNE + +[CRED037] +GORDON YEOMAN + +[CRD037A] +ALAN CAMPBELL + +[CRD037B] +MARK HANLON + +[CRD037C] +ANDRZEJ MADAJCZYK + +[CRED038] +LEAD MUSIC PRODUCER + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +LEAD AUDIO ENGINEER + +[CRED042] +ALLAN WALKER + +[CRD041A] +AUDIO ENGINEER + +[CRD041B] +AUDIO + +[CRD042A] +WILL MORTON + +[CRED043] +AUDIO PROGRAMMER + +[CRED044] +RAYMOND USHER + +[CRED045] +TEST MANAGER + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +LEAD QA + +[CRED048] +NEIL CORBETT + +[CRED049] +KEVIN WONG + +[CRED050] +QA + +[CRED051] +DAVID BEDDOES + +[CRED052] +DAVID WATSON + +[CRED053] +BARRY CLARK + +[CRED054] +ROSS SPARROW + +[CRED055] +JAMES ALLAN + +[CRED056] +NEIL MEIKLE + +[CRD056A] +GEORGE WILLIAMSON + +[CRD056B] +MATT JONES + +[CRD056C] +ROB HARBOUR + +[CRD056D] +TOM WHITTAKER + +[CRED057] +LEAD TECHNICAL SUPPORT + +[CRED058] +LORRAINE ROY + +[CRD057A] +TECHNICAL SUPPORT + +[CRED059] +CHRISTINE CHALMERS + +[CRD060A] +OFFICE SUPPORT + +[CRD060B] +KIM GURNEY + +[CRD060C] +CASSIE OLIVER + +[CRED060] +ROCKSTAR NEW YORK + +[CRED061] +EXECUTIVE PRODUCER + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCER + +[CRED064] +DAN HOUSER + +[CRED065] +VP OF DEVELOPMENT + +[CRED066] +JAMIE KING + +[CRED067] +CHIEF TECHNOLOGY OFFICER + +[CRED068] +GARY J. FOREMAN + +[CRED069] +ASSOCIATE PRODUCER + +[CRED070] +JEREMY POPE + +[CRD071A] +DIRECTOR OF QUALITY ASSURANCE + +[CRD072A] +JEFF ROSA + +[CRED071] +MUSIC SUPERVISOR + +[CRED072] +TERRY DONOVAN + +[CRED073] +PRODUCTION TEAM + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +JERONIMO BARRERA + +[CRED080] +CARLY SLATER + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +RENAUD SEBANNE + +[CRED084] +RICHARD KRUGER + +[CRD084A] +DANIEL EINZIG + +[CRD084B] +JACEN BURROWS + +[CRD084C] +LINN PR + +[CRD084D] +COVER ART + +[CRD084E] +STEPHEN BLISS + +[CRED085] +KENT PAUL'S 80 NOSTALGIA ZONE + +[CRED086] +WRITTEN BY DAN HOUSER + +[CRD086A] +PRODUCED BY ADAM TEDMAN + +[CRED087] +WWW.KENTPAUL.COM AND WWW.VICECITY.COM + +[CRED088] +CREATED BY + +[CRD088A] +ADAM TEDMAN + +[CRD088B] +DAVID YU + +[CRD088C] +JERRY LUNA + +[CRD088D] +STUART PETRI + +[CRD088E] +MICHAEL CARNEVALE + +[CRD088F] +GREG LAU + +[CRD088G] +FUTABA HAYASHI + +[CRED089] +QA MANAGER + +[CRED090] +CRAIG ARBUTHNOTT + +[CRED091] +LEAD ANALYST + +[CRED092] +ADAM DAVIDSON + +[CRD092A] +JOE HOWELL + +[CRD092B] +MARC FERNANDEZ + +[CRED093] +GAME ANALYST + +[CRED094] +RICHARD HUIE + +[CRED095] +ROCKSTAR TEST TEAM + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRD098A] +ELIZABETH SATTERWHITE + +[CRD098B] +JAMEEL VEGA + +[CRD098C] +MIKE HONG + +[CRED099] +LEE CUMMINGS + +[CRED100] +STORY + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +CUT SCENES + +[CRED108] +WRITTEN BY DAN HOUSER AND JAMES WORRALL + +[CRED109] +AUDIO DIRECTED BY DAN HOUSER AND NAVID KHONSARI + +[CRED110] +PRODUCED BY JAMIE KING + +[CRD110A] +TALENT PROCUREMENT: JAMIE KING, SEAN MACALUSO + +[CRED111] +CAST + +[CRD111A] +SUPPORTING CHARACTERS + +[CRED112] +TOMMY VERCETTI - RAY LIOTTA + +[CRED113] +KEN ROSENBERG - WILLIAN FICHTNER + +[CRED114] +SONNY FORELLI - TOM SIZEMORE + +[CRED115] +STEVE SCOTT - DENNIS HOPPER + +[CRED116] +AVERY CARRINGTON - BURT REYNOLDS + +[CRED117] +RICARDO DIAZ - LUIS GUZMAN + +[CRED118] +LANCE VANCE - PHILIP MICHAEL THOMAS + +[CRED119] +COLONEL JUAN CORTEZ - ROBERT DAVI + +[CRED120] +UMBERTO ROBINA - DANNY TREJO + +[CRED121] +PHIL CASSIDY - GARY BUSEY + +[CRED122] +MITCH BAKER - LEE MAJORS + +[CRED123] +MERCEDES CORTEZ - FAIRUZA BALK + +[CRED124] +KENT PAUL - DANNY DYER + +[CRED125] +JEZZ TORRENT - KEVIN MCKIDD + +[CRED126] +TAXI CONTROLLER - DEBORAH HARRY + +[CRED127] +CANDY SUXX - JENNA JAMESON + +[CRED128] +BJ SMITH - LAWRENCE TAYLOR + +[CRED129] +AUNTIE POULET - YOUREE CLEOMILI HARRIS + +[CRED130] +SUPPLIER - ARMANDO RIESCO + +[CRED131] +COUGAR - BLAYNE PERRY + +[CRED132] +HILARY - CHARLES TUCKER + +[CRED133] +CONGRESSMAN ALEX SHRUB - CHRIS LUCAS + +[CRED134] +OLD MAN KELLY - GEORGE DICENZO + +[CRD134A] +CAM JONES - GREG SIMS + +[CRD134B] +PSYCHO - HUNTER PLATIN + +[CRD134C] +MAUDE THE ICE CREAM LADY - JANE GENNARO + +[CRD134D] +JETHRO - JOHN ZURHELLEN + +[CRD134E] +GONZALES - JORGE PUPO + +[CRD134F] +DWAYNE - NAVID KHONSARI + +[CRD134G] +DICK - PETER MCKAY + +[CRD134H] +MIKE THE GOON & PORN GUY - ROBERT CIHRA + +[CRD134I] +PERCY - RUSSELL FOREMAN + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATED BY + +[CRD136A] +TECHNICAL DIRECTION BY ALEX HORTON + +[CRED137] +DIRECTED BY + +[CRD137A] +DIRECTED BY NAVID KHONSARI + +[CRED138] +PRODUCED BY + +[CRD138A] +PRODUCED BY JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +RECORDED AT PERSPECTIVE STUDIOS, BROOKLYN + +[CRED140] +MOTION CAPTURE ACTORS + +[CRD140A] +BLAYNE PERRY + +[CRD140B] +JONATHON SALE + +[CRD140C] +CHARLES TUCKER + +[CRD140D] +EDDIE MARRERO + +[CRD140E] +WILLIAM MCCALL + +[CRD140F] +JORGE PUPO + +[CRD140G] +ROBERT JACKSON + +[CRD140H] +TARA RADCLIFFE + +[CRD140I] +JENIFER GAMBETESE + +[CRD140J] +KRIS ACHEVARRIA + +[CRD140K] +ALI ORDOUBADI + +[CRD140L] +KAHLEEM POOLE + +[CRED141] +PEDESTRIAN DIALOGUE + +[CRD141A] +WRITTEN BY DAN HOUSER, MARC FERNANDEZ, GILLIAN TELLING AND NAVID KHONSARI + +[CRD141B] +WITH HELP FROM JEREMY POPE, LANCE WILLIAMS, AND JENNY JEMISON + +[CRED142] +WRITTEN BY + +[CRD142A] +DAN HOUSER AND JAMES WORRALL + +[CRED143] +DIRECTED BY DAN HOUSER, CRAIG CONNER, MARC FERNANDEZ, AND ALLAN WALKER + +[CRED144] +PRODUCED BY RENAUD SEBANNE + +[CRED145] +PEDESTRIANS + +[CRED146] +ADAM DAVIDSON, ADAM WATKINS, ALEJANDRO K. BROWN, ALEX ANTHONY SIOUKAS, ALEX GARCIA, + +[CRED147] +ALICE SALTZMAN, ALISON CIHRA, AMY SALIMA, AMY SALZMAN, ANDREA VIDELA, ANTHONY ATTI, + +[CRED148] +ANTHONY RIVERA, BIJAN SHAMS, BLAYNE PERRY, BRETT BISOGNO, BREYE MATA, BRIAN PANEN, + +[CRED149] +BROCK VODER, CAREY BERTINI, CHARISSE LAMBERT, CHRIS DIFAT, CHRIS REISENBERGER, + +[CRED150] +CHRISTOPHER BRODAY, CHRISTOPHER CARRO, CYNTHIA GREENE, DAMARIES LOPEZ, DAN LEE, + +[CRED151] +DAN SCHNEIDER, DAN TOYAMA, DAVID DEAN CHALTFIELD, JR., DAVID HARRISON, DAVID WILEY, + +[CRED152] +DEBORAH COLLINS, DEBRANDA CHANEY-GILES, DEMETRA KOUKOULAS, DENISE ROSADO, + +[CRED153] +DEVIN BENNETT, DEVIN WINTERBOTTOM, DORIS WOO, DOUGLAS HARRISON, DUNCAN COUTTS, + +[CRED154] +DUPE AJAYI, EDWIN AVELLANEDA, ELIZABETH HOWELL, ELIZABETH SATTERWHITE, ERIC NAGLE, + +[CRED155] +ESTEBAN KARPLUS, F. FONT, FUTABA HAYASHI, GENE HILGREEN, GERALD COSGROVE, + +[CRED156] +GERARD LUNA, GILLIAN TELLING, GREGG CARLUCCI, GREGORY CLERVOIX, GREGORY SCHWEIZER, + +[CRED157] +HADLEY TOMICKI, J. ROSSETT, JAMEEL VEGA, JASON JONES, JEFF ROSA, JENNIFER JEMISON, + +[CRED158] +JEREMY TAGGERT, JESSICA RIDER, JOSEPH GREENE,JOSEPH HOWELL, KATE DUKICH, + +[CRED159] +KEL O'NEILL, KEVIN HOPKINS, LADAWN JAMES, LANCE WILLIAMS, LAURA BUBBLES, + +[CRED160] +LAURA PATTERSON, LEE CUMMINGS, LETICIA L. YOUNG, LINDSAY KENNEDY, LISA ORITZ, + +[CRED161] +LORNA JORDAN, LUCIO AMADIO, MARCO FERNANDEZ, MARIKO TANAKA, MARLON MATTHEWS, + +[CRED162] +MARY TELLING, MASAYOSHI MITSUYAMA, MATTHEW CHUNG, MAX ALLSTADT, MAX BOGDANOV, + +[CRED163] +MELISSA ALVAREZ, MICHAEL MAY, MICHAEL ROTHSTEIN, MIGUEL VIDAL, MIKE FEDERLINE, + +[CRED164] +NATALIE DESCALZO, N'GAI MEMBERS, NICOLAS MALLO, NOELLE SADLER, NORBERT MORIVAN, + +[CRED165] +OSWALD GREENE, JR., PETER MCKAY, PETER APPEL, PRESTON SAVARESE, RAFAEL GONZALES, + +[CRED166] +RANDY JOHNSON, REY CONCEPCION, RICHARD KROGER, ROB TIBBS, ROBERT JACKSON, + +[CRED167] +ROBERT SCHULER, ROSS A. MCINTYRE, RUSSELL FOREMAN, RUTH NUNEZ, SALVADORE SUAZO, + +[CRED168] +SAM WHITE, SANTOS GONZALES, SCOTT SMITH, SEYMOUR FRAILMAN, SPELMAN BRAUMAN, + +[CRED169] +STEPHANIE TELLING, STEVE KNEZEVICH, STEVE ROBERT, SUMIKO YASUDA, SUSAN LEWIS, + +[CRED170] +SYLVIA COLACIOS, TOMOKO MIYAZAKI, TRON, VERDEL HALE, YVES MONDESIR, ZENO LEINFELDER, + +[CRED171] +DAVID BEDDOES, CHRISTINE CHALMERS, BARRY CLARK, NEIL CORBETT, KIM GURNEY, NEIL MEIKLE, + +[CRED172] +CASSIE OLIVER, LORRAINE ROY, DAVID WATSON, KEVIN WONG, WILL MORTON + +[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] +MUSIC CONSULTANCY + +[CRD218A] +HEINZ HENN + +[CRD218B] +STUART ROSS + +[CRED219] +SOUNDTRACK CO-ORDINATOR + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCER FOR ROCKSTAR GAMES + +[CRED222] +DAN HOUSER AND LAZLOW + +[CRED223] +PRODUCER FOR ROCKSTAR NORTH + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER AND IMAGING + +[CRED228] +WRITTEN BY DAN HOUSER AND LAZLOW + +[CRED229] +FLASH FM + +[CRD229A] +TONI-MARIA CHAMBERS + +[CRD229B] +IMAGING VOICE AND PRODUCTION-JEFF BERLIN + +[CRED230] +SPECIAL THANKS TO + +[CRED231] +TOMMY MOTTOLA, + +[CRED232] +MICHELLE ANTHONY, + +[CRED233] +STEVE BARNETT, + +[CRED234] +CHUCK FLECKENSTEIN, + +[CRED235] +RITA LIBERATOR + +[CRED236] +MARTIN & CLAIRE LOGAN + +[CRED237] +SANDRA HUTTON + +[CRED238] +CHRISTINE DAVIDSON + +[CRED239] +ALAN, RED & BIGFOOT + +[CRED240] +LE T + +[CRED241] +COLIN DONALD + +[CRED242] +KERRY STALLWOOD + +[CRED243] +ALAN MCGREGOR + +[CRED244] +CHRIS MORTON + +[CRED245] +EMIL BUSSE + +[CRED246] +EMILY BAILLIE + +[CRED247] +KEVIN ARCHIBALD + +[CRED248] +MORAG KERR + +[CRED249] +CATH WALKER + +[CRED250] +ISO BAR + +[CRED251] +WATERLINE + +[CRED252] +NEWS CAFE + +[CRD251A] +THE POND + +[CRD252A] +PIVO + +[CRED253] +BUDGET VIDEO RENTALS + +[CRED254] +LORNA'S SCOOTER + +[CRED255] +GARETH MURFIN + +[CRED256] +ADDITIONAL ART + +[CRED257] +TONY PORTER + +[CRED258] +CRAIG MOORE + +[CRED259] +CUT SCENE LIP-SYNC ANIMATION + +[CRED260] +COSGROVE HALL FILMS + +[CRED261] +PRODUCER - OWEN BALLHATCHET + +[CRED262] +SENIOR ANIMATOR - JON TURNER + +[CRED263] +ANIMATORS - RICHARD DRUMM + +[CRED264] +DAVE BROWN + +[CRED265] +MAIR THOMAS + +[CRED266] +PRASHANT PATEL + +[CRED267] +AUDIO TECHNOLOGY CONSULTANT + +[CRED268] +RIK EDE FOR GAMESOUND LTD. + +[CRED269] +DTS INTEGRATION SUPPORT + +[CRED270] +TED LAVERTY FOR DTS + +[CRED271] +CHRIS GREER FOR DTS + +[CRED272] +JASON PAGE FOR SCEE + +[CRED273] +RESEARCH AND ANALYSIS + +[CRED274] +VROCK + +[CRED275] +DJ: LAZLOW AS HIMSELF + +[CRED276] +IMAGING VOICE-JOE KELLY + +[CRED277] +IMAGING PRODUCTION-JONATHAN HANST + +[CRED278] +WAVE 103 + +[CRED279] +DJ: ADAM FIRST-JAMIE CANFIELD + +[CRED280] +IMAGING VOICE-JEN SWEENEY + +[CRED281] +IMAGING PRODUCTION-JONATHAN HANST + +[CRED282] +FEVER 105 + +[CRED283] +DJ: OLIVER 'LADYKILLER' BISCUIT-JULIUS DYSON + +[CRED284] +IMAGING VOICE MALE-ED MCMANN + +[CRED285] +IMAGING VOICE FEMALE-SHAWNEE SMITH + +[CRED286] +IMAGING PRODUCTION- LISTEN KITCHEN + +[CRED287] +EMOTION 98.3 + +[CRED288] +DJ: FERNANDO- FRANK CHAVEZ + +[CRED289] +IMAGING VOICE-JEN SWEENEY + +[CRED290] +IMAGING PRODUCTION-JONATHAN HANST + +[CRED291] +RADIO ESPANTOSO + +[CRED292] +DJ: PEPE-TONY CHILRODES + +[CRED293] +WILDSTYLE + +[CRED294] +DJ: MISTER MAGIC AS HIMSELF + +[CRED295] +IMAGING VOICE-FRANK SILVESTRO + +[CRED296] +IMAGING PRODUCTION-LAZLOW + +[CRED297] +KCHAT + +[CRED298] +WRITTEN BY DAN HOUSER AND LAZLOW + +[CRED299] +PRODUCED AND EDITED BY LAZLOW + +[CRED300] +DJ AMY SHECKENHAUSEN -LEYNA WEBER + +[CRED301] +JEZ TORRENT-KEVIN MCKIDD + +[CRED302] +MANDY -COLLEEN CORBETT + +[CRED303] +MICHELLE CARAPADIS-MARY BIRDSONG + +[CRED304] +MR.ZOO-CARL DOWLING + +[CRED305] +GETHSEMANEE-LYNN LIPTON + +[CRED306] +CLAUDE MAGINOT-JOHN MAUCERI + +[CRED307] +BJ SMITH-LAWRENCE TAYLOR + +[CRED308] +THOR-FRANK FAVA + +[CRED309] +RADIO CALLERS + +[CRED310] +COUZIN ED, JOSH CLARK, JASON BUHRMESTER, JUAN ALLER, WAYNE OLIVER, SUSAN LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, KEITH BROADAS + +[CRED311] +LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, + +[CRED312] +DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, + +[CRED313] +KEITH BROADAS + +[CRED314] +VCPR + +[CRED315] +WRITTEN BY DAN HOUSER AND LAZLOW + +[CRED316] +PRODUCED BY LAZLOW + +[CRED317] +MAURICE CHAVEZ-PHILLIP ANTHONY RODRIGUEZ + +[CRED318] +JONATHAN FREELOADER- PATRICK OLSEN + +[CRED319] +MICHELLE MONTANIUS-KELLY GUEST + +[CRED320] +REP. ALEX SHRUB- CHRIS LUCAS + +[CRED321] +CALLUM CRAYSHAW- SEAN MODICA + +[CRED322] +JOHN F. HICKORY- LJ GANSEN + +[CRED323] +PASTOR RICHARDS- DAVID GREEN + +[CRED324] +JAN BROWN- MAUREEN SILLIMAN + +[CRED325] +BARRY STARK- RENAUD SEBBANE + +[CRED326] +JENNY LOUISE CRAB- MARY BIRDSONG + +[CRED327] +KONSTANTINOS SMITH- KONSTANTINOS.COM + +[CRED328] +JEREMY ROBARD-PETER SILVESTRO + +[CRED329] +RADIO COMMERCIALS + +[CRED330] +WRITTEN BY DAN HOUSER AND LAZLOW + +[CRED331] +PRODUCED BY LAZLOW + +[CRED332] +ADDITIONAL JINGLES PRODUCED BY CRAIG CONNER + +[CRED333] +COMMERCIAL VOICES: + +[CRED334] +ADAM DAVIDSON, ALEX ANTHONY, ALICE SALTZMAN, AMY SALZMAN, KATE DUKICH, + +[CRED335] +ARAN RONICLE, BARB JONES, BEN KRECH, BRIAN THOMAS, BROCK YODER, CHRIS + +[CRED336] +FERRANTE, CRAIG CONNER, DAVE RYAN, DAVID GREEN, DORIS WOO, DOUGLAS + +[CRED337] +HARRISON, ED MCMANN, FRANK CHAVEZ, FRANK FAVA, GENE HILGREEN, GREG + +[CRED338] +SCHWEIZER, HUNTER PLATIN, JAMES FERRANTE, JEFF BERLIN, JEFF ROSA, JOE KELLY, + +[CRED339] +JOHN MAUCERI, JOSH CLARK, JULIE WEMYSS, KEVIN STRALEY, KIM GURNEY, LANCE + +[CRED340] +WILLIAMS, LAURA PATERSON, LAZLOW, LISA ORTIZ, LORNA JORDAN, LUCIEN JONES, + +[CRED341] +MAUREEN SILLIMAN, MIKE FERRANTE JR., PETE GUSTIN, PETER SILVESTRO, RAFF + +[CRED342] +CROLLA, RANDY JOHNSON, RICHARD KRUGER, RON REEVE, SHELLEY MILLER, SKY, TJ ALLARD + +[CRD344A] +AUDIO RECORDED AT DIGITAL ARTS STUDIOS, + +[CRED344] +NYC, TRACK 9 STUDIOS, NYC, + +[CRED345] +WEDDINGTON MULTIMEDIA, LOS ANGELES, + +[CRD345A] +SYNC SOUND, NYC AND RADIO LAZLOW, LONG ISLAND. + +[CRED346] +THANKS TO AXEL ERICSON AND WON LEE AT DIGITAL ARTS, PAUL VASQUEZ AT TRACK 9 STUDIOS, JOHN BOWEN AND JOHN HASSLER AT SYNC SOUND + +[CRED347] +MARK LLOYD + +[CRED348] +TIM BATES + +[CRED349] +KIT BROWN + +[CRED350] +ANDY MASON + +[CRED351] +PHIL DEANE + +[CRED352] +PHIL ALEXANDER + +[CRED353] +MATT HEWITT + +[CRED354] +DENBY GRACE + +[CRED355] +ANTOINE CABROL + +[CRED356] +JONATHAN STONES + +[CRED357] +MIKE BLACKBURN + +[CRED358] +TIM MCGAFF + +[CINCAM] +Cinematic-Kamera + +[RC4] +'ROCK'N ROLL MIT RUMPO' + +[LEGAL] +~g~Schalte die kriminelle Bedrohung aus! + +[GA_2] +Neuer Motor und neue Lackierung. Die Cops werden dich nicht identifizieren! + +[HELP15] +Wenn zu Fuß unterwegs, drücke die ~h~~k~~PED_LOOKBEHIND~~w~, um ~h~nach hinten zu sehen~w~. Benutze den ~h~rechten Analog-Stick~w~, um dich ~h~umzusehen~w~. + +[FEC_LB4] +Nach hinten sehen (R3-Taste) + +[PERPIC] +Versteckte Päckchen gefunden + +[CO_ONE] +Verstecktes Päckchen ~1~ von ~1~ + +[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 + +[USJ_ALL] +ALLE MONSTER-STUNTS ABSOLVIERT! + +[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] +--:---:---- --:--:-- + +[BONUS] +~g~BONUS $~1~ + +[HORN1] +Drück die ~h~~k~~VEHICLE_HORN~~w~, um zu ~h~hupen. + +[HORN2] +Drück die ~h~~k~~VEHICLE_HORN~~w~, um zu ~h~hupen. + +[HORN3] +Drück die ~h~~k~~VEHICLE_HORN~~w~, um zu ~h~hupen. + +[FEC_EXV] +In Fahrzeug ein- und aussteigen. + +[TAXI_M] +'TAXIFAHRER' + +[COP_M] +'BÜRGERWEHR' + +[FIRE_M] +'FEUERWEHRMANN' + +[AMBUL_M] +'SANITÄTER' + +[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~ + +[FESZ_LS] +Ladevorgang beendet. + +[HELI_1A] +Teste dein Können mit dem 'Sparrow'. Probiere, wie schnell du den Kurs abfliegen kannst. + +[HELI_1B] +Kurs absolviert! + +[HELIODD] +Helikopter-Jobs + +[LAW] +DIE ANWALTSMISSIONEN + +[LAW1_1] +~g~Besorge dir neue Sachen in Rafaels Shop. + +[LAW4_6] +Nieder mit der Geschäftsleitung! + +[LAW4_7] +Tod den Bossen! + +[LAW4_8] +Kampf, Kampf, Kampf, Kampf! + +[LAW4_9] +Mehr Urlaub, weniger Arbeit! + +[LAW4_11] +Kampf, Kampf, Kampf, Kampf! + +[LAW4_12] +Es lebe die Revolution! + +[GENERAL] +DIE COLONEL-MISSIONEN + +[GEN3_4] +Tommy Vercetti. Los, komm... + +[GEN3_13] +Was ist los, Mann?! Steig auf das Dach gegenüber im Hof bevor sie kommen! + +[GEN3_17] +Mist! Willst du mich umbringen?! + +[GEN3_21] +~g~Er hat Diaz' Geld! Stelle ihn und hole es zurück! + +[GEN3_24] +~r~Diaz hat's erwischt! Du hast versagt! + +[GEN3_26] +~r~Du hast Diaz erwischt! + +[GEN3_27] +~r~Du hast Diaz' Bodyguards erwischt! + +[GEN3_31] +~g~Begib dich zum Übergabeort und pass auf Diaz auf. + +[GEN3_32] +~g~Begib dich zu deinem Beobachtungsposten auf dem Dach des Gebäudes gegenüber von Lance. + +[COKE] +DIE KOKS-BARON-MISSIONEN + +[COK1_3] +Ich hoffe, du brichst dir das Genick! + +[COK1_6] +Ich hab diese Idioten satt! + +[COK2_7] +Siehst du diese Bojen? Versuch, die Lichter auszuschießen. + +[COK2_10] +Eines ist sicher: Du schießt besser als du laberst. + +[COK2_11] +Danke. Du bist ein echter Charmeur. + +[COK2_12] +Ich weiß, Tommy. + +[COK2_18] +Stehst du auf Kenny Loggins? + +[COK2_19] +Mann, ich liebe diese Scheibe. + +[COK2_26] +~r~Du hast Lance erwischt! + +[COK3_1] +Nicht schießen, Mann! + +[COK3_2] +Was ist in dem Zeug drin? + +[COK3_3] +Er klaut das Boot. Mist! + +[COK3_4] +Hilfe! Irgendein Irrer stiehlt das Boot, Mann! + +[COK4_W] +Uff! Das ist der letzte. + +[COK4_X] +Ich lass die Mühle an. + +[COK4_Y] +Ich glaube, wir haben ein paar neue Freunde. + +[COK4_2] +Ja. + +[COK4_6] +Weißt du, wo's lang geht? + +[COK4_7] +Haben wir uns verfahren? + +[COK4_8] +Wir haben Konkurrenz bekommen! + +[COK4_9] +Knöpf sie dir vor! + +[COK4_9A] +Zeit für den Lance Vance Dance! + +[COK4_10] +Die sind Kleinholz! Und Fischfutter. + +[COK4_11] +Geschafft! Die anderen Schiffchen taugen nicht viel. + +[COK4_17] +Die wollen's wissen! + +[COK4_18] +Ich hab nasse Füße! WIR KRIEGEN WASSER INS BOOT! + +[COK4_21] +Achtung, Brücke! + +[COK4_22] +Raus! Das Ding geht hoch! + +[COK4_23] +Gut geschossen. + +[COK4_29] +~r~Du hast Lance erwischt! + +[ASS1_6] +Geh nur, Tommy, ich komm schon zurecht! + +[ASS1_7] +Das ist für euch, ihr verdammten Mörder!! + +[ASS1_8] +Ich komm hier nicht weg! + +[ASS1_9] +Ich bin da, Tommy! + +[ASS1_10] +Hey, das ist aber ein herzallerliebstes Beet. + +[ASS1_11] +Hey Tommy, krieg ich ein Zimmer mit Blick auf die Bucht? + +[ASS1_12] +Schön hohe Decken hat's hier... + +[ASS1_3] +Lance! Ich brauche Deckung! + +[ASS1_4] +Diaz muss drinnen sein! + +[ASS1_5] +Lance! + +[ASS1_15] +~g~Stürme die Villa und erledige Diaz! + +[ASS1_17] +~g~Es führen mehrere Wege in die Villa. + +[TAXWAR] +TAXI-KRIEG-MISSIONEN + +[NOTAXI] +~g~Du brauchst ein Kaufman-Taxi, um diese Mission zu aktivieren. + +[TAXW1_5] +~g~Du musst in einem Kaufman-Taxi sitzen! + +[TAX2_4] +Auf geht's, Tommy. + +[TAX2_5] +Polier ihm die Visage. + +[TAX2_6] +Der hat nicht mal eine Lizenz. + +[TAX2_7] +Verdammte Limo-Services. + +[TAXW3_1] +~g~Hole Mercedes ab. + +[RACE1] +~g~3...2...1...LOS, LOS, LOS! + +[RACE2] +~g~3 + +[RACE3] +~g~2 + +[RACE4] +~g~1 + +[RACE5] +~g~LOS! + +[FIRST] +~b~1. + +[SECOND] +~b~2. + +[THIRD] +~b~3. + +[FOURTH] +~b~4. + +[RACETM] +~b~ZEIT: ~1~:~1~ + +[RACETM2] +~b~ZEIT: ~1~:0~1~ + +[RACEFA] +~r~Du hast das Rennen verloren! + +[TEX1_5] +~r~Er ist entkommen! + +[SEG3_1] +ZEIT: + +[SEG3_2] +~g~Begib dich zu dem Transporter mit dem RC Raider und den Zeitzünder-Bomben. + +[SEG3_3] +~g~Du musst den RC Raider benutzen, um 4 Bomben zu 4 Zielzonen auf dem Grundstück zu bringen. + +[SERG3_5] +~g~Du kannst immer nur 1 Bombe transportieren und kannst erfolgreich platzierten Bomben nicht wieder aufnehmen. + +[SEG3_7] +~g~Sobald du die ERSTE Bombe platziert hast, läuft der Timer des Zeitzünders an. Du musst dann alle Bomben innerhalb dieses Zeitraums platzieren. + +[SEG3_8] +~g~Alle 4 Bomben müssen in den 4 Zielzonen platziert werden, um die Mission zu erfüllen und das Gebäude zu demolieren. + +[SEG3_9] +~g~Zielzone getroffen! Noch 3 Bomben. + +[SEG3_10] +~g~Zielzone getroffen! Noch 2 Bomben. + +[SEG3_11] +~g~Zielzone getroffen! Noch 1 Bombe. + +[SEG3_12] +~r~Ziel verfehlt. Hol dir eine Bombe! + +[SEG3_13] +~g~Wirf die Bombe in einer Zielzone ab. + +[SEG3_14] +~r~Die Zeit ist um. Demolierung des Gebäudes fehlgeschlagen. + +[SEG3_15] +~r~Dein RC Raider ist zerstört. Wie willst du jetzt die Bomben transportieren? + +[AVERY] +AVERY-MISSIONEN + +[ASM] +ATTENTÄTERMISSIONEN + +[ASM_1] +ATTENTÄTERMISSION 1 + +[ASM1_1] +~g~Mr. Teal, Ihre Hilfe bei der Beseitigung der Landeier war äußerst wertvoll. Ich habe noch mehr Arbeit, die eine eher 'zupackende' Art verlangt. Ihr nächster Job klebt unter dem Telefon. + +[ASM1_2] +~g~Begib zu dich dem Fernsprecher vor dem Einkaufszentrum in Washington. + +[ASM1_3] +~g~Carl Pearson, Pizza-Lieferant. Er darf seine Lieferungen nicht durchführen. + +[ASM1_4] +~g~Schalte den Pizza-Lieferanten aus, bevor er seine Lieferungen abschließt. + +[ASM_2] +ATTENTÄTERMISSION 2 + +[ASM_3] +ATTENTÄTERMISSION 3 + +[ASM3_A] +Marcus Hammond, Franco Carter, Dick Tanner, Nick Kong und Stuntman Driver gehören zu einem europäischen Syndikat und planen einen Überfall. + +[ASM3_B] +Sie sind alle bereits in Position. Schalten Sie sie aus, bevor es losgeht. Sie haben 9 Minuten. Ich habe in der Nähe Waffen deponiert, die Sie brauchen werden. + +[ASM3_1] +~g~Hol dir die Waffe, die Mr. Black für dich deponiert hat. + +[ASM3_2] +~g~Geh nicht zu dicht an die Zielperson heran, sonst bemerkt sie dich. + +[ASM3_3] +~g~Es geht schneller, wenn du dir eine günstige Position nahe ihrem Standort suchst und zuschlägst, ohne gesehen zu werden. + +[ASM3_4] +~g~Er hat dich gesehen. Du musst ihn irgendwie ausschalten! + +[ASM3_5] +~g~Marcus Hammond befindet sich bei den Werbetafeln in Washington Beach. + +[ASM3_6] +~g~Franco Carter befindet sich bei DBP Security nahe dem Ocean Drive. + +[ASM3_7] +~g~Dick Tanner ist in der Nähe des Juweliers in Vice Point. + +[ASM3_8] +~g~Nick Kong befindet sich nähe Washington Beach. + +[ASM3_9] +~g~Stuntman Driver ist in Washington. + +[ASM3_10] +Du hast nicht alle ausgeschaltet. + +[ASM_4] +ATTENTÄTERMISSION 4 + +[ASM4_1] +~g~Hol dir das Gewehr, das im Laub vor dem Flughafen-Terminal für dich deponiert wurde. + +[ASM4_2] +~g~Verfehle dein Ziel nicht, du alarmierst sonst die Leibwächter. Und bleib auf Distanz, damit er dich nicht bemerkt. + +[ASM4_3] +~g~Beobachte die Frau über den Check-in-Schaltern im Terminal. TU IHR NICHTS. + +[ASM4_4] +~g~Erledige den Mann, dem sie den Aktenkoffer gibt, aber erst, NACHDEM ER IHN GENOMMEN HAT. Hole den Koffer und bringe ihn ins Ammu-Nation Downtown. + +[ASM4_5] +Hol dir den Aktenkoffer. + +[ASM4_6] +~g~Bring den Aktenkoffer ins Ammu-Nation Downtown. + +[ASM4_7] +~r~Du hast die Frau erwischt, du Idiot! + +[ASM4_8] +~r~Die Zielperson hat gehört, wie du geschossen hast. Der Deal ist geplatzt! + +[ASM4_9] +~r~Die Zielperson hat das Flugzeug bestiegen! + +[ASM4_11] +~r~Die Zielperson hat dich gesehen! Der Deal ist geplatzt! + +[ASM4_13] +~g~Er hat dich bemerkt und flieht. Schalte ihn aus und hol dir den Aktenkoffer! + +[ASM4_14] +~g~Der Distanz-Balken am oberen rechten Bildschirmrand zeigt dir, wie nahe du der Zielperson bist. Lass ihn nicht voll werden, sonst sieht sie dich. + +[ASM_5] +ATTENTÄTERMISSION 5 + +[KICK] +KICKSTART + +[KICK1_3] +~g~Anzahl, wie oft der Fuß abgesetzt wurde: ~1~ + +[KICK1_4] +~g~Zeitstrafe: ~1~ Sekunden + +[BANK] +BANK-MISSIONEN + +[BANK1] +BANK-MISSION 1 + +[BANK2] +BANK-MISSION 2 + +[BJM2_21] +~g~Triff so viele Ziele als möglich, solange deine Munition reicht. + +[BANK3] +BANK-MISSION 3 + +[BJM3_1] +~g~Besorg dir ein schnelles Auto und fahre an den Start. + +[BNK4_2A] +Die Mechaniker haben das Baby super hergerichtet. + +[BNK4_3G] +Oh, Mist, jetzt haben wir die Cops am Hals! + +[BNK4_3H] +Und wir sind noch nicht mal da. + +[BNK4_3K] +Also, erst mal müssen wir die Cops abschütteln... + +[BNK4_3L] +Großer Gott, Tommy, willst du uns alle umbringen?! + +[BNK4_3N] +Alles, was ich gern habe, geht kaputt! + +[BNK4_26] +Verdammt! Da sind sie schon! + +[BNK4_32] +Spreng die Schließfächer mit Sprengstoff auf. + +[BNK4_36] +Wo ist Cam? + +[BNK4_37] +In der Hölle. + +[BNK4_38] +Das ist der letzte! LOS! LOS! LOS! + +[BNK_39] +Shit! Wo bleibt Hilary? + +[BK4_40A] +Ich werd was für seine Verlustängste tun! + +[BNK4_42] +Hey, Jungs! Los, rein! Ich geb euch Deckung! + +[BNK4_43] +Ich hab alles im Griff! FAHR! + +[BNK4_44] +Geschafft! Wir sind reich! REICH! + +[BNK4_45] +Ein Jammer, dass Cam es nicht gepackt hat. Er war 'n guter Kerl. + +[BNK4_46] +Ja. Andererseits, so bleibt mehr für uns! + +[BNK4_47] +Sehr richtig! YEEEEHAAAH! + +[BNK4_48] +Tommy, eine Massage gefällig? + +[BNK4_49] +Hi, Mercedes. Ja, ich bin ein bisschen verspannt... + +[BNK450A] +Was hab ich gesagt, Tommy? Korrupte Bullen müssen sich vorsehen, wenn Kent Paul in der Stadt ist. + +[BNK450B] +Komm, gib mir 'nen größeren Batzen ab. Na komm. Ich brauch neue Klamotten. + +[BNK4_51] +Ich finde, du siehst ok aus. + +[KENT] +KENT PAUL-MISSIONEN + +[KENT1] +KENT PAUL-MISSION 1 + +[COUNT] +FÄLSCHER-MISSIONEN + +[COUNT1] +FÄLSCHER-MISSION 1 + +[COUNT2] +FÄLSCHER-MISSION 2 + +[BIKE] +DIE BIKER GANG-MISSIONEN + +[BIKE1] +BIKER-MISSION 1 + +[BIKE2] +BIKER-MISSION 2 + +[BIKE3] +BIKER-MISSION 3 + +[GOAWAY1] +Komm wieder, wenn du die Haiti-Gang-Missionen abgeschlossen hast. + +[HAIT] +DIE HAITI-GANG-MISSIONEN + +[HAIT1] +HAITI-MISSION 1 + +[HAIT2] +HAITI-MISSION 2 + +[HAIT3] +HAITI-MISSION 3 + +[HAM3_6] +~g~Verwende das Präzisionsgewehr, das ich für dich besorgt habe. + +[ROCK] +DIE ROCKBAND-GANG-MISSIONEN + +[ROK1_4] +~g~Ok, ich glaube, das ist es, was du suchst... + +[ROK1_1E] +~g~Das kostet mehr als du hast! + +[ROK1_1F] +~g~Komm wieder, wenn du die Kohle hast. + +[RBM2_6] +~g~Wow! Die ist 'n Kerl! Halt ihn auf! + +[ROCK3] +ROCKBAND-MISSION 3 + +[ROK3_6D] +~r~und IHR UND EUER KOPFPUTZ mit dazu! + +[ROK3_40] +Neben dem Kühler für die Babynahrung? + +[RBM3_5] +~g~Bring Love Fist zu ihrem Auftritt. + +[CUBANM] +DIE KUBA-GANG-MISSIONEN + +[CUBAN1] +KUBA-MISSION 1 + +[CUBAN2] +KUBA-MISSION 2 + +[CUB2_10] +~r~Du sollst Haitianer ausschalten, keine Kubaner! + +[CUBAN3] +KUBA-MISSION 3 + +[CUBAN4] +KUBA-MISSION 4 + +[CUB4_04] +~r~Du hast die Basis alarmiert. Jetzt kommen wir niemals rein! + +[CUB4_05] +~r~Du solltest doch im Auto bleiben. Jetzt lassen sie uns niemals rein. + +[CUB4_25] +Okay, los geht's. + +[PROT] +SCHUTZGELD-MISSIONEN + +[PRO1_02] +~g~Verlasse das Einkaufszentrum. + +[PRO3_06] +~g~Häng die Cops ab. + +[PORN] +PORNO-MISSIONEN + +[PORN1] +PORNO-MISSION 1 + +[POR1_03] +~r~Candy ist erledigt! + +[PORN2] +PORNO-MISSION 2 + +[PORN3] +PORNO-MISSION 3 + +[POR3_18] +Man hat dich bemerkt! + +[PORN4] +PORNO-MISSON 4 + +[POR4_04] +~g~Die Büros liegen auf der anderen Seite dieses Tors. + +[PHIL] +PHIL-MISSIONEN + +[PHIL1] +PHIL-MISSION 1 + +[PHIL2] +PHIL-MISSION 2 + +[PIZ1_A] +PIZZABOTEN-MISSION + +[PIZ1_03] +~g~Fahr zurück zum Pizza-Service für weitere Bestellungen. + +[PIZ1_04] +~g~Hier sind die neuen Bestellungen. + +[PIZ1_10] +Drücke die ~h~R3-Taste~w~, um die Pizza-Missionen abzubrechen. + +[CNTBUY1] +Druckerei gekauft: $ ~1~ + +[CARBUY] +Autohaus gekauft: $ ~1~ + +[PORNBUY] +Filmstudio gekauft: $ ~1~ + +[ICEBUY] +Eiscremefabrik gekauft: $ ~1~ + +[TAXIBUY] +Taxiunternehmen gekauft: $ ~1~ + +[BANKBUY] +Malibu Club gekauft: $ ~1~ + +[BOATBUY] +Bootswerft gekauft: $ ~1~ + +[PRNT_NO] +Zurzeit kannst du die Druckerei nicht kaufen. Komm später wieder. + +[CAR_NO] +Zurzeit kannst du das Autohaus nicht kaufen. Komm später wieder. + +[PORN_NO] +Zurzeit kannst du das Filmstudio nicht kaufen. Komm später wieder. + +[ICE_NO] +Zurzeit kannst du die Eiscremefabrik nicht kaufen. Komm später wieder. + +[TAXI_NO] +Zurzeit kannst du das Taxiunternehmen nicht kaufen. Komm später wieder. + +[BANK_NO] +Zurzeit kannst du den Malibu Club nicht kaufen. Komm später wieder. + +[BOAT_NO] +Zurzeit kannst du die Bootswerft nicht kaufen. Komm später wieder. + +[PRNT_R3] +Drücke R3, um die Druckerei zu kaufen. Preis: $~1~ + +[CAR_R3] +Drücke R3, um das Autohaus zu kaufen. Preis: $~1~ + +[PORN_R3] +Drücke R3, um das Filmstudio zu kaufen. Preis: $~1~ + +[ICE_R3] +Drücke R3, um die Eiscremefabrik zu kaufen. Preis: $~1~ + +[TAXI_R3] +Drücke R3, um das Taxiunternehmen zu kaufen. Preis: $~1~ + +[BANK_R3] +Drücke R3, um den Malibu Club zu kaufen. Preis: $~1~ + +[BOAT_R3] +Drücke R3, um die Bootswerft zu kaufen. Preis: $~1~ + +[COL2_6] +Keine Bewegung, amerikanisches Imperialistenschwein! + +[COL2_6B] +Das ist Eigentum des französischen Staates. + +[COL2_6C] +Her damit! + +[COL3_A] +Thomas, schön, dass Sie da sind. + +[COL3_B] +Verzeihen Sie, dass ich direkt zum Geschäftlichen komme. + +[COL3_C] +Diaz bat mich, eine kleine Transaktion für ihn zu überwachen. + +[COL3_D] +Hoffentlich läuft es diesmal besser als zuletzt. + +[COL3_E] +Deshalb wende ich mich ja an Sie, mein Freund. + +[COL3_F] +Ich habe im Parkhaus ein wenig Artillerie deponiert. + +[COL3_G] +Holen Sie sich die und beschützen Sie Diaz' Männer bei der Übergabe. + +[COL4_2] +Ich weiß nicht, Sir! + +[COL4_5] +Sir, zu Befehl, Sir! + +[COL4_10] +Gehen wir ein paar Donuts essen. + +[COL4_16] +Fahrzeug wird weggeschafft, Sir! + +[COL4_25] +Selbstzerstörung des Fahrzeugs eingeleitet! + +[COL5_6] +Mercedes - dieses Kind bringt mich noch ins Grab. + +[COL5_8] +Verdammte Bande! + +[COL5_5] +Nehmt das, Franzosenschweine! + +[CNT2_1] +Macht ihn fertig! + +[CNT2_2] +Hol die Platten! + +[CNT2_3] +Beschütze den Kurier! + +[FINKILL] +Ok, Jungs, macht ihn fertig! + +[FIN_1A] +Komm her, du hinterhältiger Dreckskerl! + +[FIN_1B] +Jetzt bist du fällig, du mieser Verräter! + +[FIN_1C] +Das wird dein letzter 'Dance', Lance Vance! + +[FIN_2B] +Ach, wirklich? + +[FIN_2C] +Der dumme Spruch war schon im Kindergarten alt! + +[FIN_3] +Keiner da, um dich rauszuhauen, diesmal, hä, Tommy? + +[FIN_4] +Du bist am Ende, Tommy. + +[FIN_5] +Du hast dich auf die falsche Seite geschlagen, Lance... + +[FIN_6] +Sonny ist oben am Safe mit MEINEM Geld... + +[FIN_10] +Sonny? SONNY! Jetzt bist du fällig! + +[FIN_11A] +Du hast mir 15 Jahre gestohlen, Sonny. + +[FIN_11B] +Und das wirst du mir jetzt büßen! + +[FIN_12A] +Du kapierst es immer noch nicht, oder? + +[FIN_12B] +Dein Arsch gehört mir, Tommy. + +[FIN_12C] +Diese 15 Jahre hast du anstelle von mir abgesessen! + +[FIN_13] +Schnappt ihn euch. Er hat nie was begriffen. + +[RACES_4] +3 + +[RACES_5] +2 + +[RACES_6] +1 + +[RACES_7] +LOS! + +[RACES_9] +Zeit: ~1~:~1~ + +[RACES] +ZEIT: + +[RACES17] +Neue Bestzeit: ~1~:~1~ + +[RACES18] +PREISGELD: $~1~ + +[RACES20] +Neue Bestzeit: ~1~:0~1~ + +[RACES21] +Zeit: ~1~:0~1~ + +[RCH1_1] +~g~Benutze den ferngesteuerten Helikopter, den RC Raider, um die Checkpoints zu PASSIEREN. + +[RCH1_2] +~g~Die CHECKPOINTS sind überall auf dem Flughafen verteilt. + +[RCH1_3] +~g~Du hast ~c~8 MINUTEN~g~, um alle ~c~20 zu passieren! + +[RCH1_5] +Zeit + +[RCRC1_2] +~g~Begib dich zur Startlinie! + +[RCRC1_4] +~g~3 + +[RCRC1_5] +~g~2 + +[RCRC1_6] +~g~1 + +[RCRC1_7] +~g~LOS! + +[RCRC1_8] +~g~Absolvierte Zeit: ~1~ Sekunden + +[RCPL1_1] +~g~Liefere dir mit 3 anderen RC Barons ein CHECKPOINT-RENNEN. + +[RCPL1_2] +~g~Du musst durch die ~o~MITTLERE MARKIERUNG ~g~hindurch, um einen Checkpoint zu passieren. + +[RCPL1_3] +~g~Begib dich zur Startlinie! + +[ICC1_O] +Was ist denn in Sie gefahren? + +[FEA_2SP] +2 Boxen + +[FEA_4SP] +Mehr als 2 Boxen + +[FEA_EAR] +Kopfhörer + +[FEA_NAH] +KEINE AUDIO HARDWARE + +[FET_APP] +ÜBERNEHMEN + +[FES_SKN] +SKIN-NAME + +[FES_DAT] +DATUM + +[FES_SET] +Skin verwenden + +[FET_DEF] +Standard wiederherst. + +[FESZ_QZ] +Dieses Spiel wirklich speichern? + +[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] +Hinauszoomen + +[FEC_EEX] +Ein-/Aussteigen + +[FEC_RAD] +Radio + +[FEC_SUB] +Spezialmission + +[FEC_CMR] +Blickwinkel ändern + +[FEC_JMP] +Springen + +[FEC_SPN] +Sprinten + +[FEC_HND] +Handbremse + +[FEC_LOL] +Nach links sehen + +[FEC_LOR] +Nach rechts sehen + +[FEC_NTR] +Nächstes Ziel + +[FEC_PTT] +Vorheriges Ziel + +[FEC_LBA] +Nach hinten sehen + +[FEC_CEN] +Kamera zentrieren + +[FET_CCN] +Classic + +[FET_SCN] +Standard + +[FET_CFT] +ZU FUSS + +[FET_CCR] +IN FAHRZEUG + +[FET_CAC] +AKTION + +[FEC_IBT] +- + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +NICHT BEL. + +[FEC_TFL] +Links sehen + Geschütz L + +[FEC_TFR] +Rechts sehen + Geschütz R + +[FEC_MWF] +MAUSRAD AUFW. + +[FEC_MWB] +MAUSRAD ABW. + +[FEC_ORR] +oder + +[FEC_NUS] +NICHT VERWENDET + +[FEC_LUD] +Aufw. sehen + +[FEC_LDU] +Abw. sehen + +[FEC_CMP] +COMBO: L+R SEHEN + +[LAW_1A] +law_1a + +[LAW_1B] +law_1b + +[LAW_2A] +law_2a + +[LAW_2B] +law_2b + +[FEH_STA] +STATISTIKEN + +[FEH_LOA] +LADEN + +[FEH_CON] +STEUERUNG + +[FEH_AUD] +AUDIO + +[FEH_DIS] +ANZEIGE + +[FEH_LAN] +SPRACHE + +[FEH_SGA] +NEUES SPIEL STARTEN + +[FEO_CON] +Controller-Setup + +[FEO_AUD] +Audio-Setup + +[FEO_DIS] +Anzeigen-Setup + +[FEO_LAN] +Sprachen-Setup + +[FEO_PLA] +Spieler-Setup + +[FEA_OUT] +Ausgabe + +[FEA_ST] +Stereo + +[FEA_DTS] +DTS + +[FEA_RSS] +Radiosender + +[FEA_NON] +RADIO AUS + +[FEA_FM0] +WILDSTYLE + +[FEA_FM1] +FLASH FM + +[FEA_FM2] +KCHAT + +[FEA_FM3] +FEVER 105 + +[FEA_FM4] +VROCK + +[FEA_FM5] +VCPR + +[FEA_FM7] +EMOTION 98.3 + +[FEA_FM8] +WAVE 103 + +[FED_BRI] +Helligkeit + +[FED_TRA] +Unschärfe-FX + +[FED_SUB] +Untertitel + +[FED_WIS] +Breitbild + +[FED_POS] +Anzeige-Position + +[FEP_RES] +Fortsetzen + +[FEP_STG] +Spiel starten + +[FEP_STA] +Statistiken + +[FEP_BRI] +Missionsinfos + +[FEP_OPT] +Optionen + +[FEP_QUI] +Spiel beenden + +[FES_LOA] +Spiel laden + +[FES_DEL] +Spiel löschen + +[FEC_CSU] +Controller-Setup + +[FEC_RED] +Steuerung ändern + +[FEC_MOU] +Maus-Einstellg. + +[DISTGOL] +Mit Golfwagen zurückgel. Entfernung (Meilen) + +[DISTGOM] +Mit Golfwagen zurückgel. Entfernung (Meter) + +[ST_FAVR] +Lieblings-Radiosender + +[ST_WSTR] +Unbeliebtester Radiosender + +[ST_FAVV] +Lieblingsfahrzeug + +[ST_STAR] +Anzahl angehäufter Fahndungssterne + +[ST_HEAD] +Anzahl Köpfe + +[ST_GANG] +Unbeliebteste Gang + +[ST_STGN] +Anzahl losgewordener Fahndungssterne + +[TYREPOP] +Zerschossene Reifen + +[TYRESLA] +Aufgeschlitzte Reifen + +[ST_BRK] +Erledigte Gegner im Chaos-Derby + +[ST_LTBR] +Längste Zeit im Chaos-Derby (Sekunden) + +[ST_GNG1] +Kubaner + +[ST_GNG2] +Haitianer + +[ST_GNG3] +Möchtegern-Gangster + +[ST_GNG4] +Diaz' Gang + +[ST_GNG5] +Sicherheitsbeamte + +[ST_GNG6] +Biker-Gang + +[ST_GNG7] +Vercetti-Gang + +[ST_GNG8] +Golfer + +[FEA_FM6] +ESPANTOSO + +[ST_ASSI] +Ausgeführte Attentätermissionen + +[DISTBIK] +Mit Motorrad zurückgel. Strecke (Meilen) + +[DISTBIM] +Mit Motorrad zurückgel. Strecke (Meter) + +[HOTEL] +Ocean View Hotel + +[KICK1_9] +VERBLEIBENDE CHECKPOINTS: + +[FIN_B6] +Du hast nicht genug Geld, um diese Mission zu beginnen. + +[TEX3_9] +~g~Steuere den Helikopter über eine Bombe, um sie aufzunehmen. + +[HELP22] +Begib dich zu dem grünen Haus-Symbol auf dem Radar. + +[FES_SSC] +Daten wurden gespeichert. Weiter mit OK. + +[FES_DSC] +Daten wurden gelöscht. Weiter mit OK. + +[FESZ_QC] +Dieses beschädigte Spiel überschreiben? + +[FES_CHE] +Achtung! Ein oder mehrere Cheats sind aktiviert, dies kann sich auf die Speicherung auswirken. Es wird empfohlen, dieses Spiel nicht zu speichern. + +[FET_SG] +SPIEL SPEICHERN + +[FEH_BRI] +MISSIONSINFO + +[FEH_MAP] +KARTE + +[FEM_OK] +OK + +[FEC_CRO] +Ducken + +[FEC_CR3] +Ducken (L3-Taste) + +[FEC_SMT] +Spezialmission + +[FEC_SM3] +Spezialmission (R3-Taste) + +[FEC_RSC] +Radiosender + +[ST_PR01] +Flieger + +[ST_PR02] +Luftwaffensoldat + +[ST_PR03] +Gefreiter + +[ST_PR04] +Unteroffizier + +[ST_PR05] +Leutnant + +[ST_PR06] +Stabsunteroffizier + +[ST_PR07] +Hauptmann + +[ST_PR08] +Könner + +[ST_PR09] +Profi + +[ST_PR10] +Roter Baron + +[ST_PR11] +Wildgans + +[ST_PR12] +Viper + +[ST_PR13] +Falke + +[ST_PR14] +Adler + +[ST_PR15] +Blitz + +[ST_PR16] +Tornado + +[ST_PR17] +Taifun + +[ST_PR18] +Luftwaffengeneral + +[ST_PR19] +Ass + +[FET_LG] +SPIEL LADEN + +[CAR_EXP] +Straßenfahrzeuge zerstört + +[BOA_EXP] +Boote zerstört + +[HEL_DST] +Flugzeuge & Helikopter zerstört + +[STFT_01] +Schnellste Zeit bei 'Wheels of Steels' + +[STFT_02] +Schnellste Zeit bei 'Der Fahrer' + +[STFT_03] +Schnellste Zeit auf Geländemotorradstrecke + +[STFT_04] +Schnellste Zeit bei Modellflugzeug-Rennen + +[STFT_05] +Schnellste Zeit mit den ferngesteuerten Autos + +[STFT_06] +Schnellste Zeit bei Helikopter-Rennen + +[STFT_07] +Schnellste Zeit bei 'Todeskaracho' + +[STFT_08] +Schnellste Zeit bei 'Ocean Drive' + +[STFT_09] +Schnellste Zeit bei 'Küsten-Rallye' + +[STFT_10] +Schnellste Zeit bei 'Capital Cruise' + +[STFT_11] +Schnellste Zeit bei 'Tour!' + +[STFT_12] +Schnellste Zeit bei 'V.C. Endurance' + +[STHC_01] +High-Score bei Schießstand-Mission + +[STHC_02] +Beste Trefferquote am Schießstand (in Prozent) + +[STHC_03] +Anzahl getätigter Drogendeals + +[HELP24] +Du kannst jetzt Aufträge vom Colonel annehmen. + +[HELP25] +Du kannst jetzt Aufträge von Avery Carrington annehmen. + +[HELP29] +Außerhalb eine Mision kannst du zu dem Bekleidungsgeschäft gehen. + +[HELP30] +Wenn du neue Klamotten kaufst, reduziert sich dein Fahndungslevel auf null. + +[ASM4_24] +Entfernung: + +[RBM1_6] +~g~Bring Mercedes und den 'Love Juice' zu der Band ins Aufnahmestudio. + +[RBM1_3] +NICHT MEHR BENÖTIGT + +[HAM1_5] +NICHT MEHR BENÖTIGT + +[RBM1_11] +NICHT MEHR BENÖTIGT + +[HELP31] +Um aus dem fahrenden Fahrzeug zu schießen, sieh zuerst mit ~k~~VEHICLE_LOOKLEFT~ oder ~k~~VEHICLE_LOOKRIGHT~ nach links oder rechts. + +[HELP34] +Du brauchst eine Maschinenpistole für einen 'Drive-By'. + +[STRIP_1] +~r~Nicht genug Cash, du windiger Geizhals! + +[EXIT_1] +~k~~PED_SPRINT~ zum Beenden. + +[ASM1_B] +Ihr nächster Auftrag klebt unter dem Telefon. + +[ASM1_C] +Ich habe noch mehr Arbeit, die eine eher 'zupackende' Art verlangt. + +[SCARF] +Apartment 3c + +[LAW4_10] +Reiche Manager sind Schweine! + +[RCH1_6] +~g~Benutze den ferngesteuerten Helikopter, um Checkpoints überall auf dem Flughafen abzufliegen. + +[RCH1_9] +~b~GESAMTZEIT: ~1~:~1~ + +[RCH1_10] +~b~GESAMTZEIT: ~1~:0~1~ + +[WHEEL01] +ZWEIRAD DOPPELBONUS: $ ~1~ Distanz: ~1~.~1~m Zeit: ~1~ Sekunden + +[WHEEL02] +ZWEIRAD DOPPELBONUS: $ ~1~ Distanz: ~1~ Fuß Zeit: ~1~ Sekunden + +[WHEEL03] +ZWEIRAD BONUS: $ ~1~ Zeit: ~1~ Sekunden + +[WHEEL04] +ZWEIRAD BONUS: $ ~1~ Distanz: ~1~.~1~m + +[WHEEL05] +ZWEIRAD BONUS: $ ~1~ Distanz: ~1~ Fuß + +[WHEEL06] +WHEELIE BONUS: $ ~1~ Distanz: ~1~.~1~m Zeit: ~1~ Sekunden + +[WHEEL07] +WHEELIE BONUS: $ ~1~ Distanz: ~1~ Fuß Time: ~1~ Sekunden + +[WHEEL08] +WHEELIE BONUS: $ ~1~ Zeit: ~1~ Sekunden + +[WHEEL09] +WHEELIE BONUS: $ ~1~ Distanz: ~1~.~1~m + +[WHEEL10] +WHEELIE BONUS: $ ~1~ Distanz: ~1~.~1~ Fuß + +[WHEEL11] +VOLLBREMSUNGSBONUS: $ ~1~ Distanz: ~1~.~1~m Zeit: ~1~ Sekunden + +[WHEEL12] +VOLLBREMSUNGSBONUS: $ ~1~ Distanz: ~1~ Fuß Zeit: ~1~ Sekunden + +[WHEEL13] +VOLLBREMSUNGSBONUS: $ ~1~ Zeit: ~1~ Sekunden + +[WHEEL14] +VOLLBREMSUNGSBONUS: $ ~1~ Distanz: ~1~.~1~m + +[WHEEL15] +VOLLBREMSUNGSBONUS: $ ~1~ Distanz: ~1~ Fuß + +[ROK3_72] +Love Fist! + +[ROK3_74] +Ah, seht mal, was ist das? Hey, Tommy, leg mal dieses Band ein. + +[POR1_19] +Hey! + +[DESPERA] +Desperado + +[MOB_99A] +Begib dich zu dem Fernsprecher neben dem Einkaufszentrum in Washington Beach. + +[MOB_98A] +Begib dich zu dem Fernsprecher in Vice Point. + +[MOB_96A] +Begib dich zu dem Fernsprecher beim Flughafen-Terminal. + +[MOB_95A] +Begib dich zu dem Fernsprecher in Little Havana. + +[BNK1_1] +Kann ich Ihnen helfen, Sir? + +[BNK1_2] +Der Typ ist verkleidet! + +[BNK1_3] +Er ist verrückt geworden! + +[BNK1_4] +Wer zum Teufel sind Sie? + +[BNK1_5] +Wo ist Ihre Dienstmarke? + +[BNK1_6] +Da sind sie! Feuer frei! + +[MOB_24A] +Hallo, spricht da Mr. Vercetti? + +[MOB_24B] +Ja. + +[MOB_24C] +Hier Cortez. Sie waren bei meiner Party. + +[MOB_24D] +Ja. Ich erinnere mich. + +[MOB_24E] +Mr. Vercetti, es war höchst unglücklich, was da bei der Abwicklung Ihres Geschäfts vorgefallen ist. + +[MOB_24F] +Ich weiß. + +[MOB_24G] +Sie sollen wissen, dass ich und meine Leute alles tun, um der Sache auf den Grund zu gehen. + +[MOB_24H] +Falls Sie mit mir persönlich sprechen wollen, finden Sie mich auf dem Schiff. Guten Tag, Senor. + +[BNK2_6] +Der Typ ist geisteskrank! + +[ANGEL] +Angel + +[CUBJET] +Kubanischer Jetmax + +[SANDKIN] +Sandking + +[POLMAV] +Polizei-Maverick + +[BOXVILL] +Boxville + +[BENSON] +Benson + +[HOTRINA] +Hotring Racer + +[HOTRINB] +Hotring Racer + +[BLOODRA] +Chaos-Derby Banger + +[BLOODRB] +Chaos-Derby Banger + +[MAFIACR] +Mafia Cruiser + +[COP_M2] +'EINSATZ IN VICE CITY' + +[COP_M3] +'DONNER ÜBER VICE CITY' + +[BNK3_2] +Ich fahre nicht für dich, niemals. Das erzähle ich in der Therapie. + +[FEM_SL1] +Datei 1 nicht vorhanden + +[FEM_SL2] +Datei 2 nicht vorhanden + +[FEM_SL3] +Datei 3 nicht vorhanden + +[FEM_SL4] +Datei 4 nicht vorhanden + +[FEM_SL5] +Datei 5 nicht vorhanden + +[FEM_SL6] +Datei 6 nicht vorhanden + +[FEM_SL7] +Datei 7 nicht vorhanden + +[FEM_SL8] +Datei 8 nicht vorhanden + +[FEA_CHA] +Tonausgabe wird auf STEREO umgeschaltet. Bitte warten... + +[FEA_CHD] +Achtung! Sie schalten die Tonausgabe von STEREO auf DTS um. Bitte warten... + +[FEI_SEL] +Auswahl + +[FEI_BAC] +Zurück + +[FEI_RES] +Weiter + +[FEI_NAV] +Navigieren + +[FEI_BTX] +/-Taste - + +[FEI_BTT] +"-Taste - + +[FEI_STA] +START-Taste - + +[FEI_BTD] +; = > < - + +[FEI_STO] +Stop + +[MOB_68A] +Tommy, Alter, ich hab eine Überraschung für dich. + +[MOB_68B] +Ich bin im Aufnahmestudio mit ein paar super Musikern. + +[MOB_68C] +Warum kommst du nicht kurz vorbei? + +[MOB_68D] +Kannst dir denken, dass es sich lohnt, oder? Bis dann. + +[OUTFT1] +Straße + +[OUTFT2] +Abendgarderobe + +[OUTFT3] +Overall + +[OUTFT4] +Country Club + +[OUTFT5] +Havana + +[OUTFT6] +Cop + +[OUTFT7] +Bankräuber + +[OUTFT8] +Freizeit + +[OUTFT9] +Mr. Vercetti + +[OUTFT10] +Trainingsanzug + +[OUTFT13] +MC Tommy + +[CAR_AS1] +AUTOHAUS ERWORBEN + +[CAR_AS2] +~g~Sunshine Autos generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +[BUYSAVE] +~g~Außerhalb einer Mission kannst du dein Spiel hier umsonst speichern. + +[BUYGARG] +~g~Du kannst in dieser Garage auch Autos abstellen. + +[STRPBUY] +Pole Position Club gekauft: $ ~1~ + +[STRP_R3] +Drücke R3, um den Pole Position Club zu kaufen. Preis: $~1~ + +[NBMN_R3] +Drücke R3, um Elswanko Casa zu kaufen. Preis: $~1~ + +[GA_4] +Autobomben kosten $1000 pro Stück. + +[GA_5] +In deinem Wagen ist schon eine Autobombe. + +[GA_6] { reVC update } +Park die Karre, mach sie durch Drücken der ~h~~k~~VEHICLE_FIREWEAPON~~w~ scharf und dann HAU AB! + +[GA_7] { reVC update } +Mach die Bombe mit der ~h~~k~~VEHICLE_FIREWEAPON~~w~ scharf. Dann geht sie hoch, wenn der Wagen angelassen wird. + +[GA_6B] { reVC update } +Park die Karre, mach sie durch Drücken der ~h~~k~~VEHICLE_FIREWEAPON~~w~ scharf und dann HAU AB! + +[GA_7B] { reVC update } +Mach die Bombe mit der ~h~~k~~VEHICLE_FIREWEAPON~~w~ scharf. Dann geht sie hoch, wenn der Wagen angelassen wird. + +[MOB_70A] +Tommy, ich bin's, Colonel Cortez. Hören Sie, Sie sind doch ein Mann, der Dinge zu erledigen weiß. + +[MOB_70B] +Sie finden mich auf dem Boot. + +[PICK2] +.357 in Verstecken angeliefert! + +[PICK3] +Kettensägen in Verstecken angeliefert! + +[PICK4] +Flammenwerfer in Verstecken angeliefert! + +[PICK5] +.308 Präzisionsgewehr in Verstecken angeliefert! + +[PICK6] +Mini-Kanonen in Verstecken angeliefert! + +[PICK7] +Raketenwerfer in Verstecken angeliefert! + +[PICK8] +Sea Sparrow jetzt bei Vercetti Estate verfügbar! + +[PICK9] +Panzer jetzt in Army-Kaserne verfügbar! + +[PICK10] +Hunter jetzt in Army-Kaserne verfügbar! + +[CLOTH1] +Abendgarderobe bei Rafaels in Ocean Beach erhältlich. + +[CLOTH2] +Straßenkleidung in Verstecken angeliefert. + +[CLOTH3] +Overall bei 'Tooled Up' im North Point Einkaufszentrum erhältlich. + +[CLOTH4] +Country Club-Bekleidung beim Golf Club in Leaf Links erhältlich. + +[CLOTH5] +Havana-Outfit bei 'Little Havana Streetwear' in Little Havana erhältlich. + +[CLOTH6] +Polizeiuniform bei Polizeistation in Washington Beach erhältlich. + +[CLOTH7] +Freizeitbekleidung bei 'Gash' im North Point Einkaufszentrum erhältlich. + +[CLOTH8] +Mr. Vercetti-Outfit bei 'Collar & Cuffs'in Ocean Beach erhältlich. + +[CLOTH9] +Trainingsanzug bei 'Jocksport' in Downtown erhältlich. + +[CLOTH10] +Bankräuber-Outfit beim Malibu Club in Vice Point erhältlich. + +[MOB_62A] +Tommy, hier Ricardo Diaz. Ich wollte dir danken, dass du mich gerettet hast. + +[MOB_62B] +Ich hab den Trottel von Cortez gefragt. Er meint, du wärst ein Mann für alle Fälle. Komm doch mal bei mir vorbei. + +[MOB_62C] +Ich brauche einen Kerl wie dich. Ich hab nämlich nur Schwachköpfe. + +[MOB_62D] +Nur lauter Schwachköpfe. Ich mache dich schwer reich. + +[GOAWAY2] +Komm wieder, wenn du die Biker Gang-Missionen abgeschlossen hast. + +[COL2_9] +Du amerikanischer Idiot! Sie sind dir hierher gefolgt! + +[LOADCOL] +Lade... + +[STFT_17] +Schnellste Zeit bei 'PCJ Rallye' + +[STFT_18] +Schnellste Zeit bei 'Krasses Gelände' + +[STFT_19] +Schnellste Zeit bei 'Teststrecke' + +[NEW_REC] +Neuer Rekord!! ~1~ Minuten und ~1~ Sekunden. + +[BMX_HOW] +~g~Fahr zwei Runden auf der Geländemotorradstrecke. ~y~Passiere dabei ~g~die ~y~CHECKPOINTS~g~! + +[BMXREW1] +~g~Jedes Mal wenn du deine bisherige Bestzeit für die zwei Runden verbesserst, + +[BMXREW2] +~g~bekommst du eine noch höhere ~y~BELOHNUNG~g~! + +[BMXRAIN] +~g~Sieht nach Regen aus... + +[ITBEG] +Am Anfang... + +[NBMNBUY] +El Swanko Casa gekauft: $ ~1~ + +[LNKVBUY] +Links View Apartment gekauft: $ ~1~ + +[HYCOBUY] +Hyman Condo gekauft: $ ~1~ + +[BUYGARS] +~g~Du kannst in diesen Garagen auch Autos abstellen. + +[OCHEBUY] +Ocean Heights Apartment gekauft: $ ~1~ + +[WASHBUY] +1102 Washington Street gekauft: $ ~1~ + +[VCPTBUY] +3321 Vice Point gekauft: $ ~1~ + +[HELP6_C] +Drücke die ~h~~k~~VEHICLE_HANDBRAKE~~w~, um die Handbremse anzuziehen. + +[HELP2_A] +Drücke die ~h~~k~~PED_SPRINT~~w~, um zu ~h~sprinten. + +[HELP4_A] +Drücke die ~h~~k~~VEHICLE_ACCELERATE~~w~, um zu beschleunigen. + +[HELP5_A] +Drücke die ~h~~k~~VEHICLE_BRAKE~~w~, um zu bremsen, oder um zurückzusetzen, wenn das Fahrzeug steht. + +[HELP8_A] +Drücke die ~h~~k~~PED_SNIPER_ZOOM_IN~~w~, um an das Ziel heranzuzoomen und die ~x~/-Taste~w~,um herauszuzoomen. + +[PBOAT_1] { reVC update } +Drücke die~h~ ~k~~VEHICLE_FIREWEAPON~~w~, um die Bordkanonen abzufeuern. + +[SEG3_4] { reVC update } +~g~Um Bomben aufzunehmen, steuere den RC Raider einfach nahe an sie heran. Um eine abzuwerfen, drücke die ~h~~k~~VEHICLE_FIREWEAPON~~g~-Taste. + +[RCR1_3] { reVC update } +~g~Wenn du diese Mission abbrechen willst, drücke die ~h~~k~~VEHICLE_FIREWEAPON~~g~, um dein Auto zu sprengen. + +[HELP32] { reVC update } +Dann feuere mit der ~h~~k~~VEHICLE_FIREWEAPON~. + +[HELP33] { reVC update } +Dann feuere mit der ~h~~k~~VEHICLE_FIREWEAPON~. + +[TTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~~w~, um Taxi-Missionen an- oder abzuschalten. + +[TTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~~w~, um Taxi-Missionen an- oder abzuschalten. + +[FTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~~w~, um Feuerwehrwagen-Missionen an- oder abzuschalten. + +[FTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~~w~, um Feuerwehrwagen-Missionen an- oder abzuschalten. + +[CTUTOR] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~~w~, um Bürgerwehr-Missionen zu aktivieren oder zu deaktivieren. + +[CTUTOR2] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~~w~, um Bürgerwehr-Missionen zu aktivieren oder zu deaktivieren. + +[HELP8_B] +Drücke die~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, um ~h~an das Ziel heranzuzoomen ~w~und die~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, um ~h~herauszuzoomen~w~. + +[ATUTOR3] +Drücke die ~h~~k~~TOGGLE_SUBMISSIONS~~w~, um Krankenwagen-Missionen an- oder abzuschalten. + +[GUN_H1] +~w~Drück die~h~ ~k~~PED_SPRINT~~w~, um zu kaufen. ~w~Drück die~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, um zu gehen. + +[PU_CF3] { reVC update } +Drück die ~h~~k~~PED_ANSWER_PHONE~~w~, um die augenblickliche Waffe in diesem Slot auszutauschen. + +[PU_CF4] { reVC update } +Drück die ~h~~k~~PED_ANSWER_PHONE~~w~, um die augenblickliche Waffe in diesem Slot auszutauschen. + +[HELP9_B] +Drücke die~h~ ~k~~PED_FIREWEAPON~~w~, um das Präzisionsgewehr ~h~abzufeuern~w~. + +[HELP37] +Wenn du doch nicht in ein Auto einsteigen willst, das du im Begriff bist, zu klauen, drück die ~h~~k~~PED_SPRINT~. + +[HELP6_A] +Drücke die ~h~~k~~VEHICLE_HANDBRAKE~~w~, um die Handbremse anzuziehen. + +[HELP6_D] +Drücke die ~h~~k~~VEHICLE_HANDBRAKE~~w~, um die Handbremse anzuziehen. + +[HELP26] +Drücke die ~h~~k~~VEHICLE_ENTER_EXIT~~w~, um in ein Fahrzeug ein- oder auszusteigen. + +[HELP27] +Drücke ~h~~k~~VEHICLE_TURRETUP~~w~ oder ~h~~k~~VEHICLE_TURRETDOWN~~w~ um dein Gewicht auf einem Motorrad zu verlagern. + +[HELP28] +Drücke ~h~~k~~VEHICLE_TURRETUP~~w~ oder ~h~~k~~VEHICLE_TURRETDOWN~~w~ um dein Gewicht auf einem Motorrad zu verlagern. + +[HELP35] +Benutze die ~h~~k~~GO_LEFT~~w~ oder ~h~~k~~GO_RIGHT~~w~ um das Fahrzeug zu steuern. + +[HELP36] +Benutze die ~h~~k~~GO_LEFT~~w~ oder ~h~~k~~GO_RIGHT~~w~ um das Fahrzeug zu steuern. + +[HELP42] +Folge dem ~q~rosa Symbol~w~, um zum Hotel zu kommen. + +[HELP19] +Stell dich in die ~q~rosa Markierung~w~, um weiterzumachen. + +[HELP1] +Halte in der Mitte der ~q~rosa Markierung. + +[HELP12] +Geh ins Zentrum der ~q~rosa Markierung~w~, um eine Mission zu starten. + +[SEG3_6] +~g~Um eine Zielzone zu treffen, musst du die Bombe innerhalb der ~q~rosa Markierung~g~ abwerfen. Die Reihenfolge spielt dabei keine Rolle. + +[S_PROMP] +Außerhalb einer Mission kannst du dein Spiel speichern, indem du das ~h~Cassetten-Symbol aufnimmst. + +[HELP16] +Geh durch die Eingangstür, um das ~h~Ocean View Hotel~w~ zu betreten. + +[HELP43] +~g~Begib dich zum ~h~Ocean View Hotel~g~ am Ocean Drive. + +[HELI_F1] +~r~Heli-Ceckpoint-Mission abgebrochen! + +[AMMUHLP] +Wenn du Waffen brauchst, geh zu ~h~AmmuNation~w~. Das ~h~Pistolensymbol~w~ auf dem Radar zeigt dir den Weg. + +[HELI_1] +Downtown Heli-Checkpoint + +[HELI_2] +Ocean Beach Heli-Checkpoint + +[HELI_3] +Vice Point Heli-Checkpoint + +[HELI_4] +Little Haiti Heli-Checkpoint + +[FST_MFR] +Lieblings-Radiosender + +[FST_LFR] +Unbeliebtester Radiosender + +[FEI_HOL] +Halten + +[FEI_ZOO] +Zoom + +[FEI_BTR] +> < - + +[FEI_NA] +Nicht verfügbar + +[MESA] +Mesa Grande + +[STRP_NO] +Zurzeit kannst du die Stripper-Bar nicht kaufen. Komm später wieder. + +[CHSE] +VERFOLGUNGSJAGD + +[NBMN_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um El Swanko Casa zu kaufen. Preis: $~1~ + +[NBMN_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um El Swanko Casa zu kaufen. Preis: $~1~ + +[NBMN_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um El Swanko Casa zu kaufen. Preis: $~1~ + +[LNKV_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Links View Apartment zu kaufen. Preis: $~1~ + +[LNKV_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Links View Apartment zu kaufen. Preis: $~1~ + +[LNKV_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Links View Apartment zu kaufen. Preis: $~1~ + +[HYCO_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Hyman Condo zu kaufen. Preis: $~1~ + +[HYCO_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Hyman Condo zu kaufen. Preis: $~1~ + +[HYCO_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Hyman Condo zu kaufen. Preis: $~1~ + +[OCHE_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Ocean Heights Apartment zu kaufen. Preis: $~1~ + +[OCHE_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Ocean Heights Apartment zu kaufen. Preis: $~1~ + +[OCHE_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Ocean Heights Apartment zu kaufen. Preis: $~1~ + +[WASH_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um 1102 Washington Street zu kaufen. Preis: $~1~ + +[WASH_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um 1102 Washington Street zu kaufen. Preis: $~1~ + +[WASH_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um 1102 Washington Street zu kaufen. Preis: $~1~ + +[VCPT_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um 3321 Vice Point zu kaufen. Preis: $~1~ + +[VCPT_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um 3321 Vice Point zu kaufen. Preis: $~1~ + +[VCPT_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um 3321 Vice Point zu kaufen. Preis: $~1~ + +[PRNT_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Druckerei zu kaufen. Preis: $~1~ + +[PRNT_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Druckerei zu kaufen. Preis: $~1~ + +[PRNT_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Druckerei zu kaufen. Preis: $~1~ + +[CAR_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Autohaus zu kaufen. Preis: $~1~ + +[CAR_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Autohaus zu kaufen. Preis: $~1~ + +[CAR_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Autohaus zu kaufen. Preis: $~1~ + +[PORN_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Filmstudio zu kaufen. Preis: $~1~ + +[PORN_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Filmstudio zu kaufen. Preis: $~1~ + +[PORN_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Filmstudio zu kaufen. Preis: $~1~ + +[ICE_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Eiscremefabrik zu kaufen. Preis: $~1~ + +[ICE_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Eiscremefabrik zu kaufen. Preis: $~1~ + +[ICE_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Eiscremefabrik zu kaufen. Preis: $~1~ + +[TAXI_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Taxiunternehmen zu kaufen. Preis: $~1~ + +[TAXI_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Taxiunternehmen zu kaufen. Preis: $~1~ + +[TAXI_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um das Taxiunternehmen zu kaufen. Preis: $~1~ + +[BANK_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um den Malibu Club zu kaufen. Preis: $~1~ + +[BANK_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um den Malibu Club zu kaufen. Preis: $~1~ + +[BANK_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um den Malibu Club zu kaufen. Preis: $~1~ + +[BOAT_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Bootswerft zu kaufen. Preis: $~1~ + +[BOAT_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Bootswerft zu kaufen. Preis: $~1~ + +[BOAT_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um die Bootswerft zu kaufen. Preis: $~1~ + +[STRP_L] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um den Pole Position Club zu kaufen. Preis: $~1~ + +[STRP_T] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um den Pole Position Club zu kaufen. Preis: $~1~ + +[STRP_C] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~, um den Pole Position Club zu kaufen. Preis: $~1~ + +[STOCK] +~r~nicht vorrätig + +[HELP14] +Um das Büro des Anwalts zu finden, folge dem ~h~'L'~w~ auf dem Radar. + +[BOAT_AS] +~g~Die Bootswerft generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +[BOAT_A2] +BOOTSWERFT-MISSIONEN ERFÜLLT + +[BOAT_N] +Checkpoint Charlie + +[BOAT_P] +~g~Sammle die Päckchen ein, ehe die Zeit um ist. + +[FEI_R1B] +R1- \ R2-Taste - + +[HELP9_A] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~-Taste, um das Präzisionsgewehr abzufeuern. + +[HELP21] +Drücke die ~h~~k~~VEHICLE_ENTER_EXIT~~w~-Taste, um in ein Fahrzeug ein- oder auszusteigen. + +[CREAM] +Stoff-Auslieferung + +[UMBERTO] +Café Robina + +[PU_CF1] +Drück die ~h~~k~~PED_ANSWER_PHONE~~w~-Taste, um diese Waffe aufzunehmen. Falls du schon eine Waffe von diesem Typ hast, wird sie durch die neue ersetzt. + +[FED_RDM] +KARTE & SYMBOLE + +[FEC_ILU] +Kamera invertieren : + +[NITRO] +Alle Taxis haben jetzt einen Jumper-Turbo! Du musst nur die Taste für die Hupe drücken. + +[RATNG53] +Windei + +[RATNG54] +Niete + +[RATNG55] +Hacker + +[RATNG56] +Mogelpaket + +[RATNG57] +Totaler Lügner + +[STHC_04] +High-Score beim Fußball-Hochhalten + +[STHC_05] +Bestes Hotring-Resultat + +[STFT_13] +Schnellste Zeit bei Downtown Heli-Checkpoint + +[STFT_14] +Schnellste Zeit Ocean Beach Heli-Checkpoint + +[STFT_15] +Schnellste Zeit bei Vice Point Heli-Checkpoint + +[STFT_16] +Schnellste Zeit bei Little Haiti Heli-Checkpoint + +[STFT_21] +Schnellste Zeit bei Hotring + +[STFT_22] +Schnellste Rundenzeit bei Hotring + +[STFT_20] +Schnellste Zeit bei 'Pylonen-Rallye' + +[HELP44] +Halte in der ~q~rosa Markierung. + +[HELP45] +Drücke die ~h~~k~~PED_DUCK~~w~ um dich zu ducken. Dadurch erhöht sich die Treffsicherheit der Waffen, die du hältst. + +[RCR1_5] +RC Bandit-Rennen + +[RCPL1_7] +RC Baron-Rennen + +[RCH1_11] +RC Raider Checkpoint-Jagd + +[FEA_CTD] +Achtung! Für diesen Modus muss DTS-kompatible Hardware angeschlosen sein. Fortfahren? + +[FEM_STE] +AUF STEREO EINSTELLEN + +[FEM_UDY] +AUF DTS EINSTELLEN + +[GREET] +Grüße aus... + +[LANCE_1] +Hey, Mann, fahr vorsichtiger! + +[LANCE_2] +Hey, pass doch auf, was du machst! + +[LANCE_3] +Hey, wo fahren wir jetzt hin? + +[LANCE_4] +Was machen wir jetzt? + +[LAW4_15] +Mehr Geld! + +[MERC_5] +Schönes Auto, Mr. Vercetti. + +[MERC_26] +SCHNELLER, SCHNELLER, SCHNELLER! + +[MERC_27] +Vorsichtig, Tommy, ich hab mir erst letzten Monat die Nase korrigieren lassen. + +[MERC_28] +Tommy, fahr vorsichtig. + +[MERC_29] +Tommy, fahr langsamer. + +[MERC_30] +Tommy, bring jemand anders um, aber bitte nicht mich. + +[MERC_31] +Tommy, Baby, bring mich nicht um! + +[MERC_32] +Tommy, ich bin froh, dass du dieses Auto gestohlen hast! + +[MERC_40] +Ich hatte so viel Spaß. + +[MERC_43] +Adios, mein Engel. + +[MERC_44] +Und mach schön weiter Bodybuilding, hörst du? + +[MERC_45] +Ciao, mein Hübscher. + +[COL5_17] +Oh, Gott, sie haben einen Helikopter! + +[COL5_18] +Schießt den Helikopter ab! + +[COL5_19] +Tommy, schießen Sie den Helikopter ab! + +[COL5_20] +Da kommt er wieder! Schießt den Helikopter ab! + +[COL5_21] +Sieh dir diesen riesigen Helikopter an! + +[COL5_22] +Da kommt er wieder! + +[FEA_DSM] +Achtung! Dieses Spiel ist auf DTS-Tonausgabe eingestellt. Dazu muss DTS-kompatible Hardware angeschlossen sein. Bitte wählen Sie, ob Sie mit DTS oder STEREO-Tonausgabe fortfahren wollen. + +[STFT_23] +Schnellste Zeit bei Checkpoint Carlie + +[HELP50] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~-Taste, um die Spielerfigur von hinten zu sehen. + +[HELP51] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~-Taste, um die Spielerfigur von hinten zu sehen. + +[HELP52] +Drücke die ~h~~k~~PED_ANSWER_PHONE~~w~-Taste, um die Spielerfigur von hinten zu sehen. + +[HELP53] +Benutze die ~h~~k~~PED_CYCLE_WEAPON_LEFT~~w~-Taste und die ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~-Taste, um zwischen deinen Waffen zu wechseln. + +[HELP46] +Es gibt acht verschiedene Waffentypen. + +[HELP47] +Du kannst von jedem Waffentyp immer nur eine bei dir tragen - also einen Typ Pistole, einen Typ Schrotflinte, usw. + +[HELP54] +~w~Preis: $~1~ ~r~Deine augenblickliche Waffe wird ersetzt, wenn du diese kaufst. + +[HELP2A2] +Drücke die ~h~~k~~PED_SPRINT~~w~-Taste, um zu ~h~sprinten. + +[HLPSN_A] +Das Präzisionsgewehr ermöglicht dir, an dein Ziel heranzuzoomen und auf größere Distanz mit hoher Genauigkeit zu schießen. + +[HLPSN_B] +Halte die~h~ ~k~~PED_LOCK_TARGET~~w~-Taste gedrückt, um mit dem Präzisionsgewehr zu ~h~zielen~w~. + +[HLPSN_C] +Halte die~h~ ~k~~PED_LOCK_TARGET~~w~-Taste gedrückt, um mit dem Präzisionsgewehr zu ~h~zielen~w~. + +[HLPSN_D] +Drücke die ~h~~k~~PED_SNIPER_ZOOM_IN~~w~-Taste, um ~h~an das Ziel heranzuzoomen ~w~und die~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~,um ~h~herauszuzoomen~w~. + +[HLPSN_E] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~-Taste, um das Präzisionsgewehr ~h~abzufeuern~w~. + +[HLPSN_F] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~-Taste, um das Präzisionsgewehr ~h~abzufeuern~w~. + +[HLPSN_G] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~-Taste, um das Präzisionsgewehr ~h~abzufeuern~w~. + +[PLANE_H] +Benutze die ~h~~k~~VEHICLE_ACCELERATE~~w~-Taste, um zu beschleunigen. Links bzw. Rechts für Richtungswechsel. + +[PLANE_4] { reVC update } +{Benutze die ~h~~k~~VEHICLE_ACCELERATE~~w~-Taste, um zu beschleunigen. Links bzw. Rechts für Richtungswechsel.} +Benutze den rechten Analog-Stick, um zu beschleunigen. Ziehe den linken Analog-Stick, um zu steigen oder drücke ihn nach vorn, um zu sinken. Links bzw. Rechts für Richtungswechsel. + +[HELP55] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~-Taste, um den Küchenchef anzugreifen. + +[STPR_8] +Pole Position Club + +[STPR_9] +3321 Vice Point + +[STPR_10] +Links View Apartment + +[STPR_11] +El Swanko Casa + +[STPR_12] +1102 Washington Street + +[STPR_13] +Ocean Heights Apartment + +[STPR_14] +Skumole Shack + +[STPR_15] +Hyman Condo + +[RCCANX] +~r~Flugzeug-Mission abgebrochen. + +[CLT_HL2] +Wenn du neue Kleider aufnimmst, werden dadurch Fahndungslevel von einem bis zwei Sternen annulliert. + +[CRED009] +MISSION DESIGN + +[CRED359] +LEE JOHNSON + +[CRED360] +HENDRIK LESSER + +[CRED361] +PASQUALE STACCHIOTTI + +[CRED362] +ENRIQUE FERNANDEZ + +[CRED363] +PAUL BYERS + +[CRED364] +MIKE EMENY + +[CRED365] +ROB DUNKIN + +[CRED366] +CHARLIE KINLOCH + +[CRED367] +KEVIN HOBSON + +[CRED368] +JIM CREE + +[MOB_66A] +Tommy, Tommy, Tommy, wieso bist du wieder zurückgekommen? + +[MOB_66B] +Ich hab dir doch gesagt, wir wollen dich hier nicht mehr sehen. + +[MOB_67A] +Tommy, ich glaube, du solltest dich hier nicht mehr blicken lassen, hörst du? + +[MOB_67B] +Die Haitianer sind nicht sehr gut auf dich zu sprechen. + +[MOB_18A] +Tommy, hier Paul. Wie geht's, Alter? Hey, ich dachte mir, das musst du hören... + +[MOB_18B] +Echt der Hammer. Du glaubst nicht, was mir für 'ne Puppe über den Weg gelaufen ist. + +[MOB_18C] +'ne Bordsteinschwalbe, oder sowas. Unten in Little Havana. + +[MOB_18D] +Sagt, sie heißt Mercedes oder so ähnlich. + +[MOB_18E] +Wahnsinn, Alter. Die Puppe musst du dir geben. + +[MOB_18F] +Da würde 'nen Toter Hormonkoller kriegen. Sie sagt, ich wär der beste, den sie je hatte. + +[MOB_18G] +Halt die Augen nach ihr offen. Bis dann. + +[MOB_72A] +Tommy, ich bin's, Lance. Du hältst jetzt mal den Rand, Tommy, ich hab nämlich keine Zeit für Geschwätz. + +[MOB_72B] +Interessiert mich auch nicht, was du zu sagen hast. Warum auch? Ich bin dir doch sowieso scheißegal, stimmt's? + +[MOB_72C] +Du solltest dich mehr um mich kümmern. Mir einen fairen Anteil geben. Weißt du... + +[MOB_72D] +Tommy... hör mal, Mann, es tut mir leid. Nur... + +[MOB_72E] +ich werd schon mein Leben lang immer nur von oben herab behandelt, wie ein kleines Kind. + +[MOB_72F] +Mein Bruder hat das auch immer gemacht. Bitte, mein Alter, mach du das nicht. + +[MOB_72G] +Ich muss auflegen. + +[MOB_63A] +Tommy, hier Earnest. Earnest Kelly. + +[MOB_63B] +Wie geht's? + +[MOB_63C] +Ganz gut. Werd zum Laufen 'nen Stock brauchen, müsste aber bald wieder arbeiten können. + +[MOB_63D] +Gut. + +[MOB_63E] +Ich hab das mit Lance gehört. Was für ein Schwein, hä? + +[MOB_63F] +Ja. + +[MOB_63G] +Trau nie einem Mann, der im Pyjama auf der Straße herumläuft. Gut, dass du ihn erledigt hast. Ich hoffe, es war nicht kurz und schmerzlos. + +[MOB_63H] +Eher nicht. Ich hätte nur nicht gedacht, dass er so einer ist... + +[MOB_63I] +Tommy, für einen wildgewordenen Irren bist du ziemlich naiv. Ich bin bald wieder an der Arbeit, dann bring ich dir mal ein paar Sachen übers Leben bei, ok? + +[MOB_63J] +Lass dir Zeit, Earnest. Pass auf dich auf. + +[MOB_16A] +Tommy, hier Paul. Wie geht's, mein Freund? + +[MOB_16B] +Was willst du, Paul? Ich brauch keine getürkten Designer-Klamotten. + +[MOB_16C] +Sehr witzig. Du weißt, dass ich mit getürkter Ware nichts am Hut habe. Wollte nur hören, ob ich nicht 'ne Rolle in einem von deinen Filmen kriegen könnte. + +[MOB_16D] +In England habe ich damals viel einschlägiges Zeug gedreht. Ich hab mehr zu bieten als du, mein Alter. + +[MOB_16E] +Paul, danke für das Angebot. Ich komm auf dich zurück. + +[MOB_16F] +Lass mich nicht hängen. Denk dran, was ich alles für dich getan habe. + +[MOB_16G] +Das versuch ich ja grade zu vergessen. + +[MOB_17A] +Tommy Vercetti. Wie geht's, großer Meister? Man hört so einiges über dich. Bist jetzt 'ne große Nummer in der Stadt, hä? + +[MOB_17B] +Paul, du bist betrunken. + +[MOB_17C] +Nein, du Trottel, ich bin nicht betrunken. Hab mir nur ein paar Ladungen Stoff gegeben, war seit ein paar Tagen nicht im Bett. + +[MOB_17D] +Und du brauchst mich nicht dumm anzureden. Ich bin nicht irgendwer. Wer hat dir denn in dieser Stadt den Weg geebnet? Ich! + +[MOB_17F] +Tatsächlich? + +[MOB_17G] +Komm mir nicht so! Ich hab dich mit den ganzen Leuten bekanntgemacht. Hab dir gezeigt, wie der Hase läuft, hab alles mögliche für dich getan, und so dankst du es mir?! + +[MOB_17H] +Du ignorierst mich. Du gibst mir keine Chance, mitzumischen, nach allem, was ich für dich getan habe! Hältst du mich für einen Schwachkopf? + +[MOB_17I] +Paul, reg dich ab. Ich hatte viel zu tun. Sei kein Idiot. + +[MOB_17J] +Ich bin kein Idiot. Das haben sie schon im Jugendknast gesagt. Wenn du Ärger haben willst, Freundchen, den kannst du haben! + +[MOB_17K] +Tommy, bitte! Du warst meine große Hoffnung. Bitte, mach dich nicht lustig über mich! + +[MOB_17L] +Paul, schlaf mal 'ne Runde. Im Ernst. + +[MOB_73A] +Tommy, hier Steve. + +[MOB_73B] +Hey, Steve. + +[MOB_73C] +Hey, aber wie! Du bist ein Genie! Ich bin ein Genie! Sie lieben uns alle. Wir brechen alle Rekorde, mein Alter. + +[MOB_73D] +Uns winken ganz große Filmpreise. Jetzt kann ich endlich meinen alten Herrn ins Heim stecken und ihm sagen, er soll die Klappe halten. + +[MOB_73E] +Äh, das ist cool, Steve. + +[MOB_73F] +Cool? Mann, das ist heiß! Heiß! H.E.I.ß! Er hat nie an mich geglaubt. Hat immer gedacht, ich wäre kein Künstler, und jetzt hab ich's geschafft! + +[MOB_73G] +Ich bin der größte Porno-Regisseur aller Zeiten, mein Freund. Wollte dir nur sagen, es ist mir eine Freude, dich kennengelernt zu haben. + +[MOB_73H] +Danke, Steve. + +[MOB_73I] +Ich liebe dich, Baby. Bleib bloß genau so wie du bist, ok? + +[MOB_73J] +Werd's mir merken. Ciao, Steve. + +[BOLLOX] +Drücke die ~o~R1~w~-Taste, um eine Bombe abzuwerfen. Drücke die ~t~"~w~-Taste zum Abbrechen. + +[BRID_OP] +Sturmwarnung vorüber. Alle Brücken zum Festland sind wieder geöffnet. + +[BRID_CL] +Sturmwarnung: Alle Brücken zum Festland sind gesperrt. + +[LG_38] +Ziel + +[ASSET_C] +POLE POSITION ERWORBEN! + +[ASSET_D] +~g~Der Pole Position Club sorgt nun für ein Einkommen von bis zu $~1~ pro Tag. Hol dir dein Geld regelmässig! + +[ST_WHEE] +Längste 'Wheelie' Zeit (sekunden) + +[ST_STOP] +Längste 'Stoppie' Zeit (sekunden) + +[ST_2WHE] +Längste 2 Rad Zeit (sekunden) + +[ST_WHED] +Längste 'Wheelie' Distanz (m) + +[ST_STOD] +Längste 'Stoppie' Distanz (m) + +[ST_2WHD] +Längste 2 Rad Distanz (m) + +[OUTFT11] +Trainer + +[OUTFT12] +Frankie + +[RELOAD] +~g~Du hast die schnell Nachladefähigkeit gewonnen! + +[APACHE] +Hunter zur Heli Landeplattform am Ocean Beach geliefert. + +[CRED369] +JOHN MCCARDLE + +[CRED370] +DAVID MURDOCH + +[CRED371] +CHRIS BROWN + +[CRED372] +PAUL GREEN + +[CRED373] +KYLE MILNE + +[CUNTY] +Neue Kleider wurden zum Vercetti Estate geliefert! + +[GOODBOY] +$50 'Guter Bürger' Bonus! + +[NEWCONT] +Neuer Kontaktpunkt am Jachthafen am Ocean Beach!! + +[FIRELVL] +Feuerwehr-Mission Level ~1~ + +[HELP56] +Drücke die ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~-Taste, um den Blickwinkel zu ändern. + +[HELP57] +Drücke die ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~-Taste, um den Blickwinkel zu ändern. + +[HELP58] +Beim Zielen kann durch Drücken der ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~-Taste zwischen Zielen hin- und her gewechselt werden. + +[HELP59] +Beim Zielen kann durch Drücken der ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~-Taste zwischen Zielen hin- und her gewechselt werden. + +[HELP60] +Wenn du während eines Autodiebstahls die ~h~~k~~PED_SPRINT~ ~w~-Taste drückst, steigst du nicht in das Fahrzeug ein. + +[HELP61] +Du hast jetzt unbegrenzt Munition und doppelte Health für alle Fahrzeuge. + +[CRED374] +KEVIN YUN + +[CRED375] +ERICK COBBS + +[CRED376] +RANDY BLAKE + +[CRED377] +BRANDON LIM + +[CRED378] +BRANDON FENOL + +[CRED379] +MICHAEL MANOLE + +[CRED380] +ALETHEIA SIMONSON + +[CRED381] +JOHN JANSEN + +[FEC_LB1] +Schau + +[FEC_LB2] +nach hinten + +[FEC_LB3] +Nach hinten schauen + +[FEC_R3] +(R3-Taste) + +[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 + +[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~ + +[FEC_WAR] +Achtung! + +[FEC_OKK] +OK + +[FEC_DLF] +Löschen fehlgeschlagen. + +[FEC_SVU] +Speichern fehlgeschlagen. + +[FEC_LUN] +Laden fehlgeschlagen. Datei beschädigt. Bitte löschen. + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FES_CSA] +Wählen Sie eine Skin aus der Liste aus: + +[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_DSN] +Standard-Player Skin.bmp + +[FET_RSO] +ORIGINAL-EINSTELLG. WIEDERHERGESTELLT + +[FET_RSC] +HARDWARE NICHT VERFÜGBAR - ORIGINAL-EINSTELLG. WIEDERHERGESTELLT + +[FEA_3DH] +AUDIO HARDWARE + +[FEA_SPK] +BOXEN KONFIGURATION + +[FEM_LOD] +DISTANZ-DARSTELLG. + +[FEM_VSC] +FRAME SYNC + +[FEM_FRM] +FRAME LIMITER + +[FEM_MM] +HAUPTMENÜ + +[FED_RES] +BILDSCHIRMAUFLSG. + +[FET_CTL] +CONTROLLER-SETUP + +[FET_OPT] +OPTIONEN + +[FEC_MSH] +MAUSEMPFINDLICHKEIT + +[FEC_IVV] +MAUS VERTIKAL INVERTIEREN + +[FET_MTI] +MAUS STEURUNGSKONFIG. + +[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 + +[FEC_SPC] +LEERT. + +[WIN_TTL] +GTA VC + +[WIN_95] +GTA VC läuft nicht unter Windows 95 + +[WIN_DX] +GTA VC benötigt mind. DirectX Version 8.1 + +[FET_EIG] +KANN DIESER AKTION KEINE STEUERUNG ZUWEISEN + +[FET_DAM] +DYNAMISCHE AKUSTIK + +[FEQ_SRE] +Wirklich beenden? Alle Daten seit dem letzten Speichern werden verlorengehen. Weiter? + +[FEQ_SRW] +Spiel wirklich beenden? + +[FET_QG] +SPIEL BEENDEN + +[FEN_STA] +SPIEL STARTEN + +[REPLAY] +WIEDERHOLUNG + +[FET_PAU] +PAUSENMENÜ + +[FEC_ANS] +Aktion + +[CVT_MSG] +Texturen werden in optimales Format für Ihre Grafikkarte konvertiert + +[FEC_SFT] +UMSCHALT + +[CVT_ERR] +Kein Platz mehr auf der Festplatte. Bitte schaffen Sie Speicherplatz, bevor Sie fortfahren. ESC zum Abbrechen. + +[FEH_VMP] +KARTE ANSEHEN + +[FES_DEE] +Löschen fehlgeschlagen! Bitte noch einmal versuchen. + +[FES_CMP] +Speichern fehlgeschlagen! Bitte noch einmal versuchen. + +[FESZ_WR] +Spiel wird gespeichert. Bitte warten... + +[FELD_WR] +Spiel wird geladen. Bitte warten... + +[FEDL_WR] +Gespeichertes Spiel wird gelöscht. Bitte warten... + +[PCRESRT] +Neues Spiel wird gestartet. Bitte warten... + +[FET_STI] +Standard-Steuerung + +[FET_CTI] +Classic-Steuerung + +[FET_PS] +SPIELER-SKIN SETUP + +[FEH_NA] +OPTION NICHT VERFÜGBAR + +[FEH_MPH] +MAUS, CURSOR ZUM BEWEGEN - BILD AUFW., BILD ABW., MAUSRAD ZUM ZOOMEN, L - LEGENDE + +[FEA_MP3] +MP3 PLAYER + +[NO_PCCD] +Bitte legen Sie die GTA Vice City Disk ein, oder drücken Sie ESC zum Abbrechen + +[FEH_SSA] +CURSOR ZUM BEWEGEN - S UM ZU SPEICHERN + +[FES_CMI] +LETZTE ERFÜLLTE MISSION + +[FET_STS] +STATISTIKEN GESPEICHERT IN 'STATS.HTML' + 'STATS.TXT' + +[WIN_VDM] +Nicht genug verfügbarer Grafikspeicher für GTA Vice City vorhanden + +[FEC_ERI] +Fehler! Eine oder mehrere Aktionen haben keine Tastenbelegungen. Bitte alle Aktionen belegen. + +[FEC_TFU] +Geschütz + nach hinten neigen + +[FEC_TFD] +Geschütz + nach vorne neigen + +[FET_RIG] +WÄHLEN SIE EINE NEUE TASTENBELEGUNG FÜR DIESE AKTION + +[FEA_NM3] +KEINE MP3-DATEIEN GEFUNDEN + +[FEA_MPB] +MP3 LAUTSTÄRKE-BOOST + +[FEA_MUS] +LAUTSTÄRKE MUSIK + +[FEA_SFX] +LAUTSTÄRKE SFX + +[FEA_ADP] +AUTOMATISCHE HARDWARE ERKENNUNG + +{=================================== MISSION TABLE AMBULAE ===================================} + +[A_COMP1:AMBULAE] +Krankenwagen-Missionen abgeschlossen: $ ~1~ + +[ATUTOR2:AMBULAE] +~g~Fahre die Patienten VORSICHTIG in die Klinik. Jede Erschütterung verringert ihre Überlebenschancen. + +[A_FULL:AMBULAE] +~r~Krankenwagen voll!! + +[A_FAIL2:AMBULAE] +~r~Deine Bummelei war tödlich für den Patienten! + +[A_FAIL3:AMBULAE] +~r~Der Patient ist tot!! + +[A_PASS:AMBULAE] +Gerettet! + +[A_COMP2:AMBULAE] +Du ermüdest nie! + +[A_CANC:AMBULAE] +~r~Krankenwagen-Mission abgebrochen! + +[A_COMP3:AMBULAE] +Krankenwagen-Missionen abgeschlossen! Du wirst beim Rennen nie ermüden! + +[ALEVEL:AMBULAE] +Krankenwagen-Mission Level ~1~ + +[A_FAIL1:AMBULAE] +Krankenwagen-Mission beendet. + +[A_SAVES:AMBULAE] +GERETTETE MENSCHEN: ~1~ + +{=================================== MISSION TABLE ASSIN1 ===================================} + +[ASM1_5:ASSIN1] +~r~Er hat seine Lieferungen abgeschlossen! + +[ASM1_6:ASSIN1] +Weitere Lieferungen: + +[ASM1_7:ASSIN1] +~g~Carl Pearson, Pizza-Lieferant. Schalte ihn aus, bevor er seine Lieferungen abschließt. + +[ASM1_A:ASSIN1] +Mr. Teal, Ihre Hilfe bei der Beseitigung der Landeier war äußerst wertvoll. Ich habe noch mehr Arbeit, die eine eher 'zupackende' Art verlangt. + +[ASM1_D:ASSIN1] +Mr. Teal, Ihre Hilfe bei der Beseitigung der Landeier war äußerst wertvoll. + +{=================================== MISSION TABLE ASSIN2 ===================================} + +[ASM2_1:ASSIN2] +~g~Mrs. Dawson verlässt bald den Juwelier in Vice Point. Schalte sie aus. Es muss wie ein Autounfall aussehen. + +[ASM2_3:ASSIN2] +~g~Das Fahrzeug wird explodieren! Hau ab! + +[ASM2_4:ASSIN2] +~r~Du hast ihr Auto beschädigt, obwohl sie nicht drin saß! Jetzt wird sie nicht einsteigen! + +[ASM2_5:ASSIN2] +~r~Sie ist entwischt! + +[ASM2_6:ASSIN2] +~r~Du warst zu nah am Unfallort! + +[ASM2_7:ASSIN2] +~g~Keine Waffen! Es soll wie ein Unfall aussehen! Dränge sie stattdessen von der Fahrbahn! + +[ASM2_8:ASSIN2] +~g~Das ganze muss wie ein Unfall aussehen. Benutze keine Waffen. + +[ASM2_9:ASSIN2] +Du brauchst einen fahrbaren Untersatz für diesen Job. + +[ASM2_10:ASSIN2] +~g~Wenn ihr Auto in Flammen aufgeht, entferne dich so weit wie möglich von der Unfallstelle. + +[ASM2_11:ASSIN2] +Hilfe! + +[ASM2_12:ASSIN2] +Hilf mir doch jemand! + +[ASM2_13:ASSIN2] +Oh Gott! + +[ASM2_A:ASSIN2] +Mein Kompliment für die gute Arbeit, Mr. Teal. Mein Kunde war sehr zufrieden. + +[ASM2_2:ASSIN2] +Health: + +{=================================== MISSION TABLE ASSIN3 ===================================} + +[ASM3_11:ASSIN3] +ZEIT: + +[ASM3_C:ASSIN3] +Eine europäische Gang plant einen Überfall auf eine Bank in Vice City. Meinen Arbeitgebern wäre sehr daran gelegen, dass das nicht passiert. + +[ASM3_D:ASSIN3] +Alle Mitglieder der Gang haben eine Tarnung, solange sie sich hier in Vice City aufhalten. Manche haben Jobs, andere geben sich als Touristen aus. + +[ASM3_E:ASSIN3] +Infos über alle Zielpersonen und ihre wahrscheinlichen Aufenthaltsorte kleben unter dem Telefon. + +[ASM3_14:ASSIN3] +~g~Dick Tanner hält sich bei DBP Security am Ocean Drive auf. + +[ASM3_15:ASSIN3] +~g~Marc Hammond und Franco Carter halten sich in der Nähe des Juwelierladens in Vice Point auf. + +[ASM3_16:ASSIN3] +~g~Nick Kong hält sich in der Nähe von Washington Beach auf. + +[ASM3_18:ASSIN3] +~g~Geh nicht zu nahe an deine Zielperson heran, sonst entdeckt sie dich und du musst hinter ihr herjagen. + +[ASM3_19:ASSIN3] +~g~Er hat dich gesehen! Schalte ihn aus! + +[ASM3_20:ASSIN3] +~g~Sie haben dich gesehen! Schalte alle beide aus! + +[ASM3_21:ASSIN3] +~r~Du hast nicht alle Mitglieder der Gang rechtzeitig erledigt! + +[ASM3_22:ASSIN3] +~g~Geh nicht zu nahe an deine Zielpersonen heran, sonst entdecken sie dich und versuchen zu fliehen. + +[ASM3_12:ASSIN3] +~g~In der Nähe sind einige Waffen für dich deponiert worden, falls du sie brauchen solltest. Du hast ~h~9 MINUTEN~g~, um alle Gang-Mitglieder auszuschalten. + +[ASM3_13:ASSIN3] +~g~Mike Griffin arbeitet an einer Plakatwand in Washington. + +[ASM3_17:ASSIN3] +~g~Charlie Dilson fährt mit dem Motorrad in Washington herum. + +{=================================== MISSION TABLE ASSIN4 ===================================} + +[ASM4_10:ASSIN4] +~g~Du warst anscheinend nicht der einzige, der hinter dem Aktenkoffer her war. Bring ihn schnell ins Ammu-Nation! + +[ASM4_12:ASSIN4] +Distanz: + +[ASM4_15:ASSIN4] +~g~Nimm das Präzisionsgewehr zu deiner Rechten. + +[ASM4_16:ASSIN4] +~g~Behalte die Frau auf der Empore im Auge. Sie wird die Rolltreppe hinuntergehen und die Zielperson nach der Uhrzeit fragen. + +[ASM4_17:ASSIN4] +~g~Schalte die Zielperson aus, NACHDEM die Frau mit ihr gesprochen hat. Aber erledige nicht die Frau. + +[ASM4_18:ASSIN4] +~g~Wenn die Zielperson ausgeschaltet ist, nimm ihren Aktenkoffer und bringe ihn zum Ammu-Nation in Downtown. + +[ASM4_19:ASSIN4] +~g~Halte Abstand von der Zielperson. Die Entfernungs-Anzeige rechts oben am Bildschirm zeigt an, wie nahe du an der Zielperson bist. + +[ASM4_20:ASSIN4] +~g~Lass den Anzeigebalken nicht an den Anschlag geraten, sonst sieht dich die Zielperson. + +[ASM4_21:ASSIN4] +~g~Schnapp dir den Aktenkoffer! + +[ASM4_22:ASSIN4] +~g~Bring den Aktenkoffer zum Ammu-Nation in Downtown. + +[ASM4_23:ASSIN4] +~g~Er hat dich entdeckt und versucht zu fliehen. Erledige ihn und schnapp dir den Aktenkoffer! + +[ASM4_25:ASSIN4] +~r~Du hast die Frau ausgeschaltet, du Idiot! + +[ASM4_26:ASSIN4] +~r~Die Zielperson hat das Flugzeug bestiegen! + +[ASM4_27:ASSIN4] +~r~Die Zielperson hat dich gesehen! Du hättest Abstand halten sollen! + +[ASM4_28:ASSIN4] +~r~Die Zielperson hat dich gesehen! Er hat gehört, wie du geschossen hast! + +[ASM4_29:ASSIN4] +~r~Schalte ihn erst aus, wenn er mit der Frau gesprochen hat! + +[ASM4_A:ASSIN4] +Es wird Zeit für dickere Brocken, Mr. Teal. Unter dem Busch zu Ihrer Rechten ist ein Gewehr. + +[ASM4_B:ASSIN4] +Behalten Sie die Frau auf der Empore über dem Check-In im Auge. Sie wird durch die Menge gehen und jemanden nach der Uhrzeit fragen. + +[ASM4_C:ASSIN4] +Sie müssen die betreffende Person ausschalten, ihren Aktenkoffer nehmen und ihn zu der Adresse, die unter dem Telefon klebt, bringen. + +{=================================== MISSION TABLE ASSIN5 ===================================} + +[ASM5_A:ASSIN5] +Auf dem Dach der Cherry Popper-Eiscremefabrik findet eine Übergabe von wertvoller Ware statt. + +[ASM5_B:ASSIN5] +Erledigen Sie alle Beteiligten, klauen Sie die Ware und bringen Sie sie zum Heliport am Flughafen. + +[ASM5_C:ASSIN5] +Links von Ihnen ist ein Tor, das zur Rückseite der Fabrik führt. + +[ASM5_1:ASSIN5] +~g~Geh auf das Gelände hinter der Cherry Popper-Eiscremefabrik und dann auf das Dach, wo der Deal abgewickelt wird. + +[ASM5_2:ASSIN5] +~g~Schnapp dir die Ware und bring sie zum Heliport am Flughafen. + +[ASM5_3:ASSIN5] +~g~Bring die Ware zum Heliport am Flughafen! + +{=================================== MISSION TABLE BANKJ1 ===================================} + +[WANTED1:BANKJ1] +~g~Schüttle die Cops ab. Verringere deinen Fahndungslevel. + +[BJM1_A:BANKJ1] +Tommy! Hey, Tommy, sieh mal, das ist super! Ich hab eine Minibar einbauen lassen! + +[BJM1_B:BANKJ1] +Wir haben unten eine ausgewachsene Bar, Ken. + +[BJM1_C:BANKJ1] +Ja, ja, wie auch immer. Tja, ich hab die Tafel besorgt, die du haben wolltest. + +[BJM1_D:BANKJ1] +Ah, das ist der Lohn des Jurastudiums: Die Fähigkeit, Anweisungen auszuführen. + +[BJM1_E:BANKJ1] +Also, ich brauche einen Safeknacker. + +[BJM1_F:BANKJ1] +Oh, ok, mal nachdenken...Safeknacker, Safeknacker...Ich hab's! Du wirst begeistert sein! + +[BJM1_G:BANKJ1] +Aah, nicht dieser Schwachkopf. Der sitzt doch. + +[BJM1_H:BANKJ1] +Wo sitzt er denn? + +[BJM1_I:BANKJ1] +In einer Zelle in einem Polizeirevier. Er wartet auf seine Verlegung. + +[BJM1_J:BANKJ1] +Ich glaube fast, er kommt auf Bewährung raus... + +[BJM1_1:BANKJ1] +~g~Befreie Cam Jones aus der Haft! + +[BJM1_3:BANKJ1] +~g~In der Umkleide des Reviers findest du etwas Nützliches. + +[BJM1_21:BANKJ1] +~g~Die Key Card zu den Zellen findet sich im Obergeschoss des Reviers. + +[BNK1_7:BANKJ1] +Cam Jones? + +[BNK1_8:BANKJ1] +Ich hol dich hier raus. + +[BNK1_10:BANKJ1] +Ja, der bin ich... + +[BNK1_11:BANKJ1] +Ganz wie du meinst! + +[BNK1_13:BANKJ1] +Ich ziehe einen Job durch, und du bist mein Safeknacker. + +[BNK1_14:BANKJ1] +Besser als in 'ner Zelle zu verrotten! + +[BJM1_22:BANKJ1] +~g~Bring Cam nach Hause! + +[BJM1_23:BANKJ1] +~g~Du brauchst zunächst die Magnetkarte für die Tür! + +[BNK1_12:BANKJ1] +Häng die Bullen ab und bring mich nach Hause! + +[BJM1_20:BANKJ1] +Die Waffe weg oder es passiert was! + +[BJM1_5:BANKJ1] +Zutritt ab hier nur für befugtes Personal. + +[BJM1_2:BANKJ1] +~r~Du solltest Cam die Freiheit bringen, nicht den Tod! + +[BJM1_4:BANKJ1] +Er ist bewaffnet! Erledigt ihn! + +{=================================== MISSION TABLE BANKJ2 ===================================} + +[BJM2_A:BANKJ2] +Wir brauchen einen Überfall-Experten. Kennt ihr einen? + +[BJM2_B:BANKJ2] +Hey, Tommy, Tommy, Tommy, das Zeug bringt dich nach vorn, Mann. + +[BJM2_C:BANKJ2] +WoooOOOooo! + +[BJM2_D:BANKJ2] +Ich könnte dein Experte sein! Überfall! Überfall! + +[BJM2_E:BANKJ2] +Du bist kein Experte, du bist ein Idiot. + +[BJM2_F:BANKJ2] +Mach dir 'nen Drink und halt die Klappe. + +[BJM2_G:BANKJ2] +Hey, aus dem Weg! + +[BJM2_H:BANKJ2] +Cam, was meinst du? + +[BJM2_I:BANKJ2] +Tja, der beste Schütze in der Stadt ist ein Kerl namens Cassidy. + +[BJM2_J:BANKJ2] +Ach ja? + +[BJM2_K:BANKJ2] +Ja. Soldat, oder wenigstens hält er sich dafür. + +[BJM2_L:BANKJ2] +Ich bezweifle, dass er je bei der Army war, aber er kann mit der Knarre umgehen. + +[BJM2_M:BANKJ2] +Wahrscheinlich ist er in der Schießanlage. + +[BJM2_2A:BANKJ2] +Bist du Phil Cassidy? + +[BJM2_2B:BANKJ2] +Wieso? + +[BJM2_2C:BANKJ2] +Ich suche einen, der mit der Kanone umgehen kann. Was ich hier so sehe, überzeugt mich nicht. + +[BJM2_2D:BANKJ2] +Jungchen, ich schieße dir auf 25 Meter eine Fliege vom Kopf. + +[BJM2_2E:BANKJ2] +Ach wirklich? + +[BJM2_2F:BANKJ2] +Ja. Hab ich bei der Army gelernt. + +[BJM2_2G:BANKJ2] +Ist Fliegen-Schießen so beliebt in der Army? Gut, dass ich keine Steuern zahle. + +[BJM2_2H:BANKJ2] +Sollte das witzig sein, Jungchen? + +[BJM2_2I:BANKJ2] +Ha ha ha ha ha! + +[BJM2_2J:BANKJ2] +Lass uns schießen. + +[BJM2_1:BANKJ2] +~g~Begib dich nach Downtown ins Ammu-Nation und sprich mit Phil Cassidy. + +[BJM2_3:BANKJ2] +TREFFERQUOTE: ~1~% + +[BJM2_4:BANKJ2] +PUNKTE RUNDE EINS: ~1~ + +[BJM2_6:BANKJ2] +PUNKTE RUNDE ZWEI: ~1~ + +[BJM2_7:BANKJ2] +GESAMTPUNKTZAHL FÜR DAS SCHIESSEN: ~1~ + +[BJM2_9:BANKJ2] +~g~Begib dich zum Startpunkt für Runde Zwei. + +[BJM2_11:BANKJ2] +~r~Phil ist erledigt! + +[BJM2_12:BANKJ2] +~r~Einer der Schützen ist erledigt! + +[BJM2_14:BANKJ2] +~g~Begib dich zum nächsten Areal! + +[BJM2_15:BANKJ2] +PUNKTE: + +[BJM2_17:BANKJ2] +~g~Sprich mit Phil. + +[BJM2_18:BANKJ2] +ZU SCHLAGEN: + +[BJM2_19:BANKJ2] +~g~Triff innerhalb des Zeitlimits so viele Ziele wie du kannst! + +[BJM2_22:BANKJ2] +~r~Du hast den Schießstand verlassen! + +[BJM2_23:BANKJ2] +~g~Wenn du den Schießstand während des Wettbewerbs verlässt, ist die Mission gescheitert. + +[BJM2_24:BANKJ2] +~g~Das nahegelegenste Ziel bringt 1 Punkt. + +[BJM2_25:BANKJ2] +~g~Das mittlere Ziel bringt 2 Punkte. + +[BJM2_27:BANKJ2] +~g~Alle Ziele dieser Runde bringen 1 Punkt. + +[BNK2_2:BANKJ2] +ZIELEN 3-2-1 FEUER! + +[BNK2_3:BANKJ2] +AREAL KLAR! + +[BNK2_4:BANKJ2] +Huuuiii! + +[BNK2_5:BANKJ2] +Der träfe nicht mal ein Scheunentor. + +[BNK2_7:BANKJ2] +Also, was ist jetzt, hilfst du mir bei dem Job? + +[BNK2_8:BANKJ2] +Jungchen, so wie du schießt, würde ich dich sogar heiraten. + +[BNK2_9A:BANKJ2] +Junge, deine Sprüche und deine Hirngespinste kannst du dir sonst wohin stecken. Du bist ein lausiger Schütze. + +[BNK2_9B:BANKJ2] +Du bist ein lausiger Schütze. + +[BJM2_28:BANKJ2] +PUNKTE RUNDE DREI: ~1~ + +[BJM2_20:BANKJ2] +~g~Geht dir die ~w~Zeit ~g~oder die ~w~Munition ~g~aus, ist die Runde beendet! + +[BJM2_26:BANKJ2] +~g~Das am weitesten entfernte Ziel bringt 3 Punkte. + +[BNK2_1:BANKJ2] +SCHARFE MUNITION + +[RANGE_1:BANKJ2] +PUNKTZAHL SCHIESS-RUNDE:~1~ + +[BJM2_2:BANKJ2] +~g~Um die Runde zu beenden, drücke die ~h~~k~~PED_JUMPING~. + +[BJM2_N:BANKJ2] +Nur die Ruhe. + +{=================================== MISSION TABLE BANKJ3 ===================================} + +[BJM3_A:BANKJ3] +Langsam fügt sich alles sehr schön zusammen hier. + +[BJM3_B:BANKJ3] +Was ist der Plan, Tommy? Was geht ab, Amigo? + +[BJM3_C:BANKJ3] +Der Plan ist, dass du dich wie ein Vollidiot benimmst. Wir brauchen einen Fahrer. + +[BJM3_D:BANKJ3] +Tommy, ich mach's. Ich kann fahren. + +[BJM3_E:BANKJ3] +Nimm Hilary, Boss, nicht diesen Labersack von einem Rechtverdreher. + +[BJM3_F:BANKJ3] +Hilary ist der beste. So schnell wie den hast noch keinen fahren sehen. Ich rufe ihn mal an. + +[BJM3_G:BANKJ3] +Hey, Hil, hier Phil. Wie läuft's? Nein, sag nichts. Dazu haben wir später Zeit. Tust du mir einen Gefallen? + +[BJM3_H:BANKJ3] +Ich hab hier einen Typ aus dem Norden. Nein, ich glaub, er war nicht beim Militär. Aber er braucht einen Fahrer. + +[BJM3_I:BANKJ3] +Für einen Job. Ok, verstehe. + +[BJM3_J:BANKJ3] +Was hat er gesagt? + +[BJM3_K:BANKJ3] +Er macht's. Kein Problem. Na ja, ein kleines vielleicht: Er leidet unter Verlustängsten. + +[BJM3_L:BANKJ3] +Er arbeitet anscheinend nicht für Leute, die ihn nicht schlagen können. Hat was mit seiner Mutter zu tun. + +[BJM3_M:BANKJ3] +Jedenfalls will er erst ein Rennen gegen dich fahren. Er wartet draußen auf dich. + +[BJM3_2A:BANKJ3] +Bist du Tommy? Klar bist du Tommy, ich meine, + +[BJM3_2B:BANKJ3] +wieso sollte sonst einer mit mir reden wollen? + +[BJM3_2C:BANKJ3] +Ok. Das ganze läuft so ab: + +[BJM3_2D:BANKJ3] +Ich fahre für dich, WENN und NUR WENN du selbst anständig fährst. + +[BJM3_2E:BANKJ3] +Verlierst du mich, verzeihe ich dir das nie. + +[BJM3_2:BANKJ3] +Hilary ist erledigt! + +[BJM3_4:BANKJ3] +~g~Du brauchst ein Auto, um mitzumachen. + +[BNK3_1:BANKJ3] +Ok, ich fahre für dich. Aber bitte, behandle mich schlecht. + +[BNK3_3A:BANKJ3] +Illegales Straßenrennen bei Vice Point. + +[BNK3_3B:BANKJ3] +An alle Einsatzkräfte. + +[BNK3_3C:BANKJ3] +Straßenrennen sind verboten und illegal! + +{=================================== MISSION TABLE BANKJ4 ===================================} + +[BNK4_A:BANKJ4] +~w~Ihr seht Gentlemen, das wird leicht verdientes Geld für uns. + +[BNK4_B:BANKJ4] +~w~Ernsthaft, Tommy, du solltest dir überlegen, Anwalt zu werden. + +[BNK4_C:BANKJ4] +~w~Was zum Geier rauchst du eigentlich, Mann? Das ist kein simpler Plan. + +[BNK4_D:BANKJ4] +~w~Ach, wer braucht schon simple Pläne? + +[BNK4_E:BANKJ4] +~w~Der Kommunismus, das war ein simpler Plan. Hat Russland aber nicht viel genützt, hah? + +[BNK4_F:BANKJ4] +~w~Ganz ruhig. Mit einem Team wie diesem ist das alles kein Problem. + +[BNK4_G:BANKJ4] +~w~Cam übernimmt den Safe. Phil? Wir beide kümmern uns um die Sicherheit, und Hilary fährt den Fluchtwagen. + +[BNK4_H:BANKJ4] +~w~Äh, hast du nicht jemanden vergessen? Jemanden, der dir unzählige Male geholfen hat in dieser Stadt? Jemanden...? + +[BNK4_I:BANKJ4] +~w~Ken...Ken, richtig. Ken wäscht das Geld für uns und stellt schon mal die Drinks kalt. + +[BNK4_J:BANKJ4] +~w~Ich verstehe nicht, was ich hier soll. + +[BNK4_K:BANKJ4] +~w~Ist doch ganz einfach. Warst du noch nie im Kino? + +[BNK4_L:BANKJ4] +~w~Wir latschen in die Bank rein, fuchteln mit den Knarren rum und gehen stinkreich wieder raus. + +[P_DEAD:BANKJ4] +~r~Phil ist erledigt!! + +[C_DEAD:BANKJ4] +~r~Cam ist erledigt!! + +[H_DEAD:BANKJ4] +~r~Hilary ist erledigt!! + +[P_HIND:BANKJ4] +~r~Du hast Phil verloren! + +[C_HIND:BANKJ4] +~r~Cam wurde abgehängt! + +[H_HIND:BANKJ4] +~r~Hilary wurde im Stich gelassen! + +[GETCAR:BANKJ4] +Steig in den Fluchtwagen und führe den Plan aus! + +[TRASHED:BANKJ4] +~r~DU HAST DEN FLUCHTWAGEN GESCHROTTET!! + +[BNK4_1:BANKJ4] +Ich fahre. + +[BNK4_2:BANKJ4] +Na prima. Beifahrer. Wenn ich das in der Therapie erzähle. + +[BNK4_3A:BANKJ4] +Hey, pass auf, wo du hinfährst, Tommy! + +[BNK4_3B:BANKJ4] +Tommy, Hilary macht sich so breit! + +[BNK4_3C:BANKJ4] +Stimmt gar nicht! + +[BNK4_3D:BANKJ4] +Doch! + +[BNK4_3E:BANKJ4] +Hey, Klappe, ihr zwei, oder ihr könnt zu Fuß gehen. + +[BNK4_3F:BANKJ4] +Ja, Hilary. + +[BNK4_3I:BANKJ4] +Herrgott, Phil, hör auf, mit diesem Ding rumzufuchteln! + +[BNK4_3J:BANKJ4] +Ja, am Ende geht das noch ins Auge! + +[BNK4_3M:BANKJ4] +Mein Baby! Alles Schrott! + +[BNK4_3O:BANKJ4] +Du hängst zu sehr an der Illusion der Ewigkeit. + +[BNK4_3P:BANKJ4] +Was? + +[BNK4_3Q:BANKJ4] +Du denkst, alles bleibt ewig. + +[BNK4_3R:BANKJ4] +Jugend, geliebte Menschen, Pizza, + +[BNK4_3S:BANKJ4] +Alles geht mal vorbei, und das musst du akzeptieren. + +[BNK4_3T:BANKJ4] +Hey, du hast recht. Danke, Cam. + +[BNK4_3U:BANKJ4] +Nichts zu danken. + +[BNK4_3V:BANKJ4] +Hey, Tommy, wieso halten wir? + +[BNK4_4A:BANKJ4] +~w~Hilary, fahr ein bisschen um den Block. + +[BNK4_5:BANKJ4] +~w~Ok, Tommy, ok. + +[BNK4_6:BANKJ4] +~w~DIES IST EIN ÜBERFALL! + +[BNK4_7:BANKJ4] +~w~KEINER BEWEGT SICH! + +[BNK4_8:BANKJ4] +~w~ALLE AN DIE WAND! + +[BNK4_9:BANKJ4] +Phil, halt die Stellung! + +[BNK4_10:BANKJ4] +Verstanden! + +[BNK4_11:BANKJ4] +Los Cam, der Tresor ist oben... + +[BK4_12A:BANKJ4] +Verdammt, das ist ein Flange 9000! + +[BK4_12B:BANKJ4] +Könnte Stunden dauern, den zu knacken. + +[BK4_12C:BANKJ4] +Oder 5 Minuten, wenn du den Manager findest. + +[BNK4_13:BANKJ4] +Ich seh nach, wo er sich verkrochen hat. + +[BK4_14A:BANKJ4] +Phil, alles im grünen Bereich? + +[BNK4_15:BANKJ4] +Klar. Im dunkelgrünen Bereich. + +[BNK4_16:BANKJ4] +Du da! Mitkommen! + +[BNK4_17:BANKJ4] +Ok! Ok! Nicht schießen! + +[BNK4_18:BANKJ4] +ICH SAGTE, KEINER RÜHRT SICH! + +[BK4_19A:BANKJ4] +Der Safe hat eine Zeitsperre, + +[BK4_19B:BANKJ4] +ihr könnt ebenso gut gleich aufgeben! + +[BK4_20A:BANKJ4] +Ach, die Zeitsperre kann ich umgehen. + +[BK4_20B:BANKJ4] +Dann brauchen wir nur noch deinen Code und alles ist in Butter! + +[BNK4_21:BANKJ4] +Bleib hier. Wenn du Zicken machst, bist du Fischfutter, klar? + +[BNK422A:BANKJ4] +Cam, wie lange noch? + +[BK4_23A:BANKJ4] +Gib mir noch 3 Minuten! + +[BK4_24A:BANKJ4] +Ich seh mal nach Phil. Bin gleich wieder da. + +[BK4_24B:BANKJ4] +Ich hab gesagt, Finger weg vom Alarm! + +[BNK4_25:BANKJ4] +Das Spezialkommando muss gleich hier sein! + +[BNK4_27:BANKJ4] +Ich könnte ein bisschen Hilfe brauchen, Tommy! + +[BNK4_28:BANKJ4] +Vice City Spezialkommando! Sie sind umzingelt! + +[BNK4_29:BANKJ4] +Umzingelt? HA HA HA HAAAAAaaa! + +[BNK4_30:BANKJ4] +Die scheißen sich ein, die korrupten Schweine! + +[BK4_31A:BANKJ4] +Tommy, der Tresor ist offen! + +[BK4_34A:BANKJ4] +Ok, wir haben die Pensionskasse der Bullen. Los, raus hier! + +[BK4_34B:BANKJ4] +Ok, ihr habt es so gewollt. Ihr hattet eure letzte Chance! + +[BK4_35A:BANKJ4] +Die stürmen den Laden! + +[BK4_35B:BANKJ4] +In Deckung! + +[BNK4_94:BANKJ4] +~w~Ok, Jungs. Jeder hält sich an den Plan . + +[BM_DEAD:BANKJ4] +~r~Du brauchst den Bank Manager lebend!! + +[ASSET_A:BANKJ4] +BANK-MISSIONEN ERFÜLLT! + +[ASSET_B:BANKJ4] +~g~Der Malibu Club generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +[IDIOT:BANKJ4] +~r~Na super - angezogen wie ein Irrer durch die Gegend laufen und Aufmerksamkeit erregen, IDIOT! + +{=================================== MISSION TABLE BARON1 ===================================} + +[COK1_A:BARON1] +Komm schon, Brauner, komm! + +[COK1_B:BARON1] +Dämlicher Klepper! Dich mach ich einen Kopf kürzer! + +[COK1_C:BARON1] +Wer ist der Blödmann? + +[COK1_D:BARON1] +Tommy Vercetti, Sie erinnern sich. + +[COK1_E:BARON1] +Tschuldigung, ich bin etwas nervös. Vertrau niemals einem verdammten Pferd! + +[COK1_F:BARON1] +Du machst deine Sache gut. Du arbeitest jetzt für mich. + +[COK1_H:BARON1] +Wie gesagt, Amigo, du arbeitest für mich. Basta. So ein Mistkerl hat mich betrogen. + +[COK1_I:BARON1] +Er denkt, ich weiß nicht, wie viel Geld mir zusteht. Aber 3% zu klauen ist genauso schlimm wie 100% zu klauen. + +[COK1_J:BARON1] +Niemand haut mich übers Ohr. NIEMAND!! + +[COK1_K:BARON1] +Folge ihm von seiner Wohnung aus und sieh nach, wo er hin will. Später erledigen wir ihn. + +[COK1_1:BARON1] +Oh, Shit! + +[COK1_2:BARON1] +Zu langsam, Opa! + +[COK1_4:BARON1] +Versager. + +[COK1_5:BARON1] +Ich würde einen Zahn zulegen, du Mistsack! + +[COK1_8:BARON1] +~g~Schnell! Schnapp dir einen Untersatz und folge ihm! + +[COK1_9:BARON1] +~r~Du sollst ihn verfolgen, nicht umlegen! + +[COK1_10:BARON1] +~r~Begib dich zum Haus des Diebes und such das Geldversteck. + +[COK1_11:BARON1] +~g~Schau durch sein Fenster. + +[COK1_7:BARON1] +~g~Er ist aufs Dach geflohen. Bleib ihm auf den Fersen, aber erledige ihn nicht. + +[COK1_G:BARON1] +Ich arbeite gegen Geld. + +{=================================== MISSION TABLE BARON2 ===================================} + +[COK2_A:BARON2] +Was bist du eigentlich für ein inkompetenter Narr? + +[COK2_B:BARON2] +NARR! NARR! NARR! NARR! + +[COK2_C:BARON2] +Tommy- + +[COK2_D:BARON2] +Was, Ricardo? + +[COK2_E:BARON2] +Diese Idioten - dauernd wollen sie einen reinlegen. + +[COK2_F:BARON2] +Das ist der Fehler an dieser Branche. + +[COK2_G:BARON2] +Was soll denn das? Aaaaah! + +[COK2_H:BARON2] +Diese Mistkerle haben mich bitter enttäuscht. + +[COK2_I:BARON2] +Bald denkt jeder Depp, er kann in Vice City Koks verkaufen. + +[COK2_J:BARON2] +Was kommt als nächstes, hah? Die verstunkene Mafia?! + +[COK2_K:BARON2] +Diese Bandengegend ist eine Festung ohne Mauern. + +[COK2_L:BARON2] +Also: Quentin hier - Quentin! QUENTIN! + +[COK2_M:BARON2] +Er fliegt dich über das Areal. + +[COK2_N:BARON2] +Heb ihr Nest aus! + +[COK2_O:BARON2] +Was soll denn das? + +[COK2_P:BARON2] +Was willst du hier? + +[COK2_Q:BARON2] +Hey, ich hab mich umgehört, und es ist klar, + +[COK2_R:BARON2] +dass Diaz in den Deal reingeplatzt ist und meinen Bruder erledigt hat. + +[COK2_S:BARON2] +Und dich erledigt er auch! + +[COK2_T:BARON2] +Mit Diaz nehm ich's auf! + +[COK2_U:BARON2] +Nein, hör zu! Ich übernehme Diaz - + +[COK2_V:BARON2] +er beginnt mir zu vertrauen. + +[COK2_1:BARON2] +Eines versteh ich nicht: Was soll das mit 'Quentin'!? + +[COK2_2:BARON2] +Weiß nicht. Hat mir immer gefallen...Quentin Vance... + +[COK2_3:BARON2] +Vance? Du heißt wirklich Lance Vance?! + +[COK2_4:BARON2] +Hey! Das hab ich schon in der Schule oft genug gehört! + +[COK2_5:BARON2] +Hast du so was schon mal aus einem Helikopter abgefeuert? + +[COK2_8:BARON2] +Wo fliegen wir denn hier? + +[COK2_9:BARON2] +Prawn Island. + +[COK2_13:BARON2] +'Lance Vance'. Du armer Teufel. + +[COK2_14:BARON2] +Ok, wir sind gleich da. + +[COK2_15:BARON2] +Wir fliegen ein paar Mal drüber weg. + +[COK2_16:BARON2] +Schalte so viele Kerle aus wie du kannst. + +[COK2_17:BARON2] +Dann setze ich dich ab, und du bist auf dich allein gestellt. + +[COK2_20:BARON2] +Shit! Das ist ja wie im Krieg hier! Schalt welche von den Schützen aus! + +[COK2_21:BARON2] +Wir werden getroffen, Mann! + +[COK2_22:BARON2] +Reparaturen an dem Kübel hier kosten! Schalt sie aus! + +[COK2_23:BARON2] +Ok, von jetzt an bist du auf dich alleine gestellt. Viel Glück! + +[COK2_24:BARON2] +Zustand Helikopter: + +[COK2_25:BARON2] +~g~Hol das Geld auf dem Dach. + +[COK2_27:BARON2] +Du bist auf MEINEM Gebiet, Mistkerl! + +[COK2_28:BARON2] +Dich mach ich fertig! + +[COK2_6:BARON2] +Nein. Aber ich kann ja unterwegs ein bisschen üben. + +[OPEN_B:BARON2] +Die Straßensperren zum Festland sind aufgehoben worden. + +{=================================== MISSION TABLE BARON3 ===================================} + +[COK3_A:BARON3] +Das gefällt euch nicht, was?! + +[COK3_B:BARON3] +Ahahahahaa, Ahahahahaa. + +[COK3_C:BARON3] +Vorsicht! Passen Sie auf, wo Sie damit hin zielen! + +[COK3_D:BARON3] +Kein Taubendreck mehr auf MEINEM Dach, was Tommy? + +[COK3_E:BARON3] +Sieht nicht so aus. + +[COK3_F:BARON3] +Das kannst du laut sagen. Also, hör zu: + +[COK3_G:BARON3] +Weißt du, wem das schnellste Boot an der Ostküste gehört? + +[COK3_H:BARON3] +Nicht wirklich. + +[COK3_I:BARON3] +MIR. Und das soll auch so bleiben. + +[COK3_J:BARON3] +Jeder Schmuggler von hier bis Caracas hat EINEN Traum: ein schnelleres Boot. + +[COK3_K:BARON3] +Es heißt, die Hull Werft hat gerade so ein Boot fertiggestellt, + +[COK3_L:BARON3] +für irgendeinen costaricanischen Idioten. + +[COK3_M:BARON3] +Und, Tommy...ICH WILL DIESES BOOT!!! + +[COK3_N:BARON3] +Ich glaube, die Tauben sind wieder da. + +[COK3_O:BARON3] +Ah! Ich dachte, ich hab euch erwischt. Wo kommst du denn her? + +[COK3_P:BARON3] +Tauben! Boom! Aaaaah! + +[COK3_5:BARON3] +~g~Suche den Schalter, um das Boot abzusenken. + +[COK3_6:BARON3] +~g~Bringe das Boot zur Villa. + +[COK3_7:BARON3] +~r~Du hast das Boot zerstört! + +[COK3_8:BARON3] +~g~Begib dich zur Bootswerft an den Docks und klau das schnellste Boot. + +[COK3_9:BARON3] +~g~Jetzt steig in das Boot. + +{=================================== MISSION TABLE BARON4 ===================================} + +[COK4_A:BARON4] +Eject! PLASTIKMÜLL! + +[COK4_B:BARON4] +Wie kannst du mir das antun? + +[COK4_C:BARON4] +Was bildest du dir ein, du mieses Stück Plastikdreck! Aaargh! + +[COK4_D:BARON4] +VERDAMMT! + +[COK4_E:BARON4] +Wenn das Ding meinen Lieblings-El-Burro-Film frisst, ist Sense! + +[COK4_F:BARON4] +Was kann ich noch tun? + +[COK4_G:BARON4] +Ist wahrscheinlich nicht eingesteckt. + +[COK4_H:BARON4] +Was? + +[COK4_I:BARON4] +Verdammt. Egal, ich kann mir noch 100 davon kaufen. + +[COK4_J:BARON4] +Also, Tommy, + +[COK4_K:BARON4] +jeden Monat legt ein 'Freiberufler' mit seiner Jacht in Vice City an. + +[COK4_L:BARON4] +Er verkauft seine Fracht an das erstbeste Schiff. + +[COK4_M:BARON4] +Du schnappst dir das Rennboot, + +[COK4_N:BARON4] +und bist vor allen anderen Pennern bei ihm. + +[COK4_O:BARON4] +Dann bringst du die Fracht hierher, ok? + +[COK4_P:BARON4] +Lass mich raten. Du dachtest, ich brauche einen Schutzengel. + +[COK4_Q:BARON4] +Ich denke nur, du solltest mich mitnehmen, Mann. + +[COK4_R:BARON4] +Du kannst lange den einsamen Macho-Helden markieren, + +[COK4_S:BARON4] +aber eines Tages werde ich dir den Hintern retten + +[COK4_T:BARON4] +und du wirst mich wahrscheinlich dafür küssen wollen. + +[COK4_U:BARON4] +Spinner. + +[COK4_V:BARON4] +Hahahaha! + +[COK4_1:BARON4] +Tommy, wir wissen, es war Diaz, der unseren Deal platzen ließ. + +[COK4_3:BARON4] +Wieso spielen wir dann die Laufburschen für ihn? + +[COK4_4:BARON4] +Je mehr wir jetzt lernen, desto weniger müssen wir lernen, wenn wir diese Stadt übernehmen! + +[COK4_5:BARON4] +Dein Stil gefällt mir. Echt erfrischend. + +[COK4_12:BARON4] +Pass auf, die kommen von überall + +[COK4_13:BARON4] +Erwischt! Fahr zu Diaz so schnell du kannst! + +[COK4_14:BARON4] +Willst du was abhaben? + +[COK4_15:BARON4] +Schönen Gruß an die Fische! + +[COK4_16:BARON4] +Nimm das! Und das! + +[COK4_19:BARON4] +Gleich kommt's noch dicker! + +[COK4_20:BARON4] +Da sind Schützen auf dem Pier! + +[COK4_24:BARON4] +Gut geschossen, mein Freund. Du bist ein erstklassiger Psycho. + +[COK4_25:BARON4] +Danke sehr. + +[COK4_26:BARON4] +Wir sehen uns, Tommy. + +[COK4_27:BARON4] +Ok, Mr. Lance Vance Dance. + +[COK4_28:BARON4] +~g~Sei vor den anderen Booten bei der Jacht! + +[COK4_31:BARON4] +~g~Begib dich zum schnellsten Boot auf dem Pier! + +[COK4_32:BARON4] +~r~Zu langsam! + +[COK4_33:BARON4] +~r~Du hast das Boot zerstört! + +[COK4_34:BARON4] +~g~Zieh diese Boote aus dem Verkehr! + +[COK4_35:BARON4] +Zustand Boot: + +{=================================== MISSION TABLE BARON5 ===================================} + +[PROP_A:BARON5] +OBJEKT ERWORBEN! + +[COK4_30:BARON5] +~r~Lance ist erledigt! + +[ASS1_A:BARON5] +Im Kofferraum sind ein paar Kanonen. + +[ASS1_B:BARON5] +Wahnsinn! Wo hast du denn die alle her? + +[ASS1_C:BARON5] +Hab ich für schlechte Zeiten zurückgelegt. + +[ASS1_D:BARON5] +Zufrieden? + +[ASS1_E:BARON5] +Ja. Und wie! + +[ASS1_F:BARON5] +Ihr dämlichen Idioten! + +[ASS1_G:BARON5] +Mein schönes Haus + +[ASS1_H:BARON5] +Seht euch an, was ihr angestellt habt! + +[ASS1_I:BARON5] +Das ist für meinen Bruder! + +[ASS1_J:BARON5] +Ich habe dir vertraut, Tommy. + +[ASS1_K:BARON5] +Ich hätte was aus dir gemacht... + +[ASS1_L:BARON5] +Gute Nacht, Mr. Diaz. + +[ASS1_1:BARON5] +Bald wimmelt's hier von Arschlöchern. Sei vorsichtig. + +[ASS1_2:BARON5] +Keine Panik, Tommy, ich geb dir Deckung. + +[ASS1_13:BARON5] +DIAZ?! Ich bin hier, um deinen Laden zu übernehmen! + +[ASS1_14:BARON5] +TOMMY! Du Verräter...Du Idiot! Dich mache ich fix und fertig... + +[ASS1_16:BARON5] +~g~Erledige Diaz! + +[BUD1:BARON5] +Lance + +[ASS1_18:BARON5] +~g~Die Tür ist verschlossen. Versuche es auf einem anderen Weg. + +[ASS1_19:BARON5] +Da lang! + +[ASS1_20:BARON5] +Tommy, ich hab ein Problem mit Quentin, nicht mit dir, Mann! + +{=================================== MISSION TABLE BIKE1 ===================================} + +[BM1_A:BIKE1] +Wo ist Baker? + +[BM1_B:BIKE1] +Ich suche Big Mitch Baker... + +[BM1_C:BIKE1] +Wer sucht ihn? + +[BM1_D:BIKE1] +Tommy Vercetti. + +[BM1_E:BIKE1] +Vercetti. + +[BM1_F:BIKE1] +Du siehst nicht wie ein Bulle aus, das heißt, du hast 1 Minute. + +[BM1_G:BIKE1] +Also drück auf die Tube. + +[BM1_H:BIKE1] +Kent Paul sagt, ihr wärt interessiert, die Security für einen Gig zu übernehmen, den er plant. + +[BM1_I:BIKE1] +Kent Paul? Pfff! Kein Wunder, dass er dich schickt. + +[BM1_J:BIKE1] +Als er das letzte Mal hier war, ist er durch Fenster wieder gegangen - und zwar splitternackt. + +[BM1_K:BIKE1] +Seid ihr nun interessiert oder nicht? + +[BM1_L:BIKE1] +Gefälligkeiten gibt's nur für Mitglieder. + +[BM1_M:BIKE1] +Wie kann ich beitreten? + +[BM1_N:BIKE1] +Wir sind hier kein Golfklub, Kleiner. Kannst du 'n Bike fahren? + +[BM1_O:BIKE1] +Kannst du auf 'm Barhocker sitzen und saufen? + +[BM1_P:BIKE1] +Cougar, Zeppelin, checkt mal ab, wie diese Sissy hier fährt. + +[BM1_2:BIKE1] +~g~Du brauchst eine Freeway oder eine Angel, um mitzumachen! + +[BM1_3:BIKE1] +~r~Die Fahrer wurden angegriffen! + +[BIKE1_1:BIKE1] +Ok, Schickimickibürschchen, dann zeig mal, was du drauf hast. + +[BM1_1:BIKE1] +~g~Schnapp dir eine Freeway oder eine Angel und geh in Startposition. + +{=================================== MISSION TABLE BIKE2 ===================================} + +[BM2_2:BIKE2] +~g~Du musst den Chaosmesser in der vorgegebenen Zeit vollmachen, um uns zu zeigen, aus welchen Holz du bist! + +[BM2_4:BIKE2] +~r~Du hast den Chaosmesser nicht rechtzeitig vollgemacht! + +[BM2_1:BIKE2] +CHAOSMESSER: + +[BM2_A:BIKE2] +Ha, ha, ha, hab dich wieder erwischt. + +[BM2_B:BIKE2] +Hey, Vercetti. + +[BM2_C:BIKE2] +Cougar meint, du fährst ziemlich gut. + +[BM2_D:BIKE2] +Ja, wie lange soll ich noch hier rumgurken? + +[BM2_E:BIKE2] +Ich bin ein sehr beschäftigter Mann. + +[BM2_F:BIKE2] +Wenn ich mich kloppen soll, damit das klar geht, dann los. + +[BM2_G:BIKE2] +Es geht bei uns nicht nur ums dreinschlagen. Man muss zur Familie gehören. + +[BM2_H:BIKE2] +Ja, ich hab schon mal zu einer Familie gehört. Hat nicht funktioniert. + +[BM2_I:BIKE2] +Klar. Aber unsere Familie kümmert sich um ihre Leute. + +[BM2_J:BIKE2] +Wir verlangen nicht, dass einer die Drecksarbeit macht und danach 15 Jahre einsitzt. + +[BM2_K:BIKE2] +Ja, ganz recht. Ich hab meine Hausaufgaben gemacht. + +[BM2_L:BIKE2] +Das hier ist die größte Familie von Außenseitern, Outlaws und Unruhestiftern. + +[BM2_M:BIKE2] +Ein paar von uns wurden sogar von ihrem eigenen Land verraten. + +[BM2_N:BIKE2] +Während des Vietnamkriegs war ich eingelocht. Miese Sache. + +[BM2_O:BIKE2] +Drum sollst du denen ja zeigen, was Sache ist. + +[BM2_P:BIKE2] +Dieses ganze verdammte Land braucht einen Arschtritt, und wir geben ihm diesen Tritt. + +[BM2_Q:BIKE2] +Also los, schnapp dir ein Bike und zeig der Stadt, wie sauer du bist. + +[BM2_R:BIKE2] +Alles klar. + +[BM2_3:BIKE2] +~g~Dieser Ton zeigt an, dass du einen Teil gefüllt hast. Mach ihn immer voller. + +{=================================== MISSION TABLE BIKE3 ===================================} + +[BM3_A:BIKE3] +Hi, Mitch. + +[BM3_B:BIKE3] +Ah, sieh an, 'Outlaw' Vercetti. + +[BM3_C:BIKE3] +Jetzt will ich sehen, wie du für deine Kumpels kämpfst. + +[BM3_D:BIKE3] +Eine Straßengang von hier hat den Fehler gemacht, meinen Hobel zu klauen. + +[BM3_E:BIKE3] +Wollten wahrscheinlich zeigen, was für coole Machos sie sind. + +[BM3_F:BIKE3] +Ich und die Jungs wollten ihnen eigentlich ein bisschen Respekt einbläuen. + +[BM3_G:BIKE3] +Aber- + +[BM3_H:BIKE3] +-dann dachte ich mir, das wäre doch ein guter Test für dich. + +[BM3_I:BIKE3] +Bring mir meine Maschine zurück und Paul kriegt seine Security. + +[BM3_2:BIKE3] +~r~Du solltest die Maschine zurückbringen, nicht schrotten! + +[BM3_3:BIKE3] +~g~Bring die Maschine zur Bar! + +[BM3_4:BIKE3] +~g~Setz dich auf die Maschine! + +[INTRUDE:BIKE3] +~g~Man hat dich bemerkt! + +[BM3_6:BIKE3] +~g~Sie sind Downtown hinter dem Ammu-Nation. + +[BM3_7:BIKE3] +~g~Du brauchst eine schnelle Maschine, um auf das Dach zu gelangen. + +[BM3_8:BIKE3] +~g~Spring mit dem Bike von diesen Stufen auf das Dach auf der anderen Seite der Straße. + +[BM3_1:BIKE3] +~g~Eine Gang hat Mitch Bakers Maschine geklaut. Beschaff sie ihm wieder! + +[BM3_9:BIKE3] +~g~Schnapp dir Mitchs Maschine und mach dich aus dem Staub! + +{=================================== MISSION TABLE BMX_1 ===================================} + +[GETBIK2:BMX_1] +Du hast ~1~ Sekunden, um auf ein Geländemotorrad zu steigen! + +{=================================== MISSION TABLE BOATBUY ===================================} + +[DRUG_1:BOATBUY] +Hallo? Hal-lo?! Hallo? + +[DRUG_13:BOATBUY] +Meister? + +[DRUG_2:BOATBUY] +Mach aus. Da kommt einer. + +[DRUG_3:BOATBUY] +Tag, Herr Anzugmensch. Sie müssen der neue Eigentümer sein. + +[DRUG_4:BOATBUY] +Ja. Welches von den Booten ist das schnellste? + +[DRUG_5:BOATBUY] +Das ist schon im Wasser, Meister. + +[DRUG_6:BOATBUY] +Dachte mir, dass Sie es mal testen wollen. + +[DRUG_7:BOATBUY] +Das Ding hat jetzt schon 300 PS unter der Haube, Meister. + +[DRUG_8:BOATBUY] +Und mit dem Fiberglas-Rumpf - das Ding pfeift nur so durch die Wellen! + +[DRUG_9:BOATBUY] +In vier Sekunden von null auf sechzig Meilen, Meister. + +[DRUG_10:BOATBUY] +Und im Rumpf kriegen Sie so 20 Ladungen vom besten jamaikanischen Gras unter. + +[DRUG_11:BOATBUY] +Nur zu, Chef, machen Sie 'ne Spritztour! + +[DRUG_12:BOATBUY] +Hey Anzugmensch, haben Sie mal Feuer? + +{=================================== MISSION TABLE CAP_1 ===================================} + +[CAP1_B1:CAP_1] +~g~Die Mafia verlangt Schutzgeld von dir. Stöbere sie auf und erledige sie. + +[CAP1_B2:CAP_1] +~g~Die Mafia verlangt von dir Schutzgeld für die Bootswerft! + +[CAP1_B3:CAP_1] +~g~Die Mafia verlangt von dir Schutzgeld für die Eiscremefabrik! + +[CAP1_B4:CAP_1] +~g~Die Mafia verlangt von dir Schutzgeld für das Autohaus! + +[CAP1_B5:CAP_1] +~g~Die Mafia verlangt von dir Schutzgeld für das Taxiunternehmen! + +[CAP_01:CAP_1] +Ok, wo brennt's denn? + +[CAP_02:CAP_1] +Wer war das? + +[CAP_03:CAP_1] +Tommy... ein paar Mafia-Typen... sie wollen wiederkommen, ihren Anteil abkassieren. + +[CAP_04:CAP_1] +Ein gewisser Mr. Forello hätte dir Geld gegeben. Mir geht's schlecht. + +[CAP_05:CAP_1] +Forelli? SONNY Forelli? + +[CAP_06:CAP_1] +Ja, so heißt er, glaube ich... ich konnte nichts gegen sie ausrichten. + +[CAP_07:CAP_1] +Keine Angst, mein Freund, ich mach dir keine Vorwürfe. + +[CAP_08:CAP_1] +Bringt ihn ins Krankenhaus. + +[CAP_09:CAP_1] +Tommy... mach den Kerl 'nen Kopf kürzer für mich... + +[CAP_10:CAP_1] +Ich mach ihn zwei Köpfe kürzer... + +[CAP1_2:CAP_1] +Du kennst die Regeln, Vercetti! + +[CAP1_3:CAP_1] +Mr. Forelli bestellt schöne Grüße! + +[CAP1_4:CAP_1] +Das ist der Harwood Butcher! + +[CAP1_5:CAP_1] +Sag Sonny, er soll mir vom Leib bleiben! + +[CAP1_6:CAP_1] +Vice City gehört nicht mehr ihm, sondern MIR! + +[CAP1_7:CAP_1] +Du glaubst, du kannst mich vom Thron stürzen, Vercetti? + +[CAP1_8:CAP_1] +Wir jagen dich bis ans Ende deiner Tage, Vercetti. + +[CAP1_9:CAP_1] +Du hast keine Chance, du irrer Idiot! + +[CAP1_10:CAP_1] +Ich mach dich fertig, Vercetti. + +[CAP1_11:CAP_1] +Du warst schon immer ein Schwachkopf. + +[CAP1_12:CAP_1] +Du wirst dran glauben, Vercetti. + +[CAP1_B6:CAP_1] +~g~Du hast den Eintreiber gefunden, erledige ihn. + +[CAP1_B7:CAP_1] +~g~Du hast den Eintreiber verloren. + +[CAP1_B8:CAP_1] +~r~Der Eintreiber verlangt für all deine Geschäfte Schutzgeld. + +[CAP1_B9:CAP_1] +~g~Die Mafia verlangt Schutzgeld für das Malibu! + +[CAP1_B0:CAP_1] +~g~Die Mafia verlangt Schutzgeld für das Filmstudio! + +[CAP1_C2:CAP_1] +~g~Die Mafia ist bei der Bootswerft angekommen! + +[CAP1_C3:CAP_1] +~g~Die Mafia ist bei der Eiscremefabrik angekommen! + +[CAP1_C4:CAP_1] +~g~Die Mafia ist bei dem Autohaus angekommen! + +[CAP1_C5:CAP_1] +~g~Die Mafia ist bei dem Taxiunternehmen angekommen! + +[CAP1_C9:CAP_1] +~g~Die Mafia ist im Malibu Club angekommen! + +[CAP1_C0:CAP_1] +~g~Die Mafia ist im Filmstudio angekommen! + +[CAP1_D2:CAP_1] +~g~Die Mafia verlässt die Bootswerft! + +[CAP1_D3:CAP_1] +~g~Die Mafia verlässt die Eiscremefabrik! + +[CAP1_D4:CAP_1] +~g~Die Mafia verlässt das Autohaus! + +[CAP1_D5:CAP_1] +~g~Die Mafia verlässt das Taxiunternehmen! + +[CAP1_D9:CAP_1] +~g~Die Mafia verlässt den Malibu Club! + +[CAP1_D0:CAP_1] +~g~Die Mafia verlässt das Filmstudio! + +[CAP1B10:CAP_1] +Du hast die Eintreiber erledigt. Es kommen weitere. + +{=================================== MISSION TABLE CARBUY ===================================} + +[CAR1_1:CARBUY] +B.J. Smith. Und Sie müssen Mr. Vercetti sein. + +[CAR1_2:CARBUY] +Soll ich Sie herumführen? + +[CAR1_3:CARBUY] +Warum nicht? + +[CAR1_4:CARBUY] +Tja, ich verkaufe das Autohaus ja eigentlich nur ungern. + +[CAR1_5:CARBUY] +War meine erste Investition, nachdem ich Football-Profi wurde. + +[CAR1_6:CARBUY] +Aber es wird Zeit für eine Luftveränderung. + +[CAR1_7:CARBUY] +Sie verlassen die Stadt? + +[CAR1_8:CARBUY] +Nicht in allzu großer Eile, hoffe ich doch? + +[CAR1_9:CARBUY] +Nein, ich bereite mich nur auf mein Comeback als Football-Profi vor. Ich hatte schon aufgehört. + +[CAR1_10:CARBUY] +Das Geschäft lief nicht allzu gut. + +[CAR1_11:CARBUY] +Da haben sich meine Angestellten was einfallen lassen, + +[CAR1_12:CARBUY] +um ein bisschen was 'nebenbei' zu erwirtschaften. + +[CAR1_13:CARBUY] +Ich könnte den Laden auch dichtmachen, bevor ich ihn Ihnen verkaufe. + +[CAR1_14:CARBUY] +Ich könnte die Bude bei Bedarf sogar abfackeln. + +[CAR1_15:CARBUY] +Das ist erstklassiger Baugrund hier. + +[CAR1_16:CARBUY] +Machen Sie sich mal keine Gedanken. + +[CAR1_17:CARBUY] +Der Laden ist genau das, was ich brauche. + +[CAR1_18:CARBUY] +Ja. Dann kommen wir also ins Geschäft? + +{=================================== MISSION TABLE CARPAR1 ===================================} + +[MM_1_A:CARPAR1] +~g~Passiere ~y~5 checkpoints~r~ OHNE irgendwelche ~r~Hütchen ~g~umzufahren! ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[CONE_1:CARPAR1] +~r~Du hast ein Hütchen umgefahren!! + +[MM_1_C:CARPAR1] +~y~PASSIERE~g~ einen Checkpoint, dann läuft die Zeit. ~g~Jeder Checkpoint bringt dir ~y~~1~ SEKUNDEN~g~. + +{=================================== MISSION TABLE COPCAR ===================================} + +[C_COMP1:COPCAR] +Bürgerwehr-Mission Level 12 beendet: Deine max. Panzerung erhöht sich auf 150 + +[CLEVEL:COPCAR] +Bürgerwehr-Mission Level ~1~ + +[C_PASS:COPCAR] +BEDROHUNG AUSGERÄUMT: $ ~1~ + +[KILLS:COPCAR] +HITS: + +[C_BREIF:COPCAR] +~g~Verdächtiger wurde zuletzt in der Gegend von ~a~ gesichtet. + +[COPCART:COPCAR] +~g~Du hast ~1~ Sekunden, um zu einem Polizeifahrzeug zurückzukehren, bevor die Mission endet. + +[C_CANC:COPCAR] +~r~Bürgerwehr-Mission abgebrochen! + +[C_TIME:COPCAR] +~r~Deine Zeit als Gesetzeshüter ist vorbei! + +{=================================== MISSION TABLE COUNT1 ===================================} + +[CM1_A:COUNT1] +Mr.Vercetti? Hey. Sie haben die alte Druckerei gekauft? + +[CM1_B:COUNT1] +Ja, mein Vater hat an diesen Dingern gearbeitet. + +[CM1_C:COUNT1] +Ich sollte in seine Fußstapfen treten, aber...es ist anders gekommen. + +[CM1_D:COUNT1] +Wollen Sie die alten Maschinen verkaufen, das Werk abreißen? + +[CM1_E:COUNT1] +Ich denke nach, ob wir was drucken sollten. Eine Zeitung, ein Magazin... + +[CM1_F:COUNT1] +Ach, Blödsinn, absoluter Blödsinn. Ich drucke lieber Geld. Ist gar nicht so schwer. + +[CM1_G:COUNT1] +In kleinerem Umfang mache ich das schon seit Jahren. + +[CM1_H:COUNT1] +Tatsächlich? + +[CM1_I:COUNT1] +Klar. Aber wir bräuchten gute Platten. + +[CM1_J:COUNT1] +Natürlich! In Florida gibt es schon ein Geldfälscher-Syndikat. + +[CM1_K:COUNT1] +Ein Syndikat? + +[CM1_L:COUNT1] +Ja. Hab aber nur gerüchteweise davon gehört. + +[CM1_M:COUNT1] +Ich kenne einen, der kennt sich mit Gerüchten aus... + +[CM1_N:COUNT1] +Hab immer die Abende mit ihm verbracht, die Walzen reinigen. + +[CM1_2A:COUNT1] +Sieh dir diesen Hintern an! + +[CM1_2B:COUNT1] +Tja, Kleine, du weißt nicht, was dir entgeht! + +[CM1_2C:COUNT1] +Na, alter Freund, wie läuft's so? + +[CM1_2D:COUNT1] +Was weißt du über Geldfälscherei? + +[CM1_2E:COUNT1] +'Oh, alles bestens, Paul, und bei dir?' + +[CM1_2F:COUNT1] +Komm her! + +[CM1_2G:COUNT1] +Ist ja gut, ist ja gut. Anscheinend bist du schwer beschäftigt. + +[CM1_2H:COUNT1] +Über Falschgeld weiß ich nur, dass die Triaden die Platten liefern. + +[CM1_2I:COUNT1] +Die haben eine Reederei unten bei den Docks. + +[CM1_2J:COUNT1] +Der Boss weiß, wann die nächsten Platten reinkommen. + +[CM1_2K:COUNT1] +Danke, Paul. + +[CM1_2L:COUNT1] +Was ist bloß los mit dir, du Wahnsinniger! + +[CM1_2M:COUNT1] +Gib mir noch einen Drink, hopp! + +[CM1_3:COUNT1] +~g~Man hat dich bemerkt! + +[CM1_5:COUNT1] +~g~Begib dich zu Kent Paul in den Malibu Club! + +[CNT1_1:COUNT1] +Wer sind Sie? Uff! Aiiee! Nicht ins Gesicht! Nicht ins Gesicht! + +[CM1_1:COUNT1] +~g~Begib dich zum Schiff der Chartered Libertine Lines bei den Docks. + +[CM1_2:COUNT1] +~g~Der Reedereichef verfügt über die benötigten Informationen. + +[CNT1_2:COUNT1] +Ok, ich rede ja! Ich rede! + +[CM1_6:COUNT1] +~g~Begib dich mit den Informationen zurück in die Druckerei! + +{=================================== MISSION TABLE COUNT2 ===================================} + +[CNT2_B1:COUNT2] +Ok, der Kurier holt die Druckplatten heute vom Hafen ab. + +[CNT2_B2:COUNT2] +Ich fange ihn ab, schnapp mir die Platten, häng die Cops ab und komm wieder hierher. + +[CNT2_B3:COUNT2] +Also, je nachdem wie es läuft, haben wir + +[CNT2_B4:COUNT2] +entweder 5 Minuten zum Gelddrucken, bis das Fälschersyndikat uns findet, oder wir haben alle Zeit der Welt. + +[CNT2_B5:COUNT2] +Jedenfalls will ich 5 Minuten nachdem ich hier bin, Scheinchen aus der Presse kommen sehen! + +[CNT2_B6:COUNT2] +Keine Sorge, Tommy. Wir sind bereit. + +[CNT2_B7:COUNT2] +Ich und die Jungs bleiben in der Nähe, falls die Cops dir in die Quere kommen. + +[CNT2_B8:COUNT2] +Ok. Jeder weiß, was er zu tun hat? Gut. Bis später. + +[CNT2_01:COUNT2] +~g~Der ~r~Kurier~g~ mit den Druckplatten kommt jeden Moment in einem Helikopter an den ~y~Docks~g~ an. + +[CNT2_02:COUNT2] +~r~Der Kurier mit den Platten ist im Helikopter geflohen. + +[CNT2_03:COUNT2] +~r~Der Kurier ist an seinem Ziel angekommen. Du kommst zu spät! + +[CNT2_04:COUNT2] +~r~Du hast die Platten in der Explosion zerstört! + +[CNT2_05:COUNT2] +~g~Du hast die Druckplatten. Bring sie in die Druckerei. + +[CNT2_06:COUNT2] +~g~Der Kurier ist hinüber. Hol dir die Platten, ehe dir jemand zuvorkommt. + +[CNT2_07:COUNT2] +~g~Eine der Wachen hat die Platten genommen. Lass ihn nicht entkommen! + +[CNT2_08:COUNT2] +~g~Der ~r~Kurier~g~ mit den Platten ist an den Docks eingetroffen. + +[CNT2_4:COUNT2] +Privatangelegenheit. Du hast hier nichts verloren! + +[CNT2_09:COUNT2] +DRUCKEREI ERWORBEN + +[CNT2_10:COUNT2] +~g~Die Druckerei generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +[CNT2_11:COUNT2] +~r~Die Platten liegen auf dem Meeresgrund! + +{=================================== MISSION TABLE CUBAN1 ===================================} + +[CUB1_A:CUBAN1] +Was ist, Mann? + +[CUB1_B:CUBAN1] +Hey, langsam, Paps, der ist für mich. Bist du der Bursche? + +[CUB1_C:CUBAN1] +Oh ja, du bist der Bursche. Glaub schon. + +[CUB1_D:CUBAN1] +Nein, glaub ich nicht. + +[CUB1_E:CUBAN1] +Ach ja? Komm her du Großmaul. + +[CUB1_F:CUBAN1] +Meinst du, du kannst es mit mir aufnehmen? + +[CUB1_G:CUBAN1] +Meinst du, du kannst mich für blöd verkaufen? + +[CUB1_H:CUBAN1] +Nein, ich glaube, deine Blödheit reicht für uns beide. + +[CUB1_I:CUBAN1] +Hey, er sagt, du bist dumm, mein Sohn. + +[CUB1_J:CUBAN1] +Und ich sage, er ist ein kleines Mädchen, Paps. + +[CUB1_K:CUBAN1] +Sieh dir doch an, wie er angezogen ist. + +[CUB1_L:CUBAN1] +Was ist los? Ist heute Weiberabend? + +[CUB1_M:CUBAN1] +Du willst ein harter Kerl sein und ziehst dich an wie ein Weib? + +[CUB1_N:CUBAN1] +Hast du auch ein Höschen an, oder was? + +[CUB1_O:CUBAN1] +Was hast du gegen Frauen? Ziehst du Männer vor? + +[CUB1_P:CUBAN1] +Ich liebe Frauen! Ich liebe alle Frauen! Ich liebe meine Mutter, Chico. + +[CUB1_Q:CUBAN1] +Ok, ok, ich glaub's dir ja. + +[CUB1_R:CUBAN1] +Kannst du fahren, Amigo? + +[CUB1_S:CUBAN1] +Ja...wie eine Frau. + +[CUB1_T:CUBAN1] +Sehr witzig. Du gefällst mir, Großer. Vielleicht kannst du mir helfen. + +[CUB1_U:CUBAN1] +Vielleicht kannst du beweisen, dass du ein Mann bist. Hah? + +[CUB1_V:CUBAN1] +Schnapp dir das Boot. + +[CUB1_W:CUBAN1] +Zeig mir, dass du ein ganzer Kerl bist, + +[CUB1_X:CUBAN1] +und kein kleines Mädchen. + +[CUB1_02:CUBAN1] +Ok Mann, behandle es wie eine Frau. + +[CUB1_03:CUBAN1] +Nicht schlecht, du bist ein echter Mann. + +[CUB1_04:CUBAN1] +Mann, du bist ein ganzer Kerl, Amigo. + +[CUB1_05:CUBAN1] +Amigo, du bist ein Mann, Mann. + +[CUB1_06:CUBAN1] +Du willst ein Mann sein, Mann? + +[CUB1_07:CUBAN1] +Du bist ein feiges Würstchen, Kleiner, heul dich bei Mammi aus. + +[CUB1_08:CUBAN1] +Du bist zu nichts zu gebrauchen. Macht einen auf harter Mann, aber fährt wie ein Idiot. + +[CUB1_09:CUBAN1] +Mann, du bist der Hammer, Mann. Du gefällst mir. Du gefällst mir sehr. + +[CUB1_10:CUBAN1] +Du bist gut, Mann. Weil du ein ganzer Kerl bist. Alle meine Freunde sind ganze Kerle. + +[CUB1_11:CUBAN1] +Du hast Rico erledigt! + +[CUB1_12:CUBAN1] +Fahre durch den ersten Checkpoint, um den Test zu beginnen. + +[CUB1_14:CUBAN1] +Steig wieder ins Boot! + +[CUB1_15:CUBAN1] +Du bist zu langsam, Mann. + +[CUB1_01:CUBAN1] +Hi, ich bin Rico. Bist du der 'ganze Kerl'? + +[CUB1_13:CUBAN1] +~g~Du hast drei Minuten, um den Rundkurs zu schaffen. + +{=================================== MISSION TABLE CUBAN2 ===================================} + +[CUB2_25:CUBAN2] +ERLEDIGE ALLE HAITIANER!! + +[CUB2_M:CUBAN2] +Wer sich mit mir anlegt, hat sich den stärksten der Stadt rausgesucht! + +[CUB2_A:CUBAN2] +Eine Kaffee, bitte, Alberto. + +[CUB2_N:CUBAN2] +Kein Problem, Tommy. + +[CUB2_B:CUBAN2] +Paps! Es gibt ein Riesenproblem! + +[CUB2_O:CUBAN2] +Umberto, mein Sohn, was ist passiert? + +[CUB2_C:CUBAN2] +Die Haitianer! Ich hasse die Haitianer! + +[CUB2_D:CUBAN2] +Die haben mir das letzte Mal ans Bein gepinkelt! + +[CUB2_E:CUBAN2] +Diese Haitianer. Wir machen sie fertig! + +[CUB2_F:CUBAN2] +Aber dazu brauchen wir Hilfe. + +[CUB2_G:CUBAN2] +Ich hab dabei schon ein paar Brüder verloren. + +[CUB2_H:CUBAN2] +Amigo, du fährst gut! + +[CUB2_I:CUBAN2] +Für eine Frau, was? + +[CUB2_J:CUBAN2] +Jetzt ist nicht die Zeit für Witze! + +[CUB2_K:CUBAN2] +Komm, fahr noch mal für mich! + +[CUB2_L:CUBAN2] +Bring meine Jungs da rüber, und dann erledigen wir diese Haitianer! + +[CUB2_01:CUBAN2] +Da ist nicht genug Platz, Mann, du brauchst ein größeres Auto. + +[CUB2_02:CUBAN2] +Wir brauchen Verstärkung aus dem Café! + +[CUB2_03:CUBAN2] +~g~Besorg dir ein Auto und hole die Kubaner vor Robinas Café ab. + +[CUB2_04:CUBAN2] +~g~Setze die Kubaner am Kampfort ab. + +[CUB2_05:CUBAN2] +Schalte den feigen Heckenschützen aus! + +[CUB2_07:CUBAN2] +Die kämpfen wie Weiber! In Deckung! + +[CUB2_09:CUBAN2] +Heckenschütze auf dem Dach! + +[CUB2_11:CUBAN2] +~r~Du Idiot! Das Auto hätten wir gebraucht! + +[CUB2_12:CUBAN2] +Hey, Amigo! Schön zu sehen, dass du's geschafft hast! + +[CUB2_13:CUBAN2] +Stinkendes Haitianer-Nest. Das werden wir komplett ausheben! + +[CUB2_14:CUBAN2] +ANGRIFF! + +[CUB2_15:CUBAN2] +Los, meine Brüder, ANGRIFF!! + +[CUB2_16:CUBAN2] +Tommy, wir haben unseren mannhaften Mut bewiesen! + +[CUB2_17:CUBAN2] +Lass uns diesen Wagen voller Stoff nehmen und abhauen! + +[CUB2_18:CUBAN2] +~g~Besorge dir ein Auto und hole die Kubaner ab. + +[CUB2_19:CUBAN2] +Wir werden kämpfen wie Männer! + +[CUB2_21:CUBAN2] +Kämpfen wie ganze Kerle! + +[CUB2_22:CUBAN2] +~g~Schalte die restlichen Haitianer aus, damit die Kubaner vorrücken können. + +[CUB2_23:CUBAN2] +~g~In Little Haiti wird es von Haitianern wimmeln, die sich an den Kubanern rächen wollen. Sei vorsichtig. + +[CUB2_24:CUBAN2] +~g~Kehre mit dem Van zu Robinas Café zurück und parke hinter dem Haus. + +{=================================== MISSION TABLE CUBAN3 ===================================} + +[CUB3_A:CUBAN3] +Alberto? Einen Kaffee, Senor? + +[CUB3_B:CUBAN3] +Paps, gib dieser falschen Schlange nichts. + +[CUB3_C:CUBAN3] +Du hast zwei Gesichter, Tommy! + +[CUB3_D:CUBAN3] +Du hast entweder zwei Gesichter oder du bist ein Feigling, Kleiner! + +[CUB3_E:CUBAN3] +Die Haitianer, Mann, die lachen mich aus. + +[CUB3_F:CUBAN3] +Ruhig, ruhig. Was gibt es für Probleme? + +[CUB3_G:CUBAN3] +Sie lachen mich aus, Tommy. Mich! + +[CUB3_H:CUBAN3] +Umberto Robina. Die tun, was sie wollen! + +[CUB3_I:CUBAN3] +Die tun nicht, was sie wollen, Umberto. Sie tun, was du sie tun lässt. + +[CUB3_J:CUBAN3] +Was? + +[CUB3_K:CUBAN3] +Soll jemand aus dem Weg geräumt werden? + +[CUB3_L:CUBAN3] +Ich kann das machen, aber es kostet. + +[CUB3_M:CUBAN3] +Ich weiß, wir sind Brüder und alles, aber hier geht's um ein Geschäft. + +[CUB3_N:CUBAN3] +Tommy. Du bist ein echter Mann. Ein Geschäftsmann, ein Gentleman. + +[CUB3_O:CUBAN3] +Die Haitianer, sie erwarten eine Schiffladung Stoff, richtig gutes Zeug. + +[CUB3_P:CUBAN3] +Wir schnappen es uns und erledigen sie. + +[CUB3_Q:CUBAN3] +Du schnappst es dir, ich passe auf dich auf. Wie auf einen Bruder, einen Sohn. + +[CUB3_R:CUBAN3] +Ich glaube, Cash ist mir lieber als auf deinen Knien zu reiten, Amigo. + +[CUB3_01:CUBAN3] +Hey, Rico. Nettes Boot. Bist du bereit? + +[CUB3_03:CUBAN3] +~g~Sammle alle Aktenkoffer mit Stoff und Geld ein. + +[CUB3_04:CUBAN3] +~g~Bringe die Drogen und das Geld zu Umberto. + +[CUB3_05:CUBAN3] +Ja, Tommy. Also, sei ein guter Kapitän heute. + +[CUB3_06:CUBAN3] +Mein Boot nützt mir nichts, wenn es durchlöchert ist, ok? + +[CUB3_07:CUBAN3] +~g~Begib dich zu Rico. Er fährt dich zum Treffpunkt. + +[CUB3_02:CUBAN3] +~g~ERLEDIGE ALLE HAITIANER AUF DEN BOOTEN!! + +[CUB3_08:CUBAN3] +Oh-oh... eine Bande Kubaner. Wir werden angegriffen! + +{=================================== MISSION TABLE CUBAN4 ===================================} + +[CUB4_A:CUBAN4] +Hey, Ladies. Wisst ihr, was ich mache? + +[CUB4_B:CUBAN4] +Ich erledige einen Haitianer. Und dann? + +[CUB4_C:CUBAN4] +Dann mache ich Liebe wie ein Mann. + +[CUB4_D:CUBAN4] +Weißt du, Kleine? So in der Art. + +[CUB4_E:CUBAN4] +Penner! + +[CUB4_F:CUBAN4] +Vollidiot. + +[CUB4_G:CUBAN4] +Hey, Baby, dich würde ich nicht mit 'ner Kneifzange anfassen! + +[CUB4_H:CUBAN4] +Umberto Robina mag Frauen, keine Ziege mit Rock! + +[CUB4_I:CUBAN4] +Tommy!! Tommy, ich liebe dich, ich liebe dich! Lass uns gehen! + +[CUB4_J:CUBAN4] +Wohin denn? Kann ich nicht noch einen Kaffee trinken? + +[CUB4_K:CUBAN4] +Keine Zeit! Außerdem habe ich erst einen getrunken. + +[CUB4_L:CUBAN4] +Wir nehmen uns die Haitianer vor. + +[CUB4_M:CUBAN4] +Tommy, wie erledigt man eine Schlange? + +[CUB4_N:CUBAN4] +Man beißt sie in den Hintern! Hahaha! + +[CUB4_O:CUBAN4] +Wenn du es sagst, Umberto. + +[CUB4_P:CUBAN4] +Tommy, geh und besorge uns ein kleines haitianisches Auto. + +[CUB4_Q:CUBAN4] +Wenn du es hast, komm zurück und hol meinen Jungen ab + +[CUB4_R:CUBAN4] +Pepe, und fahre ihn zu den Haitianern. + +[CUB4_S:CUBAN4] +Dann begibst du dich zur Laboranlage der Haitianer und verwendest ihr Lösungsmittel als Sprengstoff. + +[CUB4_T:CUBAN4] +Bumm! Bye bye! + +[CUB4_U:CUBAN4] +Umberto, was ist mit dir? + +[CUB4_V:CUBAN4] +Oh, ich halte mich raus und passe mit Paps auf das Café auf. + +[CUB4_W:CUBAN4] +Er fühlt sich nicht besonders, weißt du. + +[CUB4_02:CUBAN4] +~g~Die Bomben werden per Zeitzünder auf 45 sek. gestellt sein. + +[CUB4_07:CUBAN4] +Zum Lösungsmittel geht's hintenrum, Amigo. + +[CUB4_08:CUBAN4] +Hola, Amigos. + +[CUB4_09:CUBAN4] +Bueno. Haitian Putas. Muerte. + +[CUB4_10:CUBAN4] +Vamos. + +[CUB4_11:CUBAN4] +Genau, vamos. + +[CUB4_12:CUBAN4] +Hey, wir brauchen ein haitianisches Gang-Auto! + +[CUB4_13:CUBAN4] +Oye, suchen wir unsere Muchachos! + +[CUB4_14:CUBAN4] +Folge meinen Compadres. + +[CUB4_15:CUBAN4] +Ok, nur immer rein... + +[CUB4_16:CUBAN4] +Ich lege die Bombe. Gib mir Deckung. + +[CUB4_17:CUBAN4] +RENN! + +[CUB4_18:CUBAN4] +Mann, das ist ein schönes Viertel hier... + +[CUB4_19:CUBAN4] +Das ist doch eine Müllhalde, Mann. + +[CUB4_20:CUBAN4] +Ich kannte mal eine schöne Frau, die hat hier gewohnt. + +[CUB4_21:CUBAN4] +Die machen gute Pizza hier. + +[CUB4_22:CUBAN4] +Hey, Mann! Du fährst wie ein Verrückter! + +[CUB4_23:CUBAN4] +Hast du dich verfahren, Mann? + +[CUB4_24:CUBAN4] +Du hast Pepe vergessen, hole ihn. + +[CUB4_03:CUBAN4] +~g~Bleib im Wagen, bis er sicher auf dem Gelände geparkt ist. + +[CUB4_26:CUBAN4] +~g~Nimm dir Pepe, fahr Richtung Norden nach Little Haiti und klaue einen Voodoo. + +[CUB4_27:CUBAN4] +~g~Triff dich mit Rico und den anderen Kubanern. + +[CUB4_28:CUBAN4] +~g~Triff dich mit den anderen Kubanern bei der haitianischen Drogenfabrik. + +[CUB4_29:CUBAN4] +~g~Gehe in jede der Markierungen, um an dieser Stelle eine Bombe zu legen. + +[CUB4_30:CUBAN4] +~g~Wenn alle drei Bomben gelegt sind, entferne dich von der Fabrik bevor sie hochgeht. + +[CUB4_31:CUBAN4] +~g~Mach, dass du von der Fabrik wegkommst!! + +[CUB4_32:CUBAN4] +~g~Park den Wagen an der im Radar markierten Stelle und steig aus. + +[CUB4_06:CUBAN4] +~r~Du bist nicht weit genug von der Basis weggekommen, wir mussten die Detonation abbrechen! + +{=================================== MISSION TABLE FINALE ===================================} + +[FIN1_01:FINALE] +Was ist los? + +[FIN1_02:FINALE] +Tommy! Ah, gut, gut. Hör zu. Hör zu... + +[FIN1_03:FINALE] +Ich mag Fische. Ich liebe Fische. + +[FIN1_04:FINALE] +Ich liebe sie als Haustiere oder auch lecker zubereitet. + +[FIN1_05:FINALE] +Aber ich liebe sie nicht so sehr, dass ich als Fischfutter im Hafen enden will. + +[FIN1_06:FINALE] +Und jetzt kommen auf einmal deine italienischen Brüder an und wollen mir Zementschuhe verpassen. Und ich... + +[FIN1_07:FINALE] +Halt den Mund, Ken. Setz dich. + +[FIN1_08:FINALE] +Lance, was läuft hier, verdammt? + +[FIN1_09:FINALE] +Deine Freunde aus dem Norden, Tommy. Die sind nicht sehr froh, dass du ihren Mann erledigt hast. + +[FIN1_10:FINALE] +Sie kommen heute, um nach dem Rechten zu sehen. + +[FIN1_11:FINALE] +Sie haben länger gebraucht, als ich dachte... + +[FIN1_12:FINALE] +Jungs, wir müssen denen ein für alle Mal klar machen, dass das mein Laden ist. MEINER! + +[FIN1_13:FINALE] +Ken, hol die erste Ladung von dem Falschgeld und pack 20 Mios in Aktenkoffer. + +[FIN1_14:FINALE] +Lance, du trommelst die anderen Jungs zusammen... + +[FIN2_01:FINALE] +Tommy! + +[FIN2_02:FINALE] +Was ist? Keine Umarmung für deinen alten Freund? + +[FIN2_03:FINALE] +Ich war 15 Jahre weg vom Fenster, + +[FIN2_04:FINALE] +bin nicht mehr auf dem Laufenden, was Manieren angeht. + +[FIN2_05:FINALE] +Immer Wut im Bauch, hä, Tommy? + +[FIN2_06:FINALE] +Ich sag's ja, dein Temperament wird dir nochmal schlecht bekommen. + +[FIN2_07:FINALE] +In den Koffern sind 20 Millionen... + +[FIN2_08:FINALE] +Wie viele waren es denn? Zehn? Nein, elf Männer. + +[FIN2_09:FINALE] +So kommt man zu dem Spitznamen 'Harwood-Butcher'! Hehehe! + +[FIN2_10:FINALE] +Ich sollte EINEN Mann für dich erledigen! Die wussten, dass ich komme, Sonny... + +[FIN2_11:FINALE] +Wie redest du denn mit mir? + +[FIN2_12:FINALE] +Wie kommst du darauf, mich für diesen unglücklichen Zufall verantwortlich zu machen? + +[FIN2_13:FINALE] +Nimm einfach das Geld... + +[FIN2_14:FINALE] +Holt das Geld! + +[FIN2_15:FINALE] +Weißt du, Tommy, ich hab für dich getan, was nur ging. Himmel und Hölle in Bewegung gesetzt. + +[FIN2_16:FINALE] +Ich war dein Freund. Ich dachte, du nimmst Vernunft an. Kapierst, was gut fürs Geschäft ist. + +[FIN2_17:FINALE] +Ich hab dir vertraut, Tommy, und du hast mich enttäuscht. + +[FIN2_18:FINALE] +Aber wenigstens einer in deiner mickrigen Organisation weiß, wie man Geschäfte macht. + +[FIN2_19:FINALE] +Stimmt's, Lance? + +[FIN2_20:FINALE] +Sorry, Tommy. So läuft's in Vice City. So läuft das Geschäft. + +[FIN2_21:FINALE] +Du hast uns verraten... + +[FIN2_22:FINALE] +Nein, ich hab DICH verraten, Tommy, nur DICH. + +[FIN2_23:FINALE] +Die echten Piepen sind oben im Safe. + +[FIN2_24:FINALE] +Tommy, wie hast du dir das denn vorgestellt? + +[FIN2_25:FINALE] +Dachtest du, ich lass mich mit Blüten abspeisen? + +[FIN2_26:FINALE] +Dass ich den Schwanz einziehe und abhaue, um nicht das Gesicht zu verlieren? + +[FIN2_27:FINALE] +Nein. + +[FIN2_28:FINALE] +Ich wollte dich nur noch ein bisschen ärgern, bevor ich dich fertig mache. + +[FIN3_01:FINALE] +Tommy? + +[FIN3_02:FINALE] +Oh Gott, Tommy! Was ist passiert? + +[FIN3_03:FINALE] +Wonach sieht's denn aus? + +[FIN3_04:FINALE] +Sieht aus, als wär dein Anzug ruiniert! + +[FIN3_05:FINALE] +Und das war ein wunderbarer Anzug! Tommy, Herrgott, was ist passiert? + +[FIN3_06:FINALE] +Kleine Meinungsverschiedenheit mit einem Geschäftsfreund. Wie das so ist. + +[FIN3_07:FINALE] +Wenn ich eine Meinungsverschiedenheit mit einem Geschäftsfreund habe, schick ich ihm einen bösen Brief. + +[FIN3_08:FINALE] +Oder ich pinkle ihm in den Briefkasten, aber ich fang nicht den 3. Weltkrieg an. + +[FIN3_09:FINALE] +Vielleicht solltest du mal zu meinem Therapeuten gehen... + +[FIN3_10:FINALE] +Dieser Dreckskerl von Lance... + +[FIN3_11:FINALE] +Tommy, ich konnte den Kerl ja nie leiden. + +[FIN3_12:FINALE] +Er ist neurotisch, unsicher, selbstsüchtig - Er ist ein Arschloch! + +[FIN3_13:FINALE] +Gut, dass du ihn fertig gemacht hast! + +[FIN3_14:FINALE] +Ich glaub auch nicht, dass wir nochmal Ärger mit denen aus dem Norden kriegen... + +[FIN3_15:FINALE] +Die aus dem Norden gibt's nämlich nicht mehr. + +[FIN3_16:FINALE] +Gibt nur noch die im Süden. + +[FIN3_17:FINALE] +Moment, verstehe ich dich richtig, Tommy, Heißt das...? + +[FIN3_18:FINALE] +Na, was meinst du, was das heißt? + +[FIN3_19:FINALE] +Dass wir jetzt die Herren im Haus sind... ich meine, dass DU der Herr im Haus bist. Oh, Tommy... + +[FIN3_20:FINALE] +Weißt du, Ken, das könnte der Beginn einer wunderbaren Geschäftsbeziehung sein... + +[FIN3_21:FINALE] +Schließlich bist du ein hinterlistiger, mieser kleiner Dieb... + +[FIN3_22:FINALE] +und ich bin ein verurteilter Psychopath und Dealer. + +[FIN3_23:FINALE] +Ich weiß. Ist das nicht wunderbar? + +[FIN_B1:FINALE] +~g~Erledige den Verräter ~y~Vance~g~. + +[FIN_B2:FINALE] +~g~Erledige ~p~Sonny~g~, um die Sache ein für alle Mal zuende zu bringen. + +[FIN_B3:FINALE] +~g~Die Mafia will dein Geld stehlen. Verteidige den Safe. + +[FIN_B4:FINALE] +~g~Du hältst nicht mehr lange durch. Hol dir unten ein wenig ~w~Energie~g~. + +[FIN_B5:FINALE] +~g~Die Mafia stiehlt dein Geld. Verteidige den ~c~Safe. + +[FIN_B7:FINALE] +~r~Die Mafia hat dein gesamtes Geld gestohlen. + +[DEFSAFE:FINALE] +~g~Geh zurück zum Safe und verteidige ihn. + +{=================================== MISSION TABLE FIRETRK ===================================} + +[F_PASS1:FIRETRK] +Feuer gelöscht! + +[F_FAIL2:FIRETRK] +~r~Du kommst zu spät! + +[F_CANC:FIRETRK] +~r~Feuerwehr-Mission abgebrochen! + +[F_EXTIN:FIRETRK] +FEUER: + +[F_START:FIRETRK] +~g~Brennendes Fahrzeug in der Gegend von ~a~ gemeldet. Lösche den Brand. + +[SIREN_1:FIRETRK] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_2:FIRETRK] +Um die Sirenen dieses Fahrzeugs einzuschalten, drücke die ~h~~k~~VEHICLE_HORN~~w~. + +[FIREPRO:FIRETRK] +Feuerwehr-Mission Level 12 abgeschlossen: Du bist jetzt absolut feuerfest!! + +[F_FAIL1:FIRETRK] +Feuerwehr-Mission beendet. + +[F_STAR1:FIRETRK] +~g~Brennende Fahrzeuge in der Gegend von ~a~ gemeldet. Lösche den Brand. + +[SPRAY_4:FIRETRK] { reVC update } +Benutze die ~h~~k~~VEHICLE_FIREWEAPON~~w~-Taste, um die Wasserkanone abzufeuern und den ~h~~k~~VEHICLE_TURRETLEFT~~w~ und ~h~~k~~VEHICLE_TURRETRIGHT~~w~, um mit der Wasserkanone zu zielen. + +[SPRAY_1:FIRETRK] { reVC update } +Benutze die ~h~~k~~VEHICLE_FIREWEAPON~~w~-Taste, um die Wasserkanone abzufeuern und den ~h~~k~~VEHICLE_TURRETLEFT~~w~ und ~h~~k~~VEHICLE_TURRETRIGHT~~w~, um mit der Wasserkanone zu zielen. + +{=================================== MISSION TABLE GENERA1 ===================================} + +[GEN1_I:GENERA1] +Für diesen kleinen Gefallen werde ich Sie belohnen. Danach suchen wir Ihr Geld gemeinsam. + +[GEN1_A:GENERA1] +Mr. Vercetti! + +[GEN1_B:GENERA1] +Colonel. + +[GEN1_D:GENERA1] +Nein danke. + +[GEN1_E:GENERA1] +Es ist mir peinlich, aber es scheint, unser Problem ist zum Teil auf das lose Mundwerk einer mir vertrauten Person zurückzuführen. + +[GEN1_F:GENERA1] +Seit Jahren schleppe ich Gonzalez mit, aber nun erreicht seine Unfähigkeit einen neuen Höhepunkt. + +[GEN1_G:GENERA1] +Es ist nur gerecht, wenn Sie Gonzalez erledigen. + +[GEN1_H:GENERA1] +War er es? Mir geht es in erster Linie um das Geld. + +[GEN1_J:GENERA1] +Er ist in seinem Penthouse, vermutlich halb betrunken. Nehmen Sie das hier. + +[GEN1_06:GENERA1] +Er hat eine Kettensäge!! + +[GEN1_07:GENERA1] +Bleib mir vom Leib, du elender Mistkerl! + +[GEN1_08:GENERA1] +Gütiger Himmel, mein Leben ist ruiniert, und mein Aussehen dazu! + +[GEN1_10:GENERA1] +Ich werd dir dein Mundwerk stopfen! + +[GEN1_11:GENERA1] +Bleib stehen, du Fettsack! + +[GEN1_12:GENERA1] +Bleib stehen, und es geht ganz schnell! + +[GEN1_13:GENERA1] +Hör auf zu jammern, das interessiert keinen, Fettsack! + +[GEN1_C:GENERA1] +Danke für Ihr Kommen. Setzen Sie sich. Hummer? + +[GEN1_04:GENERA1] +~g~Benutze den Eingang, um Zugang zu Gonzalez' Dachwohnung zu erhalten. + +[GEN1_05:GENERA1] +~g~Erledige Gonzalez! + +[GEN1_09:GENERA1] +Ich zahle dir das Doppelte, Tommy, das DOPPELTE! + +[GEN1_17:GENERA1] +~g~Gonzalez versucht zu fliehen! Folge ihm durch die Türen und erledige ihn. + +[GEN1_18:GENERA1] +~r~Gonzalez hat das Polizeirevier sicher erreicht! + +[GEN1_19:GENERA1] +~g~Die Polizei von Vice City ist hinter dir her! + +[GEN1_20:GENERA1] +~g~Steig in ein Fahrzeug. + +[GEN1_21:GENERA1] +~g~Begib dich zur~h~ Pay 'N' Spray-Lackiererei~g~ in~h~ Vice Point~g~. + +[GEN1_22:GENERA1] +~g~Bring dein Fahrzeug in die Lackiererei, um deinen ~h~Fahndungslevel~g~ loszuwerden und das Fahrzeug zu ~h~reparieren ~g~und ~h~umzuspritzen~g~. Kosten - ~h~$100~g~. Diesmal gratis. + +[GEN1_01:GENERA1] +Um im Laufen eine Nahkampfattacke vorzubereiten, die~o~ |-Taste~w~ gedrückt halten. + +[GEN1_02:GENERA1] +Um im Laufen eine Nahkampfattacke vorzubereiten, die~x~ /-Taste~w~ gedrückt halten. + +[GEN1_03:GENERA1] +Um im Laufen eine Nahkampfattacke vorzubereiten, die~h~ R1-Taste~w~ gedrückt halten. + +[GEN1_14:GENERA1] +Lass die~h~ ~k~~PED_FIREWEAPON~~w~ los, um die Attacke auszuführen. + +[GEN1_15:GENERA1] +Lasse die~h~ ~k~~PED_FIREWEAPON~~w~ los, um die Attacke auszuführen. + +[GEN1_16:GENERA1] +Lasse die~h~ ~k~~PED_FIREWEAPON~~w~ los, um die Attacke auszuführen. + +[GEN1_23:GENERA1] +~g~Geh durch die Türen zurück ins Erdgeschoss. + +{=================================== MISSION TABLE GENERA2 ===================================} + +[COL2_A:GENERA2] +Tommy! Setzen Sie sich zu mir. + +[COL2_B:GENERA2] +Sieht das nicht köstlich aus? Tapirschnauze? + +[COL2_C:GENERA2] +Äh... Nein. Nein, danke. + +[COL2_D:GENERA2] +Tommy, Sie sind wie eine Pampas-Brise, die mich vom Gestank der Korruption befreit hat. + +[COL2_E:GENERA2] +Natürlich muss ich so tun, als trauere ich um ihn und muss wie immer meine Arbeit machen. + +[COL2_F:GENERA2] +Das bringt mich meinem Geld nicht näher... + +[COL2_G:GENERA2] +Tommy, mein Freund, Sie sind hier nicht in Liberty. Hier regeln wir Dinge anders. + +[COL2_H:GENERA2] +Ich werde weiter nachforschen, zunächst hätte ich aber ein lukratives Geschäft abzuschließen. + +[COL2_I:GENERA2] +Freunden tu ich gern einen Gefallen, Cortez... + +[COL2_J:GENERA2] +Sie sind ein guter Freund, Tommy. Ich wusste, ich kann auf Sie zählen. + +[COL2_K:GENERA2] +Sie müssen sich mit einem Kurier treffen, der wertvolle 'Technologie' für mich beschafft hat. + +[COL2_1:GENERA2] +Die Regen ist sich sehr nass um diese Jahreszeit... + +[COL2_2:GENERA2] +Was? + +[COL2_3:GENERA2] +Äh, comment? + +[COL2_4:GENERA2] +Hören Sie, Cortez schickt mich. Geben Sie mir die verdammten Mikrochips. + +[COL2_5:GENERA2] +Oh... ok. + +[COL2_B1:GENERA2] +~g~Triff dich mit dem Kurier im Einkaufszentrum. + +[COL2_B2:GENERA2] +~g~Der Kurier flieht mit den Lenkwaffen-Chips. Lass ihn nicht entkommen! + +[COL2_B3:GENERA2] +~g~Bring die Lenkwaffen-Chips zum Colonel. + +[COL2_F1:GENERA2] +~r~Du hast die Kontaktperson erledigt! + +[COL2_F2:GENERA2] +~r~Der Kurier ist hinüber. Schnapp dir die Lenkwaffen-Chips. + +[COL2_6A:GENERA2] +Keine Bewegung, amerikanisches Imperialistenschwein! Das ist Eigentum des französischen Staates. Her damit! + +[BLIPHLP:GENERA2] +Das Radar zeigt ein nach oben zeigendes Dreieck, das bedeutet, dass das Ziel sich in größerer Höhe befindet als der Spieler. + +[COL2_F3:GENERA2] +~r~Die Lenkwaffen-Chips liegen auf dem Meeresgrund. + +[COL2_F4:GENERA2] +~r~Der Kurier ist entkommen! Du hast dir die Chips durch die Lappen gehen lassen. + +{=================================== MISSION TABLE GENERA3 ===================================} + +[GEN3_49:GENERA3] +Lance' Gesundheitszustand: + +[GEN3_A:GENERA3] +Thomas, danke, dass Sie kommen. + +[GEN3_B:GENERA3] +Verzeihen Sie, wenn ich gleich zur Sache komme. + +[GEN3_C:GENERA3] +Diaz bat mich, eine kleinere geschäftliche Transaktion zu überwachen. + +[GEN3_D:GENERA3] +Wird hoffentlich besser laufen als die letzte, hah? + +[GEN3_E:GENERA3] +Darum habe ich an Sie gedacht, mein Freund. + +[GEN3_F:GENERA3] +Ich habe etwas zu Ihrem Schutz beim Parkhaus deponiert. + +[GEN3_G:GENERA3] +Holen Sie es ab. Und dann bewachen Sie Diaz' Männer bei dem Deal. + +[GEN3_H:GENERA3] +Danke, Amigo. + +[GEN3_1:GENERA3] +Reißt sich alles selbst unter den Nagel, wie ich sehe. + +[GEN3_2:GENERA3] +Sag mal, ist das alles, was du kannst, mir dauernd nachzuschnüffeln? Komm mit und zeig mir, dass du was drauf hast. + +[GEN3_3:GENERA3] +Könnte ich machen. Ich heiße übrigens Lance. + +[GEN3_5:GENERA3] +Du musst Cortez' neuer Mann sein. + +[GEN3_6:GENERA3] +Bis ich was besseres finde... + +[GEN3_7:GENERA3] +Sie müssen bald hier sein. Wir sollten uns gute Beobachtungsposten suchen. + +[GEN3_8:GENERA3] +Ok! Ich nehm den Balkon, du kletterst auf das Dach drüben im Hof. + +[GEN3_9:GENERA3] +Ich lebe! Idioten! Und das nur wegen dir! Wie heißt du? + +[GEN3_10:GENERA3] +Tommy. + +[GEN3_11:GENERA3] +Wir sehen uns bald wieder, glaube ich! + +[GEN3_12:GENERA3] +Wo steckt denn Lance? Shit... + +[GEN3_14:GENERA3] +Tommy! Ich brauch Hilfe! + +[GEN3_15:GENERA3] +Keine Sorge, ich hab alles im Griff! + +[GEN3_16:GENERA3] +Diaz' Männer werden umgemäht! + +[GEN3_19:GENERA3] +~g~Haitianer! Sie greifen an! Beschütze Diaz! + +[GEN3_20:GENERA3] +~g~Begib dich zum Parkhaus. Hol dir die Waffe, die der Colonel dort für dich deponiert hat. + +[GEN3_22:GENERA3] +Diaz' Gesundheitszustand: + +[GEN3_23:GENERA3] +~g~Du hast Lance vergessen. Hole ihn! + +[GEN3_25:GENERA3] +~r~Lance hat's erwischt! + +[GEN3_28:GENERA3] +~g~Bring den Aktenkoffer zu Diaz zurück. + +[GEN3_29:GENERA3] +~g~Hol dir den Aktenkoffer und bringe ihn zu Diaz zurück. + +[GEN3_30:GENERA3] +~r~Er ist mit dem Geld entwischt! Dafür macht Diaz dich kalt! + +[GEN3_33:GENERA3] +~r~Du sollst Diaz und seine Männer bewachen, nicht beschießen! + +[GEN3_34:GENERA3] +~r~Es gibt keinen Deal, wenn du die Kubaner erledigst! + +[GEN3_35:GENERA3] +~g~Er hat Diaz' Geld gestohlen! + +[GEN3_36:GENERA3] +~g~Schnapp dir das Motorrad, stelle ihn und hol Diaz' Geld zurück! + +[GEN3_37:GENERA3] +~g~. Die Kubaner kommen. Überwache den Deal. Pass auf Diaz und Lance auf. + +[GEN3_38:GENERA3] +~r~Diaz hat's erwischt! Du hast versagt! + +[GEN3_39:GENERA3] +~g~Begib dich zu deinem Beobachtungsposten die Treppe hoch. + +[GEN3_44:GENERA3] +~g~Begib dich mit Lance zum Übergabeort und pass auf Diaz auf. + +[GEN3_45:GENERA3] +Sie müssen bald hier sein. Wir sollten uns gute Beobachtungsposten suchen. + +[GEN3_40:GENERA3] { reVC update } +Um auf einem ~h~Motorrad ~w~sitzend ~h~geradeaus zu feuern~w~, drücke die ~h~~k~~VEHICLE_FIREWEAPON~. + +[GEN3_41:GENERA3] { reVC update } +Um auf einem ~h~Motorrad ~w~sitzend ~h~geradeaus zu feuern~w~, drücke die ~h~~k~~VEHICLE_FIREWEAPON~. + +[GEN3_46:GENERA3] +Scheiße! + +[GEN3_47:GENERA3] +Tommy! + +[GEN3_48:GENERA3] +Verdammt! + +[GEN3_50:GENERA3] +~r~Du hast Diaz' Geld verloren! Versuch das nächste Mal, das Geld nicht zu vernichten! + +[GEN3_51:GENERA3] +Noch mehr verdammte Haitianer in einem beschissenen Van! + +[GEN3_54:GENERA3] +Steht nicht rum, ihr Idioten! Schnappt euch diesen haitianischen Mistkerl! + +[GEN3_55:GENERA3] +Tommy! Ich bleibe hier und passe auf Diaz auf! + +[GEN3_18:GENERA3] +~g~Die Kubaner kommen. Bleib in Diaz' Nähe. Überwache den Deal. Pass auf Diaz und Lance auf. + +[GEN3_56:GENERA3] +~r~Diaz ist hinüber, er ist in einen Hinterhalt geraten. Pass das nächste Mal auf ihn auf! + +[GEN3_57:GENERA3] +Die Kruger ist ein Sturmgewehr, das einem erlaubt, in der subjektiven Kamera-Einstellung manuell zu zielen. + +[GEN3_58:GENERA3] +Halte die~h~ R1~w~-Taste gedrückt, um mit dem Sturmgewehr zu ~h~zielen~w~. + +[GEN3_59:GENERA3] +Halte die~h~ L1~w~-Taste gedrückt, um mit dem Sturmgewehr zu ~h~zielen~w~. + +[GEN3_60:GENERA3] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~-Taste, um das Sturmgewehr ~h~abzufeuern~w~. + +[GEN3_61:GENERA3] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~-Taste, um das Sturmgewehr ~h~abzufeuern~w~. + +[GEN3_62:GENERA3] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~-Taste, um das Sturmgewehr ~h~abzufeuern~w~. + +[GEN3_63:GENERA3] +Auf~h~ Motorrädern ~w~kann man nicht nur im Vorbeifahren seitlich auf Ziele schießen, man kann auch ~h~geradeaus feuern~w~. + +[GEN3_64:GENERA3] { reVC update } +Um auf einem Motorrad sitzend geradeaus zu feuern, drücke die ~h~~k~~VEHICLE_FIREWEAPON~~w~-Taste. + +[GEN3_65:GENERA3] { reVC update } +Um auf einem Motorrad sitzend geradeaus zu feuern, drücke die ~h~~k~~VEHICLE_FIREWEAPON~~w~-Taste. + +[GEN3_66:GENERA3] { reVC update } +Um auf einem Motorrad sitzend geradeaus zu feuern, drücke die ~h~~k~~VEHICLE_FIREWEAPON~~w~-Taste. + +[GEN3_67:GENERA3] +Du brauchst eine Maschinenpistole, um auf einem Motorrad sitzend geradeaus zu feuern. + +[GEN3_53:GENERA3] +MEIN GELD! + +[GEN3_52:GENERA3] +Diese Haitianer denken sie könnten RICARDO DIAZ stürzen!?! + +{=================================== MISSION TABLE GENERA4 ===================================} + +[DETON:GENERA4] +DETONATION: + +[COL4_3:GENERA4] +KONVOI HALT! + +[COL4_6:GENERA4] +WIR STEHEN UNTER FEINDLICHEM BESCHUSS! + +[COL4_7:GENERA4] +Weg da von dem Panzer, Zivilist! + +[COL4_8:GENERA4] +ICH SAGTE, WEG DA! SOFORT! + +[COL4_9:GENERA4] +AUF GEFECHTSPOSITIONEN! + +[COL4_11:GENERA4] +Schaffen Sie den Zivilisten aus dem Weg, Soldat! - Sir, zu Befehl, Sir! + +[COL4_12:GENERA4] +Zivilist im PANZER! HALTET IHN AUF! + +[COL4_13:GENERA4] +Dies ist ein Militärkonvoi, machen Sie den Weg frei! + +[COL4_14:GENERA4] +Erledigen sie ihn, Soldat. + +[COL4_15:GENERA4] +Schaffen Sie das Zivilfahrzeug aus dem Weg! -Fahrzeug wird weggeschafft, Sir! + +[COL4_17:GENERA4] +OK, EINHEIT WEITER! + +[COL4_18:GENERA4] +Da ist jemand auf dem Panzer, Sir! + +[COL4_19:GENERA4] +Gehen Sie ein paar Donuts holen, Soldat! - Zu Befehl, Sir! + +[COL4_20:GENERA4] +Ziel erfasst, Sir. + +[COL4_21:GENERA4] +HECKENSCHÜTZE! + +[COL4_22:GENERA4] +Ich seh zu, dass ich hier wegkomme! + +[COL4_23:GENERA4] +Einsatz durchgeführt! Einheit, wegtreten! - Gehen wir ein paar Donuts essen. + +[COL4_24:GENERA4] +Sicherheitsmechanismus Delta India Echo aktiviert. Selbstzerstörung des Fahrzeugs eingeleitet! + +[COL4_26:GENERA4] +Mach dein Testament, dreckiger Kommunist! + +[COL4_B2:GENERA4] +~r~Der Panzer ist wohlbehalten an seinem Ziel angekommen! + +[COL4_B5:GENERA4] +~r~Der Panzer ist zerstört worden! + +[COL4_01:GENERA4] +Diaz war sehr angetan und würde Sie gerne wiedersehen. + +[COL4_02:GENERA4] +Ist das eine gute Nachricht? + +[COL4_03:GENERA4] +Aber sicher! Obwohl mich der Verdacht beschleicht, dass Diaz für unsere Verluste verantwortlich war... + +[COL4_04:GENERA4] +Wie kommen Sie darauf? + +[COL4_05:GENERA4] +Man erhebt keine Anschuldigungen gegen einen Mann wie Diaz. Ich habe nur laut nachgedacht. + +[COL4_06:GENERA4] +Egal. Ich habe einen Vorschlag, der lukrativ für Sie sein könnte... + +[COL4_07:GENERA4] +Ich habe keine Zeit für weitere Aufträge, Cortez. + +[COL4_08:GENERA4] +Ein Mann mit solch bedrohlichen Schulden sollte doch um jede Verdienstmöglichkeit dankbar sein. Hören Sie mich wenigstens an. + +[COL4_09:GENERA4] +Na gut... + +[COL410:GENERA4] +Ich habe einen Käufer für ein 'Militärgerät', das durch die Stadt transportiert wird. Beschaffen Sie es! + +[COL411:GENERA4] +Wenn Sie es haben, rufen Sie mich unverzüglich an... + +[COL4_B4:GENERA4] +~g~Der Panzer ist abgeschlossen. Lass dir etwas einfallen, um die Besatzung herauszulocken. + +[COL4_1:GENERA4] +Was ist mit dem Kanonier? - Weiß nicht, Sir! + +[COL4_4:GENERA4] +Los, sehen Sie nach, Soldat! - Zu Befehl, Sir! + +[COL4_B1:GENERA4] +~g~Besorge das militärische Fahrzeug, das durch die Stadt gefahren wird. + +[COL4_B3:GENERA4] +~g~Liefere den Panzer in der Garage des Colonels ab, bevor er sich selbst zerstört. + +[COL4_B6:GENERA4] +~g~Finde einen Weg, den Panzer zu klauen! + +[COL4_B7:GENERA4] +~g~Fahr den Panzer in die Garage. + +[COL4_B8:GENERA4] +~g~Steig aus dem Panzer und verlasse die Garage. + +{=================================== MISSION TABLE GENERA5 ===================================} + +[COL5A_1:GENERA5] +Die Lage erfordert ein eiliges Verschwinden, Amigo. + +[COL5A_2:GENERA5] +Was gibt's für ein Problem? + +[COL5A_3:GENERA5] +Die Franzosen wollen ihre Lenkwaffen-Chips wieder und nach dem letzten Zwischenfall + +[COL5A_4:GENERA5] +zieht es mich in sicherere Gefilde. + +[COL5A_5:GENERA5] +Wäre es nicht sicherer zu fliegen? + +[COL5A_6:GENERA5] +Ich wäre erledigt, bevor ich eingecheckt hätte. Außerdem muss ich Ware außer Landes schaffen. + +[COL5A_7:GENERA5] +Brauchen Sie noch einen Bodyguard? + +[COL5A_8:GENERA5] +Sie, mein Freund, sind zehn Bodyguards wert. Hahaha. + +[COL5B_1:GENERA5] +Thomas, Sie haben mich beschützt und mir treu gedient. + +[COL5B_2:GENERA5] +Aber jetzt müssen Sie uns verlassen, ehe wir das offene Meer erreichen. + +[COL5B_4:GENERA5] +Danke, Colonel. + +[COL5B_5:GENERA5] +Eine Bitte noch. Könnten Sie ein Auge auf Mercedes haben, solange ich weg bin? + +[COL5B_6:GENERA5] +Ich glaub zwar, sie kann auf sich selbst aufpassen, aber klar. + +[COL5B_7:GENERA5] +Danke, mein Freund. Bis zu meiner Rückkehr. + +[COL5B_8:GENERA5] +Adios, Amigo. + +[COL5_7:GENERA5] +Hören Sie auf, auf mich zu schießen! + +[COL5_9:GENERA5] +Tommy, die sollen aufhören, auf mich zu schießen! + +[COL5_10:GENERA5] +Ich genieße diplomatische Immunität. + +[COL5_11:GENERA5] +Nicht schießen, ich bin ein Colonel! + +[COL5_12:GENERA5] +Thomas, machen Sie sie fertig. Mein Land wird es Ihnen danken. + +[COL5_13:GENERA5] +Tommy, wir werden von den Franzosen überrannt! + +[COL5_14:GENERA5] +Tommy, wo ich hinsehe, überall Franzosen! Wie ich es hasse! + +[COL5_15:GENERA5] +Tommy, alles in Ordnung? + +[COL5_16:GENERA5] +Das ist für Piaf und Gainesbourg und für euer dämliches französisches Weißbrot! + +[COL5_1:GENERA5] +Backbord! Backbord! + +[COL5_2:GENERA5] +Sie greifen von Steuerbord an! + +[COL5_3:GENERA5] +Die Brücke da vorn! + +[COL5_4:GENERA5] +Sie haben einen Helikopter! + +[COL5_B1:GENERA5] +~g~Beschütze den Colonel und seine Jacht um jeden Preis. + +[COL5_B2:GENERA5] +~g~Geh nach vorn und räume der Jacht des Colonels den Weg frei. + +[COL5_B3:GENERA5] +~r~Der Colonel ist hinüber! + +[COL5_B4:GENERA5] +~g~Schieße den angreifenden Helikopter vom Himmel. + +[COL5B_3:GENERA5] +Ich werde meine Privatbarkasse zu Wasser lassen. Sie gehört Ihnen, als Ausdruck meiner Dankbarkeit. + +[COL5_B5:GENERA5] +~g~Schieß die Helikopter ab, gefährde nicht die Jacht. + +[COL5_B6:GENERA5] +~g~Du hast keine Munition mehr. Hol dir an der Treppe des Oberdecks Nachschub. + +[COL5_B7:GENERA5] +~g~Du hast nur noch wenig Energie. Hol dir an der Treppe des Oberdecks Nachschub. + +{=================================== MISSION TABLE HAIT1 ===================================} + +[HAM1_A:HAIT1] +Hallo? Hallo? + +[HAM1_B:HAIT1] +Komm rein, mein Lieber, und ruh dich aus. + +[HAM1_C:HAIT1] +Du musst der große böse Mann sein, von dem mein Großvater erzählt hat. + +[HAM1_D:HAIT1] +Er erzählte immer von dir, wenn er zu Besuch kam, + +[HAM1_E:HAIT1] +und von den anderen, die auf dich warten. + +[HAM1_F:HAIT1] +Tja, wir müssen alle mal sterben, aber du... + +[HAM1_G:HAIT1] +....in deiner Haut möchte ich dann nicht stecken. Hehehe! + +[HAM1_H:HAIT1] +Es hieß, ich soll hierher kommen. + +[HAM1_I:HAIT1] +Kannst du sie hören? + +[HAM1_J:HAIT1] +Sie rufen deinen Namen, Junge. Die müssen ziemlich scharf auf dich sein, was? + +[HAM1_K:HAIT1] +Aber wenn du der alten Tante Poulet hilfst, hilft sie dir vielleicht auch. + +[HAM1_L:HAIT1] +Vielleicht kann sie dir danach einen kleinen Talisman schenken. + +[HAM1_M:HAIT1] +Ein bisschen Magie, die den Männern des Gesetzes schlechte Augen macht, hmm? + +[HAM1_N:HAIT1] +Hören Sie, das ist alles sehr, äh... Sie geben mir was? + +[HAM1_O:HAIT1] +Ich...ich...ich glaube, ich bin hier falsch. + +[HAM1_P:HAIT1] +Erweise mir ein paar Gefälligkeiten, Tommy... + +[HAM1_Q:HAIT1] +Die Kubaner, miese, hochnäsige Narren, hmmm, + +[HAM1_R:HAIT1] +haben meine lieben Haiti-Boys sehr geärgert. + +[HAM1_S:HAIT1] +Jetzt haben sie den Polizisten erzählt, wo ich meine Pülverchen versteckt habe. + +[HAM1_T:HAIT1] +Sie denken, das sind Drogen, diese Dummköpfe. + +[HAM1_U:HAIT1] +Sei ein braver Bub, Tommy, und hole Tante Poulet die Pülverchen. + +[HAM1_V:HAIT1] +Ja, ja. Sicher, sicher. + +[HAM1_1:HAIT1] +~g~Die Cops nähern sich dem Zeug. Hol es, bevor sie dort sind. + +[HAM1_2:HAIT1] +~r~Die Cops waren schneller bei dem Zeug! + +[HAM1_3:HAIT1] +~g~Bring das Zeug zum Unterschlupf! + +[HAM1_4:HAIT1] +~g~Gut. Jetzt das nächste! + +[HAM1_6:HAIT1] +~r~Das Zeug wurde vernichtet, du Idiot! + +[HAM1_7:HAIT1] +~g~Die Cops haben das Zeug! Hol es dir wieder, bevor sie weg sind! + +[HAM1_8:HAIT1] +~g~Die Cops sind unterwegs, um das Zeug abzuholen. Beeil dich! + +[HAT_1A:HAIT1] +~g~Keine Bewegung, Freundchen! + +{=================================== MISSION TABLE HAIT2 ===================================} + +[HAT2_B1:HAIT2] +~g~Begib dich zu dem Wagen, in dem die fliegenden Bomben sind. + +[HAT2_B2:HAIT2] +Schalte die Kubaner aus... + +[HAT2_B4:HAIT2] +.... und zerstöre ihre Boote! + +[HAT2_B5:HAIT2] +~g~Die Kubaner hauen ab. Lass sie nicht entkommen! + +[HAT2_B6:HAIT2] +~r~Das ferngesteuerte Flugzeug ist außer Reichweite! + +[HAT2_B7:HAIT2] +~g~Einer der Kubaner flieht in einem Auto. Lass ihn nicht entkommen! + +[HAT2_B8:HAIT2] +~r~Du hast keine ferngesteuerten Flugzeuge mehr! + +[HAT2_B9:HAIT2] +Ferngesteuerte Flugzeuge: + +[HAT2_1:HAIT2] +Oh. Entschuldigung, ich muss die falsche Adresse haben... + +[HAT2_2:HAIT2] +Du kannst gern reinkommen und dich bei einer Tasse Tee ausruhen. + +[HAT2_3:HAIT2] +Hast du etwas für mich, Tommy? + +[HAT2_4:HAIT2] +Ja... + +[HAT2_5:HAIT2] +Das kommt mir so bekannt vor hier. Ein Geruch aus meiner Kindheit - das muss ein Déjà-vu sein... + +[HAT2_6:HAIT2] +Tommy, du kannst was für mich tun, ich werd's dir erklären. Hör gut zu, ja? + +[HAT2_7:HAIT2] +Sie sehen aus wie jemand, den... + +[HAT2_8:HAIT2] +Die Kubaner haben schnelle Boote, mit denen transportieren sie Drogen übers Meer. + +[HAT2_9:HAIT2] +Davon leben sie. + +[HAT2_10:HAIT2] +Mein Neffe hat kleine fliegende Bomben gebaut, um ihnen das Handwerk zu legen. + +[HAT2_11:HAIT2] +Lass ihre Boote in die Luft fliegen. + +[HAT2_12:HAIT2] +Tja, danke für den Tee. + +[HAT2_B3:HAIT2] { reVC update } +Um eine Bombe abzuwerfen, drück die ~h~~k~~VEHICLE_FIREWEAPON~~w~-Taste. ~h~~k~~VEHICLE_ENTER_EXIT~~w~-Taste zum Beenden. + +{=================================== MISSION TABLE HAIT3 ===================================} + +[HAM3_A:HAIT3] +Hallo, hallo, ich, äh, ich suche hier jemanden... + +[HAM3_B:HAIT3] +Du siehst hungrig aus, Tommy. + +[HAM3_C:HAIT3] +Kenne ich Sie? + +[HAM3_D:HAIT3] +Sei jetzt still. + +[HAM3_E:HAIT3] +Eine Gefälligkeit noch, dann lasse ich dich gehen, Tommy. + +[HAM3_F:HAIT3] +Meine Jungs haben die Kubaner zum Kampf gefordert. + +[HAM3_G:HAIT3] +Aber ohne Kanonen. + +[HAM3_H:HAIT3] +Hmm, aber die Kubaner werden ihr blaues Wunder erleben. + +[HAM3_I:HAIT3] +Wenn sie in den Straßen kämpfen, nimmst du dieses Gewehr und räumst auf. + +[HAM3_J:HAIT3] +Keiner sieht dich, keiner hört dich. + +[HAM3_K:HAIT3] +Wenn du das für mich tust, Tommy, dann kommst frei aus meinen Schürzenbändern. + +[HAM3_1:HAIT3] +~g~Wir müssen gewinnen. Werden alle Haitianer erledigt, haben wir verloren. + +[HAM3_3:HAIT3] +~g~Vermutlich werden die Kubaner schummeln. Sei auf der Hut! + +[HAM3_4:HAIT3] +~r~Du wurdest entdeckt! Die Mission ist fehlgeschlagen! + +[HAM3_5:HAIT3] +~g~Du musst die Kubaner aus der Distanz erwischen. Man darf dich nicht sehen. + +[HAM3_8:HAIT3] +~g~Es gibt Verluste unter den Haitianern! Du musst besser zielen! + +[HAM3_7:HAIT3] +~g~Vorsicht! Die Kubaner haben Verstärkung mitgebracht. Schalte sie alle aus!! + +[HAM3_2:HAIT3] +~r~Die Haitianer sind weg vom Fenster! + +[HAM3_L:HAIT3] +Okay, Tantchen... + +{=================================== MISSION TABLE HOTEL ===================================} + +[INTB_A:HOTEL] +Tommy! Tommy, wir haben uns lange nicht gesehen. + +[INTB_B:HOTEL] +Hallo, Sonny. + +[INTB_C:HOTEL] +Ich weiß, ich weiß. Dir kommen vor Rührung die Tränen. + +[INTB_D:HOTEL] +15 Jahre ist es her - dabei kommt's mir vor, als wär's gestern gewesen. + +[INTB_E:HOTEL] +DU hast leicht reden. + +[INTB_F:HOTEL] +Hey, für die Familie in den Knast zu gehen, ist kein Zuckerschlecken, + +[INTB_G:HOTEL] +aber die Familie zeigt sich für sowas erkenntlich, ok? + +[INTB_H:HOTEL] +Also, wie ist der Deal gelaufen - hast du Schnee an der Hand? + +[INTB_I:HOTEL] +Sonny, wir sind reingelegt worden. Der Deal war eine Falle. Harry und Lee sind tot. + +[INTB_J:HOTEL] +Das ist nicht dein Ernst, Tommy. Du hast doch hoffentlich noch das Geld. + +[INTB_K:HOTEL] +Nein, Sonny... Ich hab das Geld nicht mehr. + +[INTB_L:HOTEL] +Das war mein Geld, Tommy. MEIN GELD! + +[INTB_M:HOTEL] +Versuch bloß nicht, mich reinzulegen, Tommy. Mich legt man nicht rein, das weißt du! + +[INTB_N:HOTEL] +Warte, Sonny. + +[INTB_O:HOTEL] +Du hast mein Wort darauf, dass ich dir dein Geld wiederbeschaffe. Und die Drogen. + +[INTB_P:HOTEL] +Und ich liefere dir die Kerle, die dahinterstecken. + +[INTB_Q:HOTEL] +Das weiß ich doch. Du bist kein Idiot, Tommy, aber ich warne dich - ich bin auch keiner. + +[INTB_R:HOTEL] +Wenn du's nicht wärst - ein anderer wär längst fällig! + +[INTB_S:HOTEL] +Aber uns beide verbindet eine alte Freundschaft. Ich lasse dich das regeln. + +[INTB_T:HOTEL] +Sonny, du hast mein Ehrenwort. + +[INTB_U:HOTEL] +Du hörst von mir. + +{=================================== MISSION TABLE ICECRE1 ===================================} + +[ICC1_1:ICECRE1] +~g~Benutze deinen Eis-Wagen, um in Vice City Drogen zu verkaufen. + +[ICC1_2:ICECRE1] +~g~Parke den Eis-Wagen und drücke ~h~~k~~VEHICLE_HORN~~w~, um den Eiscreme-Jingle abzuspielen, damit deine Kunden wissen, dass du Ware zu verkaufen hast. + +[ICC1_3:ICECRE1] +~g~Für jede Transaktion bekommst du Geld. Aber je mehr Transaktionen du tätigst, desto stärker wird die Polizei auf dich aufmerksam. + +[ICC1_4:ICECRE1] +~g~In dieser Gegend sind keine Kunden. Versuche es woanders. + +[ICC1_5:ICECRE1] +Getätigte Deals: + +[ICC1_6:ICECRE1] +~g~Nimm den Mr. Whopee, um in Vice City Cherry Popper-Produkte zu vertreiben. + +[ICC1_7:ICECRE1] +~g~Für jede Transaktion bekommst du Geld. Aber je mehr Transaktionen du tätigst, desto stärker wird die Polizei auf dich aufmerksam. + +[ICC1_8:ICECRE1] +~g~Um eine Transaktion zu tätigen, ~h~parke deinen Wagen ~g~und drücke die ~h~~k~~VEHICLE_HORN~~g~, um den Eiscreme-Jingle abzuspielen, damit deine Kunden wissen, dass du Ware zu verkaufen hast. + +[ICC1_9:ICECRE1] +~g~Andere Gangs werden es nicht gern sehen, dass du in ihrem Revier Geschäfte machst, du musst also mit Feindseligkeiten rechnen. + +[ICC1_10:ICECRE1] +~g~Du hast ~1~ Deals getätigt! + +[ICC1_11:ICECRE1] +~g~Du hast ~1~ Deal getätigt. + +[ICC1_12:ICECRE1] +OBJEKT ERWORBEN! + +[ICC1_13:ICECRE1] +~r~Du hast keine Deals getätigt! + +[ICC1_14:ICECRE1] +EISCREME-MISSIONEN ERFÜLLT + +[ICC1_15:ICECRE1] +~g~Die Eiscremefabrik generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +[ICC1_16:ICECRE1] +~g~Nimm den Mr. Whoopee, um in Vice City Cherry Popper-Produkte zu vertreiben. + +[ICE_AT1:ICECRE1] +EISCREMEFABRIK-MISSIONEN ERFÜLLT + +[ICE_AT2:ICECRE1] +~g~Die Cherry Popper-Fabrik generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +[ICC1_17:ICECRE1] +Stoff-Auslieferungs-Mission beendet + +[ICC1_18:ICECRE1] +Eiscremeverkauf insgesamt: $~1~ + +[ICC1_19:ICECRE1] +Insgesamt getätigte Deals: ~1~ + +{=================================== MISSION TABLE ICECUT ===================================} + +[ICC1_M:ICECUT] +Sie sind schmutzige, verrotzte, verlauste, eklige, sabbernde kleine... + +[ICC1_I:ICECUT] +Ein Baby... ein widerwärtiges, gräßliches, ekelhaftes kleines Gör! + +[ICC1_J:ICECUT] +Mammi liebt dich nicht. Du kleines Stück Scheiße! + +[ICC1_A:ICECUT] +Wer sind Sie? + +[ICC1_B:ICECUT] +Der neue Inhaber dieses Ladens. + +[ICC1_C:ICECUT] +Sind Sie, oder waren Sie je ein Kind? + +[ICC1_D:ICECUT] +Was soll denn das heißen? + +[ICC1_E:ICECUT] +Waren Sie je ein Kind? + +[ICC1_F:ICECUT] +Ja! Immer mit der Ruhe! Was ist denn mit Ihnen los? + +[ICC1_G:ICECUT] +Ich wusste es. Ein Kind. + +[ICC1_H:ICECUT] +Ein schmutziges, stinkendes, verrotztes, verlaustes, nölendes kleines Baby! + +[ICC1_K:ICECUT] +Au! Beruhigen Sie sich doch! + +[ICC1_L:ICECUT] +Ich HASSE Babies. Und ich hasse Kinder. + +[ICC1_N:ICECUT] +Es reicht jetzt! + +[ICC1_P:ICECUT] +Sie stellen doch Softeis her, oder? Das essen doch nur Kinder. + +[ICC1_Q:ICECUT] +Sind Sie komplett irre? + +[ICC1_R:ICECUT] +Erklären Sie mir das mal - warum Kinder glücklich machen, wenn Sie sie hassen? + +[ICC1_S:ICECUT] +Oh, du dämliches, verrotztes, verlaustes- + +[ICC1_T:ICECUT] +Schluss jetzt! + +[ICC1_U:ICECUT] +- Gör! + +[ICC1_V:ICECUT] +Das Eis ist nur Tarnung! + +[ICC1_W:ICECUT] +Wir vertreiben auch andere Waren. Nicht-Milchprodukte. + +[ICC1_X:ICECUT] +Und wenn ich ein Kind sehe, dann weiß ich, was ich mit ihm anfange. + +[ICC1_Y:ICECUT] +Nicht wahr, Kinderchen? Ja, ja. Mammi hat euch gar nicht lieb. + +[ICC1_Z:ICECUT] +Sie HASST euch! + +[ICC1_ZA:ICECUT] +OBJEKT ERWORBEN! + +{=================================== MISSION TABLE INTRO ===================================} + +[INT1_A:INTRO] +Tommy Vercetti...Hah! Shit. + +[INT1_B:INTRO] +Hätte nicht gedacht, dass der noch mal rauskommt. + +[INT1_C:INTRO] +Er hat den Kopf eingezogen. War fast vergessen. + +[INT1_D:INTRO] +Aber bald wird man sich an ihn erinnern. + +[INT1_E:INTRO] +Wenn man ihn wieder durch ihre Viertel tigern sieht. + +[INT1_F:INTRO] +Wird schlecht fürs Geschäft sein. + +[INT1_G:INTRO] +Tja, was sollen wir machen, Sonny? + +[INT1_H:INTRO] +Wir machen auf alte Kumpels und schicken ihn woanders hin. Ok? + +[INT1_I:INTRO] +Wir wollten doch sowieso nach Süden expandieren, oder? + +[INT1_J:INTRO] +In Vice City liegt zurzeit das Geld auf der Straße. + +[INT1_K:INTRO] +Die Kolumbianer, die Mexikaner, + +[INT1_L:INTRO] +ja, sogar die kubanischen Flüchtlinge machen alle glänzende Geschäfte. + +[INT1_M:INTRO] +Aber das geht nur mit Drogen, Sonny. + +[INT1_N:INTRO] +Aber keine der Familien rührt dieses Zeug an! + +[INT1_O:INTRO] +Die Zeiten ändern sich. + +[INT1_P:INTRO] +Die Familien können nicht wegsehen, während unsere Feinde groß abkassieren. + +[INT1_Q:INTRO] +Also schicken wir jemanden für die Drecksarbeit da runter + +[INT1_R:INTRO] +und schneiden uns 'ne hübsche Scheibe ab. Ok? + +[INT1_S:INTRO] +Wer ist unser Kontaktmann da unten? + +[INT1_T:INTRO] +Ken Rosenberg, ein Idiot von einem Anwalt. + +[INT1_U:INTRO] +Wie soll der Vercetti im Zaum halten? + +[INT1_V:INTRO] +Muss er gar nicht. + +[INT1_W:INTRO] +Wir lassen ihn einfach auf Vice City los. + +[INT1_X:INTRO] +Wir geben ihm ein bisschen Startgeld, ok? + +[INT1_Y:INTRO] +Wir warten ein paar Monate. + +[INT1_Z:INTRO] +Dann fahren wir hin + +[INT1_A1:INTRO] +und schauen mal bei ihm rein, klar? + +[INT1_A2:INTRO] +Mal sehen, wie er sich macht. + +[INT2_A:INTRO] +Hey, hey, Jungs! Ich bin, äh, Ken Rosenberg. Ha, ha, sehr gut, hey! + +[INT2_B:INTRO] +Tja, äh, ich soll euch zu dem Trefffen fahren, okay? + +[INT2_C:INTRO] +Ich hab mit den Lieferanten geredet, und die würden, + +[INT2_D:INTRO] +liebend gern mit uns ins Geschäft kommen. Und, äh, + +[INT2_E:INTRO] +wenn alles gut geht, dann dürfte da + +[INT2_F:INTRO] +ein Haufen Kohle für uns drin sein. Und das ist doch, na ja... + +[INT2_G:INTRO] +gut... + +[INT2_H:INTRO] +Okay. Es sind zwei Brüder, ok? + +[INT2_I:INTRO] +Der eine schmeißt, äh, den Laden, + +[INT2_J:INTRO] +der andere macht die Flüge. + +[INT2_K:INTRO] +Die arbeiten von Mexiko aus, + +[INT2_M:INTRO] +Sie haben eine Farm in Panama. + +[INT2_N:INTRO] +Okay, passt auf, Jungs, + +[INT2_O:INTRO] +wenn wir dort ankommen, sollte ich im Auto bleiben, + +[INT2_P:INTRO] +oder soll ich mit reinkommen? + +[INT2_Q:INTRO] +Nein. Bleib im Wagen. + +[INT2_R:INTRO] +Wisst ihr was, ich hab's mir überlegt. + +[INT2_S:INTRO] +Ich pass auf den Wagen auf. + +[INT3_A:INTRO] +Ok, das sind sie, da im Helikopter. + +[INT3_B:INTRO] +Ok, das ganze läuft so ab: + +[INT3_C:INTRO] +Die wollen eine saubere Übergabe auf offenem Gelände. + +[INT3_D:INTRO] +Alles klar? Ok, dann wollen wir mal. + +[INT3_E:INTRO] +Ok, ganz ruhig jetzt. + +[INT3_F:INTRO] +Ich bin hier. Der Wagen läuft, Baby! + +[INT3_G:INTRO] +Hast du's? + +[INT3_H:INTRO] +100% astreines kolumbianisches Koks, mein Freund. + +[INT3_I:INTRO] +Die Kohle? + +[INT3_J:INTRO] +Zehner und Zwanziger. Gebrauchte Scheine. + +[INT3_L:INTRO] +Los, los, weg hier! Fahr los! + +[INT4_A:INTRO] +Gearscht. Wir sind voll gearscht! + +[INT4_B:INTRO] +Das ist wieder typisch. + +[INT4_C:INTRO] +Da passe ich eine einziges Mal nicht auf, + +[INT4_D:INTRO] +und prompt kriege ich eins reingewürgt. + +[INT4_E:INTRO] +Hier! + +[INT4_F:INTRO] +Hör endlich auf zu jammern. Du lebst schließlich noch, oder? + +[INT4_G:INTRO] +Lass mich hier raus. + +[INT4_H:INTRO] +Schaff das Auto weg und leg dich schlafen. + +[INT4_I:INTRO] +Ich komme morgen zu dir ins Büro, dann sehen wir weiter. + +[INT4_J:INTRO] +Ok, gute Idee. Ich leg mich erstmal hin. + +[INT4_K:INTRO] +Was hast du vor? + +[INT4_L:INTRO] +Ich geh zurück in mein Hotel. + +[INT4_M:INTRO] +Ich muss nachdenken, was schiefgelaufen ist. + +[INT4_N:INTRO] +Ok. + +[INTRO1:INTRO] +Da passe ich ein einziges Mal nicht auf, und prompt kriege ich eins reingewürgt. + +[INTRO2:INTRO] +Leg dich schlafen. + +[INTRO3:INTRO] +Was hast du vor? + +[INTRO4:INTRO] +Ich komme morgen zu dir ins Büro, dann sehen wir weiter. + +[INT3_K:INTRO] +Tja, der Deal kann steigen, mein Freund. + +[INT3_M:INTRO] +Zeig her. + +[INT2_L:INTRO] +Nein, nein, nein, wartet... + +{=================================== MISSION TABLE KENT1 ===================================} + +[KPM1_A:KENT1] +Ok, du Knallkopf, ich werde deine Haut retten. + +[KPM1_B:KENT1] +Was sagst du? + +[KPM1_C:KENT1] +Du kennst doch Diaz, den Idioten, den Koks-König? + +[KPM1_D:KENT1] +Er hat deinen Freund Lance. Es heißt, dein Kumpel wollte ihm an den Kragen. + +[KPM1_E:KENT1] +Hat ihn aber fast den eigenen gekostet hat, falls du weißt, was ich meine. + +[KPM1_F:KENT1] +Wo hat er ihn hingebracht? Im Klartext! + +[KPM1_G:KENT1] +Mach dich mal locker! Sie haben ihn beim Schrottplatz erwischt. + +[KPM1_H:KENT1] +Verdammt noch mal. Psycho. + +[KPM1_2:KENT1] +~r~Du solltest Lance lebend da rausholen! + +[KPM1_3:KENT1] +Lance' Gesundheitszustand: + +[RESC_1:KENT1] +Kannst du eine Waffe halten? + +[RESC_2:KENT1] +Klar, glaub schon. Freut mich, dich zu sehen. + +[RESC_3:KENT1] +Los, wir verschwinden von hier. + +[RESC_4:KENT1] +Dank dir ist mein ganzer schöner Plan im Eimer. Das hast du sauber vermasselt, Lance. + +[RESC_5:KENT1] +Er hat meinen Bruder auf dem Gewissen. Soll ich ihm den Rasen mähen? + +[RESC_6:KENT1] +Wir müssen diesen Diaz erledigen bevor er uns erledigt. + +[RESC_7:KENT1] +Lass dich zusammenflicken, dann treffen wir uns auf der Brücke nach Star Island, ok? + +[RESC_8:KENT1] +Ok, alles klar. + +[KPM1_1:KENT1] +~g~Lance wird auf dem Schrottplatz gefangengehalten. Rette ihn! + +[KPM1_4:KENT1] +~g~Bring Lance ins Krankenhaus! + +[M_PASSN:KENT1] +MISSION ERFÜLLT! + +[KPM1_5:KENT1] +~g~Diaz' Leute sind hinter euch her. Bring Lance ins Krankenhaus. + +{=================================== MISSION TABLE KICKSTT ===================================} + +[KICK1_2:KICKSTT] +~r~Du warst nicht schnell genug bei der Maschine! + +[KICK1_7:KICKSTT] +~r~Du hast die Maschine geschrottet! + +[KICK1_8:KICKSTT] +~g~Setz dich auf das Motorrad! + +[KICK1_T:KICKSTT] +BENÖTIGTE ZEIT: + +[KICKTM:KICKSTT] +~b~ZEIT: ~1~:~1~ + +[KICKTM2:KICKSTT] +~b~ZEIT: ~1~:0~1~ + +[GETBIKE:KICKSTT] +~g~Du hast ~1~ Sekunden, um zu einer Geländemaschine zurückzukehren, bevor die Mission endet. + +[KICK1_1:KICKSTT] +~g~Absolviere den Kurs so schnell wie möglich. + +[KICK1_6:KICKSTT] +~g~Gut gemacht! + +[KICK_10:KICKSTT] +~G~Nimm den Sanchez und absolviere den Kurs, indem du alle Checkpoints passierst. + +[KICK_12:KICKSTT] +~r~Du hast es vermasselt! + +[KICK_13:KICKSTT] +~r~Du hast zu lange gebraucht! + +[KICK_11:KICKSTT] +~g~Um die Mission zu beenden, stell dich zu Fuß in die ~q~rosa Markierung~g~. + +{=================================== MISSION TABLE LAWYER1 ===================================} + +[LAW1_A:LAWYER1] +Leg dich schlafen, sagt er - + +[LAW1_B:LAWYER1] +- Ich sitz die ganze Nacht im Dunklen hier rum und trink Kaffee! + +[LAW1_C:LAWYER1] +Das ist eine Katastrophe. Wir sind so gearscht, Mann! + +[LAW1_D:LAWYER1] +Diese Gorillas, die kommen hier runter und reißen mir den Kopf ab. Es ist beinahe zum lachen! + +[LAW1_E:LAWYER1] +Dafür habe ich NICHT Jura studiert. Ok, was sollen wir jetzt machen? + +[LAW1_F:LAWYER1] +Sei still, setz dich hin und bleib ruhig. Ich sag dir, was wir machen. + +[LAW1_G:LAWYER1] +Du findest raus, wer das Koks geklaut hat - und ich nehm ihn mir vor. + +[LAW1_H:LAWYER1] +Gute Idee. SEHR gute Idee. Lass mich nachdenken, lass mich nachdenken. + +[LAW1_I:LAWYER1] +OH! Da gibt es diesen Colonel a.D., Colonel Juan Garcia Cortez. + +[LAW1_J:LAWYER1] +Der half mir, diesen Deal einzufädeln, + +[LAW1_K:LAWYER1] +und zwar ohne Vice Citys Gangster-Establishment. Ok? + +[LAW1_L:LAWYER1] +Pass auf, der gibt eine Party in der Bucht, auf seiner Luxusjacht + +[LAW1_M:LAWYER1] +da kommt alles, was in Vice City Rang und Namen hat. + +[LAW1_N:LAWYER1] +Ich hab natürlich eine Einladung, versteht sich, + +[LAW1_O:LAWYER1] +aber mich kriegen keine zehn Pferde hier raus. Auf keinen Fall! + +[LAW1_P:LAWYER1] +Halt die Klappe! Ich geh selbst hin... + +[LAW1_Q:LAWYER1] +Moment! Hey, ich steh ja auch auf den 78er Look , aber das wird dort kein nostalgisches Saufgelage. + +[LAW1_R:LAWYER1] +Ich meine, nichts gegen dich, aber mit den Klamotten wirst du dort ziemlich blöd angeschaut. + +[LAW1_S:LAWYER1] +Wieso? Was ist mit meinen Sachen? + +[LAW1_T:LAWYER1] +Pass auf. Fahr zu Rafael. Sag ihm, ich schicke dich, und er soll dich ordentlich einkleiden. + +[LAW1_U:LAWYER1] +Ok, Los jetzt. Mach hinne... + +[LAWP_1:LAWYER1] +Guten Abend. + +[LAWP_2:LAWYER1] +Ich höre, Sie sind anstelle von Mr. Rosenberg hier. + +[LAWP_3:LAWYER1] +Ich hoffe, gewisse Vorfälle haben seiner Gesundheit nicht geschadet, + +[LAWP_4:LAWYER1] +oder seiner Psyche, Mr...äh? + +[LAWP_5:LAWYER1] +Vercetti. Er leidet ein wenig an...Platzangst. + +[LAWP_6:LAWYER1] +Ausgezeichnet, ausgezeichnet. Und Sie? + +[LAWP_7:LAWYER1] +Ich will nur meine Ware. + +[LAWP_8:LAWYER1] +Ah. Eine missliche Lage für alle Beteiligten. + +[LAWP_9:LAWYER1] +Natürlich stelle ich selbst Nachforschungen an, aber + +[LAWP_10:LAWYER1] +bei solch heiklen Sachen dauert das ein wenig. + +[LAWP_11:LAWYER1] +Wir sprechen uns vielleicht später. Hm? + +[LAWP_12:LAWYER1] +Inzwischen möchte ich Ihnen meine Tochter vorstellen, + +[LAWP_13:LAWYER1] +Mercedes! + +[LAWP_14:LAWYER1] +Könntest du dich um unseren Gast kümmern, während ich mich um andere Dinge + +[LAWP_15:LAWYER1] +Natürlich, Daddy. + +[LAWP_16:LAWYER1] +Entschuldigen Sie mich, bitte. + +[LAWP_17:LAWYER1] +Mercedes!? + +[LAWP_18:LAWYER1] +Leb du mal mit so 'nem Namen. + +[LAWP_19:LAWYER1] +Na gut, ich zeig dir mal einige unserer bekannteren Gäste... + +[LAWP_20:LAWYER1] +Das ist unser Abgeordneter Alex Shrub mit dem aufgehenden Sternchen Candy Suxx.. + +[LAWP_21:LAWYER1] +Und kennen Sie schon meine reizende Frau Laura? Nein? + +[LAWP_22:LAWYER1] +Nun, leider ist sie in Alabama. Das hier ist Candy. + +[LAWP_23:LAWYER1] +Und hier haben wir den Star-Verteidiger der Vice City Mambas, BJ. + +[LAWP_24:LAWYER1] +Immer charmant. + +[LAWP_25:LAWYER1] +Ich hab ihn voll geblockt. Der sitzt heute im Rollstuhl! + +[LAWP_26:LAWYER1] +Haha, das ist gut! + +[LAWP_27:LAWYER1] +Tja, ich bin an einem super Grundstück dran. + +[LAWP_28:LAWYER1] +Und der Schleimbold dort ist Jezz Torrent, + +[LAWP_29:LAWYER1] +Der Sänger von 'Love Fist'. + +[LAWP_30:LAWYER1] +Wisst ihr, wie sie in Thailand Pingpong spielen? + +[LAWP_31:LAWYER1] +Ich verrat's euch, + +[LAWP_32:LAWYER1] +man spielt ohne Schläger, wenn ihr wisst, was ich meine! + +[LAWP_33:LAWYER1] +Impotent. + +[LAWP_34:LAWYER1] +Und das schwatzhafte Trio. + +[LAWP_35:LAWYER1] +Diese schlafende Schweißfabrik ist Papas Obersklave, Gonzalez. + +[LAWP_36:LAWYER1] +und die anderen beiden sind Pastor Richards + +[LAWP_37:LAWYER1] +und der pseudo-intellektuelle Regisseur Steve Scott. + +[LAWP_38:LAWYER1] +....leidenschaftlich mit den nymphomanischen Aliens + +[LAWP_39:LAWYER1] +Da kommt der riesige Hai und + +[LAWP_40:LAWYER1] +beißt ihnen ihr Ding ab! + +[LAWP_41:LAWYER1] +Ha! So was hat doch die Welt noch nicht gesehen, oder? + +[LAWP_42:LAWYER1] +Colonel! + +[LAWP_43:LAWYER1] +Ihre Party ist wie immer fantastisch, hahahaha! + +[LAWP_44:LAWYER1] +Ich entschuldige mich für die Verspätung. + +[LAWP_45:LAWYER1] +Ah, nicht doch, Amigo. Wie geht es ihnen? + +[LAWP_46:LAWYER1] +Unsere Geschäfte laufen schwierig - die Barbaren stehen vor den Toren. + +[LAWP_47:LAWYER1] +Eine Zeit, Freunde zu belohnen und Feinde auszuschalten, Amigo. + +[LAWP_48:LAWYER1] +Wer ist das Großmaul? + +[LAWP_49:LAWYER1] +Ricardo Diaz. Er ist Mr. Koks. + +[LAWP_50:LAWYER1] +Mercedes! + +[LAWP_51:LAWYER1] +Oh, ich will gerade meinen Freund in die Stadt bringen. + +[LAWP_52:LAWYER1] +Ein andermal, Ricardo! + +[LAWP_53:LAWYER1] +Lass uns verschwinden. + +[LAWP_54:LAWYER1] +Fahr mich zum Pole Position Club. + +[LAW1_2:LAWYER1] +~g~Begib dich zur Jacht des Colonels. + +[LAW1_4:LAWYER1] +~r~Du hast die Tochter des Colonels erledigt! + +[LAW1_5:LAWYER1] +Wirst du für meinen Vater arbeiten? + +[LAW1_6:LAWYER1] +Vielleicht. + +[LAW1_7:LAWYER1] +Darf ich meine Hand in deinen Schoß legen? + +[LAW1_8:LAWYER1] +Vielleicht... + +[LAW1_9:LAWYER1] +Es ist schwer, einen so reichen, mächtigen Vater zu haben. Los. + +[LAW1_10:LAWYER1] +Wir sehen uns, mein Hübscher! + +[LAW1_11:LAWYER1] +Ganz bestimmt. + +[LAW1_12:LAWYER1] +Hmm, netter Ofen. + +[LAW1_13:LAWYER1] +Nein! Mein Motorrad! + +[LAW1_3:LAWYER1] +~g~Bring die Tochter des Colonels zum Pole Position Club. + +[HELP20:LAWYER1] +Folge dem ~h~T-shirt-Symbol~w~ auf dem Radar, um Rafael's zu finden. + +[LAW1_14:LAWYER1] +Wow, das ist ja wirklich ein tolles Motorrad. + +[LAW1_15:LAWYER1] +Ja, Baby, hab ich mir gerade bei Howlin' Pete's besorgt. + +{=================================== MISSION TABLE LAWYER2 ===================================} + +[LAW2_A:LAWYER2] +Ah! Tja, ich hoffe, du amüsierst dich gut, während ich hier vor Angst halb umkomme. Was hast du rausgefunden? + +[LAW2_B:LAWYER2] +Dass es in dieser Stadt mehr Gangster gibt als im Knast. Wir brauchen einen Tipp von der Straße... + +[LAW2_C:LAWYER2] +Ok, lass mich nachdenken, lass mich nachdenken - + +[LAW2_D:LAWYER2] +- AH! Ich hab's! + +[LAW2_E:LAWYER2] +Ok, es gibt da so 'n Engländer, so 'n Idiot aus der Musikbranche. + +[LAW2_F:LAWYER2] +Er nennt sich Kent Paul. + +[LAW2_G:LAWYER2] +Und der ist in all den Kreisen von Vice City richtig dick drin. + +[LAW2_H:LAWYER2] +Wenn einer weiß, wo 20 Kilo Koks abgeblieben sind, + +[LAW2_I:LAWYER2] +dann dieser Typ. Er ist immer im Malibu. + +[LAW2_J:LAWYER2] +Ich seh ihn mir mal an. + +[LAW2B_A:LAWYER2] +Wo kommst du denn her? + +[LAW2B_B:LAWYER2] +Nach einer wie dir suche ich schon seit Ewigkeiten. + +[LAW2B_C:LAWYER2] +Kent Paul. Ja, ich bin hier die Nummer Eins. + +[LAW2B_D:LAWYER2] +Ich suche einen Engländer... + +[LAW2B_E:LAWYER2] +Ich ziehe hier die Strippen, verstehst du? + +[LAW2B_F:LAWYER2] +Ich lade dich ein. Ich kann dir alles besorgen, Süße. + +[LAW2B_G:LAWYER2] +Mach dir keine Gedanken. + +[LAW2B_H:LAWYER2] +Verzieh dich, Schätzchen. + +[LAW2B_I:LAWYER2] +Oi oi oi oi! + +[LAW2B_J:LAWYER2] +Bist du Kent Paul? Ich bin ein Freund von Rosenberg... + +[LAW2B_K:LAWYER2] +Rosenberg...Rosenberg... Ach, dieser abgedrehte Winkeladvokat! + +[LAW2B_L:LAWYER2] +Der bringt noch den Unschuldigsten auf den elektrischen Stuhl! + +[LAW2B_M:LAWYER2] +Mach uns noch 'nen Drink, mein Freund. + +[LAW2B_N:LAWYER2] +Bist ein echter Komiker. + +[LAW2B_O:LAWYER2] +Hör zu, ich vermisse 20 Kilo und einen Haufen Geld... + +[LAW2B_P:LAWYER2] +Drogen? Das ist doch Schwachsinn. + +[LAW2B_Q:LAWYER2] +Was weißt du darüber? + +[LAW2B_R:LAWYER2] +Oi, oi! Gerade wollte ich's sagen... + +[LAW2B_S:LAWYER2] +Es gibt da einen Koch, der verdealt Koks in der Küche eines Hotels am Ocean Drive. + +[LAW2B_T:LAWYER2] +Macht einen ziemlich zufriedenen Eindruck in letzter Zeit. Solltest du mal auschecken. + +[LAW2B_U:LAWYER2] +Mach ich. Und wir sehen uns. + +[LAW2B_V:LAWYER2] +Ja, ja. Nur zu. Hau bloß ab, du Stinktier. Dir polier ich noch die Visage! + +[LAW2B_W:LAWYER2] +Gib mir 'nen Drink. Und wo ist die Puppe? + +[LAW2C_A:LAWYER2] +Oh, sehr gut, Rambo, schlag ihn ruhig zu Brei. Dann redet er ganz bestimmt. + +[LAW2C_B:LAWYER2] +Willst du auch ein paar? + +[LAW2C_C:LAWYER2] +Hey, ruhig. Ich will das gleiche wie du, Bruder. + +[LAW2C_D:LAWYER2] +Ach ja? Und das wäre? + +[LAW2C_E:LAWYER2] +Deine Kohle und den Stoff meines toten Bruders. Aber du hast gerade unsere Spur erledigt. + +[LAW2C_F:LAWYER2] +Dumm gelaufen. Verzieh dich. + +[LAW2C_G:LAWYER2] +Hey, hey! Kein Grund, hier den dicken Mann zu spielen. + +[LAW2C_H:LAWYER2] +Sieh mal: Wir sind zwei Hombres in 'ner fremden Stadt. Wir sollten uns gegenseitig helfen. + +[LAW2C_I:LAWYER2] +Ich helf mir selbst, Bruder. + +[LAW2C_J:LAWYER2] +Bist du dir sicher? Hier nimm das. + +[LAW2C_K:LAWYER2] +Komm mit! + +[LAW2_1:LAWYER2] +Was glotzt du denn so? + +[LAW2_2:LAWYER2] +Rede endlich... + +[LAW2_3:LAWYER2] +Zwing mich doch dazu, du Pfeife! + +[LAW2_4:LAWYER2] +Mir nach! + +[LAW2_5:LAWYER2] +Ich seh zu, was ich rausfinde. Ich behalte dich im Auge, Tommy. + +[LAW2_6:LAWYER2] +~g~Begib dich zum Malibu Club und suche Kent Paul. + +[LAW2_7:LAWYER2] +~g~Suche den Küchenchef auf dem Ocean Drive. + +[LAW2_10:LAWYER2] +~g~Fahre zurück zum Hotel. + +[LAW2_11:LAWYER2] +~g~Nimm sein Handy. + +[LAW2_12:LAWYER2] +Du hast jetzt ein Handy und kannst Telefongespräche entgegennehmen! + +[LAW2_13:LAWYER2] +~g~Du hast Lance zurückgelassen! Geh ihn holen! + +[LAW2_14:LAWYER2] +Verdammt, nichts wie weg hier! + +[GUN_2A:LAWYER2] +Halte die ~h~~k~~PED_LOCK_TARGET~ ~w~gedrückt, um ~h~automatisch zu zielen~w~. Drücke die ~h~~k~~PED_FIREWEAPON~~w~, um zu ~h~feuern! + +[GUN_2C:LAWYER2] +Halte die ~h~~k~~PED_LOCK_TARGET~ ~w~gedrückt, um ~h~automatisch zu zielen~w~. Drücke die ~h~~k~~PED_FIREWEAPON~~w~, um zu ~h~feuern! + +[GUN_2D:LAWYER2] +Halte die ~h~~k~~PED_LOCK_TARGET~ ~w~gedrückt, um ~h~automatisch zu zielen~w~. Drücke die ~h~~k~~PED_FIREWEAPON~~w~, um zu ~h~feuern! + +[HELP17:LAWYER2] +Drücke die ~h~~k~~PED_FIREWEAPON~~w~, um den Küchenchef anzugreifen. + +[HELP18:LAWYER2] +Drücke die~h~ ~k~~PED_FIREWEAPON~~w~, um den Küchenchef anzugreifen. + +[LAW3_11:LAWYER2] +Stell dich in die ~q~rosa Markierung~w~, um zu sehen, was im Angebot ist. + +[LAW3_12:LAWYER2] +Du kannst Waffen auswählen, indem du die ~h~linke~w~ oder ~h~rechte~w~ ~h~Richtungstaste drückst. + +[LAW3_13:LAWYER2] +Wenn du genug Geld hast, kannst du Waffen durch Drücken der ~h~~k~~PED_SPRINT~~w~ kaufen. + +[LAW3_14:LAWYER2] +Um zu gehen, drücke die ~h~~k~~VEHICLE_ENTER_EXIT~ + +[LAW3_15:LAWYER2] +Folge dem ~h~Pistolensymbol~w~ auf dem Radar, so kommst du zu AmmuNation. + +[LAW2_15:LAWYER2] +~g~Begib dich zu AmmuNation. + +[LAW2_K:LAWYER2] +Nur die Ruhe. + +[LAW2_16:LAWYER2] +Eins musst du wissen: In dieser Stadt darfst du nie unbewaffnet sein. + +[LAW2_17:LAWYER2] +Komm, der nächste Waffenladen ist ein paar Blocks von hier. + +[LAW2_18:LAWYER2] +Tommy, jeder Mann braucht ab und zu mal ein bisschen Entspannung. + +[LAW2_19:LAWYER2] +Das ist die Pole Position Stripper-Bar. Solltest du bei Gelegenheit mal reinschauen. + +{=================================== MISSION TABLE LAWYER3 ===================================} + +[_A:LAWYER3] +Arrgh! Ach, du lieber Gott! Du! Meine Güte, ich brauch 'ne neue Hose! + +[LAW3_B:LAWYER3] +Hey, diese Psychos aus dem Norden haben angerufen. Sie kommen bald hier runter. + +[LAW3_C:LAWYER3] +Also, wo ist das verdammte Geld? + +[LAW3_D:LAWYER3] +Ganz ruhig. Soweit sind wir noch nicht. + +[LAW3_E:LAWYER3] +Ich hab wirklich gedacht, du erledigst das. + +[LAW3_F:LAWYER3] +Und jetzt sagen diese Gauner, wir sollen ihnen einen Gefallen tun. + +[LAW3_G:LAWYER3] +Du meinst, ICH soll ihnen einen Gefallen tun. + +[LAW3_H:LAWYER3] +Du sagst es. Sehe ich aus, als könnte ich Geschworene einschüchtern? + +[LAW3_I:LAWYER3] +Ich kann nicht mal ein Kind einschüchtern. Ich hab's versucht. + +[LAW3_J:LAWYER3] +Hör zu, wenn du kneifst, kriegt Forellis Cousin Georgio 5 Jahre wegen Betrugs. + +[LAW3_K:LAWYER3] +Du musst diese Typen ausschalten. + +[LAW3_L:LAWYER3] +Verstehe. Den Geschworenen helfen 'umzudenken'. Kein Problem. + +[LAW3_M:LAWYER3] +Nein, nein, nein! Das hab ich schon versucht. Das ist nicht gelaufen, + +[LAW3_N:LAWYER3] +ZWING sie dazu, umzudenken. + +[LAW3_1:LAWYER3] +Georgio lässt grüßen. + +[LAW3_2:LAWYER3] +Denk dran, 'Schuldig' ist ein hässliches Wort. + +[LAW3_3:LAWYER3] +'Unschuldig', bis ich was anderes sage. + +[LAW3_4:LAWYER3] +Er ist nicht schuldig. + +[LAW3_5:LAWYER3] +Du kennst Georgio? Merk dir: Er ist nicht schuldig. + +[LAW3_6:LAWYER3] +Nicht schuldig. Verstanden...gut. + +[LAW3_8:LAWYER3] +~r~Du hast einen Geschworenen erledigt! + +[LAW3_9:LAWYER3] +~g~Schrotte das Auto des Geschworenen, damit er aussteigt! + +[HELP40:LAWYER3] +Du kannst Autos mit dem Hammer oder einer ähnlichen Waffe zertrümmern. + +[HELP41:LAWYER3] +Oder du kannst sie mit einem Fahrzeug rammen. + +[LAW3_10:LAWYER3] +~g~Eine Nahkampfwaffe kannst du im ~h~Eisenwarenladen~g~ kaufen. + +[LAW3_20:LAWYER3] +~g~Schrotte das Auto des Geschworenen! + +[LAW3_21:LAWYER3] +Das kann doch wohl nicht wahr sein! + +[LAW3_22:LAWYER3] +Unglaublich! + +[LAW3_23:LAWYER3] +Ok! Ok! Ich hab kapiert! + +[LAW3_24:LAWYER3] +~g~Dieser Hammer wäre nützlich. + +[LAW3_7:LAWYER3] +~g~Schüchtere die 2 Geschworenen ein, aber erledige sie NICHT! + +[HELP23:LAWYER3] +Folge dem ~h~Hammer-Symbol~w~ auf dem Radar, wenn du beim Eisenwarenladen Nahkampfwaffen kaufen willst. + +[LAW3_16:LAWYER3] +Dämlicher Florida-Idiot! + +[LAW3_17:LAWYER3] +Aus dem Weg! + +{=================================== MISSION TABLE LAWYER4 ===================================} + +[LAW4_A:LAWYER4] +Avery, es versteht sich von selbst... Tommy! Tommy! Fortschritte gemacht? Nein, erzähl's mir später. + +[LAW4_B:LAWYER4] +Tommy, das ist Avery Carrington. Kennt ihr euch nicht von der Party? + +[LAW4_C:LAWYER4] +Nicht persönlich. + +[LAW4_D:LAWYER4] +Tagchen. + +[LAW4_E:LAWYER4] +Avery hat einen Vorschlag für uns. + +[LAW4_F:LAWYER4] +Haben wir nicht was besseres zu tun? + +[LAW4_G:LAWYER4] +Ich versuch hier, unseren Hals zu retten. Also würdest du mich bitte ausreden lassen? + +[LAW4_H:LAWYER4] +Ich hab Angst. Aber wenn ich schon Ende der Woche sterbe, möchte ich wenigstens nicht arm sterben. + +[LAW4_I:LAWYER4] +Jetzt beruhigt euch, ihr beiden. + +[LAW4_J:LAWYER4] +Junge, wenn du mir hilfst, sorge ich dafür, dass jeder, der dir Ärger macht, unter die Erde kommt. + +[LAW4_K:LAWYER4] +Ok, was kann ich für Sie tun? + +[LAW4_L:LAWYER4] +Eine Spedition hat ihr Lager auf einem Top-Grundstück - und will nicht verkaufen. + +[LAW4_M:LAWYER4] +Die sitzen da drauf wie die Ratten in ihren Löchern. Also müssen wir dieses Ungeziefer ausräuchern. + +[LAW4_N:LAWYER4] +Fahr hin und stich ein wenig ins Wespennest. + +[LAW4_O:LAWYER4] +Das wird die Security beschäftigen. Dann schleichst du dich rein und machst den Laden platt. + +[LAW4_P:LAWYER4] +Und du könntest dich bei Rafael's neu einkleiden. Kann 'ne Weile dauern, aber mach das ruhig mal. + +[LAW4_Q:LAWYER4] +Das gibt ein Fest. + +[LAW4_R:LAWYER4] +Wenn alles läuft wie geplant, komm mal zu mir ins Büro... + +[LAW4_1:LAWYER4] +Bitte, geht auseinander! Die Geschäftsleitung wird sich aller Probleme annehmen! + +[LAW4_2:LAWYER4] +Bitte, geht auseinander! Geht wieder nach Hause! + +[LAW4_3:LAWYER4] +Bitte, geht auseinander! Das ist nicht akzeptabel! + +[LAW4_4:LAWYER4] +Bitte, geht auseinander! Ihr landet alle auf der Straße! + +[LAW4_5:LAWYER4] +Die Knüppel raus, Jungs! Diesen Kommis zeigten wir's! + +[LAW4_13:LAWYER4] +Fange mit mind. 4 Arbeitern Streit an, um einen Aufruhr zu starten. + +[LAW4_14:LAWYER4] +~g~Zerstöre die Transporter auf dem Gelände! + +[HELP38:LAWYER4] +Wenn du jemanden ausschaltest, der eine Waffe trägt, lässt er sie fallen. + +[HELP39:LAWYER4] +Du kannst explosive Fässer anvisieren und abschießen, aber bleib auf Distanz. + +{=================================== MISSION TABLE MIAMI_1 ===================================} + +[T4X4_1A:MIAMI_1] +~g~Du hast ~1~ Sekunden, um ~y~24~g~ Checkpoints abzufahren. ~g~Die ~y~REIHENFOLGE IST BELIEBIG. + +[T4X4_1B:MIAMI_1] +~y~PASSIERE~g~ den ersten Checkpoint, dann läuft die ~r~STOPPUHR. + +[T4X4_1C:MIAMI_1] +~1~ von 24! + +[GETBIK1:MIAMI_1] +Du hast ~1~ Sekunden, um auf eine PCJ 600 zu steigen! + +[GETBIK3:MIAMI_1] +~r~Du brauchst eine PCJ 600, um diese Mission durchzuführen! + +{=================================== MISSION TABLE MM ===================================} + +[BLOD_04:MM] +ZUSTAND AUTO: + +[BLOD_05:MM] +~g~ZIELZEIT: ~1~ Minute + +[BLOD_06:MM] +~g~ZIELZEIT: ~1~ Minuten + +[BLOD_07:MM] +NEUE Bestzeit: ~1~ Sekunden + +[BLOD_08:MM] +Zerstörte Autos: ~1~ + +[BLOD_09:MM] +$~1~ + +[BLOD_10:MM] +SIEGER!! + +[BLOD_01:MM] +Fahr durch die Checkpoints, um deine Gesamtzeit zu verlängern. + +[BLOD_02:MM] +Wenn die Gesamtzeit abgelaufen ist, hast du versagt. + +[BLOD_03:MM] +Um zu gewinnen, muss deine Gesamtzeit die Zielzeit überschreiten! + +{=================================== MISSION TABLE OVALRIG ===================================} + +[HOTR_01:OVALRIG] +~g~Das Rennen geht über 12 Runden. Nur die ersten drei Plätze qualifizeren für einen Gewinn. + +[HOTR_02:OVALRIG] +~g~Wird dein Auto zerstört, wirst du disqualifiziert. + +[HOTR_03:OVALRIG] +~g~Wird dein Auto beschädigt, kannst du es an der Box reparieren lassen. + +[HOTR_04:OVALRIG] +~g~Da geht es aus dem Stadion raus. + +[HOTR_05:OVALRIG] +Zustand Auto: + +[HOTR_06:OVALRIG] +Runden: + +[HOTR_07:OVALRIG] +Neue Bestzeit: ~1~:0~1~ + +[HOTR_08:OVALRIG] +Zeit: ~1~:~1~ + +[HOTR_10:OVALRIG] +Absolvierte Zeit: + +[HOTR_09:OVALRIG] +Position: + +[HOTR_12:OVALRIG] +~r~Dein Fahrzeug ist zerstört worden! + +[HOTR_13:OVALRIG] +~r~Du hast das Rennen nicht gewonnen! + +[HOTR_14:OVALRIG] +~r~Du bist disqualifiziert worden! + +[HOTR_15:OVALRIG] +Zeit: ~1~:~1~ + +[HOTR_16:OVALRIG] +Zeit: ~1~:0~1~ + +[HOTR_17:OVALRIG] +Bestzeit: ~1~:~1~ + +[HOTR_18:OVALRIG] +Bestzeit: ~1~:0~1~ + +[HOTR_19:OVALRIG] +Bestzeit: Nicht verfügbar + +[HOTR_20:OVALRIG] +Neue Bestzeit: ~1~:~1~ + +[HOTR_21:OVALRIG] +Neue Bestzeit: ~1~:0~1~ + +[HOTR_22:OVALRIG] +Beste Platzierung: Nicht verfügbar + +[HOTR_23:OVALRIG] +Beste Platzierung: 1. + +[HOTR_24:OVALRIG] +Beste Platzierung: 2. + +[HOTR_25:OVALRIG] +Beste Platzierung: 3. + +[HOTR_26:OVALRIG] +Beste Platzierung: ~1~. + +[HOTR_27:OVALRIG] +Beste Rundenzeit: ~1~.~1~ Sekunden + +[HOTR_28:OVALRIG] +Beste Rundenzeit: ~1~.0~1~ Sekunden + +[HOTR_29:OVALRIG] +$~1~ + +[HOTR_30:OVALRIG] +1. PLATZ + +[HOTR_31:OVALRIG] +2. PLATZ + +[HOTR_32:OVALRIG] +3. PLATZ + +[HOTR_33:OVALRIG] +Beste Rundenzeit: Nicht verfügbar + +[HOTR_11:OVALRIG] +Neue beste Rundenzeit: ~1~.~1~ Sekunden + +[HOTR_34:OVALRIG] +Neue beste Rundenzeit: ~1~.0~1~ Sekunden + +{=================================== MISSION TABLE PHIL1 ===================================} + +[PHI1_HP:PHIL1] +Wenn du Granaten mit Fernzünder benutzt, wirf die Granate, dann löse die Explosion zu einem beliebigen Zeitpunkt aus. + +[PHIL1_A:PHIL1] +Phil? + +[PHIL1_B:PHIL1] +SCHNELL WEG! + +[PHIL1_C:PHIL1] +Schnell weg! + +[PHIL1_E:PHIL1] +Scheiße, Phil, das Zeug trinkst du? + +[PHIL1_F:PHIL1] +Hey, du musst es nicht trinken. + +[PHIL1_G:PHIL1] +Das haut schon rein, wenn du nur dran riechst. + +[PHIL1_H:PHIL1] +Hör mal, Phil, du sagtest, du könntest mir Artillerie besorgen... + +[PHIL1_I:PHIL1] +Klar. + +[PHIL1_J:PHIL1] +In letzter Zeit macht mir ein mexikanischer Waffenschieber Konkurrenz. + +[PHIL1_K:PHIL1] +Der müsste jetzt gerade auf seiner wöchentlichen Runde sein. + +[PHIL1_L:PHIL1] +Ramm mit deiner Karre die Ware von seinem Wagen runter, bevor er wieder abtaucht. + +[PHIL1_M:PHIL1] +Tust mir einen großen Gefallen damit. + +[PHIL1_N:PHIL1] +Und dann mach ihn fertig. + +[PHI1_01:PHIL1] +~g~Ramme die Waffen von der Ladefläche des Waffenschiebers. + +[PHI1_02:PHIL1] +~g~Der Waffenhändler hat die Ladung verloren. Schlag die Kiste kaputt und nimm die Waffe. + +[PHI1_03:PHIL1] +~g~Sie haben anscheinend Verstärkung gerufen. + +[PHI1_04:PHIL1] +~g~Jetzt erledige die restlichen Waffenschieber. + +[PHI1_06:PHIL1] +Pass doch auf, wo du hinfährst! + +[PHI1_07:PHIL1] +Hey! + +[PHIL1_O:PHIL1] +Huuuuhuuu! + +[PHIL1_D:PHIL1] +Komm nie mit 'ner offenen Flamme in die Nähe von Phil Cassidys TNT-Whiskey! + +{=================================== MISSION TABLE PHIL2 ===================================} + +[PHIL2_A:PHIL2] +Hey, Phil, wie geht's? + +[PHIL2_B:PHIL2] +Hey, Tommy. Alles klar? Lange nicht gesehen... + +[PHIL2_C:PHIL2] +Du solltest wirklich die Finger von dem TNT-Whiskey lassen. + +[PHIL2_D:PHIL2] +Das Zeug riecht ja wie Terpentin. Mir brennen schon die Augen. + +[PHIL2_E:PHIL2] +Lass stecken, Tommy. + +[PHIL2_F:PHIL2] +Komm hier rüber, ich will dir nämlich was zeigen. + +[PHIL2_G:PHIL2] +Wahnsinn! Das rieche ich ja schon von hier. Mir ist schon ganz schwindlig. + +[PHIL2_H:PHIL2] +Kümmer dich nicht um den Geruch. Tommy, sieh dir das an. + +[PHIL2_I:PHIL2] +Billige Schrottbatterien. Da auf der Bank sind noch welche. + +[PHIL2_J:PHIL2] +Tata! + +[PHIL2_K:PHIL2] +Oh, verflucht! + +[PHI2_01:PHIL2] +~g~Schnell, bring Phil ins Krankenhaus. + +[PHI2_03:PHIL2] +~r~Phil Cassidy ist tot!!! Wer soll Liberty nun mit Waffen versorgen? + +[PHI2_05:PHIL2] +Nicht ins Krankenhaus, Mann! Zu viele Cops und Vietcong! + +[PHI2_06:PHIL2] +Ich kenn 'nen Ex-Army-Arzt, der schuldet mir einen Gefallen und 'nen Rasenmäher. + +[PHI2_07:PHIL2] +Er hat 'ne Praxis unten in Little Havana. Uh, guck mal, ein Riesenfisch. + +[PHI2_08:PHIL2] +Achtung! Da in den Bäumen - Vietcong! + +[PHI2_09:PHIL2] +Spinn ich, oder ist die Straße aus Gummi? + +[PHI2_10:PHIL2] +Broken Spoon an Mother Hen, bitte kommen! + +[PHI2_11:PHIL2] +Spooney Wooney Woo Woo Woooo! + +[PHI2_12:PHIL2] +Er kommt mich holen, Jungchen! + +[PHI2_13:PHIL2] +Schwarze Schwingen breiten sich über mir aus... + +[PHI2_14:PHIL2] +Es ist wunderschön, Mann. Wunderschön... mir ist nur so kalt... + +[PHI2_15:PHIL2] +Roger! Wir haben einen betrunkenen Fahrer. + +[PHI2_04:PHIL2] +Phils Gesundheitszustand: + +[PHI_AS1:PHIL2] +PHIL-MISSIONEN ERFÜLLT + +[PHI_AS2:PHIL2] +~g~Bei Phil gibt es neue Waffen zu kaufen. + +{=================================== MISSION TABLE PIZZA ===================================} + +[PIZ1_01:PIZZA] +~g~Liefere diese Pizzas aus. Du musst den Kunden die Pizzas zuwerfen, während du an ihnen vorbeifährst. + +[PIZ1_02:PIZZA] +~g~Du hast alle Pizzas zugestellt. Fahr zurück und hole noch mehr. + +[PIZ1_05:PIZZA] +~g~Du hast 5 Minuten, um die Pizzas zu liefern, sonst rufen die Kunden einen anderen Pizza-Service an. + +[PIZ1_07:PIZZA] +~r~Du hast den Kunden erledigt! Du bist gefeuert. + +[PIZ1_08:PIZZA] +~r~Die Zeit ist um. Du bist gefeuert. + +[PIZ1_09:PIZZA] +~r~Du hast dein Motorrad geschrottet! Du bist gefeuert. + +[PIZ1_11:PIZZA] +Hey! Steig wieder aufs Motorrad! + +[PIZ1_12:PIZZA] +Verbleibende Pizzas: + +[PIZ1_06:PIZZA] +Drücke die ~h~ R3-Taste~w~, wenn du auf einem Bike sitzt und die Mission abbrechen willst. + +[PIZ1_13:PIZZA] +Liefere sie schön heiß ab. + +[PIZ1_14:PIZZA] +Kumpel, Pizzas für dich. + +[PIZ1_15:PIZZA] +Hey, na los, Mister, liefere sie schnell aus. + +[PIZ1_16:PIZZA] +Worauf wartest du, Mister? Du sollst Pizzas liefern. + +[PIZ1_17:PIZZA] +Ich weiß, du wolltest kein Pizza-Lieferant sein. Na ja, mir egal. + +[PIZ1_18:PIZZA] +Liefere die aus. + +[PIZ1_19:PIZZA] +Die müssen ausgeliefert werden. + +[PIZ1_20:PIZZA] +Na los, Mister, liefere die Dinger aus, oder du fliegst. + +[PIZ1_21:PIZZA] +Die Leute warten, Kumpel. + +[PIZ1_22:PIZZA] +Wartest du auf bessere Zeiten? Die müssen ausgeliefert werden! + +[PIZ1_23:PIZZA] +Liefer den verdammten Fraß aus, Mister. + +[PIZ1_24:PIZZA] +Die müssen ausgeliefert werden, Kumpel. + +[PIZ1_25:PIZZA] +Mann, kannst du die übernehmen? + +[PIZ1_26:PIZZA] +Mister, liefere die Dinger schnell ab, hopp, Amigo. + +[PIZ1_27:PIZZA] +Komm schon, wir sind unter Druck, liefere die Dinger aus. + +[PIZ1_28:PIZZA] +Du schon wieder? Liefere die hier schnell aus, Kumpel. + +[PIZ1_29:PIZZA] +Keine Trödelei diesmal, Kumpel. + +[PIZ1_30:PIZZA] +Na los, du fauler Hund, liefere den Fraß rechtzeitig aus. + +[PIZ1_31:PIZZA] +Du wirst nie befördert, wenn du diesmal nicht schneller machst. + +[PIZ1_32:PIZZA] +~r~Ist dir die Pizza zu heiß? + +[PIZ1_33:PIZZA] +~g~Kehre zum Restaurant zurück, um weitere Aufträge zu bekommen. + +[PIZ1_34:PIZZA] +~g~Pizza geliefert, hier ist dein Geld. + +[PIZ_WON:PIZZA] +Pizza-Mission abgeschlossen. Deine max. Gesundheit erhöht sich auf 150 + +{=================================== MISSION TABLE PORN1 ===================================} + +[POR1_15:PORN1] +Hey, Tommy, kommst du auf eine Aufwärmrunde mit rein!? + +[POR1_14:PORN1] +Du bist engagiert! + +[POR1_A:PORN1] +Action! + +[POR1_B:PORN1] +Wow! Der ist aber groß! + +[POR1_C:PORN1] +30cm, das ist Vorschrift, Baby. + +[POR1_D:PORN1] +SCHNITT! Wer ist dieser Idiot? Du da! Was machst du in meinem Studio? Was willst du? + +[POR1_E:PORN1] +Was soll das alles hier? + +[POR1_F:PORN1] +Aliens? Angelruten? + +[POR1_G:PORN1] +Wer hat jemals einen so großen Hai gesehen? + +[POR1_H:PORN1] +Das muss alles raus hier. + +[POR1_I:PORN1] +Wieso bist du in diese Branche gegangen, du Idiot? + +[POR1_J:PORN1] +Hah? + +[POR1_K:PORN1] +Wegen der Pussys, deswegen. Was ist das?? + +[POR1_L:PORN1] +Das ist meine Kunst- SICHERHEITSDIENST! + +[POR1_M:PORN1] +Hör zu, du aufgeblasener Penner, du gehörst jetzt mir. Mir gehört das alles hier. + +[POR1_N:PORN1] +Wir krempeln den Laden hier um. + +[POR1_O:PORN1] +Ich mache dich reich. + +[POR1_P:PORN1] +Äh, du...du bist Tommy Vercetti? Aber ich dachte, du wärst... + +[POR1_Q:PORN1] +Ganz recht. + +[POR1_R:PORN1] +Wir ändern hier ein paar Dinge und dann machen wir richtig Kohle. + +[POR1_S:PORN1] +Hast du dir schon mal überlegt... + +[POR1_T:PORN1] +Aber zuerst brauchen wir mal ein paar hübsche Mädels hier. + +[POR1_U:PORN1] +Ja, Girls sind ok, aber du...wow! + +[POR1_02:PORN1] +~g~Schalte Candys Agent aus, dann komm wieder und hole Candy. + +[POR1_04:PORN1] +Hey, Candy. Ich suche Filmtalente. Interessiert? + +[POR1_05:PORN1] +Klar! Aber da musst du mit meinem Agenten reden. + +[POR1_06:PORN1] +Was zum Teufel soll das? + +[POR1_07:PORN1] +Du hättest heute zu Hause bleiben sollen! + +[POR1_7B:PORN1] +Was sagt man zu so 'nem Arschloch? + +[POR1_08:PORN1] +Hey, Mercedes! + +[POR1_09:PORN1] +Hi, Tommy. Na, ein bisschen feiern? + +[POR1_10:PORN1] +Jetzt nicht, Süße. Bist du an Filmaufnahmen interessiert? + +[POR1_11:PORN1] +Klar. Wenn's schön billig und dreckig ist. + +[POR1_13:PORN1] +~g~Bring die Girls ins Studio zu Steve. + +[POR1_17:PORN1] +Wow, cooler Hai! + +[POR1_18:PORN1] +~r~Mercedes ist erledigt! + +[POR1_20:PORN1] +Tommy, wo willst du hin? Komm zurück! + +[POR1_21:PORN1] +Wo willst du hin? + +[POR1_22:PORN1] +Tommy, wann sehen wir uns mal ganz allein, nur du und ich? + +[POR1_01:PORN1] +~g~Candy Suxxx wäre perfekt für die Hauptrolle! + +[POR1_12:PORN1] +~g~Nimm Candy mit zu deinem Treffen mit Mercedes. + +[POR1_16:PORN1] +Vielleicht später, Schätzchen... + +[POR1_24:PORN1] +~g~Geh zurück und hole Candy. + +[POR1_25:PORN1] +~g~Du hast Candy vergessen. Geh sie holen. + +[POR1_23:PORN1] +~g~Candy wird sich um das Geschäft in ~h~Downtown~g~ kümmern. + +[POR1_26:PORN1] +~g~Da ist Candy. Sie scheint wieder mit dem Kongressabgeordneten Shrub zusammen gewesen zu sein. + +[POR1_27:PORN1] +Los, gehen wir. + +[POR1_28:PORN1] +Tommy, sei vorsichtig! Meine Implantate sind noch nicht versichert! + +[POR1_29:PORN1] +Das nennst du fahren? + +[POR1_30:PORN1] +Danach kann ich keinen Porno mehr machen! + +[POR1_31:PORN1] +Was ist los? Willst du mich umbringen? Ich dachte, ich wäre der Star! + +{=================================== MISSION TABLE PORN2 ===================================} + +[POR2_A:PORN2] +Was macht die Filmerei, Steve? + +[POR2_B:PORN2] +Tja, Candy ist ein Naturtalent, und die Neue ist unersättlich! + +[POR2_C:PORN2] +Die hatte schon vor der ersten Probe das halbe Team durch. + +[POR2_D:PORN2] +Jedenfalls, morgen haben wir einen Außendreh. Wir schießen ein paar Boot-Szenen. + +[POR2_E:PORN2] +Boot-Szenen? Was für Boot-Szenen? + +[POR2_F:PORN2] +Die Fischer zappeln im Netz der Leidenschaft, als ein riesiger Hai daherkommt - + +[POR2_G:PORN2] +Was habe ich über den Riesenhai gesagt? + +[POR2_H:PORN2] +Ich sagte, 'KEIN RIESENHAI', ok? + +[POR2_I:PORN2] +Halt die Kameras auf die Mädels gerichtet! + +[POR2_J:PORN2] +Ok, ok. Hey, Tommy, probieren kann ich's ja mal, oder? + +[POR2_K:PORN2] +Habt ihr die Flyer drucken lassen? + +[POR2_L:PORN2] +Ja, aber man wird uns die Dinger nicht verteilen lassen. Ich meine, + +[POR2_M:PORN2] +die sind einfach zu, äh, zu deutlich. + +[POR2_N:PORN2] +Mach dir darüber keine Gedanken. + +[POR2_O:PORN2] +Ich hab da so meine Ideen, wie wir die verteilen. + +[POR2_P:PORN2] +Ok. Hey, Candy, äh, in meinen Wohnwagen. + +[POR2_01:PORN2] +~g~Hinter den Studios steht ein altes Wasserflugzeug, das mal als Requisite für einen Indy-Film diente. + +[POR2_02:PORN2] +~g~Suche dir einen Checkpoint aus, um mit dem Abwurf der Flyer zu beginnen. + +[POR2_03:PORN2] +~g~Wirf die Flyer überall bis zum End-Checkpoint ab. + +[POR2_04:PORN2] +~r~TREIBSTOFFMANGEL!!! + +[POR2_05:PORN2] +Benutze es, um die Flyer in der Stadt zu verteilen. + +[DILDO:PORN2] +Skimmer-Tankinhalt: + +[POR2_Q:PORN2] +Oh, Mann. + +[PORN2_9:PORN2] +~g~Du hast ~1~ Sekunden, um zu einer Skimmer zurückzukehren, bevor die Mission endet. + +{=================================== MISSION TABLE PORN3 ===================================} + +[POR3_A:PORN3] +Ok, was ist jetzt wieder? + +[POR3_B:PORN3] +Schsch! + +[POR3_C:PORN3] +Nun, nach seiner Begegnung mit den Nympho-Aliens + +[POR3_D:PORN3] +kann unser Held an nichts anderes denken, als an einen riesigen Phallusberg- + +[POR3_E:PORN3] +und da wollen wir die Szene mit dem Bottich voll Kartoffelpüree drehen, aber dann... + +[POR3_F:PORN3] +Das interessiert mich nicht die Bohne. + +[POR3_G:PORN3] +D-Dreh einfach weiter. Weiter, weiter. + +[POR3_H:PORN3] +Hey, Tommy... + +[POR3_I:PORN3] +Du hast am Telefon was von rechtlichen Problemen gesagt? + +[POR3_J:PORN3] +Ach ja! Der Kongressabgeordnete Alex Shrub ist auf Wahlkampftour und buhlt um Stimmen bei den Puritanern. + +[POR3_K:PORN3] +Es heißt, er unterstützt ein Verbot der, sagen wir mal, eher + +[POR3_L:PORN3] +fleischlichen Bereiche der großartigen Unterhaltungsindustrie unseres Landes. + +[POR3_M:PORN3] +Großartig. + +[POR3_N:PORN3] +Candy! Du kennst doch Shrub. + +[POR3_O:PORN3] +Macht ihr auch ausgefallene Sachen? + +[POR3_P:PORN3] +Oh ja, oh ja, oh ja! Ja, ja, ja, JA, Oooooooh! + +[POR3_Q:PORN3] +Bitte sag, dass du das hast. + +[POR3_R:PORN3] +Gehörte das zum, äh... oder war das die Antwort für... + +[POR3_S:PORN3] +Hey, ich kann das nie unterscheiden. Jedenfalls... + +[POR3_T:PORN3] +Das beste wird sein, du folgst ihr nach dem Dreh. + +[POR3_U:PORN3] +Mal sehen, ob sie dich zu ihrem neuen Liebesnest führt. + +[POR3_V:PORN3] +Hast du eine Kamera? + +[POR3_X:PORN3] +Ja. Gebt ihm eine Kamera. + +[POR3_02:PORN3] +~r~Du hast den Abgeordneten erledigt! Jetzt kannst du ihn nicht mehr erpressen. + +[POR3_03:PORN3] +~r~Du hast die Bodyguards des Abgeordneten aufgescheucht. Sie werden ihn sofort wegbringen. + +[POR3_04:PORN3] +Candy, könntest du mich Martha nennen? + +[POR3_05:PORN3] +Oh, Alex - ich meine Martha - ich tu alles, was du willst. + +[POR3_06:PORN3] +Martha, jemand sieht uns zu. Wie erregend. + +[POR3_07:PORN3] +Sie da! Geben Sie mir die Kamera. + +[POR3_01:PORN3] +~g~Folge Candys ~h~Stretch-Limo~g~. + +[POR3_15:PORN3] +~r~Du hast Candys Stretch-Limo geschrottet! + +[POR3_17:PORN3] +~g~Begib dich mit dem Film zurück ins Pornostudio. + +[POR3_19:PORN3] +~r~Der Film ist alle! + +[POR3_21:PORN3] +~g~Du hast Candys Stretch-Limo verloren! + +[POR3_22:PORN3] +~g~Das WK Chariot Hotel gegenüber seines Balkons dürfte eine ideale Position zum Fotografieren bieten. + +[POR3_23:PORN3] +~g~Es gibt eine Seitentür, durch die du in das Hotel kommst. + +[POR3_08:PORN3] +Halte die~h~ ~k~~PED_LOCK_TARGET~ ~w~gedrückt, um mit der Kamera zu ~h~zielen~w~. + +[POR3_09:PORN3] +Halte die~h~ ~k~~PED_LOCK_TARGET~ ~w~gedrückt, um mit der Kamera zu ~h~zielen~w~. + +[POR3_10:PORN3] +Drücke die~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, um ~h~heranzuzoomen ~w~und die~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, um wieder ~h~wegzuzoomen~w~. + +[POR3_11:PORN3] +Drücke die~h~ ~k~~PED_SNIPER_ZOOM_IN~~w~, um ~h~heranzuzoomen ~w~und die~h~ ~k~~PED_SNIPER_ZOOM_OUT~~w~, um wieder ~h~wegzuzoomen~w~. + +[POR3_12:PORN3] +Drücke die~h~ ~k~~PED_FIREWEAPON~~w~, um ein Bild zu machen. + +[POR3_13:PORN3] +Drücke die~h~ ~k~~PED_FIREWEAPON~~w~, um ein Bild zu machen. + +[POR3_14:PORN3] +Drücke die~h~ ~k~~PED_FIREWEAPON~~w~, um ein Bild zu machen. + +[POR3_20:PORN3] +~g~Wenn du ein Transportmittel brauchst, nimm den ~h~Sparrow~g~ hinter dem Haus. + +[POR3_16:PORN3] +~g~Du brauchst drei gute kompromittierende Fotos von Alex Shrub mit Candy. + +[POR3_24:PORN3] +GESCHOSSENE FOTOS: + +{=================================== MISSION TABLE PORN4 ===================================} + +[POR4_A:PORN4] +Tut mir leid, ich kann das jetzt nicht schlucken. + +[POR4_B:PORN4] +Ach KOMM, Darling! + +[POR4_C:PORN4] +Der ist ausgestattet wie ein Pottwal, Herrgott noch mal, + +[POR4_D:PORN4] +wie kannst du dich da nicht einfühlen?! + +[POR4_E:PORN4] +Aber Stevie... + +[POR4_F:PORN4] +Wie geht's meinem Starregisseur? + +[POR4_G:PORN4] +Oh Mann. Der Kampf zwischen künstlerischer Ambition und + +[POR4_H:PORN4] +diesem Rumgenudle tobt unvermindert. + +[POR4_I:PORN4] +Und bevor du fragst: Ja, alle vier Videos werden veröffentlicht, wenn... + +[POR4_J:PORN4] +Schätzchen, kannst du BITTE die Anaconda im Bild halten, + +[POR4_K:PORN4] +die kostet pro Stunde mehr als du! + +[POR4_L:PORN4] +Oh, sorry, Steve. + +[POR4_M:PORN4] +Ich hab mir gedacht, als Werbung für den Start brauchen wir einen richtigen Knaller. + +[POR4_N:PORN4] +Irgendwas, was so richtig Furore macht in der Stadt. Hast du eine Idee? + +[POR4_O:PORN4] +Na ja, früher gab's da immer Galas, + +[POR4_P:PORN4] +Stars, Limos, riesige Suchscheinwerfer am Nachthimmel... + +[POR4_Q:PORN4] +Suchscheinwerfer? Ich hab eine Idee... + +[POR4_R:PORN4] +....ja, ja, ja. Die heißen Mädels mit ihren Paillettenkleidern und die Limos, oh, Premieren. + +[POR4_S:PORN4] +O ja, Ma'am, natürlich, Ma'am, + +[POR4_T:PORN4] +Und die Presse und die Lichter-Flut... + +[POR4_01:PORN4] +~g~Begib dich nach ~y~Downtown~g~und richte den Scheinwerfer auf dem Gebäude aus. + +[POR4_02:PORN4] +~g~Du brauchst ein schnelles Bike, um von Dach zu Dach zu springen. Der Wachmann fährt immer mit einer ~y~PCJ 600~g~zur Arbeit... + +[POR4_03:PORN4] +~g~Du wirst auf die Gebäudedächer müssen. In eines der oberen Büros sollte ein Lift führen... + +[POR4_06:PORN4] +~g~Kehre in das tiefer gelegene Büro zurück, wenn du noch mal auf die Dächer musst. + +[POR4_07:PORN4] +~g~Du brauchst ein Motorrad, um von Gebäude zu Gebäude zu springen. + +[POR4_08:PORN4] +~g~Brich durch das Fenster, um zu starten. Du hast bis 07:00 Zeit. Dann wird es zu hell, um ungesehen hinaufzukommen. + +[POR4_09:PORN4] +~g~Die Pfeile zeigen dir, zu welchem Gebäude du als nächstes springen musst. + +[POR4_10:PORN4] +~r~Es ist zu hell, um ungesehen dort hinaufzukommen. + +[POR4_11:PORN4] +Kehre zur Leiter zurück, wenn du noch mal auf die Dächer musst. + +[POR4_05:PORN4] +~g~Diese Treppe führt zu einem tiefer gelegenen Büro. + +[POR_AS1:PORN4] +FILMSTUDIO-MISSIONEN ERFÜLLT + +[POR_AS2:PORN4] +~g~Inter Global Films generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +{=================================== MISSION TABLE PROT1 ===================================} + +[PRO1_A:PROT1] +Oh, wir müssen diesen Laden umbauen. Das muss älter aussehen. + +[PRO1_B:PROT1] +Ich kann diesen Look nicht ab, Tommy. Was meinst du, sollen wir eine Bar einbauen..? + +[PRO1_D:PROT1] +Hört mal. + +[PRO1_E:PROT1] +Die Zeit ist gekommen, die Stadt zu übernehmen. Sie wartet nur auf uns. + +[PRO1_F:PROT1] +Wir müssen langsam Gebiete einnehmen. + +[PRO1_G:PROT1] +Vice City soll merken, dass wir die neuen Bosse sind, versteht ihr? + +[PRO1_H:PROT1] +Jetzt beruhigt euch mal kurz. Allmählich kapiere ich schon, wie das ganze hier läuft. + +[PRO1_I:PROT1] +Was du brauchst, ist eine legale Fassade, Tommy, Immobilien. Hat mir nicht geschadet. + +[PRO1_J:PROT1] +Wir müssen die Muskeln spielen lassen, sonst war die ganze harte Arbeit umsonst. + +[PRO1_K:PROT1] +Die Geschäftsleute hier wissen, dass Diaz weg ist und weigern sich, Schutzgeld zu zahlen. + +[PRO1_L:PROT1] +Oh, wir könnten es mit Schmiergeld versuchen... + +[PRO1_M:PROT1] +Schmiergeld? Blödsinn! Ich zeig euch, wie man denen Angst macht. + +[PRO1_01:PROT1] +~g~Demoliere die Schaufenster der Läden und die Inhaber werden darum betteln, zahlen zu dürfen. + +[PRO1_03:PROT1] +~r~Du sollst abhauen, nicht Kaffee trinken gehen. + +[PRO1_04:PROT1] +Mein Lebenswerk! Zerstört! + +[PRO1_05:PROT1] +Ich bin ruiniert...RUINIERT!! + +[PRO1_06:PROT1] +Ich zahle einen Haufen Schutzgeld! + +[PRO1_07:PROT1] +Mein schönes Schaufenster! + +[PRO1_08:PROT1] +Mein Laden! Mein schöner Laden! + +[PRO1_09:PROT1] +Vercetti. Merkt euch den Namen. + +[PRO1_10:PROT1] +Ich bin jetzt der Boss in der Stadt. ICH! + +[BUYP1:PROT1] +Du kannst jetzt in bestimmten Gegenden auf der Karte Objekte kaufen. + +[BUYP2:PROT1] +Wenn du ein Gebäude mit grüner Markierung siehst, kannst du dieses kaufen. + +[PRO1_N:PROT1] +Ich bin in fünf Minuten zurück... + +[PRO1_11:PROT1] +~g~Begib dich zum ~y~North Point Einkaufszentrum~g~ in ~y~Vice Point~g~. + +[PRO1_12:PROT1] +~g~Zerbrich die Schaufenster eines jeden Ladens, und die Inhaber werden um neuen Schutz betteln. + +[PRO1_C:PROT1] +Du bist mein Anwalt, nicht mein Innenarchitekt. Klar? + +[BUYP3:PROT1] +Stell dich in die Markierung und drücke die ~h~~k~~PED_ANSWER_PHONE~~w~-Taste, um das Objekt zu kaufen. + +[PRO1_13:PROT1] +~g~Du hast fünf Minuten, um alle zu demolieren. + +{=================================== MISSION TABLE PROT2 ===================================} + +[PRO2_01:PROT2] +~g~Schalte die Wachen aus, die die Front Page Bar beschützen und finde raus, wer hinter ihnen steckt. + +[PRO2_08:PROT2] +~g~Die DBP Security wird wissen, dass du kommst. Schnapp sie dir, ehe sie abhauen. + +[PRO2_A:PROT2] +Was gibt's für Probleme? + +[PRO2_B:PROT2] +Eine Bar weigert sich zu zahlen. + +[PRO2_C:PROT2] +Die glauben, die werden von so einer Schlägerbande beschützt. + +[PRO2_D:PROT2] +Aber keine Sorge, Tommy, ich regle das. + +[PRO2_E:PROT2] +Das nennst du regeln? + +[PRO2_F:PROT2] +Ihr zwei. Hebt mal den Hintern... + +[PRO2_G:PROT2] +Los. + +[PRO2_10:PROT2] +Zwei sind abgehauen. Finde sie und bring die Sache zuende. + +[PRO2_11:PROT2] +Steig in den Wagen, Nichtsnutz. + +[PRO2_02:PROT2] +Dein Schutz braucht ein bisschen mehr Schutz. + +[PRO2_03:PROT2] +Ach, verdammt! Nicht schon wieder! Das brauche ich wirklich nicht! + +[PRO2_04:PROT2] +Diese Idioten arbeiten eigentlich für die DBP Security gleich um die Ecke. + +[PRO2_05:PROT2] +Macht ihr das mal unter euch aus. + +[PRO2_06:PROT2] +Wir sehen uns. + +[PRO2_07:PROT2] +Ja, ja. Wenn's sein muss. + +[PRO2_09:PROT2] +~g~Begib dich zur Front Page Bar und sprich mit dem Besitzer. + +{=================================== MISSION TABLE PROT3 ===================================} + +[PRO3_A:PROT3] +Du Trottel! Was hast du dir dabei gedacht?! + +[PRO3_B:PROT3] +Ist dir klar, was das bedeutet?! + +[PRO3_C:PROT3] +Das könnte das Ende für uns alle sein! + +[PRO3_D:PROT3] +Der Zeitzünder muss hin gewesen sein. + +[PRO3_E:PROT3] +Der Laden war mit Sprengstoff vollgepackt wie eine Feuerwerkfabrik. + +[PRO3_F:PROT3] +Dann hat jemand den Cops einen Tipp gegeben... + +[PRO3_G:PROT3] +Was gibt's für Probleme, Jungs? + +[PRO3_H:PROT3] +Mike sollte einen Laden im Einkaufszentrum abfackeln, + +[PRO3_I:PROT3] +aber er hat's vermasselt und jetzt wimmelt es dort von Bullen. + +[PRO3_J:PROT3] +Wir müssen unser Zeug holen und verschwinden! + +[PRO3_K:PROT3] +Langsam, ihr beiden, lasst mich kurz nachdenken! + +[PRO3_L:PROT3] +Tommy Vercetti läuft nicht einfach weg. + +[PRO3_M:PROT3] +Die Cops werden das Gebäude sorgfältig durchkämmen, oder? + +[PRO3_N:PROT3] +Aber das dauert. + +[PRO3_O:PROT3] +Wir müssen den Laden selbst abfackeln. + +[PRO3_P:PROT3] +Ja, aber... + +[PRO3_Q:PROT3] +Nur ein Cop kommt auch nur in die Nähe von dem Laden! + +[PRO3_R:PROT3] +Dann gehen wir eben als Cops. + +[PRO3_S:PROT3] +Wir brauchen Uniformen und einen Streifenwagen. + +[PRO3_T:PROT3] +Und das alles nur wegen dir, Mike. + +[PRO3_U:PROT3] +Tut mir leid. + +[PRO3_V:PROT3] +Ich hab's. + +[PRO3_W:PROT3] +Wir müssen die Cops hereinlocken, + +[PRO3_X:PROT3] +dann sperren wir sie ein + +[PRO3_Y:PROT3] +und überwältigen sie. + +[PRO3_Z:PROT3] +Guter Plan. Los geht's! + +[PRO3_A1:PROT3] +Ok. + +[PRO3_01:PROT3] +Ok, Lance, machen wir die Cops auf uns aufmerksam! + +[PRO3_02:PROT3] +~g~ Nimm den Streifenwagen und lege die Bombe im Tarbrush Coffee Shop im Einkaufszentrum. + +[PRO3_03:PROT3] +~g~Du hast Lance vergessen. Hole ihn. + +[PRO3_04:PROT3] +~g~ Los geht's. + +[PRO3_05:PROT3] +~r~Du hast Lance erledigt! + +[PRO3_07:PROT3] +~g~Die Tarnung ist aufgeflogen. Beeilung, platziere die Bombe! + +[PRO3_09:PROT3] +Fessle und kneble sie! + +[PRO3_10:PROT3] +Uuh! Passt perfekt! + +[PRO3_11:PROT3] +Bisschen eng im Schritt vielleicht... + +[PRO3_12:PROT3] +Oh ja, ja. Meine auch, meine auch. + +[PRO3_13:PROT3] +Vorsicht, Bruder! Kein Cop fährt so schlecht! + +[PRO3_14:PROT3] +Denk dran, lächle die anderen Cops an. + +[PRO3_15:PROT3] +Hallo, Officer. Hübsche Marke, hübsche Marke. + +[PRO3_16:PROT3] +Ganz toll, Lance. + +[PRO3_17:PROT3] +Ok, die Zünder sind auf 5 Sekunden gestellt. + +[PRO3_18:PROT3] +5 Sekunden?!! Nichts wie raus hier! + +[PRO3_19:PROT3] +Jetzt sind sie so richtig stocksauer. + +[PRO3_20:PROT3] +~g~Bring 2 Cops dazu, dir in die Garage zu folgen. + +[PRO3_21:PROT3] +~g~Beschaff dir einen Fahndungslevel, damit dir die Cops in die Garage folgen. + +[PRO3_22:PROT3] +~g~Das Garagentor ist blockiert! Du musst es freiräumen, damit es schließen kann. + +[PRO3_23:PROT3] +~g~Stell dich in die Markierung, um die Bombe zu platzieren. + +[PRO3_24:PROT3] +~g~Verschwinde aus der Nähe des Cafés! + +[PRO_AS1:PROT3] +SCHUTZGELD-MISSIONEN ERFÜLLT + +[PRO_AS2:PROT3] +~g~Das Vercetti Estate generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +[PRO3_08:PROT3] +~g~Du musst zurück zu ~h~Vercetti Estate~g~ auf ~h~Starfish Island~g~. + +{=================================== MISSION TABLE RACES ===================================} + +[RACES_2:RACES] +~g~Du brauchst ein Fahrzeug! Das ist kein Wettlaufen! + +[RACES_3:RACES] +3..2..1.. LOS, LOS, LOS! + +[RACES_8:RACES] +~r~Du hast das Rennen nicht gewonnen! + +[RACES00:RACES] +Rennen ~1~: + +[RACES01:RACES] +Todeskaracho + +[RACES02:RACES] +Ocean Drive + +[RACES03:RACES] +Küsten-Rallye + +[RACES04:RACES] +Capital Cruise + +[RACES05:RACES] +Tour! + +[RACES06:RACES] +V.C. Endurance + +[RACES07:RACES] +Startgebühr: $~1~ + +[RACES08:RACES] +Bestzeit: ~1~:~1~ + +[RACES09:RACES] +Beste Platzierung: 1. + +[RACES10:RACES] +Beste Platzierung: 2. + +[RACES11:RACES] +Beste Platzierung: 3. + +[RACES12:RACES] +Beste Platzierung: 4. + +[RACES13:RACES] +Streckenlänge: ~1~.~1~ km + +[RACES15:RACES] +Bestzeit: Nicht verfügbar + +[RACES16:RACES] +Beste Platzierung: Nicht verfügbar + +[RACES19:RACES] +Du hast nicht genug Geld, um an diesem Rennen teilzunehmen. + +[RACES22:RACES] +Bestzeit: ~1~:0~1~ + +[RACES23:RACES] +Streckenlänge: ~1~.~1~ Meilen + +[RACES_1:RACES] +~g~Schnapp dir ein schnelles Fahrzeug und begib dich zur Startlinie. + +[RACEHLP:RACES] +~w~Drücke die~h~ ~k~~PED_SPRINT~~w~, um das ausgewählte Rennen zu starten. Drücke die~h~ ~k~~VEHICLE_ENTER_EXIT~~w~, um abzubrechen. + +{=================================== MISSION TABLE RCHELI1 ===================================} + +[WRECKED:RCHELI1] +~r~Das Fahrzeug ist Schrott! + +[RCH1_4:RCHELI1] +Verbleibende Checkpoints: + +[RCH1_7:RCHELI1] +~g~Es gibt insgesamt 20 Checkpoints. + +[RCH1_12:RCHELI1] +~g~Der ferngesteuerte Helikopter gerät außer Reichweite! + +[RCH1_13:RCHELI1] +~r~Der ferngesteuerte Helikopter ist außer Reichweite! + +[RCH1_8:RCHELI1] { reVC update } +~g~Wenn du diese Mission abbrechen willst, drücke die ~h~~k~~VEHICLE_FIREWEAPON~~g~, um deinen Heli zu sprengen. + +{=================================== MISSION TABLE RCPLNE1 ===================================} + +[RCPL1_4:RCPLNE1] +~g~Miss dich mit 3 anderen ferngesteuerten Maschinen in einem CHECKPOINT RENNEN + +[RCPL1_5:RCPLNE1] +~g~Flieg durch die Checkpoints, die über Vice City verteilt sind. + +[RCPL1_6:RCPLNE1] { reVC update } +~g~Wenn du diese Mission abbrechen willst, drücke die ~h~~k~~VEHICLE_FIREWEAPON~~g~, um dein Flugzeug zu sprengen. + +[RCPL1_8:RCPLNE1] +~g~Dein ferngesteuertes Flugzeug gerät außer Reichweite! + +[RCPL1_9:RCPLNE1] +~r~Dein ferngesteuertes Flugzeug ist außer Reichweite! + +{=================================== MISSION TABLE RCRACE1 ===================================} + +[RCRC1_1:RCRACE1] +~g~Liefere dir mit 3 anderen RC Bandits ein CHECKPOINT-RENNEN über 2 RUNDEN. + +[RCRC1_3:RCRACE1] +~g~Letzte Runde! + +[RCR1_4:RCRACE1] +Verbleibende Runden: + +[RCR1_1:RCRACE1] +~g~Tritt in einem Checkpoint-Rennen gegen 3 andere ferngesteuerte Autos an. + +[RCR1_2:RCRACE1] +~g~Du musst als erster 2 volle Runden auf dem Kurs fahren! + +[RCR1_6:RCRACE1] +~g~Dein ferngesteuertes Auto gerät außer Reichweite! + +[RCR1_7:RCRACE1] +~r~Dein ferngesteuertes Auto ist außer Reichweite! + +{=================================== MISSION TABLE ROCK1 ===================================} + +[RBM1_A:ROCK1] +AllllllllRrrighttt! + +[RBM1_B:ROCK1] +Fantastisch, einfach fantastisch! + +[RBM1_D:ROCK1] +Hey, kennst du die Jungs von Love Fist? + +[RBM1_E:ROCK1] +Nein, aber ihre Musik habe ich immer geliebt. + +[RBM1_F:ROCK1] +Ich stelle dir die Band vor. + +[RBM1_G:ROCK1] +Das ist P, Percy, Dick, und Willy ist auf dem Klo. Und das vorher in der Kabine war Jezz. + +[RBM1_H:ROCK1] +Jungs, ich möchte euch einen guten Freund vorstellen. + +[RBM1_I:ROCK1] +Das ist Tommy. Wir kennen uns schon ewig. + +[RBM1_J:ROCK1] +Alles paletti, Mann. + +[RBM1_K:ROCK1] +Und, äh, wie war dein Name noch mal? + +[RBM1_L:ROCK1] +Lass das, Jezz. Merk dir eines, + +[RBM1_M:ROCK1] +mit mir kannst du diese Spielchen nicht machen. + +[RBM1_N:ROCK1] +Ich bin dir einfach über, Sonnenscheinchen. + +[RBM1_O:ROCK1] +Die Sache ist die, Tom, die Jungs brauchen Hilfe. + +[RBM1_P:ROCK1] +Die haben hier keine Connections, kein Vitamin B. + +[RBM1_Q:ROCK1] +Wir brauchen Stoff, Alter. + +[RBM1_R:ROCK1] +Damit wir den alten Love Fist Spirit finden, verstehst du?! + +[RBM1_S:ROCK1] +Wir sind hier in Vice City, Mann. Wo liegt das Problem? + +[RBM1_U:ROCK1] +Love Juice, Mann! + +[RBM1_V:ROCK1] +Love Juice? + +[RBM1_W:ROCK1] +Genau. 2 Teile TNT-Whiskey, 1 Teil Koks, 5 Päckchen Brause und 1 Liter Sprit. + +[RBM1_X:ROCK1] +Kannst du uns weiterhelfen, Alter? + +[RBM1_Y:ROCK1] +Es würde den Jungs wirklich viel bedeuten. + +[RBM1_Z:ROCK1] +Du kannst den Jungs doch helfen, oder? + +[RBM1_7:ROCK1] +~r~Du hast den Love Juice nicht rechtzeitig besorgt! + +[RBM1_8:ROCK1] +~r~Mercedes ist erledigt! + +[RBM1_10:ROCK1] +~r~Du Idiot! Du hast die Ware vernichtet! + +[RBM1_13:ROCK1] +~g~Bring den Love Juice und Mercedes zu der Band bevor sie auf die Bühne muss. + +[RBM1_15:ROCK1] +~r~Du hast den Dealer verloren, unser Geld und den Stoff! + +[RBM1_17:ROCK1] +~g~Knöpf dir den Dealer vor und hol dir den Stoff! + +[MOB_07A:ROCK1] +Hey, die Jungs könnten ein bisschen Gesellschaft vertragen, falls du weißt, was ich meine... + +[MOB_07B:ROCK1] +Da kenne ich genau die richtige. + +[ROK1_5:ROCK1] +Hey, Mercedes! + +[ROK1_6:ROCK1] +Hi, Tommy. Na, wie läuft's so? + +[ROK1_7:ROCK1] +Alles bestens. Hör mal, willst du die Jungs von Love Fist verführen? + +[ROK1_8:ROCK1] +Ok, aber dafür bist du mir einen Gefallen schuldig. + +[RBM1_14:ROCK1] +~g~Du brauchst ein Auto oder Motorrad! + +[RBM1_1:ROCK1] +~g~Hol Mercedes in ihrer Wohnung ab. + +[RBM1_12:ROCK1] +~g~Besorg die Zutaten für den 'Love Juice' von dem Dealer. + +[ROK1_2:ROCK1] +NICHT MEHR BENÖTIGT + +[ROK1_3:ROCK1] +NICHT MEHR BENÖTIGT + +[MERC_39:ROCK1] +Wir sehen uns später, Big Boy. + +[RBM1_C:ROCK1] +Hey, Tommy! Schön, dass du kommen konntest. + +[RBM1_9:ROCK1] +~g~Besorg von dem Dealer Love Juice für Love Fist! + +[ROK1_1A:ROCK1] +Suchst du was Bestimmtes? Ich habe genau, was du brauchst! + +[ROK1_9:ROCK1] +Danke für die Kohle, du Trottel! + +[RBM1_T:ROCK1] +Wir brauchen Love Juice, Mann, klar? + +{=================================== MISSION TABLE ROCK2 ===================================} + +[RBM2_A:ROCK2] +Tommy, Mann, bin ich froh, dich zu sehen! + +[RBM2_B:ROCK2] +Was ist los? + +[RBM2_C:ROCK2] +Schlechte Vibes, Tommy... + +[RBM2_E:ROCK2] +Da ist so 'n Typ, wir kennen ihn kaum, aber er kennt uns. + +[RBM2_F:ROCK2] +So wie er da. Weiß alles über uns. + +[RBM2_G:ROCK2] +Weiß, dass Willy auf Damenunterwäsche steht, eh! + +[RBM2_H:ROCK2] +Oder dass Percy auf Duran Duran steht! + +[RBM2_K:ROCK2] +Ja, die Liebesrakete, schon gut. Aber hör zu, dieser Typ... + +[RBM2_L:ROCK2] +ja, ja, der Typ will Love Fist ausknipsen. + +[RBM2_M:ROCK2] +Ausknipsen, Tommy. + +[RBM2_N:ROCK2] +Love Fist soll verschwinden. Wie es immer heißt: Die Besten sterben jung. + +[RBM2_O:ROCK2] +Aber Tommy, du musst Love Fist retten! + +[RBM2_P:ROCK2] +Wir geben in zwei Stunden eine Autogrammstunde, und ich glaube... + +[RBM2_Q:ROCK2] +Und die Jungs glauben, dass der Kerl dort irgendwas plant. + +[RBM2_1:ROCK2] +~g~Fahre die Limo zum Ort der Autogrammstunde und versuche, den Irren zu finden. + +[RBM2_2:ROCK2] +~r~Du hast den Wagen der Band geschrottet! + +[RBM2_3:ROCK2] +~g~Geh zu der Autogrammstunde! + +[RBM2_4:ROCK2] +~g~Erledige den Irren. Lass ihn nicht entkommen! + +[RBM2_5:ROCK2] +~r~Du hast ihn verloren, du Idiot! + +[RBM2_7:ROCK2] +~r~Die Fans wurden angegriffen. Der Irre wird nicht auftauchen! + +[RBM2_8:ROCK2] +~r~Die Security-Leute wurden angegriffen. Der Irre wird nicht auftauchen! + +[PSYCH_1:ROCK2] +Love Fist wird in der Hölle braten! + +[PSYCH_2:ROCK2] +Love Fist hat mein Leben zerstört! + +[RBM2_I:ROCK2] +Halt's Maul, du Irrer! Nur weil Jezz auf Schafe steht. + +[RBM2_R:ROCK2] +Hey, Schnauze! + +[RBM2_D:ROCK2] +Das ist kein Spaß, das ist 'ne brutale Geschichte, brutal, ok? + +[RBM2_J:ROCK2] +Ich sage nur, die Liebesrakete, ok? + +{=================================== MISSION TABLE ROCK3 ===================================} + +[RBM3_A:ROCK3] +Tommy! Tommy! Tommy, Mann, der Irre ist wieder da! + +[RBM3_B:ROCK3] +Was ist denn los? + +[RBM3_C:ROCK3] +Dieser Irre lässt Love Fist nicht in Ruhe! + +[RBM3_D:ROCK3] +Du hast ihn nicht erwischt, Mann. Und jetzt ist er wieder da. + +[RBM3_E:ROCK3] +Ja, ja, ja, und es ist nämlich so... + +[RBM3_F:ROCK3] +wir brauchen für die Limo einen Fahrer, dem wir vertrauen können, + +[RBM3_G:ROCK3] +weil der Irre uns ständig bedroht! + +[RBM3_I:ROCK3] +Wir scheißen uns alle in die Hosen, Mann. + +[RBM3_J:ROCK3] +Ok, Jungs, nur die Ruhe. Ich mach das schon. + +[RBM3_K:ROCK3] +Normalerweise würde ich keine besoffenen, schottischen Tucken durch die Gegend kutschieren, + +[RBM3_L:ROCK3] +aber für euch mache ich eine Ausnahme. + +[ROK3_03:ROCK3] +Dann mach mir einen großen. + +[ROK3_04:ROCK3] +Hey, Tommy, nun mach mal halblang, Mann. + +[ROK3_08:ROCK3] +Langsam wird's langweilig. + +[ROK3_09:ROCK3] +Halt bloß das Pedal durchgedrückt!! + +[ROK3_61:ROCK3] +Wir müssen die Bombe finden! + +[ROK3_28:ROCK3] +Ich werd Bass in der Hölle spielen. + +[ROK3_30:ROCK3] +Tu doch einer was! + +[ROK3_32:ROCK3] +Ok, Obermacho, dann tu du doch was! + +[ROK3_34:ROCK3] +Willy könnte den TNT-Whiskey mit einem Strohhalm raussaugen. + +[ROK3_37:ROCK3] +Gebt Willy einen Strohhalm! + +[ROK3_41:ROCK3] +Welchen Draht, Tommy? + +[ROK3_42:ROCK3] +Den grünen. + +[ROK3_43:ROCK3] +Da ist kein grüner. Oder ist der hier grün? + +[ROK3_44:ROCK3] +Sieht für dich einer von den Drähten grün aus? + +[ROK3_49:ROCK3] +Ich hab euch doch jahrelang nur mitgeschleppt. + +[ROK3_51:ROCK3] +Ein großes keifendes Weib. + +[ROK3_52:ROCK3] +Ja. + +[ROK3_53:ROCK3] +Klappe jetzt und zieht einen Draht raus. + +[ROK3_54:ROCK3] +Welchen? + +[ROK3_55:ROCK3] +Den da... + +[ROK3_56:ROCK3] +NEIN! + +[ROK3_57:ROCK3] +Mann, alles ok. Wir sind nicht hochgegangen, Alter. Tommy, Mann, gut gemacht. Rock 'n' Roll, Mann. + +[ROK3_58:ROCK3] +Müssen wir nicht zu einem Gig? Krach machen? Groupies abgreifen? + +[ROK3_59:ROCK3] +LOVE FIST! + +[ROK3_60:ROCK3] +Bist du fertig mit der Pulle? + +[RBM3_4:ROCK3] +~r~Love Fist ist Geschichte! + +[RBM3_6:ROCK3] +DETONATION: + +[RBM3_1:ROCK3] +~g~Chauffiere Love Fist zu ihrem Auftritt. + +[RBM3_2:ROCK3] +Falls du versuchst, aus dem Auto auszusteigen, wenn die Bombe scharfgemacht ist, wird sie explodieren... + +[RBM3_3:ROCK3] +Ist der Detonations-Balken am Anschlag, explodiert die Bombe. + +[RBM3_8:ROCK3] +Je schneller du fährst, desto niedriger der Detonations-Balken. + +[RBM3_7:ROCK3] +~g~BOMBE ENTSCHÄRFT! + +[ROK3_6A:ROCK3] +~g~Love Fist. Ihr habt den Äther lange genug verschmutzt! + +[ROK3_6B:ROCK3] +~g~Ich wollte euer Freund sein. Jetzt will ich, dass ihr untergeht. + +[ROK3_62:ROCK3] +Wir dachten, wir zeigen dir mal unseren Rocktempel... + +[ROK3_63:ROCK3] +Kannst dir die Power von Love Fist reinziehen! + +[ROK3_64:ROCK3] +Hör sich das einer an, Mann. Das ist Pappmaché und Klebeband. + +[ROK3_65:ROCK3] +Hey, für die Kids ist es ein Tempel, und wir sind die Priester! + +[ROK3_66:ROCK3] +Tja, wenn die Kids es toll finden, dass ihre Priester dicht und unmusikalisch sind, + +[ROK3_67:ROCK3] +was soll man da sagen? + +[ROK3_68:ROCK3] +Oh, Mist, das Ding frisst schon wieder das Band. + +[ROK3_69:ROCK3] +Wenn das so ist, müssen wir live spielen. + +[ROK3_70:ROCK3] +Ooooh Shit! Mein Darm... + +[ROK3_01:ROCK3] +Endlich, Mann, Zeit für einen wohlverdienten Drink. + +[ROK3_02:ROCK3] +Die Halle ist nur 100 Meter die Straße runter. + +[ROK3_05:ROCK3] +Ich werd irre, wenn ich keinen Sprit kriege. + +[ROK3_07:ROCK3] +Tommy, mein Freund, wir müssen die Band retten! + +[ROK3_29:ROCK3] +Tommy, bleib auf dem Gas, Alter. + +[ROK3_31:ROCK3] +Ganz toll. 'Tu doch einer was.' Was ist denn das für eine Ansage? Da kenn ich mutigere Mädels. + +[ROK3_33:ROCK3] +Alter, ich bin Musiker. Vom Bomben-Entschärfen hab ich keinen blassen Dunst. + +[ROK3_35:ROCK3] +Eben, so was liegt dir doch, was ich so höre. + +[ROK3_38:ROCK3] +Einen Strohhalm?! Dies ist der Tour-Bus von Love Fist! + +[ROK3_39:ROCK3] +Wo zum Geier sollte hier ein Strohhalm sein? + +[ROK3_46:ROCK3] +Ich hätte euch alle rausschmeißen sollen, als es noch ging, Mann. + +[ROK3_47:ROCK3] +Kapitalist. + +[ROK3_48:ROCK3] +Kameradenschwein. + +[ROK3_73:ROCK3] +Jezz spielt das Band ab, + +[RBM3_9:ROCK3] +Wenn du angehalten wirst oder langsam fährst, wächst der Detonations-Balken. + +[ROK3_50:ROCK3] +Halt die Klappe. Du bist ein Idiot. + +[ROK3_36:ROCK3] +Hey, ich war an dem Abend vielleicht komplett zugedröhnt! + +[RBM3_H:ROCK3] +Ich scheiß mir in die Hose, Mann. Ich will zu meiner Mama! + +[ROK3_45:ROCK3] +Oh nein. Im Angesicht des Todes sieht alles grün aus. + +[ROK3_6C:ROCK3] +~g~Wenn ihr langsamer werdet, geht eure Limo hoch, IHR UND EURE HAARIGEN ÄRSCHE mit dazu! + +[ROK3_71:ROCK3] +Wir müssen uns ranhalten. Danke nochmal, Tommy, du hast es echt drauf. Ciao. + +[ROK3_1:ROCK3] +Endlich, Mann, Zeit für einen wohlverdienten Drink. Die Halle ist nur 100 Meter die Straße runter. + +[ROK3_2:ROCK3] +Mach mir mal einen großen. Hey, Tommy, leg mal 'nen anderen Sound auf, Mann. + +[ROK3_3:ROCK3] +Ich werd wirr im Kopf, wenn nichts zum Headbangen läuft. Hey, Tommy, leg mal dieses Band ein. + +[ROK3_4:ROCK3] +Love Fist. Ihr habt den Äther lange genug verschmutzt! Ich wollte euer Freund sein. + +[ROK3_5:ROCK3] +Jetzt will ich, dass ihr untergeht. Wenn ihr langsamer werdet, geht eure Limo hoch, IHR UND EURE HAARIGEN ÄRSCHE mit dazu! + +[ROK3_6:ROCK3] +Tommy, mein Freund, du musst die Band retten! Langsam wird's langweilig. Halt bloß das Pedal durchgedrückt!! + +[ROK3_7:ROCK3] +Wir müssen die Bombe finden! Können wir nicht einfach den ganzen Tag rumfahren? Wir haben jedenfalls jede Menge zu saufen. + +[ROK3_8:ROCK3] +Könnte die Bombe nicht unter der Motorhaube sein? Da kommen wir nie ran, ohne anzuhalten! Wir werden alle sterben! Ich besauf mich! + +[ROK3_9:ROCK3] +Hey, hier gibt's eine Warteschlange, Alter! Die Lösung liegt nicht in der Minibar! Weg da! + +[ROK3_10:ROCK3] +Hey, aus der Wodkapulle kommen Drähte raus! Das ist kein Wodka, das ist TNT-WHISKEY! + +[ROK3_11:ROCK3] +WAAAAAAGGGHHHH!!!! Das Ding ist scharfgemacht!! WAAAAAAAAAAGGGHHHHHH!!!! + +[ROK3_12:ROCK3] +Man hat mir immer gesagt, der Alk wird mich killen... Das kenn ich aus dem Fernsehen. Du musst einen der Drähte rausziehen. Welchen Draht? Weiß ich doch nicht, Mann. + +[ROK3_13:ROCK3] +Keinen Schimmer. Willy, sag doch mal was. Ich werd Bass in der Hölle spielen. + +[ROK3_14:ROCK3] +Tommy, bleib auf dem Gas, Mann. Tu doch einer was! Ganz toll... + +[ROK3_15:ROCK3] +'Tu doch einer was.' Was ist denn das für eine Ansage? Da kenn ich mutigere Mädchen. Ok, Obermacho, dann tu du doch was! + +[ROK3_16:ROCK3] +Alter, ich bin Musiker. Mit Bomben kenne ich mich nicht aus. Willy könnte den TNT-Whiskey mit einem Strohhalm raussaugen. + +[ROK3_17:ROCK3] +Eben, so was liegt dir doch, was ich so höre. Hey, ich war total blau an dem Abend, das wisst ihr ganz genau! + +[ROK3_18:ROCK3] +Gebt Willy einen Strohhalm! Einen Strohhalm?! Wi sind im Band-Auto von Love Fist! + +[ROK3_19:ROCK3] +Wo zum Geier sollte hier ein Strohhalm sein? Welchen Draht, Tommy? Den grünen. Da ist kein grüner. + +[ROK3_20:ROCK3] +Oder ist der hier grün? Sieht für dich einer von den Drähten grün aus? + +[ROK3_21:ROCK3] +Oh nein. Im Angesicht des Todes sieht alles grün aus. Ich hätte euch alle rausschmeißen sollen, als es noch ging, Mann. + +[ROK3_22:ROCK3] +Kameradenschwein. Kapitalist. Ich hab euch doch jahrelang nur mitgeschleppt. Halt die Klappe. Du bist ein Idiot. + +[ROK3_23:ROCK3] +Ein großes keifendes Weib. Ja. Klappe jetzt und zieht einen Draht raus. Welchen? Den da... + +[ROK3_24:ROCK3] +NEIN! Mann, alles ok. Wir sind nicht hochgegangen, Alter. + +[ROK3_25:ROCK3] +Tommy, Mann, gut gemacht. Rock 'n' Roll, Mann. Müssen wir nicht zu einem Gig? + +[ROK3_26:ROCK3] +Krach machen? Groupies abgreifen? LOVE FIST! + +[ROK3_27:ROCK3] +Bist du fertig mit der Pulle? + +{=================================== MISSION TABLE SERG1 ===================================} + +[TEX1_0:SERG1] +~g~Die Zielperson ist auf der Driving Range. Sorg dafür, dass er seinen letzten Golfball geschlagen hat! + +[TEX1_A:SERG1] +Komm rein und setz dich auf deinen Hintern, Junge. + +[TEX1_B:SERG1] +Mein Daddy hat immer gesagt, einem geschenkten Gaul schaut man nicht ins Maul. Hat's auch nie getan. + +[TEX1_C:SERG1] +Ein Gläschen alter Kentucky gefällig? + +[TEX1_D:SERG1] +Nein danke. + +[TEX1_E:SERG1] +Ein nüchterner Denker, das gefällt mir. + +[TEX1_F:SERG1] +Im Immobiliengeschäft geht's nicht um hochtrabende Verträge. + +[TEX1_G:SERG1] +Es geht um Land. Und darum, dieses Land zu kriegen. Kannst du mir folgen? + +[TEX1_H:SERG1] +Oh, ja. + +[TEX1_I:SERG1] +Ich will, dass so ein sturer Hund sein Land hergibt. + +[TEX1_J:SERG1] +Mir scheint, du könntest ihn dazu überreden. + +[TEX1_K:SERG1] +Ich bin der reinste Überredungskünstler. + +[TEX1_L:SERG1] +Ja. Er wird im Country Club sein, auf dem Golfplatz. + +[TEX1_M:SERG1] +Knarren sind dort verboten. Seine Bodyguards werden also unbewaffnet sein. + +[TEX1_N:SERG1] +Du sollst ihn so richtig gründlich vermöbeln. + +[TEX1_O:SERG1] +Hier, ich hab dir einen Mitgliedsausweis besorgt. Aber du brauchst passendere Kleidung. + +[TEX1_2:SERG1] +~g~Begib dich jetzt zum Leaf Links Golf Club. + +[TEX1_3:SERG1] +Wer ist der Kerl? Jungs, nehmt ihn euch vor. + +[TEX1_6:SERG1] +Hübscher Hintern, Baby! + +[TEX1_7:SERG1] +Bin ich das? + +[TEX1_8:SERG1] +Jedesmal wenn du in einen Golfwagen steigst, erhältst du automatisch einen Golfschläger, vorausgesetzt, du hast nicht schon eine Nahkampfwaffe. + +[TEX1_9:SERG1] +Schnapp ihn dir! + +[TEX1_10:SERG1] +Mach den Irren fertig! + +[TEX1_1:SERG1] +~g~Besorg dir bei Jocksport Golferklamotten. + +{=================================== MISSION TABLE SERG2 ===================================} + +[TEXEXIT:SERG2] +~g~Jetzt verschwinde aus Little Haiti! + +[TEX_2A:SERG2] +~g~Großartig! Sie haben dich bemerkt! + +[TEX_2B:SERG2] +~r~Narr! Die Leute müssen SEHEN, dass der Täter ein Kubaner ist! + +[TEX_2C:SERG2] +~g~Besorge dir bei Rafael's Kleidung in den Farben der kubanischen Gang. + +[TEX_2D:SERG2] +~g~Nimm dir jetzt den Boss der Haitianer in Romeros Beerdigungsinstitut vor. + +[TEX2_A:SERG2] +Tommy, das ist Donald Love. Donald, das ist Tommy Vercetti, + +[TEX2_B:SERG2] +der neueste Draufgänger hier in der Stadt. + +[TEX2_C:SERG2] +Ja...äh... + +[TEX2_D:SERG2] +Donald, sei still und hör zu. Vielleicht kannst du was lernen. + +[TEX2_E:SERG2] +Also. Nichts lässt Immobilienpreise schneller abstürzen als ein guter alter Bandenkrieg. + +[TEX2_F:SERG2] +Außer vielleicht eine Katastrophe, eine biblische Plage oder so, + +[TEX2_G:SERG2] +aber das ginge hier wohl zu weit. + +[TEX2_H:SERG2] +Kannst du mir folgen, Brillenschlange? + +[TEX2_I:SERG2] +Jüngst starb ein haitianischer Gang-Boss. Man tippt, es waren die Kubaner, aber keiner weiß es. + +[TEX2_J:SERG2] +Aber wir wollen sicher gehen. Du verkleidest dich als kubanischer Hombre + +[TEX2_K:SERG2] +und machst Krawall bei der Beerdigung. Misch sie auf und dann verzieh dich. + +[TEX2_L:SERG2] +Kannst du mir folgen, Donald? + +[TEX2_M:SERG2] +Das dürfte das Fass zum Überlaufen bringen, was? + +[TEX2_N:SERG2] +Und wir lehnen uns zurück und sehen zu, wie die Preise purzeln. + +{=================================== MISSION TABLE SERG3 ===================================} + +[TEX3_A:SERG3] +Pass auf, Junge. Ich habe ein Problem und denke, du könntest mir weiterhelfen. + +[TEX3_B:SERG3] +Ich bin kein Bauunternehmer. + +[TEX3_C:SERG3] +Nein, ich dachte mehr an deine Talente als Abrissunternehmer. + +[TEX3_D:SERG3] +Das hier zeigt, was wir geplant haben. Und so- + +[TEX3_E:SERG3] +-so sieht der betreffende Grund heute aus. + +[TEX3_F:SERG3] +Sie wollen sagen, das neue Bürohaus da ist Ihnen im Weg. + +[TEX3_G:SERG3] +Gut mitgedacht. + +[TEX3_H:SERG3] +Also, ich verdrücke mich eine Weile aus der Stadt, + +[TEX3_I:SERG3] +und wenn dieses Bürogebäude urplötzlich irreparable Schäden aufweist, dann... + +[TEX3_J:SERG3] +....fühlen Sie sich als guter Mensch verpflichtet, einzuspringen und + +[TEX3_K:SERG3] +-für die Neugestaltung eines wichtigen Gebiets der Stadt zu sorgen? + +[TEX3_L:SERG3] +Wo finde ich mehr Männer wie dich? + +[TEX3_1:SERG3] +~g~Benutze den ferngesteuerten Helikopter, um Bomben zu 4 Zielen an dem zur Sprengung vorgesehenen Gebäude zu transportieren. + +[TEX3_2:SERG3] +~g~Du musst an jedem Ziel eine Bombe abwerfen. Die Reihenfolge ist beliebig. + +[TEX3_3:SERG3] +~g~Steuere den Helikopter direkt über eine Bombe, um sie aufzunehmen. Die Bombe heftet sich dann automatisch an den Helikopter. + +[TEX3_5:SERG3] +~g~Wenn eine Bombe ihr Ziel verfehlt, kannst du sie erneut aufnehmen und es nochmal versuchen. + +[TEX3_7:SERG3] +~g~Dann hast du noch 7 Minuten, um die restlichen Bomben im Ziel zu platzieren. + +[TEX3_8:SERG3] +~g~Du hast das Ziel verfehlt! Nimm die Bombe wieder auf und versuche es nochmal! + +[TEX3_10:SERG3] +~g~Wirf die Bombe über einem der Ziele ab. + +[TEX3_11:SERG3] +Verbleibende Ziele: + +[TEX3_17:SERG3] +~r~Die Zeit ist um. Du hast es nicht geschafft, das Gebäude zu sprengen. + +[TEX3_18:SERG3] +~r~Dein Helikopter wurde zerstört! Wie willst du jetzt die Bomben transportieren? + +[TEX3_19:SERG3] +~r~Deine Bombe ist im Wasser gelandet! Du brauchst ALLE 4 Bomben, um die Sprengung vorzunehmen. + +[TEX3_20:SERG3] +~g~Dein Helikopter ist fast außer Reichweite. Du musst zurück zur Baustelle, um deine Arbeit zuende zu bringen! + +[TEX3_21:SERG3] +~r~Dein Helikopter befindet sich außer Reichweite! + +[TEX3_24:SERG3] +Drücke ~h~~k~~VEHICLE_LOOKLEFT~~w~, um den Helikopter gegen den Uhrzeigersinn zu drehen. + +[TEX3_25:SERG3] +Drücke ~h~~k~~VEHICLE_LOOKLEFT~~w~, um den Helikopter im Uhrzeigersinn zu drehen. + +[TEX3_27:SERG3] +~g~Über eine Haupttreppe hat man Zugang zu allen Stockwerken des Gebäudes. + +[TEX3_31:SERG3] +~r~Du hast den Wagen mit den Bomben und dem ferngesteuerten Helikopter zerstört! + +[TEX3_32:SERG3] +Du kannst ~h~nach hinten sehen~w~, indem du ~h~gleichzeitig ~k~~VEHICLE_LOOKLEFT~ und ~k~~VEHICLE_LOOKRIGHT~ drückst~w~. + +[TEX3_4:SERG3] { reVC update } +~g~Um eine Bombe abzuwerfen, drücke die~h~ ~k~~VEHICLE_FIREWEAPON~~w~. + +[TEX3_29:SERG3] { reVC update } +Um eine Bombe abzuwerfen, drücke die~h~ ~k~~VEHICLE_FIREWEAPON~. + +[TEX3_26:SERG3] +Drücke die ~h~~k~~VEHICLE_BRAKE~~w~, um die Rotorgeschwindigkeit zu verringern, der Helikopter ~h~verliert dann an Höhe. + +[TEX3_22:SERG3] +Drücke die ~h~~k~~VEHICLE_ACCELERATE~~w~, um die Rotorgeschwindigkeit zu erhöhen, der Helikopter ~h~gewinnt dann an Höhe. + +[TEX3_16:SERG3] +~g~Begib dich zu dem ~w~TOPFUN~g~-Wagen nahe dem zum Abriss vorgesehenen Gebäude. + +[TEX3_33:SERG3] +Wenn du eine Bombe aufgenommen hast, zeigt dir das Radar die Position des Ziels in Relation zu dem ferngesteuerten Helikopter. + +[TEX3_34:SERG3] +Ein ~h~nach oben zeigendes Dreieck ~w~bedeutet, dass das Ziel sich in ~h~größerer Höhe ~w~befindet als der Helikopter. + +[TEX3_35:SERG3] +Ein ~h~nach unten zeigendes Dreieck ~w~bedeutet, dass das Ziel sich in ~h~geringerer Höhe ~w~befindet als der Helikopter. + +[TEX3_36:SERG3] +Ein ~h~Viereck ~w~ bedeutet, dass das Ziel sich auf ~h~der gleichen Höhe ~w~befindet wie der Helikopter. + +[TEX3_6:SERG3] +~g~Wenn du die erste Bombe aufgenommen hast, wird der Zeitzünder aktiviert. + +[TEX3_28:SERG3] +Um ~h~eine Bombe aufzunehmen~w~, steuere den Helikopter direkt über sie. Der Helikopter kann immer nur eine Bombe tragen. + +[TEX3_30:SERG3] +~g~Um eine Bombe aufzunehmen, steuere den Helikopter direkt über sie. Der Helikopter kann immer nur eine Bombe tragen. + +[TEX3_12:SERG3] +~g~Bombe platziert! Es bleiben nur noch 3 Ziele! Hol die nächste Bombe. + +[TEX3_13:SERG3] +~g~Bombe platziert! Es bleiben nur noch 2 Ziele! Hol die nächste Bombe. + +[TEX3_14:SERG3] +~g~Bombe platziert! Es bleibt nur noch 1 Ziel! Hol die nächste Bombe. + +[TEX3_15:SERG3] +~r~Zeitzünder aktiviert! ~g~ Du musst die ~w~4 Bomben ~g~in der verbleibenden Zeit platzieren. + +[TEX3_37:SERG3] +Zieh den ~h~ Rechten Analog-Stick zurück~w~, um die Rotorgeschwindigkeit zu erhöhen, der Helikopter ~h~ gewinnt dann an Höhe. + +[TEX3_38:SERG3] { reVC update } +{Drück den ~h~ ~k~~VEHICLE_ACCELERATE~~w~, um die Rotorgeschwindigkeit zu verringern, der Helikopter ~h~ verliert dann an Höhe.} +Drück den ~h~ Rechten Analog-Stick nach vorn~w~, um die Rotorgeschwindigkeit zu verringern, der Helikopter ~h~ verliert dann an Höhe. + +[TEX3_39:SERG3] +Um eine Bombe abzuwerfen, drücke die ~h~~k~~VEHICLE_HANDBRAKE~~g~-Taste. + +[TEX3_40:SERG3] +Um eine Bombe abzuwerfen, drücke die ~h~~k~~VEHICLE_HANDBRAKE~~w~-Taste. + +[TEX3_23:SERG3] +Mit ~h~~k~~VEHICLE_TURRETUP~~w~ und ~h~~k~~VEHICLE_TURRETDOWN~~w~ neigst du den Helikopter in die Richtung, in die du ihn steuern willst. + +{=================================== MISSION TABLE TAXI1 ===================================} + +[FARES:TAXI1] +FAHRTEN: + +[TAXI1:TAXI1] +~g~Besorg dir einen Fahrgast. + +[TSCORE2:TAXI1] +$~1~ + +[IN_ROW:TAXI1] +~1~ SERIEN-Bonus! $~1~ + +[TAXI3:TAXI1] +~r~Dein Fahrgast ist entsetzt geflohen! + +[TAXI7:TAXI1] +~r~Dein Wagen ist Schrott. Repariere ihn. + +[TAXI4:TAXI1] +Fahrt abgeschlossen! + +[TAXI5:TAXI1] +SPEED BONUS!! + +[TAXI6:TAXI1] +Taxi-Mission beendet + +[TAXIH1:TAXI1] +Halte neben einem markierten Fußgänger, um ihn einsteigen zu lassen, dann bringe ihn rechtzeitig an sein Fahrtziel. + +[FARE1:TAXI1] +~g~Fahrtziel ~w~'Pole Position Club' ~g~in Ocean Beach. + +[FARE3:TAXI1] +~g~Fahrtziel ~w~'Marina' ~g~in Ocean Beach. + +[FARE4:TAXI1] +~g~Fahrtziel ~w~'Ammu-Nation' ~g~in Ocean Beach. + +[FARE5:TAXI1] +~g~Fahrtziel ~w~'Eisenwarenladen' ~g~in Washington Beach. + +[FARE6:TAXI1] +~g~Fahrtziel ~w~'North Point Einkaufszentrum' ~g~in Vice Point. + +[MFARE1:TAXI1] +~g~Fahrtziel ~w~'Ammu-Nation' ~g~in Downtown. + +[MFARE2:TAXI1] +~g~Fahrtziel ~w~'Terminal' ~g~im Escobar International Airport. + +[WFARE3:TAXI1] +~g~Fahrtziel ~w~'Sunshine Autos' ~g~in Little Havana. + +[WFARE4:TAXI1] +~g~Fahrtziel ~w~'Kaufman-Taxis' ~g~in Little Haiti. + +[WFARE5:TAXI1] +~g~Fahrtziel ~w~'Eisenwarenladen' ~g~in Little Havana. + +[WFARE6:TAXI1] +~g~Fahrtziel ~w~'Howlin Petes Bike Emporium' ~g~in Downtown. + +[FARE7:TAXI1] +~g~Fahrtziel ~w~'die Juweliere' ~g~in Vice Point. + +[FARE8:TAXI1] +~g~Fahrtziel ~w~'der Strand' ~g~in Ocean Beach. + +[FARE9:TAXI1] +~g~Fahrtziel ~w~'der Strand' ~g~in Washington Beach. + +[FARE10:TAXI1] +~g~Fahrtziel ~w~'der Strand' ~g~in Vice Point. + +[FARE11:TAXI1] +~g~Fahrtziel ~w~'das Krankenhaus' ~g~in Ocean Beach. + +[FARE12:TAXI1] +~g~Fahrtziel ~w~'das Krankenhaus' ~g~in Vice Point. + +[FARE13:TAXI1] +~g~Fahrtziel ~w~'die Polizeistation' ~g~in Washington Beach. + +[FARE14:TAXI1] +~g~Fahrtziel ~w~'die Polizeistation' ~g~in Vice Point. + +[FARE15:TAXI1] +~g~Fahrtziel ~w~'die Pizzagaststätte' ~g~in Vice Point. + +[WFARE7:TAXI1] +~g~Fahrtziel ~w~'die Polizeistation' ~g~in Little Havana. + +[WFARE8:TAXI1] +~g~Fahrtziel ~w~'die Polizeistation' ~g~in Downtown. + +[WFARE9:TAXI1] +~g~Fahrtziel ~w~'das Krankenhaus' ~g~in Downtown. + +[WFARE10:TAXI1] +~g~Fahrtziel ~w~'das Krankenhaus' ~g~in Little Havana. + +[WFARE11:TAXI1] +~g~Fahrtziel ~w~'der Stadium' ~g~in Downtown. + +[WFARE12:TAXI1] +~g~Fahrtziel ~w~'die Pizzagaststätte' ~g~in Little Haiti. + +[WFARE13:TAXI1] +~g~Fahrtziel ~w~'die Pizzagaststätte' ~g~in Downtown. + +[WFARE14:TAXI1] +~g~Fahrtziel ~w~'die Docks' ~g~in Viceport. + +[WFARE15:TAXI1] +~g~Fahrtziel ~w~'die Apotheke' ~g~in Little Haiti. + +[FARE2:TAXI1] +~g~Fahrtziel ~w~'Malibu Club' ~g~in Vice Point. + +{=================================== MISSION TABLE TAXICUT ===================================} + +[TAXC_A:TAXICUT] +Schätze, sie sind der neue Besitzer. + +[TAXC_B:TAXICUT] +Sind Sie 'n Mafioso? Oder vom Kartell? Sehen nicht aus wie ein Mexikaner. + +[TAXC_C:TAXICUT] +Egal. Halten Sie schon endlich Ihre Predigt von wegen 'Jetzt wird alles anders', + +[TAXC_D:TAXICUT] +bedrohen Sie ein paar von den Fahrern- + +[TAXC_E:TAXICUT] +aber nicht Ted, der ist gerade an der Leiste operiert. + +[TAXC_F:TAXICUT] +Tja, also, hier wird sich einiges ändern, Lady. + +[TAXC_G:TAXICUT] +Aber nicht doch, Jungchen. Überlassen Sie das lieber mir - + +[TAXC_H:TAXICUT] +Ich mach das schon seit Jahren. + +[TAXC_I:TAXICUT] +Alles mal herhören. + +[TAXC_J:TAXICUT] +Wir haben eine neue Geschäftsleitung, und es wird sich wieder mal einiges ändern hier. + +[TAXC_K:TAXICUT] +Unsere neue Geschäftsleitung, die- + +[TAXC_L:TAXICUT] +Von welcher Gang sind Sie? + +[TAXC_M:TAXICUT] +Ich gehöre keiner Gang an. + +[TAXC_N:TAXICUT] +Und wie heißen Sie, junger Mann? + +[TAXC_O:TAXICUT] +Vercetti, Tommy Vercetti. + +[TAXC_P:TAXICUT] +Unsere neue Geschäftsleitung, die Vercetti Gang, + +[TAXC_Q:TAXICUT] +wird dafür sorgen, dass wir keinen Ärger kriegen. + +[TAXC_R:TAXICUT] +Capiche? Ende! + +[TAXC_S:TAXICUT] +Wie fanden Sie das 'Capiche'? Ich fand's gut. + +[TAXC_T:TAXICUT] +Also, so ist das immer gelaufen: + +[TAXC_U:TAXICUT] +Wir führen die Firma weiter wie gewohnt. + +[TAXC_V:TAXICUT] +Wenn die Konkurrenz Ärger macht, gebt ihr ihnen eines auf die Mütze. + +[TAXC_W:TAXICUT] +Dann geben die uns eines auf die Mütze. + +[TAXC_X:TAXICUT] +Dann geben sie denen eines auf die Mütze. + +[TAXC_Y:TAXICUT] +Und so weiter, und so fort. Kapiert? + +[TAXC_Z:TAXICUT] +Äh, ja, ich glaub schon. + +[TAXC_A1:TAXICUT] +Schnappen Sie sich ein Taxi aus der Garage, wenn Sie Lust haben. + +{=================================== MISSION TABLE TAXIWA1 ===================================} + +[OUTTIME:TAXIWA1] +~r~Zu langsam, Mann, zu langsam! + +[TAX1_1:TAXIWA1] +Ok, eine V.I.P. müsste von Starfish Island abgeholt werden. Jemand interessiert? + +[TAX1_2:TAXIWA1] +Tommy hier. Ich übernehme das. + +[TAX1_3:TAXIWA1] +Das ist meine Fuhre. Hau ab! + +[TAX1_4:TAXIWA1] +Los, los, steigen Sie ein. Schnell! + +[TAX1_5:TAXIWA1] +Ok, ok! Aber tun Sie mir nichts! + +[TAXW1_1:TAXIWA1] +~g~Hol die V.I.P. auf Starfish Island ab. + +[TAXW1_2:TAXIWA1] +~g~Hol die V.I.P. zurück! Schalte den anderen Wagen aus! + +[TAXW1_3:TAXIWA1] +~r~Die V.I.P. ist Geschichte! + +[TAXW1_4:TAXIWA1] +~r~Die V.I.P. wurde abgesetzt! + +[TAXW1_6:TAXIWA1] +~g~Bring die V.I.P zum Flughafen! + +{=================================== MISSION TABLE TAXIWA2 ===================================} + +[TAX2_1:TAXIWA2] +An alle Wagen. Wir kriegen nirgends Fahrgäste. Was ist los mit euch? + +[TAX2_2:TAXIWA2] +VC-Taxi ist dauernd schneller als wir. Die haben einfach zu viele Autos. Keine Chance. + +[TAX2_3:TAXIWA2] +Mr. Vercetti, wenn Sie zufällig mithören: Sie müssen ein paar VC-Taxis ausschalten, sonst sind wir pleite! + +[TAXW2_1:TAXIWA2] +~g~Schalte 3 Taxis der Konkurrenz aus! + +{=================================== MISSION TABLE TAXIWA3 ===================================} + +[TAX3_1:TAXIWA3] +Wagen 13, eine Miss Cortez möchte von Ihnen ganz persönlich in Downtown abgeholt werden. + +[TAX3_2:TAXIWA3] +Ok, verstanden. Wagen 13 Ende. + +[TAX3_3:TAXIWA3] +Hmmm, keine Spur von Mercedes... + +[TAXW3_3:TAXIWA3] +~g~Schalte das Taxi des Anführers aus! + +[TAXW3_2:TAXIWA3] +~g~Halte durch, bis die Zeit abgelaufen ist. + +[TAX_AS1:TAXIWA3] +TAXIUNTERNEHMEN ERWORBEN + +[TAX_AS2:TAXIWA3] +~g~Kaufman-Taxis generiert nun bis zu $~1~ Einkünfte. Hol dir das Geld regelmäßig ab. + +[TAX3_4:TAXIWA3] +Wird Zeit, dass der Schutzengel von Kaufman-Taxis eine vor den Latz kriegt! + +[TAX3_5:TAXIWA3] +Hey, Freundchen, dir zieh ich das Fell über die Ohren! + +{ reVC 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] +TEXTURFILTERUNG + +[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? + +[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 + +[FEC_IVP] +PAD VERTIKAL INVERTIEREN + +[FEM_NON] +NONE + +[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 + +[FET_AGS] +KONTROLLEREINSTELLUNGEN + +[FEM_AUT] { aspect ratio related } +AUTO + +[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/gxt/miami/gxt.exe b/gxt/miami/gxt.exe new file mode 100644 index 0000000000000000000000000000000000000000..0f55b7605a8a83400f2b7a64d7527cfa2457c6b5 GIT binary patch literal 307200 zcmeZ`n!v!!z`(%5z`*eTKLf)K1_*F~P526$p7!ve~QW8rN85r_d86kdWV`N}B0ty3=3J7*!U~tfbC}ZF_ z&%nU&fSG~e5Ca3lDNqW7r~#1<3=9YKii(SpK}tYw+rYxW05Ylv)nc%DMX5RY$qZn> zGaO)HU{GRUVCch8=L9l$6q6nT84J!jF}%3*g@GaR@R>7b&SV&zb7FXL2F%}iVxbGe ziv>>^7&6*GqT9ft8M8p_RS@dkm_|H(GIX^#u5;_2FwPTzvIR_ z7Y49RAl{iXq1|u7x=T5-7+>5mV`OMP!qV%@6PU#i7VzTI|NsC07qI*{cwrC#l97mu zJq%Xg{Dx;?zyoIn&SAP5$v`X?97bNK!*ecnB)&X(0wqC|C%GqaYvRf^PCxuGw!(S zgb1_Fa5`W!{y`evv-*ANeFXagOAHdUH%G2$^(jCUq?a5;8#Zt=O&0x(|%DI6t4ixJg z3`Pf_43HTh^Bq}0Nw7PN#oCFbl&_o3+Lfo2eFI}0$Tp(`U<#xZjE^&d3asvf#~oP~ zymV$b9Nc}U`3*<&lON4TSVFtASvqr6Si0Y2{m)=M;>3`$5R@SUf}7t&z*Fp-u<(Fh z*DooHK~mj6J8K_wJ9G5@m%hkidSTqh!0^BHK{s2k?}otcbN>T4j=BC}>I+xGy!bLfU{*E`*% zH?)s+`aTE@vOZC++FW}_pqGWO)Aa__66OzKFHSNsGIYB>Xg&>ecD`IkVEPPgv{r|yG|zd$u!kw~}apXNi#+6S8t{AvCn&)41W+AR?i>{O;?;YwW4gV0x{r4U3mj(`Smw;o>Ce*{F3|1!C8d)WWNT!6T(|EV z2g195_PVnKK!OBRCV@(*7y17f7$EKfl__pK-F^a{?mXScJN*TYv&SrRW{3wD zGhn+x1R?X}|ACyt0Wq>0#XK~VK&G8JvqNFNGomyC=kLI7R~}FX>MZ@!{DZMnBTMwS z8@PqjeeSrM2q=0o9F{mU9Ajg6p;yEJDm5VaFg)Nz^)m*B?h7#Gr=ZH4kFXqLW9Y49 z2$ambi0BwAzSN# z61jk|7qcEgosN{&z&>SR$j||ms&DVZN>z|cAsFJ`1qkf(J_ik}7iaD=FraxAsbK)|-kA^o9a)e?n@{}zz<8^JcL7zw zP?Zp>+mogBWQkxmg% z3_z=Tuz}G06W(3RWBt8EGPpOAgMS-aaKMXzCkzZ7Y|TeF+^oNszKI5F_f+WiJ$ zp4L<1&HIPbye?M`go{A+Rj2?cyMdh4eO~)qU|7%#v%esVc)E{+@+=Pn*yRu+wEGQM zKdjya>lZ=S|LqTwegQQ7Vcj3Q-2{%a9l7ho(0ZVb>wg)~3q~JChSyw`+#t5z3y@}( z|79XCUU@@O1URdK@?8mcmPFW#Nv}XEkXlg?w}D7V`aO$Czs+xCg2TeG=ecfIp6*bN z?o*wmAC9}e0H;NZ?g*A%N5Z$JgjG1nUmovt5Xg@*5kz@T2=8!xu!GB5;(_qyJI z=GK7jb1$5K{Qut_`UYAt+`wHis09VSxcCz6f#Yl)%bXdyk0GVn__%0DY4(CM3{>`m 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�P*v|AHu}U2r7qML5W6*Aw06 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%0D`oC3l* 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

    VgnH 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 X26qfZ3@r>x3>O%Jx|E}MaD)H=5L3Ga literal 0 HcmV?d00001 diff --git a/gxt/miami/italian.txt b/gxt/miami/italian.txt new file mode 100644 index 00000000..97f510cd --- /dev/null +++ b/gxt/miami/italian.txt @@ -0,0 +1,14735 @@ +{ + 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. +} + +[IN_VEH] +~g~Ehi! Torna nel veicolo! + +[HEY] +~g~Non procedere da solo, stai insieme ai tuoi compagni! + +[HELP3] +Ricorda che puoi eseguire uno scatto solo per brevi tratti. + +[HELP4_D] +Sposta la ~h~levetta analogica destra~w~ verso l'alto per ~h~accelerare~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~. + +[HELP7_A] +Tieni premuto il ~h~~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ con il fucile di precisione. + +[HELP7_D] +Tieni premuto il ~h~~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ con il fucile di precisione. + +[HELP10] +Questa stella indica il tuo livello di sospetto. + +[HELP11] +Più stelle hai, maggiore è il tuo livello di sospetto. + +[HELP13] +In alcuni casi potresti dover utilizzare passaggi non segnati sul radar. + +[TIMER] +Questa è una missione a tempo: devi portarla a termine prima che il contatore raggiunga lo zero. + +[HORN] +~g~Suona il clacson. + +[NOMONEY] +~g~Ti servono più soldi! + +[REWARD] +RICOMPENSA ~1~$ + +[M_FAIL] +MISSIONE FALLITA! + +[M_PASS] +MISSIONE COMPLETATA! ~1~$ + +[DEAD] +MASSACRATO! + +[BUSTED] +BECCATO! + +[WEATHE1] +FORZA TEMPO SOLEGGIATO + +[WEATHE2] +FORZA TEMPO MOLTO SOLEGGIATO + +[WEATHE3] +FORZA TEMPO NUVOLOSO + +[WEATHE4] +FORZA TEMPO PIOVOSO + +[WEATHE5] +FORZA TEMPO NEBBIOSO + +[WEATHE6] +TEMPO NORMALE + +[NUMBER] +~1~ + +[LOADCAR] +CARICAMENTO VEICOLO... (PREMI L1 PER ANNULLARE) + +[CARSOFF] +Veicoli disabilitati. + +[CARS_ON] +Veicoli abilitati. + +[TEXTXYZ] +Scrittura coordinate sul file... + +[CHEATON] +Modalità trucchi attivata + +[CHEATOF] +Modalità trucchi disattivata + +[IMPORT1] +Esci fuori e aspetta il tuo veicolo. + +[PAGEB11] +Lanciafiamme depositato nel nascondiglio + +[WANT_A] +Verrai arrestato solo se hai un ~h~livello di sospetto~w~. + +[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 stavi affrontando verrà considerata come 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. + +[SAVE1] +Passa attraverso la corona 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 Ammu-Nation per comprare un'arma. + +[R_TIME] +TEMPO DI GARA: + +[PROP_1] +Non hai abbastanza soldi per comprare questa proprietà. + +[PROP_2] +Non puoi comprare una proprietà mentre sei in missione. + +[IND_ZON] +Vice City Beach + +[COM_ZON] +Vice City Mainland + +[BEACH1] +Ocean Beach + +[BEACH2] +Washington Beach + +[BEACH3] +Vice Point + +[GOLFC] +Leaf Links + +[STARI] +Starfish Island + +[DOCKS] +Porto + +[HAVANA] +Little Havana + +[HAITI] +Little Haiti + +[PORNI] +Prawn Island + +[DTOWN] +Downtown + +[VICE_C] +Vice City + +[A_PORT] +Escobar International + +[JUNKY] +Discarica + +[PISTOL] +Pistola + +[PYTHON] +.357 + +[UZI] +Uz-1 + +[TEC9] +Tec 9 + +[M4] +M4 + +[INGRAM] +Mac + +[MP5] +MP + +[RUGER] +Kruger + +[SNIPE] +Fucile di precisione + +[GRENADE] +Granate + +[SHOTGN1] +Fucile a pompa + +[SHOTGN2] +S.P.A.S. 12 + +[SHOTGN3] +Fucile a canne mozze + +[ARMOUR] +Giubbotto antiproiettile + +[LASER] +.308 Fucile di precisione + +[BASEBAT] +Mazza da baseball + +[HAMMER] +Martello + +[SCREWD] +Cacciavite + +[CLEVER] +Mannaia + +[MACHETE] +Macete + +[KNIFE] +Coltello + +[KATANA] +Katana + +[CHAINSA] +Motosega + +[G_COST] +~1~$ + +[CAR_1] +Ambulanza + +[MALIBU] +Il club Malibu + +[MANSION] +Villa di Diaz + +[TMANS] +Chez Tommy + +[STRIP] +Il club Pole Position + +[MALL1] +North Point Mall + +[BANKINT] +El Banco Corrupto Grande + +[RANGE] +Poligono di tiro + +[POL_HQ] +Polizia di VC + +[INT_B] +Un vecchio amico + +[INTB_1] +~g~Vai all'ufficio degli avvocati. + +[LAW_1] +Il party + +[LAW_2] +Rissa nel vicolo + +[LAW_3] +Furia sulla giuria + +[LAW_4] +Sommossa + +[COL_1] +Fottuto traditore + +[COL_2] +Sparatoria al Mall + +[COL_3] +Angeli custodi + +[COL_4] +Sissignore, signore! + +[COL_5] +Tutti sul ponte! + +[COK_1] +L'inseguimento + +[COK_2] +Phnom Penh '86 + +[COK_3] +La barca più veloce + +[COK_4] +Domanda e offerta + +[KENT_1] +Il braccio della morte + +[ASS_1] +Eliminazione + +[BUD_1] +Estorsione + +[BUD_2] +Rissa al bar + +[BUD_3] +Cop Land + +[CAP_1] +Fuori l'esattore + +[FIN_1] +Tieniti stretto gli amici... + +[BANK_1] +Senza via di fuga? + +[BANK_2] +Il tiratore + +[BANK_3] +L'autista + +[BANK_4] +La rapina + +[CNT_1] +Sfornare verdoni + +[CNT_2] +Colpire il corriere + +[PORN_1] +Giro di reclutamento + +[PORN_2] +Il dodo del dildo + +[PORN_3] +La foto di Martha + +[PORN_4] +Riflettori sul punto G + +[TAX_1] +Taxi di Kaufman + +[TAXI_1] +VIP + +[TAXI_2] +Rivalità amichevole + +[TAXI_3] +Cabmaggedon + +[ICE_1] +Distribuzione + +[TEX_1] +Ferro quattro + +[TEX_2] +Due in un colpo + +[TEX_3] +Il demolitore + +[PHIL_1] +Corriere delle armi + +[PHIL_2] +Broda e Saigon + +[BIKE_1] +Due ruote d'acciaio + +[BIKE_2] +Bollire la pentola + +[BIKE_3] +Valla a prendere! + +[ROCK_1] +Il succo dell'amore + +[ROCK_2] +Killer psicotico + +[ROCK_3] +Giro pubblicitario + +[ROCK_4] +Love Fist! + +[HAT_1] +Le polveri della zia + +[HAT_2] +Bombardamento! + +[HAT_3] +Tutto di nascosto + +[CUB_1] +Gara sull'acqua + +[CUB_2] +Carne da cannone + +[CUB_3] +Battaglia navale + +[CUB_4] +Voodoo a doppio taglio + +[JOB_1] +Morte per strada + +[JOB_2] +Elimina la moglie + +[JOB_3] +Incidente... + +[JOB_4] +L'ultima partenza + +[JOB_5] +Risoluzione + +[ANSWER] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per rispondere al cellulare. + +[MOB_01A] +Ciao vecchio mio! Sono Paul. Potrei avere delle informazioni per te, ma ti voglio parlare in privato. + +[MOB_01B] +Mi sto godendo un aperitivo al Malibu. + +[MOB_01C] +Credo che mi dovrai un favore o due dopo questo, carino. Ci vediamo dopo. + +[MOB_02A] +Ssssnniiiiffffff! Ehi! Tommy? Ciao, Tommy! + +[MOB_02B] +Ci sono dei problemi alla stamperia. Faresti meglio ad andare a controllare. + +[MOB_02C] +Un qualche tipo di casino. C'è qualcosa che non va. Devo andare. + +[MOB_03A] +Mr. Vercetti? Ho qui davanti a me un pezzo di carta che afferma + +[MOB_03B] +che lei si è preso carico dei debiti di BJ's Auto. + +[MOB_03C] +Con l'improvvisa scomparsa di BJ, non ho altra scelta + +[MOB_03D] +che ritenerla responsabile delle sue debolezze finanziarie. + +[MOB_03E] +Finché questo debito non sarà ripagato, + +[MOB_03F] +dovrà considerare le strade di Vice City non molto amichevoli. + +[MOB_04A] +Come butta, amico? + +[MOB_04B] +Ascolta Tommy, mi sono dimenticato di dirti che avremo bisogno di qualche gorilla extra per il concerto. + +[MOB_04C] +C'è una banda di motociclisti e sarebbero una perfetta pubblicità. + +[MOB_04D] +Occupatene tu e ti farò accedere al back stage dello spettacolo, OK? + +[MOB_05A] +Ehi, sono Mitch! Ottimo lavoro, Tommy: è bello riavere indietro la mia vecchia bambina. + +[MOB_05B] +Dì a Mr. Kent Paul che avrà la sorveglianza per lo spettacolo. + +[MOB_05C] +Hai la mia parola. + +[MOB_05D] +Adesso tieniti fuori dai guai. + +[MOB_06A] +Tommy, si è parlato molto di te, ragazzo. + +[MOB_06B] +Pensavo ti servisse un po' di calore, per cui la zia Poulet ti ha preparato una bella zuppa, hmmm? + +[MOB_06C] +Passa a trovarmi in cucina ogni tanto, OK Tommy? + +[MOB_08A] +Ehi, Tommy, ho pensato che ti potessero servire dei consigli. + +[MOB_08B] +Quando hai un'operazione in corso, dovrai passare a ritirare i soldi della settimana. + +[MOB_08C] +Lascia pensare ai ragazzi che abbiano il controllo del posto e loro cercheranno di ridurne i profitti, OK? + +[MOB_08D] +Ehi Ken, so come far funzionare le cose, OK. + +[MOB_08E] +OK, OK! Lo so che lo sai. Lo so. Stavo solo... + +[MOB_08F] +Ti stavo solo ricordando che lo so, cioè che tu sai che io so. + +[MOB_08G] +Solo per ricordartelo! + +[MOB_08H] +Sì Ken, va bene... + +[MOB_09A] +Ehi Leo! Ho del lavoro per te! + +[MOB_09B] +Non sono Leo! + +[MOB_09C] +Ehi, se Leo scopre che hai il suo telefono, ti farà a pezzi! + +[MOB_09D] +Forse Leo è già morto. Forse ho ucciso Leo e ho preso il suo telefono: non ci hai pensato, idiota? + +[MOB_09E] +Hai ucciso Leo? Devi avere due grandi cojones. Vuoi lavorare per me? + +[MOB_09F] +Passa dal bar di mio padre a Little Havana e ne possiamo parlare. + +[MOB_10A] +Tommy! Senti, devo chiederti un favore. + +[MOB_10B] +Steve! Come va il film? + +[MOB_10C] +Bene, bene. Senti ho, ehm, abbiamo bisogno di una scena di inseguimento, ma siamo a corto di denaro. + +[MOB_10D] +Ho lasciato qualche macchina in città. Sono certo che sai cosa fare. + +[MOB_10E] +OK Steve, terrò un occhio aperto. Ci vediamo! + +[MOB_11A] +Ciao figliolo, t'ho chiamato per darti un consiglio. + +[MOB_11B] +Ciao Avery. Come butta? + +[MOB_11C] +Ci sono un sacco di opportunità in questa città se possiedi le proprietà giuste, mi segui? + +[MOB_11D] +Tutto chiaro... + +[MOB_11E] +Volevo dirti di tenere gli occhi aperti: potresti trovare l'occasione giusta per far soldi. Ci becchiamo! + +[MOB_11F] +Ciao, Avery. + +[MOB12_A] +Ehi Tommy! Sono Avery! Ascolta: sono piuttosto incasinato al momento + +[MOB12_B] +e un mio rappresentante ha bisogno di un accompagnatore fino a Gator Keys. + +[MOB12_C] +Sto cercando di acquistare del terreno da quelle parti, per cui mando qualcuno in avanscoperta. + +[MOB12_D] +Mi faresti un favore? Potresti controllare che arrivi a destinazione tutto intero? + +[MOB12_E] +Certo, nessun problema Avery. Dove vuoi che lo passi a prendere? + +[MOB12_F] +Sta finendo un lavoro presso il cantiere. Gli ho detto che saresti passato lì. + +[MOB12_G] +Nessun problema. Ci vediamo, Avery. + +[MOB13_A] +Vercetti? VERCETTI!!! Maledizione, mi devi aiutare! + +[MOB13_B] +Mr. Moffat? Come sta la famiglia? + +[MOB13_C] +Brutto maledetto, BASTARDO! Mi hai sentito? + +[MOB13_D] +Beh, è stato un piacere chiacchierare... + +[MOB13_E] +ASPETTA! Aspetta Vercetti... Tommy, posso chiamarti Tommy? + +[MOB13_F] +Siamo entrambi professionisti, vero? Sono certo che sai riconoscere un affare, vero? + +[MOB13_G] +Non ho tempo da perdere, arriva al punto. + +[MOB13_H] +SOLDI. I soldi sono il punto! + +[MOB13_I] +Sono fuggito nuovamente da quei maledetti, ma non ci metteranno molto a trovarmi... Pensano si tratti di uno stupido gioco! + +[MOB13_J] +Sono a un telefono pubblico da qualche parte in questo posto di merda. + +[MOB13_K] +Aiutami a scappare prima che mi prendano e... e... oddio... + +[MOB13_L] +Beh, sono impegnato per i prossimi... + +[MOB13_M] +No! Non fare lo stronzo con me, abbi cuore! Nessuno dovrebbe fare una... una cosa simile! + +[MOB13_N] +Sono in ginocchio, Tommy: nella polvere a chiederti pietà... + +[MOB13_O] +Beh, forse potrei passare da quelle parti, vediamo se riesco a trovarti... + +[MOB13_P] +Oddio, stanno arrivando! Ti supplico, muoviti, muoviti! + +[MOB_14A] +Ehi, ciao Tommy: vedrai che mi adorerai! + +[MOB_14B] +Un piccolo uccellino mi ha detto che la divisione SWAT di Vice City ha un deposito presso un'importante banca, + +[MOB_14C] +dove tengono tutte le bustarelle intascate nel corso degli anni, + +[MOB_14D] +un po' come una pensione per gli anni a venire. + +[MOB_14E] +Logicamente, se questa informazione dovesse aiutarti a ottenere i soldi, + +[MOB_14F] +immagino che ti sentiresti obbligato a passarmene una parte... + +[MOB_14G] +Me lo ricorderò, grazie Kent. + +[MOB_14H] +Sono Paul. Vengo dal Kent, vicino a Londra, cretino. + +[MOB_14I] +Le mie conoscenze di geografia non sono mai state un gran che. + +[MOB15_A] +Tommy, amico, sono Paul, dal Kent, + +[MOB15_B] +un paio di squinzie hanno il tuo nome scritto dappertutto giù al Malibu. + +[MOB15_C] +Di cosa stai parlando? + +[MOB15_D] +Squinzie. Passere. Chiaro no? Strafighe. E anche ben messe, non credo fossero mignotte... + +[MOB15_E] +Dovresti venire a dare un'occhiata. + +[MOB16_A] +Tommy, sono Paulo, que pasa amigo? + +[MOB16_B] +Che cosa vuoi Paul: non sono interessato a vestiti di marca taroccati. + +[MOB16_C] +Molto divertente, amico, ma lo sai che non tratto merce di seconda classe. + +[MOB16_D] +Nah, ti ho chiamato per sapere se potevo avere una parte nei tuoi film, + +[MOB16_E] +quando ero in Inghilterra ho fatto un sacco di cose, sai? + +[MOB16_F] +Ho attributi più forniti di te, amico. + +[MOB16_G] +Paul, grazie per l'offerta, lo terrò presente. + +[MOB16_H] +Davvero, non scordarti di me dopo tutto quello che ho fatto per te. + +[MOB16_I] +È proprio ciò che sto cercando di dimenticare... + +[MOB17_A] +Tommy Vercetti: come va, Mr. Pezzo Grosso? + +[MOB17_B] +Ho saputo le tue novità: un vero giocatore in città, eh... + +[MOB17_C] +Paul, sei ubriaco. + +[MOB17_D] +No, idiota. Non sono ubriaco. + +[MOB17_E] +Solo un paio di bicchierini e qualche pillola: non dormo da un paio di giorni, sai... + +[MOB17_F] +Comunque, niente prediche. + +[MOB17_G] +Non sono uno stupido. Chi ti ha portato in questa città? Chi? Io, ecco chi. + +[MOB17_H] +Davvero? + +[MOB17_I] +Davvero! + +[MOB17_J] +Paul, sta' buono. Sono stato occupato, non fare l'idiota. + +[MOB17_K] +Non sono un idiota, chiaro? Questo è ciò che hanno detto in riformatorio. + +[MOB17_L] +Stai forse cercando guai, amico? Beh, stai per trovarli! + +[MOB17_M] +Tommy, amico. Per favore. Sei la mia più grande speranza! Per favore, non ridere di me. + +[MOB17_N] +Paul, cerca di dormire, davvero. + +[MOB18_A] +Tommy, sono Paulo, come butta? Bene amico, comunque, volevo farti una chiamata. + +[MOB18_B] +Ossignore, amico, non crederai mai che razza di strafiga ho appena incontrato. + +[MOB18_C] +Non so esattamente chi fosse, ma passeggiava per Little Havana. + +[MOB18_D] +Ha detto che si chiamava Mercedes o qualcosa del genere. Oddio, dovresti proprio vedere questa gnocca. + +[MOB18_E] +Riuscirebbe a succhiare via la mina da una matita. Mi ha detto che ero il migliore che avesse mai avuto e via dicendo. + +[MOB18_F] +Dovresti farci un giro. Ci vediamo! + +[MOB19_A] +Tommy V, sono KP. Kent Paul. Gira voce che ti vogliano fare a pezzi. + +[MOB19_B] +Tieni gli occhi bene aperti, amico e ricorda: io non ti ho detto niente al riguardo. + +[MOB_20A] +Ehi Tommy, sono Paul. Ho appena sentito che sei stato un ragazzaccio cattivo. + +[MOB_20B] +Qualcuno si è offeso per il tuo comportamento da grand'uomo, e adesso è davvero arrabbiato. + +[MOB_20C] +Beh, non dire che non ti ho avvertito: vantarsi è un gioco pericoloso, amico. + +[MOB_20D] +Comunque, ho anche sentito che c'è una taglia sulla tua testa e che qualcuno ti ha già preso di mira, + +[MOB_20E] +per cui guardati la schiena e ricordati di me, amico. + +[MOB71_A] +Tommy, Tommy, sono Cortez. Que pasa? + +[MOB71_B] +La situazione è interessante. A lei come va? + +[MOB71_C] +Tommy, qui è sempre una battaglia. Scusami per la battuta scontata, ma abbiamo sventato un altro golpe. + +[MOB71_D] +La gente non smette mai di pretendere di più. + +[MOB71_E] +Da quando sono tornato da Vice City, abbiamo avuto tre rivoluzioni e quattro golpe falliti. + +[MOB71_F] +Fortunatamente, sono stato promosso ogni volta. + +[MOB71_G] +Ti volevo chiedere di Mercedes. + +[MOB71_H] +Sì? Cosa vuole sapere? + +[MOB71_I] +Oh Tommy, ho sentito tutte queste storie e non so cosa pensare. + +[MOB71_J] +Forse tutti si divertono a umiliarmi. + +[MOB71_K] +Forse pensa di poter fare ciò che vuole ma dimmi, Tommy: è tutto vero? + +[MOB71_L] +ma dimmi, Tommy: è tutto vero? + +[MOB71_M] +Che cosa? + +[MOB71_N] +Le storie che ho sentito: sta pensando davvero di diventare avvocato? + +[MOB71_O] +Oh Tommy, che vergogna. Noi Cortez siamo una famiglia all'antica e non permetteremo mai che una di noi diventasse avvocato. + +[MOB71_P] +Ti prego, dimmi che non è vero. Non credo resisterei al colpo. + +[MOB71_Q] +Colonnello, le assicuro che Mercedes non diventerà mai un avvocato. Non si preoccupi. + +[MOB71_R] +Grazie Tommy, la vergogna sarebbe schiacciante. Lei è una vera dama, non una parassita, capisci? + +[MOB71_S] +Capisco, Colonnello. + +[MOB71_T] +Comunque, Tommy, devi scusarmi: è appena arrivato il nuovo ministro degli interni. + +[MOB71_U] +Qualche anno fa, ho ucciso suo padre in un golpe fallito, per cui devo essere gentile. Buona giornata, amico. + +[MOB22_A] +Tommy, ti stai rivelando molto utile, amico. + +[MOB22_B] +Grazie, Cortez. Cosa mi dice del nostro accordo? + +[MOB22_C] +Tommy, sto lavorando senza sosta nel tentativo di raggiungere il fondo di questa fossa piena di bugie e inganni, + +[MOB22_D] +hai la mia parola al riguardo, ma nel frattempo, + +[MOB22_E] +ti prego di accettare i più sinceri ringraziamenti da parte della gente per cui stai lavorando. + +[MOB_25A] +Tommy, sono Cortez. I Francesi mi stanno causando tutti i problemi possibili. + +[MOB_25B] +Maledetti ipocriti. Hanno passato secoli a derubare la povera gente e adesso osano chiamare me ladro! + +[MOB_25C] +Avrò bisogno del tuo aiuto il prima possibile. + +[MOB_25D] +Sbrigati, Tommy: ho bisogno di te. Odio questi dannati Francesi. + +[MOB_26A] +Ehi, Tommy? + +[MOB_26B] +Sì? + +[MOB_26C] +Sono Baker. Volevo dirti che ho davvero apprezzato lo show. + +[MOB_26D] +Io e i ragazzi vogliamo ringraziarti e ricordarti + +[MOB_26E] +che hai tutto il nostro rispetto. Buona giornata, continua così. + +[MOB_29A] +Salve, parlo con Mr. Tommy Vercetti? + +[MOB_29B] +Sì. + +[MOB_29C] +Beh, ho sentito dire che sei tu l'uomo da chiamare in caso di infestazione da parassiti. + +[MOB_29D] +Forse... + +[MOB_29E] +Beh, ho in corso un'infestazione davvero pesante: Haitiani da tutte le parti. + +[MOB_29F] +Mi chiamo Umberto Robina e vorrei incontrarmi con te al Cafe Robina il prima possibile, + +[MOB_29G] +perché questa volta i fottuti Haitiani sono andati troppo oltre. + +[MOB_29H] +Test + +[MOB_30A] +Tommy, sono Umberto Robina. + +[MOB_30B] +Come va al caffè? + +[MOB_30C] +Oh, meraviglioso. Incredibile, Tommy, incredibile. Niente perdenti, Tommy, solo veri uomini, sai no, e donne stupende. + +[MOB_30D] +Comunque, volevo dirti che per tutti noi adesso sei un vero Cubano. + +[MOB_30E] +Hai provato di essere all'altezza. Hai provato di avere due grandi cojones. + +[MOB_30F] +Err... grazie, Umberto. Nessuno mi ha detto una cosa simile da quando ho lasciato la galera. Ci becchiamo in giro. + +[MOB_33A] +Tommy, sono Phil: smettila di perdere tempo con le vaccate e ascoltami, intesi? + +[MOB_33B] +Bene. Ho un bel po' di broda bella forte quasi a perfetta fermentazione e mi chiedevo se ne volevi assaggiare un po'. + +[MOB_33C] +Ti assicuro, Tommy, che se ami bere, o se devi rimuovere della vernice, questa è la roba che fa per te. + +[MOB_33D] +Per me è stata una bomba, anche se non ci vedo più bene da un occhio. Ti aspetto, ciao. + +[MOB_34A] +Tommy, mi è davvero piaciuto lavorare con te. Non mi divertivo così dalle scorrerie in Vietnam, amico. + +[MOB_34B] +Se mai ti servisse qualcosa, chiamami, capito? + +[MOB_34C] +Ricordo sempre quelli coi quali ho prestato servizio, + +[MOB_34D] +e sono sicuro di poterti dare una mano, chiaro? + +[MOB_35A] +Tommy, la ferita si sta rimarginando bene. È divertente: + +[MOB_35B] +ho combattuti in sei campi di battaglia e sono sempre uscito senza un graffio... e adesso questo! + +[MOB_35C] +Phil senza un braccio. Comunque, ho una abbondante selezione di armi a una mano, per cui almeno non sarò disarmato! + +[MOB_35D] +Comunque, figliolo, lasciamo stare le stronzate sentimentali e vedi di scolarti qualcosa da parte mia! + +[MOB_36A] +Tommy, sono Phil. Ti volevo ringraziare di essere stato sul campo, figliolo. + +[MOB_36B] +Maledetti Viet, non perdono occasione per farti un attentato... + +[MOB_36C] +Comunque, la ferita sta guarendo: adesso non mi sentirò più in colpa quando riceverò il mio consueto assegno per disabili. Grazie, amico. + +[MOB_40A] +Ehi, Tommy, sono Sonny. Come va l'abbronzatura? + +[MOB_40B] +Non ho preso il sole. + +[MOB_40C] +Beh, non hai preso neanche i miei soldi, per cui mi chiedevo: + +[MOB_40D] +che cosa stai combinando? Dimmi un po', Tommy, che cosa fai? + +[MOB_40E] +Sto cercando i soldi, Sonny, non ti preoccupare. + +[MOB_40F] +Invece mi preoccupo, Tommy, è il mio stile, + +[MOB_40G] +poiché ho sempre dei problemi con gente che non ama rispettare i patti. + +[MOB_40H] +Non diventare inaffidabile, Tommy, fallo per me... + +[MOB_40I] +Fai un favore a entrambi. Non vedo l'ora di ricevere buone nuove. + +[MOB_41A] +Tommy, ti ricordi di me? + +[MOB_41B] +Ehi, Sonny. + +[MOB_41C] +Sì, sono proprio io, Sonny. Siamo vecchi amici, + +[MOB_41D] +e tu non mi scrivi mai, non mi chiami mai. Non vuoi più essere mio amico? + +[MOB_41E] +Sono stato occupato a mettere le cose a posto. Non mi hai dato un gran che di aiuto da queste parti. + +[MOB_41F] +Ah, è questa la mia colpa? Beh, ho sentito che sei stato molto occupato. + +[MOB_41G] +Occupato a uccidere i baroni della droga. Occupato a prendere il potere. + +[MOB_41H] +Non dimenticarti di noi, Tommy, poiché, te lo assicuro, io non mi sono dimenticato di te. + +[MOB_42A] +Tommy. + +[MOB_42B] +Sonny. + +[MOB_42C] +Hai di sicuro dei problemi di udito, per cui proverò ancora... + +[MOB_42D] +Tommy, dove sono i maledetti soldi, dov'è la maledetta roba e dov'è la mia parte dei tuoi ultimi guadagni? + +[MOB_42E] +Tommy, mi stai facendo fare la figura dell'idiota, e io non sto ridendo. + +[MOB_43A] +Tommy, Tommy, Tommy, avevo Sonny al telefono... OK, sei con me? + +[MOB_43B] +Non so te, ma so che qualcuno sta minacciando di morte la mia famiglia, + +[MOB_43C] +il che mi sta davvero facendo cagare sotto. Che cosa intendi fare? + +[MOB_43D] +Stai calmo, Ken, andrà tutto bene. + +[MOB_43E] +Io SONO calmo, calmo quanto un uomo in pericolo di vita! + +[MOB_43F] +Ken, ora come ora ho altre cose per la testa, + +[MOB_43G] +ci occuperemo di Forelli al momento giusto. + +[MOB_43H] +Io sono calmo. Non ti sembro calmo? Forse è la morte incombente che mi sta facendo tremare la voce. + +[MOB45_A] +Tommy, dobbiamo parlare di un po' di cose. + +[MOB45_B] +C'è qualche problema, Lance? + +[MOB45_C] +Si tratta di te, amico, non mi sembra di ricevere la fetta che merito. + +[MOB45_D] +E inoltre, mi hai imbarazzato davanti ai ragazzi. Non lo posso permettere. + +[MOB45_E] +Lance, non è andata così. Hai commesso degli errori. + +[MOB45_F] +Tommy, non sono il tuo fattorino. Non sono un tuo scagnozzo. + +[MOB45_G] +Lance, non fare casini e non avremo alcun problema. Se faccio un errore, me la puoi far pagare in qualsiasi momento. + +[MOB45_H] +Tommy, ho fatto tutto per te e mi tratti come un idiota. Non farlo. + +[MOB45_I] +Lance, non ti causerò problemi e non ti pugnalerò alle spalle, promesso. + +[MOB45_J] +Rilassati. La cosa è già abbastanza difficile senza che tu ti metta a fare il tenero con me. + +[MOB45_K] +Fidati: mi hai sentito, mi hai sentito? + +[MOB45_L] +Ti ho sentito, ma Tommy, non sopporterò ancora per lungo. + +[MOB45_M] +Lance, non fare così: adesso sono io ad avvertirti. + +[MOB45_N] +Mi hai sentito? Rilassati, prenditi qualche giorno e poi ne riparliamo, OK? + +[MOB46_A] +Tommy, sono Lance. + +[MOB46_B] +Sì? + +[MOB46_C] +Sono felice di sentirti Lance. Forza, stai tranquillo, stai tranquillo. + +[MOB46_D] +Sono nel mezzo di un problema: ho bisogno di te. + +[MOB46_E] +Niente, giusto per dire. Tommy, ce ne possiamo occupare. + +[MOB46_F] +Io e te, nessun problema. Capisci ciò che intendo? + +[MOB46_G] +Dobbiamo proprio farlo, se no siamo entrambi morti, Lance. + +[MOB46_H] +Siamo andati troppo oltre, ma grazie della chiamata. Ci sentiamo più tardi. + +[MOB_47A] +Tommy, sono Lance. Abbiamo un problema serio. Vieni qui in fretta. + +[MOB52_A] +Ehi, Leo, sembra ci sia un compratore per la roba di Diaz. + +[MOB52_B] +Devi fargli uno squillo, ragazzo, e preparare l'accordo, chiaro? + +[MOB52_C] +Dove ti trovi adesso? + +[MOB52_D] +Stai bene Leo? Mi sembri un po' strano... + +[MOB52_E] +Dimmi dove ti trovi. + +[MOB52_F] +Chi diavolo sei? Passami subito Leo! + +[MOB52_G] +Leo se ne è andato per un po' e adesso sono io in carica. + +[MOB52_H] +Fottiti, stronzo! + +[MOB54_A] +Ciao Tommy! + +[MOB54_B] +Ciao Mercedes, come butta? + +[MOB54_C] +Ho un nuovo appartamento in Vice Point... + +[MOB54_D] +magari un giorno di questi potresti farci un salto. + +[MOB54_E] +Mi piacerebbe, ci sentiamo. + +[MOB55_A] +Tommy, sono io. + +[MOB55_B] +Ciao Mercedes. + +[MOB55_C] +Tommy, sono così annoiata: perché non ci divertiamo un po' insieme? + +[MOB55_D] +Cosa intendi dire? + +[MOB55_E] +Beh, lo so che sei occupato a combattere, uccidere e corrompere gente, + +[MOB55_F] +ma io ho voglia di divertirmi un po'. Non ti scordare di me, capito? + +[MOB56_A] +Tommy, ho sentito che hai ucciso Ricardo Diaz. + +[MOB56_B] +C'è stato uno sfortunato incendio nella sua villa. + +[MOB56_C] +Credo sia morto bruciato nella sua camicia in acrilico. + +[MOB56_D] +Tommy, sono così fiera di te. Lo sapevo che eri un vero uomo. + +[MOB56_E] +Era un inutile idiota senza testa: mi rendi orgogliosa di essere tua amica. + +[MOB56_F] +No, lo so che sei impegnato a prendere il possesso di questa città, + +[MOB56_G] +ma non dimenticarti di me, capito? + +[MOB57_A] +Sono Mercedes. Non ti amo più, Tommy. + +[MOB57_B] +Proprio no, onestamente. Tu non sei più gentile con la tua Mercedes. + +[MOB57_C] +Non mi tratti più come una signora: mi ignori e io ti odio. + +[MOB57_D] +Insisto che tu venga a trovarmi immediatamente! + +[MOB58_A] +Tommy. + +[MOB58_B] +Ehi Mercedes. + +[MOB58_C] +Ehi sì, Mr. Tipo Duro. Sono davvero arrabbiata con te, Tommy. + +[MOB58_D] +Non farmi più passare le serate con Jezz Torrent. + +[MOB58_E] +È davvero patetico. A metà delle scale ha iniziato a piagnucolare per il suo cane + +[MOB58_F] +che è morto quando aveva sette anni e a ripetere che la mamma non lo ha mai amato. + +[MOB58_G] +E Tommy, indossa una parrucca e un reggiseno quando è in intimità. + +[MOB58_H] +Non sono molto felice di te! + +[MOB59_A] +Ooh Tommy, sono Mercedes. + +[MOB59_B] +Volevo solo dirti che mi sono divertita così tanto sul set del film. + +[MOB59_C] +Se hai qualcos'altro del genere, fammelo sapere. + +[MOB59_D] +Lo penso davvero. Ho sempre desiderato fare l'attrice. + +[MOB59_E] +Credo di aver imparato molto sull'approccio drammatico. + +[MOB59_F] +È così illuminante! Grazie, grazie! Spero di vederti presto! Adios. + +[MOB_99] +Raggiungi il telefono pubblico. + +[MOB_98] +Raggiungi il telefono pubblico. + +[MOB_97] +Raggiungi il telefono pubblico. + +[MOB_96] +Raggiungi il telefono pubblico. + +[MOB_95] +Raggiungi il telefono pubblico. + +[A_TIME] ++~1~ secondi + +[F_RANGE] +~g~Sei fuori dal raggio d'azione della radio. Avvicinati alla stazione dei pompieri! + +[DODO_FT] +Hai volato per ~1~ secondi! + +[GA_8] +Usa il detonatore per attivare la bomba. + +[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 bonus... + +[GA_15] +Spero che ti piaccia il nuovo colore. + +[GA_16] +La riverniciatura è gratuita. + +[GA_19] +Non siamo interessati a questo modello. + +[GA_20] +Di questo modello ne abbiamo in abbondanza. Mi dispiace, non siamo interessati. + +[CHASE] +Massima attenzione dei media + +[CHASE1] +Ignoto + +[CHASE2] +Noioso + +[CHASE3] +Appena interessante + +[CHASE4] +Giornale locale, pagina 7 + +[CHASE5] +Giornale locale, prima pagina + +[CHASE6] +Vice Courier, 1 colonna + +[CHASE7] +Vice Courier, prima pagina + +[CHASE8] +TV locale, 3 AM + +[CHASE9] +TV locale, news + +[CHASE10] +TV locale, servizio in diretta + +[CHASE11] +UFA Today, pagina 12 + +[CHASE12] +UFA Today, pagina 4 + +[CHASE13] +Foto in UFA Today + +[CHASE14] +TV nazionale, 4 AM + +[CHASE15] +TV nazionale, news + +[CHASE16] +TV nazionale, servizio in diretta + +[CHASE17] +Avvenimento internazionale + +[CHASE18] +Crisi nazionale + +[CHASE19] +Crisi internazionale + +[CHASE20] +Evento mondiale + +[CHASE21] +Roba da leggenda + +[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... + +[FEM_ON] +ON + +[FEM_OFF] +OFF + +[FEM_YES] +Sì + +[FEM_NO] +No + +[FEM_RET] +Riprova + +[FEC_NA] +ND + +[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à visuale + +[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_LB] +Guarda indietro + +[FEC_LR] +Guarda a destra + +[FEC_HOR] +Clacson + +[FEC_VES] +Comandi veicolo + +[FEC_BRA] +Freno o retromarcia + +[FEC_HAB] +Freno a mano + +[FEC_CAW] +Arma vettura + +[FEC_ACC] +Accelera + +[FEC_CCF] +Configurazione : + +[FEC_CF1] +Config 1 + +[FEC_CF2] +Config 2 + +[FEC_CF3] +Config 3 + +[FEC_CF4] +Config 4 + +[FEC_CDP] +Schermata controller : + +[FEC_ONF] +A piedi + +[FEC_INC] +In macchina + +[FEC_VIB] +Vibrazione : + +[FEL_ENG] +Inglese + +[FEL_FRE] +Francese + +[FEL_GER] +Tedesco + +[FEL_ITA] +Italiano + +[FEL_SPA] +Spagnolo + +[FED_DBG] +Menu Debug + +[FED_RID] +Reload IDE + +[FED_RIP] +Reload IPL + +[FED_PAH] +Parse Heap + +[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 + +[PL_STAT] +Statistiche giocatore + +[PE_WAST] +Persone massacrate + +[PE_WSOT] +Persone massacrate da altri + +[TM_BUST] +Arresti subiti + +[GNG_WST] +Membri delle gang massacrati + +[DED_CRI] +Criminali massacrati + +[PER_COM] +Percentuale completata + +[KGS_EXP] +Kg di esplosivo utilizzati + +[ACCURA] +Precisione + +[ST_WEAP] +Budget armi + +[ST_PROP] +Budget proprietà + +[ST_AUTO] +Budget riparazioni e colorazione auto + +[ST_PHOT] +Fotografie scattate + +[ST_LOAN] +Visite degli usurai + +[ST_STOR] +Edifici razziati + +[ST_MOVI] +Stunt nei film + +[ST_PIZZ] +Pizze consegnate + +[ST_GARB] +Spazzatura raccolta + +[ST_ICEC] +Gelati venduti + +[TOP_SHO] +Miglior punteggio al poligono + +[SHO_RAN] +Graduatoria al poligono + +[SEAGULL] +Gabbiani abbattuti + +[PROPOWN] +Proprietà posseduti + +[ST_TIME] +Tempo di gioco + +[ST_FTIM] +Ore di volo + +[ST_PRAN] +Graduatoria pilota + +[ST_RAN0] +Apprendista + +[ST_RAN1] +Navigatore + +[ST_RAN2] +Copilota + +[ST_RAN3] +Pratico + +[ST_RAN4] +Competente + +[ST_RAN5] +Anziano + +[ST_RAN6] +Asso + +[ST_RAN7] +Barone Rosso + +[ST_DRWN] +Pesci sfamati + +[ST_FASH] +Budget abbigliamento + +[ST_DAMA] +Proprietà distrutte + +[TM_DED] +Visite all'ospedale + +[DAYSPS] +Giorni trascorsi nel gioco + +[NUMSHV] +Visite al rifugio + +[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 + +[MXJUMP] +Numero max rotazioni FOLLI + +[BUL_FIR] +Colpi sparati + +[BUL_HIT] +Colpi a segno + +[SPRAYIN] +Riverniciature + +[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 completate + +[NMISON] +Missioni provate + +[PASDRO] +Passeggeri scaricati + +[MONTAX] +Soldi guadagnati in taxi + +[DAYPLC] +Spesa giornaliera polizia + +[CRIMRA] +Livello criminalità: + +[STPR_1] +Malibu + +[STPR_2] +Tipografia + +[STPR_3] +Studio cinematografico + +[STPR_4] +Fabbrica di gelato + +[STPR_5] +Lavaggio auto + +[STPR_6] +Compagnia di taxi + +[STPR_7] +Cantiere navale + +[SET1EN] +Config. 1. Abilitata. + +[GMSAVE] +Salva partita + +[FEDS_TB] +Indietro + +[FEST_OO] +su + +[FEC_TUC] +Comandi torretta + +[FEC_RS3] +Stazioni radio (tasto L3) + +[FEC_HO3] +Clacson (tasto L3) + +[C_FAIL] +Missione Vigilante terminata! + +[C_ESCP] +~r~Il sospettato è fuggito! + +[C_VIGIL] +BONUS VIGILANTE! + +[HEAL_A] +La tua ~h~salute~w~ è indicata in color arancione in alto a destra sullo schermo. + +[WRONGCD] +Disco errato. Inserisci il disco corretto. + +[NOCD] +Il cassetto del disco è vuoto. Inserisci il disco. + +[OPENCD] +Il cassetto del disco è aperto: è necessario chiuderlo. + +[CDERROR] +Errore di lettura dal DVD Grand Theft Auto: Vice City. + +[RESTART] +Avvio di una nuova partita + +[GA_3] +Niente più sconti. $100 per la riverniciatura! + +[GA_1] +Wow! Non tocco niente di COSÌ caldo! + +[GA_1A] +Torna quando non sarai così occupato... + +[HELP9_C] +Premi il ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile di precisione. + +[TAXI2] +~r~Tempo scaduto! + +[PAGEB13] +Salute consegnata al nascondiglio + +[PAGEB14] +Adrenalina consegnata al nascondiglio + +[FESZ_CA] +Annulla + +[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? + +[T4X4_1] +'Parco divertimenti PCJ' + +[BMX_1] +'Prova del fango' + +[BMX_2] +'Tracciato di prova' + +[BMXFAIL] +~r~Non sei riuscito a stabilire un nuovo record! + +[T4X4_3] +'TENUTA!' + +[MM_1] +'FOLLIA CONICA' + +[T4X4_F] +~r~Ti sei ritirato! Troppo difficile per te? + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Familiare + +[SENTINL] +Sentinel + +[RIO] +Rio + +[PATRIOT] +Patriot + +[FIRETRK] +Camion dei pompieri + +[TRASHM] +Nettezza urbana + +[STRETCH] +Stretch + +[MANANA] +Manana + +[INFERNS] +Infernus + +[VOODOO] +Voodoo + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulanza + +[FBICAR] +FBI Washington + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[WASHING] +Washington + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF a iniezione + +[HUNTER] +Hunter + +[POLICAR] +Polizia + +[ENFORCR] +Volante + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Autobus + +[RHINO] +Rhino + +[BARRCKS] +Caserma OL + +[CUBAN] +Cuban Hermes + +[HELI] +Elicottero + +[DODO] +Dodo + +[COACH] +Pullman + +[CABBIE] +Vecchio taxi + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +Bandit radiocomandato + +[ROMERO] +Carro funebre di Romero + +[PACKER] +Packer + +[ADMIRAL] +Admiral + +[SQUALO] +Squalo + +[SEASPAR] +Sea Sparrow + +[PIZZABO] +Pizza Boy + +[GANGBUR] +Gang Burrito + +[TROPIC] +Tropic + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[CADDY] +Caddy + +[ZEBRA] +Taxi Zebra + +[TOPFUN] +Top Fun + +[SKIMMER] +Skimmer + +[PCJ600] +PCJ 600 + +[PHOENIX] +Phoenix + +[FAGGIO] +Faggio + +[FREEWAY] +Freeway + +[RCBARON] +Baron radiocomandato + +[RCRAIDE] +Raider radiocomandato + +[GLENDAL] +Glendale + +[OCEANIC] +Oceanic + +[SANCHEZ] +Sanchez + +[SPARROW] +Sparrow + +[LOVEFIS] +Love Fist + +[COASTG] +Coast Guard + +[DINGHY] +Dinghy + +[HERMES] +Hermes + +[SABRE] +Sabre + +[SABRETU] +Sabre Turbo + +[WALTON] +Walton + +[REGINA] +Regina + +[COMET] +Comet + +[DELUXO] +Deluxo + +[BURRITO] +Burrito + +[SPAND] +Spandex Express + +[MARQUIS] +Marquis + +[BAGGAGE] +Baggage Handler + +[KAUFMAN] +Taxi Kaufman + +[COASTMA] +Coastguard Maverick + +[MAVERIC] +Maverick + +[RANCHER] +Rancher + +[FBIRANC] +FBI Rancher + +[VIRGO] +Virgo + +[GREENWO] +Greenwood + +[HOTRING] +Hotring Racer + +[BLISTAC] +Blista Compact + +[FEST_DF] +Distanza a piedi (miglia) + +[FEST_DC] +Distanza in auto (miglia) + +[FESTDFM] +Distanza a piedi (metri) + +[FESTDCM] +Distanza in auto (metri) + +[TOT_DIS] +Distanza totale (miglia) + +[TOTDISM] +Distanza totale (metri) + +[DISTHEL] +Distanza in elicottero (miglia) + +[DISTHEM] +Distanza in elicottero (metri) + +[DISTBOA] +Distanza in barca (miglia) + +[DISTBOM] +Distanza in barca (metri) + +[FEST_LS] +Persone salvate in ambulanza + +[FEST_CC] +Criminali uccisi in missioni Vigilante + +[FEST_FE] +Incendi estinti + +[FEST_RP] +Violenze eseguite + +[FEST_MP] +Missioni completate + +[FEST_BB] +Bling-bling Scramble: + +[FEST_H0] +Maggior numero di punti di controllo + +[FEST_GC] +Auto delle gang distrutte: + +[FEST_H1] +Distruzione Diablo + +[FEST_H2] +Massacro Mafioso + +[FEST_H3] +Calamità al Casinò + +[FEST_H4] +Demolizioni Rumpo + +[USJ] +BONUS ACROBAZIA UNICA! + +[RATNG1] +Cittadino onesto + +[RATNG2] +Sconosciuto + +[RATNG3] +Pezzente + +[RATNG4] +Furfantello + +[RATNG5] +Vandalo + +[RATNG6] +Fattorino + +[RATNG7] +Borsaiolo + +[RATNG8] +Cleptomane + +[RATNG9] +Teppista + +[RATNG10] +Ladruncolo + +[RATNG11] +Sanguisuga + +[RATNG12] +Truffatore + +[RATNG13] +Farabutto + +[RATNG14] +Informatore + +[RATNG15] +Imbroglione + +[RATNG16] +Bullo + +[RATNG17] +Canaglia + +[RATNG18] +Scapestrato + +[RATNG19] +Ruffiano + +[RATNG20] +Fuorilegge + +[RATNG21] +Delinquente + +[RATNG22] +Ladro + +[RATNG23] +Scagnozzo + +[RATNG24] +Tirapiedi + +[RATNG25] +Avanzo di galera + +[RATNG26] +Ex galeotto + +[RATNG27] +Criminale + +[RATNG28] +Picchiatore + +[RATNG29] +Saggio + +[RATNG30] +Autista + +[RATNG31] +Guardia del corpo + +[RATNG32] +Attaccabrighe + +[RATNG33] +Cacciatore di taglie + +[RATNG34] +Vigilante + +[RATNG35] +Ronin + +[RATNG36] +Riparatore + +[RATNG37] +Sicario + +[RATNG38] +Socio + +[RATNG39] +Macellaio + +[RATNG40] +Pulitore + +[RATNG41] +Assassino + +[RATNG42] +Consigliere + +[RATNG43] +Consulente + +[RATNG44] +Braccio destro + +[RATNG45] +Esecutore + +[RATNG46] +Tenente + +[RATNG47] +Aiutante del boss + +[RATNG48] +Capo + +[RATNG49] +Boss + +[RATNG50] +Intoccabile + +[RATNG51] +Don + +[RATNG52] +Re della fottuta città + +[PAGE_00] +. + +[WELCOME] +BENVENUTO A + +[TSCORE] +GUADAGNI: ~1~$ + +[PBOAT_2] { reVC update } +Premi il ~h~~k~~VEHICLE_FIREWEAPON~~w~ per sparare con i cannoni della barca. + +[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! + +[ATUTOR] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Infermiere. + +[FEST_HA] +Livello massimo della missione Infermiere + +[C_KILLS] +CRIMINALI UCCISI: ~1~ + +[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! + +[CRED001] +ROCKSTAR NORTH + +[CRD001A] +DIRETTORE DELLO STUDIO + +[CRD001B] +ANDREW SEMPLE + +[CRED002] +PRODUTTORE + +[CRD002A] +DIRETTORE DI SVILUPPO + +[CRED003] +LESLIE BENZIES + +[CRED004] +DIRETTORE ARTISTICO + +[CRED005] +AARON GARBUT + +[CRED006] +DIRETTORI TECNICI + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED010] +ANDREW DUTHIE + +[CRED011] +CRAIG FILSHIE + +[CRED012] +WILLIAM MILLS + +[CRED013] +CHRIS ROTHWELL + +[CRD013A] +IMRAN SARWAR + +[CRD013B] +JAMES WORRALL + +[CRD013C] +JOHN HAIME + +[CRED014] +SCRITTO DA + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +PROGETTAZIONE PERSONAGGI PRINCIPALI + +[CRD018A] +PROGETTAZIONE PERSONAGGI + +[CRED019] +IAN MCQUE + +[CRD019A] +TOKS SOLARIN + +[CRD019B] +ALAN DAVIDSON + +[CRED020] +ANIMATORE CAPO + +[CRED021] +ALEX HORTON + +[CRD022A] +ANIMATORI + +[CRED022] +LEE MONTGOMERY + +[CRD022B] +DUNCAN SHIELDS + +[CRD022C] +GUS BRAID + +[CRED023] +PROGETTAZIONE VEICOLI + +[CRD023A] +JOLYON ORME + +[CRD023B] +ALAN DUNCAN + +[CRD024A] +CAPO PROGETTAZIONE VEICOLI + +[CRED024] +PAUL KUROWSKI + +[CRED025] +PROGETTAZIONE MAPPA + +[CRED026] +ADAM COCHRANE + +[CRED027] +NIK TAYLOR + +[CRED028] +GARY MCADAM + +[CRED029] +KEIRAN BAILLIE + +[CRED030] +ALISDAIR WOOD + +[CRED031] +ANDREW SOOSAY + +[CRD031A] +STEVEN MULHOLLAND + +[CRD031B] +WAYLAND STANDING + +[CRD031C] +CAMPBELL J. DICK + +[CRD031D] +PROGETTAZIONE GRAFICA + +[CRD031E] +STUART PETRI + +[CRED032] +PROGRAMMATORE CAPO + +[CRD032A] +PROGRAMMATORI + +[CRED033] +ALEXANDER ROGER + +[CRED034] +GRAEME WILLIAMSON + +[CRED035] +BARANE CHAN + +[CRED036] +DEREK PAYNE + +[CRED037] +GORDON YEOMAN + +[CRD037A] +ALAN CAMPBELL + +[CRD037B] +MARK HANLON + +[CRD037C] +ANDRZEJ MADAJCZYK + +[CRED038] +CAPO PRODUZIONE MUSICA + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +CAPO INGEGNERE AUDIO + +[CRED042] +ALLAN WALKER + +[CRD041A] +INGEGNERE AUDIO + +[CRD041B] +AUDIO + +[CRD042A] +WILL MORTON + +[CRED043] +PROGRAMMATORE AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +RESPONSABILE TESTING + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +CAPO CQ + +[CRED048] +NEIL CORBETT + +[CRED049] +KEVIN WONG + +[CRED050] +CQ + +[CRED051] +DAVID BEDDOES + +[CRED052] +DAVID WATSON + +[CRED053] +BARRY CLARK + +[CRED054] +ROSS SPARROW + +[CRED055] +JAMES ALLAN + +[CRED056] +NEIL MEIKLE + +[CRD056A] +GEORGE WILLIAMSON + +[CRD056B] +MATT JONES + +[CRD056C] +ROB HARBOUR + +[CRD056D] +TOM WHITTAKER + +[CRED057] +CAPO SUPPORTO TECNICO + +[CRED058] +LORRAINE ROY + +[CRD057A] +SUPPORTO TECNICO + +[CRED059] +CHRISTINE CHALMERS + +[CRD060A] +SUPPORTO UFFICIO + +[CRD060B] +KIM GURNEY + +[CRD060C] +CASSIE OLIVER + +[CRED060] +ROCKSTAR NEW YORK + +[CRED061] +PRODUTTORE ESECUTIVO + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUTTORE + +[CRED064] +DAN HOUSER + +[CRED065] +VP DI SVILUPPO + +[CRED066] +JAMIE KING + +[CRED067] +CAPO RESPONSABILE TECNOLOGICO + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUTTORE ASSOCIATO + +[CRED070] +JEREMY POPE + +[CRD071A] +DIRETTORE DEL CQ + +[CRD072A] +JEFF ROSA + +[CRED071] +SUPERVISORE MUSICA + +[CRED072] +TERRY DONOVAN + +[CRED073] +SQUADRA DI PRODUZIONE + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +JERONIMO BARRERA + +[CRED080] +CARLY SLATER + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +RENAUD SEBANNE + +[CRED084] +RICHARD KRUGER + +[CRD084A] +DANIEL EINZIG + +[CRD084B] +JACEN BURROWS + +[CRD084C] +LINN PR + +[CRD084D] +GRAFICA DI COPERTINA + +[CRD084E] +STEPHEN BLISS + +[CRED085] +KENT PAUL'S 80 NOSTALGIA ZONE + +[CRED086] +COMPOSTA DA DAN HOUSER + +[CRD086A] +PRODOTTA DA ADAM TEDMAN + +[CRED087] +WWW.KENTPAUL.COM E WWW.VICECITY.COM + +[CRED088] +CREATO DA + +[CRD088A] +ADAM TEDMAN + +[CRD088B] +DAVID YU + +[CRD088C] +JERRY LUNA + +[CRD088D] +STUART PETRI + +[CRD088E] +MICHAEL CARNEVALE + +[CRD088F] +GREG LAU + +[CRD088G] +FUTABA HAYASHI + +[CRED089] +RESPONSABILE CQ + +[CRED090] +CRAIG ARBUTHNOTT + +[CRED091] +CAPO ANALISTA + +[CRED092] +ADAM DAVIDSON + +[CRD092A] +JOE HOWELL + +[CRD092B] +MARC FERNANDEZ + +[CRED093] +ANALISTA DI GIOCO + +[CRED094] +RICHARD HUIE + +[CRED095] +SQUADRA TESTING ROCKSTAR + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRD098A] +ELIZABETH SATTERWHITE + +[CRD098B] +JAMEEL VEGA + +[CRD098C] +MIKE HONG + +[CRED099] +LEE CUMMINGS + +[CRED100] +SCENEGGIATURA + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +SEQUENZE ANIMATE + +[CRED108] +SCRITTE DA DAN HOUSER E JAMES WORRALL + +[CRED109] +DIREZIONE AUDIO DI DAN HOUSER E NAVID KHONSARI + +[CRED110] +PRODOTTE DA JAMIE KING + +[CRD110A] +RICERCA TALENTI: JAMIE KING, SEAN MACALUSO + +[CRED111] +CAST + +[CRD111A] +PERSONAGGI PRINCIPALI + +[CRED112] +TOMMY VERCETTI - RAY LIOTTA + +[CRED113] +KEN ROSENBERG - WILLIAN FICHTNER + +[CRED114] +SONNY FORELLI - TOM SIZEMORE + +[CRED115] +STEVE SCOTT - DENNIS HOPPER + +[CRED116] +AVERY CARRINGTON - BURT REYNOLDS + +[CRED117] +RICARDO DIAZ - LUIS GUZMAN + +[CRED118] +LANCE VANCE - PHILIP MICHAEL THOMAS + +[CRED119] +COLONNELLO JUAN CORTEZ - ROBERT DAVI + +[CRED120] +UMBERTO ROBINA - DANNY TREJO + +[CRED121] +PHIL CASSIDY - GARY BUSEY + +[CRED122] +MITCH BAKER - LEE MAJORS + +[CRED123] +MERCEDES CORTEZ - FAIRUZA BALK + +[CRED124] +KENT PAUL - DANNY DYER + +[CRED125] +JEZZ TORRENT - KEVIN MCKIDD + +[CRED126] +CENTRALINISTA TAXI - DEBORAH HARRY + +[CRED127] +CANDY SUXX - JENNA JAMESON + +[CRED128] +BJ SMITH - LAWRENCE TAYLOR + +[CRED129] +AUNTIE POULET - YOUREE CLEOMILI HARRIS + +[CRED130] +SPACCIATORE - ARMANDO RIESCO + +[CRED131] +COUGAR - BLAYNE PERRY + +[CRED132] +HILARY - CHARLES TUCKER + +[CRED133] +DEPUTATO ALEX SHRUB - CHRIS LUCAS + +[CRED134] +VECCHIO KELLY - GEORGE DICENZO + +[CRD134A] +CAM JONES - GREG SIMS + +[CRD134B] +PSICOPATICO - HUNTER PLATIN + +[CRD134C] +MAUDE, LA SIGNORA DEI GELATI - JANE GENNARO + +[CRD134D] +JETHRO - JOHN ZURHELLEN + +[CRD134E] +GONZALES - JORGE PUPO + +[CRD134F] +DWAYNE - NAVID KHONSARI + +[CRD134G] +DICK - PETER MCKAY + +[CRD134H] +MIKE & PORNOSTAR - ROBERT CIHRA + +[CRD134I] +PERCY - RUSSELL FOREMAN + +[CRED135] +MOTION CAPTURE + +[CRED136] +ANIMATO DA + +[CRD136A] +DIREZIONE TECNICA DI ALEX HORTON + +[CRED137] +DIRETTO DA + +[CRD137A] +DIRETTO DA NAVID KHONSARI + +[CRED138] +PRODOTTO DA + +[CRD138A] +PRODOTTO DA JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +REGISTRATO PRESSO PERSPECTIVE STUDIOS, BROOKLYN + +[CRED140] +ATTORI MOTION CAPTURE + +[CRD140A] +BLAYNE PERRY + +[CRD140B] +JONATHON SALE + +[CRD140C] +CHARLES TUCKER + +[CRD140D] +EDDIE MARRERO + +[CRD140E] +WILLIAM MCCALL + +[CRD140F] +JORGE PUPO + +[CRD140G] +ROBERT JACKSON + +[CRD140H] +TARA RADCLIFFE + +[CRD140I] +JENIFER GAMBETESE + +[CRD140J] +KRIS ACHEVARRIA + +[CRD140K] +ALI ORDOUBADI + +[CRD140L] +KAHLEEM POOLE + +[CRED141] +DIALOGHI PEDONI + +[CRD141A] +SCRITTI DA DAN HOUSER, MARC FERNANDEZ, GILLIAN TELLING E NAVID KHONSARI + +[CRD141B] +CON L'AIUTO DI JEREMY POPE, LANCE WILLIAMS E JENNY JEMISON + +[CRED142] +SCRITTI DA + +[CRD142A] +DAN HOUSER E JAMES WORRALL + +[CRED143] +DIRETTI DA DAN HOUSER, CRAIG CONNER, MARC FERNANDEZ E ALLAN WALKER + +[CRED144] +PRODOTTI DA RENAUD SEBANNE + +[CRED145] +PEDONI + +[CRED146] +ADAM DAVIDSON, ADAM WATKINS, ALEJANDRO K. BROWN, ALEX ANTHONY SIOUKAS, ALEX GARCIA, + +[CRED147] +ALICE SALTZMAN, ALISON CIHRA, AMY SALIMA, AMY SALZMAN, ANDREA VIDELA, ANTHONY ATTI, + +[CRED148] +ANTHONY RIVERA, BIJAN SHAMS, BLAYNE PERRY, BRETT BISOGNO, BREYE MATA, BRIAN PANEN, + +[CRED149] +BROCK VODER, CAREY BERTINI, CHARISSE LAMBERT, CHRIS DIFAT, CHRIS REISENBERGER, + +[CRED150] +CHRISTOPHER BRODAY, CHRISTOPHER CARRO, CYNTHIA GREENE, DAMARIES LOPEZ, DAN LEE, + +[CRED151] +DAN SCHNEIDER, DAN TOYAMA, DAVID DEAN CHALTFIELD, JR., DAVID HARRISON, DAVID WILEY, + +[CRED152] +DEBORAH COLLINS, DEBRANDA CHANEY-GILES, DEMETRA KOUKOULAS, DENISE ROSADO, + +[CRED153] +DEVIN BENNETT, DEVIN WINTERBOTTOM, DORIS WOO, DOUGLAS HARRISON, DUNCAN COUTTS, + +[CRED154] +DUPE AJAYI, EDWIN AVELLANEDA, ELIZABETH HOWELL, ELIZABETH SATTERWHITE, ERIC NAGLE, + +[CRED155] +ESTEBAN KARPLUS, F. FONT, FUTABA HAYASHI, GENE HILGREEN, GERALD COSGROVE, + +[CRED156] +GERARD LUNA, GILLIAN TELLING, GREGG CARLUCCI, GREGORY CLERVOIX, GREGORY SCHWEIZER, + +[CRED157] +HADLEY TOMICKI, J. ROSSETT, JAMEEL VEGA, JASON JONES, JEFF ROSA, JENNIFER JEMISON, + +[CRED158] +JEREMY TAGGERT, JESSICA RIDER, JOSEPH GREENE,JOSEPH HOWELL, KATE DUKICH, + +[CRED159] +KEL O'NEILL, KEVIN HOPKINS, LADAWN JAMES, LANCE WILLIAMS, LAURA BUBBLES, + +[CRED160] +LAURA PATTERSON, LEE CUMMINGS, LETICIA L. YOUNG, LINDSAY KENNEDY, LISA ORITZ, + +[CRED161] +LORNA JORDAN, LUCIO AMADIO, MARCO FERNANDEZ, MARIKO TANAKA, MARLON MATTHEWS, + +[CRED162] +MARY TELLING, MASAYOSHI MITSUYAMA, MATTHEW CHUNG, MAX ALLSTADT, MAX BOGDANOV, + +[CRED163] +MELISSA ALVAREZ, MICHAEL MAY, MICHAEL ROTHSTEIN, MIGUEL VIDAL, MIKE FEDERLINE, + +[CRED164] +NATALIE DESCALZO, N'GAI MEMBERS, NICOLAS MALLO, NOELLE SADLER, NORBERT MORIVAN, + +[CRED165] +OSWALD GREENE, JR., PETER MCKAY, PETER APPEL, PRESTON SAVARESE, RAFAEL GONZALES, + +[CRED166] +RANDY JOHNSON, REY CONCEPCION, RICHARD KROGER, ROB TIBBS, ROBERT JACKSON, + +[CRED167] +ROBERT SCHULER, ROSS A. MCINTYRE, RUSSELL FOREMAN, RUTH NUNEZ, SALVADORE SUAZO, + +[CRED168] +SAM WHITE, SANTOS GONZALES, SCOTT SMITH, SEYMOUR FRAILMAN, SPELMAN BRAUMAN, + +[CRED169] +STEPHANIE TELLING, STEVE KNEZEVICH, STEVE ROBERT, SUMIKO YASUDA, SUSAN LEWIS, + +[CRED170] +SYLVIA COLACIOS, TOMOKO MIYAZAKI, TRON, VERDEL HALE, YVES MONDESIR, ZENO LEINFELDER, + +[CRED171] +DAVID BEDDOES, CHRISTINE CHALMERS, BARRY CLARK, NEIL CORBETT, KIM GURNEY, NEIL MEIKLE, + +[CRED172] +CASSIE OLIVER, LORRAINE ROY, DAVID WATSON, KEVIN WONG, WILL MORTON + +[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] +CONSULENZA MUSICALE + +[CRD218A] +HEINZ HENN + +[CRD218B] +STUART ROSS + +[CRED219] +COORDINATORE TRACCE AUDIO + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUTTORI ROCKSTAR GAMES + +[CRED222] +DAN HOUSER E LAZLOW + +[CRED223] +PRODUTTORE ROCKSTAR NORTH + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER E IMAGING + +[CRED228] +SCRITTO DA DAN HOUSER E LAZLOW + +[CRED229] +FLASH FM + +[CRD229A] +TONI-MARIA CHAMBERS + +[CRD229B] +VOCE E PRODUZIONE IMAGING - JEFF BERLIN + +[CRED230] +UN RINGRAZIAMENTO SPECIALE A + +[CRED231] +TOMMY MOTTOLA, + +[CRED232] +MICHELLE ANTHONY, + +[CRED233] +STEVE BARNETT, + +[CRED234] +CHUCK FLECKENSTEIN, + +[CRED235] +RITA LIBERATOR + +[CRED236] +MARTIN & CLAIRE LOGAN + +[CRED237] +SANDRA HUTTON + +[CRED238] +CHRISTINE DAVIDSON + +[CRED239] +ALAN, RED & BIGFOOT + +[CRED240] +LE T + +[CRED241] +COLIN DONALD + +[CRED242] +KERRY STALLWOOD + +[CRED243] +ALAN MCGREGOR + +[CRED244] +CHRIS MORTON + +[CRED245] +EMIL BUSSE + +[CRED246] +EMILY BAILLIE + +[CRED247] +KEVIN ARCHIBALD + +[CRED248] +MORAG KERR + +[CRED249] +CATH WALKER + +[CRED250] +ISO BAR + +[CRED251] +WATERLINE + +[CRED252] +NEWS CAFE + +[CRD251A] +THE POND + +[CRD252A] +PIVO + +[CRED253] +BUDGET VIDEO RENTALS + +[CRED254] +LORNA'S SCOOTER + +[CRED255] +GARETH MURFIN + +[CRED256] +GRAFICA ADDIZIONALE + +[CRED257] +TONY PORTER + +[CRED258] +CRAIG MOORE + +[CRED259] +SINCRONIZZAZIONE LABIALE SEQUENZE + +[CRED260] +COSGROVE HALL FILMS + +[CRED261] +PRODUTTORE - OWEN BALLHATCHET + +[CRED262] +ANIMATORE SENIOR- JON TURNER + +[CRED263] +ANIMATORI - RICHARD DRUMM + +[CRED264] +DAVE BROWN + +[CRED265] +MAIR THOMAS + +[CRED266] +PRASHANT PATEL + +[CRED267] +CONSULENTE TECNOLOGICO AUDIO + +[CRED268] +RIK EDE DI GAMESOUND LTD. + +[CRED269] +SUPPORTO INTEGRAZIONE DTS + +[CRED270] +TED LAVERTY DI DTS + +[CRED271] +CHRIS GREER DI DTS + +[CRED272] +JASON PAGE DI DTS + +[CRED273] +RICERCA E ANALISI + +[CRED274] +VROCK + +[CRED275] +DJ: LAZLOW COME SE STESSO + +[CRED276] +VOCE IMAGING - JOE KELLY + +[CRED277] +PRODUZIONE IMAGING - JONATHAN HANST + +[CRED278] +WAVE 103 + +[CRED279] +DJ: ADAM FIRST - JAMIE CANFIELD + +[CRED280] +VOCE IMAGING - JEN SWEENEY + +[CRED281] +PRODUZIONE IMAGING - JONATHAN HANST + +[CRED282] +FEVER 105 + +[CRED283] +DJ: OLIVER 'LADYKILLER' BISCUIT - JULIUS DYSON + +[CRED284] +VOCE IMAGING MASCHILE - ED MCMANN + +[CRED285] +VOCE IMAGING FEMMINILE - SHAWNEE SMITH + +[CRED286] +PRODUZIONE IMAGING - LISTEN KITCHEN + +[CRED287] +EMOTION 98.3 + +[CRED288] +DJ: FERNANDO - FRANK CHAVEZ + +[CRED289] +VOCE IMAGING - JEN SWEENEY + +[CRED290] +PRODUZIONE IMAGING - JONATHAN HANST + +[CRED291] +RADIO ESPANTOSO + +[CRED292] +DJ: PEPE - TONY CHILRODES + +[CRED293] +WILDSTYLE + +[CRED294] +DJ: MISTER MAGIC COME SE STESSO + +[CRED295] +VOCE IMAGING - FRANK SILVESTRO + +[CRED296] +PRODUZIONE IMAGING - LAZLOW + +[CRED297] +KCHAT + +[CRED298] +SCRITTA DA DAN HOUSER E LAZLOW + +[CRED299] +PRODOTTA E MIXATA DA LAZLOW + +[CRED300] +DJ AMY SHECKENHAUSEN - LEYNA WEBER + +[CRED301] +JEZ TORRENT - KEVIN MCKIDD + +[CRED302] +MANDY - COLLEEN CORBETT + +[CRED303] +MICHELLE CARAPADIS - MARY BIRDSONG + +[CRED304] +MR.ZOO - CARL DOWLING + +[CRED305] +GETHSEMANEE - LYNN LIPTON + +[CRED306] +CLAUDE MAGINOT - JOHN MAUCERI + +[CRED307] +BJ SMITH - LAWRENCE TAYLOR + +[CRED308] +THOR - FRANK FAVA + +[CRED309] +VOCI PUBBLICITÀ + +[CRED310] +COUZIN ED, JOSH CLARK, JASON BUHRMESTER, JUAN ALLER, WAYNE OLIVER, SUSAN LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, KEITH BROADAS + +[CRED311] +LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, + +[CRED312] +DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, + +[CRED313] +KEITH BROADAS + +[CRED314] +VCPR + +[CRED315] +SCRITTO DA DAN HOUSER E LAZLOW + +[CRED316] +PRODOTTO DA LAZLOW + +[CRED317] +MAURICE CHAVEZ - PHILLIP ANTHONY RODRIGUEZ + +[CRED318] +JONATHAN FREELOADER - PATRICK OLSEN + +[CRED319] +MICHELLE MONTANIUS - KELLY GUEST + +[CRED320] +DEP. ALEX SHRUB - CHRIS LUCAS + +[CRED321] +CALLUM CRAYSHAW - SEAN MODICA + +[CRED322] +JOHN F. HICKORY - LJ GANSEN + +[CRED323] +PASTOR RICHARDS - DAVID GREEN + +[CRED324] +JAN BROWN - MAUREEN SILLIMAN + +[CRED325] +BARRY STARK - RENAUD SEBBANE + +[CRED326] +JENNY LOUISE CRAB - MARY BIRDSONG + +[CRED327] +KONSTANTINOS SMITH - KONSTANTINOS.COM + +[CRED328] +JEREMY ROBARD - PETER SILVESTRO + +[CRED329] +PUBBLICITÀ RADIO + +[CRED330] +SCRITTE DA DAN HOUSER E LAZLOW + +[CRED331] +PRODOTTE DA LAZLOW + +[CRED332] +JINGLE AGGIUNTIVI PRODOTTI DA CRAIG CONNER + +[CRED333] +VOCI PUBBLICITÀ + +[CRED334] +ADAM DAVIDSON, ALEX ANTHONY, ALICE SALTZMAN, AMY SALZMAN, KATE DUKICH, + +[CRED335] +ARAN RONICLE, BARB JONES, BEN KRECH, BRIAN THOMAS, BROCK YODER, CHRIS + +[CRED336] +FERRANTE, CRAIG CONNER, DAVE RYAN, DAVID GREEN, DORIS WOO, DOUGLAS + +[CRED337] +HARRISON, ED MCMANN, FRANK CHAVEZ, FRANK FAVA, GENE HILGREEN, GREG + +[CRED338] +SCHWEIZER, HUNTER PLATIN, JAMES FERRANTE, JEFF BERLIN, JEFF ROSA, JOE KELLY, + +[CRED339] +JOHN MAUCERI, JOSH CLARK, JULIE WEMYSS, KEVIN STRALEY, KIM GURNEY, LANCE + +[CRED340] +WILLIAMS, LAURA PATERSON, LAZLOW, LISA ORTIZ, LORNA JORDAN, LUCIEN JONES, + +[CRED341] +MAUREEN SILLIMAN, MIKE FERRANTE JR., PETE GUSTIN, PETER SILVESTRO, RAFF + +[CRED342] +CROLLA, RANDY JOHNSON, RICHARD KRUGER, RON REEVE, SHELLEY MILLER, SKY, TJ ALLARD + +[CRD344A] +AUDIO REGISTRATO PRESSO DIGITAL ARTS STUDIOS, + +[CRED344] +NYC, TRACK 9 STUDIOS, NYC, + +[CRED345] +WEDDINGTON MULTIMEDIA, LOS ANGELES, + +[CRD345A] +SYNC SOUND, NYC E RADIO LAZLOW, LONG ISLAND. + +[CRED346] +UN RINGRAZIAMENTO A AXEL ERICSON E WON LEE DI DIGITAL ARTS, PAUL VASQUEZ DI TRACK 9 STUDIOS, JOHN BOWEN E JOHN HASSLER DI SYNC SOUND + +[CRED347] +MARK LLOYD + +[CRED348] +TIM BATES + +[CRED349] +KIT BROWN + +[CRED350] +ANDY MASON + +[CRED351] +PHIL DEANE + +[CRED352] +PHIL ALEXANDER + +[CRED353] +MATT HEWITT + +[CRED354] +DENBY GRACE + +[CRED355] +ANTOINE CABROL + +[CRED356] +JONOTHAN STONES + +[CRED357] +MIKE BLACKBURN + +[CRED358] +TIM MCGAFF + +[CINCAM] +Camera mobile + +[RC4] +'LA FURIA DI RUMPO' + +[LEGAL] +~g~Elimina la minaccia criminale! + +[GA_2] +Motore nuovo e carrozzeria riverniciata. Gli sbirri non ti riconosceranno! + +[HELP15] +Quando sei a piedi, premi il ~h~~k~~PED_LOOKBEHIND~~w~ per ~h~guardare indietro~w~. Usa la ~h~levetta analogica destra~w~ per ~h~guardarti attorno~w~. + +[FEC_LB4] +Guarda indietro (tasto R3) + +[PERPIC] +Pacchetti speciali recuperati + +[CO_ONE] +Pacchetto speciale ~1~ su ~1~ + +[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 + +[USJ_ALL] +TUTTE LE ACROBAZIE UNICHE COMPLETATE + +[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] +--:---:---- --:--:-- + +[BONUS] +~g~BONUS ~1~$ + +[HORN1] +Premi il ~h~~k~~VEHICLE_HORN~~w~ per attivare il ~h~clacson~w~. + +[HORN2] +Premi il ~h~~k~~VEHICLE_HORN~~w~ per attivare il ~h~clacson~w~. + +[HORN3] +Premi il ~h~~k~~VEHICLE_HORN~~w~ per attivare il ~h~clacson~w~. + +[FEC_EXV] +Esci dal veicolo + +[TAXI_M] +'TAXI DRIVER' + +[COP_M] +'VIGILANTE' + +[FIRE_M] +'POMPIERE' + +[AMBUL_M] +'INFERMIERE' + +[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~$ + +[FESZ_LS] +Caricamento completato. + +[HELI_1A] +Prova le tue abilità con lo Sparrow: scopri quanto velocemente riesci a completare il percorso. + +[HELI_1B] +Percorso completato! + +[HELIODD] +Strani incarichi con l'elicottero + +[INT2_M] +Hanno una fattoria in Panama. + +[INT2_N] +OK, va bene, ascoltate: + +[INT2_O] +quando arriviamo volete che resti in macchina + +[INT2_P] +o preferite che venga con voi altri? + +[INT2_Q] +No, resta in macchina. + +[INT2_R] +Lo sapete, ci ho pensato su. + +[INT2_S] +Penso proprio che resterò in macchina. + +[INT4_A] +Fottuti! Siamo fottuti! + +[INT4_B] +È sempre la stessa storia, + +[INT4_C] +metto fuori la testa per un maledetto secondo, + +[INT4_D] +e il fato mi tira palate di merda in faccia! + +[INT4_E] +Bene, vaffanculo! + +[INT4_F] +Smettila di fare casino e di lamentarti. Siamo vivi, non è vero? + +[INT4_G] +Fammi scendere qua. + +[INT4_H] +Fai scomparire la macchina e fatti una bella dormita. + +[INT4_I] +Passerò domani a trovarti in ufficio e troveremo un modo per risolvere questo casino. + +[INT4_J] +OK, mi sembra un'ottima idea, andrò a dormire. + +[INT4_K] +Che cosa intendi fare? + +[INT4_L] +Tornare nella mia stanza, + +[INT4_M] +schiarirmi le idee e risolvere questo casino. + +[INT4_N] +OK. + +[LAW] +MISSIONI DELL'AVVOCATO + +[LAW1_1] +~g~Vai a prendere degli abiti nuovi dal negozio di vestiti Rafael's. + +[LAW4_6] +Brucia l'amministrazione! + +[LAW4_7] +Uccidi i responsabili! + +[LAW4_8] +Combattere, combattere, combattere, combattere,. + +[LAW4_9] +Più ferie, meno lavoro! + +[LAW4_11] +Combattere, combattere, combattere, combattere,. + +[LAW4_12] +Viva la rivoluzione. + +[GENERAL] +MISSIONI DEL COLONNELLO + +[GEN3_4] +Tommy Vercetti. Andiamo... + +[GEN3_13] +Che cosa diavolo ti prende? Vai sul tetto dall'altra parte del cortile prima che arrivino! + +[GEN3_17] +Meeerda! Stai cercando di ammazzarmi? + +[GEN3_21] +~g~Ha preso i soldi di Diaz! Inseguilo e riprendili! + +[GEN3_24] +~r~Diaz è morto! Non sei riuscito a proteggerlo! + +[GEN3_26] +~r~Hai ucciso Diaz! + +[GEN3_27] +~r~Hai ucciso una guardia di Diaz! + +[GEN3_31] +~g~Vai all'appuntamento e proteggi Diaz. + +[GEN3_32] +~g~Raggiungi il tetto nell'edificio di fronte a Lance e appostati. + +[COKE] +MISSIONI DEL BARONE DELLA COCA + +[COK1_3] +Spero tu cada e ti rompa l'osso del collo! + +[COK1_6] +Sono stanco di tutto questo. + +[COK2_7] +Vedi quei segnali, ragazzo? Cerca di colpire le luci! + +[COK2_10] +Di certo sei più bravo a sparare che a parlare. + +[COK2_11] +Grazie. Anche tu hai una bella parlantina. + +[COK2_12] +Lo so, Tommy. + +[COK2_18] +Ti diverti con Kenny Loggins. + +[COK2_19] +Cielo, adoro questo album! + +[COK2_26] +~r~Hai ucciso Lance! + +[COK3_1] +Non sparare, amico! + +[COK3_2] +Che cosa sta succedendo? + +[COK3_3] +Sta prendendo la barca. Farabutto! + +[COK3_4] +Aiuto! Un idiota sta rubando la barca! + +[COK4_W] +Uugghh! Questo è l'ultimo. + +[COK4_X] +Vado ad accendere... + +[COK4_Y] +A quanto sembra abbiamo dei nuovi amici. + +[COK4_2] +Sì. + +[COK4_6] +Sai dove stiamo andando? + +[COK4_7] +Ci siamo persi? + +[COK4_8] +Abbiamo un po' di competizione! + +[COK4_9] +Falli fuori! + +[COK4_9A] +È giunta l'ora di un po' di Lance Vance Dance! + +[COK4_10] +Sono pronti per il macero! E per sfamare i pesci. + +[COK4_11] +Ce l'abbiamo fatta! Le altre imbarcazioni non sono della nostra classe. + +[COK4_17] +Stanno iniziando a pregare! + +[COK4_18] +I miei piedi sono bagnati! STIAMO IMBARCANDO ACQUA! + +[COK4_21] +Ponte in arrivo! + +[COK4_22] +Lanciati, sta per esplodere! + +[COK4_23] +Bel colpo! + +[COK4_29] +~r~Hai ucciso Lance! + +[ASS1_6] +Vai Tommy, andrà tutto bene! + +[ASS1_7] +Beccatevi questo, maledetti assassini! + +[ASS1_8] +Sono bloccato! + +[ASS1_9] +Ti copro io Tommy! + +[ASS1_10] +Ehi, questo sì che è un bel confine alberato. + +[ASS1_11] +Ehi Tommy, posso avere la stanza con la vista sulla baia? + +[ASS1_12] +Che bel soffitto alto che c'è qui... + +[ASS1_3] +Lance! Ho bisogno di copertura! + +[ASS1_5] +Lance! + +[ASS1_15] +~g~Distruggi la dimora e uccidi Diaz! + +[ASS1_17] +~g~Sono disponibili più strade all'interno della villa. + +[TAXWAR] +MISSIONI GUERRA DEI TAXI + +[NOTAXI] +~g~Hai bisogno di un taxi Kaufman per attivare questa missione. + +[TAXW1_5] +~g~Devi avere un taxi Kaufman! + +[TAX2_4] +Forza, Tommy. + +[TAX2_5] +Riempilo di botte! + +[TAX2_6] +Non ha neppure il permesso. + +[TAX2_7] +Maledetti servizi di limousine. + +[TAXW3_1] +~g~Vai a prendere Mercedes. + +[RACE1] +~g~3..2..1.. VIA VIA VIA! + +[RACE2] +~g~3 + +[RACE3] +~g~2 + +[RACE4] +~g~1 + +[RACE5] +~g~VIA! + +[FIRST] +~b~PRIMO + +[SECOND] +~b~SECONDO + +[THIRD] +~b~TERZO + +[FOURTH] +~b~QUARTO + +[RACETM] +~b~TEMPO DI GARA: ~1~:~1~ + +[RACETM2] +~b~TEMPO DI GARA: ~1~:0~1~ + +[RACEFA] +~r~Non sei riuscito a vincere la gara! + +[TEX1_5] +~r~È riuscito a fuggire! + +[SEG3_1] +TEMPO: + +[SEG3_2] +~g~Raggiungi il van che contiene il Raider radiocomandato e le bombe a tempo. + +[SEG3_3] +~g~Devi utilizzare il RAIDER RADIOCOMANDATO per trasportare le 4 bombe nelle 4 aree bersaglio all'interno del cantiere. + +[SERG3_5] +~g~Puoi trasportare solo una bomba alla volta e non puoi raccogliere dell'esplosivo correttamente posizionato. + +[SEG3_7] +~g~Una volta posizionata la PRIMA bomba con successo su di un bersaglio, partirà il conto alla rovescia, dovrai piazzare tutti gli esplosivi entro il tempo limite. + +[SEG3_8] +~g~Le 4 bombe devono essere tutte posizionate su ognuno dei bersagli per passare la missione e demolire l'edificio. + +[SEG3_9] +~g~Bomba piazzata! Ancora 3. + +[SEG3_10] +~g~Bomba piazzata! Ancora 2. + +[SEG3_11] +~g~Bomba piazzata! Ne manca una sola. + +[SEG3_12] +~g~Bomba NON piazzata! Recupera l'esplosivo! + +[SEG3_13] +~g~Piazza la bomba nella zona bersaglio. + +[SEG3_14] +~r~Tempo esaurito: hai fallito nel demolire l'edificio. + +[SEG3_15] +~r~Il Raider radiocomandato è stato distrutto! Come farai adesso a trasportare le bombe? + +[AVERY] +MISSIONI DI AVERY + +[ASM] +MISSIONI DA SICARIO + +[ASM_1] +MISSIONE DA SICARIO 1 + +[ASM1_1] +~g~Mr. Teal, il tuo aiuto nello sradicare gli stranieri è stato inestimabile per il nostro lavoro. Ho un altro lavoro con un contatto più 'diretto'. I dettagli sono stati attaccati sotto al telefono. + +[ASM1_2] +~g~Raggiungi il telefono pubblico presso il Mall in Washington Beach. + +[ASM1_3] +~g~Carl Pearson, fattorino di una pizzeria. Non deve completare le sue consegne. + +[ASM1_4] +~g~Uccidi il fattorino prima che possa completare le sue consegne. + +[ASM_2] +MISSIONE DA SICARIO 2 + +[ASM_3] +MISSIONE DA SICARIO 3 + +[ASM3_A] +Marcus Hammond, Franco Carter, Dick Tanner, Nick Kong e Stuntman Driver fanno tutti parte di un'associazione europea che si sta preparando a commettere una rapina. + +[ASM3_B] +Si trovano tutti nella loro posizione pronti all'azione. Voglio che vengano tutti eliminati prima che inizi il colpo. Ho disposto delle utili armi nei paraggi. + +[ASM3_1] +~g~Recupera le armi che Mr. Black ha lasciato per te. + +[ASM3_2] +~g~Non avvicinarti troppo, se no il bersaglio potrebbe vederti. + +[ASM3_3] +~g~Per un'eliminazione più rapida, colpisci da una locazione vicino alle loro postazioni ed evita di farti vedere. + +[ASM3_4] +~g~Ti ha visto! Fallo fuori il prima possibile! + +[ASM3_5] +~g~Marcus Hammond si trova presso dei cartelli pubblicitari in Washington. + +[ASM3_6] +~g~Franco Carter si trova presso la DBP Security vicino a Ocean Drive. + +[ASM3_7] +~g~Dick Tanner si trova presso la gioielleria in Vice Point. + +[ASM3_8] +~g~Nick Kong si trova presso Washington Beach. + +[ASM3_9] +~g~Stuntman Driver si trova presso Washington Beach. + +[ASM3_10] +~r~Non sei riuscito a eliminarli tutti quanti. + +[ASM_4] +MISSIONE DA SICARIO 4 + +[ASM4_1] +~g~Recupera il fucile che ti abbiamo lasciato tra le foglie presso il terminal dell'aeroporto. + +[ASM4_2] +~g~Non mancare il bersaglio o allarmerai le sue guardie del corpo: ricorda di tenere una certa distanza per evitare di essere visto. + +[ASM4_3] +~g~Osserva la donna sul balcone sopra ai banconi del check-in dentro il terminal. NON UCCIDERLA. + +[ASM4_4] +~g~Elimina l'uomo a cui passa la valigetta ma solo DOPO CHE L'AVRÀ RACCOLTA. Poi recupera la valigetta e portala ad Ammu-Nation in Downtown. + +[ASM4_5] +~g~Recupera la valigetta! + +[ASM4_6] +~g~Porta la valigetta ad Ammu-Nation in Downtown + +[ASM4_7] +~r~Hai ucciso la donna, idiota! + +[ASM4_8] +~r~Il bersaglio ha sentito il colpo d'arma da fuoco! L'accordo è saltato! + +[ASM4_9] +~r~Il bersaglio è salito a bordo del volo! + +[ASM4_10] +~g~Sembra tu non sia l'unico interessato alla valigetta! Portala in fretta ad Ammu-Nation! + +[ASM4_11] +~r~Il bersaglio ti ha visto! L'accordo è saltato! + +[ASM4_13] +~g~Ti ha visto e sta scappando! Uccidilo e recupera la valigetta! + +[ASM4_14] +~g~L'indicatore della distanza nella parte superiore destra dello schermo ti tiene informato sulla distanza dal bersaglio. Se si riempirà, verrai avvistato. + +[ASM_5] +MISSIONE DA SICARIO 5 + +[KICK] +KICKSTART + +[KICK1_3] +~g~Numero di volte che hai appoggiato i piedi: ~1~ + +[KICK1_4] +~g~Penalità: ~1~ secondi + +[BANK] +MISSIONI RAPINA + +[BANK1] +MISSIONE RAPINA 1 + +[BANK2] +MISSIONE RAPINA 2 + +[BJM2_3] +PERCENTUALE DI CENTRI: ~1~% + +[BJM2_15] +PUNTEGGIO: + +[BJM2_18] +PUNTEGGIO DA BATTERE: + +[BJM2_19] +~g~Colpisci il maggior numero di bersagli entro il tempo limite! + +[BJM2_21] +~g~Colpisci il maggior numero di bersagli finché hai ancora colpi. + +[BANK3] +MISSIONE RAPINA 3 + +[BJM3_1] +~g~Trova una macchina da corsa e posizionati sulla griglia di partenza. + +[BNK4_2A] +I ragazzi dell'officina hanno fatto un ottimo lavoro su questa piccola. + +[BNK4_3G] +Ommerda, abbiamo la polizia alle calcagna! + +[BNK4_3H] +...e non siamo ancora neanche arrivati. + +[BNK4_3K] +Dobbiamo seminare prima la polizia... + +[BNK4_3L] +Cristo Tommy, stai cercando di farci fuori? + +[BNK4_3N] +Tutto ciò che amo finisce a pezzi! + +[BNK422A] +Cam, quanto tempo? + +[BK4_23A] +Mi bastano 3 minuti! + +[BNK4_26] +Maledizione! Ecco che arrivano! + +[BNK4_32] +Usa l'esplosivo per aprire le cassette di sicurezza! + +[BNK4_43] +Vi copro io il culo, PARTI! + +[BNK4_51] +A me sembri a posto. + +[KENT] +MISSIONI DI KENT PAUL + +[KENT1] +MISSIONE DI KENT PAUL 1 + +[COUNT] +MISSIONI DI FALSIFICAZIONE + +[COUNT1] +MISSIONE DI FALSIFICAZIONE 1 + +[COUNT2] +MISSIONE DI FALSIFICAZIONE 2 + +[BIKE] +MISSIONI DEI MOTOCICLISTI + +[BIKE1] +MISSIONE DEI MOTOCICLISTI 1 + +[BIKE2] +MISSIONE DEI MOTOCICLISTI 2 + +[BIKE3] +MISSIONE DEI MOTOCICLISTI 3 + +[GOAWAY1] +Ritorna quando avrai completato le missioni per la gang di Haiti. + +[HAIT] +MISSIONI DEGLI HAITIANI + +[HAIT1] +MISSIONE DEGLI HAITIANI 1 + +[HAIT2] +MISSIONE DEGLI HAITIANI 2 + +[HAIT3] +MISSIONE DEGLI HAITIANI 3 + +[HAM3_6] +~g~Utilizza il fucile di precisione che ti ho lasciato per completare la missione. + +[ROCK] +MISSIONI DELLA BAND + +[ROK1_4] +~g~OK, credo tu stessi cercando questo... + +[ROK1_1E] +~g~Ti costerà di più di quanto hai! + +[ROK1_1F] +~g~Ritorna quando avrai i soldi. + +[RBM2_6] +~g~Wow! È un uomo, bloccalo! + +[ROCK3] +MISSIONE DELLA BAND 3 + +[ROK3_6D] +~r~insieme alle vostre GROSSE TESTE DA CAPELLONI! + +[ROK3_40] +Vicino al fottuto minibar? + +[RBM3_5] +~g~Porta i Love Fist allo spettacolo. + +[CUBANM] +MISSIONI DEI CUBANI + +[CUBAN1] +MISSIONE DEI CUBANI 1 + +[CUBAN2] +MISSIONE DEI CUBANI 2 + +[CUB2_10] +~r~Dovresti uccidere gli Haitiani, non i Cubani. + +[CUBAN3] +MISSIONE DEI CUBANI 3 + +[CUBAN4] +MISSIONE DEI CUBANI 4 + +[CUB4_05] +~r~Ti avevo detto di restare in macchina! Adesso non riusciremo mai a entrare! + +[CUB4_25] +OK, andiamo. + +[PROT] +MISSIONI DI PROTEZIONE + +[PRO1_H] +Perché non ti calmi per un secondo, sto finalmente cominciando a capire come funzionano le cose da queste parti. + +[PRO1_02] +~g~Esci dal Mall. + +[PRO3_06] +~g~Fai perdere le tracce. + +[PORN] +MISSIONI PORNOGRAFIA + +[PORN1] +MISSIONE PORNOGRAFIA 1 + +[POR1_03] +~r~Candy è morta! + +[PORN2] +MISSIONE PORNOGRAFIA 2 + +[PORN3] +MISSIONE PORNOGRAFIA 3 + +[POR3_18] +Sei stato visto! + +[PORN4] +MISSIONE PORNOGRAFIA 4 + +[POR4_04] +~g~Gli uffici sono dall'altra parte di questo cancello. + +[PHIL] +MISSIONI DI PHIL + +[PHIL1] +MISSIONE DI PHIL 1 + +[PHI1_06] +Ma che combini, come diavolo guidi? + +[PHI1_07] +Ehi! + +[PHIL2] +MISSIONE DI PHIL 2 + +[PIZ1_A] +MISSIONE RECAPITO PIZZE + +[PIZ1_03] +~g~Ritorna alla pizzeria e prendi le nuove ordinazioni. + +[PIZ1_04] +~g~Ecco le nuove ordinazioni. + +[PIZ1_10] +Premi il ~h~tasto R3~w~ per annullare la missione. + +[CNTBUY1] +Tipografia acquistata: ~1~$ + +[CARBUY] +Concessionario acquistato: ~1~$ + +[PORNBUY] +Studio cinematografico acquistato: ~1~$ + +[ICEBUY] +Fabbrica di gelato acquistata: ~1~$ + +[TAXIBUY] +Compagnia di taxi acquistata: ~1~$ + +[BANKBUY] +Malibu acquistato: ~1~$ + +[BOATBUY] +Cantiere navale acquistato: ~1~$ + +[PRNT_NO] +Non puoi ancora comprare la tipografia, prova più tardi. + +[CAR_NO] +Non puoi ancora comprare il concessionario, prova più tardi. + +[PORN_NO] +Non puoi ancora comprare lo studio cinematografico, prova più tardi. + +[ICE_NO] +Non puoi ancora comprare la fabbrica di gelato, prova più tardi. + +[TAXI_NO] +Non puoi ancora comprare la compagnia di taxi, prova più tardi. + +[BANK_NO] +Non puoi ancora comprare il Malibu, prova più tardi. + +[BOAT_NO] +Non puoi ancora comprare il cantiere navale, prova più tardi. + +[PRNT_R3] +Premi il tasto R3 per acquistare la tipografia per ~1~$ + +[CAR_R3] +Premi il tasto R3 per acquistare il concessionario per ~1~$ + +[PORN_R3] +Premi il tasto R3 per acquistare lo studio cinematografico per ~1~$ + +[ICE_R3] +Premi il tasto R3 per acquistare la fabbrica di gelato per ~1~$ + +[TAXI_R3] +Premi il tasto R3 per acquistare la compagnia di taxi per ~1~$ + +[BANK_R3] +Premi il tasto R3 per acquistare il Malibu per ~1~$ + +[BOAT_R3] +Premi il tasto R3 per acquistare il cantiere navale per ~1~$ + +[COL2_6] +Fermo, brutto maiale imperialista americano! + +[COL2_6B] +Questa è proprietà del governo francese. + +[COL2_6C] +È finita! + +[COL3_A] +Thomas, grazie per essere venuto. + +[COL3_B] +Scusami se giungo subito al punto. + +[COL3_C] +Diaz mi ha chiesto di sovrintendere una piccola transizione di lavoro. + +[COL3_D] +Speriamo vada meglio dell'ultima... + +[COL3_E] +Ed è per questo che ho pensato a te, amico. + +[COL3_F] +Ho lasciato un po' di supporto al parcheggio multipiano. + +[COL3_G] +Raccogli il materiale, poi raggiungi e sorveglia gli uomini di Diaz all'incontro. + +[COL4_2] +Non so signore! + +[COL4_5] +Signorsì, signore! + +[COL4_10] +Andiamo a prendere delle ciambelle. + +[COL4_16] +Signore! Sposto il veicolo, signore! + +[COL4_25] +Autodistruzione del veicolo attivata! + +[COL5_6] +Mercedes, quella ragazza sarà la mia rovina. + +[COL5_8] +Maledetti scarafaggi! + +[COL5_5] +Morite, maiali Francesi! + +[CNT2_1] +Uccidetelo + +[CNT2_2] +Recuperate le matrici! + +[CNT2_3] +Proteggete il corriere. + +[FINKILL] +OK ragazzi, uccidetelo! + +[FIN_6] +Sonny è interessato alla mia cassaforte e ai miei soldi... + +[FIN_10] +Sonny? SONNY! Sto venendo a prenderti! + +[RACES_4] +3 + +[RACES_5] +2 + +[RACES_6] +1 + +[RACES_7] +VIA! + +[RACES_9] +Tempo: ~1~:~1~ + +[RACES] +TEMPO: + +[RACES17] +Nuovo miglior tempo: ~1~:~1~ + +[RACES20] +Nuovo miglior tempo: ~1~:0~1~ + +[RACES21] +Tempo: ~1~:0~1~ + +[RCH1_1] +~g~Usa l'elicottero radiocomandato, il Raider, per PASSARE ATTRAVERSO i punti di controllo. + +[RCH1_2] +~g~I punti di controllo sono sparsi per tutto l'aeroporto. + +[RCH1_3] +~g~Hai ~c~8 MINUTI~g~ per attraversarli tutti e ~c~20~g~! + +[RCH1_5] +Tempo + +[RCRC1_1] +~g~Sfida nella GARA A TAPPE altri 3 Bandit radiocomandati su 2 GIRI. + +[RCRC1_2] +~g~Raggiungi subito la griglia di partenza! + +[RCRC1_3] +~g~Giro finale! + +[RCRC1_4] +~g~3 + +[RCRC1_5] +~g~2 + +[RCRC1_6] +~g~1 + +[RCRC1_7] +~g~VIA! + +[RCRC1_8] +~g~Tempo di gara: ~1~ secondi + +[RCPL1_1] +~g~Sfida nella GARA A TAPPE altri 3 Baron radiocomandati. + +[RCPL1_2] +~g~Devi attraversare la ~o~CORONA CENTRALE~g~ per passare con successo un punto di controllo. + +[RCPL1_3] +~g~Raggiungi subito la griglia di partenza! + +[FEA_2SP] +2 altoparlanti + +[FEA_4SP] +Più di 2 altoparlanti + +[FEA_EAR] +Cuffie + +[FEA_NAH] +NESSUNA PERIFERICA AUDIO + +[FET_APP] +APPLICA + +[FES_SKN] +NOME SKIN + +[FES_DAT] +DATA + +[FES_SET] +Usa skin + +[FET_DEF] +Ripristina predefiniti + +[FESZ_QZ] +Sei sicuro di voler salvare la partita? + +[FES_SCG] +Salvare la partita? + +[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] +Ingrandisci + +[FEC_ZOT] +Riduci + +[FEC_EEX] +Entra o esci + +[FEC_RAD] +Radio + +[FEC_SUB] +Sottomissione + +[FEC_CMR] +Cambia visuale + +[FEC_JMP] +Salto + +[FEC_SPN] +Scatto + +[FEC_HND] +Freno a mano + +[FEC_LOL] +Sguardo a sinistra + +[FEC_LOR] +Sguardo a destra + +[FEC_NTR] +Bersaglio successivo + +[FEC_PTT] +Bersaglio precedente + +[FEC_LBA] +Sguardo indietro + +[FEC_CEN] +Centra visuale + +[FET_CCN] +Classico + +[FET_SCN] +Predefinito + +[FET_CFT] +A PIEDI + +[FET_CCR] +IN MACCHINA + +[FET_CAC] +AZIONE + +[FEC_IBT] +- + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +NON ASSEGNATO + +[FEC_TFL] +Sguardo+Torretta a SX + +[FEC_TFR] +Sguardo+Torretta a DX + +[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 SX+DX + +[LAW_1A] +law_1a + +[LAW_1B] +law_1b + +[LAW_2A] +law_2a + +[LAW_2B] +law_2b + +[FEH_STA] +STATISTICHE + +[FEH_LOA] +CARICA + +[FEH_CON] +COMANDI + +[FEH_AUD] +AUDIO + +[FEH_DIS] +VIDEO + +[FEH_LAN] +LINGUA + +[FEH_SGA] +NUOVA PARTITA + +[FEO_CON] +Impostazioni controller + +[FEO_AUD] +Impostazioni audio + +[FEO_DIS] +Impostazioni video + +[FEO_LAN] +Impostazioni lingua + +[FEO_PLA] +Impostazioni giocatore + +[FEA_OUT] +Uscita + +[FEA_ST] +Stereo + +[FEA_DTS] +DTS + +[FEA_RSS] +Stazione radio + +[FEA_NON] +NO RADIO + +[FEA_FM0] +WILDSTYLE + +[FEA_FM1] +FLASH FM + +[FEA_FM2] +KCHAT + +[FEA_FM3] +FEVER 105 + +[FEA_FM4] +VROCK + +[FEA_FM5] +VCPR + +[FEA_FM7] +EMOTION 98.3 + +[FEA_FM8] +WAVE 103 + +[FED_BRI] +Luminosità + +[FED_TRA] +Tracce : + +[FED_SUB] +Sottotitoli + +[FED_WIS] +Panoramico + +[FED_POS] +Posizione schermo + +[FEP_RES] +Riprendi + +[FEP_STG] +Avvia partita + +[FEP_STA] +Statistiche + +[FEP_BRI] +Briefing + +[FEP_OPT] +Opzioni + +[FEP_QUI] +Esci + +[FES_LOA] +Carica partita + +[FES_DEL] +Elimina partita + +[FEC_CSU] +Impostazioni controller + +[FEC_RED] +Ridefinisci comandi + +[FEC_MOU] +Impostazioni mouse + +[DISTGOL] +Distanza percorsa su cart da golf (miglia) + +[DISTGOM] +Distanza percorsa su cart da golf (metri) + +[ST_FAVR] +Stazione radio preferita + +[ST_WSTR] +Stazione radio meno amata + +[ST_FAVV] +Veicolo preferito + +[ST_STAR] +Totale stelle di sospetto + +[ST_HEAD] +Colpi alla testa + +[ST_GANG] +Gang meno amata + +[ST_STGN] +Totale stelle di sospetto evase + +[TYREPOP] +Pneumatici esplosi con proiettili + +[TYRESLA] +Pneumatici tagliati con una lama + +[ST_BRK] +Numero di uccisioni nel Bloodring + +[ST_LTBR] +Permanenza massima nel Bloodring (sec) + +[ST_GNG1] +Cubani + +[ST_GNG2] +Haitiani + +[ST_GNG3] +Teppistelli + +[ST_GNG4] +Gang di Diaz + +[ST_GNG5] +Sorveglianza + +[ST_GNG6] +Gang di motociclisti + +[ST_GNG7] +Gang di Vercetti + +[ST_GNG8] +Giocatori di golf + +[FEA_FM6] +ESPANTOSO + +[ST_ASSI] +Contratti da sicario completati + +[DISTBIK] +Distanza percorsa in moto (miglia) + +[DISTBIM] +Distanza percorsa in moto (metri) + +[HOTEL] +Hotel Ocean View + +[ICC1_1] +~g~Utilizza il camioncino dei gelati per distribuire droga in Vice City. + +[ICC1_2] +~g~Parcheggia il camioncino e premi ~h~~k~~VEHICLE_HORN~~w~ per attivare il campanello e attrarre i clienti. + +[ICC1_3] +~g~Riceverai soldi per ogni transizione, ma più ne esegui, maggiori saranno le probabilità di attirare l'attenzione della polizia. + +[KICK1_9] +PUNTI DI CONTROLLO RIMANENTI: + +[FIN_B6] +Non hai abbastanza soldi per avviare questa missione. + +[TEX3_3] +~g~Per raccogliere una bomba, avvicinati con l'elicottero: la bomba verrà automaticamente raccolta. + +[TEX3_9] +~g~Raccogli una bomba avvicinandole l'elicottero. + +[HELP22] +Raggiungi la casa verde sul radar. + +[FES_FMS] +Formattazione completata. Seleziona OK per continuare. + +[FES_SSC] +Salvataggio completato. Seleziona OK per continuare. + +[FES_DSC] +Eliminazione completata. Seleziona OK per continuare. + +[FESZ_QC] +Vuoi sovrascrivere il salvataggio corrotto? + +[FES_CHE] +Attenzione! Uno o più trucchi sono stati attivati: ciò influenzerà il salvataggio. Si consiglia di non salvare questa partita. + +[FET_SG] +SALVA PARTITA + +[FEH_BRI] +BRIEFING + +[FEH_MAP] +MAPPA + +[FEM_OK] +OK + +[FEC_CRO] +Abbassarsi + +[FEC_CR3] +Abbassarsi (tasto L3) + +[FEC_SMT] +Sottomissione + +[FEC_SM3] +Sottomissione (tasto R3) + +[FEC_RSC] +Stazioni radio + +[ST_PR01] +Apprendista + +[ST_PR02] +Aviere + +[ST_PR03] +Ufficiale + +[ST_PR04] +Caporale + +[ST_PR05] +Tenente + +[ST_PR06] +Sergente + +[ST_PR07] +Capitano + +[ST_PR08] +Biggs + +[ST_PR09] +Wedge + +[ST_PR10] +Barone rosso + +[ST_PR11] +Goose + +[ST_PR12] +Viper + +[ST_PR13] +Jester + +[ST_PR14] +Chappy + +[ST_PR15] +Iceman + +[ST_PR16] +Maverick + +[ST_PR17] +Noops + +[ST_PR18] +Maresciallo + +[ST_PR19] +Asso + +[FET_LG] +CARICA PARTITA + +[CAR_EXP] +Veicoli distrutti + +[BOA_EXP] +Imbarcazioni distrutte + +[HEL_DST] +Velivoli distrutti + +[STFT_01] +Tempo migliore in 'Due ruote d'acciaio' + +[STFT_02] +Tempo migliore in 'L'autista' + +[STFT_03] +Tempo migliore nella prova del fango + +[STFT_04] +Tempo migliore con gli aerei radiocomandati + +[STFT_05] +Tempo migliore con le macchine radiocomandate + +[STFT_06] +Tempo migliore di raccolta con l'elicottero + +[STFT_07] +Tempo migliore in 'Terminal Velocity' + +[STFT_08] +Tempo migliore in 'Ocean Drive' + +[STFT_09] +Tempo migliore in 'Border Run' + +[STFT_10] +Tempo migliore in 'Capital Cruise' + +[STFT_11] +Tempo migliore in 'Tour!' + +[STFT_12] +Tempo migliore in 'V.C. Endurance' + +[STHC_01] +Punteggio migliore al poligono + +[STHC_02] +Miglior percentuale di colpi al poligono + +[STHC_03] +Numero di traffici di droga + +[HELP24] +Adesso puoi accettare le missioni del Colonnello. + +[HELP25] +Adesso puoi accettare le missioni di Avery Carrington. + +[HELP29] +Puoi far visita al negozio di abbigliamento quando non sei in missione. + +[HELP30] +Quando compri nuovi abiti, il tuo livello di sospetto scenderà a zero. + +[BJM2_22] +~r~Sei uscito dal poligono di tiro! + +[BJM2_23] +~g~Se esci dal poligono di tiro durante la competizione, fallirai la missione. + +[ASM4_24] +Distanza: + +[RBM1_6] +~g~Porta Mercedes e la 'Love Juice' al gruppo nello studio di registrazione. + +[RBM1_3] +NON NECESSARIO + +[HAM1_5] +NON NECESSARIO + +[RBM1_11] +NON NECESSARIO + +[HELP31] +Per eseguire un assalto in macchina, guarda a destra o a sinistra premendo il tasto L2 o il tasto R2. + +[HELP34] +Hai bisogno di una mitragliatrice per eseguire un assalto in macchina. + +[STRIP_1] +~r~Soldi insufficienti, brutto idiota. + +[EXIT_1] +Premi ~k~~PED_SPRINT~ per uscire. + +[ASM1_A] +Mr. Teal, il tuo aiuto nello sradicare gli stranieri è stato inestimabile per il nostro lavoro. Ho un altro compito con un contatto più 'diretto'. + +[ASM1_B] +I dettagli per il prossimo colpo sono stati attaccati sotto al telefono. + +[ASM1_C] +Ho un altro compito con un contatto più 'diretto'. + +[SCARF] +Appartamento 3c + +[LAW4_10] +La ricca amministrazione ci ha rotto! + +[GEN1_04] +~g~Attraversa la porta per accedere alla piattaforma sul tetto di Gonzalez. + +[GEN1_17] +~g~Gonzalez sta fuggendo! Seguilo attraverso le porte e finiscilo! + +[RCH1_9] +~b~TEMPO TOTALE: ~1~:~1~ + +[RCH1_10] +~b~TEMPO TOTALE: ~1~:0~1~ + +[WHEEL01] +BONUS DOPPIO SU DUE RUOTE: ~1~$ Distanza: ~1~.~1~ m Tempo: ~1~ secondi + +[WHEEL02] +BONUS DOPPIO SU DUE RUOTE: ~1~$ Distanza: ~1~ piedi Tempo: ~1~ secondi + +[WHEEL03] +BONUS SU DUE RUOTE: ~1~$ Tempo: ~1~ secondi + +[WHEEL04] +BONUS SU DUE RUOTE: ~1~$ Distanza: ~1~.~1~ m + +[WHEEL05] +BONUS SU DUE RUOTE: ~1~$ Distanza: ~1~ piedi + +[WHEEL06] +BONUS IMPENNATA: ~1~$ Distanza: ~1~.~1~ m Tempo: ~1~ secondi + +[WHEEL07] +BONUS IMPENNATA: ~1~$ Distanza: ~1~ piedi Tempo: ~1~ secondi + +[WHEEL08] +BONUS IMPENNATA: ~1~$ Tempo: ~1~ secondi + +[WHEEL09] +BONUS IMPENNATA: ~1~$ Distanza: ~1~.~1~ m + +[WHEEL10] +BONUS IMPENNATA: ~1~$ Distanza: ~1~ piedi + +[WHEEL11] +BONUS FRENATA IN PUNTA: ~1~$ Distanza: ~1~.~1~ m Tempo: ~1~ secondi + +[WHEEL12] +BONUS FRENATA IN PUNTA: ~1~$ Distanza: ~1~ piedi Tempo: ~1~ secondi + +[WHEEL13] +BONUS FRENATA IN PUNTA: ~1~$ Tempo: ~1~ secondi + +[WHEEL14] +BONUS FRENATA IN PUNTA: ~1~$ Distanza: ~1~.~1~ m + +[WHEEL15] +BONUS FRENATA IN PUNTA: ~1~$ Distanza: ~1~ piedi + +[ROK3_72] +Love Fist! + +[POR1_19] +Ehi! + +[DESPERA] +Desperado + +[MOB_99A] +Raggiungi il telefono pubblico vicino al Mall in Washington. + +[MOB_98A] +Raggiungi il telefono pubblico in Vice Point. + +[MOB_96A] +Raggiungi il telefono pubblico al terminal dell'aeroporto. + +[MOB_95A] +Raggiungi il telefono pubblico in Little Havana. + +[BNK1_1] +La posso aiutare, signore? + +[BNK1_2] +Un impostore! + +[BNK1_3] +È impazzito! + +[BNK1_4] +Ma chi diavolo sei? + +[BNK1_5] +Dov'è il tuo distintivo? + +[BNK1_6] +Eccoli là! Sparate per uccidere! + +[MOB_24A] +Salve: Mr Vercetti? + +[MOB_24B] +Sì. + +[MOB_24C] +Sono Cortez. Eri invitato al mio party. + +[MOB_24D] +Sì, ricordo. + +[MOB_24E] +Mr. Vercetti, è stato davvero uno sfortunato incidente quello avvenuto al tuo accordo. + +[MOB_24F] +Lo so. + +[MOB_24G] +Sappi che io e la mia gente stiamo facendo il massimo per scoprire cosa è successo. + +[MOB_24H] +Se vuoi parlarne in prima persona, mi troverai sulla mia barca. Buona giornata, senor. + +[BNK2_2] +MIRARE 3-2-1 FUOCO! + +[BNK2_3] +AREA RIPULITA! + +[BNK2_6] +Questo tipo è pazzo! + +[ANGEL] +Angel + +[CUBJET] +Jetmax Cubano + +[SANDKIN] +Sandking + +[POLMAV] +Maverick della polizia + +[BOXVILL] +Boxville + +[BENSON] +Benson + +[HOTRINA] +Hotring Racer + +[HOTRINB] +Hotring Racer + +[BLOODRA] +Bloodring Banger + +[BLOODRB] +Bloodring Banger + +[MAFIACR] +Cruiser della Mafia + +[COP_M2] +'VICE SQUAD' + +[COP_M3] +'BROWN THUNDER' + +[BJM2_20] +~g~Quando finisci il ~w~tempo~g~ o i ~w~colpi~g~, il turno è finito! + +[BNK3_2] +Non intendo guidare per te, per niente, e lo dirò ai miei compagni di terapia. + +[FEM_SL1] +Salvataggio 1 non trovato + +[FEM_SL2] +Salvataggio 2 non trovato + +[FEM_SL3] +Salvataggio 3 non trovato + +[FEM_SL4] +Salvataggio 4 non trovato + +[FEM_SL5] +Salvataggio 5 non trovato + +[FEM_SL6] +Salvataggio 6 non trovato + +[FEM_SL7] +Salvataggio 7 non trovato + +[FEM_SL8] +Salvataggio 8 non trovato + +[FEA_CHA] +Modifica uscita audio in STEREO. Attendere... + +[FEA_CHD] +Attenzione! Modifica uscita audio da STEREO a DTS. Attendere... + +[FEI_SEL] +Seleziona + +[FEI_BAC] +Indietro + +[FEI_RES] +Riprendi + +[FEI_NAV] +Naviga + +[FEI_BTX] +Tasto / - + +[FEI_BTT] +Tasto " - + +[FEI_STA] +Tasto START - + +[FEI_BTD] +; = > < - + +[FEI_STO] +Stop + +[MOB_68A] +Ehi Tommy, ho una sorpresa per te. + +[MOB_68B] +Sono allo studio cinematografico con dei grandi artisti. + +[MOB_68C] +Perché non vieni a farci visita? + +[MOB_68D] +Lo sai che è una buona idea, vero? Ci vediamo. + +[OUTFT1] +Da strada + +[OUTFT2] +Eleganti + +[OUTFT3] +Tuta + +[OUTFT4] +Sportivo + +[OUTFT5] +Havana + +[OUTFT6] +Da poliziotto + +[OUTFT7] +Da rapina + +[OUTFT8] +Casual + +[OUTFT9] +Mr. Vercetti + +[OUTFT10] +Da corsa + +[OUTFT13] +MC Tommy + +[HOTR_07] +Nuovo miglior tempo: ~1~:0~1~ + +[HOTR_08] +Tempo: ~1~:~1~ + +[GEN3_45] +Arriveranno fra pochi minuti: troviamo un buon punto dove appostarci... + +[CAR_AS1] +BENI CONCESSIONARIA ACQUISITI + +[CAR_AS2] +~g~La concessionaria d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +[BUYSAVE] +~g~Se non sei in missione, potrai ora salvare la partita da qui gratuitamente. + +[BUYGARG] +~g~Potrai anche depositare i mezzi in questo garage. + +[STRPBUY] +Club Pole Position acquistato: ~1~$ + +[STRP_R3] +Premi il tasto R3 per acquistare il club Pole Position per ~1~$ + +[NBMN_R3] +Premi il tasto R3 per acquistare Elswanko Casa per ~1~$ + +[GA_4] +Le bombe per le macchine costano 1000$ + +[GA_5] +La tua macchina ha già una bomba installata. + +[GA_6] { reVC update } +Parcheggiala, attivala premendo il ~h~~k~~VEHICLE_FIREWEAPON~~w~ e DATTELA A GAMBE! + +[GA_7] { reVC update } +Arma la bomba con il ~h~~k~~VEHICLE_FIREWEAPON~~w~: esploderà non appena qualcuno cercherà di avviarla. + +[GA_6B] { reVC update } +Parcheggiala, attivala premendo il ~h~~k~~VEHICLE_FIREWEAPON~~w~ e DATTELA A GAMBE! + +[GA_7B] { reVC update } +Arma la bomba con il ~h~~k~~VEHICLE_FIREWEAPON~~w~: esploderà non appena qualcuno cercherà di avviarla. + +[MOB_70A] +Tommy, sono io, il Colonnello Cortez. Ascolta senor, credo tu sia una persona capace di risolvere i problemi. + +[MOB_70B] +Mi troverai sulla mia barca. + +[PICK1] +Giubbotto antiproiettile consegnato all'hotel Ocean View! + +[PICK2] +.357 consegnato al nascondiglio! + +[PICK3] +Motosega consegnata al nascondiglio! + +[PICK4] +Lanciafiamme consegnato al nascondiglio! + +[PICK5] +.308 Fucile di precisione consegnato al nascondiglio! + +[PICK6] +Mitragliatore consegnato al nascondiglio! + +[PICK7] +Lanciamissili consegnato al nascondiglio! + +[PICK8] +Sea Sparrow adesso disponibile alla villa! + +[PICK9] +Carro armato adesso disponibile alle caserme! + +[PICK10] +Hunter adesso disponibile alle caserme! + +[HELP41] +o puoi speronarli con il tuo mezzo + +[ICC1_6] +~g~Utilizza il Mr. Whopee per distribuire i prodotti Cherry Poppers per Vice City. + +[ICC1_12] +PROPRIETÀ ACQUISTATA! + +[CLOTH1] +Abiti eleganti consegnati a Rafaels in Ocean Beach. + +[CLOTH2] +Abiti da strada consegnati al nascondiglio. + +[CLOTH3] +Tuta consegnata a Tooled Up nel Mall North Point. + +[CLOTH4] +Abiti sportivi consegnati al club di Golf in Leafs Links. + +[CLOTH5] +Abiti havana consegnati a Little Havana Streetwear in Little Havana. + +[CLOTH6] +Abiti da poliziotto consegnati alla stazione di polizia in Washington Beach. + +[CLOTH7] +Abiti casual consegnati a Gash nel Mall North Point. + +[CLOTH8] +Abiti Mr. Vercetti consegnati a Collar & Cuffs in Ocean Beach. + +[CLOTH9] +Abiti da corsa consegnati a Jocksport in Downtown. + +[CLOTH10] +Abiti da rapina consegnati al club Malibu in Vice Point. + +[RBM1_9] +~g~Recupera della Love Juice dallo spacciatore per i Love Fist! + +[MOB_62A] +Tommy, sono Ricardo Diaz. Ti volevo ringraziare per l'aiuto ai miei uomini. + +[MOB_62B] +Ho chiesto a quel coglione di Cortez e mi ha detto che il vero affare sei tu. Amico, vienimi a trovare. + +[MOB_62C] +Ho bisogno di un tipo come te. Sono circondato da coglioni, + +[MOB_62D] +coglioni dappertutto, davvero. Ti farò diventare ricco. + +[GOAWAY2] +~g~Ritorna quando avrai completato le missioni dei motociclisti. + +[COL2_9] +Idiota di un americano! Ti sei fatto seguire! + +[LOADCOL] +Caricamento... + +[STFT_17] +Tempo migliore in 'Parco divertimenti PCJ' + +[STFT_18] +Tempo migliore in 'Prova del fango' + +[STFT_19] +Tempo migliore in 'Tracciato di prova' + +[NEW_REC] +Nuovo record!!! ~1~ minuti e ~1~ secondi. + +[BMX_HOW] +~g~Fai due giri sul tracciato sterrato, ~y~passando attraverso~g~ i ~y~PUNTI DI CONTROLLO~g~! + +[BMXREW1] +~g~Ogni volta che batti il tuo record precedente per i due giri, + +[BMXREW2] +~g~otterrai una ~y~RICOMPENSA~g~ più cospicua! + +[BMXRAIN] +~g~È come se piovesse... + +[ITBEG] +All'inizio... + +[NBMNBUY] +El Swanko Casa acquistata: ~1~$ + +[LNKVBUY] +Appartamento Links View acquistato: ~1~$ + +[HYCOBUY] +Condominio Hyman acquistato: ~1~$ + +[BUYGARS] +~g~Puoi parcheggiare altri veicoli in questi garage. + +[OCHEBUY] +Appartamento Ocean Heights acquistato: ~1~$ + +[WASHBUY] +1102 Washington Street acquistata: ~1~$ + +[VCPTBUY] +3321 Vice Point acquistata: ~1~$ + +[SKUMBUY] +Skumole Shack acquistato: ~1~$ + +[HELP6_C] +Premi il ~h~~k~~VEHICLE_HANDBRAKE~~w~ per tirare il freno a mano. + +[HELP2_A] +Premi il ~h~~k~~PED_SPRINT~~w~ mentre corri per effettuare uno ~h~scatto~w~. + +[HELP4_A] +Premi il ~h~~k~~VEHICLE_ACCELERATE~~w~ per ~h~accelerare~w~. + +[HELP5_A] +Premi il ~h~~k~~VEHICLE_BRAKE~~w~ per frenare o, se il veicolo è fermo, per inserire la retromarcia. + +[HELP8_A] +Premi il ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ per zoomare col fucile e il ~x~tasto /~w~ per allargare il campo. + +[PBOAT_1] { reVC update } +Premi il ~h~~k~~VEHICLE_FIREWEAPON~~w~ per sparare con i cannoni della barca. + +[SEG3_4] { reVC update } +~g~Puoi raccogliere una bomba avvicinando il Raider telecomandato. Per posizionare la bomba, premi il ~h~~k~~VEHICLE_FIREWEAPON~. + +[RCR1_3] { reVC update } +~g~Se desideri interrompere la missione, premi il ~h~~k~~VEHICLE_FIREWEAPON~~g~ per far esplodere la macchina. + +[HELP32] { reVC update } +Poi spara con il ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[HELP33] { reVC update } +Poi spara con il ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[TTUTOR] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Taxi. + +[TTUTOR2] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Taxi. + +[FTUTOR] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Camion dei pompieri. + +[FTUTOR2] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Camion dei pompieri. + +[CTUTOR] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Vigilante. + +[CTUTOR2] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Vigilante. + +[HELP8_B] +Premi il ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ per ~h~zoomare~w~ col fucile e il ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ per ~h~allargare~w~ il campo. + +[ATUTOR3] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ per attivare o disattivare le missioni Infermiere. + +[GUN_H1] +~w~Premi il ~h~~k~~PED_SPRINT~~w~ per comprare. ~w~Premi il ~h~~k~~VEHICLE_ENTER_EXIT~~w~ per uscire. + +[PU_CF3] { reVC update } +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per sostituire l'arma attuale con quella presente in questo slot. + +[PU_CF4] { reVC update } +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per sostituire l'arma attuale con quella presente in questo slot. + +[HELP9_B] +Premi il ~h~~k~~PED_FIREWEAPON~~w~ per sparare con il fucile di precisione. + +[HELP37] +Se non vuoi entrare in un veicolo mentre stai assalendo un guidatore, premi il ~h~~k~~PED_SPRINT~~w~. + +[HELP6_A] +Premi il ~h~~k~~VEHICLE_HANDBRAKE~~w~ per tirare il freno a mano. + +[HELP6_D] +Premi il ~h~~k~~VEHICLE_HANDBRAKE~~w~ per tirare il freno a mano. + +[HELP26] +Premi il ~h~~k~~VEHICLE_ENTER_EXIT~~w~ per entrare o uscire da un veicolo. + +[HELP27] +Premi il ~h~~k~~VEHICLE_TURRETUP~~w~ o ~h~~k~~VEHICLE_TURRETDOWN~~w~ per spostare il peso sulla moto. + +[HELP28] +Premi il ~h~~k~~VEHICLE_TURRETUP~~w~ o ~h~~k~~VEHICLE_TURRETDOWN~~w~ per spostare il peso sulla moto. + +[HELP35] +Premi il ~h~~k~~GO_LEFT~~w~ o la ~h~~k~~GO_RIGHT~~w~ per sterzare. + +[HELP36] +Premi il ~h~~k~~GO_LEFT~~w~ o la ~h~~k~~GO_RIGHT~~w~ per sterzare. + +[HELP42] +Segui il ~q~segnalino rosa~w~ per trovare l'hotel. + +[HELP19] +Cammina sul ~q~segnalino rosa~w~ per continuare. + +[HELP1] +Fermati al centro del ~q~segnalino rosa~w~. + +[HELP12] +Cammina nel centro del ~q~segnalino rosa~w~ per attivare una missione. + +[SEG3_6] +~g~Per colpire con successo un bersaglio, devi sganciare la bomba nell'area con un ~q~segnalino rosa~g~. Puoi posizionare le bombe in un ordine qualsiasi. + +[S_PROMP] +Quando non sei in missione, puoi salvare la partita raccogliendo il bonus ~h~audiocassetta~w~. + +[HELP16] +Passa attraverso la porta principale dell'hotel ~h~Ocean View~w~ per entrare nell'edificio. + +[HELP43] +~g~Raggiungi l'hotel ~h~Ocean View~g~ in Ocean Drive. + +[HELI_F1] +~r~Gara a tappe con l'elicottero annullata! + +[AMMUHLP] +Se hai bisogno di armi, fai un salto ad ~h~Ammu-Nation~w~. Segui il ~h~segnale a forma di pistola~w~ sul radar. + +[HELI_1] +Tappa elicottero Downtown + +[HELI_2] +Tappa elicottero Ocean Beach + +[HELI_3] +Tappa elicottero Vice Point + +[HELI_4] +Tappa elicottero Little Haiti + +[FST_MFR] +Stazione radio preferita + +[FST_LFR] +Stazione radio meno amata + +[FEI_HOL] +Trattieni + +[FEI_ZOO] +Zoom + +[FEI_BTR] +> < - + +[FEI_NA] +N\D + +[MESA] +Mesa Grande + +[STRP_NO] +Non puoi ancora comprare il club a luci rosse, prova più tardi. + +[CHSE] +INSEGUI + +[NBMN_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare El Swanko Casa per ~1~$ + +[NBMN_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare El Swanko Casa per ~1~$ + +[NBMN_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare El Swanko Casa per ~1~$ + +[LNKV_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare l'appartamento Links View per ~1~$ + +[LNKV_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare l'appartamento Links View per ~1~$ + +[LNKV_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare l'appartamento Links View per ~1~$ + +[HYCO_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il condominio Hyman per ~1~$ + +[HYCO_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il condominio Hyman per ~1~$ + +[HYCO_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il condominio Hyman per ~1~$ + +[OCHE_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare l'appartamento Ocean Heights per ~1~$ + +[OCHE_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare l'appartamento Ocean Heights per ~1~$ + +[OCHE_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare l'appartamento Ocean Heights per ~1~$ + +[WASH_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare 1102 Washington Street per ~1~$ + +[WASH_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare 1102 Washington Street per ~1~$ + +[WASH_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare 1102 Washington Street per ~1~$ + +[VCPT_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare 3321 Vice Point per ~1~$ + +[VCPT_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare 3321 Vice Point per ~1~$ + +[VCPT_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare 3321 Vice Point per ~1~$ + +[SKUM_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare lo Skumole Shack per ~1~$ + +[SKUM_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare lo Skumole Shack per ~1~$ + +[SKUM_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare lo Skumole Shack per ~1~$ + +[PRNT_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la tipografia per ~1~$ + +[PRNT_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la tipografia per ~1~$ + +[PRNT_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la tipografia per ~1~$ + +[CAR_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il concessionario per ~1~$ + +[CAR_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il concessionario per ~1~$ + +[CAR_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il concessionario per ~1~$ + +[PORN_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare lo studio cinematografico per ~1~$ + +[PORN_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare lo studio cinematografico per ~1~$ + +[PORN_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare lo studio cinematografico per ~1~$ + +[ICE_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la fabbrica di gelato per ~1~$ + +[ICE_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la fabbrica di gelato per ~1~$ + +[ICE_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la fabbrica di gelato per ~1~$ + +[TAXI_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la compagnia di taxi per ~1~$ + +[TAXI_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la compagnia di taxi per ~1~$ + +[TAXI_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la compagnia di taxi per ~1~$ + +[BANK_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il Malibu per ~1~$ + +[BANK_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il Malibu per ~1~$ + +[BANK_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il Malibu per ~1~$ + +[BOAT_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il cantiere navale per ~1~$ + +[BOAT_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il cantiere navale per ~1~$ + +[BOAT_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il cantiere navale per ~1~$ + +[STRP_L] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il club Pole Position per ~1~$ + +[STRP_T] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il club Pole Position per ~1~$ + +[STRP_C] +Premi il ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare il club Pole Position per ~1~$ + +[STOCK] +~r~scorte esaurite + +[HELP14] +Per trovare l'ufficio dell'avvocato, segui la ~h~L~w~ sul radar. + +[RAMPAGE] +VIOLENZA! + +[RAMP_F] +VIOLENZA FALLITA! + +[RAMP_P] +VIOLENZA COMPIUTA! + +[RAMP_A] +TUTTE LE VIOLENZE COMPIUTE! + +[PAGE_01] +Uccidi ~1~ membri della gang in 2 minuti! + +[PAGE_02] +Distruggi ~1~ veicoli in 2 minuti! + +[PAGE_03] +Uccidi sparando in corsa ~1~ membri della gang in 2 minuti! + +[PAGE_04] +Travolgi ~1~ membri della gang in 2 minuti! + +[PAGE_05] +Fai saltare la testa a ~1~ membri della gang in 2 minuti! + +[SENTXS] +Sentinel XS + +[MAP_LEG] +Legenda + +[VCNMAV] +VCN Maverick + +[LG_01] +Posizione giocatore + +[LG_02] +Avery Carrington + +[LG_03] +Contatto motociclisti + +[LG_04] +Colonnello Cortez + +[LG_05] +Ricardo Diaz + +[LG_06] +Kent Paul + +[LG_07] +Avvocato + +[LG_08] +Phil Cassidy + +[LG_09] +Cantiere navale + +[LG_10] +Club Malibu + +[LG_11] +Cubani + +[LG_12] +Studio cinematografico + +[LG_13] +AmmuNation + +[LG_14] +Haitiani + +[LG_15] +Ferramenta + +[LG_16] +Nascondiglio + +[LG_17] +Fabbrica di gelato + +[LG_18] +Taxi Kaufman + +[LG_19] +Love Fist + +[LG_20] +Tipografia + +[LG_21] +Proprietà + +[LG_22] +Pay 'n' Spray + +[LG_23] +Negozio di vestiario + +[LG_24] +Dimora di Tommy + +[LG_25] +Telefono + +[LG_26] +Stazione radio Wildstyle + +[LG_27] +Stazione radio Flash FM + +[LG_28] +Stazione radio KChat + +[LG_29] +Stazione radio Fever 105 + +[LG_30] +Stazione radio VROck + +[LG_31] +Stazione radio VCPR + +[LG_32] +Stazione radio Espantoso + +[LG_33] +Stazione radio Emotion 98.3 + +[LG_34] +Stazione radio Wave 103 + +[LG_36] +Sun Yard + +[LG_37] +Club a luci rosse + +[MAP_YAH] +TU SEI QUI + +[TAXSHRT] +~g~Puoi utilizzare questo taxi Kaufman per arrivare a destinazione invece di guidare. Ti costerà 9$. + +[FE_MLG] +LEGENDA MAPPA + +[FED_RDR] +RADAR + +[FED_HUD] +INTERFACCIA + +[FED_RDL] +GRANDE + +[FED_RDB] +SOLO SEGNALI + +[FED_HUF] +DISSOLVENZA + +[FEST_HV] +Massimo livello missione Vigilante + +[BRIBE1] +Hai appena raccolto una bustarella della polizia: il tuo livello di sospetto è sceso di una stella. + +[CLOHELP] +Abiti puliti! + +[SUNSHIN] +Sunshine Autos + +[CHERRYP] +Gelateria Cherry Popper + +[KAUFCAB] +Taxi Kaufman + +[BOATYAR] +Cantiere navale + +[WANT_L] +Il tuo livello di sospetto è sospeso: se commetterai un crimine mentre le stelle lampeggiano, il tuo livello di sospetto verrà ripristinato. + +[HOTRNG] +HOTRING + +[BLODRNG] +BLOODRING + +[DIRTRNG] +DIRTRING + +[FEC_ABR] +Accelera, frena o inverti marcia + +[FEI_BTU] +; = - + +[FEI_SCR] +Scorri + +[LG_35] +Destinazione + +[BOAT_AS] +~g~Il cantiere navale d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +[BOAT_A2] +CANTIERE NAVALE COMPLETATO + +[BOAT_N] +Tappa Charlie + +[BOAT_P] +~g~Raccogli i pacchi prima dello scadere del tempo. + +[FEI_R1B] +Tasto R1 \ R2 - + +[HELP9_A] +Premi il tasto ~h~~k~~PED_FIREWEAPON~~w~ per sparare con il fucile di precisione. + +[HELP21] +Premi il tasto ~h~~k~~VEHICLE_ENTER_EXIT~~w~ per entrare o uscire da un veicolo. + +[CREAM] +Distribuzione + +[UMBERTO] +Cafe Robina + +[PU_CF1] +Premi il tasto ~h~~k~~PED_ANSWER_PHONE~~w~ per raccogliere quest'arma e sostituirla con qualsiasi altra dello stesso tipo in tuo possesso. + +[FED_RDM] +MAPPA E INDICATORI + +[FEC_ILU] +Sguardo invertito in prima persona : + +[NITRO] +Tutti i taxi adesso hanno il super salto! Premi il pulsante del clacson. + +[RATNG53] +Inaffidabile + +[RATNG54] +Imbarazzante + +[RATNG55] +Hacker + +[RATNG56] +Baro + +[RATNG57] +Bugiardo assoluto + +[STHC_04] +Miglior punteggio con la palla sulla spiaggia + +[STHC_05] +Miglior risultato Hotring + +[STFT_13] +Tempo migliore corsa in elicottero a Downtown + +[STFT_14] +Tempo migliore corsa in elicottero a Ocean Beach + +[STFT_15] +Tempo migliore corsa in elicottero a Vice Point + +[STFT_16] +Tempo migliore corsa in elicottero a Little Haiti + +[STFT_21] +Tempo migliore nell'Hotring + +[STFT_22] +Giro migliore nell'Hotring + +[STFT_20] +Tempo migliore in 'Cone Crazy' + +[HELP44] +Fermati nel ~q~ segnalino rosa. + +[HELP45] +Premi il ~h~~k~~PED_DUCK~~w~ per abbassarti. In questo modo migliorerai la precisione con le armi da fuoco. + +[RCR1_5] +Corsa Bandit radiocomandati + +[RCPL1_7] +Corsa Baron radiocomandati + +[RCH1_11] +Raccolta Raider radiocomandato + +[FEA_CTD] +Attenzione! Questa caratteristica richiede delle periferiche compatibili DTS. Vuoi procedere? + +[FEM_STE] +USA STEREO + +[FEM_UDY] +USA DTS + +[GREET] +Saluti da... + +[LANCE_1] +Ehi bello, guida con più prudenza! + +[LANCE_2] +Ehi, guarda cosa hai combinato! + +[LANCE_3] +Ehi, ma dove stai andando adesso? + +[LANCE_4] +Cosa diavolo stai facendo? + +[LAW4_15] +Più soldi! + +[MERC_5] +Bella machina, Mr. Vercetti. + +[MERC_26] +PIÙ VELOCE, PIÙ VELOCE, PIÙ VELOCE! + +[MERC_27] +Attento Tommy, mi hanno rifatto il naso il mese scorso. + +[MERC_28] +Tommy, guida con attenzione. + +[MERC_29] +Tommy, vai più piano! + +[MERC_30] +Tommy per favore, fai fuori qualcun altro! + +[MERC_31] +Tommy, baby, non farmi fuori! + +[MERC_32] +Tommy, sono felice che hai rubato questa macchina! + +[MERC_40] +Mi sono davvero divertita. + +[MERC_43] +Adios, angioletto. + +[MERC_44] +Continua a lavorarci, capito? + +[MERC_45] +Ciao, bellezza. + +[COL5_17] +Oddio, hanno un elicottero! + +[COL5_18] +Abbatti l'elicottero! + +[COL5_19] +Tommy, abbatti quell'elicottero! + +[COL5_20] +Sta tornando! Distruggi l'elicottero! + +[COL5_21] +Ma guarda quanto è grosso! + +[COL5_22] +Sta tornando di nuovo! + +[FEA_DSM] +Attenzione! Questo salvataggio utilizza il DTS e richiede periferiche compatibili. Scegli se vuoi procedere utilizzando l'uscita DTS o quella STEREO. + +[STFT_23] +Tempo migliore per la tappa Charlie + +[HELP50] +Premi il tasto ~h~~k~~PED_ANSWER_PHONE~~w~ per posizionare la visuale da dietro. + +[HELP51] +Premi il tasto ~h~~k~~PED_ANSWER_PHONE~~w ~ per posizionare la visuale da dietro. + +[HELP52] +Premi il tasto ~h~~k~~PED_ANSWER_PHONE~~w ~ per posizionare la visuale da dietro. + +[HELP53] +Premi il tasto ~h~~k~~PED_CYCLE_WEAPON_LEFT~~w~ o il tasto ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ per passare in rassegna le armi disponibili. + +[HELP46] +Sono disponibili otto diversi tipi di arma. + +[HELP47] +Puoi trasportare una sola arma per tipo alla volta: un tipo di pistola, un tipo di fucile a pompa... + +[HELP54] +~w~Costo: ~1~$ ~r~Comprando quest'arma sostituirai quella in uso. + +[HELP2A2] +Premi il tasto ~h~~k~~PED_SPRINT~~w~ mentre corri per effettuare uno ~h~scatto~w~. + +[HLPSN_A] +Il fucile di precisione ti permette di ingrandire l'immagine e colpire i bersagli con precisione dalla distanza. + +[HLPSN_B] +Tieni premuto il tasto ~h~~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ col fucile di precisione. + +[HLPSN_C] +Tieni premuto il tasto ~h~L1~w~ per ~h~mirare~w~ col fucile di precisione. + +[HLPSN_D] +Premi il tasto ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ per ~h~zoomare~w~ col fucile e il tasto ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ per ~h~allargare il campo~w~. + +[HLPSN_E] +Premi il tasto ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile di precisione. + +[HLPSN_F] +Premi il tasto ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile di precisione. + +[HLPSN_G] +Premi il tasto ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile di precisione. + +[PLANE_H] +Usa il tasto ~h~~k~~VEHICLE_ACCELERATE~~w~ per accelerare e a sinistra e a destra per curvare. + +[PLANE_4] { reVC update } +{Usa il tasto ~h~~k~~VEHICLE_ACCELERATE~~w~ per accelerare e a sinistra e a destra per curvare.} +Usa la levetta analogica destra per accelerare, premi la levetta analogica sinistra in basso per salire, in alto per scendere e a sinistra e a destra per curvare. + +[HELP55] +Premi il tasto ~h~~k~~PED_FIREWEAPON~~w~ per attaccare lo chef. + +[STPR_8] +Club Pole Position + +[STPR_9] +3321 Vice Point + +[STPR_10] +Appartamento Links View + +[STPR_11] +El Swanko Casa + +[STPR_12] +1102 Washington Street + +[STPR_13] +Appartamento Ocean Heights + +[STPR_14] +Skumole Shack + +[STPR_15] +Condominio Hyman + +[RCCANX] +~r~Aereo radiocomandato annullato. + +[CLT_HL2] +Quando viene raccolto un bonus vestiti, vengono rimosse una o due stelle del livello di sospetto. + +[CRED009] +PROGETTAZIONE MISSIONI + +[CRED359] +LEE JOHNSON + +[CRED360] +HENDRIK LESSER + +[CRED361] +PASQUALE STACCHIOTTI + +[CRED362] +ENRIQUE FERNANDEZ + +[CRED363] +PAUL BYERS + +[CRED364] +MIKE EMENY + +[CRED365] +ROB DUNKIN + +[CRED366] +CHARLIE KINLOCH + +[CRED367] +KEVIN HOBSON + +[CRED368] +JIM CREE + +[MOB_66A] +Tommy, Tommy, Tommy, perché sei tornato qua? + +[MOB_66B] +Te l'avevo detto che non vi volevamo più vedere. + +[MOB_67A] +Tommy, credo che dovresti andartene, capito? + +[MOB_67B] +I ragazzi Haitiani non sono molto felici di te. + +[MOB_18A] +Tommy, sono Paulo, come stai? Bene amico, ascolta: volevo farti una chiamata. + +[MOB_18B] +Ossignore, amico, non crederai mai che razza di strafiga ho appena incontrato. + +[MOB_18C] +Una prostituta forse, passeggiava per Little Havana. + +[MOB_18D] +Ha detto che si chiamava Mercedes o qualcosa del genere. + +[MOB_18E] +Oddio, dovresti proprio vedere questa gnocca. + +[MOB_18F] +Riuscirebbe a succhiare via la mina da una matita. Mi detto che ero il migliore che avesse mai avuto e via dicendo. + +[MOB_18G] +Dovresti farci un giro. Ci vediamo! + +[MOB_72A] +Tommy, sono io, Lance. Tieni la bocca chiusa Tommy, perché non ho tempo da perdere. + +[MOB_72B] +Non mi importa cos'hai da dire. Perché dovrei? Non te ne frega niente di me, non è vero? + +[MOB_72C] +Dovresti fare più attenzione a me. Dovresti darmi la fetta che merito. Lo sai... + +[MOB_72D] +Tommy... senti amico, mi dispiace. È solo che... + +[MOB_72E] +La gente mi ha sempre trattato con accondiscenda, come se fossi un bambino. + +[MOB_72F] +Mio fratello lo farebbe. Per favore, amico, tu non farlo. + +[MOB_72G] +Devo andare. + +[MOB_63A] +Tommy, sono Earnest. Earnest Kelly. + +[MOB_63B] +Come stai? + +[MOB_63C] +Non male. Avrò bisogno di un bastone per camminare, ma dovrei tornare al lavoro ben presto. + +[MOB_63D] +Bene. + +[MOB_63E] +Ho sentito di Lance. Che brutto storno, eh? + +[MOB_63F] +Sì. + +[MOB_63G] +Mai fidarsi di qualcuno che cammina per strada in pigiama. Ecco cosa dico. Sono felice che l'hai fatto fuori. Spero che sia stato doloroso per lui. + +[MOB_63H] +Credo di sì. Non credevo fosse quel genere di persona... + +[MOB_63I] +Tommy, per essere un pazzo violento, sei piuttosto ingenuo. Tornerò presto al lavoro e ti insegnerò un paio di cose sulla vita. Ci sentiamo. + +[MOB_63J] +Fai con calma, Earnest. Abbi cura di te. + +[MOB_16A] +Tommy, sono Paulo, que pasa amigo? + +[MOB_16B] +Che cosa vuoi Paul: non sono interessato a vestiti di marca taroccati. + +[MOB_16C] +Molto divertente, amico, ma lo sai che non tratto merce di seconda classe. Nah, ti ho chiamato per sapere se potevo avere una parte nei tuoi film. + +[MOB_16D] +Quando ero in Inghilterra ho fatto un sacco di cose, sai? Ho attributi più forniti di te, amico. + +[MOB_16E] +Paul, grazie per l'offerta, lo terrò presente. + +[MOB_16F] +Davvero, non scordarti di me dopo tutto quello che ho fatto per te. + +[MOB_16G] +È proprio ciò che sto cercando di dimenticare... + +[MOB_17A] +Tommy Vercetti: come va, Mr. Pezzo Grosso? Ho saputo le tue novità: un vero giocatore in città, eh... + +[MOB_17B] +Paul, sei ubriaco. + +[MOB_17C] +No, idiota. Non sono ubriaco. Solo un paio di bicchierini e qualche pillola: non dormo da un paio di giorni, sai... + +[MOB_17D] +Comunque, niente prediche. Non sono uno stupido. Chi ti ha portato in questa città? Chi? Io, ecco chi. + +[MOB_17F] +Davvero? + +[MOB_17G] +Niente prediche, smettila! Ti ho fatto conoscere la gente giusta, ti ho mostrato che corde tirare. Ho fatto molto per te e questo è il modo con cui mi ringrazi. + +[MOB_17H] +Mi ignori. Non mi aiuti a entrare dopo tutto ciò che ho fatto per te! Cosa credi che sia? Un idiota? + +[MOB_17I] +Paul, sta' buono. Sono stato occupato, non fare l'idiota. + +[MOB_17J] +Non sono un idiota, chiaro? Questo è ciò che hanno detto in riformatorio. Stai forse cercando guai, amico? Beh, stai per trovarli! + +[MOB_17K] +Tommy, amico. Per favore. Sei la mia più grande speranza! Per favore, non ridere di me. + +[MOB_17L] +Paul, cerca di dormire, davvero. + +[MOB_73A] +Tommy, sono Steve. + +[MOB_73B] +Ehi Steve. + +[MOB_73C] +Ehi, ciao genio. Sei uno spettacolo! Anch'io sono uno spettacolo! Ci adorano. Stiamo battendo tutti i record, amico. + +[MOB_73D] +Stiamo ottenendo dei grandi riconoscimenti. Finalmente posso comprare una casa al mio vecchio così se ne starà zitto. + +[MOB_73E] +Eeer, mi sembra perfetto, Steve. + +[MOB_73F] +Perfetto? È meraviglioso! Meraviglioso, MERAVIGLIOSO! Non ha mai creduto in me. Non pensava fossi un artista, ma adesso ce l'ho fatta. + +[MOB_73G] +Sono il miglior direttore sadomaso di tutti i tempi, amico. Volevo solo dirti che è stato un piacere averti incontrato. + +[MOB_73H] +Grazie Steve. + +[MOB_73I] +Ti voglio bene, amico. Non cambiare, capito? + +[MOB_73J] +Capito. Ci sentiamo Steve. + +[BOLLOX] +Premi il tasto ~o~R1~w~ per sganciare una bomba. Premi il tasto ~t~"~w~ per annullare. + +[BRID_OP] +Pericolo uragano terminato: tutti i ponti per la terra ferma sono nuovamente aperti. + +[BRID_CL] +Pericolo uragano: tutti i ponti per la terra ferma sono chiusi. + +[LG_38] +Bersaglio + +[ASSET_C] +POLE POSITION COMPLETATA! + +[ASSET_D] +~g~ Il Club Pole Position generera' un reddito fino ad un massimo di $~1~ al giorno. Ritira il denaro regolarmente! + +[ST_WHEE] +Impennata più lunga (sec) + +[ST_STOP] +Frenata in punta più lunga (sec) + +[ST_2WHE] +Permanenza su 2 ruote più lunga (sec) + +[ST_WHED] +Distanza piu' lunga in impennata (m) + +[ST_STOD] +Distanza piu' lunga in frenata (m) + +[ST_2WHD] +Distanza piu' lunga su 2 ruote (m) + +[OUTFT11] +Tuta da ginnastica + +[OUTFT12] +Frankie + +[RELOAD] +~g~Hai vinto l'abilita' di ricarica veloce! + +[APACHE] +Hunter consegnato all'eliporto di Ocean Beach + +[CRED369] +JOHN MCCARDLE + +[CRED370] +DAVID MURDOCH + +[CRED371] +CHRIS BROWN + +[CRED372] +PAUL GREEN + +[CRED373] +KYLE MILNE + +[CUNTY] +Nuovi vestiti consegnati alla Proprieta' di Vercetti! + +[GOODBOY] +$50 Come Bonus Buon Cittadino! + +[NEWCONT] +Nuovo ~h~Punto di Contatto~w~ disponibile alla Marina di Ocean Beach!! + +[FIRELVL] +Missione Camion dei pompieri livello ~1~ + +[HELP56] +Premi il tasto ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ per cambiare la modalità di visuale. + +[HELP57] +Premi il tasto ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ per cambiare la modalità di visuale. + +[HELP58] +Mentre prendi la mira, premi il ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~, ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~ per cambiare bersaglio. + +[HELP59] +Mentre prendi la mira, premi il ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~, ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~ per cambiare bersaglio. + +[HELP60] +Se premi il tasto ~h~~k~~PED_SPRINT~~w~ mentre assalti una macchina, non entrerai nel veicolo. + +[HELP61] +Adesso hai colpi infiniti e il doppio dell'energia su tutti i veicoli. + +[CRED374] +KEVIN YUN + +[CRED375] +ERICK COBBS + +[CRED376] +RANDY BLAKE + +[CRED377] +BRANDON LIM + +[CRED378] +BRANDON FENOL + +[CRED379] +MICHAEL MANOLE + +[CRED380] +ALETHEIA SIMONSON + +[CRED381] +JOHN JANSEN + +[FEC_LB1] +Guarda + +[FEC_LB2] +indietro + +[FEC_LB3] +Guarda indietro + +[FEC_R3] +(tasto R3) + +[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 + +[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~ + +[FEC_WAR] +Attenzione + +[FEC_OKK] +OK + +[FEC_DLF] +Eliminazione fallita. + +[FEC_SVU] +Salvataggio fallito. + +[FEC_LUN] +Caricamento fallito. File corrotto: è necessario eliminarlo. + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FES_CSA] +Seleziona una skin dalla seguente lista: + +[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_DSN] +Skin predefinita.bmp + +[FET_RSO] +RIPRISTINATE IMPOSTAZIONI ORIGINALI + +[FET_RSC] +HARDWARE NON DISPONIBILE - RIPRISTINATE IMPOSTAZIONI ORIGINALI + +[FEA_3DH] +HARDWARE AUDIO + +[FEA_SPK] +CONFIGURAZIONE ALTOPARLANTI + +[FEM_LOD] +DISTANZA VISUALE + +[FEM_VSC] +SINCRONIA FRAME + +[FEM_FRM] +LIMITATORE FRAME + +[FEM_MM] +MENU PRINCIPALE + +[FED_RES] +RISOLUZIONE + +[FET_CTL] +IMPOSTAZIONI DEI COMANDI + +[FET_OPT] +OPZIONI + +[FEC_MSH] +SENSIBILITÀ MOUSE + +[FEC_IVV] +INVERTI MOUSE VERTICALMENTE + +[FET_MTI] +CONFIGURAZIONE COL MOUSE + +[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 + +[FEC_SPC] +BARRA + +[WIN_TTL] +Grand Theft Auto VC + +[WIN_95] +Grand Theft Auto VC non è supportato da Windows 95 + +[WIN_DX] +Grand Theft Auto VC richiede DirectX versione 8.1 o superiore + +[FET_EIG] +IMPOSSIBILE ASSEGNARE UN COMANDO A QUESTA AZIONE + +[FET_DAM] +MODELLAZIONE ACUSTICA DINAMICA + +[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? + +[FET_QG] +ESCI DAL GIOCO + +[FEN_STA] +AVVIA LA PARTITA + +[FET_PAU] +MENU PAUSA + +[REPLAY] +REPLAY + +[FEC_ANS] +Azione + +[CVT_MSG] +Conversione texture per prestazioni ottimali con la tua scheda video + +[FEC_SFT] +MAIUSC + +[FEH_VMP] +VEDI MAPPA + +[FES_DEE] +Eliminazione fallita! Prova nuovamente. + +[FES_CMP] +Salvataggio fallito! Prova nuovamente. + +[FESZ_WR] +Salvataggio partita. Un momento... + +[FELD_WR] +Caricamento partita. Un momento... + +[FEDL_WR] +Eliminazione salvataggio. Un momento... + +[PCRESRT] +Avvio nuova partita. Un momento... + +[FET_STI] +Configurazione standard + +[FET_CTI] +Configurazione classica + +[FET_PS] +IMPOSTAZIONE SKIN GIOCATORE + +[FEH_NA] +OPZIONE NON DISPONIBILE + +[FEH_MPH] +MOUSE, FRECCE PER MUOVERSI - PAGSU, PAGGIÙ, ROTELLINA PER LO ZOOM, L - LEGENDA + +[FEA_MP3] +RIPRODUTTORE MP3 + +[NO_PCCD] +Inserisci il CD di GTA Vice City o premi Esc per annullare + +[FEH_SSA] +FRECCE PER MUOVERSI - S PER SALVARE + +[FES_CMI] +ULTIMA MISSIONE COMPLETATA + +[FET_STS] +STATISTICHE SALVATE NEL FILE 'STATS.HTML' + 'STATS.TXT' + +[WIN_VDM] +Memoria video insufficiente per eseguire Grand Theft Auto VC + +[FEC_ERI] +Errore! Uno o più azioni non sono assegnate a nessun tasto o pulsante. Assicurati che a tutte le azioni corrisponda un comando. + +[FEC_TFU] +Torretta + Inclina in su + +[FEC_TFD] +Torretta + Inclina in giù + +[FET_RIG] +SELEZIONA IL NUOVO COMANDO PER QUESTA AZIONE + +[FEA_NM3] +NESSUN FILE MP3 TROVATO + +[FEA_MPB] +INCREMENTO VOLUME MP3 + +[FEA_MUS] +VOLUME MUSICA + +[FEA_SFX] +VOLUME EFFETTI + +[CVT_ERR] +Spazio su disco esaurito; è necessario liberare dello spazio prima di poter procedere. Premi ESC per annullare. + +[FEA_ADP] +RILEVAMENTO AUTOMATICO + +{=================================== MISSION TABLE AMBULAE ===================================} + +[ATUTOR2:AMBULAE] +~g~Conduci i pazienti all'ospedale guidando CON PRUDENZA! Qualsiasi scossone può ucciderli. + +[A_FULL:AMBULAE] +~r~Ambulanza piena! + +[A_FAIL2:AMBULAE] +~r~La tua scarsa sollecitudine è stata fatale al paziente! + +[A_FAIL3:AMBULAE] +~r~Il paziente è morto! + +[A_PASS:AMBULAE] +Salvato! + +[A_COMP2:AMBULAE] +Non ti stancherai mai! + +[A_COMP1:AMBULAE] +Missioni Infermiere completate: ~1~$ + +[A_CANC:AMBULAE] +~r~Missione Infermiere annullata! + +[A_COMP3:AMBULAE] +Missione Infermiere completata! Non ti stancherai mentre corri! + +[ALEVEL:AMBULAE] +Missione Infermiere livello ~1~ + +[A_FAIL1:AMBULAE] +Missione Infermiere terminata. + +[A_SAVES:AMBULAE] +PERSONE SALVATE: ~1~ + +{=================================== MISSION TABLE ASSIN1 ===================================} + +[ASM1_5:ASSIN1] +~r~Ha completato le sue consegne! + +[ASM1_6:ASSIN1] +Consegne rimanenti: + +[ASM1_7:ASSIN1] +~g~Carl Pearson, fattorino di una pizzeria. Uccidilo prima che completi le sue consegne. + +[ASM1_D:ASSIN1] +Mr. Teal, il tuo aiuto nello sradicare gli stranieri è stato inestimabile per il nostro lavoro. + +{=================================== MISSION TABLE ASSIN2 ===================================} + +[ASM2_1:ASSIN2] +~g~Mrs. Dawson lascerà presto la gioielleria in Vice Point. Devi ucciderla. Deve sembrare un incidente d'auto. + +[ASM2_3:ASSIN2] +~g~Sta per esplodere! Allontanati! + +[ASM2_4:ASSIN2] +~r~Hai danneggiato la sua auto, ma lei non è sopra! Adesso non ci entrerà di sicuro! + +[ASM2_5:ASSIN2] +~r~È riuscita a scappare! + +[ASM2_6:ASSIN2] +~r~Sei stato visto troppo vicino alla scena dell'incidente! + +[ASM2_7:ASSIN2] +~g~Non utilizzare armi! Deve sembrare un incidente! Spingila fuori strada! + +[ASM2_8:ASSIN2] +~g~La morte di Mrs. Dawson deve sembrare un incidente. Non utilizzare armi. + +[ASM2_9:ASSIN2] +~g~Avrai bisogno di una vettura per questo lavoro! + +[ASM2_10:ASSIN2] +~g~Quando la sua macchina prende fuoco, allontanati il più possibile dal luogo dell'incidente. + +[ASM2_11:ASSIN2] +Aiuto! + +[ASM2_12:ASSIN2] +Qualcuno mi aiuti! + +[ASM2_13:ASSIN2] +Oddio! + +[ASM2_A:ASSIN2] +Complimenti per l'ottimo lavoro, Mr. Teal. Il mio cliente è molto soddisfatto. + +[ASM2_2:ASSIN2] +Salute: + +{=================================== MISSION TABLE ASSIN3 ===================================} + +[ASM3_11:ASSIN3] +TEMPO: + +[ASM3_C:ASSIN3] +Una gang europea ha pianificato un furto in banca a Vice City. I miei clienti preferirebbero che il colpo non vada a segno. + +[ASM3_D:ASSIN3] +Ogni membro della gang ha un nascondiglio nella città: alcuni hanno dei lavori di copertura, altri sono in vacanza. + +[ASM3_E:ASSIN3] +Dettagli e posizione più probabile di ogni bersaglio sono riportati nel nastro sotto al telefono. + +[ASM3_14:ASSIN3] +~g~Dick Tanner si trova presso la DBP Security in Ocean Drive. + +[ASM3_15:ASSIN3] +~g~Marcus Hammond e Franco Carter si trovano presso la gioielleria in Vice Point. + +[ASM3_16:ASSIN3] +~g~Nick Kong si trova presso Washington Beach. + +[ASM3_18:ASSIN3] +~g~Non avvicinarti troppo, se no il bersaglio potrebbe vederti e sarai costretto a inseguirlo! + +[ASM3_19:ASSIN3] +~g~Ti ha visto! Fallo fuori! + +[ASM3_20:ASSIN3] +~g~Ti hanno visto! Falli fuori entrambi! + +[ASM3_21:ASSIN3] +~r~Non hai ucciso tutti i membri della gang in tempo! + +[ASM3_22:ASSIN3] +~g~Non avvicinarti troppo, se no i bersagli potrebbero vederti e cercare di scappare! + +[ASM3_12:ASSIN3] +~g~Un assortimento di armi è stato lasciato per te nel caso di bisogno. Hai ~h~9 MINUTI~g~ per uccidere tutti i membri della gang. + +[ASM3_13:ASSIN3] +~g~Mike Griffin lavora su un cartello pubblicitario in Washington. + +[ASM3_17:ASSIN3] +~g~Charlie Dilson è in macchina in Washington. + +{=================================== MISSION TABLE ASSIN4 ===================================} + +[ASM4_12:ASSIN4] +Distanza: + +[ASM4_15:ASSIN4] +~g~Recupera il fucile di precisione alla tua destra. + +[ASM4_16:ASSIN4] +~g~Osserva la donna sul balcone: scenderà per le scale e chiederà l'ora a un uomo. + +[ASM4_17:ASSIN4] +~g~Solo quando la conversazione avrà termine, dovrai uccidere l'uomo con cui ha parlato ma NON la donna. + +[ASM4_18:ASSIN4] +~g~Una volta eliminato il bersaglio, recupera la sua valigetta e portala ad Ammu-Nation in Downtown. + +[ASM4_19:ASSIN4] +~g~Tieni la distanza dal bersaglio: la barra nella parte superiore destra dello schermo indica quanto gli sei vicino. + +[ASM4_20:ASSIN4] +~g~Se si riempirà, verrai avvistato. + +[ASM4_21:ASSIN4] +~g~Recupera la valigetta! + +[ASM4_22:ASSIN4] +~g~Porta la valigetta ad Ammu-Nation in Downtown. + +[ASM4_23:ASSIN4] +~g~Ti ha visto e sta fuggendo! Uccidilo e recupera la valigetta! + +[ASM4_25:ASSIN4] +~r~Hai ucciso la donna, idiota! + +[ASM4_26:ASSIN4] +~r~Il bersaglio è salito a bordo del volo! + +[ASM4_27:ASSIN4] +~r~Il bersaglio ti ha visto! Avresti dovuto tenerti a distanza! + +[ASM4_28:ASSIN4] +~r~Il bersaglio ti ha visto! Ti ha sentito sparare! + +[ASM4_29:ASSIN4] +~r~Dovevi ucciderlo solo dopo che aveva parlato con la donna! + +[ASM4_A:ASSIN4] +È giunta l'ora di occuparsi di pesci più grossi, Mr Teal. Troverai un fucile tra le foglie alla tua destra. + +[ASM4_B:ASSIN4] +Osserva la donna sul balcone sopra i banchi dei check-in: scenderà per le scale e chiederà l'ora a un uomo. + +[ASM4_C:ASSIN4] +Dovrai uccidere questa persona, recuperare la sua valigetta e portarla nella locazione riportata sotto al telefono. + +{=================================== MISSION TABLE ASSIN5 ===================================} + +[ASM5_A:ASSIN5] +C'è uno scambio di beni sopra il tetto della fabbrica di gelato Cherry Popper. + +[ASM5_B:ASSIN5] +Uccidi tutti gli elementi coinvolti, recupera la merce e portala all'eliporto presso l'aeroporto. + +[ASM5_C:ASSIN5] +Alla tua sinistra troverai un cancello che porta al retro della fabbrica. + +[ASM5_1:ASSIN5] +~g~Entra nel cortile dietro alla fabbrica di gelato Cherry Popper e apriti la strada fino al luogo dello scambio. + +[ASM5_2:ASSIN5] +~g~Recupera la merce e portala all'eliporto presso l'aeroporto. + +[ASM5_3:ASSIN5] +~g~Porta la merce all'eliporto presso l'aeroporto! + +{=================================== MISSION TABLE BANKJ1 ===================================} + +[WANTED1:BANKJ1] +~g~Semina i poliziotti per perdere il tuo livello di sospetto. + +[BJM1_A:BANKJ1] +Tommy! Ehi Tommy, guarda qua, è grandioso! Ho fatto installare un minibar! + +[BJM1_B:BANKJ1] +Abbiamo un intero bar al piano di sotto, Ken. + +[BJM1_C:BANKJ1] +Sì, sì, certo. Beh, ho portato quella lavagna che mi avevi richiesto. + +[BJM1_D:BANKJ1] +Ah, questi sono i benefici della scuola dell'obbligo: l'abilità di seguire le istruzioni. + +[BJM1_E:BANKJ1] +Adesso mi serve uno scassinatore. + +[BJM1_F:BANKJ1] +Oh, va bene, beh, fammi pensare... scassinatore, scassinatore, scassinatore... Trovato! Questo tipo ti farà impazzire! + +[BJM1_G:BANKJ1] +Ahh, nah, quello schmuck. È dentro. + +[BJM1_H:BANKJ1] +Dentro dove? + +[BJM1_I:BANKJ1] +In una cella nel quartier generale della polizia in attesa di trasferimento. + +[BJM1_J:BANKJ1] +Credo stia per essere rilasciato sulla parola... + +[BJM1_1:BANKJ1] +~g~Fai fuggire Cam Jones dalla custodia della polizia! + +[BJM1_3:BANKJ1] +~g~Troverai ciò che ti serve nello spogliatoio della stazione. + +[BJM1_21:BANKJ1] +~g~La tessera per le celle si trova nel piano superiore della stazione. + +[BNK1_7:BANKJ1] +Cam Jones? + +[BNK1_8:BANKJ1] +Ti sto facendo scappare! + +[BNK1_10:BANKJ1] +Sì, sono io... + +[BNK1_11:BANKJ1] +Come vuoi! + +[BNK1_13:BANKJ1] +Devo fare un lavoro e tu sarai il mio scassinatore. + +[BNK1_14:BANKJ1] +Sempre meglio che restare col culo in galera! + +[BJM1_22:BANKJ1] +~g~Riporta Cam a casa sua! + +[BJM1_23:BANKJ1] +~g~Prima devi recuperare la tessera! + +[BNK1_12:BANKJ1] +Fai perdere le tracce e riportami a casa! + +[BJM1_20:BANKJ1] +Metti via l'arma o ne pagherai le conseguenze! + +[BJM1_5:BANKJ1] +Solo personale autorizzato oltre a questo punto! + +[BJM1_2:BANKJ1] +~r~Dovevi far fuggire Cam, non farlo morire! + +[BJM1_4:BANKJ1] +È armato! Uccidetelo! + +{=================================== MISSION TABLE BANKJ2 ===================================} + +[BJM2_A:BANKJ2] +Abbiamo bisogno di un tiratore. Ne conosci uno? + +[BJM2_B:BANKJ2] +Ehi Tommy, Tommy, Tommy, questa roba ti tiene in forma, amico. + +[BJM2_C:BANKJ2] +WoooOOOooo! + +[BJM2_D:BANKJ2] +Potrei essere io il tuo tiratore! Spara! Spara! + +[BJM2_E:BANKJ2] +Non sei un tiratore, sei un idiota. + +[BJM2_F:BANKJ2] +Fatti un drink e sta zitto. + +[BJM2_G:BANKJ2] +Ehi, via dai miei piedi! Ye ye ye ow ow. + +[BJM2_H:BANKJ2] +Cam, cosa mi dici? + +[BJM2_I:BANKJ2] +Beh, il miglior tiratore in città è un tipo chiamato Cassidy. + +[BJM2_J:BANKJ2] +Davvero? + +[BJM2_K:BANKJ2] +Sì. Un militare, o per lo meno crede di esserlo. + +[BJM2_L:BANKJ2] +Non credo sia mai stato nell'esercito, ma sicuramente sa come tenere in mano un'arma. + +[BJM2_M:BANKJ2] +Lo troverai di sicuro al poligono di tiro. + +[BJM2_2A:BANKJ2] +Sei Phil Cassidy? + +[BJM2_2B:BANKJ2] +Perché? + +[BJM2_2C:BANKJ2] +Sto cercando un uomo che sappia come sparare. Da quello che vedo, non sono molto convinto... + +[BJM2_2D:BANKJ2] +Figliolo, potrei colpire una mosca sulla tua testa a 30 metri di distanza. + +[BJM2_2E:BANKJ2] +Davvero? + +[BJM2_2F:BANKJ2] +Certo. Ho fatto pratica nell'esercito. + +[BJM2_2G:BANKJ2] +Lo sparo alla mosca è così popolare nell'esercito? Per fortuna non pago le tasse. + +[BJM2_2H:BANKJ2] +Stai cercando di fare lo spiritoso? + +[BJM2_2I:BANKJ2] +Ah ah ah ah ah! + +[BJM2_2J:BANKJ2] +Spariamo. + +[BJM2_1:BANKJ2] +~g~Raggiungi Ammu-Nation in Downtown e parla con Phil Cassidy. + +[BJM2_4:BANKJ2] +PUNTEGGIO PRIMO TURNO: ~1~ + +[BJM2_6:BANKJ2] +PUNTEGGIO SECONDO TURNO: ~1~ + +[BJM2_7:BANKJ2] +PUNTEGGIO TOTALE: ~1~ + +[BJM2_9:BANKJ2] +~G~Raggiungi il punto di partenza del secondo turno. + +[BJM2_11:BANKJ2] +~r~Phil è morto! + +[BJM2_12:BANKJ2] +~r~Uno dei tiratori è morto! + +[BJM2_14:BANKJ2] +~g~Raggiungi la prossima area! + +[BJM2_17:BANKJ2] +~g~Vai a parlare con Phil. + +[BJM2_24:BANKJ2] +~g~Il bersaglio più vicino vale un punto. + +[BJM2_25:BANKJ2] +~g~Il bersaglio centrale vale due punti. + +[BJM2_27:BANKJ2] +~g~In questo turno, tutti i bersagli valgono un punto. + +[BNK2_4:BANKJ2] +Hoooeee! + +[BNK2_5:BANKJ2] +Non riusciresti a colpire neanche un elefante! + +[BNK2_7:BANKJ2] +Fammi un favore, aiutami a mettere a segno un colpo. + +[BNK2_8:BANKJ2] +Figliolo, da come hai sparato, se mi chiedessi di sposarti direi di sì. + +[BNK2_9A:BANKJ2] +Figliolo, ficcati la tua parlata e le tue grandi idee là dove non batte il sole. Non sai sparare per niente. + +[BNK2_9B:BANKJ2] +Non sai sparare per niente. + +[BJM2_28:BANKJ2] +PUNTEGGIO TERZO TURNO: ~1~ + +[BJM2_26:BANKJ2] +~g~Il bersaglio più lontano vale tre punti. + +[BNK2_1:BANKJ2] +MUNIZIONI VERE + +[RANGE_1:BANKJ2] +PUNTEGGIO PER COLPO: ~1~ + +[BJM2_2:BANKJ2] +~g~Per interrompere il turno, premi il ~h~~k~~PED_JUMPING~~g~. + +[BJM2_N:BANKJ2] +Rilassati. + +{=================================== MISSION TABLE BANKJ3 ===================================} + +[BJM3_A:BANKJ3] +Le cose stanno iniziando a prendere forma. + +[BJM3_B:BANKJ3] +Qual è il piano Tommy? Que pasa, amigo? + +[BJM3_C:BANKJ3] +La tua parte consiste nel continuare a fare l'idiota. Comunque, adesso abbiamo bisogno di un pilota. + +[BJM3_D:BANKJ3] +Tommy, lo farò io. Io so guidare bene. + +[BJM3_E:BANKJ3] +Ti serve Hilary, capo. Di certo non un sapientone avvocato del cazzo. + +[BJM3_F:BANKJ3] +Hilary è la persona giusta. Non troverai nessuno capace di guidare così veloce. Gli posso fare una chiamata. + +[BJM3_G:BANKJ3] +Ehi Hil, sono Phil. Come butta? No, lascia stare, ne parliamo un'altra volta. Mi faresti un favore? + +[BJM3_H:BANKJ3] +Ho qui un tipo del nord... No, no, non credo abbia fatto il servizio militare, ma ha bisogno di un guidatore. + +[BJM3_I:BANKJ3] +Per un po' di azione. OK, capisco. + +[BJM3_J:BANKJ3] +Che cosa ha detto? + +[BJM3_K:BANKJ3] +Beh, ci sta, nessun problema. Beh, un piccolo problema c'è: ha subito uno shock da abbandono. + +[BJM3_L:BANKJ3] +Sembra non voglia lavorare con nessuno che non sia capace di batterlo. Qualcosa che ha a che fare con la sua vecchia. + +[BJM3_M:BANKJ3] +Comunque, vuole prima sfidarti in una corsa, ha detto che ci aspettava fuori... + +[BJM3_2A:BANKJ3] +Sei Tommy? Beh, certo che sei Tommy, cioè... + +[BJM3_2B:BANKJ3] +Per quale altro motivo qualcuno vorrebbe parlare con me? + +[BJM3_2C:BANKJ3] +OK, vedila in questo modo... + +[BJM3_2D:BANKJ3] +Io guiderò SE, e solo SE, tu sai guidare come si deve. + +[BJM3_2E:BANKJ3] +Lasciami solo... e non te lo perdonerò mai. + +[BJM3_2:BANKJ3] +~r~Hilary è morto! + +[BJM3_4:BANKJ3] +~g~Hai bisogno di una vettura per partecipare! + +[BNK3_1:BANKJ3] +OK. Guiderò per te, ma per favore trattami male! + +[BNK3_3A:BANKJ3] +Corsa illegale in corso presso Vice Point. + +[BNK3_3B:BANKJ3] +A tutte le pattuglie. + +[BNK3_3C:BANKJ3] +Queste corse sono illegali e vietate! + +{=================================== MISSION TABLE BANKJ4 ===================================} + +[BNK4_A:BANKJ4] +~w~Come potete vedere, signori, questi saranno i soldi più facili della storia. + +[BNK4_B:BANKJ4] +~w~Tommy, seriamente, dovresti pensare di fare l'avvocato. + +[BNK4_C:BANKJ4] +~w~Ma che cosa ti fumi, amico? Non è per niente un piano semplice! + +[BNK4_D:BANKJ4] +~w~Beh, chi ha bisogno di un piano semplice? + +[BNK4_E:BANKJ4] +~w~Prendi il comunismo, beh, quello era un piano semplice. Non ha fatto così bene alla Russia, no? + +[BNK4_F:BANKJ4] +~w~Rilassatevi, OK? Con una squadra come la nostra, non ci saranno problemi. + +[BNK4_G:BANKJ4] +~w~Abbiamo Cam per la cassaforte. Phil? Noi ci occuperemo della sorveglianza, mentre Hilary guiderà l'auto per la fuga. + +[BNK4_H:BANKJ4] +~w~Uh, eh eh, non ti stai dimenticando qualcuno? Qualcuno che ti ha costantemente aiutato in questa città? Qualcuno...? + +[BNK4_I:BANKJ4] +~w~Ken... Ken, ma certo. Il nostro Ken si occuperà di lavare il denaro sporco e di tenere le birre al fresco. + +[BNK4_J:BANKJ4] +~w~Non ho ancora capito cosa ci sto a fare io qua. + +[BNK4_K:BANKJ4] +~w~Beh, è semplice: non hai mai visto un film d'azione? + +[BNK4_L:BANKJ4] +~w~Entriamo nella banca, sventoliamo in giro l'arma e usciamo molto ricchi. + +[P_DEAD:BANKJ4] +~r~Phil è morto! + +[C_DEAD:BANKJ4] +~r~Cam è morto! + +[H_DEAD:BANKJ4] +~r~Hilary è morto! + +[P_HIND:BANKJ4] +~r~Hai perso Phil! + +[C_HIND:BANKJ4] +~r~Cam è restato indietro! + +[H_HIND:BANKJ4] +~r~Hai abbandonato Hilary! + +[GETCAR:BANKJ4] +~g~Entra nell'auto e preparati alla rapina! + +[TRASHED:BANKJ4] +~r~HAI DISTRUTTO L'AUTO PER LA FUGA!!! + +[BNK4_1:BANKJ4] +Guido io. + +[BNK4_2:BANKJ4] +Ottimo. Un passeggero. Aspetta che lo dica al resto del gruppo. + +[BNK4_3A:BANKJ4] +Ehi, fai attenzione alla macchina, Tommy! + +[BNK4_3B:BANKJ4] +Tommy, Hilary occupa troppo spazio! + +[BNK4_3C:BANKJ4] +Non è vero! + +[BNK4_3D:BANKJ4] +Invece sì! + +[BNK4_3E:BANKJ4] +Ehi, state entrambi zitti o ve ne andate a piedi. + +[BNK4_3F:BANKJ4] +Sì Hilary. + +[BNK4_3I:BANKJ4] +Perdio, Phil, smettila di sventolare quell'affare in giro! + +[BNK4_3J:BANKJ4] +Sì, finirai col cavare un occhio a qualcuno! + +[BNK4_3M:BANKJ4] +La mia bambina! È a pezzi! + +[BNK4_3O:BANKJ4] +Sei accecato dall'illusione di permanenza. + +[BNK4_3P:BANKJ4] +Cosa? + +[BNK4_3Q:BANKJ4] +Pensi che tutto duri per sempre. + +[BNK4_3R:BANKJ4] +La giovinezza, le persone care, la pizza... + +[BNK4_3S:BANKJ4] +Ma tutto passa e finisce e tu lo devi accettare. + +[BNK4_3T:BANKJ4] +Ehi, hai ragione. Grazie Cam. + +[BNK4_3U:BANKJ4] +È un piacere. + +[BNK4_3V:BANKJ4] +Ehi Tommy, perché ci siamo fermati? + +[BNK4_4A:BANKJ4] +~w~Hilary, continua a guidare attorno al quartiere. + +[BNK4_5:BANKJ4] +~w~OK Tommy, OK. + +[BNK4_6:BANKJ4] +~w~QUESTA È UNA RAPINA! + +[BNK4_7:BANKJ4] +~w~NESSUNO SI MUOVA! + +[BNK4_8:BANKJ4] +~w~TUTTI CONTRO QUEL MURO! + +[BNK4_9:BANKJ4] +Phil, mantieni il controllo! + +[BNK4_10:BANKJ4] +Ricevuto capo! + +[BNK4_11:BANKJ4] +Vieni Cam, la cassaforte è di sopra... + +[BK4_12A:BANKJ4] +Maledizione! È una Flange 9000! + +[BK4_12B:BANKJ4] +Potrei impiegare ore ad aprirla, + +[BK4_12C:BANKJ4] +o cinque minuti se trovi l'amministratore. + +[BNK4_13:BANKJ4] +Vado a scoprire dove si è rintanato. + +[BK4_14A:BANKJ4] +Phil, va tutto bene? + +[BNK4_15:BANKJ4] +Certo. Tutto liscio come l'olio. + +[BNK4_16:BANKJ4] +Tu, tu vieni con me! + +[BNK4_17:BANKJ4] +OK, OK! Ti prego, non sparare! + +[BNK4_18:BANKJ4] +HO DETTO NESSUNO SI MUOVA! + +[BK4_19A:BANKJ4] +L'apertura è temporizzata, + +[BK4_19B:BANKJ4] +potete anche rinunciarci! + +[BK4_20A:BANKJ4] +Diamine, posso aggirare l'apertura a tempo, + +[BK4_20B:BANKJ4] +ma poi avremo bisogno del tuo codice per spalancarla! + +[BNK4_21:BANKJ4] +Sta qua. Se fai uno scherzo diventi cibo per pesci. Chiaro? + +[BK4_24A:BANKJ4] +Vado a controllare Phil, torno subito. + +[BK4_24B:BANKJ4] +Te l'avevo detto di non toccare l'allarme! + +[BNK4_25:BANKJ4] +La squadra SWAT sarà qui fra pochi minuti! + +[BNK4_27:BANKJ4] +Un po' di aiuto non farebbe male, Tommy. + +[BNK4_28:BANKJ4] +SWAT di Vice City! Siete completamente circondati! + +[BNK4_29:BANKJ4] +Circondati? AH AH AH Aaaaaaaaaah! + +[BNK4_30:BANKJ4] +Si stanno cagando addosso, corrotti bastardi! + +[BK4_31A:BANKJ4] +Tommy! La cassaforte è aperta! + +[BK4_34A:BANKJ4] +OK, abbiamo i fondi pensione della SWAT. Andiamocene da qui! + +[BK4_34B:BANKJ4] +OK, l'avete voluto voi! Il tempo è scaduto! + +[BK4_35A:BANKJ4] +Stanno assalendo l'edificio! + +[BK4_35B:BANKJ4] +Al riparo! + +[BNK4_36:BANKJ4] +Dov'è Cam? + +[BNK4_37:BANKJ4] +Non ce l'ha fatta... + +[BNK4_38:BANKJ4] +Questo è l'ultimo. VIA! VIA! VIA! + +[BNK_39:BANKJ4] +Merda! Dov'è Hilary? + +[BK4_40A:BANKJ4] +Gliela do io l'illusione di permanenza! + +[BNK4_42:BANKJ4] +Ehi, ragazzi! Entrate, vi copro io! + +[BNK4_44:BANKJ4] +Ce l'abbiamo fatta! Siamo ricchi, RICCHI! + +[BNK4_45:BANKJ4] +Peccato che Cam non ce l'ha fatta, era un bravo ragazzo! + +[BNK4_46:BANKJ4] +Sì, comunque... ora ce n'è più per noi! + +[BNK4_47:BANKJ4] +Maledettamente vero! YEEEEHAAAH! + +[BNK4_48:BANKJ4] +Tommy, vuoi un massaggio? + +[BNK4_49:BANKJ4] +Ciao Mercedes! Sì, effettivamente sono un po' teso... + +[BNK450A:BANKJ4] +Sai cosa ti dico Tommy? Sai cosa ti dico? I membri corrotti della SWAT faranno meglio a fare attenzione finché Kent Paul è in città. + +[BNK450B:BANKJ4] +Eddai, dammene una fetta più grande! Amico, dai! Mi devo comprare dei vestiti nuovi! + +[BNK4_94:BANKJ4] +~w~OK, ragazzi. Procediamo come pianificato. + +[BM_DEAD:BANKJ4] +~r~Hai bisogno dell'amministratore vivo! + +[ASSET_A:BANKJ4] +RAPINA ALLA BANCA COMPLETATA! + +[ASSET_B:BANKJ4] +~g~Il Malibu d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +[IDIOT:BANKJ4] +~r~Vai in giro vestito come un pazzo e attiri l'attenzione! Sei un IDIOTA! + +{=================================== MISSION TABLE BARON1 ===================================} + +[COK1_A:BARON1] +Forza, dai, forza! Yeh yeh aaaah... + +[COK1_B:BARON1] +Stupido cavallo! Ti taglierò la testa! + +[COK1_C:BARON1] +Chi è questo stronzo? + +[COK1_D:BARON1] +Tommy Vercetti, ti ricorderai di me. + +[COK1_E:BARON1] +Scusami, sono un po' ansioso. Mai fidarsi di un fottuto cavallo! + +[COK1_F:BARON1] +Hai fatto un bel lavoro: adesso lavori per me. + +[COK1_H:BARON1] +Come ti ho detto, amigo, lavori per me. Adesso sta zitto. Un qualche giuda mi ha tradito. + +[COK1_I:BARON1] +Pensava non sapessi quanti soldi stessi facendo, ma rubarmi il 3% è come rubarmi il 100%. + +[COK1_J:BARON1] +Nessuno fa questo a me. NESSUNO!!! + +[COK1_K:BARON1] +Seguilo nel suo appartamento e scopri dove va! Successivamente, lo elimineremo noi. + +[COK1_1:BARON1] +Ommerda! + +[COK1_2:BARON1] +Troppo lento, nonnino! + +[COK1_4:BARON1] +Perdente. + +[COK1_5:BARON1] +Ti conviene continuare a correre, stronzo! + +[COK1_8:BARON1] +~g~Svelto! Recupera un mezzo e inseguilo! + +[COK1_9:BARON1] +~r~Dovevi inseguirlo, non ucciderlo! + +[COK1_10:BARON1] +~r~Entra nella casa del ladro e scopri dove ha nascosto il denaro. + +[COK1_11:BARON1] +~g~Dai un'occhiata da questa finestra. + +[COK1_7:BARON1] +~g~È scappato sul tetto: stagli dietro ma non ucciderlo! + +[COK1_G:BARON1] +Lavoro per soldi. + +{=================================== MISSION TABLE BARON2 ===================================} + +[COK2_A:BARON2] +Ma che fottuti incompetenti non siete? + +[COK2_B:BARON2] +IDIOTI! IDIOTI! IDIOTI! IDIOTI! + +[COK2_C:BARON2] +Tommy, + +[COK2_D:BARON2] +questi idioti... cercano sempre di fregarti. + +[COK2_E:BARON2] +È questo il problema in questo business. + +[COK2_F:BARON2] +Che cosa credi di fare? Aaaaah! + +[COK2_G:BARON2] +Questi stronzi hanno fallito miseramente. + +[COK2_H:BARON2] +Ben presto anche i papà e le mamme penseranno di poter spacciare coca in Vice City. + +[COK2_I:BARON2] +Chi sarà il prossimo adesso? La fottuta mafia? + +[COK2_J:BARON2] +La base della loro banda è una fortezza al piano terra, + +[COK2_K:BARON2] +per cui Quentin... Quentin! QUENTIN! + +[COK2_L:BARON2] +Lui ti porterà in volo sull'area! + +[COK2_M:BARON2] +Sradicateli! + +[COK2_N:BARON2] +Che cosa credi di fare? + +[COK2_O:BARON2] +Che cosa fai qua? + +[COK2_P:BARON2] +Ehi, ho chiesto in giro ed è ovvio + +[COK2_Q:BARON2] +che Diaz ha fatto saltare l'accordo e ha freddato mio fratello. + +[COK2_R:BARON2] +E ucciderà anche te! + +[COK2_S:BARON2] +Io posso far fuori Diaz! + +[COK2_T:BARON2] +No, ascoltami! Io mi occuperò di Diaz: + +[COK2_U:BARON2] +sta cominciando a fidarsi di me. + +[COK2_1:BARON2] +Ho una domanda, però: perché 'Quentin'? + +[COK2_2:BARON2] +Non lo so, mi è sempre piaciuto... Quentin Vance... + +[COK2_3:BARON2] +Vance? Tu sei Lance Vance? + +[COK2_4:BARON2] +Ehi, ne ho avuto abbastanza a scuola! + +[COK2_5:BARON2] +Hai mai provato a sparare con quelli da un elicottero? + +[COK2_8:BARON2] +Dove ci stiamo dirigendo? + +[COK2_9:BARON2] +Prawn Island. + +[COK2_13:BARON2] +Lance Vance. Brutto bastardo. + +[COK2_14:BARON2] +OK, siamo quasi arrivati. + +[COK2_15:BARON2] +Faremo un paio di passaggi. + +[COK2_16:BARON2] +Cerca di colpire il maggior numero di nemici possibile. + +[COK2_17:BARON2] +Poi ti scarico e dovrai cavartela da solo. + +[COK2_20:BARON2] +Merda! Siamo in una zona di guerra! Abbatti un po' di gente! + +[COK2_21:BARON2] +Stiamo sotto tiro, amico! + +[COK2_22:BARON2] +Questo affare è maledettamente costoso da riparare! Falli fuori! + +[COK2_23:BARON2] +OK, adesso dovrai cavartela da solo... Buona fortuna, fratello! + +[COK2_24:BARON2] +Condizioni elicottero: + +[COK2_25:BARON2] +~g~Recupera i soldi sul tetto. + +[COK2_27:BARON2] +Sei nel MIO territorio, stronzo! + +[COK2_28:BARON2] +Stai per affondare! + +[COK2_6:BARON2] +No. Farò un po' di pratica lungo la strada. + +[OPEN_B:BARON2] +Il blocco stradale per la terraferma è stato rimosso. + +[COK2_V:BARON2] +sta cominciando a fidarsi di me. + +{=================================== MISSION TABLE BARON3 ===================================} + +[COK3_A:BARON3] +Scommetto che adesso non vi divertite più! + +[COK3_B:BARON3] +Ah ah ah ah! + +[COK3_C:BARON3] +Woh! Fai attenzione a dove punti quel coso. + +[COK3_D:BARON3] +Basta con merda di piccione sulla MIA macchina, vero Tommy? + +[COK3_E:BARON3] +Direi di sì. + +[COK3_F:BARON3] +Hai proprio ragione. Adesso ascolta: + +[COK3_G:BARON3] +sai chi possiede la barca più veloce della costa est? + +[COK3_H:BARON3] +Non così su due piedi. + +[COK3_I:BARON3] +IO. E voglio che la situazione non cambi. + +[COK3_J:BARON3] +Ogni contrabbandiere da qui a Caracas ha un solo sogno: un'imbarcazione più veloce. + +[COK3_K:BARON3] +Si mormora che il cantiere Hull-o-Caust ha appena finito di costruirne una + +[COK3_L:BARON3] +per una testa di cazzo del Costa Rica. + +[COK3_M:BARON3] +E Tommy... IO VOGLIO QUELLA BARCA!!! + +[COK3_N:BARON3] +Credo che siano tornati i tuoi piccioni. + +[COK3_O:BARON3] +Ah! Pensavo di averti preso. Da dove salti fuori? + +[COK3_P:BARON3] +Piccioni! Boom! Ah ah ah! + +[COK3_5:BARON3] +~g~Trova l'interruttore per abbassare l'imbarcazione. + +[COK3_6:BARON3] +~g~Porta la barca alla villa di Diaz. + +[COK3_7:BARON3] +~r~Hai distrutto la barca! + +[COK3_8:BARON3] +~g~Raggiungi il cantiere al porto e ruba la barca più veloce. + +[COK3_9:BARON3] +~g~Adesso entra nella barca. + +{=================================== MISSION TABLE BARON4 ===================================} + +[COK4_A:BARON4] +Sputa fuori! CATORCIO DI PLASTICA! + +[COK4_B:BARON4] +Come osi farmi questo? + +[COK4_C:BARON4] +Chi credi di essere, brutto pezzo di merdosa plastica! Aaarrgh! + +[COK4_D:BARON4] +VAFFANCULO! + +[COK4_E:BARON4] +Se hai distrutto il mio film favorito di El Burro ti spacco! + +[COK4_F:BARON4] +Che altro posso fare? + +[COK4_G:BARON4] +Forse non è collegato... + +[COK4_H:BARON4] +Cosa? + +[COK4_I:BARON4] +Merda... non importa, ne posso comprare a centinaia. + +[COK4_J:BARON4] +Adesso Tommy, + +[COK4_K:BARON4] +ogni mese un freelance naviga fino a Vice City e ormeggia il suo yacht. + +[COK4_L:BARON4] +Vende il suo carico alla prima imbarcazione. + +[COK4_M:BARON4] +Voglio che tu prenda la barca più veloce, + +[COK4_N:BARON4] +batta tutti gli altri stronzi + +[COK4_O:BARON4] +e riporti qui il carico, OK? + +[COK4_P:BARON4] +Fammi indovinare, pensavi mi avrebbe fatto comodo un angelo custode? + +[COK4_Q:BARON4] +Vorrei solo che mi lasciassi venire, amico mio. + +[COK4_R:BARON4] +Puoi continuare con queste stronzate alla rambo, + +[COK4_S:BARON4] +ma sono sicuro che un giorno o l'altro ti salverò il culo. + +[COK4_T:BARON4] +e allora vorrai baciarmi! + +[COK4_U:BARON4] +Sei pazzo. + +[COK4_V:BARON4] +Ah ah ah! + +[COK4_1:BARON4] +Allora Tommy, sappiamo che è stato Diaz a far saltare il nostro affare... + +[COK4_3:BARON4] +Allora per quale motivo continuiamo a lavorare per lui? + +[COK4_4:BARON4] +Più cose scopriamo adesso, meno dovremo imparare quando ci impossesseremo di questa città! + +[COK4_5:BARON4] +Mi piace il tuo stile, amico. Molto fico. + +[COK4_12:BARON4] +Attento, stanno arrivando da tutte le parti! + +[COK4_13:BARON4] +Preso! Torniamo da Diaz il più velocemente possibile! + +[COK4_14:BARON4] +Ne vuoi un po'? + +[COK4_15:BARON4] +Salutami i pesci! + +[COK4_16:BARON4] +Prendi questo! E questo! + +[COK4_19:BARON4] +Altri guai in arrivo! + +[COK4_20:BARON4] +Ci stanno sparando da quel molo! + +[COK4_24:BARON4] +Bel colpo, amico. Sei un vero pazzo di prima classe. + +[COK4_25:BARON4] +Beh, grazie. + +[COK4_26:BARON4] +Ci vediamo, Tommy. + +[COK4_27:BARON4] +OK, Mr. Lance Vance Dance. + +[COK4_28:BARON4] +~g~Raggiungi lo yacht prima delle altre imbarcazioni! + +[COK4_31:BARON4] +~g~Raggiungi la barca più veloce attraccata al molo! + +[COK4_32:BARON4] +~r~Troppo lento! + +[COK4_33:BARON4] +~r~Hai distrutto la barca! + +[COK4_34:BARON4] +~g~Affonda quelle imbarcazioni! + +[COK4_35:BARON4] +Condizioni barca: + +{=================================== MISSION TABLE BARON5 ===================================} + +[PROP_A:BARON5] +PROPRIETÀ ACQUISITA! + +[COK4_30:BARON5] +~r~Lance è morto! + +[ASS1_A:BARON5] +Ho messo un po' di cannoni nel bagagliaio. + +[ASS1_B:BARON5] +Accidenti! Dove hai trovato tutta questa roba? + +[ASS1_C:BARON5] +La tenevo da parte per una giornata di pioggia. + +[ASS1_D:BARON5] +Ti piace? + +[ASS1_E:BARON5] +Sì, mi piace. + +[ASS1_F:BARON5] +Brutto idiota... + +[ASS1_G:BARON5] +La mia bellissima casa... + +[ASS1_H:BARON5] +Guarda cosa hai combinato! + +[ASS1_I:BARON5] +Questo è per mio fratello! + +[ASS1_J:BARON5] +Mi fidavo di te, Tommy! + +[ASS1_K:BARON5] +Ti avrei fatto diventare... + +[ASS1_L:BARON5] +Buona notte, Mr. Diaz. + +[ASS1_1:BARON5] +Questo posto sarà zeppo di stronzi... fai attenzione... + +[ASS1_2:BARON5] +Non preoccuparti, Tommy, ti copro io. + +[ASS1_4:BARON5] +Diaz deve essere qui dentro! + +[ASS1_13:BARON5] +DIAZ? Sono venuto a prendermi il tuo business! + +[ASS1_14:BARON5] +TOMMY! Mi hai tradito... Idiota! Ti ucciderò fra un attimo! + +[ASS1_16:BARON5] +~g~Uccidi Diaz! + +[BUD1:BARON5] +Lance + +[ASS1_18:BARON5] +~g~La porta è chiusa: trova un percorso alternativo. + +[ASS1_19:BARON5] +Di qua! + +[ASS1_20:BARON5] +Tommy, ho un problema con Quentin, non con te! + +{=================================== MISSION TABLE BIKE1 ===================================} + +[BM1_A:BIKE1] +Dov'è Baker? + +[BM1_B:BIKE1] +Sto cercando Big Mitch Baker... + +[BM1_C:BIKE1] +Chi lo cerca? + +[BM1_D:BIKE1] +Tommy Vercetti. + +[BM1_E:BIKE1] +Vercetti. + +[BM1_F:BIKE1] +Non mi sembri della polizia, il che ti ha fatto guadagnare un minuto. + +[BM1_G:BIKE1] +Ti conviene parlare in fretta. + +[BM1_H:BIKE1] +Kent Paul mi ha detto che saresti stato interessato a occuparti della sorveglianza di uno spettacolo che sta allestendo. + +[BM1_I:BIKE1] +Kent Paul? Sheesh! Non mi stupisce abbia mandato te. + +[BM1_J:BIKE1] +L'ultima volta che è stato qui è uscito dalla finestra con il vestitino che gli hanno dato alla nascita. + +[BM1_K:BIKE1] +Sei interessato o no? + +[BM1_L:BIKE1] +Facciamo solo favori tra compagni. + +[BM1_M:BIKE1] +Come faccio a diventarlo? + +[BM1_N:BIKE1] +Non è uno circolo sportivo, amico. Sai guidare una moto? + +[BM1_O:BIKE1] +Sai sederti su uno sgabello e bere birra? + +[BM1_P:BIKE1] +Cougar, Zeppelin, scoprite se questa ragazzina sa portare una moto... + +[BM1_2:BIKE1] +~g~Hai bisogno di una Freeway o di un'Angel per gareggiare! + +[BM1_3:BIKE1] +~r~I partecipanti sono stati attaccati! + +[BIKE1_1:BIKE1] +Bene, bei vestiti. Vediamo cosa sai fare. + +[BM1_1:BIKE1] +~g~Recupera una Freeway o un'Angel e posizionati sulla griglia di partenza. + +{=================================== MISSION TABLE BIKE2 ===================================} + +[BM2_1:BIKE2] +CAOS: + +[BM2_A:BIKE2] +Ah ah ah, ti ho preso di nuovo! + +[BM2_B:BIKE2] +Ehi, Vercetti. + +[BM2_C:BIKE2] +Cougar ha detto che sai portare la moto come si deve. + +[BM2_D:BIKE2] +Già, quanti altri giretti devo ancora fare? + +[BM2_E:BIKE2] +Sono un uomo molto impegnato. + +[BM2_F:BIKE2] +Se serve una bella scazzottata per mettere le cose a posto, dillo chiaramente. + +[BM2_G:BIKE2] +Essere uno di noi non significa solo azzuffarsi. Si tratta di far parte di una famiglia. + +[BM2_H:BIKE2] +Sì, sono già stato parte di una famiglia. Non ha funzionato. + +[BM2_I:BIKE2] +Sì, certo, ma questa famiglia si occupa dei suoi membri. + +[BM2_J:BIKE2] +Non chiediamo a uno di fare il lavoro sporco per poi lasciarlo marcire quindici anni ai lavori forzati. + +[BM2_K:BIKE2] +Sì, esatto. Ho fatto la mia parte. + +[BM2_L:BIKE2] +Questa è la più grande famiglia di disadattati, reietti e derelitti. + +[BM2_M:BIKE2] +Merda, alcuni di noi sono stati persino traditi dalla propria nazione. + +[BM2_N:BIKE2] +Sono stato imprigionato durante il Vietnam. Brutta storia. + +[BM2_O:BIKE2] +Ed è per questo che ti chiedo di far bollire la pentola. + +[BM2_P:BIKE2] +Questa fottuta nazione ha bisogno di un bel calcio nel culo, e saremo noi a darglielo. + +[BM2_Q:BIKE2] +Per cui esci, prendi una moto e mostra a questa città quanto siamo arrabbiati! + +[BM2_R:BIKE2] +Va bene. + +[BM2_2:BIKE2] +~g~Devi riempire l'indicatore del caos entro il tempo stabilito per mostrare quanto sei arrabbiato! + +[BM2_3:BIKE2] +~g~Questo suono indica che hai riempito parte dell'indicatore, continua così. + +[BM2_4:BIKE2] +~r~Non sei riuscito a riempire l'indicatore del caos in tempo! + +{=================================== MISSION TABLE BIKE3 ===================================} + +[BM3_A:BIKE3] +Ehi, ciao Mitch. + +[BM3_B:BIKE3] +Accidenti Vercetti, hai davvero le palle. + +[BM3_C:BIKE3] +Adesso voglio vedere se sei pronto a combattere per la tua famiglia. + +[BM3_D:BIKE3] +Una gang locale ha fatto l'errore di rubare il mio mezzo... + +[BM3_E:BIKE3] +probabilmente come prova di coraggio o qualcosa del genere. + +[BM3_F:BIKE3] +Io e i miei uomini stavamo per andare a dar loro una lezione sul rispetto. + +[BM3_G:BIKE3] +Comunque, + +[BM3_H:BIKE3] +ho pensato che sarebbe stata un'ottima occasione per la tua iniziazione. + +[BM3_I:BIKE3] +Riportami indietro la mia moto e potrai dire a Paul che avrà la sua sorveglianza. + +[BM3_2:BIKE3] +~r~Dovevi riportare indietro la moto, non distruggerla! + +[BM3_3:BIKE3] +~g~Riporta la moto al bar! + +[BM3_4:BIKE3] +~g~Sali sulla moto! + +[INTRUDE:BIKE3] +~g~Ti hanno visto! + +[BM3_6:BIKE3] +~g~Si sono rintanati dietro Ammu-Nation in Downtown. + +[BM3_7:BIKE3] +~g~Avrai bisogno di una moto più veloce per accedere al tetto. + +[BM3_8:BIKE3] +~g~Usa la moto per saltare da queste scale fino al tetto nella parte più lontana della strada. + +[BM3_1:BIKE3] +~g~Una gang locale ha rubato la Angel di Mitch Baker: riportala indietro! + +[BM3_9:BIKE3] +~g~Recupera l'Angel di Mitch Baker e vattene! + +{=================================== MISSION TABLE BMX_1 ===================================} + +[BMX_REC:BMX_1] +~g~Nuovo record stabilito: ~1~!! + +[GETBIK2:BMX_1] +Hai ~1~ secondi per salire su una Dirt Bike! + +{=================================== MISSION TABLE BOATBUY ===================================} + +[DRUG_1:BOATBUY] +Salve? Saaalve? Salve? + +[DRUG_2:BOATBUY] +Tranquillo amico, sono qua. + +[DRUG_3:BOATBUY] +Ehi, damerino! Dimmi, sei il nuovo proprietario? + +[DRUG_4:BOATBUY] +Sì. Qual è la barca più veloce? + +[DRUG_5:BOATBUY] +È già in acqua, amico, + +[DRUG_6:BOATBUY] +pensavo che la volessi provare. + +[DRUG_7:BOATBUY] +Amico, ha già sopra un bel motore da 300 cavalli... + +[DRUG_8:BOATBUY] +...e la struttura in vetroresina: vola davvero sull'acqua! + +[DRUG_9:BOATBUY] +Può andare da zero a sessanta in quattro secondi spaccati... + +[DRUG_10:BOATBUY] +e può tenere venti pacchi della migliore erba giamaicana nella stiva! + +[DRUG_11:BOATBUY] +Dacci dentro, amico, è pronta a volare! + +[DRUG_12:BOATBUY] +Yo yo, uh, ehi amico, hai da accendere? + +[DRUG_13:BOATBUY] +Amico? + +{=================================== MISSION TABLE CAP_1 ===================================} + +[CAP1_B1:CAP_1] +~g~La Mafia sta tassando il tuo business. Trovali e falli fuori. + +[CAP1_B2:CAP_1] +~g~La Mafia ha tassato il tuo cantiere navale! + +[CAP1_B3:CAP_1] +~g~La Mafia ha tassato la tua fabbrica di gelato! + +[CAP1_B4:CAP_1] +~g~La Mafia ha tassato la concessionaria! + +[CAP1_B5:CAP_1] +~g~La Mafia ha tassato la tua compagnia di taxi! + +[CAP_01:CAP_1] +OK, qual è l'emergenza? + +[CAP_02:CAP_1] +CHI? + +[CAP_03:CAP_1] +Tommy... dei maledetti mafiosi... dicevano di essere venuti a prendere la loro fetta... + +[CAP_04:CAP_1] +...dicevano che erano soldi di Mr. Forello... mi sento di merda. + +[CAP_05:CAP_1] +Forelli? SONNY Forelli? + +[CAP_06:CAP_1] +Sì, quel nome... credo... hanno davvero insistito... + +[CAP_07:CAP_1] +Non preoccuparti amico, non sono arrabbiato con te. + +[CAP_08:CAP_1] +Portalo all'ospedale. + +[CAP_09:CAP_1] +Tommy... fai a quel tipo un nuovo buco da cui cagare... + +[CAP_10:CAP_1] +Ho intenzione di fargliene due. + +[CAP1_2:CAP_1] +Conosci le regole, Vercetti! + +[CAP1_3:CAP_1] +Mr. Forelli manda i suoi saluti! + +[CAP1_4:CAP_1] +È il macellaio di Harwood! + +[CAP1_5:CAP_1] +Dì a Sonny di stare alla larga! + +[CAP1_6:CAP_1] +Vice City è MIA adesso, NON sua. + +[CAP1_7:CAP_1] +Pensi di potermi tagliar fuori, Vercetti? + +[CAP1_8:CAP_1] +Ti inseguiremo fino a farti fuori, Vercetti. + +[CAP1_9:CAP_1] +Non hai nessuna possibilità, brutto cazzone psicopatico. + +[CAP1_10:CAP_1] +Ti ucciderò, Vercetti. + +[CAP1_11:CAP_1] +Sei sempre stato uno stronzo. + +[CAP1_12:CAP_1] +Ti ammazzo, Vercetti. + +[CAP1_B6:CAP_1] +~g~Hai trovato l'esattore: uccidilo! + +[CAP1_B7:CAP_1] +~g~Hai perso l'esattore. + +[CAP1_B8:CAP_1] +~r~L'esattore ha tassato tutte le tue proprietà. + +[CAP1_B9:CAP_1] +~g~La Mafia ha tassato il Malibu! + +[CAP1_B0:CAP_1] +~g~La Mafia ha tassato lo studio cinematografico! + +[CAP1_C2:CAP_1] +~g~La Mafia è arrivata al cantiere navale! + +[CAP1_C3:CAP_1] +~g~La Mafia è arrivata alla fabbrica di gelato! + +[CAP1_C4:CAP_1] +~g~La Mafia è arrivata al concessionario! + +[CAP1_C5:CAP_1] +~g~La Mafia è arrivata alla compagnia di taxi! + +[CAP1_C9:CAP_1] +~g~La Mafia è arrivata al Malibu! + +[CAP1_C0:CAP_1] +~g~La Mafia è arrivata allo studio cinematografico! + +[CAP1_D2:CAP_1] +~g~La Mafia sta abbandonando il cantiere navale! + +[CAP1_D3:CAP_1] +~g~La Mafia sta abbandonando la fabbrica di gelato! + +[CAP1_D4:CAP_1] +~g~La Mafia sta abbandonando il concessionario! + +[CAP1_D5:CAP_1] +~g~La Mafia sta abbandonando la compagnia di taxi! + +[CAP1_D9:CAP_1] +~g~La Mafia sta abbandonando il Malibu! + +[CAP1_D0:CAP_1] +~g~La Mafia sta abbandonando lo studio cinematografico! + +[CAP1B10:CAP_1] +Hai beccato gli esattori. Altri stanno arrivando. + +{=================================== MISSION TABLE CARBUY ===================================} + +[CAR1_1:CARBUY] +B.J. Smith. E tu devi essere Mr. Vercetti. + +[CAR1_2:CARBUY] +Ti andrebbe un giro? + +[CAR1_3:CARBUY] +Perché no. + +[CAR1_4:CARBUY] +Bene, mi dispiace un po' vendere il concessionario. + +[CAR1_5:CARBUY] +È stato il mio primo investimento da quando mi sono messo in proprio. + +[CAR1_6:CARBUY] +Ma adesso è giunta l'ora di andare avanti. + +[CAR1_7:CARBUY] +Lasci la città? + +[CAR1_8:CARBUY] +Non troppo in fretta, spero? + +[CAR1_9:CARBUY] +No. Sto per prepararmi al pensionamento e ai miei futuri investimenti. + +[CAR1_10:CARBUY] +Il mercato non tirava molto, + +[CAR1_11:CARBUY] +e la mia squadra si è preoccupata personalmente di diventare + +[CAR1_12:CARBUY] +più creativa nella produzione di contanti. + +[CAR1_13:CARBUY] +Logicamente, potrei smantellare l'organizzazione prima di vendere. + +[CAR1_14:CARBUY] +Diamine, potrei radere al suolo tutto quanto se volessi. + +[CAR1_15:CARBUY] +Quest'area è in pieno sviluppo. + +[CAR1_16:CARBUY] +Oh, non mi preoccuperei di ciò. + +[CAR1_17:CARBUY] +Il posto mi sembra perfetto. + +[CAR1_18:CARBUY] +Sì, lo è, allora siamo d'accordo? + +{=================================== MISSION TABLE CARPAR1 ===================================} + +[MM_1_A:CARPAR1] +~g~Attraversa ~y~5 punti di controllo ~r~SENZA~g~ abbattere i ~r~CONI~g~! ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[CONE_1:CARPAR1] +~r~Hai abbattuto un cono! + +[MM_1_C:CARPAR1] +~y~ATTRAVERSA~g~ un punto di controllo per attivare il TIMER. ~g~Ogni punto di controllo ti fornirà ~y~~1~ SECONDI~g~ extra. + +{=================================== MISSION TABLE COPCAR ===================================} + +[KILLS:COPCAR] +UCCISIONI: + +[C_BREIF:COPCAR] +~g~Sospettato visto in prossimità dell'area ~a~. + +[C_PASS:COPCAR] +MINACCIA ELIMINATA: ~1~$ + +[COPCART:COPCAR] +~g~Hai ~1~ secondi per tornare a un veicolo della polizia prima che la missione abbia termine. + +[C_CANC:COPCAR] +~r~Missione Vigilante annullata! + +[C_TIME:COPCAR] +~r~È scaduto il tuo tempo come tutore della legge! + +[C_COMP1:COPCAR] +Missione Vigilante livello 12 completata: valore massimo dell'armatura aumentato a 150. + +[CLEVEL:COPCAR] +Missione Vigilante livello ~1~ + +{=================================== MISSION TABLE COUNT1 ===================================} + +[CM1_A:COUNT1] +Mr. Vercetti? Ehi, hai comprato la vecchia tipografia? + +[CM1_B:COUNT1] +Sì, il mio vecchio un tempo lavorava nel campo. + +[CM1_C:COUNT1] +Avrei voluto seguire le sue orme, ma... ho deciso di vivere diversamente. + +[CM1_D:COUNT1] +Stai progettando di vendere il vecchio macchinario smontandolo in pezzi? + +[CM1_E:COUNT1] +Ho pensato che potremmo stampare ancora qualcosa... un giornale, una rivista... + +[CM1_F:COUNT1] +Che merda, figliolo, roba di bassa qualità. Ho sempre sognato stampare soldi. Non è poi così difficile. + +[CM1_G:COUNT1] +Sai, l'ho fatto su scala ridotta per anni. + +[CM1_H:COUNT1] +Davvero? + +[CM1_I:COUNT1] +Certo. Ma prima abbiamo bisogno di matrici di buona qualità. + +[CM1_J:COUNT1] +Ma certo! C'è già un'organizzazione di falsificatori in azione in Florida. + +[CM1_K:COUNT1] +Un'organizzazione? + +[CM1_L:COUNT1] +Sì. Beh, ho sentito solo delle voci. + +[CM1_M:COUNT1] +Conosco un tipo che si intende di voci... + +[CM1_N:COUNT1] +Un tempo passavo le serate con lui, mentre pulivo i rulli... + +[CM1_2A:COUNT1] +Accidenti che culo incredibile! + +[CM1_2B:COUNT1] +Va bene, bellezza, sei tu che ci perdi! + +[CM1_2C:COUNT1] +Ehi, ciao amico, come butta? + +[CM1_2D:COUNT1] +Che cosa sai della falsificazione? + +[CM1_2E:COUNT1] +Oh, sto bene Paul, e a te? + +[CM1_2F:COUNT1] +Vieni qua. + +[CM1_2G:COUNT1] +Va bene! Va bene! Va bene! Sei ovviamente un tipo impegnato. + +[CM1_2H:COUNT1] +Tutto ciò che so sul campo è che la Triade fornisce le matrici. + +[CM1_2I:COUNT1] +Hanno una società di spedizioni nel porto, + +[CM1_2J:COUNT1] +il capo di certo saprà quando passeranno nuovamente delle matrici! + +[CM1_2K:COUNT1] +Grazie Paul. + +[CM1_2L:COUNT1] +Qual è il tuo problema, sei un maniaco! + +[CM1_2M:COUNT1] +Dammi un altro drink, in fretta! + +[CM1_3:COUNT1] +~g~Ti hanno visto! + +[CM1_5:COUNT1] +~g~Incontrati con Kent Paul al Malibu Club! + +[CNT1_1:COUNT1] +Chi sei? Oooof! Aaiieee! Non il viso! Non il viso! + +[CM1_1:COUNT1] +~g~Vai alla compagnia marittima Chartered Libertine nel porto. + +[CM1_2:COUNT1] +~g~L'ufficiale delle spedizioni è in possesso delle informazioni che ti servono. + +[CNT1_2:COUNT1] +OK, parlerò, parlerò! + +[CM1_6:COUNT1] +~g~Ritorna con le informazioni alla tipografia! + +{=================================== MISSION TABLE COUNT2 ===================================} + +[CNT2_B1:COUNT2] +Bene, il corriere porterà le matrici al porto oggi stesso. + +[CNT2_B2:COUNT2] +Ho intenzione di intercettare il corriere, recuperare le matrici, far perdere le tracce e tornare qua. + +[CNT2_B3:COUNT2] +Bene. A seconda di quanto bene vadano le cose, + +[CNT2_B4:COUNT2] +potremmo avere cinque minuti per stampare soldi prima che l'associazione dei falsificatori ci trovi, o potremmo avere tutto l'anno. + +[CNT2_B5:COUNT2] +In ogni caso, voglio veder bigliettoni verdi uscire cinque minuti dopo che sarò tornato. Tutto chiaro? + +[CNT2_B6:COUNT2] +Non preoccuparti, Tommy. Saremo pronti. + +[CNT2_B7:COUNT2] +Io e i ragazzi saremo in giro per il quartiere se mai dovessi avere bisogno di una mano con gli inseguitori. + +[CNT2_B8:COUNT2] +Ottimo: siete tutti pronti? Perfetto. Ci vediamo più tardi... + +[CNT2_01:COUNT2] +~g~Il ~r~corriere~g~ con le matrici arriverà fra pochi attimi al ~y~porto~g~ in elicottero. + +[CNT2_02:COUNT2] +~r~Il corriere è scappato con l'elicottero. + +[CNT2_03:COUNT2] +~r~Il corriere è arrivato a destinazione sano e salvo. Sei in ritardo! + +[CNT2_04:COUNT2] +~r~Hai distrutto le matrici nell'esplosione! + +[CNT2_05:COUNT2] +~g~Hai preso le matrici. Riportale alla tipografia. + +[CNT2_06:COUNT2] +~g~Il corriere è morto e ha lasciato cadere le matrici: raccoglile prima che lo faccia qualcun altro. + +[CNT2_07:COUNT2] +~g~Una delle guardie ha raccolto le matrici: non lasciarlo scappare! + +[CNT2_08:COUNT2] +~g~Il ~r~corriere~g~ con le matrici è arrivato al porto. + +[CNT2_4:COUNT2] +Faccende private, non sei il ben venuto. + +[CNT2_09:COUNT2] +BENI TIPOGRAFIA ACQUISITI + +[CNT2_10:COUNT2] +~g~La tipografia d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +[CNT2_11:COUNT2] +~r~Le matrici adesso sono in fondo all'oceano! + +{=================================== MISSION TABLE CUBAN1 ===================================} + +[CUB1_A:CUBAN1] +Sì amico? + +[CUB1_B:CUBAN1] +Ehi, tranquillo papà, è venuto per me. Sei tu l'uomo? + +[CUB1_C:CUBAN1] +Oh sì, sei tu l'uomo. Credo per lo meno, vero? + +[CUB1_D:CUBAN1] +No, non credo. + +[CUB1_E:CUBAN1] +Ah sì? Vieni qua, osso duro. + +[CUB1_F:CUBAN1] +Pensi di potermi prendere in giro? + +[CUB1_G:CUBAN1] +Credi di poter fare lo stupido con me? + +[CUB1_H:CUBAN1] +No, credo tu stia facendo abbastanza lo stupido per entrambi. + +[CUB1_I:CUBAN1] +Ehi figliolo, ha detto che sei stupido. + +[CUB1_J:CUBAN1] +E io lo chiamo femminuccia, papà. + +[CUB1_K:CUBAN1] +Guarda un po' come va in giro vestito. + +[CUB1_L:CUBAN1] +Che cos'è questa? L'ultima moda femminile per la notte? + +[CUB1_M:CUBAN1] +Perché un osso duro come te si veste da donnina? + +[CUB1_N:CUBAN1] +Hai le mutandine come le signorine, eh? + +[CUB1_O:CUBAN1] +Cos'hai contro le donne? Preferisci gli uomini, amico? + +[CUB1_P:CUBAN1] +Mi piacciono le donne! Tutte le donne! Adoro mia madre, chico. + +[CUB1_Q:CUBAN1] +Va bene, va bene, ti credo sulla parola. + +[CUB1_R:CUBAN1] +Sai guidare, amigo? + +[CUB1_S:CUBAN1] +Certo... come una donna. + +[CUB1_T:CUBAN1] +Molto divertente. Mi piaci, ragazzo. Forse ci puoi aiutare. + +[CUB1_U:CUBAN1] +Potresti provare di essere un uomo. Huh? + +[CUB1_V:CUBAN1] +Porta fuori la barca. + +[CUB1_W:CUBAN1] +Mostra di avere due grossi cojones, + +[CUB1_X:CUBAN1] +e non piccoli minuscoli chiquita. + +[CUB1_02:CUBAN1] +OK amico, trattala come una vera signora. + +[CUB1_03:CUBAN1] +Non male, sei un vero uomo. + +[CUB1_04:CUBAN1] +Accidenti, hai davvero due grossi cojones, amigo. + +[CUB1_05:CUBAN1] +Amigo, sei un vero uomo. + +[CUB1_06:CUBAN1] +E tu ti reputi un uomo? + +[CUB1_07:CUBAN1] +Sei un piccolo gattino impaurito, bamboccio: vai a piangere dalla mamma! + +[CUB1_08:CUBAN1] +Sei un grande spreco di spazio. Cammini come un uomo, parli come un uomo, ma guidi come un idiota. + +[CUB1_09:CUBAN1] +Amico, sei tu l'uomo. Mi piaci, amico. Mi piaci molto. + +[CUB1_10:CUBAN1] +A qualsiasi ora, amico, perché tu hai i cojones e tutti i miei amici hanno grandi cajones. + +[CUB1_11:CUBAN1] +~r~Hai ucciso Rico! + +[CUB1_12:CUBAN1] +Passa per il primo punto di controllo per cominciare il test. + +[CUB1_14:CUBAN1] +Torna alla barca! + +[CUB1_15:CUBAN1] +~r~Sei troppo lento, amico. + +[CUB1_01:CUBAN1] +Ehi, sono Rico. Sei tu l'uomo con i grandi cojones? + +[CUB1_13:CUBAN1] +~g~Hai tre minuti per completare il percorso. + +{=================================== MISSION TABLE CUBAN2 ===================================} + +[CUB2_A:CUBAN2] +Un cafecito, por favor, Alberto... + +[CUB2_N:CUBAN2] +Nessun problema, Tommy. + +[CUB2_B:CUBAN2] +Papà! Un gran problema! + +[CUB2_O:CUBAN2] +Umberto, figlio mio, cosa succede? + +[CUB2_C:CUBAN2] +Gli Haitiani! Odio quei maledetti Haitiani! + +[CUB2_D:CUBAN2] +Hanno osato crearmi problemi per l'ultima vota! + +[CUB2_E:CUBAN2] +Questi Haitiani. Facciamoli fuori! + +[CUB2_F:CUBAN2] +Avremo bisogno di supporto. + +[CUB2_G:CUBAN2] +Ho già perso degli hermanos là fuori. + +[CUB2_H:CUBAN2] +Amigo, tu guidi bene! + +[CUB2_I:CUBAN2] +Per una donna, giusto? + +[CUB2_J:CUBAN2] +Non è il momento degli scherzi! + +[CUB2_K:CUBAN2] +Forza, guida nuovamente per me! + +[CUB2_L:CUBAN2] +Porta i miei ragazzi in posizione e faremo fuori quegli Haitiani! + +[CUB2_M:CUBAN2] +Si sono messi contro me, hanno scherzato con il pezzo grosso della città! + +[CUB2_01:CUBAN2] +Troppo piccola, amico, devi prendere una macchina più grande. + +[CUB2_02:CUBAN2] +Abbiamo bisogno di rinforzi dal café! + +[CUB2_03:CUBAN2] +~g~Prendi una macchina e recupera i Cubani di fronte al café Robina. + +[CUB2_04:CUBAN2] +~g~Scarica i Cubani sul luogo dello scontro. + +[CUB2_05:CUBAN2] +Fai fuori quel codardo di un cecchino! + +[CUB2_07:CUBAN2] +Combattono come donnicciole! Mettetevi al riparo! + +[CUB2_09:CUBAN2] +Cecchino sul tetto! + +[CUB2_11:CUBAN2] +~r~Idiota, avevamo bisogno della macchina. + +[CUB2_12:CUBAN2] +Ehi amigo! Sono felice che ce l'hai fatta. + +[CUB2_13:CUBAN2] +Covo puzzolente di Haitiani, li uccideremo tutti! + +[CUB2_14:CUBAN2] +CAAARICAAAA! + +[CUB2_15:CUBAN2] +Adesso, fratelli, CAAARICAAAA! + +[CUB2_16:CUBAN2] +Tommy, abbiamo provato il nostro coraggio! + +[CUB2_17:CUBAN2] +Rubiamo quel van pieno di droga e scappiamo in fretta! + +[CUB2_18:CUBAN2] +~g~Recupera una macchina per caricare i Cubani. + +[CUB2_19:CUBAN2] +Combatteremo come uomini! + +[CUB2_21:CUBAN2] +Combattiamo come uomini con grandi cojones! + +[CUB2_22:CUBAN2] +~g~Elimina il resto degli Haitiani così i Cubani potranno avanzare. + +[CUB2_23:CUBAN2] +~g~Little Haiti sarà piena di Haitiani desiderosi di pareggiare il conto con i Cubani. Guardati la schiena. + +[CUB2_24:CUBAN2] +~g~Torna al Café Robina con il van e parcheggialo sul retro. + +[CUB2_25:CUBAN2] +UCCIDI TUTTI GLI HAITIANI!!! + +{=================================== MISSION TABLE CUBAN3 ===================================} + +[CUB3_A:CUBAN3] +Alberto. Un caffè, senor. + +[CUB3_B:CUBAN3] +Papà, non servire questo serpente nascosto nella paglia. + +[CUB3_C:CUBAN3] +Sei un doppiogiochista, Tommy! + +[CUB3_D:CUBAN3] +O fai il doppio gioco o sei un perdente, ragazzo! + +[CUB3_E:CUBAN3] +Gli Haitiani! Merda, stanno ridendo di me. + +[CUB3_F:CUBAN3] +Ehi. Calmati. Qual è il tuo problema? + +[CUB3_G:CUBAN3] +Ridono di me, Tommy! Di me! + +[CUB3_H:CUBAN3] +Umberto Robina! Fanno tutto ciò che vogliono! + +[CUB3_I:CUBAN3] +Nessuno fa ciò che vuole, Umberto, fanno ciò che tu permetti loro di fare. + +[CUB3_J:CUBAN3] +Cosa? + +[CUB3_K:CUBAN3] +Vuoi che mi occupi di qualcuno? + +[CUB3_L:CUBAN3] +Posso pensarci io, ma ti costerà. + +[CUB3_M:CUBAN3] +Lo so che siamo come fratelli, ma stiamo parlando di affari. + +[CUB3_N:CUBAN3] +Tommy, sei un vero uomo. Un vero businessman, un gentleman. + +[CUB3_O:CUBAN3] +Questi Haitiani. Hanno un carico di merce in arrivo via mare, roba di prima qualità. + +[CUB3_P:CUBAN3] +La prendiamo e li facciamo fuori. + +[CUB3_Q:CUBAN3] +Tu la prendi, io ti copro. Come mio fratello, come mio figlio. + +[CUB3_R:CUBAN3] +Preferisco i soldi che saltellare sulle tue gambe, amigo. + +[CUB3_01:CUBAN3] +Ehi Rico, bella barca. Sei pronto? + +[CUB3_03:CUBAN3] +~g~Raccogli tutte le valigette piene di soldi e droga. + +[CUB3_04:CUBAN3] +~g~Riporta la droga e i soldi a Umberto. + +[CUB3_05:CUBAN3] +Sì Tommy. Vedi di sparare come si deve oggi, + +[CUB3_06:CUBAN3] +non voglio riempire la mia barca di buchi, OK? + +[CUB3_07:CUBAN3] +~g~Incontrati con Rico, lui ti porterà fino alla locazione dell'incontro. + +[CUB3_02:CUBAN3] +~g~UCCIDI TUTTI GLI HAITIANI SULLE BARCHE!!! + +[CUB3_08:CUBAN3] +Uh oh... un gruppo di Cubani. Siamo sotto attacco! + +{=================================== MISSION TABLE CUBAN4 ===================================} + +[CUB4_A:CUBAN4] +Ehi signorine, sapete che cosa voglio fare? + +[CUB4_B:CUBAN4] +Voglio far fuori un Haitiano. E poi? + +[CUB4_C:CUBAN4] +Poi voglio fare l'amore come un vero uomo. + +[CUB4_D:CUBAN4] +Capito chica? Proprio così. + +[CUB4_E:CUBAN4] +Fallito! + +[CUB4_F:CUBAN4] +Cafone. + +[CUB4_G:CUBAN4] +Ehi bella, non ti toccherei neanche con un bastone da lontano! + +[CUB4_H:CUBAN4] +Umberto Robina, a lui piacciono le vere donne! Non delle capre in minigonna! + +[CUB4_I:CUBAN4] +Tommy! Tommy, ti voglio bene, davvero! Andiamo! + +[CUB4_J:CUBAN4] +Dove? Non posso prendere prima una tazza di caffè? + +[CUB4_K:CUBAN4] +Non c'è tempo per il caffè! Inoltre, io l'ho già preso. + +[CUB4_L:CUBAN4] +Dobbiamo far fuori gli Haitiani. + +[CUB4_M:CUBAN4] +Tommy, come fai a uccidere un serpente? + +[CUB4_N:CUBAN4] +Lo mordi sul culo! Ah ah ah! + +[CUB4_O:CUBAN4] +Se lo dici te, Umberto. + +[CUB4_P:CUBAN4] +Tommy, recupera una piccola auto Haitiana. + +[CUB4_Q:CUBAN4] +Quando ce l'hai, torna qui e carica il mio ragazzo, + +[CUB4_R:CUBAN4] +Pepe, e portalo fino agli Haitiani. + +[CUB4_S:CUBAN4] +Poi aggiri l'impianto di lavorazione degli Haitiani e utilizzi il solvente come esplosivo. + +[CUB4_T:CUBAN4] +Boom! Bye bye! + +[CUB4_U:CUBAN4] +Umberto, e tu? + +[CUB4_V:CUBAN4] +Oh, io resto nelle retrovie e controllo il café con papà. + +[CUB4_W:CUBAN4] +Non si sente molto bene, sai? + +[CUB4_02:CUBAN4] +~g~Le bombe verranno piazzate con un timer di 45 secondi. + +[CUB4_04:CUBAN4] +~r~Hai allarmato la base, adesso non riusciremo mai a entrare! + +[CUB4_07:CUBAN4] +Il solvente è sul retro, amigo. + +[CUB4_08:CUBAN4] +Hola, Amigos. + +[CUB4_09:CUBAN4] +Bueno. Haitiane Putas. Muerte. + +[CUB4_10:CUBAN4] +Vamos. + +[CUB4_11:CUBAN4] +Vamos davvero. + +[CUB4_12:CUBAN4] +Ehi, abbiamo bisogno di un'auto degli Haitiani! + +[CUB4_13:CUBAN4] +Oye, andiamo a trovare i nostri muchachos! + +[CUB4_14:CUBAN4] +Seguimi, compadres. + +[CUB4_15:CUBAN4] +OK, vai dentro... + +[CUB4_16:CUBAN4] +Io posizionerò le bombe, coprimi! + +[CUB4_17:CUBAN4] +CORRI! + +[CUB4_18:CUBAN4] +Amico, questa sì che è una bella parte della città... + +[CUB4_19:CUBAN4] +Questo luogo è una fogna. + +[CUB4_20:CUBAN4] +Avevo una bella donna... viveva da queste parti. + +[CUB4_21:CUBAN4] +Lo sai, fanno delle buone pizze da queste parti. + +[CUB4_22:CUBAN4] +Whoa, amico. Guidi come un cavallo impazzito! + +[CUB4_23:CUBAN4] +Ti sei perso, amico? + +[CUB4_24:CUBAN4] +Hai lasciato indietro Pepe, vai a riprenderlo! + +[CUB4_03:CUBAN4] +~g~Resta in macchina fino a quando questa non sarà parcheggiata all'interno. + +[CUB4_26:CUBAN4] +~g~Prendi Pepe, dirigiti a nord verso Little Haiti e ruba una macchina Voodoo. + +[CUB4_27:CUBAN4] +~g~Raggiungi Rico e gli altri Cubani. + +[CUB4_28:CUBAN4] +~g~Unisciti agli altri Cubani presso la fabbrica di droga Haitiana. + +[CUB4_29:CUBAN4] +~g~Cammina su ogni segnalino per posizionare una bomba. + +[CUB4_30:CUBAN4] +~g~Dopo aver posizionato tutte e tre le bombe, scappa dalla fabbrica prima che esploda. + +[CUB4_31:CUBAN4] +~g~Scappa dalla fabbrica!!! + +[CUB4_32:CUBAN4] +~g~Parcheggia l'auto nel punto segnalato ed esci. + +[CUB4_06:CUBAN4] +~r~Non ti sei allontanato a sufficienza dalla base e abbiamo dovuto interrompere l'esplosione! + +{=================================== MISSION TABLE FINALE ===================================} + +[FIN_1A:FINALE] +Vieni qui brutto doppiogiochista di merda! + +[FIN_1B:FINALE] +Ti faccio a pezzi, traditore del cazzo! + +[FIN_1C:FINALE] +Questo è l'ultimo ballo per Lance Vance! + +[FIN_2B:FINALE] +Oh, ma davvero! + +[FIN_2C:FINALE] +Te l'avevo detto che ne avevo avuto abbastanza a scuola! + +[FIN_3:FINALE] +Adesso chi ti coprirà il culo, eh Tommy? + +[FIN_4:FINALE] +Sei storia, Tommy, storia! + +[FIN_5:FINALE] +Hai scelto il lato sbagliato, Lance... + +[FIN_11A:FINALE] +Sonny, mi hai rubato quindici anni di vita... + +[FIN_11B:FINALE] +E adesso te la farò pagare! + +[FIN_12A:FINALE] +Non hai ancora capito, vero? + +[FIN_12B:FINALE] +Io ti POSSIEDO, Tommy. + +[FIN_12C:FINALE] +Quei quindici anni erano miei! + +[FIN_13:FINALE] +Prendetelo ragazzi, non ha mai capito niente. + +[FIN1_01:FINALE] +Cosa sta succedendo? + +[FIN1_02:FINALE] +Tommy! Oh bene, bene. Ascolta, ascolta. Uh, ascolta. + +[FIN1_03:FINALE] +A me piacciono i pesci. Adoro i pesci. + +[FIN1_04:FINALE] +Mi piacciono quando nuotano in una boccia o come cibo in un piatto, + +[FIN1_05:FINALE] +ma per quanto li ami, non voglio dormire in loro compagnia. + +[FIN1_06:FINALE] +Va bene, ma adesso i tuoi fratelli italiani stanno venendo per mettermi ai piedi delle belle scarpe in cemento e io... + +[FIN1_07:FINALE] +Sta zitto Ken. Siediti. + +[FIN1_08:FINALE] +Lance, di che diavolo stai parlando? + +[FIN1_09:FINALE] +Sono i tuoi amici del nord, Tommy. Non sono felici di cosa hai fatto al loro uomo. + +[FIN1_10:FINALE] +Oggi verranno giù per questioni di lavoro. + +[FIN1_11:FINALE] +Ci hanno messo di più di quanto credessi... + +[FIN1_12:FINALE] +Ragazzi, dobbiamo essere molto chiari. Non voglio che ci siano dubbi sul fatto che questa è la mia operazione. MIA! + +[FIN1_13:FINALE] +Ken, recupera la prima partita di soldi falsi e metti venti milioni in delle valigette. + +[FIN1_14:FINALE] +Lance, raduna i ragazzi... + +[FIN2_01:FINALE] +Tommy! + +[FIN2_02:FINALE] +Cosa? Niente abbraccio per un vecchio amico? + +[FIN2_03:FINALE] +Sono stato per quindici anni fuori dal giro, + +[FIN2_04:FINALE] +sono un po' arrugginito in fatto di etichetta familiare. + +[FIN2_05:FINALE] +Sempre arrabbiato, vero Tommy? + +[FIN2_06:FINALE] +Non ti avevo detto che il tuo carattere ti avrebbe messo nei guai, eh? + +[FIN2_07:FINALE] +Ci sono venti milioni in quelle valigette... + +[FIN2_08:FINALE] +Quanti hai detto? Dieci? No, undici uomini. + +[FIN2_09:FINALE] +È così che sei diventato il macellaio di Harwood! Eh eh eh! + +[FIN2_10:FINALE] +Mi hai inviato per uccidere un uomo, UN UOMO. Sapevano che stavo arrivando Sonny... + +[FIN2_11:FINALE] +Modera il tono. + +[FIN2_12:FINALE] +Qualcuno potrebbe pensare che stai incolpando me per le sfortunate circostanze. + +[FIN2_13:FINALE] +Prendi i fottuti soldi... + +[FIN2_14:FINALE] +Prendi i fottuti soldi? + +[FIN2_15:FINALE] +Lo sai, Tommy? Ho fatto quello che ho potuto per te. Ho tirato i fili, ho chiesto favori. + +[FIN2_16:FINALE] +Ero tuo amico, Tommy. Pensavo che avresti avuto buon senso, che avresti capito cosa è bene per il business. + +[FIN2_17:FINALE] +Mi fidavo di te, Tommy. E tu mi hai deluso. + +[FIN2_18:FINALE] +Ma alla fine qualcuno nella tua organizzazione del cazzo ha capito come si fa il business. + +[FIN2_19:FINALE] +Non è vero, Lance? + +[FIN2_20:FINALE] +Mi dispiace Tommy. Questa è Vice City. Questo è il suo business. + +[FIN2_21:FINALE] +Ci hai venduto... + +[FIN2_22:FINALE] +No. Ho venduto TE, Tommy, ho venduto TE. + +[FIN2_23:FINALE] +I soldi veri sono di sopra nella cassaforte. + +[FIN2_24:FINALE] +Allora Tommy, questo era il grande piano? + +[FIN2_25:FINALE] +Pensavi davvero che avrei preso i soldi finti? + +[FIN2_26:FINALE] +Salva la faccia e scappa con la coda tra le gambe! + +[FIN2_27:FINALE] +No. + +[FIN2_28:FINALE] +Volevo solo farti incazzare prima di ucciderti. + +[FIN3_01:FINALE] +Tommy? + +[FIN3_02:FINALE] +Oh mio Dio, Tommy! Che cosa è successo? + +[FIN3_03:FINALE] +Che cosa ti sembra sia successo? + +[FIN3_04:FINALE] +Mi sembra ti si sia rovinato l'abito! + +[FIN3_05:FINALE] +Accidenti, Tommy, era così bello! Tommy, che diavolo è successo? + +[FIN3_06:FINALE] +Ho avuto una divergenza di vedute con un socio in affari, sai come vanno queste cose. + +[FIN3_07:FINALE] +Tommy, se io ho una divergenza con un socio, gli mando una lettera di insulti. + +[FIN3_08:FINALE] +Forse gli piscio nella casella della posta. Ma non do il via alla terza guerra mondiale! + +[FIN3_09:FINALE] +Lo sai, forse dovresti parlare con il mio strizzacervelli... + +[FIN3_10:FINALE] +Quello stupido idiota, Lance... + +[FIN3_11:FINALE] +Tommy, non mi è mai piaciuto quel tipo, OK? + +[FIN3_12:FINALE] +È nevrotico, insicuro, egocentrico... quel tipo è un vero stronzo! + +[FIN3_13:FINALE] +Sono felice che l'hai fatto fuori! + +[FIN3_14:FINALE] +Non penso che riceveremo ulteriori problemi dal nord... + +[FIN3_15:FINALE] +...fondamentalmente perché non c'è più un 'nord'. + +[FIN3_16:FINALE] +Adesso è tutto a sud. + +[FIN3_17:FINALE] +Aspetta, vuoi forse dire ciò che penso io, caro il mio Tommy? + +[FIN3_18:FINALE] +Cosa credi che significhi? + +[FIN3_19:FINALE] +Che siamo in carica... cioè, che sei in carica! Oh Tommy... + +[FIN3_20:FINALE] +Lo sai, Ken, questo potrebbe essere l'inizio di uno splendido rapporto lavorativo... + +[FIN3_21:FINALE] +Dopo tutto, tu sei un convincente, diffamante, fottuto ladro. + +[FIN3_22:FINALE] +e io sono uno psicopatico assassino convinto e uno spacciatore. + +[FIN3_23:FINALE] +Lo so, non è meraviglioso? + +[FIN_B1:FINALE] +~g~Uccidi ~y~Vance~g~, il fottuto traditore. + +[FIN_B2:FINALE] +~g~Trova ~p~Sonny~g~ e metti la parola fine a questa storia. + +[FIN_B3:FINALE] +~g~La Mafia sta cercando di rubare i tuoi soldi. Difendi la cassaforte. + +[FIN_B4:FINALE] +~g~Sei prossimo alla morte: recupera della ~w~salute~g~ dal basso. + +[FIN_B5:FINALE] +~g~La Mafia sta rubando i tuoi soldi. Difendi la ~c~cassaforte~g~. + +[FIN_B7:FINALE] +~r~La Mafia ha rubato i tuoi soldi! + +[DEFSAFE:FINALE] +~g~Ritorna alla cassaforte e difendila. + +{=================================== MISSION TABLE FIRETRK ===================================} + +[F_PASS1:FIRETRK] +Incendio spento! + +[F_FAIL2:FIRETRK] +~r~Sei arrivato tardi! + +[F_CANC:FIRETRK] +~r~Missione Pompieri annullata! + +[F_EXTIN:FIRETRK] +INCENDI: + +[F_START:FIRETRK] +~g~Veicolo in fiamme avvistato in ~a~. Vai a spegnere il fuoco. + +[SIREN_1:FIRETRK] +Per attivare la sirena di questo veicolo premi il ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_2:FIRETRK] +Per attivare la sirena di questo veicolo premi il ~h~~k~~VEHICLE_HORN~~w~. + +[FIREPRO:FIRETRK] +Missione Camion dei pompieri livello 12 completata: adesso sei permanentemente ignifugo!!! + +[F_FAIL1:FIRETRK] +Missione Camion dei pompieri terminata. + +[F_STAR1:FIRETRK] +~g~Veicoli in fiamme presso l'area ~a~. Vai a spegnere l'incendio. + +[SPRAY_4:FIRETRK] { reVC update } +Premi il tasto ~h~~k~~VEHICLE_FIREWEAPON~~w~ per sparare con il cannone ad acqua e la ~h~~k~~VEHICLE_TURRETLEFT~~w~ e ~h~~k~~VEHICLE_TURRETRIGHT~~w~ per mirare. + +[SPRAY_1:FIRETRK] { reVC update } +Premi il tasto ~h~~k~~VEHICLE_FIREWEAPON~~w~ per sparare con il cannone ad acqua e la ~h~~k~~VEHICLE_TURRETLEFT~~w~ e ~h~~k~~VEHICLE_TURRETRIGHT~~w~ per mirare. + +{=================================== MISSION TABLE GENERA1 ===================================} + +[GEN1_A:GENERA1] +Mr. Vercetti! + +[GEN1_B:GENERA1] +Colonnello. + +[GEN1_D:GENERA1] +No, grazie. + +[GEN1_E:GENERA1] +Mi vergogno ad ammettere che una delle cause dei nostri problemi comuni sembra sia stata la linguaccia di una persona di cui credevo di potermi fidare. + +[GEN1_F:GENERA1] +Mi sono tenuto Gonzalez per anni, ma ora la sua incompetenza ha raggiunto il fondo. + +[GEN1_G:GENERA1] +Sarebbe solo un bene se eliminassi Gonzalez... + +[GEN1_H:GENERA1] +È stato lui? A me importa solo dei miei soldi. + +[GEN1_I:GENERA1] +Questa gentilezza verrà ricompensata dopo troveremo insieme i tuoi soldi. + +[GEN1_J:GENERA1] +Lo troverai nella sua Penthouse, probabilmente mezzo ubriaco. Usa questo... + +[GEN1_06:GENERA1] +Eeek! Ha una motosega! + +[GEN1_07:GENERA1] +Stammi lontano, brutto bastardo! + +[GEN1_08:GENERA1] +Oddio santo, ho distrutto la mia vita e il mio look! + +[GEN1_10:GENERA1] +Ho deciso di chiudere quella tua maledetta boccaccia! + +[GEN1_11:GENERA1] +Smettila di correre, brutto grassone! + +[GEN1_12:GENERA1] +Stai fermo, così la facciamo finita! + +[GEN1_13:GENERA1] +Smettila di strillare, non gliene frega a nessuno, grassone! + +[GEN1_C:GENERA1] +Grazie per essere venuto. Siediti. Vuoi dell'aragosta? + +[GEN1_05:GENERA1] +~g~Uccidi Gonzalez! + +[GEN1_09:GENERA1] +Ti pagherò doppio, Tommy, DOPPIO! + +[GEN1_18:GENERA1] +~r~Gonzalez ha raggiunto sano e salvo la stazione di polizia! + +[GEN1_19:GENERA1] +~g~La polizia di Vice City ti sta inseguendo! + +[GEN1_20:GENERA1] +~g~Prendi un veicolo. + +[GEN1_21:GENERA1] +~g~Raggiungi il ~h~Pay 'N' Spray~g~ in ~h~Vice Point~g~. + +[GEN1_22:GENERA1] +~g~Guida il tuo veicolo attraverso il carrozziere per perdere il ~h~livello di sospetto~g~, ~h~riparare~g~ e ~h~ricolorare~g~ il mezzo. Costo: ~h~100$~g~. Questa volta è gratis. + +[GEN1_01:GENERA1] +Quando corri, tieni premuto il ~o~tasto |~w~ per preparare un attacco corpo a corpo. + +[GEN1_02:GENERA1] +Quando corri, tieni premuto il ~x~tasto /~w~ per preparare un attacco corpo a corpo. + +[GEN1_03:GENERA1] +Quando corri, tieni premuto il ~h~tasto R1~w~ per preparare un attacco corpo a corpo. + +[GEN1_14:GENERA1] +Rilascia il ~h~~k~~PED_FIREWEAPON~~w~ per eseguire l'attacco. + +[GEN1_15:GENERA1] +Rilascia il ~h~~k~~PED_FIREWEAPON~~w~ per eseguire l'attacco. + +[GEN1_16:GENERA1] +Rilascia il ~h~~k~~PED_FIREWEAPON~~w~ per eseguire l'attacco. + +[GEN1_23:GENERA1] +~g~Passa per le porte per tornare al piano terra. + +{=================================== MISSION TABLE GENERA2 ===================================} + +[COL2_A:GENERA2] +Tommy! Vieni qui da me. + +[COL2_B:GENERA2] +Non ti sembra delizioso, vero? Lingua di Tapia? + +[COL2_C:GENERA2] +Eerr... No, no grazie. + +[COL2_D:GENERA2] +Tommy, sei come un vento fresco della pampa che mi libera dall'olezzo della corruzione, + +[COL2_E:GENERA2] +benché mi addolori la recente dipartita, devo continuare come sempre con il mio lavoro. + +[COL2_F:GENERA2] +Non mi sembra ci stiamo avvicinando ai miei soldi... + +[COL2_G:GENERA2] +Tommy, amico mio, non sei più a Liberty. Qui le cose vanno in modo diverso. + +[COL2_H:GENERA2] +Continuerò le mie ricerche, ma nel frattempo ho un interessante accordo da proporti. + +[COL2_I:GENERA2] +Un favore per un amico, Cortez. + +[COL2_J:GENERA2] +Sei un vero amico, Tommy, Ero certo che non mi avresti deluso. + +[COL2_K:GENERA2] +Ho bisogno che tu incontri un corriere che mi ha recuperato una tecnologia di cui ho bisogno... + +[COL2_1:GENERA2] +La pioggia, è così très umido in questo periodo dell'anno... + +[COL2_2:GENERA2] +Cosa? + +[COL2_3:GENERA2] +Ah, comment? + +[COL2_4:GENERA2] +Ascolta, mi manda Cortez. Dammi quei maledetti chip. + +[COL2_5:GENERA2] +Oh... d'accord. + +[COL2_B1:GENERA2] +~g~Incontra il corriere al Mall. + +[COL2_B2:GENERA2] +~g~Il correre sta scappando con i chip: non farlo scappare! + +[COL2_B3:GENERA2] +~g~Porta i chip al Colonnello. + +[COL2_F1:GENERA2] +~r~Hai ucciso il contatto! + +[COL2_F2:GENERA2] +~r~Il corriere è morto. Raccogli i chip. + +[COL2_6A:GENERA2] +Fermo, brutto maiale imperialista americano! Questa è proprietà del governo francese. Passamela! + +[BLIPHLP:GENERA2] +Se il segnale sul radar è un triangolo verso l'alto, significa che il bersaglio è in posizione più elevata rispetto al giocatore. + +[COL2_F3:GENERA2] +~r~I chip adesso riposano in fondo all'oceano. + +[COL2_F4:GENERA2] +~r~Il corriere è scappato! Non sei riuscito a recuperare i chip. + +{=================================== MISSION TABLE GENERA3 ===================================} + +[GEN3_A:GENERA3] +Thomas, grazie per essere venuto. + +[GEN3_B:GENERA3] +Perdonami se giungo subito al punto. + +[GEN3_C:GENERA3] +Diaz mi ha chiesto di sovrintendere una piccola transizione di lavoro. + +[GEN3_D:GENERA3] +Speriamo vada meglio dell'ultima... + +[GEN3_E:GENERA3] +Ed è per questo che ho pensato a te, amico. + +[GEN3_F:GENERA3] +Ho lasciato un po' di supporto al parcheggio multipiano. + +[GEN3_G:GENERA3] +Raccogli il materiale, poi raggiungi e sorveglia gli uomini di Diaz all'incontro. + +[GEN3_H:GENERA3] +Gracias, amigo. + +[GEN3_1:GENERA3] +Ami l'azione, vedo... + +[GEN3_2:GENERA3] +Senti, ti capita mai di fare qualcos'altro che non sia seguirmi dappertutto? Perché non vieni con me e mi dimostri di servire a qualcosa? + +[GEN3_3:GENERA3] +Potrei anche farlo. A proposito, mi chiamo Lance. + +[GEN3_5:GENERA3] +Devi essere il nuovo uomo di Cortez. + +[GEN3_6:GENERA3] +Fino a quando non mi capiteranno opportunità più lucrose. + +[GEN3_7:GENERA3] +Arriveranno fra pochi minuti: meglio trovare un buon punto dove appostarsi... + +[GEN3_8:GENERA3] +OK! Io prendo il balcone, tu prendi il tetto dall'altra parte del cortile. + +[GEN3_9:GENERA3] +Sono vivo, brutti stronzi! E tutto grazie a te! Come ti chiami? + +[GEN3_10:GENERA3] +Tommy. + +[GEN3_11:GENERA3] +Ci incontreremo di nuovo, credo! + +[GEN3_12:GENERA3] +Dove diavolo è andato Lance? Merda... + +[GEN3_14:GENERA3] +Tommy! Ho bisogno di aiuto! + +[GEN3_15:GENERA3] +Non preoccuparti, ti copro io! + +[GEN3_16:GENERA3] +Gli uomini di Diaz si stanno facendo massacrare! + +[GEN3_19:GENERA3] +~g~Haitiani! Stanno mandando a monte l'affare! Proteggi Diaz! + +[GEN3_20:GENERA3] +~g~Entra nel parcheggio multipiano e recupera ciò che il Colonnello ha lasciato per te. + +[GEN3_22:GENERA3] +Salute di Diaz: + +[GEN3_23:GENERA3] +~g~Hai lasciato Lance indietro! Vallo a prendere! + +[GEN3_25:GENERA3] +~r~Lance è morto! + +[GEN3_28:GENERA3] +~g~Riporta la valigetta a Diaz. + +[GEN3_29:GENERA3] +~g~Raccogli la valigetta e riportala a Diaz. + +[GEN3_30:GENERA3] +~r~È scappato con i soldi! Diaz ti strapperà le palle per questo! + +[GEN3_33:GENERA3] +~r~Dovresti proteggere Diaz e i suoi uomini, non sparargli addosso! + +[GEN3_34:GENERA3] +~r~Non ci sarà nessun accordo se spari ai Cubani! + +[GEN3_35:GENERA3] +~g~Ha rubato i soldi di Diaz! + +[GEN3_36:GENERA3] +~g~Prendi la moto, inseguilo e recupera i soldi di Diaz! + +[GEN3_37:GENERA3] +~g~Stanno arrivando i Cubani. Assicurati che niente vada storto e proteggi Diaz e Lance. + +[GEN3_38:GENERA3] +~r~Diaz è morto! Non sei riuscito a proteggerlo! + +[GEN3_39:GENERA3] +~g~Raggiungi la tua postazione sopra alle scale. + +[GEN3_44:GENERA3] +~g~Vai con Lance all'appuntamento e proteggi Diaz. + +[GEN3_40:GENERA3] { reVC update } +Per ~h~sparare davanti~w~ su una ~h~moto~w~ premi il ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_41:GENERA3] { reVC update } +Per ~h~sparare davanti~w~ su una ~h~moto~w~ premi il ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_46:GENERA3] +Meerda! + +[GEN3_47:GENERA3] +Tommy! + +[GEN3_48:GENERA3] +Maledizione! + +[GEN3_49:GENERA3] +SALUTE DI LANCE: + +[GEN3_50:GENERA3] +~r~Hai distrutto i soldi di Diaz! La prossima volta cerca di non ridurre le banconote in cenere! + +[GEN3_51:GENERA3] +Altri fottuti Haitiani in un merdoso van! + +[GEN3_54:GENERA3] +Non restare lì impalato, idiota, insegui quello stronzo di un Haitiano! + +[GEN3_55:GENERA3] +Tommy! Io resto qua a proteggere Diaz! + +[GEN3_18:GENERA3] +~g~Stanno arrivando i Cubani, resta vicino a Diaz. Assicurati che niente vada storto e proteggi Diaz e Lance. + +[GEN3_56:GENERA3] +~r~Diaz è stato assalito e ucciso! La prossima volta, cerca di proteggerlo! + +[GEN3_57:GENERA3] +Il Kruger è un fucile d'assalto che permette di mirare in prima persona. + +[GEN3_58:GENERA3] +Tieni premuto il tasto ~h~R1~w~ per ~h~mirare~w~ con il fucile d'assalto. + +[GEN3_59:GENERA3] +Tieni premuto il tasto ~h~L1~w~ per ~h~mirare~w~ col fucile d'assalto. + +[GEN3_60:GENERA3] +Premi il tasto ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile d'assalto. + +[GEN3_61:GENERA3] +Premi il tasto ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile d'assalto. + +[GEN3_62:GENERA3] +Premi il tasto ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~ col fucile d'assalto. + +[GEN3_63:GENERA3] +Oltre a permetterti di sparare in corsa, con le ~h~moto~w~ puoi anche ~h~sparare in avanti~w~. + +[GEN3_64:GENERA3] { reVC update } +Per sparare in avanti quando sei su una moto, premi il tasto ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_65:GENERA3] { reVC update } +Per sparare in avanti quando sei su una moto, premi il tasto ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_66:GENERA3] { reVC update } +Per sparare in avanti quando sei su una moto, premi il tasto ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_67:GENERA3] +Hai bisogno di una mitragliatrice per poter sparare in avanti. + +[GEN3_53:GENERA3] +I MIEI SOLDI! + +[GEN3_52:GENERA3] +Questi Haitiani pensano che possono farsi RICARDO DIAZ!?! + +{=================================== MISSION TABLE GENERA4 ===================================} + +[DETON:GENERA4] +DETONAZIONE: + +[COL4_3:GENERA4] +CONVOGLIO ALT! + +[COL4_6:GENERA4] +SIAMO SOTTO FUOCO NEMICO! + +[COL4_7:GENERA4] +Civile, si allontani dal carro armato! + +[COL4_8:GENERA4] +HO DETTO, si allontani, IMMEDIATAMENTE! + +[COL4_9:GENERA4] +POSIZIONI DIFENSIVE! + +[COL4_11:GENERA4] +Porta via quel civile, soldato! - Signorsì, signore! + +[COL4_12:GENERA4] +Civile nel CARRO ARMATO! FERMATELO! + +[COL4_13:GENERA4] +Questo è un convoglio militare, non ostacoli il passaggio. + +[COL4_14:GENERA4] +Fallo fuori, soldato. + +[COL4_15:GENERA4] +Spostate quel veicolo civile via dalla strada! -Signore! Sposto il veicolo, signore! + +[COL4_17:GENERA4] +OK, PLOTONE, IN MARCIA! + +[COL4_18:GENERA4] +Qualcuno è entrato nel carro armato, signore! + +[COL4_19:GENERA4] +Vai a prendere delle ciambelle, soldato! - Signorsì, signore! + +[COL4_20:GENERA4] +Bersaglio acquisito, signore! + +[COL4_21:GENERA4] +CECCHINO! + +[COL4_22:GENERA4] +Io me la squaglio. + +[COL4_23:GENERA4] +Obiettivo completato! Plotone riposo! -Andiamo a mangiarci qualche ciambella. + +[COL4_24:GENERA4] +Protocollo di sicurezza Delta India Echo attivato! Autodistruzione del veicolo attivata! + +[COL4_26:GENERA4] +Preparati a morire, maledetto comunista! + +[COL4_B2:GENERA4] +~r~Il carro armato è arrivato con successo a destinazione! + +[COL4_B5:GENERA4] +~r~Il carro armato è stato distrutto! + +[COL4_01:GENERA4] +Diaz era soddisfatto e vorrebbe incontrarti nuovamente. + +[COL4_02:GENERA4] +È un buon segno? + +[COL4_03:GENERA4] +Ma certo! Benché credo che Diaz sia il responsabile della nostra sfortunata perdita... + +[COL4_04:GENERA4] +Che cosa glielo fa pensare? + +[COL4_05:GENERA4] +Nessuno accusa direttamente un uomo come Diaz... sto solo pensando a voce altra... + +[COL4_06:GENERA4] +Non importa. Ho una proposta che potrebbe interessarti... + +[COL4_07:GENERA4] +Non ho più tempo per i suoi lavori, Cortez. + +[COL4_08:GENERA4] +Pensavo che un uomo con dei debiti così pericolosi fosse desideroso di opportunità. Per favore, Tommy, almeno ascoltami. + +[COL4_09:GENERA4] +Mi dica... + +[COL410:GENERA4] +Ho un acquirente per un mezzo militare che sta attraversando la città. Recuperalo per me... + +[COL411:GENERA4] +e, quando lo avrai, vorrei che mi chiamassi immediatamente. Poi... + +[COL4_B4:GENERA4] +~g~Il carro armato è chiuso. Trova un modo per far uscire gli occupanti. + +[COL4_1:GENERA4] +Che cos'ha il mitragliatore? -Non so signore! + +[COL4_4:GENERA4] +-Vai di sopra, soldato. -Signorsì, signore! + +[COL4_B1:GENERA4] +~g~Impadronisciti del mezzo militare che sta attraversando la città. + +[COL4_B3:GENERA4] +~g~Porta il carro armato al garage del Colonnello prima che si autodistrugga. + +[COL4_B6:GENERA4] +~g~Trova un modo per rubare il carro armato! + +[COL4_B7:GENERA4] +~g~Porta il carro armato dentro al garage. + +[COL4_B8:GENERA4] +~g~Esci dal carro armato e abbandona il garage. + +{=================================== MISSION TABLE GENERA5 ===================================} + +[COL5A_1:GENERA5] +Le circostanze richiedono una rapida partenza, amigo. + +[COL5A_2:GENERA5] +Qual è il problema? + +[COL5A_3:GENERA5] +Uh, i Francesi rivogliono la loro tecnologia missilistica indietro dopo quell'incidente... + +[COL5A_4:GENERA5] +Sento che è giunta l'ora di partire per porti più sicuri. + +[COL5A_5:GENERA5] +Non sarebbe meglio via aria? + +[COL5A_6:GENERA5] +Sarei morto ancor prima di raggiungere il check-in. Inoltre, devo portare la mia merce fuori dal paese. + +[COL5A_7:GENERA5] +Serve un'altra pistola? + +[COL5A_8:GENERA5] +Tu, amico, vali come dieci pistole... ah ah ah! + +[COL5B_1:GENERA5] +Thomas, mi hai protetto e servito bene. + +[COL5B_2:GENERA5] +Ma adesso devi abbandonarci prima che raggiungiamo il mare aperto. + +[COL5B_4:GENERA5] +Grazie mille, Colonnello. + +[COL5B_5:GENERA5] +Un'ultima richiesta, mentre sono via: terresti un occhio su Mercedes per me? + +[COL5B_6:GENERA5] +Credo sappia badare a sé stessa, ma certo, la terrò sott'occhio. + +[COL5B_7:GENERA5] +Grazie mille, amico. A presto! + +[COL5B_8:GENERA5] +Adios, amigo. + +[COL5_7:GENERA5] +Smettila di spararmi! + +[COL5_9:GENERA5] +Tommy, falli smettere di sparare! + +[COL5_10:GENERA5] +Ho l'immunità diplomatica! + +[COL5_11:GENERA5] +Non sparare, sono il Colonnello! + +[COL5_12:GENERA5] +Thomas, uccidili: la mia nazione ti sarà riconoscente. + +[COL5_13:GENERA5] +Tommy, siamo assaliti dai Francesi! + +[COL5_14:GENERA5] +Tommy, dovunque guardo vedo dei Francesi. Io li odio! + +[COL5_15:GENERA5] +Tommy, come stai? + +[COL5_16:GENERA5] +Questo è per la Piaf e Gainesbourg e per le vostre stupide baguette! + +[COL5_1:GENERA5] +A babordo! A babordo! + +[COL5_2:GENERA5] +Ci attaccano da tribordo! + +[COL5_3:GENERA5] +Il ponte qui davanti! + +[COL5_4:GENERA5] +Hanno un elicottero! + +[COL5_B1:GENERA5] +~g~Difendi a tutti i costi il Colonnello e il suo yacht. + +[COL5_B2:GENERA5] +~g~Vai a prua e libera la strada per il passaggio dello yacht del Colonnello. + +[COL5_B3:GENERA5] +~r~Il Colonnello è morto! + +[COL5_B4:GENERA5] +~g~Abbatti l'elicottero! + +[COL5B_3:GENERA5] +Farò calare la mia lancia personale. Tienila, amico, un pegno della mia gratitudine. + +[COL5_B5:GENERA5] +~g~Abbatti gli elicotteri e proteggi lo yacht. + +[COL5_B6:GENERA5] +~g~Hai finito i colpi: recuperane altri dalle scale al ponte superiore. + +[COL5_B7:GENERA5] +~g~Sei a corto di salute: recuperane altra dalle scale al ponte superiore + +{=================================== MISSION TABLE HAIT1 ===================================} + +[HAM1_A:HAIT1] +Permesso? + +[HAM1_B:HAIT1] +Entra pure, caro, e riposa l'anima. + +[HAM1_C:HAIT1] +Devi essere il grande uomo cattivo di cui parlava mio nonno. + +[HAM1_D:HAIT1] +Mi ha raccontato di te, sai, quando mi fa visita, + +[HAM1_E:HAIT1] +e degli altri che ti aspettano. + +[HAM1_F:HAIT1] +Adesso, siamo tutti morti da tempo, ma tu sai, + +[HAM1_G:HAIT1] +non vorrei essere nei tuoi panni, eh eh eh! + +[HAM1_H:HAIT1] +Ho ricevuto un messaggio. Sono venuto. + +[HAM1_I:HAIT1] +Li puoi sentire? + +[HAM1_J:HAIT1] +Stanno chiamando il tuo nome, devono proprio volerti, non credi? + +[HAM1_K:HAIT1] +Magari adesso dai una mano alla zia Poulet, uh, e forse lei ti aiuta. + +[HAM1_L:HAIT1] +Forse potrà darti un piccolo juju dopo tutto questo. + +[HAM1_M:HAIT1] +Un po' di magia per dare all'uomo di legge il malocchio, hmmm? + +[HAM1_N:HAIT1] +Ascolta, tutto questo è, uhm... darmi cosa? + +[HAM1_O:HAIT1] +Credo, credo proprio di aver sbagliato indirizzo... + +[HAM1_P:HAIT1] +Fai per me questo favore, Tommy... + +[HAM1_Q:HAIT1] +I Cubani, foofoo molto orgogliosi, hmmm, + +[HAM1_R:HAIT1] +hanno dato molti grattacapi ai miei ragazzi Haitiani. + +[HAM1_S:HAIT1] +Adesso hanno detto alla polizia dove nascondo le mie polveri. + +[HAM1_T:HAIT1] +Pensano sia droga, gli stupidi. + +[HAM1_U:HAIT1] +Adesso fai il bravo, Tommy, e vai a prendere le polveri per la zia Poulet. + +[HAM1_V:HAIT1] +Sì, sì, certo, vado. + +[HAM1_1:HAIT1] +~g~I poliziotti si stanno avvicinando alle nostre riserve. Fermali prima che ci riescano. + +[HAM1_2:HAIT1] +~r~I poliziotti sono arrivati per primi alla riserva! + +[HAM1_3:HAIT1] +~g~Riporta questa roba al nascondiglio! + +[HAM1_4:HAIT1] +~g~Ottimo! Adesso pensa al prossimo! + +[HAM1_6:HAIT1] +~r~La riserva è stata distrutta, idiota! + +[HAM1_7:HAIT1] +~g~La polizia ha preso la nostra riserva! Recuperala prima che se ne vada! + +[HAM1_8:HAIT1] +~g~I poliziotti sono vicini alla riserva: datti una mossa! + +[HAT_1A:HAIT1] +~g~Non muoverti, allocco! + +{=================================== MISSION TABLE HAIT2 ===================================} + +[HAT2_B1:HAIT2] +~g~Raggiungi il van che contiene le bombe volanti. + +[HAT2_B2:HAIT2] +Uccidi i Cubani... + +[HAT2_B4:HAIT2] +...e distruggi le loro imbarcazioni! + +[HAT2_B5:HAIT2] +~g~I Cubani stanno fuggendo: non lasciarli scappare! + +[HAT2_B6:HAIT2] +~r~L'aereo radiocomandato si sta allontanando dal raggio d'azione! + +[HAT2_B7:HAIT2] +~g~Uno dei Cubani sta fuggendo in macchina. Non lasciare nessun testimone! + +[HAT2_B8:HAIT2] +~g~Non hai più aerei radiocomandati! + +[HAT2_B9:HAIT2] +Aerei radiocomandati: + +[HAT2_1:HAIT2] +Oh. Scusa, devo aver sbagliato indirizzo... + +[HAT2_2:HAIT2] +Puoi entrare comunque a riposare i piedi e a bere un po' di tè. + +[HAT2_3:HAIT2] +Hai qualcosa per me, Tommy? + +[HAT2_4:HAIT2] +Sì... + +[HAT2_5:HAIT2] +Questo posto mi sembra familiare. Un odore della mia gioventù... un deja vu... + +[HAT2_6:HAIT2] +Adesso Tommy, ti sussurrerò una piccola commissione che devi farmi. Ascoltami attentamente, va bene? + +[HAT2_7:HAIT2] +Mi ricordi qualcuno che... + +[HAT2_8:HAIT2] +I Cubani hanno imbarcazioni molto veloci che utilizzano per trasportare la droga via mare. + +[HAT2_9:HAIT2] +È il loro modo di vivere. + +[HAT2_10:HAIT2] +Mio nipote ha costruito delle simpatiche bombe per farli fuori. + +[HAT2_11:HAIT2] +Fai esplodere le barche e inchioda le loro bare. + +[HAT2_12:HAIT2] +Beh, grazie per il tè. + +[HAT2_B3:HAIT2] { reVC update } +Premi il ~h~~k~~VEHICLE_FIREWEAPON~~w~ per sganciare una bomba. Premi il ~h~~k~~VEHICLE_ENTER_EXIT~ "~w~ per annullare. + +{=================================== MISSION TABLE HAIT3 ===================================} + +[HAM3_A:HAIT3] +Salve, salve, io, uh... Stavo cercando qualcuno... + +[HAM3_B:HAIT3] +Mi sembri affamato, Tommy. + +[HAM3_C:HAIT3] +Ci conosciamo? + +[HAM3_D:HAIT3] +Zitto ora. + +[HAM3_E:HAIT3] +Un'ultima cosa e potrai andare, Tommy. + +[HAM3_F:HAIT3] +I miei ragazzi sono in guerra con i Cubani. + +[HAM3_G:HAIT3] +Ma niente pistole. + +[HAM3_H:HAIT3] +Hmmm, ma i Cubani hanno una sorpresa in serbo. + +[HAM3_I:HAIT3] +Mentre combattono per la strada, tu prendi questo fucile e li elimini durante la confusione. + +[HAM3_J:HAIT3] +Nessuno ti vede, nessuno ti sente. + +[HAM3_K:HAIT3] +Adesso, Tommy, fai questo per me e non sarai più aggrappato al mio grembiule. + +[HAM3_1:HAIT3] +~g~Dobbiamo vincere questa battaglia. Se tutti gli Haitiani muoiono, sarà la fine. + +[HAM3_3:HAIT3] +~g~Sospetto che i Cubani bareranno, per cui stai in guardia. + +[HAM3_4:HAIT3] +~r~Ti hanno visto! La missione è fallita! + +[HAM3_5:HAIT3] +~g~Devi eliminare i Cubani da lontano. Non devono vederti. + +[HAM3_8:HAIT3] +~g~Gli Haitiani stanno morendo! Migliora la tua mira! + +[HAM3_7:HAIT3] +~g~Attento! I Cubani hanno chiamato rinforzi. Uccidili tutti quanti! + +[HAM3_2:HAIT3] +~r~Gli Haitiani sono morti! + +[HAM3_L:HAIT3] +Sssì zia... + +{=================================== MISSION TABLE HOTEL ===================================} + +[INTB_A:HOTEL] +Tommy! Tommy, quanto tempo è passato! + +[INTB_B:HOTEL] +Ciao Sonny. + +[INTB_C:HOTEL] +Lo so, lo so, sei sopraffatto dall'emozione. + +[INTB_D:HOTEL] +Quindici anni... sembra come se fosse ieri. + +[INTB_E:HOTEL] +Credo dipenda dal punto di vista. + +[INTB_F:HOTEL] +Ehi, stare dentro per la famiglia non è una passeggiata, + +[INTB_G:HOTEL] +ma la famiglia si preoccupa dei suoi, OK? + +[INTB_H:HOTEL] +Allora, raccontami come è andata... Eri in ballo con l'oro bianco? + +[INTB_I:HOTEL] +Ascolta Sonny, ci avevano incastrato. L'incontro era un'imboscata. Harry e Lee sono morti. + +[INTB_J:HOTEL] +Basta con queste storie, Tommy. Hai ancora i soldi, vero? + +[INTB_K:HOTEL] +...no Sonny... non ho i soldi. + +[INTB_L:HOTEL] +Quelli erano i miei soldi, Tommy, I MIEI SOLDI! + +[INTB_M:HOTEL] +Non cercare di fottermi, Tommy, perché sai che non sono una persona a cui piace essere presa per il culo! + +[INTB_N:HOTEL] +Aspetta Sonny. + +[INTB_O:HOTEL] +Hai la mia parola che recupererò i tuoi soldi e la droga. + +[INTB_P:HOTEL] +E ti farò avere per posta l'uccello dei responsabili. + +[INTB_Q:HOTEL] +Ehi, ne sono certo. Non sei uno stupido, Tommy, ma ti avverto: non lo sono neanch'io. + +[INTB_R:HOTEL] +Se fosse stato qualcun altro, adesso sarebbe già MORTO. + +[INTB_S:HOTEL] +Ma visto che sei tu, che ci conosciamo da tempo, ti permetterò di gestire la situazione. + +[INTB_T:HOTEL] +Ehi, Sonny, hai la mia parola. + +[INTB_U:HOTEL] +Mi farò vivo. + +{=================================== MISSION TABLE ICECRE1 ===================================} + +[ICC1_4:ICECRE1] +~g~Non ci sono clienti in quest'area, prova in un'altra. + +[ICC1_5:ICECRE1] +Transazioni effettuate: + +[ICC1_7:ICECRE1] +~g~Riceverai soldi per ogni transazione, ma più ne esegui, maggiori saranno le probabilità di attirare l'attenzione della polizia. + +[ICC1_8:ICECRE1] +~g~Per eseguire una transazione, ~h~parcheggia il camioncino~g~ e premi il ~h~~k~~VEHICLE_HORN~~g~ per suonare il campanello e attrarre i clienti. + +[ICC1_9:ICECRE1] +~g~Le gang locali non apprezzeranno il tuo lavoro nel loro territorio, per cui aspettati azioni ostili. + +[ICC1_10:ICECRE1] +~g~Hai portato a termine ~1~ transazioni! + +[ICC1_11:ICECRE1] +~g~Hai portato a termine ~1~ transazione! + +[ICC1_13:ICECRE1] +~r~Non hai portato a termine nessuna transazione. + +[ICC1_14:ICECRE1] +BENI FABBRICA DI GELATO ACQUISITI + +[ICC1_15:ICECRE1] +~g~La fabbrica di gelato d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +[ICC1_16:ICECRE1] +~g~Utilizza il Mr. Whoopee per distribuire i prodotti Cherry Poppers per Vice City. + +[ICE_AT1:ICECRE1] +FABBRICA DI GELATO COMPLETATA + +[ICE_AT2:ICECRE1] +~g~La fabbrica Cherry Popper d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +[ICC1_17:ICECRE1] +Missione di distribuzione terminata. + +[ICC1_18:ICECRE1] +Vendita totale gelati: ~1~$ + +[ICC1_19:ICECRE1] +Transizioni totali eseguite: ~1~ + +{=================================== MISSION TABLE ICECUT ===================================} + +[ICC1_A:ICECUT] +Chi sei? + +[ICC1_B:ICECUT] +Il nuovo proprietario. + +[ICC1_C:ICECUT] +Sei per caso, o lo sei mai stato, un bambino? + +[ICC1_D:ICECUT] +Ma di cosa stai parlando? + +[ICC1_E:ICECUT] +Sei stato un bambino? + +[ICC1_F:ICECUT] +Certo! Calmati! Che ti prende? + +[ICC1_G:ICECUT] +Lo sapevo. Un bambino. + +[ICC1_H:ICECUT] +Uno sporco, puzzolente, moccioso, lamentoso, vile, vomitevole, piagnucoloso piccolo bambino! + +[ICC1_I:ICECUT] +Un bambino... un mostruoso, orribile disgustoso piccolo bambino. Boo hoo. + +[ICC1_J:ICECUT] +La mamma non ti ama, brutto piccolo avanzo! + +[ICC1_K:ICECUT] +Ow! Accidenti, calmati! + +[ICC1_L:ICECUT] +Io ODIO i bambini, odio i poppanti. + +[ICC1_M:ICECUT] +Sono sporchi, puzzolenti, mocciosi, vomitevoli piccoli... + +[ICC1_N:ICECUT] +Ne ho avuto abbastanza! + +[ICC1_O:ICECUT] +Ma che diavolo hai? + +[ICC1_P:ICECUT] +Tu fai gelati, vero? Sono solo per bambini. + +[ICC1_Q:ICECUT] +Ma che psicopatica sei? + +[ICC1_R:ICECUT] +È così mi sono chiesta: perché fare felici i bambini se li odio? + +[ICC1_S:ICECUT] +Oh, stupidi, schifosi, mocciosi... + +[ICC1_T:ICECUT] +Sta zitta! + +[ICC1_U:ICECUT] +...Monellaccio! + +[ICC1_V:ICECUT] +La gelateria è solo una facciata. + +[ICC1_W:ICECUT] +Noi distribuiamo altro, prodotti non caseari. + +[ICC1_X:ICECUT] +E se vedo un bambino, non perdo occasione. + +[ICC1_Y:ICECUT] +Non è vero, bambini? Sì, sì, lo faccio. La mamma non vi ama. + +[ICC1_Z:ICECUT] +Lei vi ODIA! + +[ICC1_ZA:ICECUT] +PROPRIETÀ ACQUISTATA! + +{=================================== MISSION TABLE INTRO ===================================} + +[INT1_A:INTRO] +Tommy Vercetti... Ommerda! + +[INT1_B:INTRO] +Pensavo non lo avrebbero mai rilasciato. + +[INT1_C:INTRO] +Ha tenuto la testa bassa... molta gente ha dimenticato. + +[INT1_D:INTRO] +Ma molti ricorderanno ben presto... + +[INT1_E:INTRO] +Quando lo vedranno camminare per le strade del loro vicinato. + +[INT1_F:INTRO] +Non sarà una cosa positiva per il business. + +[INT1_G:INTRO] +Beh, che cosa facciamo, Sonny? + +[INT1_H:INTRO] +Lo trattiamo come un amico e lo teniamo impegnato fuori città, OK? + +[INT1_I:INTRO] +Parlavamo di espanderci verso sud, vero? + +[INT1_J:INTRO] +Vice City è oro puro in questi giorni. + +[INT1_K:INTRO] +I Colombiani, i Messicani, che diamine, + +[INT1_L:INTRO] +anche i rifugiati Cubani si sono ritagliati una bella fetta della torta. + +[INT1_M:INTRO] +Ma si tratta di droga, Sonny, + +[INT1_N:INTRO] +nessuno delle famiglie toccherà quella merda! + +[INT1_O:INTRO] +I tempi cambiano. + +[INT1_P:INTRO] +Le famiglie non possono girare le spalle mentre i nostri nemici si godono il raccolto. + +[INT1_Q:INTRO] +Per cui mandiamo qualcuno a fare il lavoro sporco + +[INT1_R:INTRO] +e a prendere una bella fetta per noi, OK? + +[INT1_S:INTRO] +Chi è il nostro contatto laggiù? + +[INT1_T:INTRO] +Ken Rosenberg, uno schmuck di un avvocato. + +[INT1_U:INTRO] +Come farà a tenere al guinzaglio Vercetti? + +[INT1_V:INTRO] +Non dovrà farlo. + +[INT1_W:INTRO] +Mandiamolo alla ribalta a Vice City + +[INT1_X:INTRO] +con un po' di denaro per cominciare, OK? + +[INT1_Y:INTRO] +Lasciamogli qualche mese. + +[INT1_Z:INTRO] +Poi andiamo giù, + +[INT1_A1:INTRO] +gli facciamo visita, giusto? + +[INT1_A2:INTRO] +E vediamo come se la sta cavando... + +[INT2_A:INTRO] +Ehi, ehi, amici! Sono, uh, Ken Rosenberg! Eh, eh, ottimo, ehi! + +[INT2_B:INTRO] +Bene, uh, vi accompagno in auto all'appuntamento, OK? + +[INT2_C:INTRO] +Ho parlato con i fornitori e loro sono molto, ehm, + +[INT2_D:INTRO] +desiderosi di avviare una relazione commerciale, per cui, uh, + +[INT2_E:INTRO] +se tutto dovesse andare come deve, noi, dovremmo, uh, + +[INT2_F:INTRO] +ottenere dei buoni risultati economici, il che, cioè... + +[INT2_G:INTRO] +è bene... + +[INT2_H:INTRO] +OK allora. Sono fratelli, OK. + +[INT2_I:INTRO] +Uno si occupa del, uh, business, + +[INT2_J:INTRO] +mentre l'altro si occupa dei voli. + +[INT2_K:INTRO] +Adesso operano dal Messico, + +[INT3_A:INTRO] +OK, sono loro sull'elicottero. + +[INT3_B:INTRO] +Bene, questo è l'accordo. + +[INT3_C:INTRO] +Vogliono uno scambio diretto in campo aperto. + +[INT3_D:INTRO] +Va bene? OK, nervi saldi, andiamo. + +[INT3_E:INTRO] +Vedi di star calmo. + +[INT3_F:INTRO] +Resto qui con l'auto pronta a partire! + +[INT3_G:INTRO] +Chiaro? + +[INT3_H:INTRO] +Colombiana 100% pura di prima classe, amici miei. + +[INT3_I:INTRO] +I verdoni? + +[INT3_J:INTRO] +Da dieci e da venti... usati. + +[INT3_L:INTRO] +Forza, vieni qua! Muoviti! + +[INTRO1:INTRO] +metto fuori la testa per un maledetto secondo, e il fato mi tira palate di merda in faccia! + +[INTRO2:INTRO] +Vai a riposarti. + +[INTRO3:INTRO] +Che cosa intendi fare? + +[INTRO4:INTRO] +Passerò domani a trovarti in ufficio e troveremo un modo per risolvere questo casino. + +[INT3_K:INTRO] +Siamo d'accordo allora, amico. AH AH! + +[INT3_M:INTRO] +Fammi dare un'occhiata. + +[INT2_L:INTRO] +no, no, no, aspetta... + +{=================================== MISSION TABLE KENT1 ===================================} + +[KPM1_A:KENT1] +Ehi bello, ho delle informazioni sul tuo amico. + +[KPM1_B:KENT1] +Ma di cosa stai parlando? + +[KPM1_C:KENT1] +Conosci quel fallito di Diaz, il signore degli spacciatori? + +[KPM1_D:KENT1] +Ha preso il tuo amico, Lance. Sembra che abbia cercato di scavalcarlo... + +[KPM1_E:KENT1] +ma non ha saltato abbastanza in alto, se capisci cosa intendo. + +[KPM1_F:KENT1] +Dove lo hanno portato? Senza giri di parole! + +[KPM1_G:KENT1] +Stai calmo! Lo hanno trasportato attraverso la città fino alla discarica. + +[KPM1_H:KENT1] +Maledizione... Brutto idiota. + +[KPM1_2:KENT1] +~r~Dovevi portare Lance fuori vivo da lì! + +[KPM1_3:KENT1] +SALUTE DI LANCE: + +[RESC_1:KENT1] +Ce la fai a usare un'arma? + +[RESC_2:KENT1] +Certo... credo... È bello rivederti! + +[RESC_3:KENT1] +Andiamocene da qua. + +[RESC_4:KENT1] +Bene, con questo hai mandato a puttane tutti i miei bei piani. L'hai proprio fatta grossa, Lance. + +[RESC_5:KENT1] +Ha ucciso mio fratello. Che cosa ti aspettavi facessi, che gli tagliassi il prato? + +[RESC_6:KENT1] +Dobbiamo far fuori quello stronzo di Diaz prima che sia lui a farlo. + +[RESC_7:KENT1] +Preparati e incontriamoci al ponte di Star Island, OK? + +[RESC_8:KENT1] +OK, ricevuto. + +[KPM1_1:KENT1] +~g~Lance viene tenuto nella discarica: vai a salvarlo! + +[KPM1_4:KENT1] +~g~Porta Lance all'ospedale! + +[M_PASSN:KENT1] +MISSIONE COMPLETATA! + +[KPM1_5:KENT1] +~g~Gli uomini di Diaz ti stanno dietro: porta Lance all'ospedale. + +{=================================== MISSION TABLE KICKSTT ===================================} + +[KICK1_2:KICKSTT] +~r~Non sei tornato abbastanza velocemente alla tua moto! + +[KICK1_7:KICKSTT] +~r~Hai distrutto la moto! + +[KICK1_8:KICKSTT] +~g~Ritorna sulla moto! + +[KICK1_T:KICKSTT] +TEMPO IMPIEGATO: + +[KICKTM:KICKSTT] +~b~DURATA EVENTO: ~1~:~1~ + +[KICKTM2:KICKSTT] +~b~DURATA EVENTO: ~1~:0~1~ + +[GETBIKE:KICKSTT] +~g~Hai ~1~ secondi per tornare sulla tua moto prima che la missione abbia termine. + +[KICK1_1:KICKSTT] +~g~Completa il tracciato il più velocemente possibile. + +[KICK1_6:KICKSTT] +~g~Complimenti! + +[KICK_10:KICKSTT] +~g~Usa il Sanchez per completare il percorso passando per tutti i punti di controllo. + +[KICK_12:KICKSTT] +~g~Ti sei bloccato! + +[KICK_13:KICKSTT] +~g~Ci hai messo troppo tempo! + +[KICK_11:KICKSTT] +~g~Per abbandonare la missione, posizionati a piedi sul ~q~segnalino rosa~g~. + +{=================================== MISSION TABLE LAWYER1 ===================================} + +[LAW1_A:LAWYER1] +Vai a riposarti un po', ha detto... + +[LAW1_B:LAWYER1] +...ho passato tutta la sera su questa seggiola a bere caffè con la luce spenta. + +[LAW1_C:LAWYER1] +È un disastro. Siamo veramente fottuti, amico! + +[LAW1_D:LAWYER1] +Quei gorilla, non sto scherzando, verranno qui da me e mi staccheranno la testa. È ridicolo! + +[LAW1_E:LAWYER1] +NON mi sono laureato in giurisprudenza per questo! Bene, dimmi un po': che cosa hai intenzione di fare? + +[LAW1_F:LAWYER1] +Sta zitto, siediti e rilassati. Ti dirò che cosa faremo. + +[LAW1_G:LAWYER1] +Tu dovrai scoprire chi ha preso la nostra cocaina... e io li farò fuori. + +[LAW1_H:LAWYER1] +È una buona idea. Anzi, è un'OTTIMA idea. Fammici pensare, fammici pensare, fammici pensare. + +[LAW1_I:LAWYER1] +Ah! C'è quel Colonnello in pensione, il Colonnello Juan Garcia Cortez. + +[LAW1_J:LAWYER1] +È stato lui che mi ha aiutato a preparare il piano + +[LAW1_K:LAWYER1] +ben lontano dai criminali di Vice City. OK? + +[LAW1_L:LAWYER1] +Adesso ascolta: sta organizzando un party nella baia sul suo costoso yacht + +[LAW1_M:LAWYER1] +e tutte le personalità di spicco di Vice City saranno presenti. + +[LAW1_N:LAWYER1] +Io ho un invito, chiaramente ne ho uno... + +[LAW1_O:LAWYER1] +ma non ho la minima intenzione di mettere la testa fuori dalla porta, per nessun motivo! + +[LAW1_P:LAWYER1] +Te l'ho detto, sta zitto! Ci andrò io... + +[LAW1_Q:LAWYER1] +Whoa, whoa, whoa! Ehi, anche a me piace lo stile del 78, ma, lo sai, non si tratta di una festa con birra e spogliarelliste. + +[LAW1_R:LAWYER1] +Intendo dire, senza offesa, che molta gente potrebbe girarsi a guardarti per la ragione sbagliata... + +[LAW1_S:LAWYER1] +Vuoi dire che c'è qualcosa che non va in come mi vesto? + +[LAW1_T:LAWYER1] +OK, ascolta. Fermati da Rafael's, digli che ti mando io e lui si occuperà di renderti presentabile. + +[LAW1_U:LAWYER1] +OK, forza, andiamo... + +[LAWP_1:LAWYER1] +Buenas noches. + +[LAWP_2:LAWYER1] +Da quanto ho capito, è qui per conto di Mr. Rosenberg. + +[LAWP_3:LAWYER1] +Spero che gli avvenimenti recenti non abbiano avuto effetti sulla sua salute o, uh, + +[LAWP_4:LAWYER1] +sulla sua sanità mentale, Mr...? + +[LAWP_5:LAWYER1] +Vercetti. Ha solo avuto un attacco di... agorafobia. + +[LAWP_6:LAWYER1] +Eccellente, eccellente. E lei? + +[LAWP_7:LAWYER1] +Voglio solo la mia merce. + +[LAWP_8:LAWYER1] +Ah. Si è trattato di sfortunate circostanze per tutte le persone coinvolte. + +[LAWP_9:LAWYER1] +Chiaramente, ho dato il via a delle ricerche personali + +[LAWP_10:LAWYER1] +ma una situazione così delicata richiede tempo. + +[LAWP_11:LAWYER1] +Magari ne parleremo più tardi, va bene? + +[LAWP_12:LAWYER1] +Nel frattempo, le presento mia figlia, + +[LAWP_13:LAWYER1] +Mercedes! + +[LAWP_14:LAWYER1] +Cara mia, perché non ti prendi cura del nostro ospite mentre io mi occupo delle mie cose? + +[LAWP_15:LAWYER1] +Certamente, papà. + +[LAWP_16:LAWYER1] +Mi scusi. + +[LAWP_17:LAWYER1] +Mercedes? + +[LAWP_18:LAWYER1] +Non è facile portare questo nome. + +[LAWP_19:LAWYER1] +Comunque, lasci che le presenti alcuni dei nostri distinti ospiti... + +[LAWP_20:LAWYER1] +Lui è Alex Shrub, il nostro deputato, con Candy Suxxx, la famosa star siliconata... + +[LAWP_21:LAWYER1] +Avete conosciuto la mia dolce moglie, Laura? + +[LAWP_22:LAWYER1] +Veramente questa è Candy, mia moglie si trova in Alabama ora. + +[LAWP_23:LAWYER1] +E da quella parte abbiamo la stella dei Vice City Mambas, BJ. + +[LAWP_24:LAWYER1] +un vero ammaliatore + +[LAWP_25:LAWYER1] +L'ho placcato, davvero, e l'ho fatto finire su una sedia a rotelle! + +[LAWP_26:LAWYER1] +Ah, ah, questa è buona! + +[LAWP_27:LAWYER1] +Beh, adesso sto cercando di acquistare dei terreni di qualità. + +[LAWP_28:LAWYER1] +E quella specie di anfibio da piscina è Jezz Torrent, + +[LAWP_29:LAWYER1] +voce solista dei Love Fist. + +[LAWP_30:LAWYER1] +Ve lo posso dire... sapete come giocano a ping-pong in Tailandia? + +[LAWP_31:LAWYER1] +Da non crederci... + +[LAWP_32:LAWYER1] +di certo, non richiede l'uso della racchetta, se capite cosa intendo! + +[LAWP_33:LAWYER1] +Impotente. + +[LAWP_34:LAWYER1] +E il loquace trio. + +[LAWP_35:LAWYER1] +Quella pustola sudata e addormentata è il braccio destro di papà, Gonzalez, + +[LAWP_36:LAWYER1] +mentre gli altri due sono il pastore Richards + +[LAWP_37:LAWYER1] +e il regista pseudo-intellettuale, Steve Scott. + +[LAWP_38:LAWYER1] +...pare che abbiano una passione per le ninfomani... + +[LAWP_39:LAWYER1] +...quando lo squalo gigante arriva e + +[LAWP_40:LAWYER1] +strappa a morsi i loro uccelli! + +[LAWP_41:LAWYER1] +Ah! Non si è mai visto qualcosa del genere prima, vero? + +[LAWP_42:LAWYER1] +Colonnello! + +[LAWP_43:LAWYER1] +I suoi party sono sempre un successo! Ah ah ah! + +[LAWP_44:LAWYER1] +Posso solo chiedere scusa per il mio ritardo. + +[LAWP_45:LAWYER1] +Ah, de nada amigo. E come vanno gli affari? + +[LAWP_46:LAWYER1] +Sai, sono molto difficili...i nemici sono sempre alle costole. + +[LAWP_47:LAWYER1] +Il tempo di ricompensare gli amici e liquidare i nemici, amigo. + +[LAWP_48:LAWYER1] +Chi è il chiacchierone? + +[LAWP_49:LAWYER1] +Ricardo Diaz. È Mr. Coca. + +[LAWP_50:LAWYER1] +Mercedes! + +[LAWP_51:LAWYER1] +Oh, stavo giusto riaccompagnando i miei amici in città. + +[LAWP_52:LAWYER1] +Un'altra volta, Ricardo! + +[LAWP_53:LAWYER1] +Andiamocene. + +[LAWP_54:LAWYER1] +Anzi, portatemi al club Pole Position. + +[LAW1_2:LAWYER1] +~g~Raggiungi lo yacht del Colonnello. + +[LAW1_4:LAWYER1] +~r~Hai ucciso la figlia del Colonnello! + +[LAW1_5:LAWYER1] +Lavorerai per mio padre? + +[LAW1_6:LAWYER1] +Forse. + +[LAW1_7:LAWYER1] +Ti dispiace se appoggio la mano tra le tue gambe? + +[LAW1_8:LAWYER1] +Forse... + +[LAW1_9:LAWYER1] +È così difficile avere un padre ricco e potente. Vamos. + +[LAW1_10:LAWYER1] +Ci vediamo in giro, stallone! + +[LAW1_11:LAWYER1] +Sono sicuro di sì. + +[LAW1_12:LAWYER1] +Hmmm... bella moto. + +[LAW1_13:LAWYER1] +No! La mia moto! + +[LAW1_3:LAWYER1] +~g~Porta la figlia del Colonnello al club Pole Position. + +[HELP20:LAWYER1] +Segui il ~h~segnale a forma di maglietta~w~ per trovare Rafael's. + +[LAW1_14:LAWYER1] +Accidenti, mi piace: la tua moto va alla grande. + +[LAW1_15:LAWYER1] +Eh sì, l'ho appena presa da Howlin' Pete's. + +{=================================== MISSION TABLE LAWYER2 ===================================} + +[LAW2_A:LAWYER2] +Ah! Beh, spero tu ti sia divertito. Io stavo quasi per impazzire dalla preoccupazione. Che cos'hai scoperto? + +[LAW2_B:LAWYER2] +Che ci sono più criminali a piede libero in città che nella prigione. Abbiamo bisogno di qualcuno di strada... + +[LAW2_C:LAWYER2] +OK, fammici pensare, fammici pensare, fammici pensare... + +[LAW2_D:LAWYER2] +Ah! Trovato! + +[LAW2_E:LAWYER2] +OK, c'è questo inglese, un tizio legato al campo della musica, + +[LAW2_F:LAWYER2] +mi sembra si chiami Kent Paul. + +[LAW2_G:LAWYER2] +Comunque, ha ficcato il naso nelle chiappe di metà della gente di Vice City + +[LAW2_H:LAWYER2] +per cui, se qualcuno sa dove sono questi 20 chili di coca, + +[LAW2_I:LAWYER2] +è sicuramente lui, chiaro? È sempre al Malibu. + +[LAW2_J:LAWYER2] +Andrò a trovarlo. + +[LAW2B_A:LAWYER2] +Ehi, da dove sbuchi? + +[LAW2B_B:LAWYER2] +Ho cercato per anni un fiorellino come te, bellezza... + +[LAW2B_C:LAWYER2] +Kent Paul, tesoro. Sì, sono il capo da queste parti. + +[LAW2B_D:LAWYER2] +Sto cercando un tipo inglese... + +[LAW2B_E:LAWYER2] +Risolvo i problemi, capisci cosa intendo? + +[LAW2B_F:LAWYER2] +Ti tratterò bene: qualsiasi cosa tu voglia, te la farò avere, cara. + +[LAW2B_G:LAWYER2] +Non preoccuparti di niente, bellezza. + +[LAW2B_H:LAWYER2] +Vattene, tesoro. + +[LAW2B_I:LAWYER2] +Oi oi oi oi oi! + +[LAW2B_J:LAWYER2] +Sei Kent Paul? Sono un amico di Rosenberg... + +[LAW2B_K:LAWYER2] +Rosenberg... Rosenberg... Oh, l'avvocato matto! + +[LAW2B_L:LAWYER2] +Quel tipo potrebbe difendere un innocente fino alla sedia elettrica! + +[LAW2B_M:LAWYER2] +Dacci un altro drink, amico. + +[LAW2B_N:LAWYER2] +Un altro comico. + +[LAW2B_O:LAWYER2] +Ascoltami: mi hanno soffiato venti chili e un sacco di contanti... + +[LAW2B_P:LAWYER2] +Droga amico? È roba da delinquenti. + +[LAW2B_Q:LAWYER2] +Che cosa ne sai? + +[LAW2B_R:LAWYER2] +Oi oi! Ciò che volevo dire è + +[LAW2B_S:LAWYER2] +che c'è uno chef che spaccia sottobanco nella cucina di un hotel su Ocean Drive. + +[LAW2B_T:LAWYER2] +Recentemente, mi è sembrato molto felice di come gli buttava... Potresti passare a trovarlo e investigare... + +[LAW2B_U:LAWYER2] +Lo farò... ci becchiamo in giro. + +[LAW2B_V:LAWYER2] +Beh, sì certo! Vai, vai pure idiota. Ci penserò io a tenerti a bada! + +[LAW2B_W:LAWYER2] +Dammi un bicchiere... e dov'è finita quella sgualdrina? + +[LAW2C_A:LAWYER2] +Oh, ottimo lavoro, sei proprio un duro. Riducilo in poltiglia, questo sicuramente lo aiuterà a diventare più loquace! + +[LAW2C_B:LAWYER2] +Ne vuoi anche tu? + +[LAW2C_C:LAWYER2] +Ehi, calma. Quel che va bene a te, va bene a me, amico. + +[LAW2C_D:LAWYER2] +Ah sì? E di cosa si tratta? + +[LAW2C_E:LAWYER2] +I verdoni... e l'amica bianca del mio povero fratello. Sfortunatamente, hai appena fatto fuori il nostro contatto. + +[LAW2C_F:LAWYER2] +Gli incidenti capitano. Scompari. + +[LAW2C_G:LAWYER2] +Ehi, ehi, whoa. Non c'è bisogno di recitare la parte del 'Giustiziere solitario'. + +[LAW2C_H:LAWYER2] +Io la vedo così: siamo due hombres in una strana città. Dobbiamo guardarci a vicenda le spalle. + +[LAW2C_I:LAWYER2] +Le mie spalle stanno benissimo, fratello... + +[LAW2C_J:LAWYER2] +Ne sei sicuro? Ehi, prendi questa. + +[LAW2C_K:LAWYER2] +Seguimi! + +[LAW2_1:LAWYER2] +Che cos'hai da guardare? + +[LAW2_2:LAWYER2] +Ti conviene iniziare a parlare... + +[LAW2_3:LAWYER2] +Maledetto stronzo! + +[LAW2_4:LAWYER2] +Da questa parte! + +[LAW2_5:LAWYER2] +Vediamo cosa riesco a scoprire. Ci vediamo, Tommy. + +[LAW2_6:LAWYER2] +~g~Vai al Malibu e trova Kent Paul. + +[LAW2_7:LAWYER2] +~g~Trova lo chef in Ocean Drive. + +[LAW2_10:LAWYER2] +~g~Torna in macchina all'hotel. + +[LAW2_11:LAWYER2] +~g~Raccogli il suo cellulare. + +[LAW2_12:LAWYER2] +Cellulare acquisito! Adesso potrai ricevere telefonate! + +[LAW2_13:LAWYER2] +~g~Hai lasciato Lance indietro! Vallo a prendere! + +[LAW2_14:LAWYER2] +Andiamocene da qui! + +[GUN_2A:LAWYER2] +Tieni premuto il ~h~~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~! + +[GUN_2C:LAWYER2] +Tieni premuto il ~h~~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~! + +[GUN_2D:LAWYER2] +Tieni premuto il ~h~~k~~PED_LOCK_TARGET~~w~ per la ~h~mira automatica~w~ e premi il ~h~~k~~PED_FIREWEAPON~~w~ per ~h~sparare~w~! + +[HELP17:LAWYER2] +Premi il ~h~~k~~PED_FIREWEAPON~~w~ per attaccare lo chef. + +[HELP18:LAWYER2] +Premi il ~h~~k~~PED_FIREWEAPON~~w~ per attaccare lo chef. + +[LAW3_11:LAWYER2] +Posizionati sul ~q~segnalino rosa~w~ per vedere le armi in offerta. + +[LAW3_12:LAWYER2] +Puoi selezionare l'arma premendo il ~h~tasto direzionale~w~ a ~h~sinistra~w~ o a ~h~destra~w~. + +[LAW3_13:LAWYER2] +Se hai denaro a sufficienza puoi comprare armi premendo il ~h~~k~~PED_SPRINT~~w~. + +[LAW3_14:LAWYER2] +Puoi uscire da un negozio premendo il ~h~~k~~VEHICLE_ENTER_EXIT~~w~. + +[LAW3_15:LAWYER2] +Segui il ~h~segnale a forma di pistola~w~ per trovare ~h~Ammu-Nation~w~. + +[LAW2_15:LAWYER2] +~g~Raggiungi Ammu-Nation. + +[LAW2_K:LAWYER2] +Vedi di star calmo. + +[LAW2_16:LAWYER2] +Devi sapere qualcosa su questa città: hai bisogno di potenza di fuoco. + +[LAW2_17:LAWYER2] +Forza, l'armaiolo locale è solo a qualche isolato di distanza. + +[LAW2_18:LAWYER2] +Tommy, tutti quanti hanno bisogno di un po' di relax ogni tanto. + +[LAW2_19:LAWYER2] +Ecco il club a luci rosse Pole Position. Facci un salto ogni tanto. + +{=================================== MISSION TABLE LAWYER3 ===================================} + +[LAW3_A:LAWYER3] +Aarrgh! Maledizione, sei tu! Oddio, avrò bisogno di un nuovo paio di pantaloni! + +[LAW3_B:LAWYER3] +Ehi, quei psicopatici del nord hanno drizzato le orecchie e verranno a visitarci molto presto. + +[LAW3_C:LAWYER3] +Dove diavolo sono i fottuti soldi? + +[LAW3_D:LAWYER3] +Rilassati, rilassati. Non siamo ancora arrivati a questa parte. + +[LAW3_E:LAWYER3] +Pensavo ti stessi prendendo cura di questo aspetto, davvero. + +[LAW3_F:LAWYER3] +E adesso quei delinquenti si aspettano addirittura da noi un favore. + +[LAW3_G:LAWYER3] +Vuoi dire che io devo far loro un favore. + +[LAW3_H:LAWYER3] +Oh, beh, certo, proprio così. Credi davvero che potrei intimidire una giuria? + +[LAW3_I:LAWYER3] +Non riuscirei a intimidire neppure un bambino... e credimi, ci ho provato. + +[LAW3_J:LAWYER3] +Adesso o ci riusciamo, oppure il cugino di Forelli, Giorgio, si becca cinque anni per frode. + +[LAW3_K:LAWYER3] +Devi CONVINCERE questi tipi! + +[LAW3_L:LAWYER3] +Capisco. Devo aiutare la giuria a cambiare idea. Non preoccuparti... + +[LAW3_M:LAWYER3] +No no no no NO! Ci ho provato. Le cose con la giuria non sono andate per il meglio, + +[LAW3_N:LAWYER3] +per cui ASSICURATI che cambino davvero idea. + +[LAW3_1:LAWYER3] +Giorgio ti manda i suoi saluti. + +[LAW3_2:LAWYER3] +Ricorda: colpevole è una parolaccia. + +[LAW3_3:LAWYER3] +Innocente fino a prova contraria... ecco cosa dire. + +[LAW3_4:LAWYER3] +Lo sai che non è colpevole. + +[LAW3_5:LAWYER3] +Ti ricordi di Giorgio? Ricordati che è innocente. + +[LAW3_6:LAWYER3] +Non colpevole... Capito? Bene. + +[LAW3_8:LAWYER3] +~r~Hai ucciso un giurato! + +[LAW3_9:LAWYER3] +~g~Distruggi l'auto del giurato per farlo uscire! + +[HELP40:LAWYER3] +Puoi distruggere le macchine utilizzando un martello o un'arma simile. + +[LAW3_10:LAWYER3] +~g~Visita il ~h~ferramenta~g~ per comprare un'arma corpo a corpo. + +[LAW3_20:LAWYER3] +~g~Distruggi la macchina del giurato! + +[LAW3_21:LAWYER3] +Non posso credere che sta succedendo! + +[LAW3_22:LAWYER3] +Incredibile! + +[LAW3_23:LAWYER3] +OK! OK! Ho compreso il messaggio! + +[LAW3_24:LAWYER3] +~g~Quel martello potrebbe tornare utile. + +[LAW3_7:LAWYER3] +~g~Minaccia i due giurati, ma NON ucciderli! + +[HELP23:LAWYER3] +Segui il ~h~segnale a forma di martello~w~ se vuoi comprare delle armi corpo a copro dal ferramenta. + +[LAW3_16:LAWYER3] +Stupidi idioti del Florida. + +[LAW3_17:LAWYER3] +Via dai piedi. + +{=================================== MISSION TABLE LAWYER4 ===================================} + +[LAW4_A:LAWYER4] +Avery, è chiaro che... Tommy! Tommy! Qualche progresso? No no no, parliamone dopo, mi racconti dopo. + +[LAW4_B:LAWYER4] +Tommy, ti presento Avery Carrington: credo tu lo abbia incontrato al party. + +[LAW4_C:LAWYER4] +Non di persona. + +[LAW4_D:LAWYER4] +Piacere. + +[LAW4_E:LAWYER4] +Avery ha una proposta da farci. + +[LAW4_F:LAWYER4] +Non ti sembra che abbiamo già altro da fare? + +[LAW4_G:LAWYER4] +Sto cercando di tenere lontani i lupi dalla porta, per cui apprezzerei un po' di collaborazione. + +[LAW4_H:LAWYER4] +Sono tirato come un filo e, anche se dovessi morire alla fine della settimana, desidererei non farlo da povero! + +[LAW4_I:LAWYER4] +Adesso calmatevi, tutti e due. + +[LAW4_J:LAWYER4] +Figliolo, dammi una mano e vedrò di mettere a nanna sottoterra qualsiasi stronzo vi stia dando problemi. + +[LAW4_K:LAWYER4] +OK, cosa posso fare? + +[LAW4_L:LAWYER4] +Questa società di consegne ha un deposito su un terreno di prima qualità. Non vogliono vendere. + +[LAW4_M:LAWYER4] +Si sono rintanati come dei ratti di prateria, per cui vorrei andassi a trovarli e li facessi uscire con un po' di fumo. + +[LAW4_N:LAWYER4] +Raggiungi il posto e fai scoppiare un vespaio: + +[LAW4_O:LAWYER4] +mentre la sorveglianza sarà impegnata, entra dentro e mettili fuori gioco. + +[LAW4_P:LAWYER4] +Passa pure da Rafael's per un cambio d'abito: ci potresti mettere un po', ma va bene lo stesso. + +[LAW4_Q:LAWYER4] +Potrebbe funzionare una sommossa. + +[LAW4_R:LAWYER4] +Se le cose vanno come devono, passa a trovarmi nel mio ufficio... + +[LAW4_1:LAWYER4] +Disperdetevi! L'amministrazione discuterà qualsiasi rimostranza in modo appropriato! + +[LAW4_2:LAWYER4] +Disperdetevi! Tornate a casa! + +[LAW4_3:LAWYER4] +Disperdetevi! Questo comportamento è inaccettabile! + +[LAW4_4:LAWYER4] +Disperdetevi! Rischiate di finire tutti per strada! + +[LAW4_5:LAWYER4] +Forza, ragazzi! Spacchiamo il cranio a qualche rosso! + +[LAW4_13:LAWYER4] +~g~Comincia a combattere con almeno 4 lavoratori per avviare la sommossa. + +[LAW4_14:LAWYER4] +~g~Distruggi i van nell'autorimessa! + +[HELP38:LAWYER4] +Se uccidi qualcuno con un'arma, la lascerà cadere. + +[HELP39:LAWYER4] +Puoi far esplodere i barili di esplosivo, ma tieni la distanza! + +{=================================== MISSION TABLE MIAMI_1 ===================================} + +[T4X4_1A:MIAMI_1] +~g~Hai ~1~ secondi per attraversare ~y~24~g~ punti di controllo. ~g~Puoi attraversarli in ~y~QUALSIASI ORDINE~g~. + +[T4X4_1B:MIAMI_1] +~y~ATTRAVERSA~g~ il primo punto di controllo per attivare il ~r~TIMER~g~. + +[T4X4_1C:MIAMI_1] +~1~ su 24! + +[GETBIK1:MIAMI_1] +Hai ~1~ secondi per salire su una PCJ 600! + +[GETBIK3:MIAMI_1] +~r~Hai bisogno di una PCJ 600 per affrontare questa missione! + +{=================================== MISSION TABLE MM ===================================} + +[BLOD_04:MM] +CONDIZIONI MACCHINA: + +[BLOD_05:MM] +~g~TEMPO BERSAGLIO: ~1~ minuto + +[BLOD_06:MM] +~g~TEMPO BERSAGLIO: ~1~ minuti + +[BLOD_07:MM] +NUOVO miglior tempo: ~1~ secondi + +[BLOD_08:MM] +Veicoli distrutti: ~1~ + +[BLOD_09:MM] +~1~$ + +[BLOD_10:MM] +VINCITORE!!! + +[BLOD_01:MM] +Guida attraverso i punti di controllo per aumentare il tuo tempo globale. + +[BLOD_02:MM] +Fallirai se il tempo globale scadrà a zero. + +[BLOD_03:MM] +Porta il tuo tempo globale sopra a quello bersaglio per vincere! + +{=================================== MISSION TABLE OVALRIG ===================================} + +[HOTR_01:OVALRIG] +~g~La gara dura 12 giri: solo il primo, il secondo e il terzo si qualificheranno come vincitori. + +[HOTR_02:OVALRIG] +~g~Se il tuo mezzo viene distrutto, sarai squalificato. + +[HOTR_03:OVALRIG] +~g~Se danneggi il mezzo, potrai ripararlo ai box. + +[HOTR_04:OVALRIG] +~g~Da qui potrai uscire dallo stadio. + +[HOTR_05:OVALRIG] +Condizioni macchina: + +[HOTR_06:OVALRIG] +Giri: + +[HOTR_10:OVALRIG] +Tempo di gara: + +[HOTR_09:OVALRIG] +Posizione: + +[HOTR_12:OVALRIG] +~r~La tua macchina è stata distrutta! + +[HOTR_13:OVALRIG] +~r~Non hai vinto la gara! + +[HOTR_14:OVALRIG] +~r~Sei stato squalificato! + +[HOTR_15:OVALRIG] +Tempo: ~1~:~1~ + +[HOTR_16:OVALRIG] +Tempo: ~1~:0~1~ + +[HOTR_17:OVALRIG] +Tempo migliore: ~1~:~1~ + +[HOTR_18:OVALRIG] +Tempo migliore: ~1~:0~1~ + +[HOTR_19:OVALRIG] +Tempo migliore: NA + +[HOTR_20:OVALRIG] +Nuovo tempo migliore: ~1~:~1~ + +[HOTR_21:OVALRIG] +Nuovo tempo migliore: ~1~:0~1~ + +[HOTR_22:OVALRIG] +Miglior risultato: NA + +[HOTR_23:OVALRIG] +Miglior risultato: PRIMO + +[HOTR_24:OVALRIG] +Miglior risultato: SECONDO + +[HOTR_25:OVALRIG] +Miglior risultato: TERZO + +[HOTR_26:OVALRIG] +Miglior risultato: ~1~ + +[HOTR_27:OVALRIG] +Miglior giro: ~1~.~1~ secondi + +[HOTR_28:OVALRIG] +Miglior giro: ~1~.0~1~ secondi + +[HOTR_29:OVALRIG] +~1~$ + +[HOTR_30:OVALRIG] +PRIMA POSIZIONE + +[HOTR_31:OVALRIG] +SECONDA POSIZIONE + +[HOTR_32:OVALRIG] +TERZA POSIZIONE + +[HOTR_33:OVALRIG] +Miglior giro: NA + +[HOTR_11:OVALRIG] +Nuovo giro migliore: ~1~.~1~ secondi + +[HOTR_34:OVALRIG] +Nuovo giro migliore: ~1~.0~1~ secondi + +{=================================== MISSION TABLE PHIL1 ===================================} + +[PHIL1_A:PHIL1] +Phil? + +[PHIL1_B:PHIL1] +CORRI! + +[PHIL1_C:PHIL1] +Corri! + +[PHIL1_E:PHIL1] +Merda Phil, tu bevi quella schifezza? + +[PHIL1_F:PHIL1] +Accidenti, non la devi mica bere... + +[PHIL1_G:PHIL1] +basta una sniffata per sballare. + +[PHIL1_H:PHIL1] +Ascoltami Phil, mi hai detto che potevi fornirmi delle armi... + +[PHIL1_I:PHIL1] +Stanne certo! + +[PHIL1_J:PHIL1] +C'è un trafficante d'armi Messicano con cui ho fatto un po' di affari. + +[PHIL1_K:PHIL1] +Fa la sua consegna settimanale più o meno adesso. + +[PHIL1_L:PHIL1] +Sperona il suo camion e fai cadere le armi prima che scappi. + +[PHIL1_M:PHIL1] +E già che ci sei, fammi un favore: + +[PHIL1_N:PHIL1] +fallo fuori. + +[PHI1_01:PHIL1] +~g~Fai cadere le armi dal camion del trafficante. + +[PHI1_02:PHIL1] +~g~Il trafficante ha lasciato cadere il carico. Distruggi le casse e recupera le armi. + +[PHI1_03:PHIL1] +~g~Sembra abbiano chiesto rinforzi. + +[PHI1_04:PHIL1] +~g~Adesso finisci i trafficanti ancora vivi. + +[PHI1_HP:PHIL1] +Quando utilizzi una granata a tempo, lanciala e poi falla esplodere in un secondo momento. + +[PHIL1_O:PHIL1] +Hoooooweeeeee! + +[PHIL1_D:PHIL1] +Nona avvicinare mai una fiamma alla broda di Phil Cassidy! + +{=================================== MISSION TABLE PHIL2 ===================================} + +[PHIL2_A:PHIL2] +Ehi Phil, come butta? + +[PHIL2_B:PHIL2] +Eeeeehi Tommy, Come va? E passcato moolto tempo... + +[PHIL2_C:PHIL2] +Devi davvero smetterla con quella broda, + +[PHIL2_D:PHIL2] +accidenti, puzza di solvente. Mi fa bruciare gli occhi... + +[PHIL2_E:PHIL2] +Shshs shhh silenzio Tommy, + +[PHIL2_F:PHIL2] +e vieni qua che ho qualcoscia da farti vedere... qualcoscia... + +[PHIL2_G:PHIL2] +Woof! Oddio! Riuscirei a sniffarlo da qui in fondo! Mi gira già la testa! + +[PHIL2_H:PHIL2] +Non preoccuparti per l'odore, Tommy, guarda quescto... + +[PHIL2_I:PHIL2] +La fottuta batteria o qualcosc'altro... Scen'è un altra sulla mensola... + +[PHIL2_J:PHIL2] +TA-DAAA! + +[PHIL2_K:PHIL2] +Ommerda! + +[PHI2_01:PHIL2] +~g~Forza, porta Phil all'ospedale. + +[PHI2_03:PHIL2] +~r~Phil Cassidy è morto!!! Adesso chi ti rifornirà di armi? + +[PHI2_05:PHIL2] +Non all'ospedale, amico! Troppi poliziotti e Viet Cong! + +[PHI2_06:PHIL2] +C'è un vecchio ex-medico militare che mi deve qualche favore e una falciatrice. + +[PHI2_07:PHIL2] +Si trova lungo Little Havana, oooh guarda, un pesce gigante. + +[PHI2_08:PHIL2] +Attento! Nemico tra gli alberi! + +[PHI2_09:PHIL2] +Mi sbaglio o le strade sono di gelatina? + +[PHI2_10:PHIL2] +Cucchiaio Storto a Mamma Gallina, mi ricevi? + +[PHI2_11:PHIL2] +Cucchiaione-ione Woo Woo Woooo! + +[PHI2_12:PHIL2] +Sta venendo per me, amico! + +[PHI2_13:PHIL2] +Piume nere che svolazzano dappertutto... + +[PHI2_14:PHIL2] +È così bello, amico... così bello... ma così freddo... + +[PHI2_15:PHIL2] +10-4, abbiamo un guidatore ubriaco. + +[PHI2_04:PHIL2] +SALUTE DI PHIL: + +[PHI_AS1:PHIL2] +PHILS PLACE COMPLETATO + +[PHI_AS2:PHIL2] +~g~Nuove armi disponibili presso Phils Place. + +{=================================== MISSION TABLE PIZZA ===================================} + +[PIZ1_01:PIZZA] +~g~Vai a consegnare queste pizze: devi lanciarle ai clienti. Fai un giro e tira le pizze. + +[PIZ1_02:PIZZA] +~g~Hai lanciato tutte le pizze, torna indietro e prendine altre. + +[PIZ1_05:PIZZA] +~g~Hai cinque minuti per consegnare le pizze prima che i clienti chiamino un altro negozio di consegne. + +[PIZ1_07:PIZZA] +~r~Hai ucciso un cliente! Sei licenziato! + +[PIZ1_08:PIZZA] +~r~Tempo scaduto. Sei licenziato! + +[PIZ1_09:PIZZA] +~r~Hai distrutto il nostro mezzo! Sei licenziato! + +[PIZ1_11:PIZZA] +Ehi! Rimettiti in sella! + +[PIZ1_12:PIZZA] +Pizze mancanti: + +[PIZ1_06:PIZZA] +Premi il ~h~~k~~TOGGLE_SUBMISSIONS~~w~ mentre sei in moto per interrompere la missione. + +[PIZ1_13:PIZZA] +Consegnale belle calde e fragranti. + +[PIZ1_14:PIZZA] +Amico, eccoti le pizze. + +[PIZ1_15:PIZZA] +Ehi, amico, consegnale in fretta. + +[PIZ1_16:PIZZA] +Che cosa stai aspettando, amico? Hai delle pizze da consegnare! + +[PIZ1_17:PIZZA] +So che non volevi fare il fattorino delle pizze, ma non me ne frega niente. + +[PIZ1_18:PIZZA] +Consegna queste. + +[PIZ1_19:PIZZA] +Devi consegnare queste. + +[PIZ1_20:PIZZA] +Forza, ragazzo, consegna queste o sei licenziato. + +[PIZ1_21:PIZZA] +C'è gente in attesa, amico. + +[PIZ1_22:PIZZA] +Che cosa stai aspettando? Devono essere consegnate! + +[PIZ1_23:PIZZA] +Consegna queste maledette pizze. + +[PIZ1_24:PIZZA] +Forza, consegnale calde. + +[PIZ1_25:PIZZA] +Ehi, ti occuperesti di queste? + +[PIZ1_26:PIZZA] +Ehi, consegna subito queste: forza amico. + +[PIZ1_27:PIZZA] +Forza, siamo di fretta: consegnale subito. + +[PIZ1_28:PIZZA] +Ancora tu? Bene, consegnale in fretta, amico. + +[PIZ1_29:PIZZA] +Non c'è tempo da perdere, consegnale tutte. + +[PIZ1_30:PIZZA] +Muoviti, brutto pelandrone, consegna questa roba in tempo. + +[PIZ1_31:PIZZA] +Non otterrai una promozione se non ti muoverai più in fretta. + +[PIZ1_32:PIZZA] +~r~La pizza è troppo calda per te? + +[PIZ1_33:PIZZA] +~g~Torna alla pizzeria per altre ordinazioni. + +[PIZ1_34:PIZZA] +~g~Pizze consegnate, ecco i soldi. + +[PIZ_WON:PIZZA] +Missione Pizza completata. Valore massimo dell'salute aumentato a 150. + +{=================================== MISSION TABLE PORN1 ===================================} + +[POR1_A:PORN1] +Azione! + +[POR1_B:PORN1] +Whoa! Questo sì che è un bel... + +[POR1_C:PORN1] +30 centimetri, è questa la norma, baby. + +[POR1_D:PORN1] +TAGLIA! Chi è quell'idiota? Tu! TU! Che cosa stai facendo sul mio set? PERCHÉ? + +[POR1_E:PORN1] +Cosa sono tute queste stronzate? + +[POR1_F:PORN1] +Alieni? Canne da pesca? + +[POR1_G:PORN1] +Chi ha mai visto uno squalo così grande? + +[POR1_H:PORN1] +Tutta questa roba deve andarsene. + +[POR1_I:PORN1] +Perché sei venuto a rompere, idiota? + +[POR1_J:PORN1] +Eh? + +[POR1_K:PORN1] +Per la passera, ecco perché! Che cos'è questo? + +[POR1_L:PORN1] +Questa è la mia arte. SORVEGLIANZA! + +[POR1_M:PORN1] +Ascolta, lurido fighettino, ti ho comprato. Ho comprato tutto questo. + +[POR1_N:PORN1] +E ho intenzione di cambiare le cose da queste parti... + +[POR1_O:PORN1] +Ti farò ricco. + +[POR1_P:PORN1] +Uh. Tu sei... tu... tu sei Tommy Vercetti? Pensavo che tu fossi... + +[POR1_Q:PORN1] +Esatto. + +[POR1_R:PORN1] +Faremo numerosi cambiamenti da queste parte e inizieremo a fare soldi veri. + +[POR1_S:PORN1] +Effettivamente, hai mai pensato che... + +[POR1_T:PORN1] +Ma prima dobbiamo trovare delle tope che meritino. + +[POR1_U:PORN1] +Sì. Le ragazze sono carine, ma sai... Wow! + +[POR1_02:PORN1] +~g~Metti fuori gioco il pappone di Candy e poi passa a prenderla. + +[POR1_04:PORN1] +Yo, Candy. Sto cercando delle attrici talentuose: sei interessata? + +[POR1_05:PORN1] +Certo! Ma prima dovrai parlare con il mio agente... + +[POR1_06:PORN1] +Che DIAVOLO stai facendo? + +[POR1_07:PORN1] +Avresti fatto meglio a stare a casa oggi! + +[POR1_7B:PORN1] +Ma hai visto questo stronzo? + +[POR1_08:PORN1] +Ehi Mercedes! + +[POR1_09:PORN1] +Ehi Tommy, vuoi divertirti? + +[POR1_10:PORN1] +Non adesso, tesoro. Sei interessata a fare dei film? + +[POR1_11:PORN1] +Certo, sempre che possano premiare il mio talento. + +[POR1_13:PORN1] +~g~Porta le ragazze allo studio e presentale a Steve. + +[POR1_14:PORN1] +Sei assunta! + +[POR1_15:PORN1] +Ehi Tommy, vieni anche tu per un riscaldamento? + +[POR1_17:PORN1] +Whoa, che bello squalo! + +[POR1_18:PORN1] +~r~Mercedes è morta! + +[POR1_20:PORN1] +Tommy, dove stai andando? Torna qui! + +[POR1_21:PORN1] +Dove stai andando? + +[POR1_22:PORN1] +Tommy, quando passeremo un po' di tempo io e te da soli? + +[POR1_01:PORN1] +~g~Candy Suxxx sarebbe perfetta per il ruolo da protagonista! + +[POR1_12:PORN1] +~g~Porta Candy con te all'appuntamento con Mercedes. + +[POR1_16:PORN1] +Magari più tardi... + +[POR1_24:PORN1] +~g~Torna indietro e prendi Candy. + +[POR1_25:PORN1] +~g~Hai lasciato Candy indietro, torna a prenderla. + +[POR1_23:PORN1] +~g~Candy starà lavorando in ~h~Downtown~g~. + +[POR1_26:PORN1] +~g~Ecco Candy, sembra sia stata ancora con il deputato Shrub. + +[POR1_27:PORN1] +Forza, andiamo. + +[POR1_28:PORN1] +Tommy, fai attenzione! Il mio seno siliconato non è ancora assicurato! + +[POR1_29:PORN1] +È questo il modo di guidare? + +[POR1_30:PORN1] +Non posso recitare dopo tutto questo! + +[POR1_31:PORN1] +Cosa? Stai cercando di uccidermi? Credevo di essere una star! + +{=================================== MISSION TABLE PORN2 ===================================} + +[POR2_A:PORN2] +Come stanno andando le riprese, Steve? + +[POR2_B:PORN2] +Beh, Candy è un talento naturale e l'altra ragazza... è insaziabile! + +[POR2_C:PORN2] +Si è passata metà del cast e dello staff prima ancora che iniziassi a fare i controlli delle luci. + +[POR2_D:PORN2] +Comunque, eh, domani andiamo fuori a registrare le scene sulla barca... + +[POR2_E:PORN2] +Scene sulla barca? Quali scene sulla barca? + +[POR2_F:PORN2] +I pescatori sono presi dalla passione quando lo squalo gigante arriva e... + +[POR2_G:PORN2] +Che cosa ho detto sullo squalo gigante? + +[POR2_H:PORN2] +Ho detto: 'NIENTE SQUALO GIGANTE', tutto chiaro? + +[POR2_I:PORN2] +Lascia semplicemente le telecamere puntate sull'azione! + +[POR2_J:PORN2] +OK, OK. Ehi Tommy, ci devo pure provare, no? + +[POR2_K:PORN2] +Hai fatto stampare quei volantini? + +[POR2_L:PORN2] +Sì, ma nessuno ci farà distribuire quella roba, cioè... + +[POR2_M:PORN2] +Lasciano semplicemente troppo, uh... poco all'immaginazione. + +[POR2_N:PORN2] +Non preoccuparti per quello. + +[POR2_O:PORN2] +Ho le mie idee sulla distribuzione. + +[POR2_P:PORN2] +OK. Ehi Candy, uh, nella mia roulotte. + +[POR2_01:PORN2] +~g~C'è un idrovolante che è stato utilizzato nelle scene di alcuni vecchi film di Indy nel retro degli studi. + +[POR2_02:PORN2] +~g~Passa sopra uno dei punti di controllo per cominciare a distribuire volantini. + +[POR2_03:PORN2] +~g~Lancia i volantini fino alla fine del punto di controllo. + +[POR2_04:PORN2] +~r~CARBURANTE AL MINIMO!!! + +[POR2_05:PORN2] +Usalo per distribuire i volantini per la città. + +[DILDO:PORN2] +Carburante: + +[POR2_Q:PORN2] +Accidenti. + +[PORN2_9:PORN2] +~g~Hai ~1~ secondi per tornare sulla tua Skimmer prima che la missione abbia termine. + +{=================================== MISSION TABLE PORN3 ===================================} + +[POR3_A:PORN3] +OK, qual è il problema adesso? + +[POR3_B:PORN3] +SSShhhh! + +[POR3_C:PORN3] +Beh, dopo il suo incontro ravvicinato con le ninfo-invasori, + +[POR3_D:PORN3] +il nostro eroe non riesce a pensare ad altro che a questa immensa montagna fallica... + +[POR3_E:PORN3] +ed è qui che vogliamo girare la scena con la tinozza piena di puré, ma poi noi... + +[POR3_F:PORN3] +Non me ne può fregar di meno, + +[POR3_G:PORN3] +Basta che continui, continui, continui. + +[POR3_H:PORN3] +Ehi Tommy... + +[POR3_I:PORN3] +mi hai accennato qualcosa al telefono riguardo dei problemi legali... + +[POR3_J:PORN3] +Ah sì! Il deputato Alex Shrub, lanciato verso la cattura di nuovi elettori, ha deciso di conquistare i puritani. + +[POR3_K:PORN3] +Si mormora che voglia supportare delle misure per limitare, diciamo, + +[POR3_L:PORN3] +l'aspetto più carnale della grande industria nazionale dell'intrattenimento. + +[POR3_M:PORN3] +Ottimo. + +[POR3_N:PORN3] +Candy! Conosci Shrub, + +[POR3_O:PORN3] +voi ragazzi vi siete lanciati in qualcosa di perverso? + +[POR3_P:PORN3] +Oh sì, oh sì, oh sì! Sì sì sì SÌ OOOoooh! + +[POR3_Q:PORN3] +Ti prego, dimmi che lo hai ripreso. + +[POR3_R:PORN3] +Faceva parte del... uh... o stava parlando con... + +[POR3_S:PORN3] +Ehi, non saprei dire. Comunque... + +[POR3_T:PORN3] +Ti consiglio vivamente di seguirla dopo le riprese, + +[POR3_U:PORN3] +vedi se va dritta al suo nuovo nido d'amore. + +[POR3_V:PORN3] +Hai una macchina fotografica? + +[POR3_X:PORN3] +Certo, dategliene una. + +[POR3_02:PORN3] +~r~Hai ucciso il deputato! Adesso come farai a ricattarlo? + +[POR3_03:PORN3] +~r~Hai allarmato la sorveglianza del deputato: lo porteranno immediatamente via. + +[POR3_04:PORN3] +Candy, mi chiameresti Martha? + +[POR3_05:PORN3] +Oh Alex, cioè Martha. Tutto ciò che vuoi! + +[POR3_06:PORN3] +Martha, qualcuno ci sta osservando... che cosa perversa. + +[POR3_07:PORN3] +Ehi tu. Dammi quella macchina fotografica! + +[POR3_01:PORN3] +~g~Segui la ~h~limousine~g~ di Candy. + +[POR3_15:PORN3] +~r~Hai distrutto la limousine di Candy! + +[POR3_17:PORN3] +~g~Torna allo studio con il rullino. + +[POR3_19:PORN3] +~r~Hai finito il rullino! + +[POR3_21:PORN3] +~g~Hai perso la limousine di Candy! + +[POR3_22:PORN3] +~g~L'hotel WR Chariot di fronte al balcone dovrebbe essere un luogo ideale per scattare le foto. + +[POR3_23:PORN3] +~g~C'è un ingresso laterale che ti permetterà di entrare nell'hotel. + +[POR3_08:PORN3] +Tieni premuto il ~h~~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ con la macchina fotografica. + +[POR3_09:PORN3] +Tieni premuto il ~h~~k~~PED_LOCK_TARGET~~w~ per ~h~mirare~w~ con la macchina fotografica. + +[POR3_10:PORN3] +Premi il ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ per ~h~zoomare~w~ con la macchina fotografica e il ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ per ~h~allargare il campo~w~. + +[POR3_11:PORN3] +Premi il ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ per ~h~zoomare~w~ con la macchina fotografica e il ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ per ~h~allargare il campo~w~. + +[POR3_12:PORN3] +Premi il ~h~~k~~PED_FIREWEAPON~~w~ per scattare una foto. + +[POR3_13:PORN3] +Premi il ~h~~k~~PED_FIREWEAPON~~w~ per scattare una foto. + +[POR3_14:PORN3] +Premi il ~h~~k~~PED_FIREWEAPON~~w~ per scattare una foto. + +[POR3_20:PORN3] +~g~Se hai bisogno di un mezzo, usa lo ~h~Sparrow~g~ sul retro. + +[POR3_16:PORN3] +~g~Hai bisogno di tre foto di qualità di Alex Shrub con Candy. + +[POR3_24:PORN3] +FOTO SCATTATE: + +{=================================== MISSION TABLE PORN4 ===================================} + +[POR4_A:PORN4] +Mi dispiace, ma adesso proprio non riesco ad ingoiarlo. + +[POR4_B:PORN4] +Oh EDDAI tesoro! + +[POR4_C:PORN4] +È inalberato come un capodoglio, accidenti, + +[POR4_D:PORN4] +come fai a non farti coinvolgere dalla parte? + +[POR4_E:PORN4] +Ma Stevie... + +[POR4_F:PORN4] +Come sta andando il mio regista? + +[POR4_G:PORN4] +Oh, accidenti. Lo scontro tra l'integrità artistica e + +[POR4_H:PORN4] +i sobbalzi e le pompanti azioni continua senza sosta. + +[POR4_I:PORN4] +E, prima che tu me lo chieda, sì, tutti e quattro i film saranno pronti per... + +[POR4_J:PORN4] +Dolcezza, potresti PER FAVORE tenere l'anaconda nell'inquadratura, + +[POR4_K:PORN4] +mi costa più lei all'ora di te! + +[POR4_L:PORN4] +Oh, scusa Steve. + +[POR4_M:PORN4] +Pensavo, abbiamo bisogno di qualche grossa acrobazia per promuovere il lancio. + +[POR4_N:PORN4] +Qualcosa che farà davvero impatto sulla città... ti viene in mente niente? + +[POR4_O:PORN4] +Beh, nei vecchio tempi si facevano le feste di gala, + +[POR4_P:PORN4] +attori, limousine, il cielo notturno illuminato dai riflettori... + +[POR4_Q:PORN4] +Riflettori? Mi è venuta un'idea... + +[POR4_R:PORN4] +...sì, sì, sì. I vestiti pieni di lustrini, e le limousine, oh, le prime visioni... + +[POR4_S:PORN4] +Oh, sì signora, certo signora, + +[POR4_T:PORN4] +e la stampa, e gli sbarramenti di luci... + +[POR4_01:PORN4] +~g~Raggiungi ~y~Downtown~g~ e posiziona i riflettori sopra l'edificio. + +[POR4_02:PORN4] +~g~Avrai bisogno di una moto veloce per saltare da un tetto all'altro. La guardia della sorveglianza possiede una ~y~PCJ 600~g~... + +[POR4_03:PORN4] +~g~Devi raggiungere i tetti degli edifici. Ci dovrebbe essere un ascensore in uno degli uffici ai piani superiori... + +[POR4_06:PORN4] +~g~Torna all'ufficio ai piani inferiori se vuoi accedere nuovamente ai tetti. + +[POR4_07:PORN4] +~g~Avrai bisogno di una moto per saltare da un edificio all'altro. + +[POR4_08:PORN4] +~g~Lanciati attraverso la finestra per avviare il percorso. Avrai tempo fino alle 07:00 prima che diventi troppo luminoso per muoverti non visto. + +[POR4_09:PORN4] +~g~I bonus ti mostreranno il successivo edificio su cui saltare. + +[POR4_10:PORN4] +~r~È troppo tardi per arrivare in cima non visto. + +[POR4_11:PORN4] +Ritorna alla scala se vuoi accedere nuovamente ai tetti. + +[POR4_05:PORN4] +~g~Queste scale portano a un ufficio ai piani inferiori. + +[POR_AS1:PORN4] +STUDIO CINEMATOGRAFICO COMPLETATO + +[POR_AS2:PORN4] +~g~L'Inter Global Films d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +{=================================== MISSION TABLE PROT1 ===================================} + +[PRO1_B:PROT1] +Non sopporto più questo aspetto. Tommy, che ne dici? E se mettessimo un bar... + +[PRO1_D:PROT1] +Ascoltami, + +[PRO1_E:PROT1] +è giunta l'ora di prendere in mano questa città. È tutto pronto per questo. + +[PRO1_F:PROT1] +Dobbiamo cominciare a impadronirci del territorio + +[PRO1_G:PROT1] +e far sapere a Vice City che ci sono nuovi giocatori in città, mi segui? + +[PRO1_I:PROT1] +Tommy, ciò di cui hai bisogno è una facciata legale, delle proprietà. A me ha sempre fatto comodo. + +[PRO1_J:PROT1] +Dobbiamo iniziare a mostrare i denti o tutto questo lavoro sarà stato vano. + +[PRO1_K:PROT1] +I gruppi locali sanno che Diaz è morto e si rifiutano di pagare per la protezione! + +[PRO1_L:PROT1] +Potremmo provare con la corruzione... + +[PRO1_M:PROT1] +Corruzione? A fanculo la corruzione! Ti mostrerò io come si fa a spaventarli. + +[PRO1_01:PROT1] +~g~Assali i negozi e fuggi: i proprietari chiederanno la tua protezione. + +[PRO1_03:PROT1] +~r~Si trattava di un assali e fuggi, non di un assali e bevi un caffè. + +[PRO1_04:PROT1] +Tutti i miei beni, distrutti! + +[PRO1_05:PROT1] +Rovinato... ROVINATO! + +[PRO1_06:PROT1] +Maledizione, devo pagare la protezione! + +[PRO1_07:PROT1] +La mia meravigliosa vetrina! + +[PRO1_08:PROT1] +Il mio negozio, il mio bellissimo negozio. + +[PRO1_09:PROT1] +Vercetti, ricorda questo nome. + +[PRO1_10:PROT1] +Adesso gestisco io questa città. IO! + +[BUYP1:PROT1] +Non puoi comprare proprietà in certe aree della mappa. + +[BUYP2:PROT1] +Se vedi un'icona a forma di casa verde, puoi comprare quella proprietà. + +[PRO1_N:PROT1] +Sarò di ritorno fra cinque minuti... + +[PRO1_11:PROT1] +~g~Raggiungi il ~y~Mall North Point~g~ in ~y~Vice Point~g~. + +[PRO1_12:PROT1] +~g~Distruggi le vetrine di tutti i negozi e i proprietari saranno pronti a chiederti protezione. + +[PRO1_A:PROT1] +Oh, dobbiamo ridecorare questo posto, deve sembrare più vecchio. + +[PRO1_C:PROT1] +Sei il mio avvocato, Rosenberg, non il mio arredatore. Chiaro? + +[BUYP3:PROT1] +Posizionati all'interno del simbolo e premi il tasto ~h~~k~~PED_ANSWER_PHONE~~w~ per acquistare la proprietà. + +[PRO1_13:PROT1] +~g~Hai cinque minuti per distruggerli tutti. + +{=================================== MISSION TABLE PROT2 ===================================} + +[PRO2_A:PROT2] +Qual è il problema? + +[PRO2_B:PROT2] +Alcuni bar si rifiutano di pagare. + +[PRO2_C:PROT2] +Dicono di essere protetti da una gang locale di malviventi. + +[PRO2_D:PROT2] +Ma non preoccuparti, Tommy, me ne occupo io. + +[PRO2_E:PROT2] +E tu te ne occupi in questo modo? + +[PRO2_F:PROT2] +Voi due, alzate il culo... + +[PRO2_G:PROT2] +Andiamo. + +[PRO2_01:PROT2] +~g~Fai fuori le guardie che sorvegliano il Front Page Bar e scopri chi le ha rifornite. + +[PRO2_10:PROT2] +~g~Altri due sono riusciti a scappare. Rintracciali e finisci questa storia. + +[PRO2_11:PROT2] +Entra in macchina, idiota. + +[PRO2_02:PROT2] +La tua protezione ha bisogno di più protezione. + +[PRO2_03:PROT2] +Maledizione, non ancora! Non ne voglio sapere! + +[PRO2_04:PROT2] +Questi idioti operano per la DBP Security, con sede dietro all'isolato. + +[PRO2_05:PROT2] +Vedete di risolverla per i fatti vostri. + +[PRO2_06:PROT2] +Ci si vede più tardi. + +[PRO2_07:PROT2] +Sì, sì, certo. + +[PRO2_08:PROT2] +~g~La DBP Security presto scoprirà le tue intenzioni: attacca prima che si ritiri. + +[PRO2_09:PROT2] +~g~Vai a parlare con il proprietario del Front Page. + +{=================================== MISSION TABLE PROT3 ===================================} + +[PRO3_A:PROT3] +Cretino! Ma a cosa pensavi? + +[PRO3_B:PROT3] +Ti rendi conto che cosa hai combinato? + +[PRO3_C:PROT3] +Potremmo essere tutti rovinati! + +[PRO3_D:PROT3] +Il timer deve essersi inceppato. + +[PRO3_E:PROT3] +Quel posto era stato minato per saltare come una fabbrica di fuochi d'artificio. + +[PRO3_F:PROT3] +Ma qualcuno ha informato i poliziotti... + +[PRO3_G:PROT3] +Qual è il problema, ragazzi? + +[PRO3_H:PROT3] +Mike doveva dar fuoco a un locale del Mall, + +[PRO3_I:PROT3] +ma ha fatto casino con i fusibili e adesso la polizia sta indagando. + +[PRO3_J:PROT3] +Dobbiamo preparare la nostra roba e andarcene da qui! + +[PRO3_K:PROT3] +Rilassatevi, tutti e due: fatemi pensare per un attimo! + +[PRO3_L:PROT3] +Tommy Vercetti non scappa con la coda fra le gambe. + +[PRO3_M:PROT3] +I poliziotti passeranno al setaccio l'intero edificio, giusto? + +[PRO3_N:PROT3] +Ma tutto ciò richiede tempo. + +[PRO3_O:PROT3] +Dobbiamo entrare e dar fuoco noi stessi al locale. + +[PRO3_P:PROT3] +Sì, ma... + +[PRO3_Q:PROT3] +Solo un poliziotto potrebbe avvicinarsi a meno di un chilometro dal luogo! + +[PRO3_R:PROT3] +Allora andiamo come poliziotti. + +[PRO3_S:PROT3] +Recupereremo delle uniformi... e avremo bisogno di una volante. + +[PRO3_T:PROT3] +Tutto questo grazie a te, Mike. + +[PRO3_U:PROT3] +Mi dispiace. + +[PRO3_V:PROT3] +Ho capito. + +[PRO3_W:PROT3] +Quello che dobbiamo fare è attirare i poliziotti con un gestaccio, + +[PRO3_X:PROT3] +Metterli con le spalle al muro + +[PRO3_Y:PROT3] +e sopraffarli. + +[PRO3_Z:PROT3] +Ottimo piano, andiamo! + +[PRO3_A1:PROT3] +Bene. + +[PRO3_01:PROT3] +OK Lance, attira l'attenzione dei poliziotti! + +[PRO3_02:PROT3] +~g~Prendi la macchina della polizia e vai a posizionare la bomba nel Tarbrush Café dentro al Mall. + +[PRO3_03:PROT3] +~g~Hai lasciato Lance indietro, torna a prenderlo. + +[PRO3_04:PROT3] +~g~Andiamo. + +[PRO3_05:PROT3] +~r~Hai ucciso Lance! + +[PRO3_07:PROT3] +~g~È saltata la vostra copertura. Muoviti a piazzare la bomba! + +[PRO3_09:PROT3] +Legali e imbavagliali! + +[PRO3_10:PROT3] +Oooh. Mi calza a pennello! + +[PRO3_11:PROT3] +Un po' stretto sul pacco... + +[PRO3_12:PROT3] +Oh sì, hai ragione, anche a me! + +[PRO3_13:PROT3] +Tranquillo fratello: nessun poliziotto guida così male! + +[PRO3_14:PROT3] +Ricorda: sorridi agli altri poliziotti! + +[PRO3_15:PROT3] +Ehi agente, bel distintivo, bel distintivo. + +[PRO3_16:PROT3] +Un po' più sciolto, Lance. + +[PRO3_17:PROT3] +OK, i timer sono pronti, 5 secondi alla scadenza. + +[PRO3_18:PROT3] +5 secondi? Dobbiamo scappare di corsa da qua! + +[PRO3_19:PROT3] +Questo deve davvero averli irritati. + +[PRO3_20:PROT3] +~g~Fatevi inseguire dai due poliziotti nel garage. + +[PRO3_21:PROT3] +~g~Ottieni un livello di sospetto così i poliziotti ti inseguiranno fino al garage. + +[PRO3_22:PROT3] +~g~La porta del garage è bloccata! Liberala così potrai chiuderla. + +[PRO3_23:PROT3] +~g~Cammina sul segnalino per piazzare la bomba. + +[PRO3_24:PROT3] +~g~Allontanati dal Café! + +[PRO_AS1:PROT3] +CIRCOLO DI PROTEZIONE COMPLETATO + +[PRO_AS2:PROT3] +~g~La proprietà di Vercetti d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +[PRO3_08:PROT3] +~g~Torna alla ~h~proprietà di Vercetti~g~ su ~h~Starfish Island~g~. + +{=================================== MISSION TABLE RACES ===================================} + +[RACES_2:RACES] +~g~Hai bisogno di un veicolo per partecipare, non è mica una maratona! + +[RACES_3:RACES] +3..2..1.. VIA VIA VIA! + +[RACES_8:RACES] +~r~Non hai vinto la corsa! + +[RACES00:RACES] +Corsa ~1~: + +[RACES01:RACES] +Terminal Velocity + +[RACES02:RACES] +Ocean Drive + +[RACES03:RACES] +Border Run + +[RACES04:RACES] +Capital Cruise + +[RACES05:RACES] +Tour! + +[RACES06:RACES] +V.C. Endurance + +[RACES07:RACES] +Costo d'ingresso: ~1~$ + +[RACES08:RACES] +Tempo migliore: ~1~:~1~ + +[RACES09:RACES] +Miglior risultato: PRIMO + +[RACES10:RACES] +Miglior risultato: SECONDO + +[RACES11:RACES] +Miglior risultato: TERZO + +[RACES12:RACES] +Miglior risultato: QUARTO + +[RACES13:RACES] +Lunghezza tracciato: ~1~.~1~ km + +[RACES15:RACES] +Miglior tempo: ND + +[RACES16:RACES] +Miglior risultato: ND + +[RACES18:RACES] +HAI VINTO: ~1~$ + +[RACES19:RACES] +Non puoi permetterti di partecipare a questa gara. + +[RACES22:RACES] +Tempo migliore: ~1~:0~1~ + +[RACES23:RACES] +Lunghezza tracciato: ~1~.~1~ miglia + +[RACES_1:RACES] +~g~Recupera un veicolo veloce e raggiungi la griglia di partenza. + +[RACEHLP:RACES] +~w~Premi il ~h~~k~~PED_SPRINT~~w~ per avviare la corsa selezionata. ~w~Premi il ~h~~k~~VEHICLE_ENTER_EXIT~~w~ per uscire. + +{=================================== MISSION TABLE RCHELI1 ===================================} + +[WRECKED:RCHELI1] +~r~Il veicolo è a pezzi! + +[RCH1_4:RCHELI1] +Punti di controllo rimanenti: + +[RCH1_6:RCHELI1] +~g~Usa l'elicottero radiocomandato per attraversare i punti di controllo nella zona dell'aeroporto. + +[RCH1_7:RCHELI1] +~g~In totale ci sono 20 punti di controllo. + +[RCH1_12:RCHELI1] +~r~L'elicottero radiocomandato si sta allontanando dal raggio d'azione! + +[RCH1_13:RCHELI1] +~r~L'elicottero radiocomandato è fuori portata! + +[RCH1_8:RCHELI1] { reVC update } +~g~Se desideri interrompere la missione, premi il ~h~~k~~VEHICLE_FIREWEAPON~~g~ per far esplodere l'elicottero. + +{=================================== MISSION TABLE RCPLNE1 ===================================} + +[RCPL1_4:RCPLNE1] +~g~Sfida nella GARA A TAPPE altri 3 aerei radiocomandati. + +[RCPL1_5:RCPLNE1] +~g~Vola attraverso i punti di controllo in Vice City. + +[RCPL1_6:RCPLNE1] { reVC update } +~g~Se desideri interrompere la missione, premi il ~h~~k~~VEHICLE_FIREWEAPON~~g~ per far esplodere l'aereo. + +[RCPL1_8:RCPLNE1] +~g~L'aereo radiocomandato è quasi fuori portata! + +[RCPL1_9:RCPLNE1] +~r~L'aereo radiocomandato è fuori portata! + +{=================================== MISSION TABLE RCRACE1 ===================================} + +[RCR1_4:RCRACE1] +Giri rimanenti: + +[RCR1_1:RCRACE1] +~g~Sfida in una gara a tappe altre 3 macchine radiocomandate. + +[RCR1_2:RCRACE1] +~g~Per vincere, completa per primo due giri del tracciato! + +[RCR1_6:RCRACE1] +~g~La macchina radiocomandata è quasi fuori portata! + +[RCR1_7:RCRACE1] +~r~La macchina radiocomandata è fuori portata! + +{=================================== MISSION TABLE ROCK1 ===================================} + +[RBM1_A:ROCK1] +AllllllllRrrighttt! + +[RBM1_B:ROCK1] +Brillante, davvero brillante! + +[RBM1_D:ROCK1] +Ehi, hai già incontrato i Love Fist? + +[RBM1_E:ROCK1] +No, non ancora, ma mi piace molto la vostra musica. + +[RBM1_F:ROCK1] +Lascia che ti presenti la band. + +[RBM1_G:ROCK1] +Questo è P, Percy, Dick. Willy è nel cesso! Jezz è invece il tipo che hai visto prima nella cabina di registrazione. + +[RBM1_H:ROCK1] +Ragazzi, vorrei presentarvi un mio carissimo amico. + +[RBM1_I:ROCK1] +Si chiama Tommy. Ci conosciamo da tempo. + +[RBM1_J:ROCK1] +Ciao amico. + +[RBM1_K:ROCK1] +E, ehm, qual era il tuo nome? + +[RBM1_L:ROCK1] +Eddai Jezz che lo sai, + +[RBM1_M:ROCK1] +non fare questi scherzetti con me, amico, + +[RBM1_N:ROCK1] +sono troppo furbo per cascarci, tesoro! + +[RBM1_O:ROCK1] +Vedi, la situazione Tom è che i ragazzi hanno bisogno di aiuto. + +[RBM1_P:ROCK1] +Non hanno i contatti giusti da questa parte, del tipo 'come sta tuo padre'. + +[RBM1_Q:ROCK1] +Abbiamo bisogno di droga, amico! + +[RBM1_R:ROCK1] +Dobbiamo fare un po' di casino alla Love Fist, capito? + +[RBM1_S:ROCK1] +Beh, questa è Vice City. Qual è il problema? + +[RBM1_U:ROCK1] +Love Juice, sì! + +[RBM1_V:ROCK1] +Love Juice? + +[RBM1_W:ROCK1] +Sì, due parti di broda, una parte di polvere, cinque bombe effervescenti e un litro di petrolio. + +[RBM1_X:ROCK1] +Ci puoi aiutare, amico? + +[RBM1_Y:ROCK1] +Beh, significherebbe molto per i ragazzi. + +[RBM1_Z:ROCK1] +Lo farai per loro, vero? + +[RBM1_7:ROCK1] +~r~Non hai portato la Love Juice in tempo! + +[RBM1_8:ROCK1] +~r~Mercedes è morta! + +[RBM1_10:ROCK1] +~r~Idiota! Hai distrutto la merce! + +[RBM1_13:ROCK1] +~g~Porta la 'Love Juice' e Mercedes alla band prima che salgano sul palco. + +[RBM1_15:ROCK1] +~r~Hai perso lo spacciatore, i soldi e la droga! + +[RBM1_17:ROCK1] +~g~Uccidi lo spacciatore e recupera la droga! + +[MOB_07A:ROCK1] +Ehi amico, ai ragazzi non dispiacerebbe un po' di compagnia, se capisci cosa intendo... + +[MOB_07B:ROCK1] +Conosco la ragazza giusta. + +[ROK1_5:ROCK1] +Ehi Mercedes! + +[ROK1_6:ROCK1] +Ciao Tommy, come stai? + +[ROK1_7:ROCK1] +Non male. Senti, ti piacerebbe intrattenere i Love Fist? + +[ROK1_8:ROCK1] +OK, ma per questo favore voglio una ricompensa... + +[RBM1_14:ROCK1] +~g~Hai bisogno di una macchina o di una moto! + +[RBM1_1:ROCK1] +~g~Vai a prendere Mercedes al suo appartamento. + +[RBM1_12:ROCK1] +~g~Raccogli gli ingredienti per la 'Love Juice' dallo spacciatore. + +[ROK1_2:ROCK1] +NON NECESSARIO + +[ROK1_3:ROCK1] +NON NECESSARIO + +[MERC_39:ROCK1] +Ci vediamo più tardi, tesoro. + +[RBM1_C:ROCK1] +Ehi, Tommy! Sono felice che ce tu l'abbia fatta. + +[ROK1_1A:ROCK1] +Stai cercando qualcosa di speciale? Ho proprio ciò che ti serve! + +[ROK1_9:ROCK1] +Grazie per i soldi, idiota! + +[RBM1_T:ROCK1] +Abbiamo bisogno di Love Juice, hai presente? + +{=================================== MISSION TABLE ROCK2 ===================================} + +[RBM2_A:ROCK2] +Tommy, amico, sono felice di vederti! + +[RBM2_B:ROCK2] +Cosa succede? + +[RBM2_C:ROCK2] +Brutte vibrazioni, Tommy... + +[RBM2_E:ROCK2] +C'è questo tipo, lo conosciamo appena, ma lui la sa lunga su di noi. + +[RBM2_F:ROCK2] +Come questo tipo. Sa tutto su di noi. + +[RBM2_G:ROCK2] +Sa che Willy ama la biancheria intima femminile, eh! + +[RBM2_H:ROCK2] +O che a Percy piacciono i Duran Duran! + +[RBM2_K:ROCK2] +Sì, come nella storia dei missili, esatto. Ma ascolta, questo bastardo... + +[RBM2_L:ROCK2] +cioè, sì, questo tipo, vuole vedere i Love Fist morti. + +[RBM2_M:ROCK2] +Morti, Tommy. + +[RBM2_N:ROCK2] +Love Fist morti. Lo sai come si dice, i buoni muoiono giovani. + +[RBM2_O:ROCK2] +Ma Tommy, devi salvare i Love Fist! + +[RBM2_P:ROCK2] +Abbiamo un concerto fra due ore e credo... + +[RBM2_Q:ROCK2] +I ragazzi credono che il maniaco stia architettando qualcosa di losco. + +[RBM2_1:ROCK2] +~g~Guida la limousine al concerto e cerca di attirare allo scoperto lo psicopatico. + +[RBM2_2:ROCK2] +~r~Hai distrutto l'auto della band! + +[RBM2_3:ROCK2] +~g~Vai al concerto! + +[RBM2_4:ROCK2] +~g~Becca lo psicopatico! Non farlo scappare! + +[RBM2_5:ROCK2] +~r~Lo hai perso, idiota! + +[RBM2_7:ROCK2] +~r~I fan sono stati attaccati: lo psicopatico non si farà vivo! + +[RBM2_8:ROCK2] +~r~Le guardie della sorveglianza sono state attaccate: lo psicopatico non si farà vivo! + +[PSYCH_1:ROCK2] +Vedrò i Love Fist bruciare! + +[PSYCH_2:ROCK2] +I Love Fist hanno rovinato la mia vita! + +[RBM2_I:ROCK2] +Sta zitto, idiota. Solo perché a Jezz piacciono le pecore. + +[RBM2_R:ROCK2] +Ma sta' zitto! + +[RBM2_D:ROCK2] +Certo, niente scherzi, è roba forte, davvero forte sai? + +[RBM2_J:ROCK2] +È una questione di passione, sai? + +{=================================== MISSION TABLE ROCK3 ===================================} + +[RBM3_A:ROCK3] +Tommy! Tommy! Tommy amico, lo psicopatico è tornato! + +[RBM3_B:ROCK3] +Che succede? + +[RBM3_C:ROCK3] +Quello psicopatico non vuole lasciar stare i Love Fist! + +[RBM3_D:ROCK3] +Non lo hai ucciso, amico. E adesso è tornato. + +[RBM3_E:ROCK3] +Sì, sì, sì e il problema è che... + +[RBM3_F:ROCK3] +Il problema è che abbiamo bisogno di qualcuno di cui ci fidiamo per guidare la limousine, + +[RBM3_G:ROCK3] +poiché il pazzo continua a minacciarci! + +[RBM3_I:ROCK3] +Stiamo letteralmente cadendo a pezzi. + +[RBM3_J:ROCK3] +OK ragazzi, calmatevi. Me ne occuperò io. + +[RBM3_K:ROCK3] +Generalmente non perdo tempo a portare in giro un gruppo di ubriaconi scozzesi bisessuali, + +[RBM3_L:ROCK3] +ma, nel vostro caso, farò un'eccezione. + +[ROK3_03:ROCK3] +Conviene fare un giro più lungo. + +[ROK3_04:ROCK3] +Ehi Tommy, cambia la stazione. + +[ROK3_08:ROCK3] +Mi sto annoiando di tutto questo. + +[ROK3_09:ROCK3] +Tieni premuto il maledetto pedale! + +[ROK3_61:ROCK3] +Dobbiamo trovare la bomba! + +[ROK3_28:ROCK3] +Suonerò il basso all'inferno. + +[ROK3_30:ROCK3] +Qualcuno faccia qualcosa. + +[ROK3_32:ROCK3] +OK, osso duro, fai qualcosa tu allora. + +[ROK3_34:ROCK3] +Willy, potresti succhiare la broda con una cannuccia. + +[ROK3_37:ROCK3] +Passa una cannuccia a Willy! + +[ROK3_41:ROCK3] +Quale filo, Tommy? + +[ROK3_42:ROCK3] +Quello verde. + +[ROK3_43:ROCK3] +Non c'è un filo verde! Questo forse è verde... + +[ROK3_44:ROCK3] +Qualcuno di questi fili ti sembra verde? + +[ROK3_49:ROCK3] +Ti ho tenuto con me per anni. + +[ROK3_51:ROCK3] +Una ragazzina isterica. + +[ROK3_52:ROCK3] +Sì. + +[ROK3_53:ROCK3] +Taci e taglia un filo. + +[ROK3_54:ROCK3] +Quale filo? + +[ROK3_55:ROCK3] +Questo qua... + +[ROK3_56:ROCK3] +NO! + +[ROK3_57:ROCK3] +Oddio, siamo vivi. Non siamo esplosi, amico. Tommy, bel lavoro. Rock and roll, ragazzi. + +[ROK3_58:ROCK3] +Non abbiamo uno spettacolo a cui andare? Del casino da fare? Fans di cui abusare? + +[ROK3_59:ROCK3] +LOVE FIST! + +[ROK3_60:ROCK3] +Hai finito con quella bottiglia? + +[RBM3_4:ROCK3] +~r~Hai ucciso i Love Fist! + +[RBM3_6:ROCK3] +DETONAZIONE: + +[RBM3_1:ROCK3] +~g~Trasporta i Love Fist al concerto. + +[RBM3_2:ROCK3] +Mentre la bomba è innescata, esploderà se cercherai di lasciare la macchina... + +[RBM3_3:ROCK3] +Se la barra di detonazione si riempie completamente, la bomba esploderà. + +[RBM3_8:ROCK3] +Più velocemente guidi, più lentamente si riempirà. + +[RBM3_7:ROCK3] +~g~BOMBA DISINNESCATA! + +[ROK3_6A:ROCK3] +~g~Love Fist. Avete finito di inquinare l'etere. + +[ROK3_6B:ROCK3] +~g~Vi ho dato la possibilità di essere amici. Adesso vi darò la possibilità di morire. + +[ROK3_62:ROCK3] +Per cui abbiamo pensato di mostrarti il nostro Tempio del Rock. + +[ROK3_63:ROCK3] +Per percepire il furore alla Love Fist! + +[ROK3_64:ROCK3] +Ascolta come parli, amico. Si tratta di carta pesta e adesivo. + +[ROK3_65:ROCK3] +Ehi, ragazzi, questo è il tempio e noi siamo i sacerdoti. + +[ROK3_66:ROCK3] +Beh, se i ragazzi amano i loro sacerdoti a pezzi e mezzi sordi, + +[ROK3_67:ROCK3] +chi sono per discutere? + +[ROK3_68:ROCK3] +Oddio, si è rovinato nuovamente il nastro. + +[ROK3_69:ROCK3] +Se andiamo avanti così, dovremo suonare dal vivo. + +[ROK3_70:ROCK3] +Oooh merda! La pancia... + +[ROK3_74:ROCK3] +Ah guarda: che cos'è? Ehi Tommy, metti su questo nastro. + +[ROK3_01:ROCK3] +Finalmente, amico: è l'ora di un bel drink. + +[ROK3_02:ROCK3] +Il concerto è a cento metri lungo la strada. + +[ROK3_05:ROCK3] +Mi rimbambisco se non agito la testa. + +[ROK3_07:ROCK3] +Tommy, amico, devi salvare la band! + +[ROK3_29:ROCK3] +Tommy, continua a guidare senza rallentare! + +[ROK3_31:ROCK3] +Bella: 'Qualcuno faccia qualcosa'. Ma che idiozia è questa? Ho conosciuto froci più coraggiosi. + +[ROK3_33:ROCK3] +Ascolta, sono un musicista. Non ho la minima idea di come disarmare una bomba. + +[ROK3_35:ROCK3] +Sì, mi è giunta voce che sei bravo in queste cose. + +[ROK3_38:ROCK3] +Una cannuccia? Questo è il furgone da tour dei Love Fist! + +[ROK3_39:ROCK3] +Dove diavolo la trovo una cannuccia, amico? + +[ROK3_46:ROCK3] +Avrei dovuto scaricarti quando ne ho avuto la possibilità. + +[ROK3_47:ROCK3] +Capitalista. + +[ROK3_48:ROCK3] +Schiavo dalla gloria. + +[ROK3_73:ROCK3] +Jezz sta riproducendo il nastro. + +[RBM3_9:ROCK3] +Se ti fermi o guidi lentamente, la barra di detonazione aumenterà. + +[ROK3_50:ROCK3] +Sta zitto. Sei un idiota. + +[ROK3_36:ROCK3] +Ehi, ero completamente fuori di testa quella notte, e voi lo sapete! + +[RBM3_H:ROCK3] +Mi sto cagando addosso, amico. Voglio la mamma! + +[ROK3_45:ROCK3] +La fine imminente mi fa vedere tutto verde. + +[ROK3_6C:ROCK3] +~g~Rallenta e la limousine esploderà, insieme ai vostri GROSSI CULI PELOSI! + +[ROK3_71:ROCK3] +Dobbiamo andare avanti con quello che abbiamo. Grazie ancora Tommy. Cioè, sai cosa intendo, ciao! + +[ROK3_1:ROCK3] +Alla fine è giunta l'ora di un meritato bicchierino. Lo spettacolo si tiene a circa cento metri lungo la strada. + +[ROK3_2:ROCK3] +Conviene fare un giro più lungo. Ehi Tommy, cambia la stazione. + +[ROK3_3:ROCK3] +Sì Tommy, mi rimbambisco se non agito la testa. Ehi guarda, cos'è questo? Ehi Tommy, metti su questa cassetta. + +[ROK3_4:ROCK3] +Love Fist. Avete finito di inquinare l'etere. Vi ho dato la possibilità di essere amici. + +[ROK3_5:ROCK3] +Adesso vi darò la possibilità di morire. Se rallentate, la vostra limousine esploderà, insieme ai vostri GROSSI CULI PELOSI! + +[ROK3_6:ROCK3] +Tommy, devi salvare la band! Mi sto annoiando di tutto questo. Tieni premuto il maledetto pedale! + +[ROK3_7:ROCK3] +Dobbiamo trovare la bomba! Perché? Non possiamo guidare tutto il giorno? È vero, c'è un sacco di roba da bere... + +[ROK3_8:ROCK3] +La bomba non sarà nel motore? Dovremmo fermarci per prenderla! Moriremo tutti quanti! Credo che mi ubriacherò! + +[ROK3_9:ROCK3] +Ehi, c'è una coda da rispettare, amico! La risposta non è nel minibar! Spostati! + +[ROK3_10:ROCK3] +Ehi, dalla bottiglia di vodka escono un sacco di fili! Non è vodka, quella è BRODA! + +[ROK3_11:ROCK3] +WAAAAAAGGGHHHH!!! Ed è predisposta per esplodere! WAAAAAAAAAAAAGGGHHHHHHHH!!! + +[ROK3_12:ROCK3] +Lo dicevano che l'alcol mi avrebbe fatto fuori... L'ho visto in televisione, devi tagliare uno dei fili. Quale? Non lo so, amico. + +[ROK3_13:ROCK3] +Non ne ho idea. Willy, dimmi qualcosa. Suonerò il basso all'inferno. + +[ROK3_14:ROCK3] +Tommy, continua a guidare senza rallentare! Qualcuno faccia qualcosa. Bella! + +[ROK3_15:ROCK3] +'Qualcuno faccia qualcosa'. Ma che idiozia è questa? Ho conosciuto froci più coraggiosi. OK, osso duro, fai qualcosa tu allora. + +[ROK3_16:ROCK3] +Ascolta, io sono un musicista. Non ho la minima idea di come disarmare una bomba. Willy, potresti succhiare la broda con una cannuccia. + +[ROK3_17:ROCK3] +Sì, mi è giunta voce che sei bravo in queste cose. Ehi, ero completamente fuori di testa quella notte, e voi lo sapete! + +[ROK3_18:ROCK3] +Passa una cannuccia a Willy! Una cannuccia? Questo è il furgone da tour di Love Fist! + +[ROK3_19:ROCK3] +Dove diavolo la trovo una cannuccia, non so se mi spiego? Quale filo, Tommy? Quello verde. Non c'è un filo verde! + +[ROK3_20:ROCK3] +Questo forse è verde... Qualcuno di questi fili ti sembra verde? + +[ROK3_21:ROCK3] +Ih no! La fine imminente mi fa vedere tutto verde! Avrei dovuto scaricarti quando ne ho avuto la possibilità. + +[ROK3_22:ROCK3] +Accecato dalla gloria. Capitalista. Ti ho tenuto con me per anni. Sta zitto. Sei un idiota. + +[ROK3_23:ROCK3] +Una ragazzina isterica. Sì. Taci e taglia un filo. Quale filo? Questo qua... + +[ROK3_24:ROCK3] +NO! Oddio, siamo vivi. Non siamo esplosi, amico. + +[ROK3_25:ROCK3] +Tommy, bel lavoro. Rock and roll, ragazzi. Non abbiamo uno spettacolo a cui andare? + +[ROK3_26:ROCK3] +Del casino da fare? Fans di cui abusare? LOVE FIST! + +[ROK3_27:ROCK3] +Hai finito con quella bottiglia? + +{=================================== MISSION TABLE SERG1 ===================================} + +[TEX1_A:SERG1] +Vieni a metterti qui vicino a me, figliolo. + +[TEX1_B:SERG1] +Diamine, mio padre diceva sempre 'A caval donato non si guarda in bocca' ed effettivamente non lo ha mai fatto. + +[TEX1_C:SERG1] +Vuoi un goccio di sano Kentucky? + +[TEX1_D:SERG1] +No, grazie. + +[TEX1_E:SERG1] +Sei uno dalla testa lucida, mi piace. + +[TEX1_F:SERG1] +Ora, il business dei terreni non ha niente a che fare con quello che dicono i giornali. + +[TEX1_G:SERG1] +Si tratta solo di polvere! E la volontà di possederla! Mi segui, figliolo? + +[TEX1_H:SERG1] +Sì, certo. + +[TEX1_J:SERG1] +Tu mi sembri la persona giusta per convincerlo. + +[TEX1_K:SERG1] +La persuasione è il mio forte. + +[TEX1_L:SERG1] +Bene. Lo troverai al circolo sportivo, presso il campo da golf. + +[TEX1_M:SERG1] +Non permettono di entrare con le armi, per cui le sue guardie del corpo non saranno armate. + +[TEX1_N:SERG1] +Vai e riempilo di santissime mazzate. + +[TEX1_O:SERG1] +Prendi, ti ho iscritto al club, inoltre, credo proprio avrai bisogno di abiti più adeguati. + +[TEX1_2:SERG1] +~g~Raggiungi il club di golf Leaf Links. + +[TEX1_0:SERG1] +~g~Il bersaglio si sta allenando a golf: assicurati che sia la sua ultima partita. + +[TEX1_3:SERG1] +Chi è questo tipo? Ragazzi, occupatevene voi. + +[TEX1_6:SERG1] +Bel culo bimba! + +[TEX1_7:SERG1] +Ma sono io? + +[TEX1_I:SERG1] +Bene, c'è un tenace bastardo che non si decide a vendere. + +[TEX1_8:SERG1] +Ogni volta che entri su un cart da golf, ottieni automaticamente una mazza da golf, sempre se lo spazio per l'arma corpo a corpo è disponibile. + +[TEX1_9:SERG1] +Prendilo! + +[TEX1_10:SERG1] +Uccidi lo psicopatico! + +[TEX1_1:SERG1] +~g~Recupera degli abiti da golf presso Jocksport's. + +{=================================== MISSION TABLE SERG2 ===================================} + +[TEX_2A:SERG2] +~g~Eccellente! Ti hanno visto! + +[TEX_2B:SERG2] +~r~Pazzo! La gente deve essere TESTIMONE di un attacco da parte di un Cubano! + +[TEX_2C:SERG2] +~g~Vai a recuperare dei vestiti con i colori della gang dei Cubani da Rafael's! + +[TEX_2D:SERG2] +~g~Adesso metti fuori gioco il capo della gang degli Haitiani presso l'impresa di pompe funebri Romero's. + +[TEX2_A:SERG2] +Tommy, questo è Donald Love. Donald, questo è Tommy Vercetti, + +[TEX2_B:SERG2] +l'ultimo cowboy arrivato da queste parti. + +[TEX2_C:SERG2] +Yeh... uh... + +[TEX2_D:SERG2] +Donald, sta zitto e ascoltami, potresti imparare qualcosa. + +[TEX2_E:SERG2] +Adesso, niente fa calare i prezzi dei terreni come una bella guerra tra gang rivali. + +[TEX2_F:SERG2] +Eccetto, forse, un disastro, come una piaga biblica o qualcosa del genere. + +[TEX2_G:SERG2] +Ma per il caso in questione mi sembra un po' troppo. + +[TEX2_H:SERG2] +Capisci, brutto idiota quattr'occhi? + +[TEX2_I:SERG2] +Recentemente è morto un capo gang degli Haitiani e, a quanto sembra, sono stati i Cubani. + +[TEX2_J:SERG2] +Togliamo loro qualsiasi dubbio, allora! Vestiti da ombre Cubano + +[TEX2_K:SERG2] +e vai a disturbare il funerale. Fai un bel casino e dattela a gambe. + +[TEX2_L:SERG2] +Hai capito, Donald? + +[TEX2_M:SERG2] +Questo dovrebbe mettere il coyote nel pollaio, vero? + +[TEX2_N:SERG2] +Dopodiché dovremo solo attendere e veder crollare i prezzi. + +[TEXEXIT:SERG2] +~g~Vattene da Little Haiti! + +{=================================== MISSION TABLE SERG3 ===================================} + +[TEX3_A:SERG3] +Guarda qua, figliolo. Ho un problema e sono certo tu possa darmi una mano. + +[TEX3_B:SERG3] +Non sono un geometra. + +[TEX3_C:SERG3] +No, mi riferivo principalmente alle tue doti di demolitore. + +[TEX3_D:SERG3] +Ecco la faccenda: questo è il progetto approvato, e questo, + +[TEX3_E:SERG3] +questo è il terreno che stiamo seguendo. + +[TEX3_F:SERG3] +Stai cercando di dirmi che questo nuovo isolato di uffici è nel posto sbagliato. + +[TEX3_G:SERG3] +Capisci al volo. + +[TEX3_H:SERG3] +Adesso devo allontanarmi per un po' dalla città... + +[TEX3_I:SERG3] +...e se la costruzione di quegli uffici dovesse subire un improvviso e insormontabile problema strutturale, beh, allora... + +[TEX3_J:SERG3] +come ogni civile e ragionevole individuo, ti sentiresti obbligato a intervenire + +[TEX3_K:SERG3] +per favorire il ringiovanimento di un'area così importante della città? + +[TEX3_L:SERG3] +Dove posso ordinare altra gente come te? + +[TEX3_1:SERG3] +~g~Utilizza l'elicottero radiocomandato per trasportare le bombe nelle quattro aree bersaglio dell'edificio. + +[TEX3_2:SERG3] +~g~Devi posizionare una bomba su ogni bersaglio. Puoi piazzare l'esplosivo in qualsiasi ordine. + +[TEX3_5:SERG3] +~g~Se fai cadere una bomba senza successo, potrai raccoglierla nuovamente e riprovare. + +[TEX3_7:SERG3] +~g~Dovrai piazzare tutti gli esplosivi rimanenti in 7 minuti! + +[TEX3_8:SERG3] +~g~Hai mancato il bersaglio! Raccogli la bomba e riprova! + +[TEX3_10:SERG3] +~g~Fai cadere la bomba sul bersaglio. + +[TEX3_11:SERG3] +Bersagli rimanenti: + +[TEX3_17:SERG3] +~r~Il tempo è scaduto: non sei riuscito a demolire l'edificio! + +[TEX3_18:SERG3] +~r~L'elicottero radiocomandato è stato distrutto! Come farai adesso a trasportare le bombe? + +[TEX3_19:SERG3] +~r~Hai fatto cadere la bomba in acqua! Hai bisogno di tutte le 4 bombe per demolire il cantiere! + +[TEX3_20:SERG3] +~g~L'elicottero radiocomandato è quasi fuori portata. Torna indietro al cantiere e completa il lavoro! + +[TEX3_21:SERG3] +~r~L'elicottero radiocomandato è fuori portata! + +[TEX3_24:SERG3] +Premi il ~h~~k~~VEHICLE_LOOKLEFT~~w~ per ruotare l'elicottero in senso antiorario. + +[TEX3_25:SERG3] +Premi il ~h~~k~~VEHICLE_LOOKLEFT~~w~ per ruotare l'elicottero in senso orario. + +[TEX3_27:SERG3] +~g~Le scale centrali ti permetteranno di accedere a tutti i piani dell'edificio. + +[TEX3_31:SERG3] +~r~Hai distrutto il van contenente le bombe e l'elicottero radiocomandato! + +[TEX3_32:SERG3] +Puoi ~h~guardare indietro~w~ premendo ~h~simultaneamente ~k~~VEHICLE_LOOKLEFT~ e ~k~~VEHICLE_LOOKRIGHT~~w~. + +[TEX3_4:SERG3] { reVC update } +~g~Per sganciare una bomba, premi il ~h~~k~~VEHICLE_FIREWEAPON~~g~. + +[TEX3_29:SERG3] { reVC update } +Per sganciare una bomba, premi il ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[TEX3_26:SERG3] +Premi il ~h~~k~~VEHICLE_BRAKE~~w~ per ridurre la velocità del rotore dell'elicottero in modo da farlo abbassare. + +[TEX3_22:SERG3] +Premi il ~h~~k~~VEHICLE_ACCELERATE~~w~ per aumentare la velocità del rotore dell'elicottero in modo da ~h~farlo alzare. + +[TEX3_16:SERG3] +~g~Raggiungi il van ~w~TOPFUN~g~ presso il cantiere edile da demolire. + +[TEX3_33:SERG3] +Una volta raccolta una bomba, il radar mostrerà la pozione del bersaglio rispetto all'elicottero radiocomandato. + +[TEX3_34:SERG3] +Un ~h~triangolo verso l'alto~w~ indica che il bersaglio si trova ~h~più in alto~w~ dell'elicottero. + +[TEX3_35:SERG3] +Un ~h~triangolo verso il basso~w~ indica che il bersaglio si trova ~h~più in basso~w~ dell'elicottero. + +[TEX3_36:SERG3] +Un ~h~quadrato~w~ indica che il bersaglio si trova alla ~h~stessa altezza~w~ dell'elicottero. + +[TEX3_6:SERG3] +~g~Una volta raccolta la prima bomba, comincerà il conto alla rovescia. + +[TEX3_28:SERG3] +Per ~h~raccogliere una bomba~w~, avvicinaci l'elicottero radiocomandato. L'elicottero può trasportare una sola bomba alla volta. + +[TEX3_30:SERG3] +Per raccogliere una bomba, avvicinaci l'elicottero radiocomandato. L'elicottero può trasportare una sola bomba alla volta. + +[TEX3_12:SERG3] +~g~Bomba posizionata! Ancora 3 bersagli! Torna indietro e recupera un'altra bomba. + +[TEX3_13:SERG3] +~g~Bomba posizionata! Ancora 2 bersagli! Torna indietro e recupera un'altra bomba. + +[TEX3_14:SERG3] +~g~Bomba posizionata! Ancora 1 bersaglio! Torna indietro e recupera un'altra bomba. + +[TEX3_15:SERG3] +~r~Conto alla rovescia avviato! ~g~Devi posizionare le ~w~4 bombe~g~ in tempo! + +[TEX3_37:SERG3] +Sposta ~h~in basso la levetta analogica destra~w~ per aumentare la velocità del rotore e ~h~far salire~w~ l'elicottero. + +[TEX3_38:SERG3] { reVC update } +{Sposta ~h~~k~~VEHICLE_ACCELERATE~~w~ per ridurre la velocità del rotore e ~h~far scendere~w~ l'elicottero.} +Sposta ~h~in alto la levetta analogica destra~w~ per ridurre la velocità del rotore e ~h~far scendere~w~ l'elicottero. + +[TEX3_39:SERG3] +~g~Per sganciare una bomba, premi il tasto ~h~~k~~VEHICLE_HANDBRAKE~~g~. + +[TEX3_40:SERG3] +Per sganciare una bomba, premi il tasto ~h~~k~~VEHICLE_HANDBRAKE~~w~. + +[TEX3_23:SERG3] +Premi i tasti ~h~~k~~VEHICLE_TURRETUP~~w~ e ~h~~k~~VEHICLE_TURRETDOWN~~w~ per inclinare l'elicottero nella direzione desiderata. + +{=================================== MISSION TABLE TAXI1 ===================================} + +[FARES:TAXI1] +CLIENTI: + +[TAXI1:TAXI1] +~g~Trova un passeggero. + +[TSCORE2:TAXI1] +~1~$ + +[IN_ROW:TAXI1] +Bonus ~1~ DI SEGUITO! ~1~$ + +[TAXI3:TAXI1] +~r~Il passeggero è fuggito terrorizzato! + +[TAXI7:TAXI1] +~r~La tua auto è distrutta, falla riparare. + +[TAXI4:TAXI1] +Corsa completata! + +[TAXI5:TAXI1] +BONUS VELOCITÀ! + +[TAXI6:TAXI1] +Missione taxi terminata + +[TAXIH1:TAXI1] +Fermati vicino a un pedone evidenziato per farlo salire a bordo e portarlo a destinazione prima che scada il tempo. + +[FARE1:TAXI1] +~g~Destinazione: ~w~'Club Pole Position'~g~ in Ocean Beach. + +[FARE3:TAXI1] +~g~Destinazione: ~w~'Marina'~g~ in Ocean Beach. + +[FARE4:TAXI1] +~g~Destinazione: ~w~'Ammu-Nation'~g~ in Ocean Beach. + +[FARE5:TAXI1] +~g~Destinazione: ~w~'Hardware store'~g~ in Washington Beach. + +[FARE6:TAXI1] +~g~Destinazione: ~w~'Mall North Point'~g~ in Vice Point. + +[MFARE1:TAXI1] +~g~Destinazione: ~w~'Ammu-Nation'~g~ in Downtown. + +[MFARE2:TAXI1] +~g~Destinazione: ~w~'il terminal'~g~ all'aeroporto internazionale Escobar. + +[WFARE3:TAXI1] +~g~Destinazione: ~w~'Sunshine Autos'~g~ in Little Havana. + +[WFARE4:TAXI1] +~g~Destinazione: ~w~'Taxi Kaufman'~g~ in Little Haiti. + +[WFARE5:TAXI1] +~g~Destinazione: ~w~'Hardware store'~g~ in Little Havana. + +[WFARE6:TAXI1] +~g~Destinazione: ~w~'Howlin Petes Bike'~g~ in Downtown. + +[FARE7:TAXI1] +~g~Destinazione: ~w~'il negozio dei gioiellieri'~g~ in Vice Point. + +[FARE8:TAXI1] +~g~Destinazione: ~w~'la spiaggia'~g~ in Ocean Beach. + +[FARE9:TAXI1] +~g~Destinazione: ~w~'la spiaggia'~g~ in Washington Beach. + +[FARE10:TAXI1] +~g~Destinazione: ~w~'la spiaggia'~g~ in Vice Point. + +[FARE11:TAXI1] +~g~Destinazione: ~w~'l'ospedale'~g~ in Ocean Beach. + +[FARE12:TAXI1] +~g~Destinazione: ~w~'l'ospedale'~g~ in Vice Point. + +[FARE13:TAXI1] +~g~Destinazione: ~w~'la stazione di polizia'~g~ in Washington Beach. + +[FARE14:TAXI1] +~g~Destinazione: ~w~'la stazione di polizia'~g~ in Vice Point. + +[FARE15:TAXI1] +~g~Destinazione: ~w~'il ristorante della pizza'~g~ in Vice Point. + +[WFARE7:TAXI1] +~g~Destinazione: ~w~'la stazione di polizia'~g~ in Little Havana. + +[WFARE8:TAXI1] +~g~Destinazione: ~w~'la stazione di polizia'~g~ in Downtown. + +[WFARE9:TAXI1] +~g~Destinazione: ~w~'l'ospedale'~g~ in Downtown. + +[WFARE10:TAXI1] +~g~Destinazione: ~w~'l'ospedale'~g~ in Little Havana. + +[WFARE11:TAXI1] +~g~Destinazione: ~w~'lo stadio'~g~ in Downtown. + +[WFARE12:TAXI1] +~g~Destinazione: ~w~'il ristorante della pizza'~g~ in Little Haiti. + +[WFARE13:TAXI1] +~g~Destinazione: ~w~'il ristorante della pizza'~g~ in Downtown. + +[WFARE14:TAXI1] +~g~Destinazione: ~w~'i bacini'~g~ in Viceport. + +[WFARE15:TAXI1] +~g~Destinazione: ~w~'la farmacia'~g~ in Little Haiti. + +[FARE2:TAXI1] +~g~Destinazione: ~w~'Club Malibu'~g~ in Vice Point. + +{=================================== MISSION TABLE TAXICUT ===================================} + +[TAXC_A:TAXICUT] +Immagino tu sia il nuovo proprietario. + +[TAXC_B:TAXICUT] +Di chi fai parte? Mafia? Cartello? Non sembri Messicano... + +[TAXC_C:TAXICUT] +Comunque, immagino ora comincerai con la vecchia frase 'le cose stanno per cambiare da queste parti', + +[TAXC_D:TAXICUT] +magari minaccerai uno degli autisti... + +[TAXC_E:TAXICUT] +vacci piano con quello là, Ted, ha appena avuto un'ernia al disco. + +[TAXC_F:TAXICUT] +Beh, sì. Le cose stanno per cambiare da queste parti, signora. + +[TAXC_G:TAXICUT] +Che felicità, figliolo. Me ne occupo io... + +[TAXC_H:TAXICUT] +Ormai lo faccio da anni. + +[TAXC_I:TAXICUT] +Attenzione a tutti: + +[TAXC_J:TAXICUT] +è cambiata la gestione e le cose stanno nuovamente per cambiare da queste parti. + +[TAXC_K:TAXICUT] +Il nostro nuovo direttore, della... + +[TAXC_L:TAXICUT] +Di quale gang fai parte? + +[TAXC_M:TAXICUT] +Beh, non faccio parte di nessuna gang. + +[TAXC_N:TAXICUT] +Allora qual è il tuo maledetto nome, ragazzo? + +[TAXC_O:TAXICUT] +Vercetti, Tommy Vercetti. + +[TAXC_P:TAXICUT] +In nostro nuovo direttore, della Vercetti Gang, + +[TAXC_Q:TAXICUT] +si assicurerà che non ci succedano incidenti. + +[TAXC_R:TAXICUT] +Capisce? Fine! + +[TAXC_S:TAXICUT] +Ti è piaciuto il 'Capisce'? A me è piaciuto il 'Capisce'. + +[TAXC_T:TAXICUT] +Allora è così che ha funzionato nel passato: + +[TAXC_U:TAXICUT] +noi portiamo avanti l'azienda come sempre. + +[TAXC_V:TAXICUT] +Se abbiamo problemi con la concorrenza, tu ti occupi di riempirli di botte. + +[TAXC_W:TAXICUT] +Poi loro ci riempiono di botte, + +[TAXC_X:TAXICUT] +poi tu riempi loro di botte nuovamente, + +[TAXC_Y:TAXICUT] +eccetera, eccetera. Tutto chiaro? + +[TAXC_Z:TAXICUT] +Uh, sì, almeno credo... + +[TAXC_A1:TAXICUT] +Prendi un taxi dal garage se ti senti di lanciarti subito nella mischia. + +{=================================== MISSION TABLE TAXIWA1 ===================================} + +[OUTTIME:TAXIWA1] +~r~Troppo lento, troppo lento! + +[TAX1_1:TAXIWA1] +OK, abbiamo un cliente danaroso che richiede un taxi da Starfish Island: qualche volontario? + +[TAX1_2:TAXIWA1] +Qui Tommy, lo prendo io! + +[TAX1_3:TAXIWA1] +Questo cliente è mio, indietro! + +[TAX1_4:TAXIWA1] +Forza, forza, entra in fretta! + +[TAX1_5:TAXIWA1] +OK, OK! Ti prego, non farmi male! + +[TAXW1_1:TAXIWA1] +~g~Passa a prendere il VIP su Starfish Island. + +[TAXW1_2:TAXIWA1] +~g~Porta indietro il VIP! Metti fuori gioco l'altra macchina! + +[TAXW1_3:TAXIWA1] +~r~Il VIP è morto! + +[TAXW1_4:TAXIWA1] +~r~Il VIP è arrivato a destinazione! + +[TAXW1_6:TAXIWA1] +~g~Porta il VIP all'aeroporto! + +{=================================== MISSION TABLE TAXIWA2 ===================================} + +[TAX2_1:TAXIWA2] +Chiamata a tutti i veicoli: stiamo perdendo clienti in tutta la città. Che cosa sta succedendo? + +[TAX2_2:TAXIWA2] +I taxi VC continuano a batterci sul tempo. Sono in troppi, non possiamo competere! + +[TAX2_3:TAXIWA2] +Mr. Vercetti, se sei in ascolto, devi mettere fuori gioco un po' dei taxi VC, se no falliamo! + +[TAXW2_1:TAXIWA2] +~g~Distruggi 3 taxi rivali! + +{=================================== MISSION TABLE TAXIWA3 ===================================} + +[TAX3_1:TAXIWA3] +Taxi 13, la signorina Cortez ha bisogno di essere prelevata da Down Town e ha richiesto esplicitamente di te. + +[TAX3_2:TAXIWA3] +OK, ricevuto. Taxi 13 chiudo. + +[TAX3_3:TAXIWA3] +Hmmm, nessun segno di Mercedes... + +[TAXW3_3:TAXIWA3] +~g~Metti fuori combattimento il capo dei taxisti! + +[TAXW3_2:TAXIWA3] +~g~Resta vivo fino allo scadere del tempo. + +[TAX_AS1:TAXIWA3] +BENI COMPAGNIA DEI TAXI ACQUISITI + +[TAX_AS2:TAXIWA3] +La compagnia dei taxi d'ora in poi genererà introiti per un massimo di ~1~$. Ricordati di recuperarli regolarmente. + +[TAX3_4:TAXIWA3] +È giunta l'ora che l'angelo custode dei Taxi Kaufman entri in azione! + +[TAX3_5:TAXIWA3] +Ehi ragazzo, ti abbronzo il fondoschiena! + +{ re3 updates } +{ new languages } +[FEL_JAP] +GIAPPONESE + +[FEL_POL] +POLACCO + +[FEL_RUS] +RUSSO + +{ 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] +RIGIOCA MISSIONE + +[FESZ_RM] +RIGIOCA? + +[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 + +[FEC_IVP] +INVERT PAD VERTICALLY + +[FEM_NON] +NONE + +[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 + +[FET_AGS] +GAMEPAD SETTINGS + +[FEM_AUT] { aspect ratio related } +AUTO + +[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/gxt/miami/spanish.txt b/gxt/miami/spanish.txt new file mode 100644 index 00000000..c3a4e0ee --- /dev/null +++ b/gxt/miami/spanish.txt @@ -0,0 +1,14422 @@ +{ + 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 Vice City Spanish (Spain) Translation } +{ Contains some of the official fixes made by Rockstar for the iOS port } +{ Additional translation rewrites, corrections and fixes by IlDucci } +[IN_VEH] +~g~¡Eh! ¡Vuelve al vehículo! + +[HEY] +~g~¡No vayas solo, mantén tu grupo unido! + +[HELP3] +Solo puedes esprintar durante cortos periodos de tiempo, antes de cansarte. + +[HELP4_D] +Mueve el ~h~joystick analógico derecho~w~ hacia arriba para ~h~acelerar. + +[HELP5_D] +Tira del ~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 se ha detenido. + +[HELP7_A] +Pulsa y mantén pulsado el ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar~w~ con el rifle de francotirador. + +[HELP7_D] +Pulsa y mantén pulsado el ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar~w~ con el rifle de francotirador. + +[HELP10] +Esta insignia indica que tienes un nivel de se busca por la policía. + +[HELP11] +Cuantas más insignias tengas, mayor es tu nivel de se busca. + +[HELP13] +Algunas veces puedes necesitar ir por caminos que el radar no detecta. + +[TIMER] +Ésta es una misión cronometrada, debes completarla antes de que el cronómetro llegue a cero. + +[HORN] +~g~Toca la bocina. + +[NOMONEY] +~g~¡Necesitas más dinero! + +[REWARD] +RECOMPENSA ~1~ $ + +[M_FAIL] +¡MISIÓN FALLIDA! + +[M_PASS] +¡MISIÓN SUPERADA! ~1~ $ + +[DEAD] +¡ELIMINADO! + +[BUSTED] +¡ARRESTADO! + +[WEATHE1] +FORZAR TIEMPO SOLEADO + +[WEATHE2] +FORZAR TIEMPO EXTREMADAMENTE SOLEADO + +[WEATHE3] +FORZAR TIEMPO NUBLADO + +[WEATHE4] +FORZAR TIEMPO LLUVIOSO + +[WEATHE5] +FORZAR TIEMPO CON NIEBLA + +[WEATHE6] +TIEMPO NORMAL + +[NUMBER] +~1~ + +[LOADCAR] +CARGANDO VEHÍCULO... (PULSA L1 PARA CANCELAR) + +[CARSOFF] +Coches desactivados. + +[CARS_ON] +Coches activados. + +[TEXTXYZ] +Escribiendo coordenadas en el archivo... + +[CHEATON] +Modo trucos ACTIVADO + +[CHEATOF] +Modo trucos DESACTIVADO + +[IMPORT1] +Ve fuera y espera tu vehículo. + +[PAGEB11] +Entregado lanzallamas en el escondite + +[WANT_A] +Sólo serás arrestado si tienes un ~h~nivel de se busca. + +[WANT_B] +Tu ~h~nivel de se busca~w~ viene representado por la fila de estrellas en la parte superior derecha de la pantalla. + +[WANT_C] +Ahora tienes un ~h~nivel de se busca~w~ de una... + +[WANT_D] +dos... + +[WANT_E] +tres... + +[WANT_F] +A medida que tu ~h~nivel de se busca~w~ aumente, atraerás a representantes más poderosos de las fuerzas policiales. + +[WANT_G] +Cuando te hayan ~h~''arrestado''~w~ serás conducido a la comisaría más cercana. + +[WANT_H] +Los polis te quitarán todas las armas y parte de tu dinero como soborno. + +[WANT_I] +Cualquier misión en la que estuvieses habrá fracasado. + +[WANT_J] +Encontrarás modos de reducir tu nivel de se busca cuanto más juegues. + +[WANT_K] +Si estás en un coche, el ~h~TALLER DE PINTURA~w~ limpiará ~h~tu nivel de búsqueda. + +[HEAL_B] +Cuando seas ~h~''eliminado''~w~ serás conducido al hospital más cercano. + +[HEAL_C] +Perderás tus armas y los médicos te quitarán algo de dinero por recomponerte. + +[HEAL_E] +Encontrarás modos de curarte o de protegerte cuanto más juegues al juego. + +[BRIBE1] +Acabas de recoger un soborno de la policía, esto disminuirá en una estrella tu nivel de se busca. + +[SAVE1] +Entra al corona para ~h~guardar la partida~w~. No puedes guardar durante una misión. + +[SAVE2] +Cualquier vehículo que quede en este garaje será almacenado cuando se guarde la partida. + +[AMMU] +Ve dentro de Ammu-Nation para comprar un arma. + +[R_TIME] +TIEMPO DE CARRERA: + +[PROP_1] +No tienes suficiente dinero para adquirir esta propiedad + +[PROP_2] +No puedes comprar propiedades mientras estás en una misión. + +[IND_ZON] +Vice City Beach + +[COM_ZON] +Península de Vice City + +[BEACH1] +Ocean Beach + +[BEACH2] +Washington Beach + +[BEACH3] +Vice Point + +[GOLFC] +Leaf Links + +[STARI] +Starfish Island + +[DOCKS] +Viceport + +[HAVANA] +Little Havana + +[HAITI] +Little Haiti + +[PORNI] +Prawn Island + +[DTOWN] +Centro de la ciudad + +[VICE_C] +Vice City + +[A_PORT] +Escobar International + +[JUNKY] +Vertedero + +[PISTOL] +Pistola + +[PYTHON] +.357 + +[UZI] +Uz-1 + +[TEC9] +Tec 9 + +[M4] +M4 + +[INGRAM] +Mac + +[MP5] +MP + +[RUGER] +Kruger + +[SNIPE] +Rifle de francotirador + +[GRENADE] +Granadas + +[SHOTGN1] +Escopeta + +[SHOTGN2] +S.P.A.S. 12 + +[SHOTGN3] +Escopeta de cañones recortados + +[ARMOUR] +Chaleco antibalas + +[LASER] +.308 Rifle de francotirador + +[BASEBAT] +Bate de béisbol + +[HAMMER] +Martillo + +[SCREWD] +Destornillador + +[CLEVER] +Cuchillo de carnicero + +[MACHETE] +Machete + +[KNIFE] +Cuchillo + +[KATANA] +Katana + +[CHAINSA] +Sierra eléctrica + +[G_COST] +~1~ $ + +[CAR_1] +Ambulancia + +[MALIBU] +El Club Malibú + +[MANSION] +Mansión de Díaz + +[TMANS] +Finca de Vercetti + +[STRIP] +El Club Pole Position + +[MALL1] +Centro comercial North Point + +[BANKINT] +El Banco Corrupto Grande + +[RANGE] +Campo de tiro con rifle + +[POL_HQ] +Cuartel general de VCPD + +[INT_B] +Un viejo amigo + +[INTB_1] +~g~Ve a la oficina del Abogado. + +[LAW_1] +La fiesta + +[LAW_2] +Pelea en el callejón trasero + +[LAW_3] +Furia en el jurado + +[LAW_4] +Disturbios + +[COL_1] +Cerdo traidor + +[COL_2] +Tiros en el centro comercial + +[COL_3] +Ángeles guardianes + +[COL_4] +¡Señor, sí, señor! + +[COL_5] +¡Todos con las manos arriba! + +[COK_1] +La caza + +[COK_2] +Phnom Penh '86 + +[COK_3] +El barco más rápido + +[COK_4] +Oferta y demanda + +[KENT_1] +El corredor de la muerte + +[ASS_1] +Borrar + +[BUD_1] +Extorsión + +[BUD_2] +Pelea en el bar + +[BUD_3] +Tierra de polis + +[CAP_1] +Liquida al cobrador + +[FIN_1] +Mantén cerca a tus amigos... + +[BANK_1] +¿Sin escapatoria? + +[BANK_2] +El tirador + +[BANK_3] +El conductor + +[BANK_4] +El atraco + +[CNT_1] +Descubriendo el pastel + +[CNT_2] +Ataca al mensajero + +[PORN_1] +Campaña de reclutamiento + +[PORN_2] +Consolador Dodo + +[PORN_3] +La foto policial de Marta + +[PORN_4] +Punto G + +[TAX_1] +Taxis Kaufman + +[TAXI_1] +V.I.P. + +[TAXI_2] +Rivalidad amistosa + +[TAXI_3] +Taxigedón + +[ICE_1] +Distribución + +[TEX_1] +Hierro número cuatro + +[TEX_2] +Dos leves impactos + +[TEX_3] +Demoledor + +[PHIL_1] +Traficante de armas + +[PHIL_2] +Boomshine Saigon + +[BIKE_1] +Moto con llantas de aleación + +[BIKE_2] +Incitando al macho + +[BIKE_3] +Moto robada + +[ROCK_1] +Love Juice + +[ROCK_2] +Asesino psicópata + +[ROCK_3] +Gira publicitaria + +[ROCK_4] +¡Love Fist! + +[HAT_1] +Poción mágica + +[HAT_2] +¡Bombas fuera! + +[HAT_3] +Juego sucio + +[CUB_1] +El desafío del barco trucado + +[CUB_2] +Carne de cañón + +[CUB_3] +Encuentro naval + +[CUB_4] +Vudú troyano + +[JOB_1] +Muerte en la carretera + +[JOB_2] +Elimina a la esposa + +[JOB_3] +Autocidio + +[JOB_4] +Comprobar el registro + +[JOB_5] +Cabos sueltos + +[ANSWER] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para responder a una llamada a tu teléfono móvil. + +[MOB_01A] +¡Estás bien figura! Soy Paul. Puede que tenga algún resultado para ti, pero necesito hablar contigo cara a cara. + +[MOB_01B] +Estoy de relax en el Malibu. + +[MOB_01C] +Creo que después de esto me vas a deber un favor o dos, guapo. Hasta luego. + +[MOB_02A] +Ssssnniiiiffffff... ¡Eh! Hola, ¿Tommy? ¡Tommy! + +[MOB_02B] +Tenemos un problema en la imprenta. Mejor que vayas y lo compruebes. + +[MOB_02C] +Algún tipo de lío. Las cosas andan revueltas. Tengo que irme. + +[MOB_03A] +¿Sr. Vercetti? Tengo un trozo de mierda firmada que dice + +[MOB_03B] +que usted ha asumido todas las deudas de BJ's Auto. + +[MOB_03C] +Con la repentina desaparición de BJ no tengo más remedio + +[MOB_03D] +que hacerle responsable de su inestabilidad financiera + +[MOB_03E] +Hasta que esta cuenta quede completamente saldada + +[MOB_03F] +debería considerar que las calles de Vice City son poco seguras. + +[MOB_04A] +¿Cómo estás, tío? Soy Paulo. + +[MOB_04B] +Mira, Tommy, me olvidé de mencionar que vamos a necesitar más seguridad para el concierto. + +[MOB_04C] +Hay una banda de moteros liderada por Mitch Baker que serían la publicidad ideal. + +[MOB_04D] +Solucióname esto y te conseguiré un pase para la actuación, ¿vale? + +[MOB_05A] +Soy Mitch. Hiciste un buen trabajo, Tommy. Es bueno volver a tener a mi pequeña en casa. + +[MOB_05B] +Dile al Sr. Kent Paul que dispondrá de seguridad para la actuación. + +[MOB_05C] +Tiene mi palabra de ello. + +[MOB_05D] +Intenta no meterte en problemas. + +[MOB_06A] +Tommy, esos de abajo han estado rajando sobre ti, chico. + +[MOB_06B] +Pensé que podrías necesitar consuelo casero así que la tía Poulet te preparará una sopa, ¿hmmm? + +[MOB_06C] +Pásate por mi cocina alguna vez, ¿de acuerdo Tommy? + +[MOB_08A] +Eh Tommy, pensé que podrías necesitar algún asesoramiento financiero. + +[MOB_08B] +Una vez que tengas la operación en marcha, tendrás que pasarte a recoger la recaudación de la semana. + +[MOB_08C] +Deja que esos tipos crean que son los amos del lugar e intentarán cepillarse los beneficios... ¿vale? + +[MOB_08D] +Eh, sé como llevar el negocio, Ken. + +[MOB_08E] +Vale, vale. Lo sé, ya sabes. Lo sé. Estaba, + +[MOB_08F] +Sólo estaba, ya sabes, diciéndote que sé, que tú sabes que yo lo sé. + +[MOB_08G] +¡Solo manteniéndolo a punto, nene! + +[MOB_08H] +Lo que sea, Ken, lo que sea... + +[MOB_09A] +¡Eh, Leo! ¡Tengo trabajo para ti! + +[MOB_09B] +No soy Leo. + +[MOB_09C] +¡Eh, como Leo sepa que tienes su teléfono, te va a liquidar! + +[MOB_09D] +Quizás Leo ya esté muerto. Quizás yo le maté y cogí su teléfono, ¿te has parado a pensar eso, gilipollas? + +[MOB_09E] +¿Mataste a Leo? Debes tener unas pelotas muy grandes... ¿quieres trabajar para mí? + +[MOB_09F] +Pásate por el café de mi padre en Little Havana y podremos hablar de tú a tú. + +[MOB_10A] +¡Tommy! Mira, tengo que pedirte un favor. + +[MOB_10B] +¡Steve! ¿Cómo van con el rodaje? + +[MOB_10C] +Bien, bien. Yo, je, NOSOTROS necesitamos una escena con una persecución de coches, pero nuestro presupuesto no da para más. + +[MOB_10D] +Me han dejado un coche en la ciudad. Tú sabrás qué hacer. + +[MOB_10E] +De acuerdo, Steve, estaré al tanto. Te veo luego. + +[MOB_11A] +¿Qué hay, hijo? Pensé sólo en llamarte para darte algún consejo. + +[MOB_11B] +Hola, Avery. ¿Qué te preocupa? + +[MOB_11C] +Hay muchas oportunidades en esta ciudad si dispones de las propiedades adecuadas, ¿entiendes lo que quiero decir? + +[MOB_11D] +Creo que sí... + +[MOB_11E] +Lo que digo es que mantengas los ojos abiertos y podrás encontrar la oportunidad de negocio perfecta. Te veré luego. + +[MOB_11F] +Nos vemos, Avery. + +[MOB12_A] +¡Eh, Tommy, soy Avery! Ahora escucha, estoy muy liado en este momento + +[MOB12_B] +y uno de mis representantes necesita carabina para ir a Gator Keys. + +[MOB12_C] +Estoy detrás de unas tierras por allí, así que voy a enviar alguien a que ablande el trato. + +[MOB12_D] +Podrías hacerme un favor y asegurarte de que llega allí, ¿entendido? + +[MOB12_E] +Sí, eso está hecho, Avery. ¿Dónde te gustaría que le recogiese? + +[MOB12_F] +Está terminando algunos negocios en la obra. Le dije que le recogerías allí. + +[MOB12_G] +Ningún problema. Nos vemos, Avery. + +[MOB13_A] +¿Vercetti? ¡VERCETTI! ¡Maldito seas, tío, tienes que ayudarme! + +[MOB13_B] +¿Sr. Moffat? ¿Cómo le va la vida en familia? + +[MOB13_C] +Maldito seas, DEMONIOS, ¿me oyes? + +[MOB13_D] +Bueno, fue agradable charlar... + +[MOB13_E] +¡ESPERA! Espera, Vercetti Tommy, ¿Puedo llamarte Tommy? + +[MOB13_F] +Los dos somos hombres de negocios, ¿verdad? Reconoces un buen negocio cuando lo ves. + +[MOB13_G] +No tengo tiempo para charlar, vaya al grano. + +[MOB13_H] +DINERO. El dinero es el maldito asunto. + +[MOB13_I] +Me escapado de la trena otra vez, pero nunca tardan mucho en encontrarme... ¡creen que es un maldito juego! + +[MOB13_J] +Estoy en un teléfono público en alguna parte de este agujero de mierda olvidado de la mano de Dios. + +[MOB13_K] +Sácame de aquí antes de que me lleven de vuelta y... oh... Dios... + +[MOB13_L] +Bueno, estoy ocupado durante las próximas... + +[MOB13_M] +¡No! ¡No me jodas, ten piedad! Ningún hombre debería tener que hacer esas, esas cosas. + +[MOB13_N] +Estoy de rodillas, Tommy, en el fango, rogándote, por favor... + +[MOB13_O] +Supongo que podría desviarme hacia allí, veré si puedo localizarte... + +[MOB13_P] +Oh, Dios, ya vienen. Por el amor de Dios, date prisa, ¡date prisa! + +[MOB_14A] +Hola, Tommy, me vas a adorar, amigo. + +[MOB_14B] +Un pajarito me ha dicho que la división SWAT de Vice City tiene un depósito en una entidad bancaria ciertamente grande, + +[MOB_14C] +donde guardan todos los sobornos que han recibido durante estos años, + +[MOB_14D] +¡como una especie de fondo de retiro para los veteranos! + +[MOB_14E] +Por supuesto, si esta información te ayudase a adquirir parte de ese dinero, + +[MOB_14F] +¿Supongo que te sentirías obligado a mandarme algo? + +[MOB_14G] +Lo tendré en mente, gracias Kent. + +[MOB_14H] +Es Paul. Soy de Kent, cerca de Londres, idiota. + +[MOB_14I] +Mi conocimiento de la geografía provincial inglesa ya no es lo que era. + +[MOB15_A] +Tommy, colega, soy Paul, de Kent, + +[MOB15_B] +un par de titis han escrito tu nombre por todas partes, en el Malibú. + +[MOB15_C] +¿De qué estás hablando? + +[MOB15_D] +Tías. Palomitas. Ya sabes. Chicas. De las buenas, no creas que son furcias ni nada por el estilo. + +[MOB15_E] +Tienes que venir a echarles un vistazo. + +[MOB16_A] +Tommy, aquí Paulo, ¿qué pasa amigo? + +[MOB16_B] +¿Qué quieres Paul? No quiero ropa de imitación. + +[MOB16_C] +Muy divertido, colega, pero yo no toco la ropa falsificada. + +[MOB16_D] +Nada, sólo llamaba para ver si consigo un papel en una de tus películas, + +[MOB16_E] +en Inglaterra hice un montón de porno, colega. + +[MOB16_F] +Guardo más fuego que tu, hijo mío. + +[MOB16_G] +Paul, gracias por la oferta, lo tendré en mente. + +[MOB16_H] +En serio, no te olvides de mí, después de todo lo que he hecho por ti. + +[MOB16_I] +Eso es lo que estoy intentando olvidar. + +[MOB19_A] +Tommy V, aquí KP. Kent Paul. En las calles se dice que hay gente que te quiere despellejar. + +[MOB19_B] +Mantén los ojos abiertos. Y recuerda, yo no te he dicho nada de esto. + +[MOB_20A] +De acuerdo, Tommy, soy Paul. Me ha dicho un pajarito que has sido un chico muy malo. + +[MOB_20B] +A alguien le ha ofendido que de repente estés actuando como si fueses el mandamás, dándote aires de grandeza. + +[MOB_20C] +Bueno, no digas que nunca te advertí. Presumir es un juego de tontos, hijo. + +[MOB_20D] +En cualquier caso, oí que se ha puesto un precio a tu cabeza y que alguien te la va a partir, + +[MOB_20E] +así que ten cuidado, y acuérdate de mí, colega. + +[MOB21_A] +Tommy, Thomas, soy Cortez. ¿Qué pasa? + +[MOB21_B] +Las cosas van bien. ¿Cómo estás? + +[MOB21_G] +De todos modos, Tommy, quisiera preguntarte algo sobre Mercedes. + +[MOB21_H] +¿Sí? ¿Qué pasa con ella? + +[MOB21_I] +Oh Tommy, Tommy, oigo todas esas historias y no sé qué pensar. + +[MOB21_K] +Quizás ella piense que puede hacer lo que quiera ahora que pero dime, Tommy, ¿es verdad? + +[MOB21_M] +¿El qué es verdad? + +[MOB21_N] +Las historias que he oído. ¿De verdad va a ser abogada? + +[MOB21_O] +Oh Tommy, la vergüenza. Los Cortez somos una familia orgullosa + +[MOB21_P] +y nunca permitiríamos que una hija nuestra se convirtiese en abogada. Por favor, dime que no es así. No creo que pudiese soportarlo. + +[MOB21_Q] +Coronel, puedo asegurarle que Mercedes nunca será abogada. No tema. + +[MOB21_R] +Gracias, Tommy, gracias. La vergüenza sería insoportable. + +[MOB21_S] +Lo sé, coronel. + +[MOB21_T] +Bueno, Tommy, debes perdonarme, ha llegado el nuevo ministro de interior. + +[MOB21_U] +Hace muchos años, yo maté a su padre en un golpe fallido así que debo ser cortés. Buenos días, amigo. + +[MOB21_C] +Tommy, aquí siempre es una lucha. Siento el ruido, acabamos de tener otro golpe fallido. + +[MOB21_D] +La gente es la señora más exigente de todas. + +[MOB21_E] +Desde que he vuelto de Vice City y hasta ahora, hemos tenido tres revoluciones y cuatro golpes. + +[MOB21_F] +Afortunadamente, me han ascendido cada vez. + +[MOB21_J] +Quizás todo el mundo me está humillando. + +[MOB21_L] +pero dime, Tommy, ¿es verdad? + +[MOB22_A] +Tommy, estás demostrando ser muy útil, amigo mío. + +[MOB22_B] +Gracias, Cortez. ¿Qué hay de mi asunto? + +[MOB22_C] +Tommy, estoy trabajando infatigablemente en tu nombre para conseguir llegar al fondo de esta zanja de apestosas mentiras y engaños, + +[MOB22_D] +tienes mi palabra al respecto, pero mientras tanto, + +[MOB22_E] +por favor, acepta el estimable agradecimiento de mi gente por tu trabajo para nosotros. + +[MOB_25A] +Tommy, soy Cortez. Los franceses me están causando todo tipo de problemas. + +[MOB_25B] +Malditos hipócritas. ¡Se pasan cien años robando a países pobres y me llaman ladrón a mí! + +[MOB_25C] +Voy a necesitar tu ayuda tan pronto como sea posible. + +[MOB_25D] +Por favor, Tommy, date prisa, te necesito, ¿de acuerdo? Odio a esos malditos franceses. + +[MOB_26A] +Hola, ¿Tommy? + +[MOB_26B] +¿Sí? + +[MOB_26C] +Soy Baker. Sólo quería decirte que verdaderamente disfruté con el espectáculo. + +[MOB_26D] +Los chicos y yo queremos darte las gracias, y recordarte, + +[MOB_26E] +que te tienes nuestro respeto. Buenos días. Sigue cabalgando duro, chico. + +[MOB_29A] +Hola, ¿hablo con el Sr. Tommy Vercetti? + +[MOB_29B] +Sí. + +[MOB_29C] +Bueno, he oído por radio macuto que usted es el hombre que hay que buscar cuando alguien tiene una plaga de bichos. + +[MOB_29D] +Puede... + +[MOB_29E] +Bueno, yo tengo una plaga de bichos de verdad. Haitianos, por todas partes. + +[MOB_29F] +Mi nombre es Umberto Robina y quiero que nos reunamos en el Café Robina lo antes posible, + +[MOB_29G] +porque, como le digo, estos malditos haitianos se han pasado de la raya esta vez. + +[MOB_29H] +Prueba + +[MOB_30A] +Tommy, soy Umberto Robina. + +[MOB_30B] +¿Cómo está el café? + +[MOB_30C] +Oh, maravilloso. Increíble. Tommy, increíble. Sin blandengues, Tommy, sólo hombres de verdad, ya sabes, ¡y las mujeres hermosas! + +[MOB_30D] +De todos modos, quería decirte que para papá y para mí, para nosotros, ahora eres un cubano. + +[MOB_30E] +Has demostrado que tienes unas pelotas de cojones, tío. + +[MOB_30F] +Gracias, Umberto. Nadie me había dicho eso desde que salí de la cárcel. Nos vemos. + +[MOB_33A] +Tommy, soy Phil, olvida toda esa mierda del pasado y escúchame, ¿me oyes? + +[MOB_33B] +Bien. Tengo un pelotazo extra a punto de fermentar y me preguntaba si te apetecería probarlo. + +[MOB_33C] +En serio, Tommy, si quieres una copa, o si necesitas ponerte a tono, este material hará de ti un hombre. + +[MOB_33D] +A mí me pasó aunque todavía no puedo ver por un ojo. Te estaré esperando, lo que oyes. + +[MOB_34A] +Tommy, de verdad que he disfrutado trabajando contigo. No me había divertido tanto desde la loma en Vietnam, colega. + +[MOB_34B] +De todas formas, si necesitas algo, llámame, ¿me oyes? + +[MOB_34C] +Siempre me acuerdo de aquellos con los que he servido, + +[MOB_34D] +y estoy seguro de que puedo ayudarte, ¿me oyes? + +[MOB_35A] +Tommy, la herida está cicatrizando bien. Lo gracioso es + +[MOB_35B] +que he luchado en 6 zonas de combate y siempre he salido sin un rasguño, ¡y ahora esto! + +[MOB_35C] +Phil el manco. Aún así, tengo una buena selección armas de fuego para mancos, así que nunca seré Phil el desarmado, lo que oyes. + +[MOB_35D] +Venga, hijo, corta con esa mierda sentimental y pídete una copa, ¡me oyes! + +[MOB_36A] +Tommy, soy Phil, quiero darte las gracias por ayudarme allí, hijo, + +[MOB_36B] +maldito Charlie, siempre te tenderá una emboscada de un modo o de otro. + +[MOB_36C] +Bueno, la herida está cicatrizando bien, y eso quiere decir que voy a dejar de estafar al gobierno con mi cheque de incapacidad. Gracias, colega. + +[MOB_40A] +Eh, Tommy, soy Sonny. ¿Qué tal el bronceado? + +[MOB_40B] +No tengo bronceado. + +[MOB_40C] +Bueno, tampoco tienes mi dinero, así que me estoy preguntando, + +[MOB_40D] +¿qué estás haciendo? Así que, dime Tommy ¿qué estás haciendo? + +[MOB_40E] +Estoy buscando el dinero, Sonny. No te preocupes. + +[MOB_40F] +Me estoy preocupando, Tommy, ese es mi estilo, + +[MOB_40G] +porque parece que en mi vida tengo este problema con gente que no es de confianza. + +[MOB_40H] +No seas una persona poco fiable, Tommy, por favor. + +[MOB_40I] +Haznos un favor. Tengo muchas ganas de tener noticias tuyas. + +[MOB_41A] +Tommy, ¿te acuerdas de mí? + +[MOB_41B] +Hola, Sonny. + +[MOB_41C] +Sí, está bien, Sonny. Somos viejos amigos, + +[MOB_41D] +y nunca me escribes, nunca llamas. ¿No quieres que sigamos siendo amigos? + +[MOB_41E] +He estado muy ocupado intentando arreglar las cosas. No me diste mucho apoyo. + +[MOB_41F] +Oh, y es culpa mía, ¿verdad? Bueno, he oído que has estado muy ocupado. + +[MOB_41G] +Ocupado matando a capos. Ocupado tomando el poder. + +[MOB_41H] +No te olvides de nosotros, Tommy, porque puedo asegurarte que yo no me he olvidado de ti. + +[MOB_42A] +Tommy. + +[MOB_42B] +Sonny. + +[MOB_42C] +Obviamente tienes un problema a la hora de escuchar a los demás, de modo que lo intentaré de nuevo. + +[MOB_42D] +Tommy, ¿dónde está el maldito dinero, dónde está el maldito material y dónde está mi parte de tus nuevos golpes? + +[MOB_42E] +Me estás haciendo quedar como un idiota, Tommy, y no me hace gracia. + +[MOB_43A] +Tommy, Tommy, Tommy, tenía a Sonny al teléfono, de acuerdo, ¿me sigues? + +[MOB_43B] +No sé tú, pero hay algo sobre un hombre que amenaza con asesinar a mi familia + +[MOB_43C] +que realmente me pone enfermo. ¿Qué vas a hacer? + +[MOB_43D] +Ten calma, Ken, todo irá bien. + +[MOB_43E] +¡ESTOY calmado, todo lo calmado que puede estar un hombre cuando teme por su vida! + +[MOB_43F] +Ken, ahora mismo tengo otras cosas en la cabeza, + +[MOB_43G] +nos ocuparemos de Forelli cuando llegue el momento. + +[MOB_43H] +Estoy calmado. ¿No te lo parece? Debe ser la muerte inminente la que le hace esto a mi voz. + +[MOB45_A] +Tommy, tenemos cosas de las que hablar. + +[MOB45_B] +¿Cuál es el problema, Lance? + +[MOB45_C] +Eres tú, amigo, creo que no me estás dando una tajada justa. + +[MOB45_D] +Y lo que es más, me estás avergonzando delante de los chicos. No puedo permitirlo. + +[MOB45_E] +Lance, las cosas no son así. Has estado cometiendo errores. + +[MOB45_F] +Tommy, no soy tu chico de los recados. No soy tu emisario. + +[MOB45_G] +Lance, no la cagues, y no tendremos ningún problema. Si la cago yo, entonces podrás venir a reprochármelo en cualquier momento. + +[MOB45_H] +Tommy, he hecho de todo por ti, pero me tratas como si fuese estúpido. No hagas eso. + +[MOB45_I] +Lance no te voy a timar ni a apuñalarte por la espalda, de acuerdo. + +[MOB45_J] +Tómatelo con calma. Esto ya es lo suficientemente duro sin que te me pongas sentimental. + +[MOB45_K] +Confía en mí. ¿Me oyes, me oyes? + +[MOB45_L] +Te oigo, pero Tommy, no puedo aguantar esto por más tiempo. + +[MOB45_M] +Lance, no seas así. Ahora te estoy dando un aviso. + +[MOB45_N] +¿Me oyes? Relájate, tómate unos días libres, y luego hablaremos, ¿vale? + +[MOB46_A] +¡Eh, Tommy! Soy Lance. + +[MOB46_B] +¿Sí? + +[MOB46_C] +Me alegro tanto de tener noticias tuyas, Lance. Venga, tío, enróllate, enróllate. + +[MOB46_D] +Estoy ocupado. ¿Qué quieres? + +[MOB46_E] +Nada. Solo llamo para decirte, ya sabes. Mira Tommy, podemos hacer esto. + +[MOB46_F] +Tú y yo, sin ningún problema. ¿Sabes lo que quiero decir? + +[MOB46_G] +Vamos a tener que hacerlo, porque de otro modo, estaremos muertos, Lance. + +[MOB46_H] +Ya hemos ido demasiado lejos. Pero gracias por llamar. Hablaré contigo más tarde. + +[MOB_47A] +Tommy, Lance, tenemos problemas serios. Ven aquí. Enseguida. + +[MOB52_A] +Eh, Leo, creo que tengo un comprador para la mercancía de Díaz. + +[MOB52_B] +Tienes que llamarle, tío, montar el trato, ¿sabes? + +[MOB52_C] +¿Dónde estás? + +[MOB52_D] +¿Estás bien, Leo? Te noto un tanto diferente. + +[MOB52_E] +Simplemente dime dónde estás. + +[MOB52_F] +¿Quién demonios eres? ¡Que se ponga Leo! + +[MOB52_G] +Leo se ha ido de viaje unos días, me dejó al cargo de todo. + +[MOB52_H] +¡Que te jodan, tío! + +[MOB54_A] +¡Hola, Tommy! + +[MOB54_B] +Hola, Mercedes, ¿cómo te va? + +[MOB54_C] +Tengo un apartamento nuevo en Vice Point, + +[MOB54_D] +pensé que quizás querrías pasarte en algún momento. + +[MOB54_E] +Me encantaría. Te veo luego. + +[MOB55_A] +Tommy, soy yo. + +[MOB55_B] +Hola, Mercedes. + +[MOB55_C] +Tommy, estoy tan aburrida, ¿cuándo vamos a divertirnos un poco? + +[MOB55_D] +¿Qué quieres decir? + +[MOB55_E] +Bueno, sé que estás ocupado luchando y matando y sobornando a gente, + +[MOB55_F] +pero yo sólo quiero divertirme un poco. Así que no te olvides de mí, ¿me oyes? + +[MOB56_A] +Tommy, oí que te cargaste a Ricardo Díaz. + +[MOB56_B] +Hubo un desafortunado tiroteo en su mansión. + +[MOB56_C] +Creo que se quemó hasta morir en su camisa acrílica. + +[MOB56_D] +Tommy, estoy tan orgullosa de ti. Sabía que eras un hombre de verdad. + +[MOB56_E] +Era un marrano en pantalones, me haces sentir muy orgullosa de ser tu amiga. + +[MOB56_F] +No, sé que vas a estar ocupado intentando tomar las riendas de esta ciudad, + +[MOB56_G] +pero no te olvides de mí, ¿me oyes? + +[MOB57_A] +Soy Mercedes. Ya no te amo, Tommy. + +[MOB57_B] +Ya no lo hago. De verdad. Porque ya no eres amable con Mercedes. + +[MOB57_C] +Ya no la tratas como una dama. Me ignoras y te odio. + +[MOB57_D] +¡Insisto en que vengas a verme enseguida! + +[MOB58_A] +Tommy. + +[MOB58_B] +Hola, Mercedes. + +[MOB58_C] +Hola, tipo duro. Estoy muy enfadada contigo, Tommy. + +[MOB58_D] +No me hagas ir nunca más por ahí con Jezz Torrent otra vez. + +[MOB58_E] +Es patético. A mitad de camino, empieza a llorar por su perrito + +[MOB58_F] +que murió cuando él tenía siete años y que si su madre nunca le había querido. + +[MOB58_G] +Y Tommy. En privado lleva peluca y sostén. + +[MOB58_H] +¡No estoy muy contenta contigo! + +[MOB59_A] +Ooh Tommy, soy Mercedes. + +[MOB59_B] +Sólo quería decirte que me divertí mucho en el plató de rodaje. + +[MOB59_C] +Cualquier otra cosa que tengas así, me lo dices. + +[MOB59_D] +Lo digo de verdad. Siempre quise ser actriz. + +[MOB59_E] +Creo que aprendí mucho sobre el proceso dramático. + +[MOB59_F] +¡Es tan instructivo! Gracias. Gracias. Hasta muy, muy pronto. Adiós. + +[MOB_99] +Ve al teléfono público del lugar. + +[MOB_98] +Ve al teléfono público del lugar. + +[MOB_97] +Ve al teléfono público del lugar. + +[MOB_96] +Ve al teléfono público del lugar. + +[MOB_95] +Ve al teléfono público del lugar. + +[A_TIME] ++~1~ segundos + +[DODO_FT] +¡Volaste durante ~1~ segundos! + +[GA_8] +Utiliza el detonador para activar la bomba. + +[GA_10] +Muy bien. Aquí están tus ~1~ $. + +[GA_11] +Ya tenemos ese coche. ¡No nos sirve! + +[GA_12] +Bomba activada. + +[GA_13] +Entregado como un profesional. Completa la lista y habrá una bonificación para ti. + +[GA_14] +Todos los coches. ¡ESTUPENDO! Aquí tienes una cosilla. + +[GA_15] +Espero que te guste el nuevo color. + +[GA_16] +Volver a pintarlo es gratis. + +[GA_19] +No estamos interesados en este modelo. + +[GA_20] +Tenemos más de eso de los que podemos colocar. Lo siento, tío, no hay trato. + +[CHASE] +Atención de la prensa + +[CHASE1] +Ignorado + +[CHASE2] +Aburrido + +[CHASE3] +Algo interesante + +[CHASE4] +Página 7 del periódico local + +[CHASE5] +Portada del periódico local + +[CHASE6] +Página 2 del Vice Courier + +[CHASE7] +Portada del Vice Courier + +[CHASE8] +TV Local a las 3 de la mañana + +[CHASE9] +Noticias locales en TV + +[CHASE10] +Cobertura en directo de la TV Local + +[CHASE11] +Página 12 del UFA Today + +[CHASE12] +Página 4 del UFA Today + +[CHASE13] +Imagen en UFA Today + +[CHASE14] +TV Nacional a las 4 de la mañana + +[CHASE15] +Noticias de la TV Nacional + +[CHASE16] +Cobertura en directo de la TV Nacional + +[CHASE17] +Noticias internacionales + +[CHASE18] +Crisis nacional + +[CHASE19] +Crisis internacional + +[CHASE20] +Acontecimiento mundial + +[CHASE21] +Leyenda + +[CR_1] +La grúa no puede levantar este vehículo + +[PU_MONY] +No tienes suficiente dinero. + +[CO_ALL] +Los tienes todos. Aquí tienes una cosilla... + +[FEM_ON] +SÍ + +[FEM_OFF] +NO + +[FEM_YES] +Sí + +[FEM_NO] +No + +[FEM_RET] +REINTENTAR + +[FEC_NA] +NO DISPONIBLE + +[FEC_CWL] +Escoger arma hacia la izquierda + +[FEC_CWR] +Escoger arma hacia la derecha + +[FEC_LOF] +Mirar hacia adelante + +[FEC_TAR] +Objetivo + +[FEC_MOV] +Movimiento + +[FEC_CAM] +Modos de cámara + +[FEC_PAU] +Pausa + +[FEC_ENV] +Entrar en el vehículo + +[FEC_JUM] +Saltar + +[FEC_ATT] +Atacar o disparar arma + +[FEC_RUN] +Correr + +[FEC_FPC] +Cámara en primera persona + +[FEC_LL] +Mirar a la izquierda + +[FEC_LB] +Retrovisor + +[FEC_LR] +Mirar a la derecha + +[FEC_ABR] +Acelerar, frenar o dar marcha atrás + +[FEC_HOR] +Bocina + +[FEC_VES] +Control del vehículo + +[FEC_BRA] +Freno o marcha atrás + +[FEC_HAB] +Freno de mano + +[FEC_CAW] +Arma del vehículo + +[FEC_ACC] +Acelerar + +[FEC_CCF] +Configuración: + +[FEC_CF1] +Configuración 1 + +[FEC_CF2] +Configuración 2 + +[FEC_CF3] +Configuración 3 + +[FEC_CF4] +Configuración 4 + +[FEC_CDP] +Pantalla del mando + +[FEC_ONF] +A pie + +[FEC_INC] +En coche + +[FEC_VIB] +Vibración : + +[FEL_ENG] +Inglés + +[FEL_FRE] +Francés + +[FEL_GER] +Alemán + +[FEL_ITA] +Italiano + +[FEL_SPA] +Español + +[FED_DBG] +Menú de soluciones técnicas + +[FED_RID] +Recargar IDE + +[FED_RIP] +Recargar IPL + +[FED_PAH] +Parse Heap + +[FED_DFL] +CLos guiones::DbgBandera + +[FED_DLS] +Gran luz blanca de solución técnica encendida + +[FED_SPR] +Mostrar grupos de aceras + +[FED_SCR] +Mostrar grupos de carreteras + +[FED_SCZ] +Mostrar zonas de desechos + +[FED_DSR] +Solicitud de solución técnica del streaming + +[FED_SCP] +gbMostrarcolisiónPolys + +[PL_STAT] +Estadísticas del jugador + +[PE_WAST] +Gente que te has cargado + +[PE_WSOT] +Gente eliminada por otros + +[TM_BUST] +Veces detenido + +[GNG_WST] +Miembros de la banda liquidados + +[DED_CRI] +Criminales liquidados + +[PER_COM] +Porcentaje completado + +[KGS_EXP] +Kilogramos de explosivos empleados + +[ACCURA] +Precisión + +[ST_WEAP] +Presupuesto armamentístico + +[ST_PROP] +Presupuesto de propiedades + +[ST_AUTO] +Presupuesto para reparación de coches y pintura + +[ST_PHOT] +Fotografías tomadas + +[ST_LOAN] +Visitas de usureros prestamistas + +[ST_STOR] +Almacenes desvalijados + +[ST_MOVI] +Escenas de especialista + +[ST_PIZZ] +Pizzas entregadas + +[ST_GARB] +Recogidas de basura efectuadas + +[ST_ICEC] +''Helado'' vendido + +[TOP_SHO] +Puntuación máxima en el campo de tiro + +[SHO_RAN] +Clasificación del campo de tiro + +[SEAGULL] +Gaviotas disparadas + +[PROPOWN] +Propiedad en posesión + +[ST_TIME] +Tiempo de juego + +[ST_FTIM] +Horas de vuelo + +[ST_PRAN] +Clasificación de los pilotos + +[ST_RAN0] +Aprendiz + +[ST_RAN1] +Navegante + +[ST_RAN2] +Copiloto + +[ST_RAN3] +Junior + +[ST_RAN4] +Competente + +[ST_RAN5] +Senior + +[ST_RAN6] +As + +[ST_RAN7] +Barón rojo + +[ST_DRWN] +Peces alimentados + +[ST_FASH] +Presupuesto para moda + +[ST_DAMA] +Coste de propiedades destruidas + +[TM_DED] +Visitas al hospital + +[DAYSPS] +Días transcurridos en el juego + +[NUMSHV] +Visitas a pisos francos + +[MXCARD] +Max. Distancia de salto LOCO (pies) + +[MXCARJ] +Max. Altura de salto LOCO (pies) + +[MXCARDM] +Máxima distancia de salto LOCO (m) + +[MXCARJM] +Máxima altura de salto LOCO (m) + +[MXFLIP] +Máximas vueltas de salto LOCO + +[MXJUMP] +Máxima rotación de salto LOCO + +[BUL_FIR] +Balas disparadas + +[BUL_HIT] +Aciertos + +[SPRAYIN] +Pintura + +[BSTSTU] +Mejor maniobra LOCA hasta ahora + +[INSTUN] +Maniobra loca + +[PRINST] +Maniobra loca perfecta + +[DBINST] +Maniobra loca doble + +[DBPINS] +Maniobra loca doble perfecta + +[TRINST] +Maniobra loca triple + +[PRTRST] +Maniobra loca triple perfecta + +[QUINST] +Maniobra loca cuádruple + +[PQUINS] +Maniobra loca cuádruple perfecta + +[NOSTUC] +No se ha completado ninguna + +[NOUNIF] +Saltos únicos completados + +[NMISON] +Intentos de misión + +[PASDRO] +Pasajeros en destino + +[MONTAX] +Dinero conseguido con el taxi + +[DAYPLC] +Gastos diarios provocados a la policía + +[CRIMRA] +Tasa criminal: + +[STPR_1] +El Malibú + +[STPR_2] +Imprenta + +[STPR_3] +Estudio de cine + +[STPR_4] +Fábrica de helados + +[STPR_5] +Salón de coches + +[STPR_6] +Compañía de taxis + +[STPR_7] +Astillero + +[SET1EN] +Configuración 1. Activada + +[GMSAVE] +Guardar partida + +[FEDS_TB] +Volver + +[FEST_OO] +de + +[FEC_TUC] +Control de la torreta + +[FEC_RS3] +Cambiar emisora de radio (Botón L3) + +[FEC_HO3] +Bocina (Botón L3) + +[C_FAIL] +¡Misión de justiciero terminada! + +[C_ESCP] +~r~¡El sospechoso ha escapado! + +[C_VIGIL] +¡BONIFICACIÓN DE JUSTICIERO! + +[HEAL_A] +Tu ~h~salud~w~ aparece en naranja en la parte superior derecha de la pantalla. + +[WRONGCD] +Disco incorrecto. Introduce el disco correcto. + +[NOCD] +La bandeja del disco está vacía. Por favor, inserta el disco. + +[OPENCD] +La bandeja del disco está abierta. Por favor, cierra la bandeja de disco. + +[CDERROR] +Error al leer el DVD de Grand Theft Auto: Vice City + +[RESTART] +Iniciando nueva partida + +[GA_3] +No hay más regalitos. ¡100 $ para volver a pintar! + +[GA_1] +¡Hala! ¡Demasiado peligroso para mi gusto! + +[GA_1A] +Vuelve cuando no estés tan ocupado... + +[HELP9_C] +Pulsa ~h~~k~~k~~PED_FIREWEAPON~~w~para ~h~disparar~w~ el fusil de francotirador. + +[TAXI2] +~r~¡Te has quedado sin tiempo! + +[PAGEB13] +Salud entregada en el escondite + +[PAGEB14] +Adrenalina entregada en el escondite + +[FESZ_CA] +Cancelar + +[FES_NGA] +Nueva partida + +[FES_CAN] +Cancelar + +[FESZ_QL] +Todo el progreso no guardado de tu partida actual se perderá. ¿Continuar cargando? + +[FESZ_QD] +¿Proceder a borrar esta partida guardada? + +[FESZ_QO] +¿Proceder para sobrescribir esta partida guardada? + +[FESZ_QR] +¿Seguro que quieres empezar una nueva partida? Todo el progreso desde la última partida guardada se perderá. ¿Continuar? + +[T4X4_1] +Prueba de PCJ + +[BMX_1] +Trial de tierra + +[BMX_2] +Pista de pruebas + +[BMXFAIL] +~r~¡No has conseguido un nuevo récord! + +[BMX_REC] +~g~¡Nuevo récord establecido: ~1~! + +[T4X4_3] +¡AGARRADO! + +[MM_1] +CONOCIRCUITO + +[T4X4_F] +~r~¡Te rajaste! ¿Demasiado duro para ti? + +[LANDSTK] +Landstalker + +[IDAHO] +Idaho + +[STINGER] +Stinger + +[LINERUN] +Linerunner + +[PEREN] +Perennial + +[SENTINL] +Sentinel + +[RIO] +Río + +[PATRIOT] +Patriot + +[FIRETRK] +Camión de bomberos + +[TRASHM] +Trashmaster + +[STRETCH] +Limusina + +[MANANA] +Mañana + +[INFERNS] +Infernus + +[VOODOO] +Voodoo + +[PONY] +Pony + +[MULE] +Mule + +[CHEETAH] +Cheetah + +[AMBULAN] +Ambulancia + +[FBICAR] +FBI Washington + +[MOONBM] +Moonbeam + +[ESPERAN] +Esperanto + +[TAXI] +Taxi + +[WASHING] +Washington + +[BOBCAT] +Bobcat + +[WHOOPEE] +Mr Whoopee + +[BFINJC] +BF Injection + +[HUNTER] +Hunter + +[POLICAR] +Policía + +[ENFORCR] +Enforcer + +[SECURI] +Securicar + +[BANSHEE] +Banshee + +[PREDATR] +Predator + +[BUS] +Autobús + +[RHINO] +Rhino + +[BARRCKS] +Barracks OL + +[CUBAN] +Cuban Hermes + +[HELI] +Helicóptero + +[DODO] +Dodo + +[COACH] +Autocar + +[CABBIE] +Taxi clásico + +[STALION] +Stallion + +[RUMPO] +Rumpo + +[RCBANDT] +Bandit RC + +[ROMERO] +Romero's Hearse + +[PACKER] +Packer + +[ADMIRAL] +Admiral + +[SQUALO] +Squalo + +[SEASPAR] +Sea Sparrow + +[PIZZABO] +Pizza Boy + +[GANGBUR] +Gang Burrito + +[TROPIC] +Tropic + +[SPEEDER] +Speeder + +[REEFER] +Reefer + +[FLATBED] +Flatbed + +[YANKEE] +Yankee + +[CADDY] +Caddy + +[ZEBRA] +Taxi cebra + +[TOPFUN] +Top Fun + +[SKIMMER] +Skimmer + +[PCJ600] +PCJ 600 + +[PHOENIX] +Phoenix + +[FAGGIO] +Faggio + +[FREEWAY] +Freeway + +[RCBARON] +Barón RC + +[RCRAIDE] +Raider RC + +[GLENDAL] +Glendale + +[OCEANIC] +Oceanic + +[SANCHEZ] +Sanchez + +[SPARROW] +Sparrow + +[LOVEFIS] +Love Fist + +[COASTG] +Guardacostas + +[DINGHY] +Dinghy + +[HERMES] +Hermes + +[SABRE] +Sabre + +[SABRETU] +Sabre Turbo + +[WALTON] +Walton + +[REGINA] +Regina + +[COMET] +Comet + +[DELUXO] +Deluxo + +[BURRITO] +Burrito + +[SPAND] +Spand Express + +[MARQUIS] +Marquis + +[BAGGAGE] +Mozo de equipaje + +[KAUFMAN] +Taxi Kaufman + +[COASTMA] +Guardacostas Maverick + +[MAVERIC] +Maverick + +[RANCHER] +Rancher + +[FBIRANC] +FBI Rancher + +[VIRGO] +Virgo + +[GREENWO] +Greenwood + +[HOTRING] +Hotring Racer + +[BLISTAC] +Blista Compact + +[FEST_DF] +Dist. recorrida a pie (millas) + +[FEST_DC] +Dist. recorrida en coche (millas) + +[FESTDFM] +Distancia recorrida a pie (m) + +[FESTDCM] +Distancia recorrida en coche (m) + +[TOT_DIS] +Distancia total recorrida (millas) + +[TOTDISM] +Distancia total recorrida (m) + +[DISTHEL] +Dist. recorrida en helicóptero (millas) + +[DISTHEM] +Distancia recorrida en helicóptero (m) + +[DISTBOA] +Dist. recorrida en barco (millas) + +[DISTBOM] +Distancia recorrida en barco (m) + +[FEST_LS] +Gente salvada en una ambulancia + +[FEST_CC] +Criminales asesinados en misión Vigilante + +[FEST_FE] +Total de fuegos extinguidos + +[FEST_RP] +Masacres superadas + +[FEST_MP] +Misiones superadas + +[FEST_BB] +Bling-bling Scramble: + +[FEST_H0] +Mayoría de puntos de control + +[FEST_GC] +Total de coches de bandas: + +[FEST_H1] +Destrucción del diablo + +[FEST_H2] +Masacre de la mafia + +[FEST_H3] +Casino Calamity + +[FEST_H4] +Rumpo Wrecker + +[USJ] +¡BONIFICACIÓN POR MANIOBRA ÚNICA! + +[USJ_DUN] +MANIOBRA YA COMPLETADA + +[RATNG1] +Ciudadano honrado + +[RATNG2] +Nadie en especial + +[RATNG3] +Basurero + +[RATNG4] +Cleptómano + +[RATNG5] +Vándalo + +[RATNG6] +Chico de los recados + +[RATNG7] +Carterista + +[RATNG8] +Cleptómano + +[RATNG9] +Chivato + +[RATNG10] +Rata + +[RATNG11] +Lince + +[RATNG12] +Estafador + +[RATNG13] +Timador + +[RATNG14] +Corredor de apuestas + +[RATNG15] +Buscavidas + +[RATNG16] +Matón + +[RATNG17] +Chusma + +[RATNG18] +Bribón + +[RATNG19] +Rufián + +[RATNG20] +Proscrito + +[RATNG21] +Malhechor + +[RATNG22] +Asesino + +[RATNG23] +Super matón + +[RATNG24] +Matón + +[RATNG25] +Presidiario + +[RATNG26] +Exconvicto + +[RATNG27] +Criminal + +[RATNG28] +Saqueador + +[RATNG29] +Mafioso + +[RATNG30] +Conductor + +[RATNG31] +Gorila + +[RATNG32] +Asesino profesional + +[RATNG33] +Asuntos internos + +[RATNG34] +Agente + +[RATNG35] +Ronin + +[RATNG36] +Manitas + +[RATNG37] +Asesino a sueldo + +[RATNG38] +Asociado + +[RATNG39] +Carnicero + +[RATNG40] +Encargado de la limpieza + +[RATNG41] +Asesino + +[RATNG42] +Consigliere + +[RATNG43] +Maleante + +[RATNG44] +Mano derecha + +[RATNG45] +Ejecutor + +[RATNG46] +Teniente + +[RATNG47] +Subjefe + +[RATNG48] +Capo + +[RATNG49] +Jefe + +[RATNG50] +Negado + +[RATNG51] +Don + +[RATNG52] +Rey de la Jodida Ciudad + +[PAGE_00] +. + +[WELCOME] +BIENVENIDO A + +[TSCORE] +GANANCIAS: ~1~ $ + +[PBOAT_2] { reVC update } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para disparar los cañones del barco. + +[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~_ ¡qué gran aterrizaje! + +[ATUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para activar o desactivar las misiones de ATS. + +[FEST_HA] +Nivel más alto de misión de ATS + +[C_KILLS] +CRIMINALES ASESINADOS: ~1~ + +[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é gran aterrizaje! + +[CRED001] +ROCKSTAR NORTH + +[CRD001A] +DIRECTOR DEL ESTUDIO + +[CRD001B] +ANDREW SEMPLE + +[CRED002] +PRODUCTOR + +[CRD002A] +DIRECTOR DE DESARROLLO + +[CRED003] +LESLIE BENZIES + +[CRED004] +DIRECTOR DE ARTE + +[CRED005] +AARON GARBUT + +[CRED006] +DIRECTORES TÉCNICOS + +[CRED007] +OBBE VERMEIJ + +[CRED008] +ADAM FOWLER + +[CRED010] +ANDREW DUTHIE + +[CRED011] +CRAIG FILSHIE + +[CRED012] +WILLIAM MILLS + +[CRED013] +CHRIS ROTHWELL + +[CRD013A] +IMRAN SARWAR + +[CRD013B] +JAMES WORRALL + +[CRD013C] +JOHN HAIME + +[CRED014] +ESCRITO POR + +[CRED015] +JAMES WORRALL + +[CRED016] +PAUL KUROWSKI + +[CRED017] +DAN HOUSER + +[CRED018] +DISEÑO DEL PERSONAJE PRINCIPAL + +[CRD018A] +DISEÑO DE PERSONAJES + +[CRED019] +IAN MCQUE + +[CRD019A] +TOKS SOLARIN + +[CRD019B] +ALAN DAVIDSON + +[CRED020] +ANIMACIÓN PRINCIPAL + +[CRED021] +ALEX HORTON + +[CRD022A] +ANIMACIÓN + +[CRED022] +LEE MONTGOMERY + +[CRD022B] +DUNCAN SHIELDS + +[CRD022C] +GUS BRAID + +[CRED023] +DISEÑO DE VEHÍCULOS + +[CRD023A] +JOLYON ORME + +[CRD023B] +ALAN DUNCAN + +[CRD024A] +DISEÑO DEL VEHÍCULO PRINCIPAL + +[CRED024] +PAUL KUROWSKI + +[CRED025] +DISEÑO DE MAPAS + +[CRED026] +ADAM COCHRANE + +[CRED027] +NIK TAYLOR + +[CRED028] +GARY MCADAM + +[CRED029] +KEIRAN BAILLIE + +[CRED030] +ALISDAIR WOOD + +[CRED031] +ANDREW SOOSAY + +[CRD031A] +STEVEN MULHOLLAND + +[CRD031B] +WAYLAND STANDING + +[CRD031C] +CAMPBELL J. DICK + +[CRD031D] +DISEÑO GRÁFICO + +[CRD031E] +STUART PETRI + +[CRED032] +PROGRAMADOR JEFE + +[CRD032A] +PROGRAMADORES + +[CRED033] +ALEXANDER ROGER + +[CRED034] +GRAEME WILLIAMSON + +[CRED035] +BARANE CHAN + +[CRED036] +DEREK PAYNE + +[CRED037] +GORDON YEOMAN + +[CRD037A] +ALAN CAMPBELL + +[CRD037B] +MARK HANLON + +[CRD037C] +ANDRZEJ MADAJCZYK + +[CRED038] +PRODUCTOR JEFE MUSICAL + +[CRED039] +CRAIG CONNER + +[CRED040] +STUART ROSS + +[CRED041] +INGENIERO JEFE DE AUDIO + +[CRED042] +ALLAN WALKER + +[CRD041A] +INGENIERO DE AUDIO + +[CRD041B] +AUDIO + +[CRD042A] +WILL MORTON + +[CRED043] +PROGRAMADOR DE AUDIO + +[CRED044] +RAYMOND USHER + +[CRED045] +JEFE DE PRUEBAS + +[CRED046] +CRAIG ARBUTHNOTT + +[CRED047] +CONTROL DE CALIDAD PRINCIPAL + +[CRED048] +NEIL CORBETT + +[CRED049] +KEVIN WONG + +[CRED050] +CONTROL DE CALIDAD + +[CRED051] +DAVID BEDDOES + +[CRED052] +DAVID WATSON + +[CRED053] +BARRY CLARK + +[CRED054] +ROSS SPARROW + +[CRED055] +JAMES ALLAN + +[CRED056] +NEIL MEIKLE + +[CRD056A] +GEORGE WILLIAMSON + +[CRD056B] +MATT JONES + +[CRD056C] +ROB HARBOUR + +[CRD056D] +TOM WHITTAKER + +[CRED057] +SOPORTE TÉCNICO PRINCIPAL + +[CRED058] +LORRAINE ROY + +[CRD057A] +SOPORTE TÉCNICO + +[CRED059] +CHRISTINE CHALMERS + +[CRD060A] +SOPORTE DE OFICINA + +[CRD060B] +KIM GURNEY + +[CRD060C] +CASSIE OLIVER + +[CRED060] +ROCKSTAR NUEVA YORK + +[CRED061] +GABINETE DE APOYO + +[CRED062] +SAM HOUSER + +[CRED063] +PRODUCTOR + +[CRED064] +DAN HOUSER + +[CRED065] +VICEPRESIDENTE DE DESARROLLO + +[CRED066] +JAMIE KING + +[CRED067] +OFICIAL JEFE DE TECNOLOGÍA + +[CRED068] +GARY J. FOREMAN + +[CRED069] +PRODUCTOR ASOCIADO + +[CRED070] +JEREMY POPE + +[CRD071A] +DIRECTOR DE CONTROL DE CALIDAD + +[CRD072A] +JEFF ROSA + +[CRED071] +SUPERVISOR MUSICAL + +[CRED072] +TERRY DONOVAN + +[CRED073] +EQUIPO DE PRODUCCIÓN + +[CRED074] +TERRY DONOVAN + +[CRED075] +JENNIFER KOLBE + +[CRED076] +JENEFER GROSS + +[CRED077] +LAURA PATERSON + +[CRED078] +JEFF CASTANEDA + +[CRED079] +JERONIMO BARRERA + +[CRED080] +CARLY SLATER + +[CRED081] +JUNG KWAK + +[CRED082] +BRIAN WOOD + +[CRED083] +RENAUD SEBANNE + +[CRED084] +RICHARD KRUGER + +[CRD084A] +DANIEL EINZIG + +[CRD084B] +JACEN BURROWS + +[CRD084C] +LINN PR + +[CRD084D] +DISEÑO DE PORTADA + +[CRD084E] +STEPHEN BLISS + +[CRED085] +KENT PAUL'S 80 NOSTALGIA ZONE + +[CRED086] +ESCRITO POR DAN HOUSER + +[CRD086A] +PRODUCIDO POR ADAM TEDMAN + +[CRED087] +WWW.KENTPAUL.COM AND WWW.VICECITY.COM + +[CRED088] +CREADO POR + +[CRD088A] +ADAM TEDMAN + +[CRD088B] +DAVID YU + +[CRD088C] +JERRY LUNA + +[CRD088D] +STUART PETRI + +[CRD088E] +MICHAEL CARNEVALE + +[CRD088F] +GREG LAU + +[CRD088G] +FUTABA HAYASHI + +[CRED089] +JEFE DE CONTROL DE CALIDAD + +[CRED090] +CRAIG ARBUTHNOTT + +[CRED091] +ANALISTA JEFE + +[CRED092] +ADAM DAVIDSON + +[CRD092A] +JOE HOWELL + +[CRD092B] +MARC FERNANDEZ + +[CRED093] +ANALISTA DEL JUEGO + +[CRED094] +RICHARD HUIE + +[CRED095] +EQUIPO DE PRUEBAS DE ROCKSTAR + +[CRED096] +LANCE WILLIAMS + +[CRED097] +JOE GREENE + +[CRED098] +BRIAN PLANER + +[CRD098A] +ELIZABETH SATTERWHITE + +[CRD098B] +JAMEEL VEGA + +[CRD098C] +MIKE HONG + +[CRED099] +LEE CUMMINGS + +[CRED100] +HISTORIA + +[CRED101] +JAMES WORRALL + +[CRED102] +DAN HOUSER + +[CRED103] +ADAM TEDMAN + +[CRED104] +PAUL YEATES + +[CRED105] +JENEFER GROSS + +[CRED106] +LAURA PATERSON + +[CRED107] +SECUENCIAS + +[CRED108] +ESCRITAS POR DAN HOUSER Y JAMES WORRALL + +[CRED109] +DIRECCIÓN DE AUDIO DE DAN HOUSER Y NAVID KHONSARI + +[CRED110] +PRODUCIDAS POR JAMIE KING + +[CRD110A] +SELECCIÓN DE ACTORES: JAMIE KING, SEAN MACALUSO + +[CRED111] +REPARTO + +[CRD111A] +PERSONAJES DE REPARTO + +[CRED112] +TOMMY VERCETTI - RAY LIOTTA + +[CRED113] +KEN ROSENBERG - WILLIAN FICHTNER + +[CRED114] +SONNY FORELLI - TOM SIZEMORE + +[CRED115] +STEVE SCOTT - DENNIS HOPPER + +[CRED116] +AVERY CARRINGTON - BURT REYNOLDS + +[CRED117] +RICARDO DÍAZ - LUIS GUZMÁN + +[CRED118] +LANCE VANCE - PHILIP MICHAEL THOMAS + +[CRED119] +CORONEL JUAN CORTEZ - ROBERT DAVI + +[CRED120] +UMBERTO ROBINA - DANNY TREJO + +[CRED121] +PHIL CASSIDY - GARY BUSEY + +[CRED122] +MITCH BAKER - LEE MAJORS + +[CRED123] +MERCEDES CORTEZ - FAIRUZA BALK + +[CRED124] +KENT PAUL - DANNY DYER + +[CRED125] +JEZZ TORRENT - KEVIN MCKIDD + +[CRED126] +SUPERVISOR TAXIS - DEBORAH HARRY + +[CRED127] +CANDY SUXX - JENNA JAMESON + +[CRED128] +BJ SMITH - LAWRENCE TAYLOR + +[CRED129] +TÍA POULET - YOUREE CLEOMILI HARRIS + +[CRED130] +PROVEEDOR - ARMANDO RIESCO + +[CRED131] +COUGAR - BLAYNE PERRY + +[CRED132] +HILARY - CHARLES TUCKER + +[CRED133] +CONGRESISTA ALEX SHRUB - CHRIS LUCAS + +[CRED134] +VIEJO KELLY - GEORGE DICENZO + +[CRD134A] +CAM JONES - GREG SIMS + +[CRD134B] +PSICÓPATA - HUNTER PLATIN + +[CRD134C] +MAUDE LA VENDEDORA DE HELADOS - JANE GENNARO + +[CRD134D] +JETHRO - JOHN ZURHELLEN + +[CRD134E] +GONZÁLEZ - JORGE PUPO + +[CRD134F] +DWAYNE - NAVID KHONSARI + +[CRD134G] +DICK - PETER MCKAY + +[CRD134H] +MIKE EL MATÓN & CHICO PORNO - ROBERT CIHRA + +[CRD134I] +PERCY - RUSSELL FOREMAN + +[CRED135] +CAPTURA DE MOVIMIENTOS + +[CRED136] +ANIMACIÓN DE + +[CRD136A] +DIRECCIÓN TÉCNICA DE ALEX HORTON + +[CRED137] +DIRIGIDO POR + +[CRD137A] +DIRIGIDO POR NAVID KHONSARI + +[CRED138] +PRODUCIDO POR + +[CRD138A] +PRODUCIDO POR JAMIE KING + +[CRD138B] +RENAUD SEBBANE + +[CRED139] +GRABADO EN PERSPECTIVE STUDIOS, BROOKLYN + +[CRED140] +ACTORES DE CAPTURA DE MOVIMIENTOS + +[CRD140A] +BLAYNE PERRY + +[CRD140B] +JONATHON SALE + +[CRD140C] +CHARLES TUCKER + +[CRD140D] +EDDIE MARRERO + +[CRD140E] +WILLIAM MCCALL + +[CRD140F] +JORGE PUPO + +[CRD140G] +ROBERT JACKSON + +[CRD140H] +TARA RADCLIFFE + +[CRD140I] +JENIFER GAMBETESE + +[CRD140J] +KRIS ACHEVARRIA + +[CRD140K] +ALI ORDOUBADI + +[CRD140L] +KAHLEEM POOLE + +[CRED141] +DIÁLOGOS DE LOS PEATONES + +[CRD141A] +ESCRITOS POR DAN HOUSER, MARC FERNÁNDEZ, GILLIAN TELLING Y NAVID KHONSARI + +[CRD141B] +CON LA AYUDA DE JEREMY POPE, LANCE WILLIAMS, Y JENNY JEMISON + +[CRED142] +ESCRITO POR + +[CRD142A] +DAN HOUSER Y JAMES WORRALL + +[CRED143] +DIRIGIDO POR DAN HOUSER, CRAIG CONNER, MARC FERNÁNDEZ, Y ALLAN WALKER + +[CRED144] +PRODUCIDO POR RENAUD SEBANNE + +[CRED145] +PEATONES + +[CRED146] +ADAM DAVIDSON, ADAM WATKINS, ALEJANDRO K. BROWN, ALEX ANTHONY SIOUKAS, ALEX GARCÍA, + +[CRED147] +ALICE SALTZMAN, ALISON CIHRA, AMY SALIMA, AMY SALZMAN, ANDREA VIDELA, ANTHONY ATTI, + +[CRED148] +ANTHONY RIVERA, BIJAN SHAMS, BLAYNE PERRY, BRETT BISOGNO, BREYE MATA, BRIAN PANEN, + +[CRED149] +BROCK VODER, CAREY BERTINI, CHARISSE LAMBERT, CHRIS DIFAT, CHRIS REISENBERGER, + +[CRED150] +CHRISTOPHER BRODAY, CHRISTOPHER CARRO, CYNTHIA GREENE, DAMARIES LÓPEZ, DAN LEE, + +[CRED151] +DAN SCHNEIDER, DAN TOYAMA, DAVID DEAN CHALTFIELD, JR., DAVID HARRISON, DAVID WILEY, + +[CRED152] +DEBORAH COLLINS, DEBRANDA CHANEY-GILES, DEMETRA KOUKOULAS, DENISE ROSADO, + +[CRED153] +DEVIN BENNETT, DEVIN WINTERBOTTOM, DORIS WOO, DOUGLAS HARRISON, DUNCAN COUTTS, + +[CRED154] +DUPE AJAYI, EDWIN AVELLANEDA, ELIZABETH HOWELL, ELIZABETH SATTERWHITE, ERIC NAGLE, + +[CRED155] +ESTEBAN KARPLUS, F. FONT, FUTABA HAYASHI, GENE HILGREEN, GERALD COSGROVE, + +[CRED156] +GERARD LUNA, GILLIAN TELLING, GREGG CARLUCCI, GREGORY CLERVOIX, GREGORY SCHWEIZER, + +[CRED157] +HADLEY TOMICKI, J. ROSSETT, JAMEEL VEGA, JASON JONES, JEFF ROSA, JENNIFER JEMISON, + +[CRED158] +JEREMY TAGGERT, JESSICA RIDER, JOSEPH GREENE,JOSEPH HOWELL, KATE DUKICH, + +[CRED159] +KEL O'NEILL, KEVIN HOPKINS, LADAWN JAMES, LANCE WILLIAMS, LAURA BUBBLES, + +[CRED160] +LAURA PATTERSON, LEE CUMMINGS, LETICIA L. YOUNG, LINDSAY KENNEDY, LISA ORITZ, + +[CRED161] +LORNA JORDAN, LUCIO AMADIO, MARCO FERNÁNDEZ, MARIKO TANAKA, MARLON MATTHEWS, + +[CRED162] +MARY TELLING, MASAYOSHI MITSUYAMA, MATTHEW CHUNG, MAX ALLSTADT, MAX BOGDANOV, + +[CRED163] +MELISSA ÁLVAREZ, MICHAEL MAY, MICHAEL ROTHSTEIN, MIGUEL VIDAL, MIKE FEDERLINE, + +[CRED164] +NATALIE DESCALZO, N'GAI MEMBERS, NICOLAS MALLO, NOELLE SADLER, NORBERT MORIVAN, + +[CRED165] +OSWALD GREENE, JR., PETER MCKAY, PETER APPEL, PRESTON SAVARESE, RAFAEL GONZÁLEZ, + +[CRED166] +RANDY JOHNSON, REY CONCEPCIÓN, RICHARD KROGER, ROB TIBBS, ROBERT JACKSON, + +[CRED167] +ROBERT SCHULER, ROSS A. MCINTYRE, RUSSELL FOREMAN, RUTH NÚÑEZ, SALVADORE SUAZO, + +[CRED168] +SAM WHITE, SANTOS GONZÁLEZ, SCOTT SMITH, SEYMOUR FRAILMAN, SPELMAN BRAUMAN, + +[CRED169] +STEPHANIE TELLING, STEVE KNEZEVICH, STEVE ROBERT, SUMIKO YASUDA, SUSAN LEWIS, + +[CRED170] +SYLVIA COLACIOS, TOMOKO MIYAZAKI, TRON, VERDEL HALE, YVES MONDESIR, ZENO LEINFELDER, + +[CRED171] +DAVID BEDDOES, CHRISTINE CHALMERS, BARRY CLARK, NEIL CORBETT, KIM GURNEY, NEIL MEIKLE, + +[CRED172] +CASSIE OLIVER, LORRAINE ROY, DAVID WATSON, KEVIN WONG, WILL MORTON + +[CRED175] +ADAM DAVIDSON + +[CRED176] +LANCE WILLIAMS + +[CRED177] +NEIL MCCAFFREY + +[CRED178] +LAURA PATERSON + +[CRED179] +REY CONCEPCIÓN + +[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 HÉRCULES + +[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] +ASESOR MUSICAL + +[CRD218A] +HEINZ HENN + +[CRD218B] +STUART ROSS + +[CRED219] +COORDINADOR DE LA BANDA SONORA + +[CRED220] +TERRY DONOVAN + +[CRED221] +PRODUCTOR DE ROCKSTAR GAMES + +[CRED222] +DAN HOUSER Y LAZLOW + +[CRED223] +PRODUCTOR DE ROCKSTAR NORTH + +[CRED224] +CRAIG CONNER + +[CRED225] +ALLAN WALKER + +[CRED226] +LAZLOW + +[CRED227] +DJ BANTER E IMÁGENES + +[CRED228] +ESCRITO POR DAN HOUSER Y LAZLOW + +[CRED229] +FLASH FM + +[CRD229A] +TONI-MARIA CHAMBERS + +[CRD229B] +VOCES FICTICIAS Y PRODUCCIÓN-JEFF BERLIN + +[CRED230] +GRACIAS ESPECIALES A + +[CRED231] +TOMMY MOTTOLA, + +[CRED232] +MICHELLE ANTHONY, + +[CRED233] +STEVE BARNETT, + +[CRED234] +CHUCK FLECKENSTEIN, + +[CRED235] +RITA LIBERATOR + +[CRED236] +MARTIN & CLAIRE LOGAN + +[CRED237] +SANDRA HUTTON + +[CRED238] +CHRISTINE DAVIDSON + +[CRED239] +ALAN, RED & BIGFOOT + +[CRED240] +LE T + +[CRED241] +COLIN DONALD + +[CRED242] +KERRY STALLWOOD + +[CRED243] +ALAN MCGREGOR + +[CRED244] +CHRIS MORTON + +[CRED245] +EMIL BUSSE + +[CRED246] +EMILY BAILLIE + +[CRED247] +KEVIN ARCHIBALD + +[CRED248] +MORAG KERR + +[CRED249] +CATH WALKER + +[CRED250] +ISO BAR + +[CRED251] +WATERLINE + +[CRED252] +NEWS CAFE + +[CRD251A] +THE POND + +[CRD252A] +PIVO + +[CRED253] +BUDGET VIDEO RENTALS + +[CRED254] +LORNA'S SCOOTER + +[CRED255] +GARETH MURFIN + +[CRED256] +ARTE ADICIONAL + +[CRED257] +TONY PORTER + +[CRED258] +CRAIG MOORE + +[CRED259] +ANIMACIÓN DE SINCRONIZACIÓN LABIAL DE LAS SECUENCIAS + +[CRED260] +COSGROVE HALL FILMS + +[CRED261] +PRODUCTOR - OWEN BALLHATCHET + +[CRED262] +ANIMADOR SENIOR - JON TURNER + +[CRED263] +ANIMADORES - RICHARD DRUMM + +[CRED264] +DAVE BROWN + +[CRED265] +MAIR THOMAS + +[CRED266] +PRASHANT PATEL + +[CRED267] +ASESOR TECNOLOGÍA DE AUDIO + +[CRED268] +RIK EDE PARA GAMESOUND LTD. + +[CRED269] +SOPORTE DTS INTEGRATION + +[CRED270] +TED LAVERTY DE DTS + +[CRED271] +CHRIS GREER DE DTS + +[CRED272] +JASON PAGE DE SCEE + +[CRED273] +INVESTIGACIÓN Y ANÁLISIS + +[CRED274] +VROCK + +[CRED275] +DJ: LAZLOW COMO ÉL MISMO + +[CRED276] +VOZ-JOE KELLY + +[CRED277] +PRODUCCIÓN-JONATHAN HANST + +[CRED278] +WAVE 103 + +[CRED279] +DJ: ADAM FIRST-JAMIE CANFIELD + +[CRED280] +VOZ-JEN SWEENEY + +[CRED281] +PRODUCCIÓN-JONATHAN HANST + +[CRED282] +FEVER 105 + +[CRED283] +DJ: OLIVER 'LADYKILLER' BISCUIT-JULIUS DYSON + +[CRED284] +VOZ MASCULINA-ED MCMANN + +[CRED285] +VOZ FEMENINA-SHAWNEE SMITH + +[CRED286] +PRODUCCIÓN- LISTEN KITCHEN + +[CRED287] +EMOTION 98.3 + +[CRED288] +DJ: FERNANDO- FRANK CHAVEZ + +[CRED289] +VOZ-JEN SWEENEY + +[CRED290] +PRODUCCIÓN-JONATHAN HANST + +[CRED291] +RADIO ESPANTOSO + +[CRED292] +DJ: PEPE-TONY CHILRODES + +[CRED293] +WILDSTYLE + +[CRED294] +DJ: MISTER MAGIC AS HIMSELF + +[CRED295] +VOZ-FRANK SILVESTRO + +[CRED296] +PRODUCCIÓN-LAZLOW + +[CRED297] +KCHAT + +[CRED298] +ESCRITO POR DAN HOUSER Y LAZLOW + +[CRED299] +PRODUCIDO Y EDITADO POR LAZLOW + +[CRED300] +DJ AMY SHECKENHAUSEN -LEYNA WEBER + +[CRED301] +JEZ TORRENT-KEVIN MCKIDD + +[CRED302] +MANDY -COLLEEN CORBETT + +[CRED303] +MICHELLE CARAPADIS-MARY BIRDSONG + +[CRED304] +MR.ZOO-CARL DOWLING + +[CRED305] +GETHSEMANEE-LYNN LIPTON + +[CRED306] +CLAUDE MAGINOT-JOHN MAUCERI + +[CRED307] +BJ SMITH-LAWRENCE TAYLOR + +[CRED308] +THOR-FRANK FAVA + +[CRED309] +PERSONAS LLAMANDO A LA RADIO + +[CRED310] +COUZIN ED, JOSH CLARK, JASON BUHRMESTER, JUAN ALLER, WAYNE OLIVER, SUSAN LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, KEITH BROADAS + +[CRED311] +LEWIS, GILLIAN TELLING, TOM MURRAY, MIKE FERRANTE SR., EMMANUEL GOLDSTEIN, + +[CRED312] +DAN HOUSER, NICK MANDELOS, GERRY COSGROVE, MIKE PALERMO, PORKCHOP, + +[CRED313] +KEITH BROADAS + +[CRED314] +VCPR + +[CRED315] +ESCRITO POR DAN HOUSER Y LAZLOW + +[CRED316] +PRODUCIDO POR LAZLOW + +[CRED317] +MAURICE CHAVEZ-PHILLIP ANTHONY RODRIGUEZ + +[CRED318] +JONATHAN FREELOADER- PATRICK OLSEN + +[CRED319] +MICHELLE MONTANIUS-KELLY GUEST + +[CRED320] +REP. ALEX SHRUB- CHRIS LUCAS + +[CRED321] +CALLUM CRAYSHAW- SEAN MODICA + +[CRED322] +JOHN F. HICKORY- LJ GANSEN + +[CRED323] +PASTOR RICHARDS- DAVID GREEN + +[CRED324] +JAN BROWN- MAUREEN SILLIMAN + +[CRED325] +BARRY STARK- RENAUD SEBBANE + +[CRED326] +JENNY LOUISE CRAB- MARY BIRDSONG + +[CRED327] +KONSTANTINOS SMITH- KONSTANTINOS.COM + +[CRED328] +JEREMY ROBARD-PETER SILVESTRO + +[CRED329] +ANUNCIOS DE RADIO + +[CRED330] +ESCRITOS POR DAN HOUSER Y LAZLOW + +[CRED331] +PRODUCIDOS POR LAZLOW + +[CRED332] +JINGLES ADICIONALES PRODUCIDOS POR CRAIG CONNER + +[CRED333] +VOCES EN LOS ANUNCIOS: + +[CRED334] +ADAM DAVIDSON, ALEX ANTHONY, ALICE SALTZMAN, AMY SALZMAN, KATE DUKICH, + +[CRED335] +ARAN RONICLE, BARB JONES, BEN KRECH, BRIAN THOMAS, BROCK YODER, CHRIS + +[CRED336] +FERRANTE, CRAIG CONNER, DAVE RYAN, DAVID GREEN, DORIS WOO, DOUGLAS + +[CRED337] +HARRISON, ED MCMANN, FRANK CHÁVEZ, FRANK FAVA, GENE HILGREEN, GREG + +[CRED338] +SCHWEIZER, HUNTER PLATIN, JAMES FERRANTE, JEFF BERLIN, JEFF ROSA, JOE KELLY, + +[CRED339] +JOHN MAUCERI, JOSH CLARK, JULIE WEMYSS, KEVIN STRALEY, KIM GURNEY, LANCE + +[CRED340] +WILLIAMS, LAURA PATERSON, LAZLOW, LISA ORTIZ, LORNA JORDAN, LUCIEN JONES, + +[CRED341] +MAUREEN SILLIMAN, MIKE FERRANTE JR., PETE GUSTIN, PETER SILVESTRO, RAFF + +[CRED342] +CROLLA, RANDY JOHNSON, RICHARD KRUGER, RON REEVE, SHELLEY MILLER, SKY, TJ ALLARD + +[CRD344A] +AUDIO GRABADO EN DIGITAL ARTS STUDIOS, + +[CRED344] +NYC, TRACK 9 STUDIOS, NYC, + +[CRED345] +WEDDINGTON MULTIMEDIA, LOS ANGELES, + +[CRD345A] +SYNC SOUND, NYC Y RADIO LAZLOW, LONG ISLAND. + +[CRED346] +GRACIAS A AXEL ERICSON Y WON LEE EN DIGITAL ARTS, PAUL VÁSQUEZ EN TRACK 9 STUDIOS, JOHN BOWEN Y JOHN HASSLER EN SYNC SOUND + +[CRED347] +MARK LLOYD + +[CRED348] +TIM BATES + +[CRED349] +KIT BROWN + +[CRED350] +ANDY MASON + +[CRED351] +PHIL DEANE + +[CRED352] +PHIL ALEXANDER + +[CRED353] +MATT HEWITT + +[CRED354] +DENBY GRACE + +[CRED355] +ANTOINE CABROL + +[CRED356] +JONOTHAN STONES + +[CRED357] +MIKE BLACKBURN + +[CRED358] +TIM MCGAFF + +[CINCAM] +Cámara cinemática + +[RC4] +MASACRES RUMPO + +[LEGAL] +~g~¡Elimina la amenaza criminal! + +[GA_2] +Nuevo motor y trabajo de pintura. ¡Los policías no te reconocerán! + +[HELP15] +Cuando vayas a pie, pulsa ~h~~k~~PED_LOOKBEHIND~~w~ para ~h~mirar detrás~w~. Utiliza el ~h~joystick analógico derecho~w~ para ~h~mirar a tu alrededor~w~. + +[FEC_LB4] +Mirar detrás (botón R3) + +[PERPIC] +Objetos ocultos encontrados + +[CO_ONE] +Objeto oculto ~1~ de ~1~ + +[GA_21] +No puedes almacenar más coches en este garaje. + +[CHEAT1] +Truco activado + +[CHEAT2] +Truco de arma + +[CHEAT3] +Truco de salud + +[CHEAT4] +Truco de blindaje + +[CHEAT5] +Truco de nivel de se busca + +[CHEAT6] +Truco de dinero + +[CHEAT7] +Truco de tiempo + +[USJ_ALL] +¡TODAS LAS MANIOBRAS ÚNICAS COMPLETADAS! + +[JAN] +Ene + +[FEB] +Feb + +[MAR] +Mar + +[APR] +Abr + +[MAY] +May + +[JUN] +Jun + +[JUL] +Jul + +[AUG] +Ago + +[SEP] +Sep + +[OCT] +Oct + +[NOV] +Nov + +[DEC] +Dic + +[DEFDT] +--:---:---- --:--:-- + +[BONUS] +~g~BONIFICACIÓN ~1~ $ + +[HORN1] +Pulsa ~h~~k~~VEHICLE_HORN~~w~para tocar el ~h~claxon. + +[HORN2] +Pulsa ~h~~k~~VEHICLE_HORN~~w~para tocar el ~h~claxon. + +[HORN3] +Pulsa ~h~~k~~VEHICLE_HORN~~w~para tocar el ~h~claxon. + +[FEC_EXV] +Entrar y salir de vehículo + +[TAXI_M] +'TAXISTA' + +[COP_M] +'JUSTICIERO' + +[FIRE_M] +'BOMBERO' + +[AMBUL_M] +'ATS' + +[HJ_IS] +BONIFICACIÓN POR ACROBACIA: ~1~ $ + +[HJ_PIS] +BONIFICACIÓN POR ACROBACIA PERFECTA: ~1~ $ + +[HJ_DIS] +BONIFICACIÓN POR ACROBACIA DOBLE: ~1~ $ + +[HJ_PDIS] +BONIFICACIÓN POR ACROBACIA DOBLE PERFECTA: ~1~ $ + +[HJ_TIS] +BONIFICACIÓN POR ACROBACIA TRIPLE: ~1~ $ + +[HJ_PTIS] +BONIFICACIÓN POR ACROBACIA TRIPLE PERFECTA: ~1~ $ + +[HJ_QIS] +BONIFICACIÓN POR ACROBACIA CUÁDRUPLE: ~1~ $ + +[HJ_PQIS] +BONIFICACIÓN POR ACROBACIA CUÁDRUPLE PERFECTA: ~1~ $ + +[FESZ_LS] +Carga completada con éxito. + +[HELI_1A] +Prueba tus habilidades con el Sparrow, comprueba lo rápido que puedes completar el circuito. + +[HELI_1B] +¡Circuito completado! ~1~ $ + +[HELIODD] +Trabajos esporádicos del helicóptero + +[LAW] +LAS MISIONES DEL ABOGADO + +[LAW1_1] +~g~Consigue unos trapos nuevos en la tienda de ropa de Rafael. + +[LAW4_6] +¡Quemad la dirección! + +[LAW4_7] +¡Matad a los jefes! + +[LAW4_8] +Luchad, luchad, luchad, luchad. + +[LAW4_9] +¡Más vacaciones, menos trabajo! + +[LAW4_11] +¡Luchad! ¡Luchad! ¡Luchad! ¡Luchad! + +[LAW4_12] +¡Viva la revolución! + +[GENERAL] +LAS MISIONES DEL CORONEL + +[GEN3_4] +Tommy Vercetti. Vamos... + +[GEN3_13] +¿Cuál es tu problema, tío? ¡Sube al tejado al otro lado del patio antes de que aparezcan! + +[GEN3_17] +¡Mierda! ¿Intentas matarme? + +[GEN3_21] +~g~¡Tiene el dinero de Díaz! ¡Ve tras él y recupéralo! + +[GEN3_24] +~r~¡Díaz murió! ¡Has fracasado al protegerle! + +[GEN3_26] +~r~¡Le pegaste un tiro a Díaz! + +[GEN3_27] +~r~¡Disparaste a los guardaespaldas de Díaz! + +[GEN3_31] +~g~Ahora ve al punto de entrega y cuida de Díaz. + +[GEN3_32] +~g~Ve a tu posición elevada en el tejado del edificio opuesto a donde está Lance. + +[COKE] +LAS MISIONES DEL BARÓN DE LA NIEVE + +[COK1_3] +¡Espero que te caigas y te rompas el cuello! + +[COK1_6] +Estoy cansado de estos mamones. + +[COK2_7] +¿Veis esos marcadores chicos? ¡Probad a dar a las luces! + +[COK2_10] +Desde luego eres mejor disparando que charlando. + +[COK2_11] +Gracias. Eres un verdadero encanto. + +[COK2_12] +Lo sé, Tommy. + +[COK2_18] +¿Te gusta Kenny Loggins? + +[COK2_19] +Dios, ¡me encanta este disco! + +[COK2_26] +~r~¡Mataste a Lance! + +[COK3_1] +¡No dispares tío! + +[COK3_2] +¿Qué hay ahí? + +[COK3_3] +Se está llevando el barco. Gorrón. + +[COK3_4] +¡Ayuda! ¡Un tipo me está robando el barco, tío! + +[COK4_W] +¡Uugghh! Ése era el último. + +[COK4_X] +Voy a arrancarlo + +[COK4_Y] +Creo que tenemos algunos amigos nuevos. + +[COK4_2] +Sí. + +[COK4_6] +¿A dónde crees que vamos? + +[COK4_7] +¿Nos hemos perdido? + +[COK4_8] +¡Tenemos competencia! + +[COK4_9] +¡Elimínales! + +[COK4_9A] +¡Es hora del baile de Lance Vance! + +[COK4_10] +¡Ya son astillas! Y comida para los peces. + +[COK4_11] +¡Lo conseguimos! Esos otros barcos no son de la clase VIP. + +[COK4_17] +¡Se están desesperando! + +[COK4_18] +¡Mis malditos pies están mojados! ¡NOS ESTÁ ENTRANDO AGUA! + +[COK4_21] +¡Subiendo el puente! + +[COK4_22] +¡Salta, está apunto de estallar! + +[COK4_23] +Buen disparo. + +[COK4_29] +~r~¡Mataste a Lance! + +[ASS1_6] +¡Adelante Tommy, yo estaré bien! + +[ASS1_7] +¡Comeos esto, asesinos hijos de puta! + +[ASS1_8] +¡Me han dado! + +[ASS1_9] +¡Te tengo cubierto, Tommy! + +[ASS1_10] +Eh, este seto es precioso. + +[ASS1_11] +Tommy, ¿puedo tener una habitación con vistas a la bahía? + +[ASS1_12] +Unos techos altos muy bonitos... + +[ASS1_3] +¡Lance! ¡Necesito que me cubras! + +[ASS1_4] +¡Díaz debe de estar dentro! + +[ASS1_5] +¡Lance! + +[TAXWAR] +MISIONES DE LA GUERRA DE TAXIS + +[NOTAXI] +~g~Necesitas un Taxi Kaufman para activar esta misión. + +[TAXW1_5] +~g~¡Necesitas estar en un taxi Kaufman! + +[TAX2_4] +Adelante, Tommy. + +[TAX2_5] +Dale una buena paliza. + +[TAX2_6] +Ni siquiera tiene licencia. + +[TAX2_7] +Malditos servicios de limusina. + +[TAXW3_1] +~g~Ve y recoge a Mercedes. + +[RACE1] +~g~3..2..1.. ¡VAMOS VAMOS VAMOS! + +[RACE2] +~g~3 + +[RACE3] +~g~2 + +[RACE4] +~g~1 + +[RACE5] +~g~¡ADELANTE! + +[FIRST] +~b~1 + +[SECOND] +~b~2 + +[THIRD] +~b~3 + +[FOURTH] +~b~4 + +[RACETM] +~b~TIEMPO DE CARRERA: ~1~:~1~ + +[RACETM2] +~b~TIEMPO DE CARRERA: ~1~:0~1~ + +[RACEFA] +~r~¡Has fracasado en ganar la carrera! + +[HOTRNG] +HOTRING + +[BLODRNG] +BLOODRING + +[DIRTRNG] +DIRTRING + +[TEX1_5] +~r~¡Se escapó! + +[SEG3_1] +TIEMPO: + +[SEG3_2] +~g~Ve a la camioneta que contiene el Helicótero RC y las bombas por control remoto con temporizador. + +[SEG3_3] +~g~Debes emplear el Helicótero RC para transportar 4 bombas hasta 4 objetivos en lugar del edificio. + +[SERG3_5] +~g~Sólo puedes llevar una bomba en cada ocasión y no puedes recoger bombas colocadas con éxito. + +[SEG3_7] +~g~Una vez que hayas soltado con éxito la PRIMERA bomba sobre una zona del objetivo, el cronómetro de detonación se pondrá en marcha; de modo que tendrás que soltar todas las bombas dentro de este período de tiempo. + +[SEG3_8] +~g~Las 4 bombas deben estar colocadas en las 4 zonas objetivo para superar la misión y demoler el edificio. + +[SEG3_9] +~g~¡Alcanzaste el objetivo! Quedan 3. + +[SEG3_10] +~g~¡Alcanzaste el objetivo! Quedan 2. + +[SEG3_11] +~g~¡Alcanzaste el objetivo! ¡Sólo queda 1! + +[SEG3_12] +~r~¡Fallaste el blanco! ¡Ve a por una bomba! + +[SEG3_13] +~g~Deja caer la bomba en una zona del objetivo. + +[SEG3_14] +~r~Te quedaste sin tiempo y fracasaste en demoler el edificio. + +[SEG3_15] +~r~¡Tu Helicótero RC ha sido destruido! ¿Cómo vas a transportar las bombas ahora? + +[AVERY] +MISIONES DE AVERY + +[ASM] +MISIONES DE ASESINO + +[ASM_1] +MISIÓN DE ASESINO 1 + +[ASM1_1] +~g~Sr. Teal, su ayuda en la eliminación de los forasteros fue inestimablemente buena para el negocio. Tengo más trabajo para usted y más próximo a la ''intervención''. Encontrará su siguiente trabajo pegado con cinta adhesiva bajo el teléfono. + +[ASM1_2] +~g~Ve a la cabina telefónica que hay frente al centro comercial de Washington. + +[ASM1_3] +~g~Carl Pearson, un repartidor de pizza. No debe finalizar sus entregas. + +[ASM1_4] +~g~Elimina al repartidor de pizza antes de que complete sus entregas. + +[ASM_2] +MISIÓN DE ASESINO 2 + +[ASM_3] +MISIÓN DE ASESINO 3 + +[ASM3_1] +~g~Ve a coger el arma que el Sr. Black ha dejado para ti. + +[ASM3_2] +~g~No te acerques demasiado al objetivo o podría verte. + +[ASM3_3] +~g~Para un trabajo más rápido, colócate en lugares cercanos a su ubicación y evita ser visto. Esto te proporcionará una buena posición para eliminarles. + +[ASM3_4] +~g~¡Te ha visto! ¡Mejor elimínale de cualquier modo que puedas! + +[ASM3_5] +~g~Marcus Hammond está situado cerca de los anuncios de Washington. + +[ASM3_6] +~g~Franco Carter está situado en el DBP de Seguridad, cerca de Ocean Drive. + +[ASM3_7] +~g~Dick Tanner está cerca de la joyería de Vice Point. + +[ASM3_8] +~g~Nick Kong está colocado cerca de Washington Beach. + +[ASM3_9] +~g~El conductor está situado en Washington. + +[ASM3_10] +~r~Fallaste en matarles a todos. + +[ASM_4] +MISIÓN DE ASESINO 4 + +[ASM4_1] +~g~Ve a por el rifle que te han dejado entre el follaje que hay en las cercanías de la terminal del aeropuerto. + +[ASM4_2] +~g~No falles tu blanco o podrías alertar a sus guardaespaldas, y recuerda mantener las distancias para que no te detecte. + +[ASM4_3] +~g~Vigila a la mujer de la galería que hay sobre los mostradores de facturación, dentro de la terminal del aeropuerto. PERO NO LA MATES. + +[ASM4_4] +~g~Mata al hombre al que ella dé el maletín, pero sólo DESPUÉS DE QUE ÉSTE LO RECOJA. Entonces recupera el maletín y llévalo a Ammu-Nation en el centro de la ciudad. + +[ASM4_5] +~g~¡Consigue el maletín! + +[ASM4_6] +~g~Lleva al maletín a Ammu-Nation en el centro de la ciudad. + +[ASM4_7] +~r~¡Estúpido, has matado a la mujer! + +[ASM4_8] +~r~¡El blanco te oyó disparar el arma! ¡El trato queda anulado! + +[ASM4_9] +~r~¡El blanco ha embarcado en su vuelo! + +[ASM4_11] +~r~¡El blanco te ha visto! ¡El trato queda anulado! + +[ASM4_13] +~g~Te ha visto y está huyendo, ¡atrápale y consigue el maletín! + +[ASM4_14] +~g~La barra de distancia en la parte superior derecha de la pantalla te proporciona una indicación de lo cerca que estás de tu blanco. No permitas que se llene o éste te verá. + +[ASM_5] +MISIÓN DE ASESINO 5 + +[KICK] +ARRANQUE + +[KICK1_3] +~g~Número de veces que pusiste el pie: ~1~ + +[KICK1_4] +~g~Tiempo de penalización: ~1~ segundos + +[BANK] +MISIONES DEL ATRACO AL BANCO + +[BANK1] +MISIÓN DE ATRACO AL BANCO 1 + +[BANK2] +MISIÓN DE ATRACO AL BANCO 2 + +[BJM2_21] +~g~Acierta a tantos blancos como puedas mientras te quede munición. + +[BANK3] +MISIÓN DE ATRACO AL BANCO 3 + +[BJM3_1] +~g~Consigue un coche rápido y ve a la parrilla de salida. + +[BNK4_2A] +Los chicos hicieron un trabajo genial con este pequeño. + +[BNK4_3G] +¡Oh, mierda, ahora los polis andan tras nosotros! + +[BNK4_3H] +Y ni siquiera hemos llegado. + +[BNK4_3K] +Primero tendremos que despistar a los polis... + +[BNK4_3L] +Joder, Tommy, ¿intentas matarnos a todos? + +[BNK4_3N] +¡Todo lo que me importa acaba destrozado! + +[BNK4_26] +¡Maldición! ¡Aquí vienen! + +[BNK4_32] +¡Usa los explosivos para abrir las cajas de depósito! + +[BNK4_36] +¿Dónde está Cam? + +[BNK4_37] +Es historia... + +[BNK4_38] +Ése era el último. ¡Vamos! ¡Vamos! ¡Vamos! + +[BNK_39] +¡Mierda! ¿Dónde está Hilary? + +[BK4_40A] +¡Ya le daré yo miedo al abandono! + +[BNK4_42] +¡Eh, tíos! ¡Subid! ¡Yo os cubro! + +[BNK4_43] +¡Ya cubro yo nuestros culos, tú conduce! + +[BNK4_44] +¡Lo conseguimos! ¡Somos ricos! ¡Ricos! + +[BNK4_45] +Es una pena que Cam no lo consiguiese, ¡era un buen tipo! + +[BNK4_46] +Sí. Pero aún así... ¡más tajada para nosotros! + +[BNK4_47] +¡Claro que sí! ¡Sí! + +[BNK4_48] +Tommy, ¿te gustaría un masaje? + +[BNK4_49] +¡Hola, Mercedes! Sí, estoy un poco tenso... + +[BNK450A] +¿Qué te dije, Tommy? ¿Qué te dije? Maldito SWAT, será mejor que tengas cuidado cuando Kent Paul esté en la ciudad. + +[BNK450B] +Vamos, dame una tajada más grande, colega, venga. Tengo que comprarme ropa nueva. + +[BNK4_51] +Yo creo que tienes buen aspecto. + +[KENT] +MISIONES DE KENT PAUL + +[KENT1] +MISIÓN DE KENT PAUL 1 + +[COUNT] +MISIONES DE FALSIFICACIÓN + +[COUNT1] +MISIÓN DE FALSIFICACIÓN 1 + +[COUNT2] +MISIÓN DE FALSIFICACIÓN 2 + +[BIKE] +LAS MISIONES DE LA PANDILLA DE MOTEROS + +[BIKE1] +MISIÓN DE MOTORISTA 1 + +[BIKE2] +MISIÓN DE MOTORISTA 2 + +[BIKE3] +MISIÓN DE MOTORISTA 3 + +[GOAWAY1] +Vuelve cuando hayas terminado con las misiones de la banda haitiana. + +[HAIT] +LAS MISIONES DE LA BANDA HAITIANA + +[HAIT1] +MISIÓN HAITIANA 1 + +[HAIT2] +MISIÓN HAITIANA 2 + +[HAIT3] +MISIÓN HAITIANA 3 + +[HAM3_6] +~g~Utiliza el rifle de francotirador que te he dejado para cumplir tu tarea. + +[ROCK] +LAS MISIONES DE LA BANDA DE ROCK + +[ROK1_4] +~g~De acuerdo, creo que esto es lo que andabais buscando... + +[ROK1_1E] +~g~¡Costará más de lo que tienes! + +[ROK1_1F] +~g~Vuelve cuando tengas el dinero. + +[RBM2_6] +~g~¡Guau! ¡Ella es un tío, detenle! + +[ROCK3] +MISIONES DE LA BANDA DE ROCK 3 + +[RBM3_5] +~g~Lleva a los Love Fist al escenario. + +[CUBANM] +LAS MISIONES DE LA BANDA CUBANA + +[CUBAN1] +MISIÓN CUBANA 1 + +[CUBAN2] +MISIÓN CUBANA 2 + +[CUB2_10] +~r~¡Se supone que debes matar a haitianos, no a cubanos! + +[CUBAN3] +MISIÓN CUBANA 3 + +[CUBAN4] +MISIÓN CUBANA 4 + +[PROT] +MISIONES DE PROTECCIÓN + +[PORN] +MISIONES PORNO + +[PORN1] +MISIÓN PORNO 1 + +[POR1_03] +~r~¡Candy está muerta! + +[PORN2] +MISIÓN PORNO 2 + +[PORN3] +MISIÓN PORNO 3 + +[PORN4] +MISIÓN PORNO 4 + +[PHIL] +MISIONES DE PHIL + +[PHIL1] +MISIÓN DE PHIL 1 + +[PHIL2] +MISIÓN DE PHIL 2 + +[PIZ1_A] +MISIÓN DEL REPARTIDOR DE PIZZAS + +[CNTBUY1] +Compra de la imprenta: ~1~ $ + +[CARBUY] +Compra del salón de coches: ~1~ $ + +[PORNBUY] +Compra del estudio cinematográfico: ~1~ $ + +[ICEBUY] +Compra de la fábrica de helados: ~1~ $ + +[TAXIBUY] +Compra de la empresa de taxis: ~1~ $ + +[BANKBUY] +Compra del Malibú: ~1~ $ + +[BOATBUY] +Compra del astillero: ~1~ $ + +[PRNT_NO] +No puedes comprar la imprenta en este momento, vuelve más tarde. + +[CAR_NO] +No puedes comprar el salón de coches en este momento, vuelve más tarde. + +[PORN_NO] +No puedes comprar el estudio cinematográfico en este momento, vuelve más tarde. + +[ICE_NO] +No puedes comprar la fábrica de helados en este momento, vuelve más tarde. + +[TAXI_NO] +No puedes comprar la compañía de taxis en este momento, vuelve más tarde. + +[BANK_NO] +No puedes comprar el Malibú en este momento, vuelve más tarde. + +[BOAT_NO] +No puedes comprar el astillero en este momento, vuelve más tarde. + +[COL2_6] +¡Alto, cerdo imperialista americano! + +[COL2_6B] +Eso es propiedad del gobierno francés. + +[COL2_6C] +¡y se acabó! + +[COL3_A] +Thomas, aprecio que vinieses. + +[COL3_B] +Perdóname por ir directamente al asunto. + +[COL3_C] +Diaz me ha pedido que supervise una transacción de un negocio menor. + +[COL3_D] +Esperemos que vaya mejor que la última vez. + +[COL3_E] +Por eso es por lo que pensé en ti, amigo mío. + +[COL3_F] +He dejado algo de protección en el aparcamiento. + +[COL3_G] +Recógele y luego id a ver a los hombres de Diaz en el punto de entrega. + +[COL4_2] +¡No lo sé, señor! + +[COL4_5] +¡Señor, sí, señor! + +[COL4_10] +Vamos a comer unos donuts. + +[COL4_16] +¡Señor! ¡Moviendo el vehículo, señor! + +[COL4_25] +¡Iniciada autodestrucción del vehículo! + +[COL5_6] +Mercedes, esa chica me matará. + +[COL5_8] +¡Maldita cucaracha! + +[COL5_5] +¡Morid, cerdos franceses! + +[CNT2_1] +Matadle. + +[CNT2_2] +¡Conseguid las planchas! + +[CNT2_3] +¡Proteged al mensajero! + +[FINKILL] +¡De acuerdo, chicos, matadle! + +[FIN_1A] +¡Ven aquí, traidor, pedazo de mierda! + +[FIN_1B] +¡Vas a caer, mamón traicionero! + +[FIN_1C] +¡Éste es el último baile para Lance Vance! + +[FIN_2B] +¡Oh, eso crees tú! + +[FIN_2C] +¡Ya tuve suficiente de eso en la escuela! + +[FIN_3] +Ahora no hay nadie para cubrirte el trasero, ¿eh, Tommy? + +[FIN_4] +Eres historia, Tommy, historia. + +[FIN_5] +Escogiste el lado equivocado, Lance... + +[FIN_6] +Sonny está arriba con la caja fuerte y mi dinero... + +[FIN_10] +¿Sonny? ¡Sonny! ¡Voy por ti! + +[FIN_11A] +Me arrebataste quince años, Sonny... + +[FIN_11B] +¡Y ahora voy a hacértelo pagar! + +[FIN_12A] +Todavía no lo captas, ¿verdad? + +[FIN_12B] +Eres de mi propiedad, Tommy. + +[FIN_12C] +¡Esos quince años eran míos para gastarlos! + +[FIN_13] +Atrapadle chicos, nunca comprendió nada. + +[RACES_4] +3 + +[RACES_5] +2 + +[RACES_6] +1 + +[RACES_7] +¡ADELANTE! + +[RACES_9] +Tiempo: ~1~:~1~ + +[RACES] +TIEMPO: + +[RACES17] +Nuevo mejor tiempo: ~1~:~1~ + +[RACES18] +HAS GANADO: ~1~ $ + +[RACES20] +Nuevo mejor tiempo: ~1~:0~1~ + +[RACES21] +Tiempo: ~1~:0~1~ + + +[RCH1_2] +~g~Los CONTROLES están esparcidos a lo largo del aeropuerto. + +[RCH1_5] +Tiempo: + +[RCRC1_2] +~g~¡Ve a la parrilla de salida! + +[RCRC1_4] +~g~3 + +[RCRC1_5] +~g~2 + +[RCRC1_6] +~g~1 + +[RCRC1_7] +~g~¡ADELANTE! + +[RCRC1_8] +~g~Tiempo de carrera: ~1~ segundos + +[RCPL1_1] +~g~Compite en una CARRERA DE PUNTOS DE CONTROL con otros tres Barón RC. + +[RCPL1_2] +~g~Debes atravesar la ~o~CORONA DEL CENTRO ~g~para pasar con éxito un punto de control. + +[RCPL1_3] +~g~¡Ve ahora a la parrilla de salida! + +[ICC1_O] +¿Qué pasa contigo? + +[FEA_2SP] +2 altavoces + +[FEA_4SP] +Más de 2 altavoces + +[FEA_EAR] +Auriculares + +[FEA_NAH] +NO HAY HARDWARE DE AUDIO + +[FET_APP] +APLICAR + +[FES_SKN] +NOMBRE DE APARIENCIA + +[FES_DAT] +FECHA + +[FES_SET] +Utilizar apariencia + +[FET_DEF] +Restaurar valores por defecto + +[FESZ_QZ] +¿Seguro de que quieres guardar esta partida? + +[FES_SCG] +¿Guardar la partida actual? + +[FES_LCG] +¿Cargar la partida y continuar 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 y 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_LOL] +Mirar hacia la izquierda + +[FEC_LOR] +Mirar hacia la derecha + +[FEC_NTR] +Siguiente objetivo + +[FEC_PTT] +Objetivo anterior + +[FEC_LBA] +Mirar detrás + +[FEC_CEN] +Centrar cámara + +[FET_CFT] +A PIE + +[FET_CCR] +EN COCHE + +[FET_CAC] +ACCIÓN + +[FEC_IBT] +- + +[FEC_MXO] +MXB1 + +[FEC_MXT] +MXB2 + +[FEC_UNB] +ILIMITADO + +[FEC_TFL] +Mirar a izquierd+Torreta L + +[FEC_TFR] +Mirar a derecha+Torreta R + +[FEC_MWF] +RUEDA DEL RATÓN ARRIBA + +[FEC_MWB] +RUEDA DEL RATÓN ABAJO + +[FEC_ORR] +o + +[FEC_NUS] +NO UTILIZADO + +[FEC_LUD] +Mirar arriba + +[FEC_LDU] +Mirar abajo + +[FEC_CMP] +COMBO: MIRAR I+d + +[LAW_1A] +law_1a + +[LAW_1B] +law_1b + +[LAW_2A] +law_2a + +[LAW_2B] +law_2b + +[FEH_STA] +ESTADÍSTICAS + +[FEH_LOA] +CARGAR + +[FEH_CON] +CONTROLES + +[FEH_AUD] +SONIDO + +[FEH_DIS] +PANTALLA + +[FEH_LAN] +IDIOMA + +[FEH_SGA] +INICIAR NUEVA PARTIDA + +[FEO_CON] +Configuración del mando + +[FEO_AUD] +Configuración de sonido + +[FEO_DIS] +Configuración de pantalla + +[FEO_LAN] +Configuración de idioma + +[FEO_PLA] +Configuración del jugador + +[FET_PS] +CONFIGURACIÓN DE APARIENCIA + +[FEA_OUT] +Salida + +[FEA_ST] +Estéreo + +[FEA_DTS] +DTS + +[FEA_RSS] +Emisora de radio + +[FEA_NON] +RADIO APAGADA + +[FEA_FM0] +WILDSTYLE + +[FEA_FM1] +FLASH FM + +[FEA_FM2] +KCHAT + +[FEA_FM3] +FEVER 105 + +[FEA_FM4] +VROCK + +[FEA_FM5] +VCPR + +[FEA_FM7] +EMOTION 98.3 + +[FEA_FM8] +WAVE 103 + +[FED_BRI] +Brillo + +[FED_TRA] +Estelas + +[FED_SUB] +Subtítulos + +[FED_WIS] +Formato 16:9 + +[FED_POS] +Posición de la pantalla + +[FED_RDR] +RADAR + +[FED_HUD] +MODO HUD + +[FED_RDB] +SOLO ICONOS + +[FE_MLG] +LEYENDA DEL MAPA + +[FEI_SCR] +Desplazarse + +[FEP_RES] +Continuar + +[FEP_STG] +Iniciar partida + +[FEP_STA] +Estadísticas + +[FEP_BRI] +Resumen + +[FEP_OPT] +Opciones + +[FEP_QUI] +Salir del juego + +[FES_LOA] +Cargar partida + +[FES_DEL] +Borrar partida + +[FEC_CSU] +Configuración del controlador + +[FEC_RED] +Redefinir controles + +[FEC_MOU] +Configuración del ratón + +[DISTGOL] +Distancia recorrida en carro de golf (millas) + +[DISTGOM] +Distancia recorrida en carro de golf (m) + +[ST_FAVR] +Estación de radio favorita + +[ST_WSTR] +estación de radio que menos te gusta + +[ST_FAVV] +Vehículo favorito + +[ST_STAR] +Número total de estrellas se busca conseguidas + +[ST_HEAD] +Número de disparos a la cabeza + +[ST_GANG] +Banda que menos te gusta + +[ST_STGN] +Número total de estrellas se busca evadidas + +[TYREPOP] +Neumáticos reventados por disparos + +[TYRESLA] +Neumáticos rajados con una navaja + +[ST_BRK] +Número de muertes en el anillo sangriento + +[ST_LTBR] +Mayor tiempo en el anillo sangriento (segs.) + +[ST_GNG1] +Cubanos + +[ST_GNG2] +Haitianos + +[ST_GNG3] +Aspirantes callejeros + +[ST_GNG4] +Pandilleros de Díaz + +[ST_GNG5] +Guardias de seguridad + +[ST_GNG6] +Banda de motoristas + +[ST_GNG7] +Banda de Vercetti + +[ST_GNG8] +Golfistas + +[FEA_FM6] +ESPANTOSO + +[ST_ASSI] +Contratos de asesinato completados + +[DISTBIK] +Dist. Recorrida en moto (millas) + +[DISTBIM] +Distancia recorrida en moto (m) + +[HOTEL] +Ocean View + +[KICK1_9] +PUNTOS DE CONTROL: + +[FIN_B6] +No tienes suficiente dinero para empezar esta misión. + +[TEX3_9] +~g~Recoge una bomba pasando con el helicóptero RC cerca de ella. + +[HELP22] +Ve a la señal de la casa verde que hay en el radar. + +[FES_FMS] +Éxito al formatear. Selecciona Aceptar para continuar. + +[FES_SSC] +Éxito al guardar. Selecciona Aceptar para continuar. + +[FES_DSC] +Éxito al borrar. Selecciona Aceptar para continuar. + +[FESZ_QC] +¿Deseas sobreescribir esta partida guardada dañada? + +[FES_CHE] +¡Atención! Se han activado uno o más trucos, esto puede afectar a tus partidas guardadas. Te recomendamos no guardar esta partida. + +[FET_SG] +GUARDAR PARTIDA + +[FEH_BRI] +INFORME + +[FEH_MAP] +MAPA + +[FEM_OK] +Aceptar + +[FEC_CRO] +Agacharse + +[FEC_CR3] +Agacharse (Botón L3) + +[FEC_SMT] +Sub-misión + +[FEC_SM3] +Sub-misión (Botón R3) + +[FEC_RSC] +Emisoras de radio + +[ST_PR01] +Aviador + +[ST_PR02] +Piloto del ejército + +[ST_PR03] +Oficial piloto + +[ST_PR04] +Cabo + +[ST_PR05] +Teniente + +[ST_PR06] +Sargento + +[ST_PR07] +Capitán + +[ST_PR08] +Biggs + +[ST_PR09] +Wedge + +[ST_PR10] +Barón Rojo + +[ST_PR11] +Ganso + +[ST_PR12] +Víbora + +[ST_PR13] +Jester + +[ST_PR14] +Chappy + +[ST_PR15] +Iceman + +[ST_PR16] +Maverick + +[ST_PR17] +Negado + +[ST_PR18] +General de la Armada aérea + +[ST_PR19] +As + +[FET_LG] +CARGAR JUEGO + +[CAR_EXP] +Vehículos destruidos + +[BOA_EXP] +Barcos destruidos + +[HEL_DST] +Aviones y helicópteros destruidos + +[STFT_01] +Tiempo más rápido de ''Moto con llantas de aleación'' + +[STFT_02] +Tiempo más rápido de ''El conductor'' + +[STFT_03] +Tiempo más rápido en el Circuito de Tierra + +[STFT_04] +Tiempo más rápido en la Carrera de Aviones RC + +[STFT_05] +Tiempo más rápido en la Carrera de Coches RC + +[STFT_06] +Tiempo más rápido Control del Helicóptero RC + +[STFT_07] +Tiempo más rápido en ''Velocidad terminal'' + +[STFT_08] +Tiempo más rápido en ''Ocean Drive'' + +[STFT_09] +Tiempo más rápido en ''Carrera por el borde'' + +[STFT_10] +Tiempo más rápido en ''Capital Cruise'' + +[STFT_11] +Tiempo más rápido en ''¡Tour!'' + +[STFT_12] +Tiempo más rápido en ''Aguante en V.C.'' + +[STHC_01] +Puntuación más alta en el campo de tiro + +[STHC_02] +Mejor porcentaje de impactos en el campo de tiro + +[STHC_03] +Ventas de droga realizadas + +[HELP24] +Ahora puedes aceptar trabajos del Coronel. + +[HELP25] +Ahora puedes aceptar trabajos de Avery Carrington. + +[HELP29] +Puedes visitar la tienda de ropa cuando no estés en una misión. + +[HELP30] +Cuando compres ropa nueva, tu nivel de se busca se establecerá en cero. + +[ASM4_24] +distancia: + +[RBM1_6] +~g~Lleva a Mercedes y el ''love juice'' a la banda en el estudio de grabación. + +[HELP31] +Para hacer una pasada, mira primero a izquierda o derecha con ~k~~VEHICLE_LOOKLEFT~ o ~k~~VEHICLE_LOOKRIGHT~. + +[HELP34] +Para realizar una pasada deberás tener un subfusil. + +[STRIP_1] +~r~No tienes dinero suficiente, tacaño miserable. + +[EXIT_1] +Pulsa ~k~~PED_SPRINT~ para salir. + +[ASM1_B] +Encontrará su siguiente trabajo pegado con cinta adhesiva bajo el teléfono. + +[ASM1_C] +Tengo más trabajo para usted que requerirá tomar ''el volante'' de la situación. + +[SCARF] +Apartamento 3c + +[LAW4_10] +¡Mamones de directores! + +[RCH1_6] +~g~Utiliza el helicóptero RC para pasar por los puntos de control dispersados por el aeropuerto. + +[RCH1_9] +~b~TIEMPO TOTAL: ~1~:~1~ + +[RCH1_10] +~b~TIEMPO TOTAL: ~1~:0~1~ + +[WHEEL01] +DOBLE BONIFICACIÓN EN DOS RUEDAS: ~1~ $ Distancia: ~1~,~1~m Tiempo: ~1~ segundos + +[WHEEL02] +DOBLE BONIFICACIÓN EN DOS RUEDAS: ~1~ $ Distancia: ~1~ pies Tiempo: ~1~ segundos + +[WHEEL03] +BONIFICACIÓN EN DOS RUEDAS: ~1~ $ Tiempo: ~1~ segundos + +[WHEEL04] +BONIFICACIÓN EN DOS RUEDAS: ~1~ $ Distancia: ~1~,~1~m + +[WHEEL05] +BONIFICACIÓN EN DOS RUEDAS: ~1~ $ Distancia: ~1~ pies + +[WHEEL06] +BONIFICACIÓN EN CABALLITO: ~1~ $ Distancia: ~1~,~1~m Tiempo: ~1~ segundos + +[WHEEL07] +BONIFICACIÓN EN CABALLITO: ~1~ $ Distancia: ~1~ pies Tiempo: ~1~ segundos + +[WHEEL08] +BONIFICACIÓN EN CABALLITO: ~1~ $ Tiempo: ~1~ segundos + +[WHEEL09] +BONIFICACIÓN EN CABALLITO: ~1~ $ Distancia: ~1~,~1~m + +[WHEEL10] +BONIFICACIÓN EN CABALLITO: ~1~ $ Distancia: ~1~ pies + +[WHEEL11] +BONIFICACIÓN EN PARADA MÁS LARGA: ~1~ $ Distancia: ~1~,~1~m Tiempo: ~1~ segundos + +[WHEEL12] +BONIFICACIÓN EN PARADA MÁS LARGA: ~1~ $ Distancia: ~1~ pies Tiempo: ~1~ segundos + +[WHEEL13] +BONIFICACIÓN EN PARADA MÁS LARGA: ~1~ $ Tiempo: ~1~ segundos + +[WHEEL14] +BONIFICACIÓN EN PARADA MÁS LARGA: ~1~ $ Distancia: ~1~,~1~m + +[WHEEL15] +BONIFICACIÓN EN PARADA MÁS LARGA: ~1~ $ Distancia: ~1~ pies + +[ROK3_72] +¡Love Fist! + +[POR1_19] +¡Eh! + +[DESPERA] +Desesperado + +[MOB_99A] +Ve hasta el teléfono público junto al centro comercial en Washington. + +[MOB_98A] +Ve hasta el teléfono público en Vice Point. + +[MOB_96A] +Ve hasta el teléfono público en la terminal del aeropuerto. + +[MOB_95A] +Ve hasta el teléfono público en Little Havana. + +[BNK1_1] +¿Le puedo ayudar, señor? + +[BNK1_2] +¡Hay un impostor! + +[BNK1_3] +¡Se ha vuelto loco! + +[BNK1_4] +¿Quién demonios eres? + +[BNK1_5] +¿Dónde está tu placa? + +[BNK1_6] +¡Ahí están! ¡Dispara a matar! + +[MOB_24A] +Hola, ¿Sr. Vercetti? + +[MOB_24B] +Sí. + +[MOB_24C] +Soy Cortez. Usted estuvo en mi fiesta. + +[MOB_24D] +Sí, lo recuerdo. + +[MOB_24E] +Sr. Vercetti, ha sido un desgraciado incidente lo que ocurrió con su operación de negocios. + +[MOB_24F] +Lo sé. + +[MOB_24G] +Quiero que sepa que tanto yo como mi gente estamos haciendo todo lo que podemos para llegar al fondo de este tema. + +[MOB_24H] +Si desea hablar conmigo de manera más privada, me podrá encontrar en el barco. Buenos días, señor. + +[BNK2_6] +¡Este tío está chalado! + +[ANGEL] +Ángel + +[CUBJET] +Jetmax Cubano + +[SANDKIN] +Sandking + +[POLMAV] +Maverick de la policía + +[BOXVILL] +Boxville + +[BENSON] +Benson + +[HOTRINA] +Corredor de Hotring + +[HOTRINB] +Corredor de Hotring + +[BLOODRA] +Coche de Bloodring + +[BLOODRB] +Coche de Bloodring + +[MAFIACR] +Yate de la Mafia + +[COP_M2] +ANTI VICIO + +[COP_M3] +TRUENO MARRÓN + +[BNK3_2] +No voy a conducir por ti, ni soñando, voy a compartir esto con el grupo. + +[FEM_SL1] +No hay archivo 1 guardado + +[FEM_SL2] +No hay archivo 2 guardado + +[FEM_SL3] +No hay archivo 3 guardado + +[FEM_SL4] +No hay archivo 4 guardado + +[FEM_SL5] +No hay archivo 5 guardado + +[FEM_SL6] +No hay archivo 6 guardado + +[FEM_SL7] +No hay archivo 7 guardado + +[FEM_SL8] +No hay archivo 8 guardado + +[FEA_CHA] +Cambiando la salida de audio a ESTÉREO. Espera... + +[FEA_CHD] +¡Aviso! Estás cambiando la salida de ESTÉREO a DTS. Espera... + +[FEI_SEL] +Selec. + +[FEI_BAC] +Atrás + +[FEI_RES] +Reanudar + +[FEI_NAV] +Navegar + +[FEI_BTX] +botón / - + +[FEI_BTT] +botón " - + +[FEI_STA] +Botón START - + +[FEI_BTD] +; = > < - + +[FEI_STO] +Parar + +[MOB_68A] +Tommy, tío, tengo una sorpresa para ti. + +[MOB_68B] +Me encuentro en el estudio de grabación con unos artistas importantes. + +[MOB_68C] +¿Por qué no te vienes por aquí? + +[MOB_68D] +Sabes que esto tiene sentido, ¿a que sí? Hasta dentro de un rato. + +[OUTFT1] +Calle + +[OUTFT2] +Etiqueta + +[OUTFT3] +Mono de trabajo + +[OUTFT4] +Club de campo + +[OUTFT5] +Habana + +[OUTFT6] +Policía + +[OUTFT7] +Atraco al banco + +[OUTFT8] +Informal + +[OUTFT9] +Sr. Vercetti + +[OUTFT10] +Chándal + +[OUTFT13] +MC Tommy + +[CAR_AS1] +CONCESIONARIO DE COCHES CONSOLIDADO + +[CAR_AS2] +~g~El Concesionario de coches Sunshine generará ahora unos ingresos de hasta ~1~ $ máximo. Asegúrate de recaudarlos regularmente. + +[BUYSAVE] +~g~A partir de ahora, podrás guardar tu partida aquí cuando no estés en una misión. + +[BUYGARG] +~g~También puedes almacenar vehículos en este garaje. + +[STRPBUY] +Adquirido el club Pole Position: ~1~ $ + +[GA_4] +Las bombas de coche son 500 $ cada una. + +[GA_5] +Tu coche ya está equipado con una bomba. + +[GA_6] { reVC update } +¡Apárcalo, actívala pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~ y SAL PITANDO! + +[GA_7] { reVC update } +¡Ármalo pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~. La bomba explotará cuando se arranque el motor. + +[GA_6B] { reVC update } +¡Apárcalo, actívala pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~ y SAL PITANDO! + +[GA_7B] { reVC update } +¡Ármalo pulsando ~h~~k~~VEHICLE_FIREWEAPON~~w~. La bomba explotará cuando se arranque el motor. + +[MOB_70A] +Tommy, soy yo, el coronel Cortez. Mira, me parece que eres la clase de hombre que lleva a cabo los trabajos. Así que ayúdame, por favor. + +[MOB_70B] +Me podrás encontrar en el barco. + +[PICK1] +¡El chaleco antibalas ha sido entregado en el hotel Ocean View! + +[PICK2] +¡La .357 ha sido entregada en el hotel Ocean View! + +[PICK3] +¡La sierra mecánica ha sido entregada en el hotel Ocean View! + +[PICK4] +¡El lanzallamas ha sido entregado en el hotel Ocean View! + +[PICK5] +¡El rifle de francotirador .308 ha sido entregado en el hotel Ocean View! + +[PICK6] +¡La Ametralladora Pesada ha sido entregada en el hotel Ocean View! + +[PICK7] +¡El lanzacohetes ha sido entregado en el hotel Ocean View! + +[PICK8] +¡El Sea Sparrow está ahora disponible en la mansión de Starfish Island! + +[PICK9] +¡El tanque está ahora disponible en los cuarteles del ejército! + +[PICK10] +¡El Hunter está ahora disponible en los cuarteles del ejército! + +[CLOTH1] +El traje de etiqueta ha sido entregado en Rafaels en Ocean Beach. + +[CLOTH2] +El traje de calle ha sido entregado en los pisos francos. + +[CLOTH3] +El mono de trabajo ha sido entregado en Tooled Up en el centro comercial de North Point. + +[CLOTH4] +El traje del club de campo ha sido entregado en el club de golf de Leaf Links. + +[CLOTH5] +El traje Havana ha sido entregado en la tienda de ropa de Little Havana. + +[CLOTH6] +El traje de policía ha sido entregado en la jefatura de policía de Washington Beach. + +[CLOTH7] +El traje informal ha sido entregado en Gash del centro comercial de North Point. + +[CLOTH8] +El traje del Sr. Vercetti ha sido entregado en Collar & Cuffs de Ocean Beach. + +[CLOTH9] +El chándal ha sido entregado en Jocksport del centro de la ciudad. + +[CLOTH10] +El traje para el atraco al banco ha sido entregado en el Club Malibu de Vice Point. + +[MOB_62A] +Tommy, soy Ricardo Díaz, quiero darte las gracias por cuidar de mí, tío. + +[MOB_62B] +He preguntado al gilipollas de Cortez, y me ha dicho que eres bueno, amigo, ¿por qué no vienes a verme? + +[MOB_62C] +Necesito un tío como tú. Todos los que tengo son gilipollas perdidos, + +[MOB_62D] +gilipollas por todas partes, tío. Yo te puedo hacer muy rico. + +[GOAWAY2] +~g~Regresa cuando hayas terminado las misiones de la banda de moteros. + +[COL2_9] +¡Estúpido americano! ¡Te han seguido hasta aquí! + +[LOADCOL] +Cargando... + +[STFT_17] +Tiempo más rápido en la ''Prueba PCJ'' + +[STFT_18] +Tiempo más rápido en el ''Trial de tierra'' + +[STFT_19] +Tiempo más rápido en la ''Pista de Pruebas'' + +[NEW_REC] +¡Nuevo récord establecido! ~w~~1~ minutos ~g~y ~w~~1~ segundos. + +[BMX_HOW] +~g~¡Da dos vueltas a la pista de tierra, ~y~pasando por ~g~los ~y~PUNTOS DE CONTROL ~g~por el camino! + +[BMXREW1] +~g~¡Cada vez que batas tu récord anterior de las dos vueltas + +[BMXREW2] +~g~conseguirás una ~y~RECOMPENSA ~g~mejor! + +[BMXRAIN] +~g~Parece lluvia... + +[ITBEG] +Al principio... + +[NBMNBUY] +Casa Swanko adquirida: ~1~ $ + +[LNKVBUY] +Apartamento en Links View adquirido: ~1~ $ + +[HYCOBUY] +Piso de Hyman adquirido: ~1~ $ + +[BUYGARS] +~g~Puedes guardar vehículos en estos garajes. + +[OCHEBUY] +Apartamento de Ocean Heights adquirido: ~1~ $ + +[WASHBUY] +1102 de la calle Washington adquirido: ~1~ $ + +[VCPTBUY] +3321 de Vice Point adquirido: ~1~ $ + +[SKUMBUY] +Chabola Skumole adquirida: ~1~ $ + +[HELP6_C] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~~w~ para usar el freno de mano del vehículo. + +[HELP2_A] +Pulsa ~h~~k~~PED_SPRINT~~w~~w~ cuando estés corriendo para ~h~esprintar. + +[HELP4_A] +Pulsa ~h~~k~~VEHICLE_ACCELERATE~~w~ para acelerar. + +[HELP5_A] +Pulsa ~h~~k~~VEHICLE_BRAKE~~w~ para frenar o para dar marcha atrás si el vehículo se ha detenido. + +[HELP8_A] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ para hacer zoom con el rifle y ~h~~k~~PED_SNIPER_ZOOM_OUT~~w~ para alejar la vista. + +[PBOAT_1] { reVC update } +Pulsa ~h~ ~k~~VEHICLE_FIREWEAPON~~w~ para disparar los cañones del barco. + +[SEG3_4] { reVC update } +~g~Puedes recoger bombas simplemente pilotando tu Helicóptero RC cerca de cada una de ellas, para soltar una bomba pulsa ~h~~k~~VEHICLE_FIREWEAPON~~g~. + +[RCR1_3] { reVC update } +~g~Si quieres abandonar esta misión, pulsa ~h~~k~~VEHICLE_FIREWEAPON~~g~ para detonar tu coche RC. + +[HELP32] { reVC update } +A continuación dispara pulsando ~h~~k~~VEHICLE_FIREWEAPON~. + +[HELP33] { reVC update } +A continuación dispara pulsando ~h~~k~~VEHICLE_FIREWEAPON~. + +[TTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para comenzar o cancelar las misiones de taxista. + +[TTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para comenzar o cancelar las misiones de taxista. + +[FTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para comenzar o cancelar las misiones del camión de bomberos. + +[FTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para comenzar o cancelar las misiones del camión de bomberos. + +[CTUTOR] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para comenzar o cancelar las misiones de justiciero. + +[CTUTOR2] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para comenzar o cancelar las misiones de justiciero. + +[HELP8_B] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~ ~w~para ~h~acercar la vista ~w~con el rifle y ~h~~k~~PED_SNIPER_ZOOM_OUT~ ~w~para ~h~alejar la vista ~w~otra vez. + +[ATUTOR3] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ para comenzar o cancelar las misiones de ATS. + +[GUN_H1] +~w~Pulsa ~h~~k~~PED_SPRINT~~w~ para comprar. ~w~Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para salir. + +[PU_CF3] { reVC update } +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para reemplazar tu arma actual en esta ranura. + +[PU_CF4] { reVC update } +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para reemplazar tu arma actual en esta ranura. + +[HELP9_B] +Pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar~w~ el rifle de francotirador. + +[HELP37] +Si no quieres subir al coche mientras se lo estás robando a alguien, pulsa ~h~~k~~PED_SPRINT~. + +[HELP6_A] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~~w~ para usar el freno de mano del vehículo. + +[HELP6_D] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~~w~ para usar el freno de mano del vehículo. + +[HELP26] +Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para subir o bajar de un vehículo. + +[HELP27] +Pulsa ~h~~k~~VEHICLE_TURRETUP~~w~ o ~h~~k~~VEHICLE_TURRETDOWN~~w~ para equilibrar tu peso en la moto. + +[HELP28] +Pulsa ~h~~k~~VEHICLE_TURRETUP~~w~ o ~h~~k~~VEHICLE_TURRETDOWN~~w~ para equilibrar tu peso en la moto. + +[HELP35] +Pulsa ~h~~k~~GO_LEFT~~w~ o ~h~~k~~GO_RIGHT~~w~ para conducir el vehículo. + +[HELP36] +Pulsa ~h~~k~~GO_LEFT~~w~ o ~h~~k~~GO_RIGHT~~w~ para conducir el vehículo. + +[HELP42] +Ve al ~q~icono rosa~w~ para encontrar el hotel. + +[HELP19] +Camina hasta el ~q~marcador rosa~w~ para continuar. + +[HELP1] +Párate en el centro del ~q~marcador rosa. + +[HELP12] +Camina hasta el centro del ~q~marcador rosa~w~ para activar una misión. + +[SEG3_6] +~g~Para acertar en una zona objetivo con éxito, deberás soltar una bomba en la zona representada por el ~q~marcador rosa~w~. Puedes soltar las bombas en cualquier orden. + +[S_PROMP] +Cuando no estés en una misión podrás guardar tu progreso recogiendo las ~h~cintas de cassette~w~. + +[HELP16] +Cruza la puerta principal del hotel ~h~Ocean View~w~ para entrar en el edificio. + +[HELP43] +~g~Ve al hotel ~h~Ocean View~g~ en Ocean Drive. + +[HELI_F1] +~r~¡Misión de puntos de control cancelada! + +[AMMUHLP] +Si necesitas armas pásate por ~h~Ammu-Nation~w~. Está indicado en el radar como una ~h~pistola~w~ azul. + +[HELI_1] +Control del Helicóptero de Centro de la ciudad + +[HELI_2] +Control del Helicóptero de Ocean Beach + +[HELI_3] +Control del Helicóptero de Vice Point + +[HELI_4] +Control del Helicóptero de Little Haiti + +[FST_MFR] +Emisora de radio preferida + +[FST_LFR] +Emisora de radio menos escuchada + +[FEI_HOL] +Mantener + +[FEI_ZOO] +Zoom + +[FEI_BTR] +> < - + +[FEI_NA] +N\A + +[MESA] +Mesa Grande + +[STRP_NO] +No puedes comprar el club de striptease en este momento, vuelve luego. + +[CHSE] +PERSECUCIÓN + +[NBMN_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la casa Swanko por ~1~ $. + +[NBMN_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la casa Swanko por ~1~ $. + +[NBMN_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la casa Swanko por ~1~ $. + +[LNKV_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el apartamento de Links View por ~1~ $. + +[LNKV_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el apartamento de Linsk View por ~1~ $. + +[LNKV_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el apartamento de Links View por ~1~ $. + +[HYCO_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el piso de Hyman por ~1~ $. + +[HYCO_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el piso de Hyman por ~1~ $. + +[HYCO_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el piso de Hyman por ~1~ $. + +[OCHE_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el apartamento de Ocean Heights por ~1~ $. + +[OCHE_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el apartamento de Ocean Heights por ~1~ $. + +[OCHE_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el apartamento de Ocean Heights por ~1~ $. + +[WASH_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el 1102 de la calle Washington por ~1~ $. + +[WASH_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el 1102 de la calle Washington por ~1~ $. + +[WASH_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el 1102 de la calle Washington por ~1~ $. + +[VCPT_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el 3321 de Vice Point por ~1~ $. + +[VCPT_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el 3321 de Vice Point por ~1~ $. + +[VCPT_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el 3321 de Vice Point por ~1~ $. + +[SKUM_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la chabola Skumole por ~1~ $. + +[SKUM_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~para comprar la chabola Skumole por ~1~ $. + +[SKUM_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~ ~w~ para comprar la chabola Skumole por ~1~ $. + +[PRNT_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la imprenta por ~1~ $. + +[PRNT_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la imprenta por ~1~ $. + +[PRNT_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la imprenta por ~1~ $. + +[CAR_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el concesionario de coches por ~1~ $. + +[CAR_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el concesionario de coches por ~1~ $. + +[CAR_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el concesionario de coches por ~1~ $. + +[PORN_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar los estudios de cine por ~1~ $. + +[PORN_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar los estudios de cine por ~1~ $. + +[PORN_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar los estudios de cine por ~1~ $. + +[ICE_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la fábrica de helados por ~1~ $. + +[ICE_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la fábrica de helados por ~1~ $. + +[ICE_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la fábrica de helados por ~1~ $. + +[TAXI_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la compañía de taxis por ~1~ $. + +[TAXI_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la compañía de taxis por ~1~ $. + +[TAXI_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la compañía de taxis por ~1~ $. + +[BANK_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el Malibú por ~1~ $. + +[BANK_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el Malibú por ~1~ $. + +[BANK_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el Malibú por ~1~ $. + +[BOAT_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el astillero por ~1~ $. + +[BOAT_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el astillero por ~1~ $. + +[BOAT_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el astillero por ~1~ $. + +[STRP_L] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el club Pole Position por ~1~ $. + +[STRP_T] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el club Pole Position por ~1~ $. + +[STRP_C] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar el club Pole Position por ~1~ $. + +[STOCK] +~r~Agotado + +[HELP14] +Para encontrar el bufete del abogado, ve hacia la ~h~señal de la L~w~ que hay en el radar. + +[RAMPAGE] +¡MASACRE! + +[RAMP_F] +¡MASACRE FALLIDA! + +[RAMP_P] +¡MASACRE COMPLETADA! + +[RAMP_A] +¡TODAS LAS MASACRES COMPLETADAS! + +[PAGE_01] +¡Liquida a ~1~ criminales en 2 minutos! + +[PAGE_02] +¡Destruye ~1~ vehículos en 2 minutos! + +[PAGE_03] +¡Pasa en el coche y cárgate a ~1~ criminales en 2 minutos! + +[PAGE_04] +¡Atropella y mata a ~1~ criminales en 2 minutos! + +[PAGE_05] +¡Abate a tiros a ~1~ criminales en 2 minutos! + +[SENTXS] +Sentinel XS + +[MAP_LEG] +Leyenda + +[VCNMAV] +VCN Maverick + +[LG_01] +Posición del jugador + +[LG_02] +Avery Carrington + +[LG_03] +Contacto del motero + +[LG_04] +Coronel Cortez + +[LG_05] +Ricardo Díaz + +[LG_06] +Kent Paul + +[LG_07] +Abogado + +[LG_08] +Phil Cassidy + +[LG_09] +Astillero + +[LG_10] +Club Malibú + +[LG_11] +Cubanos + +[LG_12] +Estudio Cinematográfico + +[LG_13] +Ammu-Nation + +[LG_14] +Haitianos + +[LG_15] +Ferretería + +[LG_16] +Piso franco + +[LG_17] +Helado + +[LG_18] +Taxis Kaufman + +[LG_19] +Love Fist + +[LG_20] +Imprenta + +[LG_21] +Propiedad + +[LG_22] +Taller de Pintura + +[LG_23] +Tienda de ropa + +[LG_24] +Mansión de Tommy + +[LG_25] +Teléfono + +[LG_26] +Emisora de radio Wildstyle + +[LG_27] +Emisora de radio Flash FM + +[LG_28] +Emisora de radio Kchat + +[LG_29] +Emisora de radio Fever 105 + +[LG_30] +Emisora de radio VRock + +[LG_31] +Emisora de radio VCPR + +[LG_32] +Emisora de radio Espantoso + +[LG_33] +Emisora de radio Emotion 98.3 + +[LG_34] +Emisora de radio Wave 103 + +[LG_35] +Destino + +[LG_36] +Solarium + +[LG_37] +Club de Striptease + +[LG_38] +Objetivo + +[MAP_YAH] +ESTÁS AQUÍ + +[TAXSHRT] +~g~Puedes coger este taxi Kaufman para ir a tu destino sin conducir. Te costará 9 $. + +[FEST_HV] +Nivel más alto de misión de justiciero + +[CLOHELP] +¡Ropa limpia! + +[SUNSHIN] +Coches Sunshine + +[CHERRYP] +Helados Cherry Popper + +[KAUFCAB] +Taxis Kaufman + +[BOATYAR] +El Astillero + +[WANT_L] +Has perdido tu nivel de se busca, si cometes un crimen mientras las estrellas parpadean, tu nivel total de se busca se restablecerá. + +[FEI_BTU] +; = - + +[BOAT_AS] +~g~El astillero generará ahora unos ingresos hasta un máximo de ~1~ $. Asegúrate de recaudarlos de manera regular. + +[BOAT_A2] +ASTILLERO COMPLETADO + +[BOAT_N] +Punto de control Charlie + +[BOAT_P] +~g~Recoge los paquetes antes de que se acabe el tiempo. + +[FEI_R1B] +Botón R1 \ R2 - + +[HELP9_A] +Pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para disparar el rifle de francotirador. + +[HELP21] +Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~para subir o bajar de un vehículo. + +[CREAM] +Distribución + +[UMBERTO] +Café Robina + +[PU_CF1] +Pulsa ~h~~k~~PED_ANSWER_PHONE~ ~w~para recoger esta arma. Reemplazará cualquier arma del mismo tipo que tengas. + +[FED_RDM] +MAPA E ICONOS + +[FEC_ILU] +Invertir vista en primera persona + +[NITRO] +¡Ahora todos los taxis disponen de un impulsor de salto! Simplemente toca el claxon. + +[RATNG53] +Informal + +[RATNG54] +Vergüenza + +[RATNG55] +Hacker + +[RATNG56] +Liante + +[RATNG57] +Mentiroso compulsivo + +[STHC_04] +Puntuación más alta por mantener la pelota en el aire en la playa + +[STHC_05] +Mejor resultado de Hotring + +[STFT_13] +Mejor tiempo en el Control del Helicóptero del centro + +[STFT_14] +Mejor tiempo en el Control del Helicóptero de Ocean Beach + +[STFT_15] +Mejor tiempo en el Control del Helicóptero de Vice Point + +[STFT_16] +Mejor tiempo en el Control del Helicóptero de Little Haití + +[STFT_21] +Tiempo más rápido en Hotring + +[STFT_22] +Vuelta más rápida en Hotring + +[STFT_20] +Tiempo más rápido en el ''Conocircuito'' + +[HELP44] +Detente en el ~q~marcador rosa. + +[HELP45] +Pulsa ~h~~k~~PED_DUCK~~w~ para agacharte. Esto aumentará tu puntería con las armas que llevas. + +[RCR1_5] +Carrera Bandit RC + +[RCPL1_7] +Carrera Barón RC + +[RCH1_11] +Recogidas del Raider RC + +[FEA_CTD] +¡Aviso! Esta característica requiere que el dispositivo compatible DTS esté conectado. ¿Continuar? + +[FEM_STE] +UTILIZAR ESTÉREO + +[GREET] +Saludos desde... + +[LANCE_1] +¡Venga, tío, conduce con más cuidado! + +[LANCE_2] +¡Eh, mira lo que haces! + +[LANCE_3] +¿Eh, dónde vamos ahora? + +[LANCE_4] +¿Qué estamos haciendo ahora? + +[LAW4_15] +¡Más dinero! + +[MERC_5] +Bonito coche, Mr. Vercetti. + +[MERC_26] +¡DEPRISA, DEPRISA, DEPRISA! + +[MERC_27] +Con cuidado Tommy, me arreglaron la nariz el mes pasado. + +[MERC_28] +Conduce con cuidado Tommy. + +[MERC_29] +Ve más despacio Tommy. + +[MERC_30] +Tommy, si no te importa mata a quien quieras menos a mí. + +[MERC_31] +¡Tommy, cielo, no me mates! + +[MERC_32] +¡Tommy, me alegro que robaras este coche! + +[MERC_40] +Me he divertido tanto. + +[MERC_43] +Adiós, mi ángel. + +[MERC_44] +Ahora sigue machacándote, me oyes. + +[MERC_45] +Ciao, guapetón. + +[COL5_17] +¡Oh, Dios mío, tienen un helicóptero! + +[COL5_18] +¡Dispara al helicóptero! + +[COL5_19] +Tommy, ¡hazte con ese helicóptero! + +[COL5_20] +¡Ahí viene otra vez! ¡Derriba a ese helicóptero! + +[COL5_21] +¡Mira el tamaño de ese helicóptero! + +[COL5_22] +¡Ahí viene de nuevo! + +[FEA_DSM] +¡Aviso! Esta partida guardada está configurada para utilizar DTS. Esto requiere que el dispositivo compatible DTS esté conectado. Por favor selecciona si quieres continuar usando el sistema DTS o ESTÉREO. + +[STFT_23] +Tiempo más rápido en Punto de control Charlie + +[HELP50] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para colocar la cámara detrás de ti. + +[HELP51] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para colocar la cámara detrás de ti. + +[HELP52] +Pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para colocar la cámara detrás de ti. + +[HELP53] +Pulsa ~h~~k~~PED_CYCLE_WEAPON_LEFT~~w~ o ~h~~k~~PED_CYCLE_WEAPON_RIGHT~~w~ para elegir entre todas tus armas disponibles. + +[HELP46] +Hay ocho categorías de armas diferentes. + +[HELP47] +Puedes llevar una arma de cada categoría al mismo tiempo: un tipo de pistola, un tipo de rifle. + +[HELP54] +~w~Cuesta: ~1~ $. ~r~Si compras esta arma reemplazarás la actual. + +[HELP2A2] +Pulsa ~h~~k~~PED_SPRINT~~w~ cuando corras para ~h~esprintar. + +[HLPSN_A] +El rifle de francotirador te permite hacer zoom de acercamiento y disparar con más precisión a objetivos a una cierta distancia. + +[HLPSN_B] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar~w~ con el rifle de francotirador. + +[HLPSN_C] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar~w~ con el rifle de francotirador. + +[HLPSN_D] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~~w~ para ~h~acercar la vista ~w~con el rifle y ~h~~k~~PED_SNIPER_ZOOM_OUT~ ~w~para ~h~alejarla~w~. + +[HLPSN_E] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar~w~ el rifle de francotirador. + +[HLPSN_F] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar~w~ el rifle de francotirador. + +[HLPSN_G] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar~w~ el rifle de francotirador. + +[PLANE_H] +Mueve ~h~~k~~VEHICLE_ACCELERATE~~w~ hacia delante para acelerar y a izquierda o derecha para girar. + +[PLANE_4] { reVC update } +{Mueve ~h~~k~~VEHICLE_ACCELERATE~~w~ hacia delante para acelerar y a izquierda o derecha para girar.} +Utiliza el joystick analógico derecho para acelerar, tira hacia atrás el joystick analógico izquierdo para ascender, empújalo hacia adelante para descender. Para girar muévelo a izquierda o derecha. + +[HELP55] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para atacar al chef. + +[STPR_8] +Club Pole Position + +[STPR_9] +3321 de Vice Point + +[STPR_10] +Apartamento de Links View + +[STPR_11] +Casa Swanko + +[STPR_12] +1102 de Washington Street + +[STPR_13] +Apartamento de Ocean Heights + +[STPR_14] +Chabola Skumole + +[STPR_15] +Piso de Hyman + +[RCCANX] +~r~Misión de avión RC cancelada. + +[CLT_HL2] +Cuando recojas ropa, te librarás de una o dos estrellas del nivel de se busca. + +[CRED009] +DISEÑO DE MISIONES + +[CRED359] +LEE JOHNSON + +[CRED360] +HENDRIK LESSER + +[CRED361] +PASQUALE STACCHIOTTI + +[CRED362] +ENRIQUE FERNÁNDEZ + +[CRED363] +PAUL BYERS + +[CRED364] +MIKE EMENY + +[CRED365] +ROB DUNKIN + +[CRED366] +CHARLIE KINLOCH + +[CRED367] +KEVIN HOBSON + +[CRED368] +JIM CREE + +[MOB_66A] +Tommy, Tommy, ¿para qué has vuelto? + +[MOB_66B] +Ya te he dicho que no queremos verte más. + +[MOB_67A] +Tommy, creo que no deberías acercarte, ¿me oyes? + +[MOB_67B] +Los chicos haitianos no están muy contentos contigo. + +[MOB_18A] +Tommy, soy Paulo, ¿cómo estás? Vale hombre, bueno pensé que tenía que mandarte una nota. + +[MOB_18B] +Oh Dios misericordioso, hijo mío, no te vas a creer la categoría de la furcia que acabo de conocer. + +[MOB_18C] +Prostituta callejera o algo parecido, cerca de Little Havana, tío. + +[MOB_18D] +Me dijo que se llamaba Mercedes o algo así. + +[MOB_18E] +Oh Dios, tío, tienes que probar a esa palomita. + +[MOB_18F] +Sabe como ponerte a tono. Dijo que yo era el mejor tío con el que había estado en toda su vida. + +[MOB_18G] +Estate al tanto por si la ves. Hasta luego. + +[MOB_72A] +Tommy, soy yo, Lance. Mantén la boca cerrada Tommy porque no tengo ganas de hablar. + +[MOB_72B] +No estoy interesado en lo que tengas que decir. ¿Por qué debería estarlo? ¿Te preocupas tú por mí? + +[MOB_72C] +Tienes que cuidarme un poco mejor. Darme una buena tajada, ya sabes... + +[MOB_72D] +Tommy... oh, mira, tío, Lo siento. Es solo que... + +[MOB_72E] +La gente me ha estado sobreprotegiendo toda mi vida, tratándome como a un niño. + +[MOB_72F] +Mi hermano haría eso. Por favor, tío, no lo hagas. + +[MOB_72G] +Tengo que irme. + +[MOB_63A] +Tommy, soy Earnest. Earnest Kelly. + +[MOB_63B] +¿Cómo estás? + +[MOB_63C] +Estoy bien. Necesitaré un bastón para andar pero muy pronto volveré al trabajo. + +[MOB_63D] +Bien. + +[MOB_63E] +Me enteré de lo de Lance. Vaya cabroncete, ¿eh? + +[MOB_63F] +Sí. + +[MOB_63G] +No te fíes nunca de un hombre que va por la calle en pijama. Y es lo que digo yo. Menos mal que lo mataste. Espero que el mamón sufriera. + +[MOB_63H] +Creo que sí. Lo que pasa es que yo no creí que... + +[MOB_63I] +Tommy, para ser un chalado furioso, eres bastante ingenuo. Cuando vuelva a trabajar pronto te enseñaré un par de cosas sobre la vida, me oyes. + +[MOB_63J] +Tómate tu tiempo, Earnest. Cuídate. + +[MOB_16A] +Tommy, soy Paulo, ¿qué pasa amigo? + +[MOB_16B] +Qué quieres Paul. No necesito ropa de marca de imitación. + +[MOB_16C] +Muy gracioso, tío, pero sabes que no toco nada de mercancía ilegal. No, sólo llamaba para ver si puedo conseguir un papel en una de tus películas. + +[MOB_16D] +Allí en Inglaterra hice un montón de porno, colega. Soy bastante más ardiente que tú, pequeño. + +[MOB_16E] +Paul, gracias por la oferta, lo tendré en cuenta. + +[MOB_16F] +En serio, después de todo lo que he hecho por ti, no te olvides de mí. + +[MOB_16G] +Eso es lo que intento olvidar. + +[MOB_17A] +Tommy Vercetti, ¿Qué tal te va Sr. Influyente? He oído un montón de cosas sobre ti, así que ahora eres el que manda en la ciudad, eh... + +[MOB_17B] +Paul, estás borracho. + +[MOB_17C] +No, imbécil, no estoy borracho. Sólo he tomado un par de copas y alguna que otra invitación, no me he acostado en los dos últimos días, sabes. + +[MOB_17D] +En cualquier caso, no me vengas con esto. No soy tonto. ¿Quién te estableció en esta ciudad? ¿Quién? Yo. Que te enteres. + +[MOB_17F] +¿De verdad? + +[MOB_17G] +No me vengas con esto. Yo te presenté a la gente. Te enseñé cómo hacer bien los trabajos, hice muchas cosas por ti, y así es como me lo pagas. + +[MOB_17H] +Me ignoras. Ni siguieras me das acceso a nada, después de todo lo que hice por ti. ¿Quién te crees que soy? ¿Un retrasado mental o algo así? + +[MOB_17I] +Paul, tranquilízate. He estado muy ocupado, no te comportes como un idiota.. + +[MOB_17J] +No soy un idiota, colega. Eso es lo que decían en el reformatorio. ¿Estás buscando bronca? ¡Porque la vas a tener! + +[MOB_17K] +Tommy, colega. Por favor. ¡Tú eras mi gran esperanza! Por favor, ¡no te rías de mí! + +[MOB_17L] +Paul, vete a dormir un poco, de verdad. + +[MOB_73A] +Tommy, soy Steve. + +[MOB_73B] +Eh, Steve. + +[MOB_73C] +¡Eres un portento! ¡Soy un portento! La gente está loca por nosotros. Vamos a entrar en la historia, amigo. + +[MOB_73D] +Se trata de los premios más importantes. Podré llevar a mi padre a una residencia y no tener que escucharle nunca más. + +[MOB_73E] +Eso está bien, Steve. + +[MOB_73F] +Mejor que bien tío. Mejor, mucho mejor. Nunca creyó en mí. Nunca creyó que pudiera ser un artista y ahora lo he conseguido. + +[MOB_73G] +Soy el mejor director de cine porno de todos los tiempos, amigo. Solo quería decirte que ha sido un placer conocerte. + +[MOB_73H] +Gracias Steve. + +[MOB_73I] +Te quiero tío, No cambies, me oyes. + +[MOB_73J] +Te oigo. Adiós Steve. + +[BOLLOX] +Pulsa ~h~~k~~VEHICLE_HANDBRAKE~ ~w~para soltar una bomba. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~ ~w~para cancelar. + +[BRID_OP] +El temporal ha pasado, todos los puentes hacia la península están ahora abiertos. + +[BRID_CL] +Alerta de temporal: Cerrados todos los puentes hacia la península. + +[ASSET_C] +¡CLUB POLE POSITION CONSOLIDADO! + +[ASSET_D] +El club Pole Position generará ahora unos ingresos máximos de hasta ~1~ $ al día. ¡Asegúrate de recaudarlos regularmente! + +[ST_WHEE] +Mayor tiempo en caballito (seg) + +[ST_STOP] +Mayor tiempo en parada (seg) + +[ST_2WHE] +Mayor tiempo en dos ruedas (seg) + +[ST_WHED] +Mayor distancia en caballito (m) + +[ST_STOD] +Mayor distancia en parada (m) + +[ST_2WHD] +Mayor distancia en dos ruedas (m) + +[OUTFT11] +Chándal + +[OUTFT12] +Frankie + +[RELOAD] +~g~¡Has conseguido la habilidad de recargar rápidamente! + +[APACHE] +Hunter entregado en el helipuerto de Ocean Beach. + +[CRED369] +JOHN MCCARDLE + +[CRED370] +DAVID MURDOCH + +[CRED371] +CHRIS BROWN + +[CRED372] +PAUL GREEN + +[CRED373] +KYLE MILNE + +[CRED374] +KEVIN YUN + +[CRED375] +ERICK COBBS + +[CRED376] +RANDY BLAKE + +[CRED377] +BRANDON LIM + +[CRED378] +BRANDON FENOL + +[CRED379] +MICHAEL MANOLE + +[CRED380] +ALETHEIA SIMONSON + +[CRED381] +JOHN JANSEN + +[CUNTY] +¡Ropa nueva entregada en la finca de Vercetti! + +[GOODBOY] +¡Prima de 50 $ por comportamiento ejemplar! + +[NEWCONT] +¡Nuevo ~h~punto de contacto ~w~disponible en el puerto deportivo en Ocean Beach! + +[FIRELVL] +Misión de camión de bomberos nivel ~1~ + + +[HELP56] +Pulsa ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ para ajustar la cámara. + +[HELP57] +Pulsa ~h~~k~~CAMERA_CHANGE_VIEW_ALL_SITUATIONS~~w~ para ajustar la cámara. + +[HELP58] +Mientras estás apuntando, puedes pulsar ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ o ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~para cambiar de objetivo. + +[HELP59] +Mientras estás apuntando, puedes pulsar ~h~~k~~PED_CYCLE_TARGET_LEFT~~w~ o ~h~~k~~PED_CYCLE_TARGET_RIGHT~~w~para cambiar de objetivo. + +[HELP60] +Si pulsas ~h~~k~~PED_SPRINT~ ~w~ mientras estás robando un coche, no te subirás en él. + +[HELP61] +Ahora dispones de munición ilimitada y el doble de robustez en todos los vehículos. + +[FEC_LB1] +Mirar + +[FEC_LB2] +detrás + +[FEC_LB3] +Mirar detrás + +[FEC_R3] +(botón R3) + +[FEC_PED] +Controles a pie + +[FEC_VEH] +Controles en vehículo + +[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] +Cambiar Arma a la Izq. + +[FEC_CRI] +Cambiar Arma a la Dcha. + +[FEC_LKT] +Bloquear objetivo + +[FEC_PJP] +Salto Peatón + +[FEC_PSP] +Sprint Peatón + +[FEC_PSH] +Disparar Peatón + +[FEC_TLF] +Siguiente objetivo a la izq. + +[FEC_TRG] +Siguiente objetivo a la dcha. + +[FEC_CCM] +Centrar cámara detrás del jugador + +[FEC_SZI] +Acercar vista rifle francotirador + +[FEC_SZO] +Alejar vista rifle francotirador + +[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] +Cambiar armas primera persona + +[FEC_SMS] +Mostrar puntero del ratón + +[FEC_CMS] +Cambiar modo cámara en todas situaciones. + +[FEC_TSS] +Capturar pantalla + +[FEC_DBG] +DEBUG MENU + +[FEC_TGD] +Cambiar mando de juego/depuración + +[FEC_TDO] +Cambiar cámara depuración No + +[FEC_IVH] +Invertir horizontalidad ratón + +[FEC_MSL] +BIR + +[FEC_MSM] +BCR + +[FEC_MSR] +BDR + +[FEC_QUE] +??? + +[FEC_TWO] +Permitidas sólo dos teclas del teclado. + +[FEC_UMS] +Por favor sólo teclas de ratón. + +[FEC_OMS] +Permitida sólo una tecla del ratón. + +[FEC_UJS] +Por favor sólo botones de joystick. + +[FEC_OJS] +Permitido sólo un botón de joystick por acción. + +[FEC_PTL] +Usar Bloquear objetivo al cambiar arma a izq. + +[FEC_PTR] +Usar Bloquear objetivo al cambiar arma a dcha. + +[FEC_LBC] +Usar Mirar a izq. con Mirar a la dcha. + +[FEC_JBO] +JOY ~1~ + +[FEC_WAR] +Aviso + +[FEC_OKK] +Aceptar + +[FEC_DLF] +Error al borrar. + +[FEC_SVU] +Error al guardar. + +[FEC_LUN] +Error al cargar. Archivo dañado, por favor bórralo. + +[FEC_PAD] +Gamepad + +[FEC_JOY] +Joystick + +[FES_CSA] +Seleccionar aspecto de la lista inferior: + +[FET_HRD] +AJUSTES POR DEFECTO RESTAURADOS + +[FET_MST] +CONDUCCIÓN CONTROLADA POR EL RATÓN + +[FEC_STR] +NUM. INICIO + +[FET_MIG] +IZQUIERDA, DERECHA, RUEDA DEL RATÓN PARA AJUSTAR + +[FET_CIG] +RETROCESO PARA QUITAR - BIR, VOLVER A CAMBIAR + +[FET_DSN] +Apariencia predeterminada del jugador.bmp + +[FET_RSO] +RESTAURADO AJUSTE ORIGINAL + +[FET_RSC] +HARDAWARE NO DISPONIBLE. RESTAURADO AJUSTE ORIGINAL + +[FEA_3DH] +HARDWARE DE SONIDO + +[FEA_SPK] +AJUSTE DE ALTAVOCES + +[FEM_LOD] +DIST. DE REPRESENTACIÓN + +[FEM_VSC] +SINCRONÍA DE IMAGEN + +[FEM_FRM] +LIMITADOR DE CUADROS + +[FEM_MM] +MENÚ PRINCIPAL + +[FED_RES] +RESOLUCIÓN DE PANTALLA + +[FET_CTL] +CONFIGURAR MANDO + +[FET_OPT] +OPCIONES + +[FEC_MSH] +SENSIBILIDAD DEL RATÓN + +[FEC_IVV] +INVERTIR VERTICALIDAD RATÓN + +[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 DCH + +[FEC_LCT] +CTRL I + +[FEC_RCT] +CTRL D + +[FEC_LAL] +ALT I + +[FEC_RAL] +ALT D + +[FEC_LWD] +WIN I + +[FEC_RWD] +WIN D + +[FEC_WRC] +APMENÚ + +[FEC_SPC] +ESPACIO + +[WIN_TTL] +Grand Theft Auto VC + +[WIN_95] +Grand Theft Auto VC no se puede ejecutar bajo W95 + +[WIN_DX] +Grand Theft Auto VC necesita al menos la versión 8.1 de DirectX + +[FET_EIG] +NO SE PUEDE DEFINIR UN CONTROL PARA ESTA ACCIÓN + +[FET_DAM] +MODELADO ACÚSTICO DINÁMICO + +[FEQ_SRE] +¿Seguro que quieres salir? Se perderán todos los progresos desde la última partida guardada. ¿Quieres proceder? + +[FEQ_SRW] +¿Seguro que quieres salir del juego? + +[FET_QG] +SALIR DEL JUEGO + +[FEN_STA] +INICIAR JUEGO + +[FET_PAU] +MENÚ PAUSA + +[REPLAY] +VOLVER A JUGAR + +[FEC_ANS] +Acción + +[CVT_MSG] +Convertir texturas a un formato óptimo para tu tarjeta de vídeo + +[FEC_SFT] +MAYÚS + +[FEH_VMP] +VER MAPA + +[FES_DEE] +¡Error al eliminar! Por favor, inténtalo de nuevo. + +[FES_CMP] +¡Error al guardar! Por favor, inténtalo de nuevo. + +[FESZ_WR] +Guardando partida actual. Por favor, espera... + +[FELD_WR] +Cargando el juego. Por favor, espera... + +[FEDL_WR] +Eliminando partida guardada. Por favor, espera... + +[PCRESRT] +Iniciando nueva partida. Por favor, espera... + +[FET_STI] +Controles estándar + +[FET_CTI] +Controles clásicos + +[FEH_NA] +OPCIÓN NO DISPONIBLE + +[FEH_MPH] +RATÓN, CURSORES PARA MOVERSE - RE. PAG, AV. PAG, RUEDA RATÓN PARA ZOOM, L - LEYENDA + +[FEA_MP3] +REPRODUCTOR MP3 + +[NO_PCCD] +Por favor, introduce tu CD de GTA Vice City o pulsa ESC para cancelar + +[FEH_SSA] +CURSORES PARA MOVERSE - S PARA GUARDAR EN UN ARCHIVO + +[FES_CMI] +ÚLTIMA MISIÓN SUPERADA + +[FET_STS] +ESTADÍSTICAS GUARDADAS EN 'STATS.HTML' + 'STATS.TXT' + +[WIN_VDM] +Grand Theft Auto VC no pudo encontrar suficiente memoria de vídeo disponible + +[FEC_ERI] +¡Error! Una o más acciones de control no están vinculadas a ninguna tecla o botón. Comprueba que todas las acciones de control estén definidas. + +[FEC_TFU] +Torreta + Inclinar arriba + +[FEC_TFD] +Torreta + Inclinar abajo + +[FET_RIG] +ELIGE UN NUEVO CONTROL PARA ESTA ACCIÓN + +[FEA_NM3] +NO SE ENCONTRARON ARCHIVOS MP3 + +[FEA_MPB] +SUBIR VOLUMEN DE MP3 + +[FEA_MUS] +VOLUMEN DE MÚSICA + +[FEA_SFX] +VOLUMEN DE EFECTOS + +[CVT_ERR] +Te has quedado sin espacio en el disco duro. Por favor, antes de seguir consigue algo de espacio en tu disco duro. Pulsa ESC para cancelar. + +[FEA_ADP] +DETECTAR EL HARDWARE AUTOMÁTICAMENTE + +{=================================== MISSION TABLE AMBULAE ===================================} + +[ATUTOR2:AMBULAE] +~g~Lleva a los pacientes al hospital CON CUIDADO. Cada bache reduce sus posibilidades de supervivencia. + +[A_FULL:AMBULAE] +~r~¡Ambulancia llena! + +[A_FAIL2:AMBULAE] +~r~¡Tu falta de urgencia ha sido mortal para el paciente! + +[A_FAIL3:AMBULAE] +~r~¡El paciente está muerto! + +[A_PASS:AMBULAE] +¡Rescatado! + +[A_COMP2:AMBULAE] +¡Nunca te cansarás! + +[A_CANC:AMBULAE] +~r~¡Misión de ATS cancelada! + +[A_COMP3:AMBULAE] +¡Misiones de ATS completadas! ¡Nunca te cansarás cuando estés corriendo! + +[ALEVEL:AMBULAE] +Misión de ATS nivel ~1~ + +[A_FAIL1:AMBULAE] +Misión de ATS terminada. + +[A_SAVES:AMBULAE] +PERSONAS SALVADAS: ~1~ + +[A_COMP1:AMBULAE] +Misiones de ATS completas: ~1~ $ + +{=================================== MISSION TABLE ASSIN1 ===================================} + +[ASM1_5:ASSIN1] +~r~¡Completó sus entregas! + +[ASM1_6:ASSIN1] +Entregas restantes: + +[ASM1_7:ASSIN1] +~g~Carl Pearson, repartidor de pizza. Mátale antes que finalice el reparto. + +[ASM1_A:ASSIN1] +Sr. Teal, su ayuda en erradicar a esos forasteros fue inestimable para el negocio. Tengo más trabajo para usted y más cercano a la ''intervención''. + +[ASM1_D:ASSIN1] +Sr. Teal, su ayuda en erradicar a esos forasteros fue inestimable para el negocio. + +{=================================== MISSION TABLE ASSIN2 ===================================} + +[ASM2_1:ASSIN2] +~g~La Sra. Dawson dejará pronto la joyería de Vice Point. Mátala. Debe parecer un accidente de coche. + +[ASM2_3:ASSIN2] +~g~¡Va a estallar! ¡Largo de aquí! + +[ASM2_4:ASSIN2] +~r~Dañaste su coche ¡y ni siquiera está en él! ¡Ahora no se subirá ahí! + +[ASM2_5:ASSIN2] +~r~¡Se escapó! + +[ASM2_6:ASSIN2] +~r~¡Estabas demasiado cerca de la escena del ''accidente''! + +[ASM2_7:ASSIN2] +~g~¡No utilices armas! ¡Se supone que debe parecer un accidente! ¡En vez de eso, sácala de la carretera! + +[ASM2_8:ASSIN2] +~g~Debes hacer que la muerte de la Sra. Dawson parezca un accidente. No utilices ningún arma. + +[ASM2_9:ASSIN2] +~g~¡Necesitas un coche para este trabajo! + +[ASM2_10:ASSIN2] +~g~Cuando el coche explote en llamas vete lo más lejos posible de la escena del accidente. + +[ASM2_11:ASSIN2] +¡Ayúdame! + +[ASM2_12:ASSIN2] +¡Que alguien me ayude! + +[ASM2_13:ASSIN2] +¡oh, Dios mío! + +[ASM2_A:ASSIN2] +Felicidades por un trabajo bien hecho, Sr. Teal. Mi cliente está muy satisfecho. + +[ASM2_2:ASSIN2] +salud: + +{=================================== MISSION TABLE ASSIN3 ===================================} + +[ASM3_11:ASSIN3] +TIEMPO: + +[ASM3_C:ASSIN3] +Una banda europea planea atracar un banco en Vice City. A mis jefes les gustaría que esto no sucediese. + +[ASM3_D:ASSIN3] +Cada miembro de la banda tiene una tapadera mientras está aquí en Vice City. Algunos tienen trabajos sin importancia y otros están de vacaciones. + +[ASM3_E:ASSIN3] +Cada objetivo y sus posibles paraderos están pegados con cinta adhesiva bajo el teléfono. + +[ASM3_14:ASSIN3] +~g~Dick Tanner está ubicado junto al DBP de Seguridad en Ocean Drive. + +[ASM3_15:ASSIN3] +~g~Marcus Hammond y Franco Carter están ubicados junto a la joyería en Vice Point. + +[ASM3_16:ASSIN3] +~g~Nick Kong está en su barco cerca de Washington Beach. + +[ASM3_18:ASSIN3] +~g~¡No te acerque demasiado al objetivo o te descubrirá y tendrás que perseguirle hasta atraparle! + +[ASM3_19:ASSIN3] +~g~¡Te ha visto! ¡Acaba con él! + +[ASM3_20:ASSIN3] +~g~¡Te han visto! ¡Asegúrate de acabar con los dos! + +[ASM3_21:ASSIN3] +~r~¡No mataste a todos los miembros de la banda a tiempo! + +[ASM3_22:ASSIN3] +~g~¡No te acerques demasiado a los objetivos o te descubrirán e intentarán escapar. + +[ASM3_12:ASSIN3] +~g~Se ha dejado cerca una selección de armas en caso de necesidad. Dispones de ~h~9 MINUTOS ~g~para matar a todos los miembros de la banda. + +[ASM3_13:ASSIN3] +~g~Mike Griffin está trabajando en una valla de publicidad en Washington. + +[ASM3_17:ASSIN3] +~g~Charlie Dilson está dando una vuelta en su moto en Washington. + +{=================================== MISSION TABLE ASSIN4 ===================================} + +[ASM4_10:ASSIN4] +~g~¡Parece que no eres el único que persigue el maletín! ¡Llévalo deprisa a Ammu- Nation! + +[ASM4_12:ASSIN4] +Distancia: + +[ASM4_15:ASSIN4] +~g~Busca el rifle de francotirador hacia tu derecha. + +[ASM4_16:ASSIN4] +~g~Observa a la mujer de la galería, bajará por las escaleras mecánicas y preguntará a alguien la hora. + +[ASM4_17:ASSIN4] +~g~Solamente cuando la conversación haya concluido, mata a la persona con la que habló, pero no la mates a ella. + +[ASM4_18:ASSIN4] +~g~Cuando el objetivo haya muerto, recupera el maletín y llévalo a Ammu-Nation, en el centro. + +[ASM4_19:ASSIN4] +~g~Mantén tu distancia del objetivo, la barra de distancia de la parte superior de la pantalla te indica lo cerca que estás del objetivo. + +[ASM4_20:ASSIN4] +~g~No dejes que se llene o te verá. + +[ASM4_21:ASSIN4] +~g~¡Recoge el maletín! + +[ASM4_22:ASSIN4] +~g~Lleva el maletín a Ammu-Nation, en el centro. + +[ASM4_23:ASSIN4] +~g~¡Te ha visto y se escapa, atrápale y consigue el maletín! + +[ASM4_25:ASSIN4] +~r~¡Has matado a la mujer, idiota! + +[ASM4_26:ASSIN4] +~r~¡El objetivo ha subido a su avión! + +[ASM4_27:ASSIN4] +~r~¡El objetivo te ha visto! ¡Deberías mantener tu distancia! + +[ASM4_28:ASSIN4] +~r~¡El objetivo te ha visto! ¡Te ha escuchado disparar el arma! + +[ASM4_29:ASSIN4] +~r~¡Mátale sólo después que haya hablado con la mujer! + +[ASM4_A:ASSIN4] +Es hora de ir a por el pez gordo, Sr. Teal. Hay un rifle a su derecha, tras la maceta. + +[ASM4_B:ASSIN4] +Observe a la mujer que está en la galería por encima de los mostradores de facturación. Caminará por entre la gente y le preguntará a alguien la hora. + +[ASM4_C:ASSIN4] +Debe matar a esa persona, recoger el maletín y llevarlo al lugar adherido con cinta bajo el teléfono. + +{=================================== MISSION TABLE ASSIN5 ===================================} + +[ASM5_A:ASSIN5] +Un valioso canje va a tener lugar en el tejado de la compañía de helados Cherry Popper. + +[ASM5_B:ASSIN5] +Liquide a todos los implicados, robe el maletín y llévelo al helipuerto del aeropuerto. + +[ASM5_C:ASSIN5] +Hay una puerta a su izquierda que conduce a la parte trasera de la fábrica. + +[ASM5_1:ASSIN5] +~g~Entra al complejo por detrás de la compañía de helados Cherry Popper y dirígete al tejado donde va a tener lugar el trato. + +[ASM5_2:ASSIN5] +~g~Consigue el maletín y llévalo al helipuerto del aeropuerto. + +[ASM5_3:ASSIN5] +~g~¡Lleva el maletín al helipuerto del aeropuerto! + +{=================================== MISSION TABLE BANKJ1 ===================================} + +[WANTED1:BANKJ1] +~g~¡Quítate de encima a los polis y pierde tu nivel de se busca! + +[BJM1_A:BANKJ1] +¡Tommy! ¡Eh, Tommy, mira esto, es genial! ¡He hecho que nos instalen un minibar! + +[BJM1_B:BANKJ1] +Tenemos todo un bar abajo, Ken. + +[BJM1_C:BANKJ1] +Sí, sí, vale. Bueno, tengo la pizarra que pediste. + +[BJM1_D:BANKJ1] +Eso es lo bueno de estudiar derecho: te enseñan a seguir las instrucciones. + +[BJM1_E:BANKJ1] +Necesito un hombre para la caja fuerte. + +[BJM1_F:BANKJ1] +De acuerdo, bueno, déjame pensar... caja fuerte, caja fuerte, caja fuerte, caja fuerte... ¡Lo tengo! ¡Este tipo te va a encantar! + +[BJM1_G:BANKJ1] +Ah, ese idiota no. Está encerrado. + +[BJM1_H:BANKJ1] +¿Encerrado dónde? + +[BJM1_I:BANKJ1] +En la celda de una comisaría esperando el traslado. + +[BJM1_J:BANKJ1] +Creo que está a punto de conseguir la libertad condicional... + +[BJM1_1:BANKJ1] +~g~¡Libera a Cam Jones de la comisaría de policía! + +[BJM1_3:BANKJ1] +~g~Encontrarás algo útil en el vestuario de la comisaría. + +[BJM1_21:BANKJ1] +~g~Puedes encontrar la llave de las celdas en el piso de arriba de la comisaría. + +[BNK1_7:BANKJ1] +¿Cam Jones? + +[BNK1_8:BANKJ1] +¡Te voy a sacar de aquí! + +[BNK1_10:BANKJ1] +Sí, soy yo... + +[BNK1_11:BANKJ1] +¡Lo que tú digas! + +[BNK1_13:BANKJ1] +Voy a hacer un trabajo y tu serás mi revienta cajas fuertes. + +[BNK1_14:BANKJ1] +¡Eso es mejor que pudrirse en una celda! + +[BJM1_22:BANKJ1] +~g~¡Lleva a Cam de vuelta a su casa! + +[BJM1_23:BANKJ1] +~g~¡Necesitas coger la tarjeta llave primero! + +[BNK1_12:BANKJ1] +¡Piérdeles el rastro y llévame a mi casa! + +[BJM1_20:BANKJ1] +¡Guarda el arma o te enfréntate a las consecuencias! + +[BJM1_5:BANKJ1] +¡Sólo personal autorizado más allá de este punto! + +[BJM1_2:BANKJ1] +~r~¡Se supone que tenías que sacar a rastras a Cam, no matarle! + +[BJM1_4:BANKJ1] +¡Está armado! ¡Mátale! + +{=================================== MISSION TABLE BANKJ2 ===================================} + +[BJM2_A:BANKJ2] +Necesitamos a un atracador. ¿Conoces a alguno? + +[BJM2_B:BANKJ2] +Eh, Tommy, Tommy, Tommy, esta mierda te pone a tono, tío. + +[BJM2_C:BANKJ2] +¡Guauuuuu! + +[BJM2_D:BANKJ2] +¡Yo podría ser tu atracador! ¡Manos arriba! ¡Manos arriba! + +[BJM2_E:BANKJ2] +Tú no eres un atracador, tú eres un idiota. + +[BJM2_F:BANKJ2] +Ahora ponte una copa y cállate. + +[BJM2_G:BANKJ2] +Eh. ¡Quítate de mi camino! Sí, sí, sí, eh, eh. + +[BJM2_H:BANKJ2] +¿Cam, tú qué opinas? + +[BJM2_I:BANKJ2] +Bueno, el mejor tirador de esta ciudad es un tipo llamado Cassidy. + +[BJM2_J:BANKJ2] +¿Seguro? + +[BJM2_K:BANKJ2] +Sí. Un militar, o al menos él piensa que lo es. + +[BJM2_L:BANKJ2] +Dudo que estuviese alguna vez en el ejército, pero desde luego sabe manejar las armas. + +[BJM2_M:BANKJ2] +Estará en el campo de tiro. + +[BJM2_2A:BANKJ2] +¿Tú eres Phil Cassidy? + +[BJM2_2B:BANKJ2] +¿Por qué? + +[BJM2_2C:BANKJ2] +Estoy buscando a un hombre que sepa manejar un arma. Pero ahora que te veo, no estoy muy convencido. + +[BJM2_2D:BANKJ2] +Hijo, podría quitarte una mosca de la cabeza de un solo disparo a 100 metros. + +[BJM2_2E:BANKJ2] +Oh, ¿de verdad? + +[BJM2_2F:BANKJ2] +Sí. Aprendí en el ejército. + +[BJM2_2G:BANKJ2] +¿Se suele disparar a las moscas en el ejército? Me alegra no pagar impuestos. + +[BJM2_2H:BANKJ2] +¿Intentas ser gracioso? + +[BJM2_2I:BANKJ2] +~n~ + +[BJM2_2J:BANKJ2] +Vamos a disparar. + +[BJM2_1:BANKJ2] +~g~Ve a Ammu-Nation en el centro de la ciudad y habla con Phil Cassidy. + +[BJM2_3:BANKJ2] +TASA DE IMPACTOS: ~1~% + +[BJM2_4:BANKJ2] +PUNTUACIÓN PRIMERA FASE: ~1~ + +[BJM2_6:BANKJ2] +PUNTUACIÓN SEGUNDA FASE: ~1~ + +[BJM2_7:BANKJ2] +PUNTUACIÓN TOTAL DE DISPARO: ~1~ + +[BJM2_9:BANKJ2] +~g~Ve al punto de inicio de la segunda fase. + +[BJM2_11:BANKJ2] +~r~¡Phil está muerto! + +[BJM2_12:BANKJ2] +~r~¡Uno de los tiradores está muerto! + +[BJM2_14:BANKJ2] +~g~¡Pasa a la siguiente fase! + +[BJM2_15:BANKJ2] +PUNTUACIÓN: + +[BJM2_17:BANKJ2] +~g~Ve y habla con Phil. + +[BJM2_18:BANKJ2] +PUNTUACIÓN A SUPERAR: + +[BJM2_19:BANKJ2] +~g~¡Acierta a tantos blancos como puedas dentro del límite de tiempo! + +[BJM2_22:BANKJ2] +~r~¡Has abandonado el campo de tiro! + +[BJM2_23:BANKJ2] +~g~Si abandonas el campo de tiro durante la competición, fracasarás en la misión. + +[BJM2_24:BANKJ2] +~g~El objetivo más cercano vale un punto. + +[BJM2_25:BANKJ2] +~g~El objetivo del medio vale dos puntos. + +[BJM2_27:BANKJ2] +~g~Todos los objetivos de esta ronda valen un punto. + +[BNK2_2:BANKJ2] +APUNTA, 3, 2, 1... ¡FUEGO! + +[BNK2_3:BANKJ2] +¡ZONA DESPEJADA! + +[BNK2_4:BANKJ2] +¡Yupiiiiiii! + +[BNK2_5:BANKJ2] +¡No podrías darle ni a un elefante a 2 metros! + +[BNK2_7:BANKJ2] +Entonces, ¿quieres hacerme un favor y ayudarme a preparar un atraco? + +[BNK2_8:BANKJ2] +Hijo, disparando de ese modo, si me pidieras que sea tu mujer, te diría que sí. + +[BNK2_9A:BANKJ2] +Hijo, mejor será que te vayas y te metas tus ideas y palabrería donde te quepan. Eres una mierda disparando. + +[BNK2_9B:BANKJ2] +Eres una mierda disparando. + +[BJM2_28:BANKJ2] +PUNTUACIÓN RONDA TRES: ~1~ + +[BJM2_20:BANKJ2] +~g~¡La ronda se acabará cuando te quedes sin ~w~tiempo ~g~o ~w~munición~g~! + +[BJM2_26:BANKJ2] +~g~El objetivo más lejano vale tres puntos. + +[BNK2_1:BANKJ2] +MUNICIÓN CARGADA + +[RANGE_1:BANKJ2] +PUNTUACIÓN POR DISPARO: ~1~ + +[BJM2_2:BANKJ2] +~g~Para abandonar la ronda, pulsa ~h~~k~~PED_JUMPING~. + +[BJM2_N:BANKJ2] +Tranqui. + +{=================================== MISSION TABLE BANKJ3 ===================================} + +[BJM3_A:BANKJ3] +Las cosas empiezan a ir bien. + +[BJM3_B:BANKJ3] +¿Cuál es el plan, Tommy? ¿Qué pasa, amigo? + +[BJM3_C:BANKJ3] +El plan es que tú sigas haciendo el imbécil. En fin, necesitamos a un conductor. + +[BJM3_D:BANKJ3] +Tommy, yo lo haré. Sé conducir. + +[BJM3_E:BANKJ3] +Necesitamos a Hilary, señor, no a un picapleitos charlatán. + +[BJM3_F:BANKJ3] +Hilary es nuestra mejor opción. Seguro que no has visto nunca conducir a nadie tan rápido. Voy a darle un toque. + +[BJM3_G:BANKJ3] +Eh, Hil, soy Phil. ¿Cómo te va? No, no hables. Recordaremos los viejos tiempos más tarde. ¿Quieres hacerme un favor? + +[BJM3_H:BANKJ3] +Tengo a un tipo del norte... No, no, no creo que sirviese en el ejército, pero quiere un conductor. + +[BJM3_I:BANKJ3] +Por un poco de acción. De acuerdo, comprendo. + +[BJM3_J:BANKJ3] +¿Qué dijo? + +[BJM3_K:BANKJ3] +Bueno, lo hará, no hay problema. Bueno, podría haber un pequeño problema... Verás, tiene miedo al abandono. + +[BJM3_L:BANKJ3] +Y parece que no trabajará para nadie que no pueda derrotarle. Tiene algo que ver con su madre o algo así. + +[BJM3_M:BANKJ3] +De todas formas primero quiere echarte una carrera, dice que se encontrará contigo fuera... + +[BJM3_2A:BANKJ3] +¿Eres Tommy? Por supuesto que eres Tommy, quiero decir, + +[BJM3_2B:BANKJ3] +¿quién más querría hablar conmigo? + +[BJM3_2C:BANKJ3] +Vale. Esto es lo que hay. + +[BJM3_2D:BANKJ3] +Conduciré para ti solamente si me demuestras que conduces bien. + +[BJM3_2E:BANKJ3] +Déjame tirado, y nunca te perdonaré. + +[BJM3_2:BANKJ3] +~r~¡Hilary está muerto! + +[BJM3_4:BANKJ3] +~g~¡Necesitas un coche para participar! + +[BNK3_1:BANKJ3] +Vale. Te llevaré pero por favor, machácame un poco. + +[BNK3_3A:BANKJ3] +Carrera callejera ilegal en progreso en Vice Point. + +[BNK3_3B:BANKJ3] +Llamando a todos los agentes. + +[BNK3_3C:BANKJ3] +Corredores callejeros, ¡esto es ilegal y no está permitido! + +{=================================== MISSION TABLE BANKJ4 ===================================} + +[BNK4_A:BANKJ4] +~w~Como podéis ver, caballeros, éste va a ser el dinero más fácil que hayamos ganado jamás. + +[BNK4_B:BANKJ4] +~w~Tommy, de verdad, tienes que plantearte seriamente el meterte en la abogacía. + +[BNK4_C:BANKJ4] +~w~Pero, ¿qué coño te estás fumando, tío? ¡Este no es un plan sencillo! + +[BNK4_D:BANKJ4] +~w~Bueno, de todas formas, ¿quién necesita un plan sencillo? + +[BNK4_E:BANKJ4] +~w~Mira el comunismo, bien, pues ese era un plan sencillo. Y jodió bien a Rusia ¿no? + +[BNK4_F:BANKJ4] +~w~Cálmate, ¿vale? Con un equipo como éste, no va a haber problemas. + +[BNK4_G:BANKJ4] +~w~Tenemos a Cam en la caja fuerte. ¿Phil? Tú y yo nos encargaremos de la seguridad y Hilary conducirá el coche de huída. + +[BNK4_H:BANKJ4] +~w~Uh, je, je, ¿no te olvidas de alguien? ¿Alguien que te ayudó siempre en esta ciudad? ¿Alguien...? + +[BNK4_I:BANKJ4] +~w~Ken... Ken, es cierto. Aquí Ken, él nos lavará el dinero y mantendrá las bebidas frías. + +[BNK4_J:BANKJ4] +~w~No comprendo qué se supone que debo hacer aquí. + +[BNK4_K:BANKJ4] +~w~Mira, es sencillo. ¿No has visto nunca una película? + +[BNK4_L:BANKJ4] +~w~Entramos en el banco, enseñamos las armas y nos marchamos siendo hombres muy ricos. + +[P_DEAD:BANKJ4] +~r~¡Phil está muerto! + +[C_DEAD:BANKJ4] +~r~¡Cam está muerto! + +[H_DEAD:BANKJ4] +~r~¡Hilary está muerto! + +[P_HIND:BANKJ4] +~r~¡Has perdido a Phil! + +[C_HIND:BANKJ4] +~r~¡Cam se ha quedado atrás! + +[H_HIND:BANKJ4] +~r~¡Hilary ha sido abandonado! + +[GETCAR:BANKJ4] +~g~¡Sube en el coche de escapada para hacer el trabajito del banco! + +[TRASHED:BANKJ4] +~r~¡DESTROZASTE EL COCHE DE FUGA! + +[BNK4_1:BANKJ4] +Yo conduciré. + +[BNK4_2:BANKJ4] +Genial. Un pasajero. Espera a que les hable de esto al grupo. + +[BNK4_3A:BANKJ4] +¡Eh, cuidado con las ruedas, Tommy! + +[BNK4_3B:BANKJ4] +¡Tommy, Hilary está ocupando mucho espacio! + +[BNK4_3C:BANKJ4] +¡No es verdad! + +[BNK4_3D:BANKJ4] +¡Sí! + +[BNK4_3E:BANKJ4] +Eh, callaos los dos o iréis andando. + +[BNK4_3F:BANKJ4] +Sí, Hilary. + +[BNK4_3I:BANKJ4] +Por el amor de Dios, Phil, deja de agitar esa cosa. + +[BNK4_3J:BANKJ4] +¡Sí, le sacarás un ojo a alguien! + +[BNK4_3M:BANKJ4] +¡Mi pequeña! ¡Está hecha un desastre! + +[BNK4_3O:BANKJ4] +Te aferras a la ilusión de una permanencia perpetua... + +[BNK4_3P:BANKJ4] +¿Qué? + +[BNK4_3Q:BANKJ4] +Crees que todas las cosas durarán. + +[BNK4_3R:BANKJ4] +La juventud, los seres queridos, la pizza... + +[BNK4_3S:BANKJ4] +Todo pasará o terminará y debes aceptar eso. + +[BNK4_3T:BANKJ4] +Sí, tienes razón. Gracias, Cam. + +[BNK4_3U:BANKJ4] +No hay de que. + +[BNK4_3V:BANKJ4] +Eh, Tommy, ¿por qué nos hemos detenido? + +[BNK4_4A:BANKJ4] +~w~Hilary, sigue conduciendo en torno a la manzana. + +[BNK4_5:BANKJ4] +~w~De acuerdo, Tommy, de acuerdo. + +[BNK4_6:BANKJ4] +~w~¡ESTO ES UN ATRACO! + +[BNK4_7:BANKJ4] +~w~¡QUE NADIE SE MUEVA! + +[BNK4_8:BANKJ4] +~w~¡TODO EL MUNDO CONTRA ESA PARED! + +[BNK4_9:BANKJ4] +¡Phil, quédate vigilando! + +[BNK4_10:BANKJ4] +¡No problemo! + +[BNK4_11:BANKJ4] +Vamos, Cam, la cámara acorazada está arriba... + +[BK4_12A:BANKJ4] +¡Maldición! ¡Es una Flange 9000! + +[BK4_12B:BANKJ4] +Podría tardar horas en abrirla, + +[BK4_12C:BANKJ4] +o cinco minutos si encuentras al director. + +[BNK4_13:BANKJ4] +Iré a averiguar en qué agujero se ha metido. + +[BK4_14A:BANKJ4] +Phil, ¿van bien las cosas? + +[BNK4_15:BANKJ4] +Sí. Todo está muy tranquilo. + +[BNK4_16:BANKJ4] +¡Tú, ven conmigo! + +[BNK4_17:BANKJ4] +¡De acuerdo! ¡De acuerdo! ¡Por favor, no dispare! + +[BNK4_18:BANKJ4] +¡DIJE QUE NO SE MOVIESE NADIE! + +[BK4_19A:BANKJ4] +Tiene un cierre temporizado. + +[BK4_19B:BANKJ4] +¡Podríais rendiros ahora mismo! + +[BK4_20A:BANKJ4] +Diablos, puedo hacer un puente en el cierre temporizado, + +[BK4_20B:BANKJ4] +¡Así que solo necesitaremos tu código de acceso y ya está! + +[BNK4_21:BANKJ4] +Quédate aquí. Intenta algo y serás comida para los peces. ¿Lo captas? + +[BNK422A:BANKJ4] +Cam, ¿cuánto tiempo? + +[BK4_23A:BANKJ4] +¡Dame cinco minutos! + +[BK4_24A:BANKJ4] +Voy a comprobar qué tal va Phil, volveré. + +[BK4_24B:BANKJ4] +¡Te dije que no tocases esa alarma! + +[BNK4_25:BANKJ4] +¡Los de operaciones especiales estarán aquí en cualquier momento! + +[BNK4_27:BANKJ4] +¡Me vendría bien un poco de ayuda aquí, Tommy! + +[BNK4_28:BANKJ4] +¡SWAT de Vice City! ¡Estáis rodeados! + +[BNK4_29:BANKJ4] +¿Rodeados? + +[BNK4_30:BANKJ4] +La están cagando, ¡bastardos corruptos! + +[BK4_31A:BANKJ4] +¡Tommy! ¡La cámara acorazada está abierta! + +[BK4_34A:BANKJ4] +De acuerdo, tenemos el fondo de pensiones de los SWAT. ¡Salgamos de aquí! + +[BK4_34B:BANKJ4] +¡De acuerdo, vosotros lo habéis pedido! ¡Habéis tenido vuestra última oportunidad! + +[BK4_35A:BANKJ4] +¡Van a arrasar el lugar! + +[BK4_35B:BANKJ4] +¡A cubierto! + +[BNK4_94:BANKJ4] +~w~De acuerdo chicos. Ha sido tan fácil como lo habíamos planeado. + +[BM_DEAD:BANKJ4] +~r~¡Necesitas al director del banco vivo! + +[ASSET_A:BANKJ4] +¡ATRACO AL BANCO COMPLETADO! + +[ASSET_B:BANKJ4] +~g~El Club Malibú generará ahora unos ingresos máximos de hasta ~1~ $ al día. ¡Asegúrate de recaudarlos regularmente! + +[IDIOT:BANKJ4] +~r~ Eso es, ve por ahí vestido como un chiflado y llamando la atención, ¡IDIOTA! + +{=================================== MISSION TABLE BARON1 ===================================} + +[COK1_A:BARON1] +Vamos nena, vamos, sí, ¡sí! + +[COK1_B:BARON1] +¡Estúpido caballo! ¡Te cortaré la cabeza! + +[COK1_C:BARON1] +¿Quién es este gilipollas? + +[COK1_D:BARON1] +Tommy Vercetti, ¿me recuerdas? + +[COK1_E:BARON1] +Perdóname, estoy un poco alterado. ¡Nunca te fíes de un maldito caballo! + +[COK1_F:BARON1] +Hiciste un buen trabajo... ahora trabajas para mí. + +[COK1_H:BARON1] +Como dije amigo, trabajas para mí. Ahora, cállate. Algún Judas me ha traicionado. + +[COK1_I:BARON1] +Él cree que no sé cuando dinero debería sacar con esto, pero robar un 3% es tan malo como robar el 100%. + +[COK1_J:BARON1] +Nadie me hace esto a mí. ¡NADIE! + +[COK1_K:BARON1] +¡Le sigues desde su apartamento y ves a dónde va! Más tarde, le mataremos. + +[COK1_1:BARON1] +¡Mierda! + +[COK1_2:BARON1] +¡Demasiado lento, abuelo! + +[COK1_4:BARON1] +Perdedor. + +[COK1_5:BARON1] +¡Mejor sigue corriendo, gilipollas! + +[COK1_8:BARON1] +~g~¡Rápido! ¡Consigue un vehículo y síguele! + +[COK1_9:BARON1] +~r~¡Se supone que debes seguirle, no matarle! + +[COK1_10:BARON1] +~r~Ve a la casa del ladrón y descubre dónde esconde el dinero. + +[COK1_11:BARON1] +~g~Echa un vistazo por su ventana. + +[COK1_7:BARON1] +~g~¡Se ha escapado por el tejado, ve tras él pero no le mates! + +[COK1_G:BARON1] +Trabajo por dinero. + +{=================================== MISSION TABLE BARON2 ===================================} + +[COK2_A:BARON2] +¿Qué clase de tonto incompetente eres? + +[COK2_B:BARON2] +¡TONTO! ¡TONTO! ¡TONTO! ¡TONTO! + +[COK2_C:BARON2] +Tommy... + +[COK2_D:BARON2] +¿Qué pasa, Ricardo? + +[COK2_E:BARON2] +Estos idiotas... Siempre intentan joderle a uno. + +[COK2_F:BARON2] +Ese es el problema de este negocio. + +[COK2_G:BARON2] +¿Qué crees que estás haciendo? + +[COK2_H:BARON2] +Estos mamones me han fallado miserablemente. + +[COK2_I:BARON2] +Pronto cualquier madre o padre creerán que pueden vender farlopa en Vice City. + +[COK2_J:BARON2] +¿Y ahora qué, eh? ¿La apestosa Mafia? + +[COK2_K:BARON2] +Ese antro de mafiosos es una fortaleza inexpugnable desde el suelo, + +[COK2_L:BARON2] +así que Quentin... ¡Quentin! ¡QUENTIN! + +[COK2_M:BARON2] +¡Volará sobre la zona! + +[COK2_N:BARON2] +¡Hacedles desaparecer! + +[COK2_O:BARON2] +¿Qué crees que estás haciendo? + +[COK2_P:BARON2] +¿Qué haces aquí? + +[COK2_Q:BARON2] +He estado haciendo preguntas por ahí, y es obvio + +[COK2_R:BARON2] +que Diaz se saltó el trato y se cargó a mi hermano. + +[COK2_S:BARON2] +¡Y te matará a ti también! + +[COK2_T:BARON2] +¡Puedo encargarme de Diaz! + +[COK2_U:BARON2] +No. ¡Escúchame! Yo me encargaré de Diaz... + +[COK2_V:BARON2] +Está empezando a confiar en mí. + +[COK2_1:BARON2] +Hay una cosa que me mosquea... ¿por qué Quentin? + +[COK2_2:BARON2] +No sé, siempre me ha gustado... Quentin Vance... + +[COK2_3:BARON2] +¿Vance? ¿Tu nombre es Lance Vance? + +[COK2_4:BARON2] +¡Eh! ¡Ya tuve bastante con eso en la escuela! + +[COK2_5:BARON2] +¿Alguna vez has disparado uno de esos desde un helicóptero? + +[COK2_8:BARON2] +En fin, ¿a dónde nos dirigimos? + +[COK2_9:BARON2] +A Prawn Island. + +[COK2_13:BARON2] +Lance Vance. Pobre desgraciado. + +[COK2_14:BARON2] +Bien, casi hemos llegado. + +[COK2_15:BARON2] +Daremos un par de pasadas. + +[COK2_16:BARON2] +Así que elimina a tantos como puedas. + +[COK2_17:BARON2] +Después te dejaré abajo y te las arreglarás solo. + +[COK2_20:BARON2] +¡Mierda! ¡Ésto es una zona de guerra! ¡Elimina a algunos de esos pistoleros! + +[COK2_21:BARON2] +¡Nos están alcanzando, tío! + +[COK2_22:BARON2] +¡Arreglar esta cosa no sale barato! ¡Elimínalos! + +[COK2_23:BARON2] +Vale, de ahora en adelante es cosa tuya... ¡Buena suerte, hermano! + +[COK2_24:BARON2] +Helicóptero: + +[COK2_25:BARON2] +~g~Ve y recoge el dinero del tejado. + +[COK2_27:BARON2] +¡Estás en mi territorio, gilipollas! + +[COK2_28:BARON2] +¡Vas a desaparecer! + +[COK2_6:BARON2] +No. Practicaré un poco por el camino. + +[OPEN_B:BARON2] +Los controles policiales con dirección a la península han sido levantados + +{=================================== MISSION TABLE BARON3 ===================================} + +[COK3_A:BARON3] +Ahora ya no os sentís tan complacidas, ¿eh? + +[COK3_B:BARON3] +~n~ + +[COK3_C:BARON3] +¡Guau! Mira hacia donde mueves esa cosa. + +[COK3_D:BARON3] +Ya no habrá más mierda de paloma en mi coche, ¿eh, Tommy? + +[COK3_E:BARON3] +Supongo que no. + +[COK3_F:BARON3] +Tienes toda la razón. Ahora escucha, + +[COK3_G:BARON3] +¿sabes quién posee el barco más rápido de la costa este? + +[COK3_H:BARON3] +Ni idea. + +[COK3_I:BARON3] +Yo. Y quiero que siga siendo así. + +[COK3_J:BARON3] +Cada traficante de aquí a Caracas tiene un sueño, un barco más rápido. + +[COK3_K:BARON3] +Se rumorea que el astillero acaba de terminar un barco así + +[COK3_L:BARON3] +para algún mamón costarricense. + +[COK3_M:BARON3] +Y Tommy... ¡QUIERO ESE BARCO! + +[COK3_N:BARON3] +Creo que han vuelto tus palomas. + +[COK3_O:BARON3] +¡Ah! Pensé que te tenía. ¿De dónde has salido? + +[COK3_P:BARON3] +¡Palomas! ¡Boom! ¡Aaaaah! + +[COK3_5:BARON3] +~g~Localiza el interruptor para bajar el barco. + +[COK3_6:BARON3] +~g~Lleva el barco a la mansión. + +[COK3_7:BARON3] +~r~¡Destruiste el barco! + +[COK3_8:BARON3] +~g~Ve al astillero, en los muelles y roba el barco más rápido. + +[COK3_9:BARON3] +~g~Ahora sube al barco. + +{=================================== MISSION TABLE BARON4 ===================================} + +[COK4_A:BARON4] +¡Expúlsala! ¡MIERDA DE PLÁSTICO! + +[COK4_B:BARON4] +¿Por qué me haces esto? + +[COK4_C:BARON4] +¿Quién te crees que eres, maldita mierda de plástico? + +[COK4_D:BARON4] +¡QUE TE JODAN! + +[COK4_E:BARON4] +Se traga mi película favorita de El burro, ¡se muere! + +[COK4_F:BARON4] +¿Qué más podría haber hecho? + +[COK4_G:BARON4] +Probablemente no estaba enchufado. + +[COK4_H:BARON4] +¿Qué? + +[COK4_I:BARON4] +Maldición... No importa, puedo comprar cien más. + +[COK4_J:BARON4] +Ahora, Tommy, + +[COK4_K:BARON4] +cada mes un camello autónomo navega hasta Vice City y amarra su yate. + +[COK4_L:BARON4] +Vende su partida al primer barco. + +[COK4_M:BARON4] +Quiero que cojas la lancha motora + +[COK4_N:BARON4] +y que elimines a todos los otros cabrones, + +[COK4_O:BARON4] +luego me traes aquí el alijo, ¿entendido? + +[COK4_P:BARON4] +Déjame adivinar, pensaste que me podía venir bien un ángel guardián. + +[COK4_Q:BARON4] +Solo digo que tienes que dejarme participar, tío. + +[COK4_R:BARON4] +Ahora ya puedes contarme toda esa basura del 'chico duro solitario', + +[COK4_S:BARON4] +pero sé que un día te voy a salvar el culo + +[COK4_T:BARON4] +¡y probablemente querrás darme un beso! + +[COK4_U:BARON4] +Colgado. + +[COK4_V:BARON4] +~n~ + +[COK4_1:BARON4] +Así que Tommy, sabemos que fue Díaz quien jodió nuestro trato... + +[COK4_3:BARON4] +Así que, ¿por qué coño vamos por ahí haciéndole recados? + +[COK4_4:BARON4] +Cuanto más aprendamos ahora, ¡menos tendremos que aprender cuando tomemos el control de esta ciudad! + +[COK4_5:BARON4] +Me encanta tu estilo, tío. Muy fresco. + +[COK4_12:BARON4] +¡Cuidado, vienen de todas partes! + +[COK4_13:BARON4] +Ya está. ¡Dirígete hacia Díaz tan rápido como puedas! + +[COK4_14:BARON4] +¿Queréis un poco de esto? + +[COK4_15:BARON4] +¡A dormir con los peces! + +[COK4_16:BARON4] +¡Comed! ¡COMED! + +[COK4_19:BARON4] +¡Más problemas ahí adelante! + +[COK4_20:BARON4] +¡Hay más pistoleros en el malecón! + +[COK4_24:BARON4] +Buen disparo, amigo mío. Eres un verdadero y auténtico lunático de categoría A. + +[COK4_25:BARON4] +Bueno, gracias. + +[COK4_26:BARON4] +Nos vemos, Tommy. + +[COK4_27:BARON4] +Vale, Sr. Lance Vance. + +[COK4_28:BARON4] +~g~¡Ve hasta el yate antes de que lleguen los otros barcos! + +[COK4_31:BARON4] +~g~¡Ve al barco más rápido del embarcadero! + +[COK4_32:BARON4] +~r~¡Demasiado lento! + +[COK4_33:BARON4] +~r~¡Has destruido el barco! + +[COK4_34:BARON4] +~g~¡Hunde esos barcos! + +[COK4_35:BARON4] +Estado del barco: + +{=================================== MISSION TABLE BARON5 ===================================} + +[PROP_A:BARON5] +¡PROPIEDAD ADQUIRIDA! + +[COK4_30:BARON5] +~r~¡Lance está muerto! + +[ASS1_A:BARON5] +He conseguido unos juguetes nuevos. Están en el maletero. + +[ASS1_B:BARON5] +¡Mierda! ¿Dónde conseguiste todo este material? + +[ASS1_C:BARON5] +Lo he estado guardando para un momento como éste. + +[ASS1_D:BARON5] +¿Te gusta? + +[ASS1_E:BARON5] +Sí, me gusta. + +[ASS1_F:BARON5] +Estúpidos mamones... + +[ASS1_G:BARON5] +Mi hermosa casa. + +[ASS1_H:BARON5] +¡Mirad lo que le habéis hecho! + +[ASS1_I:BARON5] +¡Esto es por mi hermano! + +[ASS1_J:BARON5] +Confiaba en ti, Tommy. + +[ASS1_K:BARON5] +Te hubiese convertido en... + +[ASS1_L:BARON5] +Diga buenas noches, Sr. Díaz. + +[ASS1_1:BARON5] +Este lugar va a estar hasta arriba de capullos... ten cuidado... + +[ASS1_2:BARON5] +No te preocupes Tommy, te cubro. + +[ASS1_13:BARON5] +¡DÍAZ! ¡He venido a encargarme de tu negocio! + +[ASS1_14:BARON5] +¡TOMMY! Me has traicionado... ¡Imbécil! Voy a acabar contigo muy pronto... + +[ASS1_15:BARON5] +~g~¡Arrasa esa mansión y mata a Diaz! + +[ASS1_16:BARON5] +~g~¡Mata a Diaz! + +[ASS1_17:BARON5] +~g~Hay múltiples caminos para entrar en la mansión. + +[BUD1:BARON5] +Lance + +[ASS1_18:BARON5] +~g~La puerta está cerrada con llave, intenta otra ruta. + +[ASS1_19:BARON5] +¡Por aquí! + +[ASS1_20:BARON5] +¡Tommy, mi problema es con Quentin, no contigo, tío! + +{=================================== MISSION TABLE BIKE1 ===================================} + +[BM1_A:BIKE1] +¿Dónde está Baker? + +[BM1_B:BIKE1] +Estoy buscando al gran Mitch Baker... + +[BM1_C:BIKE1] +¿Quién le busca? + +[BM1_D:BIKE1] +Tommy Vercetti. + +[BM1_E:BIKE1] +Vercetti. + +[BM1_F:BIKE1] +No pareces policía, así que eso te concede un minuto. + +[BM1_G:BIKE1] +Habla rápido. + +[BM1_H:BIKE1] +Kent Paul dijo que podrías estar interesado en llevar la seguridad de una gira que él ha organizado. + +[BM1_I:BIKE1] +¿Kent Paul? No me extraña que te enviase en su lugar. + +[BM1_J:BIKE1] +La última vez que estuvo aquí, se marchó por la ventana sin nada, tal cual le trajo su madre al mundo. + +[BM1_K:BIKE1] +¿Estás interesado o no? + +[BM1_L:BIKE1] +Sólo hacemos favores a los nuestros. + +[BM1_M:BIKE1] +¿Cómo puedo unirme a vosotros? + +[BM1_N:BIKE1] +Este no es un club de campo, chico. ¿Sabes montar en moto? + +[BM1_O:BIKE1] +¿Sabes sentarte en un taburete y beber? + +[BM1_P:BIKE1] +Cougar, Zeppelin, id a ver cómo se le da la moto a esta nenaza... + +[BM1_2:BIKE1] +~g~¡Necesitas una Freeway o una Ángel para competir! + +[BM1_3:BIKE1] +~r~¡Los corredores han sido atacados! + +[BIKE1_1:BIKE1] +Bien, modelitos caros. Veamos qué puedes hacer. + +[BM1_1:BIKE1] +~g~Consigue una Freeway o una Ángel y ve a la parrilla de salida. + +{=================================== MISSION TABLE BIKE2 ===================================} + +[BM2_1:BIKE2] +CAOSMETRO: + +[BM2_A:BIKE2] +Te he vuelto a ganar. + +[BM2_B:BIKE2] +Eh, Vercetti. + +[BM2_C:BIKE2] +Cougar dice que sabes manejar muy bien una moto. + +[BM2_D:BIKE2] +Sí, ¿cuántos recados más voy a tener que hacer? + +[BM2_E:BIKE2] +Soy un hombre muy ocupado. + +[BM2_F:BIKE2] +Si es un combate lo que va arreglarlo todo, entonces adelante. + +[BM2_G:BIKE2] +Ser uno de nosotros no va solo de fanfarronear. Va de ser parte de una familia. + +[BM2_H:BIKE2] +Sí, he sido parte de una familia antes. No funcionó. + +[BM2_I:BIKE2] +Sí, seguro, pero esta familia cuida de los suyos. + +[BM2_J:BIKE2] +No le pedimos a un hombre que haga el trabajo sucio y luego le dejamos pasar quince años de sequía. + +[BM2_K:BIKE2] +Sí, es verdad. He hecho mis deberes. + +[BM2_L:BIKE2] +Ésta de aquí es la familia más grande de inadaptados, rebeldes y tipos duros. + +[BM2_M:BIKE2] +Diablos, algunos de nosotros incluso hemos sido traicionados por nuestro propio país. + +[BM2_N:BIKE2] +Estuve preso durante Vietnam. Un tema desagradable. + +[BM2_O:BIKE2] +Por eso es por lo que voy a pedirte que vayas a armar jaleo. + +[BM2_P:BIKE2] +Este maldito país necesita una patada en el culo, y nosotros se la vamos a dar. + +[BM2_Q:BIKE2] +¡Así que sal de ahí, pilla una moto, demuestra a esta ciudad lo cabreado que estás! + +[BM2_R:BIKE2] +De acuerdo. + +[BM2_2:BIKE2] +~g~¡Debes llenar el Caosmetro en el tiempo determinado para demostrarnos lo duro que eres! + +[BM2_3:BIKE2] +~g~Este sonido indica que has llenado una parte del contador, continúa así. + +[BM2_4:BIKE2] +~r~¡Has fracasado en llenar el Caosmetro a tiempo! + +{=================================== MISSION TABLE BIKE3 ===================================} + +[BM3_A:BIKE3] +Hola, Mitch. + +[BM3_B:BIKE3] +Bueno, si es el ''tipo duro'' Vercetti. + +[BM3_C:BIKE3] +Ahora quiero ver lo bien que sabes luchar por tu territorio. + +[BM3_D:BIKE3] +Una banda callejera local cometió el error de robar mi moto, + +[BM3_E:BIKE3] +probablemente para hacerse los machos o algo así. + +[BM3_F:BIKE3] +Los chicos y yo iríamos a darles una lección sobre el respeto a las propiedades ajenas y todo eso. + +[BM3_G:BIKE3] +En fin... + +[BM3_H:BIKE3] +Entonces empecé a pensar que esto sería una buena iniciación para ti. + +[BM3_I:BIKE3] +Devuélveme mi moto y puedes decirle a Paul que tiene su seguridad. + +[BM3_2:BIKE3] +~r~¡Se suponía que debías traer de vuelta la moto, no destruirla! + +[BM3_3:BIKE3] +~g~¡Lleva la moto de vuelta al bar! + +[BM3_4:BIKE3] +~g~¡Sube a la moto! + +[INTRUDE:BIKE3] +~g~¡Te han visto! + +[BM3_6:BIKE3] +~g~Están refugiados detrás de Ammu-Nation en la zona del centro. + +[BM3_7:BIKE3] +~g~Necesitarás una moto rápida para conseguir acceder al tejado. + +[BM3_8:BIKE3] +~g~Utiliza la moto para saltar desde esas escaleras hasta el tejado que hay en el extremo opuesto de la carretera. + +[BM3_1:BIKE3] +~g~Una banda local ha robado la Ángel de Mitch Baker. ¡Recupérala! + +[BM3_9:BIKE3] +~g~¡Recupera la Ángel de Mitch y sal de ahí! + +{=================================== MISSION TABLE BMX_1 ===================================} + +[GETBIK2:BMX_1] +¡Tienes ~1~ segundos para subirte a una moto de motocross! + +{=================================== MISSION TABLE BOATBUY ===================================} + +[DRUG_1:BOATBUY] +¿Hola? + +[DRUG_2:BOATBUY] +Apágalo. Hay un tipo ahí afuera. + +[DRUG_3:BOATBUY] +¡Eh, el tipo del traje! Supongo que eres el nuevo propietario. + +[DRUG_4:BOATBUY] +Sí. ¿Cuál de los barcos es el más rápido? + +[DRUG_5:BOATBUY] +Ya está en el agua, tío, + +[DRUG_6:BOATBUY] +Pensé que querrías probarlo. + +[DRUG_7:BOATBUY] +Tío, funciona con un motor de 300 caballos... + +[DRUG_8:BOATBUY] +y el casco es de fibra de vidrio, ¡sale disparado por las olas! + +[DRUG_9:BOATBUY] +Puede ponerse de cero a noventa tan sólo en cuatro segundos, tío... + +[DRUG_10:BOATBUY] +¡y puede llevar como veinte contenedores de la mejor hierba jamaicana justo en el casco! + +[DRUG_11:BOATBUY] +Así que adelante, tío, ¡está preparada para volar! + +[DRUG_12:BOATBUY] +Tipo trajeado, ¿tienes fuego? + +[DRUG_13:BOATBUY] +¿Colega? + +{=================================== MISSION TABLE CAP_1 ===================================} + +[CAP1_B1:CAP_1] +~g~La mafia está imponiendo impuestos a tus negocios. Encuéntrales y mátales. + +[CAP1_B2:CAP_1] +~g~¡La mafia ha saqueado el astillero! + +[CAP1_B3:CAP_1] +~g~¡La mafia ha saqueado la fábrica de helados! + +[CAP1_B4:CAP_1] +~g~¡La mafia ha saqueado el salón de autos! + +[CAP1_B5:CAP_1] +~g~¡La mafia ha saqueado la compañía de taxis! + +[CAP_01:CAP_1] +¿Cuál es la emergencia? + +[CAP_02:CAP_1] +¿QUIÉN? + +[CAP_03:CAP_1] +Tommy... unos matones mafiosos... dijeron que vendrían a recoger su parte... + +[CAP_04:CAP_1] +Dijeron que era dinero del Sr. Forello... me siento una basura. + +[CAP_05:CAP_1] +¿Forelli? ¿SONNY Forelli? + +[CAP_06:CAP_1] +Sí, ése es el tipo... creo... insistieron mucho... + +[CAP_07:CAP_1] +No te preocupes, abuelo, no estoy enfadado contigo. + +[CAP_08:CAP_1] +Llevadle al hospital. + +[CAP_09:CAP_1] +Tommy... rájale un nuevo culo a ese tipo por mí... + +[CAP_10:CAP_1] +Voy a rajarle en dos. + +[CAP1_2:CAP_1] +¡Conoces las reglas, Vercetti! + +[CAP1_3:CAP_1] +¡El Sr. Forelli envía sus saludos! + +[CAP1_4:CAP_1] +¡Es el carnicero de Harwood! + +[CAP1_5:CAP_1] +Decidle a Sonny... ¡Que permanezca alejado! + +[CAP1_6:CAP_1] +Vice City ahora es MÍA, NO suya. + +[CAP1_7:CAP_1] +¿Crees que puedes eliminarme, Vercetti? + +[CAP1_8:CAP_1] +Seguiremos yendo tras de ti hasta que mueras, Vercetti. + +[CAP1_9:CAP_1] +No tienes ni una oportunidad, mamón psicótico. + +[CAP1_10:CAP_1] +Te mataré, Vercetti. + +[CAP1_11:CAP_1] +Siempre fuiste un gilipollas. + +[CAP1_12:CAP_1] +Vas a morir, Vercetti. + +[CAP1_B6:CAP_1] +~g~Has encontrado al cobrador, remátale. + +[CAP1_B7:CAP_1] +~g~Has perdido al cobrador. + +[CAP1_B8:CAP_1] +~r~El cobrador ha saqueado todos tus negocios. + +[CAP1_B9:CAP_1] +~g~¡La mafia ha saqueado a El Malibu! + +[CAP1_B0:CAP_1] +~g~¡La mafia ha saqueado al estudio cinematográfico! + +[CAP1_C2:CAP_1] +~g~¡La mafia ha llegado al astillero! + +[CAP1_C3:CAP_1] +~g~¡La mafia ha llegado a la fábrica de helados! + +[CAP1_C4:CAP_1] +~g~¡La mafia ha llegado al concesionario de coches! + +[CAP1_C5:CAP_1] +~g~¡La mafia ha llegado a la compañía de taxis! + +[CAP1_C9:CAP_1] +~g~¡La mafia ha llegado al Malibú! + +[CAP1_C0:CAP_1] +~g~¡La mafia ha llegado al estudio cinematográfico! + +[CAP1_D2:CAP_1] +~g~¡La mafia abandona el astillero! + +[CAP1_D3:CAP_1] +~g~La mafia abandona la fábrica de helados! + +[CAP1_D4:CAP_1] +~g~¡La mafia abandona el concesionario de coches! + +[CAP1_D5:CAP_1] +~g~¡La mafia abandona la compañía de taxis! + +[CAP1_D9:CAP_1] +~g~¡La mafia abandona el Malibú! + +[CAP1_D0:CAP_1] +~g~¡La mafia abandona el estudio cinematográfico! + +[CAP1B10:CAP_1] +Has puesto límite a los recaudadores. Vienen más de camino. + +{=================================== MISSION TABLE CARBUY ===================================} + +[CAR1_1:CARBUY] +B.J. Smith. Y usted debe ser el Sr. Vercetti. + +[CAR1_2:CARBUY] +¿Le gustaría dar una vuelta? + +[CAR1_3:CARBUY] +Podría ser. + +[CAR1_4:CARBUY] +Estoy muy triste por vender el negocio. + +[CAR1_5:CARBUY] +Esta fue mi primera inversión después de convertirme en profesional. + +[CAR1_6:CARBUY] +Pero es hora de que me mude. + +[CAR1_7:CARBUY] +¿Deja la ciudad? + +[CAR1_8:CARBUY] +No demasiado deprisa, espero. + +[CAR1_9:CARBUY] +No. Me estoy retirando pero simplemente para preparar mi regreso. + +[CAR1_10:CARBUY] +El negocio no era demasiado fuerte, + +[CAR1_11:CARBUY] +y mi equipo quizás se puso demasiado + +[CAR1_12:CARBUY] +creativo con la generación de la riqueza. + +[CAR1_13:CARBUY] +Obviamente, habría podido desmantelar el negocio antes de cederlo. + +[CAR1_14:CARBUY] +Diablos, podría haber quemado el sitio si hubiese querido. + +[CAR1_15:CARBUY] +Esta es tierra para una urbanización de primera. + +[CAR1_16:CARBUY] +Oh, yo no me preocuparía por nada de eso. + +[CAR1_17:CARBUY] +Este lugar parece perfecto. + +[CAR1_18:CARBUY] +Sí, así es, ¿así que entiendo que tenemos un trato? + +{=================================== MISSION TABLE CARPAR1 ===================================} + +[MM_1_A:CARPAR1] +~g~Atraviesa ~y~5 puntos de control ~r~¡SIN ~g~aplastar ningún ~r~CONO! ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[CONE_1:CARPAR1] +~r~¡Aplastaste un cono! + +[MM_1_C:CARPAR1] +~y~PASA POR~g~ un punto de control para activar el temporizador. ~g~Cada punto de control te dará ~y~~1~ SEGUNDOS~g~. + +{=================================== MISSION TABLE COPCAR ===================================} + +[KILLS:COPCAR] +MUERTES: + +[C_BREIF:COPCAR] +~g~Sospechoso visto por última vez en la zona de ~a~. + +[C_PASS:COPCAR] +AMENAZA ELIMINADA: ~1~ $ + +[COPCART:COPCAR] +~g~Tienes ~1~ segundos para volver al vehículo de policía antes de que termine la misión. + +[C_CANC:COPCAR] +~r~¡Misión de justiciero cancelada! + +[C_TIME:COPCAR] +~r~¡Tu tiempo como representante de la ley se ha terminado! + +[C_COMP1:COPCAR] +Has completado el nivel 12 de la misión de justiciero: tu armadura personal máxima ha aumentado a 150. + +[CLEVEL:COPCAR] +Misión de justiciero nivel ~1~ + +{=================================== MISSION TABLE COUNT1 ===================================} + +[CM1_A:COUNT1] +¿Sr. Vercetti? ¿Comprando la vieja imprenta? + +[CM1_B:COUNT1] +Sí, mi viejo solía trabajar con ellas. + +[CM1_C:COUNT1] +Iba a seguirle en el negocio, pero... viví una vida diferente. + +[CM1_D:COUNT1] +¿Planea vender la vieja maquinaria o destruirla? + +[CM1_E:COUNT1] +Estoy pensando que podíamos imprimir algo, un periódico, una revista... + +[CM1_F:COUNT1] +Oh, mierda, hijo, todo eso es mierda de baja calidad. Siempre me ha apetecido imprimir dinero. No es tan difícil. + +[CM1_G:COUNT1] +Ya sabes, llevo haciéndolo a pequeña escala durante años. + +[CM1_H:COUNT1] +¿De verdad? + +[CM1_I:COUNT1] +Claro. Pero necesitamos unas planchas de buena calidad. + +[CM1_J:COUNT1] +¡Por supuesto! Hay un sindicato de la falsificación que opera ya en Florida. + +[CM1_K:COUNT1] +¿Un sindicato? + +[CM1_L:COUNT1] +Sí. He escuchado algunos rumores. + +[CM1_M:COUNT1] +Conozco a un tipo que es bueno con los rumores... + +[CM1_N:COUNT1] +Solía pasar las tardes con él, limpiando los rodillos... + +[CM1_2A:COUNT1] +¡Menudo trasero! + +[CM1_2B:COUNT1] +¡Vale chica, tú te lo pierdes! + +[CM1_2C:COUNT1] +Buenas, figura, ¿cómo va todo? + +[CM1_2D:COUNT1] +¿Qué sabes sobre falsificación? + +[CM1_2E:COUNT1] +Estoy bien, Paul, ¿qué tal tú? + +[CM1_2F:COUNT1] +¡Ven aquí! + +[CM1_2G:COUNT1] +¡Vale! ¡Vale! Obviamente eres un hombre ocupado. + +[CM1_2H:COUNT1] +Todo lo que sé sobre los asuntos turbios es que las tríadas suministran las planchas. + +[CM1_2I:COUNT1] +Tienen una compañía de envíos en los muelles, + +[CM1_2J:COUNT1] +¡el jefe sabrá cuándo vienen las próximas planchas! + +[CM1_2K:COUNT1] +Gracias, Paul. + +[CM1_2L:COUNT1] +¡Cuál es tu problema, lunático! + +[CM1_2M:COUNT1] +¡Ponme otra copa, pronto! + +[CM1_3:COUNT1] +~g~¡Te han visto! + +[CM1_5:COUNT1] +~g~¡Ve a encontrarte con Kent Paul en el Club Malibú! + +[CNT1_1:COUNT1] +¿Quién eres tú? ¡En la cara no! ¡En la cara no! + +[CM1_1:COUNT1] +~g~Ve al barco de la línea charter Libertine en el muelle. + +[CM1_2:COUNT1] +~g~El encargado de los envíos tendrá toda la información necesaria. + +[CNT1_2:COUNT1] +Vale, ¡hablaré! ¡hablaré! + +[CM1_6:COUNT1] +~g~¡Lleva toda la información de vuelta a la imprenta! + +{=================================== MISSION TABLE COUNT2 ===================================} + +[CNT2_B1:COUNT2] +De acuerdo, hoy el mensajero moverá las planchas de los muelles. + +[CNT2_B2:COUNT2] +Voy a interceptarles, coger las planchas, deshacerme de las pruebas, y volver aquí. + +[CNT2_B3:COUNT2] +Dependiendo de lo bien que vaya esto, + +[CNT2_B4:COUNT2] +podemos tener cinco minutos para imprimir el dinero antes de que el sindicato de falsificación nos encuentre, o puede que tengamos todo el año. + +[CNT2_B5:COUNT2] +De cualquier modo, quiero que los verdes salgan de las prensas cinco minutos después de que vuelva. ¿Entendido? + +[CNT2_B6:COUNT2] +No te preocupes, Tommy. Estaremos preparados. + +[CNT2_B7:COUNT2] +Los otros chicos y yo estaremos por aquí en el vecindario, en caso de que necesites que nos ocupemos de cualquier problema. + +[CNT2_B8:COUNT2] +De acuerdo, ¿todo el mundo está tranquilo? De acuerdo. Os veré luego... + +[CNT2_01:COUNT2] +~g~El ~r~mensajero~g~ con las planchas de falsificación llegará en cualquier momento a los ~y~muelles~g~ en un helicóptero. + +[CNT2_02:COUNT2] +~r~El mensajero de las planchas ha huido en el helicóptero. + +[CNT2_03:COUNT2] +~r~¡El mensajero ha llegado a su destino sano y salvo, llegas demasiado tarde! + +[CNT2_04:COUNT2] +~r~¡Has destruido las planchas en la explosión! + +[CNT2_05:COUNT2] +~g~Tienes las planchas de falsificación. Llévalas a la imprenta. + +[CNT2_06:COUNT2] +~g~El mensajero ha muerto y ha dejado caer las planchas, recógelas antes de que lo haga otro. + +[CNT2_07:COUNT2] +~g~Una de las guardias ha recogido las planchas, no la dejes escapar. + +[CNT2_08:COUNT2] +~g~El ~r~mensajero~g~ ha llegado a los muelles con las planchas. + +[CNT2_4:COUNT2] +Negocio privado. ¡No eres bienvenido! + +[CNT2_09:COUNT2] +IMPRENTA CONSOLIDADA + +[CNT2_10:COUNT2] +~g~La imprenta generará ahora unos ingresos hasta un máximo de ~1~ $. Asegúrate que lo recaudas de manera regular. + +[CNT2_11:COUNT2] +~r~¡Las planchas están en el fondo del mar! + +{=================================== MISSION TABLE CUBAN1 ===================================} + +[CUB1_A:CUBAN1] +¿Sí, tío? + +[CUB1_B:CUBAN1] +Eh, tranquilo papito, este hombre es para mí. Tú, ¿tú eres el chico? + +[CUB1_C:CUBAN1] +Oh, sí. Tú eres el chico. Eso creo, ¿sabes? + +[CUB1_D:CUBAN1] +No. No creo que lo sepa. + +[CUB1_E:CUBAN1] +¿Oh, sí? Ven aquí, tipo duro. + +[CUB1_F:CUBAN1] +¿Crees que puedes desafiarme? + +[CUB1_G:CUBAN1] +¿Crees que puedes jugar a ese estúpido juego conmigo? + +[CUB1_H:CUBAN1] +No, creo que ya lo estás haciendo tú lo suficientemente estúpido por los dos. + +[CUB1_I:CUBAN1] +Eh, te llamó tonto, hijo. + +[CUB1_J:CUBAN1] +Y yo le llamé niñita, papá. + +[CUB1_K:CUBAN1] +Mírale, vestido así. + +[CUB1_L:CUBAN1] +¿Qué es esto, un traje de noche? + +[CUB1_M:CUBAN1] +¿Eres alguna clase de tipo duro y vistes como una mujer? + +[CUB1_N:CUBAN1] +También llevas pantis como una mujer, ¿eh? + +[CUB1_O:CUBAN1] +¿Qué tienes contra las mujeres? ¿Prefieres a los hombres, grandullón? + +[CUB1_P:CUBAN1] +¡Me gustan las mujeres! ¡Me gustan las mujeres! ¡Amo a mi madre, chico! + +[CUB1_Q:CUBAN1] +De acuerdo, de acuerdo. Aceptaré tu palabra. + +[CUB1_R:CUBAN1] +¿Sabes conducir, amigo? + +[CUB1_S:CUBAN1] +Sí... como una mujer. + +[CUB1_T:CUBAN1] +Muy gracioso. Me gustas, grandullón. Quizás puedas ayudarme. + +[CUB1_U:CUBAN1] +Quizás puedas demostrar que eres un hombre. ¿Eh? + +[CUB1_V:CUBAN1] +Saca el barco. + +[CUB1_W:CUBAN1] +Demuéstrame que tienes un par de cojones, + +[CUB1_X:CUBAN1] +y no unos chiquitos de ratón. + +[CUB1_02:CUBAN1] +De acuerdo, tío, trátalo como a una mujer. + +[CUB1_03:CUBAN1] +No está mal, un hombre de verdad. + +[CUB1_04:CUBAN1] +Tío, tienes unas pelotas bien grandes, amigo. + +[CUB1_05:CUBAN1] +Amigo, eres un tío. + +[CUB1_06:CUBAN1] +¿Puedes decir que eres un machote, tío? + +[CUB1_07:CUBAN1] +Eres un ratón asustado, niñito, vete a llorar con tu mamá. + +[CUB1_08:CUBAN1] +Eres una bazofia. Andas como un tío, hablas con un tío, pero conduces como un idiota. + +[CUB1_09:CUBAN1] +Brother, eres un macho. Me gustas, tío. Me gustas un montón. + +[CUB1_10:CUBAN1] +En cualquier momento, tío. Porque tienes un par. Y todos mis amigos tienen un buen par de cojones. + +[CUB1_11:CUBAN1] +~r~¡Mataste a Rico! + +[CUB1_12:CUBAN1] +Atraviesa el primer punto de control para comenzar la prueba. + +[CUB1_14:CUBAN1] +¡Vuelve al bote! + +[CUB1_15:CUBAN1] +~r~Brother, eres demasiado lento. + +[CUB1_01:CUBAN1] +Eh, soy Rico. ¿Eres tú el tío de las pelotas grandes? + +[CUB1_13:CUBAN1] +~g~Tienes tres minutos para recorrer el circuito. + +{=================================== MISSION TABLE CUBAN2 ===================================} + +[CUB2_A:CUBAN2] +Un cafetito, por favor, Alberto... + +[CUB2_N:CUBAN2] +Sin problema, Tommy. + +[CUB2_B:CUBAN2] +¡Papá! ¡Un gran problema! + +[CUB2_O:CUBAN2] +Umberto, mi hijo, ¿qué ocurre? + +[CUB2_C:CUBAN2] +¡Los haitianos! ¡Odio a estos haitianos! + +[CUB2_D:CUBAN2] +¡Me han molestado por última vez! + +[CUB2_E:CUBAN2] +¡Nos desharemos de ellos! + +[CUB2_F:CUBAN2] +Sólo necesitamos un poco de apoyo. + +[CUB2_G:CUBAN2] +Ya he perdido algunos hermanos ahí fuera. + +[CUB2_H:CUBAN2] +¡Amigo, tú conduces bien! + +[CUB2_I:CUBAN2] +Para ser una mujer. ¿No? + +[CUB2_J:CUBAN2] +¡Éste no es momento de bromear! + +[CUB2_K:CUBAN2] +¡Vamos, maneja de nuevo para mí! + +[CUB2_L:CUBAN2] +¡Lleva a mis chicos allí y eliminaremos a esos haitianos! + +[CUB2_M:CUBAN2] +¡Se meten conmigo y se meten con el chico grande de la ciudad! + +[CUB2_01:CUBAN2] +No hay suficiente espacio, brother, necesitas un coche más grande. + +[CUB2_02:CUBAN2] +¡Necesitamos refuerzos del café! + +[CUB2_03:CUBAN2] +~g~Consigue un coche y recoge a los cubanos frente al Café Robina. + +[CUB2_04:CUBAN2] +~g~Ve y deja a los cubanos en la zona de combate. + +[CUB2_05:CUBAN2] +¡Elimina a ese francotirador cobarde! + +[CUB2_07:CUBAN2] +¡Luchan como nenas! ¡Ponte a cubierto! + +[CUB2_09:CUBAN2] +¡Francotirador en el tejado! + +[CUB2_11:CUBAN2] +~r~Estúpido, necesitábamos ese coche. + +[CUB2_12:CUBAN2] +¡Eh, amigo! ¡Me alegro de que hayas podido venir! + +[CUB2_13:CUBAN2] +¡Apestoso nido de haitianos, vamos a matarlos a todos! + +[CUB2_14:CUBAN2] +¡A LA CARGA! + +[CUB2_15:CUBAN2] +¡Ahora, hermanos, A LA CAAARGA! + +[CUB2_16:CUBAN2] +¡Tommy, hemos demostrado nuestro valor y nuestra hombría! + +[CUB2_17:CUBAN2] +¡Vamos a robar esa furgoneta llena de farlopa y pegarnos el piro! + +[CUB2_18:CUBAN2] +~g~Consigue un coche y recoge a los cubanos. + +[CUB2_19:CUBAN2] +¡Vamos a combatir como machos! + +[CUB2_21:CUBAN2] +¡Luchad como machos con un par de cojones! + +[CUB2_22:CUBAN2] +~g~Termina con el resto de los haitianos para que los cubanos puedan avanzar. + +[CUB2_23:CUBAN2] +~g~Little Haiti estará atestado de haitianos intentando desquitarse con los cubanos. ¡Ten cuidado! + +[CUB2_24:CUBAN2] +~g~Vuelve al Café Robina en la furgoneta y aparca en la parte trasera. + +[CUB2_25:CUBAN2] +¡MATA A TODOS LOS HAITIANOS! + +{=================================== MISSION TABLE CUBAN3 ===================================} + +[CUB3_A:CUBAN3] +Alberto. Un cafetito, señor. + +[CUB3_B:CUBAN3] +Papaíto, no sirvas a esta sabandija. + +[CUB3_C:CUBAN3] +¡Tienes dos caras, Tommy! + +[CUB3_D:CUBAN3] +¡Tú, guapito de cara, o eres un falso o eres un calzonazos! + +[CUB3_E:CUBAN3] +¡Los haitianos! Tío, se están riendo de mí. + +[CUB3_F:CUBAN3] +Vale, vale. ¿Qué problema tienes? + +[CUB3_G:CUBAN3] +Se están riendo de mí, Tommy. ¡De mí! + +[CUB3_H:CUBAN3] +¡Umberto Robina! ¡Están haciendo lo que quieren! + +[CUB3_I:CUBAN3] +Nadie hace lo que quiere, Umberto, hacen lo que les dejes hacer. + +[CUB3_J:CUBAN3] +¿Qué? + +[CUB3_K:CUBAN3] +¿Quieres que alguien se ocupe del asunto? + +[CUB3_L:CUBAN3] +Puedo manejarlo, pero va a costarte. + +[CUB3_M:CUBAN3] +Sé que somos hermanos y todo eso, pero esto es un negocio. + +[CUB3_N:CUBAN3] +Tommy. Eres un verdadero macho. Un tío de negocios, un caballero. + +[CUB3_O:CUBAN3] +Estos haitianos. Tienen un alijo de producto que va a venir desde alta mar, un material de primera. + +[CUB3_P:CUBAN3] +Lo agarraremos y acabaremos con ellos. + +[CUB3_Q:CUBAN3] +Tú lo agarrarás y yo cuidaré de ti. Como a un hermano. Como a un hijo. + +[CUB3_R:CUBAN3] +Creo que prefiero el dinero a que me mezas en tus rodillas, amigo. + +[CUB3_01:CUBAN3] +Eh, Rico. Bonito barco, ¿estás preparado? + +[CUB3_03:CUBAN3] +~g~Recoge todos los maletines llenos de nieve y dinero. + +[CUB3_04:CUBAN3] +~g~Lleva la nieve y la pasta de vuelta a Umberto. + +[CUB3_05:CUBAN3] +Sí, Tommy. Tienes que apuntar bien. + +[CUB3_06:CUBAN3] +Mi barco, no me sirve lleno de agujeros, ¿eh? + +[CUB3_07:CUBAN3] +~g~Ve y reúnete con Rico. Te llevará al punto de encuentro. + + +[CUB3_02:CUBAN3] +~g~¡MATA A TODOS LOS HAITIANOS DEL BARCO! +[CUB3_08:CUBAN3] +Oh, oh... Una banda de cubanos. ¡Nos atacan! + +{=================================== MISSION TABLE CUBAN4 ===================================} + +[CUB4_A:CUBAN4] +Ey, señoritas. ¿Sabéis qué voy a hacer? + +[CUB4_B:CUBAN4] +Primero voy a matar un haitiano. ¿Y después? + +[CUB4_C:CUBAN4] +Voy a hacer el amor como un hombre. + +[CUB4_D:CUBAN4] +¿Sabes chica? Algo así. + +[CUB4_E:CUBAN4] +¡Perdedor! + +[CUB4_F:CUBAN4] +Mamón. + +[CUB4_G:CUBAN4] +¡Ey, nena, no te tocaría ni con una pértiga de tres metros! + +[CUB4_H:CUBAN4] +¡A Umberto Robina le gustan las damas! ¡No una cabra con faldas! + +[CUB4_I:CUBAN4] +¡Tommy! ¡Tommy, te quiero, te quiero! ¡Vámonos! + +[CUB4_J:CUBAN4] +¿Irnos a donde? ¿No me puedo tomar primero una taza de café? + +[CUB4_K:CUBAN4] +¡No hay tiempo para café! Además, acabo de tomarme uno. + +[CUB4_L:CUBAN4] +Vamos a eliminar a los haitianos. + +[CUB4_M:CUBAN4] +Tommy, ¿qué tiene un elefante dentro de la trompa? + +[CUB4_N:CUBAN4] +¡Dos metros de mocos! ¡Ja, ja, ja! + +[CUB4_O:CUBAN4] +Lo que tú digas, Umberto. + +[CUB4_P:CUBAN4] +Tommy, ve y consíguenos un cochecito haitiano. + +[CUB4_Q:CUBAN4] +Cuando lo tengas, regresa y recoge a mi chico + +[CUB4_R:CUBAN4] +Pepe, y llévale a donde los haitianos. + +[CUB4_S:CUBAN4] +Luego te vas hasta la planta de procesamiento de los haitianos y utilizas su disolvente como explosivo. + +[CUB4_T:CUBAN4] +¡Boom! Y ¡Bye bye! + +[CUB4_U:CUBAN4] +Umberto, ¿qué hay de ti? + +[CUB4_V:CUBAN4] +Oh, voy a quedarme atrás, a vigilar el café con papá. + +[CUB4_W:CUBAN4] +No se siente muy bien, ¿sabes? + +[CUB4_02:CUBAN4] +~g~Las bombas se colocarán con un temporizador de 45 segundos. + +[CUB4_04:CUBAN4] +~r~¡Alertaste a la base, ahora no habrá modo de entrar! + +[CUB4_07:CUBAN4] +El disolvente está a la vuelta, amigo. + +[CUB4_08:CUBAN4] +Hola, amigos. + +[CUB4_09:CUBAN4] +Bueno. Haitianos putas. Muerte. + +[CUB4_10:CUBAN4] +Vamos. + +[CUB4_11:CUBAN4] +Sí, vamos. + +[CUB4_12:CUBAN4] +¡Ey, necesitamos un coche de la banda haitiana! + +[CUB4_13:CUBAN4] +¡Oye, vamos a encontrar a nuestros muchachos! + +[CUB4_14:CUBAN4] +Sigue a mis compadres. + +[CUB4_15:CUBAN4] +De acuerdo, allá vas... + +[CUB4_16:CUBAN4] +¡Voy a colocar la bomba, cubridme! + +[CUB4_17:CUBAN4] +¡CORRED! + +[CUB4_18:CUBAN4] +Tío, esta es una parte bonita de la ciudad... + +[CUB4_19:CUBAN4] +Compadre, este lugar es un vertedero. + +[CUB4_20:CUBAN4] +Yo tenía una hermosa mujer... vivía por este barrio. + +[CUB4_21:CUBAN4] +Sabes, allí hacen unas buenas pizzas. + +[CUB4_22:CUBAN4] +¡Tío! ¡Conduces como un loco! + +[CUB4_23:CUBAN4] +¿Te has perdido tío? + +[CUB4_24:CUBAN4] +Dejaste atrás a Pepe, ve a por él. + +[CUB4_03:CUBAN4] +~g~Permanece en el coche hasta que puedas aparcar sin percances dentro del recinto. + +[CUB4_26:CUBAN4] +~g~Recoge a Pepe, dirígete al norte a Little Haiti y roba un coche Voodoo. + +[CUB4_27:CUBAN4] +~g~Ve y reúnete con Rico y los otros cubanos. + +[CUB4_28:CUBAN4] +~g~Reúnete con los otros cubanos en el laboratorio de sustancias químicas haitiano. + +[CUB4_29:CUBAN4] +~g~Camina hacia cada uno de los marcadores para colocar la bomba en ese lugar. + +[CUB4_30:CUBAN4] +~g~Después de colocar las tres bombas, aléjate de la fábrica antes que salte por los aires. + +[CUB4_31:CUBAN4] +~g~¡Aléjate de la fábrica! + +[CUB4_32:CUBAN4] +~g~Aparca el coche en el marcador y bájate de él. + +[CUB4_06:CUBAN4] +~r~No te alejaste lo suficiente de la base, ¡y hemos tenido que abortar la explosión! + +{=================================== MISSION TABLE FINALE ===================================} + +[FIN1_01:FINALE] +¿Qué está pasando? + +[FIN1_02:FINALE] +¡Tommy! Oh, bien, bien. Escucha, escucha. Uh, escucha, + +[FIN1_03:FINALE] +me gusta el pescado. Me encanta el pescado. + +[FIN1_04:FINALE] +Me encantan como mascotas en peceras, o como comida en un plato, + +[FIN1_05:FINALE] +pero aunque los adoro, no quiero dormir con ellos. + +[FIN1_06:FINALE] +De acuerdo, pero ahora mismo, tus hermanos italianos van a salir de ahí para ponerme esos zapatos de cemento y yo... + +[FIN1_07:FINALE] +Cállate, Ken. Siéntate. + +[FIN1_08:FINALE] +Lance, ¿qué demonios ocurre? + +[FIN1_09:FINALE] +Es tu amigo del norte, Tommy. No está muy contento de que matases a su hombre. + +[FIN1_10:FINALE] +Vienen hoy para ver el negocio. + +[FIN1_11:FINALE] +Tardaron más de lo que pensaba... + +[FIN1_12:FINALE] +Chicos, tenemos que acabar con esto y no dejar duda de que ésta es mi operación. ¡Mía! + +[FIN1_13:FINALE] +Ken, pon tres millones de la primera tanda de dinero falsificado en maletines. + +[FIN1_14:FINALE] +Lance, reúne a los muchachos... + +[FIN2_01:FINALE] +¡Tommy! + +[FIN2_02:FINALE] +¿Qué? ¿No hay un gran abrazo para tu viejo colega? + +[FIN2_03:FINALE] +He pasado quince años fuera del negocio, + +[FIN2_04:FINALE] +estoy un poco oxidado respecto a las costumbres de la familia. + +[FIN2_05:FINALE] +Siempre enfadado, ¿eh? Tommy. + +[FIN2_06:FINALE] +No te dije que tu temperamento te metería en problemas, ¿eh? + +[FIN2_07:FINALE] +Hay tres millones en los maletines... + +[FIN2_08:FINALE] +¿Cuántos fueron? ¿Diez? No, once hombres. + +[FIN2_09:FINALE] +¡Así es como llegaron a llamarte el Carnicero de Harwood! ¡Je je je! + +[FIN2_10:FINALE] +Me enviaste a matar a un hombre, UN HOMBRE. Sabían que iba para allá. + +[FIN2_11:FINALE] +Vigila tu tono. + +[FIN2_12:FINALE] +Cualquiera pensaría que me echas la culpa por ese desafortunado conjunto de circunstancias. + +[FIN2_13:FINALE] +Simplemente coge el dinero... + +[FIN2_14:FINALE] +¿Que coja el maldito dinero? + +[FIN2_15:FINALE] +¿Sabes, Tommy? Hice lo que pude por ti, tiré de algunos hilos, pedí favores. + +[FIN2_16:FINALE] +Yo era tu amigo, Tommy. Esperaba que entraras en razón, ver qué es bueno para el negocio. + +[FIN2_17:FINALE] +Confié en ti, Tommy, y me decepcionaste. + +[FIN2_18:FINALE] +Pero al menos alguno de tu organización de mierda sabe hacer negocios, + +[FIN2_19:FINALE] +¿No es verdad, Lance? + +[FIN2_20:FINALE] +Lo siento, Tommy. Esto es Vice City. Son negocios. + +[FIN2_21:FINALE] +Nos vendiste. + +[FIN2_22:FINALE] +No. Te vendí a TI, Tommy, te vendí a TI. + +[FIN2_23:FINALE] +El dinero de verdad está arriba, en la caja fuerte. + +[FIN2_24:FINALE] +Así que Tommy, ¿cuál es el gran plan? + +[FIN2_25:FINALE] +¿Creías que solo me llevaría el dinero falso? + +[FIN2_26:FINALE] +¿Salvar el culo y huir con el rabo entre las piernas? + +[FIN2_27:FINALE] +No. + +[FIN2_28:FINALE] +Solo quería joderte antes de matarte. + +[FIN3_01:FINALE] +¿Tommy? + +[FIN3_02:FINALE] +Oh, Dios mío, ¡Tommy! ¿Qué ha sucedido? + +[FIN3_03:FINALE] +¿A ti qué te parece? + +[FIN3_04:FINALE] +¡Parece que te han arruinado el traje! + +[FIN3_05:FINALE] +¡Y Tommy, era un traje muy bonito! Tommy, ¿qué diablos ha ocurrido? + +[FIN3_06:FINALE] +Tuve un desacuerdo con un socio del negocio, ya sabes cómo son estas cosas. + +[FIN3_07:FINALE] +Tommy, cuando tengo un desacuerdo con un socio, le envío una carta muy enfadado. + +[FIN3_08:FINALE] +Quizás me mee en su buzón. No empiezo la tercera guerra mundial. + +[FIN3_09:FINALE] +¿Sabes? Quizás deberías hablar con mi psiquiatra... + +[FIN3_10:FINALE] +Ése estúpido mamón, Lance... + +[FIN3_11:FINALE] +Tommy. A mí nunca me gustó ese tipo, ¿de acuerdo? + +[FIN3_12:FINALE] +Era neurótico, era inseguro, era egocéntrico... ¡ese tipo era un gilipollas! + +[FIN3_13:FINALE] +¡Me alegro de que lo eliminases! + +[FIN3_14:FINALE] +No creo que vayamos a tener más líos desde la parte norte... + +[FIN3_15:FINALE] +porque ya no hay 'parte norte'. + +[FIN3_16:FINALE] +Ahora es todo sur. + +[FIN3_17:FINALE] +Espera, eso quiere decir lo que creo que quiere decir, Tommy, ¿nene? + +[FIN3_18:FINALE] +¿Qué crees que significa? + +[FIN3_19:FINALE] +Que estamos al mando... Quiero decir, que tú estás al mando. Oh, Tommy... + +[FIN3_20:FINALE] +¿Sabes, Ken? Este podría ser el comienzo de una hermosa relación comercial... + +[FIN3_21:FINALE] +Después de todo, eres un cómplice, traicionero, ladrón de tres al cuarto + +[FIN3_22:FINALE] +y yo soy un asesino psicópata convicto y además paso farlopa. + +[FIN3_23:FINALE] +Lo sé. ¿No es sencillamente hermoso? + +[FIN_B1:FINALE] +~g~Ve y liquida al traidor de ~y~Lance Vance~g~. + +[FIN_B2:FINALE] +~g~Liquida a ~p~Sonny~g~ y acaba con esto de una vez por todas. + +[FIN_B3:FINALE] +~g~La mafia está intentando robarte el dinero. Protege la caja fuerte. + +[FIN_B4:FINALE] +~g~Estás casi muerto, consigue ~w~salud~g~ en el piso de abajo. + +[FIN_B5:FINALE] +~g~La mafia está robándote el dinero, protege la ~c~caja fuerte~g~. + +[FIN_B7:FINALE] +~r~La mafia te ha robado todo el dinero. + +[DEFSAFE:FINALE] +~g~Regresa a la caja fuerte y protégela. + +{=================================== MISSION TABLE FIRETRK ===================================} + +[F_PASS1:FIRETRK] +¡Fuego extinguido! + +[F_FAIL2:FIRETRK] +~r~¡Llegas demasiado tarde! + +[F_CANC:FIRETRK] +~r~¡Misión de bombero cancelada! + +[F_EXTIN:FIRETRK] +FUEGOS: + +[F_START:FIRETRK] +~g~Se ha informado de un vehículo ardiendo en la zona de ~a~. Ve y extingue el fuego. + +[SIREN_1:FIRETRK] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[SIREN_2:FIRETRK] +Para activar la sirena de este vehículo, pulsa ~h~~k~~VEHICLE_HORN~~w~. + +[FIREPRO:FIRETRK] +Nivel 12 de la misión del camión de bomberos completado. ¡Ahora eres completamente ignífugo! + +[F_FAIL1:FIRETRK] +Misión de bombero terminada. + +[F_STAR1:FIRETRK] +~g~Se ha informado de que hay vehículos ardiendo en la ~a~ zona. Ve y apaga el fuego. + +[SPRAY_4:FIRETRK] { reVC update } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~ ~w~para disparar el cañón de agua y ~h~~k~~VEHICLE_TURRETLEFT~~w~ o ~h~~k~~VEHICLE_TURRETRIGHT~~w~ para apuntar con él. + +[SPRAY_1:FIRETRK] { reVC update } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~ ~w~para disparar el cañón de agua y ~h~~k~~VEHICLE_TURRETLEFT~~w~ o ~h~~k~~VEHICLE_TURRETRIGHT~~w~ para apuntar con él. + +{=================================== MISSION TABLE GENERA1 ===================================} + +[GEN1_A:GENERA1] +¡Sr. Vercetti! + +[GEN1_B:GENERA1] +Coronel. + +[GEN1_D:GENERA1] +No, gracias. + +[GEN1_E:GENERA1] +Me avergüenza admitir que una de las causas de nuestro mutuo problema parece ser la lengua larga de un hombre en el que solía confiar. + +[GEN1_F:GENERA1] +He tenido conmigo a González durante años, pero ahora su incompetencia alcanza nuevas cotas. + +[GEN1_G:GENERA1] +Lo correcto es que mates a González... + +[GEN1_H:GENERA1] +¿Lo hizo él? El dinero es lo único importante para mí. + +[GEN1_I:GENERA1] +Por tu gentileza te compensaré y después buscaremos juntos tu dinero. + +[GEN1_J:GENERA1] +Estará en su ático, probablemente medio borracho. Utiliza esto... + +[GEN1_K:GENERA1] +Utiliza esto... + +[GEN1_06:GENERA1] +¡Eh! ¡Tiene una sierra! + +[GEN1_07:GENERA1] +¡Mantente alejado de mí, cabrón miserable! + +[GEN1_08:GENERA1] +¡Maldición, he desperdiciado mi vida y mi belleza! + +[GEN1_10:GENERA1] +¡Te voy a cerrar esa bocaza tuya! + +[GEN1_11:GENERA1] +¡Deja de correr gorda bola de sebo! + +[GEN1_12:GENERA1] +¡Estate quieto y haré que sea rápido! + +[GEN1_13:GENERA1] +Deja de chillar, a nadie le importa, ¡gordo! + +[GEN1_C:GENERA1] +Gracias por venir. Por favor siéntese. ¿Langosta? + +[GEN1_05:GENERA1] +~g~¡Ve y liquida a González! + +[GEN1_09:GENERA1] +Te pagaré el doble, Tommy, ¡EL DOBLE! + +[GEN1_18:GENERA1] +~r~¡González ha conseguido llegar a la Jefatura de Policía! + +[GEN1_19:GENERA1] +~g~¡La policía de Vice City viene a por ti! + +[GEN1_20:GENERA1] +~g~Sube a un vehículo. + +[GEN1_21:GENERA1] +~g~Ve al~h~ taller de pintura~g~ de~h~ Vice Point~g~. + +[GEN1_22:GENERA1] +~g~Conduce tu vehículo hasta el interior del ~h~taller de pintura~w~ para perder tu ~h~nivel de se busca~w~, reparar y volver a pintar tu vehículo. El precio es ~h~100 $~g~. Esta vez es gratis. + +[GEN1_01:GENERA1] +Cuando estés corriendo, mantén pulsado ~h~~k~~PED_FIREWEAPON~~w~ para preparar un ataque cuerpo a cuerpo. + +[GEN1_02:GENERA1] +Cuando estés corriendo, mantén pulsado ~h~~k~~PED_FIREWEAPON~~w~ para preparar un ataque cuerpo a cuerpo. + +[GEN1_03:GENERA1] +Cuando estés corriendo, mantén pulsado ~h~~k~~PED_FIREWEAPON~~w~ para preparar un ataque cuerpo a cuerpo. + +[GEN1_14:GENERA1] +Suelta ~h~~k~~PED_FIREWEAPON~~w~ para atacar. + +[GEN1_15:GENERA1] +Suelta ~h~~k~~PED_FIREWEAPON~~w~ para atacar. + +[GEN1_16:GENERA1] +Suelta ~h~~k~~PED_FIREWEAPON~~w~ para atacar. + +[GEN1_23:GENERA1] +~g~Vuelve pasando por las puertas para regresar a la planta baja. + +{=================================== MISSION TABLE GENERA2 ===================================} + +[COL2_A:GENERA2] +¡Tommy! Ven, acércate. + +[COL2_B:GENERA2] +Qué buena pinta, ¿eh? ¿Hocico de tapir? + +[COL2_C:GENERA2] +No. No, gracias. + +[COL2_D:GENERA2] +Tommy, eres como una brisa de la pampa que me ha liberado de la peste de la corrupción, + +[COL2_E:GENERA2] +aunque, parece que debo llorar su paso y seguir con el negocio como siempre. + +[COL2_F:GENERA2] +Esto no me está acercando mucho a mi dinero... + +[COL2_G:GENERA2] +Tommy, amigo mío, ahora no estás en Liberty. Aquí hacemos las cosas de un modo diferente. + +[COL2_H:GENERA2] +Continuaré con mis investigaciones pero mientras tanto tengo un valioso trato que cerrar. + +[COL2_I:GENERA2] +Un favor para un amigo, Cortez. + +[COL2_J:GENERA2] +Eres un buen amigo, Tommy. Sabía que no me decepcionarías. + +[COL2_K:GENERA2] +Necesito que te encuentres con un mensajero que ha conseguido una tecnología valiosa para mí... + +[COL2_1:GENERA2] +La lluvia, está muy húmeda esta época del año... + +[COL2_2:GENERA2] +¿Qué? + +[COL2_3:GENERA2] +Ah, comment? + +[COL2_4:GENERA2] +Mira, me envía Cortez. Tan sólo dame los malditos chips. + +[COL2_5:GENERA2] +Oh... d'accord. + +[COL2_B1:GENERA2] +~g~Encuéntrate con el mensajero en el centro comercial. + +[COL2_B2:GENERA2] +~g~El mensajero está huyendo con los chips guía, ¡no le dejes escapar! + +[COL2_B3:GENERA2] +~g~Recupera los chips guía y llévaselos de vuelta al Coronel. + +[COL2_F1:GENERA2] +~r~¡Mataste al contacto! + +[COL2_F2:GENERA2] +~r~El mensajero está muerto. Coge los chips guía. + +[COL2_6A:GENERA2] +¡Alto, cerdo imperialista americano! Eso es propiedad del gobierno francés. ¡Y se acabó! + +[BLIPHLP:GENERA2] +Cuando el icono en el radar es un triángulo apuntando hacia arriba, indica que el objetivo está por encima del jugador. + +[COL2_F3:GENERA2] +~r~Los chips de orientación están en el fondo del mar. + +[COL2_F4:GENERA2] +~r~¡El mensajero ha escapado! Has fracasado en conseguir los chips de orientación. + +{=================================== MISSION TABLE GENERA3 ===================================} + +[GEN3_A:GENERA3] +Thomas, agradezco que hayas venido. + +[GEN3_B:GENERA3] +Perdóname por ir directamente a los negocios. + +[GEN3_C:GENERA3] +Díaz me ha pedido que supervise una transacción comercial poco importante. + +[GEN3_D:GENERA3] +Esperemos que vaya mejor que la última vez, ¿eh? + +[GEN3_E:GENERA3] +Por eso es por lo que pensé en ti, amigo mío. + +[GEN3_F:GENERA3] +He dejado protección en un aparcamiento de varias plantas. + +[GEN3_G:GENERA3] +Recógela... luego ve a vigilar a los hombres de Díaz en el punto de entrega. + +[GEN3_H:GENERA3] +Gracias, amigo. + +[GEN3_1:GENERA3] +Monopolizando toda la acción, ya veo... + +[GEN3_2:GENERA3] +Mira, ¿quieres dejar de seguirme a todas partes? ¿Por qué no vienes y me demuestras que vales para algo? + +[GEN3_3:GENERA3] +Podría hacerlo. Mi nombre es Lance, por cierto. + +[GEN3_5:GENERA3] +Tú debes de ser el nuevo matón de Cortez. + +[GEN3_6:GENERA3] +Hasta que surjan oportunidades mejor pagadas. + +[GEN3_7:GENERA3] +Estarán aquí en cualquier momento. Será mejor que consigamos una posición ventajosa... + +[GEN3_8:GENERA3] +¡De acuerdo! Yo iré al balcón, tú sube a ese tejado al otro lado del patio. + +[GEN3_9:GENERA3] +¡Estoy vivo! ¡Mamones! ¡Y todo gracias a ti! ¿Cómo te llamas? + +[GEN3_10:GENERA3] +Tommy. + +[GEN3_11:GENERA3] +Nos veremos pronto, ¡creo! + +[GEN3_12:GENERA3] +Mierda. ¿Dónde está Lance? + +[GEN3_14:GENERA3] +¡Tommy! ¡Necesito ayuda aquí! + +[GEN3_15:GENERA3] +¡No te preocupes, te tengo cubierto! + +[GEN3_16:GENERA3] +¡Los hombres de Díaz están siendo eliminados! + +[GEN3_19:GENERA3] +~g~¡Haitianos! ¡Están saboteando el trato! ¡Protege a Díaz! + +[GEN3_20:GENERA3] +~g~Ve al aparcamiento donde podrás recoger el arma que te dejó el Coronel. + +[GEN3_22:GENERA3] +Salud de Díaz: + +[GEN3_23:GENERA3] +~g~¡Has dejado a Lance atrás! ¡Ve a por él! + +[GEN3_25:GENERA3] +~r~¡Lance murió! + +[GEN3_28:GENERA3] +~g~Devuélvele el maletín a Díaz. + +[GEN3_29:GENERA3] +~g~Recoge el maletín y llévaselo a Díaz. + +[GEN3_30:GENERA3] +~r~¡Se escapó con el dinero! ¡Díaz te arrancará las pelotas por ésto! + +[GEN3_33:GENERA3] +~r~¡Mira adónde disparas! Se supone que tienes que proteger a Díaz y a sus hombres, ¡no dispararles! + +[GEN3_34:GENERA3] +~r~¡No va a haber ningún trato si disparas a los cubanos! + +[GEN3_35:GENERA3] +~g~¡Ha robado el dinero de Díaz! + +[GEN3_36:GENERA3] +~g~¡Pilla una moto, persíguele y recupera el dinero de Díaz! + +[GEN3_37:GENERA3] +~g~Aquí vienen los cubanos. Vigila el trato y asegúrate que Díaz y Lance están a salvo. + +[GEN3_38:GENERA3] +~r~¡Díaz ha muerto! ¡Has fallado en protegerle! + +[GEN3_39:GENERA3] +~g~Ve a tu posición estratégica en el piso de arriba. + +[GEN3_44:GENERA3] +~g~Ve con Lance al punto de entrega y vigila a Díaz. + +[GEN3_45:GENERA3] +Estarán aquí en cualquier minuto, mejor será que vayamos tomando posiciones estratégicas. + +[GEN3_40:GENERA3] { reVC update } +Para ~h~disparar hacia el frente ~w~sobre una ~h~moto ~w~pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_41:GENERA3] { reVC update } +Para ~h~disparar hacia el frente ~w~sobre una ~h~moto ~w~pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_46:GENERA3] +¡Mierda! + +[GEN3_47:GENERA3] +¡Tommy! + +[GEN3_48:GENERA3] +¡Maldición! + +[GEN3_49:GENERA3] +Salud de Lance: + +[GEN3_50:GENERA3] +~r~¡Has perdido el dinero de Díaz! ¡La próxima vez intenta no convertir mi dinero en cenizas! + +[GEN3_51:GENERA3] +¡Más condenados haitianos en una furgoneta apestosa! + +[GEN3_54:GENERA3] +¡No os quedéis ahí, panda de gilipollas, perseguid a ese haitiano cabrón! + +[GEN3_55:GENERA3] +¡Tommy! ¡Yo me quedo aquí a proteger a Díaz! + +[GEN3_18:GENERA3] +~g~Aquí llegan los cubanos, mantente cerca de Díaz. Asegúrate de que la negociación entre Díaz y Lance sea segura. + +[GEN3_56:GENERA3] +~r~¡Díaz ha caído en una emboscada y ha muerto! ¡La próxima vez no le pierdas de vista! + +[GEN3_57:GENERA3] +El Kruger es un rifle de asalto que te permite apuntar en primera persona manualmente. + +[GEN3_58:GENERA3] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~~w~ para ~h~apuntar~w~ con un rifle de asalto. + +[GEN3_59:GENERA3] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~~w~ para ~h~apuntar~w~ con un rifle de asalto. + +[GEN3_60:GENERA3] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar~w~ un rifle de asalto. + +[GEN3_61:GENERA3] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar~w~ un rifle de asalto. + +[GEN3_62:GENERA3] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para ~h~disparar~w~ un rifle de asalto. + +[GEN3_63:GENERA3] +Además de realizar maniobras de acercamiento,~h~ las motos ~w~te permiten ~h~disparar hacia adelante~w~. + +[GEN3_64:GENERA3] { reVC update } +Para ~h~disparar hacia el frente ~w~sobre una ~h~moto ~w~pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_65:GENERA3] { reVC update } +Para ~h~disparar hacia el frente ~w~sobre una ~h~moto ~w~pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_66:GENERA3] { reVC update } +Para ~h~disparar hacia el frente ~w~sobre una ~h~moto ~w~pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~. + +[GEN3_67:GENERA3] +Para disparar hacia el frente en una moto deberás tener un subfusil. + +[GEN3_53:GENERA3] +¡Mi dinero! + +[GEN3_52:GENERA3] +¿Esos haitianos creen que pueden enfrentarse a RICARDO DÍAZ? + +{=================================== MISSION TABLE GENERA4 ===================================} + +[DETON:GENERA4] +DETONACIÓN: + +[COL4_3:GENERA4] +¡CONVOY, ALTO! + +[COL4_6:GENERA4] +¡NOS ESTÁN DISPARANDO! + +[COL4_7:GENERA4] +¡Civil, aléjese del tanque! + +[COL4_8:GENERA4] +¡DIJE, alejaos, INMEDIATAMENTE! + +[COL4_9:GENERA4] +¡POSICIONES DEFENSIVAS! + +[COL4_11:GENERA4] +- ¡Quite a esos civiles fuera de mi camino, soldado! - ¡Señor, sí, señor! + +[COL4_12:GENERA4] +¡Civil en el TANQUE! ¡DETENEDLE! + +[COL4_13:GENERA4] +Este es un convoy militar, no obstruyan nuestra ruta. + +[COL4_14:GENERA4] +Tírelo, soldado. + +[COL4_15:GENERA4] +- ¡Mueva ese vehículo civil fuera de nuestro camino! - ¡Señor! ¡Moviendo el vehículo, señor! + +[COL4_17:GENERA4] +¡Bien, PELOTÓN, MUÉVANSE! + +[COL4_18:GENERA4] +¡Hay alguien en el tanque, señor! + +[COL4_19:GENERA4] +- ¡Vaya a conseguir unos donuts, soldado! - ¡Señor, sí, señor! + +[COL4_20:GENERA4] +Blanco localizado, señor. + +[COL4_21:GENERA4] +¡FRANCOTIRADOR! + +[COL4_22:GENERA4] +Me voy de aquí. + +[COL4_23:GENERA4] +- ¡Objetivo completado! ¡Pelotón, rompan filas! - Vamos a comer unos cuantos donuts. + +[COL4_24:GENERA4] +¡Activado protocolo de seguridad Delta India Eco! ¡Iniciada autodestrucción del vehículo! + +[COL4_26:GENERA4] +¡Prepárate para morir, basura comunista! + +[COL4_B2:GENERA4] +~r~El tanque llegó a su destino a salvo! + +[COL4_B5:GENERA4] +~r~¡El tanque ha sido destruido! + +[COL4_01:GENERA4] +Díaz estaba complacido, y le gustaría verte de nuevo. + +[COL4_02:GENERA4] +¿Es eso algo bueno? + +[COL4_03:GENERA4] +¡Por supuesto! Aunque estoy empezando a pensar que Díaz fue el responsable de nuestra desafortunada pérdida... + +[COL4_04:GENERA4] +¿Qué te hace decir eso? + +[COL4_05:GENERA4] +Uno no esgrime acusaciones contra un hombre como Díaz... Solo estoy pensando en voz alta... + +[COL4_06:GENERA4] +No importa. Tengo una propuesta de la que te podrías beneficiar... + +[COL4_07:GENERA4] +No tengo tiempo para hacer más recados, Cortez. + +[COL4_08:GENERA4] +Yo habría pensado que un hombre con unas deudas tan grandes estaría ansioso por encontrar oportunidades. Por favor, Tommy, al menos escúchame. + +[COL4_09:GENERA4] +Adelante... + +[COL410:GENERA4] +Tengo un comprador para una pieza de equipo militar que está siendo transportada a través de la ciudad. Recógela para mí... + +[COL411:GENERA4] +y una vez que la tengas, quiero que me llames inmediatamente, entonces... + +[COL4_B4:GENERA4] +~g~El tanque está bloqueado. Busca un modo de engañar a los ocupantes. + +[COL4_1:GENERA4] +- ¿Que qué pasa con el artillero? - ¡No lo sé, señor! + +[COL4_4:GENERA4] +- Ve arriba soldado. - ¡Sí, señor! + +[COL4_B1:GENERA4] +~g~Ve y adquiere el vehículo militar que se está paseando por la ciudad. + +[COL4_B3:GENERA4] +~g~Deja el tanque en el escondite del Coronel antes que se autodestruya. + +[COL4_B6:GENERA4] +~g~¡Encuentra el modo de robar el tanque! + +[COL4_B7:GENERA4] +~g~Conduce el tanque al garaje. + +[COL4_B8:GENERA4] +~g~Bájate del tanque y sal del garaje. + +{=================================== MISSION TABLE GENERA5 ===================================} + +[COL5A_1:GENERA5] +Las circunstancias fuerzan una apresurada partida, amigo. + +[COL5A_2:GENERA5] +¿Cuál es el problema? + +[COL5A_3:GENERA5] +Los franceses quieren recuperar la tecnología de sus misiles y después del último incidente, + +[COL5A_4:GENERA5] +siento que es momento de encontrar puertos más seguros. + +[COL5A_5:GENERA5] +¿No sería más seguro volar? + +[COL5A_6:GENERA5] +Estaría muerto antes de alcanzar el mostrador de facturación de equipajes. Además, necesito sacar del país mi mercancía. + +[COL5A_7:GENERA5] +¿Necesitas otro guardaespaldas? + +[COL5A_8:GENERA5] +Tú, amigo mío, vales por diez guardaespaldas... + +[COL5B_1:GENERA5] +Thomas, me has protegido y servido bien. + +[COL5B_2:GENERA5] +Pero ahora debes dejarnos antes de que lleguemos a mar abierto. + +[COL5B_4:GENERA5] +Gracias, Coronel. + +[COL5B_5:GENERA5] +Una petición más, mientras estoy fuera, ¿podrías vigilar a Mercedes por mí? + +[COL5B_6:GENERA5] +Creo que ella puede cuidarse por sí misma, pero seguro, la vigilaré. + +[COL5B_7:GENERA5] +Gracias, amigo mío. Hasta luego. + +[COL5B_8:GENERA5] +Adiós, amigo. + +[COL5_7:GENERA5] +¡Deja de dispararme! + +[COL5_9:GENERA5] +¡Tommy, haz que dejen de dispararme! + +[COL5_10:GENERA5] +¡Tengo inmunidad diplomática! + +[COL5_11:GENERA5] +¡No disparen, soy un Coronel! + +[COL5_12:GENERA5] +Thomas, mátales, mi país te adorará. + +[COL5_13:GENERA5] +¡Tommy, los franceses nos superan! + +[COL5_14:GENERA5] +Tommy, mire a donde mire, hay franceses, ¡lo odio! + +[COL5_15:GENERA5] +Tommy, ¿cómo estás? + +[COL5_16:GENERA5] +¡Ésto es por Piaf y Gainesbourg y por vuestro estúpido pan francés! + +[COL5_1:GENERA5] +¡A babor! ¡A babor! + +[COL5_2:GENERA5] +¡Están atacando desde estribor! + +[COL5_3:GENERA5] +¡El puente está ahí delante! + +[COL5_4:GENERA5] +¡Tienen un helicóptero! + +[COL5_B1:GENERA5] +~g~Protege al Coronel y su yate a toda costa. + +[COL5_B2:GENERA5] +~g~Ponte delante y despeja la ruta del yate del Coronel. + +[COL5_B3:GENERA5] +~r~¡El Coronel está muerto! + +[COL5_B4:GENERA5] +~g~Destruye el helicóptero de ataque. + +[COL5B_3:GENERA5] +Bajaré mi lancha personal. Quédatela, amigo, en señal de mi agradecimiento. + +[COL5_B5:GENERA5] +~g~Dispara para abatir los helicópteros, no pongas en peligro el yate. + +[COL5_B6:GENERA5] +~g~Te has quedado sin munición, consigue más de las escaleras en la cubierta superior. + +[COL5_B7:GENERA5] +~g~Te estás quedando sin salud, consigue más de las escaleras en la cubierta superior. + +{=================================== MISSION TABLE HAIT1 ===================================} + +[HAM1_A:HAIT1] +¿Hola? ¿Hola? + +[HAM1_B:HAIT1] +Entra, cielo, y descansa los pies. + +[HAM1_C:HAIT1] +Tú debes de ser el gran hombre malo sobre el que ha estado hablando mi abuelo. + +[HAM1_D:HAIT1] +Me cuenta cosas sobre ti, ya sabes, cuando me visita, + +[HAM1_E:HAIT1] +y sobre los otros que te esperan. + +[HAM1_F:HAIT1] +Bien, todos llevamos muertos mucho tiempo, pero tú... + +[HAM1_G:HAIT1] +No me gustaría estar en tu pellejo. + +[HAM1_H:HAIT1] +Recibí un mensaje. Para que viniese aquí. + +[HAM1_I:HAIT1] +¿Puedes oírles? + +[HAM1_J:HAIT1] +Están diciendo tu nombre, deben quererte mucho, ¿no crees? + +[HAM1_K:HAIT1] +Ahora ayudarás a la tía Poulet y quizás ella te ayude. + +[HAM1_L:HAIT1] +Quizás ella pueda darte un pequeño talismán después de todo esto. + +[HAM1_M:HAIT1] +¿Darte algo de magia para echar el mal de ojo a la policía? + +[HAM1_N:HAIT1] +Mira, esto es todo muy... ¿darme qué? + +[HAM1_O:HAIT1] +Yo, yo, creo que recibí la dirección equivocada... + +[HAM1_P:HAIT1] +Haz esto por mí, Tommy... + +[HAM1_Q:HAIT1] +Los cubanos, tontos orgullosos y desagradables, + +[HAM1_R:HAIT1] +han estado causando problemas a mis encantadores chicos haitianos. + +[HAM1_S:HAIT1] +Ahora le han dicho a la policía donde he estado almacenando mis polvos. + +[HAM1_T:HAIT1] +Ellos piensan que es farlopa, pardillos. + +[HAM1_U:HAIT1] +Ahora sé un buen chico, Tommy, y ve a conseguir los polvos para la tía Poulet. + +[HAM1_V:HAIT1] +Sí, sí, seguro, seguro. + +[HAM1_1:HAIT1] +~g~Los polis se están acercando a nuestros escondites. Ve y consíguelos antes que ellos lo hagan. + +[HAM1_2:HAIT1] +~r~¡Los polis llegaron primero al escondite! + +[HAM1_3:HAIT1] +~g~¡Lleva este material de vuelta al escondite! + +[HAM1_4:HAIT1] +~g~¡Bien! ¡Ahora el siguiente! + +[HAM1_6:HAIT1] +~r~¡Ese almacén está destruido, idiota! + +[HAM1_7:HAIT1] +~g~¡Los polis han conseguido nuestro alijo! ¡Recupéralo antes de que se marchen! + +[HAM1_8:HAIT1] +~g~Los polis están de camino para recoger el alijo, ¡date prisa! + +[HAT_1A:HAIT1] +~g~¡No muevas ni un solo dedo! + +{=================================== MISSION TABLE HAIT2 ===================================} + +[HAT2_B1:HAIT2] +~g~Ve hasta la camioneta que contiene las bombas aéreas. + +[HAT2_B2:HAIT2] +Mata a los cubanos... + +[HAT2_B4:HAIT2] +¡Y destruye sus barcos! + +[HAT2_B5:HAIT2] +~g~Los cubanos están huyendo. ¡No les dejes escapar! + +[HAT2_B6:HAIT2] +~r~¡El avión RC está yendo demasiado lejos del alcance! + +[HAT2_B7:HAIT2] +~g~Uno de los cubanos está escapando en un coche. ¡No dejes ningún testigo! + +[HAT2_B8:HAIT2] +~r~¡No te quedan aviones RC! + +[HAT2_B9:HAIT2] +Aviones RC: + +[HAT2_1:HAIT2] +Oh. Lo siento, debo tener la dirección equivocada... + +[HAT2_2:HAIT2] +Bueno, también podrías entrar y descansar los pies y tomarte una taza de té. + +[HAT2_3:HAIT2] +¿Tienes algo ahí para mí? + +[HAT2_4:HAIT2] +Sí... + +[HAT2_5:HAIT2] +Este lugar me parece familiar. Un olor de la niñez... un deja vu... + +[HAT2_6:HAIT2] +Bien, Tommy, te voy a susurrar un pequeño recado para que lo hagas. Escúchame bien, ¿vale? + +[HAT2_7:HAIT2] +Te pareces a alguien que yo... + +[HAT2_8:HAIT2] +Los cubanos tienen barcos rápidos que usan para cruzar el charco cargados de polvo. + +[HAT2_9:HAIT2] +Es su sustento. + +[HAT2_10:HAIT2] +Mi sobrino ha estado preparando pequeñas bombas aéreas para eliminarles. + +[HAT2_11:HAIT2] +Vuela los barcos y conviértelos en madera de ataúd. + +[HAT2_12:HAIT2] +Muchas gracias por el té. + +[HAT2_B3:HAIT2] { reVC update } +Pulsa ~h~~k~~VEHICLE_FIREWEAPON~~w~ para soltar una bomba. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para cancelar. + +{=================================== MISSION TABLE HAIT3 ===================================} + +[HAM3_A:HAIT3] +¿Hola? Hola, eh... Estaba buscando a alguien por aquí... + +[HAM3_B:HAIT3] +Pareces hambriento, Tommy, + +[HAM3_C:HAIT3] +¿Te conozco? + +[HAM3_D:HAIT3] +Silencio. + +[HAM3_E:HAIT3] +Una cosa más y te podré dejar marchar, Tommy. + +[HAM3_F:HAIT3] +Mis chicos están en guerra con los cubanos. + +[HAM3_G:HAIT3] +Pero sin armas. + +[HAM3_H:HAIT3] +Pero los cubanos tienen una sorpresa por llegar. + +[HAM3_I:HAIT3] +Mientras combaten en las calles, tú tomarás este rifle y los matarás en medio del alboroto. + +[HAM3_J:HAIT3] +Nadie te verá, nadie te oirá. + +[HAM3_K:HAIT3] +Tommy, si haces esto por mí, ya no estarás atado a los lazos de mi delantal. + +[HAM3_1:HAIT3] +~g~Debemos ganar esta batalla. Si todos los haitianos mueren, perdemos nosotros. + +[HAM3_3:HAIT3] +~g~Espero que los cubanos hagan trampas así que permanece alerta. + +[HAM3_4:HAIT3] +~r~¡Te han visto! ¡La misión ha fracasado! + +[HAM3_5:HAIT3] +~g~Debes matar a los cubanos desde lejos. No deben verte. + +[HAM3_8:HAIT3] +~g~¡Los haitianos están muriendo! Mejora tu puntería. + +[HAM3_7:HAIT3] +~g~¡Cuidado! Los cubanos han traído refuerzos. ¡¡Acaba con todos ellos!! + +[HAM3_2:HAIT3] +~r~¡Los haitianos han muerto! + +[HAM3_L:HAIT3] +Vale, tía... + +{=================================== MISSION TABLE HOTEL ===================================} + +[INTB_A:HOTEL] +¡Tommy! Tommy, ha pasado tanto tiempo. + +[INTB_B:HOTEL] +Hola, Sonny. + +[INTB_C:HOTEL] +Lo sé, lo sé. Te abruma la emoción. + +[INTB_D:HOTEL] +Quince años... si parece que fue ayer. + +[INTB_E:HOTEL] +Supongo que es cuestión de perspectiva. + +[INTB_F:HOTEL] +Eh, estar en el talego por la familia no es agradable, + +[INTB_G:HOTEL] +aunque la familia cuida de los suyos, ¿no es verdad? + +[INTB_H:HOTEL] +Y bien, cómo fue el negocio... ¿estás sentado en una pila de oro blanco? + +[INTB_I:HOTEL] +Mira, Sonny, nos tendieron una trampa. El negocio fue una emboscada. Harry y Lee están muertos. + +[INTB_J:HOTEL] +Me estás tomando el pelo, ¿no? Tommy. Dime que aún tienes el dinero. + +[INTB_K:HOTEL] +No Sonny... no tengo el dinero. + +[INTB_L:HOTEL] +Ése era mi dinero, Tommy, ¡mi dinero! + +[INTB_M:HOTEL] +¡Espero que no me andes jodiendo, Tommy, porque sabes que soy un hombre con el que no se puede jugar! + +[INTB_N:HOTEL] +Espera Sonny. + +[INTB_O:HOTEL] +Tienes mi garantía personal de que voy a conseguir recuperar el dinero y el alijo. + +[INTB_P:HOTEL] +Y te voy a enviar por correo las pelotas de los responsables. + +[INTB_Q:HOTEL] +Eh, ya lo sé. No eres tonto, Tommy, pero te prevengo, yo tampoco lo soy. + +[INTB_R:HOTEL] +Si se tratase de cualquier otro, ya estarías MUERTO. + +[INTB_S:HOTEL] +Pero como se trata de ti, como hemos hecho historia, voy a dejar que te ocupes de esto. + +[INTB_T:HOTEL] +Mira, Sonny, tienes mi palabra. + +[INTB_U:HOTEL] +Estaremos en contacto. + +{=================================== MISSION TABLE ICECRE1 ===================================} + +[ICC1_1:ICECRE1] +~g~Utiliza la furgoneta de los helados para distribuir perica en Vice City. + +[ICC1_3:ICECRE1] +~g~Recibirás dinero por cada venta que hagas, pero cuantas más transacciones hagas más atraerás la atención de la policía. + +[ICC1_4:ICECRE1] +~g~No hay ningún cliente en esta zona, inténtalo en otra. + +[ICC1_5:ICECRE1] +Negocios hechos: + +[ICC1_6:ICECRE1] +~g~Utiliza la furgoneta Mr. Whoopee para distribuir los productos Cherry Popper por Vice City. + +[ICC1_7:ICECRE1] +~g~Por cada transacción que hagas recibirás dinero, aunque cuantas más operaciones lleves a cabo más atención provocarás de la policía. + +[ICC1_8:ICECRE1] +~g~Para llevar a cabo el negocio ~h~aparca la furgoneta, ~g~y pulsa el ~h~~k~~VEHICLE_HORN~ ~g~para que suene la música que llamará la atención de la gente. + +[ICC1_9:ICECRE1] +~g~A las bandas locales no les gustará que hagas negocios en su territorio así que si haces esto, prepárate para hostilidades. + +[ICC1_10:ICECRE1] +~g~Has hecho ~1~ tratos! + +[ICC1_11:ICECRE1] +~g~Has hecho ~1~ trato! + +[ICC1_12:ICECRE1] +¡PROPIEDAD ADQUIRIDA! + +[ICC1_13:ICECRE1] +~r~¡No conseguiste ningún trato! + +[ICC1_14:ICECRE1] +FÁBRICA DE HELADOS CONSOLIDADA + +[ICC1_15:ICECRE1] +~g~La fábrica de helados generará ahora unos ingresos máximos de hasta ~1~ $. Asegúrate de recaudarlos regularmente. + +[ICC1_2:ICECRE1] +~g~Aparca la furgoneta y pulsa ~h~~k~~VEHICLE_HORN~~w~ para que suene la música indicando a los clientes que estás listo para hacer empezar. + +[ICC1_16:ICECRE1] +~g~Utiliza la furgoneta Mr. Whoopee para distribuir los productos Cherry Popper por Vice City. + +[ICE_AT1:ICECRE1] +FÁBRICA DE HELADOS CONSOLIDADA + +[ICE_AT2:ICECRE1] +~g~La fábrica Cherry Popper generará ahora unos ingresos hasta un máximo de ~1~ $. Asegúrate de recaudarlos de manera regular. + +[ICC1_17:ICECRE1] +Misión de Distribución finalizada + +[ICC1_18:ICECRE1] +Venta total de helados: ~1~ $ + +[ICC1_19:ICECRE1] +Total de negocios hechos: ~1~ + +{=================================== MISSION TABLE ICECUT ===================================} + +[ICC1_A:ICECUT] +¿Quién eres tú? + +[ICC1_B:ICECUT] +El nuevo propietario. + +[ICC1_C:ICECUT] +¿Fuiste ahora o en cualquier momento un niño? + +[ICC1_D:ICECUT] +¿De qué estás hablando? + +[ICC1_E:ICECUT] +¿Fuiste niño? + +[ICC1_F:ICECUT] +¡Sí! ¡Calma! ¿Qué pasa contigo? + +[ICC1_G:ICECUT] +Lo sabía. Un niño. + +[ICC1_H:ICECUT] +¡Un maldito, apestoso, lloriqueante, mocoso, vil, vomitivo y pequeño bebé chillón! + +[ICC1_I:ICECUT] +Bebés... Criaturas repugnantes, horribles y espantosas. + +[ICC1_J:ICECUT] +Mami no te quiere. ¡mierdecilla! + +[ICC1_K:ICECUT] +Cálmate. + +[ICC1_L:ICECUT] +ODIO los bebés, odio los niños. + +[ICC1_M:ICECUT] +Son unos pequeños guarros, llorones, mocosos, perversos y vomitivos... + +[ICC1_N:ICECUT] +¡Ya es suficiente! + +[ICC1_O:ICECUT] +¿Qué pasa contigo? + +[ICC1_P:ICECUT] +¿Fabricas helado, de acuerdo? Es básicamente para los niños. + +[ICC1_Q:ICECUT] +¿Qué clase de psicópata eres tú? + +[ICC1_R:ICECUT] +Solo para que lo entienda, ¿para qué hacer felices a los niños si los odias? + +[ICC1_S:ICECUT] +Oh, estúpido, apestoso, lloriqueante... + +[ICC1_T:ICECUT] +¡Cállate! + +[ICC1_U:ICECUT] +¡Mocoso! + +[ICC1_V:ICECUT] +El helado es una tapadera. + +[ICC1_W:ICECUT] +Distribuimos otros productos no lácteos. + +[ICC1_X:ICECUT] +Y si veo un niño, le saco provecho. + +[ICC1_Y:ICECUT] +¿Verdad, niños? Sí, sí, lo hago. Mamá no os quiere. + +[ICC1_Z:ICECUT] +¡Os ODIA! + +[ICC1_ZA:ICECUT] +¡PROPIEDAD ADQUIRIDA! + +{=================================== MISSION TABLE INTRO ===================================} + +[INT1_A:INTRO] +Tommy Vercetti... Mierda. + +[INT1_B:INTRO] +No pensé que le fueran a soltar nunca. + +[INT1_C:INTRO] +Mantuvo la cabeza gacha, ayuda a la gente a olvidar. + +[INT1_D:INTRO] +La gente lo recordará antes de lo que crees. + +[INT1_E:INTRO] +Cuando le vean paseando por las calles de su barrio. + +[INT1_F:INTRO] +Será malo para el negocio. + +[INT1_G:INTRO] +Bien, ¿qué vamos a hacer, Sonny? + +[INT1_H:INTRO] +Le tratamos como a un viejo amigo y le tenemos ocupado fuera de la ciudad. ¿De acuerdo? + +[INT1_I:INTRO] +Hemos estado hablando de expandirnos hacia el sur. ¿Verdad? + +[INT1_J:INTRO] +Hoy en día Vice City es oro de veinticuatro quilates. + +[INT1_K:INTRO] +Los colombianos, los mejicanos, demonios, + +[INT1_L:INTRO] +incluso esos refugiados cubanos se están repartiendo un buen pedazo de la acción. + +[INT1_M:INTRO] +Pero todo es por la nieve, Sonny. + +[INT1_N:INTRO] +¡Ninguna de las familias tocará esa mierda! + +[INT1_O:INTRO] +Los tiempos cambian. + +[INT1_P:INTRO] +Las familias no pueden seguir dando la espalda mientras nuestros enemigos recogen las recompensas. + +[INT1_Q:INTRO] +Así que enviaremos a alguien para que nos hagan el trabajo sucio + +[INT1_R:INTRO] +y tranquilamente nos llevaremos un buen cacho. ¿Vale? + +[INT1_S:INTRO] +¿Quién es nuestro contacto allí? + +[INT1_T:INTRO] +Ken Rosenberg, un estúpido abogado. + +[INT1_U:INTRO] +¿Cómo va a atar corto a Vercetti? + +[INT1_V:INTRO] +No necesitamos que lo haga. + +[INT1_W:INTRO] +Simplemente le soltaremos en Vice City + +[INT1_X:INTRO] +y le daremos un poco de dinero para que empiece. ¿De acuerdo? + +[INT1_Y:INTRO] +Le damos unos cuantos meses. + +[INT1_Z:INTRO] +Luego vamos, + +[INT1_A1:INTRO] +y le hacemos una pequeña visita, ¿de acuerdo? + +[INT1_A2:INTRO] +Para ver cómo le va. + +[INT2_A:INTRO] +¡Eh, eh, tíos! ¡Aquí, eh, Ken Rosenberg! ¡Je, je, genial! + +[INT2_B:INTRO] +Bueno, voy a llevaros a la reunión, ¿vale? + +[INT2_C:INTRO] +He hablado con los proveedores, y ellos están muy... + +[INT2_D:INTRO] +interesados en empezar una relación de negocios, así que... + +[INT2_E:INTRO] +si todo esto va bien, deberíamos... + +[INT2_F:INTRO] +empezar a obtener beneficios, lo cual es, ya sabes... + +[INT2_G:INTRO] +Bueno. + +[INT2_H:INTRO] +Entonces. Son hermanos, ¿vale? + +[INT2_I:INTRO] +Uno dirige el negocio, + +[INT2_J:INTRO] +y el otro se encarga de los vuelos. + +[INT2_K:INTRO] +Ahora operan fuera de Méjico + +[INT3_A:INTRO] +Bien, son los del helicóptero. + +[INT3_B:INTRO] +De acuerdo, este es el trato. + +[INT3_C:INTRO] +Quieren un intercambio directo en campo abierto. + +[INT3_D:INTRO] +¿De acuerdo? De acuerdo, permanece firme, vamos. + +[INT3_E:INTRO] +De acuerdo, ahora con cuidado. + +[INT3_F:INTRO] +Estoy aquí. ¡El coche está en marcha, nena! + +[INT3_G:INTRO] +¿Lo tienes? + +[INT3_H:INTRO] +Colombiana, 100% con un grado de pureza A, amigo. + +[INT3_I:INTRO] +¿Los verdes? + +[INT3_J:INTRO] +De diez y de veinte... usados. + +[INT3_L:INTRO] +¡Vamos, fuera de aquí! ¡Arranca! + +[INT4_A:INTRO] +¡Jodidos! ¡Estamos jodidos! + +[INT4_B:INTRO] +Muy típico, + +[INT4_C:INTRO] +saco la cabeza del agujero un jodido segundo, + +[INT4_D:INTRO] +¡Y el destino me echa la mierda a la cara! + +[INT4_E:INTRO] +Bueno, ¡jódete! + +[INT4_F:INTRO] +Cierra el pico y deja de quejarte. Estás vivo, ¿no? + +[INT4_G:INTRO] +Déjame justo aquí. + +[INT4_H:INTRO] +Ve y abandona el coche y luego vete a dormir un poco. + +[INT4_I:INTRO] +Mañana me pasaré por tu oficina y podemos empezar a solucionar esto. + +[INT4_J:INTRO] +De acuerdo, es una buena idea, iré a dormir un poco. + +[INT4_K:INTRO] +¿Qué vas a hacer tú. + +[INT4_L:INTRO] +Me las arreglaré para volver a mi hotel. + +[INT4_M:INTRO] +A despejar la mente y a pensar sobre esta mierda. + +[INT4_N:INTRO] +Vale. + +[INTRO1:INTRO] +Saco la cabeza del agujero un jodido segundo y el destino, ¡me echa la mierda en la cara! + +[INTRO2:INTRO] +Vete a dormir un poco. + +[INTRO3:INTRO] +¿Qué es lo que vas a hacer? + +[INTRO4:INTRO] +Mañana me pasaré por tu oficina y empezaremos a solucionar todo este desorden. + +[INT3_K:INTRO] +Creo que hemos llegado a un acuerdo, amigo mío. + +[INT3_M:INTRO] +Déjame ver. + +[INT2_L:INTRO] +no, no, no, espera... + +[INT3_N:INTRO] +¡Mierda! + +{=================================== MISSION TABLE KENT1 ===================================} + +[KPM1_A:KENT1] +A ver tío, te voy a salvar el culo, colega. + +[KPM1_B:KENT1] +¿De qué coño me estás hablando? + +[KPM1_C:KENT1] +Conoces a ese bastardo de Díaz, Don Farlopa. + +[KPM1_D:KENT1] +Tiene a tu colega, Lance. Dicen que tu amigo intentó atacarle por sorpresa... + +[KPM1_E:KENT1] +pero no fue lo suficientemente sorprendente, si entiendes lo que te quiero decir. + +[KPM1_F:KENT1] +¿A dónde le llevó? + +[KPM1_G:KENT1] +¡Tranquilo! Le hicieron cruzar la ciudad hasta el desguace. + +[KPM1_H:KENT1] +Maldita sea... ¡Chalado! + +[KPM1_2:KENT1] +~r~¡Se suponía que debías sacar con vida a Lance! + +[KPM1_3:KENT1] +SALUD DE LANCE: + +[RESC_1:KENT1] +¿Crees que puedes usar un arma? + +[RESC_2:KENT1] +Sí... supongo... yo también me alegro de verte. + +[RESC_3:KENT1] +Salgamos de aquí. + +[RESC_4:KENT1] +Ahí va todo mi cuidadoso plan, directo a la mierda, gracias a ti. La jodiste bien. + +[RESC_5:KENT1] +Mató a mi hermano. ¿Qué esperabas que hiciese, cortarle el césped? + +[RESC_6:KENT1] +Vamos a tener que eliminar a ese mamón de Díaz antes de que él nos elimine a nosotros. + +[RESC_7:KENT1] +Haz que te curen y encuéntrate conmigo en el puente que va a Star Island, ¿de acuerdo? + +[RESC_8:KENT1] +De acuerdo, entendido. + +[KPM1_1:KENT1] +~g~¡Lance está siendo retenido en el desguace, ve y libérale! + +[KPM1_4:KENT1] +~g~¡Lleva a Lance al hospital! + +[M_PASSN:KENT1] +¡MISIÓN SUPERADA! + +[KPM1_5:KENT1] +~g~¡Los hombres de Díaz te están buscando! Lleva a Lance al hospital. + +{=================================== MISSION TABLE KICKSTT ===================================} + +[KICK1_2:KICKSTT] +~r~¡No volviste a la moto lo suficientemente rápido! + +[KICK1_7:KICKSTT] +~r~¡Has destrozado la moto! + +[KICK1_8:KICKSTT] +~g~¡Sube a la moto! + +[KICK1_T:KICKSTT] +TIEMPO EMPLEADO: + +[KICKTM:KICKSTT] +~b~TIEMPO DE PRUEBA: ~1~:~1~ + +[KICKTM2:KICKSTT] +~b~TIEMPO DE PRUEBA: ~1~:0~1~ + +[GETBIKE:KICKSTT] +~g~Tienes ~1~ segundos para volver a subir a una moto antes de que termine la misión. + +[KICK1_1:KICKSTT] +~g~Completa el circuito lo más rápidamente posible. + +[KICK1_6:KICKSTT] +~g~¡Bien hecho! + +[KICK_10:KICKSTT] +~g~Utiliza la Sanchez para completar el circuito pasando por todos los puntos de control. + +[KICK_12:KICKSTT] +~r~¡La has cagado! + +[KICK_13:KICKSTT] +~r~¡Has tardado mucho tiempo! + +[KICK_11:KICKSTT] +~g~Para abandonar la misión, sitúate en el ~q~marcador rosa~g~. + +{=================================== MISSION TABLE LAWYER1 ===================================} + +[LAW1_A:LAWYER1] +Ve a dormir un poco, dice... + +[LAW1_B:LAWYER1] +¡Llevo sentado en esta silla toda la noche con las luces apagadas y bebiendo café! + +[LAW1_C:LAWYER1] +Esto es un desastre. ¡Estamos muy jodidos, tío! + +[LAW1_D:LAWYER1] +Estos gorilas, escúchame, van a venir aquí a arrancarme la cabeza. ¡Es ridículo! + +[LAW1_E:LAWYER1] +¡NO fui a la universidad para ésto! De acuerdo, ¿ahora qué coño vamos a hacer? + +[LAW1_F:LAWYER1] +Cállate, siéntate y relájate. Te diré lo que vamos a hacer. + +[LAW1_G:LAWYER1] +Vas a descubrir quién se llevó nuestra nieve... y después voy a cargármelos. + +[LAW1_H:LAWYER1] +Es una buena idea. Es una idea GENIAL. Déjame pensar, déjame pensar, déjame pensar. + +[LAW1_I:LAWYER1] +¡Oh! Está este Coronel retirado, el coronel Juan García Cortez. + +[LAW1_J:LAWYER1] +Él es el que me ayudó a montar esta operación + +[LAW1_K:LAWYER1] +bien lejos de los matones establecidos de Vice City. ¿Entendido? + +[LAW1_L:LAWYER1] +Bien, escucha. Está dando una fiesta en su lujoso yate en la bahía + +[LAW1_M:LAWYER1] +y todos los hombres más importantes de Vice City van a estar allí. + +[LAW1_N:LAWYER1] +Tengo invitación, por supuesto que tengo invitación, + +[LAW1_O:LAWYER1] +pero no sueñes que vaya a asomar la cabeza por esa puerta... ¡ni lo sueñes! + +[LAW1_P:LAWYER1] +¡Te lo dije, cállate! Iré yo mismo... + +[LAW1_Q:LAWYER1] +A mí también me gusta el estilo de 1978, pero esto no va a ser una fiesta con cerveza y tías en pelotas. + +[LAW1_R:LAWYER1] +Quiero decir, sin ánimo de ofender, creo que podrías hacer que la gente se volviese a mirar por razones equivocadas. + +[LAW1_S:LAWYER1] +¿Qué hay de malo con cómo voy vestido? + +[LAW1_T:LAWYER1] +Vale, mira, ten. Pásate por donde Rafael, dile que te envío yo, hará que parezcas respetable. + +[LAW1_U:LAWYER1] +Bien, ve, vamos... + +[LAWP_1:LAWYER1] +Buenas noches. + +[LAWP_2:LAWYER1] +Tengo entendido que viene de parte del Sr. Rosenberg, + +[LAWP_3:LAWYER1] +Espero que ningún problema reciente le haya afectado a su salud física, o uh, + +[LAWP_4:LAWYER1] +o mental, Sr... ¿eh? + +[LAWP_5:LAWYER1] +Vercetti. Tan sólo tiene un poco de... agorafobia. + +[LAWP_6:LAWYER1] +Excelente, excelente. ¿Y usted? + +[LAWP_7:LAWYER1] +Yo solo quiero mi farlopa. + +[LAWP_8:LAWYER1] +Ah. Ha sido un desgraciado cúmulo de circunstancias para todos los interesados. + +[LAWP_9:LAWYER1] +Por supuesto, he iniciado mis propias pesquisas + +[LAWP_10:LAWYER1] +pero un asunto tan delicado llevará tiempo. + +[LAWP_11:LAWYER1] +Quizás hablemos más tarde. ¿Eh? + +[LAWP_12:LAWYER1] +Mientras tanto, permítame presentarle a mi hija. + +[LAWP_13:LAWYER1] +¡Mercedes! + +[LAWP_14:LAWYER1] +Cara mía, ¿podrías acompañar a nuestro invitado mientras atiendo a mis obligaciones? + +[LAWP_15:LAWYER1] +Por supuesto, papá. + +[LAWP_16:LAWYER1] +Por favor, discúlpeme. + +[LAWP_17:LAWYER1] +¿Mercedes? + +[LAWP_18:LAWYER1] +Intenta acostumbrarte. + +[LAWP_19:LAWYER1] +En fin, déjame presentarte a algunos de nuestros invitados más distinguidos... + +[LAWP_20:LAWYER1] +Ese es nuestro congresista Alex Shrub con la sonriente estrella de la silicona Candy Suxxx... + +[LAWP_21:LAWYER1] +¿Conoces a mi encantadora esposa, Laura? ¿No? + +[LAWP_22:LAWYER1] +Bueno, desafortunadamente ella está en Alabama. Te presento a Candy. + +[LAWP_23:LAWYER1] +Y por ahí tenemos a la estirada estrella de las Mambas de Vice City, BJ. + +[LAWP_24:LAWYER1] +Siempre tan encantador. + +[LAWP_25:LAWYER1] +Le paré en seco, lo hice, ¡y le dejé en una silla de ruedas! + +[LAWP_26:LAWYER1] +¡Ja, ja, ésa es buena! + +[LAWP_27:LAWYER1] +Bien, ahora estoy mirando una verdadera propiedad de primera. + +[LAWP_28:LAWYER1] +Y ese anfibio al lado de la piscina es Jezz Torrent, + +[LAWP_29:LAWYER1] +cantante principal de Love Fist. + +[LAWP_30:LAWYER1] +¿Sabéis cómo juegan al ping-pong en Tailandia? + +[LAWP_31:LAWYER1] +Dejad que os lo cuente, + +[LAWP_32:LAWYER1] +¡no hace falta una pala precisamente! ¿entendéis lo que quiero decir? + +[LAWP_33:LAWYER1] +Impotente. + +[LAWP_34:LAWYER1] +Y el trío charlatán. + +[LAWP_35:LAWYER1] +Esa glándula sudorípara durmiente es la mano derecha de papá, González + +[LAWP_36:LAWYER1] +y los otros dos son el pastor Richards + +[LAWP_37:LAWYER1] +y el director de cine seudointelectual, Steve Scott. + +[LAWP_38:LAWYER1] +Pasión con las invasoras ninfómanas, + +[LAWP_39:LAWYER1] +entonces aparece el tiburón gigante y... + +[LAWP_40:LAWYER1] +¡Les arranca las pollas de un mordisco! + +[LAWP_41:LAWYER1] +Ja, bien. Nunca has visto nada igual, ¿Verdad? + +[LAWP_42:LAWYER1] +¡Coronel! + +[LAWP_43:LAWYER1] +¡Sus fiestas tienen siempre un gran éxito! + +[LAWP_44:LAWYER1] +Sólo puedo pedir disculpas por mi tardía llegada. + +[LAWP_45:LAWYER1] +Ah, de nada amigo. ¿Cómo nos encontramos? + +[LAWP_46:LAWYER1] +Nuestro negocio es muy estresante, siempre intentan desbancarnos. + +[LAWP_47:LAWYER1] +Hay un momento para recompensar a los amigos de uno y para liquidar a los enemigos, amigo. + +[LAWP_48:LAWYER1] +¿Quién es el bocazas? + +[LAWP_49:LAWYER1] +Ricardo Díaz, alias Don Farlopa. + +[LAWP_50:LAWYER1] +¡Mercedes! + +[LAWP_51:LAWYER1] +Oh, estaba a punto de llevar a mi amigo de vuelta a la ciudad. + +[LAWP_52:LAWYER1] +¡En otro momento, Ricardo! + +[LAWP_53:LAWYER1] +Salgamos de aquí. + +[LAWP_54:LAWYER1] +Mejor llévame al club Pole Position. + +[LAW1_2:LAWYER1] +~g~Ve al barco del Coronel. + +[LAW1_4:LAWYER1] +~r~¡Mataste a la hija del Coronel! + +[LAW1_5:LAWYER1] +¿Trabajarás para mi padre? + +[LAW1_6:LAWYER1] +Quizás. + +[LAW1_7:LAWYER1] +¿Te importa que pose mi mano en tu regazo? + +[LAW1_8:LAWYER1] +Quizás... + +[LAW1_9:LAWYER1] +Es tan difícil tener un padre rico y poderoso. Vamos. + +[LAW1_10:LAWYER1] +¡Nos vemos, guapo! + +[LAW1_11:LAWYER1] +Estoy seguro. + +[LAW1_12:LAWYER1] +Bonita moto. + +[LAW1_13:LAWYER1] +¡No! ¡Mi moto! + +[LAW1_3:LAWYER1] +~g~Lleva a la hija del Coronel al club Pole Position. + +[HELP20:LAWYER1] +Ve al icono de la ~h~camiseta~w~ que hay en el radar para encontrar la tienda de Rafael. + +[LAW1_14:LAWYER1] +¡Guau!, Me encantaría probar tu moto. + +[LAW1_15:LAWYER1] +Sí pequeño, pues recógela de Howlin' Pete's + +{=================================== MISSION TABLE LAWYER2 ===================================} + +[LAW2_A:LAWYER2] +¡Ah! Espero que te lo estés pasando bien. Porque yo me estoy volviendo loco de preocupación. ¿Qué has descubierto? + +[LAW2_B:LAWYER2] +Que hay más criminales en esta ciudad que en la cárcel. Necesitamos un confidente de las calles... + +[LAW2_C:LAWYER2] +Vale, déjame pensar, déjame pensar, déjame pensar... + +[LAW2_D:LAWYER2] +¡AH! ¡Lo tengo! + +[LAW2_E:LAWYER2] +Está este inglés, un baboso de la industria musical, + +[LAW2_F:LAWYER2] +se hace llamar Kent Paul. + +[LAW2_G:LAWYER2] +Bueno, tiene la cabeza tan metida en la mayoría de los culos de Vice City + +[LAW2_H:LAWYER2] +que si alguien sabe el paradero de 20 kilos de farlopa, + +[LAW2_I:LAWYER2] +es este tío, ¿de acuerdo? Siempre está en el Malibú. + +[LAW2_J:LAWYER2] +Le haré una visita. + +[LAW2B_A:LAWYER2] +¿De dónde saliste? + +[LAW2B_B:LAWYER2] +He estado buscando una chica como tú durante siglos, tía... + +[LAW2B_C:LAWYER2] +Kent Paul, tía. Por aquí soy el que manda. + +[LAW2B_D:LAWYER2] +Estoy buscando a un tipo inglés... + +[LAW2B_E:LAWYER2] +Muevo hilos, ¿entiendes lo que quiero decir? + +[LAW2B_F:LAWYER2] +Te invitaré. Lo que quieras, te lo conseguiré, chica. + +[LAW2B_G:LAWYER2] +No te preocupes por nada, tía. + +[LAW2B_H:LAWYER2] +Piérdete, guapa. + +[LAW2B_I:LAWYER2] +¡Eh, eh, eh, eh, eh! + +[LAW2B_J:LAWYER2] +¿Eres Kent Paul? Soy amigo de Rosenberg... + +[LAW2B_K:LAWYER2] +Rosenberg... Rosenberg... ¡Oh, ese chalado abogado de pacotilla! + +[LAW2B_L:LAWYER2] +¡Ese tipo podría defender a un inocente y mandarle directamente al corredor de la muerte! + +[LAW2B_M:LAWYER2] +Ponnos otra copa, hermano. + +[LAW2B_N:LAWYER2] +Todo el mundo es cómico. + +[LAW2B_O:LAWYER2] +Escúchame, he perdido veinte kilos y un montón de pasta... + +[LAW2B_P:LAWYER2] +¿Drogas, tío? Es un juego de tontos. + +[LAW2B_Q:LAWYER2] +¿Qué sabes al respecto? + +[LAW2B_R:LAWYER2] +¡Eh, eh! A lo que iba es que, + +[LAW2B_S:LAWYER2] +hay un chef-camello que tiene su negocio en la cocina de un hotel en Ocean Drive. + +[LAW2B_T:LAWYER2] +Últimamente parece muy satisfecho de sí mismo. ¿Podrías ir y tantearle? + +[LAW2B_U:LAWYER2] +Lo haré... y ya nos veremos. + +[LAW2B_V:LAWYER2] +Sí, está bien. Venga... vete, idiota. ¡Te voy a quitar las luces de un puñetazo! + +[LAW2B_W:LAWYER2] +Ponme una copa... ¡y dónde está esa zorra! + +[LAW2C_A:LAWYER2] +Oh, así se hace, tipo duro. Machácale. Eso debería hacerle muy parlanchín. + +[LAW2C_B:LAWYER2] +¿Tú también quieres un poco? + +[LAW2C_C:LAWYER2] +Eh, relájate. Quiero lo que tú quieres, hermano. + +[LAW2C_D:LAWYER2] +¿Oh, sí? ¿Y qué es eso? + +[LAW2C_E:LAWYER2] +Tu dinero... y la nieve de mi hermano muerto. Desgraciadamente, hiciste callar a nuestro contacto. + +[LAW2C_F:LAWYER2] +Los accidentes ocurren. Piérdete. + +[LAW2C_G:LAWYER2] +Eh, eh, guau. No hay necesidad de hacerte el llanero solitario conmigo. + +[LAW2C_H:LAWYER2] +Tal y como yo lo veo... somos dos hombres en una ciudad extraña. Necesitamos cubrirnos las espaldas mutuamente. + +[LAW2C_I:LAWYER2] +Mi espalda está bien, hermano... + +[LAW2C_J:LAWYER2] +¿Estás seguro de eso? Ten, toma esto... + +[LAW2C_K:LAWYER2] +¡Sígueme! + +[LAW2_1:LAWYER2] +¿Qué estás mirando? + +[LAW2_2:LAWYER2] +Mejor empieza a hablar... + +[LAW2_3:LAWYER2] +¡Oblígame, mamón! + +[LAW2_4:LAWYER2] +¡Por aquí! + +[LAW2_5:LAWYER2] +Voy a ver qué puedo averiguar. Te estaré vigilando, Tommy. + +[LAW2_6:LAWYER2] +~g~Ve al club Malibú y busca a Kent Paul. + +[LAW2_7:LAWYER2] +~g~Ve y busca al chef en Ocean Drive. + +[LAW2_10:LAWYER2] +~g~Regresa al hotel. + +[LAW2_11:LAWYER2] +~g~Recoge su teléfono móvil. + +[LAW2_12:LAWYER2] +¡Adquirido un teléfono móvil! Ahora puedes recibir llamadas telefónicas. + +[LAW2_13:LAWYER2] +~g~¡Has dejado a Lance atrás! ¡Ve y tráele! + +[LAW2_14:LAWYER2] +¡Tenemos que salir pitando de aquí! + +[GUN_2A:LAWYER2] +¡Mantén ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! + +[GUN_2C:LAWYER2] +¡Mantén ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! + +[GUN_2D:LAWYER2] +¡Mantén ~h~~k~~PED_LOCK_TARGET~ ~w~para ~h~apuntar automáticamente~w~ y pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para ~h~disparar! + +[HELP17:LAWYER2] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para atacar al chef. + +[HELP18:LAWYER2] +Pulsa ~h~~k~~PED_FIREWEAPON~~w~ para atacar al chef. + +[LAW3_11:LAWYER2] +Sitúate en el ~q~marcador rosa~w~ para ver las armas disponibles. + +[LAW3_12:LAWYER2] +Puedes seleccionar armas pulsando ~h~izquierda~w~ o ~h~derecha~w~ en los ~h~botones de dirección~w~. + +[LAW3_13:LAWYER2] +Si dispones de suficiente dinero, puedes comprar armas pulsando ~h~~k~~PED_SPRINT~~w~. + +[LAW3_14:LAWYER2] +Puedes salir de la tienda pulsando ~h~~k~~VEHICLE_ENTER_EXIT~~w~. + +[LAW3_15:LAWYER2] +Ve al ~h~icono de la pistola~w~ que hay en el radar para encontrar el ~h~Ammu-Nation~w~. + +[LAW2_15:LAWYER2] +~g~Ve a Ammu-Nation. + +[LAW2_K:LAWYER2] +Tómatelo con calma. + +[LAW2_16:LAWYER2] +Tienes que entender una cosa sobre esta ciudad. Tienes que saber protegerte. + +[LAW2_17:LAWYER2] +Venga, la tienda de armas local está a un par de manzanas de aquí. + +[LAW2_18:LAWYER2] +Tommy, cada hombre necesita un poco de rock de vez en cuando. + +[LAW2_19:LAWYER2] +Este de aquí es el club de striptease Pole Position. Quizás quieras entrar alguna vez. + +{=================================== MISSION TABLE LAWYER3 ===================================} + +[LAW3_A:LAWYER3] +¡Aagh! ¡Oh, por el amor de Dios, eres tú! Oh, Jesús, ¡voy a necesitar pantalones nuevos! + +[LAW3_B:LAWYER3] +Eh, esos psicópatas del norte han estado entrometiéndose y pronto vendrán aquí. + +[LAW3_C:LAWYER3] +Bien, ¿dónde está el maldito dinero? + +[LAW3_D:LAWYER3] +Relájate, relájate. Aún no hemos llegado a esa parte. + +[LAW3_E:LAWYER3] +Pensé que te estabas ocupando de esto, de verdad que sí. + +[LAW3_F:LAWYER3] +Y ahora esos tipos dicen que les tenemos que hacer un favor. + +[LAW3_G:LAWYER3] +Quieres decir que yo tengo que hacerles un favor. + +[LAW3_H:LAWYER3] +Oh, por supuesto, eso es lo que quiero decir. ¿Doy yo la impresión de poder intimidar a un jurado? + +[LAW3_I:LAWYER3] +No podría intimidar a un niño y créeme, lo he intentado. + +[LAW3_J:LAWYER3] +Ahora mira, es eso, o al primo de Forelli, Giorgio, le caerán cinco años por fraude. + +[LAW3_K:LAWYER3] +¡Tienes que encargarte de esos tipos! + +[LAW3_L:LAWYER3] +Comprendo. Ayuda al jurado a que cambie de idea. No te preocupes por ello. + +[LAW3_M:LAWYER3] +¡No no no no... NO! Ya intenté eso. El caso del jurado no fue tan bien, + +[LAW3_N:LAWYER3] +así que HAZ que cambien de idea. + +[LAW3_1:LAWYER3] +Giorgio envía sus saludos. + +[LAW3_2:LAWYER3] +Recuerda, ''culpable'' es una palabra fea. + +[LAW3_3:LAWYER3] +Inocente hasta que yo diga lo contrario. + +[LAW3_4:LAWYER3] +Sabes que no es culpable. + +[LAW3_5:LAWYER3] +¿Recuerdas a Giorgio? Recuerda que es inocente. + +[LAW3_6:LAWYER3] +No culpable. ¿Comprendido? Bien. + +[LAW3_8:LAWYER3] +~r~¡Mataste a un miembro del jurado! + +[LAW3_9:LAWYER3] +~g~¡Destroza el coche del miembro del jurado para sacarle de ahí! + +[HELP40:LAWYER3] +Puedes destrozar coches usando el martillo o un arma similar. + +[HELP41:LAWYER3] +o puedes aplastarlas con un vehículo + +[LAW3_20:LAWYER3] +~g~¡Destroza el coche del miembro del jurado! + +[LAW3_21:LAWYER3] +¡No puedo creer lo que está pasando! + +[LAW3_22:LAWYER3] +¡Increíble! + +[LAW3_23:LAWYER3] +¡Ya! ¡Ya! ¡Entendido! + +[LAW3_24:LAWYER3] +~g~Ese martillo será útil. + +[LAW3_7:LAWYER3] +~g~¡Ve y coacciona a los dos miembros del jurado pero NO los mates! + +[HELP23:LAWYER3] +Puedes ir al ~h~icono del martillo~w~ que hay en el radar si quieres comprar armas blancas en la ferretería. + +[LAW3_16:LAWYER3] +Estúpido cretino de Florida. + +[LAW3_17:LAWYER3] +¡Quítate de en medio! + +{=================================== MISSION TABLE LAWYER4 ===================================} + +[LAW4_A:LAWYER4] +Avery, ni que decir tiene... ¡Tommy! ¡Tommy! ¿Algún progreso? No, no, no... cuéntamelo luego, cuéntamelo luego. + +[LAW4_B:LAWYER4] +Tommy, éste es Avery Carrington... ¿Creo que os conocisteis en la fiesta? + +[LAW4_C:LAWYER4] +No en persona. + +[LAW4_D:LAWYER4] +¿Qué tal? + +[LAW4_E:LAWYER4] +Avery tiene una proposición. + +[LAW4_F:LAWYER4] +¿No tenemos otras cosas en mente? + +[LAW4_G:LAWYER4] +Estoy intentando mantener todo bajo control así que, ¿podrías darme un respiro? + +[LAW4_H:LAWYER4] +Estoy tenso como un alambre e incluso si estoy muerto al final de la semana, me gustaría pensar que no moriré pobre. + +[LAW4_I:LAWYER4] +Bien, calmaos, los dos. + +[LAW4_J:LAWYER4] +Hijo, si me ayudas, me encargaré de que cualquier capullo que te esté molestando se eche una buena siesta. + +[LAW4_K:LAWYER4] +De acuerdo, ¿qué puedo hacer por ti? + +[LAW4_L:LAWYER4] +Esta compañía de reparto tiene el almacén en unos terrenos de primera. No venderán. + +[LAW4_M:LAWYER4] +Están aguantando como una vieja rata de la pradera, así que tenemos que ir allí y ahumar a esas alimañas para hacerlas salir. + +[LAW4_N:LAWYER4] +Ve allí y alborota el avispero. + +[LAW4_O:LAWYER4] +Los de seguridad tendrán las manos ocupadas y entonces podrás colarte y dejarles fuera de combate. + +[LAW4_P:LAWYER4] +Y podrías pasarte por Rafael para cambiarte de ropa. Podrías estar allí un rato, pero sí, ve. + +[LAW4_Q:LAWYER4] +Debería ser un motín. + +[LAW4_R:LAWYER4] +Si las cosas salen como deberían, pásate alguna vez por mi oficina... + +[LAW4_1:LAWYER4] +Por favor, dispérsense. ¡La dirección discutirá cualquier queja de la forma apropiada! + +[LAW4_2:LAWYER4] +Por favor, dispérsense. ¡Vuelvan a sus casas! + +[LAW4_3:LAWYER4] +¡Por favor, dispérsense! ¡Esto no es apropiado! + +[LAW4_4:LAWYER4] +Por favor, dispérsense. Todos acabaréis en la calle. + +[LAW4_5:LAWYER4] +¡Desenfundad, chicos! ¡Vamos a romper unos cuantos cráneos comunistas! + +[LAW4_13:LAWYER4] +~g~Empieza a luchar con al menos 4 trabajadores para iniciar una revuelta. + +[LAW4_14:LAWYER4] +~g~¡Destruye las furgonetas del complejo! + +[HELP38:LAWYER4] +Si matas a alguien que lleva un arma, la dejará caer. + +[HELP39:LAWYER4] +Puedes disparar a los barriles explosivos, pero mantén la distancia. + +{=================================== MISSION TABLE MIAMI_1 ===================================} + +[T4X4_1A:MIAMI_1] +~g~Tienes ~1~ segundos para pasar por ~y~24~g~ puntos de control. ~g~Puedes atravesarlos en ~y~CUALQUIER ORDEN. + +[T4X4_1B:MIAMI_1] +~y~ATRAVIESA~g~ el primer punto de control para poner en marcha el ~r~CRONÓMETRO. + +[T4X4_1C:MIAMI_1] +¡~1~ de 24! + +[GETBIK1:MIAMI_1] +¡Tienes ~1~ segundos para subirte en una PCJ 600! + +[GETBIK3:MIAMI_1] +~r~Necesitas una PCJ 600 para realizar esta misión! + +{=================================== MISSION TABLE MM ===================================} + +[BLOD_04:MM] +ESTADO DEL COCHE: + +[BLOD_05:MM] +~g~TIEMPO OBJETIVO: ~1~ Minuto + +[BLOD_06:MM] +~g~TIEMPO OBJETIVO: ~1~ Minutos + +[BLOD_07:MM] +NUEVO Mejor Tiempo: ~1~ Segundos + +[BLOD_08:MM] +Coches destrozados: ~1~ + +[BLOD_09:MM] +~1~ $ + +[BLOD_10:MM] +¡GANADOR! + +[BLOD_01:MM] +Pasa por todos los puntos de control para aumentar tu tiempo total. + +[BLOD_02:MM] +Fracasarás si tu tiempo total llega a cero. + +[BLOD_03:MM] +¡Consigue que tu tiempo total sean superior al objetivo a superar! + +{=================================== MISSION TABLE OVALRIG ===================================} + +[HOTR_01:OVALRIG] +~g~La carrera dura 12 vueltas. Solamente los puestos 1, 2 y 3 obtienen premios en metálico. + +[HOTR_02:OVALRIG] +~g~Si destrozas tu coche serás descalificado. + +[HOTR_03:OVALRIG] +~g~Cuando tu coche sufra daños podrás repararlo en boxes. + +[HOTR_04:OVALRIG] +~g~Este es el camino para salir del estadio. + +[HOTR_05:OVALRIG] +Estado del coche: + +[HOTR_06:OVALRIG] +Vueltas: + +[HOTR_07:OVALRIG] +Nuevo mejor tiempo: ~1~:0~1~ + +[HOTR_08:OVALRIG] +Tiempo: ~1~:~1~ + +[HOTR_10:OVALRIG] +Tiempo de carrera: + +[HOTR_09:OVALRIG] +Posición: + +[HOTR_12:OVALRIG] +~r~¡Tu coche está destrozado! + +[HOTR_13:OVALRIG] +~r~¡No ganaste la carrera! + +[HOTR_14:OVALRIG] +~r~¡Has sido descalificado! + +[HOTR_15:OVALRIG] +Tiempo: ~1~:~1~ + +[HOTR_16:OVALRIG] +Tiempo: ~1~:0~1~ + +[HOTR_17:OVALRIG] +Mejor Tiempo: ~1~:~1~ + +[HOTR_18:OVALRIG] +Mejor Tiempo: ~1~:0~1~ + +[HOTR_19:OVALRIG] +Mejor Tiempo: NA + +[HOTR_20:OVALRIG] +Nuevo Mejor Tiempo: ~1~:~1~ + +[HOTR_21:OVALRIG] +Nuevo Mejor Tiempo: ~1~:0~1~ + +[HOTR_22:OVALRIG] +Mejor Resultado: NA + +[HOTR_23:OVALRIG] +Mejor resultado: 1 + +[HOTR_24:OVALRIG] +Mejor resultado: 2 + +[HOTR_25:OVALRIG] +Mejor resultado: 3 + +[HOTR_26:OVALRIG] +Mejor resultado: ~1~ + +[HOTR_27:OVALRIG] +Mejor Tiempo de Vuelta: ~1~.~1~ segundos + +[HOTR_28:OVALRIG] +Mejor Tiempo de Vuelta: ~1~.0~1~ segundos + +[HOTR_29:OVALRIG] +~1~ $ + +[HOTR_30:OVALRIG] +PUESTO: 1 + +[HOTR_31:OVALRIG] +PUESTO: 2 + +[HOTR_32:OVALRIG] +PUESTO: 3 + +[HOTR_33:OVALRIG] +Mejor tiempo de vuelta: ninguno + +[HOTR_11:OVALRIG] +Nuevo mejor tiempo de vuelta: ~1~,~1~ segundos + +[HOTR_34:OVALRIG] +Nuevo mejor tiempo de vuelta: ~1~,0~1~ segundos + +{=================================== MISSION TABLE PHIL1 ===================================} + +[PHIL1_A:PHIL1] +¿Phil? + +[PHIL1_B:PHIL1] +¡CORRE! + +[PHIL1_C:PHIL1] +¡Corre! + +[PHIL1_E:PHIL1] +Mierda Phil, ¿te bebes ese mejunje? + +[PHIL1_F:PHIL1] +Diablos, no tienes que bebértelo... + +[PHIL1_G:PHIL1] +solo tienes que olerlo para colocarte. + +[PHIL1_H:PHIL1] +Escucha, Phil, dijiste que podrías proporcionarme algo de potencia de fuego... + +[PHIL1_I:PHIL1] +Por supuesto. + +[PHIL1_J:PHIL1] +Hay un traficante de armas mejicano que me ha estado pisando el negocio últimamente. + +[PHIL1_K:PHIL1] +Suele realizar su ronda semanal a esta hora. + +[PHIL1_L:PHIL1] +Haz caer la mercancía de la parte de atrás de sus camiones antes de que acabe fuera de nuestro alcance. + +[PHIL1_M:PHIL1] +Y mientras estás en ello me estarás haciendo un favor. + +[PHIL1_N:PHIL1] +Luego acaba con él. + +[PHI1_01:PHIL1] +~g~Ve y pilla las armas de la parte trasera de los camiones del traficante. + +[PHI1_02:PHIL1] +~g~El traficante de armas ha perdido la carga. Destroza la caja y recoge el arma. + +[PHI1_03:PHIL1] +~g~Parece que han llamado pidiendo refuerzos. + +[PHI1_04:PHIL1] +~g~Ahora ve y acaba con el resto de los traficantes. + +[PHI1_HP:PHIL1] +Cuando utilices Detonador de Granadas, lanza una granada y después provoca la explosión en cualquier momento. + +[PHIL1_O:PHIL1] +¡Jooooodeeeeer! + +[PHIL1_D:PHIL1] +¡Nunca acerques una llama a uno de los alambiques de boomshine de Phil Cassidy! + +{=================================== MISSION TABLE PHIL2 ===================================} + +[PHIL2_A:PHIL2] +Hola, Phil, ¿cómo te va? + +[PHIL2_B:PHIL2] +Ehhh, Tommy. ¿Cómo estás? Hace tanto tiempo... + +[PHIL2_C:PHIL2] +Te juro que deberías dejar ese mejunje, tío. + +[PHIL2_D:PHIL2] +Huele como decapante de pintura. Hace que me ardan los ojos... + +[PHIL2_E:PHIL2] +Shhh, shhh, tú mismo, Tommy, + +[PHIL2_F:PHIL2] +y ven aquí porque hay algo que quiero enseñarte... algo. + +[PHIL2_G:PHIL2] +¡Dios! ¿Debería poder oler eso desde aquí? Me estoy mareando. + +[PHIL2_H:PHIL2] +No te preocupes por el olor, Tommy, mira esto. + +[PHIL2_I:PHIL2] +Las malditas pilas baratas o algo así. Hay más en el banco. + +[PHIL2_J:PHIL2] +¡TA-DAAA! + +[PHIL2_K:PHIL2] +¡Maldición! + +[PHI2_01:PHIL2] +~g~Rápido, lleva a Phil al hospital. + +[PHI2_03:PHIL2] +~r~¡Phil Cassidy está muerto! ¿Ahora quién va a suministrar armas en Vice City? + +[PHI2_05:PHIL2] +¡Al hospital no, tío! ¡Demasiados polis y vietcongs! + +[PHI2_06:PHIL2] +Hay un excirujano del ejército que me debe unos cuantos favores... y un cortador de césped. + +[PHI2_07:PHIL2] +Tiene un lugar llamado Little Havana, oh mira, un pez gigante. + +[PHI2_08:PHIL2] +¡Cuidado! ¡Charlies en la línea de los árboles! + +[PHI2_09:PHIL2] +¿Soy yo o las carreteras están hechas de gelatina? + +[PHI2_10:PHIL2] +Cuchara rota a madre gallina, ¿me recibes? + +[PHI2_11:PHIL2] +¡Cucharita, rita, rit, rit! + +[PHI2_12:PHIL2] +¡Viene a por mí, chico! + +[PHI2_13:PHIL2] +Hay alas de plumas negras aleteando por todas partes... + +[PHI2_14:PHIL2] +Es hermoso, tío... es hermoso... pero hace tanto frío... + +[PHI2_15:PHIL2] +10-4 tenemos un conductor borracho. + +[PHI2_04:PHIL2] +SALUD DE PHIL: + +[PHI_AS1:PHIL2] +LOCAL DE PHIL CONSOLIDADO + +[PHI_AS2:PHIL2] +~g~Nuevas armas disponibles para comprar en el Local de Phil. + +{=================================== MISSION TABLE PIZZA ===================================} + +[PIZ1_01:PIZZA] +~g~Ve a entregar estas pizzas, debes lanzar la pizza a los clientes. Pasa cerca para lanzar las pizzas. + +[PIZ1_02:PIZZA] +~g~Has lanzado todas tus pizzas, vuelve y recoge algunas más. + +[PIZ1_05:PIZZA] +~g~Tienes cinco minutos para entregar los pedidos antes de que los clientes telefoneen a otra pizzería. + +[PIZ1_07:PIZZA] +~r~¡Mataste al cliente! Estás despedido. + +[PIZ1_08:PIZZA] +~r~Te has quedado sin tiempo. Estás despedido. + +[PIZ1_09:PIZZA] +~r~¡Destruiste nuestra moto! Estás despedido. + +[PIZ1_11:PIZZA] +¡Eh! ¡Vuelve a subir a la moto! + +[PIZ1_12:PIZZA] +Pizzas restantes: + +[PIZ1_06:PIZZA] +Pulsa ~h~~k~~TOGGLE_SUBMISSIONS~~w~ cuando estés en la moto para cancelar la misión. + +[PIZ1_13:PIZZA] +Reparte éstas bien calentitas. + +[PIZ1_14:PIZZA] +Amigo, te toca repartir éstas. + +[PIZ1_15:PIZZA] +A ver, tío, repártelas rápido. + +[PIZ1_16:PIZZA] +¿Qué estás esperando, tío? Tienes pizzas que repartir. + +[PIZ1_17:PIZZA] +Ya sé que no querías ser el chico de las pizzas, vale me importa un cuerno. + +[PIZ1_18:PIZZA] +Reparte éstas. + +[PIZ1_19:PIZZA] +Hay que repartir éstas. + +[PIZ1_20:PIZZA] +Venga hombre, reparte estas cosas o estás despedido. + +[PIZ1_21:PIZZA] +Tenemos gente esperando amigo. + +[PIZ1_22:PIZZA] +¿Qué estás esperando? ¡Hay que repartir esto! + +[PIZ1_23:PIZZA] +Haz el reparto de la maldita comida hombre. + +[PIZ1_24:PIZZA] +Hay que repartir éstas, amigo. + +[PIZ1_25:PIZZA] +Tío, ¿te puedes llevar éstas? + +[PIZ1_26:PIZZA] +Oye, haz el reparto pronto, vamos amigo. + +[PIZ1_27:PIZZA] +Venga, tenemos prisa, haz ya el reparto. + +[PIZ1_28:PIZZA] +¿Otra vez tú? Bueno, reparte éstas deprisa, amigo. + +[PIZ1_29:PIZZA] +No pierdas el tiempo amigo. + +[PIZ1_30:PIZZA] +Venga ya vago hijo de puta, reparte esta mierda de una vez. + +[PIZ1_31:PIZZA] +No tendrás nunca un ascenso a menos que te muevas más rápido esta vez. + +[PIZ1_32:PIZZA] +~r~¿La pizza es demasiado caliente para encargarte de ella? + +[PIZ1_33:PIZZA] +~g~Vuelve al restaurante a por más pedidos. + +[PIZ1_34:PIZZA] +~g~Pizza repartida, aquí está tu dinero. + +[PIZ_WON:PIZZA] +Misión de Pizza Completada. Tu salud Personal máxima ha aumentado a 150. + +{=================================== MISSION TABLE PORN1 ===================================} + +[POR1_A:PORN1] +Acción. + +[POR1_B:PORN1] +¡Guau! Eso sí que es grande. + +[POR1_C:PORN1] +30 cm, eso es para pensárselo, nene. + +[POR1_D:PORN1] +¡CORTEN! ¿Quién es este idiota? ¡Tú! ¡Tú! ¿Por qué estás en el set? ¿POR QUÉ? + +[POR1_E:PORN1] +¿De qué va toda esta basura? + +[POR1_F:PORN1] +¿Alienígenas? ¿Cañas de pescar? + +[POR1_G:PORN1] +¿Quién ha visto alguna vez a un tiburón tan grande? + +[POR1_H:PORN1] +Hay que desechar todo este material. + +[POR1_I:PORN1] +¿Por qué te metiste en este negocio, mamón? + +[POR1_J:PORN1] +¿Eh? + +[POR1_K:PORN1] +¡Por los chochos, evidentemente! ¿Qué es esto? + +[POR1_L:PORN1] +Éste es mi arte. ¡SEGURIDAD! + +[POR1_M:PORN1] +Mira, pomposo gilipollas, ahora eres de mi propiedad. Yo poseo todo esto. + +[POR1_N:PORN1] +Vamos a cambiar este lugar... + +[POR1_O:PORN1] +Voy a hacerte rico. + +[POR1_P:PORN1] +Usted es... Usted... ¿Usted es Tommy Vercetti? Pero yo creí que usted era... + +[POR1_Q:PORN1] +Eso es. + +[POR1_R:PORN1] +Vamos a hacer algunos cambios por aquí y vamos a empezar a hacer dinero de verdad. + +[POR1_S:PORN1] +En realidad, alguna vez has pensado en... + +[POR1_T:PORN1] +Pero primero, vamos a necesitar unas chicas guapas de verdad... + +[POR1_U:PORN1] +Sí, las chicas están bien, pero tú... ¡guau! + +[POR1_02:PORN1] +~g~ Ve y elimina al chulo de Candy, luego vuelve y recoge a Candy. + +[POR1_04:PORN1] +Candy. Estoy buscando una estrella de cine. ¿Estás interesada? + +[POR1_05:PORN1] +¡Claro! Pero tendrás que hablar con mi agente... + +[POR1_06:PORN1] +¿Qué DEMONIOS estás haciendo? + +[POR1_07:PORN1] +¡Hoy deberías haberte quedado en casa! + +[POR1_7B:PORN1] +¿Puedes creer a este gilipollas? + +[POR1_08:PORN1] +¡Eh, Mercedes! + +[POR1_09:PORN1] +¡Eh, Tommy! ¿Quieres ir de fiesta? + +[POR1_10:PORN1] +Ahora no, preciosa. ¿Te interesa hacer algunas películas? + +[POR1_11:PORN1] +Por supuesto. Mientras que sea una peli barata y cutre. + +[POR1_13:PORN1] +~g~Lleva a las chicas de vuelta al Estudio para que conozcan a Steve. + +[POR1_14:PORN1] +¡Contratada! + +[POR1_15:PORN1] +Eh Tommy, ¿¡qué, vienes a entrar en calor!? + +[POR1_17:PORN1] +¡Guau, un tiburón genial! + +[POR1_18:PORN1] +~r~¡Mercedes está muerta! + +[POR1_20:PORN1] +Tommy, ¿a dónde vas? ¡Vuelve aquí! + +[POR1_21:PORN1] +¿A dónde vas? + +[POR1_22:PORN1] +Tommy, ¿cuando vamos a pasar algún tiempo a solas? + +[POR1_01:PORN1] +~g~¡Candy Suxxx sería perfecta para el papel principal! + +[POR1_12:PORN1] +~g~Ve con Candy a encontrarte con Mercedes. + +[POR1_16:PORN1] +Quizás después, nena... + +[POR1_24:PORN1] +~g~Vuelve y recoge a Candy. + +[POR1_25:PORN1] +~g~Te has dejado atrás a Candy, ve y tráela. + +[POR1_23:PORN1] +~g~Candy estará ocupándose de unos asuntos en el ~h~centro de la ciudad~g~. + +[POR1_26:PORN1] +~g~Aquí está Candy, parece que ha estado otra vez con el congresista Shrub. + +[POR1_27:PORN1] +Venga, vamos. + +[POR1_28:PORN1] +¡Tommy ten cuidado! ¡Aún no he asegurado mis implantes! + +[POR1_29:PORN1] +¿Y a esto le llamas conducir? + +[POR1_30:PORN1] +¡No podré hacer ningún numerito porno después de esto! + +[POR1_31:PORN1] +¿Qué? ¿Me estás intentando matar? ¡Creía que era la estrella era yo! + +{=================================== MISSION TABLE PORN2 ===================================} + +[POR2_A:PORN2] +¿Cómo va el rodaje, Steve? + +[POR2_B:PORN2] +Bien, Candy es una fuera de serie y esa chica nueva ¡es insaciable! + +[POR2_C:PORN2] +Se repasó a la mitad del reparto antes de que pudiese medir la luz. + +[POR2_D:PORN2] +De todas formas, eh, mañana vamos a exteriores a rodar las escenas del barco... + +[POR2_E:PORN2] +¿Escenas del barco, qué escenas del barco? + +[POR2_F:PORN2] +Los pescadores son presa de la pasión cuando aparece el tiburón gigante... + +[POR2_G:PORN2] +¿Qué te dije del tiburón gigante? + +[POR2_H:PORN2] +Dije, ''SIN TIBURÓN GIGANTE'', ¿de acuerdo? + +[POR2_I:PORN2] +¡Tú solo tienes que mantener las cámaras apuntando a los conejos! + +[POR2_J:PORN2] +De acuerdo, de acuerdo, eh Tommy, uno tiene que intentarlo, ¿de acuerdo? + +[POR2_K:PORN2] +¿Has hecho que impriman esos folletos? + +[POR2_L:PORN2] +Sí, pero nadie va a distribuir esas cosas, quiero decir que... + +[POR2_M:PORN2] +son demasiado... poco imaginativos. + +[POR2_N:PORN2] +No te preocupes por eso. + +[POR2_O:PORN2] +Tengo mis propias ideas para la distribución. + +[POR2_P:PORN2] +Vale. Candy, ven a mi caravana. + +[POR2_01:PORN2] +~g~Hay un hidroavión que se utilizó como atrezzo en una antigua película independiente a la vuelta de los estudios. + +[POR2_02:PORN2] +~g~Escoge uno de los puntos de control para empezar a dejar caer los folletos. + +[POR2_03:PORN2] +~g~Deja caer los folletos por todo el camino hasta el último punto de control. + +[POR2_04:PORN2] +~r~¡COMBUSTIBLE BAJO! + +[POR2_05:PORN2] +Utilízalo para distribuir los folletos por toda la ciudad. + +[DILDO:PORN2] +Combustible del Skimmer: + +[POR2_Q:PORN2] +¡Jo!, tío. + +[PORN2_9:PORN2] +~g~Tienes ~1~ segundos para volver a subir a una Skimmer antes de que termine la misión. + +{=================================== MISSION TABLE PORN3 ===================================} + +[POR3_A:PORN3] +Vale, ¿cuál es el problema ahora? + +[POR3_B:PORN3] +¡SSShhhh! + +[POR3_C:PORN3] +Bien, después de este encuentro cercano con las invasoras ninfómanas + +[POR3_D:PORN3] +nuestro héroe se encuentra incapaz de pensar en nada excepto en esta enorme montaña fálica... + +[POR3_E:PORN3] +y es entonces cuando queremos hacer la escena con la tina de puré de patatas, pero entonces... + +[POR3_F:PORN3] +¡Me importa una mierda! + +[POR3_G:PORN3] +¡Sigue, sigue! + +[POR3_H:PORN3] +Eh Tommy... + +[POR3_I:PORN3] +¿Mencionaste algo sobre un problema legal al teléfono? + +[POR3_J:PORN3] +¡Vaya que sí! El congresista Alex Shrub se ha subido al tren preelectoral y va tras el voto puritano. + +[POR3_K:PORN3] +Hay rumores de que va a apoyar medidas restrictivas, digamos, + +[POR3_L:PORN3] +para el aspecto más lujurioso de la gran industria del entretenimiento de esta nación. + +[POR3_M:PORN3] +Genial. + +[POR3_N:PORN3] +¡Candy! Tú conoces a Shrub, + +[POR3_O:PORN3] +¿llegasteis a algo pervertido? + +[POR3_P:PORN3] +¡Oh sí, oh sí, oh sí! ¡Sí sí sí SÍ! ¡OOOoooh! + +[POR3_Q:PORN3] +Por favor dime que filmaste eso. + +[POR3_R:PORN3] +¿Eso era parte del eh... o estaba hablando conmigo...? + +[POR3_S:PORN3] +Eh, uno nunca podría decirlo... De todas formas... + +[POR3_T:PORN3] +Probablemente sea mejor que la sigas después del rodaje, + +[POR3_U:PORN3] +para ver si te lleva a su nuevo nido de amor. + +[POR3_V:PORN3] +¿Tienes una cámara? + +[POR3_X:PORN3] +Vale, conseguidle una cámara. + +[POR3_02:PORN3] +~r~¡Has asesinado al Congresista! Ahora no habrá modo de que puedas chantajearle. + +[POR3_03:PORN3] +~r~Has alertado a la protección del Congresista, le sacarán de aquí inmediatamente. + +[POR3_04:PORN3] +Candy, ¿podrías llamarme Marta? + +[POR3_05:PORN3] +Oh Alex, quiero decir Marta. Lo que tú digas. + +[POR3_06:PORN3] +Marta, alguien está mirando... qué pervertido. + +[POR3_07:PORN3] +Tú, el de ahí. Dame esa cámara. + +[POR3_01:PORN3] +~g~Sigue la ~h~limusina~g~ de Candy. + +[POR3_15:PORN3] +~r~¡Has destruido la limusina de Candy! + +[POR3_17:PORN3] +~g~Regresa a la compañía de pornografía con las fotos. + +[POR3_19:PORN3] +~r~¡Te has quedado sin película! + +[POR3_21:PORN3] +~g~¡Has perdido la limusina de Candy! + +[POR3_22:PORN3] +~g~El Hotel WK Chariot frente a este balcón debería proporcionar el lugar ideal para la sesión fotográfica. + +[POR3_23:PORN3] +~g~Hay una puerta lateral que te permitirá acceder al hotel. + +[POR3_08:PORN3] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para~h~ enfocar~w~ con la cámara. + +[POR3_09:PORN3] +Mantén pulsado ~h~~k~~PED_LOCK_TARGET~ ~w~para~h~ enfocar~w~ con la cámara. + +[POR3_10:PORN3] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~ ~w~para ~h~acercar el zoom ~w~con la cámara y ~h~~k~~PED_SNIPER_ZOOM_OUT~ ~w~para ~h~alejarlo ~w~de nuevo. + +[POR3_11:PORN3] +Pulsa ~h~~k~~PED_SNIPER_ZOOM_IN~ ~w~para ~h~acercar el zoom ~w~con la cámara y ~h~~k~~PED_SNIPER_ZOOM_OUT~ ~w~para ~h~alejarlo ~w~de nuevo. + +[POR3_12:PORN3] +Pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para sacar una foto. + +[POR3_13:PORN3] +Pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para sacar una foto. + +[POR3_14:PORN3] +Pulsa ~h~~k~~PED_FIREWEAPON~ ~w~para sacar una foto. + +[POR3_20:PORN3] +~g~Si necesitas transporte, utiliza el ~h~Sparrow~g~ que está en la parte de atrás. + +[POR3_16:PORN3] +~g~Necesitas tres buenas fotos de Alex Shrub con Candy para chantajearle. + +[POR3_24:PORN3] +FOTOS HECHAS: + +{=================================== MISSION TABLE PORN4 ===================================} + +[POR4_A:PORN4] +Lo siento, pero no puedo tragarme eso ahora. + +[POR4_B:PORN4] +¡Oh, VENGA, cariño! + +[POR4_C:PORN4] +La tiene como un cachalote, por el amor de Dios, + +[POR4_D:PORN4] +¿cómo es que no puedes sentir el papel? + +[POR4_E:PORN4] +Pero Stevie... + +[POR4_F:PORN4] +¿Cómo está mi director estrella? + +[POR4_G:PORN4] +Oh, tío. La lucha entre la integridad artística y + +[POR4_H:PORN4] +la acción cargante y bombeante continúa sin perder intensidad. + +[POR4_I:PORN4] +Y antes de que puedas preguntarlo, sí, los cuatro vídeos serán lanzados en sus... + +[POR4_J:PORN4] +Cariño, podrías POR FAVOR mantener la anaconda dentro del plano, + +[POR4_K:PORN4] +¡cuesta por hora más de lo que cuestas tú! + +[POR4_L:PORN4] +Oh, lo siento, Steve. + +[POR4_M:PORN4] +Estaba pensando, necesitamos algún tipo de gran truco publicitario para promover de verdad el lanzamiento. + +[POR4_N:PORN4] +Algo que tenga un verdadero impacto en la ciudad, ¿tienes alguna idea? + +[POR4_O:PORN4] +Bueno, en los viejos tiempos solían organizar fiestas, + +[POR4_P:PORN4] +estrellas, limusinas, el cielo nocturno con los focos entrecruzados... + +[POR4_Q:PORN4] +¿Focos? Tengo una idea... + +[POR4_R:PORN4] +...sí, sí, sí. Los numeritos de lentejuelas, y las limusinas, oh, premieres. + +[POR4_S:PORN4] +Oh, sí, madam, por supuesto madam, + +[POR4_T:PORN4] +y la prensa, y el bombardeo de luces... + +[POR4_01:PORN4] +~g~Ve al ~y~centro~g~ y ajusta los focos sobre lo alto del edificio. + +[POR4_02:PORN4] +~g~Necesitarás una moto rápida para saltar de un tejado a otro. El guardia de seguridad normalmente lleva una ~y~PCJ 600~g~ para ir a trabajar... + +[POR4_03:PORN4] +~g~Necesitarás subir a los tejados de los edificios. Debería de haber un ascensor que lleve a una de las oficinas superiores... + +[POR4_06:PORN4] +~g~Vuelve a la oficina inferior si de nuevo necesitas acceso a los tejados. + +[POR4_07:PORN4] +~g~Necesitarás una moto para poder saltar de un edificio a otro. + +[POR4_08:PORN4] +~g~Atraviesa la ventana para empezar el recorrido. Tienes hasta las 07:00 antes de que haya demasiada luz como para subir allí sin ser visto. + +[POR4_09:PORN4] +~g~Los marcadores rosas te mostrarán a qué edificio saltar después. + +[POR4_10:PORN4] +~r~Hay demasiada luz como para subir ahí sin ser visto. + +[POR4_11:PORN4] +~g~Vuelve a la escalera si necesitas acceder de nuevo a los tejados. + +[POR4_05:PORN4] +~g~Estas escaleras llevan a una oficina en el piso inferior. + +[POR_AS1:PORN4] +ESTUDIO CINEMATOGRÁFICO CONSOLIDADO + +[POR_AS2:PORN4] +~g~Inter Global Films generará ahora unos ingresos máximos de hasta ~1~ $. Asegúrate que lo recaudas de forma regular. + +{=================================== MISSION TABLE PROT1 ===================================} + +[PRO1_B:PROT1] +No puedo soportar este look. Tommy, ¿Tú que dices? ¿Qué te parece si ponemos un bar en... + +[PRO1_D:PROT1] +Escúchame, + +[PRO1_E:PROT1] +Es hora de tomar esta ciudad. Está ahí, esperándonos. + +[PRO1_F:PROT1] +Necesitamos empezar a ampliar nuestro territorio, + +[PRO1_G:PROT1] +vamos a dejar que Vice City sepa que somos los nuevos tíos importantes de la ciudad, ¿sabes a lo que me refiero? + +[PRO1_I:PROT1] +Lo que necesitas es un frente legítimo, Tommy, una verdadera propiedad. A mí nunca me ha hecho daño. + +[PRO1_J:PROT1] +Necesitamos empezar a utilizar la fuerza o podemos despedirnos de todo el trabajo duro que hemos hecho. + +[PRO1_K:PROT1] +¡Los negociantes locales saben que Díaz está muerto, y se niegan a pagar la protección! + +[PRO1_L:PROT1] +Oh. Podríamos intentar el soborno... + +[PRO1_M:PROT1] +¿Soborno? ¡Que le jodan al soborno! Te mostraré cómo hacer que se asusten. + +[PRO1_01:PROT1] +~g~Ve a atracar los escaparates de las tiendas y tendrás a los propietarios suplicando protección. + +[PRO1_03:PROT1] +~r~Se suponía que deberías atacar y salir corriendo, no atacar y tomarte un café. + +[PRO1_04:PROT1] +¡Mi sustento, destruido! + +[PRO1_05:PROT1] +¡Arruinado... ESTOY ARRUINADO! + +[PRO1_06:PROT1] +¡Pagaré lo que sea por protección! + +[PRO1_07:PROT1] +¡Mi hermoso escaparate destruido! + +[PRO1_08:PROT1] +Mi tienda. Mi maravillosa tienda. + +[PRO1_09:PROT1] +Vercetti. Recuerda el nombre. + +[PRO1_10:PROT1] +Ahora dirijo esta ciudad. ¡YO! + +[BUYP1:PROT1] +Ahora puedes comprar propiedades en ciertas zonas de la ciudad. + +[BUYP2:PROT1] +Si ves un indicador con una casa verde, puedes comprar esa propiedad. + +[PRO1_N:PROT1] +Volveré aquí en cinco minutos... + +[PRO1_11:PROT1] +~g~Ve al ~y~Centro Comercial North Point~g~ en ~y~Vice Point~g~. + +[PRO1_12:PROT1] +~g~Destroza los escaparates de todas las tiendas y los propietarios te suplicarán para obtener nueva protección. + +[PRO1_A:PROT1] +Oh, tenemos que volver a pintar este sitio. Tenemos que hacer que parezca más viejo. + +[PRO1_C:PROT1] +Rosenberg, eres mi abogado, no mi diseñador de interiores, ¿lo entiendes? + +[BUYP3:PROT1] +Sitúate en el marcador, a continuación pulsa ~h~~k~~PED_ANSWER_PHONE~~w~ para comprar la propiedad. + +[PRO1_13:PROT1] +~g~Dispones de cinco minutos para machacarlos a todos. + +{=================================== MISSION TABLE PROT2 ===================================} + +[PRO2_A:PROT2] +¿Cuál es el problema? + +[PRO2_B:PROT2] +Un bar se niega a pagar. + +[PRO2_C:PROT2] +Creen que están protegidos por una banda de matones locales. + +[PRO2_D:PROT2] +Pero no te preocupes, Tommy, yo me encargo. + +[PRO2_E:PROT2] +¿Llamas a esto encargarte? + +[PRO2_F:PROT2] +Vosotros dos, en pie... + +[PRO2_G:PROT2] +Vamos. + +[PRO2_01:PROT2] +~g~Elimina a los guardias que vigilan el Bar Front Page y descubre quién los ha puesto ahí. + +[PRO2_10:PROT2] +~g~Dos más han huido. Sígueles la pista y acaba con ellos. + +[PRO2_11:PROT2] +Sube al coche, inútil. + +[PRO2_02:PROT2] +Tu seguridad necesita un poco más de seguridad. + +[PRO2_03:PROT2] +¡Ahh! demonios, ¡otra vez no! ¡no necesito esta basura! + +[PRO2_04:PROT2] +Estos idiotas pertenecen al DBP de Seguridad que hay a la vuelta de la esquina. + +[PRO2_05:PROT2] +¡Eh tíos! Arregladlo entre vosotros. + +[PRO2_06:PROT2] +Nos vemos en un rato. + +[PRO2_07:PROT2] +Sí, vale, lo que tú digas. + +[PRO2_08:PROT2] +~g~El DBP de Seguridad sabrá que estás de camino, ve y atrápalos antes que escapen. + +[PRO2_09:PROT2] +~g~Ve y habla con el dueño del bar Front Page. + +{=================================== MISSION TABLE PROT3 ===================================} + +[PRO3_A:PROT3] +¡Idiota! ¿En qué estabas pensando? + +[PRO3_B:PROT3] +¿Te das cuenta de lo que significa ésto? + +[PRO3_C:PROT3] +¡Podríamos estar todos hundidos! + +[PRO3_D:PROT3] +El temporizador debe haberse jodido. + +[PRO3_E:PROT3] +Ese lugar estaba preparado para estallar como una fábrica de fuegos artificiales. + +[PRO3_F:PROT3] +Entonces alguien avisó a la poli... + +[PRO3_G:PROT3] +¿Cuál es el problema, colegas? + +[PRO3_H:PROT3] +Se suponía que Mike iba a quemar algún sitio en el centro comercial, + +[PRO3_I:PROT3] +pero la cagó con los fusibles y ahora eso está plagado de polis. + +[PRO3_J:PROT3] +¡Tenemos que conseguir nuestras cosas y salir de aquí! + +[PRO3_K:PROT3] +¡Relajaos los dos, dejadme pensar un segundo! + +[PRO3_L:PROT3] +Tommy Vercetti no corta por lo sano y huye. + +[PRO3_M:PROT3] +Los polis van a repasar ese edificio con un buen cepillo de dientes, ¿verdad? + +[PRO3_N:PROT3] +Pero eso lleva tiempo. + +[PRO3_O:PROT3] +Tenemos que ir y quemar nosotros mismos ese sitio. + +[PRO3_P:PROT3] +Sí, pero... + +[PRO3_Q:PROT3] +¡Nadie excepto un poli podría acercarse a una milla de ese lugar! + +[PRO3_R:PROT3] +Así que iremos como polis. + +[PRO3_S:PROT3] +Tenemos que conseguir uniformes, y vamos a necesitar un coche patrulla. + +[PRO3_T:PROT3] +Todo gracias a ti, Mike. + +[PRO3_U:PROT3] +Lo siento. + +[PRO3_V:PROT3] +Lo capto. + +[PRO3_W:PROT3] +Lo que tenemos que hacer es atraer a los polis enseñándoles el dedo, + +[PRO3_X:PROT3] +encerrarles + +[PRO3_Y:PROT3] +y asaltarles. + +[PRO3_Z:PROT3] +Buen plan. ¡Vamos! + +[PRO3_A1:PROT3] +De acuerdo. + +[PRO3_01:PROT3] +De acuerdo, Lance, ¡vamos a por los polis! + +[PRO3_02:PROT3] +~g~ Roba el coche de policía y coloca la bomba en el Café Tarbrush del centro comercial. + +[PRO3_03:PROT3] +~g~ Has dejado atrás a Lance, vuelve y recógele. + +[PRO3_04:PROT3] +~g~ Vamos. + +[PRO3_05:PROT3] +~r~¡Has matado a Lance! + +[PRO3_07:PROT3] +~g~ Te has descubierto. ¡Date prisa y coloca la bomba! + +[PRO3_08:PROT3] +~g~Vuelve a la ~h~finca de Vercetti~g~ en ~h~Starfish Island~g~. + +[PRO3_09:PROT3] +¡Átales y amordázales! + +[PRO3_10:PROT3] +¡Me queda como un guante! + +[PRO3_11:PROT3] +Un poco ajustado en la entrepierna... + +[PRO3_12:PROT3] +Oh, sí, sí, el mío también. El mío también. + +[PRO3_13:PROT3] +¡Relájate, hermano! ¡Ningún poli conduce tan mal! + +[PRO3_14:PROT3] +Recuerda, sonríe a los otros polis. + +[PRO3_15:PROT3] +Hola, agente. Bonita placa, bonita placa. + +[PRO3_16:PROT3] +Muy desenvuelto, Lance. + +[PRO3_17:PROT3] +De acuerdo, los temporizadores están colocados, 5 segundos. + +[PRO3_18:PROT3] +¿5 segundos? ¡Tenemos que salir de aquí pitando! + +[PRO3_19:PROT3] +Ahora que los hemos enfadado de verdad. + +[PRO3_20:PROT3] +~g~ Haz que dos polis te sigan hasta el garaje. + +[PRO3_21:PROT3] +~g~Consigue un nivel de se busca suficiente para que los polis te sigan hasta el garaje. + +[PRO3_22:PROT3] +~g~¡La puerta del garaje está bloqueada! El acceso tiene que estar despejado para que se pueda cerrar. + +[PRO3_23:PROT3] +~g~Camina hacia el marcador para colocar la bomba. + +[PRO3_24:PROT3] +~g~¡Aléjate del Café! + +[PRO_AS1:PROT3] +ANILLO DE PROTECCIÓN COMPLETADO + +[PRO_AS2:PROT3] +~g~La finca de Vercetti generará ahora unos ingresos máximos de hasta ~1~ $. Asegúrate de recaudarlo regularmente. + +{=================================== MISSION TABLE RACES ===================================} + +[RACES_2:RACES] +~g~¡Necesitas un vehículo para correr, esto no es una carrera a pie! + +[RACES_3:RACES] +3... 2... 1... ¡ADELANTE! ¡ADELANTE! ¡ADELANTE! + +[RACES_8:RACES] +~r~¡No has ganado la carrera! + +[RACES00:RACES] +Carrera ~1~: + +[RACES01:RACES] +Velocidad terminal + +[RACES02:RACES] +Ocean Drive + +[RACES03:RACES] +Carrera por el borde + +[RACES04:RACES] +Capital Cruise + +[RACES05:RACES] +¡Tour! + +[RACES06:RACES] +Aguante en V.C. + +[RACES07:RACES] +Precio de entrada: ~1~ $ + +[RACES08:RACES] +Mejor tiempo: ~1~:~1~ + +[RACES09:RACES] +Mejor resultado: 1 + +[RACES10:RACES] +Mejor resultado: 2 + +[RACES11:RACES] +Mejor resultado: 3 + +[RACES12:RACES] +Mejor resultado: 4 + +[RACES13:RACES] +Longitud de pista: ~1~.~1~ km + +[RACES15:RACES] +Mejor tiempo: NA + +[RACES16:RACES] +Mejor resultado: NA + +[RACES19:RACES] +No puedes permitirte entrar en esta carrera. + +[RACES22:RACES] +Mejor tiempo: ~1~:0~1~ + +[RACES23:RACES] +Longitud de la pista: ~1~.~1~ millas + +[RACES_1:RACES] +~g~Consigue un vehículo rápido y ve a la parrilla de salida. + +[RACEHLP:RACES] +~w~Pulsa ~h~~k~~PED_SPRINT~~w~ para empezar la carrera seleccionada. Pulsa ~h~~k~~VEHICLE_ENTER_EXIT~~w~ para salir. + +{=================================== MISSION TABLE RCHELI1 ===================================} + +[WRECKED:RCHELI1] +~r~¡El vehículo está destrozado! + +[RCH1_4:RCHELI1] +Puntos de control: + +[RCH1_7:RCHELI1] +~g~Hay 20 puntos de control en total. + +[RCH1_12:RCHELI1] +~g~¡El helicóptero RC está saliendo fuera de su alcance de control! + +[RCH1_13:RCHELI1] +~r~¡El helicóptero RC se ha salido de su radio de alcance! + +[RCH1_8:RCHELI1] { reVC update } +~g~Si deseas abandonar esta misión, pulsa ~h~~k~~VEHICLE_FIREWEAPON~~g~ para detonar tu helicóptero RC. + +{=================================== MISSION TABLE RCPLNE1 ===================================} + +[RCPL1_4:RCPLNE1] +~g~Compite en una CARRERA DE PUNTOS DE CONTROL contra otros tres aviones RC. + +[RCPL1_5:RCPLNE1] +~g~Vuela a través de los puntos de control dispersados por Vice City. + +[RCPL1_6:RCPLNE1] { reVC update } +~g~Si deseas abandonar esta misión, pulsa ~h~~k~~VEHICLE_FIREWEAPON~~g~ para detonar tu avión RC. + +[RCPL1_8:RCPLNE1] +~g~¡Tu avión RC está saliendo fuera del alcance! + +[RCPL1_9:RCPLNE1] +~r~¡Tu avión RC se salió del alcance! + +{=================================== MISSION TABLE RCRACE1 ===================================} + +[RCRC1_1:RCRACE1] +~g~Compite en una CARRERA DE PUNTOS DE CONTROL contra 3 Barón RC durante dos vueltas. + +[RCRC1_3:RCRACE1] +~g~¡Vuelta final! + +[RCR1_4:RCRACE1] +vueltas restantes: + +[RCR1_1:RCRACE1] +~g~Compite en una carrera de puntos de control contra otros tres coches RC. + +[RCR1_2:RCRACE1] +~g~¡Consigue ser el primero en completar las dos vueltas del circuito para ganar! + +[RCR1_6:RCRACE1] +~g~¡Tu coche RC se está saliendo fuera del alcance! + +[RCR1_7:RCRACE1] +~r~¡Tu coche RC se salió del alcance! + +{=================================== MISSION TABLE ROCK1 ===================================} + +[RBM1_A:ROCK1] +~n~ + +[RBM1_B:ROCK1] +¡Brillante, jodidamente brillante! + +[RBM1_D:ROCK1] +¿Eh, habías conocido antes a los Love Fist? + +[RBM1_E:ROCK1] +No, pero siempre me encantó vuestra música. + +[RBM1_F:ROCK1] +Déjame presentarte a la banda. + +[RBM1_G:ROCK1] +Estos son P, Percy, Dick, y Willy está en el kaze, y ese de antes de la cabina era Jezz, + +[RBM1_H:ROCK1] +y tíos, quiero que conozcáis a un buen amigo mío. + +[RBM1_I:ROCK1] +Este es Tommy. Nos conocemos desde hace mucho. + +[RBM1_J:ROCK1] +De acuerdo, colega. + +[RBM1_K:ROCK1] +Y, eh, ¿cuál era tu nombre? + +[RBM1_L:ROCK1] +Déjalo, Jezz, lo recuerdas, + +[RBM1_M:ROCK1] +no juegues a eso conmigo, colega, + +[RBM1_N:ROCK1] +¡soy demasiado astuto para eso, encanto! + +[RBM1_O:ROCK1] +Verás, Tom, la cosa es que los chicos necesitan algo de ayuda. + +[RBM1_P:ROCK1] +No tienen muchos contactos en la zona, gente a la que preguntarle ''¿vienes mucho por aquí?'' y todo eso. + +[RBM1_Q:ROCK1] +¡Necesitamos unos tiritos, colega! + +[RBM1_R:ROCK1] +¿Vamos a pillar la vieja furia Love Fist, sabes? + +[RBM1_S:ROCK1] +Bueno, esto es Vice City, tío. ¿Cuál es el problema? + +[RBM1_U:ROCK1] +¡Love juice, tío! + +[RBM1_V:ROCK1] +¿Love juice? + +[RBM1_W:ROCK1] +Sí, dos partes de boomshine, una parte de farlopa, cinco caramelos con picapica y un litro de gasolina. + +[RBM1_X:ROCK1] +¿Puedes ayudarnos, colega? + +[RBM1_Y:ROCK1] +De verdad significaría mucho para los chicos. + +[RBM1_Z:ROCK1] +Puedes hacer eso por los muchachos, ¿verdad? + +[RBM1_7:ROCK1] +~r~¡No conseguiste a tiempo el ''love juice''! + +[RBM1_8:ROCK1] +~r~¡Mercedes está muerta! + +[RBM1_10:ROCK1] +~r~¡Idiota! ¡Has destruido el material! + +[RBM1_13:ROCK1] +~g~Lleva el ''love juice'' y a Mercedes a la banda antes de que sean requeridos en el escenario. + +[RBM1_15:ROCK1] +~r~¡Has perdido al camello, nuestra pasta y la nieve! + +[RBM1_17:ROCK1] +~g~¡Mata al camello y consigue los ingredientes! + +[MOB_07A:ROCK1] +Eh colega, a los chicos les vendría bien algo de compañía, si entiendes lo que quiero decir... + +[MOB_07B:ROCK1] +Conozco a la chica apropiada. + +[ROK1_5:ROCK1] +¡Hola, Mercedes! + +[ROK1_6:ROCK1] +Hola, Tommy. ¿Cómo estás? + +[ROK1_7:ROCK1] +Bien. Escucha, ¿te apetece acostarte con los Love Fist? + +[ROK1_8:ROCK1] +De acuerdo, pero sólo como un favor que espero que me sea devuelto... + +[RBM1_14:ROCK1] +~g~¡Necesitas un coche o una moto! + +[RBM1_1:ROCK1] +~g~Ve y recoge a Mercedes en su apartamento. + +[RBM1_12:ROCK1] +~g~Ve y reúnete con el camello para conseguir los ingredientes del ''love juice''. + +[ROK1_2:ROCK1] +YA NO ES NECESARIO + +[ROK1_3:ROCK1] +YA NO ES NECESARIO + +[MERC_39:ROCK1] +Hasta luego grandullón. + +[RBM1_C:ROCK1] +¡Eh, Tommy! Me alegro que pudieras venir. + +[RBM1_9:ROCK1] +~g~¡Ve y reúnete con el camello de los Love Fist para conseguir algo de ''love juice''! + +[ROK1_1A:ROCK1] +¿Buscas algo especial? ¡Yo tengo lo que necesitas! + +[ROK1_9:ROCK1] +¡Gracias por el dinero, mamón! + +[RBM1_T:ROCK1] +Necesitamos ''love juice'', tío, ¿sabes? + +{=================================== MISSION TABLE ROCK2 ===================================} + +[RBM2_A:ROCK2] +Tommy, tío. ¡Me alegro de verte! + +[RBM2_B:ROCK2] +¿Qué sucede? + +[RBM2_C:ROCK2] +Malas vibraciones, Tommy... + +[RBM2_E:ROCK2] +Hay un tipo, apenas le conocemos, pero él nos conoce a nosotros. + +[RBM2_F:ROCK2] +Como este tipo, que lo sabe todo de nosotros. + +[RBM2_G:ROCK2] +Sabe que a Willy le gusta ponerse ropa interior de mujeres. + +[RBM2_H:ROCK2] +¡O que a Percy le gusta Duran Duran! + +[RBM2_K:ROCK2] +Sí, bien, tu cohete del amor. Pero escucha, este tipo... + +[RBM2_L:ROCK2] +El tipo, quiere ver a los Love Fist muertos. + +[RBM2_M:ROCK2] +Muertos, Tommy. + +[RBM2_N:ROCK2] +El final Love Fist. Ya sabes lo que dicen, los buenos mueren jóvenes. + +[RBM2_O:ROCK2] +¡Pero Tommy, tienes que salvar a los Love Fist! + +[RBM2_P:ROCK2] +Tenemos que firmar en dos horas y creo... + +[RBM2_Q:ROCK2] +Los chicos creen que el sospechoso va a intentar hacer algún recibimiento infame. + +[RBM2_1:ROCK2] +~g~Conduce la limusina a la firma de autógrafos e intenta encontrar al psicópata. + +[RBM2_2:ROCK2] +~r~¡Destrozaste el coche de la banda! + +[RBM2_3:ROCK2] +~g~¡Ve a la firma de autógrafos! + +[RBM2_4:ROCK2] +~g~¡Atrapa al psicópata! ¡No le dejes escapar! + +[RBM2_5:ROCK2] +~r~¡Lo perdiste, idiota! + +[RBM2_7:ROCK2] +~r~¡Los fans han sido atacados, el psicópata no aparecerá! + +[RBM2_8:ROCK2] +~r~¡Los guardas de seguridad han sido atacados, el psicópata no aparecerá! + +[PSYCH_1:ROCK2] +¡Me encantaría ver arder a los Love Fist! + +[PSYCH_2:ROCK2] +¡Los Love Fist arruinaron mi vida! + +[RBM2_I:ROCK2] +Cállate, idiota. Sólo porque Jezz se folla ovejas. + +[RBM2_R:ROCK2] +¡Eh, cierra la boca! + +[RBM2_D:ROCK2] +Sí, no estoy de broma, es un tema muy gordo tío, muy gordo, ¿sabes? + +[RBM2_J:ROCK2] +Es lo que atrae a mi cohete del amor, ¿entiendes? + +{=================================== MISSION TABLE ROCK3 ===================================} + +[RBM3_A:ROCK3] +¡Tommy! ¡Tommy! ¡Tommy, tío, ese psicópata ha vuelto! + +[RBM3_B:ROCK3] +¿Qué está pasando? + +[RBM3_C:ROCK3] +¡Ese psicópata no dejará en paz a los Love Fist! + +[RBM3_D:ROCK3] +No le mataste, tío. Y ahora ha vuelto. + +[RBM3_E:ROCK3] +Sí, sí, sí, y la cuestión es... + +[RBM3_F:ROCK3] +La cuestión es que necesitamos alguien en quien poder confiar para que conduzca la limo, + +[RBM3_G:ROCK3] +¡porque ese chiflado sigue con las amenazas! + +[RBM3_I:ROCK3] +Aquí estamos todos acojonados, tío. + +[RBM3_J:ROCK3] +De acuerdo tíos, calmaos, yo me encargaré de esto. + +[RBM3_K:ROCK3] +Normalmente, no me ocuparía de ir por ahí haciendo de chófer para una panda de bisexuales escoceses bebidos, + +[RBM3_L:ROCK3] +pero, en vuestro caso, haré una excepción. + +[RBM3_4:ROCK3] +~r~¡Has matado a los Love Fist! + +[RBM3_6:ROCK3] +DETONACIÓN: + +[RBM3_1:ROCK3] +~g~Conduce a los Love Fist al local del concierto. + +[RBM3_2:ROCK3] +Si intentas abandonar el coche mientras la bomba está armada explotará... + +[RBM3_3:ROCK3] +Si la barra de detonación se llena completamente la bomba explotará. + +[RBM3_8:ROCK3] +Cuanto más rápido conduzcas más despacio se moverá la barra de detonación. + +[RBM3_7:ROCK3] +~g~¡BOMBA DESACTIVADA! + +[ROK3_62:ROCK3] +y pensamos que podríamos enseñarte nuestro Templo del Rock. + +[ROK3_63:ROCK3] +¡Para que te vayas acostumbrando a la furia de Love Fist! + +[ROK3_64:ROCK3] +Escúchate a ti mismo, tío. Es papel maché y cinta aislante. + +[ROK3_65:ROCK3] +Eh, para los chavales, ¡esto es un templo y nosotros somos los sacerdotes! + +[ROK3_66:ROCK3] +Si, vale, si a los chavales les gustan los sacerdotes colgados y sin oído musical, + +[ROK3_67:ROCK3] +¿quién soy yo para discutirlo? + +[ROK3_68:ROCK3] +Vaya por Dios, se está comiendo la cinta otra vez. + +[ROK3_69:ROCK3] +Con este ritmo tendremos que tocar en directo. + +[ROK3_70:ROCK3] +Oohh ¡mierda! Mis tripas... + +[ROK3_73:ROCK3] +Jezz está escuchando la cinta, + +[RBM3_9:ROCK3] +Si te paran o reduces la velocidad la barra de detonación aumentará. + +[RBM3_H:ROCK3] +¡Me estoy cagando de miedo tío. Necesito a mi, mamá! + +[ROK3_71:ROCK3] +Tenemos que seguir con esto. Gracias de nuevo Tommy, ya sabes lo que quiero decir, apúntate una, ¡adiós! + +[ROK3_1:ROCK3] +Al fin tío, hora de un merecido trago. La sala de conciertos está solo a unos cientos de metros calle abajo. + +[ROK3_2:ROCK3] +Mejor ponme uno doble. Eh Tommy, cambia los temas, tío. + +[ROK3_3:ROCK3] +Me siento perdido si la cabeza no me estalla. Ah, mira, ¿Qué es esto? Eh Tommy, pon esta cinta. + +[ROK3_4:ROCK3] +Love Fist. Vuestro tiempo contaminando las ondas se ha terminado. Os di la oportunidad de ser amigos. + +[ROK3_5:ROCK3] +Ahora os doy la oportunidad de morir. ¡Intentad aminorar la marcha y vuestra limusina explotará junto con vuestros GRANDES Y PELUDOS CULOS! + +[ROK3_6:ROCK3] +Tommy amigo, ¡tienes que salvar a la banda! Yo me estoy cansando de esto. ¡Mantén pisado el acelerador! + +[ROK3_7:ROCK3] +¡Tenemos que encontrar la bomba! ¿Podremos conducir por ahí todo el día? Sí, tenemos suficiente bebida.. + +[ROK3_8:ROCK3] +¿La bomba no estará en el motor? Tendremos que parar y cogerla. ¡Vamos a morir todos! ¡Me voy a emborrachar! + +[ROK3_9:ROCK3] +Eh, ¡amigo, aquí hay cola! ¡La respuesta no está en el mueble bar! ¡Sal de mi camino! + +[ROK3_10:ROCK3] +Eh, ¡a la botella de vodka le salen unos cables! ¡Eso no es vodka!, ¡Eso es una BOMBA! + +[ROK3_11:ROCK3] +¡Y está preparada para explotar! + +[ROK3_12:ROCK3] +Siempre me dijeron que la bebida acabaría conmigo. He visto esto en la tele. Tienes que tirar de uno de los cables. ¿De qué cable? Pues no sé tío. + +[ROK3_13:ROCK3] +No tengo ni idea. Willy, di algo. Voy a tocar el bajo en el otro mundo. + +[ROK3_14:ROCK3] +Tommy tío, sigue conduciendo deprisa, amigo. Que alguien haga algo. Si, ¡qué listo! + +[ROK3_15:ROCK3] +''Que alguien haga algo'', qué mierda es esa, he visto a niñas con más valor. Tipo duro, haz tú algo. + +[ROK3_16:ROCK3] +Mira tío, yo toco un instrumento y no tengo ni idea de desactivar bombas. Willy podrías beberte el líquido de la bomba con una pajita. + +[ROK3_17:ROCK3] +Sí, he oído que eres bueno en ese tipo de cosas. ¡Eh, aquella noche estaba muy colgado, como bien sabes! + +[ROK3_18:ROCK3] +¡Anda pásale una pajita a Willy! ¿Una pajita? ¡Este es el autobús de la gira de los Love Fist! + +[ROK3_19:ROCK3] +¿De dónde voy a sacar yo una pajita? ¿me entiendes? ¿Qué cable Tommy? El verde. No hay ninguno verde. + +[ROK3_20:ROCK3] +¿O es que este es verde? ¿Te parece verde alguno de estos cables? + +[ROK3_21:ROCK3] +¡Oh no! ¡La muerte está en las cartas! ¡Todo parece verde! Tenía que haberos dejado tirados cuando tuve la oportunidad. + +[ROK3_22:ROCK3] +Buscador de gloria. Capitalista. He estado tirando de ti durante años. Cállate. Eres un espantajo. + +[ROK3_23:ROCK3] +Una nena grande y chillona. Sí. Cállate y tira del cable. ¿Qué cable? Este... + +[ROK3_24:ROCK3] +¡NO, tío! estamos bien. ¡No hemos saltado por los aires, amigo. + +[ROK3_25:ROCK3] +Tommy, tío, chachi. Rock and roll, tío. ¿No tenemos que actuar? + +[ROK3_26:ROCK3] +¿Meter bulla? ¿Abusar de las fans? ¡LOVE FIST! + +[ROK3_27:ROCK3] +¿Has terminado con esa botella? + +{=================================== MISSION TABLE SERG1 ===================================} + +[TEX1_A:SERG1] +Entra y disfruta de los asientos de cuero, hijo. + +[TEX1_B:SERG1] +Demonios, mi padre solía decir, a caballo regalado no le mires el diente, y por Dios que nunca lo hizo. + +[TEX1_C:SERG1] +¿Te gustaría un trago del viejo Kentucky? + +[TEX1_D:SERG1] +No gracias. + +[TEX1_E:SERG1] +Alguien con la mente despejada, me gusta eso. + +[TEX1_F:SERG1] +El negocio de la propiedad no se trata solo de papeleo pretencioso. + +[TEX1_G:SERG1] +¡Se trata de tierra! ¡Y el derecho a reclamarla! ¿Me sigues, hijo? + +[TEX1_H:SERG1] +Sí. + +[TEX1_J:SERG1] +Y yo creo que eres la clase de tío para persuadirle. + +[TEX1_K:SERG1] +La persuasión es mi fuerte. + +[TEX1_L:SERG1] +Sí, estará en el club de campo, en el campo de golf. + +[TEX1_M:SERG1] +Allí no permiten armas, así que sus guardaespaldas no llevarán protección. + +[TEX1_N:SERG1] +Ve y dale una paliza inolvidable. + +[TEX1_O:SERG1] +Bien, toma... te he conseguido un carné de socio, y chico, vas a necesitar un tipo de ropa más apropiada. + +[TEX1_2:SERG1] +~g~Ahora ve al Club de golf Leaf Links. + +[TEX1_0:SERG1] +~g~El objetivo está en el campo de prácticas disfrutando de una partida de golf. ¡Asegúrate de que sea la última! + +[TEX1_3:SERG1] +¿Quién es este tipo? Chicos, ocuparos de él. + +[TEX1_6:SERG1] +¡Bonito culo nena! + +[TEX1_7:SERG1] +¿Este soy yo? + +[TEX1_I:SERG1] +Bueno, necesito algún cabrón obstinado que suelte algo de mierda. + +[TEX1_8:SERG1] +Cada vez que subas a un Caddy recibirás automáticamente un palo de golf, siempre que la ranura de armas blancas esté vacía. + +[TEX1_9:SERG1] +¡Atrapadle! + +[TEX1_10:SERG1] +¡Mata a ese psicópata! + +[TEX1_1:SERG1] +~g~Ve y consigue ropa de golf en Jocksports. + +{=================================== MISSION TABLE SERG2 ===================================} + +[TEX_2A:SERG2] +~g~¡Excelente! ¡Te han visto! + +[TEX_2B:SERG2] +~r~¡Idiota! ¡La gente debe PRESENCIAR a un cubano haciendo el trabajito! + +[TEX_2C:SERG2] +~g~¡Ve y consígue algo con los colores de la banda cubana en Little Havana! + +[TEX_2D:SERG2] +~g~¡Ahora elimina al capo de la banda haitiana en la Funeraria de Romero! + +[TEX2_A:SERG2] +Tommy, este es Donald Love. Donald, te presento a Tommy Vercetti, + +[TEX2_B:SERG2] +el último tirador más rápido en llegar a estos lares. + +[TEX2_C:SERG2] +Si... eh... + +[TEX2_D:SERG2] +Donald, ahora cállate y escucha, puede que aprendas algo. + +[TEX2_E:SERG2] +Veamos. Nada hace que los precios de las propiedades bajen más rápido que una guerra entre bandas al viejo estilo. + +[TEX2_F:SERG2] +Excepto un desastre quizás, como la peste de la que habla la biblia o algo así, + +[TEX2_G:SERG2] +aunque puede que en este caso vayamos demasiado lejos. + +[TEX2_H:SERG2] +¿Te estás enterando, capullo cuatro ojos? + +[TEX2_I:SERG2] +Hace poco murió un capo de una banda haitiana. Al parecer lo hicieron los cubanos, nadie lo sabe. + +[TEX2_J:SERG2] +¡Pero se lo haremos saber! Disfrázate de cubano + +[TEX2_K:SERG2] +y ve a reventar el funeral. Crea confusión y después le sigues bien de cerca. + +[TEX2_L:SERG2] +¿Te estás enterando, Donald? + +[TEX2_M:SERG2] +Eso debería meter al zorro en el gallinero. ¿Eh? + +[TEX2_N:SERG2] +y ahora sólo tenemos que sentarnos a esperar y ver cómo caen los precios. + +[TEXEXIT:SERG2] +~g~¡Lárgate de Little Haiti ahora! + +{=================================== MISSION TABLE SERG3 ===================================} + +[TEX3_A:SERG3] +Bien, mira, hijo. Tengo un problema y creo que podrías ayudarme con él. + +[TEX3_B:SERG3] +No soy constructor. + +[TEX3_C:SERG3] +No, estaba pensando más bien en tus habilidades para la demolición. + +[TEX3_D:SERG3] +Bien, esto de aquí es el centro empresarial tal y como se ha planeado, y éste + +[TEX3_E:SERG3] +es el solar que estamos buscando. + +[TEX3_F:SERG3] +Intentas decir que este nuevo bloque de oficinas está en medio. + +[TEX3_G:SERG3] +Lo captas rápido. + +[TEX3_H:SERG3] +Bien, voy a irme de esta ciudad durante un tiempo + +[TEX3_I:SERG3] +y si ese centro de oficinas se enfrentase a problemas estructurales repentinos e insalvables, entonces yo... + +[TEX3_J:SERG3] +¿Como buen ciudadano se sentirá obligado a intervenir y + +[TEX3_K:SERG3] +salvar la remodelación de una zona tan importante de la ciudad? + +[TEX3_L:SERG3] +¿Dónde puedo conseguir más tipos como tú? + +[TEX3_1:SERG3] +~g~Utiliza el helicóptero RC para transportar las bombas a los cuatro objetivos del edificio que debes demoler. + +[TEX3_2:SERG3] +~g~Debes colocar una bomba en cada objetivo. Puedes colocarlas en cualquier orden. + +[TEX3_3:SERG3] +~g~Para recoger una bomba, dirige el helicóptero RC hasta a ella. Solo puedes llevar las bombas de una en una. + +[TEX3_4:SERG3] { reVC update } +~g~Para soltar una bomba pulsa ~h~~k~~VEHICLE_FIREWEAPON~~g~. + +[TEX3_5:SERG3] +~g~Si fallas al colocar una bomba podrás recogerla e intentarlo de nuevo. + +[TEX3_6:SERG3] +~g~Cuando hayas recogido una bomba por primera vez, el temporizador de detonación se pondrá en marcha. + +[TEX3_7:SERG3] +~g~¡A continuación deberás conseguir colocar el resto de las bombas en 7 minutos! + +[TEX3_8:SERG3] +~g~¡Has fallado en el objetivo! ¡Recoge la bomba e inténtalo de nuevo! + +[TEX3_10:SERG3] +~g~Suelta la bomba en el objetivo. + +[TEX3_11:SERG3] +objetivos restantes: + +[TEX3_17:SERG3] +~r~¡Te has quedado sin tiempo! + +[TEX3_18:SERG3] +~r~¡Tu helicóptero RC ha sido destruido! + +[TEX3_19:SERG3] +~r~¡Has lanzado la bomba en el agua! ¡Esa no es manera de pescar! + +[TEX3_20:SERG3] +~g~¡Tu Helicóptero RC está casi fuera del alcance del control remoto! + +[TEX3_21:SERG3] +~r~¡Tu Helicóptero RC está fuera del alcance del control remoto! + +[TEX3_24:SERG3] +Pulsa ~h~~k~~VEHICLE_LOOKLEFT~ ~w~para girar el helicóptero en sentido contrario a las agujas del reloj. + +[TEX3_25:SERG3] +Pulsa ~h~~k~~VEHICLE_LOOKLEFT~ ~w~para girar el helicóptero en sentido de las agujas del reloj. + +[TEX3_27:SERG3] +~g~Una escalera central permite el acceso a todas las plantas del edificio. + +[TEX3_31:SERG3] +~r~¡Has destruido la furgoneta de TOPFUN que tenía las bombas y el helicóptero RC! + +[TEX3_32:SERG3] +Puedes ~h~mirar atrás~w~ si ~h~pulsas simultáneamente ~k~~VEHICLE_LOOKLEFT~ y ~k~~VEHICLE_LOOKRIGHT~~w~. + +[TEX3_29:SERG3] { reVC update } +~g~Para soltar una bomba pulsa ~h~~k~~VEHICLE_FIREWEAPON~~g~. + +[TEX3_26:SERG3] +Pulsa ~h~~k~~VEHICLE_BRAKE~~w~ para disminuir la velocidad del rotor y ~h~descender~w~. + +[TEX3_22:SERG3] +Pulsa ~h~~k~~VEHICLE_ACCELERATE~~w~ para aumentar la velocidad del rotor y así ~h~ascender~w~. + +[TEX3_16:SERG3] +~g~Ve a la furgoneta ~w~TOPFUN ~g~cercana al solar de construcción que se va a demoler. + +[TEX3_33:SERG3] +Una vez que recojas una bomba el radar te mostrará la posición del objetivo con relación al helicóptero RC. + +[TEX3_34:SERG3] +Un ~h~icono triangular apuntando hacia arriba ~w~indica que el objetivo está ~h~por encima ~w~del helicóptero RC. + +[TEX3_35:SERG3] +Un ~h~icono triangular apuntando hacia abajo ~w~indica que el objetivo está ~h~por debajo ~w~del helicóptero RC. + +[TEX3_36:SERG3] +Un ~h~icono cuadrado ~w~indica que el objetivo está al ~h~mismo nivel ~w~que el helicóptero RC. + +[TEX3_28:SERG3] +Para ~h~recoger una bomba~w~, simplemente dirige el helicóptero RC hasta ella. El helicóptero RC sólo puede transportar una bomba. + +[TEX3_30:SERG3] +~g~Para recoger una bomba, simplemente dirige el helicóptero RC hasta ella. El helicóptero RC sólo puede transportar una bomba. + +[TEX3_12:SERG3] +~g~¡Bomba colocada! ¡Quedan 3 objetivos más! Vuelve a por otra bomba. + +[TEX3_13:SERG3] +~g~¡Bomba colocada! ¡Quedan 2 objetivos más! Vuelve a por otra bomba. + +[TEX3_14:SERG3] +~g~¡Bomba colocada! ¡Queda 1 objetivo más! Vuelve a por otra bomba. + +[TEX3_15:SERG3] +~r~¡El temporizador de detonación se ha puesto en marcha! ~g~Debes colocar las ~w~4 bombas ~g~en el tiempo restante! + +[TEX3_37:SERG3] +Si empujas ~h~el joystick analógico derecho hacia atrás~w~, aumentarás la velocidad del rotor y así ~h~ascenderá~w~. + +[TEX3_38:SERG3] { reVC update } +{Pulsa ~h~~k~~VEHICLE_ACCELERATE~~w~ para aumentar la velocidad del rotor y así ~h~ascender~w~.} +Si empujas ~h~el joystick analógico derecho hacia adelante ~w~la velocidad del rotor aumenta, haciendo que el helicóptero ~h~descienda. + +[TEX3_39:SERG3] +~g~Para soltar una bomba pulsa ~h~~k~~VEHICLE_HANDBRAKE~~w~. + +[TEX3_40:SERG3] +~g~Para soltar una bomba pulsa ~h~~k~~VEHICLE_HANDBRAKE~~w~. + +[TEX3_23:SERG3] +Pulsa ~h~~k~~VEHICLE_TURRETUP~~w~ y ~h~~k~~VEHICLE_TURRETDOWN~~w~ para inclinar el helicóptero en la dirección deseada. + +{=================================== MISSION TABLE TAXI1 ===================================} + +[FARES:TAXI1] +CARRERAS: + +[TAXI1:TAXI1] +~g~Busca un cliente. + +[TSCORE2:TAXI1] +~1~ $ + +[IN_ROW:TAXI1] +~1~ ¡Racha de clientes! ~1~ $ + +[TAXI3:TAXI1] +~r~¡Tu pasajero huye aterrorizado! + +[TAXI7:TAXI1] +~r~Tu coche está destrozado, haz que lo reparen. + +[TAXI4:TAXI1] +¡Viaje completado! + +[TAXI5:TAXI1] +¡VIAJE RÁPIDO! + +[TAXI6:TAXI1] +Misión de taxista cancelada + +[TAXIH1:TAXI1] +Detente cerca de un peatón que esté resaltado para recogerle y llevarle a su destino antes de que se agote el tiempo. + +[FARE1:TAXI1] +~g~Destino ~w~''el club Pole Position'' ~g~en Ocean Beach. + +[FARE3:TAXI1] +~g~Destino ~w~''el club náutico'' ~g~en Ocean Beach. + +[FARE4:TAXI1] +~g~Destino ~w~''Ammu-Nation'' ~g~en Ocean Beach. + +[FARE5:TAXI1] +~g~Destino ~w~''la ferretería'' ~g~en Washington Beach. + +[FARE6:TAXI1] +~g~Destino ~w~''el centro comercial North Point'' ~g~en Vice Point. + +[MFARE1:TAXI1] +~g~Destino ~w~''Ammu-Nation'' ~g~en el centro. + +[MFARE2:TAXI1] +~g~Destino ~w~''la terminal'' ~g~en el aeropuerto Escobar International. + +[WFARE3:TAXI1] +~g~Destino ~w~''Coches Sunshine'' ~g~en Little Havana. + +[WFARE4:TAXI1] +~g~Destino ~w~''Taxis Kaufman'' ~g~en Little Haiti. + +[WFARE5:TAXI1] +~g~Destino ~w~''la ferretería'' ~g~en Little Havana. + +[WFARE6:TAXI1] +~g~Destino ~w~''Emporio Howlin Petes Bike'' ~g~en el centro. + +[FARE7:TAXI1] +~g~Destino ~w~''la joyería'' ~g~en Vice Point. + +[FARE8:TAXI1] +~g~Destino ~w~''la playa'' ~g~en Ocean Beach. + +[FARE9:TAXI1] +~g~Destino ~w~''la playa'' ~g~en Washington Beach. + +[FARE10:TAXI1] +~g~Destino ~w~''la playa'' ~g~en Vice Point. + +[FARE11:TAXI1] +~g~Destino ~w~''el hospital'' ~g~en Ocean Beach. + +[FARE12:TAXI1] +~g~Destino ~w~''el hospital'' ~g~en Vice Point. + +[FARE13:TAXI1] +~g~Destino ~w~''la comisaría de policía'' ~g~en Washington Beach. + +[FARE14:TAXI1] +~g~Destino ~w~''la comisaría de policía'' ~g~en Vice Point. + +[FARE15:TAXI1] +~g~Destino ~w~''la pizzería'' ~g~en Vice Point. + +[WFARE7:TAXI1] +~g~Destino ~w~''la comisaría de policía'' ~g~en Little Havana. + +[WFARE8:TAXI1] +~g~Destino ~w~''la comisaría de policía'' ~g~en el centro de la ciudad. + +[WFARE9:TAXI1] +~g~Destino ~w~''el hospital'' ~g~en el centro de la ciudad. + +[WFARE10:TAXI1] +~g~Destino ~w~''el hospital'' ~g~en Little Havana. + +[WFARE11:TAXI1] +~g~Destino ~w~''el estadio'' ~g~en el centro de la ciudad. + +[WFARE12:TAXI1] +~g~Destino ~w~''la pizzería'' ~g~en Little Haiti. + +[WFARE13:TAXI1] +~g~Destino ~w~''la pizzería'' ~g~en el centro de la ciudad. + +[WFARE14:TAXI1] +~g~Destino ~w~''los muelles'' ~g~en Viceport. + +[WFARE15:TAXI1] +~g~Destino ~w~''la farmacia'' ~g~en Little Haiti. + +[FARE2:TAXI1] +~g~Destino ~w~''el club Malibú'' ~g~en Vice Point. + +{=================================== MISSION TABLE TAXICUT ===================================} + +[TAXC_A:TAXICUT] +Supongo que eres el nuevo propietario. + +[TAXC_B:TAXICUT] +¿Qué eres, mafioso? ¿Del cártel? No pareces mejicano... + +[TAXC_C:TAXICUT] +En fin, será mejor que sueltes el rollo de ''las cosas van a cambiar por aquí'', + +[TAXC_D:TAXICUT] +quizás amenazar a alguno de los conductores... + +[TAXC_E:TAXICUT] +ve con calma con Ted, el de ahí, acaban de curarle la hernia. + +[TAXC_F:TAXICUT] +Bueno, sí. Las cosas van a cambiar por aquí, señora. + +[TAXC_G:TAXICUT] +Oh, mierda, hijo. Anda dejarme esto a mí... + +[TAXC_H:TAXICUT] +Llevo haciéndolo años. + +[TAXC_I:TAXICUT] +Ahora oíd esto. + +[TAXC_J:TAXICUT] +Ahora tenemos una nueva dirección y las cosas van a cambiar por aquí. + +[TAXC_K:TAXICUT] +Nuestra nueva dirección, los... + +[TAXC_L:TAXICUT] +¿De qué banda eres? + +[TAXC_M:TAXICUT] +Bueno, en realidad no formo parte de ninguna banda. + +[TAXC_N:TAXICUT] +¿Cómo coño te llamas, muchacho? + +[TAXC_O:TAXICUT] +Vercetti, Tommy Vercetti. + +[TAXC_P:TAXICUT] +Nuestra nueva administración, la banda de Vercetti, + +[TAXC_Q:TAXICUT] +se va a encargar de que no tengamos problemas. + +[TAXC_R:TAXICUT] +¿Capiche? ¡Corto! + +[TAXC_S:TAXICUT] +¿Te gustó el ''capiche''? A mí me gustó el ''capiche''. + +[TAXC_T:TAXICUT] +Así que así es como funcionaban las cosas en el pasado, + +[TAXC_U:TAXICUT] +Dirigimos la compañía como siempre. + +[TAXC_V:TAXICUT] +Si tenemos algún problema con las compañías rivales, tú les das una somanta de palos. + +[TAXC_W:TAXICUT] +Luego ellos nos la dan a nosotros, + +[TAXC_X:TAXICUT] +y después, se la vuelves a dar a ellos, + +[TAXC_Y:TAXICUT] +etcétera, etcétera. ¿Lo captas? + +[TAXC_Z:TAXICUT] +Sí, supongo... + +[TAXC_A1:TAXICUT] +Simplemente toma un taxi del garaje si tienes ganas de subir a bordo. + +{=================================== MISSION TABLE TAXIWA1 ===================================} + +[OUTTIME:TAXIWA1] +~r~¡Demasiado lento, tío, demasiado lento! + +[TAX1_1:TAXIWA1] +Tenemos un cliente importante que necesita que lo recojan en Starfish Island. ¿Alguien me recibe? + +[TAX1_2:TAXIWA1] +¡Aquí Tommy, yo lo recogeré! + +[TAX1_3:TAXIWA1] +¡Este es mi cliente, lárgate, gilipollas! + +[TAX1_4:TAXIWA1] +Venga, venga, entre, ¡rápido! + +[TAX1_5:TAXIWA1] +¡Vale, vale! ¡No me haga daño! + +[TAXW1_1:TAXIWA1] +~g~Recoge al V.I.P. en Starfish Island. + +[TAXW1_2:TAXIWA1] +~g~¡Recupera al V.I.P! ¡Destroza el otro coche! + +[TAXW1_3:TAXIWA1] +~r~¡El V.I.P. está muerto! + +[TAXW1_4:TAXIWA1] +~r~¡El V.I.P. se ha apeado! + +[TAXW1_6:TAXIWA1] +~g~¡Lleva al V.I.P. al aeropuerto! + +{=================================== MISSION TABLE TAXIWA2 ===================================} + +[TAX2_1:TAXIWA2] +Llamando a todos los taxis, estamos perdiendo clientes por toda la ciudad. ¿Qué os pasa, tíos? + +[TAX2_2:TAXIWA2] +Taxis VC sigue llevándonos la delantera. ¡Tienen tantos coches que no podemos competir! + +[TAX2_3:TAXIWA2] +Sr. Vercetti, si está ahí escuchando, ¡tiene que poner fuera de circulación unos cuantos Taxis VC antes de que nos hundan! + +[TAXW2_1:TAXIWA2] +~g~¡Destruye 3 de los taxis rivales! + +{=================================== MISSION TABLE TAXIWA3 ===================================} + +[TAX3_1:TAXIWA3] +Coche 13, tengo a una tal Señorita Cortez, preguntó especialmente por usted. + +[TAX3_2:TAXIWA3] +Vale, entendido. ¡Coche 13 en ruta! + +[TAX3_3:TAXIWA3] +No hay rastro de Mercedes... + +[TAXW3_3:TAXIWA3] +~g~Destrulle al taxi principal! + +[TAXW3_2:TAXIWA3] +~g~Permanece con vida hasta que se agote el temporizador. + +[TAX_AS1:TAXIWA3] +COMPAÑÍA DE TAXIS CONSOLIDADA + +[TAX_AS2:TAXIWA3] +~g~Los taxis Kaufman generarán ahora unos ingresos de hasta ~1~ $ máximo. Asegúrate de recaudarlos regularmente. + +[TAX3_4:TAXIWA3] +¡Es hora que el ángel guardián de Taxis Kaufman coma algo del parachoques! + +[TAX3_5:TAXIWA3] +¡Eh chico te voy a zurrar la badana! + +{ reVC updates } +{ new languages } +[FEL_JAP] +JAPONÉS + +[FEL_POL] +POLACO + +[FEL_RUS] +RUSO + +{ new display menus } +[FET_GFX] +CONFIGURACIÓN DE 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 + +[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] +FLUJO DE VEHÍCULOS + +[FED_PRM] +LUCES EN PEATONES + +[FED_RGL] +BRILLO DE CARRETERAS + +[FED_CLF] +FILTRO DE COLOR + +[FED_WLM] +MAPAS DE LUZ DEL MUNDO + +[FED_MBL] +DESENFOQ. MOVIMIENTO + +[FEM_SIM] +SIMPLE + +[FEM_NRM] +NORMAL + +[FEM_MOB] +MÓVIL + +[FED_MFX] +MATFX + +[FED_NEO] +NEO + +[FEM_PS2] +PS2 + +[FEM_XBX] +XBOX + +[FEC_IVP] +INVERTIR VERTICALIDAD MANDO + +[FEM_NON] +NADA + +[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 + +[FET_AGS] +AJUSTES DE MANDO + +[FEM_AUT] { aspect ratio related } +AUTO + +[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/dreamcast/Makefile b/liberty/Makefile similarity index 64% rename from dreamcast/Makefile rename to liberty/Makefile index 5370d3ce..5116de51 100644 --- a/dreamcast/Makefile +++ b/liberty/Makefile @@ -4,8 +4,11 @@ # # Put the filename of the output binary here -PROJECT_NAME = dca3 -TARGET ?= dca3.elf +PROJECT_NAME = dca-liberty +TEAM_NAME ="the gang" +DISC_SERIAL = DCA-LBRT +RELEASE_DATE = 20250401 +TARGET ?= dca-liberty.elf HAVE_CDI4DC := $(shell which cdi4dc > /dev/null 2>&1 && echo "yes" || echo "no") IS_MAC := $(shell uname -s | grep -i "darwin" > /dev/null && echo "yes" || echo "no") @@ -13,8 +16,10 @@ IS_MAC := $(shell uname -s | grep -i "darwin" > /dev/null && echo "yes" || echo MOD_NAME?= GTA_DIR?=../../gta3 +GTA_GAMEFILES_LOOSE_DIR?=../gamefiles/liberty GTA_MOD_DIR?=../../gta3_mod$(MOD_NAME) GTA_MOD_IMG_DIR?=$(GTA_MOD_DIR)/img +GTA_MOD_CUTS_DIR=$(GTA_MOD_DIR)/cuts GTA_MOD_SFX_DIR?=$(GTA_MOD_DIR)/sfx GTA_MOD_LOOSE_DIR?=$(GTA_MOD_DIR)/loose @@ -25,6 +30,9 @@ REPACK_IMG_DC_DIR?=$(REPACK_DIR)/img-dc REPACK_SFX_ORIG_DIR?=$(REPACK_DIR)/sfx-orig REPACK_SFX_DC_DIR?=$(REPACK_DIR)/sfx-dc REPACK_STREAM_DECODED_DIR?=$(REPACK_DIR)/stream-decoded +REPACK_STREAM_DEST_DIR=$(REPACK_GTA_DIR)/stream +REPACK_CUTS_ORIG_DIR?=$(REPACK_DIR)/cuts-orig +REPACK_CUTS_DC_DIR?=$(REPACK_DIR)/cuts-dc LIBS := TEXCONV_FLAGS := @@ -38,6 +46,9 @@ MKDCDISC_PAD_OPTION=-N ifeq ($(FOR_DISC),1) AUDIO_STREAM_OPTION=-q MKDCDISC_PAD_OPTION= +else ifeq ($(FOR_DISC),2) +AUDIO_STREAM_OPTION=-m +MKDCDISC_PAD_OPTION= endif all: $(TARGET) @@ -45,8 +56,8 @@ all: $(TARGET) include common.mk OBJS = $(RE3_OBJS) $(RW_OBJS) \ - ../src/audio/sampman_dc.o \ - ../src/prof/profiler.o + ../src/liberty/audio/sampman_dc.o \ + ../src/common/prof/profiler.o OBJS_TEXCONV = $(RW_OBJS:.o=.texconv.o) OBJS_TEXCONV += \ @@ -60,44 +71,51 @@ OBJS_TEXCONV += \ ../vendor/koshle/hlepvr_irq.texconv.o \ ../vendor/koshle/hlematrix3d.texconv.o \ ../vendor/librw/src/dc/vq.texconv.o \ - ../src/fakerw/fake.texconv.o \ - ../src/skel/crossplatform.texconv.o \ - ../src/rw/TxdStore.texconv.o \ - texconv.texconv.o \ + ../src/liberty/fakerw/fake.texconv.o \ + ../src/liberty/skel/crossplatform.texconv.o \ + ../src/liberty/rw/TxdStore.texconv.o \ + ../src/tools/texconv.texconv.o \ ../vendor/TriStripper/src/connectivity_graph.texconv.o \ ../vendor/TriStripper/src/policy.texconv.o \ ../vendor/TriStripper/src/tri_stripper.texconv.o \ - ../src/rw/VisibilityPlugins.texconv.o \ - ../src/rw/NodeName.texconv.o \ - ../src/animation/RpAnimBlend.texconv.o \ - ../src/animation/Bones.texconv.o \ - ../src/animation/AnimBlendAssociation.texconv.o \ - ../src/animation/AnimBlendNode.texconv.o \ - ../src/animation/AnimBlendClumpData.texconv.o \ - ../src/rw/MemoryMgr.texconv.o \ - ../src/math/Quaternion.texconv.o \ + ../src/liberty/rw/VisibilityPlugins.texconv.o \ + ../src/liberty/rw/NodeName.texconv.o \ + ../src/liberty/animation/RpAnimBlend.texconv.o \ + ../src/liberty/animation/Bones.texconv.o \ + ../src/liberty/animation/AnimBlendAssociation.texconv.o \ + ../src/liberty/animation/AnimBlendNode.texconv.o \ + ../src/liberty/animation/AnimBlendClumpData.texconv.o \ + ../src/liberty/rw/MemoryMgr.texconv.o \ + ../src/liberty/math/Quaternion.texconv.o \ + ../vendor/librw/src/ps2-x/ps2.texconv.o \ + ../vendor/librw/src/ps2-x/ps2device.texconv.o \ + ../vendor/librw/src/ps2-x/ps2raster.texconv.o \ + ../vendor/librw/src/ps2-x/ps2skin.texconv.o \ ../vendor/librw/src/d3d-x/d3d.texconv.o \ ../vendor/librw/src/d3d-x/d3d8.texconv.o \ ../vendor/librw/src/d3d-x/d3d8render.texconv.o \ + ../vendor/librw/src/bmp.texconv.o \ + ../vendor/librw/src/png.texconv.o \ + ../vendor/librw/src/lodepng/lodepng.texconv.o # Add compilation units to this list to explicity compile them with # -O3 optimizations, while the rest get the default (-Os) treatment # to conserve RAM. OBJS_O3 = \ ../vendor/librw/src/dc/rwdc.o \ - ../src/core/World.o \ - ../src/collision/Collision.o \ - ../src/math/math.o \ - ../src/math/Matrix.o \ - ../src/math/Quaternion.o \ - ../src/math/Rect.o \ - ../src/math/Vector.o \ + ../src/liberty/core/World.o \ + ../src/liberty/collision/Collision.o \ + ../src/liberty/math/math.o \ + ../src/liberty/math/Matrix.o \ + ../src/liberty/math/Quaternion.o \ + ../src/liberty/math/Rect.o \ + ../src/liberty/math/Vector.o \ ../vendor/librw/src/base.o \ - ../src/renderer/Shadows.o + ../src/liberty/renderer/Shadows.o OBJS_NO_FAST_MATH = \ - ../src/core/Cam.o \ - ../src/core/Camera.o + ../src/liberty/core/Cam.o \ + ../src/liberty/core/Camera.o KOS_CPPFLAGS += -fbuiltin -ffast-math -ffp-contract=fast \ -mfsrra -mfsca @@ -127,8 +145,8 @@ clean-texconv: -rm -f texconv clean-pvrtex: - $(MAKE) -C ./pvrtex clean - -rm -f ./pvrtex/pvrtex + $(MAKE) -C ../vendor/pvrtex clean + -rm -f ../vendor/pvrtex/pvrtex clean-objs: -rm -f $(OBJS) @@ -156,12 +174,12 @@ $(OBJS_NO_FAST_MATH): %.o: %.cpp $(TARGET): $(OBJS) kos-c++ -o $(TARGET) $(OBJS) -Wl,--gc-sections -Wl,--as-needed -Wl,-Map,output.map \ -flto=auto $(if $(WITH_IDE),-lkosfat) $(if $(WITH_SD),-lkosfat) -Wl,--build-id=sha1 - @echo && echo && echo "*** Build Completed Successfully ***" && echo && echo + @echo && echo && echo "*** Build Completed Successfully ($(TARGET)) ***" && echo && echo run: $(TARGET) $(KOS_LOADER) $(TARGET) -$(REPACK_GTA_DIR)/GTA3SF8.b: GTA3SF8.b +$(REPACK_GTA_DIR)/GTA3SF8.b: assets/GTA3SF8.b mkdir -p $(@D) cp $< $@ @@ -178,7 +196,7 @@ $(REPACK_GTA_DIR)/%.ico: %.ico .PHONY: pvrtex pvrtex: - $(MAKE) -C ./pvrtex + $(MAKE) -C ../vendor/pvrtex IP.BIN: rm -f IP.BIN @@ -216,28 +234,28 @@ $(PROJECT_NAME)-prebuilt.iso: IP.BIN 1ST_READ_PREBUILT.BIN $(REPACK_DIR)/repacke ifeq ($(HAVE_CDI4DC), yes) $(PROJECT_NAME).cdi: $(PROJECT_NAME).iso cdi4dc $(PROJECT_NAME).iso $(PROJECT_NAME).cdi > cdi.log - @echo && echo && echo "*** CDI Baked Successfully ***" && echo && echo + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo $(PROJECT_NAME)-no-repack.cdi: $(PROJECT_NAME)-no-repack.iso cdi4dc $(PROJECT_NAME)-no-repack.iso $(PROJECT_NAME)-no-repack.cdi > cdi.log - @echo && echo && echo "*** CDI Baked Successfully ***" && echo && echo + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo $(PROJECT_NAME)-prebuilt.cdi: $(PROJECT_NAME)-prebuilt.iso cdi4dc $(PROJECT_NAME).iso $(PROJECT_NAME).cdi > cdi.log rm 1ST_READ_PREBUILT.BIN - @echo && echo && echo "*** CDI Baked Successfully ***" && echo && echo + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo else $(PROJECT_NAME).cdi: $(TARGET) $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico - mkdcdisc -e $(TARGET) -o $(PROJECT_NAME).cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n DCA3 -a "the gang" - @echo && echo && echo "*** CDI Baked Successfully ***" && echo && echo + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME).cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n $(PROJECT_NAME) -a $(TEAM_NAME) -s $(DISC_SERIAL) -r $(RELEASE_DATE) + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo $(PROJECT_NAME)-no-repack.cdi: $(TARGET) $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico - mkdcdisc -e $(TARGET) -o $(PROJECT_NAME)-no-repack.cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n DCA3 -a "the gang" - @echo && echo && echo "*** CDI Baked Successfully ***" && echo && echo + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME)-no-repack.cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n $(PROJECT_NAME) -a $(TEAM_NAME) -s $(DISC_SERIAL) -r $(RELEASE_DATE) + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo $(PROJECT_NAME)-prebuilt.cdi: $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico - mkdcdisc -e $(TARGET) -o $(PROJECT_NAME).cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n DCA3 -a "the gang" - @echo && echo && echo "*** CDI Baked Successfully ***" && echo && echo + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME).cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n $(PROJECT_NAME) -a $(TEAM_NAME) -s $(DISC_SERIAL) -r $(RELEASE_DATE) + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo endif cdi: $(PROJECT_NAME).cdi @@ -263,23 +281,32 @@ gprof: @echo "\033[42m Profiling data saved to $(TARGET)-kernel.png \033[0m" # tools -imgtool: imgtool.cpp - $(CXX) -std=c++17 -o $@ -Og $< +imgtool: ../src/tools/imgtool.cpp + $(CXX) -std=c++17 -o $@ -O0 -g $< -extract-sfx: extract-sfx.cpp - $(CXX) -std=c++17 -o $@ -Og $< +extract-sfx: ../src/tools/extract-sfx.cpp + $(CXX) -std=c++17 -o $@ -O0 -g $< -pack-sfx: pack-sfx.cpp - $(CXX) -std=c++17 -o $@ -Og $< +pack-sfx: ../src/tools/pack-sfx.cpp + $(CXX) -std=c++17 -o $@ -O0 -g $< -aud2adpcm: aud2adpcm.c +aud2adpcm: ../src/tools/aud2adpcm.c $(CC) -o $@ -O3 -g $< -I../vendor/minimp3 texconv: $(OBJS_TEXCONV) | pvrtex # You'll have to rebuild pvrtex manually if you change it $(CXX) -o $@ $(OBJS_TEXCONV) %.texconv.o: %.cpp - $(CXX) -std=c++2a -c -O3 -g -MMD -MP -o $@ -I../vendor/koshle $(INCLUDE) -I../vendor/emu -I../vendor/crypto -I../vendor/TriStripper/include $(DEFINES) -DDC_TEXCONV -DDC_SIM -D_INC_WINDOWS $(TEXCONV_FLAGS) $< + $(CXX) -std=c++2a -c -O3 -g -MMD -MP -o $@ -I../vendor/koshle -I../vendor/librw/src $(INCLUDE) -I../vendor/emu -I../vendor/crypto -I../vendor/TriStripper/include $(DEFINES) -DDC_TEXCONV -DDC_SIM -D_INC_WINDOWS $(TEXCONV_FLAGS) $< + +animtool: ../src/tools/animtool.cpp + $(CXX) -std=c++17 -o $@ -g -O0 $< + +coltool: ../src/tools/coltool.cpp + $(CXX) -std=c++17 -o $@ -g -O0 $< + +streamheaderpack: ../src/tools/streamheaderpack.cpp + $(CXX) -std=c++17 -o $@ -g -O0 $< -include $(DEPS) @@ -320,6 +347,22 @@ TXD_OPTS_NEWS = 512 512 TXD_OPTS_SPLASH1 = 512 512 TXD_OPTS_SPLASH2 = 512 512 TXD_OPTS_SPLASH3 = 512 512 +TXD_OPTS_frontend = 512 512 \ + --delete-tex "fe_arrows4" \ + --delete-tex "fe_arrows2" \ + --delete-tex "fe_arrows1" \ + --delete-tex "fe_controllersh" \ + --delete-tex "fe_controller" \ + \ + --include-tex assets/dc_ps2d.png dc_ps2d \ + --include-tex assets/dc_ps2f.png dc_ps2f \ + --include-tex assets/dc_xboxd.png dc_xboxd \ + --include-tex assets/dc_xboxf.png dc_xboxf \ + --include-tex assets/ps4_d.png ps4_d \ + --include-tex assets/ps4_f.png ps4_f \ + --include-tex assets/xbox_d.png xbox_d \ + --include-tex assets/xbox_f.png xbox_f + DEFAULT_RES = 512 PVR_ENCODER ?= PVRTEX @@ -333,19 +376,28 @@ TEXTURE_DOWNSAMPLE_IMG ?= HALF -include sfxlooplist.mk -include wavlist.mk -include mp3list.mk +-include cuts.mk IMG_TEXTURES_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_TEXTURES)) IMG_MODELS_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_MODELS)) + +CUTS_IFP_DC = $(addprefix $(REPACK_CUTS_DC_DIR)/, $(CUTS_IFP)) +CUTS_MISC_DC = $(addprefix $(REPACK_CUTS_DC_DIR)/, $(CUTS_MISC)) + LOOSE_FILES_DC = $(addprefix $(REPACK_GTA_DIR)/, $(MISC_FILES)) + SFX_DC_DIR = $(REPACK_GTA_DIR)/sfx SFX_DC_RAW = $(SFX_DC_DIR)/sfx_all.raw SFX_DC_DSC = $(SFX_DC_DIR)/sfx_all.dsc -STREAM_ADPCM_DC = $(addprefix $(REPACK_GTA_DIR)/stream/, $(STREAM_WAV:.wav=.APM)) \ - $(addprefix $(REPACK_GTA_DIR)/stream/, $(STREAM_MP3:.mp3=.APM)) +STREAM_ADPCM_DC = $(addprefix $(REPACK_STREAM_DEST_DIR)/, $(STREAM_WAV:.wav=.APM)) \ + $(addprefix $(REPACK_STREAM_DEST_DIR)/, $(STREAM_MP3:.mp3=.APM)) IMG_TEXTURES_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_TEXTURES)) IMG_MODELS_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_MODELS)) +CUTS_IFP_ORIG = $(addprefix $(REPACK_CUTS_ORIG_DIR)/, $(CUTS_IFP)) +CUTS_MISC_ORIG = $(addprefix $(REPACK_CUTS_ORIG_DIR)/, $(CUTS_MISC)) + SFX_ORIG = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_WAV)) SFX_ORIG_LOOP = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_LOOP_WAV)) SFX_REPACK_DC_WAV = $(addprefix $(REPACK_SFX_DC_DIR)/, $(SFX_WAV) $(SFX_LOOP_WAV)) @@ -355,11 +407,13 @@ STREAM_WAV_DECODED = $(addprefix $(REPACK_STREAM_DECODED_DIR)/, $(STREAM_WAV)) .PRECIOUS: $(SFX_ORIG) $(SFX_REPACK_DC) $(STREAM_MP3_DECODED) $(STREAM_WAV_DECODED) -$(REPACK_DIR)/repacked: $(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir $(LOOSE_FILES_DC) $(STREAM_ADPCM_DC) $(SFX_DC_RAW) $(SFX_DC_DSC) +$(REPACK_DIR)/repacked: $(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir $(LOOSE_FILES_DC) $(STREAM_ADPCM_DC) $(SFX_DC_RAW) $(SFX_DC_DSC) streamheaderpack mkdir -p $(@D) +# $(REPACK_GTA_DIR) needed as first argument as paths in the game prefix with stream/ + ./streamheaderpack liberty $(REPACK_GTA_DIR) $(REPACK_STREAM_DEST_DIR)/hdr.bin @git archive --format zip --output "$(REPACK_GTA_DIR)/DCA3-$(GIT_VERSION).zip" HEAD @touch $@ - @echo && echo && echo "*** Repack Completed Successfully ***" && echo && echo + @echo && echo && echo "*** Repack Completed Successfully ($(PROJECT_NAME)) ***" && echo && echo $(REPACK_DIR)/unpacked: imgtool $(GTA_DIR)/models/gta3.img $(GTA_DIR)/models/gta3.dir mkdir -p $(@D) @@ -369,6 +423,15 @@ $(REPACK_DIR)/unpacked: imgtool $(GTA_DIR)/models/gta3.img $(GTA_DIR)/models/gta $(IMG_TEXTURES_ORIG) $(IMG_MODELS_ORIG): $(REPACK_DIR)/unpacked @touch $@ +$(REPACK_DIR)/unpacked-cuts: imgtool $(GTA_DIR)/anim/cuts.img $(GTA_DIR)/anim/cuts.dir + mkdir -p $(@D) + ./imgtool unpack "$(GTA_DIR)/anim/cuts" "$(REPACK_CUTS_ORIG_DIR)" + @touch $@ + +$(CUTS_IFP_ORIG) $(CUTS_MISC_ORIG): $(REPACK_DIR)/unpacked-cuts + @test -f $@ + @touch $@ + # First try the mods img directory $(REPACK_IMG_DC_DIR)/%.dff: $(GTA_MOD_IMG_DIR)/%.dff texconv @mkdir -p $(@D) @@ -401,6 +464,30 @@ $(REPACK_IMG_DC_DIR)/%.TXD: $(REPACK_IMG_ORIG_DIR)/%.TXD texconv @mkdir -p $(@D) ./texconv $< $@ $(DEFAULT_RES) $(DEFAULT_RES) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_IMG) + +### cuts +# First try the mods img directory +$(REPACK_CUTS_DC_DIR)/%.dat: $(GTA_MOD_CUTS_DIR)/%.dat + @mkdir -p $(@D) + cp $< $@ +$(REPACK_CUTS_DC_DIR)/%.anm: $(GTA_MOD_CUTS_DIR)/%.anm + @mkdir -p $(@D) + cp $< $@ +$(REPACK_CUTS_DC_DIR)/%.ifp: $(GTA_MOD_CUTS_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ + +# if not, the extracted img directory +$(REPACK_CUTS_DC_DIR)/%.dat: $(REPACK_CUTS_ORIG_DIR)/%.dat + @mkdir -p $(@D) + cp $< $@ +$(REPACK_CUTS_DC_DIR)/%.anm: $(REPACK_CUTS_ORIG_DIR)/%.anm + @mkdir -p $(@D) + cp $< $@ +$(REPACK_CUTS_DC_DIR)/%.ifp: $(REPACK_CUTS_ORIG_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ + # first try the mods loose directory $(REPACK_GTA_DIR)/%.dff: $(GTA_MOD_LOOSE_DIR)/%.dff texconv @mkdir -p $(@D) @@ -426,6 +513,14 @@ $(REPACK_GTA_DIR)/%.TXD: $(GTA_MOD_LOOSE_DIR)/%.TXD texconv @mkdir -p $(@D) ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE +# then the gamefiles directory +$(REPACK_GTA_DIR)/%.txd: $(GTA_GAMEFILES_LOOSE_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) +$(REPACK_GTA_DIR)/%.TXD: $(GTA_GAMEFILES_LOOSE_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) + # if not, the original files $(REPACK_GTA_DIR)/%.txd: $(GTA_DIR)/%.txd texconv @mkdir -p $(@D) @@ -434,6 +529,38 @@ $(REPACK_GTA_DIR)/%.TXD: $(GTA_DIR)/%.TXD texconv @mkdir -p $(@D) ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) +# first try the mods loose directory +$(REPACK_GTA_DIR)/%.ifp: $(GTA_MOD_LOOSE_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ +$(REPACK_GTA_DIR)/%.IFP: $(GTA_MOD_LOOSE_DIR)/%.IFP animtool + @mkdir -p $(@D) + ./animtool $< $@ + +#if not, the original files +$(REPACK_GTA_DIR)/%.ifp: $(GTA_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ +$(REPACK_GTA_DIR)/%.IFP: $(GTA_DIR)/%.IFP animtool + @mkdir -p $(@D) + ./animtool $< $@ + +# first try the mods loose directory +$(REPACK_GTA_DIR)/%.col: $(GTA_MOD_LOOSE_DIR)/%.col coltool + @mkdir -p $(@D) + ./coltool $< $@ +$(REPACK_GTA_DIR)/%.COL: $(GTA_MOD_LOOSE_DIR)/%.COL coltool + @mkdir -p $(@D) + ./coltool $< $@ + +#if not, the original files +$(REPACK_GTA_DIR)/%.col: $(GTA_DIR)/%.col coltool + @mkdir -p $(@D) + ./coltool $< $@ +$(REPACK_GTA_DIR)/%.COL: $(GTA_DIR)/%.COL coltool + @mkdir -p $(@D) + ./coltool $< $@ + $(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC) mkdir -p $(@D) mkdir -p "$(REPACK_GTA_DIR)/models/gta3" @@ -443,6 +570,15 @@ $(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC) $(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir: $(REPACK_DIR)/packed @touch $@ +$(REPACK_DIR)/packed-cuts: $(CUTS_IFP_DC) $(CUTS_MISC_DC) + mkdir -p $(@D) + mkdir -p "$(REPACK_GTA_DIR)/anim/cuts" + ./imgtool pack "$(REPACK_GTA_DIR)/anim/cuts" "$(REPACK_CUTS_DC_DIR)" + @touch $@ + +$(REPACK_GTA_DIR)/anim/cuts.img $(REPACK_GTA_DIR)/anim/cuts.dir: $(REPACK_DIR)/packed-cuts + @touch $@ + # sfx processing $(REPACK_DIR)/unpacked-sfx: extract-sfx $(GTA_DIR)/audio/sfx.SDT $(GTA_DIR)/audio/sfx.RAW mkdir -p $(@D) @@ -469,24 +605,36 @@ $(REPACK_SFX_DC_DIR)/%.pcm: $(REPACK_SFX_ORIG_DIR)/%.wav aud2adpcm # stream processing # first try the mods loose directory -$(REPACK_GTA_DIR)/stream/%.APM: $(GTA_MOD_LOOSE_DIR)/audio/%.wav aud2adpcm +$(REPACK_STREAM_DEST_DIR)/%.APM: $(GTA_MOD_LOOSE_DIR)/audio/%.wav aud2adpcm @mkdir -p $(@D) ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ -$(REPACK_GTA_DIR)/stream/%.APM: $(GTA_MOD_LOOSE_DIR)/audio/%.mp3 aud2adpcm +$(REPACK_STREAM_DEST_DIR)/%.APM: $(GTA_MOD_LOOSE_DIR)/audio/%.mp3 aud2adpcm @mkdir -p $(@D) ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ # then original folder -$(REPACK_GTA_DIR)/stream/%.APM: $(GTA_DIR)/audio/%.wav aud2adpcm +$(REPACK_STREAM_DEST_DIR)/%.APM: $(GTA_DIR)/audio/%.wav aud2adpcm @mkdir -p $(@D) ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ -$(REPACK_GTA_DIR)/stream/%.APM: $(GTA_DIR)/audio/%.mp3 aud2adpcm +$(REPACK_STREAM_DEST_DIR)/%.APM: $(GTA_DIR)/audio/%.mp3 aud2adpcm @mkdir -p $(@D) ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ -# Note: This is last so it has least priority, files should be processed if possible +# Note: These are last so they have least priority, files should be processed if possible + +# first try mods dir +$(REPACK_GTA_DIR)/%: $(GTA_MOD_LOOSE_DIR)/% + @mkdir -p $(@D) + cp $< $@ + +# then gamefiles +$(REPACK_GTA_DIR)/%: $(GTA_GAMEFILES_LOOSE_DIR)/% + @mkdir -p $(@D) + cp $< $@ + +# then original files $(REPACK_GTA_DIR)/%: $(GTA_DIR)/% @mkdir -p $(@D) cp $< $@ diff --git a/dreamcast/GTA3SF8.b b/liberty/assets/GTA3SF8.b similarity index 100% rename from dreamcast/GTA3SF8.b rename to liberty/assets/GTA3SF8.b diff --git a/liberty/assets/dc_ps2d.png b/liberty/assets/dc_ps2d.png new file mode 100644 index 0000000000000000000000000000000000000000..c66449f9f6fb476f803c835057fa6221a26e60b5 GIT binary patch literal 90082 zcmeAS@N?(olHy`uVBq!ia0y~yU}9ikU}WH6V_;x7I^kS80|SF_iEBhjaDG}zd16s2 zLr`j7N@`JfVo~b3e?rO(3_XSTUtaE4hAUgUBPi_ zik?DO*FF)!ml|SB`dxcC#9VY3<8{7dH7G~~Ot|OW;J~H0P(@KuMN#0=>$KnRe*P^? zSDP%L^Pb=3Vck4+|FX1qcg%{P&#Qj7^Es0YU;E*Q1s0>!kO+d$;R<(N^W4$wSfShC zz)*HHQ=H)_!zEV76MMNcS23<(`yjw@BWIgy)Y>xlqM}W!T)V~gGZe5hEWf-nU~Sm@ zy_aA9k5yPC$6zPK|CS+6lx5)$xwMz_99-TrNodH)iL)f$+k58xeOaYdyc>H%4*vv-lX8ebm56v;JtEziL*L3EwSq8=C4MO=LPH-=mHmTvjxow+u8dtT>^ zYmdv7b@`q*Hg(Jrni!asU^ct=)2B~)6Pfn?c+{QDFmG4$OTP(^^z`+Y|N8n`m_f?A z>`k1KRQl6jd+xV*f**Kg0ho_8SD>;C)>uccek&d!?3rNDTg{=HLF@{*E8 zix+bJ7;WS+|;9uJEoA{w$DR%Ctj^S~G-2{&W(Lu>x3}}N zPMA1jFlGwMz3O90ewc zWR@}*_*v;`Xeij*??2fqW$LsxYU{jv+j67D8k81ata$b0ZsbhLH zW!BLo$EYYNh6UyC?{W9Z*>WkpVp*W7_1a4OK%Aq%l;ALaA=w21|JO%sRMJ*bQu;r) z{9fj*wb9#eF$C-`d&|}VmdB*Yb>692Jr-bMYRW zH<{tvoAsPc69RSrPFqohSbIc9)Gd9dn=5VqU{K@>`+_`i6E-i3m z?p9iN{OBj)h9F0QM@zC(Q&UTAY;0aN@7*ohq;MzX&T{72%TyRtIGkoZnkD8Vyiok~ z6n&0EOS4ihG}NlIGMG6EtP`Bb^(ML^IP3Hy^QD>*w}Y5ZFlLvZlX|t(w4o{~g87E| zgwqrOm%k=c~vX252Q%_D(eQTC~?@mzVryCoSlNnh+Qr#WTgaWI0zo&_cin4}l z-_A^xTQ%L#*!Z(|uiN9l?DtwPh7`+AzL_(x{_U-;YAz|$&z+rZ{yvo<@6q1VOFz51 zy1u--yF6b;Of2mD!-5(=mzkz6kwtHAY!qZD`1!p)3W=IIJG%|?Hu`bUO5&Rnv zT-Wg;C@t;T<@!gV`|}kR`7iO((AM6}WWkhhw~3WoXsYOyrQXv+7=EXno%K~kaIT73 zmbpNn#Kz2(NlINz49hQDc6F?~7I2=`MZ~WBT}-~~ugy!lmo8l@#?XFiTWz-O{u5XF<9^{%Y2~| z$FIJ~Xk*Oo&J10_RN=sjGYpfNo}Zih+Q}vO<+bu#jxOoz;`UlGFszH&`N?ME`#?@X z)pPS~qa&{^PWqEtw!2o5;m?=L{#8o9rgZ3qc3<~!`Th9uWBKqq|HU?bi|}#rblK9; zaZY%msg_n&P)Nv>^*?NivN~SZ1O)}!`BN3K3o^O+sN6;>BWnSi$m`n z;}lj)VUVbObwyKN$!hVQvL`15ca^Nn+Fki}>vax>t+}_qiD-s&UtZ>Wnx&xR-k!?# zsSm%uyZida*6i@97HQ|^REizQ4V~xmrB}qv)O6~z^Yi_^r|T`q*IthVRzl%U*>cC)MxrEd$QDf`m~y_SHq`Fo2K^saN6dH z3LLkiw&&Tlt|~iI^6(I=Rlx&>-240HGQ`N*)l{r2Ih(aLsGUz%h(Ts&$;+TApY~l{ z9d7;T@#Ck-i!XZQ-rjb1$>oP*86*VzfG7hL90|& zR`zW{Vc}1yski2^Gbv4q|F>t4&FK>pm4)s9|M@J^emGEZ?)?pk&Snjl84fi!H`_0` zy`lK|xi9VVbrnK_vg%4&nwpyTx8+73ovI!F=2QWN4dRm~Pd@JXJl9P6>x+xYyba&= zVt17|TE)l2yt%VIKRz=ua;8jvSXh`|!}Fs>IXh$T>PKyPF@42~6)7(*l$8%}%D-=S zxL4Zzi9zk}Z+H0;4op;b-xqeT`pb)pmzFG1Y5#QR#6;!&Ob#-pu7ngUDR> z!a~ENM~+-LJKOxJ`01A=x9oqvF|N$p&^A`@{9|_ z<7*73cch#>{@`Hq)X$&97|cI?{``BMUF|PHhMjtPdRyxLR$0osy1GuXb&kr(c{5uj z%PPlgc8s4v=;GAFZM>gqm;24#wKPwLkDXWA>Jyx`}bY|3A;|KmGjlsYrAF>q7apr+(^o ztk5$&eCPJ2+TY)1Gw?BZ6qT3%e{i6Yxp>NTV`JmB%l+r;dHVXAo}Fj=``3B<|8HDf z`tR(i-0VGFFE(v;$f_Uz{{B8LDk>VewA#tZsfS_Fa{u{uEgetF_GVvSx99Y9{nPg+ zT)1#SJL%T-y1!q;fBswS-v4Y%&dpC9?d``Kd8N%xyw$d}oXIff`pc5P3oF`OL=;bgEZeA>zd3l7MMiiS>_J2&^|tJUj^mif$7+WvjgB%#k27CJ93 zwYt0S;oRY)p)jmzP%xwEroKUG{M*H52*{p)eLYMmJ!kCqgtZN6Fg?@#67pP!$5 z^UGKSY)UxD^z*2`rsl?PFD^dri72-RwVB=Pe!X0t#x3;e4oGSa^-P-8w zX6lPP#ky1LeZSwxG0VEKL2+e>mY1*Z)bs0Ob_R7F-k5W9laSDI79k-akHr@S7=lw$ zQe*@pJ3G$JwRUI7_?8oS+k=&r^~t+;dPj>b6cruA-oCxP-QVheG4InW!hvgy=C`)C zZppvDPr9?SQ@-ZM!}fn$v#-BnO^7ZiD6s0(+V<#C(oSU-gWd7_>vpXSUcN6lEbLqB z)hyo$k2c-bW>-o-H%D=8n0JEwfxRv%lTWQ&aeTUb-H*ien>KAaeW~{I^YiKqCGTRE zZY%o!?(QdBS?jVpelC)iOgDZuZEa~eQJ9jJ=JoI`sI=}qVwQhTXRbcm?B=y`_P3X8 zSh;fLrOyfm)lK3D-^lJ`dJwatAaSEe*~3GvCN)0_EcaJdRz7+7&@pQ5G%v3wjql$`*xziTZbT%L9Hx15_(wZnNT zD=Sk^Rb*dZCwp~~Yq#-|8LvPs=S@*-@2wA8EzBSzX_67J>eIXY=^fb7TwY5-FQ?qFC?{~Z3b1=NUyF0wk z`ISIyObpjD|M~Z(-PofhD7ZFitJkXpzB|j-RCOQSq^<28m%B`5UGQ?he+(05&))52 zRr%tAqNZQ*biLRuE?<~5m$4TW73s9|%bQJ^Htm%BsrUQ;&jYEvz0rHR-pNb*mFj0Q zOqe(CooCn1J&nxlle)XTMH3&LHNUUI@Z{yoou-zSJ2xBsF+OkO%#afp82IV9iH=SW zL%>|C(ldPR&hJ(%_nE20?YqJ(NKHvp@MQ7vu5-Jt+`qp+a%)!3_L7&EB7<&x`SL|e z+pUdH_7#gmzwNh(si)rF+`L>Q-R0}^Ni$|>tQOIW+vCBIab-o|r@d;EJNe}8e2(|a zFQ2ULf9d1n<1d%buUl05`r5@UnU|MT^sX>*+q+&dOmS`SUF;HT9|Y z^L}~zIK~9O&(F@vtGDtVn`2qLj=_TQ0sB<1tM4B7+g}4&dg@PcW#vxKo1W6WZq=u! z=`Lo>`S)b9|0jn|sD(AM7mewNWp8Mnn3BSS(`{C#~*j~zKO zML%N0gH>Ux@9s}OFDJgSrnJN3Qe(7r*&7K)1t|v$mug|dIdl5gMsI)CwD_nCrx#!tJ}AA(V|0%CQ@^sK7HD^W{pmhSfap4R_*x%@oS4eOd8AG%ezzW6P_JahK!Y*VRTzQq?`XlQB{rlh3ISiE@g z>9RLBCbB2I|MBs0>cc&2)|`3v{(ZgF8s0t2ufJ|q^O^C$)kWJw=1Yij_T^>1e;E&? z7@b^ouBnMByRvfU-3${e9=2vN1vyD zSV@TqBuluPYc=A~3q_YQC1}}fI=gpm+ zlNkcmMs2NATy*f%HKU9R3M-8{)-RmnFMM$^gF)h5omNdarCs z^=2xzn9QrNHt}#*X3A*Z%&tcFtzSMfdhr&u4xeaG#;!Ce)1z+vI1U~jA4Y+lg^%5CK2XroTE*h?x`-pk zlxdD%x-i3yoyF;&7dp4gMQq6kjI68Mm!q(;{C!;W{Q37|nhz>)zvvdzJtcD4Ms3>j zgzb5ElXjQCpU1XL)sx}E=W}~<=lm8^`lQ(UbY)VDl&rXTxs%oV0*e*S?R+c=mv$CE zPgy+c@^b&n!Tz?c++sQ_jIw0y>ueYoY`<5f-8+NTP*Bj-&FzxAd~M2(ijPTLov-Rw zU-jy@{U*U^;5*w)x9iAG-_=*Wa&K=_WoQ%C4m;82t2VjwSf8x7e9Z?(Bh4Px`X3M3 zmv;SJD|Kp?UTtvRogE*ayt=yDy|uM9bR!QR2Zw_E{8eqgrOk2{JfB}b&n)lGj?NAy z&xN!_qu#p!N~tiWyd=uLP6Aj=CHPv)c3L(`gZ5;msarE{9** z`uX{JZ{_cs4jnoa^z^5ATt#Euzn{-pf+TEurA)mx9bYl&exIzh5JS}dx>~8uZ6fA* zcNSQczVayj{3rJwYbDo#mnE0B->>stxNxDM%a*PV4Q1uzs@vOgP2atH_fM>Qsi0wZ z+RRIrg1ET2in=mY)JS->gwwH<)XWMU{=h6Dt_!NetxP$o`SsVge)H}A+J!FOu;D_`_Po2v43Dm6eO+#xevU_}H9=NdI=kZS z*6Zi${(L-MeCY7u>GGCEOFBAu^7s8r6K9C^_VRj?{^@S{{ZxhyrVU~Yr%s=??w(%f z>F4*(Ec@CTw}1csy-!I=Igy+F{M_87T2o&gd3SgBQ?sCukd(Q0wZFca->XQjtf=_m z{_x?$=U(34x#{QT{PYlLaX(=7KYH0ui#b{olIHbxWPEyZGD!VnZAwbY)WCT=tQ20J znQ3evny+#-^Xe*5#s&WNf4?v*{W|->JpbO5p36qlbfeY!{}mS(pS*f?DuW0+J3ISi zC)ZeB_lSrYMdz$U9p+{|{h}I}yIi4P%UbUC?fm_3SsZw!OjaaKP!p7u-Mij@z8!DT zRJ9|f3M)4--l_O});xZ_Vr{eXdj03TdcW0d$%}|p58vF~ z{auG);=FnDxQ~1|K9x}Xuzt)@W{5i@uaUNtZ>uR3$Qt4ZzWj~_kd7BbGO zo;QoE4k{`tN{Zf^CCa2Am*e}TmOu1$NdECdt=yLnPGg&ARr6!RokUkxSJ9PMUkNjO zb6T@`srU4{uhE5qg1r)kkCYnT+E#yCv+2vTnd$R7**AQ?v$MEJYU$FYPo6#%{d>Wg z-&A$c{Ok_B`A5nEqoQ`Lb?p{=J54Y4RvQz?YYomzygrvb)OcKhMTeGUte#w1~(R zub%Idr5@=jeZ0FRYVFId+1K-K?<#$*74|zx){U{kzh`;XUhxS|hcgT$bZo1?^{@$8 zrkt2?m#=;CuDhf%y-bI%$|E{hMpMQUEbveJdxOm3aThlHt^Y!lN>6uga_SV+^px|KJemUE&KYwaU z-rbSpYiEv(jP&qIsw=4b@vwdF`aPdcbv-*fd%CNeo8OuhD;!EnOtiGLF6<~w=4fiT zaU0N5V`i>sS~0i9g52VIZ;GFsn7HENV)yj7 zx3}k)CnY`F@#Ra&oBR9aqt=S)Mr~nWkg=%<@VdOO=I5u-)6;ZM?<;(KY`=c&t}k2S z*1N~W$%*MknH)^p+-qC?E$5@Kzm4Fg)YH>Wo}Q*#%+4_RWQnbU!UK0DFPoYlAH>xr zXD(R3UjNjoQw3jM1irbk(RuQW85&DB?##TrOpqZ;O-1EPI-%^% z7*Y1-#>Zdg_iL2bX8N^QObwQG6wnh4Y~P%IzOLZusi`af{X5g~FZlE0 z<9Wsfj?HYBy}i8|9pcn$pPiAc`TzI(r9_R0=S+?Q*KX%*o8sKgcXsQ|oWFba?oBkB zDIhG|>^s|RE5nDiFH&CZe)Hzdv+QeYBsKP0yG&UUw%T7f@VajFwwjd}Gpc_6`o&eY zJ9hfqxxF179B*!IT@4y5vtro6wxBC{$!`h&4HMVtYH6(!Wf0#Q^)_T>h*M$VMpG@V zRXM-Ey;U_dG&Ex-K)fjvZm* zdNBnIE9Op_vgOOh<8s-Pr%k(dF>UkJ%*)GsFRTn+9_QiZRRwC3P1cXw^CK=HA;H$% z-2BGdvipDj)LdQ}ygaY&@7L?TLBYZOtt~BYI=Z{Ln{HGdJ#s`uCw`xe^qIzUN(`A- zUmfIpP*PU5?fx{~=vQK*qAwXl8Y($h7&w|X^mVM8V_*Nzcx~A2-FbI+DJY%aw8^MC zC@AROGT+&s4j(!s#=Ift{=UEMA0Ho&&w7(Fvjnyl;OeTY7uT%O`9EpWq-7T`UR3QC z*Vkhbp0$5()z_>^67QFn?f(0xm0SE-RYk>?wZY5z=J=)0J#@(Fbt|`cUfzuj3zw`} zlT&?tU92tRoDETHXB9s`XIu2^OQ!UUg{z)uyh&%sb9HrHKjZAPDyiOM?yIjp0*$0r ze}AVNv$H5QE-PylU&HC6-QvrIFMgdfXO8}-PoLb_SXdkcSoYq`xwdr45)oOek`)EF zx8)jl_^!6CejaCjuJ-(cgU$2V3z!dtZV)Wde|f7%(%9yp$_Ji{(l<02Djs#J&tsUK z9VAu$mL)oEnx@?8d8a^Q=T1HI4@~=T)axVDi7y{M+*onqQh1WgOa`-%)9)lU{4`sw zwC*&+G=_{n>|MJfe0IycQC5<)aATMetoufsflD-1#oT=RNrnqcJST5I@XE)?;WoF~ zqf-K(byw>gK6EJNz_e#mv>v~@zJC6d0ObRA1v44qroWqQTfI%;n(L$pKc?t53C=5Gyj;sGBd32s$3QD#GLzp7lXp0@}+_8SF@Iuy}fnP zt$D?lFY3mHk6gqV;+smXf-K)KoU{M);cz~)!|^`Z_Rgp08LI6VFa1?^>zVMbU;MPb z?RT3EmQMxiH^ls}=s2--#R?DGoEsa`SsgpXSrnZFS{7?feWh{ZcR9xyxg_3(jdypK zUuQn>dj0-&7hjfSAADuNd0_wQ5&&My_!RnZ??()lh*I$2K{-vsR zQ+N02RD-MkJU48+Y;0{EeQBDdRrxy^P6f7{qbGFv_H~?KWhk+_TYrFMQE@`m@*q32&_~6V0xvvZiwZFb(K7aJ6h{6U8${} z*Vo0?Zi#xEvp23jWc~H{OBp89k5>3I-;fp%FyLgcDt+~3i2}#+BfG447wj~e=Pp;d zWWV3?+sm%MR{mP`H&tAGddkMUKE@jyW=B;QhpldZ`SsV^s=fc__$~Kl6s~%7;El+% z1#M<0c3R)jcXPMeFtJti&EY7vZxg2$Fd9b{Htn3I%bLKp`9!W_+tiqk*F+D`JYXfD zdM}}PYuSeB*ZNLP-zxLwF`uEn)W_|PKC7+P9=LPp)Z*OgVnmP?MkKe?~ zz2xxq19lPHo%zGqwG8Yz!UKPv$zFKr&~2V?cUCVJeSe*ejm_%R>C?tuUS6yAC-Za7 zOK=MMx@{p_^*8gZ9m^GO>|P+ae)>JNd)Ja|t{*9AOeoD4JMa1MRo8yi@LLSo{=QLL zG7@*y{M_`$FWgZfVs~Ko@uhWjb=Upo+3Z|b`T5y@riAzn2?wW28mHZ1$rcYjV0GyB zLw>Uh0gYQ%{<+T)&A#TY_xqWyI`O`@79UHJi#>4f@2po|Gui7mSP31tcUNaaKEr_+ zwdy%dvE*;f1Y}Fmg%ku6DF{y9Y6A_=I7JtjnUilc>nyJP+#A%LSB&f=eOJW(^p-6 zrJc}ptm*#le$eWrdP7G?N5zC$!pb*Zyw`u*vG32P)0Vlnx1G(swkGoDlarJ8|JJ_z z&)OjO)|UKP=J|2=Hl?24V_E#HrfU7?V)jb01L_QR4olk_!DcDnVyN)>v$`<#;N3^d z=hs!SaYb`6Y$KRYv1{og5@JKpE!*;ac;3JVG4t(;shwQ}Xk`eSyj zAUpY`)cxnZSrxW=bx%Y2`+Ilam*?%yzp**pU%qg63X?%qQO8-7GF@V=Ra#emQUZ`kj*ti!J8ZvxzhOlNXpMcBZe1A;NX*v;5ZUo%U~=6mI-; zv9e+iU^)1uI??h!&!NMIZDV6%Lj0Crw#mA(!ulci0=@|K|2z%b66V&~8i;TCAAekn zHNlR*=%AVLE{Y)uet75UJJXpM1WVa& zub#^=g`;UhSm9amGoNQYIgvbDfusHwM^DE*rq4R+3>)6v-(Sx#L%I8QYdm-1XQAD9 z<+z?HzW;vd2Yr&^1 z_J=1kL@~Wk|F^E)i#;T?puD_2ZhgDo^5)sIrDI}a7hcR*5Vkq(L4ifW=9>u{BNEJJ zCmPL6Fp&xj3}iH5T(nCdky)5S>7dOH)#l2hznwc$#Z07lZES3I7#g(~^Y74SWnhqZ z{-U01G?Qz0>FY8(Bcn|mIU<*(8{Yi*dpm!BDOb~m*&SUUpSu||AK3F#(cD~IfA5z| z%OCw+9e7q-UhpcTfuGgCce~%aHLOeyE?m7j;pMzkhI=9NHs5~B!N>Pcf5!EF0axDW z_I~DXeBm$M`C88tF9s_*J1=wKRYg4T&#Kpx01ZDWz|2!|T zN;H}o5E!V)t=OdCur$a~fF)yV)Qb|U6<4!XT+QNWV%U9`Z}wTX*=O7C+=)p^Pk(%C zYqp24uWMPE*_trz1GnxzX1K?8J6&1k%;6*@3z>8G`+oZ|FnoR+A8&PId;Wax8-Fss zXs_Rs1X>Cp|1v5kM~8>4S)2jX&H8_=SK86#@PRo;Dw=-s?Pq!*?kJG*wm~E1g#07p z^m88e^|yAH=ZgvQG6|b3nR~vG~mdGA(OAIskf9E%gqetM8r(6jdTD9%(#Xf%#X>f8B z*!DLrwdkE>mciA377XVdHyTczJzF|zYgTL8*;xxiSBHK0{JDAga`icW>N;ZFZ_0Mx zN&J6NaGQ$C9X?NsfR!N}JUmAZ^V{=mjq1I0Dd@@b=gO9rk}FrP^zia(I+?;0@w-AK zhk5zk1gX`3SA0=CfAyctIooG-A`DOZW_AQbMRgrG;IJkC{=YY;rfNqwNO~|bELyZ^ z$^7a=hYsy(X>F}#d?vZhO8(uQoyD&{pSRb4%Xdw5jWR2Pii!#Y!@SyWH=AO2m#uAe zx*I?D$&-}KxUB*QBA=QnWp%Hzet7WqZRyw7)`BLJ5+|$qrnR)TPTjp@hYs6^T;7Y* zj~+W#cVlC+`^hDLG()Z$#4vAGUs3w+&(G}I-`{#|OJ7a7oMZNRWlME)z}x*tx*3)+ zzF283d&(A&4$ti;RlP~P^#Ice2DGPXvU$ac@2^r7RaHeZGcid?Nru_4q_qCIGW=lk(N@T_1t=p#RJ*`KGW#P7x+`qrRu87@j#>OwFldflG7WQjq zS9C9%X~JazX9c_2H*Q26Iet9(_qVrpywYYqC(fPwmT_Z)BEy4AOTEw8R)4dYSNTlR zcX!!ah6QB~4hOzh?foa&>t?)f$&w{Ir%&G^sKAl4U5{abb35NvyP6*#LJdN5kK2cB zowJ)^;U;4lrUW*T%OO>J;iE@J8`rE^bLM5qFVADa>F4LQ9!xm!`~Cj)d3Sac<{HXN*wt8U%emQf;J^Vb zriP?@|6;!Ka5WY5b=+fq&B4HszxV4j6_XGd>#{Y|uKZM2R}Y@97t7LoaDk_XheJff z4AF*b_wUQ+-riPgW@T0Nc5C+ao=FS=0Raj+vAaYpEiE%HE$I}HlhgBLGtE7Fdgtkk ztx-(3m)+o77Wm6ttM_HV>M-4sS64VebMsQBSuDM7%$slaOqw+5_K}MGiOU3f+?0FV zlw)FJKfYMp51Jl3apugCmBGseq@}wzZ8B=eG^=7N;A#q}`SIc5q3YSye9sR)*(bzs z;L86?8sgd4)*L)<|DR{77whDcEupK!6g4zB7$(e^(J^U~5RZgG!>g;S)n6+(J2|QF zI?i!hyp!WWD1+AW%bbn^pkKA>*49XwW=&ZWd1F_p_O_gxObnng>Cg?f z4+3LidUouv(7e%kd&)!6j`K^GEMdva%w!0-xHfwGQQPWoGMr}5`%Sa2aj-QrGJI-o zYBFP%VBV%~-s7gs;`rkF0!QYXT-9%HZT<0Nvj39978|zSWn?J(lee}g&s}(f?b+E4 zmUlifZLZnRrWO&)wE66g)@F8oF*a6K$pg>6DKGu3HI?h`?sE72b$VQ(SndsG>)3Cvw{!mE%{ZY>y!*(*JLPxsnH=&po&`_WiEJu%F=$UM>3c#kinwv&yFt-H$VKw)Uf8DMdha@CflnQdrnph zS19{;>d*F^o1YFpc<>;I;UL57Ka(d?JxdpUD%J=;_f8`UM=a$9nFj(R#8#mu==W#082~TI_HWC3$xr`f@Qn! zx-Jdc%lqx#c@|@dkV_H=q!Y>n*9L56cUF)1>(;o|W(UIpORf35o!?g;VA&F4knZ+> zvamAW^SXz1!VFKI)?Z&Ae|)NTI0FMSJ6}!p>uYO8tM>jYeOcw=!oqOlYSz{m_N=W@ zMrFG!OJ7}K`}*ptv~KL}T~lv9v;FtuG1HO$vcyEibMx){r|Cu?dRcPnGIw2F9Ye#q z*xmE4z5dF))Iv%5Y2&@fD;wJ8cbEi77#=%*-1}$O-%qFY^R*6VnMg6LcyVcI_r>$) z-`l?Y^Y8EP#O=2~KAG%)D)IEGQ!0)e4BHw@{~eHzSpK)+qhJHSVpdSr0nW5NFT?*f zZOQT5bVOYDRdi?lIUl@r$0zv#9BDUqQCM7Ta`0aN7;iQcV7A<0$ zbnWH*zAZf$Qj8iqE^N&X|MKD@^ZtLkWYadg8foNkZ_`TSyiw92dmw}1^FbLI#)RA} zSFeg{P37WiXHMHJ>9U0BLdYL!P6hch+K=+rn(W}(w&l~Gx%!6fpU*$oC&+N*;*^|+ z^JmQH`1GlWL1TttvfHk>_4Uk;UL4HY>b2^}r!8By{7uLze^Fw!CVszM`uTZZXBnlQ z%3|2cXeKn@JYSAsLHPPOvyOY$Tojph?%cUz#fk=#`*!*FY(UQZ|Lgkxxbo7{waQOD zqjVV_Xk^7P-xKWG`%+ceRa|^JXtX6`LtivA1GkvYf%EqNb@qHdXC3eS$Kl?NLgwJ* zeyj{%-rNk9`ugC>$;lczIxG?FYWlSgLKfOJlo$LyC!ZkOC4XSrBUV<{g_WP5t%%=m zmvwy|?^G{VrKW=k4p~`RdNDg1W}D|b?MXMCVc)Thp-Pg6?WXemM@PLs91)fk5G|ED z@b|ThyHHwJSJ#8LZ+VrR?(QlzEPUjm6Sv1BO7=6~j5bxq1JMHe1ONT|mvMca>@2ff zE`EOgOe?tWIU9*)cK&PKj&H5Y z--Y&rPSBUFSWX4hK9RL5xzN1w5`A@T zEw{hF|CZd_V!5}rG~Uc~X(`u7_CS8_Bd?3Pt4dbGIT?#Slzd`Eu2-{1e@MaF`ntqgni?geGK zMd|0~tq5M;cju$9U`$L5sEcysh|7~_&kkL`E^eN8r{VHTm*3yse!P*~e{vSnR4-O@ zb8{6Hl?Pv5UY<0;RLnyqNC`ISL%pUT4db(5Unyy-DFtlE$BK2bZ-B zbJ||mKWUe*lX(8_om^aCV50NVZviJ67%mBAu8G|I>}Ax}thpu<|9Q!&Gb^ z%gtbxdrKr{ciG#msi~=Df0~z><=@%ixPyJJeL+1Z11rNlOxA)T}xpd4K!*`}t@3EL*0is2IOCZm$%>f{Ph?6;)N|6c%wmeE3lQ`O~MSmet>Myu7>w zL_}DYU+#SKCMP95-MjGQB~Q~nh8xjb8~UqsxJqQ z_1Y_0SV;W-7Fu;F=iVO6HePAAsa~z1x!cdzGfV_n76cd-3(@* zEhsHL8tiY|x-NG2sq+E}zZF5fK1D^wMU1)D8#FgQXL$3IyXy~|%YDA5PoGMdWL$U| zySq$OKYCk^dBeGcU?GMERqtscnOko$hpqmq^smal>#joCKeO4q#m~?E^>=kWs>853 z;H-bANBB#3KR-S-zd1j2Z-|_$xU%8aOTJ62NzdOGSbTUoJ)Uj))Tyk@KUnI|t$xjX zx>lKiqiF(L!pkX|!A#-nVr~lhh^jhF@nLnGuuUeHIc=vdgGJ4U2aZe)4Gj&+d3kw% z&!m0kF>p4>{5(0wK>O`d`T9Q}ulC#jleoL9G}zV6t&A<$cBPNnWkbW>0Dk`d6Q4eP zdLNLvp+wAQS2oMCZ~MD$>+BU~dC0(E^XtXpV=XN#YbERszbrZR@a~;EdRy}D{`z_3 z$dUgIZ~uG~nBnWh*$}~y%_nCgv2up>JCgw|9@A<&=>e|}w*xh9ZUzRNLpKqt2)NxBG zBqSu^^|iGf{r&Cj?d)1wS|6Ux&QJXJ=cmE!vl`mkhi&7I)%zD($*u@KF5R@iDKAg% z)wQ+lrLV6g-rAD+=`s7rGF=t(cupNlGc(xuh;KS zy1p(}oAE$`!Ih>%u@#Pvj7`nW>H-&A#B`&2_SF0=S|rhKV(a?2VIeXIK_jh&*Gu(PrV)gsR#$?{p({z6?zPV=3Lx$m4kL2X-d3UWe<;)!gR(af5w00`jo84a>_&eT7Gdeu_{eJ)X zrbA!zczt%=W!-$UM)(Z#Y$=DiZoN`Bjg5`-BP(PjC10|9DDo|wGJX1T1_=%|2aW_b z&-$*GmKFns#O=H1&6x3m;X~_>=e*yHrZaBn%In&DFeKm2QK0Sr%`c2Q8NF7^=FFTq zQ?yswT|xc8H9O z?C9z`!{Wg8Q7BYnk+6#`qct{!@HwEN=9%fdIdWD0-!^r_&<3Bf7TrzgL; zvGK!)51_(3C`gD;)~ZF$whGkRxUsL+IwmFt6s2!&Ze}mr9b1<-i9ta!^x6ZPldFGq z?mlohz=!kQ{r&O`3&K{L@Bjbrx9H{N{_j;|yE{84eu|J25=yH7cvRf^z`ely4UUbI zJM^h7x!gmz zj;CHfu#7d}ZC(C`T3-j3PoH03Uf#bobamM4X?n3$4gxKfT_5}zX7DXj)L@lf`JG8v z=~|O*cU*#*)Yb$0a+kW4NFCsE6fj~~uw_e#z-d+ni~4^y9UQXOWhUz)H>V|KWW10t zU`#S=C|Q1arTFrgUrZb#uU9m6@G(cIavb{lDz#c*$9t9r>lS7Q$Fyhj@2N(djNh8N zzNo)xV!}2aJ%)4KO&|XJSH35?LS))Ev%EV$WRGsmOW@^XFi1Z)r!XPxx@W@a4gWl^ zFcq(U!^#jB7q`hUzO;iQY_(`yT-=L0JB`cU-RWGjMyKTcJy}pQ8pR9=v;}r@uYqL%75m+l4k2UoN^Qnn;0K8drA|K2~!|JW%s(%^O9VUDe;;eLi3^ z%|NR6!t1Z6ejMBX``vD1alM!y@f-L`MQ(&W-~RZd-(0J|=2lj}j5qjwW!~V}GD&OQ z1<~Jgjx2f>>YS?}rO4gXAgKK0$=8Hn=Ggr|pUqZZ<~!R=+(AUJSAZpP#-o4Rm&kv9 zeqMjRVRG9Z{;sQ8tzp`?pL{E^n!A2`*40;Emn>P5o0*w8@9pjF?^R;|d^|3HaaE`` zXhMH|*lKqMhm4GjW2-_}|Js&){hj3In}2rrEzf3J@N4%+l?KnZx3aG5D8zF~j72qwmiJd3SehDy{OJ_9MtWb@R;&`)aFq?B1}>O;v7arrI|{Hw=!nJa zuakXyyZk`<{Ikvh0RnQBOJvu@@9z`U4tsEB`7Y=zBjJ_+uPfx zmH*G0H-8BuXlR&;iD^OD>W5FCvTlvK`_%7A?d4^@jnd|MYf5LIzB`ru@^b&<=k5RZ z6h1!2%e;ZB>#F)9eIW)9O>OPpq6hwLOg=96?c29wEu6xua&D<`913SpV&jpxz`^7eA}|9&J-{lLZRz?0<`b~t)% zm`=n7hWGn^uS-d0WXQO+<>h9EKJ)BrYo;Ge$cPD6;cI7}IC0|M4vjxgPEMX*_VUux zw*C9GlaeqDV3pHJOegii0P`B`*%N8#g| zM~@zbfw%i@{Ji|@>+909&2qEeUtZpSX?5CO_4zeLyS;JAGz zZS(5Yt6#Hh;Jd(;uwQ}Wc=n0g-U**Se?3xg{b-G<#vv2OrySc^4mcX_Wn(zkrB_|O z=o0rF=@|x73#8LkVQT?+w~3_QmtUc*+fcu-X!>^MB-;|!05uM$9ZGK!3`}QlkC=X) z;f)ZBV(^|g{xc08aewkR-z;%%=c`>@_xIP&ZZX|iljqNmKYjgrxk1COBufvS|E3Jq zERH9{9&-sj&RTRLuleF)_x+|y>!Wn6&d;rp_|jr?(^~U z6ARJ08eqV@`WAy0!xF|DdK^t}K82pyykubklLwo0tqHUA#9T2J$GCk8f`Wn<)6UK^ zoxf!1(yKE~GB524TYSM`&t$)Umqqb2lShvpeYjJ69yIXv^XJbKr%pMY zo~9eP+)sAO^y#P1bF2`#1?qz8>+4UMI`!b)yRvt8l^#CYExz`DGCTcdbaU*!r94}VeO2Zhj_y199-(U4ri{Z<)43keLnJz9YRo_y6`?0dHoJgEJY0`u_b97`+t!l_Rv3s?g zMFD6NI0J*t_dCVM_SOD=#dzTGVP=Mytx<1f=K85~Yl6o9j`d1EJ-hVR_xJYxOM`Sl z6PPdD@xbFF!ZR4-e%xPF`8^5&yS8};M%+%T&BRU*r9 zcaCMTjQ*YvO;QQp+f+L@Nn77@JGJ-x$M1=oBuwZ_q?>^W!weFl{bRi zxOJy&;@UKw$8nlMG@~H<6wOao0Wl7O1xgBwP6$lY)T)jO)0}d3nh{s)ySdvkjnBRB zpIqG0@!9py-dpcWpYED8Sv_6F@_eoR_aAlQ_YB?KQk|R*y-7PeOV`Dy=lrM3_D)(t zn-|m+O%eViRIA{7St4vHx%#YbsvhvCFV+_0u)#bm{Cgp4l_Vo0;w!pF3 z&u5m2<`Uo8W<~$r`|X)EeY*M8(ge*5Xu zr<;F@{vOr_?I^gmIsN=zw&ujX?(WkCzs=qI<<7pixR`O1va)ivxVU)z!*}oQ)qHqx za7*^}yyg4%|3A{u!0_=%r?8>c)K}GS)Re6E{JH2ZZ~ObpW&hLrpJvq69-3=Y_2q@( z{Mv6f-E!t|vh&NWF|7Rbnn$@*>_3Bd@CX;5*_Ku2@+P80?jCEO0)Yh!P z)nU4K?%WZPiraj%$7`w6+OTA!nH)SkDw>*{%?BHH@3uD2yK~^6qxa^tvmd^GO?`D` z<(1Xp{cF~&$vXUBed64?o_njlRz7TvJaSZbLFDEP(1wtzDl4&W*5-o^H*=Piy}dQV zM=eC_s=o{aJ3ISQ*6llX%=q%9LM{?^QhJ-FaJ`YxmWk+UxfmVwbOB@K`&)oljQDD(ceX zT@@ddX3m_sB4pK?mzS59$8AnKYoqXG?e=?Y@XE?y_ni~U?^U|jRptKp@Q}$&Z<2idpFq8qvrqME zIJZS9q}pwr%YDF3UteGL(jw2oy69~=lFKe;%yHOd{`~xWe=jetZME-b+t>f=E`EM) zE-MSKgaN~nEp4a1J^1nQ@slS{7^ZYAx)E~k-J3NUsgq~VUOmq&cb4#_{PJb#=jYw~ zRKEWD>(=SG1#mifI;M?I{yLPQ(YCAvMJYO*= zaIX0Q&=S-~j~`#%TlMu)+3Rb(4$oh|-tDmZYT5e8&Cf)Zh_16uF~9QbPVxEPl9G}+ zMu~@7CQVlNfA{~-=kvE2-wCVv2&7m&c=P6s0Hdc zwdLiRM@PG3pXRFj&Drqo{{H*7_Se_@dU$X&_}A6_(^_z~`u*PWRqp+A*0#2`yBQ^V z-OTdtc(}T%kdA4cxGlRlME|uTwIy*b7 z8~p8lKAD^xyv&F5cv-TzsHmn+$Ls6s>r=kJyW7trUw7Lcv_rxA!Lh^q_AyW1y?fWa zw&?rs_xrccv8@i1u`F70Y}pD45h ztvd+@Cw_7N`0?YyQg3mqvNs)GOM`eo9qFvCj~+d0a$9^bY2${}(_-i5SUPXdyUW4D zlX7Xv$x~cgZ~Xv`&votD6XWFMG{Z2N?ft&rdVG9RW$*5soTeKMnvj#~WlK*_Z|x0^ zi#)ym>blt7H}=*3UjIrn!$fNC)vQ|XJa^tV#Ygvlkq!$9*--!g-~61@Cr%`Md~`JM zPTu$2_Sjve*Vn}wss5;*IB}wVTYLNZgMC}LAG};X|62~I8K+>$nSE`|M$kH+SD-x$ zda=70Kpn`L8D65&(yPxrO+Pi@&5_++g#Za=N5`~B^$FCQPDmP2f0q^5CSlzYG2 z-aT=9tNw1ZWGG|%Yf!%2H?LRMNV{v1Q_Qu6&h1}*eSNLXwV0jHqw&VEUTJL>-|71C zVKbr{Gfz*~zpv@8=;h@Vu_a^T*8`2rhjx{|7MaIm{p<4CYaaw#)frF{B zvGLG_h0c+?%ij7f$bEdQmp`GHVc(xm-qZBs``i{Q+S|vcZjBO+1noYJKi=2!_I^|K zGlq^u5-n4T*%F?goBL>iW3z^)rsL9}jR^;vq-V~Y`LChu`@6fQE`e{h)&H-1%PwDY zK~+h~>FT;z>tY|D9=0WBGZrmUvVRcW>$W-W-t)7w&0k(wDZFy!$|cK}D{DW_y|tw? z@o-z`!-o%F{rFMgxK8WbsZ&fZq$DM8hTd}z3Yui8r{`Dw`kL<6=H}+D(cANw6n^~r z_2|u;JD~zC@*A|dyu7@UuC5B5aW<`~sVO9UecV^ZA0H0$tFH)I^+tc+k44do-FjQz zyvZq!h?wDKm*?r}Y5SmfiPzQLThD7O+A+}fHg zE@x4oQ1{?KBX|ZjF)Au*OW5kIQES6Owgr{UF8lqXm0P@To>A&4&m6u3%Y0``nCHoO zXwN(I@9*#3Rn^tneJ79gO3N}nJ2!Xr6T9s>H*}d9r%&Gwj*Qfd{vW?-TM@hS&bPOsB6L6l=~-K&A`MrCXf-uAuZ-NRHgDcMFODD) z*VIK5HS1+%WiP(|`sc^veibz}A=$fg9y~~J>z8x(@bK9Baew=X)2A2v%}oFe#fPnn z@%;Qux1+!Rc!tTM>#r}q{>o)J!EN!u9JA!DQ7c!iQnIp|wP1n5rAwDioI3UB)z#If zR$V!Kn3ey`g9d3QJz%rY)G>^yhmh|AC4=e~aXX7}yMWdE>))uCP8-NNzJZ%q}AjDns% zD%!ooNw2Tw$wYUS@bK$iKW*H5rH*b$JpAJ7YH@~*8#k_r-F;2g)7>fZ>hE8_iXI$b zbP2S%yCw6ow${{D&b!Or?wK@svj4V>i;rgXw!DgOt(Ihb(VF`;-9)Ohw^uah()^ua zt4q(kySuym&5ezVc|ZI*t-t>b^PXQn9`}EjGRv7Evp?|Pjn2-_O*t#BC2cI3xLw|^ zX2z8(A$RWH)xLA*j>)Hw*Vf1TySlc1IKa$5W%bo8-37)oecs&K%B`=jZ&kMEuBe2d z;KGgCn*#R~O+MLjFyTQXyWEeHul~x;X~-=B1JbbKhR z<})Eb!Rq=gP|9*?azeyWQ*M<)y~-ad-LqX}0N+Dy#u& z?5pF}=QC^YO}?v@;2#ljuzP`7CUjh#!5v+ zg;An;?a_13O8GaRe8@DZA}BOel#h>Z(}%u*h=>(yG&X$XT@$nO(}x~O<9mPKY(76v zn8oq(w%ptD@^wERHlDZre#gH=sE&Of^T`|bzg{d(Nl({4TA7!Z$0K7Qpsx?w^`N1v zyL8nmE_ZkL9yjHvwPJJq7TFgR6f9Vf|0i?3FM~Qdy+o`%m3}-huPy{jVK!SbcTT)htmy_SILrHg7im`uh6v(A8lpu4aLX zZqP8u{Q2^IeV5+;z8dazvRA6plwEe^hkFs}nG`Hm54qGYO za69DT+uPgszkB<3t;pk(yW|hI@oJZrmX=Q5ewwd2YOP(<71sw#gI50d^ZC4{mR1oz z4^K^y-0X=H6W=pvOl>G%u=mfA`}gOcooybxyDZl?BxK5(6Iy-J=6!G8K zpX}KiIel|C`topdYad@1Q2FD-L#N$kZ(rQmx!EZ_E+$52etlxEl<5;IwW8wU$lYbS zsi~=5%op}l7R%NDDV%PaeQi%m$eueg3%sZ6iHM5_2L*u+<9aeX_|E3^^WMeJ&g_hj zjsCyBKwq|j=a>&^kM5{f*hz*Kss0VA8xlb^3I7 zAD9x_@J^cNbhpmlbVP)0azx3|et);K7tTZ)|IxDBI zubiJmIq-Y*y16ua-zImR264-c^xJ2*5L%{(c3 zOgCDqn1ANXnOt8#G%zyHdwprCxBbtW^)Wk}jwUTkJw46u+nx{GJlAgtUhe0XWAiRE zGczS6rQu3diHO7c>(Pyx+~RtF+yq$m-`iVVFE1ta%RPF>G$la|K}ktZ7Z(<*xqV$- zT%ZL?+qauXt(_)qu5v78=2iYBb5_T#5AX4bi<6r#NXDpFVMLa&9c-ejyr_>L{=wLZ@rVraAiC zB6PN7UG?&OyJYFolUHt=P85+@JYk1vop{>0IhM8e_tky|Eh8@~D+@by+EmJyIVWpD zL&b*&jyFHVyt==?zEz`ZQ^X8aJG;1C?w8@uo<7~%d&KwQ%coCWudjN_d= zEBSa|X?a=MzRNS!ZEWuFXFgO~wsa}yCP#h_j*8o5wS@|rfxC9>c=6^okN;dNRrgz5 z6Q>B*GX;w5-6_ksAuKd><*#pVxwo}mxVk$0=+5Hjo66osF@grIr5!43v2fwSDbuELZDVp05)wLb_N?pr>(aNj=g0RvVTkabW3iCQ zqViM9!iyO?B_$<1+iKO<#P6@$pumyewaDUX*4BNif>y@F#>N(wmgX{Zkv$D;6% zl(h8klQ%b~{|#Fgvr^tT?acmFd=KKR7)})(KYzY|!UO@m_8llQK3(Ku|2IL=S!>UpJz5O@CvQ*x_u=8ghx=~yoZ6aw-NV!K z;KhrE%PT)WOIzYqb(G`g^O=m_-rhd`>+5UAKIWx?fq}eMWp5&~e}8)$-q`c)?QQdk z-wU6d5R6=~s5yJ-X#hRq4goUq5A8ZOy;Gud%PM&ny1@t*zONC!MEHoHGZMi0$m`j@{c^ zEuL7+@Me+3_8rMgpmi&W6P#-61rBfc`@g2P_T`O@%3-Uge)?2YR8(~0>{(SUEw7Vb zPwz~a>b2D1^v_?v7F~Y1V$GVA=jZ0`*tJVY?AnB9XJ=pDTg}hK%`MEz$1PoH{j+_>>`QBlz>HC5GxK`U1TE@qSJbz9l&xG3oSzSE_WjR%+e%`K|@_w%{= zljqN?S=iaNzyDw8+|IJwZ1>Z>)!*yRW|&-?VU+6i`pA(Z-`_5(c4xeNX{mQKkEGF( zn>~`opy`CYxivL5SzBjaxDep*GVZ|X)2p?Vl#(){qGnxwa&of0Md_;$lZJx{JE9^Y zZfxJNW5?GWg^#bzKAZOQT$QgtOI=}MVYGSftt~gMU*E3o!MgYQy4c5OW*TedxK8q( zu9tfvcFx>-qjyW0H^@d$-PzvGZe9K^<^8?A?~;%A{pEX6`02^X{IYjQ$KQjo~6LC^z3YN@r|v=QsZ0!^= zm)-U&->hr96}w(QSlBrsVL{cSBb}$7Oqw<=O@Hri@14d=i^9STowH6(wqIevXEnFa zwOg$4!vn{S{`2j+CqyxNdU^`}|2+Rc2WZK8Xpm%IU!TuhD^YQA@iso$tV!G%40Gnr zZGAq!K2DeW>Yb-n{nMvUw@%(1H*em&GqX&)*?6T45)L$M`Sj@%``kHmrpTU}Jo9Lh zW$%**FZb@<`}50X|D6>d7d?M)u-X2|x8Jfh6%$mwr!Bbr6122XbT+fPw$`uTzpE>% zs&<|JeQ$TUJ_8>gpZa^JyT8%}xb`ql3ck8BSiPpEMnhZs@WqQ0o0^zd91om7|6cux ziGpO`q)9?rT3Q9ArLOC*e?Dt|f5N0mPM@EhT^YXKF8O%h(X6dnQ4IT89Vgtr`|jw{ zyV7?~*8TV|sy3Ok`{>PJ#NbP?%gYScW0()_O%n&|5;3O{`a)Lc-^*Z*O~Y2+oyN)=1uPj zN>O|_I5x95PRQT)ePUF8=nbcLO$srtML*Unf4o=x=f}sx9!+t99^6yEcurP(yJ2O+ zw4&LE_WXF2CnFlXk>oxGWjmqZkdjX!rRUc5MGiM>OY4foQeOWm26n2Z{DJzln? zd0g!Gaj-3Yb;acNRzA0^kKeYvJPvj;pNDc*3{46-;;tW=RV@9RnwvTK z_`V$$%e%WVx&7uu<=MVti9`eaBKGU6Q@ocx_Q%5TS@7VY;@j> zXJ=>6IGd)Vq;$nh)aF6iZ?}qy9qeUO<`o(GR#p942HMZe$IX44&*s647bg}tHnS|T zXJ$Bg@?>WCx|o@_7dp4wty{Ow>tk=Ml=D8%^Gq*qta6=S6XZEF@xSElF6Hjs6Vf{t zy=V!!q9z|*S9Nz=J&U7-Ue}^a)9RFzejpZY^((JQQK}$S_7Ku<$Qds_j1P~k7hRB zj2jykKH6|%fBbyATCW|o!CHP()~|oREBpF7)-01*|EH}gHr%!+@%Fad%quGx9a(0$9Ok%0pJFls!>AvfG z?a=9~b3}H0zjonNR!hl_{^a=GbKY%Nvs~BRoFM<{_5RzI0xXS}E(O`x z+Dcj$sqEZoDI)S@bLHo>S*F=;1qBA`esdNqS;CT^|9*bWwH&j@Z{PNA-fUc1S$S%! zM6cVSLx(C}ENmB9E)vn?QdRnNzwPrkZ*rKoFK#bCygo2ff5+)M`Fl;RG(EGTeSLlRO32C8`H38Tadx)(d5g+V zPre*D;BY0&R8mq>MMb6H$%%=(#JZQ}B_<|rRNx3+7r#HQ{@fhP$#2w}RBnHL+P&z8 zoxT14nQNoB`|Yp$YsJnd13Ihd$M4_DU5oxcDdg;Y{VVVOPw$4`pytOf&_P{FCrsX` z=&(9gxQiUMonQ0GbI!qpjOPy@9_*Dix2X8Az%5Qzx$<^$e8t07UgPw0e_s9g_}G|V z#^S=k!-tzcKR^HZ=kxjd*)Qy?{jGZN;6cUP+wEE_>Ecfbuzg_bG(hu$fJ}R|ray1_uZ*AS}8?oYQ*2bu{ zPo6y!5)f$U?&emT?CB}nxu_#-_0kO+1op=DuU@UK9lnl5K|`a%UH+|qM1;i7ojdEf zx9`t+^Yk6dMYZ^t$Vg95&(5F|8r+X(_^2J7!R#fid?)SH3xH?Q`}{c(R@S6ZPCKd-m^&5esUzQrxw z!+*Q(Qtoe0w@-8aP7M3qKH*Tl(dm1!wgHX;UOUcC72rG}y4gN?i!jT>l(kV?<@Qy6 zewL%s*b}v__>?2pe%+^KS04PnapOiy`Tg2*!=N2L?MIAe>?wG7NYgz3-jz9)#m~5J ze_wHLd;WdR2Uz}5mRyY@|b&gQqTKVP_TVe)jn*jeo^5la}ieEj&)oy9Ri zh2v0ZpvcpL+EY(YPcLSeGiOd&rpYY5w9P+V^HvKqO*wAQv`1WAyxdk#kI&qE`(jm9 zRilFwCrp^IAwr-fd*8l&M{nM|x#@-Hp$`uaufO-U`j2czqsYmQjkV8fD){u}8jfY9*=H@*l&F!mMS}LpIH$ zDQ2^GZhjOiz%pm<+^J@s>gwuQS5`PWI5ZrZ^5XUD?%A`Y#kyNpty;DJsGx?H)*|oe zdMfJiPwwpGUb1A#hQz~cJZ#J#KYna!Z%^JBF{Lc!CgY08&1%oj&sR4#7LJdPKbraJ z!-oa=_xG)d+dJ#dotOz$NqowpYb?&j3?4mtG{Y=c>hrU+hwt8vU7509IBKm} zubXpRoZRzsb02?tdiu$eCmN45`}+EJ?AQTXQ9XUSxYb-fc6N3dS=oy(OS%+WZnqW| zWyHUmJAE;0XlSSe4;vRZx2CS{(nX7y7GLa8;NYq)s;ICieRaigX;7kx)caXVA3lCe zJl-b@3aD=J$%Tc53(MX*ELuE!bKzq*P>*}kPvr@#|E*rN>c-Aub^ZN+gtq71UF11g zEoN`kR4LOe4Zg1ScJ{u$zOJa`q9UWXxVVby>g2~q4jeqlC@wC3>C&Ygb$@@&ZJ&Jj za&}#v8YuAP{$-gu2)+|Kc_HWh*;QJmP5;9@t%#{;vsi>#~>@Lf-DtqIxx9Y2gw)XD)iwuqeD^{#< zSbg==lga)MUcNloz{t$Ox$^X#o%scCZW#K_^?LB;=4P+#<*Qd~Z_T>8D0sPFMOBp& z2b0_4iK|v=%`}^JtXKN+)2FUgRl6R2QMkH7$64!;bxFM52C1t*xC3?BV|weQ<{eJ^ zV_ouM!3T$m%F2h&o-OnEDAnus>C>kj1rM3nnj4#%n7q7sO)@S_`0&9XYD>mJW_}xo zwNZ^%R(@t!?ms{A@-p8qFE6*>+gm+(=1kYn(5tn^|29p@4GR;?zP4uJf&~p~zOycD zNOZQbwVk?W&z?!sLqEO?JvguStZnccrJq>iSx~ISY^CO*)=h)Xb zzIl_gE^e<^W~Qd1;z5(#TP*qc@~sjlQj7wFf}9*38+BPl&hM-J?QlsyvS-RYo!$E4 z5xrS|TvwDatorEhq?_~e--Znapa${m*|R6j*VoZ`a%!qJDAh%8&r33x^7!FHzsJV} zx8&cS*T#D|$eEd&8Q$$)v)*s{;spyDOtY^A zgoPd3ka+mRw{Kyeet*Ao<38`V-`gW}yq33wtyZ?+~h8r8gizx`aR(q);KHC|m`@9*HiaPHi> z8E4b9w6zVTdbz|Kwr@9Q=X;a>{aq|*!(ZCjMJLYxU%Teo-qt;@?(MBM7uSmc&CM=L zJ3Gru@7}zZpWZjjv9JH}u-((cBjIoxuZyeeU)|P5S65e)+*?x|o7pxe9uAkWtC`U* zSLN~Y(u8PbWo6}zdp5H^eJaYkv%@hfORKZ9Q$eTj#HQO$1nz>_#1%sM@-_lj9UX@S&dq=xt(TXQu z92ez@Kl@$7(WHfLy;3K13N~rYUo+!J^p5M#bx+&-&ob%E-}kfaXty|M zLtX2y+c{?1$qrgfyj)yL78Wz|?(PZ<3vcq*GU^SvI?)S zkAHq@>gklUw2AZQ=W|DPuL#MCiH+st_;CFAa<vf7qh2g;`@ES`|AGwS{Q1bU^ere@%jy4Y+ruq%el2@=I7_&V5$AMFJrc1nk6iS|mFE{fmp{`24O_nCKhncCUeiHM5c zy{_a?&C%rGZr|44Ep1-B`NE4W-%ao6acHqH7F}9z{BY&+A61X9$LuP3S-$?y z(prv?P|1x^y5GN4Z;8-Z5u&B$Q_;mMy)Wr#m*|lrTYmcf_ju;MV$GT(2?hdka(*#) zoYKzEbM^7bmNL%+ot*dPw(;Bvm;A2sIhg)a|G2)G!6k4_`;xnN**qtn=UU+!c!ouf ziJjH)MvD19&Y<=CB`wrB4*eHjx&3N`&f)F+FH)-YykFfD?^tBP(y1Rib@JrT&K@2o z;#7o`jLV|#eCM>9utmeh)^?_Sy`7-o#4j%|&whP9e?!hqqp&p*eraiHReR^nm~pM- z)s@x@7Xm;{_&s~~2Clv;!NX=`WaQz!`^?MBoo{Y$pME`VHnYc;Gs(yMmX^P_>saIw z7^oQb`mZYEs#U9$baZ&~`TgS}KZrTU_Z>ZYSoX612ev<=0hw1aOr*^6;<{(=mVSGC ztGc%JXZ~7#t*Kni3wbu*tU14b|Dr`oQCl)xFE8_r+@2?^zxRvLy?b^NJp12$IJqHe zt%!)og>AXfhYm{%if?atc{%;j%a@wmY$reLU#=e4n{~(i&tlLLBUVQNm)+MFPvl67 zd1qw9^n%Yv&ZgqS3SMcmn$1Z^x$IRG6>JYo(~Uk>QB_qJ84;oJ$^6-0m%uL%zrC8j z)O$LcQmAVD!DaFV^UodT?OYziYEbaNfqR2Y=gJSJ@Afkglj!J(9kOiHc`uZ-4Oh`g5a#2Ml-a+&Ovjq@uC$8YviKQHCn~rrbgKN#jfufAF>fBcfB)Vx;Q)iWzrQ~hH}}m=si#E(MVPne-HkF!J0oH1 zP+4jD_*ifCk_{USq*i`-{$uWOVb?%ijzjN_s_IkT#IDo!7diT&_V>4)_Mgufck9RQ zx*{g=`t|GIQDtS{&Zn6NikLPj{8@EascTWQ0!O_`iM{CT1LwL#wb#j5m7Ean%&L(3 zKL4g*4qt;tmx}6KMK!gy(A8lZ6Asqw*|R5NYnEs;JO9EJD<<3ILQ8fbIs49J{ik1pPp*J zxVn1!p(Ew?m5eM?Q=C?7^@K>izyB(}Q6WJ`-+lS7&DV9bT1&r6X-(}~v`FdJ*6jBF zetv25yp%ID)GuUh-G5?3h53}JQ!lRwR9>`rcJY^tKX>P_-?g>uDYjp87*lZJ9AmqC7 zh*{Y}1rE<`#g{EDEOefHAW%e_Ve{U-d$$^=ompX>e{avml`B_X zzrH^H{`T76-=cHppLPz?d&5-Gu_!{L>yftB)U5e`rz>?m`s^Vz$J8Zo&8FI8O}RLKOUbS5HR8V?EG_2KRwm{@%y*1?A!*a{rln5r>$YDmu}fIWzQa)ygMfz z9BTdi=fmOeIrsO?y|y;`;`aRbStgxNn)&UP_|Lbq`Tythm)F;)yLO8iCmv$?^yyRG zGL;|UP68}z%00yI=XZpC-0x-ack1&z(1Oo3XBYQ>OaJ*PRYg_x;u6ov_D>jQ_^5rU zvbC+YJF_v_J++phI^+FI&+x@k-v$4xu{*5vxs+jYs9S$uMEc!brKYn?GL5R=-P!q? zahcCdCeX5|M>B8SxKZ~_zO1My2z26MAH(D0{rLvj*K{^}EuF<|vj6nPq=t8okN3-e zKdC-HN8Htr;pBwvKUz1|%GLk*XqbI%&CRMuM>yxtm?2SJdCu7 z_vfaVZj_CfprB(#Ma9D-Bo7s!vY`FZauwWtO{3 zbhg(#*+p;Sd1NdM0+(ODx?;^5A4ehnNef&4JwG?Mdg+D@0lXm$^8ZeK{GS!VIAzL| z2~(!H6c!qOettgv)|SjQk(=9eqqkkyQOFX!%tuveks%+Sl#bZyMf&+0BMQpOj@`XG zcf*FZlK1zfo;joAH^<^&pX~if6DBMuVgB;!>g=kouP*PeuUGS**Rp)M`r$U-$Jf?I z?_bT~x9r;5WAW_WN1GHJ*2nFQ*!D*F+uPgTK0Z9IuCDbOuL2??Js%$fElh1UPCs|& z=eNJp8`ij)z7zcC%Xi?q$EFQ>T2s9w%yMSj-;j8iA#MM^U$6U_;yIc==x(q!F!-Rd zLD)KZTfxIa(!amIKd-5)yLMy!|9{W>WUa%Z!@_QF%Z+|^T7UnXSI?e3`*PO&zRQ(0 zk-_XgR$hH|{%Y3Onn00u&L#o9E~VSsa(9E`kdf)|7K|EPbDT4_hA>8yOU|>CE}_=MQFWy*9D>==#{* zVLK~7Kbtepwz}%->hSZD4gxIx-A6aEe7N$mWbFpyfAi~py*zt%w)yjOb1Xj}G|RoU zgL%r7DIs&MO0x=5Q=eWwU;r-Nnm1ZX9q*GpUHScPdGSOgh4;+Y-yQA$sxHV95E$s_ z>dIPJ$UW0Yby|2_Wou6l&obZHj-Q{MHJE)?MSVGFmv`R1GYabJ)0>)V91fg5Eu5b( zpLKPW>*Hg+E2FpT*;aj75WBk!l&@O3#f>UHB>ejJ_H;A9-HCU1cfY*5TYRxw?~&7| zyZdCVC(WMydo`O|mH!q?u6qCf|Ms)8H8-wUp|NN0UeJ-SGi)rssSBOh&#pF^)85`b zYD>n+X}XgfR?5mZ{914MIV+Pl%cjmH*g*r|0r zTK@0fzm3*qZ!|zB2tRzhxZ`B!`+dLP_4f4iBqiM5lzMtm^p=c3X$KM4?8L-{PZze! zecJrhUfa0J!NDPNecax8!Yl{xm+>VzI)aYB`Ls50aoe$b9zTBnp3dZ~rKM%_B)77% z60|#H*OVDEGI+STKlk_dZ@;o4(D~}F(rm%mPjBol&u1*y8@)X*YpR#1T7B7-6@lV> zayB#Gy?Q03Yt2wuSvl1_@6L`3I|?5^9d!JSZIz8r zK!89qJHMKN!32N%*hatQi>trC`|;z)hnLIeU)oby3|c<&=<(yi^77?No=kuGST!qY zYVX1K@44^)mktOFY@9z|9(4Tk+gn?gER9{1ceiTF6p{b$YES&)lC|1${N_!`+qDb! z{GC4M$NUzJuBas%^A^9I_~HMGV$Vj6t~!s_>tPE#1X?EVH?$M+w2EUWVs*6m%igD| zur>QsarPz!j_>=kzWrDHXUfOWpuq9Fp}nTIcKMeV7nAQC=C{AIAp82dipt89Ooskf z)nzthMk9-USykLXwa5aLO2` zojKn(ea@ViJL}`^^M40R*jAOiy|~!@f6kkIwZA_#Tw1VTK}O~6>+$t-ch~&#{QpEDZ0JPFnq!Ens`b{EmU&E~0(=y{c|jZn1#4xO1!5pK}yo+4uWh^}c^q z%$%H@DXFQ61`-)6T-%ypA|?E_>aTwF%6i*4rY9lKkAA;jzyB$>{+P)$uu4SVn?d;Q+Dhuav_&d;;`*)6WWZ{79RUmKb)W|+MCTGid&-hO*q z?(H?gYCaz(9e;OkueH6N-n*{dyLYeF=;Cu*yfJ9?)mhC=O?SfA$K`Tya(?9H<__+- zdw#BUcvpzQ4%w3jjIyq1ELBr5HQoB>Sg&+w!_Kg^Q3r173QLCnhQ?Yircn zuZ_zYySlqir=OoUw^8KReSVog#rx0M9u!zG^AG4!6D8fZMxC9VXXfu`nCo}^`TX@s zmzVj@v7CJ5>+6?0cI^@n6?Kh>kf^Jx+p%LuKxE`f4<^w1PWieY^W1t5ef;tz+98(l!W~K{oD9tPyPD(`p1vQ{S|*cF1)h-e#4_jk9IF( zdh(&Ll?4DYt;6<+U3j-zN!1? z>RkADolPfZ#|O1bwWTsEkADBYp!3g?B}-bu<7>Z$CbUJZy~oPq7Uep7x$o?2Gxxuo zC+Zr=yJb@R`uP2JFK(vKx9tfLu>GJXzmoUL(QOBQ{&?JvO3n=pk8I{^3~z%vx0(x_!z{k%HQ3ocyVE2 zzlys0{Pn9=RqZZ*e$LrM%2&AI)G4pm3!U3}8ki@j$glUCYi0Q9Gw5EoY160AmNLx> z5fT(!IAy-PZPgZ*6*n2Z{6a*T^ zF1!);pRM`ep8Wg!j1D|{@uK3Cn$6}LWp+o79b0y1SE)CPSA**Rv;Cjr4+&R<)m_}0 z9WGb3LiYd9^Ys#TH7bmZ|7yNo4F|Q}il3jGICZM)_Po0GescI?=(;QDLPXZC?C z^78Uhrd-btG%`<_I`wZNyF=LIH`mr`zu)uO?C4Ri4XZ_i)DOI$)-K|DkvrhobnoeU z8?&$Ll~}D@9I*IeKuE}uD=UNNv(3x9yzJ}(W%s^6tC$tK7Tr6RwoB&5$A|6m-{yn% z9Bt3LyNW%<%G$bJS+d^9*w{3_?&s6?hJ(kCr$@!b-FtYy`dzu@IyH0i>rtE2d{_T9 zQ@O3g)l{%uw9KHadqwK$X_m}_fq{WXm;24#bvtn#t%d*7KlYb1xCE9Q zIW}YY#f&J0&`0N2hOeI|S#Tw5tC!&UGc%3##l^*?XPaa`dibblH*>+GBb|)>cE47v zbL|!jXLSTE#rmwItSrx3@t~3Yi>9XL#hp8M-dneBU9OC*tTV%M?eKL+7}L#iZoUv6dcdwp$fG>ZzTO**aSO!W3V$;p!@`FJFL z?cSEXvir$drU{cKB{8f@N=jnd8nyP2$FwO^QW$y;9z4jqVQl~pTl32o7Z<^J=}#qKOh)ti*Q zkL6_X|J@fPcZZ(8?C<~NxP1MQyLYGR#ad0CQ86oj|KDj(o}_Hrv+9>EA9Zj3_~FBWgddXiHKy+jT>@Q}Uw-)RonKj*nfv4ZB4u0I z$tQah8m9O2uZ`OJ=;`$Mhd0ybD;gS3ytXzvIPl}~8~c6LgiW)52}wv~yk}@pmfZ3r z{@;`H0z5xX>ffvToon~^OK?Ms0gtU(HhbgFKRVid*w)6z zqU?=C{=T2j&ie(399`$JI-7BaCdZ-AW$%62ZYgc$K7M^oWbwq@YMu`gmugnCpIM}_ zj4__OX~V(1@4OpC{lmh-F0F|)cI%aLeSdH7>&mLCcZ_y|EQx#X{_t~P&(D7^wb;G? zT+Pd+)8p2zS|v2U{@+i1G5xqZQZE`sd_OX+*_K?V&vB^e_VcMP9$Z}P{*>uL_w8-D z-Z}U7RQj(Cv%d3gPR}9@IreGXO&>N->#vvk_mQuGcXp$oShuP}RO!OawxH8AKvktp z{=Ge~S@Gb( zgPT&l%jPL?Tv-wL*l~i|;?vXhrRA#MY&1L2q3S(Nr{-a+c$KB4rLO7hvyD={Z29@` z6%OOvw{PFpe(W{BR}irJ>igS$xwY?hKG#zg zy|N_9ecc!Hh3o&QSXi`d*kEw4`n~P1FE5*$o7;G$_dWXY@o`E@3TQMCbj@U8;m3Tf zU!h@PL7}0p85tTIH*P#L*BW#|Ut_ym*6B9hctM#zOo@q!EUc`X%HPLHndNkZtxmnN zBG6~HS?^-^exrg12due|tt#wK+!|F_ROIyh#fei>wIe@VvyRza`8WM>jBIR0)+3f_ zI+gviOtXXH{#6GZw{Ja~bnxiW)@y5{i~s$ptlTWMQtm~+1%rJidmd<=!>_N40~foA zD=RCTzbvr|TNCl|=QQ1DuBa^;hQ|&bbetw5cWbdb|H1R;*K^EZW?{;?xoPQw`2BUc zrB<=^4`dszt&J9zll!-B_wMRz4UEinMK=8W{BC_RoB^ig@9wluTqL-AqJ=|B1(TQ6 z2H)9c3a^6IU~2)|KznY)#l_Q)P3vQsVOP6L>V#eWzZ!j&=gbq{$w#{=b4h@=4nFHCZ7Y)Au@^GXlIgf>Q~%gf8V^-7h#v;Y6| zysJX_#YL|E%QJ~MF^cJ}WVUw>`#TDnS0SJzZsO>LU0 zx_Wq&ju_+0<>iq&t<>f6|wrpK@cX##WtgUS#;^NQq@9z5Q_#k-m%|C_BY`kiq z^6~YK!pCv79}cp+XP8KZrlzJ=CM6|R78MokyqM9$q~-1H?Qdai9WUT&SoG(|NBxx{ zs|2IghUFT~)asMB&-3u}(_6o4RTk@$xt7Ig0Vyd@emXX@?X7)rVPTlZ(bD!EzALLa zGU^^3=@ff>tatY0DN{n2H&j(t?q!=Z(>VRy>hhJP= zyuAMZ-|w{y_VV)cuYCFPMc{!?LPEll$&)WvB!CVrbG9yfvqD6t?(5a?oeejWHmbC= zxBEL?@=f^p>1jB>yj=|66_#nA#6>U4SeKpo{p#xKLlYF8Kh>qq;Io!%^P6imY4YUe ziy2Ft+rJ$-F0X3%(PZVyl`Sn-QeRwH7!nqy^`CB4VT^1A?+Ilo;q3`T%875MEvQ{4V_tgfj47vLE zU%jA9;E|g*CHGDizMQoD!^uzn2aYywQ?B}u;9~nF?_5ZzgyxQ^?vK`0i0Q;Q-2Ndk zt>y<~TwI*j68F_tgCZh4Dl2#D>V7_bytuS?JHITi{!K3NW4zP?+} z%`jYSWM>z*zu=)$&f04%tgN;x!&X~cTUzp-J$aJz^XJJ-3azcJIk&cSK6sE|Rq|qi z%$`MylB_P&{`>Q@U+HW!1LzFIhf9|(y}$SGH-E|V*Vjg?=iT1sn{j1@;O4ZmO18GL z5ALp6vxY0D>|EB?P4)ll80>yLXzr8hbz=n`dUbhPw)`bff zzE9-1(Wan#BEtv=Wx zsNB~tVaTLn*Ie6h@bKZqS1w-ssPI6m>(M??OMPX?Dz@C)+d{XnPScN{2iopq<6xY4 zs3mNXh97usf-$+)_*R#a9RW?XRCmV3MHYSz-i z$H%xH_WLbgykGhs1Spd%O1Uw`b>fe;dOy5r>cD8lPWXo?%AI^W1ygL?|aql{`a5Hd_FS=bUw(pIXAQ4hVFZP z9duHG`|ocjK~uZiavz^ObLL2f$-N+ke=aAU2Hkwdf3JGJYrMWvVM6%3xgQkb?4r)_ z`aVgGcXCiz8J|As{kvnWkJBT5UQ3!+_s4VE`iRD(S&8T8+2-8dHq~>o+U3pZ{5jjC zQ}a4BM2bM?FwUPpfA-fvwaKRCp;PZ{N}zW*`KB<-dU<)x>+I=y!(#IO(o>`NH>C6T zBv$|Zdfl`kX!&K?s;^hWKTlx#d*aNQB?}fX99VHN^_9vRC?`)Yr8H6(0}iv21! zNrvyZj?Jl>|DR6lH_e|fe|ed2as21g`um^UOrKx+xA61!{QLXPU%zh7=<({((r#-u zEpzkfXU_Np1qHphwRJVmk}7^nOUqM?%xoH0v$md9vt`hl@W^e#9sXd(?bE*PZ~1S` z)gUbPpyhMvW(~(%FYj?I|G;tO_y0`mk{1D>a};l{|8DEx=jX?I-E42Vb$s~a4WEzp z+yASuo^JZ;ch&c@al%S=H+Fq&!0c9&oY}G%Amjh&!;);@--WByTx>ieAFhhsU*IC{yhEO z-slUp6OwkUXaTYLNE zrQYIy{{H3U|mu^D}5IQ%sCgWu>K}BItm{$@AkK zo}Y^iSZ%r`OZ50cXLf^`J{6Ufe-|cfj^0sOQJH!2%$X;PE9He`#E;enFS(nRn<;9v zlV_?^=i!G3B7)od*X!p#e*E&%Z$7U6G~=2h1zlZSJ7rkdA|oS1!tbAWz#`Qv9vV8e zrM>;~g$oUzZ)Th3-Puw3@87>~>~a+ed*arAXSJyO^yI}{>+*H%Hh*u*e(2af(>R?E zbl_}?QKg+a*Am_?R?vNsP9jHT4*NxvxiU*+fA+~*Gswj5F57DPK3YRd z3)JanSX#E*_MWBqluZ-bY%tJ&0J?3e z_xQ|T6^~lByji|v$KEy#5w2tXjyskkV6>&maF;xGFf{%3O?sJAi8x$$B7 zui2p8UTH=%+vM!)Y%(7o>s_xs)yq-j=(6JH=Vlri8~3K4o0IAE>HT}Ad++boygz^cT2_CPxq9{L zmh5tCt*Ns%Z8CaNWEr(q{Ol~(&3Un!V%@B3!%pk|vApnd&(G3eE4F0YmHfR<>n2=Y zVV-1SmXMXBqGTmB)2Z*?g5^o|it1r+AD*eqTpS$|RpJ_N|9?-@{Q9_$?d#qjWP9=Z zV{fr0YRj8(Zn`Z*PnJ=Gk;= zboD?E0s*REVK$h%`PYu2n4t5+}HxG`|q<(C~jJx{*8 zyv$JY>dMK#_s@Y>8wl@=iMzW#e!torpLy*>fZ1Der@No*GD>qEoAtt zN{fzv2@DN&Ei5!_c=7zX`J+ESKQBF}wZV6>d;h$P3!U2wEkqmM-dkbmPUJn-q5H?wL@= z;);V2U-VfN;RL&ZVg>CV&@A|sCt7goYacRa3 ziK%DPob{&Pb`31axv^nk`^Ahm3SCP3_Sx~s+ch0-=bt=dM#t4GRb%7Ht5#`U$ujNi z>;&z%-+Q{~?XTwMX3&6FU|?W`&a`Z{H#^Bx`0jyR<7lzT5rYZg1-8Y4>M8vbjF{MaFhP$2mr+U9W4``7Mv`ap7z0@8>VtIY&2o zo5bP6ht1vH-QBn6+}!lKLs0n)Xuia}Xy=}~3kw|kANrWu)%~$pq#-0Nt^NMa&dtC3 z%b#?(a7jo@XXoGFxA)nq$4Qx)D}Vc6-BVe7tXJASD(V(!U)$`-lP9M?ShN1qN%i?h z6j_a#K&z@4cArf%ex$<7&CjoHZa#g{qD6Dc-rkb^`03NDOG~|{-&nF_$)2^*+w)51 z?fUaw?bUwK?II2TnG3>{rdY;{^&1_ze&6W77~9^s_c!M=SlFEA>0b1M&A>;*^#H;DB^UAfW9M=>+c>cVd zm-%+J8=~@K&IamDSDwsi^Yu!wjAhZ0%Ul~a?%MV1@7vqk|6A1l`f?t8=$Wc&YOR;o zsb3EdFFm_kqwAhU`4xu7#>SjmTTXuc$o;^(!CkIWMN?5x5j3%%uQ2t_v4;gFwjT~K zf7)03dzG5s9F7Cw@wKk6udTiR^4i+lL38Zt8Wt^5n$DEa(b=i#xau3Hhi1F5noq)s z35rwA7tZ;owmI?QBG;UIdv2CKVxO$$`zz_ukxqVww^O}9_fCNpfIH>nteJDVROnm3 zjAc>H>uYO|tE;Q;zRSeUC&RGe`0?YrFUc{+Sm#fU(Yvm^^e0onhX;-fh1b``Mo&yy z=agb3={-#+@zN5{g(pv*Jl^=AtE(&Y-@ku_+TrVBzML^W&oJrtfs-;B)`gFb{65^q zTg}Olb9otWYisM1r%z8NB_)Y?WG|U9apK3F)2ClQb?n$Nz617WCV!aU7tM9xzlBR+ zkLxGLQ;h59*Zs;A2)gjl{!+O9k|ukv1y>Rbco=gSo<4p0&qI%iK`&;908fwWCXF@E z)q*qGcq9_o#nwe^OuA|N{m$aAA3tjDjEi1;@x{H&nLcWIvAbF{oXWnr?%7+Kw`}jJ zQ(j%&-Jl&+QoU?jqkLmy=W1U#q5Eb1c7;fO-)VTIT_u%XLTjZv}||B-Mqu+ z&)<)%_1mGg_k->qF}`-@x#z_5^WV>2wV-12hMPG*e*C&{<%Uc0Ucvtd7~X_wK0dr+ zz4Cg6`HMCMz1vrsjMn0Dk964F0Gs=*MEfZfz`>+jlvE}t3V4H#X+N(JSSE1Or(1ICMvsE{r&ZN z{i(gx-;Ii%cx1b{v>55>^$9AwvCOq7d{jKgI_dyu>&xZ{9fqxoH0m0sJTsS2Ff^Qa z#VW~O%~YwJb^LqkK|!k{_xZ7MGrrk+M@xGC!+Ks@JjA(ecsmdQ@Ar z_g?w6HIe>rZ*5(zE2Mq%yvMs1`Zb?QnIk>ovoj>3I>Xnv` z7`LF{!y7qfZ_2EnJ{8R{krLrLs>|2#`aq`H@f4%P?YBRE|6aa3Bu}htck_V-3P-B6 zOGCF-{H#dMoU|#3)zM*XSmNdtcXyXK3KT@VeQ|o#$FSr_ALR6l3`@5f`!`1-$JSMW-k>9Ex3h;f6?o9lGh{oC@o zc;51czTIVScNHJ&k>uv);##q$B>k{$ckoYfmWMJI%0E0fIGM$9#_FJzdmLBIFL-uF za@Fe9hZ%IlPA_})>XoE%`nflSprJKBc{?5j8M~SvXO}Ho<~&*5zpn1|G+lq|lbfG- zdV1!Yo0|I0H%dLVcz4;`s-rJoAJ5x9U!3LPo0pfDTibpk9`V zCh<8-t89OJvBX?`uhq!5MCO)&g1bPA>bbJ!iCcvPt)t;|lpJc`p6qUqi zaHR6?*(QaM>YkmpL0@lE4GLnQK?C&aQg1@%TLBnLek! zy}g}&W{%}%ACaSs9+8ZO9<%oGYB>Fh-NdkJ_3EvSjg8+GmO3*9#K-5qy}$qe>xmO5 zJ`D^E+;8*u%jG(U=BrtyA|fIjmX?;%6T}Xtp8NRC$>?^g<#P-18>$>lvmagYRIK8E z&^70@TUD5q_4~s0n-@&3mRM)r+uJ*3>Qq57F~7PxJ7xD}kDfkl-M(FH^2sAdyTv-BYPh(&FAiGy zZ>9C^|EedR7MV!#itERLwiG1&{PdKCo&E8{v|o4a)`W3$dUkGjrQUV!>BI}mCr2|f zbnZBwwt468N!p?_den;eQZK&Rc8!INEooy!OyEaz%|EJ74YgizJ@T3J|F6N4D|@pK zN!D-PUitaiuBN7@{*M)P>zl4G^PSz7cWXGflJj`*Hcipf`^gzHUlA zU8Wzs?TyByXnncNy*D22S;zWAE>pL1(rL$SYBpZ;;*r9V`<4gE%>;+v{Ev_5Y@>%Zj#5D_UgYK?l z0$q{veonvq#1nI3a#kq`oxG{Hq>B0f<{#5F82l2y%E_@R*et03{dT)`dRkhXlhO7q zyWON+0%MGepPkwGe$VGVZLjCq>yoZ~YPp%S&wXi-Wx=zfyFl0Fg}3uaei9UVnyfSF zeeMI(T^nxXY>VV-eZ0=4IKAoX_q$n}Pt%F~^yP8C{k|Eez4#S$&xq^C zEr{R$PbX%Fz}6_=ni`v%H*dDIw1Cb*1YJcIwf0!#^#^X#3!U|*FAiIMb5Esl+3vY{ zcXv&kKHYtNT=}E-@7=4atcrHdxwiIpg4t|Csos>-xoVm#ZKKwTE!X@eW|Lqvv!l27 z>E`oxk5?|Ar(|n8H*m4r#+sl0Ci(YzX3dH!4|v*}U}9$U;$HO(^Y_kWy8g@G%6ew} zEirlVdShg6254cwX$K#B(Nv-T8yvPRTYEfW+4F~(2fyKL?2w~o(WXUP=qV^}9zz4`3VU2)4i zR_OU#w{HEFuleA3tVhyV@7=w_|>Ae@cPWvW-&WEemoHQr|vgrMb*cn z;_+XVnqa#PyCjQ ziK`YZN?M*CAH6MSW#q;r*PpM~?~nWZ@woi|8M9~qp7MG(=n8<;l$1T9Vq)L^?kIfx z?V-rOsZ*yebL|#;I>)~Lp6j7^>%&%0XJKWnWyzSkmG4eQM#h32JAQPn(CKyiY zbM6#{%-;;4<7dLwMk!9_d~;`Kx1#eqRRe>DZ*Ol;oA}z#ix_%Kc7tY4_K+f`g&@3+{0#myM*I?vMw$zJGg)E==#0G z=c}HHsp+M4ci--@`6{}q*emk$!|f5iVl~Zw1FNeSe*UbeU?LDZK|an(_jujegyYv= zdwO|1dT6*fGb^iT^3NSQLig_7dzYQHH7Z2ws_DsvCQ@gw9-Jqo7rid;q1Zp|56hz% zTmrwi8ojPyOi4|Z3=RtFd;a7}#FebA*Fh&tuLxRM)4<}evGVgXySdioW@qNx|Nr~p zLxFEh%$$Yp{c?2-V%@HLN?u;dJy0ywyG(gfr))!~u=>3CT_u@`QBhH!PE1s8X7uv% zTEooFXS3$|YiT|iivstOk}Z4-_U+pzl`(h9lrO@QPWIiobLZI3;^)ugEsK`eSzAX- z7$i6-c)STuOia{#et!OX8cJ>{BsrZRl-?(B6AT^;wg=f}qe z1OzyQg-t6x*u>gAdD5gBhOVxzSyf+Om8#g=?|(PZUGCxT_xrk8QxX#sd1NdKp5575 z>|I<`^h`H?UyXo6aPVZ%RONClEv;7*ih7@_s;WwF&%gig-KC}8$^CM+zbai^T%OJ_ zOm1RkViI9sYj)(fss8ol<@IlGZhlVA%F2p=e#eYg#^Qs-B+iYF%P+Tnd3*c1?9tto zpP#AizAN|VYIuC)YGvFNa#Wx2{~d^4nDH@Ox}0n37CorVq5(! zAUgVVY&mbr(^FG*B0fl-JbCg&ijh^xivzE&uIA+70qr`7(3vL9_Il;jteTn{i`ri% zpP!!xwK>%1*G#&+-2d|4YV)Y=eUBd;JS-wU{lf)k{>v8LXBLr9& z=lZ!PEcnv#@@th#;E}xT!QtV@muLoS=xU}OJj6QZZhh0c!|VdHuSnfbs6W%2cItDt z<;JfJsxvFUyuRFTU3Kr^wYAZbwpAv)yu3et{aO^Dv0~l2V;Ls9>W#NX`8qf#?Ay0b zf`{!)nz7MLp7z6yK`ULB2JM`$=y*w}Chus?okR7)|8m3+ILvv%$Jg#W$2k4m(m8Wv zv=z)}pRK>UqwsMbC$>+biRsi&uT^|~d$ zzrTO~Zznod5PsgoG2DN}*URfFJublbUx}j=sL@A`lDIKNBi#q79Hao+a(95x z;eX$@@3(#PtM28crAvSQ{Aspe-@=87yaqQ-)k{lDe>%*+xY(V&;#+uN;6jEi7cXA) zUl+IcmV}hlub!~gzQ5mWK5zBw_wU`Gb7N1OI<<B<%5kfI<&GYZYls-JvdaI*@W3B=RU-<32yUVXD9If8*Yahdw@YPo(dfn=NJY|^P^QadYfU{8w=2`v3@yQgV|?m{(imwfYSqPzEjB+MIN!)A3tJoInUtiId;3_j+3Xc7Ry;T^U$0_jHm#+l z<^EaC{c>m0KKn*8y0kO5dUg8f{Xe{s_rJ~x6Dd!PdkcCMI4orNeAGUR9?vn8e)@DO zkCe#-v)o$&QBhn6RtC?s`EbC)X!9q7E7usblJdHj=D*LgF@MM@BqX$A)v9A@n|F2p z+Vl6EwTkNY=gm9c7bg4+tO5W^oG3T}oa{g_Z_Y9yN6d+%xOT zbFsXK_EPoRm>!(-z54p=wYRsoOEcE54f|b|oc#D%=;|=WIZ>O_&aV9Ze*b?#&ZApA1+)7P(9Eotj?3R`KFMV*fJ^mx;@%YTPLggOEG45i|N)>eR=Uv zENOSy+f}S7ptaMc1_l>uYe0w4=tgb1(AKhpmjg6jDb?#X+img3tv7SJ_Uy6QJAL}} zn;9mn^ka7|d2x01bpMi)E!CyA1rHkJPO@%PQd4_&`2GI>bsp#E*~TBtWYW>mSz}r8 zA;Gy{&i0U!vT`WH@qYRB*%=uE6D~bE7jGNr*H>V!|Nh_K-`Nw_Fdi-1S@Wn}zU~I2 z#M@tWkB{|M9q?A0T>JO>{(q({%Rg+t{kHFLJOA}lDVr8pzuR&6?KaS{GjahZQZ~(5 zv}loc?5>iV4el}r*TwEG@>QFBet~0iO!th64-XDjEzmSI4ZXZB*4kY7#6suxR})T6 zkYkVn4YiebKI-b?I{okO@2`JvpXUtKk|JRo4g zZ}*$KO0#7Zs^9H=?o+h$&Qb;o(4^71>e9mL(Tqt+Nd~je=I#F!|L>DVxA^~Fy;!Ty zUoQKD8i7Uy4;&61R=%;XR(f~&`(@GF^Nt)pE-WVpy1_1O^UO_~j6Qw(bYpwId}(Rv zmdwj->hmg?nhzdW>OFmiS+3Q>g^D%5-K1RQ^Z3aJYB20rBbA?e&JJx2`2+q zT@A^(*|%&{*MS-PGWt77!89;PlW-diuM>_xqTAr0>s}5wIg9 z<>7^u$xC!?kD1^9cVzkeJx7E6?fg`6Wf)22ULjhWn6h#vS|JK_qppH z`yEWnzQ3 zOz(K!-rk;Hem%Ba6|`)h-JM1zw(jid)0@A%x%oMF&Y}9R zudb%9T)C3{O4io=!orQWPi-iF9~W-pZ1+I&_5&voIUUd$TE-UZ3vcCZzkaX$er?wI zV_H+a7*+)b2S4rX?9BWB``z8$U!OjG`pYc$R!eJ3%Z|q?W_foaxMzrP9krT#(nMzY zhwV{o`#_2C$F|(to0j{{y>)Ua!<6aMqrbhoo6YpeHSo*fnS~DvERM{zE|;31RP^0? zcX{xHc{Y`s&OI!cu{G+gXc*t~itKh?>1Qvlth_vV-D62f$+kz29{uVRR^L{_>Am~z zJB#9HKFSV3K|wm_Y@T$B>zh4$`ZRLlk{0jION}niXHS}RX!ZJir)KBxJNSHl{keaC z*(*MuHJ@qz-g#d2JIj6lsth|jJMYw0+gO*sd-CGq;=EH=?*EYp3|z8o8E6$%UY;IX zbED^EwTt^|h1&UK)9&utYV+~P1nJ}ryo~`*|+Y+ZqA0|JX znfN40Q%6fnNm23PtmnJVtv8q2X|LLGN2GXoy6t4XgY6i#^S! zvUSg)ni8wKrnh&AFoYkJIbUME`KC`rg~i*+y9>VD;Cy2?an~*>f!vDMOqFR-8q?-@ zN$Gg?rI>v>a)n9F&c5-&1c5eQX|L2&)kPXfTccL403A$nX}5pAiB#`zsj$^k+uGQ^ zRPCMp+TO+n)adwhB)LE7;B4#pQ63pF>QmyZQi?HS0XaxQ^;rk_5u`t?7-ZCC2m z{^hVHSbxxJa9cd_|BuK0+temw5K-YajvZ)Uf#t&L5LZR)8h z*A~0=c76N$wX{F~`MJ5J4Ld;>jazVsFf3WVT$uJ~=r#dik;`;jd?a${`pId7nV_&!DUV;IG$h4_b zZx&qYVd{}IR?|^cJ-Rw{b=befZoN$XIX$2|V;MhfPCq|y%k8&kpBC+8OSreEa^vY~ zx|h$)v;D2T#L3x0hVS^1PT}*6eR+3x?L2wbf38*N{K=CqFA34o&AqjyP#ZKuzB)f6 zV}hb);L&r3wI(pJ1qZtqeR$v~rWfOp^~&Sfnaa5{jnhAUI=$W?{apOd4~O|@+W${3 zDk^$$eZ4$08&ARS>R84!u2!a{L7i@kmo0NUe{XNKr&rebEt!`&`T3JyU&~#xbm_+% z$;mJ8?l%AW^(zZ2E9hokFE6hj?{>fM?CM&y=g+R233(M-9A&$k%ii8v7_@T5nl*bC zvK}~nx_iTDH@xjjKw!Sf!mtPu4 zEoHJ}bCzAd=Yx${2~TAy>%PRb87p)b9t`Nco94PQXz{mCZqfpd?+f2=KAiRR_WSyT z0}ZKHjX8ic``TYLJMRKbvK0Z3i=5Tpkto3$*T?G%FoNH@qgWLIJQwj?U-#xks8c<>0 zqa)^B=Ine}+(GI>*vb&2sxL1D4!lh^m+Dp34qNl$&GYBx8%tkb`@XAhzIFMzjxEOe zj$PIQJLdfMS|7KU3Dk#mwF(Fho_yA_j`zC%#KPs;Qao(Mg@qegGX*W4&p9mRZm7IaBGu(u)~(uBYU$G4#kq~G7ibFbpD zZ`%2JM_V}08-Xm^+p&XVX;A0cw8JS`P9lzLr!GBLn<_pE)%|=beym6Ga04T=fdr3>o7;!8=Jz+`+~ntxE|Uih&TiehwSES} zi4>!W6DBnHJ$H6=JeYq@oP}|+Cs*OdsI@#&CLLR|uWu^6tk%@T^k27zxk;hm$&bLU zuC5*L_x=9x_3P4o`|eGa6Q7{;Mp{~0Q&aQdyWQ+UQc|y$Wo(ODTl4v?zsA!KUz!`Y zWQCo%fA7ok|FUl@zaHk+iAq#kIkm~VQuId7zawVhhJR-!9NRzbxW4qI%6~i4^JZ0E z4BhOw{BmJJJ=T?ic5r zxN~aL`^ysZelf(w#a-X@Ax3}O^R$l!a)l1poxHqGtvPbUCDqy4dE@r&<;jJGA9Ib< z&g{tk`s(UYkDdP>=a>A`{Zp;bwMeXj`9R+G+wv6;81E+wZB3L63Ml@%KRKq}ioZdD zK0dzx`}h6-_paKw@#E3r z=jUAW@9o*?pf~;WqeqXJQjBK)eR6U#FE1}|phM~Eu(e^#x3}ltzxRIk`+d@&4mZ~o zH5HXTyi-8^$;aY45dxfBf>wq|+S%EiXS!JY{9Nsy+xh#iDrLSjm9;9d(CB(3s~x^> zPEb_TubRziXPa0YH-v_Un!dZge}CTneYL&fdNDKPL`6e0jAo|2F0r!v^5Sy`FU+^bhk?&=z=juqnT&sO`Uo*B05^SRb=D5 z;<+oM_+`BO{pJ6F25(laUY*_N!t!LhG_@*T=;sn@C;tnDyk)Qt#<{(VJ3Eeq6O`73b~k`Sl@7g92k?W4}L}o&W9Z`Sb5V zQ@ZZ2b25`T5uB=jYj;Iej|YX}(?UK3-m4-Pz{(_cs6j_VzPd)2h|0y;&V^ z=yoN&TNS!GZbO6)zxCfoZMN0lUR+-n8=Y(*ait;Y#)icE_sVYP#xqXj7T1&6o^o=M z!-Cw9kSlf%3Kw6T@!{R>_y1CVetK%965idt_)ct5$jXo;1Bn+mHZt4)+0er-FLU(4 z!R8y=a;@L*`OH`U`}X~c$G!U}&6siE!$W0I3(!E~m1Wr*j-5LzLqb9rloS;gg4XQz z$yy6ZNI2xKowqgn`o_e=`X^F~mif>3+gO*1dwaY4(xAlcw*yySeet#GTZ&AogSkh>+NEobH2Q>{?bv73RC8#>rD)6I zg>4%YOr^HU>^*oc*0$zjSm?&$?}E#G_We}#(VHF|8rs_5&;R`VoI@?s#7;7Am?^!% z`pu0sVuxmJRnqxorZ?BJYvHb*2^|tTy`>fGu0j&``+I6 zTE{J4^P#cm%8I~$x=~wJ?D=IKC~~-P*&ikutC9}+`agpC`JT%IG(^^Y+G1>MY%J(% zn0a=V>8kL!%G7i}zdpVI+aC{_ck1u?(6lIed!CW{&;HfL@VFX5Zxpg4`+`hfEiz&1Ff9(u+1( zZyl$eIJ-K>@4IS~LdYZBVujd?|NOuz(2Rpc{_}+Y1^LNtx`Tu{u-F`n#NJeH(U})%9 zokASm}R!_KeXb-=f%?>YtmaUhU z`3CR(ey^HKr7_K!E6!Pf#nym5lYM^EmdT+?tkY&PHebxR^X&2C$4MVAD;<^0*zx9n znPJUd&K#zB<$d>?R1V0V`^awfe8IP)uV+yq=d^T+e5|LE<5vC9|G|9oWBdW8w4D`uqRvxtSBTeEIU<7SHeByK-g9 zTbuU=K_4FZEpgJA_gI%L?BAcy=iP0}f{j$(6&*b}S$#TZ(*(QPUnTQFb32V+c&=E# zXJ5kV*l;?vF*_?u(lChybnV#X<^Jg}E+~RpjaP#G3y;g%Mr?b-{_I@s>h9CC(Sd=6$NOZT-`J=;$F`a+uqAG9RpEKt?~5+~OxS#rgM&js_pS7? zUTJR+4};9i`vM{&E|rx(<;DIfL>fFPlDxaSJpK8(xgjAT0^;J&!x*Hbr9o$oJwDc} zY->ArYxZ>^2?>vo5Rt^hL>D)=M{nQ06+2V3^H0~@A6K~!UOgV0Tvod;tQK^4_T9YW z4+|1DMyy!3ZrPG0EsGW@d3k#Wt_-pH@u0cnQ_CHOV z>`ks4&FndK%F9HG*O&KehgSR^X5|gDqy$+cjb_fd)g^SMm-otR?GL;>2Lqygb4x9C zCo5;(FE*H-)wikoyWXcyp8{5!vaquUhlW1=C%L}-Eod`e?en?i&-Q&jXPv9=KkrX) zR@ST4d#k^HQ(?_+WoJKeJAeP#UteE4CcU?A*|Lm=!F$jCIBd5p2$HSi6SO|MA7+g~qqWD$HE@pthZC2Bv$M1KgXHHowEN`k z>+~BAKkN`@d8n!K%{DwYu3Muky74=!qXnx%*CO#C%};V3*($6M(MJ7*JFjypR7` zJ86-<=zqOq6BU-U?vvA;{wJ%&vj6|;zmah<*G!}vvaN;ds>T{C)gGb@9t4 zsh9m4R=2Bu&54;d@qT$seBS$~d*0=j8JkM5?MM(t)UcB+P zjERZq$G6-0lJ@^HL$pM@#s2nQT6xo7yO>4+JNbsu^0%!@f> zY{dGMLDj|=^^(oL;r{(c%H6jRFMAoRs-HgEUQrmI;;(>7nc zal<1vcJAtP*1f&G7BYNSvP>5)T(~9sdSBVwTMu8qer@M3z%s>4)o5nV%HZWf5)udQ z?Uj6Yiz_ZpNWQ+NhX3BIoTO(hVt;DWRlQp0EUBo^daWa48|v$`#e8j2jp59tAGdad zTvC0i!?sXAQhj?sP|@A0z;Fd6rKXj^@rt&#vU5)zTq!j1;pXcJE7u=O+a6f)QABDj zU;cfDSJ|rrt1A~zu$(Y6`@^*8leYVKogZpyt;)JHhhgEuh4WIEWGg8t@qvzCu3qBn zB)s6wwY9fbcu&{+x^m5$lH(jFQZ{YmYkw?#Jv~q)p3SbHcAjxM-|>EVbxX@i-vX7P&3%Fub1#m_u#Rz0s=RR8}UcTiB! zmC5Stesw{{B{5|L92PM9?Wni!GYVdD%c$ zDa`9ymiywuLjBx3J3d~Hulu?5|AmFl+Z#iyZf0#25-G7fw`tk3Ws7Qme>3jt>Y6sk zs&tiaQqrURRpOv~FNAl_wJM$UyuiXv-1VcRir9_pbFOi5_l{1}jrL>MHp^!j$JVIW zBfK4-`D82t>W<5n*K}?X+~XuD;#z2ZiOomGIISo5=BCtjzG*_&SM2z){>c8-5*Oaj zw8;w-_`CVyk^lPxn(E*F$iG&TQnqhht6N?4_CIbv7QeOqc;2kMvtZ|rp6u|**x0j= z`|TYQ5)_2hd=lQ=I3BU_k-D1yyh9C)=UR{S>WBS1XgFz=uztiuT8ZwO_apaOlt> z4o*%-z3IstBQmnGb~R0QSbTB9D~3(9qN~PT1JBW$#&j z(Dh8RPsB|HMAsb+$X~kZn$LpyT!MnNbLXy#`q2F^*|DFiwW+CzDaFWAFfdTYww$Nf zS=XqQ>y4PpMaP5;6U9JXP5pDnL&95?^VzHGJr9@l{*&Qrx4vm=!M5bZg?t?YzV^f4SG{}p<&Q!7IhpIX6Fsc2+_kOxaw94>_O6@SWYyjK{ydrN zANBm{)7o2Gv#(19ie%gBnu&^vt_aa8Dk_?A`ssQ5UoRFjOqn|Mrn>K}l)pcp&o^ym z=YKXmzRq!Bz=EKaO3_=hw(eQ5V1a(n(gUl{-b(f0s^@69n^&)+ruJ;(wYAZ^rB9?7 z>4=?v(a_LvF)%PNE#2C!U+(SViy8OkSQe`(L~cntJ1h0)kH`H_&(1QPJm02rQbPj+ zgR76v8Fh#CJv}}7?R>Jk6m)gpe%Vp@_#S)B<;#}~k9J?Za;4?kwQG88QCd?^b+0pw z&=GrfX6E68&FrAFS~KqNvt=mSxo7F&hY<#;r>2OU|2faTe&63-^LrEa|9I4WW|k>; z!;2R$cKrMMJOAd^?CX<1KRUS!T(-}@m9;e}D(VpE zGQNe*?G@kemj8UUTL0#jOyStwWsmjiE2qfWR$bbZ>K(PEx5c1i9&g-;FyG!FawX$Xj@ji47aDHn9Lw68WHfU_)Y=_4=uUVnGD>aU** zo!c+oxbb42c>CeT?c2@QMsHVn9AVjJ#cp@6=*^-7Kj&D<@J(#F_V0<9)X9_Pi*g^k zM{Jm6eAD;I^use8*XGSK4O|%#6d!+nrg1taAK#+ubN}9$uurzu(=2hVRid$XUc;tC zIbTATnn>|h{ALrHX3}$y@$AHzSr(h?nMIen=KV@&cy@O7;_I&kSr}WLEQ+_kBX?>^e(qjorL zv*w#J>(9^5zMeC2VxgLDbX^6<4Ro)3$c71=e5DAbzj*K5kn?wBw^K(6wMU^wgXssru< zEJ?}9o<2SIZcaPMN1?E1r^!v=;+nrfD)2N0d)?k?y3wCrt==qQRidFAwPnGwWzUvH z{?22Yz^PRrt=MQBwQ-)BpQE*BZT+E?TsxiJ6`6!eV#+T_rDzT)XC-O*?%4 ze1Bv4*GHTE`8RxDF;TGjuZGUXhf6043#~YoRxBX)sMVQ8P;jl^u`b`M9S81~e=WKH z`)rTvrb!_emU^eN@yPJ3Klkj-Zna|RW;EP>)y%Oe%LV~LnLZGgUed`weNOz ze_9iJCFfV;^ZB)H*1x}{pPY0wBs@GgI{J0k3K_oRjc?w(`4=9)d)cx!UTHI)`nOxJ z-^LXL=-!Bli68x?Yf*yH>2@_07%8e;?mn z@=_@M{5;kTw*P)Se#fxu&YhTPI+2fl{rH=^{chQw;^*f!GuAgOSfG%2XGdZ9m;CLw zGjnpDJUKaeo%7k9UW@`P2lcv=Lenx9_%Y za^?HI-x*%q`dakXY-!NR!~FJdZtndZd2d^;v~Kh^6;so%#ScC=GP9qW;PZ^nhDoa z&xb?PJ*Y1HJL-W>qR;7Rx@+fJm**W>*<&ecI3dX_W=5a!v4^YI?-TN$ zYqj))MV)Q-oBbl&MH-@iOqet0jCg#_$sS4LlP69j z+}~H5k~S~p`8nBhIt-k8_KUY~H~;wYqs^BKhj}C}%wMwP+mGMBgTuq!D=UA_ciO-D z`s>X5cAf2VRUJ=HPY0a`a%)Rw^W@17+dha~`_Hj1^vg5(*3-Egl8$oeh;g5{|8HYw zXLah7UiI-eW!7c8d()5EpS#|DxuPhF^W*lFnU{ln8|L#RH#1MZ+32P_LFRpPl=6m} zx%2qNq+i~y*KmF(`A53!;A_Xl7X#wr+T!B9H>aI_ac8G-e_vh4`)Rt*gWS~fUjH=E zTFYNntXwwRm5VE6Rp5%PQ(`O*we_nn$XN4q;l=(7Ypcz7mA*c9V`G2eiwlb9f9_k? zU-9C?!tAg$5gSd-&CN@0ZOOD`@O#MrV439hsI^O$FAt86=KdhN;o#T1#pi9)>pq=S zzwfka?xAyYtz)?+C>k1W+_`%0=|@c>1%ER9{P?oAR`tDpa9V%=nNLqo$G$Ua_gij! zx9<1bTbC{cZQsPbqx?#^qP)kn(@%Y77%(mkI@u|#&b7>Uw%C^3+h#R2H8Ttnnbysc z`~3X;@)avM?CtFj9Xj-?ut-%WcGr~1&1pMdYpw`c)z;F&vi{evs>rl7wdMZv)zsA7 z*ft~|=X-W;u69**HRy!l?OPu_aP+jVd@|8pDk&++$+0T!`}^sSHcy^3i7S8q-!k*e zJAJa&D^{&Kboz8`-qEea;)@q8vh(%zeajT_P)30p5%G=*yi(gr&oop1|34EzyFUBSL-9L z%b({r)~CH%V0?G_+UV_*=FB;>z_Iz_Yu^2Vvv}I9l9H2uzB=vy^Yr56mwyBF*4xP+ zd0<`J$rCIjwPe9J(3uAxeW$+tUq4^P_qNI1=4}p=txl<@*a8_Pl}__EH7#P|s&kbS z6%l2WkqIj`b7OYQsn{$f9ltq!3w{q}b2!{c%%6~8Nf zKIciy_1f6^uZy2rTTDwxd-_a9?YyC&NYvby!N(sjyq+9n!f<6uTYrDQ(6w(4wmp|$ zx-7puX~v9>%bFd1(`NhiNg4M&{^)MGBSj>0y1($3wXUOP8IS;n>{vee&G7ho@?Xdw6(goW8khVR{S~Xm_IG(p6O=J5uD=$L?M>?Kk6@ z2X05XjnmJi9P5$1eD(S9zLQdkp{qhX4J3Hh-S}~^d0p%`&F9aSeL9kMcGkv%hfaI; z?AaE&I!t$a*xIPG_iMl3)k;3r6It-(#l^iR5?=+U*0PE4_3sw^`(klF6Tht0lYFz; zAHRN8RdKoYZd26SvZYn-tHRdax^-%*cD0i6^Of=Y?dxBqh?_0Q)|pRQc3D9p)OvtPyY;3-4P z!bdEOjEs*ypSRD?H;{;NtNQur>Hc4D%U)gS1oh^2xcAHPD}{$M8dQJF>FVxY8NYvD z*y<-1455}+Kudg=DThlIyuD?5rG#&T_s`#~n`%qzemra!5f#mxpnc)quA`NIqd%-K zW@r&GJdvvRb>d;;Ch7=kMQGpZ|%6+YBo{2rToR?Ut6N78x12PWtcs zy9Mr(R6c$BlyPkhYco54;;So~Z|?1#y)F0ls?~?;ZMH<|My;JDsvX8)wD4?#LBZc& zU*o5~GM=H3>yCL`7M~=Yk!8r*GzEXgU7r(StqO#IaB)fo5|zjwJuk) zuHHTUo`ZwKg+u-JwtV%x>w11FbB-o$UN})@k&@DjTN{}b8yjtY=lFw8>kSJtQ&d!3 z6SKGR;$pjmi<44SWFz=yF&MBk+~wM)v!Lac>8ej3)xBTd`&+nalhMa--;TX3d6Xs| zV)0{Z)BRk<77nJEkDs{3^%6GUbWpn!tFtv%U*xL)0nv4!IjG*o#zx1b@$6;H9+Ogp zoqE{S7;JvO*_`!aA6J0y&)K44V%u0|%4~0n{=Y1L)m8iIZ#u89t>wO$VUm)PvZLq^ z!~EKBGtF#mW0^R(xutbHgG{=ZFYG8xF8vzIxO3GCwg)~wJ~0I^E-cJ?cl^hv)A~Pu zyZJbnnBrZ)tyjeU<2S(+XR&!t10p=t#+zzrMbn6t{kR z{@q=sE0d4+T~t^eUG?R~!u{{}{bpNzdtJ=VpsN3WzpH}oM%~#rbJi@-O;U_obfdO( zxUO7eUG${m)(>s@AD$c+8JE7PdVX$h-~0Xl<4Oy{w?3G^y#8Q?Z40G?b-cj8m00EEV!8Q%VO@i*Y)?`JSlSf zZnW)_6Q84XUe&wj&)wHXZM7(QA+U4j&UJo`S|5FNvnLlna}s=5_$OHSM8u^kN=n)O-waW2F=iuQTMuTj<$I>Y?oAQ=Zkl!GMESjE|Jry@o<6^P!-feL zE(9z)|EEiRe%-WK>$*p8UJ8q7w;bS_xHjY7C&yb`-#e6)m{?lgT-U`jH7AlQYu>Nz z3%E9I^gVs@H6_$*VaV7VvXNi`ubXm=cGxk0(&oAzHEQ- zWr^vOkZ2Lm&1n`@Usj|Y=@9e|4hkwcJx$m8*5>r{Q4Rhc9vq+>wQOzYZZL?vQF_-Z za+a!^n%nig)#cMKPn|t`wGgXnL-2Ax!^}%6XPa2L88r0t=9T~W@UY*y?2Uz?zW)24 z4Gj#SE%nnC|9?C#FKJ)5r;2yd?XB76j2)s*5}*TRb+oh&-M($T-l>&~wPD+e6&gvP zIV1HQrka|b>^|&~AM;;aSokVp<$)#9b1v`a{V&Dt!0}Nv&$qQt#`4fC(`*h-&WEky zaS8?o6TGMEWrFTW7MHLoeRYM;xt;H9@RU_sPI!epjXSCJD#TnfPjmfi#^-O3{n~px zLdtW}9H$QP7J*}q&1}B0dn!IMpP01tJ7U2G%&CzD|Gqgz<_`QPft%jdHVF>hXoIwK6Q0* zVOh9v;g4UxflWx_I#NWo3A0d%3U^2NM&M zhqw3R)B5|D1TFRIk+WrMKm70#s|NR^OFA0H#*-Tw7=C@Np2x@>w=c7GZJ5>!AGPgX zOrTLS$(Jun>OOJm&9(FWeoIA8+bi}x4&YWp3sOIhEm8TT1^>XqjL!ta* zl?yKF@Bb6DIevfL?Wv)`Y9)Jl>psKJ4wq#m8qodGcfzm-Btj6-v1tleXl% zzP5IDT>am#=bfCL<1b~HSgpPaD#osIOy@fhyD`bN|G0epo#o-{d8Yu2kD&h31u_xIJhxVZ&|gdEwJeEd%5qWjG2`(-RI z*%duGAuj3o(nW#K_Q*;k7)Tg(b$2sYe|xht;qo%yw+wUq zo*T|S`|Z!F(A7syOjPbyQBiSWDtUD!bN2lC_oYuHxjgFD-?!oD@#CwzFK+*lU6NQQ z{I9Zn;(bO{lWi`w`*Lm?{rdLybx`2y=u9%ldGwBghu_vlZ%^Bjd%Ns)3#TyGEc5(-JexMnSa0w+o*9?q&V?Z&$Z;l$PTOu1!;?ZrrAE+nv!;Slw@pb~?r^w&!r4q+;5I&1E6H{?3J3@H>(F?HhIRJH2VuWGlM zi!Jr^&fQoSyIbEeuRCscS#JNGJ26|sec1ms{+Qp-pfT&(I{gDjyTw6Ql3)FBr*gsn zmBGuut%=&Ys>;pn*y97~w$Z`uTlF_+y}7(mipOC|<1`-zJubzxg(n=FwEnJQjf~s$9&`Zp+pDXuv!76WYd)cV zf!47V|4ZK7_&ELB+uPX_RgDTQWY$gC(&RTuT(kKUg5w<&D((^m1g0=?K>B|-%?Pftx<+tk#gru{fD zCZ;Fu{i@~f3twDVn0|Xp=I1j@JSUrg?pp|MIJM3G<@4v^Yv%N~GhDiKX&&pkpTB=E zXZ-K@^Wd7ey}y)-EH16~o~~EC|L?ck50-jQ2hFmc{yAgDjJ+oo2KlH>ejRmB>&lu< zPrMJuvW1I1&g_YMf3T66UBci&Xx*=u>d(&1bdHFS*tl_{1P|MmC|$GJy=&I!gsqP| z`s(WH51&7$pO~OnUczvSdu#988HUNA1N5ZLauh5qX7I^c1w=&r=r3dndCYGVsB36A zQPMc=($?(j_ESwVO{9KS9zXwQLkVYXR>-5DR;HfDqKU;;6C2K)G08ELo+zpv6p^!h z(Zv&A-o1UfF-toB%$%cZ^!FV&e0cGlr7c={E(~5V=cm5E`hMSs&dyG?DQ@vk4;Y@} zyLeo?|I-8Uryr{)a@DN0({ydS(;2<}(Cr5Td8eATJMewDAHCPI=Eny$jZL50`D8Ec z4*h(FN$8Ye`|AU5igLC;6kESO;D6@zb+PIRcP=h=SB|fKyLI}MjEdBHN zgZ+C3h3dmwuc~v4>xJ~z3x3kFl6&xW`~7`AYm#eYI6fK$Y$| zvt~u`azDL)jW=`N@tZ{NDtVbV<;#(s#m~F$S3Z|@uc_JdeM{!$ia#HZGp-0+e9YS6 zx0>&)nubey+fAlO-PG**bgiGAPsXF-+068NQ&djw$hm10wIyR=-t{ACo8L0pEqEjE z&$Q{`8ny|uXaAP^@PwJ)rhx0{kNFmbPJ6yw^6qUcwXOP6@V4T?0mjV7$9ju*mA`*C z>D-w!XP)f+eos4UciGzK9fHaxhue6W71Y$0ZN8afx25dut+(&@{mx^2ATA`-bTMPj z^%a52Ha2-0JAeNAwdi8TzV%UCR~v><3G_^DjE|c-U`gi-y92Xat1s^{s9FB20{2?HU z^|#>mY;#G;nJg?n{^zkT;Nf%lc}wWt;k!qi9<&Q8yVY!{`1t5u*SQu^OQuiLohB{O zJs{@qfBv1>?6-AKRva^$eYR|MSJ!^o|7`3JmVKKUy*)4Vf2_;Rdj)sG<7-8~Kb;=W zcI)jo{&^LTIGxuA4cVKJM z$nF#=I{lZeIZ?99&+GuZe9eJ#bFB>}c-BRI{rvp=^O(I=U!Cpk_q*>df1kG|boDd? zsb2H5_meihm=G*C9++FxH36%-CEt5E|j2w-Gn+&FXQ%&QY5H`Rr$o;qQI zfZeYb%HK6P<>loyO-!aZHnU}hHXG*NG6@O}zB<8a|6J?xd-g406|}Xvcki9C)O$M9rj3z%DnM(d)og6$ zgsqJdl#=RlTYT_xu;2V?2f6fx{gzLjFhPKciRs-7mJAcAPoFmjP#jrH}}X9mwWZ~Du>(ok0)&mh>7{L+)cyA#>S%d zq0&BwwGSM)y7Qkr(G}yqckkYduT>y(IXMqD9`IiNv~h*Uw|P5U93AZx6%TH=_mG^R z(z!^9TUB*`k<7nB%X{~kRKKoRW>BQ!S@rk*9RErM&q-M^`=1^A`_E>3#x6F6mKA+6 zp;r~9Uv5|R^x7F+yy<4n?1`7<{2IE&^~<{Y`ogA8_nD-!w`hCcSND(k1q@7g=kJ)x zHg)FzAiaO)4yW~EcS(pezjHCWCADLr%G3An^Vh}g-PN72srq{!gIwK@hjUJx@Ze9` zoB!z1BLLXpn(yM~&cViU6N@7sIv z@7BV{$5#4(XRMxA^XX(({+^G=k{2#q$j-~we7;B0xX$$dj~^9RH!LZB&6m1Pza}I^ zWc~iS$J_UnybSVOS-v#?{yyzLfBwjY-oL#iQ~2S-hgVhv9^P8}_t)2-zp}$kE;#>< z`N?%TiX(8&{Q3D`zJCW@1Z`LQ3v|(7!=cIQ{!!W2*U9E=jaoX-wtAP6lGrleF1=n8}TFo`@{COu=L{zl!SK*sH6nLEW^!zrHGKXq=eJ z^mFxtw{Lr=PZwYAH}}!){QaQgcnp(|8I-&*SaWUB{v}b4lMW5teliKQTapE z+G$NqOw(5-Iyy2YCMHg?kg3QpagnG!kXY_ouxBr?h`4xgP|@=#T~hDPpE^|*TfSZ7 zfc-5-0jE3tbK<_R6zRq8GTGwREA{Ss`@<8Oo(%J9zul}oJzf92v!Cdlug7wnI-WNi zXKX*2V)f+pYwM@{Jlg{+r?B@k)H?L7;*+!a(G<4Y_E~|&qrjl9dI9s+r;8RZzVD=2 z&r7;;&tw)_x` z>|CjJU}y1j3Dc}8#yUDazy5qa9~;9EdTnF={e8bytkAe_8I-Cp+axpT%F7a=6MXH9 z_DsFL(7C;)^vz=T{-B_sqR9(={Qc7pG%#Ao@O}F9NoDm_EzQ;W(+ewKERp{htqq%cd9|=N8xd@bS33b!~0!Qc2^qKUx1y$bDR!bVNO{MIW?Zc6w2`pI=|V z97fI?RtX0h<~rz4nEBGZPiCj*rOSESKR=tDU-a~pXjfO4hK|maii(PWokgia!opW) zFHzIi*FS#${{7`ePfuxRGCkMd|EH+-)|N~=&~BL*U#tFop8tPE=@HN&Yma_?ef{cg z#gw8vqds~2cS=9MzPy~i_+&~~cX#)1kqb8eYvkuuy^`$e>Z()aep6z#F={Q>iMDXwL@5_5C zi)(6XG<0K@I@BY>$@$WzO9}+&`tx#O}>gwup&dPJq6E2Gv`>I|3c6D{Q{ocaI$D~eA*Pq|- z^Xc;aq9gWg{};Fvgs+RaDRRPz?{5C@$$iTYl`>lwKjYb)e*RxT<*li^A2Uoj_G!_g zMakLM*1X(S`1qI=x0p^qxVP!f=L~-m@3<&Qnr4ZdJb98w!eGJ6)B5{mde`i}Q+cR` zQ#Uv`__p7CyS=M+<^KQo_vpR7)l1Fq*8~Ukrk9m%%c>}z(3>tg!87fY*ZoT?0+pwK zIP&rF@r@BWEUc`V)@5&A%sT$>?{CJKyN4M5{Qaw`rImDhTkg+^6DJlbDk@%8QdWLn z|7PRyF3`l6iIGvzm6gHIS*L6{@bGZ^)!2R}4n?^`YwtdPy?(#mw*32gyb=ZnO#dG` z!h+vT_XHCITOR4u~|(9-_~ zZgIVq%&k#1H8n9Vn|gY9j(>T1Ih6a;;$N1J9z8Pam$&~nb#L`|yXgIOe^2|{|NU}7 z(YY;TTZGQ!xqj;V_Wj$E|DS`e{qwS`8&=tsuNH8u4ljVM1=zb=Y+ufrQVSWsvNtzC zCj*OWwQR2Vs3c{U<55y#^77?Ny~@OL=j9hOVw5c22{@&#VP|0}`1hxBUhOx@O`A7^ z>d=3GSXo$Drp)f0H+y#X`t|yckM%B&+?=-O=d=HY)!%aXWUXADpPL(dHb_UT+k5f9 zey7D3A3S^rTKEyRdg@&3@=aM+wKi?u{PNDvK)YWrlBLY^eB9iQ#e4abSjf1zwrrO6 z&@>3VdB5YNqQOj`C(oY?i;A}H-*121f6jq9^Zlnf*w^ggo$BGkD*E?CW|{rrfzFc=_d%?AK49cJAMA@3uHlw^-!n&70lt4jrGwFmd90Jd> z=llKo|0ktPv%ZwyFZ}f6(IuXGEm`%RcUx!G^IclPylb9tY3Pna~x$;*rD z-@kuOp3)5FDdx#ZNhR-iHLqO#{qOJZ=}Pq%-+%w~De3jKwJItq0@Bj2U!}0;NZQr> zSa7UYnxB`Gv(WHypN!=rDZii9lMh_nQTSNps=x}1(pOVnm53-$-*<;A-E04b{QGwM ze!bFWFp)a@eC5iO&tG3#`&uj8s`S;AU*-3Iet!NrIVovZmWhEyDm^?-Fuyx~Yh!Zz^>cHr_bMwZ*N1j~y#N1S`0^_jw+mj+ zdiwC;LzaEa`)+Sa<(^;nOVeRP+#>3Wpno+>% z!Ta~;Z){Ayxha+Vc;C^{z6%*9Gknx?%%nk=_4v**|ZCWtv{`W6mTzuE3q^536KQ9-yKF-(C zkc zkM&;Ley?hEWJE;7^Y8bn^K~{{e^@Z*-IbNW-MZ1+X0!-QJfV4#qrr2s8gKp2r_;}$ zoniP``N^c0mjs+TdhhNoug|=@t90gxpWkPz6t?q9tMN*k^|Z9GbWfi?{oC#Q{j~-P z3J;V;T=xbwuf4M|`FM2h?QOn#f8$oZm13A&7;T~`oi1LP{M;{c*Z#V{zdru{_O@Dh zCvTB&-m#~9_Uz$gWMsTACL&V7SbgVP{hNpF@?qXP_OtwA1a-Co&d;-r?w2xMWw>Fr z*Ho=bx3*??i|fbziJ5O#%jY}K#9?RWF$r*6MJKWygDy1KeJhL0aV zCY_w5%H}!g-p;bOQk~ZVzx3`uu zcJ%gs4ZQij;l}3lewGVMy{GFuxfQYYX3n%nk5k*PCvDVW_v28U<}v9?h~#1Uo$KbW z4qtDSduvPD%a<>2@=BYP$m-kL#y)<=utL>-_XOE_?vB^KgdO#|@@cnj^tKhhRf>Ms zUDiC2XfMyO`6g&R!j;|Sc}u;f-m|X#RT7%_N8-r3*xf1&lTVf?+S=Ae`1qXBS$_Fu z^x}&Gva)NLt(s+ABlp$py#MX(?emOvTeGg-dh_z-ORoNDQ>K7U;pTMU2-IlVu;_~f zXs+~r&F9`-FS+t!xz{S~-?77j`-85I&Ys0G1(`p7{FpFh%8@o+X-%z7cTzg`?%n(0 z!-s&c`(xh}m76+ze|vphtan(LTH0pG<-vKU9=}|vZeue?(l~9x%$b}Hhue4~cNVEm z)pq}@_ISNh>caT_b#o6KP*C%k;m|Kr)2miG45 zd#}a%P7ZN*H@)>LJuDw zqD6}?EnQL1c_z(RR=%?3#f{%HDqo1$CS?l=KWS&P?{SkqH`ls9Z1uv(oLoLj+laCs zCe{5@bPLLVeaXBd^5qokcfH<8DxOlkX>l)eZES5HgBIN$Q+Dgo@UQG(`0)LEesj*B z%jP*Z9?YIRIXUC{I$2TeFcyx9Dlad8`0ybtGcz+keouuV+sp#B?@x4fR1_5*V?{DR zD+B(6_5x0xtnUA9RovcPuN_uBDR6RXdjH^H^Y;2bACF(XQhVvrB_8dtH8U=(i`|{2 z;pbdcRW(=Ex=emo+stdne4DcQkdLN2E}laMxs}q9`*hEiYjqp+~1~YxXuDd2w;^ z?1cgf#l^)(kM&A}u8s@YUsv1kDlRT=vv&BpJD{P!dDVY@eC$3kQEZxC>@U%RPwnz` zKLod5dga*6)|z{7Pi1hgl<6Y-pHGCp-m37OXY|8LST4suHMeDh!NC}0M%jJl8^Vbw6!s9VR^JA>a99od$Q4f=Z;tP_ZS%_ zsAvjTJlp&I-s{p=S5}_4v9;B1lBv7Dw|e^$v%?#1=6LVjvnTHU*6i@^%*)GkPnjiy&%g!%1Cw249ZGCdKyS~pd&AzvVR z<`n13KR*hKii(sNudR#yo!l*^du(mg)>oib=e}oRVs8!^gVEP&>gOq4v&kUpEH#)NH}n5%NCRG^RDHqsH%!e_qufp zJX%=#`daAEFPHtrcUY=Cm#=!Uux5e({<^6?**ZW;v6>2EKlXGLkywca#b{!LN5|ENwHQ`v^i)-Sd zq7UD{f1l;|rF!8s&RJ*Eq)fA1Hm9AHw5i~DeQj;(-CbXE?(CS@D{a2$YSxN4+gYnZ zR|hS0Vr^<_;^5#oaQk-jTg&%cv(r;jCQO`o@aE?9*d<K|@%W2tYo$fC zT0nblqN1eymVaKSA^%SSu6ea`T9qz^Kvu84Cgrb=X3B+&b%R| z`%v|0$cAW#NiTjo#d~>qfsX3AdDCbnXllIV<)x`-&fFIEw6%HCaY8)4=HNu-_Sjv* zlT<8AtnPk4u{zds{q^4wYp=DJSk1kj%dR8M-tY}ny4F58(5TIL*!+IY;nnN+ah+h> zykWzK&Mr}HI}s_VSFK4JeY{r|nnc6M>QtG>SS+PHCJ-^a(t zvmMX<-=1@G(wjFq6L|kHzq_+I_x84llO|ny`{(Cp4n>(sDw$K;A~rfYI?896NKIcD z`0NUkh7RahyXn)*mli)iC%isx@2@MM^>2GBKgaOaG&MCzJ$d?6H*!Zo;`H+SwdPVg zeP&P94*vvNORV4-^kS-ZxYN~-`+vW)K7OE)x%KeFgo}$@zr4FE-4L}kE7X167cRGn z_jFs|2lUhz6&ZmRXIhuPd$M}{zN7~S8XIIT?kRaGw72^EHPC%ETn~PIecktbe*L|l zm;LQuN}1)HI5*e&6TB#Bg(SuU4#=w^`J%!fUElsl!9B zGxKbtbMEcg$rZ4)^!2p`cYHu|G<|Zmx6)o*Sm@%iW`0(?v#_0TQ%jGNy<~ry9=G$!`N9^WSsO&XZGZ*_)=L}dB38v@~PH&(^JZ(hK3in zHrZBuc%Xi2W3_^I>dMc%`Ti9B`SDSHSINsq4q>JxO4?PbirXKsJn3NH^e958Xy*Kd z3l=E+{`2#*^u*%SsS`fCe9kXlr{XDe!18IJ=a0*}Qm$(h`^#zd@tP4e4DnQ8w6 zWz+BNspOs*%&V{P{94GN0JBYNUPLjh4V&GiBWEOJIp?WE*xgymowIq6bpWN`c8!pZv%ynXQW>CvOz;-KvkrrFn?tXw{C*UGgUq;82j3CPI! zczAF`Z_j&rX{q-sOBEB7DPe1)K7PBM9~=swY$sT*Il`O{kpQYw)gS1CwC~^ zXWw!s<3KQH2dG;A{rt(37B)7vMOU^nZ+d7n`|O^N$D|KxRbEdq`uM0@f7RJ6pWol! z{@xI_HtP3;_r>qcb8l^Nv1Hp_{ywaCzwC=^%Y3D8H}l)=kaA^qc`6sV_8PNTx9X|m z$JGyAzdn7IX|_?u1qBt)PYe-zDmLEOS-hNO%9nR{e|P_WzyH3L=cN}rcI?Qzx+-+> zk|jruwg*=8F=Uv`V&6IObJ=s9wg2C2KF{ao>bkS#{Jg4Hnz`@q?TrmI7tOmfOzZSd0!O)b$HR^AkUN7p`o0633ubn)(=74J8?(FH=2Wy#rI!!U< zJ0SS9(E81zbJ{Ibyvw3^d3%zwXyc?f>^y1SZ@4d?GAY`z0_aIM{IZ z+49`4YbHq)7Zt@cxv!13_xSxyw!muck|j$fOq&K;yZQ6y&lx^y=DO7%zI*}2^nwKn zPR`CNLskkMJa}-*lqm^Yqc%pR%KqDLb-v#EPKL^qlbg@mI3H%?o%!?Zbs3AQ9@mvC zpTwMH*ev; z>P<;W*|kVHuWg0E%-6+ypurDSrw)c*x5c}Qo_4WsFK2LwmAuXyUqw6wHcJn4Ubebv{G z+OmRq(+7!*7ca)_sn~eo_U+rNla6-DDNHxf-lMVkZXUn8;-i~y%ftoGU%p)Idw*YT ztXx)DnAr7ov4@WzXZM`6A!_ZCB}*O@SVU|-7Wy=H+V=Na(dSN_Ncj2bDZ`gHH;Zdm zhpm0bob<5Gab588zU<>IEi9Y?r>CDhcTUaPT6*)%HRs}sA0A@8zP0AZ!}h?rj17s0 z+g8Tve{ScI+@z$SaNz3vwAjk?zyk8uTS0@1v)@UU7h{O(c5#qCwV=*|AOJp;s5m~m`|KPuWW5S z`^1S8MQ@i}%!pw#R#Ix}>EZEFo4i^kpfzqiXxirVbp6X~qse8lZQvez+i%!?<~-bmUr|1Uv_$)b#n35 ztWrzmfB*iqw6!g|{`%s}5---~-*$(Nwr{-sHe&6y*&(Cw>@il>~LRL=tKGQf| zW$N?ALOoY6Se>|U&icEtluLU5A{Ecu;(9R^QmRJJ3oK&zT;AT=T0YOVdfJ8!8;%E> zxJ;TfDd2MP#*G_y*VfcHHTKEL$$fcw`MBx*i;La8D=RA}g{?NN{qym-TFNt<%*@P1 zvAfHZ6crC<|NZqfs&)H;mBGue+;?*d2$;am_5AsroyA%=Z~i-H{a%KPlQVO<+>!+g z8m8$+CvCp@<^*3@;9RTHKiOASB!=u}4Em;C`!?la4TGo>y>@xWz&d)a9dCM*a%akaB-cdhgXHHFf*x2|pl-5Pduj%Be* z!;Bd-J~FfMcsM?5oj7sg!I{SCpyPxJ+M~4|{N1s`Lb0WS$D(g`#cUPNFK=&84_@wP zIdSo_XB##cIPWfd%VeHc;weApq1*qKqQ~=?3;6%Di8RD0O`kT+t(lGYPX3wgt#|X> zS6|(g$5Qhd*kJJI@86wOU$s`HYzbR^ zG{tD*j2Rx&^wKgo>Xyz$Z!Iw{;hRPl_U{X<6&wh1f<>4KLkGEuL+)CQGf-MTvn_*;Y09_yX za{2tAh=>*PwpCvw5-L1BPdX+1UgkUdSLwSuH$!DFY{?AP3SD(%dVF1FXrOWKihbqJ z&PaZ(E~p#umHUUpwk%xGkQzE%&yNu&{Dm#lzMqj~+d;xO#DA++Hi~@O3G7 zca=(R+_+J?rnXkP{Ozr)`<+_3Zf9LtVfg6MQtzod0~Me4F@2ryHC4+n`52F0+@2Tz zpG@}u=H^+fqN+M^(xj&4%hR7N^`5>3wCjwil6C#nEKNPVd$Q8fv!&(a{uNBui@mWa z^|W@s-LDs-Yr|&0fBpJ(ug;1myVj|ys`5x0xzx@o`S#|fXIxyIUgF}->8W z)zkZS;q_Oy27kMspquOkWo7@Kn4st!$08TMvnUnRM4mg#-2d5>S0z@@&dogyI`4W_ z=<02H1_m3PHm9APb!Bz9{z}(wF?|_}0tJSdAO1#12i`Acc=s^A(P_uY+xp@miPG(d z9rN<^d}o;`T3XJ0^eCyOrsl-y)5Vej%?BG)JZGFu+p}lShN!hKO02rNyEXOn)GRD+ zOnnzveYE`TEzqI`9Wn0A%uETMdoN0M?GBn<&u{MhG&AScmWiU;VHcMB%UhMaaLCGf z6&vnx=G-~Q+;@+jWuz4@UNim9otRz$rxh<}6g|=^veIUH@b~-u=YPN7-=Eq1!V&@ zTPw}5gT?1g$G++F>i<<{gQ~5gT{1=~9k2GL{rLH_*Y(*~h9ZmDvtM^R`&zrWxxF)y z>Rl%gdv#T4?9Lx`e}A>Uzqi-=GpOGpDJuHaz~TD3*q67qW=m|!h*UUmb8|XqTJP}* z&FPy&;-@peZ?37G65`5|C(0kyqO64;RO(fQ3wtkN30hT)jwa zrhdZjCzJjEG;EhP$uKB>abaP!##PVqUteCzSeNSLbXyzkbvC}qt zlg`hx-FtgW=H|ysr^jV6Us&QPJj*0=k^8#?FS=uL z%j}GeH>(;gfAi7k++o?m^Nc&%|5tJ-#`M}h;fo2{sl?#K;pEgL=#*eK`{aoe1)3*T zMtxcpxISnpS6yA*k)ua1F7+07a(3Q$`|X?i`{)1pQ)5;3rXtu<^TfQ}ck{x%UTs<# zu#kzZnX&z`x%I2>1?&>NZd_bk5_W%Lzf{>OyZ4nWHF+1d#Z`BD$gVw%=7kJgced;5 z>0RSc^iuJ>G-di^CC{LB0*ky03qKy=7S}7$(a=!P)8p%PbI!@p@mqd*?{&|(^Yd({ z+kCr`9DO$J^NQr-eR~BZB|H6=KmPRe^pT@S4XeMs*~sAP>Z;u69T^!ZtRKHG=IZ(L z{Y#dpi0MX|EL^c7L#jZ^;^Sd{`!CTO4_&?UZpWveW!{svSlHR^d-wA4^4IV0?7Tcx ziJ=X2+>Y53wd}g^bup4t=FWXv`RR$LkdV-h|Nnkp%rJTK?Afkx^|$PLjCPWelFXAP zP5SWl>(k})>n@$2Z(nblFvB?AZ+XAnuN9wWnP%_n?&;zA`1$ka4N+?wK|2CKM|I6M ze;=vJ6r9#>=Q*j5Tk+QOCr^6(yu7Loq!<-GIKbHBrrg-r=rQTYB3He$YM(DGbPoS> z|Nr0n3tL)R3dQweUc~OLDm9VnHMF*#4cb*FF8+LH_4jw@8BL_ldhqk}?-qD;`OKL! zY@mCAOiWF$-VMCXw}zjao7;Vw+O@T@yT7e$hZm9(~kM&BYOY*e2B_<}`c>4|1Rsfx_7q%|u z=A%zfPwP%l_Fq<1d(!J6q*jV?4SVy+X!g_lj+J@8%689v@j@zOg}}M5udiFy|Fhw1 zXWpt}q^8!kXOB(D(nDo$Z*8pmTP3D5;p*=kGiggp%Pm<~yB<792yb`z`lD?3UDrFF z{{GJ=y2~9rf8JlWcu)IomR>jKgaiR$b-xFPTDxohd_3;re*MSgD8b$F_U1uLj^2H! zqp2DAnP<@+-$e{6o>puntD?5Ps`m8sl((4c*Zk>P-IGasvra6$r^~Q!-@cm%{#RC2 z$vK_#zMsGO=9T2=MrZG7I*}IX=jJTgn0)+Lzoc>6Jh#P}>s$<0G1PoG$gZ1xb=A~6 z+w<=qzmmB?pwv|AEaw9MwNYE;Y|G!>NqXYND!XaJ1)BYsx{k?LwzaoBrdwcuc zjg84`{FYZAcv;f6t26Q`p!==of{r11Ubee-!R41%l-)D@l-DolJvGNn zWBzBZqWXrt^XAQ4?=>l8`;HwEuhl-+$o;Uiv-{U$^sgguv0G)oob501CswtMGMzG9 z30tF#RXksrrk|VB@$c_%b4S0c898Rrb2k0&n;uuS^6#IYpVeP|{UxWWsyg+~jlbcI zu8)uRi$6ZzZ+~HJ^ma38vz!?VuD{;Rp}1<-Rxv%dvMXuI+8qDV{usa5^Syw-o@eTW z{0XA$d@@IN6h7V<)%{N5SLoARc3!C?pPru1xUfL+c>ihbscOEn0s;aY%F4_@hv}>e z?Uu8xT5$cfW#uQ8lP6EIu&@Ly_mkBMTXSM%@bVuYkITm{aq4w*;;?+O>e^}js4X+{ z?(Uj8)qY?5eNNLa^78dP?ecXq)O=@cNIdLzWz&V-FBx8xSnb)n7kuuj>FJ|agDqJP z&+%JcZ7dzHus7}OEICo_FrNDxlaIf@ShJtsGAFhn=Idg2J@+!D>CdM<^)zz|>nUcK z`6r6U$4TwY%ASv}PaN}QTD5qF;p-IGS^$qbO2txM{~NA+FFml;=pMV}@pY#j&U1Ko zYPr|Xs411Zm3yS$-f1e=5eybME}^5V-QK zsRQ%&T%QxqH?G+>d*P`vzZCWd%U;XK$*uGB_Fmmpv(Dti^Q~JF{8>{Dn#vZ<{Azhh z?>V=VU-gTrkDn@8JMEYLuEg;DL%xIFC*Ms;Sy^0a!HGLRt;;(5|MmL)Z8vk0-ruWT z_uWTLc;UjtA3uH+Jo90?zW)7!%*)HJtc#t!%y;&sU8ULKopbKgCyGzki(Qm=cUOjq zRQP4Z<-rLizb-8G4v*TJRhs{`UR<#yAZ0ph{$5iNMV9$>za-rjPyF)b%kF)@u4kBt zh>1;_RJ56C?ItgswdeJ(SpH)?cU&@W+8e!R=Wa=qUpgs%;#^VW`uJUk_dPY-_4B~_ zrE$BXuC;N6tetpUvD5xq#!Hu#&&@+~rZB3TUkQKT;`206?b4-7bME}yvozR_t)6ew zxwEl{ZP_2l{o~!Ra0-)}y87hVv%QaYiN0S^uivs@!-fg$av8m0tM{&4_v_Ek&vUG{ zcD;C!(Z*ZdJ<~W{sU=2hd5JB{6OE>`Y07s0EWWLSGZMPCHUcWv)aIxK^6B8HrNSStt=|)YOH}Bi>Imv6oyyN2JPM$21 zPB)*|*?E7P(%$y!AMOv{U;MDAy?V!P4M)zIPoF#qh=_=I7o;ERcRg$Cr3H@64(ry} zy;#`Jc;U5e-m2~Uu1aiDStI7}ug}Al&%>6#XU`tcw#d_`yB8_#R6K9{xV~J$Q%F(q z;F|dTeVxMUmlis+->WIsU}gPjzO`xJ?10nO#YJ^nE6zCcdEY;5`^W#Gydf-{=zDrTG~^-yZ0wLOq)7oLg+R# zuSs8Kv#R}LQ)$w9l>}i=Y8C7U0^l0rMT>dPUVNi5oeNxtC+aQrwlDbo&@&Hs!;o`<#j{EJf-LwxC-Lp8fdoLuJ<4H1;CiPj{X86BhnT zIXlaAZ%s{2ozd*GW?oaR{vIyidva%Q%>MX)52mf+N;f+#BkwtBsY9uiY}DFwf8F>` znAbHQY&e^yJ@x+Ize>j61MD~yIXE~#$tgUpl6CXVBX@Rccl7nC>ApSB`FHgM70*Hg zZtwk5&#zxv_kZiXc}4oG`kiWi8;dD@|8V_*eDU@mlMVe0|5>9H*B@z~x?i`EA2jfH z^8Wq*j{oM!Pq#Lnw$QnKU+Tt)K85LRe}n^A3vT3W`}5{a4rtPT%FP_RCZ&o(@jz*f z6Q@pXQd3j=bf@^duj%Zw$0x2oX)o!i(EUM0E$-oVspQb(`@j5O`mn_6=~#v}g0H&Ru;yXGX=- z@T;m$9U&=YyR22}j@No|e4HVWH2HS&nYrJ8oi>!JG~9IcWl7!Dwb9!Xo^0Cnsr~Ld zr4|7>r;hd(fqsqt1^bib{&)Xyw_tGU@H_F!cb(b3$H)7hKb;<*c5;&Hy0_2H&AqK) zZ(r~J^wiY*n#X5EDYk^XGR;?XaaDAQO!}yMrT*Mp>*H5fhaYY@#c%iHf#>$W$}IvD zojUCA9P{M}bo{u)dw-lKhhWgJ(xa#OqBf;?UhnAO2wNBPvcG1ZpZ4#c+Miczv=~gg zcR%-!b*?!dNB!5U;p*=0?!h4;B782OMq_7QvWr%K!~GAHwJidHS=rBS=Ll=IaELqo zm|C90T6v=Me9Yb|*7n1N;bD&_l_+0%{q}9|{{8l0tEaMZixvF*lnPqX@$q9}>)*$Z z9vRF|6Dci?Wb08@Vc%wWT^Hxgm%;|?!yT>tUY>GcyPPIUD2JUM^Yukc`-@bz(; zuitLE@p=F8AFmrEPn^w>W0f7%?lWTg>9#^>nczl9&Z+G3u6Z%@|iYin;a>KI7% z?%yeSHr6ROL46a)`ETd#ey^GM>DPWUuHx+(8mF+9r8-W`K51rJs|j z`u=Wj`i~C}vp;|O#MLive{ONV-73qfFE0udUFHZRWv<+~j6;!+OYs|X^3-YDE#{t+ zbLx=ad9m|-hrpu-fkUw#hptu?-rrZd>Pq^dE>Z1GEduj76!{dk9}WF}U!x@>zVG~V z<(40g92?^+&xks$2|Z@1)e`Zfz(R{tF)rv?ZHVOllJ3+08BX8cmirsDEvBEHm33>~ zlZo!T^6%~nT|B2f`PIHt|6aEW`0sxEzegbHuI{$iFUwZkJkYf<`S`cj_xJy=d9d)? z+uP>1HY7T)5=d$hNSbccw(Gx@qWOk``LDBIl?EL9@AY@jfyCvPZ!W*`t$+Hg*;Rbo zmMmTRb>C&nunEmUwR71Ki^EBe{prV{;^)^_M=HZ zy|uJOYJbf3Vz}E^xvnQx=Fc~c1MMfA?B5-~5V7{!`ibu+wrwg45;Hq?+wywe?VlWq zd6CMi*DUMh5?`_A`|BO&3qL4mspj)`ohV{$?3H7MPp-QcyV#@b~&4h3&O6goDa5MkK4@j*F5je z4c|#iHkC{|{I;yxcx~A1>szz0|2>iA$1umPRtmHg-Lk2{W5LVEU%0V4LLbE(S{i_jnl93adB;$XIHz+(rfj( zP1~aP)l|yr>#twb$jn~FP|Nu0+FI|Dk`m4bcXt#f>uyS#w%YI9%aT>vZ(ccE=aaSi zlYVn)`q=yT70pheyUce+FYyBSA`F^AxT(CzKE%P*HKS<=ETSJALGdON6{$R}gr5HjV? zxzDezuJ)O0HB~!&UBK3?&^DgRW?AdHXP~XYRbMmOcqE%tJj-JzJo~S{L;8Q@=6xx5 zcKB!9-lhwx;Z!_l_$=H1Kj(d(_dP+t^<#pQ z(;@M*v(5b{ZqMeh;ossW)t|da;~vu^R>vL7t5|p^h;SOt^qFH@E!Obj#f$v1cXuMU zJGF8(PdH}5Un#%x0Gp%0oP{xgBC{EjC#)*}av*BS$31pI+TUaO+LPBPaQIGSX_)eR zhJM$hgR8^WpX=`GI_2%}e?4qfNT!}pJ|}~@x%s2Vj~A~E)8>^jIWhhCX)mj}>*m+w zKAkXUQ%s;pIph3OUTyz9{=5}rcwqU|H!DkPwt0S--9By|*Ra`HmzJE&U2uM>_w=^i zbvt8r79}_6Ha0eTPf3-p`>}9G!NWt>E-&|w|Mcauzjg2bJ3EUjU#(pJ;{JYnw&upk z>i$XFZ{OF`(Ye!>)9|*;`s2rsKYst-oO5$ilvJpR;_^z!A) zb45Ad-rhdH_sS%rC+h3gPkr6aZ~te4pt76A>l3Vw3g+hKFRq5iKLs5G+@JTZz+xBU z{rz9BMe9E@n$aU?yKCd&hZY|mb?fiz&-rt_Up~LHqhkX*3rocO+HaAkJB8J6Y)bWB z61ToTeDzho$M%JfT)b0Kp48qxdi;2}p04iSa|s4H4q+>eqQDadZF7%yPrtA|zh3=t{QB#c zw`PZjt%*3H(R}5s zKG#VL{?n&U-QqbCx3g#|cTmXNGEM%SR8sZxIVJ3kwTN zRhBPe{_t|9ar!rJ^%QYKrF6iL+j`Xg`1Q_O0)9z1SD`_U^v1xBC0H{Cz*4{kEF>PQ`#VBRVEV zM)!Vm*numTE?s&&apJ_cav%QxaDR|r%J4*H%jF0e+o}-Fokj{z57o7Rw!0j6Ej@ky zeE+)G-MidfTw0n;nq{oZ)(FVSo%`|e@!b2re?0E5e7$!2jqUmJ+F@&UeEIYFeEr9R z?D9Fx85b9^dZ^~#*;U%D9lp+>;DJMWf9~wFZTC;Q#zscReCON!oeElcDla9~wSWKq z;@vlH+>p=Ro_BXw?5A1-&F>FCJv}WVCU$9ay8p(!*p<;M?&gWd*ZoW_O-fqy?#0E$ z)84$vS$-(|XqPBsRNT&@r@|_kJ`8R>5(m3PwJj6JIb?QOZ)hUw?#{9PNh)l1I4?$2@i z9oNMfjEs$URGar7IKa@<+|0?vRrO$X&dp6NEiD2TA`lt=aDP_tkRn@RTfLoTeY|7!xCNd6{qWrza;@T+IS) z_*%|f$Luom?fR=(70>5xpJ`Rfm3zzP=&7lzKYaV9Xm9^tKX>Jtiy1pAKdY6$OR=1O z^2D=)tBUv(Bn8Uf-rD-)&(F_tM~@y=QBk=v(=hp1Et|W@(QOXm6Vw#e@BKFG!L2}j zC8tT(_<4DGZ|*K%uO7s{r%K^v9B9h^&DGW7yUX9Rfvzw;n0k8J-RJM$%lC^c1fA{u z@NoO*OWyhu=jdFVW%vKj=Uat^h3#4)D-2{6)YXsQTOF<++{z^?W1N1jM9yAEXHBo) za_g|oX=kTT{LRjA?&kj%PT`tWp{uX)U5MFLauPJi99&Uh;l(V|;5Aii?~<_9{neF~ zTTh%kS=luIK$oa?n~0cL-JI#ux2vkEriQqXS|NrNwwA^p` zYAH#{pSD6j{{+qu_`meW?qG(-6Kw47vNvtypWf~z;(GP%?d_q7iHThgZFxhs+XVye+p3>jm@NIY5+aRv3?Imw-eXUiQn>uOI zB!-HAmFd4^rh2*VulqZtKWj-?kA)0hpKS5A#q0hY?iO~vC_6pi$H8WH#uwMtM%V2u zdRo%Z@7=CnJ=-i-=Y;X!+uQRu=ij$$*u6FD>L-_ZvP+gOoj7$W>x9i!Ute9`TmAhV z!-ajd)wj0i^G}-ocTwE>`JlU}9T#7`vN1wuv5Ay#eM8yXTNl^GT0bvqZEel}{qyHm zelD)6Pe;Y$WBNsQCIA2TmtoI`L)_;kg4TmOIXN*gY|XkVb#H-VGv5hjr?PD-q8D}) zK3;Z$Kgi3g%g7_*)s>Zx9r$GB?CWH}6GZQJ3ak6g>F@1*%X?u{s<&&ezQD}l=pI__d;h|w^d2=0airi!NfOozg z9vZE!tUS_YJ-yQApt*I>=2{!uz5jw9^Iv-JxHPEn@iAU5Zr*dLo0?~|EqVV>;f;{% z#%*!`7jtYjHc@@PXl~8?$jxaRQ%{Rs|Jb%FbT!k31&+-3&2n$?xOiy?MMZf{*NgS= z@eyITwkC4(^6>R>JMY(ikNxd$_fti|!a|~9%^ICqX1TBa+|`TUSM&M7!Dd-kS6A8p zpXdMIp*?ZaoH;TJb8c>uJKV;rY-4k$$HZ)PO-;>*4<9~gYiULGc%S_E?CfmK;AK3Z zOUp0!rKF~=TotA7aq(}(23XpI&Q+!bN8nk&u;9m z|NklN?5v+(Utd3OZDbV0!_8g%?{IN(v1p!4_tVqU^=BHV_c2`BY@U0|M0FQz?e4ewm&bmff!lLg;9|E^ z(aTp&)9Cu!kbZrA{C}nIv;6NcWh%VCy?uSLK}Uc8^V#|P1R12va%S|JWQRqs4ddeG zW|Yy5-X>G=;=;n9KoMpZ#}n*g$BrDa@XqCvwPI;l|K3*U*RImn5|%|OqFNyba?FB5 zLR=~;EEX%s{d}%oU0J!enFO|C;o&k}FLp}x{TnGpxvS##{(5wJx;{UX zgmIdWfAhhD_!sq;m-*^5={J3;4p@Daqv=5Xzt59<57h}aDLi=k^rguwt`aL*X|px^ z^KY(;y>DFdBA~=-?&O|n^XG$33H$No@=q2vwvQ*(RNhxTkh=1zb|jey!>Uc``o3UlP5S`Vwj>G-j{f|%`#N;pk(ag9Xoc2 z>ODRQI`=X-dvarDeC=1!soLRrx7S8*k3Et6`qWfy(Z~7Dk9ceLW?WR#3SW0e-^!|L z=ceYUtywFdot?eB=8D#ReJ!m+2?n6^+X^0Py}hxqIqmGMyT`YJTKH2qXDX?%Ik>s8 zmF=D@rW@5#U0J!(v8$}+%SHDk%a%1QxxRm~d%qkb=z4Z<-`Qq=Zyi4DY@2+HXTl;) zt*14UCQaIK`z;GAt0?G%>T^?$EcKo)@;Ls-zu)iuGw<&DYLk)|UQny*fIgZlO9 z=0RJ6-9qK88R})9{JZ{qqIZmxNydRms@@UX^J1SI>y@7W@ynMQwoZ;6h3Dp2#vL$N za_{o-#)a#c9TO83_4ohT^vQ5x-QQo*jg|^aIo15;ocQwc@_Wz~xQtIvPya8{S@0<( zD=SNRZJ6|$nn1Ifq_VPY60)*unKWHNb0gp0-K|#9(#raOppn_y?&p)qyr6xa>5q@~ z&R3qgFsI^x>NVGW$E)_nabA>+-j);D^+#c@O{GxxQKyWI6?%Gl=YFba8oxD4JvHO+ z-@n@0r=PB1m~EEJm3wc`&-9-^f4*;PY1#7f`uh715)ZfW&ax~{Q}D1n-O$i*@a?Uw z(l+Jq?zmR%{Wtf*g$v7neSdEc%D(ICJ{)9kWSBQ^UdJMfQ-=>HixmAjn6}xncZ>IH z&sW#i_b*?rE~*uxk#&9DU700((RcRM&Q|rF_TcH$rBAdQtIv78zP8r(&!0b66nH;v zy8PtEj>6>0$9kppS?-CmEcAVUZ*M$k?M=J?3v1TtSc{4zP`G8{>IxfMRRlW55K;?mem#Oj$RwJwX?JHVA^KIuJ7F*TAR%$ zuN0Ymw#`fRZ=Vbw`yvirX|n@aTmP=uqGNC0zcKmvqj&EPzPh^l#f62;$;bPCPn2s= z;1CfLd-V40*)ubZK}$pqx9xR1+{PQRF^M&HciHk4CmaF;FD9!yv}s)b@%zV*O_iV3 zetmu2elq3J<9_?Z2L~EKLj^q_zB_X?HJnXTR#$)Se!9`xe)_GQrLV6o@|>)uyEbO$ zr}|5mE;(71yx8!x*ZkfME`_cnjSqdVzcI7%Bphg9{QlN?=E-QCX<|D{)_Hq+2Hul% zE<5HLc;&^{Un2JQ_JTJPb}bR!=d^BzkZa(HGiUZZr~;kJGI`!Szra92waK1KFTd>Q z=vc5}gFv9jR1ReY!KK{X+?@R7%kG~%e}1|Dd^^w@L<0$fnLb+b^72|CD<1e*n`B%# zV0momvSpK|JQ$pO&&NB7wl`>tm z=1s!;dwWmaHeRH$?6K3Ed1kp%*5&VxByCJEkYI5X=;-P?lw)RnzE|G7kYiwMseWBp(t*!F@GZZpkiin6POwK>j)YQa3fkSuS zRIO0Zq6vxKW9obM?ya48!eZNO^ZaAVSLZGN@W3FU_^EGNnp$gXYfD?(s^sM4xlawf zLKLj5qEtDYW^DTNm$S9X{PE#o+oVZC zscib^=30x}+uJWpKHkUB{P)kFU!XbOA02|qUydF*>nf-rcE(_)PwMKhwYyg5-QA^M`{5w_PKJjMAI_`V`>*Hl!xdsuQm3rn@97R( z{qy89hahtY!SLBh+pRa;Tmz~l+^v|79#u8Ty3(;@$F?Zu2t+XaM#4r#C7b7=qnzxnrSKI^La&2b3`5UASwPu;cspJsrCklE~B&&g^= z)!*3t^p0*mZ|Cmk_wD{I7tgg}!66|_3W}aw3V8ni&AIcn-_Oi6W-YSXsP3^jP5g`l z-$ZF1ws0nsix)3mS+75Jb9-CcqsNb(y}Y`P=9qDFeP15EJrA@(pry4{_1(?Q%ct$$ z{k!CWc76T-^{#pdz+e@qrbko>TR5IV#3qByu6#ax3|f**vyl&EMj3;eKpH@o3fpq zT}5^E=Gxz7$rTkpzHy0awYa;wn!dcXb#>;Q9fjT|Qof$;e6nmB&r5%O$yA**iFe7f zXV0#=D9JGyWL?oPw6lwoFi2?dGW-{|dg^3#f2JASTwI$P>a}lcx7nzvspVc;;(1e8 z-LK@|WY8kGhlkrge*SDNA}c$0@#U9R|9;>9|1D~J-d(-jck9?R4}HkCX83phLB6^z zM^iz7$kA)8tgM|~BHA9CH=drZZ(R8)<@gN4`{<_22@^h*-Me?ss{Qc8x~Hc^-TGuAWi8}{g)fU3SjqHFdV70&a6kY< zz^25*ZQmQje*XNqe9Grf-@fs&Wl8h2HSkKCb!<*QUnkx<;jzgDt|wNv)ESyDX6W4A zl*-L;@!~}}IXSsS8eOe!iv#a$PUmlske04yzRr+d;#V@}8 z`sCR&yAMmRzt+?;N-(%nEh;W;IMavU#4Ky8RN#@5EapMvJhU0nq_QtImJ@b6!~e6d*^ zwDO;6^|zeKe#>8PsQ#XpZrr8jJFDQ;70qe-@pgip@mFh}oS2yBu>Z&rm-N`jJ(9+t zql$lheVymtCv!3P))r6DwF*JGX18w~IB?*zp`qcqFSlnJUlQ1ze}CVPJ$v@-RR}%5 zCVG3{moFut1S1F1rW?C!ieBt48C_l7+Pzsxm96vrc6F~@L>GYXU~{;oYanXE;`X6s5~V$Rr%=^1_2gD))x^A zom&5QcXZ5H7qoKErp=p0#i~>e?0&z``sBlc8oS(ETYfxwlA`KgUi|ypTQNt0Ns}i{ zn)l}3-rrw3IywTSdfOg7N?I4Y+bu3`p4iGbv-#}p|8)--zrMEiGOxUyP2l?L?TjfY zDR27v``_CpAM5$Ga^=d~PhSi2$=O(Z|Mm6t?BCzsTI*O^{**nMxwGo)tN*O*?90Et zy88O>=jZ47nKH%p=G@$LBm4Tg(3p)$M?15wuBx<>;mfS@H*tgCHX zwrp84&$e3c(#sNASw8mb>*Jq)dwcuT`{!l`o4uxLod#_Qy}7ga`Npu-rBiIQzu6Z* z^SONBz=5W3Z*Q+Rl;Fu_G%_~6%FMd|XJ@UC-0{H}v|IUj zpR9yo5{qs1w?%$)t^D#1#Iw%zYd@RDe&n~ugnHI3QM!(fjwL{Ed(_$^$B#dc*;DZ`d%>lt+Tm5E+1J+eUCfYq z{`PIJ&$ev-psf7I$9ngj)Ak|=hsi2H!ttit*xv1Iwxi`n|RF$3(DT! z+7uk8Q&H);@A$vJzo$O``R&qD@BPkQ5;n|1J5xeJM7YItI+)u0XBZruHDkt)GC#j_ zM>eIN-ZfP>S}a9&+Mz>E-`?Czj@XtHX%m#PQ=Y#==l}Z0fj$n`952n)?-yC9;V|o> zcW|m_t$!zrLDiRxXJ=+EuBnk*x$t~*fNZC)oZ5SH>6Ke@w(r@eTjVFLxupIP`~Cb5 zlgs?hTvMedsyIw;_!7TknckhxA^s;3sR*9kB1ke%Qvb3T6cd^waPMWkFI)!samFPLVpALYQ67m|6Ko9+fcgV^ySOlTeGi& z?jDjmaOmG%4u1ahcXup%ZvK9Q4AhgtClBEQugev zjo4M9DQi_y@NG{03>C-JXR!ej&hIFETwoz{W{zd|fddXPGV9xmV%COr&z~<}wtH^u z?lQxI2Mj!H%oi_S)QS0F*(+^+EJ0(*jvYVp&Hr$nnxc7lqOv>aa4Su{X>-yX1p-!I zU1>I3mo0M6^XpE1eAg^1J}gK+)+0GzT~+ny2_|Q^CC|TzHQdZuXIJ~{%Q?_&@#IO9 zobvMYN?%>6{CH<)@oUf!%KO{-`(q!xeS24AiI$A&T#gyZNl8lQ=h;?+PEwaGdwc8a z%GtA{Ljy&27CvqZe!-);N{rK>?9Gk5=(LSRGY{e6GSLA&V< z^6u;qOFus^c8P_cL;Abk;+S0}D-TEK@BO;*WJ=Ye4-XGd(T|_EV1dHLvuAxR>i_N8 zw10iWy+s>qIRDN4_4QwlbP9|A{`u3C zdF2!nvKwB+rrP|w$YG=C`bF?g)hwYRrFlV*H(SE;J1s%ZP+zPRyel0ZPN$lTKZ{Y+P~Oy9kGcV>=dbMo=NjY&tn9zDAC z@!Rdm5ql~OXPf0dY88)5xUu2kmDS<=Ys0$Z)-T_(bx|_F#n#-ojZ3VjMH@9y?f_T#Z`ZXv%o@T$L`&apFeNEn)UR|Oye{2?dO-i zzQ$xw@Zhx1Y_s0l-`_6oF4wpF^TBzU&rGFaT{+&1DpS2$HM&0Kzr2_4A>x{2cDwZ7 zjF0S|OW!|FV7L3xS(>)wo^1S|Lw27U;)BJjzb`*`T(xT&o6nz_wK1$||ISoRP?C9E zpE%pwLnSBtP2GhyqrMaC=QoL-to-#WecR9D5BHy9oSIHTC!xxhnwm11J}pZT9v)&SifHX^Rrr?emUEj=I@oS zt&czd>+9>vhpqL-2?xAQ^6yPLaKPb9m91F!)ADfMjIB{NHZ~Fa>ufJC_h0VTEA{2| zb$&+y4GoQiYilAyRth=x%h_(Y{dT|guLWV=hrH_Q>ZVMab#woJhSSsalix<2{80Yx z&diC*_ZLKNPRlTny0tyu|8QGx#EuWfzrMWmeR?YN-<2(A#4JQyDvucxP8uG*|lfy-kY1#&9%eVshDjuT^qIaScXnf z7?+1{506RittqPB(+VFPaBSm|^zriIa&G5a8oD}c&%a-@@yZ0?AeI3RlX*5GSeBFmvtJiO;|Iav0 zFV<~y+Sxh2r6O-x7GLyO9kw>(@-p7l;p@}Bzq@fN`o;sMGS| z!-o&cb{}mznQ|y;W5ULWITPE@+eJGhB`MifeNoWT>U#7j>A9_@rlz8nR@d`+)qU&t z|C`0i{meM;PQ{j}wZ-yQbNR~4`S08*nKEr!lG*H?m7mqr{pY1zUl;r4_V(+QwuOa- zCGYOE>c#E~*qG$%uDWM_qefR(TSIkLRu<@FA35Gh$L(eMWgCwFh<8!*II?uhmTZ%} zJ1?eht6veYGNhuS;=|SO_`)YA5@(s^g2G)xQ}d_i@*`PWg8~8^932_|{rk7)*Q%@g5B|UF$Qfz5!3)oj<=_0r- zr!)QOsi|9XZcgHpwX)#dsJ-z*)>a{L@#DSH<`H|VMAt@dUlzRlT!u*&7uOe1uW9mR z@2Ds(_kOv@cXk%5sHs_e7Zwhlem%Z^?lj%#z};oJHa0eznwtNPR@DWHXliOse1Cef zii*s`-S77$Us&KcuqY1zxm6j&i&>LjV|8_rzXnYQ5MLW@I^b1 zt7*e?{yFcIIRi!Jvp-V~<5hYtlx%BwH!tF`|2{=R=%nOW?vl7ks0NtQs$+#Pkcy`ihaKofwkuC48k zTmS!d6+^=j)k+4v&%a+CQ&;SMV#BdWBk%6E+}jh*&9(mj>((WAiA5T}l&1ciT52H! zIMFRl9%*#bX*o+6c7}2TpP8u;^R>{2_DxcPg08C z-Lce*-F4*a>r5G&iiRgoQm$l~9`BPa)p{Shr$Vs4zWz#_X-_mx_8gc=4(i_kdTn4|Ne8cOydLh*VQ&PH@^?|D6w$)SMl|(zVq_SiX2Us z*1l`E_`YDWfdo(EWHn!@;+L0}f>ts}+uGVr?~yQcVp-0VP&z%=vih42ue8~b!-sD& zE&kjqZSJQadHnqUH`%|W4J22V&2QxvpH}zh<8j^zELWF(xbfrvTWA0pu{!Qh>`J;> z{QR6%YQ_JYz`5~F3LF*H)x|F^C{CJuK6qVBrB;XrsH%JSdee{UZ`s>#YwGBfSTRfW zws}p}3f!6%dgsoa3op0$%(3X)vL(BsqGH1A*{3Hc1_dvA^CrhfO*m}z*L~GWrH?-= zYG`z5hp*dE`8mxj?~aFu2gl5rGq>c)djJ0Rc1QJhz1G&MclXX7P1|hwwB`Sdbu&8L z7P~GL>gee?aQyh}H*aJ>nj#`3mM&Fu5e(5o6N&{(qM+|q}7%ct??6#I&I zEQ%1TVD+>(UibRiT1!!_kPFdkuPOAp8MAm=-L3ll?d{IQ#KhK5uqmWvfc z2(-M9dEcthbzZ47mv@4=WJ86cz_$Na{tCNp*$v`1I-SLx-F|M;TUCSv4snY>j%K z-DsU~z`OpI<$oa)DPFVLz3=zk{xV(m^YR5D5gM9|fg&eQo|K$3V}`|zyzR;yOj2k2 zcl&iMI&pHc`s!UetDf$P%gfI8_42y)?A^OKY0uR2m2(apIB?UxI4b|my}i|5F)??3 zdnlQ|ZTLQQD(IR9UUT#9pnY{O%_=@59G!4!d;a|=6DCZ^E`N7NvO#6uw*^(Fx6iBp z_tR0@EGMGw@v+_$A3l6&_1M!YpS0p=QlyZiq@|Uz@?*!M*_$UG<3Il7y7d05l z>g)fTr<9(P^w{)}zeS^~v{4{t*D9vH+jDP=U0NBu{HVh&jV|2@KUhoUHgsSA^y!n` zs*shJSQcN*(ACm<_3-iI&&7Y5{nqdOcI);0+HW_d0!7|>1a>WBh>VO}uB4>2?#YuU zB2S+@@!{v>>|{J~;)F%Wa=*D-il3jG+0gw*bm2;SK~Vhag%#&J>@qETb0cbV+SyHt zC-e5TeEGoi;qUkR|FuT%LR%!$7Gl2BLDvRqj&Rd+0?UX z!Q4#=fh$86X>{#6Jzd}Ze}ef^z4x~@@{5X!EULeO`a92#c8jxTY>ispzDmIQ_0`qv z6C~v2=i3!OJ2NkIRY>ByJ3CuiS{OL8w*Hb`5WU!~H||N?e1Uawdrv(&+AYp_eM{6@ z<0GK5{oA{{yIE$a__Q^;|-BMFlPPTb4hqHUp4fdTYFC1)U zKO0~7bE(nq`OyIZ1&$&|xlYt_IAk5I`}QXC)z#JRGmX5g1?KIo_^4F1ci$4v$!c<0 zTca3GoH;XxgGa_V?Tm=Z)q_TC&5f+wViAuYKi?*6o6l#AyU(U=ZdfAxFaBVV7|X*+3LJ|LKNOfWsqfSAdGP#s{_^tj=VwAiTt9vKR8U^NJnihPAHRMHNl18vg^6WmX127n6nuXd zYgPK{$d{LwPoCQK`GO2XuUm82>eNFmoHKmXY;0{$oH&tiu<5)23uE`urrEQlwWfXu z2xiFIDwUs~f3Ny|?6dRp>+f4R3i$o}`EzULogEtwrxn&Z1P+IcF{|b_d;I zz`)KYGsCd*)06+8Godr;>g-ZaPg}ZTg-0R7iPNWzIJ>1UE%ps~B_NC_8 z%=Ds|@4PRp%HF)#Bi4LSVcGKK-0gg_kG{RVy<0DS-<&+N*|V9p9`BdGf8cKHq-*ET zo-NI}zb`g4AfVwAhsV;Og_mD;^z`&t)cyGp?s0Z+Q0ltFJPhrJ6Xi{_righKKNI_& z(lp=c+S~27-ZmVfWeT}=!uf6KqTxjDI|#YIR+=*6|Q z(yXkkMRN1ni)t7Gr_5O~X_8Q%thZCC|_3c3$S%vp3`7qI@PM zCavE;f68WEUl+S?i~mNx0N~-X!?viIt3cd0SF*}PuDeg+vRzoeV zQ*Ab8wg3Nq7ZDfdc64;S_25B*^rpSP-tB(>;rsXHm7l9;HdcLowK4g4pZwhUck|SZ zjVJFaeI2G5zHUzO&tttp8?WBKe}AWrwYBt4*TQXg^YU9%OwVu4zHU+ZN#(%X4<9}x zJqbDc_4W0sPZ`?fOY-GPZORz_&0Mi|kEX8f(j7Y_^7HfA_~mM9etmfvS}3_-`&fp?33UFVdLDvI?|3WzrFR+lQ z`EW4D$k^D^*H_ipc=DV%GI@7*`TCucy>s_&;L4C0K59+P%^LChm=-Qn3|SE{v7>|I z)Ku;M*|YmzUCn;*{JUWF_jgM@CohZ8vD{x{v|pI5xv`B``qA6BzJY;)xwp5aK0PIB zl6#A1ZP@1v?-d(%@0Pyw{@JVMu=zC?oLHOL`R%yp{5|+cY)8-b*VorSe|C0ugpOEH zP>@Bz1BT4Z%u}zd@7I2x>%KMXsulB{z17ourOjvEy>sWtzrVk=o2Yki(!azi*5~Xj(~loNGQPO9qwuj_@G_s7 z^CnK*c;fi+=XycWW!Kh3I(vC>N%cP4*Cn|7?z=gK;cFs3UOL(>ewyhpLs3!Dnaj)l zuPc3I_@-$7NYFK~HH4F);gZ;nw6n8jNttF@$j)?b=VOexy*5-O6J(d^<@r239wOVGU@Ion;o%kS5I|MKeU>baXIU%C`Df6pG9Q`7a| z*96qo)EraVIHg^YS7EuQ;1_IBRQAP>|D1qkSc>ug#q@Wwiv4>x!$d z+Qm+8h*}F;Yx(T#?BX{OLZGXy+4asQK>a(VlJG0F5-|eXSy6X87 z&&hMFz5V^mrS5@tKFOv$-M$ul5=CFhvsM1{Y&^ZZxVC0peFVC=o^4Oa{<^=ro}QjQ z|I(Vs&Ee0_%oGmVS(M6fYMQQf;9axy0hq)3^|qE+-~Gqa}+S zo7uvaypVTW9N1Z~0(6K|P|D3;mzVn|o6VlP-*;_Tw*p63UTepfGqdMcJnH)}j z(8Z{C)2!UjSlD-H^@ylGFBxTB@YfX zo_j3O>vn5P=Ha8=;+Cbarc9oq%%5i3!1>3H{eehO=fqcEUK)#ti=Sru@Ng-k%Dl#@ zXJ?xSZ_l%p;bY&Pe?RTV2SL#BvtBWmGfY7HUmF@2l9F#vm_EIHeq968RIjHw-fy(7 zJT9;>km{C;xH)0^bpMa#rZrm%b9= zYj3`{x0?Iyt*x&)k902D@eQ;`#@N`HpPiM}c6rY188bXQJv;M$e|uZ<_LgYF_j}d- zX1TW>ynH!RR!}gJi-UtBWM#` z87Jj6HQn;ibZ6+3wPph?Q><1Dl2kr<@}w7QmxQEbq~y+HA)R7NmMxpNyY~0C@}*u= zl^CY!M!U_ot7VawpC2tKD7eqj(Xq}UEiEl=y8gZ&i?U*3=1i`iIAscp0_cRC<7dzM zYG#Kh@vXS}YW0pO*>=C*Y`(HOy#K|E7X>jv21PEcb^CZfD))j?5I^q(+r$Qc`@bQT z41ue!o|$RfzF>g@L!Z38Utyu)-sjto8u7RaMX8l$Gn?7@iyj@}EU}t<@b>NK)|k`Ulczn1%hlhWclVQ3 z*_(*T4Gj%vZrr#LKdV#LZI%AIjp^s}E?vId+0juE;P*ZJu<83h3QBX9FINXmC4PG9 zz32bG5yxBE?(E(NJ4-%8tT z`{lL4?6X%^1UeT=yu9=@-e-ovyvgeR7ngV@^ROM(fB&M~Y4z1fbLXD@`1rW|mR}}8 zC%$i2m{rZ!U~6a?XsC33$9)4Wk173vECy+3X2|CjJn2X&W^ufcy)oh%)7**2KdaBH zc;tRt`+UXCO{uvaeuV}GHbzE86)8(hR&I@2yKQCg@~+d<^{+FXnP>ZZ@w(XEVGMB$ z9lgD*)4keOhp&I8AGha+tU}p~3kwgMNcs9R?7o{<`S^D3Wxt^fNvo_WIIVz1LzR;*YNkkTKUc0nutzsL-m+FvEx z!`4R0x|cDCtl#I{&Ub9X27~3y3e~R^&h7%Ou~@pV_V+sb(pMqO@$vU3O;mQ@XPJL* z&-`F-V^=pf(2)hEH8nK`Grc%CK9pM-?-xJyT~)d2`~k01Ras^^5&jhws;fd*A8VIC zw>0^S8yh*RnU}J*f=1XcFFSi< zlj=OjX12hcMXCFKrrDmIW$L>2TEcewImH)VmZYSm6+JxklYQ;JAJ%0n+1;j0o5t27 zY5Z)jtaX{jY55y|5dtmVi!`?R`uf%;pPr_xpPG`gA^p7EqQ#3jd3cWGZU20?e3DBXU0q#4 zJ?g)|zq3p99*a-_9btNQVg;X0)Rq&4zrJLy-7~+_W9iX#H<;!`Z_T>e?LA$u)+^5} zwZEsQX7h#(2Ja{P+eL1u`B@~-(0s7KbM@7}ORv9Pe0;38I{o#vwWsVZIXOG$cPx_l z{d9VKpRcFq%&2uSmM(!$-re1;qNY~#;=;nBw9PwXDk^p`OVm6$G0{sqeBB(CaL+AdTw?^&VmVdwRY}(?gudj;g zJ4~*Z+)2Z|o|~ZrQ&5yXu6r#81shmv?-*9J>1I zsW%@lZ9Z=o9kw*+Yrr+lucu32Un|XN9K`8?t1L&$M*$W&F>#tXXPEiY7?l-rpW6_HZx8I&iKDG4&-&C(u>+*Lo zqDxQTn_-yD!O8j4`Pk|E_ve54BF!Mtdvry}_ntLD#q}>PD609)IB~RFTvJbPnQONg zf9*A%OQqRcqwd_f6Tp%sqV6!0QFDI%k8sQF^8Z4WSQlv|ZNL5T^XJ|E_74){y3eL9 zuKfHAbixj|xZZ`$>H5q3<{tX_`T5Gg#cZywuJykPSsfkLh7~_MlezTFInkvD7P8m8 zo&IemW6iC#(c2gJ&Nef3xpXOj`E=bg>*o!O9zUPjHN3yMIX!s7se;857crN$AKn;y z@1KCs!}rWfB)7Q-1zloQDT?AROJ(wg+dq@+c> z4}LzMAKxP4`?^_$<4OCJO$JQ6L3i?OUl+T(k8wxw^S;zOI|}nW=6%ddxXQfF_hZ8v zk0h_p@HI`hR%^^%VQ@^LOE@P~oc!goM zLa(AyP~5*2Zi|2V3AC`N96cQ6anbYPrKR4*ERH+6j~u*k@_EhIPft%p z?!L2B#^SAQBIsd=%^58C?pJMaq?CtIO(XXzpy&W96 z{QB#Or{}x_ZJAQqq>>g9A@P3SZ#_v#$)~@+zqgmSOrJhu#tHTL9$a(J&NeSsaA)^?W8A;rSv&8@Gt^XARlk}ZCGiKp<&kd;FIHXo13tF5y=QJ{Uy zYiW>W{5r4Jb;n|b{t3p~S1R ztqNW3_ViTfjeWJ&#_8ukt80?3uY12TcK0{u%nwVhW^IhhRZ&r~%P5t))bM}zhT7lX zY@U?d%s*iBz*vR(t8)Ky#_RTM72+==e_s4{N=JOge~$nM#wm~^Q~33PE#`YCB80K(!Q)IIi?Vq?GwD;YQTp+#ah|&JVN-qPCC9^-6-x{I9 zrq__&XYBaT+948j2zts+KVL`3g9T_fND{o$sH-_7t3Deo%>g~>$aP; ztApW}%2oRoJ>6aKWpT~@&-1?jsVi$pJv}X!^EG$E#YL{uL1(L!oA^r0NJ#APy>umO zE1!sn$dR^5m~aKX+y8MpSb^DCwt-*fq;im7SuHnT`a!`Wv` z%B?LbH%%ygZS>;BizRE;^vwVFMZU9hh$B*v-ckte)WMpa<9Sc z(lcq?yYdcZnI><(>ESWKTP=9mk%h0V)F$`cT$8*tYU9nEBgc*jN=tj^8Cc`kG~AY<~ILH9t2uw$@hHKR=Gw_a9A?{4UEFwtDKaWy?Oi z3bt4-(JZv=_+!c3ZI5;ypI@!-=%r9@)+3VtfFsdB!tR>M+rK?MJ@=nHd&Z_(S>OBS z#QyMO&u*xuKW9EXNBZ>U+Kqxaw0lzO^fOH0e^{CxZW_rAQmtjhq}*J5pb8??5FmAUTxY5o0w zv?rf5xtzECJZQeapHG%6^JhrIop~*1ww-@}fB%2bSb!~~fV_PD;k3=U%wO*AF8_ac zUF_~PjlZ1)_b*uR;03#U&4<-@?%dfw&oJ5TcDMe%AKyUE+I{YXk6Q4g^Ur^m*4Nu} zi|ajEIz8^td-26{Y`Yv9%r0EM>>V8)z2Z$=RFqSAxVogIWX;E;nS0_uryt0K{W7l+ zlQ;QdDCL`R%eVG_VB*&2!C+VJ)Jf5I%vo1M0dHwZ*Oj@sH+Q$ zi?`p*IhM7xO{Jze`SZ`3gkr+p)D=0wsnz`u5J=-o;D2xsE+$_cb!{ea(8}>{;6eo!`|HdV9LsWu`K?2+mq2 z%6t@bO;+0W+w)!1w$?Gdo;7RPUeO&Tia}e?GyO3v@KZQF@pxms;)Zh)JN`)pasOU? z=FFL2XU_Qes0A-N|Gb(n^3rBTcgCdTP9DMTh zX=Pj6+L-O7QoU|>^30F-$#$3Q>YnW#*mYvXg8!z@stkc0ck|XeaTQifOx+m3S@81G z(q*Sko%(vhO;UM#`G;!^_D*Jsto8LVEtwOUf7IBCUpf@bvQE0F?&KuZwpXuS#j~Bu zuC1-z%dmdmuT_Qb@9n+M-*dX+b?xFMGMc7;LIMK?&z?Q2qORT_{eYvaH#{_SbK+s` zFWHCe zJ3rq(Z_|Oa%`-P`GK$d?&%U;1>4psgn{S@EwKdyt_E{5i^UGJRxWvZFK7E>=k+H)i zIXSq(`C5lWHp71TtIZc}F2DYI>E1oR>gwMDkHs3UTnQ1=i#gIIs$KDF3se+ zhW)15*FXo{b#-<9d{=&bL+x*QO>OPe!)^N|#fw#&4>nxQdVHWUN2agiYA@5FFW*GF zPBwTkh+I5mxJ0t^i++}9~>9w7aOZ=~wKcO7hrIdhT}`zz%_shg ze~IpU@O_QP-!=)Q>XZGQ-QxO1A0N5$$y#~T{0R}$jXGg`-sZ6Jd7IN)GCM;;LqDF< zUjO08s~Zv)_UV{(e!=5oyn3;_T%4VmZ*QwLdd8wGG;3L9ivZt~Yp2pu z_}hecPI>I|E<4ZNthS7`Pws$z%LnD@u}$&p%s zvu1ABo39=0Y09Zfo1ZuF&i&qUxw3iEq)8Jdgw)j7e13j@`QpXRrLT={Yk0z|TzW3B4 zHbV)X8#&t+%;e0zzHaH7H9ZUVW=#m3_U`vY$3IpdM2r6Jl*(zu=iM!`IUFcoX>MNC>%{)rs$gdXyT;RnI6keMxIbRKl6@r(Ss7Jl`B>x+}~FV zy3;G?)|Spadu%`pUFz%queiVXWXhqJC6ngczfq~z*>;8NsB^a$gGgYrfD*T+uhi_> zvk(9J`r2^z*~NLgE|5A{lhp^7pqaX?GulIs0qE_|35me{%>j6`naE3_)ojzVj-tk+N>*y0lj*aXKmM?F2Zs$uhkl@&CC1J?c%&5q6ynlYewKXc!bRt2g zE3~h1y`OX}m}w9HAKjqWHZRP!39mE|*FE^(Z;O&})7%60ioL7nx$nC8ciM*B3i8)C&6|uL<)YrFH#-dfry(*~ z5Numng1(35<7RgLMX#q-&$2zWu25Q3^y!yx-##AV)-U++Vd8?dpVmHM+Mh5ftJ6L~ zk70IG{fSejlx+X%zkFRB7+1&EeYDD7>;zjgW32h)$&*2Y+sd&;5;q%G2Kab|FfHhC zE6bjBjeSG<`Mjv8SsQQWD8IWh{po+luC6W*U*FVcXJ)ok$}ucFW`EH4X7bS=8~Pjm zzWVt1xTlYgif%5WtE+3nfee#-Ki?Uw3DaJ?cI}({|LYmHt2H*CIMwWAm>I~~pd_`@ zecB_-@^>=qd@>xKU;9|Pk2(bh3s0RIx+Ze-v|qof&dt&6F3Hq8<&d^#r_TZTMf?6S znVMb&?V5Y_YO{%1+_4d@2ex+xHx5&a6hdKLtpe-}t5&Z*dGcq^ zjT<+1uTzxnb=woCukJrjMOT;C_p(LRmy9LervrIYzIWF!MSuSE`Lm$YhJ=Goe)DWP zufN{=+Nfb!=8>aE3m+ZfRs{HDfqatYCorK>S3~2$gM-aJ zGllGP^cxN;m_GLQ^7>d|Q}cQD{UsYVXiO69mF{?zb33bA;Q}w~I|wq~_e6oyDGBUXQM9N0j`SQxus~2xonLf&ESWtO+p6zbS;$?rfeYTsw{`C9r`Mch>I2n2dayB?Iz85UG za(h#%wzytQ!l@~m+s_K z3j>{A`mw_1%9Sg3rZI3R&M->l`ug>2&fQ(C5?WVfe(d3Wd3r&B3#%4mfX0G02j>S* zPse|H*gpG$32*mNpg^V}*0?^X?E3Amy6Jw?tXWc*E?x5Q z@maEJ6_?_X2@LXUUe)|~s3s*PrSz2}hS{zrK{;GQjwys`L4Ze{`+)kD|hLR zm~|6QPF|J?8qWXor>5-94bBr?4jT>$d%UxA=;F~732gRa5aIGP{;-}Q?mff%eZS?m zY}q0pF5WH>lyvRki$}M%X4`zfV+2}I?B(sHDJycWG*KvPrmP}bJ22bK;Qv&8=VImOavKFibD>pPc& z&ulZ_JwKnh75@2AxH0SMD#iD&rahi6Y?=Tv$YzfY%PtOfEJGftpsIds{9`|$aH|FfBnFALBR=~^$D_P~DP-A4}{pYm*|VLHCF7xDnpZb|=iClC^xt!DLB-p7wp_b8jBI7e-rl>Dn?R?rFls@Pbo&C?i_#5m!4G}Jo gi;$=!q0j$gf9*A3Z(H|_fq{X+)78&qol`;+0P;9Cn*aa+ literal 0 HcmV?d00001 diff --git a/liberty/assets/dc_ps2f.png b/liberty/assets/dc_ps2f.png new file mode 100644 index 0000000000000000000000000000000000000000..df2b1d2776d0e7a698b51d47546966276b38b71d GIT binary patch literal 93428 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%0D*IoBxFEB|}B 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 LtDnm{r-UW|T!Vr< literal 0 HcmV?d00001 diff --git a/liberty/assets/dc_xboxf.png b/liberty/assets/dc_xboxf.png new file mode 100644 index 0000000000000000000000000000000000000000..112a5b59e2d630dc3fe57eb9da574fc6a818f061 GIT binary patch literal 91910 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%0D-moFzzM5d-j}H$g3Av^; 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 apET|N`<3VCZDn9!VDNPHb6Mw<&;$U%0gWO6 literal 0 HcmV?d00001 diff --git a/liberty/common.mk b/liberty/common.mk new file mode 100644 index 00000000..d372495a --- /dev/null +++ b/liberty/common.mk @@ -0,0 +1,401 @@ +GIT_VERSION := $(shell git describe --always --tags --long --dirty 2>/dev/null || echo "NO_GIT") +CI_JOB_ID ?= 00000000 + + +git-version/git-version.tmp: + @echo "Generating git-version.tmp with GIT_VERSION = \"$(GIT_VERSION)\"" + @mkdir -p git-version + @echo "#pragma once" > git-version/git-version.tmp + @echo "#ifndef VERSION_H" >> git-version/git-version.tmp + @echo "#define VERSION_H" >> git-version/git-version.tmp + @echo "#define GIT_VERSION \"$(GIT_VERSION)\"" >> git-version/git-version.tmp + @echo "#define CI_JOB_ID \"$(CI_JOB_ID)\"" >> git-version/git-version.tmp + @echo "#endif // VERSION_H" >> git-version/git-version.tmp + +git-version/git-version.h: git-version/git-version.tmp + @if [ ! -f git-version/git-version.h ] || ! cmp -s git-version/git-version.tmp git-version/git-version.h; then \ + echo "Updating git-version.h"; \ + cp git-version/git-version.tmp git-version/git-version.h; \ + else \ + echo "git-version.h is up to date. No change."; \ + fi + +.PHONY: git-version/git-version.tmp + +../src/liberty/skel/dc/dc.cpp: git-version/git-version.h + + +# List all of your C files here, but change the extension to ".o" +# Include "romdisk.o" if you want a rom disk. +RE3_OBJS = \ + ../src/liberty/animation/AnimBlendAssocGroup.o \ + ../src/liberty/animation/AnimBlendAssociation.o \ + ../src/liberty/animation/AnimBlendClumpData.o \ + ../src/liberty/animation/AnimBlendHierarchy.o \ + ../src/liberty/animation/AnimBlendNode.o \ + ../src/liberty/animation/AnimBlendSequence.o \ + ../src/liberty/animation/AnimManager.o \ + ../src/liberty/animation/Bones.o \ + ../src/liberty/animation/CutsceneMgr.o \ + ../src/liberty/animation/FrameUpdate.o \ + ../src/liberty/animation/RpAnimBlend.o \ + \ + ../src/liberty/buildings/Building.o \ + ../src/liberty/buildings/Treadable.o \ + \ + ../src/liberty/collision/ColBox.o \ + ../src/liberty/collision/ColLine.o \ + ../src/liberty/collision/Collision.o \ + ../src/liberty/collision/ColModel.o \ + ../src/liberty/collision/ColPoint.o \ + ../src/liberty/collision/ColSphere.o \ + ../src/liberty/collision/ColTriangle.o \ + ../src/liberty/collision/TempColModels.o \ + ../src/liberty/collision/VuCollision.o \ + \ + ../src/liberty/control/AutoPilot.o \ + ../src/liberty/control/Bridge.o \ + ../src/liberty/control/CarAI.o \ + ../src/liberty/control/CarCtrl.o \ + ../src/liberty/control/Curves.o \ + ../src/liberty/control/Darkel.o \ + ../src/liberty/control/GameLogic.o \ + ../src/liberty/control/Garages.o \ + ../src/liberty/control/NameGrid.o \ + ../src/liberty/control/OnscreenTimer.o \ + ../src/liberty/control/PathFind.o \ + ../src/liberty/control/Phones.o \ + ../src/liberty/control/Pickups.o \ + ../src/liberty/control/PowerPoints.o \ + ../src/liberty/control/Record.o \ + ../src/liberty/control/Remote.o \ + ../src/liberty/control/Replay.o \ + ../src/liberty/control/Restart.o \ + ../src/liberty/control/RoadBlocks.o \ + ../src/liberty/control/SceneEdit.o \ + ../src/liberty/control/Script.o \ + ../src/liberty/control/Script2.o \ + ../src/liberty/control/Script3.o \ + ../src/liberty/control/Script4.o \ + ../src/liberty/control/Script5.o \ + ../src/liberty/control/Script6.o \ + ../src/liberty/control/ScriptDebug.o \ + ../src/liberty/control/TrafficLights.o \ + \ + ../src/liberty/core/Accident.o \ + ../src/liberty/core/Cam.o \ + ../src/liberty/core/Camera.o \ + ../src/liberty/core/CdStreamDC.o \ + ../src/liberty/core/Clock.o \ + ../src/liberty/core/ControllerConfig.o \ + ../src/liberty/core/Debug.o \ + ../src/liberty/core/Directory.o \ + ../src/liberty/core/EventList.o \ + ../src/liberty/core/FileLoader.o \ + ../src/liberty/core/FileMgr.o \ + ../src/liberty/core/Fire.o \ + ../src/liberty/core/Frontend.o \ + ../src/liberty/core/FrontEndControls.o \ + ../src/liberty/core/Frontend_PS2.o \ + ../src/liberty/core/Game.o \ + ../src/liberty/core/IniFile.o \ + ../src/liberty/core/Lists.o \ + ../src/liberty/core/main.o \ + ../src/liberty/core/MenuScreens.o \ + ../src/liberty/core/MenuScreensCustom.o \ + ../src/liberty/core/obrstr.o \ + ../src/liberty/core/Pad.o \ + ../src/liberty/core/Placeable.o \ + ../src/liberty/core/PlayerInfo.o \ + ../src/liberty/core/Pools.o \ + ../src/liberty/core/Profile.o \ + ../src/liberty/core/Radar.o \ + ../src/liberty/core/Range2D.o \ + ../src/liberty/core/Range3D.o \ + ../src/liberty/core/re3.o \ + ../src/liberty/core/References.o \ + ../src/liberty/core/Stats.o \ + ../src/liberty/core/Streaming.o \ + ../src/liberty/core/SurfaceTable.o \ + ../src/liberty/core/timebars.o \ + ../src/liberty/core/Timer.o \ + ../src/liberty/core/TimeStep.o \ + ../src/liberty/core/User.o \ + ../src/liberty/core/Wanted.o \ + ../src/liberty/core/World.o \ + ../src/liberty/core/ZoneCull.o \ + ../src/liberty/core/Zones.o \ + \ + ../src/liberty/entities/Dummy.o \ + ../src/liberty/entities/Entity.o \ + ../src/liberty/entities/Physical.o \ + \ + ../src/liberty/fakerw/fake.o \ + \ + ../src/liberty/math/math.o \ + ../src/liberty/math/Matrix.o \ + ../src/liberty/math/Quaternion.o \ + ../src/liberty/math/Rect.o \ + ../src/liberty/math/Vector.o \ + \ + ../src/liberty/modelinfo/BaseModelInfo.o \ + ../src/liberty/modelinfo/ClumpModelInfo.o \ + ../src/liberty/modelinfo/MloModelInfo.o \ + ../src/liberty/modelinfo/ModelIndices.o \ + ../src/liberty/modelinfo/ModelInfo.o \ + ../src/liberty/modelinfo/PedModelInfo.o \ + ../src/liberty/modelinfo/SimpleModelInfo.o \ + ../src/liberty/modelinfo/TimeModelInfo.o \ + ../src/liberty/modelinfo/VehicleModelInfo.o \ + \ + ../src/liberty/objects/CutsceneHead.o \ + ../src/liberty/objects/CutsceneObject.o \ + ../src/liberty/objects/DummyObject.o \ + ../src/liberty/objects/Object.o \ + ../src/liberty/objects/ObjectData.o \ + ../src/liberty/objects/ParticleObject.o \ + ../src/liberty/objects/Projectile.o \ + \ + ../src/liberty/peds/CivilianPed.o \ + ../src/liberty/peds/CopPed.o \ + ../src/liberty/peds/EmergencyPed.o \ + ../src/liberty/peds/Gangs.o \ + ../src/liberty/peds/Ped.o \ + ../src/liberty/peds/PedAI.o \ + ../src/liberty/peds/PedChat.o \ + ../src/liberty/peds/PedDebug.o \ + ../src/liberty/peds/PedFight.o \ + ../src/liberty/peds/PedIK.o \ + ../src/liberty/peds/PedPlacement.o \ + ../src/liberty/peds/PedRoutes.o \ + ../src/liberty/peds/PedType.o \ + ../src/liberty/peds/PlayerPed.o \ + ../src/liberty/peds/Population.o \ + \ + ../src/liberty/renderer/Antennas.o \ + ../src/liberty/renderer/Clouds.o \ + ../src/liberty/renderer/Console.o \ + ../src/liberty/renderer/Coronas.o \ + ../src/liberty/renderer/Credits.o \ + ../src/liberty/renderer/Draw.o \ + ../src/liberty/renderer/Fluff.o \ + ../src/liberty/renderer/Font.o \ + ../src/liberty/renderer/Glass.o \ + ../src/liberty/renderer/Hud.o \ + ../src/liberty/renderer/Instance.o \ + ../src/liberty/renderer/Lines.o \ + ../src/liberty/renderer/MBlur.o \ + ../src/liberty/renderer/Particle.o \ + ../src/liberty/renderer/ParticleMgr.o \ + ../src/liberty/renderer/PlayerSkin.o \ + ../src/liberty/renderer/PointLights.o \ + ../src/liberty/renderer/RenderBuffer.o \ + ../src/liberty/renderer/Renderer.o \ + ../src/liberty/renderer/Rubbish.o \ + ../src/liberty/renderer/Shadows.o \ + ../src/liberty/renderer/Skidmarks.o \ + ../src/liberty/renderer/SpecialFX.o \ + ../src/liberty/renderer/Sprite.o \ + ../src/liberty/renderer/Sprite2d.o \ + ../src/liberty/renderer/TexList.o \ + ../src/liberty/renderer/Timecycle.o \ + ../src/liberty/renderer/WaterCannon.o \ + ../src/liberty/renderer/WaterLevel.o \ + ../src/liberty/renderer/Weather.o \ + \ + ../src/liberty/rw/ClumpRead.o \ + ../src/liberty/rw/Lights.o \ + ../src/liberty/rw/MemoryHeap.o \ + ../src/liberty/rw/MemoryMgr.o \ + ../src/liberty/rw/NodeName.o \ + ../src/liberty/rw/RwHelper.o \ + ../src/liberty/rw/RwMatFX.o \ + ../src/liberty/rw/RwPS2AlphaTest.o \ + ../src/liberty/rw/TexRead.o \ + ../src/liberty/rw/TexturePools.o \ + ../src/liberty/rw/TxdStore.o \ + ../src/liberty/rw/VisibilityPlugins.o \ + \ + ../src/liberty/skel/crossplatform.o \ + ../src/liberty/skel/events.o \ + ../src/liberty/skel/skeleton.o \ + ../src/liberty/skel/dc/dc.o \ + \ + ../src/liberty/text/Messages.o \ + ../src/liberty/text/Pager.o \ + ../src/liberty/text/Text.o \ + \ + ../src/liberty/vehicles/Automobile.o \ + ../src/liberty/vehicles/Boat.o \ + ../src/liberty/vehicles/CarGen.o \ + ../src/liberty/vehicles/Cranes.o \ + ../src/liberty/vehicles/DamageManager.o \ + ../src/liberty/vehicles/Door.o \ + ../src/liberty/vehicles/Floater.o \ + ../src/liberty/vehicles/HandlingMgr.o \ + ../src/liberty/vehicles/Heli.o \ + ../src/liberty/vehicles/Plane.o \ + ../src/liberty/vehicles/Train.o \ + ../src/liberty/vehicles/Transmission.o \ + ../src/liberty/vehicles/Vehicle.o \ + \ + ../src/liberty/weapons/BulletInfo.o \ + ../src/liberty/weapons/Explosion.o \ + ../src/liberty/weapons/ProjectileInfo.o \ + ../src/liberty/weapons/ShotInfo.o \ + ../src/liberty/weapons/Weapon.o \ + ../src/liberty/weapons/WeaponEffects.o \ + ../src/liberty/weapons/WeaponInfo.o \ + \ + ../src/liberty/audio/AudioCollision.o \ + ../src/liberty/audio/AudioLogic.o \ + ../src/liberty/audio/AudioManager.o \ + ../src/liberty/audio/AudioScriptObject.o \ + ../src/liberty/audio/DMAudio.o \ + ../src/liberty/audio/MusicManager.o \ + ../src/liberty/audio/PolRadio.o \ + ../src/liberty/audio/sampman_miles.o \ + ../src/liberty/audio/sampman_oal.o \ + \ + ../src/liberty/save/Date.o \ + ../src/liberty/save/GenericGameStorage.o \ + ../src/liberty/save/MemoryCard.o \ + ../src/liberty/save/PCSave.o \ + \ + ../src/liberty/extras/debugmenu.o \ + ../src/liberty/extras/frontendoption.o \ + ../src/liberty/extras/postfx.o \ + ../src/liberty/extras/screendroplets.o \ + \ + ../src/common/vmu/vmu.o \ + ../vendor/miniLZO/minilzo.o \ + \ + +# Excluded \ + ../src/liberty/extras/custompipes.o \ + ../src/liberty/extras/custompipes_d3d9.o \ + ../src/liberty/extras/custompipes_gl.o \ + ../src/liberty/core/CdStream.o \ + ../src/liberty/core/CdStreamPosix.o \ + ../src/liberty/extras \ + ../src/liberty/extras/GitSHA1.cpp.in \ + ../src/liberty/core/AnimViewer.o \ + +RW_OBJS = \ + ../vendor/librw/src/anim.o \ + ../vendor/librw/src/base.o \ + ../vendor/librw/src/camera.o \ + ../vendor/librw/src/charset.o \ + ../vendor/librw/src/clump.o \ + ../vendor/librw/src/engine.o \ + ../vendor/librw/src/error.o \ + ../vendor/librw/src/frame.o \ + ../vendor/librw/src/geometry.o \ + ../vendor/librw/src/geoplg.o \ + ../vendor/librw/src/hanim.o \ + ../vendor/librw/src/image.o \ + ../vendor/librw/src/light.o \ + ../vendor/librw/src/matfx.o \ + ../vendor/librw/src/pipeline.o \ + ../vendor/librw/src/plg.o \ + ../vendor/librw/src/prim.o \ + ../vendor/librw/src/raster.o \ + ../vendor/librw/src/render.o \ + ../vendor/librw/src/skin.o \ + ../vendor/librw/src/texture.o \ + ../vendor/librw/src/tristrip.o \ + ../vendor/librw/src/userdata.o \ + ../vendor/librw/src/uvanim.o \ + ../vendor/librw/src/world.o \ + \ + ../vendor/librw/src/dc/rwdc.o \ + ../vendor/librw/src/dc/alloc.o + +# Excluded \ + ../vendor/librw/src/d3d-x/d3d.o \ + ../vendor/librw/src/d3d-x/d3d8.o \ + ../vendor/librw/src/d3d-x/d3d8render.o \ + ../vendor/librw/src/d3d/d3d8.o \ + ../vendor/librw/src/d3d/d3d8matfx.o \ + ../vendor/librw/src/d3d/d3d8render.o \ + ../vendor/librw/src/d3d/d3d8skin.o \ + ../vendor/librw/src/d3d/d3d9.o \ + ../vendor/librw/src/d3d/d3d9matfx.o \ + ../vendor/librw/src/d3d/d3d9render.o \ + ../vendor/librw/src/d3d/d3d9skin.o \ + ../vendor/librw/src/d3d/d3d.o \ + ../vendor/librw/src/d3d/d3ddevice.o \ + ../vendor/librw/src/d3d/d3dimmed.o \ + ../vendor/librw/src/d3d/d3drender.o \ + ../vendor/librw/src/d3d/xbox.o \ + ../vendor/librw/src/d3d/xboxmatfx.o \ + ../vendor/librw/src/d3d/xboxskin.o \ + ../vendor/librw/src/d3d/xboxvfmt.o \ + \ + ../vendor/librw/src/gl/gl3.o \ + ../vendor/librw/src/gl/gl3device.o \ + ../vendor/librw/src/gl/gl3immed.o \ + ../vendor/librw/src/gl/gl3matfx.o \ + ../vendor/librw/src/gl/gl3pipe.o \ + ../vendor/librw/src/gl/gl3raster.o \ + ../vendor/librw/src/gl/gl3render.o \ + ../vendor/librw/src/gl/gl3shader.o \ + ../vendor/librw/src/gl/gl3skin.o \ + ../vendor/librw/src/gl/wdgl.o \ + ../vendor/librw/src/gl/glad/glad.cXXX \ + \ + ../vendor/librw/src/ps2/pds.o \ + ../vendor/librw/src/ps2/ps2.o \ + ../vendor/librw/src/ps2/ps2device.o \ + ../vendor/librw/src/ps2/ps2matfx.o \ + ../vendor/librw/src/ps2/ps2raster.o \ + ../vendor/librw/src/ps2/ps2skin.o \ + +INCLUDE = \ +-I../src/liberty/animation \ +-I../src/liberty/audio \ +-I../src/liberty/buildings \ +-I../src/liberty/collision \ +-I../src/liberty/control \ +-I../src/liberty/core \ +-I../src/liberty/entities \ +-I../src/liberty/extras \ +-I../src/liberty/fakerw \ +-I../src/liberty/math \ +-I../src/liberty/modelinfo \ +-I../src/liberty/objects \ +-I../src/liberty/peds \ +-I../src/liberty/renderer \ +-I../src/liberty/rw \ +-I../src/liberty/save \ +-I../src/liberty/skel \ +-I../src/liberty/text \ +-I../src/liberty/vehicles \ +-I../src/liberty/weapons \ +-I../src/liberty/audio/eax \ +-I../src/liberty/audio/oal \ +-I../src/liberty/extras/shaders \ +-I../src/liberty/extras/shaders/obj \ +-I../src/liberty/skel/glfw \ +-I../src/liberty/skel/win \ +\ +-I../vendor/librw \ +\ +-I../vendor/miniLZO \ +\ +-I../src/common \ +\ +-Igit-version + +DEFINES = -DRW_DC -DLIBRW $(if $(WITH_LOGGING),-DWITH_LOGGING) $(if $(WITH_DCLOAD),-DDC_CHDIR=/pc) \ + $(if $(WITH_BEEPS),-DWITH_BEEPS) +FLAGS = -fpermissive -Wno-sign-compare -Wno-parentheses -Wno-maybe-uninitialized \ + -Wno-format -Wno-strict-aliasing -Wno-unused-variable \ + -Wno-unused-but-set-variable -Wno-write-strings \ + -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion \ + -Wno-multichar -Wno-unused-value -Wno-char-subscripts -Wno-reorder \ + -Wno-unused-function -Wno-class-memaccess -fno-permissive + +CPPFLAGS += $(INCLUDE) $(DEFINES) $(FLAGS) +CFLAGS += -std=gnu17 $(CPPFLAGS) +CXXFLAGS += -std=gnu++20 $(CPPFLAGS) \ No newline at end of file diff --git a/liberty/cuts.mk b/liberty/cuts.mk new file mode 100644 index 00000000..178e17b1 --- /dev/null +++ b/liberty/cuts.mk @@ -0,0 +1,286 @@ +CUTS_IFP = \ + A1_ss0.ifp \ + A2_pp.ifp \ + A3_ss.ifp \ + A4_pdr.ifp \ + A5_k2ft.ifp \ + A6_BAIT.ifp \ + A7_ETG.ifp \ + A8_PS.ifp \ + A9_ASD.ifp \ + BET.ifp \ + C1_TEX.ifp \ + D1_STOG.ifp \ + D2_KK.ifp \ + D3_ADO.ifp \ + D4_GTA2.ifp \ + D4_GTA.ifp \ + D5_ES.ifp \ + D6_STS.ifp \ + D7_MLD.ifp \ + el_ph1.ifp \ + el_ph2.ifp \ + el_ph3.ifp \ + el_ph4.ifp \ + END.ifp \ + hd_ph1.ifp \ + hd_ph2.ifp \ + hd_ph3.ifp \ + hd_ph4.ifp \ + hd_ph5.ifp \ + J0_dm2.ifp \ + J1_lfl.ifp \ + J2_kcl.ifp \ + J3_vh.ifp \ + J4_ETH.ifp \ + J5_dst.ifp \ + J6_tbj.ifp \ + JB.ifp \ + k1_kbo.ifp \ + k2_gis.ifp \ + k3_ds.ifp \ + k4_shi2.ifp \ + k4_shi.ifp \ + k5_sd.ifp \ + l1_lg.ifp \ + l2_dsb.ifp \ + l3_dm.ifp \ + l4_pap.ifp \ + l5_tfb.ifp \ + mt_ph1.ifp \ + mt_ph2.ifp \ + mt_ph3.ifp \ + mt_ph4.ifp \ + R0_PDR2.ifp \ + R1_SW.ifp \ + R2_AP.ifp \ + R3_ED.ifp \ + R4_GF.ifp \ + R5_PB.ifp \ + R6_MM.ifp \ + S0_MAS.ifp \ + S1_PF.ifp \ + S2_CTG2.ifp \ + S2_CTG.ifp \ + S3_RTC.ifp \ + S4_BDBA.ifp \ + S4_BDBB.ifp \ + S4_BDBD.ifp \ + S5_LRQb.ifp \ + S5_LRQc.ifp \ + S5_LRQ.ifp \ + t1_tol.ifp \ + t2_tpu.ifp \ + t3_mas.ifp \ + t4_tat.ifp \ + t5_bf.ifp \ + yd_ph1.ifp \ + yd_ph2.ifp \ + yd_ph3.ifp \ + yd_ph4.ifp + +CUTS_MISC = \ + a1_ss0_asuka.anm \ + a1_ss0.dat \ + a1_ss0_maria.anm \ + a1_ss0_player.anm \ + a2_pp.dat \ + a2_pp_player.anm \ + a3_ss_asuka.anm \ + a3_ss.dat \ + a3_ss_kenji.anm \ + a3_ss_player.anm \ + a4_pdr_asuka.anm \ + a4_pdr.dat \ + a4_pdr_player.anm \ + a5_k2ft.dat \ + a5_k2ft_player.anm \ + a6_bait_asuka.anm \ + a6_bait.dat \ + a6_bait_player.anm \ + a7_etg_asuka.anm \ + a7_etg.dat \ + a7_etg_player.anm \ + a8_ps_asuka.anm \ + A8_PS.dat \ + a8_ps_maria.anm \ + a8_ps_player.anm \ + a9_asd.dat \ + a9_asd_player.anm \ + bet_cat.anm \ + bet.dat \ + c1_tex_cat.anm \ + c1_tex.dat \ + d1_stog.dat \ + d1_stog_love.anm \ + d1_stog_player.anm \ + D2_KK.dat \ + d2_kk_love.anm \ + d2_kk_player.anm \ + d3_ado.dat \ + d3_ado_love.anm \ + d3_ado_player.anm \ + d4_gta2_asuka.anm \ + d4_gta2_cat.anm \ + d4_gta2.dat \ + d4_gta2_miguel.anm \ + D4_GTA.dat \ + d4_gta_love.anm \ + d4_gta_player.anm \ + d5_es.dat \ + d5_es_love.anm \ + d5_es_player.anm \ + d6_sts.dat \ + d6_sts_love.anm \ + d6_sts_player.anm \ + d7_mld.dat \ + el_ph1.dat \ + el_ph2.dat \ + el_ph3.dat \ + el_ph4.dat \ + end.dat \ + hd_ph1.dat \ + hd_ph2.dat \ + hd_ph3.dat \ + hd_ph4.dat \ + hd_ph5.dat \ + intro.dat \ + J0_dm2.dat \ + j0_dm2_joey.anm \ + j0_dm2_misty.anm \ + j0_dm2_player.anm \ + J1_lfl.dat \ + j1_lfl_joey.anm \ + j1_lfl_misty.anm \ + j1_lfl_player.anm \ + J2_KCL.dat \ + j2_kcl_joey.anm \ + j2_kcl_player.anm \ + j3_vh.dat \ + j3_vh_joey.anm \ + j3_vh_player.anm \ + J4_ETH.dat \ + j4_eth_joey.anm \ + j4_eth_player.anm \ + j4_eth_tony.anm \ + J5_dst.dat \ + j5_dst_joey.anm \ + j5_dst_player.anm \ + j6_tbj.dat \ + j6_tbj_joey.anm \ + j6_tbj_player.anm \ + jb_col1.anm \ + jb.dat \ + K1_KBO.dat \ + k1_kbo_kenji.anm \ + k1_kbo_player.anm \ + K2_GIS.dat \ + k2_gis_kenji.anm \ + k2_gis_player.anm \ + k3_ds.dat \ + k3_ds_kenji.anm \ + k3_ds_player.anm \ + k4_shi2.dat \ + k4_shi2_keeper.anm \ + k4_shi2_player.anm \ + k4_shi.dat \ + k4_shi_kenji.anm \ + k4_shi_player.anm \ + K5_SD.dat \ + k5_sd_kenji.anm \ + k5_sd_player.anm \ + L1_LG.dat \ + l1_lg_eight.anm \ + l1_lg_luigi.anm \ + l1_lg_micky.anm \ + l1_lg_player.anm \ + l2_dsb.dat \ + l2_DSB_micky.anm \ + l2_dsb_player.anm \ + L3_DM.dat \ + l3_dm_luigi.anm \ + l3_dm_micky.anm \ + l3_dm_player.anm \ + l4_pap.dat \ + l4_pap_luigi.anm \ + l4_pap_player.anm \ + L5_TFB.dat \ + l5_tfb_luigi.anm \ + l5_tfb_micky.anm \ + l5_tfb_player.anm \ + mt_ph1.dat \ + mt_ph2.dat \ + mt_ph3.dat \ + mt_ph4.dat \ + R0_PDR2.dat \ + R0_pdr2_player.anm \ + R0_pdr2_ray.anm \ + R1_SW.dat \ + R1_sw_player.anm \ + R1_sw_ray.anm \ + R2_AP.dat \ + R2_ap_player.anm \ + R2_ap_ray.anm \ + r3_ed.dat \ + R3_ed_player.anm \ + R3_ed_ray.anm \ + R4_GF.dat \ + R4_gf_player.anm \ + R4_gf_ray.anm \ + R5_PB.dat \ + R5_pb_player.anm \ + R5_pb_ray.anm \ + R6_MM.dat \ + R6_mm_player.anm \ + R6_mm_ray.anm \ + S0_MAS.dat \ + S0_mas_frank.anm \ + s0_mas_joey.anm \ + s0_mas_luigi.anm \ + s0_mas_player.anm \ + S0_mas_tony.anm \ + S1_PF.dat \ + S1_pf_frank.anm \ + S1_pf_maria.anm \ + S1_pf_player.anm \ + S2_ctg2_cat.anm \ + S2_ctg2_curly.anm \ + s2_ctg2.dat \ + S2_ctg2_miguel.anm \ + S2_CTG.dat \ + s2_ctg_frank.anm \ + S3_rtc.dat \ + S3_rtc_frank.anm \ + S4_BDBA.dat \ + S4_BDBA_eight.anm \ + S4_BDBA_player.anm \ + S4_BDBB.dat \ + S4_BDBB_eight.anm \ + S4_BDBB_player.anm \ + S4_BDBD.dat \ + S5_lrqb_asuka.anm \ + s5_lrqb.dat \ + S5_lrqb_maria.anm \ + S5_lrqc_asuka.anm \ + S5_LRQC.dat \ + S5_lrqc_maria.anm \ + S5_lrqc_player.anm \ + S5_LRQ.dat \ + S5_lrq_frank.anm \ + S5_lrq_player.anm \ + T1_TOL.dat \ + t1_tol_player.anm \ + t1_tol_tony.anm \ + T2_TPU.dat \ + t2_tpu_player.anm \ + T3_MAS.dat \ + t3_mas_player.anm \ + T4_TAT.dat \ + t4_tat_player.anm \ + T5_BF.dat \ + t5_bf_player.anm \ + t5_bf_tony.anm \ + yd_ph1.dat \ + yd_ph2.dat \ + yd_ph3.dat \ + yd_ph4.dat \ No newline at end of file diff --git a/dreamcast/gta3files.mk b/liberty/gta3files.mk similarity index 99% rename from dreamcast/gta3files.mk rename to liberty/gta3files.mk index f2aeaaf8..ee139b4f 100644 --- a/dreamcast/gta3files.mk +++ b/liberty/gta3files.mk @@ -1,3 +1,5 @@ +# models/coll/peds.col # not actually used + MISC_FILES = \ anim/cuts.dir \ anim/cuts.img \ @@ -110,7 +112,6 @@ MISC_FILES = \ models/Coll/commer.col \ models/Coll/generic.col \ models/Coll/indust.col \ - models/Coll/peds.col \ models/Coll/suburb.col \ models/Coll/vehicles.col \ models/Coll/weapons.col \ diff --git a/liberty/ip.txt b/liberty/ip.txt new file mode 100644 index 00000000..8860f97b --- /dev/null +++ b/liberty/ip.txt @@ -0,0 +1,9 @@ +Device Info : CD-ROM1/1 +Area Symbols : JUE +Peripherals : E000F10 +Product No : DCA-LBRT +Version : V2.000 +Release Date : 20250401 +Boot Filename : 1ST_READ.BIN +SW Maker Name : the gang +Game Title : dca-liberty diff --git a/dreamcast/modlist.mk b/liberty/modlist.mk similarity index 100% rename from dreamcast/modlist.mk rename to liberty/modlist.mk diff --git a/dreamcast/mp3list.mk b/liberty/mp3list.mk similarity index 100% rename from dreamcast/mp3list.mk rename to liberty/mp3list.mk diff --git a/dreamcast/sfxlist.mk b/liberty/sfxlist.mk similarity index 100% rename from dreamcast/sfxlist.mk rename to liberty/sfxlist.mk diff --git a/dreamcast/sfxlooplist.mk b/liberty/sfxlooplist.mk similarity index 91% rename from dreamcast/sfxlooplist.mk rename to liberty/sfxlooplist.mk index 2e579a85..21dd1225 100644 --- a/dreamcast/sfxlooplist.mk +++ b/liberty/sfxlooplist.mk @@ -1,4 +1,4 @@ -SFX_LOOP_WAV = \ +src/liberty/sfxlooplist.mkSFX_LOOP_WAV = \ sfx_0_loop.wav \ sfx_185_loop.wav \ sfx_19_loop.wav \ diff --git a/dreamcast/sim.mk b/liberty/sim.mk similarity index 57% rename from dreamcast/sim.mk rename to liberty/sim.mk index 9a2b78f4..75334842 100644 --- a/dreamcast/sim.mk +++ b/liberty/sim.mk @@ -1,14 +1,11 @@ -TARGET ?= dca3-sim.elf - -IS_MAC := $(shell uname -s | grep -i "darwin" > /dev/null && echo "yes" || echo "no") - +TARGET ?= dca-liberty-sim.elf all: $(TARGET) include common.mk OBJS = $(RE3_OBJS) $(RW_OBJS) \ - ../src/audio/sampman_null.o + ../src/liberty/audio/sampman_null.o OBJS_SIM=$(OBJS:.o=.sim.o) \ ../vendor/koshle/hlekos.sim.o \ @@ -37,31 +34,17 @@ DEPS_SIM=$(DEPS_SIM1:.o3=.d) CXXFLAGS+= -MMD -MP -ifeq ($(IS_MAC), yes) %.sim.o: %.c - $(CC) -c -O0 -g -fno-pic -no-pie -o $@ $(CFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 -DMACOS64 $< + $(CC) -c -O0 -g -fno-pic -no-pie -o $@ $(CFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -DMACOS64 $< %.sim.o: %.cpp - $(CXX) -c -O0 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 -DMACOS64 $< + $(CXX) -c -O0 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -DMACOS64 $< %.sim.o3: %.cpp - $(CXX) -c -O3 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 -DMACOS64 $< -else -# Using sse2 here for valgrind compatibility -%.sim.o: %.c - $(CC) -msse2 -mfpmath=sse -c -O0 -g -fno-pic -no-pie -o $@ $(CFLAGS) -I../vendor/koshle -I../vendor/emu -m32 -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 $< -%.sim.o: %.cpp - $(CXX) -msse2 -mfpmath=sse -c -O0 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -m32 -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 $< -%.sim.o3: %.cpp - $(CXX) -msse2 -mfpmath=sse -c -O3 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -m32 -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -D_FILE_OFFSET_BITS=64 $< -endif + $(CXX) -c -O3 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -DMACOS64 $< clean: -rm -f $(OBJS_SIM) $(TARGET) -ifeq ($(IS_MAC), yes) $(TARGET): $(OBJS_SIM) $(CXX) -fno-pic -no-pie -o $(TARGET) $(OBJS_SIM) -lX11 -else -$(TARGET): $(OBJS_SIM) - $(CXX) -m32 -fno-pic -no-pie -o $(TARGET) $(OBJS_SIM) -lX11 -endif + -include $(DEPS_SIM) \ No newline at end of file diff --git a/dreamcast/texlist.mk b/liberty/texlist.mk similarity index 100% rename from dreamcast/texlist.mk rename to liberty/texlist.mk diff --git a/dreamcast/wavlist.mk b/liberty/wavlist.mk similarity index 100% rename from dreamcast/wavlist.mk rename to liberty/wavlist.mk diff --git a/miami/GTAVCSF8.b b/miami/GTAVCSF8.b new file mode 100644 index 0000000000000000000000000000000000000000..13e45eedb53372430408e90be9592295da625b93 GIT binary patch literal 201828 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 literal 0 HcmV?d00001 diff --git a/miami/Makefile b/miami/Makefile new file mode 100644 index 00000000..584a2543 --- /dev/null +++ b/miami/Makefile @@ -0,0 +1,668 @@ +# +# Basic KallistiOS skeleton / test program +# Copyright (C)2001-2004 Megan Potter +# + +# Put the filename of the output binary here +PROJECT_NAME = dca-miami +TEAM_NAME ="the gang" +DISC_SERIAL = DCA-MIAM +RELEASE_DATE = 20250401 +TARGET ?= dca-miami.elf + +HAVE_CDI4DC := $(shell which cdi4dc > /dev/null 2>&1 && echo "yes" || echo "no") +IS_MAC := $(shell uname -s | grep -i "darwin" > /dev/null && echo "yes" || echo "no") + +MOD_NAME?= + +GTA_DIR?=../../miami +GTA_GAMEFILES_LOOSE_DIR?=../gamefiles/miami +GTA_MOD_DIR?=../../miami_mod$(MOD_NAME) +GTA_MOD_IMG_DIR?=$(GTA_MOD_DIR)/img +GTA_MOD_CUTS_DIR=$(GTA_MOD_DIR)/cuts +GTA_MOD_SFX_DIR?=$(GTA_MOD_DIR)/sfx +GTA_MOD_LOOSE_DIR?=$(GTA_MOD_DIR)/loose + +REPACK_DIR?=repack-data +REPACK_GTA_DIR?=$(REPACK_DIR)/miami +REPACK_IMG_ORIG_DIR?=$(REPACK_DIR)/miami-img-orig +REPACK_IMG_DC_DIR?=$(REPACK_DIR)/miami-img-dc +REPACK_SFX_ORIG_DIR?=$(REPACK_DIR)/miami-sfx-orig +REPACK_SFX_DC_DIR?=$(REPACK_DIR)/miami-sfx-dc +REPACK_STREAM_DECODED_DIR?=$(REPACK_DIR)/miami-stream-decoded +REPACK_STREAM_DEST_DIR=$(REPACK_GTA_DIR)/stream +REPACK_CUTS_ORIG_DIR?=$(REPACK_DIR)/miami-cuts-orig +REPACK_CUTS_DC_DIR?=$(REPACK_DIR)/miami-cuts-dc + +LIBS := +TEXCONV_FLAGS := + +ifeq ($(IS_MAC), yes) +TEXCONV_FLAGS += -DMACOS64 +endif + +AUDIO_STREAM_OPTION=-t +MKDCDISC_PAD_OPTION=-N +ifeq ($(FOR_DISC),1) +AUDIO_STREAM_OPTION=-q +MKDCDISC_PAD_OPTION= +else ifeq ($(FOR_DISC),2) +AUDIO_STREAM_OPTION=-m +MKDCDISC_PAD_OPTION= +endif + +all: $(TARGET) + +include common.mk + +OBJS = $(RE3_OBJS) $(RW_OBJS) \ + ../src/miami/audio/sampman_dc.o \ + ../src/common/prof/profiler.o + +OBJS_TEXCONV = $(RW_OBJS:.o=.texconv.o) +OBJS_TEXCONV += \ + ../vendor/koshle/hlekos.texconv.o \ + ../vendor/koshle/hlepvr_mem.texconv.o \ + ../vendor/koshle/hlepvr_prim.texconv.o \ + ../vendor/koshle/hlepvr_scene.texconv.o \ + ../vendor/koshle/hlepvr_misc.texconv.o \ + ../vendor/koshle/hlepvr_init_term.texconv.o \ + ../vendor/koshle/hlepvr_buffers.texconv.o \ + ../vendor/koshle/hlepvr_irq.texconv.o \ + ../vendor/koshle/hlematrix3d.texconv.o \ + ../vendor/librw/src/dc/vq.texconv.o \ + ../src/miami/fakerw/fake.texconv.o \ + ../src/miami/skel/crossplatform.texconv.o \ + ../src/miami/rw/TxdStore.texconv.o \ + ../src/miami/rw/RwHelper.texconv.o \ + ../src/tools/texconv.texconv.o \ + ../vendor/TriStripper/src/connectivity_graph.texconv.o \ + ../vendor/TriStripper/src/policy.texconv.o \ + ../vendor/TriStripper/src/tri_stripper.texconv.o \ + ../src/miami/rw/VisibilityPlugins.texconv.o \ + ../src/miami/rw/NodeName.texconv.o \ + ../src/miami/animation/RpAnimBlend.texconv.o \ + ../src/miami/animation/Bones.texconv.o \ + ../src/miami/animation/AnimBlendAssociation.texconv.o \ + ../src/miami/animation/AnimBlendNode.texconv.o \ + ../src/miami/animation/AnimBlendClumpData.texconv.o \ + ../src/miami/rw/MemoryMgr.texconv.o \ + ../src/miami/math/Quaternion.texconv.o \ + ../vendor/librw/src/ps2-x/ps2.texconv.o \ + ../vendor/librw/src/ps2-x/ps2device.texconv.o \ + ../vendor/librw/src/ps2-x/ps2raster.texconv.o \ + ../vendor/librw/src/ps2-x/ps2skin.texconv.o \ + ../vendor/librw/src/d3d-x/d3d.texconv.o \ + ../vendor/librw/src/d3d-x/d3d8.texconv.o \ + ../vendor/librw/src/d3d-x/d3d8render.texconv.o \ + ../vendor/librw/src/bmp.texconv.o \ + ../vendor/librw/src/png.texconv.o \ + ../vendor/librw/src/lodepng/lodepng.texconv.o + +# Add compilation units to this list to explicity compile them with +# -O3 optimizations, while the rest get the default (-Os) treatment +# to conserve RAM. +OBJS_O3 = \ + ../vendor/librw/src/dc/rwdc.o \ + ../src/miami/core/World.o \ + ../src/miami/collision/Collision.o \ + ../src/miami/math/math.o \ + ../src/miami/math/Matrix.o \ + ../src/miami/math/Quaternion.o \ + ../src/miami/math/Rect.o \ + ../src/miami/math/Vector.o \ + ../src/vendor/librw/src/base.o \ + ../src/miami/renderer/Shadows.o + +OBJS_NO_FAST_MATH = \ + ../src/miami/core/Cam.o \ + ../src/miami/core/Camera.o \ + ../src/miami/vehicles/Bike.o \ + ../src/miami/renderer/Particle.o + +KOS_CPPFLAGS += -fbuiltin -ffast-math -ffp-contract=fast \ + -mfsrra -mfsca + +ifdef KOS_BASE +include $(KOS_BASE)/Makefile.rules +else +$(warning "KOS_BASE is not set. Only prebuilt targets will work.") +endif + +DEPS = $(OBJS:.o=.d) $(OBJS_TEXCONV:.o:.d) + +CXXFLAGS += $(if $(WITH_32MB),-O3,-Os) \ + $(if $(WITH_IDE),-DWITH_IDE) \ + $(if $(WITH_PROF),-DWITH_PROF=\"$(WITH_PROF)\") \ + -MMD -MP -ffunction-sections -fdata-sections -ffast-math \ + -fmerge-all-constants -fomit-frame-pointer -ml -std=gnu++20 \ + -fno-exceptions -fno-rtti -fipa-pta -Wno-write-strings \ + -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion \ + -Wno-multichar -Wno-unused-value -Wno-char-subscripts -Wno-reorder \ + -Wno-unused-function -Wno-class-memaccess -fno-permissive \ + -fno-asynchronous-unwind-tables -fno-enforce-eh-specs -fno-non-call-exceptions \ + -fno-strict-aliasing -fwrapv -Wno-unknown-pragmas + +clean-texconv: + -rm -f $(OBJS_TEXCONV) + -rm -f texconv + +clean-pvrtex: + $(MAKE) -C ../vendor/pvrtex clean + -rm -f ../vendor/pvrtex/pvrtex + +clean-objs: + -rm -f $(OBJS) + +clean: + -rm -f $(OBJS) + -rm -f $(OBJS_TEXCONV) + -rm -f $(TARGET) + -rm -f $(TARGET).bin + -rm -f 1ST_READ.BIN + -rm -f IP.BIN + -rm -f $(PROJECT_NAME).iso + -rm -f $(PROJECT_NAME).ds.iso + -rm -f $(PROJECT_NAME).cdi + -rm -f $(DEPS) + -rm -rf $(REPACK_DIR) + -rm -rf analyze-profile + +$(OBJS_O3): %.o: %.cpp + kos-c++ $(CXXFLAGS) $(CPPFLAGS) -O3 -c $< -o $@ + +$(OBJS_NO_FAST_MATH): %.o: %.cpp + kos-c++ $(CXXFLAGS) $(CPPFLAGS) -O3 -c $< -o $@ -fno-fast-math + +$(TARGET): $(OBJS) + kos-c++ -o $(TARGET) $(OBJS) -Wl,--gc-sections -Wl,--as-needed -Wl,-Map,output.map \ + $(if $(WITH_IDE),-lkosfat) $(if $(WITH_SD),-lkosfat) -Wl,--build-id=sha1 + @echo && echo && echo "*** Build Completed Successfully ($(TARGET)) ***" && echo && echo + +run: $(TARGET) + $(KOS_LOADER) $(TARGET) + +$(REPACK_GTA_DIR)/GTAVCSF8.b: GTAVCSF8.b + mkdir -p $(@D) + cp $< $@ + +1ST_READ.BIN: $(TARGET) + rm -f $(TARGET).bin + rm -f 1ST_READ.BIN + kos-objcopy -R .stack -O binary $(TARGET) $(TARGET).bin + $(KOS_BASE)/utils/scramble/scramble $(TARGET).bin 1ST_READ.BIN + +.PHONY: pvrtex + +pvrtex: + $(MAKE) -C ../vendor/pvrtex + +IP.BIN: + rm -f IP.BIN + $(KOS_BASE)/utils/makeip/makeip ip.txt IP.BIN + +$(PROJECT_NAME).iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTAVCSF8.b + rm -f $(PROJECT_NAME).iso + rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN + cp 1ST_READ.BIN $(REPACK_GTA_DIR) + mkisofs -C 0,11702 -V $(PROJECT_NAME) -G IP.BIN -r -J -l -o $(PROJECT_NAME).iso $(REPACK_GTA_DIR) + +$(PROJECT_NAME)-no-repack.iso: IP.BIN 1ST_READ.BIN $(REPACK_GTA_DIR)/GTAVCSF8.b + rm -f $(PROJECT_NAME)-no-repack.iso + rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN + cp 1ST_READ.BIN $(REPACK_GTA_DIR) + mkisofs -C 0,11702 -V $(PROJECT_NAME) -G IP.BIN -r -J -l -o $(PROJECT_NAME)-no-repack.iso $(REPACK_GTA_DIR) + + +$(PROJECT_NAME).ds.iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTAVCSF8.b + rm -f $(PROJECT_NAME).ds.iso + rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN + cp $(TARGET).bin $(REPACK_GTA_DIR)/1ST_READ.BIN + mkisofs -V $(PROJECT_NAME) -G IP.BIN -r -J -l -o $(PROJECT_NAME).ds.iso $(REPACK_GTA_DIR) + +1ST_READ_PREBUILT.BIN: + kos-objcopy -R .stack -O binary $(TARGET) $(TARGET)-prebuilt.bin + $(KOS_BASE)/utils/scramble/scramble $(TARGET)-prebuilt.bin 1ST_READ_PREBUILT.BIN + mkdir -p $(REPACK_GTA_DIR) + +$(PROJECT_NAME)-prebuilt.iso: IP.BIN 1ST_READ_PREBUILT.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTAVCSF8.b + rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN + cp 1ST_READ_PREBUILT.BIN $(REPACK_GTA_DIR)/1ST_READ.BIN + mkisofs -C 0,11702 -V $(PROJECT_NAME) -G IP.BIN -r -J -l -o $(PROJECT_NAME).iso $(REPACK_GTA_DIR) + +ifeq ($(HAVE_CDI4DC), yes) +$(PROJECT_NAME).cdi: $(PROJECT_NAME).iso + cdi4dc $(PROJECT_NAME).iso $(PROJECT_NAME).cdi > cdi.log + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo + +$(PROJECT_NAME)-no-repack.cdi: $(PROJECT_NAME)-no-repack.iso + cdi4dc $(PROJECT_NAME)-no-repack.iso $(PROJECT_NAME)-no-repack.cdi > cdi.log + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo + +$(PROJECT_NAME)-prebuilt.cdi: $(PROJECT_NAME)-prebuilt.iso + cdi4dc $(PROJECT_NAME).iso $(PROJECT_NAME).cdi > cdi.log + rm 1ST_READ_PREBUILT.BIN + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo +else +$(PROJECT_NAME).cdi: $(TARGET) $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTAVCSF8.b + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME).cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n $(PROJECT_NAME) -a $(TEAM_NAME) -s $(DISC_SERIAL) -r $(RELEASE_DATE) + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo + +$(PROJECT_NAME)-no-repack.cdi: $(TARGET) $(REPACK_GTA_DIR)/GTAVCSF8.b + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME)-no-repack.cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n $(PROJECT_NAME) -a $(TEAM_NAME) -s $(DISC_SERIAL) -r $(RELEASE_DATE) + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo + +$(PROJECT_NAME)-prebuilt.cdi: $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTAVCSF8.b + mkdcdisc -e $(TARGET) -o $(PROJECT_NAME).cdi -d $(REPACK_GTA_DIR)/ $(MKDCDISC_PAD_OPTION) -n $(PROJECT_NAME) -a $(TEAM_NAME) -s $(DISC_SERIAL) -r $(RELEASE_DATE) + @echo && echo && echo "*** CDI Baked Successfully ($@) ***" && echo && echo +endif + +cdi: $(PROJECT_NAME).cdi + +cdi-no-repack: $(PROJECT_NAME)-no-repack.cdi + +dsiso: $(PROJECT_NAME).ds.iso + +cdi-prebuilt: $(PROJECT_NAME)-prebuilt.cdi + +sim: $(REPACK_DIR)/repacked + $(MAKE) -f sim.mk + +run-flycast: + flycast ./$(PROJECT_NAME).cdi + +repack-dc: $(REPACK_DIR)/repacked + +gprof: + @sh-elf-gprof $(TARGET) $(REPACK_DIR)/kernel_gmon_1.out > gprof.out + @cat gprof.out | gprof2dot | dot -Tpng -o $(TARGET)-kernel.png + @-rm -rf gprof.out + @echo "\033[42m Profiling data saved to $(TARGET)-kernel.png \033[0m" + +# tools +imgtool: ../src/tools/imgtool.cpp + $(CXX) -std=c++17 -o $@ -Og $< + +extract-sfx: ../src/tools/extract-sfx.cpp + $(CXX) -std=c++17 -o $@ -Og $< + +pack-sfx: ../src/tools/pack-sfx.cpp + $(CXX) -std=c++17 -o $@ -Og $< + +adf2mp3: ../src/tools/adf2mp3.cpp + $(CXX) -o $@ -O3 -g $< + +aud2adpcm: ../src/tools/aud2adpcm.c + $(CC) -o $@ -O3 -g $< -I../vendor/minimp3 + +texconv: $(OBJS_TEXCONV) | pvrtex # You'll have to rebuild pvrtex manually if you change it + $(CXX) -o $@ $(OBJS_TEXCONV) + +%.texconv.o: %.cpp + $(CXX) -std=c++2a -c -O0 -g -MMD -MP -o $@ -I../vendor/koshle -I../vendor/librw/src $(INCLUDE) -I../vendor/emu -I../vendor/crypto -I../vendor/TriStripper/include $(DEFINES) -DDC_TEXCONV -DDC_SIM $(TEXCONV_FLAGS) $< + +animtool: ../src/tools/animtool.cpp + $(CXX) -std=c++17 -o $@ -g -O0 $< + +coltool: ../src/tools/coltool.cpp + $(CXX) -std=c++17 -o $@ -g -O0 $< + +streamheaderpack: ../src/tools/streamheaderpack.cpp + $(CXX) -std=c++17 -o $@ -g -O0 $< + +-include $(DEPS) + +#### Repacking #### + +TXD_OPTS_fonts = 256 256 +TXD_OPTS_fronten1 = 512 512 +TXD_OPTS_fronten2 = 512 512 +# TXD_OPTS_generic = 512 512 +# TXD_OPTS_wheels = 512 512 +TXD_OPTS_hud = 128 128 +TXD_OPTS_INTRO = 512 512 +# TXD_OPTS_MISC = 512 512 +# TXD_OPTS_particle = 512 512 +TXD_OPTS_intro1 = 512 512 +TXD_OPTS_intro2 = 512 512 +TXD_OPTS_INTRO3 = 512 512 +TXD_OPTS_intro4 = 512 512 +TXD_OPTS_LOADSC0 = 512 512 +TXD_OPTS_LOADSC1 = 512 512 +TXD_OPTS_LOADSC10 = 512 512 +TXD_OPTS_LOADSC11 = 512 512 +TXD_OPTS_LOADSC12 = 512 512 +TXD_OPTS_LOADSC13 = 512 512 +TXD_OPTS_LOADSC2 = 512 512 +TXD_OPTS_LOADSC3 = 512 512 +TXD_OPTS_LOADSC4 = 512 512 +TXD_OPTS_LOADSC5 = 512 512 +TXD_OPTS_LOADSC6 = 512 512 +TXD_OPTS_LOADSC7 = 512 512 +TXD_OPTS_LOADSC8 = 512 512 +TXD_OPTS_LOADSC9 = 512 512 +TXD_OPTS_NEWS = 128 128 +TXD_OPTS_outro = 512 512 +TXD_OPTS_SPLASH1 = 512 512 +TXD_OPTS_SPLASH2 = 512 512 +TXD_OPTS_SPLASH3 = 512 512 + +DEFAULT_RES = 512 + +PVR_ENCODER ?= PVRTEX +TEXTURE_DOWNSAMPLE_TXD ?= NONE +TEXTURE_DOWNSAMPLE_IMG ?= HALF + +-include texlist.mk +-include modlist.mk +-include imgmisc.mk +-include gta3files.mk +-include sfxlist.mk +-include sfxlooplist.mk +-include wavlist.mk +-include mp3list.mk +-include cuts.mk + +IMG_TEXTURES_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_TEXTURES)) +IMG_MODELS_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_MODELS)) +IMG_IFP_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_IFP)) +IMG_COL_DC = $(addprefix $(REPACK_IMG_DC_DIR)/, $(IMG_COL)) + +CUTS_IFP_DC = $(addprefix $(REPACK_CUTS_DC_DIR)/, $(CUTS_IFP)) +CUTS_MISC_DC = $(addprefix $(REPACK_CUTS_DC_DIR)/, $(CUTS_MISC)) + +LOOSE_FILES_DC = $(addprefix $(REPACK_GTA_DIR)/, $(MISC_FILES)) + +SFX_DC_DIR = $(REPACK_GTA_DIR)/sfx +SFX_DC_RAW = $(SFX_DC_DIR)/sfx_all.raw +SFX_DC_DSC = $(SFX_DC_DIR)/sfx_all.dsc +STREAM_ADPCM_DC = $(addprefix $(REPACK_STREAM_DEST_DIR)/, $(STREAM_WAV:.wav=.APM)) \ + $(addprefix $(REPACK_STREAM_DEST_DIR)/, $(STREAM_MP3:.mp3=.APM)) \ + $(addprefix $(REPACK_STREAM_DEST_DIR)/, $(STREAM_ADF:.adf=.APM)) + +IMG_TEXTURES_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_TEXTURES)) +IMG_MODELS_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_MODELS)) +IMG_IFP_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_IFP)) +IMG_COL_ORIG = $(addprefix $(REPACK_IMG_ORIG_DIR)/, $(IMG_COL)) + +CUTS_IFP_ORIG = $(addprefix $(REPACK_CUTS_ORIG_DIR)/, $(CUTS_IFP)) +CUTS_MISC_ORIG = $(addprefix $(REPACK_CUTS_ORIG_DIR)/, $(CUTS_MISC)) + +SFX_ORIG = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_WAV)) +SFX_ORIG_LOOP = $(addprefix $(REPACK_SFX_ORIG_DIR)/, $(SFX_LOOP_WAV)) +SFX_REPACK_DC_WAV = $(addprefix $(REPACK_SFX_DC_DIR)/, $(SFX_WAV) $(SFX_LOOP_WAV)) +SFX_REPACK_DC = $(SFX_REPACK_DC_WAV:.wav=.pcm) +STREAM_ADF_DECODED = $(addprefix $(REPACK_STREAM_DECODED_DIR)/, $(STREAM_ADF:.adf=.mp3)) + +.PRECIOUS: $(SFX_ORIG) $(SFX_REPACK_DC) $(STREAM_ADF_DECODED) + +$(REPACK_DIR)/repacked: $(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir $(REPACK_GTA_DIR)/anim/cuts.img $(REPACK_GTA_DIR)/anim/cuts.dir $(LOOSE_FILES_DC) $(STREAM_ADPCM_DC) $(SFX_DC_RAW) $(SFX_DC_DSC) streamheaderpack + mkdir -p $(@D) +# $(REPACK_GTA_DIR) needed as first argument as paths in the game prefix with stream/ + ./streamheaderpack miami $(REPACK_GTA_DIR) $(REPACK_STREAM_DEST_DIR)/hdr.bin + @git archive --format zip --output "$(REPACK_GTA_DIR)/DCA3-$(GIT_VERSION).zip" HEAD + @touch $@ + @echo && echo && echo "*** Repack Completed Successfully ($(PROJECT_NAME)) ***" && echo && echo + +$(REPACK_DIR)/unpacked: imgtool $(GTA_DIR)/models/gta3.img $(GTA_DIR)/models/gta3.dir + mkdir -p $(@D) + ./imgtool unpack "$(GTA_DIR)/models/gta3" "$(REPACK_IMG_ORIG_DIR)" + @touch $@ + +$(IMG_TEXTURES_ORIG) $(IMG_MODELS_ORIG) $(IMG_IFP_ORIG) $(IMG_COL_ORIG): $(REPACK_DIR)/unpacked + @test -f $@ + @touch $@ + +$(REPACK_DIR)/unpacked-cuts: imgtool $(GTA_DIR)/anim/cuts.img $(GTA_DIR)/anim/cuts.dir + mkdir -p $(@D) + ./imgtool unpack "$(GTA_DIR)/anim/cuts" "$(REPACK_CUTS_ORIG_DIR)" + @touch $@ + +$(CUTS_IFP_ORIG) $(CUTS_MISC_ORIG): $(REPACK_DIR)/unpacked-cuts + @test -f $@ + @touch $@ + +# First try the mods img directory +$(REPACK_IMG_DC_DIR)/%.dff: $(GTA_MOD_IMG_DIR)/%.dff texconv + @mkdir -p $(@D) + ./texconv $< $@ +$(REPACK_IMG_DC_DIR)/%.DFF: $(GTA_MOD_IMG_DIR)/%.DFF texconv + @mkdir -p $(@D) + ./texconv $< $@ + +# if not, the extracted img directory +$(REPACK_IMG_DC_DIR)/%.dff: $(REPACK_IMG_ORIG_DIR)/%.dff texconv + @mkdir -p $(@D) + ./texconv $< $@ +$(REPACK_IMG_DC_DIR)/%.DFF: $(REPACK_IMG_ORIG_DIR)/%.DFF texconv + @mkdir -p $(@D) + ./texconv $< $@ + +# first try the mods img directory. NB, the textures are not resized here, unlike normal .img textures +$(REPACK_IMG_DC_DIR)/%.txd: $(GTA_MOD_IMG_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE +$(REPACK_IMG_DC_DIR)/%.TXD: $(GTA_MOD_IMG_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE + +# if not, the extracted img directory +$(REPACK_IMG_DC_DIR)/%.txd: $(REPACK_IMG_ORIG_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ $(DEFAULT_RES) $(DEFAULT_RES) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_IMG) +$(REPACK_IMG_DC_DIR)/%.TXD: $(REPACK_IMG_ORIG_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ $(DEFAULT_RES) $(DEFAULT_RES) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_IMG) + +# First try the mods img directory +$(REPACK_IMG_DC_DIR)/%.col: $(GTA_MOD_IMG_DIR)/%.col coltool + @mkdir -p $(@D) + ./coltool $< $@ +$(REPACK_IMG_DC_DIR)/%.ifp: $(GTA_MOD_IMG_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ + +# if not, the extracted img directory +$(REPACK_IMG_DC_DIR)/%.col: $(REPACK_IMG_ORIG_DIR)/%.col coltool + @mkdir -p $(@D) + ./coltool $< $@ +$(REPACK_IMG_DC_DIR)/%.ifp: $(REPACK_IMG_ORIG_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ + +### cuts +# First try the mods img directory +$(REPACK_CUTS_DC_DIR)/%.dat: $(GTA_MOD_CUTS_DIR)/%.dat + @mkdir -p $(@D) + cp $< $@ +$(REPACK_CUTS_DC_DIR)/%.ifp: $(GTA_MOD_CUTS_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ + +# if not, the extracted img directory +$(REPACK_CUTS_DC_DIR)/%.dat: $(REPACK_CUTS_ORIG_DIR)/%.dat + @mkdir -p $(@D) + cp $< $@ +$(REPACK_CUTS_DC_DIR)/%.ifp: $(REPACK_CUTS_ORIG_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ + + +# first try the mods loose directory +$(REPACK_GTA_DIR)/%.dff: $(GTA_MOD_LOOSE_DIR)/%.dff texconv + @mkdir -p $(@D) + ./texconv $< $@ +$(REPACK_GTA_DIR)/%.DFF: $(GTA_MOD_LOOSE_DIR)/%.DFF texconv + @mkdir -p $(@D) + ./texconv $< $@ + +#if not, the original files +$(REPACK_GTA_DIR)/%.dff: $(GTA_DIR)/%.dff texconv + @mkdir -p $(@D) + ./texconv $< $@ +$(REPACK_GTA_DIR)/%.DFF: $(GTA_DIR)/%.DFF texconv + @mkdir -p $(@D) + ./texconv $< $@ + +# first try the mods loose directory +# Note the mods loose directory is not resized, unlike the normal .txd textures +$(REPACK_GTA_DIR)/%.txd: $(GTA_MOD_LOOSE_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE +$(REPACK_GTA_DIR)/%.TXD: $(GTA_MOD_LOOSE_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ 1024 1024 -e $(PVR_ENCODER) -d NONE + +# then the gamefiles directory +$(REPACK_GTA_DIR)/%.txd: $(GTA_GAMEFILES_LOOSE_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) +$(REPACK_GTA_DIR)/%.TXD: $(GTA_GAMEFILES_LOOSE_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) + +# if not, the original files +$(REPACK_GTA_DIR)/%.txd: $(GTA_DIR)/%.txd texconv + @mkdir -p $(@D) + ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) +$(REPACK_GTA_DIR)/%.TXD: $(GTA_DIR)/%.TXD texconv + @mkdir -p $(@D) + ./texconv $< $@ $(TXD_OPTS_$(notdir $*)) -e $(PVR_ENCODER) -d $(TEXTURE_DOWNSAMPLE_TXD) + +# first try the mods loose directory +$(REPACK_GTA_DIR)/%.ifp: $(GTA_MOD_LOOSE_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ +$(REPACK_GTA_DIR)/%.IFP: $(GTA_MOD_LOOSE_DIR)/%.IFP animtool + @mkdir -p $(@D) + ./animtool $< $@ + +#if not, the original files +$(REPACK_GTA_DIR)/%.ifp: $(GTA_DIR)/%.ifp animtool + @mkdir -p $(@D) + ./animtool $< $@ +$(REPACK_GTA_DIR)/%.IFP: $(GTA_DIR)/%.IFP animtool + @mkdir -p $(@D) + ./animtool $< $@ + +# first try the mods loose directory +$(REPACK_GTA_DIR)/%.col: $(GTA_MOD_LOOSE_DIR)/%.col coltool + @mkdir -p $(@D) + ./coltool $< $@ +$(REPACK_GTA_DIR)/%.COL: $(GTA_MOD_LOOSE_DIR)/%.COL coltool + @mkdir -p $(@D) + ./coltool $< $@ + +#if not, the original files +$(REPACK_GTA_DIR)/%.col: $(GTA_DIR)/%.col coltool + @mkdir -p $(@D) + ./coltool $< $@ +$(REPACK_GTA_DIR)/%.COL: $(GTA_DIR)/%.COL coltool + @mkdir -p $(@D) + ./coltool $< $@ + +$(REPACK_DIR)/packed: $(IMG_TEXTURES_DC) $(IMG_MODELS_DC) $(IMG_IFP_DC) $(IMG_COL_DC) + mkdir -p $(@D) + mkdir -p "$(REPACK_GTA_DIR)/models/gta3" + ./imgtool pack "$(REPACK_GTA_DIR)/models/gta3" "$(REPACK_IMG_DC_DIR)" + @touch $@ + +$(REPACK_GTA_DIR)/models/gta3.img $(REPACK_GTA_DIR)/models/gta3.dir: $(REPACK_DIR)/packed + @touch $@ + +$(REPACK_DIR)/packed-cuts: $(CUTS_IFP_DC) $(CUTS_MISC_DC) + mkdir -p $(@D) + mkdir -p "$(REPACK_GTA_DIR)/anim/cuts" + ./imgtool pack "$(REPACK_GTA_DIR)/anim/cuts" "$(REPACK_CUTS_DC_DIR)" + @touch $@ + +$(REPACK_GTA_DIR)/anim/cuts.img $(REPACK_GTA_DIR)/anim/cuts.dir: $(REPACK_DIR)/packed-cuts + @touch $@ + +# sfx processing +$(REPACK_DIR)/unpacked-sfx: extract-sfx $(GTA_DIR)/Audio/sfx.SDT $(GTA_DIR)/Audio/sfx.RAW + mkdir -p $(@D) + mkdir -p "$(REPACK_SFX_ORIG_DIR)" + ./extract-sfx "$(GTA_DIR)/Audio/sfx.SDT" "$(GTA_DIR)/Audio/sfx.RAW" "$(REPACK_SFX_ORIG_DIR)" + @touch $@ + +$(SFX_ORIG): $(REPACK_DIR)/unpacked-sfx + @test -f $@ + @touch $@ + +$(SFX_ORIG_LOOP): $(SFX_ORIG) + @test -f $@ + @touch $@ + +# try first mods sfx directory +$(REPACK_SFX_DC_DIR)/%.pcm: $(GTA_MOD_SFX_DIR)/%.wav aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm -raw $< $@ + +# then original (extracted) sfx directory +$(REPACK_SFX_DC_DIR)/%.pcm: $(REPACK_SFX_ORIG_DIR)/%.wav aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm -raw $< $@ + +# stream processing + +# first try the mods loose directory +$(REPACK_STREAM_DEST_DIR)/%.APM: $(GTA_MOD_LOOSE_DIR)/Audio/%.wav aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +$(REPACK_STREAM_DECODED_DIR)/%.mp3: $(GTA_MOD_LOOSE_DIR)/Audio/%.adf adf2mp3 + @mkdir -p $(@D) + ./adf2mp3 $< $@ + +$(REPACK_STREAM_DEST_DIR)/%.APM: $(GTA_MOD_LOOSE_DIR)/Audio/%.mp3 aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +$(REPACK_STREAM_DEST_DIR)/%.APM: $(REPACK_STREAM_DECODED_DIR)/%.mp3 aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +# then original folder +$(REPACK_STREAM_DEST_DIR)/%.APM: $(GTA_DIR)/Audio/%.wav aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +$(REPACK_STREAM_DECODED_DIR)/%.mp3: $(GTA_DIR)/Audio/%.adf adf2mp3 + @mkdir -p $(@D) + ./adf2mp3 $< $@ + +$(REPACK_STREAM_DEST_DIR)/%.APM: $(GTA_DIR)/Audio/%.mp3 aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +$(REPACK_STREAM_DEST_DIR)/%.APM: $(REPACK_STREAM_DECODED_DIR)/%.mp3 aud2adpcm + @mkdir -p $(@D) + ./aud2adpcm $(AUDIO_STREAM_OPTION) $< $@ + +# Note: These are last so they have least priority, files should be processed if possible + +# first try mods dir +$(REPACK_GTA_DIR)/%: $(GTA_MOD_LOOSE_DIR)/% + @mkdir -p $(@D) + cp $< $@ + +# then gamefiles +$(REPACK_GTA_DIR)/%: $(GTA_GAMEFILES_LOOSE_DIR)/% + @mkdir -p $(@D) + cp $< $@ + +# then original files +$(REPACK_GTA_DIR)/%: $(GTA_DIR)/% + @mkdir -p $(@D) + cp $< $@ + +$(SFX_DC_RAW): pack-sfx $(SFX_REPACK_DC) + mkdir -p $(@D) + ./pack-sfx "$(GTA_DIR)/Audio/sfx.SDT" $(SFX_DC_RAW) $(SFX_DC_DSC) $(REPACK_SFX_DC_DIR) + + $(SFX_DC_DSC): $(SFX_DC_RAW) + ls -l $@ + +analyze-profile: analyze-profile.cpp + $(CXX) -std=c++17 -O3 analyze-profile.cpp -o analyze-profile diff --git a/miami/common.mk b/miami/common.mk new file mode 100644 index 00000000..552b958e --- /dev/null +++ b/miami/common.mk @@ -0,0 +1,416 @@ +GIT_VERSION := $(shell git describe --always --tags --long --dirty 2>/dev/null || echo "NO_GIT") +CI_JOB_ID ?= 00000000 + + +git-version/git-version.tmp: + @echo "Generating git-version.tmp with GIT_VERSION = \"$(GIT_VERSION)\"" + @mkdir -p git-version + @echo "#pragma once" > git-version/git-version.tmp + @echo "#ifndef VERSION_H" >> git-version/git-version.tmp + @echo "#define VERSION_H" >> git-version/git-version.tmp + @echo "#define GIT_VERSION \"$(GIT_VERSION)\"" >> git-version/git-version.tmp + @echo "#define CI_JOB_ID \"$(CI_JOB_ID)\"" >> git-version/git-version.tmp + @echo "#endif // VERSION_H" >> git-version/git-version.tmp + +git-version/git-version.h: git-version/git-version.tmp + @if [ ! -f git-version/git-version.h ] || ! cmp -s git-version/git-version.tmp git-version/git-version.h; then \ + echo "Updating git-version.h"; \ + cp git-version/git-version.tmp git-version/git-version.h; \ + else \ + echo "git-version.h is up to date. No change."; \ + fi + +.PHONY: git-version/git-version.tmp + +../src/miami/skel/dc/dc.cpp: git-version/git-version.h + + + +# List all of your C files here, but change the extension to ".o" +# Include "romdisk.o" if you want a rom disk. +RE3_OBJS = \ + ../src/miami/animation/AnimBlendAssocGroup.o \ + ../src/miami/animation/AnimBlendAssociation.o \ + ../src/miami/animation/AnimBlendClumpData.o \ + ../src/miami/animation/AnimBlendHierarchy.o \ + ../src/miami/animation/AnimBlendNode.o \ + ../src/miami/animation/AnimBlendSequence.o \ + ../src/miami/animation/AnimManager.o \ + ../src/miami/animation/Bones.o \ + ../src/miami/animation/CutsceneMgr.o \ + ../src/miami/animation/FrameUpdate.o \ + ../src/miami/animation/RpAnimBlend.o \ + \ + ../src/miami/buildings/Building.o \ + ../src/miami/buildings/Treadable.o \ + \ + ../src/miami/collision/ColBox.o \ + ../src/miami/collision/ColLine.o \ + ../src/miami/collision/Collision.o \ + ../src/miami/collision/ColModel.o \ + ../src/miami/collision/ColPoint.o \ + ../src/miami/collision/ColSphere.o \ + ../src/miami/collision/ColStore.o \ + ../src/miami/collision/ColTriangle.o \ + ../src/miami/collision/TempColModels.o \ + ../src/miami/collision/VuCollision.o \ + \ + ../src/miami/control/AutoPilot.o \ + ../src/miami/control/Bridge.o \ + ../src/miami/control/CarAI.o \ + ../src/miami/control/CarCtrl.o \ + ../src/miami/control/Curves.o \ + ../src/miami/control/Darkel.o \ + ../src/miami/control/GameLogic.o \ + ../src/miami/control/Garages.o \ + ../src/miami/control/NameGrid.o \ + ../src/miami/control/OnscreenTimer.o \ + ../src/miami/control/PathFind.o \ + ../src/miami/control/Phones.o \ + ../src/miami/control/Pickups.o \ + ../src/miami/control/PowerPoints.o \ + ../src/miami/control/Record.o \ + ../src/miami/control/Remote.o \ + ../src/miami/control/Replay.o \ + ../src/miami/control/Restart.o \ + ../src/miami/control/RoadBlocks.o \ + ../src/miami/control/SceneEdit.o \ + ../src/miami/control/Script.o \ + ../src/miami/control/Script2.o \ + ../src/miami/control/Script3.o \ + ../src/miami/control/Script4.o \ + ../src/miami/control/Script5.o \ + ../src/miami/control/Script6.o \ + ../src/miami/control/Script7.o \ + ../src/miami/control/Script8.o \ + ../src/miami/control/SetPieces.o \ + ../src/miami/control/ScriptDebug.o \ + ../src/miami/control/TrafficLights.o \ + \ + ../src/miami/core/Accident.o \ + ../src/miami/core/Cam.o \ + ../src/miami/core/Camera.o \ + ../src/miami/core/CdStreamDC.o \ + ../src/miami/core/Clock.o \ + ../src/miami/core/ControllerConfig.o \ + ../src/miami/core/Debug.o \ + ../src/miami/core/Directory.o \ + ../src/miami/core/EventList.o \ + ../src/miami/core/FileLoader.o \ + ../src/miami/core/FileMgr.o \ + ../src/miami/core/Fire.o \ + ../src/miami/core/Frontend.o \ + ../src/miami/core/FrontEndControls.o \ + ../src/miami/core/Frontend_PS2.o \ + ../src/miami/core/Game.o \ + ../src/miami/core/IniFile.o \ + ../src/miami/core/Lists.o \ + ../src/miami/core/main.o \ + ../src/miami/core/MenuScreens.o \ + ../src/miami/core/MenuScreensCustom.o \ + ../src/miami/core/obrstr.o \ + ../src/miami/core/Pad.o \ + ../src/miami/core/Placeable.o \ + ../src/miami/core/PlayerInfo.o \ + ../src/miami/core/Pools.o \ + ../src/miami/core/Profile.o \ + ../src/miami/core/Radar.o \ + ../src/miami/core/Range2D.o \ + ../src/miami/core/Range3D.o \ + ../src/miami/core/re3.o \ + ../src/miami/core/References.o \ + ../src/miami/core/Ropes.o \ + ../src/miami/core/Stats.o \ + ../src/miami/core/Streaming.o \ + ../src/miami/core/SurfaceTable.o \ + ../src/miami/core/timebars.o \ + ../src/miami/core/Timer.o \ + ../src/miami/core/TimeStep.o \ + ../src/miami/core/User.o \ + ../src/miami/core/Wanted.o \ + ../src/miami/core/World.o \ + ../src/miami/core/ZoneCull.o \ + ../src/miami/core/Zones.o \ + \ + ../src/miami/entities/Dummy.o \ + ../src/miami/entities/Entity.o \ + ../src/miami/entities/Physical.o \ + \ + ../src/miami/fakerw/fake.o \ + \ + ../src/miami/math/math.o \ + ../src/miami/math/Matrix.o \ + ../src/miami/math/Quaternion.o \ + ../src/miami/math/Rect.o \ + ../src/miami/math/Vector.o \ + \ + ../src/miami/modelinfo/BaseModelInfo.o \ + ../src/miami/modelinfo/ClumpModelInfo.o \ + ../src/miami/modelinfo/MloModelInfo.o \ + ../src/miami/modelinfo/ModelIndices.o \ + ../src/miami/modelinfo/ModelInfo.o \ + ../src/miami/modelinfo/PedModelInfo.o \ + ../src/miami/modelinfo/SimpleModelInfo.o \ + ../src/miami/modelinfo/TimeModelInfo.o \ + ../src/miami/modelinfo/VehicleModelInfo.o \ + ../src/miami/modelinfo/WeaponModelInfo.o \ + \ + ../src/miami/objects/CutsceneObject.o \ + ../src/miami/objects/DummyObject.o \ + ../src/miami/objects/Object.o \ + ../src/miami/objects/ObjectData.o \ + ../src/miami/objects/ParticleObject.o \ + ../src/miami/objects/Projectile.o \ + ../src/miami/objects/Stinger.o \ + \ + ../src/miami/peds/CivilianPed.o \ + ../src/miami/peds/CopPed.o \ + ../src/miami/peds/EmergencyPed.o \ + ../src/miami/peds/Gangs.o \ + ../src/miami/peds/Ped.o \ + ../src/miami/peds/PedAI.o \ + ../src/miami/peds/PedChat.o \ + ../src/miami/peds/PedDebug.o \ + ../src/miami/peds/PedFight.o \ + ../src/miami/peds/PedIK.o \ + ../src/miami/peds/PedAttractor.o \ + ../src/miami/peds/PedPlacement.o \ + ../src/miami/peds/PedRoutes.o \ + ../src/miami/peds/PedType.o \ + ../src/miami/peds/PlayerPed.o \ + ../src/miami/peds/Population.o \ + \ + ../src/miami/renderer/Antennas.o \ + ../src/miami/renderer/Clouds.o \ + ../src/miami/renderer/Console.o \ + ../src/miami/renderer/Coronas.o \ + ../src/miami/renderer/Credits.o \ + ../src/miami/renderer/CutsceneShadow.o \ + ../src/miami/renderer/Draw.o \ + ../src/miami/renderer/Fluff.o \ + ../src/miami/renderer/Font.o \ + ../src/miami/renderer/Glass.o \ + ../src/miami/renderer/Hud.o \ + ../src/miami/renderer/Instance.o \ + ../src/miami/renderer/Lines.o \ + ../src/miami/renderer/MBlur.o \ + ../src/miami/renderer/Occlusion.o \ + ../src/miami/renderer/Particle.o \ + ../src/miami/renderer/ParticleMgr.o \ + ../src/miami/renderer/PlayerSkin.o \ + ../src/miami/renderer/PointLights.o \ + ../src/miami/renderer/RenderBuffer.o \ + ../src/miami/renderer/Renderer.o \ + ../src/miami/renderer/Rubbish.o \ + ../src/miami/renderer/Shadows.o \ + ../src/miami/renderer/ShadowCamera.o \ + ../src/miami/renderer/Skidmarks.o \ + ../src/miami/renderer/SpecialFX.o \ + ../src/miami/renderer/Sprite.o \ + ../src/miami/renderer/Sprite2d.o \ + ../src/miami/renderer/TexList.o \ + ../src/miami/renderer/Timecycle.o \ + ../src/miami/renderer/VarConsole.o \ + ../src/miami/renderer/WaterCannon.o \ + ../src/miami/renderer/WaterCreatures.o \ + ../src/miami/renderer/WaterLevel.o \ + ../src/miami/renderer/Weather.o \ + ../src/miami/renderer/WindModifiers.o \ + \ + ../src/miami/rw/ClumpRead.o \ + ../src/miami/rw/Lights.o \ + ../src/miami/rw/MemoryHeap.o \ + ../src/miami/rw/MemoryMgr.o \ + ../src/miami/rw/NodeName.o \ + ../src/miami/rw/RwHelper.o \ + ../src/miami/rw/RwMatFX.o \ + ../src/miami/rw/RwPS2AlphaTest.o \ + ../src/miami/rw/TexRead.o \ + ../src/miami/rw/TexturePools.o \ + ../src/miami/rw/TxdStore.o \ + ../src/miami/rw/VisibilityPlugins.o \ + \ + ../src/miami/skel/crossplatform.o \ + ../src/miami/skel/events.o \ + ../src/miami/skel/skeleton.o \ + ../src/miami/skel/dc/dc.o \ + \ + ../src/miami/text/Messages.o \ + ../src/miami/text/Pager.o \ + ../src/miami/text/Text.o \ + \ + ../src/miami/vehicles/Automobile.o \ + ../src/miami/vehicles/Boat.o \ + ../src/miami/vehicles/Bike.o \ + ../src/miami/vehicles/CarGen.o \ + ../src/miami/vehicles/Cranes.o \ + ../src/miami/vehicles/DamageManager.o \ + ../src/miami/vehicles/Door.o \ + ../src/miami/vehicles/Floater.o \ + ../src/miami/vehicles/HandlingMgr.o \ + ../src/miami/vehicles/Heli.o \ + ../src/miami/vehicles/Plane.o \ + ../src/miami/vehicles/Train.o \ + ../src/miami/vehicles/Transmission.o \ + ../src/miami/vehicles/Vehicle.o \ + \ + ../src/miami/weapons/BulletInfo.o \ + ../src/miami/weapons/Explosion.o \ + ../src/miami/weapons/ProjectileInfo.o \ + ../src/miami/weapons/ShotInfo.o \ + ../src/miami/weapons/Weapon.o \ + ../src/miami/weapons/WeaponEffects.o \ + ../src/miami/weapons/WeaponInfo.o \ + \ + ../src/miami/audio/AudioCollision.o \ + ../src/miami/audio/AudioLogic.o \ + ../src/miami/audio/AudioManager.o \ + ../src/miami/audio/AudioScriptObject.o \ + ../src/miami/audio/DMAudio.o \ + ../src/miami/audio/MusicManager.o \ + ../src/miami/audio/PolRadio.o \ + ../src/miami/audio/sampman_miles.o \ + ../src/miami/audio/sampman_oal.o \ + \ + ../src/miami/save/Date.o \ + ../src/miami/save/GenericGameStorage.o \ + ../src/miami/save/MemoryCard.o \ + ../src/miami/save/PCSave.o \ + \ + ../src/miami/extras/debugmenu.o \ + ../src/miami/extras/frontendoption.o \ + ../src/miami/extras/postfx.o \ + ../src/miami/extras/screendroplets.o \ + \ + ../vendor/miniLZO/minilzo.o \ + \ + ../src/common/vmu/vmu.o + +# Excluded \ + ../src/miami/extras/custompipes.o \ + ../src/miami/extras/custompipes_d3d9.o \ + ../src/miami/extras/custompipes_gl.o \ + ../src/miami/core/CdStream.o \ + ../src/miami/core/CdStreamPosix.o \ + ../src/miami/extras \ + ../src/miami/extras/GitSHA1.cpp.in \ + ../src/miami/core/AnimViewer.o \ + +RW_OBJS = \ + ../vendor/librw/src/anim.o \ + ../vendor/librw/src/base.o \ + ../vendor/librw/src/camera.o \ + ../vendor/librw/src/charset.o \ + ../vendor/librw/src/clump.o \ + ../vendor/librw/src/engine.o \ + ../vendor/librw/src/error.o \ + ../vendor/librw/src/frame.o \ + ../vendor/librw/src/geometry.o \ + ../vendor/librw/src/geoplg.o \ + ../vendor/librw/src/hanim.o \ + ../vendor/librw/src/image.o \ + ../vendor/librw/src/light.o \ + ../vendor/librw/src/matfx.o \ + ../vendor/librw/src/pipeline.o \ + ../vendor/librw/src/plg.o \ + ../vendor/librw/src/prim.o \ + ../vendor/librw/src/raster.o \ + ../vendor/librw/src/render.o \ + ../vendor/librw/src/skin.o \ + ../vendor/librw/src/texture.o \ + ../vendor/librw/src/tristrip.o \ + ../vendor/librw/src/userdata.o \ + ../vendor/librw/src/uvanim.o \ + ../vendor/librw/src/world.o \ + \ + ../vendor/librw/src/dc/rwdc.o \ + ../vendor/librw/src/dc/alloc.o + +# Excluded \ + ../vendor/librw/src/d3d-x/d3d.o \ + ../vendor/librw/src/d3d-x/d3d8.o \ + ../vendor/librw/src/d3d-x/d3d8render.o \ + ../vendor/librw/src/d3d/d3d8.o \ + ../vendor/librw/src/d3d/d3d8matfx.o \ + ../vendor/librw/src/d3d/d3d8render.o \ + ../vendor/librw/src/d3d/d3d8skin.o \ + ../vendor/librw/src/d3d/d3d9.o \ + ../vendor/librw/src/d3d/d3d9matfx.o \ + ../vendor/librw/src/d3d/d3d9render.o \ + ../vendor/librw/src/d3d/d3d9skin.o \ + ../vendor/librw/src/d3d/d3d.o \ + ../vendor/librw/src/d3d/d3ddevice.o \ + ../vendor/librw/src/d3d/d3dimmed.o \ + ../vendor/librw/src/d3d/d3drender.o \ + ../vendor/librw/src/d3d/xbox.o \ + ../vendor/librw/src/d3d/xboxmatfx.o \ + ../vendor/librw/src/d3d/xboxskin.o \ + ../vendor/librw/src/d3d/xboxvfmt.o \ + \ + ../vendor/librw/src/gl/gl3.o \ + ../vendor/librw/src/gl/gl3device.o \ + ../vendor/librw/src/gl/gl3immed.o \ + ../vendor/librw/src/gl/gl3matfx.o \ + ../vendor/librw/src/gl/gl3pipe.o \ + ../vendor/librw/src/gl/gl3raster.o \ + ../vendor/librw/src/gl/gl3render.o \ + ../vendor/librw/src/gl/gl3shader.o \ + ../vendor/librw/src/gl/gl3skin.o \ + ../vendor/librw/src/gl/wdgl.o \ + ../vendor/librw/src/gl/glad/glad.cXXX \ + \ + ../vendor/librw/src/ps2/pds.o \ + ../vendor/librw/src/ps2/ps2.o \ + ../vendor/librw/src/ps2/ps2device.o \ + ../vendor/librw/src/ps2/ps2matfx.o \ + ../vendor/librw/src/ps2/ps2raster.o \ + ../vendor/librw/src/ps2/ps2skin.o \ + +INCLUDE = \ +-I../src/miami/animation \ +-I../src/miami/audio \ +-I../src/miami/buildings \ +-I../src/miami/collision \ +-I../src/miami/control \ +-I../src/miami/core \ +-I../src/miami/entities \ +-I../src/miami/extras \ +-I../src/miami/fakerw \ +-I../src/miami/math \ +-I../src/miami/modelinfo \ +-I../src/miami/objects \ +-I../src/miami/peds \ +-I../src/miami/renderer \ +-I../src/miami/rw \ +-I../src/miami/save \ +-I../src/miami/skel \ +-I../src/miami/text \ +-I../src/miami/vehicles \ +-I../src/miami/weapons \ +-I../src/miami/audio/eax \ +-I../src/miami/audio/oal \ +-I../src/miami/extras/shaders \ +-I../src/miami/extras/shaders/obj \ +-I../src/miami/skel/glfw \ +-I../src/miami/skel/win \ +\ +-I../vendor/librw \ +\ +-I../vendor/miniLZO \ +\ +-I../src/common \ +\ +-Igit-version + +DEFINES = -DRW_DC -DLIBRW $(if $(WITH_LOGGING),-DWITH_LOGGING) $(if $(WITH_DCLOAD),-DDC_CHDIR=/pc) \ + $(if $(WITH_BEEPS),-DWITH_BEEPS) +FLAGS = -fpermissive -Wno-sign-compare -Wno-parentheses -Wno-maybe-uninitialized \ + -Wno-format -Wno-strict-aliasing -Wno-unused-variable \ + -Wno-unused-but-set-variable -Wno-write-strings \ + -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion \ + -Wno-multichar -Wno-unused-value -Wno-char-subscripts -Wno-reorder \ + -Wno-unused-function -Wno-class-memaccess -fno-permissive + +CPPFLAGS += $(INCLUDE) $(DEFINES) $(FLAGS) +CFLAGS += -std=gnu17 $(CPPFLAGS) +CXXFLAGS += -std=gnu++20 $(CPPFLAGS) \ No newline at end of file diff --git a/miami/cuts.mk b/miami/cuts.mk new file mode 100644 index 00000000..f668c9d1 --- /dev/null +++ b/miami/cuts.mk @@ -0,0 +1,151 @@ +CUTS_IFP = \ + ass_1.ifp \ + ass_2.ifp \ + bank_1.ifp \ + bank_2a.ifp \ + bank_2b.ifp \ + bank_3a.ifp \ + bank_3b.ifp \ + bank_4.ifp \ + bike_1.ifp \ + bike_2.ifp \ + bike_3.ifp \ + bud_1.ifp \ + bud_2.ifp \ + bud_3.ifp \ + cap_1.ifp \ + car_1.ifp \ + cnt_1a.ifp \ + cnt_1b.ifp \ + cnt_2.ifp \ + cok_1.ifp \ + cok_2a.ifp \ + cok_2b.ifp \ + cok_3.ifp \ + cok_4a.ifp \ + cok_4a2.ifp \ + cok_4b.ifp \ + col_1.ifp \ + col_2.ifp \ + col_3a.ifp \ + col_4a.ifp \ + col_4b.ifp \ + col_5a.ifp \ + col_5b.ifp \ + cub_1.ifp \ + cub_2.ifp \ + cub_3.ifp \ + cub_4.ifp \ + drug_1.ifp \ + fin.ifp \ + fin_2.ifp \ + finale.ifp \ + hat_1.ifp \ + hat_2.ifp \ + hat_3.ifp \ + ice_1.ifp \ + int_a.ifp \ + int_b.ifp \ + int_d.ifp \ + int_m.ifp \ + int_m2.ifp \ + law_1a.ifp \ + law_1b.ifp \ + law_2a.ifp \ + law_2b.ifp \ + law_2c.ifp \ + law_3.ifp \ + law_4.ifp \ + phil_1.ifp \ + phil_2.ifp \ + porn_1.ifp \ + porn_2.ifp \ + porn_3.ifp \ + porn_4.ifp \ + resc_1a.ifp \ + rok_1.ifp \ + rok_2.ifp \ + rok_3a.ifp \ + rok_4a.ifp \ + rok_4b.ifp \ + rok_4c.ifp \ + rok_4d.ifp \ + stripa.ifp \ + tax_1.ifp \ + tex_1.ifp \ + tex_2.ifp \ + tex_3.ifp + +CUTS_MISC = \ + ass_1.dat \ + ass_2.dat \ + bank_1.dat \ + bank_2a.dat \ + bank_2b.dat \ + bank_3a.dat \ + bank_3b.dat \ + bank_4.dat \ + bike_1.dat \ + bike_2.dat \ + bike_3.dat \ + bud_1.dat \ + bud_2.dat \ + bud_3.dat \ + cap_1.dat \ + car_1.dat \ + cnt_1a.dat \ + cnt_1b.dat \ + cnt_2.dat \ + cok_1.dat \ + cok_2a.dat \ + cok_2b.dat \ + cok_3.dat \ + cok_4a.dat \ + cok_4a2.dat \ + cok_4b.dat \ + col_1.dat \ + col_2.dat \ + col_3a.dat \ + col_4a.dat \ + col_5a.dat \ + col_5b.dat \ + cub_1.dat \ + cub_2.dat \ + cub_3.dat \ + cub_4.dat \ + drug_1.dat \ + fin.dat \ + fin_2.dat \ + finale.dat \ + hat_1.dat \ + hat_2.dat \ + hat_3.dat \ + ice_1.dat \ + int_a.dat \ + int_b.dat \ + int_d.dat \ + int_m.dat \ + int_m2.dat \ + law_1a.dat \ + law_1b.dat \ + law_2a.dat \ + law_2b.dat \ + law_2c.dat \ + law_3.dat \ + law_4.dat \ + phil_1.dat \ + phil_2.dat \ + porn_1.dat \ + porn_2.dat \ + porn_3.dat \ + porn_4.dat \ + resc_1a.dat \ + rok_1.dat \ + rok_2.dat \ + rok_3a.dat \ + rok_4a.dat \ + stripa.dat \ + tax_1.dat \ + tex_1.dat \ + tex_2.dat \ + tex_3.dat \ No newline at end of file diff --git a/miami/gta3files.mk b/miami/gta3files.mk new file mode 100644 index 00000000..6bd1b88f --- /dev/null +++ b/miami/gta3files.mk @@ -0,0 +1,210 @@ +# models/coll/peds.col # not actually used + +MISC_FILES = \ + \ + txd/LOADSC8.TXD \ + txd/LOADSC6.TXD \ + txd/outro.txd \ + txd/LOADSC2.TXD \ + txd/LOADSC5.TXD \ + txd/LOADSC3.TXD \ + txd/NEWS.TXD \ + txd/LOADSC11.TXD \ + txd/LOADSC12.TXD \ + txd/LOADSC10.TXD \ + txd/LOADSC1.TXD \ + txd/LOADSC9.TXD \ + txd/LOADSC7.TXD \ + txd/SPLASH2.TXD \ + txd/LOADSC13.TXD \ + txd/INTRO3.TXD \ + txd/SPLASH3.TXD \ + txd/SPLASH1.TXD \ + txd/intro2.txd \ + txd/intro1.txd \ + txd/LOADSC0.TXD \ + txd/LOADSC4.TXD \ + txd/intro4.txd \ + \ + models/fonts.txd \ + models/hud.txd \ + models/coll/weapons.col \ + models/coll/vehicles.col \ + models/coll/generic.col \ + models/MISC.TXD \ + models/generic.txd \ + models/gta3.dir \ + models/fronten2.txd \ + models/particle.txd \ + models/fronten1.txd \ + models/INTRO.TXD \ + models/generic/wheels.TXD \ + models/generic/wheels.DFF \ + models/generic/arrow.DFF \ + models/generic/player.bmp \ + models/generic/air_vlo.DFF \ + models/generic/zonecylb.DFF \ + models/gta3.img \ + \ + anim/ped.ifp \ + \ + Icons/app.ico \ + Icons/bin.ico \ + \ + skins/texture_guide.jpg \ + \ + movies/GTAtitles.mpg \ + movies/Logo.mpg \ + \ + TEXT/german.gxt \ + TEXT/italian.gxt \ + TEXT/spanish.gxt \ + TEXT/american.gxt \ + TEXT/french.gxt \ + \ + data/weapon.dat \ + data/train.dat \ + \ + data/maps/haiti/haiti.ide \ + data/maps/haiti/haiti.col \ + data/maps/haiti/haiti.ipl \ + data/maps/generic.ide \ + \ + data/maps/hotel/hotel.ipl \ + data/maps/hotel/hotel.ide \ + data/maps/hotel/hotel.col \ + \ + data/maps/islandsf/islandsf.ide \ + data/maps/islandsf/islandsf.ipl \ + data/maps/islandsf/islandsf.col \ + \ + data/maps/washintn/washintn.ipl \ + data/maps/washintn/washintn.ide \ + data/maps/washintn/washintn.col \ + data/maps/map6.dat \ + \ + data/maps/littleha/littleha.ipl \ + data/maps/littleha/littleha.ide \ + data/maps/littleha/littleha.col \ + \ + data/maps/mall/mall.ide \ + data/maps/mall/mall.col \ + data/maps/mall/mall.ipl \ + data/maps/map7.dat \ + \ + data/maps/bar/bar.ide \ + \ + data/maps/nbeach/nbeach.ipl \ + data/maps/nbeach/nbeach.ide \ + data/maps/nbeach/nbeach.col \ + \ + data/maps/golf/golf.ide \ + data/maps/golf/golf.col \ + data/maps/golf/golf.ipl \ + data/maps/map0.dat \ + \ + data/maps/bank/bank.col \ + data/maps/bank/bank.ide \ + data/maps/bank/bank.ipl \ + \ + data/maps/nbeachw/nbeachw.ide \ + data/maps/nbeachw/nbeachw.col \ + data/maps/nbeachw/nbeachw.ipl \ + data/maps/map4.dat \ + \ + data/maps/airport/airport.ipl \ + data/maps/airport/airport.ide \ + data/maps/airport/airport.col \ + data/maps/nbeachbt/nbeachbt.col \ + data/maps/nbeachbt/nbeachbt.ide \ + data/maps/nbeachbt/nbeachbt.ipl \ + data/maps/map5.dat \ + data/maps/lawyers/lawyers.ide \ + data/maps/lawyers/lawyers.col \ + data/maps/lawyers/lawyers.ipl \ + data/maps/haitiN/haitin.ipl \ + data/maps/haitiN/haitin.col \ + data/maps/haitiN/haitiN.ide \ + data/maps/downtown/downtown.col \ + data/maps/downtown/downtown.ipl \ + data/maps/downtown/downtown.ide \ + data/maps/stadint/stadint.col \ + data/maps/stadint/stadint.ide \ + data/maps/stadint/stadint.ipl \ + data/maps/club/club.ide \ + data/maps/club/CLUB.ipl \ + data/maps/club/CLUB.col \ + data/maps/yacht/yacht.ipl \ + data/maps/yacht/yacht.col \ + data/maps/yacht/yacht.ide \ + data/maps/cull.ipl \ + data/maps/map3.dat \ + data/maps/airportN/airportN.ipl \ + data/maps/airportN/airportN.col \ + data/maps/airportN/airportN.ide \ + data/maps/oceandrv/oceandrv.ipl \ + data/maps/oceandrv/oceandrv.ide \ + data/maps/oceandrv/oceandrv.col \ + data/maps/starisl/starisl.col \ + data/maps/starisl/starisl.ide \ + data/maps/starisl/starisl.ipl \ + data/maps/map1.dat \ + data/maps/oceandn/oceandN.ide \ + data/maps/oceandn/oceandN.col \ + data/maps/oceandn/oceandN.ipl \ + data/maps/washints/washints.ipl \ + data/maps/washints/washints.col \ + data/maps/washints/washints.ide \ + data/maps/mansion/mansion.col \ + data/maps/mansion/mansion.ipl \ + data/maps/mansion/mansion.ide \ + data/maps/downtows/downtows.ipl \ + data/maps/downtows/downtows.col \ + data/maps/downtows/downtows.ide \ + data/maps/bridge/bridge.col \ + data/maps/bridge/bridge.ide \ + data/maps/bridge/bridge.ipl \ + data/maps/docks/docks.col \ + data/maps/docks/docks.ide \ + data/maps/docks/docks.ipl \ + data/maps/concerth/concerth.ipl \ + data/maps/concerth/concerth.ide \ + data/maps/concerth/concerth.col \ + data/maps/paths.ipl \ + data/maps/cisland/cisland.ide \ + data/maps/cisland/cisland.ipl \ + data/maps/cisland/cisland.col \ + data/maps/stripclb/stripclb.ide \ + data/maps/stripclb/stripclb.ipl \ + data/maps/stripclb/stripclb.col \ + data/maps/map2.dat \ + data/pedstats.dat \ + data/gta_vc.dat \ + data/occlu.ipl \ + data/handling.cfg \ + data/timecyc.dat \ + data/paths/flight3.dat \ + data/paths/spath0.dat \ + data/paths/flight2.dat \ + data/paths/flight.dat \ + data/map.zon \ + data/water.dat \ + data/particle.cfg \ + data/navig.zon \ + data/default.dat \ + data/carcols.dat \ + data/fistfite.dat \ + data/train2.dat \ + data/info.zon \ + data/ped.dat \ + data/WATERPRO.DAT \ + data/main.sc \ + data/debug.sc \ + data/object.dat \ + data/animviewer.dat \ + data/Cullzoneempty.dat \ + data/surface.dat \ + data/main.scm \ + data/default.ide \ + data/pedgrp.dat \ + data/Cullzone.dat \ No newline at end of file diff --git a/miami/imgmisc.mk b/miami/imgmisc.mk new file mode 100644 index 00000000..948a8e4f --- /dev/null +++ b/miami/imgmisc.mk @@ -0,0 +1,61 @@ +IMG_COL = \ + airport.col \ + airportN.col \ + bank.col \ + bridge.col \ + cisland.col \ + CLUB.col \ + concerth.col \ + docks.col \ + downtown.col \ + downtows.col \ + golf.col \ + haiti.col \ + haitin.col \ + hotel.col \ + islandsf.col \ + lawyers.col \ + littleha.col \ + mall.col \ + mansion.col \ + nbeachbt.col \ + nbeach.col \ + nbeachw.col \ + oceandN.col \ + oceandrv.col \ + stadint.col \ + starisl.col \ + stripclb.col \ + washintn.col \ + washints.col \ + yacht.col + +IMG_IFP = \ + baseball.ifp \ + biked.ifp \ + bikeh.ifp \ + bikes.ifp \ + bikev.ifp \ + buddy.ifp \ + chainsaw.ifp \ + coach.ifp \ + colt45.ifp \ + flame.ifp \ + grenade.ifp \ + knife.ifp \ + lance.ifp \ + m60.ifp \ + medic.ifp \ + playidles.ifp \ + python.ifp \ + rifle.ifp \ + riot.ifp \ + shotgun.ifp \ + skate.ifp \ + sniper.ifp \ + strip.ifp \ + sunbathe.ifp \ + sword.ifp \ + tec.ifp \ + uzi.ifp \ + van.ifp \ No newline at end of file diff --git a/dreamcast/ip.txt b/miami/ip.txt similarity index 65% rename from dreamcast/ip.txt rename to miami/ip.txt index 8cfb4b37..1f44b9d3 100644 --- a/dreamcast/ip.txt +++ b/miami/ip.txt @@ -1,9 +1,9 @@ Device Info : CD-ROM1/1 Area Symbols : JUE Peripherals : E000F10 -Product No : T0000 +Product No : DCA-MIAM Version : V1.000 -Release Date : 20000627 +Release Date : 20250401 Boot Filename : 1ST_READ.BIN SW Maker Name : the gang -Game Title : DCA3 +Game Title : dca-miami diff --git a/miami/modlist.mk b/miami/modlist.mk new file mode 100644 index 00000000..4dc69853 --- /dev/null +++ b/miami/modlist.mk @@ -0,0 +1,4614 @@ +IMG_MODELS = \ + 4x4finishline.dff \ + 4x4racecotop2.dff \ + 4x4racecotop.dff \ + 4x4rockstop.dff \ + 4x4signstop.dff \ + admiral.dff \ + adrenaline.dff \ + air_brway_030.dff \ + air_brway_31.dff \ + air_brway_32.dff \ + air_brway_33.dff \ + air_brway_34.dff \ + airplan.dff \ + airport_carpark0.dff \ + airportgate.dff \ + airtrain.dff \ + ak47.dff \ + ambulan.dff \ + ammunation_dt.dff \ + ammu_trannies.dff \ + ammu_windows1.dff \ + angel.dff \ + ap_06trailer01.dff \ + ap_3dbillb.dff \ + apairprtbits01.dff \ + apairprtbits05.dff \ + apairprtbits07.dff \ + apairprtbits08.dff \ + ap_barriergate1.dff \ + ap_barriergate2.dff \ + ap_barriergate3.dff \ + ap_barriergate4.dff \ + ap_billboards1.dff \ + ap_billboards2.dff \ + ap_billboards3.dff \ + ap_billboards4.dff \ + ap_billboards5.dff \ + ap_blastdef_01.dff \ + ap_blastdef_03.dff \ + ap_boardshad1.dff \ + ap_booth1_01.dff \ + ap_booth1_02.dff \ + ap_booth2_01.dff \ + ap_booth2_03.dff \ + ap_booth3_01.dff \ + ap_boxjunct1.dff \ + ap_carbush2_01.dff \ + ap_carparkterm1.dff \ + ap_concwall1.dff \ + ap_dockstairs1.dff \ + ap_dockwall1.dff \ + ap_firetrucks.dff \ + ap_flargetank1.dff \ + ap_flyingschool1.dff \ + ap_freight1_01b.dff \ + ap_freight1_01.dff \ + ap_freight2_01a.dff \ + ap_freight2_01.dff \ + ap_freight3_01a.dff \ + ap_freight3_01.dff \ + ap_freightterminal1.dff \ + ap_fuelbay1.dff \ + ap_fuelshad1.dff \ + ap_gatedisp1_01.dff \ + ap_gatedisp1_02.dff \ + ap_gatesA.dff \ + ap_gatesB.dff \ + ap_gsecurity1.dff \ + ap_gsecurity2.dff \ + ap_hangar1_01.dff \ + ap_hangar1_03.dff \ + ap_hangar1_04.dff \ + ap_hland_01.dff \ + ap_hookerinn1.dff \ + ap_hotelpool.dff \ + ap_hotroadsect1.dff \ + ap_jumbo_01.dff \ + ap_jumbo_BRIDGE.dff \ + ap_kerb1.dff \ + ap_learjet1_01.dff \ + ap_midtank1.dff \ + ap_newesca_01.dff \ + ap_newesca_02.dff \ + ap_newescaGLAS_01.dff \ + ap_newescaGLAS_02.dff \ + ap_oilpipes1.dff \ + ap_oiltank1_01.dff \ + Apoulet.dff \ + ap_planters1_01.dff \ + ap_planters2_01.dff \ + ap_pots1_01.dff \ + ap_pots2_01.dff \ + ap_radar1_01.dff \ + ap_rland01.dff \ + ap_rland02.dff \ + ap_rland03.dff \ + ap_rland04.dff \ + ap_rland05.dff \ + ap_rland06.dff \ + ap_rland07.dff \ + ap_rland08.dff \ + ap_rland10.dff \ + ap_rlandnew9.dff \ + ap_roadsect1.dff \ + ap_roadsect2a.dff \ + ap_roadsect2b.dff \ + ap_roadsect3b.dff \ + ap_roadsect3.dff \ + ap_roadsect4.dff \ + ap_roadsect5.dff \ + ap_roadsigns1_02.dff \ + ap_roadsigns1_03.dff \ + ap_runsign10_03.dff \ + ap_runsign3_01.dff \ + ap_runsign6_02.dff \ + ap_runsign7_03.dff \ + ap_runsigns5_01.dff \ + ap_screens1_01.dff \ + ap_screens2_01.dff \ + ap_seafences.dff \ + ap_seahangstuft.dff \ + ap_seaplaland1.dff \ + ap_seaplanedock1.dff \ + ap_seaplanehanger1.dff \ + ap_seaplanfence1.dff \ + ap_security1.dff \ + ap_security2.dff \ + ap_smahangar2_01.dff \ + ap_smahangar2_02.dff \ + ap_smahangar2_03.dff \ + ap_smahangar2_04.dff \ + ap_smahangar2_05.dff \ + ap_smahangar2_06.dff \ + ap_smallhangars1_01.dff \ + ap_smallhangars1_02.dff \ + ap_smallhangars1_03.dff \ + ap_smallradar1_02.dff \ + ap_smallradar1.dff \ + ap_stairsout02.dff \ + ap_stairsout03.dff \ + ap_stairsout04.dff \ + ap_stairsout1.dff \ + ap_steps1_01.dff \ + ap_steps1_02.dff \ + ap_steps1_03.dff \ + ap_steps1_04.dff \ + ap_steps1_05.dff \ + ap_steps1_06.dff \ + ap_steps1_07.dff \ + ap_steps1_08.dff \ + ap_stepsn1_01.dff \ + ap_stepsn1_02.dff \ + ap_subfraightback01.dff \ + ap_subfraightback02.dff \ + ap_subfraightback04.dff \ + ap_termAintfloor1b.dff \ + ap_termAintfloor1.dff \ + ap_termAintfloor2b.dff \ + ap_termAintfloor2.dff \ + ap_termbbase1.dff \ + ap_terminA_01.dff \ + ap_terminA2_01.dff \ + ap_terminAchek1_01.dff \ + ap_terminAchek1_02.dff \ + ap_terminAgatesA.dff \ + ap_terminAgatesB.dff \ + ap_terminAgrflo1.dff \ + ap_terminAgrflo2.dff \ + ap_terminalB1.dff \ + ap_terminA_roof1.dff \ + ap_terminA_roof2.dff \ + ap_termsign1_01.dff \ + ap_termsign1_dy.dff \ + ap_termsign1_nt.dff \ + ap_termsign2_01.dff \ + ap_termwindows1b.dff \ + ap_termwindows1.dff \ + ap_tower.dff \ + ap_trailer_01.dff \ + ap_transpol1.dff \ + ap_treesfw1_01.dff \ + ap_treeshot1_02.dff \ + ap_wallfence1.dff \ + ap_wallfence2.dff \ + ap_wallfence3.dff \ + ap_wallfence4.dff \ + ap_wallfence5.dff \ + ap_wallfence6.dff \ + ap_wallfence7.dff \ + ap_windowstruts.dff \ + arcadesjm01.dff \ + arcadesjm02.dff \ + arcadesjm03.dff \ + arcadesjm.dff \ + armour.dff \ + army.dff \ + baggage.dff \ + bank_officedesks1.dff \ + bank_other_strctr.dff \ + bank_palms02.dff \ + bank_palms09.dff \ + bank_palms21.dff \ + bank_palms23.dff \ + bank_palms28.dff \ + bank_palms32.dff \ + bank_palms33.dff \ + bank_props2.dff \ + bankrecept01.dff \ + bankrecept.dff \ + bankstructure4.dff \ + bank_table05.dff \ + bank_table10.dff \ + bank_table1.dff \ + bank_table4.dff \ + bank_vault.dff \ + banshee.dff \ + bar_barrier10b.dff \ + bar_barrier10.dff \ + bar_barrier12.dff \ + bar_barrier16.dff \ + bar_barriergate1.dff \ + bar_gatebar01.dff \ + bar_gatebox01.dff \ + barracks.dff \ + barrel1.dff \ + barrel2.dff \ + barrel3.dff \ + barrel4.dff \ + barrelexpos.dff \ + Barrierm.dff \ + barrierturn.dff \ + basketballcourt04.dff \ + basketballcourt05.dff \ + bat.dff \ + bb_fence1.dff \ + bb_pooltable1_01.dff \ + bb_roof1_01.dff \ + bb_walljunk1_01.dff \ + beachball.dff \ + beachblend01.dff \ + beachblend02.dff \ + beachblend03.dff \ + beachblend04.dff \ + beachblend05.dff \ + beachblend06.dff \ + beachblend07.dff \ + beachblend08.dff \ + beachblend09.dff \ + beachblend10.dff \ + beachblend11.dff \ + beachblend12.dff \ + beachblend13.dff \ + beachblend14.dff \ + beachblend15.dff \ + beachblend16.dff \ + beachbta.dff \ + beachbtb2.dff \ + beachbtb.dff \ + beachbt.dff \ + beach_bush02.dff \ + beach_bush04.dff \ + beach_bush06s.dff \ + beach_bush08s.dff \ + beachlo1.dff \ + beachtowel01.dff \ + beachtowel02.dff \ + beachtowel03.dff \ + beachtowel04.dff \ + bed_shop.dff \ + benson.dff \ + bfinject.dff \ + BFOBE.dff \ + bfori.dff \ + bfost.dff \ + bfotr.dff \ + bfybe.dff \ + BFYPR.dff \ + bfyri.dff \ + BFYST.dff \ + bga.dff \ + bgb.dff \ + b_hse_doors.dff \ + b_hse_ext.dff \ + b_hse_interior.dff \ + b_hse_interiorrays.dff \ + b_hse_pier.dff \ + b_hse_pierfence.dff \ + bigdollar.dff \ + biggrass2.dff \ + bighotelgrnd.dff \ + big_poly_tings_b.dff \ + big_poly_tings.dff \ + biker_bar_exterior.dff \ + bikeshop_glass2.dff \ + bikeshop_glass.dff \ + BillBd04.dff \ + BillBd1.dff \ + BillBd2.dff \ + BillBd3.dff \ + billboard.dff \ + bin1.dff \ + bka.dff \ + bkb.dff \ + BlackBag1.dff \ + BlackBag2.dff \ + bldigste1mesh.dff \ + bldngst2meshdam.dff \ + bldngst2mesh.dff \ + blimp_day.dff \ + blimp_night.dff \ + blistac.dff \ + bloodra.dff \ + bloodrb.dff \ + BMOBE.dff \ + bmodk.dff \ + bmost.dff \ + bmotr.dff \ + bmybb.dff \ + bmybe.dff \ + bmybu.dff \ + bmycr.dff \ + bmypi.dff \ + bmyri.dff \ + bmyst.dff \ + bnk_dsk_acses.dff \ + bnk_dsk_acsesno2.dff \ + bnk_ext_int.dff \ + bnk_grill01.dff \ + bnk_lft_door1.dff \ + bnk_lft_door2.dff \ + bnk_lightsmain.dff \ + bnk_maindr1.dff \ + bnk_maindr2.dff \ + bnk_main_glass.dff \ + bnk_mainnewref.dff \ + bnk_main_rework.dff \ + bnk_main_rooflights.dff \ + bnk_man_ofc01.dff \ + bnk_man_ofc.dff \ + bnk_mngr_chr.dff \ + bnk_mngr_rf_lights.dff \ + bnk_recp_lites.dff \ + bnk_scrty_tbl.dff \ + bnk_shado_2.dff \ + bnk_valt_door.dff \ + boatcranelg0.dff \ + boatcranesm0.dff \ + Boat_houseA01.dff \ + Boat_houseA.dff \ + boat_kb1.dff \ + boat_kb2.dff \ + boatpole.dff \ + bobcat.dff \ + bodyarmour.dff \ + bollard.dff \ + bollardlight.dff \ + bomb.dff \ + bonus.dff \ + boomshine.dff \ + bounca.dff \ + bouy.dff \ + boxville.dff \ + brassknuckle.dff \ + brfcase.dff \ + bribe.dff \ + briefcase.dff \ + buddy.dff \ + buddyshot.dff \ + build1.dff \ + buildblown.dff \ + buildingground40.dff \ + buildingground50.dff \ + buildingsite2.dff \ + buldingsite1.dff \ + burger.dff \ + burrito.dff \ + bus.dff \ + bussign1.dff \ + bustopm.dff \ + cabbie.dff \ + caddy.dff \ + camera.dff \ + camerapickup.dff \ + carback.dff \ + cardboardbox2.dff \ + cardboardbox4.dff \ + cardboardbox.dff \ + card.dff \ + car_fucko03.dff \ + carparkfence0.dff \ + carshowflags.dff \ + carsontrack.dff \ + carwashpole1.dff \ + carwashpole2.dff \ + carwashpole3.dff \ + carwashsign1.dff \ + Cba.dff \ + Cbb.dff \ + cb_CLUBSTAIRS.dff \ + ccfan.dff \ + cdrivra.dff \ + cdrivrb.dff \ + cdseaan.dff \ + cdseabed01.dff \ + cdseabed02.dff \ + cdseabed03.dff \ + cdseabed04.dff \ + cdseabed05.dff \ + cdseabed06.dff \ + cdseabed07.dff \ + cdseabed08.dff \ + cdseabed09.dff \ + cdseabed10.dff \ + cdseabed11.dff \ + cdseabed12.dff \ + cdseabed13.dff \ + cdseabed14.dff \ + cdseabed15.dff \ + cdseabed16.dff \ + cdseabed17.dff \ + cdseabed18.dff \ + cdseabed19.dff \ + cdseabed20.dff \ + cdseabed21.dff \ + cdseabed22.dff \ + cdseabed23.dff \ + cdseabed24.dff \ + cdseabed25.dff \ + cdseabed26.dff \ + cdseabed27.dff \ + cdseabed28.dff \ + cdseabed29.dff \ + cdseabed30.dff \ + cdseabed31.dff \ + cdseabed32.dff \ + cdseabed33.dff \ + cellphone.dff \ + cgona.dff \ + cgonb.dff \ + cgonc.dff \ + cgoona.dff \ + cgoonb.dff \ + chairsntable.dff \ + chairsntableml.dff \ + chairthings2.dff \ + chairthings.dff \ + cheetah.dff \ + chef.dff \ + chnsaw.dff \ + chopper.dff \ + chromegun.dff \ + ci_Acyclorama.dff \ + ci_alights1_01.dff \ + ci_astage.dff \ + ci_astageint.dff \ + ci_astagelight1.dff \ + ci_astagelight2.dff \ + ci_astagelights.dff \ + ci_Asteps.dff \ + ci_astudganwal.dff \ + ci_astudiogantry.dff \ + ci_astudlights.dff \ + ci_backdrop.dff \ + ci_backgateclose.dff \ + ci_backgateopen.dff \ + ci_backlot1.dff \ + ci_backlot2.dff \ + ci_backlot3.dff \ + ci_backlotprops1.dff \ + ci_backlotprops2.dff \ + ci_backlotprops3.dff \ + ci_bcyclorama1.dff \ + ci_blights1_01.dff \ + ci_boards1.dff \ + ci_boards2.dff \ + ci_bstage.dff \ + ci_bstageint.dff \ + ci_bstudganwal.dff \ + ci_bstudiogantry.dff \ + ci_bstudlights.dff \ + ci_busht_02.dff \ + ci_busht_04.dff \ + ci_busht_06.dff \ + ci_busht_08.dff \ + ci_busht_11.dff \ + ci_camera1_01.dff \ + ci_clightdesk.dff \ + ci_cstage.dff \ + ci_cstageint.dff \ + ci_dirtcorn1.dff \ + ci_dirtcorn2.dff \ + ci_dirtcorn3.dff \ + ci_dirtcorn4.dff \ + ci_dstage.dff \ + ci_dstageext.dff \ + ci_dstagelight.dff \ + ci_dstageshad.dff \ + ci_dstudganwal.dff \ + ci_dstudiogantry.dff \ + ci_dstudlights.dff \ + ci_extertank.dff \ + ci_fence2.dff \ + ci_flatprop01.dff \ + ci_flatprop02.dff \ + ci_fountain.dff \ + ci_gatesclosed.dff \ + ci_gatesopen.dff \ + ci_gateway.dff \ + ci_gobo1.dff \ + ci_graf_fountain.dff \ + ci_graf_mansion1.dff \ + ci_graf_mansion3.dff \ + ci_guardhouse1.dff \ + ci_industrial1.dff \ + ci_islandeast.dff \ + ci_islandnorth.dff \ + ci_islandwest.dff \ + ci_jawsramp1.dff \ + ci_jetygatesclosed.dff \ + ci_jetygatesopen.dff \ + ci_kerb1.dff \ + ci_lander.dff \ + ci_lights1_01.dff \ + ci_lights1_02.dff \ + ci_manintstuff.dff \ + ci_mans1grafint.dff \ + ci_mans1grndfl.dff \ + ci_mans1int.dff \ + ci_mans1istfl.dff \ + ci_mans1rail1.dff \ + ci_mans1rail.dff \ + ci_mans1roof.dff \ + ci_mans1stair1.dff \ + ci_mans1stair2.dff \ + ci_mans2rail1.dff \ + ci_manscourt.dff \ + ci_mansion1.dff \ + ci_mansion2.dff \ + ci_mansion3ballust.dff \ + ci_mansion3.dff \ + ci_mansionwall1.dff \ + ci_moonsurface.dff \ + ci_nbbridge1.dff \ + ci_nbbridge1fn2.dff \ + ci_nbbridge1fn3.dff \ + ci_notice03.dff \ + ci_offices.dff \ + ci_officewindows.dff \ + ci_palettes.dff \ + ci_planter1.dff \ + ci_planter2.dff \ + ci_poolhouse.dff \ + ci_poolsteps.dff \ + ci_poolwall1.dff \ + ci_pornobed.dff \ + ci_pornocarpet.dff \ + ci_pornset1.dff \ + ci_pornsetxtra.dff \ + ci_posters.dff \ + ci_rdsigns01.dff \ + ci_rdsigns.dff \ + ci_redhead1_01.dff \ + ci_screening2.dff \ + ci_screening.dff \ + ci_screeningstairs.dff \ + ci_seajetty.dff \ + ci_setshad1.dff \ + ci_shipset.dff \ + ci_spaceflag.dff \ + ci_stairsout01.dff \ + ci_studiowall.dff \ + ci_studwallgraf.dff \ + ci_table1_01.dff \ + ci_tankship.dff \ + ci_trees02.dff \ + ci_trees03.dff \ + ci_trees04.dff \ + ci_trees05.dff \ + ci_trees1.dff \ + ci_watertank.dff \ + ci_windmach2.dff \ + ci_windmach.dff \ + Cla.dff \ + cl_bar_temp1.dff \ + cl_bckrm_couchs.dff \ + cl_bckrm_tablewee.dff \ + cl_bckrm_tble.dff \ + Clb.dff \ + clchr.dff \ + cl_coolers.dff \ + cl_curtains.dff \ + cl_dancefloor.dff \ + cleaver.dff \ + cl_light_st.dff \ + cl_lightsuprts.dff \ + cl_main_room12.dff \ + cl_main_roomb2.dff \ + cl_main_roomb.dff \ + cl_main_room.dff \ + cl_mainwallsb.dff \ + cl_mainwalls.dff \ + cl_of_opac.dff \ + cl_ofs_props.dff \ + clothesp.dff \ + cl_railingb.dff \ + cl_railing.dff \ + cl_recessed_flr.dff \ + cl_recessedlights1.dff \ + cl_spots.dff \ + cl_stage.dff \ + cl_stage_roof.dff \ + cl_tablesetlrg.dff \ + cl_tbl_chait_tmpb.dff \ + cl_tbl_chait_tmp.dff \ + cl_tblesetsml.dff \ + clubbackroom.dff \ + clubceilingdome.dff \ + club_exterior01.dff \ + club_exterior02.dff \ + club_exterior03.dff \ + club_exterior04.dff \ + club_exterior05.dff \ + club_exterior06.dff \ + club_exterior07.dff \ + club_exterior08.dff \ + club_exterior09.dff \ + club_exterior10.dff \ + club_exterior11.dff \ + club_exterior12.dff \ + cl_yachtmdl.dff \ + cmraman.dff \ + cntrlrsac1.dff \ + coach.dff \ + coastg.dff \ + cofbarbothint.dff \ + cof_bardetail.dff \ + cof_extwindw.dff \ + cof_intfloor.dff \ + cof_seat.dff \ + cof_shad1.dff \ + cof_shad2.dff \ + cof_shad4.dff \ + cofsho_ext1.dff \ + cof_shpdetail1.dff \ + cof_xtremedet.dff \ + coke_voodoo.dff \ + coknife.dff \ + cokpoolwater.dff \ + colonel.dff \ + colphon.dff \ + colt45.dff \ + coltray.dff \ + Columbiangate.dff \ + comet.dff \ + comgate03.dff \ + comgate1closed.dff \ + comgate1.dff \ + comgate1open.dff \ + comgate2closed.dff \ + comgate2.dff \ + comgate2open.dff \ + compound_bits.dff \ + compound_fence.dff \ + concerth02.dff \ + concerth03.dff \ + concerth04.dff \ + concerth05.dff \ + concerth06.dff \ + concerth07.dff \ + concerth08.dff \ + concerth09.dff \ + concerth10.dff \ + concerth11.dff \ + concerth12.dff \ + concerth27.dff \ + concerth29.dff \ + concerth48.dff \ + concerth49.dff \ + concerth50.dff \ + concerth51.dff \ + concerth52.dff \ + concerth54.dff \ + concerth56.dff \ + const_woodfence30.dff \ + cooker1.dff \ + cop_blackpoly.dff \ + cop_cells.dff \ + Cop.dff \ + cop_dr_closed.dff \ + cop_dr_open.dff \ + cop_entrance.dff \ + cop_entrance_fake.dff \ + cop_fake_ext01.dff \ + cop_fake_ext02.dff \ + cop_fake_ext03.dff \ + cop_hallway.dff \ + cop_hallway_fake.dff \ + cop_mainroom.dff \ + cop_pillar_fake.dff \ + cop_pillar_shad.dff \ + cop_processing.dff \ + cop_waterref.dff \ + cougar.dff \ + courier.dff \ + cpyth.dff \ + craigpackage.dff \ + cranebasea0.dff \ + crewa.dff \ + crewb.dff \ + crgoshp010.dff \ + crocneon.dff \ + CSAlsb2.dff \ + CSAlscb2.dff \ + CSAlscb.dff \ + CSassA.dff \ + CSassB.dff \ + CSassC.dff \ + CSavery.dff \ + CSbigm.dff \ + CSBJ.dff \ + CSBuddy.dff \ + cs_cam.dff \ + CScamj.dff \ + CSCandy.dff \ + CScgona.dff \ + CScgonb.dff \ + CScgonc.dff \ + cschopb.dff \ + cs_chop.dff \ + CScolo.dff \ + CScougr.dff \ + CSdeal.dff \ + CSdiaz.dff \ + CSDick.dff \ + CSdirec.dff \ + CSdisp.dff \ + CSdlove.dff \ + CSDoris.dff \ + CSdwayn.dff \ + cservra.dff \ + cservrb.dff \ + CSFiller.dff \ + CSganga.dff \ + CSgangb.dff \ + CSgangc.dff \ + CSgona.dff \ + CSgonb.dff \ + CSgonc.dff \ + CSGonz.dff \ + CShlary.dff \ + CSJetro.dff \ + CSJezz.dff \ + CSJuggz.dff \ + CSKelly.dff \ + CSken.dff \ + CSkent.dff \ + CSLance.dff \ + cs_limo.dff \ + CSMaude.dff \ + CSmerc2.dff \ + CSMerc.dff \ + CSmike.dff \ + CSmitch.dff \ + CSpablo.dff \ + CSPapa.dff \ + CSPepe.dff \ + CSPercy.dff \ + CSphil.dff \ + CSplay10.dff \ + CSplay11.dff \ + CSplay2.dff \ + CSplay3.dff \ + CSplay4.dff \ + CSplay5.dff \ + CSplay6.dff \ + CSplay7.dff \ + CSplay8.dff \ + CSplay9.dff \ + CSplay.dff \ + cs_reef.dff \ + CSrich.dff \ + CSsonny.dff \ + CSumbto.dff \ + CSzepp.dff \ + cuban.dff \ + C_worker.dff \ + dcfan.dff \ + deaddodo.dff \ + deagl.dff \ + deco_buildkb09.dff \ + deco_buildkb25.dff \ + deco_buildkb29_dy.dff \ + deco_buildkb29_nt.dff \ + deco_buildkb_dy.dff \ + deco_buildkb_nt.dff \ + deco_copgrnd.dff \ + deco_polbuild.dff \ + deco_polgrnda10.dff \ + deco_polgrnda12.dff \ + deco_polgrnda13.dff \ + deco_polgrnda14.dff \ + deco_polgrnda16.dff \ + deco_polgrnda17.dff \ + deco_polgrnda7.dff \ + deco_polgrnda8.dff \ + deco_polgrnda9.dff \ + delcase.dff \ + deli_exterior_front.dff \ + deli_interior.dff \ + deluxo.dff \ + destruct03.dff \ + destruct04.dff \ + destruct05.dff \ + destruct1.dff \ + destruct2.dff \ + dgoona.dff \ + dgoonb.dff \ + dgoonc.dff \ + diaz.dff \ + dinghy.dff \ + dirtdetail2.dff \ + dispensary_gls.dff \ + divingboard.dff \ + dk_bombdoor.dff \ + dk_bridgesupp01.dff \ + dk_bridgesupp03.dff \ + dk_bridgesupp04.dff \ + dk_camjonesdoor.dff \ + dkcargohull2b.dff \ + dkcargohull2c.dff \ + dkcargohull2.dff \ + dk_cargoshp01.dff \ + dk_cargoshp03.dff \ + dk_cargoshp04.dff \ + dk_cargoshp05.dff \ + dk_cargoshp100.dff \ + dk_cargoshp10.dff \ + dk_cargoshp12.dff \ + dk_cargoshp24.dff \ + dk_cargoshp25.dff \ + dk_cargoshp28.dff \ + dk_cargoshp31.dff \ + dk_cargoshp32.dff \ + dk_cargoshp35.dff \ + dk_cargoshp40.dff \ + dk_cargoshp41.dff \ + dk_cargoshp47.dff \ + dk_cargoshp50.dff \ + dk_cargoshp51.dff \ + dk_cargoshp53.dff \ + dk_cargoshp54.dff \ + dk_cargoshp64.dff \ + dk_cargoshp65.dff \ + dk_cargoshp66.dff \ + dk_cargoshp68.dff \ + dk_cargoshp70.dff \ + dk_cargoshp71.dff \ + dk_cargoshp72.dff \ + dk_cargoshp73.dff \ + dk_cargoshp76.dff \ + dk_cargoshp95.dff \ + dk_dockbridge.dff \ + dk_dockroads01.dff \ + dk_dockroads02.dff \ + dk_dockroads03.dff \ + dk_dockroads04.dff \ + dk_dockroads05.dff \ + dk_dockroads06.dff \ + dk_dockroads08.dff \ + dkfirtpass2.dff \ + dk_gatebar01.dff \ + dk_gatebar02.dff \ + dk_gatebox01.dff \ + dkglue3.dff \ + dk_paynspray.dff \ + dk_paynspraydoor.dff \ + dk_rail01.dff \ + dk_rail02.dff \ + dk_rail03.dff \ + dk_rail04.dff \ + dk_rail05.dff \ + dk_rail06.dff \ + dk_rail07.dff \ + dk_rdsignsbr.dff \ + dk_rdsignsn.dff \ + dk_reef.dff \ + dkshipment01.dff \ + dkshipment03.dff \ + dkshipment05.dff \ + dk_shipstair.dff \ + dk_waretank.dff \ + dk_waretankdoor1.dff \ + doc_crane_cab01.dff \ + doc_crane_cab02.dff \ + doc_crane_cab03.dff \ + doc_crane_cab04.dff \ + doc_crane_cab0.dff \ + doc_craneeggs04.dff \ + doc_dockwareold.dff \ + dock_camjones.dff \ + dockcranescale01.dff \ + dockcranescale0.dff \ + dockfence.dff \ + dockfuel02.dff \ + dockfuel07.dff \ + dockgate01.dff \ + dockgate02.dff \ + dock_grassarea.dff \ + dockgrass.dff \ + dock_props01.dff \ + dock_props02.dff \ + docks10.dff \ + docks10rail.dff \ + docks21.dff \ + docks28.dff \ + docks29.dff \ + docks30.dff \ + docks31.dff \ + docks32.dff \ + docks37.dff \ + docks40.dff \ + docks42.dff \ + docks43.dff \ + docks46.dff \ + docks47.dff \ + docks48.dff \ + docks49.dff \ + docks50.dff \ + docks51.dff \ + docks52.dff \ + docks53.dff \ + docks60.dff \ + docks61.dff \ + docks62.dff \ + docks85.dff \ + docks92.dff \ + docks93.dff \ + docks95.dff \ + docks96.dff \ + docksprops04.dff \ + docksprops10.dff \ + docksprops11.dff \ + docksprops12.dff \ + docksprops13.dff \ + docksprops14.dff \ + docksprops15.dff \ + docksware01.dff \ + doc_shedbig30.dff \ + dolphin.dff \ + doontoon03.dff \ + doontoon_04_bit2.dff \ + doontoon04.dff \ + doontoon09.dff \ + doontoon10.dff \ + doontoon11.dff \ + doontoon13.dff \ + doontoon14.dff \ + doontoon16.dff \ + doontoon18.dff \ + doontoon19.dff \ + doontoon20.dff \ + doontoon21.dff \ + doontoon_22_b.dff \ + doontoon22.dff \ + doontoon23.dff \ + doontoon24.dff \ + doontoon25_alfas.dff \ + doontoon25.dff \ + doontoon26.dff \ + doontoon27.dff \ + doontoon28.dff \ + doontoon29.dff \ + doontoon30.dff \ + doontoon31.dff \ + doontoon32_alfa.dff \ + doontoon32.dff \ + doontoon34.dff \ + doontoon35.dff \ + doontoon36.dff \ + doontoon37.dff \ + doontoon38.dff \ + doontoon39.dff \ + doontoon40.dff \ + doontoon41.dff \ + doontoon44.dff \ + doontoon45.dff \ + doontoon47.dff \ + doontoon49.dff \ + doontoon50.dff \ + doontoon51.dff \ + doontoon53.dff \ + doontoon54.dff \ + doontoon55.dff \ + doontoon56.dff \ + doontoon58.dff \ + doontoon59.dff \ + doontoon61.dff \ + doontoon62.dff \ + doontoon63.dff \ + doontoon65.dff \ + doontoon66_bushes01.dff \ + doontoon66.dff \ + doontoon67.dff \ + doontoon70.dff \ + doontoon72b.dff \ + doontoon72.dff \ + doontoon73b.dff \ + doontoon73.dff \ + doontoon74.dff \ + doontoon76.dff \ + doontoon77.dff \ + doontoon_newbit.dff \ + doontoon_shops.dff \ + doontoon_top01.dff \ + doontoon_top02.dff \ + doontoon_top03.dff \ + doontoon_top04.dff \ + doontoon_top05.dff \ + doontoon_top06.dff \ + doontoon_top07.dff \ + doontoon_top08.dff \ + doontoon_top09.dff \ + doontoon_top10.dff \ + doontoon_top_bb.dff \ + doublestreetlght1.dff \ + dowbikershop.dff \ + dow_pizzaplacedt.dff \ + drchair.dff \ + drink.dff \ + drive_flowers06.dff \ + drug_blue.dff \ + drug_green.dff \ + drug_orange.dff \ + drugpak.dff \ + drug_red.dff \ + drugstoreext.dff \ + drugstoreint.dff \ + drug_white.dff \ + drug_yellow.dff \ + drycleaners.dff \ + dshotgn.dff \ + dt_20_holewall.dff \ + dtbloodring_a.dff \ + dt_bowlsign.dff \ + dt_carpsaces03.dff \ + dt_carpsaces06.dff \ + dt_carpsaces13.dff \ + dt_carpsaces14.dff \ + dt_carpsaces15.dff \ + dt_carpsaces2.dff \ + dt_carspaces1.dff \ + dt_compound_alfas.dff \ + dt_compound_esc.dff \ + dt_compound_roof.dff \ + dtdirtring_a.dff \ + dt_gspot_stairs.dff \ + dt_hotel_waldt.dff \ + dthotring_a.dff \ + dt_marscafe.dff \ + dt_nbeach01.dff \ + dt_nbeach02.dff \ + dt_nbeach03.dff \ + dt_nbeach05.dff \ + dt_nbeach06.dff \ + dt_nbeach07.dff \ + dt_nbeach08.dff \ + dtn_buildnew1.dff \ + dtn_buildnew2.dff \ + dtn_cablesa.dff \ + dtn_cablesb.dff \ + dtn_cablesc.dff \ + dtn_cablesd.dff \ + dtn_cablese.dff \ + dtn_cablesf.dff \ + dtn_comp_polysa.dff \ + dtn_hospital_fence.dff \ + dt_nitelites1.dff \ + dt_nitelites2.dff \ + dt_nitelites3.dff \ + dt_nitelites4.dff \ + dt_nitelites5.dff \ + dt_nitelites6.dff \ + dt_nitelites7.dff \ + dt_nitelites8.dff \ + dtns_kbshop1b.dff \ + dtns_kbshop1.dff \ + dtn_staddoora.dff \ + dtn_staddoorb.dff \ + dtn_stadspacesA.dff \ + dtn_stadspacesB.dff \ + dtn_veg3.dff \ + dtn_veg4.dff \ + dtoon62_spaces.dff \ + dt_roofshit1.dff \ + dt_savedra.dff \ + dt_savedrb.dff \ + dt_savedrc.dff \ + dt_savedrd.dff \ + dts_bbdoor.dff \ + dt_scabby_fence01.dff \ + dt_scabby_fence03.dff \ + dt_scabby_fence04.dff \ + dt_scabby_fence07.dff \ + dts_cop_park.dff \ + dts_glassplane_a.dff \ + dts_gs_liftdoorL.dff \ + dts_gs_liftdoorR.dff \ + dt_sheraton_fence.dff \ + dts_nitelites1.dff \ + dts_offglassa.dff \ + dts_offglassb.dff \ + dts_office2.dff \ + dt_spotlite_01.dff \ + dts_soapytitwank_a.dff \ + dts_soapytitwank_b.dff \ + dts_spot_a.dff \ + dt_stadium__alfa1.dff \ + dt_stadiumalfa2.dff \ + dt_stadium_entrance.dff \ + dt_stadiumland_a.dff \ + dt_stadiumland_b.dff \ + dt_stadiumland_c.dff \ + dt_stadiumland_d.dff \ + dt_stadium_start.dff \ + dts_telwire1.dff \ + dts_telwire2.dff \ + dts_telwire4.dff \ + dts_telwire9.dff \ + dts_tit_stares.dff \ + dt_track_flags01.dff \ + dt_tracktyres_01.dff \ + dt_tracktyres_02.dff \ + dt_tracktyres_03.dff \ + dt_tracktyres_04.dff \ + dt_tracktyres_05.dff \ + dt_tracktyres_06.dff \ + dt_VAJ.dff \ + dump1.dff \ + dwn_bikerbarfrnt.dff \ + dwnmiamilanda.dff \ + dw_scuz_kb.dff \ + dynamite.dff \ + dzplant.dff \ + Electricgate.dff \ + elevator.dff \ + enforcer.dff \ + Esc_step.dff \ + esperant.dff \ + espreso.dff \ + exhaust.dff \ + expltrk.dff \ + faggio.dff \ + faketarget.dff \ + fbicar.dff \ + fbi.dff \ + fbiranch.dff \ + fence01.dff \ + fencehaiti.dff \ + fencehaitism.dff \ + fencesmallb.dff \ + fffinhaiti.dff \ + fingers.dff \ + fire_hydrant.dff \ + fireman.dff \ + firetruk.dff \ + fireyfire01.dff \ + fireyfire.dff \ + fish1s.dff \ + fish1single.dff \ + fish2s.dff \ + fish2single.dff \ + fish3s.dff \ + fish3single.dff \ + fixedbects06.dff \ + flame.dff \ + Flame_tins.dff \ + flatbedbak.dff \ + flatbed.dff \ + floozya.dff \ + floozyb.dff \ + floozyc.dff \ + Frame1Damaged.dff \ + Frame1.dff \ + freeway.dff \ + FSfa.dff \ + fuckedcars01.dff \ + fuckedcars02.dff \ + fuckedcars03.dff \ + fuckedcars04.dff \ + fuckedcars05.dff \ + fuckedcars06.dff \ + gangb.dff \ + gangbur.dff \ + gardenbencha.dff \ + Gasgrenade.dff \ + Gash_bags1.dff \ + gashbags2.dff \ + gate4.dff \ + gate5.dff \ + gcfan.dff \ + Gda.dff \ + Gdb.dff \ + Gdyn_barrier17.dff \ + GeoSphere01.dff \ + gf_clubhouse1.dff \ + gf_clublight1_01.dff \ + gf_drdivide1_01.dff \ + gf_drivingrange1.dff \ + gf_flagstees1.dff \ + gf_flagstees2.dff \ + gf_flagstees3.dff \ + gf_flagstees42.dff \ + gf_flagstees43.dff \ + gf_flagstees4.dff \ + gf_golfwall.dff \ + gf_hedge1_01.dff \ + gf_hedge2_01.dff \ + gf_hedge3_01.dff \ + gf_hedge4_01.dff \ + gf_hedge5_01.dff \ + gf_hedge6_01.dff \ + gf_ivy_balcny_01.dff \ + gf_ivy_balcny_07.dff \ + gf_mainisl2b_01.dff \ + gf_mainisl4_01.dff \ + gf_mainisl5_01.dff \ + gf_mainisland1a_01.dff \ + gf_mainisland1b_01.dff \ + gf_mainisland1m_01.dff \ + gf_mainisland2a_01.dff \ + gf_mainisland3a_01.dff \ + gf_mainisland3b_01.dff \ + gf_mainisland6a_01.dff \ + gf_mainisland6b_01.dff \ + gf_mainisland6n_01.dff \ + gf_northbridge1.dff \ + gf_northisland1b.dff \ + gf_northisland1c.dff \ + gf_northisland1.dff \ + gf_northisland2.dff \ + gf_northisland3.dff \ + gf_pedbridge3.dff \ + gf_picket1_01.dff \ + gf_planters2.dff \ + gf_planters4.dff \ + gf_rollerjump.dff \ + gf_signPlane01.dff \ + gf_smallisland1_01.dff \ + gf_smallislebridge1.dff \ + gf_stadlight1_02.dff \ + gf_tennisfence.dff \ + gf_tree1_01.dff \ + gf_tree2_02.dff \ + gf_treesfw1_01.dff \ + gf_treesfw2_01.dff \ + gf_treesfw3_01.dff \ + gf_treesfw4_01.dff \ + gf_treesfw5_01.dff \ + gf_water01.dff \ + gf_water02.dff \ + gf_water03.dff \ + gf_woodbridge1_01.dff \ + ggneonsign2.dff \ + ggsalonsign1.dff \ + glassfx_composh.dff \ + glendale.dff \ + glue_corner01.dff \ + glue_straight01.dff \ + glue_straight02.dff \ + gluetest03.dff \ + gluetest19.dff \ + golfclub.dff \ + golf_gatesclosed.dff \ + golf_gatesopen.dff \ + golfroad1.dff \ + golfroad2.dff \ + golfroad3.dff \ + golfroad4.dff \ + golfroad5.dff \ + golfroad7.dff \ + gpass.dff \ + grassbitsjm.dff \ + grasspatch.dff \ + greenwoo.dff \ + grenade.dff \ + guesta.dff \ + guestb.dff \ + guestc.dff \ + guestd.dff \ + gunbox.dff \ + guncolt.dff \ + Gun_docs.dff \ + haicoast1.dff \ + haicoast2.dff \ + haicoast3.dff \ + haicoast4.dff \ + haicoast5.dff \ + haicoasta2.dff \ + haicoastfirst.dff \ + haigrnda1.dff \ + hai_houses27.dff \ + hai_roadsigns.dff \ + haitaxidr.dff \ + haitbuildws14.dff \ + haitgangshop.dff \ + haitgangshopdoor.dff \ + HAITI1RD.dff \ + haitibuilds.dff \ + haiticar08.dff \ + haiticar09.dff \ + haiticar10.dff \ + haiticar12.dff \ + haiticar13.dff \ + haiticent.dff \ + haitihut1.dff \ + haitihut2.dff \ + haitihut3.dff \ + haitimall.dff \ + haitinwire11.dff \ + haitinwire22.dff \ + haitinwire23.dff \ + haitiroadbuild.dff \ + haitiskyg.dff \ + haitiskyt.dff \ + haitistation01.dff \ + haititag.dff \ + haitroada.dff \ + haitroadb.dff \ + haitroadc.dff \ + haiwashpaynspray.dff \ + haiwshpnsdoor.dff \ + haiwshpspryneon1.dff \ + haiwsignday_dy.dff \ + haiwsignight_nt.dff \ + hammer.dff \ + hatrifl.dff \ + hatsog.dff \ + havanahouse01.dff \ + havanahouse02.dff \ + havanahouse03.dff \ + havanahouse04.dff \ + havanahouse06.dff \ + havanahouse07.dff \ + havanahouse09.dff \ + havanahouse10.dff \ + havanahouse11.dff \ + havanahouse12b.dff \ + havanahouse12.dff \ + havbackbit.dff \ + havbit01.dff \ + havbit02.dff \ + havbit03.dff \ + havbuild.dff \ + hav_car_show_int.dff \ + hav_car_show_out2.dff \ + hav_car_show_out.dff \ + havgangshop.dff \ + hav_garagedoor02.dff \ + hav_garagedoor03.dff \ + hav_garagedoor04.dff \ + hav_garagedoor1.dff \ + havhotel.dff \ + hdrugfactoryintb.dff \ + hdrugfactoryint.dff \ + hdrugfactorypol.dff \ + health.dff \ + Heli_box.dff \ + Helipad0.dff \ + helix_barrier.dff \ + hermes.dff \ + hfobe.dff \ + hfori.dff \ + HFOST.dff \ + HFOTR.dff \ + hfybe.dff \ + HFYBU.dff \ + HFYCG.dff \ + HFYMD.dff \ + HFYPR.dff \ + HFYRI.dff \ + HFYST.dff \ + hibita1.dff \ + hlrycar.dff \ + hmobe.dff \ + hmoca.dff \ + hmori.dff \ + hmost.dff \ + hmotr.dff \ + hmyap.dff \ + hmybe.dff \ + hmyri.dff \ + hmyst.dff \ + Hna.dff \ + Hnb.dff \ + hogans_alley_b.dff \ + hogans_alley.dff \ + hospital.dff \ + hot_bar1_01.dff \ + hot_drawers1_01.dff \ + hoteltrees01.dff \ + hoteltrees02.dff \ + hoteltrees03.dff \ + hoteltrees05.dff \ + hoteltrees06.dff \ + hot_mags1.dff \ + hotrina.dff \ + hotrinb.dff \ + hotring.dff \ + hot_room317.dff \ + hotroomfan.dff \ + hotshad1.dff \ + hot_trans1.dff \ + htable.dff \ + ht_doors.dff \ + ht_fans_dy.dff \ + ht_fans_nt.dff \ + ht_kb_couch1_dy.dff \ + ht_kb_couch1_nt.dff \ + htl_dco_chair03_dy.dff \ + htl_dco_chair03_nt.dff \ + htl_exterior03_dy.dff \ + htl_exterior03_nt.dff \ + htl_fan_rotate_dy.dff \ + htl_fan_rotate_nt.dff \ + htl_fan_static_dy.dff \ + htl_fan_static_nt.dff \ + htl_gls_1_dy.dff \ + htl_gls_1_nt.dff \ + htl_gls_2_dy.dff \ + htl_gls_2_nt.dff \ + htl_gls_3_dy.dff \ + htl_gls_3_nt.dff \ + htl_gls_lobby.dff \ + htl_lftdoor1_dy.dff \ + htl_lftdoor1_nt.dff \ + htl_maintiles_dy.dff \ + htl_maintiles_nt.dff \ + ht_mainfloor2_dy.dff \ + ht_mainfloor2_nt.dff \ + ht_mainfloor_dy.dff \ + ht_mainfloor_nt.dff \ + htnroadnew.dff \ + ht_upstairs.dff \ + ht_veg01_dy.dff \ + ht_veg01_nt.dff \ + ht_veg02_dy.dff \ + ht_veg02_nt.dff \ + ht_veg04_dy.dff \ + ht_veg04_nt.dff \ + hunter.dff \ + hvood01.dff \ + hvood.dff \ + hvoodext.dff \ + icedoor.dff \ + idaho.dff \ + IGalscb.dff \ + IGbuddy.dff \ + IGBudy2.dff \ + IGBudy3.dff \ + IGCandy.dff \ + IGColon.dff \ + IGDiaz.dff \ + IGDick.dff \ + IGGonz.dff \ + IGHlary.dff \ + IGHlry2.dff \ + IGJezz.dff \ + IGKen.dff \ + IGMerc2.dff \ + IGMerc.dff \ + IGMike2.dff \ + IGmike.dff \ + IGPercy.dff \ + IGphil2.dff \ + IGPhil3.dff \ + IGPhil.dff \ + IGSonny.dff \ + ihaitsnip.dff \ + ihaitwarhus.dff \ + ih-sjmcrush.dff \ + ih-sjmphil.dff \ + infernus.dff \ + info.dff \ + ingramsl.dff \ + instruments.dff \ + invite.dff \ + IslandLOD1.dff \ + IslandLOD2.dff \ + IslandLODbeach.dff \ + IslandLODmainland.dff \ + jellyfish01.dff \ + jellyfish.dff \ + jetmax.dff \ + jewler_opac_stf.dff \ + jewler_shop.dff \ + jfoto.dff \ + jmoto.dff \ + jump_box_02.dff \ + jump_box108.dff \ + jump_box112.dff \ + jump_box113.dff \ + jump_box114.dff \ + jump_box115.dff \ + junk_tyre.dff \ + jw_coffin.dff \ + jzgirla.dff \ + jzgirlb.dff \ + katana.dff \ + kaufman.dff \ + kb_canopy_test.dff \ + kb_chr_tbl_test.dff \ + kb_couch06.dff \ + kb_couch09.dff \ + kb_couch15.dff \ + kb_planterbox.dff \ + kb_planter+bush2.dff \ + kb_planterbush2.dff \ + kb_planter+bush.dff \ + kb_pot_1.dff \ + kentpaul.dff \ + kettle.dff \ + keycard.dff \ + KgoonA.dff \ + KgoonB.dff \ + kickbannerside4.dff \ + kickbannerstart01.dff \ + kickbus03.dff \ + kickbus04.dff \ + kickbus05.dff \ + kickcar06.dff \ + kickcar17.dff \ + kickcar23.dff \ + kickcar28.dff \ + kickcar36.dff \ + kickcar3.dff \ + kickdetail2.dff \ + kickdetail.dff \ + kickdrums05.dff \ + kickdrums07.dff \ + kickdrums16.dff \ + kickdrums1.dff \ + kickdrums2.dff \ + kickfence.dff \ + kickhay1.dff \ + kickhay2.dff \ + kickhay3.dff \ + kickobs02.dff \ + kickobs1.dff \ + kickramp02.dff \ + kickramp03.dff \ + kickramp04.dff \ + kickramp1.dff \ + kickstart.dff \ + kicktyres03.dff \ + kicktyres2.dff \ + kicktyres.dff \ + killfrenzy.dff \ + knifecur.dff \ + labiggrass01.dff \ + labiggrass02.dff \ + labiggrass.dff \ + lampost_coast.dff \ + lamppost1.dff \ + lamppost2.dff \ + lamppost3.dff \ + landjump2.dff \ + landjump.dff \ + landstal.dff \ + laser.dff \ + lawcutmapa.dff \ + lawcutmapb.dff \ + lawcutmapc.dff \ + lawdoor.dff \ + lawyer.dff \ + lcfan.dff \ + lf_banner.dff \ + lf_mediastage.dff \ + lh2glue01.dff \ + lh2glue.dff \ + lha_carfence.dff \ + lhaitcut.dff \ + lhaitnewbt.dff \ + lharoadab.dff \ + lhavnew_bush.dff \ + lhavroad1.dff \ + lhavroad2.dff \ + lhbacknbit.dff \ + lhbasket.dff \ + lhbillboard05xx.dff \ + lhblbrd1.dff \ + lhblbrd2.dff \ + lhblbrd3.dff \ + lhblbrd8.dff \ + lh_boxgirdbridge.dff \ + lhbuild034.dff \ + lhbuild1b.dff \ + lh_buildingg1.dff \ + lh_buildingg2.dff \ + lh_buildingg3.dff \ + lh_buildingw1.dff \ + lhcargo.dff \ + lh_carshow_windows.dff \ + lhcoastsky1.dff \ + lhcoastsky2.dff \ + lh_crushefuck.dff \ + lh_crushers08.dff \ + lh_crushers09.dff \ + lh_crushers10.dff \ + lh_crushers11.dff \ + lh_crushers12.dff \ + lh_crushers13.dff \ + lh_crushers14.dff \ + lh_crushers15.dff \ + lhddirt.dff \ + lhdirt2s.dff \ + lhdirtpass1.dff \ + lhdirtpassax1.dff \ + lhdirtpassz.dff \ + lh_dockposts1.dff \ + lh_dockposts2.dff \ + lh_dockposts3.dff \ + lh_dockpostsN1.dff \ + lh_dockpostsN2.dff \ + lh_dockpostsN3.dff \ + lhglue01.dff \ + lhglue3.dff \ + lhgrasspatchg.dff \ + lhgrnda1.dff \ + lh_groundstains.dff \ + lh_haiblockc1.dff \ + lh_haiblockc2.dff \ + lh_haiblockc3.dff \ + lh-hitbuild1aa.dff \ + lh_hitbuild1a.dff \ + lh_icecream01.dff \ + lh_icecream02.dff \ + lh_icedoor.dff \ + lh_imp_exp01.dff \ + lh_imp_exp02.dff \ + lh_imp_exp03.dff \ + lh_imp_exp04.dff \ + lhjetty.dff \ + lh_misc02.dff \ + lhouse_barrier1.dff \ + lhouse_barrier2.dff \ + lhouse_barrier3.dff \ + lhparkline2.dff \ + lh_phils.dff \ + lh_philsext.dff \ + lh_philsint.dff \ + lh_pianoshop.dff \ + lh_pizzaplaceb.dff \ + lh_pizzaplace.dff \ + lh_race01.dff \ + lh_race02.dff \ + lh_race03.dff \ + lh_race04.dff \ + lh_race05.dff \ + lh_race06.dff \ + lh_racemap.dff \ + lhroadc1.dff \ + lhroadc2.dff \ + lhroadc3.dff \ + lhroadc4.dff \ + lhroofst01.dff \ + lhroofst02.dff \ + lhroofst03.dff \ + lhroofst04.dff \ + lhroofst07.dff \ + lhroofst08.dff \ + lhroofst09.dff \ + lhroofst10.dff \ + lhroofst11.dff \ + lhroofst12.dff \ + lhroofst14.dff \ + lhroofst16.dff \ + lhroofst17.dff \ + lhroofst18.dff \ + lhroofst19.dff \ + lhroofstuffg.dff \ + lhroofwee.dff \ + lhsbackbit.dff \ + lh_showdoor03.dff \ + lh_showdoor1.dff \ + lhstairs7.dff \ + lhsteps3.dff \ + lh_strikethrough.dff \ + lhtankdoor.dff \ + lh_taxifirm01.dff \ + lh_taxifirm02.dff \ + lhwallbit.dff \ + lihawasteb2.dff \ + line.dff \ + linerun.dff \ + lithahousebk2.dff \ + litharoada2.dff \ + litharoada3.dff \ + litharoada4.dff \ + litharoada6.dff \ + litharoada8.dff \ + litharoada9.dff \ + lithavabit01.dff \ + lithavabit03.dff \ + lithavabit04.dff \ + lithavabit05.dff \ + lithavabit06.dff \ + lithavabit07.dff \ + lithavabit08.dff \ + lithavabit09.dff \ + lithavabit10.dff \ + lithavabit11.dff \ + lithavabit12.dff \ + lithavroad3.dff \ + lithawaste1.dff \ + lithawaste2.dff \ + lithit14.dff \ + lithitsjm.dff \ + litlhaitibuilding02.dff \ + litlhaitibuilding06.dff \ + litlhaitibuilding07.dff \ + litlhaitibuilding09.dff \ + littlehabroad.dff \ + littlehacoast02.dff \ + littlehacoast05.dff \ + littleha_hardwareb.dff \ + littleha_hardware.dff \ + littleha_police.dff \ + littlharoadgg2.dff \ + lobster.dff \ + lobtray.dff \ + LODachb1.dff \ + LODachb.dff \ + LODachbit02.dff \ + LODachbit03.dff \ + LODachbit04b.dff \ + LODachbit04.dff \ + LODachbit05.dff \ + LODachbit06.dff \ + LODach_police.dff \ + LODachroads01.dff \ + LODachroads02.dff \ + LODachroads03.dff \ + LODachroads09.dff \ + LODachsand1.dff \ + LODachsand2b.dff \ + LODachsand2.dff \ + LODachwjetty01.dff \ + LODachwjetty02.dff \ + LODacne.dff \ + LODallbit.dff \ + lodalleys1_01_dy.dff \ + lodalleys1_01_nt.dff \ + lodalleys1b_01_dy.dff \ + lodalleys1b_01_nt.dff \ + lodalleys2_01_dy.dff \ + lodalleys2_01_nt.dff \ + lodalleys3_01_dy.dff \ + lodalleys3_01_nt.dff \ + lodalleys3b_01_dy.dff \ + lodalleys3b_01_nt.dff \ + LODallroof01.dff \ + lodanahouse01.dff \ + lodanahouse02.dff \ + lodanahouse03.dff \ + lodanahouse04.dff \ + lodanahouse06.dff \ + lodanahouse07.dff \ + lodanahouse09.dff \ + lodanahouse10.dff \ + lodanahouse11.dff \ + LODanahouse12.dff \ + LODanrd1_dy.dff \ + LODanrd1_nt.dff \ + LODanrda03_dy.dff \ + LODanrda03_nt.dff \ + LODanrda04_dy.dff \ + LODanrda04_nt.dff \ + LODanrda05_dy.dff \ + LODanrda05_nt.dff \ + LODanrda06_dy.dff \ + LODanrda06_nt.dff \ + LODanroad04.dff \ + LODanroad05.dff \ + LODanrod2b_dy.dff \ + LODanrod2b_nt.dff \ + LODargohull2.dff \ + lodasket.dff \ + LODastage.dff \ + LODastageint.dff \ + LODavehotel.dff \ + lodawasteb2.dff \ + lodawyersfront.dff \ + lodbackbit.dff \ + LODbacklot1.dff \ + lod_backside.dff \ + LOD_beacon2_dy.dff \ + LOD_beacon2_nt.dff \ + LOD_beacon_dy.dff \ + LOD_beacon_nt.dff \ + lodbighotel_dy.dff \ + lodbighotel_nt.dff \ + lodbighotpool.dff \ + LODbigshops1.dff \ + LODbigshops2.dff \ + LODbillboards1.dff \ + LODbillboards2.dff \ + LODbillboards3.dff \ + LODbillboards4.dff \ + LODbillboards5.dff \ + lodbit02.dff \ + lodbit03.dff \ + LODbowlsign.dff \ + LODboxgirdbridge.dff \ + LODbphq.dff \ + LODbridgesupp01.dff \ + LODbridgesupp03.dff \ + LODbridgesupp04.dff \ + LOD_bridgew.dff \ + LODbstage.dff \ + LODbstageint.dff \ + lodbuil19wall.dff \ + lod_build01.dff \ + LODbuild01.dff \ + lod_build02.dff \ + LODbuild02.dff \ + lod_build03.dff \ + lod_build04.dff \ + lod_build05.dff \ + lodbuild.dff \ + LODbuilding01.dff \ + LODbuildingg1.dff \ + LODbuildingg2.dff \ + LODbuildingg3.dff \ + LODbuildingw1.dff \ + LODbuildkb23_dy.dff \ + LODbuildkb23_nt.dff \ + LODbuildkb27_dy.dff \ + LODbuildkb27_nt.dff \ + LODbuildkb2_dy.dff \ + LODbuildkb2_nt.dff \ + LOD_buildnew1.dff \ + LOD_buildnew2.dff \ + LODbuildws02.dff \ + LODbuildws03.dff \ + LODbuildws04.dff \ + LODbuildws05.dff \ + LODbuildws06.dff \ + LODbuildws07.dff \ + LODbuildws08.dff \ + LODbuildws09.dff \ + LODbuildws10.dff \ + LODbuildws12.dff \ + LODbuildws13.dff \ + LODbuildws14.dff \ + LODbuildws16.dff \ + LODbuildws17.dff \ + LODbuildws18.dff \ + LODbuildws19.dff \ + LODbuildws20.dff \ + LODbuildws21.dff \ + lodbuildws22.dff \ + lodbuildws2326.dff \ + LODbuildws2329.dff \ + LODbuildws23.dff \ + LODbuildws24.dff \ + LODbuildws25.dff \ + lodbuildws264.dff \ + LODbuildws26.dff \ + LODbuildws27.dff \ + LODbuildws28.dff \ + LODbuildws29.dff \ + LODbuildws30.dff \ + lodbuildws312.dff \ + lodbuildws31.dff \ + lodbuildws322.dff \ + lodbuildws32.dff \ + lodbuildws332.dff \ + lodbuildws33.dff \ + LODbuildws35.dff \ + LODbuildws36.dff \ + LODbuildws40.dff \ + LODbuildws41.dff \ + lodbuildws42.dff \ + LODbuildws43.dff \ + lodbuildws43ins.dff \ + LODbuildws44.dff \ + LODbuildws45.dff \ + LODcargoshp03.dff \ + LODcargoshp04.dff \ + LODcargoshp05.dff \ + LODcargoshp24.dff \ + LODcargoshp25.dff \ + LODcargoshp28.dff \ + LODcargoshp32.dff \ + LODcargoshp35.dff \ + LODcargoshp40.dff \ + LODcargoshp41.dff \ + LODcargoshp47.dff \ + LODcargoshp50.dff \ + LODcargoshp51.dff \ + LODcargoshp64.dff \ + LODcargoshp70.dff \ + LODcargoshp71.dff \ + LODcargoshp72.dff \ + LODcargoshp73.dff \ + LODcarparkterm1.dff \ + LOD_car_show_out.dff \ + LODcasagrande_dy.dff \ + LODcasagrande_nt.dff \ + lodcbtwbrdge.dff \ + LODchariot.dff \ + lodchariot_lghts01.dff \ + lodchariot_lghts02.dff \ + LODchbta.dff \ + LODchbtb2.dff \ + LODchbtb.dff \ + LODchbt.dff \ + LODchlo1.dff \ + LODcland01.dff \ + LODcland03.dff \ + LODcland05.dff \ + LODcland09.dff \ + LODcland10.dff \ + LODcland11.dff \ + LODcland12.dff \ + LODcland13_gf.dff \ + LODclevelander_dy.dff \ + LODclevelander_nt.dff \ + LODclubback.dff \ + LODclubhouse1.dff \ + LODclubout_dy.dff \ + LODclubout_nt.dff \ + LODcoast04.dff \ + LODcoast06.dff \ + LODcoast08.dff \ + LODcoast1.dff \ + LODcoast2.dff \ + LODcoast3.dff \ + LODcoast4.dff \ + LODcoast5.dff \ + LODcoasta2.dff \ + LODcoast.dff \ + LODcoastfirst.dff \ + LODcolony2_dy.dff \ + LODcolony2_nt.dff \ + LOD_colony_dy.dff \ + LODcolony_dy.dff \ + LOD_colony_nt.dff \ + LODcolony_nt.dff \ + LODcompound_esc.dff \ + LODcompound_roof.dff \ + LODconcwall1.dff \ + lodcpaynspray.dff \ + lodcrushbn.dff \ + LODcrushefuck.dff \ + LODcrushers08.dff \ + LODcrushers10.dff \ + LODcrushers12.dff \ + LODcrushers14.dff \ + lodcrushju.dff \ + lodcrushmnf.dff \ + LODcstage.dff \ + LODcstageint.dff \ + LODdbridge.dff \ + lodd_build2.dff \ + LODd_buildnew.dff \ + LODd_downtown02.dff \ + LODd_downtown03.dff \ + LODd_downtown04.dff \ + LODd_downtown05.dff \ + LODd_downtown06.dff \ + LODd_downtown07.dff \ + LODd_downtown08.dff \ + LODd_downtown09.dff \ + LODd_downtown10.dff \ + LODd_downtown11.dff \ + LODd_downtown12.dff \ + LODd_downtown13.dff \ + LODd_downtown14.dff \ + LODd_downtown15.dff \ + LODd_downtown16.dff \ + LODd_downtown17.dff \ + LODd_downtown_new1.dff \ + LODd_downtown_new2.dff \ + LODdingsite1.dff \ + LODdockbridge.dff \ + LODdockroads01.dff \ + LODdockroads02.dff \ + LODdockroads03.dff \ + LODdockroads04.dff \ + LODdockroads05.dff \ + LODdockroads06.dff \ + LODdockroads08.dff \ + LODdockwall1.dff \ + LOD_dockwareold.dff \ + LODdrivingrange1.dff \ + LODd_stadiuma.dff \ + LODdstageext.dff \ + LODecopromnd2.dff \ + LODecopromnd.dff \ + LODecoshop01.dff \ + LODecoshop02.dff \ + LODecoshop03.dff \ + LODer2.dff \ + LODextertank.dff \ + LODfiretrucks.dff \ + LODflargetank1.dff \ + LODflyingschool1.dff \ + LODfreightterminal1.dff \ + lod_frntstep.dff \ + LODfroad1.dff \ + LODfroad2.dff \ + LODfroad3.dff \ + LODfroad4.dff \ + LODfroad5.dff \ + LODfsthus.dff \ + lodgarage02.dff \ + lodgarage03.dff \ + lodgarage1.dff \ + LODgarden1.dff \ + LODgate03.dff \ + LODgate1.dff \ + LODgate2.dff \ + LODgatesA.dff \ + LODgatesB.dff \ + LODgolfwall.dff \ + LOD_grnd2.dff \ + LOD_grnd3.dff \ + LOD_grnd4.dff \ + LOD_grnd5.dff \ + LOD_grnd6.dff \ + LODgrnda1.dff \ + LODground1.dff \ + LODground2.dff \ + LODground3.dff \ + LODground4.dff \ + LODground5.dff \ + LODgroundplane.dff \ + LOD_grssypatch.dff \ + LODgstoreext.dff \ + LODhafinrd.dff \ + LODhahousebk2.dff \ + LODhaiblockc1.dff \ + LODhaiblockc2.dff \ + LODhaiblockc3.dff \ + lodh_ammu.dff \ + LODhangar1_01.dff \ + LODhangar1_04.dff \ + LODhangar1.dff \ + LODharoada2.dff \ + LODharoada3.dff \ + LODharoada4.dff \ + LODharoada6.dff \ + LODharoada8.dff \ + LODharoada9.dff \ + lodhavabit01.dff \ + lodhavabit04.dff \ + lodhavabit05.dff \ + lodhavabit06.dff \ + lodhavabit07.dff \ + lodhavabit08.dff \ + lodhavabit09.dff \ + lodhavabit10.dff \ + lodhavabit11.dff \ + lodhavabit12.dff \ + LODhavroad3.dff \ + lodhawaste1.dff \ + lodhawaste2.dff \ + LODhbit04bgrd1.dff \ + LODhbit04grd2.dff \ + LODh_brdgsup1.dff \ + LODhbtjetty01.dff \ + LODhbtjetty02.dff \ + LODhbuild003.dff \ + LODhbuild005.dff \ + LODhbuild013.dff \ + LODhbuild014.dff \ + LODhbuild015.dff \ + LODhbuild017.dff \ + LODhbuild018.dff \ + LODhbuild020.dff \ + LODhbuild021.dff \ + LODhbuild025.dff \ + LODhbuild030.dff \ + LODhbuild033.dff \ + LODhbuild040.dff \ + lodhbuild043.dff \ + LODhbuild050.dff \ + LODhbuild058.dff \ + LODhbuild062.dff \ + LODhbuild063.dff \ + LODhbuild066.dff \ + LODhbuild068.dff \ + LODhbuild069.dff \ + LODhbuild071.dff \ + LODhbuild072.dff \ + LODhbuild073.dff \ + LODhbuild074.dff \ + LODhbuild075.dff \ + lodhbuild0812.dff \ + lodhbuild081.dff \ + LODhbuild111.dff \ + LODhbuild112.dff \ + lodhbuild114.dff \ + LODhbuild116.dff \ + LODhbuild119.dff \ + LODhbuild120.dff \ + LODhbuild121.dff \ + LODhbuild184.dff \ + LODhbuild187.dff \ + lodhbuild192.dff \ + lodhbuild195.dff \ + LODhbuild198.dff \ + lodhbuild203.dff \ + lodhbuild213.dff \ + lodhbuild214.dff \ + lodhbuild215.dff \ + lodhbuild216.dff \ + LODh_deco01.dff \ + LODh_deco02.dff \ + LODh_deco03.dff \ + LODh_deco04.dff \ + LODh_deco05.dff \ + LODh_hardwares.dff \ + LODh_hosp02.dff \ + LODh_hosp03.dff \ + LODh_hospgrnd.dff \ + LODh_hospital.dff \ + LODhipment01.dff \ + LODhipment03.dff \ + LODhipment05.dff \ + LODhit14.dff \ + LODhitbuild1aa.dff \ + LODhitbuild1a.dff \ + LODhitsjm.dff \ + LODhland_01.dff \ + LODhnewsky1.dff \ + LODhnewsky2.dff \ + LODhnewsky3.dff \ + LODhookerinn1.dff \ + LODhoose.dff \ + LOD_hotel01.dff \ + LOD_hotel02.dff \ + LOD_hotel03.dff \ + LOD_hotel04.dff \ + LOD_hotel05.dff \ + LOD_hotel06.dff \ + LOD_hotel07.dff \ + LOD_hotel08.dff \ + LODhotel10.dff \ + LOD_hotel13.dff \ + LOD_hotel15.dff \ + LODhotel.dff \ + LODhotelgrnd.dff \ + LOD_hotelhot_dy.dff \ + LOD_hotelhot_nt.dff \ + LODhotel_waldt.dff \ + lodhotel_windows.dff \ + LOD_houses27.dff \ + lodhpaynspray.dff \ + LODh_pizzaplace.dff \ + LODhpshoutdet.dff \ + lodhsebuild1.dff \ + LOD_htlpoolbar01.dff \ + LOD_htlpoolbase01b.dff \ + LOD_htlpoolbase01.dff \ + LOD_htlpoolbase02.dff \ + LOD_htlpoolbase03.dff \ + LOD_htlpoolbase04.dff \ + LOD_htlpoolrm01.dff \ + LOD_htlpoolrm02.dff \ + LODhtooceanrd1.dff \ + LODhundermall1.dff \ + LODhwbridge.dff \ + LODiamiland048.dff \ + LODiamiland049.dff \ + LODiamiland050.dff \ + LODiamiland051.dff \ + LODiamiland052.dff \ + LODiamiland053.dff \ + LODiamiland054.dff \ + LODiamiland055.dff \ + LODiamiland056.dff \ + LODiamiland057.dff \ + LODiamiland058.dff \ + LODiamiland059.dff \ + LODiamiland060.dff \ + LODiamiland061.dff \ + LODiamiland063.dff \ + LODiamiland065.dff \ + LODiamiland068.dff \ + LODicecream01.dff \ + lodi_exterior_front.dff \ + lodighsandgrs1.dff \ + LODigste1mesh.dff \ + LODina1.dff \ + LODina2.dff \ + LODina3.dff \ + LODindustrial1.dff \ + LODinhaiti.dff \ + LODipad0.dff \ + LODislandeast.dff \ + LODislandnorth.dff \ + LODislandwest.dff \ + LODita1.dff \ + LODitcut.dff \ + LODitnewbt.dff \ + LODitsnip.dff \ + LODitwarhus.dff \ + LODjumbo_01.dff \ + LODk_camjones.dff \ + LODkcranescale01.dff \ + LODkcranescale0.dff \ + LODkfuel02.dff \ + LODk_grassarea.dff \ + LODkhus.dff \ + LODks10.dff \ + LODks21.dff \ + LODks28.dff \ + LODks29.dff \ + LODks30.dff \ + LODks31.dff \ + LODks32.dff \ + LODks37.dff \ + LODks40.dff \ + LODks42.dff \ + LODks43.dff \ + LODks46.dff \ + LODks47.dff \ + LODks48.dff \ + LODks49.dff \ + LODks50.dff \ + LODks51.dff \ + LODks52.dff \ + LODks53.dff \ + LODks60.dff \ + LODks61.dff \ + LODks62.dff \ + LODks85.dff \ + LODks92.dff \ + LODks93.dff \ + LODks95.dff \ + LODks96.dff \ + LODksprops04.dff \ + LODksprops10.dff \ + LODksprops11.dff \ + LODksprops14.dff \ + LODksprops15.dff \ + LODksware01.dff \ + LODl2.dff \ + LODlargebuild.dff \ + LODlBd1.dff \ + LODlBd2.dff \ + LODlBd3.dff \ + LODld1.dff \ + LODldblown.dff \ + LODldingground40.dff \ + LODldingground50.dff \ + LODldingsite2.dff \ + LOD_leslie_dy.dff \ + LOD_leslie_nt.dff \ + LODlhaitibuilding02.dff \ + LODlhaitibuilding06.dff \ + lodlhaitibuilding07.dff \ + LODlhaitibuilding09.dff \ + LODlhland.dff \ + LODlightext.dff \ + LODlleygb_dy.dff \ + LODlleygb_nt.dff \ + LODlleyground2.dff \ + LODlleyground4.dff \ + LODlleyground5.dff \ + LODlleyground.dff \ + LODlloutside.dff \ + LOD_macalpin_dy.dff \ + LOD_macalpin_nt.dff \ + lodmain_body.dff \ + LODmainisl2b_01.dff \ + LODmainisl4_01.dff \ + LODmainisl5_01.dff \ + LODmainisland1a_01.dff \ + LODmainisland1b_01.dff \ + LODmainisland2a_01.dff \ + LODmainisland3a_01.dff \ + LODmainisland3b_01.dff \ + LODmainisland6a_01.dff \ + LODmainisland6b_01.dff \ + LOD_majest_dy.dff \ + LODmajestic2c_dy.dff \ + LODmajestic2c_nt.dff \ + LOD_majestic2x_dy.dff \ + LOD_majestic2x_nt.dff \ + LOD_majestic_dy.dff \ + LOD_majestic_nt.dff \ + LOD_majesticz_dy.dff \ + LOD_majesticz_nt.dff \ + LOD_majest_nt.dff \ + LODmallcp_01.dff \ + LODmallcp_02.dff \ + LODmallcp_03.dff \ + LODmallcp_04.dff \ + LODmallcp_05.dff \ + lodmalsign1.dff \ + lodmalsign2.dff \ + lodmalsign3.dff \ + lodmalsign4.dff \ + lodmalsign5.dff \ + LOD_mansion01.dff \ + LOD_mansion02.dff \ + LOD_mansion03.dff \ + LOD_mansion04.dff \ + LODmansion1.dff \ + LODmansion2.dff \ + LODmansion3.dff \ + LOD_mansionbase01.dff \ + LOD_mansionbase02.dff \ + LODmarscafe.dff \ + LODmiamilanda.dff \ + LODmiland027a.dff \ + LODmiland037.dff \ + LODmiland039.dff \ + LODmiland041.dff \ + LODmiland170.dff \ + lodmiland171.dff \ + lodmiland172.dff \ + LODmiland173.dff \ + LODmiland174.dff \ + LODmiland175.dff \ + LODmiland176.dff \ + LODmiland177.dff \ + LODmiland178.dff \ + LODmiland179.dff \ + LODmiland180.dff \ + LODmiland_kb01_dy.dff \ + LODmiland_kb01_nt.dff \ + lodmiland_kb02.dff \ + LODmiland_kb03.dff \ + LODmiland_kb04.dff \ + LODmiland_kb10b.dff \ + lodmiland_kb10.dff \ + LODmiland_kb11b.dff \ + LODmiland_kb11.dff \ + LODmiland_kb12b.dff \ + LODmiland_kb12.dff \ + LODmiland_kb13.dff \ + LODmiland_ws04b.dff \ + LODmiland_ws04.dff \ + lodmiodnroadxa.dff \ + LODmrgbase1.dff \ + LODmrgbuild1.dff \ + LODmrgbuild2.dff \ + LODmrgtrees4.dff \ + LODnbbridge1.dff \ + LODnbeach01.dff \ + LODnbeach02.dff \ + LODnbeach03.dff \ + LODnbeach05.dff \ + LODnbeach06.dff \ + LODnbeach07.dff \ + LODnbeach08.dff \ + LODnbwroad01.dff \ + LODnbwroad02.dff \ + LODnbwroad03.dff \ + LODnbwroad04.dff \ + LODnbwroad05.dff \ + LODnbwroad06.dff \ + LODnbwroad07.dff \ + LODnbwroad08.dff \ + LODnd04.dff \ + LODnebasea0.dff \ + LODnetopa0.dff \ + LODnewscafe_dy.dff \ + LODnewscafe_nt.dff \ + LODngst2mesh.dff \ + LODnorthbridge1.dff \ + LODnorthisland1.dff \ + LODnorthisland2.dff \ + LODnorthisland3.dff \ + lodnorthstar.dff \ + lodnrthroad01.dff \ + lodnrthroad02.dff \ + lodnrthroad03.dff \ + lodnrthroad04.dff \ + lodnrthroad05.dff \ + lodnrthroad06.dff \ + lodnrthroad07.dff \ + lodnrthroad08.dff \ + lodnrthroad09.dff \ + lodnrthroad10.dff \ + lodnrthroad11.dff \ + lodnrthroad12.dff \ + lodnrthroad13.dff \ + lodnrthroad14.dff \ + lodnrthroad15.dff \ + lodnrthroad16.dff \ + lodnrthroad17.dff \ + lodnrthroad18.dff \ + lodnrthroad19.dff \ + lodnrthroad20.dff \ + lodnrthroad21.dff \ + lodnrthroad22.dff \ + lodnrthroad23.dff \ + lodnrthroad24.dff \ + lodnrthroad25.dff \ + lodnrthroad26.dff \ + lodnrthroad27.dff \ + lodnrthroad28.dff \ + LODntoon03.dff \ + LODntoon_04_bit2.dff \ + LODntoon04.dff \ + LODntoon09.dff \ + LODntoon10.dff \ + LODntoon11.dff \ + LODntoon13.dff \ + LODntoon14.dff \ + LODntoon16.dff \ + LODntoon18.dff \ + LODntoon19.dff \ + LODntoon20.dff \ + LODntoon21.dff \ + LODntoon22.dff \ + LODntoon23.dff \ + LODntoon24.dff \ + LODntoon25.dff \ + LODntoon26.dff \ + LODntoon27.dff \ + LODntoon28.dff \ + LODntoon29.dff \ + LODntoon30.dff \ + LODntoon31.dff \ + LODntoon32.dff \ + LODntoon34.dff \ + LODntoon35.dff \ + LODntoon36.dff \ + LODntoon37.dff \ + LODntoon38.dff \ + LODntoon39.dff \ + LODntoon40.dff \ + LODntoon41.dff \ + LODntoon44.dff \ + LODntoon45.dff \ + LODntoon47.dff \ + LODntoon49.dff \ + LODntoon50.dff \ + LODntoon51.dff \ + LODntoon53.dff \ + LODntoon54.dff \ + LODntoon55.dff \ + LODntoon56.dff \ + LODntoon58.dff \ + LODntoon59.dff \ + LODntoon61.dff \ + LODntoon62.dff \ + LODntoon63.dff \ + LODntoon65.dff \ + LODntoon66.dff \ + LODntoon67.dff \ + LODntoon70.dff \ + LODntoon72b.dff \ + LODntoon72.dff \ + LODntoon73b.dff \ + LODntoon73.dff \ + LODntoon74.dff \ + LODntoon76.dff \ + LODntoon77.dff \ + LODntoon_newbit.dff \ + LODntoon_shops.dff \ + LODntoon_top01.dff \ + LODntoon_top02.dff \ + LODntoon_top03.dff \ + LODntoon_top04.dff \ + LODntoon_top05.dff \ + LODntoon_top06.dff \ + LODntoon_top07.dff \ + LODntoon_top08.dff \ + LODntoon_top09.dff \ + LODntoon_top10.dff \ + LODntoon_top_bb.dff \ + LODoada1.dff \ + LODoadc1.dff \ + LODoadc2.dff \ + LODoadc3.dff \ + LODoadc4.dff \ + LODoadnew1.dff \ + LODoastsky1.dff \ + LODoastsky2.dff \ + lodo_buildkb09.dff \ + LODo_buildkb25.dff \ + LODo_buildkb29_dy.dff \ + LODo_buildkb29_nt.dff \ + LODo_buildkb_dy.dff \ + LODo_buildkb_nt.dff \ + LODoceanrdN1_dy.dff \ + LODoceanrdN1_nt.dff \ + LODo_copgrnd.dff \ + LODoffices.dff \ + LODo_polbuild.dff \ + LODo_polgrnda10.dff \ + LODo_polgrnda12.dff \ + LODo_polgrnda13.dff \ + LODo_polgrnda14.dff \ + LODo_polgrnda16.dff \ + LODo_polgrnda17.dff \ + LODo_polgrnda7.dff \ + LODo_polgrnda8.dff \ + LODo_polgrnda9.dff \ + LODorthmallext1.dff \ + LODorthmalllftex.dff \ + LODoshp010.dff \ + LODospgrnd.dff \ + LODotel01.dff \ + LODotel02.dff \ + LODotel03.dff \ + LODotel04.dff \ + LODotel05.dff \ + LODotel06.dff \ + LODotel07.dff \ + LODotel08.dff \ + lodotelsign35.dff \ + lodowsand1.dff \ + lodowsand2.dff \ + lodpad_grdn_2.dff \ + LODpaynspray.dff \ + LODpedbridge3.dff \ + LODpelican2_dy.dff \ + LODpelican2_nt.dff \ + LODpelican_dy.dff \ + LODpelican_nt.dff \ + LODpfronts03.dff \ + LODphils.dff \ + LODpital.dff \ + LOD_pizzaplace.dff \ + LODpizzaplace.dff \ + LOD_pizzaplacedt.dff \ + LODport_carpark0.dff \ + LODpound_bits.dff \ + LODprintworks.dff \ + LODracecotop2.dff \ + LODracecotop.dff \ + LODrbits05.dff \ + LODrbits06.dff \ + LODrbits13b.dff \ + LODrbits13c.dff \ + LODrbits13.dff \ + LODrbits17.dff \ + LODrbits22.dff \ + LODrbits24.dff \ + LODrbits45.dff \ + LODrbits46.dff \ + LODrbits49.dff \ + LODrbits50.dff \ + LODrbits51.dff \ + LODrbits52.dff \ + LODrbits54.dff \ + LODrbits55.dff \ + LODrbits56.dff \ + LODrbits57.dff \ + LODrbits58.dff \ + LODrbits59.dff \ + LODrbl02b.dff \ + LODrblocks02.dff \ + LODrblocks03.dff \ + LODrblocks04.dff \ + LODrbridge2.dff \ + LODrdepot.dff \ + lodrentrance02.dff \ + lodrentrance1.dff \ + LODrhouse.dff \ + LODrisland_road1.dff \ + LODrisland_road2.dff \ + LODrisland_road3.dff \ + LODrisland_road4.dff \ + LODrisland_road6.dff \ + LODrland01.dff \ + LODrland02.dff \ + LODrland03.dff \ + LODrland04.dff \ + LODrland05.dff \ + LODrland06.dff \ + LODrland07.dff \ + LODrland08.dff \ + LODrland10.dff \ + LODrlandnew9.dff \ + lodrnda1.dff \ + LOD_road01.dff \ + LODroad01.dff \ + lodroad01_dy.dff \ + lodroad01_nt.dff \ + LOD_road02.dff \ + LODroad02.dff \ + LOD_road03.dff \ + LODroad03.dff \ + LODroad03od.dff \ + LOD_road04.dff \ + LODroad04.dff \ + LODroad04od_dy.dff \ + LODroad04od_nt.dff \ + LOD_road05.dff \ + LODroad05.dff \ + LODroad05od.dff \ + LOD_road06.dff \ + LODroad06.dff \ + LOD_road07.dff \ + LODroad07.dff \ + LOD_road08.dff \ + LODroad08.dff \ + LODroad08_dy.dff \ + lodroad08_nt.dff \ + LOD_road09.dff \ + LODroad09.dff \ + LOD_road10.dff \ + LODroad10.dff \ + LOD_road11.dff \ + LODroad11.dff \ + LODroad12.dff \ + LOD_road12_gf.dff \ + LODroad13.dff \ + LODroad14.dff \ + LODroad15.dff \ + LODroadab.dff \ + LODroadnew.dff \ + LODroadsect1.dff \ + LODroadsect2a.dff \ + LODroadsect2b.dff \ + LODroadsect3.dff \ + LODroadsect4.dff \ + LODroadsect5.dff \ + LODroadsigns1_02.dff \ + LODroadsigns1_03.dff \ + LOD_roadswsh01.dff \ + LOD_roadswsh04.dff \ + LOD_roadswsh05.dff \ + lod_roadswsh28.dff \ + LODroadx.dff \ + LOD_rooftopstart.dff \ + LODrtrees1.dff \ + LODrtrees2.dff \ + LODrtrees3.dff \ + LODrtrees4.dff \ + LODrtrees5.dff \ + LODrwaterfrnt05.dff \ + LODrwaterfrnt06b.dff \ + LODrwaterfrnt06.dff \ + LODrwaterfrnt07b.dff \ + LODrwaterfrnt07.dff \ + LODrwaterfrnt08.dff \ + LODrwaterfrnt09b.dff \ + LODrwaterfrnt09.dff \ + LODrwaterfrnt10.dff \ + LODrwaterfrnt11.dff \ + LODrwaterfrnt12.dff \ + LODsbrgeStartf_dt.dff \ + LODsbrgeStartf_nt.dff \ + LODsbrgeStart_r_dt.dff \ + LODsbrgeStart_r_nt.dff \ + LODsbridge07_dt.dff \ + LODsbridge07_nt.dff \ + LODsbridge1_dt.dff \ + LODsbridge1_nt.dff \ + LODsbridge2_dt.dff \ + LODsbridge2_nt.dff \ + LODsbridge3_dt.dff \ + LODsbridge3_nt.dff \ + LODsbridge4_dt.dff \ + LODsbridge4_nt.dff \ + LODsbridge5_dt.dff \ + LODsbridge5_nt.dff \ + LODsbridge6_dt.dff \ + LODsbridge6_nt.dff \ + LODsbridge_dt.dff \ + LODsbridge_nt.dff \ + lodscarland226.dff \ + LODscreening2.dff \ + LODscreening.dff \ + LODseaplaland1.dff \ + LODseaplanehanger1.dff \ + LODsection.dff \ + LODse_ext.dff \ + LODse_pier.dff \ + LODse_pierfence.dff \ + LOD_shedbig30.dff \ + lodsho_ext1.dff \ + LOD_shop.dff \ + LODshopfronts01b.dff \ + LODshopfronts01.dff \ + LODshopfronts02b.dff \ + LODshopfronts02.dff \ + lodsho_recordshop1.dff \ + LODshpfrnts1grd.dff \ + LODshpfrts1bgrd.dff \ + LODsion1_A.dff \ + LODsion1_base.dff \ + LODsion1b.dff \ + LODsion1gardens.dff \ + LODsion2_A.dff \ + LODsion2_B.dff \ + LODsion2c.dff \ + LODsion3a.dff \ + LODsion3_base.dff \ + LODsion3_B.dff \ + LODsion3_C.dff \ + LODsion3gardens.dff \ + LODsjmcrush.dff \ + LODsmahangar2_01.dff \ + LODsmahangar2_02.dff \ + LODsmahangar2_03.dff \ + LODsmahangar2_04.dff \ + LODsmahangar2_05.dff \ + LODsmahangar2_06.dff \ + LODsmallhangars1_01.dff \ + LODsmallhangars1_02.dff \ + LODsmallhangars1_03.dff \ + LODsmallisland1_01.dff \ + LODsmallislebridge1.dff \ + LODsmallradar1_02.dff \ + LODsmallradar1.dff \ + LODspotlite_01.dff \ + LODstadium_entrance.dff \ + LODstadiumland_a.dff \ + LODstadiumland_b.dff \ + LODstadiumland_c.dff \ + LODstadiumland_d.dff \ + LODstadium_start.dff \ + LOD_starlite_dy.dff \ + LOD_starlite_nt.dff \ + lodsthroad01.dff \ + lodsthroad02.dff \ + lodsthroad03.dff \ + lodsthroad04.dff \ + lodsthroad05.dff \ + lodsthroad06.dff \ + lodsthroad07.dff \ + lodsthroad08.dff \ + lodsthroad09.dff \ + lodsthroad10.dff \ + lodsthroad11.dff \ + lodsthroad12.dff \ + lodsthroad13.dff \ + lodsthroad14.dff \ + lodsthroad15.dff \ + lodsthroad16.dff \ + lodsthroad17.dff \ + lodsthroad18.dff \ + lodsthroad19.dff \ + lodsthroad20.dff \ + lodsthroad21.dff \ + lodsthroad22.dff \ + lodsthroad23.dff \ + lodsthroad24.dff \ + LODstudiowall.dff \ + LODst_woodfence30.dff \ + LODtaxifirm02.dff \ + LODtbuildws14.dff \ + LODtermAintfloor1.dff \ + LODterminA_01.dff \ + LODterminalB1.dff \ + LODTI1RD.dff \ + LODtibuilds.dff \ + LODticent.dff \ + LOD_tides2_dy.dff \ + LOD_tides2_nt.dff \ + LODtides3_dy.dff \ + LODtides3_nt.dff \ + LOD_tides_dy.dff \ + LOD_tides_nt.dff \ + LODtihut1.dff \ + LODtihut2.dff \ + LODtihut3.dff \ + LODtimall.dff \ + LODtiroadbuild.dff \ + LODtiskyg.dff \ + LODtiskyt.dff \ + LODtistation01.dff \ + LODtistation.dff \ + LODtlehabroad.dff \ + LODtlehacoast02.dff \ + LODtlehacoast05.dff \ + lodtleha_hardware.dff \ + LODtleha_police.dff \ + LODtlharoadgg2.dff \ + LODtower_01.dff \ + LODtower_02.dff \ + LODtower_03.dff \ + LODtower.dff \ + LODtranspol1.dff \ + LODtroada.dff \ + LODtroadb.dff \ + LODtship_structure0.dff \ + LODuild034.dff \ + LODuild1b.dff \ + LODuilding01.dff \ + LODuilding05.dff \ + LODunation_dt.dff \ + LODvroad1.dff \ + LODvroad2.dff \ + lodwalkway2.dff \ + LODwall2.dff \ + LODwall3.dff \ + LODwall.dff \ + LODwaretank.dff \ + lodwashpaynspray.dff \ + LODwatertank.dff \ + LODwoodbridge1.dff \ + LODworksramps.dff \ + lod_wtrftr_1a.dff \ + lodxrefhirise1.dff \ + lodxrefhse1.dff \ + lodxrefhse2.dff \ + loopbig.dff \ + looplop2.dff \ + lotion.dff \ + lounger.dff \ + lounge_towel_up.dff \ + lounge_wood_dn.dff \ + lounge_wood_up.dff \ + lovefist.dff \ + lwchara.dff \ + lwcharb.dff \ + lw_floor_opac.dff \ + lw_lights.dff \ + lw_officedesk01.dff \ + lw_palm1.dff \ + lw_roombitsbb.dff \ + lw_roombitsb.dff \ + lw_roombitsc.dff \ + lw_room_main.dff \ + lw_rug.dff \ + lw_shado.dff \ + m4.dff \ + m60.dff \ + machete.dff \ + maidmalltopa.dff \ + male01.dff \ + mall2.dff \ + mall_ammu.dff \ + mall_ammuwin.dff \ + mallbushdense.dff \ + mallbushs.dff \ + mall_fans.dff \ + mall_hardwarebits.dff \ + mall_hardware.dff \ + mallint1.dff \ + mallint2.dff \ + mallint3.dff \ + mallint5.dff \ + mallint5up_b.dff \ + mallint5up.dff \ + mallint7.dff \ + mallint8.dff \ + mallint9.dff \ + malllights.dff \ + mallseats01.dff \ + mallstrut.dff \ + malltreereflect2.dff \ + malltreereflect.dff \ + malltrees01.dff \ + MALLUNDER.dff \ + Man1_baseHedge.dff \ + man1garden_hedge.dff \ + Man3_base_hedge.dff \ + man3ghedge.dff \ + Man4garagedoors.dff \ + man4ladderpool01.dff \ + manana.dff \ + man_backside2.dff \ + man_backside.dff \ + man_backside_int.dff \ + man_build01_al.dff \ + man_build01.dff \ + man_build02_al.dff \ + man_build02.dff \ + man_build03_al.dff \ + man_build03.dff \ + man_build04.dff \ + man_build04_fnce.dff \ + man_build05_al.dff \ + man_build05.dff \ + man_build_int.dff \ + man_cellar1.dff \ + man_cellar2.dff \ + man_chandeliers.dff \ + man_couch02.dff \ + man_doorclosed.dff \ + man_dooropen.dff \ + man_doorway2.dff \ + man_f_chandelier.dff \ + man_frntstep.dff \ + man_frntstepGD.dff \ + man_grnd2.dff \ + man_grnd3.dff \ + man_grnd4.dff \ + man_grnd5.dff \ + man_grnd6.dff \ + man_hall_floor.dff \ + man_hall_pillars.dff \ + man_hall_ref.dff \ + man_hall_wall1.dff \ + man_hallway1.dff \ + man_hallway2.dff \ + man_hedge1.dff \ + man_lights_pool.dff \ + man_maze.dff \ + man_monitor_base.dff \ + man_monitors02.dff \ + manmrgbase1.dff \ + manmrgbuild1.dff \ + manmrgbuild2.dff \ + man_pool.dff \ + man_pool_shado1.dff \ + man_pool_water.dff \ + Man_rubbish03.dff \ + man_safenew.dff \ + man_safenewdr.dff \ + man_safenewmny.dff \ + mansbushes2.dff \ + mansbushes.dff \ + man_scar_curtains.dff \ + man_scar_props206.dff \ + man_scar_propv.dff \ + man_scrface_walls.dff \ + man_scrfashado.dff \ + man_scrfce_chstrset.dff \ + man_scrfce_floor1.dff \ + man_sdr_folge.dff \ + man_sdr_lamps.dff \ + man_sdr_paintin.dff \ + man_sdr_props.dff \ + man_sdr_rug.dff \ + man_sdr_seating.dff \ + man_sdr_strctur.dff \ + man_sdr_tables.dff \ + man_sdr_tabls.dff \ + man_sdr_tvstf.dff \ + man_sidrm_shad.dff \ + Mansion1_A.dff \ + Mansion1_base.dff \ + mansion1b.dff \ + mansion1gardens.dff \ + Mansion2_A.dff \ + Mansion2_B.dff \ + Mansion2c.dff \ + Mansion2_C_fence.dff \ + mansion3a.dff \ + mansion3_base.dff \ + mansion3_B.dff \ + Mansion3_C.dff \ + mansion3gardens2.dff \ + mansion3gardens.dff \ + Mansion4gate.dff \ + mansionfence.dff \ + mansion_new_doors.dff \ + mans_merge03.dff \ + mans_merge07.dff \ + mans_merge09.dff \ + man_tablkb1.dff \ + man_twr_stairsB.dff \ + man_twr_stairs.dff \ + man_wtr_fill.dff \ + man_wtrftr_1a.dff \ + man_wtrftr_2a.dff \ + man_wtrftr_2.dff \ + man_wtrftr_3a.dff \ + man_wtrftr_wtr1.dff \ + man_xchandeliers.dff \ + many_polys2.dff \ + many_polys.dff \ + marina1.dff \ + marina2.dff \ + marina3.dff \ + marinafence01.dff \ + marinafence02.dff \ + marinafence03.dff \ + marinafence04.dff \ + marinafence05.dff \ + marinaplanter1.dff \ + marinaplanter2.dff \ + marinasign1.dff \ + marinasign2.dff \ + marinaveg1.dff \ + marinaveg2.dff \ + marinaveg3.dff \ + marquis.dff \ + mask.dff \ + maverick.dff \ + Mba.dff \ + mb_ammobox1.dff \ + mb_ammobox2.dff \ + mb_ammobox3.dff \ + Mbb.dff \ + mbtbemp.dff \ + mbtbful.dff \ + mcane.dff \ + mc_assaultcourse1.dff \ + mc_assaultcourse2.dff \ + mc_barrackshad.dff \ + mc_camotent1_01.dff \ + mc_camotent1_04.dff \ + mc_camotent1_05.dff \ + mc_crashbar1.dff \ + mc_crashbar2.dff \ + mc_dirtjump1.dff \ + mc_fence1.dff \ + mc_fence2.dff \ + mc_fence3.dff \ + mc_fencshad.dff \ + mc_flags1.dff \ + mc_flags2.dff \ + mc_ground1.dff \ + mc_ground2.dff \ + mc_ground3.dff \ + mc_ground4.dff \ + mc_ground5.dff \ + mc_hangar1.dff \ + mc_hangarint1.dff \ + mc_kerb_01.dff \ + mc_kerb_02.dff \ + mc_largebuild.dff \ + mc_overlights1.dff \ + mc_overlights2.dff \ + mc_shed04.dff \ + mc_shed1.dff \ + mc_tower_01.dff \ + mc_tower_02.dff \ + mc_tower_03.dff \ + mc_treesfw1_01.dff \ + mc_treesfw2_01.dff \ + mc_treesfw3_01.dff \ + mc_wall2.dff \ + mc_wall3.dff \ + mc_wall.dff \ + medic.dff \ + merced.dff \ + mesa.dff \ + mfour.dff \ + mgoona.dff \ + Miamibin.dff \ + miamifirehyd.dff \ + miamiland027a.dff \ + miamiland037.dff \ + miamiland039.dff \ + miamiland041.dff \ + miamiland170.dff \ + miamiland171.dff \ + miamiland172.dff \ + miamiland173.dff \ + miamiland174.dff \ + miamiland175.dff \ + miamiland176.dff \ + miamiland177.dff \ + miamiland178.dff \ + miamiland179.dff \ + miamiland180.dff \ + miamiland_kb01_dy.dff \ + miamiland_kb01_nt.dff \ + miamiland_kb02.dff \ + miamiland_kb03.dff \ + miamiland_kb04.dff \ + miamiland_kb10b.dff \ + miamiland_kb10.dff \ + miamiland_kb11b.dff \ + miamiland_kb11.dff \ + miamiland_kb12b.dff \ + miamiland_kb12.dff \ + miamiland_kb13.dff \ + miamiland_ws04b.dff \ + miamiland_ws04.dff \ + miamiodnroadxa.dff \ + miamphon.dff \ + midmallfloorw.dff \ + midmallflorre.dff \ + mine.dff \ + minigun2.dff \ + minigun.dff \ + missile.dff \ + mlamppost.dff \ + ml_borders.dff \ + ml_bordersdoor.dff \ + ml_borderswin01.dff \ + ml_borderswin02.dff \ + ml_cofcounter1.dff \ + ml_coffee_dsk.dff \ + ml_coffwin01.dff \ + ml_coffwin02.dff \ + ml_coffwinframe01.dff \ + ml_coffwinframe02.dff \ + ml_doorsbotl.dff \ + ml_doorsbotr.dff \ + ml_doorscarp.dff \ + ml_doorsmidl.dff \ + ml_doorsmidr.dff \ + ml_doorstopl.dff \ + ml_doorstopr.dff \ + ml_escl.dff \ + ml_gap.dff \ + ml_gapdoors.dff \ + ml_gapwindows01.dff \ + ml_gapwindows02.dff \ + ml_gapwindows03.dff \ + ml_gapwindows04.dff \ + ml_gapwindows05.dff \ + ml_gapwindows06.dff \ + ml_gapwindows07.dff \ + ml_gapwindows08.dff \ + ml_gapwindows09.dff \ + ml_gapwindows10.dff \ + ml_gapwindows11.dff \ + ml_gapwindows12.dff \ + ml_glasslft.dff \ + mlgrndmidtop.dff \ + mlgrounds.dff \ + ml_jewelcounter.dff \ + ml_jewelglass.dff \ + ml_jewellers.dff \ + ml_jewelwin01.dff \ + ml_jewelwin02.dff \ + ml_jewelwin03.dff \ + ml_jewelwinframe01.dff \ + ml_jewelwinframe02.dff \ + mlmallbush.dff \ + mlmallflag02.dff \ + mlmallflag12.dff \ + mlmallflag14.dff \ + mlmallplan02.dff \ + mlmallplan04.dff \ + mlmallplan05.dff \ + mlmallroof01.dff \ + mlmallroof.dff \ + mlmiamiland056.dff \ + mlmiamiland058.dff \ + mlmiamiland060b.dff \ + mlmiamiland060.dff \ + mlngrnd.dff \ + mlnorthmall.dff \ + mlnorthmalllft.dff \ + mlnorthmalllftex.dff \ + mlnorthmallrt.dff \ + mlnorthmalltop.dff \ + mlNpoint02.dff \ + mlNpoint04.dff \ + mlNpoint06.dff \ + mlobjects_n02.dff \ + mlobjects_n03.dff \ + mlobjects_n04.dff \ + mlobjects_n.dff \ + mlobjects_s02.dff \ + mloven02.dff \ + mloven.dff \ + ml_planterbed.dff \ + ml_record3.dff \ + ml_record.dff \ + mlrecords.dff \ + ml_recordwin01.dff \ + ml_recordwin02.dff \ + ml_recordwin03.dff \ + ml_recordwin04.dff \ + ml_recordwin05.dff \ + ml_recordwin06.dff \ + ml_tarbrushdamage.dff \ + ml_tarbrush.dff \ + ml_tartable.dff \ + ml_vegbits01.dff \ + ml_vegbits02.dff \ + ml_vegbits03.dff \ + ml_vegbits04.dff \ + ml_vegbits05.dff \ + ml_water2.dff \ + ml_water.dff \ + mna_table_scrfce.dff \ + mn_treesis_int.dff \ + mn_treesisl05.dff \ + mob_detailsb.dff \ + mob_door2.dff \ + mob_door3.dff \ + mob_mobroom2.dff \ + molotov.dff \ + moneybag.dff \ + Money.dff \ + moonbeam.dff \ + mp5lng.dff \ + mpfshrt.dff \ + mporna.dff \ + Mpostbox1.dff \ + mrwhoop.dff \ + mserver.dff \ + MTraffic1.dff \ + MTraffic2.dff \ + MTraffic3.dff \ + MTraffic4.dff \ + muck_nbeachw.dff \ + mule.dff \ + nb2glue.dff \ + nbbridgbit2.dff \ + nbchbit04bgrd1.dff \ + nbchbit04grd2.dff \ + nbchbtjetty01.dff \ + nbchbtjetty02.dff \ + nbchflw01.dff \ + nbchflw02.dff \ + nbchwbridgb.dff \ + nbchwbridge.dff \ + nbdcoshp01glss.dff \ + nbdcprmndfnc1.dff \ + nbdcprmndfnc2.dff \ + nbdecopromnd2.dff \ + nbdecopromnd.dff \ + nbdecoshop01.dff \ + nbdecoshop02.dff \ + nbdecoshop03.dff \ + nbdecoshplants.dff \ + nbeachb1.dff \ + nbeachb.dff \ + nbeachbed01.dff \ + nbeachbed02.dff \ + nbeachbit02.dff \ + nbeachbit03.dff \ + nbeachbit03rails.dff \ + nbeachbit04b.dff \ + nbeachbit04.dff \ + nbeachbit05.dff \ + nbeachbit06.dff \ + nbeachbridge.dff \ + nbeach_police02.dff \ + nbeach_police.dff \ + nbeachrdsign.dff \ + nbeachroads01.dff \ + nbeachroads02.dff \ + nbeachroads03.dff \ + nbeachroads09.dff \ + nbeachsand1.dff \ + nbeachsand2b.dff \ + nbeachsand2.dff \ + nbeachvegy1.dff \ + nbeachvegy2.dff \ + nbeachwater1.dff \ + nbeachwjetty01.dff \ + nbeachwjetty02.dff \ + nbecbtwbrdge.dff \ + nbec_divingbds.dff \ + nbechneon2.dff \ + nbecland013.dff \ + nbecland014.dff \ + nbecland01.dff \ + nbecland03.dff \ + nbecland05.dff \ + nbecland09.dff \ + nbecland10.dff \ + nbecland11.dff \ + nbecland12bball.dff \ + nbecland12.dff \ + nbecland12gates.dff \ + nbecland13_gf.dff \ + nbecnssignday_dy.dff \ + nbecnssignight_nt.dff \ + nbecpaynspray.dff \ + nbecpnsdoor.dff \ + nbecpnsneon1.dff \ + nbhospgrnd02.dff \ + nbhospgrnd.dff \ + nbhotel01.dff \ + nbhotel02.dff \ + nbhotel02glss.dff \ + nbhotel03.dff \ + nbhotel04.dff \ + nbhotel05.dff \ + nbhotel06.dff \ + nbhotel07.dff \ + nbhotel08.dff \ + nbhtel06shdw.dff \ + nbmiamiland048.dff \ + nbmiamiland049.dff \ + nbmiamiland050.dff \ + nbmiamiland051.dff \ + nbmiamiland052.dff \ + nbmiamiland053.dff \ + nbmiamiland054.dff \ + nbmiamiland055.dff \ + nbmiamiland057.dff \ + nbmiamiland059.dff \ + nbmiamiland061.dff \ + nbmiamiland063.dff \ + nbmiamiland065.dff \ + nbmiamiland068.dff \ + nbnorthmallext1.dff \ + nb_road01.dff \ + nb_road02.dff \ + nb_road03.dff \ + nb_road04.dff \ + nb_road05.dff \ + nb_road06.dff \ + nb_road07.dff \ + nb_road08.dff \ + nb_road09.dff \ + nb_road10.dff \ + nb_road11.dff \ + nb_road12.dff \ + nb_road13.dff \ + nb_road14.dff \ + nb_road15.dff \ + nbroada1.dff \ + nbroadnew1.dff \ + nbroofst11.dff \ + nbsavehotel.dff \ + nbsavhtlglass.dff \ + nbt_apartpool01.dff \ + nbt_apartpool02.dff \ + nbt_apartpool03.dff \ + nbt_balcony02.dff \ + nbt_balcony03.dff \ + nbt_balcony05.dff \ + nbt_barfence01.dff \ + nbtgardoor02.dff \ + nbtgardoor03.dff \ + nbtgardoor.dff \ + nbt_hotel0102.dff \ + nbt_hotel01.dff \ + nbt_hotel0202.dff \ + nbt_hotel02.dff \ + nbt_hotel0302.dff \ + nbt_hotel03.dff \ + nbt_hotel03glass.dff \ + nbt_hotel0402.dff \ + nbt_hotel04.dff \ + nbt_hotel05.dff \ + nbt_hotel0602.dff \ + nbt_hotel06balcony.dff \ + nbt_hotel06.dff \ + nbt_hotel0702.dff \ + nbt_hotel07.dff \ + nbt_hotel07fence.dff \ + nbt_hotel08.dff \ + nbthotel08fence02.dff \ + nbthotel10.dff \ + nbt_hotel13.dff \ + nbt_hotel15.dff \ + nbt_hoteltrees01.dff \ + nbt_hoteltrees02.dff \ + nbt_htlpiswr01.dff \ + nbt_htlpiswr02.dff \ + nbt_htlpool01.dff \ + nbt_htlpoolbar01.dff \ + nbt_htlpoolbase01b.dff \ + nbt_htlpoolbase01.dff \ + nbt_htlpoolbase02.dff \ + nbt_htlpoolbase03.dff \ + nbt_htlpoolbase04.dff \ + nbt_htlpoolrm01.dff \ + nbt_htlpoolrm02.dff \ + nbt_mans04balcony.dff \ + nbt_mansion01.dff \ + nbt_mansion02.dff \ + nbt_mansion03.dff \ + nbt_mansion0402.dff \ + nbt_mansion04.dff \ + nbt_mansionbase01.dff \ + nbt_mansionbase02.dff \ + nbt_mansiontrees01.dff \ + nbt_mansiontrees02.dff \ + nbt_mansiontrees03.dff \ + nbt_mansiontrees04.dff \ + nbt_mansiontrees05.dff \ + nbt_mnsnwtr01.dff \ + nbt_mnsnwtr02.dff \ + nbt_pizzaplace.dff \ + nbt_road01.dff \ + nbt_road02.dff \ + nbt_road03.dff \ + nbt_road04.dff \ + nbt_road05.dff \ + nbt_road06.dff \ + nbt_road07.dff \ + nbt_road08.dff \ + nbt_road09.dff \ + nbt_road10.dff \ + nbt_road11.dff \ + nbt_road12_gf.dff \ + nbtroadx.dff \ + nbt_rooftopstart.dff \ + nbtshopfronts01b.dff \ + nbtshopfronts01.dff \ + nbtshopfronts02b.dff \ + nbtshopfronts02.dff \ + nbtshopfronts02win.dff \ + nbtshpfrnts1grd.dff \ + nbtshpfrts1bgrd.dff \ + nbt_skip.dff \ + nbt_tikiprops01.dff \ + nbw_bridgew.dff \ + nbw_bush01.dff \ + nbw_bush02.dff \ + nbwbusshy2.dff \ + nbw_grssypatch.dff \ + nbwneon01.dff \ + nbwneon02.dff \ + nbw_roadsigns.dff \ + newashneon245.dff \ + newbuild01.dff \ + newbuild02.dff \ + newbuilding0102.dff \ + newbuilding01.dff \ + new_bushsm.dff \ + new_bushtest42.dff \ + new_bushtest.dff \ + new_insideb.dff \ + new_inside.dff \ + newmallcp_01.dff \ + newmallcp_02.dff \ + newmallcp_03.dff \ + newmallcp_04.dff \ + newmallcp_05.dff \ + newpools.dff \ + newramp2.dff \ + newramp.dff \ + newsstand1.dff \ + newstandnew1.dff \ + newstandnew2.dff \ + newstandnew3.dff \ + newstandnew4.dff \ + newstandnew5.dff \ + newtowerdoor1.dff \ + nhaiticar13.dff \ + nhaitiplnt14.dff \ + nhaitiplnt15.dff \ + nhaitplatdr.dff \ + nhaitplatdrr.dff \ + nhvood.dff \ + nitestick.dff \ + nmalloutside.dff \ + noparkingsign1.dff \ + npw_press1_02.dff \ + nrth1veg21.dff \ + nrth1veg37.dff \ + nrth1veg42.dff \ + nrth3veg05.dff \ + nrth3veg08.dff \ + nrth3veg16.dff \ + nrth3veg25.dff \ + nrth3veg35.dff \ + nrth3veg50.dff \ + nrth3veg59.dff \ + nrth4veg05.dff \ + nrth4veg08.dff \ + nrth4veg09.dff \ + nrth4veg212.dff \ + nrth4veg21.dff \ + nrth7veg09.dff \ + nrth7veg.dff \ + nt_aircon1_01.dff \ + nt_aircon1_02.dff \ + nt_aircon1dbl.dff \ + nt_aircon3_01.dff \ + nt_alarm1_01.dff \ + nt_alarm2_01.dff \ + nt_alarm3_01.dff \ + nt_bed1_01.dff \ + nt_cablebox1_01.dff \ + nt_cablebox2_01.dff \ + nt_cablebox3_01.dff \ + nt_cablebox4_01.dff \ + nt_cablebox5_01.dff \ + nt_cablebox6_01.dff \ + nt_couch_1.dff \ + nt_firehose_01.dff \ + nt_ladder_01.dff \ + nt_ladder_02.dff \ + nt_roadblockCI.dff \ + nt_roadblockGF.dff \ + nt_securecam1_01.dff \ + nt_securecam2_01.dff \ + nt_security1.dff \ + nt_vent1_01.dff \ + nt_vent2_01.dff \ + nt_vent3_01.dff \ + nt_wassily1_02.dff \ + nubuilding1glass.dff \ + nw_nbwroad01.dff \ + nw_nbwroad02.dff \ + nw_nbwroad03.dff \ + nw_nbwroad04.dff \ + nw_nbwroad05.dff \ + nw_nbwroad06.dff \ + nw_nbwroad07.dff \ + nw_nbwroad08.dff \ + ocdneona_nt.dff \ + ocdneonb_nt.dff \ + oceanic.dff \ + oceanrd1_dy.dff \ + oceanrd1_nt.dff \ + oceanrda03_dy.dff \ + oceanrda03_nt.dff \ + oceanrda04_dy.dff \ + oceanrda04_nt.dff \ + oceanrda05_dy.dff \ + oceanrda05_nt.dff \ + oceanrda06_dy.dff \ + oceanrda06_nt.dff \ + oceanroad04.dff \ + oceanroad05.dff \ + ocndbridge.dff \ + od2glue.dff \ + od_acne.dff \ + odalleygb_dy.dff \ + odalleygb_nt.dff \ + odalleyground2_dy.dff \ + odalleyground2_nt.dff \ + odalleyground4_dy.dff \ + odalleyground4_nt.dff \ + odalleyground5_dy.dff \ + odalleyground5_nt.dff \ + odalleyground_dy.dff \ + odalleyground_nt.dff \ + od_alleys1_01_dy.dff \ + od_alleys1_01_nt.dff \ + od_alleys1b_01_dy.dff \ + od_alleys1b_01_nt.dff \ + od_alleys2_01_dy.dff \ + od_alleys2_01_nt.dff \ + od_alleys3_01_dy.dff \ + od_alleys3_01_nt.dff \ + od_alleys3b_01_dy.dff \ + od_alleys3b_01_nt.dff \ + od_bighotcons.dff \ + od_bighotel_dy.dff \ + od_bighotel_nt.dff \ + od_bighotpool2.dff \ + od_bighotpool.dff \ + od_bighotsign01.dff \ + od_bighotsign.dff \ + od_bigshops1.dff \ + od_bigshops2.dff \ + od_bphq.dff \ + od_buildkb23_dy.dff \ + od_buildkb23_nt.dff \ + od_buildkb27_dy.dff \ + od_buildkb27_nt.dff \ + od_buildkb2_dy.dff \ + od_buildkb2_nt.dff \ + od_casagrande_dy.dff \ + od_casagrande_nt.dff \ + od_celldoor02.dff \ + od_celldoor03.dff \ + od_celldoor04.dff \ + od_celldoor1.dff \ + od_celldoor3.dff \ + od_celldoor4.dff \ + od_chariot.dff \ + od_chariotland.dff \ + od_chariot_lghts01.dff \ + od_chariot_lghts02.dff \ + od_char_signs_dy.dff \ + od_char_signs_nt.dff \ + od_clbdr_close.dff \ + od_clbdr_open.dff \ + od_clevelander_dy.dff \ + od_clevelander_nt.dff \ + od_clevelfence.dff \ + od_clubback.dff \ + od_clubdoora.dff \ + od_clubdoors.dff \ + od_clubfence_dy.dff \ + od_clubfence_nt.dff \ + od_clubfnce.dff \ + od_clubneon.dff \ + od_clubout_dy.dff \ + od_clubout_nt.dff \ + od_colony2_dy.dff \ + od_colony2_nt.dff \ + od_colony_dy.dff \ + od_colony_nt.dff \ + od_copwindows.dff \ + od_cornercrap1.dff \ + od_cornercrap2.dff \ + od_cornercrap3.dff \ + oddirtpass1.dff \ + od_dirtshad1.dff \ + od_dirtshad3.dff \ + od_dirtshad4.dff \ + od_dirty1.dff \ + od_dirty2.dff \ + od_dirty4.dff \ + od_divingbds.dff \ + od_divingbdsteps.dff \ + oddoorway2.dff \ + oddoorway.dff \ + oddoorwayS02.dff \ + oddoorwayS1.dff \ + od_fpbarsign.dff \ + od_groyne01.dff \ + odhighsandgrs1.dff \ + od_hotel_windows.dff \ + od_lhland.dff \ + od_lightbeam.dff \ + od_lightext.dff \ + od_lightint.dff \ + odlowsand1.dff \ + odlowsand2.dff \ + od_majestic2c_dy.dff \ + od_majestic2c_nt.dff \ + odnanrod2b_dy.dff \ + odnanrod2b_nt.dff \ + odn_beacon2_dy.dff \ + odn_beacon2_nt.dff \ + odn_colony_dy.dff \ + odn_colony_nt.dff \ + odndirt1.dff \ + odndirt2.dff \ + odndirt3.dff \ + odndirt4.dff \ + odndoorway3.dff \ + od_neona1.dff \ + od_neonn.dff \ + od_neons2b_nt.dff \ + od_neons3b_nt.dff \ + odneontest01.dff \ + odneontest.dff \ + od_neonx1.dff \ + od_newscafe_dy.dff \ + od_newscafe_nt.dff \ + odn_hotelhot_dy.dff \ + odn_hotelhot_nt.dff \ + odn_hotshad1.dff \ + odn_hotshad2.dff \ + odn_hotshad3.dff \ + odn_majest_dy.dff \ + odn_majestic2x_dy.dff \ + odn_majestic2x_nt.dff \ + odn_majesticz_dy.dff \ + odn_majesticz_nt.dff \ + odn_majest_nt.dff \ + odnneaon1.dff \ + odnneaon2.dff \ + odnneaon3.dff \ + odnneaon4.dff \ + odn_neonht1a.dff \ + od_northstar.dff \ + odNroad01_dy.dff \ + odNroad01_nt.dff \ + odNroad02.dff \ + odNroad03od.dff \ + odNroad04od_dy.dff \ + odNroad04od_nt.dff \ + odNroad05od.dff \ + odNroad06_dy.dff \ + odNroad06_nt.dff \ + odNroad08_dy.dff \ + odNroad08_nt.dff \ + odn_tides2_dy.dff \ + odn_tides2_nt.dff \ + odnvegbush1.dff \ + odnvegbush2b.dff \ + odnvegbush2.dff \ + odnwiret.dff \ + od_oceanrdN1_dy.dff \ + od_oceanrdN1_nt.dff \ + od_pat_hutb.dff \ + od_pat_hut.dff \ + od_pelican2_dy.dff \ + od_pelican2_nt.dff \ + od_pelican_dy.dff \ + od_pelican_nt.dff \ + odrampbit.dff \ + odrroofst02.dff \ + odrroofst06.dff \ + odrroofst07.dff \ + odrv_bushes01.dff \ + odrv_bushes.dff \ + od_tides3_dy.dff \ + od_tides3_nt.dff \ + od_tracks01.dff \ + od_tracks03.dff \ + od_tracks05.dff \ + od_tracks06.dff \ + od_tracks08.dff \ + od_tracks10.dff \ + od_tracks12.dff \ + od_tracks14.dff \ + od_vbnet.dff \ + od_walkway2.dff \ + od_walkway3.dff \ + od_wt.dff \ + od_wtlegs.dff \ + od_wtshadow.dff \ + od_wtsign.dff \ + package1.dff \ + packer.dff \ + palette.dff \ + palettejumpN04.dff \ + papermachn01.dff \ + parkbench1.dff \ + parkingmeter.dff \ + parkingmeterg.dff \ + parktable1.dff \ + patriot.dff \ + pcj600.dff \ + peren.dff \ + petrolcanm.dff \ + petrolpump.dff \ + Pga.dff \ + Pgb.dff \ + pharmacy_kb1.dff \ + pharmacy_kb2.dff \ + ph_build_dr_closed.dff \ + ph_build_dr_open.dff \ + ph_build_stairs.dff \ + pheonix.dff \ + philbmb.dff \ + phils_compnd_gate.dff \ + phnplay.dff \ + phoncol.dff \ + phonebooth1.dff \ + phonesign.dff \ + pickupmusic.dff \ + pickupsave.dff \ + pierentrance02.dff \ + pierentrance1.dff \ + pitchrb.dff \ + pizza_box1.dff \ + pizzabox.dff \ + pizzaboy.dff \ + pizza_mess2.dff \ + Pizza_mess.dff \ + plank01.dff \ + plank02.dff \ + plank1.dff \ + plankk2.dff \ + plants01.dff \ + plants04.dff \ + plants05b.dff \ + plants05.dff \ + play10.dff \ + play11.dff \ + play12.dff \ + player2.dff \ + player3.dff \ + player4.dff \ + player5.dff \ + player6.dff \ + player7.dff \ + player8.dff \ + player9.dff \ + player.dff \ + plc_stinger.dff \ + plnt_ref_bnk01.dff \ + plnt_ref_bnk02.dff \ + plyphon.dff \ + polbals.dff \ + police.DFF \ + pollstaeps1.dff \ + polmav.dff \ + Poloroids.dff \ + pony.dff \ + pooladder01.dff \ + pooladder.dff \ + poolcue.dff \ + poolq.dff \ + poolwaterwshs.dff \ + pornposters.dff \ + portcabGAZ06.dff \ + postbox1.dff \ + pot_01.dff \ + pot_02.dff \ + pot_03.dff \ + predator.dff \ + printdooor.dff \ + printra.dff \ + printrb.dff \ + printrc.dff \ + propashtray1.dff \ + propbeerglass1.dff \ + propcollecttable.dff \ + property_fsale.dff \ + property_locked.dff \ + prop_group2.dff \ + prop_group3.dff \ + props_group1.dff \ + propvodkabotl1.dff \ + propwinebotl1.dff \ + propwinebotl2.dff \ + propwineglass1.dff \ + psycho.dff \ + pw_backfence1.dff \ + pw_bayfence2_01.dff \ + pw_groundplane.dff \ + pw_paperroll_01.dff \ + pw_paperroll_02.dff \ + pw_paperroll_03.dff \ + pw_presrommshad.dff \ + pw_press1_03.dff \ + pw_priint.dff \ + pw_printworks.dff \ + pw_staffpark01.dff \ + pw_stairbarrier.dff \ + pw_stairs.dff \ + python.dff \ + rafaelsign1.dff \ + rafelneonsgn.dff \ + ramp.dff \ + rancher.dff \ + rcbandit.dff \ + rcbaron.dff \ + rcbomb.dff \ + rcgoblin.dff \ + rcraider.dff \ + rcyclbank01.dff \ + recordingstudio.dff \ + recsho_recordshop1.dff \ + reefer.dff \ + regina.dff \ + rhino.dff \ + rio.dff \ + road_downtown02.dff \ + road_downtown03.dff \ + road_downtown04.dff \ + road_downtown05.dff \ + road_downtown06.dff \ + road_downtown07.dff \ + road_downtown08.dff \ + road_downtown09.dff \ + road_downtown10.dff \ + road_downtown11.dff \ + road_downtown12.dff \ + road_downtown13.dff \ + road_downtown14.dff \ + road_downtown15.dff \ + road_downtown16.dff \ + road_downtown17.dff \ + road_downtown_new1.dff \ + road_downtown_new2.dff \ + roadsign.dff \ + road_stadiuma.dff \ + roadworkbarrier1.dff \ + rocketed_win1.dff \ + rocketla.dff \ + rockpatch03.dff \ + rockpatch1.dff \ + romero.dff \ + roofbit07.dff \ + roofbit2.dff \ + RoosbrgeStartf_dt.dff \ + RoosbrgeStartf_nt.dff \ + RoosbrgeStart_r_dt.dff \ + RoosbrgeStart_r_nt.dff \ + Roosbridge07_dt.dff \ + Roosbridge07_nt.dff \ + Roosbridge1_dt.dff \ + Roosbridge1_nt.dff \ + Roosbridge2_dt.dff \ + Roosbridge2_nt.dff \ + Roosbridge3_dt.dff \ + Roosbridge3_nt.dff \ + Roosbridge4_dt.dff \ + Roosbridge4_nt.dff \ + Roosbridge5_dt.dff \ + Roosbridge5_nt.dff \ + Roosbridge6_dt.dff \ + Roosbridge6_nt.dff \ + Roosbridge_dt.dff \ + Roosbridge_nt.dff \ + Rose_Bush01.dff \ + Rose_Bush03.dff \ + ruger.dff \ + rumpo.dff \ + runup01.dff \ + runup.dff \ + rustship_structure0.dff \ + sabre.dff \ + sabretur.dff \ + sam.dff \ + sanchez.dff \ + sandcastle1.dff \ + sandcastle2.dff \ + sandking.dff \ + satdishbig.dff \ + satdishsml.dff \ + sb.dff \ + scarbuildglas.dff \ + scarinterblood1.dff \ + scarinterior1.dff \ + scar_winglass.dff \ + scenery.dff \ + schair.dff \ + scrapgate.dff \ + screwdriver.dff \ + seabedcars04.dff \ + seabedcars.dff \ + seabedshit02.dff \ + seabedshit1.dff \ + seacorpse01.dff \ + seacorpse.dff \ + seaspar.dff \ + securica.dff \ + sentinel.dff \ + sentxs.dff \ + seveneleven.dff \ + sewer2.dff \ + sexgarden2.dff \ + sexpad_grdn_2.dff \ + sexypoolcover.dff \ + sfrenda.dff \ + sfrendb.dff \ + Sga.dff \ + Sgb.dff \ + sgc.dff \ + sgoona.dff \ + sgoonb.dff \ + shado_chuf.dff \ + shark.dff \ + shipstairs.dff \ + shootra.dff \ + shootrb.dff \ + shopfronts03.dff \ + shopwindows_dts.dff \ + shotgspa.dff \ + shotvcr.dff \ + Shovel.dff \ + shpfrnts03rail01.dff \ + shpfrnts03rail02.dff \ + signagelh.dff \ + sjmbigsig2.dff \ + sjmbigsig.dff \ + sjmbigsign01.dff \ + sjmbigsign02.dff \ + sjmbigsign.dff \ + sjmcoast04.dff \ + sjmcoast06.dff \ + sjmcoast08.dff \ + sjmcoast.dff \ + sjmcrushbn.dff \ + sjmcrushgate.dff \ + sjmcrushju.dff \ + sjmcrushlpo.dff \ + sjmcrushmnf.dff \ + sjmcrushtankr.dff \ + sjmfsthus.dff \ + sjmgrass2.dff \ + sjmgrass.dff \ + sjmhafinrd.dff \ + sjmhoose.dff \ + sjmkhus.dff \ + sjmmet2.dff \ + sjmmet.dff \ + sjmnd04.dff \ + sjmsection.dff \ + sjmtionb02.dff \ + sjmuilding01.dff \ + sjmuilding05.dff \ + S_keep.dff \ + skimmer.dff \ + smashbar.dff \ + smashbarpost.dff \ + sniper.dff \ + spad_build2.dff \ + spad_buildnew.dff \ + spad_dr_closed1.dff \ + spad_dr_closed2.dff \ + spad_dr_closed3.dff \ + spad_dr_open1.dff \ + spad_dr_open2.dff \ + spad_dr_open3.dff \ + spad_veg1.dff \ + spad_water.dff \ + spad_watr1.dff \ + spad_watr2.dff \ + spand.dff \ + spandxa.dff \ + spandxb.dff \ + sparrow.dff \ + speed.dff \ + speeder.dff \ + squalo.dff \ + stad_roada.dff \ + stad_roadb.dff \ + stad_roadc.dff \ + stad_roadd.dff \ + stallion.dff \ + stand01.dff \ + stand02.dff \ + standblack01.dff \ + standblack02.dff \ + standblack03.dff \ + stand.dff \ + starbbnet02.dff \ + starbitbush.dff \ + starbits05.dff \ + starbits06.dff \ + starbits13b.dff \ + starbits13c.dff \ + starbits13.dff \ + starbits17.dff \ + starbits20.dff \ + starbits22.dff \ + starbits24.dff \ + starbits45.dff \ + starbits46.dff \ + starbits49.dff \ + starbits50.dff \ + starbits51.dff \ + starbits52.dff \ + starbits54.dff \ + starbits55.dff \ + starbits56.dff \ + starbits57.dff \ + starbits58.dff \ + starbits59.dff \ + starbl02b.dff \ + STARBLOCKER02.dff \ + STARBLOCKER1.dff \ + starblocks02.dff \ + starblocks03.dff \ + starblocks04.dff \ + starbridge2b.dff \ + starbridge2.dff \ + stardepot.dff \ + stardepot_FENCE.dff \ + starfbed02.dff \ + starfbed03.dff \ + starfbed04.dff \ + starfbed05.dff \ + starfbed06.dff \ + stargatedetail.dff \ + StarGate.dff \ + Starhouse.dff \ + starisland_road1.dff \ + starisland_road2.dff \ + starisland_road3.dff \ + starisland_road4.dff \ + starisland_road6.dff \ + starladder.dff \ + starmainwall1.dff \ + Starrockpool.dff \ + starsbush2.dff \ + starscaffold_pole.dff \ + starwallmain2.dff \ + starwallmain4.dff \ + starwallmain5.dff \ + starwaterfrnt05.dff \ + starwaterfrnt06b.dff \ + starwaterfrnt06.dff \ + starwaterfrnt07b.dff \ + starwaterfrnt07.dff \ + starwaterfrnt08.dff \ + starwaterfrnt09b.dff \ + starwaterfrnt09.dff \ + starwaterfrnt10.dff \ + starwaterfrnt11.dff \ + starwaterfrnt12.dff \ + stationfence.dff \ + st_garden1.dff \ + Stglue01.dff \ + Stglue06.dff \ + Stglue07.dff \ + STGLUE.dff \ + stiltsville03.dff \ + stiltsville05.dff \ + stiltsville1.dff \ + stinger.dff \ + stObject23.dff \ + Stonebench1.dff \ + stPOOLw1.dff \ + stPOOLWATER02.dff \ + stPOOLWATER5.dff \ + stpoolwater.dff \ + Streetlamp1.dff \ + Streetlamp2.dff \ + stretch.dff \ + Stripa.dff \ + stripback.dff \ + stripb.dff \ + stripc.dff \ + stripclbdrclsd.dff \ + stripclbdropen.dff \ + stripinter.dff \ + stripinterfloor.dff \ + stripinterupside.dff \ + striplights.dff \ + strpbckdrclsd.dff \ + strpclubsigns1.dff \ + strtbarrier01.dff \ + stuntman03.dff \ + stuntman2.dff \ + sub_floodlite.dff \ + submarine.dff \ + sub_roadbarrier.dff \ + sub_roadleft.dff \ + sub_roadright.dff \ + subwaygate.dff \ + sutibag.dff \ + svegrgedoor.dff \ + svehsebuild1.dff \ + svntray.dff \ + swat.dff \ + swivelchair_A.dff \ + swivelchair_B.dff \ + tall_fence.dff \ + tar_civ1.dff \ + tar_civ2.dff \ + tar_downleft.dff \ + tar_downright.dff \ + tar_frame.dff \ + tar_gun1.dff \ + tar_gun2.dff \ + tar_top.dff \ + tar_upleft.dff \ + tar_upright.dff \ + taxi_d.dff \ + taxi.DFF \ + teargas.dff \ + tec9.dff \ + telewire02.dff \ + telewire03.dff \ + telewire04.dff \ + telgrphpole02.dff \ + telly.dff \ + tesad06.dff \ + tesad06sas.dff \ + tesad06ss.dff \ + test_track.dff \ + thebolla03.dff \ + thebolla04.dff \ + thebolla1.dff \ + thebolla2.dff \ + thebowl01.dff \ + thebowl03.dff \ + thebowl04.dff \ + thebowl05.dff \ + thebowl06.dff \ + thebowl07.dff \ + thebowl08.dff \ + thebowl09.dff \ + thebowl10.dff \ + thebowl11.dff \ + thebowl12.dff \ + thebowl13.dff \ + thebowl14.dff \ + thebowl15.dff \ + thebowl16.dff \ + thebowl17.dff \ + ticketbooth1a.dff \ + ticketbooth1.dff \ + ticketbooth2a.dff \ + ticketbooth2.dff \ + tiny_rock.dff \ + topfun.dff \ + toyz.dff \ + trafficcone.dff \ + trafficlight1.dff \ + trashcan.dff \ + trash.dff \ + treeshadairha01.dff \ + treeshadairha02.dff \ + treeshadairha03.dff \ + treeshadairha04.dff \ + treeshadairha05.dff \ + treeshadairha06.dff \ + treeshadairha07.dff \ + treeshadairha08.dff \ + treeshadci01.dff \ + treeshadci02.dff \ + treeshadci03.dff \ + treeshadci04.dff \ + treeshaddtn01.dff \ + treeshaddtn02.dff \ + treeshaddtn03.dff \ + treeshaddtn04.dff \ + treeshaddtn05.dff \ + treeshaddtn06.dff \ + treeshaddtn07.dff \ + treeshaddtn08.dff \ + treeshaddtn09.dff \ + treeshaddtn10.dff \ + treeshadnbt01.dff \ + treeshadnbt02.dff \ + treeshadnbt03.dff \ + treeshadnbt04.dff \ + treeshadnbt05.dff \ + treeshadnbt06.dff \ + treeshadnbt07.dff \ + treeshadowgolf01.dff \ + treeshadowgolf02.dff \ + treeshadowgolf03.dff \ + treeshadowgolf04.dff \ + treeshadowgolf05.dff \ + treeshadowgolf06.dff \ + treeshadowgolf07.dff \ + treeshadowgolf08.dff \ + treeshadstr01.dff \ + treeshadstr02.dff \ + treeshadstr03.dff \ + treeshadwn01.dff \ + treeshadwn02.dff \ + treeshadwn03.dff \ + treeshadwn04.dff \ + treeshadwn05.dff \ + treeshadws01.dff \ + treeshadws02.dff \ + treeshadws03.dff \ + treeshadws04.dff \ + treeshadws05.dff \ + treeshadws06.dff \ + Trophies.dff \ + trophy1.dff \ + tropic.dff \ + tunnelentrance.dff \ + turtle.dff \ + undermallneon.dff \ + unrocketed_win1.dff \ + uzi.dff \ + vcnmav.dff \ + veged01.dff \ + veged02.dff \ + veged.dff \ + vegeha1.dff \ + vegetationb03.dff \ + vegetationb.dff \ + veg_fern_balcny_kb1.dff \ + veg_fern_balcny_kb2.dff \ + veg_gaz.dff \ + veg_ivy_balcny_kb3.dff \ + veg_palm01.dff \ + veg_palm02.dff \ + veg_palm03.dff \ + veg_palm04.dff \ + veg_palmbig14.dff \ + veg_palmkb10.dff \ + veg_palmkb13.dff \ + veg_palmkb14.dff \ + veg_palmkb1.dff \ + veg_palmkb2.dff \ + veg_palmkb3.dff \ + veg_palmkb4.dff \ + veg_palmkb5.dff \ + veg_palmkb7.dff \ + veg_palmkb8.dff \ + veg_palmkb9.dff \ + veg_palmkbb11.dff \ + veg_palwee01.dff \ + veg_palwee02.dff \ + veg_palwee03.dff \ + veg_tree3.dff \ + veg_treea1.dff \ + veg_treea3.dff \ + veg_treeb1.dff \ + vendmach.dff \ + vice1.dff \ + vice2.dff \ + vice3.dff \ + vice4.dff \ + vice5.dff \ + vice6.dff \ + vice7.dff \ + vice8.dff \ + vicechee.dff \ + virgo.dff \ + voodoo.dff \ + vrates.dff \ + walton.dff \ + wash_ammu.dff \ + washawning1.dff \ + washawning2b.dff \ + washawninga1.dff \ + washbitsnew03.dff \ + washbitsnew04.dff \ + washbld195fenc.dff \ + wash_brdgsup1.dff \ + washbuild003.dff \ + washbuild005.dff \ + washbuild013.dff \ + washbuild014.dff \ + washbuild015.dff \ + washbuild017.dff \ + washbuild018.dff \ + washbuild020.dff \ + washbuild021.dff \ + washbuild025.dff \ + washbuild025neon.dff \ + washbuild030.dff \ + washbuild033.dff \ + washbuild039.dff \ + washbuild040.dff \ + washbuild041.dff \ + washbuild043.dff \ + washbuild05067.dff \ + washbuild050.dff \ + washbuild058.dff \ + washbuild062.dff \ + washbuild063.dff \ + washbuild066.dff \ + washbuild068.dff \ + washbuild069.dff \ + washbuild071.dff \ + washbuild072.dff \ + washbuild073.dff \ + washbuild074.dff \ + washbuild075.dff \ + washbuild0812.dff \ + washbuild081.dff \ + washbuild111.dff \ + washbuild112.dff \ + washbuild114.dff \ + washbuild116.dff \ + washbuild119.dff \ + washbuild120.dff \ + washbuild121.dff \ + washbuild184.dff \ + washbuild187.dff \ + washbuild191.dff \ + washbuild192.dff \ + washbuild194.dff \ + washbuild195.dff \ + washbuild198.dff \ + washbuild199.dff \ + washbuild200.dff \ + washbuild201.dff \ + washbuild202.dff \ + washbuild203.dff \ + washbuild206.dff \ + washbuild210.dff \ + washbuild213.dff \ + washbuild214.dff \ + washbuild215.dff \ + washbuild216.dff \ + washbuild217.dff \ + washbuild231.dff \ + washcrprkdirt1.dff \ + wash_deco012.dff \ + wash_deco01.dff \ + wash_deco022.dff \ + wash_deco02.dff \ + wash_deco032.dff \ + wash_deco03.dff \ + wash_deco042.dff \ + wash_deco04.dff \ + wash_deco05.dff \ + washdeconeon1.dff \ + washdirtbuild02.dff \ + washdirtbuild1.dff \ + washelipad.dff \ + washer.dff \ + washfence1.dff \ + washfence2.dff \ + washfntinwtr.dff \ + washgasneon01.dff \ + washgasneon02.dff \ + washgasneon.dff \ + washgaspump.dff \ + wash_hardwares.dff \ + wash_hardwindow.dff \ + wash_hosp02.dff \ + wash_hosp03.dff \ + wash_hospgrnd.dff \ + wash_hospital.dff \ + washing.dff \ + washmallwater.dff \ + washnewsky1.dff \ + washnewsky2.dff \ + washnewsky3.dff \ + washotelneon1.dff \ + washpaynspray.dff \ + wash_pizzaplace.dff \ + washpshoutdet.dff \ + washpshoutveg.dff \ + washrafalpha.dff \ + washskyplant1.dff \ + washskyplant2.dff \ + washtooceanrd1.dff \ + washundermall1.dff \ + washvegy237.dff \ + washvegy238.dff \ + washvegy239.dff \ + washvegy240.dff \ + washvegy2413.dff \ + washvegy241.dff \ + washvegy242.dff \ + washvegy2.dff \ + washvegy3.dff \ + washvegy4.dff \ + wastebin.dff \ + waterjump1.dff \ + waterjump2.dff \ + waterjumpx2.dff \ + wfobe.dff \ + wfogo.dff \ + wfori.dff \ + wfosh.dff \ + wfost.dff \ + wfotr.dff \ + wfybe.dff \ + wfybu.dff \ + wfycst.dff \ + wfyg1.dff \ + wfyg2.dff \ + wfyjg.dff \ + WFYLG.dff \ + wfypr.dff \ + wfyri.dff \ + wfysh.dff \ + wfysk.dff \ + wfyst.dff \ + wglasssmash.dff \ + wmobe.dff \ + wmobu.dff \ + wmoca.dff \ + wmogo.dff \ + wmori.dff \ + wmost.dff \ + wmotr.dff \ + wmybe.dff \ + wmybu.dff \ + wmycr.dff \ + wmycw.dff \ + wmygo.dff \ + wmyjg.dff \ + WMYLG.dff \ + wmypi.dff \ + wmyri.dff \ + wmysk.dff \ + wmyst.dff \ + woodenbox.dff \ + woshmaldirt1.dff \ + woshmallglas1.dff \ + woshmallglas2.dff \ + woshmalmarkings.dff \ + woshmalneon2.dff \ + wsgbuildwl97.dff \ + wshammuglas.dff \ + wshbldws26bit.dff \ + wshbuil19wall.dff \ + wshbuildneon9.dff \ + wshbuildws02.dff \ + wshbuildws03.dff \ + wshbuildws04.dff \ + wshbuildws05.dff \ + wshbuildws06.dff \ + wshbuildws07.dff \ + wshbuildws08.dff \ + wshbuildws09.dff \ + wshbuildws10.dff \ + wshbuildws12.dff \ + wshbuildws13.dff \ + wshbuildws14.dff \ + wshbuildws16.dff \ + wshbuildws17.dff \ + wshbuildws18.dff \ + wshbuildws19.dff \ + wshbuildws20.dff \ + wshbuildws21.dff \ + wshbuildws22.dff \ + wshbuildws2326.dff \ + wshbuildws2329.dff \ + wshbuildws23.dff \ + wshbuildws24.dff \ + wshbuildws25.dff \ + wshbuildws264.dff \ + wshbuildws26.dff \ + wshbuildws27.dff \ + wshbuildws28.dff \ + wshbuildws29.dff \ + wshbuildws30.dff \ + wshbuildws312.dff \ + wshbuildws31.dff \ + wshbuildws322.dff \ + wshbuildws32.dff \ + wshbuildws332.dff \ + wshbuildws33.dff \ + wshbuildws35.dff \ + wshbuildws36.dff \ + wshbuildws38.dff \ + wshbuildws40.dff \ + wshbuildws41.dff \ + wshbuildws42.dff \ + wshbuildws43.dff \ + wshbuildws43ins2.dff \ + wshbuildws43ins.dff \ + wshbuildws44.dff \ + wshbuildws45.dff \ + wshcarprksign.dff \ + wshchurchwal.dff \ + wshcokland.dff \ + wshcrprtstuf1.dff \ + wshcrprtstuf2.dff \ + wshgarage02.dff \ + wshgarage03.dff \ + wshgarage1.dff \ + wshhotelawn1.dff \ + wshhotelwater1.dff \ + wshmallneon.dff \ + wshmalsign1.dff \ + wshmalsign2.dff \ + wshmalsign3.dff \ + wshmalsign4.dff \ + wshmalsign5.dff \ + wshmoundlaw1.dff \ + wshneon.dff \ + wshnrthlawdoor.dff \ + wshnrthpstr02.dff \ + wshnrthpstr03.dff \ + wshnrthpstr05.dff \ + wshnrthpstr07.dff \ + wshnrthpstr1.dff \ + wshnrthroad01.dff \ + wshnrthroad02.dff \ + wshnrthroad03.dff \ + wshnrthroad04.dff \ + wshnrthroad05.dff \ + wshnrthroad06.dff \ + wshnrthroad07.dff \ + wshnrthroad08.dff \ + wshnrthroad09.dff \ + wshnrthroad10.dff \ + wshnrthroad11.dff \ + wshnrthroad12.dff \ + wshnrthroad13.dff \ + wshnrthroad14.dff \ + wshnrthroad15.dff \ + wshnrthroad16.dff \ + wshnrthroad17.dff \ + wshnrthroad18.dff \ + wshnrthroad19.dff \ + wshnrthroad20.dff \ + wshnrthroad21.dff \ + wshnrthroad22.dff \ + wshnrthroad23.dff \ + wshnrthroad24.dff \ + wshnrthroad25.dff \ + wshnrthroad26.dff \ + wshnrthroad27.dff \ + wshnrthroad28.dff \ + wshotelsign35.dff \ + wshotelveg1.dff \ + wshperdrclsd.dff \ + wshpnsdoor.dff \ + wshpnssignday_dy.dff \ + wshpnssignight_nt.dff \ + wshprkbarrier1.dff \ + wshprkbarrier2.dff \ + wshpspryneon1.dff \ + wsh_roadblock.dff \ + wsh_roadswsh01.dff \ + wsh_roadswsh04.dff \ + wsh_roadswsh05.dff \ + wsh_roadswsh28.dff \ + wshscarland226.dff \ + wshsthpstr02.dff \ + wshsthpstr03.dff \ + wshsthpstr04.dff \ + wshsthpstr05.dff \ + wshsthroad01.dff \ + wshsthroad02.dff \ + wshsthroad03.dff \ + wshsthroad04.dff \ + wshsthroad05.dff \ + wshsthroad06.dff \ + wshsthroad07.dff \ + wshsthroad08.dff \ + wshsthroad09.dff \ + wshsthroad10.dff \ + wshsthroad11.dff \ + wshsthroad12.dff \ + wshsthroad13.dff \ + wshsthroad14.dff \ + wshsthroad15.dff \ + wshsthroad16.dff \ + wshsthroad17.dff \ + wshsthroad18.dff \ + wshsthroad19.dff \ + wshsthroad20.dff \ + wshsthroad21.dff \ + wshsthroad22.dff \ + wshsthroad23.dff \ + wshsthroad24.dff \ + wshswimupbar.dff \ + wshswmupstuf.dff \ + wshtelgrphcabl01.dff \ + wshtelgrphcabl02.dff \ + wshtelgrphcabl03.dff \ + wshtelgrphcabl04.dff \ + wshtelgrphcabl05.dff \ + wshtelgrphcabl06.dff \ + wshtelgrphcabl07.dff \ + wshtelgrphcabl08.dff \ + wshtelgrphcabl09.dff \ + wshtelgrphcabl10.dff \ + wshtelgrphcabl11.dff \ + wshtelwter1.dff \ + wshundercrprk27.dff \ + wshxrefhirise1.dff \ + wshxrefhse1.dff \ + wshxrefhse2.dff \ + wslawyersfront.dff \ + wtn_roadsigns01.dff \ + xod_beacon_dy.dff \ + xod_beacon_nt.dff \ + xod_leslie_dy.dff \ + xod_leslie_nt.dff \ + xod_macalpin_dy.dff \ + xod_macalpin_nt.dff \ + xod_majestic_dy.dff \ + xod_majestic_nt.dff \ + xod_starlite_dy.dff \ + xod_starlite_nt.dff \ + xod_tides_dy.dff \ + xod_tides_nt.dff \ + xpolytrees1_dt.dff \ + xpolytrees2_dt.dff \ + xpolytrees3_dt.dff \ + xpolytrees4_dt.dff \ + yacht_chunk_kb.dff \ + yankee.dff \ + yt_doors14.dff \ + yt_gangplnk_tmp.dff \ + yt_main_body2.dff \ + yt_main_body.dff \ + yt_tmp_boat.dff \ + zebra.dff \ No newline at end of file diff --git a/miami/mp3list.mk b/miami/mp3list.mk new file mode 100644 index 00000000..27fb0ef6 --- /dev/null +++ b/miami/mp3list.mk @@ -0,0 +1,102 @@ +STREAM_ADF = \ + EMOTION.adf \ + ESPANT.adf \ + FEVER.adf \ + FLASH.adf \ + KCHAT.adf \ + VCPR.adf \ + VROCK.adf \ + WAVE.adf \ + WILD.adf + +STREAM_MP3 = \ + cnt_1b.mp3 \ + int_b.mp3 \ + rok_1.mp3 \ + phil_2.mp3 \ + cok_2b.mp3 \ + Malibu.mp3 \ + bank_1.mp3 \ + Hwater.mp3 \ + bclosed.mp3 \ + resc_1a.mp3 \ + bank_3a.mp3 \ + bike_3.mp3 \ + tex_2.mp3 \ + col_2.mp3 \ + porn_3.mp3 \ + bike_1.mp3 \ + finale.mp3 \ + ass_2.mp3 \ + law_4.mp3 \ + car_1.mp3 \ + bank_2b.mp3 \ + tex_1.mp3 \ + cok_4b.mp3 \ + miscom.mp3 \ + bank_2a.mp3 \ + law_1b.mp3 \ + MallAmb.mp3 \ + bank_3b.mp3 \ + ass_1.mp3 \ + DirtRing.mp3 \ + col_5a.mp3 \ + tax_1.mp3 \ + cnt_1a.mp3 \ + bike_2.mp3 \ + int_a.mp3 \ + bud_1.mp3 \ + cok_2a.mp3 \ + City.mp3 \ + cap_1.mp3 \ + int_m.mp3 \ + cnt_2.mp3 \ + tex_3.mp3 \ + law_2c.mp3 \ + law_2b.mp3 \ + col_3a.mp3 \ + cok_4a2.mp3 \ + rok_2.mp3 \ + ice_1.mp3 \ + hat_1.mp3 \ + hat_2.mp3 \ + cub_4.mp3 \ + Glight.mp3 \ + Strip.mp3 \ + bud_3.mp3 \ + Hbeach.mp3 \ + bopen.mp3 \ + fin2.mp3 \ + cub_3.mp3 \ + cok_4a.mp3 \ + porn_2.mp3 \ + cub_1.mp3 \ + col_4a.mp3 \ + Hotel.mp3 \ + police.mp3 \ + law_2a.mp3 \ + Law4Riot.mp3 \ + cok_1.mp3 \ + col_5b.mp3 \ + Water.mp3 \ + HCity.mp3 \ + cok_3.mp3 \ + law_1a.mp3 \ + porn_1.mp3 \ + porn_4.mp3 \ + taxi.mp3 \ + cub_2.mp3 \ + law_3.mp3 \ + hat_3.mp3 \ + phil_1.mp3 \ + bank_4.mp3 \ + BeachAmb.mp3 \ + FIST.mp3 \ + bud_2.mp3 \ + ambsil.mp3 \ + int_d.mp3 \ + fin.mp3 \ + rok_3a.mp3 \ + drug_1.mp3 \ + stripa.mp3 \ + col_1.mp3 \ No newline at end of file diff --git a/miami/sfxlist.mk b/miami/sfxlist.mk new file mode 100644 index 00000000..aae163ff --- /dev/null +++ b/miami/sfxlist.mk @@ -0,0 +1,9943 @@ +SFX_WAV = \ + sfx_0.wav \ + sfx_1000.wav \ + sfx_1001.wav \ + sfx_1002.wav \ + sfx_1003.wav \ + sfx_1004.wav \ + sfx_1005.wav \ + sfx_1006.wav \ + sfx_1007.wav \ + sfx_1008.wav \ + sfx_1009.wav \ + sfx_100.wav \ + sfx_1010.wav \ + sfx_1011.wav \ + sfx_1012.wav \ + sfx_1013.wav \ + sfx_1014.wav \ + sfx_1015.wav \ + sfx_1016.wav \ + sfx_1017.wav \ + sfx_1018.wav \ + sfx_1019.wav \ + sfx_101.wav \ + sfx_1020.wav \ + sfx_1021.wav \ + sfx_1022.wav \ + sfx_1023.wav \ + sfx_1024.wav \ + sfx_1025.wav \ + sfx_1026.wav \ + sfx_1027.wav \ + sfx_1028.wav \ + sfx_1029.wav \ + sfx_102.wav \ + sfx_1030.wav \ + sfx_1031.wav \ + sfx_1032.wav \ + sfx_1033.wav \ + sfx_1034.wav \ + sfx_1035.wav \ + sfx_1036.wav \ + sfx_1037.wav \ + sfx_1038.wav \ + sfx_1039.wav \ + sfx_103.wav \ + sfx_1040.wav \ + sfx_1041.wav \ + sfx_1042.wav \ + sfx_1043.wav \ + sfx_1044.wav \ + sfx_1045.wav \ + sfx_1046.wav \ + sfx_1047.wav \ + sfx_1048.wav \ + sfx_1049.wav \ + sfx_104.wav \ + sfx_1050.wav \ + sfx_1051.wav \ + sfx_1052.wav \ + sfx_1053.wav \ + sfx_1054.wav \ + sfx_1055.wav \ + sfx_1056.wav \ + sfx_1057.wav \ + sfx_1058.wav \ + sfx_1059.wav \ + sfx_105.wav \ + sfx_1060.wav \ + sfx_1061.wav \ + sfx_1062.wav \ + sfx_1063.wav \ + sfx_1064.wav \ + sfx_1065.wav \ + sfx_1066.wav \ + sfx_1067.wav \ + sfx_1068.wav \ + sfx_1069.wav \ + sfx_106.wav \ + sfx_1070.wav \ + sfx_1071.wav \ + sfx_1072.wav \ + sfx_1073.wav \ + sfx_1074.wav \ + sfx_1075.wav \ + sfx_1076.wav \ + sfx_1077.wav \ + sfx_1078.wav \ + sfx_1079.wav \ + sfx_107.wav \ + sfx_1080.wav \ + sfx_1081.wav \ + sfx_1082.wav \ + sfx_1083.wav \ + sfx_1084.wav \ + sfx_1085.wav \ + sfx_1086.wav \ + sfx_1087.wav \ + sfx_1088.wav \ + sfx_1089.wav \ + sfx_108.wav \ + sfx_1090.wav \ + sfx_1091.wav \ + sfx_1092.wav \ + sfx_1093.wav \ + sfx_1094.wav \ + sfx_1095.wav \ + sfx_1096.wav \ + sfx_1097.wav \ + sfx_1098.wav \ + sfx_1099.wav \ + sfx_109.wav \ + sfx_10.wav \ + sfx_1100.wav \ + sfx_1101.wav \ + sfx_1102.wav \ + sfx_1103.wav \ + sfx_1104.wav \ + sfx_1105.wav \ + sfx_1106.wav \ + sfx_1107.wav \ + sfx_1108.wav \ + sfx_1109.wav \ + sfx_110.wav \ + sfx_1110.wav \ + sfx_1111.wav \ + sfx_1112.wav \ + sfx_1113.wav \ + sfx_1114.wav \ + sfx_1115.wav \ + sfx_1116.wav \ + sfx_1117.wav \ + sfx_1118.wav \ + sfx_1119.wav \ + sfx_111.wav \ + sfx_1120.wav \ + sfx_1121.wav \ + sfx_1122.wav \ + sfx_1123.wav \ + sfx_1124.wav \ + sfx_1125.wav \ + sfx_1126.wav \ + sfx_1127.wav \ + sfx_1128.wav \ + sfx_1129.wav \ + sfx_112.wav \ + sfx_1130.wav \ + sfx_1131.wav \ + sfx_1132.wav \ + sfx_1133.wav \ + sfx_1134.wav \ + sfx_1135.wav \ + sfx_1136.wav \ + sfx_1137.wav \ + sfx_1138.wav \ + sfx_1139.wav \ + sfx_113.wav \ + sfx_1140.wav \ + sfx_1141.wav \ + sfx_1142.wav \ + sfx_1143.wav \ + sfx_1144.wav \ + sfx_1145.wav \ + sfx_1146.wav \ + sfx_1147.wav \ + sfx_1148.wav \ + sfx_1149.wav \ + sfx_114.wav \ + sfx_1150.wav \ + sfx_1151.wav \ + sfx_1152.wav \ + sfx_1153.wav \ + sfx_1154.wav \ + sfx_1155.wav \ + sfx_1156.wav \ + sfx_1157.wav \ + sfx_1158.wav \ + sfx_1159.wav \ + sfx_115.wav \ + sfx_1160.wav \ + sfx_1161.wav \ + sfx_1162.wav \ + sfx_1163.wav \ + sfx_1164.wav \ + sfx_1165.wav \ + sfx_1166.wav \ + sfx_1167.wav \ + sfx_1168.wav \ + sfx_1169.wav \ + sfx_116.wav \ + sfx_1170.wav \ + sfx_1171.wav \ + sfx_1172.wav \ + sfx_1173.wav \ + sfx_1174.wav \ + sfx_1175.wav \ + sfx_1176.wav \ + sfx_1177.wav \ + sfx_1178.wav \ + sfx_1179.wav \ + sfx_117.wav \ + sfx_1180.wav \ + sfx_1181.wav \ + sfx_1182.wav \ + sfx_1183.wav \ + sfx_1184.wav \ + sfx_1185.wav \ + sfx_1186.wav \ + sfx_1187.wav \ + sfx_1188.wav \ + sfx_1189.wav \ + sfx_118.wav \ + sfx_1190.wav \ + sfx_1191.wav \ + sfx_1192.wav \ + sfx_1193.wav \ + sfx_1194.wav \ + sfx_1195.wav \ + sfx_1196.wav \ + sfx_1197.wav \ + sfx_1198.wav \ + sfx_1199.wav \ + sfx_119.wav \ + sfx_11.wav \ + sfx_1200.wav \ + sfx_1201.wav \ + sfx_1202.wav \ + sfx_1203.wav \ + sfx_1204.wav \ + sfx_1205.wav \ + sfx_1206.wav \ + sfx_1207.wav \ + sfx_1208.wav \ + sfx_1209.wav \ + sfx_120.wav \ + sfx_1210.wav \ + sfx_1211.wav \ + sfx_1212.wav \ + sfx_1213.wav \ + sfx_1214.wav \ + sfx_1215.wav \ + sfx_1216.wav \ + sfx_1217.wav \ + sfx_1218.wav \ + sfx_1219.wav \ + sfx_121.wav \ + sfx_1220.wav \ + sfx_1221.wav \ + sfx_1222.wav \ + sfx_1223.wav \ + sfx_1224.wav \ + sfx_1225.wav \ + sfx_1226.wav \ + sfx_1227.wav \ + sfx_1228.wav \ + sfx_1229.wav \ + sfx_122.wav \ + sfx_1230.wav \ + sfx_1231.wav \ + sfx_1232.wav \ + sfx_1233.wav \ + sfx_1234.wav \ + sfx_1235.wav \ + sfx_1236.wav \ + sfx_1237.wav \ + sfx_1238.wav \ + sfx_1239.wav \ + sfx_123.wav \ + sfx_1240.wav \ + sfx_1241.wav \ + sfx_1242.wav \ + sfx_1243.wav \ + sfx_1244.wav \ + sfx_1245.wav \ + sfx_1246.wav \ + sfx_1247.wav \ + sfx_1248.wav \ + sfx_1249.wav \ + sfx_124.wav \ + sfx_1250.wav \ + sfx_1251.wav \ + sfx_1252.wav \ + sfx_1253.wav \ + sfx_1254.wav \ + sfx_1255.wav \ + sfx_1256.wav \ + sfx_1257.wav \ + sfx_1258.wav \ + sfx_1259.wav \ + sfx_125.wav \ + sfx_1260.wav \ + sfx_1261.wav \ + sfx_1262.wav \ + sfx_1263.wav \ + sfx_1264.wav \ + sfx_1265.wav \ + sfx_1266.wav \ + sfx_1267.wav \ + sfx_1268.wav \ + sfx_1269.wav \ + sfx_126.wav \ + sfx_1270.wav \ + sfx_1271.wav \ + sfx_1272.wav \ + sfx_1273.wav \ + sfx_1274.wav \ + sfx_1275.wav \ + sfx_1276.wav \ + sfx_1277.wav \ + sfx_1278.wav \ + sfx_1279.wav \ + sfx_127.wav \ + sfx_1280.wav \ + sfx_1281.wav \ + sfx_1282.wav \ + sfx_1283.wav \ + sfx_1284.wav \ + sfx_1285.wav \ + sfx_1286.wav \ + sfx_1287.wav \ + sfx_1288.wav \ + sfx_1289.wav \ + sfx_128.wav \ + sfx_1290.wav \ + sfx_1291.wav \ + sfx_1292.wav \ + sfx_1293.wav \ + sfx_1294.wav \ + sfx_1295.wav \ + sfx_1296.wav \ + sfx_1297.wav \ + sfx_1298.wav \ + sfx_1299.wav \ + sfx_129.wav \ + sfx_12.wav \ + sfx_1300.wav \ + sfx_1301.wav \ + sfx_1302.wav \ + sfx_1303.wav \ + sfx_1304.wav \ + sfx_1305.wav \ + sfx_1306.wav \ + sfx_1307.wav \ + sfx_1308.wav \ + sfx_1309.wav \ + sfx_130.wav \ + sfx_1310.wav \ + sfx_1311.wav \ + sfx_1312.wav \ + sfx_1313.wav \ + sfx_1314.wav \ + sfx_1315.wav \ + sfx_1316.wav \ + sfx_1317.wav \ + sfx_1318.wav \ + sfx_1319.wav \ + sfx_131.wav \ + sfx_1320.wav \ + sfx_1321.wav \ + sfx_1322.wav \ + sfx_1323.wav \ + sfx_1324.wav \ + sfx_1325.wav \ + sfx_1326.wav \ + sfx_1327.wav \ + sfx_1328.wav \ + sfx_1329.wav \ + sfx_132.wav \ + sfx_1330.wav \ + sfx_1331.wav \ + sfx_1332.wav \ + sfx_1333.wav \ + sfx_1334.wav \ + sfx_1335.wav \ + sfx_1336.wav \ + sfx_1337.wav \ + sfx_1338.wav \ + sfx_1339.wav \ + sfx_133.wav \ + sfx_1340.wav \ + sfx_1341.wav \ + sfx_1342.wav \ + sfx_1343.wav \ + sfx_1344.wav \ + sfx_1345.wav \ + sfx_1346.wav \ + sfx_1347.wav \ + sfx_1348.wav \ + sfx_1349.wav \ + sfx_134.wav \ + sfx_1350.wav \ + sfx_1351.wav \ + sfx_1352.wav \ + sfx_1353.wav \ + sfx_1354.wav \ + sfx_1355.wav \ + sfx_1356.wav \ + sfx_1357.wav \ + sfx_1358.wav \ + sfx_1359.wav \ + sfx_135.wav \ + sfx_1360.wav \ + sfx_1361.wav \ + sfx_1362.wav \ + sfx_1363.wav \ + sfx_1364.wav \ + sfx_1365.wav \ + sfx_1366.wav \ + sfx_1367.wav \ + sfx_1368.wav \ + sfx_1369.wav \ + sfx_136.wav \ + sfx_1370.wav \ + sfx_1371.wav \ + sfx_1372.wav \ + sfx_1373.wav \ + sfx_1374.wav \ + sfx_1375.wav \ + sfx_1376.wav \ + sfx_1377.wav \ + sfx_1378.wav \ + sfx_1379.wav \ + sfx_137.wav \ + sfx_1380.wav \ + sfx_1381.wav \ + sfx_1382.wav \ + sfx_1383.wav \ + sfx_1384.wav \ + sfx_1385.wav \ + sfx_1386.wav \ + sfx_1387.wav \ + sfx_1388.wav \ + sfx_1389.wav \ + sfx_138.wav \ + sfx_1390.wav \ + sfx_1391.wav \ + sfx_1392.wav \ + sfx_1393.wav \ + sfx_1394.wav \ + sfx_1395.wav \ + sfx_1396.wav \ + sfx_1397.wav \ + sfx_1398.wav \ + sfx_1399.wav \ + sfx_139.wav \ + sfx_13.wav \ + sfx_1400.wav \ + sfx_1401.wav \ + sfx_1402.wav \ + sfx_1403.wav \ + sfx_1404.wav \ + sfx_1405.wav \ + sfx_1406.wav \ + sfx_1407.wav \ + sfx_1408.wav \ + sfx_1409.wav \ + sfx_140.wav \ + sfx_1410.wav \ + sfx_1411.wav \ + sfx_1412.wav \ + sfx_1413.wav \ + sfx_1414.wav \ + sfx_1415.wav \ + sfx_1416.wav \ + sfx_1417.wav \ + sfx_1418.wav \ + sfx_1419.wav \ + sfx_141.wav \ + sfx_1420.wav \ + sfx_1421.wav \ + sfx_1422.wav \ + sfx_1423.wav \ + sfx_1424.wav \ + sfx_1425.wav \ + sfx_1426.wav \ + sfx_1427.wav \ + sfx_1428.wav \ + sfx_1429.wav \ + sfx_142.wav \ + sfx_1430.wav \ + sfx_1431.wav \ + sfx_1432.wav \ + sfx_1433.wav \ + sfx_1434.wav \ + sfx_1435.wav \ + sfx_1436.wav \ + sfx_1437.wav \ + sfx_1438.wav \ + sfx_1439.wav \ + sfx_143.wav \ + sfx_1440.wav \ + sfx_1441.wav \ + sfx_1442.wav \ + sfx_1443.wav \ + sfx_1444.wav \ + sfx_1445.wav \ + sfx_1446.wav \ + sfx_1447.wav \ + sfx_1448.wav \ + sfx_1449.wav \ + sfx_144.wav \ + sfx_1450.wav \ + sfx_1451.wav \ + sfx_1452.wav \ + sfx_1453.wav \ + sfx_1454.wav \ + sfx_1455.wav \ + sfx_1456.wav \ + sfx_1457.wav \ + sfx_1458.wav \ + sfx_1459.wav \ + sfx_145.wav \ + sfx_1460.wav \ + sfx_1461.wav \ + sfx_1462.wav \ + sfx_1463.wav \ + sfx_1464.wav \ + sfx_1465.wav \ + sfx_1466.wav \ + sfx_1467.wav \ + sfx_1468.wav \ + sfx_1469.wav \ + sfx_146.wav \ + sfx_1470.wav \ + sfx_1471.wav \ + sfx_1472.wav \ + sfx_1473.wav \ + sfx_1474.wav \ + sfx_1475.wav \ + sfx_1476.wav \ + sfx_1477.wav \ + sfx_1478.wav \ + sfx_1479.wav \ + sfx_147.wav \ + sfx_1480.wav \ + sfx_1481.wav \ + sfx_1482.wav \ + sfx_1483.wav \ + sfx_1484.wav \ + sfx_1485.wav \ + sfx_1486.wav \ + sfx_1487.wav \ + sfx_1488.wav \ + sfx_1489.wav \ + sfx_148.wav \ + sfx_1490.wav \ + sfx_1491.wav \ + sfx_1492.wav \ + sfx_1493.wav \ + sfx_1494.wav \ + sfx_1495.wav \ + sfx_1496.wav \ + sfx_1497.wav \ + sfx_1498.wav \ + sfx_1499.wav \ + sfx_149.wav \ + sfx_14.wav \ + sfx_1500.wav \ + sfx_1501.wav \ + sfx_1502.wav \ + sfx_1503.wav \ + sfx_1504.wav \ + sfx_1505.wav \ + sfx_1506.wav \ + sfx_1507.wav \ + sfx_1508.wav \ + sfx_1509.wav \ + sfx_150.wav \ + sfx_1510.wav \ + sfx_1511.wav \ + sfx_1512.wav \ + sfx_1513.wav \ + sfx_1514.wav \ + sfx_1515.wav \ + sfx_1516.wav \ + sfx_1517.wav \ + sfx_1518.wav \ + sfx_1519.wav \ + sfx_151.wav \ + sfx_1520.wav \ + sfx_1521.wav \ + sfx_1522.wav \ + sfx_1523.wav \ + sfx_1524.wav \ + sfx_1525.wav \ + sfx_1526.wav \ + sfx_1527.wav \ + sfx_1528.wav \ + sfx_1529.wav \ + sfx_152.wav \ + sfx_1530.wav \ + sfx_1531.wav \ + sfx_1532.wav \ + sfx_1533.wav \ + sfx_1534.wav \ + sfx_1535.wav \ + sfx_1536.wav \ + sfx_1537.wav \ + sfx_1538.wav \ + sfx_1539.wav \ + sfx_153.wav \ + sfx_1540.wav \ + sfx_1541.wav \ + sfx_1542.wav \ + sfx_1543.wav \ + sfx_1544.wav \ + sfx_1545.wav \ + sfx_1546.wav \ + sfx_1547.wav \ + sfx_1548.wav \ + sfx_1549.wav \ + sfx_154.wav \ + sfx_1550.wav \ + sfx_1551.wav \ + sfx_1552.wav \ + sfx_1553.wav \ + sfx_1554.wav \ + sfx_1555.wav \ + sfx_1556.wav \ + sfx_1557.wav \ + sfx_1558.wav \ + sfx_1559.wav \ + sfx_155.wav \ + sfx_1560.wav \ + sfx_1561.wav \ + sfx_1562.wav \ + sfx_1563.wav \ + sfx_1564.wav \ + sfx_1565.wav \ + sfx_1566.wav \ + sfx_1567.wav \ + sfx_1568.wav \ + sfx_1569.wav \ + sfx_156.wav \ + sfx_1570.wav \ + sfx_1571.wav \ + sfx_1572.wav \ + sfx_1573.wav \ + sfx_1574.wav \ + sfx_1575.wav \ + sfx_1576.wav \ + sfx_1577.wav \ + sfx_1578.wav \ + sfx_1579.wav \ + sfx_157.wav \ + sfx_1580.wav \ + sfx_1581.wav \ + sfx_1582.wav \ + sfx_1583.wav \ + sfx_1584.wav \ + sfx_1585.wav \ + sfx_1586.wav \ + sfx_1587.wav \ + sfx_1588.wav \ + sfx_1589.wav \ + sfx_158.wav \ + sfx_1590.wav \ + sfx_1591.wav \ + sfx_1592.wav \ + sfx_1593.wav \ + sfx_1594.wav \ + sfx_1595.wav \ + sfx_1596.wav \ + sfx_1597.wav \ + sfx_1598.wav \ + sfx_1599.wav \ + sfx_159.wav \ + sfx_15.wav \ + sfx_1600.wav \ + sfx_1601.wav \ + sfx_1602.wav \ + sfx_1603.wav \ + sfx_1604.wav \ + sfx_1605.wav \ + sfx_1606.wav \ + sfx_1607.wav \ + sfx_1608.wav \ + sfx_1609.wav \ + sfx_160.wav \ + sfx_1610.wav \ + sfx_1611.wav \ + sfx_1612.wav \ + sfx_1613.wav \ + sfx_1614.wav \ + sfx_1615.wav \ + sfx_1616.wav \ + sfx_1617.wav \ + sfx_1618.wav \ + sfx_1619.wav \ + sfx_161.wav \ + sfx_1620.wav \ + sfx_1621.wav \ + sfx_1622.wav \ + sfx_1623.wav \ + sfx_1624.wav \ + sfx_1625.wav \ + sfx_1626.wav \ + sfx_1627.wav \ + sfx_1628.wav \ + sfx_1629.wav \ + sfx_162.wav \ + sfx_1630.wav \ + sfx_1631.wav \ + sfx_1632.wav \ + sfx_1633.wav \ + sfx_1634.wav \ + sfx_1635.wav \ + sfx_1636.wav \ + sfx_1637.wav \ + sfx_1638.wav \ + sfx_1639.wav \ + sfx_163.wav \ + sfx_1640.wav \ + sfx_1641.wav \ + sfx_1642.wav \ + sfx_1643.wav \ + sfx_1644.wav \ + sfx_1645.wav \ + sfx_1646.wav \ + sfx_1647.wav \ + sfx_1648.wav \ + sfx_1649.wav \ + sfx_164.wav \ + sfx_1650.wav \ + sfx_1651.wav \ + sfx_1652.wav \ + sfx_1653.wav \ + sfx_1654.wav \ + sfx_1655.wav \ + sfx_1656.wav \ + sfx_1657.wav \ + sfx_1658.wav \ + sfx_1659.wav \ + sfx_165.wav \ + sfx_1660.wav \ + sfx_1661.wav \ + sfx_1662.wav \ + sfx_1663.wav \ + sfx_1664.wav \ + sfx_1665.wav \ + sfx_1666.wav \ + sfx_1667.wav \ + sfx_1668.wav \ + sfx_1669.wav \ + sfx_166.wav \ + sfx_1670.wav \ + sfx_1671.wav \ + sfx_1672.wav \ + sfx_1673.wav \ + sfx_1674.wav \ + sfx_1675.wav \ + sfx_1676.wav \ + sfx_1677.wav \ + sfx_1678.wav \ + sfx_1679.wav \ + sfx_167.wav \ + sfx_1680.wav \ + sfx_1681.wav \ + sfx_1682.wav \ + sfx_1683.wav \ + sfx_1684.wav \ + sfx_1685.wav \ + sfx_1686.wav \ + sfx_1687.wav \ + sfx_1688.wav \ + sfx_1689.wav \ + sfx_168.wav \ + sfx_1690.wav \ + sfx_1691.wav \ + sfx_1692.wav \ + sfx_1693.wav \ + sfx_1694.wav \ + sfx_1695.wav \ + sfx_1696.wav \ + sfx_1697.wav \ + sfx_1698.wav \ + sfx_1699.wav \ + sfx_169.wav \ + sfx_16.wav \ + sfx_1700.wav \ + sfx_1701.wav \ + sfx_1702.wav \ + sfx_1703.wav \ + sfx_1704.wav \ + sfx_1705.wav \ + sfx_1706.wav \ + sfx_1707.wav \ + sfx_1708.wav \ + sfx_1709.wav \ + sfx_170.wav \ + sfx_1710.wav \ + sfx_1711.wav \ + sfx_1712.wav \ + sfx_1713.wav \ + sfx_1714.wav \ + sfx_1715.wav \ + sfx_1716.wav \ + sfx_1717.wav \ + sfx_1718.wav \ + sfx_1719.wav \ + sfx_171.wav \ + sfx_1720.wav \ + sfx_1721.wav \ + sfx_1722.wav \ + sfx_1723.wav \ + sfx_1724.wav \ + sfx_1725.wav \ + sfx_1726.wav \ + sfx_1727.wav \ + sfx_1728.wav \ + sfx_1729.wav \ + sfx_172.wav \ + sfx_1730.wav \ + sfx_1731.wav \ + sfx_1732.wav \ + sfx_1733.wav \ + sfx_1734.wav \ + sfx_1735.wav \ + sfx_1736.wav \ + sfx_1737.wav \ + sfx_1738.wav \ + sfx_1739.wav \ + sfx_173.wav \ + sfx_1740.wav \ + sfx_1741.wav \ + sfx_1742.wav \ + sfx_1743.wav \ + sfx_1744.wav \ + sfx_1745.wav \ + sfx_1746.wav \ + sfx_1747.wav \ + sfx_1748.wav \ + sfx_1749.wav \ + sfx_174.wav \ + sfx_1750.wav \ + sfx_1751.wav \ + sfx_1752.wav \ + sfx_1753.wav \ + sfx_1754.wav \ + sfx_1755.wav \ + sfx_1756.wav \ + sfx_1757.wav \ + sfx_1758.wav \ + sfx_1759.wav \ + sfx_175.wav \ + sfx_1760.wav \ + sfx_1761.wav \ + sfx_1762.wav \ + sfx_1763.wav \ + sfx_1764.wav \ + sfx_1765.wav \ + sfx_1766.wav \ + sfx_1767.wav \ + sfx_1768.wav \ + sfx_1769.wav \ + sfx_176.wav \ + sfx_1770.wav \ + sfx_1771.wav \ + sfx_1772.wav \ + sfx_1773.wav \ + sfx_1774.wav \ + sfx_1775.wav \ + sfx_1776.wav \ + sfx_1777.wav \ + sfx_1778.wav \ + sfx_1779.wav \ + sfx_177.wav \ + sfx_1780.wav \ + sfx_1781.wav \ + sfx_1782.wav \ + sfx_1783.wav \ + sfx_1784.wav \ + sfx_1785.wav \ + sfx_1786.wav \ + sfx_1787.wav \ + sfx_1788.wav \ + sfx_1789.wav \ + sfx_178.wav \ + sfx_1790.wav \ + sfx_1791.wav \ + sfx_1792.wav \ + sfx_1793.wav \ + sfx_1794.wav \ + sfx_1795.wav \ + sfx_1796.wav \ + sfx_1797.wav \ + sfx_1798.wav \ + sfx_1799.wav \ + sfx_179.wav \ + sfx_17.wav \ + sfx_1800.wav \ + sfx_1801.wav \ + sfx_1802.wav \ + sfx_1803.wav \ + sfx_1804.wav \ + sfx_1805.wav \ + sfx_1806.wav \ + sfx_1807.wav \ + sfx_1808.wav \ + sfx_1809.wav \ + sfx_180.wav \ + sfx_1810.wav \ + sfx_1811.wav \ + sfx_1812.wav \ + sfx_1813.wav \ + sfx_1814.wav \ + sfx_1815.wav \ + sfx_1816.wav \ + sfx_1817.wav \ + sfx_1818.wav \ + sfx_1819.wav \ + sfx_181.wav \ + sfx_1820.wav \ + sfx_1821.wav \ + sfx_1822.wav \ + sfx_1823.wav \ + sfx_1824.wav \ + sfx_1825.wav \ + sfx_1826.wav \ + sfx_1827.wav \ + sfx_1828.wav \ + sfx_1829.wav \ + sfx_182.wav \ + sfx_1830.wav \ + sfx_1831.wav \ + sfx_1832.wav \ + sfx_1833.wav \ + sfx_1834.wav \ + sfx_1835.wav \ + sfx_1836.wav \ + sfx_1837.wav \ + sfx_1838.wav \ + sfx_1839.wav \ + sfx_183.wav \ + sfx_1840.wav \ + sfx_1841.wav \ + sfx_1842.wav \ + sfx_1843.wav \ + sfx_1844.wav \ + sfx_1845.wav \ + sfx_1846.wav \ + sfx_1847.wav \ + sfx_1848.wav \ + sfx_1849.wav \ + sfx_184.wav \ + sfx_1850.wav \ + sfx_1851.wav \ + sfx_1852.wav \ + sfx_1853.wav \ + sfx_1854.wav \ + sfx_1855.wav \ + sfx_1856.wav \ + sfx_1857.wav \ + sfx_1858.wav \ + sfx_1859.wav \ + sfx_185.wav \ + sfx_1860.wav \ + sfx_1861.wav \ + sfx_1862.wav \ + sfx_1863.wav \ + sfx_1864.wav \ + sfx_1865.wav \ + sfx_1866.wav \ + sfx_1867.wav \ + sfx_1868.wav \ + sfx_1869.wav \ + sfx_186.wav \ + sfx_1870.wav \ + sfx_1871.wav \ + sfx_1872.wav \ + sfx_1873.wav \ + sfx_1874.wav \ + sfx_1875.wav \ + sfx_1876.wav \ + sfx_1877.wav \ + sfx_1878.wav \ + sfx_1879.wav \ + sfx_187.wav \ + sfx_1880.wav \ + sfx_1881.wav \ + sfx_1882.wav \ + sfx_1883.wav \ + sfx_1884.wav \ + sfx_1885.wav \ + sfx_1886.wav \ + sfx_1887.wav \ + sfx_1888.wav \ + sfx_1889.wav \ + sfx_188.wav \ + sfx_1890.wav \ + sfx_1891.wav \ + sfx_1892.wav \ + sfx_1893.wav \ + sfx_1894.wav \ + sfx_1895.wav \ + sfx_1896.wav \ + sfx_1897.wav \ + sfx_1898.wav \ + sfx_1899.wav \ + sfx_189.wav \ + sfx_18.wav \ + sfx_1900.wav \ + sfx_1901.wav \ + sfx_1902.wav \ + sfx_1903.wav \ + sfx_1904.wav \ + sfx_1905.wav \ + sfx_1906.wav \ + sfx_1907.wav \ + sfx_1908.wav \ + sfx_1909.wav \ + sfx_190.wav \ + sfx_1910.wav \ + sfx_1911.wav \ + sfx_1912.wav \ + sfx_1913.wav \ + sfx_1914.wav \ + sfx_1915.wav \ + sfx_1916.wav \ + sfx_1917.wav \ + sfx_1918.wav \ + sfx_1919.wav \ + sfx_191.wav \ + sfx_1920.wav \ + sfx_1921.wav \ + sfx_1922.wav \ + sfx_1923.wav \ + sfx_1924.wav \ + sfx_1925.wav \ + sfx_1926.wav \ + sfx_1927.wav \ + sfx_1928.wav \ + sfx_1929.wav \ + sfx_192.wav \ + sfx_1930.wav \ + sfx_1931.wav \ + sfx_1932.wav \ + sfx_1933.wav \ + sfx_1934.wav \ + sfx_1935.wav \ + sfx_1936.wav \ + sfx_1937.wav \ + sfx_1938.wav \ + sfx_1939.wav \ + sfx_193.wav \ + sfx_1940.wav \ + sfx_1941.wav \ + sfx_1942.wav \ + sfx_1943.wav \ + sfx_1944.wav \ + sfx_1945.wav \ + sfx_1946.wav \ + sfx_1947.wav \ + sfx_1948.wav \ + sfx_1949.wav \ + sfx_194.wav \ + sfx_1950.wav \ + sfx_1951.wav \ + sfx_1952.wav \ + sfx_1953.wav \ + sfx_1954.wav \ + sfx_1955.wav \ + sfx_1956.wav \ + sfx_1957.wav \ + sfx_1958.wav \ + sfx_1959.wav \ + sfx_195.wav \ + sfx_1960.wav \ + sfx_1961.wav \ + sfx_1962.wav \ + sfx_1963.wav \ + sfx_1964.wav \ + sfx_1965.wav \ + sfx_1966.wav \ + sfx_1967.wav \ + sfx_1968.wav \ + sfx_1969.wav \ + sfx_196.wav \ + sfx_1970.wav \ + sfx_1971.wav \ + sfx_1972.wav \ + sfx_1973.wav \ + sfx_1974.wav \ + sfx_1975.wav \ + sfx_1976.wav \ + sfx_1977.wav \ + sfx_1978.wav \ + sfx_1979.wav \ + sfx_197.wav \ + sfx_1980.wav \ + sfx_1981.wav \ + sfx_1982.wav \ + sfx_1983.wav \ + sfx_1984.wav \ + sfx_1985.wav \ + sfx_1986.wav \ + sfx_1987.wav \ + sfx_1988.wav \ + sfx_1989.wav \ + sfx_198.wav \ + sfx_1990.wav \ + sfx_1991.wav \ + sfx_1992.wav \ + sfx_1993.wav \ + sfx_1994.wav \ + sfx_1995.wav \ + sfx_1996.wav \ + sfx_1997.wav \ + sfx_1998.wav \ + sfx_1999.wav \ + sfx_199.wav \ + sfx_19.wav \ + sfx_1.wav \ + sfx_2000.wav \ + sfx_2001.wav \ + sfx_2002.wav \ + sfx_2003.wav \ + sfx_2004.wav \ + sfx_2005.wav \ + sfx_2006.wav \ + sfx_2007.wav \ + sfx_2008.wav \ + sfx_2009.wav \ + sfx_200.wav \ + sfx_2010.wav \ + sfx_2011.wav \ + sfx_2012.wav \ + sfx_2013.wav \ + sfx_2014.wav \ + sfx_2015.wav \ + sfx_2016.wav \ + sfx_2017.wav \ + sfx_2018.wav \ + sfx_2019.wav \ + sfx_201.wav \ + sfx_2020.wav \ + sfx_2021.wav \ + sfx_2022.wav \ + sfx_2023.wav \ + sfx_2024.wav \ + sfx_2025.wav \ + sfx_2026.wav \ + sfx_2027.wav \ + sfx_2028.wav \ + sfx_2029.wav \ + sfx_202.wav \ + sfx_2030.wav \ + sfx_2031.wav \ + sfx_2032.wav \ + sfx_2033.wav \ + sfx_2034.wav \ + sfx_2035.wav \ + sfx_2036.wav \ + sfx_2037.wav \ + sfx_2038.wav \ + sfx_2039.wav \ + sfx_203.wav \ + sfx_2040.wav \ + sfx_2041.wav \ + sfx_2042.wav \ + sfx_2043.wav \ + sfx_2044.wav \ + sfx_2045.wav \ + sfx_2046.wav \ + sfx_2047.wav \ + sfx_2048.wav \ + sfx_2049.wav \ + sfx_204.wav \ + sfx_2050.wav \ + sfx_2051.wav \ + sfx_2052.wav \ + sfx_2053.wav \ + sfx_2054.wav \ + sfx_2055.wav \ + sfx_2056.wav \ + sfx_2057.wav \ + sfx_2058.wav \ + sfx_2059.wav \ + sfx_205.wav \ + sfx_2060.wav \ + sfx_2061.wav \ + sfx_2062.wav \ + sfx_2063.wav \ + sfx_2064.wav \ + sfx_2065.wav \ + sfx_2066.wav \ + sfx_2067.wav \ + sfx_2068.wav \ + sfx_2069.wav \ + sfx_206.wav \ + sfx_2070.wav \ + sfx_2071.wav \ + sfx_2072.wav \ + sfx_2073.wav \ + sfx_2074.wav \ + sfx_2075.wav \ + sfx_2076.wav \ + sfx_2077.wav \ + sfx_2078.wav \ + sfx_2079.wav \ + sfx_207.wav \ + sfx_2080.wav \ + sfx_2081.wav \ + sfx_2082.wav \ + sfx_2083.wav \ + sfx_2084.wav \ + sfx_2085.wav \ + sfx_2086.wav \ + sfx_2087.wav \ + sfx_2088.wav \ + sfx_2089.wav \ + sfx_208.wav \ + sfx_2090.wav \ + sfx_2091.wav \ + sfx_2092.wav \ + sfx_2093.wav \ + sfx_2094.wav \ + sfx_2095.wav \ + sfx_2096.wav \ + sfx_2097.wav \ + sfx_2098.wav \ + sfx_2099.wav \ + sfx_209.wav \ + sfx_20.wav \ + sfx_2100.wav \ + sfx_2101.wav \ + sfx_2102.wav \ + sfx_2103.wav \ + sfx_2104.wav \ + sfx_2105.wav \ + sfx_2106.wav \ + sfx_2107.wav \ + sfx_2108.wav \ + sfx_2109.wav \ + sfx_210.wav \ + sfx_2110.wav \ + sfx_2111.wav \ + sfx_2112.wav \ + sfx_2113.wav \ + sfx_2114.wav \ + sfx_2115.wav \ + sfx_2116.wav \ + sfx_2117.wav \ + sfx_2118.wav \ + sfx_2119.wav \ + sfx_211.wav \ + sfx_2120.wav \ + sfx_2121.wav \ + sfx_2122.wav \ + sfx_2123.wav \ + sfx_2124.wav \ + sfx_2125.wav \ + sfx_2126.wav \ + sfx_2127.wav \ + sfx_2128.wav \ + sfx_2129.wav \ + sfx_212.wav \ + sfx_2130.wav \ + sfx_2131.wav \ + sfx_2132.wav \ + sfx_2133.wav \ + sfx_2134.wav \ + sfx_2135.wav \ + sfx_2136.wav \ + sfx_2137.wav \ + sfx_2138.wav \ + sfx_2139.wav \ + sfx_213.wav \ + sfx_2140.wav \ + sfx_2141.wav \ + sfx_2142.wav \ + sfx_2143.wav \ + sfx_2144.wav \ + sfx_2145.wav \ + sfx_2146.wav \ + sfx_2147.wav \ + sfx_2148.wav \ + sfx_2149.wav \ + sfx_214.wav \ + sfx_2150.wav \ + sfx_2151.wav \ + sfx_2152.wav \ + sfx_2153.wav \ + sfx_2154.wav \ + sfx_2155.wav \ + sfx_2156.wav \ + sfx_2157.wav \ + sfx_2158.wav \ + sfx_2159.wav \ + sfx_215.wav \ + sfx_2160.wav \ + sfx_2161.wav \ + sfx_2162.wav \ + sfx_2163.wav \ + sfx_2164.wav \ + sfx_2165.wav \ + sfx_2166.wav \ + sfx_2167.wav \ + sfx_2168.wav \ + sfx_2169.wav \ + sfx_216.wav \ + sfx_2170.wav \ + sfx_2171.wav \ + sfx_2172.wav \ + sfx_2173.wav \ + sfx_2174.wav \ + sfx_2175.wav \ + sfx_2176.wav \ + sfx_2177.wav \ + sfx_2178.wav \ + sfx_2179.wav \ + sfx_217.wav \ + sfx_2180.wav \ + sfx_2181.wav \ + sfx_2182.wav \ + sfx_2183.wav \ + sfx_2184.wav \ + sfx_2185.wav \ + sfx_2186.wav \ + sfx_2187.wav \ + sfx_2188.wav \ + sfx_2189.wav \ + sfx_218.wav \ + sfx_2190.wav \ + sfx_2191.wav \ + sfx_2192.wav \ + sfx_2193.wav \ + sfx_2194.wav \ + sfx_2195.wav \ + sfx_2196.wav \ + sfx_2197.wav \ + sfx_2198.wav \ + sfx_2199.wav \ + sfx_219.wav \ + sfx_21.wav \ + sfx_2200.wav \ + sfx_2201.wav \ + sfx_2202.wav \ + sfx_2203.wav \ + sfx_2204.wav \ + sfx_2205.wav \ + sfx_2206.wav \ + sfx_2207.wav \ + sfx_2208.wav \ + sfx_2209.wav \ + sfx_220.wav \ + sfx_2210.wav \ + sfx_2211.wav \ + sfx_2212.wav \ + sfx_2213.wav \ + sfx_2214.wav \ + sfx_2215.wav \ + sfx_2216.wav \ + sfx_2217.wav \ + sfx_2218.wav \ + sfx_2219.wav \ + sfx_221.wav \ + sfx_2220.wav \ + sfx_2221.wav \ + sfx_2222.wav \ + sfx_2223.wav \ + sfx_2224.wav \ + sfx_2225.wav \ + sfx_2226.wav \ + sfx_2227.wav \ + sfx_2228.wav \ + sfx_2229.wav \ + sfx_222.wav \ + sfx_2230.wav \ + sfx_2231.wav \ + sfx_2232.wav \ + sfx_2233.wav \ + sfx_2234.wav \ + sfx_2235.wav \ + sfx_2236.wav \ + sfx_2237.wav \ + sfx_2238.wav \ + sfx_2239.wav \ + sfx_223.wav \ + sfx_2240.wav \ + sfx_2241.wav \ + sfx_2242.wav \ + sfx_2243.wav \ + sfx_2244.wav \ + sfx_2245.wav \ + sfx_2246.wav \ + sfx_2247.wav \ + sfx_2248.wav \ + sfx_2249.wav \ + sfx_224.wav \ + sfx_2250.wav \ + sfx_2251.wav \ + sfx_2252.wav \ + sfx_2253.wav \ + sfx_2254.wav \ + sfx_2255.wav \ + sfx_2256.wav \ + sfx_2257.wav \ + sfx_2258.wav \ + sfx_2259.wav \ + sfx_225.wav \ + sfx_2260.wav \ + sfx_2261.wav \ + sfx_2262.wav \ + sfx_2263.wav \ + sfx_2264.wav \ + sfx_2265.wav \ + sfx_2266.wav \ + sfx_2267.wav \ + sfx_2268.wav \ + sfx_2269.wav \ + sfx_226.wav \ + sfx_2270.wav \ + sfx_2271.wav \ + sfx_2272.wav \ + sfx_2273.wav \ + sfx_2274.wav \ + sfx_2275.wav \ + sfx_2276.wav \ + sfx_2277.wav \ + sfx_2278.wav \ + sfx_2279.wav \ + sfx_227.wav \ + sfx_2280.wav \ + sfx_2281.wav \ + sfx_2282.wav \ + sfx_2283.wav \ + sfx_2284.wav \ + sfx_2285.wav \ + sfx_2286.wav \ + sfx_2287.wav \ + sfx_2288.wav \ + sfx_2289.wav \ + sfx_228.wav \ + sfx_2290.wav \ + sfx_2291.wav \ + sfx_2292.wav \ + sfx_2293.wav \ + sfx_2294.wav \ + sfx_2295.wav \ + sfx_2296.wav \ + sfx_2297.wav \ + sfx_2298.wav \ + sfx_2299.wav \ + sfx_229.wav \ + sfx_22.wav \ + sfx_2300.wav \ + sfx_2301.wav \ + sfx_2302.wav \ + sfx_2303.wav \ + sfx_2304.wav \ + sfx_2305.wav \ + sfx_2306.wav \ + sfx_2307.wav \ + sfx_2308.wav \ + sfx_2309.wav \ + sfx_230.wav \ + sfx_2310.wav \ + sfx_2311.wav \ + sfx_2312.wav \ + sfx_2313.wav \ + sfx_2314.wav \ + sfx_2315.wav \ + sfx_2316.wav \ + sfx_2317.wav \ + sfx_2318.wav \ + sfx_2319.wav \ + sfx_231.wav \ + sfx_2320.wav \ + sfx_2321.wav \ + sfx_2322.wav \ + sfx_2323.wav \ + sfx_2324.wav \ + sfx_2325.wav \ + sfx_2326.wav \ + sfx_2327.wav \ + sfx_2328.wav \ + sfx_2329.wav \ + sfx_232.wav \ + sfx_2330.wav \ + sfx_2331.wav \ + sfx_2332.wav \ + sfx_2333.wav \ + sfx_2334.wav \ + sfx_2335.wav \ + sfx_2336.wav \ + sfx_2337.wav \ + sfx_2338.wav \ + sfx_2339.wav \ + sfx_233.wav \ + sfx_2340.wav \ + sfx_2341.wav \ + sfx_2342.wav \ + sfx_2343.wav \ + sfx_2344.wav \ + sfx_2345.wav \ + sfx_2346.wav \ + sfx_2347.wav \ + sfx_2348.wav \ + sfx_2349.wav \ + sfx_234.wav \ + sfx_2350.wav \ + sfx_2351.wav \ + sfx_2352.wav \ + sfx_2353.wav \ + sfx_2354.wav \ + sfx_2355.wav \ + sfx_2356.wav \ + sfx_2357.wav \ + sfx_2358.wav \ + sfx_2359.wav \ + sfx_235.wav \ + sfx_2360.wav \ + sfx_2361.wav \ + sfx_2362.wav \ + sfx_2363.wav \ + sfx_2364.wav \ + sfx_2365.wav \ + sfx_2366.wav \ + sfx_2367.wav \ + sfx_2368.wav \ + sfx_2369.wav \ + sfx_236.wav \ + sfx_2370.wav \ + sfx_2371.wav \ + sfx_2372.wav \ + sfx_2373.wav \ + sfx_2374.wav \ + sfx_2375.wav \ + sfx_2376.wav \ + sfx_2377.wav \ + sfx_2378.wav \ + sfx_2379.wav \ + sfx_237.wav \ + sfx_2380.wav \ + sfx_2381.wav \ + sfx_2382.wav \ + sfx_2383.wav \ + sfx_2384.wav \ + sfx_2385.wav \ + sfx_2386.wav \ + sfx_2387.wav \ + sfx_2388.wav \ + sfx_2389.wav \ + sfx_238.wav \ + sfx_2390.wav \ + sfx_2391.wav \ + sfx_2392.wav \ + sfx_2393.wav \ + sfx_2394.wav \ + sfx_2395.wav \ + sfx_2396.wav \ + sfx_2397.wav \ + sfx_2398.wav \ + sfx_2399.wav \ + sfx_239.wav \ + sfx_23.wav \ + sfx_2400.wav \ + sfx_2401.wav \ + sfx_2402.wav \ + sfx_2403.wav \ + sfx_2404.wav \ + sfx_2405.wav \ + sfx_2406.wav \ + sfx_2407.wav \ + sfx_2408.wav \ + sfx_2409.wav \ + sfx_240.wav \ + sfx_2410.wav \ + sfx_2411.wav \ + sfx_2412.wav \ + sfx_2413.wav \ + sfx_2414.wav \ + sfx_2415.wav \ + sfx_2416.wav \ + sfx_2417.wav \ + sfx_2418.wav \ + sfx_2419.wav \ + sfx_241.wav \ + sfx_2420.wav \ + sfx_2421.wav \ + sfx_2422.wav \ + sfx_2423.wav \ + sfx_2424.wav \ + sfx_2425.wav \ + sfx_2426.wav \ + sfx_2427.wav \ + sfx_2428.wav \ + sfx_2429.wav \ + sfx_242.wav \ + sfx_2430.wav \ + sfx_2431.wav \ + sfx_2432.wav \ + sfx_2433.wav \ + sfx_2434.wav \ + sfx_2435.wav \ + sfx_2436.wav \ + sfx_2437.wav \ + sfx_2438.wav \ + sfx_2439.wav \ + sfx_243.wav \ + sfx_2440.wav \ + sfx_2441.wav \ + sfx_2442.wav \ + sfx_2443.wav \ + sfx_2444.wav \ + sfx_2445.wav \ + sfx_2446.wav \ + sfx_2447.wav \ + sfx_2448.wav \ + sfx_2449.wav \ + sfx_244.wav \ + sfx_2450.wav \ + sfx_2451.wav \ + sfx_2452.wav \ + sfx_2453.wav \ + sfx_2454.wav \ + sfx_2455.wav \ + sfx_2456.wav \ + sfx_2457.wav \ + sfx_2458.wav \ + sfx_2459.wav \ + sfx_245.wav \ + sfx_2460.wav \ + sfx_2461.wav \ + sfx_2462.wav \ + sfx_2463.wav \ + sfx_2464.wav \ + sfx_2465.wav \ + sfx_2466.wav \ + sfx_2467.wav \ + sfx_2468.wav \ + sfx_2469.wav \ + sfx_246.wav \ + sfx_2470.wav \ + sfx_2471.wav \ + sfx_2472.wav \ + sfx_2473.wav \ + sfx_2474.wav \ + sfx_2475.wav \ + sfx_2476.wav \ + sfx_2477.wav \ + sfx_2478.wav \ + sfx_2479.wav \ + sfx_247.wav \ + sfx_2480.wav \ + sfx_2481.wav \ + sfx_2482.wav \ + sfx_2483.wav \ + sfx_2484.wav \ + sfx_2485.wav \ + sfx_2486.wav \ + sfx_2487.wav \ + sfx_2488.wav \ + sfx_2489.wav \ + sfx_248.wav \ + sfx_2490.wav \ + sfx_2491.wav \ + sfx_2492.wav \ + sfx_2493.wav \ + sfx_2494.wav \ + sfx_2495.wav \ + sfx_2496.wav \ + sfx_2497.wav \ + sfx_2498.wav \ + sfx_2499.wav \ + sfx_249.wav \ + sfx_24.wav \ + sfx_2500.wav \ + sfx_2501.wav \ + sfx_2502.wav \ + sfx_2503.wav \ + sfx_2504.wav \ + sfx_2505.wav \ + sfx_2506.wav \ + sfx_2507.wav \ + sfx_2508.wav \ + sfx_2509.wav \ + sfx_250.wav \ + sfx_2510.wav \ + sfx_2511.wav \ + sfx_2512.wav \ + sfx_2513.wav \ + sfx_2514.wav \ + sfx_2515.wav \ + sfx_2516.wav \ + sfx_2517.wav \ + sfx_2518.wav \ + sfx_2519.wav \ + sfx_251.wav \ + sfx_2520.wav \ + sfx_2521.wav \ + sfx_2522.wav \ + sfx_2523.wav \ + sfx_2524.wav \ + sfx_2525.wav \ + sfx_2526.wav \ + sfx_2527.wav \ + sfx_2528.wav \ + sfx_2529.wav \ + sfx_252.wav \ + sfx_2530.wav \ + sfx_2531.wav \ + sfx_2532.wav \ + sfx_2533.wav \ + sfx_2534.wav \ + sfx_2535.wav \ + sfx_2536.wav \ + sfx_2537.wav \ + sfx_2538.wav \ + sfx_2539.wav \ + sfx_253.wav \ + sfx_2540.wav \ + sfx_2541.wav \ + sfx_2542.wav \ + sfx_2543.wav \ + sfx_2544.wav \ + sfx_2545.wav \ + sfx_2546.wav \ + sfx_2547.wav \ + sfx_2548.wav \ + sfx_2549.wav \ + sfx_254.wav \ + sfx_2550.wav \ + sfx_2551.wav \ + sfx_2552.wav \ + sfx_2553.wav \ + sfx_2554.wav \ + sfx_2555.wav \ + sfx_2556.wav \ + sfx_2557.wav \ + sfx_2558.wav \ + sfx_2559.wav \ + sfx_255.wav \ + sfx_2560.wav \ + sfx_2561.wav \ + sfx_2562.wav \ + sfx_2563.wav \ + sfx_2564.wav \ + sfx_2565.wav \ + sfx_2566.wav \ + sfx_2567.wav \ + sfx_2568.wav \ + sfx_2569.wav \ + sfx_256.wav \ + sfx_2570.wav \ + sfx_2571.wav \ + sfx_2572.wav \ + sfx_2573.wav \ + sfx_2574.wav \ + sfx_2575.wav \ + sfx_2576.wav \ + sfx_2577.wav \ + sfx_2578.wav \ + sfx_2579.wav \ + sfx_257.wav \ + sfx_2580.wav \ + sfx_2581.wav \ + sfx_2582.wav \ + sfx_2583.wav \ + sfx_2584.wav \ + sfx_2585.wav \ + sfx_2586.wav \ + sfx_2587.wav \ + sfx_2588.wav \ + sfx_2589.wav \ + sfx_258.wav \ + sfx_2590.wav \ + sfx_2591.wav \ + sfx_2592.wav \ + sfx_2593.wav \ + sfx_2594.wav \ + sfx_2595.wav \ + sfx_2596.wav \ + sfx_2597.wav \ + sfx_2598.wav \ + sfx_2599.wav \ + sfx_259.wav \ + sfx_25.wav \ + sfx_2600.wav \ + sfx_2601.wav \ + sfx_2602.wav \ + sfx_2603.wav \ + sfx_2604.wav \ + sfx_2605.wav \ + sfx_2606.wav \ + sfx_2607.wav \ + sfx_2608.wav \ + sfx_2609.wav \ + sfx_260.wav \ + sfx_2610.wav \ + sfx_2611.wav \ + sfx_2612.wav \ + sfx_2613.wav \ + sfx_2614.wav \ + sfx_2615.wav \ + sfx_2616.wav \ + sfx_2617.wav \ + sfx_2618.wav \ + sfx_2619.wav \ + sfx_261.wav \ + sfx_2620.wav \ + sfx_2621.wav \ + sfx_2622.wav \ + sfx_2623.wav \ + sfx_2624.wav \ + sfx_2625.wav \ + sfx_2626.wav \ + sfx_2627.wav \ + sfx_2628.wav \ + sfx_2629.wav \ + sfx_262.wav \ + sfx_2630.wav \ + sfx_2631.wav \ + sfx_2632.wav \ + sfx_2633.wav \ + sfx_2634.wav \ + sfx_2635.wav \ + sfx_2636.wav \ + sfx_2637.wav \ + sfx_2638.wav \ + sfx_2639.wav \ + sfx_263.wav \ + sfx_2640.wav \ + sfx_2641.wav \ + sfx_2642.wav \ + sfx_2643.wav \ + sfx_2644.wav \ + sfx_2645.wav \ + sfx_2646.wav \ + sfx_2647.wav \ + sfx_2648.wav \ + sfx_2649.wav \ + sfx_264.wav \ + sfx_2650.wav \ + sfx_2651.wav \ + sfx_2652.wav \ + sfx_2653.wav \ + sfx_2654.wav \ + sfx_2655.wav \ + sfx_2656.wav \ + sfx_2657.wav \ + sfx_2658.wav \ + sfx_2659.wav \ + sfx_265.wav \ + sfx_2660.wav \ + sfx_2661.wav \ + sfx_2662.wav \ + sfx_2663.wav \ + sfx_2664.wav \ + sfx_2665.wav \ + sfx_2666.wav \ + sfx_2667.wav \ + sfx_2668.wav \ + sfx_2669.wav \ + sfx_266.wav \ + sfx_2670.wav \ + sfx_2671.wav \ + sfx_2672.wav \ + sfx_2673.wav \ + sfx_2674.wav \ + sfx_2675.wav \ + sfx_2676.wav \ + sfx_2677.wav \ + sfx_2678.wav \ + sfx_2679.wav \ + sfx_267.wav \ + sfx_2680.wav \ + sfx_2681.wav \ + sfx_2682.wav \ + sfx_2683.wav \ + sfx_2684.wav \ + sfx_2685.wav \ + sfx_2686.wav \ + sfx_2687.wav \ + sfx_2688.wav \ + sfx_2689.wav \ + sfx_268.wav \ + sfx_2690.wav \ + sfx_2691.wav \ + sfx_2692.wav \ + sfx_2693.wav \ + sfx_2694.wav \ + sfx_2695.wav \ + sfx_2696.wav \ + sfx_2697.wav \ + sfx_2698.wav \ + sfx_2699.wav \ + sfx_269.wav \ + sfx_26.wav \ + sfx_2700.wav \ + sfx_2701.wav \ + sfx_2702.wav \ + sfx_2703.wav \ + sfx_2704.wav \ + sfx_2705.wav \ + sfx_2706.wav \ + sfx_2707.wav \ + sfx_2708.wav \ + sfx_2709.wav \ + sfx_270.wav \ + sfx_2710.wav \ + sfx_2711.wav \ + sfx_2712.wav \ + sfx_2713.wav \ + sfx_2714.wav \ + sfx_2715.wav \ + sfx_2716.wav \ + sfx_2717.wav \ + sfx_2718.wav \ + sfx_2719.wav \ + sfx_271.wav \ + sfx_2720.wav \ + sfx_2721.wav \ + sfx_2722.wav \ + sfx_2723.wav \ + sfx_2724.wav \ + sfx_2725.wav \ + sfx_2726.wav \ + sfx_2727.wav \ + sfx_2728.wav \ + sfx_2729.wav \ + sfx_272.wav \ + sfx_2730.wav \ + sfx_2731.wav \ + sfx_2732.wav \ + sfx_2733.wav \ + sfx_2734.wav \ + sfx_2735.wav \ + sfx_2736.wav \ + sfx_2737.wav \ + sfx_2738.wav \ + sfx_2739.wav \ + sfx_273.wav \ + sfx_2740.wav \ + sfx_2741.wav \ + sfx_2742.wav \ + sfx_2743.wav \ + sfx_2744.wav \ + sfx_2745.wav \ + sfx_2746.wav \ + sfx_2747.wav \ + sfx_2748.wav \ + sfx_2749.wav \ + sfx_274.wav \ + sfx_2750.wav \ + sfx_2751.wav \ + sfx_2752.wav \ + sfx_2753.wav \ + sfx_2754.wav \ + sfx_2755.wav \ + sfx_2756.wav \ + sfx_2757.wav \ + sfx_2758.wav \ + sfx_2759.wav \ + sfx_275.wav \ + sfx_2760.wav \ + sfx_2761.wav \ + sfx_2762.wav \ + sfx_2763.wav \ + sfx_2764.wav \ + sfx_2765.wav \ + sfx_2766.wav \ + sfx_2767.wav \ + sfx_2768.wav \ + sfx_2769.wav \ + sfx_276.wav \ + sfx_2770.wav \ + sfx_2771.wav \ + sfx_2772.wav \ + sfx_2773.wav \ + sfx_2774.wav \ + sfx_2775.wav \ + sfx_2776.wav \ + sfx_2777.wav \ + sfx_2778.wav \ + sfx_2779.wav \ + sfx_277.wav \ + sfx_2780.wav \ + sfx_2781.wav \ + sfx_2782.wav \ + sfx_2783.wav \ + sfx_2784.wav \ + sfx_2785.wav \ + sfx_2786.wav \ + sfx_2787.wav \ + sfx_2788.wav \ + sfx_2789.wav \ + sfx_278.wav \ + sfx_2790.wav \ + sfx_2791.wav \ + sfx_2792.wav \ + sfx_2793.wav \ + sfx_2794.wav \ + sfx_2795.wav \ + sfx_2796.wav \ + sfx_2797.wav \ + sfx_2798.wav \ + sfx_2799.wav \ + sfx_279.wav \ + sfx_27.wav \ + sfx_2800.wav \ + sfx_2801.wav \ + sfx_2802.wav \ + sfx_2803.wav \ + sfx_2804.wav \ + sfx_2805.wav \ + sfx_2806.wav \ + sfx_2807.wav \ + sfx_2808.wav \ + sfx_2809.wav \ + sfx_280.wav \ + sfx_2810.wav \ + sfx_2811.wav \ + sfx_2812.wav \ + sfx_2813.wav \ + sfx_2814.wav \ + sfx_2815.wav \ + sfx_2816.wav \ + sfx_2817.wav \ + sfx_2818.wav \ + sfx_2819.wav \ + sfx_281.wav \ + sfx_2820.wav \ + sfx_2821.wav \ + sfx_2822.wav \ + sfx_2823.wav \ + sfx_2824.wav \ + sfx_2825.wav \ + sfx_2826.wav \ + sfx_2827.wav \ + sfx_2828.wav \ + sfx_2829.wav \ + sfx_282.wav \ + sfx_2830.wav \ + sfx_2831.wav \ + sfx_2832.wav \ + sfx_2833.wav \ + sfx_2834.wav \ + sfx_2835.wav \ + sfx_2836.wav \ + sfx_2837.wav \ + sfx_2838.wav \ + sfx_2839.wav \ + sfx_283.wav \ + sfx_2840.wav \ + sfx_2841.wav \ + sfx_2842.wav \ + sfx_2843.wav \ + sfx_2844.wav \ + sfx_2845.wav \ + sfx_2846.wav \ + sfx_2847.wav \ + sfx_2848.wav \ + sfx_2849.wav \ + sfx_284.wav \ + sfx_2850.wav \ + sfx_2851.wav \ + sfx_2852.wav \ + sfx_2853.wav \ + sfx_2854.wav \ + sfx_2855.wav \ + sfx_2856.wav \ + sfx_2857.wav \ + sfx_2858.wav \ + sfx_2859.wav \ + sfx_285.wav \ + sfx_2860.wav \ + sfx_2861.wav \ + sfx_2862.wav \ + sfx_2863.wav \ + sfx_2864.wav \ + sfx_2865.wav \ + sfx_2866.wav \ + sfx_2867.wav \ + sfx_2868.wav \ + sfx_2869.wav \ + sfx_286.wav \ + sfx_2870.wav \ + sfx_2871.wav \ + sfx_2872.wav \ + sfx_2873.wav \ + sfx_2874.wav \ + sfx_2875.wav \ + sfx_2876.wav \ + sfx_2877.wav \ + sfx_2878.wav \ + sfx_2879.wav \ + sfx_287.wav \ + sfx_2880.wav \ + sfx_2881.wav \ + sfx_2882.wav \ + sfx_2883.wav \ + sfx_2884.wav \ + sfx_2885.wav \ + sfx_2886.wav \ + sfx_2887.wav \ + sfx_2888.wav \ + sfx_2889.wav \ + sfx_288.wav \ + sfx_2890.wav \ + sfx_2891.wav \ + sfx_2892.wav \ + sfx_2893.wav \ + sfx_2894.wav \ + sfx_2895.wav \ + sfx_2896.wav \ + sfx_2897.wav \ + sfx_2898.wav \ + sfx_2899.wav \ + sfx_289.wav \ + sfx_28.wav \ + sfx_2900.wav \ + sfx_2901.wav \ + sfx_2902.wav \ + sfx_2903.wav \ + sfx_2904.wav \ + sfx_2905.wav \ + sfx_2906.wav \ + sfx_2907.wav \ + sfx_2908.wav \ + sfx_2909.wav \ + sfx_290.wav \ + sfx_2910.wav \ + sfx_2911.wav \ + sfx_2912.wav \ + sfx_2913.wav \ + sfx_2914.wav \ + sfx_2915.wav \ + sfx_2916.wav \ + sfx_2917.wav \ + sfx_2918.wav \ + sfx_2919.wav \ + sfx_291.wav \ + sfx_2920.wav \ + sfx_2921.wav \ + sfx_2922.wav \ + sfx_2923.wav \ + sfx_2924.wav \ + sfx_2925.wav \ + sfx_2926.wav \ + sfx_2927.wav \ + sfx_2928.wav \ + sfx_2929.wav \ + sfx_292.wav \ + sfx_2930.wav \ + sfx_2931.wav \ + sfx_2932.wav \ + sfx_2933.wav \ + sfx_2934.wav \ + sfx_2935.wav \ + sfx_2936.wav \ + sfx_2937.wav \ + sfx_2938.wav \ + sfx_2939.wav \ + sfx_293.wav \ + sfx_2940.wav \ + sfx_2941.wav \ + sfx_2942.wav \ + sfx_2943.wav \ + sfx_2944.wav \ + sfx_2945.wav \ + sfx_2946.wav \ + sfx_2947.wav \ + sfx_2948.wav \ + sfx_2949.wav \ + sfx_294.wav \ + sfx_2950.wav \ + sfx_2951.wav \ + sfx_2952.wav \ + sfx_2953.wav \ + sfx_2954.wav \ + sfx_2955.wav \ + sfx_2956.wav \ + sfx_2957.wav \ + sfx_2958.wav \ + sfx_2959.wav \ + sfx_295.wav \ + sfx_2960.wav \ + sfx_2961.wav \ + sfx_2962.wav \ + sfx_2963.wav \ + sfx_2964.wav \ + sfx_2965.wav \ + sfx_2966.wav \ + sfx_2967.wav \ + sfx_2968.wav \ + sfx_2969.wav \ + sfx_296.wav \ + sfx_2970.wav \ + sfx_2971.wav \ + sfx_2972.wav \ + sfx_2973.wav \ + sfx_2974.wav \ + sfx_2975.wav \ + sfx_2976.wav \ + sfx_2977.wav \ + sfx_2978.wav \ + sfx_2979.wav \ + sfx_297.wav \ + sfx_2980.wav \ + sfx_2981.wav \ + sfx_2982.wav \ + sfx_2983.wav \ + sfx_2984.wav \ + sfx_2985.wav \ + sfx_2986.wav \ + sfx_2987.wav \ + sfx_2988.wav \ + sfx_2989.wav \ + sfx_298.wav \ + sfx_2990.wav \ + sfx_2991.wav \ + sfx_2992.wav \ + sfx_2993.wav \ + sfx_2994.wav \ + sfx_2995.wav \ + sfx_2996.wav \ + sfx_2997.wav \ + sfx_2998.wav \ + sfx_2999.wav \ + sfx_299.wav \ + sfx_29.wav \ + sfx_2.wav \ + sfx_3000.wav \ + sfx_3001.wav \ + sfx_3002.wav \ + sfx_3003.wav \ + sfx_3004.wav \ + sfx_3005.wav \ + sfx_3006.wav \ + sfx_3007.wav \ + sfx_3008.wav \ + sfx_3009.wav \ + sfx_300.wav \ + sfx_3010.wav \ + sfx_3011.wav \ + sfx_3012.wav \ + sfx_3013.wav \ + sfx_3014.wav \ + sfx_3015.wav \ + sfx_3016.wav \ + sfx_3017.wav \ + sfx_3018.wav \ + sfx_3019.wav \ + sfx_301.wav \ + sfx_3020.wav \ + sfx_3021.wav \ + sfx_3022.wav \ + sfx_3023.wav \ + sfx_3024.wav \ + sfx_3025.wav \ + sfx_3026.wav \ + sfx_3027.wav \ + sfx_3028.wav \ + sfx_3029.wav \ + sfx_302.wav \ + sfx_3030.wav \ + sfx_3031.wav \ + sfx_3032.wav \ + sfx_3033.wav \ + sfx_3034.wav \ + sfx_3035.wav \ + sfx_3036.wav \ + sfx_3037.wav \ + sfx_3038.wav \ + sfx_3039.wav \ + sfx_303.wav \ + sfx_3040.wav \ + sfx_3041.wav \ + sfx_3042.wav \ + sfx_3043.wav \ + sfx_3044.wav \ + sfx_3045.wav \ + sfx_3046.wav \ + sfx_3047.wav \ + sfx_3048.wav \ + sfx_3049.wav \ + sfx_304.wav \ + sfx_3050.wav \ + sfx_3051.wav \ + sfx_3052.wav \ + sfx_3053.wav \ + sfx_3054.wav \ + sfx_3055.wav \ + sfx_3056.wav \ + sfx_3057.wav \ + sfx_3058.wav \ + sfx_3059.wav \ + sfx_305.wav \ + sfx_3060.wav \ + sfx_3061.wav \ + sfx_3062.wav \ + sfx_3063.wav \ + sfx_3064.wav \ + sfx_3065.wav \ + sfx_3066.wav \ + sfx_3067.wav \ + sfx_3068.wav \ + sfx_3069.wav \ + sfx_306.wav \ + sfx_3070.wav \ + sfx_3071.wav \ + sfx_3072.wav \ + sfx_3073.wav \ + sfx_3074.wav \ + sfx_3075.wav \ + sfx_3076.wav \ + sfx_3077.wav \ + sfx_3078.wav \ + sfx_3079.wav \ + sfx_307.wav \ + sfx_3080.wav \ + sfx_3081.wav \ + sfx_3082.wav \ + sfx_3083.wav \ + sfx_3084.wav \ + sfx_3085.wav \ + sfx_3086.wav \ + sfx_3087.wav \ + sfx_3088.wav \ + sfx_3089.wav \ + sfx_308.wav \ + sfx_3090.wav \ + sfx_3091.wav \ + sfx_3092.wav \ + sfx_3093.wav \ + sfx_3094.wav \ + sfx_3095.wav \ + sfx_3096.wav \ + sfx_3097.wav \ + sfx_3098.wav \ + sfx_3099.wav \ + sfx_309.wav \ + sfx_30.wav \ + sfx_3100.wav \ + sfx_3101.wav \ + sfx_3102.wav \ + sfx_3103.wav \ + sfx_3104.wav \ + sfx_3105.wav \ + sfx_3106.wav \ + sfx_3107.wav \ + sfx_3108.wav \ + sfx_3109.wav \ + sfx_310.wav \ + sfx_3110.wav \ + sfx_3111.wav \ + sfx_3112.wav \ + sfx_3113.wav \ + sfx_3114.wav \ + sfx_3115.wav \ + sfx_3116.wav \ + sfx_3117.wav \ + sfx_3118.wav \ + sfx_3119.wav \ + sfx_311.wav \ + sfx_3120.wav \ + sfx_3121.wav \ + sfx_3122.wav \ + sfx_3123.wav \ + sfx_3124.wav \ + sfx_3125.wav \ + sfx_3126.wav \ + sfx_3127.wav \ + sfx_3128.wav \ + sfx_3129.wav \ + sfx_312.wav \ + sfx_3130.wav \ + sfx_3131.wav \ + sfx_3132.wav \ + sfx_3133.wav \ + sfx_3134.wav \ + sfx_3135.wav \ + sfx_3136.wav \ + sfx_3137.wav \ + sfx_3138.wav \ + sfx_3139.wav \ + sfx_313.wav \ + sfx_3140.wav \ + sfx_3141.wav \ + sfx_3142.wav \ + sfx_3143.wav \ + sfx_3144.wav \ + sfx_3145.wav \ + sfx_3146.wav \ + sfx_3147.wav \ + sfx_3148.wav \ + sfx_3149.wav \ + sfx_314.wav \ + sfx_3150.wav \ + sfx_3151.wav \ + sfx_3152.wav \ + sfx_3153.wav \ + sfx_3154.wav \ + sfx_3155.wav \ + sfx_3156.wav \ + sfx_3157.wav \ + sfx_3158.wav \ + sfx_3159.wav \ + sfx_315.wav \ + sfx_3160.wav \ + sfx_3161.wav \ + sfx_3162.wav \ + sfx_3163.wav \ + sfx_3164.wav \ + sfx_3165.wav \ + sfx_3166.wav \ + sfx_3167.wav \ + sfx_3168.wav \ + sfx_3169.wav \ + sfx_316.wav \ + sfx_3170.wav \ + sfx_3171.wav \ + sfx_3172.wav \ + sfx_3173.wav \ + sfx_3174.wav \ + sfx_3175.wav \ + sfx_3176.wav \ + sfx_3177.wav \ + sfx_3178.wav \ + sfx_3179.wav \ + sfx_317.wav \ + sfx_3180.wav \ + sfx_3181.wav \ + sfx_3182.wav \ + sfx_3183.wav \ + sfx_3184.wav \ + sfx_3185.wav \ + sfx_3186.wav \ + sfx_3187.wav \ + sfx_3188.wav \ + sfx_3189.wav \ + sfx_318.wav \ + sfx_3190.wav \ + sfx_3191.wav \ + sfx_3192.wav \ + sfx_3193.wav \ + sfx_3194.wav \ + sfx_3195.wav \ + sfx_3196.wav \ + sfx_3197.wav \ + sfx_3198.wav \ + sfx_3199.wav \ + sfx_319.wav \ + sfx_31.wav \ + sfx_3200.wav \ + sfx_3201.wav \ + sfx_3202.wav \ + sfx_3203.wav \ + sfx_3204.wav \ + sfx_3205.wav \ + sfx_3206.wav \ + sfx_3207.wav \ + sfx_3208.wav \ + sfx_3209.wav \ + sfx_320.wav \ + sfx_3210.wav \ + sfx_3211.wav \ + sfx_3212.wav \ + sfx_3213.wav \ + sfx_3214.wav \ + sfx_3215.wav \ + sfx_3216.wav \ + sfx_3217.wav \ + sfx_3218.wav \ + sfx_3219.wav \ + sfx_321.wav \ + sfx_3220.wav \ + sfx_3221.wav \ + sfx_3222.wav \ + sfx_3223.wav \ + sfx_3224.wav \ + sfx_3225.wav \ + sfx_3226.wav \ + sfx_3227.wav \ + sfx_3228.wav \ + sfx_3229.wav \ + sfx_322.wav \ + sfx_3230.wav \ + sfx_3231.wav \ + sfx_3232.wav \ + sfx_3233.wav \ + sfx_3234.wav \ + sfx_3235.wav \ + sfx_3236.wav \ + sfx_3237.wav \ + sfx_3238.wav \ + sfx_3239.wav \ + sfx_323.wav \ + sfx_3240.wav \ + sfx_3241.wav \ + sfx_3242.wav \ + sfx_3243.wav \ + sfx_3244.wav \ + sfx_3245.wav \ + sfx_3246.wav \ + sfx_3247.wav \ + sfx_3248.wav \ + sfx_3249.wav \ + sfx_324.wav \ + sfx_3250.wav \ + sfx_3251.wav \ + sfx_3252.wav \ + sfx_3253.wav \ + sfx_3254.wav \ + sfx_3255.wav \ + sfx_3256.wav \ + sfx_3257.wav \ + sfx_3258.wav \ + sfx_3259.wav \ + sfx_325.wav \ + sfx_3260.wav \ + sfx_3261.wav \ + sfx_3262.wav \ + sfx_3263.wav \ + sfx_3264.wav \ + sfx_3265.wav \ + sfx_3266.wav \ + sfx_3267.wav \ + sfx_3268.wav \ + sfx_3269.wav \ + sfx_326.wav \ + sfx_3270.wav \ + sfx_3271.wav \ + sfx_3272.wav \ + sfx_3273.wav \ + sfx_3274.wav \ + sfx_3275.wav \ + sfx_3276.wav \ + sfx_3277.wav \ + sfx_3278.wav \ + sfx_3279.wav \ + sfx_327.wav \ + sfx_3280.wav \ + sfx_3281.wav \ + sfx_3282.wav \ + sfx_3283.wav \ + sfx_3284.wav \ + sfx_3285.wav \ + sfx_3286.wav \ + sfx_3287.wav \ + sfx_3288.wav \ + sfx_3289.wav \ + sfx_328.wav \ + sfx_3290.wav \ + sfx_3291.wav \ + sfx_3292.wav \ + sfx_3293.wav \ + sfx_3294.wav \ + sfx_3295.wav \ + sfx_3296.wav \ + sfx_3297.wav \ + sfx_3298.wav \ + sfx_3299.wav \ + sfx_329.wav \ + sfx_32.wav \ + sfx_3300.wav \ + sfx_3301.wav \ + sfx_3302.wav \ + sfx_3303.wav \ + sfx_3304.wav \ + sfx_3305.wav \ + sfx_3306.wav \ + sfx_3307.wav \ + sfx_3308.wav \ + sfx_3309.wav \ + sfx_330.wav \ + sfx_3310.wav \ + sfx_3311.wav \ + sfx_3312.wav \ + sfx_3313.wav \ + sfx_3314.wav \ + sfx_3315.wav \ + sfx_3316.wav \ + sfx_3317.wav \ + sfx_3318.wav \ + sfx_3319.wav \ + sfx_331.wav \ + sfx_3320.wav \ + sfx_3321.wav \ + sfx_3322.wav \ + sfx_3323.wav \ + sfx_3324.wav \ + sfx_3325.wav \ + sfx_3326.wav \ + sfx_3327.wav \ + sfx_3328.wav \ + sfx_3329.wav \ + sfx_332.wav \ + sfx_3330.wav \ + sfx_3331.wav \ + sfx_3332.wav \ + sfx_3333.wav \ + sfx_3334.wav \ + sfx_3335.wav \ + sfx_3336.wav \ + sfx_3337.wav \ + sfx_3338.wav \ + sfx_3339.wav \ + sfx_333.wav \ + sfx_3340.wav \ + sfx_3341.wav \ + sfx_3342.wav \ + sfx_3343.wav \ + sfx_3344.wav \ + sfx_3345.wav \ + sfx_3346.wav \ + sfx_3347.wav \ + sfx_3348.wav \ + sfx_3349.wav \ + sfx_334.wav \ + sfx_3350.wav \ + sfx_3351.wav \ + sfx_3352.wav \ + sfx_3353.wav \ + sfx_3354.wav \ + sfx_3355.wav \ + sfx_3356.wav \ + sfx_3357.wav \ + sfx_3358.wav \ + sfx_3359.wav \ + sfx_335.wav \ + sfx_3360.wav \ + sfx_3361.wav \ + sfx_3362.wav \ + sfx_3363.wav \ + sfx_3364.wav \ + sfx_3365.wav \ + sfx_3366.wav \ + sfx_3367.wav \ + sfx_3368.wav \ + sfx_3369.wav \ + sfx_336.wav \ + sfx_3370.wav \ + sfx_3371.wav \ + sfx_3372.wav \ + sfx_3373.wav \ + sfx_3374.wav \ + sfx_3375.wav \ + sfx_3376.wav \ + sfx_3377.wav \ + sfx_3378.wav \ + sfx_3379.wav \ + sfx_337.wav \ + sfx_3380.wav \ + sfx_3381.wav \ + sfx_3382.wav \ + sfx_3383.wav \ + sfx_3384.wav \ + sfx_3385.wav \ + sfx_3386.wav \ + sfx_3387.wav \ + sfx_3388.wav \ + sfx_3389.wav \ + sfx_338.wav \ + sfx_3390.wav \ + sfx_3391.wav \ + sfx_3392.wav \ + sfx_3393.wav \ + sfx_3394.wav \ + sfx_3395.wav \ + sfx_3396.wav \ + sfx_3397.wav \ + sfx_3398.wav \ + sfx_3399.wav \ + sfx_339.wav \ + sfx_33.wav \ + sfx_3400.wav \ + sfx_3401.wav \ + sfx_3402.wav \ + sfx_3403.wav \ + sfx_3404.wav \ + sfx_3405.wav \ + sfx_3406.wav \ + sfx_3407.wav \ + sfx_3408.wav \ + sfx_3409.wav \ + sfx_340.wav \ + sfx_3410.wav \ + sfx_3411.wav \ + sfx_3412.wav \ + sfx_3413.wav \ + sfx_3414.wav \ + sfx_3415.wav \ + sfx_3416.wav \ + sfx_3417.wav \ + sfx_3418.wav \ + sfx_3419.wav \ + sfx_341.wav \ + sfx_3420.wav \ + sfx_3421.wav \ + sfx_3422.wav \ + sfx_3423.wav \ + sfx_3424.wav \ + sfx_3425.wav \ + sfx_3426.wav \ + sfx_3427.wav \ + sfx_3428.wav \ + sfx_3429.wav \ + sfx_342.wav \ + sfx_3430.wav \ + sfx_3431.wav \ + sfx_3432.wav \ + sfx_3433.wav \ + sfx_3434.wav \ + sfx_3435.wav \ + sfx_3436.wav \ + sfx_3437.wav \ + sfx_3438.wav \ + sfx_3439.wav \ + sfx_343.wav \ + sfx_3440.wav \ + sfx_3441.wav \ + sfx_3442.wav \ + sfx_3443.wav \ + sfx_3444.wav \ + sfx_3445.wav \ + sfx_3446.wav \ + sfx_3447.wav \ + sfx_3448.wav \ + sfx_3449.wav \ + sfx_344.wav \ + sfx_3450.wav \ + sfx_3451.wav \ + sfx_3452.wav \ + sfx_3453.wav \ + sfx_3454.wav \ + sfx_3455.wav \ + sfx_3456.wav \ + sfx_3457.wav \ + sfx_3458.wav \ + sfx_3459.wav \ + sfx_345.wav \ + sfx_3460.wav \ + sfx_3461.wav \ + sfx_3462.wav \ + sfx_3463.wav \ + sfx_3464.wav \ + sfx_3465.wav \ + sfx_3466.wav \ + sfx_3467.wav \ + sfx_3468.wav \ + sfx_3469.wav \ + sfx_346.wav \ + sfx_3470.wav \ + sfx_3471.wav \ + sfx_3472.wav \ + sfx_3473.wav \ + sfx_3474.wav \ + sfx_3475.wav \ + sfx_3476.wav \ + sfx_3477.wav \ + sfx_3478.wav \ + sfx_3479.wav \ + sfx_347.wav \ + sfx_3480.wav \ + sfx_3481.wav \ + sfx_3482.wav \ + sfx_3483.wav \ + sfx_3484.wav \ + sfx_3485.wav \ + sfx_3486.wav \ + sfx_3487.wav \ + sfx_3488.wav \ + sfx_3489.wav \ + sfx_348.wav \ + sfx_3490.wav \ + sfx_3491.wav \ + sfx_3492.wav \ + sfx_3493.wav \ + sfx_3494.wav \ + sfx_3495.wav \ + sfx_3496.wav \ + sfx_3497.wav \ + sfx_3498.wav \ + sfx_3499.wav \ + sfx_349.wav \ + sfx_34.wav \ + sfx_3500.wav \ + sfx_3501.wav \ + sfx_3502.wav \ + sfx_3503.wav \ + sfx_3504.wav \ + sfx_3505.wav \ + sfx_3506.wav \ + sfx_3507.wav \ + sfx_3508.wav \ + sfx_3509.wav \ + sfx_350.wav \ + sfx_3510.wav \ + sfx_3511.wav \ + sfx_3512.wav \ + sfx_3513.wav \ + sfx_3514.wav \ + sfx_3515.wav \ + sfx_3516.wav \ + sfx_3517.wav \ + sfx_3518.wav \ + sfx_3519.wav \ + sfx_351.wav \ + sfx_3520.wav \ + sfx_3521.wav \ + sfx_3522.wav \ + sfx_3523.wav \ + sfx_3524.wav \ + sfx_3525.wav \ + sfx_3526.wav \ + sfx_3527.wav \ + sfx_3528.wav \ + sfx_3529.wav \ + sfx_352.wav \ + sfx_3530.wav \ + sfx_3531.wav \ + sfx_3532.wav \ + sfx_3533.wav \ + sfx_3534.wav \ + sfx_3535.wav \ + sfx_3536.wav \ + sfx_3537.wav \ + sfx_3538.wav \ + sfx_3539.wav \ + sfx_353.wav \ + sfx_3540.wav \ + sfx_3541.wav \ + sfx_3542.wav \ + sfx_3543.wav \ + sfx_3544.wav \ + sfx_3545.wav \ + sfx_3546.wav \ + sfx_3547.wav \ + sfx_3548.wav \ + sfx_3549.wav \ + sfx_354.wav \ + sfx_3550.wav \ + sfx_3551.wav \ + sfx_3552.wav \ + sfx_3553.wav \ + sfx_3554.wav \ + sfx_3555.wav \ + sfx_3556.wav \ + sfx_3557.wav \ + sfx_3558.wav \ + sfx_3559.wav \ + sfx_355.wav \ + sfx_3560.wav \ + sfx_3561.wav \ + sfx_3562.wav \ + sfx_3563.wav \ + sfx_3564.wav \ + sfx_3565.wav \ + sfx_3566.wav \ + sfx_3567.wav \ + sfx_3568.wav \ + sfx_3569.wav \ + sfx_356.wav \ + sfx_3570.wav \ + sfx_3571.wav \ + sfx_3572.wav \ + sfx_3573.wav \ + sfx_3574.wav \ + sfx_3575.wav \ + sfx_3576.wav \ + sfx_3577.wav \ + sfx_3578.wav \ + sfx_3579.wav \ + sfx_357.wav \ + sfx_3580.wav \ + sfx_3581.wav \ + sfx_3582.wav \ + sfx_3583.wav \ + sfx_3584.wav \ + sfx_3585.wav \ + sfx_3586.wav \ + sfx_3587.wav \ + sfx_3588.wav \ + sfx_3589.wav \ + sfx_358.wav \ + sfx_3590.wav \ + sfx_3591.wav \ + sfx_3592.wav \ + sfx_3593.wav \ + sfx_3594.wav \ + sfx_3595.wav \ + sfx_3596.wav \ + sfx_3597.wav \ + sfx_3598.wav \ + sfx_3599.wav \ + sfx_359.wav \ + sfx_35.wav \ + sfx_3600.wav \ + sfx_3601.wav \ + sfx_3602.wav \ + sfx_3603.wav \ + sfx_3604.wav \ + sfx_3605.wav \ + sfx_3606.wav \ + sfx_3607.wav \ + sfx_3608.wav \ + sfx_3609.wav \ + sfx_360.wav \ + sfx_3610.wav \ + sfx_3611.wav \ + sfx_3612.wav \ + sfx_3613.wav \ + sfx_3614.wav \ + sfx_3615.wav \ + sfx_3616.wav \ + sfx_3617.wav \ + sfx_3618.wav \ + sfx_3619.wav \ + sfx_361.wav \ + sfx_3620.wav \ + sfx_3621.wav \ + sfx_3622.wav \ + sfx_3623.wav \ + sfx_3624.wav \ + sfx_3625.wav \ + sfx_3626.wav \ + sfx_3627.wav \ + sfx_3628.wav \ + sfx_3629.wav \ + sfx_362.wav \ + sfx_3630.wav \ + sfx_3631.wav \ + sfx_3632.wav \ + sfx_3633.wav \ + sfx_3634.wav \ + sfx_3635.wav \ + sfx_3636.wav \ + sfx_3637.wav \ + sfx_3638.wav \ + sfx_3639.wav \ + sfx_363.wav \ + sfx_3640.wav \ + sfx_3641.wav \ + sfx_3642.wav \ + sfx_3643.wav \ + sfx_3644.wav \ + sfx_3645.wav \ + sfx_3646.wav \ + sfx_3647.wav \ + sfx_3648.wav \ + sfx_3649.wav \ + sfx_364.wav \ + sfx_3650.wav \ + sfx_3651.wav \ + sfx_3652.wav \ + sfx_3653.wav \ + sfx_3654.wav \ + sfx_3655.wav \ + sfx_3656.wav \ + sfx_3657.wav \ + sfx_3658.wav \ + sfx_3659.wav \ + sfx_365.wav \ + sfx_3660.wav \ + sfx_3661.wav \ + sfx_3662.wav \ + sfx_3663.wav \ + sfx_3664.wav \ + sfx_3665.wav \ + sfx_3666.wav \ + sfx_3667.wav \ + sfx_3668.wav \ + sfx_3669.wav \ + sfx_366.wav \ + sfx_3670.wav \ + sfx_3671.wav \ + sfx_3672.wav \ + sfx_3673.wav \ + sfx_3674.wav \ + sfx_3675.wav \ + sfx_3676.wav \ + sfx_3677.wav \ + sfx_3678.wav \ + sfx_3679.wav \ + sfx_367.wav \ + sfx_3680.wav \ + sfx_3681.wav \ + sfx_3682.wav \ + sfx_3683.wav \ + sfx_3684.wav \ + sfx_3685.wav \ + sfx_3686.wav \ + sfx_3687.wav \ + sfx_3688.wav \ + sfx_3689.wav \ + sfx_368.wav \ + sfx_3690.wav \ + sfx_3691.wav \ + sfx_3692.wav \ + sfx_3693.wav \ + sfx_3694.wav \ + sfx_3695.wav \ + sfx_3696.wav \ + sfx_3697.wav \ + sfx_3698.wav \ + sfx_3699.wav \ + sfx_369.wav \ + sfx_36.wav \ + sfx_3700.wav \ + sfx_3701.wav \ + sfx_3702.wav \ + sfx_3703.wav \ + sfx_3704.wav \ + sfx_3705.wav \ + sfx_3706.wav \ + sfx_3707.wav \ + sfx_3708.wav \ + sfx_3709.wav \ + sfx_370.wav \ + sfx_3710.wav \ + sfx_3711.wav \ + sfx_3712.wav \ + sfx_3713.wav \ + sfx_3714.wav \ + sfx_3715.wav \ + sfx_3716.wav \ + sfx_3717.wav \ + sfx_3718.wav \ + sfx_3719.wav \ + sfx_371.wav \ + sfx_3720.wav \ + sfx_3721.wav \ + sfx_3722.wav \ + sfx_3723.wav \ + sfx_3724.wav \ + sfx_3725.wav \ + sfx_3726.wav \ + sfx_3727.wav \ + sfx_3728.wav \ + sfx_3729.wav \ + sfx_372.wav \ + sfx_3730.wav \ + sfx_3731.wav \ + sfx_3732.wav \ + sfx_3733.wav \ + sfx_3734.wav \ + sfx_3735.wav \ + sfx_3736.wav \ + sfx_3737.wav \ + sfx_3738.wav \ + sfx_3739.wav \ + sfx_373.wav \ + sfx_3740.wav \ + sfx_3741.wav \ + sfx_3742.wav \ + sfx_3743.wav \ + sfx_3744.wav \ + sfx_3745.wav \ + sfx_3746.wav \ + sfx_3747.wav \ + sfx_3748.wav \ + sfx_3749.wav \ + sfx_374.wav \ + sfx_3750.wav \ + sfx_3751.wav \ + sfx_3752.wav \ + sfx_3753.wav \ + sfx_3754.wav \ + sfx_3755.wav \ + sfx_3756.wav \ + sfx_3757.wav \ + sfx_3758.wav \ + sfx_3759.wav \ + sfx_375.wav \ + sfx_3760.wav \ + sfx_3761.wav \ + sfx_3762.wav \ + sfx_3763.wav \ + sfx_3764.wav \ + sfx_3765.wav \ + sfx_3766.wav \ + sfx_3767.wav \ + sfx_3768.wav \ + sfx_3769.wav \ + sfx_376.wav \ + sfx_3770.wav \ + sfx_3771.wav \ + sfx_3772.wav \ + sfx_3773.wav \ + sfx_3774.wav \ + sfx_3775.wav \ + sfx_3776.wav \ + sfx_3777.wav \ + sfx_3778.wav \ + sfx_3779.wav \ + sfx_377.wav \ + sfx_3780.wav \ + sfx_3781.wav \ + sfx_3782.wav \ + sfx_3783.wav \ + sfx_3784.wav \ + sfx_3785.wav \ + sfx_3786.wav \ + sfx_3787.wav \ + sfx_3788.wav \ + sfx_3789.wav \ + sfx_378.wav \ + sfx_3790.wav \ + sfx_3791.wav \ + sfx_3792.wav \ + sfx_3793.wav \ + sfx_3794.wav \ + sfx_3795.wav \ + sfx_3796.wav \ + sfx_3797.wav \ + sfx_3798.wav \ + sfx_3799.wav \ + sfx_379.wav \ + sfx_37.wav \ + sfx_3800.wav \ + sfx_3801.wav \ + sfx_3802.wav \ + sfx_3803.wav \ + sfx_3804.wav \ + sfx_3805.wav \ + sfx_3806.wav \ + sfx_3807.wav \ + sfx_3808.wav \ + sfx_3809.wav \ + sfx_380.wav \ + sfx_3810.wav \ + sfx_3811.wav \ + sfx_3812.wav \ + sfx_3813.wav \ + sfx_3814.wav \ + sfx_3815.wav \ + sfx_3816.wav \ + sfx_3817.wav \ + sfx_3818.wav \ + sfx_3819.wav \ + sfx_381.wav \ + sfx_3820.wav \ + sfx_3821.wav \ + sfx_3822.wav \ + sfx_3823.wav \ + sfx_3824.wav \ + sfx_3825.wav \ + sfx_3826.wav \ + sfx_3827.wav \ + sfx_3828.wav \ + sfx_3829.wav \ + sfx_382.wav \ + sfx_3830.wav \ + sfx_3831.wav \ + sfx_3832.wav \ + sfx_3833.wav \ + sfx_3834.wav \ + sfx_3835.wav \ + sfx_3836.wav \ + sfx_3837.wav \ + sfx_3838.wav \ + sfx_3839.wav \ + sfx_383.wav \ + sfx_3840.wav \ + sfx_3841.wav \ + sfx_3842.wav \ + sfx_3843.wav \ + sfx_3844.wav \ + sfx_3845.wav \ + sfx_3846.wav \ + sfx_3847.wav \ + sfx_3848.wav \ + sfx_3849.wav \ + sfx_384.wav \ + sfx_3850.wav \ + sfx_3851.wav \ + sfx_3852.wav \ + sfx_3853.wav \ + sfx_3854.wav \ + sfx_3855.wav \ + sfx_3856.wav \ + sfx_3857.wav \ + sfx_3858.wav \ + sfx_3859.wav \ + sfx_385.wav \ + sfx_3860.wav \ + sfx_3861.wav \ + sfx_3862.wav \ + sfx_3863.wav \ + sfx_3864.wav \ + sfx_3865.wav \ + sfx_3866.wav \ + sfx_3867.wav \ + sfx_3868.wav \ + sfx_3869.wav \ + sfx_386.wav \ + sfx_3870.wav \ + sfx_3871.wav \ + sfx_3872.wav \ + sfx_3873.wav \ + sfx_3874.wav \ + sfx_3875.wav \ + sfx_3876.wav \ + sfx_3877.wav \ + sfx_3878.wav \ + sfx_3879.wav \ + sfx_387.wav \ + sfx_3880.wav \ + sfx_3881.wav \ + sfx_3882.wav \ + sfx_3883.wav \ + sfx_3884.wav \ + sfx_3885.wav \ + sfx_3886.wav \ + sfx_3887.wav \ + sfx_3888.wav \ + sfx_3889.wav \ + sfx_388.wav \ + sfx_3890.wav \ + sfx_3891.wav \ + sfx_3892.wav \ + sfx_3893.wav \ + sfx_3894.wav \ + sfx_3895.wav \ + sfx_3896.wav \ + sfx_3897.wav \ + sfx_3898.wav \ + sfx_3899.wav \ + sfx_389.wav \ + sfx_38.wav \ + sfx_3900.wav \ + sfx_3901.wav \ + sfx_3902.wav \ + sfx_3903.wav \ + sfx_3904.wav \ + sfx_3905.wav \ + sfx_3906.wav \ + sfx_3907.wav \ + sfx_3908.wav \ + sfx_3909.wav \ + sfx_390.wav \ + sfx_3910.wav \ + sfx_3911.wav \ + sfx_3912.wav \ + sfx_3913.wav \ + sfx_3914.wav \ + sfx_3915.wav \ + sfx_3916.wav \ + sfx_3917.wav \ + sfx_3918.wav \ + sfx_3919.wav \ + sfx_391.wav \ + sfx_3920.wav \ + sfx_3921.wav \ + sfx_3922.wav \ + sfx_3923.wav \ + sfx_3924.wav \ + sfx_3925.wav \ + sfx_3926.wav \ + sfx_3927.wav \ + sfx_3928.wav \ + sfx_3929.wav \ + sfx_392.wav \ + sfx_3930.wav \ + sfx_3931.wav \ + sfx_3932.wav \ + sfx_3933.wav \ + sfx_3934.wav \ + sfx_3935.wav \ + sfx_3936.wav \ + sfx_3937.wav \ + sfx_3938.wav \ + sfx_3939.wav \ + sfx_393.wav \ + sfx_3940.wav \ + sfx_3941.wav \ + sfx_3942.wav \ + sfx_3943.wav \ + sfx_3944.wav \ + sfx_3945.wav \ + sfx_3946.wav \ + sfx_3947.wav \ + sfx_3948.wav \ + sfx_3949.wav \ + sfx_394.wav \ + sfx_3950.wav \ + sfx_3951.wav \ + sfx_3952.wav \ + sfx_3953.wav \ + sfx_3954.wav \ + sfx_3955.wav \ + sfx_3956.wav \ + sfx_3957.wav \ + sfx_3958.wav \ + sfx_3959.wav \ + sfx_395.wav \ + sfx_3960.wav \ + sfx_3961.wav \ + sfx_3962.wav \ + sfx_3963.wav \ + sfx_3964.wav \ + sfx_3965.wav \ + sfx_3966.wav \ + sfx_3967.wav \ + sfx_3968.wav \ + sfx_3969.wav \ + sfx_396.wav \ + sfx_3970.wav \ + sfx_3971.wav \ + sfx_3972.wav \ + sfx_3973.wav \ + sfx_3974.wav \ + sfx_3975.wav \ + sfx_3976.wav \ + sfx_3977.wav \ + sfx_3978.wav \ + sfx_3979.wav \ + sfx_397.wav \ + sfx_3980.wav \ + sfx_3981.wav \ + sfx_3982.wav \ + sfx_3983.wav \ + sfx_3984.wav \ + sfx_3985.wav \ + sfx_3986.wav \ + sfx_3987.wav \ + sfx_3988.wav \ + sfx_3989.wav \ + sfx_398.wav \ + sfx_3990.wav \ + sfx_3991.wav \ + sfx_3992.wav \ + sfx_3993.wav \ + sfx_3994.wav \ + sfx_3995.wav \ + sfx_3996.wav \ + sfx_3997.wav \ + sfx_3998.wav \ + sfx_3999.wav \ + sfx_399.wav \ + sfx_39.wav \ + sfx_3.wav \ + sfx_4000.wav \ + sfx_4001.wav \ + sfx_4002.wav \ + sfx_4003.wav \ + sfx_4004.wav \ + sfx_4005.wav \ + sfx_4006.wav \ + sfx_4007.wav \ + sfx_4008.wav \ + sfx_4009.wav \ + sfx_400.wav \ + sfx_4010.wav \ + sfx_4011.wav \ + sfx_4012.wav \ + sfx_4013.wav \ + sfx_4014.wav \ + sfx_4015.wav \ + sfx_4016.wav \ + sfx_4017.wav \ + sfx_4018.wav \ + sfx_4019.wav \ + sfx_401.wav \ + sfx_4020.wav \ + sfx_4021.wav \ + sfx_4022.wav \ + sfx_4023.wav \ + sfx_4024.wav \ + sfx_4025.wav \ + sfx_4026.wav \ + sfx_4027.wav \ + sfx_4028.wav \ + sfx_4029.wav \ + sfx_402.wav \ + sfx_4030.wav \ + sfx_4031.wav \ + sfx_4032.wav \ + sfx_4033.wav \ + sfx_4034.wav \ + sfx_4035.wav \ + sfx_4036.wav \ + sfx_4037.wav \ + sfx_4038.wav \ + sfx_4039.wav \ + sfx_403.wav \ + sfx_4040.wav \ + sfx_4041.wav \ + sfx_4042.wav \ + sfx_4043.wav \ + sfx_4044.wav \ + sfx_4045.wav \ + sfx_4046.wav \ + sfx_4047.wav \ + sfx_4048.wav \ + sfx_4049.wav \ + sfx_404.wav \ + sfx_4050.wav \ + sfx_4051.wav \ + sfx_4052.wav \ + sfx_4053.wav \ + sfx_4054.wav \ + sfx_4055.wav \ + sfx_4056.wav \ + sfx_4057.wav \ + sfx_4058.wav \ + sfx_4059.wav \ + sfx_405.wav \ + sfx_4060.wav \ + sfx_4061.wav \ + sfx_4062.wav \ + sfx_4063.wav \ + sfx_4064.wav \ + sfx_4065.wav \ + sfx_4066.wav \ + sfx_4067.wav \ + sfx_4068.wav \ + sfx_4069.wav \ + sfx_406.wav \ + sfx_4070.wav \ + sfx_4071.wav \ + sfx_4072.wav \ + sfx_4073.wav \ + sfx_4074.wav \ + sfx_4075.wav \ + sfx_4076.wav \ + sfx_4077.wav \ + sfx_4078.wav \ + sfx_4079.wav \ + sfx_407.wav \ + sfx_4080.wav \ + sfx_4081.wav \ + sfx_4082.wav \ + sfx_4083.wav \ + sfx_4084.wav \ + sfx_4085.wav \ + sfx_4086.wav \ + sfx_4087.wav \ + sfx_4088.wav \ + sfx_4089.wav \ + sfx_408.wav \ + sfx_4090.wav \ + sfx_4091.wav \ + sfx_4092.wav \ + sfx_4093.wav \ + sfx_4094.wav \ + sfx_4095.wav \ + sfx_4096.wav \ + sfx_4097.wav \ + sfx_4098.wav \ + sfx_4099.wav \ + sfx_409.wav \ + sfx_40.wav \ + sfx_4100.wav \ + sfx_4101.wav \ + sfx_4102.wav \ + sfx_4103.wav \ + sfx_4104.wav \ + sfx_4105.wav \ + sfx_4106.wav \ + sfx_4107.wav \ + sfx_4108.wav \ + sfx_4109.wav \ + sfx_410.wav \ + sfx_4110.wav \ + sfx_4111.wav \ + sfx_4112.wav \ + sfx_4113.wav \ + sfx_4114.wav \ + sfx_4115.wav \ + sfx_4116.wav \ + sfx_4117.wav \ + sfx_4118.wav \ + sfx_4119.wav \ + sfx_411.wav \ + sfx_4120.wav \ + sfx_4121.wav \ + sfx_4122.wav \ + sfx_4123.wav \ + sfx_4124.wav \ + sfx_4125.wav \ + sfx_4126.wav \ + sfx_4127.wav \ + sfx_4128.wav \ + sfx_4129.wav \ + sfx_412.wav \ + sfx_4130.wav \ + sfx_4131.wav \ + sfx_4132.wav \ + sfx_4133.wav \ + sfx_4134.wav \ + sfx_4135.wav \ + sfx_4136.wav \ + sfx_4137.wav \ + sfx_4138.wav \ + sfx_4139.wav \ + sfx_413.wav \ + sfx_4140.wav \ + sfx_4141.wav \ + sfx_4142.wav \ + sfx_4143.wav \ + sfx_4144.wav \ + sfx_4145.wav \ + sfx_4146.wav \ + sfx_4147.wav \ + sfx_4148.wav \ + sfx_4149.wav \ + sfx_414.wav \ + sfx_4150.wav \ + sfx_4151.wav \ + sfx_4152.wav \ + sfx_4153.wav \ + sfx_4154.wav \ + sfx_4155.wav \ + sfx_4156.wav \ + sfx_4157.wav \ + sfx_4158.wav \ + sfx_4159.wav \ + sfx_415.wav \ + sfx_4160.wav \ + sfx_4161.wav \ + sfx_4162.wav \ + sfx_4163.wav \ + sfx_4164.wav \ + sfx_4165.wav \ + sfx_4166.wav \ + sfx_4167.wav \ + sfx_4168.wav \ + sfx_4169.wav \ + sfx_416.wav \ + sfx_4170.wav \ + sfx_4171.wav \ + sfx_4172.wav \ + sfx_4173.wav \ + sfx_4174.wav \ + sfx_4175.wav \ + sfx_4176.wav \ + sfx_4177.wav \ + sfx_4178.wav \ + sfx_4179.wav \ + sfx_417.wav \ + sfx_4180.wav \ + sfx_4181.wav \ + sfx_4182.wav \ + sfx_4183.wav \ + sfx_4184.wav \ + sfx_4185.wav \ + sfx_4186.wav \ + sfx_4187.wav \ + sfx_4188.wav \ + sfx_4189.wav \ + sfx_418.wav \ + sfx_4190.wav \ + sfx_4191.wav \ + sfx_4192.wav \ + sfx_4193.wav \ + sfx_4194.wav \ + sfx_4195.wav \ + sfx_4196.wav \ + sfx_4197.wav \ + sfx_4198.wav \ + sfx_4199.wav \ + sfx_419.wav \ + sfx_41.wav \ + sfx_4200.wav \ + sfx_4201.wav \ + sfx_4202.wav \ + sfx_4203.wav \ + sfx_4204.wav \ + sfx_4205.wav \ + sfx_4206.wav \ + sfx_4207.wav \ + sfx_4208.wav \ + sfx_4209.wav \ + sfx_420.wav \ + sfx_4210.wav \ + sfx_4211.wav \ + sfx_4212.wav \ + sfx_4213.wav \ + sfx_4214.wav \ + sfx_4215.wav \ + sfx_4216.wav \ + sfx_4217.wav \ + sfx_4218.wav \ + sfx_4219.wav \ + sfx_421.wav \ + sfx_4220.wav \ + sfx_4221.wav \ + sfx_4222.wav \ + sfx_4223.wav \ + sfx_4224.wav \ + sfx_4225.wav \ + sfx_4226.wav \ + sfx_4227.wav \ + sfx_4228.wav \ + sfx_4229.wav \ + sfx_422.wav \ + sfx_4230.wav \ + sfx_4231.wav \ + sfx_4232.wav \ + sfx_4233.wav \ + sfx_4234.wav \ + sfx_4235.wav \ + sfx_4236.wav \ + sfx_4237.wav \ + sfx_4238.wav \ + sfx_4239.wav \ + sfx_423.wav \ + sfx_4240.wav \ + sfx_4241.wav \ + sfx_4242.wav \ + sfx_4243.wav \ + sfx_4244.wav \ + sfx_4245.wav \ + sfx_4246.wav \ + sfx_4247.wav \ + sfx_4248.wav \ + sfx_4249.wav \ + sfx_424.wav \ + sfx_4250.wav \ + sfx_4251.wav \ + sfx_4252.wav \ + sfx_4253.wav \ + sfx_4254.wav \ + sfx_4255.wav \ + sfx_4256.wav \ + sfx_4257.wav \ + sfx_4258.wav \ + sfx_4259.wav \ + sfx_425.wav \ + sfx_4260.wav \ + sfx_4261.wav \ + sfx_4262.wav \ + sfx_4263.wav \ + sfx_4264.wav \ + sfx_4265.wav \ + sfx_4266.wav \ + sfx_4267.wav \ + sfx_4268.wav \ + sfx_4269.wav \ + sfx_426.wav \ + sfx_4270.wav \ + sfx_4271.wav \ + sfx_4272.wav \ + sfx_4273.wav \ + sfx_4274.wav \ + sfx_4275.wav \ + sfx_4276.wav \ + sfx_4277.wav \ + sfx_4278.wav \ + sfx_4279.wav \ + sfx_427.wav \ + sfx_4280.wav \ + sfx_4281.wav \ + sfx_4282.wav \ + sfx_4283.wav \ + sfx_4284.wav \ + sfx_4285.wav \ + sfx_4286.wav \ + sfx_4287.wav \ + sfx_4288.wav \ + sfx_4289.wav \ + sfx_428.wav \ + sfx_4290.wav \ + sfx_4291.wav \ + sfx_4292.wav \ + sfx_4293.wav \ + sfx_4294.wav \ + sfx_4295.wav \ + sfx_4296.wav \ + sfx_4297.wav \ + sfx_4298.wav \ + sfx_4299.wav \ + sfx_429.wav \ + sfx_42.wav \ + sfx_4300.wav \ + sfx_4301.wav \ + sfx_4302.wav \ + sfx_4303.wav \ + sfx_4304.wav \ + sfx_4305.wav \ + sfx_4306.wav \ + sfx_4307.wav \ + sfx_4308.wav \ + sfx_4309.wav \ + sfx_430.wav \ + sfx_4310.wav \ + sfx_4311.wav \ + sfx_4312.wav \ + sfx_4313.wav \ + sfx_4314.wav \ + sfx_4315.wav \ + sfx_4316.wav \ + sfx_4317.wav \ + sfx_4318.wav \ + sfx_4319.wav \ + sfx_431.wav \ + sfx_4320.wav \ + sfx_4321.wav \ + sfx_4322.wav \ + sfx_4323.wav \ + sfx_4324.wav \ + sfx_4325.wav \ + sfx_4326.wav \ + sfx_4327.wav \ + sfx_4328.wav \ + sfx_4329.wav \ + sfx_432.wav \ + sfx_4330.wav \ + sfx_4331.wav \ + sfx_4332.wav \ + sfx_4333.wav \ + sfx_4334.wav \ + sfx_4335.wav \ + sfx_4336.wav \ + sfx_4337.wav \ + sfx_4338.wav \ + sfx_4339.wav \ + sfx_433.wav \ + sfx_4340.wav \ + sfx_4341.wav \ + sfx_4342.wav \ + sfx_4343.wav \ + sfx_4344.wav \ + sfx_4345.wav \ + sfx_4346.wav \ + sfx_4347.wav \ + sfx_4348.wav \ + sfx_4349.wav \ + sfx_434.wav \ + sfx_4350.wav \ + sfx_4351.wav \ + sfx_4352.wav \ + sfx_4353.wav \ + sfx_4354.wav \ + sfx_4355.wav \ + sfx_4356.wav \ + sfx_4357.wav \ + sfx_4358.wav \ + sfx_4359.wav \ + sfx_435.wav \ + sfx_4360.wav \ + sfx_4361.wav \ + sfx_4362.wav \ + sfx_4363.wav \ + sfx_4364.wav \ + sfx_4365.wav \ + sfx_4366.wav \ + sfx_4367.wav \ + sfx_4368.wav \ + sfx_4369.wav \ + sfx_436.wav \ + sfx_4370.wav \ + sfx_4371.wav \ + sfx_4372.wav \ + sfx_4373.wav \ + sfx_4374.wav \ + sfx_4375.wav \ + sfx_4376.wav \ + sfx_4377.wav \ + sfx_4378.wav \ + sfx_4379.wav \ + sfx_437.wav \ + sfx_4380.wav \ + sfx_4381.wav \ + sfx_4382.wav \ + sfx_4383.wav \ + sfx_4384.wav \ + sfx_4385.wav \ + sfx_4386.wav \ + sfx_4387.wav \ + sfx_4388.wav \ + sfx_4389.wav \ + sfx_438.wav \ + sfx_4390.wav \ + sfx_4391.wav \ + sfx_4392.wav \ + sfx_4393.wav \ + sfx_4394.wav \ + sfx_4395.wav \ + sfx_4396.wav \ + sfx_4397.wav \ + sfx_4398.wav \ + sfx_4399.wav \ + sfx_439.wav \ + sfx_43.wav \ + sfx_4400.wav \ + sfx_4401.wav \ + sfx_4402.wav \ + sfx_4403.wav \ + sfx_4404.wav \ + sfx_4405.wav \ + sfx_4406.wav \ + sfx_4407.wav \ + sfx_4408.wav \ + sfx_4409.wav \ + sfx_440.wav \ + sfx_4410.wav \ + sfx_4411.wav \ + sfx_4412.wav \ + sfx_4413.wav \ + sfx_4414.wav \ + sfx_4415.wav \ + sfx_4416.wav \ + sfx_4417.wav \ + sfx_4418.wav \ + sfx_4419.wav \ + sfx_441.wav \ + sfx_4420.wav \ + sfx_4421.wav \ + sfx_4422.wav \ + sfx_4423.wav \ + sfx_4424.wav \ + sfx_4425.wav \ + sfx_4426.wav \ + sfx_4427.wav \ + sfx_4428.wav \ + sfx_4429.wav \ + sfx_442.wav \ + sfx_4430.wav \ + sfx_4431.wav \ + sfx_4432.wav \ + sfx_4433.wav \ + sfx_4434.wav \ + sfx_4435.wav \ + sfx_4436.wav \ + sfx_4437.wav \ + sfx_4438.wav \ + sfx_4439.wav \ + sfx_443.wav \ + sfx_4440.wav \ + sfx_4441.wav \ + sfx_4442.wav \ + sfx_4443.wav \ + sfx_4444.wav \ + sfx_4445.wav \ + sfx_4446.wav \ + sfx_4447.wav \ + sfx_4448.wav \ + sfx_4449.wav \ + sfx_444.wav \ + sfx_4450.wav \ + sfx_4451.wav \ + sfx_4452.wav \ + sfx_4453.wav \ + sfx_4454.wav \ + sfx_4455.wav \ + sfx_4456.wav \ + sfx_4457.wav \ + sfx_4458.wav \ + sfx_4459.wav \ + sfx_445.wav \ + sfx_4460.wav \ + sfx_4461.wav \ + sfx_4462.wav \ + sfx_4463.wav \ + sfx_4464.wav \ + sfx_4465.wav \ + sfx_4466.wav \ + sfx_4467.wav \ + sfx_4468.wav \ + sfx_4469.wav \ + sfx_446.wav \ + sfx_4470.wav \ + sfx_4471.wav \ + sfx_4472.wav \ + sfx_4473.wav \ + sfx_4474.wav \ + sfx_4475.wav \ + sfx_4476.wav \ + sfx_4477.wav \ + sfx_4478.wav \ + sfx_4479.wav \ + sfx_447.wav \ + sfx_4480.wav \ + sfx_4481.wav \ + sfx_4482.wav \ + sfx_4483.wav \ + sfx_4484.wav \ + sfx_4485.wav \ + sfx_4486.wav \ + sfx_4487.wav \ + sfx_4488.wav \ + sfx_4489.wav \ + sfx_448.wav \ + sfx_4490.wav \ + sfx_4491.wav \ + sfx_4492.wav \ + sfx_4493.wav \ + sfx_4494.wav \ + sfx_4495.wav \ + sfx_4496.wav \ + sfx_4497.wav \ + sfx_4498.wav \ + sfx_4499.wav \ + sfx_449.wav \ + sfx_44.wav \ + sfx_4500.wav \ + sfx_4501.wav \ + sfx_4502.wav \ + sfx_4503.wav \ + sfx_4504.wav \ + sfx_4505.wav \ + sfx_4506.wav \ + sfx_4507.wav \ + sfx_4508.wav \ + sfx_4509.wav \ + sfx_450.wav \ + sfx_4510.wav \ + sfx_4511.wav \ + sfx_4512.wav \ + sfx_4513.wav \ + sfx_4514.wav \ + sfx_4515.wav \ + sfx_4516.wav \ + sfx_4517.wav \ + sfx_4518.wav \ + sfx_4519.wav \ + sfx_451.wav \ + sfx_4520.wav \ + sfx_4521.wav \ + sfx_4522.wav \ + sfx_4523.wav \ + sfx_4524.wav \ + sfx_4525.wav \ + sfx_4526.wav \ + sfx_4527.wav \ + sfx_4528.wav \ + sfx_4529.wav \ + sfx_452.wav \ + sfx_4530.wav \ + sfx_4531.wav \ + sfx_4532.wav \ + sfx_4533.wav \ + sfx_4534.wav \ + sfx_4535.wav \ + sfx_4536.wav \ + sfx_4537.wav \ + sfx_4538.wav \ + sfx_4539.wav \ + sfx_453.wav \ + sfx_4540.wav \ + sfx_4541.wav \ + sfx_4542.wav \ + sfx_4543.wav \ + sfx_4544.wav \ + sfx_4545.wav \ + sfx_4546.wav \ + sfx_4547.wav \ + sfx_4548.wav \ + sfx_4549.wav \ + sfx_454.wav \ + sfx_4550.wav \ + sfx_4551.wav \ + sfx_4552.wav \ + sfx_4553.wav \ + sfx_4554.wav \ + sfx_4555.wav \ + sfx_4556.wav \ + sfx_4557.wav \ + sfx_4558.wav \ + sfx_4559.wav \ + sfx_455.wav \ + sfx_4560.wav \ + sfx_4561.wav \ + sfx_4562.wav \ + sfx_4563.wav \ + sfx_4564.wav \ + sfx_4565.wav \ + sfx_4566.wav \ + sfx_4567.wav \ + sfx_4568.wav \ + sfx_4569.wav \ + sfx_456.wav \ + sfx_4570.wav \ + sfx_4571.wav \ + sfx_4572.wav \ + sfx_4573.wav \ + sfx_4574.wav \ + sfx_4575.wav \ + sfx_4576.wav \ + sfx_4577.wav \ + sfx_4578.wav \ + sfx_4579.wav \ + sfx_457.wav \ + sfx_4580.wav \ + sfx_4581.wav \ + sfx_4582.wav \ + sfx_4583.wav \ + sfx_4584.wav \ + sfx_4585.wav \ + sfx_4586.wav \ + sfx_4587.wav \ + sfx_4588.wav \ + sfx_4589.wav \ + sfx_458.wav \ + sfx_4590.wav \ + sfx_4591.wav \ + sfx_4592.wav \ + sfx_4593.wav \ + sfx_4594.wav \ + sfx_4595.wav \ + sfx_4596.wav \ + sfx_4597.wav \ + sfx_4598.wav \ + sfx_4599.wav \ + sfx_459.wav \ + sfx_45.wav \ + sfx_4600.wav \ + sfx_4601.wav \ + sfx_4602.wav \ + sfx_4603.wav \ + sfx_4604.wav \ + sfx_4605.wav \ + sfx_4606.wav \ + sfx_4607.wav \ + sfx_4608.wav \ + sfx_4609.wav \ + sfx_460.wav \ + sfx_4610.wav \ + sfx_4611.wav \ + sfx_4612.wav \ + sfx_4613.wav \ + sfx_4614.wav \ + sfx_4615.wav \ + sfx_4616.wav \ + sfx_4617.wav \ + sfx_4618.wav \ + sfx_4619.wav \ + sfx_461.wav \ + sfx_4620.wav \ + sfx_4621.wav \ + sfx_4622.wav \ + sfx_4623.wav \ + sfx_4624.wav \ + sfx_4625.wav \ + sfx_4626.wav \ + sfx_4627.wav \ + sfx_4628.wav \ + sfx_4629.wav \ + sfx_462.wav \ + sfx_4630.wav \ + sfx_4631.wav \ + sfx_4632.wav \ + sfx_4633.wav \ + sfx_4634.wav \ + sfx_4635.wav \ + sfx_4636.wav \ + sfx_4637.wav \ + sfx_4638.wav \ + sfx_4639.wav \ + sfx_463.wav \ + sfx_4640.wav \ + sfx_4641.wav \ + sfx_4642.wav \ + sfx_4643.wav \ + sfx_4644.wav \ + sfx_4645.wav \ + sfx_4646.wav \ + sfx_4647.wav \ + sfx_4648.wav \ + sfx_4649.wav \ + sfx_464.wav \ + sfx_4650.wav \ + sfx_4651.wav \ + sfx_4652.wav \ + sfx_4653.wav \ + sfx_4654.wav \ + sfx_4655.wav \ + sfx_4656.wav \ + sfx_4657.wav \ + sfx_4658.wav \ + sfx_4659.wav \ + sfx_465.wav \ + sfx_4660.wav \ + sfx_4661.wav \ + sfx_4662.wav \ + sfx_4663.wav \ + sfx_4664.wav \ + sfx_4665.wav \ + sfx_4666.wav \ + sfx_4667.wav \ + sfx_4668.wav \ + sfx_4669.wav \ + sfx_466.wav \ + sfx_4670.wav \ + sfx_4671.wav \ + sfx_4672.wav \ + sfx_4673.wav \ + sfx_4674.wav \ + sfx_4675.wav \ + sfx_4676.wav \ + sfx_4677.wav \ + sfx_4678.wav \ + sfx_4679.wav \ + sfx_467.wav \ + sfx_4680.wav \ + sfx_4681.wav \ + sfx_4682.wav \ + sfx_4683.wav \ + sfx_4684.wav \ + sfx_4685.wav \ + sfx_4686.wav \ + sfx_4687.wav \ + sfx_4688.wav \ + sfx_4689.wav \ + sfx_468.wav \ + sfx_4690.wav \ + sfx_4691.wav \ + sfx_4692.wav \ + sfx_4693.wav \ + sfx_4694.wav \ + sfx_4695.wav \ + sfx_4696.wav \ + sfx_4697.wav \ + sfx_4698.wav \ + sfx_4699.wav \ + sfx_469.wav \ + sfx_46.wav \ + sfx_4700.wav \ + sfx_4701.wav \ + sfx_4702.wav \ + sfx_4703.wav \ + sfx_4704.wav \ + sfx_4705.wav \ + sfx_4706.wav \ + sfx_4707.wav \ + sfx_4708.wav \ + sfx_4709.wav \ + sfx_470.wav \ + sfx_4710.wav \ + sfx_4711.wav \ + sfx_4712.wav \ + sfx_4713.wav \ + sfx_4714.wav \ + sfx_4715.wav \ + sfx_4716.wav \ + sfx_4717.wav \ + sfx_4718.wav \ + sfx_4719.wav \ + sfx_471.wav \ + sfx_4720.wav \ + sfx_4721.wav \ + sfx_4722.wav \ + sfx_4723.wav \ + sfx_4724.wav \ + sfx_4725.wav \ + sfx_4726.wav \ + sfx_4727.wav \ + sfx_4728.wav \ + sfx_4729.wav \ + sfx_472.wav \ + sfx_4730.wav \ + sfx_4731.wav \ + sfx_4732.wav \ + sfx_4733.wav \ + sfx_4734.wav \ + sfx_4735.wav \ + sfx_4736.wav \ + sfx_4737.wav \ + sfx_4738.wav \ + sfx_4739.wav \ + sfx_473.wav \ + sfx_4740.wav \ + sfx_4741.wav \ + sfx_4742.wav \ + sfx_4743.wav \ + sfx_4744.wav \ + sfx_4745.wav \ + sfx_4746.wav \ + sfx_4747.wav \ + sfx_4748.wav \ + sfx_4749.wav \ + sfx_474.wav \ + sfx_4750.wav \ + sfx_4751.wav \ + sfx_4752.wav \ + sfx_4753.wav \ + sfx_4754.wav \ + sfx_4755.wav \ + sfx_4756.wav \ + sfx_4757.wav \ + sfx_4758.wav \ + sfx_4759.wav \ + sfx_475.wav \ + sfx_4760.wav \ + sfx_4761.wav \ + sfx_4762.wav \ + sfx_4763.wav \ + sfx_4764.wav \ + sfx_4765.wav \ + sfx_4766.wav \ + sfx_4767.wav \ + sfx_4768.wav \ + sfx_4769.wav \ + sfx_476.wav \ + sfx_4770.wav \ + sfx_4771.wav \ + sfx_4772.wav \ + sfx_4773.wav \ + sfx_4774.wav \ + sfx_4775.wav \ + sfx_4776.wav \ + sfx_4777.wav \ + sfx_4778.wav \ + sfx_4779.wav \ + sfx_477.wav \ + sfx_4780.wav \ + sfx_4781.wav \ + sfx_4782.wav \ + sfx_4783.wav \ + sfx_4784.wav \ + sfx_4785.wav \ + sfx_4786.wav \ + sfx_4787.wav \ + sfx_4788.wav \ + sfx_4789.wav \ + sfx_478.wav \ + sfx_4790.wav \ + sfx_4791.wav \ + sfx_4792.wav \ + sfx_4793.wav \ + sfx_4794.wav \ + sfx_4795.wav \ + sfx_4796.wav \ + sfx_4797.wav \ + sfx_4798.wav \ + sfx_4799.wav \ + sfx_479.wav \ + sfx_47.wav \ + sfx_4800.wav \ + sfx_4801.wav \ + sfx_4802.wav \ + sfx_4803.wav \ + sfx_4804.wav \ + sfx_4805.wav \ + sfx_4806.wav \ + sfx_4807.wav \ + sfx_4808.wav \ + sfx_4809.wav \ + sfx_480.wav \ + sfx_4810.wav \ + sfx_4811.wav \ + sfx_4812.wav \ + sfx_4813.wav \ + sfx_4814.wav \ + sfx_4815.wav \ + sfx_4816.wav \ + sfx_4817.wav \ + sfx_4818.wav \ + sfx_4819.wav \ + sfx_481.wav \ + sfx_4820.wav \ + sfx_4821.wav \ + sfx_4822.wav \ + sfx_4823.wav \ + sfx_4824.wav \ + sfx_4825.wav \ + sfx_4826.wav \ + sfx_4827.wav \ + sfx_4828.wav \ + sfx_4829.wav \ + sfx_482.wav \ + sfx_4830.wav \ + sfx_4831.wav \ + sfx_4832.wav \ + sfx_4833.wav \ + sfx_4834.wav \ + sfx_4835.wav \ + sfx_4836.wav \ + sfx_4837.wav \ + sfx_4838.wav \ + sfx_4839.wav \ + sfx_483.wav \ + sfx_4840.wav \ + sfx_4841.wav \ + sfx_4842.wav \ + sfx_4843.wav \ + sfx_4844.wav \ + sfx_4845.wav \ + sfx_4846.wav \ + sfx_4847.wav \ + sfx_4848.wav \ + sfx_4849.wav \ + sfx_484.wav \ + sfx_4850.wav \ + sfx_4851.wav \ + sfx_4852.wav \ + sfx_4853.wav \ + sfx_4854.wav \ + sfx_4855.wav \ + sfx_4856.wav \ + sfx_4857.wav \ + sfx_4858.wav \ + sfx_4859.wav \ + sfx_485.wav \ + sfx_4860.wav \ + sfx_4861.wav \ + sfx_4862.wav \ + sfx_4863.wav \ + sfx_4864.wav \ + sfx_4865.wav \ + sfx_4866.wav \ + sfx_4867.wav \ + sfx_4868.wav \ + sfx_4869.wav \ + sfx_486.wav \ + sfx_4870.wav \ + sfx_4871.wav \ + sfx_4872.wav \ + sfx_4873.wav \ + sfx_4874.wav \ + sfx_4875.wav \ + sfx_4876.wav \ + sfx_4877.wav \ + sfx_4878.wav \ + sfx_4879.wav \ + sfx_487.wav \ + sfx_4880.wav \ + sfx_4881.wav \ + sfx_4882.wav \ + sfx_4883.wav \ + sfx_4884.wav \ + sfx_4885.wav \ + sfx_4886.wav \ + sfx_4887.wav \ + sfx_4888.wav \ + sfx_4889.wav \ + sfx_488.wav \ + sfx_4890.wav \ + sfx_4891.wav \ + sfx_4892.wav \ + sfx_4893.wav \ + sfx_4894.wav \ + sfx_4895.wav \ + sfx_4896.wav \ + sfx_4897.wav \ + sfx_4898.wav \ + sfx_4899.wav \ + sfx_489.wav \ + sfx_48.wav \ + sfx_4900.wav \ + sfx_4901.wav \ + sfx_4902.wav \ + sfx_4903.wav \ + sfx_4904.wav \ + sfx_4905.wav \ + sfx_4906.wav \ + sfx_4907.wav \ + sfx_4908.wav \ + sfx_4909.wav \ + sfx_490.wav \ + sfx_4910.wav \ + sfx_4911.wav \ + sfx_4912.wav \ + sfx_4913.wav \ + sfx_4914.wav \ + sfx_4915.wav \ + sfx_4916.wav \ + sfx_4917.wav \ + sfx_4918.wav \ + sfx_4919.wav \ + sfx_491.wav \ + sfx_4920.wav \ + sfx_4921.wav \ + sfx_4922.wav \ + sfx_4923.wav \ + sfx_4924.wav \ + sfx_4925.wav \ + sfx_4926.wav \ + sfx_4927.wav \ + sfx_4928.wav \ + sfx_4929.wav \ + sfx_492.wav \ + sfx_4930.wav \ + sfx_4931.wav \ + sfx_4932.wav \ + sfx_4933.wav \ + sfx_4934.wav \ + sfx_4935.wav \ + sfx_4936.wav \ + sfx_4937.wav \ + sfx_4938.wav \ + sfx_4939.wav \ + sfx_493.wav \ + sfx_4940.wav \ + sfx_4941.wav \ + sfx_4942.wav \ + sfx_4943.wav \ + sfx_4944.wav \ + sfx_4945.wav \ + sfx_4946.wav \ + sfx_4947.wav \ + sfx_4948.wav \ + sfx_4949.wav \ + sfx_494.wav \ + sfx_4950.wav \ + sfx_4951.wav \ + sfx_4952.wav \ + sfx_4953.wav \ + sfx_4954.wav \ + sfx_4955.wav \ + sfx_4956.wav \ + sfx_4957.wav \ + sfx_4958.wav \ + sfx_4959.wav \ + sfx_495.wav \ + sfx_4960.wav \ + sfx_4961.wav \ + sfx_4962.wav \ + sfx_4963.wav \ + sfx_4964.wav \ + sfx_4965.wav \ + sfx_4966.wav \ + sfx_4967.wav \ + sfx_4968.wav \ + sfx_4969.wav \ + sfx_496.wav \ + sfx_4970.wav \ + sfx_4971.wav \ + sfx_4972.wav \ + sfx_4973.wav \ + sfx_4974.wav \ + sfx_4975.wav \ + sfx_4976.wav \ + sfx_4977.wav \ + sfx_4978.wav \ + sfx_4979.wav \ + sfx_497.wav \ + sfx_4980.wav \ + sfx_4981.wav \ + sfx_4982.wav \ + sfx_4983.wav \ + sfx_4984.wav \ + sfx_4985.wav \ + sfx_4986.wav \ + sfx_4987.wav \ + sfx_4988.wav \ + sfx_4989.wav \ + sfx_498.wav \ + sfx_4990.wav \ + sfx_4991.wav \ + sfx_4992.wav \ + sfx_4993.wav \ + sfx_4994.wav \ + sfx_4995.wav \ + sfx_4996.wav \ + sfx_4997.wav \ + sfx_4998.wav \ + sfx_4999.wav \ + sfx_499.wav \ + sfx_49.wav \ + sfx_4.wav \ + sfx_5000.wav \ + sfx_5001.wav \ + sfx_5002.wav \ + sfx_5003.wav \ + sfx_5004.wav \ + sfx_5005.wav \ + sfx_5006.wav \ + sfx_5007.wav \ + sfx_5008.wav \ + sfx_5009.wav \ + sfx_500.wav \ + sfx_5010.wav \ + sfx_5011.wav \ + sfx_5012.wav \ + sfx_5013.wav \ + sfx_5014.wav \ + sfx_5015.wav \ + sfx_5016.wav \ + sfx_5017.wav \ + sfx_5018.wav \ + sfx_5019.wav \ + sfx_501.wav \ + sfx_5020.wav \ + sfx_5021.wav \ + sfx_5022.wav \ + sfx_5023.wav \ + sfx_5024.wav \ + sfx_5025.wav \ + sfx_5026.wav \ + sfx_5027.wav \ + sfx_5028.wav \ + sfx_5029.wav \ + sfx_502.wav \ + sfx_5030.wav \ + sfx_5031.wav \ + sfx_5032.wav \ + sfx_5033.wav \ + sfx_5034.wav \ + sfx_5035.wav \ + sfx_5036.wav \ + sfx_5037.wav \ + sfx_5038.wav \ + sfx_5039.wav \ + sfx_503.wav \ + sfx_5040.wav \ + sfx_5041.wav \ + sfx_5042.wav \ + sfx_5043.wav \ + sfx_5044.wav \ + sfx_5045.wav \ + sfx_5046.wav \ + sfx_5047.wav \ + sfx_5048.wav \ + sfx_5049.wav \ + sfx_504.wav \ + sfx_5050.wav \ + sfx_5051.wav \ + sfx_5052.wav \ + sfx_5053.wav \ + sfx_5054.wav \ + sfx_5055.wav \ + sfx_5056.wav \ + sfx_5057.wav \ + sfx_5058.wav \ + sfx_5059.wav \ + sfx_505.wav \ + sfx_5060.wav \ + sfx_5061.wav \ + sfx_5062.wav \ + sfx_5063.wav \ + sfx_5064.wav \ + sfx_5065.wav \ + sfx_5066.wav \ + sfx_5067.wav \ + sfx_5068.wav \ + sfx_5069.wav \ + sfx_506.wav \ + sfx_5070.wav \ + sfx_5071.wav \ + sfx_5072.wav \ + sfx_5073.wav \ + sfx_5074.wav \ + sfx_5075.wav \ + sfx_5076.wav \ + sfx_5077.wav \ + sfx_5078.wav \ + sfx_5079.wav \ + sfx_507.wav \ + sfx_5080.wav \ + sfx_5081.wav \ + sfx_5082.wav \ + sfx_5083.wav \ + sfx_5084.wav \ + sfx_5085.wav \ + sfx_5086.wav \ + sfx_5087.wav \ + sfx_5088.wav \ + sfx_5089.wav \ + sfx_508.wav \ + sfx_5090.wav \ + sfx_5091.wav \ + sfx_5092.wav \ + sfx_5093.wav \ + sfx_5094.wav \ + sfx_5095.wav \ + sfx_5096.wav \ + sfx_5097.wav \ + sfx_5098.wav \ + sfx_5099.wav \ + sfx_509.wav \ + sfx_50.wav \ + sfx_5100.wav \ + sfx_5101.wav \ + sfx_5102.wav \ + sfx_5103.wav \ + sfx_5104.wav \ + sfx_5105.wav \ + sfx_5106.wav \ + sfx_5107.wav \ + sfx_5108.wav \ + sfx_5109.wav \ + sfx_510.wav \ + sfx_5110.wav \ + sfx_5111.wav \ + sfx_5112.wav \ + sfx_5113.wav \ + sfx_5114.wav \ + sfx_5115.wav \ + sfx_5116.wav \ + sfx_5117.wav \ + sfx_5118.wav \ + sfx_5119.wav \ + sfx_511.wav \ + sfx_5120.wav \ + sfx_5121.wav \ + sfx_5122.wav \ + sfx_5123.wav \ + sfx_5124.wav \ + sfx_5125.wav \ + sfx_5126.wav \ + sfx_5127.wav \ + sfx_5128.wav \ + sfx_5129.wav \ + sfx_512.wav \ + sfx_5130.wav \ + sfx_5131.wav \ + sfx_5132.wav \ + sfx_5133.wav \ + sfx_5134.wav \ + sfx_5135.wav \ + sfx_5136.wav \ + sfx_5137.wav \ + sfx_5138.wav \ + sfx_5139.wav \ + sfx_513.wav \ + sfx_5140.wav \ + sfx_5141.wav \ + sfx_5142.wav \ + sfx_5143.wav \ + sfx_5144.wav \ + sfx_5145.wav \ + sfx_5146.wav \ + sfx_5147.wav \ + sfx_5148.wav \ + sfx_5149.wav \ + sfx_514.wav \ + sfx_5150.wav \ + sfx_5151.wav \ + sfx_5152.wav \ + sfx_5153.wav \ + sfx_5154.wav \ + sfx_5155.wav \ + sfx_5156.wav \ + sfx_5157.wav \ + sfx_5158.wav \ + sfx_5159.wav \ + sfx_515.wav \ + sfx_5160.wav \ + sfx_5161.wav \ + sfx_5162.wav \ + sfx_5163.wav \ + sfx_5164.wav \ + sfx_5165.wav \ + sfx_5166.wav \ + sfx_5167.wav \ + sfx_5168.wav \ + sfx_5169.wav \ + sfx_516.wav \ + sfx_5170.wav \ + sfx_5171.wav \ + sfx_5172.wav \ + sfx_5173.wav \ + sfx_5174.wav \ + sfx_5175.wav \ + sfx_5176.wav \ + sfx_5177.wav \ + sfx_5178.wav \ + sfx_5179.wav \ + sfx_517.wav \ + sfx_5180.wav \ + sfx_5181.wav \ + sfx_5182.wav \ + sfx_5183.wav \ + sfx_5184.wav \ + sfx_5185.wav \ + sfx_5186.wav \ + sfx_5187.wav \ + sfx_5188.wav \ + sfx_5189.wav \ + sfx_518.wav \ + sfx_5190.wav \ + sfx_5191.wav \ + sfx_5192.wav \ + sfx_5193.wav \ + sfx_5194.wav \ + sfx_5195.wav \ + sfx_5196.wav \ + sfx_5197.wav \ + sfx_5198.wav \ + sfx_5199.wav \ + sfx_519.wav \ + sfx_51.wav \ + sfx_5200.wav \ + sfx_5201.wav \ + sfx_5202.wav \ + sfx_5203.wav \ + sfx_5204.wav \ + sfx_5205.wav \ + sfx_5206.wav \ + sfx_5207.wav \ + sfx_5208.wav \ + sfx_5209.wav \ + sfx_520.wav \ + sfx_5210.wav \ + sfx_5211.wav \ + sfx_5212.wav \ + sfx_5213.wav \ + sfx_5214.wav \ + sfx_5215.wav \ + sfx_5216.wav \ + sfx_5217.wav \ + sfx_5218.wav \ + sfx_5219.wav \ + sfx_521.wav \ + sfx_5220.wav \ + sfx_5221.wav \ + sfx_5222.wav \ + sfx_5223.wav \ + sfx_5224.wav \ + sfx_5225.wav \ + sfx_5226.wav \ + sfx_5227.wav \ + sfx_5228.wav \ + sfx_5229.wav \ + sfx_522.wav \ + sfx_5230.wav \ + sfx_5231.wav \ + sfx_5232.wav \ + sfx_5233.wav \ + sfx_5234.wav \ + sfx_5235.wav \ + sfx_5236.wav \ + sfx_5237.wav \ + sfx_5238.wav \ + sfx_5239.wav \ + sfx_523.wav \ + sfx_5240.wav \ + sfx_5241.wav \ + sfx_5242.wav \ + sfx_5243.wav \ + sfx_5244.wav \ + sfx_5245.wav \ + sfx_5246.wav \ + sfx_5247.wav \ + sfx_5248.wav \ + sfx_5249.wav \ + sfx_524.wav \ + sfx_5250.wav \ + sfx_5251.wav \ + sfx_5252.wav \ + sfx_5253.wav \ + sfx_5254.wav \ + sfx_5255.wav \ + sfx_5256.wav \ + sfx_5257.wav \ + sfx_5258.wav \ + sfx_5259.wav \ + sfx_525.wav \ + sfx_5260.wav \ + sfx_5261.wav \ + sfx_5262.wav \ + sfx_5263.wav \ + sfx_5264.wav \ + sfx_5265.wav \ + sfx_5266.wav \ + sfx_5267.wav \ + sfx_5268.wav \ + sfx_5269.wav \ + sfx_526.wav \ + sfx_5270.wav \ + sfx_5271.wav \ + sfx_5272.wav \ + sfx_5273.wav \ + sfx_5274.wav \ + sfx_5275.wav \ + sfx_5276.wav \ + sfx_5277.wav \ + sfx_5278.wav \ + sfx_5279.wav \ + sfx_527.wav \ + sfx_5280.wav \ + sfx_5281.wav \ + sfx_5282.wav \ + sfx_5283.wav \ + sfx_5284.wav \ + sfx_5285.wav \ + sfx_5286.wav \ + sfx_5287.wav \ + sfx_5288.wav \ + sfx_5289.wav \ + sfx_528.wav \ + sfx_5290.wav \ + sfx_5291.wav \ + sfx_5292.wav \ + sfx_5293.wav \ + sfx_5294.wav \ + sfx_5295.wav \ + sfx_5296.wav \ + sfx_5297.wav \ + sfx_5298.wav \ + sfx_5299.wav \ + sfx_529.wav \ + sfx_52.wav \ + sfx_5300.wav \ + sfx_5301.wav \ + sfx_5302.wav \ + sfx_5303.wav \ + sfx_5304.wav \ + sfx_5305.wav \ + sfx_5306.wav \ + sfx_5307.wav \ + sfx_5308.wav \ + sfx_5309.wav \ + sfx_530.wav \ + sfx_5310.wav \ + sfx_5311.wav \ + sfx_5312.wav \ + sfx_5313.wav \ + sfx_5314.wav \ + sfx_5315.wav \ + sfx_5316.wav \ + sfx_5317.wav \ + sfx_5318.wav \ + sfx_5319.wav \ + sfx_531.wav \ + sfx_5320.wav \ + sfx_5321.wav \ + sfx_5322.wav \ + sfx_5323.wav \ + sfx_5324.wav \ + sfx_5325.wav \ + sfx_5326.wav \ + sfx_5327.wav \ + sfx_5328.wav \ + sfx_5329.wav \ + sfx_532.wav \ + sfx_5330.wav \ + sfx_5331.wav \ + sfx_5332.wav \ + sfx_5333.wav \ + sfx_5334.wav \ + sfx_5335.wav \ + sfx_5336.wav \ + sfx_5337.wav \ + sfx_5338.wav \ + sfx_5339.wav \ + sfx_533.wav \ + sfx_5340.wav \ + sfx_5341.wav \ + sfx_5342.wav \ + sfx_5343.wav \ + sfx_5344.wav \ + sfx_5345.wav \ + sfx_5346.wav \ + sfx_5347.wav \ + sfx_5348.wav \ + sfx_5349.wav \ + sfx_534.wav \ + sfx_5350.wav \ + sfx_5351.wav \ + sfx_5352.wav \ + sfx_5353.wav \ + sfx_5354.wav \ + sfx_5355.wav \ + sfx_5356.wav \ + sfx_5357.wav \ + sfx_5358.wav \ + sfx_5359.wav \ + sfx_535.wav \ + sfx_5360.wav \ + sfx_5361.wav \ + sfx_5362.wav \ + sfx_5363.wav \ + sfx_5364.wav \ + sfx_5365.wav \ + sfx_5366.wav \ + sfx_5367.wav \ + sfx_5368.wav \ + sfx_5369.wav \ + sfx_536.wav \ + sfx_5370.wav \ + sfx_5371.wav \ + sfx_5372.wav \ + sfx_5373.wav \ + sfx_5374.wav \ + sfx_5375.wav \ + sfx_5376.wav \ + sfx_5377.wav \ + sfx_5378.wav \ + sfx_5379.wav \ + sfx_537.wav \ + sfx_5380.wav \ + sfx_5381.wav \ + sfx_5382.wav \ + sfx_5383.wav \ + sfx_5384.wav \ + sfx_5385.wav \ + sfx_5386.wav \ + sfx_5387.wav \ + sfx_5388.wav \ + sfx_5389.wav \ + sfx_538.wav \ + sfx_5390.wav \ + sfx_5391.wav \ + sfx_5392.wav \ + sfx_5393.wav \ + sfx_5394.wav \ + sfx_5395.wav \ + sfx_5396.wav \ + sfx_5397.wav \ + sfx_5398.wav \ + sfx_5399.wav \ + sfx_539.wav \ + sfx_53.wav \ + sfx_5400.wav \ + sfx_5401.wav \ + sfx_5402.wav \ + sfx_5403.wav \ + sfx_5404.wav \ + sfx_5405.wav \ + sfx_5406.wav \ + sfx_5407.wav \ + sfx_5408.wav \ + sfx_5409.wav \ + sfx_540.wav \ + sfx_5410.wav \ + sfx_5411.wav \ + sfx_5412.wav \ + sfx_5413.wav \ + sfx_5414.wav \ + sfx_5415.wav \ + sfx_5416.wav \ + sfx_5417.wav \ + sfx_5418.wav \ + sfx_5419.wav \ + sfx_541.wav \ + sfx_5420.wav \ + sfx_5421.wav \ + sfx_5422.wav \ + sfx_5423.wav \ + sfx_5424.wav \ + sfx_5425.wav \ + sfx_5426.wav \ + sfx_5427.wav \ + sfx_5428.wav \ + sfx_5429.wav \ + sfx_542.wav \ + sfx_5430.wav \ + sfx_5431.wav \ + sfx_5432.wav \ + sfx_5433.wav \ + sfx_5434.wav \ + sfx_5435.wav \ + sfx_5436.wav \ + sfx_5437.wav \ + sfx_5438.wav \ + sfx_5439.wav \ + sfx_543.wav \ + sfx_5440.wav \ + sfx_5441.wav \ + sfx_5442.wav \ + sfx_5443.wav \ + sfx_5444.wav \ + sfx_5445.wav \ + sfx_5446.wav \ + sfx_5447.wav \ + sfx_5448.wav \ + sfx_5449.wav \ + sfx_544.wav \ + sfx_5450.wav \ + sfx_5451.wav \ + sfx_5452.wav \ + sfx_5453.wav \ + sfx_5454.wav \ + sfx_5455.wav \ + sfx_5456.wav \ + sfx_5457.wav \ + sfx_5458.wav \ + sfx_5459.wav \ + sfx_545.wav \ + sfx_5460.wav \ + sfx_5461.wav \ + sfx_5462.wav \ + sfx_5463.wav \ + sfx_5464.wav \ + sfx_5465.wav \ + sfx_5466.wav \ + sfx_5467.wav \ + sfx_5468.wav \ + sfx_5469.wav \ + sfx_546.wav \ + sfx_5470.wav \ + sfx_5471.wav \ + sfx_5472.wav \ + sfx_5473.wav \ + sfx_5474.wav \ + sfx_5475.wav \ + sfx_5476.wav \ + sfx_5477.wav \ + sfx_5478.wav \ + sfx_5479.wav \ + sfx_547.wav \ + sfx_5480.wav \ + sfx_5481.wav \ + sfx_5482.wav \ + sfx_5483.wav \ + sfx_5484.wav \ + sfx_5485.wav \ + sfx_5486.wav \ + sfx_5487.wav \ + sfx_5488.wav \ + sfx_5489.wav \ + sfx_548.wav \ + sfx_5490.wav \ + sfx_5491.wav \ + sfx_5492.wav \ + sfx_5493.wav \ + sfx_5494.wav \ + sfx_5495.wav \ + sfx_5496.wav \ + sfx_5497.wav \ + sfx_5498.wav \ + sfx_5499.wav \ + sfx_549.wav \ + sfx_54.wav \ + sfx_5500.wav \ + sfx_5501.wav \ + sfx_5502.wav \ + sfx_5503.wav \ + sfx_5504.wav \ + sfx_5505.wav \ + sfx_5506.wav \ + sfx_5507.wav \ + sfx_5508.wav \ + sfx_5509.wav \ + sfx_550.wav \ + sfx_5510.wav \ + sfx_5511.wav \ + sfx_5512.wav \ + sfx_5513.wav \ + sfx_5514.wav \ + sfx_5515.wav \ + sfx_5516.wav \ + sfx_5517.wav \ + sfx_5518.wav \ + sfx_5519.wav \ + sfx_551.wav \ + sfx_5520.wav \ + sfx_5521.wav \ + sfx_5522.wav \ + sfx_5523.wav \ + sfx_5524.wav \ + sfx_5525.wav \ + sfx_5526.wav \ + sfx_5527.wav \ + sfx_5528.wav \ + sfx_5529.wav \ + sfx_552.wav \ + sfx_5530.wav \ + sfx_5531.wav \ + sfx_5532.wav \ + sfx_5533.wav \ + sfx_5534.wav \ + sfx_5535.wav \ + sfx_5536.wav \ + sfx_5537.wav \ + sfx_5538.wav \ + sfx_5539.wav \ + sfx_553.wav \ + sfx_5540.wav \ + sfx_5541.wav \ + sfx_5542.wav \ + sfx_5543.wav \ + sfx_5544.wav \ + sfx_5545.wav \ + sfx_5546.wav \ + sfx_5547.wav \ + sfx_5548.wav \ + sfx_5549.wav \ + sfx_554.wav \ + sfx_5550.wav \ + sfx_5551.wav \ + sfx_5552.wav \ + sfx_5553.wav \ + sfx_5554.wav \ + sfx_5555.wav \ + sfx_5556.wav \ + sfx_5557.wav \ + sfx_5558.wav \ + sfx_5559.wav \ + sfx_555.wav \ + sfx_5560.wav \ + sfx_5561.wav \ + sfx_5562.wav \ + sfx_5563.wav \ + sfx_5564.wav \ + sfx_5565.wav \ + sfx_5566.wav \ + sfx_5567.wav \ + sfx_5568.wav \ + sfx_5569.wav \ + sfx_556.wav \ + sfx_5570.wav \ + sfx_5571.wav \ + sfx_5572.wav \ + sfx_5573.wav \ + sfx_5574.wav \ + sfx_5575.wav \ + sfx_5576.wav \ + sfx_5577.wav \ + sfx_5578.wav \ + sfx_5579.wav \ + sfx_557.wav \ + sfx_5580.wav \ + sfx_5581.wav \ + sfx_5582.wav \ + sfx_5583.wav \ + sfx_5584.wav \ + sfx_5585.wav \ + sfx_5586.wav \ + sfx_5587.wav \ + sfx_5588.wav \ + sfx_5589.wav \ + sfx_558.wav \ + sfx_5590.wav \ + sfx_5591.wav \ + sfx_5592.wav \ + sfx_5593.wav \ + sfx_5594.wav \ + sfx_5595.wav \ + sfx_5596.wav \ + sfx_5597.wav \ + sfx_5598.wav \ + sfx_5599.wav \ + sfx_559.wav \ + sfx_55.wav \ + sfx_5600.wav \ + sfx_5601.wav \ + sfx_5602.wav \ + sfx_5603.wav \ + sfx_5604.wav \ + sfx_5605.wav \ + sfx_5606.wav \ + sfx_5607.wav \ + sfx_5608.wav \ + sfx_5609.wav \ + sfx_560.wav \ + sfx_5610.wav \ + sfx_5611.wav \ + sfx_5612.wav \ + sfx_5613.wav \ + sfx_5614.wav \ + sfx_5615.wav \ + sfx_5616.wav \ + sfx_5617.wav \ + sfx_5618.wav \ + sfx_5619.wav \ + sfx_561.wav \ + sfx_5620.wav \ + sfx_5621.wav \ + sfx_5622.wav \ + sfx_5623.wav \ + sfx_5624.wav \ + sfx_5625.wav \ + sfx_5626.wav \ + sfx_5627.wav \ + sfx_5628.wav \ + sfx_5629.wav \ + sfx_562.wav \ + sfx_5630.wav \ + sfx_5631.wav \ + sfx_5632.wav \ + sfx_5633.wav \ + sfx_5634.wav \ + sfx_5635.wav \ + sfx_5636.wav \ + sfx_5637.wav \ + sfx_5638.wav \ + sfx_5639.wav \ + sfx_563.wav \ + sfx_5640.wav \ + sfx_5641.wav \ + sfx_5642.wav \ + sfx_5643.wav \ + sfx_5644.wav \ + sfx_5645.wav \ + sfx_5646.wav \ + sfx_5647.wav \ + sfx_5648.wav \ + sfx_5649.wav \ + sfx_564.wav \ + sfx_5650.wav \ + sfx_5651.wav \ + sfx_5652.wav \ + sfx_5653.wav \ + sfx_5654.wav \ + sfx_5655.wav \ + sfx_5656.wav \ + sfx_5657.wav \ + sfx_5658.wav \ + sfx_5659.wav \ + sfx_565.wav \ + sfx_5660.wav \ + sfx_5661.wav \ + sfx_5662.wav \ + sfx_5663.wav \ + sfx_5664.wav \ + sfx_5665.wav \ + sfx_5666.wav \ + sfx_5667.wav \ + sfx_5668.wav \ + sfx_5669.wav \ + sfx_566.wav \ + sfx_5670.wav \ + sfx_5671.wav \ + sfx_5672.wav \ + sfx_5673.wav \ + sfx_5674.wav \ + sfx_5675.wav \ + sfx_5676.wav \ + sfx_5677.wav \ + sfx_5678.wav \ + sfx_5679.wav \ + sfx_567.wav \ + sfx_5680.wav \ + sfx_5681.wav \ + sfx_5682.wav \ + sfx_5683.wav \ + sfx_5684.wav \ + sfx_5685.wav \ + sfx_5686.wav \ + sfx_5687.wav \ + sfx_5688.wav \ + sfx_5689.wav \ + sfx_568.wav \ + sfx_5690.wav \ + sfx_5691.wav \ + sfx_5692.wav \ + sfx_5693.wav \ + sfx_5694.wav \ + sfx_5695.wav \ + sfx_5696.wav \ + sfx_5697.wav \ + sfx_5698.wav \ + sfx_5699.wav \ + sfx_569.wav \ + sfx_56.wav \ + sfx_5700.wav \ + sfx_5701.wav \ + sfx_5702.wav \ + sfx_5703.wav \ + sfx_5704.wav \ + sfx_5705.wav \ + sfx_5706.wav \ + sfx_5707.wav \ + sfx_5708.wav \ + sfx_5709.wav \ + sfx_570.wav \ + sfx_5710.wav \ + sfx_5711.wav \ + sfx_5712.wav \ + sfx_5713.wav \ + sfx_5714.wav \ + sfx_5715.wav \ + sfx_5716.wav \ + sfx_5717.wav \ + sfx_5718.wav \ + sfx_5719.wav \ + sfx_571.wav \ + sfx_5720.wav \ + sfx_5721.wav \ + sfx_5722.wav \ + sfx_5723.wav \ + sfx_5724.wav \ + sfx_5725.wav \ + sfx_5726.wav \ + sfx_5727.wav \ + sfx_5728.wav \ + sfx_5729.wav \ + sfx_572.wav \ + sfx_5730.wav \ + sfx_5731.wav \ + sfx_5732.wav \ + sfx_5733.wav \ + sfx_5734.wav \ + sfx_5735.wav \ + sfx_5736.wav \ + sfx_5737.wav \ + sfx_5738.wav \ + sfx_5739.wav \ + sfx_573.wav \ + sfx_5740.wav \ + sfx_5741.wav \ + sfx_5742.wav \ + sfx_5743.wav \ + sfx_5744.wav \ + sfx_5745.wav \ + sfx_5746.wav \ + sfx_5747.wav \ + sfx_5748.wav \ + sfx_5749.wav \ + sfx_574.wav \ + sfx_5750.wav \ + sfx_5751.wav \ + sfx_5752.wav \ + sfx_5753.wav \ + sfx_5754.wav \ + sfx_5755.wav \ + sfx_5756.wav \ + sfx_5757.wav \ + sfx_5758.wav \ + sfx_5759.wav \ + sfx_575.wav \ + sfx_5760.wav \ + sfx_5761.wav \ + sfx_5762.wav \ + sfx_5763.wav \ + sfx_5764.wav \ + sfx_5765.wav \ + sfx_5766.wav \ + sfx_5767.wav \ + sfx_5768.wav \ + sfx_5769.wav \ + sfx_576.wav \ + sfx_5770.wav \ + sfx_5771.wav \ + sfx_5772.wav \ + sfx_5773.wav \ + sfx_5774.wav \ + sfx_5775.wav \ + sfx_5776.wav \ + sfx_5777.wav \ + sfx_5778.wav \ + sfx_5779.wav \ + sfx_577.wav \ + sfx_5780.wav \ + sfx_5781.wav \ + sfx_5782.wav \ + sfx_5783.wav \ + sfx_5784.wav \ + sfx_5785.wav \ + sfx_5786.wav \ + sfx_5787.wav \ + sfx_5788.wav \ + sfx_5789.wav \ + sfx_578.wav \ + sfx_5790.wav \ + sfx_5791.wav \ + sfx_5792.wav \ + sfx_5793.wav \ + sfx_5794.wav \ + sfx_5795.wav \ + sfx_5796.wav \ + sfx_5797.wav \ + sfx_5798.wav \ + sfx_5799.wav \ + sfx_579.wav \ + sfx_57.wav \ + sfx_5800.wav \ + sfx_5801.wav \ + sfx_5802.wav \ + sfx_5803.wav \ + sfx_5804.wav \ + sfx_5805.wav \ + sfx_5806.wav \ + sfx_5807.wav \ + sfx_5808.wav \ + sfx_5809.wav \ + sfx_580.wav \ + sfx_5810.wav \ + sfx_5811.wav \ + sfx_5812.wav \ + sfx_5813.wav \ + sfx_5814.wav \ + sfx_5815.wav \ + sfx_5816.wav \ + sfx_5817.wav \ + sfx_5818.wav \ + sfx_5819.wav \ + sfx_581.wav \ + sfx_5820.wav \ + sfx_5821.wav \ + sfx_5822.wav \ + sfx_5823.wav \ + sfx_5824.wav \ + sfx_5825.wav \ + sfx_5826.wav \ + sfx_5827.wav \ + sfx_5828.wav \ + sfx_5829.wav \ + sfx_582.wav \ + sfx_5830.wav \ + sfx_5831.wav \ + sfx_5832.wav \ + sfx_5833.wav \ + sfx_5834.wav \ + sfx_5835.wav \ + sfx_5836.wav \ + sfx_5837.wav \ + sfx_5838.wav \ + sfx_5839.wav \ + sfx_583.wav \ + sfx_5840.wav \ + sfx_5841.wav \ + sfx_5842.wav \ + sfx_5843.wav \ + sfx_5844.wav \ + sfx_5845.wav \ + sfx_5846.wav \ + sfx_5847.wav \ + sfx_5848.wav \ + sfx_5849.wav \ + sfx_584.wav \ + sfx_5850.wav \ + sfx_5851.wav \ + sfx_5852.wav \ + sfx_5853.wav \ + sfx_5854.wav \ + sfx_5855.wav \ + sfx_5856.wav \ + sfx_5857.wav \ + sfx_5858.wav \ + sfx_5859.wav \ + sfx_585.wav \ + sfx_5860.wav \ + sfx_5861.wav \ + sfx_5862.wav \ + sfx_5863.wav \ + sfx_5864.wav \ + sfx_5865.wav \ + sfx_5866.wav \ + sfx_5867.wav \ + sfx_5868.wav \ + sfx_5869.wav \ + sfx_586.wav \ + sfx_5870.wav \ + sfx_5871.wav \ + sfx_5872.wav \ + sfx_5873.wav \ + sfx_5874.wav \ + sfx_5875.wav \ + sfx_5876.wav \ + sfx_5877.wav \ + sfx_5878.wav \ + sfx_5879.wav \ + sfx_587.wav \ + sfx_5880.wav \ + sfx_5881.wav \ + sfx_5882.wav \ + sfx_5883.wav \ + sfx_5884.wav \ + sfx_5885.wav \ + sfx_5886.wav \ + sfx_5887.wav \ + sfx_5888.wav \ + sfx_5889.wav \ + sfx_588.wav \ + sfx_5890.wav \ + sfx_5891.wav \ + sfx_5892.wav \ + sfx_5893.wav \ + sfx_5894.wav \ + sfx_5895.wav \ + sfx_5896.wav \ + sfx_5897.wav \ + sfx_5898.wav \ + sfx_5899.wav \ + sfx_589.wav \ + sfx_58.wav \ + sfx_5900.wav \ + sfx_5901.wav \ + sfx_5902.wav \ + sfx_5903.wav \ + sfx_5904.wav \ + sfx_5905.wav \ + sfx_5906.wav \ + sfx_5907.wav \ + sfx_5908.wav \ + sfx_5909.wav \ + sfx_590.wav \ + sfx_5910.wav \ + sfx_5911.wav \ + sfx_5912.wav \ + sfx_5913.wav \ + sfx_5914.wav \ + sfx_5915.wav \ + sfx_5916.wav \ + sfx_5917.wav \ + sfx_5918.wav \ + sfx_5919.wav \ + sfx_591.wav \ + sfx_5920.wav \ + sfx_5921.wav \ + sfx_5922.wav \ + sfx_5923.wav \ + sfx_5924.wav \ + sfx_5925.wav \ + sfx_5926.wav \ + sfx_5927.wav \ + sfx_5928.wav \ + sfx_5929.wav \ + sfx_592.wav \ + sfx_5930.wav \ + sfx_5931.wav \ + sfx_5932.wav \ + sfx_5933.wav \ + sfx_5934.wav \ + sfx_5935.wav \ + sfx_5936.wav \ + sfx_5937.wav \ + sfx_5938.wav \ + sfx_5939.wav \ + sfx_593.wav \ + sfx_5940.wav \ + sfx_5941.wav \ + sfx_5942.wav \ + sfx_5943.wav \ + sfx_5944.wav \ + sfx_5945.wav \ + sfx_5946.wav \ + sfx_5947.wav \ + sfx_5948.wav \ + sfx_5949.wav \ + sfx_594.wav \ + sfx_5950.wav \ + sfx_5951.wav \ + sfx_5952.wav \ + sfx_5953.wav \ + sfx_5954.wav \ + sfx_5955.wav \ + sfx_5956.wav \ + sfx_5957.wav \ + sfx_5958.wav \ + sfx_5959.wav \ + sfx_595.wav \ + sfx_5960.wav \ + sfx_5961.wav \ + sfx_5962.wav \ + sfx_5963.wav \ + sfx_5964.wav \ + sfx_5965.wav \ + sfx_5966.wav \ + sfx_5967.wav \ + sfx_5968.wav \ + sfx_5969.wav \ + sfx_596.wav \ + sfx_5970.wav \ + sfx_5971.wav \ + sfx_5972.wav \ + sfx_5973.wav \ + sfx_5974.wav \ + sfx_5975.wav \ + sfx_5976.wav \ + sfx_5977.wav \ + sfx_5978.wav \ + sfx_5979.wav \ + sfx_597.wav \ + sfx_5980.wav \ + sfx_5981.wav \ + sfx_5982.wav \ + sfx_5983.wav \ + sfx_5984.wav \ + sfx_5985.wav \ + sfx_5986.wav \ + sfx_5987.wav \ + sfx_5988.wav \ + sfx_5989.wav \ + sfx_598.wav \ + sfx_5990.wav \ + sfx_5991.wav \ + sfx_5992.wav \ + sfx_5993.wav \ + sfx_5994.wav \ + sfx_5995.wav \ + sfx_5996.wav \ + sfx_5997.wav \ + sfx_5998.wav \ + sfx_5999.wav \ + sfx_599.wav \ + sfx_59.wav \ + sfx_5.wav \ + sfx_6000.wav \ + sfx_6001.wav \ + sfx_6002.wav \ + sfx_6003.wav \ + sfx_6004.wav \ + sfx_6005.wav \ + sfx_6006.wav \ + sfx_6007.wav \ + sfx_6008.wav \ + sfx_6009.wav \ + sfx_600.wav \ + sfx_6010.wav \ + sfx_6011.wav \ + sfx_6012.wav \ + sfx_6013.wav \ + sfx_6014.wav \ + sfx_6015.wav \ + sfx_6016.wav \ + sfx_6017.wav \ + sfx_6018.wav \ + sfx_6019.wav \ + sfx_601.wav \ + sfx_6020.wav \ + sfx_6021.wav \ + sfx_6022.wav \ + sfx_6023.wav \ + sfx_6024.wav \ + sfx_6025.wav \ + sfx_6026.wav \ + sfx_6027.wav \ + sfx_6028.wav \ + sfx_6029.wav \ + sfx_602.wav \ + sfx_6030.wav \ + sfx_6031.wav \ + sfx_6032.wav \ + sfx_6033.wav \ + sfx_6034.wav \ + sfx_6035.wav \ + sfx_6036.wav \ + sfx_6037.wav \ + sfx_6038.wav \ + sfx_6039.wav \ + sfx_603.wav \ + sfx_6040.wav \ + sfx_6041.wav \ + sfx_6042.wav \ + sfx_6043.wav \ + sfx_6044.wav \ + sfx_6045.wav \ + sfx_6046.wav \ + sfx_6047.wav \ + sfx_6048.wav \ + sfx_6049.wav \ + sfx_604.wav \ + sfx_6050.wav \ + sfx_6051.wav \ + sfx_6052.wav \ + sfx_6053.wav \ + sfx_6054.wav \ + sfx_6055.wav \ + sfx_6056.wav \ + sfx_6057.wav \ + sfx_6058.wav \ + sfx_6059.wav \ + sfx_605.wav \ + sfx_6060.wav \ + sfx_6061.wav \ + sfx_6062.wav \ + sfx_6063.wav \ + sfx_6064.wav \ + sfx_6065.wav \ + sfx_6066.wav \ + sfx_6067.wav \ + sfx_6068.wav \ + sfx_6069.wav \ + sfx_606.wav \ + sfx_6070.wav \ + sfx_6071.wav \ + sfx_6072.wav \ + sfx_6073.wav \ + sfx_6074.wav \ + sfx_6075.wav \ + sfx_6076.wav \ + sfx_6077.wav \ + sfx_6078.wav \ + sfx_6079.wav \ + sfx_607.wav \ + sfx_6080.wav \ + sfx_6081.wav \ + sfx_6082.wav \ + sfx_6083.wav \ + sfx_6084.wav \ + sfx_6085.wav \ + sfx_6086.wav \ + sfx_6087.wav \ + sfx_6088.wav \ + sfx_6089.wav \ + sfx_608.wav \ + sfx_6090.wav \ + sfx_6091.wav \ + sfx_6092.wav \ + sfx_6093.wav \ + sfx_6094.wav \ + sfx_6095.wav \ + sfx_6096.wav \ + sfx_6097.wav \ + sfx_6098.wav \ + sfx_6099.wav \ + sfx_609.wav \ + sfx_60.wav \ + sfx_6100.wav \ + sfx_6101.wav \ + sfx_6102.wav \ + sfx_6103.wav \ + sfx_6104.wav \ + sfx_6105.wav \ + sfx_6106.wav \ + sfx_6107.wav \ + sfx_6108.wav \ + sfx_6109.wav \ + sfx_610.wav \ + sfx_6110.wav \ + sfx_6111.wav \ + sfx_6112.wav \ + sfx_6113.wav \ + sfx_6114.wav \ + sfx_6115.wav \ + sfx_6116.wav \ + sfx_6117.wav \ + sfx_6118.wav \ + sfx_6119.wav \ + sfx_611.wav \ + sfx_6120.wav \ + sfx_6121.wav \ + sfx_6122.wav \ + sfx_6123.wav \ + sfx_6124.wav \ + sfx_6125.wav \ + sfx_6126.wav \ + sfx_6127.wav \ + sfx_6128.wav \ + sfx_6129.wav \ + sfx_612.wav \ + sfx_6130.wav \ + sfx_6131.wav \ + sfx_6132.wav \ + sfx_6133.wav \ + sfx_6134.wav \ + sfx_6135.wav \ + sfx_6136.wav \ + sfx_6137.wav \ + sfx_6138.wav \ + sfx_6139.wav \ + sfx_613.wav \ + sfx_6140.wav \ + sfx_6141.wav \ + sfx_6142.wav \ + sfx_6143.wav \ + sfx_6144.wav \ + sfx_6145.wav \ + sfx_6146.wav \ + sfx_6147.wav \ + sfx_6148.wav \ + sfx_6149.wav \ + sfx_614.wav \ + sfx_6150.wav \ + sfx_6151.wav \ + sfx_6152.wav \ + sfx_6153.wav \ + sfx_6154.wav \ + sfx_6155.wav \ + sfx_6156.wav \ + sfx_6157.wav \ + sfx_6158.wav \ + sfx_6159.wav \ + sfx_615.wav \ + sfx_6160.wav \ + sfx_6161.wav \ + sfx_6162.wav \ + sfx_6163.wav \ + sfx_6164.wav \ + sfx_6165.wav \ + sfx_6166.wav \ + sfx_6167.wav \ + sfx_6168.wav \ + sfx_6169.wav \ + sfx_616.wav \ + sfx_6170.wav \ + sfx_6171.wav \ + sfx_6172.wav \ + sfx_6173.wav \ + sfx_6174.wav \ + sfx_6175.wav \ + sfx_6176.wav \ + sfx_6177.wav \ + sfx_6178.wav \ + sfx_6179.wav \ + sfx_617.wav \ + sfx_6180.wav \ + sfx_6181.wav \ + sfx_6182.wav \ + sfx_6183.wav \ + sfx_6184.wav \ + sfx_6185.wav \ + sfx_6186.wav \ + sfx_6187.wav \ + sfx_6188.wav \ + sfx_6189.wav \ + sfx_618.wav \ + sfx_6190.wav \ + sfx_6191.wav \ + sfx_6192.wav \ + sfx_6193.wav \ + sfx_6194.wav \ + sfx_6195.wav \ + sfx_6196.wav \ + sfx_6197.wav \ + sfx_6198.wav \ + sfx_6199.wav \ + sfx_619.wav \ + sfx_61.wav \ + sfx_6200.wav \ + sfx_6201.wav \ + sfx_6202.wav \ + sfx_6203.wav \ + sfx_6204.wav \ + sfx_6205.wav \ + sfx_6206.wav \ + sfx_6207.wav \ + sfx_6208.wav \ + sfx_6209.wav \ + sfx_620.wav \ + sfx_6210.wav \ + sfx_6211.wav \ + sfx_6212.wav \ + sfx_6213.wav \ + sfx_6214.wav \ + sfx_6215.wav \ + sfx_6216.wav \ + sfx_6217.wav \ + sfx_6218.wav \ + sfx_6219.wav \ + sfx_621.wav \ + sfx_6220.wav \ + sfx_6221.wav \ + sfx_6222.wav \ + sfx_6223.wav \ + sfx_6224.wav \ + sfx_6225.wav \ + sfx_6226.wav \ + sfx_6227.wav \ + sfx_6228.wav \ + sfx_6229.wav \ + sfx_622.wav \ + sfx_6230.wav \ + sfx_6231.wav \ + sfx_6232.wav \ + sfx_6233.wav \ + sfx_6234.wav \ + sfx_6235.wav \ + sfx_6236.wav \ + sfx_6237.wav \ + sfx_6238.wav \ + sfx_6239.wav \ + sfx_623.wav \ + sfx_6240.wav \ + sfx_6241.wav \ + sfx_6242.wav \ + sfx_6243.wav \ + sfx_6244.wav \ + sfx_6245.wav \ + sfx_6246.wav \ + sfx_6247.wav \ + sfx_6248.wav \ + sfx_6249.wav \ + sfx_624.wav \ + sfx_6250.wav \ + sfx_6251.wav \ + sfx_6252.wav \ + sfx_6253.wav \ + sfx_6254.wav \ + sfx_6255.wav \ + sfx_6256.wav \ + sfx_6257.wav \ + sfx_6258.wav \ + sfx_6259.wav \ + sfx_625.wav \ + sfx_6260.wav \ + sfx_6261.wav \ + sfx_6262.wav \ + sfx_6263.wav \ + sfx_6264.wav \ + sfx_6265.wav \ + sfx_6266.wav \ + sfx_6267.wav \ + sfx_6268.wav \ + sfx_6269.wav \ + sfx_626.wav \ + sfx_6270.wav \ + sfx_6271.wav \ + sfx_6272.wav \ + sfx_6273.wav \ + sfx_6274.wav \ + sfx_6275.wav \ + sfx_6276.wav \ + sfx_6277.wav \ + sfx_6278.wav \ + sfx_6279.wav \ + sfx_627.wav \ + sfx_6280.wav \ + sfx_6281.wav \ + sfx_6282.wav \ + sfx_6283.wav \ + sfx_6284.wav \ + sfx_6285.wav \ + sfx_6286.wav \ + sfx_6287.wav \ + sfx_6288.wav \ + sfx_6289.wav \ + sfx_628.wav \ + sfx_6290.wav \ + sfx_6291.wav \ + sfx_6292.wav \ + sfx_6293.wav \ + sfx_6294.wav \ + sfx_6295.wav \ + sfx_6296.wav \ + sfx_6297.wav \ + sfx_6298.wav \ + sfx_6299.wav \ + sfx_629.wav \ + sfx_62.wav \ + sfx_6300.wav \ + sfx_6301.wav \ + sfx_6302.wav \ + sfx_6303.wav \ + sfx_6304.wav \ + sfx_6305.wav \ + sfx_6306.wav \ + sfx_6307.wav \ + sfx_6308.wav \ + sfx_6309.wav \ + sfx_630.wav \ + sfx_6310.wav \ + sfx_6311.wav \ + sfx_6312.wav \ + sfx_6313.wav \ + sfx_6314.wav \ + sfx_6315.wav \ + sfx_6316.wav \ + sfx_6317.wav \ + sfx_6318.wav \ + sfx_6319.wav \ + sfx_631.wav \ + sfx_6320.wav \ + sfx_6321.wav \ + sfx_6322.wav \ + sfx_6323.wav \ + sfx_6324.wav \ + sfx_6325.wav \ + sfx_6326.wav \ + sfx_6327.wav \ + sfx_6328.wav \ + sfx_6329.wav \ + sfx_632.wav \ + sfx_6330.wav \ + sfx_6331.wav \ + sfx_6332.wav \ + sfx_6333.wav \ + sfx_6334.wav \ + sfx_6335.wav \ + sfx_6336.wav \ + sfx_6337.wav \ + sfx_6338.wav \ + sfx_6339.wav \ + sfx_633.wav \ + sfx_6340.wav \ + sfx_6341.wav \ + sfx_6342.wav \ + sfx_6343.wav \ + sfx_6344.wav \ + sfx_6345.wav \ + sfx_6346.wav \ + sfx_6347.wav \ + sfx_6348.wav \ + sfx_6349.wav \ + sfx_634.wav \ + sfx_6350.wav \ + sfx_6351.wav \ + sfx_6352.wav \ + sfx_6353.wav \ + sfx_6354.wav \ + sfx_6355.wav \ + sfx_6356.wav \ + sfx_6357.wav \ + sfx_6358.wav \ + sfx_6359.wav \ + sfx_635.wav \ + sfx_6360.wav \ + sfx_6361.wav \ + sfx_6362.wav \ + sfx_6363.wav \ + sfx_6364.wav \ + sfx_6365.wav \ + sfx_6366.wav \ + sfx_6367.wav \ + sfx_6368.wav \ + sfx_6369.wav \ + sfx_636.wav \ + sfx_6370.wav \ + sfx_6371.wav \ + sfx_6372.wav \ + sfx_6373.wav \ + sfx_6374.wav \ + sfx_6375.wav \ + sfx_6376.wav \ + sfx_6377.wav \ + sfx_6378.wav \ + sfx_6379.wav \ + sfx_637.wav \ + sfx_6380.wav \ + sfx_6381.wav \ + sfx_6382.wav \ + sfx_6383.wav \ + sfx_6384.wav \ + sfx_6385.wav \ + sfx_6386.wav \ + sfx_6387.wav \ + sfx_6388.wav \ + sfx_6389.wav \ + sfx_638.wav \ + sfx_6390.wav \ + sfx_6391.wav \ + sfx_6392.wav \ + sfx_6393.wav \ + sfx_6394.wav \ + sfx_6395.wav \ + sfx_6396.wav \ + sfx_6397.wav \ + sfx_6398.wav \ + sfx_6399.wav \ + sfx_639.wav \ + sfx_63.wav \ + sfx_6400.wav \ + sfx_6401.wav \ + sfx_6402.wav \ + sfx_6403.wav \ + sfx_6404.wav \ + sfx_6405.wav \ + sfx_6406.wav \ + sfx_6407.wav \ + sfx_6408.wav \ + sfx_6409.wav \ + sfx_640.wav \ + sfx_6410.wav \ + sfx_6411.wav \ + sfx_6412.wav \ + sfx_6413.wav \ + sfx_6414.wav \ + sfx_6415.wav \ + sfx_6416.wav \ + sfx_6417.wav \ + sfx_6418.wav \ + sfx_6419.wav \ + sfx_641.wav \ + sfx_6420.wav \ + sfx_6421.wav \ + sfx_6422.wav \ + sfx_6423.wav \ + sfx_6424.wav \ + sfx_6425.wav \ + sfx_6426.wav \ + sfx_6427.wav \ + sfx_6428.wav \ + sfx_6429.wav \ + sfx_642.wav \ + sfx_6430.wav \ + sfx_6431.wav \ + sfx_6432.wav \ + sfx_6433.wav \ + sfx_6434.wav \ + sfx_6435.wav \ + sfx_6436.wav \ + sfx_6437.wav \ + sfx_6438.wav \ + sfx_6439.wav \ + sfx_643.wav \ + sfx_6440.wav \ + sfx_6441.wav \ + sfx_6442.wav \ + sfx_6443.wav \ + sfx_6444.wav \ + sfx_6445.wav \ + sfx_6446.wav \ + sfx_6447.wav \ + sfx_6448.wav \ + sfx_6449.wav \ + sfx_644.wav \ + sfx_6450.wav \ + sfx_6451.wav \ + sfx_6452.wav \ + sfx_6453.wav \ + sfx_6454.wav \ + sfx_6455.wav \ + sfx_6456.wav \ + sfx_6457.wav \ + sfx_6458.wav \ + sfx_6459.wav \ + sfx_645.wav \ + sfx_6460.wav \ + sfx_6461.wav \ + sfx_6462.wav \ + sfx_6463.wav \ + sfx_6464.wav \ + sfx_6465.wav \ + sfx_6466.wav \ + sfx_6467.wav \ + sfx_6468.wav \ + sfx_6469.wav \ + sfx_646.wav \ + sfx_6470.wav \ + sfx_6471.wav \ + sfx_6472.wav \ + sfx_6473.wav \ + sfx_6474.wav \ + sfx_6475.wav \ + sfx_6476.wav \ + sfx_6477.wav \ + sfx_6478.wav \ + sfx_6479.wav \ + sfx_647.wav \ + sfx_6480.wav \ + sfx_6481.wav \ + sfx_6482.wav \ + sfx_6483.wav \ + sfx_6484.wav \ + sfx_6485.wav \ + sfx_6486.wav \ + sfx_6487.wav \ + sfx_6488.wav \ + sfx_6489.wav \ + sfx_648.wav \ + sfx_6490.wav \ + sfx_6491.wav \ + sfx_6492.wav \ + sfx_6493.wav \ + sfx_6494.wav \ + sfx_6495.wav \ + sfx_6496.wav \ + sfx_6497.wav \ + sfx_6498.wav \ + sfx_6499.wav \ + sfx_649.wav \ + sfx_64.wav \ + sfx_6500.wav \ + sfx_6501.wav \ + sfx_6502.wav \ + sfx_6503.wav \ + sfx_6504.wav \ + sfx_6505.wav \ + sfx_6506.wav \ + sfx_6507.wav \ + sfx_6508.wav \ + sfx_6509.wav \ + sfx_650.wav \ + sfx_6510.wav \ + sfx_6511.wav \ + sfx_6512.wav \ + sfx_6513.wav \ + sfx_6514.wav \ + sfx_6515.wav \ + sfx_6516.wav \ + sfx_6517.wav \ + sfx_6518.wav \ + sfx_6519.wav \ + sfx_651.wav \ + sfx_6520.wav \ + sfx_6521.wav \ + sfx_6522.wav \ + sfx_6523.wav \ + sfx_6524.wav \ + sfx_6525.wav \ + sfx_6526.wav \ + sfx_6527.wav \ + sfx_6528.wav \ + sfx_6529.wav \ + sfx_652.wav \ + sfx_6530.wav \ + sfx_6531.wav \ + sfx_6532.wav \ + sfx_6533.wav \ + sfx_6534.wav \ + sfx_6535.wav \ + sfx_6536.wav \ + sfx_6537.wav \ + sfx_6538.wav \ + sfx_6539.wav \ + sfx_653.wav \ + sfx_6540.wav \ + sfx_6541.wav \ + sfx_6542.wav \ + sfx_6543.wav \ + sfx_6544.wav \ + sfx_6545.wav \ + sfx_6546.wav \ + sfx_6547.wav \ + sfx_6548.wav \ + sfx_6549.wav \ + sfx_654.wav \ + sfx_6550.wav \ + sfx_6551.wav \ + sfx_6552.wav \ + sfx_6553.wav \ + sfx_6554.wav \ + sfx_6555.wav \ + sfx_6556.wav \ + sfx_6557.wav \ + sfx_6558.wav \ + sfx_6559.wav \ + sfx_655.wav \ + sfx_6560.wav \ + sfx_6561.wav \ + sfx_6562.wav \ + sfx_6563.wav \ + sfx_6564.wav \ + sfx_6565.wav \ + sfx_6566.wav \ + sfx_6567.wav \ + sfx_6568.wav \ + sfx_6569.wav \ + sfx_656.wav \ + sfx_6570.wav \ + sfx_6571.wav \ + sfx_6572.wav \ + sfx_6573.wav \ + sfx_6574.wav \ + sfx_6575.wav \ + sfx_6576.wav \ + sfx_6577.wav \ + sfx_6578.wav \ + sfx_6579.wav \ + sfx_657.wav \ + sfx_6580.wav \ + sfx_6581.wav \ + sfx_6582.wav \ + sfx_6583.wav \ + sfx_6584.wav \ + sfx_6585.wav \ + sfx_6586.wav \ + sfx_6587.wav \ + sfx_6588.wav \ + sfx_6589.wav \ + sfx_658.wav \ + sfx_6590.wav \ + sfx_6591.wav \ + sfx_6592.wav \ + sfx_6593.wav \ + sfx_6594.wav \ + sfx_6595.wav \ + sfx_6596.wav \ + sfx_6597.wav \ + sfx_6598.wav \ + sfx_6599.wav \ + sfx_659.wav \ + sfx_65.wav \ + sfx_6600.wav \ + sfx_6601.wav \ + sfx_6602.wav \ + sfx_6603.wav \ + sfx_6604.wav \ + sfx_6605.wav \ + sfx_6606.wav \ + sfx_6607.wav \ + sfx_6608.wav \ + sfx_6609.wav \ + sfx_660.wav \ + sfx_6610.wav \ + sfx_6611.wav \ + sfx_6612.wav \ + sfx_6613.wav \ + sfx_6614.wav \ + sfx_6615.wav \ + sfx_6616.wav \ + sfx_6617.wav \ + sfx_6618.wav \ + sfx_6619.wav \ + sfx_661.wav \ + sfx_6620.wav \ + sfx_6621.wav \ + sfx_6622.wav \ + sfx_6623.wav \ + sfx_6624.wav \ + sfx_6625.wav \ + sfx_6626.wav \ + sfx_6627.wav \ + sfx_6628.wav \ + sfx_6629.wav \ + sfx_662.wav \ + sfx_6630.wav \ + sfx_6631.wav \ + sfx_6632.wav \ + sfx_6633.wav \ + sfx_6634.wav \ + sfx_6635.wav \ + sfx_6636.wav \ + sfx_6637.wav \ + sfx_6638.wav \ + sfx_6639.wav \ + sfx_663.wav \ + sfx_6640.wav \ + sfx_6641.wav \ + sfx_6642.wav \ + sfx_6643.wav \ + sfx_6644.wav \ + sfx_6645.wav \ + sfx_6646.wav \ + sfx_6647.wav \ + sfx_6648.wav \ + sfx_6649.wav \ + sfx_664.wav \ + sfx_6650.wav \ + sfx_6651.wav \ + sfx_6652.wav \ + sfx_6653.wav \ + sfx_6654.wav \ + sfx_6655.wav \ + sfx_6656.wav \ + sfx_6657.wav \ + sfx_6658.wav \ + sfx_6659.wav \ + sfx_665.wav \ + sfx_6660.wav \ + sfx_6661.wav \ + sfx_6662.wav \ + sfx_6663.wav \ + sfx_6664.wav \ + sfx_6665.wav \ + sfx_6666.wav \ + sfx_6667.wav \ + sfx_6668.wav \ + sfx_6669.wav \ + sfx_666.wav \ + sfx_6670.wav \ + sfx_6671.wav \ + sfx_6672.wav \ + sfx_6673.wav \ + sfx_6674.wav \ + sfx_6675.wav \ + sfx_6676.wav \ + sfx_6677.wav \ + sfx_6678.wav \ + sfx_6679.wav \ + sfx_667.wav \ + sfx_6680.wav \ + sfx_6681.wav \ + sfx_6682.wav \ + sfx_6683.wav \ + sfx_6684.wav \ + sfx_6685.wav \ + sfx_6686.wav \ + sfx_6687.wav \ + sfx_6688.wav \ + sfx_6689.wav \ + sfx_668.wav \ + sfx_6690.wav \ + sfx_6691.wav \ + sfx_6692.wav \ + sfx_6693.wav \ + sfx_6694.wav \ + sfx_6695.wav \ + sfx_6696.wav \ + sfx_6697.wav \ + sfx_6698.wav \ + sfx_6699.wav \ + sfx_669.wav \ + sfx_66.wav \ + sfx_6700.wav \ + sfx_6701.wav \ + sfx_6702.wav \ + sfx_6703.wav \ + sfx_6704.wav \ + sfx_6705.wav \ + sfx_6706.wav \ + sfx_6707.wav \ + sfx_6708.wav \ + sfx_6709.wav \ + sfx_670.wav \ + sfx_6710.wav \ + sfx_6711.wav \ + sfx_6712.wav \ + sfx_6713.wav \ + sfx_6714.wav \ + sfx_6715.wav \ + sfx_6716.wav \ + sfx_6717.wav \ + sfx_6718.wav \ + sfx_6719.wav \ + sfx_671.wav \ + sfx_6720.wav \ + sfx_6721.wav \ + sfx_6722.wav \ + sfx_6723.wav \ + sfx_6724.wav \ + sfx_6725.wav \ + sfx_6726.wav \ + sfx_6727.wav \ + sfx_6728.wav \ + sfx_6729.wav \ + sfx_672.wav \ + sfx_6730.wav \ + sfx_6731.wav \ + sfx_6732.wav \ + sfx_6733.wav \ + sfx_6734.wav \ + sfx_6735.wav \ + sfx_6736.wav \ + sfx_6737.wav \ + sfx_6738.wav \ + sfx_6739.wav \ + sfx_673.wav \ + sfx_6740.wav \ + sfx_6741.wav \ + sfx_6742.wav \ + sfx_6743.wav \ + sfx_6744.wav \ + sfx_6745.wav \ + sfx_6746.wav \ + sfx_6747.wav \ + sfx_6748.wav \ + sfx_6749.wav \ + sfx_674.wav \ + sfx_6750.wav \ + sfx_6751.wav \ + sfx_6752.wav \ + sfx_6753.wav \ + sfx_6754.wav \ + sfx_6755.wav \ + sfx_6756.wav \ + sfx_6757.wav \ + sfx_6758.wav \ + sfx_6759.wav \ + sfx_675.wav \ + sfx_6760.wav \ + sfx_6761.wav \ + sfx_6762.wav \ + sfx_6763.wav \ + sfx_6764.wav \ + sfx_6765.wav \ + sfx_6766.wav \ + sfx_6767.wav \ + sfx_6768.wav \ + sfx_6769.wav \ + sfx_676.wav \ + sfx_6770.wav \ + sfx_6771.wav \ + sfx_6772.wav \ + sfx_6773.wav \ + sfx_6774.wav \ + sfx_6775.wav \ + sfx_6776.wav \ + sfx_6777.wav \ + sfx_6778.wav \ + sfx_6779.wav \ + sfx_677.wav \ + sfx_6780.wav \ + sfx_6781.wav \ + sfx_6782.wav \ + sfx_6783.wav \ + sfx_6784.wav \ + sfx_6785.wav \ + sfx_6786.wav \ + sfx_6787.wav \ + sfx_6788.wav \ + sfx_6789.wav \ + sfx_678.wav \ + sfx_6790.wav \ + sfx_6791.wav \ + sfx_6792.wav \ + sfx_6793.wav \ + sfx_6794.wav \ + sfx_6795.wav \ + sfx_6796.wav \ + sfx_6797.wav \ + sfx_6798.wav \ + sfx_6799.wav \ + sfx_679.wav \ + sfx_67.wav \ + sfx_6800.wav \ + sfx_6801.wav \ + sfx_6802.wav \ + sfx_6803.wav \ + sfx_6804.wav \ + sfx_6805.wav \ + sfx_6806.wav \ + sfx_6807.wav \ + sfx_6808.wav \ + sfx_6809.wav \ + sfx_680.wav \ + sfx_6810.wav \ + sfx_6811.wav \ + sfx_6812.wav \ + sfx_6813.wav \ + sfx_6814.wav \ + sfx_6815.wav \ + sfx_6816.wav \ + sfx_6817.wav \ + sfx_6818.wav \ + sfx_6819.wav \ + sfx_681.wav \ + sfx_6820.wav \ + sfx_6821.wav \ + sfx_6822.wav \ + sfx_6823.wav \ + sfx_6824.wav \ + sfx_6825.wav \ + sfx_6826.wav \ + sfx_6827.wav \ + sfx_6828.wav \ + sfx_6829.wav \ + sfx_682.wav \ + sfx_6830.wav \ + sfx_6831.wav \ + sfx_6832.wav \ + sfx_6833.wav \ + sfx_6834.wav \ + sfx_6835.wav \ + sfx_6836.wav \ + sfx_6837.wav \ + sfx_6838.wav \ + sfx_6839.wav \ + sfx_683.wav \ + sfx_6840.wav \ + sfx_6841.wav \ + sfx_6842.wav \ + sfx_6843.wav \ + sfx_6844.wav \ + sfx_6845.wav \ + sfx_6846.wav \ + sfx_6847.wav \ + sfx_6848.wav \ + sfx_6849.wav \ + sfx_684.wav \ + sfx_6850.wav \ + sfx_6851.wav \ + sfx_6852.wav \ + sfx_6853.wav \ + sfx_6854.wav \ + sfx_6855.wav \ + sfx_6856.wav \ + sfx_6857.wav \ + sfx_6858.wav \ + sfx_6859.wav \ + sfx_685.wav \ + sfx_6860.wav \ + sfx_6861.wav \ + sfx_6862.wav \ + sfx_6863.wav \ + sfx_6864.wav \ + sfx_6865.wav \ + sfx_6866.wav \ + sfx_6867.wav \ + sfx_6868.wav \ + sfx_6869.wav \ + sfx_686.wav \ + sfx_6870.wav \ + sfx_6871.wav \ + sfx_6872.wav \ + sfx_6873.wav \ + sfx_6874.wav \ + sfx_6875.wav \ + sfx_6876.wav \ + sfx_6877.wav \ + sfx_6878.wav \ + sfx_6879.wav \ + sfx_687.wav \ + sfx_6880.wav \ + sfx_6881.wav \ + sfx_6882.wav \ + sfx_6883.wav \ + sfx_6884.wav \ + sfx_6885.wav \ + sfx_6886.wav \ + sfx_6887.wav \ + sfx_6888.wav \ + sfx_6889.wav \ + sfx_688.wav \ + sfx_6890.wav \ + sfx_6891.wav \ + sfx_6892.wav \ + sfx_6893.wav \ + sfx_6894.wav \ + sfx_6895.wav \ + sfx_6896.wav \ + sfx_6897.wav \ + sfx_6898.wav \ + sfx_6899.wav \ + sfx_689.wav \ + sfx_68.wav \ + sfx_6900.wav \ + sfx_6901.wav \ + sfx_6902.wav \ + sfx_6903.wav \ + sfx_6904.wav \ + sfx_6905.wav \ + sfx_6906.wav \ + sfx_6907.wav \ + sfx_6908.wav \ + sfx_6909.wav \ + sfx_690.wav \ + sfx_6910.wav \ + sfx_6911.wav \ + sfx_6912.wav \ + sfx_6913.wav \ + sfx_6914.wav \ + sfx_6915.wav \ + sfx_6916.wav \ + sfx_6917.wav \ + sfx_6918.wav \ + sfx_6919.wav \ + sfx_691.wav \ + sfx_6920.wav \ + sfx_6921.wav \ + sfx_6922.wav \ + sfx_6923.wav \ + sfx_6924.wav \ + sfx_6925.wav \ + sfx_6926.wav \ + sfx_6927.wav \ + sfx_6928.wav \ + sfx_6929.wav \ + sfx_692.wav \ + sfx_6930.wav \ + sfx_6931.wav \ + sfx_6932.wav \ + sfx_6933.wav \ + sfx_6934.wav \ + sfx_6935.wav \ + sfx_6936.wav \ + sfx_6937.wav \ + sfx_6938.wav \ + sfx_6939.wav \ + sfx_693.wav \ + sfx_6940.wav \ + sfx_6941.wav \ + sfx_6942.wav \ + sfx_6943.wav \ + sfx_6944.wav \ + sfx_6945.wav \ + sfx_6946.wav \ + sfx_6947.wav \ + sfx_6948.wav \ + sfx_6949.wav \ + sfx_694.wav \ + sfx_6950.wav \ + sfx_6951.wav \ + sfx_6952.wav \ + sfx_6953.wav \ + sfx_6954.wav \ + sfx_6955.wav \ + sfx_6956.wav \ + sfx_6957.wav \ + sfx_6958.wav \ + sfx_6959.wav \ + sfx_695.wav \ + sfx_6960.wav \ + sfx_6961.wav \ + sfx_6962.wav \ + sfx_6963.wav \ + sfx_6964.wav \ + sfx_6965.wav \ + sfx_6966.wav \ + sfx_6967.wav \ + sfx_6968.wav \ + sfx_6969.wav \ + sfx_696.wav \ + sfx_6970.wav \ + sfx_6971.wav \ + sfx_6972.wav \ + sfx_6973.wav \ + sfx_6974.wav \ + sfx_6975.wav \ + sfx_6976.wav \ + sfx_6977.wav \ + sfx_6978.wav \ + sfx_6979.wav \ + sfx_697.wav \ + sfx_6980.wav \ + sfx_6981.wav \ + sfx_6982.wav \ + sfx_6983.wav \ + sfx_6984.wav \ + sfx_6985.wav \ + sfx_6986.wav \ + sfx_6987.wav \ + sfx_6988.wav \ + sfx_6989.wav \ + sfx_698.wav \ + sfx_6990.wav \ + sfx_6991.wav \ + sfx_6992.wav \ + sfx_6993.wav \ + sfx_6994.wav \ + sfx_6995.wav \ + sfx_6996.wav \ + sfx_6997.wav \ + sfx_6998.wav \ + sfx_6999.wav \ + sfx_699.wav \ + sfx_69.wav \ + sfx_6.wav \ + sfx_7000.wav \ + sfx_7001.wav \ + sfx_7002.wav \ + sfx_7003.wav \ + sfx_7004.wav \ + sfx_7005.wav \ + sfx_7006.wav \ + sfx_7007.wav \ + sfx_7008.wav \ + sfx_7009.wav \ + sfx_700.wav \ + sfx_7010.wav \ + sfx_7011.wav \ + sfx_7012.wav \ + sfx_7013.wav \ + sfx_7014.wav \ + sfx_7015.wav \ + sfx_7016.wav \ + sfx_7017.wav \ + sfx_7018.wav \ + sfx_7019.wav \ + sfx_701.wav \ + sfx_7020.wav \ + sfx_7021.wav \ + sfx_7022.wav \ + sfx_7023.wav \ + sfx_7024.wav \ + sfx_7025.wav \ + sfx_7026.wav \ + sfx_7027.wav \ + sfx_7028.wav \ + sfx_7029.wav \ + sfx_702.wav \ + sfx_7030.wav \ + sfx_7031.wav \ + sfx_7032.wav \ + sfx_7033.wav \ + sfx_7034.wav \ + sfx_7035.wav \ + sfx_7036.wav \ + sfx_7037.wav \ + sfx_7038.wav \ + sfx_7039.wav \ + sfx_703.wav \ + sfx_7040.wav \ + sfx_7041.wav \ + sfx_7042.wav \ + sfx_7043.wav \ + sfx_7044.wav \ + sfx_7045.wav \ + sfx_7046.wav \ + sfx_7047.wav \ + sfx_7048.wav \ + sfx_7049.wav \ + sfx_704.wav \ + sfx_7050.wav \ + sfx_7051.wav \ + sfx_7052.wav \ + sfx_7053.wav \ + sfx_7054.wav \ + sfx_7055.wav \ + sfx_7056.wav \ + sfx_7057.wav \ + sfx_7058.wav \ + sfx_7059.wav \ + sfx_705.wav \ + sfx_7060.wav \ + sfx_7061.wav \ + sfx_7062.wav \ + sfx_7063.wav \ + sfx_7064.wav \ + sfx_7065.wav \ + sfx_7066.wav \ + sfx_7067.wav \ + sfx_7068.wav \ + sfx_7069.wav \ + sfx_706.wav \ + sfx_7070.wav \ + sfx_7071.wav \ + sfx_7072.wav \ + sfx_7073.wav \ + sfx_7074.wav \ + sfx_7075.wav \ + sfx_7076.wav \ + sfx_7077.wav \ + sfx_7078.wav \ + sfx_7079.wav \ + sfx_707.wav \ + sfx_7080.wav \ + sfx_7081.wav \ + sfx_7082.wav \ + sfx_7083.wav \ + sfx_7084.wav \ + sfx_7085.wav \ + sfx_7086.wav \ + sfx_7087.wav \ + sfx_7088.wav \ + sfx_7089.wav \ + sfx_708.wav \ + sfx_7090.wav \ + sfx_7091.wav \ + sfx_7092.wav \ + sfx_7093.wav \ + sfx_7094.wav \ + sfx_7095.wav \ + sfx_7096.wav \ + sfx_7097.wav \ + sfx_7098.wav \ + sfx_7099.wav \ + sfx_709.wav \ + sfx_70.wav \ + sfx_7100.wav \ + sfx_7101.wav \ + sfx_7102.wav \ + sfx_7103.wav \ + sfx_7104.wav \ + sfx_7105.wav \ + sfx_7106.wav \ + sfx_7107.wav \ + sfx_7108.wav \ + sfx_7109.wav \ + sfx_710.wav \ + sfx_7110.wav \ + sfx_7111.wav \ + sfx_7112.wav \ + sfx_7113.wav \ + sfx_7114.wav \ + sfx_7115.wav \ + sfx_7116.wav \ + sfx_7117.wav \ + sfx_7118.wav \ + sfx_7119.wav \ + sfx_711.wav \ + sfx_7120.wav \ + sfx_7121.wav \ + sfx_7122.wav \ + sfx_7123.wav \ + sfx_7124.wav \ + sfx_7125.wav \ + sfx_7126.wav \ + sfx_7127.wav \ + sfx_7128.wav \ + sfx_7129.wav \ + sfx_712.wav \ + sfx_7130.wav \ + sfx_7131.wav \ + sfx_7132.wav \ + sfx_7133.wav \ + sfx_7134.wav \ + sfx_7135.wav \ + sfx_7136.wav \ + sfx_7137.wav \ + sfx_7138.wav \ + sfx_7139.wav \ + sfx_713.wav \ + sfx_7140.wav \ + sfx_7141.wav \ + sfx_7142.wav \ + sfx_7143.wav \ + sfx_7144.wav \ + sfx_7145.wav \ + sfx_7146.wav \ + sfx_7147.wav \ + sfx_7148.wav \ + sfx_7149.wav \ + sfx_714.wav \ + sfx_7150.wav \ + sfx_7151.wav \ + sfx_7152.wav \ + sfx_7153.wav \ + sfx_7154.wav \ + sfx_7155.wav \ + sfx_7156.wav \ + sfx_7157.wav \ + sfx_7158.wav \ + sfx_7159.wav \ + sfx_715.wav \ + sfx_7160.wav \ + sfx_7161.wav \ + sfx_7162.wav \ + sfx_7163.wav \ + sfx_7164.wav \ + sfx_7165.wav \ + sfx_7166.wav \ + sfx_7167.wav \ + sfx_7168.wav \ + sfx_7169.wav \ + sfx_716.wav \ + sfx_7170.wav \ + sfx_7171.wav \ + sfx_7172.wav \ + sfx_7173.wav \ + sfx_7174.wav \ + sfx_7175.wav \ + sfx_7176.wav \ + sfx_7177.wav \ + sfx_7178.wav \ + sfx_7179.wav \ + sfx_717.wav \ + sfx_7180.wav \ + sfx_7181.wav \ + sfx_7182.wav \ + sfx_7183.wav \ + sfx_7184.wav \ + sfx_7185.wav \ + sfx_7186.wav \ + sfx_7187.wav \ + sfx_7188.wav \ + sfx_7189.wav \ + sfx_718.wav \ + sfx_7190.wav \ + sfx_7191.wav \ + sfx_7192.wav \ + sfx_7193.wav \ + sfx_7194.wav \ + sfx_7195.wav \ + sfx_7196.wav \ + sfx_7197.wav \ + sfx_7198.wav \ + sfx_7199.wav \ + sfx_719.wav \ + sfx_71.wav \ + sfx_7200.wav \ + sfx_7201.wav \ + sfx_7202.wav \ + sfx_7203.wav \ + sfx_7204.wav \ + sfx_7205.wav \ + sfx_7206.wav \ + sfx_7207.wav \ + sfx_7208.wav \ + sfx_7209.wav \ + sfx_720.wav \ + sfx_7210.wav \ + sfx_7211.wav \ + sfx_7212.wav \ + sfx_7213.wav \ + sfx_7214.wav \ + sfx_7215.wav \ + sfx_7216.wav \ + sfx_7217.wav \ + sfx_7218.wav \ + sfx_7219.wav \ + sfx_721.wav \ + sfx_7220.wav \ + sfx_7221.wav \ + sfx_7222.wav \ + sfx_7223.wav \ + sfx_7224.wav \ + sfx_7225.wav \ + sfx_7226.wav \ + sfx_7227.wav \ + sfx_7228.wav \ + sfx_7229.wav \ + sfx_722.wav \ + sfx_7230.wav \ + sfx_7231.wav \ + sfx_7232.wav \ + sfx_7233.wav \ + sfx_7234.wav \ + sfx_7235.wav \ + sfx_7236.wav \ + sfx_7237.wav \ + sfx_7238.wav \ + sfx_7239.wav \ + sfx_723.wav \ + sfx_7240.wav \ + sfx_7241.wav \ + sfx_7242.wav \ + sfx_7243.wav \ + sfx_7244.wav \ + sfx_7245.wav \ + sfx_7246.wav \ + sfx_7247.wav \ + sfx_7248.wav \ + sfx_7249.wav \ + sfx_724.wav \ + sfx_7250.wav \ + sfx_7251.wav \ + sfx_7252.wav \ + sfx_7253.wav \ + sfx_7254.wav \ + sfx_7255.wav \ + sfx_7256.wav \ + sfx_7257.wav \ + sfx_7258.wav \ + sfx_7259.wav \ + sfx_725.wav \ + sfx_7260.wav \ + sfx_7261.wav \ + sfx_7262.wav \ + sfx_7263.wav \ + sfx_7264.wav \ + sfx_7265.wav \ + sfx_7266.wav \ + sfx_7267.wav \ + sfx_7268.wav \ + sfx_7269.wav \ + sfx_726.wav \ + sfx_7270.wav \ + sfx_7271.wav \ + sfx_7272.wav \ + sfx_7273.wav \ + sfx_7274.wav \ + sfx_7275.wav \ + sfx_7276.wav \ + sfx_7277.wav \ + sfx_7278.wav \ + sfx_7279.wav \ + sfx_727.wav \ + sfx_7280.wav \ + sfx_7281.wav \ + sfx_7282.wav \ + sfx_7283.wav \ + sfx_7284.wav \ + sfx_7285.wav \ + sfx_7286.wav \ + sfx_7287.wav \ + sfx_7288.wav \ + sfx_7289.wav \ + sfx_728.wav \ + sfx_7290.wav \ + sfx_7291.wav \ + sfx_7292.wav \ + sfx_7293.wav \ + sfx_7294.wav \ + sfx_7295.wav \ + sfx_7296.wav \ + sfx_7297.wav \ + sfx_7298.wav \ + sfx_7299.wav \ + sfx_729.wav \ + sfx_72.wav \ + sfx_7300.wav \ + sfx_7301.wav \ + sfx_7302.wav \ + sfx_7303.wav \ + sfx_7304.wav \ + sfx_7305.wav \ + sfx_7306.wav \ + sfx_7307.wav \ + sfx_7308.wav \ + sfx_7309.wav \ + sfx_730.wav \ + sfx_7310.wav \ + sfx_7311.wav \ + sfx_7312.wav \ + sfx_7313.wav \ + sfx_7314.wav \ + sfx_7315.wav \ + sfx_7316.wav \ + sfx_7317.wav \ + sfx_7318.wav \ + sfx_7319.wav \ + sfx_731.wav \ + sfx_7320.wav \ + sfx_7321.wav \ + sfx_7322.wav \ + sfx_7323.wav \ + sfx_7324.wav \ + sfx_7325.wav \ + sfx_7326.wav \ + sfx_7327.wav \ + sfx_7328.wav \ + sfx_7329.wav \ + sfx_732.wav \ + sfx_7330.wav \ + sfx_7331.wav \ + sfx_7332.wav \ + sfx_7333.wav \ + sfx_7334.wav \ + sfx_7335.wav \ + sfx_7336.wav \ + sfx_7337.wav \ + sfx_7338.wav \ + sfx_7339.wav \ + sfx_733.wav \ + sfx_7340.wav \ + sfx_7341.wav \ + sfx_7342.wav \ + sfx_7343.wav \ + sfx_7344.wav \ + sfx_7345.wav \ + sfx_7346.wav \ + sfx_7347.wav \ + sfx_7348.wav \ + sfx_7349.wav \ + sfx_734.wav \ + sfx_7350.wav \ + sfx_7351.wav \ + sfx_7352.wav \ + sfx_7353.wav \ + sfx_7354.wav \ + sfx_7355.wav \ + sfx_7356.wav \ + sfx_7357.wav \ + sfx_7358.wav \ + sfx_7359.wav \ + sfx_735.wav \ + sfx_7360.wav \ + sfx_7361.wav \ + sfx_7362.wav \ + sfx_7363.wav \ + sfx_7364.wav \ + sfx_7365.wav \ + sfx_7366.wav \ + sfx_7367.wav \ + sfx_7368.wav \ + sfx_7369.wav \ + sfx_736.wav \ + sfx_7370.wav \ + sfx_7371.wav \ + sfx_7372.wav \ + sfx_7373.wav \ + sfx_7374.wav \ + sfx_7375.wav \ + sfx_7376.wav \ + sfx_7377.wav \ + sfx_7378.wav \ + sfx_7379.wav \ + sfx_737.wav \ + sfx_7380.wav \ + sfx_7381.wav \ + sfx_7382.wav \ + sfx_7383.wav \ + sfx_7384.wav \ + sfx_7385.wav \ + sfx_7386.wav \ + sfx_7387.wav \ + sfx_7388.wav \ + sfx_7389.wav \ + sfx_738.wav \ + sfx_7390.wav \ + sfx_7391.wav \ + sfx_7392.wav \ + sfx_7393.wav \ + sfx_7394.wav \ + sfx_7395.wav \ + sfx_7396.wav \ + sfx_7397.wav \ + sfx_7398.wav \ + sfx_7399.wav \ + sfx_739.wav \ + sfx_73.wav \ + sfx_7400.wav \ + sfx_7401.wav \ + sfx_7402.wav \ + sfx_7403.wav \ + sfx_7404.wav \ + sfx_7405.wav \ + sfx_7406.wav \ + sfx_7407.wav \ + sfx_7408.wav \ + sfx_7409.wav \ + sfx_740.wav \ + sfx_7410.wav \ + sfx_7411.wav \ + sfx_7412.wav \ + sfx_7413.wav \ + sfx_7414.wav \ + sfx_7415.wav \ + sfx_7416.wav \ + sfx_7417.wav \ + sfx_7418.wav \ + sfx_7419.wav \ + sfx_741.wav \ + sfx_7420.wav \ + sfx_7421.wav \ + sfx_7422.wav \ + sfx_7423.wav \ + sfx_7424.wav \ + sfx_7425.wav \ + sfx_7426.wav \ + sfx_7427.wav \ + sfx_7428.wav \ + sfx_7429.wav \ + sfx_742.wav \ + sfx_7430.wav \ + sfx_7431.wav \ + sfx_7432.wav \ + sfx_7433.wav \ + sfx_7434.wav \ + sfx_7435.wav \ + sfx_7436.wav \ + sfx_7437.wav \ + sfx_7438.wav \ + sfx_7439.wav \ + sfx_743.wav \ + sfx_7440.wav \ + sfx_7441.wav \ + sfx_7442.wav \ + sfx_7443.wav \ + sfx_7444.wav \ + sfx_7445.wav \ + sfx_7446.wav \ + sfx_7447.wav \ + sfx_7448.wav \ + sfx_7449.wav \ + sfx_744.wav \ + sfx_7450.wav \ + sfx_7451.wav \ + sfx_7452.wav \ + sfx_7453.wav \ + sfx_7454.wav \ + sfx_7455.wav \ + sfx_7456.wav \ + sfx_7457.wav \ + sfx_7458.wav \ + sfx_7459.wav \ + sfx_745.wav \ + sfx_7460.wav \ + sfx_7461.wav \ + sfx_7462.wav \ + sfx_7463.wav \ + sfx_7464.wav \ + sfx_7465.wav \ + sfx_7466.wav \ + sfx_7467.wav \ + sfx_7468.wav \ + sfx_7469.wav \ + sfx_746.wav \ + sfx_7470.wav \ + sfx_7471.wav \ + sfx_7472.wav \ + sfx_7473.wav \ + sfx_7474.wav \ + sfx_7475.wav \ + sfx_7476.wav \ + sfx_7477.wav \ + sfx_7478.wav \ + sfx_7479.wav \ + sfx_747.wav \ + sfx_7480.wav \ + sfx_7481.wav \ + sfx_7482.wav \ + sfx_7483.wav \ + sfx_7484.wav \ + sfx_7485.wav \ + sfx_7486.wav \ + sfx_7487.wav \ + sfx_7488.wav \ + sfx_7489.wav \ + sfx_748.wav \ + sfx_7490.wav \ + sfx_7491.wav \ + sfx_7492.wav \ + sfx_7493.wav \ + sfx_7494.wav \ + sfx_7495.wav \ + sfx_7496.wav \ + sfx_7497.wav \ + sfx_7498.wav \ + sfx_7499.wav \ + sfx_749.wav \ + sfx_74.wav \ + sfx_7500.wav \ + sfx_7501.wav \ + sfx_7502.wav \ + sfx_7503.wav \ + sfx_7504.wav \ + sfx_7505.wav \ + sfx_7506.wav \ + sfx_7507.wav \ + sfx_7508.wav \ + sfx_7509.wav \ + sfx_750.wav \ + sfx_7510.wav \ + sfx_7511.wav \ + sfx_7512.wav \ + sfx_7513.wav \ + sfx_7514.wav \ + sfx_7515.wav \ + sfx_7516.wav \ + sfx_7517.wav \ + sfx_7518.wav \ + sfx_7519.wav \ + sfx_751.wav \ + sfx_7520.wav \ + sfx_7521.wav \ + sfx_7522.wav \ + sfx_7523.wav \ + sfx_7524.wav \ + sfx_7525.wav \ + sfx_7526.wav \ + sfx_7527.wav \ + sfx_7528.wav \ + sfx_7529.wav \ + sfx_752.wav \ + sfx_7530.wav \ + sfx_7531.wav \ + sfx_7532.wav \ + sfx_7533.wav \ + sfx_7534.wav \ + sfx_7535.wav \ + sfx_7536.wav \ + sfx_7537.wav \ + sfx_7538.wav \ + sfx_7539.wav \ + sfx_753.wav \ + sfx_7540.wav \ + sfx_7541.wav \ + sfx_7542.wav \ + sfx_7543.wav \ + sfx_7544.wav \ + sfx_7545.wav \ + sfx_7546.wav \ + sfx_7547.wav \ + sfx_7548.wav \ + sfx_7549.wav \ + sfx_754.wav \ + sfx_7550.wav \ + sfx_7551.wav \ + sfx_7552.wav \ + sfx_7553.wav \ + sfx_7554.wav \ + sfx_7555.wav \ + sfx_7556.wav \ + sfx_7557.wav \ + sfx_7558.wav \ + sfx_7559.wav \ + sfx_755.wav \ + sfx_7560.wav \ + sfx_7561.wav \ + sfx_7562.wav \ + sfx_7563.wav \ + sfx_7564.wav \ + sfx_7565.wav \ + sfx_7566.wav \ + sfx_7567.wav \ + sfx_7568.wav \ + sfx_7569.wav \ + sfx_756.wav \ + sfx_7570.wav \ + sfx_7571.wav \ + sfx_7572.wav \ + sfx_7573.wav \ + sfx_7574.wav \ + sfx_7575.wav \ + sfx_7576.wav \ + sfx_7577.wav \ + sfx_7578.wav \ + sfx_7579.wav \ + sfx_757.wav \ + sfx_7580.wav \ + sfx_7581.wav \ + sfx_7582.wav \ + sfx_7583.wav \ + sfx_7584.wav \ + sfx_7585.wav \ + sfx_7586.wav \ + sfx_7587.wav \ + sfx_7588.wav \ + sfx_7589.wav \ + sfx_758.wav \ + sfx_7590.wav \ + sfx_7591.wav \ + sfx_7592.wav \ + sfx_7593.wav \ + sfx_7594.wav \ + sfx_7595.wav \ + sfx_7596.wav \ + sfx_7597.wav \ + sfx_7598.wav \ + sfx_7599.wav \ + sfx_759.wav \ + sfx_75.wav \ + sfx_7600.wav \ + sfx_7601.wav \ + sfx_7602.wav \ + sfx_7603.wav \ + sfx_7604.wav \ + sfx_7605.wav \ + sfx_7606.wav \ + sfx_7607.wav \ + sfx_7608.wav \ + sfx_7609.wav \ + sfx_760.wav \ + sfx_7610.wav \ + sfx_7611.wav \ + sfx_7612.wav \ + sfx_7613.wav \ + sfx_7614.wav \ + sfx_7615.wav \ + sfx_7616.wav \ + sfx_7617.wav \ + sfx_7618.wav \ + sfx_7619.wav \ + sfx_761.wav \ + sfx_7620.wav \ + sfx_7621.wav \ + sfx_7622.wav \ + sfx_7623.wav \ + sfx_7624.wav \ + sfx_7625.wav \ + sfx_7626.wav \ + sfx_7627.wav \ + sfx_7628.wav \ + sfx_7629.wav \ + sfx_762.wav \ + sfx_7630.wav \ + sfx_7631.wav \ + sfx_7632.wav \ + sfx_7633.wav \ + sfx_7634.wav \ + sfx_7635.wav \ + sfx_7636.wav \ + sfx_7637.wav \ + sfx_7638.wav \ + sfx_7639.wav \ + sfx_763.wav \ + sfx_7640.wav \ + sfx_7641.wav \ + sfx_7642.wav \ + sfx_7643.wav \ + sfx_7644.wav \ + sfx_7645.wav \ + sfx_7646.wav \ + sfx_7647.wav \ + sfx_7648.wav \ + sfx_7649.wav \ + sfx_764.wav \ + sfx_7650.wav \ + sfx_7651.wav \ + sfx_7652.wav \ + sfx_7653.wav \ + sfx_7654.wav \ + sfx_7655.wav \ + sfx_7656.wav \ + sfx_7657.wav \ + sfx_7658.wav \ + sfx_7659.wav \ + sfx_765.wav \ + sfx_7660.wav \ + sfx_7661.wav \ + sfx_7662.wav \ + sfx_7663.wav \ + sfx_7664.wav \ + sfx_7665.wav \ + sfx_7666.wav \ + sfx_7667.wav \ + sfx_7668.wav \ + sfx_7669.wav \ + sfx_766.wav \ + sfx_7670.wav \ + sfx_7671.wav \ + sfx_7672.wav \ + sfx_7673.wav \ + sfx_7674.wav \ + sfx_7675.wav \ + sfx_7676.wav \ + sfx_7677.wav \ + sfx_7678.wav \ + sfx_7679.wav \ + sfx_767.wav \ + sfx_7680.wav \ + sfx_7681.wav \ + sfx_7682.wav \ + sfx_7683.wav \ + sfx_7684.wav \ + sfx_7685.wav \ + sfx_7686.wav \ + sfx_7687.wav \ + sfx_7688.wav \ + sfx_7689.wav \ + sfx_768.wav \ + sfx_7690.wav \ + sfx_7691.wav \ + sfx_7692.wav \ + sfx_7693.wav \ + sfx_7694.wav \ + sfx_7695.wav \ + sfx_7696.wav \ + sfx_7697.wav \ + sfx_7698.wav \ + sfx_7699.wav \ + sfx_769.wav \ + sfx_76.wav \ + sfx_7700.wav \ + sfx_7701.wav \ + sfx_7702.wav \ + sfx_7703.wav \ + sfx_7704.wav \ + sfx_7705.wav \ + sfx_7706.wav \ + sfx_7707.wav \ + sfx_7708.wav \ + sfx_7709.wav \ + sfx_770.wav \ + sfx_7710.wav \ + sfx_7711.wav \ + sfx_7712.wav \ + sfx_7713.wav \ + sfx_7714.wav \ + sfx_7715.wav \ + sfx_7716.wav \ + sfx_7717.wav \ + sfx_7718.wav \ + sfx_7719.wav \ + sfx_771.wav \ + sfx_7720.wav \ + sfx_7721.wav \ + sfx_7722.wav \ + sfx_7723.wav \ + sfx_7724.wav \ + sfx_7725.wav \ + sfx_7726.wav \ + sfx_7727.wav \ + sfx_7728.wav \ + sfx_7729.wav \ + sfx_772.wav \ + sfx_7730.wav \ + sfx_7731.wav \ + sfx_7732.wav \ + sfx_7733.wav \ + sfx_7734.wav \ + sfx_7735.wav \ + sfx_7736.wav \ + sfx_7737.wav \ + sfx_7738.wav \ + sfx_7739.wav \ + sfx_773.wav \ + sfx_7740.wav \ + sfx_7741.wav \ + sfx_7742.wav \ + sfx_7743.wav \ + sfx_7744.wav \ + sfx_7745.wav \ + sfx_7746.wav \ + sfx_7747.wav \ + sfx_7748.wav \ + sfx_7749.wav \ + sfx_774.wav \ + sfx_7750.wav \ + sfx_7751.wav \ + sfx_7752.wav \ + sfx_7753.wav \ + sfx_7754.wav \ + sfx_7755.wav \ + sfx_7756.wav \ + sfx_7757.wav \ + sfx_7758.wav \ + sfx_7759.wav \ + sfx_775.wav \ + sfx_7760.wav \ + sfx_7761.wav \ + sfx_7762.wav \ + sfx_7763.wav \ + sfx_7764.wav \ + sfx_7765.wav \ + sfx_7766.wav \ + sfx_7767.wav \ + sfx_7768.wav \ + sfx_7769.wav \ + sfx_776.wav \ + sfx_7770.wav \ + sfx_7771.wav \ + sfx_7772.wav \ + sfx_7773.wav \ + sfx_7774.wav \ + sfx_7775.wav \ + sfx_7776.wav \ + sfx_7777.wav \ + sfx_7778.wav \ + sfx_7779.wav \ + sfx_777.wav \ + sfx_7780.wav \ + sfx_7781.wav \ + sfx_7782.wav \ + sfx_7783.wav \ + sfx_7784.wav \ + sfx_7785.wav \ + sfx_7786.wav \ + sfx_7787.wav \ + sfx_7788.wav \ + sfx_7789.wav \ + sfx_778.wav \ + sfx_7790.wav \ + sfx_7791.wav \ + sfx_7792.wav \ + sfx_7793.wav \ + sfx_7794.wav \ + sfx_7795.wav \ + sfx_7796.wav \ + sfx_7797.wav \ + sfx_7798.wav \ + sfx_7799.wav \ + sfx_779.wav \ + sfx_77.wav \ + sfx_7800.wav \ + sfx_7801.wav \ + sfx_7802.wav \ + sfx_7803.wav \ + sfx_7804.wav \ + sfx_7805.wav \ + sfx_7806.wav \ + sfx_7807.wav \ + sfx_7808.wav \ + sfx_7809.wav \ + sfx_780.wav \ + sfx_7810.wav \ + sfx_7811.wav \ + sfx_7812.wav \ + sfx_7813.wav \ + sfx_7814.wav \ + sfx_7815.wav \ + sfx_7816.wav \ + sfx_7817.wav \ + sfx_7818.wav \ + sfx_7819.wav \ + sfx_781.wav \ + sfx_7820.wav \ + sfx_7821.wav \ + sfx_7822.wav \ + sfx_7823.wav \ + sfx_7824.wav \ + sfx_7825.wav \ + sfx_7826.wav \ + sfx_7827.wav \ + sfx_7828.wav \ + sfx_7829.wav \ + sfx_782.wav \ + sfx_7830.wav \ + sfx_7831.wav \ + sfx_7832.wav \ + sfx_7833.wav \ + sfx_7834.wav \ + sfx_7835.wav \ + sfx_7836.wav \ + sfx_7837.wav \ + sfx_7838.wav \ + sfx_7839.wav \ + sfx_783.wav \ + sfx_7840.wav \ + sfx_7841.wav \ + sfx_7842.wav \ + sfx_7843.wav \ + sfx_7844.wav \ + sfx_7845.wav \ + sfx_7846.wav \ + sfx_7847.wav \ + sfx_7848.wav \ + sfx_7849.wav \ + sfx_784.wav \ + sfx_7850.wav \ + sfx_7851.wav \ + sfx_7852.wav \ + sfx_7853.wav \ + sfx_7854.wav \ + sfx_7855.wav \ + sfx_7856.wav \ + sfx_7857.wav \ + sfx_7858.wav \ + sfx_7859.wav \ + sfx_785.wav \ + sfx_7860.wav \ + sfx_7861.wav \ + sfx_7862.wav \ + sfx_7863.wav \ + sfx_7864.wav \ + sfx_7865.wav \ + sfx_7866.wav \ + sfx_7867.wav \ + sfx_7868.wav \ + sfx_7869.wav \ + sfx_786.wav \ + sfx_7870.wav \ + sfx_7871.wav \ + sfx_7872.wav \ + sfx_7873.wav \ + sfx_7874.wav \ + sfx_7875.wav \ + sfx_7876.wav \ + sfx_7877.wav \ + sfx_7878.wav \ + sfx_7879.wav \ + sfx_787.wav \ + sfx_7880.wav \ + sfx_7881.wav \ + sfx_7882.wav \ + sfx_7883.wav \ + sfx_7884.wav \ + sfx_7885.wav \ + sfx_7886.wav \ + sfx_7887.wav \ + sfx_7888.wav \ + sfx_7889.wav \ + sfx_788.wav \ + sfx_7890.wav \ + sfx_7891.wav \ + sfx_7892.wav \ + sfx_7893.wav \ + sfx_7894.wav \ + sfx_7895.wav \ + sfx_7896.wav \ + sfx_7897.wav \ + sfx_7898.wav \ + sfx_7899.wav \ + sfx_789.wav \ + sfx_78.wav \ + sfx_7900.wav \ + sfx_7901.wav \ + sfx_7902.wav \ + sfx_7903.wav \ + sfx_7904.wav \ + sfx_7905.wav \ + sfx_7906.wav \ + sfx_7907.wav \ + sfx_7908.wav \ + sfx_7909.wav \ + sfx_790.wav \ + sfx_7910.wav \ + sfx_7911.wav \ + sfx_7912.wav \ + sfx_7913.wav \ + sfx_7914.wav \ + sfx_7915.wav \ + sfx_7916.wav \ + sfx_7917.wav \ + sfx_7918.wav \ + sfx_7919.wav \ + sfx_791.wav \ + sfx_7920.wav \ + sfx_7921.wav \ + sfx_7922.wav \ + sfx_7923.wav \ + sfx_7924.wav \ + sfx_7925.wav \ + sfx_7926.wav \ + sfx_7927.wav \ + sfx_7928.wav \ + sfx_7929.wav \ + sfx_792.wav \ + sfx_7930.wav \ + sfx_7931.wav \ + sfx_7932.wav \ + sfx_7933.wav \ + sfx_7934.wav \ + sfx_7935.wav \ + sfx_7936.wav \ + sfx_7937.wav \ + sfx_7938.wav \ + sfx_7939.wav \ + sfx_793.wav \ + sfx_7940.wav \ + sfx_7941.wav \ + sfx_7942.wav \ + sfx_7943.wav \ + sfx_7944.wav \ + sfx_7945.wav \ + sfx_7946.wav \ + sfx_7947.wav \ + sfx_7948.wav \ + sfx_7949.wav \ + sfx_794.wav \ + sfx_7950.wav \ + sfx_7951.wav \ + sfx_7952.wav \ + sfx_7953.wav \ + sfx_7954.wav \ + sfx_7955.wav \ + sfx_7956.wav \ + sfx_7957.wav \ + sfx_7958.wav \ + sfx_7959.wav \ + sfx_795.wav \ + sfx_7960.wav \ + sfx_7961.wav \ + sfx_7962.wav \ + sfx_7963.wav \ + sfx_7964.wav \ + sfx_7965.wav \ + sfx_7966.wav \ + sfx_7967.wav \ + sfx_7968.wav \ + sfx_7969.wav \ + sfx_796.wav \ + sfx_7970.wav \ + sfx_7971.wav \ + sfx_7972.wav \ + sfx_7973.wav \ + sfx_7974.wav \ + sfx_7975.wav \ + sfx_7976.wav \ + sfx_7977.wav \ + sfx_7978.wav \ + sfx_7979.wav \ + sfx_797.wav \ + sfx_7980.wav \ + sfx_7981.wav \ + sfx_7982.wav \ + sfx_7983.wav \ + sfx_7984.wav \ + sfx_7985.wav \ + sfx_7986.wav \ + sfx_7987.wav \ + sfx_7988.wav \ + sfx_7989.wav \ + sfx_798.wav \ + sfx_7990.wav \ + sfx_7991.wav \ + sfx_7992.wav \ + sfx_7993.wav \ + sfx_7994.wav \ + sfx_7995.wav \ + sfx_7996.wav \ + sfx_7997.wav \ + sfx_7998.wav \ + sfx_7999.wav \ + sfx_799.wav \ + sfx_79.wav \ + sfx_7.wav \ + sfx_8000.wav \ + sfx_8001.wav \ + sfx_8002.wav \ + sfx_8003.wav \ + sfx_8004.wav \ + sfx_8005.wav \ + sfx_8006.wav \ + sfx_8007.wav \ + sfx_8008.wav \ + sfx_8009.wav \ + sfx_800.wav \ + sfx_8010.wav \ + sfx_8011.wav \ + sfx_8012.wav \ + sfx_8013.wav \ + sfx_8014.wav \ + sfx_8015.wav \ + sfx_8016.wav \ + sfx_8017.wav \ + sfx_8018.wav \ + sfx_8019.wav \ + sfx_801.wav \ + sfx_8020.wav \ + sfx_8021.wav \ + sfx_8022.wav \ + sfx_8023.wav \ + sfx_8024.wav \ + sfx_8025.wav \ + sfx_8026.wav \ + sfx_8027.wav \ + sfx_8028.wav \ + sfx_8029.wav \ + sfx_802.wav \ + sfx_8030.wav \ + sfx_8031.wav \ + sfx_8032.wav \ + sfx_8033.wav \ + sfx_8034.wav \ + sfx_8035.wav \ + sfx_8036.wav \ + sfx_8037.wav \ + sfx_8038.wav \ + sfx_8039.wav \ + sfx_803.wav \ + sfx_8040.wav \ + sfx_8041.wav \ + sfx_8042.wav \ + sfx_8043.wav \ + sfx_8044.wav \ + sfx_8045.wav \ + sfx_8046.wav \ + sfx_8047.wav \ + sfx_8048.wav \ + sfx_8049.wav \ + sfx_804.wav \ + sfx_8050.wav \ + sfx_8051.wav \ + sfx_8052.wav \ + sfx_8053.wav \ + sfx_8054.wav \ + sfx_8055.wav \ + sfx_8056.wav \ + sfx_8057.wav \ + sfx_8058.wav \ + sfx_8059.wav \ + sfx_805.wav \ + sfx_8060.wav \ + sfx_8061.wav \ + sfx_8062.wav \ + sfx_8063.wav \ + sfx_8064.wav \ + sfx_8065.wav \ + sfx_8066.wav \ + sfx_8067.wav \ + sfx_8068.wav \ + sfx_8069.wav \ + sfx_806.wav \ + sfx_8070.wav \ + sfx_8071.wav \ + sfx_8072.wav \ + sfx_8073.wav \ + sfx_8074.wav \ + sfx_8075.wav \ + sfx_8076.wav \ + sfx_8077.wav \ + sfx_8078.wav \ + sfx_8079.wav \ + sfx_807.wav \ + sfx_8080.wav \ + sfx_8081.wav \ + sfx_8082.wav \ + sfx_8083.wav \ + sfx_8084.wav \ + sfx_8085.wav \ + sfx_8086.wav \ + sfx_8087.wav \ + sfx_8088.wav \ + sfx_8089.wav \ + sfx_808.wav \ + sfx_8090.wav \ + sfx_8091.wav \ + sfx_8092.wav \ + sfx_8093.wav \ + sfx_8094.wav \ + sfx_8095.wav \ + sfx_8096.wav \ + sfx_8097.wav \ + sfx_8098.wav \ + sfx_8099.wav \ + sfx_809.wav \ + sfx_80.wav \ + sfx_8100.wav \ + sfx_8101.wav \ + sfx_8102.wav \ + sfx_8103.wav \ + sfx_8104.wav \ + sfx_8105.wav \ + sfx_8106.wav \ + sfx_8107.wav \ + sfx_8108.wav \ + sfx_8109.wav \ + sfx_810.wav \ + sfx_8110.wav \ + sfx_8111.wav \ + sfx_8112.wav \ + sfx_8113.wav \ + sfx_8114.wav \ + sfx_8115.wav \ + sfx_8116.wav \ + sfx_8117.wav \ + sfx_8118.wav \ + sfx_8119.wav \ + sfx_811.wav \ + sfx_8120.wav \ + sfx_8121.wav \ + sfx_8122.wav \ + sfx_8123.wav \ + sfx_8124.wav \ + sfx_8125.wav \ + sfx_8126.wav \ + sfx_8127.wav \ + sfx_8128.wav \ + sfx_8129.wav \ + sfx_812.wav \ + sfx_8130.wav \ + sfx_8131.wav \ + sfx_8132.wav \ + sfx_8133.wav \ + sfx_8134.wav \ + sfx_8135.wav \ + sfx_8136.wav \ + sfx_8137.wav \ + sfx_8138.wav \ + sfx_8139.wav \ + sfx_813.wav \ + sfx_8140.wav \ + sfx_8141.wav \ + sfx_8142.wav \ + sfx_8143.wav \ + sfx_8144.wav \ + sfx_8145.wav \ + sfx_8146.wav \ + sfx_8147.wav \ + sfx_8148.wav \ + sfx_8149.wav \ + sfx_814.wav \ + sfx_8150.wav \ + sfx_8151.wav \ + sfx_8152.wav \ + sfx_8153.wav \ + sfx_8154.wav \ + sfx_8155.wav \ + sfx_8156.wav \ + sfx_8157.wav \ + sfx_8158.wav \ + sfx_8159.wav \ + sfx_815.wav \ + sfx_8160.wav \ + sfx_8161.wav \ + sfx_8162.wav \ + sfx_8163.wav \ + sfx_8164.wav \ + sfx_8165.wav \ + sfx_8166.wav \ + sfx_8167.wav \ + sfx_8168.wav \ + sfx_8169.wav \ + sfx_816.wav \ + sfx_8170.wav \ + sfx_8171.wav \ + sfx_8172.wav \ + sfx_8173.wav \ + sfx_8174.wav \ + sfx_8175.wav \ + sfx_8176.wav \ + sfx_8177.wav \ + sfx_8178.wav \ + sfx_8179.wav \ + sfx_817.wav \ + sfx_8180.wav \ + sfx_8181.wav \ + sfx_8182.wav \ + sfx_8183.wav \ + sfx_8184.wav \ + sfx_8185.wav \ + sfx_8186.wav \ + sfx_8187.wav \ + sfx_8188.wav \ + sfx_8189.wav \ + sfx_818.wav \ + sfx_8190.wav \ + sfx_8191.wav \ + sfx_8192.wav \ + sfx_8193.wav \ + sfx_8194.wav \ + sfx_8195.wav \ + sfx_8196.wav \ + sfx_8197.wav \ + sfx_8198.wav \ + sfx_8199.wav \ + sfx_819.wav \ + sfx_81.wav \ + sfx_8200.wav \ + sfx_8201.wav \ + sfx_8202.wav \ + sfx_8203.wav \ + sfx_8204.wav \ + sfx_8205.wav \ + sfx_8206.wav \ + sfx_8207.wav \ + sfx_8208.wav \ + sfx_8209.wav \ + sfx_820.wav \ + sfx_8210.wav \ + sfx_8211.wav \ + sfx_8212.wav \ + sfx_8213.wav \ + sfx_8214.wav \ + sfx_8215.wav \ + sfx_8216.wav \ + sfx_8217.wav \ + sfx_8218.wav \ + sfx_8219.wav \ + sfx_821.wav \ + sfx_8220.wav \ + sfx_8221.wav \ + sfx_8222.wav \ + sfx_8223.wav \ + sfx_8224.wav \ + sfx_8225.wav \ + sfx_8226.wav \ + sfx_8227.wav \ + sfx_8228.wav \ + sfx_8229.wav \ + sfx_822.wav \ + sfx_8230.wav \ + sfx_8231.wav \ + sfx_8232.wav \ + sfx_8233.wav \ + sfx_8234.wav \ + sfx_8235.wav \ + sfx_8236.wav \ + sfx_8237.wav \ + sfx_8238.wav \ + sfx_8239.wav \ + sfx_823.wav \ + sfx_8240.wav \ + sfx_8241.wav \ + sfx_8242.wav \ + sfx_8243.wav \ + sfx_8244.wav \ + sfx_8245.wav \ + sfx_8246.wav \ + sfx_8247.wav \ + sfx_8248.wav \ + sfx_8249.wav \ + sfx_824.wav \ + sfx_8250.wav \ + sfx_8251.wav \ + sfx_8252.wav \ + sfx_8253.wav \ + sfx_8254.wav \ + sfx_8255.wav \ + sfx_8256.wav \ + sfx_8257.wav \ + sfx_8258.wav \ + sfx_8259.wav \ + sfx_825.wav \ + sfx_8260.wav \ + sfx_8261.wav \ + sfx_8262.wav \ + sfx_8263.wav \ + sfx_8264.wav \ + sfx_8265.wav \ + sfx_8266.wav \ + sfx_8267.wav \ + sfx_8268.wav \ + sfx_8269.wav \ + sfx_826.wav \ + sfx_8270.wav \ + sfx_8271.wav \ + sfx_8272.wav \ + sfx_8273.wav \ + sfx_8274.wav \ + sfx_8275.wav \ + sfx_8276.wav \ + sfx_8277.wav \ + sfx_8278.wav \ + sfx_8279.wav \ + sfx_827.wav \ + sfx_8280.wav \ + sfx_8281.wav \ + sfx_8282.wav \ + sfx_8283.wav \ + sfx_8284.wav \ + sfx_8285.wav \ + sfx_8286.wav \ + sfx_8287.wav \ + sfx_8288.wav \ + sfx_8289.wav \ + sfx_828.wav \ + sfx_8290.wav \ + sfx_8291.wav \ + sfx_8292.wav \ + sfx_8293.wav \ + sfx_8294.wav \ + sfx_8295.wav \ + sfx_8296.wav \ + sfx_8297.wav \ + sfx_8298.wav \ + sfx_8299.wav \ + sfx_829.wav \ + sfx_82.wav \ + sfx_8300.wav \ + sfx_8301.wav \ + sfx_8302.wav \ + sfx_8303.wav \ + sfx_8304.wav \ + sfx_8305.wav \ + sfx_8306.wav \ + sfx_8307.wav \ + sfx_8308.wav \ + sfx_8309.wav \ + sfx_830.wav \ + sfx_8310.wav \ + sfx_8311.wav \ + sfx_8312.wav \ + sfx_8313.wav \ + sfx_8314.wav \ + sfx_8315.wav \ + sfx_8316.wav \ + sfx_8317.wav \ + sfx_8318.wav \ + sfx_8319.wav \ + sfx_831.wav \ + sfx_8320.wav \ + sfx_8321.wav \ + sfx_8322.wav \ + sfx_8323.wav \ + sfx_8324.wav \ + sfx_8325.wav \ + sfx_8326.wav \ + sfx_8327.wav \ + sfx_8328.wav \ + sfx_8329.wav \ + sfx_832.wav \ + sfx_8330.wav \ + sfx_8331.wav \ + sfx_8332.wav \ + sfx_8333.wav \ + sfx_8334.wav \ + sfx_8335.wav \ + sfx_8336.wav \ + sfx_8337.wav \ + sfx_8338.wav \ + sfx_8339.wav \ + sfx_833.wav \ + sfx_8340.wav \ + sfx_8341.wav \ + sfx_8342.wav \ + sfx_8343.wav \ + sfx_8344.wav \ + sfx_8345.wav \ + sfx_8346.wav \ + sfx_8347.wav \ + sfx_8348.wav \ + sfx_8349.wav \ + sfx_834.wav \ + sfx_8350.wav \ + sfx_8351.wav \ + sfx_8352.wav \ + sfx_8353.wav \ + sfx_8354.wav \ + sfx_8355.wav \ + sfx_8356.wav \ + sfx_8357.wav \ + sfx_8358.wav \ + sfx_8359.wav \ + sfx_835.wav \ + sfx_8360.wav \ + sfx_8361.wav \ + sfx_8362.wav \ + sfx_8363.wav \ + sfx_8364.wav \ + sfx_8365.wav \ + sfx_8366.wav \ + sfx_8367.wav \ + sfx_8368.wav \ + sfx_8369.wav \ + sfx_836.wav \ + sfx_8370.wav \ + sfx_8371.wav \ + sfx_8372.wav \ + sfx_8373.wav \ + sfx_8374.wav \ + sfx_8375.wav \ + sfx_8376.wav \ + sfx_8377.wav \ + sfx_8378.wav \ + sfx_8379.wav \ + sfx_837.wav \ + sfx_8380.wav \ + sfx_8381.wav \ + sfx_8382.wav \ + sfx_8383.wav \ + sfx_8384.wav \ + sfx_8385.wav \ + sfx_8386.wav \ + sfx_8387.wav \ + sfx_8388.wav \ + sfx_8389.wav \ + sfx_838.wav \ + sfx_8390.wav \ + sfx_8391.wav \ + sfx_8392.wav \ + sfx_8393.wav \ + sfx_8394.wav \ + sfx_8395.wav \ + sfx_8396.wav \ + sfx_8397.wav \ + sfx_8398.wav \ + sfx_8399.wav \ + sfx_839.wav \ + sfx_83.wav \ + sfx_8400.wav \ + sfx_8401.wav \ + sfx_8402.wav \ + sfx_8403.wav \ + sfx_8404.wav \ + sfx_8405.wav \ + sfx_8406.wav \ + sfx_8407.wav \ + sfx_8408.wav \ + sfx_8409.wav \ + sfx_840.wav \ + sfx_8410.wav \ + sfx_8411.wav \ + sfx_8412.wav \ + sfx_8413.wav \ + sfx_8414.wav \ + sfx_8415.wav \ + sfx_8416.wav \ + sfx_8417.wav \ + sfx_8418.wav \ + sfx_8419.wav \ + sfx_841.wav \ + sfx_8420.wav \ + sfx_8421.wav \ + sfx_8422.wav \ + sfx_8423.wav \ + sfx_8424.wav \ + sfx_8425.wav \ + sfx_8426.wav \ + sfx_8427.wav \ + sfx_8428.wav \ + sfx_8429.wav \ + sfx_842.wav \ + sfx_8430.wav \ + sfx_8431.wav \ + sfx_8432.wav \ + sfx_8433.wav \ + sfx_8434.wav \ + sfx_8435.wav \ + sfx_8436.wav \ + sfx_8437.wav \ + sfx_8438.wav \ + sfx_8439.wav \ + sfx_843.wav \ + sfx_8440.wav \ + sfx_8441.wav \ + sfx_8442.wav \ + sfx_8443.wav \ + sfx_8444.wav \ + sfx_8445.wav \ + sfx_8446.wav \ + sfx_8447.wav \ + sfx_8448.wav \ + sfx_8449.wav \ + sfx_844.wav \ + sfx_8450.wav \ + sfx_8451.wav \ + sfx_8452.wav \ + sfx_8453.wav \ + sfx_8454.wav \ + sfx_8455.wav \ + sfx_8456.wav \ + sfx_8457.wav \ + sfx_8458.wav \ + sfx_8459.wav \ + sfx_845.wav \ + sfx_8460.wav \ + sfx_8461.wav \ + sfx_8462.wav \ + sfx_8463.wav \ + sfx_8464.wav \ + sfx_8465.wav \ + sfx_8466.wav \ + sfx_8467.wav \ + sfx_8468.wav \ + sfx_8469.wav \ + sfx_846.wav \ + sfx_8470.wav \ + sfx_8471.wav \ + sfx_8472.wav \ + sfx_8473.wav \ + sfx_8474.wav \ + sfx_8475.wav \ + sfx_8476.wav \ + sfx_8477.wav \ + sfx_8478.wav \ + sfx_8479.wav \ + sfx_847.wav \ + sfx_8480.wav \ + sfx_8481.wav \ + sfx_8482.wav \ + sfx_8483.wav \ + sfx_8484.wav \ + sfx_8485.wav \ + sfx_8486.wav \ + sfx_8487.wav \ + sfx_8488.wav \ + sfx_8489.wav \ + sfx_848.wav \ + sfx_8490.wav \ + sfx_8491.wav \ + sfx_8492.wav \ + sfx_8493.wav \ + sfx_8494.wav \ + sfx_8495.wav \ + sfx_8496.wav \ + sfx_8497.wav \ + sfx_8498.wav \ + sfx_8499.wav \ + sfx_849.wav \ + sfx_84.wav \ + sfx_8500.wav \ + sfx_8501.wav \ + sfx_8502.wav \ + sfx_8503.wav \ + sfx_8504.wav \ + sfx_8505.wav \ + sfx_8506.wav \ + sfx_8507.wav \ + sfx_8508.wav \ + sfx_8509.wav \ + sfx_850.wav \ + sfx_8510.wav \ + sfx_8511.wav \ + sfx_8512.wav \ + sfx_8513.wav \ + sfx_8514.wav \ + sfx_8515.wav \ + sfx_8516.wav \ + sfx_8517.wav \ + sfx_8518.wav \ + sfx_8519.wav \ + sfx_851.wav \ + sfx_8520.wav \ + sfx_8521.wav \ + sfx_8522.wav \ + sfx_8523.wav \ + sfx_8524.wav \ + sfx_8525.wav \ + sfx_8526.wav \ + sfx_8527.wav \ + sfx_8528.wav \ + sfx_8529.wav \ + sfx_852.wav \ + sfx_8530.wav \ + sfx_8531.wav \ + sfx_8532.wav \ + sfx_8533.wav \ + sfx_8534.wav \ + sfx_8535.wav \ + sfx_8536.wav \ + sfx_8537.wav \ + sfx_8538.wav \ + sfx_8539.wav \ + sfx_853.wav \ + sfx_8540.wav \ + sfx_8541.wav \ + sfx_8542.wav \ + sfx_8543.wav \ + sfx_8544.wav \ + sfx_8545.wav \ + sfx_8546.wav \ + sfx_8547.wav \ + sfx_8548.wav \ + sfx_8549.wav \ + sfx_854.wav \ + sfx_8550.wav \ + sfx_8551.wav \ + sfx_8552.wav \ + sfx_8553.wav \ + sfx_8554.wav \ + sfx_8555.wav \ + sfx_8556.wav \ + sfx_8557.wav \ + sfx_8558.wav \ + sfx_8559.wav \ + sfx_855.wav \ + sfx_8560.wav \ + sfx_8561.wav \ + sfx_8562.wav \ + sfx_8563.wav \ + sfx_8564.wav \ + sfx_8565.wav \ + sfx_8566.wav \ + sfx_8567.wav \ + sfx_8568.wav \ + sfx_8569.wav \ + sfx_856.wav \ + sfx_8570.wav \ + sfx_8571.wav \ + sfx_8572.wav \ + sfx_8573.wav \ + sfx_8574.wav \ + sfx_8575.wav \ + sfx_8576.wav \ + sfx_8577.wav \ + sfx_8578.wav \ + sfx_8579.wav \ + sfx_857.wav \ + sfx_8580.wav \ + sfx_8581.wav \ + sfx_8582.wav \ + sfx_8583.wav \ + sfx_8584.wav \ + sfx_8585.wav \ + sfx_8586.wav \ + sfx_8587.wav \ + sfx_8588.wav \ + sfx_8589.wav \ + sfx_858.wav \ + sfx_8590.wav \ + sfx_8591.wav \ + sfx_8592.wav \ + sfx_8593.wav \ + sfx_8594.wav \ + sfx_8595.wav \ + sfx_8596.wav \ + sfx_8597.wav \ + sfx_8598.wav \ + sfx_8599.wav \ + sfx_859.wav \ + sfx_85.wav \ + sfx_8600.wav \ + sfx_8601.wav \ + sfx_8602.wav \ + sfx_8603.wav \ + sfx_8604.wav \ + sfx_8605.wav \ + sfx_8606.wav \ + sfx_8607.wav \ + sfx_8608.wav \ + sfx_8609.wav \ + sfx_860.wav \ + sfx_8610.wav \ + sfx_8611.wav \ + sfx_8612.wav \ + sfx_8613.wav \ + sfx_8614.wav \ + sfx_8615.wav \ + sfx_8616.wav \ + sfx_8617.wav \ + sfx_8618.wav \ + sfx_8619.wav \ + sfx_861.wav \ + sfx_8620.wav \ + sfx_8621.wav \ + sfx_8622.wav \ + sfx_8623.wav \ + sfx_8624.wav \ + sfx_8625.wav \ + sfx_8626.wav \ + sfx_8627.wav \ + sfx_8628.wav \ + sfx_8629.wav \ + sfx_862.wav \ + sfx_8630.wav \ + sfx_8631.wav \ + sfx_8632.wav \ + sfx_8633.wav \ + sfx_8634.wav \ + sfx_8635.wav \ + sfx_8636.wav \ + sfx_8637.wav \ + sfx_8638.wav \ + sfx_8639.wav \ + sfx_863.wav \ + sfx_8640.wav \ + sfx_8641.wav \ + sfx_8642.wav \ + sfx_8643.wav \ + sfx_8644.wav \ + sfx_8645.wav \ + sfx_8646.wav \ + sfx_8647.wav \ + sfx_8648.wav \ + sfx_8649.wav \ + sfx_864.wav \ + sfx_8650.wav \ + sfx_8651.wav \ + sfx_8652.wav \ + sfx_8653.wav \ + sfx_8654.wav \ + sfx_8655.wav \ + sfx_8656.wav \ + sfx_8657.wav \ + sfx_8658.wav \ + sfx_8659.wav \ + sfx_865.wav \ + sfx_8660.wav \ + sfx_8661.wav \ + sfx_8662.wav \ + sfx_8663.wav \ + sfx_8664.wav \ + sfx_8665.wav \ + sfx_8666.wav \ + sfx_8667.wav \ + sfx_8668.wav \ + sfx_8669.wav \ + sfx_866.wav \ + sfx_8670.wav \ + sfx_8671.wav \ + sfx_8672.wav \ + sfx_8673.wav \ + sfx_8674.wav \ + sfx_8675.wav \ + sfx_8676.wav \ + sfx_8677.wav \ + sfx_8678.wav \ + sfx_8679.wav \ + sfx_867.wav \ + sfx_8680.wav \ + sfx_8681.wav \ + sfx_8682.wav \ + sfx_8683.wav \ + sfx_8684.wav \ + sfx_8685.wav \ + sfx_8686.wav \ + sfx_8687.wav \ + sfx_8688.wav \ + sfx_8689.wav \ + sfx_868.wav \ + sfx_8690.wav \ + sfx_8691.wav \ + sfx_8692.wav \ + sfx_8693.wav \ + sfx_8694.wav \ + sfx_8695.wav \ + sfx_8696.wav \ + sfx_8697.wav \ + sfx_8698.wav \ + sfx_8699.wav \ + sfx_869.wav \ + sfx_86.wav \ + sfx_8700.wav \ + sfx_8701.wav \ + sfx_8702.wav \ + sfx_8703.wav \ + sfx_8704.wav \ + sfx_8705.wav \ + sfx_8706.wav \ + sfx_8707.wav \ + sfx_8708.wav \ + sfx_8709.wav \ + sfx_870.wav \ + sfx_8710.wav \ + sfx_8711.wav \ + sfx_8712.wav \ + sfx_8713.wav \ + sfx_8714.wav \ + sfx_8715.wav \ + sfx_8716.wav \ + sfx_8717.wav \ + sfx_8718.wav \ + sfx_8719.wav \ + sfx_871.wav \ + sfx_8720.wav \ + sfx_8721.wav \ + sfx_8722.wav \ + sfx_8723.wav \ + sfx_8724.wav \ + sfx_8725.wav \ + sfx_8726.wav \ + sfx_8727.wav \ + sfx_8728.wav \ + sfx_8729.wav \ + sfx_872.wav \ + sfx_8730.wav \ + sfx_8731.wav \ + sfx_8732.wav \ + sfx_8733.wav \ + sfx_8734.wav \ + sfx_8735.wav \ + sfx_8736.wav \ + sfx_8737.wav \ + sfx_8738.wav \ + sfx_8739.wav \ + sfx_873.wav \ + sfx_8740.wav \ + sfx_8741.wav \ + sfx_8742.wav \ + sfx_8743.wav \ + sfx_8744.wav \ + sfx_8745.wav \ + sfx_8746.wav \ + sfx_8747.wav \ + sfx_8748.wav \ + sfx_8749.wav \ + sfx_874.wav \ + sfx_8750.wav \ + sfx_8751.wav \ + sfx_8752.wav \ + sfx_8753.wav \ + sfx_8754.wav \ + sfx_8755.wav \ + sfx_8756.wav \ + sfx_8757.wav \ + sfx_8758.wav \ + sfx_8759.wav \ + sfx_875.wav \ + sfx_8760.wav \ + sfx_8761.wav \ + sfx_8762.wav \ + sfx_8763.wav \ + sfx_8764.wav \ + sfx_8765.wav \ + sfx_8766.wav \ + sfx_8767.wav \ + sfx_8768.wav \ + sfx_8769.wav \ + sfx_876.wav \ + sfx_8770.wav \ + sfx_8771.wav \ + sfx_8772.wav \ + sfx_8773.wav \ + sfx_8774.wav \ + sfx_8775.wav \ + sfx_8776.wav \ + sfx_8777.wav \ + sfx_8778.wav \ + sfx_8779.wav \ + sfx_877.wav \ + sfx_8780.wav \ + sfx_8781.wav \ + sfx_8782.wav \ + sfx_8783.wav \ + sfx_8784.wav \ + sfx_8785.wav \ + sfx_8786.wav \ + sfx_8787.wav \ + sfx_8788.wav \ + sfx_8789.wav \ + sfx_878.wav \ + sfx_8790.wav \ + sfx_8791.wav \ + sfx_8792.wav \ + sfx_8793.wav \ + sfx_8794.wav \ + sfx_8795.wav \ + sfx_8796.wav \ + sfx_8797.wav \ + sfx_8798.wav \ + sfx_8799.wav \ + sfx_879.wav \ + sfx_87.wav \ + sfx_8800.wav \ + sfx_8801.wav \ + sfx_8802.wav \ + sfx_8803.wav \ + sfx_8804.wav \ + sfx_8805.wav \ + sfx_8806.wav \ + sfx_8807.wav \ + sfx_8808.wav \ + sfx_8809.wav \ + sfx_880.wav \ + sfx_8810.wav \ + sfx_8811.wav \ + sfx_8812.wav \ + sfx_8813.wav \ + sfx_8814.wav \ + sfx_8815.wav \ + sfx_8816.wav \ + sfx_8817.wav \ + sfx_8818.wav \ + sfx_8819.wav \ + sfx_881.wav \ + sfx_8820.wav \ + sfx_8821.wav \ + sfx_8822.wav \ + sfx_8823.wav \ + sfx_8824.wav \ + sfx_8825.wav \ + sfx_8826.wav \ + sfx_8827.wav \ + sfx_8828.wav \ + sfx_8829.wav \ + sfx_882.wav \ + sfx_8830.wav \ + sfx_8831.wav \ + sfx_8832.wav \ + sfx_8833.wav \ + sfx_8834.wav \ + sfx_8835.wav \ + sfx_8836.wav \ + sfx_8837.wav \ + sfx_8838.wav \ + sfx_8839.wav \ + sfx_883.wav \ + sfx_8840.wav \ + sfx_8841.wav \ + sfx_8842.wav \ + sfx_8843.wav \ + sfx_8844.wav \ + sfx_8845.wav \ + sfx_8846.wav \ + sfx_8847.wav \ + sfx_8848.wav \ + sfx_8849.wav \ + sfx_884.wav \ + sfx_8850.wav \ + sfx_8851.wav \ + sfx_8852.wav \ + sfx_8853.wav \ + sfx_8854.wav \ + sfx_8855.wav \ + sfx_8856.wav \ + sfx_8857.wav \ + sfx_8858.wav \ + sfx_8859.wav \ + sfx_885.wav \ + sfx_8860.wav \ + sfx_8861.wav \ + sfx_8862.wav \ + sfx_8863.wav \ + sfx_8864.wav \ + sfx_8865.wav \ + sfx_8866.wav \ + sfx_8867.wav \ + sfx_8868.wav \ + sfx_8869.wav \ + sfx_886.wav \ + sfx_8870.wav \ + sfx_8871.wav \ + sfx_8872.wav \ + sfx_8873.wav \ + sfx_8874.wav \ + sfx_8875.wav \ + sfx_8876.wav \ + sfx_8877.wav \ + sfx_8878.wav \ + sfx_8879.wav \ + sfx_887.wav \ + sfx_8880.wav \ + sfx_8881.wav \ + sfx_8882.wav \ + sfx_8883.wav \ + sfx_8884.wav \ + sfx_8885.wav \ + sfx_8886.wav \ + sfx_8887.wav \ + sfx_8888.wav \ + sfx_8889.wav \ + sfx_888.wav \ + sfx_8890.wav \ + sfx_8891.wav \ + sfx_8892.wav \ + sfx_8893.wav \ + sfx_8894.wav \ + sfx_8895.wav \ + sfx_8896.wav \ + sfx_8897.wav \ + sfx_8898.wav \ + sfx_8899.wav \ + sfx_889.wav \ + sfx_88.wav \ + sfx_8900.wav \ + sfx_8901.wav \ + sfx_8902.wav \ + sfx_8903.wav \ + sfx_8904.wav \ + sfx_8905.wav \ + sfx_8906.wav \ + sfx_8907.wav \ + sfx_8908.wav \ + sfx_8909.wav \ + sfx_890.wav \ + sfx_8910.wav \ + sfx_8911.wav \ + sfx_8912.wav \ + sfx_8913.wav \ + sfx_8914.wav \ + sfx_8915.wav \ + sfx_8916.wav \ + sfx_8917.wav \ + sfx_8918.wav \ + sfx_8919.wav \ + sfx_891.wav \ + sfx_8920.wav \ + sfx_8921.wav \ + sfx_8922.wav \ + sfx_8923.wav \ + sfx_8924.wav \ + sfx_8925.wav \ + sfx_8926.wav \ + sfx_8927.wav \ + sfx_8928.wav \ + sfx_8929.wav \ + sfx_892.wav \ + sfx_8930.wav \ + sfx_8931.wav \ + sfx_8932.wav \ + sfx_8933.wav \ + sfx_8934.wav \ + sfx_8935.wav \ + sfx_8936.wav \ + sfx_8937.wav \ + sfx_8938.wav \ + sfx_8939.wav \ + sfx_893.wav \ + sfx_8940.wav \ + sfx_8941.wav \ + sfx_8942.wav \ + sfx_8943.wav \ + sfx_8944.wav \ + sfx_8945.wav \ + sfx_8946.wav \ + sfx_8947.wav \ + sfx_8948.wav \ + sfx_8949.wav \ + sfx_894.wav \ + sfx_8950.wav \ + sfx_8951.wav \ + sfx_8952.wav \ + sfx_8953.wav \ + sfx_8954.wav \ + sfx_8955.wav \ + sfx_8956.wav \ + sfx_8957.wav \ + sfx_8958.wav \ + sfx_8959.wav \ + sfx_895.wav \ + sfx_8960.wav \ + sfx_8961.wav \ + sfx_8962.wav \ + sfx_8963.wav \ + sfx_8964.wav \ + sfx_8965.wav \ + sfx_8966.wav \ + sfx_8967.wav \ + sfx_8968.wav \ + sfx_8969.wav \ + sfx_896.wav \ + sfx_8970.wav \ + sfx_8971.wav \ + sfx_8972.wav \ + sfx_8973.wav \ + sfx_8974.wav \ + sfx_8975.wav \ + sfx_8976.wav \ + sfx_8977.wav \ + sfx_8978.wav \ + sfx_8979.wav \ + sfx_897.wav \ + sfx_8980.wav \ + sfx_8981.wav \ + sfx_8982.wav \ + sfx_8983.wav \ + sfx_8984.wav \ + sfx_8985.wav \ + sfx_8986.wav \ + sfx_8987.wav \ + sfx_8988.wav \ + sfx_8989.wav \ + sfx_898.wav \ + sfx_8990.wav \ + sfx_8991.wav \ + sfx_8992.wav \ + sfx_8993.wav \ + sfx_8994.wav \ + sfx_8995.wav \ + sfx_8996.wav \ + sfx_8997.wav \ + sfx_8998.wav \ + sfx_8999.wav \ + sfx_899.wav \ + sfx_89.wav \ + sfx_8.wav \ + sfx_9000.wav \ + sfx_9001.wav \ + sfx_9002.wav \ + sfx_9003.wav \ + sfx_9004.wav \ + sfx_9005.wav \ + sfx_9006.wav \ + sfx_9007.wav \ + sfx_9008.wav \ + sfx_9009.wav \ + sfx_900.wav \ + sfx_9010.wav \ + sfx_9011.wav \ + sfx_9012.wav \ + sfx_9013.wav \ + sfx_9014.wav \ + sfx_9015.wav \ + sfx_9016.wav \ + sfx_9017.wav \ + sfx_9018.wav \ + sfx_9019.wav \ + sfx_901.wav \ + sfx_9020.wav \ + sfx_9021.wav \ + sfx_9022.wav \ + sfx_9023.wav \ + sfx_9024.wav \ + sfx_9025.wav \ + sfx_9026.wav \ + sfx_9027.wav \ + sfx_9028.wav \ + sfx_9029.wav \ + sfx_902.wav \ + sfx_9030.wav \ + sfx_9031.wav \ + sfx_9032.wav \ + sfx_9033.wav \ + sfx_9034.wav \ + sfx_9035.wav \ + sfx_9036.wav \ + sfx_9037.wav \ + sfx_9038.wav \ + sfx_9039.wav \ + sfx_903.wav \ + sfx_9040.wav \ + sfx_9041.wav \ + sfx_9042.wav \ + sfx_9043.wav \ + sfx_9044.wav \ + sfx_9045.wav \ + sfx_9046.wav \ + sfx_9047.wav \ + sfx_9048.wav \ + sfx_9049.wav \ + sfx_904.wav \ + sfx_9050.wav \ + sfx_9051.wav \ + sfx_9052.wav \ + sfx_9053.wav \ + sfx_9054.wav \ + sfx_9055.wav \ + sfx_9056.wav \ + sfx_9057.wav \ + sfx_9058.wav \ + sfx_9059.wav \ + sfx_905.wav \ + sfx_9060.wav \ + sfx_9061.wav \ + sfx_9062.wav \ + sfx_9063.wav \ + sfx_9064.wav \ + sfx_9065.wav \ + sfx_9066.wav \ + sfx_9067.wav \ + sfx_9068.wav \ + sfx_9069.wav \ + sfx_906.wav \ + sfx_9070.wav \ + sfx_9071.wav \ + sfx_9072.wav \ + sfx_9073.wav \ + sfx_9074.wav \ + sfx_9075.wav \ + sfx_9076.wav \ + sfx_9077.wav \ + sfx_9078.wav \ + sfx_9079.wav \ + sfx_907.wav \ + sfx_9080.wav \ + sfx_9081.wav \ + sfx_9082.wav \ + sfx_9083.wav \ + sfx_9084.wav \ + sfx_9085.wav \ + sfx_9086.wav \ + sfx_9087.wav \ + sfx_9088.wav \ + sfx_9089.wav \ + sfx_908.wav \ + sfx_9090.wav \ + sfx_9091.wav \ + sfx_9092.wav \ + sfx_9093.wav \ + sfx_9094.wav \ + sfx_9095.wav \ + sfx_9096.wav \ + sfx_9097.wav \ + sfx_9098.wav \ + sfx_9099.wav \ + sfx_909.wav \ + sfx_90.wav \ + sfx_9100.wav \ + sfx_9101.wav \ + sfx_9102.wav \ + sfx_9103.wav \ + sfx_9104.wav \ + sfx_9105.wav \ + sfx_9106.wav \ + sfx_9107.wav \ + sfx_9108.wav \ + sfx_9109.wav \ + sfx_910.wav \ + sfx_9110.wav \ + sfx_9111.wav \ + sfx_9112.wav \ + sfx_9113.wav \ + sfx_9114.wav \ + sfx_9115.wav \ + sfx_9116.wav \ + sfx_9117.wav \ + sfx_9118.wav \ + sfx_9119.wav \ + sfx_911.wav \ + sfx_9120.wav \ + sfx_9121.wav \ + sfx_9122.wav \ + sfx_9123.wav \ + sfx_9124.wav \ + sfx_9125.wav \ + sfx_9126.wav \ + sfx_9127.wav \ + sfx_9128.wav \ + sfx_9129.wav \ + sfx_912.wav \ + sfx_9130.wav \ + sfx_9131.wav \ + sfx_9132.wav \ + sfx_9133.wav \ + sfx_9134.wav \ + sfx_9135.wav \ + sfx_9136.wav \ + sfx_9137.wav \ + sfx_9138.wav \ + sfx_9139.wav \ + sfx_913.wav \ + sfx_9140.wav \ + sfx_9141.wav \ + sfx_9142.wav \ + sfx_9143.wav \ + sfx_9144.wav \ + sfx_9145.wav \ + sfx_9146.wav \ + sfx_9147.wav \ + sfx_9148.wav \ + sfx_9149.wav \ + sfx_914.wav \ + sfx_9150.wav \ + sfx_9151.wav \ + sfx_9152.wav \ + sfx_9153.wav \ + sfx_9154.wav \ + sfx_9155.wav \ + sfx_9156.wav \ + sfx_9157.wav \ + sfx_9158.wav \ + sfx_9159.wav \ + sfx_915.wav \ + sfx_9160.wav \ + sfx_9161.wav \ + sfx_9162.wav \ + sfx_9163.wav \ + sfx_9164.wav \ + sfx_9165.wav \ + sfx_9166.wav \ + sfx_9167.wav \ + sfx_9168.wav \ + sfx_9169.wav \ + sfx_916.wav \ + sfx_9170.wav \ + sfx_9171.wav \ + sfx_9172.wav \ + sfx_9173.wav \ + sfx_9174.wav \ + sfx_9175.wav \ + sfx_9176.wav \ + sfx_9177.wav \ + sfx_9178.wav \ + sfx_9179.wav \ + sfx_917.wav \ + sfx_9180.wav \ + sfx_9181.wav \ + sfx_9182.wav \ + sfx_9183.wav \ + sfx_9184.wav \ + sfx_9185.wav \ + sfx_9186.wav \ + sfx_9187.wav \ + sfx_9188.wav \ + sfx_9189.wav \ + sfx_918.wav \ + sfx_9190.wav \ + sfx_9191.wav \ + sfx_9192.wav \ + sfx_9193.wav \ + sfx_9194.wav \ + sfx_9195.wav \ + sfx_9196.wav \ + sfx_9197.wav \ + sfx_9198.wav \ + sfx_9199.wav \ + sfx_919.wav \ + sfx_91.wav \ + sfx_9200.wav \ + sfx_9201.wav \ + sfx_9202.wav \ + sfx_9203.wav \ + sfx_9204.wav \ + sfx_9205.wav \ + sfx_9206.wav \ + sfx_9207.wav \ + sfx_9208.wav \ + sfx_9209.wav \ + sfx_920.wav \ + sfx_9210.wav \ + sfx_9211.wav \ + sfx_9212.wav \ + sfx_9213.wav \ + sfx_9214.wav \ + sfx_9215.wav \ + sfx_9216.wav \ + sfx_9217.wav \ + sfx_9218.wav \ + sfx_9219.wav \ + sfx_921.wav \ + sfx_9220.wav \ + sfx_9221.wav \ + sfx_9222.wav \ + sfx_9223.wav \ + sfx_9224.wav \ + sfx_9225.wav \ + sfx_9226.wav \ + sfx_9227.wav \ + sfx_9228.wav \ + sfx_9229.wav \ + sfx_922.wav \ + sfx_9230.wav \ + sfx_9231.wav \ + sfx_9232.wav \ + sfx_9233.wav \ + sfx_9234.wav \ + sfx_9235.wav \ + sfx_9236.wav \ + sfx_9237.wav \ + sfx_9238.wav \ + sfx_9239.wav \ + sfx_923.wav \ + sfx_9240.wav \ + sfx_9241.wav \ + sfx_9242.wav \ + sfx_9243.wav \ + sfx_9244.wav \ + sfx_9245.wav \ + sfx_9246.wav \ + sfx_9247.wav \ + sfx_9248.wav \ + sfx_9249.wav \ + sfx_924.wav \ + sfx_9250.wav \ + sfx_9251.wav \ + sfx_9252.wav \ + sfx_9253.wav \ + sfx_9254.wav \ + sfx_9255.wav \ + sfx_9256.wav \ + sfx_9257.wav \ + sfx_9258.wav \ + sfx_9259.wav \ + sfx_925.wav \ + sfx_9260.wav \ + sfx_9261.wav \ + sfx_9262.wav \ + sfx_9263.wav \ + sfx_9264.wav \ + sfx_9265.wav \ + sfx_9266.wav \ + sfx_9267.wav \ + sfx_9268.wav \ + sfx_9269.wav \ + sfx_926.wav \ + sfx_9270.wav \ + sfx_9271.wav \ + sfx_9272.wav \ + sfx_9273.wav \ + sfx_9274.wav \ + sfx_9275.wav \ + sfx_9276.wav \ + sfx_9277.wav \ + sfx_9278.wav \ + sfx_9279.wav \ + sfx_927.wav \ + sfx_9280.wav \ + sfx_9281.wav \ + sfx_9282.wav \ + sfx_9283.wav \ + sfx_9284.wav \ + sfx_9285.wav \ + sfx_9286.wav \ + sfx_9287.wav \ + sfx_9288.wav \ + sfx_9289.wav \ + sfx_928.wav \ + sfx_9290.wav \ + sfx_9291.wav \ + sfx_9292.wav \ + sfx_9293.wav \ + sfx_9294.wav \ + sfx_9295.wav \ + sfx_9296.wav \ + sfx_9297.wav \ + sfx_9298.wav \ + sfx_9299.wav \ + sfx_929.wav \ + sfx_92.wav \ + sfx_9300.wav \ + sfx_9301.wav \ + sfx_9302.wav \ + sfx_9303.wav \ + sfx_9304.wav \ + sfx_9305.wav \ + sfx_9306.wav \ + sfx_9307.wav \ + sfx_9308.wav \ + sfx_9309.wav \ + sfx_930.wav \ + sfx_9310.wav \ + sfx_9311.wav \ + sfx_9312.wav \ + sfx_9313.wav \ + sfx_9314.wav \ + sfx_9315.wav \ + sfx_9316.wav \ + sfx_9317.wav \ + sfx_9318.wav \ + sfx_9319.wav \ + sfx_931.wav \ + sfx_9320.wav \ + sfx_9321.wav \ + sfx_9322.wav \ + sfx_9323.wav \ + sfx_9324.wav \ + sfx_9325.wav \ + sfx_9326.wav \ + sfx_9327.wav \ + sfx_9328.wav \ + sfx_9329.wav \ + sfx_932.wav \ + sfx_9330.wav \ + sfx_9331.wav \ + sfx_9332.wav \ + sfx_9333.wav \ + sfx_9334.wav \ + sfx_9335.wav \ + sfx_9336.wav \ + sfx_9337.wav \ + sfx_9338.wav \ + sfx_9339.wav \ + sfx_933.wav \ + sfx_9340.wav \ + sfx_9341.wav \ + sfx_9342.wav \ + sfx_9343.wav \ + sfx_9344.wav \ + sfx_9345.wav \ + sfx_9346.wav \ + sfx_9347.wav \ + sfx_9348.wav \ + sfx_9349.wav \ + sfx_934.wav \ + sfx_9350.wav \ + sfx_9351.wav \ + sfx_9352.wav \ + sfx_9353.wav \ + sfx_9354.wav \ + sfx_9355.wav \ + sfx_9356.wav \ + sfx_9357.wav \ + sfx_9358.wav \ + sfx_9359.wav \ + sfx_935.wav \ + sfx_9360.wav \ + sfx_9361.wav \ + sfx_9362.wav \ + sfx_9363.wav \ + sfx_9364.wav \ + sfx_9365.wav \ + sfx_9366.wav \ + sfx_9367.wav \ + sfx_9368.wav \ + sfx_9369.wav \ + sfx_936.wav \ + sfx_9370.wav \ + sfx_9371.wav \ + sfx_9372.wav \ + sfx_9373.wav \ + sfx_9374.wav \ + sfx_9375.wav \ + sfx_9376.wav \ + sfx_9377.wav \ + sfx_9378.wav \ + sfx_9379.wav \ + sfx_937.wav \ + sfx_9380.wav \ + sfx_9381.wav \ + sfx_9382.wav \ + sfx_9383.wav \ + sfx_9384.wav \ + sfx_9385.wav \ + sfx_9386.wav \ + sfx_9387.wav \ + sfx_9388.wav \ + sfx_9389.wav \ + sfx_938.wav \ + sfx_9390.wav \ + sfx_9391.wav \ + sfx_9392.wav \ + sfx_9393.wav \ + sfx_9394.wav \ + sfx_9395.wav \ + sfx_9396.wav \ + sfx_9397.wav \ + sfx_9398.wav \ + sfx_9399.wav \ + sfx_939.wav \ + sfx_93.wav \ + sfx_9400.wav \ + sfx_9401.wav \ + sfx_9402.wav \ + sfx_9403.wav \ + sfx_9404.wav \ + sfx_9405.wav \ + sfx_9406.wav \ + sfx_9407.wav \ + sfx_9408.wav \ + sfx_9409.wav \ + sfx_940.wav \ + sfx_9410.wav \ + sfx_9411.wav \ + sfx_9412.wav \ + sfx_9413.wav \ + sfx_9414.wav \ + sfx_9415.wav \ + sfx_9416.wav \ + sfx_9417.wav \ + sfx_9418.wav \ + sfx_9419.wav \ + sfx_941.wav \ + sfx_9420.wav \ + sfx_9421.wav \ + sfx_9422.wav \ + sfx_9423.wav \ + sfx_9424.wav \ + sfx_9425.wav \ + sfx_9426.wav \ + sfx_9427.wav \ + sfx_9428.wav \ + sfx_9429.wav \ + sfx_942.wav \ + sfx_9430.wav \ + sfx_9431.wav \ + sfx_9432.wav \ + sfx_9433.wav \ + sfx_9434.wav \ + sfx_9435.wav \ + sfx_9436.wav \ + sfx_9437.wav \ + sfx_9438.wav \ + sfx_9439.wav \ + sfx_943.wav \ + sfx_9440.wav \ + sfx_9441.wav \ + sfx_9442.wav \ + sfx_9443.wav \ + sfx_9444.wav \ + sfx_9445.wav \ + sfx_9446.wav \ + sfx_9447.wav \ + sfx_9448.wav \ + sfx_9449.wav \ + sfx_944.wav \ + sfx_9450.wav \ + sfx_9451.wav \ + sfx_9452.wav \ + sfx_9453.wav \ + sfx_9454.wav \ + sfx_9455.wav \ + sfx_9456.wav \ + sfx_9457.wav \ + sfx_9458.wav \ + sfx_9459.wav \ + sfx_945.wav \ + sfx_9460.wav \ + sfx_9461.wav \ + sfx_9462.wav \ + sfx_9463.wav \ + sfx_9464.wav \ + sfx_9465.wav \ + sfx_9466.wav \ + sfx_9467.wav \ + sfx_9468.wav \ + sfx_9469.wav \ + sfx_946.wav \ + sfx_9470.wav \ + sfx_9471.wav \ + sfx_9472.wav \ + sfx_9473.wav \ + sfx_9474.wav \ + sfx_9475.wav \ + sfx_9476.wav \ + sfx_9477.wav \ + sfx_9478.wav \ + sfx_9479.wav \ + sfx_947.wav \ + sfx_9480.wav \ + sfx_9481.wav \ + sfx_9482.wav \ + sfx_9483.wav \ + sfx_9484.wav \ + sfx_9485.wav \ + sfx_9486.wav \ + sfx_9487.wav \ + sfx_9488.wav \ + sfx_9489.wav \ + sfx_948.wav \ + sfx_9490.wav \ + sfx_9491.wav \ + sfx_9492.wav \ + sfx_9493.wav \ + sfx_9494.wav \ + sfx_9495.wav \ + sfx_9496.wav \ + sfx_9497.wav \ + sfx_9498.wav \ + sfx_9499.wav \ + sfx_949.wav \ + sfx_94.wav \ + sfx_9500.wav \ + sfx_9501.wav \ + sfx_9502.wav \ + sfx_9503.wav \ + sfx_9504.wav \ + sfx_9505.wav \ + sfx_9506.wav \ + sfx_9507.wav \ + sfx_9508.wav \ + sfx_9509.wav \ + sfx_950.wav \ + sfx_9510.wav \ + sfx_9511.wav \ + sfx_9512.wav \ + sfx_9513.wav \ + sfx_9514.wav \ + sfx_9515.wav \ + sfx_9516.wav \ + sfx_9517.wav \ + sfx_9518.wav \ + sfx_9519.wav \ + sfx_951.wav \ + sfx_9520.wav \ + sfx_9521.wav \ + sfx_9522.wav \ + sfx_9523.wav \ + sfx_9524.wav \ + sfx_9525.wav \ + sfx_9526.wav \ + sfx_9527.wav \ + sfx_9528.wav \ + sfx_9529.wav \ + sfx_952.wav \ + sfx_9530.wav \ + sfx_9531.wav \ + sfx_9532.wav \ + sfx_9533.wav \ + sfx_9534.wav \ + sfx_9535.wav \ + sfx_9536.wav \ + sfx_9537.wav \ + sfx_9538.wav \ + sfx_9539.wav \ + sfx_953.wav \ + sfx_9540.wav \ + sfx_9541.wav \ + sfx_9542.wav \ + sfx_9543.wav \ + sfx_9544.wav \ + sfx_9545.wav \ + sfx_9546.wav \ + sfx_9547.wav \ + sfx_9548.wav \ + sfx_9549.wav \ + sfx_954.wav \ + sfx_9550.wav \ + sfx_9551.wav \ + sfx_9552.wav \ + sfx_9553.wav \ + sfx_9554.wav \ + sfx_9555.wav \ + sfx_9556.wav \ + sfx_9557.wav \ + sfx_9558.wav \ + sfx_9559.wav \ + sfx_955.wav \ + sfx_9560.wav \ + sfx_9561.wav \ + sfx_9562.wav \ + sfx_9563.wav \ + sfx_9564.wav \ + sfx_9565.wav \ + sfx_9566.wav \ + sfx_9567.wav \ + sfx_9568.wav \ + sfx_9569.wav \ + sfx_956.wav \ + sfx_9570.wav \ + sfx_9571.wav \ + sfx_9572.wav \ + sfx_9573.wav \ + sfx_9574.wav \ + sfx_9575.wav \ + sfx_9576.wav \ + sfx_9577.wav \ + sfx_9578.wav \ + sfx_9579.wav \ + sfx_957.wav \ + sfx_9580.wav \ + sfx_9581.wav \ + sfx_9582.wav \ + sfx_9583.wav \ + sfx_9584.wav \ + sfx_9585.wav \ + sfx_9586.wav \ + sfx_9587.wav \ + sfx_9588.wav \ + sfx_9589.wav \ + sfx_958.wav \ + sfx_9590.wav \ + sfx_9591.wav \ + sfx_9592.wav \ + sfx_9593.wav \ + sfx_9594.wav \ + sfx_9595.wav \ + sfx_9596.wav \ + sfx_9597.wav \ + sfx_9598.wav \ + sfx_9599.wav \ + sfx_959.wav \ + sfx_95.wav \ + sfx_9600.wav \ + sfx_9601.wav \ + sfx_9602.wav \ + sfx_9603.wav \ + sfx_9604.wav \ + sfx_9605.wav \ + sfx_9606.wav \ + sfx_9607.wav \ + sfx_9608.wav \ + sfx_9609.wav \ + sfx_960.wav \ + sfx_9610.wav \ + sfx_9611.wav \ + sfx_9612.wav \ + sfx_9613.wav \ + sfx_9614.wav \ + sfx_9615.wav \ + sfx_9616.wav \ + sfx_9617.wav \ + sfx_9618.wav \ + sfx_9619.wav \ + sfx_961.wav \ + sfx_9620.wav \ + sfx_9621.wav \ + sfx_9622.wav \ + sfx_9623.wav \ + sfx_9624.wav \ + sfx_9625.wav \ + sfx_9626.wav \ + sfx_9627.wav \ + sfx_9628.wav \ + sfx_9629.wav \ + sfx_962.wav \ + sfx_9630.wav \ + sfx_9631.wav \ + sfx_9632.wav \ + sfx_9633.wav \ + sfx_9634.wav \ + sfx_9635.wav \ + sfx_9636.wav \ + sfx_9637.wav \ + sfx_9638.wav \ + sfx_9639.wav \ + sfx_963.wav \ + sfx_9640.wav \ + sfx_9641.wav \ + sfx_9642.wav \ + sfx_9643.wav \ + sfx_9644.wav \ + sfx_9645.wav \ + sfx_9646.wav \ + sfx_9647.wav \ + sfx_9648.wav \ + sfx_9649.wav \ + sfx_964.wav \ + sfx_9650.wav \ + sfx_9651.wav \ + sfx_9652.wav \ + sfx_9653.wav \ + sfx_9654.wav \ + sfx_9655.wav \ + sfx_9656.wav \ + sfx_9657.wav \ + sfx_9658.wav \ + sfx_9659.wav \ + sfx_965.wav \ + sfx_9660.wav \ + sfx_9661.wav \ + sfx_9662.wav \ + sfx_9663.wav \ + sfx_9664.wav \ + sfx_9665.wav \ + sfx_9666.wav \ + sfx_9667.wav \ + sfx_9668.wav \ + sfx_9669.wav \ + sfx_966.wav \ + sfx_9670.wav \ + sfx_9671.wav \ + sfx_9672.wav \ + sfx_9673.wav \ + sfx_9674.wav \ + sfx_9675.wav \ + sfx_9676.wav \ + sfx_9677.wav \ + sfx_9678.wav \ + sfx_9679.wav \ + sfx_967.wav \ + sfx_9680.wav \ + sfx_9681.wav \ + sfx_9682.wav \ + sfx_9683.wav \ + sfx_9684.wav \ + sfx_9685.wav \ + sfx_9686.wav \ + sfx_9687.wav \ + sfx_9688.wav \ + sfx_9689.wav \ + sfx_968.wav \ + sfx_9690.wav \ + sfx_9691.wav \ + sfx_9692.wav \ + sfx_9693.wav \ + sfx_9694.wav \ + sfx_9695.wav \ + sfx_9696.wav \ + sfx_9697.wav \ + sfx_9698.wav \ + sfx_9699.wav \ + sfx_969.wav \ + sfx_96.wav \ + sfx_9700.wav \ + sfx_9701.wav \ + sfx_9702.wav \ + sfx_9703.wav \ + sfx_9704.wav \ + sfx_9705.wav \ + sfx_9706.wav \ + sfx_9707.wav \ + sfx_9708.wav \ + sfx_9709.wav \ + sfx_970.wav \ + sfx_9710.wav \ + sfx_9711.wav \ + sfx_9712.wav \ + sfx_9713.wav \ + sfx_9714.wav \ + sfx_9715.wav \ + sfx_9716.wav \ + sfx_9717.wav \ + sfx_9718.wav \ + sfx_9719.wav \ + sfx_971.wav \ + sfx_9720.wav \ + sfx_9721.wav \ + sfx_9722.wav \ + sfx_9723.wav \ + sfx_9724.wav \ + sfx_9725.wav \ + sfx_9726.wav \ + sfx_9727.wav \ + sfx_9728.wav \ + sfx_9729.wav \ + sfx_972.wav \ + sfx_9730.wav \ + sfx_9731.wav \ + sfx_9732.wav \ + sfx_9733.wav \ + sfx_9734.wav \ + sfx_9735.wav \ + sfx_9736.wav \ + sfx_9737.wav \ + sfx_9738.wav \ + sfx_9739.wav \ + sfx_973.wav \ + sfx_9740.wav \ + sfx_9741.wav \ + sfx_9742.wav \ + sfx_9743.wav \ + sfx_9744.wav \ + sfx_9745.wav \ + sfx_9746.wav \ + sfx_9747.wav \ + sfx_9748.wav \ + sfx_9749.wav \ + sfx_974.wav \ + sfx_9750.wav \ + sfx_9751.wav \ + sfx_9752.wav \ + sfx_9753.wav \ + sfx_9754.wav \ + sfx_9755.wav \ + sfx_9756.wav \ + sfx_9757.wav \ + sfx_9758.wav \ + sfx_9759.wav \ + sfx_975.wav \ + sfx_9760.wav \ + sfx_9761.wav \ + sfx_9762.wav \ + sfx_9763.wav \ + sfx_9764.wav \ + sfx_9765.wav \ + sfx_9766.wav \ + sfx_9767.wav \ + sfx_9768.wav \ + sfx_9769.wav \ + sfx_976.wav \ + sfx_9770.wav \ + sfx_9771.wav \ + sfx_9772.wav \ + sfx_9773.wav \ + sfx_9774.wav \ + sfx_9775.wav \ + sfx_9776.wav \ + sfx_9777.wav \ + sfx_9778.wav \ + sfx_9779.wav \ + sfx_977.wav \ + sfx_9780.wav \ + sfx_9781.wav \ + sfx_9782.wav \ + sfx_9783.wav \ + sfx_9784.wav \ + sfx_9785.wav \ + sfx_9786.wav \ + sfx_9787.wav \ + sfx_9788.wav \ + sfx_9789.wav \ + sfx_978.wav \ + sfx_9790.wav \ + sfx_9791.wav \ + sfx_9792.wav \ + sfx_9793.wav \ + sfx_9794.wav \ + sfx_9795.wav \ + sfx_9796.wav \ + sfx_9797.wav \ + sfx_9798.wav \ + sfx_9799.wav \ + sfx_979.wav \ + sfx_97.wav \ + sfx_9800.wav \ + sfx_9801.wav \ + sfx_9802.wav \ + sfx_9803.wav \ + sfx_9804.wav \ + sfx_9805.wav \ + sfx_9806.wav \ + sfx_9807.wav \ + sfx_9808.wav \ + sfx_9809.wav \ + sfx_980.wav \ + sfx_9810.wav \ + sfx_9811.wav \ + sfx_9812.wav \ + sfx_9813.wav \ + sfx_9814.wav \ + sfx_9815.wav \ + sfx_9816.wav \ + sfx_9817.wav \ + sfx_9818.wav \ + sfx_9819.wav \ + sfx_981.wav \ + sfx_9820.wav \ + sfx_9821.wav \ + sfx_9822.wav \ + sfx_9823.wav \ + sfx_9824.wav \ + sfx_9825.wav \ + sfx_9826.wav \ + sfx_9827.wav \ + sfx_9828.wav \ + sfx_9829.wav \ + sfx_982.wav \ + sfx_9830.wav \ + sfx_9831.wav \ + sfx_9832.wav \ + sfx_9833.wav \ + sfx_9834.wav \ + sfx_9835.wav \ + sfx_9836.wav \ + sfx_9837.wav \ + sfx_9838.wav \ + sfx_9839.wav \ + sfx_983.wav \ + sfx_9840.wav \ + sfx_9841.wav \ + sfx_9842.wav \ + sfx_9843.wav \ + sfx_9844.wav \ + sfx_9845.wav \ + sfx_9846.wav \ + sfx_9847.wav \ + sfx_9848.wav \ + sfx_9849.wav \ + sfx_984.wav \ + sfx_9850.wav \ + sfx_9851.wav \ + sfx_9852.wav \ + sfx_9853.wav \ + sfx_9854.wav \ + sfx_9855.wav \ + sfx_9856.wav \ + sfx_9857.wav \ + sfx_9858.wav \ + sfx_9859.wav \ + sfx_985.wav \ + sfx_9860.wav \ + sfx_9861.wav \ + sfx_9862.wav \ + sfx_9863.wav \ + sfx_9864.wav \ + sfx_9865.wav \ + sfx_9866.wav \ + sfx_9867.wav \ + sfx_9868.wav \ + sfx_9869.wav \ + sfx_986.wav \ + sfx_9870.wav \ + sfx_9871.wav \ + sfx_9872.wav \ + sfx_9873.wav \ + sfx_9874.wav \ + sfx_9875.wav \ + sfx_9876.wav \ + sfx_9877.wav \ + sfx_9878.wav \ + sfx_9879.wav \ + sfx_987.wav \ + sfx_9880.wav \ + sfx_9881.wav \ + sfx_9882.wav \ + sfx_9883.wav \ + sfx_9884.wav \ + sfx_9885.wav \ + sfx_9886.wav \ + sfx_9887.wav \ + sfx_9888.wav \ + sfx_9889.wav \ + sfx_988.wav \ + sfx_9890.wav \ + sfx_9891.wav \ + sfx_9892.wav \ + sfx_9893.wav \ + sfx_9894.wav \ + sfx_9895.wav \ + sfx_9896.wav \ + sfx_9897.wav \ + sfx_9898.wav \ + sfx_9899.wav \ + sfx_989.wav \ + sfx_98.wav \ + sfx_9900.wav \ + sfx_9901.wav \ + sfx_9902.wav \ + sfx_9903.wav \ + sfx_9904.wav \ + sfx_9905.wav \ + sfx_9906.wav \ + sfx_9907.wav \ + sfx_9908.wav \ + sfx_9909.wav \ + sfx_990.wav \ + sfx_9910.wav \ + sfx_9911.wav \ + sfx_9912.wav \ + sfx_9913.wav \ + sfx_9914.wav \ + sfx_9915.wav \ + sfx_9916.wav \ + sfx_9917.wav \ + sfx_9918.wav \ + sfx_9919.wav \ + sfx_991.wav \ + sfx_9920.wav \ + sfx_9921.wav \ + sfx_9922.wav \ + sfx_9923.wav \ + sfx_9924.wav \ + sfx_9925.wav \ + sfx_9926.wav \ + sfx_9927.wav \ + sfx_9928.wav \ + sfx_9929.wav \ + sfx_992.wav \ + sfx_9930.wav \ + sfx_9931.wav \ + sfx_9932.wav \ + sfx_9933.wav \ + sfx_9934.wav \ + sfx_9935.wav \ + sfx_9936.wav \ + sfx_9937.wav \ + sfx_9938.wav \ + sfx_9939.wav \ + sfx_993.wav \ + sfx_9940.wav \ + sfx_994.wav \ + sfx_995.wav \ + sfx_996.wav \ + sfx_997.wav \ + sfx_998.wav \ + sfx_999.wav \ + sfx_99.wav \ + sfx_9.wav + \ No newline at end of file diff --git a/miami/sfxlooplist.mk b/miami/sfxlooplist.mk new file mode 100644 index 00000000..65ea8ab2 --- /dev/null +++ b/miami/sfxlooplist.mk @@ -0,0 +1,34 @@ +SFX_LOOP_WAV = \ + sfx_0_loop.wav \ + sfx_176_loop.wav \ + sfx_1_loop.wav \ + sfx_25_loop.wav \ + sfx_287_loop.wav \ + sfx_2_loop.wav \ + sfx_331_loop.wav \ + sfx_384_loop.wav \ + sfx_387_loop.wav \ + sfx_390_loop.wav \ + sfx_393_loop.wav \ + sfx_396_loop.wav \ + sfx_399_loop.wav \ + sfx_3_loop.wav \ + sfx_402_loop.wav \ + sfx_408_loop.wav \ + sfx_411_loop.wav \ + sfx_414_loop.wav \ + sfx_419_loop.wav \ + sfx_420_loop.wav \ + sfx_422_loop.wav \ + sfx_423_loop.wav \ + sfx_432_loop.wav \ + sfx_436_loop.wav \ + sfx_440_loop.wav \ + sfx_444_loop.wav \ + sfx_4_loop.wav \ + sfx_5_loop.wav \ + sfx_62_loop.wav \ + sfx_6_loop.wav \ + sfx_7_loop.wav \ + sfx_82_loop.wav + \ No newline at end of file diff --git a/miami/sim.mk b/miami/sim.mk new file mode 100644 index 00000000..ced1fe22 --- /dev/null +++ b/miami/sim.mk @@ -0,0 +1,50 @@ +TARGET ?= dca-miami-sim.elf + +all: $(TARGET) + +include common.mk + +OBJS = $(RE3_OBJS) $(RW_OBJS) \ + ../src/miami/audio/sampman_null.o + +OBJS_SIM=$(OBJS:.o=.sim.o) \ + ../vendor/koshle/hlekos.sim.o \ + ../vendor/koshle/hlematrix3d.sim.o \ + ../vendor/koshle/hlepvr_mem.sim.o \ + ../vendor/koshle/hlepvr_prim.sim.o \ + ../vendor/koshle/hlepvr_scene.sim.o \ + ../vendor/koshle/hlepvr_misc.sim.o \ + ../vendor/koshle/hlepvr_init_term.sim.o \ + ../vendor/koshle/hlepvr_buffers.sim.o \ + ../vendor/koshle/hlepvr_irq.sim.o \ + ../vendor/koshle/hlepvr_fog.sim.o \ + \ + ../vendor/emu/emu/window.sim.o \ + \ + ../vendor/emu/lxdream/tacore.sim.o3 \ + \ + ../vendor/emu/refsw/pvr_mem.sim.o3 \ + ../vendor/emu/refsw/pvr_regs.sim.o3 \ + ../vendor/emu/refsw/refsw_lists.sim.o3 \ + ../vendor/emu/refsw/refsw_tile.sim.o3 \ + ../vendor/emu/refsw/TexUtils.sim.o3 \ + +DEPS_SIM1=$(OBJS_SIM:.o=.d) +DEPS_SIM=$(DEPS_SIM1:.o3=.d) + +CXXFLAGS+= -MMD -MP + +%.sim.o: %.c + $(CC) -c -O0 -g -fno-pic -no-pie -o $@ $(CFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -DMACOS64 $< +%.sim.o: %.cpp + $(CXX) -c -O0 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -DMACOS64 $< +%.sim.o3: %.cpp + $(CXX) -c -O3 -g -fno-pic -no-pie -o $@ $(CXXFLAGS) -I../vendor/koshle -I../vendor/emu -U_WIN32 -UWIN32 -UWINNT -Ui386 -DDC_SIM -DMACOS64 $< + +clean: + -rm -f $(OBJS_SIM) $(TARGET) + +$(TARGET): $(OBJS_SIM) + $(CXX) -fno-pic -no-pie -o $(TARGET) $(OBJS_SIM) -lX11 + +-include $(DEPS_SIM) \ No newline at end of file diff --git a/miami/texlist.mk b/miami/texlist.mk new file mode 100644 index 00000000..a7093251 --- /dev/null +++ b/miami/texlist.mk @@ -0,0 +1,1362 @@ +IMG_TEXTURES = \ + admiral.txd \ + air_carshow.txd \ + airgrndb.txd \ + airplan.txd \ + airporterminal.txd \ + airport.txd \ + airsignn.txd \ + airstuff2.txd \ + airtrain.txd \ + ak47.txd \ + alleyprop.txd \ + ambulan.txd \ + angel.txd \ + ap_airstuff1.txd \ + ap_billboard.txd \ + apbitsN.txd \ + ap_build1.txd \ + ap_build2.txd \ + ap_build3.txd \ + ap_build4.txd \ + ap_buildings2.txd \ + ap_carpark3.txd \ + apcarparkN.txd \ + ap_checkpoints.txd \ + apchecpointN.txd \ + ap_ci_indus2.txd \ + ap_clothes.txd \ + ap_francisland.txd \ + ap_fuel1.txd \ + ap_fuel2.txd \ + ap_fuel3.txd \ + ap_ground1.txd \ + apgroundN.txd \ + apJumboN.txd \ + ap_jumbos.txd \ + ap_learjets.txd \ + ap_LODbit.txd \ + ap_LOD.txd \ + ap_misc1bit.txd \ + ap_misc1.txd \ + ap_newprops1opac.txd \ + Apoulet.txd \ + ap_roadsect1.txd \ + aproadsN.txd \ + ap_roads.txd \ + ap_runsigns1.txd \ + ap_seaplanprop.txd \ + ap_seasplane.txd \ + ap_terminAint.txd \ + ap_terminal1boards.txd \ + ap_terminal1.txd \ + ap_termwindows.txd \ + ap_viceairport.txd \ + armour.txd \ + army.txd \ + baggage.txd \ + bakstgstuff.txd \ + bank_ext_int.txd \ + banshee.txd \ + barracks.txd \ + barrelexpos.txd \ + barrierm.txd \ + barrier.txd \ + bat.txd \ + bb_barroomtrans.txd \ + bb_barroom.txd \ + bb_exterior.txd \ + beachball.txd \ + bed.txd \ + benches.txd \ + benchm.txd \ + benson.txd \ + bfinject.txd \ + BFOBE.txd \ + bfori.txd \ + bfost.txd \ + bfotr.txd \ + bfybe.txd \ + BFYPR.txd \ + bfyri.txd \ + BFYST.txd \ + bga.txd \ + bgb.txd \ + bilbrd1.txd \ + BillBrd.txd \ + bitcrusher5.txd \ + bka.txd \ + bkb.txd \ + blistac.txd \ + bloodra.txd \ + bloodrb.txd \ + BMOBE.txd \ + bmodk.txd \ + bmost.txd \ + bmotr.txd \ + bmybb.txd \ + bmybe.txd \ + bmybu.txd \ + bmycr.txd \ + bmypi.txd \ + bmyri.txd \ + bmyst.txd \ + bnk_propy.txd \ + boathouse.txd \ + bobcat.txd \ + bomb.txd \ + bonus.txd \ + bounca.txd \ + boxes.txd \ + boxville.txd \ + brassknuckle.txd \ + breifcase.txd \ + brfcase.txd \ + bridge.txd \ + buddyshot.txd \ + buddy.txd \ + buildinghan.txd \ + buildingsite2.txd \ + buildsite.txd \ + buoy.txd \ + burger.txd \ + burrito.txd \ + bustopm.txd \ + bus.txd \ + bwidge.txd \ + cabbie.txd \ + caddy.txd \ + camera.txd \ + canopy.txd \ + carback.txd \ + card.txd \ + carwreck.txd \ + Cba.txd \ + Cbb.txd \ + ccfan.txd \ + cdrivra.txd \ + cdrivrb.txd \ + cellphone.txd \ + centralresac1.txd \ + cgona.txd \ + cgonb.txd \ + cgonc.txd \ + cgoona.txd \ + cgoonb.txd \ + chairsnstufh.txd \ + chairsntable.txd \ + cheetah.txd \ + chef.txd \ + chem2.txd \ + chem.txd \ + chnsaw.txd \ + chopper.txd \ + chromegun.txd \ + ci_backlot.txd \ + ci_ballust.txd \ + ci_dirtcorn.txd \ + ci_filmstuff1.txd \ + ci_industrial.txd \ + ci_island1.txd \ + ci_island3.txd \ + ci_LOD.txd \ + ci_mansion1.txd \ + ci_mansion2.txd \ + ci_mansionint1.txd \ + ci_mansrails.txd \ + ci_pornset.txd \ + ci_posters.txd \ + ci_redhead.txd \ + ci_setstuff1.txd \ + ci_setstuff2.txd \ + ci_setstuff4.txd \ + ci_shadcay.txd \ + ci_shadnit.txd \ + ci_studio1.txd \ + ci_studio2.txd \ + ci_studio3.txd \ + ci_studio4.txd \ + ci_studio5.txd \ + ci_studio6.txd \ + ci_studionew1.txd \ + Cla.txd \ + Clb.txd \ + clchr.txd \ + cleaver.txd \ + cl_ext1.txd \ + cl_ext2.txd \ + cl_ext3.txd \ + cl_vegbits.txd \ + cl_yacht.txd \ + cmraman.txd \ + coach.txd \ + coastg.txd \ + coffeshpext.txd \ + cofintout.txd \ + cofshad.txd \ + cofshopintdet.txd \ + cofshopint.txd \ + cofshopwind.txd \ + coknife.txd \ + colonel.txd \ + colphon.txd \ + colt45.txd \ + coltray.txd \ + comet.txd \ + compound_fence.txd \ + copdoorstuff.txd \ + cop_fake_ext02.txd \ + cop_fake_ext03.txd \ + cop_fake_ext04.txd \ + cop_oceandrv.txd \ + copshop.txd \ + cop.txd \ + corpsedrown.txd \ + courier.txd \ + cpyth.txd \ + craigpackage.txd \ + cranes.txd \ + crate.txd \ + crewa.txd \ + crewb.txd \ + crusher1.txd \ + crusher2.txd \ + crusher3.txd \ + crusherbn.txd \ + crusherb.txd \ + CSAlsb2.txd \ + CSAlscb2.txd \ + CSAlscb.txd \ + CSassA.txd \ + CSassB.txd \ + CSassC.txd \ + CSavery.txd \ + CSbigm.txd \ + CSBJ.txd \ + CSBuddy.txd \ + CScamj.txd \ + cs_cam.txd \ + CSCandy.txd \ + CScgona.txd \ + CScgonb.txd \ + CScgonc.txd \ + cschopb.txd \ + cs_chop.txd \ + CScolo.txd \ + CScougr.txd \ + CSdeal.txd \ + CSdiaz.txd \ + CSDick.txd \ + CSdirec.txd \ + CSdisp.txd \ + CSdlove.txd \ + CSDoris.txd \ + CSdwayn.txd \ + cservra.txd \ + cservrb.txd \ + CSFiller.txd \ + CSganga.txd \ + CSgangb.txd \ + CSgangc.txd \ + CSgona.txd \ + CSgonb.txd \ + CSgonc.txd \ + CSGonz.txd \ + CShlary.txd \ + CSJetro.txd \ + CSJezz.txd \ + CSJuggz.txd \ + CSKelly.txd \ + CSkent.txd \ + CSken.txd \ + CSLance.txd \ + cs_limo.txd \ + CSMaude.txd \ + CSmerc2.txd \ + CSMerc.txd \ + CSmike.txd \ + CSmitch.txd \ + CSpablo.txd \ + CSPapa.txd \ + CSPepe.txd \ + CSPercy.txd \ + CSphil.txd \ + CSplay10.txd \ + CSplay11.txd \ + CSplay2.txd \ + CSplay3.txd \ + CSplay4.txd \ + CSplay5.txd \ + CSplay6.txd \ + CSplay7.txd \ + CSplay8.txd \ + CSplay9.txd \ + CSplay.txd \ + cs_reef.txd \ + CSrich.txd \ + CSsonny.txd \ + CSumbto.txd \ + CSzepp.txd \ + cuban.txd \ + C_worker.txd \ + dcfan.txd \ + deaddodo.txd \ + deagl.txd \ + delcase.txd \ + deliext.txd \ + deliint.txd \ + deluxo.txd \ + destructo.txd \ + dgoona.txd \ + dgoonb.txd \ + dgoonc.txd \ + diaz.txd \ + dinghy.txd \ + dkcargoshp.txd \ + dkglue.txd \ + dk_houses.txd \ + dk_midbuilds.txd \ + dk_safecrack.txd \ + doccrane.txd \ + docentrance.txd \ + docfactories.txd \ + dock_bridge.txd \ + dockfence.txd \ + dockgrass.txd \ + dockgrnd.txd \ + docklight.txd \ + dockroads.txd \ + docksb.txd \ + dock_ships.txd \ + dock_shipsunder.txd \ + docks.txd \ + dolphin.txd \ + drchair.txd \ + drink.txd \ + drubish.txd \ + drugfactint.txd \ + drugpak.txd \ + drycleaners.txd \ + dshotgn.txd \ + dt_ammu3.txd \ + dt_ammu.txd \ + dt_blimp.txd \ + dt_bloodring.txd \ + dt_bluebuild.txd \ + dt_bridges.txd \ + dt_brij_LOD.txd \ + dt_compoundroof.txd \ + dt_cube_build.txd \ + dt_CUNT2.txd \ + dt_CUNT.txd \ + dt_dirtrng.txd \ + dt_egg1.txd \ + dt_firestation2.txd \ + dt_firestation.txd \ + dt_hole_build.txd \ + dt_hospital.txd \ + dt_hotring.txd \ + dt_lites.txd \ + dt_LOD2.txd \ + dt_LOD.txd \ + dt_modern1.txd \ + dt_modern2.txd \ + dt_nbeach.txd \ + dtn_cables2.txd \ + dtn_cables.txd \ + dt_newtest1.txd \ + dt_newtest2.txd \ + dtn_ground2.txd \ + dtn_ground.txd \ + dtn_roads.txd \ + dt_ntrack.txd \ + dtn_veg.txd \ + dt_qbert.txd \ + dts_bikebar_ext.txd \ + dts_copshop.txd \ + dt_scyscrap2.txd \ + dts_grassbits.txd \ + dts_ground.txd \ + dt_sheraton.txd \ + dt_shops130.txd \ + dt_shops13.txd \ + dt_shops14.txd \ + dt_shops180.txd \ + dt_shops182.txd \ + dt_shops188.txd \ + dt_shops18.txd \ + dt_shops1.txd \ + dt_shops2.txd \ + dt_shops3.txd \ + dt_shops5.txd \ + dt_shops6.txd \ + dt_shops.txd \ + dts_LODbig.txd \ + dts_LODsmall2.txd \ + dts_LODsmall.txd \ + dts_nbeach.txd \ + dts_nitelites.txd \ + dts_office.txd \ + dts_roads.txd \ + dts_shops1.txd \ + dts_shops_new1.txd \ + dts_shops.txd \ + dt_stadium.txd \ + dt_tall_build01.txd \ + dt_topshops1100.txd \ + dt_topshops130.txd \ + dt_topshops1.txd \ + dt_topshops4.txd \ + dt_topshops50.txd \ + dt_topshops5.txd \ + dt_topshops6.txd \ + dt_topshops.txd \ + dt_VCnews.txd \ + dynamite.txd \ + dynbarrels.txd \ + dynbuket.txd \ + dyncones.txd \ + dyn_drugs.txd \ + dynhydrent.txd \ + dynjunk.txd \ + dyn_keycard.txd \ + dynnewstnd2.txd \ + dynnewstnd.txd \ + dynphnm.txd \ + dynphn.txd \ + dynpostbx.txd \ + dynrecycle.txd \ + dynsigns.txd \ + dyntraffic.txd \ + dyn_trash.txd \ + dzplant.txd \ + electricgate.txd \ + enforcer.txd \ + escstep.txd \ + esperant.txd \ + espreso.txd \ + expltrk.txd \ + faggio.txd \ + fanani.txd \ + fbicar.txd \ + fbiranch.txd \ + fbi.txd \ + fencehaiti.txd \ + fences.txd \ + fffinhaiti.txd \ + fingers.txd \ + fireman.txd \ + firem.txd \ + firetruk.txd \ + fish1.txd \ + fish2.txd \ + fish3.txd \ + flame.txd \ + flatbedbak.txd \ + flatbed.txd \ + floozya.txd \ + floozyb.txd \ + floozyc.txd \ + foodcourt.txd \ + freeway.txd \ + FSfa.txd \ + fuckedcarlh.txd \ + funhiati.txd \ + gangb.txd \ + gangbur.txd \ + gangshop.txd \ + gasgren.txd \ + gcfan.txd \ + Gda.txd \ + Gdb.txd \ + Generic.txd \ + gfflag.txd \ + gf_hedge1.txd \ + gf_LOD.txd \ + gf_nbtecland.txd \ + gf_northbridge.txd \ + gfnorthisland3.txd \ + gfpalms.txd \ + gf_roadblock.txd \ + gfwater.txd \ + glendale.txd \ + golf3.txd \ + golf_bridge.txd \ + golf_clubhouse1.txd \ + golf_clubhouse2.txd \ + golfclub.txd \ + gpass.txd \ + grasspatch.txd \ + greenwoo.txd \ + grenade.txd \ + gtatrees.txd \ + guesta.txd \ + guestb.txd \ + guestc.txd \ + guestd.txd \ + gunbox.txd \ + guncolt.txd \ + habuildblok.txd \ + hahospital.txd \ + hai_carshowint.txd \ + hai_hoj.txd \ + haitbuild41.txd \ + haitbuildb.txd \ + haitbuildsea1.txd \ + haitbuildsea2.txd \ + haitcontext.txd \ + haitcont.txd \ + haitiblk1sj.txd \ + haitinwire.txd \ + haitisig.txd \ + haititag.txd \ + haitplntn.txd \ + haitplnt.txd \ + haitrdbuilf.txd \ + haiwash.txd \ + hammer.txd \ + hatrifl.txd \ + havbasketball.txd \ + hav_health.txd \ + hav_police.txd \ + helipad.txd \ + helixbarrier.txd \ + hermes.txd \ + hfobe.txd \ + hfori.txd \ + HFOST.txd \ + HFOTR.txd \ + hfybe.txd \ + HFYBU.txd \ + HFYCG.txd \ + HFYMD.txd \ + HFYPR.txd \ + HFYRI.txd \ + HFYST.txd \ + h_haibloce.txd \ + hi_cutbnk_alp.txd \ + hi_cutbnkchr.txd \ + hi_cutbnkcoch.txd \ + hi_cutbnkmain3.txd \ + hi_cutbnkmainnw.txd \ + hi_cutbnkrecpt.txd \ + hi_cutbnkvault.txd \ + hi_cutclof_opa.txd \ + hi_cutclubbar.txd \ + hi_cutcluboffice.txd \ + hi_cutclwaltst.txd \ + hi_cutcouch.txd \ + hi_cuthoomintshad.txd \ + hi_cuthotel1.txd \ + hi_cuthtl2.txd \ + hi_cuthtl4.txd \ + hi_cuthtlalp.txd \ + hi_cutint1.txd \ + hi_cutintprops.txd \ + hi_cutlaw1.txd \ + hi_cutlaw2.txd \ + hi_cutlaw3b.txd \ + hi_cutlaw3c.txd \ + hi_cutlaw3.txd \ + hi_cutlawshad.txd \ + hi_cutmaincl.txd \ + hi_cutmanshad.txd \ + hi_cutmngof.txd \ + hi_cutpropy.txd \ + hi_cutrshad.txd \ + hi_cutsafehoos.txd \ + hi_cutsafe.txd \ + hi_cutsdrm_1.txd \ + hi_cutsdrm_2.txd \ + hi_cutsdrm_3.txd \ + hi_cuttblprop.txd \ + hlrycar.txd \ + hmobe.txd \ + hmoca.txd \ + hmori.txd \ + hmost.txd \ + hmotr.txd \ + hmyap.txd \ + hmybe.txd \ + hmyri.txd \ + hmyst.txd \ + Hna.txd \ + Hnb.txd \ + hotelroomint2.txd \ + hotelroomint.txd \ + hot_mobint.txd \ + hotrina.txd \ + hotrinb.txd \ + hotring.txd \ + hot_roomtrans.txd \ + htable.txd \ + htdoorhtlalp.txd \ + htl_ext03.txd \ + htlpoolbase.txd \ + hunter.txd \ + icons1.txd \ + icons2.txd \ + icons3.txd \ + icons4.txd \ + icons5.txd \ + icons6.txd \ + icons7.txd \ + icons8.txd \ + icons9.txd \ + icons.txd \ + idaho.txd \ + IGalscb.txd \ + IGbuddy.txd \ + IGBudy2.txd \ + IGBudy3.txd \ + IGCandy.txd \ + IGColon.txd \ + IGDiaz.txd \ + IGDick.txd \ + IGGonz.txd \ + IGHlary.txd \ + IGHlry2.txd \ + IGJezz.txd \ + IGKen.txd \ + IGMerc2.txd \ + IGMerc.txd \ + IGMike2.txd \ + IGmike.txd \ + IGPercy.txd \ + IGphil2.txd \ + IGPhil3.txd \ + IGPhil.txd \ + IGSonny.txd \ + ihaitcutmf.txd \ + ihsnidxc.txd \ + infernus.txd \ + ingramsl.txd \ + intstairod.txd \ + invite.txd \ + islandlolodb.txd \ + islandlolodm.txd \ + islandmanext1.txd \ + jellyfish1.txd \ + jellyfish2.txd \ + jetmax.txd \ + jfoto.txd \ + jmoto.txd \ + jumpbox.txd \ + junk.txd \ + jwcoffin.txd \ + jwlr.txd \ + jzgirla.txd \ + jzgirlb.txd \ + katana.txd \ + kaufman.txd \ + kentpaul.txd \ + kettle.txd \ + KgoonA.txd \ + KgoonB.txd \ + kickstart.txd \ + knifecur.txd \ + landjump.txd \ + landstal.txd \ + laser.txd \ + lawcutext.txd \ + lawdoor.txd \ + lawyer.txd \ + lcfan.txd \ + lf_mediastage.txd \ + lhaith9.txd \ + lhaiths.txd \ + lhaithutn.txd \ + lhaitiblk1fg.txd \ + lhaitiblk2.txd \ + lhaitiblk4sj.txd \ + lhaitiblk6f.txd \ + lhaitiblk6k.txd \ + lhaitiblkhuty.txd \ + lhaitiblkplat.txd \ + lhaitiblkplt2.txd \ + lhaitiblksick.txd \ + lhalleysect.txd \ + lhbillbrd.txd \ + lhblbrd2.txd \ + lhblbrd3.txd \ + lhblbrd8.txd \ + lhbuild3.txd \ + lhcabbyext.txd \ + lhcabby.txd \ + lh_dockstuff1.txd \ + lh_dockstuffN1.txd \ + lhfuckercars.txd \ + lhgangshop.txd \ + lh_garagebits.txd \ + lhgardens.txd \ + lhglue.txd \ + lh_haibljki.txd \ + lh_haiblockc1.txd \ + lh_haiblockc2.txd \ + lhhotelbg.txd \ + lh_imp_exp1.txd \ + lh_imp_exp2.txd \ + lh_imp_exp3.txd \ + lh_imp_exp4.txd \ + lhjetty.txd \ + lhmachine1mn.txd \ + lhmachine1m.txd \ + lhparkline.txd \ + lh_PWbuild1grnd.txd \ + lh_PWbuild1.txd \ + lh_PWbuild2.txd \ + lh_PWbuild3.txd \ + LH_PWInterior2.txd \ + LH_PWInterior.txd \ + lhrace1.txd \ + lhrace2.txd \ + lhrace3.txd \ + lhrace4.txd \ + lhrace5.txd \ + lhrace6.txd \ + lh_racemap.txd \ + lhroadsign.txd \ + lh_roads.txd \ + lhrooft2.txd \ + lhrooft.txd \ + lh_taxi.txd \ + linerun.txd \ + lithablk12.txd \ + lithablk22.txd \ + lithablk4.txd \ + lithablk9.txd \ + lithahotel.txd \ + lithahouse.txd \ + lithawaste.txd \ + litthabg1.txd \ + litthablk6.txd \ + littlehab2.txd \ + littlhab1.txd \ + littlhablk7.txd \ + lobster.txd \ + lobtray.txd \ + LODairpN.txd \ + LODbridge.txd \ + LODbuildws07.txd \ + lod_docks2.txd \ + lod_docks.txd \ + LODhaitibig.txd \ + LODhaitinbig.txd \ + LODhaitin.txd \ + LODhaiti.txd \ + LODhavanabrg.txd \ + LODhavana.txd \ + lodhavbig.txd \ + lod_mall.txd \ + lod_man_grnds.txd \ + LODnbchw.txd \ + LODnbeachbtbig.txd \ + LODnbeach.txd \ + LODnbeachwbig.txd \ + LODodriveN.txd \ + LODodrive.txd \ + lod_starbig.txd \ + lod_starsmall.txd \ + LODwashnbrdge.txd \ + LODwashsth.txd \ + LODwshsth99.txd \ + LODwsouth08.txd \ + LODxrefhot.txd \ + lofbanner.txd \ + lounger.txd \ + lovefist.txd \ + luggage.txd \ + lwchara.txd \ + lwcharb.txd \ + m4.txd \ + m60.txd \ + machete.txd \ + mainclub2.txd \ + male01.txd \ + mall_ammu.txd \ + mallcarpark.txd \ + mallext.txd \ + mall_ground.txd \ + mallitems.txd \ + mallplants.txd \ + mall_props.txd \ + mallroof.txd \ + malltotal.txd \ + mall_veg.txd \ + manana.txd \ + manbackside.txd \ + man_cellar.txd \ + manf.txd \ + man_grnds_kb.txd \ + man_hall_2.txd \ + man_hall.txd \ + mannewdoors.txd \ + man_pool1.txd \ + man_pool2.txd \ + man_pool_shado.txd \ + man_props.txd \ + man_scarface.txd \ + mansion_details2.txd \ + mansion_details.txd \ + mansiondoor.txd \ + mansionfence.txd \ + mansion_new.txd \ + mansion_Rubbish.txd \ + mansionwall.txd \ + man_twr.txd \ + man_veg_ext.txd \ + man_wtr_ftr.txd \ + marinacpark.txd \ + marquis.txd \ + maverick.txd \ + Mba.txd \ + Mbb.txd \ + mbtbemp.txd \ + mbtbful.txd \ + mcane.txd \ + mcompounda1.txd \ + medic.txd \ + merced.txd \ + mesa.txd \ + metal.txd \ + mfour.txd \ + mgoona.txd \ + miamibin.txd \ + militcompound3.txd \ + mine.txd \ + minigun2.txd \ + minigun.txd \ + missile.txd \ + mitraffic.txd \ + ml_books.txd \ + ml_clothes.txd \ + ml_coffee.txd \ + ml_jewellers.txd \ + ml_tuneless.txd \ + molotov.txd \ + moonbeam.txd \ + mp5lng.txd \ + mpfshrt.txd \ + mporna.txd \ + mrwhoop.txd \ + mserver.txd \ + mule.txd \ + nairport.txd \ + nb_bridge1.txd \ + nbchbtjetty.txd \ + nbchdrtrack.txd \ + nbchflw.txd \ + nbdcprmndfnc.txd \ + nbdecopromnd.txd \ + nbdecoshop01.txd \ + nbdecoshop02.txd \ + nbdecoshop03.txd \ + nbeachbit03rails.txd \ + nbeachbridge.txd \ + nbeachgen.txd \ + nbeach_lod.txd \ + nbeachneon.txd \ + nbeachpayns.txd \ + nbeachrdsign.txd \ + nbeachwgen.txd \ + nbecbtwbrdge.txd \ + nbecdiving.txd \ + nbecland12bball.txd \ + nbecland12gates.txd \ + nbglue.txd \ + nbhospital.txd \ + nbhotel01.txd \ + nbhotel02.txd \ + nbhotel03.txd \ + nbhotel04.txd \ + nbhotel05.txd \ + nbhotel06.txd \ + nbhotel07.txd \ + nbhotel08.txd \ + nb_police.txd \ + nbrooft.txd \ + nbsavehotel.txd \ + nbt_apartpool01.txd \ + nbt_apartpool.txd \ + nbt_barfence01.txd \ + nbtecland.txd \ + nbt_hotel01.txd \ + nbt_hotel02.txd \ + nbt_hotel03glass.txd \ + nbt_hotel03.txd \ + nbt_hotel04.txd \ + nbt_hotel05.txd \ + nbt_hotel06balcony.txd \ + nbt_hotel06.txd \ + nbt_hotel07fence.txd \ + nbt_hotel07.txd \ + nbthotel08fence.txd \ + nbthotel08.txd \ + nbt_htlpoolbar01.txd \ + nbt_htlpoolbase.txd \ + nbt_htlpoolrm.txd \ + nbt_mans04balcony.txd \ + nbt_mansion01.txd \ + nbt_mansion02.txd \ + nbt_mansion03.txd \ + nbt_mansion04.txd \ + nbt_mansionbase01.txd \ + nbt_mansionbase02.txd \ + nbt_mnsnwtr.txd \ + nbt_pizzaplace.txd \ + nbtroofnext.txd \ + nbtrooftop.txd \ + nbtshopfronts02.txd \ + nbtshops.txd \ + nbtsmallres.txd \ + nbwbridge.txd \ + nbw_grssypatch.txd \ + nbw_hotel06balcony.txd \ + nbw_hotel06.txd \ + nbwjetty.txd \ + nbw_pinkblock.txd \ + nbwtoolshp5.txd \ + ncarwreck.txd \ + neonnd.txd \ + newashbuild1.txd \ + newbuilding01.txd \ + newrampn.txd \ + newramp.txd \ + newroof.txd \ + newspadbuild.txd \ + newstand.txd \ + nitestick.txd \ + northbuild.txd \ + nubuilding1glass.txd \ + oceanic.txd \ + oceanstrip10.txd \ + oceanstrip2.txd \ + ocmiamistrip12.txd \ + ocmiamistrip16.txd \ + ocmiamistrip5.txd \ + ocmiamistrip6.txd \ + ocmiamistrip7.txd \ + ocmiamistrip8.txd \ + ocmiamistrip9.txd \ + ocmiamistrip9X.txd \ + ocmiamistrip.txd \ + ocnmiamistrip.txd \ + od_beachstuff.txd \ + od_bighotel.txd \ + od_bighotpool.txd \ + od_bigshops2.txd \ + od_bphq.txd \ + od_chariot.txd \ + od_clubback.txd \ + od_clubout.txd \ + od_cop_interior.txd \ + od_fence.txd \ + od_hotels1.txd \ + od_lighthousein.txd \ + od_lighthouse.txd \ + od_neons1.txd \ + odneontest.txd \ + odnglue.txd \ + odnhotelblk.txd \ + odnhotelspr.txd \ + odnroads.txd \ + odnrooft.txd \ + odnwire.txd \ + od_watertower.txd \ + od_waterwrx.txd \ + package1.txd \ + packer.txd \ + patriot.txd \ + pcj600.txd \ + peren.txd \ + petrolcan.txd \ + petrol.txd \ + Pga.txd \ + Pgb.txd \ + pharmacyint.txd \ + pheonix.txd \ + philbmb.txd \ + philinter.txd \ + philss.txd \ + phnplay.txd \ + phoncol.txd \ + pitchrb.txd \ + pizzaboy.txd \ + play10.txd \ + play11.txd \ + play12.txd \ + player2.txd \ + player3.txd \ + player4.txd \ + player5.txd \ + player6.txd \ + player7.txd \ + player8.txd \ + player9.txd \ + player.txd \ + plc_stinger.txd \ + plyphon.txd \ + polbals.txd \ + policechair.txd \ + police.txd \ + polmav.txd \ + pony.txd \ + poolcue.txd \ + poolq.txd \ + portabarrier.txd \ + predator.txd \ + printra.txd \ + printrb.txd \ + printrc.txd \ + propbarstuff.txd \ + prop_pizzabox.txd \ + psycho.txd \ + python.txd \ + radar00.txd \ + radar01.txd \ + radar02.txd \ + radar03.txd \ + radar04.txd \ + radar05.txd \ + radar06.txd \ + radar07.txd \ + radar08.txd \ + radar09.txd \ + radar10.txd \ + radar11.txd \ + radar12.txd \ + radar13.txd \ + radar14.txd \ + radar15.txd \ + radar16.txd \ + radar17.txd \ + radar18.txd \ + radar19.txd \ + radar20.txd \ + radar21.txd \ + radar22.txd \ + radar23.txd \ + radar24.txd \ + radar25.txd \ + radar26.txd \ + radar27.txd \ + radar28.txd \ + radar29.txd \ + radar30.txd \ + radar31.txd \ + radar32.txd \ + radar33.txd \ + radar34.txd \ + radar35.txd \ + radar36.txd \ + radar37.txd \ + radar38.txd \ + radar39.txd \ + radar40.txd \ + radar41.txd \ + radar42.txd \ + radar43.txd \ + radar44.txd \ + radar45.txd \ + radar46.txd \ + radar47.txd \ + radar48.txd \ + radar49.txd \ + radar50.txd \ + radar51.txd \ + radar52.txd \ + radar53.txd \ + radar54.txd \ + radar55.txd \ + radar56.txd \ + radar57.txd \ + radar58.txd \ + radar59.txd \ + radar60.txd \ + radar61.txd \ + radar62.txd \ + radar63.txd \ + rafneonsign.txd \ + ramp2.txd \ + rancher.txd \ + rcbandit.txd \ + rcbaron.txd \ + rcbomb.txd \ + rcgoblin.txd \ + rcraider.txd \ + recstudio2.txd \ + recstudio.txd \ + reefer.txd \ + regina.txd \ + rhino.txd \ + rio.txd \ + roadsign.txd \ + rocketla.txd \ + rock.txd \ + rocky.txd \ + romero.txd \ + roofbit.txd \ + ruger.txd \ + rumpo.txd \ + sabretur.txd \ + sabre.txd \ + sam.txd \ + sanchez.txd \ + sandcastle.txd \ + sandking.txd \ + satdish.txd \ + scarinterior.txd \ + scenery.txd \ + schair.txd \ + screwdriver.txd \ + seabed.txd \ + seaspar.txd \ + securica.txd \ + sentinel.txd \ + sentxs.txd \ + seveneleven.txd \ + sfrenda.txd \ + sfrendb.txd \ + Sga.txd \ + Sgb.txd \ + sgc.txd \ + sgoona.txd \ + sgoonb.txd \ + sharedalleyod.txd \ + shared_beach.txd \ + sharedbiggrass.txd \ + shark.txd \ + shipthisgamenow.txd \ + shootra.txd \ + shootrb.txd \ + shopfronts03.txd \ + shotgspa.txd \ + shotvcr.txd \ + signs.txd \ + sjmahaitiground.txd \ + sjmbncrusher5.txd \ + sjmh1haitiroad.txd \ + sjmh5haitiroad.txd \ + sjmhahaitiground.txd \ + sjmhbghaitiground.txd \ + sjmjcrusher4.txd \ + sjmroadhaitiN.txd \ + sjmsprayn.txd \ + sjmspray.txd \ + S_keep.txd \ + skimmer.txd \ + smashbarr.txd \ + sniper.txd \ + spad_buildnew.txd \ + spad_main.txd \ + spad_opac.txd \ + spad_shado.txd \ + spad_water.txd \ + spand.txd \ + spandxa.txd \ + spandxb.txd \ + sparrow.txd \ + speeder.txd \ + speed.txd \ + squalo.txd \ + ssjmcrusher5.txd \ + stagebar.txd \ + stagestuff.txd \ + stallion.txd \ + standext.txd \ + sta_pinkblock.txd \ + star1b.txd \ + star1.txd \ + star2.txd \ + star3.txd \ + star4.txd \ + star5.txd \ + starbits06.txd \ + starbits17.txd \ + starbits22.txd \ + starbits24.txd \ + starbits49.txd \ + starbits50.txd \ + starbits51.txd \ + starbridge.txd \ + starbuildsite.txd \ + stardepot.txd \ + starf.txd \ + StarLD.txd \ + starmansionst.txd \ + starpool.txd \ + starroad.txd \ + starvillas01.txd \ + starwaterfrnt01.txd \ + stilty.txd \ + stinger.txd \ + stmansion.txd \ + streetlights.txd \ + stretch.txd \ + Stripa.txd \ + stripback.txd \ + stripb.txd \ + stripc.txd \ + stripinter.txd \ + striplights.txd \ + strpclbsgns.txd \ + stunt.txd \ + subcrates.txd \ + submarine.txd \ + svntray.txd \ + swashlandb.txd \ + swashnwbuild5.txd \ + swat.txd \ + swimupbar.txd \ + swshneon.txd \ + swshtelgrph.txd \ + targets.txd \ + taxi_d.txd \ + taxi.txd \ + teargas.txd \ + tec9.txd \ + telegraph.txd \ + telephait.txd \ + telly.txd \ + tikiprops01.txd \ + tit_wank.txd \ + topfun.txd \ + towerdoor.txd \ + transbits2.txd \ + trash.txd \ + treeshad.txd \ + tropic.txd \ + tunnel.txd \ + turtle.txd \ + uzi.txd \ + vcnmav.txd \ + vegetation.txd \ + vend.txd \ + vice1.txd \ + vice2.txd \ + vice3.txd \ + vice4.txd \ + vice5.txd \ + vice6.txd \ + vice7.txd \ + vice8.txd \ + vicechee.txd \ + virgo.txd \ + voodoo.txd \ + walton.txd \ + wash_ammu.txd \ + washbballcrt.txd \ + washbuild120.txd \ + washbuild124.txd \ + washbuild13.txd \ + washbuild15.txd \ + washbuild1.txd \ + washbuild41.txd \ + washbuild43.txd \ + washbuild44.txd \ + washbuild5.txd \ + washbuild6.txd \ + washbuild7.txd \ + washcorn2.txd \ + washcpark2.txd \ + washfence128.txd \ + wash_hosp.txd \ + washing.txd \ + washlandb.txd \ + washland.txd \ + washmallnew.txd \ + washmallpark.txd \ + washmarina.txd \ + washmiamistrip11.txd \ + washmiamistrip13.txd \ + washmiamistrip2.txd \ + washmiamistrip3.txd \ + washmiamistrip4.txd \ + washnewbuild1.txd \ + washnewbuild21.txd \ + washnewbuild23.txd \ + washnewbuild24.txd \ + washnewbuild25.txd \ + washnewbuild2.txd \ + washnewbuild3.txd \ + washnewbuild5.txd \ + washnewbuild6.txd \ + washnrthpstr1.txd \ + washotel2.txd \ + washrestone.txd \ + washroads.txd \ + washsfehse.txd \ + washshops1.txd \ + washsky1.txd \ + washsthpstr.txd \ + washwater.txd \ + wastebin.txd \ + wa_tool2.txd \ + wfobe.txd \ + wfogo.txd \ + wfori.txd \ + wfosh.txd \ + wfost.txd \ + wfotr.txd \ + wfybe.txd \ + wfybu.txd \ + wfycst.txd \ + wfyg1.txd \ + wfyg2.txd \ + wfyjg.txd \ + WFYLG.txd \ + wfypr.txd \ + wfyri.txd \ + wfysh.txd \ + wfysk.txd \ + wfyst.txd \ + wglass.txd \ + wire.txd \ + wmobe.txd \ + wmobu.txd \ + wmoca.txd \ + wmogo.txd \ + wmori.txd \ + wmost.txd \ + wmotr.txd \ + wmybe.txd \ + wmybu.txd \ + wmycr.txd \ + wmycw.txd \ + wmygo.txd \ + wmyjg.txd \ + WMYLG.txd \ + wmypi.txd \ + wmyri.txd \ + wmysk.txd \ + wmyst.txd \ + woodpanels.txd \ + woshmalglas.txd \ + wshbuildirt.txd \ + wshbuildws40.txd \ + wshcoklnd.txd \ + wshcrprkdrt.txd \ + wshcrprkstuf.txd \ + wshgarage1.txd \ + wshmolmrk.txd \ + wshneon.txd \ + wshotelsign35.txd \ + wshrdblck.txd \ + wshsprayshp.txd \ + wshsthbuild1.txd \ + wshstripint.txd \ + wshtelgrph.txd \ + wshxrefhirise.txd \ + wshxrefhse2.txd \ + wshxrefhse.txd \ + wshxreflod.txd \ + wshxrefpump.txd \ + wslawyers.txd \ + xrefhotels.txd \ + yacht_kb.txd \ + yacht_lod.txd \ + yachttings.txd \ + yacht.txd \ + yankee.txd \ + yt_launch.txd \ + zebra.txd \ No newline at end of file diff --git a/miami/wavlist.mk b/miami/wavlist.mk new file mode 100644 index 00000000..af3e4a6c --- /dev/null +++ b/miami/wavlist.mk @@ -0,0 +1,1121 @@ +STREAM_WAV = \ + phil1_3.wav \ + bnk4_3k.wav \ + rok3_4.wav \ + shaft05.wav \ + mob_58c.wav \ + mob_72d.wav \ + cok4_6.wav \ + bnk2_4.wav \ + cap1_4.wav \ + bike1_3.wav \ + finkill.wav \ + resc_1.wav \ + fin_11b.wav \ + mob_05c.wav \ + col4_14.wav \ + mob_20a.wav \ + mob_71g.wav \ + mob_58a.wav \ + bust_14.wav \ + gimme13.wav \ + col5_19.wav \ + bnk4_28.wav \ + law1_8.wav \ + col1_1.wav \ + col5_8.wav \ + cub4_9.wav \ + mob_70a.wav \ + STRIP_3.wav \ + bud1_3.wav \ + law4_1d.wav \ + rok3_71.wav \ + fin_1c.wav \ + law3_16.wav \ + mob_35c.wav \ + bnk4_3t.wav \ + fin_13.wav \ + job2_4.wav \ + prn1_12.wav \ + cok4_10.wav \ + bnk4_29.wav \ + bust_16.wav \ + mob_10a.wav \ + col3_19.wav \ + race4.wav \ + mob_61b.wav \ + phil2_4.wav \ + law3_10.wav \ + mob_71i.wav \ + bnk2_7.wav \ + mob_24b.wav \ + col2_2.wav \ + mob_25c.wav \ + OOH2.wav \ + race12.wav \ + bnk4_3p.wav \ + mono_7.wav \ + mob_63f.wav \ + CameraR.wav \ + bjm1_5.wav \ + cok2_4.wav \ + mob_02a.wav \ + bust_24.wav \ + porn3_3.wav \ + law2_5.wav \ + race10.wav \ + porn1_9.wav \ + mob_66a.wav \ + fud_19.wav \ + race7.wav \ + bnk1_10.wav \ + mob_17e.wav \ + piss_06.wav \ + ass_10.wav \ + star_3.wav \ + mob_43a.wav \ + cok1_4.wav \ + bud3_9b.wav \ + race9.wav \ + fin_2c.wav \ + band_01.wav \ + hot2.wav \ + bnk1_5.wav \ + bnk3_3b.wav \ + tex1_5.wav \ + ass_8.wav \ + mob_10e.wav \ + mob_63g.wav \ + bnk4_6.wav \ + mob_16d.wav \ + fin_10.wav \ + col3_17.wav \ + mob_24e.wav \ + bnk4_21.wav \ + mob_52C.wav \ + mob_52b.wav \ + mob_52h.wav \ + mob_58b.wav \ + bk4_19b.wav \ + col3_14.wav \ + col5_18.wav \ + rok3_1.wav \ + rok3_19.wav \ + bnk4_3s.wav \ + mob_57a.wav \ + fud_10.wav \ + cub4_10.wav \ + mob_45g.wav \ + mob_68c.wav \ + star_1.wav \ + law2_10.wav \ + piss_03.wav \ + race14.wav \ + col3_13.wav \ + law2_9.wav \ + bust_17.wav \ + mob_36a.wav \ + col5_14.wav \ + bud1_10.wav \ + rok1_5.wav \ + mob_40a.wav \ + col2_13.wav \ + col1_3.wav \ + bk4_24b.wav \ + tax3_4.wav \ + bnk4_8.wav \ + fin_3.wav \ + cub1_9.wav \ + cok2_8c.wav \ + rok3_67.wav \ + mob_34b.wav \ + bnk4_25.wav \ + col4_1.wav \ + cub4_1.wav \ + bud1_4.wav \ + col1_4.wav \ + mob_08a.wav \ + ass_11.wav \ + cub1_3.wav \ + bud1_5.wav \ + mob_72f.wav \ + cok2_2.wav \ + cub1_6.wav \ + bud2_3.wav \ + LiftBel.wav \ + golf_2.wav \ + rok3_25.wav \ + col3_9.wav \ + hot8.wav \ + cub1_7.wav \ + cok2_14.wav \ + cok2_7c.wav \ + mob_45f.wav \ + col5_11.wav \ + mob_34d.wav \ + rok3_10.wav \ + cok4_23.wav \ + mono_3.wav \ + law4_4.wav \ + fud_14.wav \ + mob_34a.wav \ + col4_20.wav \ + bnk422b.wav \ + BAR_6.wav \ + mob_33a.wav \ + tax1_1.wav \ + mob_68d.wav \ + resc_8.wav \ + gimme04.wav \ + mob_95a.wav \ + fud_16.wav \ + bnk4_3q.wav \ + rok3_9.wav \ + mob_24a.wav \ + burg_12.wav \ + cok4_4.wav \ + mob_56d.wav \ + fin_2b.wav \ + prn1_21.wav \ + tex3_5.wav \ + col2_6a.wav \ + mob_06c.wav \ + col4_2.wav \ + cok210a.wav \ + bud1_9.wav \ + bnk4_3i.wav \ + bnk4_2.wav \ + mob_16b.wav \ + cap1_8.wav \ + cnt1_4.wav \ + mob_41h.wav \ + mob_29c.wav \ + mob_30e.wav \ + intro1.wav \ + mob_10d.wav \ + rok3_69.wav \ + job3_3.wav \ + tax1_3.wav \ + merc_39.wav \ + cub2_3b.wav \ + mob_63e.wav \ + cub2_5.wav \ + cub2_3a.wav \ + race1.wav \ + star_4.wav \ + ass_9.wav \ + STRIP_8.wav \ + job2_5.wav \ + cnt1_3.wav \ + col4_24.wav \ + bud1_1.wav \ + mob_45d.wav \ + bnk1_2.wav \ + tax2_1.wav \ + prn1_14.wav \ + BikeRev.wav \ + mob_09c.wav \ + mob_40g.wav \ + bnk2_1.wav \ + mob_04c.wav \ + AirHornR.wav \ + mob_99a.wav \ + cub1_2.wav \ + job1_1d.wav \ + SnipSCRL.wav \ + bnk4_42.wav \ + mob_55c.wav \ + law3_2.wav \ + bud3_8b.wav \ + mob_11f.wav \ + bnk4_7.wav \ + rok3_64.wav \ + mob_56e.wav \ + mono11.wav \ + band_02.wav \ + mob_26e.wav \ + cub4_6.wav \ + mob_14e.wav \ + mob_09a.wav \ + mob_63h.wav \ + burg_09.wav \ + bust_26.wav \ + mob_05a.wav \ + col1_5.wav \ + STRIP_7.wav \ + hot1.wav \ + mob_71f.wav \ + col3_11.wav \ + mob_71l.wav \ + mob_42a.wav \ + col4_18.wav \ + piss_13.wav \ + mob_03e.wav \ + shark_4.wav \ + mob_55d.wav \ + phil2_2.wav \ + law4_3.wav \ + mob_45l.wav \ + mob_73f.wav \ + mob_29a.wav \ + STRIP_2.wav \ + col2_16.wav \ + col3_18.wav \ + mob_05b.wav \ + law1_9.wav \ + tex1_6.wav \ + mob_09e.wav \ + tax2_4.wav \ + burg_01.wav \ + bnk4_38.wav \ + golf_1.wav \ + law2_3.wav \ + BAR_3.wav \ + phil2_1.wav \ + prn1_15.wav \ + col5_7.wav \ + gimme05.wav \ + bud3_8a.wav \ + cub4_8.wav \ + cub2_3c.wav \ + col3_4.wav \ + cub1_1.wav \ + mob_14a.wav \ + bud3_9a.wav \ + gimme11.wav \ + mob_73c.wav \ + cok4_27.wav \ + lanamu2.wav \ + crust08.wav \ + band_07.wav \ + hot7.wav \ + cub3_2.wav \ + mob_41f.wav \ + bnk4_3m.wav \ + bud3_1b.wav \ + mob_11c.wav \ + mob_63c.wav \ + bnk4_3f.wav \ + tex2_1.wav \ + rok3_63.wav \ + mob_10b.wav \ + lanstp2.wav \ + mob_01a.wav \ + col5_15.wav \ + mob_62b.wav \ + mob_71e.wav \ + cub1_5.wav \ + col3_24.wav \ + mob_58d.wav \ + bud3_6.wav \ + col5_6.wav \ + mob_30f.wav \ + job2_7.wav \ + prn1_13.wav \ + bk4_14a.wav \ + mob_45c.wav \ + bnk4_1.wav \ + tax3_3.wav \ + mob_03a.wav \ + tex1_3.wav \ + mob_14c.wav \ + crust03.wav \ + fud_04.wav \ + bnk4_37.wav \ + mob_11a.wav \ + cnt2_4.wav \ + law3_25.wav \ + mob_17g.wav \ + bnk4_32.wav \ + mob_71c.wav \ + Cheer1.wav \ + bnk4_4a.wav \ + burg_10.wav \ + cub1_8.wav \ + mob_36c.wav \ + shark_3.wav \ + cok4_2.wav \ + mob_40b.wav \ + mob_11e.wav \ + mob_57e.wav \ + band_05.wav \ + job4_2.wav \ + cub3_4.wav \ + porn1_8.wav \ + cub4_14.wav \ + bnk1_7.wav \ + mob_40c.wav \ + race11.wav \ + crust07.wav \ + col1_8.wav \ + burg_05.wav \ + gimme15.wav \ + piss_12.wav \ + bk4_34a.wav \ + BAR_2.wav \ + bnk4_9.wav \ + mob_40e.wav \ + mob_73i.wav \ + cok2_13.wav \ + piss_04.wav \ + mob_71h.wav \ + tax2_7.wav \ + bud3_1c.wav \ + cub4_5.wav \ + mob_54e.wav \ + bk4_31a.wav \ + law3_11.wav \ + bust_05.wav \ + cub2_9.wav \ + mob_46e.wav \ + race6.wav \ + col3_20.wav \ + mob_56c.wav \ + cok210b.wav \ + mob_29b.wav \ + mob_20c.wav \ + resc_5.wav \ + cub4_12.wav \ + cub2_7.wav \ + bk4_19a.wav \ + bust_02.wav \ + mob_36b.wav \ + mob_29e.wav \ + LiftOp.wav \ + tex3_2.wav \ + cub1_4.wav \ + bnk2_3.wav \ + rok3_16.wav \ + col3_15.wav \ + burg_06.wav \ + rok3_21.wav \ + rok3_27.wav \ + col5_5.wav \ + piss_15.wav \ + cnt1_1.wav \ + cok4_18.wav \ + mob_20e.wav \ + cok4_3.wav \ + bnk4_48.wav \ + bnk4_95.wav \ + prn1_19.wav \ + mono_8.wav \ + job2_6.wav \ + bnk4_3v.wav \ + bk4_23c.wav \ + rok2_01.wav \ + mob_45k.wav \ + bnk4_13.wav \ + mob_54c.wav \ + bnk4_18.wav \ + cok3_3.wav \ + tax1_5.wav \ + prn1_11.wav \ + law2_6.wav \ + STRIP_5.wav \ + mob_30b.wav \ + intro4.wav \ + bnk4_11.wav \ + job5_3.wav \ + cub4_13.wav \ + mob_73a.wav \ + mob_68b.wav \ + mob_46b.wav \ + cub4_2.wav \ + mob_96a.wav \ + col3_25.wav \ + col4_19.wav \ + col5_3.wav \ + col4_21.wav \ + cok4_17.wav \ + bnk1_1.wav \ + mob_41d.wav \ + bust_21.wav \ + rok3_8.wav \ + resc_7.wav \ + tex3_4.wav \ + fin_5.wav \ + hot11.wav \ + col2_12.wav \ + bust_10.wav \ + phil2_7.wav \ + bk4_31b.wav \ + hot15.wav \ + bnk3_4b.wav \ + job2_8.wav \ + mob_63b.wav \ + cap1_10.wav \ + race13.wav \ + bnk450b.wav \ + cub3_3.wav \ + law4_9.wav \ + cok2_9.wav \ + col3_2a.wav \ + job2_9.wav \ + mob_26a.wav \ + cok3_2.wav \ + Cheer2.wav \ + rok3_3.wav \ + cub3_1.wav \ + law4_1a.wav \ + cub1_10.wav \ + mob_62a.wav \ + bust_23.wav \ + mob_43e.wav \ + mob_24f.wav \ + law1_6.wav \ + bust_15.wav \ + law2_4.wav \ + rok3_22.wav \ + rok3_73.wav \ + mob_18b.wav \ + gimme03.wav \ + mob_24d.wav \ + cok2_6.wav \ + col4_12.wav \ + cok2_21.wav \ + bnk3_3a.wav \ + cub4_7.wav \ + cap1_3.wav \ + cub2_11.wav \ + mob_63i.wav \ + tax3_1.wav \ + mob_14f.wav \ + mob_04a.wav \ + crust04.wav \ + cok4_15.wav \ + mob_52a.wav \ + rok3_65.wav \ + mob_09d.wav \ + col5_1.wav \ + job4_3.wav \ + psych_2.wav \ + rok1_1b.wav \ + mob_68a.wav \ + prn1_18.wav \ + AirHornL.wav \ + hot12.wav \ + mob_72a.wav \ + phil2_8.wav \ + law1_4.wav \ + piss_14.wav \ + law1_7.wav \ + mob_18c.wav \ + race15.wav \ + shark_1.wav \ + col3_8.wav \ + bk4_23a.wav \ + bnk4_39.wav \ + mob_30d.wav \ + tex1_4.wav \ + mob_43f.wav \ + tax1_2.wav \ + prn1_16.wav \ + cok2_8a.wav \ + bnk2_8.wav \ + mob_09f.wav \ + crust05.wav \ + mob_66b.wav \ + mob_40h.wav \ + mob_30c.wav \ + tex3_3.wav \ + cok2_1.wav \ + law1_5.wav \ + bud2_5.wav \ + mob_17j.wav \ + bnk3_2.wav \ + prn1_20.wav \ + mob_46h.wav \ + mob_16a.wav \ + shark_5.wav \ + mob_26c.wav \ + mob_20b.wav \ + cok2_7b.wav \ + rok1_7.wav \ + mob_11b.wav \ + cok2_20.wav \ + col1_7.wav \ + mob_47a.wav \ + col2_14.wav \ + gimme07.wav \ + mob_46d.wav \ + bnk2_5.wav \ + col5_4.wav \ + law3_21.wav \ + tax2_2.wav \ + bud3_1a.wav \ + col4_16.wav \ + mono15.wav \ + job1_1c.wav \ + STRIP_1.wav \ + mob_71b.wav \ + mob_55a.wav \ + rok1_8.wav \ + phil2_6.wav \ + mob_52g.wav \ + lanamu1.wav \ + porn1_5.wav \ + bust_22.wav \ + mob_46c.wav \ + bk4_40b.wav \ + mob_34c.wav \ + mob_35d.wav \ + bk4_20b.wav \ + bnk1_4.wav \ + col5_10.wav \ + fin_12c.wav \ + mob_17a.wav \ + tex3_7.wav \ + cok212b.wav \ + gimme12.wav \ + mob_71n.wav \ + col1_2.wav \ + porn1_3.wav \ + bnk4_44.wav \ + CarRev.wav \ + mono14.wav \ + fud_18.wav \ + mono_5.wav \ + fud_12.wav \ + mob_08e.wav \ + mob_08f.wav \ + rok3_7.wav \ + rok3_5.wav \ + cub4_11.wav \ + mob_29d.wav \ + mob_55b.wav \ + cok4_9a.wav \ + mob_16e.wav \ + rok3_13.wav \ + cnt2_2.wav \ + fud_02.wav \ + mob_63d.wav \ + rok3_12.wav \ + ass_7.wav \ + fud_08.wav \ + STRIP_6.wav \ + law2_8.wav \ + hot13.wav \ + fud_07.wav \ + gimme10.wav \ + CameraL.wav \ + bnk1_8.wav \ + col5_2.wav \ + mob_57b.wav \ + bust_27.wav \ + mob_16c.wav \ + bud2_6.wav \ + fud_01.wav \ + bud1_2.wav \ + golf_3.wav \ + InLift.wav \ + mob_17b.wav \ + burg_08.wav \ + band_06.wav \ + Cheer3.wav \ + cok4_26.wav \ + col4_7.wav \ + burg_11.wav \ + mob_41a.wav \ + mob_71d.wav \ + col4_3.wav \ + bust_03.wav \ + bnk4_3o.wav \ + mob_25a.wav \ + law3_4.wav \ + mob_73d.wav \ + mob_18d.wav \ + bnk3_3c.wav \ + col5_13.wav \ + BAR_8.wav \ + law4_5.wav \ + mob_05d.wav \ + mob_40i.wav \ + bnk4_27.wav \ + col3_5.wav \ + rok3_18.wav \ + mob_04b.wav \ + mob_04d.wav \ + bk4_23d.wav \ + fud_17.wav \ + hot10.wav \ + bnk4_98.wav \ + fin_1b.wav \ + cok1_1.wav \ + mob_71a.wav \ + bk4_12a.wav \ + mob_08g.wav \ + mob_11d.wav \ + law1_2.wav \ + resc_2.wav \ + bnk4_45.wav \ + law3_22.wav \ + col3_3.wav \ + col5_22.wav \ + mob_26b.wav \ + col4_4.wav \ + cok2_15.wav \ + rok3_26.wav \ + mob_63j.wav \ + col3_7.wav \ + mob_45h.wav \ + col5_21.wav \ + cok4_19.wav \ + porn3_2.wav \ + shaft03.wav \ + mob_45e.wav \ + bnk4_4b.wav \ + mob_35a.wav \ + law2_1.wav \ + bud3_8c.wav \ + rok3_14.wav \ + bnk4_3h.wav \ + burg_07.wav \ + col2_5.wav \ + bk4_24a.wav \ + col3_2b.wav \ + bnk4_26.wav \ + bust_12.wav \ + bnk4_30.wav \ + mob_43c.wav \ + bike1_2.wav \ + mob_40d.wav \ + rok3_62.wav \ + cnt2_3.wav \ + bnk4_36.wav \ + hot14.wav \ + resc_4.wav \ + bnk2_9.wav \ + col4_15.wav \ + mob_18f.wav \ + bnk4_43.wav \ + law3_19.wav \ + piss_16.wav \ + rok3_2.wav \ + law2_2.wav \ + job3_1.wav \ + cap1_2.wav \ + piss_11.wav \ + mob_55f.wav \ + col4_13.wav \ + mob_03d.wav \ + cok4_25.wav \ + rok1_9.wav \ + band_08.wav \ + mono_2.wav \ + cub2_10.wav \ + fud_05.wav \ + mob_20d.wav \ + bnk4_3e.wav \ + bnk4_10.wav \ + hot5.wav \ + mob_56b.wav \ + cok4_13.wav \ + ass_1.wav \ + BK4_40a.wav \ + bnk4_5.wav \ + bnk4_46.wav \ + porn1_2.wav \ + race5.wav \ + gimme02.wav \ + job4_1.wav \ + bust_09.wav \ + mob_45b.wav \ + law2_7.wav \ + bnk4_15.wav \ + mob_35b.wav \ + cub4_4.wav \ + col3_16.wav \ + law3_1.wav \ + mob_58f.wav \ + bjm1_4.wav \ + bnk1_13.wav \ + fud_15.wav \ + cok4_8.wav \ + bud3_5.wav \ + col5_17.wav \ + col4_11.wav \ + PHIL211.wav \ + cok1_3.wav \ + Cheer4.wav \ + mob_46g.wav \ + mob_08b.wav \ + burg_02.wav \ + mono_4.wav \ + mob_14g.wav \ + law3_13.wav \ + col2_4.wav \ + phil2_5.wav \ + ass_12.wav \ + prn1_17.wav \ + rok3_66.wav \ + mob_17k.wav \ + cok210c.wav \ + bud3_9c.wav \ + col4_6.wav \ + crust02.wav \ + col2_3.wav \ + intro3.wav \ + col4_8.wav \ + mob_72e.wav \ + bike1_1.wav \ + mob_41e.wav \ + mob_14d.wav \ + mob_17h.wav \ + cub4_15.wav \ + mob_52d.wav \ + mob_72b.wav \ + mob_42b.wav \ + col2_11.wav \ + piss_10.wav \ + col3_23.wav \ + col5_20.wav \ + mob_18e.wav \ + band_04.wav \ + mob_71k.wav \ + mob_07b.wav \ + bnk4_3c.wav \ + rok3_15.wav \ + ass_14.wav \ + bnk4_49.wav \ + piss_17.wav \ + mob_63a.wav \ + ass_5.wav \ + bud3_3.wav \ + piss_08.wav \ + law4_7.wav \ + bnk1_11.wav \ + bust_07.wav \ + cub4_3.wav \ + mob_71m.wav \ + LiftCl.wav \ + mob_17l.wav \ + shaft06.wav \ + cok2_8d.wav \ + SnipShort.wav \ + resc_6.wav \ + fud_06.wav \ + bnk4_47.wav \ + mob_43g.wav \ + piss_09.wav \ + bk4_12b.wav \ + mob_01b.wav \ + bud3_2.wav \ + fud_13.wav \ + burg_04.wav \ + mono16.wav \ + piss_01.wav \ + fin_6.wav \ + rok3_70.wav \ + mob_06a.wav \ + piss_19.wav \ + MobR1.wav \ + mob_72g.wav \ + mob_41c.wav \ + mob_45m.wav \ + tax3_2.wav \ + bnk4_3b.wav \ + cap1_6.wav \ + col3_12.wav \ + phil1_2.wav \ + bk4_23b.wav \ + mob_29g.wav \ + mob_41b.wav \ + mob_42d.wav \ + mob_41g.wav \ + bnk3_1.wav \ + bnk3_4c.wav \ + bust_08.wav \ + mob_56a.wav \ + shaft07.wav \ + col4_10.wav \ + prn1_3a.wav \ + col1_6.wav \ + mob_72c.wav \ + law4_6.wav \ + ass_13.wav \ + law3_6.wav \ + mob_30a.wav \ + cok4_12.wav \ + bnk1_6.wav \ + mob_40f.wav \ + law3_23.wav \ + rok3_68.wav \ + bk4_34b.wav \ + piss_02.wav \ + tex3_8.wav \ + phil210.wav \ + race2.wav \ + law3_24.wav \ + BAR_5.wav \ + col4_9.wav \ + porn1_7.wav \ + band_03.wav \ + law3_14.wav \ + cok212a.wav \ + bnk4_99.wav \ + cok2_16.wav \ + bust_28.wav \ + law1_10.wav \ + shaft08.wav \ + bnk4_94.wav \ + crust06.wav \ + bud2_7.wav \ + bnk4_3d.wav \ + mob_18g.wav \ + ass_4.wav \ + bnk450a.wav \ + law3_18.wav \ + mob_54b.wav \ + cnt1_2.wav \ + col3_21.wav \ + hat_1a.wav \ + mob_45n.wav \ + bnk4_97.wav \ + bnk422a.wav \ + bnk4_17.wav \ + phil2_9.wav \ + col4_25.wav \ + mob_08d.wav \ + mono_1.wav \ + law1_3.wav \ + tex3_1.wav \ + col4_5.wav \ + tax2_3.wav \ + SFX_01.wav \ + cok4_21.wav \ + cok4_9.wav \ + psych_1.wav \ + mob_58g.wav \ + gimme14.wav \ + law1_1.wav \ + resc_9.wav \ + bnk3_4a.wav \ + cok1_6.wav \ + cub2_6.wav \ + cok1_5.wav \ + mob_71j.wav \ + cok4_7.wav \ + job3_2.wav \ + col2_9.wav \ + col3_10.wav \ + mob_06b.wav \ + mob_33c.wav \ + mob_02b.wav \ + cap1_5.wav \ + bust_01.wav \ + col3_6.wav \ + bust_18.wav \ + bnk4_51.wav \ + col4_26.wav \ + cap1_12.wav \ + mob_54a.wav \ + bust_13.wav \ + prn1_10.wav \ + hot9.wav \ + mob_43b.wav \ + bust_25.wav \ + mob_57d.wav \ + tex1_2.wav \ + mob_14h.wav \ + gimme09.wav \ + bnk4_3g.wav \ + rok3_23.wav \ + bnk1_14.wav \ + resc_10.wav \ + fud_09.wav \ + job1_1b.wav \ + mob_02c.wav \ + cok4_14.wav \ + bnk4_41.wav \ + mob_17i.wav \ + cok1_2.wav \ + col2_10.wav \ + ass_6.wav \ + gimme08.wav \ + porn1_1.wav \ + mob_54d.wav \ + col3_2.wav \ + hot3.wav \ + job2_1b.wav \ + cok4_22.wav \ + cok2_3.wav \ + mob_57c.wav \ + mob_17c.wav \ + mob_70b.wav \ + mob_62d.wav \ + crust09.wav \ + job2_3.wav \ + porn3_4.wav \ + mob_46f.wav \ + cok4_11.wav \ + job2_2.wav \ + tax3_5.wav \ + col4_17.wav \ + law4_10.wav \ + cok4_20.wav \ + BK4_35b.wav \ + col2_15.wav \ + bud2_4.wav \ + mob_42e.wav \ + shaft04.wav \ + col2_7.wav \ + col5_9.wav \ + race3.wav \ + hot4.wav \ + lanstp1.wav \ + tax2_5.wav \ + mob_45j.wav \ + bud2_1.wav \ + STRIP_4.wav \ + mob_25d.wav \ + mob_16g.wav \ + col2_8.wav \ + BAR_4.wav \ + mob_03b.wav \ + race8.wav \ + bud3_4.wav \ + mob_43h.wav \ + cub4_16.wav \ + tex1_1.wav \ + job5_2.wav \ + fud_03.wav \ + mob_42c.wav \ + ass_2.wav \ + law4_1b.wav \ + star_2.wav \ + LiftRun.wav \ + col4_22.wav \ + piss_18.wav \ + cok2_8b.wav \ + col2_1.wav \ + cok4_24.wav \ + mob_61a.wav \ + mob_07a.wav \ + cok4_5.wav \ + cap1_7.wav \ + fin_12a.wav \ + shaft02.wav \ + rok3_11.wav \ + fud_20.wav \ + bnk1_3.wav \ + law3_20.wav \ + shaft01.wav \ + mob_14b.wav \ + bnk1_12.wav \ + bust_11.wav \ + bnk4_16.wav \ + cnt1_5.wav \ + mob_73g.wav \ + BK4_35a.wav \ + law3_12.wav \ + bust_19.wav \ + mob_26d.wav \ + col5_12.wav \ + bnk2_2.wav \ + cnt2_1.wav \ + cub2_4a.wav \ + law3_5.wav \ + mob_24c.wav \ + mob_33b.wav \ + cok2_7a.wav \ + cok4_1.wav \ + mob_98a.wav \ + cok2_5.wav \ + rok1_6.wav \ + mob_03c.wav \ + mob_25b.wav \ + rok3_6.wav \ + law3_3.wav \ + SFX_02.wav \ + mob_62c.wav \ + fud_11.wav \ + tax1_4.wav \ + law4_8.wav \ + porn1_4.wav \ + cub2_8.wav \ + cok3_1.wav \ + OOH1.wav \ + mob_43d.wav \ + mono_9.wav \ + piss_05.wav \ + mono10.wav \ + mob_45a.wav \ + mob_24g.wav \ + piss_07.wav \ + bud3_1.wav \ + mob_18a.wav \ + bud2_2.wav \ + mob_33d.wav \ + mob_52f.wav \ + resc_3.wav \ + bnk4_3u.wav \ + law3_17.wav \ + hot6.wav \ + STRIP_9.wav \ + mob_29f.wav \ + cap1_9.wav \ + bud3_7.wav \ + mono13.wav \ + mono_6.wav \ + job5_1.wav \ + bust_06.wav \ + gimme01.wav \ + mono12.wav \ + mob_01c.wav \ + mob_52e.wav \ + crust01.wav \ + shark_2.wav \ + cok4_16.wav \ + rok3_20.wav \ + BAR_1.wav \ + mob_08c.wav \ + bust_20.wav \ + mob_17d.wav \ + bust_04.wav \ + cub2_1.wav \ + ass_3.wav \ + col3_1.wav \ + fin_12b.wav \ + BlowRoof.wav \ + col4_23.wav \ + mob_16f.wav \ + bnk2_6.wav \ + mob_55e.wav \ + rok3_24.wav \ + mob_09b.wav \ + tex3_6.wav \ + cok3_4.wav \ + pager.wav \ + fin_11a.wav \ + col5_16.wav \ + porn3_1.wav \ + mob_56f.wav \ + mob_10c.wav \ + mob_24h.wav \ + mob_58e.wav \ + fin_1a.wav \ + BAR_7.wav \ + cub4_5a.wav \ + tax2_6.wav \ + phil2_3.wav \ + mob_45i.wav \ + porn1_6.wav \ + bk4_14b.wav \ + intro2.wav \ + fin_4.wav \ + law4_1c.wav \ + rok1_1a.wav \ + cub2_2.wav \ + burg_03.wav \ + bnk4_3j.wav \ + rok3_17.wav \ + bk4_20a.wav \ + gimme06.wav \ + cap1_11.wav \ + bk4_12c.wav \ + mob_46a.wav \ + bnk4_3r.wav \ + bnk4_3a.wav \ + bjm1_20.wav \ + bnk4_96.wav \ No newline at end of file diff --git a/premake-vs2015.cmd b/premake-vs2015.cmd deleted file mode 100644 index fc1bd295..00000000 --- a/premake-vs2015.cmd +++ /dev/null @@ -1 +0,0 @@ -premake5 vs2015 --with-librw diff --git a/premake-vs2017.cmd b/premake-vs2017.cmd deleted file mode 100644 index f3562da6..00000000 --- a/premake-vs2017.cmd +++ /dev/null @@ -1 +0,0 @@ -premake5 vs2017 --with-librw diff --git a/premake-vs2019.cmd b/premake-vs2019.cmd deleted file mode 100644 index 9971831a..00000000 --- a/premake-vs2019.cmd +++ /dev/null @@ -1 +0,0 @@ -premake5 vs2019 --with-librw diff --git a/premake5.exe b/premake5.exe deleted file mode 100644 index a8483721467dc3d67ca25ac14a6ff811c0bb92c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1395712 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

    %OeW9 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ΝR zLIOJw?tD;tFc91(Z9Q4aWqhEM9b()6Ql9Quj^G!zAjf}bKEh!g$5X@A?aNcIW9=tU zD-Bv4QBwH7K%o2F{{o&DVjyL{JRnbg0I3YGXSMbds0ru>pHw2(?Z?v{CeZ1@(-|Pp z?Z?vT!P3pw$y2v+yTw{Bm2&htF~@^S znr=6aPA87;Fwj5-OE+65Q@1MzWWO@x*u}`$!~e?!0$$jFoB`T|ZT+u=3v`_<14KJ` z1Vjmx_WcDqy#%^_L3`)A-B~)FK*wJ&86W5rgeU}+0{q>+9L5K_!&#t$p!=uFcwRgJ zEj99I0iDp!VSE6T7JWIODxg^$>PbX&f&3fRUCIO7Zw@UhV5I{Gq)68)Bab^51dl?uQ;D>?24&Me1r)F7!HuP?nfb|{3vbc^hFgSqjeG=&d3G(o7Kk%A$ zM*w&Mwi`?LdC=)NY~A53VJ{NFqhg><4I16fEX{{O*J`wOm;L}PuQR^XeeOj8Xr;L8 zmrlqjqTNqp595j^<4fJ=UpW5y|35ws+_3`()c?{yuy|wvD^NgF0E$*{_;Cb>bssYR z77n>;RiZN%v_dNt9(FJfgBtVDz5k%h@94q+N#W4)0eqmCJ~ZqgyVuXP-YyC64r2*^ zQ3BqJ2Jx&rOY?D%XRma}{%O5k!fAY|`}B+EpP-9Nf5dkh7$1N%w!mSK^Aou#4E7u} zK^R|p;r|oW$7i7RgG4Z>#JmW~M4^Ae!dq|GE7pK2N9oV}b*vgZA)uQSnHCuEgfMhC z!4G#jxJ&rM+11}4?e9!?RR#?%ed<2A6D$Niw&zRtVc3a1Ji9=5KEVr8I^N0X_zXfbbXZ7BMq)pVmH+#gJtq{V6Q)h3sNxhVJj(H@a(o@Gtq! za_||8cIg*sFo)^jGbZNG-N%G~b)V;7a**ZV0~YOL-K8ATARg1f2Ta-@x?Ops4+ew< z1%s}PFa45X!V|)f#R*EmTQ7lnKq}Cn?LNG7yH{t?_lNu0T=fohy1qc1Ui78gmFN4z z?pxg_x?Op8fuupvK?XX?ui|fuUZb26XxyDB9#8Iu<~Ec;Gy$4{aF))EVkk z!OG>c7!JPR2*_9gRXYVuEl64@AY%hmx)f})2<9LOczrpCwyQw5FORgZNOP?M189Mw zN_Z&?_&~o{neJK%>(ljoH7wT0i#Z_sbYl-gN8kekUc8>i%n%$N6!1c6J~Ko2jqYFF zt~yx^+Q(*sCj^25UT}fLeKop2Y5OX4y7Is-)8+wRwQl{rp1+30`gAd;_D!(kK*uy2 zA3)sd0WSY}K(+bD7Y3XE|8L$0+G@uDy67gBr@QY5Bj_~HKb@gJI`@GNneChhI&iio zxE^$yh;L8;$#ucuFMfj#e>kGidH{4R(4WpW(D-V% z>yOSh(C}*KK9H);c_1A~nHH3Bp%~eH6E=a|_XX4`gSqbmlKVjC<95yiot2C1KG4YS z|81a=-v8S`S_A)Yo5IS#@PFF`RtARP@E6f&?gOa=yAPzXb00`m=RA-OobGFUbA$o3 zS-abiKm5Q>P$M~wU)Xg=IEZ}F-3A(up85SJh_|5o7KmEW8T$c5Z#d@qo8iT-jiA8| z(4a>5kM2XAwST(7qtl(PceD?6`kqMR&pQFS%^G%Fwe`;uf$rm|>p}T74u0Yn^nCyt zKnL~8FWOk>jVDqlc3E?ovvp(LvNI^f8rN(z3`eBbb{{#*AUP_!mby(`#=%f zJq;AGFLbwoG{(7Jc)^EmM4JZaWY}pstPBk0C?-7Uo(2l^?mkciym$@P7w7um#hES4 z44rMDLx#JLcTNKxIXu&{?Ewn|Ln)8tv?nYK4E!yfpz;4ckj=+je=~N1Wjn!g)_ou| zYec%+K(4Eo>YfHN-TF`&$7?1UPnrspg+-wa&ec|631~ns@hsoYcJ!#RhVcKTe}V#DT$}}pAF%hPfxOo}4>a}i?XUoU>od^u)V3bT3h{{l+dy≥X(U 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

    g+c=vhGsxQc*SJywx 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 z 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

    p>`~aFY1Nj#+E(?k? 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

    c(sW}B8QHa{qyc7%%qd6H8z>rZiTLsX7D;H?o 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

    Bq(TnVfzjkD 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 HCWOTtZ}Iaix}uI zEF|xNJ6P~2f{3BS5W+-A`T(a(XcR+CLQxJe0o^X}xHBlRK%EH>Yz77f@LlepMl%Bg z10)rIhLWI@s9Xhzy&~WaVq!ris6$$kn3ukH z4){Llq{Qr0c+&-{0^$Nt!HvH_2c7n*V5guAG7%I(X_@J$`3u~TL|W#DtS~wgQRstG z2@a=23sb@_Q$VR}z)rKVg}M!D2&&_dQW>Z=hpGWvq@$3DDC=-I4z#KbRSzWkKqRap z&&e-OEz*F@AjE^G`ayLs^oU1rU_ecPpOOL54LN-o#D>k+qvW@OqAYN1gIos9T;PyK zp2tBX0PqsBlA=_k$`fogTpY!0R4K4EkmOJTIf(#d5I8A9d! z0WqPVC<`>i14?_KWr*Mv_n-zQ*dgH1fvi!0&Nt=dr-0^`3iQBMf_jW4pv#OPhG;_W z8im>fG7=glP~{~>iJ%>?sTv?X)m-^`Nkxfy;QN(e=?r|UDAbtJQtNI z=4eD&2)dsZ+LnP@1X2U{5acRqa0v`j>70?7lLA!-zq$}|cmyP%K$<}nI+6v*7fXSh z1zGk2O6B0X2sHQ#UK9!z0j~!E*N@O;7LYVl1m4>UYSKblw3(2C5Gh;1)~SFK9;k%? zYkDh#%tg$xfz8%~4sAe;)xqmnkanacpwRGz=!A@xATMVDw^-2J409T&^$1E*pnyzC zO-n5*N=*Uh28dzsMg(+AELbV%DwUkFRBXz@DF^Btuv+wh2G3@JB0~qd_ZJdHpyC6B ziKvf3HE>aCdS)?bH7H_HR|RN~Ab2JZb}t}kok?;K=n*s(x%4=osy^7BE50N^)0Gru@3F*h@(5^t!&-I3{?iQ$TcRUM8q?Ow0k>UyE<~5IBlWKn_4%KIB+X zkdvwq0SZ(FkTlW=9Vn{d=USks!(223S{;#Eq5#qY*{=&*paV)XL?2y&d|Cy{ITpxA zS0Jqk@+0mP3($EKkRAbevKe$_0;uExom^q7P_3Ypo}3J7;6WS?QjBp-1*{YV56K{( zA5)%P4BA3ikqj^B^+7GY)Vz{nJtV=D#F9jCD;LrM1Igv5q{5eIz#DGh42^G*5a!V} z;D`k!PJIvoI^nAzsRG2rV;{7BfZA3ItGwX81ULDiB@t|N1YD4VEi-}cC1YS0BzxFobek)8}v1`|(DPUb4kOV$IOT$BpQG~n3}a16U8mL%pt*Y$#S z7iQ+AgF6Sh5rw6hCAOfoK%lrTD9Ox)tbnQJ@=r?xrD9!w(E1(lrRYk!{)S3AT;Pcw zh!iOExq*%}v{g{j^#?7=3x;S{()9r zjY!E%Ooupws}}4H$n_9l$0IGU2YE0(wFDxm0gfI>Fv5KeuGiBt)Ae!_A-BtdROu*y zG(yzCdR7oKz-2M*L2^B#$4Jrg$%LU6J;BW;+2-GuR4?~kDBy2zj znLsWIBN!o2Gw|tv1sX9?0yU24NI~}#mTL|nX$>5YpleBzlR+IVEb5>|23R3D!qLq} zzCa8bTwukZXjcH=!2lW$1cjzvab<2&eoir{*eTHS_k%X3AZZZV+5>9^U8n#$Is()J z!muBd;6Tw-Pykkoa6IhVL~x!sx}YK zG9WonBN?O_a{f9j*?~ks_QOp8FSJ3vZ7mi&ewvpIDYU`TkY*KFq8eV+fK7@9*#zmw zFfcG=rYV3{oq-}eFEaXwX*x8wc*pAavM5Wx*YS%zVA_qRf(1 z*nDD9YJrBjg1Qb=jW*~=Nzn1;F-4#Wa?r>LT!(rz_^vWYYY*fP4Rz3^2QZ5?L5Gwe zOCn6w1vR_C{sk*ikA;{HoqmUBHE{T(f=|K;Ey@WY&L$byZa(19cs)Xmv$6-w@1Kg7b~Qd}TP_7|d6J^G(2fH8|f4%#Trr3z&li zG~j#-FkchSw*>RG;Cx3gUmMPM0`qm@d>1fZ56*W5^Y!6;H!$A{#y5tTXAS2=%(H>> zox$pD;e3esc5pt#e0w+_V!i{6ZvqLxXc*rVVtz~vTmWK0ESwLqAP&xlSP&2ALo7&u z@y!g>xoWwM3DotQbHz>aVJgfmN=)vt`@GUdown|BAMG5G}$gB?hF$|;*5(My^1-A*_ zUWfDtpw$k$Q41OQM^*|N*wKR& z-3qqg$r~6izX-hc4Lld6fyYdUW+fev>h#o-;L75X)ZCE7bcn;DrfK3aAS1uH1e^B! zVm*+Q2I#s}NXrCz?-?Wof~K#tQ!AmXHxcPS-ai1eJ|`HQ^db66Qj0*V@Uv4ZK|?u^ zHKKL43QEwSDyTG=0xj6E1&<__o6aJhI8{v z6biuJfw~CPOb2(EzytbVH$mI{D9RF3QXm?Tl!59cFouR4eCY?cNKA!92FQi577)n! zpq(ISfr8Bmpn?GuoG>lBU=A?j9Jdy^`h6&KF zV({oH(?$6va%?^0=H-kh_qDLC#0! zgI5SBqlmz_(0~fgcpU|ZBSGaRbbWkUL|O-B8%av z$ib&wLbZU{c$9+1&tay6*w9FWMmjX$AYqAFiGxO`p>;Tz3CdWI#vEwk2Ie!=>Kwfl zi&m@Bptgq;X31&bp;=h|gU(7qTLGnzO?}0n!nL{<8gj6ce4(PS8Xw%3fOI9mEdhvX zaD`F~4ZZl}v~K<7Gx$}kWHn-C9bch*9J4O(C$#|)^bg_Imn zr$S=`QHFv{fjSB7^+c?NC|DJ^6a}%M(FY1d$O*iV$OX$Im9F45tDv9+rwVYnL z^!y@7a~s+MhZoaOKG>g-)B@K7sjA_j0uEzPn7~@r;PM<~AySh#J+&l1DGM}U3aTby z83`f;8tP6<*MoBMixiadld_bc$s06M50S9~tT%8e}+h!yI(y5Ij?W+vlL|RG@5LlA#AS8?ieK zdQ>@X^FUb$r*>F=#H}5tR+!fy%>+pL#Pkhx+ymr4aJB&jL=NP%F=)956UhTFDYI38 z7KNaC2;2~Zw>7}a+n~0BiYstY2x<$03N)}%s3ce+q{sociWJ}tL69>*gXn1KE;9|Z zK@HmGhG;AXjZ1?{B9IpFLi~7e{3syGWsnT0$f|~HO#vT=Rvev@Sp?DsIUNnk%`XBS zk_K8A4T=n840S%(XvlU1@HiaQ4R9Y~wHqu8N-dzH+n_et+QKe70;N7kT2rtE`Hm~8 zC^az~90w>4fOUl685U9>6oW$;n%!Wj1QcVStqTz6p;ow1rLbfRQ;Oi{7lEP{OKd}g z3Ly0!NFO9ErRC?O!0U@*L|s%2X{ckZDq)6zg9c(QC~;s64}d%eJwFht1$#uGD9I%iItt0fsU=`jiebehL^nJV zz!?YPWki^R1wm;n9;_EB@q$|}po{@ZKxygGhR~fXY0&GOIVJvK&BBaEJ>Nmos zAMOx{RwFd6pmYdvF=!MF5%VdjsRf`8fktvkg&tT+lM76txAbfkPy+#~3T`c^e1?{f z#o#=gomvSB15i8{Yd}&wIHaM&%1Fruyx9t*Uk}UzO=fCjr&fZ~1*Gc^+P4NSo0Cf_ zKzkcNxxOf|9JEd!9AjYBp!<`cIRi3u2pUd+IJ!8sL@zD1BsoI^61QM`AOnJ+^iom* zwhN*Q-5#(gINgB45Qfpx5Nd-J>?UxmgF_+{93`N@fcD_QQlJC@Vnka9al5qM|>6#WpLa4XA&oJ(O^`{Kd77qypYa02STPL}c2*)~ zS)9&A)&x2^xuil5SqPHiplKWw7OmK>@BMGUuA&~O@52Z#wyI><#Mq__e#n?Q5ipgI!LU_e#_ zuBULMX^<{Rk^+_0Fh#H-v}91t4hlq2Apq(VLZcGS%`Z{_C1mheFes7cCxcI`f`>4y z6AlhAWP_liTcFeGi!&h2cu)p}_h*w!D!@e+X6*qPJ^>%i269FYc>V&^AcI!dphh%t zJuJA{kgS)g0X`QRyvYV?GRQ2*L_~2kSQ0)Rl?IXm=U*@j)KmhEtwUNK;EgC?_2BIz znp_|?dig~u;A=B8VN+6|-YBT;0+Pi#9|e+tL=LFd$w`Hpj+iq7DF8QdAgW;XaS>>G zArmxC0iJ~^)`9sDoP3l)n_Kcq(^E^pyAD8c2VWFxqX51((9m27lzkA5D#Sf5(6$0B z$dn2o$Nm>X+q=-Uw4lTUaSeDF2oyBMh(R9>)GaCCr4ZmfDPR^T-r?y0tP>=HSQiFv z3zz0)7M7;MW)w8iz@Y#d<|))s042I&O|J3`(CN3JDM(OLF-@-+I{g41K`MmwnL)i> zuHsZE&sG6R6{rgg+Bt!w&sM-Ll9Ua6b z5Qrcpn4t&}g$EeDlQ}50fG}2#nK`N8A_#{@NU?#M zK2VF~v~*1_(C|!7ejfOaL(r5rXm%8o-9aZYRfCTWgSF7Wj?{y-a};WI6qIU|AO;k} z_i3OVNkq^D(0#`sCm@@E!iOvuQ&IpeJ&IS-gl9aEB2Xd%Ra$B3dN415G9*X})=mXQ z4Tuk_!y(xl!~=T?au^AyB@6N|XgPX%Y6(OzGcPS4RKh}hhol;IJpfo2)DBQft0*-G zGD8Y79V!k9FJ#TIi~ z4zFcEl?1pr0;vb#%zV9g*m7uag9GFZq@~4BIjGm6UCp%8oE*@-GN8zUULge<@rUkC z2?pP<3)2DdJLm!fs20Fe7cyK+eidO)Y_WN+B7v{<$bKQ2}(bDi_oTV86lb0yT-0 zH1hKEQZ+$W)j?cSj5tgQ8tA$C;6t4>K^GKaJx2hN+(9e#kS?tUU(n5!3%?FW4|LWj zs5*oWdPB+@s1@X$my5VV9(OC&Y8OC`twqrfi7WWIyWpKxuyaqi7VMA~9em9a;S+YjS5rYw7go2?N3j;< zYHU5xnoWaC}xUnmB8zmm0;$;oQC8qP-;Q(AX=!S$brqN&`<|m z@&rB@SRHgm2kcm1Xl)G90X7Eee3-RJ*%EyIBP=or=1q1(EOj4 z4zV6|&LvuiBWZwHk3FzKdZCASf=vYlB&2TvE^g({JPSe)G{+4#LN6J% z5*V5vL5uxBT`edV+RB2of;3x*O^-}Un@=`(DZ$NEO$Z6u(;uqQ#2J1;J0XHs?69%-82W*k!B0#^YGE9cx4L{JqJ zr9#yqiRBkVu9-nm3ELxyQGGzuE<9y|)|o)|K^B7o0lA$6SviJnO-g))hI(dNYN3LL ziUuTyY3isJYeEmIhIh?D^(OW>fM|k-Ex6Q1azlK9hI(pVN@kim_!3xk&@MyteSqME zQ35-91C(7sy%JD~ssrh1f{Q>s-yj6-aDjWLdPS+l6_Cy;w7>uv0UaJd z?uCPTF<>dsHW@u7sA5n}0bSSx+G3kol$uTmd~hS&p!C!dm^-0P z&;-dTfqV%mSx_%l26s8ZnGICJBWF0!+EK^`2k_Et&|pyx*l)0jSI8kt(A1HhS^`lF zIxPs4m_U&ZVuS7_1ohw$ryJ#@CW1~MfY}9JZmt7547MZ#W;A$t0C-3iVkKA&I4U7B z;Hnvv`eI5{ixpyGkdCp3rVz*_b%^m{czTDGeIPfYCAP$z94m!_qEv9CE2wC=`Z~F~ zxOw`x25Ty)XgK=^M8Y|cQwudwQzpzu3UJF1g&&%AD1nVo4lC60sfH(ca9Iye^2k+_ zIwGBbcRGTGXkiLLsX<+@sJK8K9P5~Fh86Ch(iqJhpb!so4G!@43wBk|wp9Qd4tG5$ zhZRAqAee0+1E8%S+%|#Be6UJro`ROem}NPnaghq%UFS+ZGk2%{lUC}+zi3#JuXl|rJ?}~7)>k4;mR<3A%#~=i9>2~ zM!tf3D!3X0Es%y=0tzfp*P|#4eEt?h1EgFn$S=;U&{5F{_Hgy_35g7F)dZ)x3Jvv` z5*3YLPajvm5KV=mTm{`U1tcdx(M=3unh?jqie*TA6sLj?I>|5A%LJY64O%~>RGyial3!j7J>FX>CI&opq@)ikHEB|I zWTwF;bHPyy9t#C+#?geX`vw>H(1TK-Wicp4!=gsDSi#N?IZQxt0!lH6!Wm>5Y$z2} zAQnK=HYhvSLV^ZbOJSyVL^BSWMj^)u!IB(kQ~|WSA|JFV29(y)GK-2!AQdD?1H^Ph zkp#+rpa=oARWOQbnC;MV6B13JSq4!1B@;eph?t`Qc??|t!khyNJkYQ{#F2>b1Q`M@ zh(QXF!xikkl+=`FfrO@lf}4*c=mdM!Vs#yua!_DuK$U^x0n|bRX$Eb@ z#cn>dF$PkEGrDTac5VlBxkxtfQc$Ut|TE z6#-%RWf_=(hSEU+r9cd?5l*s@AOOvwfO?>yvu^ZDAV(D<1u#e&6cUIz8i=->%p~v% zW!TgZ%oot01Wn#RRA_?yl#`i+>??4g42fNBTS&nM2@B}h1cU=#iVm_Lixa%5AKFJGGg3fC2m6Nxfs%=~EvWUR zIamr2)WR8Bb|9Az;5G~4;sL2Qixx;A&#P!an{QSMDjLv`0JZQCg)4{$&2u0oylPjd zQGjU%wZcJlYXPJx4hVAf@$~a{_H_vc*U(U5SPiYB;T-I&si2>ipOc@Sub`h_T4JT* zprE1wPNEPyV0l+l!2zi%S5N`%$S5vNDlSn_Rn^gl^>wUNRdrCBsL0j^_y>nLg?jq9 zAiD==VG8cCf^r~I4hMykf`W6fvjQylKx+cfTxWiM4x;#0N-j=DlvF{k!S1f0`#~VZ z4^B-*sm1B3c_4kDA{EQI%8V)pBqn8o4$Z`QXengkR7r;m>ORn5DVGxHszlHM zogg}`0L02jECQVxSPL3$0_9dL_CYUWP6QnnP?ich(-ZfhrkQyqsp+Xj;B!B57zb)b zBxfXM=7EM7GV}Au@P>(=nUaozuC9Wgi?@Qivomb+x0p-GM9%``EfYOU5M`_fDkGo@ z!RKfqDK!RPpsHjHGK@>f7^2Y>az84b;3zJ^9|YJ#9yHqwa#4C>QYPpgU@oQNk{r-k zVPL_$QV^AsnUoBc!xtMRpyDJqKP8ona0BZF*#s&gb-0vLGK)ba7|{u!ASW{kRErQ} zH5dF$HCQ_pR9(SWYJfP<3<2un!Se&8HvyWcfE7ylWvP(W4Vn2m3QG9}sd<=78^A+i z;7R^ea8CymKH#+npfv`m(EXa_O5mmaAUUW=qLQYj0wi-o3I@=)AD)rH{A?YC)FRM9 z<@v>Wx!Eb1MH-+fBhZK;XdOa+HpDE*(umZeA{_+-h<`u@0JLcY8tQ`#^TFH;ZX3Y@ zNda~s8>qttUULTua2?R5qmtA-_>EQ|Sp{2=Vo-n}sZ%P~PErDg0ayum)D6V4DhI8L z01f#gpc;BQ zseW<}sJIW!D^5%UAIl9D2q-OrbFB(Wi$LX7Fp?50ka|!(p~D5T2-KXZ=E8L@u6}rg zk`5QB&mNMQo9Yia7Z-$$I>2L)@&o9;oA9WlIJHJ>T zc78E>n;x7%9m68P=7Bm1R*qqyq~jPC0h-!Y(uawG&OZ(gc15TQ4t53E7wn3pE;!f~ zs?HciAw)3P6;m`?DLB~GM9)wO)UJct1~sKthYNNNG1!%`xC#dy=V6wD>Ba~>JpkOb8sdv%>x~8l$w&9k%&^bc|p>7er{$-T2W$dD!32;@BV;iJpW3t z30bAZCApR0^XkF55|WL#= zJ+c|34Wa^8oxqL9sUD&NrXFme0&K7jJhBEc5Hzj~8bj4l09iuB3ECiKu*0Ci-h-59 znfZCJZZlXETFQb`x~oTVGANQDMFErtJ>Od2)guuS@}NVm!CD~+MM>X1NEZ^Gurvd6 zBFuV<0uXvgHE1dseCQk6A>_#3Bqk7{#*q|^;L}yWN9boexxuEL;e4U&@qsm})Ahwda4p(SiCM=B-Ho!61+0zpg z`XJLlX&61sp>EZH(x7CM4ht8^Vqs9*7&Pb%YK}lvK^n;rX?y_a^Vq5j~!xw4%pY=Sx|`i;K^0c`b1Ep7qXrV+;af6%CID2 z&^ji6H#dkMu$r%=?-K9f7#!~!;p_@I+0)hlx|<4G7DHSDZd#!SXlWkARmB=`7HW8c zVgqtDO}vf*ObwFvVM2HVAXEt)05G*EK|t7zSPoc+G<_ldg18V8H1HiqpwL9ohqUhq zygmTt%QR4N0Bx0n#$>SXwt#AZSOOi9f!GdeC%btjCfFK2Wn}i7J+&& zkR~3i$^f;3OA>QHO>*Q5EC@8zeL|ew!EJHRyyT+P+|)d%5#S2J)6Y4`)z{T8#L>sf z&mXMJH!&|WEfw0@_I330baM?3fk^seZ?yaQySTbJhWdo~csfC95YS*LDD=64Vb{2T zZFBJfbsECKlisN*js>9ka3%fVP^aL?;1E||t8h<07ys~JaNUlj-QeQt6zUGCh#O# zSR7(+B_tN09n1t)4nFEiN#D=k$KTx_)LYLlQpnU%$V|`6FG4PUK^Yw)n34)^QRZZV zosbGCkf7TkK|z+DS_18DxmJ{ijLEZ)m8zW&judJfJlNm z=%7jd#Dapd(j1DZnwr z!wOOffXWoml>o3R5Lyg?RpawKa-_jdW3T3lRssb*sPhAAwU z)K37-%OFkqf;|bTz#zsU+9c@u!AFII#$XWB{U{sZp!(rQe}S9>S`r7=2fC0Fn*5Q* zq(F5XG&v!MXf#MGj!XcO!j%O;a(FTTNV*s_X`YvjlGMTC;DA7Hkbss%!B(V#3LkhK z3OlF{T?1%%A2ehMQVf}#B&Y?}Ee2_U#WixR39S@U^FV{uNO=O3iy(q96Tr13N|uM1 z14{PghL8jg(Sg$Dfm#3^4o9;ADoT_UDG)25TCmxX2D~ppKy-9{xe`e*Pigu@c>A1+QR#KhUmB1?Y(Z3Q3g;Ua4tmMX8kvZbg}?DLD!n zpu0Y-^!1BU(^D(-K+BHwlXCLY^>a!S^|Ole^E6?zFbYU+Qi#=6f?5h%G3)9E%IRDR z3JNeUK(`7&xjG;zWY;O!Dj;z|6F87e4Xuab3yM-pN-Eil6(k4RdCeLagd0I8=9V4l2@9mkzasp z5_rdRet{-v_DMajG&d==NFChq%P#;qF~30151fx6W@Q%pft7$l6y!PVu@|DL0E@p6 zygA2DfRcg(RD1Yl}05$j^ZsuT%{cR!~p?jbg$}G?4o=!K*)l z6*9r+=VCFwBqJ5(j>KXGm^*Y7$}=*PGZa9FI%MV*gX;F=%wkwzC@3g^+DRq(kU)n- zAJ`&4m_-m71+bAmr7*9;JWvaDF}9iy;w|)Wh3Po>JL0p2AWdLONY1uWH5QD z3fygmq-vnl$QwL$6Q5WdUs{r8p#c{GCvaU|kU_e-3UFaijKks=A_pQt z7?OVPs%}VhF@73JRbmx}l*# zWo2bWg@U0$1sFhrLP0^nMpsv%v^X`bGzWA91~?BYq!s1og6}G{($`PT(<{%+&MZhx z$xPJCFG|-3@$^GW(k!5Eg)1q!*Ku@&eQgmBO5D;QLm7=QyyK|ukc zCnvugtOeX*hJ}i*f(A&Xf|f!C)JT{nXyihQ1W4!@R5%+bXv0;4n>=tYqA(09EI=xu zR)USh7MNzHrpBhoQ3Cg-uC9V1BseP-z@ds9tOUXY?OsNdKru8hKnoT~=>jRl5a~%* z!Jq=97Aat$wn73kH!}qin5dQ^48t1(3bxQf4P-6IDiZ?*Ed{8VPAdp>9JEVsIYV+pvTPsMGwdxOG5UznV{$a*<<0R z0L~B!3dkuRQ64GTD?!~2$}&(ULK-9T2z!@q>-CiT%4Gms-plp$XEwlBZ1nMItr;J$>6{TwU%pq-*hItozr$o7KV0k#-5m4mVi#HrW} zM>h_|kh~O7Y=MG8K|uku1GFR=b_^ksr=U&&RU1%yKuh@)G{AG9(4YfftDC9;3L4O` zp^_%JKtPxbzTPFVxHz?_1UX=kgf+k`S>PTAdlV@zL-b-*1Z|yR#%4%SVkT%j-aWA> zDKR}&12i%i4+;R}P%TI-Do#y_2T%AS;ue;CQ*+X+P?LpH3Di`DbcnG^Ito|~289K9 zfCmypdN5=B)BG}Xz)1{L&!I_z#ktS~{nI=`e0{)D3bxQDJlr_2Fh>70KhG6((QztR z7Lk;}ZW;!04JpFlF`izOpB)eBIzeyofy6Rs>v0Ka2nCdJi@`|>QYYx@D!3OVCV`To z0(3`#0;q8W$_@|}5F4O!@gOKD#6Msu%U2& z>%n3jQ6uMN=IAJZRe?GInRyDyiN&el&;Tzj2X*T+@>3K*2jJzYmnbAA6e+6%CBRC($JWwJA zZ83setE-m+uG1hr>0(Fdz&&_a1FcsHDRr#iLtE(X(9}`L1epzuX?P5JrYV%Csu!gy zl;r26D3s(w$7C|oD#7cAiWHJli$E7lgSHr?fN%9G29Fk{j5N{TWI;10`#9BvLe_P<2IQ#~g~At_ZM zC$Xv$l#@z9BWDVr<@h-YpsbXim#UChoLvlcIw*ueJqAdAD27I#CnNzthHNs^pceR~ z=B1ZpfOLQglSD{$4(d7}7b1}4rC?_Z&0@qD3GyB+*ujYmW{84;COG?o?8`5L+N^=< zRu@PH#_CoDs1{Hc1IEQ2V^Oz>Qg{6FsC>bmLeeQx76ceVv-=0K2lD>W~@?75;zPXPJ=ZJ zjG&f*dK#D}rx9s#8p$T-5ovNB$tD*OX>t+CCYKOtatQ&Gp+OE$av7fxyh8Hr`73W*BPkwV14zn+3;3FxlKoD@*2ptvM4FC{T2 zKQC1WROx1>ftK!oS{9JIP!)<2^U_n1YfEtY)zwu<%ZHqK0BRK_=0VyanV{LR;)2v< zPzwQ~RR=WD2x)*MDwLGx>w*S26pBlWit^JFOHvg;m%xBq9$<4p1f*O6DFaVvAXix# zsTDd3hGwub0i+r{Zh%m12O9cyu`qy)J0iqwz~WA@b?p$Ha1uTps*sV1a2sgK54n+6 z4i8g^-$BKAPJTH`(8Ci|PJTSXleUmb1SNH)xWq;$r=+GiySS2`rqI(4#3$g*{4sOYpr-ItJ;LMPd3QG1M?|{#$LL__8oF=GcotL6e zT#yNF1Lv1Q+Us!hK{mr1u@F&E^8vzwkdSJ^Rsllm=_x>m=LychL9~O)d6WdBtE&KZ zH?*ED0yp(R*;~C>A+0#yqz&}i2F0IHA3`Z?t(ek0@UG<) zXgicps|DH$qjn!3!4Q?;b#4?jYpaQG)~Z*l6G{z;780iA~et`2oT+bdu_ccdXgv;+h07gJ?~5RqQ+dKxrdX9)8& zBvpXIp)4^+M*+lu2X1~|DuIv%$29C3Taeze#2ips16O5mhJq`27*!!Jzg!_T6Fi`! z4oW!rpoNGE;E~Q^aN{gdL0v~3)GY%ad81HT3}S;$kjTs{O@%e0Aj4vKeF$<1yyXe; zA;E4J$UzyYNc|=pi5WGJu_hUiA5e_KR_7okncUn&P(D+yMU*v&M2tMRJa|$LG)rUi z7`!qiqU?l*F|`X$kn14P4zEeUr3H8~HcFBP*GZ@a268zEkp`2H(jXe8G{EM4c)Y4d zBcc;*0^AzpD5X&dAW|tL96)&*dl3kB8qTUCn$)TTyn>vVnj;n^B%#3oE>CdP9AL?K zPd^t|(BTkIS+wnCJoND>Orb~!{DDAqus zKzvyUFFSR0u@%aoIH9mmhQtWL(iUVBN)d@80U**b$dgnpZDSGHb6AwNgo_(!7=ue& zDz!R5B`vsU1vTK2R@Q?`M(mTN;D!fiZCG|@fkI+lC1^o!Nh-K|14_$~;>cD3PhkZ% zAu|m+y=n(;Ye9x(LCsxQ4GwYv`gCq-9=vo0b(Qi#W`K?#fj9BNL4&2qh8%Jb7lKAq zs5q+&t8~EKHuX5rVGv+@px1Ol$_c0|z~POh;S1J)S;>GpVra%k>mr8!6k_4C>&U@} zY!`78|5)r%ttMzTWa&GhIvJbU(FDyVs=CKwG-%^1KEpxs&|bKLwt@*L*?~L+UVHT?NlHP=W=mp)OKL$xH!N$lyUH@E|R? zY6k6ugct^!ngxxNWP^r#6;g8xN-9Bf&ftCp#86N+0d4z$mH?0@AJ`<6#tKv#w37_n zGY0tq-s6EgtWp8wn$#4~cynT&f_iF6vbsWIQF&2mQR9=g^%)~q)O0fEg%)J z0+Glh4$=aNQ54f)&B_7K<&Zi9)RW391|5|N-na+ebqEUn&=5CWV zGC+%_5{pt53Q~*GQj<$^DnWh5%oGLi65q_c^kPuC2M%lnjl^P*GKC;FX9du>yh1T_ zNZwczydqmyS0SkswBR?h7`~YwQoVx@Z3C}3%!S$viXT|w0*&Ds>KJNTftE#UfHx`{ zfSd}hbD?c%J6i=K@Tf2-e}Gpj80i?H=s-!8I5IRS)54twYPl9GfDiis1&Re|ZmX!M z5;mO;s$kHX?1Mb!6AKD*DizXm@{Hr972H5e z&Ois*r00W{se%V{K`=gI6Dbk~ggI z0`C+@ESd)OW+BE9ZaN{xjX-PJAePnHDky`pDQH9p*#vN_6ubx$Tsf#0E99i6l_-D? ztxy0h&kw&J^9$mb zOweLh$l49i;!LnzNUq8Q&3J^c zZKpzIV4F!$%z~u`qySM+fI0!c21xq|5`cPodZ7J%;KT%48C_hG2?`m|u&V}a+!YjX zP$xpWhb0+B`Q-|U(1sDD3Wb;ljlD!r2Le**ft7-c0J|O@rum@Y0&Pc6P0r6t0kuz| zfdVxF+$sTUfdw8&9+Y4}7!tPd{%%rYa&~b}VsS=%QEEX>VlwzVy?B_k27DJ3cwwMY zOdj-lFGwzcTBsBg16jic3Q+JeKwDb{rI;cdY9XuAP}P>;Pz&jKp{h;7p%$|C4^?d% z4z-Zge8_6i7Cl0}rXCZc1fG9DQ5qA2(vd)@jfsIP28B7I%?oKH!#e~qF{P>oCKXCL z3dzv+4>(PN;s=BwaRl1vS{z@JAD@&O4_&4bpP3gAYR<$%%mdkBtDp{ws_VgflaZ5nF7m7 zkPT#zc#F@;EJ-a&%+Uasil}2tkhsrFEeFlfKnp#qbWm%VaC*>DfJY71>jDy^A)p2GX{o6>pk?#m9TTPLIhCN)1YT7I-fICKL4uu-0q#72 z%B|wm!qU{dwgC^oj@HtVv5)>o~AeV!dWu+$Of;KwkDZI!i&xS6vgL4o~vcQIClMskKxGrwCAygD9#%C5WCLXc-151wu}B z1n+T#q)d2vh76m>=NH8z4|0bf_E$o@0onQk3I$M#$;<;ST`f@nT@(OHA)qtRK>c}8 zuMj+A3F?)CrevT=6V~7W-#q}@R|*P!Pzeai^SZjQV|1Vcu|=s0ApPLp9c0F!7`!$X zba)F$6XYl~aD>Ah2ie&KSv?E21(ae+U=~5GfH|Z*F|P#Vk>Y%iYcf(5Kt2GSiv#X@ zmt=tV$?58Xmz5Ny7K2X1&jcmdlGLKy%)HFvlFVcz`yoLJ@hj{+IOtj@h!n&Zgrwp@ z!JYeXpk&}w<|BfT%#sX+M39l7 zViq)DRAhRGdIU73BhM0=tJTglx!gj?;!kQh0MIf0?^X)qD;`%1yBhEJva>< z79cl4g3=Rowi+mzW~QWqmJNa$2#~;pTtfvvy9;C_e8Xxnyr?cuRY(SPJWCXEQWJ|y z6q1WlK^8(SgG4a6qy}3DwHMay%**#qt#k(6hf`DxI*SbCTc{Ff{}VmqCPGi40q54F z{E`e%c?8N9;I1Ad4#0td*fs*%Fb$4YP=N#Xy)|fWX%XliAth{?8rh|~x(XiPRr;W< zv!Lllh2jkG;E6(VenBO;q{v8BfLz%Oby{&gD8fL20-C@p0OdfqBf+&Cq`d}lD=ewO zQvl?2aYWh&#TrjehmTl zm7ygKBpfqA83fcDPb>zt(mHsHPa1Pc5)hw_uMsFus zfg~Um5W*FpEq_XoyXT7)z=z`GQ2@2qK?75udKYon2C@OrH6qYn zTR~z`YF_AY=L+s5= zgN&WQl3jk0f(EGMOv{HPdeATy)KqX}K-qd=8$gm^^FX?hWl*MGps}G2(gGg>0oA{t z79?0Z&Nd`eEBJgBXl&x?9>Z#7G;<+o4W%s!_jMh#7Y!=VkeU>buz_Sz(B+VI&Mf_Bh@k9ES*;6?LYKz?y% zNhavz&b&0xF5^l)6c2g&xsmQ6ghO?8VFxc@^PGZTeo3ko=maE4O$#nbKm+dzpu5n) z1CHRM2a9zS3ecU2Vn-&l@K8c-=qrJftdfnbf-XEPlwfCcg2n^DF$OxtBo#VQnFwk* zWaO8F#-KAHM>0Ud8dYL289q*Tz(KF|pn z3d!Je>p=ZmB|BSK%z+~patI4}j)Op?8>VVoT3TAdbfd&Oif0KVYjh8xSW9^#z!&kz zk%wTzQnfA?A7ZG2*R0?K4=ziQY67HQB}5)nMyV&|=jWs*=Aje@P)o5G2Mt(Iody|> zgRB<;H;_?`096aLNKRa!2X03U&XkBOhExQBMo1u zhKM~#A0BKBmVopl#T>|9ls+Aj#kLAaV+hbj3cR@p847|lL!i+G30X)B0Mt+@ z1}zLkAEg0_fi4w-D#Jb41_~q4_DFOC(82>AuaMHnF%Qz~M4X=t7KgeJ?t0G~hPB8oX7q+F*f<@?Vv|%zm_S=rA<>z!phG(GG^`M3(;y!{ z0r5UGIqDUFZ)rgDI=GJy8jyq72wD*o4NV2$A*|&5yyV0Z4XEQl7qn_N6)`5bLxPwF)G?Bmyame5nGz;QthJ!LCtlogzg5-Uq zyErhqV-WQiz67^3auX{wP-S!!l%ej%un$ojK#WAs5ui|pWeCV(S-3iMt&jl9PK94W zVhg)EL{AUV5ryRhNXZH+-j$S4T?H|xxHJizRKb^kfNv&7GFeX#+Huw^N-fY(QUD!U zi4xN4(jzDtVo#GHA>eTgoMjhUIHN`ddTK!R2(0T5 z3mHhrfxQ8ZH5~;|`T-Z6&;$r_C~`pwD!Jh03?ykG*GQnUuo^VC4@yGFg(132lyqY9 zl)$kLUJ9V6hm`h|YVnnmkmQChW+*BtKprL#GZ1g%EiEvj1>$O0pn~QYY_U~aR49?a zxezvu0uCnhcvn&_wo(OMJBH*Oc$oq6KdBK8&UFxbp%oR>UL_r99HYc2JpF*0uZZl2 zT;+fop0F7J@PMU0q_hXk@Il9BL8Gjoju&VE2-^72Q7Fj(AIXtff_97_s5c2aH4?jx z=&3jwUbQ01O=y&Z8i%$D@Od$C-3=ZpMm7Uv3rHsjLn0qO-&vAh5D&gLJsuJP(Ao&x za7V3tz{v=r8#MTdHOWA=fi5w^*j@q|xd8QL&;|*0bpwJt!yH3gHI#A_AydnsxyT~W z&=Gi664d1^C`v8M%r7lgC@4+J$xH^_l9gGUp9k9Z=?NNXQZG?R%S;EY76z^E0v&?_ zYA7kBmsTo(CO1=4;1vb5*8v@wMT&MbiM$l>fEzrdpt~fX!36awHeZ4p|DYx|r0EY% zi|U{{10;rjPY2WvwCOR>^o|w8UU;V*G^~ehO9wd4z)=N?Yq^fE6N$B8I}K(?i@8202+5nj*kkn4lGsK6J$j zbYxKpoFyT-1fG*%o1+k;+Ykky16MQ>i_#&-CqqiEywq|~ryYLq3P>7MP{2k|L5(TQ z`5lldq%{Nz3ZQi);HD8|1OXH@h{gygl_CNOB1N44E zP!G8PG-;Kc3S9w~3%c7A>G$*xKFNG^tuPn2;G%*L%rKqmuDoQOZ%`8e)&`>HX zE-6jP%vXXf*MjoF+M#Tyk>&YC*~OqEkic?q9#pmfa-T9-4$6kgfTmP3)4>uDCRDtn zC^Z!<3Sxk70L|7ZQ-IHuRVyf!6&o2Cn1XtuV3q}pWe7T38Z2vQ1Q#=gv*0=m&EaB} zN(!~$c!5N28EA<}fnFl`a3#>~p&DhHpi9=vib02N!fPFHfedSuD3lfJfp6f5&q>Tn zFHKBO1r6BhxhQCI!DT@Ab{6ZUfcD;3D=4Mpg4*y(=_$z|cjTlbD=E}+73hJMeivn? zq-vBE>nJGY=LJB5PDw`rR$^)7=cJ_N=_nKwWodGO*&1MGW*X>JvMkWWS((KtU@=IN zEYM3&%}XsxEJ+0kg6=MbMmH!bK>Y|<^uyIb5>;wm3Y>xLGLW{^L`iKB~&frW{=p(&(n!r`P` zz0@4=oItT2G;ENsqyr}dsLpCcuGY&)O-xBG(m@h|?1(8w7J{1RoSz4706`}mko17U zu^4peC#Yb8B}N5!*#S>HphTw0RiKxepQE7^Qv~W0aTVx6`r4p=GDr^8>;RucqJiln zXv&T+&``J0cY&sVJ9S7@VX+tDJBY1dO+@H{`X8>zP)FScYLk0uW{O?4YH_WNKC*~9 z*jaj|rJ2x7fyE_QgAif|s6t3iO9z)=kQ}X-nwXpcDf7TeK-1`u@~5CED>wcrvt9?;Dov0PMmp*?`AJ!J1~#A$5h$YL3p7B2pevI>qF`Db=F7}t z$c^%#LK@OvfrMISu}f-FDX1!ejg@5PrGPxEmsFaWlaid4uA`t1R-z7Wx`5U)!F2i; zlw{^+7N>$LKuDD6WfnsOt5Q>(K>9%@folQKmB`@rv$mk?;hp{cg8hA59RmU&Wi8z4 zNf4JOA^E4cGzoHjwVk1jK7s>E*^t3aRHNZH&tsTuXrrH#iQQxaqD=O1403hx@pQs4 z*@$S9L5U;S!#~u=MIpq)Gg!gNRYBcIT|uKfGcP5-yjauTo(tS3A}&CT(437N$)GVx zaC;4ERBmZbNhUPGz}f2Xq*R=lm!6aAmYI|4oS$2enFB7!K#_r60--TKFBNJkl!cHg zDN0OEwNov&(TA`==KyD>fs%wC_!f7)fTDa*@u;JqZm6!Kpl+b933pFnX=Z8(STBTy zp*Iku0GrOT)S{&PVu-ykHiqsnh$5`|OLB@q9xnsY2+tH(=B9vh3fN6>4wAvemAOgz zImMtZc7dM1AE?U-N|{K)N^Xh8B|e#X+2Aou49U`*93>rvOq37`h8XITnVssa14=4j z7i*#g7O0Pqn^^_%Gn@l;8!Sr}r>1~4qv=g9C zVKXeV*e5fuv;t%xjEQWhe{cju3dBH`fR;^A>tH;%eNX}HHo6t1raA??Kn#X)kPQ#c z&q*xG1jlJ+F^q#yP@J5R3To09flNSf5DH3*Q(ZvUgeE7Jq^5wKh$;egCMZ5r;Of#+ z6H7{qQo+eV$uTDcUDTJEhnTJ%+mKLY_mzF?fKr9qJ;C6>oera9`%yeWCgwnkHB(NgzdIfN3 zp^6}d7Lr0xu2J$&OT!vSrNybi<(VbP8Q^X6PzNH5AZ#x!P7Te=ED1?1E-40Ejv|0$ zH>3c9sl^sG;PA`I%!A}A1P{Xy&pePSP<|(n)rwLTDHo#x^+N1Qg{Lq!yJ_f+8DL0O76l)V$!5#N=$ZqQu-( zkU~@egu;T%WU$vkG?McQG9eof{qwM z@Ns5(UVc$(Xdd`7o1&7`lz^hd^xQ;nD#0lSZ!0E)TL7s=;IW#N+!R|S(BMFNO0unz zp^}b54v1j{>USf0G0^^%hLMhf5==X2CnD6O=y?Bt5Kn)SWTo}6S8^WGwNM@T|lv9ppHvbK~a8sQDQDgk&1?kuZw41aYGO{s6rX!uFTwm{Gt+&5Ms{&R1H%9pdhg%1Jshy z0s94^2h!id9x$1?1t4vpN*-b-F28^pMV?T_xYXpAg50PCnjI|3&?_lQ%qs?+F0BFW zO@Rhric&$%xUy7`J7HZQh^wbi)l+2>k0=H*TN;B+nl#-te@hT`O zKsdM@mXeijcevQ4SKor9LG;0_q}sjs?xI5D_&Mpr8TS z2Fc@|c_pCC9TTIX5du!heu=rMntEBq`FS`C32+M@JmCh)gwU2YLQn^>*Z~~pA6#E@KBxf7D=EemHqa6rY(A1}kcH9QgDgeBMNp*(H$erETm|hH zrj+Iu#6$LMfa*|WE}}lkO)P+Pr7|H^G72A|46@(FGq(WL&jz&&Py~?bKk!IE9cUyV z7~ZLdRtu2&)i2)@TnzhUCV~4a;N*em9YSYZphbg%K2j?d92eLmAUV(_wV)W(%SF*s zRGJ6!JBUJfrl>RzH1mM2(7DX0ASDSj7Ex512j!@P&!;a>RVW7?*8thU486|+wD>Ql zJh8G^AvF)Q3L*uz84J9z7g86efaX#2@)f{N2Q{WZ3xvyyGD}KQ^I$_gpo#-(n~Fvl zc-{fIic1E&DkcU(gX%XMeIzXiUqKWE6r~y&m>7dRjVgey&@bO5H8&A7G=NZxB!;fs zHLo-`q_P0q8iN=N7eQAF8YjxnD@F<4{G=>U9!C`fjbjyqoByd$3CQ>pSR6Vcms(Mf zlbM`Zf>c3+b_W-`Ld8LyZcr@<8x4cCJ#0Z+Aks3^^`HU}HJ~ZG{QMkH;{-fI2et`( zpAA?LRJDSIK&o}XJFj8(YeGhEAPy`}&MyGX#(+v_m;xOIr9^NyUkNg%1)3wu1l>0d zcS(9`36kMpGjtS`K)caF@u`HbeUX!y1P(sX$(NwC23so#Hcv-ENkIu`4WE;l1Tr30 zg5_i;ff_*Y(U_FfG?2+CkyVshj1YtP7}BCpK=unn2$ZNnp#?WEwIUUy*-k~nzZ6=G zgUSn-u)2-{M57+4yoc)obp%UNd@_?jT^>*tL=}KH>54$NB<6y`-T>6B1NEaoa#JrSZL_Y#r=N1&Df)f|0)tZ~jqoJMxl^(?*`OctS8FpHF`AJ#UT6)R3Db`w$i3@8ja7)lyOD`q0z*2u4^0d1WF6;Q}CHqa6ak_k#ceQ2Z} z8Kj+6Qj`jwQ`Y1Hu|Rz{Q0pvJqohbjp_(f{FR3UoFF7MM4|I?j(&6KI`6;P73Mr`t zB^lrn5zNw2P`AP65|Co3Q&RI%bP%RifNM#Z4>dsQuo|ilF%=ptAOmtz6Vs6AO>t@k zx356e2{>FpQHRk>1?kl*O3eYO0wveH6s}qw$U-_D1tYNcKw*sP2*eT-MA-<67f1w_ z7J>VCpg00m%1B{_BT8Xo4A5`{MI`)yG(9~%Sh<>690Dp8L8T+=20Hx&v!EIV zye17anxO&H0UFm)h6vl*Di}aFH^vueKy6gFQ3bbXbik8<&`b-u$q#BHxX^>@SHM;p zfaQ>~k~OG;M>QWjgr!;xnodOVAc%vou{bp+HMvAXU0GcRQq%H_QX#!^urBbSj7a?a(h`_~sTHZo z;F)%p)Z*l#Oz`F7AO|4oT$j{>RM1wfWT>SG8E8WXOUQyEMFSj61*OFq8tOJdr8%G> zzXH8-4Rz2!0k~*V*HI{f7Ls6zfW&mTKWvpieF7zQP;8WIsJmnq7vv;XqH0Zn2!b*_ ziaGv8DXB%aN=60-N}y;c(8C&?AUEnLKpdG5UbX>pBuE1&PryoOgzxC}v(f}pj0?=fp222aMhyuG9nmdrQMRH;ZG*5wgREZ^eP+vnjQQ&+QT$%)0rVY!3 zARSOe$W8~dK^A~*Qn!HyOC>l<=9iY_6jlYWaD=JVL&~=h1?3v*HpP|2 zR*-Av!8H{US6xQ|hOq^Mi8rKz!INnnp@$8l1;Z6m$iC`Qn@XZ{cv7HPJC6G(3l%Q>-Ts?el;ett{Sq1IG zp+!JaVsR?6LQpnLOUtZ)C3MijQp_Z-08Zm*l?BM*u%uD0q3)5IQ{bGR2RdOFG(`qZ zm)NTckVd4+0>p)w1C5UqMBRs!O^|9tSXKe)L{bdSBA`+hBn~YDpu-v9j0Q`W$l77b zAbL|lt5rcq5S4<$1Y$WTM3O<<@QT5L;3BU;50c}OA=+RifdaOK2G4~~`T04ppa+l1 zqsIlf&kS-qC=n!QK&%J595TI+>~4q>i1lD4;89;l9)*Y^ISMl&!W{(~k;LmL9R+nz zK894A;Px-56a={v!hu$I5chyqcz`n^gbQj4RKu!P@OE2U$gX)k5C=K;aDj6U{BWUIirwh+|TVA*X z&_etKRtSy_Tv|~h1Vt4j^uZM-QMQ0GC|Dsz3_@a4-9{fdl0cCG(t;GdkkSDbi=bdd zswW^NJ4}+k=?>yB%pw#XeGtbXDA+Z51SRnC) ztQS0zj-nAP0?~y%R*(W7(e_4em4j+F{5}L53`wbu@Nr6HBd|BL5o#5%D)!7v&M8ev z^~{4dv_K6iTP3%|oZ?itfnk~8V_I#Mz{_5hK*JH>#FAg20r50e??Jm3SbT<*u)w~8 zH)aryg=&U70o~*1@`z#@>JLc!7nJbPf)!>t!FCP8F$z%IZ1j=aIj|s5x6y|rN!%tw zn_uY45LFh$ooEd!3LJoLGPIF}>Hv_XXr%&jFB2u=Tq^Stb2F2X3l+4|v)B=IZ2+`~ z3Mxra`g4f*f%QUQ+M!04flkq~Rf6jWg>JcqI=TRC3$4ICDR^*$@*}to4M@x^f<+}r&{oN{0$ifN zvo2Vftx}vSN(!lo1Fe%QElC9zC&;4+ptK5#eGm=Wh=G~%K&zf{I}K|bz(;0K6AfG# z=7oUNqT-O*)>r~s z52+6UQA@4-3yH6xlz|~34K1UGY7QPO*%iykFG{9Gt*l`M6e!7}jDEpJjp4-+N)H94 zH3TyQ-kw8k=HaVr@wWMphUigz0PfjChVEfjL!7T#j8Vd%xEdyp)Zs^IO&|(k(5^3i z(9{e<9#n5&G)oaf^C&I^4HxTS6uOY{ZiFz*@g!6ZINb&BpJA~Ytvtszu8-n+d?^Id zw#fwDi3y(+z@9|lDix4QI=E75rxld>9guH8gR}8E3gA7~(8H*Tz*}ciVY5Ks9n$b3 z8dMv@Cx~D%14&j|kaSZ9s$|PG)RB@OxVHvM!XP@sb_QXd8~R|9e!+*U|XqjfsL zofnAJp5W;nc&H(oyI3=zXK@f@t9uHxi2(BlY=}SsI_m*#o?+Tft3-=Fp97iBf;4zQ zxeixjh8Pz@+A^3bLA5Gc3_xeMNQwe~$cz{~4p2%IbV+I_YtT#-D11;{3=#kZ9V~Vs zNd=Nf5#<$@@lIGu#ajMhE#GiWv!b{M+MdOj=v1i4&B-gaRmvzSDX`Ml2kqd>O)S>S z%}g%JFV0UZ(M!(H)dzP(^o{#B7V*t8Q@TgPV&|yfZAhH6e3^Y{1GEg>D2DI!1EDU0BL2S?iEyV~+ECP*nR&zxwftGhc zmhLOXLXNpYCT$g}6%v!co1{U@>_9yQ9R-Dw{Or^`(7|6S8qn3*;3>#xrGla?J&?CS zJE}2_BWM^j>OsrBkqiU{JEmbESE3mR9{2&RrG~o>Y9J^{YeG#d&dH<1iNVnQ;z(`; znMi>fVJ0FwvOK$(%8mq?NM%QYQVux3L6>mCH<*AUi(uS98t6#jh;AOLbBQz$RPiE* zB)XxXnF)l!L>h{0AgF1ehiVEaDMB#;164IZ?gQBmOO9aU5|fHSxe^?1P=g>8nsMma zPgTRMG$#kNNEXF7=z1%H#-ZmIRSi$j@$;D_l_d7rsS#`X+VugElvi{ z5*2`!i$D*}NiNPvEGht-2AgPr*#r^<9UhZh461;XbQGYqm0mLV7$W%LC$PC#@bPG% zZ6V2t=qnCzX$>t-Edp7i3A!E&>^5bvDe!g9nCJLFT%!k4hI}TCrUGn|6C?vQ4^(M` zX0|{F<{zkhf8a)P`Ac7fBWyuwpC8@cfO$o4cQk+0J#!w2FO>SGzzl~)O{;SEyA$X0ItI=GY6yqyZ{1J z^n&*{gAzEZkDv!bLcCp|SCU_voPll`(t3YTDucGqFwIiH8ag0#NSO*8I0$EwlBv+N zl99m5k}E*YAwGpsHRixpr$OQccZg6Y;y`vG#~XCn3F!Lf{1SLEl~5gt!}W_z$>8fv4a`gb2*lex>fIC6L(% zG$%t%#9fGkwdz6iVs|aX2qd?H+zZ0+u@Y#V26GK)OiKgQ6(*}vL<&-n8K4pc)YF6% zBj9uZ5(XbYR!|HtN<8No(g$fbW9WnmNALq6}dS`da#86XrBpq z9f&3um;!C111&LuT>7c5rmmx)prn?VTVSn(U4;!qMNSDW1)xDr1tqm~umb1;Yp_+| z#YK6k<&a|o6{@*PiZavFQ;VRjAIOP>21+_y#TofUC7^Z^xPhqz*#fB$3_kNt0TjMU zI$TIAqd>h-$YgCvssg5Ba0Qf_3_7+pC$(5fhYJ$8`Jk+)1U)YRBnsUI2r@0VI9a#M z&;aBX$dQGhO0F2{&T0iE=ls0l{G3$Bf&$P^9LSM1sVPt{WRI9nW)es)I3uwr6|{{F z#PLnc25*G}v7yI3D20|}=46&sg4?hVyWqzof`$eZl$=4Towc>Wlro5PQ$|t^TDc5& z8CP;L!jZYf$w~?epmo71`6YR&par|t3QD=fd8s8}{k5Qt9^pu*Qo=HeMtOEI=uk#* zcCNKT3{ium z7u2%?55<5Z4%DFn1-PLBe2hki3mS@`0t+K;;HZE$9$puv*Z>57=0+5|CQh zMx1!}Xpj!j5=bRbJSc${*TZu{Sz=MKCKpJ(UU6y(NFIJ{H%J0hz(T6Uoc!|CB5=sr zDu7q!TY>M~R*F+C1?61GS}{GaDow6<_h>K+tRA$75o9pfVocY-wy1!-0?I3BJ_a3; z7yvy$2kbPM*TEH%f~^8bAE+~fzTO|=Y|vtJ1DF*sUxU`ef+R8(v=t0NOAIp=Y-|;j zAtr(s7(z>Run1^V04N+_7;XwU)Y4NC3yeWdfdxCndawa_90{6o0lCCb2WCBdR{}^Y zC~AxH3yMKb0JZar@(aMr(Lq}dz>$jXSa1Lmbf{ZqYEB9`FhM6;<`;l_njri1;?qD< zkXAlY7(m6rrb9disxP3fgLIJcixgm+AV47sk+)Rqtpdm=pk4PUi5h%c1vnaDJ3%0EkXlp(x{MGKSg;E&zy*hbx@xhy zLP~xr==!!21@JX{AR9mjO@ZZY7)US(Kj#I>`o_)KVcL;F}+F6HDNWghBgn5cl7J;}_-= z#Qq)d)Fae{XwVu_kiS4h8EV*oPE1WK0c`?J11Az(j)WZMf$2twepnd;wGUJ=)j|qM z;ynk=K2RfYhZAUI6I47x0|{1LQV>coBT#|~WfT*f_z_ba1cFeZ7VLUZv4WZzAc2VH z0n`wLxdk%_(X_!7BK!>vK$ryB4Oq@IF921kY2X9I;fvKly;@L%7&Q6@WkW|JAf*DR z88Xix{1nr3i4ZlNeflp{at%I18l34^Q z6T#}B=i)-LH(VOzpv004y{!DqJPoMHItuDZnR%d1JZb5ApzVmDkcFJj1fG#dPD_XE zgMqg9A&vnB7j9#aH0Gs1-3T8}2Ay@0mL3iErJkM==qROF&>jGgyAdu0t=n1rJyJ^Ewcj69Ec#gNf1$xSxQP!-H;?iW0{b1@+y`YtND2hs+?19Np9BLnpAi89)&}Q-%!YFy z_ChDSK!(~XAVLi8I?yf1Y5C9^3TzteP#kc-0xSYG17aW~3cxP4RX}i%B|+AJJOD38 zkZgnK0IAS~Xo2P)r~|=%hDbm&21EkFfwhQ0t^$|B@LB*`WF{x(!44&YopqxFK8_?Y zJr&%j0q?eh6d~Y)w_znvha0X!ZJ(TXGuKBWdU0s%1rT2+8& zl)x?o?bb|7*Mp5TfD;^OhykJ;Dw~{B1d;$BaE#oL2Gx#WQ$RHy!g06_gF25W^B^T= za$34?VqRi8__%RcIha`t8OH$Ue5jx%sB8fXfJ_IUGUo5*2CIWg(o66d19cXPNl-CV z!=S+jvIcIPtpdVuNFxf;0fEkwVRI?gFogIwvlwo$f(9rVf?B}vJfi_|6?hIpNiQWQ zM+uy25Sm~G7|7Y+GuQpy+#pd2bvh#Ep#mCk&0teCK!@Wbrl*49TPYV3^3Y*#9EsU8 zFS#fc)ObzI0fh_LL7*}eR>&zKM!~p1r@g^WTk;2=BaeDyJV-t*9ePf@SrIM6i3*P-2@u|Nfn?x0>bFdgC6|_Dl465- zib3}S>4B0iG}ggBfXhIF1)SGFx{gFA|w((RW~G;K%ESd0%cba*9sJa8cO;~ItqE2IXd8ronZ!-q=L_| zDI(xOkTFPJ$xDHm4bIRYS

    Op!fk-E68CCZ`Ys&GpON{o(eii7?Nc)K&F6W8pHsH zfF7zA&;=k!AqmfQpaK+CC9KW@$w1A820vUAta*uDBTN=mGq_;{G67smDZvU+s1YCu zaLuKo0P+DMxIpJBfx;P-bU|etB(FoqfQ#X|9jplwi4YZNr2$wTy~ry9mqcJum~&tU zOQxrmK+ov|Wm|B_YJjW9+_OdB|=p+zs~ zk}X|b(EX{Ipk4k7B^ik&3VHdklL8gW6N?pKCsF29g2uX1lS@(|V|I|@0hD6Fbw2za z3QYHa&r65a<)D5Cq~WOqJ_9wexVSVoRY4shrVcvD6na*xJt(o_b3DvyP&Wc}GA|^n zVVuRNqo4%7js|2i^sIbPMFKu*F}0+$C=b?(26aC`p$rWuaO{8*E|wUA6c?~+3Vs3< zv>3x$lYvYIm9shuN)X*3N93ho42@(Yf{&Ypj#QPT7MG;u!76re0|PeF4d$R!9$*nr z#Q|zhgEeY^g+M2~KvF0)+TraTkoTZsh}Ipte>01n^9w3{@{=L0G)U0}%A?>BK+v(6 z;8Y6oAmqSIzkIj`6l)PyfEF2m)FjslWah(gfh1$e{{JZ}M=d;v|m zfzSO)P0;`kyhB``n+RIy5Az^&pb(VXVnO90*y*{61z4*xxK@x8kpl@7pqXi)P=f?I z#3t}^3eYr7GJHT1)gFifh*hA|fl?Fmpp_;>M!^cR6?$X3Gd z*96C8NoewS!Kx}9^15UYML3qxAR%UsiQyOzJtH9^*g6#vf7hz=(L=miH0$U3;5tREO zvfy3;q-Bnf12<~H7J^)XZYg*rKxzpn9mA}IowyB2!BB%hizM@Nia{NR{JczP1qD-y z@Gzcr17Mr<(xJD7!Vcnr@L{H>rzFF>S>P5os7!?Qo}n@juYx5YOsF_yp(^AkdC(oO z=_$#eD@|+_z&Da|fw|zo2bEr2iABj7nI);opu^L^0u>f!ps{S-Tw^089j*%S9a|6q zGZQ5pu38-~gl3eRD{K{%bkjgrN9b^Yujzr^&jS(<2hVsa>2Sf;J3%gg07<1~=9Q!+ zBT0d-S^!H#DCuworGkdhL6b_wAhG-cB^@s03&%i$Y2ZWYbwOspE=vLnm*#og7LN%9v z8t9xsCEc{Vd|il~F8DeiNMK>RZcCw>%Mo_m2)sVFMRK7(Xt9kys1{Yy^*2(`P0Ivd3#ALo z3m^}I8!ceV{L__mxImjUAUtF2>Vq?@K)b7ybp4BQC@4q;4~Hu0`r|O7R)-6G&k@L_ z5XYqjcsfJ;Q3mew!G{z(O}U9>6`+6!c7}mw(gVJ!V4j24Z zCs1&BWTt0;6@-I&&N^JK6(vQ9Z~+C~@Klh1k`9+^S!z*bNk(QK=Qus~@J=rmX*-E;-r^fZuOE~K3gpbQD>--5H126*}pS{;E} zAE0pP!FKMM2;OY=)oA=i3BwL@oz z5N3j=Lcs+-9wSpSi?Evr9|8kc0Z>CB9ziXXAg+cgK-?b#5yoLNxD3PP9fTREK7xo7 z@)X2q?B0T#05up?u|ex8u+P9=h2?GqP?AYcErBkVE7pK9bQHiBydYXJh%rl0TLQFq z9J*o;G58GA3vNcD1PWxJK}SJdHw~)DRvpyOg>oQOWM&$~PAq|nOFz_&NM3}f#OgTxCAdUeyXp;Kmnd z1vI?*1r`L|+zdJ{8NT?fnhP?B<&&8N>ampOfiJcPr3iRt2H6DeGl9x&$mlVs?^&Sd z>*(ws9095aQXmIFf%brdxRB)pN}39_piTm+Hu$=8aGwFx83Dc&*|DX$0Kurpab|IuRhZ?V&1GWI96?sMrHYEayAW-gw zVUS8t3!YGVfz6325fy&mIh@SmG|&~RpeaZ2)CzRk3P;$$)j~>WunFm@B`}vmOn|Jy z1J6*EWawoUL*f+FWQE8R75X4ALkz-Rfhy^O%0x&&;&cXd_a3z9g2Vwhv4e`J+{EnE zV(1Bipp=ZI96(kAO02NtkE9J6C768-WWDh63=*u#`FY8SB^oeOAn^@y5_p6I?3RLJ zV=xUK>`*EwDAmm=Gtx~@0rk}rL5nQmVu?kdei3NtHhK;QnFgvuK`sZKkddDQb3aHF zl%C*rMuVynXrfgVHfX04#^#LggV=faO2|QVg||3*tOI=*2Xk(66pV7lf5$NR=~aZw55| zR&znj2Kh>_A~(k+H8}@#esCt}Iuax)s6SkDz{BV|$htr$yXEIWm)e7`+`!a_Ll)v^ z=tVrmD5fSBfNtmmp8)EajOLgk_>NdE$g*j)aDaIkVJi`yM{x`MP)igy!B#e4mxUZ8 z1YKf@st;vL6}l8?k%D_^W(tZpy}Vti}LeJKr=PP zpcNn}GOz=NK(Z(jkZK0LNEDmv(Ub%f<%7~EiYd_40dg+LC&f6T1to}yh!WTi1^1%- z(gG9&0h71jeI!)yKfvN{oV!;blh-y8!t)NmBR-Tq5gIYeIG87~Yx)7!q)K>t_078ac zz^Crx7-@nGx0isGq@{x`0Gj}=sK7U(DX80oBxdG-MZuR^Ab8MwS4)yXbJazqpbN#J zXJLbyA;@#qaQA~&mO{3tLvA}$P`7c;$j>iM1)X*VvoAa&HBSMacWjkn)L}Q1LU!ds z6Q5>`I>@6j>V}4T1~Ka3qp;!&G>kzvmw_S>wEG5}B9P5;t;j4cDb@fP=^G55=7pbb zQVbffHa60XiP6)GiSdOU^Q92%;$0jQC&-xp3{IhpbIk zN(Z$#LBsaQyXm2Fj-W$L@)Aoj%Tg8G{X!K$8`IJD)I(LmhIGNoKqi7J5Xhn50sg_B z5jqOKnR)KvI^b~(=gM@@CJfLvLh$XmATz)iG-s9!YO6waxPunc!1t_^y_+7kb6!V5 z3FLl|!@&F6A$Gu)qk~&k7`x~}V?q#h;6@qLk<}>s=aWH3fI6Y+$;n`YA$10*L8_pX zo1d2tlFJ2er`J(XDu8UO2hHo{=XvI3mSiS^re2Y%N)1r6zpym3C{+U%IhtGrdawyb zNPOuf7Zm7a!nH%LEdXtB$Gx{5ac~ocVc;X2kWQ;8&o0(cz)+Q*S^|q%P%|C0vmbgt zqb63>d8tK-C8_bCJ|eie0`9PY+z3tP$bJS_wuD>=N#ig#fD<`dsGvKspePHvtP)hy zf{q*nr72L|Q=k_b;_2fV5((*6B6%LH2WANtkCj4BVt}7g0O@Xm`mHFZ9QitWySjP$ zxRPu>%xW|XARYiuPV2$9IDmqUWDAl(TcjY7gXDIY$w)^!6~V<&4s;?RtU#$0);I!3 zKe%-QJq-eU7y`cZ3O+^wboNpzB(0H>BEhzS5*yeS+$j#p4sgU1v;(XPv~UF4QmTb@ zU13c=NDB|z`O=7YbPnzkRf*Z4$ze1rFup)TJu@vYB{dDYf*92FtI$wafO2hB zi`C%^H6hN{gIDI@dvn!O^HMU?K-vm4)YZXR1MCS{l2rglHPVqkMc`pg)H8pSVq!ow zSxih!j1s7dg3ek(ltPcOhyhCqHAj6<$sA@n|Xy7zLRTH8@ z6Q>H5m>5+J4Lx1Wm>7s+6%AEGh;s0HV2I6F!T`MjDaptz2Au!^8ssV{$|{DX)8zcp zJkVMY1MrMrd3G^qw7CF!1PM4@!wWXZ(!b0!1<<*wpu|y<2|A3$Rsm!-e5W%cEyIk3 z(4b>AKt%=UF1A#d3m^(Xi9Q~b!LmRWLuXJy7C>_`e6$->=aFS8$YK}<1Qx_*xSgQ-2b_9AfeXT!X)p)Diwr$K|BwL3AV(h`S05|PyblU6 zSOEyN9O7I^7(fdg=+Z5yb_IoCPajvm5CvNW2dEI1k_9!gbF))2i!@R)i$HBgnAxd? z3K|9Z#hDd4DjLBau0B2?kpZrnnou)iN>nr;7HKMgwJYcrC_oB1WMe=(M{w#;(I_rW zDlSpb*8vq+I;zE*pfVCgJG4v$1s@hCgU*gBMs>Gpu@$-@F(nSE$r=nW*Ss6B#A&0 zC!~A>Er?PA7f_%zC5VDT0b&a*p(#UEgHsNaZL0uffKG=|fMjH-2(lD>9YPtTQh{cF zh)UEF0W$T2GLr%JiH?F2s4f7-0w_XZttlmlogiVu8ubXqyJEH$qLl5G`0ms)1#r7EZvJNvo> ztLrF$C9O&eK&Js~!m7sNlGFlFZyL1b9IODevOozmg^-w+Qmh2K8alTaw8{@vQEqB+ zabkL^5_ko-GQ=QwLWgi|74nPqN{SNmigOZ6QlXbo6g%dnfQIFYHNc9%%aLKNQ1ICU zDVas6f}m^ys)seXAnpK>`9%tlB{J|6f`Ur(@<6K*Rf`qi?uYss5+tzf4=$W?i_>AE zU_KyVF0V;$8f?SCs|PJb+4OP(}yk zU6>L?Aq}eZL1i%L1WiOa3^D@TP=aXym&ox2u+j~b5I`7|SaTp7C=_fJpivK6t$}PA z$S`ob1zegzDg#WTP?JJ&MruwDXi7%|l5pWwv4WMY0=RyLmK2$3pc-2*wIWqVMFX^J z6_QaDpd!xsxxodg$(l$tIEqoZ#VPs4P@@Po7$B_>h<6~RZir)$yK4w?!3${*f`R~d zkqeGOh+&AsO*A#Rkj+%ERZ!7@8?UJk&bOdwMAi&xxPT%8HS{266D+zB#zWInCgg@v z=;&TNXpAJMG!b-I1DXVAWmth8Ryk12gPOb8Wnn{9Shd2NvrxUzIvah^05m*-Wt_P{ z4|G8YWLN@{E}?vg1Y-06Bn&!!P6NKT5!7FTpGB9a!H-HWC}V&yG`Qhv zz>@GBgXvx!1+Y?0)P*__zre!^t_aj20)-3supfwX(Ud}h6)X>O9N2KM8^A0*h-z@0 zfsz2Moe7o%D?@cEXt5b+KP+gGkOnB|z=;jSfSLq}9B7*v+>8WEf}$1N@C6SLrKIYC z`g$0uKt&8(CCn*c{UH0m&H)7}s*{k(o~3#VD5mFI|w^K%0Z`afp5qKElvS(z>``j zsRbn&utb`Y53)o@AtfIcR$LGvbcaFAgX#qbNJ^?+W-%n7Qy^gvNde$Y0zQRD59&v7 zE=$XUa>4!t4U2-E0rC*2i3V~8+^>iP4>JdPUql{g@D^blk|G0to;V5nBZVSjvI6!$7GPq7afKHA)~}M%D(I zC*wkd6AndCr=aJS%v_Kq8i+!>q$o2tH#G$shR6jnsF*PXm5Y#|h6IHIQu!Wj2yQhf zgJj?V3esf&Y5{>oL8gO5z-1)FYlsO9kYp}+hak9&g<4aZ>kO)0A*Cd$*~sN%9+EnE z9D}noXs#w7RNX<10Yx!b0$O&%!PU2vy@f(O)vLR6O^cY;$+N-Cr<1s$)Vr>6v7 zsDK&}pdf{genZ>`_8-h+U=FBtXBZ1sX|4;84}b+(NO@4fEqE-%mLyJp7i~Q)f)5)tLM{i3K(k%o7G5=y^Pq$G@a3|(dXWBLF?bP{4p*)oNcPSNuVXCpoL$c+=QeJ*^RjTn5zf5$Q!!a4CIx>w35^!I3L69$fiQv54utu zWmy^c%23GduL{Vk`asM1REtsW;fAjcj8O;YLWmMaBqv}~0lD`Dy3iOjrU6o*0F6wGKv@k}Ecif>2R|i^!4>AK@ZJ?(eNSH!G z&juw}K~W647bqtOBZa_?hom?}LLtHn@Pvxn^{|z@(BpB6LCFLV0G|nD+jJ1iHDU2y$>ZB<-f=C4)8_C@6uh z5Gv5C&``H=#F}Ej&cq=Fnz2hQC`RkXYA+}qCzn(}A`B)5 zN`$D2K7C(14Scf1|Pb)RFey;3nle{#6XHb*H>zSay%%> zK<9{%3ng%R!6ptmCO$U>npn_c6Oty2{qsO!3`;GLa;w-Mbih2KpeY9}&OuuSPh9;J zADo&4K7bE&Z(y;tiiT=2+zL%lB7yVl)X}Rloaqo-@eA?0I)D5|O&s>PtmM~DvM zlL~Y~54499mQtWX@Pq>8gKK(7I)RD6lL;j2yH=zomx5{wm(=3qqRavi6C9CPCDe5k zz-b3+7$p0HVieTu#5%4GI*Bf|0@RCu?^8nv!Se;YHk6*{0bW8nN9AD>$SZ`dZ6RDj#% z@D>Qn`JlbAc_rYx6u@W6SCr@|wtNifJfs?^O6&jGg4Cs`#3Qr1#%}OB)GsQdx0xV@X!SKbT$YV)E!g;2Q0K& z1r48rh9*F_5P_9|y76EJXygJkaR-?-gH*JjBO767=7GZ=G<=hp2A$?ZKC%pK1gI~V zkE~x4MKdS~fkP}hJGBxf0~!VgYX+N-5x1bD86jRRhWNF(C|MId>_MJFnG+~3O4bA0 z1ep;isetg%X9O^8%S|mxPsOwjV!i^>qywr2nQ02y(2!0qh6Xf}XF*;B#{uLn1!Nf= z1<;}t(3WasQH*H|42Ob}7ih^6IJZDKkYIr&HU(P+s5mGAgG`6Xz=rZs#6YDEhAq(j zYM_9J8jdIcz&pN*Q%m%a6zM?=2pt8G*^mgp9<8al1tpabb0H(8pezq5p7h{yFykQQ zB8KbIQcIFEz)1&W5cpap9R<)%esBc<&IzDl2)GEO$qAN(CSY5IqQvra&;f6tLMJ=5 z608YqBxpt}FB7`m9_mD}1lT{I(1I8QIZqLE`3#r^n(ftq83eK&EDEt1Y6|QiVu*gQ znVz7FIv8l*r4Gm-oXu9rYT4h?@KZ-9qfiW19MF21 zzTfo5JUtt%?)nFf*8=M7c|HP z76UP$V$i`DurQR3sER>);lnG4?I@r<1)w$}sIjONlcxk-O9NSN36TWrge;ECO)NkX zgW6GyBm|#U4}dMlLXu1eZEuAvX#roF2{ssN2&e;!Y!GoN~C4vAR|HN zqhm1=>JhNpKxRVX4s!N7C`u5kH6YFet#XAd#{xIez=zU;Y=f=C2j?-Uc5o&GIZ{DE zwKyK4AGZDu9EyR`@D` z5}*>Og9tj29d`3D+@}a!)Xy zWRxa>S}6L^txGUkH#xs3RlhtlI}>CqD4(GgRnXD`TI7LZKOo58%hfp~7_{mPyaEf- zqJs_zK~~v9gun|@l1o4(4QK=nG{{}7qo4#z6-t_*`@IyDKr6fuyIfGM##KgxGE9yF zxR8c+sURXHpd(WXkP1++5>UMZa)Blnhy_}ViaOyAoe8!@)XaHki(J6Q6zeGDf#+Eu zX{Z*m1P``w7nCr-xk~|*HxyJfV1cBm06wz`5+1O{r7#24)pZoWSV0{T4xk`T%*nAr zHUzpD4{P=W8-`R_Q9Udm27;4vJWOsqoL5fpQ;)h|->H}m|h{XpGgTPBMkQG4g69zTEN^?@d;e%xfJ+cO*mGmIX zP#ureI#eY@S%|6y$;$Ww=uO+81E)Pl)!a2TH^z3VrE(%XtAsgXzUK!V9^9^K7r^2=W;}4VQXt^s{kHfhORT$K+&cE zl1DDQ!OfAA%f`;KGmPmr`5Xago7(%@SR_Y9Kt*wF~)K6g1Ac%8q6%3)C%1um9%_~WS+GPm! zRb?vpxNMjx)LZ4CgUi8UAio+y{RKVh**QNaA5>d{0~Km2#F5~m$RW}oeP9JJ$3Rqo zZdrjT0F85{7Uk-JRe(GJQ4*gFGC#Nk6oa-3pd+wgGv{FQKofpo6Ewi;U_Ib`@Lkwo z73rxZA*mH55JhO!F<1eT4LS<>IVqYDi;I!^n%5Z3`7*Z zkn#&`4#=nAyGtR9+ceNM5pV#=5m?5tAr?T_K7fk_C>!TFAhrrh;H4JkN;+KdP7^pi zDnX8~QGlFZqhMrU2vU=R7=8zBFoU1`fvFgFp_l?_r8?-ab4as2AJoQzpKOYB3W%+O zQf_gwZkeHpp^^?40Pvv;#CQqZ#Kr0nun|zlA-e^<#SO7a z2y6gU8Hyjk`XMbhLfRp!kaUAuZ=m@N&|qKpzRZ&i7jYXOb;9?Ag9&h%l2SZXy^IZDkuekmr#Sdrb(H3 ziAA6Tb3wDXh+WmN{0x~`1Z|~&6^Eb&OQ7)r4J9otg^-L?NdHvEQHR0KD$iZk-dK^-8NxoH|o(EZ#Bs>M1AiI7P?1;~V; z0@Q{1MM^pf@c}`upizu?=vf({>yAKYyKBHDoc#kLgFM|mLf}$L;JxG^%}`A`3h`J} zDIqjK$0(rTZmJLsx(FgauQ(Rw<>hnPLfG&MY&o8Qk zjFV&N0G}v}rqmidKmh6*!5tT!oLCYIF#^({hVDFpnx2_j3|657bvv|&geD70j}WUM z3&=n<7^vx=4buibM4=e6^#q|8JkJCTxNJno!-Fs_d$gz@*~uYL{MWfH3jNH zB#RV4tCJvE6LjPSD8#H4^7B#^^3$v!$ACklRY^ftS5+6B7NOw-QVTlQK?$@80US=C z1xIkRz$3)4ctWusI)VT%NI+9)iAAa4Tc$J;brh1|Qx}QRhOwYM6rcswpzwzzY;ZRm z)nlMJ^IXux2G|hz8Oh+F!$@R$aMz^dbHTj_D*aJBsA~xB(L=omHY(aM7CL(rZ4?XX zIK%Wq3rLVF(S%@J$SFg?NF@{WFlT7V3mUbD7A6o8q{0MdhJvcDp*hq=P({#0tOG60 z^pGMK!-X(oKx2oA@MT&GpvHYsE|w8QXql-89ZAg0hx!s!k3kYL=6q0MUP@7ZChCn# zP%&u4mlZ?#;I?mWeoARhYOx+%6s`(ftAo`*D{?NFd-BUti;6N+QZ=9wprgj2^?N{0 zVhLzGR0(k-nSw@sP6}xI0@6h?uz3sxTTn+LEf2Dn6;z;thK?(XL3fVYg6@F;ANi!` z=;spT@96@bX@ngYWe01On?mQjp}G-9+bSqkSeQX&5yB8ra0w1dF3>4Rm^?@vRD?n- z#^O`ZDKl8IpP^KOAiY}qPzrk8#w$RL4@RDxG3DR z)Vvf;E>PlNV1V{}ic@pTz-4$fmx2PUo(E+$1zQDmLp>usLvFW42NPr+6pI+_c_ z^7RZ+fJ%V*pvy9gKwW(Wjbu$wKgv|WHLobMuryV{J+UbH@Y+NL=lqgHu-bssqTI~l zVo-sZS*(zeT9lepsgPcjm{*dTqN9*jlnN?76_PU&i_%j;`_B~;^C}ezQj3c7^Az%v zK(}{+Mv@?AfmV1Z@o* zxN)bTQIe4gc8^jpRF{$_*t(R|#2iqmSCWyc0FzQE&n(HvFD(Hb8d3tfB@1-6v;t&* zO=ey?*c_OWoXp%z&?UF|;B9US;9x0MD9KkSElvfMv;~zq3c2|ynQ0&zv}Qy{p`bJ= zC$l&M6qgDLDVd-V+@#WyR2_w4kVJB79!O_mUWz{G1c%g|9EIfk0#KtBlk&?_L5`{f`xev$&P;~H0yJj8u7N~)UVaHkvN!`Y z1CRuofdTKWO97jfnFnr`!~9nS3W4I1#5~X`k_w>4bY^~DF*wGN{H+Jp8sg!q5bW<3 z67Cq}s^A%{5D?@a=IP?5_7ZR!9@224B7pdUw z>F1)O;2IGSg(zk0`ZlnpMsyif@_$oUx-4mhog@V*h+Itq?K zp2461atreJ)lmQivA-KgiKm}}v%jC8E98VgQ1F9X4|Wbbru>6IVhW+bt{@#?Lls*?=UpJ`uo{`CkO^@UbjTJ``NLgbP?TB>I;;!aNCMddtvA4Ya8U+f)!8bj ztAi>yXdMqXptv9>v&5;=CBHDI90t^ZP3|sFrzh+LGA&a!w1m>*=+_owhdGZlthF1v7jYLT;Stf zK&qjxhn#W%u@+{WLP26tacV$OD)bUK$GjBq)IfM^{tYx0Xwjnh%kPy&^(1&N^BNI<)G zRSRKhH9a#ATrVkTgJM}1T!5$M<(H%?B!Y$)pkWM>gN`C7XWR4C$Ts~0aBr*f)CJ3%qhr7%qz`JEy_$T2JPW4DF!DG zu*%e8M6h9o6F3wVVEfh)@{sI+GuXhX9L?UrhAn!TshXm#sj6qMp_-xvr$Oh6Lds8Qomm39 z0vlopxUN$NwNUfY)j{dWIlnX~1=QkGfX+cHKnI1PHK#_grb1d#ey)OQAtEDzq7szg zA7^#c`lzXRsm-YCN|1f{x69PqyHKml27eZe$X;d;xE0PXrCofVsMcpt9N!k5Qf} z#TvPlo+&yC;Qo$h3do(HThS{$Q$WT-MNu*dxRQr1y$2P5ApM|{7gPkpGLo(#D39h8 zr$TcaR&&5=@<7dWNURm7=A`*S+gM-?5Ds$8f$Tz#C9pEMs}Tl(y$_azngMp4t}ZAE zfePhBP>%;RLX}yZ4=E3!kpl~2gi+9-ftUjd9%NIB6~LXtobtrVVuj+|#GIVeB88HS zL{O&#QUw$v0s>(sI6T3{Fw|P)@IrSsvN~{(ffne40vxIr=44RVKppFuqN$*(tAJb$ zq5BxrZUA9O(8JqPP(OI4_+%EBfRjcs+{|K-?;+}QD}7*N$jTslLB52@KwJY2R?vV! zrmdll0$hCxq%Vn31#0T$gJL2TmKKYnGh-2Uf;N4CLjW|K16Bw!8?+$a01=Atynt-3 ztpYey!Ocjp3A(xpsD{{ro9v(^pNGyT$2|!KQ%=El` z(2^WbRxKz>O-@Zo%}WN2p(sGc?G%jk4D=uez#!ri6bd;dh)@6@w+)IjP`wMvo8a() zh5&Nnh6Dgul?@~xKox?IKAu7N@R0qu}&>+Gz2doNi4w#2(4kXav=0La@=0JpD z=0JE5b6_bQQm`XsZm=tnn*d2AC<(5Qf+fU3LwzA5vC9d=C$vywq|HaNz1FKz#`I9F}$;DCnS#WT*xu#}b8{ z)WqTv@I)Y@-=0~lke8a80$vON4k2)(8mtMWO%3)QI9Y(@bRZ6Z*pCPv9kBOcjapcz z0IUP#BuIV$YXYk&NzMS(V4(3Huz3o$5RbtGld%n;flbp$L@w$Z=qc6dsTw(iR*nwa6ts z()M78RXPfweZim*LfJbEDL-`-KvfFp91Y|(HSszMdU|^BNOXefb> z@&Zk+!WJOH=DeUz(g2;G2eA*b{1D+*I14;R0V+yTQ@|UJsq2@p)Ibj?P zO)f}=Oij^C&dD!M)qoqO$pvRZCLH0LP+?&NicDD0fQG)nD{GKU&P-E47y+tKU?#w= zgqMYIb6H?Ye%UXy|@W~Oy$r@k{pq4tM@Pm30q6S<~ zKt(`_4x$+3C(xQ6T;50o9gq%+0Z=f&Qz~d32B^^tG6zzufmw)sBk*(jp%#K222Rf) zi$EA;W(jE04i?zZ6;P=f`AJ#e(`$7UK)E$3F*zHSz92Vfg33KTXd2Lf8v{BA8j>Xt z=^myZWTYk+c*AO52|^k?EenYN@QIZLh;`^NH^6MhwTTTW$7Jg$q!xjfcZ zzXrHPVE`T5h6XdZ&jRXkf^>t2Zou=AnW-tTgIzV!6l@e=y3ia0879z31FiXmnhRSQ z0a=~}nsHSv*3;8d0+-wwpzRCr?R)v4ySX4|H-KFUFBCz2B)FRp?#W95r$~4N!qP!W zera+BD8|5v02Tp|2!gHy0r?Iz`GcW~uF(W`D99P#K(*g}B!Tw4g633w8?uDA9oq83C=Q1f}TIiV~~h zlEk8tV$l4!hLWBVG|j-w0xdX&oNWLWfkr2&JIw_a0Oe!QR?e*a%sdUSh>k*jQWiWD zg7Xf@MsT?fRsgEH!7kO)0}V=o6A;Lw(7*uOn4Vgam;_$dnhG%$QWj#QGe}{fQ4Bu% z7FssIYg2Gp53W+7GO$u36I5WLb;DpOH@~z*19GxAD16}c4J6}d=Iep;HAn?G4B#~s zL>R7IM*-U*K1g=tXCpB zGsrK3)KJh}W{`1L=;|Ca-=lDK6*R!1VF;~IQ6y~@P`J7ZhFqY7hhX-D90R4Jt5v{g`70`HxKaP{;+d~oWA zrhKqCSODQxy;9gQjGzRf9%E!|8DnT+9%E=}2%WA*sD&1+u7=|;~|p^uscdX#zK=0 z%uIE4O~?{ntf5|^kz1S&O~8;^GqDJC6HB$8o?b0-Gf5dFo|4a%Sd{v_il`*h5Pt8ltPl2?*Gc(gPz;ZeYN}(Zc zx)z{q7NsR=x)z|7je4L{CG9J6b6|5Qwn~P22B4Ls5Cd$LREw4DL1+HKjD$E7?u_^X zOACqTmq&~*)QsyRwJpy~U}ymUQq;G=~a%&h!8(CV^cP+<>V zQ-ClGl8C`MwivW?vLrb}2Q+#Ls#i5ZBWSRLSQr2iz1YNiE9LQ7Fhs%*+Fs zQW27$otg)!3qS`QfTTgit-3m-j)V;#L)vSgK!*fy0eFLv0kn&k2yRYRgT_5U%fm`^ ziVGAn^ALNp;XTD-NRd;JSOT(02eiZm>KO1a3Fu%8NYFz&4T+%UXR%drX%grvGf)WX z8iEd;1KVC)02=HCl>xblP=7$f0X&(55QHCch7htFc3#USHVydRHiFsrWPwf zN*5%xU>T4&dMh8ABU6i$6AMzIkp=QIOfSTnO3|tss+y{Ls;a8ms=BILs`jeUskY$qqCp&zt5;Bz3R=*QCXVAEUAWacpesDl3ddZ8he3Ok^gugNHPmh3 zCWFsCF4s_p&HCFafa@&KIkljq1y4;#=72l{HVfS1u+fK`g+&uuNJ0Duy=xTQuPA^V zz5{7>K%zOX09rJXcf1AYDgorU%>yU>JlHV`;3E=1#Wd(78cZkTff5-cCxQH5s|h^= zKd%5>Fy_JT`T!NT5a)t5z=WYL2OYBqEA3KJEArDc3gCx6=qThBz&7|ox7H#{X2Rs4 z#W=E@Aw)0OjRoM`4ss(1BORd)PY%$Oh#VH+&520q9In7ISysXd9D@n~O0d0K+TN|1=z+=#(jl(V#s(cHnj$s8gkGqYu>tP3E9ov5=!wQo;9eAjZWI zS4!dZ5BSV$Sl$Eq9Xv{AtDpp$A5YB8ODzJQ1q5ojse}8%whExeBdG0|2Ts9YM;q~#>0gA#W& z7ih;zW=U!?XgUF;ovXsa3`8p^>E;?6fj4+pSeV6|nZN|iOu&M*I$WT==^#-q$o_Gk z%p}n4f-dOVw$v0Q9j;)|K8IwOa872D5~z+snt%s86yzY#ZBr08K{mob!WZU9NC4^O zCKkAXoDEW<0}gypz`#5MZ{UG-fny17d`@OkQDRZ0OJ-4OatVC5DK4)o>H6sU=$953 z=|h|h_dbe*nF&7M_+Z!way6`U0Xq@ARt9v6E>4eOm;v8=2O6Tx%u|4F?}UaAC=fIA zvY{83DnWK7fjW@zv&)n;!JA28HAiu3QHciFEjkM7AWwrw2cT&dWmp!p%Lyv3qmYsb zS{)3k6`&Km5MWyK#kpEkf-yLK$}!RWk-IILcERw$XEqLn1Q&E4YANs6_5ZdkAe^XF3L-U zp3#|^2G*?yN$;QoC4)m8Lp+^*Je?q}LJ3fak3bXGKH%wQXt?1HPq57(0&+ZrUSd)) zsGI|hA%GT#;WAt|2W&h@qaKKW>IR+63Mwrj1sf=qU>Kwbl;;U0m$cHH9FPNu3pCE^xJw1D(Joq^Fj^Tn;e-GN=Pu9}XJc%`Aq*Cur;$ zB2PrfgIxmYRS*b7oKArDJfI03;stO@2Pbe?89S)TQ<$Sbd4rrX6{a8RDJnMn$eaZZ9c4^)zYI+^OQllq|XjH^IH@+v58flFEf1sTq= z3UZu2BUqt8Qei|wSpjkfYEhGsSPVY(9kc}%97LdbaY#K>91W@nA?#SaVsP^&Co>6L zUF3n(<16=|HbKJxn$nO;6wp#8$Uq`^bWsCz(RX@k3AE#^p^3ctTt`7EJTosPzr0vU z0XD!0iT46M@Chnv%$be z>VS;|9XACQ3NFdd1$!jDLP>|qF(=0}54vNv1Z-+zPEKYX*ea(~(8w>?~+FMW8)m!IgO>i51SEWnun#Ih7y<`MRJMe@1?Jh)*zxUsjR>c0)#f zImESPU}t)QSEvRQC8p;lf|Nm4tU|RyF86?xi6BYP$%CL)whmWPeraAxF?fL)Bn72` zZUj=&MH07#q&$Dn*d>|-$mPL_X{jK$g1acl+KLm?Qb8?Jh(%y4Ag4coEph>?M-sIK zB{63VQD-F`E|0_2mVh$)Cp;tj*=mYh~Q!79!YIV4h3kp&)6G0cCfbEV} za@N+)Nd%_^&@M_PT~NVPl&4!5l(nhLrnIkQ-yv;e%> zFR>(5!7kp03v7&~g%UO+EG?9DAPNjBuqrSF9YqEOEUA)3-UAbKzXT}3v^r^SVvwySQ4TRbV5yTW)}doU z1!vFyTnTimGDs0*Sp#Tf0Mrr#tI)%2O+f5}v}d52i$D`> zxuEm{)(k#dRS(0V&_Osz%MPIhH1G#%Cxh%nle1M&@=r^HE_6YYQvlD*g4 zdT=9c6_mhX1l5S)NT^Q8Vmgqu$U1{kK`Y2pap?uS7SzVhOj7{GB($LlGXb>p3S=Ux zW9z^!N=q!wDN)h{tpNh-MArl3qpAiktpYb+6H79aVQOq`6+kWlPivt24=M%Lh&@^4 z!Wx4)nMujHC7`&61O}?J@mm58MMz>G$`G#k=TRHB@I0ERGB9JM8n;&jN}C4^k>fyMO*ov0pwh!gOG2NplTHA2mU zMu0LXR0@hx%Q92T5!3CU?OKqd?~q0)kyPRwwN=uEngu_J4<*pRwiTt8r52SCVHrdr z9=jk4@K^=DOA~yfDG}Bo6yvcHp$Ln$&^|OcS%6Df^x6#CcP`d|F?1AiK>Hc6joCos z0ICux9-tzS!D5vB0O`Dg#?GN?Y{3KQP!1#}Kx4-sDbpMb%2ou8VL$E~<|H0Ig;6WtaBzZ407j&poN@fvws0}>zP>d(? zK=~9g#1GO2PFf&g&~bD5pcw(&^ZfL5(F;Y3KkUNCG}L3^E9G+ecz8kzF61>Ah1k+kwQvt3RD7=^})*-q38Tz z&MTv?OG@F&)dK|y$ak<=v=pcSM24#4Rw8k9VjDT!6QBZPA%*=rtO9yJ_m!yI=b`_Rp7H5JEI7$Z3_d`umQh=la zuvwtHqBB$CkbP67%wG3XH z47<1qvVH}8h$$q5!N!0V4ME#VptBR8Hst1~fVS}2Dky;#m_U0CP^&;KbjZLk#3G2* zpaclQ(84Vj-}h{DwAz#;~=QVv#{fJ-vS8Ju9}fp>W!dkka( z$X`jIi@&gV3e@d|bs1rvfF(N6N?0WqB~bYQYUB}$Du^?{27)35k5gPSi$G`5gXX_s zO(B#(2F<1xW$9sY2Z8hnb}=}PA?+B5lVBkO3M@^eHIR^U2x-X}D6l}$f@i51C;}mE zW*sh&a=ilBHG<%kj$k23N=NV^(>%pGy1ELGA<&SF%wo_n2uX>>nV|Jql?s_Dsd*(J zHfTf?Vn{)OKX_?Cv4$q(M9kt0@EJv!pp!jwDixAa6~K43rszW!vOt_uoRON76H-}_ z3R<5DHV9H1g2Nl?WQYhX5}<>j*uqf|3a+g~2XO0`Wl@B!<4V32HVXUqO6JgE#_>tRaUos9a1gC;-*m zpiqV;4v;%R7^y}|E+_!i$0dn*DTzf0hhnLk!0RjX@>5bFH7sO#O$sQZYk)-I6ZVi~ z0tyb)Oz7ea$|sIKKJhMYK926-jO`y95)c{!;%X}BCM&4efI<*7qMHvo9UK%|Itn1e zAuTcVDhyPV=qM z1Y9^n#UKVH=H!5rBGe>UR|_I2m1&M#5`HJ)IKK>Gzj%VA?djV{m-KTHDLV1#rUlsxmmHybIy1^Ci7NI$bUwFG=4o;s`)0%bQ)-hqzWq6a1< z*&+I=VEu^T0-vszp98u(Jw3Gq7S14VDS(cChL#erD~v%l!dweF(Y-`NT}j1333PiWC@q07dJw>J5Sl|E zHbC#K30Bay1^Wn`SHZU!BH4s&f*zCu8`LTWM-EIBGFS!e*PuHDoO7|d!o}4o)E%!A z;Cl7IEHvA}VtDO_mJ?{ULlOd14&1Q@ogsqcbBGjZU<{%Yl!Fjr&?{3FK!= zM2OmA907t-SV1g++6@~{K~aItfe-}{YfznuE(3RLa7d7+pS!1T0Cp!s^g&Bd91e$+ zr)VyQBq&5Rr=V@CprQe-@>GizR5W0T4OAV1qa4J5It0W-aT!R!G052ibQLC2ss!JF z2OfKX1{J6q3z<>{xknR{9Ko$D(42REUa=+)Pe99ekS?UMg{1rmZM-UgJ?ad*X*VBH zpg^*y61<78qzPU=si2|(3MU_E;D8$Xo=%W<0kmNaD$+pB0Z5?2n(O3Rgs=yPwaDEL zkkvR!Q&>$#ewZaf?iGggcw9V#K(Xxacc3$yK)rH>;{4L0Wbm;t#kQa$-l1x3t7{ctBZrVW z3N+{mU8qaAzJ_W6jg5j98CB!A2IOW)Nd?*61s%&uPc4DC4z#rr;`jpa?n=xXrZKkQ z<)@^A?nf#p$$mzDXebpj>P1T11k_Z8SO8{$ z2S7nN7ZMWSb~HHWf-79GlC*U2OgN}%0_E$0C$98dE5H|AfNq@ux3wTuGuZvG0EKGc zLO3`xClz9qUPfZEGuR(s&7g*v22?p@St}RJ;}B1R0w08-yB?q>f*VOtA@ExG%wn7! z0!T|3H0A>xqJ+d6sG$KG;LcA;)hkK`Zy|l)nIo)#%H116+tOk4`L(8o5ecNvIKlpf z`n?vGld+6)5HSP-Z>)hDe-I}YYm_Axfi?maL)+Y{#R@So>Yz)galL#8Kh$(^TL#Ty#0aUqxigw8Q2W;*J_3lAq=#V@PG8SS+v4%QQm8D>1tFEH} zPQFMf9byNL!5NUjNW%swCPN3ekW7VIs-vKu4|Ol8Wgn<|K#o&T0@F|iC4yq)TmW7M z09rGHFEAj>ji8#LP9vxh>{)0^1H}rlBS}zekiwOqRX8F{K><0+v_VmZ;eU9XL9Hdp zcUTgMf&wfuk?eyS3b6~624Q_kSat%5LE{-Rt%!48F|h!&v%Ekru^CKf}kG3LrNwlGrwm3R<+iMc7@ovmPF!1^nUjlkE=f<-`6 z3ndw;#iTrSF4!xup)TRRsKj8>?aODMQ>10-cCtZ=9poAGLsP^Lb6zV=ME+mi9)H^x( zpnQU48#Gct7iz2|1Y>Ge5s1#k$&Mw|$hZ;?kB@bxZ2FX7B z-UQoCFo3~rZbB(DGq0eu1SLg6;}Vfisc0KGRqB9`$_7n6A__BDTEZ-a!3}WCgoHC+ zfG%fDOfE?+DozD$Y{6|XJguM<@6e^~N;+Ihj=|2Jo*;&=lXEbL4$aF1&60x3KHS9? z_+D=;g$<_9z~Ksa1=%SGY$0M{KT;whWF_t}%t0x-F;WEF(I`O&ttDI)R#)h(E1k z_Zd>60Byqn?TiBX1TvMMS(d5`7DeJ}YZFK$;66KKcON{3;;0I;Q!7(btP+cg5-Sna z5$>o1n_LPx+X=<5IE;t;7>|(!mEbNL;iQ$DSphzL11&M*G#WhCoLH2O;!up_Mp{Mz zr%9L6f}Bjyejrd^fj|unb_6uX;`JiI5|=oO^72xXOQ7~q;Bowciq`tV2ve%~B{i>% z0uO+kKwKLN97EtDE+sXkpeVm2KRF*ybJkWt305B9ss6x*g7%XpQdmzUgLViixs)ds zfi@p21%qz9gz@k-$-xdtPbH_QMJkZoU7SE=k&h3U##5xitOK3Ai8qdwmo064O&tpj<-MgQ6U*)Wb-b;M@)=^gzlpkdwr{Dq_N9_YP2y}c|NoqzuczZIukj8K>JhVtE^NLeJ-B6JKh{!-7WuyiJ`0%&V z0$LX{pm`%Im*4oyHIRi+PvMPa>|q8HCAP=`Z%2dO-HvP*W1PWyLP}c_qH7Mc|<>gs5j;Mru(esCla73eI7{!LCY>;S;cFpz}A7MuZWHjF45K z*@-^o3o7WqJN7}xBV^`*`eG2n(M<;*Z3FUFW=<+XEte8l9JF{3MG$nBf?Fb}@dPyu zM4>sw4WtNk=6*7AD4{6=nH-Xto9bUsl9>w~LIt@Q+VVv9se%H!n|%`tKxs|M*A3i7 z_btsS$qXpUPfjf^&M$)c1!NecQ5)`@kywOeAeu}3^36<~GE2ZG&wx$!%XiK%s05uX z3hIq0`Q^K&WR^JQrGU;`fm8yRF7nFWX!`7Kw6;@3);#9(gVfl zS|PEY8k|{$q!5=P(6JiuctlealnUDWR0dvd3XVnSi3U)=!nC333eHGO$uCb$2}>-> z1l4t5oshW@G>zy<5PIMe%v|i=3(ZSO1+@@UJi&WL(lS$vz&3;M-8O-Vo!STSgpPaVRpN>$h4f-sB0NlL+10WJkf zV#v~<#DpRb5>VIS!q6{#tZ`=j|1i53*h{s{D^N^%Bo?JW*?58|CBGyu zwFG|5Dsgt=jQmt^yC)?T+#83E;b)el=Hd^0SegbM35@Z*+;m)Px3~HKlDLI20-&{)Rd8H89^t@6~!yYUGQIVck3RZ)sI!7D} zN}&tkr-|xtfhN$whkt??2B4Y}!~nYl%m61DXio*K*su!hJ8f-4@LZd-wzg3sR0Mpw zCdiNAvom$Tp3~Nb7z#c)5@IZb2{stQ1RV?t(FQRbA__Jh%!F6~VH$uE4DM_Uimv>k z+{C=(RM2r3c*6ls-5PV9@m*VowG}sx=cwL1(Cz^pF(n)@DBDi!WrFl$fF&k)s3TPq(DVY-(xF)ep2--FW+ii?w31%A$oW$V) zL~NS^)O0H@NK6LJPr$7(W>oLRIWzqkZk&q3Ew zpqPr`a8yIl!WfirP}Wm`OAk^O-@zS&xzro8XoUwNA!p$BJ9Jy@pzu1>Ep+oasB))) z$MFR=$Vz(oFTWtQsH74+j{-gh7P-{GHX9CGS&q~F(DX0j4o5KkFhR8j&u2_NnpjDi&I8e~@}A@$)=T3Db)7LL`4 zptOtcYn@5zuEO6%+%reH`Tmjh+x;!GE;6g0Ig8eiP6^slFO|=z3SJCJwfDVMoOi3+DO;J$y&r??b z?au|BUyxstky@mX4BF19$E5^mgTk^3HUrH`G7#^eAmr8yP;&!skU$j>okS~XUmzA& z7SnWK1ajUl-cW%gO!SSciQp+JrKDo;jBG|?W;SSkP$@GXRJJJPWagEE2j+4UlkddZ{B!bo_5t{UZdl+ZoRFasP1DaO``37F> zVH8Y+^C{$5Dk{$uK@6qQOcBIz&~O8BX@=Om1TmKuGer=SX)#j-T1-yHHbK~0LyfY; zoYGWHE-gWVqSh8=xkV+A(?65li?uy z?jgsVC_s;%)5`;mF6U%crNR`0&*gwB%FE0FpNEo~mx8C!0NH^IvKtiju*w~G(kGmS zN})@Z@Mj^^mI$$l3A_Y?c8%21(o9gH2lqXq353zwK{VFTV;wZ&n4^#nx^fA$?+bDW z1vm+S4lFh{gPacmQ2-vjQ~(``0IEwfZ4Gsd%|Pd6L(E79-JJy1X;oaBq>-tU2|nUP z19V(Za)wn}W?qVhQnaI!vx}=+Vp4KSYMOzek+F%XnYo3fZY=0tWzexI=r)0D16{&{ zFxXPZ5`15ylCF}bf&w_Dp=(0bU}&OaXoAp!%@#BbmO6$=b`aKJWTIn4gbkpO$j^p^ zDE#R9Owgg=nb1=eLqk1Xl)(7_nk^vSE-fekXGTz9;H(VL@5UmoU__*hYA)nvyki*X zB1a|1un2I=(bLfwOoNy1aw!D|yTZ7j=A=?^aIhYIWl2V`LU5!fORmvK@+3 zef`6nTtS0~0saBbej#9%vyX464~XRx1r>7g1#f+J^7RBe*UiujJhSQO3f>9r=IaPj z?dIzUo<<9D3GxHoOo+$nWuT@RikmSr9k{H3?EC^VL0T}EfP+L)#+-1*D^bht;RPK^ zAVIqVNR4UOo)GX-X^`g;*P7w)7UY1=txHTvDFU5k09`%>FL`h_Iq($apq(c9MW8Ng zI%JzKa(czqb%Qly@R$jzw^NIXQd2-R1ZZnoW=U!?=pH>nW@8UNM7TjtGJ%|w0y(4v z&&+3Dab|j6Y6`*03ScX4Am)F8KPsWtqj9NwC!x6ximG znUazU3MnPeypmMtkcd)1Q9dX+q^96q$pBs^np#X{r{ME-VsUY5Zc9?$pGb%*0|1CA2|y1&Dw%>>wIV@aZ|o zYEh4KfvQKomsSZ=4}5eLs@xY;%%rDcQ3;>chN|=jAAnYjsuJTg2?ZsvkZVOrD&*!! zRP`9EJr$JTQ}w7SLB&>LUJ7D^oPrWa2zk2%itj+{4B@pMXmK}40PYM_vkQta2Y8E}*6LkRf3d8$dgJ%fQQ zi&GU6P~ta12Pyy!<^;(74iH7qNJ>xu-FBRsn4+hy13%#y)~3%)gLEK}ui6H;(YV0& zK#hQ?MpgqkdJC)uHc|~$1mkK#On^EJDhcI*bwff0?%^;{w?;z=y8lT*3AE52wv`FV z=a93RkfrhZ59~rrJ)kxl0sE2nK0)k9Z?P$W+Eq$;tw-0Q0oDTcK4x5?D+XPd30_AG zZekMaT+l8oJnl6j!@)+7^T4U>V(>mJ&;TJkX`^f;0Byi1F2V0w6b%~S(MDWOMNvvp z$b#Jo+Xn&7glMKgW$`8j6fGLCjTJbO0*YdiT#LHZ3X%}=z=tP*mt!b^s&<4sm9P{E zNUAkJs&P0GNf~Jl%qQ%`v^1o6C+xpBe8pt=LIC?p(G;65CDix{jrhjkYfl)&;x=^jfY zqASMk6oe{Rj|ilWKmzm2$Ky0mTEH7f2rEHKLDpf05JC+J&O+?Bg~VNQP7&cO2vdaJ z9T3^HG-7I1Y@2Jr4gobHk=m7zE3xqg5mp`8U5KePj~K@y_I!e!m;jy5Lk%x1sR~&g zb_c;#kPvgAmDGmtsvWVQTLHY-+7OY@vA76RHFkF)t0U2ApzWxTU_v$oE1Fe-;P=FSr$D1i2vF z7T^sz{5r8a8Cg$mVrCwaRUi!nY5>Tl8uZE%_40X8dk|jAVad3dszDZG<_WrdVW!*E$E6(M-Tgup z+?|~jG}4ok_0zSr;qtkedFkaK-M*Q5?%`O}fCk%1_bg<*Mo9-cX$kSMt%4FE59|54 zh7j;?ZgC!H>9>M{f|74$a#4P9ep-nFSf566aWcYV`FZ(}MJP&s`DLj&ISRh{dHD(& zxyi+vN+9>t>TrRU?!ydIPzuN|0NEUB#dgPmrUKLmJH*@aQAt96z+=0xn$?eDjmQ>z)-r z<4q;03NZD03gP)h*~JRwnI#ztQK?1wPDP1%sn7*EAbauH2^tH-j6~2?mze$yN=-~r z07-#-m8cLI9sSk(vYAB8cW?7s!EL3MJs% zQ1c+?D}r`abJe0`O|&p9OD#&uF9vOXKr_!BveYkCA+@5UC=n8N3MHu(B?^!MEL@JX zRe&t&0}VKpVKEmxybX~AC2P>p;Gjg1TTp__L{KWQRZs#ik0GwKfV&+OTg8xNR0=7X zX=$lNsd*&|pdrgd&@JT(kQFjYpu^%ps|Qj*r(S^a0CaSn&Z~ zwgZ*&FG$UE3U<*^@JlTL)A>aTZbhl7PQflP<0g#w1iGjio&zC< zK;59A0n3cK5OGkR)Fj{x=sFWjyI|Q8sz4zVHD_j~fiG78EnI-#bq+6m6soykxfPtN zia}@pV98Qx`9*1&B?`g89tz2+MJ1VOpaXa_^YavvGg6bYi}gTm2hGESruSecbR&&d zA$0~*K*#NYXXGFgoRB^p7yLjI*lBl2xLw{nwXvn?j|X5rDf)TFOpDD0xc4;3eHF@N=*T6d{(IC zf-F%2O`>L2fKKN~Qi2M>ComM0^osK_4Jk=1$xJ52kVIrNAPK`NC9$YHGY`~O0J{v5 zXCS+<;ci2ANMb<&VN6|DVE0Rk<=XYUR2Z_~U(7o-jglXlhjKXzOhNl7N zeAq62kP3)iJSifzA{9$qLbQSB#=r>+5wt{kB_$_Ei7OL)%mZ@D!Jcj~?82Qhu%!+( zLx~6>>Uak1N1P=CC(a)GYifh;yF04+jU=M54!IoRiiw+7<6%bX-S%e28dCvp>AVek(&csXlbitsAr(0keZjA zpOTrEZmSd;;-+h%WN)VqUzeGfUYwEy>eDH473dX|7H4Rv+c@W^r21r*q{f3A=AhlU z3Vw;XskTb0#Yze;i6x1kdu<@R;P4Qia9bt6d?h<|9fk7jV(_)`Itri#$|*^3TZ$BH z72x5f2i{o?3N45SKrABe7lavBP?QC_;|?v33W~Bo?GtcBfZCa$<(;4l40RNgU@Da~ zxsX(W?yyczEkV9&4G|xDphN=NN3Ej()dE?HSWuJ&_8{mQcaUe1caA`p|Efed8sxYCs(ZX77FK^Eo0V=yPN1k@uc4k$`326X|Du87Pl%?*G_E7&R+AYWvU zNI|KI$r+HvE#M@CSVIac6BKO0H&es7pjD|bHv9@mm;lTf(J+BnP@^}G3(YAgTx|tI zE|_vG`fU}!YhgjnJP-!m>WJ(wJ6i=Kh#i<2T`w1OIA=jlDsnKuO@rrA@J*Axi6upu z6`%kHrPmyV;?$hf!&< z!#(Bc&? zj#(zdv_hgDxjrk7D=Oh?Ao&=YE)n;XfC_O0 z57h92>wq%p#k1v?w+7D(cT>HwWjg>V6MRUpI!NXjhFF9MC^WT#ew?x|L?0$(i) z)&;Q|>~gSWP?tfpfhEB)3OZ*NbWblhFu}r_Tu?*7l%AdfSi4?weqM5731necv5tZg zsLc;rhXFe95Udj12uT4s18fXvCV&f~70sI<)zGVZ!Q-DAN}+k#dHLme3LtNQ91n^Q zaK!3?Y{*Lin+3|ruy6%SYQk3OL!uq52p-??gp!w94vAGAh0;7o=PxxylM8GDQVxcP z0ayZ-pb9{VtSAo@Qqa4iGj$X|DnKsGL{Bnc30tr{+72D4Imj(hh*K176*4o^G!QP+ zQOE=DIY7<0N-zh3!VcsbkX2Tx6$Lr@DXAK;xBw;UcpXq%5M10q-3QhJzT6eW0yTp` zCr2WCJsu>X0U}_s;077gg`nF_L0t=Yh@uuv(GX?OY8|>U0_+D!a@7MhMiNUjlsuuk zDAGVn9YI%yr)5?ssH+yMDZq1=yh= zNsueka#HlbOI=foA$bJkrD#LYjonC6(NNJ?Pzr{!!D$C%IZA?s#u_9TV5sdyrh()@0Sc;7(lXPba-bFoR34-e z!R3Nls+XDrK8hZc2dZl^M8UoR=>qwLO9_6ak5WNKeqJT$)_BmyASI|_kY*;bNC;>L zO$kID7gB=*+UX0vKu-gtMMnW@6WrNw2ZEwJ z3zBv))IdT2+FXP91TyRk+6+`slnQRZI_H9Km{kHDYlAF+!_`o~g8J)_OTa;Qr)TDY zW}-m7Xg#Fv9>`muwhts7AR1`UJ))p1;`Lyn`9+ZVm*! zAhWq3qugL7coGlP5!Chd1#P!0&jc0I)e1@wK8OwgjR5OSzNG2 zBa$>4Av7A3q|q3xu~vr*)GY?D83)BN%wztb(@p(BeRd^Xf6yL3$SMt61tnd7(A`0x z15Usp;1AlM8=P4M8r4?P^#@f=!JyUA2+3L<@Q^;(bclgz0iK`@v$Z;07<)-TsvN^2 zz`Ao2%RpyqfHn)m`5>nR2fIS}#o$E|pk2doK2#nQ6iT|e#l@*$=YjSS!<0aTqCq=c zKzoWo?G1=Z5D$D<2g*)jP?&Oow*^BC$jvWH0?qG2HV8w+3i1n*L3bH(1%USoLj{v_ za!WziDuA~QL&S2bAnKfaJs|=~xuCN`KpTnSe2@=7JBlF!X@+K?6k)o8MJkWmrS{*Llm2@E@pcD&UW1>{6!-d$}3(=EWmReL1A65Sb3(AoR+SmkegUg z0J^*Z)+Ge@wn42TP`Q(yT7sjrLlTA^BT)>l(2*p-T_PnVSdmuE<(~&`t$~-T>867# zZzSb9T%i3tpaKZ&18nl8IXPexKnWg;d@ZQF*5N8e+LQ|lFOa<;9ceHLUC?~Kk`5Ov zwA1qPby4L&H>IGj@k22RB&iFYYt;ppV&FZq$mU^D05TNX9ZLj-a5Y!764DLKN{~cf zp^%>hYDs}EX$B=S6gAq~*i?bf?gmwbpg2oL6!-8Iz@VfRtpqx88)8RsNs4VU_=N3f zCC~xfNTQ%6#9SazOQ=m?wcxY2qrt~>BdG-~qUDMPEibk}Rt-D78!TgKfMgD6%`d13 zgUF!if!YCCR*SF&!Ut^xM{1FRJPNAn!F~lLWLWZr)RggHanPUycpMu`eGgFw(*+MR z-0cIfi7@qGj)E<8EGsKNGfx9zAjD!&0}<4`fHW=AQ%fK%BIrsMux7BEHQ`k=WFP=+ z5Lgh6sv!Xp;>xpan|4wcv?^cpU}E9VJM^PM}6KL=a{I zxU&v%7utxMx^B2SsNoG!59xbA2a8f5H|C%j3>78H0BI)X zX6A*Z7J;rl1h=7~jcceTXbJ&GHmDQ5qnLzyw)(rMGC;=r`RDcCQ{=u?#2~>tq;*-f+hp1tsyd1XfmKv{UI{uXfmL4_`xzpiKsG0pvC}1 z2DvOu*474J1OP4z!G1$2^S~lHkkS==IRKh0)W_h{{!#o46M^eBMD{mC7UFXV8`kPkj0pVjxqfmh9eQV4QF z1PLmEs#nBG4&Wq=)DzQ9L&(`e7Z5Aya6wLwfFw;M1sSkICBTbr^Ad9~RDjm{K@XdN zpEZG@2%5%Ip-nxE<0in4#O?%LsA65HQ@|%Rz?yF0E-R@1gXULAfdMICkSih*YCxz- z_;kQZ2jXi%sG+3RbMV>^Qj|i=GjPcS_9;=FbkN;|#Tt@IkS1e*gPA(NJ01RC(m1VsyIY6fxB zoDQO&0q#=3%t!-uHet0M@}eG43j`_-pSy%ug4X;1b<*?FLAK>4mT0K!LS3q$TCA?4 zP@q>>nqQKdqLG%T3GpyIu|R?sYotJp03F{3@`;WDh@}Hs)NS_mQFkebOsO6~v zHAD}xngcY6671m^!c^NC?1#%T@uV1!M=hx1p_Nu$Kl+cp-%u#9^6v3eZXb z%!d@Qpv4*Bbq%B@OGslKY!awt;EjI^5 zB|XqMIS3;er^yACS5Q(>@elHJ_w+-yD=!6mfup1wo};4+)~W-URspj?-8}e&3T7rk zuiPMQkDSb;#G-VNf51r`l5)ulJXn_sc^N*~>qtxS!81fqw<+l+K^NVFrfPBOhAnR= zLo3SlY|zL_Eh#O^0}Uz{CFZ7r*0?EwBNaCArKFounU|QGnG9a?4N7z%$01}Pp$6XG z49?0Zp_-Ed9l3*qC)A&ybUj#yuAbGIY^uU*tL#`x;L>(jyVWldlX$J}$-TcxLUC1^7TP1Mq2QmVwSSv~e zm*Ak>p{@j)cgrbGg(Ws{Wd%y31$y8r2%km8`JlypkTD5(%LUCYkP1*s24V%o!Q^Eo zh+cHhg3k9!%*lxd*`ovYEZimP4&XvSA1sQ)S$Qe2gj@tlygFQvJpk|Q8F4_e)U#`W-ZlAyT=-s%J=JW$Ah=Sd;g;8w!cZ)ZZ+V&a(8 zgS!XW1>hV3PWPa+4T)3A(}RLW5vXMF(Jw77($C3EGKL=8g%-K+>BRwbnwbf7f*+b% zpy>`g$AO+cbba9S%Ake|XsC$`a~RntGpQ)C2su`e0~{QKpcDmfB7)l1h^7K`%ovnj zGV`+GjVo|D04ikRO+RSs3KC_|YzQ_SRPOkIGZ{SVA=>-UJOfs!7nGU{p*IwO+v3;^RM*8Bq7bj9Cntlv3TpgAV-Ryy z0A_Y2Fq8o{5lh~OXBSYZgPKwxH5X{`a zBON57V50MQ6JbR-^fHwMUrYDkmW2_m5cTTugPqadq;7aJh&poC&xHoS)c>J;c@ z7K3LLK`lrPkYZ@i;RqwRS|uEV2w*eQQ%hj+2{9A0XcDw67Sw0WEQa*_Kx^F~@{nYO z)lRURAWd6Hprq!dfLCxLSM;Fut0hHw@JvupQj`a34#5&hX_7{!!J_-_5u!Sed(j=%l104lj z3$%cOL>(-y5a|wN20WNB?E`m4$g&Tt6Q6ykoio(5gyBA;%d0x~X|7aCM+^1=b1y@45h2 zU!XD`wB8SP_bRCEfV?OU(sqrG=2B3|OiL|P&`{9;WsU*`6%7~HFmKP0fBAD>k}bI4RIw`RDfC{>EQJZiJ*qBQeu&k4p$Lq4WkmMi%^sbntH^T1qT@i%ETb2 zLl$a+M~0Z%5PAjfmgrV1sca_`ax?}l{7(}Kr~ap zsQ{`6cGn4LJ2RGb(~yi?Rt&k)3e`;O)qtPyf~gobV66bE3_wf9AcJ`MpxO=`+WF;RQRpsjP}JlWC+n6O znt;}HfOnY1gF5!bAZLOi6LiorY*Rm|C5+TH1aTo}hJlWf&{0rAY;M<4Q1VU81|M<_ zQstMQmkOdoOQ46v!_<0~q~=17S%Fv$+iC`KZnc7vGe{HoT6+cXwe|{1Zpz@rbr98{ zNQOI)hA=G@|BB?Sf08848NryxFo92Er;1uf3YgKrT;uKK`xyrJ=@2Z|&EQ}`n9 z;QXA@lFa-(@FrML0s-yNg>5}XT9gG^G|h!LID(kT*c7vM=ZWjG6XH^gHA4j_J~50fRZ6-`X7?^;fHU461$0>Dfn)W z{G4K#DUf_;2)TA1a;QQGD0{%Z}q2iz{4G{p#A-GU^(8hkS42S_01D$SA0JB-I4D3Fr6qsWG z)&O-GMBaca7rsRS6zsKJxuA_1u)~U6Q8ry5l)|?CW26CSAzIA^u~4rfH^(J4IVTaa zGg^l$7p4=$OwY+rO3Wz+F*8e2a}j6gyBFn`7Qlp|9H@byfG7qnE(2BNu!a-zxeefS zT%ZR(w*j;1$z0VC7|u~&=e2b{flHgG}0jH8NU4*s$QWYHz%*y z7JTH7mA-y)az<)yVzFK>?1VbKI85gW+WCvN@Z|K3k$zus1o#k@*o_3o z9a5CR4Xsu!t_AOv%+dp0E(U3fA-NaXcu>4Tj0g1_VFzo}fu^{#^gvk%+y(&+;leU1 zJWP@82N?smUllag06R_tVml}gA|+gq3(zt#zC@gy1G@DgGba^G3dCX*#6R%72XZHx zWuTM<*&BqB5V4qto+xn`h~`JQqw)DH5q{d6C!{rxG=7Z5vv4!fyyy%IAUqy}>qYkt zie``}kdg{M_d(nM&Ghh8;fTjauoME?%&ViIuBw4gF_t_C3!F;Ojwz5!(UKInl)x9< z(8Ad{u^#W4F#_R&udaf& z0U*r@$e9ZTpjtjHGrc&q1bjsh>@p(IHh1VbHVUAV>!1@AsYM!a7N}4FZFvSAFzKA1 zSCW{Sms+F)IXp8sIlmwkJ{p>wlUQ7=13Kjt-0*;#pqH0g?hKM6%~0r=0@zf@hz7W) z4eD>}C>TLXq}K_b5S(76PD7EK~@VSU|(_$a$(LKOZT{!9y415^y%u zgS48!R>1=rB#S4lLINB-HJq263K|9kMFex4ar5&oCY=m+(>{K ztN@7-urs5<$pL(V6?_vgTpd^!Bzr35;)BX z5T8I?1NKLL3HVB@#JuEG@Gv68T8IKjK7_I{-3E~WTM2g}L=2)4s{-Af9GEJIPvAUTh^3$+7F=GyN@{R%25}ce2e<}+6y6}aQ5_C54&rK% zt>9z>8Wt#mmzH2HMX7m^QWIX36MU^XkH4F0oEDH@{)IdRK z1}9dKM?iy@pb18}v5?9DJsgm9!ovWh736)8$Kr9h9BwWmVW4=qC^bE^xFoem1AI^m ztgZz`7pN!(o%{lJDk#1{Nd+tc&Iiz(n44K#oSByn&M`{xN> z=Ln#>Kq3l|MQ~sy_)q~*fR=)Gw!)eQU?(A3BVaB#n}AvHjmc|Q22pzE}CaC z6@a{e;$m>d*T^r*OwY_q%+XN*^EJ7U+cp@InQ01W7QvK2&woPJtpo{9aNxs~=_nwJ zLahWj4TMphmy+U`13sx95-Om^JUmVyDHrTXY#|6zi#tCn`Qc13HTgnkd07DR3TvMIY8|3)wjVE2a~%`T>+xA#n^|BmuTr4_Z9HOGy|%zeu4P zymkylSv1%lq@0FhuOTGpV1~nk55HNED?-6Z926@cj2ScF><%#=5^7)?4+#DSowLD3(b4a!1g@ZuG0NOU$**eaC4 z!vZB7;CGoq$|q3yqN4!4LRCire13dt5hz1}?JJH3DTvjB>jE8UR?P)freF)xpu+_< zLctbh7#Db(JYvoiR71m!fn1~tG8-DbNNz|>N%2U`OUVIs&mox+$^~Z*La93*NATgc zo;a&P2@0I@vF2iEI6>nB?oCi=fx{6w03n$JT^N!%K(SiQm6!t3h!hT7kZ@421>clX zmI@6J+@TI~E!Jqy%+D)^77kqCv;{j>0hV-NjT~^P0UB-rHMWa2suh&-!3XK*r=@{9 zi{M6vQY|$0pmi5m5OHQ6*nDV71!lubDNtm@!$Jwu zlDoi>3=1Pr$by3bR4T(SlYp26HZ(I0>I_)Oq^5woj^HE@HVP>j=|S@#q>hF;09MR{ z+pGvE~YKiN1&Vz?v}=bD^`e3t^z&Kj(Y#RWblEdItof98L1Er3aZ5l8HvRTNvWxM z3J8CIgQGk%Cr2SE6`B=N^kP8AeW(_LDF+a(1dC3PtuQvi5SUMO6d=w3CmKv2+uAB9 zffF<|Ou=joC73y&>pQ?g#n4j@K$pcJ4G4ge1tgdt2^2a80pTE}R#1lmB8VKcsYRf0 zg|xh2TM(5>^T11RO7cOsYJoy1GY>?96RnN{;`nCplm{feKtcfIIj}0!R1a>7fevO* z%1H&~bkIqu5N%+^pz~676qGa{EY)HKa7YbE;AbQj zyTC4DPXX7bpbn7+*1`f(NPvrU_@S&2zku|>2jAf#55AWMPhkP8tiZ7dNej^82;Q)P z<&)GDsA5p@2UVm8u^XJ=VJ!%#7}QeGx-w)9)GC}TU~N4-g%c!cfqLVF3oF%PNV^l1 zB&bzhf$fJk#V9GQ6si?+i_>lKm01dqRvIYRz%V2};c1}=bg5BsDl}q1kpfO)U{@li zD{yQ=7nZ{JYlBtkC_v4CFLuS2c0u(HSh*E+XtG#CNej0872+nu5FDbcNlPtB&Ok3~ zs=0C#OOi7nl@oLjTO+wRwM0iDDJMTU8&oKP?ks|(1|)-vQ%hi_Bv%n+#~u9aQi!3& z8c^kW@h};vu?PV@@PW6GRy}C3A83swOtBu!7_d_idUO;p%tbK<+(xK{IXO46I2+Vg z0j;ivoy-bv*?|T>;xR^NpvHp-KtMyfpzBw_ZqWc4298TmFAoxt&?wUYi9q{(poPGo zgR>!#0gfy1z?6;xVrCXv&4CLLaG``*bfXsk&@>7Xgd{~QZ4-!#z-GddAtW46?XLqRY=~1uMyAB273_{(xBFY20UA6fJS_hON$UW1AMzSbeKCo z8{%fza0+NN4%F;Z0=ogxqEN5}%`@ud7N>(_7u=vgsb~=i9a3hZr*w#DF~sR`$AKz* zaByKpfsO)b?IgI(18RCe>U&tf9UM&1R1c0{u&W^Pot&7LpO={o8gzlIh=$J3f<`-v z!1iD?;^9#V9x{QQZw)sHUROY+qY*|yM%LcA1q8!oDNRCO0WQc1~5V+tQ`vV zC?pj@f&!FDl^`uG(Bc`Nj!-b$^h~1UYP=u^F%7mUG4h~c3m@kA4s%F%|ckO{2 z#t=V&T5aHvhX){{HU&311c07o8n<)A_c-uJ|%L_rx8%iyLhbXHdnTvru? zT?49IpjC8g5qO>lJX{6pL4d{zQj5TcGb7Cc6+jIES3Kw@BNje@jnXT~DJ@FO0gcXq z$3h@=0zwnC4ue)R$WDY7M^FVwLFAlYng`jGQjiJh;X~a6^AyNKphYM+wCX5e4<{W3 zUC_1`Xp;w05F-sRK&Hc?cIYT%LW*cad5)Ml#qb@DDOBjSm(c43K*hL*68P|LB^`xo zE^wy{l+|JF9dIE5Z&W}lM$lTI{N&W);`}0TDnV|`f%^E!^%JCovQ?;tws9eG0S+yU zwgp%%ID)|}(B>Y{vOTC(utos5G=wRD1PC<2g9Sm!ONR?|c4cA_$_^u_R}ys;lF&Vn z2usoVMG8qsY~@7I>G;Y?ka`$uOIjkRcn9S(urCyBL1*_XCn`V=BLUTi1x091PK?fs z)lo=_&WuGJ7eW{XG7f4!Xayz66X0upAXmwu#8y&%K4>94x$y-l2cSo(g4R8AMJs{b z0X_*1%mN*4mY0vp2aVf+G80@uejZo>$h9E#AR0vgbWs#Y14sh2C=FKQf+Go1(?Tmu zNa%vaK_kDALtY^rKG=XFcnJek1=4^&xTyne{3)eY6hLmNf(DSaLVA7)WQqf#6O#0x zp$rxR2QwCjK}~?R0Ep3nazzeQ1EhffNWS z8kD)gv$N1eLZCCL^Rea+XaR;Tt3z!DnFE@$OH2Z9RDk#Dz}qw+MHp#C2r=f8kfEW* zX&?oV4p&K0VqS4hBKYn^SZxO@QQ%&VM=uzlGVp6NP_u7-F{B#?I?f8#pM%5))CtwF z-Warp47#`rOO6H&eS)(yC}cr{DUduvMqv!K7i7+83>D>pRuF+Zr=Spqce%-mB4~vO zjwUi922$#RS`bK;C3qYJR6d~hIk1;&uw;u=u$4d)7Bt@Bg&M626wpbM>7etY$!jYR z2nCcv6nhvzlwb*g%rphiN&I?gIr;fTkP=oC6tv(Vfz&YAO@w+0*~olI#RM}2d%Z!8 zAclkoEM67B>B>8`5_BXWXz~Sby9-)0fzEemQU)57KyR)< zjgHQa)ln#eOWBt>VF(uyRv=0j=6LedJ<_2`uv*bgWZ=FkWLdj`seL=%RW z%D{V;G@}h6V>LKzfR9(A<`kdI;u6qi0a(=w=}&-`^W@~0yOb8>WF{w;fR=GV0~9=N zgAfOgM8KQ}8li*^dw_c{u$cs?JZypse1Uy_8mf(;$u0D=>fri725WLbx!|>HphhuB z6r2Fiatt^jg8YTiGy!K1^yUdtT>|R0pbQLVrYR_cH7VFB7(gNr6jPu+D(Ex?h*U~G z%xU0rDDsO^aa#`V!@y$@QY=6t4C+0wk3h|7&=jLyW^rn6K}jX76h+Pe$m&5E7s|&d z%dnJJiJ(g>@%9QKE(19Ov;-0CPe^wI9I?Jtk7sYh2OCZrpwozDO5iMPU3q#yV10`KTT!dMWW28KM z?j;nqSe%TlsL#nPF2OA6K_1aTEQf)+1Fi6fC?lccfvfD#FH#uLkp$Y;0g&WU1isT2 zx&#fhh6>G11ki==F6jBYE(}%W_adf|OGb=!+o-0&yf%;7p^}n!`I*_)c61cVjdj!&%RM%Gr z`v`jNBP0hSoljMo1SvSdH$xhMo3cu()zH!lY!0a92Aa4_Pc6aH69<>Y&@cci2MLhW zl7VVQ?ndJpXMnWIQ8F5=s)ofOH14qtD?kzt&b|@Ua7YMYZ^J{BAUENmF%Iw|G zaKPRNF(^twP#1&D(Mw59OD!r&O#$Dvg0vY18i?R}fL2ioO}3zMBXIdh+K3L8d;re0 z(DaKv79mzbViBqu()EI0AdP&n2qe{{78OC3Ai`!?LF3>!W-Y;E)u1_Ia8m@?4In{? zRY)ZV%@Ym8kJJfa;)dO!iHjnifySmlWeKP(fMs$8$czN^_D;y9)8NpCtY83#cV?Ob zWL*(h45dW|TD74J76!Fq4Z%evm{-1}p+L7Agd~a0cuuh)X~V zFcMQzz!OeL(<$&ms2Z#d+ZHou5P^~rWMT-a2W&XpziE)|YG6|a$fJ<-k62oO;&JG9 zIt*XvC}8(Fw3!N9aRAO6kR=4r&BdU60mASQ$V)AU9QmY?s0rFRkqkO8NDnmqn+PtK z5L1eIspX&x0`v1U@(W5pY9TzZTCfPnTw`7{O<(8)-sAlOq-HdF>Qftp$YsyHFb zl~XH9pfXU0f%QPyP#H)v0ZTxbP;t-*DMSD)hu~s_AJP#8P@|FS2Jjg)(2%I+$_1T* z13gIxbO1|PW^rj^PB8ccG3btHkZ?gxVhQM$Qt(kOpwq5E=aYar5GO$k7O+SWs8USL zOHKvzK<)%HATy=K=o%#b_?VfG8ET? zP9`YOE7yRed8DAh;!}LV1hp}wvLF={AkdSZKuo9%Xfhj#>mKUq0^));RD-0zi4-J| zomvUHw;RNSteu4)3k5yr2{VMSI29IH1*OFq8tOJ!JgyGP$)MFt80U>(F%BM(#V{y+z;J;cHdVziWk^{LrK~{CY~VBcL3@MK()Gap06Q3|kOIvC6y>I-WG0rR z!jEJDWg!$JL75YjdqC6wNSgCYOA1O$P_z+m9>|i?;#B`MUpHqEJ2@kwO#NzDUY#0NkB3lu!KvJfa0!?xbTPkTWLWN_{+*H8x~EL$bjVkJ;# zG%+_76e(CUHA+B$?u<+aDMiZ8NbvwFz(IW=nDao+0jEu9?WLDl3_COtECp`wfl?vp zq;9P76Xxs((g8ZI4io{1bNoRyOqg>3^m=&Lvedj1kRnjXg*iKy7MJ9Mmpi2(1S5QX zToOwXp+`&R7a?TAQN;n7F3W>a4Jql6>zI?&M!b1 z;aE@*mRgjVmWgalKz?xvXq{LJl5-#(Bv_;>z;ZJ6h78j@HCgoHHp|k{`^(3+&hKmvAqg65pU7&IWhi?$N;q@7`XhPMF zmNmgm6o`G0nyUn~EhiUz7Fls{ei7&fFt8WlSry#+fj9(o#GzhEQ6i{8kg8Eq1nMp3 z=Oq;-<|SvO=9PdKr$dc`wE2Te5{pVe=XFBWRAhqMhbT&1^HM+xa#9mPSCAv5K!=g( zD1axYK$#orH@IW4c?;S3&>|5U51{1-kW2vb0?7NI+obZq-2{z9og~;I&dlPHe9&P| z8i~-8CZH6kmj_-R0|~Q4kWHY{3bbesyypm7JtiSZBtaxV!?1d=Yfr#MJ+yn1genSk zFsS4Ky8_(BL$wHMidA}XX_AJLs-CKzsy=AvB509sUJ7Xb9?eXsO^H}-LfC@pY67-E z-HC9SjRIUh$jy*!gp@JCDGn()XyoOmfQxo$a}FYfwGPNnNd*TYC;=dq?$`}?%}YTT zonHVtMiOE)DD7fPE}*o8&;e;%g2Z4>fn6B^I<*K^ErCbE;H!7Qx?zEl!41kXtaW=u+)m=oYIuk6ws+>;9?iFU=Cy_OqT)y_d$FQH5ycQX6B?q zs%%g`gs{Lt2QG|Zc@H8EaVDt329N(IHXnv6bXfg-MN-NN!@?s551v>>} z$oMkUv+6dWNJjA$ERBK+Az0dkHE5t^9z;DT-DrZ_JIa`D02fE#LsxV1Ku5r(Wu`;D z4-Oj8ZdOPa384lhkU+73R4_wq#L{v`N|(@{F(_3)7cqfKW6;hM@Ks>d7_}r+14I=( zKp=~u!R>BPI?2hw==eePp;wMb8qtz;m@}vu0zIoKIW1jBK~GN)>_bS=0FqUQr4~@f z%dIpIba-8EG>8M<3Ji)3rC4ZP07{-PCGewt!D_+lM?spv(%?t{@jzuoVs0u(5EN@r z`$4V*_35#+jv<{091T|FV1YRWoZnE)1;-xzL_?%_a!E}pO;3lk!jK~ioPA)01x92c z>jL>flM5bW$Wq|ci__{bXL!4kc$<+lK`aJkPf(O;C?QEHfkOb2xRE>?=8V?vB*|XX zCM(2tNYp^?fCQcPsuvvM7~<*dSjoAr;+@b<1;6ObD#MU)fZ^gd= zG?ohUy#+<7&}rKIBE;#n zU~6F4{s!PylAD>AnVXmslnOpT8swIw#Ny24pwhgO%-mGxjMU_8uu(~+X=$L|b8=}> zW=SPj9HbaBw-k_{54vp>Bmln;9()W&W?ni-un1xr^o}i%NGf={Jg9&G1%VIfAZt($ z45}U?>zbDVI>{}?Gq1R$s1)47h4$Xla`F>PGV{^{@-y>FeDhOMb3kT7Z2=7@c;=<$ zgM>Q56lgL5 zxeY9upPN|%z3JR1GYQN|Zbo7;(f|qgwgs?kS~}_?cO{I06to*d(VdFra!^p&hgzP2T!&PMffT^14pfDZ z@)1cXD4JwB7 zVQniQ1r?zx5viI0-9-c~_FPg6Qu9($^OB)?DKkAUzbF-Q7&BA}s2o6KL5Nd8#eIGr zIFlo!(9MF3m4ZqGkP#qDJ@b-_QbE@PC4$RFgjT3kXc-$a<%g&~U`C~<=A{-TmZZW4 z#6ZTw#K7$!q_PYohg5cgt{j6IQ(T$^xxpKxCI>XS?U<5Mlv-TuSe{q}o|%Exh^dGn zACOW=Md0l3>*5&VmYkfrelp zmzE*L4RV;|Wv3RA5z@#;px++}E`&kb^7B#Fpac4-?u8lzaVv6+!-n9I-GNdYz$XguIRe~) zBSZnTxQ4gSpotHby-@-f9!i)Y441+g#GvLWct!;+h;2Pe(xsrU^Qzi*6vOkb&Dt zqkg^x_rgMlH(3ukuM-M6ingc?*7PAO)l9BkZPr5bLbLNsA5B~V$ehlXmU9~WA-@QGxDupi zlvxbQ5xJRFsVU&X0^}t4`Acc(ddWFOpq+&%I@8kiK&3crH3!7x2n(}#GZQZGa3j<` z#t`>Fc_yIg8#qP^8AK+7Wf72*K*w%^M*|^!!2E&|(5OAgW8ex6)HKb{D~5R%e4n+R zZ)r|RW&mu5AJjC+C=q!5DeUMYkYPFsO8UM5&_V_@h7J-$Y#%DntI$wKTyO(*5o|0R zVjA?;Oprz$1trMAf0%7cg!hm;2%vCAYQcdV0P-8Cmrx7}P{=MO(0cyTqCD{7Zjf+D z1+Aom4aMiBrlu6TR)98tWtM=7VUVe?F=Vh&pzy&MF$2pWEwM>Y1#5=hke*rrxn4RG z%k|Po7bAlVfvoOVQcwaf+y^a}fp(`sk_xs8>Pq0t)4@DFJrEZh{~&%b^uP`MRzUZsfx;6khciZzBLi!+g0&MB zuQ+VR*;;L0UkNSlJ=1e0^M(~TUwkNUyz((tE8ggnFm_hqp7c=0lC%@)K1aV zD@e{zQisF}C|HpVz!|#u@(Z#7M1`+sZen^W)I@NQfKs(G$fKDc8E8>JmRrybAx_e1qyxE<3jO5Kh$G5~TmQb7s{W^kqer4neS zg5(3RD9%imlLD3pnFq~tpb`L@xF82Ha)DJqRuqD7w1&!pOEpm5fXIX9U%~Yt36%&~ zCsC;n)~wK!sOPYEEKFW*K-)7Pc?}>I`V%4C+gR zLJO`Rk;>3Q48^Zl6Aj!9qEbDqm5rI3kOLB&@NE?!eg&7!pg=>)osjHWlv<1=2FW^L zt?>8<^RVT1kOW3H!E_W>AA|Ig6;4HU#E2PFA{C*olaKuFpu!RlWmGcacaA!fh>7QP+@R9-8P`>~?ChG6z1{MVC1g$yFNlY)+^UFt>tb}w6z}|Dhn8~zN zGKTpNR5Bp@u@qx|6XHo|hX`C~>iOk6VXA|a_z)X0Wo@CSfI*uz7}jRyq#%uhLF|ME zY;FoDCFU1{7AisxS%Ru8cFaow4QzpvC8WKU3>gqAhLjhuWDXjGPsuDo6$F_M8sP*b zo(c_ha9<2+Flf*YdnU|H0UbFWQxua2tM8El4|#Pp#0St`C#>;-zA_nFWtD5FgXU&o z3$tN1XJ)2>OwofI3%+0ybTOK*lXI|+f{~7bAyWB{>=4iiZb*Is83St$CleW*;Gs#f zf)i>3q%eo}*g#@B3QCZG;{w+?;0w=@90~I_NGV4Cg*pTjF(A*thw(s>m;*hJ-xe}J z2s_jbT1OX`q!xhEPjXs1jm{XA`ycXp{7w{5-!vnP|(2a zg#>+OZb43dN~#9bTqRH$4Z@H}fH({-AHqj>A%P5S%fXXQS~}q(4iY1vbyNjf%@oi+;F% ztZ|%zeNhD{dLS(-P-O#E1YPJ+jMidt!EOZ1en|3z<@~gCJv7tsw58xqhfXg*T!rdQ zs1(*Z8d8%&S1f{7xIhemCd#yQ(2+Kv(x93Pv}*wrsS36V20C1R`FWuIM+Q1vkbB;J zGLsZ+6^wMaf=d!hGLs=(6VPZg)Gc|4EoC5UU|vs#S^?S=1zs!`3p%^m5E2o{B{XFC z8rgN=rVP>;5p;yxEx*VoKRGc6(z*wGBo!P_5cS~X1aT9@NLwY<6wrPfur$P8P})JU z8)f4PvKwf<=mYG01yBjEK%g)LMGYt+f`=|(r8T_tf%S6q-~}X1J*>(H*@RTP<18d$ zsT5SPtAk2Lbxl~=1eu5fr52c>>8T~~z80E=2&aKv4Al#5Tljbc}DCELM{z3M_R`;VX-+;IbmY>sdOR(l4{7c-x=0n>bBzPy@(K!UPzuZPasjS$@ zz`z1>tP3a`AUFQOi2)=CFP*_t?4XGTP+tKfP{5^w3*I$(Fz=SY&L#vI0t zRl!z4$xulL#54l2jg%B>p@{`tXhTjZ2G<~EsmY*wI^o+h!7WjUB2ZL=VhCkk2y{+I z1$bHrI(h{54kY}ryOD5p0r4TWHLA$|Bev!M#V|^iMRFlFJ8YFek%J?yQ}a+4K!F^D zmg3UV^}wnMKx<@C(iEyzSPlRMDMAC*ypOTA7i29sg@H9!B&ULhdB8UlfUewyb=r|_ zhbe+B4~AI}Dzwmx0aH+;-ObS_*i}hK!2p!~AUmsZ+NKoj>H#S_U`E+NOB7?2WP#}j zT$u`E2NA5%4E8p1oPsWc2kj1mk4v~hHXp(F9-)Q;>b@krk&U_984~W0G9RA5N{TX* zLB&5fi@7BhgV}hp7|d930zt}Oh=!FL#xiTdnFeK@4br$VXb=_@Z(w=oSSC{I8sZnY zIJSXI4Bem_yIe!vEx#x^6=gv*EKQ*_H(+KX*VibHDg~|i!Eh2(6t9CIYEYa5UO)u) zK32y-bRatg)O0{*$*unV9Ae3_}3Mxe=KH3@p?1@iD{ zd1hWpetEHu0(8(5#K=ws6>LQA2n5xJY3X{9kyKC}RG=5`>F44f9t_SP3Q!9`b0glCMnkJ5XaImJtMJS`VV1?{3aLli;{hT91?8AWwAczXdtHMmmnhMYA5OF2*tSc_y_YjHs( z1Zsrv`zzDAVd55NGTWQBG`Nirc1yX89HmxipvyrZ2|w7wG04>gw3Q0fsKJOK zXrY)}0GaKC4AX%p10kzD^%9ecL30qG1c@A|2!DYS9B4QUJjDt+fe1AI1D|KgPs#$1 z4ucE73 zF$n=O8Cu*y3p#`x)^ZB#xgZczAYlmcC@7pjfea2S(77w%;DN{?jXxkf2T}-;hEB>t zIH2M{MI!)uxDt5fLQIT`2Fj*Uuqb2%3q0uS2|82&{U{Vz_&|(>WN@T_0nI!it>XuI z2q{lPd^&z9?qnE##a1T=UD5`%RnUGCM`(_~fPz=Ct zT|r4EY%eUtXOJWY>lFE?rP&(jDEQ~u8tW*4b0CQ8R+^Io5(v($O0_lAQ3x(bO-->i z!Z{cLT6zOowu;nA$}duYWld-$j2vc2u7V{_a9Igz-n!uWfM=eOz>xr`?u99axEPUAL-Qa$A=PhGr} zmw-fVm8w;XYe6MTK~a`oX=!E(q#{R2THpnb;GzV2pjid#5rZJDSPLR(?nZ2?g*XCI z2&X{Shd|YUir^IF_Bz-q&L&)p$Lh_;dV3`qW zJ4OKlb`E9=1SJd51Ue{GKzhnSxgG3Okf^N^<}&H|L-V9LNHJtRD!27^*mHFQl#P-;$UVliyh2zZ$Y zxM>Z(u@$WZVGj9BQ94IV9^IVVV)M-QWauA^YAqhN&8z``Ejpu7s2-G@6DlnjbWN-`k> ze6Xo$kSIzA9UQ{QPJKeXdhtvdqMX!&a4tAm)74qW2VMjy2A`%=cY3blaM4(Vqa?Z~y&d*78EGU3%{z8rw zq%slG6+zko4{`xG>w!Wa)V71QL&2tE&K5&9A|V-od$lmcE^t2;RG~qS00)&0uwjLA zNFhR}=@HntEVh;;&M6N_9t53$3cC2*R>1(&gon>nKzq1Qe?Sr)Lam{tnVF$6sBnX{ zXdq#S9w6}P!qg(rA{= zkc10b(OQ%R+Fl4bI~U{xkSuKA4K|PpDxyI3EW~s0osOW9SdcKb`W+F(p;*ongN+wM zigK`($YvALYyfNf!4}ivaG(Onb!b~LVG)9H#u+TeL3sw2&|xdDKua#56(6k01znj6 zJs>S4qbR>LJp*P2ioM7+F808Hj2xo@Qpzmh0XO0VSG~%-vS|T0peTg8K`sBLW(tL!4C4R0jQ6bn^+JH z4Kh&s0`+7c(5f`(VQ`S73LWx+c*rfWBrylNVi|Ux8z|W+fHD(EE9ihX(4Y`x|2raO zfD}WG#ar`2%>bWG2eBU#BE+u4gQ(5{9mNN#2vD2_pI87p3#lC=s`MP_hSQ9#9(=)d|LsIkwC+1&B3ZS#Z(^g&XXw zK2QXL;t#9_Yaxg0ghfb5qQoR<`Y9wc7radvHuwxFX24k{9LHIWcq6YOHwSv1iw3ka zXk%ZIo1*|t;kHVKdIm}gsd>rypfj*+m7skTT|*;NBPDxKJp?MW!58F!h=O{DnC-y~ z0zVxY61E3391wS6$N=&8J28}O4`wj9@5oTIJ(!_jrW3;lb}xno4JQVlpPmd2X-*6c z+k+V%R5>wpY!7DGz~aR4g58TjVY?H^RPrz*6B9EtGcyYmu%ZDr0)U-Dz%jspXLm4z z$nIbUlb@ap3cG_DGD|6U^{oS1?1to?wQGJ;4k$dx9BKwgod>`0L5guqT+I zWlu0e$DUw@344MWX6y-OIPlk#VZokYh825)88++*W_ZBj#js>oFvFHT!3;b01T*Xb zso4|Ea0F!jo?wPEAho|d87}MzX1KE_nBfJ;zCFRoxHgUrEThJw$I3@QhM85TTsWY9Sn%)s!>k-_F*FoVN8M+O%V{oauw0Hpq-BSXZ& zV1@&)9T^fp`rbG)WPsFvcVxKm%ag(2fg?i!Nc_1YL(Rcp289=n3@rzP89uylWat5z z|I(3R3dp>#jtp}^{AZ2~YYql8Yh7eHt zc64I+aXOfxK-Y=k4~W)wVqiHF%ITOs_knP0KaweGJLY5Ol&zWF`4cSf%Q_ci4B;+|U%sCUx@FCNQVZoVT zhJsQjh9zf$85l~P7*>Gv=QuHJITOqfkn6;-2PB{G#Bk(HFoQy|6T=CRzG5eaGiQPs z1j?NlCL9fB2q<%6xByaL=)`aX6GOncV1@}ZoEQ?$1v4yY zbz;c*>&dWTf)hi-xnKr|X-*6s=YkmuraLiIfYRGkCvf?3q27r>;g=^vLoFh_K;{|L zL(CJH;lvPeE|}p$3&h-nHYbLFzn%;e8k`t1&IL1kXmMgFI2X*o&;+s1V1g4v#kpXH z4NXqq^n9QOVqd^ig!!QI|HEb{28Ij43=8HuF$i1;W>DDV#2|4Ym_cB^6NAEqV1@}x zofvd31T!SeaRTSd33HqnEI{g3J2AL`I=*TDPUy*ON)X9&sl7 z>ZInNmZg@hW}~)C^@!?JMH_`}49gkTsIE|)s5n`1lHvqKd8H|en^gCxDljNAC^0BA zs4%E9s4=KBXfS9pXfbFr=rHIq=rQOs7%&(z7%><#m@t?!m@$|$STI;JSTR^L*f7{K z*fH2MI50ReI59XgxG=afxG}ghcrbV}crkc0_%Qe~_%Zl11TX|L1Th3NgfN6MgfWCO zL@-1$L@`7yA%#4*G(Brqg0Brzm2q%fp1q%ov3WH4khWHDqjkd8lak^ny#9mnxdMj8mStg8l)Pm znys3pDxu1w+M(K_+M_x_wLx_w^L>WN%mtj&kgAlTq@b**{GGu=X#<0#(sl+ZrFw-;3=32jz*7Yy0|Ns(hD$;jFnJ~@A0Gu$ zi%Wi*0eiVlBHz1Rft)iXkL0rVf5|E^U7GxI@zj)gMjf~Lmh8AwDfjZ~gN~b*rfd6b zacKOFJirma$OHQ`s|MiuZqxXbDo^_l+rq2_{gsVk`>5DEtVYYF< z!s7k6mG@HiR<5PLR%|uRnX$>~+R5Fw9e(av^El^*QEAM5*YmvB?nK&NT)oENrkWMQ zy}*XR>nFq#FYMp?Ws9QnkzjW`8c{`XiI67G4H{Ikprt*?|V|z`$zMxM= z#A?~ZgSNV9+a@^PDPt?V&A3P5(hMzutM5BoHx|lo-Nv@`$^QAqS9ZKubdtTo%n4Xg|kufIu*_XiV?4-mTnHvVFt$qwipWaSo%c)+`1s(tjpr^RF>H|-iK?| z$r|3fHifJKs~nls1q8VFwJGqNRNa~pzPUADf%8h*&iPLg&++}d7dPYNO--d47dC8J zas9Zs?dHfv-Yt68F?+YK&DnK8p?SyKZJYPc;(4@V^X)#B8#wSL(e}~vI zW5biapH5G0C^XBFe9o4gS0;Vs-vjN-ol#Y{1kQNfP0zo-^XJd^`}&;sZ5O}Vw;|!h z;%nFHCSP25#}o*ga zHMsX~niRD~n$EpkTS#x?nca7367RmNPq)9nQ=@qQHusfFm)o2k zUHSN9^WBWR=3DY-#r8EutL^;uuzW*)x%+mBXHgs-1zG(6Pg%32#c(qU{-2lHpSdpS z$L0UoiLU2!#3~+fEqHf{_j*V(i{GKm%$A92>C3;1C0y`z&-A=qo@eKkb#dLDsB1UV zxbHiBu)bj)yl&6Rqw{v(t3SWV{pJ6y1~Q-a?(I6X>y*Ld&B4nSZ&6`2yRd6A+x4@W z;rBxJr{7f8t4iCv!z=L!lXOPx6zzO1^L^Y~7WVNRmcGv#)%%`F7gQ!V8cItxC7Br= z4%XFXIhvE=`lY&GXL7 z%xDi3I^-pwd^7lR!Iz3jj_(sgudmY+dA8O3`qmjT(^{HYBWFlANvvd;efyX0hFPc0 zR>sDbKax|W`Ftq zS!?&ULxzh=C(3l1EMTATd(V9FUA--QmG@SvTD;$6^S|r; zesHNvdfBe)f9RM@$Zf_h(J2nQ`FfjQC@(v7(_%mG(FGPhUv?|@FQ3MBZd$J}C&QT- z55w!O-ATXlP6a0e)Ct|t}A zKC8^Ww>89Jev5x(gb<&Yq_QagtpW>`*^bJ#%d`KcK8t!*qsn86VB> zl2AOe+&JOy9uTQ3K5 z$exXssmoV*&uF&hS~)}MkT({<@;^b!I$sbfwkr z$}rdoG0fN*Y}mYDLejeKi@{qbi5>lNH~h={BU6?idVh7=&2KOFu&-OuE3-ZOp5Yv= z_gbxQE3;3n@hG^QFCF_sPs{D$L$iBn3)qj=Cx^TUQA+*n`}FfBrX`0~N~ce3F;H7D zUvtO0qfdU$zxUVb%;#&>e_x#6lfAOxZ`7ulcAWD!)myZz-k>6*^HG%D)W}UsuDINg zeapiFkFOW91I?P(OTCi`9gb(;U#!%Jtjp846^a{Ymq(6sg3%Dvl86fBtA>A1Va zEmoPCJ;y@oh`p#`D<7YR(+vM~=9M9J2YywCHl9xMkw0qD823ea#@yw6o9|2$T_w#B z@-)iO|If^%r0X|=E6<7hKlBW%`xP)%_j>genKMbkUA?+NyQi5 ztv!_Lo;T4WM}I+Z=?gOQ9NMY&f=?=!Ph0A-r$0@4;u3YOuDuWToc^@1 z_rmL>dw&lozkhaf@54PJe=aQNwQufHtJ^fuey7TXf*+!%Gp*g8IaQVajozA_dgn`2 z-BEMS;CE#f9^W^v+K)))D&wNB@K_&$^;<);*9@Z=jDT)OkPY~5qH3l^)%xSxC?D_6cq zUQ;$fZVk7-^z6j9QitxZmiW3gQ}V-1apO*(14^9Q$;%{v!+?v^&YAgcp5sR2y35#n)X`L%P*%?{r(Wn3v? z(y~9?aP!rv2CuR&8{K6TG@kG@TV;9wO4Wac@6}I6>#F(qCMj2|FHlmN`Ak9dvx=g| zmI^KTd%LvV68>lwa@lEoktx(YT(((f)`_orYZsa8ALyM`@cns2;krn<;+e*GieywL zmuve6mKA*zFL9lHwY1_+XPy5x?|Lx~-de?^^EFFK8Y(BtIaQomz*6=1)UoOfdV6x( zUFvf0{j$h^IpJ4cPTSV(=r`qAOhJYjrWT(wIqKIZ*^A~Tr>)jb3_SQU;mY-8sgL)j zr0il=O6$#hkgjn0x`+5eQBOblVDHM(iC%w_@A;qRl<}LiJ<4b4-5I_!W*u=`|AEc@ zo1cU0L6s&K7o)R|#S#1tTF*S4WP7@u12R5_q%#?Z+U+R~=e)K(tmnXw;N7dNf*y&~ z1YE7#7x=>RQPlllh3K}oX^|V-mqeIMd>zaDTQergH9sy_e{Fn&wCTg`h2I~%JHPSK z&85YUV>a77%f9*Vxl#I#r>wkHPb|FD-t#Lwc^5Ws(VN6C32&b~(tmNeNn z#$>+wutWIo@k z&F|dc$-e$RZRNFXH{M@q+@yPzWl7Qn<8uoxW)(cU94n=AN#I}HQS~Wvj?`)0I_Bvh zar{hJ_@Uo6Qx7j{x_ofz3&8^`*885Gf3*M9vFej2J_>T5EMa#!%zByocy6wr{~Z4ChSTxuxsg zp5_NKdvC=>?SE@BV;=(p14C;1o*msMI9I(}zf~xFdQ!j9hkNFC;+X%LPsZi2>nC%3Jzl%q;i|-eKe>Jv=9_A7SMEJ3 zF>U^WdM$NH_rJSp6dai*^?wog{=#F0ZG_0|2>TVv+a|~wUjDwYGwXbr2>*&xYaX)K z?`ugo%yy|@LELt6nP-tZR(`R$FY43!{LsY*4Rx7(`lqY2J<1O+;^#emJY!?A=5dxp zp9MQ^ew~r;GU5N_KXn0V@1)Idh54%Q5{&nuqb4wSW9l}(g z{${twhr)aMJ|_(te%=m!?wUMjfqnMRF5qE|!~YOJ)fq3o z6?b&66MJti_QRoxWv;s29u@hQijEbbGuK~#(0yt{jIs5$$hUD9pN4Lz3vjyn2K zdk?n#Vb$046!-G`y8d&SlF>B5d0iFzv}efPoUY(~@=Jx@I*$Vf82?^cn&y}#6F2R9 zrKOht^l9^BKYdhw$|{-dn^1TCYv0AB=dZWjix2AKJY@cIYt>CRhKxOmdoH{(yx-9u zmTZvt^O_RNysTM!m+fW0agcGpS>Bel!JHhTv;2x=|7)IGD%e)Nz2xC>{`YhZRINRp7nR<)q z%t_M%{^{w>+!>vK4fB(W z-Yi$#FY&hiUCOmJ+jQ%SUrw85id(*H4cmM~`TVY_Gk5G=%CEt+V_rOK2gfm3TvFKc{;Ggh;l|cZwryMG=kI^A^u>-V#ue-*7x6Rv zG?~V^qVFN!42k+I-rXFzwz?Bja;85^j$zEs4*VjMlbF<+YH;II5`$k3+tjzxj7zJV zIKCYG%6}w0b;HeLLfc;kbnn~o>d(%Onxwn3_eF2%diGqd`TYBePpa>x#0aacf#(+Q zVJNw>+u)Jc{iTk!H>O@+a`8y+PkGReF7XKi|i+pUBh7hG6$i;bCOpRVHa ziNDKRb25On&_jS%T*1p}*tT1c;=55T|9`TfHyfo|8Rd>s|m&(_(+*T7U zz9ZKBIGI~$N{V$0Z*G=dRaVrBCw%9*Cv*Ov%f+xxy_S96#t;0HUD`PoUy@_|l#$7H z=->CG@Ysgb^wW|#W`%j#Y|sB)kuK}Jto=aXR#jB`U9U4gcizwM+xPyb`1XCy2^;#Z zUc0vV#lnk|>nv^@I^uu-Q_#iT=^vNv2~W4*%68XdlbQDoUhP}!xuku}n7zI^vQ#D9 zPk4WLMf&|v!@R!tZkhWkPbY5f*^|~RrJ8?fK~Tmcvok#9Q+IQ_Gb=HP?F?mA(?7b) zdf%qK+#1?jq9(;`&SE)q{r~c<7tR~%+?>}PeQ%xY?365}5qVqSDKa|++Uf#hG_3S@?RzVt@^(jF{?wJ16dH;VT zt;5V`&btWZryedAeHSg#@who|*2xc6sHu*Imr=x_K?? zj>CQKH1iwQA6D*J7kqE`yrb@$&et1k{r__B-cK^8b{*;p-aOerWy|7ayDpfqp1saC zIpkirrt;17{hQOO^o}HY?TF2gX41;np0b5|pZQ^)zJ*b&_oa22-uHs?zofLGqmfxs zleTW~;gp=CEVbodT*IQ4xA{9xJAUG37stgzyFGusf6?{$%gxhkw)*pIU03HjuUWUR zeTK|=uR}tC!8er!D!vq4PJHh;NpIctQ1h+NL}X@cz0TU)GObB^M&xXUl@c3tf8Abb zc6!#c^4Qq_IXN;{9PRI)jpCa(wMAu7&wSCx%Qv|x>|0r$`0>TVWiLKocz*a^v&yZb zn^I$A*R67KoBujL`;10e!QV_v!|ZhmT2b%0**SHDWGr&;yP17H7R$crZ9z!km+Vx7 z-CCc&FETu|ty5-V=>+x#CgSt={N`)v-KDznUZu^Z_ZG#UEB|M`@Yv^gRJu0eo|esr zN`KauNp&uV{B_%JhsYe8BHG2+%eUKMnevO~{T4S5Su8lptGN4%57)Hi{ldM|&c&Qz z;B>ui=#lp;sk{7P@Tpz9V?!1%$Pw-6wZArT8sGGL*JlJAKeJNs&BI@pzWzErxhUy+ zOm^inS%;9V_agmU=8N$OMevI%OR88D+_F`6oSphNdwI>XsAu7)IaU2GSfnscT(?-F zYyKnS17v0{0urFHWRZ>1S4xj47}Iw`R3bVvOASbw)KIefV{?a!7R;_LYwCFK0dQJLvr zPQk$&<&F*0&91xm={{py%CPn59_f~rLdPztjHunw&gCy=MCIJPDXQx)$75C(uP&`S z$Bsef?#>wuLJOJ=gS*!yO_;Pb_~P9!N5zi3{}TTG(DEtYZce+pj(yL|?J~V9<`~|~ zZq<6Pbt=2^?d1ZGH9umd^B=lt>80H>dsu&zeL={JkYwM_sY*V}osduDEm`nzd9r`_rni+UXyl?|rsq95h7+>F=_ z%Zoh<9&QQDzVNlmt=TL&c2oK3U)TLEJ$%Oh^UT&W53aZLuAe64wXIi~d+vgQ6D_+P zJDHVZ-J~pX*bPPPk7)4mwWj;eaHwr(v>BdHjqw+J9zr=0kTRwM{=(Iae zLl~t0_!~xDPfD73t}^(B=R<$-fM0cC)z@{WCY_PFqTAagY%*GYll zuP^j2Jn;8k(#>b@l|}YE+{?TC!XLG+W_$aIo9YTKsO-!_fNrf}St9LzOOmy0{b-Bw0zae?fUlkW11 z%H`w|WHqJrxz|X&O`I*U`u-uw%&lLAOh26v{yyWs;6}A&0>wTPMQx%)ME)5li|y#E z60dsVYvJqmz_MTGy7kFPeOBB*cG|nVv$1Py;kG$o6=cg4kZKku9$`MGHsAEtVKWnn zm5her`&$gAUfpbTIs28dAmd$??57h{SN1Pge{c9tO*i_aa+0r)(gO8rg=aIB6jeTp zYE^8}(B5@VUh_|an}!`%p>Cnf7oE*zhxNXmn5A#NXl=o)-UEdZ&%YPTMXoElV?48b zvZ_p3pucvB_{XBstFv9}I`34}dvEiv<>e5oIiI9h*-)~i!bxs&70ZHC)yGc#&Do>3 zA-B$@J>TNjy}Vx&US@A?%gHK#6P;lg#FY8j!Zc}pJx6k`sC}aL>a>KH2Ln@=UB8l& zviEVC67#O~2bsMd*H0^WiY^rQ4wm=xnpj%te=qrupA6?|pQ!DVd}rKU>ULz-40pB< z>s=lEzPU819CSQugbRMnI@BQ+jym9=ovZ*EoZJ9OwzMTD3c% zM&wc8zPhVXk1StAD+J$s4mV-oL^-KK!|O>G;3tIrIL9vaS3jC7|>BMwRr>c}JXn#H=g*%<$6V)1l`24=uK8 zAKk(Qzi>KE`FdJ=@%P@TzrOkZKYOzxHsQuD^YB}LI{9wfJ+{18_;t_y&GWnOepS45 z$K3nr^=zIS*H)&zzw-XZwyV0E8ZRU*VY#^Aoblyn1zDF=q+*Z8{S!DcXNvlIvJk{p+!OKmH4hX)OdfIpWic|eZ=bt!Pee5K+;KwsA>?LR0a-Gi~xGZ&! zX{YYQ_0t?CNuvn zwRu)bM^gmf{sy@X&l>OCy4W)LOh{|svgCGg39GiNMZ7B?U0AR}Vb}CkX)NbfFUkA4 z?sY}YdQBnkwfSp=)~r3Iy8L6K#4_X8`b$bf%a(4pcU8YzYPjm}yUH`5>X?nQ%y$?pJ+3|PgUbonJbc@FO zS`PoN{JjrSt~0hXdR^PU<5Sj+yASS{8%HDygat^3&wsJh;MXsK`65nheiYuXK5!!a z`0}--MI6p&&*iI4U0YwED<~mqzhwLGc8Lq~q|U!kUsT-hwBeY-8jdRWlM9U2+e!4D zh~~0R-@2)t;evjEMdtK3@jR`OA0F90o!*|>)_(D>)q^eP`~9QJjWz6FJrQUNiYR~V z@b(7pkHGyIAGCig^q4;XNR*O+)}wjh{`H>u-;RIfn0(b?d2Q~W0}`h5FZlH;Z`Ynb zP2#A!R{erqf88aS92IH=zVuI8;ql`8Y>^0CD~z(DT;Jx`qeUr}g;`mwRNN=H*{hyfNc=BFk~j zn>!Zxxa80H`sech3GdPZ>cVcBOY`ki_uasbb>f zJozuyYTucgXH7_(sBhn)IXU9(7MFw&o*(Rna=t6>FZ+0Y*S|^k-?-`R=nR=Hp8iI? zfT{3<$L^Cp`uA@CY%oZ6eIA-^zhKVg=HovPnq`PrcJUWIi8VhR^DtnYU&w!lZ!c!3 zuIuh77Bhb@#?s{QV~?HsT*a62Dl?p}w7 zOIZK39TfM})L;MA&&x=u>~q&V!D%zJ_f;rNzbRYs<)rrkk9B&N{xTlOa!gzLeOjE1 zzm{cX?EGodpDKU+dH*Zn(Hd~$a9l8;L7C8oLmU2D10RWR;MneRKcb00gC z@3^S-ru=Yx@l(Gt&~dVyNwC_+Td!wb+rRnev6tMg3wtC#@Fgwu$z->g9k{PW-R$r`}zQl)aY!aN$O-&mwY9 zyjN5<3ZKrrzPg-m)6PQ+V;Jj>ST49GE_F*_jP1*#&44e-b1lZXa7&syr7&s&t7&s>|2(w!=FmO~bFmPBfFmPUA5M&o*VBpAL zVBpYTVBp*UTK2=hz|q0Lz~RBb!1;kefSrYbfg^;0fkT8rl;bgjFuOJb14ju11BVF% z1Lp|_L3UmS296X41`ZVl2F?`>3>+Z!Ees4CE({EuFBk;aIT#o?Vi*`WWEdDYXE2Dc z2Qx5m)G#n`*f20~-e3@77iVDL$YEgM&|zTU+`+)W!Nb77(Zj&N;lsed`GY}#otc4w zBZz^4Lx@3?V=;p;yD|dC983%h98C-i z98L@joKF}8*x4BvIHDLBIHVXDIHxd(usbs_a8xlca9A-ga9&{$Vi#s$;K*WN;Lu`V z;M~H%zyS)+E(Qh;F9rtAFAM_gtPBhsVGIl$Vhj!(3=G2T#taM`Wef})W(*9RXBY(8 z`572E(ij*x)EF2z*Dx?}fWo_tfq}z~fr0Z4g8(}x0|Q4K0|SQ~0|VzA1`+mT1_q8g z1_lm01_sVM3_|SE3=AB33=AB43=Eum7#KMC7#KME7#KMG7#KMJFhJrzkb!|ikU^BA z85;kE3=ABG3=Euypz)u`z`&u%z`(hPfq{dOfq|oufq}!3fr0Z8H2xzQ7&s&u7&s?E zjX;BaMN;Cuy*|5yeF4p{~U&RNj-uVrB1 zuw`K2yakQ_Tm}XXT?PiuT?`Byp!C$sz`)_lz`*$n8vmd)Bg`PmaTpr^#S9D_#taOc z$Dr{K3R7hU2F_&+3>={J(#*iX;mp9m`3xHW(F_b6(hLlo)1dKR&A`B6&A`BU4I2O1 z3=ACF3=Evx7#KJ}>8YE6fy0}Df%6+Q{=*pPE^Bgq((-{~z)EO8! z*D)|~fYMt#0|SRU0|VzfX#B@BFmT8-FmTR;#(zBn1BX2W1Lr+x{O2<;aOg8IaPDJZ z-~gqkeg*~(e+CB5f6(~HmH!Vy;~$p)7oz3=htT+k<^PG$_=n~Hi_rLo<^PRn`Trv{ z{&D61lhF8w<^Ppv`Tr#}{$crlCN%zG`Tr&~{$crlCtCjh35|bT`Tr<1{$crlDO&!2 z3XOkQ{+|kse^~y%3XOkQ{@;q0|Gz@xA6NcA3yptR{$Goh|KCF6AC~{;LgOEn|L;QM zAC~|3qUHa;(D=ud{|`gsAC~_Yqvij{(D;Yt|H;t!hvomv(D;Yt|IKLm|1&iHapnKh z(D;Yt|J7*u|1~uJVflYHH2z`v|28!KVflYITK@kHjelJE|2Q=MVflYKTK<0yjel7F zpAL3zlB|p@qO6V#vaE~_0<6po60D3&BCLuGGOUaW zLadw&Qml*|Vyuk}4y+6e0<267;;fB~!mN@Y{Stz#>>&MYqO6G^{RslBEFk^NBCLiW z{RTp;ydeENVyp+j`a%5<5EeM&%)r9H!ob16!m!{hh|S6%z`y}zvoRz*uGjK2nfW=|#28ITxxB^210|O&S9K>Q`WMF}?nHZTE83aIVkQpE>$RNld#2~~V z%plAl!XUyR${@-h#vsNZ&LGYp!63mP$soxf#URBX%^=Mn!yv;T%OK0(z~I2Z$iT$F z%)r9H%D~RR$-v7X$RNp}$Y99e$dJg;$Z(K>k+G4Hk%^I+k%f_sk%N(kQGii`QGwBb z(Sb1mXxkAxJ3BjgH8=+-G&sS43k`6i0Z@41 z!=SLlhe6>?2?oUxC76LhKu}0XSQr$Ipjd-OlNc66#n3smX@Iq$WjOx z7(n@(m4SnSl|h1m6`Yeq7+4v27+4u(7+4vY7+4vE7+4v&7+4vk7+4wD7+4v^7+4wj z7+4wP7+4t?8CV$v8CV%O8CV%48Cbzkl!29jmw}Z*mVuRlnSqr-n1Pjnn}L-<8k|d6 z8N?Y_!C8+I0<_kenSp@;)NLZh97eQt+#vnP7-SYQW?^7pK*k^!BV%McL1u%@0_g|o zVFPu|7#J9M7#J7?7#J8N7#J877#J7~7#J8F7#J8p1y2J51H%E3A&d+R4dfUA^$)VW zAoqdn289Wzd&mWK10U2KB2c%;K;5GOb(00uT>((H6)-S>%54VFDtM3^AAsD61B09n z!XS5mFenT_7!(d54000)gWLteAh&@q$bBHp$-uw>!XUSTFgF7O0||x+wsA1q>_`|@#z{J48(8R#O;KabeaEO6}L5YEbVG#odLlOfA!y^U`1|bFxhDi(@ z3_%PW43`)<7>pP=7&b9*FcdLxFnnU*VBlilVCZ7tVDMt#U^vCV!Jx&!!LW*fgCUE7 zgW(ke2ZIy?2g57|4u&WO4u)F{91KBlz`^j0frCMefrDWh0|!GG0|&!31`Y-@1`dX83>*w)3>*yK7&sXC z7&sXE7&sXG7&sWtF>o;GF>o-fW8h%OW8h$T$H2iL$H2ibkAZ_Bj)8;W9s>u19Rml$ zJ_ZhkItC7ge+(Q9j0_wMjSL(Njtm?O2N^gR6d5=e7BX-!BrkP!$$@V22KVJhE4_!22TbKhLa2&44Moa3@aHp7%~|+ z7+x}PFi0|RFwA7&V2EVkV7SS^!C=Y2!LXBogQ1dvgW)H*bm3rVW#C|NW#C{q%D};( z%D};}l!1dGm4SodDFX+CC<6z>R0a-)PzDZ$s|*|rrVJbmTNyYQN*Op9zA|ty@G@{P z^fGWT_%d)XoMqr(&}HCYSj)h{kjucq@RostL6(7oVJ-s)Lo5Ra!(9dr23rOWhP@0N z47Cg#41XCo7=#%(8H5=*874DuGE8RVWC&*9WC&*DWVp=0$#9vGlfjsQlfjsglVLLh zC&Ok&PKIIzPKIJePKM76oD82CIT^SaI2pJZIT^YcI2pPbIT^eeI2pVdIT=nfa59`` zxP|? zlcAk~lcAlFlfj*Vlfj*lli@f6C&O_@P6l-bP6l;GPKMpOKScJp(7hdPYu$d**Up+ z`2~eV#U-U>RH@%=rM-t1!ux0h>9y85tNr^-CkjFa`z&ci#|@abO%8;tZxiM7S$L+$k8sKJx$n z|8PeL+lGOG!4WF{0mOEJh;LzFU~q+s>o77fxI)E2YMi0s8H@}J&QNiX8egdR5=I6F zU#K`pO#oE<0V4xL08|{LCK4(x!^FT42^9ya@qmg)FflNAK*d37oS|+8i913Sf!KZ! zCo@c8Vqoxe@d*d{`vMaKgEQ3cApiQJu*1PRAmR|qI{yFv?+o=Yi0$hd3{Dduwi`q_ z14z9aL^%UU+!bmjh#l?-k%;*R3R{Sy75@GI@9Ye=iUGv-f+%5l^7sFLSE%|GU^Ya3 z&fow4-Jxng;(k#Oy#{~(|M!c6NI=*S^#;-L6)hq6I&8on2E~Oplnn}hA1E6X{=QH) zDE$4PY*4uQL)oCP0_6b^*XRHL|A8O|s6F=o|NkH;`^vxn|6#d)#=rmnA)&y)01Des zs2WgshC$h&@B~LX0|Nsn+#;ajpzw@@vO(bkj!-BccS(Rd?<2|wZGEUQ43wFp8xJ!_ z!Y+hC$1a4yz%GQr#V&*)#4dy(!!CrO#4d!P!!Cqjid_i93cC=79d;oMXY4{4?%0Je ze6b5*V6hKjkg*S8FtHC|@UahJNU;xLsId=Wm|`Eou*N=wVTXMP!v*^gh8OlB3=9q- z3=$3@36L`3_T7Z3=d~gV1_~H=4@W&y9fx$6^fx|I`LBcVFLB%nI zLBlbG!Nf6y!NM_w!NoCz!3QMI=*1A>7{ZX^7{XBC7{cIiKA54#F@&MRF@#}?V+g|n z#}I}!jv)*?977n+IEFCXa13Gi;26U22c*|2gh9k9gh9b6gu%iogu%lpgdxN!grVk_ zFGGw|2t$rj2t$Qa2t$Wc2*VVo5QY^_Aq)qcLKsdsg)lsD3SoHR6vDvZ9Kyii9KxUg z&6A+8FD^>fk1r@n1s!Y_pPQJOrIU}nD6^y_H4h|#CX-lFlA2pk0@VL;p!oWdx8TO zE(SXG6|9N@p*O!6ECN@PnUBf`oyG=Hg3wb^l$r`wg=9{8PJU8i4pb$QcyVfOS!xkj zHQbJjl9B?j2$H(2Vz|fP>Of-UMTrFkPy-l{)FbZnMfGDzYEdq_*=d=nIVli9xY@}? znI)-3nTZf#gx^w9OY*Z*F~c|+d^BTmY6(OG!kqlPlGF;QD3bf2M{dJh4p$F4y&mdQ zxL9H`JX#TA1(^_=kj#XHF<2O`4s_rl_+V17Fv1_{Mfs)BK!&S@9_n}^p$wW%7aB*nf2U~_5pBbq+Q0IdE08V!LDfuONun>p1 z7b2dXoD4MpWG+N3w-{5bI1f`S8I*Fsx{>r|=B1az#URm9te*_JzFa>ZOo1h#?uD0M znR%J8h)>K*Day}G0het#`H9ff#=rm*htgnUpltoJVkj%VEV%*{H(*(W9)x^xPM(p0 zfhkUT*nyMatN^zYp&x1wk{!wUDXBS`C8_8RhKs`(P&;8(T9b0I;ctj+lCNB4HqQw%;M5SSe9l;(TAjflw6pZ5Po_JB=%wQIZ!^> zCNz&j#K0uj4Pcf&n2b*@fTTbhWPDU7N`u`8W$S|vG=yeI6fq01GPs#wbrtbt z$)M~2k-{z)Us?=v5Q=%G@x}Q$rJx`H>qe7<6p)|>0$7#-)oj!FA6y+B{(*ss{ z$N{)e6{ulpXb=xEqars4;yW~XXr@Fl*BGJ_RSZOP0mLp#EJ`d&*941$8o&9VD~&*02HR-oSWr73)L*u>XJ7zz z2S9wE(nN;b)RM&9)RK(+6b1(HQJ4%24Dqgh;4Ubn8N|QJCu>#m2PzM2W zi$`i+Vo54PUTJPpYLRNOLPjE}Gob*w-!471NTDdTpeVICHLnERDn~I7bV_StQK~{o zc|P1&P`3lwPM8|iVg=A;3JM@=8B~j{R8y=V?09zuhJwW8?8Nj`hUEOB{L+%lyi^7T z25{4!fq@}29~5RK86ba_mZX8EQ5aIe{h`b}1qcIn`7c9qVonZ7owGu5Nn&!gLVj6l zQCd!ZIY=xyF%NX2XHjZ#X>O`QUVff#acOZuYFvy)TR~X=PKlvmK0~Eq$(t$*ozcTpr(}qIHr;_ zO7pT6G;;G(QWY}cQOb~%nU@H%0MsT2`7JXqB^5L#qaGg*W~wVBXC!9kDU{^rE9B(o zrCTc$q?7^D?NZ1|%}X!IKnl_H)DpO08meDY zGRr{zOR7|;N-fGql`BXsO3N?GRZ!1URW(p&U|>*B%P&$_$jQvjEK$fUEiM6d3=mnxR_s}9|MD4MMNHG%t|#U zCk5mmRXszKbOr_nUBti-gGOFyPL3u6gH4QTiX8(3gK7Z-gDNQPL)`-DPARCXs;VZd zgI$$VniyYDTAYyvi5_(Z20cAJhG+;+L5YEZK`9pG?-GU7y!_Ji429g(+o$W2N4`Ji3^vZ9=azypC|Et1UjWXW@$o5{Wng|Lm=PbJ zl$e(SGA{{SpvA{0Re%`r@x>Vs{lyuef-XM3G%pvdFDWm-1f)I(TrS4P=YWb3hWL0$ z2?uctD0eb2z!G3mW=VNwajHUo0jS)}%+FKE2RR)spIDSxl98KQl9>#V0Oh6J#JoyS zNv8oxu9?LOswto}0|{J(#2io|4r;|f3MMOsq@4WZY;ZPA%1j6Mc^Hz4QWHUic1k`2 z1L%ZpklR3XW*P%SYF-LMT7D5jIwYtWKw_DBptvew0QtWtwIDUIgnNd`E$ z89m2vi7BbcnYoEM3MrZCnI#IT6$Pou zC8;S447r)bpv$%u)T`AQLPOkiEg;Ds;-AE#bWnnUtEp9IfSHq8oSayYilU4G$xV=u zLdvU9^~I@$rKx$zsSL341Qccp5Y-BxUTH39WQKu(p&-9F6P#r7OG^~;(;(GHF#|(+ zQGQ-JG(14!iACw9pa>`i=YPl*W}v`_hb}aN!1B5IWg!2gq!yQCf==nr&r<*mF)1V# zfhs4so4^eLaKWdLSOP7#R8v5AEI~>)P`g4Ml7&E4F~IEt)uK5iV5>^=K(#jm+|2x< zl++>xSP7y4GNL%Y2sEh3z>ru}ln5$S^A*6^0o0aB2Bl|+Zy@aeQ2KzG3H1g8$N-SI zI_zp_bx1J}$p;FFuy8HOS5Pk~%FF{5*r3@=1s*P5PHqMUP}c!e?SjIwC@~Kl*YWY7 zvLEbE2GE^&`9%s~4pNd~fVKsaGeCM25Y~s zLCZWCic-NTmLWMmCnq(zgdsV<6x@IV)oLJr6{nUIB$gJZLfIf;a6y?_4C=yV=7H*q z`1pdv%%WmM9OR`IgN9gPAq{GHfl6xRzz5f-Xn_Q>BdLUe!Bzv*%+O?jcRry5y^7$p zoRV6B7GMx{3@|^WCl)1v>(i8c&`1CSq+SJ$1*DcB0v2Qz#MulW+aM}IZCy}}!oUEk zTS4xDDq$#q6w#o5b!uU0BB)3MvB8x}QDS*|DyZyBEH4Jp#i`(+WGF_nuOuH*xPi>A zfEpMd<{A|2=?|)g8GK3;6-@Pv8DOmj^!x{jMo_yuFEIzyZbHeOFj)o$Xm*7qCs;eF zI43h1T3Y0T;|-SoA!QDzGsD0D$#w$ovnY=tnnAbzknWe*=my>Wv04larFo#s z6hVaysD%f)`xZ2454s8tWDY2OK<}i0w7ykSz_SPOrt!ugdDUWY6Q9AF(VD^98gi~D z$o(KWF3!As@azS+bHc!&4k|hl(^K_Si=jC>3Dhix2QtJ0P|5)n_PO8=7+9+UxFHQn zPGGJT7bnV$x2Ijle~gye;s%p?Yg zc8G(a7J~B(sP0mX$tg{Yu?O{at)c7~P?$mmU=sGof_i!}pfRyjuvSw&W4Lxq34}I1 zs4Be}gjoy>3_hWb@d1t@;AvaM7<;{xoE&S#7(Kn1oXjL7^C7~Zpi0ci0V@KTmjdaN zfy{7*8WO{0&1KJ}$fe5#Do^s#Ky4%Bbe9Kel7Uh}XHt5=&Acbpx83%wkX(shgjs3-0qVFeExAIVGofcxL7L76gZuhgU>Y zMpi|s*3>8`D_1C|D0?V-DrYI@D*Gx2D~BqVD~BsrDud>dVP!;eejfDl1%={*)MW6* zfZ$etu{wilG6O?NW^Sqir0!LK)=fzufdWVmJrz=QXJi&DWP%0>a&o|B0|SFr2?GPD zYYeJ?Kz;q>lF9;5Ws{qqms$euNiu-i{;5To$zVG`{!9inT^Se{K)wg@Q!>-iK=y;! zsTHZor6s8h>8T~Dd1VZsG8okI1-Cs*atpvw1#u=Q-$Jr8atjI(*D0mB1q$hzWvO}a zCIhI>fcATm6AMa8i&9}N6IIm|L^}goeknlJFytn}+K@0Ajnupp=%|Cbsw!wK1dAG2 zJgdj5Yhp74CXd?uQdLa?nFntVK^*}q4`8-~)6_u? zEvRx(xeIUSLQP{}h*Qzhw%5~$R@FuLyA;%CNmEb<^#n8XK*KXRiOG3nbyNpWLTBa{K)MQ`l8b>MMm2>YM%BO=WPoZS1A{?@YEDiCtPTYA z5AyOAK$A2@i8&x+LH#*hZ3SfmB!56{(tz}TQd1PtKv&ai!rPoM`SQ#Z&=^ILLP1e# za%M58QwNF$P}3XiXHZE52_ywjua;ro_VI8>|c|s9j5m1d{<715*^C z&H(Bs<|dXXBo=2wVk0ppGd&N~K>$ZWL4J8^5y)Xip!^DUH;9ik9#)!HoSB}Nnu0W> z0ZkL&epYIcLPbBPL4`bcg#t7(L6KaXkzWKV<3Vh2 zKOJH-C~6r%27|%@RN8>(v`oZIsMRFgMGzshA-#IUnqY#A47$0BB4x5j;+joC+Fo0@a_z3@NGU45<|b3?-m8A2fW@ za`Hh#1`KJrpwVRphSHo8hMfF#hTOyoP(B6?*)bF)rZ5yG=B4E4g66V7ba5&u*Mh7n zE(8ZBLrG#DLqR43Lq;jM+XT`Nb0(-BhO>*xL1*DJFt~a8xN0#lFeK;X7lU#tXhZ_k zP=kbfPO5?iL0LE;J;ss$i6s5zQj02*IV02OhG3J_)Bx*w!B-qSxGa#cJ7tX~Fd z=Yi$HjX-ce7Ew;-=BI$FI0n#Qc}ikY3TWT}Y+q(E)My3L0~#a-MFlwR zz)O1u1_My}7r?-f>WVNMmS$n*CFX#7UX^fjK`lE_Lp-^(h#@Z@G@b@3c^DW#10W#3 zq~(+rg9fx=c7w!0kqq-+0mx^dG4R|1kQEHYsj1l@eZ{FIWl5!JF#m%?2sG542lgOX zDMN8d3OJz@m!#x_JXTzil3G;6z>t=c7hF=xz+k6Z3@K{2}+YIbpI3EWJO8tC*7LrGC$a%xgyGRQxWu~~$VG1D)o9|tN3 zbHRB5E)Nodj^BWoAX(T*1AJBl6kqv8xu9M+Xch$IH}DFyRM7Y%Xc7f9zL8i`3LdLU z1dnroCpkdnT``yiD&|3RJ)o=ys*&?^3&5+sP_vpFusI=!85P8q^J}! zqz#I5SS&sAZ^H3cn;0b(eqv&4{- zn3S4RtPj!+F$c{EP&{SkK_|6xKr>9BF>8ggOz4~wNIRsA2DM_qu0VDIIM0K|KtYB> z#-Kq1YU+A=dg_P(1+_m?bHMc$tkJ3fFQ*a8O7q}B23hL}s*50Vf1r8=Vklw?4pK-V zMO1NRUP)pFWB?zOM!|lEI38BaLyJL${5;s`Gicry3g|b1d(xQCOR1kx@tvWamGV@Tw0ICF(=t`5py(3Vr1~qXR81f4s zBRuf%$u9y0Oi^kvxGkKQnwkO{v&~lkS8|YI3N(rVn^OjbKbQ^5ET92V==>ol976nq zf?WMVqC5ivTwQb{pn!ov)xaVLv^H9`SYNLyvp^3b!N8ze3}-SFX(urRc(a1GrZEI~ zvw^mzF`$~q01}7FGlRCYF$8!sg0>7cGMI51%I?yx!qG##2>u27s=mC6894>bkc+fV>i z=nM=DP_g8qWO!)+uTP=VJ>bqY%-rIV{G!wp=u8l#%?k=S<7{LR;O%Fn7SrPs)VP!$P`8pz;?QHeh|ax*&&w=RQ*NQoy4X(5^k)-O%orKEx>y zD`4is90d_Yw1H6dD1fYjn+MYa5k=^Mi-VGET4Ip`v>rpm8@N>ujWv)ySbRcUrT~=z zC1iz^OmKk?8onqlDN0SuRRB$&F@Rb@&?Ext&w#~2%LPE`18h2YP!VKyacNpwW^yKI zQVTMH$BcJW=UpZPG%K!sD>dl$VZ_R99v0=Df*c? zIjQN;j&WiMD3QPhBtnCH7#tOfK+{Z+d4SYJP-&n8o}|c6&d&iUhBWXL%E3!IN)*83 zjtWVonK>o8nV^v`g%Z$UQBG!3a%oYHLP{xUq7d?<)obyX_ zQu5SGKx0t(Ic2FJyDBQdt6&%~&Eh+L-NzKcIEFv#T&CM?Xjda0UstgQnApuqjAsMMKt&nBvp!N=uCEy4L z1wK?g$lwCdTogU18?3BzDaIZQsIXM;7Maf822nccw0hK4YiFqmD zumQUVR&WG31_whugDxJ1P29-H92BMn`FX{u(6$CByuo^t6G0)KmYEJ32?Na;lw_nT zfCCq_!UN;?xpIFA6*= z7*dp&SDcnw1S+%=6$%oIK*dAN2#bpKPfdWA2g&0>S^X@E99r8X)-{~ zFUbe32FTL~b>YRvadH zViKejO{xS9+apCcxQhp^N*Ej!V8t2S8jxnl7;~aR637$KsdQMpCuQb=Za9WUIkZRs z^*2Bj3Z#DkY7iHtreqeSCYKZ|WPsKxC01nSmgXuX=0YZO9rF}mxgV+2EdWiMLWbo* zqnzM!mVv<+K2}|lnv<7W0v+RH0L4u~YHE=}GH5+;8YnfVf{O9rU=Iao5eQnAR1Pi9 z{XzTXg2B;`$W(dxB^jVhqLG&mZ3PwUDCFgX^9pF31!PBZQDs3%zCvnVdS)JIEgZCF zU~o>%1DA845&=_PVzEL>YFZ*_Ac=ti7Ir213a~1yI8^~d6I@+!DkO}-84B9f0C^Q` z4ya@+&qxK0W`lAz$oXJ(;EV?U|SI(#(=1AKrMdQOiBvG zb(sYjkV24wfgu1rtPztciKU>$H6`FENP#R}gd`znN95Qk#&i?NHBdzg8ld?+dj(Lv zQCzH0l$oAUQf#lu5Smw%nw+1WmstfGe+4yzQWU^tpfhNnO&++Ohn2&+uFwm(89@D5 z$l5ee{!#!n02n}N36c)M?H*9;RUx$^IW;u})CoqEAs|OVMpPj6Bs^X~HE~91i2^9g zgUbn!N<lQUkXLZQLlLIe6WSJouNZ@x z84B%Plt61v1y9gKauB4+0*)3~8!;8MKd%^Oo?pH~acOb}Xt=WolxE>`e{jD!C+6g& z7QxoKfCiOPixiwggM9o0LgJnM{lenmMIW>XDP};n7t}9@6{e6qgwPTfRG>gx1Ht*p z*{LPqY!9mUDnT)*0LrcUMXAYU3=Bab!2t@+!KsCyUIb)hCcj7l);j=6K-CmOdzsKm zB^17npdhiRI90(dBmk0dax%elvCiP7t)Rg0~gP%#YdNrT4D zVRb4fjTV$9h01ONaE^xh|&;+H9M1^8-{4+54rGgq%NDIaiQ&OPXz;1^wpoMghp?*N_&jf(h zhl0Bu1)!lEkOifPt`E5EE&+E-GK&?EC7|gKO_YHFQTKz~3GYKOI6|DIkd~O6nNtZ) zw+Km4nGSOY4h1m%usoTW2g-m9aC1SnC6*;-=72{pLE%yY+6V+LAVJ~lmkQyR=7Ccq zSQShR)Qd<;1=nNX(E?{kcL$WNKy@~>0RztKp!OF`95j0mnt}k0bwInI#RZAUsSJp) zfw>l1GN+~>$$?yqAr5y+F|2e4<*j`9&`(-w5omd0F}TeN>Ghzvt0*-M)T3o!@X60F zu(xMm0ImOlOyfb;HFfq?R<+%V)pA%|$aq`>8&U44wi1@zAwL1_wCr6if zCr>{YPe1oy2B>?CrrOnsGFN>P<)8Le|)g7qmK_vowK8#pMOZar(bXgSQJe> zz|k4pCI!2}*)=F6-Zdh?GYC|mLhJ^Mh5C8>`G@<#^@7xe2l@NC$A<%288&>2S)~nxcbI>`h_|A z!2Rap3JEiq4w!vNh6VWhcsfTy%|a3nat#cHcn2ouAMWQKKp>{Da<@q zKkW8+`Z@di2Kcy!xW+pNxw^Reg?KvpfRCntgb|XwuWK+QL||rm`UQu&xp_K!y84C0 z`?~u22Z7cP!Ssc=2KhPq#JdIsLCz9@iGzI~@8ReNJ=X(OJisx?(bqM^H3;e_XlTWU z_sd} zD0U!feH|kpx`P~jg5zByoLyaAK(a75LjykE(I>boj;Oc@+ zLgNF1{6pe{LjyqL6QH&@IRE=wAct4*I-*|sNggt@&0r8;lgSrV`u7Q#Y zDDeFK;{Dy+d_4VJKx$h4mQ@!Kh)0!Njx;jGbA!T&_5VljDQmY z#C^dYp&>5*;ePR+exN{d2L&@+pRZ$3h_hpm3tH;NArEpMsDOrtZ!laxB!z;~I;3!c z+8qKe6TyXuKiJub(16(y;uz%a8WImLQQ_jAzOM1X-mc-$_yHB=u!pM)s80bdazRBNIETZ`gIa)=IAQwX?uvH{^>YRl#|V80xqu*7 zP=bg1A6}5e2l@MhkK=)u2dN4WMI)l%LY9Yn1ZtjVJiJtO^n>`s(=QZK1|Z4#!7@6? zERZ<1ItC&R(jJed9;s|bI1rTWz~Sr}@8j?6=o1f)Pq@4M{NsIs9bsV$YC}To4s!K# zb@vbPbPNHVnFJ9F_H_4i3<(W#MOX|q$2H#52UOYl#6!y_1r37=RRfC%O$A+r5YR9_ zq-9VFIy4}sQXxM%xwNQQ0X(ddsF0Rel9&S;0MQTe306qVNi8Y?wb0=ssT!$z>3ZN< zi{gyL>{MuXOjA$67c^L!k_ui)0X?<_-Z}=gXF%Pe@XWlF{PJRj;L75X)LezsGSHMt zPJTLgTpTpD4Rt7JK3f;GImj8&E=~rGl7gH9>VoTlSI~gkd>~`7poS}Q|FJkVH5W8?m6)QV09w?ZsF0Uh z4l)cx*H^1$@#H$jce2ph;~|KRyxMMPl%P3V_B* zK~V|Xpc$=N3|b+gS{w_v12o2$TA~mPY7@8yIQn>oxq{X%q@}>Z7%~3=9{Ke|QWziR z=o1WAZ3WShnWo49%6y=`An^GC&?q`+`VF)qum}{0;8XzVH-c7?zCNfux{@fV*v=;03L=?P{Z`1}OujJ#adJ^q5mi z6hfRq?Kc-!AIC_L0xkvyBoWZm6gXar6G0n>RE#2tess zk&6LIzZHlL8s`ItA!xQ2yE&=I`eEZ(nR(!RrT{HzA!F2vT%hr+RM4VV&`ArbpjAH# zNR9`c=a`odn!X2*$$>WdW~QX(m1LHH$4Nk4`(W2#aJ$CMCo~u|cmUfW6`We4>ztpT zotbLI0P|>Yun%a9ou^x*hktMgXtoKo9x1g*0b1Y{D;O#yLeHH5ml-+v;1v$gSOcX= z)tnTRGzzLLA@z1iKIljQ&=N#&{hFMZr%;etkP2Eu2TFG!K6uSV0cfWMXfH}pW@1hb z*dO4*e}$ydyp$a9v^r$k9Go-?Kxf~9&b@=qpMl1ZK_L%yF=QDBs3oCLnOdTwP@b4s z!o@&Ptwv>Pi6+P&r8y~ZSL!Gvl|lyCAr(VOKFA~BxQ18*ayeE-NO>LH<^hj{W#;67 zmsLPZ(agMb9nkcvI%u4$v^W)%u`u<4yarlS3_T72O$WFQlvxa(2GLO{&dw|VS4Em2 zvjS4{K+987Qy>x`AqHR28W)hO!DnY6$`#OD3&?Mfa0eZ(0m`h&iFqlYQ_>(&P?QRq zluOJ@flp)vcm}wJJ9>tIT7ku>phdW_iPVCk{EAA@02p+31G2>c-fn`d4FNSURf{zd z#g_&stiW#fbn$~FL2$_gTKtuoR|4}rB$tKeWr9~6q~wE^Y=Vwd1kLGz^M!(vYOxYn zCp1}r+H*w;DVfEfK|OGZ0G(`RV1UgIf%aA=CWFrCfn~`&@KhaW{693vM@Jz!KPNvA z7BWB|44^yys9A%7B^!IWGoO3F|X3 z7#f-D8R!}48A4AGg{_SPO&D0|>w{E*iavcE&}u-)24-+o2$BHJFT?eNIC=Tt^H)?6 zPB4zvj|Ew;Z?6od;qE~euqiFdu~UHWuwrlv2>`eM(@F{$Ts)maKpYSa%Cn&POAsGK zgZQ4lj$p+g8pIFwg=j9$Edj494)6~!2B|N|F97c`O)UnEua<+_(s}7QmEieR@OhK4 z7EXgwG6Mss z*s{)*;85lB3N(w+{HC0rC_vXQv430iN@c}{p5s?fG3|9L3V0ECn z0o3>@HqBsw(4aN@$=Sswi4eXCln+{;SW;2|N<(Pt&MGPuijy-^b0L`!R0n~_lo9C~ z>@09qq5~Q~2QLWAECw$HD*^W=Knq(y-7?T5SgqW- zg(@roH`+B+i$NJPT2;%$G#YUL3gmQL$f>thda!d;ty1* zXOx0s613HVAt1k~1b$QkWK9lu(F`ap!SW@zbpvr0qP_-g1cFrxAjg9I7oV3}Qj`py ztF(r$#Dgw-0GADkdHH#jx%r@F?+j@r1rDhdiJ%Q{ddc~@47kDoTF-%6PT(vD-L?b` z4bWOykXPX48mQ1L$p^bDFEJO~^8nQ;ka9LJ9b^p1H;~3SxE=u&??vE_Cv<5TB!V&I zAdvxg69#M%B4`mgc%B|q zJ;N)I@KjLkqyR4VA(dQtszP~UUWq~?xEqdSVq#upd156O_^cUFwt)2IL7@SjI0rEh zsudL=P4hFTy;z3hc(8ezpgQ}r{k#TgK zH7}QKtb(noiKV3_mLGS1s21xefR@&P84w#3Km~0I z7X!5VVgT)4gSI0WK)Fk`m=l3O<023)LwN}UgR+8OYDqEpoEK2(rT}T*Du5P|a49Q* zN@`GM0TqdQ8Hp7Ydc~>wDf!98`XCuyh%1ZrGD>oDxRfD7aiEG9+_!)(_6IEhuY?t= ziVChNnI)ht7?t^@MGByW=|!2v*?L@{;A2qcQdV#SkJdq(7NGuZei2wM+%d?{)6d-s z+H(aJ1jUJEpo5e_@ej^_;1Mg;6i`_LYJ-AC9bg3mxZMk?O2B7d#6yMSA)AO0{Zmjd zs}@_s#=g>1OF*?6XrB|h8Y|Tlh%%J*OyK=rsh~~H;Qdkx&?RtXnThZgUvRJw{6r4b zVk-uyPxa09OpWynbzNM;7#NV*nZ_1ox&_I)<(YZLMxZqk#zqXDW+n`(IVlW!AOf6T ztiUYz>Kuhc#Ck#4&DR>?zoS z3eXA$(84zGdIWIhhs?@@_aB3T6VwR>XEewve(*sn3gFg7v7Ul6WMfeg_)tMThTzQH z(j3rmL?UE4pRs`nsFwy_m<_4`pyOh`0a^9)ibYK~V&1`XI@n#uHROXjrebCQ#bwG=MtFsh}0!;0c9P9R<)yOyE^*Ag!=*)slR0%OF{y zIJpSC&qsAX4@np3Gz3SLc~nxX)*A}uoy+|6cyoI#{tke`!-C>6jN zm4U$p)C>R(8-N@JDpL&&40J)8w=(leLAv3q1;M*`p-W7`br?9HL1Xlw{wPRyNj^A7 zFo5=cg4gCldSE`mgO$O`1gLIMLh#E6_h*VzOB5MYRaFg)Di|2R{X2U# zP#;rYUp1M*7rLHVLp3KwQvtEoIW0d2v>hH?H-p!7DkOrI7=up+1+P&7We#wlhX(mr zfjbc3Fb*!y1nuGk4NWV6)}Diuxx0pd7cs|z~ipq znhaErLE63fc?v0+piv0Wwgd1WV4^}=QEG7pta%2VBeGHe4IP0tl`8~6_6dLs7KQRu z(CS+&1r2Z*>w|{abrcFJ=jt6u&DX5GDHDf`eObSUkiOJcZWs9jP@G&6O zVvrIP83qPjT`MpGg}JH`C`DVT8lcb&464Q;@e~E91ZW#LgQ_`Lj6u~DL^Cj`ntjNRx9Hc4)xl2n+AqZ4lffE*N#VRo9KGb`H<3;7u1)7waq}&kuaM=DhpDfOZLJ0N)=({6x^=R5Lk5%&KD)A zc_q3aT~-Q6yCL)|bj!=jbwT^1bxVtKz%@#03fS$c6<~@1hhM-=GE0IPxd z7g8#Mwm}u-q=L)IVo>Ddr=?jz{0f>CMVosB#TCd9&`_0QS$-zy{N40SP=Bx}6}Re5i@qY1Jx8G2 z4$2GA0wP7h(7+JnuDpCmMGW3B0WO!pEKuF3r{Gu&+Vl=7dO?L5==c=ypgv^I0BOt> zq!Y5GAu}gcAulyOza%peycY-DLDT?wU%^n%z#dd*f;r$iLjk%q3A7MCKMfq>IttmT zsRdwWW?nkTiIBZgxYffeI+%KIBm*E-LrFe(7Q7_C7_?^i)C9 zf(#50z7eR&%HRVoZy^mMP>7U(x6gr+Kgjj^Mj(fQ)hnoi&-qaREgFZJWt;+Wv!R}W zjzS`GAVQ8pf;7_@{8G~sOESTonLKd9hHUwSCM3kl0Ei}7?FlQ7tr$Q7rJI>SX`p(3pr#d#fpI;JUm?25osd|Xk0G< zG$yVKN=TqqE$H+FD+bVbqx zAXY#MEj=y<*W7}VO2|G>$Vr8etixcaXUJfvXTabJK9$yr0l7&C@)J~C0a+YWVuR)* zL2(O9GvG2GTz5gT3Z#w#*9oBc+)U6(#u)pDtUzG_G7T|i9pdTh8s_K&9*hAEsi+zl zfu;-prL!{7&c@-b3qBr?N$m}`dT1mka1`QuwznFvq48Xq$YySKLd~3Du71jAT4^N zd<98Qpnfj&@F3726?_vqmL3ZzoP$8;*IF?!Kxjw?wFT#RP`E4`lPIYehH%Lkt6`RRi8j;*yi&0^ThOI&~dftLlMC z2G?THNqd>a8DQr^Yn&3$O)WW~Rs|#xae?xxm1;&Q1B0`_pP#F92y_e+A_hxyAb&&U zLCsoFB1Z8)DDI(cCQvaBDxzV^OTe@L6_ud5&WcI}m@sV240;p*Xb}l$feEOrfw%)c zo}mCb!yyNBZkZx}GmtyU;C2f%F5z|}hTtKMD@c0^oCe@(pzZ>l}K4j)8 zpelofCd37x7A~|T;)1tLpsg$hhLBJ{KUbgl;1I_USA}A5rx`p`0h^?N<^qM>RLIs6 zg#z#dCAfZtw_iaq4?b)Iv@rzSnS;#qK&o)4b70{PIu$ZAHATVS8Efx8c&&B2i2Q)mJMr3vsVq!iG=B&f_y&H? zudp5iBu-7OV5tgJHkafh*9j=O3lz%;J&@)ks91t7HHNQ90VR9Tt!h;%X!#S|9iYB%J~(fJ0yHDFC>3EJG=e}9@EQS}mQAf7 zvr~}O=Af!Fr4&3diO>OZIYcce0*`O&RL{^8M9|F?~?k|Cg1qIa9_U~@nlmr==G(~xDkka082-pZP=;c!>~YE1GHdK0qGEClzIv|eZjndNLPq@4=r7xDMhKzKpB+i z`pgs@hS=&Axc@*&9n@~XQeT1MjwrpLA$&*`2hxJIyvDH4z^DQ=GXm=KQpAAPtssk` zX$2a<3MjRao-%`BjF44ue=tcg?2C{@q^odl_1sBa7oM1=o|%^#pPpJ0548`JUh+yH7d0T((r=0LYfaWOC;)Mw@;7l99=2dx%DSE*2xng*Xd2K5&Z`rwThX!(wO_)$@6Sw7@U zFUZ+{kU4LJUhsi{AUERG3r^giwM_81DoZWOOsfR7lt7yiz+>|Tpp(AyN{SVNp{gK5 zEudC)BDhThYBGWbjUZJLsOJkg-wv`S3L={XYDR;XXMLmEQ~g91Y+LmopWLmERSLn=cGg91Y^+MX%}h5)dR5{7(+WQKf(90r(3 z8AB#R3PUOb=yY~uHw626g4PuyM=LnmpdAg+U>Zu?I3^dCW)`J7!;?_4MRTNJkd4r@A3zZXu3{CEDi!>LA?Y8qZVNQt4+;cCT5|LW@Y4Yu2$u?u zZs`0oLLS!I1ML_CAK91++cyRtm#rv4^$?1mK=m#tnZnFK6u_WGAXeakb&$Mj4s>k> zcz+ARZctGSX_bIXg13;d7*hgXhm)VC05=LbF2M71cpL%I4IV;4$=kufKH#0Bgv&M~ zjIs@p28&X4F_IxRKf+ocaPR3TfRFqHFYE*b7&Jo@sTYgmA>oM{|EYQ4bJ0L6r4ma( zhhTyZ1GNGr2+*t%Xmt&=Go@Np%7s+!L0t>l4ge}FKr^AR>n5?d8{uZq5%-|}Ey|fh z@N-+BjRx=-2SN`h_Ce_cv{wyW219CbP?+FKL@4PH;%{)x0h(sSWeg-4pd=!M8^D@~ z$^-~KXkiNpNYGLZ@CH4EI#^V}V; z3cB45JbQwiXVBGy#?KNfkyRtyg|0R^KQ9eDrT{81z?-XZtA!NIkR?fwV=Qs2g-+4s z7l9HGL@|zd1}&EXpVA2`-au_Z==psJvq2J|oeiKy1Y#>iNj|9eqX24df`Xm_MHT!& z)byfM=$WygP6+6HCy-)<*`QWTGQ1!Mr`R0uRkp>DO?R-ZzsUIsDGWhpCo5p+N6LH9 z)0HulfO_Y!1Ib`3xv|G7JbXY-DM^o;odtZYJF31!&`Fn|h6!lJ z52#5B?iIj?S3p4zx+*a~E?Gf_A>s@z0&td%XzCJEQc^*eXoD&vl)R6o z4xIc`h^WWV+)A`QXvP5@6$~1~2aiyLhN_Xqu29kdK|fmjKh zk5foY0$r<-Q>mZdY|s^QiJ3XDV;Ko2iPe}d&g;0?Kc+X5`33xRTsBVDPWnec!dhZMjs>!O!pnI1=v}&>~m=9uu%76IW z6Y?d+(EVtzJ!<(yphGsnTceRx6@!j~2Hho$I5!x)st?o<1aJEWnGf0*3R+$aS`&ya zi!oLNUvL8%F49W}-89X>keQp9o~jSw=w%gvuAPO7Wfi2RGcf2uCO4#IX@q~6&t)s2(+__0b+-)E+`&hY|z}kE)Jk-U;#S%rvjutoWX@5lOdfUm7$mc z6e*xpl%O>v;06}x{zmXRs`%pKf=nv~-^A?H;LP;AM9@_M&>{}pmdGzs03XtmQvpiQ zehjG$=?wV{B@CGii3}wSpmkZn0ie^0z^NY6=Ky6Z@aQ1;lt<7$<{VJfl?vKY+w#i<^w(ipDNN+B40imR<^1s6kbaDXSMWt3k8 zDlDP?12I8&xh3a<+e+Tx5sD-*2^#kRIS$u|14s@rCY}fxmq2Qdf~Es;c7#A-ye&>-KorG7#0H12@omz=vhN+%0=yV5$WTS$-BttXfGD8M5d5GPq$#7eO zJsd%6O%d)hgW8dj0XY~O+Vnt}V`yw@VyR~cf}n6KDo%tZImmzr+`R~Opskn;xhY2B znI#!P!Hy`(v6*WEGB-EHlvwqqAoZZ&!lnU>dkhUg`#XajgM%G)13;(OU{eEfL~%x< zk&y|eso2zm;tHhR)QmXwpg<|kNHn$}(tJ?bD9%VUHN+B(Xznr6GhzURH+t9_>6wDW zO&JXJjP(pn^o;e4^o-0vYxevY7>x8x^-T0k7=qz!Q-1<9n=L?L3{hhSRcp@R z!vND~!2mjZ6(Vm5zE3H&1bXriTn#wb8T=7^P>?bBhr-M-1O*90NoEP?2xq7rh9Gal z*ybQN6y$?iEY9FtJ0bR&fX#uj!DfJOU;rIN4Y$h#Y!0+`1<#8@)q|p$0lND-Ge0k| zG%*K!burXV6L8v!Ff}j;1z$1l37*$V%Y}g+{DZrL~udvHV6BqC?6ag48^73IU~oM5{Sn@b{kph8Crnu@dDqW0G9&m z2gzA7Ffb&6mhC&|gKjoV%*-o+`rXhBQXuIW8h{dmiJp-j=xh*hdI!Y^7I{O^y`PCj zre-+gjllB87A834jluG!pjZaapEEEp7=ZQ{7OR4;d1ipD9A{tvZLm_+Gbz?p)iZ(+ z3Lushgh*CRRxJjNH9|-R1_scAbJbFiEOs4wM#bR4Nl@AWZAyck_obkq5ESgF04Ycn zyi-A^>VYOzGE2Zmk1(j_lro?yU|=XJPSnWLgwUy)3=Ap7i5j3wX+d-$l%Ecz3rdsX zvr{X<=kujxfR!<%WN3h-K{Q-C7!t1xszxaaP&TL~3Tj?M@5TV_t5X1PsRTDyL2G_< zDj332i$H4t859&0piJ;EIry~CoC-)^I~bgQ72p|IK|ujU2xbwYsqYAz1_qgnMHr$Z z7_?46!3A_hJXjr^3y}uxTgWU*g(y%^P;dnw?EvON6d(mU!a@eHKe0(cG=cVNz`7(5 zwQ%>r6o9)yPOk2rehQ$2q&?j{ogG75!J-Uc($&ueQ-*=TITdsY1So}|V0#EJz}|9h z&?2|{6QTSM_7b`-A-@$l9U$^5P};!Z$H8Q&J4<~ae2sDlT~H699s0p^LyI2xTxAA^ z2Qb)3OW)BuN-fVxoQ091{ z%zS&W^3bF9{%?->y*V<`9;)76rKqB!Vnw_CteGItV-K?XKg_-Z|Do~n0UDkUptJzP zd&^({ob=ip7#ObaHA>>(0XvET7QYZ41H%V!eTFZ5pekYU2jar~4Wq&Shq)i-FPQsj z>tDkDgt`lsp7#raK_fK0m)c7$V3b|>_7K}Eq0r40Z3~c^2X{Wl2hC9G$ z1_p3Cv4_!C*7Nqv`YvM&qn~nI+*l&eXdksCEi^RrsC~oRH)`RbOYN&4f?R$d>Mxjo z2*n2n#2N5%;ytu{5CGN0HX!$%N(pBHVVFXYE&u=XL(3I@i12~i5PJ{Ywif`W!hRy2;fzTGytcH`S2QHaTyTg?A$Nu|FiMsi##T-mG zmf!hQ^=Utcqu&0Uswjy6;NgVuKR?vJFq(nk0mu*aaM}VCpU>~Y_ZK}<-ek{t@SqZh$_*Qar0cmn zDVywj($@!y9eZfQP$Q9OzGRdAif7uiGyWXy z6nSC8V3Ly^n7+;ao3-}5&_mB{7>xOaCNAG*f9Jv+&RmP9HVnIesa<-$&Hi=Y=NYSi zp0Qy#6TD7DW4pbSNaRi1`z1CERt{?-GPc|6Jjrt~NqB3+u;H1?ycOH+Z(o#o|B>~% z4a2rPnWrzd+y9$l`QgsG`!)<~8K&!M?yzT%Sv7a}{v$RF*N)C8&)i}EVByQBg0}B% z7=m7?9$vY_{%@N64Tt|rZ5XsBI|;nnVV}76Qta4I73@^XD<-?6TkRM?pa~;+YLYBAaaFnqBs>4sUkF*UqtF_?G9h z`OPl--+SiFa_8J*!*GhnkY9JVed3*$^$RuL+c1QRoX^SKZJ!y!`PP2o0~-dx-CAeX z?zRtmd|vDNuWdFA<>nr`?{?d-73+-HQMz8i$8-`=|LZjF3vA@Pj01v8ShW4ffh! z6JPOt(ffxs41uC&W)|$VXWjL*ZGYDz8-{gT*RXHgYv0baZASH-*ES3itcB$t_u9vY zuXr-!!$}*4Y0k1Q4folrPhdLs>oTJagXgBLF-80AFTP#pa)u?@hM{<}-K9AvrT6;V{@3Y^ubaI}$$`Koe2fK|98}GNTTE0l=kNHg-hUng9b|w4mS;KT4 zGp}5?VL1F$de4^q_Us9pTtbo_+AzrRPPhKL-#%DyeurM?3mb+IwxIu72kbRB1|4}e z^Rf+t(Ssi?*$3=()~MdN+5N2ZaGigu1 z*)V)yOVHOnXwUKeN5^O7S2hgyI?a#e9<)DR&ephw_r49ov!=GhbqDR^l^!@B`1jC; zAtxe^@%=&jzV&k)SXchEVVF>TVX6KhdzBRy5})F}+Azdsyz?tKWS=CnCqk?LzYW9Z zr6>PvIAp(4rgnP#`F%DFyN(sF|9Hs$`!0@xQzc()7&moMMA`IQZWm)@k+Uk=-QE|84k`S!wwLF<)& zx# ze#CzLjH-8HO0R7gGT)iqwm53f&}px@ckeG72AiqJ4pttucVF``U+(b-8wQTHmuq(& zwLe^u>UQG$R~v@MFE-BqeboMn`_KO&w;tIr%>8UQ-R78m*fY*s4U#`?7~Z|Co>F_v ze(9{I2F+^@*)Xh^T{L~)G5edR{w6v!|F>aaeZOtN|6}&cWDJ&0ns(5JVZoU*>m82U zFBIW@ZYcZ9hT)O%w_Te)O?xt#fwqfY&EA2Uc!v5c) zDbKk=PS`N4JU#n3$4UFE=MOHi@BCrI@N8`ar`Jh)MfL!HbIU(A4FB}^2X>#d|7tD! zGGfV38wRh~M+?rJv=6gNwYu{0lMTbaz^mVQPuaf@&s}jR;k*q)lJ)9f|5NrRAt~bO z%f8q!a7C1FpK!|lLPO*|X2w4@3?e^#lrEjJPucn9?#kd#HVpB#_EUsU+iwq%n9El2 z#fITnbP{jqY5T=7&#ROhzuPdxNuKPTcG})>51;V#eXnd7Hu|-T-Z*XlxID2{e8(>v zhM&teERj58|M%5LX+O(U6naMZjoUxZ$dqU=&>nj_E zF#po}duQwwgQp*Ei1=c|z@H?jt8mudd6U&7_J`kW7!C;3JW4!k@3hWl{RWlaHVn_J zPtIR_)?Pc})yh!!mo^N!H+WN@p0&3NWxL(Qe%*%QX@0Dc`Z;^Hg?$X3H(%K>Tv%|1 zJ@cHs``>El{cGo(AyBdq2Ro|Vep5|GY@{ZVb~F|aQ&w9_A-;_y*ixp$A;na^t$a| z&fDkfRcyK=`rU>>)tD_s`hxx3wjaWKs~Kz=Ocd7s@VQ_wc)C7J@hF=uLlWaT<^Buy zy&o8*9MU;$8RU5Q6E9w{cYn&yw4{pDmZ2l|*aqQ?_KedO%r$H0wPnb^)-FR zVNkf(^+DsZ{iA#0o5~_T+b|rsBl0c#vVFmWNWRy4+_ntMDi;W@zii)Xu~~ga;|CiC z@6ul9PnYep{s>R8sphd|Fj`>KV|vAYI`j3zds8@V86>apzp1!l|3WEIyz@4XErY|a z%;Y^+?3b(dM}M*8uw{58xc<}sEB4tz0+-!-58E(YFmsvZeAWKwe6Bld;@{XXn6BuL zYQJiKVqs@Cs}_eXL)ZBq#;32^_iAZZWNJLKVc6*YLXH2L{i)+cTsf62whUHJjvEJG zv#%0l=4#LSYs2t(2UGO)YxWywsy9Bp`PhcRT9I|`?Q8bz87=cCALFoPs6S!zOa8ij z=j_i`!gC(mFs$7uQJiw!o^P@E8{-YUwhRaDIXPBdx38X~n7iBQi4B8|UBk||*X?I< zYe~#7Vz6a6|MEk-(GB}#-?NqZ?)dY=u$3zZK+JvZ&|bgj^I zI{nv%VTLlx&5JkfkNH$6zbYwsJY`{qLG0~-dF6-?1<@7fzbp7(0)_n$Tl4|lix z{c_iS&X>Tb{3pD&40&&sov^uQZ>Y=V`K{uP4MW;2&b=-7>{&h6Zi`*cZOhPl%>VY8 zd-iWn->Q)7;In1etSql0df&b~ufD4D2AeIz6itO0vG?tlH_d#OarB1`!+!OD=F9He zSH@->km6^wWtbY7&hq}gJ@>ufi^+VSY#5Si+5SsEuxFpSRpr)dMq38!br&Z&Jh1n7 z>DxI`Q`(kc{@)L_sSoU}0yo6E#Qe5lczo(0L)Qa)w>*!tj*)L|7-W=YhP1!)vyVNr&-CBY#;7S`%dm8F?l-|l_PugkCqD!+*fQiqKll{;$X@P@%F#KQ z0=5k0`kPZ$J+j~TLw1LAv$QS4ERB=?-yhkRHtjKdau>vJ)!*Ur*q&8LVV9(utS!U# zc7;Wg9@}UBb2j9T{b!yq+x!?b=uwdk&VWW(^l{8)~{Q~O{svvW^h%h)n-+v*)D zd}^@f#&i3x zt>&N_n*A61K3>$7mz7l<5|EPZB`TfkCwhZiyEj)QI>`TvH zm|?$J+Ll3eQnuvr7xq!EzPjph0=5hSE+Nv2FYT?O4xP%gm9k~n{?$OX=A}LR1^;`0 zmdo2R{N1`m>&8p_+*(ieOVuK_4D~-!1597pYyYZ^^5hq>Wq1_0W%lG(_Fv=jC6cYb z+b}Sx3rc@}WzSW*MZf6bM;nGe=eh3%zqW79Jayr&A&)Hs>*>O;TVLDjx2@iGT~N-J zp?XhovE&>3urRf+;`O|?47ZdoWY@j1kDke~fK%$P4a4k5EMK0yu`hFy<^8?*by0Y#D^MJ56PHZ!ggCAb!rNFE$LNM=u>NdT;-!^14X>GAUaIm-6jN z&)?hs(a@<7%zJCYV51}*sQC+H3TLuR6#80a} z*xwUq6ZK*JVZ(6EzQ*kJ2m8MpTIcS5|K47?-zCcO{d@bP*KP@KefQpeMyviQy?5{J z?aVZ5m%V*&ucNR|N9OH&`zhi}ZuGx-Z|~dtp@H$ud;4vD6{>}=-`meWs&MSytM~T$ z6=##YUcI+J(UJ6f^UL@4Z%YsNOTTUA4G-hXdj!!hfJ)&2MO;va%7 zo9?~0zxQ`X@$tLw?U|-8UCwp)y?yVluUEY9ytlV!Z2jGJ`@Mb6Ts`R%x8B=dk2-72 zeCxe^|2%6~o15?LPo)L>7vFeqf5huu;EL<-?I%?|^|^iRy*JYW^CZ1&@0tGIK5YJ$&!4Bfx93n5xHBhrW6OK{u!e2f_Zr{Z zvoTt7FxS7gpW%7OLB8g_{lT39!p4>F?R#YGYwgP3+fSS4QR7(r-d_0#8=pR88PASnX&KfH@7gj zX-2)b?=}h(eh~KFe%Zscn7-im_D=S-awY-q?H}E^9d^;@y?wR7UZyn9_x7a%!SbJ6 z-`m&hKfbcm>Ak(-7MuAW?B3hY)j0hp$@;y0+uP>-XUyN*cf{sD(KUH*&lk{}(Pr@8 zo`>hE$2Fbz_A{+an`AZL+k4)eEEuEu-abv|+~#_PxCypZt=?lJD(R zIF4lh7kzKvke^s7B=p{1OJv1K8NT=SbGF(?%5lB7Kl}NZia6VQ`$P7ZHQ1Tn+rK;g zA@SYcclJLlD>v`>{?6V~l-Dxo{X2WrNza2$JbP#Fwtn$rseAA2|Lk-)>v{E^eU^m6 z$B;Ac>^Czr8`vCvXD@HWk@bJ)JNttLE9>TMcxNxJv(ra%`8#`u=kc5J=f1NyUntqT zaPm9*`Rbc@uIqefKRL`@WP1HO`{yn;6;Y+{>=R-f4*bY^XRjISeylLzojtctd-BoH zclPVNN((-Fy|Z_YJapu@{X6^D3=!t{Oy1c`KMR>ROY@z*w-e`i1G#thYA&DWE)aTW zpA)+K-b=Q3_S;JsCTl+Irt5{!EzqRLo zl5-_A@2x#opl;)ggtzu~exHHr1tPq03wQ{O~!&>1J>3Lq9(EV%B(TFJZU; za*5>Z?&}|RgU*x*}MNTQ7_*5%HH!*<(d;+uk2NsR^7FYe`Wuk z)j8&z(JT8C{wt=Gvc9svmV0oQ-u0LEV*h-PeOdm}zN`It-l6iB_TJtr)F-*Uv~T>J zBNZp|(%x$IQYD3lFYG@~UwQoEniuwMw`$FD%3s*a%Ko={;qbzq!92>sj`M}RO6y*Q zv$=~vHiB+i&hKQKeq4xdwJ7xt;hCUA-9e{zx>Gl z4E(z zFL(2eY7gx9sSC1}=H0j7xUor7r2U?K?zd&LE2rJH|HZs>_MX{y>{s>HTs|@Nw!Mzh zzUP6>x9nf*&)_?hbkjZ{%=ov0(G7d{eZigY-d(fjEqL_y#nh|zVU3Bq)+t@F-^Cx2 zBh!D$KA`Y*mdvFK_8kAXFGlVkvj?6!q9oA)QLT(|5| z?G2FpgSR^AaQRB1U&R?#`}SLZW3!0Wz6X+5=;V!q&12Y?R342^pLBHpiHvoY*2f=$ z_vYe`)eV%~jv)D7>@<#9f%&crWWq;PXK5gCt@H~}*$=z|HGqAaQXJ0XR|gJud!dekWVvh_A8M51A}jtQ1c(y z`^}k9uj4g$|Hi!yA2(UN0m(OPd{740UmE3_TFS9+zgw@-`RoaALGlXE!b{-#b$_fW zGhDF5{+8~sFvt1tK=KbZCbUE4FW6sAkK~h=U9*2f%=h!tlfd)b3gStnaQOiJNdDA? zv-hj1UR>OJ11#_Gt+oSd{{fJBkf;Qcp!p;)15QBo+vCu`A8Nln4*T~*-M=4)`}afr zZ;!+O`=Q}yk0bo{L&JYRj_}_Pjlcak;%`4R{`cdE|NYSPV~->K?1!d5dmQO+KQ#T? z<4C{zq3PcqNBZ9n%|G@y^3Q%~{iVd1G;{YdQkp{rmqj^ z_Jia>`5!Ki=6;YoDF4Id(fkjR2jzdbJX-jHXG%Mmp>qR zWc%%*?uV6MAbDi>+e7^iEB`?9$o@w!KSA=y;b#xc|FH5GB##{a=;b#^UI8io?4j`w zEC0drNbzqE&Hu3a11yh}e$eY5usl-wvxlZ%Sp5Z-@8pex#?J?PX!?iMe;|3}^luN% zKd|}}B#)eb(Cc52JaYa+ufIX^$oUto{AXbBEoJ80U%l2Qp`qMvmJS1h@V9i+pi4bA zC9R^DH$*ZpB%XMd==VLr=EUq$3DiJDWup zFB`pi%)oGCPqJKI;9{GAXrT*?!i)^n79Z3?jxMpeue);HEGI^WZT3M)!GSAnssd|m zb`&u(><<>U@9FHeDLtM4I(ji9L%hC~-S;=MY*y{eFl@iV$k2OunTun7m(8`Fqz-E~ zCWa|1Ry_XOH_gT(sWh+3f{EeNbNLzC?hQ6~YaXS#W-~DuuI9{r_`S*I;XV1UbLTNJ zZ06!AYBKM(;bwlato0HT!?x8Md`o?sZA7;la{XgtX7KuPIo9%Szs-NKluaBq%nX{f z5@&k!7uuX!X=T2=keQ)7uDe+C(sG+`^PigBUe3&r>!Y>YV}7fRl8KPdvU|)7tdr)d zy6sqDqp9~@kxPt)L1@jPgty7gHoBdO2^@Yb43*3aZ#_JdXLG^k;MPTLEDRggSM5GG zq0B~I%k1i{{VWWeS{k#@G%v7OrZcx}(@z!#_UEe(Zs1&M)961pOxuK&q3`-%30Bj| zHn-Hbnz`q*GH`~-JbIWs!A3L0<1YJZRtD1pDcaABrr3BWt`*IH&dR{m^I>L-&;py5 zP^%4fDr^jFcZwO>Kg_gIQ3`FcNMd6s3|+wM+dI?d47mNn#?a<|u5G9GQX4&~RGUrr z*%(&y<||B6SZq@q|K>%hJUhd(<>Gxxi3@B_q&tPZj%R0Rn=j|K#$}OB8{_L@k%jCG zp*h!9KYX2PljL{a?DHdbh70NcchCPf%|_^)&&*C$4u<=TCqzW#r`o)kOkiBYxN#2m`SusNzYuO+j^X8+&H(Cm3!4DH6JCzVU}*sQ%AHFN$8E{18cx_8o4 z7TNp~_203`n44i8>(q^Uf(vcj;(mWBtLJ7odpDlz`u`G}O9#Ct$erP4*vzxuU|n9Z z&77dtyR#*E7}i8`UhIF+V&i==T<>E#4?~9Jq#T{>beqfx@9!vV=V919^S&8p!DO52 zPIfO{c3y_%?6%8V7nj(yFZq2(B7&D;&7^}@1ETtDT8(ZqKU>MmFloA%O!4j+HgU77 zji>+NWw_j7mB6yC#O9lQ_jA1fJ_a>U&PnR|6K!rzSh8sS58`euJxkf%9Jh}2iLslvH*UDxo0Q(T6dP)+?X)0@!)cP&{=Va!XD0owF9Aa6S&>T zzyPB;pnMoT3EVDaV1UtbP(F-a2X5CgFu-UhC?7`O0Jn=77+`b}lnqt`(BFggj! zhtU#HK8(Kb9AY1g?t=1Rv>TKUqnV(57<~xbPhnty(M?c3jCO+ZVKf_*52H^#h1d_H z`=ERn9R}sYXgMe!Mn41hqZk-q^ady&Mpr@kFxm~uhtVQXK8$|!7~&ony$Q;P(M?c3 zj1GeGVYCXA52L?5f|v)RPeJ)GdLEPyqf4NC80`h+!)OI4A4dNG_cIw7VDtqjA4ac$ z@?mrnlnFk9%7@V{P(F-Kg7RUs6O<356`*_= z%>d=YX?Vc^x($Bgihcd|!hUrd)lc5DVPL2bn_V^E{`zxi^_yBMwhRo1ly9ajw?C*N zqrc*Vye)%+;6VeP_4W>qyMo(R7}_!%NS-b3y4n6hK;mU)CtF*F1lFFQ_uK4UvJ3*P z!c}Y;4xF;fox00@{ShXg%49oRh6h>&?5g|h@4UNa?`&;g%g}J8X5RG!_D?)3T9mff z*fJy}@LZmG$o?kBok|8@TU&;P{Gx9EBlcTnYdbIbW^2o^;Ca~w#$)zRPu#z9J;>0O zAs|qUbHj1_#ca{GN^cD;@^w`FLU zb^3en8T&lBzOPR2>}(knva8y)&e={k*;NZ)M5z^5(V-8>(Nj zEWKdw!jt)Iih+SG!;I7GWYR9$cW1HlFv#fIGE}_(-KcrVe&(N*wN>lIZ5b3E?s@d` zlKmmsULhU>6o{sD1W?ZFYoY4RH4(%mZ9PH>OV7Y*$XaikDv2G(3YV?q-MIyZTs75TkTD< zY-|}M)aRXjb=y90cKhwTE;U;Qj@{R`Pr74&D5w4Z{C@(r3=M}T@R{AUzsBzpaa+g4 zmf^rdmQ`2o+W(dMpc{C@$d;j_BI#1eJ^MZ3Q{KAn{Aa_kK`)12;J*Eh&*pQ>&*|7Q z%$POj#G3o|Imh_~cZ2?DyGkU?_QHVas4) z`aEd=1N+PEm*;eDm9%B>(VO?0^P&B7ZadBNTSB%BS9V-B4t;2U`0|DoGBa##8J2MG z+BWy0ea50Ctr6+kwhTY=)9*ifXuqm?XXCC2DO-j;`)+J7cw}Fl=8?ztN641J=A_)b z@<;ZSvrUVQgUoChWLkb|?tf(eX8Ywti>|*m3_SiO;p~s?O+F-ry_gM3zf)H<_&m1X zYqI9gWCvMWhBHs^I!t)_G?cGUAg)~$Cg3GB%-zf~n& zwPBD@S=IFJsr^2;TRav_O12DW>)8wP%b|qw4Ko40^uKEhtLdX zAngK&iBLKmLNh?y2?w4++64{JcEW*j2>*a1l;(lZ4$yYOfhCZ3!GS^u-Cz%)8KCV1 zhr7^r!CDC2Pz$A@?F0t}C?DERXgCUO7tDmxxe)q*Ba{|{&<)Ub!hu7Oc0t2T2+dFk zp%1{?35rnuPiXyr8A2a`wG-MQ{Dwp*Z3CehVC{r2kb2(%+D>p-3*|%G2?x?4`~$8K z+Cc?EGcZHxN055I;UI)|SO}pT8X+`85`;bgYbU5c_zkQO+Tl5*-fuVwp&8ad=mW5J zLJ@@T5DK9gETOb4gmz$r(6DyGfm4ur|G;_(?JyNWGgL$90|^kC!5u<3=t5`)SUcf2 zq@H(xwi6moLirmZG{Z~??a&CJ8!{m@1FW534duhy3A_+~1GJss@BmuR!`ca3AbbaC zJK;bFlwS;`&whRoDrYA`)w7+I{<$BB{Ut0zS`2~!-m)RHGwk(O8X=BUK5R<&wZ;d_YwNra1 zHG0`HG#EE@zuI8`R9b(^ba6jh28NYMA69O*cZm|!doO5U%fRr%KO=RU{d)VAy`Ne@ z{8-~Xk~{6||1rO9N>H(7a5!*%=ds=P5o)PUFSfhdGBn(ISX8vnK4y`z@=84~TZRLC zP1k!4*t0Qh5j^T_YRhoI*sA}|L3^K)+|@miuC@#g>(#5Y58H24O8vWVHM=bXLy1pV z*%AAK{@E(?5Bu6OBuq&0IB?WH!z{k;%25qlhJ=duOW2Rw|9B=F`KrwyRR8^o^f_T) z?zJ)Sa*VSr!+|wfp?xRq1>S{S?2s|BWq6>?Y;gXRed@&h_l(W;Y#9>P2^;aAv48uh zd*dEaZ(D|hYuA2yp0(H1U3e&bE69JkT_>B**?*GYz4%qj#g^g0w8t)c&fA-Zq-p%z z>~72Oz>M=s#RYq&NFk1IA1!PdCTvnYCv?&N=`L4(nN}NHh7XU+xz}8@UtGt+Eim2Q zmSMxC2}_+X*~eaa`J!~TjV(jKQeq{^3#TaVamRU!prvezC3oiDCT9$ zup!>1h3$%c?w{Y@3un98GF*^YcxBcV`=w`?ip|f0!b|W`wC+`VZ@Uiulx8bihK8o} z$NR6^N7cTvjVblCWoRg#QWtp5{z$r&l17)GErY=ON$QWT*;`Ji<5TfBv}ItpSbwAN zy8Z3E%ZBT_RBagwG_$)IZrGRIJk_&9y6%mO-F`ukONadxi77GKWCz8G*cA36Xc~|23YTws(uO zErY?tZn1}V>>o+SvMye$Z_6N{tS~eEu02cXi)jIGC2ScE{O5i2=C1v!uD3clkDP58 zKA5h3U3ky_;ncndoMMc&3=S@rH-Ecl|4T$nH+r*!Eki-cJd>*X_6iekHWuW8(qqrO zoWJ+&=VrF)xu*ZJVfav4x6=NB{kv}e=R$GnwhS5}y(J9~>~C|OtFZ2|wq;;AA0T${ zf&J1T!Pi0JZng{!8`U@%AKFJ;V(&V+*w2>Xh1cCP4iD`QUaFJ4bxPir!K1`Bx#6L` zW7Pw8@lYjOh8cZ%PWvC)hsLw!{LeDCWmvE|_Wj?7_JubXTkko!*fIzxoXD_wWM8#Z zb7BdPx-COP)B@$INA}A^HW|Fy;A6|+U@9}W|JMHC8UFrXS={zAY&dK}`@jCRWw@Tlx0gZTez>LL&HeTE(g(66?DjGkuyiS{ys^LeM(=rr zJ)V0R4g~JcGq|yT8mmX1+6mXa3=`^Et#(}B?`rNc|N9ryy$lQfm@jj@zMog(!~8F* z4tp6KRz072`r3XihUHhIv>o>{TnK*gHRRg<Uh|9`9a; z4-+|Lc3s+Uw9Gb#eX99h27?7_KY3o-?>}wpKh6rRy$lQ!avW}6+;4PXx!rX)*S!oI z{4bkkUfjQ=rPt(GpyFPJ3-(r=zc1`x>deWoW{vh$eZrEm!ZMsk!$VA{Y+;=)01T!_A)H+;9R@% z#QxBzPbs@PefBaiC@oRAdVIg_QjQBQS&DlZ80rshV>!P6-LzUIvEj|7(+v?mw(Fh25OPcQ1p&|E8r=kL>?4uRwV6Uc0>v3p7=?9Xq@~ zX8*LN4VRquGCa7HRq^xCeq}R{Lmh$Edl?c+m_F+s+HV^haOkmz!Cr<2>yVzrgZnQ^ zZ(DqAhwEMj2DL>`rybb;Em2o_#y#)73=Ls1+;jHt4@+9Qz1hTfFN4FoNmDrX?Qg5U z`lmD0VJ`zi#M50Bd-hL1*v>3#YPy%f;oGn7H@o(`@V#1nS{M|*nVy^CcJ6n-K6BGA zb@ROp4GSD6U)jDtbk+AgKi>K6Wnh@R%EoWo{!?~x%$HUf?qz6rb;(uxN_5)8_pWkJGI@rJeRNI7FQ<-LrB3^yYcOn`(XcGBn)zvBGM@evOdzFBJ_% z_cAo}f9cCxyMIq?S)h%k#a;%6jO+XISM681UUNH51{9x72me+q-)|JW{!^E^$zBGA zP$j+kCHv3j9-Zvwq_vl!!EC8#)587U!S~~NrCs(iFetn;m7crb_tO^L?ed`XRApG} zG=2Z%4b0vj8Qk|WFbMEoN}jmi{;_}B&KGWb85lnJ$m_K4FMi4&VdAO1mw}-o_XubC zewCMJ!*(Tu!WUHMqhm<_z~MinAK>s0O8rnnRD18-5UxCt>A#}qfD18x1Ux3o* zq4YT@eHKcefzqd;^eHHP5=x(d(#N6nF(`c$N*{sJhoST#D18t@GYCN9{{TOPZh+Db zP?`ZsAK-(^Lum&n%>bnj@IvLGv;&l8fYJwepz=`K0ZKDK=>yzQc_{4wr5T{~0WPRK zly-p93{d(2CsZCvJ3wg$D1CqfDi5U{pfm%NKEMu@htdvEngL25V1vp-X$L6H0HqJG zLgk^f1C(Zf(g#?e@=)3VN;5#|1I$o)DD42H8KCq5Ca64=c7W0hQ2GEPR31t@Kxqaj zeE_;oya7r(KxxpvV)POL!EdP13{bQWO*Ht%$*Bn1*Ze@+*u-A;i_wFgKu%i*h64%n z_u1Q_SdxL?U7Fc^)C#>PRDWnU6YJr9YOtz*L%Mu*vklQznZ4(Y0JRy;Me?& zBzu3O3F00J#>a-u zOZCQE*)lLVu*@;Yu#eu;Y?PVmZ_B{2;qUfEnfCW&42u@9g50~H@4iU3eM`gQmUD}B zLH^w?)ShF%=gH4A9nluH3=9`41Ape)+ijGq3AX^bujA_M?0ow}c0z@BH-Y*`SDyaJ zE3mIpV9c%N1=(M+PE(`MULaTJYw$l8PfUyZlKUPtZ2tfo64`!{SXIaXr-H1*;Zzdi?B28Mv( z6ZNI`aRv7ICX+z@6ON;bmSy(u1jApfvu3z;NY|nRm7QrMaJ8xpIQcpK@p)OO1W`KI0C<=b-epWvb7< z8vFe};shdeLGeGs=0SO_y@AYxd5W!|@L#aK(y-3HM(N1<-+`d?q{1%wy3T&r=j_fj zc98p8wD&Ekx1S@vE2%jYl>R16GSP3a*Xs3-KW(FB%fMhW>sm>J{Z+Ozy%pS`^ucGe z@nVC0dF1&pRYz@G28KUd`t%y@E9A9{%@2XX*FrhJyV3q&>gD%y(?I+WH9;>L?Y&ux z@9pRW@k0(-xHj3(u@w;6`3p22IN`J6q9*&UubLdw@4MSFFkBPh``=`*?8bMaofni| z!leF2H`~`goPNbw_?ry_gO$Mh9nJRHKf~UC`3`c=woi{mTkL;*68~{E5)^(v4&E+m zu@@=a|J+0!)PK;cyLP_Ce&K65+X=zuwhRm%>Q{7H?ag=nSP`*6*_MI9WZ#v}R(t1D zce39c0?8*i-FVh&f8^5|SJ?tkcsOmj=iFwmzS^ilG8=TlF_XxP1#R|cc7%t{mI9?8 z^!CD;iKo_CX8j4Qg*Z=)3f_bpM6Qc~|=Xcq3t1m6&R8#<|kG$Ag zvR}x@qGo{*NPkNCS ztr~k77;c#F6fNH0GtG>3aVe;MqcLm!&Z7OLWqNWCU3K;{FqHHzi7ndy%%}TsS)>16 z28KO?GZ>5ZFFrBrI(LE8UIvC2YF$eU_d7GRDw+NU`RB`-D$m0G`Oh!kea-<=e}y^g zZNYw)75u+fd;;-nPKQq{*w4ayV%-6KQ2X(LvWscKe*gcg&+8k5_#Y(WHs$XZY2xwR zw+_^vPT)2CmA9WoWRc58XOR0ilo*}z_IH*_a>Xfv_!q*@b>{BB`*WYJ?pcGq3=A{Y z&A6Dezr}CM>rVxs_FjNmf@IGAN#7d;56$!fxp$vRV)p)(n>&{KZ4lhcz+h4FY(>`o zP0@TTyt=j^e$dhnnfoid*43?&1BLGipG1qy{n7n5*HpQI+JhI;r5ZB!XWM+f>|g5b}M1_qA*&HU;6bJMRFs;vgK_Zg~9!qfKuo4d_Z_%EnEDX$9kx-CKWZ(x(S z7PEhEH|ydvi$L-5;Hz!yO(F%*@N2C%3fb0_lu};u-?%Hxi6qEsySkR?!Ox* zt)8I%goAmiQ22hCQ~rM!EYjc0z_39!YhmdA*;_nk=KKYjH^DK}AY^}Icp>+yQjmKV zEK1rRwEwwSP3{XTPD4cKPqO*mBO~#%EA`G^9n`@Y%np zs^^B2IVgS?@X3Dj++Sd#q}u%xl>QU+uL-;FzijoD>u@;8{Dc$DhA#W{cd`oiDuU!4 zZmRk_?%%o7@sp(zNWS54#Z8<2rCP7dlu|D2VPI%D)F)!TpSgm={mFf>f7bW<8}8pU z$6w>TwcTC@h6cgcBI^5FErRaem;&+-gSDin`2K%d-A1vO#rGoWeK^a(UGzI>zV?7C zgm!R&&-HtCWLNKhtLda5c+@$gmzGd&<7MDw1WbKJ|G978)P9g zgA9ajkb=+*k`VfUID~c(gU|;=Ahd%pggzh$p&JAsGy^|`Zs3K`3_K9}02hRI;Dpcz z*deq78-!+Hh0qPm5SoDrLN_o#XomkHkofoup&kA}=mWnXw8Kvb&F}+4H++N83|}F1 z!)FNX@Cia6_yD0D-b3gEZy~h98wkzt8bUX`gwPBxAauhs2<`9`LLYbxp&cGU=mQTR zbi;iJ&2SGwH{5~H47VY4!%Yb7a05agxCWseu0rSommze+B?!%M5kfbdhtLe?AoPJV z5Zd81gg$T*LOYy*&c0uTd z9T1vfJA`i73ZWUcKrZ$gwO|OKxhUi?Jym}ZUq(1}N>&0O2>(L+Ar_5Sjr>JJdq>2WlWR1C(~ChVUDzAoPJs z2+aVc9V#IFhH?mfpbSDgK(C{gl2%!4#g0DLlJ~NPza$JptM5)gnu9(LNh>V zhdcbnv zk|F#9Nf4R=N;@P%_zejV`anE{W`NQTaS(n(EQDr&(he~YenT{bJ`e?=8KAU7B!u4( z0ih3sLudvl-4F)h9|(oe3{cu31j26!hR_FsAT$G%b_j&<4+KDH1}N>|58*fXLFfa% z5Sjr>JNQ8O4c-v?fER>zfYJ?~5dHxV2+aVcVf8<}L4d|Mz`(G>F7oAUh7U@h{tm+e zMuro1dUv+vgg*oIk3js3c3HQ(S0=H8`ZEj+%naAkhFkid1|t6kEB@As$3g8Huv zGq@SP+b!ZKNqM;gEU&`b@WbxP--njxZh-oy3}^Tle%U$gQSjR_3oPFw(D2*tP}Ur^ z=D%S3M1&6fv3tPMa8_?IsQ=2qAmZ@Pjx`}?a?uuW_;H9G_;07!_x?pru_37cFT%5c z!G84_i-YII*+Be%JPwTZT>=W%1arauSL0(~vhUpyTGA8*hbF8G-aa5p-a&FI<=N@LMjZzstZR{D8$?afx1*`A>b2e2T~dR{Ki= zJ_>vL!1}Ly$@&;sUUJ$D|!N?N>YRnj?1x9KS`f3|#iNzZN%MehunxGHA&k;If}2?Epx%?=ZYo+zrA|E2iE#iUqSA>r+t9mKJBZ?p^eF4|NRzo z5U?){XTF$M4eDPr+!vo9VE;bdSz%r#X#9lXo5TkJdkMjao8FVa=8H%L2->?_Z{&Ls zqy%!0x%37>`vn%~8jWpyK>S1*0U>+dl|^osIzat>hB(;-A^Z37tf436!SOXk?tqZ} zA?x~(u7_qI^%vw7gzc})5E?F(mwYbi5>)1#8o1!4PzSv$F!V!-?Y zWdjlW8c*{mw3?^w z2Ts4sH5Q24HyBo}-Q@sQFRaBNX5T$yU&Shg56&lxIoT+$v+cS zhjrlct1Os7-d<}hS7G@taQdzbO^~L&PYMge$U&6%B3n|whRmnJSG8(_MhX0 zIG@Xb(pQ6?X@jDDZ1>y!(gr276^iybUCMHI8BIaq>1BRH(OzfOGPyky!TGz? zfW#z{<3=9p6EG?AmzsOfysN4t|?`ybhm7!#xiQ$-D+^wDs@%|ON8*w*k}a}>CI7V=0?vCpXK<~U*tj<0mj2`csv zU)@>h_Zbu(4EwzfsMxofq%Pn-1kUddykDr;|9@QFVzUf1e%)Z}E1_y%8o4x}{1m8u zY?$Zgp=!UoQCL^&HmLk*U<#;EwQsK6C-3%24;22ofeTdaV=Q@&Sc~d_!gqSm1yy^a zLzYp0Qo!klC4@oEzAn)_Zw}H zExa|y5M+?nT7bZ#_4_FBrKVb;GkuSP`C&RgSs@9Tmap>^D48 zD&}-r1Z1D)op_P`y}Q?ONB;h?hk=3N;vIdF{hufIE!L5@0?Ak0{V%-#qTbc!xI>`v zC%k z;ivMu|7$_xNesD9?hEdpru&BLK_fW)8K15f+`lnw=KH&O;P9FJv|ez3V5mUcw-T^@ zD$o1{_X`SdXFJ^T2^1cip2-XDKfLel{me69{SMFH3+(q^W$~`Y+6ZL-ndkcj_B$NZ z_{-u54$rU`{Q~=^F1uzXB@Rvx8(-uL>_5x)YR!TWaC}+5v=`W~R{HGM#*KO){rg|? z3+!jEbNf?l1yFvFia^*s9xcoh~|%?I;e z+?D6qKY#XGBkxYI`V05=bMFsKICAL*E7<*8?#FZQ7hSG!g*yr~{>U)x!GEs({3{=r zF-`%eU#*Axx%TgFTmNa<2GDpk1KXo`uKgS##-|p20gW#*Jb3h7qKweKGIsS@t(xSXDhW3G9ES_xG9iFZiV5y0#3QewMwjXWsAmvfN@~5Lmt2 z2YKfG?myzQHtA@B^gsHrpK1R(?W{LWx4_|F^D&-jKX*bl>yt)s`WE{1pK-scdeL$o zC$M{$eVWg>pL0*hl$}yw|FeCLXWTC+bnF+CJ2<|leg4m||FN=M=KeFvAoCTz%xBnd z-E}YS#&&T2TJgo6VZWJ1g4fR};Pe{vYXARz+dQ21%88nS)F-_D|8HNyz1O?+48i4F z%p3cE`vO;N;^5m4c3;Tb`oH_`+Jw10_zeyZmv{UB?DIXpDcdR(oWGyEv;VVCM0B2x z`er7Oc_-f2|K7JFW9s#@q2Tnh;=}%5`$A_fI(A|PIDfQ!{Qq-b$%J-hhZkV`BR<*x z+$ZsQ>eL12!Rb@wbN!EfZ4Ww=uYCra|K{`l@B1q6U*y%F0S@0aU;cmFcbvV1`+5jC zelxz>f7|y`(oycTCOCW^eBJ+bU)9qM$>&#q$Csyk`~PKMb=%L2y6vF(76zN|_Fwk7 zw<$N+3xeJE;Cub&eamWf%Wu8}^BaEb|Fn;%d}GVOEO7iu{QUoMU*MC9|Lk($@#z&m z?LY4Owzoq|fD1G~z+m#L{=>c%PhNI&{sPVSFcf^Pf4{Hp&c4t{1#oyte5-%A@56DfV z&JPBE{y*BMD!FN{(Q&Z>$3w-~ZS4&0J^sWbpxT`v36v|K)v*=YH+BJZJ`z zZ}|8B!oF;A8?I*+;P_zp_y6?1PtDGSAwl5ssNvuLA3{{#|&=v}K1Y z?UJ{2YqE^6=Z9haYWL*M?ZEE~`rLEWp`|W3U%+gGpZIcMz$Na%QmLod2 zc|zfy*802`qRt=e^G=pa$}FC+2ePmEgMI3j(j||Idu{H0yQ`U9@WEcqX5*v#6MObR z_BnsB|KGC7dtY0kP4nktr|jl^us2OKl&J8S2;KMm!JaGej+frD88+YDi?k=5`Cwn@ z|48hP+yv-8=nwWm)koFEtsHEmFaKMg_WOf<^Rk*$Gy9)R~rf3%-x_HovJ{mFX*`<9-V5%tlYbM?EOSq@D$)!=>8 zAMH!KuJ3f5(75MSZ;!>#u8;QF{L?Qg3ryTo3f@Qk(f&z>XSGjspUssRmX-&*Kiac@ z(=%-gp1EfgcwhBLdz+L+40(+6Y-Df#Q3`nb(Y||oHPejy6Zb&&S%0#h(&E)vdc4$S`y$rk+v+yVw6R~d$^Ts7C;KH|zU&J<*ah8({mK63 zONW`pdz)+;lfOQ^-}1@6me;<=%4gOd$iD1P_Mt*+*S`>Ivzc)7)`j{VpX?V)SzNor z<+um3Py3Vo7025l?o1PG!u0J{u7C5%zFGOIEB~oN=)UdG_D`A&FJ*mcvoXBk^QS=Z zvwaPVMBno{UC@2ppY4}MnzbmZPqz6ue^UI>u+R1yZhkTEV!HR72Jh?sZ2$0;)Baah z)App@V9j0E^Vxn6%S@Zzsq^-H2k-O#Z2#zM;x)4;rF)*P5I(GX__O_C%~3g)m`@p~0S4=XH$^0C*XZnhOn_msT z*w3hpe|-0N;~vPq@Gth~8{V^2&GxVfXOMmCnDxcJtSjZB_mnx%ed1s2KO{}qw)kMN z&ArRlKWHufVjs5e;^(B(Goky&zt}%=db57IbFB^U-M=$8-T7i~T#>cs_L~_t%fb7| zzt}$xkG#aWdCDHAwDXH@iG8(yH+gSnYgzxECh)%Uul8$fHp=d+YS_a+DfZNoz_0eR zp1yrQC!=={WS{w0`!$ng8FhBgwE25{`W2zxulCPnXGK4p+63Kq{?&esyBn*I(KMUw zZjoJTC%@V+YRhbt_O^lUL;q?Y`>es>=d#&0oOjB0K4kr7?|v_Aw$r1@ds;(5`_jMJ zPut>9cd&W#9)-haOx&Em*&B7vEw%CJ*`o^Hr~b{}SSGTabKC4a#VdWTgf@P&PqF*% zvd$vG26Ar8H~Y|(O~NL3XYbj6i@Q|lz&Cpy?*Hjwoke@}r9k`GzuB+qu*tC6m%XQD zO5o4h|KIGt@wKV)+jQ(H0q<-7Za=fCqs=|9aZgF(Qjb~o-|elhrsmvSF=5Y1@ILqN z_Lp-F!y{ED?6Hr%=bzc|-CjqGiEBsN44ZcFzW49;`ZHPHm5a9QF*<0#`1as;`>VcU z7kz~0L-)adx38-d-Nh7JU?U`D7oyJc!(MB8Zcpmeepl+uN#S8k@cCoXYFxi zH!1fY_P@_ME^fKjvFGh1&_4Me_Pf4anf>F~{5><%x^5ot`eARiwdV7oDKnw_=6~3$ zdAEgqd^ydg$7QRS(fJ?ts~*2Jio05{2eOa;hkefU$BTr|wAvi|Kao3Hw3MJZSc!}wb`B87=vjxvEY6Azw946_TJ9ZpSvewvw`ru%3t=!l8>IM zJ3Sk^Pyd&_;ot9kCx1<_srr0*fA-;D_7`%VpE8-$zX!5!|CfE^nOKvv3#Z$pZR^!! z=lgB1@}$A%>%G}~euMY%|F&np-eM5fkhI4t@@a2T}at^^i`wiQcw0$`~*Jk>zY;$eZ|Mt#(y!)3foD4me z;J!W+5|FVD0Q**9nH-|GMNoTh9Sgm%x}b7KPNoPz)Ma+8;S4)$ob`B>PmedzLk z`{$LZ(@I_^*`OV7#!!-&2Wkf3#J>b|I=-Y#v&W^t_RzD?$7)T``>C#CsC!T=%4eIK zYY)E0O7I|t{Nqw>SF>B;_AqD_EX{d-1_otUX{#- z^94(zZzZp7wV%zcd?%uGr+tU{X*CYv4*OQmKV>JiuG#P6*j?UQQ);hRsM{g0(O*KU@dve$5n-<-N=y8W*RBj>>LH|#lY_HTXM(Qp4srkpWk<01P#{jksB z{T=qo%QX)0n;*CT@N>I@&zA=Kml@8AtL|^JPnPJ^*tVt8{=;rv@xIgB>>~_AAABu6 zX)p4^KJxNcJ-B-Vj^1Ntc3J{=2h6>8!6K87_U(p?Uocz6`Q`F9IRCHumOBBK8|`;* zsJanneARyG3$Albn^)Vb8H!Xo?z(G#MMFFOW6)CjT&Yiyt3@x^Ppv(q)betH{i7U^-%U(@4bRI z?e|5bZ9V>Et9{wuMUy8TxNJW~T>X~7%uV*%_nD3NbzX#?k95mEU8HB5es=*pe9fJC zW=MWJ3U>$0y^K$p&+vbQ-Al&6;QC^Ti}kjmAky58ub)MH@bP zfX^R^ z$%N})1b9B#FYfbOAQke({_>eDi+N@r?eC^(DJgFKVqaIjQc0ZSlYO4of}no!ul9xv ztL~aBezLE*;_kv&^VMFqevX{r- zJC=cgADOQOk6-B2cmET1_!ZLk&!qUTeQzRfI?qBehlqlmzCsTe9PYZ@SBak^>2NNu zHZzZb*`cXhv)T8LjKj4FS0p8SSRDd1o@g;Rzp{U(&?mSfw+b4*3=TV7jUTH$y#noz z!CcG0kQO;DHFBCF+i{W#1s&uSES8_!?c~4~y@54)gQx?f{B?JLl)utYb1}+Ws5ru%FxKTA9{xXH z8N>aNAMu^%)eZ%CJP3BGUiiF=YyZpnPn3J2R`1tS3f2|n{bm<9MNDy1(L(z@zN)*o zUVgZ*&-&->+s9k>b0=?MO))-ew|woacPf@8_S2iZDtB(*zHj<>(Fb20WA}%7Nj>=; zyVx#p(V-`+7kSzEtu=3m}oR@^|e^PPt zzWlZ+b`e!&*V!bT?HL$eX}#Eea)0708r@c1y;z5l0`-*6yTUW~gsYUyLnWk)fbNQj2(F!T8SA}!z-_N}C zSL*J`ed~Ai+9w-V?tgV`zQHxoEp~-0&-U$V&bFVvY{3-4b#wP^PLHvxzTm&#&aQt} z>E1TGCENEc*(hgg&v4+p<~h%!`y(cc?l-OmkDq|uoAXY-{X-h2d&8E_sEx3cg1eXT zPRHCsEbMUiYHO7gd$GRV*Y4U9A*?lLe`QxjsM_M|cGo@{zx(1b%f4gAofVdy`}ghG zdS#W{_oDp^-!n~Y>R(}ZUUSbajiz+_9qT(Mg?*W@@9h@(lEt6h_9v&+uiU?-($4;W z=c$6*X7&sV7OfC|cVPcR??txryTRj`VE1O`oE7}bi0R(x-l9^B*@AHQ8k9Nd?ssQ| zyZ3~G#m*}!kM>FI&e`3wddmJuzP%mYVHfOb_=^)}Z=7K-_f{=a=6zC;N>3*pu;0JuMa05&R(W>6^j|#HpKD~# zu)yqEz5CAndoCS1^fOKvG+qRDukyyfpVGF%+Ea-1kaAk;NUsYk+`aj>8*X2!`fUf+ z#K6FyJI!g{(sft&z5h|EIzzd8|4q3r4M&j^c1L?9H(1S@ZXf*UzW(K!rTg-Y)52HP zBrsoRS%Z99ZRyGv#CFO@#B(xoGdZozA{nx`!xF;ximA*yvh3-C+7#ePzu~{ ze&Ui)Kqcvn=?zB?}$Je}*Qy#HqO13>eW5SRQuv8eSyJf?dgvIue?-*@f)tZ#7lB4i+}dNr48tQv>x zAosCOgQpvaECWN*#0N(<<@Va8i!$!X3QNS0kC|F2?{_QQj{CjIzu7_SH+vlQn;o=%v&T`t*+c6$xO;KcZ_xGw+`Ty35BAXZgFTM+ zgB`T}V2`8yUp(XZASSXLiu`nLUp7nH{u! zW{;zNW)E$j!OI6+?K5cm7kXbQ0|SosFHHUbj`r^XX#4j7j`r^XX#4j7j`lCSeub95 zINQI_{sqiExcV0dp#6*eIQka{p#6*eIQka{p#6&jIQkd+q5TVJe+YN~0@{Cr`wvI| zZ9laCwjW3TZU1FR|7|~x{@Z?N|7|~x{@Z?N{|(w7`e2W%{|4sKi-d{ zf4m>sKi-d{f4m>sKi-d{f4m>sKZb_y2YX!oW9aw;+`Tx)ANE7XANJ!If7lNlf7p*> z{9!+I{9!+i@rV7;@dtSL;u?Q|j_<(Ti(`Ce|8B_m&VC%@JNu#IJNt2r@9c+;@9f7h zzO&yJGQI;3UtHrm(D5_4dvT1P?T3z^?Z+{GwjVlvwjam%*?#Ew*?t`3XZxY!XYlaF zHGT#ipM<*?$N1!a==kJ*9OIMwq2rVLag0yyhmKF~$1y&+A38n>cQ3B-Nl1BT0l#mX zJx0V>ZP`Q{30=^A+du6g{DpfUe9i+UM+fABWKl2mb%B2g@K4 zkoN7-{l;LhdH?p@+uL$)FWdieQMcZ%7t`#)!p58Jj~B`uFO-=N9dABr@BikA-z|Wen*#&G^}R+( z96S(tjC;S$A>z2h2O`fv(Em{X!u*q=~$LDTEtyB{7Bofzo{7T!?$11z5Z zANUW6a|8{Ep#xAA5FR2vA$$X9Ib>k-uSq-AT{PSQMl&#g(}_Kdwz8hLXV!NaTNwS6 z`ir`_9k656)%%PB5B*;Q>fVJ)E`xx&N8HLHhB@;h#b4S0O$IDMH2&Q5Xqx zFN}tX!)Vw&_b?hB{`HJtB_H5)=uuwI8J>sih2<3f|Nnn}e`)Pi-=m>N_b&%0ysP%X zRc$k7&OB)^1J?Hdnhp{1192n5zhGrZ=Vn0U|JTE4!}PA_^*2K8_b*_%&XK*wzT9tD z{_0K5_I*d2{_ov?&K{EQ!RI%ELym!g0kpqnKg``w^C9I21I%Br{QUr$iXK4a1t7{9 zen7<;7{J~NJqq>5;LA_Ng+(P$M}w*66DM;G;#C}AG+2osa~EDn>=#g})&rn-=gl zgkj1>gg%fHms|{CSOXOU$-@iEZfW!>`hA`ZL>H+aV@$}|m2*VE~vp{M< z=7Z#s%|g}(lFRuU!k}>p)lC+cLKqyNVjy{tIuJ%S2c)N=k&Pj?lZ_#kd=48+-Nv$YR$pufg zgWTZ7zyKAiU|?wAWH`XYz@V3zl30?cVhS?sKoche3rLNDL2_C;1H%y*-#I8mCBoSe zq~-*g7)bC8s#p|A-31gePmnT(D`;XM!5e5|pv-uuiIYJBMqN8QHuLusVfiPKE?O zZU(&~P}ngrG+Y#6xC3%8n6FX z3=6J`K*GMLBw5AN5p4Go6tO6<-7ApALLlnapol@#ZMZ7Jz{AbJpa-^Cr2^!pJr6_} zKn!S{8-c`39*Qu4$}FguF-Xkgp$Kx?Fae2iJP~1N1DOX&FJ>Syh7TeP2D}UmdQiU^ z{1ss+0EGjHuTsRY;I9b73mygry`K8V|ZKO237{*gBWL!$|LKyYj$JsX-1X5f~0>& zw;RLiZdCaTNb(^2jx2`AS1)#BXkF~a0CUF@G3fX%nu6Fo47sTtk#kFtsrIk=>~{!;L|82E=}t zJaRako8iWA2`X>C0HRLidI$r^E`#eK3_jN(_JGPxkUcOrg7Vn)`4IK6G8$QqdjTRm zki!*ZhV&YUK5Xs*$w!<*mEVFEo-C(P<=W-+Mp$mJeLzhEA!Ji7jKIjHjJ{{NSWDvz!|xd>GrUH|h6RQVlf z>A#~GRUX}ZuS)##Ts5fj==rOv5mg>Nd^kE$<hjE*BadNp!jLq zj$dAzfPC{_RQ<^53uOLzS$StQUJmB$-ULL)wMaccCL$J}^+ua8- z4;CKC;R>?jMklH~y8gAZ@XH_BfGUq{{!UI$hBKU=3=B^|t#y!X3=9%aL>P87aWa6~ z>kttJh7OQ?11H0j#|U*do`^8`G;uO?JVc1`Jr`kcXy9aMKoYZhF2Z2Zz{#)#NpHb( z5r&v1PMGm1^q1!%3>Th@Fep6{VK90k!r=BqgdyaK2t&#f5r&c{A`E+;i!dyCF2XSB zxd=nea}kCo&ms29KxvhyA`B)^MHn2OiZFyd6=BGGD#B3rRD_}LsR+Y@ry>j+o{BIW zdMd(j?WqXEi>D$Cf1Zjk2t5;FPnWv7;2u0FwA%+ z!m#0)2*ZJAA`I7_i7peZ$%iUy%k|-cq_tC@K%Ju;k^h$(0dVvr1v5WdhbOT^4^OubUhPc;CU&+ z!1h9fLFR=BgWd}f2AdZm41O;}7-C+CFyy=tVJLec!cg}@gkjnX5r%ayL>SJz5Mg-o zLWJQPNc_DBgUm}22BViE3?45<7*bw}Fx0#hVd#1(!Z7cp2*a+IA`ItViZHx-DZ;?< zN`yh`l?a2$D-i~-S0W4vuS6KCUWqVFcqPKHBDuS3=MA2MHrYF7#a!~7#dg^7#i3Z7#hSG7#g@37#dg@7#f0}i!g99Ff@oVFf<4= zFf<4-Ff`;dFf^1fFfK zG&6;PAwMV8*hnuWCx@Y%DJV7FzaTZwJGIia!jWMHh#y#*T2vX9m{Xbx5@Lu3i8<%w z7pHosRx(tAc&>S+xlkQ7Fg{p=Bf}&RA8dkWURu6)YNaCsgQH8BV}K{rN(L4N_tX-% z%$(G))S}|d{5+7l;LNI2M+Sa|u+$>3UZ`OV3|Q1UGK9GX1$+AYfqn3a*|#*OB-5#~ zBsC;IJToQLIU})%fr~XbwZyr!s3^VI`wmeo<~>iEnCgabkLE zI0Hj6lY453PhxS2Yf({t5d*_|2Iu^ON|5uy85r1L28QIrZD9}w>A+(46_9vvNs32e zUP?|X0|O^pcu{6as&jr`aehv!Bf~EikeeVp-~5zR28MK&pwz?^xB`ZMjP9u=P%cD; zB*;vtT>(Y;$*IM~3>!dVAdX{7N)bqap$a78o1apelM1msoPog|BnI+^V@XL-W>RTM zYH>INLldK0W?qV0W>GOX2ErK_`oJQ7sTELxaxfnp-3;Q4E~z=GC8;1|!Wk|x`{XAl z=C~E5rZT){_RTK?i@R2YGwfmsO3lqLOGOT8bx;g|G=^m6rZSvmfTTZ=KsZAeYj9<8 zNop=gDkL8y24XQVyac%!A;nMz;zRreQskGI3yLuokSH{if|H9VF)t-Qmx19BM8q+lvqIO!WkH@Ld4xuOFR;bGXfHe5_1`j zL&RKCi%W{~D?#E63^yTSAdX97Ng~4$2psmUd- z6$~8=;5-D$j|}A?H$mb6BI3xv(826k49do-#l`tWZmEeSrA4U$MXAN9c_j>u%$~(A zsY#{j>8V99vGuH>c^TlcCB?NOIkf=fXt&IqlGGvwhI~+L;FP`uvOTyYv8bf90F+C@ z8T?ql#cV)oQ5vW`%u7xM2VZItgA_PzH7^ z2IW!^iy;*$E`xnCi%UFG6H^%aSVK~aax?Q1OH!d8XJEL(5>%2Cnpd8gm%@;Uqzf&c z9rIFx3o`S-u4Gui8j@4&n3I#A%)qb+#CA_D0oND|i$T0#7;hnn2TIfo$B<(mRAe#e zGrHy_=cl9w7K`KKxN~su9 z6D>6%9QBVoejhr^Y;qwC|8j_k@05UV2fkBtirL-UiTuMT{^Bh{HL(K_iVE76Z zfus-yhD5MEFy1=k^2ISP1>^>poCu_@1bL6)2Ba3u%}vZp@yX0fb!51SCKAq22#y12 zF(1yrzyxwFl$**itTU)h6&7`#bJp#nJExa1`&{{ zp~b01E~#mWr8y-&&Yms|3}K9*<`X3TAPPY>^Fl_qoYLY9PzXS(vtkAYKadR|5m58U zwSqwfl*>VUXrgtkU|{eA`zt6F;!78p4tTX!4314Xm`;RvI0J(ZNEE5XvIiP}NW~_@ zVQ|<7fPBax0P+tg`9!x|JDWJwqson}-(7a@+% zq`&~4$01HNLKR3KXl@1}LYM`btKmRWhOR&5V+g|$#!!N|9ttu(hA^C948@5BK87&p zf#&(3cJ}N>TZaG@A8HgM0l^a z7gCx4nFShC3kXK_CsaAnRDcVG`Wb>5C^La*r(#tYz~C~V_Rshn!f@kr2!q6z5QZ6F zLcn9hFo&YYDNLNYv;a;3u}1+XfXEMZGBGJRB{eNQBQq;ICpRy@ps=X8q_nKOqOuC& zGe;+97gslT4^J;|A74NJfWV;OkkGL3h)D3-5Qv)LK;ldW(8Z28lL48v@gNPeA)Xu< znOMNhjwQ?t3<*t@O^VQ9UUnbCShy5IH_m_X2gGuyYXH+Lm>C!jF21!GS7bK+1F2?U zaDdWViB#_hRlkjyf#Lk3CyS&Qo-TT^C>mlI5(ziIZXsAlAS(ky;H|h@3JmeLvTk86 zQEI$=K5S^g949Pf#(izA+28OCWcZS9jZVXZn+!)eMxH0ryac4NP+KqwX zmnXQ)b=c~_aDmT}fkDubVFRxtgTpNchJ;%V3=Ov&7&hE;V7PG0fg#|I1B1bB2Zjr` z9T*aBIxu{=>A)ax*MTA6t^>n?I}QvV?l>?s+;w1RxZ}XE;f@1?!+i&a3HKcsKHPU; zPV0iGvaNwl_L&7Tuh6%467!qDPFfhDvVA$}+fg#|n0|Uc5 z2Zja^e(%6=;e!Lif=><%6BHd83gjIb6h1pJ6ev0}7<_hM*zm=H;lLLM28OQ=3<+Ny z7!KTXV3+`+zdA5H`02oK;J*Wd!*2%$fjYPP6uvt!1pIJdSn$Jvq2Q+j!v>I= zpAHNcemXELNOoXY5aGZuA<}_iLX-o;1SbcE2VM>g0^SY`4c-n67rY%9HuyL&2>3cM zJn(d2DDZM%*x=>B5a8y(u)y7c;Q>cCJC(!oXH#>!Gq8wrf+nUA20&Si3?T6dptYU?q6{3MNP)_%5Ef;q1vP@9d=6&NlFKlL zV1{^xFa}qKAckNDPX>PmKZbY)PX<2*HwJ&O%DpV_|3P|@tYAI^1B8Zcyk%iv0lOV4 z38pfbKstgMN*Ib5G8yt1(iz+sG8u9hQW-oM@)*(>@);NyT)=7!7)%%p7%Uk;v?~M1 zML7&=42%rU4EYSX3+QW=UEvcYx~ zGbn&nr!XimlrUs4q=My(7*ZK>84?+?;rx7tEQVBuWCqaY04oMg21dBsTo_UriW!o@ zZZ2RbVaNpA0F)%Xt zFr+f1GbA$PfPGoOPzkm%ogoA4CXmlj#S|Dc7@Qe2859_d7z`K;7>pQn!7LC@fq|Og z;l#kpz{ubabq^@!K(2(vT^>9Z3edtsk0F(z0vbO?;4lS+SUy7vLn%0d{}gNPGI zSXzL?5+sArgC3&T!qbHTl+MB!5*do%v4}`v3=Ap^pfV_gp$MEZiWzbk5>eunlVt;o zjeSLKjzSq^I@DI(P|rYJAr&+_otc+zs~#HSrfZ?DP+XFjmy(#1pOjpqo0C^;t6o}^XH}e>k(!%WtecyeT$Ep&pH`xqoS$ozSe&a@W~i=^ zo0ylGmRei_o#BAkX2;8=pkPx{R9ajD8t8+XrbNIrV%NgVoq_Yx`IC392BGX!ju*MV4$5c43JWkWfNDHO zgK)!rhUbjJjbn{_jMo@HG1f5AH<2+_FjXu(Dcgy*?DjtP751RT>7X_IYzA%ykp=|@bq36aMut9y35ErR%M4kK{ES+R zHX0o=`e&qTY-Q|ioNYYUc&D+gNw`UtiJ0k6(~D-d=CdpoTIgBsx0JNHrTe-#m37f$|l!lip^>p zRXZy?8G94^DfSEOFW7$p`J;e=VFd#NgN(r?14%=BLtn!(!)C)>hTjY&jjWAgjp~gU zjhl>b8?&0!nyfSNFfBK2G3_;7WxC(=ylIG8oLP!lo7n`jS!Vmqj+#9-duhgC&Td|1 zzRdi;d4$DAiwMhUma{DvTCT9%Zy97Y$?BU`hxJVB?bcj2(l+Tf_iSWsm25R_4Q$PA z?QC6beQbkmqihpxGi>v1583{(HM2{xE3#{`zXEb+0|P@30|SGDL4!eyL5D$)!32XT z1~Uxi7%VVYVz9zsjll+kEe1Ob_81&6IAUh2W)n@0-*vuo$Pn+{uoVEC3VPF|;C1f3Goon;Y=B{m<-5k4Pc5mzi z?Pcth?2YZ6?7i&+>=W%9?DyL6&IlOczpq@jkPp`n?f zyCEoTA2*aW(lfF)YBhRfq+{%6oM3#w_>HlP$u<)eQ%}=S(^S(^(@xV>rcX>inEIJb zG_x?DZeebD(DI69i}eiao7Qt}j@$6qw%g9PU2l8QmftSd?vCAayDa;8ka%0bz@Wpx zz;MHm(desDo=LaKD^pIhBWAzMw9Ma|M_Zn?Wwm=~$7Y{qf6ks^10?JWjVz4z7(Fn0 zV)VkO+IX|ErAduRrD?nAOw;A2J57(9-ZXt~`qPx%Owvr<%+k!=EYd99tkSIAY^K?A zvy*0z%*4%2%rni~%y*mLFz2<KM&6 zI%f3Gh|So{xX`%8_`b1-iJ?h?$xf3OCK;xwW{b`4nf)?THa9XaGH)~AVIF9)(1OEK z!Lr#&kTHxYfKtUT1+}jdQ2vmOfi{ZGRI_riJGapnXcIivu9@c<}1u=t+cH#+1#}G zZu8%U$5zf(+1AMRr0oS;T{|N?f4eZd61z&fD|WB!{@Y2|%h)T}tJrJU>)0FEC)lUh zXV~Z17uc8BSJ>CsH`uq>ci8vXPXM`tff3vW{ArMA=w}palw-8Rh|^fW_<->vaf?VH#t~Vy10oU}kIPY1Ux2 z+|1H^j`?cy&*r{XT~<8Sde+s}kE|tZoNYR6y6q?1AF^iyOqEHWO^8+cwy>*mcUG2t;aGPg2!F%Ptex3IJPV##C`XdP|+(^}9b&^Frkr>&sfaZnmJU}R7M z?Qb=#F>EkwG3+qxF`QsH#jwL9&vb&Roq3ftXag)GI8GLuU$cH_{l@ycHIt3Fje(7c zjfIVkjf0JgjfahoO@K{^O@vL1O@d8|O@>X5O@U2`O@&R3O@mE~O@~d7%>!u+PWn1!81lEqDn9~KgpiI#gT&scU?&9r)E#c3UAood}i{ zn{zfxY{l$k?R4!@?aJ*!?JwFhOn{_~Nd_yil<(IK9~!C}85tEA)f#mgO*B%p(Xd%# zyTNvg?GD>LwtsBH?M&=v*@JFzOkrSP&^JghC^cATu+DIz(F~)jMn8-=jCqU&jJu7O z8_zJ=WpdW!wh6bXnyHnkhiSJdmzk;gG>hLBoR)l+a+U^`ZkB%5G1k{?+HL!6*W2&6 zKW2a4{*CbJ*sT&2&44 z1E3xr1H%`iC}SxT4HFX+3lkd?2NM?)4-+4g0Fw}t2$L9-1d|k#43iuaHq&C$d!`O% zyUY$+oUwRpppOM>1p@~IE5jPY`G#ifa z-IiM{FIqmaw6Jou%CRc6YO|8I4zW(QUS@s8y2EC&%_bXt+X%ZXyDfI%_TBb}?HN8W zg4;pK1{Vxm3_T4)46hq983`M?7}XfH8LcteYxK{k!|YWMBfDZ)MP7Fx6nC z(M2O3lL(VDCihG%O!G`@OgEU`F#TxCZ>D3GX*R)Zw%JKDcXJ=}X!AC6b_)*+K}&1P zO3M$H3RaTV0oJFiFIk_piMBm&`^a{g-5I-kb}aS`0!$2`jr-yTG6tpwD-CWLSQ*+I zW*e?IykaP4wAx6=IMVo%@qc48lL;o5Oc~8$&1B8REqX0ZT3odFWg%v%VcBH4$MTA0 zo>i08T&pEkE3GzL?XcQwb;jzqm4vmCwTE?*^;+wz*6*xEY-ZaA+Ev&cxBG3UZ|`eA z*M7JC2YUtuCh#1cfdQ+bprNc`uwkp=G($zB`9`tExh6Fxdra<{NSf-JI+})>W|}sb z&M~z%b2ZO2&oi$x|6xAIVx0wtrJZHCWr3xX^$S}$J43q^dj!X)CnhgU-k5wa`C{_J70o@0H%TEu3x&2gKh zwx)KDcD3*tXolTUdxiieh8zY4hH8T*124l>hSf$Vjc*&jF#c-HWFlZ9Vj^K8W1?W9 zVxnO($7HdIlDVO|gSo$XqIrRNjd_E4i+P86kNE`iDdscG=a?@rUt+$(e2w`A^DX8( z%=efdFh62`!u*W+1@rwDXDxPHF(fd7>tkjEK?4f|H-jL91cMZV41*kl0)rBR3WFMh z1_L|8NW)yCoyI?mpP0m&?lToNvorHH3owf@OE60@%P`9^D=;fDt1zoEn`5@ljKkc{ ze46`8%-6>mYc0J zV>C}P&otj=&S0TrnQuAUa)qUdRissk)mE!ZR)4JctTU|VS#PpFYyHxi#m2^_#Ad3^ zI-4st_iY4i&24jRJ8T!(-nRW{>th#W*JyXr?y;Say|TTly^npSeY5>0`@{A(A!TC& z#C}Z!4}(gBg9gtH{u%HZS{T+F?l62{sA=S9lxuX}sKfZTv53iHlXWKh!S()klRqZK zrX8khOhwFe&FsuP%}ULtnJqTEYGz<=XWnT(%lwe}Ov`zeudP2>%h)K`sMu)O@Y$x@ z-n5-(x76;q{TcfQ_CM_Zfb#YPCU75z!GOhp!@$;HvcU|49R`mLz8JhQykaD3tYEBa z>|*S1Jm2`Kv7kw~Nv!E+(;cQaO%==x&HT)Y%*xHCnE9J0m^YZuG2d_g%>28#tVM%G ztHlh93lVVZtD`{(8>oDtj>wfDE)+enM zZTxMP*a+L2*ml|OuvN43w)<_TW8Y=J+5W2ibC5e1FoDnL_-0USxX?i>6u&^EL&>Nc8WbjRqQF|$dS$r_VQCZ|p8O{bX7G?g(s zWM*UTXTHMxp80ok0gH5tRTf(;vMg^|uC|J?K4;@)8*1BQd))Set%6;w-6T65dpr9X z_U9n(J^;y!3o3*} zHe5C$Hj*|`HnVIN*=)7ZvE{YfZ}-N|*Ph`56Sy76Zt%$Hz0r2#jV3Cl+NOeLer5?) z=~fk1HC7E)3=be>nZLnR!zYG{M&FIjo5Yz3n@gFSTRK|$SO!>zSVmaJST7woRs z-LSi3_rUIn9fJZhxQzW|AZe&z=m-siw}xK~wT+${eK7iB^uy?n5rgps+_tGc#Lr zQ459uW^las8ayy~0`6To8f6)k8B3U-FxR)>wB`e~-xDBe*o-cj-8KW+_0{Z;8M8Tu zxv06E`9<@apcBUmn89aobegR;dt>pz;)}%(i$4|&mJAKd;5K)j5r=V|@nq8nrcX>? zn7%P}HBUD$H7~F#fu^k%tCdy)HX=3>HjpxfVFEL_?8rALF*s+i$8d%58siPdTa0(W zM|{p0UogI6e8c#T@o(b{Qx|g&b06~n^APh0^BD64^AvMPOSr_m!hE7dw>5Gf;;D_O zt&FXPt&wf0ZH#S(ZINxa?IhcIwkvE8*`Bn$YJ1=Ir!9jWkDZvEft|UXn_YlifnB*> zvt7U4YP(H#`|M8G{kCJW=d+ivx3p(izzptl85x)xcp6MLsxrFN{ZH#TbZ4cVNv1i!846cW~4dxomGb%OFGd*OcZ9c_fhQ%C<1r|#zR#>dD z*kG~6Vu!^Zc)RU_#TAPi7I!QjSUjDpF{@5_v3fhX=O51AMI@$)>#@eRZ7T8wWHrw{tPPd(7yU2Ep?Kazkw#RHQ+FrMP zX#3ih*-pvM#V*3`ncY`AHv3|G1_l;}1E9Gv18GA|Lm4AQBXuKPBV!{=BYPuPBX6TX zqi~~GqhzB@qkN-MqiUl@qjsZSqsc}yjpiFIHCk=7(P+ETUZcZCCymYMn8@I8?hR58w(nX8_OCi8*3Wt8=D$i8#@}i8~Yju8%G+)8>bp)8y6av8`l~) z8+RJ_8&5T!ZM@KUx$#=#&Bi;8_ZuHIK5cx__`3033Y~R}`3(m{j2H6>c%vVFD=!PMDlAxnObyXA6+S6fw>K8MW3iH83?XMeYrQ z`mms}hXm6U(+txb(*n~H(+blX(+1NP(+<-f(+Q@>P0yKLHoa^5-1MXAZ&Ow?eltlk zWiwqfb2CRXZ?jOdc(Y8iVzXMacC(3Qv(1*8tvB0gcG&E!*>$srX0Oe@n*BH9G#56P zHCH#+GdDH2F?TliG7mJ5FpoD+GtV_IF|RhCVLsn{8N3B|#r%f(9rFj~Pt0GKzcK#+ zA7f{+;IQDa5U>!jkg$-kP_R(3(6G?4Ft9MOu&}VPaIkQ(@PL;75f(8P2^J|985W3A z@~1_hWsK!o%N>?)Eyb;5tdy;St@c`7wYqQh((1F-Un_QNL2GGiRcn1~OKWFqU+Zw| zMC)wpQtNu_PV33mbFG(KZ?xWReboBA^-b%?)^Dx9TQk~l+lbo8+i2Pt+t}K;+XUJ~ z+oam$gL6Qy&2*cEHmhy6+U&PEX>-};uFZ3sk2b$;SZ(=jC2f^$b#2XU9c{gBLv7=2 zGi{4)Yi+06&bQrWyVv%R?HSu^whwGy*?zJ8XUkzHWT#^nVi#{$Vpm~TV~0GdGsA9< z-2%HMb}Q`G*ln=eVzFE8xg*nP13V)w)Dj~#-ukG+7s zh&}4)mWjQEy^Xzty^Fnvy^no>eTY4%=EFVaHN}30{T%xR_Dk$n*srnQfHcZ+!v2i? z1^X-ZH|+1&Kd^sd{{r5B|6|V}z``&EG+6|0Bk~vs7>F1Y8Qd{oFl064GE_9wG}JS6 zH=Jv@#qf;bOT#v!B}NayHQ+C!e@3ACp3hjqSjkw+*v2^6IMq1UxZb$Sc&+hH<0Hmr zjjtOYGP!LMZu;6Z+U$ZEzqy8aqU8sxFIGRS{#Y?svsiOj^H>X5i&#ro%UGYbo@pa% zyV}kMRJSXzFo4Pc^PEu`*3FJ8vds{?S~^g3Z#?a*yQ; zOBpL;s~)R;R)W^+trKk~+Zfn+*loA_Y0qH50`B{98%;F2Wc1Cb(0GRNQse0+rKSf= zlg+Bl)|hQD+hVrEY>(Livm<6F%+8oyFuP)Q!|aaP1G6V)FU;PUeK7lCHr4W$m5udh z+ZFbkL2h$k0oNZ*1|0?*hE|4`44)XOnJhLbG+k$U!Bof0+RWK3$Sm3{$t>6GvDq&( z9rJMWeDkU1>&-vPs$txIfDZToCj*XV{^+M}2*3Yd!S_|1& z+ceuuwbitnXZOm^$$pakI(S-PU|>jK0rw9Z4bB?eGLSY@H8e4FF^n{vWcb&R-ALHT z(kR|&hS4IUKSpfEyv9?E?-<*d%r!Y+^3BBDw9j;b=`GVO=JFPYEv{GySt(j+TZLK~ zS_fMvSa(^++kUZSw&S&Pvs1HQWWN#Q#{w4c7^Q>3djmd0SHnq$&kR2pvKiGF-7;1; z@i6f>2{XwuX)&2;^3){2G}W}yRLIQ4%+qX!nUeWT^VQ}o7Md1;7Pl=PTKusPu$*nV z%JQt`GfPJ+KdTC>9;;JUkFEY#$ynQ2r(3sMFR(sp{l@yQwU~{qjh{`rO@qxJ8xPws zTMK)8d(bXr&|Kp~qnBp2<{p-w)&bUu)|J-FtoK>Jwq~|5u<@{|wQ07QXS2=brH#F9 zf^C)UVcXlbpfp!!HxbkWv%7BRVV_{X*PdYl3uxG%!OUQ9$zgN?I|uNc2G4mYVVnPgIE+G)yVrex-C zmTor1Y>nAXvyWzS=4R&Y=JU5YuPy2DD+R2!@?IAHMBfYs2*(95vfaIxV9!xx64M!H6M zMr}r$jZPc=FcL7HYy8OgrE!7DTGK72&rN5T%`sbGw!~}&k$LTh*&j0oa~5+Ba~^X6 za}je1a~X35a}{$9a~*R7a}#q5a~pF9b9`-0Qbs07tJgFvbu0}mO)M=eZ7dxuT`WD2 zdRz&XDV7WbA3t2RMW{FWBC) z<+KyFlegowSF^XbkFYPa@3LQPzsLS6WUO=p3%Jh&8Y{Il2r|evXfc>;u+8AS!3zUM zLn%XJLm$Ib!_9_w4VjGNj6WIwF@A5#WFcy)YiVQ|Vp(O`YPrVpsO3bf?^a*z84j?3 z`yQtZ&KtZk_+v1|@Py%8Lph@qqw~fujO9(NO#Dq6On#bhm)oi1gmbtxofcaeW-RAeq-*R@bc5tsSjnt&^;KtZQvH+q|~?CVNhtW#9)iT8-pJP28Jny6AafFo;G}C$Y~^JWNhSWRBmKw z>}0&unA>EM$wL!MQ+v~9)2XK0Ob?l!FnezH+f3fv$lS&}&wP&gQS(pc$1Gl2e6?V+ z6tz^d47NOB`P|abs>^D&)psjD>kR8!>j~DEthsGuY*cL$ZGPLR+gjTGw3}uB&i)rD z&pu#bSi-=-U~J%Nu*hJC!8wB`2BC)8hW8D>8gd&c8krmU8YLSoH|jB7Ykb`JfibIz zpoy%BrirPEk4dOWf=QZ5p-H*PbrU($bkhK{c(XdQUb7iyr_FAe-8U0AFEO8HzSsP# zxu!*m#Ttu8U|059&avEMdD`-|M{Lj8_SiFgU}2cUz`!7G;9{`I;Gw~DgU<#kMxjQVj1CyRFyb&)F}5;(Z2aHY z&Lqs_tqG^8n`x5i4bz`y4Ccr!%odAl;PO7ivfXmJrM{Jgm7|rHRj^fzRjO5B>%Z2VHnVJ&*_^hyV)NX_&MwjJy4`!bzjng* zvi27C9rhFK=h&aOziR&!6u%0r44^fg+y*iR-UgctuNnR_lrwTNN;8^aw8ChI(Fvm) zMlXyUjTabqnXWY5Ynp3TWj5PvmDxkHS7x^6ZsyC)x0pXScd|&fm}ark;<&|S3uY^E zD=RBUD-Ww2tJPMUtbSTE*w}*G_Az*7s3G%bf9x3ySix;O$QUi8{Vic2W1wK5VxVE5 zgE2-9S_87$fZtHYu;1vO@oVE!(_o7amabNiIW^ck!c}WGn{JyMHfpvyw$8ROwwr8s z*q*n2XX|V?&+dZV15ms>u!6@(`3+(W<{2C`cw{7EtZwXVoM=4X#M11PS)KU;b6<-l z3ty{htLav!t)5!_wz9B}vW~Y-v(B|Hv97jmvhK9*xAw6?o+*A{^TAftF37IeZj#+{ zyEM@HqyScM8~m5Se*+f7BEx-#pN;+)+nKhT-ZEu2t2bL|CT?D7G2h~ig`Z`z+IgyGbFHr$BTa%bQsPzJZtpS zh}%@cbfW2F(|@KCW_o6u%=Vi-Fq1d8G7m9dZvM&qxA|X-m6k^=IjwB1K3EA^AF}>p ztzctsv)1mXoi!*;6tIHF$j%$28A=)H8a*(TG4nB7Zg#}%wwb>9G4pHYN*0r!lqu~j|+lIdl8H~h?9E{Ezu^EHP z{u1L><1XVV#w(3a8(%P1HkoYl#>Cas*EGm9(X`z3oat-R52im&r1 z;SFL_pt+hUC9c44aX1~p4n-?|$ww|^T zwu!d;Z29d}?c%`kyumIL(gs)n2@e4SD}$p3!iFV=OAL*T(u_)s_`$Vyv&nvwk0#ot zwx;Q(Ii|IyQ%#SWI-BL0)thyituQ-lcEe1{+|sp-qPDD#Hz(=lGR?TBUY!a?5#_!`>dU9Ds8sg7}zecy=p69*KGIS&d)yGe!e}! z237{p+;_6UOoPh?EQZF0OAM`yZWyT;M;P}R8=91x%rQA((rBt`)?n6R)?wCTcE!xV zJlnj|{H(=C3rovQmSR?utO~3pYz%BtZNAuW+s?ARVH<2W%}&_f(|)`CZBUpWfaKj^ zgKC3A25g27hG~X9hBplvjMR;SjOH30GDYMd3n>^b(+nu&2ZJ*e_x0SS0wllM{wR5q{w`;WPvRiF;*Y1N| zgngp@a{FT-doQqp*DM4Z6d9Z__-gRafWy$nu*dMS;SWOtBUd9oqX?sVqc)@IM%#?U zjJ1uOji(r&GQMbh*Z8aPKVuFPZ4)PxAd{&kb4<>gRGNvJtDD=Ihnp9gH z{EqoU^H=6y%*`xfES6cEv^ZzMZ0TW{W7%ps(Q>EdXG>q34%^kXUu=2p0_+akJ+lk8 zFSCCPa@PZhyZ8;V4DJ~GGcYrZHk@Gi){w&}!)T7tDWe-kuZ?~hF&oPos~cMzyBbFs zmm2pQzcuDFDK%MW^4R2$iLR-&nVVTF*cGSDubA(&V764Tw6)x7^~CC})hDapR?^mS z)@!Z*SbN)~+SJ?3gs(x-vh}f5v2(ObwrjP!VfWZB&_3V3$9}c_Es(oDK+1d(gJ6SR z!=;A%4WApnGvqW?wR~&&(~{Ln&`Q=y-)e@{ZYv#YQ)@r#Z0j!T?bgq%HEaxR zylp~lDr~0N9JRS)^U!9O?HOAQJ8!!TyCS=(cH8YffYUenx~dQM3<7N6d3+~>3WJRX zdkv%voeV<_jf{eg;*HiBu^Jm1w;L}r-fR5G_=~ZfNsh^6lY=JDO=3;^OqZGNGrej0 z#Z<}6!YtfuvKfncq#>mFsW|7S+nFB~8iX5MH+W|7-9X6D&#>OG$8e$H4#S&<-wkDr+Kn`g zCmMe<7Bw+8X*Jno^4LVxG}*M?wAb{Bse@Ua*;2FJW>?IfoBcA=G

    YYW~E0n#FYs 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 Jj$&{K001AkYeN74 diff --git a/premake5.lua b/premake5.lua deleted file mode 100644 index ebbcf4a5..00000000 --- a/premake5.lua +++ /dev/null @@ -1,477 +0,0 @@ -newoption { - trigger = "glfwdir64", - value = "PATH", - description = "Directory of glfw", - default = "vendor/glfw-3.3.2.bin.WIN64", -} - -newoption { - trigger = "glfwdir32", - value = "PATH", - description = "Directory of glfw", - default = "vendor/glfw-3.3.2.bin.WIN32", -} - -newoption { - trigger = "with-asan", - description = "Build with address sanitizer" -} - -newoption { - trigger = "with-librw", - description = "Build and use librw from this solution" -} - -newoption { - trigger = "with-opus", - description = "Build with opus" -} - -newoption { - trigger = "with-lto", - description = "Build with link time optimization" -} - -newoption { - trigger = "no-git-hash", - description = "Don't print git commit hash into binary" -} - -newoption { - trigger = "no-full-paths", - description = "Don't print full paths into binary" -} - -require("autoconf") - -if(_OPTIONS["with-librw"]) then - Librw = "vendor/librw" -else - Librw = os.getenv("LIBRW") or "vendor/librw" -end - -function getsys(a) - if a == 'windows' then - return 'win' - end - return a -end - -function getarch(a) - if a == 'x86_64' then - return 'amd64' - elseif a == 'ARM' then - return 'arm' - elseif a == 'ARM64' then - return 'arm64' - end - return a -end - -workspace "re3" - language "C++" - configurations { "Debug", "Release" } - startproject "re3" - location "build" - symbols "Full" - staticruntime "off" - - if _OPTIONS["with-asan"] then - buildoptions { "-fsanitize=address -g3 -fno-omit-frame-pointer" } - linkoptions { "-fsanitize=address" } - end - - filter { "system:windows" } - configurations { "Vanilla" } - platforms { - "win-x86-RW33_d3d8-mss", - "win-x86-librw_d3d9-mss", - "win-x86-librw_gl3_glfw-mss", - "win-x86-RW33_d3d8-oal", - "win-x86-librw_d3d9-oal", - "win-x86-librw_gl3_glfw-oal", - "win-amd64-librw_d3d9-oal", - "win-amd64-librw_gl3_glfw-oal", - } - - filter { "system:linux" } - platforms { - "linux-x86-librw_gl3_glfw-oal", - "linux-amd64-librw_gl3_glfw-oal", - "linux-arm-librw_gl3_glfw-oal", - "linux-arm64-librw_gl3_glfw-oal", - } - - filter { "system:bsd" } - platforms { - "bsd-x86-librw_gl3_glfw-oal", - "bsd-amd64-librw_gl3_glfw-oal", - "bsd-arm-librw_gl3_glfw-oal", - "bsd-arm64-librw_gl3_glfw-oal" - } - - filter { "system:macosx" } - platforms { - "macosx-arm64-librw_gl3_glfw-oal", - "macosx-amd64-librw_gl3_glfw-oal", - } - - filter "configurations:Debug" - defines { "DEBUG" } - - filter "configurations:not Debug" - defines { "NDEBUG" } - optimize "Speed" - if(_OPTIONS["with-lto"]) then - flags { "LinkTimeOptimization" } - end - - filter { "platforms:win*" } - system "windows" - - filter { "platforms:linux*" } - system "linux" - - filter { "platforms:bsd*" } - system "bsd" - - filter { "platforms:macosx*" } - system "macosx" - - filter { "platforms:*x86*" } - architecture "x86" - - filter { "platforms:*amd64*" } - architecture "amd64" - - filter { "platforms:*arm*" } - architecture "ARM" - - filter { "platforms:macosx-arm64-*", "files:**.cpp"} - buildoptions { "-target", "arm64-apple-macos11", "-std=gnu++14" } - - filter { "platforms:macosx-arm64-*", "files:**.c"} - buildoptions { "-target", "arm64-apple-macos11" } - - filter { "platforms:macosx-amd64-*", "files:**.cpp"} - buildoptions { "-target", "x86_64-apple-macos10.12", "-std=gnu++14" } - - filter { "platforms:macosx-amd64-*", "files:**.c"} - buildoptions { "-target", "x86_64-apple-macos10.12" } - - filter { "platforms:*librw_d3d9*" } - defines { "RW_D3D9" } - if(not _OPTIONS["with-librw"]) then - libdirs { path.join(Librw, "lib/win-%{getarch(cfg.architecture)}-d3d9/%{cfg.buildcfg}") } - end - - filter "platforms:*librw_gl3_glfw*" - defines { "RW_GL3" } - if(not _OPTIONS["with-librw"]) then - libdirs { path.join(Librw, "lib/%{getsys(cfg.system)}-%{getarch(cfg.architecture)}-gl3/%{cfg.buildcfg}") } - end - - filter "platforms:*x86-librw_gl3_glfw*" - includedirs { path.join(_OPTIONS["glfwdir32"], "include") } - - filter "platforms:*amd64-librw_gl3_glfw*" - includedirs { path.join(_OPTIONS["glfwdir64"], "include") } - - filter {} - - function setpaths (gamepath, exepath) - if (gamepath) then - postbuildcommands { - '{COPYFILE} "%{cfg.buildtarget.abspath}" "' .. gamepath .. '%{cfg.buildtarget.name}"' - } - debugdir (gamepath) - if (exepath) then - -- Used VS variable $(TargetFileName) because it doesn't accept premake tokens. Does debugcommand even work outside VS?? - debugcommand (gamepath .. "$(TargetFileName)") - dir, file = exepath:match'(.*/)(.*)' - debugdir (gamepath .. (dir or "")) - end - end - end - -if(_OPTIONS["with-librw"]) then -project "librw" - kind "StaticLib" - targetname "rw" - targetdir(path.join(Librw, "lib/%{cfg.platform}/%{cfg.buildcfg}")) - files { path.join(Librw, "src/*.*") } - files { path.join(Librw, "src/*/*.*") } - files { path.join(Librw, "src/gl/*/*.*") } - - filter { "platforms:*x86*" } - architecture "x86" - - filter { "platforms:*amd64*" } - architecture "amd64" - - filter "platforms:win*" - defines { "_CRT_SECURE_NO_WARNINGS", "_CRT_NONSTDC_NO_DEPRECATE" } - staticruntime "on" - buildoptions { "/Zc:sizedDealloc-" } - - filter "platforms:bsd*" - includedirs { "/usr/local/include" } - libdirs { "/usr/local/lib" } - - -- Support MacPorts and Homebrew - filter "platforms:macosx-arm64-*" - includedirs { "/opt/local/include" } - includedirs {"/opt/homebrew/include" } - libdirs { "/opt/local/lib" } - libdirs { "/opt/homebrew/lib" } - - filter "platforms:macosx-amd64-*" - includedirs { "/opt/local/include" } - includedirs {"/usr/local/include" } - libdirs { "/opt/local/lib" } - libdirs { "/usr/local/lib" } - - filter "platforms:*gl3_glfw*" - staticruntime "off" - - filter "platforms:*RW33*" - flags { "ExcludeFromBuild" } - filter {} -end - -local function addSrcFiles( prefix ) - return prefix .. "/*cpp", prefix .. "/*.h", prefix .. "/*.c", prefix .. "/*.ico", prefix .. "/*.rc" -end - -project "re3" - kind "WindowedApp" - targetname "re3" - targetdir "bin/%{cfg.platform}/%{cfg.buildcfg}" - - if(_OPTIONS["with-librw"]) then - dependson "librw" - end - - files { addSrcFiles("src") } - files { addSrcFiles("src/animation") } - files { addSrcFiles("src/audio") } - files { addSrcFiles("src/audio/eax") } - files { addSrcFiles("src/audio/oal") } - files { addSrcFiles("src/buildings") } - files { addSrcFiles("src/collision") } - files { addSrcFiles("src/control") } - files { addSrcFiles("src/core") } - files { addSrcFiles("src/entities") } - files { addSrcFiles("src/math") } - files { addSrcFiles("src/modelinfo") } - files { addSrcFiles("src/objects") } - files { addSrcFiles("src/peds") } - files { addSrcFiles("src/renderer") } - files { addSrcFiles("src/rw") } - files { addSrcFiles("src/save") } - files { addSrcFiles("src/skel") } - files { addSrcFiles("src/skel/glfw") } - files { addSrcFiles("src/text") } - files { addSrcFiles("src/vehicles") } - files { addSrcFiles("src/weapons") } - files { addSrcFiles("src/extras") } - if(not _OPTIONS["no-git-hash"]) then - files { "src/extras/GitSHA1.cpp" } -- this won't be in repo in first build - else - removefiles { "src/extras/GitSHA1.cpp" } -- but it will be everytime after - end - - includedirs { "src" } - includedirs { "src/animation" } - includedirs { "src/audio" } - includedirs { "src/audio/eax" } - includedirs { "src/audio/oal" } - includedirs { "src/buildings" } - includedirs { "src/collision" } - includedirs { "src/control" } - includedirs { "src/core" } - includedirs { "src/entities" } - includedirs { "src/math" } - includedirs { "src/modelinfo" } - includedirs { "src/objects" } - includedirs { "src/peds" } - includedirs { "src/renderer" } - includedirs { "src/rw" } - includedirs { "src/save/" } - includedirs { "src/skel/" } - includedirs { "src/skel/glfw" } - includedirs { "src/text" } - includedirs { "src/vehicles" } - includedirs { "src/weapons" } - includedirs { "src/extras" } - - if(not _OPTIONS["no-git-hash"]) then - defines { "USE_OUR_VERSIONING" } - end - - if _OPTIONS["with-opus"] then - includedirs { "vendor/ogg/include" } - includedirs { "vendor/opus/include" } - includedirs { "vendor/opusfile/include" } - end - - filter "configurations:Vanilla" - defines { "VANILLA_DEFINES" } - - filter "platforms:*mss" - defines { "AUDIO_MSS" } - includedirs { "vendor/milessdk/include" } - libdirs { "vendor/milessdk/lib" } - - if _OPTIONS["with-opus"] then - filter "platforms:win*" - libdirs { "vendor/ogg/win32/VS2015/Win32/%{cfg.buildcfg}" } - libdirs { "vendor/opus/win32/VS2015/Win32/%{cfg.buildcfg}" } - libdirs { "vendor/opusfile/win32/VS2015/Win32/Release-NoHTTP" } - filter {} - defines { "AUDIO_OPUS" } - end - - filter "platforms:*oal" - defines { "AUDIO_OAL" } - - filter {} - if(os.getenv("GTA_III_RE_DIR")) then - setpaths(os.getenv("GTA_III_RE_DIR") .. "/", "%(cfg.buildtarget.name)") - end - - filter "platforms:win*" - files { addSrcFiles("src/skel/win") } - includedirs { "src/skel/win" } - buildoptions { "/Zc:sizedDealloc-" } - linkoptions "/SAFESEH:NO" - characterset ("MBCS") - targetextension ".exe" - if(_OPTIONS["no-full-paths"]) then - usefullpaths "off" - linkoptions "/PDBALTPATH:%_PDB%" - end - if(_OPTIONS["with-librw"]) then - -- external librw is dynamic - staticruntime "on" - end - if(not _OPTIONS["no-git-hash"]) then - prebuildcommands { '"%{prj.location}..\\printHash.bat" "%{prj.location}..\\src\\extras\\GitSHA1.cpp"' } - end - - filter "platforms:not win*" - if(not _OPTIONS["no-git-hash"]) then - prebuildcommands { '"%{prj.location}/../printHash.sh" "%{prj.location}/../src/extras/GitSHA1.cpp"' } - end - - filter "platforms:win*glfw*" - staticruntime "off" - - filter "platforms:*glfw*" - premake.modules.autoconf.parameters = "-lglfw -lX11" - autoconfigure { - -- iterates all configs and runs on them - ["dontWrite"] = function (cfg) - check_symbol_exists(cfg, "haveX11", "glfwGetX11Display", { "X11/Xlib.h", "X11/XKBlib.h", "GLFW/glfw3.h", "GLFW/glfw3native.h" }, "GLFW_EXPOSE_NATIVE_X11") - if cfg.autoconf["haveX11"] ~= nil and cfg.autoconf["haveX11"] == 1 then - table.insert(cfg.links, "X11") - table.insert(cfg.defines, "GET_KEYBOARD_INPUT_FROM_X11") - end - end - } - - filter "platforms:win*oal" - includedirs { "vendor/openal-soft/include" } - includedirs { "vendor/libsndfile/include" } - includedirs { "vendor/mpg123/include" } - - filter "platforms:win-x86*oal" - libdirs { "vendor/mpg123/lib/Win32" } - libdirs { "vendor/libsndfile/lib/Win32" } - libdirs { "vendor/openal-soft/libs/Win32" } - - filter "platforms:win-amd64*oal" - libdirs { "vendor/mpg123/lib/Win64" } - libdirs { "vendor/libsndfile/lib/Win64" } - libdirs { "vendor/openal-soft/libs/Win64" } - - filter "platforms:linux*oal" - links { "openal", "mpg123", "sndfile", "pthread" } - - filter "platforms:bsd*oal" - links { "openal", "mpg123", "sndfile", "pthread" } - - filter "platforms:macosx*oal" - links { "openal", "mpg123", "sndfile", "pthread" } - - filter "platforms:macosx-arm64-*oal" - includedirs { "/opt/homebrew/opt/openal-soft/include" } - libdirs { "/opt/homebrew/opt/openal-soft/lib" } - - filter "platforms:macosx-amd64-*oal" - includedirs { "/usr/local/opt/openal-soft/include" } - libdirs { "/usr/local/opt/openal-soft/lib" } - - if _OPTIONS["with-opus"] then - filter {} - links { "libogg" } - links { "opus" } - links { "opusfile" } - end - - filter "platforms:*RW33*" - includedirs { "sdk/rwsdk/include/d3d8" } - libdirs { "sdk/rwsdk/lib/d3d8/release" } - links { "rwcore", "rpworld", "rpmatfx", "rpskin", "rphanim", "rtbmp", "rtquat", "rtcharse", "rpanisot" } - defines { "RWLIBS" } - linkoptions "/SECTION:_rwcseg,ER!W /MERGE:_rwcseg=.text" - - filter "platforms:*librw*" - defines { "LIBRW" } - files { addSrcFiles("src/fakerw") } - includedirs { "src/fakerw" } - includedirs { Librw } - if(_OPTIONS["with-librw"]) then - libdirs { "vendor/librw/lib/%{cfg.platform}/%{cfg.buildcfg}" } - end - links { "rw" } - - filter "platforms:*d3d9*" - defines { "USE_D3D9" } - links { "d3d9" } - - filter "platforms:*x86*d3d*" - includedirs { "sdk/dx8sdk/include" } - libdirs { "sdk/dx8sdk/lib" } - - filter "platforms:win-x86*gl3_glfw*" - libdirs { path.join(_OPTIONS["glfwdir32"], "lib-" .. string.gsub(_ACTION or '', "vs", "vc")) } - links { "opengl32", "glfw3" } - - filter "platforms:win-amd64*gl3_glfw*" - libdirs { path.join(_OPTIONS["glfwdir64"], "lib-" .. string.gsub(_ACTION or '', "vs", "vc")) } - links { "opengl32", "glfw3" } - - filter "platforms:linux*gl3_glfw*" - links { "GL", "glfw" } - - filter "platforms:bsd*gl3_glfw*" - links { "GL", "glfw", "sysinfo" } - includedirs { "/usr/local/include" } - libdirs { "/usr/local/lib" } - - filter "platforms:macosx-arm64-*gl3_glfw*" - links { "glfw" } - linkoptions { "-framework OpenGL" } - includedirs { "/opt/local/include" } - includedirs {"/opt/homebrew/include" } - libdirs { "/opt/local/lib" } - libdirs { "/opt/homebrew/lib" } - - filter "platforms:macosx-amd64-*gl3_glfw*" - links { "glfw" } - linkoptions { "-framework OpenGL" } - includedirs { "/opt/local/include" } - includedirs {"/usr/local/include" } - libdirs { "/opt/local/lib" } - libdirs { "/usr/local/lib" } diff --git a/premake5Linux b/premake5Linux deleted file mode 100755 index 1ca751679b30a4fcc547ae41d928965520c4223f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2035312 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

    g}raDh#R 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� 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

    7>w{ZyC_N(-Qk$O0aaLzn!EVLO|;dsING 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


    )wDA7_|IkL%G3Rj5 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Ŭ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≺CaWXc|8PJc9u0oP$o~I0qV( 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
  1. D9QF(EGCAeD(>3e=ReEXu0pMl}^ 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

    CP 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¬K?ddSzJ=A5u>hJ9HXuY-5iZ%7wD{^ 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Ő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�BiPu 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 z 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

    9Js7xZw8;vVPlz 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 D_NM?U diff --git a/printHash.bat b/printHash.bat deleted file mode 100644 index ef1cd9d6..00000000 --- a/printHash.bat +++ /dev/null @@ -1,26 +0,0 @@ -@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 deleted file mode 100755 index 213d9353..00000000 --- a/printHash.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/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/public/index.html b/public/index.html deleted file mode 100644 index bb91a4cd..00000000 --- a/public/index.html +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - DCA3 - A port of RE3 for Dreamcast - - - - - - -

    - - -
    -
    -

    DCA3

    -

    The first-ever port of Grand Theft Auto III to the SEGA Dreamcast, built from the REGTA reverse engineering project.

    - Download Now -
    -
    - - -
    -

    Key Features

    -
    -
    -

    Faithful Port

    -

    Experience GTA3 as it was meant to be, now on the Dreamcast with okay visuals and sluggish gameplay.

    -
    -
    -

    Open Source

    -

    Built on the REGTA reverse engineering project, ensuring accuracy and community-driven development.

    -
    -
    -

    First Release

    -

    Celebrating our first release with not very much optimized performance and compatibility for Dreamcast hardware.

    -
    -
    -
    - - -
    -

    Download DCA3

    -

    Get started with the first release of and bring GTA3 to your Dreamcast. You'll need a PC copy of GTA3 to create your .cdi

    - Download Version 1.0 -
    - - -
    -

    No rights reserved. Made with love for the Dreamcast community.

    -
    - - - - - diff --git a/src/animation/AnimBlendNode.cpp b/src/animation/AnimBlendNode.cpp deleted file mode 100644 index 8c3a6bc7..00000000 --- a/src/animation/AnimBlendNode.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#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){ - float kfAdt = sequence->GetDeltaTime(frameA); - float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt; - if(sequence->type & CAnimBlendSequence::KF_TRANS){ - auto kfAt = sequence->GetTranslation(frameA); - auto kfBt = sequence->GetTranslation(frameB); - trans = kfBt + t*(kfAt - kfBt); - trans *= blend; - } - if(sequence->type & CAnimBlendSequence::KF_ROT){ - auto kfAr = sequence->GetRotation(frameA); - auto kfBr = sequence->GetRotation(frameB); - rot.Slerp(kfBr, kfAr, 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->GetDeltaTime(frameA); - } - - 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->GetDeltaTime(frameA)){ - t -= sequence->GetDeltaTime(frameA); - frameB = frameA++; - if(frameA >= sequence->numFrames){ - // reached end of animation - if(!association->IsRepeating()) - return false; - frameA = 0; - frameB = 0; - } - } - - remainingTime = sequence->GetDeltaTime(frameA) - t; - } - - CalcDeltas(); - return true; -} - -void -CAnimBlendNode::CalcDeltas(void) -{ - if((sequence->type & CAnimBlendSequence::KF_ROT) == 0) - return; - auto kfAr = sequence->GetRotation(frameA); - auto kfBr = sequence->GetRotation(frameB); - float cos = DotProduct(kfAr, kfBr); - 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){ - auto kfAdt = sequence->GetDeltaTime(frameA); - float t = (kfAdt - remainingTime)/kfAdt; - if(sequence->type & CAnimBlendSequence::KF_TRANS){ - auto kfAt = sequence->GetTranslation(frameA); - auto kfBt = sequence->GetTranslation(frameB); - trans = kfBt + t*(kfAt - kfBt); - 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){ - if(sequence->type & CAnimBlendSequence::KF_TRANS) - trans = sequence->GetTranslation(sequence->numFrames-1) * blend; - } -} diff --git a/src/animation/AnimBlendSequence.h b/src/animation/AnimBlendSequence.h deleted file mode 100644 index bedc85ee..00000000 --- a/src/animation/AnimBlendSequence.h +++ /dev/null @@ -1,166 +0,0 @@ -#pragma once - -#include "Quaternion.h" - -#ifdef MoveMemory -#undef MoveMemory // windows shit -#endif - -// TODO: put them somewhere else? -static int16 checked_f2i16(float f) { - assert(f >= -32768 && f <= 32767); - return f; -} - -static uint16 checked_f2u16(float f) { - assert(f >= 0 && f <= 65535); - return f; -} - -#define KF_MINDELTA (1/256.f) - -struct KeyFrame { - int16 rot[4]; // 4096 - uint16 dltTime; // 256 - - CQuaternion rotation_() { - return { rot[0] * (1/4096.f), rot[1] * (1/4096.f), rot[2] * (1/4096.f), rot[3] * (1/4096.f) }; - } - - void rotation_(const CQuaternion& q) { - rot[0] = checked_f2i16(q.x * 4096.0f); - rot[1] = checked_f2i16(q.y * 4096.0f); - rot[2] = checked_f2i16(q.z * 4096.0f); - rot[3] = checked_f2i16(q.w * 4096.0f); - } - - float deltaTime_() { - return dltTime * (1/256.0f); - } - - void deltaTime_(float t) { - dltTime = checked_f2u16(t * 256); // always round down - } -}; - -struct KeyFrameTransUncompressed : KeyFrame { - // Some animations use bigger range, eg during the intro - CVector trans; - CVector translation_() { - return trans; - } - - void translation_(const CVector &v) { - trans = v; - } -}; - -struct KeyFrameTransCompressed : KeyFrame { - int16 trans[3]; // 128 - - CVector translation_() { - return { trans[0] * (1/128.f), trans[1] * (1/128.f), trans[2] * (1/128.f)}; - } - - void translation_(const CVector &v) { - trans[0] = checked_f2i16(v.x * 128.f); - trans[1] = checked_f2i16(v.y * 128.f); - trans[2] = checked_f2i16(v.z * 128.f); - } -}; - - -// The sequence of key frames of one animated node -class CAnimBlendSequence -{ -public: - enum { - KF_ROT = 1, - KF_TRANS = 2, - KF_COMPRESSED = 4, // only applicable for KF_TRANS - }; - int32 type; - char name[24]; - int32 numFrames; -#ifdef PED_SKIN - int16 boneTag; -#endif - void *keyFrames; - - CAnimBlendSequence(void); - virtual ~CAnimBlendSequence(void); - void SetName(char *name); - void SetNumFrames(int numFrames, bool translation, bool compress); - void RemoveQuaternionFlips(void); - - - void SetTranslation(int n, const CVector &v) { - if (type & KF_COMPRESSED) { - ((KeyFrameTransCompressed*)keyFrames)[n].translation_(v); - } else if (type & KF_TRANS) { - ((KeyFrameTransUncompressed*)keyFrames)[n].translation_(v); - } else { - assert(false && "SetTranslation called on sequence without translation"); - } - } - - CVector GetTranslation(int n) { - if (type & KF_COMPRESSED) { - return ((KeyFrameTransCompressed*)keyFrames)[n].translation_(); - } else if (type & KF_TRANS) { - return ((KeyFrameTransUncompressed*)keyFrames)[n].translation_(); - } else { - assert(false && "GetTranslation called on sequence without translation"); - } - } - - void SetRotation(int n, const CQuaternion &q) { - if (type & KF_COMPRESSED) { - ((KeyFrameTransCompressed*)keyFrames)[n].rotation_(q); - } else if (type & KF_TRANS) { - ((KeyFrameTransUncompressed*)keyFrames)[n].rotation_(q); - } else { - ((KeyFrame*)keyFrames)[n].rotation_(q); - } - } - - CQuaternion GetRotation(int n) { - if (type & KF_COMPRESSED) { - return ((KeyFrameTransCompressed*)keyFrames)[n].rotation_(); - } else if (type & KF_TRANS) { - return ((KeyFrameTransUncompressed*)keyFrames)[n].rotation_(); - } else { - return ((KeyFrame*)keyFrames)[n].rotation_(); - } - } - - void SetDeltaTime(int n, float t) { - if (type & KF_COMPRESSED) { - ((KeyFrameTransCompressed*)keyFrames)[n].deltaTime_(t); - } else if (type & KF_TRANS) { - ((KeyFrameTransUncompressed*)keyFrames)[n].deltaTime_(t); - } else { - ((KeyFrame*)keyFrames)[n].deltaTime_(t); - } - } - - float GetDeltaTime(int n) { - if (type & KF_COMPRESSED) { - return ((KeyFrameTransCompressed*)keyFrames)[n].deltaTime_(); - } else if (type & KF_TRANS) { - return ((KeyFrameTransUncompressed*)keyFrames)[n].deltaTime_(); - } else { - return ((KeyFrame*)keyFrames)[n].deltaTime_(); - } - } - - bool HasTranslation(void) { return !!(type & KF_TRANS); } - 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/prof/profiler.cpp b/src/common/prof/profiler.cpp similarity index 100% rename from src/prof/profiler.cpp rename to src/common/prof/profiler.cpp diff --git a/src/prof/profiler.h b/src/common/prof/profiler.h similarity index 100% rename from src/prof/profiler.h rename to src/common/prof/profiler.h diff --git a/src/vmu/vmu.cpp b/src/common/vmu/vmu.cpp similarity index 98% rename from src/vmu/vmu.cpp rename to src/common/vmu/vmu.cpp index e063012c..fb83c5cb 100644 --- a/src/vmu/vmu.cpp +++ b/src/common/vmu/vmu.cpp @@ -10,7 +10,7 @@ # include # endif -extern bool _dcAudioInitialized; +extern bool _bSampmanInitialised; // ====== STATIC METHODS ===== @@ -86,7 +86,7 @@ void VmuProfiler::run() { #ifdef DC_SH4 if(auto *dev = maple_enum_type(0, MAPLE_FUNC_MEMCARD); - dev && _dcAudioInitialized && updated_) + dev && _bSampmanInitialised && updated_) { pvr_stats_t pvrStats; pvr_get_stats(&pvrStats); uint32_t sramStats = snd_mem_available(); diff --git a/src/vmu/vmu.h b/src/common/vmu/vmu.h similarity index 100% rename from src/vmu/vmu.h rename to src/common/vmu/vmu.h diff --git a/src/CMakeLists.txt b/src/liberty/CMakeLists.txt similarity index 100% rename from src/CMakeLists.txt rename to src/liberty/CMakeLists.txt diff --git a/src/animation/AnimBlendAssocGroup.cpp b/src/liberty/animation/AnimBlendAssocGroup.cpp similarity index 100% rename from src/animation/AnimBlendAssocGroup.cpp rename to src/liberty/animation/AnimBlendAssocGroup.cpp diff --git a/src/animation/AnimBlendAssocGroup.h b/src/liberty/animation/AnimBlendAssocGroup.h similarity index 100% rename from src/animation/AnimBlendAssocGroup.h rename to src/liberty/animation/AnimBlendAssocGroup.h diff --git a/src/animation/AnimBlendAssociation.cpp b/src/liberty/animation/AnimBlendAssociation.cpp similarity index 98% rename from src/animation/AnimBlendAssociation.cpp rename to src/liberty/animation/AnimBlendAssociation.cpp index fcaa84fa..f1b5face 100644 --- a/src/animation/AnimBlendAssociation.cpp +++ b/src/liberty/animation/AnimBlendAssociation.cpp @@ -55,6 +55,8 @@ void CAnimBlendAssociation::FreeAnimBlendNodeArray(void) { assert(nodes != nil); + for(unsigned i = 0; i < numNodes; i++) + nodes[i].Destroy(); RwFreeAlign(nodes); } diff --git a/src/animation/AnimBlendAssociation.h b/src/liberty/animation/AnimBlendAssociation.h similarity index 100% rename from src/animation/AnimBlendAssociation.h rename to src/liberty/animation/AnimBlendAssociation.h diff --git a/src/animation/AnimBlendClumpData.cpp b/src/liberty/animation/AnimBlendClumpData.cpp similarity index 100% rename from src/animation/AnimBlendClumpData.cpp rename to src/liberty/animation/AnimBlendClumpData.cpp diff --git a/src/animation/AnimBlendClumpData.h b/src/liberty/animation/AnimBlendClumpData.h similarity index 100% rename from src/animation/AnimBlendClumpData.h rename to src/liberty/animation/AnimBlendClumpData.h diff --git a/src/liberty/animation/AnimBlendHierarchy.cpp b/src/liberty/animation/AnimBlendHierarchy.cpp new file mode 100644 index 00000000..462c387a --- /dev/null +++ b/src/liberty/animation/AnimBlendHierarchy.cpp @@ -0,0 +1,57 @@ +#include "common.h" + +#include "AnimBlendSequence.h" +#include "AnimBlendHierarchy.h" + +CAnimBlendHierarchy::CAnimBlendHierarchy(void) +{ + sequences = nil; + numSequences = 0; + totalLength = 0.0f; +} + +void +CAnimBlendHierarchy::Shutdown(void) +{ + RemoveAnimSequences(); +} + +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++){ +#ifdef FIX_BUGS + if(sequences[i].numFrames == 0) + continue; +#endif + float seqTime = sequences[i].GetEndTime(); + totalLength = Max(totalLength, seqTime); + } +} + +void +CAnimBlendHierarchy::RemoveAnimSequences(void) +{ + delete[] sequences; + numSequences = 0; +} + +#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/liberty/animation/AnimBlendHierarchy.h similarity index 100% rename from src/animation/AnimBlendHierarchy.h rename to src/liberty/animation/AnimBlendHierarchy.h diff --git a/src/animation/AnimBlendList.h b/src/liberty/animation/AnimBlendList.h similarity index 100% rename from src/animation/AnimBlendList.h rename to src/liberty/animation/AnimBlendList.h diff --git a/src/liberty/animation/AnimBlendNode.cpp b/src/liberty/animation/AnimBlendNode.cpp new file mode 100644 index 00000000..c54a83a6 --- /dev/null +++ b/src/liberty/animation/AnimBlendNode.cpp @@ -0,0 +1,189 @@ +#include "common.h" + +#include "AnimBlendAssociation.h" +#include "AnimBlendNode.h" + +void +CAnimBlendNode::Init(void) +{ + frameA = -1; + remainingTime = 0.0f; + sequence = nil; + association = nil; + player = nil; +} + +void CAnimBlendNode::Destroy(void) { + if (player) { + delete player; + player = nil; + } +} + +bool +CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight) +{ + assert (player && player->keyFrames == sequence->keyFrames); + + 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){ + float kfAdt = player->GetNextTimeDelta(); + float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt; + if(player->type & CAnimBlendSequence::KF_TRANS){ + auto kfdAt = player->GetNextTranslationDelta(); + auto kfBt = player->GetPrevTranslation(); + trans = kfBt + t*kfdAt; + trans *= blend; + } + if(player->type & CAnimBlendSequence::KF_ROT){ + auto kfAr = player->GetNextRotation(); + auto kfBr = player->GetPrevRotation(); + rot.Slerp(kfBr, kfAr, theta, invSin, t); + rot *= blend; + } + } + + return looped; +} + +bool +CAnimBlendNode::NextKeyFrame(void) +{ + assert(player != nil); + bool looped; + + if(player->numFrames <= 1) + return false; + + looped = false; + + // Advance as long as we have to + while(remainingTime <= 0.0f){ + frameA++; + + if(frameA >= player->numFrames){ + // reached end of animation + if(!association->IsRepeating()){ + frameA--; + remainingTime = 0.0f; + assert(frameA == player->curFrame); + CalcDeltas(); + return false; + } + looped = true; + frameA = 0; + } + player->AdvanceFrame(); + remainingTime += player->GetNextTimeDelta(); + } + + assert(frameA == player->curFrame); + CalcDeltas(); + return looped; +} + +// Set animation to time t +bool +CAnimBlendNode::FindKeyFrame(float t) +{ + if (player == nil) { + player = new CAnimBlendPlayer(); + player->Init(sequence->keyFrames, sequence->type, sequence->numFrames); + } + if(player->numFrames < 1) + return false; + + frameA = 0; + player->SeekToStart(); + + assert (player->keyFrames == sequence->keyFrames); + + if(player->numFrames == 1){ + remainingTime = 0.0f; + }else{ + // advance until t is between frameB and frameA + frameA++; + player->AdvanceFrame(); + while (t > player->GetNextTimeDelta()) { + t -= player->GetNextTimeDelta(); + if (frameA + 1 >= player->numFrames) { + // reached end of animation + if (!association->IsRepeating()) { + assert(frameA == player->curFrame); + CalcDeltas(); + remainingTime = 0.0f; + return false; + } + // Frame 0 is effectively skipped here + // Looks like an re3 / game bug? + frameA = 0; + player->SeekToStart(); + } + frameA++; + player->AdvanceFrame(); + } + + remainingTime = player->GetNextTimeDelta() - t; + } + + assert(frameA == player->curFrame); + CalcDeltas(); + return true; +} + +void +CAnimBlendNode::CalcDeltas(void) +{ + if((player->type & CAnimBlendSequence::KF_ROT) == 0) + return; + auto kfAr = player->GetNextRotation(); + auto kfBr = player->GetPrevRotation(); + float cos = DotProduct(kfAr, kfBr); + 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){ + auto kfAdt = player->GetNextTimeDelta(); + float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt; + if(player->type & CAnimBlendSequence::KF_TRANS){ + auto kfdAt = player->GetNextTranslationDelta(); + auto kfBt = player->GetPrevTranslation(); + trans = kfBt + t*kfdAt; + 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){ + if(player->type & CAnimBlendSequence::KF_TRANS){ + CVector pos = sequence->GetEndTranslation(); + trans = pos * blend; + } + } +} diff --git a/src/animation/AnimBlendNode.h b/src/liberty/animation/AnimBlendNode.h similarity index 94% rename from src/animation/AnimBlendNode.h rename to src/liberty/animation/AnimBlendNode.h index 89924d6a..71ea6b0c 100644 --- a/src/animation/AnimBlendNode.h +++ b/src/liberty/animation/AnimBlendNode.h @@ -13,12 +13,13 @@ public: 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 + CAnimBlendPlayer* player; CAnimBlendSequence *sequence; CAnimBlendAssociation *association; void Init(void); + void Destroy(void); bool Update(CVector &trans, CQuaternion &rot, float weight); bool NextKeyFrame(void); bool FindKeyFrame(float t); diff --git a/src/animation/AnimBlendSequence.cpp b/src/liberty/animation/AnimBlendSequence.cpp similarity index 53% rename from src/animation/AnimBlendSequence.cpp rename to src/liberty/animation/AnimBlendSequence.cpp index 0718f37c..200f7c77 100644 --- a/src/animation/AnimBlendSequence.cpp +++ b/src/liberty/animation/AnimBlendSequence.cpp @@ -25,45 +25,6 @@ CAnimBlendSequence::SetName(char *name) strncpy(this->name, name, 24); } -void -CAnimBlendSequence::SetNumFrames(int numFrames, bool translation, bool compress) -{ - int sz; - - if(translation){ - type |= KF_ROT | KF_TRANS; - if (compress) { - type |= KF_COMPRESSED; - sz = sizeof(KeyFrameTransCompressed); - } else { - sz = sizeof(KeyFrameTransUncompressed); - } - }else{ - sz = sizeof(KeyFrame); - type |= KF_ROT; - } - keyFrames = RwMalloc(sz * numFrames); - this->numFrames = numFrames; -} - -void -CAnimBlendSequence::RemoveQuaternionFlips(void) -{ - int i; - CQuaternion last; - - if(numFrames < 2) - return; - - last = GetRotation(0); - for(i = 1; i < numFrames; i++){ - auto KFr = GetRotation(i); - if(DotProduct(last, KFr) < 0.0f) - SetRotation(i, -KFr); - last = GetRotation(i); - } -} - #ifdef USE_CUSTOM_ALLOCATOR bool CAnimBlendSequence::MoveMemory(void) diff --git a/src/liberty/animation/AnimBlendSequence.h b/src/liberty/animation/AnimBlendSequence.h new file mode 100644 index 00000000..2fefc63b --- /dev/null +++ b/src/liberty/animation/AnimBlendSequence.h @@ -0,0 +1,402 @@ +#pragma once + +#include "Quaternion.h" + +#ifdef MoveMemory +#undef MoveMemory // windows shit +#endif +struct CAnimBlendPlayer { + enum { + KF_ROT = 1, + KF_TRANS = 2, + + FLAGS_HAS_ROT_Y = 1 << 8, + FLAGS_HAS_ROT_P = 1 << 9, + FLAGS_HAS_ROT_R = 1 << 10, + + FLAGS_HAS_TRANS_X = 1 << 11, + FLAGS_HAS_TRANS_Y = 1 << 12, + FLAGS_HAS_TRANS_Z = 1 << 13, + FLAGS_HAS_TRANS_ANY = 7 << 11, + FLAGS_HAS_TRANS_LARGE = 1 << 14, + FLAGS_QUAT0_NEG = 1 << 15 + }; + + int32 type; + void* keyFrames; + int32 curFrame; + int32 numFrames; + CQuaternion currentRotation; + CQuaternion nextRotation; + CVector currentTranslation; + CVector nextTranslation; + + unsigned readOffset; + unsigned readOffset_initial; + + uint16_t predicted_y, predicted_p, predicted_r; + float predicted_tx = 0, predicted_ty = 0, predicted_tz = 0; + float nextDeltaTime; + + template + T read_unaligned(uint32_t ro) { + T rv; + for (unsigned i = 0; i < sizeof(T); i++) { + ((uint8_t*)&rv)[i] = ((uint8_t*)keyFrames)[ro]; + ro++; + } + readOffset = ro; + return rv; + } + template + __always_inline T read() { + if (!(readOffset & (sizeof(T) -1))) { + return read_aligned(); + } else { + return read_unaligned(readOffset); + } + } + + template + __always_inline T read_aligned() { + T rv; + rv = *(T*)((uint8_t*)keyFrames + readOffset); + readOffset += sizeof(T); + return rv; + } + + __always_inline CQuaternion fromSphericalFixed(uint16_t y, uint16_t p, uint16_t r) { + CQuaternion q; + #if !defined(DC_SH4) + q.w = cos((y / 65536.0f) * 2 * M_PI) * cos((p / 65536.0f) * 2 * M_PI); + q.x = cos((y / 65536.0f) * 2 * M_PI) * sin((p / 65536.0f) * 2 * M_PI); + q.y = sin((y / 65536.0f) * 2 * M_PI) * cos((r / 65536.0f) * 2 * M_PI); + q.z = sin((y / 65536.0f) * 2 * M_PI) * sin((r / 65536.0f) * 2 * M_PI); + #else + register float __ys __asm__("fr0"); + register float __yc __asm__("fr1"); + register float __ps __asm__("fr2"); + register float __pc __asm__("fr3"); + register float __rs __asm__("fr4"); + register float __rc __asm__("fr5"); + + __asm__ __volatile__( + R"( + lds %[y],fpul + fsca fpul, dr0 + lds %[p],fpul + fsca fpul, dr2 + lds %[r],fpul + fsca fpul, dr4 + )" + : "=f" (__ys), "=f" (__yc), "=f" (__ps), "=f" (__pc), "=f" (__rs), "=f" (__rc) + : "0" (__ys), "1" (__yc), "2" (__ps), "3" (__pc), "4" (__rs), "5" (__rc), [y]"r"(y), [p]"r"(p), [r]"r"(r)); + + q.w = __yc * __pc; + q.x = __yc * __ps; + q.y = __ys * __rc; + q.z = __ys * __rs; + #endif + return q; + } + + void AdvanceFrame() { + if (++curFrame == numFrames){ + currentRotation = nextRotation; + currentTranslation = nextTranslation; + SeekToStart(); + return; + } + + // rotation + { + currentRotation = nextRotation; + + // For rotation Y: + if (type & FLAGS_HAS_ROT_Y) { + uint8_t byteVal = read(); + if (byteVal == 128) { + predicted_y = read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_y += diff * 8; + } + } + // For rotation P: + if (type & FLAGS_HAS_ROT_P) { + uint8_t byteVal = read(); + if (byteVal == 128) { + predicted_p = read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_p += diff * 8; + } + } + // For rotation R: + if (type & FLAGS_HAS_ROT_R) { + uint8_t byteVal = read(); + if (byteVal == 128) { + predicted_r = read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_r += diff * 8; + } + } + + nextRotation = fromSphericalFixed(predicted_y, predicted_p, predicted_r); + } + + // translation + if (type & KF_TRANS) { + currentTranslation = nextTranslation; + if (type & FLAGS_HAS_TRANS_X) { + uint8_t byteVal = read(); + if (byteVal == 128) { + uint16_t diff = read(); + if (diff != 32768) { + predicted_tx += static_cast(diff) / 128.f; + } else { + predicted_tx = read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_tx += diff / 127.f; + } + } + // Translation Y: + if (type & FLAGS_HAS_TRANS_Y) { + uint8_t byteVal = read(); + if (byteVal == 128) { + uint16_t diff = read(); + if (diff != 32768) { + predicted_ty += static_cast(diff) / 128.f; + } else { + predicted_ty = read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_ty += diff / 127.f; + } + } + // Translation Z: + if (type & FLAGS_HAS_TRANS_Z) { + uint8_t byteVal = read(); + if (byteVal == 128) { + uint16_t diff = read(); + if (diff != 32768) { + predicted_tz += static_cast(diff) / 128.f; + } else { + predicted_tz = read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_tz += diff / 127.f; + } + } + + nextTranslation = { predicted_tx, predicted_ty, predicted_tz }; + } + + // time delta + quaternion flips + { + uint8_t byteValPacked = read(); + uint8_t byteVal = byteValPacked & 127; + float diff; + if (byteVal == 127) { + uint16_t fixed_diff = read(); + diff = fixed_diff / 256.f; + } else { + diff = byteVal / 256.f; + } + nextDeltaTime = diff; + + if (byteValPacked & 128) { + nextRotation = -nextRotation; + } + } + } + + CQuaternion GetPrevRotation() { + return currentRotation; + } + CQuaternion GetNextRotation() { + return nextRotation; + } + float GetNextTimeDelta() { + return nextDeltaTime; + } + + CVector GetPrevTranslation() { + return currentTranslation; + } + CVector GetNextTranslationDelta() { + return nextTranslation - currentTranslation; + } + + void Init(void* kf, int32 tp, int nF) { + keyFrames = kf; + type = tp; + numFrames = nF; + + SeekToStart(); + currentTranslation = nextTranslation; + currentRotation = nextRotation; + } + + void SeekToStart() { + readOffset = 0; + float startTime = read_aligned(); + float endTime = read_aligned(); + + if (type & KF_TRANS) { + CVector startTranslation; + if (type & FLAGS_HAS_TRANS_LARGE) { + startTranslation.x = read_aligned(); + startTranslation.y = read_aligned(); + startTranslation.z = read_aligned(); + predicted_tx = startTranslation.x; + predicted_ty = startTranslation.y; + predicted_tz = startTranslation.z; + + CVector endTranslation; + // Read final translation (may be used for verification or ignored) + endTranslation.x = read_aligned(); + endTranslation.y = read_aligned(); + endTranslation.z = read_aligned(); + } else { + startTranslation.x = read_aligned() / 128.f; + startTranslation.y = read_aligned() / 128.f; + startTranslation.z = read_aligned() / 128.f; + predicted_tx = startTranslation.x; + predicted_ty = startTranslation.y; + predicted_tz = startTranslation.z; + + CVector endTranslation; + // Read final translation (for completeness) + endTranslation.x = read_aligned() / 128.f; + endTranslation.y = read_aligned() / 128.f; + endTranslation.z = read_aligned() / 128.f; + } + + nextTranslation = startTranslation; + } else { + CVector startTranslation = { 0, 0, 0 }; + CVector endTranslation = { 0, 0, 0 }; + nextTranslation = startTranslation; + } + + predicted_y = read_aligned(); + predicted_p = read_aligned(); + predicted_r = read_aligned(); + nextRotation = fromSphericalFixed(predicted_y, predicted_p, predicted_r); + + if (type & FLAGS_QUAT0_NEG) { + nextRotation = -nextRotation; + } + + nextDeltaTime = startTime; + curFrame = 0; + } +}; + + +// The sequence of key frames of one animated node +class CAnimBlendSequence +{ + template + __always_inline T read_aligned(uint32_t &readOffset) { + T rv; + rv = *(T*)((uint8_t*)keyFrames + readOffset); + readOffset += sizeof(T); + return rv; + } +public: + enum { + KF_ROT = 1, + KF_TRANS = 2, + + FLAGS_HAS_ROT_Y = 1 << 8, + FLAGS_HAS_ROT_P = 1 << 9, + FLAGS_HAS_ROT_R = 1 << 10, + + FLAGS_HAS_TRANS_X = 1 << 11, + FLAGS_HAS_TRANS_Y = 1 << 12, + FLAGS_HAS_TRANS_Z = 1 << 13, + FLAGS_HAS_TRANS_ANY = 7 << 11, + FLAGS_HAS_TRANS_LARGE = 1 << 14, + FLAGS_QUAT0_NEG = 1 << 15 + }; + int32 type; + char name[24]; + int32 numFrames; +#ifdef PED_SKIN + int16 boneTag; +#endif + void *keyFrames; + + + struct InitData { + CVector startTranslation, endTranslation; + float endTime; + }; + + __always_inline InitData GetInitData() { + InitData rv; + uint32_t readOffset = 0; + + float startTime = read_aligned(readOffset); + rv.endTime = read_aligned(readOffset); + + if (type & KF_TRANS) { + if (type & FLAGS_HAS_TRANS_LARGE) { + rv.startTranslation.x = read_aligned(readOffset); + rv.startTranslation.y = read_aligned(readOffset); + rv.startTranslation.z = read_aligned(readOffset); + + // Read final translation (may be used for verification or ignored) + rv.endTranslation.x = read_aligned(readOffset); + rv.endTranslation.y = read_aligned(readOffset); + rv.endTranslation.z = read_aligned(readOffset); + } else { + rv.startTranslation.x = read_aligned(readOffset) / 128.f; + rv.startTranslation.y = read_aligned(readOffset) / 128.f; + rv.startTranslation.z = read_aligned(readOffset) / 128.f; + + // Read final translation (for completeness) + rv.endTranslation.x = read_aligned(readOffset) / 128.f; + rv.endTranslation.y = read_aligned(readOffset) / 128.f; + rv.endTranslation.z = read_aligned(readOffset) / 128.f; + } + } else { + rv.startTranslation = { 0, 0, 0 }; + rv.endTranslation = { 0, 0, 0 }; + } + + return rv; + } + + + CVector GetStartTranslation() { + return GetInitData().startTranslation; + } + float GetEndTime() { + return GetInitData().endTime; + } + CVector GetEndTranslation() { + return GetInitData().endTranslation; + } + + CAnimBlendSequence(void); + virtual ~CAnimBlendSequence(void); + void SetName(char *name); + + bool HasTranslation(void) { return !!(type & KF_TRANS); } + 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/liberty/animation/AnimManager.cpp similarity index 87% rename from src/animation/AnimManager.cpp rename to src/liberty/animation/AnimManager.cpp index b3889328..ec146853 100644 --- a/src/animation/AnimManager.cpp +++ b/src/liberty/animation/AnimManager.cpp @@ -780,134 +780,70 @@ CAnimManager::LoadAnimFile(int fd, bool compress) char ident[4]; uint32 size; }; - IfpHeader anpk, info, name, dgan, cpan, anim; - int numANPK; + IfpHeader anpv; char buf[256]; - int i, j, k, l; + int 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; - } + CFileMgr::Read(fd, (char*)&anpv, sizeof(IfpHeader)); + assert(memcmp(anpv.ident, "ANPV", 4) == 0); + + // block name + CFileMgr::Read(fd, buf, anpv.size); + CAnimBlock *animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; + strncpy(animBlock->name, buf, 24); + int32_t numAnims; + CFileMgr::Read(fd, (char*)&numAnims, sizeof(numAnims)); + animBlock->numAnims = numAnims; - 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; - animBlock->firstIndex = ms_numAnimations; + for(j = 0; j < animBlock->numAnims; j++){ + CAnimBlendHierarchy *hier = &ms_aAnimations[ms_numAnimations++]; - for(j = 0; j < animBlock->numAnims; j++){ - CAnimBlendHierarchy *hier = &ms_aAnimations[ms_numAnimations++]; + // animation name + int32_t animNameLength; + CFileMgr::Read(fd, (char*)&animNameLength, sizeof(animNameLength)); + CFileMgr::Read(fd, buf, animNameLength); + hier->SetName(buf); - // animation name - CFileMgr::Read(fd, (char*)&name, sizeof(IfpHeader)); - ROUNDSIZE(name.size); - CFileMgr::Read(fd, buf, name.size); - hier->SetName(buf); + int32_t numSeqs; + CFileMgr::Read(fd, (char*)&numSeqs, sizeof(animNameLength)); + hier->numSequences = numSeqs; + hier->sequences = new CAnimBlendSequence[hier->numSequences]; - // 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 + int32_t seqNameLength; + CFileMgr::Read(fd, (char*)&seqNameLength, sizeof(seqNameLength)); + CFileMgr::Read(fd, buf, seqNameLength); + seq->SetName(buf); - 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); + int32_t numFrames; + CFileMgr::Read(fd, (char*)&numFrames, sizeof(numFrames)); + seq->numFrames = numFrames; + int32_t boneTag; + CFileMgr::Read(fd, (char*)&boneTag, sizeof(boneTag)); + #ifdef PED_SKIN - if(anim.size == 44) - seq->SetBoneTag(*(int*)(buf+40)); + seq->SetBoneTag(boneTag); #endif - if(numFrames == 0) - continue; + 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, compress); - }else if(!CGeneral::faststrncmp(info.ident, "KRT0", 4)) { - hasTranslation = true; - seq->SetNumFrames(numFrames, true, compress); - }else if(!CGeneral::faststrncmp(info.ident, "KR00", 4)){ - seq->SetNumFrames(numFrames, false, compress); - } + uint32_t dataSize; + CFileMgr::Read(fd, (char*)&dataSize, sizeof(dataSize)); + uint16_t flags; + CFileMgr::Read(fd, (char*)&flags, sizeof(flags)); - float *frameTimes = (float*)RwMalloc(sizeof(float) * numFrames); - - 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]); - - seq->SetRotation(l, rot); - seq->SetTranslation(l, trans); - // scaling ignored - frameTimes[l] = 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]); - - seq->SetRotation(l, rot); - seq->SetTranslation(l, trans); - frameTimes[l] = fbuf[7]; // absolute time here - }else{ - CFileMgr::Read(fd, buf, 0x14); - CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); - rot.Invert(); - - seq->SetRotation(l, rot); - frameTimes[l] = fbuf[4]; // absolute time here - } - } - - // convert absolute time to deltas - float running_sum = 0.0f; - for (l = 0; l < numFrames; l++) { - auto dt = frameTimes[l] - running_sum; - seq->SetDeltaTime(l, dt); - assert(seq->GetDeltaTime(l) <= dt); - running_sum += seq->GetDeltaTime(l); - // if (seq->GetDeltaTime(l) == 0.0f && dt != 0.0f) { - // seq->SetDeltaTime(l, KF_MINDELTA); - // } - - // assert(seq->GetDeltaTime(l) != 0.0f || dt == 0.0f); - } - RwFree(frameTimes); - } - - hier->RemoveQuaternionFlips(); - hier->CalcTotalTime(); + seq->keyFrames = RwMalloc(dataSize); + assert(seq->keyFrames); + CFileMgr::Read(fd, (char*)seq->keyFrames, dataSize - sizeof(flags)); + seq->type = flags; } + + hier->CalcTotalTime(); } } diff --git a/src/animation/AnimManager.h b/src/liberty/animation/AnimManager.h similarity index 100% rename from src/animation/AnimManager.h rename to src/liberty/animation/AnimManager.h diff --git a/src/animation/AnimationId.h b/src/liberty/animation/AnimationId.h similarity index 100% rename from src/animation/AnimationId.h rename to src/liberty/animation/AnimationId.h diff --git a/src/animation/Bones.cpp b/src/liberty/animation/Bones.cpp similarity index 100% rename from src/animation/Bones.cpp rename to src/liberty/animation/Bones.cpp diff --git a/src/animation/Bones.h b/src/liberty/animation/Bones.h similarity index 100% rename from src/animation/Bones.h rename to src/liberty/animation/Bones.h diff --git a/src/animation/CutsceneMgr.cpp b/src/liberty/animation/CutsceneMgr.cpp similarity index 99% rename from src/animation/CutsceneMgr.cpp rename to src/liberty/animation/CutsceneMgr.cpp index ade0bba9..98823224 100644 --- a/src/animation/CutsceneMgr.cpp +++ b/src/liberty/animation/CutsceneMgr.cpp @@ -267,7 +267,7 @@ CCutsceneMgr::SetupCutsceneToStart(void) 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 + pAnimBlendAssoc->hierarchy->sequences[0].GetTranslation(0)); + ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + pAnimBlendAssoc->hierarchy->sequences[0].GetStartTranslation()); CWorld::Add(ms_pCutsceneObjects[i]); pAnimBlendAssoc->SetRun(); } else { diff --git a/src/animation/CutsceneMgr.h b/src/liberty/animation/CutsceneMgr.h similarity index 100% rename from src/animation/CutsceneMgr.h rename to src/liberty/animation/CutsceneMgr.h diff --git a/src/animation/FrameUpdate.cpp b/src/liberty/animation/FrameUpdate.cpp similarity index 100% rename from src/animation/FrameUpdate.cpp rename to src/liberty/animation/FrameUpdate.cpp diff --git a/src/animation/RpAnimBlend.cpp b/src/liberty/animation/RpAnimBlend.cpp similarity index 100% rename from src/animation/RpAnimBlend.cpp rename to src/liberty/animation/RpAnimBlend.cpp diff --git a/src/animation/RpAnimBlend.h b/src/liberty/animation/RpAnimBlend.h similarity index 100% rename from src/animation/RpAnimBlend.h rename to src/liberty/animation/RpAnimBlend.h diff --git a/src/audio/AudioCollision.cpp b/src/liberty/audio/AudioCollision.cpp similarity index 100% rename from src/audio/AudioCollision.cpp rename to src/liberty/audio/AudioCollision.cpp diff --git a/src/audio/AudioCollision.h b/src/liberty/audio/AudioCollision.h similarity index 100% rename from src/audio/AudioCollision.h rename to src/liberty/audio/AudioCollision.h diff --git a/src/audio/AudioLogic.cpp b/src/liberty/audio/AudioLogic.cpp similarity index 99% rename from src/audio/AudioLogic.cpp rename to src/liberty/audio/AudioLogic.cpp index 4624d83a..e9834efa 100644 --- a/src/audio/AudioLogic.cpp +++ b/src/liberty/audio/AudioLogic.cpp @@ -3789,6 +3789,10 @@ cAudioManager::ProcessPedOneShots(cPedParams ¶ms) if (iSound > 60) iSound = 21; } + // In some cases the left and right channels have different loop points + // This looks like a data file issue where the left and right channels have different loop points + // This is a hot fix to always have the correct loop point for each channel individually + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) AddSampleToRequestedQueue(); } } @@ -6302,7 +6306,7 @@ cPedComments::Process() { case LOADING_STATUS_NOT_LOADED: SampleManager.LoadPedComment(sampleIndex); -#ifdef GTA_PS2 // on PC ped comment is loaded at once +#if defined(GTA_PS2) || defined(RW_DC) // on PC ped comment is loaded at once break; #endif case LOADING_STATUS_LOADED: diff --git a/src/audio/AudioManager.cpp b/src/liberty/audio/AudioManager.cpp similarity index 100% rename from src/audio/AudioManager.cpp rename to src/liberty/audio/AudioManager.cpp diff --git a/src/audio/AudioManager.h b/src/liberty/audio/AudioManager.h similarity index 100% rename from src/audio/AudioManager.h rename to src/liberty/audio/AudioManager.h diff --git a/src/audio/AudioSamples.h b/src/liberty/audio/AudioSamples.h similarity index 100% rename from src/audio/AudioSamples.h rename to src/liberty/audio/AudioSamples.h diff --git a/src/audio/AudioScriptObject.cpp b/src/liberty/audio/AudioScriptObject.cpp similarity index 100% rename from src/audio/AudioScriptObject.cpp rename to src/liberty/audio/AudioScriptObject.cpp diff --git a/src/audio/AudioScriptObject.h b/src/liberty/audio/AudioScriptObject.h similarity index 100% rename from src/audio/AudioScriptObject.h rename to src/liberty/audio/AudioScriptObject.h diff --git a/src/audio/DMAudio.cpp b/src/liberty/audio/DMAudio.cpp similarity index 100% rename from src/audio/DMAudio.cpp rename to src/liberty/audio/DMAudio.cpp diff --git a/src/audio/DMAudio.h b/src/liberty/audio/DMAudio.h similarity index 100% rename from src/audio/DMAudio.h rename to src/liberty/audio/DMAudio.h diff --git a/src/audio/MusicManager.cpp b/src/liberty/audio/MusicManager.cpp similarity index 100% rename from src/audio/MusicManager.cpp rename to src/liberty/audio/MusicManager.cpp diff --git a/src/audio/MusicManager.h b/src/liberty/audio/MusicManager.h similarity index 100% rename from src/audio/MusicManager.h rename to src/liberty/audio/MusicManager.h diff --git a/src/audio/PolRadio.cpp b/src/liberty/audio/PolRadio.cpp similarity index 100% rename from src/audio/PolRadio.cpp rename to src/liberty/audio/PolRadio.cpp diff --git a/src/audio/PolRadio.h b/src/liberty/audio/PolRadio.h similarity index 100% rename from src/audio/PolRadio.h rename to src/liberty/audio/PolRadio.h diff --git a/src/audio/audio_enums.h b/src/liberty/audio/audio_enums.h similarity index 100% rename from src/audio/audio_enums.h rename to src/liberty/audio/audio_enums.h diff --git a/src/audio/eax/eax-util.cpp b/src/liberty/audio/eax/eax-util.cpp similarity index 100% rename from src/audio/eax/eax-util.cpp rename to src/liberty/audio/eax/eax-util.cpp diff --git a/src/audio/eax/eax-util.h b/src/liberty/audio/eax/eax-util.h similarity index 100% rename from src/audio/eax/eax-util.h rename to src/liberty/audio/eax/eax-util.h diff --git a/src/audio/eax/eax.h b/src/liberty/audio/eax/eax.h similarity index 100% rename from src/audio/eax/eax.h rename to src/liberty/audio/eax/eax.h diff --git a/src/audio/oal/aldlist.cpp b/src/liberty/audio/oal/aldlist.cpp similarity index 100% rename from src/audio/oal/aldlist.cpp rename to src/liberty/audio/oal/aldlist.cpp diff --git a/src/audio/oal/aldlist.h b/src/liberty/audio/oal/aldlist.h similarity index 100% rename from src/audio/oal/aldlist.h rename to src/liberty/audio/oal/aldlist.h diff --git a/src/audio/oal/channel.cpp b/src/liberty/audio/oal/channel.cpp similarity index 100% rename from src/audio/oal/channel.cpp rename to src/liberty/audio/oal/channel.cpp diff --git a/src/audio/oal/channel.h b/src/liberty/audio/oal/channel.h similarity index 100% rename from src/audio/oal/channel.h rename to src/liberty/audio/oal/channel.h diff --git a/src/audio/oal/oal_utils.cpp b/src/liberty/audio/oal/oal_utils.cpp similarity index 100% rename from src/audio/oal/oal_utils.cpp rename to src/liberty/audio/oal/oal_utils.cpp diff --git a/src/audio/oal/oal_utils.h b/src/liberty/audio/oal/oal_utils.h similarity index 100% rename from src/audio/oal/oal_utils.h rename to src/liberty/audio/oal/oal_utils.h diff --git a/src/audio/oal/stream.cpp b/src/liberty/audio/oal/stream.cpp similarity index 100% rename from src/audio/oal/stream.cpp rename to src/liberty/audio/oal/stream.cpp diff --git a/src/audio/oal/stream.h b/src/liberty/audio/oal/stream.h similarity index 100% rename from src/audio/oal/stream.h rename to src/liberty/audio/oal/stream.h diff --git a/src/audio/sampman.h b/src/liberty/audio/sampman.h similarity index 83% rename from src/audio/sampman.h rename to src/liberty/audio/sampman.h index 70f1b47a..00c445ae 100644 --- a/src/audio/sampman.h +++ b/src/liberty/audio/sampman.h @@ -174,6 +174,10 @@ public: void UpdateEffectsVolume(void); +#ifdef DC_SH4 + void UpdateChannelVolume(uint32 nChannel); +#endif + void SetEffectsMasterVolume(uint8 nVolume); void SetMusicMasterVolume (uint8 nVolume); void SetEffectsFadeVolume (uint8 nVolume); @@ -474,6 +478,7 @@ static char PS2StreamedNameTable[][25]= }; #endif +#ifdef PC_AUDIO_PATHS static char StreamedNameTable[][25] = { "AUDIO\\HEAD.WAV", @@ -673,204 +678,6 @@ static char StreamedNameTable[][25] = "AUDIO\\k1_b.WAV", "AUDIO\\cat1.WAV" }; +#endif -static char DCStreamedNameTable[][25] = -{ - "stream/HEAD.APM", - "stream/CLASS.APM", - "stream/KJAH.APM", - "stream/RISE.APM", - "stream/LIPS.APM", - "stream/GAME.APM", - "stream/MSX.APM", - "stream/FLASH.APM", - "stream/CHAT.APM", - "stream/HEAD.APM", - "stream/POLICE.APM", - "stream/CITY.APM", - "stream/WATER.APM", - "stream/COMOPEN.APM", - "stream/SUBOPEN.APM", - "stream/JB.APM", - "stream/BET.APM", - "stream/L1_LG.APM", - "stream/L2_DSB.APM", - "stream/L3_DM.APM", - "stream/L4_PAP.APM", - "stream/L5_TFB.APM", - "stream/J0_DM2.APM", - "stream/J1_LFL.APM", - "stream/J2_KCL.APM", - "stream/J3_VH.APM", - "stream/J4_ETH.APM", - "stream/J5_DST.APM", - "stream/J6_TBJ.APM", - "stream/T1_TOL.APM", - "stream/T2_TPU.APM", - "stream/T3_MAS.APM", - "stream/T4_TAT.APM", - "stream/T5_BF.APM", - "stream/S0_MAS.APM", - "stream/S1_PF.APM", - "stream/S2_CTG.APM", - "stream/S3_RTC.APM", - "stream/S5_LRQ.APM", - "stream/S4_BDBA.APM", - "stream/S4_BDBB.APM", - "stream/S2_CTG2.APM", - "stream/S4_BDBD.APM", - "stream/S5_LRQB.APM", - "stream/S5_LRQC.APM", - "stream/A1_SSO.APM", - "stream/A2_PP.APM", - "stream/A3_SS.APM", - "stream/A4_PDR.APM", - "stream/A5_K2FT.APM", - "stream/K1_KBO.APM", - "stream/K2_GIS.APM", - "stream/K3_DS.APM", - "stream/K4_SHI.APM", - "stream/K5_SD.APM", - "stream/R0_PDR2.APM", - "stream/R1_SW.APM", - "stream/R2_AP.APM", - "stream/R3_ED.APM", - "stream/R4_GF.APM", - "stream/R5_PB.APM", - "stream/R6_MM.APM", - "stream/D1_STOG.APM", - "stream/D2_KK.APM", - "stream/D3_ADO.APM", - "stream/D5_ES.APM", - "stream/D7_MLD.APM", - "stream/D4_GTA.APM", - "stream/D4_GTA2.APM", - "stream/D6_STS.APM", - "stream/A6_BAIT.APM", - "stream/A7_ETG.APM", - "stream/A8_PS.APM", - "stream/A9_ASD.APM", - "stream/K4_SHI2.APM", - "stream/C1_TEX.APM", - "stream/EL_PH1.APM", - "stream/EL_PH2.APM", - "stream/EL_PH3.APM", - "stream/EL_PH4.APM", - "stream/YD_PH1.APM", - "stream/YD_PH2.APM", - "stream/YD_PH3.APM", - "stream/YD_PH4.APM", - "stream/HD_PH1.APM", - "stream/HD_PH2.APM", - "stream/HD_PH3.APM", - "stream/HD_PH4.APM", - "stream/HD_PH5.APM", - "stream/MT_PH1.APM", - "stream/MT_PH2.APM", - "stream/MT_PH3.APM", - "stream/MT_PH4.APM", - "stream/MISCOM.APM", - "stream/END.APM", - "stream/lib_a1.APM", - "stream/lib_a2.APM", - "stream/lib_a.APM", - "stream/lib_b.APM", - "stream/lib_c.APM", - "stream/lib_d.APM", - "stream/l2_a.APM", - "stream/j4t_1.APM", - "stream/j4t_2.APM", - "stream/j4t_3.APM", - "stream/j4t_4.APM", - "stream/j4_a.APM", - "stream/j4_b.APM", - "stream/j4_c.APM", - "stream/j4_d.APM", - "stream/j4_e.APM", - "stream/j4_f.APM", - "stream/j6_1.APM", - "stream/j6_a.APM", - "stream/j6_b.APM", - "stream/j6_c.APM", - "stream/j6_d.APM", - "stream/t4_a.APM", - "stream/s1_a.APM", - "stream/s1_a1.APM", - "stream/s1_b.APM", - "stream/s1_c.APM", - "stream/s1_c1.APM", - "stream/s1_d.APM", - "stream/s1_e.APM", - "stream/s1_f.APM", - "stream/s1_g.APM", - "stream/s1_h.APM", - "stream/s1_i.APM", - "stream/s1_j.APM", - "stream/s1_k.APM", - "stream/s1_l.APM", - "stream/s3_a.APM", - "stream/s3_b.APM", - "stream/el3_a.APM", - "stream/mf1_a.APM", - "stream/mf2_a.APM", - "stream/mf3_a.APM", - "stream/mf3_b.APM", - "stream/mf3_b1.APM", - "stream/mf3_c.APM", - "stream/mf4_a.APM", - "stream/mf4_b.APM", - "stream/mf4_c.APM", - "stream/a1_a.APM", - "stream/a3_a.APM", - "stream/a5_a.APM", - "stream/a4_a.APM", - "stream/a4_b.APM", - "stream/a4_c.APM", - "stream/a4_d.APM", - "stream/k1_a.APM", - "stream/k3_a.APM", - "stream/r1_a.APM", - "stream/r2_a.APM", - "stream/r2_b.APM", - "stream/r2_c.APM", - "stream/r2_d.APM", - "stream/r2_e.APM", - "stream/r2_f.APM", - "stream/r2_g.APM", - "stream/r2_h.APM", - "stream/r5_a.APM", - "stream/r6_a.APM", - "stream/r6_a1.APM", - "stream/r6_b.APM", - "stream/lo2_a.APM", - "stream/lo6_a.APM", - "stream/yd2_a.APM", - "stream/yd2_b.APM", - "stream/yd2_c.APM", - "stream/yd2_c1.APM", - "stream/yd2_d.APM", - "stream/yd2_e.APM", - "stream/yd2_f.APM", - "stream/yd2_g.APM", - "stream/yd2_h.APM", - "stream/yd2_ass.APM", - "stream/yd2_ok.APM", - "stream/h5_a.APM", - "stream/h5_b.APM", - "stream/h5_c.APM", - "stream/ammu_a.APM", - "stream/ammu_b.APM", - "stream/ammu_c.APM", - "stream/door_1.APM", - "stream/door_2.APM", - "stream/door_3.APM", - "stream/door_4.APM", - "stream/door_5.APM", - "stream/door_6.APM", - "stream/t3_a.APM", - "stream/t3_b.APM", - "stream/t3_c.APM", - "stream/k1_b.APM", - "stream/cat1.APM" -}; #endif \ No newline at end of file diff --git a/src/audio/sampman_dc.cpp b/src/liberty/audio/sampman_dc.cpp similarity index 93% rename from src/audio/sampman_dc.cpp rename to src/liberty/audio/sampman_dc.cpp index 0f48a4b8..e9e90650 100644 --- a/src/audio/sampman_dc.cpp +++ b/src/liberty/audio/sampman_dc.cpp @@ -15,6 +15,7 @@ #define debugf(...) // dbglog(DBG_CRITICAL, __VA_ARGS__) #include "sampman.h" +#include "sampman_dc_streams.h" #include "AudioManager.h" #include "MusicManager.h" #include "Frontend.h" @@ -151,7 +152,6 @@ void aica_snd_sfx_freq_vol(int chn, int freq, int vol) { cSampleManager SampleManager; bool8 _bSampmanInitialised = FALSE; -bool _dcAudioInitialized = false; uint32 BankStartOffset[MAX_SFX_BANKS]; char SampleBankDescFilename[] = "sfx/sfx_all.dsc"; @@ -166,8 +166,14 @@ struct sfx_bank { std::map sfx_banks; int nPedSlotSfx[MAX_PEDSFX]; +uint32 nPedSlotSfxReqId[MAX_PEDSFX]; uintptr_t nPedSlotSfxAddr[MAX_PEDSFX]; uint8_t nCurrentPedSlot; +file_t fdPedSfx; +volatile uint32 nPedSfxReqReadId = 1; +volatile uint32 nPedSfxReqNextId = 1; + +static int32 DCStreamedLength[TOTAL_STREAMED_SOUNDS]; struct WavHeader { // RIFF Header @@ -442,23 +448,36 @@ cSampleManager::Initialise(void) } }); + nPedSfxReqNextId = 1; + nPedSfxReqReadId = 1; for ( int32 i = 0; i < MAX_PEDSFX; i++ ) { nPedSlotSfx[i] = -1; + nPedSlotSfxReqId[i] = 0; nPedSlotSfxAddr[i] = snd_mem_malloc(PED_BLOCKSIZE_ADPCM); debugf("PedSlot %d buffer: %p\n", i, (void*)nPedSlotSfxAddr[i]); } nCurrentPedSlot = 0; - _dcAudioInitialized = true; + fdPedSfx = fs_open(SampleBankDataFilename, O_RDONLY); + + assert(fdPedSfx >= 0); + + file_t fd = fs_open("stream/hdr.bin", O_RDONLY); + assert(fd >= 0); + static_assert(sizeof(DCStreamedLength) == TOTAL_STREAMED_SOUNDS*sizeof(int32)); + assert(fs_read(fd, DCStreamedLength, sizeof(DCStreamedLength)) == sizeof(DCStreamedLength)); + fs_close(fd); + + _bSampmanInitialised = true; return TRUE; } void cSampleManager::Terminate(void) { - + fs_close(fdPedSfx); } bool8 cSampleManager::CheckForAnAudioFileOnCD(void) @@ -474,7 +493,14 @@ char cSampleManager::GetCDAudioDriveLetter(void) void cSampleManager::UpdateEffectsVolume(void) { - // TODO + if(_bSampmanInitialised) { + std::lock_guard lk(channel_mtx); + for (int i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++) { + if (channels[i].ch != -1) { + UpdateChannelVolume(i); + } + } + } } @@ -637,7 +663,7 @@ cSampleManager::IsPedCommentLoaded(uint32 nComment) slot += ARRAY_SIZE(nPedSlotSfx); #endif if ( nComment == nPedSlotSfx[slot] ) - return LOADING_STATUS_LOADED; + return nPedSlotSfxReqId[slot] <= nPedSfxReqReadId ? LOADING_STATUS_LOADED : LOADING_STATUS_LOADING; } return LOADING_STATUS_NOT_LOADED; @@ -698,27 +724,28 @@ cSampleManager::LoadPedComment(uint32 nComment) assert(m_aSamples[nComment].nByteSize < PED_BLOCKSIZE_ADPCM); - file_t fd = fs_open(SampleBankDataFilename, O_RDONLY); - - assert(fd >= 0); - debugf("Loading ped comment %d, offset: %d, size: %d\n", nComment, m_aSamples[nComment].nFileOffset, m_aSamples[nComment].nByteSize); - fs_seek(fd, m_aSamples[nComment].nFileOffset, SEEK_SET); + CdStreamQueueAudioRead(nComment, (void*)nPedSlotSfxAddr[nCurrentPedSlot], m_aSamples[nComment].nByteSize, m_aSamples[nComment].nFileOffset, [](AudioReadCmd* cmd) { + debugf("Loading ped comment %d, offset: %d, size: %d\n", nComment, m_aSamples[nComment].nFileOffset, m_aSamples[nComment].nByteSize); + fs_seek(fdPedSfx, cmd->seek, SEEK_SET); - // TODO: When we can dma directly to AICA, we can use this instead - // fs_read(fd, SPU_BASE_U8 + nPedSlotSfxAddr[nCurrentPedSlot], sizeof(nPedSlotSfxAddr)); + // TODO: When we can dma directly to AICA, we can use this instead + // fs_read(fdPedSfx, SPU_BASE_U8 + (uintptr_t)cmd->dest, cmd->size); - void* stagingBuffer = memalign(32, m_aSamples[nComment].nByteSize); - assert(stagingBuffer != 0); - debugf("Allocated %d bytes at %p\n", m_aSamples[nComment].nByteSize, stagingBuffer); - int rs = fs_read(fd, stagingBuffer, m_aSamples[nComment].nByteSize); - debugf("Read %d bytes, expected %d\n", rs, m_aSamples[nComment].nByteSize); - assert(rs == m_aSamples[nComment].nByteSize); + void* stagingBuffer = memalign(32, cmd->size); + assert(stagingBuffer != 0); + debugf("Allocated %d bytes at %p\n", cmd->size, stagingBuffer); + int rs = fs_read(fdPedSfx, stagingBuffer, cmd->size); + debugf("Read %d bytes, expected %d\n", rs, cmd->size); + assert(rs == cmd->size); - fs_close(fd); + spu_memload((uintptr_t)cmd->dest, stagingBuffer, cmd->size); + free(stagingBuffer); + nPedSfxReqReadId = nPedSfxReqReadId + 1; + }); - spu_memload(nPedSlotSfxAddr[nCurrentPedSlot], stagingBuffer, m_aSamples[nComment].nByteSize); - free(stagingBuffer); + nPedSlotSfxReqId[nCurrentPedSlot] = ++nPedSfxReqNextId; + nPedSlotSfx[nCurrentPedSlot] = nComment; if ( ++nCurrentPedSlot >= MAX_PEDSFX ) @@ -839,9 +866,10 @@ cSampleManager::InitialiseChannel(uint32 nChannel, uint32 nSfx, uint8 nBank) return TRUE; } -void updateVol(uint32 nChannel) { +void cSampleManager::UpdateChannelVolume(uint32 nChannel) { auto newVol = channels[nChannel].emittingVol * channels[nChannel].attenuationVol / 255; + newVol = m_nEffectsFadeVolume * newVol * m_nEffectsVolume >> 14; // newVol = 255; // printf("updateVol(nChannel: %d) vol: %d, newVol: %d\n", nChannel, channels[nChannel].vol, newVol); if (channels[nChannel].vol != newVol) { @@ -864,7 +892,7 @@ cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) channels[nChannel].emittingVol = linearlize_volume(nVolume);// nVolume * 255 / MAX_VOLUME; channels[nChannel].attenuationVol = 255; - updateVol(nChannel); + UpdateChannelVolume(nChannel); verbosef("SetChannelVolume(nChannel: %d) vol: %d\n", nChannel, nVolume); } @@ -1120,7 +1148,8 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) debugf("StartStreamedFile(%d, %d, %d)\n", nFile, nPos, nStream); uint32_t seek_aligned = 0; if (nPos) { - uint32 seek_bytes = nPos * streams[nStream].rate / (streams[nStream].stereo ? 1000: 2000); + uint64_t seek_bytes = (uint64_t)nPos * streams[nStream].rate / (streams[nStream].stereo ? 1000: 2000); + assert(seek_bytes <= INT32_MAX); seek_aligned = seek_bytes & ~(streams[nStream].stereo ? (STREAM_STAGING_READ_SIZE_STEREO-1) : (STREAM_STAGING_READ_SIZE_MONO-1)); } PreloadStreamedFile(nFile, nStream, seek_aligned); @@ -1152,7 +1181,9 @@ cSampleManager::GetStreamedFilePosition(uint8 nStream) ASSERT( nStream < MAX_STREAMS ); int32 rv = 0; - return streams[nStream].played_samples * 1000 / streams[nStream].rate; + int64_t rv64 = (int64_t)streams[nStream].played_samples * 1000 / streams[nStream].rate; + assert(rv64 <= INT32_MAX); + rv = (int32)rv64; // if(streams[nStream].fd >= 0) { // rv = fs_tell(streams[nStream].fd); // } @@ -1167,6 +1198,7 @@ cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, uint8 nEffect if (nVolume > MAX_VOLUME) nVolume = MAX_VOLUME; nVolume = linearlize_volume(nVolume); //nVolume * 255 / MAX_VOLUME; + nVolume = m_nMusicFadeVolume * nVolume * m_nMusicVolume >> 14; if (streams[nStream].vol != nVolume || streams[nStream].nPan != nPan) { streams[nStream].vol = nVolume; streams[nStream].nPan = nPan; @@ -1189,18 +1221,10 @@ int32 cSampleManager::GetStreamedFileLength(uint8 nFile) { ASSERT( nFile < TOTAL_STREAMED_SOUNDS ); - int32 rv = 1; // Look in MusicManager.cpp:268 - file_t fd = fs_open(DCStreamedNameTable[nFile], O_RDONLY); - assert(fd >= 0); - WavHeader hdr; - assert(fs_read(fd, &hdr, sizeof(hdr)) == sizeof(hdr)); - - rv = hdr.dataSize * 2000 / hdr.numOfChan / hdr.samplesPerSec; - - fs_close(fd); + auto rv = DCStreamedLength[nFile]; debugf("GetStreamedFileLength: %d %d\n", nFile, rv); - return rv <= 0 ? 1 : rv; + return rv; } bool8 @@ -1275,9 +1299,17 @@ void cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) if (nVolume > MAX_VOLUME) nVolume = MAX_VOLUME; + // 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 ) + { + nVolume /= 4; + } + std::lock_guard lk(channel_mtx); channels[nChannel].emittingVol = linearlize_volume(nVolume); // nVolume * 255 / MAX_VOLUME; - updateVol(nChannel); + UpdateChannelVolume(nChannel); } float calculatePan(float x, float z) { @@ -1322,7 +1354,7 @@ void cSampleManager::SetChannel3DPosition (uint32 nChannel, float fX, float channels[nChannel].fY = fY; channels[nChannel].fZ = fZ; channels[nChannel].attenuationVol = calculateAttenuation(channels[nChannel].fX, channels[nChannel].fY, channels[nChannel].fZ, channels[nChannel].distMin, channels[nChannel].distMax) * 255; - updateVol(nChannel); + UpdateChannelVolume(nChannel); } SetChannelPan(nChannel, calculatePan(-fX, fZ) * 63 + 64); @@ -1335,7 +1367,7 @@ void cSampleManager::SetChannel3DDistances (uint32 nChannel, float fMax, floa channels[nChannel].distMin = fMin; channels[nChannel].distMax = fMax; channels[nChannel].attenuationVol = calculateAttenuation(channels[nChannel].fX, channels[nChannel].fY, channels[nChannel].fZ, channels[nChannel].distMin, channels[nChannel].distMax) * 255; - updateVol(nChannel); + UpdateChannelVolume(nChannel); } #endif diff --git a/src/liberty/audio/sampman_dc_streams.h b/src/liberty/audio/sampman_dc_streams.h new file mode 100644 index 00000000..696b07e0 --- /dev/null +++ b/src/liberty/audio/sampman_dc_streams.h @@ -0,0 +1,200 @@ + +static char DCStreamedNameTable[][25] = +{ + "stream/HEAD.APM", + "stream/CLASS.APM", + "stream/KJAH.APM", + "stream/RISE.APM", + "stream/LIPS.APM", + "stream/GAME.APM", + "stream/MSX.APM", + "stream/FLASH.APM", + "stream/CHAT.APM", + "stream/HEAD.APM", + "stream/POLICE.APM", + "stream/CITY.APM", + "stream/WATER.APM", + "stream/COMOPEN.APM", + "stream/SUBOPEN.APM", + "stream/JB.APM", + "stream/BET.APM", + "stream/L1_LG.APM", + "stream/L2_DSB.APM", + "stream/L3_DM.APM", + "stream/L4_PAP.APM", + "stream/L5_TFB.APM", + "stream/J0_DM2.APM", + "stream/J1_LFL.APM", + "stream/J2_KCL.APM", + "stream/J3_VH.APM", + "stream/J4_ETH.APM", + "stream/J5_DST.APM", + "stream/J6_TBJ.APM", + "stream/T1_TOL.APM", + "stream/T2_TPU.APM", + "stream/T3_MAS.APM", + "stream/T4_TAT.APM", + "stream/T5_BF.APM", + "stream/S0_MAS.APM", + "stream/S1_PF.APM", + "stream/S2_CTG.APM", + "stream/S3_RTC.APM", + "stream/S5_LRQ.APM", + "stream/S4_BDBA.APM", + "stream/S4_BDBB.APM", + "stream/S2_CTG2.APM", + "stream/S4_BDBD.APM", + "stream/S5_LRQB.APM", + "stream/S5_LRQC.APM", + "stream/A1_SSO.APM", + "stream/A2_PP.APM", + "stream/A3_SS.APM", + "stream/A4_PDR.APM", + "stream/A5_K2FT.APM", + "stream/K1_KBO.APM", + "stream/K2_GIS.APM", + "stream/K3_DS.APM", + "stream/K4_SHI.APM", + "stream/K5_SD.APM", + "stream/R0_PDR2.APM", + "stream/R1_SW.APM", + "stream/R2_AP.APM", + "stream/R3_ED.APM", + "stream/R4_GF.APM", + "stream/R5_PB.APM", + "stream/R6_MM.APM", + "stream/D1_STOG.APM", + "stream/D2_KK.APM", + "stream/D3_ADO.APM", + "stream/D5_ES.APM", + "stream/D7_MLD.APM", + "stream/D4_GTA.APM", + "stream/D4_GTA2.APM", + "stream/D6_STS.APM", + "stream/A6_BAIT.APM", + "stream/A7_ETG.APM", + "stream/A8_PS.APM", + "stream/A9_ASD.APM", + "stream/K4_SHI2.APM", + "stream/C1_TEX.APM", + "stream/EL_PH1.APM", + "stream/EL_PH2.APM", + "stream/EL_PH3.APM", + "stream/EL_PH4.APM", + "stream/YD_PH1.APM", + "stream/YD_PH2.APM", + "stream/YD_PH3.APM", + "stream/YD_PH4.APM", + "stream/HD_PH1.APM", + "stream/HD_PH2.APM", + "stream/HD_PH3.APM", + "stream/HD_PH4.APM", + "stream/HD_PH5.APM", + "stream/MT_PH1.APM", + "stream/MT_PH2.APM", + "stream/MT_PH3.APM", + "stream/MT_PH4.APM", + "stream/MISCOM.APM", + "stream/END.APM", + "stream/lib_a1.APM", + "stream/lib_a2.APM", + "stream/lib_a.APM", + "stream/lib_b.APM", + "stream/lib_c.APM", + "stream/lib_d.APM", + "stream/l2_a.APM", + "stream/j4t_1.APM", + "stream/j4t_2.APM", + "stream/j4t_3.APM", + "stream/j4t_4.APM", + "stream/j4_a.APM", + "stream/j4_b.APM", + "stream/j4_c.APM", + "stream/j4_d.APM", + "stream/j4_e.APM", + "stream/j4_f.APM", + "stream/j6_1.APM", + "stream/j6_a.APM", + "stream/j6_b.APM", + "stream/j6_c.APM", + "stream/j6_d.APM", + "stream/t4_a.APM", + "stream/s1_a.APM", + "stream/s1_a1.APM", + "stream/s1_b.APM", + "stream/s1_c.APM", + "stream/s1_c1.APM", + "stream/s1_d.APM", + "stream/s1_e.APM", + "stream/s1_f.APM", + "stream/s1_g.APM", + "stream/s1_h.APM", + "stream/s1_i.APM", + "stream/s1_j.APM", + "stream/s1_k.APM", + "stream/s1_l.APM", + "stream/s3_a.APM", + "stream/s3_b.APM", + "stream/el3_a.APM", + "stream/mf1_a.APM", + "stream/mf2_a.APM", + "stream/mf3_a.APM", + "stream/mf3_b.APM", + "stream/mf3_b1.APM", + "stream/mf3_c.APM", + "stream/mf4_a.APM", + "stream/mf4_b.APM", + "stream/mf4_c.APM", + "stream/a1_a.APM", + "stream/a3_a.APM", + "stream/a5_a.APM", + "stream/a4_a.APM", + "stream/a4_b.APM", + "stream/a4_c.APM", + "stream/a4_d.APM", + "stream/k1_a.APM", + "stream/k3_a.APM", + "stream/r1_a.APM", + "stream/r2_a.APM", + "stream/r2_b.APM", + "stream/r2_c.APM", + "stream/r2_d.APM", + "stream/r2_e.APM", + "stream/r2_f.APM", + "stream/r2_g.APM", + "stream/r2_h.APM", + "stream/r5_a.APM", + "stream/r6_a.APM", + "stream/r6_a1.APM", + "stream/r6_b.APM", + "stream/lo2_a.APM", + "stream/lo6_a.APM", + "stream/yd2_a.APM", + "stream/yd2_b.APM", + "stream/yd2_c.APM", + "stream/yd2_c1.APM", + "stream/yd2_d.APM", + "stream/yd2_e.APM", + "stream/yd2_f.APM", + "stream/yd2_g.APM", + "stream/yd2_h.APM", + "stream/yd2_ass.APM", + "stream/yd2_ok.APM", + "stream/h5_a.APM", + "stream/h5_b.APM", + "stream/h5_c.APM", + "stream/ammu_a.APM", + "stream/ammu_b.APM", + "stream/ammu_c.APM", + "stream/door_1.APM", + "stream/door_2.APM", + "stream/door_3.APM", + "stream/door_4.APM", + "stream/door_5.APM", + "stream/door_6.APM", + "stream/t3_a.APM", + "stream/t3_b.APM", + "stream/t3_c.APM", + "stream/k1_b.APM", + "stream/cat1.APM" +}; \ No newline at end of file diff --git a/src/audio/sampman_miles.cpp b/src/liberty/audio/sampman_miles.cpp similarity index 100% rename from src/audio/sampman_miles.cpp rename to src/liberty/audio/sampman_miles.cpp diff --git a/src/audio/sampman_null.cpp b/src/liberty/audio/sampman_null.cpp similarity index 100% rename from src/audio/sampman_null.cpp rename to src/liberty/audio/sampman_null.cpp diff --git a/src/audio/sampman_oal.cpp b/src/liberty/audio/sampman_oal.cpp similarity index 100% rename from src/audio/sampman_oal.cpp rename to src/liberty/audio/sampman_oal.cpp diff --git a/src/audio/soundlist.h b/src/liberty/audio/soundlist.h similarity index 100% rename from src/audio/soundlist.h rename to src/liberty/audio/soundlist.h diff --git a/src/buildings/Building.cpp b/src/liberty/buildings/Building.cpp similarity index 100% rename from src/buildings/Building.cpp rename to src/liberty/buildings/Building.cpp diff --git a/src/buildings/Building.h b/src/liberty/buildings/Building.h similarity index 100% rename from src/buildings/Building.h rename to src/liberty/buildings/Building.h diff --git a/src/buildings/Solid.h b/src/liberty/buildings/Solid.h similarity index 100% rename from src/buildings/Solid.h rename to src/liberty/buildings/Solid.h diff --git a/src/buildings/Treadable.cpp b/src/liberty/buildings/Treadable.cpp similarity index 100% rename from src/buildings/Treadable.cpp rename to src/liberty/buildings/Treadable.cpp diff --git a/src/buildings/Treadable.h b/src/liberty/buildings/Treadable.h similarity index 100% rename from src/buildings/Treadable.h rename to src/liberty/buildings/Treadable.h diff --git a/src/collision/ColBox.cpp b/src/liberty/collision/ColBox.cpp similarity index 100% rename from src/collision/ColBox.cpp rename to src/liberty/collision/ColBox.cpp diff --git a/src/collision/ColBox.h b/src/liberty/collision/ColBox.h similarity index 100% rename from src/collision/ColBox.h rename to src/liberty/collision/ColBox.h diff --git a/src/collision/ColLine.cpp b/src/liberty/collision/ColLine.cpp similarity index 100% rename from src/collision/ColLine.cpp rename to src/liberty/collision/ColLine.cpp diff --git a/src/collision/ColLine.h b/src/liberty/collision/ColLine.h similarity index 100% rename from src/collision/ColLine.h rename to src/liberty/collision/ColLine.h diff --git a/src/collision/ColModel.cpp b/src/liberty/collision/ColModel.cpp similarity index 100% rename from src/collision/ColModel.cpp rename to src/liberty/collision/ColModel.cpp diff --git a/src/collision/ColModel.h b/src/liberty/collision/ColModel.h similarity index 100% rename from src/collision/ColModel.h rename to src/liberty/collision/ColModel.h diff --git a/src/collision/ColPoint.cpp b/src/liberty/collision/ColPoint.cpp similarity index 100% rename from src/collision/ColPoint.cpp rename to src/liberty/collision/ColPoint.cpp diff --git a/src/collision/ColPoint.h b/src/liberty/collision/ColPoint.h similarity index 100% rename from src/collision/ColPoint.h rename to src/liberty/collision/ColPoint.h diff --git a/src/collision/ColSphere.cpp b/src/liberty/collision/ColSphere.cpp similarity index 100% rename from src/collision/ColSphere.cpp rename to src/liberty/collision/ColSphere.cpp diff --git a/src/collision/ColSphere.h b/src/liberty/collision/ColSphere.h similarity index 100% rename from src/collision/ColSphere.h rename to src/liberty/collision/ColSphere.h diff --git a/src/collision/ColTriangle.cpp b/src/liberty/collision/ColTriangle.cpp similarity index 92% rename from src/collision/ColTriangle.cpp rename to src/liberty/collision/ColTriangle.cpp index c025924a..f90f393c 100644 --- a/src/collision/ColTriangle.cpp +++ b/src/liberty/collision/ColTriangle.cpp @@ -2,7 +2,7 @@ #include "ColTriangle.h" void -CColTriangle::Set(const CompressedVector *, int a, int b, int c, uint8 surf, uint8 piece) +CColTriangle::Set(const CompressedVector *, uint16 a, uint16 b, uint16 c, uint8 surf, uint8 piece) { this->a = a; this->b = b; diff --git a/src/collision/ColTriangle.h b/src/liberty/collision/ColTriangle.h similarity index 94% rename from src/collision/ColTriangle.h rename to src/liberty/collision/ColTriangle.h index 03299463..500f8baf 100644 --- a/src/collision/ColTriangle.h +++ b/src/liberty/collision/ColTriangle.h @@ -18,7 +18,7 @@ struct CColTriangle uint16 c; uint8 surface; - void Set(const CompressedVector *v, int a, int b, int c, uint8 surf, uint8 piece); + void Set(const CompressedVector *v, uint16 a, uint16 b, uint16 c, uint8 surf, uint8 piece); }; struct CColTrianglePlane diff --git a/src/collision/Collision.cpp b/src/liberty/collision/Collision.cpp similarity index 100% rename from src/collision/Collision.cpp rename to src/liberty/collision/Collision.cpp diff --git a/src/collision/Collision.h b/src/liberty/collision/Collision.h similarity index 100% rename from src/collision/Collision.h rename to src/liberty/collision/Collision.h diff --git a/src/collision/CompressedVector.h b/src/liberty/collision/CompressedVector.h similarity index 85% rename from src/collision/CompressedVector.h rename to src/liberty/collision/CompressedVector.h index caf07664..3945ebf2 100644 --- a/src/collision/CompressedVector.h +++ b/src/liberty/collision/CompressedVector.h @@ -5,7 +5,7 @@ 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 = lroundf(x*128.0f); this->y = lroundf(y*128.0f); this->z = lroundf(z*128.0f); }; + void SetFixed(int16 x, int16 y, int16 z) { this->x = x; this->y = y; this->z = z; }; #ifdef GTA_PS2 void Unpack(uint128 &qword) const { __asm__ volatile ( diff --git a/src/collision/TempColModels.cpp b/src/liberty/collision/TempColModels.cpp similarity index 100% rename from src/collision/TempColModels.cpp rename to src/liberty/collision/TempColModels.cpp diff --git a/src/collision/TempColModels.h b/src/liberty/collision/TempColModels.h similarity index 100% rename from src/collision/TempColModels.h rename to src/liberty/collision/TempColModels.h diff --git a/src/collision/VuCollision.cpp b/src/liberty/collision/VuCollision.cpp similarity index 100% rename from src/collision/VuCollision.cpp rename to src/liberty/collision/VuCollision.cpp diff --git a/src/collision/VuCollision.h b/src/liberty/collision/VuCollision.h similarity index 100% rename from src/collision/VuCollision.h rename to src/liberty/collision/VuCollision.h diff --git a/src/collision/vu0Collision.dsm b/src/liberty/collision/vu0Collision.dsm similarity index 100% rename from src/collision/vu0Collision.dsm rename to src/liberty/collision/vu0Collision.dsm diff --git a/src/collision/vu0Collision_1.s b/src/liberty/collision/vu0Collision_1.s similarity index 100% rename from src/collision/vu0Collision_1.s rename to src/liberty/collision/vu0Collision_1.s diff --git a/src/collision/vu0Collision_2.s b/src/liberty/collision/vu0Collision_2.s similarity index 100% rename from src/collision/vu0Collision_2.s rename to src/liberty/collision/vu0Collision_2.s diff --git a/src/control/AutoPilot.cpp b/src/liberty/control/AutoPilot.cpp similarity index 100% rename from src/control/AutoPilot.cpp rename to src/liberty/control/AutoPilot.cpp diff --git a/src/control/AutoPilot.h b/src/liberty/control/AutoPilot.h similarity index 100% rename from src/control/AutoPilot.h rename to src/liberty/control/AutoPilot.h diff --git a/src/control/Bridge.cpp b/src/liberty/control/Bridge.cpp similarity index 100% rename from src/control/Bridge.cpp rename to src/liberty/control/Bridge.cpp diff --git a/src/control/Bridge.h b/src/liberty/control/Bridge.h similarity index 100% rename from src/control/Bridge.h rename to src/liberty/control/Bridge.h diff --git a/src/control/CarAI.cpp b/src/liberty/control/CarAI.cpp similarity index 100% rename from src/control/CarAI.cpp rename to src/liberty/control/CarAI.cpp diff --git a/src/control/CarAI.h b/src/liberty/control/CarAI.h similarity index 100% rename from src/control/CarAI.h rename to src/liberty/control/CarAI.h diff --git a/src/control/CarCtrl.cpp b/src/liberty/control/CarCtrl.cpp similarity index 100% rename from src/control/CarCtrl.cpp rename to src/liberty/control/CarCtrl.cpp diff --git a/src/control/CarCtrl.h b/src/liberty/control/CarCtrl.h similarity index 100% rename from src/control/CarCtrl.h rename to src/liberty/control/CarCtrl.h diff --git a/src/control/Curves.cpp b/src/liberty/control/Curves.cpp similarity index 100% rename from src/control/Curves.cpp rename to src/liberty/control/Curves.cpp diff --git a/src/control/Curves.h b/src/liberty/control/Curves.h similarity index 100% rename from src/control/Curves.h rename to src/liberty/control/Curves.h diff --git a/src/control/Darkel.cpp b/src/liberty/control/Darkel.cpp similarity index 100% rename from src/control/Darkel.cpp rename to src/liberty/control/Darkel.cpp diff --git a/src/control/Darkel.h b/src/liberty/control/Darkel.h similarity index 100% rename from src/control/Darkel.h rename to src/liberty/control/Darkel.h diff --git a/src/control/GameLogic.cpp b/src/liberty/control/GameLogic.cpp similarity index 100% rename from src/control/GameLogic.cpp rename to src/liberty/control/GameLogic.cpp diff --git a/src/control/GameLogic.h b/src/liberty/control/GameLogic.h similarity index 100% rename from src/control/GameLogic.h rename to src/liberty/control/GameLogic.h diff --git a/src/control/Garages.cpp b/src/liberty/control/Garages.cpp similarity index 100% rename from src/control/Garages.cpp rename to src/liberty/control/Garages.cpp diff --git a/src/control/Garages.h b/src/liberty/control/Garages.h similarity index 100% rename from src/control/Garages.h rename to src/liberty/control/Garages.h diff --git a/src/control/NameGrid.cpp b/src/liberty/control/NameGrid.cpp similarity index 100% rename from src/control/NameGrid.cpp rename to src/liberty/control/NameGrid.cpp diff --git a/src/control/NameGrid.h b/src/liberty/control/NameGrid.h similarity index 100% rename from src/control/NameGrid.h rename to src/liberty/control/NameGrid.h diff --git a/src/control/OnscreenTimer.cpp b/src/liberty/control/OnscreenTimer.cpp similarity index 100% rename from src/control/OnscreenTimer.cpp rename to src/liberty/control/OnscreenTimer.cpp diff --git a/src/control/OnscreenTimer.h b/src/liberty/control/OnscreenTimer.h similarity index 100% rename from src/control/OnscreenTimer.h rename to src/liberty/control/OnscreenTimer.h diff --git a/src/control/PathFind.cpp b/src/liberty/control/PathFind.cpp similarity index 100% rename from src/control/PathFind.cpp rename to src/liberty/control/PathFind.cpp diff --git a/src/control/PathFind.h b/src/liberty/control/PathFind.h similarity index 100% rename from src/control/PathFind.h rename to src/liberty/control/PathFind.h diff --git a/src/control/Phones.cpp b/src/liberty/control/Phones.cpp similarity index 100% rename from src/control/Phones.cpp rename to src/liberty/control/Phones.cpp diff --git a/src/control/Phones.h b/src/liberty/control/Phones.h similarity index 100% rename from src/control/Phones.h rename to src/liberty/control/Phones.h diff --git a/src/control/Pickups.cpp b/src/liberty/control/Pickups.cpp similarity index 100% rename from src/control/Pickups.cpp rename to src/liberty/control/Pickups.cpp diff --git a/src/control/Pickups.h b/src/liberty/control/Pickups.h similarity index 100% rename from src/control/Pickups.h rename to src/liberty/control/Pickups.h diff --git a/src/control/PowerPoints.cpp b/src/liberty/control/PowerPoints.cpp similarity index 100% rename from src/control/PowerPoints.cpp rename to src/liberty/control/PowerPoints.cpp diff --git a/src/control/PowerPoints.h b/src/liberty/control/PowerPoints.h similarity index 100% rename from src/control/PowerPoints.h rename to src/liberty/control/PowerPoints.h diff --git a/src/control/Record.cpp b/src/liberty/control/Record.cpp similarity index 100% rename from src/control/Record.cpp rename to src/liberty/control/Record.cpp diff --git a/src/control/Record.h b/src/liberty/control/Record.h similarity index 100% rename from src/control/Record.h rename to src/liberty/control/Record.h diff --git a/src/control/Remote.cpp b/src/liberty/control/Remote.cpp similarity index 100% rename from src/control/Remote.cpp rename to src/liberty/control/Remote.cpp diff --git a/src/control/Remote.h b/src/liberty/control/Remote.h similarity index 100% rename from src/control/Remote.h rename to src/liberty/control/Remote.h diff --git a/src/control/Replay.cpp b/src/liberty/control/Replay.cpp similarity index 100% rename from src/control/Replay.cpp rename to src/liberty/control/Replay.cpp diff --git a/src/control/Replay.h b/src/liberty/control/Replay.h similarity index 100% rename from src/control/Replay.h rename to src/liberty/control/Replay.h diff --git a/src/control/Restart.cpp b/src/liberty/control/Restart.cpp similarity index 100% rename from src/control/Restart.cpp rename to src/liberty/control/Restart.cpp diff --git a/src/control/Restart.h b/src/liberty/control/Restart.h similarity index 100% rename from src/control/Restart.h rename to src/liberty/control/Restart.h diff --git a/src/control/RoadBlocks.cpp b/src/liberty/control/RoadBlocks.cpp similarity index 100% rename from src/control/RoadBlocks.cpp rename to src/liberty/control/RoadBlocks.cpp diff --git a/src/control/RoadBlocks.h b/src/liberty/control/RoadBlocks.h similarity index 100% rename from src/control/RoadBlocks.h rename to src/liberty/control/RoadBlocks.h diff --git a/src/control/SceneEdit.cpp b/src/liberty/control/SceneEdit.cpp similarity index 100% rename from src/control/SceneEdit.cpp rename to src/liberty/control/SceneEdit.cpp diff --git a/src/control/SceneEdit.h b/src/liberty/control/SceneEdit.h similarity index 100% rename from src/control/SceneEdit.h rename to src/liberty/control/SceneEdit.h diff --git a/src/control/Script.cpp b/src/liberty/control/Script.cpp similarity index 100% rename from src/control/Script.cpp rename to src/liberty/control/Script.cpp diff --git a/src/control/Script.h b/src/liberty/control/Script.h similarity index 100% rename from src/control/Script.h rename to src/liberty/control/Script.h diff --git a/src/control/Script2.cpp b/src/liberty/control/Script2.cpp similarity index 100% rename from src/control/Script2.cpp rename to src/liberty/control/Script2.cpp diff --git a/src/control/Script3.cpp b/src/liberty/control/Script3.cpp similarity index 100% rename from src/control/Script3.cpp rename to src/liberty/control/Script3.cpp diff --git a/src/control/Script4.cpp b/src/liberty/control/Script4.cpp similarity index 99% rename from src/control/Script4.cpp rename to src/liberty/control/Script4.cpp index 4e798be3..dc592d8f 100644 --- a/src/control/Script4.cpp +++ b/src/liberty/control/Script4.cpp @@ -1721,8 +1721,8 @@ int8 CRunningScript::ProcessCommands900To999(int32 command) return 0; int attempts; int model = -1; - int index = CGeneral::GetRandomNumberInRange(0, 50); - for (attempts = 0; attempts < 50; attempts++) { + int index = CGeneral::GetRandomNumberInRange(0, MAXVEHICLESLOADED); + for (attempts = 0; attempts < MAXVEHICLESLOADED; attempts++) { if (model != -1) break; model = CStreaming::ms_vehiclesLoaded[index]; diff --git a/src/control/Script5.cpp b/src/liberty/control/Script5.cpp similarity index 100% rename from src/control/Script5.cpp rename to src/liberty/control/Script5.cpp diff --git a/src/control/Script6.cpp b/src/liberty/control/Script6.cpp similarity index 100% rename from src/control/Script6.cpp rename to src/liberty/control/Script6.cpp diff --git a/src/control/ScriptCommands.h b/src/liberty/control/ScriptCommands.h similarity index 100% rename from src/control/ScriptCommands.h rename to src/liberty/control/ScriptCommands.h diff --git a/src/control/ScriptDebug.cpp b/src/liberty/control/ScriptDebug.cpp similarity index 100% rename from src/control/ScriptDebug.cpp rename to src/liberty/control/ScriptDebug.cpp diff --git a/src/control/TrafficLights.cpp b/src/liberty/control/TrafficLights.cpp similarity index 100% rename from src/control/TrafficLights.cpp rename to src/liberty/control/TrafficLights.cpp diff --git a/src/control/TrafficLights.h b/src/liberty/control/TrafficLights.h similarity index 100% rename from src/control/TrafficLights.h rename to src/liberty/control/TrafficLights.h diff --git a/src/core/Accident.cpp b/src/liberty/core/Accident.cpp similarity index 100% rename from src/core/Accident.cpp rename to src/liberty/core/Accident.cpp diff --git a/src/core/Accident.h b/src/liberty/core/Accident.h similarity index 100% rename from src/core/Accident.h rename to src/liberty/core/Accident.h diff --git a/src/core/AnimViewer.cpp b/src/liberty/core/AnimViewer.cpp similarity index 100% rename from src/core/AnimViewer.cpp rename to src/liberty/core/AnimViewer.cpp diff --git a/src/core/AnimViewer.h b/src/liberty/core/AnimViewer.h similarity index 100% rename from src/core/AnimViewer.h rename to src/liberty/core/AnimViewer.h diff --git a/src/core/Cam.cpp b/src/liberty/core/Cam.cpp similarity index 100% rename from src/core/Cam.cpp rename to src/liberty/core/Cam.cpp diff --git a/src/core/Camera.cpp b/src/liberty/core/Camera.cpp similarity index 100% rename from src/core/Camera.cpp rename to src/liberty/core/Camera.cpp diff --git a/src/core/Camera.h b/src/liberty/core/Camera.h similarity index 100% rename from src/core/Camera.h rename to src/liberty/core/Camera.h diff --git a/src/core/CdStream.cpp b/src/liberty/core/CdStream.cpp similarity index 100% rename from src/core/CdStream.cpp rename to src/liberty/core/CdStream.cpp diff --git a/src/core/CdStream.h b/src/liberty/core/CdStream.h similarity index 86% rename from src/core/CdStream.h rename to src/liberty/core/CdStream.h index 420a3c8d..0a84d01a 100644 --- a/src/core/CdStream.h +++ b/src/liberty/core/CdStream.h @@ -1,4 +1,5 @@ #pragma once +#include #define CDSTREAM_SECTOR_SIZE 2048 @@ -43,7 +44,14 @@ char *CdStreamGetImageName(int32 cd); void CdStreamRemoveImages(void); int32 CdStreamGetNumImages(void); -void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek); +struct AudioReadCmd { + void* dest; + int fd; + size_t size; + size_t seek; + std::function callback; +}; +void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek, std::function callback = nullptr); void CdStreamDiscardAudioRead(int fd); #ifdef FLUSHABLE_STREAMING diff --git a/src/core/CdStreamDC.cpp b/src/liberty/core/CdStreamDC.cpp similarity index 98% rename from src/core/CdStreamDC.cpp rename to src/liberty/core/CdStreamDC.cpp index 04c02327..ab83f872 100644 --- a/src/core/CdStreamDC.cpp +++ b/src/liberty/core/CdStreamDC.cpp @@ -26,12 +26,6 @@ #include "CdStream.h" #include "rwcore.h" #include "MemoryMgr.h" -struct AudioReadCmd { - void* dest; - int fd; - size_t size; - size_t seek; -}; #define CDDEBUG(f, ...) debug ("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) #define CDTRACE(f, ...) printf("%s: " f "\n", "cdvd_stream", ## __VA_ARGS__) @@ -489,8 +483,16 @@ std::vector pendingAudioReads; std::mutex pendingAudioReadsMutex; #endif // Will replace a previous read request for the same file descriptor -void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek) { +void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek, std::function callback) { AudioReadCmd cmd = { pBuffer, fd, bytes, seek}; + if (!callback) { + cmd.callback = [](AudioReadCmd* cmd){ + lseek(cmd->fd, cmd->seek, SEEK_SET); + read(cmd->fd, cmd->dest, cmd->size); + }; + } else { + cmd.callback = callback; + } { #if !defined(DC_SH4) std::lock_guard lock(pendingAudioReadsMutex); @@ -514,6 +516,7 @@ void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek) { sem_post(gCdStreamSema); } + void CdStreamDiscardAudioRead(int fd) { #if !defined(DC_SH4) std::lock_guard lock(pendingAudioReadsMutex); @@ -564,8 +567,7 @@ int read_loop(int fd, void* pBuffer, size_t bytes) { total_read += read_bytes; auto cmd = CdStreamNextAudioRead(); while (cmd.fd != -1) { - lseek(cmd.fd, cmd.seek, SEEK_SET); - read(cmd.fd, cmd.dest, cmd.size); + cmd.callback(&cmd); cmd = CdStreamNextAudioRead(); } } @@ -581,8 +583,7 @@ void *CdStreamThread(void *param) auto cmd = CdStreamNextAudioRead(); while (cmd.fd != -1) { - lseek(cmd.fd, cmd.seek, SEEK_SET); - read(cmd.fd, cmd.dest, cmd.size); + cmd.callback(&cmd); cmd = CdStreamNextAudioRead(); } diff --git a/src/core/CdStreamPosix.cpp b/src/liberty/core/CdStreamPosix.cpp similarity index 100% rename from src/core/CdStreamPosix.cpp rename to src/liberty/core/CdStreamPosix.cpp diff --git a/src/core/Clock.cpp b/src/liberty/core/Clock.cpp similarity index 100% rename from src/core/Clock.cpp rename to src/liberty/core/Clock.cpp diff --git a/src/core/Clock.h b/src/liberty/core/Clock.h similarity index 100% rename from src/core/Clock.h rename to src/liberty/core/Clock.h diff --git a/src/core/ControllerConfig.cpp b/src/liberty/core/ControllerConfig.cpp old mode 100644 new mode 100755 similarity index 87% rename from src/core/ControllerConfig.cpp rename to src/liberty/core/ControllerConfig.cpp index dcaaa8f2..fc9e20e3 --- a/src/core/ControllerConfig.cpp +++ b/src/liberty/core/ControllerConfig.cpp @@ -2054,21 +2054,60 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr static const char* Dreamcast_DPad_Down = "D-Pad Down"; static const char* Dreamcast_DPad_Left = "D-Pad Left"; static const char* Dreamcast_DPad_Right = "D-Pad Right"; - static const char* Dreamcast_LefAnalog_Up = "Joystick Up"; - static const char* Dreamcast_LefAnalog_Down = "Joystick Down"; - static const char* Dreamcast_LefAnalog_Left = "Joystick Left"; - static const char* Dreamcast_LefAnalog_Right = "Joystick Right"; - static const char* Dreamcast_NotAssigned = "Not Assigned"; + static const char* Dreamcast_Stick_Up = "Joystick Up"; + static const char* Dreamcast_Stick_Down = "Joystick Down"; + static const char* Dreamcast_Stick_Left = "Joystick Left"; + static const char* Dreamcast_Stick_Right = "Joystick Right"; + static const char* Dreamcast_VehicleLookLeft = "A + Joystick Left"; + static const char* Dreamcast_VehicleLookRight = "A + Joystick Right"; + static const char* Dreamcast_CenterCamera = "Double click X"; + static const char* Dreamcast_CameraUp = "X + Joystick Up"; + static const char* Dreamcast_CameraDown = "X + Joystick Down"; + static const char* Dreamcast_CameraLeft = "X + Joystick Left"; + static const char* Dreamcast_CameraRight = "X + Joystick Right"; + static const char* Dreamcast_LookBehind = "Right trigger + Left trigger"; + static const char* Dreamcast_NotAssigned = "Not Assigned"; - if (CMenuManager::m_ControlMethod == CONTROL_CLASSIC) // CONTROL_CLASSIC + //Dual sticks + static const char* LefAnalog_Up = "Left Stick Up"; + static const char* LefAnalog_Down = "Left Stick Down"; + static const char* LefAnalog_Left = "Left Stick Left"; + static const char* LefAnalog_Right = "Left Stick Right"; + + static const char* RightAnalog_Up = "Right Stick Up"; + static const char* RightAnalog_Down = "Right Stick Down"; + static const char* RightAnalog_Left = "Right Stick Left"; + static const char* RightAnalog_Right = "Right Stick Right"; + + //Xbox Specific + static const char* Xbox_VehicleLookLeft = "LB"; + static const char* Xbox_VehicleLookRight = "RB"; + static const char* Xbox_VehicleLookBehind = "RB + LB"; + static const char* Xbox_Back = "Back"; + + + //PS2 Specific + static const char* Dreamcast_PS2_VehicleLookLeft = "B + Joystick Left"; + static const char* Dreamcast_PS2_VehicleLookRight = "B + Joystick Right"; + static const char* PS2_L1 = "L1"; + static const char* PS2_R1 = "R1"; + static const char* PS2_L2 = "L2"; + static const char* PS2_R2 = "R2"; + static const char* PS2_Cross = "Cross"; + static const char* PS2_Square = "Square"; + static const char* PS2_Circle = "Circle"; + static const char* PS2_Triangle = "Triangle"; + + + if ((CPad::GetPad(0)->Mode == 0) && (CPad::GetPad(0)->IsDualAnalog == 0)) // Standard DC controller, Xbox like bindings { switch (action) { case VEHICLE_LOOKLEFT: - for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_VehicleLookLeft[i]) != '\0' && i < iLimitCopy; i++); break; case VEHICLE_LOOKRIGHT: - for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_VehicleLookRight[i]) != '\0' && i < iLimitCopy; i++); break; case VEHICLE_HORN: for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); @@ -2083,28 +2122,28 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); break; case VEHICLE_CHANGE_RADIO_STATION: - for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); break; case VEHICLE_BRAKE: for (int i = 0; (ActionText[i] = Dreamcast_LeftTrigger[i]) != '\0' && i < iLimitCopy; i++); break; case TOGGLE_SUBMISSIONS: - for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); break; case GO_LEFT: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Left[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_Stick_Left[i]) != '\0' && i < iLimitCopy; i++); break; case GO_RIGHT: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Right[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_Stick_Right[i]) != '\0' && i < iLimitCopy; i++); break; case GO_FORWARD: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Up[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_Stick_Up[i]) != '\0' && i < iLimitCopy; i++); break; case GO_BACK: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_Stick_Down[i]) != '\0' && i < iLimitCopy; i++); break; case PED_LOOKBEHIND: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); break; case PED_FIREWEAPON: for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); @@ -2122,22 +2161,22 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr for (int i = 0; (ActionText[i] = Dreamcast_LeftTrigger[i]) != '\0' && i < iLimitCopy; i++); break; case PED_JUMPING: - for (int i = 0; (ActionText[i] = Dreamcast_X[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_B[i]) != '\0' && i < iLimitCopy; i++); break; case PED_SPRINT: for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); break; case PED_CYCLE_TARGET_LEFT: - for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); break; case PED_CYCLE_TARGET_RIGHT: - for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); break; case PED_CENTER_CAMERA_BEHIND_PLAYER: - for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_CenterCamera[i]) != '\0' && i < iLimitCopy; i++); break; case PED_SNIPER_ZOOM_IN: - for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_X[i]) != '\0' && i < iLimitCopy; i++); break; case PED_SNIPER_ZOOM_OUT: for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); @@ -2170,7 +2209,7 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr for (int i = 0; (ActionText[i] = Dreamcast_DPad_Up[i]) != '\0' && i < iLimitCopy; i++); break; case VEHICLE_LOOKBEHIND: - for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_LookBehind[i]) != '\0' && i < iLimitCopy; i++); break; case NETWORK_TALK: // Not Used on Dreamcast for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); @@ -2198,15 +2237,15 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr break; } } - else // CONTROL_STANDARD + if ((CPad::GetPad(0)->Mode == 0) && (CPad::GetPad(0)->IsDualAnalog == 1)) // Dual analog, Xbox like bindings { switch (action) { case VEHICLE_LOOKLEFT: - for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Xbox_VehicleLookLeft[i]) != '\0' && i < iLimitCopy; i++); break; case VEHICLE_LOOKRIGHT: - for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Xbox_VehicleLookRight[i]) != '\0' && i < iLimitCopy; i++); break; case VEHICLE_HORN: for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); @@ -2230,19 +2269,19 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); break; case GO_LEFT: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Left[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = LefAnalog_Left[i]) != '\0' && i < iLimitCopy; i++); break; case GO_RIGHT: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Right[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = LefAnalog_Right[i]) != '\0' && i < iLimitCopy; i++); break; case GO_FORWARD: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Up[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = LefAnalog_Up[i]) != '\0' && i < iLimitCopy; i++); break; case GO_BACK: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); break; case PED_LOOKBEHIND: - for (int i = 0; (ActionText[i] = Dreamcast_LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); break; case PED_FIREWEAPON: for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); @@ -2266,17 +2305,155 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); break; case PED_CYCLE_TARGET_LEFT: - for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); break; case PED_CYCLE_TARGET_RIGHT: - for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); break; case PED_CENTER_CAMERA_BEHIND_PLAYER: - for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + for (int i = 0; (ActionText[i] = Dreamcast_CenterCamera[i]) != '\0' && i < iLimitCopy; i++); break; case PED_SNIPER_ZOOM_IN: + for (int i = 0; (ActionText[i] = Dreamcast_X[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SNIPER_ZOOM_OUT: for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); break; + case PED_1RST_PERSON_LOOK_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_UP: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_DOWN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETLEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETRIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETUP: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETDOWN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case CAMERA_CHANGE_VIEW_ALL_SITUATIONS: + for (int i = 0; (ActionText[i] = Xbox_Back[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Xbox_VehicleLookBehind[i]) != '\0' && i < iLimitCopy; i++); + break; + case NETWORK_TALK: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case _CONTROLLERACTION_36: // What is that??? + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case TOGGLE_DPAD: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case SWITCH_DEBUG_CAM_ON: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case TAKE_SCREEN_SHOT: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case SHOW_MOUSE_POINTER_TOGGLE: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case MAX_CONTROLLERACTIONS: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + default: + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + } + } + if ((CPad::GetPad(0)->Mode == 1) && (CPad::GetPad(0)->IsDualAnalog == 0)) // Standard DC controller, PS2 like bindings + { + switch (action) + { + case VEHICLE_LOOKLEFT: + for (int i = 0; (ActionText[i] = Dreamcast_PS2_VehicleLookLeft[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_LOOKRIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_PS2_VehicleLookRight[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_HORN: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_HANDBRAKE: + for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_ENTER_EXIT: + for (int i = 0; (ActionText[i] = Dreamcast_Y[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_ACCELERATE: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_CHANGE_RADIO_STATION: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_BRAKE: + for (int i = 0; (ActionText[i] = Dreamcast_X[i]) != '\0' && i < iLimitCopy; i++); + break; + case TOGGLE_SUBMISSIONS: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_Stick_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_Stick_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_FORWARD: + for (int i = 0; (ActionText[i] = Dreamcast_Stick_Up[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_BACK: + for (int i = 0; (ActionText[i] = Dreamcast_Stick_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_FIREWEAPON: + for (int i = 0; (ActionText[i] = Dreamcast_B[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_FIREWEAPON: + for (int i = 0; (ActionText[i] = Dreamcast_B[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_WEAPON_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_WEAPON_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_LOCK_TARGET: + for (int i = 0; (ActionText[i] = Dreamcast_RightTrigger[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_JUMPING: + for (int i = 0; (ActionText[i] = Dreamcast_X[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SPRINT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_TARGET_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_TARGET_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CENTER_CAMERA_BEHIND_PLAYER: + for (int i = 0; (ActionText[i] = Dreamcast_CenterCamera[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SNIPER_ZOOM_IN: + for (int i = 0; (ActionText[i] = Dreamcast_X[i]) != '\0' && i < iLimitCopy; i++); + break; case PED_SNIPER_ZOOM_OUT: for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); break; @@ -2308,8 +2485,147 @@ wchar *CControllerConfigManager::GetControllerSettingTextWithOrderNumber(e_Contr for (int i = 0; (ActionText[i] = Dreamcast_DPad_Up[i]) != '\0' && i < iLimitCopy; i++); break; case VEHICLE_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Dreamcast_LookBehind[i]) != '\0' && i < iLimitCopy; i++); + break; + case NETWORK_TALK: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case _CONTROLLERACTION_36: // What is that??? + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case TOGGLE_DPAD: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case SWITCH_DEBUG_CAM_ON: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case TAKE_SCREEN_SHOT: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case SHOW_MOUSE_POINTER_TOGGLE: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + case MAX_CONTROLLERACTIONS: // Not Used on Dreamcast + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + default: + for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); + break; + } + } + + if ((CPad::GetPad(0)->Mode == 1) && (CPad::GetPad(0)->IsDualAnalog == 1)) // Dual Stick, PS2 like bindings + { + switch (action) + { + case VEHICLE_LOOKLEFT: + for (int i = 0; (ActionText[i] = PS2_L2[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_LOOKRIGHT: + for (int i = 0; (ActionText[i] = PS2_R2[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_HORN: for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); break; + case VEHICLE_HANDBRAKE: + for (int i = 0; (ActionText[i] = PS2_R1[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_ENTER_EXIT: + for (int i = 0; (ActionText[i] = PS2_Triangle[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_ACCELERATE: + for (int i = 0; (ActionText[i] = PS2_Cross[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_CHANGE_RADIO_STATION: + for (int i = 0; (ActionText[i] = PS2_L1[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_BRAKE: + for (int i = 0; (ActionText[i] = PS2_Square[i]) != '\0' && i < iLimitCopy; i++); + break; + case TOGGLE_SUBMISSIONS: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_LEFT: + for (int i = 0; (ActionText[i] = LefAnalog_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_RIGHT: + for (int i = 0; (ActionText[i] = LefAnalog_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_FORWARD: + for (int i = 0; (ActionText[i] = LefAnalog_Up[i]) != '\0' && i < iLimitCopy; i++); + break; + case GO_BACK: + for (int i = 0; (ActionText[i] = LefAnalog_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Down[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_FIREWEAPON: + for (int i = 0; (ActionText[i] = PS2_Circle[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_FIREWEAPON: + for (int i = 0; (ActionText[i] = PS2_Circle[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_WEAPON_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Left[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_WEAPON_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Right[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_LOCK_TARGET: + for (int i = 0; (ActionText[i] = PS2_R1[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_JUMPING: + for (int i = 0; (ActionText[i] = PS2_Square[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SPRINT: + for (int i = 0; (ActionText[i] = PS2_Circle[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_TARGET_LEFT: + for (int i = 0; (ActionText[i] = PS2_L2[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CYCLE_TARGET_RIGHT: + for (int i = 0; (ActionText[i] = PS2_R2[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_CENTER_CAMERA_BEHIND_PLAYER: + for (int i = 0; (ActionText[i] = Dreamcast_CenterCamera[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SNIPER_ZOOM_IN: + for (int i = 0; (ActionText[i] = PS2_Square[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_SNIPER_ZOOM_OUT: + for (int i = 0; (ActionText[i] = PS2_Cross[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_LEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_RIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_UP: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case PED_1RST_PERSON_LOOK_DOWN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETLEFT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETRIGHT: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETUP: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_TURRETDOWN: + for (int i = 0; (ActionText[i] = Dreamcast_A[i]) != '\0' && i < iLimitCopy; i++); + break; + case CAMERA_CHANGE_VIEW_ALL_SITUATIONS: + for (int i = 0; (ActionText[i] = Dreamcast_DPad_Up[i]) != '\0' && i < iLimitCopy; i++); + break; + case VEHICLE_LOOKBEHIND: + for (int i = 0; (ActionText[i] = Dreamcast_LookBehind[i]) != '\0' && i < iLimitCopy; i++); + break; case NETWORK_TALK: // Not Used on Dreamcast for (int i = 0; (ActionText[i] = Dreamcast_NotAssigned[i]) != '\0' && i < iLimitCopy; i++); break; diff --git a/src/core/ControllerConfig.h b/src/liberty/core/ControllerConfig.h similarity index 100% rename from src/core/ControllerConfig.h rename to src/liberty/core/ControllerConfig.h diff --git a/src/core/Crime.h b/src/liberty/core/Crime.h similarity index 100% rename from src/core/Crime.h rename to src/liberty/core/Crime.h diff --git a/src/core/Debug.cpp b/src/liberty/core/Debug.cpp similarity index 100% rename from src/core/Debug.cpp rename to src/liberty/core/Debug.cpp diff --git a/src/core/Debug.h b/src/liberty/core/Debug.h similarity index 100% rename from src/core/Debug.h rename to src/liberty/core/Debug.h diff --git a/src/core/Directory.cpp b/src/liberty/core/Directory.cpp similarity index 100% rename from src/core/Directory.cpp rename to src/liberty/core/Directory.cpp diff --git a/src/core/Directory.h b/src/liberty/core/Directory.h similarity index 100% rename from src/core/Directory.h rename to src/liberty/core/Directory.h diff --git a/src/core/EventList.cpp b/src/liberty/core/EventList.cpp similarity index 100% rename from src/core/EventList.cpp rename to src/liberty/core/EventList.cpp diff --git a/src/core/EventList.h b/src/liberty/core/EventList.h similarity index 100% rename from src/core/EventList.h rename to src/liberty/core/EventList.h diff --git a/src/core/FileLoader.cpp b/src/liberty/core/FileLoader.cpp similarity index 98% rename from src/core/FileLoader.cpp rename to src/liberty/core/FileLoader.cpp index 8a362c11..8ebfe11c 100644 --- a/src/core/FileLoader.cpp +++ b/src/liberty/core/FileLoader.cpp @@ -215,7 +215,7 @@ CFileLoader::LoadCollisionFile(const char *filename) fd = CFileMgr::OpenFile(filename, "rb"); while(CFileMgr::Read(fd, (char*)&header, sizeof(header))){ - assert(header.ident == 'LLOC'); + assert(header.ident == 'CLOC'); CFileMgr::Read(fd, (char*)work_buff, header.size); memcpy(modelname, work_buff, 24); @@ -295,14 +295,8 @@ CFileLoader::LoadCollisionModel(uint8 *buf, CColModel &model, char *modelname) 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(lroundf(Abs(*(float*)buf)) >= 256 || - lroundf(Abs(*(float*)(buf+4))) >= 256 || - lroundf(Abs(*(float*)(buf+8))) >= 256) { - dbglog(DBG_CRITICAL, "%s:Collision volume too big\n", modelname); - assert(false && "Collision volume too big"); - } - buf += 12; + model.vertices[i].SetFixed(*(int16*)buf, *(int16*)(buf+2), *(int16*)(buf+4)); + buf += 6; } }else model.vertices = nil; @@ -313,8 +307,11 @@ CFileLoader::LoadCollisionModel(uint8 *buf, CColModel &model, char *modelname) 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; + model.triangles[i].Set(model.vertices, *(uint16*)buf, *(uint16*)(buf+2), *(uint16*)(buf+4), buf[6], buf[7]); + buf += 8; + assert(model.triangles[i].a < numVertices); + assert(model.triangles[i].b < numVertices); + assert(model.triangles[i].c < numVertices); } }else model.triangles = nil; diff --git a/src/core/FileLoader.h b/src/liberty/core/FileLoader.h similarity index 100% rename from src/core/FileLoader.h rename to src/liberty/core/FileLoader.h diff --git a/src/core/FileMgr.cpp b/src/liberty/core/FileMgr.cpp similarity index 100% rename from src/core/FileMgr.cpp rename to src/liberty/core/FileMgr.cpp diff --git a/src/core/FileMgr.h b/src/liberty/core/FileMgr.h similarity index 100% rename from src/core/FileMgr.h rename to src/liberty/core/FileMgr.h diff --git a/src/core/Fire.cpp b/src/liberty/core/Fire.cpp similarity index 100% rename from src/core/Fire.cpp rename to src/liberty/core/Fire.cpp diff --git a/src/core/Fire.h b/src/liberty/core/Fire.h similarity index 100% rename from src/core/Fire.h rename to src/liberty/core/Fire.h diff --git a/src/core/FrontEndControls.cpp b/src/liberty/core/FrontEndControls.cpp similarity index 100% rename from src/core/FrontEndControls.cpp rename to src/liberty/core/FrontEndControls.cpp diff --git a/src/core/FrontEndControls.h b/src/liberty/core/FrontEndControls.h similarity index 100% rename from src/core/FrontEndControls.h rename to src/liberty/core/FrontEndControls.h diff --git a/src/core/Frontend.cpp b/src/liberty/core/Frontend.cpp similarity index 90% rename from src/core/Frontend.cpp rename to src/liberty/core/Frontend.cpp index 93305817..e6546b30 100644 --- a/src/core/Frontend.cpp +++ b/src/liberty/core/Frontend.cpp @@ -235,12 +235,26 @@ const char* FrontendFilenames[][2] = { {"fe_iconaudio", "" }, {"fe_icondisplay", "" }, {"fe_iconlanguage", "" }, + +#ifdef RW_DC + {"dc_xboxf",""}, + {"dc_xboxd",""}, + {"dc_ps2f",""}, + {"dc_ps2d",""}, + {"xbox_f",""}, + {"xbox_d",""}, + {"ps2_f",""}, + {"ps2_d",""}, + +#else {"fe_controller", "" }, {"fe_controllersh", "" }, {"fe_arrows1", "" }, {"fe_arrows2", "" }, {"fe_arrows3", "" }, {"fe_arrows4", "" }, + +#endif {"fe_radio1", "" }, {"fe_radio2", "" }, {"fe_radio3", "" }, @@ -1118,6 +1132,46 @@ CMenuManager::Draw() #ifdef PS2_SAVE_DIALOG if(!m_bRenderGameInMenu) #endif + +#ifdef RW_DC + if (aScreens[m_nCurrScreen].m_ScreenName[0] != '\0') { + + SET_FONT_FOR_MENU_HEADER + + if (strcmp("FET_AGS", aScreens[m_nCurrScreen].m_ScreenName) == 0) + { + wchar *PageName = nil; + char asciiTemp[32]; + wchar unicodeTemp[64]; + + sprintf(asciiTemp, "CONTROLLER SETUP"); + AsciiToUnicode(asciiTemp, unicodeTemp); + PageName = unicodeTemp; + + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), PageName); + } + else if (strcmp("FET_CTL", aScreens[m_nCurrScreen].m_ScreenName) == 0) + { + wchar *PageName = nil; + char asciiTemp[32]; + wchar unicodeTemp[64]; + + sprintf(asciiTemp, "KEYBOARD MOUSE SETUP"); + AsciiToUnicode(asciiTemp, unicodeTemp); + PageName = unicodeTemp; + + CFont::PrintString(PAGE_NAME_X(MENUHEADER_POS_X), SCREEN_SCALE_FROM_BOTTOM(MENUHEADER_POS_Y), PageName); + } + else + { + 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; + } + +#else if (aScreens[m_nCurrScreen].m_ScreenName[0] != '\0') { SET_FONT_FOR_MENU_HEADER @@ -1126,6 +1180,7 @@ CMenuManager::Draw() // Weird place to put that. nextYToUse += 24.0f + 10.0f; } +#endif 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)); @@ -1380,7 +1435,29 @@ CMenuManager::Draw() sprintf(gString, "FEM_SL%d", i); leftText = TheText.Get(gString); } - } else { + } + else if ((strcmp(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName, "FET_AGS")) == 0) + { + //wchar *rightText = nil; + char asciiTemp[32]; + wchar unicodeTemp[64]; + + sprintf(asciiTemp, "CONTROLLER SETUP"); + AsciiToUnicode(asciiTemp, unicodeTemp); + leftText = unicodeTemp; + } + else if ((strcmp(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName, "FET_CTL")) == 0) + { + //wchar *rightText = nil; + char asciiTemp[32]; + wchar unicodeTemp[64]; + + sprintf(asciiTemp, "KEYBOARD MOUSE SETUP"); + AsciiToUnicode(asciiTemp, unicodeTemp); + leftText = unicodeTemp; + } + + else { leftText = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName); } @@ -1461,10 +1538,16 @@ CMenuManager::Draw() case MENUACTION_CTRLCONFIG: switch (CPad::GetPad(0)->Mode) { case 0: - rightText = TheText.Get("FEC_CF1"); + //rightText = TheText.Get("FEC_CF1"); + sprintf(asciiTemp, "XBOX LIKE"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; break; case 1: - rightText = TheText.Get("FEC_CF2"); + //rightText = TheText.Get("FEC_CF2"); + sprintf(asciiTemp, "PS2 LIKE"); + AsciiToUnicode(asciiTemp, unicodeTemp); + rightText = unicodeTemp; break; case 2: rightText = TheText.Get("FEC_CF3"); @@ -1933,7 +2016,7 @@ CMenuManager::Draw() char strver[200]; wchar ustr[200]; - snprintf(strver, sizeof(strver), "DCA3: %s", getExecutableTag()); + snprintf(strver, sizeof(strver), "dca-liberty: %s", getExecutableTag()); AsciiToUnicode(strver, ustr); CFont::SetScale(MENU_X(MENU_TEXT_SIZE_X*3/4), MENU_Y(MENU_TEXT_SIZE_Y*3/4)); @@ -3725,7 +3808,11 @@ CMenuManager::LoadController(int8 type) } // Unload current textures +#ifdef RW_DC + for (int i = DC_XBOXF; i <= PS2_D; i++) +#else for (int i = FE_CONTROLLER; i <= FE_ARROWS4; i++) +#endif m_aFrontEndSprites[i].Delete(); // Unload txd @@ -3758,7 +3845,11 @@ CMenuManager::LoadController(int8 type) assert(txdSlot != -1); // Load new textures CTxdStore::SetCurrentTxd(txdSlot); +#ifdef RW_DC + for (int i = DC_XBOXF; i <= PS2_D; i++) { +#else for (int i = FE_CONTROLLER; i <= FE_ARROWS4; i++) { +#endif m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); } @@ -5388,10 +5479,12 @@ CMenuManager::ProcessButtonPresses(void) #ifdef FIX_BUGS case MENUACTION_CTRLCONFIG: CPad::GetPad(0)->Mode += changeValueBy; - if (CPad::GetPad(0)->Mode > 3) + //if (CPad::GetPad(0)->Mode > 3) + if (CPad::GetPad(0)->Mode > 1) CPad::GetPad(0)->Mode = 0; else if (CPad::GetPad(0)->Mode < 0) - CPad::GetPad(0)->Mode = 3; + //CPad::GetPad(0)->Mode = 3; + CPad::GetPad(0)->Mode = 1; SaveSettings(); DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); break; @@ -5541,7 +5634,8 @@ CMenuManager::ProcessOnOffMenuOptions() #ifndef FIX_BUGS case MENUACTION_CTRLCONFIG: CPad::GetPad(0)->Mode++; - if (CPad::GetPad(0)->Mode > 3) + //if (CPad::GetPad(0)->Mode > 3) + if (CPad::GetPad(0)->Mode > 1) CPad::GetPad(0)->Mode = 0; DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_SETTING_CHANGE, 0); break; @@ -5883,7 +5977,6 @@ CMenuManager::PrintController(void) #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) { @@ -6174,7 +6267,373 @@ CMenuManager::PrintController(void) #undef X #undef Y } + +#elif RW_DC +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) + + switch (CPad::GetPad(0)->Mode) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (m_DisplayControllerOnFoot) + m_aFrontEndSprites[XBOX_F].Draw(MENU_X_LEFT_ALIGNED(65.0f), MENU_Y(75.0f), MENU_X(512.0f), MENU_Y(256.0f), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[XBOX_D].Draw(MENU_X_LEFT_ALIGNED(65.0f), MENU_Y(75.0f), MENU_X(512.0f), MENU_Y(256.0f), CRGBA(255, 255, 255, 255)); + + } + else + { + if (m_DisplayControllerOnFoot) + m_aFrontEndSprites[DC_XBOXF].Draw(MENU_X_LEFT_ALIGNED(65.0f), MENU_Y(75.0f), MENU_X(512.0f), MENU_Y(256.0f), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[DC_XBOXD].Draw(MENU_X_LEFT_ALIGNED(65.0f), MENU_Y(75.0f), MENU_X(512.0f), MENU_Y(256.0f), CRGBA(255, 255, 255, 255)); + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (m_DisplayControllerOnFoot) + m_aFrontEndSprites[PS2_F].Draw(MENU_X_LEFT_ALIGNED(65.0f), MENU_Y(75.0f), MENU_X(512.0f), MENU_Y(256.0f), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[PS2_D].Draw(MENU_X_LEFT_ALIGNED(65.0f), MENU_Y(75.0f), MENU_X(512.0f), MENU_Y(256.0f), CRGBA(255, 255, 255, 255)); + } + else + { + if (m_DisplayControllerOnFoot) + m_aFrontEndSprites[DC_PS2F].Draw(MENU_X_LEFT_ALIGNED(65.0f), MENU_Y(75.0f), MENU_X(512.0f), MENU_Y(256.0f), CRGBA(255, 255, 255, 255)); + else + m_aFrontEndSprites[DC_PS2D].Draw(MENU_X_LEFT_ALIGNED(65.0f), MENU_Y(75.0f), MENU_X(512.0f), MENU_Y(256.0f), CRGBA(255, 255, 255, 255)); + } + break; + } + + // 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: + // if (CPad::GetPad(0)->IsDualAnalog) + // { + // 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")); + // } + // else + // { + // // CFont::SetRightJustifyOn(); // X + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(220.0f), TheText.Get("FEC_CWL")); + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(232.0f), TheText.Get("FEC_LB")); + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(206.0f), TheText.Get("FEC_CAM")); + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(176.0f), TheText.Get("FEC_MOV")); + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(247.0f), MENU_Y(101.0f), TheText.Get("FEC_TAR")); + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(297.0f), MENU_Y(89.0f), TheText.Get("FEC_CWR")); + // // CFont::SetJustifyOn(); // X + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(257.0f), TheText.Get("FEC_PAU")); + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(89.0f), TheText.Get("FEC_CMR")); + // // CFont::SetJustifyOn(); // X + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(195.0f), TheText.Get("FEC_JUM")); + // // CFont::SetJustifyOn(); // X + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(178.0f), TheText.Get("FEC_ENV")); + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(212.0f), TheText.Get("FEC_RUN")); + // // CFont::PrintString(MENU_X_LEFT_ALIGNED(394.0f), MENU_Y(101.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 1: + // if (CPad::GetPad(0)->IsDualAnalog) + // { + // 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")); + // } + // else + // { + // CFont::SetRightJustifyOn(); // X + // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(220.0f), TheText.Get("FEC_CWL")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(232.0f), TheText.Get("FEC_LB")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(206.0f), TheText.Get("FEC_CAM")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(176.0f), TheText.Get("FEC_MOV")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(247.0f), MENU_Y(101.0f), TheText.Get("FEC_CMR")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(297.0f), MENU_Y(89.0f), TheText.Get("FEC_CWR")); + // CFont::SetJustifyOn(); // X + // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(257.0f), TheText.Get("FEC_PAU")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(89.0f), TheText.Get("FEC_JUM")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(178.0f), TheText.Get("FEC_ENV")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(212.0f), TheText.Get("FEC_RUN")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(394.0f), MENU_Y(101.0f), TheText.Get("FEC_TAR")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(195.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; + + // // 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; + + // if (CPad::GetPad(0)->IsDualAnalog) + // { + // 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")); + // } + // else + // { + // CFont::SetRightJustifyOn(); // X + // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(220.0f), TheText.Get("FEC_SUB")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(232.0f), TheText.Get("FEC_HRN")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(206.0f), TheText.Get("FEC_CAM")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(202.0f), MENU_Y(176.0f), TheText.Get("FEC_VES")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(247.0f), MENU_Y(101.0f), TheText.Get("FEC_BRA")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(297.0f), MENU_Y(89.0f), TheText.Get("FEC_RSC")); + // CFont::SetJustifyOn(); // X + // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(257.0f), TheText.Get("FEC_PAU")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(344.0f), MENU_Y(89.0f), TheText.Get("FEC_CMR")); + // CFont::SetJustifyOn(); // X + // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(195.0f), TheText.Get("FEC_HAB")); + // CFont::SetJustifyOn(); // X + + // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(212.0f), TheText.Get("FEC_PFR")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(394.0f), MENU_Y(101.0f), TheText.Get("FEC_ACC")); + // CFont::PrintString(MENU_X_LEFT_ALIGNED(438.45f), MENU_Y(178.0f), TheText.Get("FEC_EXV")); + + // //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")); + // } + + + // 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 +} #else + void CMenuManager::PrintController(void) { @@ -6395,6 +6854,7 @@ CMenuManager::PrintController(void) CFont::SetDropShadowPosition(0); // X } + #endif #ifdef MENU_MAP diff --git a/src/core/Frontend.h b/src/liberty/core/Frontend.h similarity index 99% rename from src/core/Frontend.h rename to src/liberty/core/Frontend.h index dbb86a8d..7af71bbe 100644 --- a/src/core/Frontend.h +++ b/src/liberty/core/Frontend.h @@ -105,12 +105,23 @@ enum eFrontendSprites FE_ICONAUDIO, FE_ICONDISPLAY, FE_ICONLANGUAGE, +#ifdef RW_DC + DC_XBOXF, + DC_XBOXD, + DC_PS2F, + DC_PS2D, + XBOX_F, + XBOX_D, + PS2_F, + PS2_D, +#else FE_CONTROLLER, FE_CONTROLLERSH, FE_ARROWS1, FE_ARROWS2, FE_ARROWS3, FE_ARROWS4, +#endif FE_RADIO1, FE_RADIO2, FE_RADIO3, diff --git a/src/core/FrontendTriggers.h b/src/liberty/core/FrontendTriggers.h similarity index 100% rename from src/core/FrontendTriggers.h rename to src/liberty/core/FrontendTriggers.h diff --git a/src/core/Frontend_PS2.cpp b/src/liberty/core/Frontend_PS2.cpp similarity index 100% rename from src/core/Frontend_PS2.cpp rename to src/liberty/core/Frontend_PS2.cpp diff --git a/src/core/Frontend_PS2.h b/src/liberty/core/Frontend_PS2.h similarity index 100% rename from src/core/Frontend_PS2.h rename to src/liberty/core/Frontend_PS2.h diff --git a/src/core/Game.cpp b/src/liberty/core/Game.cpp similarity index 100% rename from src/core/Game.cpp rename to src/liberty/core/Game.cpp diff --git a/src/core/Game.h b/src/liberty/core/Game.h similarity index 100% rename from src/core/Game.h rename to src/liberty/core/Game.h diff --git a/src/core/General.h b/src/liberty/core/General.h similarity index 100% rename from src/core/General.h rename to src/liberty/core/General.h diff --git a/src/core/IniFile.cpp b/src/liberty/core/IniFile.cpp similarity index 100% rename from src/core/IniFile.cpp rename to src/liberty/core/IniFile.cpp diff --git a/src/core/IniFile.h b/src/liberty/core/IniFile.h similarity index 100% rename from src/core/IniFile.h rename to src/liberty/core/IniFile.h diff --git a/src/core/Lists.cpp b/src/liberty/core/Lists.cpp similarity index 100% rename from src/core/Lists.cpp rename to src/liberty/core/Lists.cpp diff --git a/src/core/Lists.h b/src/liberty/core/Lists.h similarity index 100% rename from src/core/Lists.h rename to src/liberty/core/Lists.h diff --git a/src/core/MenuScreens.cpp b/src/liberty/core/MenuScreens.cpp similarity index 100% rename from src/core/MenuScreens.cpp rename to src/liberty/core/MenuScreens.cpp diff --git a/src/core/MenuScreensCustom.cpp b/src/liberty/core/MenuScreensCustom.cpp similarity index 100% rename from src/core/MenuScreensCustom.cpp rename to src/liberty/core/MenuScreensCustom.cpp index abd7694d..23a9b7e2 100644 --- a/src/core/MenuScreensCustom.cpp +++ b/src/liberty/core/MenuScreensCustom.cpp @@ -902,11 +902,11 @@ CMenuScreenCustom aScreens[MENUPAGES] = { #ifndef GTA_HANDHELD MENUACTION_SCREENRES, "FED_RES", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, #endif + MULTISAMPLING_SELECTOR 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 diff --git a/src/core/Pad.cpp b/src/liberty/core/Pad.cpp similarity index 99% rename from src/core/Pad.cpp rename to src/liberty/core/Pad.cpp index 9469c3d7..bc4bd8ef 100755 --- a/src/core/Pad.cpp +++ b/src/liberty/core/Pad.cpp @@ -2785,22 +2785,22 @@ bool CPad::WeaponJustDown(void) case 0: //Xbox Mode if (CPad::GetPad(0)->IsDualAnalog) { - if (NewState.RightTrigger > 128) + if (NewState.RightTrigger > 128 && !(OldState.RightTrigger > 128)) return true; } else { - if (NewState.RightTrigger > 128) + if (NewState.RightTrigger > 128 && !(OldState.RightTrigger > 128)) return true; } case 1: //PS2 Mode if (CPad::GetPad(0)->IsDualAnalog) { - return NewState.B; + return NewState.B && !OldState.B; } else { - return NewState.B; + return NewState.B && !OldState.B; } } @@ -3211,6 +3211,7 @@ bool CPad::JumpJustDown(void) } } + return false; #else return !!(NewState.Square && !OldState.Square); #endif @@ -3297,6 +3298,8 @@ bool CPad::ShiftTargetLeftJustDown(void) return !!(NewState.DPadLeft && !OldState.DPadLeft); } } + + return false; #else return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); @@ -3331,6 +3334,8 @@ bool CPad::ShiftTargetRightJustDown(void) return !!(NewState.DPadRight && !OldState.DPadRight); } } + + return false; #else return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); diff --git a/src/core/Pad.h b/src/liberty/core/Pad.h similarity index 100% rename from src/core/Pad.h rename to src/liberty/core/Pad.h diff --git a/src/core/Placeable.cpp b/src/liberty/core/Placeable.cpp similarity index 100% rename from src/core/Placeable.cpp rename to src/liberty/core/Placeable.cpp diff --git a/src/core/Placeable.h b/src/liberty/core/Placeable.h similarity index 100% rename from src/core/Placeable.h rename to src/liberty/core/Placeable.h diff --git a/src/core/PlayerInfo.cpp b/src/liberty/core/PlayerInfo.cpp similarity index 100% rename from src/core/PlayerInfo.cpp rename to src/liberty/core/PlayerInfo.cpp diff --git a/src/core/PlayerInfo.h b/src/liberty/core/PlayerInfo.h similarity index 100% rename from src/core/PlayerInfo.h rename to src/liberty/core/PlayerInfo.h diff --git a/src/core/Pools.cpp b/src/liberty/core/Pools.cpp similarity index 100% rename from src/core/Pools.cpp rename to src/liberty/core/Pools.cpp diff --git a/src/core/Pools.h b/src/liberty/core/Pools.h similarity index 100% rename from src/core/Pools.h rename to src/liberty/core/Pools.h diff --git a/src/core/Profile.cpp b/src/liberty/core/Profile.cpp similarity index 100% rename from src/core/Profile.cpp rename to src/liberty/core/Profile.cpp diff --git a/src/core/Profile.h b/src/liberty/core/Profile.h similarity index 100% rename from src/core/Profile.h rename to src/liberty/core/Profile.h diff --git a/src/core/Radar.cpp b/src/liberty/core/Radar.cpp similarity index 97% rename from src/core/Radar.cpp rename to src/liberty/core/Radar.cpp index a40a421e..c348078f 100644 --- a/src/core/Radar.cpp +++ b/src/liberty/core/Radar.cpp @@ -1114,13 +1114,12 @@ CRadar::LoadTextures() 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) +#define WAYPOINT_R (255>>4) +#define WAYPOINT_G (72>>4) +#define WAYPOINT_B (77>>4) - RwRaster *raster = RwRasterCreate(16, 16, 0, rwRASTERTYPETEXTURE | rwRASTERFORMAT8888); + RwRaster *raster = RwRasterCreate(16, 16, 16, rwRASTERTYPETEXTURE | rwRASTERFORMAT4444); - #if defined(RW_DC) RwUInt16 *pixels = (RwUInt16 *)RwRasterLock(raster, 0, rwRASTERLOCKWRITE); for(int x = 0; x < 16; x++) for(int y = 0; y < 16; y++) @@ -1132,31 +1131,10 @@ CRadar::LoadTextures() || (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 - pixels[x + y * 16] = (WAYPOINT_B>>4) | ((WAYPOINT_G>>4) << 4) | ((WAYPOINT_R>>4) << 8) | (15 << 12); + pixels[x + y * 16] = WAYPOINT_B | (WAYPOINT_G << 4) | (WAYPOINT_R << 8) | (15 << 12); else pixels[x + y * 16] = 0xF000; // black } - #else - 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 - } - #endif RwRasterUnlock(raster); WaypointSprite.m_pTexture = RwTextureCreate(raster); RwTextureSetFilterMode(WaypointSprite.m_pTexture, rwFILTERLINEAR); diff --git a/src/core/Radar.h b/src/liberty/core/Radar.h similarity index 100% rename from src/core/Radar.h rename to src/liberty/core/Radar.h diff --git a/src/core/Range2D.cpp b/src/liberty/core/Range2D.cpp similarity index 100% rename from src/core/Range2D.cpp rename to src/liberty/core/Range2D.cpp diff --git a/src/core/Range2D.h b/src/liberty/core/Range2D.h similarity index 100% rename from src/core/Range2D.h rename to src/liberty/core/Range2D.h diff --git a/src/core/Range3D.cpp b/src/liberty/core/Range3D.cpp similarity index 100% rename from src/core/Range3D.cpp rename to src/liberty/core/Range3D.cpp diff --git a/src/core/Range3D.h b/src/liberty/core/Range3D.h similarity index 100% rename from src/core/Range3D.h rename to src/liberty/core/Range3D.h diff --git a/src/core/References.cpp b/src/liberty/core/References.cpp similarity index 100% rename from src/core/References.cpp rename to src/liberty/core/References.cpp diff --git a/src/core/References.h b/src/liberty/core/References.h similarity index 100% rename from src/core/References.h rename to src/liberty/core/References.h diff --git a/src/core/Stats.cpp b/src/liberty/core/Stats.cpp similarity index 100% rename from src/core/Stats.cpp rename to src/liberty/core/Stats.cpp diff --git a/src/core/Stats.h b/src/liberty/core/Stats.h similarity index 100% rename from src/core/Stats.h rename to src/liberty/core/Stats.h diff --git a/src/core/Streaming.cpp b/src/liberty/core/Streaming.cpp similarity index 99% rename from src/core/Streaming.cpp rename to src/liberty/core/Streaming.cpp index d9f22245..b6dedc32 100644 --- a/src/core/Streaming.cpp +++ b/src/liberty/core/Streaming.cpp @@ -1164,6 +1164,12 @@ bool re3RemoveLeastUsedModel() { return CStreaming::RemoveLeastUsedModel(); } +bool re3EmergencyRemoveModel() { + auto usedmem = CStreaming::ms_memoryUsed; + CStreaming::DeleteRwObjectsBehindCamera(CStreaming::ms_memoryUsed-1); + return usedmem != CStreaming::ms_memoryUsed; +} + bool CStreaming::RemoveLeastUsedModel(void) { diff --git a/src/core/Streaming.h b/src/liberty/core/Streaming.h similarity index 100% rename from src/core/Streaming.h rename to src/liberty/core/Streaming.h diff --git a/src/core/SurfaceTable.cpp b/src/liberty/core/SurfaceTable.cpp similarity index 100% rename from src/core/SurfaceTable.cpp rename to src/liberty/core/SurfaceTable.cpp diff --git a/src/core/SurfaceTable.h b/src/liberty/core/SurfaceTable.h similarity index 100% rename from src/core/SurfaceTable.h rename to src/liberty/core/SurfaceTable.h diff --git a/src/core/TimeStep.cpp b/src/liberty/core/TimeStep.cpp similarity index 100% rename from src/core/TimeStep.cpp rename to src/liberty/core/TimeStep.cpp diff --git a/src/core/TimeStep.h b/src/liberty/core/TimeStep.h similarity index 100% rename from src/core/TimeStep.h rename to src/liberty/core/TimeStep.h diff --git a/src/core/Timer.cpp b/src/liberty/core/Timer.cpp similarity index 100% rename from src/core/Timer.cpp rename to src/liberty/core/Timer.cpp diff --git a/src/core/Timer.h b/src/liberty/core/Timer.h similarity index 100% rename from src/core/Timer.h rename to src/liberty/core/Timer.h diff --git a/src/core/User.cpp b/src/liberty/core/User.cpp similarity index 100% rename from src/core/User.cpp rename to src/liberty/core/User.cpp diff --git a/src/core/User.h b/src/liberty/core/User.h similarity index 100% rename from src/core/User.h rename to src/liberty/core/User.h diff --git a/src/core/Wanted.cpp b/src/liberty/core/Wanted.cpp similarity index 100% rename from src/core/Wanted.cpp rename to src/liberty/core/Wanted.cpp diff --git a/src/core/Wanted.h b/src/liberty/core/Wanted.h similarity index 100% rename from src/core/Wanted.h rename to src/liberty/core/Wanted.h diff --git a/src/core/World.cpp b/src/liberty/core/World.cpp similarity index 100% rename from src/core/World.cpp rename to src/liberty/core/World.cpp diff --git a/src/core/World.h b/src/liberty/core/World.h similarity index 100% rename from src/core/World.h rename to src/liberty/core/World.h diff --git a/src/core/ZoneCull.cpp b/src/liberty/core/ZoneCull.cpp similarity index 100% rename from src/core/ZoneCull.cpp rename to src/liberty/core/ZoneCull.cpp diff --git a/src/core/ZoneCull.h b/src/liberty/core/ZoneCull.h similarity index 100% rename from src/core/ZoneCull.h rename to src/liberty/core/ZoneCull.h diff --git a/src/core/Zones.cpp b/src/liberty/core/Zones.cpp similarity index 100% rename from src/core/Zones.cpp rename to src/liberty/core/Zones.cpp diff --git a/src/core/Zones.h b/src/liberty/core/Zones.h similarity index 100% rename from src/core/Zones.h rename to src/liberty/core/Zones.h diff --git a/src/core/common.h b/src/liberty/core/common.h similarity index 98% rename from src/core/common.h rename to src/liberty/core/common.h index f1b9cada..661499d6 100644 --- a/src/core/common.h +++ b/src/liberty/core/common.h @@ -3,7 +3,7 @@ #define _CRT_SECURE_NO_WARNINGS #define _USE_MATH_DEFINES -#include "common_defines.h" +#include "src/common_defines.h" #ifdef __MWERKS__ #define __STDC_LIMIT_MACROS // so we get UINT32_MAX etc @@ -187,8 +187,8 @@ __always_inline uint32 ldb(uint32 p, uint32 s, uint32 w) #define SCREEN_HEIGHT ((float)448) #endif #else -#define SCREEN_WIDTH ((float)640) -#define SCREEN_HEIGHT ((float)480) +#define SCREEN_WIDTH ((float)RsGlobal.width) +#define SCREEN_HEIGHT ((float)RsGlobal.height) #endif #define SCREEN_HEIGHT_PAL ((float)512) diff --git a/src/core/config.h b/src/liberty/core/config.h similarity index 98% rename from src/core/config.h rename to src/liberty/core/config.h index 1da628f8..e8fa4443 100644 --- a/src/core/config.h +++ b/src/liberty/core/config.h @@ -212,7 +212,7 @@ enum Config { // 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) +// #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) @@ -308,10 +308,10 @@ enum Config { // 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 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 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 diff --git a/src/core/main.cpp b/src/liberty/core/main.cpp similarity index 99% rename from src/core/main.cpp rename to src/liberty/core/main.cpp index 4b536d3d..9f80c19b 100644 --- a/src/core/main.cpp +++ b/src/liberty/core/main.cpp @@ -163,36 +163,36 @@ Error(char *fmt, ...) void ValidateVersion() { - int32 file = CFileMgr::OpenFile("models\\coll\\peds.col", "rb"); - char buff[128]; + // int32 file = CFileMgr::OpenFile("models\\coll\\peds.col", "rb"); + // char buff[128]; - if ( file != -1 ) - { - CFileMgr::Seek(file, 100, SEEK_SET); + // 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); - } + // 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; - } - } + // if ( !strncmp(buff, "grandtheftauto3", 15) ) + // { + // strncpy(version_name, &buff[15], 64); + // CFileMgr::CloseFile(file); + // return; + // } + // } - LoadingScreen("Invalid version", NULL, NULL); + // LoadingScreen("Invalid version", NULL, NULL); - while(true) - { - ; - } + // while(true) + // { + // ; + // } } bool diff --git a/src/core/main.h b/src/liberty/core/main.h similarity index 100% rename from src/core/main.h rename to src/liberty/core/main.h diff --git a/src/core/obrstr.cpp b/src/liberty/core/obrstr.cpp similarity index 100% rename from src/core/obrstr.cpp rename to src/liberty/core/obrstr.cpp diff --git a/src/core/obrstr.h b/src/liberty/core/obrstr.h similarity index 100% rename from src/core/obrstr.h rename to src/liberty/core/obrstr.h diff --git a/src/core/re3.cpp b/src/liberty/core/re3.cpp similarity index 99% rename from src/core/re3.cpp rename to src/liberty/core/re3.cpp index 0a1f8629..4f3e50ef 100644 --- a/src/core/re3.cpp +++ b/src/liberty/core/re3.cpp @@ -56,7 +56,7 @@ #include #endif -#include "../vmu/vmu.h" +#include "vmu/vmu.h" #ifdef RWLIBS extern "C" int vsprintf(char* const _Buffer, char const* const _Format, va_list _ArgList); diff --git a/src/core/templates.h b/src/liberty/core/templates.h similarity index 100% rename from src/core/templates.h rename to src/liberty/core/templates.h diff --git a/src/core/timebars.cpp b/src/liberty/core/timebars.cpp similarity index 100% rename from src/core/timebars.cpp rename to src/liberty/core/timebars.cpp diff --git a/src/core/timebars.h b/src/liberty/core/timebars.h similarity index 100% rename from src/core/timebars.h rename to src/liberty/core/timebars.h diff --git a/src/entities/Dummy.cpp b/src/liberty/entities/Dummy.cpp similarity index 100% rename from src/entities/Dummy.cpp rename to src/liberty/entities/Dummy.cpp diff --git a/src/entities/Dummy.h b/src/liberty/entities/Dummy.h similarity index 100% rename from src/entities/Dummy.h rename to src/liberty/entities/Dummy.h diff --git a/src/entities/Entity.cpp b/src/liberty/entities/Entity.cpp similarity index 99% rename from src/entities/Entity.cpp rename to src/liberty/entities/Entity.cpp index c38f12c7..7659f9de 100644 --- a/src/entities/Entity.cpp +++ b/src/liberty/entities/Entity.cpp @@ -83,6 +83,11 @@ CEntity::~CEntity(void) ResolveReferences(); } +bool +CEntity::IsFence(void) { + return IsObject() && ::IsFence(static_cast(this)->GetModelIndex()); +} + void CEntity::SetModelIndex(uint32 id) { diff --git a/src/entities/Entity.h b/src/liberty/entities/Entity.h similarity index 99% rename from src/entities/Entity.h rename to src/liberty/entities/Entity.h index 6174b61d..10b81c18 100644 --- a/src/entities/Entity.h +++ b/src/liberty/entities/Entity.h @@ -131,6 +131,7 @@ public: 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; } + bool IsFence(void); RpAtomic *GetAtomic(void) { assert(RwObjectGetType(m_rwObject) == rpATOMIC); diff --git a/src/entities/Physical.cpp b/src/liberty/entities/Physical.cpp similarity index 99% rename from src/entities/Physical.cpp rename to src/liberty/entities/Physical.cpp index 32a3df3b..ee1335ee 100644 --- a/src/entities/Physical.cpp +++ b/src/liberty/entities/Physical.cpp @@ -59,7 +59,7 @@ CPhysical::CPhysical(void) bInfiniteMass = false; bIsInWater = false; bHitByTrain = false; - bSkipLineCol = false; + bSkipLineCol = IsFence(); m_fDistanceTravelled = 0.0f; m_treadable[PATH_CAR] = nil; @@ -1461,6 +1461,10 @@ CPhysical::ProcessCollisionSectorList(CPtrList *lists) skipCollision = false; altcollision = false; + if(A->IsFence() && B->IsFence()) { + skipCollision = true; + A->bSkipLineCol = true; + } if(B->IsBuilding()) skipCollision = false; else if(IsStreetLight(A->GetModelIndex()) && @@ -1922,12 +1926,12 @@ CPhysical::ProcessCollision(void) n = NUMSTEPS(0.09f); step = savedTimeStep / n; } - }else if(responsecase == COLLRESPONSE_SMALLBOX || responsecase == COLLRESPONSE_FENCEPART){ + }else if(responsecase == COLLRESPONSE_SMALLBOX){ if(distSq >= sq(0.15f)){ n = NUMSTEPS(0.15f); step = savedTimeStep / n; } - }else{ + }else if(responsecase != COLLRESPONSE_FENCEPART){ if(distSq >= sq(0.3f)){ n = NUMSTEPS(0.3f); step = savedTimeStep / n; diff --git a/src/entities/Physical.h b/src/liberty/entities/Physical.h similarity index 100% rename from src/entities/Physical.h rename to src/liberty/entities/Physical.h diff --git a/src/extras/GitSHA1.cpp.in b/src/liberty/extras/GitSHA1.cpp.in similarity index 100% rename from src/extras/GitSHA1.cpp.in rename to src/liberty/extras/GitSHA1.cpp.in diff --git a/src/extras/GitSHA1.h b/src/liberty/extras/GitSHA1.h similarity index 100% rename from src/extras/GitSHA1.h rename to src/liberty/extras/GitSHA1.h diff --git a/src/extras/arrow.inc b/src/liberty/extras/arrow.inc similarity index 100% rename from src/extras/arrow.inc rename to src/liberty/extras/arrow.inc diff --git a/src/extras/cursor.inc b/src/liberty/extras/cursor.inc similarity index 100% rename from src/extras/cursor.inc rename to src/liberty/extras/cursor.inc diff --git a/src/extras/custompipes.cpp b/src/liberty/extras/custompipes.cpp similarity index 100% rename from src/extras/custompipes.cpp rename to src/liberty/extras/custompipes.cpp diff --git a/src/extras/custompipes.h b/src/liberty/extras/custompipes.h similarity index 100% rename from src/extras/custompipes.h rename to src/liberty/extras/custompipes.h diff --git a/src/extras/custompipes_d3d9.cpp b/src/liberty/extras/custompipes_d3d9.cpp similarity index 100% rename from src/extras/custompipes_d3d9.cpp rename to src/liberty/extras/custompipes_d3d9.cpp diff --git a/src/extras/custompipes_gl.cpp b/src/liberty/extras/custompipes_gl.cpp similarity index 100% rename from src/extras/custompipes_gl.cpp rename to src/liberty/extras/custompipes_gl.cpp diff --git a/src/extras/debugmenu.cpp b/src/liberty/extras/debugmenu.cpp similarity index 100% rename from src/extras/debugmenu.cpp rename to src/liberty/extras/debugmenu.cpp diff --git a/src/extras/debugmenu.h b/src/liberty/extras/debugmenu.h similarity index 100% rename from src/extras/debugmenu.h rename to src/liberty/extras/debugmenu.h diff --git a/src/extras/frontendoption.cpp b/src/liberty/extras/frontendoption.cpp similarity index 100% rename from src/extras/frontendoption.cpp rename to src/liberty/extras/frontendoption.cpp diff --git a/src/extras/frontendoption.h b/src/liberty/extras/frontendoption.h similarity index 100% rename from src/extras/frontendoption.h rename to src/liberty/extras/frontendoption.h diff --git a/src/liberty/extras/ini.h b/src/liberty/extras/ini.h new file mode 100644 index 00000000..f14489e6 --- /dev/null +++ b/src/liberty/extras/ini.h @@ -0,0 +1,857 @@ +/* + * 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 + +#ifdef RW_DC +#include +#endif + +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; + std::size_t start_offt; + + 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(const_cast(fileContents.c_str()), fileSize); + fileReadStream.close(); + T_LineData output; + if (fileSize == 0) + { + return output; + } + std::string buffer; + buffer.reserve(50); + std::cout << "PRE READ! "<< fileSize << fileContents << std::endl; +#ifdef RW_DC + { + vmu_pkg_t vmu_pkg; + if(vmu_pkg_parse(reinterpret_cast(const_cast(fileContents.c_str())), &vmu_pkg) != 0) { + std::cout << "FUCKIN REESTED!" << std::endl; + start_offt = 0; + } else { + start_offt = reinterpret_cast(vmu_pkg.data) + - reinterpret_cast(fileContents.c_str()); + } + } +#endif + std::cout << "POST READ!" << std::endl; + for (std::size_t i = start_offt; 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) + { + std::cout << "CREATING INI READER" << std::endl; + fileReadStream.open(filename, std::ios::in | std::ios::binary); + if (keepLineData) + { + lineData = std::make_shared(); + } + + start_offt = 0; + } + ~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; + std::stringstream memStream; + inline static std::ios_base::iostate lastError_; + public: + bool prettyPrint = false; + + INIGenerator(std::string const& filename) + : fileWriteStream(filename, std::ios::out | std::ios::binary) + { + lastError_ = fileWriteStream.rdstate(); + std::cerr << "INIGenerator::INIGenerator(): " << fileWriteStream.rdstate() << std::endl; + } + + ~INIGenerator() + { + std::cerr << "INIGenerator::~INIGenerator()" << std::endl; + memStream << "FUCKINGBITCH!"; + std::string str = memStream.str(); + const char *buf = str.c_str(); + int buf_size = str.length(); + size_t stream_size = memStream.tellp(); + + if(!fileWriteStream.good()) { + std::cerr << "NO GOOD!" << std::endl; + return; + } + +#ifdef RW_DC + uint8_t *data; + uint8_t icon_buf[512 * 1]; + vmu_pkg_t vmu_pkg = { + .desc_short = "DCA3 Settings", + .desc_long = "DCA3 Settings", + .app_id = "DCA3", + .icon_cnt = 1, + .icon_anim_speed = 0, + .data_len = buf_size, + .icon_data = icon_buf, + .data = reinterpret_cast(buf), + }; + + //std::cout << "LOADING ICON" << std::endl; + if (vmu_pkg_load_icon(&vmu_pkg, "settings.ico") < 0) { + std::cerr << "FAILED TO LOAD ICON!" << std::endl; + vmu_pkg.icon_cnt = 0; + } + + //std::cout << "BUILDING PACKAGE" << std::endl; + int pkg_size = 0; + if(vmu_pkg_build(&vmu_pkg, &data, &pkg_size) < 0) { + std::cerr << "FAILED TO BUILD PACKAGE!" << std::endl; + lastError_ = std::ios_base::badbit; + return; + } + + buf = reinterpret_cast(data); + std::cout << "WRITING: " << std::string(buf, buf + pkg_size) << std::endl; +#endif + + fileWriteStream.write(buf, pkg_size); + lastError_ = fileWriteStream.rdstate(); + +#ifdef RW_DC + free(data); +#endif + std::cerr << "WROTE: " << lastError_ << std::endl; + std::cerr << "STREAM_SIZE: " << stream_size << std::endl; + std::cerr << "BUF_SIZE: " << buf_size << std::endl; + std::cerr << "PKG_SIZE: " << pkg_size << std::endl; + } + + bool operator<<(const std::string& str) + { + memStream << str; + return true; + } + + bool operator<<(INIStructure const& data) + { + if (!data.size()) + { + return true; + } + auto it = data.begin(); + for (;;) + { + auto const& section = it->first; + auto const& collection = it->second; + memStream + << "[" + << section + << "]"; + if (collection.size()) + { + memStream << INIStringUtil::endl; + auto it2 = collection.begin(); + for (;;) + { + auto key = it2->first; + INIStringUtil::replace(key, "=", "\\="); + auto value = it2->second; + INIStringUtil::trim(value); + memStream + << key + << ((prettyPrint) ? " = " : "=") + << value; + if (++it2 == collection.end()) + { + break; + } + memStream << INIStringUtil::endl; + } + } + if (++it == data.end()) + { + break; + } + memStream << INIStringUtil::endl; + if (prettyPrint) + { + memStream << INIStringUtil::endl; + } + } + return true; + } + + operator bool() const + { + return fileWriteStream.rdstate() & fileWriteStream.goodbit; + } + + static std::ios_base::iostate lastError() { return lastError_; } + }; + + 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) + { + if(INIGenerator generator(filename); generator) { + generator.prettyPrint = prettyPrint; + generator << data; + } + return INIGenerator::lastError() & std::ios_base::goodbit; + } + + 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); + { + if (INIGenerator generator(filename); generator && output.size()) + { + generator.prettyPrint = prettyPrint; + auto line = output.begin(); + for (;;) + { + generator << *line; + if (++line == output.end()) + { + break; + } + generator << INIStringUtil::endl; + } + } + return INIGenerator::lastError() & std::ios_base::goodbit; + } + 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; + } + if(INIGenerator generator(filename); generator) { + generator.prettyPrint = pretty; + generator << data; + } + return INIGenerator::lastError() & std::ios_base::goodbit; + } + 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/liberty/extras/postfx.cpp similarity index 100% rename from src/extras/postfx.cpp rename to src/liberty/extras/postfx.cpp diff --git a/src/extras/postfx.h b/src/liberty/extras/postfx.h similarity index 100% rename from src/extras/postfx.h rename to src/liberty/extras/postfx.h diff --git a/src/extras/re3_inttypes.h b/src/liberty/extras/re3_inttypes.h similarity index 100% rename from src/extras/re3_inttypes.h rename to src/liberty/extras/re3_inttypes.h diff --git a/src/extras/screendroplets.cpp b/src/liberty/extras/screendroplets.cpp similarity index 100% rename from src/extras/screendroplets.cpp rename to src/liberty/extras/screendroplets.cpp diff --git a/src/extras/screendroplets.h b/src/liberty/extras/screendroplets.h similarity index 100% rename from src/extras/screendroplets.h rename to src/liberty/extras/screendroplets.h diff --git a/src/extras/shaders/colourfilterIII.frag b/src/liberty/extras/shaders/colourfilterIII.frag similarity index 100% rename from src/extras/shaders/colourfilterIII.frag rename to src/liberty/extras/shaders/colourfilterIII.frag diff --git a/src/extras/shaders/colourfilterIII_PS.hlsl b/src/liberty/extras/shaders/colourfilterIII_PS.hlsl similarity index 100% rename from src/extras/shaders/colourfilterIII_PS.hlsl rename to src/liberty/extras/shaders/colourfilterIII_PS.hlsl diff --git a/src/extras/shaders/contrast.frag b/src/liberty/extras/shaders/contrast.frag similarity index 100% rename from src/extras/shaders/contrast.frag rename to src/liberty/extras/shaders/contrast.frag diff --git a/src/extras/shaders/contrastPS.hlsl b/src/liberty/extras/shaders/contrastPS.hlsl similarity index 100% rename from src/extras/shaders/contrastPS.hlsl rename to src/liberty/extras/shaders/contrastPS.hlsl diff --git a/src/extras/shaders/default_UV2.vert b/src/liberty/extras/shaders/default_UV2.vert similarity index 100% rename from src/extras/shaders/default_UV2.vert rename to src/liberty/extras/shaders/default_UV2.vert diff --git a/src/extras/shaders/default_UV2_VS.hlsl b/src/liberty/extras/shaders/default_UV2_VS.hlsl similarity index 100% rename from src/extras/shaders/default_UV2_VS.hlsl rename to src/liberty/extras/shaders/default_UV2_VS.hlsl diff --git a/src/extras/shaders/im2d.vert b/src/liberty/extras/shaders/im2d.vert similarity index 100% rename from src/extras/shaders/im2d.vert rename to src/liberty/extras/shaders/im2d.vert diff --git a/src/extras/shaders/im2d_UV2.vert b/src/liberty/extras/shaders/im2d_UV2.vert similarity index 100% rename from src/extras/shaders/im2d_UV2.vert rename to src/liberty/extras/shaders/im2d_UV2.vert diff --git a/src/extras/shaders/lighting.h b/src/liberty/extras/shaders/lighting.h similarity index 100% rename from src/extras/shaders/lighting.h rename to src/liberty/extras/shaders/lighting.h diff --git a/src/extras/shaders/make_glsl.sh b/src/liberty/extras/shaders/make_glsl.sh similarity index 100% rename from src/extras/shaders/make_glsl.sh rename to src/liberty/extras/shaders/make_glsl.sh diff --git a/src/extras/shaders/make_hlsl.cmd b/src/liberty/extras/shaders/make_hlsl.cmd similarity index 100% rename from src/extras/shaders/make_hlsl.cmd rename to src/liberty/extras/shaders/make_hlsl.cmd diff --git a/src/extras/shaders/makeinc_glsl.sh b/src/liberty/extras/shaders/makeinc_glsl.sh similarity index 100% rename from src/extras/shaders/makeinc_glsl.sh rename to src/liberty/extras/shaders/makeinc_glsl.sh diff --git a/src/extras/shaders/makeinc_hlsl.sh b/src/liberty/extras/shaders/makeinc_hlsl.sh similarity index 100% rename from src/extras/shaders/makeinc_hlsl.sh rename to src/liberty/extras/shaders/makeinc_hlsl.sh diff --git a/src/extras/shaders/neoGloss.frag b/src/liberty/extras/shaders/neoGloss.frag similarity index 100% rename from src/extras/shaders/neoGloss.frag rename to src/liberty/extras/shaders/neoGloss.frag diff --git a/src/extras/shaders/neoGloss.vert b/src/liberty/extras/shaders/neoGloss.vert similarity index 100% rename from src/extras/shaders/neoGloss.vert rename to src/liberty/extras/shaders/neoGloss.vert diff --git a/src/extras/shaders/neoGloss_PS.hlsl b/src/liberty/extras/shaders/neoGloss_PS.hlsl similarity index 100% rename from src/extras/shaders/neoGloss_PS.hlsl rename to src/liberty/extras/shaders/neoGloss_PS.hlsl diff --git a/src/extras/shaders/neoGloss_VS.hlsl b/src/liberty/extras/shaders/neoGloss_VS.hlsl similarity index 100% rename from src/extras/shaders/neoGloss_VS.hlsl rename to src/liberty/extras/shaders/neoGloss_VS.hlsl diff --git a/src/extras/shaders/neoRim.vert b/src/liberty/extras/shaders/neoRim.vert similarity index 100% rename from src/extras/shaders/neoRim.vert rename to src/liberty/extras/shaders/neoRim.vert diff --git a/src/extras/shaders/neoRimSkin.vert b/src/liberty/extras/shaders/neoRimSkin.vert similarity index 100% rename from src/extras/shaders/neoRimSkin.vert rename to src/liberty/extras/shaders/neoRimSkin.vert diff --git a/src/extras/shaders/neoRimSkin_VS.hlsl b/src/liberty/extras/shaders/neoRimSkin_VS.hlsl similarity index 100% rename from src/extras/shaders/neoRimSkin_VS.hlsl rename to src/liberty/extras/shaders/neoRimSkin_VS.hlsl diff --git a/src/extras/shaders/neoRim_VS.hlsl b/src/liberty/extras/shaders/neoRim_VS.hlsl similarity index 100% rename from src/extras/shaders/neoRim_VS.hlsl rename to src/liberty/extras/shaders/neoRim_VS.hlsl diff --git a/src/extras/shaders/neoVehicle.frag b/src/liberty/extras/shaders/neoVehicle.frag similarity index 100% rename from src/extras/shaders/neoVehicle.frag rename to src/liberty/extras/shaders/neoVehicle.frag diff --git a/src/extras/shaders/neoVehicle.vert b/src/liberty/extras/shaders/neoVehicle.vert similarity index 100% rename from src/extras/shaders/neoVehicle.vert rename to src/liberty/extras/shaders/neoVehicle.vert diff --git a/src/extras/shaders/neoVehicle_PS.hlsl b/src/liberty/extras/shaders/neoVehicle_PS.hlsl similarity index 100% rename from src/extras/shaders/neoVehicle_PS.hlsl rename to src/liberty/extras/shaders/neoVehicle_PS.hlsl diff --git a/src/extras/shaders/neoVehicle_VS.hlsl b/src/liberty/extras/shaders/neoVehicle_VS.hlsl similarity index 100% rename from src/extras/shaders/neoVehicle_VS.hlsl rename to src/liberty/extras/shaders/neoVehicle_VS.hlsl diff --git a/src/extras/shaders/neoWorldIII.frag b/src/liberty/extras/shaders/neoWorldIII.frag similarity index 100% rename from src/extras/shaders/neoWorldIII.frag rename to src/liberty/extras/shaders/neoWorldIII.frag diff --git a/src/extras/shaders/neoWorldIII_PS.hlsl b/src/liberty/extras/shaders/neoWorldIII_PS.hlsl similarity index 100% rename from src/extras/shaders/neoWorldIII_PS.hlsl rename to src/liberty/extras/shaders/neoWorldIII_PS.hlsl diff --git a/src/extras/shaders/obj/colourfilterIII_PS.cso b/src/liberty/extras/shaders/obj/colourfilterIII_PS.cso similarity index 100% rename from src/extras/shaders/obj/colourfilterIII_PS.cso rename to src/liberty/extras/shaders/obj/colourfilterIII_PS.cso diff --git a/src/extras/shaders/obj/colourfilterIII_PS.inc b/src/liberty/extras/shaders/obj/colourfilterIII_PS.inc similarity index 100% rename from src/extras/shaders/obj/colourfilterIII_PS.inc rename to src/liberty/extras/shaders/obj/colourfilterIII_PS.inc diff --git a/src/extras/shaders/obj/colourfilterIII_frag.inc b/src/liberty/extras/shaders/obj/colourfilterIII_frag.inc similarity index 100% rename from src/extras/shaders/obj/colourfilterIII_frag.inc rename to src/liberty/extras/shaders/obj/colourfilterIII_frag.inc diff --git a/src/extras/shaders/obj/contrastPS.cso b/src/liberty/extras/shaders/obj/contrastPS.cso similarity index 100% rename from src/extras/shaders/obj/contrastPS.cso rename to src/liberty/extras/shaders/obj/contrastPS.cso diff --git a/src/extras/shaders/obj/contrastPS.inc b/src/liberty/extras/shaders/obj/contrastPS.inc similarity index 100% rename from src/extras/shaders/obj/contrastPS.inc rename to src/liberty/extras/shaders/obj/contrastPS.inc diff --git a/src/extras/shaders/obj/contrast_frag.inc b/src/liberty/extras/shaders/obj/contrast_frag.inc similarity index 100% rename from src/extras/shaders/obj/contrast_frag.inc rename to src/liberty/extras/shaders/obj/contrast_frag.inc diff --git a/src/extras/shaders/obj/default_UV2_VS.cso b/src/liberty/extras/shaders/obj/default_UV2_VS.cso similarity index 100% rename from src/extras/shaders/obj/default_UV2_VS.cso rename to src/liberty/extras/shaders/obj/default_UV2_VS.cso diff --git a/src/extras/shaders/obj/default_UV2_VS.inc b/src/liberty/extras/shaders/obj/default_UV2_VS.inc similarity index 100% rename from src/extras/shaders/obj/default_UV2_VS.inc rename to src/liberty/extras/shaders/obj/default_UV2_VS.inc diff --git a/src/extras/shaders/obj/default_UV2_vert.inc b/src/liberty/extras/shaders/obj/default_UV2_vert.inc similarity index 100% rename from src/extras/shaders/obj/default_UV2_vert.inc rename to src/liberty/extras/shaders/obj/default_UV2_vert.inc diff --git a/src/extras/shaders/obj/im2d_UV2_vert.inc b/src/liberty/extras/shaders/obj/im2d_UV2_vert.inc similarity index 100% rename from src/extras/shaders/obj/im2d_UV2_vert.inc rename to src/liberty/extras/shaders/obj/im2d_UV2_vert.inc diff --git a/src/extras/shaders/obj/im2d_vert.inc b/src/liberty/extras/shaders/obj/im2d_vert.inc similarity index 100% rename from src/extras/shaders/obj/im2d_vert.inc rename to src/liberty/extras/shaders/obj/im2d_vert.inc diff --git a/src/extras/shaders/obj/neoGloss_PS.cso b/src/liberty/extras/shaders/obj/neoGloss_PS.cso similarity index 100% rename from src/extras/shaders/obj/neoGloss_PS.cso rename to src/liberty/extras/shaders/obj/neoGloss_PS.cso diff --git a/src/extras/shaders/obj/neoGloss_PS.inc b/src/liberty/extras/shaders/obj/neoGloss_PS.inc similarity index 100% rename from src/extras/shaders/obj/neoGloss_PS.inc rename to src/liberty/extras/shaders/obj/neoGloss_PS.inc diff --git a/src/extras/shaders/obj/neoGloss_VS.cso b/src/liberty/extras/shaders/obj/neoGloss_VS.cso similarity index 100% rename from src/extras/shaders/obj/neoGloss_VS.cso rename to src/liberty/extras/shaders/obj/neoGloss_VS.cso diff --git a/src/extras/shaders/obj/neoGloss_VS.inc b/src/liberty/extras/shaders/obj/neoGloss_VS.inc similarity index 100% rename from src/extras/shaders/obj/neoGloss_VS.inc rename to src/liberty/extras/shaders/obj/neoGloss_VS.inc diff --git a/src/extras/shaders/obj/neoGloss_frag.inc b/src/liberty/extras/shaders/obj/neoGloss_frag.inc similarity index 100% rename from src/extras/shaders/obj/neoGloss_frag.inc rename to src/liberty/extras/shaders/obj/neoGloss_frag.inc diff --git a/src/extras/shaders/obj/neoGloss_vert.inc b/src/liberty/extras/shaders/obj/neoGloss_vert.inc similarity index 100% rename from src/extras/shaders/obj/neoGloss_vert.inc rename to src/liberty/extras/shaders/obj/neoGloss_vert.inc diff --git a/src/extras/shaders/obj/neoRimSkin_VS.cso b/src/liberty/extras/shaders/obj/neoRimSkin_VS.cso similarity index 100% rename from src/extras/shaders/obj/neoRimSkin_VS.cso rename to src/liberty/extras/shaders/obj/neoRimSkin_VS.cso diff --git a/src/extras/shaders/obj/neoRimSkin_VS.inc b/src/liberty/extras/shaders/obj/neoRimSkin_VS.inc similarity index 100% rename from src/extras/shaders/obj/neoRimSkin_VS.inc rename to src/liberty/extras/shaders/obj/neoRimSkin_VS.inc diff --git a/src/extras/shaders/obj/neoRimSkin_vert.inc b/src/liberty/extras/shaders/obj/neoRimSkin_vert.inc similarity index 100% rename from src/extras/shaders/obj/neoRimSkin_vert.inc rename to src/liberty/extras/shaders/obj/neoRimSkin_vert.inc diff --git a/src/extras/shaders/obj/neoRim_VS.cso b/src/liberty/extras/shaders/obj/neoRim_VS.cso similarity index 100% rename from src/extras/shaders/obj/neoRim_VS.cso rename to src/liberty/extras/shaders/obj/neoRim_VS.cso diff --git a/src/extras/shaders/obj/neoRim_VS.inc b/src/liberty/extras/shaders/obj/neoRim_VS.inc similarity index 100% rename from src/extras/shaders/obj/neoRim_VS.inc rename to src/liberty/extras/shaders/obj/neoRim_VS.inc diff --git a/src/extras/shaders/obj/neoRim_vert.inc b/src/liberty/extras/shaders/obj/neoRim_vert.inc similarity index 100% rename from src/extras/shaders/obj/neoRim_vert.inc rename to src/liberty/extras/shaders/obj/neoRim_vert.inc diff --git a/src/extras/shaders/obj/neoVehicle_PS.cso b/src/liberty/extras/shaders/obj/neoVehicle_PS.cso similarity index 100% rename from src/extras/shaders/obj/neoVehicle_PS.cso rename to src/liberty/extras/shaders/obj/neoVehicle_PS.cso diff --git a/src/extras/shaders/obj/neoVehicle_PS.inc b/src/liberty/extras/shaders/obj/neoVehicle_PS.inc similarity index 100% rename from src/extras/shaders/obj/neoVehicle_PS.inc rename to src/liberty/extras/shaders/obj/neoVehicle_PS.inc diff --git a/src/extras/shaders/obj/neoVehicle_VS.cso b/src/liberty/extras/shaders/obj/neoVehicle_VS.cso similarity index 100% rename from src/extras/shaders/obj/neoVehicle_VS.cso rename to src/liberty/extras/shaders/obj/neoVehicle_VS.cso diff --git a/src/extras/shaders/obj/neoVehicle_VS.inc b/src/liberty/extras/shaders/obj/neoVehicle_VS.inc similarity index 100% rename from src/extras/shaders/obj/neoVehicle_VS.inc rename to src/liberty/extras/shaders/obj/neoVehicle_VS.inc diff --git a/src/extras/shaders/obj/neoVehicle_frag.inc b/src/liberty/extras/shaders/obj/neoVehicle_frag.inc similarity index 100% rename from src/extras/shaders/obj/neoVehicle_frag.inc rename to src/liberty/extras/shaders/obj/neoVehicle_frag.inc diff --git a/src/extras/shaders/obj/neoVehicle_vert.inc b/src/liberty/extras/shaders/obj/neoVehicle_vert.inc similarity index 100% rename from src/extras/shaders/obj/neoVehicle_vert.inc rename to src/liberty/extras/shaders/obj/neoVehicle_vert.inc diff --git a/src/extras/shaders/obj/neoWorldIII_PS.cso b/src/liberty/extras/shaders/obj/neoWorldIII_PS.cso similarity index 100% rename from src/extras/shaders/obj/neoWorldIII_PS.cso rename to src/liberty/extras/shaders/obj/neoWorldIII_PS.cso diff --git a/src/extras/shaders/obj/neoWorldIII_PS.inc b/src/liberty/extras/shaders/obj/neoWorldIII_PS.inc similarity index 100% rename from src/extras/shaders/obj/neoWorldIII_PS.inc rename to src/liberty/extras/shaders/obj/neoWorldIII_PS.inc diff --git a/src/extras/shaders/obj/neoWorldIII_frag.inc b/src/liberty/extras/shaders/obj/neoWorldIII_frag.inc similarity index 100% rename from src/extras/shaders/obj/neoWorldIII_frag.inc rename to src/liberty/extras/shaders/obj/neoWorldIII_frag.inc diff --git a/src/extras/shaders/obj/screenDroplet_PS.cso b/src/liberty/extras/shaders/obj/screenDroplet_PS.cso similarity index 100% rename from src/extras/shaders/obj/screenDroplet_PS.cso rename to src/liberty/extras/shaders/obj/screenDroplet_PS.cso diff --git a/src/extras/shaders/obj/screenDroplet_PS.inc b/src/liberty/extras/shaders/obj/screenDroplet_PS.inc similarity index 100% rename from src/extras/shaders/obj/screenDroplet_PS.inc rename to src/liberty/extras/shaders/obj/screenDroplet_PS.inc diff --git a/src/extras/shaders/obj/screenDroplet_frag.inc b/src/liberty/extras/shaders/obj/screenDroplet_frag.inc similarity index 100% rename from src/extras/shaders/obj/screenDroplet_frag.inc rename to src/liberty/extras/shaders/obj/screenDroplet_frag.inc diff --git a/src/extras/shaders/obj/simple_frag.inc b/src/liberty/extras/shaders/obj/simple_frag.inc similarity index 100% rename from src/extras/shaders/obj/simple_frag.inc rename to src/liberty/extras/shaders/obj/simple_frag.inc diff --git a/src/extras/shaders/screenDroplet.frag b/src/liberty/extras/shaders/screenDroplet.frag similarity index 100% rename from src/extras/shaders/screenDroplet.frag rename to src/liberty/extras/shaders/screenDroplet.frag diff --git a/src/extras/shaders/screenDroplet_PS.hlsl b/src/liberty/extras/shaders/screenDroplet_PS.hlsl similarity index 100% rename from src/extras/shaders/screenDroplet_PS.hlsl rename to src/liberty/extras/shaders/screenDroplet_PS.hlsl diff --git a/src/extras/shaders/simple.frag b/src/liberty/extras/shaders/simple.frag similarity index 100% rename from src/extras/shaders/simple.frag rename to src/liberty/extras/shaders/simple.frag diff --git a/src/extras/shaders/standardConstants.h b/src/liberty/extras/shaders/standardConstants.h similarity index 100% rename from src/extras/shaders/standardConstants.h rename to src/liberty/extras/shaders/standardConstants.h diff --git a/src/fakerw/fake.cpp b/src/liberty/fakerw/fake.cpp similarity index 100% rename from src/fakerw/fake.cpp rename to src/liberty/fakerw/fake.cpp diff --git a/src/fakerw/rpanisot.h b/src/liberty/fakerw/rpanisot.h similarity index 100% rename from src/fakerw/rpanisot.h rename to src/liberty/fakerw/rpanisot.h diff --git a/src/fakerw/rphanim.h b/src/liberty/fakerw/rphanim.h similarity index 100% rename from src/fakerw/rphanim.h rename to src/liberty/fakerw/rphanim.h diff --git a/src/fakerw/rpmatfx.h b/src/liberty/fakerw/rpmatfx.h similarity index 100% rename from src/fakerw/rpmatfx.h rename to src/liberty/fakerw/rpmatfx.h diff --git a/src/fakerw/rpskin.h b/src/liberty/fakerw/rpskin.h similarity index 100% rename from src/fakerw/rpskin.h rename to src/liberty/fakerw/rpskin.h diff --git a/src/fakerw/rpworld.h b/src/liberty/fakerw/rpworld.h similarity index 100% rename from src/fakerw/rpworld.h rename to src/liberty/fakerw/rpworld.h diff --git a/src/fakerw/rtbmp.h b/src/liberty/fakerw/rtbmp.h similarity index 100% rename from src/fakerw/rtbmp.h rename to src/liberty/fakerw/rtbmp.h diff --git a/src/fakerw/rtcharse.h b/src/liberty/fakerw/rtcharse.h similarity index 100% rename from src/fakerw/rtcharse.h rename to src/liberty/fakerw/rtcharse.h diff --git a/src/fakerw/rtpng.h b/src/liberty/fakerw/rtpng.h similarity index 100% rename from src/fakerw/rtpng.h rename to src/liberty/fakerw/rtpng.h diff --git a/src/fakerw/rtquat.h b/src/liberty/fakerw/rtquat.h similarity index 100% rename from src/fakerw/rtquat.h rename to src/liberty/fakerw/rtquat.h diff --git a/src/liberty/fakerw/rwcore.h b/src/liberty/fakerw/rwcore.h new file mode 100644 index 00000000..3430a5ae --- /dev/null +++ b/src/liberty/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/liberty/fakerw/rwplcore.h similarity index 100% rename from src/fakerw/rwplcore.h rename to src/liberty/fakerw/rwplcore.h diff --git a/src/math/Matrix.cpp b/src/liberty/math/Matrix.cpp similarity index 93% rename from src/math/Matrix.cpp rename to src/liberty/math/Matrix.cpp index 88433b26..41265e87 100644 --- a/src/math/Matrix.cpp +++ b/src/liberty/math/Matrix.cpp @@ -282,7 +282,7 @@ CMatrix::SetRotate(float xAngle, float yAngle, float zAngle) void CMatrix::RotateX(float x) { -#ifdef DC_SH4 +#if 0 && defined(DC_SH4) // this is bugged and does not yield correct results mat_load(reinterpret_cast(this)); mat_rotate_x(x); mat_store(reinterpret_cast(this)); @@ -312,7 +312,7 @@ CMatrix::RotateX(float x) void CMatrix::RotateY(float y) { -#ifdef DC_SH4 +#if 0 && defined(DC_SH4) // this is bugged and does not yield correct results mat_load(reinterpret_cast(this)); mat_rotate_y(y); mat_store(reinterpret_cast(this)); @@ -342,7 +342,7 @@ CMatrix::RotateY(float y) void CMatrix::RotateZ(float z) { -#ifdef DC_SH4 +#if 0 && defined(DC_SH4) // this is bugged and does not yield correct results mat_load(reinterpret_cast(this)); mat_rotate_z(z); mat_store(reinterpret_cast(this)); @@ -372,7 +372,7 @@ CMatrix::RotateZ(float z) void CMatrix::Rotate(float x, float y, float z) { -#ifdef DC_SH4 +#if 0 && defined(DC_SH4) // this is bugged and does not yield correct results mat_load(reinterpret_cast(this)); mat_rotate(x, y, z); mat_store(reinterpret_cast(this)); @@ -404,6 +404,20 @@ CMatrix::Rotate(float x, float y, float z) float z2 = sZ * sY - (cZ * sX) * cY; float z3 = cX * cY; + #if !defined(DC_TEXCONV) && !defined(DC_SIM) + this->rx = fipr(x1, y1, z1, 0, rx, ry, rz, 0); + this->ry = fipr(x2, y2, z2, 0, rx, ry, rz, 0); + this->rz = fipr(x3, y3, z3, 0, rx, ry, rz, 0); + this->fx = fipr(x1, y1, z1, 0, ux, uy, uz, 0); + this->fy = fipr(x2, y2, z2, 0, ux, uy, uz, 0); + this->fz = fipr(x3, y3, z3, 0, ux, uy, uz, 0); + this->ux = fipr(x1, y1, z1, 0, ax, ay, az, 0); + this->uy = fipr(x2, y2, z2, 0, ax, ay, az, 0); + this->uz = fipr(x3, y3, z3, 0, ax, ay, az, 0); + this->px = fipr(x1, y1, z1, 0, px, py, pz, 0); + this->py = fipr(x2, y2, z2, 0, px, py, pz, 0); + this->pz = fipr(x3, y3, z3, 0, px, py, pz, 0); + #else this->rx = x1 * rx + y1 * ry + z1 * rz; this->ry = x2 * rx + y2 * ry + z2 * rz; this->rz = x3 * rx + y3 * ry + z3 * rz; @@ -416,6 +430,7 @@ CMatrix::Rotate(float x, float y, float z) this->px = x1 * px + y1 * py + z1 * pz; this->py = x2 * px + y2 * py + z2 * pz; this->pz = x3 * px + y3 * py + z3 * pz; + #endif #endif } diff --git a/src/math/Matrix.h b/src/liberty/math/Matrix.h similarity index 100% rename from src/math/Matrix.h rename to src/liberty/math/Matrix.h diff --git a/src/math/Quaternion.cpp b/src/liberty/math/Quaternion.cpp similarity index 100% rename from src/math/Quaternion.cpp rename to src/liberty/math/Quaternion.cpp diff --git a/src/math/Quaternion.h b/src/liberty/math/Quaternion.h similarity index 100% rename from src/math/Quaternion.h rename to src/liberty/math/Quaternion.h diff --git a/src/math/Rect.cpp b/src/liberty/math/Rect.cpp similarity index 100% rename from src/math/Rect.cpp rename to src/liberty/math/Rect.cpp diff --git a/src/math/Rect.h b/src/liberty/math/Rect.h similarity index 100% rename from src/math/Rect.h rename to src/liberty/math/Rect.h diff --git a/src/math/Vector.cpp b/src/liberty/math/Vector.cpp similarity index 100% rename from src/math/Vector.cpp rename to src/liberty/math/Vector.cpp diff --git a/src/math/Vector.h b/src/liberty/math/Vector.h similarity index 100% rename from src/math/Vector.h rename to src/liberty/math/Vector.h diff --git a/src/math/Vector2D.h b/src/liberty/math/Vector2D.h similarity index 100% rename from src/math/Vector2D.h rename to src/liberty/math/Vector2D.h diff --git a/src/math/VuVector.h b/src/liberty/math/VuVector.h similarity index 100% rename from src/math/VuVector.h rename to src/liberty/math/VuVector.h diff --git a/src/math/math.cpp b/src/liberty/math/math.cpp similarity index 100% rename from src/math/math.cpp rename to src/liberty/math/math.cpp diff --git a/src/math/maths.h b/src/liberty/math/maths.h similarity index 98% rename from src/math/maths.h rename to src/liberty/math/maths.h index 35e34806..9959bf61 100644 --- a/src/math/maths.h +++ b/src/liberty/math/maths.h @@ -1,6 +1,6 @@ #pragma once -#include "common_defines.h" +#include "src/common_defines.h" #include #include diff --git a/src/modelinfo/BaseModelInfo.cpp b/src/liberty/modelinfo/BaseModelInfo.cpp similarity index 100% rename from src/modelinfo/BaseModelInfo.cpp rename to src/liberty/modelinfo/BaseModelInfo.cpp diff --git a/src/modelinfo/BaseModelInfo.h b/src/liberty/modelinfo/BaseModelInfo.h similarity index 100% rename from src/modelinfo/BaseModelInfo.h rename to src/liberty/modelinfo/BaseModelInfo.h diff --git a/src/modelinfo/ClumpModelInfo.cpp b/src/liberty/modelinfo/ClumpModelInfo.cpp similarity index 100% rename from src/modelinfo/ClumpModelInfo.cpp rename to src/liberty/modelinfo/ClumpModelInfo.cpp diff --git a/src/modelinfo/ClumpModelInfo.h b/src/liberty/modelinfo/ClumpModelInfo.h similarity index 100% rename from src/modelinfo/ClumpModelInfo.h rename to src/liberty/modelinfo/ClumpModelInfo.h diff --git a/src/modelinfo/MloModelInfo.cpp b/src/liberty/modelinfo/MloModelInfo.cpp similarity index 100% rename from src/modelinfo/MloModelInfo.cpp rename to src/liberty/modelinfo/MloModelInfo.cpp diff --git a/src/modelinfo/MloModelInfo.h b/src/liberty/modelinfo/MloModelInfo.h similarity index 100% rename from src/modelinfo/MloModelInfo.h rename to src/liberty/modelinfo/MloModelInfo.h diff --git a/src/modelinfo/ModelIndices.cpp b/src/liberty/modelinfo/ModelIndices.cpp similarity index 100% rename from src/modelinfo/ModelIndices.cpp rename to src/liberty/modelinfo/ModelIndices.cpp diff --git a/src/modelinfo/ModelIndices.h b/src/liberty/modelinfo/ModelIndices.h similarity index 100% rename from src/modelinfo/ModelIndices.h rename to src/liberty/modelinfo/ModelIndices.h diff --git a/src/modelinfo/ModelInfo.cpp b/src/liberty/modelinfo/ModelInfo.cpp similarity index 100% rename from src/modelinfo/ModelInfo.cpp rename to src/liberty/modelinfo/ModelInfo.cpp diff --git a/src/modelinfo/ModelInfo.h b/src/liberty/modelinfo/ModelInfo.h similarity index 100% rename from src/modelinfo/ModelInfo.h rename to src/liberty/modelinfo/ModelInfo.h diff --git a/src/modelinfo/PedModelInfo.cpp b/src/liberty/modelinfo/PedModelInfo.cpp similarity index 100% rename from src/modelinfo/PedModelInfo.cpp rename to src/liberty/modelinfo/PedModelInfo.cpp diff --git a/src/modelinfo/PedModelInfo.h b/src/liberty/modelinfo/PedModelInfo.h similarity index 100% rename from src/modelinfo/PedModelInfo.h rename to src/liberty/modelinfo/PedModelInfo.h diff --git a/src/modelinfo/SimpleModelInfo.cpp b/src/liberty/modelinfo/SimpleModelInfo.cpp similarity index 100% rename from src/modelinfo/SimpleModelInfo.cpp rename to src/liberty/modelinfo/SimpleModelInfo.cpp diff --git a/src/modelinfo/SimpleModelInfo.h b/src/liberty/modelinfo/SimpleModelInfo.h similarity index 100% rename from src/modelinfo/SimpleModelInfo.h rename to src/liberty/modelinfo/SimpleModelInfo.h diff --git a/src/modelinfo/TimeModelInfo.cpp b/src/liberty/modelinfo/TimeModelInfo.cpp similarity index 100% rename from src/modelinfo/TimeModelInfo.cpp rename to src/liberty/modelinfo/TimeModelInfo.cpp diff --git a/src/modelinfo/TimeModelInfo.h b/src/liberty/modelinfo/TimeModelInfo.h similarity index 100% rename from src/modelinfo/TimeModelInfo.h rename to src/liberty/modelinfo/TimeModelInfo.h diff --git a/src/modelinfo/VehicleModelInfo.cpp b/src/liberty/modelinfo/VehicleModelInfo.cpp similarity index 100% rename from src/modelinfo/VehicleModelInfo.cpp rename to src/liberty/modelinfo/VehicleModelInfo.cpp diff --git a/src/modelinfo/VehicleModelInfo.h b/src/liberty/modelinfo/VehicleModelInfo.h similarity index 100% rename from src/modelinfo/VehicleModelInfo.h rename to src/liberty/modelinfo/VehicleModelInfo.h diff --git a/src/modelinfo/XtraCompsModelInfo.h b/src/liberty/modelinfo/XtraCompsModelInfo.h similarity index 100% rename from src/modelinfo/XtraCompsModelInfo.h rename to src/liberty/modelinfo/XtraCompsModelInfo.h diff --git a/src/objects/CutsceneHead.cpp b/src/liberty/objects/CutsceneHead.cpp similarity index 100% rename from src/objects/CutsceneHead.cpp rename to src/liberty/objects/CutsceneHead.cpp diff --git a/src/objects/CutsceneHead.h b/src/liberty/objects/CutsceneHead.h similarity index 100% rename from src/objects/CutsceneHead.h rename to src/liberty/objects/CutsceneHead.h diff --git a/src/objects/CutsceneObject.cpp b/src/liberty/objects/CutsceneObject.cpp similarity index 100% rename from src/objects/CutsceneObject.cpp rename to src/liberty/objects/CutsceneObject.cpp diff --git a/src/objects/CutsceneObject.h b/src/liberty/objects/CutsceneObject.h similarity index 100% rename from src/objects/CutsceneObject.h rename to src/liberty/objects/CutsceneObject.h diff --git a/src/objects/DummyObject.cpp b/src/liberty/objects/DummyObject.cpp similarity index 100% rename from src/objects/DummyObject.cpp rename to src/liberty/objects/DummyObject.cpp diff --git a/src/objects/DummyObject.h b/src/liberty/objects/DummyObject.h similarity index 100% rename from src/objects/DummyObject.h rename to src/liberty/objects/DummyObject.h diff --git a/src/objects/Object.cpp b/src/liberty/objects/Object.cpp similarity index 100% rename from src/objects/Object.cpp rename to src/liberty/objects/Object.cpp diff --git a/src/objects/Object.h b/src/liberty/objects/Object.h similarity index 100% rename from src/objects/Object.h rename to src/liberty/objects/Object.h diff --git a/src/objects/ObjectData.cpp b/src/liberty/objects/ObjectData.cpp similarity index 100% rename from src/objects/ObjectData.cpp rename to src/liberty/objects/ObjectData.cpp diff --git a/src/objects/ObjectData.h b/src/liberty/objects/ObjectData.h similarity index 100% rename from src/objects/ObjectData.h rename to src/liberty/objects/ObjectData.h diff --git a/src/objects/ParticleObject.cpp b/src/liberty/objects/ParticleObject.cpp similarity index 100% rename from src/objects/ParticleObject.cpp rename to src/liberty/objects/ParticleObject.cpp diff --git a/src/objects/ParticleObject.h b/src/liberty/objects/ParticleObject.h similarity index 100% rename from src/objects/ParticleObject.h rename to src/liberty/objects/ParticleObject.h diff --git a/src/objects/Projectile.cpp b/src/liberty/objects/Projectile.cpp similarity index 100% rename from src/objects/Projectile.cpp rename to src/liberty/objects/Projectile.cpp diff --git a/src/objects/Projectile.h b/src/liberty/objects/Projectile.h similarity index 100% rename from src/objects/Projectile.h rename to src/liberty/objects/Projectile.h diff --git a/src/peds/CivilianPed.cpp b/src/liberty/peds/CivilianPed.cpp similarity index 100% rename from src/peds/CivilianPed.cpp rename to src/liberty/peds/CivilianPed.cpp diff --git a/src/peds/CivilianPed.h b/src/liberty/peds/CivilianPed.h similarity index 100% rename from src/peds/CivilianPed.h rename to src/liberty/peds/CivilianPed.h diff --git a/src/peds/CopPed.cpp b/src/liberty/peds/CopPed.cpp similarity index 100% rename from src/peds/CopPed.cpp rename to src/liberty/peds/CopPed.cpp diff --git a/src/peds/CopPed.h b/src/liberty/peds/CopPed.h similarity index 100% rename from src/peds/CopPed.h rename to src/liberty/peds/CopPed.h diff --git a/src/peds/DummyPed.h b/src/liberty/peds/DummyPed.h similarity index 100% rename from src/peds/DummyPed.h rename to src/liberty/peds/DummyPed.h diff --git a/src/peds/EmergencyPed.cpp b/src/liberty/peds/EmergencyPed.cpp similarity index 100% rename from src/peds/EmergencyPed.cpp rename to src/liberty/peds/EmergencyPed.cpp diff --git a/src/peds/EmergencyPed.h b/src/liberty/peds/EmergencyPed.h similarity index 100% rename from src/peds/EmergencyPed.h rename to src/liberty/peds/EmergencyPed.h diff --git a/src/peds/Gangs.cpp b/src/liberty/peds/Gangs.cpp similarity index 100% rename from src/peds/Gangs.cpp rename to src/liberty/peds/Gangs.cpp diff --git a/src/peds/Gangs.h b/src/liberty/peds/Gangs.h similarity index 100% rename from src/peds/Gangs.h rename to src/liberty/peds/Gangs.h diff --git a/src/peds/Ped.cpp b/src/liberty/peds/Ped.cpp similarity index 100% rename from src/peds/Ped.cpp rename to src/liberty/peds/Ped.cpp diff --git a/src/peds/Ped.h b/src/liberty/peds/Ped.h similarity index 100% rename from src/peds/Ped.h rename to src/liberty/peds/Ped.h diff --git a/src/peds/PedAI.cpp b/src/liberty/peds/PedAI.cpp similarity index 99% rename from src/peds/PedAI.cpp rename to src/liberty/peds/PedAI.cpp index b6e575c9..0fe9542f 100644 --- a/src/peds/PedAI.cpp +++ b/src/liberty/peds/PedAI.cpp @@ -4249,7 +4249,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void) if (!seq->HasTranslation()) vecPedDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - vecPedDraggedOutCarAnimOffset = seq->GetTranslation(seq->numFrames - 1); + vecPedDraggedOutCarAnimOffset = seq->GetEndTranslation(); } } @@ -4260,7 +4260,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void) if (!seq->HasTranslation()) vecPedCarDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - vecPedCarDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1); + vecPedCarDoorAnimOffset = seq->GetEndTranslation(); } } @@ -4271,7 +4271,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void) if (!seq->HasTranslation()) vecPedCarDoorLoAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - vecPedCarDoorLoAnimOffset = seq->GetTranslation(seq->numFrames - 1); + vecPedCarDoorLoAnimOffset = seq->GetEndTranslation(); } } @@ -4282,7 +4282,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void) if (!seq->HasTranslation()) vecPedQuickDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - vecPedQuickDraggedOutCarAnimOffset = seq->GetTranslation(seq->numFrames - 1); + vecPedQuickDraggedOutCarAnimOffset = seq->GetEndTranslation(); } } @@ -4293,7 +4293,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void) if (!seq->HasTranslation()) vecPedVanRearDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - vecPedVanRearDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1); + vecPedVanRearDoorAnimOffset = seq->GetEndTranslation(); } } @@ -4304,7 +4304,7 @@ CPed::SetAnimOffsetForEnterOrExitVehicle(void) if (!seq->HasTranslation()) vecPedTrainDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); else { - vecPedTrainDoorAnimOffset = seq->GetTranslation(seq->numFrames - 1); + vecPedTrainDoorAnimOffset = seq->GetEndTranslation(); } } } diff --git a/src/peds/PedChat.cpp b/src/liberty/peds/PedChat.cpp similarity index 100% rename from src/peds/PedChat.cpp rename to src/liberty/peds/PedChat.cpp diff --git a/src/peds/PedDebug.cpp b/src/liberty/peds/PedDebug.cpp similarity index 100% rename from src/peds/PedDebug.cpp rename to src/liberty/peds/PedDebug.cpp diff --git a/src/peds/PedFight.cpp b/src/liberty/peds/PedFight.cpp similarity index 100% rename from src/peds/PedFight.cpp rename to src/liberty/peds/PedFight.cpp diff --git a/src/peds/PedIK.cpp b/src/liberty/peds/PedIK.cpp similarity index 100% rename from src/peds/PedIK.cpp rename to src/liberty/peds/PedIK.cpp diff --git a/src/peds/PedIK.h b/src/liberty/peds/PedIK.h similarity index 100% rename from src/peds/PedIK.h rename to src/liberty/peds/PedIK.h diff --git a/src/peds/PedPlacement.cpp b/src/liberty/peds/PedPlacement.cpp similarity index 100% rename from src/peds/PedPlacement.cpp rename to src/liberty/peds/PedPlacement.cpp diff --git a/src/peds/PedPlacement.h b/src/liberty/peds/PedPlacement.h similarity index 100% rename from src/peds/PedPlacement.h rename to src/liberty/peds/PedPlacement.h diff --git a/src/peds/PedRoutes.cpp b/src/liberty/peds/PedRoutes.cpp similarity index 100% rename from src/peds/PedRoutes.cpp rename to src/liberty/peds/PedRoutes.cpp diff --git a/src/peds/PedRoutes.h b/src/liberty/peds/PedRoutes.h similarity index 100% rename from src/peds/PedRoutes.h rename to src/liberty/peds/PedRoutes.h diff --git a/src/peds/PedType.cpp b/src/liberty/peds/PedType.cpp similarity index 100% rename from src/peds/PedType.cpp rename to src/liberty/peds/PedType.cpp diff --git a/src/peds/PedType.h b/src/liberty/peds/PedType.h similarity index 100% rename from src/peds/PedType.h rename to src/liberty/peds/PedType.h diff --git a/src/peds/PlayerPed.cpp b/src/liberty/peds/PlayerPed.cpp similarity index 100% rename from src/peds/PlayerPed.cpp rename to src/liberty/peds/PlayerPed.cpp diff --git a/src/peds/PlayerPed.h b/src/liberty/peds/PlayerPed.h similarity index 100% rename from src/peds/PlayerPed.h rename to src/liberty/peds/PlayerPed.h diff --git a/src/peds/Population.cpp b/src/liberty/peds/Population.cpp similarity index 100% rename from src/peds/Population.cpp rename to src/liberty/peds/Population.cpp diff --git a/src/peds/Population.h b/src/liberty/peds/Population.h similarity index 100% rename from src/peds/Population.h rename to src/liberty/peds/Population.h diff --git a/src/renderer/2dEffect.h b/src/liberty/renderer/2dEffect.h similarity index 100% rename from src/renderer/2dEffect.h rename to src/liberty/renderer/2dEffect.h diff --git a/src/renderer/Antennas.cpp b/src/liberty/renderer/Antennas.cpp similarity index 100% rename from src/renderer/Antennas.cpp rename to src/liberty/renderer/Antennas.cpp diff --git a/src/renderer/Antennas.h b/src/liberty/renderer/Antennas.h similarity index 100% rename from src/renderer/Antennas.h rename to src/liberty/renderer/Antennas.h diff --git a/src/renderer/Clouds.cpp b/src/liberty/renderer/Clouds.cpp similarity index 99% rename from src/renderer/Clouds.cpp rename to src/liberty/renderer/Clouds.cpp index ac0a2ce0..f91999db 100644 --- a/src/renderer/Clouds.cpp +++ b/src/liberty/renderer/Clouds.cpp @@ -261,7 +261,7 @@ CClouds::Render(void) 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, + (uint16)IndividualRotation/65536.0f * 6.28f + ms_cameraRoll, //was: 65336 fluffyalpha); bCloudOnScreen[i] = true; }else diff --git a/src/renderer/Clouds.h b/src/liberty/renderer/Clouds.h similarity index 100% rename from src/renderer/Clouds.h rename to src/liberty/renderer/Clouds.h diff --git a/src/renderer/Console.cpp b/src/liberty/renderer/Console.cpp similarity index 100% rename from src/renderer/Console.cpp rename to src/liberty/renderer/Console.cpp diff --git a/src/renderer/Console.h b/src/liberty/renderer/Console.h similarity index 100% rename from src/renderer/Console.h rename to src/liberty/renderer/Console.h diff --git a/src/renderer/Coronas.cpp b/src/liberty/renderer/Coronas.cpp similarity index 100% rename from src/renderer/Coronas.cpp rename to src/liberty/renderer/Coronas.cpp diff --git a/src/renderer/Coronas.h b/src/liberty/renderer/Coronas.h similarity index 100% rename from src/renderer/Coronas.h rename to src/liberty/renderer/Coronas.h diff --git a/src/renderer/Credits.cpp b/src/liberty/renderer/Credits.cpp similarity index 100% rename from src/renderer/Credits.cpp rename to src/liberty/renderer/Credits.cpp diff --git a/src/renderer/Credits.h b/src/liberty/renderer/Credits.h similarity index 100% rename from src/renderer/Credits.h rename to src/liberty/renderer/Credits.h diff --git a/src/renderer/Draw.cpp b/src/liberty/renderer/Draw.cpp similarity index 100% rename from src/renderer/Draw.cpp rename to src/liberty/renderer/Draw.cpp diff --git a/src/renderer/Draw.h b/src/liberty/renderer/Draw.h similarity index 100% rename from src/renderer/Draw.h rename to src/liberty/renderer/Draw.h diff --git a/src/renderer/Fluff.cpp b/src/liberty/renderer/Fluff.cpp similarity index 100% rename from src/renderer/Fluff.cpp rename to src/liberty/renderer/Fluff.cpp diff --git a/src/renderer/Fluff.h b/src/liberty/renderer/Fluff.h similarity index 100% rename from src/renderer/Fluff.h rename to src/liberty/renderer/Fluff.h diff --git a/src/renderer/Font.cpp b/src/liberty/renderer/Font.cpp similarity index 100% rename from src/renderer/Font.cpp rename to src/liberty/renderer/Font.cpp diff --git a/src/renderer/Font.h b/src/liberty/renderer/Font.h similarity index 100% rename from src/renderer/Font.h rename to src/liberty/renderer/Font.h diff --git a/src/renderer/Glass.cpp b/src/liberty/renderer/Glass.cpp similarity index 100% rename from src/renderer/Glass.cpp rename to src/liberty/renderer/Glass.cpp diff --git a/src/renderer/Glass.h b/src/liberty/renderer/Glass.h similarity index 100% rename from src/renderer/Glass.h rename to src/liberty/renderer/Glass.h diff --git a/src/renderer/Hud.cpp b/src/liberty/renderer/Hud.cpp similarity index 100% rename from src/renderer/Hud.cpp rename to src/liberty/renderer/Hud.cpp diff --git a/src/renderer/Hud.h b/src/liberty/renderer/Hud.h similarity index 100% rename from src/renderer/Hud.h rename to src/liberty/renderer/Hud.h diff --git a/src/renderer/Instance.cpp b/src/liberty/renderer/Instance.cpp similarity index 100% rename from src/renderer/Instance.cpp rename to src/liberty/renderer/Instance.cpp diff --git a/src/renderer/Instance.h b/src/liberty/renderer/Instance.h similarity index 100% rename from src/renderer/Instance.h rename to src/liberty/renderer/Instance.h diff --git a/src/renderer/Lines.cpp b/src/liberty/renderer/Lines.cpp similarity index 100% rename from src/renderer/Lines.cpp rename to src/liberty/renderer/Lines.cpp diff --git a/src/renderer/Lines.h b/src/liberty/renderer/Lines.h similarity index 100% rename from src/renderer/Lines.h rename to src/liberty/renderer/Lines.h diff --git a/src/renderer/MBlur.cpp b/src/liberty/renderer/MBlur.cpp similarity index 100% rename from src/renderer/MBlur.cpp rename to src/liberty/renderer/MBlur.cpp diff --git a/src/renderer/MBlur.h b/src/liberty/renderer/MBlur.h similarity index 100% rename from src/renderer/MBlur.h rename to src/liberty/renderer/MBlur.h diff --git a/src/renderer/Particle.cpp b/src/liberty/renderer/Particle.cpp similarity index 100% rename from src/renderer/Particle.cpp rename to src/liberty/renderer/Particle.cpp diff --git a/src/renderer/Particle.h b/src/liberty/renderer/Particle.h similarity index 100% rename from src/renderer/Particle.h rename to src/liberty/renderer/Particle.h diff --git a/src/renderer/ParticleMgr.cpp b/src/liberty/renderer/ParticleMgr.cpp similarity index 100% rename from src/renderer/ParticleMgr.cpp rename to src/liberty/renderer/ParticleMgr.cpp diff --git a/src/renderer/ParticleMgr.h b/src/liberty/renderer/ParticleMgr.h similarity index 100% rename from src/renderer/ParticleMgr.h rename to src/liberty/renderer/ParticleMgr.h diff --git a/src/renderer/ParticleType.h b/src/liberty/renderer/ParticleType.h similarity index 100% rename from src/renderer/ParticleType.h rename to src/liberty/renderer/ParticleType.h diff --git a/src/renderer/PlayerSkin.cpp b/src/liberty/renderer/PlayerSkin.cpp similarity index 100% rename from src/renderer/PlayerSkin.cpp rename to src/liberty/renderer/PlayerSkin.cpp diff --git a/src/renderer/PlayerSkin.h b/src/liberty/renderer/PlayerSkin.h similarity index 100% rename from src/renderer/PlayerSkin.h rename to src/liberty/renderer/PlayerSkin.h diff --git a/src/renderer/PointLights.cpp b/src/liberty/renderer/PointLights.cpp similarity index 100% rename from src/renderer/PointLights.cpp rename to src/liberty/renderer/PointLights.cpp diff --git a/src/renderer/PointLights.h b/src/liberty/renderer/PointLights.h similarity index 100% rename from src/renderer/PointLights.h rename to src/liberty/renderer/PointLights.h diff --git a/src/renderer/RenderBuffer.cpp b/src/liberty/renderer/RenderBuffer.cpp similarity index 100% rename from src/renderer/RenderBuffer.cpp rename to src/liberty/renderer/RenderBuffer.cpp diff --git a/src/renderer/RenderBuffer.h b/src/liberty/renderer/RenderBuffer.h similarity index 100% rename from src/renderer/RenderBuffer.h rename to src/liberty/renderer/RenderBuffer.h diff --git a/src/renderer/Renderer.cpp b/src/liberty/renderer/Renderer.cpp similarity index 100% rename from src/renderer/Renderer.cpp rename to src/liberty/renderer/Renderer.cpp diff --git a/src/renderer/Renderer.h b/src/liberty/renderer/Renderer.h similarity index 100% rename from src/renderer/Renderer.h rename to src/liberty/renderer/Renderer.h diff --git a/src/renderer/Rubbish.cpp b/src/liberty/renderer/Rubbish.cpp similarity index 100% rename from src/renderer/Rubbish.cpp rename to src/liberty/renderer/Rubbish.cpp diff --git a/src/renderer/Rubbish.h b/src/liberty/renderer/Rubbish.h similarity index 100% rename from src/renderer/Rubbish.h rename to src/liberty/renderer/Rubbish.h diff --git a/src/renderer/Shadows.cpp b/src/liberty/renderer/Shadows.cpp similarity index 100% rename from src/renderer/Shadows.cpp rename to src/liberty/renderer/Shadows.cpp diff --git a/src/renderer/Shadows.h b/src/liberty/renderer/Shadows.h similarity index 100% rename from src/renderer/Shadows.h rename to src/liberty/renderer/Shadows.h diff --git a/src/renderer/Skidmarks.cpp b/src/liberty/renderer/Skidmarks.cpp similarity index 100% rename from src/renderer/Skidmarks.cpp rename to src/liberty/renderer/Skidmarks.cpp diff --git a/src/renderer/Skidmarks.h b/src/liberty/renderer/Skidmarks.h similarity index 100% rename from src/renderer/Skidmarks.h rename to src/liberty/renderer/Skidmarks.h diff --git a/src/renderer/SpecialFX.cpp b/src/liberty/renderer/SpecialFX.cpp similarity index 100% rename from src/renderer/SpecialFX.cpp rename to src/liberty/renderer/SpecialFX.cpp diff --git a/src/renderer/SpecialFX.h b/src/liberty/renderer/SpecialFX.h similarity index 100% rename from src/renderer/SpecialFX.h rename to src/liberty/renderer/SpecialFX.h diff --git a/src/renderer/Sprite.cpp b/src/liberty/renderer/Sprite.cpp similarity index 100% rename from src/renderer/Sprite.cpp rename to src/liberty/renderer/Sprite.cpp diff --git a/src/renderer/Sprite.h b/src/liberty/renderer/Sprite.h similarity index 100% rename from src/renderer/Sprite.h rename to src/liberty/renderer/Sprite.h diff --git a/src/renderer/Sprite2d.cpp b/src/liberty/renderer/Sprite2d.cpp similarity index 98% rename from src/renderer/Sprite2d.cpp rename to src/liberty/renderer/Sprite2d.cpp index 5214ef4e..c0976fdf 100644 --- a/src/renderer/Sprite2d.cpp +++ b/src/liberty/renderer/Sprite2d.cpp @@ -177,8 +177,8 @@ CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const C z = RwCameraGetFarClipPlane(Scene.camera); }else{ screenz = RwIm2DGetNearScreenZ(); - z = 1.0f/RecipNearClip; RecipNearClip *= 1.1f; + z = 1.0f/RecipNearClip; } recipz = 1.0f/z; float offset = 1.0f/1024.0f; @@ -231,9 +231,9 @@ CSprite2d::SetVertices(const CRect &r, const CRGBA &c0, const CRGBA &c1, const C float screenz, z, recipz; screenz = RwIm2DGetNearScreenZ(); + RecipNearClip *= 1.1f; z = 1.0f/RecipNearClip; recipz = 1.0f/z; - RecipNearClip *= 1.1f; // This is what we draw: // 0---1 @@ -286,6 +286,7 @@ CSprite2d::SetVertices(float x1, float y1, float x2, float y2, float x3, float y screenz = RwIm2DGetNearScreenZ(); recipz = RecipNearClip; RecipNearClip *= 1.1f; + z = 1/RecipNearClip; RwIm2DVertexSetScreenX(&maVertices[0], x3); RwIm2DVertexSetScreenY(&maVertices[0], y3); @@ -333,7 +334,7 @@ CSprite2d::SetVertices(int n, float *positions, float *uvs, const CRGBA &col) screenz = RwIm2DGetNearScreenZ(); recipz = RecipNearClip; RecipNearClip *= 1.1f; - z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + z = 1/RecipNearClip; for(i = 0; i < n; i++){ @@ -357,7 +358,7 @@ CSprite2d::SetMaskVertices(int n, float *positions) screenz = RwIm2DGetNearScreenZ(); recipz = RecipNearClip; RecipNearClip *= 1.1f; - z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + z = 1/RecipNearClip; for(i = 0; i < n; i++){ RwIm2DVertexSetScreenX(&maVertices[i], positions[i*2 + 0]); @@ -382,7 +383,7 @@ CSprite2d::SetVertices(RwIm2DVertex *verts, const CRect &r, const CRGBA &c0, con screenz = RwIm2DGetNearScreenZ(); recipz = RecipNearClip; RecipNearClip *= 1.1f; - z = RwCameraGetNearClipPlane(Scene.camera); // not done by game + z = 1/RecipNearClip; RwIm2DVertexSetScreenX(&verts[0], r.left); RwIm2DVertexSetScreenY(&verts[0], r.top); diff --git a/src/renderer/Sprite2d.h b/src/liberty/renderer/Sprite2d.h similarity index 100% rename from src/renderer/Sprite2d.h rename to src/liberty/renderer/Sprite2d.h diff --git a/src/renderer/TexList.cpp b/src/liberty/renderer/TexList.cpp similarity index 100% rename from src/renderer/TexList.cpp rename to src/liberty/renderer/TexList.cpp diff --git a/src/renderer/TexList.h b/src/liberty/renderer/TexList.h similarity index 100% rename from src/renderer/TexList.h rename to src/liberty/renderer/TexList.h diff --git a/src/renderer/Timecycle.cpp b/src/liberty/renderer/Timecycle.cpp similarity index 100% rename from src/renderer/Timecycle.cpp rename to src/liberty/renderer/Timecycle.cpp diff --git a/src/renderer/Timecycle.h b/src/liberty/renderer/Timecycle.h similarity index 100% rename from src/renderer/Timecycle.h rename to src/liberty/renderer/Timecycle.h diff --git a/src/renderer/WaterCannon.cpp b/src/liberty/renderer/WaterCannon.cpp similarity index 100% rename from src/renderer/WaterCannon.cpp rename to src/liberty/renderer/WaterCannon.cpp diff --git a/src/renderer/WaterCannon.h b/src/liberty/renderer/WaterCannon.h similarity index 100% rename from src/renderer/WaterCannon.h rename to src/liberty/renderer/WaterCannon.h diff --git a/src/renderer/WaterLevel.cpp b/src/liberty/renderer/WaterLevel.cpp similarity index 99% rename from src/renderer/WaterLevel.cpp rename to src/liberty/renderer/WaterLevel.cpp index 37f38b9a..945abc2c 100644 --- a/src/renderer/WaterLevel.cpp +++ b/src/liberty/renderer/WaterLevel.cpp @@ -1479,7 +1479,8 @@ CWaterLevel::AllocateBoatWakeArray() apGeomArray[geom] = RpGeometryCreate(9*9, 8*8*2, rpGEOMETRYTRISTRIP | rpGEOMETRYPRELIT | rpGEOMETRYMODULATEMATERIALCOLOR - | rpGEOMETRYTEXTURED); + | rpGEOMETRYTEXTURED + | rw::Geometry::HAS_TRIANGLES/* RW_DC specific */); ASSERT(apGeomArray[geom] != nil); RpTriangle *geomTriangles = RpGeometryGetTriangles(apGeomArray[geom]); diff --git a/src/renderer/WaterLevel.h b/src/liberty/renderer/WaterLevel.h similarity index 100% rename from src/renderer/WaterLevel.h rename to src/liberty/renderer/WaterLevel.h diff --git a/src/renderer/Weather.cpp b/src/liberty/renderer/Weather.cpp similarity index 100% rename from src/renderer/Weather.cpp rename to src/liberty/renderer/Weather.cpp diff --git a/src/renderer/Weather.h b/src/liberty/renderer/Weather.h similarity index 100% rename from src/renderer/Weather.h rename to src/liberty/renderer/Weather.h diff --git a/src/rw/ClumpRead.cpp b/src/liberty/rw/ClumpRead.cpp similarity index 100% rename from src/rw/ClumpRead.cpp rename to src/liberty/rw/ClumpRead.cpp diff --git a/src/rw/Lights.cpp b/src/liberty/rw/Lights.cpp similarity index 100% rename from src/rw/Lights.cpp rename to src/liberty/rw/Lights.cpp diff --git a/src/rw/Lights.h b/src/liberty/rw/Lights.h similarity index 100% rename from src/rw/Lights.h rename to src/liberty/rw/Lights.h diff --git a/src/rw/MemoryHeap.cpp b/src/liberty/rw/MemoryHeap.cpp similarity index 100% rename from src/rw/MemoryHeap.cpp rename to src/liberty/rw/MemoryHeap.cpp diff --git a/src/rw/MemoryHeap.h b/src/liberty/rw/MemoryHeap.h similarity index 100% rename from src/rw/MemoryHeap.h rename to src/liberty/rw/MemoryHeap.h diff --git a/src/rw/MemoryMgr.cpp b/src/liberty/rw/MemoryMgr.cpp similarity index 100% rename from src/rw/MemoryMgr.cpp rename to src/liberty/rw/MemoryMgr.cpp diff --git a/src/rw/MemoryMgr.h b/src/liberty/rw/MemoryMgr.h similarity index 100% rename from src/rw/MemoryMgr.h rename to src/liberty/rw/MemoryMgr.h diff --git a/src/rw/NodeName.cpp b/src/liberty/rw/NodeName.cpp similarity index 100% rename from src/rw/NodeName.cpp rename to src/liberty/rw/NodeName.cpp diff --git a/src/rw/NodeName.h b/src/liberty/rw/NodeName.h similarity index 100% rename from src/rw/NodeName.h rename to src/liberty/rw/NodeName.h diff --git a/src/rw/RwHelper.cpp b/src/liberty/rw/RwHelper.cpp similarity index 100% rename from src/rw/RwHelper.cpp rename to src/liberty/rw/RwHelper.cpp diff --git a/src/rw/RwHelper.h b/src/liberty/rw/RwHelper.h similarity index 100% rename from src/rw/RwHelper.h rename to src/liberty/rw/RwHelper.h diff --git a/src/rw/RwMatFX.cpp b/src/liberty/rw/RwMatFX.cpp similarity index 100% rename from src/rw/RwMatFX.cpp rename to src/liberty/rw/RwMatFX.cpp diff --git a/src/rw/RwPS2AlphaTest.cpp b/src/liberty/rw/RwPS2AlphaTest.cpp similarity index 100% rename from src/rw/RwPS2AlphaTest.cpp rename to src/liberty/rw/RwPS2AlphaTest.cpp diff --git a/src/rw/TexRead.cpp b/src/liberty/rw/TexRead.cpp similarity index 100% rename from src/rw/TexRead.cpp rename to src/liberty/rw/TexRead.cpp diff --git a/src/rw/TexturePools.cpp b/src/liberty/rw/TexturePools.cpp similarity index 100% rename from src/rw/TexturePools.cpp rename to src/liberty/rw/TexturePools.cpp diff --git a/src/rw/TexturePools.h b/src/liberty/rw/TexturePools.h similarity index 100% rename from src/rw/TexturePools.h rename to src/liberty/rw/TexturePools.h diff --git a/src/rw/TxdStore.cpp b/src/liberty/rw/TxdStore.cpp similarity index 100% rename from src/rw/TxdStore.cpp rename to src/liberty/rw/TxdStore.cpp diff --git a/src/rw/TxdStore.h b/src/liberty/rw/TxdStore.h similarity index 100% rename from src/rw/TxdStore.h rename to src/liberty/rw/TxdStore.h diff --git a/src/rw/VisibilityPlugins.cpp b/src/liberty/rw/VisibilityPlugins.cpp similarity index 100% rename from src/rw/VisibilityPlugins.cpp rename to src/liberty/rw/VisibilityPlugins.cpp diff --git a/src/rw/VisibilityPlugins.h b/src/liberty/rw/VisibilityPlugins.h similarity index 100% rename from src/rw/VisibilityPlugins.h rename to src/liberty/rw/VisibilityPlugins.h diff --git a/src/save/Date.cpp b/src/liberty/save/Date.cpp similarity index 100% rename from src/save/Date.cpp rename to src/liberty/save/Date.cpp diff --git a/src/save/Date.h b/src/liberty/save/Date.h similarity index 100% rename from src/save/Date.h rename to src/liberty/save/Date.h diff --git a/src/save/GenericGameStorage.cpp b/src/liberty/save/GenericGameStorage.cpp similarity index 96% rename from src/save/GenericGameStorage.cpp rename to src/liberty/save/GenericGameStorage.cpp index 84238082..e7770058 100644 --- a/src/save/GenericGameStorage.cpp +++ b/src/liberty/save/GenericGameStorage.cpp @@ -38,7 +38,7 @@ #include "World.h" #include "Zones.h" -#include "../vmu/vmu.h" +#include "vmu/vmu.h" #define BLOCK_COUNT 20 #define SIZE_OF_SIMPLEVARS 0xBC @@ -74,6 +74,10 @@ uint32 TimeToStayFadedBeforeFadeOut = 1750; do {\ size = C_PcSave::PcClassLoadRoutine(file, work_buff); \ if (!size) {\ + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; \ + if (!CloseFile(file)) { \ + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; \ + } \ return false; \ } \ buf = work_buff;\ @@ -95,8 +99,12 @@ do {\ MakeSpaceForSizeInBufferPointer(presize, buf, postsize);\ save_func(buf, &size);\ CopySizeAndPreparePointer(presize, buf, postsize, reserved, size);\ - if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff))\ + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff)) { \ + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; \ + if (!CloseFile(file)) \ + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; \ return false;\ + } \ totalSize += buf - work_buff;\ } while (0) @@ -195,8 +203,12 @@ GenericSave(int file) postsize = buf; CTheScripts::SaveAllScripts(buf, &size); CopySizeAndPreparePointer(presize, buf, postsize, reserved, size); - if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff)) + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; return false; + } totalSize = buf - work_buff; @@ -232,15 +244,19 @@ GenericSave(int file) if (size > sizeof(work_buff)) size = sizeof(work_buff); if (size > 4) { - if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, size)) + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, size)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; return false; + } totalSize += size; } } // Write checksum and close - CFileMgr::Write(file, (const char *) &CheckSum, sizeof(CheckSum)); - if (CFileMgr::GetErrorReadWrite(file)) { + bool err = CFileMgr::Write(file, (const char *) &CheckSum, sizeof(CheckSum)) != sizeof(CheckSum); + if (err || CFileMgr::GetErrorReadWrite(file)) { PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; if (!CloseFile(file)) PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; @@ -269,9 +285,18 @@ GenericLoad() CDate dummy; // unused CPad::ResetCheats(); file = CFileMgr::OpenFile(LoadFileName, "rb"); - assert(file != 0); + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } size = C_PcSave::PcClassLoadRoutine(file, work_buff); - assert(size != 0); + if (!size) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; + if (!CloseFile(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + } + return false; + } buf = (work_buff + 0x40); ReadDataFromBufferPointer(buf, saveSize); #ifdef MISSION_REPLAY // a hack to keep compatibility but get new data from save @@ -563,10 +588,9 @@ RestoreForStartLoad() } uint32_t size = C_PcSave::PcClassLoadRoutine(file, work_buff); - assert(size != 0); uint8 *buf = work_buff; - if (CFileMgr::GetErrorReadWrite(file)) { + if (size == 0 || CFileMgr::GetErrorReadWrite(file)) { PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; if (!CloseFile(file)) PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; diff --git a/src/save/GenericGameStorage.h b/src/liberty/save/GenericGameStorage.h similarity index 100% rename from src/save/GenericGameStorage.h rename to src/liberty/save/GenericGameStorage.h diff --git a/src/save/MemoryCard.cpp b/src/liberty/save/MemoryCard.cpp similarity index 100% rename from src/save/MemoryCard.cpp rename to src/liberty/save/MemoryCard.cpp diff --git a/src/save/MemoryCard.h b/src/liberty/save/MemoryCard.h similarity index 100% rename from src/save/MemoryCard.h rename to src/liberty/save/MemoryCard.h diff --git a/src/save/PCSave.cpp b/src/liberty/save/PCSave.cpp similarity index 80% rename from src/save/PCSave.cpp rename to src/liberty/save/PCSave.cpp index e8ff416a..2a16fd7d 100644 --- a/src/save/PCSave.cpp +++ b/src/liberty/save/PCSave.cpp @@ -15,7 +15,7 @@ #include "minilzo.h" #include "main.h" -#include "../vmu/vmu.h" +#include "vmu/vmu.h" const char* _psGetUserFilesFolder(); @@ -61,8 +61,10 @@ C_PcSave::SaveSlot(int32 slot) #endif DoGameSpecificStuffBeforeSave(); if (GenericSave(file)) { - if (!!CFileMgr::CloseFile(file)) + if (!!CFileMgr::CloseFile(file)) { nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; + return false; + } return true; } @@ -74,23 +76,26 @@ C_PcSave::SaveSlot(int32 slot) uint32_t C_PcSave::PcClassLoadRoutine(int32 file, uint8 *data) { uint32 size; - CFileMgr::Read(file, (char*)&size, sizeof(size)); + bool err = CFileMgr::Read(file, (char*)&size, sizeof(size)) != sizeof(size); + if (err) { + return 0; + } assert(data == work_buff); if (!(size & 0x80000000)) { assert(align4bytes(size) == size); - CFileMgr::Read(file, (char*)data, align4bytes(size)); - if (CFileMgr::GetErrorReadWrite(file)) { + err = CFileMgr::Read(file, (char*)data, align4bytes(size)) != align4bytes(size); + if (err || CFileMgr::GetErrorReadWrite(file)) { return 0; } return size; } else { size &= ~0x80000000; uint8* compressed = (uint8*)malloc(size); - CFileMgr::Read(file, (const char*)compressed, size); - if (CFileMgr::GetErrorReadWrite(file)) { + err = CFileMgr::Read(file, (const char*)compressed, size) != size; + if (err || CFileMgr::GetErrorReadWrite(file)) { free(compressed); return 0; } @@ -120,26 +125,36 @@ C_PcSave::PcClassSaveRoutine(int32 file, uint8 *data, uint32 size) if (crv == LZO_E_OK) { uint32_t compressed_size32 = compressed_size | 0x80000000; - CFileMgr::Write(file, (const char*)&compressed_size32, sizeof(compressed_size32)); - if (CFileMgr::GetErrorReadWrite(file)) { + bool err = CFileMgr::Write(file, (const char*)&compressed_size32, sizeof(compressed_size32)) != sizeof(compressed_size32); + if (err || CFileMgr::GetErrorReadWrite(file)) { free(compressed); nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); return false; } - CFileMgr::Write(file, (const char*)compressed, compressed_size); + err = CFileMgr::Write(file, (const char*)compressed, compressed_size) != compressed_size; free(compressed); - } else if (crv == LZO_E_NOT_COMPRESSIBLE) { - free(compressed); - uint32_t compressed_size32 = size; - CFileMgr::Write(file, (const char*)&compressed_size32, sizeof(compressed_size32)); - if (CFileMgr::GetErrorReadWrite(file)) { + if (err || CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + } else if (crv == LZO_E_NOT_COMPRESSIBLE) { + free(compressed); + uint32_t compressed_size32 = size; + bool err = CFileMgr::Write(file, (const char*)&compressed_size32, sizeof(compressed_size32)) != sizeof(compressed_size32); + if (err || CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + err = CFileMgr::Write(file, (const char*)data, align4bytes(size)) != align4bytes(size); + if (err || CFileMgr::GetErrorReadWrite(file)) { nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); return false; } - CFileMgr::Write(file, (const char*)data, align4bytes(size)); } else { free(compressed); return false; diff --git a/src/save/PCSave.h b/src/liberty/save/PCSave.h similarity index 100% rename from src/save/PCSave.h rename to src/liberty/save/PCSave.h diff --git a/src/save/SaveBuf.h b/src/liberty/save/SaveBuf.h similarity index 100% rename from src/save/SaveBuf.h rename to src/liberty/save/SaveBuf.h diff --git a/src/skel/crossplatform.cpp b/src/liberty/skel/crossplatform.cpp similarity index 98% rename from src/skel/crossplatform.cpp rename to src/liberty/skel/crossplatform.cpp index 564a7e5d..5d56ba4a 100644 --- a/src/skel/crossplatform.cpp +++ b/src/liberty/skel/crossplatform.cpp @@ -53,9 +53,13 @@ HANDLE FindFirstFile(const char* pathname, WIN32_FIND_DATA* firstfile) { if (realFolder) free(realFolder); - HANDLE d; - if ((d = (HANDLE)opendir(firstfile->folder)) == NULL || !FindNextFile(d, firstfile)) - return NULL; + HANDLE d; + if ((d = (HANDLE)opendir(firstfile->folder)) == NULL || !FindNextFile(d, firstfile)) { + if (d != NULL) { + closedir((DIR*)d); + } + return NULL; + } return d; } diff --git a/src/skel/crossplatform.h b/src/liberty/skel/crossplatform.h similarity index 100% rename from src/skel/crossplatform.h rename to src/liberty/skel/crossplatform.h diff --git a/src/skel/dc/dc.cpp b/src/liberty/skel/dc/dc.cpp similarity index 97% rename from src/skel/dc/dc.cpp rename to src/liberty/skel/dc/dc.cpp index 4f2e8ccb..d3e3bde9 100644 --- a/src/skel/dc/dc.cpp +++ b/src/liberty/skel/dc/dc.cpp @@ -1,6 +1,6 @@ #if defined RW_DC -#include "../vmu/vmu.h" +#include "vmu/vmu.h" #include #include #include @@ -8,7 +8,7 @@ #if !defined(DC_SIM) #include KOS_INIT_FLAGS(INIT_IRQ | INIT_CONTROLLER | INIT_CDROM | INIT_VMU); -#include "../prof/profiler.h" +#include "prof/profiler.h" #endif #ifdef _WIN32 @@ -62,7 +62,7 @@ long _dwOperatingSystemVersion; #include "AnimViewer.h" #include "Font.h" #include "MemoryMgr.h" -#include "../../dreamcast/git-version.h" +#include "git-version.h" #include "dc.h" #include @@ -2028,65 +2028,10 @@ __attribute__((noinline)) void stacktrace() { #include #include -extern "C" { - extern const unsigned char _build_id_start[]; - extern const unsigned char _build_id_end[]; -} - -std::string getBuildId() -{ - // Pointer to the start of the .note.gnu.build-id section - const unsigned char *p = _build_id_start; - - // Parse the ELF note header - struct NoteHeader { - uint32_t n_namesz; - uint32_t n_descsz; - uint32_t n_type; - }; - - // Read header fields (be careful with endianness if needed) - const auto* note = reinterpret_cast(p); - - // Move p beyond the note header - p += sizeof(NoteHeader); - - // Skip the "name" field + alignment (e.g. "GNU\0") - // name is note->n_namesz bytes, then align up to 4 bytes - auto nameBytes = (note->n_namesz + 3u) & ~3u; - p += nameBytes; - - // Now p should point to the actual build-id bytes, which are note->n_descsz in length. - const unsigned char* buildId = p; - auto buildIdSize = note->n_descsz; - - // Convert it to a hex string - std::ostringstream oss; - oss << std::hex << std::setfill('0'); - for (uint32_t i = 0; i < buildIdSize; i++) { - oss << std::setw(2) << static_cast(buildId[i]); - } - - return oss.str(); -} -#else -std::string getBuildId() { - return "non-dreamcast-build"; -} #endif -const char* getSourceId() { - return GIT_VERSION; -} - -const char* getCIJobId() { - return CI_JOB_ID; -} - -static std::string executableTag = getBuildId().substr(0, 10) + ":" + getSourceId() + ":" + getCIJobId(); - const char* getExecutableTag() { - return executableTag.c_str(); + return GIT_VERSION ":" CI_JOB_ID; } int diff --git a/src/skel/dc/dc.h b/src/liberty/skel/dc/dc.h similarity index 100% rename from src/skel/dc/dc.h rename to src/liberty/skel/dc/dc.h diff --git a/src/skel/events.cpp b/src/liberty/skel/events.cpp similarity index 100% rename from src/skel/events.cpp rename to src/liberty/skel/events.cpp diff --git a/src/skel/events.h b/src/liberty/skel/events.h similarity index 100% rename from src/skel/events.h rename to src/liberty/skel/events.h diff --git a/src/skel/glfw/glfw.cpp b/src/liberty/skel/glfw/glfw.cpp similarity index 100% rename from src/skel/glfw/glfw.cpp rename to src/liberty/skel/glfw/glfw.cpp diff --git a/src/skel/platform.h b/src/liberty/skel/platform.h similarity index 100% rename from src/skel/platform.h rename to src/liberty/skel/platform.h diff --git a/src/skel/skeleton.cpp b/src/liberty/skel/skeleton.cpp similarity index 100% rename from src/skel/skeleton.cpp rename to src/liberty/skel/skeleton.cpp diff --git a/src/skel/skeleton.h b/src/liberty/skel/skeleton.h similarity index 100% rename from src/skel/skeleton.h rename to src/liberty/skel/skeleton.h diff --git a/src/skel/win/gta3.ico b/src/liberty/skel/win/gta3.ico similarity index 100% rename from src/skel/win/gta3.ico rename to src/liberty/skel/win/gta3.ico diff --git a/src/skel/win/resource.h b/src/liberty/skel/win/resource.h similarity index 100% rename from src/skel/win/resource.h rename to src/liberty/skel/win/resource.h diff --git a/src/skel/win/win.cpp b/src/liberty/skel/win/win.cpp similarity index 100% rename from src/skel/win/win.cpp rename to src/liberty/skel/win/win.cpp diff --git a/src/skel/win/win.h b/src/liberty/skel/win/win.h similarity index 100% rename from src/skel/win/win.h rename to src/liberty/skel/win/win.h diff --git a/src/skel/win/win.rc b/src/liberty/skel/win/win.rc similarity index 100% rename from src/skel/win/win.rc rename to src/liberty/skel/win/win.rc diff --git a/src/text/Messages.cpp b/src/liberty/text/Messages.cpp similarity index 100% rename from src/text/Messages.cpp rename to src/liberty/text/Messages.cpp diff --git a/src/text/Messages.h b/src/liberty/text/Messages.h similarity index 100% rename from src/text/Messages.h rename to src/liberty/text/Messages.h diff --git a/src/text/Pager.cpp b/src/liberty/text/Pager.cpp similarity index 100% rename from src/text/Pager.cpp rename to src/liberty/text/Pager.cpp diff --git a/src/text/Pager.h b/src/liberty/text/Pager.h similarity index 100% rename from src/text/Pager.h rename to src/liberty/text/Pager.h diff --git a/src/text/Text.cpp b/src/liberty/text/Text.cpp similarity index 100% rename from src/text/Text.cpp rename to src/liberty/text/Text.cpp diff --git a/src/text/Text.h b/src/liberty/text/Text.h similarity index 100% rename from src/text/Text.h rename to src/liberty/text/Text.h diff --git a/src/vehicles/Automobile.cpp b/src/liberty/vehicles/Automobile.cpp similarity index 100% rename from src/vehicles/Automobile.cpp rename to src/liberty/vehicles/Automobile.cpp diff --git a/src/vehicles/Automobile.h b/src/liberty/vehicles/Automobile.h similarity index 100% rename from src/vehicles/Automobile.h rename to src/liberty/vehicles/Automobile.h diff --git a/src/vehicles/Bike.h b/src/liberty/vehicles/Bike.h similarity index 100% rename from src/vehicles/Bike.h rename to src/liberty/vehicles/Bike.h diff --git a/src/vehicles/Boat.cpp b/src/liberty/vehicles/Boat.cpp similarity index 100% rename from src/vehicles/Boat.cpp rename to src/liberty/vehicles/Boat.cpp diff --git a/src/vehicles/Boat.h b/src/liberty/vehicles/Boat.h similarity index 100% rename from src/vehicles/Boat.h rename to src/liberty/vehicles/Boat.h diff --git a/src/vehicles/CarGen.cpp b/src/liberty/vehicles/CarGen.cpp similarity index 100% rename from src/vehicles/CarGen.cpp rename to src/liberty/vehicles/CarGen.cpp diff --git a/src/vehicles/CarGen.h b/src/liberty/vehicles/CarGen.h similarity index 100% rename from src/vehicles/CarGen.h rename to src/liberty/vehicles/CarGen.h diff --git a/src/vehicles/Cranes.cpp b/src/liberty/vehicles/Cranes.cpp similarity index 100% rename from src/vehicles/Cranes.cpp rename to src/liberty/vehicles/Cranes.cpp diff --git a/src/vehicles/Cranes.h b/src/liberty/vehicles/Cranes.h similarity index 100% rename from src/vehicles/Cranes.h rename to src/liberty/vehicles/Cranes.h diff --git a/src/vehicles/DamageManager.cpp b/src/liberty/vehicles/DamageManager.cpp similarity index 100% rename from src/vehicles/DamageManager.cpp rename to src/liberty/vehicles/DamageManager.cpp diff --git a/src/vehicles/DamageManager.h b/src/liberty/vehicles/DamageManager.h similarity index 100% rename from src/vehicles/DamageManager.h rename to src/liberty/vehicles/DamageManager.h diff --git a/src/vehicles/Door.cpp b/src/liberty/vehicles/Door.cpp similarity index 100% rename from src/vehicles/Door.cpp rename to src/liberty/vehicles/Door.cpp diff --git a/src/vehicles/Door.h b/src/liberty/vehicles/Door.h similarity index 100% rename from src/vehicles/Door.h rename to src/liberty/vehicles/Door.h diff --git a/src/vehicles/Floater.cpp b/src/liberty/vehicles/Floater.cpp similarity index 100% rename from src/vehicles/Floater.cpp rename to src/liberty/vehicles/Floater.cpp diff --git a/src/vehicles/Floater.h b/src/liberty/vehicles/Floater.h similarity index 100% rename from src/vehicles/Floater.h rename to src/liberty/vehicles/Floater.h diff --git a/src/vehicles/HandlingMgr.cpp b/src/liberty/vehicles/HandlingMgr.cpp similarity index 100% rename from src/vehicles/HandlingMgr.cpp rename to src/liberty/vehicles/HandlingMgr.cpp diff --git a/src/vehicles/HandlingMgr.h b/src/liberty/vehicles/HandlingMgr.h similarity index 100% rename from src/vehicles/HandlingMgr.h rename to src/liberty/vehicles/HandlingMgr.h diff --git a/src/vehicles/Heli.cpp b/src/liberty/vehicles/Heli.cpp similarity index 100% rename from src/vehicles/Heli.cpp rename to src/liberty/vehicles/Heli.cpp diff --git a/src/vehicles/Heli.h b/src/liberty/vehicles/Heli.h similarity index 100% rename from src/vehicles/Heli.h rename to src/liberty/vehicles/Heli.h diff --git a/src/vehicles/Plane.cpp b/src/liberty/vehicles/Plane.cpp similarity index 100% rename from src/vehicles/Plane.cpp rename to src/liberty/vehicles/Plane.cpp diff --git a/src/vehicles/Plane.h b/src/liberty/vehicles/Plane.h similarity index 100% rename from src/vehicles/Plane.h rename to src/liberty/vehicles/Plane.h diff --git a/src/vehicles/Train.cpp b/src/liberty/vehicles/Train.cpp similarity index 100% rename from src/vehicles/Train.cpp rename to src/liberty/vehicles/Train.cpp diff --git a/src/vehicles/Train.h b/src/liberty/vehicles/Train.h similarity index 100% rename from src/vehicles/Train.h rename to src/liberty/vehicles/Train.h diff --git a/src/vehicles/Transmission.cpp b/src/liberty/vehicles/Transmission.cpp similarity index 100% rename from src/vehicles/Transmission.cpp rename to src/liberty/vehicles/Transmission.cpp diff --git a/src/vehicles/Transmission.h b/src/liberty/vehicles/Transmission.h similarity index 100% rename from src/vehicles/Transmission.h rename to src/liberty/vehicles/Transmission.h diff --git a/src/vehicles/Vehicle.cpp b/src/liberty/vehicles/Vehicle.cpp similarity index 100% rename from src/vehicles/Vehicle.cpp rename to src/liberty/vehicles/Vehicle.cpp diff --git a/src/vehicles/Vehicle.h b/src/liberty/vehicles/Vehicle.h similarity index 100% rename from src/vehicles/Vehicle.h rename to src/liberty/vehicles/Vehicle.h diff --git a/src/weapons/BulletInfo.cpp b/src/liberty/weapons/BulletInfo.cpp similarity index 89% rename from src/weapons/BulletInfo.cpp rename to src/liberty/weapons/BulletInfo.cpp index bfe27e18..7b187d7c 100644 --- a/src/weapons/BulletInfo.cpp +++ b/src/liberty/weapons/BulletInfo.cpp @@ -126,6 +126,28 @@ void CBulletInfo::Update(void) } 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); + + 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--; @@ -136,31 +158,6 @@ void CBulletInfo::Update(void) 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; diff --git a/src/weapons/BulletInfo.h b/src/liberty/weapons/BulletInfo.h similarity index 100% rename from src/weapons/BulletInfo.h rename to src/liberty/weapons/BulletInfo.h diff --git a/src/weapons/Explosion.cpp b/src/liberty/weapons/Explosion.cpp similarity index 100% rename from src/weapons/Explosion.cpp rename to src/liberty/weapons/Explosion.cpp diff --git a/src/weapons/Explosion.h b/src/liberty/weapons/Explosion.h similarity index 100% rename from src/weapons/Explosion.h rename to src/liberty/weapons/Explosion.h diff --git a/src/weapons/ProjectileInfo.cpp b/src/liberty/weapons/ProjectileInfo.cpp similarity index 100% rename from src/weapons/ProjectileInfo.cpp rename to src/liberty/weapons/ProjectileInfo.cpp diff --git a/src/weapons/ProjectileInfo.h b/src/liberty/weapons/ProjectileInfo.h similarity index 100% rename from src/weapons/ProjectileInfo.h rename to src/liberty/weapons/ProjectileInfo.h diff --git a/src/weapons/ShotInfo.cpp b/src/liberty/weapons/ShotInfo.cpp similarity index 100% rename from src/weapons/ShotInfo.cpp rename to src/liberty/weapons/ShotInfo.cpp diff --git a/src/weapons/ShotInfo.h b/src/liberty/weapons/ShotInfo.h similarity index 100% rename from src/weapons/ShotInfo.h rename to src/liberty/weapons/ShotInfo.h diff --git a/src/weapons/Weapon.cpp b/src/liberty/weapons/Weapon.cpp similarity index 100% rename from src/weapons/Weapon.cpp rename to src/liberty/weapons/Weapon.cpp diff --git a/src/weapons/Weapon.h b/src/liberty/weapons/Weapon.h similarity index 100% rename from src/weapons/Weapon.h rename to src/liberty/weapons/Weapon.h diff --git a/src/weapons/WeaponEffects.cpp b/src/liberty/weapons/WeaponEffects.cpp similarity index 100% rename from src/weapons/WeaponEffects.cpp rename to src/liberty/weapons/WeaponEffects.cpp diff --git a/src/weapons/WeaponEffects.h b/src/liberty/weapons/WeaponEffects.h similarity index 100% rename from src/weapons/WeaponEffects.h rename to src/liberty/weapons/WeaponEffects.h diff --git a/src/weapons/WeaponInfo.cpp b/src/liberty/weapons/WeaponInfo.cpp similarity index 100% rename from src/weapons/WeaponInfo.cpp rename to src/liberty/weapons/WeaponInfo.cpp diff --git a/src/weapons/WeaponInfo.h b/src/liberty/weapons/WeaponInfo.h similarity index 100% rename from src/weapons/WeaponInfo.h rename to src/liberty/weapons/WeaponInfo.h diff --git a/src/weapons/WeaponType.h b/src/liberty/weapons/WeaponType.h similarity index 100% rename from src/weapons/WeaponType.h rename to src/liberty/weapons/WeaponType.h diff --git a/src/miami/CMakeLists.txt b/src/miami/CMakeLists.txt new file mode 100644 index 00000000..b605d45b --- /dev/null +++ b/src/miami/CMakeLists.txt @@ -0,0 +1,170 @@ +find_package(Threads REQUIRED) +set(THREADS_PREFER_PTHREAD_FLAG ON) + +file(GLOB_RECURSE ${PROJECT}_SOURCES "*.cpp" "*.h" "*.rc") + +function(header_directories RETURN_LIST) + file(GLOB_RECURSE ALL_SRCS *.h *.cpp *.c) + set(RELDIRS) + foreach(SRC ${ALL_SRCS}) + file(RELATIVE_PATH RELSRC "${CMAKE_CURRENT_SOURCE_DIR}" "${SRC}") + get_filename_component(RELDIR "${RELSRC}" DIRECTORY) + list(APPEND RELDIRS ${RELDIR}) + endforeach() + list(REMOVE_DUPLICATES RELDIRS) + set(${RETURN_LIST} ${RELDIRS} PARENT_SCOPE) +endfunction() + +header_directories(${PROJECT}_INCLUDES) + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/extras/GitSHA1.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/extras/GitSHA1.cpp" @ONLY) +list(APPEND ${PROJECT}_SOURCES "${CMAKE_CURRENT_BINARY_DIR}/extras/GitSHA1.cpp") + +add_executable(${EXECUTABLE} WIN32 + ${${PROJECT}_SOURCES} +) + +target_link_libraries(${EXECUTABLE} PRIVATE + librw::librw + Threads::Threads +) + +target_include_directories(${EXECUTABLE} + PRIVATE + $ + $ +) + +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() + +reVC_platform_target(${EXECUTABLE} INSTALL) diff --git a/src/miami/animation/AnimBlendAssocGroup.cpp b/src/miami/animation/AnimBlendAssocGroup.cpp new file mode 100644 index 00000000..941258f8 --- /dev/null +++ b/src/miami/animation/AnimBlendAssocGroup.cpp @@ -0,0 +1,187 @@ +#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 "ModelIndices.h" +#include "ModelInfo.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "AnimBlendAssocGroup.h" + +CAnimBlendAssocGroup::CAnimBlendAssocGroup(void) +{ + animBlock = nil; + assocList = nil; + numAssociations = 0; + firstAnimId = 0; + groupId = -1; +} + +CAnimBlendAssocGroup::~CAnimBlendAssocGroup(void) +{ + DestroyAssociations(); +} + +void +CAnimBlendAssocGroup::DestroyAssociations(void) +{ + if(assocList){ + delete[] assocList; + assocList = nil; + numAssociations = 0; + } +} + +CAnimBlendAssociation* +CAnimBlendAssocGroup::GetAnimation(uint32 id) +{ + return &assocList[id - firstAnimId]; +} + +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]; + debug("\n\nCan't find the fucking animation %s\n\n\n", name); + return nil; +} + + +CAnimBlendAssociation* +CAnimBlendAssocGroup::CopyAnimation(uint32 id) +{ + CAnimBlendAssociation *anim = GetAnimation(id); + if(anim == nil) + return nil; + + return new CAnimBlendAssociation(*anim); +} + +CAnimBlendAssociation* +CAnimBlendAssocGroup::CopyAnimation(const char *name) +{ + CAnimBlendAssociation *anim = GetAnimation(name); + if(anim == nil) + return nil; + + 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 && c1 != c2) + return false; + } +} + +CBaseModelInfo* +GetModelFromName(const char *name) +{ + int i; + CBaseModelInfo *mi; + char playername[32]; + + if(strncasecmp(name, "CSplay", 6) == 0 && + strncasecmp(CModelInfo::GetModelInfo(MI_PLAYER)->GetModelName(), "ig", 2) == 0){ + strcpy(playername, CModelInfo::GetModelInfo(MI_PLAYER)->GetModelName()); + playername[0] = 'C'; + playername[1] = 'S'; + name = playername; + } + + 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; + + 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); + if(model){ + debug("Associated anim %s with model %s\n", anim->name, model->GetModelName()); + RpClump *clump = (RpClump*)model->CreateInstance(); + RpAnimBlendClumpInit(clump); + assocList[i].Init(clump, anim); + if(IsClumpSkinned(clump)) + RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil); + RpClumpDestroy(clump); + assocList[i].animId = firstAnimId + i; + assocList[i].groupId = groupId; + }else + debug("\n\nCANNOT FIND MODELINFO WITH NAME %s\n\n\n", anim->name); + } + 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; + + 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 = firstAnimId + i; + assocList[i].groupId = groupId; + } + numAssociations = numAssocs; +} diff --git a/src/miami/animation/AnimBlendAssocGroup.h b/src/miami/animation/AnimBlendAssocGroup.h new file mode 100644 index 00000000..86f0ca18 --- /dev/null +++ b/src/miami/animation/AnimBlendAssocGroup.h @@ -0,0 +1,24 @@ +#pragma once + +class CAnimBlendAssociation; +struct CAnimBlock; + +class CAnimBlendAssocGroup +{ +public: + CAnimBlock *animBlock; + CAnimBlendAssociation *assocList; + int32 numAssociations; + int32 firstAnimId; + int32 groupId; // id of self in ms_aAnimAssocGroups + + 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/miami/animation/AnimBlendAssociation.cpp b/src/miami/animation/AnimBlendAssociation.cpp new file mode 100644 index 00000000..99e44311 --- /dev/null +++ b/src/miami/animation/AnimBlendAssociation.cpp @@ -0,0 +1,233 @@ +#include "common.h" + +#include "AnimBlendHierarchy.h" +#include "AnimBlendClumpData.h" +#include "RpAnimBlend.h" +#include "AnimManager.h" +#include "AnimBlendAssociation.h" +#include "MemoryMgr.h" + +CAnimBlendAssociation::CAnimBlendAssociation(void) +{ + groupId = -1; + 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) +{ + if(nodes) { + for(unsigned i = 0; i < numNodes; i++) + nodes[i].Destroy(); + 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]; + if(seq->boneTag == -1) + frame = RpAnimBlendClumpFindFrame(clump, seq->name); + else + frame = RpAnimBlendClumpFindBone(clump, seq->boneTag); + 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; + groupId = assoc.groupId; + 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; +} + +#if defined(DC_TEXCONV) +void +CAnimBlendAssociation::SetCurrentTime(float time) { + assert("false" && "Must not reach here"); +} +#else +void +CAnimBlendAssociation::SetCurrentTime(float time) +{ + int i; + + for(currentTime = time; currentTime >= hierarchy->totalLength; currentTime -= hierarchy->totalLength) + if (!IsRepeating()) { + currentTime = hierarchy->totalLength; + break; + } + + { + for(i = 0; i < numNodes; i++) + if(nodes[i].sequence) + nodes[i].FindKeyFrame(currentTime); + } +} +#endif + +void +CAnimBlendAssociation::SyncAnimation(CAnimBlendAssociation *other) +{ + SetCurrentTime(other->currentTime/other->hierarchy->totalLength * hierarchy->totalLength); +} + +void +CAnimBlendAssociation::Start(float time) +{ + flags |= ASSOC_RUNNING; + SetCurrentTime(time); +} + +void +CAnimBlendAssociation::UpdateTimeStep(float timeDelta, float relSpeed) +{ + if(IsRunning()) + timeStep = (flags & ASSOC_MOVEMENT ? relSpeed*hierarchy->totalLength : speed) * timeDelta; +} + +bool +CAnimBlendAssociation::UpdateTime(float timeDelta, float relSpeed) +{ + if(!IsRunning()) + return true; + if(currentTime >= hierarchy->totalLength){ + flags &= ~ASSOC_RUNNING; + return true; + } + + currentTime += timeStep; + + if(currentTime >= hierarchy->totalLength){ + // Ran past end + + if(IsRepeating()) + currentTime -= hierarchy->totalLength; + else{ + currentTime = hierarchy->totalLength; + 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/miami/animation/AnimBlendAssociation.h b/src/miami/animation/AnimBlendAssociation.h new file mode 100644 index 00000000..dbfcb722 --- /dev/null +++ b/src/miami/animation/AnimBlendAssociation.h @@ -0,0 +1,93 @@ +#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_HAS_X_TRANSLATION = 0x80, // for 2d velocity extraction + ASSOC_WALK = 0x100, // for CPed::PlayFootSteps(void) + ASSOC_IDLE = 0x200, // only xpress scratch has it by default, but game adds it to player's idle animations later + ASSOC_NOWALK = 0x400, // see CPed::PlayFootSteps(void) + ASSOC_BLOCK = 0x800, // unused in assoc description, blocks other anims from being played + ASSOC_FRONTAL = 0x1000, // anims that we fall to front + ASSOC_DRIVING = 0x2000, // new in VC +}; + +// 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; + + int16 numNodes; // taken from CAnimBlendClumpData::numFrames + int16 groupId; // ID of CAnimBlendAssocGroup this is in + // 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; + int16 animId; + int16 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); + void UpdateTimeStep(float timeDelta, float relSpeed); + bool UpdateTime(float timeDelta, float relSpeed); + bool UpdateBlend(float timeDelta); + + void SetRun(void) { flags |= ASSOC_RUNNING; } + + float GetTimeLeft() { return hierarchy->totalLength - currentTime; } + float GetProgress() { return currentTime / hierarchy->totalLength; } + + static CAnimBlendAssociation *FromLink(CAnimBlendLink *l) { + return (CAnimBlendAssociation*)((uint8*)l - offsetof(CAnimBlendAssociation, link)); + } +}; diff --git a/src/miami/animation/AnimBlendClumpData.cpp b/src/miami/animation/AnimBlendClumpData.cpp new file mode 100644 index 00000000..b333a449 --- /dev/null +++ b/src/miami/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/miami/animation/AnimBlendClumpData.h b/src/miami/animation/AnimBlendClumpData.h new file mode 100644 index 00000000..60bb783f --- /dev/null +++ b/src/miami/animation/AnimBlendClumpData.h @@ -0,0 +1,44 @@ +#pragma once + +#include "AnimBlendList.h" + + +struct AnimBlendFrameData +{ + enum { + IGNORE_ROTATION = 2, + IGNORE_TRANSLATION = 4, + VELOCITY_EXTRACTION = 8, + VELOCITY_EXTRACTION_3D = 0x10, + UPDATE_KEYFRAMES = 0x20 + }; + + uint8 flag; + RwV3d resetPos; + union { + RwFrame *frame; + RpHAnimStdInterpFrame *hanimFrame; + }; + int32 nodeID; +}; +VALIDATE_SIZE(AnimBlendFrameData, 0x18); + + +class CAnimBlendClumpData +{ +public: + CAnimBlendLink link; + int32 numFrames; + union { + CVector2D *velocity2d; + CVector *velocity3d; + }; + // order of frames is determined by RW hierarchy + AnimBlendFrameData *frames; + + CAnimBlendClumpData(void); + ~CAnimBlendClumpData(void); + void SetNumberOfFrames(int n); + void SetNumberOfBones(int n) { SetNumberOfFrames(n); } + void ForAllFrames(void (*cb)(AnimBlendFrameData*, void*), void *arg); +}; diff --git a/src/animation/AnimBlendHierarchy.cpp b/src/miami/animation/AnimBlendHierarchy.cpp similarity index 75% rename from src/animation/AnimBlendHierarchy.cpp rename to src/miami/animation/AnimBlendHierarchy.cpp index e87de493..ee7413fc 100644 --- a/src/animation/AnimBlendHierarchy.cpp +++ b/src/miami/animation/AnimBlendHierarchy.cpp @@ -2,6 +2,7 @@ #include "AnimBlendSequence.h" #include "AnimBlendHierarchy.h" +#include "AnimManager.h" CAnimBlendHierarchy::CAnimBlendHierarchy(void) { @@ -14,6 +15,7 @@ void CAnimBlendHierarchy::Shutdown(void) { RemoveAnimSequences(); + totalLength = 0.0f; } void @@ -29,29 +31,24 @@ CAnimBlendHierarchy::CalcTotalTime(void) totalLength = 0.0f; for(i = 0; i < numSequences; i++){ - float seqTime = 0.0f; - for(j = 0; j < sequences[i].numFrames; j++) - seqTime += sequences[i].GetDeltaTime(j); +#ifdef FIX_BUGS + if(sequences[i].numFrames == 0) + continue; +#endif + float seqTime = sequences[i].GetEndTime(); 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; + sequences = nil; numSequences = 0; } + #ifdef USE_CUSTOM_ALLOCATOR void CAnimBlendHierarchy::MoveMemory(bool onlyone) diff --git a/src/miami/animation/AnimBlendHierarchy.h b/src/miami/animation/AnimBlendHierarchy.h new file mode 100644 index 00000000..d363122e --- /dev/null +++ b/src/miami/animation/AnimBlendHierarchy.h @@ -0,0 +1,30 @@ +#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; + float totalLength; + + CAnimBlendHierarchy(void); + void Shutdown(void); + void SetName(char *name); + void CalcTotalTime(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/miami/animation/AnimBlendList.h b/src/miami/animation/AnimBlendList.h new file mode 100644 index 00000000..018b5988 --- /dev/null +++ b/src/miami/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/miami/animation/AnimBlendNode.cpp b/src/miami/animation/AnimBlendNode.cpp new file mode 100644 index 00000000..27873e5a --- /dev/null +++ b/src/miami/animation/AnimBlendNode.cpp @@ -0,0 +1,190 @@ +#include "common.h" + +#include "AnimBlendAssociation.h" +#include "AnimBlendNode.h" + +void +CAnimBlendNode::Init(void) +{ + frameA = -1; + remainingTime = 0.0f; + sequence = nil; + association = nil; + player = nil; +} + +void CAnimBlendNode::Destroy(void) { + if (player) { + delete player; + player = nil; + } +} + +bool +CAnimBlendNode::Update(CVector &trans, CQuaternion &rot, float weight) +{ + assert (player && player->keyFrames == sequence->keyFrames); + + 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){ + float kfAdt = player->GetNextTimeDelta(); + float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt; + if(player->type & CAnimBlendSequence::KF_TRANS){ + auto kfdAt = player->GetNextTranslationDelta(); + auto kfBt = player->GetPrevTranslation(); + trans = kfBt + t*kfdAt; + trans *= blend; + } + if(player->type & CAnimBlendSequence::KF_ROT){ + auto kfAr = player->GetNextRotation(); + auto kfBr = player->GetPrevRotation(); + rot.Slerp(kfBr, kfAr, theta, invSin, t); + rot *= blend; + } + } + + return looped; +} + +bool +CAnimBlendNode::NextKeyFrame(void) +{ + assert(player != nil); + bool looped; + + if(player->numFrames <= 1) + return false; + + looped = false; + + // Advance as long as we have to + while(remainingTime <= 0.0f){ + frameA++; + + if(frameA >= player->numFrames){ + // reached end of animation + if(!association->IsRepeating()){ + frameA--; + remainingTime = 0.0f; + assert(frameA == player->curFrame); + CalcDeltas(); + return false; + } + looped = true; + frameA = 0; + } + player->AdvanceFrame(); + remainingTime += player->GetNextTimeDelta(); + } + + assert(frameA == player->curFrame); + CalcDeltas(); + return looped; +} + +// Set animation to time t +bool +CAnimBlendNode::FindKeyFrame(float t) +{ + if (player == nil) { + player = new CAnimBlendPlayer(); + player->Init(sequence->keyFrames, sequence->type, sequence->numFrames); + } + if(player->numFrames < 1) + return false; + + frameA = 0; + player->SeekToStart(); + + assert (player->keyFrames == sequence->keyFrames); + + if(player->numFrames == 1){ + remainingTime = 0.0f; + }else{ + // advance until t is between frameB and frameA + frameA++; + player->AdvanceFrame(); + while (t > player->GetNextTimeDelta()) { + t -= player->GetNextTimeDelta(); + if (frameA + 1 >= player->numFrames) { + // reached end of animation + if (!association->IsRepeating()) { + assert(frameA == player->curFrame); + CalcDeltas(); + remainingTime = 0.0f; + return false; + } + // Frame 0 is effectively skipped here + // Looks like an re3 / game bug? + frameA = 0; + player->SeekToStart(); + } + frameA++; + player->AdvanceFrame(); + } + + remainingTime = player->GetNextTimeDelta() - t; + } + + assert(frameA == player->curFrame); + CalcDeltas(); + return true; +} + +void +CAnimBlendNode::CalcDeltas(void) +{ + if((player->type & CAnimBlendSequence::KF_ROT) == 0) + return; + auto kfAr = player->GetNextRotation(); + auto kfBr = player->GetPrevRotation(); + float cos = DotProduct(kfAr, kfBr); + 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){ + auto kfAdt = player->GetNextTimeDelta(); + float t = kfAdt == 0.0f ? 0.0f : (kfAdt - remainingTime)/kfAdt; + if(player->type & CAnimBlendSequence::KF_TRANS){ + auto kfdAt = player->GetNextTranslationDelta(); + auto kfBt = player->GetPrevTranslation(); + trans = kfBt + t*kfdAt; + 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){ + if(player->type & CAnimBlendSequence::KF_TRANS){ + CVector pos = sequence->GetEndTranslation(); + trans = pos * blend; + } + } +} \ No newline at end of file diff --git a/src/miami/animation/AnimBlendNode.h b/src/miami/animation/AnimBlendNode.h new file mode 100644 index 00000000..71ea6b0c --- /dev/null +++ b/src/miami/animation/AnimBlendNode.h @@ -0,0 +1,32 @@ +#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 + float remainingTime; // time until frames have to advance + CAnimBlendPlayer* player; + CAnimBlendSequence *sequence; + CAnimBlendAssociation *association; + + void Init(void); + void Destroy(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/miami/animation/AnimBlendSequence.cpp b/src/miami/animation/AnimBlendSequence.cpp new file mode 100644 index 00000000..8262252d --- /dev/null +++ b/src/miami/animation/AnimBlendSequence.cpp @@ -0,0 +1,46 @@ +#include "common.h" + +#include "AnimBlendSequence.h" +#include "MemoryHeap.h" + +CAnimBlendSequence::CAnimBlendSequence(void) +{ + type = 0; + numFrames = 0; + keyFrames = nil; + boneTag = -1; +} + +CAnimBlendSequence::~CAnimBlendSequence(void) +{ + if(keyFrames) + RwFree(keyFrames); +} + +void +CAnimBlendSequence::SetName(char *name) +{ + strncpy(this->name, name, 24); +} + +#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/miami/animation/AnimBlendSequence.h b/src/miami/animation/AnimBlendSequence.h new file mode 100644 index 00000000..65b51259 --- /dev/null +++ b/src/miami/animation/AnimBlendSequence.h @@ -0,0 +1,398 @@ +#pragma once + +#include + +#include "Quaternion.h" + +#ifdef MoveMemory +#undef MoveMemory // windows shit +#endif + +struct CAnimBlendPlayer { + enum { + KF_ROT = 1, + KF_TRANS = 2, + + FLAGS_HAS_ROT_Y = 1 << 8, + FLAGS_HAS_ROT_P = 1 << 9, + FLAGS_HAS_ROT_R = 1 << 10, + + FLAGS_HAS_TRANS_X = 1 << 11, + FLAGS_HAS_TRANS_Y = 1 << 12, + FLAGS_HAS_TRANS_Z = 1 << 13, + FLAGS_HAS_TRANS_ANY = 7 << 11, + FLAGS_HAS_TRANS_LARGE = 1 << 14, + FLAGS_QUAT0_NEG = 1 << 15 + }; + + int32 type; + void* keyFrames; + int32 curFrame; + int32 numFrames; + CQuaternion currentRotation; + CQuaternion nextRotation; + CVector currentTranslation; + CVector nextTranslation; + + unsigned readOffset; + unsigned readOffset_initial; + + uint16_t predicted_y, predicted_p, predicted_r; + float predicted_tx = 0, predicted_ty = 0, predicted_tz = 0; + float nextDeltaTime; + + template + T read_unaligned(uint32_t ro) { + T rv; + for (unsigned i = 0; i < sizeof(T); i++) { + ((uint8_t*)&rv)[i] = ((uint8_t*)keyFrames)[ro]; + ro++; + } + readOffset = ro; + return rv; + } + template + __always_inline T read() { + if (!(readOffset & (sizeof(T) -1))) { + return read_aligned(); + } else { + return read_unaligned(readOffset); + } + } + + template + __always_inline T read_aligned() { + T rv; + rv = *(T*)((uint8_t*)keyFrames + readOffset); + readOffset += sizeof(T); + return rv; + } + + __always_inline CQuaternion fromSphericalFixed(uint16_t y, uint16_t p, uint16_t r) { + CQuaternion q; + #if !defined(DC_SH4) + q.w = cos((y / 65536.0f) * 2 * M_PI) * cos((p / 65536.0f) * 2 * M_PI); + q.x = cos((y / 65536.0f) * 2 * M_PI) * sin((p / 65536.0f) * 2 * M_PI); + q.y = sin((y / 65536.0f) * 2 * M_PI) * cos((r / 65536.0f) * 2 * M_PI); + q.z = sin((y / 65536.0f) * 2 * M_PI) * sin((r / 65536.0f) * 2 * M_PI); + #else + register float __ys __asm__("fr0"); + register float __yc __asm__("fr1"); + register float __ps __asm__("fr2"); + register float __pc __asm__("fr3"); + register float __rs __asm__("fr4"); + register float __rc __asm__("fr5"); + + __asm__ __volatile__( + R"( + lds %[y],fpul + fsca fpul, dr0 + lds %[p],fpul + fsca fpul, dr2 + lds %[r],fpul + fsca fpul, dr4 + )" + : "=f" (__ys), "=f" (__yc), "=f" (__ps), "=f" (__pc), "=f" (__rs), "=f" (__rc) + : "0" (__ys), "1" (__yc), "2" (__ps), "3" (__pc), "4" (__rs), "5" (__rc), [y]"r"(y), [p]"r"(p), [r]"r"(r)); + + q.w = __yc * __pc; + q.x = __yc * __ps; + q.y = __ys * __rc; + q.z = __ys * __rs; + #endif + return q; + } + + void AdvanceFrame() { + if (++curFrame == numFrames){ + currentRotation = nextRotation; + currentTranslation = nextTranslation; + SeekToStart(); + return; + } + + // rotation + { + currentRotation = nextRotation; + + // For rotation Y: + if (type & FLAGS_HAS_ROT_Y) { + uint8_t byteVal = read(); + if (byteVal == 128) { + predicted_y = read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_y += diff * 8; + } + } + // For rotation P: + if (type & FLAGS_HAS_ROT_P) { + uint8_t byteVal = read(); + if (byteVal == 128) { + predicted_p = read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_p += diff * 8; + } + } + // For rotation R: + if (type & FLAGS_HAS_ROT_R) { + uint8_t byteVal = read(); + if (byteVal == 128) { + predicted_r = read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_r += diff * 8; + } + } + + nextRotation = fromSphericalFixed(predicted_y, predicted_p, predicted_r); + } + + // translation + if (type & KF_TRANS) { + currentTranslation = nextTranslation; + if (type & FLAGS_HAS_TRANS_X) { + uint8_t byteVal = read(); + if (byteVal == 128) { + uint16_t diff = read(); + if (diff != 32768) { + predicted_tx += static_cast(diff) / 128.f; + } else { + predicted_tx = read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_tx += diff / 127.f; + } + } + // Translation Y: + if (type & FLAGS_HAS_TRANS_Y) { + uint8_t byteVal = read(); + if (byteVal == 128) { + uint16_t diff = read(); + if (diff != 32768) { + predicted_ty += static_cast(diff) / 128.f; + } else { + predicted_ty = read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_ty += diff / 127.f; + } + } + // Translation Z: + if (type & FLAGS_HAS_TRANS_Z) { + uint8_t byteVal = read(); + if (byteVal == 128) { + uint16_t diff = read(); + if (diff != 32768) { + predicted_tz += static_cast(diff) / 128.f; + } else { + predicted_tz = read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_tz += diff / 127.f; + } + } + + nextTranslation = { predicted_tx, predicted_ty, predicted_tz }; + } + + // time delta + quaternion flips + { + uint8_t byteValPacked = read(); + uint8_t byteVal = byteValPacked & 127; + float diff; + if (byteVal == 127) { + uint16_t fixed_diff = read(); + diff = fixed_diff / 256.f; + } else { + diff = byteVal / 256.f; + } + nextDeltaTime = diff; + + if (byteValPacked & 128) { + nextRotation = -nextRotation; + } + } + } + + CQuaternion GetPrevRotation() { + return currentRotation; + } + CQuaternion GetNextRotation() { + return nextRotation; + } + float GetNextTimeDelta() { + return nextDeltaTime; + } + + CVector GetPrevTranslation() { + return currentTranslation; + } + CVector GetNextTranslationDelta() { + return nextTranslation - currentTranslation; + } + + void Init(void* kf, int32 tp, int nF) { + keyFrames = kf; + type = tp; + numFrames = nF; + + SeekToStart(); + currentTranslation = nextTranslation; + currentRotation = nextRotation; + } + + void SeekToStart() { + readOffset = 0; + float startTime = read_aligned(); + float endTime = read_aligned(); + + if (type & KF_TRANS) { + CVector startTranslation; + if (type & FLAGS_HAS_TRANS_LARGE) { + startTranslation.x = read_aligned(); + startTranslation.y = read_aligned(); + startTranslation.z = read_aligned(); + predicted_tx = startTranslation.x; + predicted_ty = startTranslation.y; + predicted_tz = startTranslation.z; + + CVector endTranslation; + // Read final translation (may be used for verification or ignored) + endTranslation.x = read_aligned(); + endTranslation.y = read_aligned(); + endTranslation.z = read_aligned(); + } else { + startTranslation.x = read_aligned() / 128.f; + startTranslation.y = read_aligned() / 128.f; + startTranslation.z = read_aligned() / 128.f; + predicted_tx = startTranslation.x; + predicted_ty = startTranslation.y; + predicted_tz = startTranslation.z; + + CVector endTranslation; + // Read final translation (for completeness) + endTranslation.x = read_aligned() / 128.f; + endTranslation.y = read_aligned() / 128.f; + endTranslation.z = read_aligned() / 128.f; + } + + nextTranslation = startTranslation; + } else { + CVector startTranslation = { 0, 0, 0 }; + CVector endTranslation = { 0, 0, 0 }; + nextTranslation = startTranslation; + } + + predicted_y = read_aligned(); + predicted_p = read_aligned(); + predicted_r = read_aligned(); + nextRotation = fromSphericalFixed(predicted_y, predicted_p, predicted_r); + + if (type & FLAGS_QUAT0_NEG) { + nextRotation = -nextRotation; + } + + nextDeltaTime = startTime; + curFrame = 0; + } +}; + +// The sequence of key frames of one animated node +class CAnimBlendSequence +{ + template + __always_inline T read_aligned(uint32_t &readOffset) { + T rv; + rv = *(T*)((uint8_t*)keyFrames + readOffset); + readOffset += sizeof(T); + return rv; + } + +public: + enum { + KF_ROT = 1, + KF_TRANS = 2, + + FLAGS_HAS_ROT_Y = 1 << 8, + FLAGS_HAS_ROT_P = 1 << 9, + FLAGS_HAS_ROT_R = 1 << 10, + + FLAGS_HAS_TRANS_X = 1 << 11, + FLAGS_HAS_TRANS_Y = 1 << 12, + FLAGS_HAS_TRANS_Z = 1 << 13, + FLAGS_HAS_TRANS_ANY = 7 << 11, + FLAGS_HAS_TRANS_LARGE = 1 << 14, + FLAGS_QUAT0_NEG = 1 << 15 + }; + int32 type; + char name[24]; + int32 numFrames; + int16 boneTag; + void *keyFrames; + + struct InitData { + CVector startTranslation, endTranslation; + float endTime; + }; + + __always_inline InitData GetInitData() { + InitData rv; + uint32_t readOffset = 0; + + float startTime = read_aligned(readOffset); + rv.endTime = read_aligned(readOffset); + + if (type & KF_TRANS) { + if (type & FLAGS_HAS_TRANS_LARGE) { + rv.startTranslation.x = read_aligned(readOffset); + rv.startTranslation.y = read_aligned(readOffset); + rv.startTranslation.z = read_aligned(readOffset); + + // Read final translation (may be used for verification or ignored) + rv.endTranslation.x = read_aligned(readOffset); + rv.endTranslation.y = read_aligned(readOffset); + rv.endTranslation.z = read_aligned(readOffset); + } else { + rv.startTranslation.x = read_aligned(readOffset) / 128.f; + rv.startTranslation.y = read_aligned(readOffset) / 128.f; + rv.startTranslation.z = read_aligned(readOffset) / 128.f; + + // Read final translation (for completeness) + rv.endTranslation.x = read_aligned(readOffset) / 128.f; + rv.endTranslation.y = read_aligned(readOffset) / 128.f; + rv.endTranslation.z = read_aligned(readOffset) / 128.f; + } + } else { + rv.startTranslation = { 0, 0, 0 }; + rv.endTranslation = { 0, 0, 0 }; + } + + return rv; + } + + + CVector GetStartTranslation() { + return GetInitData().startTranslation; + } + float GetEndTime() { + return GetInitData().endTime; + } + CVector GetEndTranslation() { + return GetInitData().endTranslation; + } + + CAnimBlendSequence(void); + virtual ~CAnimBlendSequence(void); + void SetName(char *name); + + bool HasTranslation(void) { return !!(type & KF_TRANS); } + bool MoveMemory(void); + + void SetBoneTag(int tag) { boneTag = tag; } +}; +VALIDATE_SIZE(CAnimBlendSequence, 0x30); diff --git a/src/miami/animation/AnimManager.cpp b/src/miami/animation/AnimManager.cpp new file mode 100644 index 00000000..73388016 --- /dev/null +++ b/src/miami/animation/AnimManager.cpp @@ -0,0 +1,1338 @@ +#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" +#include "Streaming.h" + +CAnimBlock CAnimManager::ms_aAnimBlocks[NUMANIMBLOCKS]; +CAnimBlendHierarchy CAnimManager::ms_aAnimations[NUMANIMATIONS]; +int32 CAnimManager::ms_numAnimBlocks; +int32 CAnimManager::ms_numAnimations; +CAnimBlendAssocGroup *CAnimManager::ms_aAnimAssocGroups; + +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 }, + { 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_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_FIGHT_JAB, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_ELBOW_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_ELBOW_R, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_BKICK_L, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_BKICK_R, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_DETONATE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_PUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_PARTIALPUNCH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_KICKGROUND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_THROW_UNDER, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_FIGHT_SHUFFLE_B, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { 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_CAR_JUMP_IN_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 | ASSOC_DRIVING }, + { ANIM_STD_CAR_DRIVE_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_CAR_DRIVE_LEFT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_CAR_DRIVE_RIGHT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_CAR_DRIVEBY_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_CAR_DRIVEBY_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_CAR_DRIVEBY_LEFT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_CAR_DRIVEBY_RIGHT_LO, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_CAR_LOOKBEHIND, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_BOAT_DRIVE, ASSOC_DELETEFADEDOUT | ASSOC_DRIVING }, + { ANIM_STD_BOAT_DRIVE_LEFT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_BOAT_DRIVE_RIGHT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_BOAT_LOOKBEHIND, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_STD_BIKE_PICKUP_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_BIKE_PICKUP_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_BIKE_PULLUP_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_BIKE_PULLUP_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_BIKE_ELBOW_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_BIKE_ELBOW_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_BIKE_FALLOFF, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_STD_BIKE_FALLBACK, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { 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_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_ROLLOUT_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION }, + { ANIM_STD_ROLLOUT_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION }, + { 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_FALL_ONBACK, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_FALL_ONFRONT, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_FRONTAL }, + { 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_STD_DUCK_DOWN, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_DUCK_LOW, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_DUCK_WEAPON, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_RBLOCK_SHOOT, ASSOC_DELETEFADEDOUT | 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_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_PHONE_OUT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_PHONE_TALK, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, + { ANIM_STD_SEAT_DOWN, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_SEAT_UP, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_SEAT_IDLE, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_SEAT_RVRS, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_ATM, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STD_ABSEIL, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL }, +}; +AnimAssocDesc aVanAnimDescs[] = { + { 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 }, +}; +AnimAssocDesc aCoachAnimDescs[] = { + { 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 }, +}; +AnimAssocDesc aBikeAnimDescs[] = { + { ANIM_BIKE_RIDE, ASSOC_DELETEFADEDOUT}, + { ANIM_BIKE_READY, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_LEFT, ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_RIGHT, ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_LEANB, ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_LEANF, ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_WALKBACK, ASSOC_REPEAT | ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_JUMPON_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_BIKE_JUMPON_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_BIKE_KICK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_BIKE_HIT, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_BIKE_GETOFF_LHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_BIKE_GETOFF_RHS, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_BIKE_GETOFF_BACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, + { ANIM_BIKE_DRIVEBY_LHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_DRIVEBY_RHS, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_DRIVEBY_FORWARD, ASSOC_DELETEFADEDOUT | ASSOC_PARTIAL | ASSOC_DRIVING }, + { ANIM_BIKE_RIDE_P, ASSOC_DELETEFADEDOUT | ASSOC_DRIVING }, +}; +AnimAssocDesc aMeleeAnimDescs[] = { + { ANIM_MELEE_ATTACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_MELEE_ATTACK_2ND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_MELEE_ATTACK_START, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_NOWALK }, + { ANIM_MELEE_IDLE_FIGHTMODE, ASSOC_REPEAT }, + { ANIM_MELEE_ATTACK_FINISH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION }, +}; +AnimAssocDesc aSwingAnimDescs[] = { + { ANIM_MELEE_ATTACK, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_MELEE_ATTACK_2ND, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_MELEE_ATTACK_START, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_MELEE_IDLE_FIGHTMODE, ASSOC_REPEAT }, + { ANIM_MELEE_ATTACK_FINISH, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +}; +AnimAssocDesc aWeaponAnimDescs[] = { + { ANIM_WEAPON_FIRE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_CROUCHFIRE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_RELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_CROUCHRELOAD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_WEAPON_FIRE_3RD, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +}; +AnimAssocDesc aMedicAnimDescs[] = { + { ANIM_MEDIC_CPR, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +}; +AnimAssocDesc aSunbatheAnimDescs[] = { + { ANIM_SUNBATHE_IDLE, ASSOC_REPEAT | ASSOC_PARTIAL }, + { ANIM_SUNBATHE_DOWN, ASSOC_REPEAT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION }, + { ANIM_SUNBATHE_UP, ASSOC_REPEAT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION }, + { ANIM_SUNBATHE_ESCAPE, ASSOC_REPEAT | ASSOC_PARTIAL | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION }, +}; +AnimAssocDesc aPlayerIdleAnimDescs[] = { + { ANIM_PLAYER_IDLE1, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_PLAYER_IDLE2, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_PLAYER_IDLE3, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_PLAYER_IDLE4, ASSOC_DELETEFADEDOUT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +}; +AnimAssocDesc aRiotAnimDescs[] = { + { ANIM_RIOT_ANGRY, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_RIOT_ANGRY_B, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_RIOT_CHANT, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_RIOT_PUNCHES, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_RIOT_SHOUT, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_RIOT_CHALLENGE, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_RIOT_FUCKYOU, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +}; +AnimAssocDesc aStripAnimDescs[] = { + { ANIM_STRIP_A, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STRIP_B, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STRIP_C, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STRIP_D, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STRIP_E, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STRIP_F, ASSOC_REPEAT | ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, + { ANIM_STRIP_G, ASSOC_FADEOUTWHENDONE | ASSOC_PARTIAL }, +}; +#ifdef PC_PLAYER_CONTROLS +AnimAssocDesc aStdAnimDescsSide[] = { + { ANIM_STD_WALK, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION | ASSOC_WALK }, + { ANIM_STD_RUN, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION | ASSOC_WALK }, + { ANIM_STD_RUNFAST, ASSOC_REPEAT | ASSOC_MOVEMENT | ASSOC_HAS_TRANSLATION | ASSOC_HAS_X_TRANSLATION | ASSOC_WALK }, + { 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_hbhb", + "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", + "HIT_bodyblow", + "HIT_chest", + "HIT_head", + "HIT_walk", + "HIT_wall", + "FLOOR_hit_f", + "HIT_behind", + "FIGHTIDLE", + "FIGHT2IDLE", + "FIGHTsh_F", + "FIGHTbodyblow", + "FIGHThead", + "FIGHTkick", + "FIGHTknee", + "FIGHTLhook", + "FIGHTpunch", + "FIGHTrndhse", + "FIGHTlngkck", + "FIGHTppunch", + "FIGHTjab", + "FIGHTelbowL", + "FIGHTelbowR", + "FIGHTbkickL", + "FIGHTbkickR", + "bomber", + "punchR", + "FIGHTppunch", + "KICK_floor", + "WEAPON_throwu", + "FIGHTsh_back", + "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_jumpin_LHS", + "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", + "DrivebyL_L", + "DrivebyL_R", + "CAR_LB", + "DRIVE_BOAT", + "DRIVE_BOAT_L", + "DRIVE_BOAT_R", + "DRIVE_BOAT_back", + "BIKE_pickupR", + "BIKE_pickupL", + "BIKE_pullupR", + "BIKE_pullupL", + "BIKE_elbowR", + "BIKE_elbowL", + "BIKE_fall_off", + "BIKE_fallR", + "CAR_getout_RHS", + "CAR_getoutL_RHS", + "CAR_close_RHS", + "car_hookertalk", + "idle_stance", + "idle_stance", + "CAR_crawloutRHS", + "CAR_crawloutRHS", + "CAR_rollout_LHS", + "CAR_rollout_LHS", + "Getup", + "Getup", + "Getup", + "Getup_front", + "JUMP_launch", + "JUMP_glide", + "JUMP_land", + "FALL_fall", + "FALL_glide", + "FALL_land", + "FALL_collapse", + "FALL_back", + "FALL_front", + "EV_step", + "EV_dive", + "XPRESSscratch", + "roadcross", + "TURN_180", + "ARRESTgun", + "DROWN", + "DUCK_down", + "DUCK_low", + "WEAPON_crouch", + "RBLOCK_Cshoot", + "handsup", + "handsCOWER", + "FUCKU", + "PHONE_in", + "PHONE_out", + "PHONE_talk", + "SEAT_down", + "SEAT_up", + "SEAT_idle", + "SEAT_down", + "ATM", + "abseil", +}; +char const* aVanAnimations[] = { + "VAN_openL", + "VAN_getinL", + "VAN_closeL", + "VAN_getoutL", + "VAN_open", + "VAN_getin", + "VAN_close", + "VAN_getout", +}; +char const* aCoachAnimations[] = { + "COACH_opnL", + "COACH_opnL", + "COACH_inL", + "COACH_inL", + "COACH_outL", +}; +char const* aBikesAnimations[] = { + "BIKEs_Ride", + "BIKEs_Still", + "BIKEs_Left", + "BIKEs_Right", + "BIKEs_Back", + "BIKEs_Fwd", + "BIKEs_pushes", + "BIKEs_jumponR", + "BIKEs_jumponL", + "BIKEs_kick", + "BIKEs_hit", + "BIKEs_getoffRHS", + "BIKEs_getoffLHS", + "BIKEs_getoffBACK", + "BIKEs_drivebyLHS", + "BIKEs_drivebyRHS", + "BIKEs_drivebyFT", + "BIKEs_passenger", +}; +char const* aBikevAnimations[] = { + "BIKEv_Ride", + "BIKEv_Still", + "BIKEv_Left", + "BIKEv_Right", + "BIKEv_Back", + "BIKEv_Fwd", + "BIKEv_pushes", + "BIKEv_jumponR", + "BIKEv_jumponL", + "BIKEv_kick", + "BIKEv_hit", + "BIKEv_getoffRHS", + "BIKEv_getoffLHS", + "BIKEv_getoffBACK", + "BIKEv_drivebyLHS", + "BIKEv_drivebyRHS", + "BIKEv_drivebyFT", + "BIKEv_passenger", +}; +char const* aBikehAnimations[] = { + "BIKEh_Ride", + "BIKEh_Still", + "BIKEh_Left", + "BIKEh_Right", + "BIKEh_Back", + "BIKEh_Fwd", + "BIKEh_pushes", + "BIKEh_jumponR", + "BIKEh_jumponL", + "BIKEh_kick", + "BIKEh_hit", + "BIKEh_getoffRHS", + "BIKEh_getoffLHS", + "BIKEh_getoffBACK", + "BIKEh_drivebyLHS", + "BIKEh_drivebyRHS", + "BIKEh_drivebyFT", + "BIKEh_passenger", +}; +char const* aBikedAnimations[] = { + "BIKEd_Ride", + "BIKEd_Still", + "BIKEd_Left", + "BIKEd_Right", + "BIKEd_Back", + "BIKEd_Fwd", + "BIKEd_pushes", + "BIKEd_jumponR", + "BIKEd_jumponL", + "BIKEd_kick", + "BIKEd_hit", + "BIKEd_getoffRHS", + "BIKEd_getoffLHS", + "BIKEd_getoffBACK", + "BIKEd_drivebyLHS", + "BIKEd_drivebyRHS", + "BIKEd_drivebyFT", + "BIKEd_passenger", +}; +char const* aUnarmedAnimations[] = { + "punchR", + "KICK_floor", + "FIGHTppunch", +}; +char const* aScrewdriverAnimations[] = { + "FIGHTbodyblow", + "FIGHTbodyblow", + "FIGHTppunch", + "FIGHTIDLE", + "FIGHTbodyblow", +}; +char const* aKnifeAnimations[] = { + "WEAPON_knife_1", + "WEAPON_knife_2", + "knife_part", + "WEAPON_knifeidle", + "WEAPON_knife_3", +}; +char const* aBaseballbatAnimations[] = { + "WEAPON_bat_h", + "WEAPON_bat_v", + "BAT_PART", + "WEAPON_bat_h", + "WEAPON_golfclub", +}; +char const* aGolfclubAnimations[] = { + "WEAPON_bat_h", + "WEAPON_golfclub", + "BAT_PART", + "WEAPON_bat_h", + "WEAPON_bat_v", +}; +char const* aChainsawAnimations[] = { + "WEAPON_csaw", + "WEAPON_csawlo", + "csaw_part", +}; +char const* aPythonAnimations[] = { + "python_fire", + "python_crouchfire", + "python_reload", + "python_crouchreload", +}; +char const* aColtAnimations[] = { + "colt45_fire", + "colt45_crouchfire", + "colt45_reload", + "colt45_crouchreload", + "colt45_cop", +}; +char const* aShotgunAnimations[] = { + "shotgun_fire", + "shotgun_crouchfire", +}; +char const* aBuddyAnimations[] = { + "buddy_fire", + "buddy_crouchfire", +}; +char const* aTecAnimations[] = { + "TEC_fire", + "TEC_crouchfire", + "TEC_reload", + "TEC_crouchreload", +}; +char const* aUziAnimations[] = { + "UZI_fire", + "UZI_crouchfire", + "UZI_reload", + "UZI_crouchreload", +}; +char const* aRifleAnimations[] = { + "RIFLE_fire", + "RIFLE_crouchfire", + "RIFLE_load", + "RIFLE_crouchload", +}; +char const* aM60Animations[] = { + "M60_fire", + "M60_fire", + "M60_reload", +}; +char const* aSniperAnimations[] = { + "WEAPON_sniper", +}; +char const* aThrowAnimations[] = { + "WEAPON_throw", + "WEAPON_throwu", + "WEAPON_start_throw", +}; +char const* aFlamethrowerAnimations[] = { + "FLAME_fire", +}; +char const* aMedicAnimations[] = { + "CPR", +}; +char const* aSunbatheAnimations[] = { + "bather", + "batherdown", + "batherup", + "batherscape", +}; +char const* aPlayerIdleAnimations[] = { + "stretch", + "time", + "shldr", + "strleg", +}; +char const* aRiotAnimations[] = { + "riot_angry", + "riot_angry_b", + "riot_chant", + "riot_punches", + "riot_shout", + "riot_challenge", + "riot_fuku", +}; +char const* aStripAnimations[] = { + "strip_A", + "strip_B", + "strip_C", + "strip_D", + "strip_E", + "strip_F", + "strip_G", +}; +char const* aLanceAnimations[] = { + "lance", +}; +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_armed", + "run_armed", + "run_armed", + "idle_armed", + "walk_start_armed", +}; +char const* aPlayerBBBatAnimations[] = { + "walk_player", + "run_player", + "run_player", + "IDLE_STANCE", + "walk_start", +}; +char const* aPlayerChainsawAnimations[] = { + "walk_csaw", + "run_csaw", + "run_csaw", + "IDLE_csaw", + "walk_start_csaw", +}; +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* aJoggerAnimations[] = { + "JOG_maleA", + "run_civi", + "sprint_civi", + "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* aFatWomanAnimations[] = { + "walk_fat", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const* aOldWomanAnimations[] = { + "woman_walkold", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const* aJoggerWomanAnimations[] = { + "JOG_maleB", + "woman_run", + "woman_runpanic", + "woman_idlestance", +}; +char const* aPanicChunkyAnimations[] = { + "run_fatold", + "woman_runpanic", + "woman_runpanic", + "idle_stance", +}; +char const* aSkateAnimations[] = { + "skate_run", + "skate_sprint", + "skate_sprint", + "skate_idle", +}; +#ifdef PC_PLAYER_CONTROLS +char const* aPlayerStrafeBackAnimations[] = { + "walk_back", + "run_back", + "run_back", + "IDLE_STANCE", + "walk_start_back", +}; +char const* aPlayerStrafeLeftAnimations[] = { + "walk_left", + "run_left", + "run_left", + "IDLE_STANCE", + "walk_start_left", +}; +char const* aPlayerStrafeRightAnimations[] = { + "walk_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", +}; +char const* aChainsawStrafeBackAnimations[] = { + "walk_csaw_back", + "run_csaw_back", + "run_csaw_back", + "idle_csaw", + "walkst_csaw_back", +}; +char const* aChainsawStrafeLeftAnimations[] = { + "walk_csaw_left", + "run_csaw_left", + "run_csaw_left", + "idle_csaw", + "walkst_csaw_left", +}; +char const* aChainsawStrafeRightAnimations[] = { + "walk_csaw_right", + "run_csaw_right", + "run_csaw_right", + "idle_csaw", + "walkst_csaw_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 }, + { "van", "van", MI_COP, awc(aVanAnimations), aVanAnimDescs }, + { "coach", "coach", MI_COP, awc(aCoachAnimations), aCoachAnimDescs }, + { "bikes", "bikes", MI_COP, awc(aBikesAnimations), aBikeAnimDescs }, + { "bikev", "bikev", MI_COP, awc(aBikevAnimations), aBikeAnimDescs }, + { "bikeh", "bikeh", MI_COP, awc(aBikehAnimations), aBikeAnimDescs }, + { "biked", "biked", MI_COP, awc(aBikedAnimations), aBikeAnimDescs }, + { "unarmed", "ped", MI_COP, awc(aUnarmedAnimations), aMeleeAnimDescs }, + { "screwdrv", "ped", MI_COP, awc(aScrewdriverAnimations), aMeleeAnimDescs }, + { "knife", "knife", MI_COP, awc(aKnifeAnimations), aMeleeAnimDescs }, + { "baseball", "baseball", MI_COP, awc(aBaseballbatAnimations), aSwingAnimDescs }, + { "golfclub", "baseball", MI_COP, awc(aGolfclubAnimations), aSwingAnimDescs }, + { "chainsaw", "chainsaw", MI_COP, awc(aChainsawAnimations), aMeleeAnimDescs }, + { "python", "python", MI_COP, awc(aPythonAnimations), aWeaponAnimDescs }, + { "colt45", "colt45", MI_COP, awc(aColtAnimations), aWeaponAnimDescs }, + { "shotgun", "shotgun", MI_COP, awc(aShotgunAnimations), aWeaponAnimDescs }, + { "buddy", "buddy", MI_COP, awc(aBuddyAnimations), aWeaponAnimDescs }, + { "tec", "tec", MI_COP, awc(aTecAnimations), aWeaponAnimDescs }, + { "uzi", "uzi", MI_COP, awc(aUziAnimations), aWeaponAnimDescs }, + { "rifle", "rifle", MI_COP, awc(aRifleAnimations), aWeaponAnimDescs }, + { "m60", "m60", MI_COP, awc(aM60Animations), aWeaponAnimDescs }, + { "sniper", "sniper", MI_COP, awc(aSniperAnimations), aWeaponAnimDescs }, + { "grenade", "grenade", MI_COP, awc(aThrowAnimations), aWeaponAnimDescs }, + { "flame", "flame", MI_COP, awc(aFlamethrowerAnimations), aWeaponAnimDescs }, + { "medic", "medic", MI_COP, awc(aMedicAnimations), aMedicAnimDescs }, + { "sunbathe", "sunbathe", MI_COP, 1, aSunbatheAnimations, aSunbatheAnimDescs }, // NB: not using awc here! + { "playidles", "playidles", MI_COP, awc(aPlayerIdleAnimations), aPlayerIdleAnimDescs }, + { "riot", "riot", MI_COP, awc(aRiotAnimations), aRiotAnimDescs }, + { "strip", "strip", MI_COP, awc(aStripAnimations), aStripAnimDescs }, + { "lance", "lance", MI_COP, awc(aLanceAnimations), aSunbatheAnimDescs }, + { "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 }, + { "playercsaw", "ped", MI_COP, awc(aPlayerChainsawAnimations), 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 }, + { "jogger", "ped", MI_COP, awc(aJoggerAnimations), 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 }, + { "fatwoman", "ped", MI_COP, awc(aFatWomanAnimations), aStdAnimDescs }, + { "oldwoman", "ped", MI_COP, awc(aOldWomanAnimations), aStdAnimDescs }, + { "jogwoman", "ped", MI_COP, awc(aJoggerWomanAnimations), aStdAnimDescs }, + { "panicchunky", "ped", MI_COP, awc(aPanicChunkyAnimations), aStdAnimDescs }, + { "skate", "skate", MI_COP, awc(aSkateAnimations), 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 }, + { "csawback", "ped", MI_COP, awc(aChainsawStrafeBackAnimations), aStdAnimDescs }, + { "csawleft", "ped", MI_COP, awc(aChainsawStrafeLeftAnimations), aStdAnimDescsSide }, + { "csawright", "ped", MI_COP, awc(aChainsawStrafeRightAnimations), aStdAnimDescsSide }, +#endif +}; +#undef awc + +void +CAnimManager::Initialise(void) +{ + ms_numAnimations = 0; + ms_numAnimBlocks = 0; +} + +void +CAnimManager::Shutdown(void) +{ + int i; + + for(i = 0; i < NUMANIMBLOCKS; i++) + CStreaming::RemoveAnim(i); + + for(i = 0; i < ms_numAnimations; i++) + ms_aAnimations[i].Shutdown(); + + + delete[] ms_aAnimAssocGroups; +} + + +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; +} + +int32 +CAnimManager::GetAnimationBlockIndex(const char *name) +{ + int i; + + for(i = 0; i < ms_numAnimBlocks; i++) + if(strcasecmp(ms_aAnimBlocks[i].name, name) == 0) + return i; + return -1; +} + +int32 +CAnimManager::RegisterAnimBlock(const char *name) +{ + CAnimBlock *animBlock = GetAnimationBlock(name); + if(animBlock == nil){ + animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; + strncpy(animBlock->name, name, MAX_ANIMBLOCK_NAME); + animBlock->numAnims = 0; + assert(animBlock->refCount == 0); + } + return animBlock - ms_aAnimBlocks; +} + +int32 +CAnimManager::GetNumRefsToAnimBlock(int32 block) +{ + return ms_aAnimBlocks[block].refCount; +} + +void +CAnimManager::AddAnimBlockRef(int32 block) +{ + ms_aAnimBlocks[block].refCount++; +} + +void +CAnimManager::RemoveAnimBlockRefWithoutDelete(int32 block) +{ + ms_aAnimBlocks[block].refCount--; +} + +void +CAnimManager::RemoveAnimBlockRef(int32 block) +{ + ms_aAnimBlocks[block].refCount--; + if(ms_aAnimBlocks[block].refCount == 0) + CStreaming::RemoveAnim(block); +} + +void +CAnimManager::RemoveAnimBlock(int32 block) +{ + int i; + CAnimBlock *animblock; + + animblock = &ms_aAnimBlocks[block]; + debug("Removing ANIMS %s\n", animblock->name); + for(i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++) + if(ms_aAnimAssocGroups[i].animBlock == animblock) + ms_aAnimAssocGroups[i].DestroyAssociations(); + for(i = 0; i < animblock->numAnims; i++) + ms_aAnimations[animblock->firstIndex + i].Shutdown(); + animblock->isLoaded = false; + animblock->refCount = 0; +} + +CAnimBlendHierarchy* +CAnimManager::GetAnimation(const char *name, CAnimBlock *animBlock) +{ + int i; + CAnimBlendHierarchy *hier = &ms_aAnimations[animBlock->firstIndex]; + + for(i = 0; i < animBlock->numAnims; i++){ + if(strcasecmp(hier->name, name) == 0) + 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; + } + return found; +} + +void +CAnimManager::LoadAnimFiles(void) +{ + LoadAnimFile("ANIM\\PED.IFP"); + ms_aAnimAssocGroups = new CAnimBlendAssocGroup[NUM_ANIM_ASSOC_GROUPS]; + CreateAnimAssocGroups(); +} + +void +CAnimManager::CreateAnimAssocGroups(void) +{ + int i, j; + + for(i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++){ + CAnimBlock *block = GetAnimationBlock(ms_aAnimAssocDefinitions[i].blockName); + if(block == nil || !block->isLoaded || ms_aAnimAssocGroups[i].assocList) + continue; + + 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->groupId = i; + group->firstAnimId = def->animDescs[0].animId; + group->CreateAssociations(def->blockName, clump, def->animNames, def->numAnims); + for(j = 0; j < group->numAssociations; j++) + // GetAnimation(i) in III (but it's in LoadAnimFiles), GetAnimation(group->animDesc[j].animId) in VC + group->GetAnimation(def->animDescs[j].animId)->flags |= def->animDescs[j].flags; + if(IsClumpSkinned(clump)) + RpClumpForAllAtomics(clump, AtomicRemoveAnimFromSkinCB, nil); + RpClumpDestroy(clump); + } +} + +void +CAnimManager::LoadAnimFile(const char *filename) +{ + RwStream *stream; + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, filename); + assert(stream); + LoadAnimFile(stream, true); + RwStreamClose(stream, nil); +} + +void +CAnimManager::LoadAnimFile(RwStream *stream, bool compress, char (*uncompressedAnims)[32]) +{ + #define ROUNDSIZE(x) if((x) & 3) (x) += 4 - ((x)&3) + struct IfpHeader { + char ident[4]; + uint32 size; + }; + IfpHeader anpv; + char buf[256]; + int j, k, l; + float *fbuf = (float*)buf; + + RwStreamRead(stream, &anpv, sizeof(IfpHeader)); + assert(memcmp(anpv.ident, "ANPV", 4) == 0); + + // block name + RwStreamRead(stream, buf, anpv.size); + int32_t numAnims; + RwStreamRead(stream, &numAnims, sizeof(numAnims)); + CAnimBlock *animBlock = GetAnimationBlock(buf); + if(animBlock){ + if(animBlock->numAnims == 0){ + animBlock->numAnims = numAnims; + animBlock->firstIndex = ms_numAnimations; + } + }else{ + animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; + strncpy(animBlock->name, buf, MAX_ANIMBLOCK_NAME); + animBlock->numAnims = numAnims; + animBlock->firstIndex = ms_numAnimations; + } + + debug("Loading ANIMS %s\n", animBlock->name); + + animBlock->isLoaded = true; + + int animIndex = animBlock->firstIndex; + for(j = 0; j < animBlock->numAnims; j++){ + assert(animIndex < ARRAY_SIZE(ms_aAnimations)); + CAnimBlendHierarchy *hier = &ms_aAnimations[animIndex++]; + + int32_t animNameLength; + // animation name + RwStreamRead(stream, &animNameLength, sizeof(animNameLength)); + RwStreamRead(stream, buf, animNameLength); + hier->SetName(buf); + + int32_t numSeqs; + RwStreamRead(stream, &numSeqs, sizeof(numSeqs)); + hier->numSequences = numSeqs; + 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 + int32_t seqNameLength; + RwStreamRead(stream, &seqNameLength, sizeof(seqNameLength)); + RwStreamRead(stream, buf, seqNameLength); + seq->SetName(buf); + + int32_t numFrames; + RwStreamRead(stream, &numFrames, sizeof(numFrames)); + seq->numFrames = numFrames; + int32_t boneTag; + RwStreamRead(stream, &boneTag, sizeof(boneTag)); + seq->SetBoneTag(boneTag); + + if(numFrames == 0) + continue; + + uint32_t dataSize; + RwStreamRead(stream, &dataSize, sizeof(dataSize)); + uint16_t flags; + RwStreamRead(stream, &flags, sizeof(flags)); + + seq->keyFrames = RwMalloc(dataSize); + assert(seq->keyFrames); + RwStreamRead(stream, seq->keyFrames, dataSize - sizeof(flags)); + seq->type = flags; + + if(strstr(seq->name, "L Toe")) + debug("anim %s has toe keyframes\n", hier->name); // BUG: seq->name + } + hier->CalcTotalTime(); + } + if(animIndex > ms_numAnimations) + ms_numAnimations = animIndex; +} + +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].Shutdown(); + ms_aAnimBlocks[ms_numAnimBlocks].isLoaded = false; +} diff --git a/src/miami/animation/AnimManager.h b/src/miami/animation/AnimManager.h new file mode 100644 index 00000000..f1a58b1a --- /dev/null +++ b/src/miami/animation/AnimManager.h @@ -0,0 +1,143 @@ +#pragma once + +#include "AnimBlendHierarchy.h" +#include "AnimationId.h" + +enum AssocGroupId +{ + ASSOCGRP_STD, + ASSOCGRP_VAN, + ASSOCGRP_COACH, + ASSOCGRP_BIKE_STANDARD, + ASSOCGRP_BIKE_VESPA, + ASSOCGRP_BIKE_HARLEY, + ASSOCGRP_BIKE_DIRT, + ASSOCGRP_UNARMED, + ASSOCGRP_SCREWDRIVER, + ASSOCGRP_KNIFE, + ASSOCGRP_BASEBALLBAT, + ASSOCGRP_GOLFCLUB, + ASSOCGRP_CHAINSAW, + ASSOCGRP_PYTHON, + ASSOCGRP_COLT, + ASSOCGRP_SHOTGUN, + ASSOCGRP_BUDDY, + ASSOCGRP_TEC, + ASSOCGRP_UZI, + ASSOCGRP_RIFLE, + ASSOCGRP_M60, + ASSOCGRP_SNIPER, + ASSOCGRP_THROW, + ASSOCGRP_FLAMETHROWER, + ASSOCGRP_MEDIC, + ASSOCGRP_SUNBATHE, + ASSOCGRP_PLAYER_IDLE, + ASSOCGRP_RIOT, + ASSOCGRP_STRIP, + ASSOCGRP_LANCE, + ASSOCGRP_PLAYER, + ASSOCGRP_PLAYERROCKET, + ASSOCGRP_PLAYER1ARMED, + ASSOCGRP_PLAYER2ARMED, + ASSOCGRP_PLAYERBBBAT, + ASSOCGRP_PLAYERCHAINSAW, + ASSOCGRP_SHUFFLE, + ASSOCGRP_OLD, + ASSOCGRP_GANG1, + ASSOCGRP_GANG2, + ASSOCGRP_FAT, + ASSOCGRP_OLDFAT, + ASSOCGRP_JOGGER, + ASSOCGRP_WOMAN, + ASSOCGRP_WOMANSHOP, + ASSOCGRP_BUSYWOMAN, + ASSOCGRP_SEXYWOMAN, + ASSOCGRP_FATWOMAN, + ASSOCGRP_OLDWOMAN, + ASSOCGRP_JOGWOMAN, + ASSOCGRP_PANICCHUNKY, + ASSOCGRP_SKATE, +#ifdef PC_PLAYER_CONTROLS + ASSOCGRP_PLAYERBACK, + ASSOCGRP_PLAYERLEFT, + ASSOCGRP_PLAYERRIGHT, + ASSOCGRP_ROCKETBACK, + ASSOCGRP_ROCKETLEFT, + ASSOCGRP_ROCKETRIGHT, + ASSOCGRP_CHAINSAWBACK, + ASSOCGRP_CHAINSAWLEFT, + ASSOCGRP_CHAINSAWRIGHT, +#endif + + NUM_ANIM_ASSOC_GROUPS +}; + +class CAnimBlendAssociation; +class CAnimBlendAssocGroup; + +#define MAX_ANIMBLOCK_NAME 20 + +// A block of hierarchies +struct CAnimBlock +{ + char name[MAX_ANIMBLOCK_NAME]; + bool isLoaded; + int16 refCount; + int32 firstIndex; // first animtion in ms_aAnimations + 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 CAnimBlock *GetAnimationBlock(int32 block) { return &ms_aAnimBlocks[block]; } + static CAnimBlock *GetAnimationBlock(const char *name); + static int32 GetAnimationBlockIndex(const char *name); + static int32 RegisterAnimBlock(const char *name); + static int32 GetNumRefsToAnimBlock(int32 block); + static void AddAnimBlockRef(int32 block); + static void RemoveAnimBlockRefWithoutDelete(int32 block); + static void RemoveAnimBlockRef(int32 block); + static void RemoveAnimBlock(int32 block); + 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(RwStream *stream, bool compress, char (*uncompressedAnims)[32] = nil); + static void CreateAnimAssocGroups(void); + static void RemoveLastAnimFile(void); + static CAnimBlendAssocGroup* GetAnimAssocGroups(void) { return ms_aAnimAssocGroups; } +}; diff --git a/src/miami/animation/AnimationId.h b/src/miami/animation/AnimationId.h new file mode 100644 index 00000000..0b5d8d8e --- /dev/null +++ b/src/miami/animation/AnimationId.h @@ -0,0 +1,287 @@ +#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 */ + 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_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, + + /* names made up */ + ANIM_STD_FIGHT_JAB, + ANIM_STD_FIGHT_ELBOW_L, + ANIM_STD_FIGHT_ELBOW_R, + ANIM_STD_FIGHT_BKICK_L, + ANIM_STD_FIGHT_BKICK_R, + /**/ + + ANIM_STD_DETONATE, + ANIM_STD_PUNCH, + ANIM_STD_PARTIALPUNCH, + ANIM_STD_KICKGROUND, + + ANIM_STD_THROW_UNDER, + ANIM_STD_FIGHT_SHUFFLE_B, + + 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_CAR_JUMP_IN_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_DRIVEBY_LEFT_LO, + ANIM_STD_CAR_DRIVEBY_RIGHT_LO, + ANIM_STD_CAR_LOOKBEHIND, + ANIM_STD_BOAT_DRIVE, + ANIM_STD_BOAT_DRIVE_LEFT, + ANIM_STD_BOAT_DRIVE_RIGHT, + ANIM_STD_BOAT_LOOKBEHIND, + + ANIM_STD_BIKE_PICKUP_LHS, + ANIM_STD_BIKE_PICKUP_RHS, + ANIM_STD_BIKE_PULLUP_LHS, + ANIM_STD_BIKE_PULLUP_RHS, + ANIM_STD_BIKE_ELBOW_LHS, + ANIM_STD_BIKE_ELBOW_RHS, + ANIM_STD_BIKE_FALLOFF, + ANIM_STD_BIKE_FALLBACK, + + ANIM_STD_GETOUT_RHS, + ANIM_STD_GETOUT_LO_RHS, + ANIM_STD_CAR_CLOSE_RHS, + ANIM_STD_CAR_HOOKERTALK, + + ANIM_STD_TRAIN_GETIN, + ANIM_STD_TRAIN_GETOUT, + + ANIM_STD_CRAWLOUT_LHS, + ANIM_STD_CRAWLOUT_RHS, + ANIM_STD_ROLLOUT_LHS, + ANIM_STD_ROLLOUT_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_FALL_ONBACK, + ANIM_STD_FALL_ONFRONT, + + 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_STD_DUCK_DOWN, + ANIM_STD_DUCK_LOW, + + ANIM_STD_DUCK_WEAPON, + + ANIM_STD_RBLOCK_SHOOT, + ANIM_STD_HANDSUP, + ANIM_STD_HANDSCOWER, + ANIM_STD_PARTIAL_FUCKU, + ANIM_STD_PHONE_IN, + ANIM_STD_PHONE_OUT, + ANIM_STD_PHONE_TALK, + + ANIM_STD_SEAT_DOWN, + ANIM_STD_SEAT_UP, + ANIM_STD_SEAT_IDLE, + ANIM_STD_SEAT_RVRS, + ANIM_STD_ATM, + ANIM_STD_ABSEIL, + + ANIM_STD_NUM, + + 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_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_BIKE_RIDE, + ANIM_BIKE_READY, + ANIM_BIKE_LEFT, + ANIM_BIKE_RIGHT, + ANIM_BIKE_LEANB, + ANIM_BIKE_LEANF, + ANIM_BIKE_WALKBACK, + ANIM_BIKE_JUMPON_LHS, + ANIM_BIKE_JUMPON_RHS, + ANIM_BIKE_KICK, + ANIM_BIKE_HIT, + ANIM_BIKE_GETOFF_LHS, + ANIM_BIKE_GETOFF_RHS, + ANIM_BIKE_GETOFF_BACK, + ANIM_BIKE_DRIVEBY_LHS, + ANIM_BIKE_DRIVEBY_RHS, + ANIM_BIKE_DRIVEBY_FORWARD, + ANIM_BIKE_RIDE_P, + + ANIM_ATTACK_1, + ANIM_ATTACK_2, + ANIM_ATTACK_EXTRA1, + ANIM_ATTACK_EXTRA2, + ANIM_ATTACK_3, + + // our synonyms... because originals are hard to understand + ANIM_WEAPON_FIRE = ANIM_ATTACK_1, + ANIM_WEAPON_CROUCHFIRE, + ANIM_WEAPON_FIRE_2ND = ANIM_WEAPON_CROUCHFIRE, + ANIM_WEAPON_RELOAD, + ANIM_WEAPON_CROUCHRELOAD, + ANIM_WEAPON_FIRE_3RD, + ANIM_THROWABLE_THROW = ANIM_ATTACK_1, + ANIM_THROWABLE_THROWU, + ANIM_THROWABLE_START_THROW, + ANIM_MELEE_ATTACK = ANIM_ATTACK_1, + ANIM_MELEE_ATTACK_2ND, + ANIM_MELEE_ATTACK_START, + ANIM_MELEE_IDLE_FIGHTMODE, + ANIM_MELEE_ATTACK_FINISH, + + ANIM_SUNBATHE_IDLE, + ANIM_SUNBATHE_DOWN, + ANIM_SUNBATHE_UP, + ANIM_SUNBATHE_ESCAPE, + + ANIM_MEDIC_CPR, + + ANIM_PLAYER_IDLE1, + ANIM_PLAYER_IDLE2, + ANIM_PLAYER_IDLE3, + ANIM_PLAYER_IDLE4, + + ANIM_RIOT_ANGRY, + ANIM_RIOT_ANGRY_B, + ANIM_RIOT_CHANT, + ANIM_RIOT_PUNCHES, + ANIM_RIOT_SHOUT, + ANIM_RIOT_CHALLENGE, + ANIM_RIOT_FUCKYOU, + + ANIM_STRIP_A, + ANIM_STRIP_B, + ANIM_STRIP_C, + ANIM_STRIP_D, + ANIM_STRIP_E, + ANIM_STRIP_F, + ANIM_STRIP_G, +}; \ No newline at end of file diff --git a/src/miami/animation/Bones.cpp b/src/miami/animation/Bones.cpp new file mode 100644 index 00000000..87f3b6e7 --- /dev/null +++ b/src/miami/animation/Bones.cpp @@ -0,0 +1,59 @@ +#include "common.h" +#include "PedModelInfo.h" +#include "Bones.h" + +int +ConvertPedNode2BoneTag(int node) +{ + switch(node){ + case PED_MID: return BONE_spine1; + case PED_HEAD: return BONE_head; + case PED_UPPERARML: return BONE_l_upperarm; + case PED_UPPERARMR: return BONE_r_upperarm; + case PED_HANDL: return BONE_l_hand; + case PED_HANDR: return BONE_r_hand; + case PED_UPPERLEGL: return BONE_l_thigh; + case PED_UPPERLEGR: return BONE_r_thigh; + case PED_FOOTL: return BONE_l_foot; + case PED_FOOTR: return BONE_r_foot; + case PED_LOWERLEGR: return BONE_r_calf; + case PED_LOWERLEGL: return BONE_l_calf; + case PED_FOREARML: return BONE_l_forearm; + case PED_FOREARMR: return BONE_r_forearm; + case PED_CLAVICLEL: return BONE_l_clavicle; + case PED_CLAVICLER: return BONE_r_clavicle; + case PED_NECK: return BONE_neck; + } + assert(0 && "this node has no bone"); + return -1; +} + +const char* +ConvertBoneTag2BoneName(int tag) +{ + switch(tag){ + case BONE_root: return "Root"; + case BONE_pelvis: return "Pelvis"; + case BONE_spine: return "Spine"; + case BONE_spine1: return "Spine1"; + case BONE_neck: return "Neck"; + case BONE_head: return "Head"; + case BONE_r_clavicle: return "Bip01 R Clavicle"; + case BONE_r_upperarm: return "R UpperArm"; + case BONE_r_forearm: return "R Forearm"; + case BONE_r_hand: return "R Hand"; + case BONE_r_finger: return "R Fingers"; + case BONE_l_clavicle: return "Bip01 L Clavicle"; + case BONE_l_upperarm: return "L UpperArm"; + case BONE_l_forearm: return "L Forearm"; + case BONE_l_hand: return "L Hand"; + case BONE_l_finger: return "L Fingers"; + case BONE_l_thigh: return "L Thigh"; + case BONE_l_calf: return "L Calf"; + case BONE_l_foot: return "L Foot"; + case BONE_r_thigh: return "R Thigh"; + case BONE_r_calf: return "R Calf"; + case BONE_r_foot: return "R Foot"; + } + return nil; +} diff --git a/src/miami/animation/Bones.h b/src/miami/animation/Bones.h new file mode 100644 index 00000000..e133fd7f --- /dev/null +++ b/src/miami/animation/Bones.h @@ -0,0 +1,30 @@ +#pragma once + +enum BoneTag +{ + BONE_root = 0, + BONE_pelvis = 1, + BONE_spine = 2, + BONE_spine1 = 3, + BONE_neck = 4, + BONE_head = 5, + BONE_l_clavicle = 31, + BONE_l_upperarm = 32, + BONE_l_forearm = 33, + BONE_l_hand = 34, + BONE_l_finger = 35, + BONE_r_clavicle = 21, + BONE_r_upperarm = 22, + BONE_r_forearm = 23, + BONE_r_hand = 24, + BONE_r_finger = 25, + BONE_l_thigh = 41, + BONE_l_calf = 42, + BONE_l_foot = 43, + BONE_r_thigh = 51, + BONE_r_calf = 52, + BONE_r_foot = 53, +}; + +int ConvertPedNode2BoneTag(int node); +const char *ConvertBoneTag2BoneName(int tag); diff --git a/src/miami/animation/CutsceneMgr.cpp b/src/miami/animation/CutsceneMgr.cpp new file mode 100644 index 00000000..b40a8906 --- /dev/null +++ b/src/miami/animation/CutsceneMgr.cpp @@ -0,0 +1,669 @@ +#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 "RpAnimBlend.h" +#include "ModelIndices.h" +#include "TempColModels.h" +#include "ColStore.h" +#include "Radar.h" +#include "Pools.h" + +const struct { + const char *szTrackName; + int iTrackId; +} musicNameIdAssoc[] = { + { "ASS_1", STREAMED_SOUND_CUTSCENE_ASS_1 }, + { "ASS_2", STREAMED_SOUND_CUTSCENE_ASS_2 }, + { "BANK_1", STREAMED_SOUND_CUTSCENE_BANK_1 }, + { "BANK_2A", STREAMED_SOUND_CUTSCENE_BANK_2A }, + { "BANK_2B", STREAMED_SOUND_CUTSCENE_BANK_2B }, + { "BANK_3A", STREAMED_SOUND_CUTSCENE_BANK_3A }, + { "BANK_3B", STREAMED_SOUND_CUTSCENE_BANK_3B }, + { "BANK_4", STREAMED_SOUND_CUTSCENE_BANK_4 }, + { "BIKE_1", STREAMED_SOUND_CUTSCENE_BIKE_1 }, + { "BIKE_2", STREAMED_SOUND_CUTSCENE_BIKE_2 }, + { "BIKE_3", STREAMED_SOUND_CUTSCENE_BIKE_3 }, + { "BUD_1", STREAMED_SOUND_CUTSCENE_BUD_1 }, + { "BUD_2", STREAMED_SOUND_CUTSCENE_BUD_2 }, + { "BUD_3", STREAMED_SOUND_CUTSCENE_BUD_3 }, + { "CAP_1", STREAMED_SOUND_CUTSCENE_CAP_1 }, + { "CAR_1", STREAMED_SOUND_CUTSCENE_CAR_1 }, + { "CNT_1A", STREAMED_SOUND_CUTSCENE_CNT_1A }, + { "CNT_1B", STREAMED_SOUND_CUTSCENE_CNT_1B }, + { "CNT_2", STREAMED_SOUND_CUTSCENE_CNT_2 }, + { "COK_1", STREAMED_SOUND_CUTSCENE_COK_1 }, + { "COK_2A", STREAMED_SOUND_CUTSCENE_COK_2A }, + { "COK_2B", STREAMED_SOUND_CUTSCENE_COK_2B }, + { "COK_3", STREAMED_SOUND_CUTSCENE_COK_3 }, + { "COK_4A", STREAMED_SOUND_CUTSCENE_COK_4A }, + { "COK_4A2", STREAMED_SOUND_CUTSCENE_COK_4A2 }, + { "COK_4B", STREAMED_SOUND_CUTSCENE_COK_4B }, + { "COL_1", STREAMED_SOUND_CUTSCENE_COL_1 }, + { "COL_2", STREAMED_SOUND_CUTSCENE_COL_2 }, + { "COL_3A", STREAMED_SOUND_CUTSCENE_COL_3A }, + { "COL_4A", STREAMED_SOUND_CUTSCENE_COL_4A }, + { "COL_5A", STREAMED_SOUND_CUTSCENE_COL_5A }, + { "COL_5B", STREAMED_SOUND_CUTSCENE_COL_5B }, + { "CUB_1", STREAMED_SOUND_CUTSCENE_CUB_1 }, + { "CUB_2", STREAMED_SOUND_CUTSCENE_CUB_2 }, + { "CUB_3", STREAMED_SOUND_CUTSCENE_CUB_3 }, + { "CUB_4", STREAMED_SOUND_CUTSCENE_CUB_4 }, + { "DRUG_1", STREAMED_SOUND_CUTSCENE_DRUG_1 }, + { "FIN", STREAMED_SOUND_CUTSCENE_FIN }, + { "FIN_2", STREAMED_SOUND_CUTSCENE_FIN2 }, + { "FINALE", STREAMED_SOUND_CUTSCENE_FINALE }, + { "HAT_1", STREAMED_SOUND_CUTSCENE_HAT_1 }, + { "HAT_2", STREAMED_SOUND_CUTSCENE_HAT_2 }, + { "HAT_3", STREAMED_SOUND_CUTSCENE_HAT_3 }, + { "ICE_1", STREAMED_SOUND_CUTSCENE_ICE_1 }, + { "INT_A", STREAMED_SOUND_CUTSCENE_INT_A }, + { "INT_B", STREAMED_SOUND_CUTSCENE_INT_B }, + { "INT_D", STREAMED_SOUND_CUTSCENE_INT_D }, + { "INT_M", STREAMED_SOUND_CUTSCENE_INT_M }, + { "LAW_1A", STREAMED_SOUND_CUTSCENE_LAW_1A }, + { "LAW_1B", STREAMED_SOUND_CUTSCENE_LAW_1B }, + { "LAW_2A", STREAMED_SOUND_CUTSCENE_LAW_2A }, + { "LAW_2B", STREAMED_SOUND_CUTSCENE_LAW_2B }, + { "LAW_2C", STREAMED_SOUND_CUTSCENE_LAW_2C }, + { "LAW_3", STREAMED_SOUND_CUTSCENE_LAW_3 }, + { "LAW_4", STREAMED_SOUND_CUTSCENE_LAW_4 }, + { "PHIL_1", STREAMED_SOUND_CUTSCENE_PHIL_1 }, + { "PHIL_2", STREAMED_SOUND_CUTSCENE_PHIL_2 }, + { "PORN_1", STREAMED_SOUND_CUTSCENE_PORN_1 }, + { "PORN_2", STREAMED_SOUND_CUTSCENE_PORN_2 }, + { "PORN_3", STREAMED_SOUND_CUTSCENE_PORN_3 }, + { "PORN_4", STREAMED_SOUND_CUTSCENE_PORN_4 }, + { "RESC_1A", STREAMED_SOUND_CUTSCENE_RESC_1A }, + { "ROK_1", STREAMED_SOUND_CUTSCENE_ROK_1 }, + { "ROK_2", STREAMED_SOUND_CUTSCENE_ROK_2 }, + { "ROK_3A", STREAMED_SOUND_CUTSCENE_ROK_3A }, + { "STRIPA", STREAMED_SOUND_CUTSCENE_STRIPA }, + { "TAX_1", STREAMED_SOUND_CUTSCENE_TAX_1 }, + { "TEX_1", STREAMED_SOUND_CUTSCENE_TEX_1 }, + { "TEX_2", STREAMED_SOUND_CUTSCENE_TEX_2 }, + { "TEX_3", STREAMED_SOUND_CUTSCENE_TEX_3 }, + { "GSPOT", STREAMED_SOUND_CUTSCENE_GLIGHT }, + { "FIST", STREAMED_SOUND_CUTSCENE_FIST }, + { "EL_PH1", STREAMED_SOUND_CUTSCENE_ELBURRO1_PH1 }, + { "EL_PH2", STREAMED_SOUND_CUTSCENE_ELBURRO2_PH2 }, + { 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; +bool CCutsceneMgr::ms_wasCutsceneSkipped; +uint32 CCutsceneMgr::ms_cutsceneLoadStatus; +bool CCutsceneMgr::ms_useCutsceneShadows = true; + +bool bCamLoaded; +bool bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver; // pls don't shrink the name :P +int32 NumberOfSavedWeapons; +eWeaponType SavedWeaponIDs[TOTAL_WEAPON_SLOTS]; +int32 SavedWeaponAmmo[TOTAL_WEAPON_SLOTS]; +char uncompressedAnims[8][32]; +uint32 numUncompressedAnims; + + +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_wasCutsceneSkipped = false; + ms_running = false; + ms_useLodMultiplier = false; + ms_animLoaded = false; + ms_cutsceneProcessing = false; + + ms_pCutsceneDir = new CDirectory(CUTSCENEDIRSIZE); + ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR"); + + numUncompressedAnims = 0; + uncompressedAnims[0][0] = '\0'; +} + +void +CCutsceneMgr::Shutdown(void) +{ + delete ms_pCutsceneDir; +} + +void +CCutsceneMgr::LoadCutsceneData(const char *szCutsceneName) +{ + int file; + uint32 size; + uint32 offset; + CPlayerPed *pPlayerPed; + + ms_cutsceneProcessing = true; + ms_wasCutsceneSkipped = false; + CTimer::Suspend(); + if (!bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver) + CStreaming::RemoveCurrentZonesModels(); + + ms_pCutsceneDir->numEntries = 0; + ms_pCutsceneDir->ReadDirFile("ANIM\\CUTS.DIR"); + + CStreaming::RemoveUnusedModelsInLoadedList(); + CGame::DrasticTidyUpMemory(true); + + strcpy(ms_cutsceneName, szCutsceneName); + + RwStream *stream; + stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "ANIM\\CUTS.IMG"); + assert(stream); + + // Load animations + sprintf(gString, "%s.IFP", szCutsceneName); + if (ms_pCutsceneDir->FindItem(gString, offset, size)) { + CStreaming::MakeSpaceFor(size << 11); + CStreaming::ImGonnaUseStreamingMemory(); + RwStreamSkip(stream, offset << 11); + CAnimManager::LoadAnimFile(stream, true, uncompressedAnims); + ms_cutsceneAssociations.CreateAssociations(szCutsceneName); + CStreaming::IHaveUsedStreamingMemory(); + ms_animLoaded = true; + } else { + ms_animLoaded = false; + } + RwStreamClose(stream, nil); + + // Load camera data + file = CFileMgr::OpenFile("ANIM\\CUTS.IMG", "rb"); + sprintf(gString, "%s.DAT", szCutsceneName); + if (ms_pCutsceneDir->FindItem(gString, offset, size)) { + CStreaming::ImGonnaUseStreamingMemory(); + CFileMgr::Seek(file, offset << 11, SEEK_SET); + TheCamera.LoadPathSplines(file); + CStreaming::IHaveUsedStreamingMemory(); + bCamLoaded = true; + } else { + bCamLoaded = false; + } + + CFileMgr::CloseFile(file); + + if (CGeneral::faststricmp(ms_cutsceneName, "finale")) { + 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(); + 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); + + CTimer::Resume(); +} + +void +CCutsceneMgr::FinishCutscene() +{ + ms_wasCutsceneSkipped = true; + if (bCamLoaded) { + CCutsceneMgr::ms_cutsceneTimer = TheCamera.GetCutSceneFinishTime() * 0.001f; + TheCamera.FinishCutscene(); + } + + FindPlayerPed()->bIsVisible = true; + CWorld::Players[CWorld::PlayerInFocus].MakePlayerSafe(false); +} + +void +CCutsceneMgr::SetupCutsceneToStart(void) +{ + if (bCamLoaded) { + 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()); + if (ms_pCutsceneObjects[i]->m_pAttachTo != nil) { + pAnimBlendAssoc->flags &= (~ASSOC_HAS_TRANSLATION); + } else { + ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset + pAnimBlendAssoc->hierarchy->sequences[0].GetStartTranslation()); + } + pAnimBlendAssoc->SetRun(); + } else { + ms_pCutsceneObjects[i]->SetPosition(ms_cutsceneOffset); + } + CWorld::Add(ms_pCutsceneObjects[i]); + if (RwObjectGetType(ms_pCutsceneObjects[i]->m_rwObject) == rpCLUMP) { + ms_pCutsceneObjects[i]->UpdateRpHAnim(); + } + } + + 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); + debug("Give cutscene anim %s\n", animName); + RpAnimBlendClumpRemoveAllAssociations((RpClump*)pObject->m_rwObject); + + pNewAnim = ms_cutsceneAssociations.GetAnimation(animName); + if (!pNewAnim) { + debug("\n\nHaven't I told you I can't find the fucking animation %s\n\n\n", animName); + return; + } + + CStreaming::ImGonnaUseStreamingMemory(); + pNewAnim = ms_cutsceneAssociations.CopyAnimation(animName); + CStreaming::IHaveUsedStreamingMemory(); + + pNewAnim->SetCurrentTime(0.0f); + pNewAnim->flags |= ASSOC_HAS_TRANSLATION; + pNewAnim->flags &= ~ASSOC_RUNNING; + + pAnimBlendClumpData = *RPANIMBLENDCLUMPDATA(pObject->m_rwObject); + pAnimBlendClumpData->link.Prepend(&pNewAnim->link); +} + +void +CCutsceneMgr::SetCutsceneAnimToLoop(const char* animName) +{ + ms_cutsceneAssociations.GetAnimation(animName)->flags |= ASSOC_REPEAT; +} + +CCutsceneHead * +CCutsceneMgr::AddCutsceneHead(CObject *pObject, int modelId) +{ + return nil; +} + +void UpdateCutsceneObjectBoundingBox(RpClump* clump, int modelId) +{ + if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05) { + CColModel* pColModel = &CTempColModels::ms_colModelCutObj[modelId - MI_CUTOBJ01]; + float radius = 0.0f; + RpClumpForAllAtomics(clump, CalculateBoundingSphereRadiusCB, &radius); + pColModel->boundingSphere.radius = radius; + pColModel->boundingBox.min = CVector(-radius, -radius, -radius); + pColModel->boundingBox.max = CVector(radius, radius, radius); + } +} + +CCutsceneObject * +CCutsceneMgr::CreateCutsceneObject(int modelId) +{ + CBaseModelInfo *pModelInfo; + CColModel *pColModel; + CCutsceneObject *pCutsceneObject; + + CStreaming::ImGonnaUseStreamingMemory(); + debug("Created cutscene object %s\n", CModelInfo::GetModelInfo(modelId)->GetModelName()); + if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05) { + pModelInfo = CModelInfo::GetModelInfo(modelId); + pColModel = &CTempColModels::ms_colModelCutObj[modelId - MI_CUTOBJ01]; + pModelInfo->SetColModel(pColModel); + UpdateCutsceneObjectBoundingBox((RpClump*)pModelInfo->GetRwObject(), modelId); + } else if (modelId >= MI_SPECIAL01 && modelId <= MI_SPECIAL21) { + pModelInfo = CModelInfo::GetModelInfo(modelId); + if (pModelInfo->GetColModel() == &CTempColModels::ms_colModelPed1) { + CColModel *colModel = new CColModel(); + colModel->boundingSphere.radius = 2.0f; + colModel->boundingSphere.center = CVector(0.0f, 0.0f, 0.0f); + pModelInfo->SetColModel(colModel, true); + } + pColModel = pModelInfo->GetColModel(); + float radius = 2.0f; + pColModel->boundingSphere.radius = radius; + pColModel->boundingBox.min = CVector(-radius, -radius, -radius); + pColModel->boundingBox.max = CVector(radius, radius, radius); + } + + pCutsceneObject = new CCutsceneObject(); + pCutsceneObject->SetModelIndex(modelId); + if (ms_useCutsceneShadows) + pCutsceneObject->CreateShadow(); + ms_pCutsceneObjects[ms_numCutsceneObjs++] = pCutsceneObject; + CStreaming::IHaveUsedStreamingMemory(); + return pCutsceneObject; +} + +void +CCutsceneMgr::DeleteCutsceneData(void) +{ + if (!ms_loaded) return; + CTimer::Suspend(); + + ms_cutsceneProcessing = false; + ms_useLodMultiplier = false; + ms_useCutsceneShadows = true; + + 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; + + for (int i = MI_SPECIAL01; i < MI_SPECIAL21; i++) { + CBaseModelInfo *minfo = CModelInfo::GetModelInfo(i); + CColModel *colModel = minfo->GetColModel(); + if (colModel != &CTempColModels::ms_colModelPed1) { + delete colModel; + minfo->SetColModel(&CTempColModels::ms_colModelPed1); + } + } + + if (ms_animLoaded) + CAnimManager::RemoveLastAnimFile(); + + ms_animLoaded = false; + numUncompressedAnims = 0; + uncompressedAnims[0][0] = '\0'; + + if (bCamLoaded) { + TheCamera.RestoreWithJumpCut(); + TheCamera.SetWideScreenOff(); + TheCamera.DeleteCutSceneCamDataMemory(); + } + 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, "finale")) { + DMAudio.StopCutSceneMusic(); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + } + + CStreaming::ms_disableStreaming = false; + CWorld::bProcessCutsceneOnly = false; + + if(bCamLoaded) + CGame::DrasticTidyUpMemory(TheCamera.GetScreenFadeStatus() == FADE_2); + + CPad::GetPad(0)->Clear(false); + if (bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver) { + CStreaming::LoadInitialPeds(); + CStreaming::LoadInitialWeapons(); + CStreaming::LoadInitialVehicles(); + bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver = false; + + CPlayerPed *pPlayerPed = FindPlayerPed(); + for (int i = 0; i < NumberOfSavedWeapons; i++) { + int32 weaponModelId = CWeaponInfo::GetWeaponInfo(SavedWeaponIDs[i])->m_nModelId; + uint8 flags = CStreaming::ms_aInfoForModel[weaponModelId].m_flags; + CStreaming::RequestModel(weaponModelId, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + if (CWeaponInfo::GetWeaponInfo(SavedWeaponIDs[i])->m_nModel2Id != -1) { + CStreaming::RequestModel(CWeaponInfo::GetWeaponInfo(SavedWeaponIDs[i])->m_nModel2Id, 0); + CStreaming::LoadAllRequestedModels(false); + } + if (!(flags & STREAMFLAGS_DONT_REMOVE)) + CStreaming::SetModelIsDeletable(weaponModelId); + pPlayerPed->GiveWeapon(SavedWeaponIDs[i], SavedWeaponAmmo[i], true); + } + NumberOfSavedWeapons = 0; + } + + CTimer::Resume(); +} + +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, "finale")) + 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(); + + for (int i = 0; i < ms_numCutsceneObjs; i++) { + int modelId = ms_pCutsceneObjects[i]->GetModelIndex(); + if (modelId >= MI_CUTOBJ01 && modelId <= MI_CUTOBJ05) + UpdateCutsceneObjectBoundingBox(ms_pCutsceneObjects[i]->GetClump(), modelId); + + if (ms_pCutsceneObjects[i]->m_pAttachTo != nil && modelId >= MI_SPECIAL01 && modelId <= MI_SPECIAL21) + UpdateCutsceneObjectBoundingBox(ms_pCutsceneObjects[i]->GetClump(), modelId); + } + + if (bCamLoaded) + if (CGeneral::faststricmp(ms_cutsceneName, "finale") && 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 !bCamLoaded || TheCamera.GetPositionAlongSpline() == 1.0f; } + +void +CCutsceneMgr::LoadAnimationUncompressed(char const* name) +{ + strcpy(uncompressedAnims[numUncompressedAnims], name); + + // Because that's how CAnimManager knows the end of array + ++numUncompressedAnims; + assert(numUncompressedAnims < ARRAY_SIZE(uncompressedAnims)); + uncompressedAnims[numUncompressedAnims][0] = '\0'; +} + +void +CCutsceneMgr::AttachObjectToParent(CObject *pObject, CEntity *pAttachTo) +{ + ((CCutsceneObject*)pObject)->m_pAttachmentObject = nil; + ((CCutsceneObject*)pObject)->m_pAttachTo = RpClumpGetFrame(pAttachTo->GetClump()); + + debug("Attach %s to %s\n", CModelInfo::GetModelInfo(pObject->GetModelIndex())->GetModelName(), CModelInfo::GetModelInfo(pAttachTo->GetModelIndex())->GetModelName()); +} + +void +CCutsceneMgr::AttachObjectToFrame(CObject *pObject, CEntity *pAttachTo, const char *frame) +{ + ((CCutsceneObject*)pObject)->m_pAttachmentObject = nil; + ((CCutsceneObject*)pObject)->m_pAttachTo = RpAnimBlendClumpFindFrame(pAttachTo->GetClump(), frame)->frame; + debug("Attach %s to component %s of %s\n", + CModelInfo::GetModelInfo(pObject->GetModelIndex())->GetModelName(), + frame, + CModelInfo::GetModelInfo(pAttachTo->GetModelIndex())->GetModelName()); + if (RwObjectGetType(pObject->m_rwObject) == rpCLUMP) { + RpClump *clump = (RpClump*)pObject->m_rwObject; + if (IsClumpSkinned(clump)) + RpAtomicGetBoundingSphere(GetFirstAtomic(clump))->radius *= 1.1f; + } +} + +void +CCutsceneMgr::AttachObjectToBone(CObject *pObject, CObject *pAttachTo, int bone) +{ + RpHAnimHierarchy *hanim = GetAnimHierarchyFromSkinClump(pAttachTo->GetClump()); + RwInt32 id = RpHAnimIDGetIndex(hanim, bone); + RwMatrix *matrixArray = RpHAnimHierarchyGetMatrixArray(hanim); + ((CCutsceneObject*)pObject)->m_pAttachmentObject = pAttachTo; + ((CCutsceneObject*)pObject)->m_pAttachTo = &matrixArray[id]; + debug("Attach %s to %s\n", + CModelInfo::GetModelInfo(pObject->GetModelIndex())->GetModelName(), + CModelInfo::GetModelInfo(pAttachTo->GetModelIndex())->GetModelName()); +} + +void +CCutsceneMgr::RemoveEverythingFromTheWorldForTheBiggestFuckoffCutsceneEver() +{ + CStreaming::ms_disableStreaming = true; + CColStore::RemoveAllCollision(); + CWorld::bProcessCutsceneOnly = true; + ms_cutsceneProcessing = true; + + for (int i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--) { + CPed *pPed = CPools::GetPedPool()->GetSlot(i); + if (pPed) { + if (!pPed->IsPlayer() && pPed->CanBeDeleted()) { + CWorld::Remove(pPed); + delete pPed; + } + } + } + + for (int i = CPools::GetVehiclePool()->GetSize() - 1; i >= 0; i--) { + CVehicle *pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (pVehicle) { + if (pVehicle->CanBeDeleted()) { + CWorld::Remove(pVehicle); + delete pVehicle; + } + } + } + + bIsEverythingRemovedFromTheWorldForTheBiggestFuckoffCutsceneEver = true; + CStreaming::RemoveCurrentZonesModels(); + CStreaming::SetModelIsDeletable(MI_MALE01); + CStreaming::SetModelTxdIsDeletable(MI_MALE01); + CStreaming::SetModelIsDeletable(MI_TAXI_D); + CStreaming::SetModelTxdIsDeletable(MI_TAXI_D); + CStreaming::SetModelIsDeletable(MI_NIGHTSTICK); + CStreaming::SetModelTxdIsDeletable(MI_NIGHTSTICK); + CStreaming::SetModelIsDeletable(MI_MISSILE); + CStreaming::SetModelTxdIsDeletable(MI_MISSILE); + CStreaming::SetModelIsDeletable(MI_POLICE); + CStreaming::SetModelTxdIsDeletable(MI_POLICE); + + while (CStreaming::RemoveLoadedVehicle()) ; + + CRadar::RemoveRadarSections(); + + for (int i = CPools::GetDummyPool()->GetSize() - 1; i >= 0; i--) { + CDummy* pDummy = CPools::GetDummyPool()->GetSlot(i); + if (pDummy) + pDummy->DeleteRwObject(); + } + + for (int i = CPools::GetObjectPool()->GetSize() - 1; i >= 0; i--) { + CObject* pObject = CPools::GetObjectPool()->GetSlot(i); + if (pObject) + pObject->DeleteRwObject(); + } + + for (int i = CPools::GetBuildingPool()->GetSize() - 1; i >= 0; i--) { + CBuilding* pBuilding = CPools::GetBuildingPool()->GetSlot(i); + if (pBuilding && pBuilding->m_rwObject != nil && pBuilding->bIsBIGBuilding && pBuilding->bStreamBIGBuilding) { + if (pBuilding->bIsBIGBuilding) + CStreaming::RequestModel(pBuilding->GetModelIndex(), 0); + if (!pBuilding->bImBeingRendered) + pBuilding->DeleteRwObject(); + } + } + + CPlayerPed *pPlayerPed = FindPlayerPed(); + pPlayerPed->RemoveWeaponAnims(0, -1000.0f); + NumberOfSavedWeapons = 0; + + for (int i = 0; i < TOTAL_WEAPON_SLOTS; i++) { + if (pPlayerPed->m_weapons[i].m_eWeaponType != WEAPONTYPE_UNARMED) { + SavedWeaponIDs[NumberOfSavedWeapons] = pPlayerPed->m_weapons[i].m_eWeaponType; + SavedWeaponAmmo[NumberOfSavedWeapons] = pPlayerPed->m_weapons[i].m_nAmmoTotal; + NumberOfSavedWeapons++; + } + } + + pPlayerPed->ClearWeapons(); + CGame::DrasticTidyUpMemory(true); +} \ No newline at end of file diff --git a/src/miami/animation/CutsceneMgr.h b/src/miami/animation/CutsceneMgr.h new file mode 100644 index 00000000..51ef6c04 --- /dev/null +++ b/src/miami/animation/CutsceneMgr.h @@ -0,0 +1,61 @@ +#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_wasCutsceneSkipped; + static bool ms_cutsceneProcessing; + static bool ms_useCutsceneShadows; +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 WasCutsceneSkipped(void) { return ms_wasCutsceneSkipped; } + 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 SetupCutsceneToStart(void); + static void SetCutsceneAnim(const char *animName, CObject *pObject); + static void SetCutsceneAnimToLoop(const char *animName); + static CCutsceneHead *AddCutsceneHead(CObject *pObject, int modelId); + static CCutsceneObject *CreateCutsceneObject(int modelId); + static void DeleteCutsceneData(void); + static void LoadAnimationUncompressed(char const*); + static void Update(void); + + static void AttachObjectToParent(CObject *pObject, CEntity *pAttachTo); + static void AttachObjectToFrame(CObject *pObject, CEntity *pAttachTo, const char *frame); + static void AttachObjectToBone(CObject *pObject, CObject *pAttachTo, int frame); + static void RemoveEverythingFromTheWorldForTheBiggestFuckoffCutsceneEver(); + static void DisableCutsceneShadows() { ms_useCutsceneShadows = false; } +}; diff --git a/src/miami/animation/FrameUpdate.cpp b/src/miami/animation/FrameUpdate.cpp new file mode 100644 index 00000000..727f2692 --- /dev/null +++ b/src/miami/animation/FrameUpdate.cpp @@ -0,0 +1,446 @@ +#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); +} + +void +FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg) +{ + CVector vec, pos(0.0f, 0.0f, 0.0f); + float transBlendAmount = 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; + transBlendAmount += (*node)->association->blendAmount; + } + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else + 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 = transBlendAmount*pos.x; + xform->t.y = transBlendAmount*pos.y; + xform->t.z = transBlendAmount*pos.z; + xform->t.x += (1.0f-transBlendAmount)*frame->resetPos.x; + xform->t.y += (1.0f-transBlendAmount)*frame->resetPos.y; + xform->t.z += (1.0f-transBlendAmount)*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); + if(DotProduct(rot, q) < 0.0f) + rot -= q; + else + 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; + } +} + +void +FrameUpdateCallBackOffscreen(AnimBlendFrameData *frame, void *arg) +{ + if(frame->flag & AnimBlendFrameData::VELOCITY_EXTRACTION && gpAnimBlendClump->velocity2d) + FrameUpdateCallBackWithVelocityExtractionSkinned(frame, arg); +} \ No newline at end of file diff --git a/src/miami/animation/RpAnimBlend.cpp b/src/miami/animation/RpAnimBlend.cpp new file mode 100644 index 00000000..933dac69 --- /dev/null +++ b/src/miami/animation/RpAnimBlend.cpp @@ -0,0 +1,518 @@ +#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" +#include "PedModelInfo.h" + +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; +} + +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 = GetFirstAtomic(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]; +#ifdef LIBRW + frames[i].hanimFrame = (RpHAnimStdInterpFrame*)rpHANIMHIERARCHYGETINTERPFRAME(hier, i); +#else + frames[i].hanimFrame = (RpHAnimStdInterpFrame*)rtANIMGETINTERPFRAME(hier->currentAnim, i); +#endif + } + clumpData->ForAllFrames(FrameInitCBskin, nil); + clumpData->frames[0].flag |= AnimBlendFrameData::VELOCITY_EXTRACTION; +} + +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) +{ + if(IsClumpSkinned(clump)) + RpAnimBlendClumpInitSkinned(clump); + else + 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; +} + +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))]; +} + +void +RpAnimBlendClumpFillFrameArray(RpClump *clump, AnimBlendFrameData **frames) +{ + if(IsClumpSkinned(clump)) + RpAnimBlendClumpFillFrameArraySkin(clump, frames); + else + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FillFrameArrayCBnonskin, frames); +} + +AnimBlendFrameData *pFrameDataFound; + +void +FrameFindByNameCBnonskin(AnimBlendFrameData *frame, void *arg) +{ + char *nodename = GetFrameNodeName(frame->frame); + if(!CGeneral::faststricmp(nodename, (char*)arg)) + pFrameDataFound = frame; +} + +void +FrameFindByNameCBskin(AnimBlendFrameData *frame, void *arg) +{ + const char *name = ConvertBoneTag2BoneName(frame->nodeID); + if(name && CGeneral::faststricmp(name, (char*)arg) == 0) + pFrameDataFound = frame; +} + +void +FrameFindByBoneCB(AnimBlendFrameData *frame, void *arg) +{ + if(frame->nodeID == (int32)(uintptr)arg) + pFrameDataFound = frame; +} + +AnimBlendFrameData* +RpAnimBlendClumpFindFrame(RpClump *clump, const char *name) +{ + pFrameDataFound = nil; + if(IsClumpSkinned(clump)) + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBskin, (void*)name); + else + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByNameCBnonskin, (void*)name); + return pFrameDataFound; +} + +AnimBlendFrameData* +RpAnimBlendClumpFindBone(RpClump *clump, uint32 boneTag) +{ + pFrameDataFound = nil; + (*RPANIMBLENDCLUMPDATA(clump))->ForAllFrames(FrameFindByBoneCB, (void*)boneTag); + return pFrameDataFound; +} + +#if !defined(DC_TEXCONV) +void +RpAnimBlendNodeUpdateKeyframes(AnimBlendFrameData *frames, AnimBlendFrameUpdateData *updateData, int32 numNodes) +{ + CAnimBlendNode **node; + int i; + + for(node = updateData->nodes; *node; node++){ + CAnimBlendAssociation *a = (*node)->association; + for(i = 0; i < numNodes; i++) + if((frames[i].flag & AnimBlendFrameData::VELOCITY_EXTRACTION) == 0 || + gpAnimBlendClump->velocity2d == nil){ + if((*node)[i].sequence) + (*node)[i].FindKeyFrame(a->currentTime - a->timeStep); + } + } +} +#else +void +RpAnimBlendNodeUpdateKeyframes(AnimBlendFrameData *frames, AnimBlendFrameUpdateData *updateData, int32 numNodes) { assert(false); } +#endif + +#if !defined(DC_TEXCONV) +// TODO: +// CAnimBlendClumpData::LoadFramesIntoSPR +// CAnimBlendClumpData::ForAllFramesInSPR +void +RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta, bool doRender) +{ + int i; + CAnimBlendAssociation *assoc; + 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; + assoc = CAnimBlendAssociation::FromLink(link); + if(assoc->UpdateBlend(timeDelta)){ + if(assoc->hierarchy->sequences){ + if(i < 11) + 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; + }else + debug("anim %s is not loaded\n", assoc->hierarchy->name); + } + } + + for(link = clumpData->link.next; link; link = link->next){ + assoc = CAnimBlendAssociation::FromLink(link); + assoc->UpdateTimeStep(timeDelta, totalLength == 0.0f ? 1.0f : totalBlend/totalLength); + } + + updateData.nodes[i] = nil; + + if(doRender){ + if(clumpData->frames[0].flag & AnimBlendFrameData::UPDATE_KEYFRAMES) + RpAnimBlendNodeUpdateKeyframes(clumpData->frames, &updateData, clumpData->numFrames); + if(IsClumpSkinned(clump)) + clumpData->ForAllFrames(FrameUpdateCallBackSkinned, &updateData); + else + clumpData->ForAllFrames(FrameUpdateCallBackNonSkinned, &updateData); + clumpData->frames[0].flag &= ~AnimBlendFrameData::UPDATE_KEYFRAMES; + }else{ + clumpData->ForAllFrames(FrameUpdateCallBackOffscreen, &updateData); + clumpData->frames[0].flag |= AnimBlendFrameData::UPDATE_KEYFRAMES; + } + + for(link = clumpData->link.next; link; link = link->next){ + assoc = CAnimBlendAssociation::FromLink(link); + assoc->UpdateTime(timeDelta, totalLength == 0.0f ? 1.0f : totalBlend/totalLength); + } + RwFrameUpdateObjects(RpClumpGetFrame(clump)); +} +#else +void +RpAnimBlendClumpUpdateAnimations(RpClump *clump, float timeDelta, bool doRender) { assert(false); } +#endif diff --git a/src/miami/animation/RpAnimBlend.h b/src/miami/animation/RpAnimBlend.h new file mode 100644 index 00000000..d0f7a114 --- /dev/null +++ b/src/miami/animation/RpAnimBlend.h @@ -0,0 +1,45 @@ +#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); +AnimBlendFrameData *RpAnimBlendClumpFindBone(RpClump *clump, uint32 boneTag); +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 RpAnimBlendNodeUpdateKeyframes(AnimBlendFrameData *frames, AnimBlendFrameUpdateData *updateData, int32 numNodes); +void RpAnimBlendClumpUpdateAnimations(RpClump* clump, float timeDelta, bool doRender = true); + + +extern CAnimBlendClumpData *gpAnimBlendClump; +void FrameUpdateCallBackNonSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackSkinned(AnimBlendFrameData *frame, void *arg); +void FrameUpdateCallBackOffscreen(AnimBlendFrameData *frame, void *arg); diff --git a/src/miami/audio/AudioCollision.cpp b/src/miami/audio/AudioCollision.cpp new file mode 100644 index 00000000..02330756 --- /dev/null +++ b/src/miami/audio/AudioCollision.cpp @@ -0,0 +1,405 @@ +#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, + SFX_COL_SAND_1, + SFX_COL_TARMAC_1 }; + +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] % 2; + 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +void +cAudioManager::SetUpLoopingCollisionSound(const cAudioCollision &col, uint8 counter) +{ + bool8 distCalculated = FALSE; + if(col.m_fIntensity2 > 0.0016f) { + uint8 emittingVol = SetLoopingCollisionRequestedSfxFreqAndGetVol(col); + if(emittingVol) { + CalculateDistance(distCalculated, m_sQueueSample.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; + SET_SOUND_REVERB(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 || surface1 == SURFACE_SAND_BEACH || surface2 == SURFACE_SAND_BEACH) { + 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: + case SURFACE_CONCRETE_BEACH: return GetCollisionRatio(b, 10.f, 60.f, 50.f); + case SURFACE_GRASS: + case SURFACE_GRAVEL: + case SURFACE_MUD_DRY: + case SURFACE_CARDBOARDBOX: 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: + case SURFACE_SAND_BEACH: 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/miami/audio/AudioCollision.h b/src/miami/audio/AudioCollision.h new file mode 100644 index 00000000..31be0334 --- /dev/null +++ b/src/miami/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, 0x354); diff --git a/src/miami/audio/AudioLogic.cpp b/src/miami/audio/AudioLogic.cpp new file mode 100644 index 00000000..56271fcf --- /dev/null +++ b/src/miami/audio/AudioLogic.cpp @@ -0,0 +1,10279 @@ +#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" +#include "Bike.h" +#include "WindModifiers.h" +#include "Fluff.h" +#include "Script.h" +#include "Wanted.h" +#include "debugmenu.h" + +#ifndef GTA_PS2 +#define CHANNEL_PLAYER_VEHICLE_ENGINE m_nActiveSamples +#endif + +void +cAudioManager::PreInitialiseGameSpecificSetup() +{ + unsigned DynamicBankCount = SFX_BANK_PED_COMMENTS; // first non dynamic bank + BankStartOffset[SFX_BANK_0] = SAMPLEBANK_START; + for (unsigned i = SFX_BANK_1; i < DynamicBankCount; i++) { + BankStartOffset[i] = (SAMPLEBANK_END - SAMPLEBANK_START) / DynamicBankCount + BankStartOffset[i - 1]; + } +#ifdef GTA_PS2 + BankStartOffset[SAMPLEBANK_CAR_PACARD] = SFX_CAR_ACCEL_1; + BankStartOffset[SAMPLEBANK_CAR_PATHFINDER] = SFX_CAR_ACCEL_2; + BankStartOffset[SAMPLEBANK_CAR_PORSCHE] = SFX_CAR_ACCEL_3; + BankStartOffset[SAMPLEBANK_CAR_SPIDER] = SFX_CAR_ACCEL_4; + BankStartOffset[SAMPLEBANK_CAR_MERC] = SFX_CAR_ACCEL_5; + BankStartOffset[SAMPLEBANK_CAR_MACKTRUCK] = SFX_CAR_ACCEL_6; + BankStartOffset[SAMPLEBANK_CAR_HOTROD] = SFX_CAR_ACCEL_7; + BankStartOffset[SAMPLEBANK_CAR_COBRA] = SFX_CAR_ACCEL_8; + BankStartOffset[SAMPLEBANK_CAR_NONE] = SFX_CAR_ACCEL_9; + BankStartOffset[SAMPLEBANK_FRONTEND] = SFX_PAGE_CHANGE_AND_BACK_LEFT; + BankStartOffset[SAMPLEBANK_TRAIN] = SFX_TRAIN_STATION_AMBIENCE_LOOP; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_1] = SFX_CLUB_1; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_2] = SFX_CLUB_2; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_3] = SFX_CLUB_3; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_4] = SFX_CLUB_4; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_5] = SFX_CLUB_5; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_6] = SFX_CLUB_6; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_7] = SFX_CLUB_7; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_8] = SFX_CLUB_8; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_9] = SFX_CLUB_9; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_10] = SFX_CLUB_10; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_11] = SFX_CLUB_11; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_12] = SFX_CLUB_12; + BankStartOffset[SAMPLEBANK_BUILDING_CLUB_RAGGA] = SFX_CLUB_RAGGA; + BankStartOffset[SAMPLEBANK_BUILDING_STRIP_CLUB_1] = SFX_STRIP_CLUB_1; + BankStartOffset[SAMPLEBANK_BUILDING_STRIP_CLUB_2] = SFX_STRIP_CLUB_2; + BankStartOffset[SAMPLEBANK_BUILDING_WORKSHOP] = SFX_WORKSHOP_1; + BankStartOffset[SAMPLEBANK_BUILDING_PIANO_BAR] = SFX_PIANO_BAR_1; + BankStartOffset[SAMPLEBANK_BUILDING_SAWMILL] = SFX_SAWMILL_LOOP; + BankStartOffset[SAMPLEBANK_BUILDING_DOG_FOOD_FACTORY] = SFX_DOG_FOOD_FACTORY; + BankStartOffset[SAMPLEBANK_BUILDING_LAUNDERETTE] = SFX_LAUNDERETTE_LOOP; + BankStartOffset[SAMPLEBANK_BUILDING_RESTAURANT_CHINATOWN] = SFX_RESTAURANT_CHINATOWN; + BankStartOffset[SAMPLEBANK_BUILDING_RESTAURANT_ITALY] = SFX_RESTAURANT_ITALY; + BankStartOffset[SAMPLEBANK_BUILDING_RESTAURANT_GENERIC_1] = SFX_RESTAURANT_GENERIC_1; + BankStartOffset[SAMPLEBANK_BUILDING_RESTAURANT_GENERIC_2] = SFX_RESTAURANT_GENERIC_2; + BankStartOffset[SAMPLEBANK_BUILDING_AIRPORT] = SFX_AIRPORT_ANNOUNCEMENT_1; + BankStartOffset[SAMPLEBANK_BUILDING_SHOP] = SFX_SHOP_LOOP; + BankStartOffset[SAMPLEBANK_BUILDING_CINEMA] = SFX_CINEMA_BASS_1; + BankStartOffset[SAMPLEBANK_BUILDING_DOCKS] = SFX_DOCKS_FOGHORN; + BankStartOffset[SAMPLEBANK_BUILDING_HOME] = SFX_HOME_1; + BankStartOffset[SAMPLEBANK_BUILDING_PORN_1] = SFX_PORN_1_LOOP; + BankStartOffset[SAMPLEBANK_BUILDING_PORN_2] = SFX_PORN_2_LOOP; + BankStartOffset[SAMPLEBANK_BUILDING_PORN_3] = SFX_PORN_3_LOOP; + BankStartOffset[SAMPLEBANK_BUILDING_POLICE_BALL] = SFX_POLICE_BALL_1; + BankStartOffset[SAMPLEBANK_BUILDING_BANK_ALARM] = SFX_BANK_ALARM_1; + BankStartOffset[SAMPLEBANK_BUILDING_RAVE_INDUSTRIAL] = SFX_RAVE_INDUSTRIAL; + BankStartOffset[SAMPLEBANK_BUILDING_RAVE_COMMERCIAL] = SFX_RAVE_COMMERCIAL; + BankStartOffset[SAMPLEBANK_BUILDING_RAVE_SUBURBAN] = SFX_RAVE_SUBURBAN; + BankStartOffset[SAMPLEBANK_BUILDING_RAVE_COMMERCIAL_2] = SFX_RAVE_COMMERCIAL_2; + BankStartOffset[SAMPLEBANK_BUILDING_39] = SFX_CLUB_1_1; + BankStartOffset[SAMPLEBANK_BUILDING_40] = SFX_CLUB_1_2; + BankStartOffset[SAMPLEBANK_BUILDING_41] = SFX_CLUB_1_3; + BankStartOffset[SAMPLEBANK_BUILDING_42] = SFX_CLUB_1_4; + BankStartOffset[SAMPLEBANK_BUILDING_43] = SFX_CLUB_1_5; + BankStartOffset[SAMPLEBANK_BUILDING_44] = SFX_CLUB_1_6; + BankStartOffset[SAMPLEBANK_BUILDING_45] = SFX_CLUB_1_7; + BankStartOffset[SAMPLEBANK_BUILDING_46] = SFX_CLUB_1_8; + BankStartOffset[SAMPLEBANK_BUILDING_47] = SFX_CLUB_1_9; + BankStartOffset[SAMPLEBANK_EXTRAS] = 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_nEscalatorEntity = CreateEntity(AUDIOTYPE_ESCALATOR, (void*)1); + if (m_nEscalatorEntity >= 0) + SetEntityStatus(m_nEscalatorEntity, TRUE); + + m_nExtraSoundsEntity = CreateEntity(AUDIOTYPE_EXTRA_SOUNDS, (void*)1); + if (m_nExtraSoundsEntity >= 0) + SetEntityStatus(m_nExtraSoundsEntity, TRUE); + +#ifdef GTA_BRIDGE + m_nBridgeEntity = CreateEntity(AUDIOTYPE_BRIDGE, (void*)1); + if (m_nBridgeEntity >= 0) + SetEntityStatus(m_nBridgeEntity, TRUE); +#endif // GTA_BRIDGE + + for (int i = 0; i < MISSION_AUDIO_SLOTS; i++) { + m_nMissionAudioSampleIndex[i] = NO_SAMPLE; + m_nMissionAudioLoadingStatus[i] = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus[i] = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying[i] = FALSE; + m_bIsMissionAudioAllowedToPlay[i] = FALSE; + m_bIsMissionAudio2D[i] = TRUE; + m_nMissionAudioFramesToPlay[i] = 0; + m_bIsMissionAudioPhoneCall[i] = FALSE; + m_nGlobalSfxVolumeMultiplier = 127; + } + + ResetAudioLogicTimers(CTimer::GetTimeInMilliseconds()); + m_bIsPlayerShutUp = FALSE; + m_nPlayerMood = PLAYER_MOOD_CALM; + m_nPlayerMoodTimer = 0; +} + +void +cAudioManager::PreTerminateGameSpecificShutdown() +{ +#ifdef GTA_BRIDGE + if (m_nBridgeEntity >= 0) { + DestroyEntity(m_nBridgeEntity); + m_nBridgeEntity = AEHANDLE_NONE; + } +#endif + if (m_nEscalatorEntity >= 0) { + DestroyEntity(m_nEscalatorEntity); + m_nEscalatorEntity = AEHANDLE_NONE; + } + if (m_nExtraSoundsEntity >= 0) { + DestroyEntity(m_nExtraSoundsEntity); + m_nExtraSoundsEntity = 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) +{ + 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(0); + ClearMissionAudio(1); + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); +} + +void +cAudioManager::ProcessReverb() +{ +#ifdef AUDIO_REVERB +#ifdef FIX_BUGS + const uint32 numChannels = NUM_CHANNELS_GENERIC; +#else + const uint32 numChannels = NUM_CHANNELS_GENERIC+1; +#endif + + if (SampleManager.UpdateReverb() && m_bDynamicAcousticModelingStatus) { +#ifndef GTA_PS2 + for (uint32 i = 0; i < numChannels; i++) { + if (m_asActiveSamples[i].m_bReverb) + SampleManager.SetChannelReverbFlag(i, TRUE); + } +#endif + } +#endif // AUDIO_REVERB +} + +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; + } +} + +CVehicle * +cAudioManager::FindVehicleOfPlayer() +{ + CVehicle* vehicle = FindPlayerVehicle(); + CPlayerPed* ped = FindPlayerPed(); + if (vehicle == nil && ped != nil) { + CEntity *attachedTo = ped->m_attachedTo; + if (attachedTo && attachedTo->IsVehicle()) + vehicle = (CVehicle*)attachedTo; + } + return vehicle; +} + +void +cAudioManager::ProcessSpecial() +{ + CPlayerPed *playerPed; + CVehicle *remoteVehicle; + + if (m_bIsPaused) { + if (!m_bWasPaused) { + SampleManager.SetEffectsFadeVolume(MAX_VOLUME); + SampleManager.SetMusicFadeVolume(MAX_VOLUME); + } + } else { + if (!CReplay::IsPlayingBack()) + ProcessPlayerMood(); + remoteVehicle = CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle; + playerPed = FindPlayerPed(); + if (playerPed) { + if (playerPed->m_audioEntityId >= 0 && m_asAudioEntities[playerPed->m_audioEntityId].m_bIsUsed) { + if(!playerPed->EnteringCar() && !playerPed->bInVehicle && !remoteVehicle) + SampleManager.StopChannel(CHANNEL_PLAYER_VEHICLE_ENGINE); + } + } + } +} + +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) { + SET_SOUND_REVERB(TRUE); + ProcessPhysical(id); + } + break; + case AUDIOTYPE_EXPLOSION: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessExplosions(id); + } + break; + case AUDIOTYPE_FIRE: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessFires(id); + } + break; + case AUDIOTYPE_WEATHER: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + if(CGame::currArea == AREA_MAIN_MAP || CGame::currArea == AREA_EVERYWHERE) + ProcessWeather(id); + } + break; +/* case AUDIOTYPE_CRANE: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessCrane(); + } + break;*/ + case AUDIOTYPE_SCRIPTOBJECT: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessScriptObject(id); + } + break; +#ifdef GTA_BRIDGE + case AUDIOTYPE_BRIDGE: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessBridge(); + } + break; +#endif + case AUDIOTYPE_FRONTEND: + SET_SOUND_REVERB(FALSE); + ProcessFrontEnd(); + break; + case AUDIOTYPE_PROJECTILE: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessProjectiles(); + } + break; + case AUDIOTYPE_GARAGE: + if (!m_bIsPaused) + ProcessGarages(); + break; + case AUDIOTYPE_FIREHYDRANT: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessFireHydrant(); + } + break; + case AUDIOTYPE_WATERCANNON: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessWaterCannon(id); + } + break; + case AUDIOTYPE_ESCALATOR: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessEscalators(); + } + break; + case AUDIOTYPE_EXTRA_SOUNDS: + if (!m_bIsPaused) { + SET_SOUND_REVERB(TRUE); + ProcessExtraSounds(); + } + 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 *)m_asAudioEntities[id].m_pEntity); + break; + case ENTITY_TYPE_PED: + ProcessPed((CPhysical *)m_asAudioEntities[id].m_pEntity); + break; + default: + return; + } + } +} + +enum +{ + CAR_HELI_MAX_DIST = 250, + CAR_HELI_ENGINE_MAX_DIST = 140, + CAR_HELI_ENGINE_START_MAX_DIST = 30, + CAR_HELI_ENGINE_START_VOLUME = 70, + CAR_HELI_SEAPLANE_MAX_DIST = 20, + CAR_HELI_SEAPLANE_VOLUME = 100, + CAR_HELI_REAR_MAX_DIST = 27, + CAR_HELI_REAR_VOLUME = 25, + + FLAT_TYRE_MAX_DIST = 60, + FLAT_TYRE_VOLUME = 100, + + + 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 = 35, + MODEL_CAR_ENGINE_VOLUME = 90, + MODEL_HELI_ENGINE_VOLUME = 70, + + 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 = 75, + VEHICLE_ENGINE_FULL_VOLUME = 90, + + CESNA_IDLE_MAX_DIST = 200, + CESNA_REV_MAX_DIST = 90, + CESNA_VOLUME = 80, + + PLAYER_VEHICLE_ENGINE_VOLUME = 120, + + 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_HELI_BLADE_MAX_DIST = 35, + VEHICLE_ONE_SHOT_HELI_BLADE_VOLUME = 70, + + VEHICLE_ONE_SHOT_CAR_TYRE_POP_MAX_DIST = 60, + VEHICLE_ONE_SHOT_CAR_TYRE_POP_VOLUME = 117, + + 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 = 40, + VEHICLE_ONE_SHOT_WINDSHIELD_CRACK_VOLUME = 80, + + 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 = 60, + VEHICLE_ONE_SHOT_CAR_SPLASH_VOLUME = 35, + + 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 = 90, + BOAT_ENGINE_REEFER_IDLE_VOLUME = 80, + + 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_ONE_SHOT_SHIRT_FLAP_MAX_DIST = 15, + PED_ONE_SHOT_SHIRT_FLAP_VOLUME = 90, + + PED_ONE_SHOT_MINIGUN_MAX_DIST = 150, + PED_ONE_SHOT_MINIGUN_VOLUME = MAX_VOLUME, + + PED_ONE_SHOT_SKATING_MAX_DIST = 20, + PED_ONE_SHOT_SKATING_VOLUME = 70, + + 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_VOLUME = 90, + PED_ONE_SHOT_WEAPON_UZI_VOLUME = 70, + PED_ONE_SHOT_WEAPON_SHOTGUN_VOLUME = 100, + PED_ONE_SHOT_WEAPON_M4_VOLUME = 90, + PED_ONE_SHOT_WEAPON_M16_VOLUME = MAX_VOLUME, + PED_ONE_SHOT_WEAPON_SNIPERRIFLE_VOLUME = 110, + 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 = 120, + PED_ONE_SHOT_WEAPON_BULLET_ECHO_VOLUME = 80, + + PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_FLAMETHROWER_FIRE_VOLUME = 70, + + PED_ONE_SHOT_WEAPON_CHAINSAW_MAX_DIST = 60, + PED_ONE_SHOT_WEAPON_CHAINSAW_IDLE_MAX_DIST = 50, + PED_ONE_SHOT_WEAPON_CHAINSAW_VOLUME = 100, + + 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 = 40, + PED_COMMENT_POLICE_HELI_MAX_DIST = 400, + + EXPLOSION_DEFAULT_MAX_DIST = 200, + EXPLOSION_MOLOTOV_MAX_DIST = 150, + EXPLOSION_MINE_MAX_DIST = 200, + + FIRE_DEFAULT_MAX_DIST = 80, + FIRE_DEFAULT_VOLUME = 80, + FIRE_BUILDING_MAX_DIST = 80, + FIRE_BUILDING_VOLUME = 100, + FIRE_PED_MAX_DIST = 25, + FIRE_PED_VOLUME = 60, + FIRE_EXTINGUISH_VOLUME = 100, + + WATER_CANNON_MAX_DIST = 30, + WATER_CANNON_VOLUME = 50, + + SCRIPT_OBJECT_SEAPLANE_LOW_FUEL_MAX_DIST = 1000, + SCRIPT_OBJECT_SEAPLANE_LOW_FUEL_VOLUME = 100, + + SCRIPT_OBJECT_WILLIE_CARD_SWIPE_MAX_DIST = 40, + SCRIPT_OBJECT_WILLIE_CARD_SWIPE_VOLUME = 70, + + SCRIPT_OBJECT_GATE_MAX_DIST = 40, + + SCRIPT_OBJECT_BULLET_HIT_GROUND_MAX_DIST = 50, + SCRIPT_OBJECT_BULLET_HIT_GROUND_VOLUME = 90, + + 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_HIT_BALL_VOLUME = 60, + + SCRIPT_OBJECT_GUNSHELL_MAX_DIST = 20, + SCRIPT_OBJECT_GUNSHELL_VOLUME = 30, + + SCRIPT_OBJECT_POLICE_CELL_DOOR_CLUNK_MAX_DIST = 40, + SCRIPT_OBJECT_POLICE_CELL_DOOR_CLUNK_VOLUME = 60, + + SCRIPT_OBJECT_GARAGE_DOOR_CLUNK_MAX_DIST = 80, + SCRIPT_OBJECT_GARAGE_DOOR_CLUNK_VOLUME = 60, + + //SCRIPT_OBJECT_SHORT_MAX_DIST = 30, + SCRIPT_OBJECT_LONG_MAX_DIST = 80, + SCRIPT_OBJECT_DEFAULT_VOLUME = MAX_VOLUME, + SCRIPT_OBJECT_BANK_ALARM_VOLUME = 90, + SCRIPT_OBJECT_SNORING_MAX_DIST = 6, + SCRIPT_OBJECT_SNORING_VOLUME = 25, + SCRIPT_OBJECT_GARAGE_DOOR_SLIDING_VOLUME = 90, + SCRIPT_OBJECT_SHOOTING_RANGE_TARGET_MOVING_MAX_DIST = 40, + SCRIPT_OBJECT_SHOOTING_RANGE_TARGET_MOVING_VOLUME = 60, + SCRIPT_OBJECT_NEW_WATERFALL_VOLUME = 30, + + FRONTEND_VOLUME = 127, + + //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, + + PROJECTILE_TEARGAS_MAX_DIST = 40, + PROJECTILE_TEARGAS_VOLUME = 80, + + ESCALATOR_MAX_DIST = 30, + ESCALATOR_VOLUME = 26, + + ARCADE_MAX_DIST = 18, + ARCADE_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 = 80, + MISSION_AUDIO_VOLUME = 80, +}; + +#pragma region VEHICLE AUDIO +enum eVehicleModel { + LANDSTAL, + IDAHO, + STINGER, + LINERUN, + PEREN, + SENTINEL, + RIO, + FIRETRUK, + TRASH, + STRETCH, + MANANA, + INFERNUS, + VOODOO, + PONY, + MULE, + CHEETAH, + AMBULAN, + FBICAR, + MOONBEAM, + ESPERANT, + TAXI, + WASHING, + BOBCAT, + MRWHOOP, + BFINJECT, + HUNTER, + POLICE, + ENFORCER, + SECURICA, + BANSHEE, + PREDATOR, + BUS, + RHINO, + BARRACKS, + CUBAN, + CHOPPER, + ANGEL, + COACH, + CABBIE, + STALLION, + RUMPO, + RCBANDIT, + ROMERO, + PACKER, + SENTXS, + ADMIRAL, + SQUALO, + SEASPAR, + PIZZABOY, + GANGBUR, + AIRTRAIN, + DEADDODO, + SPEEDER, + REEFER, + TROPIC, + FLATBED, + YANKEE, + CADDY, + ZEBRA, + TOPFUN, + SKIMMER, + PCJ600, + FAGGIO, + FREEWAY, + RCBARON, + RCRAIDER, + GLENDALE, + OCEANIC, + SANCHEZ, + SPARROW, + PATRIOT, + LOVEFIST, + COASTG, + DINGHY, + HERMES, + SABRE, + SABRETUR, + PHEONIX, + WALTON, + REGINA, + COMET, + DELUXO, + BURRITO, + SPAND, + MARQUIS, + BAGGAGE, + KAUFMAN, + MAVERICK, + VCNMAV, + RANCHER, + FBIRANCH, + VIRGO, + GREENWOO, + JETMAX, + HOTRING, + SANDKING, + BLISTAC, + POLMAV, + BOXVILLE, + BENSON, + MESA, + RCGOBLIN, + HOTRINA, + HOTRINB, + BLOODRA, + BLOODRB, + VICECHEE, + CAR237, + CAR238, + CAR239, + MAX_CARS, + + // HACK so this compiles + // TODO(MIAMI): check it out + DODO = -1 +}; + +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 tVehicleSampleData aVehicleSettings[MAX_CARS] = { + {SFX_CAR_REV_10, SFX_BANK_PATHFINDER, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9935, OLD_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 11487, SFX_CAR_HORN_JEEP, 9900, OLD_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PORSCHE, SFX_CAR_HORN_PORSCHE, 11025, SFX_CAR_HORN_JEEP, 9890, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_HORN_JEEP, 9960, TRUCK_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 12893, SFX_CAR_HORN_JEEP, 9500, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 10706, SFX_CAR_HORN_JEEP, 9600, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_HORN_JEEP, 9700, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_POLICE_SIREN_SLOW, 10588, TRUCK_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 31478, SFX_CAR_HORN_JEEP, 9800, TRUCK_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_BMW328, 9538, SFX_CAR_HORN_JEEP, 9900, NEW_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_HORN_JEEP, 10000, OLD_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_COBRA, SFX_CAR_HORN_BMW328, 12017, SFX_CAR_HORN_JEEP, 9900, NEW_DOOR}, + {SFX_CAR_REV_9, SFX_BANK_CADILLAC, SFX_CAR_HORN_JEEP, 22293, SFX_CAR_HORN_JEEP, 9800, NEW_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS2, 18000, SFX_CAR_HORN_JEEP, 9700, OLD_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 18286, SFX_CAR_HORN_JEEP, 9600, OLD_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PORSCHE, SFX_CAR_HORN_PORSCHE, 11025, SFX_CAR_HORN_JEEP, 9500, NEW_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 22295, SFX_AMBULANCE_SIREN_SLOW, 12688, OLD_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_MERC, SFX_CAR_HORN_PORSCHE, 9271, SFX_POLICE_SIREN_SLOW, 11471, NEW_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 12170, SFX_CAR_HORN_JEEP, 9400, OLD_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_BMW328, 11000, SFX_CAR_HORN_JEEP, 9300, OLD_DOOR}, + {SFX_CAR_REV_10, SFX_BANK_PATHFINDER, SFX_CAR_HORN_BMW328, 10796, SFX_CAR_HORN_JEEP, 9200, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 10500, SFX_CAR_HORN_JEEP, 9100, NEW_DOOR}, + {SFX_CAR_REV_10, SFX_BANK_PATHFINDER, SFX_CAR_HORN_PICKUP, 10924, SFX_CAR_HORN_JEEP, 9000, OLD_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_PICKUP, 11025, SFX_ICE_CREAM_TUNE, 11025, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_HOTROD, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9100, OLD_DOOR}, + {SFX_HELI_APACHE_1, SFX_BANK_HELI_APACHE, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9200, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_MERC, SFX_CAR_HORN_BMW328, 10706, SFX_POLICE_SIREN_SLOW, 10511, NEW_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 17260, SFX_POLICE_SIREN_SLOW, 11029, OLD_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_PICKUP, 8670, SFX_CAR_HORN_JEEP, 9300, OLD_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_COBRA, SFX_CAR_HORN_PORSCHE, 10400, SFX_CAR_HORN_JEEP, 9400, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_POLICE_SIREN_SLOW, 11912, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_BUS2, 11652, SFX_CAR_HORN_JEEP, 9500, BUS_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29711, SFX_CAR_HORN_JEEP, 9600, TRUCK_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 28043, SFX_CAR_HORN_JEEP, 9700, TRUCK_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_HOTROD, SFX_CAR_HORN_JEEP, 25400, SFX_CAR_HORN_JEEP, 9800, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9900, NEW_DOOR}, + {SFX_CAR_REV_17, SFX_BANK_VTWIN, SFX_CAR_HORN_JEEP, 26313, SFX_CAR_HORN_JEEP, 10000, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_BUS, 16291, SFX_CAR_HORN_JEEP, 10100, BUS_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_HORN_JEEP, 9900, OLD_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10233, SFX_CAR_HORN_JEEP, 9800, NEW_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_PICKUP, 8670, SFX_CAR_HORN_JEEP, 9700, OLD_DOOR}, + {SFX_RC_REV, SFX_BANK_RC, SFX_CAR_HORN_PICKUP, 20000, SFX_CAR_HORN_JEEP, 9600, NEW_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_HORN_JEEP, 9500, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 29000, SFX_CAR_HORN_JEEP, 9400, TRUCK_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_BMW328, 9003, SFX_CAR_HORN_JEEP, 9300, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_MERC, SFX_CAR_HORN_PORSCHE, 12375, SFX_CAR_HORN_JEEP, 9200, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_BUS2, 15554, SFX_CAR_HORN_JEEP, 9100, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_BUS2, 13857, SFX_CAR_HORN_JEEP, 9000, TRUCK_DOOR}, + {SFX_MOPED_REV, SFX_BANK_MOPED, SFX_CAR_HORN_JEEP, 30000, SFX_CAR_HORN_JEEP, 9100, NEW_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_COBRA, SFX_CAR_HORN_JEEP, 22043, SFX_CAR_HORN_JEEP, 9200, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_HORN_JEEP, 9300, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_HORN_JEEP, 9400, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_HORN_JEEP, 9500, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_HORN_JEEP, 9600, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 21043, SFX_CAR_HORN_JEEP, 9700, NEW_DOOR}, + {SFX_CAR_REV_5, SFX_BANK_TRUCK, SFX_CAR_HORN_TRUCK, 28043, SFX_CAR_HORN_JEEP, 9800, TRUCK_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 18286, SFX_CAR_HORN_JEEP, 9900, OLD_DOOR}, + {SFX_CAR_REV_12, SFX_BANK_GOLF_CART, SFX_CAR_HORN_JEEP, 28500, SFX_CAR_HORN_JEEP, 9800, NEW_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_56CHEV, 10842, SFX_CAR_HORN_JEEP, 9700, OLD_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_PONTIAC_SLOW, SFX_CAR_HORN_BUS2, 18000, SFX_CAR_HORN_JEEP, 9700, OLD_DOOR}, + {SFX_SEAPLANE_PRO1, SFX_BANK_PLANE_SEAPLANE, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9700, NEW_DOOR}, + {SFX_CAR_REV_20, SFX_BANK_SPORTS_BIKE, SFX_CAR_HORN_JEEP, 27000, SFX_CAR_HORN_JEEP, 9600, NEW_DOOR}, + {SFX_MOPED_REV, SFX_BANK_MOPED, SFX_CAR_HORN_JEEP, 31000, SFX_CAR_HORN_JEEP, 9500, NEW_DOOR}, + {SFX_CAR_REV_17, SFX_BANK_VTWIN, SFX_CAR_HORN_PICKUP, 11000, SFX_CAR_HORN_JEEP, 9400, NEW_DOOR}, + {SFX_RC_REV, SFX_BANK_RC, SFX_CAR_HORN_JEEP, 30000, SFX_CAR_HORN_JEEP, 15000, NEW_DOOR}, + {SFX_CAR_RC_HELI, SFX_BANK_RC_HELI, SFX_CAR_HORN_JEEP, 30000, SFX_CAR_HORN_JEEP, 15000, NEW_DOOR}, + {SFX_CAR_REV_9, SFX_BANK_CADILLAC, SFX_CAR_HORN_56CHEV, 10300, SFX_CAR_HORN_JEEP, 9100, OLD_DOOR}, + {SFX_CAR_REV_9, SFX_BANK_CADILLAC, SFX_CAR_HORN_56CHEV, 10500, SFX_CAR_HORN_JEEP, 9000, OLD_DOOR}, + {SFX_CAR_REV_19, SFX_BANK_HONDA250, SFX_CAR_HORN_JEEP, 30000, SFX_CAR_HORN_JEEP, 9000, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9100, TRUCK_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_TRUCK, 28000, SFX_CAR_HORN_JEEP, 9200, TRUCK_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_COBRA, SFX_CAR_HORN_PICKUP, 11200, SFX_CAR_HORN_JEEP, 9300, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9400, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9500, NEW_DOOR}, + {SFX_CAR_REV_9, SFX_BANK_CADILLAC, SFX_CAR_HORN_56CHEV, 10700, SFX_CAR_HORN_JEEP, 9600, OLD_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_BMW328, 9000, SFX_CAR_HORN_JEEP, 9700, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_HOTROD, SFX_CAR_HORN_BMW328, 9200, SFX_CAR_HORN_JEEP, 9800, OLD_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_COBRA, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9900, NEW_DOOR}, + {SFX_CAR_REV_11, SFX_BANK_PACARD, SFX_CAR_HORN_56CHEV, 10540, SFX_CAR_HORN_JEEP, 9935, TRUCK_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_PONTIAC_SLOW, SFX_CAR_HORN_PICKUP, 11000, SFX_CAR_HORN_JEEP, 9700, NEW_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PORSCHE, SFX_CAR_HORN_BMW328, 9500, SFX_CAR_HORN_JEEP, 9800, NEW_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_COBRA, SFX_CAR_HORN_BMW328, 9700, SFX_CAR_HORN_JEEP, 9700, NEW_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_PONTIAC_SLOW, SFX_CAR_HORN_BUS2, 18000, SFX_CAR_HORN_JEEP, 9600, OLD_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_BUS, 18000, SFX_CAR_HORN_JEEP, 9500, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9400, NEW_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_PONTIAC_SLOW, SFX_CAR_HORN_JEEP, 27513, SFX_CAR_HORN_JEEP, 9300, NEW_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_PONTIAC_SLOW, SFX_CAR_HORN_56CHEV, 10700, SFX_CAR_HORN_JEEP, 9200, OLD_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9100, TRUCK_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9000, TRUCK_DOOR}, + {SFX_CAR_REV_10, SFX_BANK_PATHFINDER, SFX_CAR_HORN_BUS2, 18000, SFX_CAR_HORN_JEEP, 9100, TRUCK_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_BUS2, 17900, SFX_POLICE_SIREN_SLOW, 10511, TRUCK_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_MERC, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9200, NEW_DOOR}, + {SFX_CAR_REV_8, SFX_BANK_PONTIAC_SLOW, SFX_CAR_HORN_BMW328, 9600, SFX_CAR_HORN_JEEP, 9300, NEW_DOOR}, + {SFX_CAR_REV_4, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9400, NEW_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_COBRA, SFX_CAR_HORN_PORSCHE, 10000, SFX_CAR_HORN_JEEP, 9500, OLD_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_HOTROD, SFX_CAR_HORN_PORSCHE, 10500, SFX_CAR_HORN_JEEP, 9600, OLD_DOOR}, + {SFX_CAR_REV_10, SFX_BANK_PATHFINDER, SFX_CAR_HORN_JEEP, 25513, SFX_CAR_HORN_JEEP, 9700, NEW_DOOR}, + {SFX_CAR_REV_1, SFX_BANK_0, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9800, NEW_DOOR}, + {SFX_CAR_REV_3, SFX_BANK_SPIDER, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9900, NEW_DOOR}, + {SFX_CAR_REV_10, SFX_BANK_PATHFINDER, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9800, NEW_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9700, NEW_DOOR}, + {SFX_CAR_RC_HELI, SFX_BANK_RC_HELI, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9600, NEW_DOOR}, + {SFX_CAR_REV_6, SFX_BANK_HOTROD, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9700, NEW_DOOR}, + {SFX_CAR_REV_7, SFX_BANK_COBRA, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9600, NEW_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9500, NEW_DOOR}, + {SFX_CAR_REV_9, SFX_BANK_CADILLAC, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9400, NEW_DOOR}, + {SFX_CAR_REV_2, SFX_BANK_PORSCHE, SFX_CAR_HORN_PORSCHE, 11025, SFX_POLICE_SIREN_SLOW, 11000, NEW_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9200, NEW_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9300, NEW_DOOR}, + {SFX_CAR_REV_1, CAR_SFX_BANKS_OFFSET, SFX_CAR_HORN_JEEP, 26513, SFX_CAR_HORN_JEEP, 9400, NEW_DOOR} }; + +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; + + if (FindVehicleOfPlayer() != veh) { + switch (CGame::currArea) + { + case AREA_OVALRING: + case AREA_BLOOD: + case AREA_DIRT: + case AREA_EVERYWHERE: + case AREA_MALL: + case AREA_MAIN_MAP: + break; + default: + return; + } + } + + 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->m_modelIndex - MI_FIRST_VEHICLE; + if (veh->GetStatus() == STATUS_SIMPLE) + params.m_fVelocityChange = veh->AutoPilot.m_fMaxTrafficSpeed * 0.02f; + else + params.m_fVelocityChange = DotProduct(veh->m_vecMoveSpeed, veh->GetForward()); + params.m_VehicleType = veh->m_vehType; + + if (CGame::currArea == AREA_MALL && FindVehicleOfPlayer() != veh) { + ProcessVehicleOneShots(params); + ProcessVehicleSirenOrAlarm(params); + ProcessEngineDamage(params); + return; + } + switch (params.m_VehicleType) { + case VEHICLE_TYPE_CAR: + UpdateGasPedalAudio(veh, params.m_VehicleType); + if (veh->m_modelIndex == MI_RCBANDIT || veh->m_modelIndex == MI_RCBARON) { + ProcessModelVehicle(params); + ProcessEngineDamage(params); + } else if (veh->m_modelIndex == MI_RCRAIDER || veh->m_modelIndex == MI_RCGOBLIN) { + ProcessModelHeliVehicle(params); + ProcessEngineDamage(params); + } else { + switch (veh->GetVehicleAppearance()) { + case VEHICLE_APPEARANCE_HELI: + ProcessCarHeli(params); + ProcessVehicleFlatTyre(params); + ProcessEngineDamage(params); + break; + case VEHICLE_APPEARANCE_BOAT: + case VEHICLE_APPEARANCE_PLANE: + break; + default: + if (ProcessVehicleRoadNoise(params)) { + ProcessReverseGear(params); + if (CWeather::WetRoads > 0.0f) + ProcessWetRoadNoise(params); + ProcessVehicleSkidding(params); + ProcessVehicleFlatTyre(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); + } + break; + } + } + ProcessVehicleOneShots(params); + ((CAutomobile*)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + case VEHICLE_TYPE_BOAT: + if (veh->m_modelIndex == MI_SKIMMER) + ProcessCarHeli(params); + else + ProcessBoatEngine(params); + ProcessBoatMovingOverWater(params); + ProcessVehicleOneShots(params); + break; + case VEHICLE_TYPE_HELI: + ProcessCarHeli(params); + ProcessVehicleOneShots(params); + break; + case VEHICLE_TYPE_PLANE: + ProcessPlane(params); + ProcessVehicleOneShots(params); + ProcessVehicleFlatTyre(params); + break; + case VEHICLE_TYPE_BIKE: + UpdateGasPedalAudio(veh, params.m_VehicleType); + if (ProcessVehicleRoadNoise(params)) { + if (CWeather::WetRoads > 0.0f) + ProcessWetRoadNoise(params); + ProcessVehicleSkidding(params); + ProcessVehicleHorn(params); + ProcessVehicleSirenOrAlarm(params); + ProcessCarBombTick(params); + ProcessEngineDamage(params); + ProcessVehicleEngine(params); + ProcessVehicleFlatTyre(params); + } + ProcessVehicleOneShots(params); + ((CBike*)veh)->m_fVelocityChangeForAudio = params.m_fVelocityChange; + break; + default: + break; + } + ProcessRainOnVehicle(params); +} + +bool8 +cAudioManager::ProcessCarHeli(cVehicleParams& params) +{ + CAutomobile* automobile; + CBoat* boat; + + uint8 Vol; + int16 brakeState; + int16 accelerateState; + uint32 freq; + float propellerSpeed; + float freqModifier; //may be relate to angle with horison + float cameraAngle; + bool8 distanceCalculatedOld; + float distanceOld; + CVector vecPosOld; + + float volumeModifier;//TODO find better name + bool8 hunterBool; + CMatrix cameraMatrix; + + boat = nil; + automobile = nil; + hunterBool = FALSE; + + static uint32 freqFrontPrev = 14287; + static uint32 freqPropellerPrev = 7143; + static uint32 freqSkimmerPrev = 14287; + + + if (params.m_fDistance < SQR(CAR_HELI_MAX_DIST)) { + if (FindPlayerVehicle() == params.m_pVehicle) { + accelerateState = Pads[0].GetAccelerate(); + brakeState = Pads[0].GetBrake(); + } else { + accelerateState = params.m_pVehicle->m_fGasPedal * 255.0f; + brakeState = params.m_pVehicle->m_fBrakePedal * 255.0f; + } + cameraMatrix = TheCamera.GetMatrix(); + freqModifier = DotProduct(cameraMatrix.GetUp(), CVector(0.0f, 1.0f, 0.0f)); + freqModifier = ABS(freqModifier); + cameraAngle = (DotProduct(params.m_pVehicle->GetMatrix().GetForward(), cameraMatrix.GetForward()) + 1.0f) / 2.0f; + if (params.m_pVehicle->GetModelIndex() == MI_SKIMMER) { + boat = (CBoat*)params.m_pVehicle; + propellerSpeed = boat->m_fMovingSpeed * 50.0f / 11.0f; + } else if (params.m_VehicleType == VEHICLE_TYPE_HELI) { + propellerSpeed = 1.0f; + } else { + automobile = (CAutomobile*)params.m_pVehicle; + propellerSpeed = automobile->m_aWheelSpeed[1] * 50.0f / 11.0f; + } + + if (propellerSpeed == 0.0f) + return TRUE; + + propellerSpeed = Min(1.0f, propellerSpeed); + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + + + //sound on long distances + if (m_sQueueSample.m_fDistance < 40.0f) { + if (m_sQueueSample.m_fDistance < 25.0f) + Vol = 0; + else + Vol = (m_sQueueSample.m_fDistance - 25.0f) * (75.0f * propellerSpeed) / 15.0f; + } else + Vol = propellerSpeed * 75.0f; + if (Vol > 0) { + m_sQueueSample.m_nVolume = ComputeVolume(Vol, CAR_HELI_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 88; + if (boat != nil) { + m_sQueueSample.m_nSampleIndex = SFX_SEAPLANE_PRO3; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + if (accelerateState > 0 || brakeState > 0) + m_sQueueSample.m_nFrequency = 4600 + Min(1.0f, (Max(accelerateState, brakeState) / 255.0f) * freqModifier) * 563; + else + m_sQueueSample.m_nFrequency = 3651 + Min(1.0f, freqModifier) * 949; + } else { + m_sQueueSample.m_nSampleIndex = SFX_HELI_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + } + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 2; + 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 = CAR_HELI_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + + CVector backPropellerPos; + if (automobile != nil) + automobile->GetComponentWorldPosition(CAR_BOOT, backPropellerPos); + else if (params.m_VehicleType == VEHICLE_TYPE_HELI) +#ifdef FIX_BUGS + backPropellerPos = +#endif + params.m_pVehicle->GetMatrix() * CVector(0.0f, -10.0f, 0.0f); + else + backPropellerPos = m_sQueueSample.m_vecPos; + + if (params.m_fDistance < SQR(CAR_HELI_ENGINE_MAX_DIST)) { + if (propellerSpeed < 0.4f) + volumeModifier = 0.0f; + else + volumeModifier = (propellerSpeed - 0.4f) * 5.0f / 3.0f; + if (!boat) { + freq = Min(1300, 7000 * freqModifier); + if (FindPlayerVehicle() == params.m_pVehicle && (accelerateState > 0 || brakeState > 0) && freq < 1300)//unnesesary freqModifier alredy <= 1300 + freq = 1300; + if (params.m_pVehicle->GetModelIndex() == MI_HUNTER) + hunterBool = TRUE; + } + + //sound from front of helicopter + Vol = (1.0f - cameraAngle) * volumeModifier * MAX_VOLUME; + if (!boat) { + if (accelerateState > 0 || brakeState > 0) + m_sQueueSample.m_nFrequency = 18000 + Min(1.0f, freqModifier * (Max(accelerateState, brakeState) / 255.0f)) * 2204; + else + m_sQueueSample.m_nFrequency = 14287 + Min(1.0f, freqModifier) * 3713; + if (propellerSpeed < 1.0f) + m_sQueueSample.m_nFrequency = (propellerSpeed + 1.0f) * (m_sQueueSample.m_nFrequency >> 1); + m_sQueueSample.m_nFrequency = Clamp2(m_sQueueSample.m_nFrequency, freqFrontPrev, 197); + freqFrontPrev = m_sQueueSample.m_nFrequency; + } + + m_sQueueSample.m_nVolume = ComputeVolume(Vol, CAR_HELI_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 3; + if (hunterBool) { + m_sQueueSample.m_nSampleIndex = SFX_HELI_APACHE_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = (volumeModifier + 1.0f) * 16000 + freq; + } else if (boat) { + m_sQueueSample.m_nSampleIndex = SFX_SEAPLANE_PRO1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + } else { + m_sQueueSample.m_nSampleIndex = SFX_CAR_HELI_MAI; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = (volumeModifier + 1.0f) * 16000 + freq; + } + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 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 = CAR_HELI_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + + + //engine starting sound + if (boat == nil && params.m_VehicleType != VEHICLE_TYPE_HELI && m_sQueueSample.m_fDistance < CAR_HELI_ENGINE_START_MAX_DIST && automobile->bEngineOn && propellerSpeed < 1.0f) { //strange way to check if automobile != nil + Vol = (1.0f - propellerSpeed / 2.0f) * CAR_HELI_ENGINE_START_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, CAR_HELI_ENGINE_START_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (hunterBool) { + m_sQueueSample.m_nSampleIndex = SFX_HELI_APACHE_4; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 3000 * propellerSpeed + 30000; + } else { + m_sQueueSample.m_nSampleIndex = SFX_CAR_HELI_STA; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 3000 * propellerSpeed + 6000; + } + m_sQueueSample.m_nCounter = 12; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 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 = CAR_HELI_ENGINE_START_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 30; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + + //after accel rotor sound + + Vol = ((cameraAngle + 1.0f) * volumeModifier * MAX_VOLUME) / 2.0f; + if (boat) { + if (accelerateState > 0 || brakeState > 0) + m_sQueueSample.m_nFrequency = 9000 + Min(1.0f, (Max(accelerateState, brakeState) / 255) * freqModifier) * 1102; + else + m_sQueueSample.m_nFrequency = 7143 + Min(1.0f, freqModifier) * 1857; + + if (propellerSpeed < 1.0f) + m_sQueueSample.m_nFrequency = (propellerSpeed + 1) * (m_sQueueSample.m_nFrequency >> 1); + + m_sQueueSample.m_nFrequency = Clamp2(m_sQueueSample.m_nFrequency, freqPropellerPrev, 98); + freqPropellerPrev = m_sQueueSample.m_nFrequency; + } + m_sQueueSample.m_nVolume = ComputeVolume(Vol, CAR_HELI_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 1; + if (hunterBool) { + m_sQueueSample.m_nSampleIndex = SFX_HELI_APACHE_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = (volumeModifier + 1) * 16000 + freq; + } else if (boat) { + m_sQueueSample.m_nSampleIndex = SFX_SEAPLANE_PRO2; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + } else { + m_sQueueSample.m_nSampleIndex = SFX_CAR_HELI_MAI2; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = (volumeModifier + 1) * 16000 + freq; + } + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 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 = CAR_HELI_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + + if (boat) { + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FIXED && m_sQueueSample.m_fDistance < CAR_HELI_SEAPLANE_MAX_DIST && propellerSpeed > 0.0f) { + m_sQueueSample.m_nVolume = ComputeVolume(propellerSpeed * CAR_HELI_SEAPLANE_VOLUME, CAR_HELI_SEAPLANE_MAX_DIST, m_sQueueSample.m_fDistance); + if (accelerateState > 0 || brakeState > 0) + m_sQueueSample.m_nFrequency = 18000 + Min(1.0f, (Max(accelerateState, brakeState) / 255.0f) * freqModifier) * 2204; + else + m_sQueueSample.m_nFrequency = 14287 + Min(1.0f, freqModifier) * 3713; + if (propellerSpeed < 1.0f) + m_sQueueSample.m_nFrequency = (propellerSpeed + 1) * (m_sQueueSample.m_nFrequency >> 1); + m_sQueueSample.m_nFrequency = Clamp2(m_sQueueSample.m_nFrequency, freqSkimmerPrev, 197); + freqSkimmerPrev = m_sQueueSample.m_nFrequency; + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_SEAPLANE_PRO4; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 12; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(propellerSpeed * CAR_HELI_SEAPLANE_VOLUME); + SET_LOOP_OFFSETS(SFX_SEAPLANE_PRO4) + m_sQueueSample.m_fSpeedMultiplier = 5.0f; + m_sQueueSample.m_MaxDistance = CAR_HELI_SEAPLANE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 7; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } else { + //vacuum cleaner sound + vecPosOld = m_sQueueSample.m_vecPos; + distanceCalculatedOld = params.m_bDistanceCalculated; + distanceOld = params.m_fDistance; + + m_sQueueSample.m_vecPos = backPropellerPos; + params.m_bDistanceCalculated = FALSE; + params.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (params.m_fDistance < SQR(CAR_HELI_REAR_MAX_DIST)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(volumeModifier * CAR_HELI_REAR_VOLUME, CAR_HELI_REAR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nSampleIndex = hunterBool ? SFX_HELI_APACHE_3 : SFX_CAR_HELI_REA; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nFrequency = (volumeModifier + 1.0f) * 16000; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(volumeModifier * CAR_HELI_REAR_VOLUME); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + m_sQueueSample.m_fSpeedMultiplier = 6.0f; + m_sQueueSample.m_MaxDistance = CAR_HELI_REAR_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + + m_sQueueSample.m_vecPos = vecPosOld; + params.m_bDistanceCalculated = distanceCalculatedOld; + params.m_fDistance = distanceOld; + } + } + return TRUE; + } + return FALSE; +} + +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; + SET_SOUND_REVERB(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->GetModelIndex() == MI_CADDY) + return TRUE; + 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_nDriveWheelsOnGround > 0) + modificator = params.m_fVelocityChange / params.m_pTransmission->fMaxReverseVelocity; + else { + if (automobile->m_nDriveWheelsOnGroundPrev > 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::ProcessModelHeliVehicle(cVehicleParams& params) +{ + static uint32 prevFreq = 22050; + + uint32 freq; + bool8 isPlayerVeh; + int16 acceletateState; + int16 brakeState; + + if (params.m_fDistance < SQR(MODEL_CAR_ENGINE_MAX_DIST)) { + if (FindPlayerVehicle() == params.m_pVehicle) + isPlayerVeh = TRUE; + else + #ifdef FIX_BUGS + isPlayerVeh = CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle == params.m_pVehicle; + #else + isPlayerVeh = CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle != nil; + #endif + if (isPlayerVeh) { + brakeState = Pads[0].GetBrake(); + acceletateState = Max(Pads[0].GetAccelerate(), Abs(Pads[0].GetCarGunUpDown()) * 2); + } else { + acceletateState = 255.0f * params.m_pVehicle->m_fGasPedal; + brakeState = 255.0f * params.m_pVehicle->m_fBrakePedal; + } + if (acceletateState < brakeState) + acceletateState = brakeState; + freq = Clamp2(5 * acceletateState + 22050, (int)prevFreq, 30); + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(MODEL_HELI_ENGINE_VOLUME, 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_CAR_RC_HELI; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFrequency = freq; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(MODEL_HELI_ENGINE_VOLUME); + SET_LOOP_OFFSETS(SFX_CAR_RC_HELI) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = MODEL_CAR_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + if (isPlayerVeh) + prevFreq = freq; + } +} + +void +cAudioManager::ProcessModelVehicle(cVehicleParams& params) +{ + static uint32 prevFreq = 14000; + static uint8 LastVol = 0; + + uint32 freq; + int16 acceletateState; + int16 brakeState; + uint8 Vol; + bool8 isPlayerVeh; + bool8 vehSlowdown; + + if (params.m_fDistance < SQR(MODEL_CAR_ENGINE_MAX_DIST)) { + if (FindPlayerVehicle() == params.m_pVehicle) + isPlayerVeh = TRUE; + else + #ifdef FIX_BUGS + isPlayerVeh = CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle == params.m_pVehicle; + #else + isPlayerVeh = CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle != nil; + #endif + if (params.m_pVehicle->m_modelIndex == MI_RCBANDIT) { + if (((CAutomobile*)params.m_pVehicle)->m_nDriveWheelsOnGround != 0) { + Vol = Min(127, 127.0f * Abs(params.m_fVelocityChange) * 3.0f); + freq = 8000.0f * Abs(params.m_fVelocityChange) + 14000; + } else { + Vol = 127; + freq = 25000; + } + if (isPlayerVeh) { + Vol = Clamp2(Vol, LastVol, 7); + freq = Clamp2(freq, prevFreq, 800); + } + 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_RC_REV; + 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(SFX_RC_REV) + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = MODEL_CAR_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 4; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + if (isPlayerVeh) { + prevFreq = freq; + LastVol = Vol; + } + } else if (params.m_pVehicle != nil) { + if (isPlayerVeh) { + acceletateState = Pads[0].GetAccelerate(); + brakeState = Pads[0].GetBrake(); + } else { + acceletateState = 255.0f * params.m_pVehicle->m_fGasPedal; + brakeState = 255.0f * params.m_pVehicle->m_fBrakePedal; + } + if (acceletateState < brakeState) + acceletateState = brakeState; + if (acceletateState <= 0) { + vehSlowdown = TRUE; + Vol = 127; + freq = 18000; + } else { + vehSlowdown = FALSE; + Vol = Min(127, (127 * acceletateState / 255) * 3.0f * Abs(params.m_fVelocityChange)); + freq = Min(22000, (8000 * acceletateState / 255 + 14000) * 3.0f * Abs(params.m_fVelocityChange)); + } + if (isPlayerVeh && !vehSlowdown) { + Vol = Clamp2(Vol, LastVol, 7); + freq = Clamp2(freq, prevFreq, 800); + } + if (!vehSlowdown) + #ifdef THIS_IS_STUPID + freq += 8000.0f * Abs(DotProduct(params.m_pVehicle->GetUp(), CVector(0.0f, 1.0f, 0.0f))); + #else + freq += 8000.0f * Abs(params.m_pVehicle->GetUp().y); + #endif + if (params.m_pVehicle->bIsDrowning) + Vol >>= 2; + 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) { + if (vehSlowdown) { + m_sQueueSample.m_nCounter = 0; + m_sQueueSample.m_nSampleIndex = SFX_RC_IDLE; + m_sQueueSample.m_nFramesToPlay = 6; + } else { + m_sQueueSample.m_nCounter = 2; + m_sQueueSample.m_nSampleIndex = SFX_RC_REV; + m_sQueueSample.m_nFramesToPlay = 4; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + 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 = 3.0f; + m_sQueueSample.m_MaxDistance = MODEL_CAR_ENGINE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + if (isPlayerVeh) { + if (vehSlowdown) { + prevFreq = freq; + LastVol = Vol; + } + } + } + } +} + +bool8 +cAudioManager::ProcessVehicleFlatTyre(cVehicleParams& params) +{ + CAutomobile* automobile; + CBike* bike; + bool8 wheelBurst; + uint8 Vol; + + float modifier; + + if (params.m_fDistance < SQR(FLAT_TYRE_MAX_DIST)) { + switch (params.m_VehicleType) { + case VEHICLE_TYPE_CAR: + automobile = (CAutomobile*)params.m_pVehicle; + wheelBurst = FALSE; + for (int i = 0; i < 4; i++) + if (automobile->Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST && automobile->m_aWheelTimer[i] > 0.0f) + wheelBurst = TRUE; + if (!wheelBurst) + return TRUE; + break; + case VEHICLE_TYPE_BIKE: + bike = (CBike*)params.m_pVehicle; + wheelBurst = FALSE; + for(int i = 0; i < 2; i++) + if (bike->m_wheelStatus[i] == WHEEL_STATUS_BURST && bike->m_aWheelTimer[i] > 0.0f) + wheelBurst = TRUE; + if (!wheelBurst) + return TRUE; + break; + default: + return TRUE; + } + modifier = Min(1.0f, Abs(params.m_fVelocityChange) / (0.3f * params.m_pTransmission->fMaxVelocity)); + if (modifier > 0.01f) { + Vol = (FLAT_TYRE_VOLUME * modifier); + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + m_sQueueSample.m_nVolume = ComputeVolume(Vol, FLAT_TYRE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 95; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 5; + m_sQueueSample.m_nSampleIndex = SFX_TYRE_BURST_L; + m_sQueueSample.m_nFrequency = (5500.0f * modifier) + 8000; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(SFX_TYRE_BURST_L) + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_MaxDistance = FLAT_TYRE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessVehicleRoadNoise(cVehicleParams& params) +{ + uint8 Vol; + uint32 freq; + float multiplier; + int sampleFreq; + float velocity; + uint8 wheelsOnGround; + + if (params.m_fDistance < SQR(VEHICLE_ROAD_NOISE_MAX_DIST)) { + switch (params.m_VehicleType) { + case VEHICLE_TYPE_CAR: + wheelsOnGround = ((CAutomobile*)params.m_pVehicle)->m_nWheelsOnGround; + break; + case VEHICLE_TYPE_BIKE: + wheelsOnGround = ((CBike*)params.m_pVehicle)->m_nWheelsOnGround; + break; + default: + wheelsOnGround = 4; + break; + } + if ((params.m_pTransmission != nil) && (wheelsOnGround > 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; + SET_SOUND_REVERB(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; + uint8 wheelsOnGround; + + if (params.m_fDistance < SQR(WET_ROAD_NOISE_MAX_DIST)) { + switch (params.m_VehicleType) { + case VEHICLE_TYPE_CAR: + wheelsOnGround = ((CAutomobile*)params.m_pVehicle)->m_nWheelsOnGround; + break; + case VEHICLE_TYPE_BIKE: + wheelsOnGround = ((CBike*)params.m_pVehicle)->m_nWheelsOnGround; + break; + default: + wheelsOnGround = 4; + break; + } + if ((params.m_pTransmission != nil) && (wheelsOnGround > 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; + SET_SOUND_REVERB(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; + uint8 wheelsOnGround; + uint8 wheelsOnGroundPrev; + CBike *bike; + tWheelState *wheelState; + float *gasPedalAudioPtr; + + bool8 isMoped = FALSE; + bool8 isGolfCart = FALSE; + 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); + if (FindPlayerVehicle() == params.m_pVehicle && params.m_pVehicle->GetModelIndex() != MI_CADDY) + ProcessPlayersVehicleEngine(params, params.m_pVehicle); + else { + transmission = params.m_pTransmission; + if (transmission != nil) { + switch (params.m_pVehicle->GetModelIndex()) { + case MI_PIZZABOY: + case MI_FAGGIO: + isMoped = TRUE; + currentGear = transmission->nNumberOfGears; + break; + case MI_CADDY: + currentGear = transmission->nNumberOfGears; + isGolfCart = TRUE; + break; + default: + currentGear = params.m_pVehicle->m_nCurrentGear; + break; + } + switch (params.m_VehicleType) { + case VEHICLE_TYPE_CAR: + automobile = (CAutomobile*)params.m_pVehicle; + wheelsOnGround = automobile->m_nDriveWheelsOnGround; + wheelsOnGroundPrev = automobile->m_nDriveWheelsOnGroundPrev; + wheelState = automobile->m_aWheelState; + gasPedalAudioPtr = &automobile->m_fGasPedalAudio; + break; + case VEHICLE_TYPE_BIKE: + bike = (CBike*)params.m_pVehicle; + wheelsOnGround = bike->m_nDriveWheelsOnGround; + wheelsOnGroundPrev = bike->m_nDriveWheelsOnGroundPrev; + wheelState = bike->m_aWheelState; + gasPedalAudioPtr = &bike->m_fGasPedalAudio; + break; + default: + debug(" ** AUDIOLOG: Unrecognised vehicle type %d in ProcessVehicleEngine() * \n", params.m_VehicleType); + return TRUE; + } + if (wheelsOnGround > 0) { + if (params.m_pVehicle->bIsHandbrakeOn && (!isMoped || !isGolfCart)) { // what a weird check + if (params.m_fVelocityChange == 0.0f) + traction = 0.9f; + } else if (params.m_pVehicle->GetStatus() == STATUS_SIMPLE || isMoped || isGolfCart) { + traction = 0.0f; + } else { + switch (transmission->nDriveType) { + case '4': + if (params.m_VehicleType == VEHICLE_TYPE_BIKE) { + for (uint8 i = 0; i < 2; i++) + if (wheelState[i] == WHEEL_STATE_SPINNING) + traction += 0.1f; + } else { + for (uint8 i = 0; i < 4; i++) + if (wheelState[i] == WHEEL_STATE_SPINNING) + traction += 0.05f; + } + break; + case 'F': + if (params.m_VehicleType == VEHICLE_TYPE_BIKE) { + if (wheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SPINNING) + traction += 0.2f; + } else { + if (wheelState[CARWHEEL_FRONT_LEFT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + if (wheelState[CARWHEEL_FRONT_RIGHT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + } + break; + case 'R': + if (params.m_VehicleType == VEHICLE_TYPE_BIKE) { + if (wheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING) + traction += 0.2f; + } else { + if (wheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + if (wheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SPINNING) + traction += 0.1f; + } + break; + default: + break; + } + } + if (transmission->fMaxVelocity > 0.0f) { + if (isMoped || isGolfCart) + modificator = Min(1.0f, Abs(params.m_fVelocityChange / transmission->fMaxVelocity > 1.0f)); + else { + if (currentGear > 0) { + relativeGearChange = Min(1.0f, + params.m_fVelocityChange - transmission->Gears[currentGear].fShiftDownVelocity) / transmission->fMaxVelocity * 2.5f; + if (traction == 0.0f && params.m_pVehicle->GetStatus() != STATUS_SIMPLE && + params.m_fVelocityChange < transmission->Gears[1].fShiftUpVelocity) + traction = 0.7f; + modificator = traction * *gasPedalAudioPtr * 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 (wheelsOnGroundPrev > 0) + *gasPedalAudioPtr *= 0.4f; + modificator = *gasPedalAudioPtr; + } + if (currentGear == 0 || wheelsOnGround > 0) { + if (params.m_VehicleType == VEHICLE_TYPE_BIKE) + freq = 22050; + else + 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; + } + if (params.m_pVehicle->bIsDrowning) + Vol >>= 2; + if (isGolfCart) { + Vol = 100 * modificator; + freq = 2130 * modificator + 4270; + m_sQueueSample.m_nCounter = 2; + } + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + if (!isGolfCart) { + if (params.m_pVehicle->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 = 10000 * modificator + 22050; + m_sQueueSample.m_nCounter = 52; + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nAccelerationSampleIndex; + m_sQueueSample.m_nCounter = 2; + } + } else { + if (params.m_pVehicle->m_fGasPedal < 0.02f) { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nBank - CAR_SFX_BANKS_OFFSET + SFX_CAR_IDLE_1; + freq = 10000 * modificator + 22050; + m_sQueueSample.m_nCounter = 52; + } else { + m_sQueueSample.m_nSampleIndex = aVehicleSettings[params.m_nIndex].m_nAccelerationSampleIndex; + m_sQueueSample.m_nCounter = 2; + } + } + } + if (isGolfCart) { + if (FindVehicleOfPlayer() == params.m_pVehicle) + m_sQueueSample.m_nSampleIndex = SFX_CAR_AFTER_ACCEL_12; + else + m_sQueueSample.m_nSampleIndex = SFX_CAR_REV_12; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; // TODO: PS2 bank + m_sQueueSample.m_nFrequency = freq + 20 * m_sQueueSample.m_nEntityIndex % 100; + } else { + m_sQueueSample.m_nBankIndex = SFX_BANK_0; // TODO: PS2 bank + m_sQueueSample.m_nFrequency = freq + 100 * m_sQueueSample.m_nEntityIndex % 1000; + } + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nPriority = 3; + if (m_sQueueSample.m_nSampleIndex == SFX_CAR_IDLE_5 || m_sQueueSample.m_nSampleIndex == SFX_CAR_REV_5) + 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +void +cAudioManager::UpdateGasPedalAudio(CVehicle* veh, int vehType) +{ + float gasPedal = Abs(veh->m_fGasPedal); + float* gasPealAudioPtr; + + switch(vehType) { + case VEHICLE_TYPE_CAR: gasPealAudioPtr = &((CAutomobile *)veh)->m_fGasPedalAudio; break; + case VEHICLE_TYPE_BIKE: gasPealAudioPtr = &((CBike *)veh)->m_fGasPedalAudio; break; + default: return; + } + if (*gasPealAudioPtr < gasPedal) + *gasPealAudioPtr = Min(*gasPealAudioPtr + 0.09f, gasPedal); + else + *gasPealAudioPtr = Max(*gasPealAudioPtr - 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } +} + +void +cAudioManager::ProcessCesna(cVehicleParams ¶ms) +{ + 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 = 4.0f; + m_sQueueSample.m_MaxDistance = CESNA_IDLE_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + SET_SOUND_REVERB(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 = 3; + 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 = 4.0f; + m_sQueueSample.m_MaxDistance = CESNA_REV_MAX_DIST; + m_sQueueSample.m_bStatic = FALSE; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +void +cAudioManager::ProcessPlayersVehicleEngine(cVehicleParams& params, CVehicle* veh) +{ + static int32 GearFreqAdj[] = { 6000, 6000, 3400, 1200, 0, -1000 }; + + float relativeVelocityChange; + float accelerationMultipler; + uint8 wheelInUseCounter; + float time; + int baseFreq; + uint8 vol; + int32 freq; + + int freqModifier; + uint32 soundOffset; + uint8 engineSoundType; + int16 accelerateState; + int16 brakeState; + bool8 channelUsed; + + uint8 currentGear; + float gasPedalAudio; + CVector pos; + bool8 slowingDown; + + tWheelState *wheelState; + CAutomobile *automobile; + CBike *bike; + float *gasPedalAudioPtr; + + uint32 gearSoundLength; + uint8 wheelsOnGround; + uint8 wheelsOnGroundPrev; + + float velocityChangeForAudio; + bool8 noGearBox; + bool8 stuckInSand; + + static int16 LastAccel = 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 isMoped = FALSE; + bool8 processedAccelSampleStopped = FALSE; + static uint32 gearSoundStartTime = CTimer::GetTimeInMilliseconds(); +#ifdef GTA_PS2 + uint8 nChannel = m_bIsSurround ? CHANNEL_DTS_PLAYER_VEHICLE_ENGINE : CHANNEL_PLAYER_VEHICLE_ENGINE; +#else + uint8 nChannel = CHANNEL_PLAYER_VEHICLE_ENGINE; +#endif + if (bPlayerJustEnteredCar) { + bAccelSampleStopped = TRUE; + bPlayerJustEnteredCar = FALSE; + nCruising = 0; + LastAccel = 0; + bLostTractionLastFrame = FALSE; + CurrentPretendGear = 1; + bHandbrakeOnLastFrame = FALSE; + } +#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(params.m_pVehicle->m_fGasPedal, 0.0f, 1.0f)); + brakeState = (255 * Clamp(params.m_pVehicle->m_fBrakePedal, 0.0f, 1.0f)); + } else { + accelerateState = Pads[0].GetAccelerate(); + brakeState = Pads[0].GetBrake(); + } + slowingDown = params.m_fVelocityChange < -0.001f; + channelUsed = SampleManager.GetChannelUsedFlag(nChannel); + if (params.m_pVehicle->GetModelIndex() == MI_PIZZABOY || params.m_pVehicle->GetModelIndex() == MI_FAGGIO) { + CurrentPretendGear = params.m_pTransmission->nNumberOfGears; + currentGear = CurrentPretendGear; + if (params.m_pVehicle->bIsHandbrakeOn) { + brakeState = 0; + nCruising = 0; + LastAccel = 0; + accelerateState = 0; + } else + nCruising = 1; + isMoped = TRUE; + } else + currentGear = params.m_pVehicle->m_nCurrentGear; + + switch (params.m_VehicleType) { + case VEHICLE_TYPE_CAR: + automobile = (CAutomobile*)params.m_pVehicle; + wheelsOnGround = automobile->m_nDriveWheelsOnGround; + wheelsOnGroundPrev = automobile->m_nDriveWheelsOnGroundPrev; + gasPedalAudioPtr = &automobile->m_fGasPedalAudio; + wheelState = automobile->m_aWheelState; + velocityChangeForAudio = automobile->m_fVelocityChangeForAudio; + break; + case VEHICLE_TYPE_BIKE: + bike = (CBike*)params.m_pVehicle; + wheelsOnGround = bike->m_nDriveWheelsOnGround; + wheelsOnGroundPrev = bike->m_nDriveWheelsOnGroundPrev; + gasPedalAudioPtr = &bike->m_fGasPedalAudio; + wheelState = bike->m_aWheelState; + velocityChangeForAudio = bike->m_fVelocityChangeForAudio; + break; + default: + debug(" ** AUDIOLOG: Unrecognised vehicle type %d in ProcessVehicleEngine() * \n", params.m_VehicleType); + return; + } + if (!isMoped) { + switch (params.m_pTransmission->nDriveType) { + case '4': + if (params.m_VehicleType != VEHICLE_TYPE_BIKE) { + wheelInUseCounter = 0; + for (uint8 i = 0; i < 4; i++) { + if (wheelState[i] != WHEEL_STATE_NORMAL) + wheelInUseCounter++; + } + if (wheelInUseCounter > 2) + lostTraction = TRUE; + } + break; + case 'F': + if (params.m_VehicleType == VEHICLE_TYPE_BIKE) { + if (wheelState[BIKEWHEEL_FRONT] != WHEEL_STATE_NORMAL) + lostTraction = TRUE; + } else { + if ((wheelState[CARWHEEL_FRONT_LEFT] != WHEEL_STATE_NORMAL || wheelState[CARWHEEL_FRONT_RIGHT] != WHEEL_STATE_NORMAL) && + (wheelState[CARWHEEL_REAR_LEFT] != WHEEL_STATE_NORMAL || wheelState[CARWHEEL_REAR_RIGHT] != WHEEL_STATE_NORMAL)) + lostTraction = TRUE; + } + break; + case 'R': + if (params.m_VehicleType == VEHICLE_TYPE_BIKE) { + if (wheelState[BIKEWHEEL_REAR] != WHEEL_STATE_NORMAL) + lostTraction = TRUE; + } else { + if (wheelState[CARWHEEL_REAR_LEFT] != WHEEL_STATE_NORMAL || wheelState[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; + + if (params.m_VehicleType == VEHICLE_TYPE_BIKE && bike->bExtraSpeed) + freqModifier += 1400; + + gearSoundLength = 0; + engineSoundType = aVehicleSettings[params.m_nIndex].m_nBank; + soundOffset = 3 * (engineSoundType - CAR_SFX_BANKS_OFFSET); + noGearBox = FALSE; + switch (engineSoundType) { + case SFX_BANK_PONTIAC: + gearSoundLength = 2526; + break; + case SFX_BANK_PORSCHE: + gearSoundLength = 3587; + break; + case SFX_BANK_SPIDER: + gearSoundLength = 4898; + break; + case SFX_BANK_MERC: + gearSoundLength = 4003; + break; + case SFX_BANK_TRUCK: + gearSoundLength = 6289; + break; + case SFX_BANK_HOTROD: + gearSoundLength = 2766; + break; + case SFX_BANK_COBRA: + gearSoundLength = 3523; + break; + case SFX_BANK_PONTIAC_SLOW: + gearSoundLength = 2773; + break; + case SFX_BANK_CADILLAC: + gearSoundLength = 2560; + break; + case SFX_BANK_PATHFINDER: + gearSoundLength = 4228; + break; + case SFX_BANK_PACARD: + gearSoundLength = 4648; + break; + case SFX_BANK_VTWIN: + gearSoundLength = 3480; + break; + case SFX_BANK_HONDA250: + gearSoundLength = 2380; + break; + case SFX_BANK_SPORTS_BIKE: + gearSoundLength = 2410; + break; + default: + noGearBox = TRUE; + break; + } + if (channelUsed && nCruising == 0 && !noGearBox) { + gearSoundLength -= 1000; + if (CTimer::GetTimeInMilliseconds() - gearSoundStartTime > gearSoundLength) { + channelUsed = FALSE; + gearSoundStartTime = CTimer::GetTimeInMilliseconds(); + } + } else + gearSoundStartTime = CTimer::GetTimeInMilliseconds(); + relativeVelocityChange = 2.0f * params.m_fVelocityChange / params.m_pTransmission->fMaxVelocity; + accelerationMultipler = Clamp(relativeVelocityChange, 0.0f, 1.0f); + gasPedalAudio = accelerationMultipler; + switch (engineSoundType) { + case SFX_BANK_MOPED: + soundOffset++; + break; + case SFX_BANK_HONDA250: + soundOffset += 2; + break; + case SFX_BANK_SPORTS_BIKE: + soundOffset += 3; + break; + default: + break; + } + if (accelerateState > 0) { + if (nCruising > 0) { +PlayCruising: + bAccelSampleStopped = TRUE; + SampleManager.StopChannel(nChannel); + if (!isMoped && (accelerateState < 150 || wheelsOnGround == 0 || brakeState > 0 || params.m_pVehicle->bIsHandbrakeOn + || lostTraction || currentGear < params.m_pTransmission->nNumberOfGears - 1)) { + nCruising = 0; + } else { + if (accelerateState < 220 || params.m_fVelocityChange + 0.001f < velocityChangeForAudio) { + 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 { + stuckInSand = params.m_VehicleType == VEHICLE_TYPE_CAR && ((CAutomobile*)params.m_pVehicle)->bStuckInSand; + if (accelerateState < 150 || wheelsOnGround == 0 || params.m_pVehicle->bIsHandbrakeOn || lostTraction + || (currentGear < 2 && params.m_fVelocityChange - velocityChangeForAudio < 0.01f) || brakeState > 0) { + if (((wheelsOnGround == 0 || params.m_pVehicle->bIsHandbrakeOn || lostTraction) && !stuckInSand) || brakeState > 0) { + if (wheelsOnGround == 0 && wheelsOnGroundPrev != 0 || (params.m_pVehicle->bIsHandbrakeOn && !bHandbrakeOnLastFrame || lostTraction && !bLostTractionLastFrame) + && wheelsOnGround != 0) { + *gasPedalAudioPtr *= 0.6f; + } + freqModifier = 0; + if (engineSoundType == SFX_BANK_GOLF_CART || engineSoundType == SFX_BANK_CAR_CHAINSAW) + baseFreq = (15000 * *gasPedalAudioPtr) + 14000; + else + baseFreq = (25000 * *gasPedalAudioPtr) + 14000; + vol = (25 * *gasPedalAudioPtr) + 60; + } else { + baseFreq = (8000 * accelerationMultipler) + 16000; + vol = (25 * accelerationMultipler) + 60; + *gasPedalAudioPtr = accelerationMultipler; + } + freq = freqModifier + baseFreq; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + if (channelUsed) { + SampleManager.StopChannel(nChannel); + bAccelSampleStopped = TRUE; + } + if (params.m_pVehicle->bIsDrowning) + vol >>= 2; + 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; + params.m_pVehicle->bAudioChangingGear = TRUE; + goto PlayCruising; + } + } + + gearSoundStartTime = CTimer::GetTimeInMilliseconds(); + params.m_pVehicle->bAudioChangingGear = TRUE; +#ifdef GTA_PS2 + SampleManager.InitialiseChannel(nChannel, 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(nChannel, ComputeVolume(PLAYER_VEHICLE_ENGINE_VOLUME, VEHICLE_ENGINE_MAX_DIST, m_sQueueSample.m_fDistance)); + SampleManager.SetChannelPan(nChannel, 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(nChannel, freq * CTimer::GetTimeScale()); +#else + SampleManager.SetChannelFrequency(nChannel, freq); +#endif + if (!channelUsed) { +#ifdef AUDIO_REVERB + SampleManager.SetChannelReverbFlag(nChannel, m_bDynamicAcousticModelingStatus != FALSE); +#endif + SampleManager.StartChannel(nChannel); + } + } + } + } else { + if (slowingDown) { + if (channelUsed) { + SampleManager.StopChannel(nChannel); + bAccelSampleStopped = TRUE; + } + if (wheelsOnGround == 0 || params.m_pVehicle->bIsHandbrakeOn || lostTraction) + gasPedalAudio = *gasPedalAudioPtr; + else if (params.m_VehicleType == VEHICLE_TYPE_BIKE) + gasPedalAudio = 0.0f; + else + gasPedalAudio = Min(1.0f, params.m_fVelocityChange / params.m_pTransmission->fMaxReverseVelocity); + *gasPedalAudioPtr = Max(0.0f, gasPedalAudio); + } else if (LastAccel > 0) { + if (channelUsed) { + SampleManager.StopChannel(nChannel); + bAccelSampleStopped = TRUE; + } + nCruising = 0; + if (wheelsOnGround == 0 + || params.m_pVehicle->bIsHandbrakeOn + || lostTraction + || params.m_fVelocityChange < 0.01f && *gasPedalAudioPtr > 0.2f) { + if (params.m_pVehicle->GetModelIndex() == MI_PIZZABOY || params.m_pVehicle->GetModelIndex() == MI_FAGGIO) { + gasPedalAudio = 0.0f; + } else { + *gasPedalAudioPtr *= 0.6f; + gasPedalAudio = *gasPedalAudioPtr; + } + } + if (gasPedalAudio > 0.05f) { + freq = (5000 * ((gasPedalAudio - 0.05f) / 0.95f)) + 19000; + vol = (25 * ((gasPedalAudio - 0.05f) / 0.95f)) + 40; + if (params.m_pVehicle->bIsDrowning) + vol >>= 2; + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + AddPlayerCarSample(vol, freq, soundOffset + SFX_CAR_FINGER_OFF_ACCEL_1, engineSoundType, 63, FALSE); + } + } + freq = (10000 * gasPedalAudio) + 22050; + vol = 110 - (40 * gasPedalAudio); + if (engineSoundType == SFX_BANK_TRUCK) + freq >>= 1; + if (params.m_pVehicle->bIsDrowning) + vol >>= 2; + AddPlayerCarSample(vol, freq, engineSoundType - CAR_SFX_BANKS_OFFSET + SFX_CAR_IDLE_1, SFX_BANK_0, 52, TRUE); + + CurrentPretendGear = Max(1, currentGear); + } + LastAccel = accelerateState; + + bHandbrakeOnLastFrame = !!params.m_pVehicle->bIsHandbrakeOn; + bLostTractionLastFrame = lostTraction; +} + +bool8 +cAudioManager::ProcessVehicleSkidding(cVehicleParams& params) +{ + CAutomobile *automobile; + CBike *bike; + uint8 numWheels; + uint8 wheelsOnGround; + float gasPedalAudio; + tWheelState* wheelStateArr; + + + cTransmission *transmission; + uint8 Vol; + float newSkidVal = 0.0f; + float skidVal = 0.0f; + + if (params.m_fDistance < SQR(VEHICLE_SKIDDING_MAX_DIST)) { + switch (params.m_VehicleType) { + case VEHICLE_TYPE_CAR: + automobile = (CAutomobile*)params.m_pVehicle; + numWheels = 4; + wheelStateArr = automobile->m_aWheelState; + wheelsOnGround = automobile->m_nWheelsOnGround; + gasPedalAudio = automobile->m_fGasPedalAudio; + break; + case VEHICLE_TYPE_BIKE: + bike = (CBike*)params.m_pVehicle; + numWheels = 2; + wheelStateArr = bike->m_aWheelState; + wheelsOnGround = bike->m_nWheelsOnGround; + gasPedalAudio = bike->m_fGasPedalAudio; + break; + default: + debug("\n * AUDIOLOG: ProcessVehicleSkidding() Unsupported vehicle type %d * \n", params.m_VehicleType); + return TRUE; + } + if (wheelsOnGround > 0) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + + for (uint8 i = 0; i < numWheels; i++) { + if (wheelStateArr[i] == WHEEL_STATE_NORMAL) + continue; + transmission = params.m_pTransmission; + switch (transmission->nDriveType) { + case '4': + newSkidVal = GetVehicleDriveWheelSkidValue(params.m_pVehicle, wheelStateArr[i], gasPedalAudio, transmission, params.m_fVelocityChange); + break; + case 'F': + if (i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT) + newSkidVal = GetVehicleDriveWheelSkidValue(params.m_pVehicle, wheelStateArr[i], gasPedalAudio, transmission, params.m_fVelocityChange); + else + newSkidVal = GetVehicleNonDriveWheelSkidValue(params.m_pVehicle, wheelStateArr[i], transmission, params.m_fVelocityChange); + break; + case 'R': + if (i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT) + newSkidVal = GetVehicleDriveWheelSkidValue(params.m_pVehicle, wheelStateArr[i], gasPedalAudio, transmission, params.m_fVelocityChange); + else + newSkidVal = GetVehicleNonDriveWheelSkidValue(params.m_pVehicle, wheelStateArr[i], 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: + case SURFACE_SAND_BEACH: + 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; + if (params.m_VehicleType == VEHICLE_TYPE_BIKE) + m_sQueueSample.m_nFrequency += 2000; + 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} + +float +cAudioManager::GetVehicleDriveWheelSkidValue(CVehicle *veh, tWheelState wheelState, float gasPedalAudio, cTransmission *transmission, float velocityChange) +{ + float relativeVelChange = 0.0f; + float velChange; + float relativeVel; + + switch (wheelState) + { + 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(veh->m_vecTurnSpeed.z) * 20.0f)); +} + +float +cAudioManager::GetVehicleNonDriveWheelSkidValue(CVehicle *veh, tWheelState wheelState, cTransmission *transmission, float velocityChange) +{ + float relativeVelChange = 0.0f; + + if (wheelState == WHEEL_STATE_SKIDDING) + relativeVelChange = Min(1.0f, Abs(velocityChange) / transmission->fMaxVelocity); + + return Max(relativeVelChange, Min(1.0f, Abs(veh->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)) + return TRUE; + + if (params.m_pVehicle->GetModelIndex() == MI_MRWHOOP) + return TRUE; + + if (params.m_pVehicle->IsAlarmOn()) + 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(params.m_pVehicle->bIsDrowning ? VEHICLE_HORN_VOLUME / 4 : 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; +#ifdef FIX_BUGS + SET_EMITTING_VOLUME(params.m_pVehicle->bIsDrowning ? VEHICLE_HORN_VOLUME / 4 : VEHICLE_HORN_VOLUME); +#else + SET_EMITTING_VOLUME(VEHICLE_HORN_VOLUME); +#endif + 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 = 4; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::UsesSiren(cVehicleParams& params) +{ + return params.m_pVehicle->UsesSiren(); +} + +bool8 +cAudioManager::UsesSirenSwitching(cVehicleParams& params) +{ + if (params.m_nIndex == FIRETRUK || params.m_nIndex == MRWHOOP) + return FALSE; + return UsesSiren(params); +} + +bool8 +cAudioManager::ProcessVehicleSirenOrAlarm(cVehicleParams& params) +{ + uint8 Vol; + + if (params.m_fDistance < SQR(VEHICLE_SIREN_MAX_DIST)) { + CVehicle *veh = params.m_pVehicle; + if (veh->m_bSirenOrAlarm || veh->IsAlarmOn()) { + if (veh->IsAlarmOn()) { + if (CTimer::GetTimeInMilliseconds() > veh->m_nCarHornTimer) + veh->m_nCarHornTimer = CTimer::GetTimeInMilliseconds() + 750; + + if (veh->m_nCarHornTimer < CTimer::GetTimeInMilliseconds() + 375) + return TRUE; + } + + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + Vol = veh->bIsDrowning ? VEHICLE_SIREN_VOLUME / 4 : VEHICLE_SIREN_VOLUME; + m_sQueueSample.m_nVolume = ComputeVolume(Vol, VEHICLE_SIREN_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = 5; + if (UsesSiren(params)) { + if (params.m_pVehicle->GetStatus() == STATUS_ABANDONED) + return TRUE; + if (veh->m_nCarHornTimer > 0 && params.m_nIndex != FIRETRUK && params.m_nIndex != MRWHOOP) { + m_sQueueSample.m_nSampleIndex = SFX_SIREN_FAST; + if (params.m_nIndex == FBIRANCH) + m_sQueueSample.m_nFrequency = 12668; + else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SIREN_FAST); + m_sQueueSample.m_nCounter = 60; + } else if (params.m_nIndex == VICECHEE) { + m_sQueueSample.m_nSampleIndex = SFX_POLICE_SIREN_SLOW; + m_sQueueSample.m_nFrequency = 11440; + } 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_nHornSample; + m_sQueueSample.m_nFrequency = aVehicleSettings[params.m_nIndex].m_nHornFrequency; + } + 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(Vol); + 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::UsesReverseWarning(uint32 model) +{ + return model == LINERUN || model == FIRETRUK || model == BUS || model == COACH || model == PACKER || model == FLATBED; +} + +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(veh->bIsDrowning ? VEHICLE_REVERSE_WARNING_VOLUME / 4 : 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; +#ifdef FIX_BUGS + SET_EMITTING_VOLUME(veh->bIsDrowning ? VEHICLE_REVERSE_WARNING_VOLUME / 4 : VEHICLE_REVERSE_WARNING_VOLUME); +#else + SET_EMITTING_VOLUME(VEHICLE_REVERSE_WARNING_VOLUME); +#endif + 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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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 == BARRACKS + || model == COACH || model == PACKER || model == FLATBED; +} + +bool8 +cAudioManager::ProcessEngineDamage(cVehicleParams& params) +{ + uint8 Vol; + + if (params.m_fDistance < SQR(ENGINE_DAMAGE_MAX_DIST)) { + if (params.m_pVehicle->m_modelIndex == MI_CADDY) + return TRUE; + if (params.m_pVehicle->GetStatus() == STATUS_WRECKED || params.m_pVehicle->m_fHealth >= 390) + return TRUE; + if (params.m_pVehicle->m_fHealth < 250.0f) { + Vol = 60; + m_sQueueSample.m_nSampleIndex = SFX_CAR_ON_FIRE; + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_ON_FIRE); + } else { + Vol = 30; + m_sQueueSample.m_nSampleIndex = SFX_PALM_TREE_LO; + m_sQueueSample.m_nPriority = 7; + m_sQueueSample.m_nFrequency = 27000; + } + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + if (params.m_pVehicle->bIsDrowning) + Vol >>= 2; + 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + return TRUE; + } + return FALSE; +} + +bool8 +cAudioManager::ProcessCarBombTick(cVehicleParams& params) +{ + uint8 bombType; + + if (params.m_fDistance < SQR(CAR_BOMB_TICK_MAX_DIST)) { + if (params.m_pVehicle->bEngineOn) { + switch (params.m_VehicleType) { + case VEHICLE_TYPE_CAR: + bombType = params.m_pVehicle->m_bombType; + break; + case VEHICLE_TYPE_BIKE: + bombType = params.m_pVehicle->m_bombType; + break; + default: + debug("\n * AUDIOLOG: ProcessCarBombTick() Unsupported vehicle type %d * \n", params.m_VehicleType); + return TRUE; + } + if (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; + SET_SOUND_REVERB(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; + bool8 stereo; + float maxDist; + static uint8 WaveIndex = 41; + static uint8 GunIndex = 53; + cPedParams pedParams; + static uint32 WaterFallFrame = 0; + + for (uint16 i = 0; i < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; i++) { + bLoop = FALSE; + stereo = FALSE; + SET_SOUND_REFLECTION(FALSE); + event = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]; + switch (event) { + case SOUND_HELI_BLADE: + { + static uint8 HeliIndex = 89; + eventRelVol = ((CAutomobile*)params.m_pVehicle)->m_aWheelSpeed[1] * 50.0f / 11.0f; + if (eventRelVol < 0.2f || eventRelVol == 1.0f) + continue; + Vol = (1.0f - eventRelVol) * VEHICLE_ONE_SHOT_HELI_BLADE_VOLUME; + maxDist = SQR(VEHICLE_ONE_SHOT_HELI_BLADE_MAX_DIST); + m_sQueueSample.m_nSampleIndex = SFX_CAR_HELI_ROT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = HeliIndex++; + if (HeliIndex > 90) + HeliIndex = 89; + m_sQueueSample.m_nFrequency = (8000 * eventRelVol) + 16000; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_HELI_BLADE_MAX_DIST; + break; + } + 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 + if (params.m_pVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI) + m_sQueueSample.m_nFrequency = 23459; + else + 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 + if (params.m_pVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI) + m_sQueueSample.m_nFrequency = 28062; + else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 32); + 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: + if (params.m_pVehicle->GetVehicleAppearance() != VEHICLE_APPEARANCE_CAR + || params.m_pVehicle->m_modelIndex == MI_CADDY) + continue; + 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_91: + 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: + if (m_FrameCounter <= WaterFallFrame) + continue; + WaterFallFrame = m_FrameCounter + 6; + 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 <= 150) + continue; + if (eventVol > 800) + m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] = 800; + eventRelVol = (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] - 150) / (800 - 150); + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[0] & 1) + 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_CAR_JUMP: + case SOUND_CAR_JUMP_2: + { + static uint8 iWheelIndex = 82; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_JUMP_MAX_DIST); +#ifdef THIS_IS_STUPID + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i] == SOUND_CAR_JUMP_2) { +#else + if (event == SOUND_CAR_JUMP_2) { +#endif + m_sQueueSample.m_nSampleIndex = SFX_TYRE_BURST_B; + Vol = Max(50, 2 * (60 * m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i])); + } else { + m_sQueueSample.m_nSampleIndex = SFX_TYRE_BUMP; + Vol = Max(VEHICLE_ONE_SHOT_CAR_JUMP_VOLUME, 2 * (100 * m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i])); + } + 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_VehicleType == VEHICLE_TYPE_BIKE) + m_sQueueSample.m_nFrequency <<= 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_CAR_TYRE_POP: + { + static uint8 WheelIndex = 91; + m_sQueueSample.m_nSampleIndex = SFX_TYRE_BURST; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = WheelIndex++; + if (WheelIndex > 94) + WheelIndex = 91; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TYRE_BURST); + m_sQueueSample.m_nFrequency += RandomDisplacement(2000); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_CAR_TYRE_POP_MAX_DIST; + maxDist = SQR(VEHICLE_ONE_SHOT_CAR_TYRE_POP_MAX_DIST); + Vol = m_anRandomTable[4] % (MAX_VOLUME-VEHICLE_ONE_SHOT_CAR_TYRE_POP_VOLUME) + VEHICLE_ONE_SHOT_CAR_TYRE_POP_VOLUME; + break; + } + case SOUND_WEAPON_SHOT_FIRED: + switch (params.m_pVehicle->m_modelIndex) { + case MI_HUNTER: + case MI_CHOPPER: + case MI_SEASPAR: + case MI_SPARROW: + case MI_MAVERICK: + case MI_VCNMAV: + if (params.m_pVehicle->m_modelIndex == MI_HUNTER) { + if (Pads[0].GetHandBrake() == 0) { + if (FindVehicleOfPlayer() != params.m_pVehicle) { + m_sQueueSample.m_nSampleIndex = SFX_M60_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + } else { + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + } + } else { + m_sQueueSample.m_nSampleIndex = SFX_M60_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + } + } else { + m_sQueueSample.m_nSampleIndex = SFX_M60_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + } + maxDist = SQR(VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST); + m_sQueueSample.m_nCounter = GunIndex++; + Vol = MAX_VOLUME; + if (GunIndex > 58) + GunIndex = 53; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_M60_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + stereo = TRUE; + break; + default: + { + maxDist = SQR(VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST); +#ifdef FIX_BUGS + uint32 sampleIndex; + uint32 frequency; + CPed *pPed = params.m_pVehicle->pDriver; + if(!pPed) + continue; + if(!pPed->HasWeaponSlot(WEAPONSLOT_SUBMACHINEGUN) || (params.m_pVehicle->GetModelIndex() == MI_PREDATOR && !pPed->IsPedDoingDriveByShooting())) { + sampleIndex = SFX_UZI_LEFT; + frequency = SampleManager.GetSampleBaseFrequency(sampleIndex); + frequency += RandomDisplacement(frequency >> 5); + } else + switch(pPed->GetWeapon(WEAPONSLOT_SUBMACHINEGUN).m_eWeaponType) { + case WEAPONTYPE_TEC9: + sampleIndex = SFX_TEC_LEFT; + frequency = RandomDisplacement(500) + 17000; + break; + case WEAPONTYPE_SILENCED_INGRAM: + sampleIndex = SFX_TEC_LEFT; + frequency = RandomDisplacement(1000) + 34000; + break; + case WEAPONTYPE_MP5: + sampleIndex = SFX_MP5_LEFT; + frequency = SampleManager.GetSampleBaseFrequency(sampleIndex); + frequency += RandomDisplacement(frequency >> 5); + break; + default: + sampleIndex = SFX_UZI_LEFT; + frequency = SampleManager.GetSampleBaseFrequency(sampleIndex); + frequency += RandomDisplacement(frequency >> 5); + break; + } + m_sQueueSample.m_nSampleIndex = sampleIndex; +#else + m_sQueueSample.m_nSampleIndex = SFX_UZI_LEFT; +#endif + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = GunIndex++; + Vol = m_anRandomTable[2] % 15 + VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_VOLUME; + if(GunIndex > 58) GunIndex = 53; +#ifdef FIX_BUGS + m_sQueueSample.m_nFrequency = frequency; +#else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_UZI_LEFT); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); +#endif + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = VEHICLE_ONE_SHOT_WEAPON_SHOT_FIRED_MAX_DIST; + SET_SOUND_REFLECTION(TRUE); + break; + } + } + break; +#ifdef GTA_TRAIN + 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; +#endif + 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(6000) + 16000; + 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 = Min(MAX_VOLUME, (3 * (eventVol / 20 * MAX_VOLUME)) / 2); + if (Vol == 0) + continue; + + m_sQueueSample.m_nSampleIndex = 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); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 4); + 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: + pedParams.m_bDistanceCalculated = params.m_bDistanceCalculated; + pedParams.m_fDistance = params.m_fDistance; + SetupPedComments(pedParams, SOUND_PED_HELI_PLAYER_FOUND); + continue; + case SOUND_PED_VCPA_PLAYER_FOUND: + pedParams.m_bDistanceCalculated = params.m_bDistanceCalculated; + pedParams.m_fDistance = params.m_fDistance; + SetupPedComments(pedParams, SOUND_PED_VCPA_PLAYER_FOUND); + 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); + SET_SOUND_REVERB(TRUE); + 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_bIs2D = FALSE; + } + } else m_sQueueSample.m_bIs2D = FALSE; + AddSampleToRequestedQueue(); + if (stereo) { + m_sQueueSample.m_nPan = 127; + m_sQueueSample.m_nSampleIndex++; + m_sQueueSample.m_nCounter = GunIndex++; + if (GunIndex > 58) + GunIndex = 53; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + continue; + + } + } + } +} + +#ifdef GTA_TRAIN +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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + return TRUE; + } + return FALSE; +} +#endif +bool8 +cAudioManager::ProcessBoatEngine(cVehicleParams& params) +{ + CBoat *boat; + float padRelativeAccerate; + + bool8 isV12 = FALSE; + static uint32 LastFreq = 2000; + static uint8 LastVol = 0; + + if (params.m_fDistance < SQR(BOAT_ENGINE_MAX_DIST)) { + boat = (CBoat *)params.m_pVehicle; + if(boat->GetStatus() == STATUS_WRECKED) + return TRUE; + + float freqModificator; + float volModificator; + uint8 BaseVol; + uint32 BaseFreq; + + switch(boat->GetModelIndex()) { + case MI_RIO: + freqModificator = 490; + volModificator = 60; + BaseVol = 20; + BaseFreq = 1888; + break; + case MI_PREDATOR: + case MI_SQUALO: + case MI_SPEEDER: + case MI_COASTG: + case MI_DINGHY: + case MI_JETMAX: + freqModificator = 6000; + volModificator = 60; + isV12 = TRUE; + BaseFreq = 9000; + BaseVol = 20; + break; + case MI_REEFER: + freqModificator = 715; + volModificator = 80; + BaseVol = 0; + BaseFreq = 3775; + break; + case MI_TROPIC: + case MI_MARQUIS: + freqModificator = 463; + volModificator = 60; + BaseVol = 20; + BaseFreq = 1782; + break; + default: + return TRUE; + } + + bool8 bIsPlayerVeh; + + if(FindPlayerVehicle() == params.m_pVehicle) { + padRelativeAccerate = Max(Pads[0].GetAccelerate(), Pads[0].GetBrake()) / 255.0f; + bIsPlayerVeh = TRUE; + } else { + padRelativeAccerate = Max(params.m_pVehicle->m_fGasPedal, params.m_pVehicle->m_fBrakePedal); + bIsPlayerVeh = FALSE; + } + + uint32 Freq = BaseFreq + (padRelativeAccerate * freqModificator); + uint8 Vol = BaseVol + (padRelativeAccerate * volModificator); + + if(!boat->bPropellerInWater) + Freq = (9 * Freq) / 8; + + if(bIsPlayerVeh) { + if(Freq > LastFreq) { + if(isV12) + Freq = Min(Freq, LastFreq + 100); + else + Freq = Min(Freq, LastFreq + 15); + } else { + if(isV12) + Freq = Max(Freq, LastFreq - 100); + else + Freq = Max(Freq, LastFreq - 15); + } + if(Vol > LastVol) + Vol = Min(Vol, LastVol + 3); + else + Vol = Max(Vol, LastVol - 3); + } + + if (Vol > 0) { + 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 = Freq; + m_sQueueSample.m_nCounter = 40; + if (isV12) + m_sQueueSample.m_nSampleIndex = SFX_BOAT_V12_LOOP; + else + m_sQueueSample.m_nSampleIndex = SFX_BOAT_CRUISER_LOOP; + 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + + if(boat->GetModelIndex() == MI_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_nFrequency = 6000; + m_sQueueSample.m_nCounter = 39; + m_sQueueSample.m_nSampleIndex = SFX_FISHING_BOAT_IDLE; + m_sQueueSample.m_nFrequency += (m_sQueueSample.m_nEntityIndex * 65536) % 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + if(bIsPlayerVeh) { + LastFreq = Freq; + LastVol = Vol; + } + 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 = 6; + SET_SOUND_REVERB(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: + 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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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; // todo port fix to re3 +} + +#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); + ProcessPedOneShots(params); +} + +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; + static uint32 iSplashFrame = 0; + + 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_SHIRT_WIND_FLAP: + if (params.m_pPed->IsPlayer() && params.m_pPed->m_pMyVehicle) { + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] > 0.0f) { + if (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] > 1.0f) + m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] = 1.0f; + + Vol = PED_ONE_SHOT_SHIRT_FLAP_VOLUME * m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + + switch (params.m_pPed->m_pMyVehicle->GetModelIndex()) + { + case MI_ANGEL: + case MI_FREEWAY: + m_sQueueSample.m_nSampleIndex = SFX_CAR_WIND_17; + break; + case MI_PCJ600: + m_sQueueSample.m_nSampleIndex = SFX_CAR_WIND_20; + break; + case MI_SANCHEZ: + m_sQueueSample.m_nSampleIndex = SFX_CAR_WIND_19; + break; + case MI_PIZZABOY: + case MI_FAGGIO: + m_sQueueSample.m_nSampleIndex = SFX_CAR_WIND_18; + break; + default: + continue; + }; + + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 71; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_SHIRT_FLAP_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_SHIRT_FLAP_MAX_DIST); + m_sQueueSample.m_nLoopCount = 0; + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + } + } + continue; + case SOUND_WEAPON_MINIGUN_ATTACK: + m_sQueueSample.m_nSampleIndex = SFX_MINIGUN_FIRE_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 68; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_MINIGUN_FIRE_LEFT); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_MINIGUN_MAX_DIST; + Vol = PED_ONE_SHOT_MINIGUN_VOLUME; + maxDist = SQR(PED_ONE_SHOT_MINIGUN_MAX_DIST); + m_sQueueSample.m_nLoopCount = 0; + SET_LOOP_OFFSETS(SFX_MINIGUN_FIRE_LEFT) + SET_EMITTING_VOLUME(PED_ONE_SHOT_MINIGUN_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + break; + case SOUND_WEAPON_MINIGUN_2: + m_sQueueSample.m_nSampleIndex = SFX_MINIGUN_FIRE_RIGHT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 69; + m_sQueueSample.m_nFrequency = 18569; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_MINIGUN_MAX_DIST; + Vol = PED_ONE_SHOT_MINIGUN_VOLUME * m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + maxDist = SQR(PED_ONE_SHOT_MINIGUN_MAX_DIST); + m_sQueueSample.m_nLoopCount = 0; + SET_LOOP_OFFSETS(SFX_MINIGUN_FIRE_RIGHT) + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 3; + break; + case SOUND_WEAPON_MINIGUN_3: + m_sQueueSample.m_nSampleIndex = SFX_MINIGUN_STOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = 69; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_MINIGUN_STOP); + m_sQueueSample.m_nPriority = 4; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_MINIGUN_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_MINIGUN_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = PED_ONE_SHOT_MINIGUN_VOLUME; + SET_EMITTING_VOLUME(PED_ONE_SHOT_MINIGUN_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + case SOUND_SKATING: + { + uint32 soundParams = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + uint8 param1 = soundParams & 0xFF; + uint32 param2 = soundParams >> 8; + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[3] & 1) + SFX_SKATE_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound; + stereo = TRUE; + iSound++; + m_sQueueSample.m_nFrequency = m_anRandomTable[1] % 1000 + 17000; + if (param2 == 0) + m_sQueueSample.m_nFrequency = (3 * m_sQueueSample.m_nFrequency) >> 2; + m_sQueueSample.m_nPriority = 6; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_SKATING_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_SKATING_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = (m_anRandomTable[2] % 20 + PED_ONE_SHOT_SKATING_VOLUME) * param1 / MAX_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + break; + } + 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: + { + uint32 weaponType = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + switch (weaponType) { + case WEAPONTYPE_SPAS12_SHOTGUN: + m_sQueueSample.m_nSampleIndex = SFX_SPAS12_TAIL_LEFT; + break; + case WEAPONTYPE_M60: + case WEAPONTYPE_HELICANNON: + m_sQueueSample.m_nSampleIndex = SFX_M60_TAIL_LEFT; + case WEAPONTYPE_UZI: + case WEAPONTYPE_MP5: + m_sQueueSample.m_nSampleIndex = SFX_UZI_END_LEFT; + break; + case WEAPONTYPE_TEC9: + case WEAPONTYPE_SILENCED_INGRAM: + m_sQueueSample.m_nSampleIndex = SFX_TEC_TAIL; + break; + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + m_sQueueSample.m_nSampleIndex = SFX_RUGER_TAIL; + break; + break; + default: + continue; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + switch (weaponType) { + case WEAPONTYPE_SILENCED_INGRAM: + m_sQueueSample.m_nFrequency = 26000; + break; + case WEAPONTYPE_TEC9: + m_sQueueSample.m_nFrequency = 13000; + break; + case WEAPONTYPE_M4: + m_sQueueSample.m_nFrequency = 15600; + break; + case WEAPONTYPE_LASERSCOPE: + m_sQueueSample.m_nFrequency = 7904; + break; + case WEAPONTYPE_SNIPERRIFLE: + m_sQueueSample.m_nFrequency = 9959; + break; + default: + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + break; + } + 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; + SET_SOUND_REFLECTION(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(); + if (!weapon) + continue; + switch (weapon->m_eWeaponType) { + case WEAPONTYPE_PYTHON: + m_sQueueSample.m_nSampleIndex = SFX_PYTHON_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PYTHON_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_BULLET_ECHO_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_BULLET_ECHO_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = MAX_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + stereo = TRUE; + break; + 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_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[1] % 10 + PED_ONE_SHOT_WEAPON_COLT45_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + stereo = TRUE; + break; + case WEAPONTYPE_ROCKET: + 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_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[0] % 20 + PED_ONE_SHOT_WEAPON_ROCKETLAUNCHER_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + 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; + stereo = TRUE; + break; + case WEAPONTYPE_M60: + case WEAPONTYPE_HELICANNON: + m_sQueueSample.m_nSampleIndex = SFX_M60_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_M60_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_BULLET_ECHO_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_BULLET_ECHO_MAX_DIST); + m_sQueueSample.m_nLoopCount = 1; + RESET_LOOP_OFFSETS + Vol = PED_ONE_SHOT_WEAPON_M16_VOLUME; + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_M16_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + stereo = TRUE; + break; + case WEAPONTYPE_MP5: + m_sQueueSample.m_nSampleIndex = SFX_MP5_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_MP5_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_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[3] % 15 + PED_ONE_SHOT_WEAPON_UZI_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + stereo = TRUE; + break; + case WEAPONTYPE_TEC9: + m_sQueueSample.m_nSampleIndex = SFX_TEC_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 17000; + 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[3] % 15 + PED_ONE_SHOT_WEAPON_UZI_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + stereo = TRUE; + break; + case WEAPONTYPE_SILENCED_INGRAM: + m_sQueueSample.m_nSampleIndex = SFX_TEC_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 34000; + 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[3] % 15 + PED_ONE_SHOT_WEAPON_UZI_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + stereo = TRUE; + break; + case WEAPONTYPE_RUGER: + m_sQueueSample.m_nSampleIndex = SFX_RUGER_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_RUGER_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_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[3] % 15 + PED_ONE_SHOT_WEAPON_M4_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + stereo = TRUE; + break; + case WEAPONTYPE_M4: + m_sQueueSample.m_nSampleIndex = SFX_RUGER_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 43150; + 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[3] % 15 + PED_ONE_SHOT_WEAPON_M4_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + stereo = TRUE; + break; + case WEAPONTYPE_UZI: + case WEAPONTYPE_MINIGUN: + 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_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[3] % 15 + PED_ONE_SHOT_WEAPON_UZI_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + stereo = TRUE; + break; + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + m_sQueueSample.m_nSampleIndex = SFX_SNIPER_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + if (weapon->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE) + m_sQueueSample.m_nFrequency = 25472; + else + m_sQueueSample.m_nFrequency = 20182; + 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_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_SNIPERRIFLE_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + stereo = TRUE; + break; + case WEAPONTYPE_SPAS12_SHOTGUN: + m_sQueueSample.m_nSampleIndex = SFX_SPAS12_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SPAS12_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_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[2] % 10 + PED_ONE_SHOT_WEAPON_SHOTGUN_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + stereo = TRUE; + break; + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_STUBBY_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_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[2] % 10 + PED_ONE_SHOT_WEAPON_SHOTGUN_VOLUME; + SET_EMITTING_VOLUME(Vol); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = TRUE; + SET_SOUND_REFLECTION(TRUE); + stereo = TRUE; + break; + default: + continue; + } + break; + case SOUND_WEAPON_RELOAD: + switch ((int32)m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]) { + case WEAPONTYPE_COLT45: + case WEAPONTYPE_PYTHON: + m_sQueueSample.m_nSampleIndex = SFX_PISTOL_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PISTOL_RELOAD) + RandomDisplacement(300); + break; + case WEAPONTYPE_TEC9: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SILENCED_INGRAM: + case WEAPONTYPE_MP5: + case WEAPONTYPE_M4: + case WEAPONTYPE_M60: + case WEAPONTYPE_HELICANNON: + m_sQueueSample.m_nSampleIndex = SFX_AK47_RELOAD; + m_sQueueSample.m_nFrequency = 39243; + break; + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_SPAS12_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + case WEAPONTYPE_RUGER: + m_sQueueSample.m_nSampleIndex = SFX_AK47_RELOAD; + m_sQueueSample.m_nFrequency = 30290; + break; + case WEAPONTYPE_ROCKET: + case WEAPONTYPE_ROCKETLAUNCHER: + m_sQueueSample.m_nSampleIndex = SFX_ROCKET_RELOAD; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ROCKET_RELOAD); + break; + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + 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_CHAINSAW_MADECONTACT: + if (FindVehicleOfPlayer()) + continue; + if ((int32)m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i] != ENTITY_TYPE_PED) + ReportCollision(params.m_pPed, params.m_pPed, SURFACE_CAR, SURFACE_TARMAC, 0.0f, 0.09f); + m_sQueueSample.m_nSampleIndex = SFX_CAR_CHAINSAW_ATTACK; +#ifdef GTA_PS2 + m_sQueueSample.m_nBankIndex = SFX_BANK_CAR_CHAINSAW; +#else + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#endif + m_sQueueSample.m_nCounter = 68; + m_sQueueSample.m_nFrequency = RandomDisplacement(500) + 22000; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_CHAINSAW_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_CHAINSAW_MAX_DIST); + m_sQueueSample.m_nLoopCount = 0; + Vol = PED_ONE_SHOT_WEAPON_CHAINSAW_VOLUME; + SET_LOOP_OFFSETS(SFX_CAR_CHAINSAW_ATTACK) + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_CHAINSAW_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + break; + case SOUND_WEAPON_CHAINSAW_ATTACK: + if (FindVehicleOfPlayer()) + continue; + m_sQueueSample.m_nSampleIndex = SFX_CAR_CHAINSAW_ATTACK; +#ifdef GTA_PS2 + m_sQueueSample.m_nBankIndex = SFX_BANK_CAR_CHAINSAW; +#else + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#endif + m_sQueueSample.m_nCounter = 68; + m_sQueueSample.m_nFrequency = 27000; + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_CHAINSAW_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_CHAINSAW_MAX_DIST); + m_sQueueSample.m_nLoopCount = 0; + Vol = PED_ONE_SHOT_WEAPON_CHAINSAW_VOLUME; + SET_LOOP_OFFSETS(SFX_CAR_CHAINSAW_ATTACK) + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_CHAINSAW_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + break; + case SOUND_WEAPON_CHAINSAW_IDLE: + if (FindVehicleOfPlayer()) + continue; + m_sQueueSample.m_nSampleIndex = SFX_CAR_CHAINSAW_IDLE; +#ifdef GTA_PS2 + m_sQueueSample.m_nBankIndex = SFX_BANK_CAR_CHAINSAW; +#else + m_sQueueSample.m_nBankIndex = SFX_BANK_0; +#endif + m_sQueueSample.m_nCounter = 70; + m_sQueueSample.m_nFrequency = 27000; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_MaxDistance = PED_ONE_SHOT_WEAPON_CHAINSAW_IDLE_MAX_DIST; + maxDist = SQR(PED_ONE_SHOT_WEAPON_CHAINSAW_IDLE_MAX_DIST); + m_sQueueSample.m_nLoopCount = 0; + Vol = PED_ONE_SHOT_WEAPON_CHAINSAW_VOLUME; + SET_LOOP_OFFSETS(SFX_CAR_CHAINSAW_IDLE) + SET_EMITTING_VOLUME(PED_ONE_SHOT_WEAPON_CHAINSAW_VOLUME); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 5; + break; + case SOUND_WEAPON_BAT_ATTACK: + case SOUND_WEAPON_KNIFE_ATTACK: + { + uint32 soundParams = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; // wtf? storing int as float + uint8 damagerType = soundParams & 0xFF; + uint32 weaponType = soundParams >> 8; + if (damagerType == ENTITY_TYPE_PED) { + switch (weaponType) { + case WEAPONTYPE_SCREWDRIVER: + case WEAPONTYPE_KNIFE: + case WEAPONTYPE_CLEAVER: + case WEAPONTYPE_MACHETE: + case WEAPONTYPE_KATANA: + if (sound == SOUND_WEAPON_KNIFE_ATTACK) + m_sQueueSample.m_nSampleIndex = SFX_KNIFE_SLASH; + else + m_sQueueSample.m_nSampleIndex = SFX_KNIFE_STAB; + 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); + break; + case WEAPONTYPE_HAMMER: + m_sQueueSample.m_nSampleIndex = m_anRandomTable[3] % 2 + SFX_HAMMER_HIT_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); + break; + default: + m_sQueueSample.m_nSampleIndex = SFX_BAT_HIT_LEFT; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = RandomDisplacement(2000) + 22000; + stereo = TRUE; + break; + } + } + else { + m_sQueueSample.m_nSampleIndex = m_anRandomTable[4] % 6 + SFX_COL_CAR_PANEL_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_nCounter = iSound++; + narrowSoundRange = TRUE; + 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; + SET_SOUND_REFLECTION(TRUE); + break; + } + case SOUND_FIGHT_37: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_38: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_39: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_1; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_40: + case SOUND_186: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_41: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_42: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_2; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_43: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_44: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_45: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_4; + m_sQueueSample.m_nFrequency = 20000; + goto AddFightSound; + case SOUND_FIGHT_46: + case SOUND_187: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 18000; + goto AddFightSound; + case SOUND_FIGHT_47: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 16500; + goto AddFightSound; + case SOUND_FIGHT_48: + m_sQueueSample.m_nSampleIndex = SFX_FIGHT_5; + m_sQueueSample.m_nFrequency = 20000; + AddFightSound: + { + uint32 soundParams = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; // wtf? storing int as float + uint8 damagerType = soundParams & 0xFF; + uint32 weaponType = soundParams >> 8; + + if (damagerType == ENTITY_TYPE_PED) { + if (weaponType == WEAPONTYPE_BRASSKNUCKLE) { + CPed* ped = params.m_pPed; + uint32 fightMove = ped->m_curFightMove; + if (fightMove == FIGHTMOVE_BACKLEFT || fightMove == FIGHTMOVE_STDPUNCH || fightMove == FIGHTMOVE_PUNCH || + ped->m_nPedState == PED_ATTACK) { + CEntity* damageEntity = ped->m_pDamageEntity; + if (!damageEntity) + m_sQueueSample.m_nSampleIndex = m_anRandomTable[3] % 2 + SFX_HAMMER_HIT_1; + else if (damageEntity->GetType() != ENTITY_TYPE_PED) + m_sQueueSample.m_nSampleIndex = m_anRandomTable[3] % 2 + SFX_HAMMER_HIT_1; + else if (((CPed*)damageEntity)->m_curFightMove != FIGHTMOVE_HITHEAD) + m_sQueueSample.m_nSampleIndex = m_anRandomTable[3] % 2 + SFX_HAMMER_HIT_1; + else + m_sQueueSample.m_nSampleIndex = SFX_HAMMER_HIT_1; + } + } + } + else { + m_sQueueSample.m_nSampleIndex = m_anRandomTable[4] % 6 + SFX_COL_CAR_PANEL_1; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency / 16); + } + } + 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: + if (m_FrameCounter <= iSplashFrame) + continue; + iSplashFrame = m_FrameCounter + 6; + 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; + case SOUND_MELEE_ATTACK_START: + { + uint32 weaponType = ((uint32)m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]) >> 8; + switch (weaponType) + { + case WEAPONTYPE_SCREWDRIVER: + case WEAPONTYPE_KNIFE: + case WEAPONTYPE_CLEAVER: + case WEAPONTYPE_MACHETE: + case WEAPONTYPE_KATANA: + m_sQueueSample.m_nSampleIndex = SFX_KNIFE_SWING; + break; + default: + m_sQueueSample.m_nSampleIndex = SFX_GOLF_CLUB_SWING; + break; + } + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nCounter = iSound++; + narrowSoundRange = TRUE; + 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_MaxDistance = PED_ONE_SHOT_WEAPON_HIT_PED_MAX_DIST; + if (weaponType == WEAPONTYPE_UNARMED || weaponType == WEAPONTYPE_BRASSKNUCKLE) + Vol = m_anRandomTable[1] % 10 + 35; + else + Vol = m_anRandomTable[2] % 20 + 70; + maxDist = SQR(PED_ONE_SHOT_WEAPON_HIT_PED_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; + } + 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; + } + SET_SOUND_REVERB(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::SetPedTalkingStatus(CPed *ped, bool8 status) +{ + if (ped != nil) + ped->m_canTalk = status; +} + +void +cAudioManager::SetPlayersMood(uint8 mood, uint32 time) +{ + if (!m_bIsInitialised) return; + + if (mood < MAX_PLAYER_MOODS) { + m_nPlayerMood = mood; + m_nPlayerMoodTimer = CTimer::GetTimeInMilliseconds() + time; + } + +} + +void +cAudioManager::ProcessPlayerMood() +{ + CPlayerPed *playerPed; + uint32 curTime = CTimer::GetTimeInMilliseconds(); + + if (m_nPlayerMoodTimer > curTime) + return; + + playerPed = FindPlayerPed(); + if (!playerPed) + return; + + if (playerPed->m_pWanted->GetWantedLevel() > 3) + m_nPlayerMood = PLAYER_MOOD_ANGRY; + else if (playerPed->m_pWanted->GetWantedLevel() > 1) + m_nPlayerMood = PLAYER_MOOD_PISSED_OFF; + else if (CTheScripts::GetLastMissionPassedTime() != -1) { + if (curTime >= CTheScripts::GetLastMissionPassedTime()) { + if (curTime < CTheScripts::GetLastMissionPassedTime() + 180000) + m_nPlayerMood = PLAYER_MOOD_WISECRACKING; + else + m_nPlayerMood = PLAYER_MOOD_CALM; + } else + CTheScripts::GetLastMissionPassedTime() = curTime; + } else + m_nPlayerMood = PLAYER_MOOD_CALM; +} + +void +cAudioManager::SetupPedComments(cPedParams ¶ms, uint16 sound) +{ + CPed *ped = params.m_pPed; + uint8 Vol; + float maxDist; + tPedComment pedComment; + + if(ped != nil) { + if(!ped->m_canTalk) return; + m_bGenericSfx = FALSE; + pedComment.m_nSampleIndex = GetPedCommentSfx(ped, sound); + if(pedComment.m_nSampleIndex == NO_SAMPLE) + return; + maxDist = PED_COMMENT_MAX_DIST; + } else { + m_bGenericSfx = TRUE; + switch(sound) { + case SOUND_PED_HELI_PLAYER_FOUND: + maxDist = PED_COMMENT_POLICE_HELI_MAX_DIST; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 20 + SFX_POLICE_HELI_1; + break; + case SOUND_PED_VCPA_PLAYER_FOUND: + maxDist = PED_COMMENT_POLICE_HELI_MAX_DIST; +#ifdef FIX_BUGS + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 23 + SFX_POLICE_BOAT_1; +#else + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 29 + SFX_POLICE_BOAT_1; +#endif + break; + case SOUND_INJURED_PED_MALE_OUCH: + maxDist = PED_COMMENT_MAX_DIST; + pedComment.m_nSampleIndex = m_anRandomTable[m_sQueueSample.m_nEntityIndex % 4] % 41 + 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] % 33 + SFX_GENERIC_FEMALE_GRUNT_1; + break; + default: + return; + } + } + + if(params.m_fDistance < SQR(maxDist)) { + CalculateDistance(params.m_bDistanceCalculated, params.m_fDistance); + 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; + 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); + } + } +} + +uint32 +cAudioManager::GetPedCommentSfx(CPed *ped, uint16 sound) +{ + if(ped->m_nPedState != PED_FALL || sound == SOUND_PED_DAMAGE || sound == SOUND_PED_HIT || sound == SOUND_PED_LAND) { + if(ped->m_getUpTimer == UINT32_MAX || ped->m_getUpTimer > CTimer::GetTimeInMilliseconds()) { + if(sound != SOUND_PED_DAMAGE && sound != SOUND_PED_HIT && sound != SOUND_PED_LAND) return NO_SAMPLE; + } + if(ped->IsPlayer()) return GetPlayerTalkSfx(ped, sound); + switch(ped->GetModelIndex()) { + case MI_PLAYER: return GetPlayerTalkSfx(ped, sound); + case MI_COP: return GetCopTalkSfx(ped, sound); + case MI_SWAT: return GetSwatTalkSfx(ped, sound); + case MI_FBI: return GetFBITalkSfx(ped, sound); + case MI_ARMY: return GetArmyTalkSfx(ped, sound); + case MI_MEDIC: return GetMedicTalkSfx(ped, sound); + case MI_FIREMAN: return GetFiremanTalkSfx(ped, sound); + case MI_MALE01: return GetDefaultTalkSfx(ped, sound); + case MI_HFYST: return GetHFYSTTalkSfx(ped, sound); + case MI_HFOST: return GetHFOSTTalkSfx(ped, sound); + case MI_HMYST: return GetHMYSTTalkSfx(ped, sound); + case MI_HMOST: return GetHMOSTTalkSfx(ped, sound); + case MI_HFYRI: return GetHFYRITalkSfx(ped, sound); + case MI_HFORI: return GetHFORITalkSfx(ped, sound); + case MI_HMYRI: return GetHMYRITalkSfx(ped, sound); + case MI_HMORI: return GetHMORITalkSfx(ped, sound); + case MI_HFYBE: return GetHFYBETalkSfx(ped, sound); + case MI_HFOBE: return GetHFOBETalkSfx(ped, sound); + case MI_HMYBE: return GetHMYBETalkSfx(ped, sound); + case MI_HMOBE: return GetHMOBETalkSfx(ped, sound); + case MI_HFYBU: return GetHFYBUTalkSfx(ped, sound); + case MI_HFYMD: return GetHFYMDTalkSfx(ped, sound); + case MI_HFYCG: return GetHFYCGTalkSfx(ped, sound); + case MI_HFYPR: return GetHFYPRTalkSfx(ped, sound); + case MI_HFOTR: return GetHFOTRTalkSfx(ped, sound); + case MI_HMOTR: return GetHMOTRTalkSfx(ped, sound); + case MI_HMYAP: return GetHMYAPTalkSfx(ped, sound); + case MI_HMOCA: return GetHMOCATalkSfx(ped, sound); + case MI_BMODK: return GetBMODKTalkSfx(ped, sound); + case MI_BMYKR: return GetBMYCRTalkSfx(ped, sound); + case MI_BFYST: return GetBFYSTTalkSfx(ped, sound); + case MI_BFOST: return GetBFOSTTalkSfx(ped, sound); + case MI_BMYST: return GetBMYSTTalkSfx(ped, sound); + case MI_BMOST: return GetBMOSTTalkSfx(ped, sound); + case MI_BFYRI: return GetBFYRITalkSfx(ped, sound); + case MI_BFORI: return GetBFORITalkSfx(ped, sound); + case MI_BMYRI: return GetBMYRITalkSfx(ped, sound); + case MI_BFYBE: return GetBFYBETalkSfx(ped, sound); + case MI_BMYBE: return GetBMYBETalkSfx(ped, sound); + case MI_BFOBE: return GetBFOBETalkSfx(ped, sound); + case MI_BMOBE: return GetBMOBETalkSfx(ped, sound); + case MI_BMYBU: return GetBMYBUTalkSfx(ped, sound); + case MI_BFYPR: return GetBFYPRTalkSfx(ped, sound); + case MI_BFOTR: return GetBFOTRTalkSfx(ped, sound); + case MI_BMOTR: return GetBMOTRTalkSfx(ped, sound); + case MI_BMYPI: return GetBMYPITalkSfx(ped, sound); + case MI_BMYBB: return GetBMYBBTalkSfx(ped, sound); + case MI_WMYCR: return GetWMYCRTalkSfx(ped, sound); + case MI_WFYST: return GetWFYSTTalkSfx(ped, sound); + case MI_WFOST: return GetWFOSTTalkSfx(ped, sound); + case MI_WMYST: return GetWMYSTTalkSfx(ped, sound); + case MI_WMOST: return GetWMOSTTalkSfx(ped, sound); + case MI_WFYRI: return GetWFYRITalkSfx(ped, sound); + case MI_WFORI: return GetWFORITalkSfx(ped, sound); + case MI_WMYRI: return GetWMYRITalkSfx(ped, sound); + case MI_WMORI: return GetWMORITalkSfx(ped, sound); + case MI_WFYBE: return GetWFYBETalkSfx(ped, sound); + case MI_WMYBE: return GetWMYBETalkSfx(ped, sound); + case MI_WFOBE: return GetWFOBETalkSfx(ped, sound); + case MI_WMOBE: return GetWMOBETalkSfx(ped, sound); + case MI_WMYCW: return GetWMYCWTalkSfx(ped, sound); + case MI_WMYGO: return GetWMYGOTalkSfx(ped, sound); + case MI_WFOGO: return GetWFOGOTalkSfx(ped, sound); + case MI_WMOGO: return GetWMOGOTalkSfx(ped, sound); + case MI_WFYLG: return GetWFYLGTalkSfx(ped, sound); + case MI_WMYLG: return GetWMYLGTalkSfx(ped, sound); + case MI_WFYBU: return GetWFYBUTalkSfx(ped, sound); + case MI_WMYBU: return GetWMYBUTalkSfx(ped, sound); + case MI_WMOBU: return GetWMOBUTalkSfx(ped, sound); + case MI_WFYPR: return GetWFYPRTalkSfx(ped, sound); + case MI_WFOTR: return GetWFOTRTalkSfx(ped, sound); + case MI_WMOTR: return GetWMOTRTalkSfx(ped, sound); + case MI_WMYPI: return GetWMYPITalkSfx(ped, sound); + case MI_WMOCA: return GetWMOCATalkSfx(ped, sound); + case MI_WFYJG: return GetWFYJGTalkSfx(ped, sound); + case MI_WMYJG: return GetWMYJGTalkSfx(ped, sound); + case MI_WFYSK: return GetWFYSKTalkSfx(ped, sound); + case MI_WMYSK: return GetWMYSKTalkSfx(ped, sound); + case MI_WFYSH: return GetWFYSHTalkSfx(ped, sound); + case MI_WFOSH: return GetWFOSHTalkSfx(ped, sound); + case MI_JFOTO: return GetJFOTOTalkSfx(ped, sound); + case MI_JMOTO: return GetJMOTOTalkSfx(ped, sound); + case MI_CBA: + case MI_CBB: return GetCBTalkSfx(ped, sound); + case MI_HNA: + case MI_HNB: return GetHNTalkSfx(ped, sound); + case MI_SGA: + case MI_SGB: return GetSGTalkSfx(ped, sound); + case MI_CLA: + case MI_CLB: return GetCLTalkSfx(ped, sound); + case MI_GDA: + case MI_GDB: return GetGDTalkSfx(ped, sound); + case MI_BKA: + case MI_BKB: return GetBKTalkSfx(ped, sound); + case MI_PGA: + case MI_PGB: return GetPGTalkSfx(ped, sound); + case MI_VICE1: + case MI_VICE2: + case MI_VICE3: + case MI_VICE4: + case MI_VICE5: + case MI_VICE7: + case MI_VICE8: return GetViceWhiteTalkSfx(ped, sound); + case MI_VICE6: return GetViceBlackTalkSfx(ped, sound); + case MI_WFYG1: return GetWFYG1TalkSfx(ped, sound); + case MI_WFYG2: return GetWFYG2TalkSfx(ped, sound); + case MI_SPECIAL01: + case MI_SPECIAL02: + case MI_SPECIAL03: + case MI_SPECIAL04: + case MI_SPECIAL05: + case MI_SPECIAL06: + case MI_SPECIAL07: + case MI_SPECIAL08: + case MI_SPECIAL09: + case MI_SPECIAL10: + case MI_SPECIAL11: + case MI_SPECIAL12: + case MI_SPECIAL13: + case MI_SPECIAL14: + case MI_SPECIAL15: + case MI_SPECIAL16: + case MI_SPECIAL17: + case MI_SPECIAL18: + case MI_SPECIAL19: + case MI_SPECIAL20: + case MI_SPECIAL21: return GetSpecialCharacterTalkSfx(ped, ped->GetModelIndex(), sound); + default: return GetGenericMaleTalkSfx(ped, sound); + } + } + + return NO_SAMPLE; +} + +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 + +#define cooldown_phrase(count) static uint8 cooldown = 0;\ +if (cooldown != 0) {\ + if (++cooldown == count) cooldown = 0;\ + return NO_SAMPLE;\ +}\ +cooldown = 1; + +uint32 +cAudioManager::GetPlayerTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + if(m_bIsPlayerShutUp) return NO_SAMPLE; + switch(sound) { + case SOUND_PED_DEATH: return SFX_PLAYER_DEATH; + case SOUND_PED_DAMAGE: + case SOUND_PED_BULLET_HIT: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_HIT_BULLET_1, 33); break; + case SOUND_PED_HIT: + case SOUND_PED_DEFEND: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_HIT_FIST_1, 42); break; + case SOUND_PED_LAND: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_HIT_GROUND_1, 35); break; + case SOUND_PED_BURNING: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ON_FIRE_1, 16); break; + case SOUND_PED_PLAYER_REACTTOCOP: + switch(m_nPlayerMood) { + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_BUSTED_1, 38); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_BUSTED_1, 20); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_BUSTED_1, 22); + break; + } + break; + case SOUND_PED_ON_FIRE: { + cooldown_phrase(8); + switch(m_nPlayerMood) { + case PLAYER_MOOD_PISSED_OFF: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_PISSED_OFF_SHOOT_1, 29); + break; + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_SHOOT_1, 39); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_SHOOT_1, 9); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_SHOOT_1, 35); + break; + } + break; + } + case SOUND_PED_AIMING: { + cooldown_phrase(8); + switch(m_nPlayerMood) { + case PLAYER_MOOD_PISSED_OFF: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_PISSED_OFF_PULL_GUN_1, 25); + break; + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_PULL_GUN_1, 52); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_PULL_GUN_1, 19); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_PULL_GUN_1, 39); + break; + } + break; + } + case SOUND_PED_CAR_JACKING: { + cooldown_phrase(4); + switch(m_nPlayerMood) { + case PLAYER_MOOD_PISSED_OFF: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_PISSED_OFF_JACKING_1, 36); + break; + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_JACKING_1, 43); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_JACKING_1, 18); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_JACKING_1, 40); + break; + } + break; + } + case SOUND_PED_MUGGING: { + cooldown_phrase(8); + switch(m_nPlayerMood) { + case PLAYER_MOOD_PISSED_OFF: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_1, 25); + break; + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_PICK_UP_CASH_1, 12); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_PICK_UP_CASH_1, 23); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_PICK_UP_CASH_1, 11); + break; + } + break; + } + case SOUND_PED_CAR_JACKED: { + cooldown_phrase(4); + switch(m_nPlayerMood) { + case PLAYER_MOOD_PISSED_OFF: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_PISSED_OFF_JACKED_1, 21); + break; + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_JACKED_1, 33); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_JACKED_1, 18); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_JACKED_1, 24); + break; + } + break; + } + case SOUND_PED_PLAYER_AFTERSEX: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_AFTERSEX_1, 18); break; + case SOUND_PED_PLAYER_BEFORESEX: + switch(m_nPlayerMood) { + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_SEX_1, 18); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_SEX_1, 10); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_SEX_1, 8); + break; + } + break; + case SOUND_PED_PLAYER_FARFROMCOPS: { + cooldown_phrase(4); + switch(m_nPlayerMood) { + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_CHASED_1, 9); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_CHASED_1, 7); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_CHASED_1, 20); + break; + } + break; + } + case SOUND_PED_ATTACK: { + cooldown_phrase(4); + switch(m_nPlayerMood) { + case PLAYER_MOOD_PISSED_OFF: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_PISSED_OFF_FIGHT_1, 61); + break; + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_FIGHT_1, 61); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_FIGHT_1, 27); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_FIGHT_1, 47); + break; + } + break; + } + case SOUND_PED_CRASH_VEHICLE: + case SOUND_PED_CRASH_CAR: + case SOUND_PED_ANNOYED_DRIVER: { + cooldown_phrase(4); + switch(m_nPlayerMood) { + case PLAYER_MOOD_PISSED_OFF: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_PISSED_OFF_CRASH_1, 44); + break; + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_CRASH_1, 41); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_CRASH_1, 19); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_CRASH_1, 43); + break; + } + break; + } + case SOUND_PED_SOLICIT: { + cooldown_phrase(4); + switch(m_nPlayerMood) { + case PLAYER_MOOD_PISSED_OFF: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_1, 17); + break; + case PLAYER_MOOD_ANGRY: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_ANGRY_PICK_UP_HOOKER_1, 6); + break; + case PLAYER_MOOD_WISECRACKING: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_1, 11); + break; + default: + GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_CALM_PICK_UP_HOOKER_1, 22); + break; + } + break; + } + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetGenericMaleTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + m_bGenericSfx = TRUE; + switch(sound) { + case SOUND_PED_DEATH: GetPhrase(sfx, ped->m_lastComment, SFX_GENERIC_MALE_DEATH_1, 41); break; + case SOUND_PED_BULLET_HIT: + case SOUND_PED_DEFEND: GetPhrase(sfx, ped->m_lastComment, SFX_GENERIC_MALE_GRUNT_1, 41); break; + case SOUND_PED_BURNING: GetPhrase(sfx, ped->m_lastComment, SFX_GENERIC_MALE_FIRE_1, 32); break; + case SOUND_PED_FLEE_SPRINT: GetPhrase(sfx, ped->m_lastComment, SFX_GENERIC_MALE_PANIC_1, 35); break; + default: return NO_SAMPLE; + } + return sfx; +} + +uint32 +cAudioManager::GetGenericFemaleTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + m_bGenericSfx = TRUE; + switch(sound) { + case SOUND_PED_DEATH: GetPhrase(sfx, ped->m_lastComment, SFX_GENERIC_FEMALE_DEATH_1, 22); break; + case SOUND_PED_BULLET_HIT: + case SOUND_PED_DEFEND: GetPhrase(sfx, ped->m_lastComment, SFX_GENERIC_FEMALE_GRUNT_1, 33); break; + case SOUND_PED_BURNING: GetPhrase(sfx, ped->m_lastComment, SFX_GENERIC_FEMALE_FIRE_1, 17); break; + case SOUND_PED_FLEE_SPRINT: GetPhrase(sfx, ped->m_lastComment, SFX_GENERIC_FEMALE_PANIC_1, 27); break; + default: return NO_SAMPLE; + } + return sfx; +} + +uint32 +cAudioManager::GetDefaultTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_GUN_PANIC_1, 12); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_JACKED_1, 12); break; +#ifdef FIX_BUGS + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_JACKING_1, 13); break; +#endif + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_MUGGED_1, 4); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_SAVED_1, 4); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_TAXI_1, 5); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_FIGHT_1, 16); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_DODGE_1, 19); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_RUN_1, 19); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_GENERIC_CRASH_1, 13); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_CAR_CRASH_1, 15); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_BLOCKED_1, 16); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_LOST_1, 5); break; +#ifdef FIX_BUGS + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_EYEING_1, 6); break; +#else + case SOUND_PED_CHAT_SEXY_FEMALE: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_EYEING_1, 6); break; +#endif + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_SHOCKED_1, 6); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_BUMP_1, 25); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_DEFAULT_VOICE_CHAT_1, 25); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetCopTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + PedState objective; + switch(sound) { + case SOUND_PED_ARREST_COP: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_ARREST_1, 4); break; + case SOUND_PED_PULLOUTWEAPON: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_PULLOUTWEAPON_1, 3); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_SAVED_1, 2); break; + case SOUND_PED_COP_TARGETING: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_COP_TARGETING_1, 4); break; + case SOUND_PED_COP_MANYCOPSAROUND: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_COP_MANYCOPSAROUND_1, 2); break; + case SOUND_PED_GUNAIMEDAT2: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_GUNAIMEDAT2_1, 2); break; + case SOUND_PED_COP_ALONE: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_COP_ALONE_1, 4); break; + case SOUND_PED_GUNAIMEDAT3: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_GUNAIMEDAT2_1, 2); break; + case SOUND_PED_COP_ASK_FOR_ID: { + cooldown_phrase(4); + GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_COP_ASK_FOR_ID_1, 2); + break; + } + case SOUND_PED_COP_LITTLECOPSAROUND: + objective = FindPlayerPed()->m_nPedState; + if(objective == PED_ARRESTED || objective == PED_DEAD || objective == PED_DIE) return NO_SAMPLE; + GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_COP_LITTLECOPSAROUND_1, 4); + break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_FIGHT_1, 4); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_DODGE_1, 3); break; +#ifdef FIX_BUGS + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_CAR_CRASH_1, 4); break; +#endif + case SOUND_PED_PED_COLLISION: + if(FindPlayerPed()->m_pWanted->GetWantedLevel() > 0) + GetPhrase(sfx, ped->m_lastComment, SFX_COP_VOICE_1_BUMP_1, 5); + else + return NO_SAMPLE; + break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return (SFX_COP_VOICE_2_ARREST_1 - SFX_COP_VOICE_1_ARREST_1) * (m_sQueueSample.m_nEntityIndex % 5) + sfx; +} + +uint32 +cAudioManager::GetSwatTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + switch(sound) { + case SOUND_PED_COP_HELIPILOTPHRASE: GetPhrase(sfx, ped->m_lastComment, SFX_SWAT_VOICE_1_COP_HELIPILOTPHRASE_1, 7); break; + case SOUND_PED_COP_TARGETING: GetPhrase(sfx, ped->m_lastComment, SFX_SWAT_VOICE_1_COP_TARGETING_1, 4); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_SWAT_VOICE_1_DODGE_1, 3); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + sfx += (SFX_SWAT_VOICE_2_DODGE_1 - SFX_SWAT_VOICE_1_DODGE_1) * (m_sQueueSample.m_nEntityIndex % 3); + return sfx; +} + +uint32 +cAudioManager::GetFBITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + switch(sound) { +#ifdef FIX_BUGS + case SOUND_PED_COP_TARGETING: GetPhrase(sfx, ped->m_lastComment, SFX_FBI_VOICE_1_COP_TARGETING_1, 6); break; +#else + case SOUND_PED_COP_TARGETING: GetPhrase(sfx, ped->m_lastComment, SFX_FBI_VOICE_1_COP_TARGETING_1, 4); break; +#endif + case SOUND_PED_COP_MANYCOPSAROUND: GetPhrase(sfx, ped->m_lastComment, SFX_FBI_VOICE_1_COP_MANYCOPSAROUND_1, 3); break; + case SOUND_PED_GUNAIMEDAT2: sfx = SFX_FBI_VOICE_1_GUNAIMEDAT2_1; break; + case SOUND_PED_GUNAIMEDAT3: GetPhrase(sfx, ped->m_lastComment, SFX_FBI_VOICE_1_GUNAIMEDAT3_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_FBI_VOICE_1_CAR_CRASH_1, 4); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } +#ifdef FIX_BUGS + sfx += (SFX_FBI_VOICE_2_GUNAIMEDAT3_1 - SFX_FBI_VOICE_1_GUNAIMEDAT3_1) * (m_sQueueSample.m_nEntityIndex % 3); +#else + sfx += 16 * (m_sQueueSample.m_nEntityIndex % 3); +#endif + return sfx; +} + +uint32 +cAudioManager::GetArmyTalkSfx(CPed *ped, uint16 sound) +{ + return GetGenericMaleTalkSfx(ped, sound); +} + +uint32 +cAudioManager::GetMedicTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + switch(sound) { + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_MEDIC_VOICE_1_FIGHT_1, 6); break; + case SOUND_PED_HEALING: GetPhrase(sfx, ped->m_lastComment, SFX_MEDIC_VOICE_1_AT_VICTIM_1, 17); break; + case SOUND_PED_LEAVE_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_MEDIC_VOICE_1_GET_OUT_VAN_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + sfx += (SFX_MEDIC_VOICE_2_FIGHT_1 - SFX_MEDIC_VOICE_1_FIGHT_1) * (m_sQueueSample.m_nEntityIndex % 2); + return sfx; +} + +uint32 +cAudioManager::GetFiremanTalkSfx(CPed *ped, uint16 sound) +{ + return GetGenericMaleTalkSfx(ped, sound); +} + +uint32 +cAudioManager::GetWFYG1TalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_GUN_COOL_1, 6); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_MUGGING_1, 2); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_JACKED_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFYG1_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WFYG1_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_FIGHT_1, 4); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_RUN_1, 2); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_BLOCKED_1, 7); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_LOST_1, 3); break; + case SOUND_PED_CHAT_SEXY_FEMALE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: return SFX_WFYG1_SHOCKED_1; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG1_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYG2TalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_GUN_COOL_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_JACKED_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_MUGGED_1, 2); break; +#ifdef FIX_BUGS + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_TAXI_1, 2); break; +#else + case SOUND_PED_TAXI_WAIT: return SFX_WFYG2_TAXI_1; +#endif + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_FIGHT_1, 5); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_DODGE_1, 8); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_BLOCKED_1, 5); break; + case SOUND_PED_WAIT_DOUBLEBACK: return SFX_WFYG2_LOST_1; + case SOUND_PED_CHAT_SEXY_FEMALE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_EYEING_1, 4); break; + case SOUND_PED_CHAT_EVENT: return SFX_WFYG2_SHOCKED_1; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYG2_CHAT_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + + return sfx; +} + +uint32 +cAudioManager::GetHFYSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_JACKING_1, 4); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_MUGGING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_MUGGED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_HFYST_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_BLOCKED_1, 7); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_LOST_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HFYST_CHAT_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFOSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_GUN_COOL_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_MUGGED_1, 3); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_GENERIC_CRASH_1, 11); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_BLOCKED_1, 9); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_LOST_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_BUMP_1, 12); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HFOST_CHAT_1, 11); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMYSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HMYST_GUN_PANIC_1, 6); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_HMYST_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_HMYST_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYST_DODGE_1, 6); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HMYST_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYST_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYST_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HMYST_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HMYST_BUMP_1, 13); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HMYST_CHAT_1, 11); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMOSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_JACKING_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_MUGGED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_HMOST_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_DODGE_1, 9); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_BLOCKED_1, 7); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: return SFX_HMOST_EYEING_1; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HMOST_CHAT_1, 11); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFYRITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_MUGGED_1, 4); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_HFYRI_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_DODGE_1, 10); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_LOST_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFYRI_BUMP_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFORITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_GUN_PANIC_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_JACKED_1, 9); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_HFORI_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_HFORI_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_DODGE_1, 6); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_BLOCKED_1, 6); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_FEMALE: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFORI_BUMP_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMYRITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_GUN_PANIC_1, 7); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_JACKING_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_JACKED_1, 8); break; + case SOUND_PED_ROBBED: return SFX_HMYRI_MUGGED_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_FIGHT_1, 5); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_DODGE_1, 9); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_GENERIC_CRASH_1, 12); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_BLOCKED_1, 7); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HMYRI_BUMP_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMORITalkSfx(CPed *ped, uint16 sound) +{ + + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_MUGGED_1, 3); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_DODGE_1, 7); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_RUN_1, 6); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_GENERIC_CRASH_1, 11); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_CAR_CRASH_1, 6); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_LOST_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HMORI_CHAT_1, 8); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFYBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_GUN_PANIC_1, 7); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_JACKED_1, 7); break; + case SOUND_PED_TAXI_WAIT: return SFX_HFYBE_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_DODGE_1, 11); break; +#ifdef FIX_BUGS // assumption + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_RUN_1, 7); break; +#endif + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_CAR_CRASH_1, 6); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_LOST_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_BUMP_1, 8); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBE_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFOBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_JACKED_1, 6); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_HFOBE_SAVED_1; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_DODGE_1, 7); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_GENERIC_CRASH_1, 5); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_CAR_CRASH_1, 6); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_BLOCKED_1, 6); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_LOST_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HFOBE_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMYBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_GUN_PANIC_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_JACKED_1, 12); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_HMYBE_SAVED_1; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_INNOCENT_1, 4); break; + case SOUND_PED_TAXI_WAIT: return SFX_HMYBE_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_DODGE_1, 7); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_GENERIC_CRASH_1, 10); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_CAR_CRASH_1, 7); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_LOST_1, 3); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_EYEING_1, 5); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HMYBE_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMOBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HMOBE_GUN_PANIC_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HMOBE_JACKED_1, 6); break; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_HMOBE_INNOCENT_1, 3); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HMOBE_DODGE_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HMOBE_BLOCKED_1, 10); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_HMOBE_EYEING_1, 4); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HMOBE_BUMP_1, 8); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + + return sfx; +} + +uint32 +cAudioManager::GetHFYBUTalkSfx(CPed *ped, uint16 sound) +{ + + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_JACKING_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_HFYBU_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_HFYBU_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_GENERIC_CRASH_1, 12); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_LOST_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFYBU_BUMP_1, 11); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFYMDTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HFYMD_GUN_PANIC_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYMD_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_HFYMD_SAVED_1, 3); break; +#ifdef FIX_BUGS + case SOUND_PED_TAXI_WAIT: return SFX_HFYMD_TAXI_1; +#else + case SOUND_PED_TAXI_WAIT: return SFX_BFOBE_TAXI_1; +#endif + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFYMD_FIGHT_1, 9); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYMD_DODGE_1, 8); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_HFYMD_SOLICIT_1, 15); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFYMD_BUMP_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFYCGTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HFYCG_GUN_PANIC_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYCG_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_HFYCG_SAVED_1; +#ifdef FIX_BUGS + case SOUND_PED_TAXI_WAIT: return SFX_HFYCG_TAXI_1; +#else + case SOUND_PED_TAXI_WAIT: return SFX_BFOBE_TAXI_1; +#endif + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYCG_DODGE_1, 8); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HFYCG_RUN_1, 4); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_HFYCG_SOLICIT_1, 14); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFYCG_BUMP_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFYPRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_GUN_COOL_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_HFYPR_SAVED_1; + case SOUND_PED_PLAYER_BEFORESEX: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_FUCKING_1, 8); break; + case SOUND_PED_TAXI_WAIT: return SFX_HFYPR_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_FIGHT_1, 10); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_DODGE_1, 9); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_SOLICIT_1, 14); break; + case SOUND_PED_CHAT_SEXY_FEMALE: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_EYEING_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HFYPR_CHAT_1, 12); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHFOTRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_HFOTR_GUN_COOL_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HFOTR_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_HFOTR_SAVED_1; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_HFOTR_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HFOTR_FIGHT_1, 6); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HFOTR_DODGE_1, 8); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HFOTR_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HFOTR_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HFOTR_CHAT_1, 12); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMOTRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_HMOTR_GUN_COOL_1, 6); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_HMOTR_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_HMOTR_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HMOTR_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HMOTR_DODGE_1, 11); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_HMOTR_SOLICIT_1, 8); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_HMOTR_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HMOTR_BUMP_1, 8); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HMOTR_CHAT_1, 9); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMOCATalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_JACKING_1, 11); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_JACKED_1, 10); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_MUGGED_1, 7); break; + case SOUND_PED_TAXI_WAIT: return SFX_HMOCA_TAXI_1; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_RUN_1, 2); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_BLOCKED_1, 8); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_EYEING_1, 2); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HMOCA_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMYCRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_GUN_COOL_1, 6); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_JACKING_1, 12); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_MUGGING_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_MUGGED_1, 3); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_SAVED_1, 2); break; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_INNOCENT_1, 4); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_DODGE_1, 8); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_BLOCKED_1, 12); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMYCR_BUMP_1, 11); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBFYSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_GUN_PANIC_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_JACKED_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_BFYST_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_RUN_1, 6); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_LOST_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_BUMP_1, 9); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYST_CHAT_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBFOSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_BFOST_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_DODGE_1, 11); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_BLOCKED_1, 7); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_LOST_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BFOST_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMYSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_GUN_COOL_1, 6); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_JACKING_1, 4); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_MUGGING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_MUGGED_1, 2); break; +#ifdef FIX_BUGS + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_TAXI_1, 2); break; +#else + case SOUND_PED_TAXI_WAIT: return SFX_BMYST_TAXI_1; +#endif + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_FIGHT_1, 6); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_DODGE_1, 8); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_BLOCKED_1, 8); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYST_CHAT_1, 12); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMOSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_GUN_PANIC_1, 9); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_MUGGED_1, 4); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_BMOST_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_BMOST_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_DODGE_1, 8); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_GENERIC_CRASH_1, 13); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_CAR_CRASH_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_LOST_1, 6); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_EYEING_1, 6); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_BUMP_1, 17); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BMOST_CHAT_1, 18); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBFYRITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_GUN_PANIC_1, 4); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_JACKING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_MUGGED_1, 3); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_BFYRI_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_DODGE_1, 8); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_RUN_1, 6); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_BLOCKED_1, 9); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_FEMALE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_EYEING_1, 3); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_SHOCKED_1, 4); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BFYRI_BUMP_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBFORITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_JACKED_1, 4); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_BFORI_SAVED_1; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_LOST_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BFORI_BUMP_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMYRITalkSfx(CPed *ped, uint16 sound) +{ + + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_GUN_PANIC_1, 7); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_JACKED_1, 4); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_BMYRI_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_BMYRI_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_DODGE_1, 8); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_BLOCKED_1, 6); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: return SFX_BMYRI_EYEING_1; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMYRI_BUMP_1, 7); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBFYBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_GUN_COOL_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_MUGGED_1, 5); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_TAXI_1, 3); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_DODGE_1, 10); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_RUN_1, 6); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_GENERIC_CRASH_1, 8); break; +#ifdef FIX_BUGS + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_CAR_CRASH_1, 10); break; +#else + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_CAR_CRASH_1, 8); break; +#endif + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_BLOCKED_1, 12); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_LOST_1, 4); break; +#ifdef FIX_BUGS + case SOUND_PED_CHAT_SEXY_FEMALE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_EYEING_1, 4); break; +#else + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_EYEING_1, 4); break; +#endif + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_SHOCKED_1, 4); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYBE_CHAT_1, 16); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMYBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_GUN_COOL_1, 4); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_JACKING_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_JACKED_1, 6); break; + case SOUND_PED_ROBBED: return SFX_BMYBE_MUGGED_1; + case SOUND_PED_ACCIDENTREACTION1: return SFX_BMYBE_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_BMYBE_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: return SFX_BMYBE_LOST_1; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBE_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBFOBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_JACKING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_JACKED_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_MUGGED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_BFOBE_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_DODGE_1, 9); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_BLOCKED_1, 8); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_BUMP_1, 8); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BFOBE_CHAT_1, 8); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMOBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_MUGGED_1, 4); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_SAVED_1, 3); break; + case SOUND_PED_TAXI_WAIT: return SFX_BMOBE_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_FIGHT_1, 10); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_DODGE_1, 11); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_CAR_CRASH_1, 9); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_BUMP_1, 5); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BMOBE_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMYBUTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_BMYBU_SAVED_1; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_INNOCENT_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_BMYBU_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_FIGHT_1, 5); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_BLOCKED_1, 8); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBU_BUMP_1, 7); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBFYPRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_GUN_COOL_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_BFYPR_SAVED_1; + case SOUND_PED_PLAYER_BEFORESEX: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_FUCKING_1, 7); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_DODGE_1, 7); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_SOLICIT_1, 13); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BFYPR_CHAT_1, 13); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBFOTRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_GUN_COOL_1, 6); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_MUGGING_1, 3); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_BFOTR_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_BFOTR_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_FIGHT_1, 6); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_DODGE_1, 9); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_SOLICIT_1, 5); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BFOTR_CHAT_1, 15); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMOTRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_GUN_COOL_1, 5); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_SAVED_1, 1); break; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_INNOCENT_1, 4); break; + case SOUND_PED_TAXI_WAIT: return SFX_BMOTR_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_DODGE_1, 11); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_RUN_1, 7); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_SOLICIT_1, 7); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_EYEING_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BMOTR_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMYPITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_JACKING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_JACKED_1, 6); break; + case SOUND_PED_ROBBED: return SFX_BMYPI_MUGGED_1; + case SOUND_PED_ACCIDENTREACTION1: return SFX_BMYPI_SAVED_1; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_GENERIC_CRASH_1, 13); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_CAR_CRASH_1, 5); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_BLOCKED_1, 6); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_EYEING_1, 4); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMYPI_BUMP_1, 9); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMYBBTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_JACKING_1, 9); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_MUGGING_1, 8); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_JACKED_1, 11); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_MUGGED_1, 5); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_SAVED_1, 6); break; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_INNOCENT_1, 4); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_TAXI_1, 3); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_FIGHT_1, 12); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_DODGE_1, 18); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_GENERIC_CRASH_1, 9); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_BLOCKED_1, 13); break; + case SOUND_PED_JEER: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_JEER_1, 16); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_EYEING_1, 16); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_SHOCKED_1, 6); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_BUMP_1, 17); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BMYBB_CHAT_1, 21); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYCRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_JACKING_1, 6); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_MUGGING_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_MUGGED_1, 3); break; + case SOUND_PED_TAXI_WAIT: return SFX_WMYCR_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_GENERIC_CRASH_1, 9); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_CAR_CRASH_1, 9); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCR_BUMP_1, 18); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_JACKING_1, 4); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_MUGGING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFYST_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WFYST_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_BLOCKED_1, 6); break; + case SOUND_PED_WAIT_DOUBLEBACK: return SFX_WFYST_LOST_1; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYST_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYSKTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSK_GUN_PANIC_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSK_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSK_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: return SFX_WFYSK_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSK_FIGHT_1, 11); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSK_DODGE_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSK_BLOCKED_1, 11); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSK_BUMP_1, 18); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYSKTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_GUN_PANIC_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_SAVED_1, 2); break; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_INNOCENT_1, 3); break; + case SOUND_PED_TAXI_WAIT: return SFX_WMYSK_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_FIGHT_1, 5); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_DODGE_1, 10); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_BUMP_1, 14); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYSK_CHAT_1, 13); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFOSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_GUN_PANIC_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_MUGGED_1, 5); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_SAVED_1, 4); break; + case SOUND_PED_TAXI_WAIT: return SFX_WFOST_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_DODGE_1, 12); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_RUN_1, 7); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_GENERIC_CRASH_1, 10); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_CAR_CRASH_1, 11); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_BLOCKED_1, 12); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_LOST_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_BUMP_1, 19); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOST_CHAT_1, 16); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_GUN_PANIC_1, 5); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_MUGGING_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_JACKED_1, 5); break; + case SOUND_PED_ROBBED: return SFX_WMYST_MUGGED_1; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMYST_SAVED_1; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_INNOCENT_1, 3); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_DODGE_1, 10); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_RUN_1, 7); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_GENERIC_CRASH_1, 5); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYST_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMOSTTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_JACKED_1, 4); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMOST_SAVED_1; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_INNOCENT_1, 3); break; + case SOUND_PED_TAXI_WAIT: return SFX_WMOST_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_DODGE_1, 8); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_BLOCKED_1, 8); break; + case SOUND_PED_JEER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_JEER_1, 4); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOST_CHAT_1, 9); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYRITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_JACKED_1, 7); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFYRI_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WFYRI_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_RUN_1, 5); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_FEMALE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYRI_BUMP_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFORITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_GUN_PANIC_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_MUGGED_1, 3); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFORI_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WFORI_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_DODGE_1, 11); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_CAR_CRASH_1, 10); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_BLOCKED_1, 7); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_LOST_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFORI_BUMP_1, 11); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYRITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_GUN_PANIC_1, 8); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_JACKED_1, 8); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMYRI_SAVED_1; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_RUN_1, 5); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_GENERIC_CRASH_1, 11); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_BLOCKED_1, 10); break; + case SOUND_PED_WAIT_DOUBLEBACK: return SFX_WMYRI_LOST_1; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_EYEING_1, 3); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_SHOCKED_1, 4); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_BUMP_1, 8); break; +#ifdef FIX_BUGS + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYRI_CHAT_1, 10); break; +#endif + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMORITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_GUN_PANIC_1, 9); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_MUGGED_1, 4); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_DODGE_1, 10); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_RUN_1, 12); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_CAR_CRASH_1, 6); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_BLOCKED_1, 10); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_EYEING_1, 3); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_SHOCKED_1, 4); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMORI_BUMP_1, 14); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_JACKED_1, 4); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFYBE_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WFYBE_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_DODGE_1, 8); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_RUN_1, 5); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_GENERIC_CRASH_1, 6); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_CAR_CRASH_1, 6); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_BLOCKED_1, 7); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBE_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_GUN_PANIC_1, 8); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_JACKING_1, 3); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_JACKED_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_DODGE_1, 12); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_RUN_1, 5); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_BLOCKED_1, 9); break; + case SOUND_PED_JEER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_JEER_1, 7); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_LOST_1, 3); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_SHOCKED_1, 6); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_BUMP_1, 14); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBE_CHAT_1, 11); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFOBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_JACKED_1, 4); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_SAVED_1, 3); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_DODGE_1, 8); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_RUN_1, 7); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_GENERIC_CRASH_1, 10); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_BLOCKED_1, 8); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOBE_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMOBETalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_JACKING_1, 4); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_MUGGING_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_JACKED_1, 8); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_SAVED_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_DODGE_1, 8); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_BLOCKED_1, 6); break; + case SOUND_PED_JEER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_JEER_1, 16); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_BUMP_1, 12); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYCWTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_GUN_PANIC_1, 6); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_JACKING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_JACKED_1, 6); break; + case SOUND_PED_ROBBED: return SFX_WMYCW_MUGGED_1; +#ifdef FIX_BUGS + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_INNOCENT_1, 3); break; +#endif + case SOUND_PED_TAXI_WAIT: return SFX_WMYCW_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_BLOCKED_1, 9); break; + case SOUND_PED_JEER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_JEER_1, 5); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_EYEING_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_BUMP_1, 9); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYCW_CHAT_1, 15); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYGOTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMYGO_SAVED_1; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_TAXI_1, 3); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_DODGE_1, 11); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_RUN_1, 6); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_CAR_CRASH_1, 7); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_BUMP_1, 9); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYGO_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFOGOTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFOGO_SAVED_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_FIGHT_1, 14); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_RUN_1, 2); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_CAR_CRASH_1, 8); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_BUMP_1, 8); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOGO_CHAT_1, 11); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMOGOTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_GUN_PANIC_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_JACKED_1, 6); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMOGO_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WMOGO_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_FIGHT_1, 13); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_DODGE_1, 12); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_RUN_1, 5); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_CAR_CRASH_1, 9); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_BUMP_1, 8); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOGO_CHAT_1, 9); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYLGTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WFYLG_GUN_COOL_1, 5); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFYLG_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WFYLG_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYLG_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYLG_DODGE_1, 8); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYLG_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYLG_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYLGTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WMYLG_GUN_COOL_1, 6); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMYLG_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WMYLG_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYLG_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYLG_DODGE_1, 9); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYLG_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYLG_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYBUTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_GUN_PANIC_1, 8); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_MUGGED_1, 4); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_TAXI_1, 2); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_RUN_1, 8); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_CAR_CRASH_1, 9); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYBU_BUMP_1, 21); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYBUTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_GUN_PANIC_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_JACKED_1, 5); break; + case SOUND_PED_ROBBED: return SFX_WMYBU_MUGGED_1; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_SAVED_1, 2); break; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_INNOCENT_1, 2); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_DODGE_1, 10); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_RUN_1, 3); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_GENERIC_CRASH_1, 5); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_CAR_CRASH_1, 9); break; +#ifdef FIX_BUGS + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_BLOCKED_1, 9); break; +#else + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBE_BLOCKED_1, 6); break; +#endif + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_LOST_1, 5); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_SHOCKED_1, 5); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYBU_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMOBUTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_GUN_PANIC_1, 6); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_JACKED_1, 7); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_SAVED_1, 3); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_FIGHT_1, 3); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_DODGE_1, 8); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_CAR_CRASH_1, 7); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_BLOCKED_1, 7); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_LOST_1, 3); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMOBU_BUMP_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYPRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WFYPR_GUN_COOL_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYPR_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFYPR_SAVED_1; + case SOUND_PED_PLAYER_BEFORESEX: GetPhrase(sfx, ped->m_lastComment, SFX_WFYPR_FUCKING_1, 5); break; + case SOUND_PED_TAXI_WAIT: return SFX_WFYPR_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYPR_FIGHT_1, 9); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYPR_DODGE_1, 10); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYPR_SOLICIT_1, 15); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYPR_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYPR_CHAT_1, 14); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFOTRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WFOTR_GUN_COOL_1, 6); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WFOTR_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WFOTR_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFOTR_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFOTR_RUN_1, 6); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOTR_SOLICIT_1, 9); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFOTR_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOTR_CHAT_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMOTRTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WMOTR_GUN_COOL_1, 5); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMOTR_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WMOTR_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMOTR_FIGHT_1, 6); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOTR_DODGE_1, 17); break; + case SOUND_PED_SOLICIT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOTR_SOLICIT_1, 7); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOTR_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOTR_SHOCKED_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMOTR_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WMOTR_CHAT_1, 13); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYPITalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_JACKING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_SAVED_1, 2); break; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_INNOCENT_1, 2); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_TAXI_1, 4); break; +#ifdef FIX_BUGS + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_FIGHT_1, 9); break; +#else + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_FIGHT_1, 7); break; +#endif + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_DODGE_1, 8); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_BLOCKED_1, 8); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_EYEING_1, 6); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYPI_BUMP_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMOCATalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_GUN_PANIC_1, 6); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_JACKING_1, 11); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_JACKED_1, 10); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMOCA_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WMOCA_TAXI_1; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_FIGHT_1, 8); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_GENERIC_CRASH_1, 9); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_CAR_CRASH_1, 10); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_BLOCKED_1, 12); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMOCA_BUMP_1, 6); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYSHTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_GUN_COOL_1, 9); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_SAVED_1, 4); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_DODGE_1, 11); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_RUN_1, 11); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_LOST_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_SHOCKED_1, 5); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_BUMP_1, 12); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFYSH_CHAT_1, 10); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFOSHTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_GUN_COOL_1, 10); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_SAVED_1, 3); break; + case SOUND_PED_TAXI_WAIT: return SFX_WFOSH_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_DODGE_1, 10); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_RUN_1, 9); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_LOST_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_SHOCKED_1, 5); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_WFOSH_CHAT_1, 9); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetJFOTOTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_GUN_PANIC_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_JACKED_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_RUN_1, 5); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_GENERIC_CRASH_1, 6); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: return SFX_JFOTO_LOST_1; + case SOUND_PED_CHAT_EVENT: return SFX_JFOTO_SHOCKED_1; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_JFOTO_CHAT_1, 13); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetJMOTOTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_GUN_PANIC_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_JACKED_1, 4); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_JMOTO_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_JMOTO_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_DODGE_1, 6); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_GENERIC_CRASH_1, 6); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_CAR_CRASH_1, 6); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: return SFX_JMOTO_LOST_1; + case SOUND_PED_CHAT_EVENT: return SFX_JMOTO_SHOCKED_1; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_BUMP_1, 8); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_JMOTO_CHAT_1, 7); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHNTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_JACKING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_MUGGED_1, 3); break; + case SOUND_PED_ACCIDENTREACTION1: sfx = SFX_HAITIAN_GANG_1_SAVED_1; break; + case SOUND_PED_TAXI_WAIT: sfx = SFX_HAITIAN_GANG_1_TAXI_1; break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_FIGHT_1, 10); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_DODGE_1, 10); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_GENERIC_CRASH_1, 9); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_BLOCKED_1, 9); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_LOST_1, 4); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_BUMP_1, 12); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HAITIAN_GANG_1_CHAT_1, 14); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return (SFX_HAITIAN_GANG_2_BLOCKED_1 - SFX_HAITIAN_GANG_1_BLOCKED_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetBKTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_JACKING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_JACKED_1, 8); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: sfx = SFX_BIKER_GANG_1_SAVED_1; break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_FIGHT_1, 9); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_DODGE_1, 9); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_GENERIC_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_BLOCKED_1, 10); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_LOST_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_BIKER_GANG_1_CHAT_1, 12); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return (SFX_BIKER_GANG_2_BLOCKED_1 - SFX_BIKER_GANG_1_BLOCKED_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetCBTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_JACKING_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_JACKED_1, 4); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: sfx = SFX_CUBAN_GANG_1_SAVED_1; break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_FIGHT_1, 9); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_DODGE_1, 9); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_CAR_CRASH_1, 8); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_GANG_1_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return (SFX_CUBAN_GANG_2_BLOCKED_1 - SFX_CUBAN_GANG_1_BLOCKED_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetSGTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_JACKING_1, 5); break; + case SOUND_PED_MUGGING: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_MUGGING_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_JACKED_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_MUGGED_1, 3); break; + case SOUND_PED_ACCIDENTREACTION1: sfx = SFX_STREET_GANG_1_SAVED_1; break; + case SOUND_PED_TAXI_WAIT: sfx = SFX_STREET_GANG_1_TAXI_1; break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_FIGHT_1, 10); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_DODGE_1, 9); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_GENERIC_CRASH_1, 6); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_CAR_CRASH_1, 6); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_BLOCKED_1, 8); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_EYEING_1, 3); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_STREET_GANG_1_CHAT_1, 12); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + if(ped->GetModelIndex() == MI_SGB) sfx += (SFX_STREET_GANG_2_BLOCKED_1 - SFX_STREET_GANG_1_BLOCKED_1); + return sfx; +} + +uint32 +cAudioManager::GetCLTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_GUN_COOL_1, 5); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_JACKING_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_JACKED_1, 6); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: sfx = SFX_CUBAN_LORD_GANG_1_SAVED_1; break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_FIGHT_1, 10); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_DODGE_1, 13); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_1, 8); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_CAR_CRASH_1, 10); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_BLOCKED_1, 10); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_CUBAN_LORD_GANG_1_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return (SFX_CUBAN_LORD_GANG_2_BLOCKED_1 - SFX_CUBAN_LORD_GANG_1_BLOCKED_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetGDTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_GUN_COOL_1, 6); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_SAVED_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_FIGHT_1, 7); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_DODGE_1, 9); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_BUMP_1, 10); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_GUARD_DUTY_1_CHAT_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return (SFX_GUARD_DUTY_2_BUMP_1 - SFX_GUARD_DUTY_1_BUMP_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetPGTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_UP: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_GUN_COOL_1, 4); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_JACKING_1, 5); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_JACKED_1, 5); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: sfx = SFX_PLAYER_GANG_1_SAVED_1; break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_TAXI_1, 2); break; + case SOUND_PED_ATTACK: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_FIGHT_1, 5); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_DODGE_1, 7); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_GENERIC_CRASH_1, 5); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_CAR_CRASH_1, 5); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_BLOCKED_1, 10); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_EYEING_1, 2); break; + case SOUND_PED_CHAT_EVENT: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_SHOCKED_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_BUMP_1, 5); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_PLAYER_GANG_1_CHAT_1, 8); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return (SFX_PLAYER_GANG_2_BLOCKED_1 - SFX_PLAYER_GANG_1_BLOCKED_1) * (m_sQueueSample.m_nEntityIndex % 3) + sfx; +} + +uint32 +cAudioManager::GetViceWhiteTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + switch(sound) { + case SOUND_PED_ARREST_COP: GetPhrase(sfx, ped->m_lastComment, SFX_VICE_VOICE_1_ARREST_1, 3); break; + case SOUND_PED_MIAMIVICE_EXITING_CAR: sfx = SFX_VICE_VOICE_1_MIAMIVICE_EXITING_CAR_1; break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + sfx += (SFX_VICE_VOICE_2_ARREST_1-SFX_VICE_VOICE_1_ARREST_1) * (m_sQueueSample.m_nEntityIndex % 5); + return sfx; +} + +uint32 +cAudioManager::GetViceBlackTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + switch(sound) { + case SOUND_PED_ARREST_COP: GetPhrase(sfx, ped->m_lastComment, SFX_VICE_VOICE_6_ARREST_1, 3); break; + case SOUND_PED_MIAMIVICE_EXITING_CAR: return SFX_VICE_VOICE_6_MIAMIVICE_EXITING_CAR_1; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetBMODKTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_GUN_PANIC_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_JACKED_1, 9); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_MUGGED_1, 2); break; + case SOUND_PED_INNOCENT: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_INNOCENT_1, 3); break; + case SOUND_PED_TAXI_WAIT: return SFX_BMODK_TAXI_1; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_DODGE_1, 7); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_RUN_1, 4); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_GENERIC_CRASH_1, 7); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_CAR_CRASH_1, 10); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_BLOCKED_1, 8); break; + case SOUND_PED_147: // this is some cut behaviour, the guy was selling something + GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_UNK_147_1, 11); + // what is this? some sort of censorship? + switch(sfx) { + case SFX_BMODK_UNK_147_5: + case SFX_BMODK_UNK_147_6: + case SFX_BMODK_UNK_147_7: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_UNK_147_1, 4); break; + default: break; + } + break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_BMODK_BUMP_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetHMYAPTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_GUN_PANIC_1, 7); break; + case SOUND_PED_CAR_JACKING: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_JACKING_1, 4); break; + case SOUND_PED_CAR_JACKED: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_JACKED_1, 7); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_SAVED_1, 2); break; + case SOUND_PED_TAXI_WAIT: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_TAXI_1, 2); break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_DODGE_1, 9); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_RUN_1, 6); break; + case SOUND_PED_CRASH_VEHICLE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_GENERIC_CRASH_1, 6); break; + case SOUND_PED_CRASH_CAR: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_CAR_CRASH_1, 9); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_BLOCKED_1, 9); break; + case SOUND_PED_WAIT_DOUBLEBACK: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_LOST_1, 2); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_EYEING_1, 3); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_BUMP_1, 11); break; + case SOUND_PED_CHAT: GetPhrase(sfx, ped->m_lastComment, SFX_HMYAP_CHAT_1, 9); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWFYJGTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WFYJG_GUN_PANIC_1, 4); break; + case SOUND_PED_ACCIDENTREACTION1: sfx = SFX_WFYJG_SAVED_1; break; + case SOUND_PED_TAXI_WAIT: sfx = SFX_WFYJG_TAXI_1; break; + case SOUND_PED_EVADE: GetPhrase(sfx, ped->m_lastComment, SFX_WFYJG_DODGE_1, 8); break; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WFYJG_RUN_1, 6); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WFYJG_BUMP_1, 12); break; + default: return GetGenericFemaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetWMYJGTalkSfx(CPed *ped, uint16 sound) +{ + uint32 sfx; + + switch(sound) { + case SOUND_PED_HANDS_COWER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYJG_GUN_PANIC_1, 4); break; + case SOUND_PED_ROBBED: GetPhrase(sfx, ped->m_lastComment, SFX_WMYJG_MUGGED_1, 2); break; + case SOUND_PED_ACCIDENTREACTION1: return SFX_WMYJG_SAVED_1; + case SOUND_PED_TAXI_WAIT: return SFX_WMYJG_TAXI_1; + case SOUND_PED_FLEE_RUN: GetPhrase(sfx, ped->m_lastComment, SFX_WMYJG_RUN_1, 5); break; + case SOUND_PED_ANNOYED_DRIVER: GetPhrase(sfx, ped->m_lastComment, SFX_WMYJG_BLOCKED_1, 10); break; + case SOUND_PED_CHAT_SEXY_MALE: GetPhrase(sfx, ped->m_lastComment, SFX_WMYJG_EYEING_1, 2); break; + case SOUND_PED_PED_COLLISION: GetPhrase(sfx, ped->m_lastComment, SFX_WMYJG_BUMP_1, 10); break; + default: return GetGenericMaleTalkSfx(ped, sound); + } + return sfx; +} + +uint32 +cAudioManager::GetSpecialCharacterTalkSfx(CPed *ped, int32 model, uint16 sound) +{ + return NO_SAMPLE; +} + +void +cAudioManager::DebugPlayPedComment(int32 sound) +{ + tPedComment pedComment; + + pedComment.m_nSampleIndex = sound; + pedComment.m_nLoadingTimeout = 10; + pedComment.m_nEntityIndex = 0; + pedComment.m_fDistance = 0.0f; + pedComment.m_nVolume = 99; +#if defined(EXTERNAL_3D_SOUND) && defined(FIX_BUGS) + pedComment.m_nEmittingVolume = 99; +#endif + + pedComment.m_vecPos = CWorld::Players[0].m_pPed->GetPosition(); + + m_sPedComments.Add(&pedComment); +} + +#ifdef DEBUGMENU +uint32 nDebugPlayPedComment = SAMPLEBANK_PED_START; + +void DebugMenuPlayPedComment() +{ + AudioManager.DebugPlayPedComment(nDebugPlayPedComment); +} + +SETTWEAKPATH("Audio"); +TWEAKUINT32N(nDebugPlayPedComment, SAMPLEBANK_PED_START, SAMPLEBANK_PED_END, 1, "Ped Comment ID"); +TWEAKFUNCN(DebugMenuPlayPedComment, "Play Ped Comment"); + +#endif + +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; + bool8 bIsPlayerComment; + static uint8 counter = 0; + static uint32 prevSamples[10] = { NO_SAMPLE, NO_SAMPLE, NO_SAMPLE, NO_SAMPLE, NO_SAMPLE, NO_SAMPLE, NO_SAMPLE, NO_SAMPLE, NO_SAMPLE, NO_SAMPLE }; + + if (AudioManager.m_bIsPaused) return; + + if (m_nPedCommentCount[m_nActiveQueue]) { + for(int i = 0; i < ARRAY_SIZE(prevSamples); i++) { + if(m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nSampleIndex == + prevSamples[(counter + 1 + i) % ARRAY_SIZE(prevSamples)]) { + m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nLoadingTimeout = -1; + goto PedCommentAlreadyAdded; + } + } +#if defined(GTA_PS2) || defined(FIX_BUGS) + uint8 IsLoadedResult; + sampleIndex = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_nSampleIndex; + if (sampleIndex >= PLAYER_COMMENTS_START && sampleIndex <= PLAYER_COMMENTS_END) { + IsLoadedResult = SampleManager.IsMissionAudioLoaded(MISSION_AUDIO_PLAYER_COMMENT, sampleIndex); + bIsPlayerComment = TRUE; + } else { + IsLoadedResult = SampleManager.IsPedCommentLoaded(sampleIndex); + bIsPlayerComment = FALSE; + } + switch(IsLoadedResult) { +#else + switch(SampleManager.IsPedCommentLoaded(sampleIndex)) { +#endif + case LOADING_STATUS_NOT_LOADED: +#if defined(GTA_PC) && !defined(FIX_BUGS) + if(!m_bDelay) +#endif +#if defined(GTA_PS2) || defined(FIX_BUGS) + if (bIsPlayerComment) + SampleManager.LoadMissionAudio(MISSION_AUDIO_PLAYER_COMMENT, sampleIndex); + else +#endif + SampleManager.LoadPedComment(sampleIndex); + break; + 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 +#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 + AudioManager.m_sQueueSample.m_MaxDistance = PED_COMMENT_MAX_DIST; + AudioManager.m_sQueueSample.m_bStatic = TRUE; + AudioManager.m_sQueueSample.m_vecPos = m_aPedCommentQueue[m_nActiveQueue][m_aPedCommentOrderList[m_nActiveQueue][0]].m_vecPos; +#ifdef AUDIO_REVERB + AudioManager.m_sQueueSample.m_bReverb = TRUE; +#endif // AUDIO_REVERB +#ifdef AUDIO_REFLECTIONS + AudioManager.m_sQueueSample.m_bReflections = TRUE; +#endif // AUDIO_REFLECTIONS + AudioManager.m_sQueueSample.m_bIs2D = FALSE; +#ifdef FIX_BUGS + if((sampleIndex >= SFX_POLICE_BOAT_1 && sampleIndex <= SFX_POLICE_BOAT_23) || + (sampleIndex >= SFX_POLICE_HELI_1 && sampleIndex <= SFX_POLICE_HELI_20)) + AudioManager.m_sQueueSample.m_MaxDistance = PED_COMMENT_POLICE_HELI_MAX_DIST; + #ifndef ATTACH_RELEASING_SOUNDS_TO_ENTITIES + else if (sampleIndex >= PLAYER_COMMENTS_START && sampleIndex <= PLAYER_COMMENTS_END) { // check if player sfx + AudioManager.m_sQueueSample.m_bIs2D = TRUE; + AudioManager.m_sQueueSample.m_nPan = 63; + } + #endif // ATTACH_RELEASING_SOUNDS_TO_ENTITIES +#endif // FIX_BUGS + 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; + prevSamples[counter++] = sampleIndex; + if(counter == 10) counter = 0; + AudioManager.AddSampleToRequestedQueue(); +#if defined(GTA_PC) && !defined(FIX_BUGS) + m_nDelayTimer = CTimer::GetTimeInMilliseconds(); + m_bDelay = TRUE; +#endif + break; + case LOADING_STATUS_LOADING: break; + default: break; + } + } + +PedCommentAlreadyAdded: + // 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; +#if defined(GTA_PC) && !defined(FIX_BUGS) + if(m_bDelay) + if(CTimer::GetTimeInMilliseconds() - m_nDelayTimer > 6000) m_bDelay = FALSE; +#endif +} + +#undef cooldown_phrase + +#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::DoesExplosionMakeSound(i) && 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; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 19000; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + SET_SOUND_REFLECTION(TRUE); + 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; + SET_SOUND_REFLECTION(TRUE); + 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; + SET_SOUND_REFLECTION(FALSE); + break; + default: + m_sQueueSample.m_MaxDistance = EXPLOSION_DEFAULT_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_EXPLOSION_1; + m_sQueueSample.m_nFrequency = RandomDisplacement(1000) + 19500; + if (type == EXPLOSION_HELI) + m_sQueueSample.m_nFrequency = 8 * m_sQueueSample.m_nFrequency / 10; //same *= 8 / 10; + m_sQueueSample.m_nPriority = 0; + m_sQueueSample.m_nBankIndex = SFX_BANK_GENERIC_EXTRA; +#ifdef FIX_BUGS + SET_SOUND_REFLECTION(TRUE); +#endif + 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; + SET_EMITTING_VOLUME(MAX_VOLUME); + RESET_LOOP_OFFSETS + SET_SOUND_REVERB(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); + 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) + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + if (gFireManager.m_aFires[i].m_bExtinguishedWithWater) { + gFireManager.m_aFires[i].m_bExtinguishedWithWater = FALSE; + Vol = FIRE_EXTINGUISH_VOLUME * gFireManager.m_aFires[i].m_fWaterExtinguishCountdown; + 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_JUMBO_TAXI; + m_sQueueSample.m_nFrequency = 19591; + m_sQueueSample.m_nFrequency += i * (m_sQueueSample.m_nFrequency >> 6); + m_sQueueSample.m_nPriority = 9; + m_sQueueSample.m_nCounter = i + 40; + 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) + SET_SOUND_REVERB(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); + m_sQueueSample.m_nVolume = ComputeVolume(WATER_CANNON_VOLUME, WATER_CANNON_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_MaxDistance = WATER_CANNON_MAX_DIST; + 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) + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +#pragma region SCRIPT_OBJECTS +void +cAudioManager::ProcessScriptObject(int32 id) +{ + if (MusicManager.m_nMusicMode == MUSICMODE_GAME) { + 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_SEAPLANE_LOW_FUEL: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SEAPLANE_LOW_FUEL_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_SEAPLANE_LOW; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_SEAPLANE_LOW_FUEL_VOLUME; +#ifdef FIX_BUGS + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_SEAPLANE_LOW); +#else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_CAR_HORN_JEEP); +#endif + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = TRUE; + SET_SOUND_REFLECTION(FALSE); + break; + case SCRIPT_SOUND_WILLIE_CARD_SWIPE: + Vol = SCRIPT_OBJECT_WILLIE_CARD_SWIPE_VOLUME; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_WILLIE_CARD_SWIPE_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_BOMB_BEEP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 20159; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_fSpeedMultiplier = 1.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(FALSE); + break; + 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_HIT_BALL: + m_sQueueSample.m_MaxDistance = COLLISION_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_HIT_BALL; + 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 = 5; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + Vol = m_anRandomTable[2] % 30 + SCRIPT_OBJECT_HIT_BALL_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: + case SURFACE_SAND_BEACH: + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_2; + m_sQueueSample.m_nFrequency = RandomDisplacement(600) + 10600; + 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(1500) + 30000; + m_sQueueSample.m_nPriority = 15; + break; + } + } else { + m_sQueueSample.m_nSampleIndex = SFX_BULLET_SHELL_HIT_GROUND_1; + m_sQueueSample.m_nFrequency = RandomDisplacement(1500) + 30000; + 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_SHOOTING_RANGE_TARGET_HIT: + 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_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_MALE_AMBULANCE_OUCH: + { + cPedParams pedParams; + pedParams.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + SetupPedComments(pedParams, SOUND_INJURED_PED_MALE_OUCH); + return; + } + case SCRIPT_SOUND_FEMALE_AMBULANCE_OUCH: + { + cPedParams pedParams; + pedParams.m_fDistance = GetDistanceSquared(m_sQueueSample.m_vecPos); + SetupPedComments(pedParams, SOUND_INJURED_PED_FEMALE); + return; + } + case SCRIPT_SOUND_POLICE_CELL_DOOR_CLUNK: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_POLICE_CELL_DOOR_CLUNK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_GATE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 10600; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 3; + Vol = SCRIPT_OBJECT_POLICE_CELL_DOOR_CLUNK_VOLUME; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + break; + case SCRIPT_SOUND_GARAGE_DOOR_CLUNK: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_GARAGE_DOOR_CLUNK_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_COL_CAR_PANEL_2; // huh? + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 22000; + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + m_sQueueSample.m_nPriority = 4; + Vol = SCRIPT_OBJECT_GARAGE_DOOR_CLUNK_VOLUME; + m_sQueueSample.m_fSpeedMultiplier = 0.0f; + m_sQueueSample.m_bIs2D = FALSE; + SET_SOUND_REFLECTION(TRUE); + 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 + SET_SOUND_REVERB(TRUE); + AddSampleToRequestedQueue(); + } + } +} + +void +cAudioManager::ProcessLoopingScriptObject(uint8 sound) +{ + uint8 Vol; + float distSquared; + + switch(sound) { + case SCRIPT_SOUND_NEW_BUILDING_BAR_1: + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_BAR_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_BAR_1; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_BAR_2: + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_BAR_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_BAR_2; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_BAR_3: + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_BAR_3; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_BAR_3; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_BAR_4: + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_BAR_4; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_BAR_4; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_MALIBU_1: + if(MusicManager.m_nPlayingTrack == STREAMED_SOUND_MALIBU_AMBIENT) return; + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_MALIBU_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_MALIBU_1; + MusicManager.SetMalibuClubTrackPos(SCRIPT_SOUND_NEW_BUILDING_MALIBU_1); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_MALIBU_2: + if(MusicManager.m_nPlayingTrack == STREAMED_SOUND_MALIBU_AMBIENT) return; + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_MALIBU_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_MALIBU_2; + MusicManager.SetMalibuClubTrackPos(SCRIPT_SOUND_NEW_BUILDING_MALIBU_2); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_MALIBU_3: + if(MusicManager.m_nPlayingTrack == STREAMED_SOUND_MALIBU_AMBIENT) return; + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_MALIBU_3; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_MALIBU_3; + MusicManager.SetMalibuClubTrackPos(SCRIPT_SOUND_NEW_BUILDING_MALIBU_3); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_STRIP_1: + if(MusicManager.m_nPlayingTrack == STREAMED_SOUND_STRIPCLUB_AMBIENT) return; + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_STRIP_1; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_STRIP_1; + MusicManager.SetStripClubTrackPos(SCRIPT_SOUND_NEW_BUILDING_STRIP_1); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_STRIP_2: + if(MusicManager.m_nPlayingTrack == STREAMED_SOUND_STRIPCLUB_AMBIENT) return; + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_STRIP_2; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_STRIP_2; + MusicManager.SetStripClubTrackPos(SCRIPT_SOUND_NEW_BUILDING_STRIP_2); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_STRIP_3: + if(MusicManager.m_nPlayingTrack == STREAMED_SOUND_STRIPCLUB_AMBIENT) return; + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_STRIP_3; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_STRIP_3; + MusicManager.SetStripClubTrackPos(SCRIPT_SOUND_NEW_BUILDING_STRIP_3); + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_BUILDING_CHURCH: + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_CHURCH; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_CHURCH; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + Vol = SCRIPT_OBJECT_DEFAULT_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 15; + m_sQueueSample.m_fSpeedMultiplier = 4.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_SNORING_LOOP: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SNORING_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_BUILDING_SNORE; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_SNORING; + Vol = SCRIPT_OBJECT_SNORING_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BUILDING_SNORE); + m_sQueueSample.m_nPriority = 6; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_BANK_ALARM_LOOP: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_BUILDINGS_BANK_ALARM; + m_sQueueSample.m_nBankIndex = SFX_BANK_BUILDING_BANK_ALARM; + Vol = SCRIPT_OBJECT_BANK_ALARM_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_BUILDINGS_BANK_ALARM); + m_sQueueSample.m_nPriority = 2; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_POLICE_CELL_DOOR_SLIDING_LOOP: + case SCRIPT_SOUND_GARAGE_DOOR_SLIDING_LOOP: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_GARAGE_DOOR_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_GARAGE_DOOR_SLIDING_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_GARAGE_DOOR_LOOP); + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_SHOOTING_RANGE_TARGET_MOVING_LOOP: + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_SHOOTING_RANGE_TARGET_MOVING_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_TANK_TURRET; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + Vol = SCRIPT_OBJECT_SHOOTING_RANGE_TARGET_MOVING_VOLUME; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_TANK_TURRET); + m_sQueueSample.m_nPriority = 4; + m_sQueueSample.m_nFramesToPlay = 3; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + break; + case SCRIPT_SOUND_NEW_WATERFALL: + Vol = SCRIPT_OBJECT_NEW_WATERFALL_VOLUME; + m_sQueueSample.m_MaxDistance = SCRIPT_OBJECT_LONG_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_BOAT_WATER_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 20812; + m_sQueueSample.m_nPriority = 4; + m_sQueueSample.m_nFramesToPlay = 9; + m_sQueueSample.m_fSpeedMultiplier = 2.0f; + m_sQueueSample.m_bIs2D = FALSE; + 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 = 0; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + SET_SOUND_REVERB(TRUE); + SET_EMITTING_VOLUME(Vol); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} +#pragma endregion All the code for script object audio on the map + +void +cAudioManager::ProcessWeather(int32 id) +{ + uint8 Vol; + float x; + float y; + float modifier; + float wind; + + 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 + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(FALSE); + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + x = 0.0f; + y = 0.0f; + CWindModifiers::FindWindModifier(TheCamera.GetPosition(), &x, &y); + modifier = Max(Abs(x), Abs(y)) * 10.0f; + modifier = Min(1.0f, modifier); + wind = Max(CWeather::Wind, modifier); + if (wind > 0.0f && CObject::fDistToNearestTree < 75.0f) { + m_sQueueSample.m_nSampleIndex = SFX_PALM_TREE_LO; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_PALM_TREE_LO); + m_sQueueSample.m_nVolume = (m_anRandomTable[1] % 10 + 45.0f) * (75.0f - CObject::fDistToNearestTree) / 75.0f * wind; + m_sQueueSample.m_nCounter = 5; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nPriority = 1; + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_bIs2D = TRUE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nFramesToPlay = 7; + SET_SOUND_REVERB(FALSE); + SET_EMITTING_VOLUME(m_sQueueSample.m_nVolume); + SET_LOOP_OFFSETS(m_sQueueSample.m_nSampleIndex) + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + CObject::fDistToNearestTree = 999999.9f; + } +} + +void +cAudioManager::ProcessFrontEnd() +{ + bool8 stereo; + bool8 processedPickup; + bool8 processedMission; + bool8 staticFreq; + bool8 center; + int16 sample; + + static uint8 iSound = 0; + static uint32 cPickupNextFrame = 0; + static uint32 cPartMisComNextFrame = 0; + static uint32 radioDial = SFX_RADIO_DIAL_1; + + for (uint32 i = 0; i < m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_AudioEvents; i++) { + staticFreq = FALSE; + processedPickup = FALSE; + center = FALSE; + processedMission = FALSE; + stereo = FALSE; + switch (m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]) { + 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_GARAGE_NO_MONEY: + case SOUND_GARAGE_BAD_VEHICLE: + case SOUND_GARAGE_BOMB_ALREADY_SET: + m_sQueueSample.m_nSampleIndex = SFX_WEAPON_LEFT; + stereo = TRUE; + staticFreq = TRUE; + center = TRUE; + break; + case SOUND_GARAGE_OPENING: + case SOUND_71: //case SOUND_41: + case SOUND_GARAGE_VEHICLE_DECLINED: + case SOUND_GARAGE_VEHICLE_ACCEPTED: + case SOUND_EVIDENCE_PICKUP: + case SOUND_UNLOAD_GOLD: + stereo = TRUE; + processedPickup = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_MONEY_LEFT; + break; + case SOUND_GARAGE_BOMB1_SET: + case SOUND_GARAGE_BOMB2_SET: + case SOUND_GARAGE_BOMB3_SET: + case SOUND_PICKUP_WEAPON_BOUGHT: + case SOUND_PICKUP_WEAPON: + center = TRUE; + processedPickup = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_WEAPON_LEFT; + stereo = TRUE; + break; + case SOUND_PICKUP_HEALTH: + case SOUND_81: //case SOUND_4B: + case SOUND_PICKUP_ADRENALINE: + case SOUND_PICKUP_ARMOUR: + stereo = TRUE; + processedPickup = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_MONEY_LEFT; + break; + case SOUND_80: + stereo = TRUE; + processedPickup = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_WEAPON_LEFT; + center = TRUE; + staticFreq = TRUE; + break; + case SOUND_PICKUP_BONUS: + case SOUND_FRONTEND_MENU_STARTING: + case SOUND_HUD: + stereo = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_INFO_LEFT; + center = TRUE; + break; + case SOUND_PICKUP_MONEY: + stereo = TRUE; + processedPickup = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_MONEY_LEFT; + break; + case SOUND_PICKUP_HIDDEN_PACKAGE: + case SOUND_PICKUP_PACMAN_PILL: + case SOUND_PICKUP_PACMAN_PACKAGE: + case SOUND_PICKUP_FLOAT_PACKAGE: + center = TRUE; + processedPickup = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_PART_MISSION_COMPLETE_LEFT; + stereo = TRUE; + break; + case SOUND_RACE_START_3: + case SOUND_RACE_START_2: + case SOUND_RACE_START_1: + case SOUND_PART_MISSION_COMPLETE: + stereo = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_PART_MISSION_COMPLETE_LEFT; + processedMission = TRUE; + center = TRUE; + break; + case SOUND_RACE_START_GO: + stereo = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_GO_LEFT; + center = TRUE; + break; + case SOUND_CLOCK_TICK: + m_sQueueSample.m_nSampleIndex = SFX_TIMER; + break; + case SOUND_FRONTEND_RADIO_TURN_OFF: + case SOUND_FRONTEND_RADIO_TURN_ON: + m_sQueueSample.m_nSampleIndex = SFX_RADIO_CLICK; + break; + case SOUND_FRONTEND_HURRICANE: + m_sQueueSample.m_nSampleIndex = SFX_HURRICANE_MA; + break; + case SOUND_BULLETTRACE_1: + case SOUND_BULLETTRACE_2: + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[0] % 2) + SFX_BULLET_PASS_1; + break; + case SOUND_AMMUNATION_IMRAN_ARM_BOMB: + m_sQueueSample.m_nSampleIndex = SFX_ARM_BOMB; + break; + case SOUND_RADIO_CHANGE: + m_sQueueSample.m_nSampleIndex = (m_anRandomTable[1] % 2) ? radioDial + 1 : radioDial + 2; + if (m_sQueueSample.m_nSampleIndex > SFX_RADIO_DIAL_12) + m_sQueueSample.m_nSampleIndex -= 12; + radioDial = m_sQueueSample.m_nSampleIndex; + break; + case SOUND_FRONTEND_HIGHLIGHT_OPTION: + stereo = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_FE_HIGHLIGHT_LEFT; + break; + case SOUND_FRONTEND_ENTER_OR_ADJUST: + stereo = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_FE_SELECT_LEFT; + break; + case SOUND_FRONTEND_BACK: + stereo = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_FE_BACK_LEFT; + break; + case SOUND_FRONTEND_FAIL: + stereo = TRUE; + m_sQueueSample.m_nSampleIndex = SFX_FE_ERROR_LEFT; + break; + case SOUND_FRONTEND_AUDIO_TEST: + m_sQueueSample.m_nSampleIndex = m_anRandomTable[0] % 3 + SFX_FE_NOISE_BURST_1; + 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 == SOUND_FRONTEND_RADIO_TURN_OFF) + m_sQueueSample.m_nFrequency = 28509; + else if (sample == SOUND_FRONTEND_RADIO_TURN_ON) + m_sQueueSample.m_nFrequency = 32000; + else if (sample == SOUND_BULLETTRACE_1 || sample == SOUND_BULLETTRACE_2) { + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + m_sQueueSample.m_nFrequency += RandomDisplacement(m_sQueueSample.m_nFrequency >> 5); + } else if (staticFreq) + m_sQueueSample.m_nFrequency = 5382; + else + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + + m_sQueueSample.m_nVolume = FRONTEND_VOLUME; + if (m_sQueueSample.m_nSampleIndex == SFX_HURRICANE_MA && CWeather::Wind > 1.0f) + m_sQueueSample.m_nVolume = (CWeather::Wind - 1.0f) * m_sQueueSample.m_nVolume; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nLoopCount = 1; + m_sQueueSample.m_bStatic = TRUE; + m_sQueueSample.m_nBankIndex = SFX_BANK_FRONT_END_MENU; + 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 = 0; + m_sQueueSample.m_fDistance = 1.0f; + } else { + sample = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_awAudioEvent[i]; + if (sample == SOUND_BULLETTRACE_1) { + m_sQueueSample.m_nPan = 20; + m_sQueueSample.m_nVolume = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + m_sQueueSample.m_nPriority = 10; + m_sQueueSample.m_fDistance = 100.0f; + } else if (sample == SOUND_BULLETTRACE_2) { + m_sQueueSample.m_nPan = 107; + m_sQueueSample.m_nVolume = m_asAudioEntities[m_sQueueSample.m_nEntityIndex].m_afVolume[i]; + m_sQueueSample.m_nPriority = 10; + m_sQueueSample.m_fDistance = 100.0f; + } else { + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_fDistance = 1.0f; + } + } + SET_SOUND_REVERB(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(); + } + if (center) { + m_sQueueSample.m_nSampleIndex++; + m_sQueueSample.m_nCounter = iSound++; + m_sQueueSample.m_nPan = 63; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(m_sQueueSample.m_nSampleIndex); + 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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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_TEARGAS: + Vol = PROJECTILE_TEARGAS_VOLUME; + m_sQueueSample.m_MaxDistance = PROJECTILE_TEARGAS_MAX_DIST; + m_sQueueSample.m_nSampleIndex = SFX_PALM_TREE_LO; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = 13879; + m_sQueueSample.m_nPriority = 7; + break; + case WEAPONTYPE_ROCKET: + 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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } + } +} + +void +cAudioManager::ProcessEscalators() +{ + float distance; + + for (int i = 0; i < CEscalators::NumEscalators; i++) { + if (!CEscalators::GetEscalator(i).IsActive()) + continue; + m_sQueueSample.m_vecPos = CEscalators::GetEscalator(i).GetPosition(); + distance = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distance < SQR(ESCALATOR_MAX_DIST)) { + m_sQueueSample.m_fDistance = Sqrt(distance); + m_sQueueSample.m_nVolume = ComputeVolume(ESCALATOR_VOLUME, ESCALATOR_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nSampleIndex = SFX_BOAT_V12_LOOP; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = i * 50 % 250 + 3973; + m_sQueueSample.m_nPriority = 3; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + m_sQueueSample.m_nFramesToPlay = 5; + m_sQueueSample.m_MaxDistance = ESCALATOR_MAX_DIST; + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + SET_EMITTING_VOLUME(ESCALATOR_VOLUME); + SET_LOOP_OFFSETS(SFX_BOAT_V12_LOOP) + SET_SOUND_REVERB(TRUE); + m_sQueueSample.m_bStatic = FALSE; + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } + } +} + +//positon of arcade machines +CVector aVecExtraSoundPosition[] = { CVector(-1042.546f, 88.794f, 11.324f), CVector(-1004.476f, 181.697f, 11.324f) }; + +void +cAudioManager::ProcessExtraSounds() +{ + float distance; + + for (int i = 0; i < ARRAY_SIZE(aVecExtraSoundPosition); i++) { + m_sQueueSample.m_vecPos = aVecExtraSoundPosition[i]; + distance = GetDistanceSquared(m_sQueueSample.m_vecPos); + if (distance < SQR(ARCADE_MAX_DIST)) { + m_sQueueSample.m_fDistance = Sqrt(distance); + m_sQueueSample.m_nVolume = ComputeVolume(ARCADE_VOLUME, ARCADE_MAX_DIST, m_sQueueSample.m_fDistance); + if (m_sQueueSample.m_nVolume > 0) { + m_sQueueSample.m_nCounter = i; + m_sQueueSample.m_nSampleIndex = SFX_ARCADE; + m_sQueueSample.m_nBankIndex = SFX_BANK_0; + m_sQueueSample.m_nFrequency = SampleManager.GetSampleBaseFrequency(SFX_ARCADE); + m_sQueueSample.m_bIs2D = FALSE; + m_sQueueSample.m_nLoopCount = 0; + m_sQueueSample.m_bStatic = FALSE; + m_sQueueSample.m_nPriority = 4; + m_sQueueSample.m_fSpeedMultiplier = 3.0f; + SET_EMITTING_VOLUME(ARCADE_VOLUME); + SET_LOOP_OFFSETS(SFX_ARCADE) + SET_SOUND_REVERB(TRUE); + m_sQueueSample.m_MaxDistance = ARCADE_MAX_DIST; + SET_SOUND_REFLECTION(FALSE); + m_sQueueSample.m_nFramesToPlay = 3; + 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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(TRUE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} +#ifdef GTA_BRIDGE +#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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(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; + SET_SOUND_REVERB(FALSE); + SET_SOUND_REFLECTION(FALSE); + AddSampleToRequestedQueue(); + } + } +} +#pragma endregion +#endif + +#pragma region MISSION_AUDIO +bool8 g_bMissionAudioLoadFailed[MISSION_AUDIO_SLOTS]; + +struct MissionAudioData { + const char *m_pName; + uint32 m_nId; +}; + +Const MissionAudioData MissionAudioNameSfxAssoc[] = { + {"mobring", STREAMED_SOUND_MISSION_MOBR1}, {"pagring", STREAMED_SOUND_MISSION_PAGER}, {"carrev", STREAMED_SOUND_MISSION_CARREV}, + {"bikerev", STREAMED_SOUND_MISSION_BIKEREV}, {"liftop", STREAMED_SOUND_MISSION_LIFTOP}, {"liftcl", STREAMED_SOUND_MISSION_LIFTCL}, + {"liftrun", STREAMED_SOUND_MISSION_LIFTRUN}, {"liftbel", STREAMED_SOUND_MISSION_LIFTBEL}, {"inlift", STREAMED_SOUND_MISSION_INLIFT}, + {"caml", STREAMED_SOUND_MISSION_CAMERAL}, {"camr", STREAMED_SOUND_MISSION_CAMERAR}, {"cheer1", STREAMED_SOUND_MISSION_CHEER1}, + {"cheer2", STREAMED_SOUND_MISSION_CHEER2}, {"cheer3", STREAMED_SOUND_MISSION_CHEER3}, {"cheer4", STREAMED_SOUND_MISSION_CHEER4}, + {"ooh1", STREAMED_SOUND_MISSION_OOH1}, {"ooh2", STREAMED_SOUND_MISSION_OOH2}, {"race1", STREAMED_SOUND_MISSION_RACE1}, + {"race2", STREAMED_SOUND_MISSION_RACE2}, {"race3", STREAMED_SOUND_MISSION_RACE3}, {"race4", STREAMED_SOUND_MISSION_RACE4}, + {"race5", STREAMED_SOUND_MISSION_RACE5}, {"race6", STREAMED_SOUND_MISSION_RACE6}, {"race7", STREAMED_SOUND_MISSION_RACE7}, + {"race8", STREAMED_SOUND_MISSION_RACE8}, {"race9", STREAMED_SOUND_MISSION_RACE9}, {"race10", STREAMED_SOUND_MISSION_RACE10}, + {"race11", STREAMED_SOUND_MISSION_RACE11}, {"race12", STREAMED_SOUND_MISSION_RACE12}, {"race13", STREAMED_SOUND_MISSION_RACE13}, + {"race14", STREAMED_SOUND_MISSION_RACE14}, {"race15", STREAMED_SOUND_MISSION_RACE15}, {"hot1", STREAMED_SOUND_MISSION_HOT1}, + {"hot2", STREAMED_SOUND_MISSION_HOT2}, {"hot3", STREAMED_SOUND_MISSION_HOT3}, {"hot4", STREAMED_SOUND_MISSION_HOT4}, + {"hot5", STREAMED_SOUND_MISSION_HOT5}, {"hot6", STREAMED_SOUND_MISSION_HOT6}, {"hot7", STREAMED_SOUND_MISSION_HOT7}, + {"hot8", STREAMED_SOUND_MISSION_HOT8}, {"hot9", STREAMED_SOUND_MISSION_HOT9}, {"hot10", STREAMED_SOUND_MISSION_HOT10}, + {"hot11", STREAMED_SOUND_MISSION_HOT11}, {"hot12", STREAMED_SOUND_MISSION_HOT12}, {"hot13", STREAMED_SOUND_MISSION_HOT13}, + {"hot14", STREAMED_SOUND_MISSION_HOT14}, {"hot15", STREAMED_SOUND_MISSION_HOT15}, {"lanstp1", STREAMED_SOUND_MISSION_LANSTP1}, + {"lanstp2", STREAMED_SOUND_MISSION_LANSTP2}, {"lanamu1", STREAMED_SOUND_MISSION_LANAMU1}, {"lanamu2", STREAMED_SOUND_MISSION_LANAMU2}, + {"airhrnl", STREAMED_SOUND_MISSION_AIRHORNL}, {"airhrnr", STREAMED_SOUND_MISSION_AIRHORNR}, {"sniper", STREAMED_SOUND_MISSION_SNIPSCRL}, + {"snipsh", STREAMED_SOUND_MISSION_SNIPSHORT}, {"bloroof", STREAMED_SOUND_MISSION_BLOWROOF}, {"sfx_01", STREAMED_SOUND_MISSION_SFX_01}, + {"sfx_02", STREAMED_SOUND_MISSION_SFX_02}, {"LAW1_1", STREAMED_SOUND_MISSION_LAW1_1}, {"LAW1_2", STREAMED_SOUND_MISSION_LAW1_2}, + {"LAW1_3", STREAMED_SOUND_MISSION_LAW1_3}, {"LAW1_4", STREAMED_SOUND_MISSION_LAW1_4}, {"LAW1_5", STREAMED_SOUND_MISSION_LAW1_5}, + {"LAW1_6", STREAMED_SOUND_MISSION_LAW1_6}, {"LAW1_7", STREAMED_SOUND_MISSION_LAW1_7}, {"LAW1_8", STREAMED_SOUND_MISSION_LAW1_8}, + {"LAW1_9", STREAMED_SOUND_MISSION_LAW1_9}, {"LAW1_10", STREAMED_SOUND_MISSION_LAW1_10}, {"LAW2_1", STREAMED_SOUND_MISSION_LAW2_1}, + {"LAW2_2", STREAMED_SOUND_MISSION_LAW2_2}, {"LAW2_3", STREAMED_SOUND_MISSION_LAW2_3}, {"LAW2_4", STREAMED_SOUND_MISSION_LAW2_4}, + {"LAW2_5", STREAMED_SOUND_MISSION_LAW2_5}, {"LAW2_6", STREAMED_SOUND_MISSION_LAW2_6}, {"LAW2_7", STREAMED_SOUND_MISSION_LAW2_7}, + {"LAW2_8", STREAMED_SOUND_MISSION_LAW2_8}, {"LAW2_9", STREAMED_SOUND_MISSION_LAW2_9}, {"LAW2_10", STREAMED_SOUND_MISSION_LAW2_10}, + {"LAW3_1", STREAMED_SOUND_MISSION_LAW3_1}, {"LAW3_2", STREAMED_SOUND_MISSION_LAW3_2}, {"LAW3_3", STREAMED_SOUND_MISSION_LAW3_3}, + {"LAW3_4", STREAMED_SOUND_MISSION_LAW3_4}, {"LAW3_5", STREAMED_SOUND_MISSION_LAW3_5}, {"LAW3_6", STREAMED_SOUND_MISSION_LAW3_6}, + {"LAW3_10", STREAMED_SOUND_MISSION_LAW3_10}, {"LAW3_11", STREAMED_SOUND_MISSION_LAW3_11}, {"LAW3_12", STREAMED_SOUND_MISSION_LAW3_12}, + {"LAW3_13", STREAMED_SOUND_MISSION_LAW3_13}, {"LAW3_14", STREAMED_SOUND_MISSION_LAW3_14}, {"LAW3_16", STREAMED_SOUND_MISSION_LAW3_16}, + {"LAW3_17", STREAMED_SOUND_MISSION_LAW3_17}, {"LAW3_18", STREAMED_SOUND_MISSION_LAW3_18}, {"LAW3_19", STREAMED_SOUND_MISSION_LAW3_19}, + {"LAW3_20", STREAMED_SOUND_MISSION_LAW3_20}, {"LAW3_21", STREAMED_SOUND_MISSION_LAW3_21}, {"LAW3_22", STREAMED_SOUND_MISSION_LAW3_22}, + {"LAW3_23", STREAMED_SOUND_MISSION_LAW3_23}, {"LAW3_24", STREAMED_SOUND_MISSION_LAW3_24}, {"LAW3_25", STREAMED_SOUND_MISSION_LAW3_25}, + {"LAW4_1a", STREAMED_SOUND_MISSION_LAW4_1A}, {"LAW4_1b", STREAMED_SOUND_MISSION_LAW4_1B}, {"LAW4_1c", STREAMED_SOUND_MISSION_LAW4_1C}, + {"LAW4_1d", STREAMED_SOUND_MISSION_LAW4_1D}, {"LAW4_10", STREAMED_SOUND_MISSION_LAW4_10}, {"LAW4_3", STREAMED_SOUND_MISSION_LAW4_3}, + {"LAW4_4", STREAMED_SOUND_MISSION_LAW4_4}, {"LAW4_5", STREAMED_SOUND_MISSION_LAW4_5}, {"LAW4_6", STREAMED_SOUND_MISSION_LAW4_6}, + {"LAW4_7", STREAMED_SOUND_MISSION_LAW4_7}, {"LAW4_8", STREAMED_SOUND_MISSION_LAW4_8}, {"LAW4_9", STREAMED_SOUND_MISSION_LAW4_9}, + {"COL1_1", STREAMED_SOUND_MISSION_COL1_1}, {"COL1_2", STREAMED_SOUND_MISSION_COL1_2}, {"COL1_3", STREAMED_SOUND_MISSION_COL1_3}, + {"COL1_4", STREAMED_SOUND_MISSION_COL1_4}, {"COL1_5", STREAMED_SOUND_MISSION_COL1_5}, {"COL1_6", STREAMED_SOUND_MISSION_COL1_6}, + {"COL1_7", STREAMED_SOUND_MISSION_COL1_7}, {"COL1_8", STREAMED_SOUND_MISSION_COL1_8}, {"COL2_1", STREAMED_SOUND_MISSION_COL2_1}, + {"COL2_2", STREAMED_SOUND_MISSION_COL2_2}, {"COL2_3", STREAMED_SOUND_MISSION_COL2_3}, {"COL2_4", STREAMED_SOUND_MISSION_COL2_4}, + {"COL2_5", STREAMED_SOUND_MISSION_COL2_5}, {"COL2_6a", STREAMED_SOUND_MISSION_COL2_6A}, {"COL2_7", STREAMED_SOUND_MISSION_COL2_7}, + {"COL2_8", STREAMED_SOUND_MISSION_COL2_8}, {"COL2_9", STREAMED_SOUND_MISSION_COL2_9}, {"COL2_10", STREAMED_SOUND_MISSION_COL2_10}, + {"COL2_11", STREAMED_SOUND_MISSION_COL2_11}, {"COL2_12", STREAMED_SOUND_MISSION_COL2_12}, {"COL2_13", STREAMED_SOUND_MISSION_COL2_13}, + {"COL2_14", STREAMED_SOUND_MISSION_COL2_14}, {"COL2_15", STREAMED_SOUND_MISSION_COL2_15}, {"COL2_16", STREAMED_SOUND_MISSION_COL2_16}, + {"COL3_1", STREAMED_SOUND_MISSION_COL3_1}, {"COL3_2", STREAMED_SOUND_MISSION_COL3_2}, {"COL3_2a", STREAMED_SOUND_MISSION_COL3_2A}, + {"COL3_2b", STREAMED_SOUND_MISSION_COL3_2B}, {"COL3_3", STREAMED_SOUND_MISSION_COL3_3}, {"COL3_4", STREAMED_SOUND_MISSION_COL3_4}, + {"COL3_5", STREAMED_SOUND_MISSION_COL3_5}, {"COL3_6", STREAMED_SOUND_MISSION_COL3_6}, {"COL3_7", STREAMED_SOUND_MISSION_COL3_7}, + {"COL3_8", STREAMED_SOUND_MISSION_COL3_8}, {"COL3_9", STREAMED_SOUND_MISSION_COL3_9}, {"COL3_10", STREAMED_SOUND_MISSION_COL3_10}, + {"COL3_11", STREAMED_SOUND_MISSION_COL3_11}, {"COL3_12", STREAMED_SOUND_MISSION_COL3_12}, {"COL3_13", STREAMED_SOUND_MISSION_COL3_13}, + {"COL3_14", STREAMED_SOUND_MISSION_COL3_14}, {"COL3_15", STREAMED_SOUND_MISSION_COL3_15}, {"COL3_16", STREAMED_SOUND_MISSION_COL3_16}, + {"COL3_17", STREAMED_SOUND_MISSION_COL3_17}, {"COL3_18", STREAMED_SOUND_MISSION_COL3_18}, {"COL3_19", STREAMED_SOUND_MISSION_COL3_19}, + {"COL3_20", STREAMED_SOUND_MISSION_COL3_20}, {"COL3_21", STREAMED_SOUND_MISSION_COL3_21}, {"COL3_23", STREAMED_SOUND_MISSION_COL3_23}, + {"COL3_24", STREAMED_SOUND_MISSION_COL3_24}, {"COL3_25", STREAMED_SOUND_MISSION_COL3_25}, {"COL4_1", STREAMED_SOUND_MISSION_COL4_1}, + {"COL4_2", STREAMED_SOUND_MISSION_COL4_2}, {"COL4_3", STREAMED_SOUND_MISSION_COL4_3}, {"COL4_4", STREAMED_SOUND_MISSION_COL4_4}, + {"COL4_5", STREAMED_SOUND_MISSION_COL4_5}, {"COL4_6", STREAMED_SOUND_MISSION_COL4_6}, {"COL4_7", STREAMED_SOUND_MISSION_COL4_7}, + {"COL4_8", STREAMED_SOUND_MISSION_COL4_8}, {"COL4_9", STREAMED_SOUND_MISSION_COL4_9}, {"COL4_10", STREAMED_SOUND_MISSION_COL4_10}, + {"COL4_11", STREAMED_SOUND_MISSION_COL4_11}, {"COL4_12", STREAMED_SOUND_MISSION_COL4_12}, {"COL4_13", STREAMED_SOUND_MISSION_COL4_13}, + {"COL4_14", STREAMED_SOUND_MISSION_COL4_14}, {"COL4_15", STREAMED_SOUND_MISSION_COL4_15}, {"COL4_16", STREAMED_SOUND_MISSION_COL4_16}, + {"COL4_17", STREAMED_SOUND_MISSION_COL4_17}, {"COL4_18", STREAMED_SOUND_MISSION_COL4_18}, {"COL4_19", STREAMED_SOUND_MISSION_COL4_19}, + {"COL4_20", STREAMED_SOUND_MISSION_COL4_20}, {"COL4_21", STREAMED_SOUND_MISSION_COL4_21}, {"COL4_22", STREAMED_SOUND_MISSION_COL4_22}, + {"COL4_23", STREAMED_SOUND_MISSION_COL4_23}, {"COL4_24", STREAMED_SOUND_MISSION_COL4_24}, {"COL4_25", STREAMED_SOUND_MISSION_COL4_25}, + {"COL4_26", STREAMED_SOUND_MISSION_COL4_26}, {"COL5_1", STREAMED_SOUND_MISSION_COL5_1}, {"COL5_2", STREAMED_SOUND_MISSION_COL5_2}, + {"COL5_3", STREAMED_SOUND_MISSION_COL5_3}, {"COL5_4", STREAMED_SOUND_MISSION_COL5_4}, {"COL5_5", STREAMED_SOUND_MISSION_COL5_5}, + {"COL5_6", STREAMED_SOUND_MISSION_COL5_6}, {"COL5_7", STREAMED_SOUND_MISSION_COL5_7}, {"COL5_8", STREAMED_SOUND_MISSION_COL5_8}, + {"COL5_9", STREAMED_SOUND_MISSION_COL5_9}, {"COL5_10", STREAMED_SOUND_MISSION_COL5_10}, {"COL5_11", STREAMED_SOUND_MISSION_COL5_11}, + {"COL5_12", STREAMED_SOUND_MISSION_COL5_12}, {"COL5_13", STREAMED_SOUND_MISSION_COL5_13}, {"COL5_14", STREAMED_SOUND_MISSION_COL5_14}, + {"COL5_15", STREAMED_SOUND_MISSION_COL5_15}, {"COL5_16", STREAMED_SOUND_MISSION_COL5_16}, {"COL5_17", STREAMED_SOUND_MISSION_COL5_17}, + {"COL5_18", STREAMED_SOUND_MISSION_COL5_18}, {"COL5_19", STREAMED_SOUND_MISSION_COL5_19}, {"COL5_20", STREAMED_SOUND_MISSION_COL5_20}, + {"COL5_21", STREAMED_SOUND_MISSION_COL5_21}, {"COL5_22", STREAMED_SOUND_MISSION_COL5_22}, {"COK1_1", STREAMED_SOUND_MISSION_COK1_1}, + {"COK1_2", STREAMED_SOUND_MISSION_COK1_2}, {"COK1_3", STREAMED_SOUND_MISSION_COK1_3}, {"COK1_4", STREAMED_SOUND_MISSION_COK1_4}, + {"COK1_5", STREAMED_SOUND_MISSION_COK1_5}, {"COK1_6", STREAMED_SOUND_MISSION_COK1_6}, {"COK2_1", STREAMED_SOUND_MISSION_COK2_1}, + {"COK2_2", STREAMED_SOUND_MISSION_COK2_2}, {"COK2_3", STREAMED_SOUND_MISSION_COK2_3}, {"COK2_4", STREAMED_SOUND_MISSION_COK2_4}, + {"COK2_5", STREAMED_SOUND_MISSION_COK2_5}, {"COK2_6", STREAMED_SOUND_MISSION_COK2_6}, {"COK2_7a", STREAMED_SOUND_MISSION_COK2_7A}, + {"COK2_7b", STREAMED_SOUND_MISSION_COK2_7B}, {"COK2_7c", STREAMED_SOUND_MISSION_COK2_7C}, {"COK2_8a", STREAMED_SOUND_MISSION_COK2_8A}, + {"COK2_8b", STREAMED_SOUND_MISSION_COK2_8B}, {"COK2_8c", STREAMED_SOUND_MISSION_COK2_8C}, {"COK2_8d", STREAMED_SOUND_MISSION_COK2_8D}, + {"COK2_9", STREAMED_SOUND_MISSION_COK2_9}, {"COK210a", STREAMED_SOUND_MISSION_COK210A}, {"COK210b", STREAMED_SOUND_MISSION_COK210B}, + {"COK210c", STREAMED_SOUND_MISSION_COK210C}, {"COK212a", STREAMED_SOUND_MISSION_COK212A}, {"COK212b", STREAMED_SOUND_MISSION_COK212B}, + {"COK2_13", STREAMED_SOUND_MISSION_COK2_13}, {"COK2_14", STREAMED_SOUND_MISSION_COK2_14}, {"COK2_15", STREAMED_SOUND_MISSION_COK2_15}, + {"COK2_16", STREAMED_SOUND_MISSION_COK2_16}, {"COK2_20", STREAMED_SOUND_MISSION_COK2_20}, {"COK2_21", STREAMED_SOUND_MISSION_COK2_21}, + {"COK2_22", STREAMED_SOUND_MISSION_COK2_22}, {"COK3_1", STREAMED_SOUND_MISSION_COK3_1}, {"COK3_2", STREAMED_SOUND_MISSION_COK3_2}, + {"COK3_3", STREAMED_SOUND_MISSION_COK3_3}, {"COK3_4", STREAMED_SOUND_MISSION_COK3_4}, {"COK4_1", STREAMED_SOUND_MISSION_COK4_1}, + {"COK4_2", STREAMED_SOUND_MISSION_COK4_2}, {"COK4_3", STREAMED_SOUND_MISSION_COK4_3}, {"COK4_4", STREAMED_SOUND_MISSION_COK4_4}, + {"COK4_5", STREAMED_SOUND_MISSION_COK4_5}, {"COK4_6", STREAMED_SOUND_MISSION_COK4_6}, {"COK4_7", STREAMED_SOUND_MISSION_COK4_7}, + {"COK4_8", STREAMED_SOUND_MISSION_COK4_8}, {"COK4_9", STREAMED_SOUND_MISSION_COK4_9}, {"COK4_9A", STREAMED_SOUND_MISSION_COK4_9A}, + {"COK4_10", STREAMED_SOUND_MISSION_COK4_10}, {"COK4_11", STREAMED_SOUND_MISSION_COK4_11}, {"COK4_12", STREAMED_SOUND_MISSION_COK4_12}, + {"COK4_13", STREAMED_SOUND_MISSION_COK4_13}, {"COK4_14", STREAMED_SOUND_MISSION_COK4_14}, {"COK4_15", STREAMED_SOUND_MISSION_COK4_15}, + {"COK4_16", STREAMED_SOUND_MISSION_COK4_16}, {"COK4_17", STREAMED_SOUND_MISSION_COK4_17}, {"COK4_18", STREAMED_SOUND_MISSION_COK4_18}, + {"COK4_19", STREAMED_SOUND_MISSION_COK4_19}, {"COK4_20", STREAMED_SOUND_MISSION_COK4_20}, {"COK4_21", STREAMED_SOUND_MISSION_COK4_21}, + {"COK4_22", STREAMED_SOUND_MISSION_COK4_22}, {"COK4_23", STREAMED_SOUND_MISSION_COK4_23}, {"COK4_24", STREAMED_SOUND_MISSION_COK4_24}, + {"COK4_25", STREAMED_SOUND_MISSION_COK4_25}, {"COK4_26", STREAMED_SOUND_MISSION_COK4_26}, {"COK4_27", STREAMED_SOUND_MISSION_COK4_27}, + {"RESC_1", STREAMED_SOUND_MISSION_RESC_1}, {"RESC_2", STREAMED_SOUND_MISSION_RESC_2}, {"RESC_3", STREAMED_SOUND_MISSION_RESC_3}, + {"RESC_4", STREAMED_SOUND_MISSION_RESC_4}, {"RESC_5", STREAMED_SOUND_MISSION_RESC_5}, {"RESC_6", STREAMED_SOUND_MISSION_RESC_6}, + {"RESC_7", STREAMED_SOUND_MISSION_RESC_7}, {"RESC_8", STREAMED_SOUND_MISSION_RESC_8}, {"RESC_9", STREAMED_SOUND_MISSION_RESC_9}, + {"RESC_10", STREAMED_SOUND_MISSION_RESC_10}, {"ASS_1", STREAMED_SOUND_MISSION_ASS_1}, {"ASS_2", STREAMED_SOUND_MISSION_ASS_2}, + {"ASS_3", STREAMED_SOUND_MISSION_ASS_3}, {"ASS_4", STREAMED_SOUND_MISSION_ASS_4}, {"ASS_5", STREAMED_SOUND_MISSION_ASS_5}, + {"ASS_6", STREAMED_SOUND_MISSION_ASS_6}, {"ASS_7", STREAMED_SOUND_MISSION_ASS_7}, {"ASS_8", STREAMED_SOUND_MISSION_ASS_8}, + {"ASS_9", STREAMED_SOUND_MISSION_ASS_9}, {"ASS_10", STREAMED_SOUND_MISSION_ASS_10}, {"ASS_11", STREAMED_SOUND_MISSION_ASS_11}, + {"ASS_12", STREAMED_SOUND_MISSION_ASS_12}, {"ASS_13", STREAMED_SOUND_MISSION_ASS_13}, {"ASS_14", STREAMED_SOUND_MISSION_ASS_14}, + {"BUD1_1", STREAMED_SOUND_MISSION_BUD1_1}, {"BUD1_2", STREAMED_SOUND_MISSION_BUD1_2}, {"BUD1_3", STREAMED_SOUND_MISSION_BUD1_3}, + {"BUD1_4", STREAMED_SOUND_MISSION_BUD1_4}, {"BUD1_5", STREAMED_SOUND_MISSION_BUD1_5}, {"BUD1_9", STREAMED_SOUND_MISSION_BUD1_9}, + {"BUD1_10", STREAMED_SOUND_MISSION_BUD1_10}, {"BUD2_1", STREAMED_SOUND_MISSION_BUD2_1}, {"BUD2_2", STREAMED_SOUND_MISSION_BUD2_2}, + {"BUD2_3", STREAMED_SOUND_MISSION_BUD2_3}, {"BUD2_4", STREAMED_SOUND_MISSION_BUD2_4}, {"BUD2_5", STREAMED_SOUND_MISSION_BUD2_5}, + {"BUD2_6", STREAMED_SOUND_MISSION_BUD2_6}, {"BUD2_7", STREAMED_SOUND_MISSION_BUD2_7}, {"BUD3_1a", STREAMED_SOUND_MISSION_BUD3_1A}, + {"BUD3_1b", STREAMED_SOUND_MISSION_BUD3_1B}, {"BUD3_1", STREAMED_SOUND_MISSION_BUD3_1}, {"BUD3_2", STREAMED_SOUND_MISSION_BUD3_2}, + {"BUD3_3", STREAMED_SOUND_MISSION_BUD3_3}, {"BUD3_4", STREAMED_SOUND_MISSION_BUD3_4}, {"BUD3_1c", STREAMED_SOUND_MISSION_BUD3_1C}, + {"BUD3_5", STREAMED_SOUND_MISSION_BUD3_5}, {"BUD3_6", STREAMED_SOUND_MISSION_BUD3_6}, {"BUD3_7", STREAMED_SOUND_MISSION_BUD3_7}, + {"BUD3_8a", STREAMED_SOUND_MISSION_BUD3_8A}, {"BUD3_8b", STREAMED_SOUND_MISSION_BUD3_8B}, {"BUD3_8c", STREAMED_SOUND_MISSION_BUD3_8C}, + {"BUD3_9a", STREAMED_SOUND_MISSION_BUD3_9A}, {"BUD3_9b", STREAMED_SOUND_MISSION_BUD3_9B}, {"BUD3_9c", STREAMED_SOUND_MISSION_BUD3_9C}, + {"CAP1_2", STREAMED_SOUND_MISSION_CAP1_2}, {"CAP1_3", STREAMED_SOUND_MISSION_CAP1_3}, {"CAP1_4", STREAMED_SOUND_MISSION_CAP1_4}, + {"CAP1_5", STREAMED_SOUND_MISSION_CAP1_5}, {"CAP1_6", STREAMED_SOUND_MISSION_CAP1_6}, {"CAP1_7", STREAMED_SOUND_MISSION_CAP1_7}, + {"CAP1_8", STREAMED_SOUND_MISSION_CAP1_8}, {"CAP1_9", STREAMED_SOUND_MISSION_CAP1_9}, {"CAP1_10", STREAMED_SOUND_MISSION_CAP1_10}, + {"CAP1_11", STREAMED_SOUND_MISSION_CAP1_11}, {"CAP1_12", STREAMED_SOUND_MISSION_CAP1_12}, {"FINKILL", STREAMED_SOUND_MISSION_FINKILL}, + {"FIN_1a", STREAMED_SOUND_MISSION_FIN_1A}, {"FIN_1b", STREAMED_SOUND_MISSION_FIN_1B}, {"FIN_1c", STREAMED_SOUND_MISSION_FIN_1C}, + {"FIN_2b", STREAMED_SOUND_MISSION_FIN_2B}, {"FIN_2c", STREAMED_SOUND_MISSION_FIN_2C}, {"FIN_3", STREAMED_SOUND_MISSION_FIN_3}, + {"FIN_4", STREAMED_SOUND_MISSION_FIN_4}, {"FIN_5", STREAMED_SOUND_MISSION_FIN_5}, {"FIN_6", STREAMED_SOUND_MISSION_FIN_6}, + {"FIN_10", STREAMED_SOUND_MISSION_FIN_10}, {"FIN_11a", STREAMED_SOUND_MISSION_FIN_11A}, {"FIN_11b", STREAMED_SOUND_MISSION_FIN_11B}, + {"FIN_12a", STREAMED_SOUND_MISSION_FIN_12A}, {"FIN_12b", STREAMED_SOUND_MISSION_FIN_12B}, {"FIN_12c", STREAMED_SOUND_MISSION_FIN_12C}, + {"FIN_13", STREAMED_SOUND_MISSION_FIN_13}, {"BNK1_1", STREAMED_SOUND_MISSION_BNK1_1}, {"BNK1_2", STREAMED_SOUND_MISSION_BNK1_2}, + {"BNK1_3", STREAMED_SOUND_MISSION_BNK1_3}, {"BNK1_4", STREAMED_SOUND_MISSION_BNK1_4}, {"BNK1_5", STREAMED_SOUND_MISSION_BNK1_5}, + {"BNK1_6", STREAMED_SOUND_MISSION_BNK1_6}, {"BNK1_7", STREAMED_SOUND_MISSION_BNK1_7}, {"BNK1_8", STREAMED_SOUND_MISSION_BNK1_8}, + {"BNK1_10", STREAMED_SOUND_MISSION_BNK1_10}, {"BNK1_11", STREAMED_SOUND_MISSION_BNK1_11}, {"BNK1_12", STREAMED_SOUND_MISSION_BNK1_12}, + {"BNK1_13", STREAMED_SOUND_MISSION_BNK1_13}, {"BNK1_14", STREAMED_SOUND_MISSION_BNK1_14}, {"BNK2_1", STREAMED_SOUND_MISSION_BNK2_1}, + {"BNK2_2", STREAMED_SOUND_MISSION_BNK2_2}, {"BNK2_3", STREAMED_SOUND_MISSION_BNK2_3}, {"BNK2_4", STREAMED_SOUND_MISSION_BNK2_4}, + {"BNK2_5", STREAMED_SOUND_MISSION_BNK2_5}, {"BNK2_6", STREAMED_SOUND_MISSION_BNK2_6}, {"BNK2_7", STREAMED_SOUND_MISSION_BNK2_7}, + {"BNK2_8", STREAMED_SOUND_MISSION_BNK2_8}, {"BNK2_9", STREAMED_SOUND_MISSION_BNK2_9}, {"BNK3_1", STREAMED_SOUND_MISSION_BNK3_1}, + {"BNK3_2", STREAMED_SOUND_MISSION_BNK3_2}, {"BNK3_3a", STREAMED_SOUND_MISSION_BNK3_3A}, {"BNK3_3b", STREAMED_SOUND_MISSION_BNK3_3B}, + {"BNK3_3c", STREAMED_SOUND_MISSION_BNK3_3C}, {"BNK3_4a", STREAMED_SOUND_MISSION_BNK3_4A}, {"BNK3_4b", STREAMED_SOUND_MISSION_BNK3_4B}, + {"BNK3_4c", STREAMED_SOUND_MISSION_BNK3_4C}, {"BNK4_1", STREAMED_SOUND_MISSION_BNK4_1}, {"BNK4_2", STREAMED_SOUND_MISSION_BNK4_2}, + {"BNK4_3A", STREAMED_SOUND_MISSION_BNK4_3A}, {"BNK4_3B", STREAMED_SOUND_MISSION_BNK4_3B}, {"BNK4_3C", STREAMED_SOUND_MISSION_BNK4_3C}, + {"BNK4_3D", STREAMED_SOUND_MISSION_BNK4_3D}, {"BNK4_3E", STREAMED_SOUND_MISSION_BNK4_3E}, {"BNK4_3F", STREAMED_SOUND_MISSION_BNK4_3F}, + {"BNK4_3G", STREAMED_SOUND_MISSION_BNK4_3G}, {"BNK4_3H", STREAMED_SOUND_MISSION_BNK4_3H}, {"BNK4_3I", STREAMED_SOUND_MISSION_BNK4_3I}, + {"BNK4_3J", STREAMED_SOUND_MISSION_BNK4_3J}, {"BNK4_3K", STREAMED_SOUND_MISSION_BNK4_3K}, {"BNK4_3M", STREAMED_SOUND_MISSION_BNK4_3M}, + {"BNK4_3O", STREAMED_SOUND_MISSION_BNK4_3O}, {"BNK4_3P", STREAMED_SOUND_MISSION_BNK4_3P}, {"BNK4_3Q", STREAMED_SOUND_MISSION_BNK4_3Q}, + {"BNK4_3R", STREAMED_SOUND_MISSION_BNK4_3R}, {"BNK4_3S", STREAMED_SOUND_MISSION_BNK4_3S}, {"BNK4_3T", STREAMED_SOUND_MISSION_BNK4_3T}, + {"BNK4_3U", STREAMED_SOUND_MISSION_BNK4_3U}, {"BNK4_3V", STREAMED_SOUND_MISSION_BNK4_3V}, {"BNK4_4a", STREAMED_SOUND_MISSION_BNK4_4A}, + {"BNK4_4b", STREAMED_SOUND_MISSION_BNK4_4B}, {"BNK4_5", STREAMED_SOUND_MISSION_BNK4_5}, {"BNK4_6", STREAMED_SOUND_MISSION_BNK4_6}, + {"BNK4_7", STREAMED_SOUND_MISSION_BNK4_7}, {"BNK4_8", STREAMED_SOUND_MISSION_BNK4_8}, {"BNK4_9", STREAMED_SOUND_MISSION_BNK4_9}, + {"BNK4_10", STREAMED_SOUND_MISSION_BNK4_10}, {"BNK4_11", STREAMED_SOUND_MISSION_BNK4_11}, {"BK4_12a", STREAMED_SOUND_MISSION_BK4_12A}, + {"BK4_12b", STREAMED_SOUND_MISSION_BK4_12B}, {"BK4_12c", STREAMED_SOUND_MISSION_BK4_12C}, {"BNK4_13", STREAMED_SOUND_MISSION_BNK4_13}, + {"BK4_14a", STREAMED_SOUND_MISSION_BK4_14A}, {"BK4_14b", STREAMED_SOUND_MISSION_BK4_14B}, {"BNK4_15", STREAMED_SOUND_MISSION_BNK4_15}, + {"BNK4_16", STREAMED_SOUND_MISSION_BNK4_16}, {"BNK4_17", STREAMED_SOUND_MISSION_BNK4_17}, {"BNK4_18", STREAMED_SOUND_MISSION_BNK4_18}, + {"BK4_19a", STREAMED_SOUND_MISSION_BK4_19A}, {"BK4_19b", STREAMED_SOUND_MISSION_BK4_19B}, {"BK4_20a", STREAMED_SOUND_MISSION_BK4_20A}, + {"BK4_20b", STREAMED_SOUND_MISSION_BK4_20B}, {"BNK4_21", STREAMED_SOUND_MISSION_BNK4_21}, {"BNK422a", STREAMED_SOUND_MISSION_BNK422A}, + {"BNK422b", STREAMED_SOUND_MISSION_BNK422B}, {"BK4_23a", STREAMED_SOUND_MISSION_BK4_23A}, {"BK4_23b", STREAMED_SOUND_MISSION_BK4_23B}, + {"BK4_23c", STREAMED_SOUND_MISSION_BK4_23C}, {"BK4_23d", STREAMED_SOUND_MISSION_BK4_23D}, {"BK4_24a", STREAMED_SOUND_MISSION_BK4_24A}, + {"BK4_24b", STREAMED_SOUND_MISSION_BK4_24B}, {"BNK4_25", STREAMED_SOUND_MISSION_BNK4_25}, {"BNK4_26", STREAMED_SOUND_MISSION_BNK4_26}, + {"BNK4_27", STREAMED_SOUND_MISSION_BNK4_27}, {"BNK4_28", STREAMED_SOUND_MISSION_BNK4_28}, {"BNK4_29", STREAMED_SOUND_MISSION_BNK4_29}, + {"BNK4_30", STREAMED_SOUND_MISSION_BNK4_30}, {"BK4_31a", STREAMED_SOUND_MISSION_BK4_31A}, {"BK4_31b", STREAMED_SOUND_MISSION_BK4_31B}, + {"BNK4_32", STREAMED_SOUND_MISSION_BNK4_32}, {"BK4_34a", STREAMED_SOUND_MISSION_BK4_34A}, {"BK4_34b", STREAMED_SOUND_MISSION_BK4_34B}, + {"BK4_35a", STREAMED_SOUND_MISSION_BK4_35A}, {"BK4_35b", STREAMED_SOUND_MISSION_BK4_35B}, {"BNK4_36", STREAMED_SOUND_MISSION_BNK4_36}, + {"BNK4_37", STREAMED_SOUND_MISSION_BNK4_37}, {"BNK4_38", STREAMED_SOUND_MISSION_BNK4_38}, {"BNK_39", STREAMED_SOUND_MISSION_BNK4_39}, + {"BK4_40a", STREAMED_SOUND_MISSION_BK4_40A}, {"BK4_40b", STREAMED_SOUND_MISSION_BK4_40B}, {"BNK4_41", STREAMED_SOUND_MISSION_BNK4_41}, + {"BNK4_42", STREAMED_SOUND_MISSION_BNK4_42}, {"BNK4_43", STREAMED_SOUND_MISSION_BNK4_43}, {"BNK4_44", STREAMED_SOUND_MISSION_BNK4_44}, + {"BNK4_45", STREAMED_SOUND_MISSION_BNK4_45}, {"BNK4_46", STREAMED_SOUND_MISSION_BNK4_46}, {"BNK4_47", STREAMED_SOUND_MISSION_BNK4_47}, + {"BNK4_48", STREAMED_SOUND_MISSION_BNK4_48}, {"BNK4_49", STREAMED_SOUND_MISSION_BNK4_49}, {"BNK450A", STREAMED_SOUND_MISSION_BNK450A}, + {"BNK450B", STREAMED_SOUND_MISSION_BNK450B}, {"BNK4_51", STREAMED_SOUND_MISSION_BNK4_51}, {"BNK4_94", STREAMED_SOUND_MISSION_BNK4_94}, + {"BNK4_95", STREAMED_SOUND_MISSION_BNK4_95}, {"BNK4_96", STREAMED_SOUND_MISSION_BNK4_96}, {"BNK4_97", STREAMED_SOUND_MISSION_BNK4_97}, + {"BNK4_98", STREAMED_SOUND_MISSION_BNK4_98}, {"BNK4_99", STREAMED_SOUND_MISSION_BNK4_99}, {"CNT1_1", STREAMED_SOUND_MISSION_CNT1_1}, + {"CNT1_2", STREAMED_SOUND_MISSION_CNT1_2}, {"CNT1_3", STREAMED_SOUND_MISSION_CNT1_3}, {"CNT1_4", STREAMED_SOUND_MISSION_CNT1_4}, + {"CNT1_5", STREAMED_SOUND_MISSION_CNT1_5}, {"CNT2_1", STREAMED_SOUND_MISSION_CNT2_1}, {"CNT2_2", STREAMED_SOUND_MISSION_CNT2_2}, + {"CNT2_3", STREAMED_SOUND_MISSION_CNT2_3}, {"CNT2_4", STREAMED_SOUND_MISSION_CNT2_4}, {"PORN1_1", STREAMED_SOUND_MISSION_PORN1_1}, + {"PORN1_2", STREAMED_SOUND_MISSION_PORN1_2}, {"PORN1_3", STREAMED_SOUND_MISSION_PORN1_3}, {"PRN1_3A", STREAMED_SOUND_MISSION_PRN1_3A}, + {"PORN1_4", STREAMED_SOUND_MISSION_PORN1_4}, {"PORN1_5", STREAMED_SOUND_MISSION_PORN1_5}, {"PORN1_6", STREAMED_SOUND_MISSION_PORN1_6}, + {"PORN1_7", STREAMED_SOUND_MISSION_PORN1_7}, {"PORN1_8", STREAMED_SOUND_MISSION_PORN1_8}, {"PORN1_9", STREAMED_SOUND_MISSION_PORN1_9}, + {"PRN1_10", STREAMED_SOUND_MISSION_PRN1_10}, {"PRN1_11", STREAMED_SOUND_MISSION_PRN1_11}, {"PRN1_12", STREAMED_SOUND_MISSION_PRN1_12}, + {"PRN1_13", STREAMED_SOUND_MISSION_PRN1_13}, {"PRN1_14", STREAMED_SOUND_MISSION_PRN1_14}, {"PRN1_15", STREAMED_SOUND_MISSION_PRN1_15}, + {"PRN1_16", STREAMED_SOUND_MISSION_PRN1_16}, {"PRN1_17", STREAMED_SOUND_MISSION_PRN1_17}, {"PRN1_18", STREAMED_SOUND_MISSION_PRN1_18}, + {"PRN1_19", STREAMED_SOUND_MISSION_PRN1_19}, {"PRN1_20", STREAMED_SOUND_MISSION_PRN1_20}, {"PRN1_21", STREAMED_SOUND_MISSION_PRN1_21}, + {"PORN3_1", STREAMED_SOUND_MISSION_PORN3_1}, {"PORN3_2", STREAMED_SOUND_MISSION_PORN3_2}, {"PORN3_3", STREAMED_SOUND_MISSION_PORN3_3}, + {"PORN3_4", STREAMED_SOUND_MISSION_PORN3_4}, {"TAX1_1", STREAMED_SOUND_MISSION_TAX1_1}, {"TAX1_2", STREAMED_SOUND_MISSION_TAX1_2}, + {"TAX1_3", STREAMED_SOUND_MISSION_TAX1_3}, {"TAX1_4", STREAMED_SOUND_MISSION_TAX1_4}, {"TAX1_5", STREAMED_SOUND_MISSION_TAX1_5}, + {"TAX2_1", STREAMED_SOUND_MISSION_TAX2_1}, {"TAX2_2", STREAMED_SOUND_MISSION_TAX2_2}, {"TAX2_3", STREAMED_SOUND_MISSION_TAX2_3}, + {"TAX2_4", STREAMED_SOUND_MISSION_TAX2_4}, {"TAX2_5", STREAMED_SOUND_MISSION_TAX2_5}, {"TAX2_6", STREAMED_SOUND_MISSION_TAX2_6}, + {"TAX2_7", STREAMED_SOUND_MISSION_TAX2_7}, {"TAX3_1", STREAMED_SOUND_MISSION_TAX3_1}, {"TAX3_2", STREAMED_SOUND_MISSION_TAX3_2}, + {"TAX3_3", STREAMED_SOUND_MISSION_TAX3_3}, {"TAX3_4", STREAMED_SOUND_MISSION_TAX3_4}, {"TAX3_5", STREAMED_SOUND_MISSION_TAX3_5}, + {"TEX1_1", STREAMED_SOUND_MISSION_TEX1_1}, {"TEX1_2", STREAMED_SOUND_MISSION_TEX1_2}, {"TEX1_3", STREAMED_SOUND_MISSION_TEX1_3}, + {"TEX1_4", STREAMED_SOUND_MISSION_TEX1_4}, {"TEX1_5", STREAMED_SOUND_MISSION_TEX1_5}, {"TEX1_6", STREAMED_SOUND_MISSION_TEX1_6}, + {"TEX2_1", STREAMED_SOUND_MISSION_TEX2_1}, {"TEX3_1", STREAMED_SOUND_MISSION_TEX3_1}, {"TEX3_2", STREAMED_SOUND_MISSION_TEX3_2}, + {"TEX3_3", STREAMED_SOUND_MISSION_TEX3_3}, {"TEX3_4", STREAMED_SOUND_MISSION_TEX3_4}, {"TEX3_5", STREAMED_SOUND_MISSION_TEX3_5}, + {"TEX3_6", STREAMED_SOUND_MISSION_TEX3_6}, {"TEX3_7", STREAMED_SOUND_MISSION_TEX3_7}, {"TEX3_8", STREAMED_SOUND_MISSION_TEX3_8}, + {"PHIL1_2", STREAMED_SOUND_MISSION_PHIL1_2}, {"PHIL1_3", STREAMED_SOUND_MISSION_PHIL1_3}, {"PHIL2_1", STREAMED_SOUND_MISSION_PHIL2_1}, + {"PHIL2_2", STREAMED_SOUND_MISSION_PHIL2_2}, {"PHIL2_3", STREAMED_SOUND_MISSION_PHIL2_3}, {"PHIL2_4", STREAMED_SOUND_MISSION_PHIL2_4}, + {"PHIL2_5", STREAMED_SOUND_MISSION_PHIL2_5}, {"PHIL2_6", STREAMED_SOUND_MISSION_PHIL2_6}, {"PHIL2_7", STREAMED_SOUND_MISSION_PHIL2_7}, + {"PHIL2_8", STREAMED_SOUND_MISSION_PHIL2_8}, {"PHIL2_9", STREAMED_SOUND_MISSION_PHIL2_9}, {"PHIL210", STREAMED_SOUND_MISSION_PHIL210}, + {"PHIL211", STREAMED_SOUND_MISSION_PHIL211}, {"BIKE1_1", STREAMED_SOUND_MISSION_BIKE1_1}, {"BIKE1_2", STREAMED_SOUND_MISSION_BIKE1_2}, + {"BIKE1_3", STREAMED_SOUND_MISSION_BIKE1_3}, {"ROK1_1a", STREAMED_SOUND_MISSION_ROK1_1A}, {"ROK1_1b", STREAMED_SOUND_MISSION_ROK1_1B}, + {"ROK1_5", STREAMED_SOUND_MISSION_ROK1_5}, {"ROK1_6", STREAMED_SOUND_MISSION_ROK1_6}, {"ROK1_7", STREAMED_SOUND_MISSION_ROK1_7}, + {"ROK1_8", STREAMED_SOUND_MISSION_ROK1_8}, {"ROK1_9", STREAMED_SOUND_MISSION_ROK1_9}, {"PSYCH_1", STREAMED_SOUND_MISSION_PSYCH_1}, + {"PSYCH_2", STREAMED_SOUND_MISSION_PSYCH_2}, {"ROK2_01", STREAMED_SOUND_MISSION_ROK2_01}, {"ROK3_1", STREAMED_SOUND_MISSION_ROK3_1}, + {"ROK3_2", STREAMED_SOUND_MISSION_ROK3_2}, {"ROK3_3", STREAMED_SOUND_MISSION_ROK3_3}, {"ROK3_4", STREAMED_SOUND_MISSION_ROK3_4}, + {"ROK3_5", STREAMED_SOUND_MISSION_ROK3_5}, {"ROK3_6", STREAMED_SOUND_MISSION_ROK3_6}, {"ROK3_7", STREAMED_SOUND_MISSION_ROK3_7}, + {"ROK3_8", STREAMED_SOUND_MISSION_ROK3_8}, {"ROK3_9", STREAMED_SOUND_MISSION_ROK3_9}, {"ROK3_10", STREAMED_SOUND_MISSION_ROK3_10}, + {"ROK3_11", STREAMED_SOUND_MISSION_ROK3_11}, {"ROK3_12", STREAMED_SOUND_MISSION_ROK3_12}, {"ROK3_13", STREAMED_SOUND_MISSION_ROK3_13}, + {"ROK3_14", STREAMED_SOUND_MISSION_ROK3_14}, {"ROK3_15", STREAMED_SOUND_MISSION_ROK3_15}, {"ROK3_16", STREAMED_SOUND_MISSION_ROK3_16}, + {"ROK3_17", STREAMED_SOUND_MISSION_ROK3_17}, {"ROK3_18", STREAMED_SOUND_MISSION_ROK3_18}, {"ROK3_19", STREAMED_SOUND_MISSION_ROK3_19}, + {"ROK3_20", STREAMED_SOUND_MISSION_ROK3_20}, {"ROK3_21", STREAMED_SOUND_MISSION_ROK3_21}, {"ROK3_22", STREAMED_SOUND_MISSION_ROK3_22}, + {"ROK3_23", STREAMED_SOUND_MISSION_ROK3_23}, {"ROK3_24", STREAMED_SOUND_MISSION_ROK3_24}, {"ROK3_25", STREAMED_SOUND_MISSION_ROK3_25}, + {"ROK3_26", STREAMED_SOUND_MISSION_ROK3_26}, {"ROK3_27", STREAMED_SOUND_MISSION_ROK3_27}, {"ROK3_62", STREAMED_SOUND_MISSION_ROK3_62}, + {"ROK3_63", STREAMED_SOUND_MISSION_ROK3_63}, {"ROK3_64", STREAMED_SOUND_MISSION_ROK3_64}, {"ROK3_65", STREAMED_SOUND_MISSION_ROK3_65}, + {"ROK3_66", STREAMED_SOUND_MISSION_ROK3_66}, {"ROK3_67", STREAMED_SOUND_MISSION_ROK3_67}, {"ROK3_68", STREAMED_SOUND_MISSION_ROK3_68}, + {"ROK3_69", STREAMED_SOUND_MISSION_ROK3_69}, {"ROK3_70", STREAMED_SOUND_MISSION_ROK3_70}, {"ROK3_71", STREAMED_SOUND_MISSION_ROK3_71}, + {"ROK3_73", STREAMED_SOUND_MISSION_ROK3_73}, {"HAT_1a", STREAMED_SOUND_MISSION_HAT_1A}, {"intro1", STREAMED_SOUND_MISSION_INTRO1}, + {"intro2", STREAMED_SOUND_MISSION_INTRO2}, {"intro3", STREAMED_SOUND_MISSION_INTRO3}, {"intro4", STREAMED_SOUND_MISSION_INTRO4}, + {"CUB1_1", STREAMED_SOUND_MISSION_CUB1_1}, {"CUB1_2", STREAMED_SOUND_MISSION_CUB1_2}, {"CUB1_3", STREAMED_SOUND_MISSION_CUB1_3}, + {"CUB1_4", STREAMED_SOUND_MISSION_CUB1_4}, {"CUB1_5", STREAMED_SOUND_MISSION_CUB1_5}, {"CUB1_6", STREAMED_SOUND_MISSION_CUB1_6}, + {"CUB1_7", STREAMED_SOUND_MISSION_CUB1_7}, {"CUB1_8", STREAMED_SOUND_MISSION_CUB1_8}, {"CUB1_9", STREAMED_SOUND_MISSION_CUB1_9}, + {"CUB1_10", STREAMED_SOUND_MISSION_CUB1_10}, {"CUB2_1", STREAMED_SOUND_MISSION_CUB2_1}, {"CUB2_2", STREAMED_SOUND_MISSION_CUB2_2}, + {"CUB2_3a", STREAMED_SOUND_MISSION_CUB2_3A}, {"CUB2_3b", STREAMED_SOUND_MISSION_CUB2_3B}, {"CUB2_3c", STREAMED_SOUND_MISSION_CUB2_3C}, + {"CUB2_4a", STREAMED_SOUND_MISSION_CUB2_4A}, {"CUB2_5", STREAMED_SOUND_MISSION_CUB2_5}, {"CUB2_6", STREAMED_SOUND_MISSION_CUB2_6}, + {"CUB2_7", STREAMED_SOUND_MISSION_CUB2_7}, {"CUB2_8", STREAMED_SOUND_MISSION_CUB2_8}, {"CUB2_9", STREAMED_SOUND_MISSION_CUB2_9}, + {"CUB2_10", STREAMED_SOUND_MISSION_CUB2_10}, {"CUB2_11", STREAMED_SOUND_MISSION_CUB2_11}, {"CUB3_1", STREAMED_SOUND_MISSION_CUB3_1}, + {"CUB3_2", STREAMED_SOUND_MISSION_CUB3_2}, {"CUB3_3", STREAMED_SOUND_MISSION_CUB3_3}, {"CUB3_4", STREAMED_SOUND_MISSION_CUB3_4}, + {"CUB4_1", STREAMED_SOUND_MISSION_CUB4_1}, {"CUB4_2", STREAMED_SOUND_MISSION_CUB4_2}, {"CUB4_3", STREAMED_SOUND_MISSION_CUB4_3}, + {"CUB4_4", STREAMED_SOUND_MISSION_CUB4_4}, {"CUB4_5", STREAMED_SOUND_MISSION_CUB4_5}, {"CUB4_5A", STREAMED_SOUND_MISSION_CUB4_5A}, + {"CUB4_6", STREAMED_SOUND_MISSION_CUB4_6}, {"CUB4_7", STREAMED_SOUND_MISSION_CUB4_7}, {"CUB4_8", STREAMED_SOUND_MISSION_CUB4_8}, + {"CUB4_9", STREAMED_SOUND_MISSION_CUB4_9}, {"CUB4_10", STREAMED_SOUND_MISSION_CUB4_10}, {"CUB4_11", STREAMED_SOUND_MISSION_CUB4_11}, + {"CUB4_12", STREAMED_SOUND_MISSION_CUB4_12}, {"CUB4_13", STREAMED_SOUND_MISSION_CUB4_13}, {"CUB4_14", STREAMED_SOUND_MISSION_CUB4_14}, + {"CUB4_15", STREAMED_SOUND_MISSION_CUB4_15}, {"CUB4_16", STREAMED_SOUND_MISSION_CUB4_16}, {"golf_1", STREAMED_SOUND_MISSION_GOLF_1}, + {"golf_2", STREAMED_SOUND_MISSION_GOLF_2}, {"golf_3", STREAMED_SOUND_MISSION_GOLF_3}, {"bar_1", STREAMED_SOUND_MISSION_BAR_1}, + {"bar_2", STREAMED_SOUND_MISSION_BAR_2}, {"bar_3", STREAMED_SOUND_MISSION_BAR_3}, {"bar_4", STREAMED_SOUND_MISSION_BAR_4}, + {"bar_5", STREAMED_SOUND_MISSION_BAR_5}, {"bar_6", STREAMED_SOUND_MISSION_BAR_6}, {"bar_7", STREAMED_SOUND_MISSION_BAR_7}, + {"bar_8", STREAMED_SOUND_MISSION_BAR_8}, {"strip_1", STREAMED_SOUND_MISSION_STRIP_1}, {"strip_2", STREAMED_SOUND_MISSION_STRIP_2}, + {"strip_3", STREAMED_SOUND_MISSION_STRIP_3}, {"strip_4", STREAMED_SOUND_MISSION_STRIP_4}, {"strip_5", STREAMED_SOUND_MISSION_STRIP_5}, + {"strip_6", STREAMED_SOUND_MISSION_STRIP_6}, {"strip_7", STREAMED_SOUND_MISSION_STRIP_7}, {"strip_8", STREAMED_SOUND_MISSION_STRIP_8}, + {"strip_9", STREAMED_SOUND_MISSION_STRIP_9}, {"star_1", STREAMED_SOUND_MISSION_STAR_1}, {"star_2", STREAMED_SOUND_MISSION_STAR_2}, + {"star_3", STREAMED_SOUND_MISSION_STAR_3}, {"star_4", STREAMED_SOUND_MISSION_STAR_4}, {"mob_01a", STREAMED_SOUND_MISSION_MOB_01A}, + {"mob_01b", STREAMED_SOUND_MISSION_MOB_01B}, {"mob_01c", STREAMED_SOUND_MISSION_MOB_01C}, {"mob_02a", STREAMED_SOUND_MISSION_MOB_02A}, + {"mob_02b", STREAMED_SOUND_MISSION_MOB_02B}, {"mob_02c", STREAMED_SOUND_MISSION_MOB_02C}, {"mob_03a", STREAMED_SOUND_MISSION_MOB_03A}, + {"mob_03b", STREAMED_SOUND_MISSION_MOB_03B}, {"mob_03c", STREAMED_SOUND_MISSION_MOB_03C}, {"mob_03d", STREAMED_SOUND_MISSION_MOB_03D}, + {"mob_03e", STREAMED_SOUND_MISSION_MOB_03E}, {"shark_1", STREAMED_SOUND_MISSION_SHARK_1}, {"shark_2", STREAMED_SOUND_MISSION_SHARK_2}, + {"shark_3", STREAMED_SOUND_MISSION_SHARK_3}, {"shark_4", STREAMED_SOUND_MISSION_SHARK_4}, {"shark_5", STREAMED_SOUND_MISSION_SHARK_5}, + {"mob_04a", STREAMED_SOUND_MISSION_MOB_04A}, {"mob_04b", STREAMED_SOUND_MISSION_MOB_04B}, {"mob_04c", STREAMED_SOUND_MISSION_MOB_04C}, + {"mob_04d", STREAMED_SOUND_MISSION_MOB_04D}, {"mob_05a", STREAMED_SOUND_MISSION_MOB_05A}, {"mob_05b", STREAMED_SOUND_MISSION_MOB_05B}, + {"mob_05c", STREAMED_SOUND_MISSION_MOB_05C}, {"mob_05d", STREAMED_SOUND_MISSION_MOB_05D}, {"mob_06a", STREAMED_SOUND_MISSION_MOB_06A}, + {"mob_06b", STREAMED_SOUND_MISSION_MOB_06B}, {"mob_06c", STREAMED_SOUND_MISSION_MOB_06C}, {"mob_07a", STREAMED_SOUND_MISSION_MOB_07A}, + {"mob_07b", STREAMED_SOUND_MISSION_MOB_07B}, {"mob_08a", STREAMED_SOUND_MISSION_MOB_08A}, {"mob_08b", STREAMED_SOUND_MISSION_MOB_08B}, + {"mob_08c", STREAMED_SOUND_MISSION_MOB_08C}, {"mob_08d", STREAMED_SOUND_MISSION_MOB_08D}, {"mob_08e", STREAMED_SOUND_MISSION_MOB_08E}, + {"mob_08f", STREAMED_SOUND_MISSION_MOB_08F}, {"mob_08g", STREAMED_SOUND_MISSION_MOB_08G}, {"mob_09a", STREAMED_SOUND_MISSION_MOB_09A}, + {"mob_09b", STREAMED_SOUND_MISSION_MOB_09B}, {"mob_09c", STREAMED_SOUND_MISSION_MOB_09C}, {"mob_09d", STREAMED_SOUND_MISSION_MOB_09D}, + {"mob_09e", STREAMED_SOUND_MISSION_MOB_09E}, {"mob_09f", STREAMED_SOUND_MISSION_MOB_09F}, {"mob_10a", STREAMED_SOUND_MISSION_MOB_10A}, + {"mob_10b", STREAMED_SOUND_MISSION_MOB_10B}, {"mob_10c", STREAMED_SOUND_MISSION_MOB_10C}, {"mob_10d", STREAMED_SOUND_MISSION_MOB_10D}, + {"mob_10e", STREAMED_SOUND_MISSION_MOB_10E}, {"mob_11a", STREAMED_SOUND_MISSION_MOB_11A}, {"mob_11b", STREAMED_SOUND_MISSION_MOB_11B}, + {"mob_11c", STREAMED_SOUND_MISSION_MOB_11C}, {"mob_11d", STREAMED_SOUND_MISSION_MOB_11D}, {"mob_11e", STREAMED_SOUND_MISSION_MOB_11E}, + {"mob_11f", STREAMED_SOUND_MISSION_MOB_11F}, {"mob_14a", STREAMED_SOUND_MISSION_MOB_14A}, {"mob_14b", STREAMED_SOUND_MISSION_MOB_14B}, + {"mob_14c", STREAMED_SOUND_MISSION_MOB_14C}, {"mob_14d", STREAMED_SOUND_MISSION_MOB_14D}, {"mob_14e", STREAMED_SOUND_MISSION_MOB_14E}, + {"mob_14f", STREAMED_SOUND_MISSION_MOB_14F}, {"mob_14g", STREAMED_SOUND_MISSION_MOB_14G}, {"mob_14h", STREAMED_SOUND_MISSION_MOB_14H}, + {"mob_16a", STREAMED_SOUND_MISSION_MOB_16A}, {"mob_16b", STREAMED_SOUND_MISSION_MOB_16B}, {"mob_16c", STREAMED_SOUND_MISSION_MOB_16C}, + {"mob_16d", STREAMED_SOUND_MISSION_MOB_16D}, {"mob_16e", STREAMED_SOUND_MISSION_MOB_16E}, {"mob_16f", STREAMED_SOUND_MISSION_MOB_16F}, + {"mob_16g", STREAMED_SOUND_MISSION_MOB_16G}, {"mob_17a", STREAMED_SOUND_MISSION_MOB_17A}, {"mob_17b", STREAMED_SOUND_MISSION_MOB_17B}, + {"mob_17c", STREAMED_SOUND_MISSION_MOB_17C}, {"mob_17d", STREAMED_SOUND_MISSION_MOB_17D}, {"mob_17e", STREAMED_SOUND_MISSION_MOB_17E}, + {"mob_17g", STREAMED_SOUND_MISSION_MOB_17G}, {"mob_17h", STREAMED_SOUND_MISSION_MOB_17H}, {"mob_17i", STREAMED_SOUND_MISSION_MOB_17I}, + {"mob_17j", STREAMED_SOUND_MISSION_MOB_17J}, {"mob_17k", STREAMED_SOUND_MISSION_MOB_17K}, {"mob_17l", STREAMED_SOUND_MISSION_MOB_17L}, + {"mob_18a", STREAMED_SOUND_MISSION_MOB_18A}, {"mob_18b", STREAMED_SOUND_MISSION_MOB_18B}, {"mob_18c", STREAMED_SOUND_MISSION_MOB_18C}, + {"mob_18d", STREAMED_SOUND_MISSION_MOB_18D}, {"mob_18e", STREAMED_SOUND_MISSION_MOB_18E}, {"mob_18f", STREAMED_SOUND_MISSION_MOB_18F}, + {"mob_18g", STREAMED_SOUND_MISSION_MOB_18G}, {"mob_20a", STREAMED_SOUND_MISSION_MOB_20A}, {"mob_20b", STREAMED_SOUND_MISSION_MOB_20B}, + {"mob_20c", STREAMED_SOUND_MISSION_MOB_20C}, {"mob_20d", STREAMED_SOUND_MISSION_MOB_20D}, {"mob_20e", STREAMED_SOUND_MISSION_MOB_20E}, + {"mob_24a", STREAMED_SOUND_MISSION_MOB_24A}, {"mob_24b", STREAMED_SOUND_MISSION_MOB_24B}, {"mob_24c", STREAMED_SOUND_MISSION_MOB_24C}, + {"mob_24d", STREAMED_SOUND_MISSION_MOB_24D}, {"mob_24e", STREAMED_SOUND_MISSION_MOB_24E}, {"mob_24f", STREAMED_SOUND_MISSION_MOB_24F}, + {"mob_24g", STREAMED_SOUND_MISSION_MOB_24G}, {"mob_24h", STREAMED_SOUND_MISSION_MOB_24H}, {"mob_25a", STREAMED_SOUND_MISSION_MOB_25A}, + {"mob_25b", STREAMED_SOUND_MISSION_MOB_25B}, {"mob_25c", STREAMED_SOUND_MISSION_MOB_25C}, {"mob_25d", STREAMED_SOUND_MISSION_MOB_25D}, + {"mob_26a", STREAMED_SOUND_MISSION_MOB_26A}, {"mob_26b", STREAMED_SOUND_MISSION_MOB_26B}, {"mob_26c", STREAMED_SOUND_MISSION_MOB_26C}, + {"mob_26d", STREAMED_SOUND_MISSION_MOB_26D}, {"mob_26e", STREAMED_SOUND_MISSION_MOB_26E}, {"mob_29a", STREAMED_SOUND_MISSION_MOB_29A}, + {"mob_29b", STREAMED_SOUND_MISSION_MOB_29B}, {"mob_29c", STREAMED_SOUND_MISSION_MOB_29C}, {"mob_29d", STREAMED_SOUND_MISSION_MOB_29D}, + {"mob_29e", STREAMED_SOUND_MISSION_MOB_29E}, {"mob_29f", STREAMED_SOUND_MISSION_MOB_29F}, {"mob_29g", STREAMED_SOUND_MISSION_MOB_29G}, + {"mob_30a", STREAMED_SOUND_MISSION_MOB_30A}, {"mob_30b", STREAMED_SOUND_MISSION_MOB_30B}, {"mob_30c", STREAMED_SOUND_MISSION_MOB_30C}, + {"mob_30d", STREAMED_SOUND_MISSION_MOB_30D}, {"mob_30e", STREAMED_SOUND_MISSION_MOB_30E}, {"mob_30f", STREAMED_SOUND_MISSION_MOB_30F}, + {"mob_33a", STREAMED_SOUND_MISSION_MOB_33A}, {"mob_33b", STREAMED_SOUND_MISSION_MOB_33B}, {"mob_33c", STREAMED_SOUND_MISSION_MOB_33C}, + {"mob_33d", STREAMED_SOUND_MISSION_MOB_33D}, {"mob_34a", STREAMED_SOUND_MISSION_MOB_34A}, {"mob_34b", STREAMED_SOUND_MISSION_MOB_34B}, + {"mob_34c", STREAMED_SOUND_MISSION_MOB_34C}, {"mob_34d", STREAMED_SOUND_MISSION_MOB_34D}, {"mob_35a", STREAMED_SOUND_MISSION_MOB_35A}, + {"mob_35b", STREAMED_SOUND_MISSION_MOB_35B}, {"mob_35c", STREAMED_SOUND_MISSION_MOB_35C}, {"mob_35d", STREAMED_SOUND_MISSION_MOB_35D}, + {"mob_36a", STREAMED_SOUND_MISSION_MOB_36A}, {"mob_36b", STREAMED_SOUND_MISSION_MOB_36B}, {"mob_36c", STREAMED_SOUND_MISSION_MOB_36C}, + {"mob_40a", STREAMED_SOUND_MISSION_MOB_40A}, {"mob_40b", STREAMED_SOUND_MISSION_MOB_40B}, {"mob_40c", STREAMED_SOUND_MISSION_MOB_40C}, + {"mob_40d", STREAMED_SOUND_MISSION_MOB_40D}, {"mob_40e", STREAMED_SOUND_MISSION_MOB_40E}, {"mob_40f", STREAMED_SOUND_MISSION_MOB_40F}, + {"mob_40g", STREAMED_SOUND_MISSION_MOB_40G}, {"mob_40h", STREAMED_SOUND_MISSION_MOB_40H}, {"mob_40i", STREAMED_SOUND_MISSION_MOB_40I}, + {"mob_41a", STREAMED_SOUND_MISSION_MOB_41A}, {"mob_41b", STREAMED_SOUND_MISSION_MOB_41B}, {"mob_41c", STREAMED_SOUND_MISSION_MOB_41C}, + {"mob_41d", STREAMED_SOUND_MISSION_MOB_41D}, {"mob_41e", STREAMED_SOUND_MISSION_MOB_41E}, {"mob_41f", STREAMED_SOUND_MISSION_MOB_41F}, + {"mob_41g", STREAMED_SOUND_MISSION_MOB_41G}, {"mob_41h", STREAMED_SOUND_MISSION_MOB_41H}, {"mob_42a", STREAMED_SOUND_MISSION_MOB_42A}, + {"mob_42b", STREAMED_SOUND_MISSION_MOB_42B}, {"mob_42c", STREAMED_SOUND_MISSION_MOB_42C}, {"mob_42d", STREAMED_SOUND_MISSION_MOB_42D}, + {"mob_42e", STREAMED_SOUND_MISSION_MOB_42E}, {"mob_43a", STREAMED_SOUND_MISSION_MOB_43A}, {"mob_43b", STREAMED_SOUND_MISSION_MOB_43B}, + {"mob_43c", STREAMED_SOUND_MISSION_MOB_43C}, {"mob_43d", STREAMED_SOUND_MISSION_MOB_43D}, {"mob_43e", STREAMED_SOUND_MISSION_MOB_43E}, + {"mob_43f", STREAMED_SOUND_MISSION_MOB_43F}, {"mob_43g", STREAMED_SOUND_MISSION_MOB_43G}, {"mob_43h", STREAMED_SOUND_MISSION_MOB_43H}, + {"mob_45a", STREAMED_SOUND_MISSION_MOB_45A}, {"mob_45b", STREAMED_SOUND_MISSION_MOB_45B}, {"mob_45c", STREAMED_SOUND_MISSION_MOB_45C}, + {"mob_45d", STREAMED_SOUND_MISSION_MOB_45D}, {"mob_45e", STREAMED_SOUND_MISSION_MOB_45E}, {"mob_45f", STREAMED_SOUND_MISSION_MOB_45F}, + {"mob_45g", STREAMED_SOUND_MISSION_MOB_45G}, {"mob_45h", STREAMED_SOUND_MISSION_MOB_45H}, {"mob_45i", STREAMED_SOUND_MISSION_MOB_45I}, + {"mob_45j", STREAMED_SOUND_MISSION_MOB_45J}, {"mob_45k", STREAMED_SOUND_MISSION_MOB_45K}, {"mob_45l", STREAMED_SOUND_MISSION_MOB_45L}, + {"mob_45m", STREAMED_SOUND_MISSION_MOB_45M}, {"mob_45n", STREAMED_SOUND_MISSION_MOB_45N}, {"mob_46a", STREAMED_SOUND_MISSION_MOB_46A}, + {"mob_46b", STREAMED_SOUND_MISSION_MOB_46B}, {"mob_46c", STREAMED_SOUND_MISSION_MOB_46C}, {"mob_46d", STREAMED_SOUND_MISSION_MOB_46D}, + {"mob_46e", STREAMED_SOUND_MISSION_MOB_46E}, {"mob_46f", STREAMED_SOUND_MISSION_MOB_46F}, {"mob_46g", STREAMED_SOUND_MISSION_MOB_46G}, + {"mob_46h", STREAMED_SOUND_MISSION_MOB_46H}, {"mob_47a", STREAMED_SOUND_MISSION_MOB_47A}, {"mob_52a", STREAMED_SOUND_MISSION_MOB_52A}, + {"mob_52b", STREAMED_SOUND_MISSION_MOB_52B}, {"mob_52c", STREAMED_SOUND_MISSION_MOB_52C}, {"mob_52d", STREAMED_SOUND_MISSION_MOB_52D}, + {"mob_52e", STREAMED_SOUND_MISSION_MOB_52E}, {"mob_52f", STREAMED_SOUND_MISSION_MOB_52F}, {"mob_52g", STREAMED_SOUND_MISSION_MOB_52G}, + {"mob_52h", STREAMED_SOUND_MISSION_MOB_52H}, {"mob_54a", STREAMED_SOUND_MISSION_MOB_54A}, {"mob_54b", STREAMED_SOUND_MISSION_MOB_54B}, + {"mob_54c", STREAMED_SOUND_MISSION_MOB_54C}, {"mob_54d", STREAMED_SOUND_MISSION_MOB_54D}, {"mob_54e", STREAMED_SOUND_MISSION_MOB_54E}, + {"mob_55a", STREAMED_SOUND_MISSION_MOB_55A}, {"mob_55b", STREAMED_SOUND_MISSION_MOB_55B}, {"mob_55c", STREAMED_SOUND_MISSION_MOB_55C}, + {"mob_55d", STREAMED_SOUND_MISSION_MOB_55D}, {"mob_55e", STREAMED_SOUND_MISSION_MOB_55E}, {"mob_55f", STREAMED_SOUND_MISSION_MOB_55F}, + {"mob_56a", STREAMED_SOUND_MISSION_MOB_56A}, {"mob_56b", STREAMED_SOUND_MISSION_MOB_56B}, {"mob_56c", STREAMED_SOUND_MISSION_MOB_56C}, + {"mob_56d", STREAMED_SOUND_MISSION_MOB_56D}, {"mob_56e", STREAMED_SOUND_MISSION_MOB_56E}, {"mob_56f", STREAMED_SOUND_MISSION_MOB_56F}, + {"mob_57a", STREAMED_SOUND_MISSION_MOB_57A}, {"mob_57b", STREAMED_SOUND_MISSION_MOB_57B}, {"mob_57c", STREAMED_SOUND_MISSION_MOB_57C}, + {"mob_57d", STREAMED_SOUND_MISSION_MOB_57D}, {"mob_57e", STREAMED_SOUND_MISSION_MOB_57E}, {"mob_58a", STREAMED_SOUND_MISSION_MOB_58A}, + {"mob_58b", STREAMED_SOUND_MISSION_MOB_58B}, {"mob_58c", STREAMED_SOUND_MISSION_MOB_58C}, {"mob_58d", STREAMED_SOUND_MISSION_MOB_58D}, + {"mob_58e", STREAMED_SOUND_MISSION_MOB_58E}, {"mob_58f", STREAMED_SOUND_MISSION_MOB_58F}, {"mob_58g", STREAMED_SOUND_MISSION_MOB_58G}, + {"mob_61a", STREAMED_SOUND_MISSION_MOB_61A}, {"mob_61b", STREAMED_SOUND_MISSION_MOB_61B}, {"mob_62a", STREAMED_SOUND_MISSION_MOB_62A}, + {"mob_62b", STREAMED_SOUND_MISSION_MOB_62B}, {"mob_62c", STREAMED_SOUND_MISSION_MOB_62C}, {"mob_62d", STREAMED_SOUND_MISSION_MOB_62D}, + {"mob_63a", STREAMED_SOUND_MISSION_MOB_63A}, {"mob_63b", STREAMED_SOUND_MISSION_MOB_63B}, {"mob_63c", STREAMED_SOUND_MISSION_MOB_63C}, + {"mob_63d", STREAMED_SOUND_MISSION_MOB_63D}, {"mob_63e", STREAMED_SOUND_MISSION_MOB_63E}, {"mob_63f", STREAMED_SOUND_MISSION_MOB_63F}, + {"mob_63g", STREAMED_SOUND_MISSION_MOB_63G}, {"mob_63h", STREAMED_SOUND_MISSION_MOB_63H}, {"mob_63i", STREAMED_SOUND_MISSION_MOB_63I}, + {"mob_63j", STREAMED_SOUND_MISSION_MOB_63J}, {"mob_66a", STREAMED_SOUND_MISSION_MOB_66A}, {"mob_66b", STREAMED_SOUND_MISSION_MOB_66B}, + {"mob_68a", STREAMED_SOUND_MISSION_MOB_68A}, {"mob_68b", STREAMED_SOUND_MISSION_MOB_68B}, {"mob_68c", STREAMED_SOUND_MISSION_MOB_68C}, + {"mob_68d", STREAMED_SOUND_MISSION_MOB_68D}, {"mob_70a", STREAMED_SOUND_MISSION_MOB_70A}, {"mob_70b", STREAMED_SOUND_MISSION_MOB_70B}, + {"mob_71a", STREAMED_SOUND_MISSION_MOB_71A}, {"mob_71b", STREAMED_SOUND_MISSION_MOB_71B}, {"mob_71c", STREAMED_SOUND_MISSION_MOB_71C}, + {"mob_71d", STREAMED_SOUND_MISSION_MOB_71D}, {"mob_71e", STREAMED_SOUND_MISSION_MOB_71E}, {"mob_71f", STREAMED_SOUND_MISSION_MOB_71F}, + {"mob_71g", STREAMED_SOUND_MISSION_MOB_71G}, {"mob_71h", STREAMED_SOUND_MISSION_MOB_71H}, {"mob_71i", STREAMED_SOUND_MISSION_MOB_71I}, + {"mob_71j", STREAMED_SOUND_MISSION_MOB_71J}, {"mob_71k", STREAMED_SOUND_MISSION_MOB_71K}, {"mob_71l", STREAMED_SOUND_MISSION_MOB_71L}, + {"mob_71m", STREAMED_SOUND_MISSION_MOB_71M}, {"mob_71n", STREAMED_SOUND_MISSION_MOB_71N}, {"mob_72a", STREAMED_SOUND_MISSION_MOB_72A}, + {"mob_72b", STREAMED_SOUND_MISSION_MOB_72B}, {"mob_72c", STREAMED_SOUND_MISSION_MOB_72C}, {"mob_72d", STREAMED_SOUND_MISSION_MOB_72D}, + {"mob_72e", STREAMED_SOUND_MISSION_MOB_72E}, {"mob_72f", STREAMED_SOUND_MISSION_MOB_72F}, {"mob_72g", STREAMED_SOUND_MISSION_MOB_72G}, + {"mob_73a", STREAMED_SOUND_MISSION_MOB_73A}, {"mob_73c", STREAMED_SOUND_MISSION_MOB_73C}, {"mob_73d", STREAMED_SOUND_MISSION_MOB_73D}, + {"mob_73f", STREAMED_SOUND_MISSION_MOB_73F}, {"mob_73g", STREAMED_SOUND_MISSION_MOB_73G}, {"mob_73i", STREAMED_SOUND_MISSION_MOB_73I}, + {"mob_95a", STREAMED_SOUND_MISSION_MOB_95A}, {"mob_96a", STREAMED_SOUND_MISSION_MOB_96A}, {"mob_98a", STREAMED_SOUND_MISSION_MOB_98A}, + {"mob_99a", STREAMED_SOUND_MISSION_MOB_99A}, {"job1_1b", STREAMED_SOUND_MISSION_JOB1_1B}, {"job1_1c", STREAMED_SOUND_MISSION_JOB1_1C}, + {"job1_1d", STREAMED_SOUND_MISSION_JOB1_1D}, {"job2_1b", STREAMED_SOUND_MISSION_JOB2_1B}, {"job2_2", STREAMED_SOUND_MISSION_JOB2_2}, + {"job2_3", STREAMED_SOUND_MISSION_JOB2_3}, {"job2_4", STREAMED_SOUND_MISSION_JOB2_4}, {"job2_5", STREAMED_SOUND_MISSION_JOB2_5}, + {"job2_6", STREAMED_SOUND_MISSION_JOB2_6}, {"job2_7", STREAMED_SOUND_MISSION_JOB2_7}, {"job2_8", STREAMED_SOUND_MISSION_JOB2_8}, + {"job2_9", STREAMED_SOUND_MISSION_JOB2_9}, {"job3_1", STREAMED_SOUND_MISSION_JOB3_1}, {"job3_2", STREAMED_SOUND_MISSION_JOB3_2}, + {"job3_3", STREAMED_SOUND_MISSION_JOB3_3}, {"job4_1", STREAMED_SOUND_MISSION_JOB4_1}, {"job4_2", STREAMED_SOUND_MISSION_JOB4_2}, + {"job4_3", STREAMED_SOUND_MISSION_JOB4_3}, {"job5_1", STREAMED_SOUND_MISSION_JOB5_1}, {"job5_2", STREAMED_SOUND_MISSION_JOB5_2}, + {"job5_3", STREAMED_SOUND_MISSION_JOB5_3}, {"bjm1_20", STREAMED_SOUND_MISSION_BJM1_20}, {"bjm1_4", STREAMED_SOUND_MISSION_BJM1_4}, + {"bjm1_5", STREAMED_SOUND_MISSION_BJM1_5}, {"merc_39", STREAMED_SOUND_MISSION_MERC_39}, {"mono_1", STREAMED_SOUND_MISSION_MONO_1}, + {"mono_2", STREAMED_SOUND_MISSION_MONO_2}, {"mono_3", STREAMED_SOUND_MISSION_MONO_3}, {"mono_4", STREAMED_SOUND_MISSION_MONO_4}, + {"mono_5", STREAMED_SOUND_MISSION_MONO_5}, {"mono_6", STREAMED_SOUND_MISSION_MONO_6}, {"mono_7", STREAMED_SOUND_MISSION_MONO_7}, + {"mono_8", STREAMED_SOUND_MISSION_MONO_8}, {"mono_9", STREAMED_SOUND_MISSION_MONO_9}, {"mono10", STREAMED_SOUND_MISSION_MONO10}, + {"mono11", STREAMED_SOUND_MISSION_MONO11}, {"mono12", STREAMED_SOUND_MISSION_MONO12}, {"mono13", STREAMED_SOUND_MISSION_MONO13}, + {"mono14", STREAMED_SOUND_MISSION_MONO14}, {"mono15", STREAMED_SOUND_MISSION_MONO15}, {"mono16", STREAMED_SOUND_MISSION_MONO16}, + {"fud_01", STREAMED_SOUND_MISSION_FUD_01}, {"fud_02", STREAMED_SOUND_MISSION_FUD_02}, {"fud_03", STREAMED_SOUND_MISSION_FUD_03}, + {"fud_04", STREAMED_SOUND_MISSION_FUD_04}, {"fud_05", STREAMED_SOUND_MISSION_FUD_05}, {"fud_06", STREAMED_SOUND_MISSION_FUD_06}, + {"fud_07", STREAMED_SOUND_MISSION_FUD_07}, {"fud_08", STREAMED_SOUND_MISSION_FUD_08}, {"fud_09", STREAMED_SOUND_MISSION_FUD_09}, + {"fud_10", STREAMED_SOUND_MISSION_FUD_10}, {"fud_11", STREAMED_SOUND_MISSION_FUD_11}, {"fud_12", STREAMED_SOUND_MISSION_FUD_12}, + {"fud_13", STREAMED_SOUND_MISSION_FUD_13}, {"fud_14", STREAMED_SOUND_MISSION_FUD_14}, {"fud_15", STREAMED_SOUND_MISSION_FUD_15}, + {"fud_16", STREAMED_SOUND_MISSION_FUD_16}, {"fud_17", STREAMED_SOUND_MISSION_FUD_17}, {"fud_18", STREAMED_SOUND_MISSION_FUD_18}, + {"fud_19", STREAMED_SOUND_MISSION_FUD_19}, {"fud_20", STREAMED_SOUND_MISSION_FUD_20}, {"burg_01", STREAMED_SOUND_MISSION_BURG_01}, + {"burg_02", STREAMED_SOUND_MISSION_BURG_02}, {"burg_03", STREAMED_SOUND_MISSION_BURG_03}, {"burg_04", STREAMED_SOUND_MISSION_BURG_04}, + {"burg_05", STREAMED_SOUND_MISSION_BURG_05}, {"burg_06", STREAMED_SOUND_MISSION_BURG_06}, {"burg_07", STREAMED_SOUND_MISSION_BURG_07}, + {"burg_08", STREAMED_SOUND_MISSION_BURG_08}, {"burg_09", STREAMED_SOUND_MISSION_BURG_09}, {"burg_10", STREAMED_SOUND_MISSION_BURG_10}, + {"burg_11", STREAMED_SOUND_MISSION_BURG_11}, {"burg_12", STREAMED_SOUND_MISSION_BURG_12}, {"crust01", STREAMED_SOUND_MISSION_CRUST01}, + {"crust02", STREAMED_SOUND_MISSION_CRUST02}, {"crust03", STREAMED_SOUND_MISSION_CRUST03}, {"crust04", STREAMED_SOUND_MISSION_CRUST04}, + {"crust05", STREAMED_SOUND_MISSION_CRUST05}, {"crust06", STREAMED_SOUND_MISSION_CRUST06}, {"crust07", STREAMED_SOUND_MISSION_CRUST07}, + {"crust08", STREAMED_SOUND_MISSION_CRUST08}, {"crust09", STREAMED_SOUND_MISSION_CRUST09}, {"band_01", STREAMED_SOUND_MISSION_BAND_01}, + {"band_02", STREAMED_SOUND_MISSION_BAND_02}, {"band_03", STREAMED_SOUND_MISSION_BAND_03}, {"band_04", STREAMED_SOUND_MISSION_BAND_04}, + {"band_05", STREAMED_SOUND_MISSION_BAND_05}, {"band_06", STREAMED_SOUND_MISSION_BAND_06}, {"band_07", STREAMED_SOUND_MISSION_BAND_07}, + {"band_08", STREAMED_SOUND_MISSION_BAND_08}, {"shaft01", STREAMED_SOUND_MISSION_SHAFT01}, {"shaft02", STREAMED_SOUND_MISSION_SHAFT02}, + {"shaft03", STREAMED_SOUND_MISSION_SHAFT03}, {"shaft04", STREAMED_SOUND_MISSION_SHAFT04}, {"shaft05", STREAMED_SOUND_MISSION_SHAFT05}, + {"shaft06", STREAMED_SOUND_MISSION_SHAFT06}, {"shaft07", STREAMED_SOUND_MISSION_SHAFT07}, {"shaft08", STREAMED_SOUND_MISSION_SHAFT08}, + {"piss_01", STREAMED_SOUND_MISSION_PISS_01}, {"piss_02", STREAMED_SOUND_MISSION_PISS_02}, {"piss_03", STREAMED_SOUND_MISSION_PISS_03}, + {"piss_04", STREAMED_SOUND_MISSION_PISS_04}, {"piss_05", STREAMED_SOUND_MISSION_PISS_05}, {"piss_06", STREAMED_SOUND_MISSION_PISS_06}, + {"piss_07", STREAMED_SOUND_MISSION_PISS_07}, {"piss_08", STREAMED_SOUND_MISSION_PISS_08}, {"piss_09", STREAMED_SOUND_MISSION_PISS_09}, + {"piss_10", STREAMED_SOUND_MISSION_PISS_10}, {"piss_11", STREAMED_SOUND_MISSION_PISS_11}, {"piss_12", STREAMED_SOUND_MISSION_PISS_12}, + {"piss_13", STREAMED_SOUND_MISSION_PISS_13}, {"piss_14", STREAMED_SOUND_MISSION_PISS_14}, {"piss_15", STREAMED_SOUND_MISSION_PISS_15}, + {"piss_16", STREAMED_SOUND_MISSION_PISS_16}, {"piss_17", STREAMED_SOUND_MISSION_PISS_17}, {"piss_18", STREAMED_SOUND_MISSION_PISS_18}, + {"piss_19", STREAMED_SOUND_MISSION_PISS_19}, {"gimme01", STREAMED_SOUND_MISSION_GIMME01}, {"gimme02", STREAMED_SOUND_MISSION_GIMME02}, + {"gimme03", STREAMED_SOUND_MISSION_GIMME03}, {"gimme04", STREAMED_SOUND_MISSION_GIMME04}, {"gimme05", STREAMED_SOUND_MISSION_GIMME05}, + {"gimme06", STREAMED_SOUND_MISSION_GIMME06}, {"gimme07", STREAMED_SOUND_MISSION_GIMME07}, {"gimme08", STREAMED_SOUND_MISSION_GIMME08}, + {"gimme09", STREAMED_SOUND_MISSION_GIMME09}, {"gimme10", STREAMED_SOUND_MISSION_GIMME10}, {"gimme11", STREAMED_SOUND_MISSION_GIMME11}, + {"gimme12", STREAMED_SOUND_MISSION_GIMME12}, {"gimme13", STREAMED_SOUND_MISSION_GIMME13}, {"gimme14", STREAMED_SOUND_MISSION_GIMME14}, + {"gimme15", STREAMED_SOUND_MISSION_GIMME15}, {"bust_01", STREAMED_SOUND_MISSION_BUST_01}, {"bust_02", STREAMED_SOUND_MISSION_BUST_02}, + {"bust_03", STREAMED_SOUND_MISSION_BUST_03}, {"bust_04", STREAMED_SOUND_MISSION_BUST_04}, {"bust_05", STREAMED_SOUND_MISSION_BUST_05}, + {"bust_06", STREAMED_SOUND_MISSION_BUST_06}, {"bust_07", STREAMED_SOUND_MISSION_BUST_07}, {"bust_08", STREAMED_SOUND_MISSION_BUST_08}, + {"bust_09", STREAMED_SOUND_MISSION_BUST_09}, {"bust_10", STREAMED_SOUND_MISSION_BUST_10}, {"bust_11", STREAMED_SOUND_MISSION_BUST_11}, + {"bust_12", STREAMED_SOUND_MISSION_BUST_12}, {"bust_13", STREAMED_SOUND_MISSION_BUST_13}, {"bust_14", STREAMED_SOUND_MISSION_BUST_14}, + {"bust_15", STREAMED_SOUND_MISSION_BUST_15}, {"bust_16", STREAMED_SOUND_MISSION_BUST_16}, {"bust_17", STREAMED_SOUND_MISSION_BUST_17}, + {"bust_18", STREAMED_SOUND_MISSION_BUST_18}, {"bust_19", STREAMED_SOUND_MISSION_BUST_19}, {"bust_20", STREAMED_SOUND_MISSION_BUST_20}, + {"bust_21", STREAMED_SOUND_MISSION_BUST_21}, {"bust_22", STREAMED_SOUND_MISSION_BUST_22}, {"bust_23", STREAMED_SOUND_MISSION_BUST_23}, + {"bust_24", STREAMED_SOUND_MISSION_BUST_24}, {"bust_25", STREAMED_SOUND_MISSION_BUST_25}, {"bust_26", STREAMED_SOUND_MISSION_BUST_26}, + {"bust_27", STREAMED_SOUND_MISSION_BUST_27}, {"bust_28", STREAMED_SOUND_MISSION_BUST_28}, {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; +} + +const char * +cAudioManager::GetMissionAudioLoadedLabel(uint8 slot) +{ + if (m_bIsInitialised && slot < MISSION_AUDIO_SLOTS && m_nMissionAudioSampleIndex[slot] != NO_SAMPLE) { + for (uint32 i = 0; MissionAudioNameSfxAssoc[i].m_pName != nil; i++) { + if (m_nMissionAudioSampleIndex[slot] == MissionAudioNameSfxAssoc[i].m_nId) + return MissionAudioNameSfxAssoc[i].m_pName; + } + } + +#ifdef THIS_IS_STUPID + return MissionAudioNameSfxAssoc[0].m_pName; // yeah this is dumb +#else + return ""; +#endif +} + +bool8 +cAudioManager::MissionScriptAudioUsesPoliceChannel(uint32 soundMission) +{ + return FALSE; +} + +void +cAudioManager::PreloadMissionAudio(uint8 slot, Const char *name) +{ + if (m_bIsInitialised && slot < MISSION_AUDIO_SLOTS) { + uint32 missionAudioSfx = FindMissionAudioSfx(name); + if (missionAudioSfx != NO_SAMPLE) { + m_nMissionAudioSampleIndex[slot] = missionAudioSfx; + m_nMissionAudioLoadingStatus[slot] = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus[slot] = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying[slot] = FALSE; + m_nMissionAudioFramesToPlay[slot] = m_nTimeSpent * SampleManager.GetStreamedFileLength(missionAudioSfx) / 1000; + m_nMissionAudioFramesToPlay[slot] *= 4; + m_bIsMissionAudioAllowedToPlay[slot] = FALSE; + m_bIsMissionAudio2D[slot] = TRUE; + g_bMissionAudioLoadFailed[slot] = FALSE; + } + } +} + +uint8 +cAudioManager::GetMissionAudioLoadingStatus(uint8 slot) +{ + if (m_bIsInitialised && slot < MISSION_AUDIO_SLOTS) + return m_nMissionAudioLoadingStatus[slot]; + + return LOADING_STATUS_LOADED; +} + +void +cAudioManager::SetMissionAudioLocation(uint8 slot, float x, float y, float z) +{ + if (m_bIsInitialised && slot < MISSION_AUDIO_SLOTS) { + m_bIsMissionAudio2D[slot] = FALSE; + m_vecMissionAudioPosition[slot] = CVector(x, y, z); + } +} + +void +cAudioManager::PlayLoadedMissionAudio(uint8 slot) +{ + if (m_bIsInitialised && slot < MISSION_AUDIO_SLOTS && m_nMissionAudioSampleIndex[slot] != NO_SAMPLE && m_nMissionAudioLoadingStatus[slot] == LOADING_STATUS_LOADED && + m_nMissionAudioPlayStatus[slot] == PLAY_STATUS_STOPPED) + m_bIsMissionAudioAllowedToPlay[slot] = TRUE; +} + +bool8 +cAudioManager::ShouldDuckMissionAudio(uint8 slot) +{ + if (IsMissionAudioSamplePlaying(slot)) + return m_nMissionAudioSampleIndex[slot] != STREAMED_SOUND_MISSION_ROK2_01; + return FALSE; +} + +bool8 +cAudioManager::IsMissionAudioSamplePlaying(uint8 slot) +{ + if (m_bIsInitialised) { + if (slot < MISSION_AUDIO_SLOTS) + return m_nMissionAudioPlayStatus[slot] == PLAY_STATUS_PLAYING; + else + return TRUE; + } + + static uint32 cPretendFrame[MISSION_AUDIO_SLOTS] = { 1, 1 }; + + return (cPretendFrame[slot]++ % 64) != 0; +} + +bool8 +cAudioManager::IsMissionAudioSampleFinished(uint8 slot) +{ + if (m_bIsInitialised) { + if (slot < MISSION_AUDIO_SLOTS) + return m_nMissionAudioPlayStatus[slot] == PLAY_STATUS_FINISHED; + else + return TRUE; + } + + static uint32 cPretendFrame[MISSION_AUDIO_SLOTS] = { 1, 1 }; + + return (cPretendFrame[slot]++ % 64) == 0; +} + +void +cAudioManager::ClearMissionAudio(uint8 slot) +{ + if (m_bIsInitialised && slot < MISSION_AUDIO_SLOTS) { + m_nMissionAudioSampleIndex[slot] = NO_SAMPLE; + m_nMissionAudioLoadingStatus[slot] = LOADING_STATUS_NOT_LOADED; + m_nMissionAudioPlayStatus[slot] = PLAY_STATUS_STOPPED; + m_bIsMissionAudioPlaying[slot] = FALSE; + m_bIsMissionAudioAllowedToPlay[slot] = FALSE; + m_bIsMissionAudio2D[slot] = TRUE; + m_nMissionAudioFramesToPlay[slot] = 0; + m_bIsMissionAudioPhoneCall[slot] = FALSE; + SampleManager.StopStreamedFile(slot + 1); + } +} + +void +cAudioManager::ProcessMissionAudioSlot(uint8 slot) +{ + float dist; + uint8 Vol; + uint8 pan; + float distSquared; + CVector vec; + + static uint8 nCheckPlayingDelay[MISSION_AUDIO_SLOTS] = { 0, 0 }; + static uint8 nFramesUntilFailedLoad[MISSION_AUDIO_SLOTS] = { 0, 0 }; + static uint8 nFramesForPretendPlaying[MISSION_AUDIO_SLOTS] = { 0, 0 }; + + if (m_nMissionAudioSampleIndex[slot] != NO_SAMPLE) { + switch (m_nMissionAudioLoadingStatus[slot]) { + case LOADING_STATUS_NOT_LOADED: + SampleManager.PreloadStreamedFile(m_nMissionAudioSampleIndex[slot], slot + 1); + m_nMissionAudioLoadingStatus[slot] = LOADING_STATUS_LOADED; + nFramesUntilFailedLoad[slot] = 0; + break; + case LOADING_STATUS_LOADING: + if (++nFramesUntilFailedLoad[slot] >= 120) { + nFramesForPretendPlaying[slot] = 0; + g_bMissionAudioLoadFailed[slot] = TRUE; + nFramesUntilFailedLoad[slot] = 0; + m_nMissionAudioLoadingStatus[slot] = LOADING_STATUS_LOADED; + } + return; + default: + return; + case LOADING_STATUS_LOADED: + if (!m_bIsMissionAudioAllowedToPlay[slot]) + break; + if (g_bMissionAudioLoadFailed[slot]) { + if (m_bTimerJustReset) { + ClearMissionAudio(slot); + SampleManager.StopStreamedFile(slot + 1); + nFramesForPretendPlaying[slot] = 0; + nCheckPlayingDelay[slot] = 0; + nFramesUntilFailedLoad[slot] = 0; + } else if (!m_bIsPaused) { + if (++nFramesForPretendPlaying[slot] >= 90) { + m_nMissionAudioPlayStatus[slot] = PLAY_STATUS_FINISHED; + m_nMissionAudioSampleIndex[slot] = NO_SAMPLE; + } else + m_nMissionAudioPlayStatus[slot] = PLAY_STATUS_PLAYING; + } + break; + } + switch (m_nMissionAudioPlayStatus[slot]) { + case PLAY_STATUS_STOPPED: + if (MissionScriptAudioUsesPoliceChannel(m_nMissionAudioSampleIndex[slot])) + SetMissionScriptPoliceAudio(m_nMissionAudioSampleIndex[slot]); + else { + if (m_bIsPaused) + SampleManager.PauseStream(TRUE, slot + 1); + if (m_bIsMissionAudio2D[slot]) { + if (m_nMissionAudioSampleIndex[slot] == STREAMED_SOUND_MISSION_CAMERAL) + SampleManager.SetStreamedVolumeAndPan(MISSION_AUDIO_VOLUME, 0, TRUE, slot + 1); + else if (m_nMissionAudioSampleIndex[slot] == STREAMED_SOUND_MISSION_CAMERAR) + SampleManager.SetStreamedVolumeAndPan(MISSION_AUDIO_VOLUME, 127, TRUE, slot + 1); + else + SampleManager.SetStreamedVolumeAndPan(MISSION_AUDIO_VOLUME, 63, TRUE, slot + 1); + } else { + distSquared = GetDistanceSquared(m_vecMissionAudioPosition[slot]); + if (distSquared < SQR(MISSION_AUDIO_MAX_DIST)) { + if (distSquared > 0.0f) { + dist = Sqrt(distSquared); + Vol = ComputeVolume(MISSION_AUDIO_VOLUME, MISSION_AUDIO_MAX_DIST, dist); + } else + Vol = MISSION_AUDIO_VOLUME; + TranslateEntity(&m_vecMissionAudioPosition[slot], &vec); + pan = ComputePan(MISSION_AUDIO_MAX_DIST, &vec); + } else { + Vol = 0; + pan = 63; + } + SampleManager.SetStreamedVolumeAndPan(Vol, pan, TRUE, slot + 1); + } + SampleManager.StartPreloadedStreamedFile(slot + 1); + } + m_nMissionAudioPlayStatus[slot] = PLAY_STATUS_PLAYING; + nCheckPlayingDelay[slot] = 30; + if (m_nMissionAudioSampleIndex[slot] >= STREAMED_SOUND_MISSION_MOB_01A && m_nMissionAudioSampleIndex[slot] <= STREAMED_SOUND_MISSION_MOB_99A) + m_bIsMissionAudioPhoneCall[slot] = TRUE; + break; + case PLAY_STATUS_PLAYING: + if (m_bTimerJustReset) { + ClearMissionAudio(slot); + SampleManager.StopStreamedFile(slot + 1); + return; + } + if (MissionScriptAudioUsesPoliceChannel(m_nMissionAudioSampleIndex[slot])) { + if (!m_bIsPaused) { + if (nCheckPlayingDelay[slot] > 0) { + nCheckPlayingDelay[slot]--; + } else if ((g_bMissionAudioLoadFailed[slot] && m_nMissionAudioFramesToPlay[slot]-- == 0) || GetMissionScriptPoliceAudioPlayingStatus() == PLAY_STATUS_FINISHED) { + m_nMissionAudioPlayStatus[slot] = PLAY_STATUS_FINISHED; + if (m_nMissionAudioSampleIndex[slot] >= STREAMED_SOUND_MISSION_MOB_01A && m_nMissionAudioSampleIndex[slot] <= STREAMED_SOUND_MISSION_MOB_99A) + m_bIsMissionAudioPhoneCall[slot] = FALSE; + m_nMissionAudioSampleIndex[slot] = NO_SAMPLE; + SampleManager.StopStreamedFile(slot + 1); + m_nMissionAudioFramesToPlay[slot] = 0; + } + } + } else if (m_bIsMissionAudioPlaying[slot]) { + if (!SampleManager.IsStreamPlaying(slot + 1) && !m_bIsPaused && !m_bWasPaused) { + if (m_nMissionAudioSampleIndex[slot] == STREAMED_SOUND_MISSION_ROK2_01) + m_nMissionAudioPlayStatus[slot] = PLAY_STATUS_STOPPED; + else { + m_nMissionAudioPlayStatus[slot] = PLAY_STATUS_FINISHED; + if (m_nMissionAudioSampleIndex[slot] >= STREAMED_SOUND_MISSION_MOB_01A && m_nMissionAudioSampleIndex[slot] <= STREAMED_SOUND_MISSION_MOB_99A) + m_bIsMissionAudioPhoneCall[slot] = FALSE; + m_nMissionAudioSampleIndex[slot] = NO_SAMPLE; + SampleManager.StopStreamedFile(slot + 1); + m_nMissionAudioFramesToPlay[slot] = 0; + } + } else { + if (m_bIsPaused) + SampleManager.PauseStream(TRUE, slot + 1); + else { + SampleManager.PauseStream(FALSE, slot + 1); + if (!m_bIsMissionAudio2D[slot]) { + distSquared = GetDistanceSquared(m_vecMissionAudioPosition[slot]); + if (distSquared < SQR(MISSION_AUDIO_MAX_DIST)) { + // BUG? Why MAX_VOLUME instead of MISSION_AUDIO_VOLUME? + if (distSquared > 0.0f) { + dist = Sqrt(distSquared); + Vol = ComputeVolume(MAX_VOLUME, MISSION_AUDIO_MAX_DIST, dist); + } else + Vol = MAX_VOLUME; + TranslateEntity(&m_vecMissionAudioPosition[slot], &vec); + pan = ComputePan(MISSION_AUDIO_MAX_DIST, &vec); + } else { + Vol = 0; + pan = 63; + } + SampleManager.SetStreamedVolumeAndPan(Vol, pan, TRUE, slot + 1); + } + } + } + } else { + if (m_bIsPaused) + break; + if (nCheckPlayingDelay[slot]-- > 0) { + if (!SampleManager.IsStreamPlaying(slot + 1)) + break; + nCheckPlayingDelay[slot] = 0; + } + m_bIsMissionAudioPlaying[slot] = TRUE; + } + break; + default: + break; + } + break; + } + } +} + +void +cAudioManager::ProcessMissionAudio() +{ + if (!m_bIsInitialised) return; + + for (int i = 0; i < MISSION_AUDIO_SLOTS; i++) + ProcessMissionAudioSlot(i); + + if (m_bIsMissionAudioPhoneCall[0] || m_bIsMissionAudioPhoneCall[1]) + m_nGlobalSfxVolumeMultiplier = 64; + else if (m_nGlobalSfxVolumeMultiplier < 127) { + m_nGlobalSfxVolumeMultiplier += 5; + if (m_nGlobalSfxVolumeMultiplier > 127) + m_nGlobalSfxVolumeMultiplier = 127; + } +} +#pragma endregion All the mission audio stuff diff --git a/src/miami/audio/AudioManager.cpp b/src/miami/audio/AudioManager.cpp new file mode 100644 index 00000000..51f1946f --- /dev/null +++ b/src/miami/audio/AudioManager.cpp @@ -0,0 +1,1434 @@ +#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 "ZoneCull.h" + +cAudioManager AudioManager; + +#define SPEED_OF_SOUND 343.f +#define TIME_SPENT 40 + +cAudioManager::cAudioManager() +{ + m_bIsInitialised = FALSE; + m_bIsSurround = TRUE; + m_nChannelOffset = 0; + 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] = ARRAY_SIZE(m_aAudioEntityOrderList); + } + + 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, 4, 2, 5, 5, 3, 5, 2, 2, 1, 1, 3, 1, 3, 3, 1, 1, 1, 1, 4, 4, 4, 3, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 3, 4, 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, 0, 0, 0, 1, 2, 2, 0, 0, 2, 3, 3, 3, 5, 1, 1, + 1, 1, 1, 2, 2, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 7, 1, 4, 3, 4, 2, 2, 2, 3, 1, 2, 1, 3, 5, 3, 4, 6, 4, 6, 3, 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, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 0, 0, 0, 0, 0, 3, 3, 1, 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::SetMP3BoostVolume(uint8 volume) +{ + SampleManager.SetMP3BoostVolume(volume); +} + +void +cAudioManager::SetEffectsFadeVol(uint8 volume) +{ + SampleManager.SetEffectsFadeVolume(volume); +} + +void +cAudioManager::SetMusicFadeVol(uint8 volume) +{ + SampleManager.SetMusicFadeVolume(volume); +} + +void +cAudioManager::SetOutputMode(bool8 surround) +{ + // on ps2 this calls another method of cAudioManager to set DTS mode on or off +} + +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(0); + ClearMissionAudio(1); + SampleManager.StopChannel(CHANNEL_POLICE_RADIO); + SampleManager.SetEffectsFadeVolume(0); + SampleManager.SetMusicFadeVolume(0); + MusicManager.ResetMusicAfterReload(); + m_bIsPlayerShutUp = FALSE; +#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::AutoDetect3DProviders() +{ +#ifdef EXTERNAL_3D_SOUND + if (m_bIsInitialised) + return SampleManager.AutoDetect3DProviders(); +#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) { + m_nActiveSamples = SampleManager.GetMaximumSupportedChannels(); + if (m_nActiveSamples > 1) + m_nActiveSamples--; + } + 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) { +#ifdef GTA_PS2 + if (m_bIsSurround) { + for (uint32 i = 0; i < NUM_CHANNELS_DTS_GENERIC; i++) + SampleManager.StopChannel(i); + + SampleManager.SetChannelFrequency(CHANNEL_DTS_POLICE_RADIO, 0); + SampleManager.SetChannelFrequency(CHANNEL_DTS_MISSION_AUDIO_1, 0); + SampleManager.SetChannelFrequency(CHANNEL_DTS_MISSION_AUDIO_2, 0); + SampleManager.SetChannelFrequency(CHANNEL_DTS_PLAYER_VEHICLE_ENGINE, 0); + } else { + for (uint32 i = 0; i < NUM_CHANNELS_GENERIC; i++) + SampleManager.StopChannel(i); + + SampleManager.SetChannelFrequency(CHANNEL_POLICE_RADIO, 0); + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO_1, 0); + SampleManager.SetChannelFrequency(CHANNEL_MISSION_AUDIO_2, 0); + SampleManager.SetChannelFrequency(CHANNEL_PLAYER_VEHICLE_ENGINE, 0); + } +#else + for (uint32 i = 0; i < NUM_CHANNELS; i++) + SampleManager.StopChannel(i); +#endif + 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; +#ifdef AUDIO_REVERB + if(m_bIsSurround) ProcessReverb(); +#endif + 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; +} + +uint8 +cAudioManager::ComputeVolume(uint8 emittingVolume, float maxDistance, float distance) +{ + float minDistance; + uint8 newEmittingVolume; + + if (maxDistance <= 0.0f) + return 0; + + minDistance = maxDistance / 5.0f; + if (minDistance > distance) + return emittingVolume; + + newEmittingVolume = emittingVolume * SQR((maxDistance - minDistance - (distance - minDistance)) + / (maxDistance - minDistance)); + return Min(127, newEmittingVolume); +} + +void +cAudioManager::TranslateEntity(Const CVector *in, CVector *out) +{ + *out = MultiplyInverse(TheCamera.GetMatrix(), *in); +} + +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 +cAudioManager::ComputeFrontRearMix(float dist, CVector *vec) +{ + int32 index = vec->y / (dist / 64.0f); + index = Min(63, ABS(index)); + + if (vec->y > 0.0f) + return Max(0, 63 - (int8)PanTable[index]); + return Min(127, PanTable[index] + 63); +} + +int32 +cAudioManager::ComputePan(float dist, CVector *vec) +{ + int32 index = vec->x / (dist / 64.0f); + index = Min(63, ABS(index)); + + if (vec->x > 0.0f) + 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]++; + m_sQueueSample.m_nFinalPriority = finalPriority; + m_sQueueSample.m_bIsPlayingFinished = FALSE; +#ifdef AUDIO_REFLECTIONS + if (m_sQueueSample.m_bIs2D || CCullZones::InRoomForAudio()) { + 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_bIsSurround && m_sQueueSample.m_bIs2D ) + m_sQueueSample.m_nFrontRearPan = 30; +#ifdef AUDIO_REVERB + if (!m_bDynamicAcousticModelingStatus) + m_sQueueSample.m_bReverb = FALSE; +#endif +#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() +{ +#ifdef FIX_BUGS + uint32 oldFreq = 0; +#else + uint32 oldFreq; +#endif + float reflectionDistance; + int32 noise; + uint8 emittingVolume; + + uint32 oldCounter = m_sQueueSample.m_nCounter; + float oldDist = m_sQueueSample.m_fDistance; + CVector oldPos = m_sQueueSample.m_vecPos; +#ifndef USE_TIME_SCALE_FOR_AUDIO + if ( CTimer::GetIsSlowMotionActive() ) { + emittingVolume = m_sQueueSample.m_nVolume; + oldFreq = m_sQueueSample.m_nFrequency; + } else +#endif + emittingVolume = (9 * m_sQueueSample.m_nVolume) >> 4; + m_sQueueSample.m_MaxDistance /= 2.0f; + + uint32 halfOldFreq = oldFreq >> 1; + + for (uint32 i = 0; i < ARRAY_SIZE(m_afReflectionsDistances); i++) { +#ifndef USE_TIME_SCALE_FOR_AUDIO + if ( CTimer::GetIsSlowMotionActive() ) + m_afReflectionsDistances[i] = (m_anRandomTable[i % 4] % 3) * 100.0f / 8.0f; +#endif + + reflectionDistance = m_afReflectionsDistances[i]; + if (reflectionDistance > 0.0f && reflectionDistance < 100.0f && reflectionDistance < m_sQueueSample.m_MaxDistance) { +#ifndef USE_TIME_SCALE_FOR_AUDIO + m_sQueueSample.m_nReflectionDelay = CTimer::GetIsSlowMotionActive() ? (reflectionDistance * 800.0f / 1029.0f) : (reflectionDistance * 500.0f / 1029.0f); +#else + m_sQueueSample.m_nReflectionDelay = reflectionDistance * 500.0f / 1029.0f; +#endif + if (m_sQueueSample.m_nReflectionDelay > 3) { + 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) { +#ifndef USE_TIME_SCALE_FOR_AUDIO + if ( CTimer::GetIsSlowMotionActive() ) { + m_sQueueSample.m_nFrequency = halfOldFreq + ((halfOldFreq * i) / ARRAY_SIZE(m_afReflectionsDistances)); + } else +#endif + { + 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(); + } + } + } + } + m_sQueueSample.m_vecPos = oldPos; + m_sQueueSample.m_fDistance = oldDist; +} + +void +cAudioManager::UpdateReflections() +{ + CVector camPos; + CColPoint colpoint; + CEntity *ent; + +#if GTA_VERSION < GTAVC_PC_10 + 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; + } +#else + if (m_FrameCounter % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[0] = camPos; + m_avecReflectionsPos[0].y += 100.f; + 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] = 100.0f; + } else if ((m_FrameCounter + 1) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[1] = camPos; + m_avecReflectionsPos[1].y -= 100.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] = 100.0f; + } else if ((m_FrameCounter + 2) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[2] = camPos; + m_avecReflectionsPos[2].x -= 100.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] = 100.0f; + } else if ((m_FrameCounter + 3) % 8 == 0) { + camPos = TheCamera.GetPosition(); + m_avecReflectionsPos[3] = camPos; + m_avecReflectionsPos[3].x += 100.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] = 100.0f; + } else if ((m_FrameCounter + 4) % 8 == 0) { + camPos = TheCamera.GetPosition(); + camPos.y += 1.0f; + m_avecReflectionsPos[4] = camPos; + m_avecReflectionsPos[4].z += 100.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] = 100.0f; + } else if ((m_FrameCounter + 5) % 8 == 0) { + camPos = TheCamera.GetPosition(); + camPos.y -= 1.0f; + m_avecReflectionsPos[5] = camPos; + m_avecReflectionsPos[5].z += 100.0f; + if (CWorld::ProcessVerticalLine(camPos, m_avecReflectionsPos[5].z, colpoint, ent, true, false, false, false, true, false, nil)) + m_afReflectionsDistances[5] = colpoint.point.z - camPos.z; + else + m_afReflectionsDistances[5] = 100.0f; + } else if ((m_FrameCounter + 6) % 8 == 0) { + camPos = TheCamera.GetPosition(); + camPos.x -= 1.0f; + m_avecReflectionsPos[6] = camPos; + m_avecReflectionsPos[6].z += 100.0f; + if (CWorld::ProcessVerticalLine(camPos, m_avecReflectionsPos[6].z, colpoint, ent, true, false, false, false, true, false, nil)) + m_afReflectionsDistances[6] = colpoint.point.z - camPos.z; + else + m_afReflectionsDistances[6] = 100.0f; + } else if ((m_FrameCounter + 7) % 8 == 0) { + camPos = TheCamera.GetPosition(); + camPos.x += 1.0f; + m_avecReflectionsPos[7] = camPos; + m_avecReflectionsPos[7].z += 100.0f; + if (CWorld::ProcessVerticalLine(camPos, m_avecReflectionsPos[7].z, colpoint, ent, true, false, false, false, true, false, nil)) + m_afReflectionsDistances[7] = colpoint.point.z - camPos.z; + else + m_afReflectionsDistances[7] = 100.0f; + } +#endif +} +#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 + 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; + + bool8 isPhoneCall; + uint8 channelOffset = 0; + +#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 (m_FrameCounter & 1) + flag = !!(j & 1); + else + flag = !(j & 1); + + if (flag && !SampleManager.GetChannelUsedFlag(j)) { + 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; + } + if (sample.m_nFramesToPlay == 0) + sample.m_nFramesToPlay = 1; + } + 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, emittingVol); +#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); +#else + if (sample.m_nVolume != m_asActiveSamples[j].m_nVolume) { + vol = Clamp2((int8)sample.m_nVolume, (int8)m_asActiveSamples[j].m_nVolume, 10); +#endif + emittingVol = m_bDoubleVolume ? 2 * Min(63, vol) : vol; + + isPhoneCall = FALSE; + for (int32 k = 0; k < MISSION_AUDIO_SLOTS; k++) { + if (m_bIsMissionAudioPhoneCall[k]) { + isPhoneCall = TRUE; + break; + } + } + if (isPhoneCall) { + emittingVol = (emittingVol * m_nGlobalSfxVolumeMultiplier) / 127; + } else { + if (m_nGlobalSfxVolumeMultiplier < 127) + emittingVol = (emittingVol * m_nGlobalSfxVolumeMultiplier) / 127; + } + +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannelEmittingVolume(j, emittingVol); + m_asActiveSamples[j].m_nEmittingVolume = vol; +#else + SampleManager.SetChannelVolume(j, emittingVol); + m_asActiveSamples[j].m_nVolume = vol; +#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 + } +#if !defined(GTA_PS2) || defined(AUDIO_REVERB) + SampleManager.SetChannelReverbFlag(j, sample.m_bReverb); +#endif + break; //continue for i + } + sample.m_bIsBeingPlayed = FALSE; + m_asActiveSamples[j].m_bIsBeingPlayed = FALSE; + //continue for j + } + } + } + } + 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++) { + uint8 k = (j + m_nChannelOffset) % m_nActiveSamples; + if (!m_asActiveSamples[k].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[k], &sample, sizeof(tSound)); + if (!m_asActiveSamples[k].m_bIs2D) { + TranslateEntity(&m_asActiveSamples[k].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(k, m_asActiveSamples[k].m_nSampleIndex, m_asActiveSamples[k].m_nBankIndex); +#else + if (SampleManager.InitialiseChannel(k, m_asActiveSamples[k].m_nSampleIndex, m_asActiveSamples[k].m_nBankIndex)) { +#endif +#ifdef USE_TIME_SCALE_FOR_AUDIO + SampleManager.SetChannelFrequency(k, m_asActiveSamples[k].m_nFrequency * timeScale); +#else + SampleManager.SetChannelFrequency(k, m_asActiveSamples[k].m_nFrequency); +#endif + isPhoneCall = FALSE; + for (int32 l = 0; l < MISSION_AUDIO_SLOTS; l++) { + if (m_bIsMissionAudioPhoneCall[l]) { + isPhoneCall = TRUE; + break; + } + } + if (!isPhoneCall || m_asActiveSamples[k].m_bIs2D) { + if (m_nGlobalSfxVolumeMultiplier < 127) + emittingVol = (emittingVol * m_nGlobalSfxVolumeMultiplier) / 127; + vol = emittingVol; + } else { + vol = (emittingVol * m_nGlobalSfxVolumeMultiplier) / 127; + } +#ifdef EXTERNAL_3D_SOUND + SampleManager.SetChannelEmittingVolume(k, vol); +#else + SampleManager.SetChannelVolume(j, emittingVol); + SampleManager.SetChannelPan(j, m_asActiveSamples[j].m_nPan); +#endif +#ifndef GTA_PS2 + SampleManager.SetChannelLoopPoints(k, m_asActiveSamples[k].m_nLoopStart, m_asActiveSamples[k].m_nLoopEnd); +#endif + SampleManager.SetChannelLoopCount(k, m_asActiveSamples[k].m_nLoopCount); +#if !defined(GTA_PS2) || defined(AUDIO_REVERB) + SampleManager.SetChannelReverbFlag(k, m_asActiveSamples[k].m_bReverb); +#endif +#ifdef EXTERNAL_3D_SOUND + if (m_asActiveSamples[k].m_bIs2D) { + uint8 offset = m_asActiveSamples[k].m_nPan; + if (offset == 63) + x = 0.0f; + else if (offset >= 63) + x = (offset - 63) * 1000.0f / 63; + else + x = -(63 - offset) * 1000.0f / 63; //same like line below + usedX = x; + usedY = 0.0f; + usedZ = 0.0f; + m_asActiveSamples[k].m_MaxDistance = 100000.0f; + } else { + usedX = position.x; + usedY = position.y; + usedZ = position.z; + } + SampleManager.SetChannel3DPosition(k, usedX, usedY, usedZ); + SampleManager.SetChannel3DDistances(k, m_asActiveSamples[k].m_MaxDistance, 0.25f * m_asActiveSamples[k].m_MaxDistance); +#endif + SampleManager.StartChannel(k); + } + m_asActiveSamples[k].m_bIsBeingPlayed = TRUE; + channelOffset++; + sample.m_bIsBeingPlayed = TRUE; + sample.m_nVolumeChange = -1; + break; + } + } + } + } + } + +#ifdef GTA_PS2 + m_nChannelOffset += channelOffset; +#endif + m_nChannelOffset %= m_nActiveSamples; + +#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; + 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); +#ifdef AUDIO_REVERB + m_asActiveSamples[i].m_bReverb = FALSE; +#endif // AUDIO_REVERB +#ifdef AUDIO_REFLECTIONS + m_asActiveSamples[i].m_nReflectionDelay = 0; + m_asActiveSamples[i].m_bReflections = FALSE; +#endif // AUDIO_REFLECTIONS + } +} + +void +cAudioManager::GenerateIntegerRandomNumberTable() +{ + for (uint32 i = 0; i < ARRAY_SIZE(m_anRandomTable); i++) + m_anRandomTable[i] = myrand(); +} + +#ifdef GTA_PS2 +void +cAudioManager::LoadBankIfNecessary(uint8 bank) +{ + if(!SampleManager.IsSampleBankLoaded(bank)) + SampleManager.LoadSampleBank(bank); +} +#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/miami/audio/AudioManager.h b/src/miami/audio/AudioManager.h new file mode 100644 index 00000000..49edcfec --- /dev/null +++ b/src/miami/audio/AudioManager.h @@ -0,0 +1,649 @@ +#pragma once + +#include "audio_enums.h" +#include "AudioCollision.h" +#include "PolRadio.h" +#include "VehicleModelInfo.h" +#include "Vehicle.h" + +class tSound +{ +public: + int32 m_nEntityIndex; // audio entity index + 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 + 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 + 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) + 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 +#if !defined(GTA_PS2) || defined(AUDIO_REVERB) // GTA_PS2 because this field exists on mobile but not on PS2 + bool8 m_bReverb; // Toggles reverb effect +#endif +#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% + uint8 m_nFrontRearPan; // Used on PS2 for surround panning +#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 + 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, 96); + +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; +#ifdef GTA_PC + bool8 m_bDelay; + uint32 m_nDelayTimer; +#endif + + 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, 0x490); + +#define MISSION_AUDIO_SLOTS (2) + +// 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: + int32 m_VehicleType; + bool8 m_bDistanceCalculated; + float m_fDistance; + CVehicle *m_pVehicle; + cTransmission *m_pTransmission; + uint32 m_nIndex; + float m_fVelocityChange; + + cVehicleParams() + { + m_VehicleType = -1; + m_bDistanceCalculated = false; + m_fDistance = 0.0f; + m_pVehicle = nil; + m_pTransmission = nil; + m_nIndex = 0; + m_fVelocityChange = 0.0f; + } +}; + +VALIDATE_SIZE(cVehicleParams, 0x1C); + +#if GTA_VERSION < GTAVC_PC_10 +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, +}; +#else +enum { + REFLECTION_NORTH = 0, + REFLECTION_SOUTH, + REFLECTION_WEST, + REFLECTION_EAST, + REFLECTION_CEIL_NORTH, + REFLECTION_CEIL_SOUTH, + REFLECTION_CEIL_WEST, + REFLECTION_CEIL_EAST, + MAX_REFLECTIONS, +}; +#endif + +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; // used on PS2 + bool8 m_bReduceReleasingPriority; + uint8 m_nActiveSamples; + bool8 m_bDoubleVolume; // unused + bool8 m_bDynamicAcousticModelingStatus; + uint8 m_nChannelOffset; + 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; + + // miami + bool8 m_bIsPlayerShutUp; + uint8 m_nPlayerMood; + uint32 m_nPlayerMoodTimer; + uint32 field_4B38_vc; + bool8 m_bGenericSfx; + + 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_nEscalatorEntity; + int32 m_nExtraSoundsEntity; +#ifdef GTA_BRIDGE + int32 m_nBridgeEntity; +#endif + + // Mission audio stuff + // So instead of making an array of struct they've added [MISSION_AUDIO_SLOTS] to every field... + // Only someone with a VERY EXTRAORDINARY mind could have come up with that + CVector m_vecMissionAudioPosition[MISSION_AUDIO_SLOTS]; + bool8 m_bIsMissionAudio2D[MISSION_AUDIO_SLOTS]; + uint32 m_nMissionAudioSampleIndex[MISSION_AUDIO_SLOTS]; + uint8 m_nMissionAudioLoadingStatus[MISSION_AUDIO_SLOTS]; + uint8 m_nMissionAudioPlayStatus[MISSION_AUDIO_SLOTS]; + bool8 m_bIsMissionAudioPlaying[MISSION_AUDIO_SLOTS]; + int32 m_nMissionAudioFramesToPlay[MISSION_AUDIO_SLOTS]; // possibly unsigned + bool8 m_bIsMissionAudioAllowedToPlay[MISSION_AUDIO_SLOTS]; + bool8 m_bIsMissionAudioPhoneCall[MISSION_AUDIO_SLOTS]; + uint8 m_nGlobalSfxVolumeMultiplier; // used to lower sfx volume during phone calls + + 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); // inlined in vc + 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 SetMP3BoostVolume(uint8 volume); + void SetEffectsFadeVol(uint8 volume); + void SetMusicFadeVol(uint8 volume); + void SetOutputMode(bool8 surround); + void ResetTimers(uint32 time); + void DestroyAllGameCreatedEntities(); + +#ifdef GTA_PC + uint8 GetNum3DProvidersAvailable(); + char *Get3DProviderName(uint8 id); + int8 GetCurrent3DProviderIndex(); + int8 AutoDetect3DProviders(); + 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(); + uint8 ComputeVolume(uint8 emittingVolume, float maxDistance, float distance); + void TranslateEntity(Const CVector *v1, CVector *v2); + int32 ComputeFrontRearMix(float, CVector *); + int32 ComputePan(float, CVector *); + uint32 ComputeDopplerEffectedFrequency(uint32 oldFreq, float position1, float position2, float speedMultiplier); + int32 RandomDisplacement(uint32 seed); + void InterrogateAudioEntities(); // inlined + void AddSampleToRequestedQueue(); + void AddDetailsToRequestedOrderList(uint8 sample); // inlined in vc +#ifdef AUDIO_REFLECTIONS + void AddReflectionsToRequestedQueue(); + void UpdateReflections(); +#endif + void AddReleasingSounds(); + void ProcessActiveQueues(); + void ClearRequestedQueue(); // inlined in vc + void ClearActiveSamples(); + void GenerateIntegerRandomNumberTable(); +#ifdef GTA_PS2 + void 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(); // inlined + uint8 ComputeEmittingVolume(uint8 emittingVolume, float maxDistance, float distance); // inlined +#endif + + // audio logic + void PreInitialiseGameSpecificSetup(); + void PostInitialiseGameSpecificSetup(); + void PreTerminateGameSpecificShutdown(); + void PostTerminateGameSpecificShutdown(); + void ResetAudioLogicTimers(uint32 timer); + void ProcessReverb(); + float GetDistanceSquared(const CVector &v); // inlined in vc + void CalculateDistance(bool8 &condition, float dist); + CVehicle *FindVehicleOfPlayer(); + void ProcessSpecial(); + void ProcessEntity(int32 sound); + void ProcessPhysical(int32 id); + + // vehicles + void ProcessVehicle(CVehicle *vehicle); + bool8 ProcessCarHeli(cVehicleParams ¶ms); + void ProcessRainOnVehicle(cVehicleParams ¶ms); + bool8 ProcessReverseGear(cVehicleParams ¶ms); + void ProcessModelHeliVehicle(cVehicleParams ¶ms); + void ProcessModelVehicle(cVehicleParams ¶ms); + bool8 ProcessVehicleFlatTyre(cVehicleParams ¶ms); + bool8 ProcessVehicleRoadNoise(cVehicleParams ¶ms); + bool8 ProcessWetRoadNoise(cVehicleParams ¶ms); + bool8 ProcessVehicleEngine(cVehicleParams ¶ms); + void UpdateGasPedalAudio(CVehicle *veh, int vehType); + 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, CVehicle *veh); + bool8 ProcessVehicleSkidding(cVehicleParams ¶ms); + float GetVehicleDriveWheelSkidValue(CVehicle *veh, tWheelState wheelState, float gasPedalAudio, cTransmission *transmission, float velocityChange); + float GetVehicleNonDriveWheelSkidValue(CVehicle *veh, tWheelState wheelState, cTransmission *transmission, float velocityChange); + bool8 ProcessVehicleHorn(cVehicleParams ¶ms); + bool8 UsesSiren(cVehicleParams ¶ms); + bool8 UsesSirenSwitching(cVehicleParams ¶ms); + bool8 ProcessVehicleSirenOrAlarm(cVehicleParams ¶ms); + bool8 UsesReverseWarning(uint32 model); + bool8 ProcessVehicleReverseWarning(cVehicleParams ¶ms); + bool8 ProcessVehicleDoors(cVehicleParams ¶ms); + bool8 ProcessAirBrakes(cVehicleParams ¶ms); + bool8 HasAirBrakes(uint32 model); + bool8 ProcessEngineDamage(cVehicleParams ¶ms); + bool8 ProcessCarBombTick(cVehicleParams ¶ms); + void ProcessVehicleOneShots(cVehicleParams ¶ms); +#ifdef GTA_TRAIN + bool8 ProcessTrainNoise(cVehicleParams ¶ms); +#endif + bool8 ProcessBoatEngine(cVehicleParams ¶ms); + bool8 ProcessBoatMovingOverWater(cVehicleParams ¶ms); + void ProcessPlane(cVehicleParams ¶ms); + void ProcessJumbo(cVehicleParams ¶ms); + void ProcessJumboTaxi(); + void ProcessJumboAccel(CPlane *plane); + void ProcessJumboTakeOff(CPlane *plane); + void ProcessJumboFlying(); + void ProcessJumboLanding(CPlane *plane); + void ProcessJumboDecel(CPlane *plane); + 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 in vc + + // peds + void ProcessPed(CPhysical *ped); + void ProcessPedOneShots(cPedParams ¶ms); + void SetPedTalkingStatus(CPed *ped, bool8 status); + void SetPlayersMood(uint8 mood, uint32 time); + void ProcessPlayerMood(); + + // ped comments + void SetupPedComments(cPedParams ¶ms, uint16 sound); + uint32 GetPedCommentSfx(CPed *ped, uint16 sound); + void GetPhrase(uint32 &phrase, uint32 &prevPhrase, uint32 sample, uint32 maxOffset); + uint32 GetPlayerTalkSfx(CPed *ped, uint16 sound); + uint32 GetGenericMaleTalkSfx(CPed *ped, uint16 sound); // inlined in vc + uint32 GetGenericFemaleTalkSfx(CPed *ped, uint16 sound); // inlined in vc + uint32 GetDefaultTalkSfx(CPed *ped, uint16 sound); + uint32 GetCopTalkSfx(CPed *ped, uint16 sound); + uint32 GetSwatTalkSfx(CPed *ped, uint16 sound); + uint32 GetFBITalkSfx(CPed *ped, uint16 sound); + uint32 GetArmyTalkSfx(CPed *ped, uint16 sound); + uint32 GetMedicTalkSfx(CPed *ped, uint16 sound); + uint32 GetFiremanTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYG1TalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYG2TalkSfx(CPed *ped, uint16 sound); + uint32 GetHFYSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetHFOSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetHMYSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetHMOSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetHFYRITalkSfx(CPed *ped, uint16 sound); + uint32 GetHFORITalkSfx(CPed *ped, uint16 sound); + uint32 GetHMYRITalkSfx(CPed *ped, uint16 sound); + uint32 GetHMORITalkSfx(CPed *ped, uint16 sound); + uint32 GetHFYBETalkSfx(CPed *ped, uint16 sound); + uint32 GetHFOBETalkSfx(CPed *ped, uint16 sound); + uint32 GetHMYBETalkSfx(CPed *ped, uint16 sound); + uint32 GetHMOBETalkSfx(CPed *ped, uint16 sound); + uint32 GetHFYBUTalkSfx(CPed *ped, uint16 sound); + uint32 GetHFYMDTalkSfx(CPed *ped, uint16 sound); + uint32 GetHFYCGTalkSfx(CPed *ped, uint16 sound); + uint32 GetHFYPRTalkSfx(CPed *ped, uint16 sound); + uint32 GetHFOTRTalkSfx(CPed *ped, uint16 sound); + uint32 GetHMOTRTalkSfx(CPed *ped, uint16 sound); + uint32 GetHMOCATalkSfx(CPed *ped, uint16 sound); + uint32 GetBMYCRTalkSfx(CPed *ped, uint16 sound); + uint32 GetBFYSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetBFOSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetBMYSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetBMOSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetBFYRITalkSfx(CPed *ped, uint16 sound); + uint32 GetBFORITalkSfx(CPed *ped, uint16 sound); + uint32 GetBMYRITalkSfx(CPed *ped, uint16 sound); + uint32 GetBFYBETalkSfx(CPed *ped, uint16 sound); + uint32 GetBMYBETalkSfx(CPed *ped, uint16 sound); + uint32 GetBFOBETalkSfx(CPed *ped, uint16 sound); + uint32 GetBMOBETalkSfx(CPed *ped, uint16 sound); + uint32 GetBMYBUTalkSfx(CPed *ped, uint16 sound); + uint32 GetBFYPRTalkSfx(CPed *ped, uint16 sound); + uint32 GetBFOTRTalkSfx(CPed *ped, uint16 sound); + uint32 GetBMOTRTalkSfx(CPed *ped, uint16 sound); + uint32 GetBMYPITalkSfx(CPed *ped, uint16 sound); + uint32 GetBMYBBTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYCRTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYSKTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYSKTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFOSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMOSTTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYRITalkSfx(CPed *ped, uint16 sound); + uint32 GetWFORITalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYRITalkSfx(CPed *ped, uint16 sound); + uint32 GetWMORITalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYBETalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYBETalkSfx(CPed *ped, uint16 sound); + uint32 GetWFOBETalkSfx(CPed *ped, uint16 sound); + uint32 GetWMOBETalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYCWTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYGOTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFOGOTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMOGOTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYLGTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYLGTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYBUTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYBUTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMOBUTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYPRTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFOTRTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMOTRTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYPITalkSfx(CPed *ped, uint16 sound); + uint32 GetWMOCATalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYSHTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFOSHTalkSfx(CPed *ped, uint16 sound); + uint32 GetJFOTOTalkSfx(CPed *ped, uint16 sound); + uint32 GetJMOTOTalkSfx(CPed *ped, uint16 sound); + uint32 GetHNTalkSfx(CPed *ped, uint16 sound); + uint32 GetBKTalkSfx(CPed *ped, uint16 sound); + uint32 GetCBTalkSfx(CPed *ped, uint16 sound); + uint32 GetSGTalkSfx(CPed *ped, uint16 sound); + uint32 GetCLTalkSfx(CPed *ped, uint16 sound); + uint32 GetGDTalkSfx(CPed *ped, uint16 sound); + uint32 GetPGTalkSfx(CPed *ped, uint16 sound); + uint32 GetViceWhiteTalkSfx(CPed *ped, uint16 sound); + uint32 GetViceBlackTalkSfx(CPed *ped, uint16 sound); + uint32 GetBMODKTalkSfx(CPed *ped, uint16 sound); + uint32 GetHMYAPTalkSfx(CPed *ped, uint16 sound); + uint32 GetWFYJGTalkSfx(CPed *ped, uint16 sound); + uint32 GetWMYJGTalkSfx(CPed *ped, uint16 sound); + uint32 GetSpecialCharacterTalkSfx(CPed *ped, int32 model, uint16 sound); + + void DebugPlayPedComment(int32 sound); + + // particles + void ProcessExplosions(int32 explosion); + void ProcessFires(int32 entity); + void ProcessWaterCannon(int32); + + // script objects + void ProcessScriptObject(int32 id); + void ProcessOneShotScriptObject(uint8 sound); + void ProcessLoopingScriptObject(uint8 sound); + + // misc + void ProcessWeather(int32 id); + void ProcessFrontEnd(); + //void ProcessCrane(); + void ProcessProjectiles(); + void ProcessEscalators(); + void ProcessExtraSounds(); + void ProcessGarages(); + void ProcessFireHydrant(); + +#ifdef GTA_BRIDGE + void ProcessBridge(); + void ProcessBridgeWarning(); + void ProcessBridgeMotor(); + void ProcessBridgeOneShots(); +#endif + + // mission audio + const char *GetMissionAudioLoadedLabel(uint8 slot); + bool8 MissionScriptAudioUsesPoliceChannel(uint32 soundMission); + void PreloadMissionAudio(uint8 slot, Const char *name); + uint8 GetMissionAudioLoadingStatus(uint8 slot); + void SetMissionAudioLocation(uint8 slot, float x, float y, float z); + void PlayLoadedMissionAudio(uint8 slot); + bool8 ShouldDuckMissionAudio(uint8 slot); + bool8 IsMissionAudioSamplePlaying(uint8 slot); + bool8 IsMissionAudioSampleFinished(uint8 slot); + void ClearMissionAudio(uint8 slot); // inlined in vc + void ProcessMissionAudioSlot(uint8 slot); + void ProcessMissionAudio(); + + // police radio + void InitialisePoliceRadioZones(); + void InitialisePoliceRadio(); + void ResetPoliceRadio(); + void SetMissionScriptPoliceAudio(uint32 sfx); // inlined and optimized + 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 in vc + + // 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 in vc + + float Sqrt(float v) const { return v <= 0.0f ? 0.0f : ::Sqrt(v); } +}; + +/* + 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 +#ifdef AUDIO_REVERB +#define SET_SOUND_REVERB(b) m_sQueueSample.m_bReverb = b +#else +#define SET_SOUND_REVERB(b) +#endif + +#if defined(AUDIO_MSS) && !defined(PS2_AUDIO_CHANNELS) +static_assert(sizeof(cAudioManager) == 0x5558, "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/miami/audio/AudioSamples.h b/src/miami/audio/AudioSamples.h new file mode 100644 index 00000000..365ca282 --- /dev/null +++ b/src/miami/audio/AudioSamples.h @@ -0,0 +1,10130 @@ +#pragma once + +#include "common.h" + +#define FIRST_PLAYER_COMMENT(e) PLAYER_COMMENTS_START, e = PLAYER_COMMENTS_START +#define LAST_PLAYER_COMMENT(e) e, PLAYER_COMMENTS_END = e + +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_CAR_HELI_MAI, // 8 + SFX_CAR_HELI_MAI2, // 9 + SFX_CAR_HELI_REA, // 10 + SFX_CAR_HELI_STA, // 11 + SFX_CAR_HELI_ROT, // 12 + SFX_CAR_HELI_FAR, // 13 + SFX_CAR_HELI_ROL, // 14 + + 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_REVERSE_GEAR, + SFX_REVERSE_GEAR_2, + SFX_CAR_STARTER, // 23 + SFX_ROAD_NOISE, // 24 + SFX_SKID, // 25 + SFX_GRAVEL_SKID, // 26 + SFX_POLICE_SIREN_SLOW, + SFX_SIREN_FAST, // 28 + SFX_AMBULANCE_SIREN_SLOW, + SFX_REVERSE_WARNING, + SFX_ICE_CREAM_TUNE, + SFX_AIR_BRAKES, // 32 + SFX_TYRE_BUMP, // 33 + SFX_TYRE_BURST_B, // 34 + SFX_TYRE_BURST, // 35 + SFX_TYRE_BURST_L, // 36 + SFX_PALM_TREE_LO, // 37 + SFX_BULLET_PASS_1, // 38 + SFX_BULLET_PASS_2, // 39 + SFX_SKATE_1, // 40 + SFX_SKATE_2, // 41 + SFX_FOOTSTEP_CONCRETE_1, + SFX_FOOTSTEP_CONCRETE_2, + SFX_FOOTSTEP_CONCRETE_3, + SFX_FOOTSTEP_CONCRETE_4, + SFX_FOOTSTEP_CONCRETE_5, + SFX_EXPLOSION_1, // 47 + SFX_EXPLOSION_2, // 48 + SFX_EXPLOSION_3, // 49 + SFX_COLT45_LEFT, // 50 + SFX_COLT45_RIGHT, // 51 + SFX_AK47_LEFT, // 52 + SFX_AK47_RIGHT, // 53 + SFX_UZI_LEFT, // 54 + SFX_UZI_RIGHT, // 55 + SFX_UZI_END_LEFT, // 56 + SFX_SNIPER_LEFT, // 57 + SFX_SNIPER_RIGHT, // 58 + SFX_ROCKET_LEFT, // 59 + SFX_ROCKET_RIGHT, // 60 + SFX_ROCKET_FLY, // 61 + SFX_FLAMETHROWER_LEFT, // 62 + SFX_FLAMETHROWER_RIGHT, // 63 + SFX_FLAMETHROWER_START_LEFT, // 64 + SFX_FLAMETHROWER_START_RIGHT, // 65 + SFX_SHOTGUN_LEFT, // 66 + SFX_SHOTGUN_RIGH, // 67 + SFX_M60_LEFT, // 68 + SFX_M60_RIGHT, // 69 + SFX_M60_TAIL_LEFT, // 70 + SFX_TEC_LEFT, // 71 + SFX_TEC_RIGHT, // 72 + SFX_TEC_TAIL, // 73 + SFX_RUGER_LEFT, // 74 + SFX_RUGER_RIGHT, // 75 + SFX_RUGER_TAIL, // 76 + SFX_PISTOL_RELOAD, // 77 + SFX_AK47_RELOAD, // 78 + SFX_ROCKET_RELOAD, // 79 + SFX_RIFLE_RELOAD, // 80 + SFX_GOLF_CLUB_SWING, // 81 + SFX_MINIGUN_FIRE_LEFT, // 82 + SFX_MINIGUN_FIRE_RIGHT, // 83 + SFX_MINIGUN_STOP, // 84 + SFX_SPAS12_LEFT, // 85 + SFX_SPAS12_RIGHT, // 86 + SFX_SPAS12_TAIL_LEFT, // 87 + SFX_PYTHON_LEFT, // 88 + SFX_PYTHON_RIGHT, // 89 + SFX_MP5_LEFT, // 90 + SFX_MP5_RIGHT, // 91 + SFX_COL_TARMAC_1, // 92 + SFX_COL_TARMAC_2, // 93 + SFX_COL_TARMAC_3, // 94 + SFX_COL_TARMAC_4, // 95 + SFX_COL_TARMAC_5, // 96 + 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, // 115 + SFX_COL_PED_2, // 116 + 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, // 127 + SFX_COL_VEG_2, // 128 + SFX_COL_VEG_3, // 129 + SFX_COL_VEG_4, // 130 + SFX_COL_VEG_5, // 131 + SFX_COL_CONTAINER_1, + SFX_COL_NEWS_VENDOR_1, + SFX_COL_NEWS_VENDOR_2, + SFX_COL_NEWS_VENDOR_3, + SFX_COL_CAR_1, // 136 + SFX_COL_CAR_2, // 137 + SFX_COL_CAR_3, // 138 + SFX_COL_CAR_4, // 139 + SFX_COL_CAR_5, // 140 + SFX_COL_CARDBOARD_1, + SFX_COL_CARDBOARD_2, + SFX_COL_GATE, // 143 + SFX_SCRAPE_CAR_1, // 144 + SFX_CRATE_SMASH, + SFX_GLASS_CRACK, // 146 + SFX_GLASS_SMASH, // 147 + SFX_GLASS_SHARD_1, + SFX_GLASS_SHARD_2, + SFX_GLASS_SHARD_3, + SFX_GLASS_SHARD_4, + SFX_PED_ON_FIRE, // 152 + SFX_CAR_ON_FIRE, // 153 + SFX_RAIN, // 154 + SFX_HURRICANE_MA, // 155 + SFX_BULLET_SHELL_HIT_GROUND_1, + SFX_BULLET_SHELL_HIT_GROUND_2, + SFX_BULLET_PED, // 158 + SFX_BULLET_CAR_1, // 159 + SFX_BULLET_CAR_2, // 160 + SFX_BULLET_CAR_3, // 161 + SFX_BULLET_WALL_1, // 162 + SFX_BULLET_WALL_2, // 163 + SFX_BULLET_WALL_3, // 164 + SFX_BAT_HIT_LEFT, // 165 + SFX_BAT_HIT_RIGH, // 166 + SFX_FIGHT_1, // 167 + SFX_FIGHT_2, // 168 + SFX_FIGHT_4, // 169 + SFX_FIGHT_5, // 170 + SFX_KNIFE_SWING, // 171 + SFX_KNIFE_SLASH, // 172 + SFX_KNIFE_STAB, // 173 + SFX_HAMMER_HIT_1, // 174 + SFX_HAMMER_HIT_2, // 175 + SFX_GARAGE_DOOR_LOOP, // 176 + SFX_COUNTDOWN, // 177 + SFX_ARM_BOMB, // 178 + SFX_POLICE_RADIO_CRACKLE, // 179 + + SFX_WEVE_GOT, + SFX_THERES, + SFX_RESPOND_TO, + SFX_A_10, + SFX_IN, + SFX_NORTH, + SFX_EAST, + SFX_SOUTH, + SFX_WEST, + SFX_CENTRAL, + SFX_POLICE_RADIO_MESSAGE_NOISE_1, + SFX_POLICE_RADIO_SUSPECT, + SFX_POLICE_RADIO_LAST_SEEN, + SFX_POLICE_RADIO_ON_FOOT, + SFX_POLICE_RADIO_IN_A, + SFX_POLICE_RADIO_DARK, + SFX_POLICE_RADIO_LIGHT, + SFX_POLICE_RADIO_BRIGHT, + + 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_POLICE_RADIO_VICE_CITY, + SFX_POLICE_RADIO_VICE_CITY_BEACH, + SFX_POLICE_RADIO_VICE_CITY_MAINLAND, + SFX_POLICE_RADIO_OCEAN_BEACH, //??? + SFX_POLICE_RADIO_WASHINGTON_BEACH, + SFX_POLICE_RADIO_VICE_POINT, + SFX_POLICE_RADIO_LEAF_LINKS, + SFX_POLICE_RADIO_STARFISH_ISLAND, //??????????? + SFX_POLICE_RADIO_VICEPORT, + SFX_POLICE_RADIO_LITTLE_HAVANA, + SFX_POLICE_RADIO_LITTLE_HAITI, + SFX_POLICE_RADIO_PRAWN_ISLAND, //??????????? IS THAT HOW SHE PRONOUNCES ISLAND? + SFX_POLICE_RADIO_DOWNTOWN, + SFX_POLICE_RADIO_ESCOBAR_INTERNATIONAL, + 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_AMBULANCE, + SFX_POLICE_RADIO_TUDOOR, + SFX_POLICE_RADIO_TRUCK, + SFX_POLICE_RADIO_FIRE_TRUCK, + SFX_POLICE_RADIO_PICKUP, + SFX_POLICE_RADIO_POLICE_CAR, + SFX_POLICE_RADIO_BOAT, + SFX_POLICE_RADIO_BUGGY, + SFX_POLICE_RADIO_BUS, + SFX_POLICE_RADIO_COACH, + SFX_POLICE_RADIO_CRUISER, + SFX_POLICE_RADIO_DINGHY, + SFX_POLICE_RADIO_GARBAGE_TRUCK, + SFX_POLICE_RADIO_GOLF_CART, + SFX_POLICE_RADIO_HEARSE, + SFX_POLICE_RADIO_HELICOPTER, + SFX_POLICE_RADIO_ICE_CREAM_VAN, + SFX_POLICE_RADIO_LOWRIDER, + SFX_POLICE_RADIO_MOPED, + SFX_POLICE_RADIO_MOTOBIKE, + SFX_POLICE_RADIO_OFFROAD, + SFX_POLICE_RADIO_PLANE, + SFX_POLICE_RADIO_RIG, + SFX_POLICE_RADIO_SEDAN, + SFX_POLICE_RADIO_SPEEDBOAT, + SFX_POLICE_RADIO_SPORTS_CAR, + SFX_POLICE_RADIO_STATION_WAGON, + SFX_POLICE_RADIO_STRETCH, + SFX_POLICE_RADIO_SWAT_VAN, + SFX_POLICE_RADIO_TANK, + SFX_POLICE_RADIO_TAXI, + SFX_POLICE_RADIO_VAN, + + SFX_HELI_1, // 198 + SFX_PHONE_RING, // 199 + SFX_CAR_REV_1, // PONT + SFX_CAR_REV_2, // PORSHE + SFX_CAR_REV_3, // SPIDER + SFX_CAR_REV_4, // MERC + SFX_CAR_REV_5, // TRUC + SFX_CAR_REV_6, // HOTROD + SFX_CAR_REV_7, // COBRA + SFX_CAR_REV_8, // PONT2 + SFX_CAR_REV_9, // CADI + SFX_CAR_REV_10, // PATHFINDER + SFX_CAR_REV_11, // PACARD + SFX_CAR_REV_12, // GOLFCART + SFX_CAR_REV_13, // SFX_CAR_IDLE_GOL + SFX_CAR_REV_14, // SFX_CAR_IDLE_GOL + SFX_CAR_REV_15, // SFX_CAR_IDLE_GOL + SFX_CAR_REV_16, // SFX_CAR_IDLE_GOL + SFX_CAR_REV_17, // VTWI + SFX_MOPED_REV, // just moped + SFX_CAR_REV_19, // HOND(A) + SFX_CAR_REV_20, // SPOR(TCAR) + SFX_CAR_IDLE_1, // PONT + SFX_CAR_IDLE_2, // PORSHE + SFX_CAR_IDLE_3, // SPIDER + SFX_CAR_IDLE_4, // MERC + SFX_CAR_IDLE_5, // TRUC + SFX_CAR_IDLE_6, // HOTROD + SFX_CAR_IDLE_7, // COBRA + SFX_CAR_IDLE_8, // PONT2 + SFX_CAR_IDLE_9, // CADI + SFX_CAR_IDLE_10, // PATHFINDER + SFX_CAR_IDLE_11, // PACARD + SFX_CAR_IDLE_12, // GOLFCART + SFX_CAR_IDLE_13, // SFX_CAR_IDLE_GOL + SFX_CAR_IDLE_14, // SFX_CAR_IDLE_GOL + SFX_CAR_IDLE_15, // SFX_CAR_IDLE_GOL + SFX_CAR_IDLE_16, // SFX_CAR_IDLE_GOL + SFX_CAR_IDLE_17, // VTWI + SFX_MOPED_IDLE, // 237 + SFX_CAR_IDLE_19, // HOND(A) + SFX_CAR_IDLE_20, // SPOR(TCAR) + SFX_JUMBO_DIST_FLY, + SFX_JUMBO_TAXI, // 241 + SFX_JUMBO_WHINE, // 242 + SFX_JUMBO_ENGINE, // 243 + SFX_JUMBO_RUMBLE, // 244 + SFX_JUMBO_LAND_WHEELS, + SFX_BOAT_CRUISER_LOOP, // 246 + SFX_BOAT_V12_LOOP, // 247 + SFX_BOAT_WATER_LOOP, + SFX_BOAT_SPLASH_1, + SFX_BOAT_SPLASH_2, + SFX_FISHING_BOAT_IDLE, + SFX_CAR_RAIN_1, // 252 + SFX_CAR_RAIN_2, // 253 + SFX_CAR_RAIN_3, // 254 + SFX_CAR_RAIN_4, // 255 + SFX_SPLASH_1, // 256 + SFX_PED_CRUNCH_1, // 257 + SFX_PED_CRUNCH_2, // 258 + SFX_WOODEN_BOX_SMASH, + SFX_CARDBOARD_BOX_SMASH, + SFX_ERROR_FIRE_ROCKET_LAUNCHER, + SFX_ERROR_FIRE_RIFLE, + SFX_TANK_TURRET, // 263 + SFX_BODY_LAND_AND_FALL, + SFX_BODY_LAND, // 265 + SFX_BOMB_BEEP, // 266 + SFX_TIMER_BEEP, // 267 + SFX_SUSPENSION_FAST_MOVE, + SFX_SUSPENSION_SLOW_MOVE_LOOP, + SFX_SHAG_SUSPENSION, + SFX_HIT_BALL, // 271 + SFX_ARCADE, // 272 + SFX_CESNA_IDLE, // 273 + SFX_CESNA_REV, // 274 + SFX_RADIO_CLICK, // 275 + SFX_RADIO_DIAL_1, // 276 + SFX_RADIO_DIAL_2, // 277 + SFX_RADIO_DIAL_3, // 278 + + // pc only + SFX_RADIO_DIAL_4, + SFX_RADIO_DIAL_5, + SFX_RADIO_DIAL_6, + SFX_RADIO_DIAL_7, + SFX_RADIO_DIAL_8, + SFX_RADIO_DIAL_9, + SFX_RADIO_DIAL_10, + SFX_RADIO_DIAL_11, + SFX_RADIO_DIAL_12, + + SFX_INFO_LEFT, // 279 + SFX_INFO_RIGHT, // 280 + SFX_INFO_CENTRE, // 281 + SFX_MONEY_LEFT, // 282 + SFX_MONEY_RIGHT, // 283 + SFX_WEAPON_LEFT, // 284 + SFX_WEAPON_RIGHT, // 285 + SFX_WEAPON_CENTRE, // 286 + SFX_PART_MISSION_COMPLETE_LEFT, // 287 + SFX_PART_MISSION_COMPLETE_RIGHT, // 288 + SFX_PART_MISSION_COMPLETE_CENTRE, // 289 + SFX_GO_LEFT, // 290 + SFX_GO_RIGHT, // 291 + SFX_GO_CENTRE, // 292 + SFX_TIMER, // 293 + SFX_EMPTY, // 294 + + SFX_FE_HIGHLIGHT_LEFT, // + SFX_FE_HIGHLIGHT_RIGHT, // + SFX_FE_SELECT_LEFT, // + SFX_FE_SELECT_RIGHT, // + SFX_FE_BACK_LEFT, // + SFX_FE_BACK_RIGHT, // + SFX_FE_ERROR_LEFT, // + SFX_FE_ERROR_RIGHT, // + SFX_FE_NOISE_BURST_1, + SFX_FE_NOISE_BURST_2, + SFX_FE_NOISE_BURST_3, + + SFX_CAR_ACCEL_1, + SFX_CAR_AFTER_ACCEL_1, + SFX_CAR_FINGER_OFF_ACCEL_1, + + SFX_CAR_ACCEL_2, + SFX_CAR_AFTER_ACCEL_2, + SFX_CAR_FINGER_OFF_ACCEL_2, + + SFX_CAR_ACCEL_3, + SFX_CAR_AFTER_ACCEL_3, + SFX_CAR_FINGER_OFF_ACCEL_3, + + SFX_CAR_ACCEL_4, + SFX_CAR_AFTER_ACCEL_4, + SFX_CAR_FINGER_OFF_ACCEL_4, + + SFX_CAR_ACCEL_5, + SFX_CAR_AFTER_ACCEL_5, + SFX_CAR_FINGER_OFF_ACCEL_5, + + SFX_CAR_ACCEL_6, + SFX_CAR_AFTER_ACCEL_6, + SFX_CAR_FINGER_OFF_ACCEL_6, + + SFX_CAR_ACCEL_7, + SFX_CAR_AFTER_ACCEL_7, + SFX_CAR_FINGER_OFF_ACCEL_7, + + SFX_CAR_ACCEL_8, + SFX_CAR_AFTER_ACCEL_8, + SFX_CAR_FINGER_OFF_ACCEL_8, + + SFX_CAR_ACCEL_9, + SFX_CAR_AFTER_ACCEL_9, + SFX_CAR_FINGER_OFF_ACCEL_9, + + SFX_CAR_ACCEL_10, + SFX_CAR_AFTER_ACCEL_10, + SFX_CAR_FINGER_OFF_ACCEL_10, + + SFX_CAR_ACCEL_11, + SFX_CAR_AFTER_ACCEL_11, + SFX_CAR_FINGER_OFF_ACCEL_11, + + SFX_CAR_ACCEL_12, + SFX_CAR_AFTER_ACCEL_12, + SFX_CAR_FINGER_OFF_ACCEL_12, + + // some CHAINSAW STUFF + SFX_CAR_CHAINSAW_IDLE, + SFX_CAR_CHAINSAW_ATTACK, + SFX_CAR_CHAINSAW_EMPTY, // unused + + SFX_RC_IDLE, // 10976 + SFX_RC_REV, // 10977 + SFX_RC_EMPTY, // 10978 + + SFX_CAR_RC_HELI, // 10979 + SFX_CAR_AFTER_ACCEL_15, // empty + SFX_CAR_FINGER_OFF_ACCEL_15, // empty + + SFX_CAR_ACCEL_16, // empty + SFX_CAR_AFTER_ACCEL_16, // empty + SFX_CAR_FINGER_OFF_ACCEL_16, // empty + + // bike stuff apparently + SFX_CAR_ACCEL_17, + SFX_CAR_AFTER_ACCEL_17, + SFX_CAR_FINGER_OFF_ACCEL_17, + SFX_CAR_WIND_17, + + SFX_CAR_ACCEL_18, + SFX_CAR_AFTER_ACCEL_18, + SFX_CAR_FINGER_OFF_ACCEL_18, + SFX_CAR_WIND_18, + + SFX_CAR_ACCEL_19, + SFX_CAR_AFTER_ACCEL_19, + SFX_CAR_FINGER_OFF_ACCEL_19, + SFX_CAR_WIND_19, + + SFX_CAR_ACCEL_20, + SFX_CAR_AFTER_ACCEL_20, + SFX_CAR_FINGER_OFF_ACCEL_20, + SFX_CAR_WIND_20, + + // some emptinnes here + SFX_CAR_ACCEL_21, + SFX_CAR_AFTER_ACCEL_21, + SFX_CAR_FINGER_OFF_ACCEL_21, + SFX_CAR_ACCEL_22, + SFX_CAR_AFTER_ACCEL_22, + SFX_CAR_FINGER_OFF_ACCEL_22, + + SFX_HELI_APACHE_1, + SFX_HELI_APACHE_2, + SFX_HELI_APACHE_3, + SFX_HELI_APACHE_4, + + // something padded for more heli? + SFX_HELI_UNUSED_1, + SFX_HELI_UNUSED_2, + SFX_HELI_UNUSED_3, + SFX_HELI_UNUSED_4, + + SFX_SEAPLANE_PRO1, // 11018 + SFX_SEAPLANE_PRO2, // 11019 + SFX_SEAPLANE_PRO3, // 11020 + SFX_SEAPLANE_PRO4, // 11021 + // low fuel + SFX_SEAPLANE_LOW, // 11022 + + // something padded for more plane? + SFX_PLANE_UNUSED_1, + SFX_PLANE_UNUSED_2, + SFX_PLANE_UNUSED_3, + SFX_PLANE_UNUSED_4, + + // script objects + SFX_BUILDINGS_BANK_ALARM, // 11027 + SFX_BUILDING_SNORE, // 11028 + SFX_BUILDING_BAR_1, // 11029 + SFX_BUILDING_BAR_2, // 11030 + SFX_BUILDING_BAR_3, // 11031 + SFX_BUILDING_BAR_4, // 11032 + SFX_BUILDING_MALIBU_1, // 11033 + SFX_BUILDING_MALIBU_2, // 11034 + SFX_BUILDING_MALIBU_3, // 11035 + SFX_BUILDING_STRIP_1, // 11036 + SFX_BUILDING_STRIP_2, // 11037 + SFX_BUILDING_STRIP_3, // 11038 + SFX_BUILDING_CHURCH, // 11039 + SFX_BUILDING_FAN_1, // 11040 + SFX_BUILDING_FAN_2, // 11041 + SFX_BUILDING_FAN_3, // 11042 + SFX_BUILDING_FAN_4, // 11043 + SFX_BUILDING_INSECTS_1, // 11044 + SFX_BUILDING_INSECTS_2, // 11045 + SFX_BUILDING_INSECTS_3, // 11046 + SFX_BUILDING_INSECTS_4, // 11047 + SFX_BUILDING_INSECTS_5, // 11048 + SFX_CLUB_1, // 11049 + SFX_CLUB_2, // 11050 + SFX_CLUB_3, // 11051 + SFX_CLUB_4, // 11052 + + 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, + + // ped comments + + SFX_BMYBB_BLOCKED_1, + SFX_BMYBB_BLOCKED_2, + SFX_BMYBB_BLOCKED_3, + SFX_BMYBB_BLOCKED_4, + SFX_BMYBB_BLOCKED_5, + SFX_BMYBB_BLOCKED_6, + SFX_BMYBB_BLOCKED_7, + SFX_BMYBB_BLOCKED_8, + SFX_BMYBB_BLOCKED_9, + SFX_BMYBB_BLOCKED_10, + SFX_BMYBB_BLOCKED_11, + SFX_BMYBB_BLOCKED_12, + SFX_BMYBB_BLOCKED_13, + SFX_BMYBB_BUMP_1, + SFX_BMYBB_BUMP_2, + SFX_BMYBB_BUMP_3, + SFX_BMYBB_BUMP_4, + SFX_BMYBB_BUMP_5, + SFX_BMYBB_BUMP_6, + SFX_BMYBB_BUMP_7, + SFX_BMYBB_BUMP_8, + SFX_BMYBB_BUMP_9, + SFX_BMYBB_BUMP_10, + SFX_BMYBB_BUMP_11, + SFX_BMYBB_BUMP_12, + SFX_BMYBB_BUMP_13, + SFX_BMYBB_BUMP_14, + SFX_BMYBB_BUMP_15, + SFX_BMYBB_BUMP_16, + SFX_BMYBB_BUMP_17, + SFX_BMYBB_CAR_CRASH_1, + SFX_BMYBB_CAR_CRASH_2, + SFX_BMYBB_CAR_CRASH_3, + SFX_BMYBB_CAR_CRASH_4, + SFX_BMYBB_CAR_CRASH_5, + SFX_BMYBB_CAR_CRASH_6, + SFX_BMYBB_CAR_CRASH_7, + SFX_BMYBB_CAR_CRASH_8, + SFX_BMYBB_CAR_CRASH_9, + SFX_BMYBB_CHAT_1, + SFX_BMYBB_CHAT_2, + SFX_BMYBB_CHAT_3, + SFX_BMYBB_CHAT_4, + SFX_BMYBB_CHAT_5, + SFX_BMYBB_CHAT_6, + SFX_BMYBB_CHAT_7, + SFX_BMYBB_CHAT_8, + SFX_BMYBB_CHAT_9, + SFX_BMYBB_CHAT_10, + SFX_BMYBB_CHAT_11, + SFX_BMYBB_CHAT_12, + SFX_BMYBB_CHAT_13, + SFX_BMYBB_CHAT_14, + SFX_BMYBB_CHAT_15, + SFX_BMYBB_CHAT_16, + SFX_BMYBB_CHAT_17, + SFX_BMYBB_CHAT_18, + SFX_BMYBB_CHAT_19, + SFX_BMYBB_CHAT_20, + SFX_BMYBB_CHAT_21, + SFX_BMYBB_DODGE_1, + SFX_BMYBB_DODGE_2, + SFX_BMYBB_DODGE_3, + SFX_BMYBB_DODGE_4, + SFX_BMYBB_DODGE_5, + SFX_BMYBB_DODGE_6, + SFX_BMYBB_DODGE_7, + SFX_BMYBB_DODGE_8, + SFX_BMYBB_DODGE_9, + SFX_BMYBB_DODGE_10, + SFX_BMYBB_DODGE_11, + SFX_BMYBB_DODGE_12, + SFX_BMYBB_DODGE_13, + SFX_BMYBB_DODGE_14, + SFX_BMYBB_DODGE_15, + SFX_BMYBB_DODGE_16, + SFX_BMYBB_DODGE_17, + SFX_BMYBB_DODGE_18, + SFX_BMYBB_EYEING_1, + SFX_BMYBB_EYEING_2, + SFX_BMYBB_EYEING_3, + SFX_BMYBB_EYEING_4, + SFX_BMYBB_EYEING_5, + SFX_BMYBB_EYEING_6, + SFX_BMYBB_EYEING_7, + SFX_BMYBB_EYEING_8, + SFX_BMYBB_EYEING_9, + SFX_BMYBB_EYEING_10, + SFX_BMYBB_EYEING_11, + SFX_BMYBB_EYEING_12, + SFX_BMYBB_EYEING_13, + SFX_BMYBB_EYEING_14, + SFX_BMYBB_EYEING_15, + SFX_BMYBB_EYEING_16, + SFX_BMYBB_FIGHT_1, + SFX_BMYBB_FIGHT_2, + SFX_BMYBB_FIGHT_3, + SFX_BMYBB_FIGHT_4, + SFX_BMYBB_FIGHT_5, + SFX_BMYBB_FIGHT_6, + SFX_BMYBB_FIGHT_7, + SFX_BMYBB_FIGHT_8, + SFX_BMYBB_FIGHT_9, + SFX_BMYBB_FIGHT_10, + SFX_BMYBB_FIGHT_11, + SFX_BMYBB_FIGHT_12, + SFX_BMYBB_GENERIC_CRASH_1, + SFX_BMYBB_GENERIC_CRASH_2, + SFX_BMYBB_GENERIC_CRASH_3, + SFX_BMYBB_GENERIC_CRASH_4, + SFX_BMYBB_GENERIC_CRASH_5, + SFX_BMYBB_GENERIC_CRASH_6, + SFX_BMYBB_GENERIC_CRASH_7, + SFX_BMYBB_GENERIC_CRASH_8, + SFX_BMYBB_GENERIC_CRASH_9, + SFX_BMYBB_GUN_COOL_1, + SFX_BMYBB_GUN_COOL_2, + SFX_BMYBB_GUN_COOL_3, + SFX_BMYBB_GUN_COOL_4, + SFX_BMYBB_GUN_COOL_5, + SFX_BMYBB_INNOCENT_1, + SFX_BMYBB_INNOCENT_2, + SFX_BMYBB_INNOCENT_3, + SFX_BMYBB_INNOCENT_4, + SFX_BMYBB_JACKED_1, + SFX_BMYBB_JACKED_2, + SFX_BMYBB_JACKED_3, + SFX_BMYBB_JACKED_4, + SFX_BMYBB_JACKED_5, + SFX_BMYBB_JACKED_6, + SFX_BMYBB_JACKED_7, + SFX_BMYBB_JACKED_8, + SFX_BMYBB_JACKED_9, + SFX_BMYBB_JACKED_10, + SFX_BMYBB_JACKED_11, + SFX_BMYBB_JACKING_1, + SFX_BMYBB_JACKING_2, + SFX_BMYBB_JACKING_3, + SFX_BMYBB_JACKING_4, + SFX_BMYBB_JACKING_5, + SFX_BMYBB_JACKING_6, + SFX_BMYBB_JACKING_7, + SFX_BMYBB_JACKING_8, + SFX_BMYBB_JACKING_9, + SFX_BMYBB_JEER_1, + SFX_BMYBB_JEER_2, + SFX_BMYBB_JEER_3, + SFX_BMYBB_JEER_4, + SFX_BMYBB_JEER_5, + SFX_BMYBB_JEER_6, + SFX_BMYBB_JEER_7, + SFX_BMYBB_JEER_8, + SFX_BMYBB_JEER_9, + SFX_BMYBB_JEER_10, + SFX_BMYBB_JEER_11, + SFX_BMYBB_JEER_12, + SFX_BMYBB_JEER_13, + SFX_BMYBB_JEER_14, + SFX_BMYBB_JEER_15, + SFX_BMYBB_JEER_16, + SFX_BMYBB_LOST_1, + SFX_BMYBB_LOST_2, + SFX_BMYBB_MUGGED_1, + SFX_BMYBB_MUGGED_2, + SFX_BMYBB_MUGGED_3, + SFX_BMYBB_MUGGED_4, + SFX_BMYBB_MUGGED_5, + SFX_BMYBB_MUGGING_1, + SFX_BMYBB_MUGGING_2, + SFX_BMYBB_MUGGING_3, + SFX_BMYBB_MUGGING_4, + SFX_BMYBB_MUGGING_5, + SFX_BMYBB_MUGGING_6, + SFX_BMYBB_MUGGING_7, + SFX_BMYBB_MUGGING_8, + SFX_BMYBB_SAVED_1, + SFX_BMYBB_SAVED_2, + SFX_BMYBB_SAVED_3, + SFX_BMYBB_SAVED_4, + SFX_BMYBB_SAVED_5, + SFX_BMYBB_SAVED_6, + SFX_BMYBB_SHOCKED_1, + SFX_BMYBB_SHOCKED_2, + SFX_BMYBB_SHOCKED_3, + SFX_BMYBB_SHOCKED_4, + SFX_BMYBB_SHOCKED_5, + SFX_BMYBB_SHOCKED_6, + SFX_BMYBB_TAXI_1, + SFX_BMYBB_TAXI_2, + SFX_BMYBB_TAXI_3, + + SFX_POLICE_BOAT_1, + SFX_POLICE_BOAT_2, + SFX_POLICE_BOAT_3, + SFX_POLICE_BOAT_4, + SFX_POLICE_BOAT_5, + SFX_POLICE_BOAT_6, + SFX_POLICE_BOAT_7, + SFX_POLICE_BOAT_8, + SFX_POLICE_BOAT_9, + SFX_POLICE_BOAT_10, + SFX_POLICE_BOAT_11, + SFX_POLICE_BOAT_12, + SFX_POLICE_BOAT_13, + SFX_POLICE_BOAT_14, + SFX_POLICE_BOAT_15, + SFX_POLICE_BOAT_16, + SFX_POLICE_BOAT_17, + SFX_POLICE_BOAT_18, + SFX_POLICE_BOAT_19, + SFX_POLICE_BOAT_20, + SFX_POLICE_BOAT_21, + SFX_POLICE_BOAT_22, + SFX_POLICE_BOAT_23, + + 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_JFOTO_BLOCKED_1, + SFX_JFOTO_BLOCKED_2, + SFX_JFOTO_BLOCKED_3, + SFX_JFOTO_BLOCKED_4, + SFX_JFOTO_BLOCKED_5, + SFX_JFOTO_BLOCKED_6, + SFX_JFOTO_BLOCKED_7, + SFX_JFOTO_BLOCKED_8, + + SFX_JFOTO_BUMP_1, + SFX_JFOTO_BUMP_2, + SFX_JFOTO_BUMP_3, + SFX_JFOTO_BUMP_4, + SFX_JFOTO_BUMP_5, + SFX_JFOTO_BUMP_6, + SFX_JFOTO_BUMP_7, + SFX_JFOTO_BUMP_8, + SFX_JFOTO_BUMP_9, + SFX_JFOTO_BUMP_10, + + SFX_JFOTO_CAR_CRASH_1, + SFX_JFOTO_CAR_CRASH_2, + SFX_JFOTO_CAR_CRASH_3, + SFX_JFOTO_CAR_CRASH_4, + SFX_JFOTO_CAR_CRASH_5, + SFX_JFOTO_CAR_CRASH_6, + SFX_JFOTO_CAR_CRASH_7, + SFX_JFOTO_CAR_CRASH_8, + + SFX_JFOTO_CHAT_1, + SFX_JFOTO_CHAT_2, + SFX_JFOTO_CHAT_3, + SFX_JFOTO_CHAT_4, + SFX_JFOTO_CHAT_5, + SFX_JFOTO_CHAT_6, + SFX_JFOTO_CHAT_7, + SFX_JFOTO_CHAT_8, + SFX_JFOTO_CHAT_9, + SFX_JFOTO_CHAT_10, + SFX_JFOTO_CHAT_11, + SFX_JFOTO_CHAT_12, + SFX_JFOTO_CHAT_13, + + SFX_JFOTO_DODGE_1, + SFX_JFOTO_DODGE_2, + SFX_JFOTO_DODGE_3, + SFX_JFOTO_DODGE_4, + SFX_JFOTO_DODGE_5, + SFX_JFOTO_DODGE_6, + SFX_JFOTO_DODGE_7, + SFX_JFOTO_DODGE_8, + SFX_JFOTO_DODGE_9, + + SFX_JFOTO_GENERIC_CRASH_1, + SFX_JFOTO_GENERIC_CRASH_2, + SFX_JFOTO_GENERIC_CRASH_3, + SFX_JFOTO_GENERIC_CRASH_4, + SFX_JFOTO_GENERIC_CRASH_5, + SFX_JFOTO_GENERIC_CRASH_6, + SFX_JFOTO_GUN_PANIC_1, + SFX_JFOTO_GUN_PANIC_2, + SFX_JFOTO_GUN_PANIC_3, + SFX_JFOTO_GUN_PANIC_4, + SFX_JFOTO_JACKED_1, + SFX_JFOTO_JACKED_2, + SFX_JFOTO_JACKED_3, + SFX_JFOTO_JACKED_4, + SFX_JFOTO_JACKED_5, + SFX_JFOTO_LOST_1, + SFX_JFOTO_MUGGED_1, + SFX_JFOTO_MUGGED_2, + SFX_JFOTO_RUN_1, + SFX_JFOTO_RUN_2, + SFX_JFOTO_RUN_3, + SFX_JFOTO_RUN_4, + SFX_JFOTO_RUN_5, + SFX_JFOTO_SAVED_1, + SFX_JFOTO_SAVED_2, + SFX_JFOTO_SHOCKED_1, + SFX_JFOTO_TAXI_1, + SFX_JFOTO_TAXI_2, + + SFX_JMOTO_BLOCKED_1, + SFX_JMOTO_BLOCKED_2, + SFX_JMOTO_BLOCKED_3, + SFX_JMOTO_BLOCKED_4, + SFX_JMOTO_BLOCKED_5, + SFX_JMOTO_BLOCKED_6, + SFX_JMOTO_BLOCKED_7, + SFX_JMOTO_BLOCKED_8, + SFX_JMOTO_BUMP_1, + SFX_JMOTO_BUMP_2, + SFX_JMOTO_BUMP_3, + SFX_JMOTO_BUMP_4, + SFX_JMOTO_BUMP_5, + SFX_JMOTO_BUMP_6, + SFX_JMOTO_BUMP_7, + SFX_JMOTO_BUMP_8, + SFX_JMOTO_CAR_CRASH_1, + SFX_JMOTO_CAR_CRASH_2, + SFX_JMOTO_CAR_CRASH_3, + SFX_JMOTO_CAR_CRASH_4, + SFX_JMOTO_CAR_CRASH_5, + SFX_JMOTO_CAR_CRASH_6, + SFX_JMOTO_CHAT_1, + SFX_JMOTO_CHAT_2, + SFX_JMOTO_CHAT_3, + SFX_JMOTO_CHAT_4, + SFX_JMOTO_CHAT_5, + SFX_JMOTO_CHAT_6, + SFX_JMOTO_CHAT_7, + SFX_JMOTO_DODGE_1, + SFX_JMOTO_DODGE_2, + SFX_JMOTO_DODGE_3, + SFX_JMOTO_DODGE_4, + SFX_JMOTO_DODGE_5, + SFX_JMOTO_DODGE_6, + SFX_JMOTO_GENERIC_CRASH_1, + SFX_JMOTO_GENERIC_CRASH_2, + SFX_JMOTO_GENERIC_CRASH_3, + SFX_JMOTO_GENERIC_CRASH_4, + SFX_JMOTO_GENERIC_CRASH_5, + SFX_JMOTO_GENERIC_CRASH_6, + SFX_JMOTO_GUN_PANIC_1, + SFX_JMOTO_GUN_PANIC_2, + SFX_JMOTO_GUN_PANIC_3, + SFX_JMOTO_GUN_PANIC_4, + SFX_JMOTO_JACKED_1, + SFX_JMOTO_JACKED_2, + SFX_JMOTO_JACKED_3, + SFX_JMOTO_JACKED_4, + SFX_JMOTO_LOST_1, + SFX_JMOTO_MUGGED_1, + SFX_JMOTO_MUGGED_2, + SFX_JMOTO_RUN_1, + SFX_JMOTO_RUN_2, + SFX_JMOTO_RUN_3, + SFX_JMOTO_RUN_4, + SFX_JMOTO_SAVED_1, + SFX_JMOTO_SHOCKED_1, + SFX_JMOTO_TAXI_1, + + SFX_BMYBE_BLOCKED_1, + SFX_BMYBE_BLOCKED_2, + SFX_BMYBE_BLOCKED_3, + SFX_BMYBE_BLOCKED_4, + SFX_BMYBE_BLOCKED_5, + SFX_BMYBE_BLOCKED_6, + SFX_BMYBE_BLOCKED_7, + SFX_BMYBE_BLOCKED_8, + SFX_BMYBE_BUMP_1, + SFX_BMYBE_BUMP_2, + SFX_BMYBE_BUMP_3, + SFX_BMYBE_BUMP_4, + SFX_BMYBE_BUMP_5, + SFX_BMYBE_BUMP_6, + SFX_BMYBE_BUMP_7, + SFX_BMYBE_BUMP_8, + SFX_BMYBE_BUMP_9, + SFX_BMYBE_BUMP_10, + SFX_BMYBE_CAR_CRASH_1, + SFX_BMYBE_CAR_CRASH_2, + SFX_BMYBE_CAR_CRASH_3, + SFX_BMYBE_CAR_CRASH_4, + SFX_BMYBE_CAR_CRASH_5, + SFX_BMYBE_CAR_CRASH_6, + SFX_BMYBE_CAR_CRASH_7, + SFX_BMYBE_CAR_CRASH_8, + SFX_BMYBE_CHAT_1, + SFX_BMYBE_CHAT_2, + SFX_BMYBE_CHAT_3, + SFX_BMYBE_CHAT_4, + SFX_BMYBE_CHAT_5, + SFX_BMYBE_CHAT_6, + SFX_BMYBE_CHAT_7, + SFX_BMYBE_CHAT_8, + SFX_BMYBE_CHAT_9, + SFX_BMYBE_CHAT_10, + SFX_BMYBE_DODGE_1, + SFX_BMYBE_DODGE_2, + SFX_BMYBE_DODGE_3, + SFX_BMYBE_DODGE_4, + SFX_BMYBE_DODGE_5, + SFX_BMYBE_DODGE_6, + SFX_BMYBE_DODGE_7, + SFX_BMYBE_DODGE_8, + SFX_BMYBE_DODGE_9, + SFX_BMYBE_DODGE_10, + SFX_BMYBE_EYEING_1, + SFX_BMYBE_EYEING_2, + SFX_BMYBE_FIGHT_1, + SFX_BMYBE_FIGHT_2, + SFX_BMYBE_FIGHT_3, + SFX_BMYBE_FIGHT_4, + SFX_BMYBE_FIGHT_5, + SFX_BMYBE_FIGHT_6, + SFX_BMYBE_FIGHT_7, + SFX_BMYBE_FIGHT_8, + SFX_BMYBE_GENERIC_CRASH_1, + SFX_BMYBE_GENERIC_CRASH_2, + SFX_BMYBE_GENERIC_CRASH_3, + SFX_BMYBE_GENERIC_CRASH_4, + SFX_BMYBE_GENERIC_CRASH_5, + SFX_BMYBE_GENERIC_CRASH_6, + SFX_BMYBE_GENERIC_CRASH_7, + SFX_BMYBE_GENERIC_CRASH_8, + SFX_BMYBE_GUN_COOL_1, + SFX_BMYBE_GUN_COOL_2, + SFX_BMYBE_GUN_COOL_3, + SFX_BMYBE_GUN_COOL_4, + SFX_BMYBE_JACKED_1, + SFX_BMYBE_JACKED_2, + SFX_BMYBE_JACKED_3, + SFX_BMYBE_JACKED_4, + SFX_BMYBE_JACKED_5, + SFX_BMYBE_JACKED_6, + SFX_BMYBE_JACKING_1, + SFX_BMYBE_JACKING_2, + SFX_BMYBE_JACKING_3, + SFX_BMYBE_LOST_1, + SFX_BMYBE_MUGGED_1, + SFX_BMYBE_SAVED_1, + SFX_BMYBE_TAXI_1, + + SFX_HFOBE_BLOCKED_1, + SFX_HFOBE_BLOCKED_2, + SFX_HFOBE_BLOCKED_3, + SFX_HFOBE_BLOCKED_4, + SFX_HFOBE_BLOCKED_5, + SFX_HFOBE_BLOCKED_6, + SFX_HFOBE_BUMP_1, + SFX_HFOBE_BUMP_2, + SFX_HFOBE_BUMP_3, + SFX_HFOBE_BUMP_4, + SFX_HFOBE_BUMP_5, + SFX_HFOBE_BUMP_6, + SFX_HFOBE_BUMP_7, + SFX_HFOBE_BUMP_8, + SFX_HFOBE_BUMP_9, + SFX_HFOBE_BUMP_10, + SFX_HFOBE_BUMP_11, + SFX_HFOBE_CAR_CRASH_1, + SFX_HFOBE_CAR_CRASH_2, + SFX_HFOBE_CAR_CRASH_3, + SFX_HFOBE_CAR_CRASH_4, + SFX_HFOBE_CAR_CRASH_5, + SFX_HFOBE_CAR_CRASH_6, + SFX_HFOBE_CHAT_1, + SFX_HFOBE_CHAT_2, + SFX_HFOBE_CHAT_3, + SFX_HFOBE_CHAT_4, + SFX_HFOBE_CHAT_5, + SFX_HFOBE_CHAT_6, + SFX_HFOBE_CHAT_7, + SFX_HFOBE_CHAT_8, + SFX_HFOBE_CHAT_9, + SFX_HFOBE_CHAT_10, + SFX_HFOBE_DODGE_1, + SFX_HFOBE_DODGE_2, + SFX_HFOBE_DODGE_3, + SFX_HFOBE_DODGE_4, + SFX_HFOBE_DODGE_5, + SFX_HFOBE_DODGE_6, + SFX_HFOBE_DODGE_7, + SFX_HFOBE_GENERIC_CRASH_1, + SFX_HFOBE_GENERIC_CRASH_2, + SFX_HFOBE_GENERIC_CRASH_3, + SFX_HFOBE_GENERIC_CRASH_4, + SFX_HFOBE_GENERIC_CRASH_5, + SFX_HFOBE_GUN_PANIC_1, + SFX_HFOBE_GUN_PANIC_2, + SFX_HFOBE_GUN_PANIC_3, + SFX_HFOBE_GUN_PANIC_4, + SFX_HFOBE_GUN_PANIC_5, + SFX_HFOBE_JACKED_1, + SFX_HFOBE_JACKED_2, + SFX_HFOBE_JACKED_3, + SFX_HFOBE_JACKED_4, + SFX_HFOBE_JACKED_5, + SFX_HFOBE_JACKED_6, + SFX_HFOBE_LOST_1, + SFX_HFOBE_LOST_2, + SFX_HFOBE_RUN_1, + SFX_HFOBE_RUN_2, + SFX_HFOBE_RUN_3, + SFX_HFOBE_RUN_4, + SFX_HFOBE_SAVED_1, + SFX_HFOBE_SHOCKED_1, + SFX_HFOBE_SHOCKED_2, + SFX_HFOBE_TAXI_1, + SFX_HFOBE_TAXI_2, + + SFX_STREET_GANG_1_BLOCKED_1, + SFX_STREET_GANG_1_BLOCKED_2, + SFX_STREET_GANG_1_BLOCKED_3, + SFX_STREET_GANG_1_BLOCKED_4, + SFX_STREET_GANG_1_BLOCKED_5, + SFX_STREET_GANG_1_BLOCKED_6, + SFX_STREET_GANG_1_BLOCKED_7, + SFX_STREET_GANG_1_BLOCKED_8, + SFX_STREET_GANG_1_BUMP_1, + SFX_STREET_GANG_1_BUMP_2, + SFX_STREET_GANG_1_BUMP_3, + SFX_STREET_GANG_1_BUMP_4, + SFX_STREET_GANG_1_BUMP_5, + SFX_STREET_GANG_1_BUMP_6, + SFX_STREET_GANG_1_BUMP_7, + SFX_STREET_GANG_1_BUMP_8, + SFX_STREET_GANG_1_BUMP_9, + SFX_STREET_GANG_1_BUMP_10, + SFX_STREET_GANG_1_CAR_CRASH_1, + SFX_STREET_GANG_1_CAR_CRASH_2, + SFX_STREET_GANG_1_CAR_CRASH_3, + SFX_STREET_GANG_1_CAR_CRASH_4, + SFX_STREET_GANG_1_CAR_CRASH_5, + SFX_STREET_GANG_1_CAR_CRASH_6, + SFX_STREET_GANG_1_CHAT_1, + SFX_STREET_GANG_1_CHAT_2, + SFX_STREET_GANG_1_CHAT_3, + SFX_STREET_GANG_1_CHAT_4, + SFX_STREET_GANG_1_CHAT_5, + SFX_STREET_GANG_1_CHAT_6, + SFX_STREET_GANG_1_CHAT_7, + SFX_STREET_GANG_1_CHAT_8, + SFX_STREET_GANG_1_CHAT_9, + SFX_STREET_GANG_1_CHAT_10, + SFX_STREET_GANG_1_CHAT_11, + SFX_STREET_GANG_1_CHAT_12, + SFX_STREET_GANG_1_DODGE_1, + SFX_STREET_GANG_1_DODGE_2, + SFX_STREET_GANG_1_DODGE_3, + SFX_STREET_GANG_1_DODGE_4, + SFX_STREET_GANG_1_DODGE_5, + SFX_STREET_GANG_1_DODGE_6, + SFX_STREET_GANG_1_DODGE_7, + SFX_STREET_GANG_1_DODGE_8, + SFX_STREET_GANG_1_DODGE_9, + SFX_STREET_GANG_1_EYEING_1, + SFX_STREET_GANG_1_EYEING_2, + SFX_STREET_GANG_1_EYEING_3, + SFX_STREET_GANG_1_FIGHT_1, + SFX_STREET_GANG_1_FIGHT_2, + SFX_STREET_GANG_1_FIGHT_3, + SFX_STREET_GANG_1_FIGHT_4, + SFX_STREET_GANG_1_FIGHT_5, + SFX_STREET_GANG_1_FIGHT_6, + SFX_STREET_GANG_1_FIGHT_7, + SFX_STREET_GANG_1_FIGHT_8, + SFX_STREET_GANG_1_FIGHT_9, + SFX_STREET_GANG_1_FIGHT_10, + SFX_STREET_GANG_1_GENERIC_CRASH_1, + SFX_STREET_GANG_1_GENERIC_CRASH_2, + SFX_STREET_GANG_1_GENERIC_CRASH_3, + SFX_STREET_GANG_1_GENERIC_CRASH_4, + SFX_STREET_GANG_1_GENERIC_CRASH_5, + SFX_STREET_GANG_1_GENERIC_CRASH_6, + SFX_STREET_GANG_1_GUN_COOL_1, + SFX_STREET_GANG_1_GUN_COOL_2, + SFX_STREET_GANG_1_GUN_COOL_3, + SFX_STREET_GANG_1_GUN_COOL_4, + SFX_STREET_GANG_1_GUN_COOL_5, + SFX_STREET_GANG_1_JACKED_1, + SFX_STREET_GANG_1_JACKED_2, + SFX_STREET_GANG_1_JACKED_3, + SFX_STREET_GANG_1_JACKED_4, + SFX_STREET_GANG_1_JACKED_5, + SFX_STREET_GANG_1_JACKING_1, + SFX_STREET_GANG_1_JACKING_2, + SFX_STREET_GANG_1_JACKING_3, + SFX_STREET_GANG_1_JACKING_4, + SFX_STREET_GANG_1_JACKING_5, + SFX_STREET_GANG_1_LOST_1, + SFX_STREET_GANG_1_LOST_2, + SFX_STREET_GANG_1_MUGGED_1, + SFX_STREET_GANG_1_MUGGED_2, + SFX_STREET_GANG_1_MUGGED_3, + SFX_STREET_GANG_1_MUGGING_1, + SFX_STREET_GANG_1_MUGGING_2, + SFX_STREET_GANG_1_MUGGING_3, + SFX_STREET_GANG_1_MUGGING_4, + SFX_STREET_GANG_1_MUGGING_5, + SFX_STREET_GANG_1_SAVED_1, + SFX_STREET_GANG_1_SHOCKED_1, + SFX_STREET_GANG_1_SHOCKED_2, + SFX_STREET_GANG_1_TAXI_1, + + SFX_STREET_GANG_2_BLOCKED_1, + SFX_STREET_GANG_2_BLOCKED_2, + SFX_STREET_GANG_2_BLOCKED_3, + SFX_STREET_GANG_2_BLOCKED_4, + SFX_STREET_GANG_2_BLOCKED_5, + SFX_STREET_GANG_2_BLOCKED_6, + SFX_STREET_GANG_2_BLOCKED_7, + SFX_STREET_GANG_2_BLOCKED_8, + SFX_STREET_GANG_2_BUMP_1, + SFX_STREET_GANG_2_BUMP_2, + SFX_STREET_GANG_2_BUMP_3, + SFX_STREET_GANG_2_BUMP_4, + SFX_STREET_GANG_2_BUMP_5, + SFX_STREET_GANG_2_BUMP_6, + SFX_STREET_GANG_2_BUMP_7, + SFX_STREET_GANG_2_BUMP_8, + SFX_STREET_GANG_2_BUMP_9, + SFX_STREET_GANG_2_BUMP_10, + SFX_STREET_GANG_2_CAR_CRASH_1, + SFX_STREET_GANG_2_CAR_CRASH_2, + SFX_STREET_GANG_2_CAR_CRASH_3, + SFX_STREET_GANG_2_CAR_CRASH_4, + SFX_STREET_GANG_2_CAR_CRASH_5, + SFX_STREET_GANG_2_CAR_CRASH_6, + SFX_STREET_GANG_2_CHAT_1, + SFX_STREET_GANG_2_CHAT_2, + SFX_STREET_GANG_2_CHAT_3, + SFX_STREET_GANG_2_CHAT_4, + SFX_STREET_GANG_2_CHAT_5, + SFX_STREET_GANG_2_CHAT_6, + SFX_STREET_GANG_2_CHAT_7, + SFX_STREET_GANG_2_CHAT_8, + SFX_STREET_GANG_2_CHAT_9, + SFX_STREET_GANG_2_CHAT_10, + SFX_STREET_GANG_2_CHAT_11, + SFX_STREET_GANG_2_CHAT_12, + SFX_STREET_GANG_2_DODGE_1, + SFX_STREET_GANG_2_DODGE_2, + SFX_STREET_GANG_2_DODGE_3, + SFX_STREET_GANG_2_DODGE_4, + SFX_STREET_GANG_2_DODGE_5, + SFX_STREET_GANG_2_DODGE_6, + SFX_STREET_GANG_2_DODGE_7, + SFX_STREET_GANG_2_DODGE_8, + SFX_STREET_GANG_2_DODGE_9, + SFX_STREET_GANG_2_EYEING_1, + SFX_STREET_GANG_2_EYEING_2, + SFX_STREET_GANG_2_EYEING_3, + SFX_STREET_GANG_2_FIGHT_1, + SFX_STREET_GANG_2_FIGHT_2, + SFX_STREET_GANG_2_FIGHT_3, + SFX_STREET_GANG_2_FIGHT_4, + SFX_STREET_GANG_2_FIGHT_5, + SFX_STREET_GANG_2_FIGHT_6, + SFX_STREET_GANG_2_FIGHT_7, + SFX_STREET_GANG_2_FIGHT_8, + SFX_STREET_GANG_2_FIGHT_9, + SFX_STREET_GANG_2_FIGHT_10, + SFX_STREET_GANG_2_GENERIC_CRASH_1, + SFX_STREET_GANG_2_GENERIC_CRASH_2, + SFX_STREET_GANG_2_GENERIC_CRASH_3, + SFX_STREET_GANG_2_GENERIC_CRASH_4, + SFX_STREET_GANG_2_GENERIC_CRASH_5, + SFX_STREET_GANG_2_GENERIC_CRASH_6, + SFX_STREET_GANG_2_GUN_COOL_1, + SFX_STREET_GANG_2_GUN_COOL_2, + SFX_STREET_GANG_2_GUN_COOL_3, + SFX_STREET_GANG_2_GUN_COOL_4, + SFX_STREET_GANG_2_GUN_COOL_5, + SFX_STREET_GANG_2_JACKED_1, + SFX_STREET_GANG_2_JACKED_2, + SFX_STREET_GANG_2_JACKED_3, + SFX_STREET_GANG_2_JACKED_4, + SFX_STREET_GANG_2_JACKED_5, + SFX_STREET_GANG_2_JACKING_1, + SFX_STREET_GANG_2_JACKING_2, + SFX_STREET_GANG_2_JACKING_3, + SFX_STREET_GANG_2_JACKING_4, + SFX_STREET_GANG_2_JACKING_5, + SFX_STREET_GANG_2_LOST_1, + SFX_STREET_GANG_2_LOST_2, + SFX_STREET_GANG_2_MUGGED_1, + SFX_STREET_GANG_2_MUGGED_2, + SFX_STREET_GANG_2_MUGGED_3, + SFX_STREET_GANG_2_MUGGING_1, + SFX_STREET_GANG_2_MUGGING_2, + SFX_STREET_GANG_2_MUGGING_3, + SFX_STREET_GANG_2_MUGGING_4, + SFX_STREET_GANG_2_MUGGING_5, + SFX_STREET_GANG_2_SAVED_1, + SFX_STREET_GANG_2_SHOCKED_1, + SFX_STREET_GANG_2_SHOCKED_2, + SFX_STREET_GANG_2_TAXI_1, + + SFX_CUBAN_LORD_GANG_1_BLOCKED_1, + SFX_CUBAN_LORD_GANG_1_BLOCKED_2, + SFX_CUBAN_LORD_GANG_1_BLOCKED_3, + SFX_CUBAN_LORD_GANG_1_BLOCKED_4, + SFX_CUBAN_LORD_GANG_1_BLOCKED_5, + SFX_CUBAN_LORD_GANG_1_BLOCKED_6, + SFX_CUBAN_LORD_GANG_1_BLOCKED_7, + SFX_CUBAN_LORD_GANG_1_BLOCKED_8, + SFX_CUBAN_LORD_GANG_1_BLOCKED_9, + SFX_CUBAN_LORD_GANG_1_BLOCKED_10, + SFX_CUBAN_LORD_GANG_1_BUMP_1, + SFX_CUBAN_LORD_GANG_1_BUMP_2, + SFX_CUBAN_LORD_GANG_1_BUMP_3, + SFX_CUBAN_LORD_GANG_1_BUMP_4, + SFX_CUBAN_LORD_GANG_1_BUMP_5, + SFX_CUBAN_LORD_GANG_1_BUMP_6, + SFX_CUBAN_LORD_GANG_1_BUMP_7, + SFX_CUBAN_LORD_GANG_1_BUMP_8, + SFX_CUBAN_LORD_GANG_1_BUMP_9, + SFX_CUBAN_LORD_GANG_1_BUMP_10, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_1, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_2, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_3, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_4, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_5, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_6, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_7, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_8, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_9, + SFX_CUBAN_LORD_GANG_1_CAR_CRASH_10, + SFX_CUBAN_LORD_GANG_1_CHAT_1, + SFX_CUBAN_LORD_GANG_1_CHAT_2, + SFX_CUBAN_LORD_GANG_1_CHAT_3, + SFX_CUBAN_LORD_GANG_1_CHAT_4, + SFX_CUBAN_LORD_GANG_1_CHAT_5, + SFX_CUBAN_LORD_GANG_1_CHAT_6, + SFX_CUBAN_LORD_GANG_1_CHAT_7, + SFX_CUBAN_LORD_GANG_1_CHAT_8, + SFX_CUBAN_LORD_GANG_1_CHAT_9, + SFX_CUBAN_LORD_GANG_1_CHAT_10, + SFX_CUBAN_LORD_GANG_1_DODGE_1, + SFX_CUBAN_LORD_GANG_1_DODGE_2, + SFX_CUBAN_LORD_GANG_1_DODGE_3, + SFX_CUBAN_LORD_GANG_1_DODGE_4, + SFX_CUBAN_LORD_GANG_1_DODGE_5, + SFX_CUBAN_LORD_GANG_1_DODGE_6, + SFX_CUBAN_LORD_GANG_1_DODGE_7, + SFX_CUBAN_LORD_GANG_1_DODGE_8, + SFX_CUBAN_LORD_GANG_1_DODGE_9, + SFX_CUBAN_LORD_GANG_1_DODGE_10, + SFX_CUBAN_LORD_GANG_1_DODGE_11, + SFX_CUBAN_LORD_GANG_1_DODGE_12, + SFX_CUBAN_LORD_GANG_1_DODGE_13, + SFX_CUBAN_LORD_GANG_1_EYEING_1, + SFX_CUBAN_LORD_GANG_1_EYEING_2, + SFX_CUBAN_LORD_GANG_1_FIGHT_1, + SFX_CUBAN_LORD_GANG_1_FIGHT_2, + SFX_CUBAN_LORD_GANG_1_FIGHT_3, + SFX_CUBAN_LORD_GANG_1_FIGHT_4, + SFX_CUBAN_LORD_GANG_1_FIGHT_5, + SFX_CUBAN_LORD_GANG_1_FIGHT_6, + SFX_CUBAN_LORD_GANG_1_FIGHT_7, + SFX_CUBAN_LORD_GANG_1_FIGHT_8, + SFX_CUBAN_LORD_GANG_1_FIGHT_9, + SFX_CUBAN_LORD_GANG_1_FIGHT_10, + SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_1, + SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_2, + SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_3, + SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_4, + SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_5, + SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_6, + SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_7, + SFX_CUBAN_LORD_GANG_1_GENERIC_CRASH_8, + SFX_CUBAN_LORD_GANG_1_GUN_COOL_1, + SFX_CUBAN_LORD_GANG_1_GUN_COOL_2, + SFX_CUBAN_LORD_GANG_1_GUN_COOL_3, + SFX_CUBAN_LORD_GANG_1_GUN_COOL_4, + SFX_CUBAN_LORD_GANG_1_GUN_COOL_5, + SFX_CUBAN_LORD_GANG_1_JACKED_1, + SFX_CUBAN_LORD_GANG_1_JACKED_2, + SFX_CUBAN_LORD_GANG_1_JACKED_3, + SFX_CUBAN_LORD_GANG_1_JACKED_4, + SFX_CUBAN_LORD_GANG_1_JACKED_5, + SFX_CUBAN_LORD_GANG_1_JACKED_6, + SFX_CUBAN_LORD_GANG_1_JACKING_1, + SFX_CUBAN_LORD_GANG_1_JACKING_2, + SFX_CUBAN_LORD_GANG_1_JACKING_3, + SFX_CUBAN_LORD_GANG_1_JACKING_4, + SFX_CUBAN_LORD_GANG_1_JACKING_5, + SFX_CUBAN_LORD_GANG_1_LOST_1, + SFX_CUBAN_LORD_GANG_1_LOST_2, + SFX_CUBAN_LORD_GANG_1_MUGGED_1, + SFX_CUBAN_LORD_GANG_1_MUGGED_2, + SFX_CUBAN_LORD_GANG_1_SAVED_1, + SFX_CUBAN_LORD_GANG_1_TAXI_1, + SFX_CUBAN_LORD_GANG_1_TAXI_2, + SFX_CUBAN_LORD_GANG_2_BLOCKED_1, + SFX_CUBAN_LORD_GANG_2_BLOCKED_2, + SFX_CUBAN_LORD_GANG_2_BLOCKED_3, + SFX_CUBAN_LORD_GANG_2_BLOCKED_4, + SFX_CUBAN_LORD_GANG_2_BLOCKED_5, + SFX_CUBAN_LORD_GANG_2_BLOCKED_6, + SFX_CUBAN_LORD_GANG_2_BLOCKED_7, + SFX_CUBAN_LORD_GANG_2_BLOCKED_8, + SFX_CUBAN_LORD_GANG_2_BLOCKED_9, + SFX_CUBAN_LORD_GANG_2_BLOCKED_10, + SFX_CUBAN_LORD_GANG_2_BUMP_1, + SFX_CUBAN_LORD_GANG_2_BUMP_2, + SFX_CUBAN_LORD_GANG_2_BUMP_3, + SFX_CUBAN_LORD_GANG_2_BUMP_4, + SFX_CUBAN_LORD_GANG_2_BUMP_5, + SFX_CUBAN_LORD_GANG_2_BUMP_6, + SFX_CUBAN_LORD_GANG_2_BUMP_7, + SFX_CUBAN_LORD_GANG_2_BUMP_8, + SFX_CUBAN_LORD_GANG_2_BUMP_9, + SFX_CUBAN_LORD_GANG_2_BUMP_10, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_1, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_2, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_3, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_4, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_5, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_6, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_7, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_8, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_9, + SFX_CUBAN_LORD_GANG_2_CAR_CRASH_10, + SFX_CUBAN_LORD_GANG_2_CHAT_1, + SFX_CUBAN_LORD_GANG_2_CHAT_2, + SFX_CUBAN_LORD_GANG_2_CHAT_3, + SFX_CUBAN_LORD_GANG_2_CHAT_4, + SFX_CUBAN_LORD_GANG_2_CHAT_5, + SFX_CUBAN_LORD_GANG_2_CHAT_6, + SFX_CUBAN_LORD_GANG_2_CHAT_7, + SFX_CUBAN_LORD_GANG_2_CHAT_8, + SFX_CUBAN_LORD_GANG_2_CHAT_9, + SFX_CUBAN_LORD_GANG_2_CHAT_10, + SFX_CUBAN_LORD_GANG_2_DODGE_1, + SFX_CUBAN_LORD_GANG_2_DODGE_2, + SFX_CUBAN_LORD_GANG_2_DODGE_3, + SFX_CUBAN_LORD_GANG_2_DODGE_4, + SFX_CUBAN_LORD_GANG_2_DODGE_5, + SFX_CUBAN_LORD_GANG_2_DODGE_6, + SFX_CUBAN_LORD_GANG_2_DODGE_7, + SFX_CUBAN_LORD_GANG_2_DODGE_8, + SFX_CUBAN_LORD_GANG_2_DODGE_9, + SFX_CUBAN_LORD_GANG_2_DODGE_10, + SFX_CUBAN_LORD_GANG_2_DODGE_11, + SFX_CUBAN_LORD_GANG_2_DODGE_12, + SFX_CUBAN_LORD_GANG_2_DODGE_13, + SFX_CUBAN_LORD_GANG_2_EYEING_1, + SFX_CUBAN_LORD_GANG_2_EYEING_2, + SFX_CUBAN_LORD_GANG_2_FIGHT_1, + SFX_CUBAN_LORD_GANG_2_FIGHT_2, + SFX_CUBAN_LORD_GANG_2_FIGHT_3, + SFX_CUBAN_LORD_GANG_2_FIGHT_4, + SFX_CUBAN_LORD_GANG_2_FIGHT_5, + SFX_CUBAN_LORD_GANG_2_FIGHT_6, + SFX_CUBAN_LORD_GANG_2_FIGHT_7, + SFX_CUBAN_LORD_GANG_2_FIGHT_8, + SFX_CUBAN_LORD_GANG_2_FIGHT_9, + SFX_CUBAN_LORD_GANG_2_FIGHT_10, + SFX_CUBAN_LORD_GANG_2_GENERIC_CRASH_1, + SFX_CUBAN_LORD_GANG_2_GENERIC_CRASH_2, + SFX_CUBAN_LORD_GANG_2_GENERIC_CRASH_3, + SFX_CUBAN_LORD_GANG_2_GENERIC_CRASH_4, + SFX_CUBAN_LORD_GANG_2_GENERIC_CRASH_5, + SFX_CUBAN_LORD_GANG_2_GENERIC_CRASH_6, + SFX_CUBAN_LORD_GANG_2_GENERIC_CRASH_7, + SFX_CUBAN_LORD_GANG_2_GENERIC_CRASH_8, + SFX_CUBAN_LORD_GANG_2_GUN_COOL_1, + SFX_CUBAN_LORD_GANG_2_GUN_COOL_2, + SFX_CUBAN_LORD_GANG_2_GUN_COOL_3, + SFX_CUBAN_LORD_GANG_2_GUN_COOL_4, + SFX_CUBAN_LORD_GANG_2_GUN_COOL_5, + SFX_CUBAN_LORD_GANG_2_JACKED_1, + SFX_CUBAN_LORD_GANG_2_JACKED_2, + SFX_CUBAN_LORD_GANG_2_JACKED_3, + SFX_CUBAN_LORD_GANG_2_JACKED_4, + SFX_CUBAN_LORD_GANG_2_JACKED_5, + SFX_CUBAN_LORD_GANG_2_JACKED_6, + SFX_CUBAN_LORD_GANG_2_JACKING_1, + SFX_CUBAN_LORD_GANG_2_JACKING_2, + SFX_CUBAN_LORD_GANG_2_JACKING_3, + SFX_CUBAN_LORD_GANG_2_JACKING_4, + SFX_CUBAN_LORD_GANG_2_JACKING_5, + SFX_CUBAN_LORD_GANG_2_LOST_1, + SFX_CUBAN_LORD_GANG_2_LOST_2, + SFX_CUBAN_LORD_GANG_2_MUGGED_1, + SFX_CUBAN_LORD_GANG_2_MUGGED_2, + SFX_CUBAN_LORD_GANG_2_SAVED_1, + SFX_CUBAN_LORD_GANG_2_TAXI_1, + SFX_CUBAN_LORD_GANG_2_TAXI_2, + SFX_CUBAN_LORD_GANG_3_BLOCKED_1, + SFX_CUBAN_LORD_GANG_3_BLOCKED_2, + SFX_CUBAN_LORD_GANG_3_BLOCKED_3, + SFX_CUBAN_LORD_GANG_3_BLOCKED_4, + SFX_CUBAN_LORD_GANG_3_BLOCKED_5, + SFX_CUBAN_LORD_GANG_3_BLOCKED_6, + SFX_CUBAN_LORD_GANG_3_BLOCKED_7, + SFX_CUBAN_LORD_GANG_3_BLOCKED_8, + SFX_CUBAN_LORD_GANG_3_BLOCKED_9, + SFX_CUBAN_LORD_GANG_3_BLOCKED_10, + SFX_CUBAN_LORD_GANG_3_BUMP_1, + SFX_CUBAN_LORD_GANG_3_BUMP_2, + SFX_CUBAN_LORD_GANG_3_BUMP_3, + SFX_CUBAN_LORD_GANG_3_BUMP_4, + SFX_CUBAN_LORD_GANG_3_BUMP_5, + SFX_CUBAN_LORD_GANG_3_BUMP_6, + SFX_CUBAN_LORD_GANG_3_BUMP_7, + SFX_CUBAN_LORD_GANG_3_BUMP_8, + SFX_CUBAN_LORD_GANG_3_BUMP_9, + SFX_CUBAN_LORD_GANG_3_BUMP_10, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_1, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_2, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_3, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_4, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_5, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_6, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_7, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_8, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_9, + SFX_CUBAN_LORD_GANG_3_CAR_CRASH_10, + SFX_CUBAN_LORD_GANG_3_CHAT_1, + SFX_CUBAN_LORD_GANG_3_CHAT_2, + SFX_CUBAN_LORD_GANG_3_CHAT_3, + SFX_CUBAN_LORD_GANG_3_CHAT_4, + SFX_CUBAN_LORD_GANG_3_CHAT_5, + SFX_CUBAN_LORD_GANG_3_CHAT_6, + SFX_CUBAN_LORD_GANG_3_CHAT_7, + SFX_CUBAN_LORD_GANG_3_CHAT_8, + SFX_CUBAN_LORD_GANG_3_CHAT_9, + SFX_CUBAN_LORD_GANG_3_CHAT_10, + SFX_CUBAN_LORD_GANG_3_DODGE_1, + SFX_CUBAN_LORD_GANG_3_DODGE_2, + SFX_CUBAN_LORD_GANG_3_DODGE_3, + SFX_CUBAN_LORD_GANG_3_DODGE_4, + SFX_CUBAN_LORD_GANG_3_DODGE_5, + SFX_CUBAN_LORD_GANG_3_DODGE_6, + SFX_CUBAN_LORD_GANG_3_DODGE_7, + SFX_CUBAN_LORD_GANG_3_DODGE_8, + SFX_CUBAN_LORD_GANG_3_DODGE_9, + SFX_CUBAN_LORD_GANG_3_DODGE_10, + SFX_CUBAN_LORD_GANG_3_DODGE_11, + SFX_CUBAN_LORD_GANG_3_DODGE_12, + SFX_CUBAN_LORD_GANG_3_DODGE_13, + SFX_CUBAN_LORD_GANG_3_EYEING_1, + SFX_CUBAN_LORD_GANG_3_EYEING_2, + SFX_CUBAN_LORD_GANG_3_FIGHT_1, + SFX_CUBAN_LORD_GANG_3_FIGHT_2, + SFX_CUBAN_LORD_GANG_3_FIGHT_3, + SFX_CUBAN_LORD_GANG_3_FIGHT_4, + SFX_CUBAN_LORD_GANG_3_FIGHT_5, + SFX_CUBAN_LORD_GANG_3_FIGHT_6, + SFX_CUBAN_LORD_GANG_3_FIGHT_7, + SFX_CUBAN_LORD_GANG_3_FIGHT_8, + SFX_CUBAN_LORD_GANG_3_FIGHT_9, + SFX_CUBAN_LORD_GANG_3_FIGHT_10, + SFX_CUBAN_LORD_GANG_3_GENERIC_CRASH_1, + SFX_CUBAN_LORD_GANG_3_GENERIC_CRASH_2, + SFX_CUBAN_LORD_GANG_3_GENERIC_CRASH_3, + SFX_CUBAN_LORD_GANG_3_GENERIC_CRASH_4, + SFX_CUBAN_LORD_GANG_3_GENERIC_CRASH_5, + SFX_CUBAN_LORD_GANG_3_GENERIC_CRASH_6, + SFX_CUBAN_LORD_GANG_3_GENERIC_CRASH_7, + SFX_CUBAN_LORD_GANG_3_GENERIC_CRASH_8, + SFX_CUBAN_LORD_GANG_3_GUN_COOL_1, + SFX_CUBAN_LORD_GANG_3_GUN_COOL_2, + SFX_CUBAN_LORD_GANG_3_GUN_COOL_3, + SFX_CUBAN_LORD_GANG_3_GUN_COOL_4, + SFX_CUBAN_LORD_GANG_3_GUN_COOL_5, + SFX_CUBAN_LORD_GANG_3_JACKED_1, + SFX_CUBAN_LORD_GANG_3_JACKED_2, + SFX_CUBAN_LORD_GANG_3_JACKED_3, + SFX_CUBAN_LORD_GANG_3_JACKED_4, + SFX_CUBAN_LORD_GANG_3_JACKED_5, + SFX_CUBAN_LORD_GANG_3_JACKED_6, + SFX_CUBAN_LORD_GANG_3_JACKING_1, + SFX_CUBAN_LORD_GANG_3_JACKING_2, + SFX_CUBAN_LORD_GANG_3_JACKING_3, + SFX_CUBAN_LORD_GANG_3_JACKING_4, + SFX_CUBAN_LORD_GANG_3_JACKING_5, + SFX_CUBAN_LORD_GANG_3_LOST_1, + SFX_CUBAN_LORD_GANG_3_LOST_2, + SFX_CUBAN_LORD_GANG_3_MUGGED_1, + SFX_CUBAN_LORD_GANG_3_MUGGED_2, + SFX_CUBAN_LORD_GANG_3_SAVED_1, + SFX_CUBAN_LORD_GANG_3_TAXI_1, + SFX_CUBAN_LORD_GANG_3_TAXI_2, + + SFX_PLAYER_GANG_1_BLOCKED_1, + SFX_PLAYER_GANG_1_BLOCKED_2, + SFX_PLAYER_GANG_1_BLOCKED_3, + SFX_PLAYER_GANG_1_BLOCKED_4, + SFX_PLAYER_GANG_1_BLOCKED_5, + SFX_PLAYER_GANG_1_BLOCKED_6, + SFX_PLAYER_GANG_1_BLOCKED_7, + SFX_PLAYER_GANG_1_BLOCKED_8, + SFX_PLAYER_GANG_1_BLOCKED_9, + SFX_PLAYER_GANG_1_BLOCKED_10, + SFX_PLAYER_GANG_1_BUMP_1, + SFX_PLAYER_GANG_1_BUMP_2, + SFX_PLAYER_GANG_1_BUMP_3, + SFX_PLAYER_GANG_1_BUMP_4, + SFX_PLAYER_GANG_1_BUMP_5, + SFX_PLAYER_GANG_1_CAR_CRASH_1, + SFX_PLAYER_GANG_1_CAR_CRASH_2, + SFX_PLAYER_GANG_1_CAR_CRASH_3, + SFX_PLAYER_GANG_1_CAR_CRASH_4, + SFX_PLAYER_GANG_1_CAR_CRASH_5, + SFX_PLAYER_GANG_1_CHAT_1, + SFX_PLAYER_GANG_1_CHAT_2, + SFX_PLAYER_GANG_1_CHAT_3, + SFX_PLAYER_GANG_1_CHAT_4, + SFX_PLAYER_GANG_1_CHAT_5, + SFX_PLAYER_GANG_1_CHAT_6, + SFX_PLAYER_GANG_1_CHAT_7, + SFX_PLAYER_GANG_1_CHAT_8, + SFX_PLAYER_GANG_1_DODGE_1, + SFX_PLAYER_GANG_1_DODGE_2, + SFX_PLAYER_GANG_1_DODGE_3, + SFX_PLAYER_GANG_1_DODGE_4, + SFX_PLAYER_GANG_1_DODGE_5, + SFX_PLAYER_GANG_1_DODGE_6, + SFX_PLAYER_GANG_1_DODGE_7, + SFX_PLAYER_GANG_1_EYEING_1, + SFX_PLAYER_GANG_1_EYEING_2, + SFX_PLAYER_GANG_1_FIGHT_1, + SFX_PLAYER_GANG_1_FIGHT_2, + SFX_PLAYER_GANG_1_FIGHT_3, + SFX_PLAYER_GANG_1_FIGHT_4, + SFX_PLAYER_GANG_1_FIGHT_5, + SFX_PLAYER_GANG_1_GENERIC_CRASH_1, + SFX_PLAYER_GANG_1_GENERIC_CRASH_2, + SFX_PLAYER_GANG_1_GENERIC_CRASH_3, + SFX_PLAYER_GANG_1_GENERIC_CRASH_4, + SFX_PLAYER_GANG_1_GENERIC_CRASH_5, + SFX_PLAYER_GANG_1_GUN_COOL_1, + SFX_PLAYER_GANG_1_GUN_COOL_2, + SFX_PLAYER_GANG_1_GUN_COOL_3, + SFX_PLAYER_GANG_1_GUN_COOL_4, + SFX_PLAYER_GANG_1_JACKED_1, + SFX_PLAYER_GANG_1_JACKED_2, + SFX_PLAYER_GANG_1_JACKED_3, + SFX_PLAYER_GANG_1_JACKED_4, + SFX_PLAYER_GANG_1_JACKED_5, + SFX_PLAYER_GANG_1_JACKING_1, + SFX_PLAYER_GANG_1_JACKING_2, + SFX_PLAYER_GANG_1_JACKING_3, + SFX_PLAYER_GANG_1_JACKING_4, + SFX_PLAYER_GANG_1_JACKING_5, + SFX_PLAYER_GANG_1_LOST_1, + SFX_PLAYER_GANG_1_LOST_2, + SFX_PLAYER_GANG_1_MUGGED_1, + SFX_PLAYER_GANG_1_MUGGED_2, + SFX_PLAYER_GANG_1_SAVED_1, + SFX_PLAYER_GANG_1_SHOCKED_1, + SFX_PLAYER_GANG_1_SHOCKED_2, + SFX_PLAYER_GANG_1_TAXI_1, + SFX_PLAYER_GANG_1_TAXI_2, + SFX_PLAYER_GANG_2_BLOCKED_1, + SFX_PLAYER_GANG_2_BLOCKED_2, + SFX_PLAYER_GANG_2_BLOCKED_3, + SFX_PLAYER_GANG_2_BLOCKED_4, + SFX_PLAYER_GANG_2_BLOCKED_5, + SFX_PLAYER_GANG_2_BLOCKED_6, + SFX_PLAYER_GANG_2_BLOCKED_7, + SFX_PLAYER_GANG_2_BLOCKED_8, + SFX_PLAYER_GANG_2_BLOCKED_9, + SFX_PLAYER_GANG_2_BLOCKED_10, + SFX_PLAYER_GANG_2_BUMP_1, + SFX_PLAYER_GANG_2_BUMP_2, + SFX_PLAYER_GANG_2_BUMP_3, + SFX_PLAYER_GANG_2_BUMP_4, + SFX_PLAYER_GANG_2_BUMP_5, + SFX_PLAYER_GANG_2_CAR_CRASH_1, + SFX_PLAYER_GANG_2_CAR_CRASH_2, + SFX_PLAYER_GANG_2_CAR_CRASH_3, + SFX_PLAYER_GANG_2_CAR_CRASH_4, + SFX_PLAYER_GANG_2_CAR_CRASH_5, + SFX_PLAYER_GANG_2_CHAT_1, + SFX_PLAYER_GANG_2_CHAT_2, + SFX_PLAYER_GANG_2_CHAT_3, + SFX_PLAYER_GANG_2_CHAT_4, + SFX_PLAYER_GANG_2_CHAT_5, + SFX_PLAYER_GANG_2_CHAT_6, + SFX_PLAYER_GANG_2_CHAT_7, + SFX_PLAYER_GANG_2_CHAT_8, + SFX_PLAYER_GANG_2_DODGE_1, + SFX_PLAYER_GANG_2_DODGE_2, + SFX_PLAYER_GANG_2_DODGE_3, + SFX_PLAYER_GANG_2_DODGE_4, + SFX_PLAYER_GANG_2_DODGE_5, + SFX_PLAYER_GANG_2_DODGE_6, + SFX_PLAYER_GANG_2_DODGE_7, + SFX_PLAYER_GANG_2_EYEING_1, + SFX_PLAYER_GANG_2_EYEING_2, + SFX_PLAYER_GANG_2_FIGHT_1, + SFX_PLAYER_GANG_2_FIGHT_2, + SFX_PLAYER_GANG_2_FIGHT_3, + SFX_PLAYER_GANG_2_FIGHT_4, + SFX_PLAYER_GANG_2_FIGHT_5, + SFX_PLAYER_GANG_2_GENERIC_CRASH_1, + SFX_PLAYER_GANG_2_GENERIC_CRASH_2, + SFX_PLAYER_GANG_2_GENERIC_CRASH_3, + SFX_PLAYER_GANG_2_GENERIC_CRASH_4, + SFX_PLAYER_GANG_2_GENERIC_CRASH_5, + SFX_PLAYER_GANG_2_GUN_COOL_1, + SFX_PLAYER_GANG_2_GUN_COOL_2, + SFX_PLAYER_GANG_2_GUN_COOL_3, + SFX_PLAYER_GANG_2_GUN_COOL_4, + SFX_PLAYER_GANG_2_JACKED_1, + SFX_PLAYER_GANG_2_JACKED_2, + SFX_PLAYER_GANG_2_JACKED_3, + SFX_PLAYER_GANG_2_JACKED_4, + SFX_PLAYER_GANG_2_JACKED_5, + SFX_PLAYER_GANG_2_JACKING_1, + SFX_PLAYER_GANG_2_JACKING_2, + SFX_PLAYER_GANG_2_JACKING_3, + SFX_PLAYER_GANG_2_JACKING_4, + SFX_PLAYER_GANG_2_JACKING_5, + SFX_PLAYER_GANG_2_LOST_1, + SFX_PLAYER_GANG_2_LOST_2, + SFX_PLAYER_GANG_2_MUGGED_1, + SFX_PLAYER_GANG_2_MUGGED_2, + SFX_PLAYER_GANG_2_SAVED_1, + SFX_PLAYER_GANG_2_SHOCKED_1, + SFX_PLAYER_GANG_2_SHOCKED_2, + SFX_PLAYER_GANG_2_TAXI_1, + SFX_PLAYER_GANG_2_TAXI_2, + SFX_PLAYER_GANG_3_BLOCKED_1, + SFX_PLAYER_GANG_3_BLOCKED_2, + SFX_PLAYER_GANG_3_BLOCKED_3, + SFX_PLAYER_GANG_3_BLOCKED_4, + SFX_PLAYER_GANG_3_BLOCKED_5, + SFX_PLAYER_GANG_3_BLOCKED_6, + SFX_PLAYER_GANG_3_BLOCKED_7, + SFX_PLAYER_GANG_3_BLOCKED_8, + SFX_PLAYER_GANG_3_BLOCKED_9, + SFX_PLAYER_GANG_3_BLOCKED_10, + SFX_PLAYER_GANG_3_BUMP_1, + SFX_PLAYER_GANG_3_BUMP_2, + SFX_PLAYER_GANG_3_BUMP_3, + SFX_PLAYER_GANG_3_BUMP_4, + SFX_PLAYER_GANG_3_BUMP_5, + SFX_PLAYER_GANG_3_CAR_CRASH_1, + SFX_PLAYER_GANG_3_CAR_CRASH_2, + SFX_PLAYER_GANG_3_CAR_CRASH_3, + SFX_PLAYER_GANG_3_CAR_CRASH_4, + SFX_PLAYER_GANG_3_CAR_CRASH_5, + SFX_PLAYER_GANG_3_CHAT_1, + SFX_PLAYER_GANG_3_CHAT_2, + SFX_PLAYER_GANG_3_CHAT_3, + SFX_PLAYER_GANG_3_CHAT_4, + SFX_PLAYER_GANG_3_CHAT_5, + SFX_PLAYER_GANG_3_CHAT_6, + SFX_PLAYER_GANG_3_CHAT_7, + SFX_PLAYER_GANG_3_CHAT_8, + SFX_PLAYER_GANG_3_DODGE_1, + SFX_PLAYER_GANG_3_DODGE_2, + SFX_PLAYER_GANG_3_DODGE_3, + SFX_PLAYER_GANG_3_DODGE_4, + SFX_PLAYER_GANG_3_DODGE_5, + SFX_PLAYER_GANG_3_DODGE_6, + SFX_PLAYER_GANG_3_DODGE_7, + SFX_PLAYER_GANG_3_EYEING_1, + SFX_PLAYER_GANG_3_EYEING_2, + SFX_PLAYER_GANG_3_FIGHT_1, + SFX_PLAYER_GANG_3_FIGHT_2, + SFX_PLAYER_GANG_3_FIGHT_3, + SFX_PLAYER_GANG_3_FIGHT_4, + SFX_PLAYER_GANG_3_FIGHT_5, + SFX_PLAYER_GANG_3_GENERIC_CRASH_1, + SFX_PLAYER_GANG_3_GENERIC_CRASH_2, + SFX_PLAYER_GANG_3_GENERIC_CRASH_3, + SFX_PLAYER_GANG_3_GENERIC_CRASH_4, + SFX_PLAYER_GANG_3_GENERIC_CRASH_5, + SFX_PLAYER_GANG_3_GUN_COOL_1, + SFX_PLAYER_GANG_3_GUN_COOL_2, + SFX_PLAYER_GANG_3_GUN_COOL_3, + SFX_PLAYER_GANG_3_GUN_COOL_4, + SFX_PLAYER_GANG_3_JACKED_1, + SFX_PLAYER_GANG_3_JACKED_2, + SFX_PLAYER_GANG_3_JACKED_3, + SFX_PLAYER_GANG_3_JACKED_4, + SFX_PLAYER_GANG_3_JACKED_5, + SFX_PLAYER_GANG_3_JACKING_1, + SFX_PLAYER_GANG_3_JACKING_2, + SFX_PLAYER_GANG_3_JACKING_3, + SFX_PLAYER_GANG_3_JACKING_4, + SFX_PLAYER_GANG_3_JACKING_5, + SFX_PLAYER_GANG_3_LOST_1, + SFX_PLAYER_GANG_3_LOST_2, + SFX_PLAYER_GANG_3_MUGGED_1, + SFX_PLAYER_GANG_3_MUGGED_2, + SFX_PLAYER_GANG_3_SAVED_1, + SFX_PLAYER_GANG_3_SHOCKED_1, + SFX_PLAYER_GANG_3_SHOCKED_2, + SFX_PLAYER_GANG_3_TAXI_1, + SFX_PLAYER_GANG_3_TAXI_2, + + SFX_GUARD_DUTY_1_BUMP_1, + SFX_GUARD_DUTY_1_BUMP_2, + SFX_GUARD_DUTY_1_BUMP_3, + SFX_GUARD_DUTY_1_BUMP_4, + SFX_GUARD_DUTY_1_BUMP_5, + SFX_GUARD_DUTY_1_BUMP_6, + SFX_GUARD_DUTY_1_BUMP_7, + SFX_GUARD_DUTY_1_BUMP_8, + SFX_GUARD_DUTY_1_BUMP_9, + SFX_GUARD_DUTY_1_BUMP_10, + SFX_GUARD_DUTY_1_CHAT_1, + SFX_GUARD_DUTY_1_CHAT_2, + SFX_GUARD_DUTY_1_CHAT_3, + SFX_GUARD_DUTY_1_CHAT_4, + SFX_GUARD_DUTY_1_CHAT_5, + SFX_GUARD_DUTY_1_CHAT_6, + SFX_GUARD_DUTY_1_CHAT_7, + SFX_GUARD_DUTY_1_CHAT_8, + SFX_GUARD_DUTY_1_CHAT_9, + SFX_GUARD_DUTY_1_CHAT_10, + SFX_GUARD_DUTY_1_DODGE_1, + SFX_GUARD_DUTY_1_DODGE_2, + SFX_GUARD_DUTY_1_DODGE_3, + SFX_GUARD_DUTY_1_DODGE_4, + SFX_GUARD_DUTY_1_DODGE_5, + SFX_GUARD_DUTY_1_DODGE_6, + SFX_GUARD_DUTY_1_DODGE_7, + SFX_GUARD_DUTY_1_DODGE_8, + SFX_GUARD_DUTY_1_DODGE_9, + SFX_GUARD_DUTY_1_EYEING_1, + SFX_GUARD_DUTY_1_EYEING_2, + SFX_GUARD_DUTY_1_FIGHT_1, + SFX_GUARD_DUTY_1_FIGHT_2, + SFX_GUARD_DUTY_1_FIGHT_3, + SFX_GUARD_DUTY_1_FIGHT_4, + SFX_GUARD_DUTY_1_FIGHT_5, + SFX_GUARD_DUTY_1_FIGHT_6, + SFX_GUARD_DUTY_1_FIGHT_7, + SFX_GUARD_DUTY_1_GUN_COOL_1, + SFX_GUARD_DUTY_1_GUN_COOL_2, + SFX_GUARD_DUTY_1_GUN_COOL_3, + SFX_GUARD_DUTY_1_GUN_COOL_4, + SFX_GUARD_DUTY_1_GUN_COOL_5, + SFX_GUARD_DUTY_1_GUN_COOL_6, + SFX_GUARD_DUTY_1_LOST_1, + SFX_GUARD_DUTY_1_LOST_2, + SFX_GUARD_DUTY_1_SAVED_1, + SFX_GUARD_DUTY_1_SAVED_2, + SFX_GUARD_DUTY_1_SHOCKED_1, + SFX_GUARD_DUTY_1_SHOCKED_2, + + SFX_GUARD_DUTY_2_BUMP_1, + SFX_GUARD_DUTY_2_BUMP_2, + SFX_GUARD_DUTY_2_BUMP_3, + SFX_GUARD_DUTY_2_BUMP_4, + SFX_GUARD_DUTY_2_BUMP_5, + SFX_GUARD_DUTY_2_BUMP_6, + SFX_GUARD_DUTY_2_BUMP_7, + SFX_GUARD_DUTY_2_BUMP_8, + SFX_GUARD_DUTY_2_BUMP_9, + SFX_GUARD_DUTY_2_BUMP_10, + SFX_GUARD_DUTY_2_CHAT_1, + SFX_GUARD_DUTY_2_CHAT_2, + SFX_GUARD_DUTY_2_CHAT_3, + SFX_GUARD_DUTY_2_CHAT_4, + SFX_GUARD_DUTY_2_CHAT_5, + SFX_GUARD_DUTY_2_CHAT_6, + SFX_GUARD_DUTY_2_CHAT_7, + SFX_GUARD_DUTY_2_CHAT_8, + SFX_GUARD_DUTY_2_CHAT_9, + SFX_GUARD_DUTY_2_CHAT_10, + SFX_GUARD_DUTY_2_DODGE_1, + SFX_GUARD_DUTY_2_DODGE_2, + SFX_GUARD_DUTY_2_DODGE_3, + SFX_GUARD_DUTY_2_DODGE_4, + SFX_GUARD_DUTY_2_DODGE_5, + SFX_GUARD_DUTY_2_DODGE_6, + SFX_GUARD_DUTY_2_DODGE_7, + SFX_GUARD_DUTY_2_DODGE_8, + SFX_GUARD_DUTY_2_DODGE_9, + SFX_GUARD_DUTY_2_EYEING_1, + SFX_GUARD_DUTY_2_EYEING_2, + SFX_GUARD_DUTY_2_FIGHT_1, + SFX_GUARD_DUTY_2_FIGHT_2, + SFX_GUARD_DUTY_2_FIGHT_3, + SFX_GUARD_DUTY_2_FIGHT_4, + SFX_GUARD_DUTY_2_FIGHT_5, + SFX_GUARD_DUTY_2_FIGHT_6, + SFX_GUARD_DUTY_2_FIGHT_7, + SFX_GUARD_DUTY_2_GUN_COOL_1, + SFX_GUARD_DUTY_2_GUN_COOL_2, + SFX_GUARD_DUTY_2_GUN_COOL_3, + SFX_GUARD_DUTY_2_GUN_COOL_4, + SFX_GUARD_DUTY_2_GUN_COOL_5, + SFX_GUARD_DUTY_2_GUN_COOL_6, + SFX_GUARD_DUTY_2_LOST_1, + SFX_GUARD_DUTY_2_LOST_2, + SFX_GUARD_DUTY_2_SAVED_1, + SFX_GUARD_DUTY_2_SAVED_2, + SFX_GUARD_DUTY_2_SHOCKED_1, + SFX_GUARD_DUTY_2_SHOCKED_2, + + SFX_GUARD_DUTY_3_BUMP_1, + SFX_GUARD_DUTY_3_BUMP_2, + SFX_GUARD_DUTY_3_BUMP_3, + SFX_GUARD_DUTY_3_BUMP_4, + SFX_GUARD_DUTY_3_BUMP_5, + SFX_GUARD_DUTY_3_BUMP_6, + SFX_GUARD_DUTY_3_BUMP_7, + SFX_GUARD_DUTY_3_BUMP_8, + SFX_GUARD_DUTY_3_BUMP_9, + SFX_GUARD_DUTY_3_BUMP_10, + SFX_GUARD_DUTY_3_CHAT_1, + SFX_GUARD_DUTY_3_CHAT_2, + SFX_GUARD_DUTY_3_CHAT_3, + SFX_GUARD_DUTY_3_CHAT_4, + SFX_GUARD_DUTY_3_CHAT_5, + SFX_GUARD_DUTY_3_CHAT_6, + SFX_GUARD_DUTY_3_CHAT_7, + SFX_GUARD_DUTY_3_CHAT_8, + SFX_GUARD_DUTY_3_CHAT_9, + SFX_GUARD_DUTY_3_CHAT_10, + SFX_GUARD_DUTY_3_DODGE_1, + SFX_GUARD_DUTY_3_DODGE_2, + SFX_GUARD_DUTY_3_DODGE_3, + SFX_GUARD_DUTY_3_DODGE_4, + SFX_GUARD_DUTY_3_DODGE_5, + SFX_GUARD_DUTY_3_DODGE_6, + SFX_GUARD_DUTY_3_DODGE_7, + SFX_GUARD_DUTY_3_DODGE_8, + SFX_GUARD_DUTY_3_DODGE_9, + SFX_GUARD_DUTY_3_EYEING_1, + SFX_GUARD_DUTY_3_EYEING_2, + SFX_GUARD_DUTY_3_FIGHT_1, + SFX_GUARD_DUTY_3_FIGHT_2, + SFX_GUARD_DUTY_3_FIGHT_3, + SFX_GUARD_DUTY_3_FIGHT_4, + SFX_GUARD_DUTY_3_FIGHT_5, + SFX_GUARD_DUTY_3_FIGHT_6, + SFX_GUARD_DUTY_3_FIGHT_7, + SFX_GUARD_DUTY_3_GUN_COOL_1, + SFX_GUARD_DUTY_3_GUN_COOL_2, + SFX_GUARD_DUTY_3_GUN_COOL_3, + SFX_GUARD_DUTY_3_GUN_COOL_4, + SFX_GUARD_DUTY_3_GUN_COOL_5, + SFX_GUARD_DUTY_3_GUN_COOL_6, + SFX_GUARD_DUTY_3_LOST_1, + SFX_GUARD_DUTY_3_LOST_2, + SFX_GUARD_DUTY_3_SAVED_1, + SFX_GUARD_DUTY_3_SAVED_2, + SFX_GUARD_DUTY_3_SHOCKED_1, + SFX_GUARD_DUTY_3_SHOCKED_2, + + SFX_VICE_VOICE_1_ARREST_1, + SFX_VICE_VOICE_1_ARREST_2, + SFX_VICE_VOICE_1_ARREST_3, + SFX_VICE_VOICE_1_MIAMIVICE_EXITING_CAR_1, + + SFX_VICE_VOICE_2_ARREST_1, + SFX_VICE_VOICE_2_ARREST_2, + SFX_VICE_VOICE_2_ARREST_3, + SFX_VICE_VOICE_2_MIAMIVICE_EXITING_CAR_1, + + SFX_VICE_VOICE_3_ARREST_1, + SFX_VICE_VOICE_3_ARREST_2, + SFX_VICE_VOICE_3_ARREST_3, + SFX_VICE_VOICE_3_MIAMIVICE_EXITING_CAR_1, + + SFX_VICE_VOICE_4_ARREST_1, + SFX_VICE_VOICE_4_ARREST_2, + SFX_VICE_VOICE_4_ARREST_3, + SFX_VICE_VOICE_4_MIAMIVICE_EXITING_CAR_1, + + SFX_VICE_VOICE_5_ARREST_1, + SFX_VICE_VOICE_5_ARREST_2, + SFX_VICE_VOICE_5_ARREST_3, + SFX_VICE_VOICE_5_MIAMIVICE_EXITING_CAR_1, + + SFX_VICE_VOICE_6_ARREST_1, + SFX_VICE_VOICE_6_ARREST_2, + SFX_VICE_VOICE_6_ARREST_3, + SFX_VICE_VOICE_6_MIAMIVICE_EXITING_CAR_1, + + SFX_DEFAULT_VOICE_BLOCKED_1, + SFX_DEFAULT_VOICE_BLOCKED_2, + SFX_DEFAULT_VOICE_BLOCKED_3, + SFX_DEFAULT_VOICE_BLOCKED_4, + SFX_DEFAULT_VOICE_BLOCKED_5, + SFX_DEFAULT_VOICE_BLOCKED_6, + SFX_DEFAULT_VOICE_BLOCKED_7, + SFX_DEFAULT_VOICE_BLOCKED_8, + SFX_DEFAULT_VOICE_BLOCKED_9, + SFX_DEFAULT_VOICE_BLOCKED_10, + SFX_DEFAULT_VOICE_BLOCKED_11, + SFX_DEFAULT_VOICE_BLOCKED_12, + SFX_DEFAULT_VOICE_BLOCKED_13, + SFX_DEFAULT_VOICE_BLOCKED_14, + SFX_DEFAULT_VOICE_BLOCKED_15, + SFX_DEFAULT_VOICE_BLOCKED_16, + SFX_DEFAULT_VOICE_BUMP_1, + SFX_DEFAULT_VOICE_BUMP_2, + SFX_DEFAULT_VOICE_BUMP_3, + SFX_DEFAULT_VOICE_BUMP_4, + SFX_DEFAULT_VOICE_BUMP_5, + SFX_DEFAULT_VOICE_BUMP_6, + SFX_DEFAULT_VOICE_BUMP_7, + SFX_DEFAULT_VOICE_BUMP_8, + SFX_DEFAULT_VOICE_BUMP_9, + SFX_DEFAULT_VOICE_BUMP_10, + SFX_DEFAULT_VOICE_BUMP_11, + SFX_DEFAULT_VOICE_BUMP_12, + SFX_DEFAULT_VOICE_BUMP_13, + SFX_DEFAULT_VOICE_BUMP_14, + SFX_DEFAULT_VOICE_BUMP_15, + SFX_DEFAULT_VOICE_BUMP_16, + SFX_DEFAULT_VOICE_BUMP_17, + SFX_DEFAULT_VOICE_BUMP_18, + SFX_DEFAULT_VOICE_BUMP_19, + SFX_DEFAULT_VOICE_BUMP_20, + SFX_DEFAULT_VOICE_BUMP_21, + SFX_DEFAULT_VOICE_BUMP_22, + SFX_DEFAULT_VOICE_BUMP_23, + SFX_DEFAULT_VOICE_BUMP_24, + SFX_DEFAULT_VOICE_BUMP_25, + SFX_DEFAULT_VOICE_CAR_CRASH_1, + SFX_DEFAULT_VOICE_CAR_CRASH_2, + SFX_DEFAULT_VOICE_CAR_CRASH_3, + SFX_DEFAULT_VOICE_CAR_CRASH_4, + SFX_DEFAULT_VOICE_CAR_CRASH_5, + SFX_DEFAULT_VOICE_CAR_CRASH_6, + SFX_DEFAULT_VOICE_CAR_CRASH_7, + SFX_DEFAULT_VOICE_CAR_CRASH_8, + SFX_DEFAULT_VOICE_CAR_CRASH_9, + SFX_DEFAULT_VOICE_CAR_CRASH_10, + SFX_DEFAULT_VOICE_CAR_CRASH_11, + SFX_DEFAULT_VOICE_CAR_CRASH_12, + SFX_DEFAULT_VOICE_CAR_CRASH_13, + SFX_DEFAULT_VOICE_CAR_CRASH_14, + SFX_DEFAULT_VOICE_CAR_CRASH_15, + SFX_DEFAULT_VOICE_CHAT_1, + SFX_DEFAULT_VOICE_CHAT_2, + SFX_DEFAULT_VOICE_CHAT_3, + SFX_DEFAULT_VOICE_CHAT_4, + SFX_DEFAULT_VOICE_CHAT_5, + SFX_DEFAULT_VOICE_CHAT_6, + SFX_DEFAULT_VOICE_CHAT_7, + SFX_DEFAULT_VOICE_CHAT_8, + SFX_DEFAULT_VOICE_CHAT_9, + SFX_DEFAULT_VOICE_CHAT_10, + SFX_DEFAULT_VOICE_CHAT_11, + SFX_DEFAULT_VOICE_CHAT_12, + SFX_DEFAULT_VOICE_CHAT_13, + SFX_DEFAULT_VOICE_CHAT_14, + SFX_DEFAULT_VOICE_CHAT_15, + SFX_DEFAULT_VOICE_CHAT_16, + SFX_DEFAULT_VOICE_CHAT_17, + SFX_DEFAULT_VOICE_CHAT_18, + SFX_DEFAULT_VOICE_CHAT_19, + SFX_DEFAULT_VOICE_CHAT_20, + SFX_DEFAULT_VOICE_CHAT_21, + SFX_DEFAULT_VOICE_CHAT_22, + SFX_DEFAULT_VOICE_CHAT_23, + SFX_DEFAULT_VOICE_CHAT_24, + SFX_DEFAULT_VOICE_CHAT_25, + SFX_DEFAULT_VOICE_DODGE_1, + SFX_DEFAULT_VOICE_DODGE_2, + SFX_DEFAULT_VOICE_DODGE_3, + SFX_DEFAULT_VOICE_DODGE_4, + SFX_DEFAULT_VOICE_DODGE_5, + SFX_DEFAULT_VOICE_DODGE_6, + SFX_DEFAULT_VOICE_DODGE_7, + SFX_DEFAULT_VOICE_DODGE_8, + SFX_DEFAULT_VOICE_DODGE_9, + SFX_DEFAULT_VOICE_DODGE_10, + SFX_DEFAULT_VOICE_DODGE_11, + SFX_DEFAULT_VOICE_DODGE_12, + SFX_DEFAULT_VOICE_DODGE_13, + SFX_DEFAULT_VOICE_DODGE_14, + SFX_DEFAULT_VOICE_DODGE_15, + SFX_DEFAULT_VOICE_DODGE_16, + SFX_DEFAULT_VOICE_DODGE_17, + SFX_DEFAULT_VOICE_DODGE_18, + SFX_DEFAULT_VOICE_DODGE_19, + SFX_DEFAULT_VOICE_EYEING_1, + SFX_DEFAULT_VOICE_EYEING_2, + SFX_DEFAULT_VOICE_EYEING_3, + SFX_DEFAULT_VOICE_EYEING_4, + SFX_DEFAULT_VOICE_EYEING_5, + SFX_DEFAULT_VOICE_EYEING_6, + SFX_DEFAULT_VOICE_FIGHT_1, + SFX_DEFAULT_VOICE_FIGHT_2, + SFX_DEFAULT_VOICE_FIGHT_3, + SFX_DEFAULT_VOICE_FIGHT_4, + SFX_DEFAULT_VOICE_FIGHT_5, + SFX_DEFAULT_VOICE_FIGHT_6, + SFX_DEFAULT_VOICE_FIGHT_7, + SFX_DEFAULT_VOICE_FIGHT_8, + SFX_DEFAULT_VOICE_FIGHT_9, + SFX_DEFAULT_VOICE_FIGHT_10, + SFX_DEFAULT_VOICE_FIGHT_11, + SFX_DEFAULT_VOICE_FIGHT_12, + SFX_DEFAULT_VOICE_FIGHT_13, + SFX_DEFAULT_VOICE_FIGHT_14, + SFX_DEFAULT_VOICE_FIGHT_15, + SFX_DEFAULT_VOICE_FIGHT_16, + SFX_DEFAULT_VOICE_GENERIC_CRASH_1, + SFX_DEFAULT_VOICE_GENERIC_CRASH_2, + SFX_DEFAULT_VOICE_GENERIC_CRASH_3, + SFX_DEFAULT_VOICE_GENERIC_CRASH_4, + SFX_DEFAULT_VOICE_GENERIC_CRASH_5, + SFX_DEFAULT_VOICE_GENERIC_CRASH_6, + SFX_DEFAULT_VOICE_GENERIC_CRASH_7, + SFX_DEFAULT_VOICE_GENERIC_CRASH_8, + SFX_DEFAULT_VOICE_GENERIC_CRASH_9, + SFX_DEFAULT_VOICE_GENERIC_CRASH_10, + SFX_DEFAULT_VOICE_GENERIC_CRASH_11, + SFX_DEFAULT_VOICE_GENERIC_CRASH_12, + SFX_DEFAULT_VOICE_GENERIC_CRASH_13, + SFX_DEFAULT_VOICE_GUN_PANIC_1, + SFX_DEFAULT_VOICE_GUN_PANIC_2, + SFX_DEFAULT_VOICE_GUN_PANIC_3, + SFX_DEFAULT_VOICE_GUN_PANIC_4, + SFX_DEFAULT_VOICE_GUN_PANIC_5, + SFX_DEFAULT_VOICE_GUN_PANIC_6, + SFX_DEFAULT_VOICE_GUN_PANIC_7, + SFX_DEFAULT_VOICE_GUN_PANIC_8, + SFX_DEFAULT_VOICE_GUN_PANIC_9, + SFX_DEFAULT_VOICE_GUN_PANIC_10, + SFX_DEFAULT_VOICE_GUN_PANIC_11, + SFX_DEFAULT_VOICE_GUN_PANIC_12, + SFX_DEFAULT_VOICE_JACKED_1, + SFX_DEFAULT_VOICE_JACKED_2, + SFX_DEFAULT_VOICE_JACKED_3, + SFX_DEFAULT_VOICE_JACKED_4, + SFX_DEFAULT_VOICE_JACKED_5, + SFX_DEFAULT_VOICE_JACKED_6, + SFX_DEFAULT_VOICE_JACKED_7, + SFX_DEFAULT_VOICE_JACKED_8, + SFX_DEFAULT_VOICE_JACKED_9, + SFX_DEFAULT_VOICE_JACKED_10, + SFX_DEFAULT_VOICE_JACKED_11, + SFX_DEFAULT_VOICE_JACKED_12, + SFX_DEFAULT_VOICE_JACKING_1, + SFX_DEFAULT_VOICE_JACKING_2, + SFX_DEFAULT_VOICE_JACKING_3, + SFX_DEFAULT_VOICE_JACKING_4, + SFX_DEFAULT_VOICE_JACKING_5, + SFX_DEFAULT_VOICE_JACKING_6, + SFX_DEFAULT_VOICE_JACKING_7, + SFX_DEFAULT_VOICE_JACKING_8, + SFX_DEFAULT_VOICE_JACKING_9, + SFX_DEFAULT_VOICE_JACKING_10, + SFX_DEFAULT_VOICE_JACKING_11, + SFX_DEFAULT_VOICE_JACKING_12, + SFX_DEFAULT_VOICE_JACKING_13, + SFX_DEFAULT_VOICE_LOST_1, + SFX_DEFAULT_VOICE_LOST_2, + SFX_DEFAULT_VOICE_LOST_3, + SFX_DEFAULT_VOICE_LOST_4, + SFX_DEFAULT_VOICE_LOST_5, + SFX_DEFAULT_VOICE_MUGGED_1, + SFX_DEFAULT_VOICE_MUGGED_2, + SFX_DEFAULT_VOICE_MUGGED_3, + SFX_DEFAULT_VOICE_MUGGED_4, + SFX_DEFAULT_VOICE_RUN_1, + SFX_DEFAULT_VOICE_RUN_2, + SFX_DEFAULT_VOICE_RUN_3, + SFX_DEFAULT_VOICE_RUN_4, + SFX_DEFAULT_VOICE_RUN_5, + SFX_DEFAULT_VOICE_RUN_6, + SFX_DEFAULT_VOICE_RUN_7, + SFX_DEFAULT_VOICE_RUN_8, + SFX_DEFAULT_VOICE_RUN_9, + SFX_DEFAULT_VOICE_RUN_10, + SFX_DEFAULT_VOICE_RUN_11, + SFX_DEFAULT_VOICE_RUN_12, + SFX_DEFAULT_VOICE_RUN_13, + SFX_DEFAULT_VOICE_RUN_14, + SFX_DEFAULT_VOICE_RUN_15, + SFX_DEFAULT_VOICE_RUN_16, + SFX_DEFAULT_VOICE_RUN_17, + SFX_DEFAULT_VOICE_RUN_18, + SFX_DEFAULT_VOICE_RUN_19, + SFX_DEFAULT_VOICE_SAVED_1, + SFX_DEFAULT_VOICE_SAVED_2, + SFX_DEFAULT_VOICE_SAVED_3, + SFX_DEFAULT_VOICE_SAVED_4, + SFX_DEFAULT_VOICE_SHOCKED_1, + SFX_DEFAULT_VOICE_SHOCKED_2, + SFX_DEFAULT_VOICE_SHOCKED_3, + SFX_DEFAULT_VOICE_SHOCKED_4, + SFX_DEFAULT_VOICE_SHOCKED_5, + SFX_DEFAULT_VOICE_SHOCKED_6, + SFX_DEFAULT_VOICE_TAXI_1, + SFX_DEFAULT_VOICE_TAXI_2, + SFX_DEFAULT_VOICE_TAXI_3, + SFX_DEFAULT_VOICE_TAXI_4, + SFX_DEFAULT_VOICE_TAXI_5, + + SFX_CUBAN_GANG_1_BLOCKED_1, + SFX_CUBAN_GANG_1_BLOCKED_2, + SFX_CUBAN_GANG_1_BLOCKED_3, + SFX_CUBAN_GANG_1_BLOCKED_4, + SFX_CUBAN_GANG_1_BLOCKED_5, + SFX_CUBAN_GANG_1_BLOCKED_6, + SFX_CUBAN_GANG_1_BLOCKED_7, + SFX_CUBAN_GANG_1_BLOCKED_8, + SFX_CUBAN_GANG_1_BUMP_1, + SFX_CUBAN_GANG_1_BUMP_2, + SFX_CUBAN_GANG_1_BUMP_3, + SFX_CUBAN_GANG_1_BUMP_4, + SFX_CUBAN_GANG_1_BUMP_5, + SFX_CUBAN_GANG_1_BUMP_6, + SFX_CUBAN_GANG_1_BUMP_7, + SFX_CUBAN_GANG_1_BUMP_8, + SFX_CUBAN_GANG_1_BUMP_9, + SFX_CUBAN_GANG_1_BUMP_10, + SFX_CUBAN_GANG_1_BUMP_11, + SFX_CUBAN_GANG_1_CAR_CRASH_1, + SFX_CUBAN_GANG_1_CAR_CRASH_2, + SFX_CUBAN_GANG_1_CAR_CRASH_3, + SFX_CUBAN_GANG_1_CAR_CRASH_4, + SFX_CUBAN_GANG_1_CAR_CRASH_5, + SFX_CUBAN_GANG_1_CAR_CRASH_6, + SFX_CUBAN_GANG_1_CAR_CRASH_7, + SFX_CUBAN_GANG_1_CAR_CRASH_8, + SFX_CUBAN_GANG_1_CHAT_1, + SFX_CUBAN_GANG_1_CHAT_2, + SFX_CUBAN_GANG_1_CHAT_3, + SFX_CUBAN_GANG_1_CHAT_4, + SFX_CUBAN_GANG_1_CHAT_5, + SFX_CUBAN_GANG_1_CHAT_6, + SFX_CUBAN_GANG_1_CHAT_7, + SFX_CUBAN_GANG_1_CHAT_8, + SFX_CUBAN_GANG_1_CHAT_9, + SFX_CUBAN_GANG_1_CHAT_10, + SFX_CUBAN_GANG_1_DODGE_1, + SFX_CUBAN_GANG_1_DODGE_2, + SFX_CUBAN_GANG_1_DODGE_3, + SFX_CUBAN_GANG_1_DODGE_4, + SFX_CUBAN_GANG_1_DODGE_5, + SFX_CUBAN_GANG_1_DODGE_6, + SFX_CUBAN_GANG_1_DODGE_7, + SFX_CUBAN_GANG_1_DODGE_8, + SFX_CUBAN_GANG_1_DODGE_9, + SFX_CUBAN_GANG_1_EYEING_1, + SFX_CUBAN_GANG_1_EYEING_2, + SFX_CUBAN_GANG_1_FIGHT_1, + SFX_CUBAN_GANG_1_FIGHT_2, + SFX_CUBAN_GANG_1_FIGHT_3, + SFX_CUBAN_GANG_1_FIGHT_4, + SFX_CUBAN_GANG_1_FIGHT_5, + SFX_CUBAN_GANG_1_FIGHT_6, + SFX_CUBAN_GANG_1_FIGHT_7, + SFX_CUBAN_GANG_1_FIGHT_8, + SFX_CUBAN_GANG_1_FIGHT_9, + SFX_CUBAN_GANG_1_GENERIC_CRASH_1, + SFX_CUBAN_GANG_1_GENERIC_CRASH_2, + SFX_CUBAN_GANG_1_GENERIC_CRASH_3, + SFX_CUBAN_GANG_1_GENERIC_CRASH_4, + SFX_CUBAN_GANG_1_GENERIC_CRASH_5, + SFX_CUBAN_GANG_1_GENERIC_CRASH_6, + SFX_CUBAN_GANG_1_GENERIC_CRASH_7, + SFX_CUBAN_GANG_1_GENERIC_CRASH_8, + SFX_CUBAN_GANG_1_GUN_COOL_1, + SFX_CUBAN_GANG_1_GUN_COOL_2, + SFX_CUBAN_GANG_1_GUN_COOL_3, + SFX_CUBAN_GANG_1_GUN_COOL_4, + SFX_CUBAN_GANG_1_GUN_COOL_5, + SFX_CUBAN_GANG_1_JACKED_1, + SFX_CUBAN_GANG_1_JACKED_2, + SFX_CUBAN_GANG_1_JACKED_3, + SFX_CUBAN_GANG_1_JACKED_4, + SFX_CUBAN_GANG_1_JACKING_1, + SFX_CUBAN_GANG_1_JACKING_2, + SFX_CUBAN_GANG_1_JACKING_3, + SFX_CUBAN_GANG_1_JACKING_4, + SFX_CUBAN_GANG_1_JACKING_5, + SFX_CUBAN_GANG_1_LOST_1, + SFX_CUBAN_GANG_1_LOST_2, + SFX_CUBAN_GANG_1_MUGGED_1, + SFX_CUBAN_GANG_1_MUGGED_2, + SFX_CUBAN_GANG_1_SAVED_1, + SFX_CUBAN_GANG_1_TAXI_1, + SFX_CUBAN_GANG_1_TAXI_2, + SFX_CUBAN_GANG_2_BLOCKED_1, + SFX_CUBAN_GANG_2_BLOCKED_2, + SFX_CUBAN_GANG_2_BLOCKED_3, + SFX_CUBAN_GANG_2_BLOCKED_4, + SFX_CUBAN_GANG_2_BLOCKED_5, + SFX_CUBAN_GANG_2_BLOCKED_6, + SFX_CUBAN_GANG_2_BLOCKED_7, + SFX_CUBAN_GANG_2_BLOCKED_8, + SFX_CUBAN_GANG_2_BUMP_1, + SFX_CUBAN_GANG_2_BUMP_2, + SFX_CUBAN_GANG_2_BUMP_3, + SFX_CUBAN_GANG_2_BUMP_4, + SFX_CUBAN_GANG_2_BUMP_5, + SFX_CUBAN_GANG_2_BUMP_6, + SFX_CUBAN_GANG_2_BUMP_7, + SFX_CUBAN_GANG_2_BUMP_8, + SFX_CUBAN_GANG_2_BUMP_9, + SFX_CUBAN_GANG_2_BUMP_10, + SFX_CUBAN_GANG_2_BUMP_11, + SFX_CUBAN_GANG_2_CAR_CRASH_1, + SFX_CUBAN_GANG_2_CAR_CRASH_2, + SFX_CUBAN_GANG_2_CAR_CRASH_3, + SFX_CUBAN_GANG_2_CAR_CRASH_4, + SFX_CUBAN_GANG_2_CAR_CRASH_5, + SFX_CUBAN_GANG_2_CAR_CRASH_6, + SFX_CUBAN_GANG_2_CAR_CRASH_7, + SFX_CUBAN_GANG_2_CAR_CRASH_8, + SFX_CUBAN_GANG_2_CHAT_1, + SFX_CUBAN_GANG_2_CHAT_2, + SFX_CUBAN_GANG_2_CHAT_3, + SFX_CUBAN_GANG_2_CHAT_4, + SFX_CUBAN_GANG_2_CHAT_5, + SFX_CUBAN_GANG_2_CHAT_6, + SFX_CUBAN_GANG_2_CHAT_7, + SFX_CUBAN_GANG_2_CHAT_8, + SFX_CUBAN_GANG_2_CHAT_9, + SFX_CUBAN_GANG_2_CHAT_10, + SFX_CUBAN_GANG_2_DODGE_1, + SFX_CUBAN_GANG_2_DODGE_2, + SFX_CUBAN_GANG_2_DODGE_3, + SFX_CUBAN_GANG_2_DODGE_4, + SFX_CUBAN_GANG_2_DODGE_5, + SFX_CUBAN_GANG_2_DODGE_6, + SFX_CUBAN_GANG_2_DODGE_7, + SFX_CUBAN_GANG_2_DODGE_8, + SFX_CUBAN_GANG_2_DODGE_9, + SFX_CUBAN_GANG_2_EYEING_1, + SFX_CUBAN_GANG_2_EYEING_2, + SFX_CUBAN_GANG_2_FIGHT_1, + SFX_CUBAN_GANG_2_FIGHT_2, + SFX_CUBAN_GANG_2_FIGHT_3, + SFX_CUBAN_GANG_2_FIGHT_4, + SFX_CUBAN_GANG_2_FIGHT_5, + SFX_CUBAN_GANG_2_FIGHT_6, + SFX_CUBAN_GANG_2_FIGHT_7, + SFX_CUBAN_GANG_2_FIGHT_8, + SFX_CUBAN_GANG_2_FIGHT_9, + SFX_CUBAN_GANG_2_GENERIC_CRASH_1, + SFX_CUBAN_GANG_2_GENERIC_CRASH_2, + SFX_CUBAN_GANG_2_GENERIC_CRASH_3, + SFX_CUBAN_GANG_2_GENERIC_CRASH_4, + SFX_CUBAN_GANG_2_GENERIC_CRASH_5, + SFX_CUBAN_GANG_2_GENERIC_CRASH_6, + SFX_CUBAN_GANG_2_GENERIC_CRASH_7, + SFX_CUBAN_GANG_2_GENERIC_CRASH_8, + SFX_CUBAN_GANG_2_GUN_COOL_1, + SFX_CUBAN_GANG_2_GUN_COOL_2, + SFX_CUBAN_GANG_2_GUN_COOL_3, + SFX_CUBAN_GANG_2_GUN_COOL_4, + SFX_CUBAN_GANG_2_GUN_COOL_5, + SFX_CUBAN_GANG_2_JACKED_1, + SFX_CUBAN_GANG_2_JACKED_2, + SFX_CUBAN_GANG_2_JACKED_3, + SFX_CUBAN_GANG_2_JACKED_4, + SFX_CUBAN_GANG_2_JACKING_1, + SFX_CUBAN_GANG_2_JACKING_2, + SFX_CUBAN_GANG_2_JACKING_3, + SFX_CUBAN_GANG_2_JACKING_4, + SFX_CUBAN_GANG_2_JACKING_5, + SFX_CUBAN_GANG_2_LOST_1, + SFX_CUBAN_GANG_2_LOST_2, + SFX_CUBAN_GANG_2_MUGGED_1, + SFX_CUBAN_GANG_2_MUGGED_2, + SFX_CUBAN_GANG_2_SAVED_1, + SFX_CUBAN_GANG_2_TAXI_1, + SFX_CUBAN_GANG_2_TAXI_2, + SFX_CUBAN_GANG_3_BLOCKED_1, + SFX_CUBAN_GANG_3_BLOCKED_2, + SFX_CUBAN_GANG_3_BLOCKED_3, + SFX_CUBAN_GANG_3_BLOCKED_4, + SFX_CUBAN_GANG_3_BLOCKED_5, + SFX_CUBAN_GANG_3_BLOCKED_6, + SFX_CUBAN_GANG_3_BLOCKED_7, + SFX_CUBAN_GANG_3_BLOCKED_8, + SFX_CUBAN_GANG_3_BUMP_1, + SFX_CUBAN_GANG_3_BUMP_2, + SFX_CUBAN_GANG_3_BUMP_3, + SFX_CUBAN_GANG_3_BUMP_4, + SFX_CUBAN_GANG_3_BUMP_5, + SFX_CUBAN_GANG_3_BUMP_6, + SFX_CUBAN_GANG_3_BUMP_7, + SFX_CUBAN_GANG_3_BUMP_8, + SFX_CUBAN_GANG_3_BUMP_9, + SFX_CUBAN_GANG_3_BUMP_10, + SFX_CUBAN_GANG_3_BUMP_11, + SFX_CUBAN_GANG_3_CAR_CRASH_1, + SFX_CUBAN_GANG_3_CAR_CRASH_2, + SFX_CUBAN_GANG_3_CAR_CRASH_3, + SFX_CUBAN_GANG_3_CAR_CRASH_4, + SFX_CUBAN_GANG_3_CAR_CRASH_5, + SFX_CUBAN_GANG_3_CAR_CRASH_6, + SFX_CUBAN_GANG_3_CAR_CRASH_7, + SFX_CUBAN_GANG_3_CAR_CRASH_8, + SFX_CUBAN_GANG_3_CHAT_1, + SFX_CUBAN_GANG_3_CHAT_2, + SFX_CUBAN_GANG_3_CHAT_3, + SFX_CUBAN_GANG_3_CHAT_4, + SFX_CUBAN_GANG_3_CHAT_5, + SFX_CUBAN_GANG_3_CHAT_6, + SFX_CUBAN_GANG_3_CHAT_7, + SFX_CUBAN_GANG_3_CHAT_8, + SFX_CUBAN_GANG_3_CHAT_9, + SFX_CUBAN_GANG_3_CHAT_10, + SFX_CUBAN_GANG_3_DODGE_1, + SFX_CUBAN_GANG_3_DODGE_2, + SFX_CUBAN_GANG_3_DODGE_3, + SFX_CUBAN_GANG_3_DODGE_4, + SFX_CUBAN_GANG_3_DODGE_5, + SFX_CUBAN_GANG_3_DODGE_6, + SFX_CUBAN_GANG_3_DODGE_7, + SFX_CUBAN_GANG_3_DODGE_8, + SFX_CUBAN_GANG_3_DODGE_9, + SFX_CUBAN_GANG_3_EYEING_1, + SFX_CUBAN_GANG_3_EYEING_2, + SFX_CUBAN_GANG_3_FIGHT_1, + SFX_CUBAN_GANG_3_FIGHT_2, + SFX_CUBAN_GANG_3_FIGHT_3, + SFX_CUBAN_GANG_3_FIGHT_4, + SFX_CUBAN_GANG_3_FIGHT_5, + SFX_CUBAN_GANG_3_FIGHT_6, + SFX_CUBAN_GANG_3_FIGHT_7, + SFX_CUBAN_GANG_3_FIGHT_8, + SFX_CUBAN_GANG_3_FIGHT_9, + SFX_CUBAN_GANG_3_GENERIC_CRASH_1, + SFX_CUBAN_GANG_3_GENERIC_CRASH_2, + SFX_CUBAN_GANG_3_GENERIC_CRASH_3, + SFX_CUBAN_GANG_3_GENERIC_CRASH_4, + SFX_CUBAN_GANG_3_GENERIC_CRASH_5, + SFX_CUBAN_GANG_3_GENERIC_CRASH_6, + SFX_CUBAN_GANG_3_GENERIC_CRASH_7, + SFX_CUBAN_GANG_3_GENERIC_CRASH_8, + SFX_CUBAN_GANG_3_GUN_COOL_1, + SFX_CUBAN_GANG_3_GUN_COOL_2, + SFX_CUBAN_GANG_3_GUN_COOL_3, + SFX_CUBAN_GANG_3_GUN_COOL_4, + SFX_CUBAN_GANG_3_GUN_COOL_5, + SFX_CUBAN_GANG_3_JACKED_1, + SFX_CUBAN_GANG_3_JACKED_2, + SFX_CUBAN_GANG_3_JACKED_3, + SFX_CUBAN_GANG_3_JACKED_4, + SFX_CUBAN_GANG_3_JACKING_1, + SFX_CUBAN_GANG_3_JACKING_2, + SFX_CUBAN_GANG_3_JACKING_3, + SFX_CUBAN_GANG_3_JACKING_4, + SFX_CUBAN_GANG_3_JACKING_5, + SFX_CUBAN_GANG_3_LOST_1, + SFX_CUBAN_GANG_3_LOST_2, + SFX_CUBAN_GANG_3_MUGGED_1, + SFX_CUBAN_GANG_3_MUGGED_2, + SFX_CUBAN_GANG_3_SAVED_1, + SFX_CUBAN_GANG_3_TAXI_1, + SFX_CUBAN_GANG_3_TAXI_2, + + SFX_BIKER_GANG_1_BLOCKED_1, + SFX_BIKER_GANG_1_BLOCKED_2, + SFX_BIKER_GANG_1_BLOCKED_3, + SFX_BIKER_GANG_1_BLOCKED_4, + SFX_BIKER_GANG_1_BLOCKED_5, + SFX_BIKER_GANG_1_BLOCKED_6, + SFX_BIKER_GANG_1_BLOCKED_7, + SFX_BIKER_GANG_1_BLOCKED_8, + SFX_BIKER_GANG_1_BLOCKED_9, + SFX_BIKER_GANG_1_BLOCKED_10, + SFX_BIKER_GANG_1_BUMP_1, + SFX_BIKER_GANG_1_BUMP_2, + SFX_BIKER_GANG_1_BUMP_3, + SFX_BIKER_GANG_1_BUMP_4, + SFX_BIKER_GANG_1_BUMP_5, + SFX_BIKER_GANG_1_BUMP_6, + SFX_BIKER_GANG_1_BUMP_7, + SFX_BIKER_GANG_1_BUMP_8, + SFX_BIKER_GANG_1_BUMP_9, + SFX_BIKER_GANG_1_BUMP_10, + SFX_BIKER_GANG_1_CHAT_1, + SFX_BIKER_GANG_1_CHAT_2, + SFX_BIKER_GANG_1_CHAT_3, + SFX_BIKER_GANG_1_CHAT_4, + SFX_BIKER_GANG_1_CHAT_5, + SFX_BIKER_GANG_1_CHAT_6, + SFX_BIKER_GANG_1_CHAT_7, + SFX_BIKER_GANG_1_CHAT_8, + SFX_BIKER_GANG_1_CHAT_9, + SFX_BIKER_GANG_1_CHAT_10, + SFX_BIKER_GANG_1_CHAT_11, + SFX_BIKER_GANG_1_CHAT_12, + SFX_BIKER_GANG_1_DODGE_1, + SFX_BIKER_GANG_1_DODGE_2, + SFX_BIKER_GANG_1_DODGE_3, + SFX_BIKER_GANG_1_DODGE_4, + SFX_BIKER_GANG_1_DODGE_5, + SFX_BIKER_GANG_1_DODGE_6, + SFX_BIKER_GANG_1_DODGE_7, + SFX_BIKER_GANG_1_DODGE_8, + SFX_BIKER_GANG_1_DODGE_9, + SFX_BIKER_GANG_1_FIGHT_1, + SFX_BIKER_GANG_1_FIGHT_2, + SFX_BIKER_GANG_1_FIGHT_3, + SFX_BIKER_GANG_1_FIGHT_4, + SFX_BIKER_GANG_1_FIGHT_5, + SFX_BIKER_GANG_1_FIGHT_6, + SFX_BIKER_GANG_1_FIGHT_7, + SFX_BIKER_GANG_1_FIGHT_8, + SFX_BIKER_GANG_1_FIGHT_9, + SFX_BIKER_GANG_1_GENERIC_CRASH_1, + SFX_BIKER_GANG_1_GENERIC_CRASH_2, + SFX_BIKER_GANG_1_GENERIC_CRASH_3, + SFX_BIKER_GANG_1_GENERIC_CRASH_4, + SFX_BIKER_GANG_1_GENERIC_CRASH_5, + SFX_BIKER_GANG_1_GENERIC_CRASH_6, + SFX_BIKER_GANG_1_GENERIC_CRASH_7, + SFX_BIKER_GANG_1_GENERIC_CRASH_8, + SFX_BIKER_GANG_1_GUN_COOL_1, + SFX_BIKER_GANG_1_GUN_COOL_2, + SFX_BIKER_GANG_1_GUN_COOL_3, + SFX_BIKER_GANG_1_GUN_COOL_4, + SFX_BIKER_GANG_1_GUN_COOL_5, + SFX_BIKER_GANG_1_JACKED_1, + SFX_BIKER_GANG_1_JACKED_2, + SFX_BIKER_GANG_1_JACKED_3, + SFX_BIKER_GANG_1_JACKED_4, + SFX_BIKER_GANG_1_JACKED_5, + SFX_BIKER_GANG_1_JACKED_6, + SFX_BIKER_GANG_1_JACKED_7, + SFX_BIKER_GANG_1_JACKED_8, + SFX_BIKER_GANG_1_JACKING_1, + SFX_BIKER_GANG_1_JACKING_2, + SFX_BIKER_GANG_1_JACKING_3, + SFX_BIKER_GANG_1_JACKING_4, + SFX_BIKER_GANG_1_LOST_1, + SFX_BIKER_GANG_1_LOST_2, + SFX_BIKER_GANG_1_MUGGED_1, + SFX_BIKER_GANG_1_MUGGED_2, + SFX_BIKER_GANG_1_SAVED_1, + SFX_BIKER_GANG_1_TAXI_1, + SFX_BIKER_GANG_1_TAXI_2, + + SFX_BIKER_GANG_2_BLOCKED_1, + SFX_BIKER_GANG_2_BLOCKED_2, + SFX_BIKER_GANG_2_BLOCKED_3, + SFX_BIKER_GANG_2_BLOCKED_4, + SFX_BIKER_GANG_2_BLOCKED_5, + SFX_BIKER_GANG_2_BLOCKED_6, + SFX_BIKER_GANG_2_BLOCKED_7, + SFX_BIKER_GANG_2_BLOCKED_8, + SFX_BIKER_GANG_2_BLOCKED_9, + SFX_BIKER_GANG_2_BLOCKED_10, + SFX_BIKER_GANG_2_BUMP_1, + SFX_BIKER_GANG_2_BUMP_2, + SFX_BIKER_GANG_2_BUMP_3, + SFX_BIKER_GANG_2_BUMP_4, + SFX_BIKER_GANG_2_BUMP_5, + SFX_BIKER_GANG_2_BUMP_6, + SFX_BIKER_GANG_2_BUMP_7, + SFX_BIKER_GANG_2_BUMP_8, + SFX_BIKER_GANG_2_BUMP_9, + SFX_BIKER_GANG_2_BUMP_10, + SFX_BIKER_GANG_2_CHAT_1, + SFX_BIKER_GANG_2_CHAT_2, + SFX_BIKER_GANG_2_CHAT_3, + SFX_BIKER_GANG_2_CHAT_4, + SFX_BIKER_GANG_2_CHAT_5, + SFX_BIKER_GANG_2_CHAT_6, + SFX_BIKER_GANG_2_CHAT_7, + SFX_BIKER_GANG_2_CHAT_8, + SFX_BIKER_GANG_2_CHAT_9, + SFX_BIKER_GANG_2_CHAT_10, + SFX_BIKER_GANG_2_CHAT_11, + SFX_BIKER_GANG_2_CHAT_12, + SFX_BIKER_GANG_2_DODGE_1, + SFX_BIKER_GANG_2_DODGE_2, + SFX_BIKER_GANG_2_DODGE_3, + SFX_BIKER_GANG_2_DODGE_4, + SFX_BIKER_GANG_2_DODGE_5, + SFX_BIKER_GANG_2_DODGE_6, + SFX_BIKER_GANG_2_DODGE_7, + SFX_BIKER_GANG_2_DODGE_8, + SFX_BIKER_GANG_2_DODGE_9, + SFX_BIKER_GANG_2_FIGHT_1, + SFX_BIKER_GANG_2_FIGHT_2, + SFX_BIKER_GANG_2_FIGHT_3, + SFX_BIKER_GANG_2_FIGHT_4, + SFX_BIKER_GANG_2_FIGHT_5, + SFX_BIKER_GANG_2_FIGHT_6, + SFX_BIKER_GANG_2_FIGHT_7, + SFX_BIKER_GANG_2_FIGHT_8, + SFX_BIKER_GANG_2_FIGHT_9, + SFX_BIKER_GANG_2_GENERIC_CRASH_1, + SFX_BIKER_GANG_2_GENERIC_CRASH_2, + SFX_BIKER_GANG_2_GENERIC_CRASH_3, + SFX_BIKER_GANG_2_GENERIC_CRASH_4, + SFX_BIKER_GANG_2_GENERIC_CRASH_5, + SFX_BIKER_GANG_2_GENERIC_CRASH_6, + SFX_BIKER_GANG_2_GENERIC_CRASH_7, + SFX_BIKER_GANG_2_GENERIC_CRASH_8, + SFX_BIKER_GANG_2_GUN_COOL_1, + SFX_BIKER_GANG_2_GUN_COOL_2, + SFX_BIKER_GANG_2_GUN_COOL_3, + SFX_BIKER_GANG_2_GUN_COOL_4, + SFX_BIKER_GANG_2_GUN_COOL_5, + SFX_BIKER_GANG_2_JACKED_1, + SFX_BIKER_GANG_2_JACKED_2, + SFX_BIKER_GANG_2_JACKED_3, + SFX_BIKER_GANG_2_JACKED_4, + SFX_BIKER_GANG_2_JACKED_5, + SFX_BIKER_GANG_2_JACKED_6, + SFX_BIKER_GANG_2_JACKED_7, + SFX_BIKER_GANG_2_JACKED_8, + SFX_BIKER_GANG_2_JACKING_1, + SFX_BIKER_GANG_2_JACKING_2, + SFX_BIKER_GANG_2_JACKING_3, + SFX_BIKER_GANG_2_JACKING_4, + SFX_BIKER_GANG_2_LOST_1, + SFX_BIKER_GANG_2_LOST_2, + SFX_BIKER_GANG_2_MUGGED_1, + SFX_BIKER_GANG_2_MUGGED_2, + SFX_BIKER_GANG_2_SAVED_1, + SFX_BIKER_GANG_2_TAXI_1, + SFX_BIKER_GANG_2_TAXI_2, + + SFX_BIKER_GANG_3_BLOCKED_1, + SFX_BIKER_GANG_3_BLOCKED_2, + SFX_BIKER_GANG_3_BLOCKED_3, + SFX_BIKER_GANG_3_BLOCKED_4, + SFX_BIKER_GANG_3_BLOCKED_5, + SFX_BIKER_GANG_3_BLOCKED_6, + SFX_BIKER_GANG_3_BLOCKED_7, + SFX_BIKER_GANG_3_BLOCKED_8, + SFX_BIKER_GANG_3_BLOCKED_9, + SFX_BIKER_GANG_3_BLOCKED_10, + SFX_BIKER_GANG_3_BUMP_1, + SFX_BIKER_GANG_3_BUMP_2, + SFX_BIKER_GANG_3_BUMP_3, + SFX_BIKER_GANG_3_BUMP_4, + SFX_BIKER_GANG_3_BUMP_5, + SFX_BIKER_GANG_3_BUMP_6, + SFX_BIKER_GANG_3_BUMP_7, + SFX_BIKER_GANG_3_BUMP_8, + SFX_BIKER_GANG_3_BUMP_9, + SFX_BIKER_GANG_3_BUMP_10, + SFX_BIKER_GANG_3_CHAT_1, + SFX_BIKER_GANG_3_CHAT_2, + SFX_BIKER_GANG_3_CHAT_3, + SFX_BIKER_GANG_3_CHAT_4, + SFX_BIKER_GANG_3_CHAT_5, + SFX_BIKER_GANG_3_CHAT_6, + SFX_BIKER_GANG_3_CHAT_7, + SFX_BIKER_GANG_3_CHAT_8, + SFX_BIKER_GANG_3_CHAT_9, + SFX_BIKER_GANG_3_CHAT_10, + SFX_BIKER_GANG_3_CHAT_11, + SFX_BIKER_GANG_3_CHAT_12, + SFX_BIKER_GANG_3_DODGE_1, + SFX_BIKER_GANG_3_DODGE_2, + SFX_BIKER_GANG_3_DODGE_3, + SFX_BIKER_GANG_3_DODGE_4, + SFX_BIKER_GANG_3_DODGE_5, + SFX_BIKER_GANG_3_DODGE_6, + SFX_BIKER_GANG_3_DODGE_7, + SFX_BIKER_GANG_3_DODGE_8, + SFX_BIKER_GANG_3_DODGE_9, + SFX_BIKER_GANG_3_FIGHT_1, + SFX_BIKER_GANG_3_FIGHT_2, + SFX_BIKER_GANG_3_FIGHT_3, + SFX_BIKER_GANG_3_FIGHT_4, + SFX_BIKER_GANG_3_FIGHT_5, + SFX_BIKER_GANG_3_FIGHT_6, + SFX_BIKER_GANG_3_FIGHT_7, + SFX_BIKER_GANG_3_FIGHT_8, + SFX_BIKER_GANG_3_FIGHT_9, + SFX_BIKER_GANG_3_GENERIC_CRASH_1, + SFX_BIKER_GANG_3_GENERIC_CRASH_2, + SFX_BIKER_GANG_3_GENERIC_CRASH_3, + SFX_BIKER_GANG_3_GENERIC_CRASH_4, + SFX_BIKER_GANG_3_GENERIC_CRASH_5, + SFX_BIKER_GANG_3_GENERIC_CRASH_6, + SFX_BIKER_GANG_3_GENERIC_CRASH_7, + SFX_BIKER_GANG_3_GENERIC_CRASH_8, + SFX_BIKER_GANG_3_GUN_COOL_1, + SFX_BIKER_GANG_3_GUN_COOL_2, + SFX_BIKER_GANG_3_GUN_COOL_3, + SFX_BIKER_GANG_3_GUN_COOL_4, + SFX_BIKER_GANG_3_GUN_COOL_5, + SFX_BIKER_GANG_3_JACKED_1, + SFX_BIKER_GANG_3_JACKED_2, + SFX_BIKER_GANG_3_JACKED_3, + SFX_BIKER_GANG_3_JACKED_4, + SFX_BIKER_GANG_3_JACKED_5, + SFX_BIKER_GANG_3_JACKED_6, + SFX_BIKER_GANG_3_JACKED_7, + SFX_BIKER_GANG_3_JACKED_8, + SFX_BIKER_GANG_3_JACKING_1, + SFX_BIKER_GANG_3_JACKING_2, + SFX_BIKER_GANG_3_JACKING_3, + SFX_BIKER_GANG_3_JACKING_4, + SFX_BIKER_GANG_3_LOST_1, + SFX_BIKER_GANG_3_LOST_2, + SFX_BIKER_GANG_3_MUGGED_1, + SFX_BIKER_GANG_3_MUGGED_2, + SFX_BIKER_GANG_3_SAVED_1, + SFX_BIKER_GANG_3_TAXI_1, + SFX_BIKER_GANG_3_TAXI_2, + + SFX_HAITIAN_GANG_1_BLOCKED_1, + SFX_HAITIAN_GANG_1_BLOCKED_2, + SFX_HAITIAN_GANG_1_BLOCKED_3, + SFX_HAITIAN_GANG_1_BLOCKED_4, + SFX_HAITIAN_GANG_1_BLOCKED_5, + SFX_HAITIAN_GANG_1_BLOCKED_6, + SFX_HAITIAN_GANG_1_BLOCKED_7, + SFX_HAITIAN_GANG_1_BLOCKED_8, + SFX_HAITIAN_GANG_1_BLOCKED_9, + SFX_HAITIAN_GANG_1_BUMP_1, + SFX_HAITIAN_GANG_1_BUMP_2, + SFX_HAITIAN_GANG_1_BUMP_3, + SFX_HAITIAN_GANG_1_BUMP_4, + SFX_HAITIAN_GANG_1_BUMP_5, + SFX_HAITIAN_GANG_1_BUMP_6, + SFX_HAITIAN_GANG_1_BUMP_7, + SFX_HAITIAN_GANG_1_BUMP_8, + SFX_HAITIAN_GANG_1_BUMP_9, + SFX_HAITIAN_GANG_1_BUMP_10, + SFX_HAITIAN_GANG_1_BUMP_11, + SFX_HAITIAN_GANG_1_BUMP_12, + SFX_HAITIAN_GANG_1_CAR_CRASH_1, + SFX_HAITIAN_GANG_1_CAR_CRASH_2, + SFX_HAITIAN_GANG_1_CAR_CRASH_3, + SFX_HAITIAN_GANG_1_CAR_CRASH_4, + SFX_HAITIAN_GANG_1_CAR_CRASH_5, + SFX_HAITIAN_GANG_1_CAR_CRASH_6, + SFX_HAITIAN_GANG_1_CAR_CRASH_7, + SFX_HAITIAN_GANG_1_CAR_CRASH_8, + SFX_HAITIAN_GANG_1_CAR_CRASH_9, + SFX_HAITIAN_GANG_1_CHAT_1, + SFX_HAITIAN_GANG_1_CHAT_2, + SFX_HAITIAN_GANG_1_CHAT_3, + SFX_HAITIAN_GANG_1_CHAT_4, + SFX_HAITIAN_GANG_1_CHAT_5, + SFX_HAITIAN_GANG_1_CHAT_6, + SFX_HAITIAN_GANG_1_CHAT_7, + SFX_HAITIAN_GANG_1_CHAT_8, + SFX_HAITIAN_GANG_1_CHAT_9, + SFX_HAITIAN_GANG_1_CHAT_10, + SFX_HAITIAN_GANG_1_CHAT_11, + SFX_HAITIAN_GANG_1_CHAT_12, + SFX_HAITIAN_GANG_1_CHAT_13, + SFX_HAITIAN_GANG_1_CHAT_14, + SFX_HAITIAN_GANG_1_DODGE_1, + SFX_HAITIAN_GANG_1_DODGE_2, + SFX_HAITIAN_GANG_1_DODGE_3, + SFX_HAITIAN_GANG_1_DODGE_4, + SFX_HAITIAN_GANG_1_DODGE_5, + SFX_HAITIAN_GANG_1_DODGE_6, + SFX_HAITIAN_GANG_1_DODGE_7, + SFX_HAITIAN_GANG_1_DODGE_8, + SFX_HAITIAN_GANG_1_DODGE_9, + SFX_HAITIAN_GANG_1_DODGE_10, + SFX_HAITIAN_GANG_1_EYEING_1, + SFX_HAITIAN_GANG_1_EYEING_2, + SFX_HAITIAN_GANG_1_FIGHT_1, + SFX_HAITIAN_GANG_1_FIGHT_2, + SFX_HAITIAN_GANG_1_FIGHT_3, + SFX_HAITIAN_GANG_1_FIGHT_4, + SFX_HAITIAN_GANG_1_FIGHT_5, + SFX_HAITIAN_GANG_1_FIGHT_6, + SFX_HAITIAN_GANG_1_FIGHT_7, + SFX_HAITIAN_GANG_1_FIGHT_8, + SFX_HAITIAN_GANG_1_FIGHT_9, + SFX_HAITIAN_GANG_1_FIGHT_10, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_1, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_2, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_3, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_4, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_5, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_6, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_7, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_8, + SFX_HAITIAN_GANG_1_GENERIC_CRASH_9, + SFX_HAITIAN_GANG_1_GUN_COOL_1, + SFX_HAITIAN_GANG_1_GUN_COOL_2, + SFX_HAITIAN_GANG_1_GUN_COOL_3, + SFX_HAITIAN_GANG_1_GUN_COOL_4, + SFX_HAITIAN_GANG_1_GUN_COOL_5, + SFX_HAITIAN_GANG_1_JACKED_1, + SFX_HAITIAN_GANG_1_JACKED_2, + SFX_HAITIAN_GANG_1_JACKED_3, + SFX_HAITIAN_GANG_1_JACKED_4, + SFX_HAITIAN_GANG_1_JACKED_5, + SFX_HAITIAN_GANG_1_JACKED_6, + SFX_HAITIAN_GANG_1_JACKING_1, + SFX_HAITIAN_GANG_1_JACKING_2, + SFX_HAITIAN_GANG_1_JACKING_3, + SFX_HAITIAN_GANG_1_JACKING_4, + SFX_HAITIAN_GANG_1_LOST_1, + SFX_HAITIAN_GANG_1_LOST_2, + SFX_HAITIAN_GANG_1_LOST_3, + SFX_HAITIAN_GANG_1_LOST_4, + SFX_HAITIAN_GANG_1_MUGGED_1, + SFX_HAITIAN_GANG_1_MUGGED_2, + SFX_HAITIAN_GANG_1_MUGGED_3, + SFX_HAITIAN_GANG_1_SAVED_1, + SFX_HAITIAN_GANG_1_TAXI_1, + + + SFX_HAITIAN_GANG_2_BLOCKED_1, + SFX_HAITIAN_GANG_2_BLOCKED_2, + SFX_HAITIAN_GANG_2_BLOCKED_3, + SFX_HAITIAN_GANG_2_BLOCKED_4, + SFX_HAITIAN_GANG_2_BLOCKED_5, + SFX_HAITIAN_GANG_2_BLOCKED_6, + SFX_HAITIAN_GANG_2_BLOCKED_7, + SFX_HAITIAN_GANG_2_BLOCKED_8, + SFX_HAITIAN_GANG_2_BLOCKED_9, + SFX_HAITIAN_GANG_2_BUMP_1, + SFX_HAITIAN_GANG_2_BUMP_2, + SFX_HAITIAN_GANG_2_BUMP_3, + SFX_HAITIAN_GANG_2_BUMP_4, + SFX_HAITIAN_GANG_2_BUMP_5, + SFX_HAITIAN_GANG_2_BUMP_6, + SFX_HAITIAN_GANG_2_BUMP_7, + SFX_HAITIAN_GANG_2_BUMP_8, + SFX_HAITIAN_GANG_2_BUMP_9, + SFX_HAITIAN_GANG_2_BUMP_10, + SFX_HAITIAN_GANG_2_BUMP_11, + SFX_HAITIAN_GANG_2_BUMP_12, + SFX_HAITIAN_GANG_2_CAR_CRASH_1, + SFX_HAITIAN_GANG_2_CAR_CRASH_2, + SFX_HAITIAN_GANG_2_CAR_CRASH_3, + SFX_HAITIAN_GANG_2_CAR_CRASH_4, + SFX_HAITIAN_GANG_2_CAR_CRASH_5, + SFX_HAITIAN_GANG_2_CAR_CRASH_6, + SFX_HAITIAN_GANG_2_CAR_CRASH_7, + SFX_HAITIAN_GANG_2_CAR_CRASH_8, + SFX_HAITIAN_GANG_2_CAR_CRASH_9, + SFX_HAITIAN_GANG_2_CHAT_1, + SFX_HAITIAN_GANG_2_CHAT_2, + SFX_HAITIAN_GANG_2_CHAT_3, + SFX_HAITIAN_GANG_2_CHAT_4, + SFX_HAITIAN_GANG_2_CHAT_5, + SFX_HAITIAN_GANG_2_CHAT_6, + SFX_HAITIAN_GANG_2_CHAT_7, + SFX_HAITIAN_GANG_2_CHAT_8, + SFX_HAITIAN_GANG_2_CHAT_9, + SFX_HAITIAN_GANG_2_CHAT_10, + SFX_HAITIAN_GANG_2_CHAT_11, + SFX_HAITIAN_GANG_2_CHAT_12, + SFX_HAITIAN_GANG_2_CHAT_13, + SFX_HAITIAN_GANG_2_CHAT_14, + SFX_HAITIAN_GANG_2_DODGE_1, + SFX_HAITIAN_GANG_2_DODGE_2, + SFX_HAITIAN_GANG_2_DODGE_3, + SFX_HAITIAN_GANG_2_DODGE_4, + SFX_HAITIAN_GANG_2_DODGE_5, + SFX_HAITIAN_GANG_2_DODGE_6, + SFX_HAITIAN_GANG_2_DODGE_7, + SFX_HAITIAN_GANG_2_DODGE_8, + SFX_HAITIAN_GANG_2_DODGE_9, + SFX_HAITIAN_GANG_2_DODGE_10, + SFX_HAITIAN_GANG_2_EYEING_1, + SFX_HAITIAN_GANG_2_EYEING_2, + SFX_HAITIAN_GANG_2_FIGHT_1, + SFX_HAITIAN_GANG_2_FIGHT_2, + SFX_HAITIAN_GANG_2_FIGHT_3, + SFX_HAITIAN_GANG_2_FIGHT_4, + SFX_HAITIAN_GANG_2_FIGHT_5, + SFX_HAITIAN_GANG_2_FIGHT_6, + SFX_HAITIAN_GANG_2_FIGHT_7, + SFX_HAITIAN_GANG_2_FIGHT_8, + SFX_HAITIAN_GANG_2_FIGHT_9, + SFX_HAITIAN_GANG_2_FIGHT_10, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_1, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_2, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_3, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_4, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_5, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_6, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_7, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_8, + SFX_HAITIAN_GANG_2_GENERIC_CRASH_9, + SFX_HAITIAN_GANG_2_GUN_COOL_1, + SFX_HAITIAN_GANG_2_GUN_COOL_2, + SFX_HAITIAN_GANG_2_GUN_COOL_3, + SFX_HAITIAN_GANG_2_GUN_COOL_4, + SFX_HAITIAN_GANG_2_GUN_COOL_5, + SFX_HAITIAN_GANG_2_JACKED_1, + SFX_HAITIAN_GANG_2_JACKED_2, + SFX_HAITIAN_GANG_2_JACKED_3, + SFX_HAITIAN_GANG_2_JACKED_4, + SFX_HAITIAN_GANG_2_JACKED_5, + SFX_HAITIAN_GANG_2_JACKED_6, + SFX_HAITIAN_GANG_2_JACKING_1, + SFX_HAITIAN_GANG_2_JACKING_2, + SFX_HAITIAN_GANG_2_JACKING_3, + SFX_HAITIAN_GANG_2_JACKING_4, + SFX_HAITIAN_GANG_2_LOST_1, + SFX_HAITIAN_GANG_2_LOST_2, + SFX_HAITIAN_GANG_2_LOST_3, + SFX_HAITIAN_GANG_2_LOST_4, + SFX_HAITIAN_GANG_2_MUGGED_1, + SFX_HAITIAN_GANG_2_MUGGED_2, + SFX_HAITIAN_GANG_2_MUGGED_3, + SFX_HAITIAN_GANG_2_SAVED_1, + SFX_HAITIAN_GANG_2_TAXI_1, + + SFX_HAITIAN_GANG_3_BLOCKED_1, + SFX_HAITIAN_GANG_3_BLOCKED_2, + SFX_HAITIAN_GANG_3_BLOCKED_3, + SFX_HAITIAN_GANG_3_BLOCKED_4, + SFX_HAITIAN_GANG_3_BLOCKED_5, + SFX_HAITIAN_GANG_3_BLOCKED_6, + SFX_HAITIAN_GANG_3_BLOCKED_7, + SFX_HAITIAN_GANG_3_BLOCKED_8, + SFX_HAITIAN_GANG_3_BLOCKED_9, + SFX_HAITIAN_GANG_3_BUMP_1, + SFX_HAITIAN_GANG_3_BUMP_2, + SFX_HAITIAN_GANG_3_BUMP_3, + SFX_HAITIAN_GANG_3_BUMP_4, + SFX_HAITIAN_GANG_3_BUMP_5, + SFX_HAITIAN_GANG_3_BUMP_6, + SFX_HAITIAN_GANG_3_BUMP_7, + SFX_HAITIAN_GANG_3_BUMP_8, + SFX_HAITIAN_GANG_3_BUMP_9, + SFX_HAITIAN_GANG_3_BUMP_10, + SFX_HAITIAN_GANG_3_BUMP_11, + SFX_HAITIAN_GANG_3_BUMP_12, + SFX_HAITIAN_GANG_3_CAR_CRASH_1, + SFX_HAITIAN_GANG_3_CAR_CRASH_2, + SFX_HAITIAN_GANG_3_CAR_CRASH_3, + SFX_HAITIAN_GANG_3_CAR_CRASH_4, + SFX_HAITIAN_GANG_3_CAR_CRASH_5, + SFX_HAITIAN_GANG_3_CAR_CRASH_6, + SFX_HAITIAN_GANG_3_CAR_CRASH_7, + SFX_HAITIAN_GANG_3_CAR_CRASH_8, + SFX_HAITIAN_GANG_3_CAR_CRASH_9, + SFX_HAITIAN_GANG_3_CHAT_1, + SFX_HAITIAN_GANG_3_CHAT_2, + SFX_HAITIAN_GANG_3_CHAT_3, + SFX_HAITIAN_GANG_3_CHAT_4, + SFX_HAITIAN_GANG_3_CHAT_5, + SFX_HAITIAN_GANG_3_CHAT_6, + SFX_HAITIAN_GANG_3_CHAT_7, + SFX_HAITIAN_GANG_3_CHAT_8, + SFX_HAITIAN_GANG_3_CHAT_9, + SFX_HAITIAN_GANG_3_CHAT_10, + SFX_HAITIAN_GANG_3_CHAT_11, + SFX_HAITIAN_GANG_3_CHAT_12, + SFX_HAITIAN_GANG_3_CHAT_13, + SFX_HAITIAN_GANG_3_CHAT_14, + SFX_HAITIAN_GANG_3_DODGE_1, + SFX_HAITIAN_GANG_3_DODGE_2, + SFX_HAITIAN_GANG_3_DODGE_3, + SFX_HAITIAN_GANG_3_DODGE_4, + SFX_HAITIAN_GANG_3_DODGE_5, + SFX_HAITIAN_GANG_3_DODGE_6, + SFX_HAITIAN_GANG_3_DODGE_7, + SFX_HAITIAN_GANG_3_DODGE_8, + SFX_HAITIAN_GANG_3_DODGE_9, + SFX_HAITIAN_GANG_3_DODGE_10, + SFX_HAITIAN_GANG_3_EYEING_1, + SFX_HAITIAN_GANG_3_EYEING_2, + SFX_HAITIAN_GANG_3_FIGHT_1, + SFX_HAITIAN_GANG_3_FIGHT_2, + SFX_HAITIAN_GANG_3_FIGHT_3, + SFX_HAITIAN_GANG_3_FIGHT_4, + SFX_HAITIAN_GANG_3_FIGHT_5, + SFX_HAITIAN_GANG_3_FIGHT_6, + SFX_HAITIAN_GANG_3_FIGHT_7, + SFX_HAITIAN_GANG_3_FIGHT_8, + SFX_HAITIAN_GANG_3_FIGHT_9, + SFX_HAITIAN_GANG_3_FIGHT_10, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_1, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_2, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_3, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_4, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_5, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_6, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_7, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_8, + SFX_HAITIAN_GANG_3_GENERIC_CRASH_9, + SFX_HAITIAN_GANG_3_GUN_COOL_1, + SFX_HAITIAN_GANG_3_GUN_COOL_2, + SFX_HAITIAN_GANG_3_GUN_COOL_3, + SFX_HAITIAN_GANG_3_GUN_COOL_4, + SFX_HAITIAN_GANG_3_GUN_COOL_5, + SFX_HAITIAN_GANG_3_JACKED_1, + SFX_HAITIAN_GANG_3_JACKED_2, + SFX_HAITIAN_GANG_3_JACKED_3, + SFX_HAITIAN_GANG_3_JACKED_4, + SFX_HAITIAN_GANG_3_JACKED_5, + SFX_HAITIAN_GANG_3_JACKED_6, + SFX_HAITIAN_GANG_3_JACKING_1, + SFX_HAITIAN_GANG_3_JACKING_2, + SFX_HAITIAN_GANG_3_JACKING_3, + SFX_HAITIAN_GANG_3_JACKING_4, + SFX_HAITIAN_GANG_3_LOST_1, + SFX_HAITIAN_GANG_3_LOST_2, + SFX_HAITIAN_GANG_3_LOST_3, + SFX_HAITIAN_GANG_3_LOST_4, + SFX_HAITIAN_GANG_3_MUGGED_1, + SFX_HAITIAN_GANG_3_MUGGED_2, + SFX_HAITIAN_GANG_3_MUGGED_3, + SFX_HAITIAN_GANG_3_SAVED_1, + SFX_HAITIAN_GANG_3_TAXI_1, + + 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_FIRE_10, + SFX_GENERIC_FEMALE_FIRE_11, + SFX_GENERIC_FEMALE_FIRE_12, + SFX_GENERIC_FEMALE_FIRE_13, + SFX_GENERIC_FEMALE_FIRE_14, + SFX_GENERIC_FEMALE_FIRE_15, + SFX_GENERIC_FEMALE_FIRE_16, + SFX_GENERIC_FEMALE_FIRE_17, + 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_DEATH_11, + SFX_GENERIC_FEMALE_DEATH_12, + SFX_GENERIC_FEMALE_DEATH_13, + SFX_GENERIC_FEMALE_DEATH_14, + SFX_GENERIC_FEMALE_DEATH_15, + SFX_GENERIC_FEMALE_DEATH_16, + SFX_GENERIC_FEMALE_DEATH_17, + SFX_GENERIC_FEMALE_DEATH_18, + SFX_GENERIC_FEMALE_DEATH_19, + SFX_GENERIC_FEMALE_DEATH_20, + SFX_GENERIC_FEMALE_DEATH_21, + SFX_GENERIC_FEMALE_DEATH_22, + 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_GRUNT_12, + SFX_GENERIC_FEMALE_GRUNT_13, + SFX_GENERIC_FEMALE_GRUNT_14, + SFX_GENERIC_FEMALE_GRUNT_15, + SFX_GENERIC_FEMALE_GRUNT_16, + SFX_GENERIC_FEMALE_GRUNT_17, + SFX_GENERIC_FEMALE_GRUNT_18, + SFX_GENERIC_FEMALE_GRUNT_19, + SFX_GENERIC_FEMALE_GRUNT_20, + SFX_GENERIC_FEMALE_GRUNT_21, + SFX_GENERIC_FEMALE_GRUNT_22, + SFX_GENERIC_FEMALE_GRUNT_23, + SFX_GENERIC_FEMALE_GRUNT_24, + SFX_GENERIC_FEMALE_GRUNT_25, + SFX_GENERIC_FEMALE_GRUNT_26, + SFX_GENERIC_FEMALE_GRUNT_27, + SFX_GENERIC_FEMALE_GRUNT_28, + SFX_GENERIC_FEMALE_GRUNT_29, + SFX_GENERIC_FEMALE_GRUNT_30, + SFX_GENERIC_FEMALE_GRUNT_31, + SFX_GENERIC_FEMALE_GRUNT_32, + SFX_GENERIC_FEMALE_GRUNT_33, + 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_GENERIC_FEMALE_PANIC_9, + SFX_GENERIC_FEMALE_PANIC_10, + SFX_GENERIC_FEMALE_PANIC_11, + SFX_GENERIC_FEMALE_PANIC_12, + SFX_GENERIC_FEMALE_PANIC_13, + SFX_GENERIC_FEMALE_PANIC_14, + SFX_GENERIC_FEMALE_PANIC_15, + SFX_GENERIC_FEMALE_PANIC_16, + SFX_GENERIC_FEMALE_PANIC_17, + SFX_GENERIC_FEMALE_PANIC_18, + SFX_GENERIC_FEMALE_PANIC_19, + SFX_GENERIC_FEMALE_PANIC_20, + SFX_GENERIC_FEMALE_PANIC_21, + SFX_GENERIC_FEMALE_PANIC_22, + SFX_GENERIC_FEMALE_PANIC_23, + SFX_GENERIC_FEMALE_PANIC_24, + SFX_GENERIC_FEMALE_PANIC_25, + SFX_GENERIC_FEMALE_PANIC_26, + SFX_GENERIC_FEMALE_PANIC_27, + + 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_FIRE_9, + SFX_GENERIC_MALE_FIRE_10, + SFX_GENERIC_MALE_FIRE_11, + SFX_GENERIC_MALE_FIRE_12, + SFX_GENERIC_MALE_FIRE_13, + SFX_GENERIC_MALE_FIRE_14, + SFX_GENERIC_MALE_FIRE_15, + SFX_GENERIC_MALE_FIRE_16, + SFX_GENERIC_MALE_FIRE_17, + SFX_GENERIC_MALE_FIRE_18, + SFX_GENERIC_MALE_FIRE_19, + SFX_GENERIC_MALE_FIRE_20, + SFX_GENERIC_MALE_FIRE_21, + SFX_GENERIC_MALE_FIRE_22, + SFX_GENERIC_MALE_FIRE_23, + SFX_GENERIC_MALE_FIRE_24, + SFX_GENERIC_MALE_FIRE_25, + SFX_GENERIC_MALE_FIRE_26, + SFX_GENERIC_MALE_FIRE_27, + SFX_GENERIC_MALE_FIRE_28, + SFX_GENERIC_MALE_FIRE_29, + SFX_GENERIC_MALE_FIRE_30, + SFX_GENERIC_MALE_FIRE_31, + SFX_GENERIC_MALE_FIRE_32, + 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_DEATH_9, + SFX_GENERIC_MALE_DEATH_10, + SFX_GENERIC_MALE_DEATH_11, + SFX_GENERIC_MALE_DEATH_12, + SFX_GENERIC_MALE_DEATH_13, + SFX_GENERIC_MALE_DEATH_14, + SFX_GENERIC_MALE_DEATH_15, + SFX_GENERIC_MALE_DEATH_16, + SFX_GENERIC_MALE_DEATH_17, + SFX_GENERIC_MALE_DEATH_18, + SFX_GENERIC_MALE_DEATH_19, + SFX_GENERIC_MALE_DEATH_20, + SFX_GENERIC_MALE_DEATH_21, + SFX_GENERIC_MALE_DEATH_22, + SFX_GENERIC_MALE_DEATH_23, + SFX_GENERIC_MALE_DEATH_24, + SFX_GENERIC_MALE_DEATH_25, + SFX_GENERIC_MALE_DEATH_26, + SFX_GENERIC_MALE_DEATH_27, + SFX_GENERIC_MALE_DEATH_28, + SFX_GENERIC_MALE_DEATH_29, + SFX_GENERIC_MALE_DEATH_30, + SFX_GENERIC_MALE_DEATH_31, + SFX_GENERIC_MALE_DEATH_32, + SFX_GENERIC_MALE_DEATH_33, + SFX_GENERIC_MALE_DEATH_34, + SFX_GENERIC_MALE_DEATH_35, + SFX_GENERIC_MALE_DEATH_36, + SFX_GENERIC_MALE_DEATH_37, + SFX_GENERIC_MALE_DEATH_38, + SFX_GENERIC_MALE_DEATH_39, + SFX_GENERIC_MALE_DEATH_40, + SFX_GENERIC_MALE_DEATH_41, + 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_GRUNT_16, + SFX_GENERIC_MALE_GRUNT_17, + SFX_GENERIC_MALE_GRUNT_18, + SFX_GENERIC_MALE_GRUNT_19, + SFX_GENERIC_MALE_GRUNT_20, + SFX_GENERIC_MALE_GRUNT_21, + SFX_GENERIC_MALE_GRUNT_22, + SFX_GENERIC_MALE_GRUNT_23, + SFX_GENERIC_MALE_GRUNT_24, + SFX_GENERIC_MALE_GRUNT_25, + SFX_GENERIC_MALE_GRUNT_26, + SFX_GENERIC_MALE_GRUNT_27, + SFX_GENERIC_MALE_GRUNT_28, + SFX_GENERIC_MALE_GRUNT_29, + SFX_GENERIC_MALE_GRUNT_30, + SFX_GENERIC_MALE_GRUNT_31, + SFX_GENERIC_MALE_GRUNT_32, + SFX_GENERIC_MALE_GRUNT_33, + SFX_GENERIC_MALE_GRUNT_34, + SFX_GENERIC_MALE_GRUNT_35, + SFX_GENERIC_MALE_GRUNT_36, + SFX_GENERIC_MALE_GRUNT_37, + SFX_GENERIC_MALE_GRUNT_38, + SFX_GENERIC_MALE_GRUNT_39, + SFX_GENERIC_MALE_GRUNT_40, + SFX_GENERIC_MALE_GRUNT_41, + 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_GENERIC_MALE_PANIC_7, + SFX_GENERIC_MALE_PANIC_8, + SFX_GENERIC_MALE_PANIC_9, + SFX_GENERIC_MALE_PANIC_10, + SFX_GENERIC_MALE_PANIC_11, + SFX_GENERIC_MALE_PANIC_12, + SFX_GENERIC_MALE_PANIC_13, + SFX_GENERIC_MALE_PANIC_14, + SFX_GENERIC_MALE_PANIC_15, + SFX_GENERIC_MALE_PANIC_16, + SFX_GENERIC_MALE_PANIC_17, + SFX_GENERIC_MALE_PANIC_18, + SFX_GENERIC_MALE_PANIC_19, + SFX_GENERIC_MALE_PANIC_20, + SFX_GENERIC_MALE_PANIC_21, + SFX_GENERIC_MALE_PANIC_22, + SFX_GENERIC_MALE_PANIC_23, + SFX_GENERIC_MALE_PANIC_24, + SFX_GENERIC_MALE_PANIC_25, + SFX_GENERIC_MALE_PANIC_26, + SFX_GENERIC_MALE_PANIC_27, + SFX_GENERIC_MALE_PANIC_28, + SFX_GENERIC_MALE_PANIC_29, + SFX_GENERIC_MALE_PANIC_30, + SFX_GENERIC_MALE_PANIC_31, + SFX_GENERIC_MALE_PANIC_32, + SFX_GENERIC_MALE_PANIC_33, + SFX_GENERIC_MALE_PANIC_34, + SFX_GENERIC_MALE_PANIC_35, + + SFX_MEDIC_VOICE_1_FIGHT_1, + SFX_MEDIC_VOICE_1_FIGHT_2, + SFX_MEDIC_VOICE_1_FIGHT_3, + SFX_MEDIC_VOICE_1_FIGHT_4, + SFX_MEDIC_VOICE_1_FIGHT_5, + SFX_MEDIC_VOICE_1_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_GET_OUT_VAN_CHAT_10, + 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_1_AT_VICTIM_13, + SFX_MEDIC_VOICE_1_AT_VICTIM_14, + SFX_MEDIC_VOICE_1_AT_VICTIM_15, + SFX_MEDIC_VOICE_1_AT_VICTIM_16, + SFX_MEDIC_VOICE_1_AT_VICTIM_17, + + SFX_MEDIC_VOICE_2_FIGHT_1, + SFX_MEDIC_VOICE_2_FIGHT_2, + SFX_MEDIC_VOICE_2_FIGHT_3, + SFX_MEDIC_VOICE_2_FIGHT_4, + SFX_MEDIC_VOICE_2_FIGHT_5, + SFX_MEDIC_VOICE_2_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_GET_OUT_VAN_CHAT_10, + 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_MEDIC_VOICE_2_AT_VICTIM_13, + SFX_MEDIC_VOICE_2_AT_VICTIM_14, + SFX_MEDIC_VOICE_2_AT_VICTIM_15, + SFX_MEDIC_VOICE_2_AT_VICTIM_16, + SFX_MEDIC_VOICE_2_AT_VICTIM_17, + + SFX_FBI_VOICE_1_GUNAIMEDAT3_1, + SFX_FBI_VOICE_1_GUNAIMEDAT3_2, + SFX_FBI_VOICE_1_GUNAIMEDAT3_3, + SFX_FBI_VOICE_1_GUNAIMEDAT3_4, + SFX_FBI_VOICE_1_CAR_CRASH_1, + SFX_FBI_VOICE_1_CAR_CRASH_2, + SFX_FBI_VOICE_1_CAR_CRASH_3, + SFX_FBI_VOICE_1_CAR_CRASH_4, + SFX_FBI_VOICE_1_GUNAIMEDAT2_1, + SFX_FBI_VOICE_1_COP_MANYCOPSAROUND_1, + SFX_FBI_VOICE_1_COP_MANYCOPSAROUND_2, + SFX_FBI_VOICE_1_COP_MANYCOPSAROUND_3, + SFX_FBI_VOICE_1_COP_TARGETING_1, + SFX_FBI_VOICE_1_COP_TARGETING_2, + SFX_FBI_VOICE_1_COP_TARGETING_3, + SFX_FBI_VOICE_1_COP_TARGETING_4, + SFX_FBI_VOICE_1_COP_TARGETING_5, + SFX_FBI_VOICE_1_COP_TARGETING_6, + + SFX_FBI_VOICE_2_GUNAIMEDAT3_1, + SFX_FBI_VOICE_2_GUNAIMEDAT3_2, + SFX_FBI_VOICE_2_GUNAIMEDAT3_3, + SFX_FBI_VOICE_2_GUNAIMEDAT3_4, + SFX_FBI_VOICE_2_CAR_CRASH_1, + SFX_FBI_VOICE_2_CAR_CRASH_2, + SFX_FBI_VOICE_2_CAR_CRASH_3, + SFX_FBI_VOICE_2_CAR_CRASH_4, + SFX_FBI_VOICE_2_GUNAIMEDAT2_1, + SFX_FBI_VOICE_2_COP_MANYCOPSAROUND_1, + SFX_FBI_VOICE_2_COP_MANYCOPSAROUND_2, + SFX_FBI_VOICE_2_COP_MANYCOPSAROUND_3, + SFX_FBI_VOICE_2_COP_TARGETING_1, + SFX_FBI_VOICE_2_COP_TARGETING_2, + SFX_FBI_VOICE_2_COP_TARGETING_3, + SFX_FBI_VOICE_2_COP_TARGETING_4, + SFX_FBI_VOICE_2_COP_TARGETING_5, + SFX_FBI_VOICE_2_COP_TARGETING_6, + + SFX_FBI_VOICE_3_GUNAIMEDAT3_1, + SFX_FBI_VOICE_3_GUNAIMEDAT3_2, + SFX_FBI_VOICE_3_GUNAIMEDAT3_3, + SFX_FBI_VOICE_3_GUNAIMEDAT3_4, + SFX_FBI_VOICE_3_CAR_CRASH_1, + SFX_FBI_VOICE_3_CAR_CRASH_2, + SFX_FBI_VOICE_3_CAR_CRASH_3, + SFX_FBI_VOICE_3_CAR_CRASH_4, + SFX_FBI_VOICE_3_GUNAIMEDAT2_1, + SFX_FBI_VOICE_3_COP_MANYCOPSAROUND_1, + SFX_FBI_VOICE_3_COP_MANYCOPSAROUND_2, + SFX_FBI_VOICE_3_COP_MANYCOPSAROUND_3, + SFX_FBI_VOICE_3_COP_TARGETING_1, + SFX_FBI_VOICE_3_COP_TARGETING_2, + SFX_FBI_VOICE_3_COP_TARGETING_3, + SFX_FBI_VOICE_3_COP_TARGETING_4, + SFX_FBI_VOICE_3_COP_TARGETING_5, + SFX_FBI_VOICE_3_COP_TARGETING_6, + + SFX_SWAT_VOICE_1_DODGE_1, + SFX_SWAT_VOICE_1_DODGE_2, + SFX_SWAT_VOICE_1_DODGE_3, + SFX_SWAT_VOICE_1_COP_HELIPILOTPHRASE_1, + SFX_SWAT_VOICE_1_COP_HELIPILOTPHRASE_2, + SFX_SWAT_VOICE_1_COP_HELIPILOTPHRASE_3, + SFX_SWAT_VOICE_1_COP_HELIPILOTPHRASE_4, + SFX_SWAT_VOICE_1_COP_HELIPILOTPHRASE_5, + SFX_SWAT_VOICE_1_COP_HELIPILOTPHRASE_6, + SFX_SWAT_VOICE_1_COP_HELIPILOTPHRASE_7, + SFX_SWAT_VOICE_1_COP_TARGETING_1, + SFX_SWAT_VOICE_1_COP_TARGETING_2, + SFX_SWAT_VOICE_1_COP_TARGETING_3, + SFX_SWAT_VOICE_1_COP_TARGETING_4, + + SFX_SWAT_VOICE_2_DODGE_1, + SFX_SWAT_VOICE_2_DODGE_2, + SFX_SWAT_VOICE_2_DODGE_3, + SFX_SWAT_VOICE_2_COP_HELIPILOTPHRASE_1, + SFX_SWAT_VOICE_2_COP_HELIPILOTPHRASE_2, + SFX_SWAT_VOICE_2_COP_HELIPILOTPHRASE_3, + SFX_SWAT_VOICE_2_COP_HELIPILOTPHRASE_4, + SFX_SWAT_VOICE_2_COP_HELIPILOTPHRASE_5, + SFX_SWAT_VOICE_2_COP_HELIPILOTPHRASE_6, + SFX_SWAT_VOICE_2_COP_HELIPILOTPHRASE_7, + SFX_SWAT_VOICE_2_COP_TARGETING_1, + SFX_SWAT_VOICE_2_COP_TARGETING_2, + SFX_SWAT_VOICE_2_COP_TARGETING_3, + SFX_SWAT_VOICE_2_COP_TARGETING_4, + + SFX_SWAT_VOICE_3_DODGE_1, + SFX_SWAT_VOICE_3_DODGE_2, + SFX_SWAT_VOICE_3_DODGE_3, + SFX_SWAT_VOICE_3_COP_HELIPILOTPHRASE_1, + SFX_SWAT_VOICE_3_COP_HELIPILOTPHRASE_2, + SFX_SWAT_VOICE_3_COP_HELIPILOTPHRASE_3, + SFX_SWAT_VOICE_3_COP_HELIPILOTPHRASE_4, + SFX_SWAT_VOICE_3_COP_HELIPILOTPHRASE_5, + SFX_SWAT_VOICE_3_COP_HELIPILOTPHRASE_6, + SFX_SWAT_VOICE_3_COP_HELIPILOTPHRASE_7, + SFX_SWAT_VOICE_3_COP_TARGETING_1, + SFX_SWAT_VOICE_3_COP_TARGETING_2, + SFX_SWAT_VOICE_3_COP_TARGETING_3, + SFX_SWAT_VOICE_3_COP_TARGETING_4, + + + SFX_WFYG1_BLOCKED_1, + SFX_WFYG1_BLOCKED_2, + SFX_WFYG1_BLOCKED_3, + SFX_WFYG1_BLOCKED_4, + SFX_WFYG1_BLOCKED_5, + SFX_WFYG1_BLOCKED_6, + SFX_WFYG1_BLOCKED_7, + SFX_WFYG1_BUMP_1, + SFX_WFYG1_BUMP_2, + SFX_WFYG1_BUMP_3, + SFX_WFYG1_BUMP_4, + SFX_WFYG1_BUMP_5, + SFX_WFYG1_BUMP_6, + SFX_WFYG1_BUMP_7, + SFX_WFYG1_BUMP_8, + SFX_WFYG1_BUMP_9, + SFX_WFYG1_BUMP_10, + SFX_WFYG1_BUMP_11, + SFX_WFYG1_CAR_CRASH_1, + SFX_WFYG1_CAR_CRASH_2, + SFX_WFYG1_CAR_CRASH_3, + SFX_WFYG1_CAR_CRASH_4, + SFX_WFYG1_CAR_CRASH_5, + SFX_WFYG1_CAR_CRASH_6, + SFX_WFYG1_CAR_CRASH_7, + SFX_WFYG1_CAR_CRASH_8, + SFX_WFYG1_CAR_CRASH_9, + SFX_WFYG1_CHAT_1, + SFX_WFYG1_CHAT_2, + SFX_WFYG1_CHAT_3, + SFX_WFYG1_CHAT_4, + SFX_WFYG1_CHAT_5, + SFX_WFYG1_CHAT_6, + SFX_WFYG1_CHAT_7, + SFX_WFYG1_CHAT_8, + SFX_WFYG1_CHAT_9, + SFX_WFYG1_CHAT_10, + SFX_WFYG1_DODGE_1, + SFX_WFYG1_DODGE_2, + SFX_WFYG1_DODGE_3, + SFX_WFYG1_DODGE_4, + SFX_WFYG1_DODGE_5, + SFX_WFYG1_DODGE_6, + SFX_WFYG1_DODGE_7, + SFX_WFYG1_DODGE_8, + SFX_WFYG1_DODGE_9, + SFX_WFYG1_EYEING_1, + SFX_WFYG1_EYEING_2, + SFX_WFYG1_FIGHT_1, + SFX_WFYG1_FIGHT_2, + SFX_WFYG1_FIGHT_3, + SFX_WFYG1_FIGHT_4, + SFX_WFYG1_GENERIC_CRASH_1, + SFX_WFYG1_GENERIC_CRASH_2, + SFX_WFYG1_GENERIC_CRASH_3, + SFX_WFYG1_GENERIC_CRASH_4, + SFX_WFYG1_GENERIC_CRASH_5, + SFX_WFYG1_GENERIC_CRASH_6, + SFX_WFYG1_GENERIC_CRASH_7, + SFX_WFYG1_GUN_COOL_1, + SFX_WFYG1_GUN_COOL_2, + SFX_WFYG1_GUN_COOL_3, + SFX_WFYG1_GUN_COOL_4, + SFX_WFYG1_GUN_COOL_5, + SFX_WFYG1_GUN_COOL_6, + SFX_WFYG1_JACKED_1, + SFX_WFYG1_JACKED_2, + SFX_WFYG1_JACKED_3, + SFX_WFYG1_JACKED_4, + SFX_WFYG1_JACKED_5, + SFX_WFYG1_LOST_1, + SFX_WFYG1_LOST_2, + SFX_WFYG1_LOST_3, + SFX_WFYG1_MUGGED_1, + SFX_WFYG1_MUGGED_2, + SFX_WFYG1_MUGGING_1, + SFX_WFYG1_MUGGING_2, + SFX_WFYG1_RUN_1, + SFX_WFYG1_RUN_2, + SFX_WFYG1_SAVED_1, + SFX_WFYG1_SHOCKED_1, + SFX_WFYG1_TAXI_1, + + SFX_WFYG2_BLOCKED_1, + SFX_WFYG2_BLOCKED_2, + SFX_WFYG2_BLOCKED_3, + SFX_WFYG2_BLOCKED_4, + SFX_WFYG2_BLOCKED_5, + SFX_WFYG2_BUMP_1, + SFX_WFYG2_BUMP_2, + SFX_WFYG2_BUMP_3, + SFX_WFYG2_BUMP_4, + SFX_WFYG2_BUMP_5, + SFX_WFYG2_BUMP_6, + SFX_WFYG2_BUMP_7, + SFX_WFYG2_BUMP_8, + SFX_WFYG2_BUMP_9, + SFX_WFYG2_BUMP_10, + SFX_WFYG2_BUMP_11, + SFX_WFYG2_CAR_CRASH_1, + SFX_WFYG2_CAR_CRASH_2, + SFX_WFYG2_CAR_CRASH_3, + SFX_WFYG2_CAR_CRASH_4, + SFX_WFYG2_CAR_CRASH_5, + SFX_WFYG2_CAR_CRASH_6, + SFX_WFYG2_CAR_CRASH_7, + SFX_WFYG2_CAR_CRASH_8, + SFX_WFYG2_CAR_CRASH_9, + SFX_WFYG2_CHAT_1, + SFX_WFYG2_CHAT_2, + SFX_WFYG2_CHAT_3, + SFX_WFYG2_CHAT_4, + SFX_WFYG2_CHAT_5, + SFX_WFYG2_CHAT_6, + SFX_WFYG2_CHAT_7, + SFX_WFYG2_CHAT_8, + SFX_WFYG2_CHAT_9, + SFX_WFYG2_DODGE_1, + SFX_WFYG2_DODGE_2, + SFX_WFYG2_DODGE_3, + SFX_WFYG2_DODGE_4, + SFX_WFYG2_DODGE_5, + SFX_WFYG2_DODGE_6, + SFX_WFYG2_DODGE_7, + SFX_WFYG2_DODGE_8, + SFX_WFYG2_EYEING_1, + SFX_WFYG2_EYEING_2, + SFX_WFYG2_EYEING_3, + SFX_WFYG2_EYEING_4, + SFX_WFYG2_FIGHT_1, + SFX_WFYG2_FIGHT_2, + SFX_WFYG2_FIGHT_3, + SFX_WFYG2_FIGHT_4, + SFX_WFYG2_FIGHT_5, + SFX_WFYG2_GENERIC_CRASH_1, + SFX_WFYG2_GENERIC_CRASH_2, + SFX_WFYG2_GENERIC_CRASH_3, + SFX_WFYG2_GENERIC_CRASH_4, + SFX_WFYG2_GENERIC_CRASH_5, + SFX_WFYG2_GENERIC_CRASH_6, + SFX_WFYG2_GENERIC_CRASH_7, + SFX_WFYG2_GUN_COOL_1, + SFX_WFYG2_GUN_COOL_2, + SFX_WFYG2_GUN_COOL_3, + SFX_WFYG2_JACKED_1, + SFX_WFYG2_JACKED_2, + SFX_WFYG2_JACKED_3, + SFX_WFYG2_JACKED_4, + SFX_WFYG2_JACKED_5, + SFX_WFYG2_LOST_1, + SFX_WFYG2_MUGGED_1, + SFX_WFYG2_MUGGED_2, + SFX_WFYG2_SHOCKED_1, + SFX_WFYG2_TAXI_1, + SFX_WFYG2_TAXI_2, // unused + + SFX_HMOCA_BLOCKED_1, + SFX_HMOCA_BLOCKED_2, + SFX_HMOCA_BLOCKED_3, + SFX_HMOCA_BLOCKED_4, + SFX_HMOCA_BLOCKED_5, + SFX_HMOCA_BLOCKED_6, + SFX_HMOCA_BLOCKED_7, + SFX_HMOCA_BLOCKED_8, + SFX_HMOCA_CAR_CRASH_1, + SFX_HMOCA_CAR_CRASH_2, + SFX_HMOCA_CAR_CRASH_3, + SFX_HMOCA_CAR_CRASH_4, + SFX_HMOCA_CAR_CRASH_5, + SFX_HMOCA_CAR_CRASH_6, + SFX_HMOCA_CAR_CRASH_7, + SFX_HMOCA_CAR_CRASH_8, + SFX_HMOCA_CHAT_1, + SFX_HMOCA_CHAT_2, + SFX_HMOCA_CHAT_3, + SFX_HMOCA_CHAT_4, + SFX_HMOCA_CHAT_5, + SFX_HMOCA_CHAT_6, + SFX_HMOCA_CHAT_7, + SFX_HMOCA_CHAT_8, + SFX_HMOCA_CHAT_9, + SFX_HMOCA_CHAT_10, + SFX_HMOCA_EYEING_1, + SFX_HMOCA_EYEING_2, + SFX_HMOCA_GUN_PANIC_1, + SFX_HMOCA_GUN_PANIC_2, + SFX_HMOCA_GUN_PANIC_3, + SFX_HMOCA_GUN_PANIC_4, + SFX_HMOCA_GUN_PANIC_5, + SFX_HMOCA_JACKED_1, + SFX_HMOCA_JACKED_2, + SFX_HMOCA_JACKED_3, + SFX_HMOCA_JACKED_4, + SFX_HMOCA_JACKED_5, + SFX_HMOCA_JACKED_6, + SFX_HMOCA_JACKED_7, + SFX_HMOCA_JACKED_8, + SFX_HMOCA_JACKED_9, + SFX_HMOCA_JACKED_10, + SFX_HMOCA_JACKING_1, + SFX_HMOCA_JACKING_2, + SFX_HMOCA_JACKING_3, + SFX_HMOCA_JACKING_4, + SFX_HMOCA_JACKING_5, + SFX_HMOCA_JACKING_6, + SFX_HMOCA_JACKING_7, + SFX_HMOCA_JACKING_8, + SFX_HMOCA_JACKING_9, + SFX_HMOCA_JACKING_10, + SFX_HMOCA_JACKING_11, + SFX_HMOCA_MUGGED_1, + SFX_HMOCA_MUGGED_2, + SFX_HMOCA_MUGGED_3, + SFX_HMOCA_MUGGED_4, + SFX_HMOCA_MUGGED_5, + SFX_HMOCA_MUGGED_6, + SFX_HMOCA_MUGGED_7, + SFX_HMOCA_RUN_1, + SFX_HMOCA_RUN_2, + SFX_HMOCA_TAXI_1, + + SFX_WFOSH_BUMP_1, + SFX_WFOSH_BUMP_2, + SFX_WFOSH_BUMP_3, + SFX_WFOSH_BUMP_4, + SFX_WFOSH_BUMP_5, + SFX_WFOSH_BUMP_6, + SFX_WFOSH_BUMP_7, + SFX_WFOSH_BUMP_8, + SFX_WFOSH_BUMP_9, + SFX_WFOSH_BUMP_10, + SFX_WFOSH_CHAT_1, + SFX_WFOSH_CHAT_2, + SFX_WFOSH_CHAT_3, + SFX_WFOSH_CHAT_4, + SFX_WFOSH_CHAT_5, + SFX_WFOSH_CHAT_6, + SFX_WFOSH_CHAT_7, + SFX_WFOSH_CHAT_8, + SFX_WFOSH_CHAT_9, + SFX_WFOSH_DODGE_1, + SFX_WFOSH_DODGE_2, + SFX_WFOSH_DODGE_3, + SFX_WFOSH_DODGE_4, + SFX_WFOSH_DODGE_5, + SFX_WFOSH_DODGE_6, + SFX_WFOSH_DODGE_7, + SFX_WFOSH_DODGE_8, + SFX_WFOSH_DODGE_9, + SFX_WFOSH_DODGE_10, + SFX_WFOSH_GUN_COOL_1, + SFX_WFOSH_GUN_COOL_2, + SFX_WFOSH_GUN_COOL_3, + SFX_WFOSH_GUN_COOL_4, + SFX_WFOSH_GUN_COOL_5, + SFX_WFOSH_GUN_COOL_6, + SFX_WFOSH_GUN_COOL_7, + SFX_WFOSH_GUN_COOL_8, + SFX_WFOSH_GUN_COOL_9, + SFX_WFOSH_GUN_COOL_10, + SFX_WFOSH_LOST_1, + SFX_WFOSH_LOST_2, + SFX_WFOSH_MUGGED_1, + SFX_WFOSH_MUGGED_2, + SFX_WFOSH_RUN_1, + SFX_WFOSH_RUN_2, + SFX_WFOSH_RUN_3, + SFX_WFOSH_RUN_4, + SFX_WFOSH_RUN_5, + SFX_WFOSH_RUN_6, + SFX_WFOSH_RUN_7, + SFX_WFOSH_RUN_8, + SFX_WFOSH_RUN_9, + SFX_WFOSH_SAVED_1, + SFX_WFOSH_SAVED_2, + SFX_WFOSH_SAVED_3, + SFX_WFOSH_SHOCKED_1, + SFX_WFOSH_SHOCKED_2, + SFX_WFOSH_SHOCKED_3, + SFX_WFOSH_SHOCKED_4, + SFX_WFOSH_SHOCKED_5, + SFX_WFOSH_TAXI_1, + SFX_WFYSK_BLOCKED_1, + SFX_WFYSK_BLOCKED_2, + SFX_WFYSK_BLOCKED_3, + SFX_WFYSK_BLOCKED_4, + SFX_WFYSK_BLOCKED_5, + SFX_WFYSK_BLOCKED_6, + SFX_WFYSK_BLOCKED_7, + SFX_WFYSK_BLOCKED_8, + SFX_WFYSK_BLOCKED_9, + SFX_WFYSK_BLOCKED_10, + SFX_WFYSK_BLOCKED_11, + SFX_WFYSK_BUMP_1, + SFX_WFYSK_BUMP_2, + SFX_WFYSK_BUMP_3, + SFX_WFYSK_BUMP_4, + SFX_WFYSK_BUMP_5, + SFX_WFYSK_BUMP_6, + SFX_WFYSK_BUMP_7, + SFX_WFYSK_BUMP_8, + SFX_WFYSK_BUMP_9, + SFX_WFYSK_BUMP_10, + SFX_WFYSK_BUMP_11, + SFX_WFYSK_BUMP_12, + SFX_WFYSK_BUMP_13, + SFX_WFYSK_BUMP_14, + SFX_WFYSK_BUMP_15, + SFX_WFYSK_BUMP_16, + SFX_WFYSK_BUMP_17, + SFX_WFYSK_BUMP_18, + SFX_WFYSK_DODGE_1, + SFX_WFYSK_DODGE_2, + SFX_WFYSK_DODGE_3, + SFX_WFYSK_DODGE_4, + SFX_WFYSK_DODGE_5, + SFX_WFYSK_DODGE_6, + SFX_WFYSK_DODGE_7, + SFX_WFYSK_DODGE_8, + SFX_WFYSK_DODGE_9, + SFX_WFYSK_FIGHT_1, + SFX_WFYSK_FIGHT_2, + SFX_WFYSK_FIGHT_3, + SFX_WFYSK_FIGHT_4, + SFX_WFYSK_FIGHT_5, + SFX_WFYSK_FIGHT_6, + SFX_WFYSK_FIGHT_7, + SFX_WFYSK_FIGHT_8, + SFX_WFYSK_FIGHT_9, + SFX_WFYSK_FIGHT_10, + SFX_WFYSK_FIGHT_11, + SFX_WFYSK_GUN_PANIC_1, + SFX_WFYSK_GUN_PANIC_2, + SFX_WFYSK_GUN_PANIC_3, + SFX_WFYSK_GUN_PANIC_4, + SFX_WFYSK_GUN_PANIC_5, + SFX_WFYSK_MUGGED_1, + SFX_WFYSK_MUGGED_2, + SFX_WFYSK_SAVED_1, + SFX_WFYSK_SAVED_2, + SFX_WFYSK_TAXI_1, + SFX_WMYLG_BUMP_1, + SFX_WMYLG_BUMP_2, + SFX_WMYLG_BUMP_3, + SFX_WMYLG_BUMP_4, + SFX_WMYLG_BUMP_5, + SFX_WMYLG_BUMP_6, + SFX_WMYLG_BUMP_7, + SFX_WMYLG_BUMP_8, + SFX_WMYLG_BUMP_9, + SFX_WMYLG_BUMP_10, + SFX_WMYLG_CHAT_1, + SFX_WMYLG_CHAT_2, + SFX_WMYLG_CHAT_3, + SFX_WMYLG_CHAT_4, + SFX_WMYLG_CHAT_5, + SFX_WMYLG_CHAT_6, + SFX_WMYLG_CHAT_7, + SFX_WMYLG_CHAT_8, + SFX_WMYLG_CHAT_9, + SFX_WMYLG_CHAT_10, + SFX_WMYLG_DODGE_1, + SFX_WMYLG_DODGE_2, + SFX_WMYLG_DODGE_3, + SFX_WMYLG_DODGE_4, + SFX_WMYLG_DODGE_5, + SFX_WMYLG_DODGE_6, + SFX_WMYLG_DODGE_7, + SFX_WMYLG_DODGE_8, + SFX_WMYLG_DODGE_9, + SFX_WMYLG_FIGHT_1, + SFX_WMYLG_FIGHT_2, + SFX_WMYLG_FIGHT_3, + SFX_WMYLG_FIGHT_4, + SFX_WMYLG_FIGHT_5, + SFX_WMYLG_FIGHT_6, + SFX_WMYLG_FIGHT_7, + SFX_WMYLG_GUN_COOL_1, + SFX_WMYLG_GUN_COOL_2, + SFX_WMYLG_GUN_COOL_3, + SFX_WMYLG_GUN_COOL_4, + SFX_WMYLG_GUN_COOL_5, + SFX_WMYLG_GUN_COOL_6, + SFX_WMYLG_SAVED_1, + SFX_WMYLG_TAXI_1, + + SFX_WMOBE_BLOCKED_1, + SFX_WMOBE_BLOCKED_2, + SFX_WMOBE_BLOCKED_3, + SFX_WMOBE_BLOCKED_4, + SFX_WMOBE_BLOCKED_5, + SFX_WMOBE_BLOCKED_6, + SFX_WMOBE_BUMP_1, + SFX_WMOBE_BUMP_2, + SFX_WMOBE_BUMP_3, + SFX_WMOBE_BUMP_4, + SFX_WMOBE_BUMP_5, + SFX_WMOBE_BUMP_6, + SFX_WMOBE_BUMP_7, + SFX_WMOBE_BUMP_8, + SFX_WMOBE_BUMP_9, + SFX_WMOBE_BUMP_10, + SFX_WMOBE_BUMP_11, + SFX_WMOBE_BUMP_12, + SFX_WMOBE_CAR_CRASH_1, + SFX_WMOBE_CAR_CRASH_2, + SFX_WMOBE_CAR_CRASH_3, + SFX_WMOBE_CAR_CRASH_4, + SFX_WMOBE_CAR_CRASH_5, + SFX_WMOBE_CAR_CRASH_6, + SFX_WMOBE_CAR_CRASH_7, + SFX_WMOBE_CAR_CRASH_8, + SFX_WMOBE_CHAT_1, + SFX_WMOBE_CHAT_2, + SFX_WMOBE_CHAT_3, + SFX_WMOBE_CHAT_4, + SFX_WMOBE_CHAT_5, + SFX_WMOBE_CHAT_6, + SFX_WMOBE_CHAT_7, + SFX_WMOBE_CHAT_8, + SFX_WMOBE_CHAT_9, + SFX_WMOBE_CHAT_10, + SFX_WMOBE_DODGE_1, + SFX_WMOBE_DODGE_2, + SFX_WMOBE_DODGE_3, + SFX_WMOBE_DODGE_4, + SFX_WMOBE_DODGE_5, + SFX_WMOBE_DODGE_6, + SFX_WMOBE_DODGE_7, + SFX_WMOBE_DODGE_8, + SFX_WMOBE_EYEING_1, + SFX_WMOBE_EYEING_2, + SFX_WMOBE_GENERIC_CRASH_1, + SFX_WMOBE_GENERIC_CRASH_2, + SFX_WMOBE_GENERIC_CRASH_3, + SFX_WMOBE_GENERIC_CRASH_4, + SFX_WMOBE_GENERIC_CRASH_5, + SFX_WMOBE_GENERIC_CRASH_6, + SFX_WMOBE_GENERIC_CRASH_7, + SFX_WMOBE_GUN_PANIC_1, + SFX_WMOBE_GUN_PANIC_2, + SFX_WMOBE_GUN_PANIC_3, + SFX_WMOBE_GUN_PANIC_4, + SFX_WMOBE_GUN_PANIC_5, + SFX_WMOBE_JACKED_1, + SFX_WMOBE_JACKED_2, + SFX_WMOBE_JACKED_3, + SFX_WMOBE_JACKED_4, + SFX_WMOBE_JACKED_5, + SFX_WMOBE_JACKED_6, + SFX_WMOBE_JACKED_7, + SFX_WMOBE_JACKED_8, + SFX_WMOBE_JACKING_1, + SFX_WMOBE_JACKING_2, + SFX_WMOBE_JACKING_3, + SFX_WMOBE_JACKING_4, + SFX_WMOBE_JEER_1, + SFX_WMOBE_JEER_2, + SFX_WMOBE_JEER_3, + SFX_WMOBE_JEER_4, + SFX_WMOBE_JEER_5, + SFX_WMOBE_JEER_6, + SFX_WMOBE_JEER_7, + SFX_WMOBE_JEER_8, + SFX_WMOBE_JEER_9, + SFX_WMOBE_JEER_10, + SFX_WMOBE_JEER_11, + SFX_WMOBE_JEER_12, + SFX_WMOBE_JEER_13, + SFX_WMOBE_JEER_14, + SFX_WMOBE_JEER_15, + SFX_WMOBE_JEER_16, + SFX_WMOBE_MUGGING_1, + SFX_WMOBE_MUGGING_2, + SFX_WMOBE_MUGGING_3, + SFX_WMOBE_MUGGING_4, + SFX_WMOBE_MUGGING_5, + SFX_WMOBE_MUGGING_6, + SFX_WMOBE_RUN_1, + SFX_WMOBE_RUN_2, + SFX_WMOBE_RUN_3, + SFX_WMOBE_RUN_4, + SFX_WMOBE_SAVED_1, + SFX_WMOBE_SAVED_2, + SFX_WMOBE_SHOCKED_1, + SFX_WMOBE_SHOCKED_2, + + + + SFX_WMYBU_BLOCKED_1, + SFX_WMYBU_BLOCKED_2, + SFX_WMYBU_BLOCKED_3, + SFX_WMYBU_BLOCKED_4, + SFX_WMYBU_BLOCKED_5, + SFX_WMYBU_BLOCKED_6, + SFX_WMYBU_BLOCKED_7, + SFX_WMYBU_BLOCKED_8, + SFX_WMYBU_BLOCKED_9, + SFX_WMYBU_BUMP_1, + SFX_WMYBU_BUMP_2, + SFX_WMYBU_BUMP_3, + SFX_WMYBU_BUMP_4, + SFX_WMYBU_BUMP_5, + SFX_WMYBU_BUMP_6, + SFX_WMYBU_BUMP_7, + SFX_WMYBU_BUMP_8, + SFX_WMYBU_BUMP_9, + SFX_WMYBU_BUMP_10, + SFX_WMYBU_BUMP_11, + SFX_WMYBU_CAR_CRASH_1, + SFX_WMYBU_CAR_CRASH_2, + SFX_WMYBU_CAR_CRASH_3, + SFX_WMYBU_CAR_CRASH_4, + SFX_WMYBU_CAR_CRASH_5, + SFX_WMYBU_CAR_CRASH_6, + SFX_WMYBU_CAR_CRASH_7, + SFX_WMYBU_CAR_CRASH_8, + SFX_WMYBU_CAR_CRASH_9, + SFX_WMYBU_CHAT_1, + SFX_WMYBU_CHAT_2, + SFX_WMYBU_CHAT_3, + SFX_WMYBU_CHAT_4, + SFX_WMYBU_CHAT_5, + SFX_WMYBU_CHAT_6, + SFX_WMYBU_CHAT_7, + SFX_WMYBU_CHAT_8, + SFX_WMYBU_CHAT_9, + SFX_WMYBU_CHAT_10, + SFX_WMYBU_DODGE_1, + SFX_WMYBU_DODGE_2, + SFX_WMYBU_DODGE_3, + SFX_WMYBU_DODGE_4, + SFX_WMYBU_DODGE_5, + SFX_WMYBU_DODGE_6, + SFX_WMYBU_DODGE_7, + SFX_WMYBU_DODGE_8, + SFX_WMYBU_DODGE_9, + SFX_WMYBU_DODGE_10, + SFX_WMYBU_EYEING_1, + SFX_WMYBU_EYEING_2, + SFX_WMYBU_GENERIC_CRASH_1, + SFX_WMYBU_GENERIC_CRASH_2, + SFX_WMYBU_GENERIC_CRASH_3, + SFX_WMYBU_GENERIC_CRASH_4, + SFX_WMYBU_GENERIC_CRASH_5, + SFX_WMYBU_GUN_PANIC_1, + SFX_WMYBU_GUN_PANIC_2, + SFX_WMYBU_GUN_PANIC_3, + SFX_WMYBU_GUN_PANIC_4, + SFX_WMYBU_GUN_PANIC_5, + SFX_WMYBU_GUN_PANIC_6, + SFX_WMYBU_INNOCENT_1, + SFX_WMYBU_INNOCENT_2, + SFX_WMYBU_JACKED_1, + SFX_WMYBU_JACKED_2, + SFX_WMYBU_JACKED_3, + SFX_WMYBU_JACKED_4, + SFX_WMYBU_JACKED_5, + SFX_WMYBU_LOST_1, + SFX_WMYBU_LOST_2, + SFX_WMYBU_LOST_3, + SFX_WMYBU_LOST_4, + SFX_WMYBU_LOST_5, + SFX_WMYBU_MUGGED_1, + SFX_WMYBU_RUN_1, + SFX_WMYBU_RUN_2, + SFX_WMYBU_RUN_3, + SFX_WMYBU_SAVED_1, + SFX_WMYBU_SAVED_2, + SFX_WMYBU_SHOCKED_1, + SFX_WMYBU_SHOCKED_2, + SFX_WMYBU_SHOCKED_3, + SFX_WMYBU_SHOCKED_4, + SFX_WMYBU_SHOCKED_5, + SFX_WMYBU_TAXI_1, + SFX_WMYBU_TAXI_2, + + SFX_WMYST_BLOCKED_1, + SFX_WMYST_BLOCKED_2, + SFX_WMYST_BLOCKED_3, + SFX_WMYST_BLOCKED_4, + SFX_WMYST_BLOCKED_5, + SFX_WMYST_BLOCKED_6, + SFX_WMYST_BLOCKED_7, + SFX_WMYST_BLOCKED_8, + SFX_WMYST_BUMP_1, + SFX_WMYST_BUMP_2, + SFX_WMYST_BUMP_3, + SFX_WMYST_BUMP_4, + SFX_WMYST_BUMP_5, + SFX_WMYST_BUMP_6, + SFX_WMYST_BUMP_7, + SFX_WMYST_BUMP_8, + SFX_WMYST_BUMP_9, + SFX_WMYST_BUMP_10, + SFX_WMYST_BUMP_11, + SFX_WMYST_CAR_CRASH_1, + SFX_WMYST_CAR_CRASH_2, + SFX_WMYST_CAR_CRASH_3, + SFX_WMYST_CAR_CRASH_4, + SFX_WMYST_CAR_CRASH_5, + SFX_WMYST_CAR_CRASH_6, + SFX_WMYST_CAR_CRASH_7, + SFX_WMYST_CAR_CRASH_8, + SFX_WMYST_CHAT_1, + SFX_WMYST_CHAT_2, + SFX_WMYST_CHAT_3, + SFX_WMYST_CHAT_4, + SFX_WMYST_CHAT_5, + SFX_WMYST_CHAT_6, + SFX_WMYST_CHAT_7, + SFX_WMYST_CHAT_8, + SFX_WMYST_CHAT_9, + SFX_WMYST_CHAT_10, + SFX_WMYST_DODGE_1, + SFX_WMYST_DODGE_2, + SFX_WMYST_DODGE_3, + SFX_WMYST_DODGE_4, + SFX_WMYST_DODGE_5, + SFX_WMYST_DODGE_6, + SFX_WMYST_DODGE_7, + SFX_WMYST_DODGE_8, + SFX_WMYST_DODGE_9, + SFX_WMYST_DODGE_10, + SFX_WMYST_EYEING_1, + SFX_WMYST_EYEING_2, + SFX_WMYST_GENERIC_CRASH_1, + SFX_WMYST_GENERIC_CRASH_2, + SFX_WMYST_GENERIC_CRASH_3, + SFX_WMYST_GENERIC_CRASH_4, + SFX_WMYST_GENERIC_CRASH_5, + SFX_WMYST_GUN_PANIC_1, + SFX_WMYST_GUN_PANIC_2, + SFX_WMYST_GUN_PANIC_3, + SFX_WMYST_GUN_PANIC_4, + SFX_WMYST_GUN_PANIC_5, + SFX_WMYST_INNOCENT_1, + SFX_WMYST_INNOCENT_2, + SFX_WMYST_INNOCENT_3, + SFX_WMYST_JACKED_1, + SFX_WMYST_JACKED_2, + SFX_WMYST_JACKED_3, + SFX_WMYST_JACKED_4, + SFX_WMYST_JACKED_5, + SFX_WMYST_LOST_1, + SFX_WMYST_LOST_2, + SFX_WMYST_MUGGED_1, + SFX_WMYST_MUGGING_1, + SFX_WMYST_MUGGING_2, + SFX_WMYST_MUGGING_3, + SFX_WMYST_MUGGING_4, + SFX_WMYST_MUGGING_5, + SFX_WMYST_RUN_1, + SFX_WMYST_RUN_2, + SFX_WMYST_RUN_3, + SFX_WMYST_RUN_4, + SFX_WMYST_RUN_5, + SFX_WMYST_RUN_6, + SFX_WMYST_RUN_7, + SFX_WMYST_SAVED_1, + SFX_WMYST_TAXI_1, + SFX_WMYST_TAXI_2, + + SFX_BMYPI_BLOCKED_1, + SFX_BMYPI_BLOCKED_2, + SFX_BMYPI_BLOCKED_3, + SFX_BMYPI_BLOCKED_4, + SFX_BMYPI_BLOCKED_5, + SFX_BMYPI_BLOCKED_6, + SFX_BMYPI_BUMP_1, + SFX_BMYPI_BUMP_2, + SFX_BMYPI_BUMP_3, + SFX_BMYPI_BUMP_4, + SFX_BMYPI_BUMP_5, + SFX_BMYPI_BUMP_6, + SFX_BMYPI_BUMP_7, + SFX_BMYPI_BUMP_8, + SFX_BMYPI_BUMP_9, + SFX_BMYPI_CAR_CRASH_1, + SFX_BMYPI_CAR_CRASH_2, + SFX_BMYPI_CAR_CRASH_3, + SFX_BMYPI_CAR_CRASH_4, + SFX_BMYPI_CAR_CRASH_5, + SFX_BMYPI_DODGE_1, + SFX_BMYPI_DODGE_2, + SFX_BMYPI_DODGE_3, + SFX_BMYPI_DODGE_4, + SFX_BMYPI_DODGE_5, + SFX_BMYPI_DODGE_6, + SFX_BMYPI_DODGE_7, + SFX_BMYPI_DODGE_8, + SFX_BMYPI_DODGE_9, + SFX_BMYPI_DODGE_10, + SFX_BMYPI_EYEING_1, + SFX_BMYPI_EYEING_2, + SFX_BMYPI_EYEING_3, + SFX_BMYPI_EYEING_4, + SFX_BMYPI_FIGHT_1, + SFX_BMYPI_FIGHT_2, + SFX_BMYPI_FIGHT_3, + SFX_BMYPI_FIGHT_4, + SFX_BMYPI_FIGHT_5, + SFX_BMYPI_FIGHT_6, + SFX_BMYPI_FIGHT_7, + SFX_BMYPI_FIGHT_8, + SFX_BMYPI_GENERIC_CRASH_1, + SFX_BMYPI_GENERIC_CRASH_2, + SFX_BMYPI_GENERIC_CRASH_3, + SFX_BMYPI_GENERIC_CRASH_4, + SFX_BMYPI_GENERIC_CRASH_5, + SFX_BMYPI_GENERIC_CRASH_6, + SFX_BMYPI_GENERIC_CRASH_7, + SFX_BMYPI_GENERIC_CRASH_8, + SFX_BMYPI_GENERIC_CRASH_9, + SFX_BMYPI_GENERIC_CRASH_10, + SFX_BMYPI_GENERIC_CRASH_11, + SFX_BMYPI_GENERIC_CRASH_12, + SFX_BMYPI_GENERIC_CRASH_13, + SFX_BMYPI_GUN_COOL_1, + SFX_BMYPI_GUN_COOL_2, + SFX_BMYPI_GUN_COOL_3, + SFX_BMYPI_GUN_COOL_4, + SFX_BMYPI_GUN_COOL_5, + SFX_BMYPI_JACKED_1, + SFX_BMYPI_JACKED_2, + SFX_BMYPI_JACKED_3, + SFX_BMYPI_JACKED_4, + SFX_BMYPI_JACKED_5, + SFX_BMYPI_JACKED_6, + SFX_BMYPI_JACKING_1, + SFX_BMYPI_JACKING_2, + SFX_BMYPI_JACKING_3, + SFX_BMYPI_JACKING_4, + SFX_BMYPI_MUGGED_1, + SFX_BMYPI_SAVED_1, + SFX_BMYPI_TAXI_1, + SFX_BMYPI_TAXI_2, + + SFX_WFYPR_BUMP_1, + SFX_WFYPR_BUMP_2, + SFX_WFYPR_BUMP_3, + SFX_WFYPR_BUMP_4, + SFX_WFYPR_BUMP_5, + SFX_WFYPR_BUMP_6, + SFX_WFYPR_BUMP_7, + SFX_WFYPR_BUMP_8, + SFX_WFYPR_BUMP_9, + SFX_WFYPR_BUMP_10, + SFX_WFYPR_BUMP_11, + SFX_WFYPR_CHAT_1, + SFX_WFYPR_CHAT_2, + SFX_WFYPR_CHAT_3, + SFX_WFYPR_CHAT_4, + SFX_WFYPR_CHAT_5, + SFX_WFYPR_CHAT_6, + SFX_WFYPR_CHAT_7, + SFX_WFYPR_CHAT_8, + SFX_WFYPR_CHAT_9, + SFX_WFYPR_CHAT_10, + SFX_WFYPR_CHAT_11, + SFX_WFYPR_CHAT_12, + SFX_WFYPR_CHAT_13, + SFX_WFYPR_CHAT_14, + SFX_WFYPR_DODGE_1, + SFX_WFYPR_DODGE_2, + SFX_WFYPR_DODGE_3, + SFX_WFYPR_DODGE_4, + SFX_WFYPR_DODGE_5, + SFX_WFYPR_DODGE_6, + SFX_WFYPR_DODGE_7, + SFX_WFYPR_DODGE_8, + SFX_WFYPR_DODGE_9, + SFX_WFYPR_DODGE_10, + SFX_WFYPR_FIGHT_1, + SFX_WFYPR_FIGHT_2, + SFX_WFYPR_FIGHT_3, + SFX_WFYPR_FIGHT_4, + SFX_WFYPR_FIGHT_5, + SFX_WFYPR_FIGHT_6, + SFX_WFYPR_FIGHT_7, + SFX_WFYPR_FIGHT_8, + SFX_WFYPR_FIGHT_9, + SFX_WFYPR_FUCKING_1, + SFX_WFYPR_FUCKING_2, + SFX_WFYPR_FUCKING_3, + SFX_WFYPR_FUCKING_4, + SFX_WFYPR_FUCKING_5, + SFX_WFYPR_GUN_COOL_1, + SFX_WFYPR_GUN_COOL_2, + SFX_WFYPR_GUN_COOL_3, + SFX_WFYPR_GUN_COOL_4, + SFX_WFYPR_GUN_COOL_5, + SFX_WFYPR_GUN_COOL_6, + SFX_WFYPR_MUGGED_1, + SFX_WFYPR_MUGGED_2, + SFX_WFYPR_SAVED_1, + SFX_WFYPR_SOLICIT_1, + SFX_WFYPR_SOLICIT_2, + SFX_WFYPR_SOLICIT_3, + SFX_WFYPR_SOLICIT_4, + SFX_WFYPR_SOLICIT_5, + SFX_WFYPR_SOLICIT_6, + SFX_WFYPR_SOLICIT_7, + SFX_WFYPR_SOLICIT_8, + SFX_WFYPR_SOLICIT_9, + SFX_WFYPR_SOLICIT_10, + SFX_WFYPR_SOLICIT_11, + SFX_WFYPR_SOLICIT_12, + SFX_WFYPR_SOLICIT_13, + SFX_WFYPR_SOLICIT_14, + SFX_WFYPR_SOLICIT_15, + SFX_WFYPR_TAXI_1, + + SFX_WMYRI_BLOCKED_1, + SFX_WMYRI_BLOCKED_2, + SFX_WMYRI_BLOCKED_3, + SFX_WMYRI_BLOCKED_4, + SFX_WMYRI_BLOCKED_5, + SFX_WMYRI_BLOCKED_6, + SFX_WMYRI_BLOCKED_7, + SFX_WMYRI_BLOCKED_8, + SFX_WMYRI_BLOCKED_9, + SFX_WMYRI_BLOCKED_10, + SFX_WMYRI_BUMP_1, + SFX_WMYRI_BUMP_2, + SFX_WMYRI_BUMP_3, + SFX_WMYRI_BUMP_4, + SFX_WMYRI_BUMP_5, + SFX_WMYRI_BUMP_6, + SFX_WMYRI_BUMP_7, + SFX_WMYRI_BUMP_8, + SFX_WMYRI_CAR_CRASH_1, + SFX_WMYRI_CAR_CRASH_2, + SFX_WMYRI_CAR_CRASH_3, + SFX_WMYRI_CAR_CRASH_4, + SFX_WMYRI_CAR_CRASH_5, + SFX_WMYRI_CAR_CRASH_6, + SFX_WMYRI_CAR_CRASH_7, + SFX_WMYRI_CAR_CRASH_8, + SFX_WMYRI_CAR_CRASH_9, + SFX_WMYRI_CHAT_1, + SFX_WMYRI_CHAT_2, + SFX_WMYRI_CHAT_3, + SFX_WMYRI_CHAT_4, + SFX_WMYRI_CHAT_5, + SFX_WMYRI_CHAT_6, + SFX_WMYRI_CHAT_7, + SFX_WMYRI_CHAT_8, + SFX_WMYRI_CHAT_9, + SFX_WMYRI_CHAT_10, + SFX_WMYRI_DODGE_1, + SFX_WMYRI_DODGE_2, + SFX_WMYRI_DODGE_3, + SFX_WMYRI_DODGE_4, + SFX_WMYRI_DODGE_5, + SFX_WMYRI_DODGE_6, + SFX_WMYRI_DODGE_7, + SFX_WMYRI_DODGE_8, + SFX_WMYRI_DODGE_9, + SFX_WMYRI_EYEING_1, + SFX_WMYRI_EYEING_2, + SFX_WMYRI_EYEING_3, + SFX_WMYRI_GENERIC_CRASH_1, + SFX_WMYRI_GENERIC_CRASH_2, + SFX_WMYRI_GENERIC_CRASH_3, + SFX_WMYRI_GENERIC_CRASH_4, + SFX_WMYRI_GENERIC_CRASH_5, + SFX_WMYRI_GENERIC_CRASH_6, + SFX_WMYRI_GENERIC_CRASH_7, + SFX_WMYRI_GENERIC_CRASH_8, + SFX_WMYRI_GENERIC_CRASH_9, + SFX_WMYRI_GENERIC_CRASH_10, + SFX_WMYRI_GENERIC_CRASH_11, + SFX_WMYRI_GUN_PANIC_1, + SFX_WMYRI_GUN_PANIC_2, + SFX_WMYRI_GUN_PANIC_3, + SFX_WMYRI_GUN_PANIC_4, + SFX_WMYRI_GUN_PANIC_5, + SFX_WMYRI_GUN_PANIC_6, + SFX_WMYRI_GUN_PANIC_7, + SFX_WMYRI_GUN_PANIC_8, + SFX_WMYRI_JACKED_1, + SFX_WMYRI_JACKED_2, + SFX_WMYRI_JACKED_3, + SFX_WMYRI_JACKED_4, + SFX_WMYRI_JACKED_5, + SFX_WMYRI_JACKED_6, + SFX_WMYRI_JACKED_7, + SFX_WMYRI_JACKED_8, + SFX_WMYRI_LOST_1, + SFX_WMYRI_RUN_1, + SFX_WMYRI_RUN_2, + SFX_WMYRI_RUN_3, + SFX_WMYRI_RUN_4, + SFX_WMYRI_RUN_5, + SFX_WMYRI_SAVED_1, + SFX_WMYRI_SHOCKED_1, + SFX_WMYRI_SHOCKED_2, + SFX_WMYRI_SHOCKED_3, + SFX_WMYRI_SHOCKED_4, + SFX_WMYRI_TAXI_1, + SFX_WMYRI_TAXI_2, + + SFX_BMOST_BUMP_1, + SFX_BMOST_BUMP_2, + SFX_BMOST_BUMP_3, + SFX_BMOST_BUMP_4, + SFX_BMOST_BUMP_5, + SFX_BMOST_BUMP_6, + SFX_BMOST_BUMP_7, + SFX_BMOST_BUMP_8, + SFX_BMOST_BUMP_9, + SFX_BMOST_BUMP_10, + SFX_BMOST_BUMP_11, + SFX_BMOST_BUMP_12, + SFX_BMOST_BUMP_13, + SFX_BMOST_BUMP_14, + SFX_BMOST_BUMP_15, + SFX_BMOST_BUMP_16, + SFX_BMOST_BUMP_17, + SFX_BMOST_CAR_CRASH_1, + SFX_BMOST_CAR_CRASH_2, + SFX_BMOST_CAR_CRASH_3, + SFX_BMOST_CAR_CRASH_4, + SFX_BMOST_CAR_CRASH_5, + SFX_BMOST_CAR_CRASH_6, + SFX_BMOST_CAR_CRASH_7, + SFX_BMOST_CAR_CRASH_8, + SFX_BMOST_CHAT_1, + SFX_BMOST_CHAT_2, + SFX_BMOST_CHAT_3, + SFX_BMOST_CHAT_4, + SFX_BMOST_CHAT_5, + SFX_BMOST_CHAT_6, + SFX_BMOST_CHAT_7, + SFX_BMOST_CHAT_8, + SFX_BMOST_CHAT_9, + SFX_BMOST_CHAT_10, + SFX_BMOST_CHAT_11, + SFX_BMOST_CHAT_12, + SFX_BMOST_CHAT_13, + SFX_BMOST_CHAT_14, + SFX_BMOST_CHAT_15, + SFX_BMOST_CHAT_16, + SFX_BMOST_CHAT_17, + SFX_BMOST_CHAT_18, + SFX_BMOST_DODGE_1, + SFX_BMOST_DODGE_2, + SFX_BMOST_DODGE_3, + SFX_BMOST_DODGE_4, + SFX_BMOST_DODGE_5, + SFX_BMOST_DODGE_6, + SFX_BMOST_DODGE_7, + SFX_BMOST_DODGE_8, + SFX_BMOST_EYEING_1, + SFX_BMOST_EYEING_2, + SFX_BMOST_EYEING_3, + SFX_BMOST_EYEING_4, + SFX_BMOST_EYEING_5, + SFX_BMOST_EYEING_6, + SFX_BMOST_FIGHT_1, + SFX_BMOST_FIGHT_2, + SFX_BMOST_FIGHT_3, + SFX_BMOST_FIGHT_4, + SFX_BMOST_FIGHT_5, + SFX_BMOST_FIGHT_6, + SFX_BMOST_FIGHT_7, + SFX_BMOST_GENERIC_CRASH_1, + SFX_BMOST_GENERIC_CRASH_2, + SFX_BMOST_GENERIC_CRASH_3, + SFX_BMOST_GENERIC_CRASH_4, + SFX_BMOST_GENERIC_CRASH_5, + SFX_BMOST_GENERIC_CRASH_6, + SFX_BMOST_GENERIC_CRASH_7, + SFX_BMOST_GENERIC_CRASH_8, + SFX_BMOST_GENERIC_CRASH_9, + SFX_BMOST_GENERIC_CRASH_10, + SFX_BMOST_GENERIC_CRASH_11, + SFX_BMOST_GENERIC_CRASH_12, + SFX_BMOST_GENERIC_CRASH_13, + SFX_BMOST_GUN_PANIC_1, + SFX_BMOST_GUN_PANIC_2, + SFX_BMOST_GUN_PANIC_3, + SFX_BMOST_GUN_PANIC_4, + SFX_BMOST_GUN_PANIC_5, + SFX_BMOST_GUN_PANIC_6, + SFX_BMOST_GUN_PANIC_7, + SFX_BMOST_GUN_PANIC_8, + SFX_BMOST_GUN_PANIC_9, + SFX_BMOST_LOST_1, + SFX_BMOST_LOST_2, + SFX_BMOST_LOST_3, + SFX_BMOST_LOST_4, + SFX_BMOST_LOST_5, + SFX_BMOST_LOST_6, + SFX_BMOST_MUGGED_1, + SFX_BMOST_MUGGED_2, + SFX_BMOST_MUGGED_3, + SFX_BMOST_MUGGED_4, + SFX_BMOST_SAVED_1, + SFX_BMOST_TAXI_1, + + SFX_HFOST_BLOCKED_1, + SFX_HFOST_BLOCKED_2, + SFX_HFOST_BLOCKED_3, + SFX_HFOST_BLOCKED_4, + SFX_HFOST_BLOCKED_5, + SFX_HFOST_BLOCKED_6, + SFX_HFOST_BLOCKED_7, + SFX_HFOST_BLOCKED_8, + SFX_HFOST_BLOCKED_9, + SFX_HFOST_BUMP_1, + SFX_HFOST_BUMP_2, + SFX_HFOST_BUMP_3, + SFX_HFOST_BUMP_4, + SFX_HFOST_BUMP_5, + SFX_HFOST_BUMP_6, + SFX_HFOST_BUMP_7, + SFX_HFOST_BUMP_8, + SFX_HFOST_BUMP_9, + SFX_HFOST_BUMP_10, + SFX_HFOST_BUMP_11, + SFX_HFOST_BUMP_12, + SFX_HFOST_CAR_CRASH_1, + SFX_HFOST_CAR_CRASH_2, + SFX_HFOST_CAR_CRASH_3, + SFX_HFOST_CAR_CRASH_4, + SFX_HFOST_CAR_CRASH_5, + SFX_HFOST_CAR_CRASH_6, + SFX_HFOST_CAR_CRASH_7, + SFX_HFOST_CAR_CRASH_8, + SFX_HFOST_CHAT_1, + SFX_HFOST_CHAT_2, + SFX_HFOST_CHAT_3, + SFX_HFOST_CHAT_4, + SFX_HFOST_CHAT_5, + SFX_HFOST_CHAT_6, + SFX_HFOST_CHAT_7, + SFX_HFOST_CHAT_8, + SFX_HFOST_CHAT_9, + SFX_HFOST_CHAT_10, + SFX_HFOST_CHAT_11, + SFX_HFOST_DODGE_1, + SFX_HFOST_DODGE_2, + SFX_HFOST_DODGE_3, + SFX_HFOST_DODGE_4, + SFX_HFOST_DODGE_5, + SFX_HFOST_DODGE_6, + SFX_HFOST_DODGE_7, + SFX_HFOST_DODGE_8, + SFX_HFOST_DODGE_9, + SFX_HFOST_DODGE_10, + SFX_HFOST_FIGHT_1, + SFX_HFOST_FIGHT_2, + SFX_HFOST_FIGHT_3, + SFX_HFOST_FIGHT_4, + SFX_HFOST_FIGHT_5, + SFX_HFOST_FIGHT_6, + SFX_HFOST_FIGHT_7, + SFX_HFOST_FIGHT_8, + SFX_HFOST_GENERIC_CRASH_1, + SFX_HFOST_GENERIC_CRASH_2, + SFX_HFOST_GENERIC_CRASH_3, + SFX_HFOST_GENERIC_CRASH_4, + SFX_HFOST_GENERIC_CRASH_5, + SFX_HFOST_GENERIC_CRASH_6, + SFX_HFOST_GENERIC_CRASH_7, + SFX_HFOST_GENERIC_CRASH_8, + SFX_HFOST_GENERIC_CRASH_9, + SFX_HFOST_GENERIC_CRASH_10, + SFX_HFOST_GENERIC_CRASH_11, + SFX_HFOST_GUN_COOL_1, + SFX_HFOST_GUN_COOL_2, + SFX_HFOST_GUN_COOL_3, + SFX_HFOST_GUN_COOL_4, + SFX_HFOST_GUN_COOL_5, + SFX_HFOST_GUN_COOL_6, + SFX_HFOST_JACKED_1, + SFX_HFOST_JACKED_2, + SFX_HFOST_JACKED_3, + SFX_HFOST_JACKED_4, + SFX_HFOST_JACKED_5, + SFX_HFOST_JACKED_6, + SFX_HFOST_JACKED_7, + SFX_HFOST_JACKED_8, + SFX_HFOST_LOST_1, + SFX_HFOST_LOST_2, + SFX_HFOST_MUGGED_1, + SFX_HFOST_MUGGED_2, + SFX_HFOST_MUGGED_3, + SFX_HFOST_TAXI_1, + SFX_HFOST_TAXI_2, + + SFX_HMORI_BLOCKED_1, + SFX_HMORI_BLOCKED_2, + SFX_HMORI_BLOCKED_3, + SFX_HMORI_BLOCKED_4, + SFX_HMORI_BLOCKED_5, + SFX_HMORI_BLOCKED_6, + SFX_HMORI_BLOCKED_7, + SFX_HMORI_BLOCKED_8, + SFX_HMORI_BUMP_1, + SFX_HMORI_BUMP_2, + SFX_HMORI_BUMP_3, + SFX_HMORI_BUMP_4, + SFX_HMORI_BUMP_5, + SFX_HMORI_BUMP_6, + SFX_HMORI_BUMP_7, + SFX_HMORI_BUMP_8, + SFX_HMORI_BUMP_9, + SFX_HMORI_BUMP_10, + SFX_HMORI_BUMP_11, + SFX_HMORI_CAR_CRASH_1, + SFX_HMORI_CAR_CRASH_2, + SFX_HMORI_CAR_CRASH_3, + SFX_HMORI_CAR_CRASH_4, + SFX_HMORI_CAR_CRASH_5, + SFX_HMORI_CAR_CRASH_6, + SFX_HMORI_CHAT_1, + SFX_HMORI_CHAT_2, + SFX_HMORI_CHAT_3, + SFX_HMORI_CHAT_4, + SFX_HMORI_CHAT_5, + SFX_HMORI_CHAT_6, + SFX_HMORI_CHAT_7, + SFX_HMORI_CHAT_8, + SFX_HMORI_DODGE_1, + SFX_HMORI_DODGE_2, + SFX_HMORI_DODGE_3, + SFX_HMORI_DODGE_4, + SFX_HMORI_DODGE_5, + SFX_HMORI_DODGE_6, + SFX_HMORI_DODGE_7, + SFX_HMORI_GENERIC_CRASH_1, + SFX_HMORI_GENERIC_CRASH_2, + SFX_HMORI_GENERIC_CRASH_3, + SFX_HMORI_GENERIC_CRASH_4, + SFX_HMORI_GENERIC_CRASH_5, + SFX_HMORI_GENERIC_CRASH_6, + SFX_HMORI_GENERIC_CRASH_7, + SFX_HMORI_GENERIC_CRASH_8, + SFX_HMORI_GENERIC_CRASH_9, + SFX_HMORI_GENERIC_CRASH_10, + SFX_HMORI_GENERIC_CRASH_11, + SFX_HMORI_GUN_PANIC_1, + SFX_HMORI_GUN_PANIC_2, + SFX_HMORI_GUN_PANIC_3, + SFX_HMORI_GUN_PANIC_4, + SFX_HMORI_GUN_PANIC_5, + SFX_HMORI_JACKED_1, + SFX_HMORI_JACKED_2, + SFX_HMORI_JACKED_3, + SFX_HMORI_JACKED_4, + SFX_HMORI_JACKED_5, + SFX_HMORI_JACKED_6, + SFX_HMORI_JACKED_7, + SFX_HMORI_JACKED_8, + SFX_HMORI_LOST_1, + SFX_HMORI_LOST_2, + SFX_HMORI_MUGGED_1, + SFX_HMORI_MUGGED_2, + SFX_HMORI_MUGGED_3, + SFX_HMORI_RUN_1, + SFX_HMORI_RUN_2, + SFX_HMORI_RUN_3, + SFX_HMORI_RUN_4, + SFX_HMORI_RUN_5, + SFX_HMORI_RUN_6, + SFX_HMORI_TAXI_1, + SFX_HMORI_TAXI_2, + + SFX_HMOTR_BUMP_1, + SFX_HMOTR_BUMP_2, + SFX_HMOTR_BUMP_3, + SFX_HMOTR_BUMP_4, + SFX_HMOTR_BUMP_5, + SFX_HMOTR_BUMP_6, + SFX_HMOTR_BUMP_7, + SFX_HMOTR_BUMP_8, + SFX_HMOTR_CHAT_1, + SFX_HMOTR_CHAT_2, + SFX_HMOTR_CHAT_3, + SFX_HMOTR_CHAT_4, + SFX_HMOTR_CHAT_5, + SFX_HMOTR_CHAT_6, + SFX_HMOTR_CHAT_7, + SFX_HMOTR_CHAT_8, + SFX_HMOTR_CHAT_9, + SFX_HMOTR_DODGE_1, + SFX_HMOTR_DODGE_2, + SFX_HMOTR_DODGE_3, + SFX_HMOTR_DODGE_4, + SFX_HMOTR_DODGE_5, + SFX_HMOTR_DODGE_6, + SFX_HMOTR_DODGE_7, + SFX_HMOTR_DODGE_8, + SFX_HMOTR_DODGE_9, + SFX_HMOTR_DODGE_10, + SFX_HMOTR_DODGE_11, + SFX_HMOTR_FIGHT_1, + SFX_HMOTR_FIGHT_2, + SFX_HMOTR_FIGHT_3, + SFX_HMOTR_FIGHT_4, + SFX_HMOTR_FIGHT_5, + SFX_HMOTR_FIGHT_6, + SFX_HMOTR_FIGHT_7, + SFX_HMOTR_GUN_COOL_1, + SFX_HMOTR_GUN_COOL_2, + SFX_HMOTR_GUN_COOL_3, + SFX_HMOTR_GUN_COOL_4, + SFX_HMOTR_GUN_COOL_5, + SFX_HMOTR_GUN_COOL_6, + SFX_HMOTR_SAVED_1, + SFX_HMOTR_SAVED_2, + SFX_HMOTR_SHOCKED_1, + SFX_HMOTR_SHOCKED_2, + SFX_HMOTR_SHOCKED_3, + SFX_HMOTR_SOLICIT_1, + SFX_HMOTR_SOLICIT_2, + SFX_HMOTR_SOLICIT_3, + SFX_HMOTR_SOLICIT_4, + SFX_HMOTR_SOLICIT_5, + SFX_HMOTR_SOLICIT_6, + SFX_HMOTR_SOLICIT_7, + SFX_HMOTR_SOLICIT_8, + SFX_HMOTR_TAXI_1, + + SFX_HMYAP_BLOCKED_1, + SFX_HMYAP_BLOCKED_2, + SFX_HMYAP_BLOCKED_3, + SFX_HMYAP_BLOCKED_4, + SFX_HMYAP_BLOCKED_5, + SFX_HMYAP_BLOCKED_6, + SFX_HMYAP_BLOCKED_7, + SFX_HMYAP_BLOCKED_8, + SFX_HMYAP_BLOCKED_9, + SFX_HMYAP_BUMP_1, + SFX_HMYAP_BUMP_2, + SFX_HMYAP_BUMP_3, + SFX_HMYAP_BUMP_4, + SFX_HMYAP_BUMP_5, + SFX_HMYAP_BUMP_6, + SFX_HMYAP_BUMP_7, + SFX_HMYAP_BUMP_8, + SFX_HMYAP_BUMP_9, + SFX_HMYAP_BUMP_10, + SFX_HMYAP_BUMP_11, + SFX_HMYAP_CAR_CRASH_1, + SFX_HMYAP_CAR_CRASH_2, + SFX_HMYAP_CAR_CRASH_3, + SFX_HMYAP_CAR_CRASH_4, + SFX_HMYAP_CAR_CRASH_5, + SFX_HMYAP_CAR_CRASH_6, + SFX_HMYAP_CAR_CRASH_7, + SFX_HMYAP_CAR_CRASH_8, + SFX_HMYAP_CAR_CRASH_9, + SFX_HMYAP_CHAT_1, + SFX_HMYAP_CHAT_2, + SFX_HMYAP_CHAT_3, + SFX_HMYAP_CHAT_4, + SFX_HMYAP_CHAT_5, + SFX_HMYAP_CHAT_6, + SFX_HMYAP_CHAT_7, + SFX_HMYAP_CHAT_8, + SFX_HMYAP_CHAT_9, + SFX_HMYAP_DODGE_1, + SFX_HMYAP_DODGE_2, + SFX_HMYAP_DODGE_3, + SFX_HMYAP_DODGE_4, + SFX_HMYAP_DODGE_5, + SFX_HMYAP_DODGE_6, + SFX_HMYAP_DODGE_7, + SFX_HMYAP_DODGE_8, + SFX_HMYAP_DODGE_9, + SFX_HMYAP_EYEING_1, + SFX_HMYAP_EYEING_2, + SFX_HMYAP_EYEING_3, + SFX_HMYAP_GENERIC_CRASH_1, + SFX_HMYAP_GENERIC_CRASH_2, + SFX_HMYAP_GENERIC_CRASH_3, + SFX_HMYAP_GENERIC_CRASH_4, + SFX_HMYAP_GENERIC_CRASH_5, + SFX_HMYAP_GENERIC_CRASH_6, + SFX_HMYAP_GUN_PANIC_1, + SFX_HMYAP_GUN_PANIC_2, + SFX_HMYAP_GUN_PANIC_3, + SFX_HMYAP_GUN_PANIC_4, + SFX_HMYAP_GUN_PANIC_5, + SFX_HMYAP_GUN_PANIC_6, + SFX_HMYAP_GUN_PANIC_7, + SFX_HMYAP_JACKED_1, + SFX_HMYAP_JACKED_2, + SFX_HMYAP_JACKED_3, + SFX_HMYAP_JACKED_4, + SFX_HMYAP_JACKED_5, + SFX_HMYAP_JACKED_6, + SFX_HMYAP_JACKED_7, + SFX_HMYAP_JACKING_1, + SFX_HMYAP_JACKING_2, + SFX_HMYAP_JACKING_3, + SFX_HMYAP_JACKING_4, + SFX_HMYAP_LOST_1, + SFX_HMYAP_LOST_2, + SFX_HMYAP_MUGGED_1, + SFX_HMYAP_MUGGED_2, + SFX_HMYAP_RUN_1, + SFX_HMYAP_RUN_2, + SFX_HMYAP_RUN_3, + SFX_HMYAP_RUN_4, + SFX_HMYAP_RUN_5, + SFX_HMYAP_RUN_6, + SFX_HMYAP_SAVED_1, + SFX_HMYAP_SAVED_2, + SFX_HMYAP_TAXI_1, + SFX_HMYAP_TAXI_2, + + SFX_HFOTR_BUMP_1, + SFX_HFOTR_BUMP_2, + SFX_HFOTR_BUMP_3, + SFX_HFOTR_BUMP_4, + SFX_HFOTR_BUMP_5, + SFX_HFOTR_BUMP_6, + SFX_HFOTR_BUMP_7, + SFX_HFOTR_BUMP_8, + SFX_HFOTR_BUMP_9, + SFX_HFOTR_BUMP_10, + SFX_HFOTR_BUMP_11, + SFX_HFOTR_CHAT_1, + SFX_HFOTR_CHAT_2, + SFX_HFOTR_CHAT_3, + SFX_HFOTR_CHAT_4, + SFX_HFOTR_CHAT_5, + SFX_HFOTR_CHAT_6, + SFX_HFOTR_CHAT_7, + SFX_HFOTR_CHAT_8, + SFX_HFOTR_CHAT_9, + SFX_HFOTR_CHAT_10, + SFX_HFOTR_CHAT_11, + SFX_HFOTR_CHAT_12, + SFX_HFOTR_DODGE_1, + SFX_HFOTR_DODGE_2, + SFX_HFOTR_DODGE_3, + SFX_HFOTR_DODGE_4, + SFX_HFOTR_DODGE_5, + SFX_HFOTR_DODGE_6, + SFX_HFOTR_DODGE_7, + SFX_HFOTR_DODGE_8, + SFX_HFOTR_FIGHT_1, + SFX_HFOTR_FIGHT_2, + SFX_HFOTR_FIGHT_3, + SFX_HFOTR_FIGHT_4, + SFX_HFOTR_FIGHT_5, + SFX_HFOTR_FIGHT_6, + SFX_HFOTR_GUN_COOL_1, + SFX_HFOTR_GUN_COOL_2, + SFX_HFOTR_GUN_COOL_3, + SFX_HFOTR_GUN_COOL_4, + SFX_HFOTR_GUN_COOL_5, + SFX_HFOTR_MUGGED_1, + SFX_HFOTR_MUGGED_2, + SFX_HFOTR_SAVED_1, + SFX_HFOTR_SHOCKED_1, + SFX_HFOTR_SHOCKED_2, + SFX_HFOTR_TAXI_1, + SFX_HFOTR_TAXI_2, + + SFX_HMOBE_BLOCKED_1, + SFX_HMOBE_BLOCKED_2, + SFX_HMOBE_BLOCKED_3, + SFX_HMOBE_BLOCKED_4, + SFX_HMOBE_BLOCKED_5, + SFX_HMOBE_BLOCKED_6, + SFX_HMOBE_BLOCKED_7, + SFX_HMOBE_BLOCKED_8, + SFX_HMOBE_BLOCKED_9, + SFX_HMOBE_BLOCKED_10, + SFX_HMOBE_BUMP_1, + SFX_HMOBE_BUMP_2, + SFX_HMOBE_BUMP_3, + SFX_HMOBE_BUMP_4, + SFX_HMOBE_BUMP_5, + SFX_HMOBE_BUMP_6, + SFX_HMOBE_BUMP_7, + SFX_HMOBE_BUMP_8, + SFX_HMOBE_DODGE_1, + SFX_HMOBE_DODGE_2, + SFX_HMOBE_DODGE_3, + SFX_HMOBE_DODGE_4, + SFX_HMOBE_DODGE_5, + SFX_HMOBE_DODGE_6, + SFX_HMOBE_DODGE_7, + SFX_HMOBE_DODGE_8, + SFX_HMOBE_DODGE_9, + SFX_HMOBE_EYEING_1, + SFX_HMOBE_EYEING_2, + SFX_HMOBE_EYEING_3, + SFX_HMOBE_EYEING_4, + SFX_HMOBE_GUN_PANIC_1, + SFX_HMOBE_GUN_PANIC_2, + SFX_HMOBE_GUN_PANIC_3, + SFX_HMOBE_INNOCENT_1, + SFX_HMOBE_INNOCENT_2, + SFX_HMOBE_INNOCENT_3, + SFX_HMOBE_JACKED_1, + SFX_HMOBE_JACKED_2, + SFX_HMOBE_JACKED_3, + SFX_HMOBE_JACKED_4, + SFX_HMOBE_JACKED_5, + SFX_HMOBE_JACKED_6, + + SFX_HFYBU_BLOCKED_1, + SFX_HFYBU_BLOCKED_2, + SFX_HFYBU_BLOCKED_3, + SFX_HFYBU_BLOCKED_4, + SFX_HFYBU_BLOCKED_5, + SFX_HFYBU_BLOCKED_6, + SFX_HFYBU_BLOCKED_7, + SFX_HFYBU_BLOCKED_8, + SFX_HFYBU_BUMP_1, + SFX_HFYBU_BUMP_2, + SFX_HFYBU_BUMP_3, + SFX_HFYBU_BUMP_4, + SFX_HFYBU_BUMP_5, + SFX_HFYBU_BUMP_6, + SFX_HFYBU_BUMP_7, + SFX_HFYBU_BUMP_8, + SFX_HFYBU_BUMP_9, + SFX_HFYBU_BUMP_10, + SFX_HFYBU_BUMP_11, + SFX_HFYBU_CAR_CRASH_1, + SFX_HFYBU_CAR_CRASH_2, + SFX_HFYBU_CAR_CRASH_3, + SFX_HFYBU_CAR_CRASH_4, + SFX_HFYBU_CAR_CRASH_5, + SFX_HFYBU_CAR_CRASH_6, + SFX_HFYBU_CAR_CRASH_7, + SFX_HFYBU_CAR_CRASH_8, + SFX_HFYBU_DODGE_1, + SFX_HFYBU_DODGE_2, + SFX_HFYBU_DODGE_3, + SFX_HFYBU_DODGE_4, + SFX_HFYBU_DODGE_5, + SFX_HFYBU_DODGE_6, + SFX_HFYBU_DODGE_7, + SFX_HFYBU_DODGE_8, + SFX_HFYBU_DODGE_9, + SFX_HFYBU_DODGE_10, + SFX_HFYBU_FIGHT_1, + SFX_HFYBU_FIGHT_2, + SFX_HFYBU_FIGHT_3, + SFX_HFYBU_FIGHT_4, + SFX_HFYBU_FIGHT_5, + SFX_HFYBU_FIGHT_6, + SFX_HFYBU_FIGHT_7, + SFX_HFYBU_GENERIC_CRASH_1, + SFX_HFYBU_GENERIC_CRASH_2, + SFX_HFYBU_GENERIC_CRASH_3, + SFX_HFYBU_GENERIC_CRASH_4, + SFX_HFYBU_GENERIC_CRASH_5, + SFX_HFYBU_GENERIC_CRASH_6, + SFX_HFYBU_GENERIC_CRASH_7, + SFX_HFYBU_GENERIC_CRASH_8, + SFX_HFYBU_GENERIC_CRASH_9, + SFX_HFYBU_GENERIC_CRASH_10, + SFX_HFYBU_GENERIC_CRASH_11, + SFX_HFYBU_GENERIC_CRASH_12, + SFX_HFYBU_GUN_PANIC_1, + SFX_HFYBU_GUN_PANIC_2, + SFX_HFYBU_GUN_PANIC_3, + SFX_HFYBU_GUN_PANIC_4, + SFX_HFYBU_GUN_PANIC_5, + SFX_HFYBU_JACKED_1, + SFX_HFYBU_JACKED_2, + SFX_HFYBU_JACKED_3, + SFX_HFYBU_JACKED_4, + SFX_HFYBU_JACKED_5, + SFX_HFYBU_JACKED_6, + SFX_HFYBU_JACKING_1, + SFX_HFYBU_JACKING_2, + SFX_HFYBU_JACKING_3, + SFX_HFYBU_LOST_1, + SFX_HFYBU_LOST_2, + SFX_HFYBU_MUGGED_1, + SFX_HFYBU_MUGGED_2, + SFX_HFYBU_SAVED_1, + SFX_HFYBU_TAXI_1, + + SFX_HFYCG_BUMP_1, + SFX_HFYCG_BUMP_2, + SFX_HFYCG_BUMP_3, + SFX_HFYCG_BUMP_4, + SFX_HFYCG_BUMP_5, + SFX_HFYCG_BUMP_6, + SFX_HFYCG_BUMP_7, + SFX_HFYCG_BUMP_8, + SFX_HFYCG_BUMP_9, + SFX_HFYCG_DODGE_1, + SFX_HFYCG_DODGE_2, + SFX_HFYCG_DODGE_3, + SFX_HFYCG_DODGE_4, + SFX_HFYCG_DODGE_5, + SFX_HFYCG_DODGE_6, + SFX_HFYCG_DODGE_7, + SFX_HFYCG_DODGE_8, + SFX_HFYCG_GUN_PANIC_1, + SFX_HFYCG_GUN_PANIC_2, + SFX_HFYCG_GUN_PANIC_3, + SFX_HFYCG_GUN_PANIC_4, + SFX_HFYCG_GUN_PANIC_5, + SFX_HFYCG_MUGGED_1, + SFX_HFYCG_MUGGED_2, + SFX_HFYCG_RUN_1, + SFX_HFYCG_RUN_2, + SFX_HFYCG_RUN_3, + SFX_HFYCG_RUN_4, + SFX_HFYCG_SAVED_1, + SFX_HFYCG_SOLICIT_1, + SFX_HFYCG_SOLICIT_2, + SFX_HFYCG_SOLICIT_3, + SFX_HFYCG_SOLICIT_4, + SFX_HFYCG_SOLICIT_5, + SFX_HFYCG_SOLICIT_6, + SFX_HFYCG_SOLICIT_7, + SFX_HFYCG_SOLICIT_8, + SFX_HFYCG_SOLICIT_9, + SFX_HFYCG_SOLICIT_10, + SFX_HFYCG_SOLICIT_11, + SFX_HFYCG_SOLICIT_12, + SFX_HFYCG_SOLICIT_13, + SFX_HFYCG_SOLICIT_14, + SFX_HFYCG_TAXI_1, + + SFX_HMYBE_BUMP_1, + SFX_HMYBE_BUMP_2, + SFX_HMYBE_BUMP_3, + SFX_HMYBE_BUMP_4, + SFX_HMYBE_BUMP_5, + SFX_HMYBE_BUMP_6, + SFX_HMYBE_BUMP_7, + SFX_HMYBE_BUMP_8, + SFX_HMYBE_BUMP_9, + SFX_HMYBE_BUMP_10, + SFX_HMYBE_CAR_CRASH_1, + SFX_HMYBE_CAR_CRASH_2, + SFX_HMYBE_CAR_CRASH_3, + SFX_HMYBE_CAR_CRASH_4, + SFX_HMYBE_CAR_CRASH_5, + SFX_HMYBE_CAR_CRASH_6, + SFX_HMYBE_CAR_CRASH_7, + SFX_HMYBE_CHAT_1, + SFX_HMYBE_CHAT_2, + SFX_HMYBE_CHAT_3, + SFX_HMYBE_CHAT_4, + SFX_HMYBE_CHAT_5, + SFX_HMYBE_CHAT_6, + SFX_HMYBE_CHAT_7, + SFX_HMYBE_CHAT_8, + SFX_HMYBE_CHAT_9, + SFX_HMYBE_CHAT_10, + SFX_HMYBE_DODGE_1, + SFX_HMYBE_DODGE_2, + SFX_HMYBE_DODGE_3, + SFX_HMYBE_DODGE_4, + SFX_HMYBE_DODGE_5, + SFX_HMYBE_DODGE_6, + SFX_HMYBE_DODGE_7, + SFX_HMYBE_EYEING_1, + SFX_HMYBE_EYEING_2, + SFX_HMYBE_EYEING_3, + SFX_HMYBE_EYEING_4, + SFX_HMYBE_EYEING_5, + SFX_HMYBE_FIGHT_1, + SFX_HMYBE_FIGHT_2, + SFX_HMYBE_FIGHT_3, + SFX_HMYBE_FIGHT_4, + SFX_HMYBE_FIGHT_5, + SFX_HMYBE_FIGHT_6, + SFX_HMYBE_FIGHT_7, + SFX_HMYBE_FIGHT_8, + SFX_HMYBE_GENERIC_CRASH_1, + SFX_HMYBE_GENERIC_CRASH_2, + SFX_HMYBE_GENERIC_CRASH_3, + SFX_HMYBE_GENERIC_CRASH_4, + SFX_HMYBE_GENERIC_CRASH_5, + SFX_HMYBE_GENERIC_CRASH_6, + SFX_HMYBE_GENERIC_CRASH_7, + SFX_HMYBE_GENERIC_CRASH_8, + SFX_HMYBE_GENERIC_CRASH_9, + SFX_HMYBE_GENERIC_CRASH_10, + SFX_HMYBE_GUN_PANIC_1, + SFX_HMYBE_GUN_PANIC_2, + SFX_HMYBE_GUN_PANIC_3, + SFX_HMYBE_GUN_PANIC_4, + SFX_HMYBE_GUN_PANIC_5, + SFX_HMYBE_GUN_PANIC_6, + SFX_HMYBE_INNOCENT_1, + SFX_HMYBE_INNOCENT_2, + SFX_HMYBE_INNOCENT_3, + SFX_HMYBE_INNOCENT_4, + SFX_HMYBE_JACKED_1, + SFX_HMYBE_JACKED_2, + SFX_HMYBE_JACKED_3, + SFX_HMYBE_JACKED_4, + SFX_HMYBE_JACKED_5, + SFX_HMYBE_JACKED_6, + SFX_HMYBE_JACKED_7, + SFX_HMYBE_JACKED_8, + SFX_HMYBE_JACKED_9, + SFX_HMYBE_JACKED_10, + SFX_HMYBE_JACKED_11, + SFX_HMYBE_JACKED_12, + SFX_HMYBE_LOST_1, + SFX_HMYBE_LOST_2, + SFX_HMYBE_LOST_3, + SFX_HMYBE_SAVED_1, + SFX_HMYBE_SHOCKED_1, + SFX_HMYBE_SHOCKED_2, + SFX_HMYBE_TAXI_1, + + SFX_WMOGO_BUMP_1, + SFX_WMOGO_BUMP_2, + SFX_WMOGO_BUMP_3, + SFX_WMOGO_BUMP_4, + SFX_WMOGO_BUMP_5, + SFX_WMOGO_BUMP_6, + SFX_WMOGO_BUMP_7, + SFX_WMOGO_BUMP_8, + SFX_WMOGO_CAR_CRASH_1, + SFX_WMOGO_CAR_CRASH_2, + SFX_WMOGO_CAR_CRASH_3, + SFX_WMOGO_CAR_CRASH_4, + SFX_WMOGO_CAR_CRASH_5, + SFX_WMOGO_CAR_CRASH_6, + SFX_WMOGO_CAR_CRASH_7, + SFX_WMOGO_CAR_CRASH_8, + SFX_WMOGO_CAR_CRASH_9, + SFX_WMOGO_CHAT_1, + SFX_WMOGO_CHAT_2, + SFX_WMOGO_CHAT_3, + SFX_WMOGO_CHAT_4, + SFX_WMOGO_CHAT_5, + SFX_WMOGO_CHAT_6, + SFX_WMOGO_CHAT_7, + SFX_WMOGO_CHAT_8, + SFX_WMOGO_CHAT_9, + SFX_WMOGO_DODGE_1, + SFX_WMOGO_DODGE_2, + SFX_WMOGO_DODGE_3, + SFX_WMOGO_DODGE_4, + SFX_WMOGO_DODGE_5, + SFX_WMOGO_DODGE_6, + SFX_WMOGO_DODGE_7, + SFX_WMOGO_DODGE_8, + SFX_WMOGO_DODGE_9, + SFX_WMOGO_DODGE_10, + SFX_WMOGO_DODGE_11, + SFX_WMOGO_DODGE_12, + SFX_WMOGO_EYEING_1, + SFX_WMOGO_EYEING_2, + SFX_WMOGO_FIGHT_1, + SFX_WMOGO_FIGHT_2, + SFX_WMOGO_FIGHT_3, + SFX_WMOGO_FIGHT_4, + SFX_WMOGO_FIGHT_5, + SFX_WMOGO_FIGHT_6, + SFX_WMOGO_FIGHT_7, + SFX_WMOGO_FIGHT_8, + SFX_WMOGO_FIGHT_9, + SFX_WMOGO_FIGHT_10, + SFX_WMOGO_FIGHT_11, + SFX_WMOGO_FIGHT_12, + SFX_WMOGO_FIGHT_13, + SFX_WMOGO_GENERIC_CRASH_1, + SFX_WMOGO_GENERIC_CRASH_2, + SFX_WMOGO_GENERIC_CRASH_3, + SFX_WMOGO_GENERIC_CRASH_4, + SFX_WMOGO_GENERIC_CRASH_5, + SFX_WMOGO_GENERIC_CRASH_6, + SFX_WMOGO_GENERIC_CRASH_7, + SFX_WMOGO_GENERIC_CRASH_8, + SFX_WMOGO_GUN_PANIC_1, + SFX_WMOGO_GUN_PANIC_2, + SFX_WMOGO_GUN_PANIC_3, + SFX_WMOGO_GUN_PANIC_4, + SFX_WMOGO_GUN_PANIC_5, + SFX_WMOGO_JACKED_1, + SFX_WMOGO_JACKED_2, + SFX_WMOGO_JACKED_3, + SFX_WMOGO_JACKED_4, + SFX_WMOGO_JACKED_5, + SFX_WMOGO_JACKED_6, + SFX_WMOGO_RUN_1, + SFX_WMOGO_RUN_2, + SFX_WMOGO_RUN_3, + SFX_WMOGO_RUN_4, + SFX_WMOGO_RUN_5, + SFX_WMOGO_SAVED_1, + SFX_WMOGO_SHOCKED_1, + SFX_WMOGO_SHOCKED_2, + SFX_WMOGO_SHOCKED_3, + SFX_WMOGO_TAXI_1, + + SFX_WMYCR_BUMP_1, + SFX_WMYCR_BUMP_2, + SFX_WMYCR_BUMP_3, + SFX_WMYCR_BUMP_4, + SFX_WMYCR_BUMP_5, + SFX_WMYCR_BUMP_6, + SFX_WMYCR_BUMP_7, + SFX_WMYCR_BUMP_8, + SFX_WMYCR_BUMP_9, + SFX_WMYCR_BUMP_10, + SFX_WMYCR_BUMP_11, + SFX_WMYCR_BUMP_12, + SFX_WMYCR_BUMP_13, + SFX_WMYCR_BUMP_14, + SFX_WMYCR_BUMP_15, + SFX_WMYCR_BUMP_16, + SFX_WMYCR_BUMP_17, + SFX_WMYCR_BUMP_18, + SFX_WMYCR_CAR_CRASH_1, + SFX_WMYCR_CAR_CRASH_2, + SFX_WMYCR_CAR_CRASH_3, + SFX_WMYCR_CAR_CRASH_4, + SFX_WMYCR_CAR_CRASH_5, + SFX_WMYCR_CAR_CRASH_6, + SFX_WMYCR_CAR_CRASH_7, + SFX_WMYCR_CAR_CRASH_8, + SFX_WMYCR_CAR_CRASH_9, + SFX_WMYCR_DODGE_1, + SFX_WMYCR_DODGE_2, + SFX_WMYCR_DODGE_3, + SFX_WMYCR_DODGE_4, + SFX_WMYCR_DODGE_5, + SFX_WMYCR_DODGE_6, + SFX_WMYCR_DODGE_7, + SFX_WMYCR_DODGE_8, + SFX_WMYCR_DODGE_9, + SFX_WMYCR_DODGE_10, + SFX_WMYCR_FIGHT_1, + SFX_WMYCR_FIGHT_2, + SFX_WMYCR_FIGHT_3, + SFX_WMYCR_FIGHT_4, + SFX_WMYCR_FIGHT_5, + SFX_WMYCR_FIGHT_6, + SFX_WMYCR_FIGHT_7, + SFX_WMYCR_GENERIC_CRASH_1, + SFX_WMYCR_GENERIC_CRASH_2, + SFX_WMYCR_GENERIC_CRASH_3, + SFX_WMYCR_GENERIC_CRASH_4, + SFX_WMYCR_GENERIC_CRASH_5, + SFX_WMYCR_GENERIC_CRASH_6, + SFX_WMYCR_GENERIC_CRASH_7, + SFX_WMYCR_GENERIC_CRASH_8, + SFX_WMYCR_GENERIC_CRASH_9, + SFX_WMYCR_GUN_COOL_1, + SFX_WMYCR_GUN_COOL_2, + SFX_WMYCR_GUN_COOL_3, + SFX_WMYCR_GUN_COOL_4, + SFX_WMYCR_GUN_COOL_5, + SFX_WMYCR_JACKING_1, + SFX_WMYCR_JACKING_2, + SFX_WMYCR_JACKING_3, + SFX_WMYCR_JACKING_4, + SFX_WMYCR_JACKING_5, + SFX_WMYCR_JACKING_6, + SFX_WMYCR_MUGGED_1, + SFX_WMYCR_MUGGED_2, + SFX_WMYCR_MUGGED_3, + SFX_WMYCR_MUGGING_1, + SFX_WMYCR_MUGGING_2, + SFX_WMYCR_MUGGING_3, + SFX_WMYCR_MUGGING_4, + SFX_WMYCR_MUGGING_5, + SFX_WMYCR_TAXI_1, + + SFX_WMYJG_BLOCKED_1, + SFX_WMYJG_BLOCKED_2, + SFX_WMYJG_BLOCKED_3, + SFX_WMYJG_BLOCKED_4, + SFX_WMYJG_BLOCKED_5, + SFX_WMYJG_BLOCKED_6, + SFX_WMYJG_BLOCKED_7, + SFX_WMYJG_BLOCKED_8, + SFX_WMYJG_BLOCKED_9, + SFX_WMYJG_BLOCKED_10, + SFX_WMYJG_BUMP_1, + SFX_WMYJG_BUMP_2, + SFX_WMYJG_BUMP_3, + SFX_WMYJG_BUMP_4, + SFX_WMYJG_BUMP_5, + SFX_WMYJG_BUMP_6, + SFX_WMYJG_BUMP_7, + SFX_WMYJG_BUMP_8, + SFX_WMYJG_BUMP_9, + SFX_WMYJG_BUMP_10, + SFX_WMYJG_EYEING_1, + SFX_WMYJG_EYEING_2, + SFX_WMYJG_GUN_PANIC_1, + SFX_WMYJG_GUN_PANIC_2, + SFX_WMYJG_GUN_PANIC_3, + SFX_WMYJG_GUN_PANIC_4, + SFX_WMYJG_MUGGED_1, + SFX_WMYJG_MUGGED_2, + SFX_WMYJG_RUN_1, + SFX_WMYJG_RUN_2, + SFX_WMYJG_RUN_3, + SFX_WMYJG_RUN_4, + SFX_WMYJG_RUN_5, + SFX_WMYJG_SAVED_1, + SFX_WMYJG_TAXI_1, + + SFX_WMOST_BLOCKED_1, + SFX_WMOST_BLOCKED_2, + SFX_WMOST_BLOCKED_3, + SFX_WMOST_BLOCKED_4, + SFX_WMOST_BLOCKED_5, + SFX_WMOST_BLOCKED_6, + SFX_WMOST_BLOCKED_7, + SFX_WMOST_BLOCKED_8, + SFX_WMOST_BUMP_1, + SFX_WMOST_BUMP_2, + SFX_WMOST_BUMP_3, + SFX_WMOST_BUMP_4, + SFX_WMOST_BUMP_5, + SFX_WMOST_BUMP_6, + SFX_WMOST_BUMP_7, + SFX_WMOST_BUMP_8, + SFX_WMOST_BUMP_9, + SFX_WMOST_BUMP_10, + SFX_WMOST_CAR_CRASH_1, + SFX_WMOST_CAR_CRASH_2, + SFX_WMOST_CAR_CRASH_3, + SFX_WMOST_CAR_CRASH_4, + SFX_WMOST_CAR_CRASH_5, + SFX_WMOST_CAR_CRASH_6, + SFX_WMOST_CAR_CRASH_7, + SFX_WMOST_CHAT_1, + SFX_WMOST_CHAT_2, + SFX_WMOST_CHAT_3, + SFX_WMOST_CHAT_4, + SFX_WMOST_CHAT_5, + SFX_WMOST_CHAT_6, + SFX_WMOST_CHAT_7, + SFX_WMOST_CHAT_8, + SFX_WMOST_CHAT_9, + SFX_WMOST_DODGE_1, + SFX_WMOST_DODGE_2, + SFX_WMOST_DODGE_3, + SFX_WMOST_DODGE_4, + SFX_WMOST_DODGE_5, + SFX_WMOST_DODGE_6, + SFX_WMOST_DODGE_7, + SFX_WMOST_DODGE_8, + SFX_WMOST_EYEING_1, + SFX_WMOST_EYEING_2, + SFX_WMOST_FIGHT_1, + SFX_WMOST_FIGHT_2, + SFX_WMOST_FIGHT_3, + SFX_WMOST_FIGHT_4, + SFX_WMOST_FIGHT_5, + SFX_WMOST_FIGHT_6, + SFX_WMOST_FIGHT_7, + SFX_WMOST_FIGHT_8, + SFX_WMOST_GENERIC_CRASH_1, + SFX_WMOST_GENERIC_CRASH_2, + SFX_WMOST_GENERIC_CRASH_3, + SFX_WMOST_GENERIC_CRASH_4, + SFX_WMOST_GENERIC_CRASH_5, + SFX_WMOST_GENERIC_CRASH_6, + SFX_WMOST_GENERIC_CRASH_7, + SFX_WMOST_GUN_COOL_1, + SFX_WMOST_GUN_COOL_2, + SFX_WMOST_GUN_COOL_3, + SFX_WMOST_GUN_COOL_4, + SFX_WMOST_GUN_COOL_5, + SFX_WMOST_INNOCENT_1, + SFX_WMOST_INNOCENT_2, + SFX_WMOST_INNOCENT_3, + SFX_WMOST_JACKED_1, + SFX_WMOST_JACKED_2, + SFX_WMOST_JACKED_3, + SFX_WMOST_JACKED_4, + SFX_WMOST_JEER_1, + SFX_WMOST_JEER_2, + SFX_WMOST_JEER_3, + SFX_WMOST_JEER_4, + SFX_WMOST_LOST_1, + SFX_WMOST_LOST_2, + SFX_WMOST_MUGGED_1, + SFX_WMOST_MUGGED_2, + SFX_WMOST_SAVED_1, + SFX_WMOST_TAXI_1, + + SFX_BFOTR_BUMP_1, + SFX_BFOTR_BUMP_2, + SFX_BFOTR_BUMP_3, + SFX_BFOTR_BUMP_4, + SFX_BFOTR_BUMP_5, + SFX_BFOTR_BUMP_6, + SFX_BFOTR_BUMP_7, + SFX_BFOTR_BUMP_8, + SFX_BFOTR_BUMP_9, + SFX_BFOTR_BUMP_10, + SFX_BFOTR_CHAT_1, + SFX_BFOTR_CHAT_2, + SFX_BFOTR_CHAT_3, + SFX_BFOTR_CHAT_4, + SFX_BFOTR_CHAT_5, + SFX_BFOTR_CHAT_6, + SFX_BFOTR_CHAT_7, + SFX_BFOTR_CHAT_8, + SFX_BFOTR_CHAT_9, + SFX_BFOTR_CHAT_10, + SFX_BFOTR_CHAT_11, + SFX_BFOTR_CHAT_12, + SFX_BFOTR_CHAT_13, + SFX_BFOTR_CHAT_14, + SFX_BFOTR_CHAT_15, + SFX_BFOTR_DODGE_1, + SFX_BFOTR_DODGE_2, + SFX_BFOTR_DODGE_3, + SFX_BFOTR_DODGE_4, + SFX_BFOTR_DODGE_5, + SFX_BFOTR_DODGE_6, + SFX_BFOTR_DODGE_7, + SFX_BFOTR_DODGE_8, + SFX_BFOTR_DODGE_9, + SFX_BFOTR_FIGHT_1, + SFX_BFOTR_FIGHT_2, + SFX_BFOTR_FIGHT_3, + SFX_BFOTR_FIGHT_4, + SFX_BFOTR_FIGHT_5, + SFX_BFOTR_FIGHT_6, + SFX_BFOTR_GUN_COOL_1, + SFX_BFOTR_GUN_COOL_2, + SFX_BFOTR_GUN_COOL_3, + SFX_BFOTR_GUN_COOL_4, + SFX_BFOTR_GUN_COOL_5, + SFX_BFOTR_GUN_COOL_6, + SFX_BFOTR_MUGGED_1, + SFX_BFOTR_MUGGED_2, + SFX_BFOTR_MUGGING_1, + SFX_BFOTR_MUGGING_2, + SFX_BFOTR_MUGGING_3, + SFX_BFOTR_SAVED_1, + SFX_BFOTR_SHOCKED_1, + SFX_BFOTR_SHOCKED_2, + SFX_BFOTR_SHOCKED_3, + SFX_BFOTR_SOLICIT_1, + SFX_BFOTR_SOLICIT_2, + SFX_BFOTR_SOLICIT_3, + SFX_BFOTR_SOLICIT_4, + SFX_BFOTR_SOLICIT_5, + SFX_BFOTR_TAXI_1, + + SFX_WFYRI_BLOCKED_1, + SFX_WFYRI_BLOCKED_2, + SFX_WFYRI_BLOCKED_3, + SFX_WFYRI_BLOCKED_4, + SFX_WFYRI_BLOCKED_5, + SFX_WFYRI_BLOCKED_6, + SFX_WFYRI_BLOCKED_7, + SFX_WFYRI_BLOCKED_8, + SFX_WFYRI_BUMP_1, + SFX_WFYRI_BUMP_2, + SFX_WFYRI_BUMP_3, + SFX_WFYRI_BUMP_4, + SFX_WFYRI_BUMP_5, + SFX_WFYRI_BUMP_6, + SFX_WFYRI_BUMP_7, + SFX_WFYRI_BUMP_8, + SFX_WFYRI_BUMP_9, + SFX_WFYRI_BUMP_10, + SFX_WFYRI_CAR_CRASH_1, + SFX_WFYRI_CAR_CRASH_2, + SFX_WFYRI_CAR_CRASH_3, + SFX_WFYRI_CAR_CRASH_4, + SFX_WFYRI_CAR_CRASH_5, + SFX_WFYRI_CAR_CRASH_6, + SFX_WFYRI_CAR_CRASH_7, + SFX_WFYRI_CAR_CRASH_8, + SFX_WFYRI_CAR_CRASH_9, + SFX_WFYRI_DODGE_1, + SFX_WFYRI_DODGE_2, + SFX_WFYRI_DODGE_3, + SFX_WFYRI_DODGE_4, + SFX_WFYRI_DODGE_5, + SFX_WFYRI_DODGE_6, + SFX_WFYRI_DODGE_7, + SFX_WFYRI_DODGE_8, + SFX_WFYRI_DODGE_9, + SFX_WFYRI_EYEING_1, + SFX_WFYRI_EYEING_2, + SFX_WFYRI_GENERIC_CRASH_1, + SFX_WFYRI_GENERIC_CRASH_2, + SFX_WFYRI_GENERIC_CRASH_3, + SFX_WFYRI_GENERIC_CRASH_4, + SFX_WFYRI_GENERIC_CRASH_5, + SFX_WFYRI_GENERIC_CRASH_6, + SFX_WFYRI_GENERIC_CRASH_7, + SFX_WFYRI_GENERIC_CRASH_8, + SFX_WFYRI_GUN_PANIC_1, + SFX_WFYRI_GUN_PANIC_2, + SFX_WFYRI_GUN_PANIC_3, + SFX_WFYRI_GUN_PANIC_4, + SFX_WFYRI_GUN_PANIC_5, + SFX_WFYRI_JACKED_1, + SFX_WFYRI_JACKED_2, + SFX_WFYRI_JACKED_3, + SFX_WFYRI_JACKED_4, + SFX_WFYRI_JACKED_5, + SFX_WFYRI_JACKED_6, + SFX_WFYRI_JACKED_7, + SFX_WFYRI_LOST_1, + SFX_WFYRI_LOST_2, + SFX_WFYRI_MUGGED_1, + SFX_WFYRI_MUGGED_2, + SFX_WFYRI_RUN_1, + SFX_WFYRI_RUN_2, + SFX_WFYRI_RUN_3, + SFX_WFYRI_RUN_4, + SFX_WFYRI_RUN_5, + SFX_WFYRI_SAVED_1, + SFX_WFYRI_SHOCKED_1, + SFX_WFYRI_SHOCKED_2, + SFX_WFYRI_TAXI_1, + SFX_BFYPR_BUMP_1, + SFX_BFYPR_BUMP_2, + SFX_BFYPR_BUMP_3, + SFX_BFYPR_BUMP_4, + SFX_BFYPR_BUMP_5, + SFX_BFYPR_BUMP_6, + SFX_BFYPR_BUMP_7, + SFX_BFYPR_BUMP_8, + SFX_BFYPR_BUMP_9, + SFX_BFYPR_BUMP_10, + SFX_BFYPR_BUMP_11, + SFX_BFYPR_CHAT_1, + SFX_BFYPR_CHAT_2, + SFX_BFYPR_CHAT_3, + SFX_BFYPR_CHAT_4, + SFX_BFYPR_CHAT_5, + SFX_BFYPR_CHAT_6, + SFX_BFYPR_CHAT_7, + SFX_BFYPR_CHAT_8, + SFX_BFYPR_CHAT_9, + SFX_BFYPR_CHAT_10, + SFX_BFYPR_CHAT_11, + SFX_BFYPR_CHAT_12, + SFX_BFYPR_CHAT_13, + SFX_BFYPR_DODGE_1, + SFX_BFYPR_DODGE_2, + SFX_BFYPR_DODGE_3, + SFX_BFYPR_DODGE_4, + SFX_BFYPR_DODGE_5, + SFX_BFYPR_DODGE_6, + SFX_BFYPR_DODGE_7, + SFX_BFYPR_FIGHT_1, + SFX_BFYPR_FIGHT_2, + SFX_BFYPR_FIGHT_3, + SFX_BFYPR_FIGHT_4, + SFX_BFYPR_FIGHT_5, + SFX_BFYPR_FIGHT_6, + SFX_BFYPR_FIGHT_7, + SFX_BFYPR_FUCKING_1, + SFX_BFYPR_FUCKING_2, + SFX_BFYPR_FUCKING_3, + SFX_BFYPR_FUCKING_4, + SFX_BFYPR_FUCKING_5, + SFX_BFYPR_FUCKING_6, + SFX_BFYPR_FUCKING_7, + SFX_BFYPR_GUN_COOL_1, + SFX_BFYPR_GUN_COOL_2, + SFX_BFYPR_GUN_COOL_3, + SFX_BFYPR_GUN_COOL_4, + SFX_BFYPR_GUN_COOL_5, + SFX_BFYPR_MUGGED_1, + SFX_BFYPR_MUGGED_2, + SFX_BFYPR_SAVED_1, + SFX_BFYPR_SHOCKED_1, + SFX_BFYPR_SHOCKED_2, + SFX_BFYPR_SOLICIT_1, + SFX_BFYPR_SOLICIT_2, + SFX_BFYPR_SOLICIT_3, + SFX_BFYPR_SOLICIT_4, + SFX_BFYPR_SOLICIT_5, + SFX_BFYPR_SOLICIT_6, + SFX_BFYPR_SOLICIT_7, + SFX_BFYPR_SOLICIT_8, + SFX_BFYPR_SOLICIT_9, + SFX_BFYPR_SOLICIT_10, + SFX_BFYPR_SOLICIT_11, + SFX_BFYPR_SOLICIT_12, + SFX_BFYPR_SOLICIT_13, + SFX_BFYPR_TAXI_1, + SFX_BFYPR_TAXI_2, + + SFX_BMYRI_BLOCKED_1, + SFX_BMYRI_BLOCKED_2, + SFX_BMYRI_BLOCKED_3, + SFX_BMYRI_BLOCKED_4, + SFX_BMYRI_BLOCKED_5, + SFX_BMYRI_BLOCKED_6, + SFX_BMYRI_BUMP_1, + SFX_BMYRI_BUMP_2, + SFX_BMYRI_BUMP_3, + SFX_BMYRI_BUMP_4, + SFX_BMYRI_BUMP_5, + SFX_BMYRI_BUMP_6, + SFX_BMYRI_BUMP_7, + SFX_BMYRI_CAR_CRASH_1, + SFX_BMYRI_CAR_CRASH_2, + SFX_BMYRI_CAR_CRASH_3, + SFX_BMYRI_CAR_CRASH_4, + SFX_BMYRI_CAR_CRASH_5, + SFX_BMYRI_CAR_CRASH_6, + SFX_BMYRI_CAR_CRASH_7, + SFX_BMYRI_DODGE_1, + SFX_BMYRI_DODGE_2, + SFX_BMYRI_DODGE_3, + SFX_BMYRI_DODGE_4, + SFX_BMYRI_DODGE_5, + SFX_BMYRI_DODGE_6, + SFX_BMYRI_DODGE_7, + SFX_BMYRI_DODGE_8, + SFX_BMYRI_EYEING_1, + SFX_BMYRI_GENERIC_CRASH_1, + SFX_BMYRI_GENERIC_CRASH_2, + SFX_BMYRI_GENERIC_CRASH_3, + SFX_BMYRI_GENERIC_CRASH_4, + SFX_BMYRI_GENERIC_CRASH_5, + SFX_BMYRI_GENERIC_CRASH_6, + SFX_BMYRI_GENERIC_CRASH_7, + SFX_BMYRI_GUN_PANIC_1, + SFX_BMYRI_GUN_PANIC_2, + SFX_BMYRI_GUN_PANIC_3, + SFX_BMYRI_GUN_PANIC_4, + SFX_BMYRI_GUN_PANIC_5, + SFX_BMYRI_GUN_PANIC_6, + SFX_BMYRI_GUN_PANIC_7, + SFX_BMYRI_JACKED_1, + SFX_BMYRI_JACKED_2, + SFX_BMYRI_JACKED_3, + SFX_BMYRI_JACKED_4, + SFX_BMYRI_LOST_1, + SFX_BMYRI_LOST_2, + SFX_BMYRI_MUGGED_1, + SFX_BMYRI_MUGGED_2, + SFX_BMYRI_RUN_1, + SFX_BMYRI_RUN_2, + SFX_BMYRI_RUN_3, + SFX_BMYRI_RUN_4, + SFX_BMYRI_SAVED_1, + SFX_BMYRI_SHOCKED_1, + SFX_BMYRI_SHOCKED_2, + SFX_BMYRI_SHOCKED_3, + SFX_BMYRI_TAXI_1, + SFX_BMYBU_BLOCKED_1, + SFX_BMYBU_BLOCKED_2, + SFX_BMYBU_BLOCKED_3, + SFX_BMYBU_BLOCKED_4, + SFX_BMYBU_BLOCKED_5, + SFX_BMYBU_BLOCKED_6, + SFX_BMYBU_BLOCKED_7, + SFX_BMYBU_BLOCKED_8, + SFX_BMYBU_BUMP_1, + SFX_BMYBU_BUMP_2, + SFX_BMYBU_BUMP_3, + SFX_BMYBU_BUMP_4, + SFX_BMYBU_BUMP_5, + SFX_BMYBU_BUMP_6, + SFX_BMYBU_BUMP_7, + SFX_BMYBU_CAR_CRASH_1, + SFX_BMYBU_CAR_CRASH_2, + SFX_BMYBU_CAR_CRASH_3, + SFX_BMYBU_CAR_CRASH_4, + SFX_BMYBU_CAR_CRASH_5, + SFX_BMYBU_CAR_CRASH_6, + SFX_BMYBU_CAR_CRASH_7, + SFX_BMYBU_DODGE_1, + SFX_BMYBU_DODGE_2, + SFX_BMYBU_DODGE_3, + SFX_BMYBU_DODGE_4, + SFX_BMYBU_DODGE_5, + SFX_BMYBU_DODGE_6, + SFX_BMYBU_DODGE_7, + SFX_BMYBU_DODGE_8, + SFX_BMYBU_DODGE_9, + SFX_BMYBU_DODGE_10, + SFX_BMYBU_EYEING_1, + SFX_BMYBU_EYEING_2, + SFX_BMYBU_FIGHT_1, + SFX_BMYBU_FIGHT_2, + SFX_BMYBU_FIGHT_3, + SFX_BMYBU_FIGHT_4, + SFX_BMYBU_FIGHT_5, + SFX_BMYBU_GENERIC_CRASH_1, + SFX_BMYBU_GENERIC_CRASH_2, + SFX_BMYBU_GENERIC_CRASH_3, + SFX_BMYBU_GENERIC_CRASH_4, + SFX_BMYBU_GENERIC_CRASH_5, + SFX_BMYBU_GENERIC_CRASH_6, + SFX_BMYBU_GENERIC_CRASH_7, + SFX_BMYBU_GUN_PANIC_1, + SFX_BMYBU_GUN_PANIC_2, + SFX_BMYBU_GUN_PANIC_3, + SFX_BMYBU_GUN_PANIC_4, + SFX_BMYBU_GUN_PANIC_5, + SFX_BMYBU_INNOCENT_1, + SFX_BMYBU_INNOCENT_2, + SFX_BMYBU_JACKED_1, + SFX_BMYBU_JACKED_2, + SFX_BMYBU_JACKED_3, + SFX_BMYBU_JACKED_4, + SFX_BMYBU_JACKED_5, + SFX_BMYBU_JACKED_6, + SFX_BMYBU_MUGGED_1, + SFX_BMYBU_MUGGED_2, + SFX_BMYBU_SAVED_1, + SFX_BMYBU_SHOCKED_1, + SFX_BMYBU_SHOCKED_2, + SFX_BMYBU_TAXI_1, + + SFX_WMYSK_BUMP_1, + SFX_WMYSK_BUMP_2, + SFX_WMYSK_BUMP_3, + SFX_WMYSK_BUMP_4, + SFX_WMYSK_BUMP_5, + SFX_WMYSK_BUMP_6, + SFX_WMYSK_BUMP_7, + SFX_WMYSK_BUMP_8, + SFX_WMYSK_BUMP_9, + SFX_WMYSK_BUMP_10, + SFX_WMYSK_BUMP_11, + SFX_WMYSK_BUMP_12, + SFX_WMYSK_BUMP_13, + SFX_WMYSK_BUMP_14, + SFX_WMYSK_CHAT_1, + SFX_WMYSK_CHAT_2, + SFX_WMYSK_CHAT_3, + SFX_WMYSK_CHAT_4, + SFX_WMYSK_CHAT_5, + SFX_WMYSK_CHAT_6, + SFX_WMYSK_CHAT_7, + SFX_WMYSK_CHAT_8, + SFX_WMYSK_CHAT_9, + SFX_WMYSK_CHAT_10, + SFX_WMYSK_CHAT_11, + SFX_WMYSK_CHAT_12, + SFX_WMYSK_CHAT_13, + SFX_WMYSK_DODGE_1, + SFX_WMYSK_DODGE_2, + SFX_WMYSK_DODGE_3, + SFX_WMYSK_DODGE_4, + SFX_WMYSK_DODGE_5, + SFX_WMYSK_DODGE_6, + SFX_WMYSK_DODGE_7, + SFX_WMYSK_DODGE_8, + SFX_WMYSK_DODGE_9, + SFX_WMYSK_DODGE_10, + SFX_WMYSK_EYEING_1, + SFX_WMYSK_EYEING_2, + SFX_WMYSK_FIGHT_1, + SFX_WMYSK_FIGHT_2, + SFX_WMYSK_FIGHT_3, + SFX_WMYSK_FIGHT_4, + SFX_WMYSK_FIGHT_5, + SFX_WMYSK_GUN_PANIC_1, + SFX_WMYSK_GUN_PANIC_2, + SFX_WMYSK_GUN_PANIC_3, + SFX_WMYSK_GUN_PANIC_4, + SFX_WMYSK_GUN_PANIC_5, + SFX_WMYSK_INNOCENT_1, + SFX_WMYSK_INNOCENT_2, + SFX_WMYSK_INNOCENT_3, + SFX_WMYSK_LOST_1, + SFX_WMYSK_LOST_2, + SFX_WMYSK_MUGGED_1, + SFX_WMYSK_MUGGED_2, + SFX_WMYSK_SAVED_1, + SFX_WMYSK_SAVED_2, + SFX_WMYSK_SHOCKED_1, + SFX_WMYSK_SHOCKED_2, + SFX_WMYSK_TAXI_1, + + SFX_WMYCW_BLOCKED_1, + SFX_WMYCW_BLOCKED_2, + SFX_WMYCW_BLOCKED_3, + SFX_WMYCW_BLOCKED_4, + SFX_WMYCW_BLOCKED_5, + SFX_WMYCW_BLOCKED_6, + SFX_WMYCW_BLOCKED_7, + SFX_WMYCW_BLOCKED_8, + SFX_WMYCW_BLOCKED_9, + SFX_WMYCW_BUMP_1, + SFX_WMYCW_BUMP_2, + SFX_WMYCW_BUMP_3, + SFX_WMYCW_BUMP_4, + SFX_WMYCW_BUMP_5, + SFX_WMYCW_BUMP_6, + SFX_WMYCW_BUMP_7, + SFX_WMYCW_BUMP_8, + SFX_WMYCW_BUMP_9, + SFX_WMYCW_CAR_CRASH_1, + SFX_WMYCW_CAR_CRASH_2, + SFX_WMYCW_CAR_CRASH_3, + SFX_WMYCW_CAR_CRASH_4, + SFX_WMYCW_CAR_CRASH_5, + SFX_WMYCW_CAR_CRASH_6, + SFX_WMYCW_CAR_CRASH_7, + SFX_WMYCW_CAR_CRASH_8, + SFX_WMYCW_CAR_CRASH_9, + SFX_WMYCW_CHAT_1, + SFX_WMYCW_CHAT_2, + SFX_WMYCW_CHAT_3, + SFX_WMYCW_CHAT_4, + SFX_WMYCW_CHAT_5, + SFX_WMYCW_CHAT_6, + SFX_WMYCW_CHAT_7, + SFX_WMYCW_CHAT_8, + SFX_WMYCW_CHAT_9, + SFX_WMYCW_CHAT_10, + SFX_WMYCW_CHAT_11, + SFX_WMYCW_CHAT_12, + SFX_WMYCW_CHAT_13, + SFX_WMYCW_CHAT_14, + SFX_WMYCW_CHAT_15, + SFX_WMYCW_DODGE_1, + SFX_WMYCW_DODGE_2, + SFX_WMYCW_DODGE_3, + SFX_WMYCW_DODGE_4, + SFX_WMYCW_DODGE_5, + SFX_WMYCW_DODGE_6, + SFX_WMYCW_DODGE_7, + SFX_WMYCW_DODGE_8, + SFX_WMYCW_DODGE_9, + SFX_WMYCW_DODGE_10, + SFX_WMYCW_EYEING_1, + SFX_WMYCW_EYEING_2, + SFX_WMYCW_EYEING_3, + SFX_WMYCW_FIGHT_1, + SFX_WMYCW_FIGHT_2, + SFX_WMYCW_FIGHT_3, + SFX_WMYCW_FIGHT_4, + SFX_WMYCW_FIGHT_5, + SFX_WMYCW_FIGHT_6, + SFX_WMYCW_FIGHT_7, + SFX_WMYCW_FIGHT_8, + SFX_WMYCW_GENERIC_CRASH_1, + SFX_WMYCW_GENERIC_CRASH_2, + SFX_WMYCW_GENERIC_CRASH_3, + SFX_WMYCW_GENERIC_CRASH_4, + SFX_WMYCW_GENERIC_CRASH_5, + SFX_WMYCW_GENERIC_CRASH_6, + SFX_WMYCW_GENERIC_CRASH_7, + SFX_WMYCW_GUN_PANIC_1, + SFX_WMYCW_GUN_PANIC_2, + SFX_WMYCW_GUN_PANIC_3, + SFX_WMYCW_GUN_PANIC_4, + SFX_WMYCW_GUN_PANIC_5, + SFX_WMYCW_GUN_PANIC_6, + SFX_WMYCW_INNOCENT_1, + SFX_WMYCW_INNOCENT_2, + SFX_WMYCW_INNOCENT_3, + SFX_WMYCW_JACKED_1, + SFX_WMYCW_JACKED_2, + SFX_WMYCW_JACKED_3, + SFX_WMYCW_JACKED_4, + SFX_WMYCW_JACKED_5, + SFX_WMYCW_JACKED_6, + SFX_WMYCW_JEER_1, + SFX_WMYCW_JEER_2, + SFX_WMYCW_JEER_3, + SFX_WMYCW_JEER_4, + SFX_WMYCW_JEER_5, + SFX_WMYCW_JACKING_1, + SFX_WMYCW_JACKING_2, + SFX_WMYCW_JACKING_3, + SFX_WMYCW_JACKING_4, + SFX_WMYCW_LOST_1, + SFX_WMYCW_LOST_2, + SFX_WMYCW_MUGGED_1, + SFX_WMYCW_TAXI_1, + + SFX_HFYST_BLOCKED_1, + SFX_HFYST_BLOCKED_2, + SFX_HFYST_BLOCKED_3, + SFX_HFYST_BLOCKED_4, + SFX_HFYST_BLOCKED_5, + SFX_HFYST_BLOCKED_6, + SFX_HFYST_BLOCKED_7, + SFX_HFYST_BUMP_1, + SFX_HFYST_BUMP_2, + SFX_HFYST_BUMP_3, + SFX_HFYST_BUMP_4, + SFX_HFYST_BUMP_5, + SFX_HFYST_BUMP_6, + SFX_HFYST_BUMP_7, + SFX_HFYST_BUMP_8, + SFX_HFYST_BUMP_9, + SFX_HFYST_BUMP_10, + SFX_HFYST_CAR_CRASH_1, + SFX_HFYST_CAR_CRASH_2, + SFX_HFYST_CAR_CRASH_3, + SFX_HFYST_CAR_CRASH_4, + SFX_HFYST_CAR_CRASH_5, + SFX_HFYST_CAR_CRASH_6, + SFX_HFYST_CAR_CRASH_7, + SFX_HFYST_CAR_CRASH_8, + SFX_HFYST_CHAT_1, + SFX_HFYST_CHAT_2, + SFX_HFYST_CHAT_3, + SFX_HFYST_CHAT_4, + SFX_HFYST_CHAT_5, + SFX_HFYST_CHAT_6, + SFX_HFYST_CHAT_7, + SFX_HFYST_CHAT_8, + SFX_HFYST_CHAT_9, + SFX_HFYST_DODGE_1, + SFX_HFYST_DODGE_2, + SFX_HFYST_DODGE_3, + SFX_HFYST_DODGE_4, + SFX_HFYST_DODGE_5, + SFX_HFYST_DODGE_6, + SFX_HFYST_DODGE_7, + SFX_HFYST_DODGE_8, + SFX_HFYST_DODGE_9, + SFX_HFYST_DODGE_10, + SFX_HFYST_FIGHT_1, + SFX_HFYST_FIGHT_2, + SFX_HFYST_FIGHT_3, + SFX_HFYST_FIGHT_4, + SFX_HFYST_FIGHT_5, + SFX_HFYST_FIGHT_6, + SFX_HFYST_FIGHT_7, + SFX_HFYST_GENERIC_CRASH_1, + SFX_HFYST_GENERIC_CRASH_2, + SFX_HFYST_GENERIC_CRASH_3, + SFX_HFYST_GENERIC_CRASH_4, + SFX_HFYST_GENERIC_CRASH_5, + SFX_HFYST_GENERIC_CRASH_6, + SFX_HFYST_GENERIC_CRASH_7, + SFX_HFYST_GUN_COOL_1, + SFX_HFYST_GUN_COOL_2, + SFX_HFYST_GUN_COOL_3, + SFX_HFYST_GUN_COOL_4, + SFX_HFYST_GUN_COOL_5, + SFX_HFYST_JACKED_1, + SFX_HFYST_JACKED_2, + SFX_HFYST_JACKED_3, + SFX_HFYST_JACKED_4, + SFX_HFYST_JACKED_5, + SFX_HFYST_JACKED_6, + SFX_HFYST_JACKING_1, + SFX_HFYST_JACKING_2, + SFX_HFYST_JACKING_3, + SFX_HFYST_JACKING_4, + SFX_HFYST_LOST_1, + SFX_HFYST_LOST_2, + SFX_HFYST_MUGGED_1, + SFX_HFYST_MUGGED_2, + SFX_HFYST_MUGGING_1, + SFX_HFYST_MUGGING_2, + SFX_HFYST_MUGGING_3, + SFX_HFYST_MUGGING_4, + SFX_HFYST_TAXI_1, + + SFX_HMOST_BLOCKED_1, + SFX_HMOST_BLOCKED_2, + SFX_HMOST_BLOCKED_3, + SFX_HMOST_BLOCKED_4, + SFX_HMOST_BLOCKED_5, + SFX_HMOST_BLOCKED_6, + SFX_HMOST_BLOCKED_7, + SFX_HMOST_BUMP_1, + SFX_HMOST_BUMP_2, + SFX_HMOST_BUMP_3, + SFX_HMOST_BUMP_4, + SFX_HMOST_BUMP_5, + SFX_HMOST_BUMP_6, + SFX_HMOST_BUMP_7, + SFX_HMOST_BUMP_8, + SFX_HMOST_BUMP_9, + SFX_HMOST_BUMP_10, + SFX_HMOST_CAR_CRASH_1, + SFX_HMOST_CAR_CRASH_2, + SFX_HMOST_CAR_CRASH_3, + SFX_HMOST_CAR_CRASH_4, + SFX_HMOST_CAR_CRASH_5, + SFX_HMOST_CAR_CRASH_6, + SFX_HMOST_CAR_CRASH_7, + SFX_HMOST_CHAT_1, + SFX_HMOST_CHAT_2, + SFX_HMOST_CHAT_3, + SFX_HMOST_CHAT_4, + SFX_HMOST_CHAT_5, + SFX_HMOST_CHAT_6, + SFX_HMOST_CHAT_7, + SFX_HMOST_CHAT_8, + SFX_HMOST_CHAT_9, + SFX_HMOST_CHAT_10, + SFX_HMOST_CHAT_11, + SFX_HMOST_DODGE_1, + SFX_HMOST_DODGE_2, + SFX_HMOST_DODGE_3, + SFX_HMOST_DODGE_4, + SFX_HMOST_DODGE_5, + SFX_HMOST_DODGE_6, + SFX_HMOST_DODGE_7, + SFX_HMOST_DODGE_8, + SFX_HMOST_DODGE_9, + SFX_HMOST_EYEING_1, + SFX_HMOST_FIGHT_1, + SFX_HMOST_FIGHT_2, + SFX_HMOST_FIGHT_3, + SFX_HMOST_FIGHT_4, + SFX_HMOST_FIGHT_5, + SFX_HMOST_FIGHT_6, + SFX_HMOST_FIGHT_7, + SFX_HMOST_FIGHT_8, + SFX_HMOST_GENERIC_CRASH_1, + SFX_HMOST_GENERIC_CRASH_2, + SFX_HMOST_GENERIC_CRASH_3, + SFX_HMOST_GENERIC_CRASH_4, + SFX_HMOST_GENERIC_CRASH_5, + SFX_HMOST_GENERIC_CRASH_6, + SFX_HMOST_GENERIC_CRASH_7, + SFX_HMOST_GUN_COOL_1, + SFX_HMOST_GUN_COOL_2, + SFX_HMOST_GUN_COOL_3, + SFX_HMOST_GUN_COOL_4, + SFX_HMOST_GUN_COOL_5, + SFX_HMOST_JACKED_1, + SFX_HMOST_JACKED_2, + SFX_HMOST_JACKED_3, + SFX_HMOST_JACKED_4, + SFX_HMOST_JACKED_5, + SFX_HMOST_JACKED_6, + SFX_HMOST_JACKING_1, + SFX_HMOST_JACKING_2, + SFX_HMOST_JACKING_3, + SFX_HMOST_LOST_1, + SFX_HMOST_LOST_2, + SFX_HMOST_MUGGED_1, + SFX_HMOST_MUGGED_2, + SFX_HMOST_TAXI_1, + + SFX_HMYRI_BLOCKED_1, + SFX_HMYRI_BLOCKED_2, + SFX_HMYRI_BLOCKED_3, + SFX_HMYRI_BLOCKED_4, + SFX_HMYRI_BLOCKED_5, + SFX_HMYRI_BLOCKED_6, + SFX_HMYRI_BLOCKED_7, + SFX_HMYRI_BUMP_1, + SFX_HMYRI_BUMP_2, + SFX_HMYRI_BUMP_3, + SFX_HMYRI_BUMP_4, + SFX_HMYRI_BUMP_5, + SFX_HMYRI_BUMP_6, + SFX_HMYRI_BUMP_7, + SFX_HMYRI_BUMP_8, + SFX_HMYRI_BUMP_9, + SFX_HMYRI_BUMP_10, + SFX_HMYRI_CAR_CRASH_1, + SFX_HMYRI_CAR_CRASH_2, + SFX_HMYRI_CAR_CRASH_3, + SFX_HMYRI_CAR_CRASH_4, + SFX_HMYRI_CAR_CRASH_5, + SFX_HMYRI_CAR_CRASH_6, + SFX_HMYRI_CAR_CRASH_7, + SFX_HMYRI_CAR_CRASH_8, + SFX_HMYRI_DODGE_1, + SFX_HMYRI_DODGE_2, + SFX_HMYRI_DODGE_3, + SFX_HMYRI_DODGE_4, + SFX_HMYRI_DODGE_5, + SFX_HMYRI_DODGE_6, + SFX_HMYRI_DODGE_7, + SFX_HMYRI_DODGE_8, + SFX_HMYRI_DODGE_9, + SFX_HMYRI_FIGHT_1, + SFX_HMYRI_FIGHT_2, + SFX_HMYRI_FIGHT_3, + SFX_HMYRI_FIGHT_4, + SFX_HMYRI_FIGHT_5, + SFX_HMYRI_GENERIC_CRASH_1, + SFX_HMYRI_GENERIC_CRASH_2, + SFX_HMYRI_GENERIC_CRASH_3, + SFX_HMYRI_GENERIC_CRASH_4, + SFX_HMYRI_GENERIC_CRASH_5, + SFX_HMYRI_GENERIC_CRASH_6, + SFX_HMYRI_GENERIC_CRASH_7, + SFX_HMYRI_GENERIC_CRASH_8, + SFX_HMYRI_GENERIC_CRASH_9, + SFX_HMYRI_GENERIC_CRASH_10, + SFX_HMYRI_GENERIC_CRASH_11, + SFX_HMYRI_GENERIC_CRASH_12, + SFX_HMYRI_GUN_PANIC_1, + SFX_HMYRI_GUN_PANIC_2, + SFX_HMYRI_GUN_PANIC_3, + SFX_HMYRI_GUN_PANIC_4, + SFX_HMYRI_GUN_PANIC_5, + SFX_HMYRI_GUN_PANIC_6, + SFX_HMYRI_GUN_PANIC_7, + SFX_HMYRI_JACKED_1, + SFX_HMYRI_JACKED_2, + SFX_HMYRI_JACKED_3, + SFX_HMYRI_JACKED_4, + SFX_HMYRI_JACKED_5, + SFX_HMYRI_JACKED_6, + SFX_HMYRI_JACKED_7, + SFX_HMYRI_JACKED_8, + SFX_HMYRI_JACKING_1, + SFX_HMYRI_JACKING_2, + SFX_HMYRI_JACKING_3, + SFX_HMYRI_MUGGED_1, + SFX_HMYRI_SHOCKED_1, + SFX_HMYRI_SHOCKED_2, + SFX_HMYRI_SHOCKED_3, + + SFX_HFYPR_BUMP_1, + SFX_HFYPR_BUMP_2, + SFX_HFYPR_BUMP_3, + SFX_HFYPR_BUMP_4, + SFX_HFYPR_BUMP_5, + SFX_HFYPR_BUMP_6, + SFX_HFYPR_BUMP_7, + SFX_HFYPR_BUMP_8, + SFX_HFYPR_BUMP_9, + SFX_HFYPR_BUMP_10, + SFX_HFYPR_CHAT_1, + SFX_HFYPR_CHAT_2, + SFX_HFYPR_CHAT_3, + SFX_HFYPR_CHAT_4, + SFX_HFYPR_CHAT_5, + SFX_HFYPR_CHAT_6, + SFX_HFYPR_CHAT_7, + SFX_HFYPR_CHAT_8, + SFX_HFYPR_CHAT_9, + SFX_HFYPR_CHAT_10, + SFX_HFYPR_CHAT_11, + SFX_HFYPR_CHAT_12, + SFX_HFYPR_DODGE_1, + SFX_HFYPR_DODGE_2, + SFX_HFYPR_DODGE_3, + SFX_HFYPR_DODGE_4, + SFX_HFYPR_DODGE_5, + SFX_HFYPR_DODGE_6, + SFX_HFYPR_DODGE_7, + SFX_HFYPR_DODGE_8, + SFX_HFYPR_DODGE_9, + SFX_HFYPR_EYEING_1, + SFX_HFYPR_EYEING_2, + SFX_HFYPR_EYEING_3, + SFX_HFYPR_FIGHT_1, + SFX_HFYPR_FIGHT_2, + SFX_HFYPR_FIGHT_3, + SFX_HFYPR_FIGHT_4, + SFX_HFYPR_FIGHT_5, + SFX_HFYPR_FIGHT_6, + SFX_HFYPR_FIGHT_7, + SFX_HFYPR_FIGHT_8, + SFX_HFYPR_FIGHT_9, + SFX_HFYPR_FIGHT_10, + SFX_HFYPR_FUCKING_1, + SFX_HFYPR_FUCKING_2, + SFX_HFYPR_FUCKING_3, + SFX_HFYPR_FUCKING_4, + SFX_HFYPR_FUCKING_5, + SFX_HFYPR_FUCKING_6, + SFX_HFYPR_FUCKING_7, + SFX_HFYPR_FUCKING_8, + SFX_HFYPR_GUN_COOL_1, + SFX_HFYPR_GUN_COOL_2, + SFX_HFYPR_GUN_COOL_3, + SFX_HFYPR_GUN_COOL_4, + SFX_HFYPR_GUN_COOL_5, + SFX_HFYPR_GUN_COOL_6, + SFX_HFYPR_MUGGED_1, + SFX_HFYPR_MUGGED_2, + SFX_HFYPR_SAVED_1, + SFX_HFYPR_SOLICIT_1, + SFX_HFYPR_SOLICIT_2, + SFX_HFYPR_SOLICIT_3, + SFX_HFYPR_SOLICIT_4, + SFX_HFYPR_SOLICIT_5, + SFX_HFYPR_SOLICIT_6, + SFX_HFYPR_SOLICIT_7, + SFX_HFYPR_SOLICIT_8, + SFX_HFYPR_SOLICIT_9, + SFX_HFYPR_SOLICIT_10, + SFX_HFYPR_SOLICIT_11, + SFX_HFYPR_SOLICIT_12, + SFX_HFYPR_SOLICIT_13, + SFX_HFYPR_SOLICIT_14, + SFX_HFYPR_TAXI_1, + + SFX_HFYMD_BUMP_1, + SFX_HFYMD_BUMP_2, + SFX_HFYMD_BUMP_3, + SFX_HFYMD_BUMP_4, + SFX_HFYMD_BUMP_5, + SFX_HFYMD_BUMP_6, + SFX_HFYMD_BUMP_7, + SFX_HFYMD_BUMP_8, + SFX_HFYMD_BUMP_9, + SFX_HFYMD_DODGE_1, + SFX_HFYMD_DODGE_2, + SFX_HFYMD_DODGE_3, + SFX_HFYMD_DODGE_4, + SFX_HFYMD_DODGE_5, + SFX_HFYMD_DODGE_6, + SFX_HFYMD_DODGE_7, + SFX_HFYMD_DODGE_8, + SFX_HFYMD_FIGHT_1, + SFX_HFYMD_FIGHT_2, + SFX_HFYMD_FIGHT_3, + SFX_HFYMD_FIGHT_4, + SFX_HFYMD_FIGHT_5, + SFX_HFYMD_FIGHT_6, + SFX_HFYMD_FIGHT_7, + SFX_HFYMD_FIGHT_8, + SFX_HFYMD_FIGHT_9, + SFX_HFYMD_GUN_PANIC_1, + SFX_HFYMD_GUN_PANIC_2, + SFX_HFYMD_GUN_PANIC_3, + SFX_HFYMD_GUN_PANIC_4, + SFX_HFYMD_GUN_PANIC_5, + SFX_HFYMD_MUGGED_1, + SFX_HFYMD_MUGGED_2, + SFX_HFYMD_SAVED_1, + SFX_HFYMD_SAVED_2, + SFX_HFYMD_SAVED_3, + SFX_HFYMD_SOLICIT_1, + SFX_HFYMD_SOLICIT_2, + SFX_HFYMD_SOLICIT_3, + SFX_HFYMD_SOLICIT_4, + SFX_HFYMD_SOLICIT_5, + SFX_HFYMD_SOLICIT_6, + SFX_HFYMD_SOLICIT_7, + SFX_HFYMD_SOLICIT_8, + SFX_HFYMD_SOLICIT_9, + SFX_HFYMD_SOLICIT_10, + SFX_HFYMD_SOLICIT_11, + SFX_HFYMD_SOLICIT_12, + SFX_HFYMD_SOLICIT_13, + SFX_HFYMD_SOLICIT_14, + SFX_HFYMD_SOLICIT_15, + SFX_HFYMD_TAXI_1, + + SFX_WFOBE_BLOCKED_1, + SFX_WFOBE_BLOCKED_2, + SFX_WFOBE_BLOCKED_3, + SFX_WFOBE_BLOCKED_4, + SFX_WFOBE_BLOCKED_5, + SFX_WFOBE_BLOCKED_6, + SFX_WFOBE_BLOCKED_7, + SFX_WFOBE_BLOCKED_8, + SFX_WFOBE_BUMP_1, + SFX_WFOBE_BUMP_2, + SFX_WFOBE_BUMP_3, + SFX_WFOBE_BUMP_4, + SFX_WFOBE_BUMP_5, + SFX_WFOBE_BUMP_6, + SFX_WFOBE_BUMP_7, + SFX_WFOBE_BUMP_8, + SFX_WFOBE_BUMP_9, + SFX_WFOBE_BUMP_10, + SFX_WFOBE_CAR_CRASH_1, + SFX_WFOBE_CAR_CRASH_2, + SFX_WFOBE_CAR_CRASH_3, + SFX_WFOBE_CAR_CRASH_4, + SFX_WFOBE_CAR_CRASH_5, + SFX_WFOBE_CAR_CRASH_6, + SFX_WFOBE_CAR_CRASH_7, + SFX_WFOBE_CHAT_1, + SFX_WFOBE_CHAT_2, + SFX_WFOBE_CHAT_3, + SFX_WFOBE_CHAT_4, + SFX_WFOBE_CHAT_5, + SFX_WFOBE_CHAT_6, + SFX_WFOBE_CHAT_7, + SFX_WFOBE_CHAT_8, + SFX_WFOBE_CHAT_9, + SFX_WFOBE_CHAT_10, + SFX_WFOBE_DODGE_1, + SFX_WFOBE_DODGE_2, + SFX_WFOBE_DODGE_3, + SFX_WFOBE_DODGE_4, + SFX_WFOBE_DODGE_5, + SFX_WFOBE_DODGE_6, + SFX_WFOBE_DODGE_7, + SFX_WFOBE_DODGE_8, + SFX_WFOBE_GENERIC_CRASH_1, + SFX_WFOBE_GENERIC_CRASH_2, + SFX_WFOBE_GENERIC_CRASH_3, + SFX_WFOBE_GENERIC_CRASH_4, + SFX_WFOBE_GENERIC_CRASH_5, + SFX_WFOBE_GENERIC_CRASH_6, + SFX_WFOBE_GENERIC_CRASH_7, + SFX_WFOBE_GENERIC_CRASH_8, + SFX_WFOBE_GENERIC_CRASH_9, + SFX_WFOBE_GENERIC_CRASH_10, + SFX_WFOBE_GUN_PANIC_1, + SFX_WFOBE_GUN_PANIC_2, + SFX_WFOBE_GUN_PANIC_3, + SFX_WFOBE_GUN_PANIC_4, + SFX_WFOBE_GUN_PANIC_5, + SFX_WFOBE_JACKED_1, + SFX_WFOBE_JACKED_2, + SFX_WFOBE_JACKED_3, + SFX_WFOBE_JACKED_4, + SFX_WFOBE_RUN_1, + SFX_WFOBE_RUN_2, + SFX_WFOBE_RUN_3, + SFX_WFOBE_RUN_4, + SFX_WFOBE_RUN_5, + SFX_WFOBE_RUN_6, + SFX_WFOBE_RUN_7, + SFX_WFOBE_SAVED_1, + SFX_WFOBE_SAVED_2, + SFX_WFOBE_SAVED_3, + SFX_WFOBE_SHOCKED_1, + SFX_WFOBE_SHOCKED_2, + SFX_WFOBE_SHOCKED_3, + SFX_WFOBE_TAXI_1, + SFX_WFOBE_TAXI_2, + + SFX_BFYRI_BLOCKED_1, + SFX_BFYRI_BLOCKED_2, + SFX_BFYRI_BLOCKED_3, + SFX_BFYRI_BLOCKED_4, + SFX_BFYRI_BLOCKED_5, + SFX_BFYRI_BLOCKED_6, + SFX_BFYRI_BLOCKED_7, + SFX_BFYRI_BLOCKED_8, + SFX_BFYRI_BLOCKED_9, + SFX_BFYRI_BUMP_1, + SFX_BFYRI_BUMP_2, + SFX_BFYRI_BUMP_3, + SFX_BFYRI_BUMP_4, + SFX_BFYRI_BUMP_5, + SFX_BFYRI_BUMP_6, + SFX_BFYRI_BUMP_7, + SFX_BFYRI_BUMP_8, + SFX_BFYRI_BUMP_9, + SFX_BFYRI_CAR_CRASH_1, + SFX_BFYRI_CAR_CRASH_2, + SFX_BFYRI_CAR_CRASH_3, + SFX_BFYRI_CAR_CRASH_4, + SFX_BFYRI_CAR_CRASH_5, + SFX_BFYRI_CAR_CRASH_6, + SFX_BFYRI_CAR_CRASH_7, + SFX_BFYRI_CAR_CRASH_8, + SFX_BFYRI_DODGE_1, + SFX_BFYRI_DODGE_2, + SFX_BFYRI_DODGE_3, + SFX_BFYRI_DODGE_4, + SFX_BFYRI_DODGE_5, + SFX_BFYRI_DODGE_6, + SFX_BFYRI_DODGE_7, + SFX_BFYRI_DODGE_8, + SFX_BFYRI_EYEING_1, + SFX_BFYRI_EYEING_2, + SFX_BFYRI_EYEING_3, + SFX_BFYRI_GENERIC_CRASH_1, + SFX_BFYRI_GENERIC_CRASH_2, + SFX_BFYRI_GENERIC_CRASH_3, + SFX_BFYRI_GENERIC_CRASH_4, + SFX_BFYRI_GENERIC_CRASH_5, + SFX_BFYRI_GENERIC_CRASH_6, + SFX_BFYRI_GENERIC_CRASH_7, + SFX_BFYRI_GUN_PANIC_1, + SFX_BFYRI_GUN_PANIC_2, + SFX_BFYRI_GUN_PANIC_3, + SFX_BFYRI_GUN_PANIC_4, + SFX_BFYRI_JACKED_1, + SFX_BFYRI_JACKED_2, + SFX_BFYRI_JACKED_3, + SFX_BFYRI_JACKED_4, + SFX_BFYRI_JACKED_5, + SFX_BFYRI_JACKED_6, + SFX_BFYRI_JACKED_7, + SFX_BFYRI_JACKED_8, + SFX_BFYRI_JACKING_1, + SFX_BFYRI_JACKING_2, + SFX_BFYRI_JACKING_3, + SFX_BFYRI_JACKING_4, + SFX_BFYRI_LOST_1, + SFX_BFYRI_LOST_2, + SFX_BFYRI_MUGGED_1, + SFX_BFYRI_MUGGED_2, + SFX_BFYRI_MUGGED_3, + SFX_BFYRI_RUN_1, + SFX_BFYRI_RUN_2, + SFX_BFYRI_RUN_3, + SFX_BFYRI_RUN_4, + SFX_BFYRI_RUN_5, + SFX_BFYRI_RUN_6, + SFX_BFYRI_SAVED_1, + SFX_BFYRI_SAVED_2, + SFX_BFYRI_SHOCKED_1, + SFX_BFYRI_SHOCKED_2, + SFX_BFYRI_SHOCKED_3, + SFX_BFYRI_SHOCKED_4, + SFX_BFYRI_TAXI_1, + + SFX_BFYBE_BLOCKED_1, + SFX_BFYBE_BLOCKED_2, + SFX_BFYBE_BLOCKED_3, + SFX_BFYBE_BLOCKED_4, + SFX_BFYBE_BLOCKED_5, + SFX_BFYBE_BLOCKED_6, + SFX_BFYBE_BLOCKED_7, + SFX_BFYBE_BLOCKED_8, + SFX_BFYBE_BLOCKED_9, + SFX_BFYBE_BLOCKED_10, + SFX_BFYBE_BLOCKED_11, + SFX_BFYBE_BLOCKED_12, + SFX_BFYBE_CAR_CRASH_1, + SFX_BFYBE_CAR_CRASH_2, + SFX_BFYBE_CAR_CRASH_3, + SFX_BFYBE_CAR_CRASH_4, + SFX_BFYBE_CAR_CRASH_5, + SFX_BFYBE_CAR_CRASH_6, + SFX_BFYBE_CAR_CRASH_7, + SFX_BFYBE_CAR_CRASH_8, + SFX_BFYBE_CAR_CRASH_9, + SFX_BFYBE_CAR_CRASH_10, + SFX_BFYBE_CHAT_1, + SFX_BFYBE_CHAT_2, + SFX_BFYBE_CHAT_3, + SFX_BFYBE_CHAT_4, + SFX_BFYBE_CHAT_5, + SFX_BFYBE_CHAT_6, + SFX_BFYBE_CHAT_7, + SFX_BFYBE_CHAT_8, + SFX_BFYBE_CHAT_9, + SFX_BFYBE_CHAT_10, + SFX_BFYBE_CHAT_11, + SFX_BFYBE_CHAT_12, + SFX_BFYBE_CHAT_13, + SFX_BFYBE_CHAT_14, + SFX_BFYBE_CHAT_15, + SFX_BFYBE_CHAT_16, + SFX_BFYBE_DODGE_1, + SFX_BFYBE_DODGE_2, + SFX_BFYBE_DODGE_3, + SFX_BFYBE_DODGE_4, + SFX_BFYBE_DODGE_5, + SFX_BFYBE_DODGE_6, + SFX_BFYBE_DODGE_7, + SFX_BFYBE_DODGE_8, + SFX_BFYBE_DODGE_9, + SFX_BFYBE_DODGE_10, + SFX_BFYBE_EYEING_1, + SFX_BFYBE_EYEING_2, + SFX_BFYBE_EYEING_3, + SFX_BFYBE_EYEING_4, + SFX_BFYBE_GENERIC_CRASH_1, + SFX_BFYBE_GENERIC_CRASH_2, + SFX_BFYBE_GENERIC_CRASH_3, + SFX_BFYBE_GENERIC_CRASH_4, + SFX_BFYBE_GENERIC_CRASH_5, + SFX_BFYBE_GENERIC_CRASH_6, + SFX_BFYBE_GENERIC_CRASH_7, + SFX_BFYBE_GENERIC_CRASH_8, + SFX_BFYBE_GUN_COOL_1, + SFX_BFYBE_GUN_COOL_2, + SFX_BFYBE_GUN_COOL_3, + SFX_BFYBE_GUN_COOL_4, + SFX_BFYBE_GUN_COOL_5, + SFX_BFYBE_GUN_COOL_6, + SFX_BFYBE_JACKED_1, + SFX_BFYBE_JACKED_2, + SFX_BFYBE_JACKED_3, + SFX_BFYBE_JACKED_4, + SFX_BFYBE_JACKED_5, + SFX_BFYBE_JACKED_6, + SFX_BFYBE_JACKED_7, + SFX_BFYBE_JACKED_8, + SFX_BFYBE_LOST_1, + SFX_BFYBE_LOST_2, + SFX_BFYBE_LOST_3, + SFX_BFYBE_LOST_4, + SFX_BFYBE_MUGGED_1, + SFX_BFYBE_MUGGED_2, + SFX_BFYBE_MUGGED_3, + SFX_BFYBE_MUGGED_4, + SFX_BFYBE_MUGGED_5, + SFX_BFYBE_RUN_1, + SFX_BFYBE_RUN_2, + SFX_BFYBE_RUN_3, + SFX_BFYBE_RUN_4, + SFX_BFYBE_RUN_5, + SFX_BFYBE_RUN_6, + SFX_BFYBE_SAVED_1, + SFX_BFYBE_SAVED_2, + SFX_BFYBE_SHOCKED_1, + SFX_BFYBE_SHOCKED_2, + SFX_BFYBE_SHOCKED_3, + SFX_BFYBE_SHOCKED_4, + SFX_BFYBE_TAXI_1, + SFX_BFYBE_TAXI_2, + SFX_BFYBE_TAXI_3, + + SFX_BMOTR_BUMP_1, + SFX_BMOTR_BUMP_2, + SFX_BMOTR_BUMP_3, + SFX_BMOTR_BUMP_4, + SFX_BMOTR_BUMP_5, + SFX_BMOTR_BUMP_6, + SFX_BMOTR_BUMP_7, + SFX_BMOTR_BUMP_8, + SFX_BMOTR_BUMP_9, + SFX_BMOTR_BUMP_10, + SFX_BMOTR_CHAT_1, + SFX_BMOTR_CHAT_2, + SFX_BMOTR_CHAT_3, + SFX_BMOTR_CHAT_4, + SFX_BMOTR_CHAT_5, + SFX_BMOTR_CHAT_6, + SFX_BMOTR_CHAT_7, + SFX_BMOTR_CHAT_8, + SFX_BMOTR_CHAT_9, + SFX_BMOTR_CHAT_10, + SFX_BMOTR_DODGE_1, + SFX_BMOTR_DODGE_2, + SFX_BMOTR_DODGE_3, + SFX_BMOTR_DODGE_4, + SFX_BMOTR_DODGE_5, + SFX_BMOTR_DODGE_6, + SFX_BMOTR_DODGE_7, + SFX_BMOTR_DODGE_8, + SFX_BMOTR_DODGE_9, + SFX_BMOTR_DODGE_10, + SFX_BMOTR_DODGE_11, + SFX_BMOTR_EYEING_1, + SFX_BMOTR_EYEING_2, + SFX_BMOTR_EYEING_3, + SFX_BMOTR_GUN_COOL_1, + SFX_BMOTR_GUN_COOL_2, + SFX_BMOTR_GUN_COOL_3, + SFX_BMOTR_GUN_COOL_4, + SFX_BMOTR_GUN_COOL_5, + SFX_BMOTR_INNOCENT_1, + SFX_BMOTR_INNOCENT_2, + SFX_BMOTR_INNOCENT_3, + SFX_BMOTR_INNOCENT_4, + SFX_BMOTR_RUN_1, + SFX_BMOTR_RUN_2, + SFX_BMOTR_RUN_3, + SFX_BMOTR_RUN_4, + SFX_BMOTR_RUN_5, + SFX_BMOTR_RUN_6, + SFX_BMOTR_RUN_7, + SFX_BMOTR_SAVED_1, + SFX_BMOTR_SOLICIT_1, + SFX_BMOTR_SOLICIT_2, + SFX_BMOTR_SOLICIT_3, + SFX_BMOTR_SOLICIT_4, + SFX_BMOTR_SOLICIT_5, + SFX_BMOTR_SOLICIT_6, + SFX_BMOTR_SOLICIT_7, + SFX_BMOTR_TAXI_1, + + SFX_BMYST_BLOCKED_1, + SFX_BMYST_BLOCKED_2, + SFX_BMYST_BLOCKED_3, + SFX_BMYST_BLOCKED_4, + SFX_BMYST_BLOCKED_5, + SFX_BMYST_BLOCKED_6, + SFX_BMYST_BLOCKED_7, + SFX_BMYST_BLOCKED_8, + SFX_BMYST_BUMP_1, + SFX_BMYST_BUMP_2, + SFX_BMYST_BUMP_3, + SFX_BMYST_BUMP_4, + SFX_BMYST_BUMP_5, + SFX_BMYST_BUMP_6, + SFX_BMYST_BUMP_7, + SFX_BMYST_BUMP_8, + SFX_BMYST_BUMP_9, + SFX_BMYST_BUMP_10, + SFX_BMYST_BUMP_11, + SFX_BMYST_CAR_CRASH_1, + SFX_BMYST_CAR_CRASH_2, + SFX_BMYST_CAR_CRASH_3, + SFX_BMYST_CAR_CRASH_4, + SFX_BMYST_CAR_CRASH_5, + SFX_BMYST_CAR_CRASH_6, + SFX_BMYST_CAR_CRASH_7, + SFX_BMYST_CAR_CRASH_8, + SFX_BMYST_CAR_CRASH_9, + SFX_BMYST_CHAT_1, + SFX_BMYST_CHAT_2, + SFX_BMYST_CHAT_3, + SFX_BMYST_CHAT_4, + SFX_BMYST_CHAT_5, + SFX_BMYST_CHAT_6, + SFX_BMYST_CHAT_7, + SFX_BMYST_CHAT_8, + SFX_BMYST_CHAT_9, + SFX_BMYST_CHAT_10, + SFX_BMYST_CHAT_11, + SFX_BMYST_CHAT_12, + SFX_BMYST_DODGE_1, + SFX_BMYST_DODGE_2, + SFX_BMYST_DODGE_3, + SFX_BMYST_DODGE_4, + SFX_BMYST_DODGE_5, + SFX_BMYST_DODGE_6, + SFX_BMYST_DODGE_7, + SFX_BMYST_DODGE_8, + SFX_BMYST_FIGHT_1, + SFX_BMYST_FIGHT_2, + SFX_BMYST_FIGHT_3, + SFX_BMYST_FIGHT_4, + SFX_BMYST_FIGHT_5, + SFX_BMYST_FIGHT_6, + SFX_BMYST_GENERIC_CRASH_1, + SFX_BMYST_GENERIC_CRASH_2, + SFX_BMYST_GENERIC_CRASH_3, + SFX_BMYST_GENERIC_CRASH_4, + SFX_BMYST_GENERIC_CRASH_5, + SFX_BMYST_GENERIC_CRASH_6, + SFX_BMYST_GENERIC_CRASH_7, + SFX_BMYST_GUN_COOL_1, + SFX_BMYST_GUN_COOL_2, + SFX_BMYST_GUN_COOL_3, + SFX_BMYST_GUN_COOL_4, + SFX_BMYST_GUN_COOL_5, + SFX_BMYST_GUN_COOL_6, + SFX_BMYST_JACKED_1, + SFX_BMYST_JACKED_2, + SFX_BMYST_JACKED_3, + SFX_BMYST_JACKED_4, + SFX_BMYST_JACKED_5, + SFX_BMYST_JACKED_6, + SFX_BMYST_JACKED_7, + SFX_BMYST_JACKED_8, + SFX_BMYST_JACKING_1, + SFX_BMYST_JACKING_2, + SFX_BMYST_JACKING_3, + SFX_BMYST_JACKING_4, + SFX_BMYST_MUGGED_1, + SFX_BMYST_MUGGED_2, + SFX_BMYST_MUGGING_1, + SFX_BMYST_MUGGING_2, + SFX_BMYST_MUGGING_3, + SFX_BMYST_MUGGING_4, + SFX_BMYST_TAXI_1, + SFX_BMYST_TAXI_2, + + SFX_WMYPI_BLOCKED_1, + SFX_WMYPI_BLOCKED_2, + SFX_WMYPI_BLOCKED_3, + SFX_WMYPI_BLOCKED_4, + SFX_WMYPI_BLOCKED_5, + SFX_WMYPI_BLOCKED_6, + SFX_WMYPI_BLOCKED_7, + SFX_WMYPI_BLOCKED_8, + SFX_WMYPI_BUMP_1, + SFX_WMYPI_BUMP_2, + SFX_WMYPI_BUMP_3, + SFX_WMYPI_BUMP_4, + SFX_WMYPI_BUMP_5, + SFX_WMYPI_BUMP_6, + SFX_WMYPI_BUMP_7, + SFX_WMYPI_BUMP_8, + SFX_WMYPI_BUMP_9, + SFX_WMYPI_BUMP_10, + SFX_WMYPI_CAR_CRASH_1, + SFX_WMYPI_CAR_CRASH_2, + SFX_WMYPI_CAR_CRASH_3, + SFX_WMYPI_CAR_CRASH_4, + SFX_WMYPI_CAR_CRASH_5, + SFX_WMYPI_CAR_CRASH_6, + SFX_WMYPI_CAR_CRASH_7, + SFX_WMYPI_CAR_CRASH_8, + SFX_WMYPI_DODGE_1, + SFX_WMYPI_DODGE_2, + SFX_WMYPI_DODGE_3, + SFX_WMYPI_DODGE_4, + SFX_WMYPI_DODGE_5, + SFX_WMYPI_DODGE_6, + SFX_WMYPI_DODGE_7, + SFX_WMYPI_DODGE_8, + SFX_WMYPI_EYEING_1, + SFX_WMYPI_EYEING_2, + SFX_WMYPI_EYEING_3, + SFX_WMYPI_EYEING_4, + SFX_WMYPI_EYEING_5, + SFX_WMYPI_EYEING_6, + SFX_WMYPI_FIGHT_1, + SFX_WMYPI_FIGHT_2, + SFX_WMYPI_FIGHT_3, + SFX_WMYPI_FIGHT_4, + SFX_WMYPI_FIGHT_5, + SFX_WMYPI_FIGHT_6, + SFX_WMYPI_FIGHT_7, + SFX_WMYPI_FIGHT_8, + SFX_WMYPI_FIGHT_9, + SFX_WMYPI_GENERIC_CRASH_1, + SFX_WMYPI_GENERIC_CRASH_2, + SFX_WMYPI_GENERIC_CRASH_3, + SFX_WMYPI_GENERIC_CRASH_4, + SFX_WMYPI_GENERIC_CRASH_5, + SFX_WMYPI_GENERIC_CRASH_6, + SFX_WMYPI_GENERIC_CRASH_7, + SFX_WMYPI_GENERIC_CRASH_8, + SFX_WMYPI_GUN_COOL_1, + SFX_WMYPI_GUN_COOL_2, + SFX_WMYPI_GUN_COOL_3, + SFX_WMYPI_GUN_COOL_4, + SFX_WMYPI_GUN_COOL_5, + SFX_WMYPI_INNOCENT_1, + SFX_WMYPI_INNOCENT_2, + SFX_WMYPI_JACKED_1, + SFX_WMYPI_JACKED_2, + SFX_WMYPI_JACKED_3, + SFX_WMYPI_JACKED_4, + SFX_WMYPI_JACKED_5, + SFX_WMYPI_JACKED_6, + SFX_WMYPI_JACKING_1, + SFX_WMYPI_JACKING_2, + SFX_WMYPI_JACKING_3, + SFX_WMYPI_JACKING_4, + SFX_WMYPI_MUGGED_1, + SFX_WMYPI_MUGGED_2, + SFX_WMYPI_SAVED_1, + SFX_WMYPI_SAVED_2, + SFX_WMYPI_TAXI_1, + SFX_WMYPI_TAXI_2, + SFX_WMYPI_TAXI_3, + SFX_WMYPI_TAXI_4, + + SFX_BMYCR_BLOCKED_1, + SFX_BMYCR_BLOCKED_2, + SFX_BMYCR_BLOCKED_3, + SFX_BMYCR_BLOCKED_4, + SFX_BMYCR_BLOCKED_5, + SFX_BMYCR_BLOCKED_6, + SFX_BMYCR_BLOCKED_7, + SFX_BMYCR_BLOCKED_8, + SFX_BMYCR_BLOCKED_9, + SFX_BMYCR_BLOCKED_10, + SFX_BMYCR_BLOCKED_11, + SFX_BMYCR_BLOCKED_12, + SFX_BMYCR_BUMP_1, + SFX_BMYCR_BUMP_2, + SFX_BMYCR_BUMP_3, + SFX_BMYCR_BUMP_4, + SFX_BMYCR_BUMP_5, + SFX_BMYCR_BUMP_6, + SFX_BMYCR_BUMP_7, + SFX_BMYCR_BUMP_8, + SFX_BMYCR_BUMP_9, + SFX_BMYCR_BUMP_10, + SFX_BMYCR_BUMP_11, + SFX_BMYCR_CAR_CRASH_1, + SFX_BMYCR_CAR_CRASH_2, + SFX_BMYCR_CAR_CRASH_3, + SFX_BMYCR_CAR_CRASH_4, + SFX_BMYCR_CAR_CRASH_5, + SFX_BMYCR_CAR_CRASH_6, + SFX_BMYCR_CAR_CRASH_7, + SFX_BMYCR_CAR_CRASH_8, + SFX_BMYCR_CAR_CRASH_9, + SFX_BMYCR_DODGE_1, + SFX_BMYCR_DODGE_2, + SFX_BMYCR_DODGE_3, + SFX_BMYCR_DODGE_4, + SFX_BMYCR_DODGE_5, + SFX_BMYCR_DODGE_6, + SFX_BMYCR_DODGE_7, + SFX_BMYCR_DODGE_8, + SFX_BMYCR_EYEING_1, + SFX_BMYCR_EYEING_2, + SFX_BMYCR_FIGHT_1, + SFX_BMYCR_FIGHT_2, + SFX_BMYCR_FIGHT_3, + SFX_BMYCR_FIGHT_4, + SFX_BMYCR_FIGHT_5, + SFX_BMYCR_FIGHT_6, + SFX_BMYCR_FIGHT_7, + SFX_BMYCR_FIGHT_8, + SFX_BMYCR_GENERIC_CRASH_1, + SFX_BMYCR_GENERIC_CRASH_2, + SFX_BMYCR_GENERIC_CRASH_3, + SFX_BMYCR_GENERIC_CRASH_4, + SFX_BMYCR_GENERIC_CRASH_5, + SFX_BMYCR_GENERIC_CRASH_6, + SFX_BMYCR_GENERIC_CRASH_7, + SFX_BMYCR_GUN_COOL_1, + SFX_BMYCR_GUN_COOL_2, + SFX_BMYCR_GUN_COOL_3, + SFX_BMYCR_GUN_COOL_4, + SFX_BMYCR_GUN_COOL_5, + SFX_BMYCR_GUN_COOL_6, + SFX_BMYCR_INNOCENT_1, + SFX_BMYCR_INNOCENT_2, + SFX_BMYCR_INNOCENT_3, + SFX_BMYCR_INNOCENT_4, + SFX_BMYCR_JACKED_1, + SFX_BMYCR_JACKED_2, + SFX_BMYCR_JACKED_3, + SFX_BMYCR_JACKED_4, + SFX_BMYCR_JACKED_5, + SFX_BMYCR_JACKED_6, + SFX_BMYCR_JACKING_1, + SFX_BMYCR_JACKING_2, + SFX_BMYCR_JACKING_3, + SFX_BMYCR_JACKING_4, + SFX_BMYCR_JACKING_5, + SFX_BMYCR_JACKING_6, + SFX_BMYCR_JACKING_7, + SFX_BMYCR_JACKING_8, + SFX_BMYCR_JACKING_9, + SFX_BMYCR_JACKING_10, + SFX_BMYCR_JACKING_11, + SFX_BMYCR_JACKING_12, + SFX_BMYCR_MUGGED_1, + SFX_BMYCR_MUGGED_2, + SFX_BMYCR_MUGGED_3, + SFX_BMYCR_MUGGING_1, + SFX_BMYCR_MUGGING_2, + SFX_BMYCR_MUGGING_3, + SFX_BMYCR_MUGGING_4, + SFX_BMYCR_MUGGING_5, + SFX_BMYCR_MUGGING_6, + SFX_BMYCR_SAVED_1, + SFX_BMYCR_SAVED_2, + + SFX_WMORI_BLOCKED_1, + SFX_WMORI_BLOCKED_2, + SFX_WMORI_BLOCKED_3, + SFX_WMORI_BLOCKED_4, + SFX_WMORI_BLOCKED_5, + SFX_WMORI_BLOCKED_6, + SFX_WMORI_BLOCKED_7, + SFX_WMORI_BLOCKED_8, + SFX_WMORI_BLOCKED_9, + SFX_WMORI_BLOCKED_10, + SFX_WMORI_BUMP_1, + SFX_WMORI_BUMP_2, + SFX_WMORI_BUMP_3, + SFX_WMORI_BUMP_4, + SFX_WMORI_BUMP_5, + SFX_WMORI_BUMP_6, + SFX_WMORI_BUMP_7, + SFX_WMORI_BUMP_8, + SFX_WMORI_BUMP_9, + SFX_WMORI_BUMP_10, + SFX_WMORI_BUMP_11, + SFX_WMORI_BUMP_12, + SFX_WMORI_BUMP_13, + SFX_WMORI_BUMP_14, + SFX_WMORI_CAR_CRASH_1, + SFX_WMORI_CAR_CRASH_2, + SFX_WMORI_CAR_CRASH_3, + SFX_WMORI_CAR_CRASH_4, + SFX_WMORI_CAR_CRASH_5, + SFX_WMORI_CAR_CRASH_6, + SFX_WMORI_DODGE_1, + SFX_WMORI_DODGE_2, + SFX_WMORI_DODGE_3, + SFX_WMORI_DODGE_4, + SFX_WMORI_DODGE_5, + SFX_WMORI_DODGE_6, + SFX_WMORI_DODGE_7, + SFX_WMORI_DODGE_8, + SFX_WMORI_DODGE_9, + SFX_WMORI_DODGE_10, + SFX_WMORI_EYEING_1, + SFX_WMORI_EYEING_2, + SFX_WMORI_EYEING_3, + SFX_WMORI_GENERIC_CRASH_1, + SFX_WMORI_GENERIC_CRASH_2, + SFX_WMORI_GENERIC_CRASH_3, + SFX_WMORI_GENERIC_CRASH_4, + SFX_WMORI_GENERIC_CRASH_5, + SFX_WMORI_GENERIC_CRASH_6, + SFX_WMORI_GENERIC_CRASH_7, + SFX_WMORI_GENERIC_CRASH_8, + SFX_WMORI_GUN_PANIC_1, + SFX_WMORI_GUN_PANIC_2, + SFX_WMORI_GUN_PANIC_3, + SFX_WMORI_GUN_PANIC_4, + SFX_WMORI_GUN_PANIC_5, + SFX_WMORI_GUN_PANIC_6, + SFX_WMORI_GUN_PANIC_7, + SFX_WMORI_GUN_PANIC_8, + SFX_WMORI_GUN_PANIC_9, + SFX_WMORI_JACKED_1, + SFX_WMORI_JACKED_2, + SFX_WMORI_JACKED_3, + SFX_WMORI_JACKED_4, + SFX_WMORI_JACKED_5, + SFX_WMORI_JACKED_6, + SFX_WMORI_LOST_1, + SFX_WMORI_LOST_2, + SFX_WMORI_MUGGED_1, + SFX_WMORI_MUGGED_2, + SFX_WMORI_MUGGED_3, + SFX_WMORI_MUGGED_4, + SFX_WMORI_RUN_1, + SFX_WMORI_RUN_2, + SFX_WMORI_RUN_3, + SFX_WMORI_RUN_4, + SFX_WMORI_RUN_5, + SFX_WMORI_RUN_6, + SFX_WMORI_RUN_7, + SFX_WMORI_RUN_8, + SFX_WMORI_RUN_9, + SFX_WMORI_RUN_10, + SFX_WMORI_RUN_11, + SFX_WMORI_RUN_12, + SFX_WMORI_SAVED_1, + SFX_WMORI_SAVED_2, + SFX_WMORI_SHOCKED_1, + SFX_WMORI_SHOCKED_2, + SFX_WMORI_SHOCKED_3, + SFX_WMORI_SHOCKED_4, + SFX_WMORI_TAXI_1, + SFX_WMORI_TAXI_2, + + SFX_WMOBU_BLOCKED_1, + SFX_WMOBU_BLOCKED_2, + SFX_WMOBU_BLOCKED_3, + SFX_WMOBU_BLOCKED_4, + SFX_WMOBU_BLOCKED_5, + SFX_WMOBU_BLOCKED_6, + SFX_WMOBU_BLOCKED_7, + SFX_WMOBU_BUMP_1, + SFX_WMOBU_BUMP_2, + SFX_WMOBU_BUMP_3, + SFX_WMOBU_BUMP_4, + SFX_WMOBU_BUMP_5, + SFX_WMOBU_BUMP_6, + SFX_WMOBU_BUMP_7, + SFX_WMOBU_BUMP_8, + SFX_WMOBU_BUMP_9, + SFX_WMOBU_BUMP_10, + SFX_WMOBU_CAR_CRASH_1, + SFX_WMOBU_CAR_CRASH_2, + SFX_WMOBU_CAR_CRASH_3, + SFX_WMOBU_CAR_CRASH_4, + SFX_WMOBU_CAR_CRASH_5, + SFX_WMOBU_CAR_CRASH_6, + SFX_WMOBU_CAR_CRASH_7, + SFX_WMOBU_DODGE_1, + SFX_WMOBU_DODGE_2, + SFX_WMOBU_DODGE_3, + SFX_WMOBU_DODGE_4, + SFX_WMOBU_DODGE_5, + SFX_WMOBU_DODGE_6, + SFX_WMOBU_DODGE_7, + SFX_WMOBU_DODGE_8, + SFX_WMOBU_EYEING_1, + SFX_WMOBU_EYEING_2, + SFX_WMOBU_FIGHT_1, + SFX_WMOBU_FIGHT_2, + SFX_WMOBU_FIGHT_3, + SFX_WMOBU_GENERIC_CRASH_1, + SFX_WMOBU_GENERIC_CRASH_2, + SFX_WMOBU_GENERIC_CRASH_3, + SFX_WMOBU_GENERIC_CRASH_4, + SFX_WMOBU_GENERIC_CRASH_5, + SFX_WMOBU_GENERIC_CRASH_6, + SFX_WMOBU_GENERIC_CRASH_7, + SFX_WMOBU_GUN_PANIC_1, + SFX_WMOBU_GUN_PANIC_2, + SFX_WMOBU_GUN_PANIC_3, + SFX_WMOBU_GUN_PANIC_4, + SFX_WMOBU_GUN_PANIC_5, + SFX_WMOBU_GUN_PANIC_6, + SFX_WMOBU_JACKED_1, + SFX_WMOBU_JACKED_2, + SFX_WMOBU_JACKED_3, + SFX_WMOBU_JACKED_4, + SFX_WMOBU_JACKED_5, + SFX_WMOBU_JACKED_6, + SFX_WMOBU_JACKED_7, + SFX_WMOBU_LOST_1, + SFX_WMOBU_LOST_2, + SFX_WMOBU_LOST_3, + SFX_WMOBU_MUGGED_1, + SFX_WMOBU_MUGGED_2, + SFX_WMOBU_SAVED_1, + SFX_WMOBU_SAVED_2, + SFX_WMOBU_SAVED_3, + SFX_WMOBU_TAXI_1, + SFX_WMOBU_TAXI_2, + + SFX_BMODK_BLOCKED_1, + SFX_BMODK_BLOCKED_2, + SFX_BMODK_BLOCKED_3, + SFX_BMODK_BLOCKED_4, + SFX_BMODK_BLOCKED_5, + SFX_BMODK_BLOCKED_6, + SFX_BMODK_BLOCKED_7, + SFX_BMODK_BLOCKED_8, + SFX_BMODK_BUMP_1, + SFX_BMODK_BUMP_2, + SFX_BMODK_BUMP_3, + SFX_BMODK_BUMP_4, + SFX_BMODK_BUMP_5, + SFX_BMODK_BUMP_6, + SFX_BMODK_BUMP_7, + SFX_BMODK_BUMP_8, + SFX_BMODK_BUMP_9, + SFX_BMODK_BUMP_10, + SFX_BMODK_CAR_CRASH_1, + SFX_BMODK_CAR_CRASH_2, + SFX_BMODK_CAR_CRASH_3, + SFX_BMODK_CAR_CRASH_4, + SFX_BMODK_CAR_CRASH_5, + SFX_BMODK_CAR_CRASH_6, + SFX_BMODK_CAR_CRASH_7, + SFX_BMODK_CAR_CRASH_8, + SFX_BMODK_CAR_CRASH_9, + SFX_BMODK_CAR_CRASH_10, + SFX_BMODK_UNK, // UNUSED + SFX_BMODK_UNK_147_1, + SFX_BMODK_UNK_147_2, + SFX_BMODK_UNK_147_3, + SFX_BMODK_UNK_147_4, + SFX_BMODK_UNK_147_5, + SFX_BMODK_UNK_147_6, + SFX_BMODK_UNK_147_7, + SFX_BMODK_UNK_147_8, + SFX_BMODK_UNK_147_9, + SFX_BMODK_UNK_147_10, + SFX_BMODK_UNK_147_11, + SFX_BMODK_UNK_147_12, + SFX_BMODK_DODGE_1, + SFX_BMODK_DODGE_2, + SFX_BMODK_DODGE_3, + SFX_BMODK_DODGE_4, + SFX_BMODK_DODGE_5, + SFX_BMODK_DODGE_6, + SFX_BMODK_DODGE_7, + SFX_BMODK_GENERIC_CRASH_1, + SFX_BMODK_GENERIC_CRASH_2, + SFX_BMODK_GENERIC_CRASH_3, + SFX_BMODK_GENERIC_CRASH_4, + SFX_BMODK_GENERIC_CRASH_5, + SFX_BMODK_GENERIC_CRASH_6, + SFX_BMODK_GENERIC_CRASH_7, + SFX_BMODK_GUN_PANIC_1, + SFX_BMODK_GUN_PANIC_2, + SFX_BMODK_GUN_PANIC_3, + SFX_BMODK_GUN_PANIC_4, + SFX_BMODK_INNOCENT_1, + SFX_BMODK_INNOCENT_2, + SFX_BMODK_INNOCENT_3, + SFX_BMODK_JACKED_1, + SFX_BMODK_JACKED_2, + SFX_BMODK_JACKED_3, + SFX_BMODK_JACKED_4, + SFX_BMODK_JACKED_5, + SFX_BMODK_JACKED_6, + SFX_BMODK_JACKED_7, + SFX_BMODK_JACKED_8, + SFX_BMODK_JACKED_9, + SFX_BMODK_MUGGED_1, + SFX_BMODK_MUGGED_2, + SFX_BMODK_RUN_1, + SFX_BMODK_RUN_2, + SFX_BMODK_RUN_3, + SFX_BMODK_RUN_4, + SFX_BMODK_TAXI_1, + + SFX_HFYBE_BUMP_1, + SFX_HFYBE_BUMP_2, + SFX_HFYBE_BUMP_3, + SFX_HFYBE_BUMP_4, + SFX_HFYBE_BUMP_5, + SFX_HFYBE_BUMP_6, + SFX_HFYBE_BUMP_7, + SFX_HFYBE_BUMP_8, + SFX_HFYBE_CAR_CRASH_1, + SFX_HFYBE_CAR_CRASH_2, + SFX_HFYBE_CAR_CRASH_3, + SFX_HFYBE_CAR_CRASH_4, + SFX_HFYBE_CAR_CRASH_5, + SFX_HFYBE_CAR_CRASH_6, + SFX_HFYBE_CHAT_1, + SFX_HFYBE_CHAT_2, + SFX_HFYBE_CHAT_3, + SFX_HFYBE_CHAT_4, + SFX_HFYBE_CHAT_5, + SFX_HFYBE_CHAT_6, + SFX_HFYBE_CHAT_7, + SFX_HFYBE_CHAT_8, + SFX_HFYBE_CHAT_9, + SFX_HFYBE_CHAT_10, + SFX_HFYBE_DODGE_1, + SFX_HFYBE_DODGE_2, + SFX_HFYBE_DODGE_3, + SFX_HFYBE_DODGE_4, + SFX_HFYBE_DODGE_5, + SFX_HFYBE_DODGE_6, + SFX_HFYBE_DODGE_7, + SFX_HFYBE_DODGE_8, + SFX_HFYBE_DODGE_9, + SFX_HFYBE_DODGE_10, + SFX_HFYBE_DODGE_11, + SFX_HFYBE_GENERIC_CRASH_1, + SFX_HFYBE_GENERIC_CRASH_2, + SFX_HFYBE_GENERIC_CRASH_3, + SFX_HFYBE_GENERIC_CRASH_4, + SFX_HFYBE_GENERIC_CRASH_5, + SFX_HFYBE_GENERIC_CRASH_6, + SFX_HFYBE_GENERIC_CRASH_7, + SFX_HFYBE_GENERIC_CRASH_8, + SFX_HFYBE_GUN_PANIC_1, + SFX_HFYBE_GUN_PANIC_2, + SFX_HFYBE_GUN_PANIC_3, + SFX_HFYBE_GUN_PANIC_4, + SFX_HFYBE_GUN_PANIC_5, + SFX_HFYBE_GUN_PANIC_6, + SFX_HFYBE_GUN_PANIC_7, + SFX_HFYBE_JACKED_1, + SFX_HFYBE_JACKED_2, + SFX_HFYBE_JACKED_3, + SFX_HFYBE_JACKED_4, + SFX_HFYBE_JACKED_5, + SFX_HFYBE_JACKED_6, + SFX_HFYBE_JACKED_7, + SFX_HFYBE_LOST_1, + SFX_HFYBE_LOST_2, + + // this is a guess, idk what she's saying + SFX_HFYBE_RUN_1, + SFX_HFYBE_RUN_2, + SFX_HFYBE_RUN_3, + SFX_HFYBE_RUN_4, + SFX_HFYBE_RUN_5, + SFX_HFYBE_RUN_6, + SFX_HFYBE_RUN_7, + + SFX_HFYBE_SHOCKED_1, + SFX_HFYBE_SHOCKED_2, + SFX_HFYBE_TAXI_1, + + SFX_HFYRI_BLOCKED_1, + SFX_HFYRI_BLOCKED_2, + SFX_HFYRI_BLOCKED_3, + SFX_HFYRI_BLOCKED_4, + SFX_HFYRI_BLOCKED_5, + SFX_HFYRI_BLOCKED_6, + SFX_HFYRI_BLOCKED_7, + SFX_HFYRI_BLOCKED_8, + SFX_HFYRI_BUMP_1, + SFX_HFYRI_BUMP_2, + SFX_HFYRI_BUMP_3, + SFX_HFYRI_BUMP_4, + SFX_HFYRI_BUMP_5, + SFX_HFYRI_BUMP_6, + SFX_HFYRI_BUMP_7, + SFX_HFYRI_BUMP_8, + SFX_HFYRI_BUMP_9, + SFX_HFYRI_CAR_CRASH_1, + SFX_HFYRI_CAR_CRASH_2, + SFX_HFYRI_CAR_CRASH_3, + SFX_HFYRI_CAR_CRASH_4, + SFX_HFYRI_CAR_CRASH_5, + SFX_HFYRI_CAR_CRASH_6, + SFX_HFYRI_CAR_CRASH_7, + SFX_HFYRI_CAR_CRASH_8, + SFX_HFYRI_DODGE_1, + SFX_HFYRI_DODGE_2, + SFX_HFYRI_DODGE_3, + SFX_HFYRI_DODGE_4, + SFX_HFYRI_DODGE_5, + SFX_HFYRI_DODGE_6, + SFX_HFYRI_DODGE_7, + SFX_HFYRI_DODGE_8, + SFX_HFYRI_DODGE_9, + SFX_HFYRI_DODGE_10, + SFX_HFYRI_GENERIC_CRASH_1, + SFX_HFYRI_GENERIC_CRASH_2, + SFX_HFYRI_GENERIC_CRASH_3, + SFX_HFYRI_GENERIC_CRASH_4, + SFX_HFYRI_GENERIC_CRASH_5, + SFX_HFYRI_GENERIC_CRASH_6, + SFX_HFYRI_GENERIC_CRASH_7, + SFX_HFYRI_GUN_PANIC_1, + SFX_HFYRI_GUN_PANIC_2, + SFX_HFYRI_GUN_PANIC_3, + SFX_HFYRI_GUN_PANIC_4, + SFX_HFYRI_GUN_PANIC_5, + SFX_HFYRI_JACKED_1, + SFX_HFYRI_JACKED_2, + SFX_HFYRI_JACKED_3, + SFX_HFYRI_JACKED_4, + SFX_HFYRI_JACKED_5, + SFX_HFYRI_JACKED_6, + SFX_HFYRI_LOST_1, + SFX_HFYRI_LOST_2, + SFX_HFYRI_MUGGED_1, + SFX_HFYRI_MUGGED_2, + SFX_HFYRI_MUGGED_3, + SFX_HFYRI_MUGGED_4, + SFX_HFYRI_RUN_1, + SFX_HFYRI_RUN_2, + SFX_HFYRI_RUN_3, + SFX_HFYRI_RUN_4, + SFX_HFYRI_SAVED_1, + SFX_HFYRI_SAVED_2, + SFX_HFYRI_SHOCKED_1, + SFX_HFYRI_SHOCKED_2, + SFX_HFYRI_SHOCKED_3, + SFX_HFYRI_TAXI_1, + SFX_BFOST_BLOCKED_1, + SFX_BFOST_BLOCKED_2, + SFX_BFOST_BLOCKED_3, + SFX_BFOST_BLOCKED_4, + SFX_BFOST_BLOCKED_5, + SFX_BFOST_BLOCKED_6, + SFX_BFOST_BLOCKED_7, + SFX_BFOST_BUMP_1, + SFX_BFOST_BUMP_2, + SFX_BFOST_BUMP_3, + SFX_BFOST_BUMP_4, + SFX_BFOST_BUMP_5, + SFX_BFOST_BUMP_6, + SFX_BFOST_BUMP_7, + SFX_BFOST_BUMP_8, + SFX_BFOST_BUMP_9, + SFX_BFOST_BUMP_10, + SFX_BFOST_CAR_CRASH_1, + SFX_BFOST_CAR_CRASH_2, + SFX_BFOST_CAR_CRASH_3, + SFX_BFOST_CAR_CRASH_4, + SFX_BFOST_CAR_CRASH_5, + SFX_BFOST_CAR_CRASH_6, + SFX_BFOST_CAR_CRASH_7, + SFX_BFOST_CAR_CRASH_8, + SFX_BFOST_CHAT_1, + SFX_BFOST_CHAT_2, + SFX_BFOST_CHAT_3, + SFX_BFOST_CHAT_4, + SFX_BFOST_CHAT_5, + SFX_BFOST_CHAT_6, + SFX_BFOST_CHAT_7, + SFX_BFOST_CHAT_8, + SFX_BFOST_CHAT_9, + SFX_BFOST_CHAT_10, + SFX_BFOST_DODGE_1, + SFX_BFOST_DODGE_2, + SFX_BFOST_DODGE_3, + SFX_BFOST_DODGE_4, + SFX_BFOST_DODGE_5, + SFX_BFOST_DODGE_6, + SFX_BFOST_DODGE_7, + SFX_BFOST_DODGE_8, + SFX_BFOST_DODGE_9, + SFX_BFOST_DODGE_10, + SFX_BFOST_DODGE_11, + SFX_BFOST_GENERIC_CRASH_1, + SFX_BFOST_GENERIC_CRASH_2, + SFX_BFOST_GENERIC_CRASH_3, + SFX_BFOST_GENERIC_CRASH_4, + SFX_BFOST_GENERIC_CRASH_5, + SFX_BFOST_GENERIC_CRASH_6, + SFX_BFOST_GENERIC_CRASH_7, + SFX_BFOST_GENERIC_CRASH_8, + SFX_BFOST_GUN_PANIC_1, + SFX_BFOST_GUN_PANIC_2, + SFX_BFOST_GUN_PANIC_3, + SFX_BFOST_GUN_PANIC_4, + SFX_BFOST_GUN_PANIC_5, + SFX_BFOST_JACKED_1, + SFX_BFOST_JACKED_2, + SFX_BFOST_JACKED_3, + SFX_BFOST_JACKED_4, + SFX_BFOST_JACKED_5, + SFX_BFOST_JACKED_6, + SFX_BFOST_JACKED_7, + SFX_BFOST_JACKED_8, + SFX_BFOST_LOST_1, + SFX_BFOST_LOST_2, + SFX_BFOST_MUGGED_1, + SFX_BFOST_MUGGED_2, + SFX_BFOST_RUN_1, + SFX_BFOST_RUN_2, + SFX_BFOST_RUN_3, + SFX_BFOST_RUN_4, + SFX_BFOST_SAVED_1, + SFX_BFOST_SAVED_2, + SFX_BFOST_TAXI_1, + SFX_BFORI_BLOCKED_1, + SFX_BFORI_BLOCKED_2, + SFX_BFORI_BLOCKED_3, + SFX_BFORI_BLOCKED_4, + SFX_BFORI_BLOCKED_5, + SFX_BFORI_BLOCKED_6, + SFX_BFORI_BLOCKED_7, + SFX_BFORI_BLOCKED_8, + SFX_BFORI_BUMP_1, + SFX_BFORI_BUMP_2, + SFX_BFORI_BUMP_3, + SFX_BFORI_BUMP_4, + SFX_BFORI_BUMP_5, + SFX_BFORI_BUMP_6, + SFX_BFORI_BUMP_7, + SFX_BFORI_BUMP_8, + SFX_BFORI_BUMP_9, + SFX_BFORI_CAR_CRASH_1, + SFX_BFORI_CAR_CRASH_2, + SFX_BFORI_CAR_CRASH_3, + SFX_BFORI_CAR_CRASH_4, + SFX_BFORI_CAR_CRASH_5, + SFX_BFORI_CAR_CRASH_6, + SFX_BFORI_CAR_CRASH_7, + SFX_BFORI_DODGE_1, + SFX_BFORI_DODGE_2, + SFX_BFORI_DODGE_3, + SFX_BFORI_DODGE_4, + SFX_BFORI_DODGE_5, + SFX_BFORI_DODGE_6, + SFX_BFORI_DODGE_7, + SFX_BFORI_DODGE_8, + SFX_BFORI_DODGE_9, + SFX_BFORI_GENERIC_CRASH_1, + SFX_BFORI_GENERIC_CRASH_2, + SFX_BFORI_GENERIC_CRASH_3, + SFX_BFORI_GENERIC_CRASH_4, + SFX_BFORI_GENERIC_CRASH_5, + SFX_BFORI_GENERIC_CRASH_6, + SFX_BFORI_GENERIC_CRASH_7, + SFX_BFORI_GUN_PANIC_1, + SFX_BFORI_GUN_PANIC_2, + SFX_BFORI_GUN_PANIC_3, + SFX_BFORI_GUN_PANIC_4, + SFX_BFORI_GUN_PANIC_5, + SFX_BFORI_JACKED_1, + SFX_BFORI_JACKED_2, + SFX_BFORI_JACKED_3, + SFX_BFORI_JACKED_4, + SFX_BFORI_LOST_1, + SFX_BFORI_LOST_2, + SFX_BFORI_MUGGED_1, + SFX_BFORI_MUGGED_2, + SFX_BFORI_RUN_1, + SFX_BFORI_RUN_2, + SFX_BFORI_RUN_3, + SFX_BFORI_RUN_4, + SFX_BFORI_SAVED_1, + SFX_BFORI_SHOCKED_1, + SFX_BFORI_SHOCKED_2, + SFX_BFORI_TAXI_1, + SFX_BFORI_TAXI_2, + + SFX_BFYST_BLOCKED_1, + SFX_BFYST_BLOCKED_2, + SFX_BFYST_BLOCKED_3, + SFX_BFYST_BLOCKED_4, + SFX_BFYST_BLOCKED_5, + SFX_BFYST_BLOCKED_6, + SFX_BFYST_BLOCKED_7, + SFX_BFYST_BLOCKED_8, + SFX_BFYST_BUMP_1, + SFX_BFYST_BUMP_2, + SFX_BFYST_BUMP_3, + SFX_BFYST_BUMP_4, + SFX_BFYST_BUMP_5, + SFX_BFYST_BUMP_6, + SFX_BFYST_BUMP_7, + SFX_BFYST_BUMP_8, + SFX_BFYST_BUMP_9, + SFX_BFYST_CAR_CRASH_1, + SFX_BFYST_CAR_CRASH_2, + SFX_BFYST_CAR_CRASH_3, + SFX_BFYST_CAR_CRASH_4, + SFX_BFYST_CAR_CRASH_5, + SFX_BFYST_CAR_CRASH_6, + SFX_BFYST_CAR_CRASH_7, + SFX_BFYST_CAR_CRASH_8, + SFX_BFYST_CAR_CRASH_9, + SFX_BFYST_CHAT_1, + SFX_BFYST_CHAT_2, + SFX_BFYST_CHAT_3, + SFX_BFYST_CHAT_4, + SFX_BFYST_CHAT_5, + SFX_BFYST_CHAT_6, + SFX_BFYST_CHAT_7, + SFX_BFYST_CHAT_8, + SFX_BFYST_CHAT_9, + SFX_BFYST_DODGE_1, + SFX_BFYST_DODGE_2, + SFX_BFYST_DODGE_3, + SFX_BFYST_DODGE_4, + SFX_BFYST_DODGE_5, + SFX_BFYST_DODGE_6, + SFX_BFYST_DODGE_7, + SFX_BFYST_DODGE_8, + SFX_BFYST_DODGE_9, + SFX_BFYST_GENERIC_CRASH_1, + SFX_BFYST_GENERIC_CRASH_2, + SFX_BFYST_GENERIC_CRASH_3, + SFX_BFYST_GENERIC_CRASH_4, + SFX_BFYST_GENERIC_CRASH_5, + SFX_BFYST_GENERIC_CRASH_6, + SFX_BFYST_GENERIC_CRASH_7, + SFX_BFYST_GENERIC_CRASH_8, + SFX_BFYST_GUN_PANIC_1, + SFX_BFYST_GUN_PANIC_2, + SFX_BFYST_GUN_PANIC_3, + SFX_BFYST_GUN_PANIC_4, + SFX_BFYST_JACKED_1, + SFX_BFYST_JACKED_2, + SFX_BFYST_JACKED_3, + SFX_BFYST_JACKED_4, + SFX_BFYST_JACKED_5, + SFX_BFYST_LOST_1, + SFX_BFYST_LOST_2, + SFX_BFYST_MUGGED_1, + SFX_BFYST_MUGGED_2, + SFX_BFYST_RUN_1, + SFX_BFYST_RUN_2, + SFX_BFYST_RUN_3, + SFX_BFYST_RUN_4, + SFX_BFYST_RUN_5, + SFX_BFYST_RUN_6, + SFX_BFYST_SAVED_1, + SFX_BFYST_SAVED_2, + SFX_BFYST_TAXI_1, + + SFX_HFORI_BLOCKED_1, + SFX_HFORI_BLOCKED_2, + SFX_HFORI_BLOCKED_3, + SFX_HFORI_BLOCKED_4, + SFX_HFORI_BLOCKED_5, + SFX_HFORI_BLOCKED_6, + SFX_HFORI_BUMP_1, + SFX_HFORI_BUMP_2, + SFX_HFORI_BUMP_3, + SFX_HFORI_BUMP_4, + SFX_HFORI_BUMP_5, + SFX_HFORI_BUMP_6, + SFX_HFORI_BUMP_7, + SFX_HFORI_BUMP_8, + SFX_HFORI_BUMP_9, + SFX_HFORI_BUMP_10, + SFX_HFORI_CAR_CRASH_1, + SFX_HFORI_CAR_CRASH_2, + SFX_HFORI_CAR_CRASH_3, + SFX_HFORI_CAR_CRASH_4, + SFX_HFORI_CAR_CRASH_5, + SFX_HFORI_CAR_CRASH_6, + SFX_HFORI_CAR_CRASH_7, + SFX_HFORI_DODGE_1, + SFX_HFORI_DODGE_2, + SFX_HFORI_DODGE_3, + SFX_HFORI_DODGE_4, + SFX_HFORI_DODGE_5, + SFX_HFORI_DODGE_6, + SFX_HFORI_EYEING_1, + SFX_HFORI_EYEING_2, + SFX_HFORI_GENERIC_CRASH_1, + SFX_HFORI_GENERIC_CRASH_2, + SFX_HFORI_GENERIC_CRASH_3, + SFX_HFORI_GENERIC_CRASH_4, + SFX_HFORI_GENERIC_CRASH_5, + SFX_HFORI_GENERIC_CRASH_6, + SFX_HFORI_GENERIC_CRASH_7, + SFX_HFORI_GUN_PANIC_1, + SFX_HFORI_GUN_PANIC_2, + SFX_HFORI_GUN_PANIC_3, + SFX_HFORI_GUN_PANIC_4, + SFX_HFORI_GUN_PANIC_5, + SFX_HFORI_GUN_PANIC_6, + SFX_HFORI_JACKED_1, + SFX_HFORI_JACKED_2, + SFX_HFORI_JACKED_3, + SFX_HFORI_JACKED_4, + SFX_HFORI_JACKED_5, + SFX_HFORI_JACKED_6, + SFX_HFORI_JACKED_7, + SFX_HFORI_JACKED_8, + SFX_HFORI_JACKED_9, + SFX_HFORI_LOST_1, + SFX_HFORI_LOST_2, + SFX_HFORI_MUGGED_1, + SFX_HFORI_MUGGED_2, + SFX_HFORI_RUN_1, + SFX_HFORI_RUN_2, + SFX_HFORI_RUN_3, + SFX_HFORI_RUN_4, + SFX_HFORI_SAVED_1, + SFX_HFORI_SHOCKED_1, + SFX_HFORI_SHOCKED_2, + SFX_HFORI_TAXI_1, + + SFX_WFYBU_BUMP_1, + SFX_WFYBU_BUMP_2, + SFX_WFYBU_BUMP_3, + SFX_WFYBU_BUMP_4, + SFX_WFYBU_BUMP_5, + SFX_WFYBU_BUMP_6, + SFX_WFYBU_BUMP_7, + SFX_WFYBU_BUMP_8, + SFX_WFYBU_BUMP_9, + SFX_WFYBU_BUMP_10, + SFX_WFYBU_BUMP_11, + SFX_WFYBU_BUMP_12, + SFX_WFYBU_BUMP_13, + SFX_WFYBU_BUMP_14, + SFX_WFYBU_BUMP_15, + SFX_WFYBU_BUMP_16, + SFX_WFYBU_BUMP_17, + SFX_WFYBU_BUMP_18, + SFX_WFYBU_BUMP_19, + SFX_WFYBU_BUMP_20, + SFX_WFYBU_BUMP_21, + SFX_WFYBU_CAR_CRASH_1, + SFX_WFYBU_CAR_CRASH_2, + SFX_WFYBU_CAR_CRASH_3, + SFX_WFYBU_CAR_CRASH_4, + SFX_WFYBU_CAR_CRASH_5, + SFX_WFYBU_CAR_CRASH_6, + SFX_WFYBU_CAR_CRASH_7, + SFX_WFYBU_CAR_CRASH_8, + SFX_WFYBU_CAR_CRASH_9, + SFX_WFYBU_GENERIC_CRASH_1, + SFX_WFYBU_GENERIC_CRASH_2, + SFX_WFYBU_GENERIC_CRASH_3, + SFX_WFYBU_GENERIC_CRASH_4, + SFX_WFYBU_GENERIC_CRASH_5, + SFX_WFYBU_GENERIC_CRASH_6, + SFX_WFYBU_GENERIC_CRASH_7, + SFX_WFYBU_GENERIC_CRASH_8, + SFX_WFYBU_GUN_PANIC_1, + SFX_WFYBU_GUN_PANIC_2, + SFX_WFYBU_GUN_PANIC_3, + SFX_WFYBU_GUN_PANIC_4, + SFX_WFYBU_GUN_PANIC_5, + SFX_WFYBU_GUN_PANIC_6, + SFX_WFYBU_GUN_PANIC_7, + SFX_WFYBU_GUN_PANIC_8, + SFX_WFYBU_JACKED_1, + SFX_WFYBU_JACKED_2, + SFX_WFYBU_JACKED_3, + SFX_WFYBU_JACKED_4, + SFX_WFYBU_JACKED_5, + SFX_WFYBU_JACKED_6, + SFX_WFYBU_JACKED_7, + SFX_WFYBU_JACKED_8, + SFX_WFYBU_MUGGED_1, + SFX_WFYBU_MUGGED_2, + SFX_WFYBU_MUGGED_3, + SFX_WFYBU_MUGGED_4, + SFX_WFYBU_RUN_1, + SFX_WFYBU_RUN_2, + SFX_WFYBU_RUN_3, + SFX_WFYBU_RUN_4, + SFX_WFYBU_RUN_5, + SFX_WFYBU_RUN_6, + SFX_WFYBU_RUN_7, + SFX_WFYBU_RUN_8, + SFX_WFYBU_SHOCKED_1, + SFX_WFYBU_SHOCKED_2, + SFX_WFYBU_SHOCKED_3, + SFX_WFYBU_TAXI_1, + SFX_WFYBU_TAXI_2, + + SFX_WFOTR_BUMP_1, + SFX_WFOTR_BUMP_2, + SFX_WFOTR_BUMP_3, + SFX_WFOTR_BUMP_4, + SFX_WFOTR_BUMP_5, + SFX_WFOTR_BUMP_6, + SFX_WFOTR_BUMP_7, + SFX_WFOTR_BUMP_8, + SFX_WFOTR_BUMP_9, + SFX_WFOTR_BUMP_10, + SFX_WFOTR_BUMP_11, + SFX_WFOTR_CHAT_1, + SFX_WFOTR_CHAT_2, + SFX_WFOTR_CHAT_3, + SFX_WFOTR_CHAT_4, + SFX_WFOTR_CHAT_5, + SFX_WFOTR_CHAT_6, + SFX_WFOTR_CHAT_7, + SFX_WFOTR_CHAT_8, + SFX_WFOTR_CHAT_9, + SFX_WFOTR_DODGE_1, + SFX_WFOTR_DODGE_2, + SFX_WFOTR_DODGE_3, + SFX_WFOTR_DODGE_4, + SFX_WFOTR_DODGE_5, + SFX_WFOTR_DODGE_6, + SFX_WFOTR_DODGE_7, + SFX_WFOTR_DODGE_8, + SFX_WFOTR_DODGE_9, + SFX_WFOTR_GUN_COOL_1, + SFX_WFOTR_GUN_COOL_2, + SFX_WFOTR_GUN_COOL_3, + SFX_WFOTR_GUN_COOL_4, + SFX_WFOTR_GUN_COOL_5, + SFX_WFOTR_GUN_COOL_6, + SFX_WFOTR_RUN_1, + SFX_WFOTR_RUN_2, + SFX_WFOTR_RUN_3, + SFX_WFOTR_RUN_4, + SFX_WFOTR_RUN_5, + SFX_WFOTR_RUN_6, + SFX_WFOTR_SAVED_1, + SFX_WFOTR_SOLICIT_1, + SFX_WFOTR_SOLICIT_2, + SFX_WFOTR_SOLICIT_3, + SFX_WFOTR_SOLICIT_4, + SFX_WFOTR_SOLICIT_5, + SFX_WFOTR_SOLICIT_6, + SFX_WFOTR_SOLICIT_7, + SFX_WFOTR_SOLICIT_8, + SFX_WFOTR_SOLICIT_9, + SFX_WFOTR_TAXI_1, + + SFX_WFYJG_BUMP_1, + SFX_WFYJG_BUMP_2, + SFX_WFYJG_BUMP_3, + SFX_WFYJG_BUMP_4, + SFX_WFYJG_BUMP_5, + SFX_WFYJG_BUMP_6, + SFX_WFYJG_BUMP_7, + SFX_WFYJG_BUMP_8, + SFX_WFYJG_BUMP_9, + SFX_WFYJG_BUMP_10, + SFX_WFYJG_BUMP_11, + SFX_WFYJG_BUMP_12, + SFX_WFYJG_DODGE_1, + SFX_WFYJG_DODGE_2, + SFX_WFYJG_DODGE_3, + SFX_WFYJG_DODGE_4, + SFX_WFYJG_DODGE_5, + SFX_WFYJG_DODGE_6, + SFX_WFYJG_DODGE_7, + SFX_WFYJG_DODGE_8, + SFX_WFYJG_GUN_PANIC_1, + SFX_WFYJG_GUN_PANIC_2, + SFX_WFYJG_GUN_PANIC_3, + SFX_WFYJG_GUN_PANIC_4, + SFX_WFYJG_RUN_1, + SFX_WFYJG_RUN_2, + SFX_WFYJG_RUN_3, + SFX_WFYJG_RUN_4, + SFX_WFYJG_RUN_5, + SFX_WFYJG_RUN_6, + SFX_WFYJG_SAVED_1, + SFX_WFYJG_TAXI_1, + + SFX_WFYSH_BUMP_1, + SFX_WFYSH_BUMP_2, + SFX_WFYSH_BUMP_3, + SFX_WFYSH_BUMP_4, + SFX_WFYSH_BUMP_5, + SFX_WFYSH_BUMP_6, + SFX_WFYSH_BUMP_7, + SFX_WFYSH_BUMP_8, + SFX_WFYSH_BUMP_9, + SFX_WFYSH_BUMP_10, + SFX_WFYSH_BUMP_11, + SFX_WFYSH_BUMP_12, + SFX_WFYSH_CHAT_1, + SFX_WFYSH_CHAT_2, + SFX_WFYSH_CHAT_3, + SFX_WFYSH_CHAT_4, + SFX_WFYSH_CHAT_5, + SFX_WFYSH_CHAT_6, + SFX_WFYSH_CHAT_7, + SFX_WFYSH_CHAT_8, + SFX_WFYSH_CHAT_9, + SFX_WFYSH_CHAT_10, + SFX_WFYSH_DODGE_1, + SFX_WFYSH_DODGE_2, + SFX_WFYSH_DODGE_3, + SFX_WFYSH_DODGE_4, + SFX_WFYSH_DODGE_5, + SFX_WFYSH_DODGE_6, + SFX_WFYSH_DODGE_7, + SFX_WFYSH_DODGE_8, + SFX_WFYSH_DODGE_9, + SFX_WFYSH_DODGE_10, + SFX_WFYSH_DODGE_11, + SFX_WFYSH_GUN_COOL_1, + SFX_WFYSH_GUN_COOL_2, + SFX_WFYSH_GUN_COOL_3, + SFX_WFYSH_GUN_COOL_4, + SFX_WFYSH_GUN_COOL_5, + SFX_WFYSH_GUN_COOL_6, + SFX_WFYSH_GUN_COOL_7, + SFX_WFYSH_GUN_COOL_8, + SFX_WFYSH_GUN_COOL_9, + SFX_WFYSH_LOST_1, + SFX_WFYSH_LOST_2, + SFX_WFYSH_MUGGED_1, + SFX_WFYSH_MUGGED_2, + SFX_WFYSH_RUN_1, + SFX_WFYSH_RUN_2, + SFX_WFYSH_RUN_3, + SFX_WFYSH_RUN_4, + SFX_WFYSH_RUN_5, + SFX_WFYSH_RUN_6, + SFX_WFYSH_RUN_7, + SFX_WFYSH_RUN_8, + SFX_WFYSH_RUN_9, + SFX_WFYSH_RUN_10, + SFX_WFYSH_RUN_11, + SFX_WFYSH_SAVED_1, + SFX_WFYSH_SAVED_2, + SFX_WFYSH_SAVED_3, + SFX_WFYSH_SAVED_4, + SFX_WFYSH_SHOCKED_1, + SFX_WFYSH_SHOCKED_2, + SFX_WFYSH_SHOCKED_3, + SFX_WFYSH_SHOCKED_4, + SFX_WFYSH_SHOCKED_5, + SFX_WFYSH_TAXI_1, + SFX_WFYSH_TAXI_2, + + SFX_WMOTR_BUMP_1, + SFX_WMOTR_BUMP_2, + SFX_WMOTR_BUMP_3, + SFX_WMOTR_BUMP_4, + SFX_WMOTR_BUMP_5, + SFX_WMOTR_BUMP_6, + SFX_WMOTR_BUMP_7, + SFX_WMOTR_BUMP_8, + SFX_WMOTR_BUMP_9, + SFX_WMOTR_BUMP_10, + SFX_WMOTR_CHAT_1, + SFX_WMOTR_CHAT_2, + SFX_WMOTR_CHAT_3, + SFX_WMOTR_CHAT_4, + SFX_WMOTR_CHAT_5, + SFX_WMOTR_CHAT_6, + SFX_WMOTR_CHAT_7, + SFX_WMOTR_CHAT_8, + SFX_WMOTR_CHAT_9, + SFX_WMOTR_CHAT_10, + SFX_WMOTR_CHAT_11, + SFX_WMOTR_CHAT_12, + SFX_WMOTR_CHAT_13, + SFX_WMOTR_DODGE_1, + SFX_WMOTR_DODGE_2, + SFX_WMOTR_DODGE_3, + SFX_WMOTR_DODGE_4, + SFX_WMOTR_DODGE_5, + SFX_WMOTR_DODGE_6, + SFX_WMOTR_DODGE_7, + SFX_WMOTR_DODGE_8, + SFX_WMOTR_DODGE_9, + SFX_WMOTR_DODGE_10, + SFX_WMOTR_DODGE_11, + SFX_WMOTR_DODGE_12, + SFX_WMOTR_DODGE_13, + SFX_WMOTR_DODGE_14, + SFX_WMOTR_DODGE_15, + SFX_WMOTR_DODGE_16, + SFX_WMOTR_DODGE_17, + SFX_WMOTR_EYEING_1, + SFX_WMOTR_EYEING_2, + SFX_WMOTR_FIGHT_1, + SFX_WMOTR_FIGHT_2, + SFX_WMOTR_FIGHT_3, + SFX_WMOTR_FIGHT_4, + SFX_WMOTR_FIGHT_5, + SFX_WMOTR_FIGHT_6, + SFX_WMOTR_GUN_COOL_1, + SFX_WMOTR_GUN_COOL_2, + SFX_WMOTR_GUN_COOL_3, + SFX_WMOTR_GUN_COOL_4, + SFX_WMOTR_GUN_COOL_5, + SFX_WMOTR_SAVED_1, + SFX_WMOTR_SHOCKED_1, + SFX_WMOTR_SHOCKED_2, + SFX_WMOTR_SHOCKED_3, + SFX_WMOTR_SOLICIT_1, + SFX_WMOTR_SOLICIT_2, + SFX_WMOTR_SOLICIT_3, + SFX_WMOTR_SOLICIT_4, + SFX_WMOTR_SOLICIT_5, + SFX_WMOTR_SOLICIT_6, + SFX_WMOTR_SOLICIT_7, + SFX_WMOTR_TAXI_1, + + SFX_BMOBE_BUMP_1, + SFX_BMOBE_BUMP_2, + SFX_BMOBE_BUMP_3, + SFX_BMOBE_BUMP_4, + SFX_BMOBE_BUMP_5, + SFX_BMOBE_CAR_CRASH_1, + SFX_BMOBE_CAR_CRASH_2, + SFX_BMOBE_CAR_CRASH_3, + SFX_BMOBE_CAR_CRASH_4, + SFX_BMOBE_CAR_CRASH_5, + SFX_BMOBE_CAR_CRASH_6, + SFX_BMOBE_CAR_CRASH_7, + SFX_BMOBE_CAR_CRASH_8, + SFX_BMOBE_CAR_CRASH_9, + SFX_BMOBE_CHAT_1, + SFX_BMOBE_CHAT_2, + SFX_BMOBE_CHAT_3, + SFX_BMOBE_CHAT_4, + SFX_BMOBE_CHAT_5, + SFX_BMOBE_CHAT_6, + SFX_BMOBE_CHAT_7, + SFX_BMOBE_CHAT_8, + SFX_BMOBE_CHAT_9, + SFX_BMOBE_CHAT_10, + SFX_BMOBE_DODGE_1, + SFX_BMOBE_DODGE_2, + SFX_BMOBE_DODGE_3, + SFX_BMOBE_DODGE_4, + SFX_BMOBE_DODGE_5, + SFX_BMOBE_DODGE_6, + SFX_BMOBE_DODGE_7, + SFX_BMOBE_DODGE_8, + SFX_BMOBE_DODGE_9, + SFX_BMOBE_DODGE_10, + SFX_BMOBE_DODGE_11, + SFX_BMOBE_FIGHT_1, + SFX_BMOBE_FIGHT_2, + SFX_BMOBE_FIGHT_3, + SFX_BMOBE_FIGHT_4, + SFX_BMOBE_FIGHT_5, + SFX_BMOBE_FIGHT_6, + SFX_BMOBE_FIGHT_7, + SFX_BMOBE_FIGHT_8, + SFX_BMOBE_FIGHT_9, + SFX_BMOBE_FIGHT_10, + SFX_BMOBE_GENERIC_CRASH_1, + SFX_BMOBE_GENERIC_CRASH_2, + SFX_BMOBE_GENERIC_CRASH_3, + SFX_BMOBE_GENERIC_CRASH_4, + SFX_BMOBE_GENERIC_CRASH_5, + SFX_BMOBE_GENERIC_CRASH_6, + SFX_BMOBE_GENERIC_CRASH_7, + SFX_BMOBE_GUN_PANIC_1, + SFX_BMOBE_GUN_PANIC_2, + SFX_BMOBE_GUN_PANIC_3, + SFX_BMOBE_GUN_PANIC_4, + SFX_BMOBE_GUN_PANIC_5, + SFX_BMOBE_JACKED_1, + SFX_BMOBE_JACKED_2, + SFX_BMOBE_JACKED_3, + SFX_BMOBE_JACKED_4, + SFX_BMOBE_JACKED_5, + SFX_BMOBE_JACKED_6, + SFX_BMOBE_MUGGED_1, + SFX_BMOBE_MUGGED_2, + SFX_BMOBE_MUGGED_3, + SFX_BMOBE_MUGGED_4, + SFX_BMOBE_SAVED_1, + SFX_BMOBE_SAVED_2, + SFX_BMOBE_SAVED_3, + SFX_BMOBE_SHOCKED_1, + SFX_BMOBE_SHOCKED_2, + SFX_BMOBE_SHOCKED_3, + SFX_BMOBE_TAXI_1, + + SFX_WMYGO_BUMP_1, + SFX_WMYGO_BUMP_2, + SFX_WMYGO_BUMP_3, + SFX_WMYGO_BUMP_4, + SFX_WMYGO_BUMP_5, + SFX_WMYGO_BUMP_6, + SFX_WMYGO_BUMP_7, + SFX_WMYGO_BUMP_8, + SFX_WMYGO_BUMP_9, + SFX_WMYGO_CAR_CRASH_1, + SFX_WMYGO_CAR_CRASH_2, + SFX_WMYGO_CAR_CRASH_3, + SFX_WMYGO_CAR_CRASH_4, + SFX_WMYGO_CAR_CRASH_5, + SFX_WMYGO_CAR_CRASH_6, + SFX_WMYGO_CAR_CRASH_7, + SFX_WMYGO_CHAT_1, + SFX_WMYGO_CHAT_2, + SFX_WMYGO_CHAT_3, + SFX_WMYGO_CHAT_4, + SFX_WMYGO_CHAT_5, + SFX_WMYGO_CHAT_6, + SFX_WMYGO_CHAT_7, + SFX_WMYGO_CHAT_8, + SFX_WMYGO_CHAT_9, + SFX_WMYGO_CHAT_10, + SFX_WMYGO_DODGE_1, + SFX_WMYGO_DODGE_2, + SFX_WMYGO_DODGE_3, + SFX_WMYGO_DODGE_4, + SFX_WMYGO_DODGE_5, + SFX_WMYGO_DODGE_6, + SFX_WMYGO_DODGE_7, + SFX_WMYGO_DODGE_8, + SFX_WMYGO_DODGE_9, + SFX_WMYGO_DODGE_10, + SFX_WMYGO_DODGE_11, + SFX_WMYGO_EYEING_1, + SFX_WMYGO_EYEING_2, + SFX_WMYGO_GENERIC_CRASH_1, + SFX_WMYGO_GENERIC_CRASH_2, + SFX_WMYGO_GENERIC_CRASH_3, + SFX_WMYGO_GENERIC_CRASH_4, + SFX_WMYGO_GENERIC_CRASH_5, + SFX_WMYGO_GENERIC_CRASH_6, + SFX_WMYGO_GENERIC_CRASH_7, + SFX_WMYGO_GUN_PANIC_1, + SFX_WMYGO_GUN_PANIC_2, + SFX_WMYGO_GUN_PANIC_3, + SFX_WMYGO_GUN_PANIC_4, + SFX_WMYGO_GUN_PANIC_5, + SFX_WMYGO_JACKED_1, + SFX_WMYGO_JACKED_2, + SFX_WMYGO_JACKED_3, + SFX_WMYGO_JACKED_4, + SFX_WMYGO_JACKED_5, + SFX_WMYGO_JACKED_6, + SFX_WMYGO_MUGGED_1, + SFX_WMYGO_MUGGED_2, + SFX_WMYGO_RUN_1, + SFX_WMYGO_RUN_2, + SFX_WMYGO_RUN_3, + SFX_WMYGO_RUN_4, + SFX_WMYGO_RUN_5, + SFX_WMYGO_RUN_6, + SFX_WMYGO_SAVED_1, + SFX_WMYGO_SHOCKED_1, + SFX_WMYGO_SHOCKED_2, + SFX_WMYGO_TAXI_1, + SFX_WMYGO_TAXI_2, + SFX_WMYGO_TAXI_3, + + SFX_WFYBE_BLOCKED_1, + SFX_WFYBE_BLOCKED_2, + SFX_WFYBE_BLOCKED_3, + SFX_WFYBE_BLOCKED_4, + SFX_WFYBE_BLOCKED_5, + SFX_WFYBE_BLOCKED_6, + SFX_WFYBE_BLOCKED_7, + SFX_WFYBE_BUMP_1, + SFX_WFYBE_BUMP_2, + SFX_WFYBE_BUMP_3, + SFX_WFYBE_BUMP_4, + SFX_WFYBE_BUMP_5, + SFX_WFYBE_BUMP_6, + SFX_WFYBE_BUMP_7, + SFX_WFYBE_BUMP_8, + SFX_WFYBE_BUMP_9, + SFX_WFYBE_BUMP_10, + SFX_WFYBE_BUMP_11, + SFX_WFYBE_CAR_CRASH_1, + SFX_WFYBE_CAR_CRASH_2, + SFX_WFYBE_CAR_CRASH_3, + SFX_WFYBE_CAR_CRASH_4, + SFX_WFYBE_CAR_CRASH_5, + SFX_WFYBE_CAR_CRASH_6, + SFX_WFYBE_CHAT_1, + SFX_WFYBE_CHAT_2, + SFX_WFYBE_CHAT_3, + SFX_WFYBE_CHAT_4, + SFX_WFYBE_CHAT_5, + SFX_WFYBE_CHAT_6, + SFX_WFYBE_CHAT_7, + SFX_WFYBE_CHAT_8, + SFX_WFYBE_CHAT_9, + SFX_WFYBE_CHAT_10, + SFX_WFYBE_DODGE_1, + SFX_WFYBE_DODGE_2, + SFX_WFYBE_DODGE_3, + SFX_WFYBE_DODGE_4, + SFX_WFYBE_DODGE_5, + SFX_WFYBE_DODGE_6, + SFX_WFYBE_DODGE_7, + SFX_WFYBE_DODGE_8, + SFX_WFYBE_GENERIC_CRASH_1, + SFX_WFYBE_GENERIC_CRASH_2, + SFX_WFYBE_GENERIC_CRASH_3, + SFX_WFYBE_GENERIC_CRASH_4, + SFX_WFYBE_GENERIC_CRASH_5, + SFX_WFYBE_GENERIC_CRASH_6, + SFX_WFYBE_GUN_PANIC_1, + SFX_WFYBE_GUN_PANIC_2, + SFX_WFYBE_GUN_PANIC_3, + SFX_WFYBE_GUN_PANIC_4, + SFX_WFYBE_GUN_PANIC_5, + SFX_WFYBE_JACKED_1, + SFX_WFYBE_JACKED_2, + SFX_WFYBE_JACKED_3, + SFX_WFYBE_JACKED_4, + SFX_WFYBE_RUN_1, + SFX_WFYBE_RUN_2, + SFX_WFYBE_RUN_3, + SFX_WFYBE_RUN_4, + SFX_WFYBE_RUN_5, + SFX_WFYBE_SAVED_1, + SFX_WFYBE_SHOCKED_1, + SFX_WFYBE_SHOCKED_2, + SFX_WFYBE_SHOCKED_3, + SFX_WFYBE_TAXI_1, + + SFX_WFORI_BLOCKED_1, + SFX_WFORI_BLOCKED_2, + SFX_WFORI_BLOCKED_3, + SFX_WFORI_BLOCKED_4, + SFX_WFORI_BLOCKED_5, + SFX_WFORI_BLOCKED_6, + SFX_WFORI_BLOCKED_7, + SFX_WFORI_BUMP_1, + SFX_WFORI_BUMP_2, + SFX_WFORI_BUMP_3, + SFX_WFORI_BUMP_4, + SFX_WFORI_BUMP_5, + SFX_WFORI_BUMP_6, + SFX_WFORI_BUMP_7, + SFX_WFORI_BUMP_8, + SFX_WFORI_BUMP_9, + SFX_WFORI_BUMP_10, + SFX_WFORI_BUMP_11, + SFX_WFORI_CAR_CRASH_1, + SFX_WFORI_CAR_CRASH_2, + SFX_WFORI_CAR_CRASH_3, + SFX_WFORI_CAR_CRASH_4, + SFX_WFORI_CAR_CRASH_5, + SFX_WFORI_CAR_CRASH_6, + SFX_WFORI_CAR_CRASH_7, + SFX_WFORI_CAR_CRASH_8, + SFX_WFORI_CAR_CRASH_9, + SFX_WFORI_CAR_CRASH_10, + SFX_WFORI_DODGE_1, + SFX_WFORI_DODGE_2, + SFX_WFORI_DODGE_3, + SFX_WFORI_DODGE_4, + SFX_WFORI_DODGE_5, + SFX_WFORI_DODGE_6, + SFX_WFORI_DODGE_7, + SFX_WFORI_DODGE_8, + SFX_WFORI_DODGE_9, + SFX_WFORI_DODGE_10, + SFX_WFORI_DODGE_11, + SFX_WFORI_FIGHT_1, + SFX_WFORI_FIGHT_2, + SFX_WFORI_FIGHT_3, + SFX_WFORI_FIGHT_4, + SFX_WFORI_FIGHT_5, + SFX_WFORI_FIGHT_6, + SFX_WFORI_FIGHT_7, + SFX_WFORI_GENERIC_CRASH_1, + SFX_WFORI_GENERIC_CRASH_2, + SFX_WFORI_GENERIC_CRASH_3, + SFX_WFORI_GENERIC_CRASH_4, + SFX_WFORI_GENERIC_CRASH_5, + SFX_WFORI_GENERIC_CRASH_6, + SFX_WFORI_GENERIC_CRASH_7, + SFX_WFORI_GENERIC_CRASH_8, + SFX_WFORI_GUN_PANIC_1, + SFX_WFORI_GUN_PANIC_2, + SFX_WFORI_GUN_PANIC_3, + SFX_WFORI_GUN_PANIC_4, + SFX_WFORI_GUN_PANIC_5, + SFX_WFORI_GUN_PANIC_6, + SFX_WFORI_JACKED_1, + SFX_WFORI_JACKED_2, + SFX_WFORI_JACKED_3, + SFX_WFORI_JACKED_4, + SFX_WFORI_JACKED_5, + SFX_WFORI_JACKED_6, + SFX_WFORI_LOST_1, + SFX_WFORI_LOST_2, + SFX_WFORI_MUGGED_1, + SFX_WFORI_MUGGED_2, + SFX_WFORI_MUGGED_3, + SFX_WFORI_SAVED_1, + SFX_WFORI_SHOCKED_1, + SFX_WFORI_SHOCKED_2, + SFX_WFORI_SHOCKED_3, + SFX_WFORI_TAXI_1, + + SFX_WFOGO_BUMP_1, + SFX_WFOGO_BUMP_2, + SFX_WFOGO_BUMP_3, + SFX_WFOGO_BUMP_4, + SFX_WFOGO_BUMP_5, + SFX_WFOGO_BUMP_6, + SFX_WFOGO_BUMP_7, + SFX_WFOGO_BUMP_8, + SFX_WFOGO_CAR_CRASH_1, + SFX_WFOGO_CAR_CRASH_2, + SFX_WFOGO_CAR_CRASH_3, + SFX_WFOGO_CAR_CRASH_4, + SFX_WFOGO_CAR_CRASH_5, + SFX_WFOGO_CAR_CRASH_6, + SFX_WFOGO_CAR_CRASH_7, + SFX_WFOGO_CAR_CRASH_8, + SFX_WFOGO_CHAT_1, + SFX_WFOGO_CHAT_2, + SFX_WFOGO_CHAT_3, + SFX_WFOGO_CHAT_4, + SFX_WFOGO_CHAT_5, + SFX_WFOGO_CHAT_6, + SFX_WFOGO_CHAT_7, + SFX_WFOGO_CHAT_8, + SFX_WFOGO_CHAT_9, + SFX_WFOGO_CHAT_10, + SFX_WFOGO_CHAT_11, + SFX_WFOGO_DODGE_1, + SFX_WFOGO_DODGE_2, + SFX_WFOGO_DODGE_3, + SFX_WFOGO_DODGE_4, + SFX_WFOGO_DODGE_5, + SFX_WFOGO_DODGE_6, + SFX_WFOGO_DODGE_7, + SFX_WFOGO_DODGE_8, + SFX_WFOGO_DODGE_9, + SFX_WFOGO_FIGHT_1, + SFX_WFOGO_FIGHT_2, + SFX_WFOGO_FIGHT_3, + SFX_WFOGO_FIGHT_4, + SFX_WFOGO_FIGHT_5, + SFX_WFOGO_FIGHT_6, + SFX_WFOGO_FIGHT_7, + SFX_WFOGO_FIGHT_8, + SFX_WFOGO_FIGHT_9, + SFX_WFOGO_FIGHT_10, + SFX_WFOGO_FIGHT_11, + SFX_WFOGO_FIGHT_12, + SFX_WFOGO_FIGHT_13, + SFX_WFOGO_FIGHT_14, + SFX_WFOGO_GENERIC_CRASH_1, + SFX_WFOGO_GENERIC_CRASH_2, + SFX_WFOGO_GENERIC_CRASH_3, + SFX_WFOGO_GENERIC_CRASH_4, + SFX_WFOGO_GENERIC_CRASH_5, + SFX_WFOGO_GENERIC_CRASH_6, + SFX_WFOGO_GENERIC_CRASH_7, + SFX_WFOGO_GUN_PANIC_1, + SFX_WFOGO_GUN_PANIC_2, + SFX_WFOGO_GUN_PANIC_3, + SFX_WFOGO_GUN_PANIC_4, + SFX_WFOGO_GUN_PANIC_5, + SFX_WFOGO_JACKED_1, + SFX_WFOGO_JACKED_2, + SFX_WFOGO_JACKED_3, + SFX_WFOGO_JACKED_4, + SFX_WFOGO_JACKED_5, + SFX_WFOGO_JACKED_6, + SFX_WFOGO_MUGGED_1, + SFX_WFOGO_MUGGED_2, + SFX_WFOGO_RUN_1, + SFX_WFOGO_RUN_2, + SFX_WFOGO_SAVED_1, + SFX_WFOGO_SHOCKED_1, + SFX_WFOGO_SHOCKED_2, + + SFX_HMYST_BUMP_1, + SFX_HMYST_BUMP_2, + SFX_HMYST_BUMP_3, + SFX_HMYST_BUMP_4, + SFX_HMYST_BUMP_5, + SFX_HMYST_BUMP_6, + SFX_HMYST_BUMP_7, + SFX_HMYST_BUMP_8, + SFX_HMYST_BUMP_9, + SFX_HMYST_BUMP_10, + SFX_HMYST_BUMP_11, + SFX_HMYST_BUMP_12, + SFX_HMYST_BUMP_13, + SFX_HMYST_CHAT_1, + SFX_HMYST_CHAT_2, + SFX_HMYST_CHAT_3, + SFX_HMYST_CHAT_4, + SFX_HMYST_CHAT_5, + SFX_HMYST_CHAT_6, + SFX_HMYST_CHAT_7, + SFX_HMYST_CHAT_8, + SFX_HMYST_CHAT_9, + SFX_HMYST_CHAT_10, + SFX_HMYST_CHAT_11, + SFX_HMYST_DODGE_1, + SFX_HMYST_DODGE_2, + SFX_HMYST_DODGE_3, + SFX_HMYST_DODGE_4, + SFX_HMYST_DODGE_5, + SFX_HMYST_DODGE_6, + SFX_HMYST_EYEING_1, + SFX_HMYST_EYEING_2, + SFX_HMYST_GENERIC_CRASH_1, + SFX_HMYST_GENERIC_CRASH_2, + SFX_HMYST_GENERIC_CRASH_3, + SFX_HMYST_GENERIC_CRASH_4, + SFX_HMYST_GENERIC_CRASH_5, + SFX_HMYST_GENERIC_CRASH_6, + SFX_HMYST_GENERIC_CRASH_7, + SFX_HMYST_GUN_PANIC_1, + SFX_HMYST_GUN_PANIC_2, + SFX_HMYST_GUN_PANIC_3, + SFX_HMYST_GUN_PANIC_4, + SFX_HMYST_GUN_PANIC_5, + SFX_HMYST_GUN_PANIC_6, + SFX_HMYST_RUN_1, + SFX_HMYST_RUN_2, + SFX_HMYST_RUN_3, + SFX_HMYST_RUN_4, + SFX_HMYST_SAVED_1, + SFX_HMYST_SHOCKED_1, + SFX_HMYST_SHOCKED_2, + SFX_HMYST_TAXI_1, + + SFX_WMOCA_BLOCKED_1, + SFX_WMOCA_BLOCKED_2, + SFX_WMOCA_BLOCKED_3, + SFX_WMOCA_BLOCKED_4, + SFX_WMOCA_BLOCKED_5, + SFX_WMOCA_BLOCKED_6, + SFX_WMOCA_BLOCKED_7, + SFX_WMOCA_BLOCKED_8, + SFX_WMOCA_BLOCKED_9, + SFX_WMOCA_BLOCKED_10, + SFX_WMOCA_BLOCKED_11, + SFX_WMOCA_BLOCKED_12, + SFX_WMOCA_BUMP_1, + SFX_WMOCA_BUMP_2, + SFX_WMOCA_BUMP_3, + SFX_WMOCA_BUMP_4, + SFX_WMOCA_BUMP_5, + SFX_WMOCA_BUMP_6, + SFX_WMOCA_CAR_CRASH_1, + SFX_WMOCA_CAR_CRASH_2, + SFX_WMOCA_CAR_CRASH_3, + SFX_WMOCA_CAR_CRASH_4, + SFX_WMOCA_CAR_CRASH_5, + SFX_WMOCA_CAR_CRASH_6, + SFX_WMOCA_CAR_CRASH_7, + SFX_WMOCA_CAR_CRASH_8, + SFX_WMOCA_CAR_CRASH_9, + SFX_WMOCA_CAR_CRASH_10, + SFX_WMOCA_DODGE_1, + SFX_WMOCA_DODGE_2, + SFX_WMOCA_DODGE_3, + SFX_WMOCA_DODGE_4, + SFX_WMOCA_DODGE_5, + SFX_WMOCA_DODGE_6, + SFX_WMOCA_DODGE_7, + SFX_WMOCA_DODGE_8, + SFX_WMOCA_DODGE_9, + SFX_WMOCA_DODGE_10, + SFX_WMOCA_EYEING_1, + SFX_WMOCA_EYEING_2, + SFX_WMOCA_FIGHT_1, + SFX_WMOCA_FIGHT_2, + SFX_WMOCA_FIGHT_3, + SFX_WMOCA_FIGHT_4, + SFX_WMOCA_FIGHT_5, + SFX_WMOCA_FIGHT_6, + SFX_WMOCA_FIGHT_7, + SFX_WMOCA_FIGHT_8, + SFX_WMOCA_GENERIC_CRASH_1, + SFX_WMOCA_GENERIC_CRASH_2, + SFX_WMOCA_GENERIC_CRASH_3, + SFX_WMOCA_GENERIC_CRASH_4, + SFX_WMOCA_GENERIC_CRASH_5, + SFX_WMOCA_GENERIC_CRASH_6, + SFX_WMOCA_GENERIC_CRASH_7, + SFX_WMOCA_GENERIC_CRASH_8, + SFX_WMOCA_GENERIC_CRASH_9, + SFX_WMOCA_GUN_PANIC_1, + SFX_WMOCA_GUN_PANIC_2, + SFX_WMOCA_GUN_PANIC_3, + SFX_WMOCA_GUN_PANIC_4, + SFX_WMOCA_GUN_PANIC_5, + SFX_WMOCA_GUN_PANIC_6, + SFX_WMOCA_JACKED_1, + SFX_WMOCA_JACKED_2, + SFX_WMOCA_JACKED_3, + SFX_WMOCA_JACKED_4, + SFX_WMOCA_JACKED_5, + SFX_WMOCA_JACKED_6, + SFX_WMOCA_JACKED_7, + SFX_WMOCA_JACKED_8, + SFX_WMOCA_JACKED_9, + SFX_WMOCA_JACKED_10, + SFX_WMOCA_JACKING_1, + SFX_WMOCA_JACKING_2, + SFX_WMOCA_JACKING_3, + SFX_WMOCA_JACKING_4, + SFX_WMOCA_JACKING_5, + SFX_WMOCA_JACKING_6, + SFX_WMOCA_JACKING_7, + SFX_WMOCA_JACKING_8, + SFX_WMOCA_JACKING_9, + SFX_WMOCA_JACKING_10, + SFX_WMOCA_JACKING_11, + SFX_WMOCA_MUGGED_1, + SFX_WMOCA_MUGGED_2, + SFX_WMOCA_SAVED_1, + SFX_WMOCA_TAXI_1, + + SFX_WMYBE_BLOCKED_1, + SFX_WMYBE_BLOCKED_2, + SFX_WMYBE_BLOCKED_3, + SFX_WMYBE_BLOCKED_4, + SFX_WMYBE_BLOCKED_5, + SFX_WMYBE_BLOCKED_6, + SFX_WMYBE_BLOCKED_7, + SFX_WMYBE_BLOCKED_8, + SFX_WMYBE_BLOCKED_9, + SFX_WMYBE_BUMP_1, + SFX_WMYBE_BUMP_2, + SFX_WMYBE_BUMP_3, + SFX_WMYBE_BUMP_4, + SFX_WMYBE_BUMP_5, + SFX_WMYBE_BUMP_6, + SFX_WMYBE_BUMP_7, + SFX_WMYBE_BUMP_8, + SFX_WMYBE_BUMP_9, + SFX_WMYBE_BUMP_10, + SFX_WMYBE_BUMP_11, + SFX_WMYBE_BUMP_12, + SFX_WMYBE_BUMP_13, + SFX_WMYBE_BUMP_14, + SFX_WMYBE_CAR_CRASH_1, + SFX_WMYBE_CAR_CRASH_2, + SFX_WMYBE_CAR_CRASH_3, + SFX_WMYBE_CAR_CRASH_4, + SFX_WMYBE_CAR_CRASH_5, + SFX_WMYBE_CAR_CRASH_6, + SFX_WMYBE_CAR_CRASH_7, + SFX_WMYBE_CAR_CRASH_8, + SFX_WMYBE_CHAT_1, + SFX_WMYBE_CHAT_2, + SFX_WMYBE_CHAT_3, + SFX_WMYBE_CHAT_4, + SFX_WMYBE_CHAT_5, + SFX_WMYBE_CHAT_6, + SFX_WMYBE_CHAT_7, + SFX_WMYBE_CHAT_8, + SFX_WMYBE_CHAT_9, + SFX_WMYBE_CHAT_10, + SFX_WMYBE_CHAT_11, + SFX_WMYBE_DODGE_1, + SFX_WMYBE_DODGE_2, + SFX_WMYBE_DODGE_3, + SFX_WMYBE_DODGE_4, + SFX_WMYBE_DODGE_5, + SFX_WMYBE_DODGE_6, + SFX_WMYBE_DODGE_7, + SFX_WMYBE_DODGE_8, + SFX_WMYBE_DODGE_9, + SFX_WMYBE_DODGE_10, + SFX_WMYBE_DODGE_11, + SFX_WMYBE_DODGE_12, + SFX_WMYBE_EYEING_1, + SFX_WMYBE_EYEING_2, + SFX_WMYBE_GENERIC_CRASH_1, + SFX_WMYBE_GENERIC_CRASH_2, + SFX_WMYBE_GENERIC_CRASH_3, + SFX_WMYBE_GENERIC_CRASH_4, + SFX_WMYBE_GENERIC_CRASH_5, + SFX_WMYBE_GENERIC_CRASH_6, + SFX_WMYBE_GENERIC_CRASH_7, + SFX_WMYBE_GENERIC_CRASH_8, + SFX_WMYBE_GUN_PANIC_1, + SFX_WMYBE_GUN_PANIC_2, + SFX_WMYBE_GUN_PANIC_3, + SFX_WMYBE_GUN_PANIC_4, + SFX_WMYBE_GUN_PANIC_5, + SFX_WMYBE_GUN_PANIC_6, + SFX_WMYBE_GUN_PANIC_7, + SFX_WMYBE_GUN_PANIC_8, + SFX_WMYBE_JACKED_1, + SFX_WMYBE_JACKED_2, + SFX_WMYBE_JACKED_3, + SFX_WMYBE_JACKED_4, + SFX_WMYBE_JACKED_5, + SFX_WMYBE_JACKED_6, + SFX_WMYBE_JACKED_7, + SFX_WMYBE_JACKING_1, + SFX_WMYBE_JACKING_2, + SFX_WMYBE_JACKING_3, + SFX_WMYBE_JEER_1, + SFX_WMYBE_JEER_2, + SFX_WMYBE_JEER_3, + SFX_WMYBE_JEER_4, + SFX_WMYBE_JEER_5, + SFX_WMYBE_JEER_6, + SFX_WMYBE_JEER_7, + SFX_WMYBE_LOST_1, + SFX_WMYBE_LOST_2, + SFX_WMYBE_LOST_3, + SFX_WMYBE_RUN_1, + SFX_WMYBE_RUN_2, + SFX_WMYBE_RUN_3, + SFX_WMYBE_RUN_4, + SFX_WMYBE_RUN_5, + SFX_WMYBE_SHOCKED_1, + SFX_WMYBE_SHOCKED_2, + SFX_WMYBE_SHOCKED_3, + SFX_WMYBE_SHOCKED_4, + SFX_WMYBE_SHOCKED_5, + SFX_WMYBE_SHOCKED_6, + + SFX_BFOBE_BLOCKED_1, + SFX_BFOBE_BLOCKED_2, + SFX_BFOBE_BLOCKED_3, + SFX_BFOBE_BLOCKED_4, + SFX_BFOBE_BLOCKED_5, + SFX_BFOBE_BLOCKED_6, + SFX_BFOBE_BLOCKED_7, + SFX_BFOBE_BLOCKED_8, + SFX_BFOBE_BUMP_1, + SFX_BFOBE_BUMP_2, + SFX_BFOBE_BUMP_3, + SFX_BFOBE_BUMP_4, + SFX_BFOBE_BUMP_5, + SFX_BFOBE_BUMP_6, + SFX_BFOBE_BUMP_7, + SFX_BFOBE_BUMP_8, + SFX_BFOBE_CAR_CRASH_1, + SFX_BFOBE_CAR_CRASH_2, + SFX_BFOBE_CAR_CRASH_3, + SFX_BFOBE_CAR_CRASH_4, + SFX_BFOBE_CAR_CRASH_5, + SFX_BFOBE_CAR_CRASH_6, + SFX_BFOBE_CAR_CRASH_7, + SFX_BFOBE_CHAT_1, + SFX_BFOBE_CHAT_2, + SFX_BFOBE_CHAT_3, + SFX_BFOBE_CHAT_4, + SFX_BFOBE_CHAT_5, + SFX_BFOBE_CHAT_6, + SFX_BFOBE_CHAT_7, + SFX_BFOBE_CHAT_8, + SFX_BFOBE_DODGE_1, + SFX_BFOBE_DODGE_2, + SFX_BFOBE_DODGE_3, + SFX_BFOBE_DODGE_4, + SFX_BFOBE_DODGE_5, + SFX_BFOBE_DODGE_6, + SFX_BFOBE_DODGE_7, + SFX_BFOBE_DODGE_8, + SFX_BFOBE_DODGE_9, + SFX_BFOBE_GENERIC_CRASH_1, + SFX_BFOBE_GENERIC_CRASH_2, + SFX_BFOBE_GENERIC_CRASH_3, + SFX_BFOBE_GENERIC_CRASH_4, + SFX_BFOBE_GENERIC_CRASH_5, + SFX_BFOBE_GENERIC_CRASH_6, + SFX_BFOBE_GENERIC_CRASH_7, + SFX_BFOBE_GUN_PANIC_1, + SFX_BFOBE_GUN_PANIC_2, + SFX_BFOBE_GUN_PANIC_3, + SFX_BFOBE_GUN_PANIC_4, + SFX_BFOBE_GUN_PANIC_5, + SFX_BFOBE_JACKED_1, + SFX_BFOBE_JACKED_2, + SFX_BFOBE_JACKED_3, + SFX_BFOBE_JACKED_4, + SFX_BFOBE_JACKED_5, + SFX_BFOBE_JACKING_1, + SFX_BFOBE_JACKING_2, + SFX_BFOBE_JACKING_3, + SFX_BFOBE_JACKING_4, + SFX_BFOBE_MUGGED_1, + SFX_BFOBE_MUGGED_2, + SFX_BFOBE_SHOCKED_1, + SFX_BFOBE_SHOCKED_2, + SFX_BFOBE_TAXI_1, + + SFX_WFYLG_BUMP_1, + SFX_WFYLG_BUMP_2, + SFX_WFYLG_BUMP_3, + SFX_WFYLG_BUMP_4, + SFX_WFYLG_BUMP_5, + SFX_WFYLG_BUMP_6, + SFX_WFYLG_BUMP_7, + SFX_WFYLG_BUMP_8, + SFX_WFYLG_BUMP_9, + SFX_WFYLG_BUMP_10, + SFX_WFYLG_CHAT_1, + SFX_WFYLG_CHAT_2, + SFX_WFYLG_CHAT_3, + SFX_WFYLG_CHAT_4, + SFX_WFYLG_CHAT_5, + SFX_WFYLG_CHAT_6, + SFX_WFYLG_CHAT_7, + SFX_WFYLG_CHAT_8, + SFX_WFYLG_CHAT_9, + SFX_WFYLG_CHAT_10, + SFX_WFYLG_DODGE_1, + SFX_WFYLG_DODGE_2, + SFX_WFYLG_DODGE_3, + SFX_WFYLG_DODGE_4, + SFX_WFYLG_DODGE_5, + SFX_WFYLG_DODGE_6, + SFX_WFYLG_DODGE_7, + SFX_WFYLG_DODGE_8, + SFX_WFYLG_FIGHT_1, + SFX_WFYLG_FIGHT_2, + SFX_WFYLG_FIGHT_3, + SFX_WFYLG_FIGHT_4, + SFX_WFYLG_FIGHT_5, + SFX_WFYLG_FIGHT_6, + SFX_WFYLG_FIGHT_7, + SFX_WFYLG_GUN_COOL_1, + SFX_WFYLG_GUN_COOL_2, + SFX_WFYLG_GUN_COOL_3, + SFX_WFYLG_GUN_COOL_4, + SFX_WFYLG_GUN_COOL_5, + SFX_WFYLG_SAVED_1, + SFX_WFYLG_TAXI_1, + + SFX_WFOST_BLOCKED_1, + SFX_WFOST_BLOCKED_2, + SFX_WFOST_BLOCKED_3, + SFX_WFOST_BLOCKED_4, + SFX_WFOST_BLOCKED_5, + SFX_WFOST_BLOCKED_6, + SFX_WFOST_BLOCKED_7, + SFX_WFOST_BLOCKED_8, + SFX_WFOST_BLOCKED_9, + SFX_WFOST_BLOCKED_10, + SFX_WFOST_BLOCKED_11, + SFX_WFOST_BLOCKED_12, + SFX_WFOST_BUMP_1, + SFX_WFOST_BUMP_2, + SFX_WFOST_BUMP_3, + SFX_WFOST_BUMP_4, + SFX_WFOST_BUMP_5, + SFX_WFOST_BUMP_6, + SFX_WFOST_BUMP_7, + SFX_WFOST_BUMP_8, + SFX_WFOST_BUMP_9, + SFX_WFOST_BUMP_10, + SFX_WFOST_BUMP_11, + SFX_WFOST_BUMP_12, + SFX_WFOST_BUMP_13, + SFX_WFOST_BUMP_14, + SFX_WFOST_BUMP_15, + SFX_WFOST_BUMP_16, + SFX_WFOST_BUMP_17, + SFX_WFOST_BUMP_18, + SFX_WFOST_BUMP_19, + SFX_WFOST_CAR_CRASH_1, + SFX_WFOST_CAR_CRASH_2, + SFX_WFOST_CAR_CRASH_3, + SFX_WFOST_CAR_CRASH_4, + SFX_WFOST_CAR_CRASH_5, + SFX_WFOST_CAR_CRASH_6, + SFX_WFOST_CAR_CRASH_7, + SFX_WFOST_CAR_CRASH_8, + SFX_WFOST_CAR_CRASH_9, + SFX_WFOST_CAR_CRASH_10, + SFX_WFOST_CAR_CRASH_11, + SFX_WFOST_CHAT_1, + SFX_WFOST_CHAT_2, + SFX_WFOST_CHAT_3, + SFX_WFOST_CHAT_4, + SFX_WFOST_CHAT_5, + SFX_WFOST_CHAT_6, + SFX_WFOST_CHAT_7, + SFX_WFOST_CHAT_8, + SFX_WFOST_CHAT_9, + SFX_WFOST_CHAT_10, + SFX_WFOST_CHAT_11, + SFX_WFOST_CHAT_12, + SFX_WFOST_CHAT_13, + SFX_WFOST_CHAT_14, + SFX_WFOST_CHAT_15, + SFX_WFOST_CHAT_16, + SFX_WFOST_DODGE_1, + SFX_WFOST_DODGE_2, + SFX_WFOST_DODGE_3, + SFX_WFOST_DODGE_4, + SFX_WFOST_DODGE_5, + SFX_WFOST_DODGE_6, + SFX_WFOST_DODGE_7, + SFX_WFOST_DODGE_8, + SFX_WFOST_DODGE_9, + SFX_WFOST_DODGE_10, + SFX_WFOST_DODGE_11, + SFX_WFOST_DODGE_12, + SFX_WFOST_GENERIC_CRASH_1, + SFX_WFOST_GENERIC_CRASH_2, + SFX_WFOST_GENERIC_CRASH_3, + SFX_WFOST_GENERIC_CRASH_4, + SFX_WFOST_GENERIC_CRASH_5, + SFX_WFOST_GENERIC_CRASH_6, + SFX_WFOST_GENERIC_CRASH_7, + SFX_WFOST_GENERIC_CRASH_8, + SFX_WFOST_GENERIC_CRASH_9, + SFX_WFOST_GENERIC_CRASH_10, + SFX_WFOST_GUN_PANIC_1, + SFX_WFOST_GUN_PANIC_2, + SFX_WFOST_GUN_PANIC_3, + SFX_WFOST_GUN_PANIC_4, + SFX_WFOST_JACKED_1, + SFX_WFOST_JACKED_2, + SFX_WFOST_JACKED_3, + SFX_WFOST_JACKED_4, + SFX_WFOST_JACKED_5, + SFX_WFOST_JACKED_6, + SFX_WFOST_JACKED_7, + SFX_WFOST_JACKED_8, + SFX_WFOST_LOST_1, + SFX_WFOST_LOST_2, + SFX_WFOST_LOST_3, + SFX_WFOST_MUGGED_1, + SFX_WFOST_MUGGED_2, + SFX_WFOST_MUGGED_3, + SFX_WFOST_MUGGED_4, + SFX_WFOST_MUGGED_5, + SFX_WFOST_RUN_1, + SFX_WFOST_RUN_2, + SFX_WFOST_RUN_3, + SFX_WFOST_RUN_4, + SFX_WFOST_RUN_5, + SFX_WFOST_RUN_6, + SFX_WFOST_RUN_7, + SFX_WFOST_SAVED_1, + SFX_WFOST_SAVED_2, + SFX_WFOST_SAVED_3, + SFX_WFOST_SAVED_4, + SFX_WFOST_TAXI_1, + + SFX_WFYST_BLOCKED_1, + SFX_WFYST_BLOCKED_2, + SFX_WFYST_BLOCKED_3, + SFX_WFYST_BLOCKED_4, + SFX_WFYST_BLOCKED_5, + SFX_WFYST_BLOCKED_6, + SFX_WFYST_BUMP_1, + SFX_WFYST_BUMP_2, + SFX_WFYST_BUMP_3, + SFX_WFYST_BUMP_4, + SFX_WFYST_BUMP_5, + SFX_WFYST_BUMP_6, + SFX_WFYST_BUMP_7, + SFX_WFYST_BUMP_8, + SFX_WFYST_BUMP_9, + SFX_WFYST_BUMP_10, + SFX_WFYST_CAR_CRASH_1, + SFX_WFYST_CAR_CRASH_2, + SFX_WFYST_CAR_CRASH_3, + SFX_WFYST_CAR_CRASH_4, + SFX_WFYST_CAR_CRASH_5, + SFX_WFYST_CAR_CRASH_6, + SFX_WFYST_CAR_CRASH_7, + SFX_WFYST_CAR_CRASH_8, + SFX_WFYST_CHAT_1, + SFX_WFYST_CHAT_2, + SFX_WFYST_CHAT_3, + SFX_WFYST_CHAT_4, + SFX_WFYST_CHAT_5, + SFX_WFYST_CHAT_6, + SFX_WFYST_CHAT_7, + SFX_WFYST_CHAT_8, + SFX_WFYST_CHAT_9, + SFX_WFYST_CHAT_10, + SFX_WFYST_DODGE_1, + SFX_WFYST_DODGE_2, + SFX_WFYST_DODGE_3, + SFX_WFYST_DODGE_4, + SFX_WFYST_DODGE_5, + SFX_WFYST_DODGE_6, + SFX_WFYST_DODGE_7, + SFX_WFYST_DODGE_8, + SFX_WFYST_DODGE_9, + SFX_WFYST_DODGE_10, + SFX_WFYST_FIGHT_1, + SFX_WFYST_FIGHT_2, + SFX_WFYST_FIGHT_3, + SFX_WFYST_FIGHT_4, + SFX_WFYST_FIGHT_5, + SFX_WFYST_FIGHT_6, + SFX_WFYST_FIGHT_7, + SFX_WFYST_GENERIC_CRASH_1, + SFX_WFYST_GENERIC_CRASH_2, + SFX_WFYST_GENERIC_CRASH_3, + SFX_WFYST_GENERIC_CRASH_4, + SFX_WFYST_GENERIC_CRASH_5, + SFX_WFYST_GENERIC_CRASH_6, + SFX_WFYST_GENERIC_CRASH_7, + SFX_WFYST_GENERIC_CRASH_8, + SFX_WFYST_GUN_COOL_1, + SFX_WFYST_GUN_COOL_2, + SFX_WFYST_GUN_COOL_3, + SFX_WFYST_GUN_COOL_4, + SFX_WFYST_GUN_COOL_5, + SFX_WFYST_JACKED_1, + SFX_WFYST_JACKED_2, + SFX_WFYST_JACKED_3, + SFX_WFYST_JACKED_4, + SFX_WFYST_JACKED_5, + SFX_WFYST_JACKED_6, + SFX_WFYST_JACKING_1, + SFX_WFYST_JACKING_2, + SFX_WFYST_JACKING_3, + SFX_WFYST_JACKING_4, + SFX_WFYST_LOST_1, + SFX_WFYST_MUGGED_1, + SFX_WFYST_MUGGED_2, + SFX_WFYST_MUGGING_1, + SFX_WFYST_MUGGING_2, + SFX_WFYST_MUGGING_3, + SFX_WFYST_MUGGING_4, + SFX_WFYST_SAVED_1, + SFX_WFYST_TAXI_1, + + 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_PULLOUTWEAPON_1, + SFX_COP_VOICE_1_PULLOUTWEAPON_2, + SFX_COP_VOICE_1_PULLOUTWEAPON_3, + SFX_COP_VOICE_1_BUMP_1, + SFX_COP_VOICE_1_BUMP_2, + SFX_COP_VOICE_1_BUMP_3, + SFX_COP_VOICE_1_BUMP_4, + SFX_COP_VOICE_1_BUMP_5, + SFX_COP_VOICE_1_COP_LITTLECOPSAROUND_1, + SFX_COP_VOICE_1_COP_LITTLECOPSAROUND_2, + SFX_COP_VOICE_1_COP_LITTLECOPSAROUND_3, + SFX_COP_VOICE_1_COP_LITTLECOPSAROUND_4, + SFX_COP_VOICE_1_GUNAIMEDAT3_1, + SFX_COP_VOICE_1_GUNAIMEDAT3_2, + SFX_COP_VOICE_1_CAR_CRASH_1, + SFX_COP_VOICE_1_CAR_CRASH_2, + SFX_COP_VOICE_1_CAR_CRASH_3, + SFX_COP_VOICE_1_CAR_CRASH_4, + SFX_COP_VOICE_1_DODGE_1, + SFX_COP_VOICE_1_DODGE_2, + SFX_COP_VOICE_1_DODGE_3, + SFX_COP_VOICE_1_FIGHT_1, + SFX_COP_VOICE_1_FIGHT_2, + SFX_COP_VOICE_1_FIGHT_3, + SFX_COP_VOICE_1_FIGHT_4, + SFX_COP_VOICE_1_GUNAIMEDAT2_1, + SFX_COP_VOICE_1_GUNAIMEDAT2_2, + SFX_COP_VOICE_1_SAVED_1, + SFX_COP_VOICE_1_SAVED_2, + SFX_COP_VOICE_1_COP_ASK_FOR_ID_1, + SFX_COP_VOICE_1_COP_ASK_FOR_ID_2, + SFX_COP_VOICE_1_COP_ALONE_1, + SFX_COP_VOICE_1_COP_ALONE_2, + SFX_COP_VOICE_1_COP_ALONE_3, + SFX_COP_VOICE_1_COP_ALONE_4, + SFX_COP_VOICE_1_COP_MANYCOPSAROUND_1, + SFX_COP_VOICE_1_COP_MANYCOPSAROUND_2, + SFX_COP_VOICE_1_COP_TARGETING_1, + SFX_COP_VOICE_1_COP_TARGETING_2, + SFX_COP_VOICE_1_COP_TARGETING_3, + SFX_COP_VOICE_1_COP_TARGETING_4, + + 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_PULLOUTWEAPON_1, + SFX_COP_VOICE_2_PULLOUTWEAPON_2, + SFX_COP_VOICE_2_PULLOUTWEAPON_3, + SFX_COP_VOICE_2_BUMP_1, + SFX_COP_VOICE_2_BUMP_2, + SFX_COP_VOICE_2_BUMP_3, + SFX_COP_VOICE_2_BUMP_4, + SFX_COP_VOICE_2_BUMP_5, + SFX_COP_VOICE_2_COP_LITTLECOPSAROUND_1, + SFX_COP_VOICE_2_COP_LITTLECOPSAROUND_2, + SFX_COP_VOICE_2_COP_LITTLECOPSAROUND_3, + SFX_COP_VOICE_2_COP_LITTLECOPSAROUND_4, + SFX_COP_VOICE_2_GUNAIMEDAT3_1, + SFX_COP_VOICE_2_GUNAIMEDAT3_2, + SFX_COP_VOICE_2_CAR_CRASH_1, + SFX_COP_VOICE_2_CAR_CRASH_2, + SFX_COP_VOICE_2_CAR_CRASH_3, + SFX_COP_VOICE_2_CAR_CRASH_4, + SFX_COP_VOICE_2_DODGE_1, + SFX_COP_VOICE_2_DODGE_2, + SFX_COP_VOICE_2_DODGE_3, + SFX_COP_VOICE_2_FIGHT_1, + SFX_COP_VOICE_2_FIGHT_2, + SFX_COP_VOICE_2_FIGHT_3, + SFX_COP_VOICE_2_FIGHT_4, + SFX_COP_VOICE_2_GUNAIMEDAT2_1, + SFX_COP_VOICE_2_GUNAIMEDAT2_2, + SFX_COP_VOICE_2_SAVED_1, + SFX_COP_VOICE_2_SAVED_2, + SFX_COP_VOICE_2_COP_ASK_FOR_ID_1, + SFX_COP_VOICE_2_COP_ASK_FOR_ID_2, + SFX_COP_VOICE_2_COP_ALONE_1, + SFX_COP_VOICE_2_COP_ALONE_2, + SFX_COP_VOICE_2_COP_ALONE_3, + SFX_COP_VOICE_2_COP_ALONE_4, + SFX_COP_VOICE_2_COP_MANYCOPSAROUND_1, + SFX_COP_VOICE_2_COP_MANYCOPSAROUND_2, + SFX_COP_VOICE_2_COP_TARGETING_1, + SFX_COP_VOICE_2_COP_TARGETING_2, + SFX_COP_VOICE_2_COP_TARGETING_3, + SFX_COP_VOICE_2_COP_TARGETING_4, + + 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_PULLOUTWEAPON_1, + SFX_COP_VOICE_3_PULLOUTWEAPON_2, + SFX_COP_VOICE_3_PULLOUTWEAPON_3, + SFX_COP_VOICE_3_BUMP_1, + SFX_COP_VOICE_3_BUMP_2, + SFX_COP_VOICE_3_BUMP_3, + SFX_COP_VOICE_3_BUMP_4, + SFX_COP_VOICE_3_BUMP_5, + SFX_COP_VOICE_3_COP_LITTLECOPSAROUND_1, + SFX_COP_VOICE_3_COP_LITTLECOPSAROUND_2, + SFX_COP_VOICE_3_COP_LITTLECOPSAROUND_3, + SFX_COP_VOICE_3_COP_LITTLECOPSAROUND_4, + SFX_COP_VOICE_3_GUNAIMEDAT3_1, + SFX_COP_VOICE_3_GUNAIMEDAT3_2, + SFX_COP_VOICE_3_CAR_CRASH_1, + SFX_COP_VOICE_3_CAR_CRASH_2, + SFX_COP_VOICE_3_CAR_CRASH_3, + SFX_COP_VOICE_3_CAR_CRASH_4, + SFX_COP_VOICE_3_DODGE_1, + SFX_COP_VOICE_3_DODGE_2, + SFX_COP_VOICE_3_DODGE_3, + SFX_COP_VOICE_3_FIGHT_1, + SFX_COP_VOICE_3_FIGHT_2, + SFX_COP_VOICE_3_FIGHT_3, + SFX_COP_VOICE_3_FIGHT_4, + SFX_COP_VOICE_3_GUNAIMEDAT2_1, + SFX_COP_VOICE_3_GUNAIMEDAT2_2, + SFX_COP_VOICE_3_SAVED_1, + SFX_COP_VOICE_3_SAVED_2, + SFX_COP_VOICE_3_COP_ASK_FOR_ID_1, + SFX_COP_VOICE_3_COP_ASK_FOR_ID_2, + SFX_COP_VOICE_3_COP_ALONE_1, + SFX_COP_VOICE_3_COP_ALONE_2, + SFX_COP_VOICE_3_COP_ALONE_3, + SFX_COP_VOICE_3_COP_ALONE_4, + SFX_COP_VOICE_3_COP_MANYCOPSAROUND_1, + SFX_COP_VOICE_3_COP_MANYCOPSAROUND_2, + SFX_COP_VOICE_3_COP_TARGETING_1, + SFX_COP_VOICE_3_COP_TARGETING_2, + SFX_COP_VOICE_3_COP_TARGETING_3, + SFX_COP_VOICE_3_COP_TARGETING_4, + + 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_PULLOUTWEAPON_1, + SFX_COP_VOICE_4_PULLOUTWEAPON_2, + SFX_COP_VOICE_4_PULLOUTWEAPON_3, + SFX_COP_VOICE_4_BUMP_1, + SFX_COP_VOICE_4_BUMP_2, + SFX_COP_VOICE_4_BUMP_3, + SFX_COP_VOICE_4_BUMP_4, + SFX_COP_VOICE_4_BUMP_5, + SFX_COP_VOICE_4_COP_LITTLECOPSAROUND_1, + SFX_COP_VOICE_4_COP_LITTLECOPSAROUND_2, + SFX_COP_VOICE_4_COP_LITTLECOPSAROUND_3, + SFX_COP_VOICE_4_COP_LITTLECOPSAROUND_4, + SFX_COP_VOICE_4_GUNAIMEDAT3_1, + SFX_COP_VOICE_4_GUNAIMEDAT3_2, + SFX_COP_VOICE_4_CAR_CRASH_1, + SFX_COP_VOICE_4_CAR_CRASH_2, + SFX_COP_VOICE_4_CAR_CRASH_3, + SFX_COP_VOICE_4_CAR_CRASH_4, + SFX_COP_VOICE_4_DODGE_1, + SFX_COP_VOICE_4_DODGE_2, + SFX_COP_VOICE_4_DODGE_3, + SFX_COP_VOICE_4_FIGHT_1, + SFX_COP_VOICE_4_FIGHT_2, + SFX_COP_VOICE_4_FIGHT_3, + SFX_COP_VOICE_4_FIGHT_4, + SFX_COP_VOICE_4_GUNAIMEDAT2_1, + SFX_COP_VOICE_4_GUNAIMEDAT2_2, + SFX_COP_VOICE_4_SAVED_1, + SFX_COP_VOICE_4_SAVED_2, + SFX_COP_VOICE_4_COP_ASK_FOR_ID_1, + SFX_COP_VOICE_4_COP_ASK_FOR_ID_2, + SFX_COP_VOICE_4_COP_ALONE_1, + SFX_COP_VOICE_4_COP_ALONE_2, + SFX_COP_VOICE_4_COP_ALONE_3, + SFX_COP_VOICE_4_COP_ALONE_4, + SFX_COP_VOICE_4_COP_MANYCOPSAROUND_1, + SFX_COP_VOICE_4_COP_MANYCOPSAROUND_2, + SFX_COP_VOICE_4_COP_TARGETING_1, + SFX_COP_VOICE_4_COP_TARGETING_2, + SFX_COP_VOICE_4_COP_TARGETING_3, + SFX_COP_VOICE_4_COP_TARGETING_4, + + 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_PULLOUTWEAPON_1, + SFX_COP_VOICE_5_PULLOUTWEAPON_2, + SFX_COP_VOICE_5_PULLOUTWEAPON_3, + SFX_COP_VOICE_5_BUMP_1, + SFX_COP_VOICE_5_BUMP_2, + SFX_COP_VOICE_5_BUMP_3, + SFX_COP_VOICE_5_BUMP_4, + SFX_COP_VOICE_5_BUMP_5, + SFX_COP_VOICE_5_COP_LITTLECOPSAROUND_1, + SFX_COP_VOICE_5_COP_LITTLECOPSAROUND_2, + SFX_COP_VOICE_5_COP_LITTLECOPSAROUND_3, + SFX_COP_VOICE_5_COP_LITTLECOPSAROUND_4, + SFX_COP_VOICE_5_GUNAIMEDAT3_1, + SFX_COP_VOICE_5_GUNAIMEDAT3_2, + SFX_COP_VOICE_5_CAR_CRASH_1, + SFX_COP_VOICE_5_CAR_CRASH_2, + SFX_COP_VOICE_5_CAR_CRASH_3, + SFX_COP_VOICE_5_CAR_CRASH_4, + SFX_COP_VOICE_5_DODGE_1, + SFX_COP_VOICE_5_DODGE_2, + SFX_COP_VOICE_5_DODGE_3, + SFX_COP_VOICE_5_FIGHT_1, + SFX_COP_VOICE_5_FIGHT_2, + SFX_COP_VOICE_5_FIGHT_3, + SFX_COP_VOICE_5_FIGHT_4, + SFX_COP_VOICE_5_GUNAIMEDAT2_1, + SFX_COP_VOICE_5_GUNAIMEDAT2_2, + SFX_COP_VOICE_5_SAVED_1, + SFX_COP_VOICE_5_SAVED_2, + SFX_COP_VOICE_5_COP_ASK_FOR_ID_1, + SFX_COP_VOICE_5_COP_ASK_FOR_ID_2, + SFX_COP_VOICE_5_COP_ALONE_1, + SFX_COP_VOICE_5_COP_ALONE_2, + SFX_COP_VOICE_5_COP_ALONE_3, + SFX_COP_VOICE_5_COP_ALONE_4, + SFX_COP_VOICE_5_COP_MANYCOPSAROUND_1, + SFX_COP_VOICE_5_COP_MANYCOPSAROUND_2, + SFX_COP_VOICE_5_COP_TARGETING_1, + SFX_COP_VOICE_5_COP_TARGETING_2, + SFX_COP_VOICE_5_COP_TARGETING_3, + SFX_COP_VOICE_5_COP_TARGETING_4, + + FIRST_PLAYER_COMMENT(SFX_PLAYER_ANGRY_BUSTED_1), + SFX_PLAYER_ANGRY_BUSTED_2, + SFX_PLAYER_ANGRY_BUSTED_3, + SFX_PLAYER_ANGRY_BUSTED_4, + SFX_PLAYER_ANGRY_BUSTED_5, + SFX_PLAYER_ANGRY_BUSTED_6, + SFX_PLAYER_ANGRY_BUSTED_7, + SFX_PLAYER_ANGRY_BUSTED_8, + SFX_PLAYER_ANGRY_BUSTED_9, + SFX_PLAYER_ANGRY_BUSTED_10, + SFX_PLAYER_ANGRY_BUSTED_11, + SFX_PLAYER_ANGRY_BUSTED_12, + SFX_PLAYER_ANGRY_BUSTED_13, + SFX_PLAYER_ANGRY_BUSTED_14, + SFX_PLAYER_ANGRY_BUSTED_15, + SFX_PLAYER_ANGRY_BUSTED_16, + SFX_PLAYER_ANGRY_BUSTED_17, + SFX_PLAYER_ANGRY_BUSTED_18, + SFX_PLAYER_ANGRY_BUSTED_19, + SFX_PLAYER_ANGRY_BUSTED_20, + SFX_PLAYER_ANGRY_BUSTED_21, + SFX_PLAYER_ANGRY_BUSTED_22, + SFX_PLAYER_ANGRY_BUSTED_23, + SFX_PLAYER_ANGRY_BUSTED_24, + SFX_PLAYER_ANGRY_BUSTED_25, + SFX_PLAYER_ANGRY_BUSTED_26, + SFX_PLAYER_ANGRY_BUSTED_27, + SFX_PLAYER_ANGRY_BUSTED_28, + SFX_PLAYER_ANGRY_BUSTED_29, + SFX_PLAYER_ANGRY_BUSTED_30, + SFX_PLAYER_ANGRY_BUSTED_31, + SFX_PLAYER_ANGRY_BUSTED_32, + SFX_PLAYER_ANGRY_BUSTED_33, + SFX_PLAYER_ANGRY_BUSTED_34, + SFX_PLAYER_ANGRY_BUSTED_35, + SFX_PLAYER_ANGRY_BUSTED_36, + SFX_PLAYER_ANGRY_BUSTED_37, + SFX_PLAYER_ANGRY_BUSTED_38, + SFX_PLAYER_ANGRY_CHASED_1, + SFX_PLAYER_ANGRY_CHASED_2, + SFX_PLAYER_ANGRY_CHASED_3, + SFX_PLAYER_ANGRY_CHASED_4, + SFX_PLAYER_ANGRY_CHASED_5, + SFX_PLAYER_ANGRY_CHASED_6, + SFX_PLAYER_ANGRY_CHASED_7, + SFX_PLAYER_ANGRY_CHASED_8, + SFX_PLAYER_ANGRY_CHASED_9, + SFX_PLAYER_ANGRY_CRASH_1, + SFX_PLAYER_ANGRY_CRASH_2, + SFX_PLAYER_ANGRY_CRASH_3, + SFX_PLAYER_ANGRY_CRASH_4, + SFX_PLAYER_ANGRY_CRASH_5, + SFX_PLAYER_ANGRY_CRASH_6, + SFX_PLAYER_ANGRY_CRASH_7, + SFX_PLAYER_ANGRY_CRASH_8, + SFX_PLAYER_ANGRY_CRASH_9, + SFX_PLAYER_ANGRY_CRASH_10, + SFX_PLAYER_ANGRY_CRASH_11, + SFX_PLAYER_ANGRY_CRASH_12, + SFX_PLAYER_ANGRY_CRASH_13, + SFX_PLAYER_ANGRY_CRASH_14, + SFX_PLAYER_ANGRY_CRASH_15, + SFX_PLAYER_ANGRY_CRASH_16, + SFX_PLAYER_ANGRY_CRASH_17, + SFX_PLAYER_ANGRY_CRASH_18, + SFX_PLAYER_ANGRY_CRASH_19, + SFX_PLAYER_ANGRY_CRASH_20, + SFX_PLAYER_ANGRY_CRASH_21, + SFX_PLAYER_ANGRY_CRASH_22, + SFX_PLAYER_ANGRY_CRASH_23, + SFX_PLAYER_ANGRY_CRASH_24, + SFX_PLAYER_ANGRY_CRASH_25, + SFX_PLAYER_ANGRY_CRASH_26, + SFX_PLAYER_ANGRY_CRASH_27, + SFX_PLAYER_ANGRY_CRASH_28, + SFX_PLAYER_ANGRY_CRASH_29, + SFX_PLAYER_ANGRY_CRASH_30, + SFX_PLAYER_ANGRY_CRASH_31, + SFX_PLAYER_ANGRY_CRASH_32, + SFX_PLAYER_ANGRY_CRASH_33, + SFX_PLAYER_ANGRY_CRASH_34, + SFX_PLAYER_ANGRY_CRASH_35, + SFX_PLAYER_ANGRY_CRASH_36, + SFX_PLAYER_ANGRY_CRASH_37, + SFX_PLAYER_ANGRY_CRASH_38, + SFX_PLAYER_ANGRY_CRASH_39, + SFX_PLAYER_ANGRY_CRASH_40, + SFX_PLAYER_ANGRY_CRASH_41, + SFX_PLAYER_ANGRY_FIGHT_1, + SFX_PLAYER_ANGRY_FIGHT_2, + SFX_PLAYER_ANGRY_FIGHT_3, + SFX_PLAYER_ANGRY_FIGHT_4, + SFX_PLAYER_ANGRY_FIGHT_5, + SFX_PLAYER_ANGRY_FIGHT_6, + SFX_PLAYER_ANGRY_FIGHT_7, + SFX_PLAYER_ANGRY_FIGHT_8, + SFX_PLAYER_ANGRY_FIGHT_9, + SFX_PLAYER_ANGRY_FIGHT_10, + SFX_PLAYER_ANGRY_FIGHT_11, + SFX_PLAYER_ANGRY_FIGHT_12, + SFX_PLAYER_ANGRY_FIGHT_13, + SFX_PLAYER_ANGRY_FIGHT_14, + SFX_PLAYER_ANGRY_FIGHT_15, + SFX_PLAYER_ANGRY_FIGHT_16, + SFX_PLAYER_ANGRY_FIGHT_17, + SFX_PLAYER_ANGRY_FIGHT_18, + SFX_PLAYER_ANGRY_FIGHT_19, + SFX_PLAYER_ANGRY_FIGHT_20, + SFX_PLAYER_ANGRY_FIGHT_21, + SFX_PLAYER_ANGRY_FIGHT_22, + SFX_PLAYER_ANGRY_FIGHT_23, + SFX_PLAYER_ANGRY_FIGHT_24, + SFX_PLAYER_ANGRY_FIGHT_25, + SFX_PLAYER_ANGRY_FIGHT_26, + SFX_PLAYER_ANGRY_FIGHT_27, + SFX_PLAYER_ANGRY_FIGHT_28, + SFX_PLAYER_ANGRY_FIGHT_29, + SFX_PLAYER_ANGRY_FIGHT_30, + SFX_PLAYER_ANGRY_FIGHT_31, + SFX_PLAYER_ANGRY_FIGHT_32, + SFX_PLAYER_ANGRY_FIGHT_33, + SFX_PLAYER_ANGRY_FIGHT_34, + SFX_PLAYER_ANGRY_FIGHT_35, + SFX_PLAYER_ANGRY_FIGHT_36, + SFX_PLAYER_ANGRY_FIGHT_37, + SFX_PLAYER_ANGRY_FIGHT_38, + SFX_PLAYER_ANGRY_FIGHT_39, + SFX_PLAYER_ANGRY_FIGHT_40, + SFX_PLAYER_ANGRY_FIGHT_41, + SFX_PLAYER_ANGRY_FIGHT_42, + SFX_PLAYER_ANGRY_FIGHT_43, + SFX_PLAYER_ANGRY_FIGHT_44, + SFX_PLAYER_ANGRY_FIGHT_45, + SFX_PLAYER_ANGRY_FIGHT_46, + SFX_PLAYER_ANGRY_FIGHT_47, + SFX_PLAYER_ANGRY_FIGHT_48, + SFX_PLAYER_ANGRY_FIGHT_49, + SFX_PLAYER_ANGRY_FIGHT_50, + SFX_PLAYER_ANGRY_FIGHT_51, + SFX_PLAYER_ANGRY_FIGHT_52, + SFX_PLAYER_ANGRY_FIGHT_53, + SFX_PLAYER_ANGRY_FIGHT_54, + SFX_PLAYER_ANGRY_FIGHT_55, + SFX_PLAYER_ANGRY_FIGHT_56, + SFX_PLAYER_ANGRY_FIGHT_57, + SFX_PLAYER_ANGRY_FIGHT_58, + SFX_PLAYER_ANGRY_FIGHT_59, + SFX_PLAYER_ANGRY_FIGHT_60, + SFX_PLAYER_ANGRY_FIGHT_61, + SFX_PLAYER_ANGRY_JACKED_1, + SFX_PLAYER_ANGRY_JACKED_2, + SFX_PLAYER_ANGRY_JACKED_3, + SFX_PLAYER_ANGRY_JACKED_4, + SFX_PLAYER_ANGRY_JACKED_5, + SFX_PLAYER_ANGRY_JACKED_6, + SFX_PLAYER_ANGRY_JACKED_7, + SFX_PLAYER_ANGRY_JACKED_8, + SFX_PLAYER_ANGRY_JACKED_9, + SFX_PLAYER_ANGRY_JACKED_10, + SFX_PLAYER_ANGRY_JACKED_11, + SFX_PLAYER_ANGRY_JACKED_12, + SFX_PLAYER_ANGRY_JACKED_13, + SFX_PLAYER_ANGRY_JACKED_14, + SFX_PLAYER_ANGRY_JACKED_15, + SFX_PLAYER_ANGRY_JACKED_16, + SFX_PLAYER_ANGRY_JACKED_17, + SFX_PLAYER_ANGRY_JACKED_18, + SFX_PLAYER_ANGRY_JACKED_19, + SFX_PLAYER_ANGRY_JACKED_20, + SFX_PLAYER_ANGRY_JACKED_21, + SFX_PLAYER_ANGRY_JACKED_22, + SFX_PLAYER_ANGRY_JACKED_23, + SFX_PLAYER_ANGRY_JACKED_24, + SFX_PLAYER_ANGRY_JACKED_25, + SFX_PLAYER_ANGRY_JACKED_26, + SFX_PLAYER_ANGRY_JACKED_27, + SFX_PLAYER_ANGRY_JACKED_28, + SFX_PLAYER_ANGRY_JACKED_29, + SFX_PLAYER_ANGRY_JACKED_30, + SFX_PLAYER_ANGRY_JACKED_31, + SFX_PLAYER_ANGRY_JACKED_32, + SFX_PLAYER_ANGRY_JACKED_33, + SFX_PLAYER_ANGRY_JACKING_1, + SFX_PLAYER_ANGRY_JACKING_2, + SFX_PLAYER_ANGRY_JACKING_3, + SFX_PLAYER_ANGRY_JACKING_4, + SFX_PLAYER_ANGRY_JACKING_5, + SFX_PLAYER_ANGRY_JACKING_6, + SFX_PLAYER_ANGRY_JACKING_7, + SFX_PLAYER_ANGRY_JACKING_8, + SFX_PLAYER_ANGRY_JACKING_9, + SFX_PLAYER_ANGRY_JACKING_10, + SFX_PLAYER_ANGRY_JACKING_11, + SFX_PLAYER_ANGRY_JACKING_12, + SFX_PLAYER_ANGRY_JACKING_13, + SFX_PLAYER_ANGRY_JACKING_14, + SFX_PLAYER_ANGRY_JACKING_15, + SFX_PLAYER_ANGRY_JACKING_16, + SFX_PLAYER_ANGRY_JACKING_17, + SFX_PLAYER_ANGRY_JACKING_18, + SFX_PLAYER_ANGRY_JACKING_19, + SFX_PLAYER_ANGRY_JACKING_20, + SFX_PLAYER_ANGRY_JACKING_21, + SFX_PLAYER_ANGRY_JACKING_22, + SFX_PLAYER_ANGRY_JACKING_23, + SFX_PLAYER_ANGRY_JACKING_24, + SFX_PLAYER_ANGRY_JACKING_25, + SFX_PLAYER_ANGRY_JACKING_26, + SFX_PLAYER_ANGRY_JACKING_27, + SFX_PLAYER_ANGRY_JACKING_28, + SFX_PLAYER_ANGRY_JACKING_29, + SFX_PLAYER_ANGRY_JACKING_30, + SFX_PLAYER_ANGRY_JACKING_31, + SFX_PLAYER_ANGRY_JACKING_32, + SFX_PLAYER_ANGRY_JACKING_33, + SFX_PLAYER_ANGRY_JACKING_34, + SFX_PLAYER_ANGRY_JACKING_35, + SFX_PLAYER_ANGRY_JACKING_36, + SFX_PLAYER_ANGRY_JACKING_37, + SFX_PLAYER_ANGRY_JACKING_38, + SFX_PLAYER_ANGRY_JACKING_39, + SFX_PLAYER_ANGRY_JACKING_40, + SFX_PLAYER_ANGRY_JACKING_41, + SFX_PLAYER_ANGRY_JACKING_42, + SFX_PLAYER_ANGRY_JACKING_43, + SFX_PLAYER_ANGRY_PICK_UP_CASH_1, + SFX_PLAYER_ANGRY_PICK_UP_CASH_2, + SFX_PLAYER_ANGRY_PICK_UP_CASH_3, + SFX_PLAYER_ANGRY_PICK_UP_CASH_4, + SFX_PLAYER_ANGRY_PICK_UP_CASH_5, + SFX_PLAYER_ANGRY_PICK_UP_CASH_6, + SFX_PLAYER_ANGRY_PICK_UP_CASH_7, + SFX_PLAYER_ANGRY_PICK_UP_CASH_8, + SFX_PLAYER_ANGRY_PICK_UP_CASH_9, + SFX_PLAYER_ANGRY_PICK_UP_CASH_10, + SFX_PLAYER_ANGRY_PICK_UP_CASH_11, + SFX_PLAYER_ANGRY_PICK_UP_CASH_12, + SFX_PLAYER_ANGRY_PICK_UP_HOOKER_1, + SFX_PLAYER_ANGRY_PICK_UP_HOOKER_2, + SFX_PLAYER_ANGRY_PICK_UP_HOOKER_3, + SFX_PLAYER_ANGRY_PICK_UP_HOOKER_4, + SFX_PLAYER_ANGRY_PICK_UP_HOOKER_5, + SFX_PLAYER_ANGRY_PICK_UP_HOOKER_6, + SFX_PLAYER_ANGRY_PULL_GUN_1, + SFX_PLAYER_ANGRY_PULL_GUN_2, + SFX_PLAYER_ANGRY_PULL_GUN_3, + SFX_PLAYER_ANGRY_PULL_GUN_4, + SFX_PLAYER_ANGRY_PULL_GUN_5, + SFX_PLAYER_ANGRY_PULL_GUN_6, + SFX_PLAYER_ANGRY_PULL_GUN_7, + SFX_PLAYER_ANGRY_PULL_GUN_8, + SFX_PLAYER_ANGRY_PULL_GUN_9, + SFX_PLAYER_ANGRY_PULL_GUN_10, + SFX_PLAYER_ANGRY_PULL_GUN_11, + SFX_PLAYER_ANGRY_PULL_GUN_12, + SFX_PLAYER_ANGRY_PULL_GUN_13, + SFX_PLAYER_ANGRY_PULL_GUN_14, + SFX_PLAYER_ANGRY_PULL_GUN_15, + SFX_PLAYER_ANGRY_PULL_GUN_16, + SFX_PLAYER_ANGRY_PULL_GUN_17, + SFX_PLAYER_ANGRY_PULL_GUN_18, + SFX_PLAYER_ANGRY_PULL_GUN_19, + SFX_PLAYER_ANGRY_PULL_GUN_20, + SFX_PLAYER_ANGRY_PULL_GUN_21, + SFX_PLAYER_ANGRY_PULL_GUN_22, + SFX_PLAYER_ANGRY_PULL_GUN_23, + SFX_PLAYER_ANGRY_PULL_GUN_24, + SFX_PLAYER_ANGRY_PULL_GUN_25, + SFX_PLAYER_ANGRY_PULL_GUN_26, + SFX_PLAYER_ANGRY_PULL_GUN_27, + SFX_PLAYER_ANGRY_PULL_GUN_28, + SFX_PLAYER_ANGRY_PULL_GUN_29, + SFX_PLAYER_ANGRY_PULL_GUN_30, + SFX_PLAYER_ANGRY_PULL_GUN_31, + SFX_PLAYER_ANGRY_PULL_GUN_32, + SFX_PLAYER_ANGRY_PULL_GUN_33, + SFX_PLAYER_ANGRY_PULL_GUN_34, + SFX_PLAYER_ANGRY_PULL_GUN_35, + SFX_PLAYER_ANGRY_PULL_GUN_36, + SFX_PLAYER_ANGRY_PULL_GUN_37, + SFX_PLAYER_ANGRY_PULL_GUN_38, + SFX_PLAYER_ANGRY_PULL_GUN_39, + SFX_PLAYER_ANGRY_PULL_GUN_40, + SFX_PLAYER_ANGRY_PULL_GUN_41, + SFX_PLAYER_ANGRY_PULL_GUN_42, + SFX_PLAYER_ANGRY_PULL_GUN_43, + SFX_PLAYER_ANGRY_PULL_GUN_44, + SFX_PLAYER_ANGRY_PULL_GUN_45, + SFX_PLAYER_ANGRY_PULL_GUN_46, + SFX_PLAYER_ANGRY_PULL_GUN_47, + SFX_PLAYER_ANGRY_PULL_GUN_48, + SFX_PLAYER_ANGRY_PULL_GUN_49, + SFX_PLAYER_ANGRY_PULL_GUN_50, + SFX_PLAYER_ANGRY_PULL_GUN_51, + SFX_PLAYER_ANGRY_PULL_GUN_52, + SFX_PLAYER_ANGRY_SEX_1, + SFX_PLAYER_ANGRY_SEX_2, + SFX_PLAYER_ANGRY_SEX_3, + SFX_PLAYER_ANGRY_SEX_4, + SFX_PLAYER_ANGRY_SEX_5, + SFX_PLAYER_ANGRY_SEX_6, + SFX_PLAYER_ANGRY_SEX_7, + SFX_PLAYER_ANGRY_SEX_8, + SFX_PLAYER_ANGRY_SEX_9, + SFX_PLAYER_ANGRY_SEX_10, + SFX_PLAYER_ANGRY_SEX_11, + SFX_PLAYER_ANGRY_SEX_12, + SFX_PLAYER_ANGRY_SEX_13, + SFX_PLAYER_ANGRY_SEX_14, + SFX_PLAYER_ANGRY_SEX_15, + SFX_PLAYER_ANGRY_SEX_16, + SFX_PLAYER_ANGRY_SEX_17, + SFX_PLAYER_ANGRY_SEX_18, + SFX_PLAYER_ANGRY_SHOOT_1, + SFX_PLAYER_ANGRY_SHOOT_2, + SFX_PLAYER_ANGRY_SHOOT_3, + SFX_PLAYER_ANGRY_SHOOT_4, + SFX_PLAYER_ANGRY_SHOOT_5, + SFX_PLAYER_ANGRY_SHOOT_6, + SFX_PLAYER_ANGRY_SHOOT_7, + SFX_PLAYER_ANGRY_SHOOT_8, + SFX_PLAYER_ANGRY_SHOOT_9, + SFX_PLAYER_ANGRY_SHOOT_10, + SFX_PLAYER_ANGRY_SHOOT_11, + SFX_PLAYER_ANGRY_SHOOT_12, + SFX_PLAYER_ANGRY_SHOOT_13, + SFX_PLAYER_ANGRY_SHOOT_14, + SFX_PLAYER_ANGRY_SHOOT_15, + SFX_PLAYER_ANGRY_SHOOT_16, + SFX_PLAYER_ANGRY_SHOOT_17, + SFX_PLAYER_ANGRY_SHOOT_18, + SFX_PLAYER_ANGRY_SHOOT_19, + SFX_PLAYER_ANGRY_SHOOT_20, + SFX_PLAYER_ANGRY_SHOOT_21, + SFX_PLAYER_ANGRY_SHOOT_22, + SFX_PLAYER_ANGRY_SHOOT_23, + SFX_PLAYER_ANGRY_SHOOT_24, + SFX_PLAYER_ANGRY_SHOOT_25, + SFX_PLAYER_ANGRY_SHOOT_26, + SFX_PLAYER_ANGRY_SHOOT_27, + SFX_PLAYER_ANGRY_SHOOT_28, + SFX_PLAYER_ANGRY_SHOOT_29, + SFX_PLAYER_ANGRY_SHOOT_30, + SFX_PLAYER_ANGRY_SHOOT_31, + SFX_PLAYER_ANGRY_SHOOT_32, + SFX_PLAYER_ANGRY_SHOOT_33, + SFX_PLAYER_ANGRY_SHOOT_34, + SFX_PLAYER_ANGRY_SHOOT_35, + SFX_PLAYER_ANGRY_SHOOT_36, + SFX_PLAYER_ANGRY_SHOOT_37, + SFX_PLAYER_ANGRY_SHOOT_38, + SFX_PLAYER_ANGRY_SHOOT_39, + + SFX_PLAYER_CALM_BUSTED_1, + SFX_PLAYER_CALM_BUSTED_2, + SFX_PLAYER_CALM_BUSTED_3, + SFX_PLAYER_CALM_BUSTED_4, + SFX_PLAYER_CALM_BUSTED_5, + SFX_PLAYER_CALM_BUSTED_6, + SFX_PLAYER_CALM_BUSTED_7, + SFX_PLAYER_CALM_BUSTED_8, + SFX_PLAYER_CALM_BUSTED_9, + SFX_PLAYER_CALM_BUSTED_10, + SFX_PLAYER_CALM_BUSTED_11, + SFX_PLAYER_CALM_BUSTED_12, + SFX_PLAYER_CALM_BUSTED_13, + SFX_PLAYER_CALM_BUSTED_14, + SFX_PLAYER_CALM_BUSTED_15, + SFX_PLAYER_CALM_BUSTED_16, + SFX_PLAYER_CALM_BUSTED_17, + SFX_PLAYER_CALM_BUSTED_18, + SFX_PLAYER_CALM_BUSTED_19, + SFX_PLAYER_CALM_BUSTED_20, + SFX_PLAYER_CALM_BUSTED_21, + SFX_PLAYER_CALM_BUSTED_22, + SFX_PLAYER_CALM_CHASED_1, + SFX_PLAYER_CALM_CHASED_2, + SFX_PLAYER_CALM_CHASED_3, + SFX_PLAYER_CALM_CHASED_4, + SFX_PLAYER_CALM_CHASED_5, + SFX_PLAYER_CALM_CHASED_6, + SFX_PLAYER_CALM_CHASED_7, + SFX_PLAYER_CALM_CHASED_8, + SFX_PLAYER_CALM_CHASED_9, + SFX_PLAYER_CALM_CHASED_10, + SFX_PLAYER_CALM_CHASED_11, + SFX_PLAYER_CALM_CHASED_12, + SFX_PLAYER_CALM_CHASED_13, + SFX_PLAYER_CALM_CHASED_14, + SFX_PLAYER_CALM_CHASED_15, + SFX_PLAYER_CALM_CHASED_16, + SFX_PLAYER_CALM_CHASED_17, + SFX_PLAYER_CALM_CHASED_18, + SFX_PLAYER_CALM_CHASED_19, + SFX_PLAYER_CALM_CHASED_20, + SFX_PLAYER_CALM_CRASH_1, + SFX_PLAYER_CALM_CRASH_2, + SFX_PLAYER_CALM_CRASH_3, + SFX_PLAYER_CALM_CRASH_4, + SFX_PLAYER_CALM_CRASH_5, + SFX_PLAYER_CALM_CRASH_6, + SFX_PLAYER_CALM_CRASH_7, + SFX_PLAYER_CALM_CRASH_8, + SFX_PLAYER_CALM_CRASH_9, + SFX_PLAYER_CALM_CRASH_10, + SFX_PLAYER_CALM_CRASH_11, + SFX_PLAYER_CALM_CRASH_12, + SFX_PLAYER_CALM_CRASH_13, + SFX_PLAYER_CALM_CRASH_14, + SFX_PLAYER_CALM_CRASH_15, + SFX_PLAYER_CALM_CRASH_16, + SFX_PLAYER_CALM_CRASH_17, + SFX_PLAYER_CALM_CRASH_18, + SFX_PLAYER_CALM_CRASH_19, + SFX_PLAYER_CALM_CRASH_20, + SFX_PLAYER_CALM_CRASH_21, + SFX_PLAYER_CALM_CRASH_22, + SFX_PLAYER_CALM_CRASH_23, + SFX_PLAYER_CALM_CRASH_24, + SFX_PLAYER_CALM_CRASH_25, + SFX_PLAYER_CALM_CRASH_26, + SFX_PLAYER_CALM_CRASH_27, + SFX_PLAYER_CALM_CRASH_28, + SFX_PLAYER_CALM_CRASH_29, + SFX_PLAYER_CALM_CRASH_30, + SFX_PLAYER_CALM_CRASH_31, + SFX_PLAYER_CALM_CRASH_32, + SFX_PLAYER_CALM_CRASH_33, + SFX_PLAYER_CALM_CRASH_34, + SFX_PLAYER_CALM_CRASH_35, + SFX_PLAYER_CALM_CRASH_36, + SFX_PLAYER_CALM_CRASH_37, + SFX_PLAYER_CALM_CRASH_38, + SFX_PLAYER_CALM_CRASH_39, + SFX_PLAYER_CALM_CRASH_40, + SFX_PLAYER_CALM_CRASH_41, + SFX_PLAYER_CALM_CRASH_42, + SFX_PLAYER_CALM_CRASH_43, + SFX_PLAYER_CALM_FIGHT_1, + SFX_PLAYER_CALM_FIGHT_2, + SFX_PLAYER_CALM_FIGHT_3, + SFX_PLAYER_CALM_FIGHT_4, + SFX_PLAYER_CALM_FIGHT_5, + SFX_PLAYER_CALM_FIGHT_6, + SFX_PLAYER_CALM_FIGHT_7, + SFX_PLAYER_CALM_FIGHT_8, + SFX_PLAYER_CALM_FIGHT_9, + SFX_PLAYER_CALM_FIGHT_10, + SFX_PLAYER_CALM_FIGHT_11, + SFX_PLAYER_CALM_FIGHT_12, + SFX_PLAYER_CALM_FIGHT_13, + SFX_PLAYER_CALM_FIGHT_14, + SFX_PLAYER_CALM_FIGHT_15, + SFX_PLAYER_CALM_FIGHT_16, + SFX_PLAYER_CALM_FIGHT_17, + SFX_PLAYER_CALM_FIGHT_18, + SFX_PLAYER_CALM_FIGHT_19, + SFX_PLAYER_CALM_FIGHT_20, + SFX_PLAYER_CALM_FIGHT_21, + SFX_PLAYER_CALM_FIGHT_22, + SFX_PLAYER_CALM_FIGHT_23, + SFX_PLAYER_CALM_FIGHT_24, + SFX_PLAYER_CALM_FIGHT_25, + SFX_PLAYER_CALM_FIGHT_26, + SFX_PLAYER_CALM_FIGHT_27, + SFX_PLAYER_CALM_FIGHT_28, + SFX_PLAYER_CALM_FIGHT_29, + SFX_PLAYER_CALM_FIGHT_30, + SFX_PLAYER_CALM_FIGHT_31, + SFX_PLAYER_CALM_FIGHT_32, + SFX_PLAYER_CALM_FIGHT_33, + SFX_PLAYER_CALM_FIGHT_34, + SFX_PLAYER_CALM_FIGHT_35, + SFX_PLAYER_CALM_FIGHT_36, + SFX_PLAYER_CALM_FIGHT_37, + SFX_PLAYER_CALM_FIGHT_38, + SFX_PLAYER_CALM_FIGHT_39, + SFX_PLAYER_CALM_FIGHT_40, + SFX_PLAYER_CALM_FIGHT_41, + SFX_PLAYER_CALM_FIGHT_42, + SFX_PLAYER_CALM_FIGHT_43, + SFX_PLAYER_CALM_FIGHT_44, + SFX_PLAYER_CALM_FIGHT_45, + SFX_PLAYER_CALM_FIGHT_46, + SFX_PLAYER_CALM_FIGHT_47, + SFX_PLAYER_CALM_JACKED_1, + SFX_PLAYER_CALM_JACKED_2, + SFX_PLAYER_CALM_JACKED_3, + SFX_PLAYER_CALM_JACKED_4, + SFX_PLAYER_CALM_JACKED_5, + SFX_PLAYER_CALM_JACKED_6, + SFX_PLAYER_CALM_JACKED_7, + SFX_PLAYER_CALM_JACKED_8, + SFX_PLAYER_CALM_JACKED_9, + SFX_PLAYER_CALM_JACKED_10, + SFX_PLAYER_CALM_JACKED_11, + SFX_PLAYER_CALM_JACKED_12, + SFX_PLAYER_CALM_JACKED_13, + SFX_PLAYER_CALM_JACKED_14, + SFX_PLAYER_CALM_JACKED_15, + SFX_PLAYER_CALM_JACKED_16, + SFX_PLAYER_CALM_JACKED_17, + SFX_PLAYER_CALM_JACKED_18, + SFX_PLAYER_CALM_JACKED_19, + SFX_PLAYER_CALM_JACKED_20, + SFX_PLAYER_CALM_JACKED_21, + SFX_PLAYER_CALM_JACKED_22, + SFX_PLAYER_CALM_JACKED_23, + SFX_PLAYER_CALM_JACKED_24, + SFX_PLAYER_CALM_JACKING_1, + SFX_PLAYER_CALM_JACKING_2, + SFX_PLAYER_CALM_JACKING_3, + SFX_PLAYER_CALM_JACKING_4, + SFX_PLAYER_CALM_JACKING_5, + SFX_PLAYER_CALM_JACKING_6, + SFX_PLAYER_CALM_JACKING_7, + SFX_PLAYER_CALM_JACKING_8, + SFX_PLAYER_CALM_JACKING_9, + SFX_PLAYER_CALM_JACKING_10, + SFX_PLAYER_CALM_JACKING_11, + SFX_PLAYER_CALM_JACKING_12, + SFX_PLAYER_CALM_JACKING_13, + SFX_PLAYER_CALM_JACKING_14, + SFX_PLAYER_CALM_JACKING_15, + SFX_PLAYER_CALM_JACKING_16, + SFX_PLAYER_CALM_JACKING_17, + SFX_PLAYER_CALM_JACKING_18, + SFX_PLAYER_CALM_JACKING_19, + SFX_PLAYER_CALM_JACKING_20, + SFX_PLAYER_CALM_JACKING_21, + SFX_PLAYER_CALM_JACKING_22, + SFX_PLAYER_CALM_JACKING_23, + SFX_PLAYER_CALM_JACKING_24, + SFX_PLAYER_CALM_JACKING_25, + SFX_PLAYER_CALM_JACKING_26, + SFX_PLAYER_CALM_JACKING_27, + SFX_PLAYER_CALM_JACKING_28, + SFX_PLAYER_CALM_JACKING_29, + SFX_PLAYER_CALM_JACKING_30, + SFX_PLAYER_CALM_JACKING_31, + SFX_PLAYER_CALM_JACKING_32, + SFX_PLAYER_CALM_JACKING_33, + SFX_PLAYER_CALM_JACKING_34, + SFX_PLAYER_CALM_JACKING_35, + SFX_PLAYER_CALM_JACKING_36, + SFX_PLAYER_CALM_JACKING_37, + SFX_PLAYER_CALM_JACKING_38, + SFX_PLAYER_CALM_JACKING_39, + SFX_PLAYER_CALM_JACKING_40, + SFX_PLAYER_CALM_PICK_UP_CASH_1, + SFX_PLAYER_CALM_PICK_UP_CASH_2, + SFX_PLAYER_CALM_PICK_UP_CASH_3, + SFX_PLAYER_CALM_PICK_UP_CASH_4, + SFX_PLAYER_CALM_PICK_UP_CASH_5, + SFX_PLAYER_CALM_PICK_UP_CASH_6, + SFX_PLAYER_CALM_PICK_UP_CASH_7, + SFX_PLAYER_CALM_PICK_UP_CASH_8, + SFX_PLAYER_CALM_PICK_UP_CASH_9, + SFX_PLAYER_CALM_PICK_UP_CASH_10, + SFX_PLAYER_CALM_PICK_UP_CASH_11, + SFX_PLAYER_CALM_PICK_UP_HOOKER_1, + SFX_PLAYER_CALM_PICK_UP_HOOKER_2, + SFX_PLAYER_CALM_PICK_UP_HOOKER_3, + SFX_PLAYER_CALM_PICK_UP_HOOKER_4, + SFX_PLAYER_CALM_PICK_UP_HOOKER_5, + SFX_PLAYER_CALM_PICK_UP_HOOKER_6, + SFX_PLAYER_CALM_PICK_UP_HOOKER_7, + SFX_PLAYER_CALM_PICK_UP_HOOKER_8, + SFX_PLAYER_CALM_PICK_UP_HOOKER_9, + SFX_PLAYER_CALM_PICK_UP_HOOKER_10, + SFX_PLAYER_CALM_PICK_UP_HOOKER_11, + SFX_PLAYER_CALM_PICK_UP_HOOKER_12, + SFX_PLAYER_CALM_PICK_UP_HOOKER_13, + SFX_PLAYER_CALM_PICK_UP_HOOKER_14, + SFX_PLAYER_CALM_PICK_UP_HOOKER_15, + SFX_PLAYER_CALM_PICK_UP_HOOKER_16, + SFX_PLAYER_CALM_PICK_UP_HOOKER_17, + SFX_PLAYER_CALM_PICK_UP_HOOKER_18, + SFX_PLAYER_CALM_PICK_UP_HOOKER_19, + SFX_PLAYER_CALM_PICK_UP_HOOKER_20, + SFX_PLAYER_CALM_PICK_UP_HOOKER_21, + SFX_PLAYER_CALM_PICK_UP_HOOKER_22, + SFX_PLAYER_CALM_PULL_GUN_1, + SFX_PLAYER_CALM_PULL_GUN_2, + SFX_PLAYER_CALM_PULL_GUN_3, + SFX_PLAYER_CALM_PULL_GUN_4, + SFX_PLAYER_CALM_PULL_GUN_5, + SFX_PLAYER_CALM_PULL_GUN_6, + SFX_PLAYER_CALM_PULL_GUN_7, + SFX_PLAYER_CALM_PULL_GUN_8, + SFX_PLAYER_CALM_PULL_GUN_9, + SFX_PLAYER_CALM_PULL_GUN_10, + SFX_PLAYER_CALM_PULL_GUN_11, + SFX_PLAYER_CALM_PULL_GUN_12, + SFX_PLAYER_CALM_PULL_GUN_13, + SFX_PLAYER_CALM_PULL_GUN_14, + SFX_PLAYER_CALM_PULL_GUN_15, + SFX_PLAYER_CALM_PULL_GUN_16, + SFX_PLAYER_CALM_PULL_GUN_17, + SFX_PLAYER_CALM_PULL_GUN_18, + SFX_PLAYER_CALM_PULL_GUN_19, + SFX_PLAYER_CALM_PULL_GUN_20, + SFX_PLAYER_CALM_PULL_GUN_21, + SFX_PLAYER_CALM_PULL_GUN_22, + SFX_PLAYER_CALM_PULL_GUN_23, + SFX_PLAYER_CALM_PULL_GUN_24, + SFX_PLAYER_CALM_PULL_GUN_25, + SFX_PLAYER_CALM_PULL_GUN_26, + SFX_PLAYER_CALM_PULL_GUN_27, + SFX_PLAYER_CALM_PULL_GUN_28, + SFX_PLAYER_CALM_PULL_GUN_29, + SFX_PLAYER_CALM_PULL_GUN_30, + SFX_PLAYER_CALM_PULL_GUN_31, + SFX_PLAYER_CALM_PULL_GUN_32, + SFX_PLAYER_CALM_PULL_GUN_33, + SFX_PLAYER_CALM_PULL_GUN_34, + SFX_PLAYER_CALM_PULL_GUN_35, + SFX_PLAYER_CALM_PULL_GUN_36, + SFX_PLAYER_CALM_PULL_GUN_37, + SFX_PLAYER_CALM_PULL_GUN_38, + SFX_PLAYER_CALM_PULL_GUN_39, + SFX_PLAYER_CALM_SEX_1, + SFX_PLAYER_CALM_SEX_2, + SFX_PLAYER_CALM_SEX_3, + SFX_PLAYER_CALM_SEX_4, + SFX_PLAYER_CALM_SEX_5, + SFX_PLAYER_CALM_SEX_6, + SFX_PLAYER_CALM_SEX_7, + SFX_PLAYER_CALM_SEX_8, + SFX_PLAYER_CALM_SHOOT_1, + SFX_PLAYER_CALM_SHOOT_2, + SFX_PLAYER_CALM_SHOOT_3, + SFX_PLAYER_CALM_SHOOT_4, + SFX_PLAYER_CALM_SHOOT_5, + SFX_PLAYER_CALM_SHOOT_6, + SFX_PLAYER_CALM_SHOOT_7, + SFX_PLAYER_CALM_SHOOT_8, + SFX_PLAYER_CALM_SHOOT_9, + SFX_PLAYER_CALM_SHOOT_10, + SFX_PLAYER_CALM_SHOOT_11, + SFX_PLAYER_CALM_SHOOT_12, + SFX_PLAYER_CALM_SHOOT_13, + SFX_PLAYER_CALM_SHOOT_14, + SFX_PLAYER_CALM_SHOOT_15, + SFX_PLAYER_CALM_SHOOT_16, + SFX_PLAYER_CALM_SHOOT_17, + SFX_PLAYER_CALM_SHOOT_18, + SFX_PLAYER_CALM_SHOOT_19, + SFX_PLAYER_CALM_SHOOT_20, + SFX_PLAYER_CALM_SHOOT_21, + SFX_PLAYER_CALM_SHOOT_22, + SFX_PLAYER_CALM_SHOOT_23, + SFX_PLAYER_CALM_SHOOT_24, + SFX_PLAYER_CALM_SHOOT_25, + SFX_PLAYER_CALM_SHOOT_26, + SFX_PLAYER_CALM_SHOOT_27, + SFX_PLAYER_CALM_SHOOT_28, + SFX_PLAYER_CALM_SHOOT_29, + SFX_PLAYER_CALM_SHOOT_30, + SFX_PLAYER_CALM_SHOOT_31, + SFX_PLAYER_CALM_SHOOT_32, + SFX_PLAYER_CALM_SHOOT_33, + SFX_PLAYER_CALM_SHOOT_34, + SFX_PLAYER_CALM_SHOOT_35, + + SFX_PLAYER_PISSED_OFF_CRASH_1, + SFX_PLAYER_PISSED_OFF_CRASH_2, + SFX_PLAYER_PISSED_OFF_CRASH_3, + SFX_PLAYER_PISSED_OFF_CRASH_4, + SFX_PLAYER_PISSED_OFF_CRASH_5, + SFX_PLAYER_PISSED_OFF_CRASH_6, + SFX_PLAYER_PISSED_OFF_CRASH_7, + SFX_PLAYER_PISSED_OFF_CRASH_8, + SFX_PLAYER_PISSED_OFF_CRASH_9, + SFX_PLAYER_PISSED_OFF_CRASH_10, + SFX_PLAYER_PISSED_OFF_CRASH_11, + SFX_PLAYER_PISSED_OFF_CRASH_12, + SFX_PLAYER_PISSED_OFF_CRASH_13, + SFX_PLAYER_PISSED_OFF_CRASH_14, + SFX_PLAYER_PISSED_OFF_CRASH_15, + SFX_PLAYER_PISSED_OFF_CRASH_16, + SFX_PLAYER_PISSED_OFF_CRASH_17, + SFX_PLAYER_PISSED_OFF_CRASH_18, + SFX_PLAYER_PISSED_OFF_CRASH_19, + SFX_PLAYER_PISSED_OFF_CRASH_20, + SFX_PLAYER_PISSED_OFF_CRASH_21, + SFX_PLAYER_PISSED_OFF_CRASH_22, + SFX_PLAYER_PISSED_OFF_CRASH_23, + SFX_PLAYER_PISSED_OFF_CRASH_24, + SFX_PLAYER_PISSED_OFF_CRASH_25, + SFX_PLAYER_PISSED_OFF_CRASH_26, + SFX_PLAYER_PISSED_OFF_CRASH_27, + SFX_PLAYER_PISSED_OFF_CRASH_28, + SFX_PLAYER_PISSED_OFF_CRASH_29, + SFX_PLAYER_PISSED_OFF_CRASH_30, + SFX_PLAYER_PISSED_OFF_CRASH_31, + SFX_PLAYER_PISSED_OFF_CRASH_32, + SFX_PLAYER_PISSED_OFF_CRASH_33, + SFX_PLAYER_PISSED_OFF_CRASH_34, + SFX_PLAYER_PISSED_OFF_CRASH_35, + SFX_PLAYER_PISSED_OFF_CRASH_36, + SFX_PLAYER_PISSED_OFF_CRASH_37, + SFX_PLAYER_PISSED_OFF_CRASH_38, + SFX_PLAYER_PISSED_OFF_CRASH_39, + SFX_PLAYER_PISSED_OFF_CRASH_40, + SFX_PLAYER_PISSED_OFF_CRASH_41, + SFX_PLAYER_PISSED_OFF_CRASH_42, + SFX_PLAYER_PISSED_OFF_CRASH_43, + SFX_PLAYER_PISSED_OFF_CRASH_44, + SFX_PLAYER_PISSED_OFF_FIGHT_1, + SFX_PLAYER_PISSED_OFF_FIGHT_2, + SFX_PLAYER_PISSED_OFF_FIGHT_3, + SFX_PLAYER_PISSED_OFF_FIGHT_4, + SFX_PLAYER_PISSED_OFF_FIGHT_5, + SFX_PLAYER_PISSED_OFF_FIGHT_6, + SFX_PLAYER_PISSED_OFF_FIGHT_7, + SFX_PLAYER_PISSED_OFF_FIGHT_8, + SFX_PLAYER_PISSED_OFF_FIGHT_9, + SFX_PLAYER_PISSED_OFF_FIGHT_10, + SFX_PLAYER_PISSED_OFF_FIGHT_11, + SFX_PLAYER_PISSED_OFF_FIGHT_12, + SFX_PLAYER_PISSED_OFF_FIGHT_13, + SFX_PLAYER_PISSED_OFF_FIGHT_14, + SFX_PLAYER_PISSED_OFF_FIGHT_15, + SFX_PLAYER_PISSED_OFF_FIGHT_16, + SFX_PLAYER_PISSED_OFF_FIGHT_17, + SFX_PLAYER_PISSED_OFF_FIGHT_18, + SFX_PLAYER_PISSED_OFF_FIGHT_19, + SFX_PLAYER_PISSED_OFF_FIGHT_20, + SFX_PLAYER_PISSED_OFF_FIGHT_21, + SFX_PLAYER_PISSED_OFF_FIGHT_22, + SFX_PLAYER_PISSED_OFF_FIGHT_23, + SFX_PLAYER_PISSED_OFF_FIGHT_24, + SFX_PLAYER_PISSED_OFF_FIGHT_25, + SFX_PLAYER_PISSED_OFF_FIGHT_26, + SFX_PLAYER_PISSED_OFF_FIGHT_27, + SFX_PLAYER_PISSED_OFF_FIGHT_28, + SFX_PLAYER_PISSED_OFF_FIGHT_29, + SFX_PLAYER_PISSED_OFF_FIGHT_30, + SFX_PLAYER_PISSED_OFF_FIGHT_31, + SFX_PLAYER_PISSED_OFF_FIGHT_32, + SFX_PLAYER_PISSED_OFF_FIGHT_33, + SFX_PLAYER_PISSED_OFF_FIGHT_34, + SFX_PLAYER_PISSED_OFF_FIGHT_35, + SFX_PLAYER_PISSED_OFF_FIGHT_36, + SFX_PLAYER_PISSED_OFF_FIGHT_37, + SFX_PLAYER_PISSED_OFF_FIGHT_38, + SFX_PLAYER_PISSED_OFF_FIGHT_39, + SFX_PLAYER_PISSED_OFF_FIGHT_40, + SFX_PLAYER_PISSED_OFF_FIGHT_41, + SFX_PLAYER_PISSED_OFF_FIGHT_42, + SFX_PLAYER_PISSED_OFF_FIGHT_43, + SFX_PLAYER_PISSED_OFF_FIGHT_44, + SFX_PLAYER_PISSED_OFF_FIGHT_45, + SFX_PLAYER_PISSED_OFF_FIGHT_46, + SFX_PLAYER_PISSED_OFF_FIGHT_47, + SFX_PLAYER_PISSED_OFF_FIGHT_48, + SFX_PLAYER_PISSED_OFF_FIGHT_49, + SFX_PLAYER_PISSED_OFF_FIGHT_50, + SFX_PLAYER_PISSED_OFF_FIGHT_51, + SFX_PLAYER_PISSED_OFF_FIGHT_52, + SFX_PLAYER_PISSED_OFF_FIGHT_53, + SFX_PLAYER_PISSED_OFF_FIGHT_54, + SFX_PLAYER_PISSED_OFF_FIGHT_55, + SFX_PLAYER_PISSED_OFF_FIGHT_56, + SFX_PLAYER_PISSED_OFF_FIGHT_57, + SFX_PLAYER_PISSED_OFF_FIGHT_58, + SFX_PLAYER_PISSED_OFF_FIGHT_59, + SFX_PLAYER_PISSED_OFF_FIGHT_60, + SFX_PLAYER_PISSED_OFF_FIGHT_61, + SFX_PLAYER_PISSED_OFF_JACKED_1, + SFX_PLAYER_PISSED_OFF_JACKED_2, + SFX_PLAYER_PISSED_OFF_JACKED_3, + SFX_PLAYER_PISSED_OFF_JACKED_4, + SFX_PLAYER_PISSED_OFF_JACKED_5, + SFX_PLAYER_PISSED_OFF_JACKED_6, + SFX_PLAYER_PISSED_OFF_JACKED_7, + SFX_PLAYER_PISSED_OFF_JACKED_8, + SFX_PLAYER_PISSED_OFF_JACKED_9, + SFX_PLAYER_PISSED_OFF_JACKED_10, + SFX_PLAYER_PISSED_OFF_JACKED_11, + SFX_PLAYER_PISSED_OFF_JACKED_12, + SFX_PLAYER_PISSED_OFF_JACKED_13, + SFX_PLAYER_PISSED_OFF_JACKED_14, + SFX_PLAYER_PISSED_OFF_JACKED_15, + SFX_PLAYER_PISSED_OFF_JACKED_16, + SFX_PLAYER_PISSED_OFF_JACKED_17, + SFX_PLAYER_PISSED_OFF_JACKED_18, + SFX_PLAYER_PISSED_OFF_JACKED_19, + SFX_PLAYER_PISSED_OFF_JACKED_20, + SFX_PLAYER_PISSED_OFF_JACKED_21, + SFX_PLAYER_PISSED_OFF_JACKING_1, + SFX_PLAYER_PISSED_OFF_JACKING_2, + SFX_PLAYER_PISSED_OFF_JACKING_3, + SFX_PLAYER_PISSED_OFF_JACKING_4, + SFX_PLAYER_PISSED_OFF_JACKING_5, + SFX_PLAYER_PISSED_OFF_JACKING_6, + SFX_PLAYER_PISSED_OFF_JACKING_7, + SFX_PLAYER_PISSED_OFF_JACKING_8, + SFX_PLAYER_PISSED_OFF_JACKING_9, + SFX_PLAYER_PISSED_OFF_JACKING_10, + SFX_PLAYER_PISSED_OFF_JACKING_11, + SFX_PLAYER_PISSED_OFF_JACKING_12, + SFX_PLAYER_PISSED_OFF_JACKING_13, + SFX_PLAYER_PISSED_OFF_JACKING_14, + SFX_PLAYER_PISSED_OFF_JACKING_15, + SFX_PLAYER_PISSED_OFF_JACKING_16, + SFX_PLAYER_PISSED_OFF_JACKING_17, + SFX_PLAYER_PISSED_OFF_JACKING_18, + SFX_PLAYER_PISSED_OFF_JACKING_19, + SFX_PLAYER_PISSED_OFF_JACKING_20, + SFX_PLAYER_PISSED_OFF_JACKING_21, + SFX_PLAYER_PISSED_OFF_JACKING_22, + SFX_PLAYER_PISSED_OFF_JACKING_23, + SFX_PLAYER_PISSED_OFF_JACKING_24, + SFX_PLAYER_PISSED_OFF_JACKING_25, + SFX_PLAYER_PISSED_OFF_JACKING_26, + SFX_PLAYER_PISSED_OFF_JACKING_27, + SFX_PLAYER_PISSED_OFF_JACKING_28, + SFX_PLAYER_PISSED_OFF_JACKING_29, + SFX_PLAYER_PISSED_OFF_JACKING_30, + SFX_PLAYER_PISSED_OFF_JACKING_31, + SFX_PLAYER_PISSED_OFF_JACKING_32, + SFX_PLAYER_PISSED_OFF_JACKING_33, + SFX_PLAYER_PISSED_OFF_JACKING_34, + SFX_PLAYER_PISSED_OFF_JACKING_35, + SFX_PLAYER_PISSED_OFF_JACKING_36, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_1, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_2, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_3, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_4, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_5, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_6, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_7, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_8, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_9, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_10, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_11, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_12, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_13, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_14, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_15, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_16, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_17, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_18, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_19, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_20, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_21, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_22, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_23, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_24, + SFX_PLAYER_PISSED_OFF_PICK_UP_CASH_25, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_1, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_2, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_3, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_4, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_5, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_6, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_7, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_8, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_9, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_10, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_11, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_12, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_13, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_14, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_15, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_16, + SFX_PLAYER_PISSED_OFF_PICK_UP_HOOKER_17, + SFX_PLAYER_PISSED_OFF_PULL_GUN_1, + SFX_PLAYER_PISSED_OFF_PULL_GUN_2, + SFX_PLAYER_PISSED_OFF_PULL_GUN_3, + SFX_PLAYER_PISSED_OFF_PULL_GUN_4, + SFX_PLAYER_PISSED_OFF_PULL_GUN_5, + SFX_PLAYER_PISSED_OFF_PULL_GUN_6, + SFX_PLAYER_PISSED_OFF_PULL_GUN_7, + SFX_PLAYER_PISSED_OFF_PULL_GUN_8, + SFX_PLAYER_PISSED_OFF_PULL_GUN_9, + SFX_PLAYER_PISSED_OFF_PULL_GUN_10, + SFX_PLAYER_PISSED_OFF_PULL_GUN_11, + SFX_PLAYER_PISSED_OFF_PULL_GUN_12, + SFX_PLAYER_PISSED_OFF_PULL_GUN_13, + SFX_PLAYER_PISSED_OFF_PULL_GUN_14, + SFX_PLAYER_PISSED_OFF_PULL_GUN_15, + SFX_PLAYER_PISSED_OFF_PULL_GUN_16, + SFX_PLAYER_PISSED_OFF_PULL_GUN_17, + SFX_PLAYER_PISSED_OFF_PULL_GUN_18, + SFX_PLAYER_PISSED_OFF_PULL_GUN_19, + SFX_PLAYER_PISSED_OFF_PULL_GUN_20, + SFX_PLAYER_PISSED_OFF_PULL_GUN_21, + SFX_PLAYER_PISSED_OFF_PULL_GUN_22, + SFX_PLAYER_PISSED_OFF_PULL_GUN_23, + SFX_PLAYER_PISSED_OFF_PULL_GUN_24, + SFX_PLAYER_PISSED_OFF_PULL_GUN_25, + SFX_PLAYER_PISSED_OFF_SHOOT_1, + SFX_PLAYER_PISSED_OFF_SHOOT_2, + SFX_PLAYER_PISSED_OFF_SHOOT_3, + SFX_PLAYER_PISSED_OFF_SHOOT_4, + SFX_PLAYER_PISSED_OFF_SHOOT_5, + SFX_PLAYER_PISSED_OFF_SHOOT_6, + SFX_PLAYER_PISSED_OFF_SHOOT_7, + SFX_PLAYER_PISSED_OFF_SHOOT_8, + SFX_PLAYER_PISSED_OFF_SHOOT_9, + SFX_PLAYER_PISSED_OFF_SHOOT_10, + SFX_PLAYER_PISSED_OFF_SHOOT_11, + SFX_PLAYER_PISSED_OFF_SHOOT_12, + SFX_PLAYER_PISSED_OFF_SHOOT_13, + SFX_PLAYER_PISSED_OFF_SHOOT_14, + SFX_PLAYER_PISSED_OFF_SHOOT_15, + SFX_PLAYER_PISSED_OFF_SHOOT_16, + SFX_PLAYER_PISSED_OFF_SHOOT_17, + SFX_PLAYER_PISSED_OFF_SHOOT_18, + SFX_PLAYER_PISSED_OFF_SHOOT_19, + SFX_PLAYER_PISSED_OFF_SHOOT_20, + SFX_PLAYER_PISSED_OFF_SHOOT_21, + SFX_PLAYER_PISSED_OFF_SHOOT_22, + SFX_PLAYER_PISSED_OFF_SHOOT_23, + SFX_PLAYER_PISSED_OFF_SHOOT_24, + SFX_PLAYER_PISSED_OFF_SHOOT_25, + SFX_PLAYER_PISSED_OFF_SHOOT_26, + SFX_PLAYER_PISSED_OFF_SHOOT_27, + SFX_PLAYER_PISSED_OFF_SHOOT_28, + SFX_PLAYER_PISSED_OFF_SHOOT_29, + + SFX_PLAYER_WISECRACKING_BUSTED_1, + SFX_PLAYER_WISECRACKING_BUSTED_2, + SFX_PLAYER_WISECRACKING_BUSTED_3, + SFX_PLAYER_WISECRACKING_BUSTED_4, + SFX_PLAYER_WISECRACKING_BUSTED_5, + SFX_PLAYER_WISECRACKING_BUSTED_6, + SFX_PLAYER_WISECRACKING_BUSTED_7, + SFX_PLAYER_WISECRACKING_BUSTED_8, + SFX_PLAYER_WISECRACKING_BUSTED_9, + SFX_PLAYER_WISECRACKING_BUSTED_10, + SFX_PLAYER_WISECRACKING_BUSTED_11, + SFX_PLAYER_WISECRACKING_BUSTED_12, + SFX_PLAYER_WISECRACKING_BUSTED_13, + SFX_PLAYER_WISECRACKING_BUSTED_14, + SFX_PLAYER_WISECRACKING_BUSTED_15, + SFX_PLAYER_WISECRACKING_BUSTED_16, + SFX_PLAYER_WISECRACKING_BUSTED_17, + SFX_PLAYER_WISECRACKING_BUSTED_18, + SFX_PLAYER_WISECRACKING_BUSTED_19, + SFX_PLAYER_WISECRACKING_BUSTED_20, + SFX_PLAYER_WISECRACKING_CHASED_1, + SFX_PLAYER_WISECRACKING_CHASED_2, + SFX_PLAYER_WISECRACKING_CHASED_3, + SFX_PLAYER_WISECRACKING_CHASED_4, + SFX_PLAYER_WISECRACKING_CHASED_5, + SFX_PLAYER_WISECRACKING_CHASED_6, + SFX_PLAYER_WISECRACKING_CHASED_7, + SFX_PLAYER_WISECRACKING_CRASH_1, + SFX_PLAYER_WISECRACKING_CRASH_2, + SFX_PLAYER_WISECRACKING_CRASH_3, + SFX_PLAYER_WISECRACKING_CRASH_4, + SFX_PLAYER_WISECRACKING_CRASH_5, + SFX_PLAYER_WISECRACKING_CRASH_6, + SFX_PLAYER_WISECRACKING_CRASH_7, + SFX_PLAYER_WISECRACKING_CRASH_8, + SFX_PLAYER_WISECRACKING_CRASH_9, + SFX_PLAYER_WISECRACKING_CRASH_10, + SFX_PLAYER_WISECRACKING_CRASH_11, + SFX_PLAYER_WISECRACKING_CRASH_12, + SFX_PLAYER_WISECRACKING_CRASH_13, + SFX_PLAYER_WISECRACKING_CRASH_14, + SFX_PLAYER_WISECRACKING_CRASH_15, + SFX_PLAYER_WISECRACKING_CRASH_16, + SFX_PLAYER_WISECRACKING_CRASH_17, + SFX_PLAYER_WISECRACKING_CRASH_18, + SFX_PLAYER_WISECRACKING_CRASH_19, + SFX_PLAYER_WISECRACKING_FIGHT_1, + SFX_PLAYER_WISECRACKING_FIGHT_2, + SFX_PLAYER_WISECRACKING_FIGHT_3, + SFX_PLAYER_WISECRACKING_FIGHT_4, + SFX_PLAYER_WISECRACKING_FIGHT_5, + SFX_PLAYER_WISECRACKING_FIGHT_6, + SFX_PLAYER_WISECRACKING_FIGHT_7, + SFX_PLAYER_WISECRACKING_FIGHT_8, + SFX_PLAYER_WISECRACKING_FIGHT_9, + SFX_PLAYER_WISECRACKING_FIGHT_10, + SFX_PLAYER_WISECRACKING_FIGHT_11, + SFX_PLAYER_WISECRACKING_FIGHT_12, + SFX_PLAYER_WISECRACKING_FIGHT_13, + SFX_PLAYER_WISECRACKING_FIGHT_14, + SFX_PLAYER_WISECRACKING_FIGHT_15, + SFX_PLAYER_WISECRACKING_FIGHT_16, + SFX_PLAYER_WISECRACKING_FIGHT_17, + SFX_PLAYER_WISECRACKING_FIGHT_18, + SFX_PLAYER_WISECRACKING_FIGHT_19, + SFX_PLAYER_WISECRACKING_FIGHT_20, + SFX_PLAYER_WISECRACKING_FIGHT_21, + SFX_PLAYER_WISECRACKING_FIGHT_22, + SFX_PLAYER_WISECRACKING_FIGHT_23, + SFX_PLAYER_WISECRACKING_FIGHT_24, + SFX_PLAYER_WISECRACKING_FIGHT_25, + SFX_PLAYER_WISECRACKING_FIGHT_26, + SFX_PLAYER_WISECRACKING_FIGHT_27, + SFX_PLAYER_WISECRACKING_JACKED_1, + SFX_PLAYER_WISECRACKING_JACKED_2, + SFX_PLAYER_WISECRACKING_JACKED_3, + SFX_PLAYER_WISECRACKING_JACKED_4, + SFX_PLAYER_WISECRACKING_JACKED_5, + SFX_PLAYER_WISECRACKING_JACKED_6, + SFX_PLAYER_WISECRACKING_JACKED_7, + SFX_PLAYER_WISECRACKING_JACKED_8, + SFX_PLAYER_WISECRACKING_JACKED_9, + SFX_PLAYER_WISECRACKING_JACKED_10, + SFX_PLAYER_WISECRACKING_JACKED_11, + SFX_PLAYER_WISECRACKING_JACKED_12, + SFX_PLAYER_WISECRACKING_JACKED_13, + SFX_PLAYER_WISECRACKING_JACKED_14, + SFX_PLAYER_WISECRACKING_JACKED_15, + SFX_PLAYER_WISECRACKING_JACKED_16, + SFX_PLAYER_WISECRACKING_JACKED_17, + SFX_PLAYER_WISECRACKING_JACKED_18, + SFX_PLAYER_WISECRACKING_JACKING_1, + SFX_PLAYER_WISECRACKING_JACKING_2, + SFX_PLAYER_WISECRACKING_JACKING_3, + SFX_PLAYER_WISECRACKING_JACKING_4, + SFX_PLAYER_WISECRACKING_JACKING_5, + SFX_PLAYER_WISECRACKING_JACKING_6, + SFX_PLAYER_WISECRACKING_JACKING_7, + SFX_PLAYER_WISECRACKING_JACKING_8, + SFX_PLAYER_WISECRACKING_JACKING_9, + SFX_PLAYER_WISECRACKING_JACKING_10, + SFX_PLAYER_WISECRACKING_JACKING_11, + SFX_PLAYER_WISECRACKING_JACKING_12, + SFX_PLAYER_WISECRACKING_JACKING_13, + SFX_PLAYER_WISECRACKING_JACKING_14, + SFX_PLAYER_WISECRACKING_JACKING_15, + SFX_PLAYER_WISECRACKING_JACKING_16, + SFX_PLAYER_WISECRACKING_JACKING_17, + SFX_PLAYER_WISECRACKING_JACKING_18, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_1, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_2, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_3, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_4, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_5, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_6, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_7, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_8, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_9, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_10, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_11, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_12, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_13, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_14, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_15, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_16, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_17, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_18, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_19, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_20, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_21, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_22, + SFX_PLAYER_WISECRACKING_PICK_UP_CASH_23, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_1, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_2, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_3, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_4, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_5, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_6, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_7, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_8, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_9, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_10, + SFX_PLAYER_WISECRACKING_PICK_UP_HOOKER_11, + SFX_PLAYER_WISECRACKING_PULL_GUN_1, + SFX_PLAYER_WISECRACKING_PULL_GUN_2, + SFX_PLAYER_WISECRACKING_PULL_GUN_3, + SFX_PLAYER_WISECRACKING_PULL_GUN_4, + SFX_PLAYER_WISECRACKING_PULL_GUN_5, + SFX_PLAYER_WISECRACKING_PULL_GUN_6, + SFX_PLAYER_WISECRACKING_PULL_GUN_7, + SFX_PLAYER_WISECRACKING_PULL_GUN_8, + SFX_PLAYER_WISECRACKING_PULL_GUN_9, + SFX_PLAYER_WISECRACKING_PULL_GUN_10, + SFX_PLAYER_WISECRACKING_PULL_GUN_11, + SFX_PLAYER_WISECRACKING_PULL_GUN_12, + SFX_PLAYER_WISECRACKING_PULL_GUN_13, + SFX_PLAYER_WISECRACKING_PULL_GUN_14, + SFX_PLAYER_WISECRACKING_PULL_GUN_15, + SFX_PLAYER_WISECRACKING_PULL_GUN_16, + SFX_PLAYER_WISECRACKING_PULL_GUN_17, + SFX_PLAYER_WISECRACKING_PULL_GUN_18, + SFX_PLAYER_WISECRACKING_PULL_GUN_19, + SFX_PLAYER_WISECRACKING_SEX_1, + SFX_PLAYER_WISECRACKING_SEX_2, + SFX_PLAYER_WISECRACKING_SEX_3, + SFX_PLAYER_WISECRACKING_SEX_4, + SFX_PLAYER_WISECRACKING_SEX_5, + SFX_PLAYER_WISECRACKING_SEX_6, + SFX_PLAYER_WISECRACKING_SEX_7, + SFX_PLAYER_WISECRACKING_SEX_8, + SFX_PLAYER_WISECRACKING_SEX_9, + SFX_PLAYER_WISECRACKING_SEX_10, + SFX_PLAYER_WISECRACKING_SHOOT_1, + SFX_PLAYER_WISECRACKING_SHOOT_2, + SFX_PLAYER_WISECRACKING_SHOOT_3, + SFX_PLAYER_WISECRACKING_SHOOT_4, + SFX_PLAYER_WISECRACKING_SHOOT_5, + SFX_PLAYER_WISECRACKING_SHOOT_6, + SFX_PLAYER_WISECRACKING_SHOOT_7, + SFX_PLAYER_WISECRACKING_SHOOT_8, + SFX_PLAYER_WISECRACKING_SHOOT_9, + SFX_PLAYER_DEATH, + SFX_PLAYER_AFTERSEX_1, + SFX_PLAYER_AFTERSEX_2, + SFX_PLAYER_AFTERSEX_3, + SFX_PLAYER_AFTERSEX_4, + SFX_PLAYER_AFTERSEX_5, + SFX_PLAYER_AFTERSEX_6, + SFX_PLAYER_AFTERSEX_7, + SFX_PLAYER_AFTERSEX_8, + SFX_PLAYER_AFTERSEX_9, + SFX_PLAYER_AFTERSEX_10, + SFX_PLAYER_AFTERSEX_11, + SFX_PLAYER_AFTERSEX_12, + SFX_PLAYER_AFTERSEX_13, + SFX_PLAYER_AFTERSEX_14, + SFX_PLAYER_AFTERSEX_15, + SFX_PLAYER_AFTERSEX_16, + SFX_PLAYER_AFTERSEX_17, + SFX_PLAYER_AFTERSEX_18, + SFX_PLAYER_HIT_BULLET_1, + SFX_PLAYER_HIT_BULLET_2, + SFX_PLAYER_HIT_BULLET_3, + SFX_PLAYER_HIT_BULLET_4, + SFX_PLAYER_HIT_BULLET_5, + SFX_PLAYER_HIT_BULLET_6, + SFX_PLAYER_HIT_BULLET_7, + SFX_PLAYER_HIT_BULLET_8, + SFX_PLAYER_HIT_BULLET_9, + SFX_PLAYER_HIT_BULLET_10, + SFX_PLAYER_HIT_BULLET_11, + SFX_PLAYER_HIT_BULLET_12, + SFX_PLAYER_HIT_BULLET_13, + SFX_PLAYER_HIT_BULLET_14, + SFX_PLAYER_HIT_BULLET_15, + SFX_PLAYER_HIT_BULLET_16, + SFX_PLAYER_HIT_BULLET_17, + SFX_PLAYER_HIT_BULLET_18, + SFX_PLAYER_HIT_BULLET_19, + SFX_PLAYER_HIT_BULLET_20, + SFX_PLAYER_HIT_BULLET_21, + SFX_PLAYER_HIT_BULLET_22, + SFX_PLAYER_HIT_BULLET_23, + SFX_PLAYER_HIT_BULLET_24, + SFX_PLAYER_HIT_BULLET_25, + SFX_PLAYER_HIT_BULLET_26, + SFX_PLAYER_HIT_BULLET_27, + SFX_PLAYER_HIT_BULLET_28, + SFX_PLAYER_HIT_BULLET_29, + SFX_PLAYER_HIT_BULLET_30, + SFX_PLAYER_HIT_BULLET_31, + SFX_PLAYER_HIT_BULLET_32, + SFX_PLAYER_HIT_BULLET_33, + SFX_PLAYER_HIT_GROUND_1, + SFX_PLAYER_HIT_GROUND_2, + SFX_PLAYER_HIT_GROUND_3, + SFX_PLAYER_HIT_GROUND_4, + SFX_PLAYER_HIT_GROUND_5, + SFX_PLAYER_HIT_GROUND_6, + SFX_PLAYER_HIT_GROUND_7, + SFX_PLAYER_HIT_GROUND_8, + SFX_PLAYER_HIT_GROUND_9, + SFX_PLAYER_HIT_GROUND_10, + SFX_PLAYER_HIT_GROUND_11, + SFX_PLAYER_HIT_GROUND_12, + SFX_PLAYER_HIT_GROUND_13, + SFX_PLAYER_HIT_GROUND_14, + SFX_PLAYER_HIT_GROUND_15, + SFX_PLAYER_HIT_GROUND_16, + SFX_PLAYER_HIT_GROUND_17, + SFX_PLAYER_HIT_GROUND_18, + SFX_PLAYER_HIT_GROUND_19, + SFX_PLAYER_HIT_GROUND_20, + SFX_PLAYER_HIT_GROUND_21, + SFX_PLAYER_HIT_GROUND_22, + SFX_PLAYER_HIT_GROUND_23, + SFX_PLAYER_HIT_GROUND_24, + SFX_PLAYER_HIT_GROUND_25, + SFX_PLAYER_HIT_GROUND_26, + SFX_PLAYER_HIT_GROUND_27, + SFX_PLAYER_HIT_GROUND_28, + SFX_PLAYER_HIT_GROUND_29, + SFX_PLAYER_HIT_GROUND_30, + SFX_PLAYER_HIT_GROUND_31, + SFX_PLAYER_HIT_GROUND_32, + SFX_PLAYER_HIT_GROUND_33, + SFX_PLAYER_HIT_GROUND_34, + SFX_PLAYER_HIT_GROUND_35, + SFX_PLAYER_HIT_FIST_1, + SFX_PLAYER_HIT_FIST_2, + SFX_PLAYER_HIT_FIST_3, + SFX_PLAYER_HIT_FIST_4, + SFX_PLAYER_HIT_FIST_5, + SFX_PLAYER_HIT_FIST_6, + SFX_PLAYER_HIT_FIST_7, + SFX_PLAYER_HIT_FIST_8, + SFX_PLAYER_HIT_FIST_9, + SFX_PLAYER_HIT_FIST_10, + SFX_PLAYER_HIT_FIST_11, + SFX_PLAYER_HIT_FIST_12, + SFX_PLAYER_HIT_FIST_13, + SFX_PLAYER_HIT_FIST_14, + SFX_PLAYER_HIT_FIST_15, + SFX_PLAYER_HIT_FIST_16, + SFX_PLAYER_HIT_FIST_17, + SFX_PLAYER_HIT_FIST_18, + SFX_PLAYER_HIT_FIST_19, + SFX_PLAYER_HIT_FIST_20, + SFX_PLAYER_HIT_FIST_21, + SFX_PLAYER_HIT_FIST_22, + SFX_PLAYER_HIT_FIST_23, + SFX_PLAYER_HIT_FIST_24, + SFX_PLAYER_HIT_FIST_25, + SFX_PLAYER_HIT_FIST_26, + SFX_PLAYER_HIT_FIST_27, + SFX_PLAYER_HIT_FIST_28, + SFX_PLAYER_HIT_FIST_29, + SFX_PLAYER_HIT_FIST_30, + SFX_PLAYER_HIT_FIST_31, + SFX_PLAYER_HIT_FIST_32, + SFX_PLAYER_HIT_FIST_33, + SFX_PLAYER_HIT_FIST_34, + SFX_PLAYER_HIT_FIST_35, + SFX_PLAYER_HIT_FIST_36, + SFX_PLAYER_HIT_FIST_37, + SFX_PLAYER_HIT_FIST_38, + SFX_PLAYER_HIT_FIST_39, + SFX_PLAYER_HIT_FIST_40, + SFX_PLAYER_HIT_FIST_41, + SFX_PLAYER_HIT_FIST_42, + SFX_PLAYER_ON_FIRE_1, + SFX_PLAYER_ON_FIRE_2, + SFX_PLAYER_ON_FIRE_3, + SFX_PLAYER_ON_FIRE_4, + SFX_PLAYER_ON_FIRE_5, + SFX_PLAYER_ON_FIRE_6, + SFX_PLAYER_ON_FIRE_7, + SFX_PLAYER_ON_FIRE_8, + SFX_PLAYER_ON_FIRE_9, + SFX_PLAYER_ON_FIRE_10, + SFX_PLAYER_ON_FIRE_11, + SFX_PLAYER_ON_FIRE_12, + SFX_PLAYER_ON_FIRE_13, + SFX_PLAYER_ON_FIRE_14, + SFX_PLAYER_ON_FIRE_15, + LAST_PLAYER_COMMENT(SFX_PLAYER_ON_FIRE_16), + TOTAL_AUDIO_SAMPLES, + NO_SAMPLE, + + // shorthands + SAMPLEBANK_START = SFX_CAR_HORN_JEEP, + SAMPLEBANK_END = SFX_FOOTSTEP_SAND_4, + SAMPLEBANK_MAX = SFX_FOOTSTEP_SAND_4 + 1, + SAMPLEBANK_PED_START = SFX_FOOTSTEP_SAND_4 + 1, + SAMPLEBANK_PED_END = SFX_PLAYER_ON_FIRE_16, + SAMPLEBANK_PED_MAX = SAMPLEBANK_PED_END + 1, +}; diff --git a/src/miami/audio/AudioScriptObject.cpp b/src/miami/audio/AudioScriptObject.cpp new file mode 100644 index 00000000..ac4bebcb --- /dev/null +++ b/src/miami/audio/AudioScriptObject.cpp @@ -0,0 +1,103 @@ +#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) +{ + if (!DMAudio.IsAudioInitialised()) return; + + cAudioScriptObject *audioScriptObject = new cAudioScriptObject(); + audioScriptObject->Posn = pos; + audioScriptObject->AudioId = id; + audioScriptObject->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(audioScriptObject); +} diff --git a/src/miami/audio/AudioScriptObject.h b/src/miami/audio/AudioScriptObject.h new file mode 100644 index 00000000..b9a7e61b --- /dev/null +++ b/src/miami/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/miami/audio/DMAudio.cpp b/src/miami/audio/DMAudio.cpp new file mode 100644 index 00000000..68e879e6 --- /dev/null +++ b/src/miami/audio/DMAudio.cpp @@ -0,0 +1,408 @@ +#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::SetOutputMode(bool8 surround) +{ + AudioManager.SetOutputMode(surround); +} + +void +cDMAudio::SetMP3BoostVolume(uint8 volume) +{ + uint8 vol = volume; + if (vol > MAX_VOLUME) vol = MAX_VOLUME; + + AudioManager.SetMP3BoostVolume(vol); +} + +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::AutoDetect3DProviders(void) +{ + return AudioManager.AutoDetect3DProviders(); +} + +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(uint32 announcement) +{ + MusicManager.PlayAnnouncement(announcement); +} + +void +cDMAudio::PlayFrontEndTrack(uint32 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(uint32 track) +{ + MusicManager.PreloadCutSceneMusic(track); +} + +void +cDMAudio::PlayPreloadedCutSceneMusic(void) +{ + MusicManager.PlayPreloadedCutSceneMusic(); +} + +void +cDMAudio::StopCutSceneMusic(void) +{ + MusicManager.StopCutSceneMusic(); +} + +void +cDMAudio::PreloadMissionAudio(uint8 slot, Const char *missionAudio) +{ + AudioManager.PreloadMissionAudio(slot, missionAudio); +} + +uint8 +cDMAudio::GetMissionAudioLoadingStatus(uint8 slot) +{ + return AudioManager.GetMissionAudioLoadingStatus(slot); +} + +void +cDMAudio::SetMissionAudioLocation(uint8 slot, float x, float y, float z) +{ + AudioManager.SetMissionAudioLocation(slot, x, y, z); +} + +void +cDMAudio::PlayLoadedMissionAudio(uint8 slot) +{ + AudioManager.PlayLoadedMissionAudio(slot); +} + +bool8 +cDMAudio::IsMissionAudioSamplePlaying(uint8 slot) +{ + return AudioManager.IsMissionAudioSamplePlaying(slot); +} + +bool8 +cDMAudio::IsMissionAudioSampleFinished(uint8 slot) +{ + return AudioManager.IsMissionAudioSampleFinished(slot); +} + +void +cDMAudio::ClearMissionAudio(uint8 slot) +{ + AudioManager.ClearMissionAudio(slot); +} + +const char * +cDMAudio::GetMissionAudioLoadedLabel(uint8 slot) +{ + return AudioManager.GetMissionAudioLoadedLabel(slot); +} + +uint8 +cDMAudio::GetRadioInCar(void) +{ + return MusicManager.GetRadioInCar(); +} + +void +cDMAudio::SetRadioInCar(uint32 radio) +{ + MusicManager.SetRadioInCar(radio); +} + +void +cDMAudio::SetRadioChannel(uint32 radio, int32 pos) +{ + MusicManager.SetRadioChannelByScript(radio, pos); +} + +void +cDMAudio::SetStartingTrackPositions(bool8 isStartGame) +{ + MusicManager.SetStartingTrackPositions(isStartGame); +} + +float * +cDMAudio::GetListenTimeArray() +{ + return MusicManager.GetListenTimeArray(); +} + +uint32 +cDMAudio::GetFavouriteRadioStation() +{ + return MusicManager.GetFavouriteRadioStation(); +} + +int32 +cDMAudio::GetRadioPosition(uint32 station) +{ + return MusicManager.GetRadioPosition(station); +} + +void +cDMAudio::SetPedTalkingStatus(CPed *ped, bool8 status) +{ + return AudioManager.SetPedTalkingStatus(ped, status); +} + +void +cDMAudio::SetPlayersMood(uint8 mood, uint32 time) +{ + return AudioManager.SetPlayersMood(mood, time); +} + +void +cDMAudio::ShutUpPlayerTalking(bool8 state) +{ + AudioManager.m_bIsPlayerShutUp = state; +} \ No newline at end of file diff --git a/src/miami/audio/DMAudio.h b/src/miami/audio/DMAudio.h new file mode 100644 index 00000000..110cb2db --- /dev/null +++ b/src/miami/audio/DMAudio.h @@ -0,0 +1,107 @@ +#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) + +#define NO_AUDIO_PROVIDER -3 +#define AUDIO_PROVIDER_NOT_DETERMINED -99 + +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 SetOutputMode(bool8 surround); + void SetMP3BoostVolume(uint8 volume); + void SetEffectsMasterVolume(uint8 volume); + void SetMusicMasterVolume(uint8 volume); + void SetEffectsFadeVol(uint8 volume); + void SetMusicFadeVol(uint8 volume); + + uint8 GetNum3DProvidersAvailable(void); + char *Get3DProviderName(uint8 id); + + int8 AutoDetect3DProviders(void); + + 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(uint32 announcement); + void PlayFrontEndTrack(uint32 track, bool8 frontendFlag); + void StopFrontEndTrack(void); + + void ResetTimers(uint32 time); + + void ChangeMusicMode(uint8 mode); + + void PreloadCutSceneMusic(uint32 track); + void PlayPreloadedCutSceneMusic(void); + void StopCutSceneMusic(void); + + void PreloadMissionAudio(uint8 slot, Const char *missionAudio); + uint8 GetMissionAudioLoadingStatus(uint8 slot); + void SetMissionAudioLocation(uint8 slot, float x, float y, float z); + void PlayLoadedMissionAudio(uint8 slot); + bool8 IsMissionAudioSamplePlaying(uint8 slot); + bool8 IsMissionAudioSampleFinished(uint8 slot); + void ClearMissionAudio(uint8 slot); + const char *GetMissionAudioLoadedLabel(uint8 slot); + + uint8 GetRadioInCar(void); + void SetRadioInCar(uint32 radio); + void SetRadioChannel(uint32 radio, int32 pos); + + void SetStartingTrackPositions(bool8 isStartGame); + float *GetListenTimeArray(); + uint32 GetFavouriteRadioStation(); + int32 GetRadioPosition(uint32 station); + void SetPedTalkingStatus(class CPed *ped, bool8 status); + void SetPlayersMood(uint8 mood, uint32 time); + void ShutUpPlayerTalking(bool8 state); +}; +extern cDMAudio DMAudio; diff --git a/src/miami/audio/MusicManager.cpp b/src/miami/audio/MusicManager.cpp new file mode 100644 index 00000000..c97b9c24 --- /dev/null +++ b/src/miami/audio/MusicManager.cpp @@ -0,0 +1,1423 @@ +#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" +#include "Stats.h" +#include "Script.h" +#include "ZoneCull.h" +#include "Weather.h" +#include "DMAudio.h" +#include "GenericGameStorage.h" + +#if !defined FIX_BUGS && (defined RADIO_SCROLL_TO_PREV_STATION || defined RADIO_OFF_TEXT) +static_assert(false, "R*'s radio implementation is quite buggy, RADIO_SCROLL_TO_PREV_STATION and RADIO_OFF_TEXT won't work without FIX_BUGS"); +#endif + +cMusicManager MusicManager; +int32 gNumRetunePresses; +int32 gRetuneCounter; +bool8 g_bAnnouncementReadPosAlready; +uint8 RadioStaticCounter = 5; +uint32 RadioStaticTimer; + +CVector vecRiotPosition(300.7f, -322.0f, 12.0f); + +uint32 NewGameRadioTimers[10] = +{ + 948160, + 452150, + 2438150, + 3538230, + 3513100, + 4246050, + 1418050, + 3178240, + 471210, + 0 +}; + +cMusicManager::cMusicManager() +{ + m_bIsInitialised = FALSE; + m_bDisabled = FALSE; + m_nFrontendTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + m_nUpcomingMusicMode = MUSICMODE_DISABLED; + m_nMusicMode = MUSICMODE_DISABLED; + m_bSetNextStation = FALSE; + + for (int i = 0; i < NUM_RADIOS; i++) + aListenTimeArray[i] = 0.0f; + + m_nLastTrackServiceTime = 0.0f; + m_nVolumeLatency = 0; + m_nCurrentVolume = 0; + m_nMaxVolume = 0; + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; +} + +void +cMusicManager::ResetMusicAfterReload() +{ + float afRadioTime[NUM_RADIOS]; + + m_bRadioSetByScript = FALSE; + m_nRadioStationScript = WILDSTYLE; + m_nRadioPosition = -1; + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + m_bSetNextStation = FALSE; + RadioStaticTimer = 0; + gNumRetunePresses = 0; + gRetuneCounter = 0; + m_nFrontendTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + m_FrontendLoopFlag = FALSE; + m_bTrackChangeStarted = FALSE; + m_nNextTrack = NO_TRACK; + m_nNextLoopFlag = FALSE; + m_bVerifyNextTrackStartedToPlay = FALSE; + m_bGameplayAllowsRadio = FALSE; + m_bRadioStreamReady = FALSE; + nFramesSinceCutsceneEnded = -1; + m_bUserResumedGame = FALSE; + m_bMusicModeChangeStarted = FALSE; + m_bEarlyFrontendTrack = FALSE; + m_nVolumeLatency = 0; + m_nCurrentVolume = 0; + m_nMaxVolume = 0; + + bool8 bRadioWasEverListened = FALSE; + + for (int i = 0; i < NUM_RADIOS; i++) { + afRadioTime[i] = CStats::GetFavoriteRadioStationList(i); + if (!bRadioWasEverListened && afRadioTime[i] != 0.0f) + bRadioWasEverListened = TRUE; + } + + if (!bRadioWasEverListened) return; + + for (int i = 0; i < NUM_RADIOS; i++) { + aListenTimeArray[i] = afRadioTime[i]; + int32 trackPos = GetSavedRadioStationPosition(i); + if (trackPos != -1) { + if (trackPos > m_aTracks[i].m_nLength) { + debug("Radio Track %d saved position is %d, Length is only %d\n", i, trackPos, m_aTracks[i].m_nLength); + trackPos %= m_aTracks[i].m_nLength; + } + m_aTracks[i].m_nPosition = trackPos; + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + } +} + +void +cMusicManager::SetStartingTrackPositions(bool8 isNewGameTimer) +{ + 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); + + if (i < STREAMED_SOUND_CITY_AMBIENT && isNewGameTimer) + m_aTracks[i].m_nPosition = NewGameRadioTimers[i]; + else if (i < STREAMED_SOUND_ANNOUNCE_BRIDGE_CLOSED) + m_aTracks[i].m_nPosition = (pos * AudioManager.m_anRandomTable[i % 5]) % m_aTracks[i].m_nLength; + else + m_aTracks[i].m_nPosition = 0; + + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + } +} + +bool8 +cMusicManager::Initialise() +{ + if (!IsInitialised()) { + m_bIsInitialised = TRUE; + SetStartingTrackPositions(FALSE); + m_bResetTimers = FALSE; + m_nResetTime = 0; + m_bRadioSetByScript = FALSE; + m_nRadioStationScript = WILDSTYLE; + m_nRadioPosition = -1; + m_nRadioInCar = NO_TRACK; + gRetuneCounter = 0; + gNumRetunePresses = 0; + m_nFrontendTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + m_nUpcomingMusicMode = MUSICMODE_DISABLED; + m_nMusicMode = MUSICMODE_DISABLED; + m_FrontendLoopFlag = FALSE; + m_bTrackChangeStarted = FALSE; + m_nNextTrack = NO_TRACK; + m_nNextLoopFlag = FALSE; + m_bVerifyNextTrackStartedToPlay = FALSE; + m_bGameplayAllowsRadio = FALSE; + m_bRadioStreamReady = FALSE; + nFramesSinceCutsceneEnded = -1; + m_bUserResumedGame = FALSE; + m_bMusicModeChangeStarted = FALSE; + m_nMusicModeToBeSet = MUSICMODE_DISABLED; + m_bEarlyFrontendTrack = FALSE; + m_nVolumeLatency = 0; + m_nCurrentVolume = 0; + m_nMaxVolume = 0; + } + return m_bIsInitialised; +} + +void +cMusicManager::Terminate() +{ + if (!IsInitialised()) return; + + if (SampleManager.IsStreamPlaying()) { + SampleManager.StopStreamedFile(); + m_nPlayingTrack = NO_TRACK; + } + m_bIsInitialised = FALSE; +} + +void +cMusicManager::SetRadioChannelByScript(uint32 station, int32 pos) +{ + if (m_bIsInitialised) { + if (station == STREAMED_SOUND_RADIO_MP3_PLAYER) + station = STREAMED_SOUND_CITY_AMBIENT; + if (station <= STREAMED_SOUND_RADIO_POLICE) { + m_bRadioSetByScript = TRUE; + m_nRadioStationScript = station; + m_nRadioPosition = pos == -1 ? -1 : pos % m_aTracks[station].m_nLength; + } + } +} + +bool8 +cMusicManager::PlayerInCar() +{ + CVehicle *vehicle = AudioManager.FindVehicleOfPlayer(); + if(!vehicle) + return FALSE; + + int32 State = FindPlayerPed()->m_nPedState; + + if(State == PED_DRAG_FROM_CAR || State == PED_EXIT_CAR || State == PED_ARRESTED) + return FALSE; + + if (vehicle->GetStatus() == STATUS_WRECKED) + return FALSE; + + return TRUE; +} + +uint32 +cMusicManager::GetRadioInCar(void) +{ + if (!m_bIsInitialised) return WILDSTYLE; + if (PlayerInCar()) { + CVehicle* veh = AudioManager.FindVehicleOfPlayer(); + if (veh != nil) { + if (UsesPoliceRadio(veh) || UsesTaxiRadio(veh)) { + if (m_nRadioInCar == NO_TRACK || (CReplay::IsPlayingBack() && !AudioManager.m_bIsPaused)) + return STREAMED_SOUND_RADIO_POLICE; + 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 = AudioManager.FindVehicleOfPlayer(); + if (veh == nil) return; + if (UsesPoliceRadio(veh) || UsesTaxiRadio(veh)) + m_nRadioInCar = station; + else + veh->m_nRadioStation = station; + } +} + +void +cMusicManager::RecordRadioStats() +{ + if (m_nPlayingTrack < NUM_RADIOS) { + double time /*Rusty*/ = CTimer::GetTimeInMillisecondsPauseMode(); + if (time > m_nLastTrackServiceTime) + aListenTimeArray[m_nPlayingTrack] += time - m_nLastTrackServiceTime; + } +} + +void +cMusicManager::ChangeMusicMode(uint8 mode) +{ + if (!IsInitialised()) return; + + switch (mode) + { + case MUSICMODE_FRONTEND: + m_nUpcomingMusicMode = MUSICMODE_FRONTEND; + +#ifdef PAUSE_RADIO_IN_FRONTEND + // rewind those streams we weren't listening right now + for( uint32 i = STREAMED_SOUND_RADIO_WILD; i < STREAMED_SOUND_CUTSCENE_ASS_1; i++ ) { + m_aTracks[i].m_nPosition = GetTrackStartPos(i); + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } +#endif + + break; + case MUSICMODE_GAME: m_nUpcomingMusicMode = MUSICMODE_GAME; break; + case MUSICMODE_CUTSCENE: + m_nUpcomingMusicMode = MUSICMODE_CUTSCENE; + if (SampleManager.IsStreamPlaying()) { + if (m_nPlayingTrack != NO_TRACK) { + RecordRadioStats(); + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + } + SampleManager.StopStreamedFile(); + while (SampleManager.IsStreamPlaying()) + SampleManager.StopStreamedFile(); + m_nMusicMode = m_nUpcomingMusicMode; + m_bMusicModeChangeStarted = FALSE; + m_bTrackChangeStarted = FALSE; + m_nNextTrack = NO_TRACK; + m_nNextLoopFlag = FALSE; + m_bVerifyNextTrackStartedToPlay = FALSE; + m_nPlayingTrack = NO_TRACK; + m_nFrontendTrack = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + m_nAnnouncement = NO_TRACK; + g_bAnnouncementReadPosAlready = FALSE; + break; + case MUSICMODE_DISABLE: m_nUpcomingMusicMode = MUSICMODE_DISABLED; break; + default: return; + } +} + +void +cMusicManager::ResetTimers(uint32 time) +{ + m_bResetTimers = TRUE; + m_nResetTime = time; +} + +void +cMusicManager::Service() +{ + if (m_bResetTimers) { + m_bResetTimers = FALSE; + m_nLastTrackServiceTime = m_nResetTime; + } + + static bool8 bRadioStatsRecorded = FALSE; + + if (!m_bIsInitialised || m_bDisabled) return; + + if (!m_bMusicModeChangeStarted) + m_nMusicModeToBeSet = m_nUpcomingMusicMode; + if (m_nMusicModeToBeSet == m_nMusicMode) { + if (!AudioManager.m_bIsPaused || AudioManager.m_bWasPaused || m_nMusicMode != MUSICMODE_FRONTEND) + { + switch (m_nMusicMode) + { + case MUSICMODE_FRONTEND: ServiceFrontEndMode(); break; + case MUSICMODE_GAME: ServiceGameMode(); break; + case MUSICMODE_CUTSCENE: SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, TRUE); break; + } + } + else + m_nMusicMode = MUSICMODE_DISABLED; + } else { + m_bMusicModeChangeStarted = TRUE; + if (!m_bUserResumedGame && !AudioManager.m_bIsPaused && AudioManager.m_bWasPaused) + m_bUserResumedGame = TRUE; + if (AudioManager.m_FrameCounter % 4 == 0) { + gNumRetunePresses = 0; + gRetuneCounter = 0; + m_bSetNextStation = FALSE; + if (SampleManager.IsStreamPlaying()) { + if (m_nPlayingTrack != NO_TRACK && !bRadioStatsRecorded) + { + RecordRadioStats(); + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + bRadioStatsRecorded = TRUE; + } + SampleManager.StopStreamedFile(); + } else { + bRadioStatsRecorded = FALSE; + m_nMusicMode = m_nMusicModeToBeSet; + m_bMusicModeChangeStarted = FALSE; + m_bTrackChangeStarted = FALSE; + m_nNextTrack = NO_TRACK; + m_nNextLoopFlag = FALSE; + m_bVerifyNextTrackStartedToPlay = FALSE; + m_nPlayingTrack = NO_TRACK; + if (m_bEarlyFrontendTrack) + m_bEarlyFrontendTrack = FALSE; + else + m_nFrontendTrack = NO_TRACK; + } + } + } +} + +void +cMusicManager::ServiceFrontEndMode() +{ + static bool8 bRadioStatsRecorded = FALSE; + +#ifdef PAUSE_RADIO_IN_FRONTEND + // pause radio + for (uint32 i = STREAMED_SOUND_RADIO_WILD; i < STREAMED_SOUND_CUTSCENE_ASS_1; i++) + m_aTracks[i].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); +#endif + + if (m_bAnnouncementInProgress) { + SampleManager.StopStreamedFile(); + if (SampleManager.IsStreamPlaying()) + return; + g_bAnnouncementReadPosAlready = FALSE; + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + m_nNextTrack = NO_TRACK; + m_nFrontendTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + } + + if (AudioManager.m_FrameCounter % 4 != 0) return; + + if (!m_bTrackChangeStarted && !m_bVerifyNextTrackStartedToPlay) { + m_nNextTrack = m_nFrontendTrack; + m_nNextLoopFlag = m_FrontendLoopFlag; + } + + if (m_nNextTrack == m_nPlayingTrack) { + if (SampleManager.IsStreamPlaying()) { + if (m_nVolumeLatency > 0) m_nVolumeLatency--; + else { + if (m_nCurrentVolume < m_nMaxVolume) + m_nCurrentVolume = Min(m_nMaxVolume, m_nCurrentVolume + 6); + SampleManager.SetStreamedVolumeAndPan(m_nCurrentVolume, 63, FALSE); + } + } else { + if (m_nPlayingTrack == STREAMED_SOUND_RADIO_MP3_PLAYER) + SampleManager.StartStreamedFile(STREAMED_SOUND_RADIO_MP3_PLAYER, 0); + else if (m_nPlayingTrack == STREAMED_SOUND_MISSION_COMPLETED && !AudioManager.m_bIsPaused) + ChangeMusicMode(MUSICMODE_GAME); + } + } else { + m_bTrackChangeStarted = TRUE; + if (m_bVerifyNextTrackStartedToPlay || !SampleManager.IsStreamPlaying()) { + bRadioStatsRecorded = FALSE; + if (SampleManager.IsStreamPlaying() || m_nNextTrack == NO_TRACK) { + m_nPlayingTrack = m_nNextTrack; + m_bVerifyNextTrackStartedToPlay = FALSE; + m_bTrackChangeStarted = FALSE; + } else { + uint32 trackStartPos = (m_nNextTrack > STREAMED_SOUND_RADIO_POLICE) ? 0 : GetTrackStartPos(m_nNextTrack); + if (m_nNextTrack != NO_TRACK) { + SampleManager.SetStreamedFileLoopFlag(m_nNextLoopFlag); + SampleManager.StartStreamedFile(m_nNextTrack, trackStartPos); + m_nVolumeLatency = 3; + m_nCurrentVolume = 0; + m_nMaxVolume = 100; + SampleManager.SetStreamedVolumeAndPan(m_nCurrentVolume, 63, FALSE); + if (m_nNextTrack < STREAMED_SOUND_CITY_AMBIENT) + m_nLastTrackServiceTime = CTimer::GetTimeInMillisecondsPauseMode(); + m_bVerifyNextTrackStartedToPlay = TRUE; + } + } + } else { + if (m_nPlayingTrack != NO_TRACK && !bRadioStatsRecorded) { + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + RecordRadioStats(); + bRadioStatsRecorded = TRUE; + } + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + SampleManager.StopStreamedFile(); + } + } +} + +void +cMusicManager::ServiceGameMode() +{ + CPed *ped = FindPlayerPed(); + CVehicle *vehicle = AudioManager.FindVehicleOfPlayer(); + m_bRadioStreamReady = m_bGameplayAllowsRadio; + m_bGameplayAllowsRadio = FALSE; + + switch (CGame::currArea) + { + case AREA_HOTEL: + case AREA_MALL: + case AREA_STRIP_CLUB: + case AREA_DIRT: + case AREA_BLOOD: + case AREA_OVALRING: + case AREA_MALIBU_CLUB: + m_bGameplayAllowsRadio = FALSE; + break; + default: + if (SampleManager.GetMusicVolume()) { + if (PlayerInCar()) + m_bGameplayAllowsRadio = TRUE; + } else + m_bGameplayAllowsRadio = FALSE; + break; + } + + if (!m_bGameplayAllowsRadio) { + nFramesSinceCutsceneEnded = -1; + gNumRetunePresses = 0; + gRetuneCounter = 0; + m_bSetNextStation = FALSE; + } else if (ped) { + if(!ped->DyingOrDead() && vehicle) { +#ifdef GTA_PC + if (SampleManager.IsMP3RadioChannelAvailable() + && vehicle->m_nRadioStation < USERTRACK + && ControlsManager.GetIsKeyboardKeyJustDown(rsF9)) + { + if (!UsesPoliceRadio(vehicle) && !UsesTaxiRadio(vehicle)) { + gNumRetunePresses = 0; + gRetuneCounter = 20; + RadioStaticCounter = 0; + if (vehicle->m_nRadioStation < USERTRACK) + { + do + ++gNumRetunePresses; + while (gNumRetunePresses + vehicle->m_nRadioStation < USERTRACK); + } + } + } +#endif + if (CPad::GetPad(0)->ChangeStationJustDown()) + { + if (!UsesPoliceRadio(vehicle) && !UsesTaxiRadio(vehicle)) { + gNumRetunePresses++; + gRetuneCounter = 20; + RadioStaticCounter = 0; + } + } +#ifdef RADIO_SCROLL_TO_PREV_STATION + else if(!CPad::GetPad(0)->ArePlayerControlsDisabled() && (CPad::GetPad(0)->GetMouseWheelDownJustDown() || CPad::GetPad(0)->GetMouseWheelUpJustDown())) { + if(!UsesPoliceRadio(vehicle) && !UsesTaxiRadio(vehicle)) { + int scrollNext = ControlsManager.GetControllerKeyAssociatedWithAction(VEHICLE_CHANGE_RADIO_STATION, MOUSE); + int scrollPrev = scrollNext == rsMOUSEWHEELUPBUTTON ? rsMOUSEWHEELDOWNBUTTON + : scrollNext == rsMOUSEWHEELDOWNBUTTON ? rsMOUSEWHEELUPBUTTON : -1; + + if(scrollPrev != -1 && !ControlsManager.IsAnyVehicleActionAssignedToMouseKey(scrollPrev)) { + gNumRetunePresses--; + gRetuneCounter = 20; + RadioStaticCounter = 0; + int track = gNumRetunePresses + vehicle->m_nRadioStation; + while(track < 0) track += NUM_RADIOS + 1; + while(track >= NUM_RADIOS + 1) track -= NUM_RADIOS + 1; + if(!DMAudio.IsMP3RadioChannelAvailable() && track == USERTRACK) gNumRetunePresses--; + } + } + } +#endif + } + } + + if (m_bUserResumedGame) + { + m_bRadioStreamReady = FALSE; + m_bUserResumedGame = FALSE; + } + if (m_nPlayingTrack == NO_TRACK && m_nFrontendTrack == NO_TRACK) + m_bRadioStreamReady = FALSE; + + if (m_bGameplayAllowsRadio) + { + if (!m_bRadioStreamReady) + { + if(vehicle == nil) { + m_nFrontendTrack = STREAMED_SOUND_RADIO_WAVE; // huh? + return; + } + if(m_bRadioSetByScript) { + if(UsesPoliceRadio(vehicle)) + m_nFrontendTrack = STREAMED_SOUND_RADIO_POLICE; + else if(UsesTaxiRadio(vehicle)) + m_nFrontendTrack = STREAMED_SOUND_RADIO_TAXI; + else { + m_nFrontendTrack = m_nRadioStationScript; + vehicle->m_nRadioStation = m_nRadioStationScript; + } + if(m_nRadioPosition != -1) { + m_aTracks[m_nFrontendTrack].m_nPosition = m_nRadioPosition; + m_aTracks[m_nFrontendTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + m_bRadioSetByScript = FALSE; + return; + } + + // This starts the radio when you enter the car. + m_nFrontendTrack = GetCarTuning(); + return; + } + if (m_nAnnouncement < NO_TRACK) { + if ((m_bAnnouncementInProgress || m_nFrontendTrack == m_nPlayingTrack) && ServiceAnnouncement()) { + if (m_bAnnouncementInProgress) { + m_bSetNextStation = FALSE; + gNumRetunePresses = 0; + gRetuneCounter = 0; + return; + } + if(m_nAnnouncement == NO_TRACK) { + m_nNextTrack = NO_TRACK; + m_nFrontendTrack = GetCarTuning(); + m_bSetNextStation = FALSE; + gRetuneCounter = 0; + gNumRetunePresses = 0; + } + } + } + if (!m_bAnnouncementInProgress + && m_nAnnouncement == NO_TRACK + && m_nPlayingTrack == STREAMED_SOUND_RADIO_MP3_PLAYER + && !SampleManager.IsStreamPlaying()) + { + SampleManager.StartStreamedFile(STREAMED_SOUND_RADIO_MP3_PLAYER, 0); + } + + if (!m_bRadioSetByScript) + { + // 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) { + m_bSetNextStation = TRUE; + gRetuneCounter = 0; + } + } +#else + if (gNumRetunePresses != 0) + { + if (--gRetuneCounter == 0) + { + m_bSetNextStation = TRUE; + gRetuneCounter = 0; + } + } +#endif + if (gRetuneCounter) + { + int32 station = gNumRetunePresses + vehicle->m_nRadioStation; +#ifdef RADIO_SCROLL_TO_PREV_STATION + while (station < 0) station += NUM_RADIOS + 1; +#endif + while (station >= NUM_RADIOS + 1) station -= NUM_RADIOS + 1; + + // Scrolling back won't hit here, so increasing isn't problem + if (!DMAudio.IsMP3RadioChannelAvailable() && station == USERTRACK) + { + ++gNumRetunePresses; + station = RADIO_OFF; + } + if (station == RADIO_OFF) + { + if (gRetuneCounter == 19) // One less then what switching radio sets, so runs right after turning off radio + { + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_TURN_OFF, 0.0f); + RadioStaticCounter = 5; + } + } + else + { +#ifdef RADIO_SCROLL_TO_PREV_STATION + if (vehicle->m_nRadioStation == RADIO_OFF && gRetuneCounter == 19) // Right after turning on the radio +#else + if (station == 0 && gRetuneCounter == 19) // Right after turning on the radio +#endif + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_RADIO_TURN_ON, 0.0f); + AudioManager.DoPoliceRadioCrackle(); + } + } + if (RadioStaticCounter < 2 && CTimer::GetTimeInMilliseconds() > RadioStaticTimer + 800) + { + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_RADIO_CHANGE, 0.0f); + RadioStaticCounter++; + RadioStaticTimer = CTimer::GetTimeInMilliseconds(); + } + if (m_bSetNextStation) + m_nFrontendTrack = GetNextCarTuning(); + if (m_nFrontendTrack >= STREAMED_SOUND_CITY_AMBIENT && m_nFrontendTrack <= STREAMED_SOUND_AMBSIL_AMBIENT) + SetUpCorrectAmbienceTrack(); + ServiceTrack(vehicle, ped); + if (m_bSetNextStation) + m_bSetNextStation = FALSE; + return; + } + if (UsesPoliceRadio(vehicle)) + m_nFrontendTrack = STREAMED_SOUND_RADIO_POLICE; + else if (UsesTaxiRadio(vehicle)) + m_nFrontendTrack = STREAMED_SOUND_RADIO_TAXI; + else { + m_nFrontendTrack = m_nRadioStationScript; + vehicle->m_nRadioStation = m_nRadioStationScript; + } + + if (m_nRadioPosition != -1) { + m_aTracks[m_nFrontendTrack].m_nPosition = m_nRadioPosition; + m_aTracks[m_nFrontendTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + + gRetuneCounter = 0; + gNumRetunePresses = 0; + m_bSetNextStation = FALSE; + m_bRadioSetByScript = FALSE; + if (m_nFrontendTrack >= STREAMED_SOUND_CITY_AMBIENT && m_nFrontendTrack <= STREAMED_SOUND_AMBSIL_AMBIENT) + SetUpCorrectAmbienceTrack(); + ServiceTrack(vehicle, ped); + if (m_bSetNextStation) + m_bSetNextStation = FALSE; + return; + } + + if (m_bAnnouncementInProgress) + { + SampleManager.StopStreamedFile(); + if (SampleManager.IsStreamPlaying()) + return; + g_bAnnouncementReadPosAlready = FALSE; + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + m_nNextTrack = NO_TRACK; + m_nFrontendTrack = NO_TRACK; + m_nPlayingTrack = NO_TRACK; + } + SetUpCorrectAmbienceTrack(); + ServiceTrack(nil, ped); +} + +void +cMusicManager::SetUpCorrectAmbienceTrack() +{ + switch (CGame::currArea) + { + case AREA_MAIN_MAP: + case AREA_EVERYWHERE: + if (CTheScripts::RiotIntensity != 0 && ((TheCamera.GetPosition() - vecRiotPosition).MagnitudeSqr() < SQR(65.0f))) + m_nFrontendTrack = STREAMED_SOUND_LAW4RIOT_AMBIENT; + else if (TheCamera.DistanceToWater <= 90.0f) { + if (CCullZones::bAtBeachForAudio) { + if (CWeather::OldWeatherType != WEATHER_HURRICANE && CWeather::NewWeatherType != WEATHER_HURRICANE || CWeather::Wind <= 1.0f) + m_nFrontendTrack = STREAMED_SOUND_BEACH_AMBIENT; + else + m_nFrontendTrack = STREAMED_SOUND_HAVANA_BEACH_AMBIENT; + } + else if (CWeather::OldWeatherType != WEATHER_HURRICANE && CWeather::NewWeatherType != WEATHER_HURRICANE || CWeather::Wind <= 1.0f) + m_nFrontendTrack = STREAMED_SOUND_WATER_AMBIENT; + else + m_nFrontendTrack = STREAMED_SOUND_HAVANA_WATER_AMBIENT; + } + else if (CWeather::OldWeatherType != WEATHER_HURRICANE && CWeather::NewWeatherType != WEATHER_HURRICANE || CWeather::Wind <= 1.0f) + m_nFrontendTrack = STREAMED_SOUND_CITY_AMBIENT; + else + m_nFrontendTrack = STREAMED_SOUND_HAVANA_CITY_AMBIENT; + break; + case AREA_HOTEL: + m_nFrontendTrack = STREAMED_SOUND_HOTEL_AMBIENT; + break; + case AREA_MALL: + m_nFrontendTrack = STREAMED_SOUND_MALL_AMBIENT; + break; + case AREA_STRIP_CLUB: + m_nFrontendTrack = STREAMED_SOUND_STRIPCLUB_AMBIENT; + break; + case AREA_DIRT: + case AREA_BLOOD: + case AREA_OVALRING: + m_nFrontendTrack = STREAMED_SOUND_DIRTRING_AMBIENT; + break; + case AREA_MALIBU_CLUB: + m_nFrontendTrack = STREAMED_SOUND_MALIBU_AMBIENT; + break; + case AREA_MANSION: + case AREA_BANK: + case AREA_LAWYERS: + case AREA_COFFEE_SHOP: + case AREA_CONCERT_HALL: + case AREA_STUDIO: + case AREA_RIFLE_RANGE: + case AREA_BIKER_BAR: + case AREA_POLICE_STATION: + m_nFrontendTrack = STREAMED_SOUND_AMBSIL_AMBIENT; + break; + } +} + +float +GetHeightScale() +{ + if (TheCamera.GetPosition().z > 20.0f) { + if (TheCamera.GetPosition().z < 50.0f) + return 1.0f - (TheCamera.GetPosition().z - 20.0f) / 30.0f; + return 0.0f; + } + return 1.0f; +} + +void +cMusicManager::ComputeAmbienceVol(bool8 reset, uint8& outVolume) +{ + static float fVol = 0.0f; + + float fHeightScale = GetHeightScale(); + + if (CTheScripts::RiotIntensity > 0) { + float distToRiotSq = (TheCamera.GetPosition() - vecRiotPosition).MagnitudeSqr(); + if (distToRiotSq < SQR(100.0f)) { + if (distToRiotSq >= SQR(65.0f)) + outVolume = (Sqrt(distToRiotSq) - 65.0f) / 35.0f * (127.0f * fHeightScale); + else if (distToRiotSq >= SQR(20.0f)) + outVolume = (CTheScripts::RiotIntensity * (1.0f - (Sqrt(distToRiotSq) - 20.0f) / 45.0f) * (127.0f * fHeightScale)) / MAX_VOLUME; + else + outVolume = (CTheScripts::RiotIntensity * (127.0f * fHeightScale)) / MAX_VOLUME; + return; + } + } + + if (reset) + fVol = 0.0f; + else if (fVol < 60.0f) { + if ((m_nPlayingTrack >= STREAMED_SOUND_HAVANA_CITY_AMBIENT) && (m_nPlayingTrack <= STREAMED_SOUND_HAVANA_BEACH_AMBIENT)) + fVol += 20.0f; + else + fVol += 1.0f; + fVol = Min(fVol, 60.0f); + } + + if ((m_nPlayingTrack >= STREAMED_SOUND_MALL_AMBIENT) && (m_nPlayingTrack <= STREAMED_SOUND_AMBSIL_AMBIENT)) { + outVolume = fVol; + return; + } + + if (CWeather::OldWeatherType == WEATHER_HURRICANE || CWeather::NewWeatherType == WEATHER_HURRICANE) { + if (CWeather::Wind > 1.0f) { + outVolume = (CWeather::Wind - 1.0f) * fVol; + return; + } + fVol = (1.0f - CWeather::Wind) * fVol; + } + + if (TheCamera.DistanceToWater > 140.0f) { + outVolume = fVol; + return; + } + + if (TheCamera.DistanceToWater > 90.0f) { + outVolume = ((TheCamera.DistanceToWater - 90.0f) / 50.0f * fVol * fHeightScale); + return; + } + + if (TheCamera.DistanceToWater > 40.0f) { + outVolume = fVol; + return; + } + + outVolume = (90.0f - fHeightScale) / 50.0f * fVol; +} + +bool8 +cMusicManager::ServiceAnnouncement() +{ + if (m_bAnnouncementInProgress) { + if (SampleManager.IsStreamPlaying()) + m_nPlayingTrack = m_nNextTrack; + else if (m_nPlayingTrack != NO_TRACK) { + m_nAnnouncement = NO_TRACK; + m_bAnnouncementInProgress = FALSE; + m_nPlayingTrack = NO_TRACK; + } + return TRUE; + } else if (SampleManager.IsStreamPlaying()) { + if (m_nPlayingTrack != NO_TRACK && !g_bAnnouncementReadPosAlready) { + RecordRadioStats(); + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + g_bAnnouncementReadPosAlready = TRUE; + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } + SampleManager.StopStreamedFile(); + } else { + g_bAnnouncementReadPosAlready = FALSE; + m_nPlayingTrack = NO_TRACK; + m_nNextTrack = m_nAnnouncement; + SampleManager.SetStreamedFileLoopFlag(FALSE); + SampleManager.StartStreamedFile(m_nNextTrack, 0); + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, FALSE); + m_bAnnouncementInProgress = TRUE; + } + + return TRUE; +} + +void +cMusicManager::ServiceTrack(CVehicle *veh, CPed *ped) +{ + static bool8 bRadioStatsRecorded = FALSE; + static bool8 bRadioStatsRecorded2 = FALSE; + uint8 volume; + if (!m_bTrackChangeStarted) + m_nNextTrack = m_nFrontendTrack; + if (gRetuneCounter != 0 || m_bSetNextStation) { + if (SampleManager.IsStreamPlaying()) { + if (m_nPlayingTrack != NO_TRACK && !bRadioStatsRecorded) { + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + RecordRadioStats(); + bRadioStatsRecorded = TRUE; + } + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + SampleManager.StopStreamedFile(); + } + return; + } + + if (bRadioStatsRecorded) { + bRadioStatsRecorded = FALSE; + m_nPlayingTrack = NO_TRACK; + } + + if (m_nNextTrack != m_nPlayingTrack) + { + m_bTrackChangeStarted = TRUE; + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + if (!(AudioManager.m_FrameCounter & 1)) { + if (m_bVerifyNextTrackStartedToPlay || !SampleManager.IsStreamPlaying()) { + bRadioStatsRecorded2 = FALSE; + if (SampleManager.IsStreamPlaying()) { + m_nPlayingTrack = m_nNextTrack; + m_bVerifyNextTrackStartedToPlay = FALSE; + m_bTrackChangeStarted = FALSE; + if (veh) { +#ifdef FIX_BUGS + if (m_nPlayingTrack >= STREAMED_SOUND_CITY_AMBIENT && m_nPlayingTrack <= STREAMED_SOUND_AMBSIL_AMBIENT) + veh->m_nRadioStation = RADIO_OFF; + else if (m_nPlayingTrack < STREAMED_SOUND_CITY_AMBIENT) + veh->m_nRadioStation = m_nPlayingTrack; +#else + if (veh->m_nRadioStation >= STREAMED_SOUND_CITY_AMBIENT && veh->m_nRadioStation <= STREAMED_SOUND_AMBSIL_AMBIENT) + veh->m_nRadioStation = RADIO_OFF; + else + veh->m_nRadioStation = m_nPlayingTrack; +#endif + } + } else { + uint32 pos = GetTrackStartPos(m_nNextTrack); + if (m_nNextTrack != NO_TRACK) { + SampleManager.SetStreamedFileLoopFlag(TRUE); + SampleManager.StartStreamedFile(m_nNextTrack, pos); + if (m_nFrontendTrack < STREAMED_SOUND_CITY_AMBIENT || m_nFrontendTrack > STREAMED_SOUND_AMBSIL_AMBIENT) + { + m_nVolumeLatency = 10; + m_nCurrentVolume = 0; + m_nMaxVolume = 100; + SampleManager.SetStreamedVolumeAndPan(m_nCurrentVolume, 63, FALSE); + } + else + { + ComputeAmbienceVol(TRUE, volume); + SampleManager.SetStreamedVolumeAndPan(volume, 63, TRUE); + } + if (m_nNextTrack < STREAMED_SOUND_CITY_AMBIENT) + m_nLastTrackServiceTime = CTimer::GetTimeInMillisecondsPauseMode(); + m_bVerifyNextTrackStartedToPlay = TRUE; + } + } + } else { + if (m_nPlayingTrack == NO_TRACK) + debug("m_nPlayingTrack == NO_TRACK, yet track playing - tidying up\n"); + else if (!bRadioStatsRecorded2) + { + m_aTracks[m_nPlayingTrack].m_nPosition = SampleManager.GetStreamedFilePosition(); + m_aTracks[m_nPlayingTrack].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + bRadioStatsRecorded2 = TRUE; + RecordRadioStats(); + if (m_nPlayingTrack >= STREAMED_SOUND_HAVANA_CITY_AMBIENT && m_nPlayingTrack <= STREAMED_SOUND_HAVANA_BEACH_AMBIENT) + { + if (m_nNextTrack >= STREAMED_SOUND_HAVANA_CITY_AMBIENT && m_nNextTrack <= STREAMED_SOUND_HAVANA_BEACH_AMBIENT) + AudioManager.PlayOneShot(AudioManager.m_nFrontEndEntity, SOUND_FRONTEND_HURRICANE, 0.0); + } + } + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + SampleManager.StopStreamedFile(); + } + } + return; + } + + if (m_nPlayingTrack >= STREAMED_SOUND_CITY_AMBIENT && m_nPlayingTrack <= STREAMED_SOUND_AMBSIL_AMBIENT) + { + ComputeAmbienceVol(FALSE, volume); + SampleManager.SetStreamedVolumeAndPan(volume, 63, TRUE); + return; + } + if (CTimer::GetIsSlowMotionActive()) + { + if (TheCamera.pTargetEntity) + { + 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 * m_nCurrentVolume; + if (AudioManager.ShouldDuckMissionAudio(0) || AudioManager.ShouldDuckMissionAudio(1)) + volume /= 4; + + uint8 pan = 0; + if (volume > 0) + { + CVector panVec; + AudioManager.TranslateEntity(&TheCamera.pTargetEntity->GetPosition(), &panVec); + pan = AudioManager.ComputePan(55.0f, &panVec); + } + if (gRetuneCounter != 0) + volume = 0; + SampleManager.SetStreamedVolumeAndPan(volume, pan, FALSE); + } + else if (AudioManager.ShouldDuckMissionAudio(0) || AudioManager.ShouldDuckMissionAudio(1)) + SampleManager.SetStreamedVolumeAndPan(m_nCurrentVolume, 63, FALSE); + else if (gRetuneCounter != 0) + SampleManager.SetStreamedVolumeAndPan(0, 63, FALSE); + else + SampleManager.SetStreamedVolumeAndPan(m_nCurrentVolume, 63, FALSE); + } + } else if (AudioManager.ShouldDuckMissionAudio(0) || AudioManager.ShouldDuckMissionAudio(1)) { + SampleManager.SetStreamedVolumeAndPan(Min(m_nCurrentVolume, 25), 63, FALSE); + nFramesSinceCutsceneEnded = 0; + } else { + if (nFramesSinceCutsceneEnded == -1) + volume = m_nCurrentVolume; + else if (nFramesSinceCutsceneEnded < 20) + { + volume = Min(m_nCurrentVolume, 25); + nFramesSinceCutsceneEnded++; + } + else if (nFramesSinceCutsceneEnded < 40) + { + volume = Min(m_nCurrentVolume, 3 * (nFramesSinceCutsceneEnded - 20) + 25); + nFramesSinceCutsceneEnded++; + } + else + { + volume = m_nCurrentVolume; + nFramesSinceCutsceneEnded = -1; + } + if (gRetuneCounter != 0) + volume = 0; + SampleManager.SetStreamedVolumeAndPan(volume, 63, FALSE); + } + if (m_nVolumeLatency > 0) + m_nVolumeLatency--; + else if (m_nCurrentVolume < m_nMaxVolume) + m_nCurrentVolume = Min(m_nMaxVolume, m_nCurrentVolume + 6); +} + +void +cMusicManager::PreloadCutSceneMusic(uint32 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_nPlayingTrack = 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_nPlayingTrack = NO_TRACK; + } +} + +void +cMusicManager::PlayFrontEndTrack(uint32 track, bool8 loopFlag) +{ + if (IsInitialised() && !m_bDisabled && track < TOTAL_STREAMED_SOUNDS && (m_nUpcomingMusicMode == MUSICMODE_FRONTEND || m_nMusicMode == MUSICMODE_FRONTEND)) + { + m_nFrontendTrack = track; + m_FrontendLoopFlag = loopFlag; + if (m_nMusicMode != MUSICMODE_FRONTEND) + m_bEarlyFrontendTrack = TRUE; + } +} + +void +cMusicManager::StopFrontEndTrack() +{ + if (m_nUpcomingMusicMode == MUSICMODE_FRONTEND || m_nMusicMode == MUSICMODE_FRONTEND) + m_nFrontendTrack = NO_TRACK; +} + +void +cMusicManager::PlayAnnouncement(uint32 announcement) +{ + if (IsInitialised() && !m_bDisabled && !m_bAnnouncementInProgress) + m_nAnnouncement = announcement; +} + +uint32 +cMusicManager::GetNextCarTuning() +{ + CVehicle *veh = AudioManager.FindVehicleOfPlayer(); + if (veh == nil) return STREAMED_SOUND_CITY_AMBIENT; + if (UsesPoliceRadio(veh)) return STREAMED_SOUND_RADIO_POLICE; + if (UsesTaxiRadio(veh)) return STREAMED_SOUND_RADIO_TAXI; + if (gNumRetunePresses != 0) { +#ifdef RADIO_SCROLL_TO_PREV_STATION + // m_nRadioStation is unsigned, so... + int station = veh->m_nRadioStation + gNumRetunePresses; + while(station < 0) station += NUM_RADIOS + 1; + while(station >= NUM_RADIOS + 1) station -= NUM_RADIOS + 1; + veh->m_nRadioStation = station; +#else + veh->m_nRadioStation += gNumRetunePresses; + while(veh->m_nRadioStation >= NUM_RADIOS + 1) + veh->m_nRadioStation -= NUM_RADIOS + 1; +#endif + DMAudio.IsMP3RadioChannelAvailable(); // woof, just call and do nothing =P they manipulate gNumRetunePresses on DisplayRadioStationName in this case + gNumRetunePresses = 0; + } + return veh->m_nRadioStation; +} + +uint32 +cMusicManager::GetCarTuning() +{ + CVehicle* veh = AudioManager.FindVehicleOfPlayer(); + if (veh == nil) return STREAMED_SOUND_CITY_AMBIENT; + if (UsesPoliceRadio(veh)) return STREAMED_SOUND_RADIO_POLICE; + if (UsesTaxiRadio(veh)) return STREAMED_SOUND_RADIO_TAXI; + if (veh->m_nRadioStation == USERTRACK && !SampleManager.IsMP3RadioChannelAvailable()) + veh->m_nRadioStation = AudioManager.m_anRandomTable[2] % USERTRACK; + return veh->m_nRadioStation; +} + +float* +cMusicManager::GetListenTimeArray() +{ + return aListenTimeArray; +} + +uint32 +cMusicManager::GetTrackStartPos(uint32 track) +{ + if (!IsInitialised()) return 0; + + uint32 pos = m_aTracks[track].m_nPosition; + if (CTimer::GetTimeInMillisecondsPauseMode() > m_aTracks[track].m_nLastPosCheckTimer) + pos += Min(CTimer::GetTimeInMillisecondsPauseMode() - m_aTracks[track].m_nLastPosCheckTimer, 270000); + else + m_aTracks[track].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + + if (pos > m_aTracks[track].m_nLength) + pos %= m_aTracks[track].m_nLength; + return pos; +} + +uint32 +cMusicManager::GetRadioPosition(uint32 station) +{ + if (station < NUM_RADIOS) + return GetTrackStartPos(station); + return 0; +} + +uint32 +cMusicManager::GetFavouriteRadioStation() +{ + uint32 favstation = 0; + + for (int i = 1; i < NUM_RADIOS; i++) { + if (aListenTimeArray[i] > aListenTimeArray[favstation]) + favstation = i; + } + + return favstation; +} + +bool8 +cMusicManager::CheckForMusicInterruptions() +{ + return (m_nPlayingTrack == STREAMED_SOUND_MISSION_COMPLETED) || (m_nPlayingTrack == STREAMED_SOUND_CUTSCENE_FINALE); +} + +void +cMusicManager::SetMalibuClubTrackPos(uint8 scriptObject) +{ + if (!IsInitialised()) + m_aTracks[STREAMED_SOUND_MALIBU_AMBIENT].m_nPosition = 8640; + if (m_nNextTrack != STREAMED_SOUND_MALIBU_AMBIENT && m_nPlayingTrack != STREAMED_SOUND_MALIBU_AMBIENT) { + switch (scriptObject) + { + case SCRIPT_SOUND_NEW_BUILDING_MALIBU_1: + m_aTracks[STREAMED_SOUND_MALIBU_AMBIENT].m_nPosition = (AudioManager.m_anRandomTable[0] % 128) + 8640; + break; + case SCRIPT_SOUND_NEW_BUILDING_MALIBU_2: + m_aTracks[STREAMED_SOUND_MALIBU_AMBIENT].m_nPosition = (AudioManager.m_anRandomTable[0] % 128) + 286720; + break; + case SCRIPT_SOUND_NEW_BUILDING_MALIBU_3: + m_aTracks[STREAMED_SOUND_MALIBU_AMBIENT].m_nPosition = (AudioManager.m_anRandomTable[0] % 128) + 509120; + break; + } + m_aTracks[STREAMED_SOUND_MALIBU_AMBIENT].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } +} + +void +cMusicManager::SetStripClubTrackPos(uint8 scriptObject) +{ + if (!IsInitialised()) + m_aTracks[STREAMED_SOUND_STRIPCLUB_AMBIENT].m_nPosition = 0; + if (m_nNextTrack != STREAMED_SOUND_STRIPCLUB_AMBIENT && m_nPlayingTrack != STREAMED_SOUND_STRIPCLUB_AMBIENT) + { + switch (scriptObject) + { + case SCRIPT_SOUND_NEW_BUILDING_STRIP_1: + m_aTracks[STREAMED_SOUND_STRIPCLUB_AMBIENT].m_nPosition = AudioManager.m_anRandomTable[0] % 128; + break; + case SCRIPT_SOUND_NEW_BUILDING_STRIP_2: + m_aTracks[STREAMED_SOUND_STRIPCLUB_AMBIENT].m_nPosition = (AudioManager.m_anRandomTable[0] % 128) + 320200; + break; + case SCRIPT_SOUND_NEW_BUILDING_STRIP_3: + m_aTracks[STREAMED_SOUND_STRIPCLUB_AMBIENT].m_nPosition = (AudioManager.m_anRandomTable[0] % 128) + 672000; + break; + } + m_aTracks[STREAMED_SOUND_STRIPCLUB_AMBIENT].m_nLastPosCheckTimer = CTimer::GetTimeInMillisecondsPauseMode(); + } +} + +void +cMusicManager::DisplayRadioStationName() +{ + uint8 gStreamedSound; + static wchar *pCurrentStation = nil; + static uint8 cDisplay = 0; + + if(!CTimer::GetIsPaused() && !TheCamera.m_WideScreenOn && PlayerInCar() && + !CReplay::IsPlayingBack()) { + CVehicle *vehicle = AudioManager.FindVehicleOfPlayer(); + + if (vehicle) + { + // Prev scroll needs it to be signed, and m_nFrontendTrack can be NO_TRACK thus FIX_BUGS +#if defined RADIO_SCROLL_TO_PREV_STATION || defined FIX_BUGS + int track; +#else + uint8 track; +#endif + gStreamedSound = vehicle->m_nRadioStation; + if (gStreamedSound >= STREAMED_SOUND_CITY_AMBIENT && gStreamedSound <= STREAMED_SOUND_AMBSIL_AMBIENT) + gStreamedSound = RADIO_OFF; + if (gNumRetunePresses != 0) + { + track = gNumRetunePresses + gStreamedSound; +#ifdef RADIO_SCROLL_TO_PREV_STATION + while (track < 0) track += NUM_RADIOS + 1; +#endif + while (track >= NUM_RADIOS + 1) track -= NUM_RADIOS + 1; + + // On scrolling back we handle this condition on key press. No need to change this. + if (!DMAudio.IsMP3RadioChannelAvailable() && track == USERTRACK) + gNumRetunePresses++; + } + else +#ifdef RADIO_OFF_TEXT + track = GetCarTuning(); // gStreamedSound or veh->m_nRadioStation would also work, but these don't cover police/taxi radios +#else + track = m_nFrontendTrack; +#endif + wchar* string = nil; + switch (track) { + case WILDSTYLE: string = TheText.Get("FEA_FM0"); break; + case FLASH_FM: string = TheText.Get("FEA_FM1"); break; + case KCHAT: string = TheText.Get("FEA_FM2"); break; + case FEVER: string = TheText.Get("FEA_FM3"); break; + case V_ROCK: string = TheText.Get("FEA_FM4"); break; + case VCPR: string = TheText.Get("FEA_FM5"); break; + case RADIO_ESPANTOSO: string = TheText.Get("FEA_FM6"); break; + case EMOTION: string = TheText.Get("FEA_FM7"); break; + case WAVE: string = TheText.Get("FEA_FM8"); break; + case USERTRACK: + if (!SampleManager.IsMP3RadioChannelAvailable()) + return; + string = TheText.Get("FEA_MP3"); break; +#ifdef RADIO_OFF_TEXT + case RADIO_OFF: { + // Otherwise RADIO OFF will be seen after pausing-resuming game and Mission Complete text + if (!m_bRadioStreamReady || !m_bGameplayAllowsRadio) + return; + + extern wchar WideErrorString[]; + + string = TheText.Get("FEA_NON"); + if (string == WideErrorString) { + pCurrentStation = nil; + return; + } + break; + } +#endif + default: return; + }; + + if (pCurrentStation != string) { + 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_STANDARD); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_WIDTH / 2 + SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(22.0f) + SCREEN_SCALE_Y(2.0f), pCurrentStation); + + 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(); + } + } + // Always show station text after entering car. Same behaviour as III and SA. +#ifdef FIX_BUGS + else + pCurrentStation = nil; +#endif +} + +bool8 +cMusicManager::UsesPoliceRadio(CVehicle *veh) +{ + switch (veh->GetModelIndex()) + { + case MI_VCNMAV: + case MI_POLMAV: + case MI_COASTG: + case MI_RHINO: + case MI_BARRACKS: + return TRUE; + case MI_MRWHOOP: + case MI_HUNTER: + return FALSE; + } + return veh->UsesSiren(); +} + +bool8 +cMusicManager::UsesTaxiRadio(CVehicle *veh) +{ + if (veh->GetModelIndex() != MI_KAUFMAN) return FALSE; + return CTheScripts::bPlayerHasMetDebbieHarry; +} + +void +cMusicManager::ServiceAmbience() +{ +} + +bool8 +cMusicManager::ChangeRadioChannel() +{ + return TRUE; +} + +// these two are empty +void cMusicManager::Enable() {} +void cMusicManager::Disable() {} diff --git a/src/miami/audio/MusicManager.h b/src/miami/audio/MusicManager.h new file mode 100644 index 00000000..52254b19 --- /dev/null +++ b/src/miami/audio/MusicManager.h @@ -0,0 +1,113 @@ +#pragma once + +#include "audio_enums.h" + +class tStreamedSample +{ +public: + uint32 m_nLength; + uint32 m_nPosition; + uint32 m_nLastPosCheckTimer; +}; + +class CVehicle; +class CPed; + +class cMusicManager +{ +public: + bool8 m_bIsInitialised; + bool8 m_bDisabled; + bool8 m_bSetNextStation; + uint8 m_nVolumeLatency; + uint8 m_nCurrentVolume; + uint8 m_nMaxVolume; + uint32 m_nAnnouncement; + bool8 m_bAnnouncementInProgress; + tStreamedSample m_aTracks[TOTAL_STREAMED_SOUNDS]; + bool8 m_bResetTimers; + uint32 m_nResetTime; + bool8 m_bRadioSetByScript; + uint8 m_nRadioStationScript; + int32 m_nRadioPosition; + uint32 m_nRadioInCar; + uint32 m_nFrontendTrack; + uint32 m_nPlayingTrack; + uint8 m_nUpcomingMusicMode; + uint8 m_nMusicMode; + bool8 m_FrontendLoopFlag; + bool8 m_bTrackChangeStarted; + uint32 m_nNextTrack; + bool8 m_nNextLoopFlag; + bool8 m_bVerifyNextTrackStartedToPlay; + bool8 m_bGameplayAllowsRadio; + bool8 m_bRadioStreamReady; + int8 nFramesSinceCutsceneEnded; + bool8 m_bUserResumedGame; + bool8 m_bMusicModeChangeStarted; + uint8 m_nMusicModeToBeSet; + bool8 m_bEarlyFrontendTrack; + float aListenTimeArray[NUM_RADIOS]; + float m_nLastTrackServiceTime; + +public: + cMusicManager(); + bool8 IsInitialised() { return m_bIsInitialised; } + uint8 GetMusicMode() { return m_nMusicMode; } + uint32 GetCurrentTrack() { return m_nPlayingTrack; } + + void ResetMusicAfterReload(); + void SetStartingTrackPositions(bool8 isNewGameTimer); + bool8 Initialise(); + void Terminate(); + + void ChangeMusicMode(uint8 mode); + void StopFrontEndTrack(); + + bool8 PlayerInCar(); + void DisplayRadioStationName(); + + void PlayAnnouncement(uint32); + void PlayFrontEndTrack(uint32, bool8); + void PreloadCutSceneMusic(uint32); + void PlayPreloadedCutSceneMusic(void); + void StopCutSceneMusic(void); + uint32 GetRadioInCar(void); + void SetRadioInCar(uint32); + void SetRadioChannelByScript(uint32, int32); + + void ResetTimers(uint32); + void Service(); + void ServiceFrontEndMode(); + void ServiceGameMode(); + void ServiceAmbience(); + void ServiceTrack(CVehicle *veh, CPed *ped); + + bool8 UsesPoliceRadio(CVehicle *veh); + bool8 UsesTaxiRadio(CVehicle *veh); + uint32 GetTrackStartPos(uint32 track); + + void ComputeAmbienceVol(bool8 reset, uint8& outVolume); + bool8 ServiceAnnouncement(); + + uint32 GetCarTuning(); + uint32 GetNextCarTuning(); + bool8 ChangeRadioChannel(); + void RecordRadioStats(); + void SetUpCorrectAmbienceTrack(); + float *GetListenTimeArray(); + uint32 GetRadioPosition(uint32 station); + uint32 GetFavouriteRadioStation(); + void SetMalibuClubTrackPos(uint8 pos); + void SetStripClubTrackPos(uint8 pos); + bool8 CheckForMusicInterruptions(); + + void Enable(); + void Disable(); +}; + +VALIDATE_SIZE(cMusicManager, 0x95C); + +extern cMusicManager MusicManager; +extern bool8 g_bAnnouncementReadPosAlready; // we have a symbol of this so it was declared in .h +float GetHeightScale(); diff --git a/src/miami/audio/PolRadio.cpp b/src/miami/audio/PolRadio.cpp new file mode 100644 index 00000000..26ade81e --- /dev/null +++ b/src/miami/audio/PolRadio.cpp @@ -0,0 +1,750 @@ +#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]; + +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, "VICE_C", SFX_POLICE_RADIO_VICE_CITY); + SETZONESFX(1, "IND_ZON", SFX_POLICE_RADIO_VICE_CITY_BEACH); + SETZONESFX(2, "COM_ZON", SFX_POLICE_RADIO_VICE_CITY_MAINLAND); + SETZONESFX(3, "BEACH1", SFX_POLICE_RADIO_OCEAN_BEACH); + SETZONESFX(4, "BEACH2", SFX_POLICE_RADIO_WASHINGTON_BEACH); + SETZONESFX(5, "BEACH3", SFX_POLICE_RADIO_VICE_POINT); + SETZONESFX(6, "GOLFC", SFX_POLICE_RADIO_LEAF_LINKS); + SETZONESFX(7, "STARI", SFX_POLICE_RADIO_STARFISH_ISLAND); + SETZONESFX(8, "DOCKS", SFX_POLICE_RADIO_VICEPORT); + SETZONESFX(9, "HAVANA", SFX_POLICE_RADIO_LITTLE_HAVANA); + SETZONESFX(10, "HAITI", SFX_POLICE_RADIO_LITTLE_HAITI); + SETZONESFX(11, "PORNI", SFX_POLICE_RADIO_PRAWN_ISLAND); + SETZONESFX(12, "DTOWN", SFX_POLICE_RADIO_DOWNTOWN); + SETZONESFX(13, "A_PORT", SFX_POLICE_RADIO_ESCOBAR_INTERNATIONAL); + +#undef SETZONESFX +} + +void +cAudioManager::InitialisePoliceRadio() +{ + m_sPoliceRadioQueue.Reset(); + for (int32 i = 0; i < ARRAY_SIZE(m_aCrimes); i++) + m_aCrimes[i].type = CRIME_NONE; +#if !defined(GTA_PS2) || defined(AUDIO_REVERB) + SampleManager.SetChannelReverbFlag(CHANNEL_POLICE_RADIO, FALSE); +#endif + 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; + SET_SOUND_REVERB(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 + CPlayerPed *playerPed = FindPlayerPed(); + if (playerPed) { + wantedLevel = playerPed->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 = 22050; + + if (!m_bIsInitialised) return; + + if (m_bIsPaused) { + if (SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) SampleManager.StopChannel(CHANNEL_POLICE_RADIO); + if (g_nMissionAudioSfx != NO_SAMPLE && bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING && + SampleManager.IsStreamPlaying(1)) { + SampleManager.PauseStream(TRUE, 1); + } + } else { + if (m_bWasPaused && g_nMissionAudioSfx != NO_SAMPLE && + bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING) { + SampleManager.PauseStream(FALSE, 1); + } + if (m_sPoliceRadioQueue.m_nSamplesInQueue == 0) bChannelOpen = FALSE; + if (cWait) { +#ifdef FIX_BUGS + cWait -= CTimer::GetLogicalFramesPassed(); +#else + --cWait; +#endif + return; + } + if (g_nMissionAudioSfx != NO_SAMPLE && !bChannelOpen) { + if (g_nMissionAudioPlayingStatus != PLAY_STATUS_STOPPED) { + if (g_nMissionAudioPlayingStatus == PLAY_STATUS_PLAYING && bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_STOPPED && + SampleManager.IsStreamPlaying(1)) { + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_PLAYING; + } + if (bMissionAudioPhysicalPlayingStatus == PLAY_STATUS_PLAYING) { + if (SampleManager.IsStreamPlaying(1)) { + DoPoliceRadioCrackle(); + } else { + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_FINISHED; + g_nMissionAudioPlayingStatus = PLAY_STATUS_FINISHED; + g_nMissionAudioSfx = NO_SAMPLE; + cWait = 30; + } + return; + } + } else if (!SampleManager.GetChannelUsedFlag(CHANNEL_POLICE_RADIO)) { + SampleManager.PreloadStreamedFile(g_nMissionAudioSfx, 1); + SampleManager.SetStreamedVolumeAndPan(MAX_VOLUME, 63, TRUE, 1); + SampleManager.StartPreloadedStreamedFile(1); + g_nMissionAudioPlayingStatus = PLAY_STATUS_PLAYING; + bMissionAudioPhysicalPlayingStatus = PLAY_STATUS_STOPPED; + return; + } + } + if (bChannelOpen) DoPoliceRadioCrackle(); + if ((g_nMissionAudioSfx == NO_SAMPLE || 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) { + bChannelOpen = FALSE; + processed = TRUE; + } + } + if (sample == NO_SAMPLE) { + if (!processed) cWait = 30; + } else { + SampleManager.InitialiseChannel(CHANNEL_POLICE_RADIO, sample, SFX_BANK_0); + switch (sample) { + case SFX_POLICE_RADIO_MESSAGE_NOISE_1: + 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(SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(m_anRandomTable[0] % 3 + SFX_WEVE_GOT); + m_sPoliceRadioQueue.Add(SFX_A_10); + switch (m_aCrimes[i].type) { + case CRIME_PED_BURNED: + case CRIME_HIT_PED_NASTYWEAPON: + m_aCrimes[i].type = CRIME_HIT_PED; + break; + case CRIME_COP_BURNED: + case CRIME_HIT_COP_NASTYWEAPON: + 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; + case CRIME_EXPLOSION: m_aCrimes[i].type = CRIME_STEAL_CAR; break; // huh? + default: break; + } +#ifdef FIX_BUGS + m_sPoliceRadioQueue.Add(m_aCrimes[i].type + SFX_CRIME_1 - 1); +#else + m_sPoliceRadioQueue.Add(m_aCrimes[i].type + SFX_CRIME_1); +#endif + m_sPoliceRadioQueue.Add(SFX_IN); + 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(SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(NO_SAMPLE); + break; + } + } + } + m_aCrimes[i].type = CRIME_NONE; + AgeCrimes(); + return TRUE; +} + +Const uint32 gCarColourTable[][3] = { + {NO_SAMPLE, SFX_POLICE_RADIO_BLACK, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_WHITE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_RED, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_PURPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_YELLOW, NO_SAMPLE}, + {SFX_POLICE_RADIO_BRIGHT, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, SFX_POLICE_RADIO_BLUE, SFX_POLICE_RADIO_GREY}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, SFX_POLICE_RADIO_RED, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_RED, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_RED, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_RED, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_RED, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_RED, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_RED, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_ORANGE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_ORANGE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_ORANGE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_ORANGE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_ORANGE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_ORANGE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_YELLOW, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_YELLOW, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_YELLOW, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_YELLOW, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_YELLOW, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_YELLOW, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_GREEN, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_GREEN, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_GREEN, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_GREEN, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_GREEN, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_GREEN, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_BLUE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_PURPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_PURPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_PURPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_PURPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_PURPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_PURPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_SILVER, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_SILVER, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_SILVER, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_SILVER, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_SILVER, NO_SAMPLE}, + {NO_SAMPLE, SFX_POLICE_RADIO_SILVER, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_LIGHT, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE}, + {SFX_POLICE_RADIO_DARK, NO_SAMPLE, NO_SAMPLE} +}; + +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 = FindVehicleOfPlayer(); + 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()) { + case MI_LANDSTAL: + case MI_PATRIOT: + case MI_RANCHER: + case MI_FBIRANCH: + case MI_SANDKING: + sample = SFX_POLICE_RADIO_OFFROAD; + break; + case MI_IDAHO: + case MI_MANANA: + case MI_ESPERANT: + case MI_CUBAN: + case MI_STALLION: + case MI_SABRE: + case MI_SABRETUR: + case MI_VIRGO: + case MI_BLISTAC: + sample = SFX_POLICE_RADIO_TUDOOR; + break; + case MI_STINGER: + case MI_INFERNUS: + case MI_CHEETAH: + case MI_BANSHEE: + case MI_PHEONIX: + case MI_COMET: + case MI_DELUXO: + case MI_HOTRING: + sample = SFX_POLICE_RADIO_SPORTS_CAR; + break; + case MI_LINERUN: + sample = SFX_POLICE_RADIO_RIG; + break; + case MI_PEREN: + case MI_REGINA: + sample = SFX_POLICE_RADIO_STATION_WAGON; + break; + case MI_SENTINEL: + case MI_FBICAR: + case MI_WASHING: + case MI_SENTXS: + case MI_ADMIRAL: + case MI_GLENDALE: + case MI_OCEANIC: + case MI_HERMES: + case MI_GREENWOO: + sample = SFX_POLICE_RADIO_SEDAN; + break; + case MI_RIO: + sample = SFX_POLICE_RADIO_CRUISER; + break; + case MI_FIRETRUCK: + sample = SFX_POLICE_RADIO_FIRE_TRUCK; + break; + case MI_TRASH: + sample = SFX_POLICE_RADIO_GARBAGE_TRUCK; + break; + case MI_STRETCH: + case MI_LOVEFIST: + sample = SFX_POLICE_RADIO_STRETCH; + break; + case MI_VOODOO: + sample = SFX_POLICE_RADIO_LOWRIDER; + break; + case MI_PONY: + case MI_MOONBEAM: + case MI_SECURICA: + case MI_RUMPO: + case MI_GANGBUR: + case MI_YANKEE: + case MI_TOPFUN: + case MI_BURRITO: + case MI_SPAND: + sample = SFX_POLICE_RADIO_VAN; + break; + case MI_MULE: + case MI_BARRACKS: + case MI_PACKER: + case MI_FLATBED: + sample = SFX_POLICE_RADIO_TRUCK; + break; + case MI_AMBULAN: + sample = SFX_POLICE_RADIO_AMBULANCE; + break; + case MI_TAXI: + case MI_CABBIE: + case MI_ZEBRA: + case MI_KAUFMAN: + sample = SFX_POLICE_RADIO_TAXI; + break; + case MI_BOBCAT: + case MI_WALTON: + sample = SFX_POLICE_RADIO_PICKUP; + break; + case MI_MRWHOOP: + sample = SFX_POLICE_RADIO_ICE_CREAM_VAN; + break; + case MI_BFINJECT: + sample = SFX_POLICE_RADIO_BUGGY; + break; + case MI_HUNTER: + case MI_CHOPPER: + case MI_SEASPAR: + case MI_SPARROW: + case MI_MAVERICK: + case MI_VCNMAV: + case MI_POLMAV: + sample = SFX_POLICE_RADIO_HELICOPTER; + break; + case MI_POLICE: + sample = SFX_POLICE_RADIO_POLICE_CAR; + break; + case MI_ENFORCER: + sample = SFX_POLICE_RADIO_SWAT_VAN; + break; + case MI_PREDATOR: + case MI_SQUALO: + case MI_SPEEDER: + sample = SFX_POLICE_RADIO_SPEEDBOAT; + break; + case MI_BUS: + sample = SFX_POLICE_RADIO_BUS; + break; + case MI_RHINO: + sample = SFX_POLICE_RADIO_TANK; + break; + case MI_ANGEL: + case MI_PCJ600: + case MI_FREEWAY: + case MI_SANCHEZ: + sample = SFX_POLICE_RADIO_MOTOBIKE; + break; + case MI_COACH: + sample = SFX_POLICE_RADIO_COACH; + break; + case MI_ROMERO: + sample = SFX_POLICE_RADIO_HEARSE; + break; + case MI_PIZZABOY: + case MI_FAGGIO: + sample = SFX_POLICE_RADIO_MOPED; + break; + case MI_DEADDODO: + case MI_SKIMMER: + sample = SFX_POLICE_RADIO_PLANE; + break; + case MI_REEFER: + case MI_TROPIC: + case MI_COASTG: + case MI_MARQUIS: + case MI_JETMAX: + sample = SFX_POLICE_RADIO_BOAT; + break; + case MI_CADDY: + sample = SFX_POLICE_RADIO_GOLF_CART; + break; + case MI_DINGHY: + sample = SFX_POLICE_RADIO_DINGHY; + break; + default: + //debug("\n *** UNKNOWN CAR MODEL INDEX %d *** ", veh->GetModelIndex()); + return; + } + m_sPoliceRadioQueue.Add(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); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_IN_A); + if (color_pre_modifier != NO_SAMPLE) + m_sPoliceRadioQueue.Add(color_pre_modifier); + if (main_color != NO_SAMPLE) + m_sPoliceRadioQueue.Add(main_color); + if (color_post_modifier != NO_SAMPLE) + m_sPoliceRadioQueue.Add(color_post_modifier); + m_sPoliceRadioQueue.Add(sample); + m_sPoliceRadioQueue.Add(SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(NO_SAMPLE); + } + } + } 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(SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(NO_SAMPLE); + } + } +} + +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(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); + 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(SFX_POLICE_RADIO_MESSAGE_NOISE_1); + m_sPoliceRadioQueue.Add(NO_SAMPLE); + 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 > 1200) m_aCrimes[i].type = CRIME_NONE; + } + } +} diff --git a/src/miami/audio/PolRadio.h b/src/miami/audio/PolRadio.h new file mode 100644 index 00000000..55b2c9f7 --- /dev/null +++ b/src/miami/audio/PolRadio.h @@ -0,0 +1,67 @@ +#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 NO_SAMPLE; + } + +}; + +VALIDATE_SIZE(cPoliceRadioQueue, 244); diff --git a/src/miami/audio/audio_enums.h b/src/miami/audio/audio_enums.h new file mode 100644 index 00000000..22b425e7 --- /dev/null +++ b/src/miami/audio/audio_enums.h @@ -0,0 +1,1336 @@ +#pragma once + +enum eRadioStation +{ + WILDSTYLE, + FLASH_FM, + KCHAT, + FEVER, + V_ROCK, + VCPR, + RADIO_ESPANTOSO, + EMOTION, + WAVE, + USERTRACK, + NUM_RADIOS = 10, + POLICE_RADIO = 10, + RADIO_OFF = 10, + //TAXI_RADIO, +}; + +enum eMusicMode +{ + MUSICMODE_FRONTEND = 0, + MUSICMODE_GAME, + MUSICMODE_CUTSCENE, + MUSICMODE_DISABLE, + MUSICMODE_DISABLED, +}; + +enum ePlayerMood +{ + PLAYER_MOOD_CALM = 0, + PLAYER_MOOD_PISSED_OFF, + PLAYER_MOOD_ANGRY, + PLAYER_MOOD_WISECRACKING, + MAX_PLAYER_MOODS, +}; + +enum eStreamedSounds +{ + STREAMED_SOUND_RADIO_WILD, + STREAMED_SOUND_RADIO_FLASH, + STREAMED_SOUND_RADIO_KCHAT, + STREAMED_SOUND_RADIO_FEVER, + STREAMED_SOUND_RADIO_VROCK, + STREAMED_SOUND_RADIO_VCPR, + STREAMED_SOUND_RADIO_ESPANTOSO, + STREAMED_SOUND_RADIO_EMOTION, + STREAMED_SOUND_RADIO_WAVE, + STREAMED_SOUND_RADIO_MP3_PLAYER, + STREAMED_SOUND_CITY_AMBIENT, + STREAMED_SOUND_WATER_AMBIENT, + STREAMED_SOUND_BEACH_AMBIENT, + STREAMED_SOUND_HAVANA_CITY_AMBIENT, + STREAMED_SOUND_HAVANA_WATER_AMBIENT, + STREAMED_SOUND_HAVANA_BEACH_AMBIENT, + STREAMED_SOUND_MALL_AMBIENT, + STREAMED_SOUND_STRIPCLUB_AMBIENT, + STREAMED_SOUND_MALIBU_AMBIENT, + STREAMED_SOUND_HOTEL_AMBIENT, + STREAMED_SOUND_DIRTRING_AMBIENT, + STREAMED_SOUND_LAW4RIOT_AMBIENT, + STREAMED_SOUND_AMBSIL_AMBIENT, + STREAMED_SOUND_RADIO_POLICE, + STREAMED_SOUND_RADIO_TAXI, + STREAMED_SOUND_ANNOUNCE_BRIDGE_CLOSED, + STREAMED_SOUND_ANNOUNCE_BRIDGE_OPEN, + STREAMED_SOUND_CUTSCENE_ASS_1, + STREAMED_SOUND_CUTSCENE_ASS_2, + STREAMED_SOUND_CUTSCENE_BANK_1, + STREAMED_SOUND_CUTSCENE_BANK_2A, + STREAMED_SOUND_CUTSCENE_BANK_2B, + STREAMED_SOUND_CUTSCENE_BANK_3A, + STREAMED_SOUND_CUTSCENE_BANK_3B, + STREAMED_SOUND_CUTSCENE_BANK_4, + STREAMED_SOUND_CUTSCENE_BIKE_1, + STREAMED_SOUND_CUTSCENE_BIKE_2, + STREAMED_SOUND_CUTSCENE_BIKE_3, + STREAMED_SOUND_CUTSCENE_BUD_1, + STREAMED_SOUND_CUTSCENE_BUD_2, + STREAMED_SOUND_CUTSCENE_BUD_3, + STREAMED_SOUND_CUTSCENE_CAP_1, + STREAMED_SOUND_CUTSCENE_CAR_1, + STREAMED_SOUND_CUTSCENE_CNT_1A, + STREAMED_SOUND_CUTSCENE_CNT_1B, + STREAMED_SOUND_CUTSCENE_CNT_2, + STREAMED_SOUND_CUTSCENE_COK_1, + STREAMED_SOUND_CUTSCENE_COK_2A, + STREAMED_SOUND_CUTSCENE_COK_2B, + STREAMED_SOUND_CUTSCENE_COK_3, + STREAMED_SOUND_CUTSCENE_COK_4A, + STREAMED_SOUND_CUTSCENE_COK_4A2, + STREAMED_SOUND_CUTSCENE_COK_4B, + STREAMED_SOUND_CUTSCENE_COL_1, + STREAMED_SOUND_CUTSCENE_COL_2, + STREAMED_SOUND_CUTSCENE_COL_3A, + STREAMED_SOUND_CUTSCENE_COL_4A, + STREAMED_SOUND_CUTSCENE_COL_5A, + STREAMED_SOUND_CUTSCENE_COL_5B, + STREAMED_SOUND_CUTSCENE_CUB_1, + STREAMED_SOUND_CUTSCENE_CUB_2, + STREAMED_SOUND_CUTSCENE_CUB_3, + STREAMED_SOUND_CUTSCENE_CUB_4, + STREAMED_SOUND_CUTSCENE_DRUG_1, + STREAMED_SOUND_CUTSCENE_FIN, + STREAMED_SOUND_CUTSCENE_FIN2, + STREAMED_SOUND_CUTSCENE_FINALE, + STREAMED_SOUND_CUTSCENE_HAT_1, + STREAMED_SOUND_CUTSCENE_HAT_2, + STREAMED_SOUND_CUTSCENE_HAT_3, + STREAMED_SOUND_CUTSCENE_ICE_1, + STREAMED_SOUND_CUTSCENE_INT_A, + STREAMED_SOUND_CUTSCENE_INT_B, + STREAMED_SOUND_CUTSCENE_INT_D, + STREAMED_SOUND_CUTSCENE_INT_M, + STREAMED_SOUND_CUTSCENE_LAW_1A, + STREAMED_SOUND_CUTSCENE_LAW_1B, + STREAMED_SOUND_CUTSCENE_LAW_2A, + STREAMED_SOUND_CUTSCENE_LAW_2B, + STREAMED_SOUND_CUTSCENE_LAW_2C, + STREAMED_SOUND_CUTSCENE_LAW_3, + STREAMED_SOUND_CUTSCENE_LAW_4, + STREAMED_SOUND_CUTSCENE_PHIL_1, + STREAMED_SOUND_CUTSCENE_PHIL_2, + STREAMED_SOUND_CUTSCENE_PORN_1, + STREAMED_SOUND_CUTSCENE_PORN_2, + STREAMED_SOUND_CUTSCENE_PORN_3, + STREAMED_SOUND_CUTSCENE_PORN_4, + STREAMED_SOUND_CUTSCENE_RESC_1A, + STREAMED_SOUND_CUTSCENE_ROK_1, + STREAMED_SOUND_CUTSCENE_ROK_2, + STREAMED_SOUND_CUTSCENE_ROK_3A, + STREAMED_SOUND_CUTSCENE_STRIPA, + STREAMED_SOUND_CUTSCENE_TAX_1, + STREAMED_SOUND_CUTSCENE_TEX_1, + STREAMED_SOUND_CUTSCENE_TEX_2, + STREAMED_SOUND_CUTSCENE_TEX_3, + STREAMED_SOUND_CUTSCENE_GLIGHT, + STREAMED_SOUND_CUTSCENE_FIST, + STREAMED_SOUND_CUTSCENE_ELBURRO1_PH1, + STREAMED_SOUND_CUTSCENE_ELBURRO2_PH2, + STREAMED_SOUND_MISSION_COMPLETED, + STREAMED_SOUND_MISSION_COMPLETED4, + STREAMED_SOUND_MISSION_MOBR1, + STREAMED_SOUND_MISSION_PAGER, + STREAMED_SOUND_MISSION_CARREV, + STREAMED_SOUND_MISSION_BIKEREV, + STREAMED_SOUND_MISSION_LIFTOP, + STREAMED_SOUND_MISSION_LIFTCL, + STREAMED_SOUND_MISSION_LIFTRUN, + STREAMED_SOUND_MISSION_LIFTBEL, + STREAMED_SOUND_MISSION_INLIFT, + STREAMED_SOUND_MISSION_SFX_01, + STREAMED_SOUND_MISSION_SFX_02, + STREAMED_SOUND_MISSION_CAMERAL, + STREAMED_SOUND_MISSION_CAMERAR, + STREAMED_SOUND_MISSION_CHEER1, + STREAMED_SOUND_MISSION_CHEER2, + STREAMED_SOUND_MISSION_CHEER3, + STREAMED_SOUND_MISSION_CHEER4, + STREAMED_SOUND_MISSION_OOH1, + STREAMED_SOUND_MISSION_OOH2, + STREAMED_SOUND_MISSION_RACE1, + STREAMED_SOUND_MISSION_RACE2, + STREAMED_SOUND_MISSION_RACE3, + STREAMED_SOUND_MISSION_RACE4, + STREAMED_SOUND_MISSION_RACE5, + STREAMED_SOUND_MISSION_RACE6, + STREAMED_SOUND_MISSION_RACE7, + STREAMED_SOUND_MISSION_RACE8, + STREAMED_SOUND_MISSION_RACE9, + STREAMED_SOUND_MISSION_RACE10, + STREAMED_SOUND_MISSION_RACE11, + STREAMED_SOUND_MISSION_RACE12, + STREAMED_SOUND_MISSION_RACE13, + STREAMED_SOUND_MISSION_RACE14, + STREAMED_SOUND_MISSION_RACE15, + STREAMED_SOUND_MISSION_HOT1, + STREAMED_SOUND_MISSION_HOT2, + STREAMED_SOUND_MISSION_HOT3, + STREAMED_SOUND_MISSION_HOT4, + STREAMED_SOUND_MISSION_HOT5, + STREAMED_SOUND_MISSION_HOT6, + STREAMED_SOUND_MISSION_HOT7, + STREAMED_SOUND_MISSION_HOT8, + STREAMED_SOUND_MISSION_HOT9, + STREAMED_SOUND_MISSION_HOT10, + STREAMED_SOUND_MISSION_HOT11, + STREAMED_SOUND_MISSION_HOT12, + STREAMED_SOUND_MISSION_HOT13, + STREAMED_SOUND_MISSION_HOT14, + STREAMED_SOUND_MISSION_HOT15, + STREAMED_SOUND_MISSION_LANSTP1, + STREAMED_SOUND_MISSION_LANSTP2, + STREAMED_SOUND_MISSION_LANAMU1, + STREAMED_SOUND_MISSION_LANAMU2, + STREAMED_SOUND_MISSION_AIRHORNL, + STREAMED_SOUND_MISSION_AIRHORNR, + STREAMED_SOUND_MISSION_SNIPSCRL, + STREAMED_SOUND_MISSION_SNIPSHORT, + STREAMED_SOUND_MISSION_BLOWROOF, + STREAMED_SOUND_MISSION_ASS_1, + STREAMED_SOUND_MISSION_ASS_2, + STREAMED_SOUND_MISSION_ASS_3, + STREAMED_SOUND_MISSION_ASS_4, + STREAMED_SOUND_MISSION_ASS_5, + STREAMED_SOUND_MISSION_ASS_6, + STREAMED_SOUND_MISSION_ASS_7, + STREAMED_SOUND_MISSION_ASS_8, + STREAMED_SOUND_MISSION_ASS_9, + STREAMED_SOUND_MISSION_ASS_10, + STREAMED_SOUND_MISSION_ASS_11, + STREAMED_SOUND_MISSION_ASS_12, + STREAMED_SOUND_MISSION_ASS_13, + STREAMED_SOUND_MISSION_ASS_14, + STREAMED_SOUND_MISSION_BIKE1_1, + STREAMED_SOUND_MISSION_BIKE1_2, + STREAMED_SOUND_MISSION_BIKE1_3, + STREAMED_SOUND_MISSION_BNK1_1, + STREAMED_SOUND_MISSION_BNK1_2, + STREAMED_SOUND_MISSION_BNK1_3, + STREAMED_SOUND_MISSION_BNK1_4, + STREAMED_SOUND_MISSION_BNK1_5, + STREAMED_SOUND_MISSION_BNK1_6, + STREAMED_SOUND_MISSION_BNK1_7, + STREAMED_SOUND_MISSION_BNK1_8, + STREAMED_SOUND_MISSION_BNK1_10, + STREAMED_SOUND_MISSION_BNK1_11, + STREAMED_SOUND_MISSION_BNK1_12, + STREAMED_SOUND_MISSION_BNK1_13, + STREAMED_SOUND_MISSION_BNK1_14, + STREAMED_SOUND_MISSION_BNK2_1, + STREAMED_SOUND_MISSION_BNK2_2, + STREAMED_SOUND_MISSION_BNK2_3, + STREAMED_SOUND_MISSION_BNK2_4, + STREAMED_SOUND_MISSION_BNK2_5, + STREAMED_SOUND_MISSION_BNK2_6, + STREAMED_SOUND_MISSION_BNK2_7, + STREAMED_SOUND_MISSION_BNK2_8, + STREAMED_SOUND_MISSION_BNK2_9, + STREAMED_SOUND_MISSION_BNK3_1, + STREAMED_SOUND_MISSION_BNK3_2, + STREAMED_SOUND_MISSION_BNK3_3A, + STREAMED_SOUND_MISSION_BNK3_3B, + STREAMED_SOUND_MISSION_BNK3_3C, + STREAMED_SOUND_MISSION_BNK3_4A, + STREAMED_SOUND_MISSION_BNK3_4B, + STREAMED_SOUND_MISSION_BNK3_4C, + STREAMED_SOUND_MISSION_BNK4_1, + STREAMED_SOUND_MISSION_BNK4_2, + STREAMED_SOUND_MISSION_BNK4_3A, + STREAMED_SOUND_MISSION_BNK4_3B, + STREAMED_SOUND_MISSION_BNK4_3C, + STREAMED_SOUND_MISSION_BNK4_3D, + STREAMED_SOUND_MISSION_BNK4_3E, + STREAMED_SOUND_MISSION_BNK4_3F, + STREAMED_SOUND_MISSION_BNK4_3G, + STREAMED_SOUND_MISSION_BNK4_3H, + STREAMED_SOUND_MISSION_BNK4_3I, + STREAMED_SOUND_MISSION_BNK4_3J, + STREAMED_SOUND_MISSION_BNK4_3K, + STREAMED_SOUND_MISSION_BNK4_3M, + STREAMED_SOUND_MISSION_BNK4_3O, + STREAMED_SOUND_MISSION_BNK4_3P, + STREAMED_SOUND_MISSION_BNK4_3Q, + STREAMED_SOUND_MISSION_BNK4_3R, + STREAMED_SOUND_MISSION_BNK4_3S, + STREAMED_SOUND_MISSION_BNK4_3T, + STREAMED_SOUND_MISSION_BNK4_3U, + STREAMED_SOUND_MISSION_BNK4_3V, + STREAMED_SOUND_MISSION_BNK4_4A, + STREAMED_SOUND_MISSION_BNK4_4B, + STREAMED_SOUND_MISSION_BNK4_5, + STREAMED_SOUND_MISSION_BNK4_6, + STREAMED_SOUND_MISSION_BNK4_7, + STREAMED_SOUND_MISSION_BNK4_8, + STREAMED_SOUND_MISSION_BNK4_9, + STREAMED_SOUND_MISSION_BNK4_10, + STREAMED_SOUND_MISSION_BNK4_11, + STREAMED_SOUND_MISSION_BK4_12A, + STREAMED_SOUND_MISSION_BK4_12B, + STREAMED_SOUND_MISSION_BK4_12C, + STREAMED_SOUND_MISSION_BNK4_13, + STREAMED_SOUND_MISSION_BK4_14A, + STREAMED_SOUND_MISSION_BK4_14B, + STREAMED_SOUND_MISSION_BNK4_15, + STREAMED_SOUND_MISSION_BNK4_16, + STREAMED_SOUND_MISSION_BNK4_17, + STREAMED_SOUND_MISSION_BNK4_18, + STREAMED_SOUND_MISSION_BK4_19A, + STREAMED_SOUND_MISSION_BK4_19B, + STREAMED_SOUND_MISSION_BK4_20A, + STREAMED_SOUND_MISSION_BK4_20B, + STREAMED_SOUND_MISSION_BNK4_21, + STREAMED_SOUND_MISSION_BNK422A, + STREAMED_SOUND_MISSION_BNK422B, + STREAMED_SOUND_MISSION_BK4_23A, + STREAMED_SOUND_MISSION_BK4_23B, + STREAMED_SOUND_MISSION_BK4_23C, + STREAMED_SOUND_MISSION_BK4_23D, + STREAMED_SOUND_MISSION_BK4_24A, + STREAMED_SOUND_MISSION_BK4_24B, + STREAMED_SOUND_MISSION_BNK4_25, + STREAMED_SOUND_MISSION_BNK4_26, + STREAMED_SOUND_MISSION_BNK4_27, + STREAMED_SOUND_MISSION_BNK4_28, + STREAMED_SOUND_MISSION_BNK4_29, + STREAMED_SOUND_MISSION_BNK4_30, + STREAMED_SOUND_MISSION_BK4_31A, + STREAMED_SOUND_MISSION_BK4_31B, + STREAMED_SOUND_MISSION_BNK4_32, + STREAMED_SOUND_MISSION_BK4_34A, + STREAMED_SOUND_MISSION_BK4_34B, + STREAMED_SOUND_MISSION_BK4_35A, + STREAMED_SOUND_MISSION_BK4_35B, + STREAMED_SOUND_MISSION_BNK4_36, + STREAMED_SOUND_MISSION_BNK4_37, + STREAMED_SOUND_MISSION_BNK4_38, + STREAMED_SOUND_MISSION_BNK4_39, + STREAMED_SOUND_MISSION_BK4_40A, + STREAMED_SOUND_MISSION_BK4_40B, + STREAMED_SOUND_MISSION_BNK4_41, + STREAMED_SOUND_MISSION_BNK4_42, + STREAMED_SOUND_MISSION_BNK4_43, + STREAMED_SOUND_MISSION_BNK4_44, + STREAMED_SOUND_MISSION_BNK4_45, + STREAMED_SOUND_MISSION_BNK4_46, + STREAMED_SOUND_MISSION_BNK4_47, + STREAMED_SOUND_MISSION_BNK4_48, + STREAMED_SOUND_MISSION_BNK4_49, + STREAMED_SOUND_MISSION_BNK450A, + STREAMED_SOUND_MISSION_BNK450B, + STREAMED_SOUND_MISSION_BNK4_51, + STREAMED_SOUND_MISSION_BNK4_94, + STREAMED_SOUND_MISSION_BNK4_95, + STREAMED_SOUND_MISSION_BNK4_96, + STREAMED_SOUND_MISSION_BNK4_97, + STREAMED_SOUND_MISSION_BNK4_98, + STREAMED_SOUND_MISSION_BNK4_99, + STREAMED_SOUND_MISSION_BUD1_1, + STREAMED_SOUND_MISSION_BUD1_2, + STREAMED_SOUND_MISSION_BUD1_3, + STREAMED_SOUND_MISSION_BUD1_4, + STREAMED_SOUND_MISSION_BUD1_5, + STREAMED_SOUND_MISSION_BUD1_9, + STREAMED_SOUND_MISSION_BUD1_10, + STREAMED_SOUND_MISSION_BUD2_1, + STREAMED_SOUND_MISSION_BUD2_2, + STREAMED_SOUND_MISSION_BUD2_3, + STREAMED_SOUND_MISSION_BUD2_4, + STREAMED_SOUND_MISSION_BUD2_5, + STREAMED_SOUND_MISSION_BUD2_6, + STREAMED_SOUND_MISSION_BUD2_7, + STREAMED_SOUND_MISSION_BUD3_1, + STREAMED_SOUND_MISSION_BUD3_1A, + STREAMED_SOUND_MISSION_BUD3_1B, + STREAMED_SOUND_MISSION_BUD3_1C, + STREAMED_SOUND_MISSION_BUD3_2, + STREAMED_SOUND_MISSION_BUD3_3, + STREAMED_SOUND_MISSION_BUD3_4, + STREAMED_SOUND_MISSION_BUD3_5, + STREAMED_SOUND_MISSION_BUD3_6, + STREAMED_SOUND_MISSION_BUD3_7, + STREAMED_SOUND_MISSION_BUD3_8A, + STREAMED_SOUND_MISSION_BUD3_8B, + STREAMED_SOUND_MISSION_BUD3_8C, + STREAMED_SOUND_MISSION_BUD3_9A, + STREAMED_SOUND_MISSION_BUD3_9B, + STREAMED_SOUND_MISSION_BUD3_9C, + STREAMED_SOUND_MISSION_CAP1_2, + STREAMED_SOUND_MISSION_CAP1_3, + STREAMED_SOUND_MISSION_CAP1_4, + STREAMED_SOUND_MISSION_CAP1_5, + STREAMED_SOUND_MISSION_CAP1_6, + STREAMED_SOUND_MISSION_CAP1_7, + STREAMED_SOUND_MISSION_CAP1_8, + STREAMED_SOUND_MISSION_CAP1_9, + STREAMED_SOUND_MISSION_CAP1_10, + STREAMED_SOUND_MISSION_CAP1_11, + STREAMED_SOUND_MISSION_CAP1_12, + STREAMED_SOUND_MISSION_CNT1_1, + STREAMED_SOUND_MISSION_CNT1_2, + STREAMED_SOUND_MISSION_CNT1_3, + STREAMED_SOUND_MISSION_CNT1_4, + STREAMED_SOUND_MISSION_CNT1_5, + STREAMED_SOUND_MISSION_CNT2_1, + STREAMED_SOUND_MISSION_CNT2_2, + STREAMED_SOUND_MISSION_CNT2_3, + STREAMED_SOUND_MISSION_CNT2_4, + STREAMED_SOUND_MISSION_COK1_1, + STREAMED_SOUND_MISSION_COK1_2, + STREAMED_SOUND_MISSION_COK1_3, + STREAMED_SOUND_MISSION_COK1_4, + STREAMED_SOUND_MISSION_COK1_5, + STREAMED_SOUND_MISSION_COK1_6, + STREAMED_SOUND_MISSION_COK2_1, + STREAMED_SOUND_MISSION_COK2_2, + STREAMED_SOUND_MISSION_COK2_3, + STREAMED_SOUND_MISSION_COK2_4, + STREAMED_SOUND_MISSION_COK2_5, + STREAMED_SOUND_MISSION_COK2_6, + STREAMED_SOUND_MISSION_COK2_7A, + STREAMED_SOUND_MISSION_COK2_7B, + STREAMED_SOUND_MISSION_COK2_7C, + STREAMED_SOUND_MISSION_COK2_8A, + STREAMED_SOUND_MISSION_COK2_8B, + STREAMED_SOUND_MISSION_COK2_8C, + STREAMED_SOUND_MISSION_COK2_8D, + STREAMED_SOUND_MISSION_COK2_9, + STREAMED_SOUND_MISSION_COK210A, + STREAMED_SOUND_MISSION_COK210B, + STREAMED_SOUND_MISSION_COK210C, + STREAMED_SOUND_MISSION_COK212A, + STREAMED_SOUND_MISSION_COK212B, + STREAMED_SOUND_MISSION_COK2_13, + STREAMED_SOUND_MISSION_COK2_14, + STREAMED_SOUND_MISSION_COK2_15, + STREAMED_SOUND_MISSION_COK2_16, + STREAMED_SOUND_MISSION_COK2_20, + STREAMED_SOUND_MISSION_COK2_21, + STREAMED_SOUND_MISSION_COK2_22, + STREAMED_SOUND_MISSION_COK3_1, + STREAMED_SOUND_MISSION_COK3_2, + STREAMED_SOUND_MISSION_COK3_3, + STREAMED_SOUND_MISSION_COK3_4, + STREAMED_SOUND_MISSION_COK4_1, + STREAMED_SOUND_MISSION_COK4_2, + STREAMED_SOUND_MISSION_COK4_3, + STREAMED_SOUND_MISSION_COK4_4, + STREAMED_SOUND_MISSION_COK4_5, + STREAMED_SOUND_MISSION_COK4_6, + STREAMED_SOUND_MISSION_COK4_7, + STREAMED_SOUND_MISSION_COK4_8, + STREAMED_SOUND_MISSION_COK4_9, + STREAMED_SOUND_MISSION_COK4_9A, + STREAMED_SOUND_MISSION_COK4_10, + STREAMED_SOUND_MISSION_COK4_11, + STREAMED_SOUND_MISSION_COK4_12, + STREAMED_SOUND_MISSION_COK4_13, + STREAMED_SOUND_MISSION_COK4_14, + STREAMED_SOUND_MISSION_COK4_15, + STREAMED_SOUND_MISSION_COK4_16, + STREAMED_SOUND_MISSION_COK4_17, + STREAMED_SOUND_MISSION_COK4_18, + STREAMED_SOUND_MISSION_COK4_19, + STREAMED_SOUND_MISSION_COK4_20, + STREAMED_SOUND_MISSION_COK4_21, + STREAMED_SOUND_MISSION_COK4_22, + STREAMED_SOUND_MISSION_COK4_23, + STREAMED_SOUND_MISSION_COK4_24, + STREAMED_SOUND_MISSION_COK4_25, + STREAMED_SOUND_MISSION_COK4_26, + STREAMED_SOUND_MISSION_COK4_27, + STREAMED_SOUND_MISSION_COL1_1, + STREAMED_SOUND_MISSION_COL1_2, + STREAMED_SOUND_MISSION_COL1_3, + STREAMED_SOUND_MISSION_COL1_4, + STREAMED_SOUND_MISSION_COL1_5, + STREAMED_SOUND_MISSION_COL1_6, + STREAMED_SOUND_MISSION_COL1_7, + STREAMED_SOUND_MISSION_COL1_8, + STREAMED_SOUND_MISSION_COL2_1, + STREAMED_SOUND_MISSION_COL2_2, + STREAMED_SOUND_MISSION_COL2_3, + STREAMED_SOUND_MISSION_COL2_4, + STREAMED_SOUND_MISSION_COL2_5, + STREAMED_SOUND_MISSION_COL2_6A, + STREAMED_SOUND_MISSION_COL2_7, + STREAMED_SOUND_MISSION_COL2_8, + STREAMED_SOUND_MISSION_COL2_9, + STREAMED_SOUND_MISSION_COL2_10, + STREAMED_SOUND_MISSION_COL2_11, + STREAMED_SOUND_MISSION_COL2_12, + STREAMED_SOUND_MISSION_COL2_13, + STREAMED_SOUND_MISSION_COL2_14, + STREAMED_SOUND_MISSION_COL2_15, + STREAMED_SOUND_MISSION_COL2_16, + STREAMED_SOUND_MISSION_COL3_1, + STREAMED_SOUND_MISSION_COL3_2, + STREAMED_SOUND_MISSION_COL3_2A, + STREAMED_SOUND_MISSION_COL3_2B, + STREAMED_SOUND_MISSION_COL3_3, + STREAMED_SOUND_MISSION_COL3_4, + STREAMED_SOUND_MISSION_COL3_5, + STREAMED_SOUND_MISSION_COL3_6, + STREAMED_SOUND_MISSION_COL3_7, + STREAMED_SOUND_MISSION_COL3_8, + STREAMED_SOUND_MISSION_COL3_9, + STREAMED_SOUND_MISSION_COL3_10, + STREAMED_SOUND_MISSION_COL3_11, + STREAMED_SOUND_MISSION_COL3_12, + STREAMED_SOUND_MISSION_COL3_13, + STREAMED_SOUND_MISSION_COL3_14, + STREAMED_SOUND_MISSION_COL3_15, + STREAMED_SOUND_MISSION_COL3_16, + STREAMED_SOUND_MISSION_COL3_17, + STREAMED_SOUND_MISSION_COL3_18, + STREAMED_SOUND_MISSION_COL3_19, + STREAMED_SOUND_MISSION_COL3_20, + STREAMED_SOUND_MISSION_COL3_21, + STREAMED_SOUND_MISSION_COL3_23, + STREAMED_SOUND_MISSION_COL3_24, + STREAMED_SOUND_MISSION_COL3_25, + STREAMED_SOUND_MISSION_COL4_1, + STREAMED_SOUND_MISSION_COL4_2, + STREAMED_SOUND_MISSION_COL4_3, + STREAMED_SOUND_MISSION_COL4_4, + STREAMED_SOUND_MISSION_COL4_5, + STREAMED_SOUND_MISSION_COL4_6, + STREAMED_SOUND_MISSION_COL4_7, + STREAMED_SOUND_MISSION_COL4_8, + STREAMED_SOUND_MISSION_COL4_9, + STREAMED_SOUND_MISSION_COL4_10, + STREAMED_SOUND_MISSION_COL4_11, + STREAMED_SOUND_MISSION_COL4_12, + STREAMED_SOUND_MISSION_COL4_13, + STREAMED_SOUND_MISSION_COL4_14, + STREAMED_SOUND_MISSION_COL4_15, + STREAMED_SOUND_MISSION_COL4_16, + STREAMED_SOUND_MISSION_COL4_17, + STREAMED_SOUND_MISSION_COL4_18, + STREAMED_SOUND_MISSION_COL4_19, + STREAMED_SOUND_MISSION_COL4_20, + STREAMED_SOUND_MISSION_COL4_21, + STREAMED_SOUND_MISSION_COL4_22, + STREAMED_SOUND_MISSION_COL4_23, + STREAMED_SOUND_MISSION_COL4_24, + STREAMED_SOUND_MISSION_COL4_25, + STREAMED_SOUND_MISSION_COL4_26, + STREAMED_SOUND_MISSION_COL5_1, + STREAMED_SOUND_MISSION_COL5_2, + STREAMED_SOUND_MISSION_COL5_3, + STREAMED_SOUND_MISSION_COL5_4, + STREAMED_SOUND_MISSION_COL5_5, + STREAMED_SOUND_MISSION_COL5_6, + STREAMED_SOUND_MISSION_COL5_7, + STREAMED_SOUND_MISSION_COL5_8, + STREAMED_SOUND_MISSION_COL5_9, + STREAMED_SOUND_MISSION_COL5_10, + STREAMED_SOUND_MISSION_COL5_11, + STREAMED_SOUND_MISSION_COL5_12, + STREAMED_SOUND_MISSION_COL5_13, + STREAMED_SOUND_MISSION_COL5_14, + STREAMED_SOUND_MISSION_COL5_15, + STREAMED_SOUND_MISSION_COL5_16, + STREAMED_SOUND_MISSION_COL5_17, + STREAMED_SOUND_MISSION_COL5_18, + STREAMED_SOUND_MISSION_COL5_19, + STREAMED_SOUND_MISSION_COL5_20, + STREAMED_SOUND_MISSION_COL5_21, + STREAMED_SOUND_MISSION_COL5_22, + STREAMED_SOUND_MISSION_CUB1_1, + STREAMED_SOUND_MISSION_CUB1_2, + STREAMED_SOUND_MISSION_CUB1_3, + STREAMED_SOUND_MISSION_CUB1_4, + STREAMED_SOUND_MISSION_CUB1_5, + STREAMED_SOUND_MISSION_CUB1_6, + STREAMED_SOUND_MISSION_CUB1_7, + STREAMED_SOUND_MISSION_CUB1_8, + STREAMED_SOUND_MISSION_CUB1_9, + STREAMED_SOUND_MISSION_CUB1_10, + STREAMED_SOUND_MISSION_CUB2_1, + STREAMED_SOUND_MISSION_CUB2_2, + STREAMED_SOUND_MISSION_CUB2_3A, + STREAMED_SOUND_MISSION_CUB2_3B, + STREAMED_SOUND_MISSION_CUB2_3C, + STREAMED_SOUND_MISSION_CUB2_4A, + STREAMED_SOUND_MISSION_CUB2_5, + STREAMED_SOUND_MISSION_CUB2_6, + STREAMED_SOUND_MISSION_CUB2_7, + STREAMED_SOUND_MISSION_CUB2_8, + STREAMED_SOUND_MISSION_CUB2_9, + STREAMED_SOUND_MISSION_CUB2_10, + STREAMED_SOUND_MISSION_CUB2_11, + STREAMED_SOUND_MISSION_CUB3_1, + STREAMED_SOUND_MISSION_CUB3_2, + STREAMED_SOUND_MISSION_CUB3_3, + STREAMED_SOUND_MISSION_CUB3_4, + STREAMED_SOUND_MISSION_CUB4_1, + STREAMED_SOUND_MISSION_CUB4_2, + STREAMED_SOUND_MISSION_CUB4_3, + STREAMED_SOUND_MISSION_CUB4_4, + STREAMED_SOUND_MISSION_CUB4_5, + STREAMED_SOUND_MISSION_CUB4_5A, + STREAMED_SOUND_MISSION_CUB4_6, + STREAMED_SOUND_MISSION_CUB4_7, + STREAMED_SOUND_MISSION_CUB4_8, + STREAMED_SOUND_MISSION_CUB4_9, + STREAMED_SOUND_MISSION_CUB4_10, + STREAMED_SOUND_MISSION_CUB4_11, + STREAMED_SOUND_MISSION_CUB4_12, + STREAMED_SOUND_MISSION_CUB4_13, + STREAMED_SOUND_MISSION_CUB4_14, + STREAMED_SOUND_MISSION_CUB4_15, + STREAMED_SOUND_MISSION_CUB4_16, + STREAMED_SOUND_MISSION_GOLF_1, + STREAMED_SOUND_MISSION_GOLF_2, + STREAMED_SOUND_MISSION_GOLF_3, + STREAMED_SOUND_MISSION_BAR_1, + STREAMED_SOUND_MISSION_BAR_2, + STREAMED_SOUND_MISSION_BAR_3, + STREAMED_SOUND_MISSION_BAR_4, + STREAMED_SOUND_MISSION_BAR_5, + STREAMED_SOUND_MISSION_BAR_6, + STREAMED_SOUND_MISSION_BAR_7, + STREAMED_SOUND_MISSION_BAR_8, + STREAMED_SOUND_MISSION_STRIP_1, + STREAMED_SOUND_MISSION_STRIP_2, + STREAMED_SOUND_MISSION_STRIP_3, + STREAMED_SOUND_MISSION_STRIP_4, + STREAMED_SOUND_MISSION_STRIP_5, + STREAMED_SOUND_MISSION_STRIP_6, + STREAMED_SOUND_MISSION_STRIP_7, + STREAMED_SOUND_MISSION_STRIP_8, + STREAMED_SOUND_MISSION_STRIP_9, + STREAMED_SOUND_MISSION_STAR_1, + STREAMED_SOUND_MISSION_STAR_2, + STREAMED_SOUND_MISSION_STAR_3, + STREAMED_SOUND_MISSION_STAR_4, + STREAMED_SOUND_MISSION_FIN_1A, + STREAMED_SOUND_MISSION_FIN_1B, + STREAMED_SOUND_MISSION_FIN_1C, + STREAMED_SOUND_MISSION_FIN_2B, + STREAMED_SOUND_MISSION_FIN_2C, + STREAMED_SOUND_MISSION_FIN_3, + STREAMED_SOUND_MISSION_FIN_4, + STREAMED_SOUND_MISSION_FIN_5, + STREAMED_SOUND_MISSION_FIN_6, + STREAMED_SOUND_MISSION_FIN_10, + STREAMED_SOUND_MISSION_FIN_11A, + STREAMED_SOUND_MISSION_FIN_11B, + STREAMED_SOUND_MISSION_FIN_12A, + STREAMED_SOUND_MISSION_FIN_12B, + STREAMED_SOUND_MISSION_FIN_12C, + STREAMED_SOUND_MISSION_FIN_13, + STREAMED_SOUND_MISSION_FINKILL, + STREAMED_SOUND_MISSION_LAW1_1, + STREAMED_SOUND_MISSION_LAW1_2, + STREAMED_SOUND_MISSION_LAW1_3, + STREAMED_SOUND_MISSION_LAW1_4, + STREAMED_SOUND_MISSION_LAW1_5, + STREAMED_SOUND_MISSION_LAW1_6, + STREAMED_SOUND_MISSION_LAW1_7, + STREAMED_SOUND_MISSION_LAW1_8, + STREAMED_SOUND_MISSION_LAW1_9, + STREAMED_SOUND_MISSION_LAW1_10, + STREAMED_SOUND_MISSION_LAW2_1, + STREAMED_SOUND_MISSION_LAW2_2, + STREAMED_SOUND_MISSION_LAW2_3, + STREAMED_SOUND_MISSION_LAW2_4, + STREAMED_SOUND_MISSION_LAW2_5, + STREAMED_SOUND_MISSION_LAW2_6, + STREAMED_SOUND_MISSION_LAW2_7, + STREAMED_SOUND_MISSION_LAW2_8, + STREAMED_SOUND_MISSION_LAW2_9, + STREAMED_SOUND_MISSION_LAW2_10, + STREAMED_SOUND_MISSION_LAW3_1, + STREAMED_SOUND_MISSION_LAW3_2, + STREAMED_SOUND_MISSION_LAW3_3, + STREAMED_SOUND_MISSION_LAW3_4, + STREAMED_SOUND_MISSION_LAW3_5, + STREAMED_SOUND_MISSION_LAW3_6, + STREAMED_SOUND_MISSION_LAW3_10, + STREAMED_SOUND_MISSION_LAW3_11, + STREAMED_SOUND_MISSION_LAW3_12, + STREAMED_SOUND_MISSION_LAW3_13, + STREAMED_SOUND_MISSION_LAW3_14, + STREAMED_SOUND_MISSION_LAW3_16, + STREAMED_SOUND_MISSION_LAW3_17, + STREAMED_SOUND_MISSION_LAW3_18, + STREAMED_SOUND_MISSION_LAW3_19, + STREAMED_SOUND_MISSION_LAW3_20, + STREAMED_SOUND_MISSION_LAW3_21, + STREAMED_SOUND_MISSION_LAW3_22, + STREAMED_SOUND_MISSION_LAW3_23, + STREAMED_SOUND_MISSION_LAW3_24, + STREAMED_SOUND_MISSION_LAW3_25, + STREAMED_SOUND_MISSION_LAW4_1A, + STREAMED_SOUND_MISSION_LAW4_1B, + STREAMED_SOUND_MISSION_LAW4_1C, + STREAMED_SOUND_MISSION_LAW4_1D, + STREAMED_SOUND_MISSION_LAW4_10, + STREAMED_SOUND_MISSION_LAW4_3, + STREAMED_SOUND_MISSION_LAW4_4, + STREAMED_SOUND_MISSION_LAW4_5, + STREAMED_SOUND_MISSION_LAW4_6, + STREAMED_SOUND_MISSION_LAW4_7, + STREAMED_SOUND_MISSION_LAW4_8, + STREAMED_SOUND_MISSION_LAW4_9, + STREAMED_SOUND_MISSION_PHIL1_2, + STREAMED_SOUND_MISSION_PHIL1_3, + STREAMED_SOUND_MISSION_PHIL2_1, + STREAMED_SOUND_MISSION_PHIL2_2, + STREAMED_SOUND_MISSION_PHIL2_3, + STREAMED_SOUND_MISSION_PHIL2_4, + STREAMED_SOUND_MISSION_PHIL2_5, + STREAMED_SOUND_MISSION_PHIL2_6, + STREAMED_SOUND_MISSION_PHIL2_7, + STREAMED_SOUND_MISSION_PHIL2_8, + STREAMED_SOUND_MISSION_PHIL2_9, + STREAMED_SOUND_MISSION_PHIL210, + STREAMED_SOUND_MISSION_PHIL211, + STREAMED_SOUND_MISSION_PORN1_1, + STREAMED_SOUND_MISSION_PORN1_2, + STREAMED_SOUND_MISSION_PORN1_3, + STREAMED_SOUND_MISSION_PRN1_3A, + STREAMED_SOUND_MISSION_PORN1_4, + STREAMED_SOUND_MISSION_PORN1_5, + STREAMED_SOUND_MISSION_PORN1_6, + STREAMED_SOUND_MISSION_PORN1_7, + STREAMED_SOUND_MISSION_PORN1_8, + STREAMED_SOUND_MISSION_PORN1_9, + STREAMED_SOUND_MISSION_PRN1_10, + STREAMED_SOUND_MISSION_PRN1_11, + STREAMED_SOUND_MISSION_PRN1_12, + STREAMED_SOUND_MISSION_PRN1_13, + STREAMED_SOUND_MISSION_PRN1_14, + STREAMED_SOUND_MISSION_PRN1_15, + STREAMED_SOUND_MISSION_PRN1_16, + STREAMED_SOUND_MISSION_PRN1_17, + STREAMED_SOUND_MISSION_PRN1_18, + STREAMED_SOUND_MISSION_PRN1_19, + STREAMED_SOUND_MISSION_PRN1_20, + STREAMED_SOUND_MISSION_PRN1_21, + STREAMED_SOUND_MISSION_PORN3_1, + STREAMED_SOUND_MISSION_PORN3_2, + STREAMED_SOUND_MISSION_PORN3_3, + STREAMED_SOUND_MISSION_PORN3_4, + STREAMED_SOUND_MISSION_PSYCH_1, + STREAMED_SOUND_MISSION_PSYCH_2, + STREAMED_SOUND_MISSION_ROK2_01, + STREAMED_SOUND_MISSION_ROK3_1, + STREAMED_SOUND_MISSION_ROK3_2, + STREAMED_SOUND_MISSION_ROK3_3, + STREAMED_SOUND_MISSION_ROK3_4, + STREAMED_SOUND_MISSION_ROK3_5, + STREAMED_SOUND_MISSION_ROK3_6, + STREAMED_SOUND_MISSION_ROK3_7, + STREAMED_SOUND_MISSION_ROK3_8, + STREAMED_SOUND_MISSION_ROK3_9, + STREAMED_SOUND_MISSION_ROK3_10, + STREAMED_SOUND_MISSION_ROK3_11, + STREAMED_SOUND_MISSION_ROK3_12, + STREAMED_SOUND_MISSION_ROK3_13, + STREAMED_SOUND_MISSION_ROK3_14, + STREAMED_SOUND_MISSION_ROK3_15, + STREAMED_SOUND_MISSION_ROK3_16, + STREAMED_SOUND_MISSION_ROK3_17, + STREAMED_SOUND_MISSION_ROK3_18, + STREAMED_SOUND_MISSION_ROK3_19, + STREAMED_SOUND_MISSION_ROK3_20, + STREAMED_SOUND_MISSION_ROK3_21, + STREAMED_SOUND_MISSION_ROK3_22, + STREAMED_SOUND_MISSION_ROK3_23, + STREAMED_SOUND_MISSION_ROK3_24, + STREAMED_SOUND_MISSION_ROK3_25, + STREAMED_SOUND_MISSION_ROK3_26, + STREAMED_SOUND_MISSION_ROK3_27, + STREAMED_SOUND_MISSION_ROK3_62, + STREAMED_SOUND_MISSION_ROK3_63, + STREAMED_SOUND_MISSION_ROK3_64, + STREAMED_SOUND_MISSION_ROK3_65, + STREAMED_SOUND_MISSION_ROK3_66, + STREAMED_SOUND_MISSION_ROK3_67, + STREAMED_SOUND_MISSION_ROK3_68, + STREAMED_SOUND_MISSION_ROK3_69, + STREAMED_SOUND_MISSION_ROK3_70, + STREAMED_SOUND_MISSION_ROK3_71, + STREAMED_SOUND_MISSION_ROK3_73, + STREAMED_SOUND_MISSION_RESC_1, + STREAMED_SOUND_MISSION_RESC_2, + STREAMED_SOUND_MISSION_RESC_3, + STREAMED_SOUND_MISSION_RESC_4, + STREAMED_SOUND_MISSION_RESC_5, + STREAMED_SOUND_MISSION_RESC_6, + STREAMED_SOUND_MISSION_RESC_7, + STREAMED_SOUND_MISSION_RESC_8, + STREAMED_SOUND_MISSION_RESC_9, + STREAMED_SOUND_MISSION_RESC_10, + STREAMED_SOUND_MISSION_ROK1_1A, + STREAMED_SOUND_MISSION_ROK1_1B, + STREAMED_SOUND_MISSION_ROK1_5, + STREAMED_SOUND_MISSION_ROK1_6, + STREAMED_SOUND_MISSION_ROK1_7, + STREAMED_SOUND_MISSION_ROK1_8, + STREAMED_SOUND_MISSION_ROK1_9, + STREAMED_SOUND_MISSION_TAX1_1, + STREAMED_SOUND_MISSION_TAX1_2, + STREAMED_SOUND_MISSION_TAX1_3, + STREAMED_SOUND_MISSION_TAX1_4, + STREAMED_SOUND_MISSION_TAX1_5, + STREAMED_SOUND_MISSION_TAX2_1, + STREAMED_SOUND_MISSION_TAX2_2, + STREAMED_SOUND_MISSION_TAX2_3, + STREAMED_SOUND_MISSION_TAX2_4, + STREAMED_SOUND_MISSION_TAX2_5, + STREAMED_SOUND_MISSION_TAX2_6, + STREAMED_SOUND_MISSION_TAX2_7, + STREAMED_SOUND_MISSION_TAX3_1, + STREAMED_SOUND_MISSION_TAX3_2, + STREAMED_SOUND_MISSION_TAX3_3, + STREAMED_SOUND_MISSION_TAX3_4, + STREAMED_SOUND_MISSION_TAX3_5, + STREAMED_SOUND_MISSION_TEX1_1, + STREAMED_SOUND_MISSION_TEX1_2, + STREAMED_SOUND_MISSION_TEX1_3, + STREAMED_SOUND_MISSION_TEX1_4, + STREAMED_SOUND_MISSION_TEX1_5, + STREAMED_SOUND_MISSION_TEX1_6, + STREAMED_SOUND_MISSION_TEX2_1, + STREAMED_SOUND_MISSION_TEX3_1, + STREAMED_SOUND_MISSION_TEX3_2, + STREAMED_SOUND_MISSION_TEX3_3, + STREAMED_SOUND_MISSION_TEX3_4, + STREAMED_SOUND_MISSION_TEX3_5, + STREAMED_SOUND_MISSION_TEX3_6, + STREAMED_SOUND_MISSION_TEX3_7, + STREAMED_SOUND_MISSION_TEX3_8, + STREAMED_SOUND_MISSION_HAT_1A, + STREAMED_SOUND_MISSION_INTRO1, + STREAMED_SOUND_MISSION_INTRO2, + STREAMED_SOUND_MISSION_INTRO3, + STREAMED_SOUND_MISSION_INTRO4, + STREAMED_SOUND_MISSION_MOB_01A, + STREAMED_SOUND_MISSION_MOB_01B, + STREAMED_SOUND_MISSION_MOB_01C, + STREAMED_SOUND_MISSION_MOB_02A, + STREAMED_SOUND_MISSION_MOB_02B, + STREAMED_SOUND_MISSION_MOB_02C, + STREAMED_SOUND_MISSION_MOB_03A, + STREAMED_SOUND_MISSION_MOB_03B, + STREAMED_SOUND_MISSION_MOB_03C, + STREAMED_SOUND_MISSION_MOB_03D, + STREAMED_SOUND_MISSION_MOB_03E, + STREAMED_SOUND_MISSION_SHARK_1, + STREAMED_SOUND_MISSION_SHARK_2, + STREAMED_SOUND_MISSION_SHARK_3, + STREAMED_SOUND_MISSION_SHARK_4, + STREAMED_SOUND_MISSION_SHARK_5, + STREAMED_SOUND_MISSION_MOB_04A, + STREAMED_SOUND_MISSION_MOB_04B, + STREAMED_SOUND_MISSION_MOB_04C, + STREAMED_SOUND_MISSION_MOB_04D, + STREAMED_SOUND_MISSION_MOB_05A, + STREAMED_SOUND_MISSION_MOB_05B, + STREAMED_SOUND_MISSION_MOB_05C, + STREAMED_SOUND_MISSION_MOB_05D, + STREAMED_SOUND_MISSION_MOB_06A, + STREAMED_SOUND_MISSION_MOB_06B, + STREAMED_SOUND_MISSION_MOB_06C, + STREAMED_SOUND_MISSION_MOB_07A, + STREAMED_SOUND_MISSION_MOB_07B, + STREAMED_SOUND_MISSION_MOB_08A, + STREAMED_SOUND_MISSION_MOB_08B, + STREAMED_SOUND_MISSION_MOB_08C, + STREAMED_SOUND_MISSION_MOB_08D, + STREAMED_SOUND_MISSION_MOB_08E, + STREAMED_SOUND_MISSION_MOB_08F, + STREAMED_SOUND_MISSION_MOB_08G, + STREAMED_SOUND_MISSION_MOB_09A, + STREAMED_SOUND_MISSION_MOB_09B, + STREAMED_SOUND_MISSION_MOB_09C, + STREAMED_SOUND_MISSION_MOB_09D, + STREAMED_SOUND_MISSION_MOB_09E, + STREAMED_SOUND_MISSION_MOB_09F, + STREAMED_SOUND_MISSION_MOB_10A, + STREAMED_SOUND_MISSION_MOB_10B, + STREAMED_SOUND_MISSION_MOB_10C, + STREAMED_SOUND_MISSION_MOB_10D, + STREAMED_SOUND_MISSION_MOB_10E, + STREAMED_SOUND_MISSION_MOB_11A, + STREAMED_SOUND_MISSION_MOB_11B, + STREAMED_SOUND_MISSION_MOB_11C, + STREAMED_SOUND_MISSION_MOB_11D, + STREAMED_SOUND_MISSION_MOB_11E, + STREAMED_SOUND_MISSION_MOB_11F, + STREAMED_SOUND_MISSION_MOB_14A, + STREAMED_SOUND_MISSION_MOB_14B, + STREAMED_SOUND_MISSION_MOB_14C, + STREAMED_SOUND_MISSION_MOB_14D, + STREAMED_SOUND_MISSION_MOB_14E, + STREAMED_SOUND_MISSION_MOB_14F, + STREAMED_SOUND_MISSION_MOB_14G, + STREAMED_SOUND_MISSION_MOB_14H, + STREAMED_SOUND_MISSION_MOB_16A, + STREAMED_SOUND_MISSION_MOB_16B, + STREAMED_SOUND_MISSION_MOB_16C, + STREAMED_SOUND_MISSION_MOB_16D, + STREAMED_SOUND_MISSION_MOB_16E, + STREAMED_SOUND_MISSION_MOB_16F, + STREAMED_SOUND_MISSION_MOB_16G, + STREAMED_SOUND_MISSION_MOB_17A, + STREAMED_SOUND_MISSION_MOB_17B, + STREAMED_SOUND_MISSION_MOB_17C, + STREAMED_SOUND_MISSION_MOB_17D, + STREAMED_SOUND_MISSION_MOB_17E, + STREAMED_SOUND_MISSION_MOB_17G, + STREAMED_SOUND_MISSION_MOB_17H, + STREAMED_SOUND_MISSION_MOB_17I, + STREAMED_SOUND_MISSION_MOB_17J, + STREAMED_SOUND_MISSION_MOB_17K, + STREAMED_SOUND_MISSION_MOB_17L, + STREAMED_SOUND_MISSION_MOB_18A, + STREAMED_SOUND_MISSION_MOB_18B, + STREAMED_SOUND_MISSION_MOB_18C, + STREAMED_SOUND_MISSION_MOB_18D, + STREAMED_SOUND_MISSION_MOB_18E, + STREAMED_SOUND_MISSION_MOB_18F, + STREAMED_SOUND_MISSION_MOB_18G, + STREAMED_SOUND_MISSION_MOB_20A, + STREAMED_SOUND_MISSION_MOB_20B, + STREAMED_SOUND_MISSION_MOB_20C, + STREAMED_SOUND_MISSION_MOB_20D, + STREAMED_SOUND_MISSION_MOB_20E, + STREAMED_SOUND_MISSION_MOB_24A, + STREAMED_SOUND_MISSION_MOB_24B, + STREAMED_SOUND_MISSION_MOB_24C, + STREAMED_SOUND_MISSION_MOB_24D, + STREAMED_SOUND_MISSION_MOB_24E, + STREAMED_SOUND_MISSION_MOB_24F, + STREAMED_SOUND_MISSION_MOB_24G, + STREAMED_SOUND_MISSION_MOB_24H, + STREAMED_SOUND_MISSION_MOB_25A, + STREAMED_SOUND_MISSION_MOB_25B, + STREAMED_SOUND_MISSION_MOB_25C, + STREAMED_SOUND_MISSION_MOB_25D, + STREAMED_SOUND_MISSION_MOB_26A, + STREAMED_SOUND_MISSION_MOB_26B, + STREAMED_SOUND_MISSION_MOB_26C, + STREAMED_SOUND_MISSION_MOB_26D, + STREAMED_SOUND_MISSION_MOB_26E, + STREAMED_SOUND_MISSION_MOB_29A, + STREAMED_SOUND_MISSION_MOB_29B, + STREAMED_SOUND_MISSION_MOB_29C, + STREAMED_SOUND_MISSION_MOB_29D, + STREAMED_SOUND_MISSION_MOB_29E, + STREAMED_SOUND_MISSION_MOB_29F, + STREAMED_SOUND_MISSION_MOB_29G, + STREAMED_SOUND_MISSION_MOB_30A, + STREAMED_SOUND_MISSION_MOB_30B, + STREAMED_SOUND_MISSION_MOB_30C, + STREAMED_SOUND_MISSION_MOB_30D, + STREAMED_SOUND_MISSION_MOB_30E, + STREAMED_SOUND_MISSION_MOB_30F, + STREAMED_SOUND_MISSION_MOB_33A, + STREAMED_SOUND_MISSION_MOB_33B, + STREAMED_SOUND_MISSION_MOB_33C, + STREAMED_SOUND_MISSION_MOB_33D, + STREAMED_SOUND_MISSION_MOB_34A, + STREAMED_SOUND_MISSION_MOB_34B, + STREAMED_SOUND_MISSION_MOB_34C, + STREAMED_SOUND_MISSION_MOB_34D, + STREAMED_SOUND_MISSION_MOB_35A, + STREAMED_SOUND_MISSION_MOB_35B, + STREAMED_SOUND_MISSION_MOB_35C, + STREAMED_SOUND_MISSION_MOB_35D, + STREAMED_SOUND_MISSION_MOB_36A, + STREAMED_SOUND_MISSION_MOB_36B, + STREAMED_SOUND_MISSION_MOB_36C, + STREAMED_SOUND_MISSION_MOB_40A, + STREAMED_SOUND_MISSION_MOB_40B, + STREAMED_SOUND_MISSION_MOB_40C, + STREAMED_SOUND_MISSION_MOB_40D, + STREAMED_SOUND_MISSION_MOB_40E, + STREAMED_SOUND_MISSION_MOB_40F, + STREAMED_SOUND_MISSION_MOB_40G, + STREAMED_SOUND_MISSION_MOB_40H, + STREAMED_SOUND_MISSION_MOB_40I, + STREAMED_SOUND_MISSION_MOB_41A, + STREAMED_SOUND_MISSION_MOB_41B, + STREAMED_SOUND_MISSION_MOB_41C, + STREAMED_SOUND_MISSION_MOB_41D, + STREAMED_SOUND_MISSION_MOB_41E, + STREAMED_SOUND_MISSION_MOB_41F, + STREAMED_SOUND_MISSION_MOB_41G, + STREAMED_SOUND_MISSION_MOB_41H, + STREAMED_SOUND_MISSION_MOB_42A, + STREAMED_SOUND_MISSION_MOB_42B, + STREAMED_SOUND_MISSION_MOB_42C, + STREAMED_SOUND_MISSION_MOB_42D, + STREAMED_SOUND_MISSION_MOB_42E, + STREAMED_SOUND_MISSION_MOB_43A, + STREAMED_SOUND_MISSION_MOB_43B, + STREAMED_SOUND_MISSION_MOB_43C, + STREAMED_SOUND_MISSION_MOB_43D, + STREAMED_SOUND_MISSION_MOB_43E, + STREAMED_SOUND_MISSION_MOB_43F, + STREAMED_SOUND_MISSION_MOB_43G, + STREAMED_SOUND_MISSION_MOB_43H, + STREAMED_SOUND_MISSION_MOB_45A, + STREAMED_SOUND_MISSION_MOB_45B, + STREAMED_SOUND_MISSION_MOB_45C, + STREAMED_SOUND_MISSION_MOB_45D, + STREAMED_SOUND_MISSION_MOB_45E, + STREAMED_SOUND_MISSION_MOB_45F, + STREAMED_SOUND_MISSION_MOB_45G, + STREAMED_SOUND_MISSION_MOB_45H, + STREAMED_SOUND_MISSION_MOB_45I, + STREAMED_SOUND_MISSION_MOB_45J, + STREAMED_SOUND_MISSION_MOB_45K, + STREAMED_SOUND_MISSION_MOB_45L, + STREAMED_SOUND_MISSION_MOB_45M, + STREAMED_SOUND_MISSION_MOB_45N, + STREAMED_SOUND_MISSION_MOB_46A, + STREAMED_SOUND_MISSION_MOB_46B, + STREAMED_SOUND_MISSION_MOB_46C, + STREAMED_SOUND_MISSION_MOB_46D, + STREAMED_SOUND_MISSION_MOB_46E, + STREAMED_SOUND_MISSION_MOB_46F, + STREAMED_SOUND_MISSION_MOB_46G, + STREAMED_SOUND_MISSION_MOB_46H, + STREAMED_SOUND_MISSION_MOB_47A, + STREAMED_SOUND_MISSION_MOB_52A, + STREAMED_SOUND_MISSION_MOB_52B, + STREAMED_SOUND_MISSION_MOB_52C, + STREAMED_SOUND_MISSION_MOB_52D, + STREAMED_SOUND_MISSION_MOB_52E, + STREAMED_SOUND_MISSION_MOB_52F, + STREAMED_SOUND_MISSION_MOB_52G, + STREAMED_SOUND_MISSION_MOB_52H, + STREAMED_SOUND_MISSION_MOB_54A, + STREAMED_SOUND_MISSION_MOB_54B, + STREAMED_SOUND_MISSION_MOB_54C, + STREAMED_SOUND_MISSION_MOB_54D, + STREAMED_SOUND_MISSION_MOB_54E, + STREAMED_SOUND_MISSION_MOB_55A, + STREAMED_SOUND_MISSION_MOB_55B, + STREAMED_SOUND_MISSION_MOB_55C, + STREAMED_SOUND_MISSION_MOB_55D, + STREAMED_SOUND_MISSION_MOB_55E, + STREAMED_SOUND_MISSION_MOB_55F, + STREAMED_SOUND_MISSION_MOB_56A, + STREAMED_SOUND_MISSION_MOB_56B, + STREAMED_SOUND_MISSION_MOB_56C, + STREAMED_SOUND_MISSION_MOB_56D, + STREAMED_SOUND_MISSION_MOB_56E, + STREAMED_SOUND_MISSION_MOB_56F, + STREAMED_SOUND_MISSION_MOB_57A, + STREAMED_SOUND_MISSION_MOB_57B, + STREAMED_SOUND_MISSION_MOB_57C, + STREAMED_SOUND_MISSION_MOB_57D, + STREAMED_SOUND_MISSION_MOB_57E, + STREAMED_SOUND_MISSION_MOB_58A, + STREAMED_SOUND_MISSION_MOB_58B, + STREAMED_SOUND_MISSION_MOB_58C, + STREAMED_SOUND_MISSION_MOB_58D, + STREAMED_SOUND_MISSION_MOB_58E, + STREAMED_SOUND_MISSION_MOB_58F, + STREAMED_SOUND_MISSION_MOB_58G, + STREAMED_SOUND_MISSION_MOB_61A, + STREAMED_SOUND_MISSION_MOB_61B, + STREAMED_SOUND_MISSION_MOB_62A, + STREAMED_SOUND_MISSION_MOB_62B, + STREAMED_SOUND_MISSION_MOB_62C, + STREAMED_SOUND_MISSION_MOB_62D, + STREAMED_SOUND_MISSION_MOB_63A, + STREAMED_SOUND_MISSION_MOB_63B, + STREAMED_SOUND_MISSION_MOB_63C, + STREAMED_SOUND_MISSION_MOB_63D, + STREAMED_SOUND_MISSION_MOB_63E, + STREAMED_SOUND_MISSION_MOB_63F, + STREAMED_SOUND_MISSION_MOB_63G, + STREAMED_SOUND_MISSION_MOB_63H, + STREAMED_SOUND_MISSION_MOB_63I, + STREAMED_SOUND_MISSION_MOB_63J, + STREAMED_SOUND_MISSION_MOB_66A, + STREAMED_SOUND_MISSION_MOB_66B, + STREAMED_SOUND_MISSION_MOB_68A, + STREAMED_SOUND_MISSION_MOB_68B, + STREAMED_SOUND_MISSION_MOB_68C, + STREAMED_SOUND_MISSION_MOB_68D, + STREAMED_SOUND_MISSION_MOB_70A, + STREAMED_SOUND_MISSION_MOB_70B, + STREAMED_SOUND_MISSION_MOB_71A, + STREAMED_SOUND_MISSION_MOB_71B, + STREAMED_SOUND_MISSION_MOB_71C, + STREAMED_SOUND_MISSION_MOB_71D, + STREAMED_SOUND_MISSION_MOB_71E, + STREAMED_SOUND_MISSION_MOB_71F, + STREAMED_SOUND_MISSION_MOB_71G, + STREAMED_SOUND_MISSION_MOB_71H, + STREAMED_SOUND_MISSION_MOB_71I, + STREAMED_SOUND_MISSION_MOB_71J, + STREAMED_SOUND_MISSION_MOB_71K, + STREAMED_SOUND_MISSION_MOB_71L, + STREAMED_SOUND_MISSION_MOB_71M, + STREAMED_SOUND_MISSION_MOB_71N, + STREAMED_SOUND_MISSION_MOB_72A, + STREAMED_SOUND_MISSION_MOB_72B, + STREAMED_SOUND_MISSION_MOB_72C, + STREAMED_SOUND_MISSION_MOB_72D, + STREAMED_SOUND_MISSION_MOB_72E, + STREAMED_SOUND_MISSION_MOB_72F, + STREAMED_SOUND_MISSION_MOB_72G, + STREAMED_SOUND_MISSION_MOB_73A, + STREAMED_SOUND_MISSION_MOB_73C, + STREAMED_SOUND_MISSION_MOB_73D, + STREAMED_SOUND_MISSION_MOB_73F, + STREAMED_SOUND_MISSION_MOB_73G, + STREAMED_SOUND_MISSION_MOB_73I, + STREAMED_SOUND_MISSION_MOB_95A, + STREAMED_SOUND_MISSION_MOB_96A, + STREAMED_SOUND_MISSION_MOB_98A, + STREAMED_SOUND_MISSION_MOB_99A, + STREAMED_SOUND_MISSION_JOB1_1B, + STREAMED_SOUND_MISSION_JOB1_1C, + STREAMED_SOUND_MISSION_JOB1_1D, + STREAMED_SOUND_MISSION_JOB2_1B, + STREAMED_SOUND_MISSION_JOB2_2, + STREAMED_SOUND_MISSION_JOB2_3, + STREAMED_SOUND_MISSION_JOB2_4, + STREAMED_SOUND_MISSION_JOB2_5, + STREAMED_SOUND_MISSION_JOB2_6, + STREAMED_SOUND_MISSION_JOB2_7, + STREAMED_SOUND_MISSION_JOB2_8, + STREAMED_SOUND_MISSION_JOB2_9, + STREAMED_SOUND_MISSION_JOB3_1, + STREAMED_SOUND_MISSION_JOB3_2, + STREAMED_SOUND_MISSION_JOB3_3, + STREAMED_SOUND_MISSION_JOB4_1, + STREAMED_SOUND_MISSION_JOB4_2, + STREAMED_SOUND_MISSION_JOB4_3, + STREAMED_SOUND_MISSION_JOB5_1, + STREAMED_SOUND_MISSION_JOB5_2, + STREAMED_SOUND_MISSION_JOB5_3, + STREAMED_SOUND_MISSION_BJM1_20, + STREAMED_SOUND_MISSION_BJM1_4, + STREAMED_SOUND_MISSION_BJM1_5, + STREAMED_SOUND_MISSION_MERC_39, + STREAMED_SOUND_MISSION_MONO_1, + STREAMED_SOUND_MISSION_MONO_2, + STREAMED_SOUND_MISSION_MONO_3, + STREAMED_SOUND_MISSION_MONO_4, + STREAMED_SOUND_MISSION_MONO_5, + STREAMED_SOUND_MISSION_MONO_6, + STREAMED_SOUND_MISSION_MONO_7, + STREAMED_SOUND_MISSION_MONO_8, + STREAMED_SOUND_MISSION_MONO_9, + STREAMED_SOUND_MISSION_MONO10, + STREAMED_SOUND_MISSION_MONO11, + STREAMED_SOUND_MISSION_MONO12, + STREAMED_SOUND_MISSION_MONO13, + STREAMED_SOUND_MISSION_MONO14, + STREAMED_SOUND_MISSION_MONO15, + STREAMED_SOUND_MISSION_MONO16, + STREAMED_SOUND_MISSION_FUD_01, + STREAMED_SOUND_MISSION_FUD_02, + STREAMED_SOUND_MISSION_FUD_03, + STREAMED_SOUND_MISSION_FUD_04, + STREAMED_SOUND_MISSION_FUD_05, + STREAMED_SOUND_MISSION_FUD_06, + STREAMED_SOUND_MISSION_FUD_07, + STREAMED_SOUND_MISSION_FUD_08, + STREAMED_SOUND_MISSION_FUD_09, + STREAMED_SOUND_MISSION_FUD_10, + STREAMED_SOUND_MISSION_FUD_11, + STREAMED_SOUND_MISSION_FUD_12, + STREAMED_SOUND_MISSION_FUD_13, + STREAMED_SOUND_MISSION_FUD_14, + STREAMED_SOUND_MISSION_FUD_15, + STREAMED_SOUND_MISSION_FUD_16, + STREAMED_SOUND_MISSION_FUD_17, + STREAMED_SOUND_MISSION_FUD_18, + STREAMED_SOUND_MISSION_FUD_19, + STREAMED_SOUND_MISSION_FUD_20, + STREAMED_SOUND_MISSION_BURG_01, + STREAMED_SOUND_MISSION_BURG_02, + STREAMED_SOUND_MISSION_BURG_03, + STREAMED_SOUND_MISSION_BURG_04, + STREAMED_SOUND_MISSION_BURG_05, + STREAMED_SOUND_MISSION_BURG_06, + STREAMED_SOUND_MISSION_BURG_07, + STREAMED_SOUND_MISSION_BURG_08, + STREAMED_SOUND_MISSION_BURG_09, + STREAMED_SOUND_MISSION_BURG_10, + STREAMED_SOUND_MISSION_BURG_11, + STREAMED_SOUND_MISSION_BURG_12, + STREAMED_SOUND_MISSION_CRUST01, + STREAMED_SOUND_MISSION_CRUST02, + STREAMED_SOUND_MISSION_CRUST03, + STREAMED_SOUND_MISSION_CRUST04, + STREAMED_SOUND_MISSION_CRUST05, + STREAMED_SOUND_MISSION_CRUST06, + STREAMED_SOUND_MISSION_CRUST07, + STREAMED_SOUND_MISSION_CRUST08, + STREAMED_SOUND_MISSION_CRUST09, + STREAMED_SOUND_MISSION_BAND_01, + STREAMED_SOUND_MISSION_BAND_02, + STREAMED_SOUND_MISSION_BAND_03, + STREAMED_SOUND_MISSION_BAND_04, + STREAMED_SOUND_MISSION_BAND_05, + STREAMED_SOUND_MISSION_BAND_06, + STREAMED_SOUND_MISSION_BAND_07, + STREAMED_SOUND_MISSION_BAND_08, + STREAMED_SOUND_MISSION_SHAFT01, + STREAMED_SOUND_MISSION_SHAFT02, + STREAMED_SOUND_MISSION_SHAFT03, + STREAMED_SOUND_MISSION_SHAFT04, + STREAMED_SOUND_MISSION_SHAFT05, + STREAMED_SOUND_MISSION_SHAFT06, + STREAMED_SOUND_MISSION_SHAFT07, + STREAMED_SOUND_MISSION_SHAFT08, + STREAMED_SOUND_MISSION_PISS_01, + STREAMED_SOUND_MISSION_PISS_02, + STREAMED_SOUND_MISSION_PISS_03, + STREAMED_SOUND_MISSION_PISS_04, + STREAMED_SOUND_MISSION_PISS_05, + STREAMED_SOUND_MISSION_PISS_06, + STREAMED_SOUND_MISSION_PISS_07, + STREAMED_SOUND_MISSION_PISS_08, + STREAMED_SOUND_MISSION_PISS_09, + STREAMED_SOUND_MISSION_PISS_10, + STREAMED_SOUND_MISSION_PISS_11, + STREAMED_SOUND_MISSION_PISS_12, + STREAMED_SOUND_MISSION_PISS_13, + STREAMED_SOUND_MISSION_PISS_14, + STREAMED_SOUND_MISSION_PISS_15, + STREAMED_SOUND_MISSION_PISS_16, + STREAMED_SOUND_MISSION_PISS_17, + STREAMED_SOUND_MISSION_PISS_18, + STREAMED_SOUND_MISSION_PISS_19, + STREAMED_SOUND_MISSION_GIMME01, + STREAMED_SOUND_MISSION_GIMME02, + STREAMED_SOUND_MISSION_GIMME03, + STREAMED_SOUND_MISSION_GIMME04, + STREAMED_SOUND_MISSION_GIMME05, + STREAMED_SOUND_MISSION_GIMME06, + STREAMED_SOUND_MISSION_GIMME07, + STREAMED_SOUND_MISSION_GIMME08, + STREAMED_SOUND_MISSION_GIMME09, + STREAMED_SOUND_MISSION_GIMME10, + STREAMED_SOUND_MISSION_GIMME11, + STREAMED_SOUND_MISSION_GIMME12, + STREAMED_SOUND_MISSION_GIMME13, + STREAMED_SOUND_MISSION_GIMME14, + STREAMED_SOUND_MISSION_GIMME15, + STREAMED_SOUND_MISSION_BUST_01, + STREAMED_SOUND_MISSION_BUST_02, + STREAMED_SOUND_MISSION_BUST_03, + STREAMED_SOUND_MISSION_BUST_04, + STREAMED_SOUND_MISSION_BUST_05, + STREAMED_SOUND_MISSION_BUST_06, + STREAMED_SOUND_MISSION_BUST_07, + STREAMED_SOUND_MISSION_BUST_08, + STREAMED_SOUND_MISSION_BUST_09, + STREAMED_SOUND_MISSION_BUST_10, + STREAMED_SOUND_MISSION_BUST_11, + STREAMED_SOUND_MISSION_BUST_12, + STREAMED_SOUND_MISSION_BUST_13, + STREAMED_SOUND_MISSION_BUST_14, + STREAMED_SOUND_MISSION_BUST_15, + STREAMED_SOUND_MISSION_BUST_16, + STREAMED_SOUND_MISSION_BUST_17, + STREAMED_SOUND_MISSION_BUST_18, + STREAMED_SOUND_MISSION_BUST_19, + STREAMED_SOUND_MISSION_BUST_20, + STREAMED_SOUND_MISSION_BUST_21, + STREAMED_SOUND_MISSION_BUST_22, + STREAMED_SOUND_MISSION_BUST_23, + STREAMED_SOUND_MISSION_BUST_24, + STREAMED_SOUND_MISSION_BUST_25, + STREAMED_SOUND_MISSION_BUST_26, + STREAMED_SOUND_MISSION_BUST_27, + STREAMED_SOUND_MISSION_BUST_28, + 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_SCRIPTOBJECT, +#ifdef GTA_BRIDGE + AUDIOTYPE_BRIDGE, +#endif + AUDIOTYPE_COLLISION, + AUDIOTYPE_FRONTEND, + AUDIOTYPE_PROJECTILE, + AUDIOTYPE_GARAGE, + AUDIOTYPE_FIREHYDRANT, + AUDIOTYPE_WATERCANNON, + AUDIOTYPE_ESCALATOR, + AUDIOTYPE_EXTRA_SOUNDS, + AUDIOTYPE_POLICERADIO, + TOTAL_AUDIO_TYPES, +}; + +#ifdef GTA_PS2 +enum +{ + NUM_CHANNELS_GENERIC = 42, + CHANNEL_POLICE_RADIO = NUM_CHANNELS_GENERIC, + CHANNEL_MISSION_AUDIO_1, + CHANNEL_MISSION_AUDIO_2, + CHANNEL_PLAYER_VEHICLE_ENGINE, + NUM_CHANNELS, + + NUM_CHANNELS_DTS_GENERIC = 18, + CHANNEL_DTS_POLICE_RADIO = NUM_CHANNELS_DTS_GENERIC, + CHANNEL_DTS_MISSION_AUDIO_1, + CHANNEL_DTS_MISSION_AUDIO_2, + CHANNEL_DTS_PLAYER_VEHICLE_ENGINE, +}; +#else +enum +{ +#ifdef PS2_AUDIO_CHANNELS + NUM_CHANNELS_GENERIC = 42, +#else + NUM_CHANNELS_GENERIC = 27, +#endif + CHANNEL_POLICE_RADIO, + NUM_CHANNELS +}; +#endif + +enum +{ + MISSION_AUDIO_SLOT_1, + MISSION_AUDIO_SLOT_2, + MISSION_AUDIO_POLRADIO_CRIME_OR_COLOR, + MISSION_AUDIO_POLRADIO_AREA_OR_CAR, + MISSION_AUDIO_PLAYER_COMMENT, + MISSION_AUDIO_COUNT +}; diff --git a/src/miami/audio/eax/eax-util.cpp b/src/miami/audio/eax/eax-util.cpp new file mode 100644 index 00000000..42eef738 --- /dev/null +++ b/src/miami/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/miami/audio/eax/eax-util.h b/src/miami/audio/eax/eax-util.h new file mode 100644 index 00000000..441f0115 --- /dev/null +++ b/src/miami/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/miami/audio/eax/eax.h b/src/miami/audio/eax/eax.h new file mode 100644 index 00000000..b2210936 --- /dev/null +++ b/src/miami/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/miami/audio/oal/aldlist.cpp b/src/miami/audio/oal/aldlist.cpp new file mode 100644 index 00000000..6024adf2 --- /dev/null +++ b/src/miami/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/miami/audio/oal/aldlist.h b/src/miami/audio/oal/aldlist.h new file mode 100644 index 00000000..3ed12d84 --- /dev/null +++ b/src/miami/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/miami/audio/oal/channel.cpp b/src/miami/audio/oal/channel.cpp new file mode 100644 index 00000000..04e7e529 --- /dev/null +++ b/src/miami/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/miami/audio/oal/channel.h b/src/miami/audio/oal/channel.h new file mode 100644 index 00000000..872646c8 --- /dev/null +++ b/src/miami/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/miami/audio/oal/oal_utils.cpp b/src/miami/audio/oal/oal_utils.cpp new file mode 100644 index 00000000..e4cb0b77 --- /dev/null +++ b/src/miami/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/miami/audio/oal/oal_utils.h b/src/miami/audio/oal/oal_utils.h new file mode 100644 index 00000000..f0fa090a --- /dev/null +++ b/src/miami/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/miami/audio/oal/stream.cpp b/src/miami/audio/oal/stream.cpp new file mode 100644 index 00000000..3789573e --- /dev/null +++ b/src/miami/audio/oal/stream.cpp @@ -0,0 +1,1831 @@ +#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; + + CMP3File() : + m_pMH(nil), + m_bOpened(false), + m_nRate(0), + m_bFileNotOpenedYet(false), + m_nChannels(0) {} +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; + } +}; + +class CADFFile : public CMP3File +{ + static ssize_t r_read(void* fh, void* buf, size_t size) + { + size_t bytesRead = fread(buf, 1, size, (FILE*)fh); + uint8* _buf = (uint8*)buf; + for (size_t i = 0; i < size; i++) + _buf[i] ^= 0x22; + return bytesRead; + } + static off_t r_seek(void* fh, off_t pos, int seekType) + { + fseek((FILE*)fh, pos, seekType); + return ftell((FILE*)fh); + } + static void r_close(void* fh) + { + fclose((FILE*)fh); + } +public: + CADFFile(const char* path) + { + 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; + m_pPath = path; + // 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; + + FILE *f = fopen(m_pPath, "rb"); + + m_bOpened = f && mpg123_replace_reader_handle(m_pMH, r_read, r_seek, r_close) == MPG123_OK + && mpg123_open_handle(m_pMH, f) == 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; + } +}; + +#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); + else if (!strcasecmp(&m_aFilename[strlen(m_aFilename) - strlen(".adf")], ".adf")) + m_pSoundFile = new CADFFile(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/miami/audio/oal/stream.h b/src/miami/audio/oal/stream.h new file mode 100644 index 00000000..f0456925 --- /dev/null +++ b/src/miami/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/miami/audio/sampman.h b/src/miami/audio/sampman.h new file mode 100644 index 00000000..40cb8e0b --- /dev/null +++ b/src/miami/audio/sampman.h @@ -0,0 +1,2730 @@ +#pragma once +#include "AudioSamples.h" +#include "audio_enums.h" + +#define MAX_VOLUME 127 +#define MAX_FREQ DIGITALRATE + +struct tSample { + uint32 nFileOffset; // in bytes + uint32 nByteSize; // in bytes (*2 for adpcm samples) + uint32 nFrequency; // in hz + uint32 nLoopStartSample;// in samples, 0 if no separate loop data + uint32 nLoopFileOffset; // in bytes, 0 if none + uint32 nLoopByteSize; // in bytes, 0 if none (*2 for adpcm samples) +}; + +#ifdef GTA_PS2 +#define PS2BANK(e) e +#else +#define PS2BANK(e) e = SFX_BANK_0 +#endif // GTA_PS2 + + +enum +{ + SFX_BANK_0, +#ifdef GTA_PS2 + SFX_BANK_GENERIC_EXTRA, + SFX_BANK_PED_COMMENTS, + SFX_BANK_FRONT_END_MENU, +#else + SFX_BANK_GENERIC_EXTRA = SFX_BANK_0, + SFX_BANK_FRONT_END_MENU = SFX_BANK_0, + SFX_BANK_1, + SFX_BANK_63 = 63, + + SFX_BANK_PED_COMMENTS, + MAX_SFX_BANKS, + INVALID_SFX_BANK, +#endif + + CAR_SFX_BANKS_OFFSET, + SFX_BANK_PONTIAC = CAR_SFX_BANKS_OFFSET, + SFX_BANK_PORSCHE, + SFX_BANK_SPIDER, + SFX_BANK_MERC, + SFX_BANK_TRUCK, + SFX_BANK_HOTROD, + SFX_BANK_COBRA, + SFX_BANK_PONTIAC_SLOW, + SFX_BANK_CADILLAC, + SFX_BANK_PATHFINDER, + SFX_BANK_PACARD, + SFX_BANK_GOLF_CART, + SFX_BANK_CAR_CHAINSAW, + SFX_BANK_RC, + SFX_BANK_RC_HELI, + SFX_BANK_CAR_UNUSED_4, + + // bikes + SFX_BANK_VTWIN, + SFX_BANK_MOPED, + SFX_BANK_HONDA250, + SFX_BANK_SPORTS_BIKE, + SFX_BANK_BIKE_UNUSED_1, + SFX_BANK_BIKE_UNUSED_2, + SFX_BANK_BIKE_UNUSED_3, + SFX_BANK_BIKE_UNUSED_4, + SFX_BANK_BIKE_UNUSED_5, + SFX_BANK_BIKE_UNUSED_6, + + // heli + SFX_BANK_HELI_APACHE, + SFX_BANK_HELI_UNUSED_1, + SFX_BANK_HELI_UNUSED_2, + SFX_BANK_HELI_UNUSED_3, + SFX_BANK_HELI_UNUSED_4, + + // plane + SFX_BANK_PLANE_SEAPLANE, + SFX_BANK_PLANE_UNUSED_1, + SFX_BANK_PLANE_UNUSED_2, + SFX_BANK_PLANE_UNUSED_3, + SFX_BANK_PLANE_UNUSED_4, + PS2BANK(SFX_BANK_BUILDING_BANK_ALARM), + PS2BANK(SFX_BANK_BUILDING_SNORING), + PS2BANK(SFX_BANK_BUILDING_BAR_1), + PS2BANK(SFX_BANK_BUILDING_BAR_2), + PS2BANK(SFX_BANK_BUILDING_BAR_3), + PS2BANK(SFX_BANK_BUILDING_BAR_4), + PS2BANK(SFX_BANK_BUILDING_MALIBU_1), + PS2BANK(SFX_BANK_BUILDING_MALIBU_2), + PS2BANK(SFX_BANK_BUILDING_MALIBU_3), + PS2BANK(SFX_BANK_BUILDING_STRIP_1), + PS2BANK(SFX_BANK_BUILDING_STRIP_2), + PS2BANK(SFX_BANK_BUILDING_STRIP_3), + PS2BANK(SFX_BANK_BUILDING_CHURCH), + PS2BANK(SFX_BANK_BUILDING_FAN_1), + PS2BANK(SFX_BANK_BUILDING_FAN_2), + PS2BANK(SFX_BANK_BUILDING_INSECT_1), + PS2BANK(SFX_BANK_BUILDING_INSECT_2), + PS2BANK(SFX_BANK_BUILDING_18), + PS2BANK(SFX_BANK_BUILDING_19), + PS2BANK(SFX_BANK_BUILDING_20), + PS2BANK(SFX_BANK_BUILDING_21), + PS2BANK(SFX_BANK_FOOTSTEPS_GRASS), + PS2BANK(SFX_BANK_FOOTSTEPS_GRAVEL), + PS2BANK(SFX_BANK_FOOTSTEPS_WOOD), + PS2BANK(SFX_BANK_FOOTSTEPS_METAL), + PS2BANK(SFX_BANK_FOOTSTEPS_WATER), + PS2BANK(SFX_BANK_FOOTSTEPS_SAND), +#ifdef GTA_PS2 + MAX_SFX_BANKS, + INVALID_SFX_BANK +#endif +}; +#define MAX_PEDSFX 7 +#define PED_BLOCKSIZE 79000 +#define PED_BLOCKSIZE_ADPCM (PED_BLOCKSIZE/4) + +#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 3 + +#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_nMP3BoostVolume; + uint8 m_nEffectsFadeVolume; + uint8 m_nMusicFadeVolume; + bool8 m_nMonoMode; + char m_szCDRomRootPath[80]; + bool8 m_bInitialised; + uint8 m_nNumberOfProviders; + char *m_aAudioProviders[MAXPROVIDERS]; + tSample m_aSamples[TOTAL_AUDIO_SAMPLES]; + char m_MiscomPath[260]; + char m_WavFilesPath[260]; + char m_MP3FilesPath[188]; + void *m_aChannels[18]; + +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); + + int8 AutoDetect3DProviders(); +#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); + +#ifdef DC_SH4 + void UpdateChannelVolume(uint32 nChannel); +#endif + + void SetEffectsMasterVolume(uint8 nVolume); + void SetMusicMasterVolume (uint8 nVolume); + void SetMP3BoostVolume (uint8 nVolume); + void SetEffectsFadeVolume (uint8 nVolume); + void SetMusicFadeVolume (uint8 nVolume); + void SetMonoMode (bool8 nMode); + + bool8 LoadSampleBank (uint8 nBank); + void UnloadSampleBank (uint8 nBank); + void UnloadUnusedSampleBank(); + int8 IsSampleBankLoaded(uint8 nBank); + +#if defined (GTA_PS2) || defined (FIX_BUGS) + uint8 IsMissionAudioLoaded(uint8 nSlot, uint32 nSample); + bool8 LoadMissionAudio (uint8 nSlot, uint32 nSample); +#endif + + 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 (uint32 nFile, uint8 nStream = 0, uint32_t seek_bytes_aligned = 0); + void PauseStream (bool8 nPauseFlag, uint8 nStream = 0); + void StartPreloadedStreamedFile (uint8 nStream = 0); + bool8 StartStreamedFile (uint32 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); + void SetStreamedFileLoopFlag (bool8 nLoopFlag, uint8 nStream = 0); +#ifdef AUDIO_OAL + void Service(void); +#endif + bool8 InitialiseSampleBanks(void); + + uint8 GetMusicVolume() const { return m_nMusicVolume; } +}; + +extern cSampleManager SampleManager; +extern uint32 BankStartOffset[MAX_SFX_BANKS]; + +#ifdef PS2_AUDIO_PATHS +static char PS2StreamedNameTable[][40] = +{ + "AUDIO\\MUSIC\\WILD.VB", + "AUDIO\\MUSIC\\FLASH.VB", + "AUDIO\\MUSIC\\KCHAT.VB", // 16 khz + "AUDIO\\MUSIC\\FEVER.VB", + "AUDIO\\MUSIC\\VROCK.VB", + "AUDIO\\MUSIC\\VCPR.VB", // 16 khz + "AUDIO\\MUSIC\\ESPANT.VB", + "AUDIO\\MUSIC\\EMOTION.VB", + "AUDIO\\MUSIC\\WAVE.VB", + "AUDIO\\MUSIC\\MISCOM.VB", + "AUDIO\\MUSIC\\CITY.VB", + "AUDIO\\MUSIC\\WATER.VB", + "AUDIO\\MUSIC\\BEACHAMB.VB", + "AUDIO\\MUSIC\\HCITY.VB", + "AUDIO\\MUSIC\\HWATER.VB", + "AUDIO\\MUSIC\\HBEACH.VB", + "AUDIO\\MUSIC\\MALLAMB.VB", + "AUDIO\\MUSIC\\STRIP.VB", + "AUDIO\\MUSIC\\MALIBU.VB", + "AUDIO\\MUSIC\\HOTEL.VB", + "AUDIO\\MUSIC\\DIRTRING.VB", + "AUDIO\\MUSIC\\LAW4RIOT.VB", + "AUDIO\\MUSIC\\AMBSIL.VB", + "AUDIO\\MUSIC\\POLICE.VB", // 16 khz + "AUDIO\\MUSIC\\TAXI.VB", + "AUDIO\\MUSIC\\BCLOSED.VB", + "AUDIO\\MUSIC\\BOPEN.VB", + "AUDIO\\CUTSCENE\\ASS\\ASS_1.VB", + "AUDIO\\CUTSCENE\\ASS\\ASS_2.VB", + "AUDIO\\CUTSCENE\\BANK\\BANK_1.VB", + "AUDIO\\CUTSCENE\\BANK\\BANK_2A.VB", + "AUDIO\\CUTSCENE\\BANK\\BANK_2B.VB", + "AUDIO\\CUTSCENE\\BANK\\BANK_3A.VB", + "AUDIO\\CUTSCENE\\BANK\\BANK_3B.VB", + "AUDIO\\CUTSCENE\\BANK\\BANK_4.VB", + "AUDIO\\CUTSCENE\\BIKE\\BIKE_1.VB", + "AUDIO\\CUTSCENE\\BIKE\\BIKE_2.VB", + "AUDIO\\CUTSCENE\\BIKE\\BIKE_3.VB", + "AUDIO\\CUTSCENE\\BUD\\BUD_1.VB", + "AUDIO\\CUTSCENE\\BUD\\BUD_2.VB", + "AUDIO\\CUTSCENE\\BUD\\BUD_3.VB", + "AUDIO\\CUTSCENE\\CAP\\CAP_1.VB", + "AUDIO\\CUTSCENE\\CAR\\CAR_1.VB", + "AUDIO\\CUTSCENE\\CNT\\CNT_1A.VB", + "AUDIO\\CUTSCENE\\CNT\\CNT_1B.VB", + "AUDIO\\CUTSCENE\\CNT\\CNT_2.VB", + "AUDIO\\CUTSCENE\\COK\\COK_1.VB", + "AUDIO\\CUTSCENE\\COK\\COK_2A.VB", + "AUDIO\\CUTSCENE\\COK\\COK_2B.VB", + "AUDIO\\CUTSCENE\\COK\\COK_3.VB", + "AUDIO\\CUTSCENE\\COK\\COK_4A.VB", + "AUDIO\\CUTSCENE\\COK\\COK_4A2.VB", + "AUDIO\\CUTSCENE\\COK\\COK_4B.VB", + "AUDIO\\CUTSCENE\\COL\\COL_1.VB", + "AUDIO\\CUTSCENE\\COL\\COL_2.VB", + "AUDIO\\CUTSCENE\\COL\\COL_3A.VB", + "AUDIO\\CUTSCENE\\COL\\COL_4A.VB", + "AUDIO\\CUTSCENE\\COL\\COL_5A.VB", + "AUDIO\\CUTSCENE\\COL\\COL_5B.VB", + "AUDIO\\CUTSCENE\\CUB\\CUB_1.VB", + "AUDIO\\CUTSCENE\\CUB\\CUB_2.VB", + "AUDIO\\CUTSCENE\\CUB\\CUB_3.VB", + "AUDIO\\CUTSCENE\\CUB\\CUB_4.VB", + "AUDIO\\CUTSCENE\\DRUG\\DRUG_1.VB", + "AUDIO\\CUTSCENE\\FIN\\FIN.VB", + "AUDIO\\CUTSCENE\\FIN\\FIN2.VB", + "AUDIO\\CUTSCENE\\FINALE\\FINALE.VB", + "AUDIO\\CUTSCENE\\HAT\\HAT_1.VB", + "AUDIO\\CUTSCENE\\HAT\\HAT_2.VB", + "AUDIO\\CUTSCENE\\HAT\\HAT_3.VB", + "AUDIO\\CUTSCENE\\ICE\\ICE_1.VB", + "AUDIO\\CUTSCENE\\INT\\INT_A.VB", + "AUDIO\\CUTSCENE\\INT\\INT_B.VB", + "AUDIO\\CUTSCENE\\INT\\INT_D.VB", + "AUDIO\\CUTSCENE\\INT\\INT_M.VB", + "AUDIO\\CUTSCENE\\LAW\\LAW_1A.VB", + "AUDIO\\CUTSCENE\\LAW\\LAW_1B.VB", + "AUDIO\\CUTSCENE\\LAW\\LAW_2A.VB", + "AUDIO\\CUTSCENE\\LAW\\LAW_2B.VB", + "AUDIO\\CUTSCENE\\LAW\\LAW_2C.VB", + "AUDIO\\CUTSCENE\\LAW\\LAW_3.VB", + "AUDIO\\CUTSCENE\\LAW\\LAW_4.VB", + "AUDIO\\CUTSCENE\\PHIL\\PHIL_1.VB", + "AUDIO\\CUTSCENE\\PHIL\\PHIL_2.VB", + "AUDIO\\CUTSCENE\\PORN\\PORN_1.VB", + "AUDIO\\CUTSCENE\\PORN\\PORN_2.VB", + "AUDIO\\CUTSCENE\\PORN\\PORN_3.VB", + "AUDIO\\CUTSCENE\\PORN\\PORN_4.VB", + "AUDIO\\CUTSCENE\\RESC\\RESC_1A.VB", + "AUDIO\\CUTSCENE\\ROK\\ROK_1.VB", + "AUDIO\\CUTSCENE\\ROK\\ROK_2.VB", + "AUDIO\\CUTSCENE\\ROK\\ROK_3A.VB", + "AUDIO\\CUTSCENE\\STRIPA\\STRIPA.VB", + "AUDIO\\CUTSCENE\\TAX\\TAX_1.VB", + "AUDIO\\CUTSCENE\\TEX\\TEX_1.VB", + "AUDIO\\CUTSCENE\\TEX\\TEX_2.VB", + "AUDIO\\CUTSCENE\\TEX\\TEX_3.VB", + "AUDIO\\MUSIC\\GLIGHT.VB", + "AUDIO\\MUSIC\\FIST.VB", + "AUDIO\\MUSIC\\MISCOM.VB", + "AUDIO\\MUSIC\\MISCOM.VB", + "AUDIO\\MUSIC\\MISCOM.VB", + "AUDIO\\MUSIC\\MISCOM.VB", + "AUDIO\\MOBR1.WAV", + "AUDIO\\PAGER.WAV", + "AUDIO\\CARREV.WAV", + "AUDIO\\BIKEREV.WAV", + "AUDIO\\LIFTOP.WAV", + "AUDIO\\LIFTCL.WAV", + "AUDIO\\LIFTRUN.WAV", + "AUDIO\\LIFTBEL.WAV", + "AUDIO\\INLIFT.WAV", + "AUDIO\\SFX_01.WAV", + "AUDIO\\SFX_02.WAV", + "AUDIO\\CAMERAL.WAV", + "AUDIO\\CAMERAR.WAV", + "AUDIO\\CHEER1.WAV", + "AUDIO\\CHEER2.WAV", + "AUDIO\\CHEER3.WAV", + "AUDIO\\CHEER4.WAV", + "AUDIO\\OOH1.WAV", + "AUDIO\\OOH2.WAV", + "AUDIO\\RACE1.WAV", + "AUDIO\\RACE2.WAV", + "AUDIO\\RACE3.WAV", + "AUDIO\\RACE4.WAV", + "AUDIO\\RACE5.WAV", + "AUDIO\\RACE6.WAV", + "AUDIO\\RACE7.WAV", + "AUDIO\\RACE8.WAV", + "AUDIO\\RACE9.WAV", + "AUDIO\\RACE10.WAV", + "AUDIO\\RACE11.WAV", + "AUDIO\\RACE12.WAV", + "AUDIO\\RACE13.WAV", + "AUDIO\\RACE14.WAV", + "AUDIO\\RACE15.WAV", + "AUDIO\\HOT1.WAV", + "AUDIO\\HOT2.WAV", + "AUDIO\\HOT3.WAV", + "AUDIO\\HOT4.WAV", + "AUDIO\\HOT5.WAV", + "AUDIO\\HOT6.WAV", + "AUDIO\\HOT7.WAV", + "AUDIO\\HOT8.WAV", + "AUDIO\\HOT9.WAV", + "AUDIO\\HOT10.WAV", + "AUDIO\\HOT11.WAV", + "AUDIO\\HOT12.WAV", + "AUDIO\\HOT13.WAV", + "AUDIO\\HOT14.WAV", + "AUDIO\\HOT15.WAV", + "AUDIO\\LANSTP1.WAV", + "AUDIO\\LANSTP2.WAV", + "AUDIO\\LANAMU1.WAV", + "AUDIO\\LANAMU2.WAV", + "AUDIO\\AIRHORNL.WAV", + "AUDIO\\AIRHORNR.WAV", + "AUDIO\\SNIPSCRL.WAV", + "AUDIO\\SNIPSHORT.WAV", + "AUDIO\\BLOWROOF.WAV", + "AUDIO\\ASS_1.WAV", + "AUDIO\\ASS_2.WAV", + "AUDIO\\ASS_3.WAV", + "AUDIO\\ASS_4.WAV", + "AUDIO\\ASS_5.WAV", + "AUDIO\\ASS_6.WAV", + "AUDIO\\ASS_7.WAV", + "AUDIO\\ASS_8.WAV", + "AUDIO\\ASS_9.WAV", + "AUDIO\\ASS_10.WAV", + "AUDIO\\ASS_11.WAV", + "AUDIO\\ASS_12.WAV", + "AUDIO\\ASS_13.WAV", + "AUDIO\\ASS_14.WAV", + "AUDIO\\BIKE1_1.WAV", + "AUDIO\\BIKE1_2.WAV", + "AUDIO\\BIKE1_3.WAV", + "AUDIO\\BNK1_1.WAV", + "AUDIO\\BNK1_2.WAV", + "AUDIO\\BNK1_3.WAV", + "AUDIO\\BNK1_4.WAV", + "AUDIO\\BNK1_5.WAV", + "AUDIO\\BNK1_6.WAV", + "AUDIO\\BNK1_7.WAV", + "AUDIO\\BNK1_8.WAV", + "AUDIO\\BNK1_10.WAV", + "AUDIO\\BNK1_11.WAV", + "AUDIO\\BNK1_12.WAV", + "AUDIO\\BNK1_13.WAV", + "AUDIO\\BNK1_14.WAV", + "AUDIO\\BNK2_1.WAV", + "AUDIO\\BNK2_2.WAV", + "AUDIO\\BNK2_3.WAV", + "AUDIO\\BNK2_4.WAV", + "AUDIO\\BNK2_5.WAV", + "AUDIO\\BNK2_6.WAV", + "AUDIO\\BNK2_7.WAV", + "AUDIO\\BNK2_8.WAV", + "AUDIO\\BNK2_9.WAV", + "AUDIO\\BNK3_1.WAV", + "AUDIO\\BNK3_2.WAV", + "AUDIO\\BNK3_3A.WAV", + "AUDIO\\BNK3_3B.WAV", + "AUDIO\\BNK3_3C.WAV", + "AUDIO\\BNK3_4A.WAV", + "AUDIO\\BNK3_4B.WAV", + "AUDIO\\BNK3_4C.WAV", + "AUDIO\\BNK4_1.WAV", + "AUDIO\\BNK4_2.WAV", + "AUDIO\\BNK4_3A.WAV", + "AUDIO\\BNK4_3B.WAV", + "AUDIO\\BNK4_3C.WAV", + "AUDIO\\BNK4_3D.WAV", + "AUDIO\\BNK4_3E.WAV", + "AUDIO\\BNK4_3F.WAV", + "AUDIO\\BNK4_3G.WAV", + "AUDIO\\BNK4_3H.WAV", + "AUDIO\\BNK4_3I.WAV", + "AUDIO\\BNK4_3J.WAV", + "AUDIO\\BNK4_3K.WAV", + "AUDIO\\BNK4_3M.WAV", + "AUDIO\\BNK4_3O.WAV", + "AUDIO\\BNK4_3P.WAV", + "AUDIO\\BNK4_3Q.WAV", + "AUDIO\\BNK4_3R.WAV", + "AUDIO\\BNK4_3S.WAV", + "AUDIO\\BNK4_3T.WAV", + "AUDIO\\BNK4_3U.WAV", + "AUDIO\\BNK4_3V.WAV", + "AUDIO\\BNK4_4A.WAV", + "AUDIO\\BNK4_4B.WAV", + "AUDIO\\BNK4_5.WAV", + "AUDIO\\BNK4_6.WAV", + "AUDIO\\BNK4_7.WAV", + "AUDIO\\BNK4_8.WAV", + "AUDIO\\BNK4_9.WAV", + "AUDIO\\BNK4_10.WAV", + "AUDIO\\BNK4_11.WAV", + "AUDIO\\BK4_12A.WAV", + "AUDIO\\BK4_12B.WAV", + "AUDIO\\BK4_12C.WAV", + "AUDIO\\BNK4_13.WAV", + "AUDIO\\BK4_14A.WAV", + "AUDIO\\BK4_14B.WAV", + "AUDIO\\BNK4_15.WAV", + "AUDIO\\BNK4_16.WAV", + "AUDIO\\BNK4_17.WAV", + "AUDIO\\BNK4_18.WAV", + "AUDIO\\BK4_19A.WAV", + "AUDIO\\BK4_19B.WAV", + "AUDIO\\BK4_20A.WAV", + "AUDIO\\BK4_20B.WAV", + "AUDIO\\BNK4_21.WAV", + "AUDIO\\BNK422A.WAV", + "AUDIO\\BNK422B.WAV", + "AUDIO\\BK4_23A.WAV", + "AUDIO\\BK4_23B.WAV", + "AUDIO\\BK4_23C.WAV", + "AUDIO\\BK4_23D.WAV", + "AUDIO\\BK4_24A.WAV", + "AUDIO\\BK4_24B.WAV", + "AUDIO\\BNK4_25.WAV", + "AUDIO\\BNK4_26.WAV", + "AUDIO\\BNK4_27.WAV", + "AUDIO\\BNK4_28.WAV", + "AUDIO\\BNK4_29.WAV", + "AUDIO\\BNK4_30.WAV", + "AUDIO\\BK4_31A.WAV", + "AUDIO\\BK4_31B.WAV", + "AUDIO\\BNK4_32.WAV", + "AUDIO\\BK4_34A.WAV", + "AUDIO\\BK4_34B.WAV", + "AUDIO\\BK4_35A.WAV", + "AUDIO\\BK4_35B.WAV", + "AUDIO\\BNK4_36.WAV", + "AUDIO\\BNK4_37.WAV", + "AUDIO\\BNK4_38.WAV", + "AUDIO\\BNK4_39.WAV", + "AUDIO\\BK4_40A.WAV", + "AUDIO\\BK4_40B.WAV", + "AUDIO\\BNK4_41.WAV", + "AUDIO\\BNK4_42.WAV", + "AUDIO\\BNK4_43.WAV", + "AUDIO\\BNK4_44.WAV", + "AUDIO\\BNK4_45.WAV", + "AUDIO\\BNK4_46.WAV", + "AUDIO\\BNK4_47.WAV", + "AUDIO\\BNK4_48.WAV", + "AUDIO\\BNK4_49.WAV", + "AUDIO\\BNK450A.WAV", + "AUDIO\\BNK450B.WAV", + "AUDIO\\BNK4_51.WAV", + "AUDIO\\BNK4_94.WAV", + "AUDIO\\BNK4_95.WAV", + "AUDIO\\BNK4_96.WAV", + "AUDIO\\BNK4_97.WAV", + "AUDIO\\BNK4_98.WAV", + "AUDIO\\BNK4_99.WAV", + "AUDIO\\BUD1_1.WAV", + "AUDIO\\BUD1_2.WAV", + "AUDIO\\BUD1_3.WAV", + "AUDIO\\BUD1_4.WAV", + "AUDIO\\BUD1_5.WAV", + "AUDIO\\BUD1_9.WAV", + "AUDIO\\BUD1_10.WAV", + "AUDIO\\BUD2_1.WAV", + "AUDIO\\BUD2_2.WAV", + "AUDIO\\BUD2_3.WAV", + "AUDIO\\BUD2_4.WAV", + "AUDIO\\BUD2_5.WAV", + "AUDIO\\BUD2_6.WAV", + "AUDIO\\BUD2_7.WAV", + "AUDIO\\BUD3_1.WAV", + "AUDIO\\BUD3_1A.WAV", + "AUDIO\\BUD3_1B.WAV", + "AUDIO\\BUD3_1C.WAV", + "AUDIO\\BUD3_2.WAV", + "AUDIO\\BUD3_3.WAV", + "AUDIO\\BUD3_4.WAV", + "AUDIO\\BUD3_5.WAV", + "AUDIO\\BUD3_6.WAV", + "AUDIO\\BUD3_7.WAV", + "AUDIO\\BUD3_8A.WAV", + "AUDIO\\BUD3_8B.WAV", + "AUDIO\\BUD3_8C.WAV", + "AUDIO\\BUD3_9A.WAV", + "AUDIO\\BUD3_9B.WAV", + "AUDIO\\BUD3_9C.WAV", + "AUDIO\\CAP1_2.WAV", + "AUDIO\\CAP1_3.WAV", + "AUDIO\\CAP1_4.WAV", + "AUDIO\\CAP1_5.WAV", + "AUDIO\\CAP1_6.WAV", + "AUDIO\\CAP1_7.WAV", + "AUDIO\\CAP1_8.WAV", + "AUDIO\\CAP1_9.WAV", + "AUDIO\\CAP1_10.WAV", + "AUDIO\\CAP1_11.WAV", + "AUDIO\\CAP1_12.WAV", + "AUDIO\\CNT1_1.WAV", + "AUDIO\\CNT1_2.WAV", + "AUDIO\\CNT1_3.WAV", + "AUDIO\\CNT1_4.WAV", + "AUDIO\\CNT1_5.WAV", + "AUDIO\\CNT2_1.WAV", + "AUDIO\\CNT2_2.WAV", + "AUDIO\\CNT2_3.WAV", + "AUDIO\\CNT2_4.WAV", + "AUDIO\\COK1_1.WAV", + "AUDIO\\COK1_2.WAV", + "AUDIO\\COK1_3.WAV", + "AUDIO\\COK1_4.WAV", + "AUDIO\\COK1_5.WAV", + "AUDIO\\COK1_6.WAV", + "AUDIO\\COK2_1.WAV", + "AUDIO\\COK2_2.WAV", + "AUDIO\\COK2_3.WAV", + "AUDIO\\COK2_4.WAV", + "AUDIO\\COK2_5.WAV", + "AUDIO\\COK2_6.WAV", + "AUDIO\\COK2_7A.WAV", + "AUDIO\\COK2_7B.WAV", + "AUDIO\\COK2_7C.WAV", + "AUDIO\\COK2_8A.WAV", + "AUDIO\\COK2_8B.WAV", + "AUDIO\\COK2_8C.WAV", + "AUDIO\\COK2_8D.WAV", + "AUDIO\\COK2_9.WAV", + "AUDIO\\COK210A.WAV", + "AUDIO\\COK210B.WAV", + "AUDIO\\COK210C.WAV", + "AUDIO\\COK212A.WAV", + "AUDIO\\COK212B.WAV", + "AUDIO\\COK2_13.WAV", + "AUDIO\\COK2_14.WAV", + "AUDIO\\COK2_15.WAV", + "AUDIO\\COK2_16.WAV", + "AUDIO\\COK2_20.WAV", + "AUDIO\\COK2_21.WAV", + "AUDIO\\COK2_2.WAV", // this is probably a typo of COK2_22 + "AUDIO\\COK3_1.WAV", + "AUDIO\\COK3_2.WAV", + "AUDIO\\COK3_3.WAV", + "AUDIO\\COK3_4.WAV", + "AUDIO\\COK4_1.WAV", + "AUDIO\\COK4_2.WAV", + "AUDIO\\COK4_3.WAV", + "AUDIO\\COK4_4.WAV", + "AUDIO\\COK4_5.WAV", + "AUDIO\\COK4_6.WAV", + "AUDIO\\COK4_7.WAV", + "AUDIO\\COK4_8.WAV", + "AUDIO\\COK4_9.WAV", + "AUDIO\\COK4_9A.WAV", + "AUDIO\\COK4_10.WAV", + "AUDIO\\COK4_11.WAV", + "AUDIO\\COK4_12.WAV", + "AUDIO\\COK4_13.WAV", + "AUDIO\\COK4_14.WAV", + "AUDIO\\COK4_15.WAV", + "AUDIO\\COK4_16.WAV", + "AUDIO\\COK4_17.WAV", + "AUDIO\\COK4_18.WAV", + "AUDIO\\COK4_19.WAV", + "AUDIO\\COK4_20.WAV", + "AUDIO\\COK4_21.WAV", + "AUDIO\\COK4_22.WAV", + "AUDIO\\COK4_23.WAV", + "AUDIO\\COK4_24.WAV", + "AUDIO\\COK4_25.WAV", + "AUDIO\\COK4_26.WAV", + "AUDIO\\COK4_27.WAV", + "AUDIO\\COL1_1.WAV", + "AUDIO\\COL1_2.WAV", + "AUDIO\\COL1_3.WAV", + "AUDIO\\COL1_4.WAV", + "AUDIO\\COL1_5.WAV", + "AUDIO\\COL1_6.WAV", + "AUDIO\\COL1_7.WAV", + "AUDIO\\COL1_8.WAV", + "AUDIO\\COL2_1.WAV", + "AUDIO\\COL2_2.WAV", + "AUDIO\\COL2_3.WAV", + "AUDIO\\COL2_4.WAV", + "AUDIO\\COL2_5.WAV", + "AUDIO\\COL2_6A.WAV", + "AUDIO\\COL2_7.WAV", + "AUDIO\\COL2_8.WAV", + "AUDIO\\COL2_9.WAV", + "AUDIO\\COL2_10.WAV", + "AUDIO\\COL2_11.WAV", + "AUDIO\\COL2_12.WAV", + "AUDIO\\COL2_13.WAV", + "AUDIO\\COL2_14.WAV", + "AUDIO\\COL2_15.WAV", + "AUDIO\\COL2_16.WAV", + "AUDIO\\COL3_1.WAV", + "AUDIO\\COL3_2.WAV", + "AUDIO\\COL3_2A.WAV", + "AUDIO\\COL3_2B.WAV", + "AUDIO\\COL3_3.WAV", + "AUDIO\\COL3_4.WAV", + "AUDIO\\COL3_5.WAV", + "AUDIO\\COL3_6.WAV", + "AUDIO\\COL3_7.WAV", + "AUDIO\\COL3_8.WAV", + "AUDIO\\COL3_9.WAV", + "AUDIO\\COL3_10.WAV", + "AUDIO\\COL3_11.WAV", + "AUDIO\\COL3_12.WAV", + "AUDIO\\COL3_13.WAV", + "AUDIO\\COL3_14.WAV", + "AUDIO\\COL3_15.WAV", + "AUDIO\\COL3_16.WAV", + "AUDIO\\COL3_17.WAV", + "AUDIO\\COL3_18.WAV", + "AUDIO\\COL3_19.WAV", + "AUDIO\\COL3_20.WAV", + "AUDIO\\COL3_21.WAV", + "AUDIO\\COL3_23.WAV", + "AUDIO\\COL3_24.WAV", + "AUDIO\\COL3_25.WAV", + "AUDIO\\COL4_1.WAV", + "AUDIO\\COL4_2.WAV", + "AUDIO\\COL4_3.WAV", + "AUDIO\\COL4_4.WAV", + "AUDIO\\COL4_5.WAV", + "AUDIO\\COL4_6.WAV", + "AUDIO\\COL4_7.WAV", + "AUDIO\\COL4_8.WAV", + "AUDIO\\COL4_9.WAV", + "AUDIO\\COL4_10.WAV", + "AUDIO\\COL4_11.WAV", + "AUDIO\\COL4_12.WAV", + "AUDIO\\COL4_13.WAV", + "AUDIO\\COL4_14.WAV", + "AUDIO\\COL4_15.WAV", + "AUDIO\\COL4_16.WAV", + "AUDIO\\COL4_17.WAV", + "AUDIO\\COL4_18.WAV", + "AUDIO\\COL4_19.WAV", + "AUDIO\\COL4_20.WAV", + "AUDIO\\COL4_21.WAV", + "AUDIO\\COL4_22.WAV", + "AUDIO\\COL4_23.WAV", + "AUDIO\\COL4_24.WAV", + "AUDIO\\COL4_25.WAV", + "AUDIO\\COL4_26.WAV", + "AUDIO\\COL5_1.WAV", + "AUDIO\\COL5_2.WAV", + "AUDIO\\COL5_3.WAV", + "AUDIO\\COL5_4.WAV", + "AUDIO\\COL5_5.WAV", + "AUDIO\\COL5_6.WAV", + "AUDIO\\COL5_7.WAV", + "AUDIO\\COL5_8.WAV", + "AUDIO\\COL5_9.WAV", + "AUDIO\\COL5_10.WAV", + "AUDIO\\COL5_11.WAV", + "AUDIO\\COL5_12.WAV", + "AUDIO\\COL5_13.WAV", + "AUDIO\\COL5_14.WAV", + "AUDIO\\COL5_15.WAV", + "AUDIO\\COL5_16.WAV", + "AUDIO\\COL5_17.WAV", + "AUDIO\\COL5_18.WAV", + "AUDIO\\COL5_19.WAV", + "AUDIO\\COL5_20.WAV", + "AUDIO\\COL5_21.WAV", + "AUDIO\\COL5_22.WAV", + "AUDIO\\CUB1_1.WAV", + "AUDIO\\CUB1_2.WAV", + "AUDIO\\CUB1_3.WAV", + "AUDIO\\CUB1_4.WAV", + "AUDIO\\CUB1_5.WAV", + "AUDIO\\CUB1_6.WAV", + "AUDIO\\CUB1_7.WAV", + "AUDIO\\CUB1_8.WAV", + "AUDIO\\CUB1_9.WAV", + "AUDIO\\CUB1_10.WAV", + "AUDIO\\CUB2_1.WAV", + "AUDIO\\CUB2_2.WAV", + "AUDIO\\CUB2_3A.WAV", + "AUDIO\\CUB2_3B.WAV", + "AUDIO\\CUB2_3C.WAV", + "AUDIO\\CUB2_4A.WAV", + "AUDIO\\CUB2_5.WAV", + "AUDIO\\CUB2_6.WAV", + "AUDIO\\CUB2_7.WAV", + "AUDIO\\CUB2_8.WAV", + "AUDIO\\CUB2_9.WAV", + "AUDIO\\CUB2_10.WAV", + "AUDIO\\CUB2_11.WAV", + "AUDIO\\CUB3_1.WAV", + "AUDIO\\CUB3_2.WAV", + "AUDIO\\CUB3_3.WAV", + "AUDIO\\CUB3_4.WAV", + "AUDIO\\CUB4_1.WAV", + "AUDIO\\CUB4_2.WAV", + "AUDIO\\CUB4_3.WAV", + "AUDIO\\CUB4_4.WAV", + "AUDIO\\CUB4_5.WAV", + "AUDIO\\CUB4_5A.WAV", + "AUDIO\\CUB4_6.WAV", + "AUDIO\\CUB4_7.WAV", + "AUDIO\\CUB4_8.WAV", + "AUDIO\\CUB4_9.WAV", + "AUDIO\\CUB4_10.WAV", + "AUDIO\\CUB4_11.WAV", + "AUDIO\\CUB4_12.WAV", + "AUDIO\\CUB4_13.WAV", + "AUDIO\\CUB4_14.WAV", + "AUDIO\\CUB4_15.WAV", + "AUDIO\\CUB4_16.WAV", + "AUDIO\\GOLF_1.WAV", + "AUDIO\\GOLF_2.WAV", + "AUDIO\\GOLF_3.WAV", + "AUDIO\\BAR_1.WAV", + "AUDIO\\BAR_2.WAV", + "AUDIO\\BAR_3.WAV", + "AUDIO\\BAR_4.WAV", + "AUDIO\\BAR_5.WAV", + "AUDIO\\BAR_6.WAV", + "AUDIO\\BAR_7.WAV", + "AUDIO\\BAR_8.WAV", + "AUDIO\\STRIP_1.WAV", + "AUDIO\\STRIP_2.WAV", + "AUDIO\\STRIP_3.WAV", + "AUDIO\\STRIP_4.WAV", + "AUDIO\\STRIP_5.WAV", + "AUDIO\\STRIP_6.WAV", + "AUDIO\\STRIP_7.WAV", + "AUDIO\\STRIP_8.WAV", + "AUDIO\\STRIP_9.WAV", + "AUDIO\\STAR_1.WAV", + "AUDIO\\STAR_2.WAV", + "AUDIO\\STAR_3.WAV", + "AUDIO\\STAR_4.WAV", + "AUDIO\\FIN_1A.WAV", + "AUDIO\\FIN_1B.WAV", + "AUDIO\\FIN_1C.WAV", + "AUDIO\\FIN_2B.WAV", + "AUDIO\\FIN_2C.WAV", + "AUDIO\\FIN_3.WAV", + "AUDIO\\FIN_4.WAV", + "AUDIO\\FIN_5.WAV", + "AUDIO\\FIN_6.WAV", + "AUDIO\\FIN_10.WAV", + "AUDIO\\FIN_11A.WAV", + "AUDIO\\FIN_11B.WAV", + "AUDIO\\FIN_12A.WAV", + "AUDIO\\FIN_12B.WAV", + "AUDIO\\FIN_12C.WAV", + "AUDIO\\FIN_13.WAV", + "AUDIO\\FINKILL.WAV", + "AUDIO\\LAW1_1.WAV", + "AUDIO\\LAW1_2.WAV", + "AUDIO\\LAW1_3.WAV", + "AUDIO\\LAW1_4.WAV", + "AUDIO\\LAW1_5.WAV", + "AUDIO\\LAW1_6.WAV", + "AUDIO\\LAW1_7.WAV", + "AUDIO\\LAW1_8.WAV", + "AUDIO\\LAW1_9.WAV", + "AUDIO\\LAW1_10.WAV", + "AUDIO\\LAW2_1.WAV", + "AUDIO\\LAW2_2.WAV", + "AUDIO\\LAW2_3.WAV", + "AUDIO\\LAW2_4.WAV", + "AUDIO\\LAW2_5.WAV", + "AUDIO\\LAW2_6.WAV", + "AUDIO\\LAW2_7.WAV", + "AUDIO\\LAW2_8.WAV", + "AUDIO\\LAW2_9.WAV", + "AUDIO\\LAW2_10.WAV", + "AUDIO\\LAW3_1.WAV", + "AUDIO\\LAW3_2.WAV", + "AUDIO\\LAW3_3.WAV", + "AUDIO\\LAW3_4.WAV", + "AUDIO\\LAW3_5.WAV", + "AUDIO\\LAW3_6.WAV", + "AUDIO\\LAW3_10.WAV", + "AUDIO\\LAW3_11.WAV", + "AUDIO\\LAW3_12.WAV", + "AUDIO\\LAW3_13.WAV", + "AUDIO\\LAW3_14.WAV", + "AUDIO\\LAW3_16.WAV", + "AUDIO\\LAW3_17.WAV", + "AUDIO\\LAW3_18.WAV", + "AUDIO\\LAW3_19.WAV", + "AUDIO\\LAW3_20.WAV", + "AUDIO\\LAW3_21.WAV", + "AUDIO\\LAW3_22.WAV", + "AUDIO\\LAW3_23.WAV", + "AUDIO\\LAW3_24.WAV", + "AUDIO\\LAW3_25.WAV", + "AUDIO\\LAW4_1A.WAV", + "AUDIO\\LAW4_1B.WAV", + "AUDIO\\LAW4_1C.WAV", + "AUDIO\\LAW4_1D.WAV", + "AUDIO\\LAW4_10.WAV", + "AUDIO\\LAW4_3.WAV", + "AUDIO\\LAW4_4.WAV", + "AUDIO\\LAW4_5.WAV", + "AUDIO\\LAW4_6.WAV", + "AUDIO\\LAW4_7.WAV", + "AUDIO\\LAW4_8.WAV", + "AUDIO\\LAW4_9.WAV", + "AUDIO\\PHIL1_2.WAV", + "AUDIO\\PHIL1_3.WAV", + "AUDIO\\PHIL2_1.WAV", + "AUDIO\\PHIL2_2.WAV", + "AUDIO\\PHIL2_3.WAV", + "AUDIO\\PHIL2_4.WAV", + "AUDIO\\PHIL2_5.WAV", + "AUDIO\\PHIL2_6.WAV", + "AUDIO\\PHIL2_7.WAV", + "AUDIO\\PHIL2_8.WAV", + "AUDIO\\PHIL2_9.WAV", + "AUDIO\\PHIL210.WAV", + "AUDIO\\PHIL211.WAV", + "AUDIO\\PORN1_1.WAV", + "AUDIO\\PORN1_2.WAV", + "AUDIO\\PORN1_3.WAV", + "AUDIO\\PRN1_3A.WAV", + "AUDIO\\PORN1_4.WAV", + "AUDIO\\PORN1_5.WAV", + "AUDIO\\PORN1_6.WAV", + "AUDIO\\PORN1_7.WAV", + "AUDIO\\PORN1_8.WAV", + "AUDIO\\PORN1_9.WAV", + "AUDIO\\PRN1_10.WAV", + "AUDIO\\PRN1_11.WAV", + "AUDIO\\PRN1_12.WAV", + "AUDIO\\PRN1_13.WAV", + "AUDIO\\PRN1_14.WAV", + "AUDIO\\PRN1_15.WAV", + "AUDIO\\PRN1_16.WAV", + "AUDIO\\PRN1_17.WAV", + "AUDIO\\PRN1_18.WAV", + "AUDIO\\PRN1_19.WAV", + "AUDIO\\PRN1_20.WAV", + "AUDIO\\PRN1_21.WAV", + "AUDIO\\PORN3_1.WAV", + "AUDIO\\PORN3_2.WAV", + "AUDIO\\PORN3_3.WAV", + "AUDIO\\PORN3_4.WAV", + "AUDIO\\PSYCH_1.WAV", + "AUDIO\\PSYCH_2.WAV", + "AUDIO\\ROK2_01.WAV", + "AUDIO\\ROK3_1.WAV", + "AUDIO\\ROK3_2.WAV", + "AUDIO\\ROK3_3.WAV", + "AUDIO\\ROK3_4.WAV", + "AUDIO\\ROK3_5.WAV", + "AUDIO\\ROK3_6.WAV", + "AUDIO\\ROK3_7.WAV", + "AUDIO\\ROK3_8.WAV", + "AUDIO\\ROK3_9.WAV", + "AUDIO\\ROK3_10.WAV", + "AUDIO\\ROK3_11.WAV", + "AUDIO\\ROK3_12.WAV", + "AUDIO\\ROK3_13.WAV", + "AUDIO\\ROK3_14.WAV", + "AUDIO\\ROK3_15.WAV", + "AUDIO\\ROK3_16.WAV", + "AUDIO\\ROK3_17.WAV", + "AUDIO\\ROK3_18.WAV", + "AUDIO\\ROK3_19.WAV", + "AUDIO\\ROK3_20.WAV", + "AUDIO\\ROK3_21.WAV", + "AUDIO\\ROK3_22.WAV", + "AUDIO\\ROK3_23.WAV", + "AUDIO\\ROK3_24.WAV", + "AUDIO\\ROK3_25.WAV", + "AUDIO\\ROK3_26.WAV", + "AUDIO\\ROK3_27.WAV", + "AUDIO\\ROK3_62.WAV", + "AUDIO\\ROK3_63.WAV", + "AUDIO\\ROK3_64.WAV", + "AUDIO\\ROK3_65.WAV", + "AUDIO\\ROK3_66.WAV", + "AUDIO\\ROK3_67.WAV", + "AUDIO\\ROK3_68.WAV", + "AUDIO\\ROK3_69.WAV", + "AUDIO\\ROK3_70.WAV", + "AUDIO\\ROK3_71.WAV", + "AUDIO\\ROK3_73.WAV", + "AUDIO\\RESC_1.WAV", + "AUDIO\\RESC_2.WAV", + "AUDIO\\RESC_3.WAV", + "AUDIO\\RESC_4.WAV", + "AUDIO\\RESC_5.WAV", + "AUDIO\\RESC_6.WAV", + "AUDIO\\RESC_7.WAV", + "AUDIO\\RESC_8.WAV", + "AUDIO\\RESC_9.WAV", + "AUDIO\\RESC_10.WAV", + "AUDIO\\ROK1_1A.WAV", + "AUDIO\\ROK1_1B.WAV", + "AUDIO\\ROK1_5.WAV", + "AUDIO\\ROK1_6.WAV", + "AUDIO\\ROK1_7.WAV", + "AUDIO\\ROK1_8.WAV", + "AUDIO\\ROK1_9.WAV", + "AUDIO\\TAX1_1.WAV", + "AUDIO\\TAX1_2.WAV", + "AUDIO\\TAX1_3.WAV", + "AUDIO\\TAX1_4.WAV", + "AUDIO\\TAX1_5.WAV", + "AUDIO\\TAX2_1.WAV", + "AUDIO\\TAX2_2.WAV", + "AUDIO\\TAX2_3.WAV", + "AUDIO\\TAX2_4.WAV", + "AUDIO\\TAX2_5.WAV", + "AUDIO\\TAX2_6.WAV", + "AUDIO\\TAX2_7.WAV", + "AUDIO\\TAX3_1.WAV", + "AUDIO\\TAX3_2.WAV", + "AUDIO\\TAX3_3.WAV", + "AUDIO\\TAX3_4.WAV", + "AUDIO\\TAX3_5.WAV", + "AUDIO\\TEX1_1.WAV", + "AUDIO\\TEX1_2.WAV", + "AUDIO\\TEX1_3.WAV", + "AUDIO\\TEX1_4.WAV", + "AUDIO\\TEX1_5.WAV", + "AUDIO\\TEX1_6.WAV", + "AUDIO\\TEX2_1.WAV", + "AUDIO\\TEX3_1.WAV", + "AUDIO\\TEX3_2.WAV", + "AUDIO\\TEX3_3.WAV", + "AUDIO\\TEX3_4.WAV", + "AUDIO\\TEX3_5.WAV", + "AUDIO\\TEX3_6.WAV", + "AUDIO\\TEX3_7.WAV", + "AUDIO\\TEX3_8.WAV", + "AUDIO\\HAT_1A.WAV", + "AUDIO\\INTRO1.WAV", + "AUDIO\\INTRO2.WAV", + "AUDIO\\INTRO3.WAV", + "AUDIO\\INTRO4.WAV", + "AUDIO\\MOB_01A.WAV", + "AUDIO\\MOB_01B.WAV", + "AUDIO\\MOB_01C.WAV", + "AUDIO\\MOB_02A.WAV", + "AUDIO\\MOB_02B.WAV", + "AUDIO\\MOB_02C.WAV", + "AUDIO\\MOB_03A.WAV", + "AUDIO\\MOB_03B.WAV", + "AUDIO\\MOB_03C.WAV", + "AUDIO\\MOB_03D.WAV", + "AUDIO\\MOB_03E.WAV", + "AUDIO\\SHARK_1.WAV", + "AUDIO\\SHARK_2.WAV", + "AUDIO\\SHARK_3.WAV", + "AUDIO\\SHARK_4.WAV", + "AUDIO\\SHARK_5.WAV", + "AUDIO\\MOB_04A.WAV", + "AUDIO\\MOB_04B.WAV", + "AUDIO\\MOB_04C.WAV", + "AUDIO\\MOB_04D.WAV", + "AUDIO\\MOB_05A.WAV", + "AUDIO\\MOB_05B.WAV", + "AUDIO\\MOB_05C.WAV", + "AUDIO\\MOB_05D.WAV", + "AUDIO\\MOB_06A.WAV", + "AUDIO\\MOB_06B.WAV", + "AUDIO\\MOB_06C.WAV", + "AUDIO\\MOB_07A.WAV", + "AUDIO\\MOB_07B.WAV", + "AUDIO\\MOB_08A.WAV", + "AUDIO\\MOB_08B.WAV", + "AUDIO\\MOB_08C.WAV", + "AUDIO\\MOB_08D.WAV", + "AUDIO\\MOB_08E.WAV", + "AUDIO\\MOB_08F.WAV", + "AUDIO\\MOB_08G.WAV", + "AUDIO\\MOB_09A.WAV", + "AUDIO\\MOB_09B.WAV", + "AUDIO\\MOB_09C.WAV", + "AUDIO\\MOB_09D.WAV", + "AUDIO\\MOB_09E.WAV", + "AUDIO\\MOB_09F.WAV", + "AUDIO\\MOB_10A.WAV", + "AUDIO\\MOB_10B.WAV", + "AUDIO\\MOB_10C.WAV", + "AUDIO\\MOB_10D.WAV", + "AUDIO\\MOB_10E.WAV", + "AUDIO\\MOB_11A.WAV", + "AUDIO\\MOB_11B.WAV", + "AUDIO\\MOB_11C.WAV", + "AUDIO\\MOB_11D.WAV", + "AUDIO\\MOB_11E.WAV", + "AUDIO\\MOB_11F.WAV", + "AUDIO\\MOB_14A.WAV", + "AUDIO\\MOB_14B.WAV", + "AUDIO\\MOB_14C.WAV", + "AUDIO\\MOB_14D.WAV", + "AUDIO\\MOB_14E.WAV", + "AUDIO\\MOB_14F.WAV", + "AUDIO\\MOB_14G.WAV", + "AUDIO\\MOB_14H.WAV", + "AUDIO\\MOB_16A.WAV", + "AUDIO\\MOB_16B.WAV", + "AUDIO\\MOB_16C.WAV", + "AUDIO\\MOB_16D.WAV", + "AUDIO\\MOB_16E.WAV", + "AUDIO\\MOB_16F.WAV", + "AUDIO\\MOB_16G.WAV", + "AUDIO\\MOB_17A.WAV", + "AUDIO\\MOB_17B.WAV", + "AUDIO\\MOB_17C.WAV", + "AUDIO\\MOB_17D.WAV", + "AUDIO\\MOB_17E.WAV", + "AUDIO\\MOB_17G.WAV", + "AUDIO\\MOB_17H.WAV", + "AUDIO\\MOB_17I.WAV", + "AUDIO\\MOB_17J.WAV", + "AUDIO\\MOB_17K.WAV", + "AUDIO\\MOB_17L.WAV", + "AUDIO\\MOB_18A.WAV", + "AUDIO\\MOB_18B.WAV", + "AUDIO\\MOB_18C.WAV", + "AUDIO\\MOB_18D.WAV", + "AUDIO\\MOB_18E.WAV", + "AUDIO\\MOB_18F.WAV", + "AUDIO\\MOB_18G.WAV", + "AUDIO\\MOB_20A.WAV", + "AUDIO\\MOB_20B.WAV", + "AUDIO\\MOB_20C.WAV", + "AUDIO\\MOB_20D.WAV", + "AUDIO\\MOB_20E.WAV", + "AUDIO\\MOB_24A.WAV", + "AUDIO\\MOB_24B.WAV", + "AUDIO\\MOB_24C.WAV", + "AUDIO\\MOB_24D.WAV", + "AUDIO\\MOB_24E.WAV", + "AUDIO\\MOB_24F.WAV", + "AUDIO\\MOB_24G.WAV", + "AUDIO\\MOB_24H.WAV", + "AUDIO\\MOB_25A.WAV", + "AUDIO\\MOB_25B.WAV", + "AUDIO\\MOB_25C.WAV", + "AUDIO\\MOB_25D.WAV", + "AUDIO\\MOB_26A.WAV", + "AUDIO\\MOB_26B.WAV", + "AUDIO\\MOB_26C.WAV", + "AUDIO\\MOB_26D.WAV", + "AUDIO\\MOB_26E.WAV", + "AUDIO\\MOB_29A.WAV", + "AUDIO\\MOB_29B.WAV", + "AUDIO\\MOB_29C.WAV", + "AUDIO\\MOB_29D.WAV", + "AUDIO\\MOB_29E.WAV", + "AUDIO\\MOB_29F.WAV", + "AUDIO\\MOB_29G.WAV", + "AUDIO\\MOB_30A.WAV", + "AUDIO\\MOB_30B.WAV", + "AUDIO\\MOB_30C.WAV", + "AUDIO\\MOB_30D.WAV", + "AUDIO\\MOB_30E.WAV", + "AUDIO\\MOB_30F.WAV", + "AUDIO\\MOB_33A.WAV", + "AUDIO\\MOB_33B.WAV", + "AUDIO\\MOB_33C.WAV", + "AUDIO\\MOB_33D.WAV", + "AUDIO\\MOB_34A.WAV", + "AUDIO\\MOB_34B.WAV", + "AUDIO\\MOB_34C.WAV", + "AUDIO\\MOB_34D.WAV", + "AUDIO\\MOB_35A.WAV", + "AUDIO\\MOB_35B.WAV", + "AUDIO\\MOB_35C.WAV", + "AUDIO\\MOB_35D.WAV", + "AUDIO\\MOB_36A.WAV", + "AUDIO\\MOB_36B.WAV", + "AUDIO\\MOB_36C.WAV", + "AUDIO\\MOB_40A.WAV", + "AUDIO\\MOB_40B.WAV", + "AUDIO\\MOB_40C.WAV", + "AUDIO\\MOB_40D.WAV", + "AUDIO\\MOB_40E.WAV", + "AUDIO\\MOB_40F.WAV", + "AUDIO\\MOB_40G.WAV", + "AUDIO\\MOB_40H.WAV", + "AUDIO\\MOB_40I.WAV", + "AUDIO\\MOB_41A.WAV", + "AUDIO\\MOB_41B.WAV", + "AUDIO\\MOB_41C.WAV", + "AUDIO\\MOB_41D.WAV", + "AUDIO\\MOB_41E.WAV", + "AUDIO\\MOB_41F.WAV", + "AUDIO\\MOB_41G.WAV", + "AUDIO\\MOB_41H.WAV", + "AUDIO\\MOB_42A.WAV", + "AUDIO\\MOB_42B.WAV", + "AUDIO\\MOB_42C.WAV", + "AUDIO\\MOB_42D.WAV", + "AUDIO\\MOB_42E.WAV", + "AUDIO\\MOB_43A.WAV", + "AUDIO\\MOB_43B.WAV", + "AUDIO\\MOB_43C.WAV", + "AUDIO\\MOB_43D.WAV", + "AUDIO\\MOB_43E.WAV", + "AUDIO\\MOB_43F.WAV", + "AUDIO\\MOB_43G.WAV", + "AUDIO\\MOB_43H.WAV", + "AUDIO\\MOB_45A.WAV", + "AUDIO\\MOB_45B.WAV", + "AUDIO\\MOB_45C.WAV", + "AUDIO\\MOB_45D.WAV", + "AUDIO\\MOB_45E.WAV", + "AUDIO\\MOB_45F.WAV", + "AUDIO\\MOB_45G.WAV", + "AUDIO\\MOB_45H.WAV", + "AUDIO\\MOB_45I.WAV", + "AUDIO\\MOB_45J.WAV", + "AUDIO\\MOB_45K.WAV", + "AUDIO\\MOB_45L.WAV", + "AUDIO\\MOB_45M.WAV", + "AUDIO\\MOB_45N.WAV", + "AUDIO\\MOB_46A.WAV", + "AUDIO\\MOB_46B.WAV", + "AUDIO\\MOB_46C.WAV", + "AUDIO\\MOB_46D.WAV", + "AUDIO\\MOB_46E.WAV", + "AUDIO\\MOB_46F.WAV", + "AUDIO\\MOB_46G.WAV", + "AUDIO\\MOB_46H.WAV", + "AUDIO\\MOB_47A.WAV", + "AUDIO\\MOB_52A.WAV", + "AUDIO\\MOB_52B.WAV", + "AUDIO\\MOB_52C.WAV", + "AUDIO\\MOB_52D.WAV", + "AUDIO\\MOB_52E.WAV", + "AUDIO\\MOB_52F.WAV", + "AUDIO\\MOB_52G.WAV", + "AUDIO\\MOB_52H.WAV", + "AUDIO\\MOB_54A.WAV", + "AUDIO\\MOB_54B.WAV", + "AUDIO\\MOB_54C.WAV", + "AUDIO\\MOB_54D.WAV", + "AUDIO\\MOB_54E.WAV", + "AUDIO\\MOB_55A.WAV", + "AUDIO\\MOB_55B.WAV", + "AUDIO\\MOB_55C.WAV", + "AUDIO\\MOB_55D.WAV", + "AUDIO\\MOB_55E.WAV", + "AUDIO\\MOB_55F.WAV", + "AUDIO\\MOB_56A.WAV", + "AUDIO\\MOB_56B.WAV", + "AUDIO\\MOB_56C.WAV", + "AUDIO\\MOB_56D.WAV", + "AUDIO\\MOB_56E.WAV", + "AUDIO\\MOB_56F.WAV", + "AUDIO\\MOB_57A.WAV", + "AUDIO\\MOB_57B.WAV", + "AUDIO\\MOB_57C.WAV", + "AUDIO\\MOB_57D.WAV", + "AUDIO\\MOB_57E.WAV", + "AUDIO\\MOB_58A.WAV", + "AUDIO\\MOB_58B.WAV", + "AUDIO\\MOB_58C.WAV", + "AUDIO\\MOB_58D.WAV", + "AUDIO\\MOB_58E.WAV", + "AUDIO\\MOB_58F.WAV", + "AUDIO\\MOB_58G.WAV", + "AUDIO\\MOB_61A.WAV", + "AUDIO\\MOB_61B.WAV", + "AUDIO\\MOB_62A.WAV", + "AUDIO\\MOB_62B.WAV", + "AUDIO\\MOB_62C.WAV", + "AUDIO\\MOB_62D.WAV", + "AUDIO\\MOB_63A.WAV", + "AUDIO\\MOB_63B.WAV", + "AUDIO\\MOB_63C.WAV", + "AUDIO\\MOB_63D.WAV", + "AUDIO\\MOB_63E.WAV", + "AUDIO\\MOB_63F.WAV", + "AUDIO\\MOB_63G.WAV", + "AUDIO\\MOB_63H.WAV", + "AUDIO\\MOB_63I.WAV", + "AUDIO\\MOB_63J.WAV", + "AUDIO\\MOB_66A.WAV", + "AUDIO\\MOB_66B.WAV", + "AUDIO\\MOB_68A.WAV", + "AUDIO\\MOB_68B.WAV", + "AUDIO\\MOB_68C.WAV", + "AUDIO\\MOB_68D.WAV", + "AUDIO\\MOB_70A.WAV", + "AUDIO\\MOB_70B.WAV", + "AUDIO\\MOB_71A.WAV", + "AUDIO\\MOB_71B.WAV", + "AUDIO\\MOB_71C.WAV", + "AUDIO\\MOB_71D.WAV", + "AUDIO\\MOB_71E.WAV", + "AUDIO\\MOB_71F.WAV", + "AUDIO\\MOB_71G.WAV", + "AUDIO\\MOB_71H.WAV", + "AUDIO\\MOB_71I.WAV", + "AUDIO\\MOB_71J.WAV", + "AUDIO\\MOB_71K.WAV", + "AUDIO\\MOB_71L.WAV", + "AUDIO\\MOB_71M.WAV", + "AUDIO\\MOB_71N.WAV", + "AUDIO\\MOB_72A.WAV", + "AUDIO\\MOB_72B.WAV", + "AUDIO\\MOB_72C.WAV", + "AUDIO\\MOB_72D.WAV", + "AUDIO\\MOB_72E.WAV", + "AUDIO\\MOB_72F.WAV", + "AUDIO\\MOB_72G.WAV", + "AUDIO\\MOB_73A.WAV", + "AUDIO\\MOB_73C.WAV", + "AUDIO\\MOB_73D.WAV", + "AUDIO\\MOB_73F.WAV", + "AUDIO\\MOB_73G.WAV", + "AUDIO\\MOB_73I.WAV", + "AUDIO\\MOB_95A.WAV", + "AUDIO\\MOB_96A.WAV", + "AUDIO\\MOB_98A.WAV", + "AUDIO\\MOB_99A.WAV", + "AUDIO\\JOB1_1B.WAV", + "AUDIO\\JOB1_1C.WAV", + "AUDIO\\JOB1_1D.WAV", + "AUDIO\\JOB2_1B.WAV", + "AUDIO\\JOB2_2.WAV", + "AUDIO\\JOB2_3.WAV", + "AUDIO\\JOB2_4.WAV", + "AUDIO\\JOB2_5.WAV", + "AUDIO\\JOB2_6.WAV", + "AUDIO\\JOB2_7.WAV", + "AUDIO\\JOB2_8.WAV", + "AUDIO\\JOB2_9.WAV", + "AUDIO\\JOB3_1.WAV", + "AUDIO\\JOB3_2.WAV", + "AUDIO\\JOB3_3.WAV", + "AUDIO\\JOB4_1.WAV", + "AUDIO\\JOB4_2.WAV", + "AUDIO\\JOB4_3.WAV", + "AUDIO\\JOB5_1.WAV", + "AUDIO\\JOB5_2.WAV", + "AUDIO\\JOB5_3.WAV", + "AUDIO\\BJM1_20.WAV", + "AUDIO\\BJM1_4.WAV", + "AUDIO\\BJM1_5.WAV", + "AUDIO\\MERC_39.WAV", + "AUDIO\\MONO_1.WAV", + "AUDIO\\MONO_2.WAV", + "AUDIO\\MONO_3.WAV", + "AUDIO\\MONO_4.WAV", + "AUDIO\\MONO_5.WAV", + "AUDIO\\MONO_6.WAV", + "AUDIO\\MONO_7.WAV", + "AUDIO\\MONO_8.WAV", + "AUDIO\\MONO_9.WAV", + "AUDIO\\MONO10.WAV", + "AUDIO\\MONO11.WAV", + "AUDIO\\MONO12.WAV", + "AUDIO\\MONO13.WAV", + "AUDIO\\MONO14.WAV", + "AUDIO\\MONO15.WAV", + "AUDIO\\MONO16.WAV", + "AUDIO\\FUD_01.WAV", + "AUDIO\\FUD_02.WAV", + "AUDIO\\FUD_03.WAV", + "AUDIO\\FUD_04.WAV", + "AUDIO\\FUD_05.WAV", + "AUDIO\\FUD_06.WAV", + "AUDIO\\FUD_07.WAV", + "AUDIO\\FUD_08.WAV", + "AUDIO\\FUD_09.WAV", + "AUDIO\\FUD_10.WAV", + "AUDIO\\FUD_11.WAV", + "AUDIO\\FUD_12.WAV", + "AUDIO\\FUD_13.WAV", + "AUDIO\\FUD_14.WAV", + "AUDIO\\FUD_15.WAV", + "AUDIO\\FUD_16.WAV", + "AUDIO\\FUD_17.WAV", + "AUDIO\\FUD_18.WAV", + "AUDIO\\FUD_19.WAV", + "AUDIO\\FUD_20.WAV", + "AUDIO\\BURG_01.WAV", + "AUDIO\\BURG_02.WAV", + "AUDIO\\BURG_03.WAV", + "AUDIO\\BURG_04.WAV", + "AUDIO\\BURG_05.WAV", + "AUDIO\\BURG_06.WAV", + "AUDIO\\BURG_07.WAV", + "AUDIO\\BURG_08.WAV", + "AUDIO\\BURG_09.WAV", + "AUDIO\\BURG_10.WAV", + "AUDIO\\BURG_11.WAV", + "AUDIO\\BURG_12.WAV", + "AUDIO\\CRUST01.WAV", + "AUDIO\\CRUST02.WAV", + "AUDIO\\CRUST03.WAV", + "AUDIO\\CRUST04.WAV", + "AUDIO\\CRUST05.WAV", + "AUDIO\\CRUST06.WAV", + "AUDIO\\CRUST07.WAV", + "AUDIO\\CRUST08.WAV", + "AUDIO\\CRUST09.WAV", + "AUDIO\\BAND_01.WAV", + "AUDIO\\BAND_02.WAV", + "AUDIO\\BAND_03.WAV", + "AUDIO\\BAND_04.WAV", + "AUDIO\\BAND_05.WAV", + "AUDIO\\BAND_06.WAV", + "AUDIO\\BAND_07.WAV", + "AUDIO\\BAND_08.WAV", + "AUDIO\\SHAFT01.WAV", + "AUDIO\\SHAFT02.WAV", + "AUDIO\\SHAFT03.WAV", + "AUDIO\\SHAFT04.WAV", + "AUDIO\\SHAFT05.WAV", + "AUDIO\\SHAFT06.WAV", + "AUDIO\\SHAFT07.WAV", + "AUDIO\\SHAFT08.WAV", + "AUDIO\\PISS_01.WAV", + "AUDIO\\PISS_02.WAV", + "AUDIO\\PISS_03.WAV", + "AUDIO\\PISS_04.WAV", + "AUDIO\\PISS_05.WAV", + "AUDIO\\PISS_06.WAV", + "AUDIO\\PISS_07.WAV", + "AUDIO\\PISS_08.WAV", + "AUDIO\\PISS_09.WAV", + "AUDIO\\PISS_10.WAV", + "AUDIO\\PISS_11.WAV", + "AUDIO\\PISS_12.WAV", + "AUDIO\\PISS_13.WAV", + "AUDIO\\PISS_14.WAV", + "AUDIO\\PISS_15.WAV", + "AUDIO\\PISS_16.WAV", + "AUDIO\\PISS_17.WAV", + "AUDIO\\PISS_18.WAV", + "AUDIO\\PISS_19.WAV", + "AUDIO\\GIMME01.WAV", + "AUDIO\\GIMME02.WAV", + "AUDIO\\GIMME03.WAV", + "AUDIO\\GIMME04.WAV", + "AUDIO\\GIMME05.WAV", + "AUDIO\\GIMME06.WAV", + "AUDIO\\GIMME07.WAV", + "AUDIO\\GIMME08.WAV", + "AUDIO\\GIMME09.WAV", + "AUDIO\\GIMME10.WAV", + "AUDIO\\GIMME11.WAV", + "AUDIO\\GIMME12.WAV", + "AUDIO\\GIMME13.WAV", + "AUDIO\\GIMME14.WAV", + "AUDIO\\GIMME15.WAV", + "AUDIO\\BUST_01.WAV", + "AUDIO\\BUST_02.WAV", + "AUDIO\\BUST_03.WAV", + "AUDIO\\BUST_04.WAV", + "AUDIO\\BUST_05.WAV", + "AUDIO\\BUST_06.WAV", + "AUDIO\\BUST_07.WAV", + "AUDIO\\BUST_08.WAV", + "AUDIO\\BUST_09.WAV", + "AUDIO\\BUST_10.WAV", + "AUDIO\\BUST_11.WAV", + "AUDIO\\BUST_12.WAV", + "AUDIO\\BUST_13.WAV", + "AUDIO\\BUST_14.WAV", + "AUDIO\\BUST_15.WAV", + "AUDIO\\BUST_16.WAV", + "AUDIO\\BUST_17.WAV", + "AUDIO\\BUST_18.WAV", + "AUDIO\\BUST_19.WAV", + "AUDIO\\BUST_20.WAV", + "AUDIO\\BUST_21.WAV", + "AUDIO\\BUST_22.WAV", + "AUDIO\\BUST_23.WAV", + "AUDIO\\BUST_24.WAV", + "AUDIO\\BUST_25.WAV", + "AUDIO\\BUST_26.WAV", + "AUDIO\\BUST_27.WAV", + "AUDIO\\BUST_28.WAV", +}; +#endif + +#ifdef PC_AUDIO_PATHS +static char StreamedNameTable[][25] = +{ + "AUDIO\\WILD.ADF", + "AUDIO\\FLASH.ADF", + "AUDIO\\KCHAT.ADF", + "AUDIO\\FEVER.ADF", + "AUDIO\\VROCK.ADF", + "AUDIO\\VCPR.ADF", + "AUDIO\\ESPANT.ADF", + "AUDIO\\EMOTION.ADF", + "AUDIO\\WAVE.ADF", + "AUDIO\\MISCOM.MP3", + "AUDIO\\CITY.MP3", + "AUDIO\\WATER.MP3", + "AUDIO\\BEACHAMB.MP3", + "AUDIO\\HCITY.MP3", + "AUDIO\\HWATER.MP3", + "AUDIO\\HBEACH.MP3", + "AUDIO\\MALLAMB.MP3", + "AUDIO\\STRIP.MP3", + "AUDIO\\MALIBU.MP3", + "AUDIO\\HOTEL.MP3", + "AUDIO\\DIRTRING.MP3", + "AUDIO\\LAW4RIOT.MP3", + "AUDIO\\AMBSIL.MP3", + "AUDIO\\POLICE.MP3", + "AUDIO\\TAXI.MP3", + "AUDIO\\BCLOSED.MP3", + "AUDIO\\BOPEN.MP3", + "AUDIO\\ASS_1.MP3", + "AUDIO\\ASS_2.MP3", + "AUDIO\\BANK_1.MP3", + "AUDIO\\BANK_2A.MP3", + "AUDIO\\BANK_2B.MP3", + "AUDIO\\BANK_3A.MP3", + "AUDIO\\BANK_3B.MP3", + "AUDIO\\BANK_4.MP3", + "AUDIO\\BIKE_1.MP3", + "AUDIO\\BIKE_2.MP3", + "AUDIO\\BIKE_3.MP3", + "AUDIO\\BUD_1.MP3", + "AUDIO\\BUD_2.MP3", + "AUDIO\\BUD_3.MP3", + "AUDIO\\CAP_1.MP3", + "AUDIO\\CAR_1.MP3", + "AUDIO\\CNT_1A.MP3", + "AUDIO\\CNT_1B.MP3", + "AUDIO\\CNT_2.MP3", + "AUDIO\\COK_1.MP3", + "AUDIO\\COK_2A.MP3", + "AUDIO\\COK_2B.MP3", + "AUDIO\\COK_3.MP3", + "AUDIO\\COK_4A.MP3", + "AUDIO\\COK_4A2.MP3", + "AUDIO\\COK_4B.MP3", + "AUDIO\\COL_1.MP3", + "AUDIO\\COL_2.MP3", + "AUDIO\\COL_3A.MP3", + "AUDIO\\COL_4A.MP3", + "AUDIO\\COL_5A.MP3", + "AUDIO\\COL_5B.MP3", + "AUDIO\\CUB_1.MP3", + "AUDIO\\CUB_2.MP3", + "AUDIO\\CUB_3.MP3", + "AUDIO\\CUB_4.MP3", + "AUDIO\\DRUG_1.MP3", + "AUDIO\\FIN.MP3", + "AUDIO\\FIN2.MP3", + "AUDIO\\FINALE.MP3", + "AUDIO\\HAT_1.MP3", + "AUDIO\\HAT_2.MP3", + "AUDIO\\HAT_3.MP3", + "AUDIO\\ICE_1.MP3", + "AUDIO\\INT_A.MP3", + "AUDIO\\INT_B.MP3", + "AUDIO\\INT_D.MP3", + "AUDIO\\INT_M.MP3", + "AUDIO\\LAW_1A.MP3", + "AUDIO\\LAW_1B.MP3", + "AUDIO\\LAW_2A.MP3", + "AUDIO\\LAW_2B.MP3", + "AUDIO\\LAW_2C.MP3", + "AUDIO\\LAW_3.MP3", + "AUDIO\\LAW_4.MP3", + "AUDIO\\PHIL_1.MP3", + "AUDIO\\PHIL_2.MP3", + "AUDIO\\PORN_1.MP3", + "AUDIO\\PORN_2.MP3", + "AUDIO\\PORN_3.MP3", + "AUDIO\\PORN_4.MP3", + "AUDIO\\RESC_1A.MP3", + "AUDIO\\ROK_1.MP3", + "AUDIO\\ROK_2.MP3", + "AUDIO\\ROK_3A.MP3", + "AUDIO\\STRIPA.MP3", + "AUDIO\\TAX_1.MP3", + "AUDIO\\TEX_1.MP3", + "AUDIO\\TEX_2.MP3", + "AUDIO\\TEX_3.MP3", + "AUDIO\\GLIGHT.MP3", + "AUDIO\\FIST.MP3", + "AUDIO\\MISCOM.MP3", + "AUDIO\\MISCOM.MP3", + "AUDIO\\MISCOM.MP3", + "AUDIO\\MISCOM.MP3", + "AUDIO\\MOBR1.WAV", + "AUDIO\\PAGER.WAV", + "AUDIO\\CARREV.WAV", + "AUDIO\\BIKEREV.WAV", + "AUDIO\\LIFTOP.WAV", + "AUDIO\\LIFTCL.WAV", + "AUDIO\\LIFTRUN.WAV", + "AUDIO\\LIFTBEL.WAV", + "AUDIO\\INLIFT.WAV", + "AUDIO\\SFX_01.WAV", + "AUDIO\\SFX_02.WAV", + "AUDIO\\CAMERAL.WAV", + "AUDIO\\CAMERAR.WAV", + "AUDIO\\CHEER1.WAV", + "AUDIO\\CHEER2.WAV", + "AUDIO\\CHEER3.WAV", + "AUDIO\\CHEER4.WAV", + "AUDIO\\OOH1.WAV", + "AUDIO\\OOH2.WAV", + "AUDIO\\RACE1.WAV", + "AUDIO\\RACE2.WAV", + "AUDIO\\RACE3.WAV", + "AUDIO\\RACE4.WAV", + "AUDIO\\RACE5.WAV", + "AUDIO\\RACE6.WAV", + "AUDIO\\RACE7.WAV", + "AUDIO\\RACE8.WAV", + "AUDIO\\RACE9.WAV", + "AUDIO\\RACE10.WAV", + "AUDIO\\RACE11.WAV", + "AUDIO\\RACE12.WAV", + "AUDIO\\RACE13.WAV", + "AUDIO\\RACE14.WAV", + "AUDIO\\RACE15.WAV", + "AUDIO\\HOT1.WAV", + "AUDIO\\HOT2.WAV", + "AUDIO\\HOT3.WAV", + "AUDIO\\HOT4.WAV", + "AUDIO\\HOT5.WAV", + "AUDIO\\HOT6.WAV", + "AUDIO\\HOT7.WAV", + "AUDIO\\HOT8.WAV", + "AUDIO\\HOT9.WAV", + "AUDIO\\HOT10.WAV", + "AUDIO\\HOT11.WAV", + "AUDIO\\HOT12.WAV", + "AUDIO\\HOT13.WAV", + "AUDIO\\HOT14.WAV", + "AUDIO\\HOT15.WAV", + "AUDIO\\LANSTP1.WAV", + "AUDIO\\LANSTP2.WAV", + "AUDIO\\LANAMU1.WAV", + "AUDIO\\LANAMU2.WAV", + "AUDIO\\AIRHORNL.WAV", + "AUDIO\\AIRHORNR.WAV", + "AUDIO\\SNIPSCRL.WAV", + "AUDIO\\SNIPSHORT.WAV", + "AUDIO\\BLOWROOF.WAV", + "AUDIO\\ASS_1.WAV", + "AUDIO\\ASS_2.WAV", + "AUDIO\\ASS_3.WAV", + "AUDIO\\ASS_4.WAV", + "AUDIO\\ASS_5.WAV", + "AUDIO\\ASS_6.WAV", + "AUDIO\\ASS_7.WAV", + "AUDIO\\ASS_8.WAV", + "AUDIO\\ASS_9.WAV", + "AUDIO\\ASS_10.WAV", + "AUDIO\\ASS_11.WAV", + "AUDIO\\ASS_12.WAV", + "AUDIO\\ASS_13.WAV", + "AUDIO\\ASS_14.WAV", + "AUDIO\\BIKE1_1.WAV", + "AUDIO\\BIKE1_2.WAV", + "AUDIO\\BIKE1_3.WAV", + "AUDIO\\BNK1_1.WAV", + "AUDIO\\BNK1_2.WAV", + "AUDIO\\BNK1_3.WAV", + "AUDIO\\BNK1_4.WAV", + "AUDIO\\BNK1_5.WAV", + "AUDIO\\BNK1_6.WAV", + "AUDIO\\BNK1_7.WAV", + "AUDIO\\BNK1_8.WAV", + "AUDIO\\BNK1_10.WAV", + "AUDIO\\BNK1_11.WAV", + "AUDIO\\BNK1_12.WAV", + "AUDIO\\BNK1_13.WAV", + "AUDIO\\BNK1_14.WAV", + "AUDIO\\BNK2_1.WAV", + "AUDIO\\BNK2_2.WAV", + "AUDIO\\BNK2_3.WAV", + "AUDIO\\BNK2_4.WAV", + "AUDIO\\BNK2_5.WAV", + "AUDIO\\BNK2_6.WAV", + "AUDIO\\BNK2_7.WAV", + "AUDIO\\BNK2_8.WAV", + "AUDIO\\BNK2_9.WAV", + "AUDIO\\BNK3_1.WAV", + "AUDIO\\BNK3_2.WAV", + "AUDIO\\BNK3_3A.WAV", + "AUDIO\\BNK3_3B.WAV", + "AUDIO\\BNK3_3C.WAV", + "AUDIO\\BNK3_4A.WAV", + "AUDIO\\BNK3_4B.WAV", + "AUDIO\\BNK3_4C.WAV", + "AUDIO\\BNK4_1.WAV", + "AUDIO\\BNK4_2.WAV", + "AUDIO\\BNK4_3A.WAV", + "AUDIO\\BNK4_3B.WAV", + "AUDIO\\BNK4_3C.WAV", + "AUDIO\\BNK4_3D.WAV", + "AUDIO\\BNK4_3E.WAV", + "AUDIO\\BNK4_3F.WAV", + "AUDIO\\BNK4_3G.WAV", + "AUDIO\\BNK4_3H.WAV", + "AUDIO\\BNK4_3I.WAV", + "AUDIO\\BNK4_3J.WAV", + "AUDIO\\BNK4_3K.WAV", + "AUDIO\\BNK4_3M.WAV", + "AUDIO\\BNK4_3O.WAV", + "AUDIO\\BNK4_3P.WAV", + "AUDIO\\BNK4_3Q.WAV", + "AUDIO\\BNK4_3R.WAV", + "AUDIO\\BNK4_3S.WAV", + "AUDIO\\BNK4_3T.WAV", + "AUDIO\\BNK4_3U.WAV", + "AUDIO\\BNK4_3V.WAV", + "AUDIO\\BNK4_4A.WAV", + "AUDIO\\BNK4_4B.WAV", + "AUDIO\\BNK4_5.WAV", + "AUDIO\\BNK4_6.WAV", + "AUDIO\\BNK4_7.WAV", + "AUDIO\\BNK4_8.WAV", + "AUDIO\\BNK4_9.WAV", + "AUDIO\\BNK4_10.WAV", + "AUDIO\\BNK4_11.WAV", + "AUDIO\\BK4_12A.WAV", + "AUDIO\\BK4_12B.WAV", + "AUDIO\\BK4_12C.WAV", + "AUDIO\\BNK4_13.WAV", + "AUDIO\\BK4_14A.WAV", + "AUDIO\\BK4_14B.WAV", + "AUDIO\\BNK4_15.WAV", + "AUDIO\\BNK4_16.WAV", + "AUDIO\\BNK4_17.WAV", + "AUDIO\\BNK4_18.WAV", + "AUDIO\\BK4_19A.WAV", + "AUDIO\\BK4_19B.WAV", + "AUDIO\\BK4_20A.WAV", + "AUDIO\\BK4_20B.WAV", + "AUDIO\\BNK4_21.WAV", + "AUDIO\\BNK422A.WAV", + "AUDIO\\BNK422B.WAV", + "AUDIO\\BK4_23A.WAV", + "AUDIO\\BK4_23B.WAV", + "AUDIO\\BK4_23C.WAV", + "AUDIO\\BK4_23D.WAV", + "AUDIO\\BK4_24A.WAV", + "AUDIO\\BK4_24B.WAV", + "AUDIO\\BNK4_25.WAV", + "AUDIO\\BNK4_26.WAV", + "AUDIO\\BNK4_27.WAV", + "AUDIO\\BNK4_28.WAV", + "AUDIO\\BNK4_29.WAV", + "AUDIO\\BNK4_30.WAV", + "AUDIO\\BK4_31A.WAV", + "AUDIO\\BK4_31B.WAV", + "AUDIO\\BNK4_32.WAV", + "AUDIO\\BK4_34A.WAV", + "AUDIO\\BK4_34B.WAV", + "AUDIO\\BK4_35A.WAV", + "AUDIO\\BK4_35B.WAV", + "AUDIO\\BNK4_36.WAV", + "AUDIO\\BNK4_37.WAV", + "AUDIO\\BNK4_38.WAV", + "AUDIO\\BNK4_39.WAV", + "AUDIO\\BK4_40A.WAV", + "AUDIO\\BK4_40B.WAV", + "AUDIO\\BNK4_41.WAV", + "AUDIO\\BNK4_42.WAV", + "AUDIO\\BNK4_43.WAV", + "AUDIO\\BNK4_44.WAV", + "AUDIO\\BNK4_45.WAV", + "AUDIO\\BNK4_46.WAV", + "AUDIO\\BNK4_47.WAV", + "AUDIO\\BNK4_48.WAV", + "AUDIO\\BNK4_49.WAV", + "AUDIO\\BNK450A.WAV", + "AUDIO\\BNK450B.WAV", + "AUDIO\\BNK4_51.WAV", + "AUDIO\\BNK4_94.WAV", + "AUDIO\\BNK4_95.WAV", + "AUDIO\\BNK4_96.WAV", + "AUDIO\\BNK4_97.WAV", + "AUDIO\\BNK4_98.WAV", + "AUDIO\\BNK4_99.WAV", + "AUDIO\\BUD1_1.WAV", + "AUDIO\\BUD1_2.WAV", + "AUDIO\\BUD1_3.WAV", + "AUDIO\\BUD1_4.WAV", + "AUDIO\\BUD1_5.WAV", + "AUDIO\\BUD1_9.WAV", + "AUDIO\\BUD1_10.WAV", + "AUDIO\\BUD2_1.WAV", + "AUDIO\\BUD2_2.WAV", + "AUDIO\\BUD2_3.WAV", + "AUDIO\\BUD2_4.WAV", + "AUDIO\\BUD2_5.WAV", + "AUDIO\\BUD2_6.WAV", + "AUDIO\\BUD2_7.WAV", + "AUDIO\\BUD3_1.WAV", + "AUDIO\\BUD3_1A.WAV", + "AUDIO\\BUD3_1B.WAV", + "AUDIO\\BUD3_1C.WAV", + "AUDIO\\BUD3_2.WAV", + "AUDIO\\BUD3_3.WAV", + "AUDIO\\BUD3_4.WAV", + "AUDIO\\BUD3_5.WAV", + "AUDIO\\BUD3_6.WAV", + "AUDIO\\BUD3_7.WAV", + "AUDIO\\BUD3_8A.WAV", + "AUDIO\\BUD3_8B.WAV", + "AUDIO\\BUD3_8C.WAV", + "AUDIO\\BUD3_9A.WAV", + "AUDIO\\BUD3_9B.WAV", + "AUDIO\\BUD3_9C.WAV", + "AUDIO\\CAP1_2.WAV", + "AUDIO\\CAP1_3.WAV", + "AUDIO\\CAP1_4.WAV", + "AUDIO\\CAP1_5.WAV", + "AUDIO\\CAP1_6.WAV", + "AUDIO\\CAP1_7.WAV", + "AUDIO\\CAP1_8.WAV", + "AUDIO\\CAP1_9.WAV", + "AUDIO\\CAP1_10.WAV", + "AUDIO\\CAP1_11.WAV", + "AUDIO\\CAP1_12.WAV", + "AUDIO\\CNT1_1.WAV", + "AUDIO\\CNT1_2.WAV", + "AUDIO\\CNT1_3.WAV", + "AUDIO\\CNT1_4.WAV", + "AUDIO\\CNT1_5.WAV", + "AUDIO\\CNT2_1.WAV", + "AUDIO\\CNT2_2.WAV", + "AUDIO\\CNT2_3.WAV", + "AUDIO\\CNT2_4.WAV", + "AUDIO\\COK1_1.WAV", + "AUDIO\\COK1_2.WAV", + "AUDIO\\COK1_3.WAV", + "AUDIO\\COK1_4.WAV", + "AUDIO\\COK1_5.WAV", + "AUDIO\\COK1_6.WAV", + "AUDIO\\COK2_1.WAV", + "AUDIO\\COK2_2.WAV", + "AUDIO\\COK2_3.WAV", + "AUDIO\\COK2_4.WAV", + "AUDIO\\COK2_5.WAV", + "AUDIO\\COK2_6.WAV", + "AUDIO\\COK2_7A.WAV", + "AUDIO\\COK2_7B.WAV", + "AUDIO\\COK2_7C.WAV", + "AUDIO\\COK2_8A.WAV", + "AUDIO\\COK2_8B.WAV", + "AUDIO\\COK2_8C.WAV", + "AUDIO\\COK2_8D.WAV", + "AUDIO\\COK2_9.WAV", + "AUDIO\\COK210A.WAV", + "AUDIO\\COK210B.WAV", + "AUDIO\\COK210C.WAV", + "AUDIO\\COK212A.WAV", + "AUDIO\\COK212B.WAV", + "AUDIO\\COK2_13.WAV", + "AUDIO\\COK2_14.WAV", + "AUDIO\\COK2_15.WAV", + "AUDIO\\COK2_16.WAV", + "AUDIO\\COK2_20.WAV", + "AUDIO\\COK2_21.WAV", + "AUDIO\\COK2_2.WAV", // this is probably a typo of COK2_22 + "AUDIO\\COK3_1.WAV", + "AUDIO\\COK3_2.WAV", + "AUDIO\\COK3_3.WAV", + "AUDIO\\COK3_4.WAV", + "AUDIO\\COK4_1.WAV", + "AUDIO\\COK4_2.WAV", + "AUDIO\\COK4_3.WAV", + "AUDIO\\COK4_4.WAV", + "AUDIO\\COK4_5.WAV", + "AUDIO\\COK4_6.WAV", + "AUDIO\\COK4_7.WAV", + "AUDIO\\COK4_8.WAV", + "AUDIO\\COK4_9.WAV", + "AUDIO\\COK4_9A.WAV", + "AUDIO\\COK4_10.WAV", + "AUDIO\\COK4_11.WAV", + "AUDIO\\COK4_12.WAV", + "AUDIO\\COK4_13.WAV", + "AUDIO\\COK4_14.WAV", + "AUDIO\\COK4_15.WAV", + "AUDIO\\COK4_16.WAV", + "AUDIO\\COK4_17.WAV", + "AUDIO\\COK4_18.WAV", + "AUDIO\\COK4_19.WAV", + "AUDIO\\COK4_20.WAV", + "AUDIO\\COK4_21.WAV", + "AUDIO\\COK4_22.WAV", + "AUDIO\\COK4_23.WAV", + "AUDIO\\COK4_24.WAV", + "AUDIO\\COK4_25.WAV", + "AUDIO\\COK4_26.WAV", + "AUDIO\\COK4_27.WAV", + "AUDIO\\COL1_1.WAV", + "AUDIO\\COL1_2.WAV", + "AUDIO\\COL1_3.WAV", + "AUDIO\\COL1_4.WAV", + "AUDIO\\COL1_5.WAV", + "AUDIO\\COL1_6.WAV", + "AUDIO\\COL1_7.WAV", + "AUDIO\\COL1_8.WAV", + "AUDIO\\COL2_1.WAV", + "AUDIO\\COL2_2.WAV", + "AUDIO\\COL2_3.WAV", + "AUDIO\\COL2_4.WAV", + "AUDIO\\COL2_5.WAV", + "AUDIO\\COL2_6A.WAV", + "AUDIO\\COL2_7.WAV", + "AUDIO\\COL2_8.WAV", + "AUDIO\\COL2_9.WAV", + "AUDIO\\COL2_10.WAV", + "AUDIO\\COL2_11.WAV", + "AUDIO\\COL2_12.WAV", + "AUDIO\\COL2_13.WAV", + "AUDIO\\COL2_14.WAV", + "AUDIO\\COL2_15.WAV", + "AUDIO\\COL2_16.WAV", + "AUDIO\\COL3_1.WAV", + "AUDIO\\COL3_2.WAV", + "AUDIO\\COL3_2A.WAV", + "AUDIO\\COL3_2B.WAV", + "AUDIO\\COL3_3.WAV", + "AUDIO\\COL3_4.WAV", + "AUDIO\\COL3_5.WAV", + "AUDIO\\COL3_6.WAV", + "AUDIO\\COL3_7.WAV", + "AUDIO\\COL3_8.WAV", + "AUDIO\\COL3_9.WAV", + "AUDIO\\COL3_10.WAV", + "AUDIO\\COL3_11.WAV", + "AUDIO\\COL3_12.WAV", + "AUDIO\\COL3_13.WAV", + "AUDIO\\COL3_14.WAV", + "AUDIO\\COL3_15.WAV", + "AUDIO\\COL3_16.WAV", + "AUDIO\\COL3_17.WAV", + "AUDIO\\COL3_18.WAV", + "AUDIO\\COL3_19.WAV", + "AUDIO\\COL3_20.WAV", + "AUDIO\\COL3_21.WAV", + "AUDIO\\COL3_23.WAV", + "AUDIO\\COL3_24.WAV", + "AUDIO\\COL3_25.WAV", + "AUDIO\\COL4_1.WAV", + "AUDIO\\COL4_2.WAV", + "AUDIO\\COL4_3.WAV", + "AUDIO\\COL4_4.WAV", + "AUDIO\\COL4_5.WAV", + "AUDIO\\COL4_6.WAV", + "AUDIO\\COL4_7.WAV", + "AUDIO\\COL4_8.WAV", + "AUDIO\\COL4_9.WAV", + "AUDIO\\COL4_10.WAV", + "AUDIO\\COL4_11.WAV", + "AUDIO\\COL4_12.WAV", + "AUDIO\\COL4_13.WAV", + "AUDIO\\COL4_14.WAV", + "AUDIO\\COL4_15.WAV", + "AUDIO\\COL4_16.WAV", + "AUDIO\\COL4_17.WAV", + "AUDIO\\COL4_18.WAV", + "AUDIO\\COL4_19.WAV", + "AUDIO\\COL4_20.WAV", + "AUDIO\\COL4_21.WAV", + "AUDIO\\COL4_22.WAV", + "AUDIO\\COL4_23.WAV", + "AUDIO\\COL4_24.WAV", + "AUDIO\\COL4_25.WAV", + "AUDIO\\COL4_26.WAV", + "AUDIO\\COL5_1.WAV", + "AUDIO\\COL5_2.WAV", + "AUDIO\\COL5_3.WAV", + "AUDIO\\COL5_4.WAV", + "AUDIO\\COL5_5.WAV", + "AUDIO\\COL5_6.WAV", + "AUDIO\\COL5_7.WAV", + "AUDIO\\COL5_8.WAV", + "AUDIO\\COL5_9.WAV", + "AUDIO\\COL5_10.WAV", + "AUDIO\\COL5_11.WAV", + "AUDIO\\COL5_12.WAV", + "AUDIO\\COL5_13.WAV", + "AUDIO\\COL5_14.WAV", + "AUDIO\\COL5_15.WAV", + "AUDIO\\COL5_16.WAV", + "AUDIO\\COL5_17.WAV", + "AUDIO\\COL5_18.WAV", + "AUDIO\\COL5_19.WAV", + "AUDIO\\COL5_20.WAV", + "AUDIO\\COL5_21.WAV", + "AUDIO\\COL5_22.WAV", + "AUDIO\\CUB1_1.WAV", + "AUDIO\\CUB1_2.WAV", + "AUDIO\\CUB1_3.WAV", + "AUDIO\\CUB1_4.WAV", + "AUDIO\\CUB1_5.WAV", + "AUDIO\\CUB1_6.WAV", + "AUDIO\\CUB1_7.WAV", + "AUDIO\\CUB1_8.WAV", + "AUDIO\\CUB1_9.WAV", + "AUDIO\\CUB1_10.WAV", + "AUDIO\\CUB2_1.WAV", + "AUDIO\\CUB2_2.WAV", + "AUDIO\\CUB2_3A.WAV", + "AUDIO\\CUB2_3B.WAV", + "AUDIO\\CUB2_3C.WAV", + "AUDIO\\CUB2_4A.WAV", + "AUDIO\\CUB2_5.WAV", + "AUDIO\\CUB2_6.WAV", + "AUDIO\\CUB2_7.WAV", + "AUDIO\\CUB2_8.WAV", + "AUDIO\\CUB2_9.WAV", + "AUDIO\\CUB2_10.WAV", + "AUDIO\\CUB2_11.WAV", + "AUDIO\\CUB3_1.WAV", + "AUDIO\\CUB3_2.WAV", + "AUDIO\\CUB3_3.WAV", + "AUDIO\\CUB3_4.WAV", + "AUDIO\\CUB4_1.WAV", + "AUDIO\\CUB4_2.WAV", + "AUDIO\\CUB4_3.WAV", + "AUDIO\\CUB4_4.WAV", + "AUDIO\\CUB4_5.WAV", + "AUDIO\\CUB4_5A.WAV", + "AUDIO\\CUB4_6.WAV", + "AUDIO\\CUB4_7.WAV", + "AUDIO\\CUB4_8.WAV", + "AUDIO\\CUB4_9.WAV", + "AUDIO\\CUB4_10.WAV", + "AUDIO\\CUB4_11.WAV", + "AUDIO\\CUB4_12.WAV", + "AUDIO\\CUB4_13.WAV", + "AUDIO\\CUB4_14.WAV", + "AUDIO\\CUB4_15.WAV", + "AUDIO\\CUB4_16.WAV", + "AUDIO\\GOLF_1.WAV", + "AUDIO\\GOLF_2.WAV", + "AUDIO\\GOLF_3.WAV", + "AUDIO\\BAR_1.WAV", + "AUDIO\\BAR_2.WAV", + "AUDIO\\BAR_3.WAV", + "AUDIO\\BAR_4.WAV", + "AUDIO\\BAR_5.WAV", + "AUDIO\\BAR_6.WAV", + "AUDIO\\BAR_7.WAV", + "AUDIO\\BAR_8.WAV", + "AUDIO\\STRIP_1.WAV", + "AUDIO\\STRIP_2.WAV", + "AUDIO\\STRIP_3.WAV", + "AUDIO\\STRIP_4.WAV", + "AUDIO\\STRIP_5.WAV", + "AUDIO\\STRIP_6.WAV", + "AUDIO\\STRIP_7.WAV", + "AUDIO\\STRIP_8.WAV", + "AUDIO\\STRIP_9.WAV", + "AUDIO\\STAR_1.WAV", + "AUDIO\\STAR_2.WAV", + "AUDIO\\STAR_3.WAV", + "AUDIO\\STAR_4.WAV", + "AUDIO\\FIN_1A.WAV", + "AUDIO\\FIN_1B.WAV", + "AUDIO\\FIN_1C.WAV", + "AUDIO\\FIN_2B.WAV", + "AUDIO\\FIN_2C.WAV", + "AUDIO\\FIN_3.WAV", + "AUDIO\\FIN_4.WAV", + "AUDIO\\FIN_5.WAV", + "AUDIO\\FIN_6.WAV", + "AUDIO\\FIN_10.WAV", + "AUDIO\\FIN_11A.WAV", + "AUDIO\\FIN_11B.WAV", + "AUDIO\\FIN_12A.WAV", + "AUDIO\\FIN_12B.WAV", + "AUDIO\\FIN_12C.WAV", + "AUDIO\\FIN_13.WAV", + "AUDIO\\FINKILL.WAV", + "AUDIO\\LAW1_1.WAV", + "AUDIO\\LAW1_2.WAV", + "AUDIO\\LAW1_3.WAV", + "AUDIO\\LAW1_4.WAV", + "AUDIO\\LAW1_5.WAV", + "AUDIO\\LAW1_6.WAV", + "AUDIO\\LAW1_7.WAV", + "AUDIO\\LAW1_8.WAV", + "AUDIO\\LAW1_9.WAV", + "AUDIO\\LAW1_10.WAV", + "AUDIO\\LAW2_1.WAV", + "AUDIO\\LAW2_2.WAV", + "AUDIO\\LAW2_3.WAV", + "AUDIO\\LAW2_4.WAV", + "AUDIO\\LAW2_5.WAV", + "AUDIO\\LAW2_6.WAV", + "AUDIO\\LAW2_7.WAV", + "AUDIO\\LAW2_8.WAV", + "AUDIO\\LAW2_9.WAV", + "AUDIO\\LAW2_10.WAV", + "AUDIO\\LAW3_1.WAV", + "AUDIO\\LAW3_2.WAV", + "AUDIO\\LAW3_3.WAV", + "AUDIO\\LAW3_4.WAV", + "AUDIO\\LAW3_5.WAV", + "AUDIO\\LAW3_6.WAV", + "AUDIO\\LAW3_10.WAV", + "AUDIO\\LAW3_11.WAV", + "AUDIO\\LAW3_12.WAV", + "AUDIO\\LAW3_13.WAV", + "AUDIO\\LAW3_14.WAV", + "AUDIO\\LAW3_16.WAV", + "AUDIO\\LAW3_17.WAV", + "AUDIO\\LAW3_18.WAV", + "AUDIO\\LAW3_19.WAV", + "AUDIO\\LAW3_20.WAV", + "AUDIO\\LAW3_21.WAV", + "AUDIO\\LAW3_22.WAV", + "AUDIO\\LAW3_23.WAV", + "AUDIO\\LAW3_24.WAV", + "AUDIO\\LAW3_25.WAV", + "AUDIO\\LAW4_1A.WAV", + "AUDIO\\LAW4_1B.WAV", + "AUDIO\\LAW4_1C.WAV", + "AUDIO\\LAW4_1D.WAV", + "AUDIO\\LAW4_10.WAV", + "AUDIO\\LAW4_3.WAV", + "AUDIO\\LAW4_4.WAV", + "AUDIO\\LAW4_5.WAV", + "AUDIO\\LAW4_6.WAV", + "AUDIO\\LAW4_7.WAV", + "AUDIO\\LAW4_8.WAV", + "AUDIO\\LAW4_9.WAV", + "AUDIO\\PHIL1_2.WAV", + "AUDIO\\PHIL1_3.WAV", + "AUDIO\\PHIL2_1.WAV", + "AUDIO\\PHIL2_2.WAV", + "AUDIO\\PHIL2_3.WAV", + "AUDIO\\PHIL2_4.WAV", + "AUDIO\\PHIL2_5.WAV", + "AUDIO\\PHIL2_6.WAV", + "AUDIO\\PHIL2_7.WAV", + "AUDIO\\PHIL2_8.WAV", + "AUDIO\\PHIL2_9.WAV", + "AUDIO\\PHIL210.WAV", + "AUDIO\\PHIL211.WAV", + "AUDIO\\PORN1_1.WAV", + "AUDIO\\PORN1_2.WAV", + "AUDIO\\PORN1_3.WAV", + "AUDIO\\PRN1_3A.WAV", + "AUDIO\\PORN1_4.WAV", + "AUDIO\\PORN1_5.WAV", + "AUDIO\\PORN1_6.WAV", + "AUDIO\\PORN1_7.WAV", + "AUDIO\\PORN1_8.WAV", + "AUDIO\\PORN1_9.WAV", + "AUDIO\\PRN1_10.WAV", + "AUDIO\\PRN1_11.WAV", + "AUDIO\\PRN1_12.WAV", + "AUDIO\\PRN1_13.WAV", + "AUDIO\\PRN1_14.WAV", + "AUDIO\\PRN1_15.WAV", + "AUDIO\\PRN1_16.WAV", + "AUDIO\\PRN1_17.WAV", + "AUDIO\\PRN1_18.WAV", + "AUDIO\\PRN1_19.WAV", + "AUDIO\\PRN1_20.WAV", + "AUDIO\\PRN1_21.WAV", + "AUDIO\\PORN3_1.WAV", + "AUDIO\\PORN3_2.WAV", + "AUDIO\\PORN3_3.WAV", + "AUDIO\\PORN3_4.WAV", + "AUDIO\\PSYCH_1.WAV", + "AUDIO\\PSYCH_2.WAV", + "AUDIO\\ROK2_01.WAV", + "AUDIO\\ROK3_1.WAV", + "AUDIO\\ROK3_2.WAV", + "AUDIO\\ROK3_3.WAV", + "AUDIO\\ROK3_4.WAV", + "AUDIO\\ROK3_5.WAV", + "AUDIO\\ROK3_6.WAV", + "AUDIO\\ROK3_7.WAV", + "AUDIO\\ROK3_8.WAV", + "AUDIO\\ROK3_9.WAV", + "AUDIO\\ROK3_10.WAV", + "AUDIO\\ROK3_11.WAV", + "AUDIO\\ROK3_12.WAV", + "AUDIO\\ROK3_13.WAV", + "AUDIO\\ROK3_14.WAV", + "AUDIO\\ROK3_15.WAV", + "AUDIO\\ROK3_16.WAV", + "AUDIO\\ROK3_17.WAV", + "AUDIO\\ROK3_18.WAV", + "AUDIO\\ROK3_19.WAV", + "AUDIO\\ROK3_20.WAV", + "AUDIO\\ROK3_21.WAV", + "AUDIO\\ROK3_22.WAV", + "AUDIO\\ROK3_23.WAV", + "AUDIO\\ROK3_24.WAV", + "AUDIO\\ROK3_25.WAV", + "AUDIO\\ROK3_26.WAV", + "AUDIO\\ROK3_27.WAV", + "AUDIO\\ROK3_62.WAV", + "AUDIO\\ROK3_63.WAV", + "AUDIO\\ROK3_64.WAV", + "AUDIO\\ROK3_65.WAV", + "AUDIO\\ROK3_66.WAV", + "AUDIO\\ROK3_67.WAV", + "AUDIO\\ROK3_68.WAV", + "AUDIO\\ROK3_69.WAV", + "AUDIO\\ROK3_70.WAV", + "AUDIO\\ROK3_71.WAV", + "AUDIO\\ROK3_73.WAV", + "AUDIO\\RESC_1.WAV", + "AUDIO\\RESC_2.WAV", + "AUDIO\\RESC_3.WAV", + "AUDIO\\RESC_4.WAV", + "AUDIO\\RESC_5.WAV", + "AUDIO\\RESC_6.WAV", + "AUDIO\\RESC_7.WAV", + "AUDIO\\RESC_8.WAV", + "AUDIO\\RESC_9.WAV", + "AUDIO\\RESC_10.WAV", + "AUDIO\\ROK1_1A.WAV", + "AUDIO\\ROK1_1B.WAV", + "AUDIO\\ROK1_5.WAV", + "AUDIO\\ROK1_6.WAV", + "AUDIO\\ROK1_7.WAV", + "AUDIO\\ROK1_8.WAV", + "AUDIO\\ROK1_9.WAV", + "AUDIO\\TAX1_1.WAV", + "AUDIO\\TAX1_2.WAV", + "AUDIO\\TAX1_3.WAV", + "AUDIO\\TAX1_4.WAV", + "AUDIO\\TAX1_5.WAV", + "AUDIO\\TAX2_1.WAV", + "AUDIO\\TAX2_2.WAV", + "AUDIO\\TAX2_3.WAV", + "AUDIO\\TAX2_4.WAV", + "AUDIO\\TAX2_5.WAV", + "AUDIO\\TAX2_6.WAV", + "AUDIO\\TAX2_7.WAV", + "AUDIO\\TAX3_1.WAV", + "AUDIO\\TAX3_2.WAV", + "AUDIO\\TAX3_3.WAV", + "AUDIO\\TAX3_4.WAV", + "AUDIO\\TAX3_5.WAV", + "AUDIO\\TEX1_1.WAV", + "AUDIO\\TEX1_2.WAV", + "AUDIO\\TEX1_3.WAV", + "AUDIO\\TEX1_4.WAV", + "AUDIO\\TEX1_5.WAV", + "AUDIO\\TEX1_6.WAV", + "AUDIO\\TEX2_1.WAV", + "AUDIO\\TEX3_1.WAV", + "AUDIO\\TEX3_2.WAV", + "AUDIO\\TEX3_3.WAV", + "AUDIO\\TEX3_4.WAV", + "AUDIO\\TEX3_5.WAV", + "AUDIO\\TEX3_6.WAV", + "AUDIO\\TEX3_7.WAV", + "AUDIO\\TEX3_8.WAV", + "AUDIO\\HAT_1A.WAV", + "AUDIO\\INTRO1.WAV", + "AUDIO\\INTRO2.WAV", + "AUDIO\\INTRO3.WAV", + "AUDIO\\INTRO4.WAV", + "AUDIO\\MOB_01A.WAV", + "AUDIO\\MOB_01B.WAV", + "AUDIO\\MOB_01C.WAV", + "AUDIO\\MOB_02A.WAV", + "AUDIO\\MOB_02B.WAV", + "AUDIO\\MOB_02C.WAV", + "AUDIO\\MOB_03A.WAV", + "AUDIO\\MOB_03B.WAV", + "AUDIO\\MOB_03C.WAV", + "AUDIO\\MOB_03D.WAV", + "AUDIO\\MOB_03E.WAV", + "AUDIO\\SHARK_1.WAV", + "AUDIO\\SHARK_2.WAV", + "AUDIO\\SHARK_3.WAV", + "AUDIO\\SHARK_4.WAV", + "AUDIO\\SHARK_5.WAV", + "AUDIO\\MOB_04A.WAV", + "AUDIO\\MOB_04B.WAV", + "AUDIO\\MOB_04C.WAV", + "AUDIO\\MOB_04D.WAV", + "AUDIO\\MOB_05A.WAV", + "AUDIO\\MOB_05B.WAV", + "AUDIO\\MOB_05C.WAV", + "AUDIO\\MOB_05D.WAV", + "AUDIO\\MOB_06A.WAV", + "AUDIO\\MOB_06B.WAV", + "AUDIO\\MOB_06C.WAV", + "AUDIO\\MOB_07A.WAV", + "AUDIO\\MOB_07B.WAV", + "AUDIO\\MOB_08A.WAV", + "AUDIO\\MOB_08B.WAV", + "AUDIO\\MOB_08C.WAV", + "AUDIO\\MOB_08D.WAV", + "AUDIO\\MOB_08E.WAV", + "AUDIO\\MOB_08F.WAV", + "AUDIO\\MOB_08G.WAV", + "AUDIO\\MOB_09A.WAV", + "AUDIO\\MOB_09B.WAV", + "AUDIO\\MOB_09C.WAV", + "AUDIO\\MOB_09D.WAV", + "AUDIO\\MOB_09E.WAV", + "AUDIO\\MOB_09F.WAV", + "AUDIO\\MOB_10A.WAV", + "AUDIO\\MOB_10B.WAV", + "AUDIO\\MOB_10C.WAV", + "AUDIO\\MOB_10D.WAV", + "AUDIO\\MOB_10E.WAV", + "AUDIO\\MOB_11A.WAV", + "AUDIO\\MOB_11B.WAV", + "AUDIO\\MOB_11C.WAV", + "AUDIO\\MOB_11D.WAV", + "AUDIO\\MOB_11E.WAV", + "AUDIO\\MOB_11F.WAV", + "AUDIO\\MOB_14A.WAV", + "AUDIO\\MOB_14B.WAV", + "AUDIO\\MOB_14C.WAV", + "AUDIO\\MOB_14D.WAV", + "AUDIO\\MOB_14E.WAV", + "AUDIO\\MOB_14F.WAV", + "AUDIO\\MOB_14G.WAV", + "AUDIO\\MOB_14H.WAV", + "AUDIO\\MOB_16A.WAV", + "AUDIO\\MOB_16B.WAV", + "AUDIO\\MOB_16C.WAV", + "AUDIO\\MOB_16D.WAV", + "AUDIO\\MOB_16E.WAV", + "AUDIO\\MOB_16F.WAV", + "AUDIO\\MOB_16G.WAV", + "AUDIO\\MOB_17A.WAV", + "AUDIO\\MOB_17B.WAV", + "AUDIO\\MOB_17C.WAV", + "AUDIO\\MOB_17D.WAV", + "AUDIO\\MOB_17E.WAV", + "AUDIO\\MOB_17G.WAV", + "AUDIO\\MOB_17H.WAV", + "AUDIO\\MOB_17I.WAV", + "AUDIO\\MOB_17J.WAV", + "AUDIO\\MOB_17K.WAV", + "AUDIO\\MOB_17L.WAV", + "AUDIO\\MOB_18A.WAV", + "AUDIO\\MOB_18B.WAV", + "AUDIO\\MOB_18C.WAV", + "AUDIO\\MOB_18D.WAV", + "AUDIO\\MOB_18E.WAV", + "AUDIO\\MOB_18F.WAV", + "AUDIO\\MOB_18G.WAV", + "AUDIO\\MOB_20A.WAV", + "AUDIO\\MOB_20B.WAV", + "AUDIO\\MOB_20C.WAV", + "AUDIO\\MOB_20D.WAV", + "AUDIO\\MOB_20E.WAV", + "AUDIO\\MOB_24A.WAV", + "AUDIO\\MOB_24B.WAV", + "AUDIO\\MOB_24C.WAV", + "AUDIO\\MOB_24D.WAV", + "AUDIO\\MOB_24E.WAV", + "AUDIO\\MOB_24F.WAV", + "AUDIO\\MOB_24G.WAV", + "AUDIO\\MOB_24H.WAV", + "AUDIO\\MOB_25A.WAV", + "AUDIO\\MOB_25B.WAV", + "AUDIO\\MOB_25C.WAV", + "AUDIO\\MOB_25D.WAV", + "AUDIO\\MOB_26A.WAV", + "AUDIO\\MOB_26B.WAV", + "AUDIO\\MOB_26C.WAV", + "AUDIO\\MOB_26D.WAV", + "AUDIO\\MOB_26E.WAV", + "AUDIO\\MOB_29A.WAV", + "AUDIO\\MOB_29B.WAV", + "AUDIO\\MOB_29C.WAV", + "AUDIO\\MOB_29D.WAV", + "AUDIO\\MOB_29E.WAV", + "AUDIO\\MOB_29F.WAV", + "AUDIO\\MOB_29G.WAV", + "AUDIO\\MOB_30A.WAV", + "AUDIO\\MOB_30B.WAV", + "AUDIO\\MOB_30C.WAV", + "AUDIO\\MOB_30D.WAV", + "AUDIO\\MOB_30E.WAV", + "AUDIO\\MOB_30F.WAV", + "AUDIO\\MOB_33A.WAV", + "AUDIO\\MOB_33B.WAV", + "AUDIO\\MOB_33C.WAV", + "AUDIO\\MOB_33D.WAV", + "AUDIO\\MOB_34A.WAV", + "AUDIO\\MOB_34B.WAV", + "AUDIO\\MOB_34C.WAV", + "AUDIO\\MOB_34D.WAV", + "AUDIO\\MOB_35A.WAV", + "AUDIO\\MOB_35B.WAV", + "AUDIO\\MOB_35C.WAV", + "AUDIO\\MOB_35D.WAV", + "AUDIO\\MOB_36A.WAV", + "AUDIO\\MOB_36B.WAV", + "AUDIO\\MOB_36C.WAV", + "AUDIO\\MOB_40A.WAV", + "AUDIO\\MOB_40B.WAV", + "AUDIO\\MOB_40C.WAV", + "AUDIO\\MOB_40D.WAV", + "AUDIO\\MOB_40E.WAV", + "AUDIO\\MOB_40F.WAV", + "AUDIO\\MOB_40G.WAV", + "AUDIO\\MOB_40H.WAV", + "AUDIO\\MOB_40I.WAV", + "AUDIO\\MOB_41A.WAV", + "AUDIO\\MOB_41B.WAV", + "AUDIO\\MOB_41C.WAV", + "AUDIO\\MOB_41D.WAV", + "AUDIO\\MOB_41E.WAV", + "AUDIO\\MOB_41F.WAV", + "AUDIO\\MOB_41G.WAV", + "AUDIO\\MOB_41H.WAV", + "AUDIO\\MOB_42A.WAV", + "AUDIO\\MOB_42B.WAV", + "AUDIO\\MOB_42C.WAV", + "AUDIO\\MOB_42D.WAV", + "AUDIO\\MOB_42E.WAV", + "AUDIO\\MOB_43A.WAV", + "AUDIO\\MOB_43B.WAV", + "AUDIO\\MOB_43C.WAV", + "AUDIO\\MOB_43D.WAV", + "AUDIO\\MOB_43E.WAV", + "AUDIO\\MOB_43F.WAV", + "AUDIO\\MOB_43G.WAV", + "AUDIO\\MOB_43H.WAV", + "AUDIO\\MOB_45A.WAV", + "AUDIO\\MOB_45B.WAV", + "AUDIO\\MOB_45C.WAV", + "AUDIO\\MOB_45D.WAV", + "AUDIO\\MOB_45E.WAV", + "AUDIO\\MOB_45F.WAV", + "AUDIO\\MOB_45G.WAV", + "AUDIO\\MOB_45H.WAV", + "AUDIO\\MOB_45I.WAV", + "AUDIO\\MOB_45J.WAV", + "AUDIO\\MOB_45K.WAV", + "AUDIO\\MOB_45L.WAV", + "AUDIO\\MOB_45M.WAV", + "AUDIO\\MOB_45N.WAV", + "AUDIO\\MOB_46A.WAV", + "AUDIO\\MOB_46B.WAV", + "AUDIO\\MOB_46C.WAV", + "AUDIO\\MOB_46D.WAV", + "AUDIO\\MOB_46E.WAV", + "AUDIO\\MOB_46F.WAV", + "AUDIO\\MOB_46G.WAV", + "AUDIO\\MOB_46H.WAV", + "AUDIO\\MOB_47A.WAV", + "AUDIO\\MOB_52A.WAV", + "AUDIO\\MOB_52B.WAV", + "AUDIO\\MOB_52C.WAV", + "AUDIO\\MOB_52D.WAV", + "AUDIO\\MOB_52E.WAV", + "AUDIO\\MOB_52F.WAV", + "AUDIO\\MOB_52G.WAV", + "AUDIO\\MOB_52H.WAV", + "AUDIO\\MOB_54A.WAV", + "AUDIO\\MOB_54B.WAV", + "AUDIO\\MOB_54C.WAV", + "AUDIO\\MOB_54D.WAV", + "AUDIO\\MOB_54E.WAV", + "AUDIO\\MOB_55A.WAV", + "AUDIO\\MOB_55B.WAV", + "AUDIO\\MOB_55C.WAV", + "AUDIO\\MOB_55D.WAV", + "AUDIO\\MOB_55E.WAV", + "AUDIO\\MOB_55F.WAV", + "AUDIO\\MOB_56A.WAV", + "AUDIO\\MOB_56B.WAV", + "AUDIO\\MOB_56C.WAV", + "AUDIO\\MOB_56D.WAV", + "AUDIO\\MOB_56E.WAV", + "AUDIO\\MOB_56F.WAV", + "AUDIO\\MOB_57A.WAV", + "AUDIO\\MOB_57B.WAV", + "AUDIO\\MOB_57C.WAV", + "AUDIO\\MOB_57D.WAV", + "AUDIO\\MOB_57E.WAV", + "AUDIO\\MOB_58A.WAV", + "AUDIO\\MOB_58B.WAV", + "AUDIO\\MOB_58C.WAV", + "AUDIO\\MOB_58D.WAV", + "AUDIO\\MOB_58E.WAV", + "AUDIO\\MOB_58F.WAV", + "AUDIO\\MOB_58G.WAV", + "AUDIO\\MOB_61A.WAV", + "AUDIO\\MOB_61B.WAV", + "AUDIO\\MOB_62A.WAV", + "AUDIO\\MOB_62B.WAV", + "AUDIO\\MOB_62C.WAV", + "AUDIO\\MOB_62D.WAV", + "AUDIO\\MOB_63A.WAV", + "AUDIO\\MOB_63B.WAV", + "AUDIO\\MOB_63C.WAV", + "AUDIO\\MOB_63D.WAV", + "AUDIO\\MOB_63E.WAV", + "AUDIO\\MOB_63F.WAV", + "AUDIO\\MOB_63G.WAV", + "AUDIO\\MOB_63H.WAV", + "AUDIO\\MOB_63I.WAV", + "AUDIO\\MOB_63J.WAV", + "AUDIO\\MOB_66A.WAV", + "AUDIO\\MOB_66B.WAV", + "AUDIO\\MOB_68A.WAV", + "AUDIO\\MOB_68B.WAV", + "AUDIO\\MOB_68C.WAV", + "AUDIO\\MOB_68D.WAV", + "AUDIO\\MOB_70A.WAV", + "AUDIO\\MOB_70B.WAV", + "AUDIO\\MOB_71A.WAV", + "AUDIO\\MOB_71B.WAV", + "AUDIO\\MOB_71C.WAV", + "AUDIO\\MOB_71D.WAV", + "AUDIO\\MOB_71E.WAV", + "AUDIO\\MOB_71F.WAV", + "AUDIO\\MOB_71G.WAV", + "AUDIO\\MOB_71H.WAV", + "AUDIO\\MOB_71I.WAV", + "AUDIO\\MOB_71J.WAV", + "AUDIO\\MOB_71K.WAV", + "AUDIO\\MOB_71L.WAV", + "AUDIO\\MOB_71M.WAV", + "AUDIO\\MOB_71N.WAV", + "AUDIO\\MOB_72A.WAV", + "AUDIO\\MOB_72B.WAV", + "AUDIO\\MOB_72C.WAV", + "AUDIO\\MOB_72D.WAV", + "AUDIO\\MOB_72E.WAV", + "AUDIO\\MOB_72F.WAV", + "AUDIO\\MOB_72G.WAV", + "AUDIO\\MOB_73A.WAV", + "AUDIO\\MOB_73C.WAV", + "AUDIO\\MOB_73D.WAV", + "AUDIO\\MOB_73F.WAV", + "AUDIO\\MOB_73G.WAV", + "AUDIO\\MOB_73I.WAV", + "AUDIO\\MOB_95A.WAV", + "AUDIO\\MOB_96A.WAV", + "AUDIO\\MOB_98A.WAV", + "AUDIO\\MOB_99A.WAV", + "AUDIO\\JOB1_1B.WAV", + "AUDIO\\JOB1_1C.WAV", + "AUDIO\\JOB1_1D.WAV", + "AUDIO\\JOB2_1B.WAV", + "AUDIO\\JOB2_2.WAV", + "AUDIO\\JOB2_3.WAV", + "AUDIO\\JOB2_4.WAV", + "AUDIO\\JOB2_5.WAV", + "AUDIO\\JOB2_6.WAV", + "AUDIO\\JOB2_7.WAV", + "AUDIO\\JOB2_8.WAV", + "AUDIO\\JOB2_9.WAV", + "AUDIO\\JOB3_1.WAV", + "AUDIO\\JOB3_2.WAV", + "AUDIO\\JOB3_3.WAV", + "AUDIO\\JOB4_1.WAV", + "AUDIO\\JOB4_2.WAV", + "AUDIO\\JOB4_3.WAV", + "AUDIO\\JOB5_1.WAV", + "AUDIO\\JOB5_2.WAV", + "AUDIO\\JOB5_3.WAV", + "AUDIO\\BJM1_20.WAV", + "AUDIO\\BJM1_4.WAV", + "AUDIO\\BJM1_5.WAV", + "AUDIO\\MERC_39.WAV", + "AUDIO\\MONO_1.WAV", + "AUDIO\\MONO_2.WAV", + "AUDIO\\MONO_3.WAV", + "AUDIO\\MONO_4.WAV", + "AUDIO\\MONO_5.WAV", + "AUDIO\\MONO_6.WAV", + "AUDIO\\MONO_7.WAV", + "AUDIO\\MONO_8.WAV", + "AUDIO\\MONO_9.WAV", + "AUDIO\\MONO10.WAV", + "AUDIO\\MONO11.WAV", + "AUDIO\\MONO12.WAV", + "AUDIO\\MONO13.WAV", + "AUDIO\\MONO14.WAV", + "AUDIO\\MONO15.WAV", + "AUDIO\\MONO16.WAV", + "AUDIO\\FUD_01.WAV", + "AUDIO\\FUD_02.WAV", + "AUDIO\\FUD_03.WAV", + "AUDIO\\FUD_04.WAV", + "AUDIO\\FUD_05.WAV", + "AUDIO\\FUD_06.WAV", + "AUDIO\\FUD_07.WAV", + "AUDIO\\FUD_08.WAV", + "AUDIO\\FUD_09.WAV", + "AUDIO\\FUD_10.WAV", + "AUDIO\\FUD_11.WAV", + "AUDIO\\FUD_12.WAV", + "AUDIO\\FUD_13.WAV", + "AUDIO\\FUD_14.WAV", + "AUDIO\\FUD_15.WAV", + "AUDIO\\FUD_16.WAV", + "AUDIO\\FUD_17.WAV", + "AUDIO\\FUD_18.WAV", + "AUDIO\\FUD_19.WAV", + "AUDIO\\FUD_20.WAV", + "AUDIO\\BURG_01.WAV", + "AUDIO\\BURG_02.WAV", + "AUDIO\\BURG_03.WAV", + "AUDIO\\BURG_04.WAV", + "AUDIO\\BURG_05.WAV", + "AUDIO\\BURG_06.WAV", + "AUDIO\\BURG_07.WAV", + "AUDIO\\BURG_08.WAV", + "AUDIO\\BURG_09.WAV", + "AUDIO\\BURG_10.WAV", + "AUDIO\\BURG_11.WAV", + "AUDIO\\BURG_12.WAV", + "AUDIO\\CRUST01.WAV", + "AUDIO\\CRUST02.WAV", + "AUDIO\\CRUST03.WAV", + "AUDIO\\CRUST04.WAV", + "AUDIO\\CRUST05.WAV", + "AUDIO\\CRUST06.WAV", + "AUDIO\\CRUST07.WAV", + "AUDIO\\CRUST08.WAV", + "AUDIO\\CRUST09.WAV", + "AUDIO\\BAND_01.WAV", + "AUDIO\\BAND_02.WAV", + "AUDIO\\BAND_03.WAV", + "AUDIO\\BAND_04.WAV", + "AUDIO\\BAND_05.WAV", + "AUDIO\\BAND_06.WAV", + "AUDIO\\BAND_07.WAV", + "AUDIO\\BAND_08.WAV", + "AUDIO\\SHAFT01.WAV", + "AUDIO\\SHAFT02.WAV", + "AUDIO\\SHAFT03.WAV", + "AUDIO\\SHAFT04.WAV", + "AUDIO\\SHAFT05.WAV", + "AUDIO\\SHAFT06.WAV", + "AUDIO\\SHAFT07.WAV", + "AUDIO\\SHAFT08.WAV", + "AUDIO\\PISS_01.WAV", + "AUDIO\\PISS_02.WAV", + "AUDIO\\PISS_03.WAV", + "AUDIO\\PISS_04.WAV", + "AUDIO\\PISS_05.WAV", + "AUDIO\\PISS_06.WAV", + "AUDIO\\PISS_07.WAV", + "AUDIO\\PISS_08.WAV", + "AUDIO\\PISS_09.WAV", + "AUDIO\\PISS_10.WAV", + "AUDIO\\PISS_11.WAV", + "AUDIO\\PISS_12.WAV", + "AUDIO\\PISS_13.WAV", + "AUDIO\\PISS_14.WAV", + "AUDIO\\PISS_15.WAV", + "AUDIO\\PISS_16.WAV", + "AUDIO\\PISS_17.WAV", + "AUDIO\\PISS_18.WAV", + "AUDIO\\PISS_19.WAV", + "AUDIO\\GIMME01.WAV", + "AUDIO\\GIMME02.WAV", + "AUDIO\\GIMME03.WAV", + "AUDIO\\GIMME04.WAV", + "AUDIO\\GIMME05.WAV", + "AUDIO\\GIMME06.WAV", + "AUDIO\\GIMME07.WAV", + "AUDIO\\GIMME08.WAV", + "AUDIO\\GIMME09.WAV", + "AUDIO\\GIMME10.WAV", + "AUDIO\\GIMME11.WAV", + "AUDIO\\GIMME12.WAV", + "AUDIO\\GIMME13.WAV", + "AUDIO\\GIMME14.WAV", + "AUDIO\\GIMME15.WAV", + "AUDIO\\BUST_01.WAV", + "AUDIO\\BUST_02.WAV", + "AUDIO\\BUST_03.WAV", + "AUDIO\\BUST_04.WAV", + "AUDIO\\BUST_05.WAV", + "AUDIO\\BUST_06.WAV", + "AUDIO\\BUST_07.WAV", + "AUDIO\\BUST_08.WAV", + "AUDIO\\BUST_09.WAV", + "AUDIO\\BUST_10.WAV", + "AUDIO\\BUST_11.WAV", + "AUDIO\\BUST_12.WAV", + "AUDIO\\BUST_13.WAV", + "AUDIO\\BUST_14.WAV", + "AUDIO\\BUST_15.WAV", + "AUDIO\\BUST_16.WAV", + "AUDIO\\BUST_17.WAV", + "AUDIO\\BUST_18.WAV", + "AUDIO\\BUST_19.WAV", + "AUDIO\\BUST_20.WAV", + "AUDIO\\BUST_21.WAV", + "AUDIO\\BUST_22.WAV", + "AUDIO\\BUST_23.WAV", + "AUDIO\\BUST_24.WAV", + "AUDIO\\BUST_25.WAV", + "AUDIO\\BUST_26.WAV", + "AUDIO\\BUST_27.WAV", + "AUDIO\\BUST_28.WAV", +}; +#endif \ No newline at end of file diff --git a/src/miami/audio/sampman_dc.cpp b/src/miami/audio/sampman_dc.cpp new file mode 100644 index 00000000..0f0cad55 --- /dev/null +++ b/src/miami/audio/sampman_dc.cpp @@ -0,0 +1,1479 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "crossplatform.h" + +#if !defined(AUDIO_OAL) && !defined(AUDIO_MSS) +#define verbosef(...) // dbglog(DBG_CRITICAL, __VA_ARGS__) +#define debugf(...) // dbglog(DBG_CRITICAL, __VA_ARGS__) + +#include "sampman.h" +#include "sampman_dc_streams.h" +#include "AudioManager.h" +#include "MusicManager.h" +#include "Frontend.h" +#include "Timer.h" + +#include +#include +#include + +#include +#include + +#include "CdStream.h" + +#define STREAM_STAGING_BUFFER_SIZE 16384 +#define STREAM_STAGING_READ_SIZE_STEREO 16384 +#define STREAM_STAGING_READ_SIZE_MONO (STREAM_STAGING_READ_SIZE_STEREO / 2) +#define STREAM_CHANNEL_BUFFER_SIZE (STREAM_STAGING_READ_SIZE_MONO * 2) // lower and upper halves +#define STREAM_CHANNEL_SAMPLE_COUNT (STREAM_CHANNEL_BUFFER_SIZE * 2) // 4 bit adpcm + +#define SPU_RAM_UNCACHED_BASE_U8 ((uint8_t *)SPU_RAM_UNCACHED_BASE) +// ************************************************************************************************ +// Begin AICA Driver stuff + +#define AICA_MEM_CHANNELS 0x020000 /* 64 * 16*4 = 4K */ + +/* Quick access to the AICA channels */ +#define AICA_CHANNEL(x) (AICA_MEM_CHANNELS + (x) * sizeof(aica_channel_t)) + +int aica_play_chn(int chn, int size, uint32_t aica_buffer, int fmt, int vol, int pan, int loop, int freq) { + // assert(size <= 65534); + // We gotta fix this at some point + if (size >= 65535) { + debugf("aica_play_chn: size too large for %p, %d, truncating to 65534\n", (void*)aica_buffer, size); + size = 65534; + } + + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_START; + chan->base = aica_buffer; + chan->type = fmt; + chan->length = size; + chan->loop = loop; + chan->loopstart = 0; + chan->loopend = size; + chan->freq = freq; + chan->vol = vol; + chan->pan = pan; + snd_sh4_to_aica(tmp, cmd->size); + return chn; +} + +void aica_stop_chn(int chn) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_STOP; + snd_sh4_to_aica(tmp, cmd->size); +} + +void aica_volpan_chn(int chn, int vol, int pan) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_PAN | AICA_CH_UPDATE_SET_VOL; + chan->vol = vol; + chan->pan = pan; + snd_sh4_to_aica(tmp, cmd->size); +} + + +void aica_snd_sfx_volume(int chn, int vol) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_VOL; + chan->vol = vol; + snd_sh4_to_aica(tmp, cmd->size); +} + +void aica_snd_sfx_pan(int chn, int pan) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_PAN; + chan->pan = pan; + snd_sh4_to_aica(tmp, cmd->size); +} + +void aica_snd_sfx_freq(int chn, int freq) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_FREQ; + chan->freq = freq; + snd_sh4_to_aica(tmp, cmd->size); +} + + +void aica_snd_sfx_freq_vol(int chn, int freq, int vol) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = chn; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_FREQ | AICA_CH_UPDATE_SET_VOL; + chan->freq = freq; + chan->vol = vol; + snd_sh4_to_aica(tmp, cmd->size); +} + +// End of aica Driver stuff +// ************************************************************************************************ + +cSampleManager SampleManager; +bool8 _bSampmanInitialised = FALSE; + +uint32 BankStartOffset[MAX_SFX_BANKS]; +char SampleBankDescFilename[] = "sfx/sfx_all.dsc"; +char SampleBankDataFilename[] = "sfx/sfx_all.raw"; + +struct sfx_bank { + uintptr_t base; + std::vector effects; + std::vector effects_loop; +}; + +std::map sfx_banks; + +int nPedSlotSfx[MAX_PEDSFX]; +uint32 nPedSlotSfxReqId[MAX_PEDSFX]; +uintptr_t nPedSlotSfxAddr[MAX_PEDSFX]; +uint8_t nCurrentPedSlot; +file_t fdPedSfx; +volatile uint32 nPedSfxReqReadId = 1; +volatile uint32 nPedSfxReqNextId = 1; + +#ifdef FIX_BUGS +uint32 gPlayerTalkSfx = UINT32_MAX; +uintptr_t gPlayerTalkData = 0; +uint32 gPlayerTalkReqId = 0; +#endif + +static int32 DCStreamedLength[TOTAL_STREAMED_SOUNDS]; + +struct WavHeader { + // RIFF Header + char riff[4]; // RIFF Header Magic header + uint32_t chunkSize; // RIFF Chunk Size + char wave[4]; // WAVE Header + // "fmt" sub-chunk + char fmt[4]; // FMT header + uint32_t subchunk1Size; // Size of the fmt chunk + uint16_t audioFormat; // Audio format 1=PCM, other values indicate compression + uint16_t numOfChan; // Number of channels 1=Mono, 2=Stereo + uint32_t samplesPerSec; // Sampling Frequency in Hz + uint32_t bytesPerSec; // bytes per second + uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo + uint16_t bitsPerSample; // Number of bits per sample + // "data" sub-chunk + char data[4]; // "data" string + uint32_t dataSize; // Size of the data section +}; + +static inline uint8_t linearlize_volume(uint8_t vol) { + // uint8_t rv = powf(10.0f, (vol - MAX_VOLUME) / 42.0f) * 255; + uint8_t rv = vol * 255 / MAX_VOLUME; + // verbosef("linearlize_volume(%d) = %d\n", vol, rv); + return rv; +} +cSampleManager::cSampleManager(void) +{ + ; +} + +cSampleManager::~cSampleManager(void) +{ + +} + +bool8 +cSampleManager::IsMP3RadioChannelAvailable(void) +{ + return FALSE; +} + + +void cSampleManager::ReleaseDigitalHandle(void) +{ +} + +void cSampleManager::ReacquireDigitalHandle(void) +{ +} + +struct alignas(32) stream_info { + uint8_t buffer[STREAM_STAGING_BUFFER_SIZE + 128]; + std::mutex mtx; + file_t fd; + uint32_t aica_buffers[2]; // left, right + int mapped_ch[2]; // left, right + int rate; + int total_samples; + int played_samples; + int file_offset; + int vol; + uint8_t nPan; + uint8_t pan[2]; + + bool stereo; + bool playing; + bool next_is_upper_half; + bool first_refill; + bool paused; +}; + +stream_info streams[MAX_STREAMS]; + + +std::mutex channel_mtx; +static struct { + uintptr_t ptr; + uintptr_t ptr_loop; // or 0 if no loop + int nSfx; + int freq; + float distMin; + float distMax; + float fX; + float fY; + float fZ; + uint8_t attenuationVol; + uint8_t emittingVol; + uint8_t vol; + uint8_t pan; + char ch; + char mapped_ch; + bool loop; + bool in_hnd_loop; + int8_t nBank; +} channels[MAXCHANNELS+MAX2DCHANNELS]; + + +// static void * fill_audio_cb (snd_stream_hnd_t hnd, int smp_req, int *smp_recv) { +// auto si = (stream_info*)snd_stream_get_userdata(hnd); + +// assert(si->hnd == hnd); + +// verbosef("fill_audio_cb: %p %d\n", si, smp_req); + +// size_t read_bytes = 0; + +// { +// std::lock_guard lk(si->mtx); +// if (si->playing) { +// read_bytes = fs_read(si->fd, si->buffer, smp_req); +// if (read_bytes <= 0) { +// verbosef("fill_audio_cb: %p stream end\n", si); +// *smp_recv = 0; +// return NULL; +// } +// } +// } + +// *smp_recv = read_bytes; + +// if(si->seek_buf > 0) { +// size_t seek_bytes = si->seek_buf; +// si->seek_buf = 0; + +// while(seek_bytes & 3) { +// ++seek_bytes; +// } +// if(seek_bytes > 128) { +// seek_bytes = 128; +// } +// memset(si->buffer + sizeof(si->buffer) - seek_bytes, 0, seek_bytes); +// return si->buffer + seek_bytes; +// } + +// return si->buffer; +// } + +std::thread snd_thread; +bool8 +cSampleManager::Initialise(void) +{ + auto init = snd_init(); + assert(init >= 0); + // snd_stream_init_ex(2, STREAM_BUFFER_SIZE); + + for (int i = 0; i< MAX_STREAMS; i++) { + streams[i].mapped_ch[0] = snd_sfx_chn_alloc(); + streams[i].mapped_ch[1] = snd_sfx_chn_alloc(); + streams[i].aica_buffers[0] = snd_mem_malloc(STREAM_CHANNEL_BUFFER_SIZE); + streams[i].aica_buffers[1] = snd_mem_malloc(STREAM_CHANNEL_BUFFER_SIZE); + debugf("Stream %d mapped to: %d, %d\n", i, streams[i].mapped_ch[0], streams[i].mapped_ch[1]); + debugf("Stream %d buffers: %p, %p\n", i, (void*)streams[i].aica_buffers[0], (void*)streams[i].aica_buffers[1]); + assert(streams[i].mapped_ch[0] != -1); + assert(streams[i].mapped_ch[1] != -1); + streams[i].fd = -1; + + streams[i].vol = 255; + streams[i].nPan = 63; + streams[i].pan[0] = 128; + streams[i].pan[1] = 128; + } + + for (int i = 0; i < (MAXCHANNELS+MAX2DCHANNELS); i++) { + channels[i].mapped_ch = snd_sfx_chn_alloc(); + debugf("Channel %d mapped to %d\n", i, channels[i].mapped_ch); + assert(channels[i].mapped_ch != -1); + } + + if (!InitialiseSampleBanks()) + return FALSE; + + snd_thread = std::thread([]() { + for(;;) { + { + std::lock_guard lk(channel_mtx); + for (int i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++) { + if (channels[i].ch != -1) { + assert(channels[i].nSfx != -1); + + uint16_t channel_pos = (g2_read_32(SPU_RAM_UNCACHED_BASE + AICA_CHANNEL(channels[i].ch) + offsetof(aica_channel_t, pos)) & 0xffff); + // verbosef("Channel %d pos: %d\n", i, channel_pos); + if (!channels[i].loop) { + auto channel_looped = g2_read_32(SPU_RAM_UNCACHED_BASE + AICA_CHANNEL(channels[i].ch) + offsetof(aica_channel_t, looped)); + // the looped flag is set even for one shots and is a reliable way to know if the channel has finished playing + if (channel_looped) { + debugf("Auto stopping channel: %d -> %d\n", i, channels[i].ch); + channels[i].ch = -1; + } + } else if (channels[i].ptr_loop && !channels[i].in_hnd_loop) { + if (channel_pos >= SampleManager.m_aSamples[channels[i].nSfx].nLoopStartSample) { + channels[i].in_hnd_loop = true; + debugf("Starting loop section: for sfx_%d_loop.wav\n", channels[i].nSfx); + snd_sfx_stop(channels[i].ch); + channels[i].ch = aica_play_chn(channels[i].mapped_ch, SampleManager.m_aSamples[channels[i].nSfx].nLoopByteSize * 2, channels[i].ptr_loop, 2 /* ADPCM */, channels[i].vol, channels[i].pan, 1, channels[i].freq); + } + } + } + } + } + + for (int i = 0; i< MAX_STREAMS; i++) { + size_t do_read = 0; + { + std::lock_guard lk(streams[i].mtx); + if (streams[i].playing) { + // get channel pos + uint32_t channel_pos = g2_read_32(SPU_RAM_UNCACHED_BASE + AICA_CHANNEL(streams[i].mapped_ch[0]) + offsetof(aica_channel_t, pos)) & 0xffff; + uint32_t logical_pos = channel_pos; + if (logical_pos > STREAM_CHANNEL_SAMPLE_COUNT/2) { + logical_pos -= STREAM_CHANNEL_SAMPLE_COUNT/2; + } + verbosef("Stream %d pos: %d, log: %d, rem: %d\n", i, channel_pos, logical_pos, streams[i].played_samples); + + bool can_refill = (streams[i].played_samples + STREAM_CHANNEL_SAMPLE_COUNT/2) < streams[i].total_samples; + bool can_fetch = (streams[i].played_samples + STREAM_CHANNEL_SAMPLE_COUNT/2 + STREAM_CHANNEL_SAMPLE_COUNT/2 + STREAM_CHANNEL_SAMPLE_COUNT/2) < streams[i].total_samples; + // copy over data if needed from staging + if (channel_pos >= STREAM_CHANNEL_SAMPLE_COUNT/2 && !streams[i].next_is_upper_half) { + streams[i].next_is_upper_half = true; + if (can_refill) { // could we need a refill? + verbosef("Filling channel %d with lower half\n", i); + // fill lower half + spu_memload(streams[i].aica_buffers[0], streams[i].buffer, STREAM_CHANNEL_BUFFER_SIZE/2); + if (streams[i].stereo) { + spu_memload(streams[i].aica_buffers[1], streams[i].buffer + STREAM_STAGING_READ_SIZE_MONO, STREAM_CHANNEL_BUFFER_SIZE/2); + } + // queue next read to staging if any + if (can_fetch) { + do_read = streams[i].stereo ? STREAM_STAGING_READ_SIZE_STEREO : STREAM_STAGING_READ_SIZE_MONO; + } + } + assert(streams[i].first_refill == false); + streams[i].played_samples += STREAM_CHANNEL_SAMPLE_COUNT/2; + } else if (channel_pos < STREAM_CHANNEL_SAMPLE_COUNT/2 && streams[i].next_is_upper_half) { + streams[i].next_is_upper_half = false; + if (can_refill) { // could we need a refill? + verbosef("Filling channel %d with upper half\n", i); + // fill upper half + spu_memload(streams[i].aica_buffers[0] + STREAM_CHANNEL_BUFFER_SIZE/2, streams[i].buffer, STREAM_CHANNEL_BUFFER_SIZE/2); + if (streams[i].stereo) { + spu_memload(streams[i].aica_buffers[1] + STREAM_CHANNEL_BUFFER_SIZE/2, streams[i].buffer + STREAM_STAGING_READ_SIZE_MONO, STREAM_CHANNEL_BUFFER_SIZE/2); + } + // queue next read to staging, if any + if (can_fetch) { + do_read = streams[i].stereo ? STREAM_STAGING_READ_SIZE_STEREO : STREAM_STAGING_READ_SIZE_MONO; + } + } + if (streams[i].first_refill) { + streams[i].first_refill = false; + } else { + streams[i].played_samples += STREAM_CHANNEL_SAMPLE_COUNT/2; + } + } + // if end of file, stop + if ((streams[i].played_samples + logical_pos) > streams[i].total_samples) { + // stop channel + debugf("Auto stopping stream: %d -> {%d, %d}, %d total\n", i, streams[i].mapped_ch[0], streams[i].mapped_ch[1], streams[i].total_samples); + aica_stop_chn(streams[i].mapped_ch[0]); + aica_stop_chn(streams[i].mapped_ch[1]); + streams[i].playing = false; + } + } + + if (do_read) { + debugf("Queueing stream read: %d, file: %d, buffer: %p, size: %d, tell: %d\n", i, streams[i].fd, streams[i].buffer, do_read, fs_tell(streams[i].fd)); + CdStreamQueueAudioRead(streams[i].fd, streams[i].buffer, do_read, streams[i].file_offset); + streams[i].file_offset += do_read; + } + } + } + thd_sleep(50); + } + }); + + nPedSfxReqNextId = 1; + nPedSfxReqReadId = 1; + for ( int32 i = 0; i < MAX_PEDSFX; i++ ) + { + nPedSlotSfx[i] = -1; + nPedSlotSfxReqId[i] = 0; + nPedSlotSfxAddr[i] = snd_mem_malloc(PED_BLOCKSIZE_ADPCM); + debugf("PedSlot %d buffer: %p\n", i, (void*)nPedSlotSfxAddr[i]); + } + + nCurrentPedSlot = 0; + + fdPedSfx = fs_open(SampleBankDataFilename, O_RDONLY); + + assert(fdPedSfx >= 0); + + file_t fd = fs_open("stream/hdr.bin", O_RDONLY); + assert(fd >= 0); + static_assert(sizeof(DCStreamedLength) == TOTAL_STREAMED_SOUNDS*sizeof(int32)); + assert(fs_read(fd, DCStreamedLength, sizeof(DCStreamedLength)) == sizeof(DCStreamedLength)); + fs_close(fd); + + _bSampmanInitialised = true; + return TRUE; +} + +void +cSampleManager::Terminate(void) +{ + fs_close(fdPedSfx); +} + +bool8 cSampleManager::CheckForAnAudioFileOnCD(void) +{ + return TRUE; +} + +char cSampleManager::GetCDAudioDriveLetter(void) +{ + return '\0'; +} + +void +cSampleManager::UpdateEffectsVolume(void) +{ + if(_bSampmanInitialised) { + std::lock_guard lk(channel_mtx); + for (int i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++) { + if (channels[i].ch != -1) { + UpdateChannelVolume(i); + } + } + } +} + + +void +cSampleManager::SetEffectsMasterVolume(uint8 nVolume) +{ + m_nEffectsVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicMasterVolume(uint8 nVolume) +{ + m_nMusicVolume = nVolume; +} + +void +cSampleManager::SetMP3BoostVolume(uint8 nVolume) +{ + // m_nMP3BoostVolume = nVolume; +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ + m_nEffectsFadeVolume = nVolume; + UpdateEffectsVolume(); +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ + m_nMusicFadeVolume = nVolume; +} + +void +cSampleManager::SetMonoMode(uint8 nMode) +{ +} + +bool8 +cSampleManager::LoadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + ASSERT( nBank != SFX_BANK_PED_COMMENTS ); + verbosef("LoadSampleBank(%d)\n", nBank); + + auto it = sfx_banks.find(nBank); + + if (it == sfx_banks.end()) { + debugf("Loading bank %d\n", nBank); + sfx_bank bank; + int firstSfx = BankStartOffset[nBank]; + int nextBankSfx = BankStartOffset[nBank+1]; + assert(firstSfx= 0); + // this is very wasteful and temporary + void* stagingBuffer = memalign(32, 8 * 2048); + assert(stagingBuffer != 0); + + // Ideally, we'd suspend the CdStream thingy here or read via that instead + uintptr_t loadOffset = bank.base; + fs_seek(fd, fileStart, SEEK_SET); + + while (fileSize > 0) { + size_t readSize = fileSize > 8 * 2048 ? 8 * 2048 : fileSize; + int rs = fs_read(fd, stagingBuffer, readSize); + debugf("Read %d bytes, expected %d\n", rs, readSize); + assert(rs == readSize); + spu_memload(loadOffset, stagingBuffer, readSize); + loadOffset += readSize; + fileSize -= readSize; + debugf("Loaded %d bytes, %d remaining\n", readSize, fileSize); + } + fs_close(fd); + free(stagingBuffer); + + + for (int nSfx = BankStartOffset[nBank]; nSfx < BankStartOffset[nBank+1]; nSfx++) { + bank.effects.push_back(m_aSamples[nSfx].nFileOffset - fileStart + bank.base); + if (m_aSamples[nSfx].nLoopStartSample) { + bank.effects_loop.push_back(m_aSamples[nSfx].nLoopFileOffset - fileStart + bank.base); + } else { + bank.effects_loop.push_back(0); + } + debugf("Loaded sfx %d at %p, loop at %p\n", nSfx, (void*)bank.effects.back(), (void*)bank.effects_loop.back()); + } + + debugf("Loaded bank %d\n", nBank); + sfx_banks[nBank] = bank; + } + return TRUE; +} + +void +cSampleManager::UnloadUnusedSampleBank() +{ + assert(--sfx_banks.end() != sfx_banks.end()); + auto bank = (--sfx_banks.end())->first; + assert(bank != SFX_BANK_0); // can't unload bank 0 for OOM + UnloadSampleBank(bank); +} + +void +cSampleManager::UnloadSampleBank(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + + verbosef("UnloadSampleBank(%d)\n", nBank); + + auto it = sfx_banks.find(nBank); + if (it != sfx_banks.end()) { + debugf("Unloading bank %d\n", nBank); + { + std::lock_guard lk(channel_mtx); + for (int i = 0; i < MAXCHANNELS+MAX2DCHANNELS; i++) { + if (channels[i].nBank == nBank) { + dbglog(DBG_CRITICAL, "Warning: Channel %d is in unloaded nBank %d\n", i, nBank); + if (channels[i].ch != -1) { + dbglog(DBG_CRITICAL, "Warning: Channel %d was activelly playing from bank %d\n", i, nBank); + snd_sfx_stop(channels[i].ch); + } + channels[i].ch = -1; + channels[i].nSfx = -1; + channels[i].nBank = -1; + channels[i].ptr = 0; + channels[i].ptr_loop = 0; + } + } + } + snd_mem_free(it->second.base); + sfx_banks.erase(it); + } +} + +int8 +cSampleManager::IsSampleBankLoaded(uint8 nBank) +{ + ASSERT( nBank < MAX_SFX_BANKS ); + + return LOADING_STATUS_LOADED; +} + +#ifdef FIX_BUGS +uint8 +cSampleManager::IsMissionAudioLoaded(uint8 nSlot, uint32 nSample) +{ + ASSERT(nSlot == MISSION_AUDIO_PLAYER_COMMENT); // only MISSION_AUDIO_PLAYER_COMMENT is supported on PC + + if (nSample == gPlayerTalkSfx) + return gPlayerTalkReqId <= nPedSfxReqReadId ? LOADING_STATUS_LOADED : LOADING_STATUS_LOADING; + else + return LOADING_STATUS_NOT_LOADED; +} + +bool8 +cSampleManager::LoadMissionAudio(uint8 nSlot, uint32 nSample) +{ + ASSERT(nSlot == MISSION_AUDIO_PLAYER_COMMENT); // only MISSION_AUDIO_PLAYER_COMMENT is supported on PC + ASSERT(nSample < TOTAL_AUDIO_SAMPLES); + + debugf("Loading mission audio comment %d, offset: %d, size: %d\n", nSample, m_aSamples[nSample].nFileOffset, m_aSamples[nSample].nByteSize); + CdStreamQueueAudioRead(nSample, (void*)gPlayerTalkData, m_aSamples[nSample].nByteSize, m_aSamples[nSample].nFileOffset, [](AudioReadCmd* cmd) { + fs_seek(fdPedSfx, cmd->seek, SEEK_SET); + + // TODO: When we can dma directly to AICA, we can use this instead + // fs_read(fdPedSfx, SPU_BASE_U8 + (uintptr_t)cmd->dest, cmd->size); + + void* stagingBuffer = memalign(32, cmd->size); + assert(stagingBuffer != 0); + debugf("Allocated %d bytes at %p\n", cmd->size, stagingBuffer); + int rs = fs_read(fdPedSfx, stagingBuffer, cmd->size); + debugf("Read %d bytes, expected %d\n", rs, cmd->size); + assert(rs == cmd->size); + + spu_memload((uintptr_t)cmd->dest, stagingBuffer, cmd->size); + free(stagingBuffer); + nPedSfxReqReadId = nPedSfxReqReadId + 1; + }); + + gPlayerTalkReqId = ++nPedSfxReqNextId; + + gPlayerTalkSfx = nSample; + + return TRUE; +} +#endif + +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 nPedSlotSfxReqId[slot] <= nPedSfxReqReadId ? LOADING_STATUS_LOADED : LOADING_STATUS_LOADING; + } + + 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) +{ + verbosef("LoadPedComment %ld\n", nComment); + + ASSERT( nComment < TOTAL_AUDIO_SAMPLES ); + ASSERT (nComment >= SAMPLEBANK_PED_START && nComment < SAMPLEBANK_PED_MAX); + + 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; + } + } + } + + assert(m_aSamples[nComment].nByteSize < PED_BLOCKSIZE_ADPCM); + + debugf("Loading ped comment %d, offset: %d, size: %d\n", nComment, m_aSamples[nComment].nFileOffset, m_aSamples[nComment].nByteSize); + CdStreamQueueAudioRead(nComment, (void*)nPedSlotSfxAddr[nCurrentPedSlot], m_aSamples[nComment].nByteSize, m_aSamples[nComment].nFileOffset, [](AudioReadCmd* cmd) { + fs_seek(fdPedSfx, cmd->seek, SEEK_SET); + + + // TODO: When we can dma directly to AICA, we can use this instead + // fs_read(fdPedSfx, SPU_BASE_U8 + (uintptr_t)cmd->dest, cmd->size); + + void* stagingBuffer = memalign(32, cmd->size); + assert(stagingBuffer != 0); + debugf("Allocated %d bytes at %p\n", cmd->size, stagingBuffer); + int rs = fs_read(fdPedSfx, stagingBuffer, cmd->size); + debugf("Read %d bytes, expected %d\n", rs, cmd->size); + assert(rs == cmd->size); + + spu_memload((uintptr_t)cmd->dest, stagingBuffer, cmd->size); + free(stagingBuffer); + nPedSfxReqReadId = nPedSfxReqReadId + 1; + }); + + nPedSlotSfxReqId[nCurrentPedSlot] = ++nPedSfxReqNextId; + + nPedSlotSfx[nCurrentPedSlot] = nComment; + + if ( ++nCurrentPedSlot >= MAX_PEDSFX ) + nCurrentPedSlot = 0; + + return TRUE; +} + +int32 +cSampleManager::GetBankContainingSound(uint32 nSfx) +{ + assert(nSfx < TOTAL_AUDIO_SAMPLES); + + for (int i = MAX_SFX_BANKS-1; i >= 0; i--) + { + if ( nSfx >= BankStartOffset[i] ) + return i; + } + + return INVALID_SFX_BANK; +} + +uint32 +cSampleManager::GetSampleBaseFrequency(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nFrequency; +} + +// now in samples +uint32 +cSampleManager::GetSampleLoopStartOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nLoopStartSample; +} + +// always loop to end +int32 +cSampleManager::GetSampleLoopEndOffset(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return -1; +} + +uint32 +cSampleManager::GetSampleLength(uint32 nSample) +{ + ASSERT( nSample < TOTAL_AUDIO_SAMPLES ); + return m_aSamples[nSample].nByteSize * 2; // adcpcm is 4 bit +} + +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 ); + // printf("InitialiseChannel(nChannel: %d, nSfx: %d, nBank: %d)\n", nChannel, nSfx, nBank); + StopChannel(nChannel); + nBank = GetBankContainingSound(nSfx); + if (nBank != SFX_BANK_PED_COMMENTS) { + // Load the bank here, without holding channel_mtx as it may call UnloadSampleBank that locks it + LoadSampleBank(nBank); + } + + std::lock_guard lk(channel_mtx); + + verbosef("InitialiseChannel %ld %ld %d\n", nChannel, nSfx, nBank); + + if (nBank == SFX_BANK_PED_COMMENTS) { + +#ifdef FIX_BUGS + if ( nSfx >= PLAYER_COMMENTS_START && nSfx <= PLAYER_COMMENTS_END ) + { + if ( IsMissionAudioLoaded(MISSION_AUDIO_PLAYER_COMMENT, nSfx) != LOADING_STATUS_LOADED ) + return FALSE; + + debugf("Channel %d is using mission audio comment %d, buffer %p, samples: %d\n", nChannel, nSfx, channels[nChannel].ptr, m_aSamples[nSfx].nByteSize*2); + + channels[nChannel].ptr = gPlayerTalkData; + } else +#endif + { + 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] ) + { + channels[nChannel].ptr = nPedSlotSfxAddr[slot]; + break; + } + } + + if (i == _TODOCONST(3)) + return FALSE; + debugf("Channel %d is using ped comment %d, buffer %p, samples: %d\n", nChannel, nSfx, channels[nChannel].ptr, m_aSamples[nSfx].nByteSize*2); + } + } else { + channels[nChannel].ptr = sfx_banks[nBank].effects[nSfx - BankStartOffset[nBank]]; + } + + channels[nChannel].nSfx = nSfx; + channels[nChannel].nBank = nBank; + + if (m_aSamples[nSfx].nLoopStartSample != 0) { + channels[nChannel].ptr_loop = sfx_banks[nBank].effects_loop[nSfx - BankStartOffset[nBank]]; + } else { + channels[nChannel].ptr_loop = 0; + } + + channels[nChannel].freq = m_aSamples[nSfx].nFrequency; + channels[nChannel].attenuationVol = 255; + // channels[nChannel].vol = 255; + // channels[nChannel].pan = 128; + channels[nChannel].loop = 0; + + return TRUE; +} + +void cSampleManager::UpdateChannelVolume(uint32 nChannel) { + + auto newVol = channels[nChannel].emittingVol * channels[nChannel].attenuationVol / 255; + newVol = m_nEffectsFadeVolume * newVol * m_nEffectsVolume >> 14; + // newVol = 255; + // printf("updateVol(nChannel: %d) vol: %d, newVol: %d\n", nChannel, channels[nChannel].vol, newVol); + if (channels[nChannel].vol != newVol) { + channels[nChannel].vol = newVol; + // printf("updateVol(nChannel: %d) vol: %d\n", nChannel, channels[nChannel].vol); + if (channels[nChannel].ch != -1) { + aica_snd_sfx_volume(channels[nChannel].ch, channels[nChannel].vol); + } + } +} + +void +cSampleManager::SetChannelVolume(uint32 nChannel, uint32 nVolume) +{ + // ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + if (nVolume > MAX_VOLUME) + nVolume = MAX_VOLUME; + std::lock_guard lk(channel_mtx); + channels[nChannel].emittingVol = linearlize_volume(nVolume);// nVolume * 255 / MAX_VOLUME; + channels[nChannel].attenuationVol = 255; + + UpdateChannelVolume(nChannel); + verbosef("SetChannelVolume(nChannel: %d) vol: %d\n", nChannel, nVolume); +} + +void +cSampleManager::SetChannelPan(uint32 nChannel, uint32 gta_pan) +{ + // ASSERT( nChannel >= MAXCHANNELS ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + // DC uses logarithmic pan + auto linear_gain = 1-abs(gta_pan-64.0f)/64.0f; + + auto db = 20.0f * std::log10(linear_gain); + if (db < -45) { + db = -45; + } + + db = fabs(db); + auto aica_paned_att = db / 0.355; + + int aica_pan; + + if (gta_pan < 64) { + aica_pan = 128 - aica_paned_att; + } else { + aica_pan = 128 + aica_paned_att; + } + + // nPan = nPan * 2; // 64 is center, for dc it is 128 + if (channels[nChannel].pan != aica_pan) { + channels[nChannel].pan = aica_pan; + if (channels[nChannel].ch != -1) { + aica_snd_sfx_pan(channels[nChannel].ch, channels[nChannel].pan); + } + } + // printf("SetChannelPan(nChannel: %d) pan: %d\n", nChannel, nPan); +} + +void +cSampleManager::SetChannelFrequency(uint32 nChannel, uint32 nFreq) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + // printf("SetChannelFrequency(nChannel: %d) freq: %d\n", nChannel, nFreq); + std::lock_guard lk(channel_mtx); + if (channels[nChannel].freq != nFreq) { + channels[nChannel].freq = nFreq; + if (channels[nChannel].ch != -1) { + aica_snd_sfx_freq(channels[nChannel].ch, channels[nChannel].freq); + } + } +} + +void +cSampleManager::SetChannelLoopPoints(uint32 nChannel, uint32 nLoopStart, int32 nLoopEnd) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + assert(channels[nChannel].nSfx != -1); + assert(m_aSamples[channels[nChannel].nSfx].nLoopStartSample == nLoopStart && -1 == nLoopEnd); +} + +void +cSampleManager::SetChannelLoopCount(uint32 nChannel, uint32 nLoopCount) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + assert(nLoopCount == 0 || nLoopCount == 1); + channels[nChannel].loop = nLoopCount == 0; + // printf("SetChannelLoopCount(nChannel: %d) loop: %d\n", nChannel, nLoopCount); +} + +bool8 +cSampleManager::GetChannelUsedFlag(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + return channels[nChannel].ch != -1; +} + +void +cSampleManager::StartChannel(uint32 nChannel) +{ + StopChannel(nChannel); + std::lock_guard lk(channel_mtx); + // printf("StartChannel(nChannel: %d) vol: %d, pan: %d, freq: %d, loop: %d, sfx: %d\n", nChannel, channels[nChannel].vol, channels[nChannel].pan, channels[nChannel].freq, channels[nChannel].loop, channels[nChannel].nSfx); + channels[nChannel].in_hnd_loop = false; + assert(channels[nChannel].nSfx != -1); + channels[nChannel].ch = aica_play_chn( + channels[nChannel].mapped_ch, + m_aSamples[channels[nChannel].nSfx].nByteSize * 2, + channels[nChannel].ptr, + 2 /* ADPCM */, + channels[nChannel].vol, + channels[nChannel].pan, + channels[nChannel].loop, + channels[nChannel].freq + ); + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); +} + +void +cSampleManager::StopChannel(uint32 nChannel) +{ + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + // printf("StopChannel(nChannel: %d)\n", nChannel); + if (channels[nChannel].ch != -1) { + snd_sfx_stop(channels[nChannel].ch); + channels[nChannel].ch = -1; + } +} + +void +cSampleManager::PreloadStreamedFile(uint32 nFile, uint8 nStream, uint32_t seek_bytes_aligned) +{ + ASSERT( nStream < MAX_STREAMS ); + ASSERT( nFile < TOTAL_STREAMED_SOUNDS ); + + file_t f = fs_open(DCStreamedNameTable[nFile], O_RDONLY); + debugf("PreloadStreamedFile(%p, %d, %d) is %s\n", f, nFile, nStream, DCStreamedNameTable[nFile]); + assert(f >= 0 ); + WavHeader hdr; + assert(fs_read(f, &hdr, sizeof(hdr)) == sizeof(hdr)); + + { + std::lock_guard lk(streams[nStream].mtx); + + // Stop if playing + // Keep in sync with StopStreamedFile + + if (streams[nStream].playing) { + streams[nStream].playing = false; + aica_stop_chn(streams[nStream].mapped_ch[0]); + aica_stop_chn(streams[nStream].mapped_ch[1]); + } + + if (streams[nStream].fd >= 0) { + CdStreamDiscardAudioRead(streams[nStream].fd); + fs_close(streams[nStream].fd); + } + streams[nStream].fd = -1; + + streams[nStream].rate = hdr.samplesPerSec; + streams[nStream].stereo = hdr.numOfChan == 2; + streams[nStream].fd = f; + streams[nStream].playing = false; + streams[nStream].total_samples = hdr.dataSize * 2 / hdr.numOfChan; + streams[nStream].played_samples = 0; + streams[nStream].next_is_upper_half = true; + streams[nStream].first_refill = true; + + debugf("PreloadStreamedFile: %s: stream: %d, freq: %d, chans: %d, byte size: %d, played samples: %d\n", DCStreamedNameTable[nFile], nStream, hdr.samplesPerSec, hdr.numOfChan, hdr.dataSize, streams[nStream].played_samples); + + + // How to avoid the lock? + if (seek_bytes_aligned) { + streams[nStream].played_samples = seek_bytes_aligned * (streams[nStream].stereo ? 1 : 2); + debugf("Seeking aligned to: %d, played_samples: %d\n", seek_bytes_aligned, streams[nStream].played_samples); + fs_seek(streams[nStream].fd, 2048 + seek_bytes_aligned, SEEK_SET); + } else { + fs_seek(f, 2048, SEEK_SET); + } + + #if 0 + // Read directly in the future + fs_read(f, SPU_RAM_UNCACHED_BASE_U8 + streams[nStream].aica_buffers[0], STREAM_STAGING_READ_SIZE_MONO); + if (streams[nStream].stereo) { + fs_read(f, SPU_RAM_UNCACHED_BASE_U8 + streams[nStream].aica_buffers[1], STREAM_STAGING_READ_SIZE_MONO); + } + #else + // Stage to memory + fs_read(f, streams[nStream].buffer, streams[nStream].stereo ? STREAM_STAGING_READ_SIZE_STEREO : STREAM_STAGING_READ_SIZE_MONO); + spu_memload(streams[nStream].aica_buffers[0], streams[nStream].buffer, STREAM_CHANNEL_BUFFER_SIZE/2); + if (streams[nStream].stereo) { + spu_memload(streams[nStream].aica_buffers[1], streams[nStream].buffer + STREAM_STAGING_READ_SIZE_MONO, STREAM_CHANNEL_BUFFER_SIZE/2); + } + #endif + + if (streams[nStream].total_samples > STREAM_CHANNEL_SAMPLE_COUNT/2) { + // If more than one buffer, prefetch the next one + fs_read(f, streams[nStream].buffer, streams[nStream].stereo ? STREAM_STAGING_READ_SIZE_STEREO : STREAM_STAGING_READ_SIZE_MONO); + } + + streams[nStream].file_offset = fs_tell(f); + } + + verbosef("PreloadStreamedFile: %p %d - %s, %d, %d, \n", f, nFile, DCStreamedNameTable[nFile], streams[nStream].rate, streams[nStream].stereo); +} + +// we can't really pause a stream, so we just make it go very slow with zero volume +void +cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + { + std::lock_guard lk(streams[nStream].mtx); + + if (nPauseFlag != streams[nStream].paused) { + streams[nStream].paused = nPauseFlag; + if(nPauseFlag) { + // rate of 0 is 172 samples/second. not ideal but gets the job done. + aica_snd_sfx_freq_vol(streams[nStream].mapped_ch[0], 0, 0); + aica_snd_sfx_freq_vol(streams[nStream].mapped_ch[1], 0, 0); + } else { + aica_snd_sfx_freq_vol(streams[nStream].mapped_ch[0], streams[nStream].rate, streams[nStream].vol); + aica_snd_sfx_freq_vol(streams[nStream].mapped_ch[1], streams[nStream].rate, streams[nStream].vol); + } + } + } +} + +void +cSampleManager::StartPreloadedStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + debugf("StartPreloadedStreamedFile(%d)\n", nStream); + std::lock_guard lk(streams[nStream].mtx); + if (streams[nStream].playing) { + return; + } + debugf("StartPreloadedStreamedFile(%d) - actually starting stream\n", nStream); + // int aica_play_chn(int chn, int size, uint32_t aica_buffer, int fmt, int vol, int pan, int loop, int freq) + aica_play_chn( + streams[nStream].mapped_ch[0], + STREAM_CHANNEL_SAMPLE_COUNT, + streams[nStream].aica_buffers[0], + 3 /* adpcm long stream */, + streams[nStream].vol, + streams[nStream].pan[0], + 1, + streams[nStream].rate + ); + + aica_play_chn( + streams[nStream].mapped_ch[1], + STREAM_CHANNEL_SAMPLE_COUNT, + streams[nStream].aica_buffers[streams[nStream].stereo ? 1 : 0], + 3 /* adpcm long stream */, + streams[nStream].vol, + streams[nStream].pan[1], + 1, + streams[nStream].rate + ); + + streams[nStream].playing = true; + // printf("StartPreloadedStreamedFile(%d)\n", nStream); + // { + // std::lock_guard lk(streams[nStream].mtx); + // streams[nStream].playing = true; + // if (streams[nStream].active) { + // snd_stream_stop(streams[nStream].hnd); + // } + // streams[nStream].active = true; + // } + // snd_stream_start_adpcm(streams[nStream].hnd, streams[nStream].rate, streams[nStream].stereo); + // snd_stream_volume(streams[nStream].hnd, streams[nStream].vol); + verbosef("StartPreloadedStreamedFile(%d)\n", nStream); +} + +bool8 +cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + debugf("StartStreamedFile(%d, %d, %d)\n", nFile, nPos, nStream); + uint32_t seek_aligned = 0; + if (nPos) { + uint64_t seek_bytes = (uint64_t)nPos * streams[nStream].rate / (streams[nStream].stereo ? 1000: 2000); + assert(seek_bytes <= INT32_MAX); + seek_aligned = seek_bytes & ~(streams[nStream].stereo ? (STREAM_STAGING_READ_SIZE_STEREO-1) : (STREAM_STAGING_READ_SIZE_MONO-1)); + } + PreloadStreamedFile(nFile, nStream, seek_aligned); + StartPreloadedStreamedFile(nStream); + return TRUE; +} + +void +cSampleManager::StopStreamedFile(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + verbosef("StopStreamedFile(%d)\n", nStream); + std::lock_guard lk(streams[nStream].mtx); + + // Keep in sync with PreloadStreamedFile + streams[nStream].playing = false; + + aica_stop_chn(streams[nStream].mapped_ch[0]); + aica_stop_chn(streams[nStream].mapped_ch[1]); + + if (streams[nStream].fd >= 0) { + CdStreamDiscardAudioRead(streams[nStream].fd); + fs_close(streams[nStream].fd); + } + streams[nStream].fd = -1; +} + +int32 +cSampleManager::GetStreamedFilePosition(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + int32 rv; + + if (streams[nStream].fd >= 0) { + int64_t rv64 = (int64_t)streams[nStream].played_samples * 1000 / streams[nStream].rate; + assert(rv64 <= INT32_MAX); + rv = (int32)rv64; + } else { + rv = 0; + } + + debugf("GetStreamedFilePosition: %d %d\n", nStream, rv); + return rv; +} + +void +cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, uint8 nEffectFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + if (nVolume > MAX_VOLUME) + nVolume = MAX_VOLUME; + nVolume = linearlize_volume(nVolume); //nVolume * 255 / MAX_VOLUME; + nVolume = m_nMusicFadeVolume * nVolume * m_nMusicVolume >> 14; + if (streams[nStream].vol != nVolume || streams[nStream].nPan != nPan) { + streams[nStream].vol = nVolume; + streams[nStream].nPan = nPan; + + auto lpan = (int8_t)nPan - 63; + lpan = lpan < 0 ? 0 : lpan > 63 ? 63 : lpan; + + auto rpan = (int8_t)nPan + 63; + rpan = rpan < 64 ? 64 : rpan > 127 ? 127 : rpan; + + streams[nStream].pan[0] = lpan * 2; + streams[nStream].pan[1] = rpan * 2; + aica_volpan_chn(streams[nStream].mapped_ch[0], streams[nStream].vol, streams[nStream].pan[0]); + aica_volpan_chn(streams[nStream].mapped_ch[1], streams[nStream].vol, streams[nStream].pan[1]); + } + // verbosef("SetStreamedVolumeAndPan: %d %d %d %d\n", nStream, nVolume, nPan, nEffectFlag); +} + +int32 +cSampleManager::GetStreamedFileLength(uint8 nFile) +{ + ASSERT( nFile < TOTAL_STREAMED_SOUNDS ); + + auto rv = DCStreamedLength[nFile]; + + debugf("GetStreamedFileLength: %d %d\n", nFile, rv); + + return rv; +} + +bool8 +cSampleManager::IsStreamPlaying(uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + std::lock_guard lk(streams[nStream].mtx); + return streams[nStream].playing && !streams[nStream].paused; +} + +bool8 +cSampleManager::InitialiseSampleBanks(void) +{ + file_t fd = fs_open(SampleBankDescFilename, O_RDONLY); + if (fd < 0) + return FALSE; + + size_t rs = fs_read(fd, m_aSamples, sizeof(tSample)*TOTAL_AUDIO_SAMPLES); + + if (rs != sizeof(tSample)*TOTAL_AUDIO_SAMPLES) { + fs_close(fd); + return FALSE; + } + + fs_close(fd); + + for (int i = 0; i < (MAXCHANNELS+MAX2DCHANNELS); i++) { + channels[i].ptr = 0; + channels[i].ptr_loop = 0; + channels[i].ch = -1; + channels[i].nSfx = -1; + channels[i].nBank = -1; + } + +#ifdef FIX_BUGS + // Find biggest player comment + uint32 nMaxPlayerSize = 0; + for (uint32 i = PLAYER_COMMENTS_START; i <= PLAYER_COMMENTS_END; i++) + nMaxPlayerSize = Max(nMaxPlayerSize, m_aSamples[i].nByteSize); + + debugf("Max player comment size: %d\n", nMaxPlayerSize); + gPlayerTalkData = snd_mem_malloc(nMaxPlayerSize); + ASSERT(gPlayerTalkData != 0); + + gPlayerTalkReqId = 0; +#endif + + LoadSampleBank(SFX_BANK_0); + + return TRUE; +} + +#if defined(EXTERNAL_3D_SOUND) +void cSampleManager::SetSpeakerConfig(int32 nConfig) { + +} +uint32 cSampleManager::GetMaximumSupportedChannels(void) { + return MAXCHANNELS; +} + +uint32 cSampleManager::GetNum3DProvidersAvailable(void) { + return 32; +} +void cSampleManager::SetNum3DProvidersAvailable(uint32 num) { + +} + +char *cSampleManager::Get3DProviderName(uint8 id) { + return "dummy"; +} +void cSampleManager::Set3DProviderName(uint8 id, char *name) { + +} + +int8 cSampleManager::GetCurrent3DProviderIndex(void) { + return 0; +} +int8 cSampleManager::SetCurrent3DProvider(uint8 which) { + return 0; +} + +int8 +cSampleManager::AutoDetect3DProviders() +{ + return 0; +} + +void cSampleManager::SetChannelEmittingVolume(uint32 nChannel, uint32 nVolume) { + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + // printf("SetChannelEmittingVolume(nChannel: %d) vol: %d\n", nChannel, nVolume); + if (nVolume > MAX_VOLUME) + nVolume = MAX_VOLUME; + + if (MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE ) { + if (MusicManager.GetCurrentTrack() == STREAMED_SOUND_CUTSCENE_FINALE) + nVolume = 0; + else + nVolume >>= 2; + } + + std::lock_guard lk(channel_mtx); + channels[nChannel].emittingVol = linearlize_volume(nVolume); // nVolume * 255 / MAX_VOLUME; + UpdateChannelVolume(nChannel); +} + +float calculatePan(float x, float z) { + // Azimuth angle (in radians), range from -PI to +PI + float theta = Atan2(x, z); + + // Use the sine function for smooth panning + float pan = Sin(theta); // Use sine to map -PI to +PI to -1.0 to +1.0 + + return pan; +} + +float calculateAttenuation(float x, float y, float z, float mindist, float maxdist) { + // Distance calculation (Euclidean distance) + float distance = Sqrt(x * x + y * y + z * z); + + // If distance is less than or equal to mindist, return full volume (attenuation = 1) + if (distance <= mindist) { + return 1.0f; + } + + // If distance is greater than or equal to maxdist, return no sound (attenuation = 0) + if (distance >= maxdist) { + return 0.0f; + } + + // Calculate attenuation using inverse distance model and rolloff factor + // Attenuation = (referenceDistance / distance) ^ rolloffFactor + float attenuation = (mindist / distance); // rolloffFactor = 1.0f + + return attenuation; +} + + + +void cSampleManager::SetChannel3DPosition (uint32 nChannel, float fX, float fY, float fZ) { + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + // printf("SetChannel3DPosition(nChannel: %d) x: %f, y: %f, z: %f\n", nChannel, fX, fY, fZ); + { + std::lock_guard lk(channel_mtx); + channels[nChannel].fX = fX; + channels[nChannel].fY = fY; + channels[nChannel].fZ = fZ; + channels[nChannel].attenuationVol = calculateAttenuation(channels[nChannel].fX, channels[nChannel].fY, channels[nChannel].fZ, channels[nChannel].distMin, channels[nChannel].distMax) * 255; + UpdateChannelVolume(nChannel); + } + + SetChannelPan(nChannel, calculatePan(-fX, fZ) * 63 + 64); + +} +void cSampleManager::SetChannel3DDistances (uint32 nChannel, float fMax, float fMin) { + ASSERT( nChannel < MAXCHANNELS+MAX2DCHANNELS ); + std::lock_guard lk(channel_mtx); + // printf("SetChannel3DDistances(nChannel: %d) min: %f, max: %f\n", nChannel, fMin, fMax); + channels[nChannel].distMin = fMin; + channels[nChannel].distMax = fMax; + channels[nChannel].attenuationVol = calculateAttenuation(channels[nChannel].fX, channels[nChannel].fY, channels[nChannel].fZ, channels[nChannel].distMin, channels[nChannel].distMax) * 255; + UpdateChannelVolume(nChannel); +} +#endif + +void +cSampleManager::SetStreamedFileLoopFlag(bool8 nLoopFlag, uint8 nChannel) +{ + // nStreamLoopedFlag[nChannel] = nLoopFlag; +} + +#endif diff --git a/src/miami/audio/sampman_dc_streams.h b/src/miami/audio/sampman_dc_streams.h new file mode 100644 index 00000000..74ca20f6 --- /dev/null +++ b/src/miami/audio/sampman_dc_streams.h @@ -0,0 +1,1228 @@ + +static char DCStreamedNameTable[][25] = +{ + "stream/WILD.APM", + "stream/FLASH.APM", + "stream/KCHAT.APM", + "stream/FEVER.APM", + "stream/VROCK.APM", + "stream/VCPR.APM", + "stream/ESPANT.APM", + "stream/EMOTION.APM", + "stream/WAVE.APM", + "stream/MISCOM.APM", + "stream/CITY.APM", + "stream/WATER.APM", + "stream/BEACHAMB.APM", + "stream/HCITY.APM", + "stream/HWATER.APM", + "stream/HBEACH.APM", + "stream/MALLAMB.APM", + "stream/STRIP.APM", + "stream/MALIBU.APM", + "stream/HOTEL.APM", + "stream/DIRTRING.APM", + "stream/LAW4RIOT.APM", + "stream/AMBSIL.APM", + "stream/POLICE.APM", + "stream/TAXI.APM", + "stream/BCLOSED.APM", + "stream/BOPEN.APM", + "stream/ASS_1.APM", + "stream/ASS_2.APM", + "stream/BANK_1.APM", + "stream/BANK_2A.APM", + "stream/BANK_2B.APM", + "stream/BANK_3A.APM", + "stream/BANK_3B.APM", + "stream/BANK_4.APM", + "stream/BIKE_1.APM", + "stream/BIKE_2.APM", + "stream/BIKE_3.APM", + "stream/BUD_1.APM", + "stream/BUD_2.APM", + "stream/BUD_3.APM", + "stream/CAP_1.APM", + "stream/CAR_1.APM", + "stream/CNT_1A.APM", + "stream/CNT_1B.APM", + "stream/CNT_2.APM", + "stream/COK_1.APM", + "stream/COK_2A.APM", + "stream/COK_2B.APM", + "stream/COK_3.APM", + "stream/COK_4A.APM", + "stream/COK_4A2.APM", + "stream/COK_4B.APM", + "stream/COL_1.APM", + "stream/COL_2.APM", + "stream/COL_3A.APM", + "stream/COL_4A.APM", + "stream/COL_5A.APM", + "stream/COL_5B.APM", + "stream/CUB_1.APM", + "stream/CUB_2.APM", + "stream/CUB_3.APM", + "stream/CUB_4.APM", + "stream/DRUG_1.APM", + "stream/FIN.APM", + "stream/FIN2.APM", + "stream/FINALE.APM", + "stream/HAT_1.APM", + "stream/HAT_2.APM", + "stream/HAT_3.APM", + "stream/ICE_1.APM", + "stream/INT_A.APM", + "stream/INT_B.APM", + "stream/INT_D.APM", + "stream/INT_M.APM", + "stream/LAW_1A.APM", + "stream/LAW_1B.APM", + "stream/LAW_2A.APM", + "stream/LAW_2B.APM", + "stream/LAW_2C.APM", + "stream/LAW_3.APM", + "stream/LAW_4.APM", + "stream/PHIL_1.APM", + "stream/PHIL_2.APM", + "stream/PORN_1.APM", + "stream/PORN_2.APM", + "stream/PORN_3.APM", + "stream/PORN_4.APM", + "stream/RESC_1A.APM", + "stream/ROK_1.APM", + "stream/ROK_2.APM", + "stream/ROK_3A.APM", + "stream/STRIPA.APM", + "stream/TAX_1.APM", + "stream/TEX_1.APM", + "stream/TEX_2.APM", + "stream/TEX_3.APM", + "stream/GLIGHT.APM", + "stream/FIST.APM", + "stream/MISCOM.APM", + "stream/MISCOM.APM", + "stream/MISCOM.APM", + "stream/MISCOM.APM", + "stream/MOBR1.APM", + "stream/PAGER.APM", + "stream/CARREV.APM", + "stream/BIKEREV.APM", + "stream/LIFTOP.APM", + "stream/LIFTCL.APM", + "stream/LIFTRUN.APM", + "stream/LIFTBEL.APM", + "stream/INLIFT.APM", + "stream/SFX_01.APM", + "stream/SFX_02.APM", + "stream/CAMERAL.APM", + "stream/CAMERAR.APM", + "stream/CHEER1.APM", + "stream/CHEER2.APM", + "stream/CHEER3.APM", + "stream/CHEER4.APM", + "stream/OOH1.APM", + "stream/OOH2.APM", + "stream/RACE1.APM", + "stream/RACE2.APM", + "stream/RACE3.APM", + "stream/RACE4.APM", + "stream/RACE5.APM", + "stream/RACE6.APM", + "stream/RACE7.APM", + "stream/RACE8.APM", + "stream/RACE9.APM", + "stream/RACE10.APM", + "stream/RACE11.APM", + "stream/RACE12.APM", + "stream/RACE13.APM", + "stream/RACE14.APM", + "stream/RACE15.APM", + "stream/HOT1.APM", + "stream/HOT2.APM", + "stream/HOT3.APM", + "stream/HOT4.APM", + "stream/HOT5.APM", + "stream/HOT6.APM", + "stream/HOT7.APM", + "stream/HOT8.APM", + "stream/HOT9.APM", + "stream/HOT10.APM", + "stream/HOT11.APM", + "stream/HOT12.APM", + "stream/HOT13.APM", + "stream/HOT14.APM", + "stream/HOT15.APM", + "stream/LANSTP1.APM", + "stream/LANSTP2.APM", + "stream/LANAMU1.APM", + "stream/LANAMU2.APM", + "stream/AIRHORNL.APM", + "stream/AIRHORNR.APM", + "stream/SNIPSCRL.APM", + "stream/SNIPSHORT.APM", + "stream/BLOWROOF.APM", + "stream/ASS_1.APM", + "stream/ASS_2.APM", + "stream/ASS_3.APM", + "stream/ASS_4.APM", + "stream/ASS_5.APM", + "stream/ASS_6.APM", + "stream/ASS_7.APM", + "stream/ASS_8.APM", + "stream/ASS_9.APM", + "stream/ASS_10.APM", + "stream/ASS_11.APM", + "stream/ASS_12.APM", + "stream/ASS_13.APM", + "stream/ASS_14.APM", + "stream/BIKE1_1.APM", + "stream/BIKE1_2.APM", + "stream/BIKE1_3.APM", + "stream/BNK1_1.APM", + "stream/BNK1_2.APM", + "stream/BNK1_3.APM", + "stream/BNK1_4.APM", + "stream/BNK1_5.APM", + "stream/BNK1_6.APM", + "stream/BNK1_7.APM", + "stream/BNK1_8.APM", + "stream/BNK1_10.APM", + "stream/BNK1_11.APM", + "stream/BNK1_12.APM", + "stream/BNK1_13.APM", + "stream/BNK1_14.APM", + "stream/BNK2_1.APM", + "stream/BNK2_2.APM", + "stream/BNK2_3.APM", + "stream/BNK2_4.APM", + "stream/BNK2_5.APM", + "stream/BNK2_6.APM", + "stream/BNK2_7.APM", + "stream/BNK2_8.APM", + "stream/BNK2_9.APM", + "stream/BNK3_1.APM", + "stream/BNK3_2.APM", + "stream/BNK3_3A.APM", + "stream/BNK3_3B.APM", + "stream/BNK3_3C.APM", + "stream/BNK3_4A.APM", + "stream/BNK3_4B.APM", + "stream/BNK3_4C.APM", + "stream/BNK4_1.APM", + "stream/BNK4_2.APM", + "stream/BNK4_3A.APM", + "stream/BNK4_3B.APM", + "stream/BNK4_3C.APM", + "stream/BNK4_3D.APM", + "stream/BNK4_3E.APM", + "stream/BNK4_3F.APM", + "stream/BNK4_3G.APM", + "stream/BNK4_3H.APM", + "stream/BNK4_3I.APM", + "stream/BNK4_3J.APM", + "stream/BNK4_3K.APM", + "stream/BNK4_3M.APM", + "stream/BNK4_3O.APM", + "stream/BNK4_3P.APM", + "stream/BNK4_3Q.APM", + "stream/BNK4_3R.APM", + "stream/BNK4_3S.APM", + "stream/BNK4_3T.APM", + "stream/BNK4_3U.APM", + "stream/BNK4_3V.APM", + "stream/BNK4_4A.APM", + "stream/BNK4_4B.APM", + "stream/BNK4_5.APM", + "stream/BNK4_6.APM", + "stream/BNK4_7.APM", + "stream/BNK4_8.APM", + "stream/BNK4_9.APM", + "stream/BNK4_10.APM", + "stream/BNK4_11.APM", + "stream/BK4_12A.APM", + "stream/BK4_12B.APM", + "stream/BK4_12C.APM", + "stream/BNK4_13.APM", + "stream/BK4_14A.APM", + "stream/BK4_14B.APM", + "stream/BNK4_15.APM", + "stream/BNK4_16.APM", + "stream/BNK4_17.APM", + "stream/BNK4_18.APM", + "stream/BK4_19A.APM", + "stream/BK4_19B.APM", + "stream/BK4_20A.APM", + "stream/BK4_20B.APM", + "stream/BNK4_21.APM", + "stream/BNK422A.APM", + "stream/BNK422B.APM", + "stream/BK4_23A.APM", + "stream/BK4_23B.APM", + "stream/BK4_23C.APM", + "stream/BK4_23D.APM", + "stream/BK4_24A.APM", + "stream/BK4_24B.APM", + "stream/BNK4_25.APM", + "stream/BNK4_26.APM", + "stream/BNK4_27.APM", + "stream/BNK4_28.APM", + "stream/BNK4_29.APM", + "stream/BNK4_30.APM", + "stream/BK4_31A.APM", + "stream/BK4_31B.APM", + "stream/BNK4_32.APM", + "stream/BK4_34A.APM", + "stream/BK4_34B.APM", + "stream/BK4_35A.APM", + "stream/BK4_35B.APM", + "stream/BNK4_36.APM", + "stream/BNK4_37.APM", + "stream/BNK4_38.APM", + "stream/BNK4_39.APM", + "stream/BK4_40A.APM", + "stream/BK4_40B.APM", + "stream/BNK4_41.APM", + "stream/BNK4_42.APM", + "stream/BNK4_43.APM", + "stream/BNK4_44.APM", + "stream/BNK4_45.APM", + "stream/BNK4_46.APM", + "stream/BNK4_47.APM", + "stream/BNK4_48.APM", + "stream/BNK4_49.APM", + "stream/BNK450A.APM", + "stream/BNK450B.APM", + "stream/BNK4_51.APM", + "stream/BNK4_94.APM", + "stream/BNK4_95.APM", + "stream/BNK4_96.APM", + "stream/BNK4_97.APM", + "stream/BNK4_98.APM", + "stream/BNK4_99.APM", + "stream/BUD1_1.APM", + "stream/BUD1_2.APM", + "stream/BUD1_3.APM", + "stream/BUD1_4.APM", + "stream/BUD1_5.APM", + "stream/BUD1_9.APM", + "stream/BUD1_10.APM", + "stream/BUD2_1.APM", + "stream/BUD2_2.APM", + "stream/BUD2_3.APM", + "stream/BUD2_4.APM", + "stream/BUD2_5.APM", + "stream/BUD2_6.APM", + "stream/BUD2_7.APM", + "stream/BUD3_1.APM", + "stream/BUD3_1A.APM", + "stream/BUD3_1B.APM", + "stream/BUD3_1C.APM", + "stream/BUD3_2.APM", + "stream/BUD3_3.APM", + "stream/BUD3_4.APM", + "stream/BUD3_5.APM", + "stream/BUD3_6.APM", + "stream/BUD3_7.APM", + "stream/BUD3_8A.APM", + "stream/BUD3_8B.APM", + "stream/BUD3_8C.APM", + "stream/BUD3_9A.APM", + "stream/BUD3_9B.APM", + "stream/BUD3_9C.APM", + "stream/CAP1_2.APM", + "stream/CAP1_3.APM", + "stream/CAP1_4.APM", + "stream/CAP1_5.APM", + "stream/CAP1_6.APM", + "stream/CAP1_7.APM", + "stream/CAP1_8.APM", + "stream/CAP1_9.APM", + "stream/CAP1_10.APM", + "stream/CAP1_11.APM", + "stream/CAP1_12.APM", + "stream/CNT1_1.APM", + "stream/CNT1_2.APM", + "stream/CNT1_3.APM", + "stream/CNT1_4.APM", + "stream/CNT1_5.APM", + "stream/CNT2_1.APM", + "stream/CNT2_2.APM", + "stream/CNT2_3.APM", + "stream/CNT2_4.APM", + "stream/COK1_1.APM", + "stream/COK1_2.APM", + "stream/COK1_3.APM", + "stream/COK1_4.APM", + "stream/COK1_5.APM", + "stream/COK1_6.APM", + "stream/COK2_1.APM", + "stream/COK2_2.APM", + "stream/COK2_3.APM", + "stream/COK2_4.APM", + "stream/COK2_5.APM", + "stream/COK2_6.APM", + "stream/COK2_7A.APM", + "stream/COK2_7B.APM", + "stream/COK2_7C.APM", + "stream/COK2_8A.APM", + "stream/COK2_8B.APM", + "stream/COK2_8C.APM", + "stream/COK2_8D.APM", + "stream/COK2_9.APM", + "stream/COK210A.APM", + "stream/COK210B.APM", + "stream/COK210C.APM", + "stream/COK212A.APM", + "stream/COK212B.APM", + "stream/COK2_13.APM", + "stream/COK2_14.APM", + "stream/COK2_15.APM", + "stream/COK2_16.APM", + "stream/COK2_20.APM", + "stream/COK2_21.APM", + "stream/COK2_2.APM", // this is probably a typo of COK2_22 + "stream/COK3_1.APM", + "stream/COK3_2.APM", + "stream/COK3_3.APM", + "stream/COK3_4.APM", + "stream/COK4_1.APM", + "stream/COK4_2.APM", + "stream/COK4_3.APM", + "stream/COK4_4.APM", + "stream/COK4_5.APM", + "stream/COK4_6.APM", + "stream/COK4_7.APM", + "stream/COK4_8.APM", + "stream/COK4_9.APM", + "stream/COK4_9A.APM", + "stream/COK4_10.APM", + "stream/COK4_11.APM", + "stream/COK4_12.APM", + "stream/COK4_13.APM", + "stream/COK4_14.APM", + "stream/COK4_15.APM", + "stream/COK4_16.APM", + "stream/COK4_17.APM", + "stream/COK4_18.APM", + "stream/COK4_19.APM", + "stream/COK4_20.APM", + "stream/COK4_21.APM", + "stream/COK4_22.APM", + "stream/COK4_23.APM", + "stream/COK4_24.APM", + "stream/COK4_25.APM", + "stream/COK4_26.APM", + "stream/COK4_27.APM", + "stream/COL1_1.APM", + "stream/COL1_2.APM", + "stream/COL1_3.APM", + "stream/COL1_4.APM", + "stream/COL1_5.APM", + "stream/COL1_6.APM", + "stream/COL1_7.APM", + "stream/COL1_8.APM", + "stream/COL2_1.APM", + "stream/COL2_2.APM", + "stream/COL2_3.APM", + "stream/COL2_4.APM", + "stream/COL2_5.APM", + "stream/COL2_6A.APM", + "stream/COL2_7.APM", + "stream/COL2_8.APM", + "stream/COL2_9.APM", + "stream/COL2_10.APM", + "stream/COL2_11.APM", + "stream/COL2_12.APM", + "stream/COL2_13.APM", + "stream/COL2_14.APM", + "stream/COL2_15.APM", + "stream/COL2_16.APM", + "stream/COL3_1.APM", + "stream/COL3_2.APM", + "stream/COL3_2A.APM", + "stream/COL3_2B.APM", + "stream/COL3_3.APM", + "stream/COL3_4.APM", + "stream/COL3_5.APM", + "stream/COL3_6.APM", + "stream/COL3_7.APM", + "stream/COL3_8.APM", + "stream/COL3_9.APM", + "stream/COL3_10.APM", + "stream/COL3_11.APM", + "stream/COL3_12.APM", + "stream/COL3_13.APM", + "stream/COL3_14.APM", + "stream/COL3_15.APM", + "stream/COL3_16.APM", + "stream/COL3_17.APM", + "stream/COL3_18.APM", + "stream/COL3_19.APM", + "stream/COL3_20.APM", + "stream/COL3_21.APM", + "stream/COL3_23.APM", + "stream/COL3_24.APM", + "stream/COL3_25.APM", + "stream/COL4_1.APM", + "stream/COL4_2.APM", + "stream/COL4_3.APM", + "stream/COL4_4.APM", + "stream/COL4_5.APM", + "stream/COL4_6.APM", + "stream/COL4_7.APM", + "stream/COL4_8.APM", + "stream/COL4_9.APM", + "stream/COL4_10.APM", + "stream/COL4_11.APM", + "stream/COL4_12.APM", + "stream/COL4_13.APM", + "stream/COL4_14.APM", + "stream/COL4_15.APM", + "stream/COL4_16.APM", + "stream/COL4_17.APM", + "stream/COL4_18.APM", + "stream/COL4_19.APM", + "stream/COL4_20.APM", + "stream/COL4_21.APM", + "stream/COL4_22.APM", + "stream/COL4_23.APM", + "stream/COL4_24.APM", + "stream/COL4_25.APM", + "stream/COL4_26.APM", + "stream/COL5_1.APM", + "stream/COL5_2.APM", + "stream/COL5_3.APM", + "stream/COL5_4.APM", + "stream/COL5_5.APM", + "stream/COL5_6.APM", + "stream/COL5_7.APM", + "stream/COL5_8.APM", + "stream/COL5_9.APM", + "stream/COL5_10.APM", + "stream/COL5_11.APM", + "stream/COL5_12.APM", + "stream/COL5_13.APM", + "stream/COL5_14.APM", + "stream/COL5_15.APM", + "stream/COL5_16.APM", + "stream/COL5_17.APM", + "stream/COL5_18.APM", + "stream/COL5_19.APM", + "stream/COL5_20.APM", + "stream/COL5_21.APM", + "stream/COL5_22.APM", + "stream/CUB1_1.APM", + "stream/CUB1_2.APM", + "stream/CUB1_3.APM", + "stream/CUB1_4.APM", + "stream/CUB1_5.APM", + "stream/CUB1_6.APM", + "stream/CUB1_7.APM", + "stream/CUB1_8.APM", + "stream/CUB1_9.APM", + "stream/CUB1_10.APM", + "stream/CUB2_1.APM", + "stream/CUB2_2.APM", + "stream/CUB2_3A.APM", + "stream/CUB2_3B.APM", + "stream/CUB2_3C.APM", + "stream/CUB2_4A.APM", + "stream/CUB2_5.APM", + "stream/CUB2_6.APM", + "stream/CUB2_7.APM", + "stream/CUB2_8.APM", + "stream/CUB2_9.APM", + "stream/CUB2_10.APM", + "stream/CUB2_11.APM", + "stream/CUB3_1.APM", + "stream/CUB3_2.APM", + "stream/CUB3_3.APM", + "stream/CUB3_4.APM", + "stream/CUB4_1.APM", + "stream/CUB4_2.APM", + "stream/CUB4_3.APM", + "stream/CUB4_4.APM", + "stream/CUB4_5.APM", + "stream/CUB4_5A.APM", + "stream/CUB4_6.APM", + "stream/CUB4_7.APM", + "stream/CUB4_8.APM", + "stream/CUB4_9.APM", + "stream/CUB4_10.APM", + "stream/CUB4_11.APM", + "stream/CUB4_12.APM", + "stream/CUB4_13.APM", + "stream/CUB4_14.APM", + "stream/CUB4_15.APM", + "stream/CUB4_16.APM", + "stream/GOLF_1.APM", + "stream/GOLF_2.APM", + "stream/GOLF_3.APM", + "stream/BAR_1.APM", + "stream/BAR_2.APM", + "stream/BAR_3.APM", + "stream/BAR_4.APM", + "stream/BAR_5.APM", + "stream/BAR_6.APM", + "stream/BAR_7.APM", + "stream/BAR_8.APM", + "stream/STRIP_1.APM", + "stream/STRIP_2.APM", + "stream/STRIP_3.APM", + "stream/STRIP_4.APM", + "stream/STRIP_5.APM", + "stream/STRIP_6.APM", + "stream/STRIP_7.APM", + "stream/STRIP_8.APM", + "stream/STRIP_9.APM", + "stream/STAR_1.APM", + "stream/STAR_2.APM", + "stream/STAR_3.APM", + "stream/STAR_4.APM", + "stream/FIN_1A.APM", + "stream/FIN_1B.APM", + "stream/FIN_1C.APM", + "stream/FIN_2B.APM", + "stream/FIN_2C.APM", + "stream/FIN_3.APM", + "stream/FIN_4.APM", + "stream/FIN_5.APM", + "stream/FIN_6.APM", + "stream/FIN_10.APM", + "stream/FIN_11A.APM", + "stream/FIN_11B.APM", + "stream/FIN_12A.APM", + "stream/FIN_12B.APM", + "stream/FIN_12C.APM", + "stream/FIN_13.APM", + "stream/FINKILL.APM", + "stream/LAW1_1.APM", + "stream/LAW1_2.APM", + "stream/LAW1_3.APM", + "stream/LAW1_4.APM", + "stream/LAW1_5.APM", + "stream/LAW1_6.APM", + "stream/LAW1_7.APM", + "stream/LAW1_8.APM", + "stream/LAW1_9.APM", + "stream/LAW1_10.APM", + "stream/LAW2_1.APM", + "stream/LAW2_2.APM", + "stream/LAW2_3.APM", + "stream/LAW2_4.APM", + "stream/LAW2_5.APM", + "stream/LAW2_6.APM", + "stream/LAW2_7.APM", + "stream/LAW2_8.APM", + "stream/LAW2_9.APM", + "stream/LAW2_10.APM", + "stream/LAW3_1.APM", + "stream/LAW3_2.APM", + "stream/LAW3_3.APM", + "stream/LAW3_4.APM", + "stream/LAW3_5.APM", + "stream/LAW3_6.APM", + "stream/LAW3_10.APM", + "stream/LAW3_11.APM", + "stream/LAW3_12.APM", + "stream/LAW3_13.APM", + "stream/LAW3_14.APM", + "stream/LAW3_16.APM", + "stream/LAW3_17.APM", + "stream/LAW3_18.APM", + "stream/LAW3_19.APM", + "stream/LAW3_20.APM", + "stream/LAW3_21.APM", + "stream/LAW3_22.APM", + "stream/LAW3_23.APM", + "stream/LAW3_24.APM", + "stream/LAW3_25.APM", + "stream/LAW4_1A.APM", + "stream/LAW4_1B.APM", + "stream/LAW4_1C.APM", + "stream/LAW4_1D.APM", + "stream/LAW4_10.APM", + "stream/LAW4_3.APM", + "stream/LAW4_4.APM", + "stream/LAW4_5.APM", + "stream/LAW4_6.APM", + "stream/LAW4_7.APM", + "stream/LAW4_8.APM", + "stream/LAW4_9.APM", + "stream/PHIL1_2.APM", + "stream/PHIL1_3.APM", + "stream/PHIL2_1.APM", + "stream/PHIL2_2.APM", + "stream/PHIL2_3.APM", + "stream/PHIL2_4.APM", + "stream/PHIL2_5.APM", + "stream/PHIL2_6.APM", + "stream/PHIL2_7.APM", + "stream/PHIL2_8.APM", + "stream/PHIL2_9.APM", + "stream/PHIL210.APM", + "stream/PHIL211.APM", + "stream/PORN1_1.APM", + "stream/PORN1_2.APM", + "stream/PORN1_3.APM", + "stream/PRN1_3A.APM", + "stream/PORN1_4.APM", + "stream/PORN1_5.APM", + "stream/PORN1_6.APM", + "stream/PORN1_7.APM", + "stream/PORN1_8.APM", + "stream/PORN1_9.APM", + "stream/PRN1_10.APM", + "stream/PRN1_11.APM", + "stream/PRN1_12.APM", + "stream/PRN1_13.APM", + "stream/PRN1_14.APM", + "stream/PRN1_15.APM", + "stream/PRN1_16.APM", + "stream/PRN1_17.APM", + "stream/PRN1_18.APM", + "stream/PRN1_19.APM", + "stream/PRN1_20.APM", + "stream/PRN1_21.APM", + "stream/PORN3_1.APM", + "stream/PORN3_2.APM", + "stream/PORN3_3.APM", + "stream/PORN3_4.APM", + "stream/PSYCH_1.APM", + "stream/PSYCH_2.APM", + "stream/ROK2_01.APM", + "stream/ROK3_1.APM", + "stream/ROK3_2.APM", + "stream/ROK3_3.APM", + "stream/ROK3_4.APM", + "stream/ROK3_5.APM", + "stream/ROK3_6.APM", + "stream/ROK3_7.APM", + "stream/ROK3_8.APM", + "stream/ROK3_9.APM", + "stream/ROK3_10.APM", + "stream/ROK3_11.APM", + "stream/ROK3_12.APM", + "stream/ROK3_13.APM", + "stream/ROK3_14.APM", + "stream/ROK3_15.APM", + "stream/ROK3_16.APM", + "stream/ROK3_17.APM", + "stream/ROK3_18.APM", + "stream/ROK3_19.APM", + "stream/ROK3_20.APM", + "stream/ROK3_21.APM", + "stream/ROK3_22.APM", + "stream/ROK3_23.APM", + "stream/ROK3_24.APM", + "stream/ROK3_25.APM", + "stream/ROK3_26.APM", + "stream/ROK3_27.APM", + "stream/ROK3_62.APM", + "stream/ROK3_63.APM", + "stream/ROK3_64.APM", + "stream/ROK3_65.APM", + "stream/ROK3_66.APM", + "stream/ROK3_67.APM", + "stream/ROK3_68.APM", + "stream/ROK3_69.APM", + "stream/ROK3_70.APM", + "stream/ROK3_71.APM", + "stream/ROK3_73.APM", + "stream/RESC_1.APM", + "stream/RESC_2.APM", + "stream/RESC_3.APM", + "stream/RESC_4.APM", + "stream/RESC_5.APM", + "stream/RESC_6.APM", + "stream/RESC_7.APM", + "stream/RESC_8.APM", + "stream/RESC_9.APM", + "stream/RESC_10.APM", + "stream/ROK1_1A.APM", + "stream/ROK1_1B.APM", + "stream/ROK1_5.APM", + "stream/ROK1_6.APM", + "stream/ROK1_7.APM", + "stream/ROK1_8.APM", + "stream/ROK1_9.APM", + "stream/TAX1_1.APM", + "stream/TAX1_2.APM", + "stream/TAX1_3.APM", + "stream/TAX1_4.APM", + "stream/TAX1_5.APM", + "stream/TAX2_1.APM", + "stream/TAX2_2.APM", + "stream/TAX2_3.APM", + "stream/TAX2_4.APM", + "stream/TAX2_5.APM", + "stream/TAX2_6.APM", + "stream/TAX2_7.APM", + "stream/TAX3_1.APM", + "stream/TAX3_2.APM", + "stream/TAX3_3.APM", + "stream/TAX3_4.APM", + "stream/TAX3_5.APM", + "stream/TEX1_1.APM", + "stream/TEX1_2.APM", + "stream/TEX1_3.APM", + "stream/TEX1_4.APM", + "stream/TEX1_5.APM", + "stream/TEX1_6.APM", + "stream/TEX2_1.APM", + "stream/TEX3_1.APM", + "stream/TEX3_2.APM", + "stream/TEX3_3.APM", + "stream/TEX3_4.APM", + "stream/TEX3_5.APM", + "stream/TEX3_6.APM", + "stream/TEX3_7.APM", + "stream/TEX3_8.APM", + "stream/HAT_1A.APM", + "stream/INTRO1.APM", + "stream/INTRO2.APM", + "stream/INTRO3.APM", + "stream/INTRO4.APM", + "stream/MOB_01A.APM", + "stream/MOB_01B.APM", + "stream/MOB_01C.APM", + "stream/MOB_02A.APM", + "stream/MOB_02B.APM", + "stream/MOB_02C.APM", + "stream/MOB_03A.APM", + "stream/MOB_03B.APM", + "stream/MOB_03C.APM", + "stream/MOB_03D.APM", + "stream/MOB_03E.APM", + "stream/SHARK_1.APM", + "stream/SHARK_2.APM", + "stream/SHARK_3.APM", + "stream/SHARK_4.APM", + "stream/SHARK_5.APM", + "stream/MOB_04A.APM", + "stream/MOB_04B.APM", + "stream/MOB_04C.APM", + "stream/MOB_04D.APM", + "stream/MOB_05A.APM", + "stream/MOB_05B.APM", + "stream/MOB_05C.APM", + "stream/MOB_05D.APM", + "stream/MOB_06A.APM", + "stream/MOB_06B.APM", + "stream/MOB_06C.APM", + "stream/MOB_07A.APM", + "stream/MOB_07B.APM", + "stream/MOB_08A.APM", + "stream/MOB_08B.APM", + "stream/MOB_08C.APM", + "stream/MOB_08D.APM", + "stream/MOB_08E.APM", + "stream/MOB_08F.APM", + "stream/MOB_08G.APM", + "stream/MOB_09A.APM", + "stream/MOB_09B.APM", + "stream/MOB_09C.APM", + "stream/MOB_09D.APM", + "stream/MOB_09E.APM", + "stream/MOB_09F.APM", + "stream/MOB_10A.APM", + "stream/MOB_10B.APM", + "stream/MOB_10C.APM", + "stream/MOB_10D.APM", + "stream/MOB_10E.APM", + "stream/MOB_11A.APM", + "stream/MOB_11B.APM", + "stream/MOB_11C.APM", + "stream/MOB_11D.APM", + "stream/MOB_11E.APM", + "stream/MOB_11F.APM", + "stream/MOB_14A.APM", + "stream/MOB_14B.APM", + "stream/MOB_14C.APM", + "stream/MOB_14D.APM", + "stream/MOB_14E.APM", + "stream/MOB_14F.APM", + "stream/MOB_14G.APM", + "stream/MOB_14H.APM", + "stream/MOB_16A.APM", + "stream/MOB_16B.APM", + "stream/MOB_16C.APM", + "stream/MOB_16D.APM", + "stream/MOB_16E.APM", + "stream/MOB_16F.APM", + "stream/MOB_16G.APM", + "stream/MOB_17A.APM", + "stream/MOB_17B.APM", + "stream/MOB_17C.APM", + "stream/MOB_17D.APM", + "stream/MOB_17E.APM", + "stream/MOB_17G.APM", + "stream/MOB_17H.APM", + "stream/MOB_17I.APM", + "stream/MOB_17J.APM", + "stream/MOB_17K.APM", + "stream/MOB_17L.APM", + "stream/MOB_18A.APM", + "stream/MOB_18B.APM", + "stream/MOB_18C.APM", + "stream/MOB_18D.APM", + "stream/MOB_18E.APM", + "stream/MOB_18F.APM", + "stream/MOB_18G.APM", + "stream/MOB_20A.APM", + "stream/MOB_20B.APM", + "stream/MOB_20C.APM", + "stream/MOB_20D.APM", + "stream/MOB_20E.APM", + "stream/MOB_24A.APM", + "stream/MOB_24B.APM", + "stream/MOB_24C.APM", + "stream/MOB_24D.APM", + "stream/MOB_24E.APM", + "stream/MOB_24F.APM", + "stream/MOB_24G.APM", + "stream/MOB_24H.APM", + "stream/MOB_25A.APM", + "stream/MOB_25B.APM", + "stream/MOB_25C.APM", + "stream/MOB_25D.APM", + "stream/MOB_26A.APM", + "stream/MOB_26B.APM", + "stream/MOB_26C.APM", + "stream/MOB_26D.APM", + "stream/MOB_26E.APM", + "stream/MOB_29A.APM", + "stream/MOB_29B.APM", + "stream/MOB_29C.APM", + "stream/MOB_29D.APM", + "stream/MOB_29E.APM", + "stream/MOB_29F.APM", + "stream/MOB_29G.APM", + "stream/MOB_30A.APM", + "stream/MOB_30B.APM", + "stream/MOB_30C.APM", + "stream/MOB_30D.APM", + "stream/MOB_30E.APM", + "stream/MOB_30F.APM", + "stream/MOB_33A.APM", + "stream/MOB_33B.APM", + "stream/MOB_33C.APM", + "stream/MOB_33D.APM", + "stream/MOB_34A.APM", + "stream/MOB_34B.APM", + "stream/MOB_34C.APM", + "stream/MOB_34D.APM", + "stream/MOB_35A.APM", + "stream/MOB_35B.APM", + "stream/MOB_35C.APM", + "stream/MOB_35D.APM", + "stream/MOB_36A.APM", + "stream/MOB_36B.APM", + "stream/MOB_36C.APM", + "stream/MOB_40A.APM", + "stream/MOB_40B.APM", + "stream/MOB_40C.APM", + "stream/MOB_40D.APM", + "stream/MOB_40E.APM", + "stream/MOB_40F.APM", + "stream/MOB_40G.APM", + "stream/MOB_40H.APM", + "stream/MOB_40I.APM", + "stream/MOB_41A.APM", + "stream/MOB_41B.APM", + "stream/MOB_41C.APM", + "stream/MOB_41D.APM", + "stream/MOB_41E.APM", + "stream/MOB_41F.APM", + "stream/MOB_41G.APM", + "stream/MOB_41H.APM", + "stream/MOB_42A.APM", + "stream/MOB_42B.APM", + "stream/MOB_42C.APM", + "stream/MOB_42D.APM", + "stream/MOB_42E.APM", + "stream/MOB_43A.APM", + "stream/MOB_43B.APM", + "stream/MOB_43C.APM", + "stream/MOB_43D.APM", + "stream/MOB_43E.APM", + "stream/MOB_43F.APM", + "stream/MOB_43G.APM", + "stream/MOB_43H.APM", + "stream/MOB_45A.APM", + "stream/MOB_45B.APM", + "stream/MOB_45C.APM", + "stream/MOB_45D.APM", + "stream/MOB_45E.APM", + "stream/MOB_45F.APM", + "stream/MOB_45G.APM", + "stream/MOB_45H.APM", + "stream/MOB_45I.APM", + "stream/MOB_45J.APM", + "stream/MOB_45K.APM", + "stream/MOB_45L.APM", + "stream/MOB_45M.APM", + "stream/MOB_45N.APM", + "stream/MOB_46A.APM", + "stream/MOB_46B.APM", + "stream/MOB_46C.APM", + "stream/MOB_46D.APM", + "stream/MOB_46E.APM", + "stream/MOB_46F.APM", + "stream/MOB_46G.APM", + "stream/MOB_46H.APM", + "stream/MOB_47A.APM", + "stream/MOB_52A.APM", + "stream/MOB_52B.APM", + "stream/MOB_52C.APM", + "stream/MOB_52D.APM", + "stream/MOB_52E.APM", + "stream/MOB_52F.APM", + "stream/MOB_52G.APM", + "stream/MOB_52H.APM", + "stream/MOB_54A.APM", + "stream/MOB_54B.APM", + "stream/MOB_54C.APM", + "stream/MOB_54D.APM", + "stream/MOB_54E.APM", + "stream/MOB_55A.APM", + "stream/MOB_55B.APM", + "stream/MOB_55C.APM", + "stream/MOB_55D.APM", + "stream/MOB_55E.APM", + "stream/MOB_55F.APM", + "stream/MOB_56A.APM", + "stream/MOB_56B.APM", + "stream/MOB_56C.APM", + "stream/MOB_56D.APM", + "stream/MOB_56E.APM", + "stream/MOB_56F.APM", + "stream/MOB_57A.APM", + "stream/MOB_57B.APM", + "stream/MOB_57C.APM", + "stream/MOB_57D.APM", + "stream/MOB_57E.APM", + "stream/MOB_58A.APM", + "stream/MOB_58B.APM", + "stream/MOB_58C.APM", + "stream/MOB_58D.APM", + "stream/MOB_58E.APM", + "stream/MOB_58F.APM", + "stream/MOB_58G.APM", + "stream/MOB_61A.APM", + "stream/MOB_61B.APM", + "stream/MOB_62A.APM", + "stream/MOB_62B.APM", + "stream/MOB_62C.APM", + "stream/MOB_62D.APM", + "stream/MOB_63A.APM", + "stream/MOB_63B.APM", + "stream/MOB_63C.APM", + "stream/MOB_63D.APM", + "stream/MOB_63E.APM", + "stream/MOB_63F.APM", + "stream/MOB_63G.APM", + "stream/MOB_63H.APM", + "stream/MOB_63I.APM", + "stream/MOB_63J.APM", + "stream/MOB_66A.APM", + "stream/MOB_66B.APM", + "stream/MOB_68A.APM", + "stream/MOB_68B.APM", + "stream/MOB_68C.APM", + "stream/MOB_68D.APM", + "stream/MOB_70A.APM", + "stream/MOB_70B.APM", + "stream/MOB_71A.APM", + "stream/MOB_71B.APM", + "stream/MOB_71C.APM", + "stream/MOB_71D.APM", + "stream/MOB_71E.APM", + "stream/MOB_71F.APM", + "stream/MOB_71G.APM", + "stream/MOB_71H.APM", + "stream/MOB_71I.APM", + "stream/MOB_71J.APM", + "stream/MOB_71K.APM", + "stream/MOB_71L.APM", + "stream/MOB_71M.APM", + "stream/MOB_71N.APM", + "stream/MOB_72A.APM", + "stream/MOB_72B.APM", + "stream/MOB_72C.APM", + "stream/MOB_72D.APM", + "stream/MOB_72E.APM", + "stream/MOB_72F.APM", + "stream/MOB_72G.APM", + "stream/MOB_73A.APM", + "stream/MOB_73C.APM", + "stream/MOB_73D.APM", + "stream/MOB_73F.APM", + "stream/MOB_73G.APM", + "stream/MOB_73I.APM", + "stream/MOB_95A.APM", + "stream/MOB_96A.APM", + "stream/MOB_98A.APM", + "stream/MOB_99A.APM", + "stream/JOB1_1B.APM", + "stream/JOB1_1C.APM", + "stream/JOB1_1D.APM", + "stream/JOB2_1B.APM", + "stream/JOB2_2.APM", + "stream/JOB2_3.APM", + "stream/JOB2_4.APM", + "stream/JOB2_5.APM", + "stream/JOB2_6.APM", + "stream/JOB2_7.APM", + "stream/JOB2_8.APM", + "stream/JOB2_9.APM", + "stream/JOB3_1.APM", + "stream/JOB3_2.APM", + "stream/JOB3_3.APM", + "stream/JOB4_1.APM", + "stream/JOB4_2.APM", + "stream/JOB4_3.APM", + "stream/JOB5_1.APM", + "stream/JOB5_2.APM", + "stream/JOB5_3.APM", + "stream/BJM1_20.APM", + "stream/BJM1_4.APM", + "stream/BJM1_5.APM", + "stream/MERC_39.APM", + "stream/MONO_1.APM", + "stream/MONO_2.APM", + "stream/MONO_3.APM", + "stream/MONO_4.APM", + "stream/MONO_5.APM", + "stream/MONO_6.APM", + "stream/MONO_7.APM", + "stream/MONO_8.APM", + "stream/MONO_9.APM", + "stream/MONO10.APM", + "stream/MONO11.APM", + "stream/MONO12.APM", + "stream/MONO13.APM", + "stream/MONO14.APM", + "stream/MONO15.APM", + "stream/MONO16.APM", + "stream/FUD_01.APM", + "stream/FUD_02.APM", + "stream/FUD_03.APM", + "stream/FUD_04.APM", + "stream/FUD_05.APM", + "stream/FUD_06.APM", + "stream/FUD_07.APM", + "stream/FUD_08.APM", + "stream/FUD_09.APM", + "stream/FUD_10.APM", + "stream/FUD_11.APM", + "stream/FUD_12.APM", + "stream/FUD_13.APM", + "stream/FUD_14.APM", + "stream/FUD_15.APM", + "stream/FUD_16.APM", + "stream/FUD_17.APM", + "stream/FUD_18.APM", + "stream/FUD_19.APM", + "stream/FUD_20.APM", + "stream/BURG_01.APM", + "stream/BURG_02.APM", + "stream/BURG_03.APM", + "stream/BURG_04.APM", + "stream/BURG_05.APM", + "stream/BURG_06.APM", + "stream/BURG_07.APM", + "stream/BURG_08.APM", + "stream/BURG_09.APM", + "stream/BURG_10.APM", + "stream/BURG_11.APM", + "stream/BURG_12.APM", + "stream/CRUST01.APM", + "stream/CRUST02.APM", + "stream/CRUST03.APM", + "stream/CRUST04.APM", + "stream/CRUST05.APM", + "stream/CRUST06.APM", + "stream/CRUST07.APM", + "stream/CRUST08.APM", + "stream/CRUST09.APM", + "stream/BAND_01.APM", + "stream/BAND_02.APM", + "stream/BAND_03.APM", + "stream/BAND_04.APM", + "stream/BAND_05.APM", + "stream/BAND_06.APM", + "stream/BAND_07.APM", + "stream/BAND_08.APM", + "stream/SHAFT01.APM", + "stream/SHAFT02.APM", + "stream/SHAFT03.APM", + "stream/SHAFT04.APM", + "stream/SHAFT05.APM", + "stream/SHAFT06.APM", + "stream/SHAFT07.APM", + "stream/SHAFT08.APM", + "stream/PISS_01.APM", + "stream/PISS_02.APM", + "stream/PISS_03.APM", + "stream/PISS_04.APM", + "stream/PISS_05.APM", + "stream/PISS_06.APM", + "stream/PISS_07.APM", + "stream/PISS_08.APM", + "stream/PISS_09.APM", + "stream/PISS_10.APM", + "stream/PISS_11.APM", + "stream/PISS_12.APM", + "stream/PISS_13.APM", + "stream/PISS_14.APM", + "stream/PISS_15.APM", + "stream/PISS_16.APM", + "stream/PISS_17.APM", + "stream/PISS_18.APM", + "stream/PISS_19.APM", + "stream/GIMME01.APM", + "stream/GIMME02.APM", + "stream/GIMME03.APM", + "stream/GIMME04.APM", + "stream/GIMME05.APM", + "stream/GIMME06.APM", + "stream/GIMME07.APM", + "stream/GIMME08.APM", + "stream/GIMME09.APM", + "stream/GIMME10.APM", + "stream/GIMME11.APM", + "stream/GIMME12.APM", + "stream/GIMME13.APM", + "stream/GIMME14.APM", + "stream/GIMME15.APM", + "stream/BUST_01.APM", + "stream/BUST_02.APM", + "stream/BUST_03.APM", + "stream/BUST_04.APM", + "stream/BUST_05.APM", + "stream/BUST_06.APM", + "stream/BUST_07.APM", + "stream/BUST_08.APM", + "stream/BUST_09.APM", + "stream/BUST_10.APM", + "stream/BUST_11.APM", + "stream/BUST_12.APM", + "stream/BUST_13.APM", + "stream/BUST_14.APM", + "stream/BUST_15.APM", + "stream/BUST_16.APM", + "stream/BUST_17.APM", + "stream/BUST_18.APM", + "stream/BUST_19.APM", + "stream/BUST_20.APM", + "stream/BUST_21.APM", + "stream/BUST_22.APM", + "stream/BUST_23.APM", + "stream/BUST_24.APM", + "stream/BUST_25.APM", + "stream/BUST_26.APM", + "stream/BUST_27.APM", + "stream/BUST_28.APM", +}; \ No newline at end of file diff --git a/src/miami/audio/sampman_miles.cpp b/src/miami/audio/sampman_miles.cpp new file mode 100644 index 00000000..9f8e8a9c --- /dev/null +++ b/src/miami/audio/sampman_miles.cpp @@ -0,0 +1,2599 @@ +#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" +#include "crossplatform.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; + +#ifdef FIX_BUGS +uint32 gPlayerTalkSfx = UINT32_MAX; +void *gPlayerTalkData = 0; +#endif + +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]; +bool8 nStreamLoopedFlag[MAX_STREAMS]; +uint32 _CurMP3Index; +int32 _CurMP3Pos; +bool8 _bIsMp3Active; +/////////////////////////////////////////////////////////////// + + +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 + +U32 RadioHandlers[9]; + +U32 WINAPI vfs_open_callback(char const* Filename, U32* FileHandle) +{ + *FileHandle = (U32)fopen(Filename, "rb"); + + // couldn't they just use stricmp once? and strlen? this is very inefficient + if ((strcmp(Filename + strlen(Filename) - 4, ".adf") == 0) || (strcmp(Filename + strlen(Filename) - 4, ".ADF") == 0)) { + for (int i = 0; i < ARRAY_SIZE(RadioHandlers); i++) { + if (RadioHandlers[i] == NULL) { + RadioHandlers[i] = *FileHandle; + break; + } + } + strcpy((char*)Filename + strlen(Filename) - 4, ".mp3"); + } + return *FileHandle; +} + +void WINAPI vfs_close_callback(U32 FileHandle) +{ + for (int i = 0; i < ARRAY_SIZE(RadioHandlers); i++) { + if (RadioHandlers[i] == FileHandle) { + RadioHandlers[i] = NULL; + break; + } + } + fclose((FILE*)FileHandle); +} + +S32 WINAPI vfs_seek_callback(U32 FileHandle, S32 Offset, U32 Type) +{ + fseek((FILE*)FileHandle, Offset, Type); + return ftell((FILE*)FileHandle); +} + +U32 WINAPI vfs_read_callback(U32 FileHandle, void* Buffer, U32 Bytes) +{ + fread(Buffer, Bytes, 1, (FILE*)FileHandle); + uint8* _Buffer = (uint8*)Buffer; + + for (int i = 0; i < ARRAY_SIZE(RadioHandlers); i++) { + if (FileHandle == RadioHandlers[i]) { + for (U32 k = 0; k < Bytes; k++) + _Buffer[k] ^= 0x22; + break; + } + } + return Bytes; +} + +cSampleManager::cSampleManager(void) : + m_nNumberOfProviders(0) +{ + ; + + AIL_set_file_callbacks(vfs_open_callback, vfs_close_callback, vfs_seek_callback, vfs_read_callback); +} + +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; +} + +int8 +cSampleManager::AutoDetect3DProviders() +{ + if (!AudioManager.IsAudioInitialised()) + return -1; + + int eax = -1, eax2 = -1, eax3 = -1, ds3dh = -1, ds3ds = -1; + + for (uint32 i = 0; i < GetNum3DProvidersAvailable(); i++) + { + char* providername = Get3DProviderName(i); + + if (!strcasecmp(providername, "CREATIVE LABS EAX (TM)")) { + AudioManager.SetCurrent3DProvider(i); + if (GetCurrent3DProviderIndex() == i) + eax = i; + } + + if (!strcasecmp(providername, "CREATIVE LABS EAX 2 (TM)")) { + AudioManager.SetCurrent3DProvider(i); + if (GetCurrent3DProviderIndex() == i) + eax2 = i; + } + + if (!strcasecmp(providername, "CREATIVE LABS EAX 3 (TM)")) { + AudioManager.SetCurrent3DProvider(i); + if (GetCurrent3DProviderIndex() == i) { + eax3 = i; + } + } + + if (!strcasecmp(providername, "DIRECTSOUND3D HARDWARE SUPPORT")) { + AudioManager.SetCurrent3DProvider(i); + if (GetCurrent3DProviderIndex() == i) + ds3dh = i; + } + + if (!strcasecmp(providername, "DIRECTSOUND3D SOFTWARE EMULATION")) { + AudioManager.SetCurrent3DProvider(i); + if (GetCurrent3DProviderIndex() == i) + ds3ds = i; + } + } + + if (eax3 != -1) + return eax3; + if (eax2 != -1) + return eax2; + if (eax != -1) + return eax; + if (ds3dh != -1) + return ds3dh; + if (ds3ds != -1) + return ds3ds; + return -1; +} +#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; + } + + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + } + + 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); + 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; + } + bInitFirstEntry = FALSE; + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + + OutputDebugString(filepath); + + bInitFirstEntry = TRUE; + } + + while ( TRUE ) + { + if ( !FindNextFile(hFind, &fd) ) + break; + + if ( bInitFirstEntry ) + { + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + int32 filepathlen = strlen(filepath); + + if ( filepathlen > 0 ) + { + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + } + + bShortcut = TRUE; + } + else + { + bShortcut = FALSE; + + if ( filepathlen > MAX_PATH ) + { + 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; + + bInitFirstEntry = FALSE; + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + } + } + } + else + { + strcpy(filepath, _mp3DirectoryPath); + strcat(filepath, fd.cFileName); + + int32 filepathlen = strlen(filepath); + + if ( filepathlen > 0 ) + { + if ( filepathlen > 4 ) + { + if ( !strcmp(&filepath[filepathlen - 4], ".lnk") ) + { + if ( _ResolveLink(filepath, filepath) ) + { + OutputDebugString("Resolving Link"); + OutputDebugString(filepath); + } + + 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); + } + else + { + strcat(filepath, " - NOT A VALID MP3"); + OutputDebugString(filepath); + } + } + } + } + + 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); + + } + +#ifdef AUDIO_CACHE + TRACE("cache"); + FILE *cacheFile = fcaseopen("audio\\sound.cache", "rb"); + bool8 CreateCache = FALSE; + if (cacheFile) { + fread(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + }else + CreateCache = TRUE; +#endif + + char filepath[MAX_PATH]; + bool8 bFileNotFound; + S32 tatalms; + + TRACE("cdrom"); + { + m_bInitialised = FALSE; + + + while (TRUE) + { + + // Find path of WAVs (originally in HDD) + int32 drive = 'C'; + +#ifndef NO_CDCHECK + 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); + strcpy(m_MiscomPath, m_szCDRomRootPath); + break; + } + } + + } while ( ++drive <= 'Z' ); +#else + m_MiscomPath[0] = '\0'; +#endif + + if ( DIG == NULL ) + { + OutputDebugString(AIL_last_error()); + Terminate(); + return FALSE; + } + +#ifdef EXTERNAL_3D_SOUND + add_providers(); +#endif + + m_szCDRomRootPath[0] = '\0'; + + strcpy(m_WavFilesPath, m_szCDRomRootPath); + +#ifdef AUDIO_CACHE + if ( CreateCache ) +#endif + for ( int32 i = STREAMED_SOUND_MISSION_MOBR1; 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 + { + m_bInitialised = FALSE; + Terminate(); + return FALSE; + } + } + + // Find path of MP3s (originally in CD-Rom) + // if NO_CDCHECK is NOT defined but AUDIO_CACHE is defined, we still need to find MP3s' path, but will exit after the first file +#ifndef NO_CDCHECK + int32 drive = 'C'; + do + { + latter[0] = drive; + latter[1] = '\0'; + + strcpy(m_szCDRomRootPath, latter); + strcat(m_szCDRomRootPath, ":"); + strcat(m_MP3FilesPath, m_szCDRomRootPath); +#else + m_MP3FilesPath[0] = '\0'; + { +#endif + + for (int32 i = 0; i < STREAMED_SOUND_MISSION_MOBR1; i++) + { +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, m_MP3FilesPath); + strcat(filepath, PS2StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + + if ( !mp3Stream[0] ) +#endif + { + strcpy(filepath, m_MP3FilesPath); + 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; + + bFileNotFound = FALSE; +#ifdef AUDIO_CACHE + if (!CreateCache) + break; + else +#endif + nStreamLength[i] = tatalms; + + } + else + { + bFileNotFound = TRUE; + break; + } + } + +#ifndef NO_CDCHECK + if (!bFileNotFound) // otherwise try next drive + break; + + } + while (++drive <= 'Z'); +#else + } +#endif + + if ( !bFileNotFound ) { + +#ifdef AUDIO_CACHE + if ( CreateCache ) +#endif + for ( int32 i = STREAMED_SOUND_MISSION_COMPLETED4; i < STREAMED_SOUND_MISSION_PAGER; i++ ) + { +#ifdef PS2_AUDIO_PATHS + strcpy(filepath, m_MiscomPath); + strcat(filepath, PS2StreamedNameTable[i]); + + mp3Stream[0] = AIL_open_stream(DIG, filepath, 0); + + if ( !mp3Stream[0] ) +#endif + { + strcpy(filepath, m_MiscomPath); + 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; + bFileNotFound = FALSE; + } + else + { + bFileNotFound = TRUE; + break; + } + } + } + + m_bInitialised = !bFileNotFound; + + if ( !m_bInitialised ) + { +#if !defined(GTA3_STEAM_PATCH) && !defined(NO_CDCHECK) + FrontEndMenuManager.WaitForUserCD(); + if ( FrontEndMenuManager.m_bQuitGameNoCD ) + { + Terminate(); + return FALSE; + } + continue; +#else + m_bInitialised = TRUE; +#endif + } + + break; + } + } + +#ifdef AUDIO_CACHE + if (CreateCache) { + cacheFile = fcaseopen("audio\\sound.cache", "wb"); + fwrite(nStreamLength, sizeof(uint32), TOTAL_STREAMED_SOUNDS, cacheFile); + fclose(cacheFile); + } +#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 FIX_BUGS + // Find biggest player comment + uint32 nMaxPedSize = 0; + for (uint32 i = PLAYER_COMMENTS_START; i <= PLAYER_COMMENTS_END; i++) + nMaxPedSize = Max(nMaxPedSize, m_aSamples[i].nSize); + + gPlayerTalkData = AIL_mem_alloc_lock(nMaxPedSize); + if ( !gPlayerTalkData ) + { + Terminate(); + return FALSE; + } +#endif + + LoadSampleBank(SFX_BANK_0); + + 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(strupr(providers[n].name), "DIRECTSOUND3D SOFTWARE EMULATION") ) + { + set_new_provider(n); + break; + } + n++; + } + + if ( n == m_nNumberOfProviders ) + { + Terminate(); + return FALSE; + } +#endif + } + + // 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; + } + +#ifdef FIX_BUGS + if ( gPlayerTalkData != 0) + { + AIL_mem_free_lock(gPlayerTalkData); + gPlayerTalkData = 0; + } +#endif + + if ( DIG ) + { + AIL_close_digital_driver(DIG); + DIG = NULL; + } + + AIL_shutdown(); + + _bSampmanInitialised = FALSE; +} + +bool8 +cSampleManager::CheckForAnAudioFileOnCD(void) +{ +#if !defined(NO_CDCHECK) // TODO: check steam, probably GTAVC_STEAM_PATCH needs to be added + char filepath[MAX_PATH]; + FILE *f; + + strcpy(filepath, m_MiscomPath); + strcat(filepath, StreamedNameTable[STREAMED_SOUND_MISSION_COMPLETED4]); + + FILE *f = fopen(filepath, "rb"); + + if ( f ) + { + fclose(f); + DMAudio.SetMusicMasterVolume(FrontEndMenuManager.m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(FrontEndMenuManager.m_PrefsSfxVolume); + DMAudio.Service(); + + return TRUE; + } + + DMAudio.SetMusicMasterVolume(0); + DMAudio.SetEffectsMasterVolume(0); + DMAudio.Service(); + + return FALSE; + +#else + return TRUE; +#endif // #if !defined(NO_CDCHECK) +} + +char +cSampleManager::GetCDAudioDriveLetter(void) +{ + if ( strlen(m_MiscomPath) != 0 ) + return m_MiscomPath[0]; + else + return '\0'; +} + +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::SetMP3BoostVolume(uint8 nVolume) +{ + m_nMP3BoostVolume = 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]; +} + +#ifdef FIX_BUGS +uint8 +cSampleManager::IsMissionAudioLoaded(uint8 nSlot, uint32 nSample) +{ + ASSERT(nSlot == MISSION_AUDIO_PLAYER_COMMENT); // only MISSION_AUDIO_PLAYER_COMMENT is supported on PC + + return nSample == gPlayerTalkSfx ? LOADING_STATUS_LOADED : LOADING_STATUS_NOT_LOADED; +} + +bool8 +cSampleManager::LoadMissionAudio(uint8 nSlot, uint32 nSample) +{ + ASSERT(nSlot == MISSION_AUDIO_PLAYER_COMMENT); // only MISSION_AUDIO_PLAYER_COMMENT is supported on PC + ASSERT(nSample < TOTAL_AUDIO_SAMPLES); + + if (fseek(fpSampleDataHandle, m_aSamples[nSample].nOffset, SEEK_SET) != 0) + return FALSE; + + if (fread(gPlayerTalkData, 1, m_aSamples[nSample].nSize, fpSampleDataHandle) != m_aSamples[nSample].nSize) + return FALSE; + + gPlayerTalkSfx = nSample; + + return TRUE; +} +#endif + +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; + } + } + } + + 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; + + float fRatio = 0.0f; + +#ifdef AUDIO_REFLECTIONS +#define MIN_DIST 0.5f +#define CALCULATE_RATIO(value, maxDist, maxRatio) (value > MIN_DIST && value < maxDist ? value / maxDist * maxRatio : 0) + + fRatio += CALCULATE_RATIO(AudioManager.m_afReflectionsDistances[REFLECTION_CEIL_NORTH], 10.0f, 1/2.f); + fRatio += CALCULATE_RATIO(AudioManager.m_afReflectionsDistances[REFLECTION_CEIL_SOUTH], 10.0f, 1/2.f); + fRatio += CALCULATE_RATIO(AudioManager.m_afReflectionsDistances[REFLECTION_CEIL_WEST], 10.0f, 1/2.f); + fRatio += CALCULATE_RATIO(AudioManager.m_afReflectionsDistances[REFLECTION_CEIL_EAST], 10.0f, 1/2.f); + + fRatio += CALCULATE_RATIO((AudioManager.m_afReflectionsDistances[REFLECTION_NORTH] + AudioManager.m_afReflectionsDistances[REFLECTION_SOUTH]) / 2.f, 4.0f, 1/3.f); + fRatio += CALCULATE_RATIO((AudioManager.m_afReflectionsDistances[REFLECTION_WEST] + AudioManager.m_afReflectionsDistances[REFLECTION_EAST]) / 2.f, 4.0f, 1/3.f); + +#undef CALCULATE_RATIO +#undef MIN_DIST +#endif + + fRatio = Clamp(fRatio, 0.0f, 0.6f); + + if ( fRatio == _fPrevEaxRatioDestination ) + return FALSE; + + if ( usingEAX3 ) + { + fRatio = Min(fRatio * 1.67f, 1.0f); + if ( EAX3ListenerInterpolate(&StartEAX3, &FinishEAX3, fRatio, &EAX3Params, false) ) + { + AIL_set_3D_provider_preference(opened_provider, "EAX all parameters", &EAX3Params); + _fEffectsLevel = fRatio * 0.75f; + } + } + else + { + if ( _usingMilesFast2D ) + _fEffectsLevel = fRatio * 0.8f; + else + _fEffectsLevel = fRatio * 0.22f; + } + _fEffectsLevel = Min(_fEffectsLevel, 1.0f); + + _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; + } +#ifdef FIX_BUGS + else if ( nSfx >= PLAYER_COMMENTS_START && nSfx <= PLAYER_COMMENTS_END ) + { + if ( !IsMissionAudioLoaded(MISSION_AUDIO_PLAYER_COMMENT, nSfx) ) + return FALSE; + + addr = (uintptr)gPlayerTalkData; + } +#endif + 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 ) { + if (MusicManager.GetCurrentTrack() == STREAMED_SOUND_CUTSCENE_FINALE) + nChannelVolume[nChannel] = 0; + else + 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.GetCurrentTrack() != STREAMED_SOUND_CUTSCENE_FINALE ) + { + 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(uint32 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, nFile < STREAMED_SOUND_MISSION_COMPLETED4 ? m_MP3FilesPath : (nFile < STREAMED_SOUND_MISSION_MOBR1 ? m_MiscomPath : m_WavFilesPath)); + strcat(filepath, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filepath, 0); + + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filepath, nFile < STREAMED_SOUND_MISSION_COMPLETED4 ? m_MP3FilesPath : (nFile < STREAMED_SOUND_MISSION_MOBR1 ? m_MiscomPath : m_WavFilesPath)); + 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(uint32 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_MiscomPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_MiscomPath); + strcat(filename, StreamedNameTable[nFile]); + mp3Stream[nStream] = + AIL_open_stream(DIG, filename, 0); + } + if(mp3Stream[nStream]) { + AIL_set_stream_loop_count(mp3Stream[nStream], nStreamLoopedFlag[nStream] ? 0 : 1); + nStreamLoopedFlag[nStream] = TRUE; + 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_MiscomPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_MiscomPath); + strcat(filename, StreamedNameTable[nFile]); + mp3Stream[nStream] = + AIL_open_stream(DIG, filename, 0); + } + if(mp3Stream[nStream]) { + AIL_set_stream_loop_count( + mp3Stream[nStream], nStreamLoopedFlag[nStream] ? 0 : 1); + nStreamLoopedFlag[nStream] = TRUE; + 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_MiscomPath); + strcat(filename, PS2StreamedNameTable[nFile]); + + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + + if ( !mp3Stream[nStream] ) +#endif + { + strcpy(filename, m_MiscomPath); + strcat(filename, StreamedNameTable[nFile]); + mp3Stream[nStream] = AIL_open_stream(DIG, filename, 0); + } + + if ( mp3Stream[nStream] ) + { + AIL_set_stream_loop_count(mp3Stream[nStream], nStreamLoopedFlag[nStream] ? 0 : 1); + nStreamLoopedFlag[nStream] = TRUE; + 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; + float boostMult = 0.0f; + + if ( m_bInitialised ) + { + if ( vol > MAX_VOLUME ) vol = MAX_VOLUME; + + if ( MusicManager.GetRadioInCar() == USERTRACK && !MusicManager.CheckForMusicInterruptions() ) + boostMult = m_nMP3BoostVolume / 64.f; + + nStreamVolume[nStream] = vol; + nStreamPan[nStream] = nPan; + + if ( mp3Stream[nStream] ) + { + if ( nEffectFlag ) + { + if ( nStream == 1 || nStream == 2 ) + AIL_set_stream_volume(mp3Stream[nStream], 128*vol*m_nEffectsVolume >> 14); + else + AIL_set_stream_volume(mp3Stream[nStream], m_nEffectsFadeVolume*vol*m_nEffectsVolume >> 14); + } + else + AIL_set_stream_volume(mp3Stream[nStream], (m_nMusicFadeVolume*vol*(uint32)(m_nMusicVolume * boostMult + 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 ( uint32 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; +} + + +void +cSampleManager::SetStreamedFileLoopFlag(bool8 nLoopFlag, uint8 nChannel) +{ + if (m_bInitialised) + nStreamLoopedFlag[nChannel] = nLoopFlag; +} + +#endif diff --git a/src/miami/audio/sampman_null.cpp b/src/miami/audio/sampman_null.cpp new file mode 100644 index 00000000..473191ee --- /dev/null +++ b/src/miami/audio/sampman_null.cpp @@ -0,0 +1,403 @@ +#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::SetMP3BoostVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetEffectsFadeVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetMusicFadeVolume(uint8 nVolume) +{ +} + +void +cSampleManager::SetMonoMode(bool8 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::IsMissionAudioLoaded(uint8 nSlot, uint32 nSample) +{ + ASSERT(nSlot < MISSION_AUDIO_COUNT); + + return LOADING_STATUS_NOT_LOADED; +} + +bool8 +cSampleManager::LoadMissionAudio(uint8 nSlot, uint32 nSample) +{ + ASSERT(nSlot < MISSION_AUDIO_COUNT); + + return FALSE; +} + +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(uint32 nFile, uint8 nStream, uint32_t) +{ + 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(uint32 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, bool8 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; +} + +void +cSampleManager::SetStreamedFileLoopFlag(bool8 nLoopFlag, uint8 nChannel) +{ +} + +int8 cSampleManager::AutoDetect3DProviders() +{ + return -1; +} + +#endif diff --git a/src/miami/audio/sampman_oal.cpp b/src/miami/audio/sampman_oal.cpp new file mode 100644 index 00000000..9cf30832 --- /dev/null +++ b/src/miami/audio/sampman_oal.cpp @@ -0,0 +1,2082 @@ +//#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; + +#ifdef FIX_BUGS +uint32 gPlayerTalkSfx = UINT32_MAX; +void *gPlayerTalkData = 0; +#endif + +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]; +bool8 nStreamLoopedFlag[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_KCHAT || track == STREAMED_SOUND_RADIO_VCPR || track == STREAMED_SOUND_RADIO_POLICE; +} + +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; +} + +int8 +cSampleManager::AutoDetect3DProviders() +{ + if (!AudioManager.IsAudioInitialised()) + return -1; + + if (defaultProvider >= 0 && defaultProvider < m_nNumberOfProviders) { + if (set_new_provider(defaultProvider)) + return defaultProvider; + } + + for (uint32 i = 0; i < GetNum3DProvidersAvailable(); i++) + { + char* providername = Get3DProviderName(i); + + if (!strcasecmp(providername, "OPENAL SOFT")) { + SetCurrent3DProvider(i); + if (GetCurrent3DProviderIndex() == i) + return i; + } + } + + return -1; +} + +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); + +#ifdef FIX_BUGS + // Find biggest player comment + uint32 nMaxPedSize = 0; + for (uint32 i = PLAYER_COMMENTS_START; i <= PLAYER_COMMENTS_END; i++) + nMaxPedSize = Max(nMaxPedSize, m_aSamples[i].nSize); + + gPlayerTalkData = malloc(nMaxPedSize); + ASSERT(gPlayerTalkData != 0); +#endif + + 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; + } + +#ifdef FIX_BUGS + if ( gPlayerTalkData != 0 ) + { + free(gPlayerTalkData); + gPlayerTalkData = 0; + } +#endif + + _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::SetMP3BoostVolume(uint8 nVolume) +{ + m_nMP3BoostVolume = 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) +{ + 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]; +} + +#ifdef FIX_BUGS +uint8 +cSampleManager::IsMissionAudioLoaded(uint8 nSlot, uint32 nSample) +{ + ASSERT(nSlot == MISSION_AUDIO_PLAYER_COMMENT); // only MISSION_AUDIO_PLAYER_COMMENT is supported on PC + + return nSample == gPlayerTalkSfx ? LOADING_STATUS_LOADED : LOADING_STATUS_NOT_LOADED; +} + +bool8 +cSampleManager::LoadMissionAudio(uint8 nSlot, uint32 nSample) +{ + ASSERT(nSlot == MISSION_AUDIO_PLAYER_COMMENT); // only MISSION_AUDIO_PLAYER_COMMENT is supported on PC + ASSERT(nSample < TOTAL_AUDIO_SAMPLES); + + if (fseek(fpSampleDataHandle, m_aSamples[nSample].nOffset, SEEK_SET) != 0) + return FALSE; + + if (fread(gPlayerTalkData, 1, m_aSamples[nSample].nSize, fpSampleDataHandle) != m_aSamples[nSample].nSize) + return FALSE; + + gPlayerTalkSfx = nSample; + + return TRUE; +} +#endif + +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; + } + } + } + +#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[SAMPLEBANK_PED] + 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; + + float fRatio = 0.0f; + +#ifdef AUDIO_REFLECTIONS +#define MIN_DIST 0.5f +#define CALCULATE_RATIO(value, maxDist, maxRatio) (value > MIN_DIST && value < maxDist ? value / maxDist * maxRatio : 0) + + fRatio += CALCULATE_RATIO(AudioManager.m_afReflectionsDistances[REFLECTION_CEIL_NORTH], 10.0f, 1/2.f); + fRatio += CALCULATE_RATIO(AudioManager.m_afReflectionsDistances[REFLECTION_CEIL_SOUTH], 10.0f, 1/2.f); + fRatio += CALCULATE_RATIO(AudioManager.m_afReflectionsDistances[REFLECTION_CEIL_WEST], 10.0f, 1/2.f); + fRatio += CALCULATE_RATIO(AudioManager.m_afReflectionsDistances[REFLECTION_CEIL_EAST], 10.0f, 1/2.f); + + fRatio += CALCULATE_RATIO((AudioManager.m_afReflectionsDistances[REFLECTION_NORTH] + AudioManager.m_afReflectionsDistances[REFLECTION_SOUTH]) / 2.f, 4.0f, 1/3.f); + fRatio += CALCULATE_RATIO((AudioManager.m_afReflectionsDistances[REFLECTION_WEST] + AudioManager.m_afReflectionsDistances[REFLECTION_EAST]) / 2.f, 4.0f, 1/3.f); + +#undef CALCULATE_RATIO +#undef MIN_DIST +#endif + + fRatio = Clamp(fRatio, 0.0f, 0.6f); + + if ( fRatio == _fPrevEaxRatioDestination ) + return FALSE; + +#ifdef JUICY_OAL + if ( usingEAX3 || _usingEFX ) +#else + if ( usingEAX3 ) +#endif + { + fRatio = Min(fRatio * 1.67f, 1.0f); + 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 = fRatio * 0.75f; + } + } + else + { + if ( _usingEFX ) + _fEffectsLevel = fRatio * 0.8f; + else + _fEffectsLevel = fRatio * 0.22f; + } + _fEffectsLevel = Min(_fEffectsLevel, 1.0f); + + _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; + } +#ifdef FIX_BUGS + else if ( nSfx >= PLAYER_COMMENTS_START && nSfx <= PLAYER_COMMENTS_END ) + { + if ( !IsMissionAudioLoaded(MISSION_AUDIO_PLAYER_COMMENT, nSfx) ) + return FALSE; + + addr = (uintptr)gPlayerTalkData; + } +#endif + 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; + + if (MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE ) { + if (MusicManager.GetCurrentTrack() == STREAMED_SOUND_CUTSCENE_FINALE) + nChannelVolume[nChannel] = 0; + else + nChannelVolume[nChannel] >>= 2; + } + + // 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; + + // increase the volume for JB.MP3 and S4_BDBD.MP3 + if (MusicManager.GetMusicMode() == MUSICMODE_CUTSCENE ) { + if (MusicManager.GetCurrentTrack() == STREAMED_SOUND_CUTSCENE_FINALE) + nChannelVolume[nChannel] = 0; + else + nChannelVolume[nChannel] >>= 2; + } + + 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(uint32 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(uint32 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 + { + // 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; + + 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() ) { + stream->SetLoopCount(nStreamLoopedFlag[nStream] ? 0 : 1); + nStreamLoopedFlag[nStream] = TRUE; + 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()) { + stream->SetLoopCount(nStreamLoopedFlag[nStream] ? 0 : 1); + nStreamLoopedFlag[nStream] = TRUE; + 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; + } + strcpy(filename, StreamedNameTable[nFile]); + + 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() ) { + stream->SetLoopCount(nStreamLoopedFlag[nStream] ? 0 : 1); + nStreamLoopedFlag[nStream] = TRUE; + 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, bool8 nEffectFlag, uint8 nStream) +{ + ASSERT( nStream < MAX_STREAMS ); + + float boostMult = 0.0f; + + if ( nVolume > MAX_VOLUME ) + nVolume = MAX_VOLUME; + + if ( nPan > MAX_VOLUME ) + nPan = MAX_VOLUME; + + if ( MusicManager.GetRadioInCar() == USERTRACK && !MusicManager.CheckForMusicInterruptions() ) + boostMult = m_nMP3BoostVolume / 64.f; + + nStreamVolume[nStream] = nVolume; + nStreamPan [nStream] = nPan; + + CStream *stream = aStream[nStream]; + + if ( stream->IsOpened() ) + { + if ( nEffectFlag ) { + if ( nStream == 1 || nStream == 2 ) + stream->SetVolume(128*nVolume*m_nEffectsVolume >> 14); + else + stream->SetVolume(m_nEffectsFadeVolume*nVolume*m_nEffectsVolume >> 14); + } + else + stream->SetVolume((m_nMusicFadeVolume*nVolume*(uint32)(m_nMusicVolume * boostMult + 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 ( uint32 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; +} + +void +cSampleManager::SetStreamedFileLoopFlag(bool8 nLoopFlag, uint8 nChannel) +{ + nStreamLoopedFlag[nChannel] = nLoopFlag; +} + +#endif diff --git a/src/miami/audio/soundlist.h b/src/miami/audio/soundlist.h new file mode 100644 index 00000000..c6dbb634 --- /dev/null +++ b/src/miami/audio/soundlist.h @@ -0,0 +1,282 @@ +#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_CAR_JUMP_2, + SOUND_CAR_TYRE_POP, + SOUND_16, + SOUND_17, + 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_HELI_BLADE, + SOUND_32, + SOUND_STEP_START, + SOUND_STEP_END, + SOUND_FALL_LAND, + SOUND_FALL_COLLAPSE, + SOUND_FIGHT_37, + SOUND_FIGHT_38, + SOUND_FIGHT_39, + SOUND_FIGHT_40, + SOUND_FIGHT_41, + SOUND_FIGHT_42, + SOUND_FIGHT_43, + SOUND_FIGHT_44, + SOUND_FIGHT_45, + SOUND_FIGHT_46, + SOUND_FIGHT_47, + SOUND_FIGHT_48, + SOUND_49, + SOUND_WEAPON_BAT_ATTACK, + SOUND_WEAPON_KNIFE_ATTACK, + SOUND_WEAPON_CHAINSAW_IDLE, + SOUND_WEAPON_CHAINSAW_ATTACK, + SOUND_WEAPON_CHAINSAW_MADECONTACT, + SOUND_WEAPON_SHOT_FIRED, + SOUND_WEAPON_RELOAD, + SOUND_WEAPON_AK47_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_70, + SOUND_71, + 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_80, + SOUND_81, + 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_91, + 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_BURNING, + SOUND_PED_PLAYER_REACTTOCOP, + SOUND_PED_ARREST_COP, + SOUND_PED_MIAMIVICE_EXITING_CAR, + SOUND_PED_COP_HELIPILOTPHRASE, + SOUND_PED_PULLOUTWEAPON, + SOUND_PED_HELI_PLAYER_FOUND, + SOUND_PED_VCPA_PLAYER_FOUND, + SOUND_PED_ON_FIRE, + SOUND_PED_AIMING, + 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_ACCIDENTREACTION1, + SOUND_PED_INNOCENT, + SOUND_PED_PLAYER_AFTERSEX, + SOUND_PED_PLAYER_BEFORESEX, + SOUND_PED_COP_TARGETING, // also used for medics + SOUND_PED_COP_MANYCOPSAROUND, // also used for medics + SOUND_PED_GUNAIMEDAT2, + SOUND_PED_COP_ALONE, // also used for medics + SOUND_PED_GUNAIMEDAT3, + SOUND_PED_COP_ASK_FOR_ID, + SOUND_PED_COP_LITTLECOPSAROUND, // also used for medics + SOUND_PED_PLAYER_FARFROMCOPS, // also used for medics + SOUND_PED_TAXI_WAIT, + SOUND_PED_ATTACK, + SOUND_PED_DEFEND, + SOUND_PED_HEALING, + SOUND_PED_LEAVE_VEHICLE, + SOUND_PED_EVADE, + SOUND_PED_FLEE_RUN, + SOUND_PED_CRASH_VEHICLE, + SOUND_PED_CRASH_CAR, + SOUND_PED_ANNOYED_DRIVER, + SOUND_PED_147, + SOUND_PED_SOLICIT, + SOUND_PED_JEER, + SOUND_PED_150, + SOUND_PED_EXTINGUISHING_FIRE, + SOUND_PED_WAIT_DOUBLEBACK, + SOUND_PED_CHAT_SEXY_FEMALE, + SOUND_PED_CHAT_SEXY_MALE, + SOUND_PED_CHAT_EVENT, + SOUND_PED_PED_COLLISION, + SOUND_PED_CHAT, + SOUND_PED_TAXI_CALL, + 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, // same sound as SOUND_HUD + + // TODO(Miami): What are 170-175?? + + SOUND_FRONTEND_RADIO_TURN_OFF = 176, // those 2 are same sound + SOUND_FRONTEND_RADIO_TURN_ON, + SOUND_FRONTEND_HURRICANE, // yes, frontend + SOUND_HUD, + SOUND_180, + SOUND_181, + SOUND_182, + SOUND_LIGHTNING, + SOUND_BULLETTRACE_1, + SOUND_BULLETTRACE_2, + SOUND_186, // makes same sound as 40 + SOUND_187, // makes same sound as 46 + SOUND_MELEE_ATTACK_START, + SOUND_SKATING, + SOUND_WEAPON_MINIGUN_ATTACK, + SOUND_WEAPON_MINIGUN_2, + SOUND_WEAPON_MINIGUN_3, + SOUND_AMMUNATION_IMRAN_ARM_BOMB, + SOUND_RADIO_CHANGE, + SOUND_FRONTEND_HIGHLIGHT_OPTION, + SOUND_FRONTEND_ENTER_OR_ADJUST, + SOUND_FRONTEND_BACK, + SOUND_FRONTEND_FAIL, + SOUND_FRONTEND_AUDIO_TEST, + SOUND_INJURED_PED_MALE_OUCH, + SOUND_INJURED_PED_FEMALE, + SOUND_SHIRT_WIND_FLAP, + SOUND_SET_203, + SOUND_TOTAL_SOUNDS = 204, + SOUND_NO_SOUND = 205, +}; + + +enum eScriptSounds { + SCRIPT_SOUND_BANK_ALARM_LOOP = 0, + SCRIPT_SOUND_PART_MISSION_COMPLETE, + SCRIPT_SOUND_POLICE_CELL_DOOR_SLIDING_LOOP, + SCRIPT_SOUND_POLICE_CELL_DOOR_CLUNK, + SCRIPT_SOUND_GARAGE_DOOR_SLIDING_LOOP, + SCRIPT_SOUND_GARAGE_DOOR_CLUNK, + SCRIPT_SOUND_SNORING_LOOP, + SCRIPT_SOUND_RACE_START_3, + SCRIPT_SOUND_RACE_START_2, + SCRIPT_SOUND_RACE_START_1, + SCRIPT_SOUND_RACE_START_GO, + SCRIPT_SOUND_SHOOTING_RANGE_TARGET_MOVING_LOOP, + SCRIPT_SOUND_SHOOTING_RANGE_TARGET_HIT, + SCRIPT_SOUND_AMMUNATION_BUY_WEAPON, + SCRIPT_SOUND_AMMUNATION_BUY_WEAPON_DENIED, + SCRIPT_SOUND_WMYCW_TICKET_SPEECH, + SCRIPT_SOUND_IMRAN_ARM_BOMB, + SCRIPT_SOUND_ANDY_SNIPER_SHOT, + SCRIPT_SOUND_WILLIE_CARD_SWIPE, + SCRIPT_SOUND_MALE_AMBULANCE_OUCH, + SCRIPT_SOUND_FEMALE_AMBULANCE_OUCH, + SCRIPT_SOUND_BUILDING_BAR_1, + SCRIPT_SOUND_BUILDING_BAR_2, + SCRIPT_SOUND_BUILDING_BAR_3, + SCRIPT_SOUND_BUILDING_BAR_4, + SCRIPT_SOUND_BUILDING_BIKER_BAR, + SCRIPT_SOUND_BUILDING_CHURCH, + SCRIPT_SOUND_BUILDING_CLUB, + SCRIPT_SOUND_BUILDING_CUBA_1, + SCRIPT_SOUND_BUILDING_CUBA_2, + SCRIPT_SOUND_BUILDING_VOODOO, + SCRIPT_SOUND_BUILDING_MUSIC_SHOP, + SCRIPT_SOUND_BUILDING_STRIPCLUB_1, + SCRIPT_SOUND_BUILDING_STRIPCLUB_2, + SCRIPT_SOUND_BUILDING_SUPERSWEEP, + SCRIPT_SOUND_SEAPLANE_LOW_FUEL, + SCRIPT_SOUND_NEW_BUILDING_BAR_1, + SCRIPT_SOUND_NEW_BUILDING_BAR_2, + SCRIPT_SOUND_NEW_BUILDING_BAR_3, + SCRIPT_SOUND_NEW_BUILDING_BAR_4, + SCRIPT_SOUND_NEW_BUILDING_MALIBU_1, + SCRIPT_SOUND_NEW_BUILDING_MALIBU_2, + SCRIPT_SOUND_NEW_BUILDING_MALIBU_3, + SCRIPT_SOUND_NEW_BUILDING_STRIP_1, + SCRIPT_SOUND_NEW_BUILDING_STRIP_2, + SCRIPT_SOUND_NEW_BUILDING_STRIP_3, + SCRIPT_SOUND_NEW_BUILDING_CHURCH, + SCRIPT_SOUND_NEW_BUILDING_FAN_1, + SCRIPT_SOUND_NEW_BUILDING_FAN_2, + SCRIPT_SOUND_NEW_BUILDING_INSECT_1, + SCRIPT_SOUND_NEW_BUILDING_INSECT_2, + SCRIPT_SOUND_NEW_WATERFALL, + 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_PAYPHONE_RINGING, + 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_HIT_BALL, + SCRIPT_SOUND_GUNSHELL_DROP, + SCRIPT_SOUND_GUNSHELL_DROP_SOFT, + SCRIPT_SOUND_TOTAL, + SCRIPT_SOUND_INVALID, +}; diff --git a/src/miami/buildings/Building.cpp b/src/miami/buildings/Building.cpp new file mode 100644 index 00000000..92c787e5 --- /dev/null +++ b/src/miami/buildings/Building.cpp @@ -0,0 +1,44 @@ +#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); +} + +bool +IsBuildingPointerValid(CBuilding* pBuilding) +{ + if (!pBuilding) + return false; + if (pBuilding->GetIsATreadable()) { + int index = CPools::GetTreadablePool()->GetJustIndex_NoFreeAssert((CTreadable*)pBuilding); +#ifdef FIX_BUGS + return index >= 0 && index < CPools::GetTreadablePool()->GetSize(); +#else + return index >= 0 && index <= CPools::GetTreadablePool()->GetSize(); +#endif + } else { + int index = CPools::GetBuildingPool()->GetJustIndex_NoFreeAssert(pBuilding); +#ifdef FIX_BUGS + return index >= 0 && index < CPools::GetBuildingPool()->GetSize(); +#else + return index >= 0 && index <= CPools::GetBuildingPool()->GetSize(); +#endif + } +} diff --git a/src/miami/buildings/Building.h b/src/miami/buildings/Building.h new file mode 100644 index 00000000..f8ddfa46 --- /dev/null +++ b/src/miami/buildings/Building.h @@ -0,0 +1,20 @@ +#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; } +}; + +bool IsBuildingPointerValid(CBuilding*); diff --git a/src/miami/buildings/Solid.h b/src/miami/buildings/Solid.h new file mode 100644 index 00000000..4ca800c2 --- /dev/null +++ b/src/miami/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/miami/buildings/Treadable.cpp b/src/miami/buildings/Treadable.cpp new file mode 100644 index 00000000..d84603a6 --- /dev/null +++ b/src/miami/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/miami/buildings/Treadable.h b/src/miami/buildings/Treadable.h new file mode 100644 index 00000000..6a183c63 --- /dev/null +++ b/src/miami/buildings/Treadable.h @@ -0,0 +1,12 @@ +#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(); + + bool GetIsATreadable(void) { return true; } +}; diff --git a/src/miami/collision/ColBox.cpp b/src/miami/collision/ColBox.cpp new file mode 100644 index 00000000..53cba88b --- /dev/null +++ b/src/miami/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/miami/collision/ColBox.h b/src/miami/collision/ColBox.h new file mode 100644 index 00000000..0df55925 --- /dev/null +++ b/src/miami/collision/ColBox.h @@ -0,0 +1,22 @@ +#pragma once + +#include "SurfaceTable.h" + +struct CBox +{ + CVector min; + CVector max; + CVector GetSize(void) { return max - min; } + void Set(const CVector &min, const CVector &max) { this->min = min; this->max = max; } +}; + +struct CColBox : public CBox +{ + uint8 surface; + uint8 piece; + + void Set(const CVector &min, const CVector &max, uint8 surf, uint8 piece); + using CBox::Set; + + CColBox& operator=(const CColBox &other); +}; \ No newline at end of file diff --git a/src/miami/collision/ColLine.cpp b/src/miami/collision/ColLine.cpp new file mode 100644 index 00000000..c6247449 --- /dev/null +++ b/src/miami/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/miami/collision/ColLine.h b/src/miami/collision/ColLine.h new file mode 100644 index 00000000..21587a06 --- /dev/null +++ b/src/miami/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/miami/collision/ColModel.cpp b/src/miami/collision/ColModel.cpp new file mode 100644 index 00000000..2224a804 --- /dev/null +++ b/src/miami/collision/ColModel.cpp @@ -0,0 +1,206 @@ +#include "common.h" +#include "ColModel.h" +#include "Collision.h" +#include "Game.h" +#include "MemoryHeap.h" +#include "Pools.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 = LEVEL_GENERIC; // generic col slot + ownsCollisionVolumes = true; +} + +CColModel::~CColModel(void) +{ + RemoveCollisionVolumes(); +} + +void* +CColModel::operator new(size_t) throw() +{ + CColModel* node = CPools::GetColModelPool()->New(); + assert(node); + return node; +} + +void +CColModel::operator delete(void *p, size_t) throw() +{ + CPools::GetColModelPool()->Delete((CColModel*)p); +} + +void +CColModel::RemoveCollisionVolumes(void) +{ + if(ownsCollisionVolumes){ + RwFree(spheres); + RwFree(lines); + RwFree(boxes); + RwFree(vertices); + RwFree(triangles); + CCollision::RemoveTrianglePlanes(this); + } + 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/miami/collision/ColModel.h b/src/miami/collision/ColModel.h new file mode 100644 index 00000000..64f05f76 --- /dev/null +++ b/src/miami/collision/ColModel.h @@ -0,0 +1,39 @@ +#pragma once + +#include "templates.h" +#include "ColBox.h" +#include "ColSphere.h" +#include "ColLine.h" +#include "ColPoint.h" +#include "ColTriangle.h" + +struct CColModel +{ + CSphere boundingSphere; + CBox boundingBox; + int16 numSpheres; + int16 numBoxes; + int16 numTriangles; + int8 numLines; + uint8 level; // colstore slot but probably still named level + bool ownsCollisionVolumes; + 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; + + void *operator new(size_t) throw(); + void operator delete(void *p, size_t) throw(); + CColModel& operator=(const CColModel& other); +}; \ No newline at end of file diff --git a/src/miami/collision/ColPoint.cpp b/src/miami/collision/ColPoint.cpp new file mode 100644 index 00000000..fbf9e8c3 --- /dev/null +++ b/src/miami/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/miami/collision/ColPoint.h b/src/miami/collision/ColPoint.h new file mode 100644 index 00000000..a15b2345 --- /dev/null +++ b/src/miami/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/miami/collision/ColSphere.cpp b/src/miami/collision/ColSphere.cpp new file mode 100644 index 00000000..65f02860 --- /dev/null +++ b/src/miami/collision/ColSphere.cpp @@ -0,0 +1,27 @@ +#include "common.h" +#include "ColSphere.h" +#include "General.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; +} + +bool +CColSphere::IntersectRay(CVector const& from, CVector const& dir, CVector &entry, CVector &exit) +{ + CVector distToCenter = from - center; + float distToTouchSqr = distToCenter.MagnitudeSqr() - sq(radius); + float root1, root2; + + if (!CGeneral::SolveQuadratic(1.0f, DotProduct(distToCenter, dir) * 2.f, distToTouchSqr, root1, root2)) + return false; + + entry = from + dir * root1; + exit = from + dir * root2; + return true; +} \ No newline at end of file diff --git a/src/miami/collision/ColSphere.h b/src/miami/collision/ColSphere.h new file mode 100644 index 00000000..f86b282a --- /dev/null +++ b/src/miami/collision/ColSphere.h @@ -0,0 +1,21 @@ +#pragma once + +#include "SurfaceTable.h" + +struct CSphere +{ + // NB: this has to be compatible with a CVuVector + CVector center; + float radius; + void Set(float radius, const CVector ¢er) { this->center = center; this->radius = radius; } +}; + +struct CColSphere : public CSphere +{ + uint8 surface; + uint8 piece; + + void Set(float radius, const CVector ¢er, uint8 surf, uint8 piece); + bool IntersectRay(CVector const &from, CVector const &dir, CVector &entry, CVector &exit); + using CSphere::Set; +}; \ No newline at end of file diff --git a/src/miami/collision/ColStore.cpp b/src/miami/collision/ColStore.cpp new file mode 100644 index 00000000..da13f01e --- /dev/null +++ b/src/miami/collision/ColStore.cpp @@ -0,0 +1,255 @@ +#include "common.h" + +#include "templates.h" +#include "General.h" +#include "ModelInfo.h" +#include "Streaming.h" +#include "FileLoader.h" +#include "Script.h" +#include "Timer.h" +#include "Camera.h" +#include "Frontend.h" +#include "Physical.h" +#include "ColStore.h" +#include "VarConsole.h" +#include "Pools.h" + +CPool *CColStore::ms_pColPool; +#ifndef MASTER +bool bDispColInMem; +#endif + +void +CColStore::Initialise(void) +{ + if(ms_pColPool == nil) + ms_pColPool = new CPool(COLSTORESIZE, "CollisionFiles"); + AddColSlot("generic"); // slot 0. not streamed +#ifndef MASTER + VarConsole.Add("Display collision in memory", &bDispColInMem, true); +#endif +} + +void +CColStore::Shutdown(void) +{ + int i; + for(i = 0; i < COLSTORESIZE; i++) + RemoveColSlot(i); + if(ms_pColPool) + delete ms_pColPool; + ms_pColPool = nil; +} + +int +CColStore::AddColSlot(const char *name) +{ + ColDef *def = ms_pColPool->New(); + assert(def); + def->isLoaded = false; + def->unused = 0; + def->bounds.left = 1000000.0f; + def->bounds.top = 1000000.0f; + def->bounds.right = -1000000.0f; + def->bounds.bottom = -1000000.0f; + def->minIndex = INT16_MAX; + def->maxIndex = INT16_MIN; + strcpy(def->name, name); + return ms_pColPool->GetJustIndex(def); +} + +void +CColStore::RemoveColSlot(int32 slot) +{ + if(GetSlot(slot)){ + if(GetSlot(slot)->isLoaded) + RemoveCol(slot); + ms_pColPool->Delete(GetSlot(slot)); + } +} + +int +CColStore::FindColSlot(const char *name) +{ + ColDef *def; + int size = ms_pColPool->GetSize(); + for(int i = 0; i < size; i++){ + def = GetSlot(i); + if(def && !CGeneral::faststricmp(def->name, name)) + return i; + } + return -1; +} + +char* +CColStore::GetColName(int32 slot) +{ + return GetSlot(slot)->name; +} + +CRect& +CColStore::GetBoundingBox(int32 slot) +{ + return GetSlot(slot)->bounds; +} + +void +CColStore::IncludeModelIndex(int32 slot, int32 modelIndex) +{ + ColDef *def = GetSlot(slot); + if(modelIndex < def->minIndex) + def->minIndex = modelIndex; + if(modelIndex > def->maxIndex) + def->maxIndex = modelIndex; +} + +bool +CColStore::LoadCol(int32 slot, uint8 *buffer, int32 bufsize) +{ + bool success; + ColDef *def = GetSlot(slot); + if(def->minIndex > def->maxIndex) + success = CFileLoader::LoadCollisionFileFirstTime(buffer, bufsize, slot); + else + success = CFileLoader::LoadCollisionFile(buffer, bufsize, slot); + if(success) + def->isLoaded = true; + else + debug("Failed to load Collision\n"); + return success; +} + +void +CColStore::RemoveCol(int32 slot) +{ + int id; + GetSlot(slot)->isLoaded = false; + for(id = 0; id < MODELINFOSIZE; id++){ + CBaseModelInfo *mi = CModelInfo::GetModelInfo(id); + if(mi){ + CColModel *col = mi->GetColModel(); + if(col && col->level == slot) + col->RemoveCollisionVolumes(); + } + } +} + +void +CColStore::LoadAllCollision(void) +{ + int i; + for(i = 1; i < COLSTORESIZE; i++) + if(GetSlot(i)) + CStreaming::RequestCol(i, 0); + + CStreaming::LoadAllRequestedModels(false); +} + +void +CColStore::RemoveAllCollision(void) +{ + int i; + for(i = 1; i < COLSTORESIZE; i++) + if(GetSlot(i)) + if(CStreaming::CanRemoveCol(i)) + CStreaming::RemoveCol(i); +} + +static bool bLoadAtSecondPosition; +static CVector2D secondPosition; + +void +CColStore::AddCollisionNeededAtPosn(const CVector2D &pos) +{ + bLoadAtSecondPosition = true; + secondPosition = pos; +} + +void +CColStore::LoadCollision(const CVector2D &pos) +{ + int i; + + if(CStreaming::ms_disableStreaming) + return; + + for(i = 1; i < COLSTORESIZE; i++){ + if(GetSlot(i) == nil) + continue; + + bool wantThisOne = false; + + if(GetBoundingBox(i).IsPointInside(pos) || + bLoadAtSecondPosition && GetBoundingBox(i).IsPointInside(secondPosition, -119.0f) || + strcmp(GetColName(i), "yacht") == 0){ + wantThisOne = true; + }else{ + for (int j = 0; j < MAX_CLEANUP; j++) { + CPhysical* pEntity = nil; + cleanup_entity_struct* pCleanup = &CTheScripts::MissionCleanUp.m_sEntities[j]; + if (pCleanup->type == CLEANUP_CAR) { + pEntity = CPools::GetVehiclePool()->GetAt(pCleanup->id); + if (!pEntity || pEntity->GetStatus() == STATUS_WRECKED) + continue; + } + else if (pCleanup->type == CLEANUP_CHAR) { + pEntity = CPools::GetPedPool()->GetAt(pCleanup->id); + if (!pEntity || ((CPed*)pEntity)->DyingOrDead()) + continue; + } + if (pEntity && !pEntity->bDontLoadCollision && !pEntity->bIsFrozen) { + if (GetBoundingBox(i).IsPointInside(pEntity->GetPosition(), -80.0f)) + wantThisOne = true; + } + } + } + + if(wantThisOne) + CStreaming::RequestCol(i, STREAMFLAGS_PRIORITY); + else + CStreaming::RemoveCol(i); + } + bLoadAtSecondPosition = false; +} + +void +CColStore::RequestCollision(const CVector2D &pos) +{ + int i; + + for(i = 1; i < COLSTORESIZE; i++) + if(GetSlot(i) && GetBoundingBox(i).IsPointInside(pos, -115.0f)) + CStreaming::RequestCol(i, STREAMFLAGS_PRIORITY); +} + +void +CColStore::EnsureCollisionIsInMemory(const CVector2D &pos) +{ + int i; + + if(CStreaming::ms_disableStreaming) + return; + + for(i = 1; i < COLSTORESIZE; i++) + if(GetSlot(i) && GetBoundingBox(i).IsPointInside(pos, -110.0f) && + !CStreaming::HasColLoaded(i)){ + CStreaming::RequestCol(i, 0); + if(TheCamera.GetScreenFadeStatus() == FADE_0) + FrontEndMenuManager.MessageScreen("LOADCOL", false); + CTimer::Suspend(); + CStreaming::LoadAllRequestedModels(false); + CTimer::Resume(); + } +} + +bool +CColStore::HasCollisionLoaded(const CVector2D &pos) +{ + int i; + + for(i = 1; i < COLSTORESIZE; i++) + if(GetSlot(i) && GetBoundingBox(i).IsPointInside(pos, -115.0f) && + !GetSlot(i)->isLoaded) + return false; + return true; +} diff --git a/src/miami/collision/ColStore.h b/src/miami/collision/ColStore.h new file mode 100644 index 00000000..8e2a3a70 --- /dev/null +++ b/src/miami/collision/ColStore.h @@ -0,0 +1,43 @@ +#pragma once + +#include "templates.h" + +struct ColDef { // made up name + int32 unused; + bool isLoaded; + CRect bounds; + char name[20]; + int16 minIndex; + int16 maxIndex; +}; + +class CColStore +{ + static CPool *ms_pColPool; + +public: + static void Initialise(void); + static void Shutdown(void); + static int AddColSlot(const char *name); + static void RemoveColSlot(int32 slot); + static int FindColSlot(const char *name); + static char *GetColName(int32 slot); + static CRect &GetBoundingBox(int32 slot); + static void IncludeModelIndex(int32 slot, int32 modelIndex); + static bool LoadCol(int32 storeID, uint8 *buffer, int32 bufsize); + static void RemoveCol(int32 slot); + static void AddCollisionNeededAtPosn(const CVector2D &pos); + static void LoadAllCollision(void); + static void RemoveAllCollision(void); + static void LoadCollision(const CVector2D &pos); + static void RequestCollision(const CVector2D &pos); + static void EnsureCollisionIsInMemory(const CVector2D &pos); + static bool HasCollisionLoaded(const CVector2D &pos); + + static ColDef *GetSlot(int slot) { + assert(slot >= 0); + assert(ms_pColPool); + assert(slot < ms_pColPool->GetSize()); + return ms_pColPool->GetSlot(slot); + } +}; diff --git a/src/miami/collision/ColTriangle.cpp b/src/miami/collision/ColTriangle.cpp new file mode 100644 index 00000000..843fb93f --- /dev/null +++ b/src/miami/collision/ColTriangle.cpp @@ -0,0 +1,32 @@ +#include "common.h" +#include "ColTriangle.h" + +#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/miami/collision/ColTriangle.h b/src/miami/collision/ColTriangle.h new file mode 100644 index 00000000..5af0cf76 --- /dev/null +++ b/src/miami/collision/ColTriangle.h @@ -0,0 +1,77 @@ +#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(uint16 a, uint16 b, uint16 c, uint8 surf) + { + this->a = a; + this->b = b; + this->c = c; + this->surface = surf; + } +}; + +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 GetNormalX() const { return normal.x; } + float GetNormalY() const { return normal.y; } + float GetNormalZ() const { return normal.z; } + float CalcPoint(const CVector &v) const { return DotProduct(normal, v) - dist; }; +#endif +}; \ No newline at end of file diff --git a/src/miami/collision/Collision.cpp b/src/miami/collision/Collision.cpp new file mode 100644 index 00000000..f39f3f35 --- /dev/null +++ b/src/miami/collision/Collision.cpp @@ -0,0 +1,2590 @@ +#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 "Camera.h" +#include "ColStore.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; + CColStore::Initialise(); +} + +void +CCollision::Shutdown(void) +{ + ms_colModelCache.Shutdown(); + CColStore::Shutdown(); +} + +void +CCollision::Update(void) +{ +} + +// unused +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; +} + +// unused +// 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) +{ +} + +void +CCollision::SortOutCollisionAfterLoad(void) +{ + CColStore::LoadCollision(TheCamera.GetPosition()); + CStreaming::LoadAllRequestedModels(false); +} + +void +CCollision::LoadCollisionScreen(eLevelName level) +{ + static Const char *levelNames[] = { + "", + "IND_ZON", + "COM_ZON", + }; + + // Why twice? + LoadingIslandScreen(levelNames[level]); + LoadingIslandScreen(levelNames[level]); +} + +// +// Test +// + + +bool +CCollision::TestSphereSphere(const CSphere &s1, const CSphere &s2) +{ + float d = s1.radius + s2.radius; + return (s1.center - s2.center).MagnitudeSqr() < d*d; +} + +bool +CCollision::TestSphereBox(const CSphere &sph, const CBox &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 CBox &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 CBox &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 0: + return false; // shouldn't happen + 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, bool ignoreShootThrough) +{ +#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(ignoreShootThrough && IsShootThrough(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(ignoreShootThrough && IsShootThrough(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; + if(ignoreShootThrough && IsShootThrough(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; + if(ignoreShootThrough && IsShootThrough(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(ignoreShootThrough && IsShootThrough(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(ignoreShootThrough && IsShootThrough(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(ignoreShootThrough && IsShootThrough(model.triangles[i].surface)) continue; + if(TestLineTriangle(newline, model.vertices, model.triangles[i], model.trianglePlanes[i])) + return true; + } + + return false; +#endif +} + +// TODO: TestPillWithSpheresInColModel, but only called from overloaded CWeapon::FireMelee which isn't used + +// +// 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; +} + +// unused +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, CStoredCollPoly *poly) +{ +#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; + if(poly){ + poly->verts[0] = va; + poly->verts[1] = vb; + poly->verts[2] = vc; + poly->valid = true; + } + 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 0: + return false; // shouldn't happen + 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, bool ignoreShootThrough) +{ +#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(ignoreShootThrough && IsShootThrough(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; + if(ignoreShootThrough && IsShootThrough(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; + if(ignoreShootThrough && IsShootThrough(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; + if(ignoreShootThrough && IsShootThrough(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; + if(ignoreShootThrough && IsShootThrough(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; + if(ignoreShootThrough && IsShootThrough(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, bool ignoreShootThrough, 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 && IsSeeThroughVertical(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 && IsSeeThroughVertical(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 && IsSeeThroughVertical(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 && IsSeeThroughVertical(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)); + + if(!TestLineBox(newline, model.boundingBox)) + return false; + + // BUG? is IsSeeThroughVertical really the right thing? also not checking shoot through + float coldist = mindist; + for(i = 0; i < model.numSpheres; i++){ + if(ignoreSeeThrough && IsSeeThroughVertical(model.spheres[i].surface)) continue; + ProcessLineSphere(newline, model.spheres[i], point, coldist); + } + + for(i = 0; i < model.numBoxes; i++){ + if(ignoreSeeThrough && IsSeeThroughVertical(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 && IsSeeThroughVertical(model.triangles[i].surface)) continue; + ProcessLineTriangle(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::RemoveTrianglePlanes(CColModel *model) +{ + if(model->trianglePlanes){ + ms_colModelCache.Remove(model->GetLinkPtr()); + model->RemoveTrianglePlanes(); + } +} + +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); +} + +static void +GetSurfaceColor(uint8 surf, uint8 &r, uint8 &g, uint8 &b) +{ + // game doesn't do this + r = 255; + g = 128; + b = 0; + + switch(CSurfaceTable::GetAdhesionGroup(surf)){ + case ADHESIVE_RUBBER: + r = 255; + g = 0; + b = 0; + break; + case ADHESIVE_HARD: + r = 255; + g = 255; + b = 128; + break; + case ADHESIVE_ROAD: + r = 128; + g = 128; + b = 128; + break; + case ADHESIVE_LOOSE: + r = 0; + g = 255; + b = 0; + break; + case ADHESIVE_SAND: + r = 255; + g = 128; + b = 128; + break; + case ADHESIVE_WET: + r = 0; + g = 0; + b = 255; + break; + } + + if(surf == SURFACE_SAND || surf == SURFACE_SAND_BEACH){ + r = 255; + g = 255; + b = 0; + } + + float f = (surf & 0xF)/32.0f + 0.5f; + r *= f; + g *= f; + b *= f; + + if(surf == SURFACE_TRANSPARENT_CLOTH || surf == SURFACE_METAL_CHAIN_FENCE || + surf == SURFACE_TRANSPARENT_STONE || surf == SURFACE_SCAFFOLD_POLE) + if(CTimer::GetFrameCounter() & 1){ + r = 0; + g = 0; + b = 0; + } +} + +void +CCollision::DrawColModel_Coloured(const CMatrix &mat, const CColModel &colModel, int32 id) +{ + int i; + int s; + CVector verts[8]; + CVector min, max; + uint8 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]; + + s = colModel.triangles[i].surface; + GetSurfaceColor(s, r, g, b); + + 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; + GetSurfaceColor(s, r, g, b); + + 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/miami/collision/Collision.h b/src/miami/collision/Collision.h new file mode 100644 index 00000000..57f5f86e --- /dev/null +++ b/src/miami/collision/Collision.h @@ -0,0 +1,68 @@ +#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; + + 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); + static void RemoveTrianglePlanes(CColModel *model); + + // all these return true if there's a collision + static bool TestSphereSphere(const CSphere &s1, const CSphere &s2); + static bool TestSphereBox(const CSphere &sph, const CBox &box); + static bool TestLineBox(const CColLine &line, const CBox &box); + static bool TestVerticalLineBox(const CColLine &line, const CBox &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, bool ignoreShootThrough); + + 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, CStoredCollPoly *poly = nil); + 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, bool ignoreShootThrough); + static bool ProcessVerticalLine(const CColLine &line, const CMatrix &matrix, CColModel &model, CColPoint &point, float &mindist, bool ignoreSeeThrough, bool ignoreShootThrough, 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/miami/collision/CompressedVector.h b/src/miami/collision/CompressedVector.h new file mode 100644 index 00000000..0c2faf91 --- /dev/null +++ b/src/miami/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 SetFixed(int16 x, int16 y, int16 z) { this->x = x; this->y = y; this->z = z; }; +#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/miami/collision/TempColModels.cpp b/src/miami/collision/TempColModels.cpp new file mode 100644 index 00000000..0c0d4376 --- /dev/null +++ b/src/miami/collision/TempColModels.cpp @@ -0,0 +1,306 @@ +#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; +CColModel CTempColModels::ms_colModelWeapon; + + +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); + + ms_colModelWeapon.boundingSphere.radius = 0.25f; + ms_colModelWeapon.boundingBox.min.x = -0.25f; + ms_colModelWeapon.boundingBox.min.y = -0.25f; + ms_colModelWeapon.boundingBox.min.z = -0.25f; + ms_colModelWeapon.boundingBox.max.x = 0.25f; + ms_colModelWeapon.boundingBox.max.y = 0.25f; + ms_colModelWeapon.boundingBox.max.z = 0.25f; + +#undef SET_COLMODEL_SPHERES +} diff --git a/src/miami/collision/TempColModels.h b/src/miami/collision/TempColModels.h new file mode 100644 index 00000000..1a888723 --- /dev/null +++ b/src/miami/collision/TempColModels.h @@ -0,0 +1,24 @@ +#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 CColModel ms_colModelWeapon; + + static void Initialise(void); +}; diff --git a/src/miami/collision/VuCollision.cpp b/src/miami/collision/VuCollision.cpp new file mode 100644 index 00000000..8828d2e1 --- /dev/null +++ b/src/miami/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/miami/collision/VuCollision.h b/src/miami/collision/VuCollision.h new file mode 100644 index 00000000..29ca4cbf --- /dev/null +++ b/src/miami/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/miami/collision/vu0Collision.dsm b/src/miami/collision/vu0Collision.dsm new file mode 100644 index 00000000..657c8b81 --- /dev/null +++ b/src/miami/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/miami/collision/vu0Collision_1.s b/src/miami/collision/vu0Collision_1.s new file mode 100644 index 00000000..055c8640 --- /dev/null +++ b/src/miami/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/miami/collision/vu0Collision_2.s b/src/miami/collision/vu0Collision_2.s new file mode 100644 index 00000000..716c29ac --- /dev/null +++ b/src/miami/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/miami/control/AutoPilot.cpp b/src/miami/control/AutoPilot.cpp new file mode 100644 index 00000000..d7c17a68 --- /dev/null +++ b/src/miami/control/AutoPilot.cpp @@ -0,0 +1,136 @@ +#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); + WriteSaveBuf(buf, m_nCruiseSpeedMultiplierType); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_fCruiseSpeedMultiplier); + 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); + WriteSaveBuf(buf, m_nSwitchDistance); + 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); + ReadSaveBuf(&m_nCruiseSpeedMultiplierType, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_fCruiseSpeedMultiplier, 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)); + ReadSaveBuf(&m_nSwitchDistance, buf); + 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/miami/control/AutoPilot.h b/src/miami/control/AutoPilot.h new file mode 100644 index 00000000..ec3bb8d8 --- /dev/null +++ b/src/miami/control/AutoPilot.h @@ -0,0 +1,137 @@ +#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, + MISSION_HELI_FLYTOCOORS, + MISSION_ATTACKPLAYER, + MISSION_PLANE_FLYTOCOORS, + MISSION_HELI_LAND, + MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_1, + MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_2, + MISSION_BLOCKPLAYER_FORWARDANDBACK +}; + +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_nCruiseSpeedMultiplierType; + float m_fCruiseSpeedMultiplier; + uint8 m_bSlowedDownBecauseOfCars : 1; + uint8 m_bSlowedDownBecauseOfPeds : 1; + uint8 m_bStayInCurrentLevel : 1; + uint8 m_bStayInFastLane : 1; + uint8 m_bIgnorePathfinding : 1; + uint8 m_nSwitchDistance; + 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; + m_nCruiseSpeedMultiplierType = 0; + m_fCruiseSpeedMultiplier = 1.0f; + } + + void ModifySpeed(float); + void RemoveOnePathNode(); +#ifdef COMPATIBLE_SAVES + void Save(uint8*& buf); + void Load(uint8*& buf); +#endif + + float GetCruiseSpeed(void) { return m_nCruiseSpeed * m_fCruiseSpeedMultiplier; } + +}; + +VALIDATE_SIZE(CAutoPilot, 0x70); diff --git a/src/miami/control/Bridge.cpp b/src/miami/control/Bridge.cpp new file mode 100644 index 00000000..1e63cf30 --- /dev/null +++ b/src/miami/control/Bridge.cpp @@ -0,0 +1,163 @@ +#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() +{ +#ifdef GTA_BRIDGE + 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); + } +#endif +} + +void CBridge::Update() +{ +#ifdef GTA_BRIDGE + 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); +#endif +} + +bool CBridge::ShouldLightsBeFlashing() +{ +#ifdef GTA_BRIDGE + return State != STATE_LIFT_PART_IS_DOWN; +#else + return false; +#endif +} + +void CBridge::FindBridgeEntities() +{ +#ifdef GTA_BRIDGE + 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; + } + } +#endif +} + +bool CBridge::ThisIsABridgeObjectMovingUp(int index) +{ +#ifdef GTA_BRIDGE + if (index != MI_BRIDGEROADSEGMENT && index != MI_BRIDGELIFT) + return false; + + return State == STATE_LIFT_PART_ABOUT_TO_MOVE_UP || State == STATE_LIFT_PART_MOVING_UP; +#else + return false; +#endif +} diff --git a/src/miami/control/Bridge.h b/src/miami/control/Bridge.h new file mode 100644 index 00000000..c5702629 --- /dev/null +++ b/src/miami/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/miami/control/CarAI.cpp b/src/miami/control/CarAI.cpp new file mode 100644 index 00000000..c8230d4d --- /dev/null +++ b/src/miami/control/CarAI.cpp @@ -0,0 +1,802 @@ +#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 "Population.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 pVehicle->AutoPilot.m_nSwitchDistance; +} + +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::BackToCruisingIfNoWantedLevel(CVehicle* pVehicle) +{ + 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; + } +} + +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->m_bSirenOrAlarm = true; + } + BackToCruisingIfNoWantedLevel(pVehicle); + 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 (pVehicle->bIsLawEnforcer) + MellowOutChaseSpeed(pVehicle); + BackToCruisingIfNoWantedLevel(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->m_bSirenOrAlarm = true; + } + BackToCruisingIfNoWantedLevel(pVehicle); + break; + case MISSION_BLOCKPLAYER_CLOSE: + if (FindSwitchDistanceFar(pVehicle) >= (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude2D() || + pVehicle->AutoPilot.m_bIgnorePathfinding) { + if (FindPlayerVehicle() && FindPlayerVehicle()->GetMoveSpeed().Magnitude() < 0.04f) +#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.04f && 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 (pVehicle->bIsLawEnforcer) + MellowOutChaseSpeed(pVehicle); + BackToCruisingIfNoWantedLevel(pVehicle); + break; + case MISSION_GOTOCOORDS: + if ((pVehicle->AutoPilot.m_vecDestinationCoors - pVehicle->GetPosition()).Magnitude2D() < FindSwitchDistanceClose(pVehicle) || + 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 < 3.0f){ + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + if (pVehicle->bParking) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->bParking = false; + } + } + 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() < FindSwitchDistanceClose(pVehicle) || + 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; + if (pVehicle->bParking) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->bParking = false; + } + } + 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){ +#ifdef FIX_BUGS // btw fixed in SA + if (FindPlayerVehicle() == pVehicle->AutoPilot.m_pTargetCar) +#endif + BackToCruisingIfNoWantedLevel(pVehicle); + 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->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; + case MISSION_ATTACKPLAYER: + if (pVehicle->bIsLawEnforcer) + MellowOutChaseSpeedBoat(pVehicle); + BackToCruisingIfNoWantedLevel(pVehicle); + break; + case MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_1: + if (((CVector2D)(pVehicle->AutoPilot.m_vecDestinationCoors) - pVehicle->GetPosition()).Magnitude() < 1.5f) + pVehicle->AutoPilot.m_nCarMission = MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_2; + BackToCruisingIfNoWantedLevel(pVehicle); + break; + case MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_2: + { + float distance = ((CVector2D)FindPlayerCoors() - pVehicle->GetPosition()).Magnitude(); + if (distance < 13.0f) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_STOP_FOREVER; + } + if (distance > 70.0f || FindPlayerPed()->m_pWanted->m_bIgnoredByEveryone || + (FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 || FindPlayerPed()->m_pWanted->m_bIgnoredByCops || CCullZones::NoPolice())) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_STOP_FOREVER; + } + break; + } + case MISSION_BLOCKPLAYER_FORWARDANDBACK: + { + CVector2D diff = (CVector2D)FindPlayerCoors() - pVehicle->GetPosition(); + float distance = Max(0.001f, diff.Magnitude()); + if (!FindPlayerVehicle() || DotProduct2D(CVector2D(diff.x / distance, diff.y / distance), FindPlayerSpeed()) > 0.05f) + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_CLOSE; + BackToCruisingIfNoWantedLevel(pVehicle); + 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 = + pVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_BOAT ? FindPoliceBoatMissionForWantedLevel() : 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; + } + if (pVehicle->bIsLawEnforcer && FindPlayerPed()->m_pWanted->GetWantedLevel() >= 1 && CCullZones::PoliceAbandonCars()) { + TellOccupantsToLeaveCar(pVehicle); + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->AutoPilot.m_nCarMission = MISSION_NONE; + } + 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_nCarMission != MISSION_BLOCKPLAYER_HANDBRAKESTOP && + pVehicle->AutoPilot.m_nCruiseSpeed != 0 && + (pVehicle->VehicleCreatedBy != RANDOM_VEHICLE || pVehicle->AutoPilot.m_nCarMission != MISSION_CRUISE)){ + if (pVehicle->AutoPilot.m_nDrivingStyle != DRIVINGSTYLE_STOP_FOR_CARS + && pVehicle->AutoPilot.m_nDrivingStyle != DRIVINGSTYLE_STOP_FOR_CARS_IGNORE_LIGHTS || + pVehicle->VehicleCreatedBy == MISSION_VEHICLE + ) { + 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->bIsLawEnforcer) { + if (pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_FARAWAY || + pVehicle->AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE) { + if (FindPlayerVehicle() && FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_BIKE) + pVehicle->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_FARAWAY; + } + } + 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; + } + float target = 1.0f; + if (pVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE) + target = CCarCtrl::FindSpeedMultiplierWithSpeedFromNodes(pVehicle->AutoPilot.m_nCruiseSpeedMultiplierType); + float change = CTimer::GetTimeStep() * 0.01f; + if (Abs(pVehicle->AutoPilot.m_fCruiseSpeedMultiplier - target) < change) + pVehicle->AutoPilot.m_fCruiseSpeedMultiplier = target; + else if (pVehicle->AutoPilot.m_fCruiseSpeedMultiplier > target) + pVehicle->AutoPilot.m_fCruiseSpeedMultiplier -= change; + else + pVehicle->AutoPilot.m_fCruiseSpeedMultiplier += change; + + if (pVehicle->bIsLawEnforcer && FindPlayerPed()->m_pWanted->GetWantedLevel() > 0) { + if (!FindPlayerVehicle() || + FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_CAR || + FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_BIKE) { + if (pVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_BOAT) { + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1000; + } + } + else if (FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_BOAT) { + if (pVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_CAR || + pVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_BIKE) { + pVehicle->AutoPilot.m_nTempAction = TEMPACT_WAIT; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1000; + } + } + } +} + +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(); +} + +float CCarAI::GetCarToParkAtCoors(CVehicle* pVehicle, CVector* pTarget) +{ + GetCarToGoToCoors(pVehicle, pTarget); + pVehicle->bParking = true; + pVehicle->AutoPilot.m_nCruiseSpeed = 10; + return (pVehicle->GetPosition() - *pTarget).Magnitude2D(); +} + +void CCarAI::AddPoliceCarOccupants(CVehicle* pVehicle) +{ + if (pVehicle->bOccupantsHaveBeenGenerated) + return; + pVehicle->bOccupantsHaveBeenGenerated = true; + switch (pVehicle->GetModelIndex()){ + case MI_FBIRANCH: + 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; + case MI_PREDATOR: + pVehicle->SetUpDriver(); + return; + case MI_VICECHEE: + { + pVehicle->SetUpDriver()->bMiamiViceCop = true; + pVehicle->SetupPassenger(0)->bMiamiViceCop = true; + CPopulation::NumMiamiViceCops += 2; + CCarCtrl::MiamiViceCycle = (CCarCtrl::MiamiViceCycle + 1) % 4; + CCarCtrl::LastTimeMiamiViceGenerated = CTimer::GetTimeInMilliseconds(); + 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]->m_leaveCarTimer = timer; + pVehicle->pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + timer += CGeneral::GetRandomNumberInRange(200, 400); + } + } +} + +void CCarAI::TellOccupantsToFleeCar(CVehicle* pVehicle) +{ + if (pVehicle->pDriver && !pVehicle->pDriver->IsPlayer()) { + pVehicle->pDriver->SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + switch (pVehicle->GetModelIndex()) { + case MI_FIRETRUCK: + case MI_FBIRANCH: + 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]->m_leaveCarTimer = timer; + pVehicle->pPassengers[i]->SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + timer += CGeneral::GetRandomNumberInRange(200, 400); + } + } +} + +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; + } +} + +uint8 CCarAI::FindPoliceBoatMissionForWantedLevel() +{ + switch (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel()) { + case 0: + case 1: return MISSION_BLOCKPLAYER_FARAWAY; + case 2: + case 3: + case 4: + case 5: + case 6: return MISSION_ATTACKPLAYER; + 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; + } + } + if (!FindPlayerVehicle() && FindPlayerPed()->GetMoveSpeed().Magnitude() < 0.07f) { + if ((FindPlayerCoors() - pVehicle->GetPosition()).Magnitude() < 30.0f) + pVehicle->AutoPilot.m_nCruiseSpeed = Min(10, pVehicle->AutoPilot.m_nCruiseSpeed); + } +} + +void CCarAI::MellowOutChaseSpeedBoat(CVehicle* pVehicle) +{ + switch (CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted->GetWantedLevel()) { + case 0: pVehicle->AutoPilot.m_nCruiseSpeed = 8; break; + case 1: pVehicle->AutoPilot.m_nCruiseSpeed = 10; break; + case 2: pVehicle->AutoPilot.m_nCruiseSpeed = 15; break; + case 3: pVehicle->AutoPilot.m_nCruiseSpeed = 20; break; + case 4: pVehicle->AutoPilot.m_nCruiseSpeed = 25; break; + case 5: pVehicle->AutoPilot.m_nCruiseSpeed = 30; break; + case 6: pVehicle->AutoPilot.m_nCruiseSpeed = 40; break; + } +} + +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 (vehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_AVOID_CARS) + return; + 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/miami/control/CarAI.h b/src/miami/control/CarAI.h new file mode 100644 index 00000000..dcd76d78 --- /dev/null +++ b/src/miami/control/CarAI.h @@ -0,0 +1,31 @@ +#pragma once + +#include "AutoPilot.h" + +class CVehicle; + +class CCarAI +{ +public: + static float FindSwitchDistanceClose(CVehicle*); + static float FindSwitchDistanceFarNormalVehicle(CVehicle*); + static float FindSwitchDistanceFar(CVehicle*); + static void BackToCruisingIfNoWantedLevel(CVehicle*); + static void UpdateCarAI(CVehicle*); + static void CarHasReasonToStop(CVehicle*); + static float GetCarToGoToCoors(CVehicle*, CVector*); + static float GetCarToParkAtCoors(CVehicle*, CVector*); + static void AddPoliceCarOccupants(CVehicle*); + static void AddAmbulanceOccupants(CVehicle*); + static void AddFiretruckOccupants(CVehicle*); + static void TellOccupantsToLeaveCar(CVehicle*); + static void TellOccupantsToFleeCar(CVehicle*); + static void TellCarToRamOtherCar(CVehicle*, CVehicle*); + static void TellCarToBlockOtherCar(CVehicle*, CVehicle*); + static uint8 FindPoliceCarMissionForWantedLevel(); + static uint8 FindPoliceBoatMissionForWantedLevel(); + static int32 FindPoliceCarSpeedForWantedLevel(CVehicle*); + static void MellowOutChaseSpeed(CVehicle*); + static void MellowOutChaseSpeedBoat(CVehicle*); + static void MakeWayForCarWithSiren(CVehicle *veh); +}; diff --git a/src/miami/control/CarCtrl.cpp b/src/miami/control/CarCtrl.cpp new file mode 100644 index 00000000..5aaf4d23 --- /dev/null +++ b/src/miami/control/CarCtrl.cpp @@ -0,0 +1,3370 @@ +#include "common.h" + +#include "CarCtrl.h" + +#include "Accident.h" +#include "Automobile.h" +#include "Bike.h" +#include "Camera.h" +#include "CarAI.h" +#include "CarGen.h" +#include "Cranes.h" +#include "Curves.h" +#include "CutsceneMgr.h" +#include "Gangs.h" +#include "Game.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 "Population.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 "WaterLevel.h" +#include "World.h" +#include "Zones.h" +#include "Pickups.h" + +#define DISTANCE_TO_SPAWN_ROADBLOCK_PEDS (51.0f) +#define DISTANCE_TO_SCAN_FOR_DANGER (14.0f) +#define DISTANCE_TO_SCAN_FOR_PED_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) + +#define PROBABILITY_OF_DEAD_PED_ACCIDENT (0.005f) +#define DISTANCE_BETWEEN_CAR_AND_DEAD_PED (6.0f) +#define PROBABILITY_OF_PASSENGER_IN_VEHICLE (0.125f) + +#define ONSCREEN_DESPAWN_RANGE (120.0f) +#define MINIMAL_DISTANCE_TO_SPAWN_ONSCREEN (100.0f) +#define REQUEST_ONSCREEN_DISTANCE ((ONSCREEN_DESPAWN_RANGE + MINIMAL_DISTANCE_TO_SPAWN_ONSCREEN) / 2) +#define OFFSCREEN_DESPAWN_RANGE (40.0f) +#define EXTENDED_RANGE_DESPAWN_MULTIPLIER (1.5f) + +bool CCarCtrl::bMadDriversCheat; +int32 CCarCtrl::NumLawEnforcerCars; +int32 CCarCtrl::NumAmbulancesOnDuty; +int32 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::MiamiViceCycle; +uint32 CCarCtrl::LastTimeMiamiViceGenerated; +int32 CCarCtrl::TotalNumOfCarsOfRating[TOTAL_CUSTOM_CLASSES]; +int32 CCarCtrl::CarArrays[TOTAL_CUSTOM_CLASSES][MAX_CAR_MODELS_IN_ARRAY]; +int32 CCarCtrl::NumRequestsOfCarRating[TOTAL_CUSTOM_CLASSES]; +int32 CCarCtrl::NumOfLoadedCarsOfRating[TOTAL_CUSTOM_CLASSES]; +int32 CCarCtrl::CarFreqArrays[TOTAL_CUSTOM_CLASSES][MAX_CAR_MODELS_IN_ARRAY]; +int32 CCarCtrl::LoadedCarsArray[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()) { + CountDownToCarsAtStart = 2; + return; + } + if (NumRandomCars < 30){ + if (CountDownToCarsAtStart == 0) + GenerateOneRandomCar(); + else if (--CountDownToCarsAtStart == 0) { + for (int i = 0; i < 100; 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; + bool bTopDownCamera = false; + 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 && !CGame::IsInInterior() && ( + 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, &carClass); + if (carModel == -1 || (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; + bTopDownCamera = true; + invertAngleLimitTest = true; + preferredDistance = OFFSCREEN_DESPAWN_RANGE + 15.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 = REQUEST_ONSCREEN_DISTANCE * 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 = OFFSCREEN_DESPAWN_RANGE; + 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 = REQUEST_ONSCREEN_DISTANCE * 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 = REQUEST_ONSCREEN_DISTANCE * 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 = OFFSCREEN_DESPAWN_RANGE; + 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 = REQUEST_ONSCREEN_DISTANCE * 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 = REQUEST_ONSCREEN_DISTANCE * 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 = OFFSCREEN_DESPAWN_RANGE; + 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 = REQUEST_ONSCREEN_DISTANCE * 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 = OFFSCREEN_DESPAWN_RANGE; + break; + } + } + if (!ThePaths.GenerateCarCreationCoors(vecTargetPos.x, vecTargetPos.y, frontX, frontY, + preferredDistance, angleLimit, invertAngleLimitTest, &spawnPosition, &curNodeId, &nextNodeId, + &positionBetweenNodes, carClass == COPS && pWanted->GetWantedLevel() >= 1)) + return; + CPathNode* pCurNode = &ThePaths.m_pathNodes[curNodeId]; + CPathNode* pNextNode = &ThePaths.m_pathNodes[nextNodeId]; + bool bBoatGenerated = false; + if ((CGeneral::GetRandomNumber() & 0xF) > Min(pCurNode->spawnRate, pNextNode->spawnRate)) + return; + if (pCurNode->bWaterPath) { + bBoatGenerated = true; + if (carClass == COPS) { + carModel = MI_PREDATOR; + carClass = COPS_BOAT; + if (!CStreaming::HasModelLoaded(MI_PREDATOR)) { + CStreaming::RequestModel(MI_PREDATOR, STREAMFLAGS_DEPENDENCY); + return; + } + } + else { + int i; + carModel = -1; + for (i = 10; i > 0 && (carModel == -1 || !CStreaming::HasModelLoaded(carModel)); i--) { + carModel = ChooseBoatModel(ChooseBoatRating(&zone)); + } + if (i == 0) + return; + } + if (pCurNode->bOnlySmallBoats || pNextNode->bOnlySmallBoats) { + if (BoatWithTallMast(carModel)) + return; + } + } + int16 colliding; + CWorld::FindObjectsKindaColliding(spawnPosition, bBoatGenerated ? 40.0f : 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 (!bBoatGenerated && !ThePaths.TestCoorsCloseness(vecTargetPos, false, spawnPosition)) + /* Testing if spawn position can reach target position via valid path. */ + return; + int16 idInNode = 0; + + 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) + /* Not spawning vehicle if road is one way and intended direction is opposide to that way. */ + return; + CVehicle* pVehicle; + if (CModelInfo::IsBoatModel(carModel)) + pVehicle = new CBoat(carModel, RANDOM_VEHICLE); + else if (CModelInfo::IsBikeModel(carModel)) + pVehicle = new CBike(carModel, RANDOM_VEHICLE); + else + pVehicle = new CAutomobile(carModel, RANDOM_VEHICLE); + pVehicle->AutoPilot.m_nPrevRouteNode = 0; + pVehicle->AutoPilot.m_nCurrentRouteNode = curNodeId; + pVehicle->AutoPilot.m_nNextRouteNode = nextNodeId; + switch (carClass) { + 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_FBIRANCH){ + pVehicle->m_currentColour1 = 0; + pVehicle->m_currentColour2 = 0; + } + pVehicle->bCreatedAsPoliceVehicle = true; + break; + case COPS_BOAT: + pVehicle->AutoPilot.m_nTempAction = TEMPACT_NONE; + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(4, 16); + pVehicle->AutoPilot.m_fMaxTrafficSpeed = pVehicle->AutoPilot.m_nCruiseSpeed; + pVehicle->AutoPilot.m_nCarMission = CCarAI::FindPoliceBoatMissionForWantedLevel(); + pVehicle->bCreatedAsPoliceVehicle = true; + break; + default: + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(9, 14); + if (carClass == EXEC) + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(12, 18); + else if (carClass == POOR) + pVehicle->AutoPilot.m_nCruiseSpeed = CGeneral::GetRandomNumberInRange(7, 10); + if (pVehicle->GetColModel()->boundingBox.max.y - pVehicle->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; + } + 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; + CBox* 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 (bBoatGenerated) { + if (!CWaterLevel::GetWaterLevel(finalPosition, &groundZ, true)) { + delete pVehicle; + return; + } + } + else { + 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; + } + if (CModelInfo::IsBoatModel(carModel)) { + finalPosition.z = groundZ; + pVehicle->bExtendedRange = true; + } + else + 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 COPS: + pVehicle->SetStatus((pVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE) ? STATUS_SIMPLE : STATUS_PHYSICS); + pVehicle->ChangeLawEnforcerState(1); + break; + case COPS_BOAT: + pVehicle->ChangeLawEnforcerState(1); + pVehicle->SetStatus(STATUS_PHYSICS); + break; + default: + bBoatGenerated ? pVehicle->SetStatus(STATUS_PHYSICS) : pVehicle->SetStatus(STATUS_SIMPLE); + break; + } + CVisibilityPlugins::SetClumpAlpha(pVehicle->GetClump(), 0); + if (!pVehicle->GetIsOnScreen()){ + if ((vecTargetPos - pVehicle->GetPosition()).Magnitude2D() > OFFSCREEN_DESPAWN_RANGE * (pVehicle->bExtendedRange ? EXTENDED_RANGE_DESPAWN_MULTIPLIER : 1.0f)) { + /* Too far away cars that are not visible aren't needed. */ + delete pVehicle; + return; + } + }else{ + if ((vecTargetPos - pVehicle->GetPosition()).Magnitude2D() > TheCamera.GenerationDistMultiplier * (pVehicle->bExtendedRange ? EXTENDED_RANGE_DESPAWN_MULTIPLIER : 1.0f) * ONSCREEN_DESPAWN_RANGE || + (vecTargetPos - pVehicle->GetPosition()).Magnitude2D() < TheCamera.GenerationDistMultiplier * MINIMAL_DISTANCE_TO_SPAWN_ONSCREEN) { + delete pVehicle; + return; + } + if ((TheCamera.GetPosition() - pVehicle->GetPosition()).Magnitude2D() < 82.5f * TheCamera.GenerationDistMultiplier || bTopDownCamera) { + delete pVehicle; + return; + } + if (pVehicle->GetModelIndex() == MI_MARQUIS) { // so marquis can only spawn if player doesn't see it? + 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 || carClass == COPS_BOAT) + CCarAI::AddPoliceCarOccupants(pVehicle); + else { + pVehicle->SetUpDriver(); + int32 passengers = 0; + for (int i = 0; i < pVehicle->m_nNumMaxPassengers; i++) + passengers += (CGeneral::GetRandomNumberInRange(0.0f, 1.0f) < PROBABILITY_OF_PASSENGER_IN_VEHICLE) ? 1 : 0; + if (CModelInfo::IsCarModel(carModel) && (CModelInfo::GetModelInfo(carModel)->GetAnimFileIndex() == CAnimManager::GetAnimationBlockIndex("van") && passengers >= 1)) + passengers = 1; + for (int i = 0; i < passengers; i++) { + CPed* pPassenger = pVehicle->SetupPassenger(i); + if (pPassenger) { + ++CPopulation::ms_nTotalCarPassengerPeds; + pPassenger->bCarPassenger = true; + } + } + } + int nMadDrivers; + switch (pVehicle->GetVehicleAppearance()) { + case VEHICLE_APPEARANCE_BIKE: + nMadDrivers = 30; + break; + case VEHICLE_APPEARANCE_BOAT: + nMadDrivers = 40; + break; + default: + nMadDrivers = 6; + break; + } + if ((CGeneral::GetRandomNumber() & 0x7F) < nMadDrivers || bMadDriversCheat) { + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle->AutoPilot.m_nCruiseSpeed += 10; + } + if (carClass == COPS) + LastTimeLawEnforcerCreated = CTimer::GetTimeInMilliseconds(); + if (pVehicle->GetModelIndex() == MI_CADDY) { + pVehicle->SetStatus(STATUS_PHYSICS); + pVehicle->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + } + if (carClass == COPS && pVehicle->GetModelIndex() == MI_VICECHEE) { + CVehicleModelInfo* pVehicleModel = (CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_VICECHEE); + switch (MiamiViceCycle) { + case 0: + pVehicleModel->SetVehicleColour(53, 77); + break; + case 1: + pVehicleModel->SetVehicleColour(15, 77); + break; + case 2: + pVehicleModel->SetVehicleColour(41, 77); + break; + case 3: + pVehicleModel->SetVehicleColour(61, 77); + break; + default: + break; + } + } + if (CGeneral::GetRandomNumberInRange(0.0f, 1.0f) >= (1 - PROBABILITY_OF_DEAD_PED_ACCIDENT)) { + if (CModelInfo::IsCarModel(pVehicle->GetModelIndex()) && !pVehicle->bIsLawEnforcer) { + if (CPopulation::AddDeadPedInFrontOfCar(pVehicle->GetPosition() + pVehicle->GetForward() * DISTANCE_BETWEEN_CAR_AND_DEAD_PED, pVehicle)) { + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + pVehicle->SetMoveSpeed(0.0f, 0.0f, 0.0f); + for (int i = 0; i < pVehicle->m_nNumPassengers; i++) { + if (pVehicle->pPassengers[i]) { + pVehicle->pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + pVehicle->pPassengers[i]->m_nLastPedState = PED_WANDER_PATH; + pVehicle->pPassengers[i]->m_vehicleInAccident = pVehicle; + pVehicle->pPassengers[i]->bDeadPedInFrontOfCar = true; + pVehicle->RegisterReference((CEntity**)&pVehicle->pPassengers[i]->m_vehicleInAccident); + } + } + if (pVehicle->pDriver) { + pVehicle->pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, pVehicle); + pVehicle->pDriver->m_nLastPedState = PED_WANDER_PATH; + pVehicle->pDriver->m_vehicleInAccident = pVehicle; + pVehicle->pDriver->bDeadPedInFrontOfCar = true; + pVehicle->RegisterReference((CEntity**)&pVehicle->pDriver->m_vehicleInAccident); + } + } + } + } +} + +bool +CCarCtrl::BoatWithTallMast(int32 mi) +{ + return mi == MI_RIO || mi == MI_TROPIC || mi == MI_MARQUIS; +} + +int32 +CCarCtrl::ChooseBoatModel(int32 rating) +{ + ++NumRequestsOfCarRating[rating]; + return ChooseCarModel(rating); +} + +int32 +CCarCtrl::ChooseBoatRating(CZoneInfo* pZoneInfo) +{ + int rnd = CGeneral::GetRandomNumberInRange(0, 1000); + for (int i = 0; i < NUM_BOAT_CLASSES - 1; i++) { + if (rnd < pZoneInfo->boatThreshold[i]) + return FIRST_BOAT_RATING + i; + } + return FIRST_BOAT_RATING + NUM_BOAT_CLASSES - 1; +} + +int32 +CCarCtrl::ChooseCarRating(CZoneInfo* pZoneInfo) +{ + int rnd = CGeneral::GetRandomNumberInRange(0, 1000); + for (int i = 0; i < NUM_CAR_CLASSES - 1; i++) { + if (rnd < pZoneInfo->carThreshold[i]) + return i; + } + return FIRST_CAR_RATING + NUM_CAR_CLASSES - 1; +} + +int32 +CCarCtrl::ChooseModel(CZoneInfo* pZone, int* pClass) { + int32 model = -1; + int32 i; + for (i = 10; i > 0 && (model == -1 || !CStreaming::HasModelLoaded(model)); i--) { + int rnd = CGeneral::GetRandomNumberInRange(0, 1000); + + if (rnd < pZone->copThreshold) { + *pClass = COPS; + model = ChoosePoliceCarModel(); + continue; + } + + int32 j; + for (j = 0; j < NUM_GANG_CAR_CLASSES; j++) { + if (rnd < pZone->gangThreshold[j]) { + *pClass = j + FIRST_GANG_CAR_RATING; + model = ChooseGangCarModel(j); + break; + } + } + + if (j != NUM_GANG_CAR_CLASSES) + continue; + + *pClass = ChooseCarRating(pZone); + model = ChooseCarModel(*pClass); + } + if (i == 0) + return -1; + return model; +} + +int32 +CCarCtrl::ChooseCarModel(int32 vehclass) +{ + int32 model = -1; + ++NumRequestsOfCarRating[vehclass]; + if (NumOfLoadedCarsOfRating[vehclass] == 0) + return -1; + int32 rnd = CGeneral::GetRandomNumberInRange(0, CarFreqArrays[vehclass][NumOfLoadedCarsOfRating[vehclass] - 1]); + int32 index = 0; + while (rnd > CarFreqArrays[vehclass][index]) + index++; + assert(LoadedCarsArray[vehclass][index]); + return LoadedCarsArray[vehclass][index]; +} + +void +CCarCtrl::AddToLoadedVehicleArray(int32 mi, int32 rating, int32 freq) +{ + LoadedCarsArray[rating][NumOfLoadedCarsOfRating[rating]] = mi; + assert(mi >= 130); + CarFreqArrays[rating][NumOfLoadedCarsOfRating[rating]] = freq; + if (NumOfLoadedCarsOfRating[rating]) + CarFreqArrays[rating][NumOfLoadedCarsOfRating[rating]] += CarFreqArrays[rating][NumOfLoadedCarsOfRating[rating] - 1]; + NumOfLoadedCarsOfRating[rating]++; +} + +void +CCarCtrl::RemoveFromLoadedVehicleArray(int32 mi, int32 rating) +{ + int index = 0; + while (LoadedCarsArray[rating][index] != -1) { + if (LoadedCarsArray[rating][index] == mi) + break; + index++; + } + assert(LoadedCarsArray[rating][index] == mi); + int32 freq = CarFreqArrays[rating][index]; + if (index > 0) + freq -= CarFreqArrays[rating][index - 1]; + while (LoadedCarsArray[rating][index + 1] != -1) { + LoadedCarsArray[rating][index] = LoadedCarsArray[rating][index + 1]; + CarFreqArrays[rating][index] = CarFreqArrays[rating][index + 1] - freq; + index++; + } + --NumOfLoadedCarsOfRating[rating]; +} + +int32 +CCarCtrl::ChooseCarModelToLoad(int32 rating) +{ + return CarArrays[rating][CGeneral::GetRandomNumberInRange(0, TotalNumOfCarsOfRating[rating])]; +} + +int32 +CCarCtrl::ChoosePoliceCarModel(void) +{ + if (FindPlayerPed()->m_pWanted->AreMiamiViceRequired() && +#ifdef FIX_BUGS + (CTimer::GetTimeInMilliseconds() > LastTimeMiamiViceGenerated + 120000 || LastTimeMiamiViceGenerated == 0) && +#else + CTimer::GetTimeInMilliseconds() > LastTimeMiamiViceGenerated + 120000 && +#endif + CStreaming::HasModelLoaded(MI_VICECHEE)) { + switch (MiamiViceCycle) { + case 0: + if (CStreaming::HasModelLoaded(MI_VICE1) && CStreaming::HasModelLoaded(MI_VICE2)) + return MI_VICECHEE; + break; + case 1: + if (CStreaming::HasModelLoaded(MI_VICE3) && CStreaming::HasModelLoaded(MI_VICE4)) + return MI_VICECHEE; + break; + case 2: + if (CStreaming::HasModelLoaded(MI_VICE5) && CStreaming::HasModelLoaded(MI_VICE6)) + return MI_VICECHEE; + break; + case 3: + if (CStreaming::HasModelLoaded(MI_VICE7) && CStreaming::HasModelLoaded(MI_VICE8)) + return MI_VICECHEE; + break; + default: + break; + } + } + 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_FBIRANCH) && + CStreaming::HasModelLoaded(MI_FBI)) + return MI_FBIRANCH; + 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 (CGangs::HaveGangModelsLoaded(gang)) + return CGangs::GetGangVehicleModel(gang); + return -1; +} + +void +CCarCtrl::AddToCarArray(int32 id, int32 vehclass) +{ + assert(TotalNumOfCarsOfRating[vehclass] < MAX_CAR_MODELS_IN_ARRAY); + 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->bCreateRoadBlockPeds = false; + } + } + } +} + +void +CCarCtrl::RemoveCarsIfThePoolGetsFull(void) +{ + if ((CTimer::GetFrameCounter() & 7) != 3) + return; + if (CPools::GetVehiclePool()->GetNoOfFreeSpaces() >= 8) + return; + int i = CPools::GetVehiclePool()->GetSize(); + float md = 10000000.f; + CVehicle* pClosestVehicle = nil; + while (i--) { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); + if (!pVehicle) + continue; + if (IsThisVehicleInteresting(pVehicle) || pVehicle->bIsLocked) + continue; + if (!pVehicle->CanBeDeleted() || CCranes::IsThisCarBeingTargettedByAnyCrane(pVehicle)) + continue; + float distance = (TheCamera.GetPosition() - pVehicle->GetPosition()).Magnitude(); + if (distance < md) { + md = distance; + pClosestVehicle = pVehicle; + } + } + if (pClosestVehicle) { + CWorld::Remove(pClosestVehicle); + delete pClosestVehicle; + } +} + +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 = OFFSCREEN_DESPAWN_RANGE; +#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 || + CTimer::GetTimeInMilliseconds() < pVehicle->m_nSetPieceExtendedRangeTime + ) +#endif + { + threshold = ONSCREEN_DESPAWN_RANGE * TheCamera.GenerationDistMultiplier; + } +#ifndef EXTENDED_OFFSCREEN_DESPAWN_RANGE + if (TheCamera.GetForward().z < -0.9f) + threshold = 70.0f; +#endif + if (pVehicle->bExtendedRange) + threshold *= EXTENDED_RANGE_DESPAWN_MULTIPLIER; + if (distanceToPlayer > threshold && !CGarages::IsPointWithinHideOutGarage(pVehicle->GetPosition())){ + if (pVehicle->GetIsOnScreen()){ + 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 || pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS_IGNORE_LIGHTS)) && + CTimer::GetTimeInMilliseconds() - pVehicle->AutoPilot.m_nTimeToStartMission > 5000 && + !pVehicle->GetIsOnScreen() && + (pVehicle->GetPosition() - vecPlayerPos).Magnitude2D() > 22.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 && + CTimer::GetTimeInMilliseconds() > pVehicle->m_nSetPieceExtendedRangeTime && + !(pVehicle->GetIsOnScreen())) { + if ((pVehicle->GetPosition() - vecPlayerPos).MagnitudeSqr() > SQR(6.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; +} + +static CVector GetRandomOffsetForVehicle(CVehicle* pVehicle, bool bNext) +{ + CVector offset; + int32 seed = ((bNext ? pVehicle->AutoPilot.m_nNextPathNodeInfo : pVehicle->AutoPilot.m_nCurrentPathNodeInfo) + pVehicle->m_randomSeed) & 7; + offset.x = (seed - 3) * 0.009f; + offset.y = ((seed >> 3) - 3) * 0.009f; + offset.z = 0.0f; + return offset; +} + +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 = GetRandomOffsetForVehicle(pVehicle, false); + directionCurrentLink += CVector(currentPathLinkForwardX, currentPathLinkForwardY, 0.0f); + directionCurrentLink.Normalise(); + CVector directionNextLink = GetRandomOffsetForVehicle(pVehicle, true); + directionNextLink += CVector(nextPathLinkForwardX, nextPathLinkForwardY, 0.0f); + directionNextLink.Normalise(); + 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.GetCruiseSpeed(); + 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.GetCruiseSpeed(); + + 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.GetCruiseSpeed()); + SlowCarDownForCarsSectorList(s->m_lists[ENTITYLIST_VEHICLES_OVERLAP], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.GetCruiseSpeed()); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.GetCruiseSpeed()); + SlowCarDownForPedsSectorList(s->m_lists[ENTITYLIST_PEDS_OVERLAP], pVehicle, left, top, right, bottom, &maxSpeed, pVehicle->AutoPilot.GetCruiseSpeed()); + } + } + pVehicle->bWarnedPeds = true; + if (pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS || pVehicle->AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_STOP_FOR_CARS_IGNORE_LIGHTS) + return maxSpeed; + return (maxSpeed + pVehicle->AutoPilot.GetCruiseSpeed()) / 2; +} + +void +CCarCtrl::ScanForPedDanger(CVehicle* pVehicle) +{ + bool storedSlowDownFlag = pVehicle->AutoPilot.m_bSlowedDownBecauseOfPeds; + float left = pVehicle->GetPosition().x - DISTANCE_TO_SCAN_FOR_PED_DANGER; + float right = pVehicle->GetPosition().x + DISTANCE_TO_SCAN_FOR_PED_DANGER; + float top = pVehicle->GetPosition().y - DISTANCE_TO_SCAN_FOR_PED_DANGER; + float bottom = pVehicle->GetPosition().y + DISTANCE_TO_SCAN_FOR_PED_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.7f * 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) / 10.0f * 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).Normalise(); + if (DotProduct2D(pOtherEntity->GetPosition() - pVehicle->GetPosition(), forwardA) < 0.0f) + return; + CVector forwardB = pOtherEntity->GetForward(); + ((CVector2D)forwardB).Normalise(); + 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.5f){ + minProximity = Max(0.0f, (minProximity - 0.2f) / 1.3f); + 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() / 0.4f + 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 && (!pVehicle->bPartOfConvoy || !pTestVehicle->bPartOfConvoy)) + WeaveForOtherCar(pTestVehicle, pVehicle, pAngleToWeaveLeft, pAngleToWeaveRight); + } +} + +void CCarCtrl::WeaveForOtherCar(CEntity* pOtherEntity, CVehicle* pVehicle, float* pAngleToWeaveLeft, float* pAngleToWeaveRight) +{ + CVehicle* pOtherCar = (CVehicle*)pOtherEntity; + 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; + 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->GetColModel()->boundingSphere.radius - + pVehicle->GetColModel()->boundingSphere.radius < distance) + return; + CVector2D forward = pVehicle->GetForward(); + forward.Normalise(); + 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 && pPed->m_attachedTo != 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) +{ + pVehicle->AutoPilot.m_nCruiseSpeedMultiplierType = ThePaths.m_pathNodes[pVehicle->AutoPilot.m_nNextRouteNode].speedLimit; + 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); + if (ThePaths.GetNode(pVehicle->AutoPilot.m_nNextRouteNode)->bOnlySmallBoats && BoatWithTallMast(pVehicle->GetModelIndex())) + pVehicle->AutoPilot.m_nCruiseSpeed = 0; + return false; + } +} + +void CCarCtrl::PickNextNodeRandomly(CVehicle* pVehicle) +{ + if (pVehicle->m_nRouteSeed) + CGeneral::SetRandomSeed(pVehicle->m_nRouteSeed); + 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]; + uint8 lanesOnCurrentPath; + bool isOnOneWayRoad; + if (pCurLink->pathNodeIndex == curNode) { + lanesOnCurrentPath = pCurLink->numLeftLanes; + isOnOneWayRoad = pCurLink->numRightLanes == 0; + } + else { + lanesOnCurrentPath = pCurLink->numRightLanes; + isOnOneWayRoad = pCurLink->numLeftLanes == 0; + } + 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; + bool nextNodeIsOneWayRoad; + 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 && (!isOnOneWayRoad || !nextNodeIsOneWayRoad)) + 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; + nextNodeIsOneWayRoad = pNextLink->pathNodeIndex == curNode ? pNextLink->numLeftLanes == 0 : pNextLink->numRightLanes == 0; + } + if (attempt >= ATTEMPTS_TO_FIND_NEXT_NODE) { + /* If we failed 15 times, then remove dead end, one way road 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 +{ + if (pVehicle->m_nRouteSeed) + CGeneral::SetRandomSeed(pVehicle->m_nRouteSeed); + 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[2]; + int16 numNodes; + float distanceToTargetNode; + ThePaths.DoPathSearch(0, pCurNode->GetPosition(), curNode, +#ifdef FIX_PATHFIND_BUG + CVector(targetX, targetY, targetZ), +#else + CVector(targetX, targetY, 0.0f), +#endif + pTargetNode, &numNodes, 2, pVehicle, &distanceToTargetNode, 999999.9f, -1); + + int newNextNode; + int nextLink; + if (numNodes != 1 && numNodes != 2 || pTargetNode[0] == pCurNode){ + if (numNodes != 2 || pTargetNode[1] == 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[1] - ThePaths.m_pathNodes; + for (int i = pCurNode->firstLink; ThePaths.ConnectedNode(i) != newNextNode; i++, nextLink++) + ; + } + } + else { + nextLink = 0; + newNextNode = pTargetNode[0] - 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->numRightLanes; + } + else { + pVehicle->AutoPilot.m_nNextDirection = -1; + lanesOnNextNode = pNextLink->numLeftLanes; + } + 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) +{ + if (pVehicle->m_nRouteSeed) + CGeneral::SetRandomSeed(pVehicle->m_nRouteSeed); + 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 < 2) + return true; + pVehicle->AutoPilot.RemoveOnePathNode(); + } + 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; + LastTimeMiamiViceGenerated = 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++) { + LoadedCarsArray[i][j] = -1; + } + NumOfLoadedCarsOfRating[i] = 0; + NumRequestsOfCarRating[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; + LastTimeMiamiViceGenerated = 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++) + NumRequestsOfCarRating[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; + 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; + 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: + if (pVehicle->AutoPilot.m_bIgnorePathfinding) { + *pSwerve = 0.0f; + *pAccel = 1.0f; + *pBrake = 0.0f; + *pHandbrake = false; + }else + 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_WAITFORDELETION: + case MISSION_HELI_LAND: + 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_GOTOCOORDS_ASTHECROWSWIMS: + SteerAIBoatWithPhysicsHeadingForTarget(pVehicle, + pVehicle->AutoPilot.m_vecDestinationCoors.x, pVehicle->AutoPilot.m_vecDestinationCoors.y, + pSwerve, pAccel, pBrake); + *pHandbrake = false; + 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; + case MISSION_HELI_FLYTOCOORS: + SteerAIHeliTowardsTargetCoors((CAutomobile*)pVehicle); + return; + case MISSION_ATTACKPLAYER: + SteerAIBoatWithPhysicsAttackingPlayer(pVehicle, pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_PLANE_FLYTOCOORS: + SteerAIPlaneTowardsTargetCoors((CAutomobile*)pVehicle); + return; + case MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_1: + SteerAICarWithPhysicsHeadingForTarget(pVehicle, nil, + pVehicle->AutoPilot.m_vecDestinationCoors.x, pVehicle->AutoPilot.m_vecDestinationCoors.y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_2: + SteerAICarWithPhysicsHeadingForTarget(pVehicle, nil, FindPlayerCoors().x, FindPlayerCoors().y, + pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_BLOCKPLAYER_FORWARDANDBACK: + SteerAICarBlockingPlayerForwardAndBack(pVehicle, pSwerve, pAccel, pBrake, pHandbrake); + return; + default: + assert(0); + return; + } +} + +void CCarCtrl::SteerAICarBlockingPlayerForwardAndBack(CVehicle* pVehicle, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + *pSwerve = 0.0f; + *pHandbrake = false; + CVector player = FindPlayerSpeed() + 0.1f * FindPlayerEntity()->GetForward(); + player.z = 0.0f; + CVector right(pVehicle->GetRight().x, pVehicle->GetRight().y, 0.0f); + right.Normalise(); + CVector forward(pVehicle->GetForward().x, pVehicle->GetForward().y, 0.0f); + forward.Normalise(); + float dpPlayerAndRight = DotProduct(player, right); + if (dpPlayerAndRight == 0.0f) + dpPlayerAndRight = 0.01f; + float dpDiffAndRight = -DotProduct((FindPlayerCoors() - pVehicle->GetPosition()), right) / dpPlayerAndRight; + if (dpDiffAndRight < 0.0f) { + *pAccel = 0.0f; + *pBrake = 0.0f; + return; + } + float dpSpeedAndForward = DotProduct(pVehicle->GetMoveSpeed(), forward); + float dpPlayerAndForward = DotProduct(player, forward); + float dpDiffAndForward = DotProduct((FindPlayerCoors() - pVehicle->GetPosition()), forward); + float multiplier = dpPlayerAndForward * dpDiffAndRight + dpDiffAndForward - dpSpeedAndForward * dpDiffAndRight; + if (multiplier > 0) { + *pAccel = Min(1.0f, 0.1f * multiplier); + *pBrake = 0.0f; + } + else if (dpSpeedAndForward > 0) { + *pAccel = 0.0f; + *pBrake = Min(1.0f, -0.1f * multiplier); + if (*pBrake > 0.95f) + *pHandbrake = true; + } + else { + *pAccel = Max(-1.0f, 0.1f * multiplier); + *pBrake = 0.0f; + } +} + +void CCarCtrl::SteerAIBoatWithPhysicsHeadingForTarget(CVehicle* pVehicle, float targetX, float targetY, float* pSwerve, float* pAccel, float* pBrake) +{ + CVector2D forward = pVehicle->GetForward(); + forward.Normalise(); + float angleToTarget = CGeneral::GetATanOfXY(targetX - pVehicle->GetPosition().x, targetY - pVehicle->GetPosition().y); + float angleForward = CGeneral::GetATanOfXY(forward.x, forward.y); + float steerAngle = LimitRadianAngle(angleToTarget - angleForward); + steerAngle = Clamp(steerAngle, -DEFAULT_MAX_STEER_ANGLE, DEFAULT_MAX_STEER_ANGLE); +#ifdef FIX_BUGS + float speedTarget = pVehicle->AutoPilot.GetCruiseSpeed(); +#else + float speedTarget = pVehicle->AutoPilot.m_nCruiseSpeed; +#endif + float currentSpeed = pVehicle->GetMoveSpeed().Magnitude() * GAME_SPEED_TO_CARAI_SPEED; + float speedDiff = speedTarget - currentSpeed; + if (speedDiff <= 0.0f) { + speedDiff < -5.0f ? *pAccel = -0.2f : *pAccel = -0.1f; + steerAngle *= -1; + } + else if (speedDiff / currentSpeed > 0.25f) { + *pAccel = 1.0f; + } + else { + *pAccel = 1.0f - (0.25f - speedDiff / currentSpeed) * 4.0f; + } + *pBrake = 0.0f; + *pSwerve = steerAngle; +} + +void CCarCtrl::SteerAIBoatWithPhysicsAttackingPlayer(CVehicle* pVehicle, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + float distanceToPlayer = (FindPlayerCoors() - pVehicle->GetPosition()).Magnitude(); + float projection = Min(distanceToPlayer / 20.0f, 2.0f); + CVector2D forward = pVehicle->GetForward(); + forward.Normalise(); + CVector2D vecToProjection = FindPlayerCoors() + FindPlayerSpeed() * projection * GAME_SPEED_TO_CARAI_SPEED; + float angleToTarget = CGeneral::GetATanOfXY(vecToProjection.x - pVehicle->GetPosition().x, vecToProjection.y - pVehicle->GetPosition().y); + float angleForward = CGeneral::GetATanOfXY(forward.x, forward.y); + float steerAngle = LimitRadianAngle(angleToTarget - angleForward); +#ifdef FIX_BUGS + float speedTarget = pVehicle->AutoPilot.GetCruiseSpeed(); +#else + float speedTarget = pVehicle->AutoPilot.m_nCruiseSpeed; +#endif + float currentSpeed = pVehicle->GetMoveSpeed().Magnitude() * GAME_SPEED_TO_CARAI_SPEED; + float speedDiff = speedTarget - currentSpeed; + if (speedDiff <= 0.0f) { + speedDiff < -5.0f ? *pAccel = -0.2f : *pAccel = -0.1f; + } + else if (speedDiff / currentSpeed > 0.25f) { + *pAccel = 1.0f; + } + else { + *pAccel = 1.0f - (0.25f - speedDiff / currentSpeed) * 4.0f; + } + *pBrake = 0.0f; + *pSwerve = steerAngle; + *pHandbrake = false; + if (pVehicle->GetModelIndex() == MI_PREDATOR && distanceToPlayer < 40.0f && steerAngle < 0.15f) + pVehicle->FireFixedMachineGuns(); +} + +float CCarCtrl::FindMaxSteerAngle(CVehicle* pVehicle) +{ + return pVehicle->GetModelIndex() == MI_ENFORCER ? 0.7f : DEFAULT_MAX_STEER_ANGLE; +} + +void CCarCtrl::SteerAIHeliTowardsTargetCoors(CAutomobile* pHeli) +{ + if (pHeli->m_aWheelSpeed[1] < 0.22f) + pHeli->m_aWheelSpeed[1] += 0.001f; + if (pHeli->m_aWheelSpeed[1] < 0.15f) + return; + CVector2D vecToTarget = pHeli->AutoPilot.m_vecDestinationCoors - pHeli->GetPosition(); + float distanceToTarget = vecToTarget.Magnitude(); +#ifdef FIX_BUGS + float speed = pHeli->AutoPilot.GetCruiseSpeed() * 0.01f; +#else + float speed = pHeli->AutoPilot.m_nCruiseSpeed * 0.01f; +#endif + if (distanceToTarget <= 100.0f) + { + if (distanceToTarget > 75.0f) + speed *= 0.7f; + else if (distanceToTarget > 10.0f) + speed *= 0.4f; + else + speed *= 0.2f; + } + vecToTarget.Normalise(); + CVector2D vecAdvanceThisFrame(vecToTarget * speed); + float resistance = Pow(0.997f, CTimer::GetTimeStep()); + pHeli->m_vecMoveSpeed.x *= resistance; + pHeli->m_vecMoveSpeed.y *= resistance; + CVector2D vecSpeedDirection = vecAdvanceThisFrame - pHeli->m_vecMoveSpeed; + float vecSpeedChangeLength = vecSpeedDirection.Magnitude(); + vecSpeedDirection.Normalise(); + float changeMultiplier = 0.002f * CTimer::GetTimeStep(); + if (distanceToTarget < 5.0f) + changeMultiplier /= 5.0f; + if (vecSpeedChangeLength < changeMultiplier) + pHeli->SetMoveSpeed(vecAdvanceThisFrame.x, vecAdvanceThisFrame.y, pHeli->GetMoveSpeed().z); + else + pHeli->AddToMoveSpeed(vecSpeedDirection * changeMultiplier); + pHeli->GetMatrix().Translate(CTimer::GetTimeStep() * pHeli->GetMoveSpeed().x, CTimer::GetTimeStep() * pHeli->GetMoveSpeed().y, 0.0f); + float ZTarget = pHeli->AutoPilot.m_vecDestinationCoors.z; + if (CTimer::GetTimeInMilliseconds() & 0x800) // switch every ~2 seconds + ZTarget += 2.0f; + float ZSpeedTarget = (ZTarget - pHeli->GetPosition().z) * 0.01f; + float ZSpeedChangeTarget = ZSpeedTarget - pHeli->GetMoveSpeed().z; + float ZSpeedChangeMax = 0.001f * CTimer::GetTimeStep(); + if (!pHeli->bHeliDestroyed) { + if (Abs(ZSpeedChangeTarget) < ZSpeedChangeMax) + pHeli->SetMoveSpeed(pHeli->GetMoveSpeed().x, pHeli->GetMoveSpeed().y, ZSpeedTarget); + else if (ZSpeedChangeTarget < 0.0f) + pHeli->AddToMoveSpeed(0.0f, 0.0f, -ZSpeedChangeMax); + else + pHeli->AddToMoveSpeed(0.0f, 0.0f, 1.5f * ZSpeedChangeMax); + } + pHeli->GetMatrix().Translate(0.0f, 0.0f, CTimer::GetTimeStep() * pHeli->GetMoveSpeed().z); + pHeli->m_vecTurnSpeed.z *= Pow(0.99f, CTimer::GetTimeStep()); + float ZTurnSpeedTarget; + if (distanceToTarget < 8.0f && pHeli->m_fHeliOrientation < 0.0f) + ZTurnSpeedTarget = 0.0f; + else { + float fAngleTarget = CGeneral::GetATanOfXY(vecToTarget.x, vecToTarget.y) + PI; + if (pHeli->m_fHeliOrientation >= 0.0f) + fAngleTarget = pHeli->m_fHeliOrientation; + fAngleTarget -= pHeli->m_fOrientation; + while (fAngleTarget < -PI) + fAngleTarget += TWOPI; + while (fAngleTarget > PI) + fAngleTarget -= TWOPI; + if (Abs(fAngleTarget) <= 0.4f) + ZTurnSpeedTarget = 0.0f; + else if (fAngleTarget < 0.0f) + ZTurnSpeedTarget = -0.03f; + else + ZTurnSpeedTarget = 0.03f; + } + float ZTurnSpeedChangeTarget = ZTurnSpeedTarget - pHeli->GetTurnSpeed().z; + float ZTurnSpeedLimit = 0.0002f * CTimer::GetTimeStep(); + if (Abs(ZTurnSpeedChangeTarget) < ZTurnSpeedLimit) + pHeli->m_vecTurnSpeed.z = ZTurnSpeedTarget; + else if (ZTurnSpeedChangeTarget < 0.0f) + pHeli->m_vecTurnSpeed.z -= ZTurnSpeedLimit; + else + pHeli->m_vecTurnSpeed.z += ZTurnSpeedLimit; + pHeli->m_fOrientation += pHeli->GetTurnSpeed().z * CTimer::GetTimeStep(); + CVector up; + if (pHeli->bHeliMinimumTilt) + up = CVector(0.5f * pHeli->GetMoveSpeed().x, 0.5f * pHeli->GetMoveSpeed().y, 1.0f); + else + up = CVector(3.0f * pHeli->GetMoveSpeed().x, 3.0f * pHeli->GetMoveSpeed().y, 1.0f); + up.Normalise(); + CVector forward(Cos(pHeli->m_fOrientation), Sin(pHeli->m_fOrientation), 0.0f); + CVector right = CrossProduct(up, forward); + forward = CrossProduct(up, right); + pHeli->GetMatrix().GetRight() = right; + pHeli->GetMatrix().GetForward() = forward; + pHeli->GetMatrix().GetUp() = up; +} + +void CCarCtrl::SteerAIPlaneTowardsTargetCoors(CAutomobile* pPlane) +{ + CVector2D vecToTarget = pPlane->AutoPilot.m_vecDestinationCoors - pPlane->GetPosition(); + float fForwardZ = (pPlane->AutoPilot.m_vecDestinationCoors.z - pPlane->GetPosition().z) / vecToTarget.Magnitude(); + fForwardZ = Clamp(fForwardZ, -0.3f, 0.3f); + float angle = CGeneral::GetATanOfXY(vecToTarget.x, vecToTarget.y); + while (angle > TWOPI) + angle -= TWOPI; + float difference = LimitRadianAngle(angle - pPlane->m_fOrientation); + float steer = difference > 0.0f ? 0.04f : -0.04f; + if (Abs(difference) < 0.2f) + steer *= 5.0f * Abs(difference); + pPlane->m_fPlaneSteer *= Pow(0.96f, CTimer::GetTimeStep()); + float steerChange = steer - pPlane->m_fPlaneSteer; + float maxChange = 0.003f * CTimer::GetTimeStep(); + if (Abs(steerChange) < maxChange) + pPlane->m_fPlaneSteer = steer; + else if (steerChange < 0.0f) + pPlane->m_fPlaneSteer -= maxChange; + else + pPlane->m_fPlaneSteer += maxChange; + pPlane->m_fOrientation += pPlane->m_fPlaneSteer * CTimer::GetTimeStep(); + CVector up(0.0f, 0.0f, 1.0f); + up.Normalise(); + CVector forward(Cos(pPlane->m_fOrientation), Sin(pPlane->m_fOrientation), fForwardZ); + forward.Normalise(); + CVector right = CrossProduct(up, forward); + right.z -= 5.0f * pPlane->m_fPlaneSteer; + right.Normalise(); + up = CrossProduct(forward, right); + up.Normalise(); + right = CrossProduct(forward, up); + pPlane->GetMatrix().GetRight() = right; + pPlane->GetMatrix().GetForward() = forward; + pPlane->GetMatrix().GetUp() = up; + float newSplit = 1.0f - Pow(0.95f, CTimer::GetTimeStep()); + float oldSplit = 1.0f - newSplit; +#ifdef FIX_BUGS + pPlane->m_vecMoveSpeed = pPlane->m_vecMoveSpeed * oldSplit + pPlane->AutoPilot.GetCruiseSpeed() * 0.01f * forward * newSplit; +#else + pPlane->m_vecMoveSpeed = pPlane->m_vecMoveSpeed * oldSplit + pPlane->AutoPilot.m_nCruiseSpeed * 0.01f * forward * newSplit; +#endif + pPlane->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); +} + +void CCarCtrl::SteerAICarWithPhysicsFollowPath(CVehicle* pVehicle, float* pSwerve, float* pAccel, float* pBrake, bool* pHandbrake) +{ + CVector2D forward = pVehicle->GetForward(); + forward.Normalise(); + 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; + SteerAICarWithPhysicsHeadingForTarget(pVehicle, nil, pVehicle->AutoPilot.m_vecDestinationCoors.x, + pVehicle->AutoPilot.m_vecDestinationCoors.y, pSwerve, pAccel, pBrake, pHandbrake); + return; + case MISSION_GOTOCOORDS_ACCURATE: + pVehicle->AutoPilot.m_nCarMission = MISSION_GOTO_COORDS_STRAIGHT_ACCURATE; + SteerAICarWithPhysicsHeadingForTarget(pVehicle, nil, pVehicle->AutoPilot.m_vecDestinationCoors.x, + pVehicle->AutoPilot.m_vecDestinationCoors.y, pSwerve, pAccel, pBrake, pHandbrake); + 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: + case DRIVINGSTYLE_STOP_FOR_CARS_IGNORE_LIGHTS: + speedStyleMultiplier = FindMaximumSpeedForThisCarInTraffic(pVehicle); +#ifdef FIX_BUGS + if (pVehicle->AutoPilot.GetCruiseSpeed() != 0) + speedStyleMultiplier /= pVehicle->AutoPilot.GetCruiseSpeed(); +#else + speedStyleMultiplier /= pVehicle->AutoPilot.m_nCruiseSpeed; +#endif + 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.Normalise(); + 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 / 20.0f); + }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::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; + pVehicle->AutoPilot.m_nPathFindNodesCount = 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) +{ + if (pVehicle->m_nRouteSeed) + CGeneral::SetRandomSeed(pVehicle->m_nRouteSeed); + 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{ + int closestLink = -1; + float md = 999999.9f; + + for (curLink = 0; curLink < pCurNode->numLinks; curLink++) { + int node = ThePaths.ConnectedNode(curLink + pCurNode->firstLink); + CPathNode* pNode = &ThePaths.m_pathNodes[node]; + if (node == pVehicle->AutoPilot.m_nNextRouteNode) + continue; + CVector vCurPos = pCurNode->GetPosition(); + CVector vNextPos = pNode->GetPosition(); + float dist = CCollision::DistToLine(&vCurPos, &vNextPos, &pVehicle->GetPosition()); + if (dist < md) { + md = dist; + closestLink = curLink; + } + } + curConnection = ThePaths.m_carPathConnections[closestLink + 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 (CGame::IsInInterior()) + 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(); +#ifdef SECUROM + if ((myrand() & 7) == 5){ + // if pirated game + CPickups::Init(); + } +#endif + } + } + } + } + } + } +} + +bool CCarCtrl::GenerateOneEmergencyServicesCar(uint32 mi, CVector vecPos) +{ + CVector pPlayerPos = FindPlayerCentreOfWorld(CWorld::PlayerInFocus); + bool created = false; + int attempts = 0; + CVector spawnPos; + int32 curNode, nextNode; + float posBetweenNodes; + while (!created && attempts < 5){ + if (ThePaths.GenerateCarCreationCoors(pPlayerPos.x, pPlayerPos.y, 0.707f, 0.707f, + REQUEST_ONSCREEN_DISTANCE, -1.0f, true, &spawnPos, &curNode, &nextNode, &posBetweenNodes, false)){ + int16 colliding[2]; + if (!ThePaths.GetNode(curNode)->bWaterPath) { + 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.Normalise(); + 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) { + if (--NumLawEnforcerCars < 0) + NumLawEnforcerCars = 0; + } + if (--NumRandomCars < 0) + NumRandomCars = 0; + return; + case MISSION_VEHICLE: + if (--NumMissionCars < 0) + NumMissionCars = 0; + return; + case PARKED_VEHICLE: + if (--NumParkedCars < 0) + NumParkedCars = 0; + return; + case PERMANENT_VEHICLE: + if (--NumPermanentCars < 0) + NumPermanentCars = 0; + 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) +{ +#ifdef GTA_BRIDGE + return mi == MI_BRIDGELIFT || mi == MI_BRIDGEROADSEGMENT; +#else + return false; +#endif +} + +bool CCarCtrl::MapCouldMoveInThisArea(float x, float y) +{ +#ifdef GTA_BRIDGE // actually they forgot that in VC... + // bridge moves up and down + return x > -342.0f && x < -219.0f && + y > -677.0f && y < -580.0f; +#else + return false; +#endif +} + +float CCarCtrl::FindSpeedMultiplierWithSpeedFromNodes(int8 type) +{ + switch (type) + { + case 1: return 1.5f; + case 2: return 2.0f; + } + return 1.0f; +} diff --git a/src/miami/control/CarCtrl.h b/src/miami/control/CarCtrl.h new file mode 100644 index 00000000..5efbe275 --- /dev/null +++ b/src/miami/control/CarCtrl.h @@ -0,0 +1,174 @@ +#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; +class CAutomobile; + +enum{ + MAX_CARS_TO_KEEP = 2, + MAX_CAR_MODELS_IN_ARRAY = 25, +}; + +#ifdef FIX_BUGS +#define FIX_PATHFIND_BUG +#endif + +class CCarCtrl +{ +public: + enum eCarClass { + NORMAL = 0, + POOR, + RICH, + EXEC, + WORKER, + BIG, + TAXI, + MOPED, + MOTORBIKE, + + LEISUREBOAT, + WORKERBOAT, + + COPS, + CUBAN, + HAITIAN, + STREET, + DIAZ, + BIKER, + SECURITY, + PLAYER, + GOLFERS, + GANG9, + COPS_BOAT, + FIRST_CAR_RATING = NORMAL, + FIRST_BOAT_RATING = LEISUREBOAT, + FIRST_GANG_CAR_RATING = CUBAN, + NUM_CAR_CLASSES = MOTORBIKE - FIRST_CAR_RATING + 1, + NUM_BOAT_CLASSES = WORKERBOAT - FIRST_BOAT_RATING + 1, + NUM_GANG_CAR_CLASSES = GANG9 - FIRST_GANG_CAR_RATING + 1, + TOTAL_CUSTOM_CLASSES = NUM_CAR_CLASSES + NUM_BOAT_CLASSES + }; + + 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*, 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 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 bool ThisRoadObjectCouldMove(int16); + static void ClearInterestingVehicleList(); + static void FindLinksToGoWithTheseNodes(CVehicle*); + static bool GenerateOneEmergencyServicesCar(uint32, CVector); + static float FindSpeedMultiplierWithSpeedFromNodes(int8); + static int32 ChooseBoatModel(int32); + static int32 ChooseBoatRating(CZoneInfo* pZoneInfo); + static int32 ChooseCarRating(CZoneInfo* pZoneInfo); + static void AddToLoadedVehicleArray(int32 mi, int32 rating, int32 freq); + static void RemoveFromLoadedVehicleArray(int32 mi, int32 rating); + static int32 ChooseCarModelToLoad(int32 rating); + static bool BoatWithTallMast(int32 mi); + static void RemoveCarsIfThePoolGetsFull(void); + static void SteerAIBoatWithPhysicsHeadingForTarget(CVehicle*, float, float, float*, float*, float*); + static void SteerAIHeliTowardsTargetCoors(CAutomobile*); + static void SteerAIPlaneTowardsTargetCoors(CAutomobile*); + static void SteerAIBoatWithPhysicsAttackingPlayer(CVehicle*, float*, float*, float*, bool*); + static void SteerAICarBlockingPlayerForwardAndBack(CVehicle*, float*, float*, float*, bool*); + + 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 bool bMadDriversCheat; + 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 CarArrays[TOTAL_CUSTOM_CLASSES][MAX_CAR_MODELS_IN_ARRAY]; + + static int32 MiamiViceCycle; + static uint32 LastTimeMiamiViceGenerated; + static int32 NumRequestsOfCarRating[TOTAL_CUSTOM_CLASSES]; + static int32 NumOfLoadedCarsOfRating[TOTAL_CUSTOM_CLASSES]; + static int32 CarFreqArrays[TOTAL_CUSTOM_CLASSES][MAX_CAR_MODELS_IN_ARRAY]; + static int32 LoadedCarsArray[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/miami/control/Curves.cpp b/src/miami/control/Curves.cpp new file mode 100644 index 00000000..5b590440 --- /dev/null +++ b/src/miami/control/Curves.cpp @@ -0,0 +1,55 @@ +#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.25f + 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 t1 = Abs(DotProduct2D(*pPos2 - *pPos1, *pDir1)); + float t2 = Abs(DotProduct2D(*pPos1 - *pPos2, *pDir2)); + float curveCoef; + if (t1 > t2) { + float coef = (t1 - t2) / (t1 + t2); +#ifdef FIX_BUGS + if (between <= coef) +#else + if (between < coef) +#endif + curveCoef = 0.0f; + else + curveCoef = 0.5f - 0.5f * Cos(3.1415f * (between - coef) * (t1 + t2) / (2 * t2)); + } + else { + float coef = 2 * t1 / (t1 + t2); +#ifdef FIX_BUGS + if (coef <= between) +#else + if (coef < between) +#endif + curveCoef = 1.0f; + else + curveCoef = 0.5f - 0.5f * Cos(3.1415f * between * (t1 + t2) / (2 * t1)); + } + *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/miami/control/Curves.h b/src/miami/control/Curves.h new file mode 100644 index 00000000..5d4e05a7 --- /dev/null +++ b/src/miami/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/miami/control/Darkel.cpp b/src/miami/control/Darkel.cpp new file mode 100644 index 00000000..a6aca57e --- /dev/null +++ b/src/miami/control/Darkel.cpp @@ -0,0 +1,439 @@ +#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 "Replay.h" +#include "Weapon.h" +#include "World.h" +#include "Stats.h" +#include "Font.h" +#include "Text.h" +#include "Vehicle.h" +#include "GameLogic.h" + +#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; +int32 CDarkel::InterruptedWeaponType; +int32 CDarkel::InterruptedWeaponSelected; + +/* + * TODO: Collect timer/kill counter RGBA colors on top like in Hud/Frontend. + * 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; +} + +void +CDarkel::DrawMessages() +{ + if (CReplay::IsPlayingBack()) + return; + + 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() - TimeOfFrenzyStart; + if (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_STANDARD); + 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_STANDARD); + 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 (TimeLimit >= 0) { + uint32 timeLeft = TimeLimit - (CTimer::GetTimeInMilliseconds() - 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)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(35.0f), SCREEN_SCALE_Y(109.0f), gUString); + CFont::SetColor(CRGBA(0, 207, 133, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f), SCREEN_SCALE_Y(108.0f), gUString); + } + } + sprintf(gString, "%d", (KillsNeeded >= 0 ? KillsNeeded : 0)); + AsciiToUnicode(gString, gUString); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(35.0f), SCREEN_SCALE_Y(144.0f), gUString); + CFont::SetColor(CRGBA(156, 91, 40, 255)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(34.0f), SCREEN_SCALE_Y(143.0f), gUString); + break; + } + case KILLFRENZY_PASSED: + { + if (bStandardSoundAndMessages) { + uint32 timePassedSinceStart = CTimer::GetTimeInMilliseconds() - TimeOfFrenzyStart; + if (CTimer::GetTimeInMilliseconds() - 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_STANDARD); +#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()]++; + switch (vehicle->GetVehicleAppearance()) { + case VEHICLE_APPEARANCE_CAR: + case VEHICLE_APPEARANCE_BIKE: + CStats::CarsExploded++;; + break; + case VEHICLE_APPEARANCE_HELI: + case VEHICLE_APPEARANCE_PLANE: + CStats::HelisDestroyed++; + break; + case VEHICLE_APPEARANCE_BOAT: + CStats::BoatsExploded++; + break; + } + +} + +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(); + + DealWithWeaponChangeAtEndOfFrenzy(); +} + +void +CDarkel::StartFrenzy(eWeaponType weaponType, int32 time, uint16 kill, int32 modelId0, wchar *text, int32 modelId2, int32 modelId3, int32 modelId4, bool standardSound, bool needHeadShot) +{ + CGameLogic::ClearShortCut(); + CGameLogic::RemoveShortCutDropOffPointForMission(); + 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) { + InterruptedWeaponSelected = player->GetWeapon()->m_eWeaponType; +#if (defined FIX_BUGS || !defined GTA_PS2) + player->RemoveWeaponAnims(InterruptedWeaponSelected, -1000.0f); +#endif + InterruptedWeaponType = player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_eWeaponType; + AmmoInterruptedWeapon = player->GetWeapon(player->GetWeaponSlot(fixedWeapon)).m_nAmmoTotal; + if (InterruptedWeaponType) + CModelInfo::GetModelInfo(CWeaponInfo::GetWeaponInfo((eWeaponType)InterruptedWeaponType)->m_nModelId)->AddRef(); +#if (!defined FIX_BUGS && defined GTA_PS2) + player->RemoveWeaponAnims(InterruptedWeaponSelected, -1000.0f); +#endif + player->GiveWeapon(fixedWeapon, 30000); + player->SetCurrentWeapon(fixedWeapon); + player->MakeChangesForNewWeapon(player->m_nSelectedWepSlot); + + if (FindPlayerVehicle()) { + player->SetCurrentWeapon(FindPlayerPed()->m_nSelectedWepSlot); + player->SetAmmo(fixedWeapon, 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(); + DealWithWeaponChangeAtEndOfFrenzy(); + + 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); + + DealWithWeaponChangeAtEndOfFrenzy(); + + if (bStandardSoundAndMessages) + DMAudio.PlayFrontEndSound(SOUND_RAMPAGE_PASSED, 0); + } +} + +void +CDarkel::DealWithWeaponChangeAtEndOfFrenzy() +{ + eWeaponType fixedWeapon; + if (WeaponType == WEAPONTYPE_UZI_DRIVEBY) + fixedWeapon = WEAPONTYPE_UZI; + else + fixedWeapon = (eWeaponType)WeaponType; + + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS && InterruptedWeaponType) + CModelInfo::GetModelInfo(CWeaponInfo::GetWeaponInfo((eWeaponType)InterruptedWeaponType)->m_nModelId)->RemoveRef(); + + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + int slot = CWeaponInfo::GetWeaponInfo(fixedWeapon)->m_nWeaponSlot; + FindPlayerPed()->RemoveWeaponModel(FindPlayerPed()->GetWeapon(slot).GetInfo()->m_nModelId); + FindPlayerPed()->GetWeapon(slot).m_eWeaponType = WEAPONTYPE_UNARMED; + FindPlayerPed()->GetWeapon(slot).m_nAmmoTotal = 0; + FindPlayerPed()->GetWeapon(slot).m_nAmmoInClip = 0; + FindPlayerPed()->GetWeapon(slot).m_eWeaponState = WEAPONSTATE_READY; + FindPlayerPed()->RemoveWeaponAnims(fixedWeapon, -1000.0f); + CModelInfo::GetModelInfo(CWeaponInfo::GetWeaponInfo(fixedWeapon)->m_nModelId)->RemoveRef(); + } + + CPlayerPed* player = FindPlayerPed(); + if (fixedWeapon < WEAPONTYPE_TOTALWEAPONS) { + player->m_nSelectedWepSlot = CWeaponInfo::GetWeaponInfo((eWeaponType)InterruptedWeaponSelected)->m_nWeaponSlot; + player->GiveWeapon((eWeaponType)InterruptedWeaponType, AmmoInterruptedWeapon, true); + } + + if (FindPlayerVehicle()) { + player->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(player->GetWeapon()->m_eWeaponType)->m_nModelId); + if (FindPlayerPed()->GetWeapon(WEAPONSLOT_SUBMACHINEGUN).m_eWeaponType) + FindPlayerPed()->m_nSelectedWepSlot = WEAPONSLOT_SUBMACHINEGUN; + else + FindPlayerPed()->m_nSelectedWepSlot = WEAPONSLOT_UNARMED; + player->SetCurrentWeapon(FindPlayerPed()->m_nSelectedWepSlot); + player->MakeChangesForNewWeapon(player->m_currentWeapon); + player->RemoveDrivebyAnims(); + } +} diff --git a/src/miami/control/Darkel.h b/src/miami/control/Darkel.h new file mode 100644 index 00000000..91955479 --- /dev/null +++ b/src/miami/control/Darkel.h @@ -0,0 +1,55 @@ +#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 int32 InterruptedWeaponType; + static int32 InterruptedWeaponSelected; + 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(); + static void DealWithWeaponChangeAtEndOfFrenzy(); + +}; diff --git a/src/miami/control/GameLogic.cpp b/src/miami/control/GameLogic.cpp new file mode 100644 index 00000000..63c685d1 --- /dev/null +++ b/src/miami/control/GameLogic.cpp @@ -0,0 +1,632 @@ +#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 "Population.h" +#include "General.h" +#include "DMAudio.h" +#include "Radar.h" +#include "Pools.h" +#include "Hud.h" +#include "Particle.h" +#include "ColStore.h" +#include "Automobile.h" +#include "MBlur.h" +#include "screendroplets.h" +#include "SaveBuf.h" + +uint8 CGameLogic::ActivePlayers; +uint8 CGameLogic::ShortCutState; +CAutomobile* CGameLogic::pShortCutTaxi; +uint32 CGameLogic::NumAfterDeathStartPoints; +CVector CGameLogic::ShortCutStart; +float CGameLogic::ShortCutStartOrientation; +CVector CGameLogic::ShortCutDestination; +float CGameLogic::ShortCutDestinationOrientation; +uint32 CGameLogic::ShortCutTimer; +CVector CGameLogic::AfterDeathStartPoints[NUM_SHORTCUT_START_POINTS]; +float CGameLogic::AfterDeathStartPointOrientation[NUM_SHORTCUT_START_POINTS]; +CVector CGameLogic::ShortCutDropOffForMission; +float CGameLogic::ShortCutDropOffOrientationForMission; +bool CGameLogic::MissionDropOffReadyToBeUsed; + +#define SHORTCUT_TAXI_COST (9) +#define TOTAL_BUSTED_AUDIO (28) + +void +CGameLogic::InitAtStartOfGame() +{ + ActivePlayers = 1; + ShortCutState = SHORTCUT_NONE; + pShortCutTaxi = nil; + NumAfterDeathStartPoints = 0; +} + +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); + CWorld::Players[CWorld::PlayerInFocus].m_pPed->Undress("player"); + CStreaming::LoadSceneCollision(pos); + CStreaming::LoadScene(pos); + CWorld::Players[CWorld::PlayerInFocus].m_pPed->Dress(); + CTimer::Update(); +} + +void +CGameLogic::Update() +{ + CVector vecRestartPos; + float fRestartFloat; + +#ifdef MISSION_REPLAY + // what a place to check! + if (gbTryingPorn4Again) { + CRunningScript* pScript = CTheScripts::pActiveScripts; + if (pScript && !CGeneral::faststricmp(pScript->m_abScriptName, "porno4")) + gbTryingPorn4Again = false; + } +#endif + + if (CCutsceneMgr::IsCutsceneProcessing()) return; + + UpdateShortCut(); + 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 - 100); + 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, true); + CRestart::FindClosestHospitalRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat); + CRestart::OverrideHospitalLevel = LEVEL_GENERIC; + CRestart::OverridePoliceStationLevel = LEVEL_GENERIC; + PassTime(720); + RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat); + AfterDeathArrestSetUpShortCutTaxi(); + SortOutStreamingAndMemory(pPlayerInfo.GetPos()); + TheCamera.m_fCamShakeForce = 0.0f; + TheCamera.SetMotionBlur(0, 0, 0, 0, MOTION_BLUR_NONE); + CPad::GetPad(0)->StopShaking(0); + CReferences::RemoveReferencesToPlayer(); + CPopulation::m_CountDownToPedsAtStart = 10; + CCarCtrl::CountDownToCarsAtStart = 10; + 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); + } + + + if (!CTheScripts::IsPlayerOnAMission() && pPlayerInfo.m_nBustedAudioStatus == BUSTEDAUDIO_NONE) { + if (CGeneral::GetRandomNumberInRange(0, 4) == 0) + pPlayerInfo.m_nBustedAudioStatus = BUSTEDAUDIO_DONE; + else { + pPlayerInfo.m_nBustedAudioStatus = BUSTEDAUDIO_LOADING; + char name[12]; + sprintf(name, pPlayerInfo.m_nCurrentBustedAudio >= 10 ? "bust_%d" : "bust_0%d", pPlayerInfo.m_nCurrentBustedAudio); + DMAudio.ClearMissionAudio(0); + DMAudio.PreloadMissionAudio(0, name); + pPlayerInfo.m_nCurrentBustedAudio = pPlayerInfo.m_nCurrentBustedAudio % TOTAL_BUSTED_AUDIO + 1; + } + } + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime > 4000 && + pPlayerInfo.m_nBustedAudioStatus == BUSTEDAUDIO_LOADING && + DMAudio.GetMissionAudioLoadingStatus(0) == 1) { + DMAudio.PlayLoadedMissionAudio(0); + pPlayerInfo.m_nBustedAudioStatus = BUSTEDAUDIO_DONE; + } + +#ifdef MISSION_REPLAY + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= AddExtraDeathDelay() + 0x1000) { +#else + if (CTimer::GetTimeInMilliseconds() - pPlayerInfo.m_nWBTime >= 0x1000) { +#endif +#ifdef FIX_BUGS + pPlayerInfo.m_nBustedAudioStatus = BUSTEDAUDIO_NONE; +#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, true); + CRestart::FindClosestPoliceRestartPoint(pPlayerInfo.GetPos(), &vecRestartPos, &fRestartFloat); + CRestart::OverrideHospitalLevel = LEVEL_GENERIC; + CRestart::OverridePoliceStationLevel = LEVEL_GENERIC; + PassTime(720); + RestorePlayerStuffDuringResurrection(pPlayerInfo.m_pPed, vecRestartPos, fRestartFloat); + AfterDeathArrestSetUpShortCutTaxi(); + 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(); + CPopulation::m_CountDownToPedsAtStart = 10; + CCarCtrl::CountDownToCarsAtStart = 10; + 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, true); + 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(); + CPopulation::m_CountDownToPedsAtStart = 10; + CCarCtrl::CountDownToCarsAtStart = 10; + 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) +{ + ClearShortCut(); + CPlayerInfo* pPlayerInfo = pPlayerPed->GetPlayerInfoForThisPlayerPed(); + pPlayerPed->m_fHealth = pPlayerInfo->m_nMaxHealth; + pPlayerPed->m_fArmour = 0.0f; + pPlayerPed->bIsVisible = true; + pPlayerPed->m_bloodyFootprintCountOrDeathTime = 0; + pPlayerPed->bDoBloodyFootprints = false; + pPlayerPed->m_nDrunkenness = 0; + pPlayerPed->m_nFadeDrunkenness = 0; + CMBlur::ClearDrunkBlur(); + pPlayerPed->m_nDrunkCountdown = 0; + 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->bCancelEnteringCar = false; + pPlayerPed->RestartNonPartialAnims(); + pPlayerInfo->MakePlayerSafe(false); + pPlayerPed->bRemoveFromWorld = false; + pPlayerPed->ClearWeaponTarget(); + pPlayerPed->SetInitialState(); + CCarCtrl::ClearInterestingVehicleList(); + pPlayerPed->Teleport(pos + CVector(0.0f, 0.0f, 1.0f)); + pPlayerPed->SetMoveSpeed(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.0f, true); + pPlayerPed->RestoreHeadingRate(); + CGame::currArea = AREA_MAIN_MAP; + CStreaming::RemoveBuildingsNotInArea(AREA_MAIN_MAP); + TheCamera.SetCameraDirectlyInFrontForFollowPed_CamOnAString(); + TheCamera.Restore(); + CReferences::RemoveReferencesToPlayer(); + CGarages::PlayerArrestedOrDied(); + CStats::CheckPointReachedUnsuccessfully(); + CWorld::Remove(pPlayerPed); + CWorld::Add(pPlayerPed); + CHud::ResetWastedText(); + CStreaming::StreamZoneModels(pos); + clearWaterDrop = true; +} + +void +CGameLogic::ClearShortCut() +{ + if (pShortCutTaxi) { + if (pShortCutTaxi->VehicleCreatedBy == MISSION_VEHICLE) { + pShortCutTaxi->VehicleCreatedBy = RANDOM_VEHICLE; + --CCarCtrl::NumMissionCars; + ++CCarCtrl::NumRandomCars; + } + CRadar::ClearBlipForEntity(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(pShortCutTaxi)); + pShortCutTaxi = nil; + } + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_SHORTCUT_TAXI); +} + +void +CGameLogic::SetUpShortCut(CVector vStartPos, float fStartAngle, CVector vEndPos, float fEndAngle) +{ + ClearShortCut(); + ShortCutState = SHORTCUT_INIT; + ShortCutStart = vStartPos; + ShortCutStartOrientation = fStartAngle; + ShortCutDestination = vEndPos; + ShortCutDestinationOrientation = fEndAngle; + CStreaming::RequestModel(MI_KAUFMAN, 0); +} + +void +CGameLogic::AbandonShortCutIfTaxiHasBeenMessedWith() +{ + if (!pShortCutTaxi) + return; + if (pShortCutTaxi->pDriver == nil || + pShortCutTaxi->pDriver->DyingOrDead() || + pShortCutTaxi->pDriver->GetPedState() == PED_DRAG_FROM_CAR || + pShortCutTaxi->pDriver->GetPedState() == PED_ON_FIRE || + pShortCutTaxi->pDriver->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE || + pShortCutTaxi->m_fHealth < 250.0f || + pShortCutTaxi->bRenderScorched) + ClearShortCut(); +} + +void +CGameLogic::AbandonShortCutIfPlayerMilesAway() +{ + if (!pShortCutTaxi) + return; + if ((FindPlayerCoors() - pShortCutTaxi->GetPosition()).Magnitude() > 120.0f) + ClearShortCut(); +} + +void +CGameLogic::UpdateShortCut() +{ + switch (ShortCutState) { + case SHORTCUT_INIT: + if (!CStreaming::HasModelLoaded(MI_KAUFMAN)) { + CStreaming::RequestModel(MI_KAUFMAN, 0); + return; + } + pShortCutTaxi = new CAutomobile(MI_KAUFMAN, RANDOM_VEHICLE); + if (!pShortCutTaxi) + return; + pShortCutTaxi->SetPosition(ShortCutStart); + pShortCutTaxi->SetHeading(DEGTORAD(ShortCutStartOrientation)); + pShortCutTaxi->PlaceOnRoadProperly(); + pShortCutTaxi->SetStatus(STATUS_PHYSICS); + pShortCutTaxi->AutoPilot.m_nCarMission = MISSION_STOP_FOREVER; + pShortCutTaxi->AutoPilot.m_nCruiseSpeed = 0; + pShortCutTaxi->SetUpDriver(); + pShortCutTaxi->VehicleCreatedBy = MISSION_VEHICLE; + ++CCarCtrl::NumMissionCars; + --CCarCtrl::NumRandomCars; + CTheScripts::ClearSpaceForMissionEntity(ShortCutStart, pShortCutTaxi); + CWorld::Add(pShortCutTaxi); + CRadar::SetEntityBlip(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(pShortCutTaxi), 0, BLIP_DISPLAY_MARKER_ONLY); + ShortCutState = SHORTCUT_IDLE; + break; + case SHORTCUT_IDLE: + if (FindPlayerPed()->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER && FindPlayerPed()->m_carInObjective == pShortCutTaxi) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_SHORTCUT_TAXI); + FindPlayerPed()->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, pShortCutTaxi); + ShortCutState = SHORTCUT_GETTING_IN; + } + AbandonShortCutIfTaxiHasBeenMessedWith(); + AbandonShortCutIfPlayerMilesAway(); + break; + case SHORTCUT_GETTING_IN: + if (pShortCutTaxi->pPassengers[0] == FindPlayerPed() || + pShortCutTaxi->pPassengers[1] == FindPlayerPed() || + pShortCutTaxi->pPassengers[2] == FindPlayerPed()) { + pShortCutTaxi->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + pShortCutTaxi->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 2500; + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(2.5f, FADE_OUT); + ShortCutState = SHORTCUT_TRANSITION; + ShortCutTimer = CTimer::GetTimeInMilliseconds() + 3000; + CMessages::AddBigMessage(TheText.Get("TAXI"), 4500, 1); + } + AbandonShortCutIfTaxiHasBeenMessedWith(); + break; + case SHORTCUT_TRANSITION: + if (CTimer::GetTimeInMilliseconds() > ShortCutTimer) { + CTimer::Suspend(); + CColStore::RequestCollision(ShortCutDestination); + CStreaming::LoadSceneCollision(ShortCutDestination); + CStreaming::LoadScene(ShortCutDestination); + CTheScripts::ClearSpaceForMissionEntity(ShortCutDestination, pShortCutTaxi); + pShortCutTaxi->Teleport(ShortCutDestination); + pShortCutTaxi->SetHeading(DEGTORAD(ShortCutDestinationOrientation)); + pShortCutTaxi->PlaceOnRoadProperly(); + pShortCutTaxi->SetMoveSpeed(pShortCutTaxi->GetForward() * 0.4f); + ShortCutTimer = CTimer::GetTimeInMilliseconds() + 1500; + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(1.0f, FADE_IN); + ShortCutState = SHORTCUT_ARRIVING; + CTimer::Resume(); + } + break; + case SHORTCUT_ARRIVING: + if (CTimer::GetTimeInMilliseconds() > ShortCutTimer) { + CWorld::Players[CWorld::PlayerInFocus].m_nMoney = Max(0, CWorld::Players[CWorld::PlayerInFocus].m_nMoney - SHORTCUT_TAXI_COST); + FindPlayerPed()->SetObjective(OBJECTIVE_LEAVE_CAR, pShortCutTaxi); + FindPlayerPed()->m_carInObjective = pShortCutTaxi; + ShortCutState = SHORTCUT_GETTING_OUT; + } + AbandonShortCutIfTaxiHasBeenMessedWith(); + break; + case SHORTCUT_GETTING_OUT: + if (pShortCutTaxi->pPassengers[0] != FindPlayerPed() && + pShortCutTaxi->pPassengers[1] != FindPlayerPed() && + pShortCutTaxi->pPassengers[2] != FindPlayerPed()) { + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_SHORTCUT_TAXI); + pShortCutTaxi->AutoPilot.m_nCarMission = MISSION_CRUISE; + pShortCutTaxi->AutoPilot.m_nCruiseSpeed = 18; + CCarCtrl::JoinCarWithRoadSystem(pShortCutTaxi); + pShortCutTaxi->VehicleCreatedBy = RANDOM_VEHICLE; + ++CCarCtrl::NumRandomCars; + --CCarCtrl::NumMissionCars; + CRadar::ClearBlipForEntity(BLIP_CAR, CPools::GetVehiclePool()->GetIndex(pShortCutTaxi)); + ShortCutState = SHORTCUT_NONE; + pShortCutTaxi = nil; + } + AbandonShortCutIfTaxiHasBeenMessedWith(); + break; + } +} + +void +CGameLogic::AddShortCutPointAfterDeath(CVector point, float angle) +{ + if (NumAfterDeathStartPoints >= NUM_SHORTCUT_START_POINTS) + return; + AfterDeathStartPoints[NumAfterDeathStartPoints] = point; + AfterDeathStartPointOrientation[NumAfterDeathStartPoints] = angle; + NumAfterDeathStartPoints++; +} + +void +CGameLogic::AddShortCutDropOffPointForMission(CVector point, float angle) +{ + ShortCutDropOffForMission = point; + ShortCutDropOffOrientationForMission = angle; + MissionDropOffReadyToBeUsed = true; +} + +void +CGameLogic::RemoveShortCutDropOffPointForMission() +{ + MissionDropOffReadyToBeUsed = false; +} + +void +CGameLogic::AfterDeathArrestSetUpShortCutTaxi() +{ + if (!MissionDropOffReadyToBeUsed) + return; + int nClosestPoint = -1; + float fDistanceToPoint = 999999.9f; + for (int i = 0; i < NUM_SHORTCUT_START_POINTS; i++) { + float dist = (AfterDeathStartPoints[i] - FindPlayerCoors()).Magnitude(); + if (dist < fDistanceToPoint) { + fDistanceToPoint = dist; + nClosestPoint = i; + } + } + if (fDistanceToPoint < 100.0f) + SetUpShortCut(AfterDeathStartPoints[nClosestPoint], + AfterDeathStartPointOrientation[nClosestPoint], + ShortCutDropOffForMission, + ShortCutDropOffOrientationForMission); + MissionDropOffReadyToBeUsed = false; +} + +void +CGameLogic::Save(uint8* buf, uint32* size) +{ +INITSAVEBUF + WriteSaveBuf(buf, NumAfterDeathStartPoints); + *size += sizeof(NumAfterDeathStartPoints); + for (int i = 0; i < NUM_SHORTCUT_START_POINTS; i++) { + WriteSaveBuf(buf, AfterDeathStartPoints[i].x); + *size += sizeof(AfterDeathStartPoints[i].x); + WriteSaveBuf(buf, AfterDeathStartPoints[i].y); + *size += sizeof(AfterDeathStartPoints[i].y); + WriteSaveBuf(buf, AfterDeathStartPoints[i].z); + *size += sizeof(AfterDeathStartPoints[i].z); + WriteSaveBuf(buf, AfterDeathStartPointOrientation[i]); + *size += sizeof(AfterDeathStartPointOrientation[i]); + } +VALIDATESAVEBUF(*size) +} + +void +CGameLogic::Load(uint8* buf, uint32 size) +{ +INITSAVEBUF + ReadSaveBuf(&NumAfterDeathStartPoints, buf); + for (int i = 0; i < NUM_SHORTCUT_START_POINTS; i++) { + ReadSaveBuf(&AfterDeathStartPoints[i].x, buf); + ReadSaveBuf(&AfterDeathStartPoints[i].y, buf); + ReadSaveBuf(&AfterDeathStartPoints[i].z, buf); + ReadSaveBuf(&AfterDeathStartPointOrientation[i], buf); + } +VALIDATESAVEBUF(size) +} diff --git a/src/miami/control/GameLogic.h b/src/miami/control/GameLogic.h new file mode 100644 index 00000000..9b774cc7 --- /dev/null +++ b/src/miami/control/GameLogic.h @@ -0,0 +1,51 @@ +#pragma once + +class CAutomobile; + +class CGameLogic +{ +public: + enum { + SHORTCUT_NONE = 0, + SHORTCUT_INIT, + SHORTCUT_IDLE, + SHORTCUT_GETTING_IN, + SHORTCUT_TRANSITION, + SHORTCUT_ARRIVING, + SHORTCUT_GETTING_OUT + }; + + 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 void ClearShortCut(); + static void SetUpShortCut(CVector, float, CVector, float); + static void AbandonShortCutIfTaxiHasBeenMessedWith(); + static void AbandonShortCutIfPlayerMilesAway(); + static void UpdateShortCut(); + static void AddShortCutPointAfterDeath(CVector, float); + static void AddShortCutDropOffPointForMission(CVector, float); + static void RemoveShortCutDropOffPointForMission(); + static void AfterDeathArrestSetUpShortCutTaxi(); + + static void Save(uint8*, uint32*); + static void Load(uint8*, uint32); + + static uint8 ActivePlayers; + static uint8 ShortCutState; + static CAutomobile* pShortCutTaxi; + static uint32 NumAfterDeathStartPoints; + static CVector ShortCutStart; + static float ShortCutStartOrientation; + static CVector ShortCutDestination; + static float ShortCutDestinationOrientation; + static uint32 ShortCutTimer; + static CVector AfterDeathStartPoints[NUM_SHORTCUT_START_POINTS]; + static float AfterDeathStartPointOrientation[NUM_SHORTCUT_START_POINTS]; + static CVector ShortCutDropOffForMission; + static float ShortCutDropOffOrientationForMission; + static bool MissionDropOffReadyToBeUsed; +}; \ No newline at end of file diff --git a/src/miami/control/Garages.cpp b/src/miami/control/Garages.cpp new file mode 100644 index 00000000..b24c9122 --- /dev/null +++ b/src/miami/control/Garages.cpp @@ -0,0 +1,2495 @@ +#include "common.h" + +#include "Garages.h" +#include "main.h" + +#include "Bike.h" +#include "Boat.h" +#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 "VarConsole.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 (500) +#define RESPRAY_PRICE (100) + +// Distances +#define DISTANCE_TO_CALL_OFF_CHASE (10.0f) +#define DISTANCE_FOR_MRWHOOP_HACK (0.5f) +#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 (3.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 (2.8f) +#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.4f) + +// 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 (500) +#define IMPORT_ALLCARS_REWARD (20500) + +// 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 HIDEOUT_DOOR_SPEED_COEFFICIENT (1.7f) +#define TIME_BETWEEN_HIDEOUT_MESSAGES (18000) + +// Camera stuff +#define MARGIN_FOR_CAMERA_COLLECTCARS (0.5f) +#define MARGIN_FOR_CAMERA_DEFAULT (0.5f) + +const int32 gaCarsToCollectInCraigsGarages[TOTAL_COLLECTCARS_GARAGES][TOTAL_COLLECTCARS_CARS] = +{ + { MI_LANDSTAL, MI_IDAHO, MI_ESPERANT, MI_STALLION, MI_RANCHER, MI_BLISTAC }, + { MI_SABRE, MI_VIRGO, MI_SENTINEL, MI_STRETCH, MI_WASHING, MI_ADMIRAL }, + { MI_CHEETAH, MI_INFERNUS, MI_BANSHEE, MI_PHEONIX, MI_COMET, MI_STINGER }, + { MI_VOODOO, MI_CUBAN, MI_CADDY, MI_BAGGAGE, MI_MRWHOOP, MI_PIZZABOY } +}; + +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::aCarsInSafeHouses[TOTAL_HIDEOUT_GARAGES][NUM_GARAGE_STORED_CARS]; +int32 hGarages = AEHANDLE_NONE; +CGarage CGarages::aGarages[NUM_GARAGES]; +bool CGarages::bCamShouldBeOutisde; + +#ifndef MASTER +bool bPrintNearestObject; +#endif + +void CGarages::Init(void) +{ +#ifndef MASTER + VarConsole.Add("Print nearest object", &bPrintNearestObject, true); +#endif + 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++) { + for (int j = 0; j < TOTAL_HIDEOUT_GARAGES; j++) + aCarsInSafeHouses[j][i].Init(); + } + hGarages = DMAudio.CreateEntity(AUDIOTYPE_GARAGE, (void*)1); + if (hGarages >= 0) + DMAudio.SetEntityStatus(hGarages, TRUE); +} + +void CGarages::Shutdown(void) +{ + NumGarages = 0; + if (hGarages < 0) + return; + DMAudio.DestroyEntity(hGarages); + hGarages = AEHANDLE_NONE; +} + +void CGarages::Update(void) +{ + static uint32 GarageToBeTidied = 0; + if (CReplay::IsPlayingBack()) + return; +#ifdef SECUROM + extern uint8 gameProcessPirateCheck; + if (gameProcessPirateCheck == 2) return; +#endif + bCamShouldBeOutisde = false; + TheCamera.pToGarageWeAreIn = nil; + TheCamera.pToGarageWeAreInForHackAvoidFirstPerson = nil; +#ifdef FIX_BUGS + for (uint32 i = 0; i < NumGarages; i++) { +#else + for (int i = 0; i < NUM_GARAGES; i++) { +#endif + if (aGarages[i].IsUsed()) + aGarages[i].Update(); + } + if ((CTimer::GetFrameCounter() & 0xF) != 0xC) + return; +#ifdef FIX_BUGS + if (++GarageToBeTidied >= NumGarages) +#else + if (++GarageToBeTidied >= NUM_GARAGES) +#endif + GarageToBeTidied = 0; + if (!aGarages[GarageToBeTidied].IsUsed()) + return; + if (!aGarages[GarageToBeTidied].IsFar()) + aGarages[GarageToBeTidied].TidyUpGarageClose(); + else + aGarages[GarageToBeTidied].TidyUpGarage(); +} + +int16 CGarages::AddOne(float X1, float Y1, float Z1, float X2, float Y2, float X3, float Y3, float Z2, uint8 type, int32 targetId) +{ + if (NumGarages >= NUM_GARAGES) { + assert(0); + return NumGarages++; + } + CGarage* pGarage = &aGarages[NumGarages]; + pGarage->m_fInfX = Min(Min(Min(X1, X2), X3), X2 + X3 - X1); + pGarage->m_fSupX = Max(Max(X1, X2), X3); + pGarage->m_fInfY = Min(Min(Min(Y1, Y2), Y3), Y2 + Y3 - Y1); + pGarage->m_fSupY = Max(Max(Y1, Y2), Y3); + pGarage->m_vecCorner1 = CVector(X1, Y1, Z1); + pGarage->m_fInfZ = Z1; + pGarage->m_vDir1 = CVector2D(X2 - X1, Y2 - Y1); + pGarage->m_vDir2 = CVector2D(X3 - X1, Y3 - Y1); + pGarage->m_fSupZ = Z2; + pGarage->m_nMaxStoredCars = NUM_GARAGE_STORED_CARS; + pGarage->m_fDir1Len = pGarage->m_vDir1.Magnitude(); + pGarage->m_fDir2Len = pGarage->m_vDir2.Magnitude(); + pGarage->m_vDir1 /= pGarage->m_fDir1Len; + pGarage->m_vDir2 /= pGarage->m_fDir2Len; + pGarage->m_pDoor1 = nil; + pGarage->m_pDoor2 = nil; + pGarage->m_fDoor1Z = Z1; + pGarage->m_fDoor2Z = Z1; + 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->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: + case GARAGE_COLLECTCARS_4: + case GARAGE_FOR_SCRIPT_TO_OPEN_FOR_CAR: + case GARAGE_HIDEOUT_FOUR: + case GARAGE_HIDEOUT_FIVE: + case GARAGE_HIDEOUT_SIX: + case GARAGE_HIDEOUT_SEVEN: + case GARAGE_HIDEOUT_EIGHT: + case GARAGE_HIDEOUT_NINE: + case GARAGE_HIDEOUT_TEN: + case GARAGE_HIDEOUT_ELEVEN: + case GARAGE_HIDEOUT_TWELVE: + 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_fInfX - DISTANCE_FOR_MRWHOOP_HACK, + m_fInfY - DISTANCE_FOR_MRWHOOP_HACK, + m_fSupX + DISTANCE_FOR_MRWHOOP_HACK, + m_fSupY + DISTANCE_FOR_MRWHOOP_HACK)) { + TheCamera.pToGarageWeAreIn = this; + CGarages::bCamShouldBeOutisde = true; + } + } + } + } + break; + default: + break; + } + } + if (m_bDeactivated && m_eGarageState == GS_FULLYCLOSED) + return; + if (m_bRotatedDoor) { +#ifdef GTA_PS2 + if (m_eGarageState == GS_OPENING) { + if (m_pDoor1) { + if (FindPlayerPed()->m_pCurrentPhysSurface == m_pDoor1) + m_pDoor1->bUsesCollision = false; + } + if (m_pDoor2) { + if (FindPlayerPed()->m_pCurrentPhysSurface == m_pDoor2) + m_pDoor2->bUsesCollision = false; + } + } + else if (m_eGarageState == GS_OPENED) { + if (m_pDoor1) + m_pDoor1->bUsesCollision = true; + if (m_pDoor2) + m_pDoor2->bUsesCollision = true; + } +#else + if (m_eGarageState == GS_OPENING || m_eGarageState == GS_OPENED) { + if (m_pDoor1) { + if (FindPlayerPed()->m_pCurrentPhysSurface == m_pDoor1 || FindPlayerPed()->GetPedState() == PED_JUMP || FindPlayerPed()->GetPedState() == PED_FALL || !FindPlayerPed()->bIsStanding) + m_pDoor1->bUsesCollision = false; + } + if (m_pDoor2) { + if (FindPlayerPed()->m_pCurrentPhysSurface == m_pDoor2 || FindPlayerPed()->GetPedState() == PED_JUMP || FindPlayerPed()->GetPedState() == PED_FALL || !FindPlayerPed()->bIsStanding) + m_pDoor2->bUsesCollision = false; + } + } + else { + if (m_pDoor1) + m_pDoor1->bUsesCollision = true; + if (m_pDoor2) + m_pDoor2->bUsesCollision = true; + } +#endif + } + switch (m_eGarageType) { + case GARAGE_RESPRAY: + switch (m_eGarageState) { + case GS_OPENED: + if (IsStaticPlayerCarEntirelyInside()) { + 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. $100 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_fInfX - DISTANCE_TO_CALL_OFF_CHASE, + m_fInfY - DISTANCE_TO_CALL_OFF_CHASE, + m_fSupX + DISTANCE_TO_CALL_OFF_CHASE, + m_fSupY + DISTANCE_TO_CALL_OFF_CHASE); + } + break; + case GS_CLOSING: + if (FindPlayerVehicle()) + ThrowCarsNearDoorOutOfGarage(FindPlayerVehicle()); + 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_fInfX - DISTANCE_TO_CALL_OFF_CHASE, + m_fInfY - DISTANCE_TO_CALL_OFF_CHASE, + m_fSupX + DISTANCE_TO_CALL_OFF_CHASE, + m_fSupY + 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->Suspend(); + } + 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() || FindPlayerVehicle()->IsBike())) { + if (FindPlayerVehicle()->m_fHealth < FREE_RESPRAY_HEALTH_THRESHOLD) + bTakeMoney = true; + FindPlayerVehicle()->m_fHealth = 1000.0f; + if (FindPlayerVehicle()->IsCar()) { + ((CAutomobile*)(FindPlayerVehicle()))->m_fFireBlowUpTimer = 0.0f; + ((CAutomobile*)(FindPlayerVehicle()))->Fix(); + } + else { + ((CBike*)(FindPlayerVehicle()))->m_fFireBlowUpTimer = 0.0f; + ((CBike*)(FindPlayerVehicle()))->Fix(); + } + FindPlayerVehicle()->m_nDoorLock = CARLOCK_UNLOCKED; + ++CStats::Sprayings; + if (FindPlayerVehicle()->GetUp().z < 0.0f) { + FindPlayerVehicle()->GetUp() = -FindPlayerVehicle()->GetUp(); + FindPlayerVehicle()->GetRight() = -FindPlayerVehicle()->GetRight(); + } + bChangedColour = false; +#ifdef FIX_BUGS + if (!FindPlayerVehicle()->IsCar() || !((CAutomobile*)(FindPlayerVehicle()))->bFixedColour) { +#else + if (!((CAutomobile*)(FindPlayerVehicle()))->bFixedColour) { +#endif + 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; + pos.x = CGeneral::GetRandomNumberInRange(m_fInfX + 0.5f, m_fSupX - 0.5f); + pos.y = CGeneral::GetRandomNumberInRange(m_fInfY + 0.5f, m_fSupY - 0.5f); + pos.z = CGeneral::GetRandomNumberInRange(m_fDoor1Z - 3.0f, m_fDoor1Z + 1.0f); + 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); + CStats::AutoPaintingBudget += 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_fInfX - DISTANCE_TO_CALL_OFF_CHASE, + m_fInfY - DISTANCE_TO_CALL_OFF_CHASE, + m_fSupX + DISTANCE_TO_CALL_OFF_CHASE, + m_fSupY + 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()) { + if (!FindPlayerVehicle() || FindPlayerVehicle()->m_bombType) { + 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: + if (FindPlayerVehicle()) + ThrowCarsNearDoorOutOfGarage(FindPlayerVehicle()); + 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(); + if (m_eGarageType == GARAGE_BOMBSHOP3) + CStreaming::RequestModel(MI_BOMB, STREAMFLAGS_DONT_REMOVE); + break; + case GS_FULLYCLOSED: + if (CTimer::GetTimeInMilliseconds() > m_nTimeToStartAction) { + if (m_eGarageType != GARAGE_BOMBSHOP3 || CStreaming::HasModelLoaded(MI_BOMB)) { + 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; + } + 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() || FindPlayerVehicle()->IsBike())) { +#if (!defined GTA_PS2 || defined FIX_BUGS) + FindPlayerVehicle()->m_bombType = CGarages::GetBombTypeForGarageType(m_eGarageType); + FindPlayerVehicle()->m_pBombRigger = FindPlayerPed(); +#else // PS2 version contained a bug: CBike was casted to CAutomobile, but due to coincidence it didn't corrupt memory + ((CAutomobile*)(FindPlayerVehicle()))->m_bombType = CGarages::GetBombTypeForGarageType(m_eGarageType); + ((CAutomobile*)(FindPlayerVehicle()))->m_pBombRigger = FindPlayerPed(); +#endif + 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; + } + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = false; + } + else { + CStreaming::RequestModel(MI_BOMB, STREAMFLAGS_DONT_REMOVE); + } + } + 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 +#ifndef GTA_PS2 + && (!m_pTarget || IsEntityTouching3D(m_pTarget)) +#endif + ) { + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = true; + } + } + else if (!FindPlayerVehicle() && m_pTarget && IsEntityEntirelyInside3D(m_pTarget, 0.0f) && + IsEntityEntirelyOutside(FindPlayerVehicle() ? (CEntity*)FindPlayerVehicle() : (CEntity*)FindPlayerPed(), 2.0f)) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + FindPlayerPed()->m_pWanted->m_bIgnoredByCops = true; + m_eGarageState = GS_CLOSING; + m_bClosingWithoutTargetCar = false; + } + break; + case GS_CLOSING: + if (m_pTarget) + ThrowCarsNearDoorOutOfGarage(m_pTarget); + 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: + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + case GARAGE_COLLECTCARS_4: + 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: + if (m_pTarget) + ThrowCarsNearDoorOutOfGarage(m_pTarget); + 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: + 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: + if (m_pTarget) + ThrowCarsNearDoorOutOfGarage(m_pTarget); + 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: + case GARAGE_HIDEOUT_FOUR: + case GARAGE_HIDEOUT_FIVE: + case GARAGE_HIDEOUT_SIX: + case GARAGE_HIDEOUT_SEVEN: + case GARAGE_HIDEOUT_EIGHT: + case GARAGE_HIDEOUT_NINE: + case GARAGE_HIDEOUT_TEN: + case GARAGE_HIDEOUT_ELEVEN: + case GARAGE_HIDEOUT_TWELVE: + 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()) >= + FindMaxNumStoredCarsForGarage()) { + 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; + StoreAndRemoveCarsForThisHideout(CGarages::aCarsInSafeHouses[CGarages::FindSafeHouseIndexForGarageType(m_eGarageType)], NUM_GARAGE_STORED_CARS); + } + 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) >= FindMaxNumStoredCarsForGarage()) { + 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) { + if (FindPlayerVehicle()->GetVehicleAppearance() != VEHICLE_APPEARANCE_HELI && FindPlayerVehicle()->GetVehicleAppearance() != VEHICLE_APPEARANCE_PLANE) { + CHud::SetHelpMessage(TheText.Get("GA_21"), false); // You cannot store any more cars in this garage. + CGarages::LastTimeHelpMessage = CTimer::GetTimeInMilliseconds(); + } + } + } + } + else if (RestoreCarsForThisHideout(CGarages::aCarsInSafeHouses[CGarages::FindSafeHouseIndexForGarageType(m_eGarageType)])) + 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: + if (m_pTarget) + ThrowCarsNearDoorOutOfGarage(m_pTarget); + 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: + case GARAGE_FOR_SCRIPT_TO_OPEN_FOR_CAR: + switch (m_eGarageState) { + case GS_OPENED: + if (m_pTarget && IsEntityEntirelyInside3D(m_pTarget, 0.0f) && !IsAnyCarBlockingDoor() && IsPlayerOutsideGarage()) { + CPad::GetPad(0)->SetDisablePlayerControls(PLAYERCONTROL_GARAGE); + 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) { + m_eGarageState = GS_FULLYCLOSED; + DMAudio.PlayOneShot(hGarages, SOUND_GARAGE_DOOR_CLOSED, 1.0f); + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_GARAGE); + } + UpdateDoorsHeight(); + 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; + } + default: + break; + } +} + +bool CGarage::IsStaticPlayerCarEntirelyInside() +{ + if (!FindPlayerVehicle()) + return false; + if (!FindPlayerVehicle()->IsCar() && !FindPlayerVehicle()->IsBike()) + 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_fInfX || pVehicle->GetPosition().x > m_fSupX || + pVehicle->GetPosition().y < m_fInfY || pVehicle->GetPosition().y > m_fSupY) + 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::IsPointInsideGarage(CVector pos) +{ + // is it IsPointInsideGarage(pos, 0.0f)? + if (pos.z < m_fInfZ) + return false; + if (pos.z > m_fSupZ) + return false; + CVector2D vecToTarget((CVector2D)pos - m_vecCorner1); + float dp = DotProduct2D(m_vDir1, vecToTarget); + if (dp < 0.0f) + return false; + if (m_fDir1Len < dp) + return false; + dp = DotProduct2D(m_vDir2, vecToTarget); + if (dp < 0.0f) + return false; + if (m_fDir2Len < dp) + return false; + return true; +} + +bool CGarage::IsPointInsideGarage(CVector pos, float m_fMargin) +{ + if (pos.z < m_fInfZ - m_fMargin) + return false; + if (pos.z > m_fSupZ + m_fMargin) + return false; + CVector2D vecToTarget((CVector2D)pos - m_vecCorner1); + float dp = DotProduct2D(m_vDir1, vecToTarget); + if (dp < -m_fMargin) + return false; + if (m_fDir1Len + m_fMargin < dp) + return false; + dp = DotProduct2D(m_vDir2, vecToTarget); + if (dp < -m_fMargin) + return false; + if (m_fDir2Len + m_fMargin < dp) + return false; + return true; +} + +bool CGarage::IsEntityEntirelyInside3D(CEntity* pEntity, float fMargin) +{ + if (pEntity->GetPosition().x < m_fInfX - fMargin || pEntity->GetPosition().x > m_fSupX + fMargin || + pEntity->GetPosition().y < m_fInfY - fMargin || pEntity->GetPosition().y > m_fSupY + fMargin || + pEntity->GetPosition().z < m_fInfZ - fMargin || pEntity->GetPosition().z > m_fSupZ + 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 (!IsPointInsideGarage(pos, fMargin - radius)) + return false; + } + return true; +} + +bool CGarage::IsEntityEntirelyOutside(CEntity * pEntity, float fMargin) +{ + if (pEntity->GetPosition().x > m_fInfX - fMargin && pEntity->GetPosition().x < m_fSupX + fMargin && + pEntity->GetPosition().y > m_fInfY - fMargin && pEntity->GetPosition().y < m_fSupY + 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 (IsPointInsideGarage(pos, fMargin + radius)) + return false; + } + return true; +} + +bool CGarage::IsGarageEmpty() +{ + int16 num; + CEntity* pEntities[16]; + CWorld::FindObjectsIntersectingCube(CVector(m_fInfX, m_fInfY, m_fInfZ), CVector(m_fSupX, m_fSupY, m_fSupZ), &num, 16, pEntities, false, true, true, false, false); + if (num <= 0) + return true; + for (int i = 0; i < 16; i++) { + if (IsEntityTouching3D(pEntities[i])) + return false; + } + return true; +} + +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_fInfX - radius > pEntity->GetPosition().x || m_fSupX + radius < pEntity->GetPosition().x || + m_fInfY - radius > pEntity->GetPosition().y || m_fSupY + radius < pEntity->GetPosition().y || + m_fInfZ - radius > pEntity->GetPosition().z || m_fSupZ + 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 (IsPointInsideGarage(pos, radius)) + 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 (!IsPointInsideGarage(pos, fMargin + radius)) + 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 || pVehicle->GetStatus() == STATUS_WRECKED) + 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 (IsPointInsideGarage(pos, radius)) + return true; + } + } + return false; +} + +void CGarage::ThrowCarsNearDoorOutOfGarage(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 (!IsPointInsideGarage(pos, 0.0f)) { + CVector vecDirectionAway(pVehicle->GetPosition().x - GetGarageCenterX(), pVehicle->GetPosition().y - GetGarageCenterY(), 0.0f); + vecDirectionAway.Normalise(); + pVehicle->AddToMoveSpeed(vecDirectionAway * CTimer::GetTimeStepInSeconds()); + } + } + } +} + +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 (IsPointInsideGarage(pos, radius)) + 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 (!IsPointInsideGarage(pos, radius)) + 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 (IsPointInsideGarage(pVehicle->GetPosition())) + 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 (!IsPointInsideGarage(pVehicle->GetPosition())) { + if (!pVehicle->bIsLocked && pVehicle->CanBeDeleted()) { + CWorld::Remove(pVehicle); + delete pVehicle; +#ifndef FIX_BUGS + return; +#endif + } + } + } +} + +void CGarages::PrintMessages() +{ + if (CTimer::GetTimeInMilliseconds() > MessageStartTime && CTimer::GetTimeInMilliseconds() < MessageEndTime) { + CFont::DrawFonts(); + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.5f)); + 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_STANDARD)); + CFont::SetColor(CRGBA(27, 89, 130, 255)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + + float y_offset = SCREEN_SCALE_Y(140.0f); + + if (MessageNumberInString2 >= 0) { + CMessages::InsertNumberInString(TheText.Get(MessageIDString), MessageNumberInString, MessageNumberInString2, -1, -1, -1, -1, gUString); + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - SCREEN_SCALE_Y(30.0f), gUString); + } + else if (MessageNumberInString >= 0) { + CMessages::InsertNumberInString(TheText.Get(MessageIDString), MessageNumberInString, -1, -1, -1, -1, -1, gUString); + CFont::PrintString(SCREEN_WIDTH / 2, y_offset - SCREEN_SCALE_Y(30.0f), gUString); + } + else { + 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: +#ifndef GTA_PS2 + case MI_FBIRANCH: +#endif + 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) { + CVector pos; + pos.x = m_fDoor1X + m_fDoorPos * m_pDoor1->GetForward().y * 5.0f / 6.0f; + pos.y = m_fDoor1Y - m_fDoorPos * m_pDoor1->GetForward().x * 5.0f / 6.0f; + pos.z = m_pDoor1->GetPosition().z; + m_pDoor1->SetPosition(pos); + 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) { + CVector pos; + pos.x = m_fDoor2X + m_fDoorPos * m_pDoor2->GetForward().y * 5.0f / 6.0f; + pos.y = m_fDoor2Y - m_fDoorPos * m_pDoor2->GetForward().x * 5.0f / 6.0f; + pos.z = m_pDoor2->GetPosition().z; + m_pDoor2->SetPosition(pos); + 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; + aGarages[garage].m_pTarget->RegisterReference((CEntity**)&aGarages[garage].m_pTarget); + 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) +{ + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + for (int i = 0; i < TOTAL_COLLECTCARS_CARS; i++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][i] || (gaCarsToCollectInCraigsGarages[ct][i] == MI_CHEETAH && mi == MI_VICECHEE)) + return (CGarages::CarTypesCollected[ct] & BIT(i)) == 0; + } + return false; +} + +bool CGarage::HasCraigCollectedThisCar(int32 mi) +{ + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + for (int i = 0; i < TOTAL_COLLECTCARS_CARS; i++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][i] || (gaCarsToCollectInCraigsGarages[ct][i] == MI_CHEETAH && mi == MI_VICECHEE)) + return CGarages::CarTypesCollected[ct] & BIT(i); + } + return false; +} + +bool CGarage::MarkThisCarAsCollectedForCraig(int32 mi) +{ + int ct = CGarages::GetCarsCollectedIndexForGarageType(m_eGarageType); + int index; + for (index = 0; index < TOTAL_COLLECTCARS_CARS; index++) { + if (mi == gaCarsToCollectInCraigsGarages[ct][index] || (gaCarsToCollectInCraigsGarages[ct][index] == MI_CHEETAH && mi == MI_VICECHEE)) + 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_fInfX) + distX = m_fInfX - X; + else if (X > m_fSupX) + distX = X - m_fSupX; + else + distX = 0.0f; + if (Y < m_fInfY) + distY = m_fInfY - Y; + else if (Y > m_fSupY) + distY = Y - m_fSupY; + 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(GetGarageCenterX() - 100.0f)); + int xend = Min(NUMSECTORS_X - 1, CWorld::GetSectorIndexX(GetGarageCenterX() + 100.0f)); + int ystart = Max(0, CWorld::GetSectorIndexY(GetGarageCenterY() - 100.0f)); + int yend = Min(NUMSECTORS_Y - 1, CWorld::GetSectorIndexY(GetGarageCenterY() + 100.0f)); + 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) { + 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; + } + } + if (m_pDoor1) + m_pDoor1->bUsesCollision = true; + if (m_pDoor2) + m_pDoor2->bUsesCollision = true; +} + +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 (!IsPointInsideGarage(pEntity->GetPosition(), 2.0f)) + 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; + aGarages[garage].m_fDoorPos = Min(aGarages[garage].m_fDoorHeight, aGarages[garage].m_fDoorPos); + aGarages[garage].UpdateDoorsHeight(); +} + +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() || pVehicle->IsBike()) + m_nCarBombType = ((CAutomobile*)pVehicle)->m_bombType; // NB: cast to CAutomobile is original behaviour +} + +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); + } + CVehicle* pVehicle; + if (CModelInfo::IsBoatModel(m_nModelIndex)) + pVehicle = new CBoat(m_nModelIndex, RANDOM_VEHICLE); + else if (CModelInfo::IsBikeModel(m_nModelIndex)) + { + CBike* pBike = new CBike(m_nModelIndex, RANDOM_VEHICLE); + pBike->bIsStanding = true; + pVehicle = pBike; + } + else + pVehicle = new CAutomobile(m_nModelIndex, RANDOM_VEHICLE); + 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; + if (pVehicle->IsCar()) + { + ((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 (IsPointInsideGarage(pVehicle->GetPosition())) { + 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) +{ +#ifdef FIX_BUGS + for (uint32 i = 0; i < NumGarages; i++) { +#else + for (int i = 0; i < NUM_GARAGES; i++) { +#endif + switch (aGarages[i].m_eGarageType) { + case GARAGE_NONE: + break; + case GARAGE_COLLECTCARS_1: + case GARAGE_COLLECTCARS_2: + case GARAGE_COLLECTCARS_3: + case GARAGE_COLLECTCARS_4: + if (aGarages[i].IsPointInsideGarage(point, MARGIN_FOR_CAMERA_COLLECTCARS)) + return true; + break; + default: + if (aGarages[i].IsPointInsideGarage(point, MARGIN_FOR_CAMERA_DEFAULT)) + return true; + break; + } + } + return false; +} + +bool CGarages::CameraShouldBeOutside() +{ + return bCamShouldBeOutisde; +} + +void CGarages::GivePlayerDetonator() +{ + CPlayerPed* pPed = FindPlayerPed(); + int slot = CWeaponInfo::GetWeaponInfo(WEAPONTYPE_DETONATOR)->m_nWeaponSlot; + pPed->GiveWeapon(WEAPONTYPE_DETONATOR, 1); + pPed->GetWeapon(pPed->GetWeaponSlot(WEAPONTYPE_DETONATOR)).m_eWeaponState = WEAPONSTATE_READY; + pPed->m_nSelectedWepSlot = slot; + if (pPed->m_storedWeapon != WEAPONTYPE_UNIDENTIFIED) + pPed->m_storedWeapon = WEAPONTYPE_DETONATOR; +} + +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() || pVehicle->IsBike())) { + if (IsPointInsideGarage(pVehicle->GetPosition())) { + 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) + continue; + if ((!pVehicle->IsCar() && !pVehicle->IsBike()) || 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 (!IsPointInsideGarage(pos, radius)) + bRemove = true; + } + } + else + bRemove = true; + if (bRemove) { + // no MISSION_VEHICLE check??? + CWorld::Remove(pVehicle); + delete pVehicle; + } + } +} + +void CGarages::PlayerArrestedOrDied() +{ + static int GarageToBeTidied = 0; // lol +#ifdef FIX_BUGS + for (uint32 i = 0; i < NumGarages; i++) { +#else + for (int i = 0; i < NUM_GARAGES; i++) { +#endif + 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: + case GARAGE_COLLECTCARS_4: + case GARAGE_FOR_SCRIPT_TO_OPEN_FOR_CAR: + case GARAGE_HIDEOUT_FOUR: + case GARAGE_HIDEOUT_FIVE: + case GARAGE_HIDEOUT_SIX: + case GARAGE_HIDEOUT_SEVEN: + case GARAGE_HIDEOUT_EIGHT: + case GARAGE_HIDEOUT_NINE: + case GARAGE_HIDEOUT_TEN: + case GARAGE_HIDEOUT_ELEVEN: + case GARAGE_HIDEOUT_TWELVE: + 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.3f)) + pVehicle->SetPosition(pos); +} + +void CGarages::CloseHideOutGaragesBeforeSave() +{ +#ifdef FIX_BUGS + for (uint32 i = 0; i < NumGarages; i++) { +#else + for (int i = 0; i < NUM_GARAGES; i++) { +#endif + if (!IsThisGarageTypeSafehouse(aGarages[i].m_eGarageType)) + continue; + if (aGarages[i].m_eGarageState != GS_FULLYCLOSED) { + aGarages[i].m_eGarageState = GS_FULLYCLOSED; + aGarages[i].StoreAndRemoveCarsForThisHideout(aCarsInSafeHouses[FindSafeHouseIndexForGarageType(aGarages[i].m_eGarageType)], NUM_GARAGE_STORED_CARS); + aGarages[i].RemoveCarsBlockingDoorNotInside(); + 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++) { + total += aCarsInSafeHouses[FindSafeHouseIndexForGarageType(type)][i].HasCar(); + } + return total; +} + +bool CGarages::IsPointWithinHideOutGarage(Const CVector& point) +{ +#ifdef FIX_BUGS + for (uint32 i = 0; i < NumGarages; i++) { +#else + for (int i = 0; i < NUM_GARAGES; i++) { +#endif + switch (aGarages[i].m_eGarageType) { + case GARAGE_HIDEOUT_ONE: + case GARAGE_HIDEOUT_TWO: + case GARAGE_HIDEOUT_THREE: + case GARAGE_HIDEOUT_FOUR: + case GARAGE_HIDEOUT_FIVE: + case GARAGE_HIDEOUT_SIX: + case GARAGE_HIDEOUT_SEVEN: + case GARAGE_HIDEOUT_EIGHT: + case GARAGE_HIDEOUT_NINE: + case GARAGE_HIDEOUT_TEN: + case GARAGE_HIDEOUT_ELEVEN: + case GARAGE_HIDEOUT_TWELVE: + if (aGarages[i].IsPointInsideGarage(point)) + return true; + default: break; + } + } + return false; +} + +bool CGarages::IsPointWithinAnyGarage(Const CVector& point) +{ +#ifdef FIX_BUGS + for (uint32 i = 0; i < NumGarages; i++) { +#else + for (int i = 0; i < NUM_GARAGES; i++) { +#endif + switch (aGarages[i].m_eGarageType) { + case GARAGE_NONE: + continue; + default: + if (aGarages[i].IsPointInsideGarage(point)) + return true; + } + } + return false; +} + +void CGarages::SetAllDoorsBackToOriginalHeight() +{ +#ifdef FIX_BUGS + for (uint32 i = 0; i < NumGarages; i++) { +#else + for (int i = 0; i < NUM_GARAGES; i++) { +#endif + 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().x = aGarages[i].m_fDoor1X; + aGarages[i].m_pDoor1->GetMatrix().GetPosition().y = aGarages[i].m_fDoor1Y; + 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().x = aGarages[i].m_fDoor2X; + aGarages[i].m_pDoor2->GetMatrix().GetPosition().y = aGarages[i].m_fDoor2Y; + 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) +{ +//INITSAVEBUF + *size = 7876; // for some reason it's not actual size again + //*size = (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CarTypesCollected) + sizeof(uint32) + TOTAL_HIDEOUT_GARAGES * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage)); +#if !defined THIS_IS_STUPID && defined COMPATIBLE_SAVES + memset(buf + 7340, 0, *size - 7340); // 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++) { + for (int j = 0; j < TOTAL_HIDEOUT_GARAGES; j++) { + WriteSaveBuf(buf, aCarsInSafeHouses[j][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].m_nMaxStoredCars); + 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, 1); + 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_vecCorner1); + WriteSaveBuf(buf, aGarages[i].m_fInfZ); + WriteSaveBuf(buf, aGarages[i].m_vDir1); + WriteSaveBuf(buf, aGarages[i].m_vDir2); + WriteSaveBuf(buf, aGarages[i].m_fSupZ); + WriteSaveBuf(buf, aGarages[i].m_fDir1Len); + WriteSaveBuf(buf, aGarages[i].m_fDir2Len); + WriteSaveBuf(buf, aGarages[i].m_fInfX); + WriteSaveBuf(buf, aGarages[i].m_fSupX); + WriteSaveBuf(buf, aGarages[i].m_fInfY); + WriteSaveBuf(buf, aGarages[i].m_fSupY); + 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); + ZeroSaveBuf(buf, sizeof(aGarages[i].m_sStoredCar)); +#else + WriteSaveBuf(buf, aGarages[i]); +#endif + } +//VALIDATESAVEBUF(*size); +} + +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) +{ +//INITSAVEBUF + assert(size == 7876); + //assert(size == (6 * sizeof(uint32) + TOTAL_COLLECTCARS_GARAGES * sizeof(*CarTypesCollected) + sizeof(uint32) + TOTAL_HIDEOUT_GARAGES * NUM_GARAGE_STORED_CARS * sizeof(CStoredCar) + NUM_GARAGES * sizeof(CGarage))); + 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++) { + for (int j = 0; j < TOTAL_HIDEOUT_GARAGES; j++) { + ReadSaveBuf(&aCarsInSafeHouses[j][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].m_nMaxStoredCars, 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, 1); + 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_vecCorner1, buf); + ReadSaveBuf(&aGarages[i].m_fInfZ, buf); + ReadSaveBuf(&aGarages[i].m_vDir1, buf); + ReadSaveBuf(&aGarages[i].m_vDir2, buf); + ReadSaveBuf(&aGarages[i].m_fSupZ, buf); + ReadSaveBuf(&aGarages[i].m_fDir1Len, buf); + ReadSaveBuf(&aGarages[i].m_fDir2Len, buf); + ReadSaveBuf(&aGarages[i].m_fInfX, buf); + ReadSaveBuf(&aGarages[i].m_fSupX, buf); + ReadSaveBuf(&aGarages[i].m_fInfY, buf); + ReadSaveBuf(&aGarages[i].m_fSupY, 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); + 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].m_bRecreateDoorOnNextRefresh = true; + aGarages[i].RefreshDoorPointers(true); + if (aGarages[i].m_eGarageType == GARAGE_CRUSHER) + aGarages[i].UpdateCrusherAngle(); + else + aGarages[i].UpdateDoorsHeight(); + } +//VALIDATESAVEBUF(size); + + MessageEndTime = 0; + bCamShouldBeOutisde = false; + MessageStartTime = 0; +} + +bool +CGarages::IsModelIndexADoor(uint32 id) +{ + return 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_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; +} + +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/miami/control/Garages.h b/src/miami/control/Garages.h new file mode 100644 index 00000000..358d404d --- /dev/null +++ b/src/miami/control/Garages.h @@ -0,0 +1,303 @@ +#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, + GARAGE_COLLECTCARS_4, + GARAGE_FOR_SCRIPT_TO_OPEN_FOR_CAR, + GARAGE_HIDEOUT_FOUR, + GARAGE_HIDEOUT_FIVE, + GARAGE_HIDEOUT_SIX, + GARAGE_HIDEOUT_SEVEN, + GARAGE_HIDEOUT_EIGHT, + GARAGE_HIDEOUT_NINE, + GARAGE_HIDEOUT_TEN, + GARAGE_HIDEOUT_ELEVEN, + GARAGE_HIDEOUT_TWELVE +}; + +enum +{ + TOTAL_COLLECTCARS_GARAGES = 4, + TOTAL_HIDEOUT_GARAGES = 12, + TOTAL_COLLECTCARS_CARS = 6 +}; + +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 + +class CGarage +{ +public: + uint8 m_eGarageType; + uint8 m_eGarageState; + uint8 m_nMaxStoredCars; + 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; + CVector2D m_vecCorner1; + float m_fInfZ; + CVector2D m_vDir1; + CVector2D m_vDir2; + float m_fSupZ; + float m_fDir1Len; + float m_fDir2Len; + float m_fInfX; + float m_fSupX; + float m_fInfY; + float m_fSupY; + 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; + 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_fInfX + m_fSupX) / 2; } + float GetGarageCenterY() { return (m_fInfY + m_fSupY) / 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_fInfX) > SWITCH_GARAGE_DISTANCE_CLOSE || + Abs(TheCamera.GetPosition().y - m_fInfY) > SWITCH_GARAGE_DISTANCE_CLOSE; +#endif + } + void TidyUpGarageClose(); + void TidyUpGarage(); + void RefreshDoorPointers(bool); + void UpdateCrusherAngle(); + void UpdateDoorsHeight(); + bool IsEntityEntirelyInside3D(CEntity*, float); + bool IsEntityEntirelyOutside(CEntity*, float); + 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(); + + bool IsPointInsideGarage(CVector); + bool IsPointInsideGarage(CVector, float); + void ThrowCarsNearDoorOutOfGarage(CVehicle*); + + int32 FindMaxNumStoredCarsForGarage() { return Min(NUM_GARAGE_STORED_CARS, m_nMaxStoredCars); } + +}; + +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 aCarsInSafeHouses[TOTAL_HIDEOUT_GARAGES][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(float X1, float Y1, float Z1, float X2, float Y2, float X3, float Y3, float Z2, 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 void SetMaxNumStoredCarsForGarage(int16 garage, uint8 num) { aGarages[garage].m_nMaxStoredCars = num; } + + static bool IsCarSprayable(CVehicle*); + static float FindDoorHeightForMI(int32); + static void CloseHideOutGaragesBeforeSave(void); + static int32 CountCarsInHideoutGarage(uint8); + static int32 GetBombTypeForGarageType(uint8 type) { return type - GARAGE_BOMBSHOP1 + 1; } + static int32 GetCarsCollectedIndexForGarageType(uint8 type) + { + switch (type) { + case GARAGE_COLLECTCARS_1: return 0; + case GARAGE_COLLECTCARS_2: return 1; + case GARAGE_COLLECTCARS_3: return 2; + case GARAGE_COLLECTCARS_4: return 3; + default: assert(0); + } + return 0; + } + static int32 FindSafeHouseIndexForGarageType(uint8 type) + { + switch (type) { + case GARAGE_HIDEOUT_ONE: return 0; + case GARAGE_HIDEOUT_TWO: return 1; + case GARAGE_HIDEOUT_THREE: return 2; + case GARAGE_HIDEOUT_FOUR: return 3; + case GARAGE_HIDEOUT_FIVE: return 4; + case GARAGE_HIDEOUT_SIX: return 5; + case GARAGE_HIDEOUT_SEVEN: return 6; + case GARAGE_HIDEOUT_EIGHT: return 7; + case GARAGE_HIDEOUT_NINE: return 8; + case GARAGE_HIDEOUT_TEN: return 9; + case GARAGE_HIDEOUT_ELEVEN: return 10; + case GARAGE_HIDEOUT_TWELVE: return 11; + } + return -1; + } + static bool IsThisGarageTypeSafehouse(uint8 type) { return FindSafeHouseIndexForGarageType(type) >= 0; } + +}; diff --git a/src/miami/control/NameGrid.cpp b/src/miami/control/NameGrid.cpp new file mode 100644 index 00000000..204e8b9c --- /dev/null +++ b/src/miami/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/miami/control/NameGrid.h b/src/miami/control/NameGrid.h new file mode 100644 index 00000000..d52cec73 --- /dev/null +++ b/src/miami/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/miami/control/OnscreenTimer.cpp b/src/miami/control/OnscreenTimer.cpp new file mode 100644 index 00000000..5045c1e0 --- /dev/null +++ b/src/miami/control/OnscreenTimer.cpp @@ -0,0 +1,165 @@ +#include "common.h" + + +#include "DMAudio.h" +#include "Hud.h" +#include "Replay.h" +#include "Timer.h" +#include "Script.h" +#include "OnscreenTimer.h" +#include "Camera.h" + +void +COnscreenTimer::Init() +{ + m_bDisabled = false; + for(uint32 i = 0; i < NUMONSCREENCOUNTERS; i++) { + m_sCounters[i].m_nCounterOffset = 0; + + for(uint32 j = 0; j < ARRAY_SIZE(m_sCounters[0].m_aCounterText); j++) + m_sCounters[i].m_aCounterText[j] = '\0'; + + m_sCounters[i].m_nType = COUNTER_DISPLAY_NUMBER; + m_sCounters[i].m_bCounterProcessed = false; + } + for(uint32 i = 0; i < NUMONSCREENCLOCKS; i++) { + m_sClocks[i].m_nClockOffset = 0; + + for(uint32 j = 0; j < ARRAY_SIZE(m_sClocks[0].m_aClockText); j++) + m_sClocks[i].m_aClockText[j] = '\0'; + + m_sClocks[i].m_bClockProcessed = false; + m_sClocks[i].m_bClockGoingDown = true; + } +} + +void +COnscreenTimer::Process() +{ + if(!CReplay::IsPlayingBack() && !m_bDisabled) + for(uint32 i = 0; i < NUMONSCREENCLOCKS; i++) + m_sClocks[i].Process(); +} + +void +COnscreenTimer::ProcessForDisplay() +{ + if(CHud::m_Wants_To_Draw_Hud) { + m_bProcessed = false; + for(uint32 i = 0; i < NUMONSCREENCLOCKS; i++) { + m_sClocks[i].m_bClockProcessed = false; + if (m_sClocks[i].m_nClockOffset != 0) { + m_sClocks[i].ProcessForDisplayClock(); + m_sClocks[i].m_bClockProcessed = true; + m_bProcessed = true; + } + } + for(uint32 i = 0; i < NUMONSCREENCOUNTERS; i++) { + m_sCounters[i].m_bCounterProcessed = false; + if (m_sCounters[i].m_nCounterOffset != 0) { + m_sCounters[i].ProcessForDisplayCounter(); + m_sCounters[i].m_bCounterProcessed = true; + m_bProcessed = true; + } + } + } +} + +void +COnscreenTimer::ClearCounter(uint32 offset) +{ + for(uint32 i = 0; i < NUMONSCREENCOUNTERS; i++) { + if(offset == m_sCounters[i].m_nCounterOffset) { + m_sCounters[i].m_nCounterOffset = 0; + m_sCounters[i].m_aCounterText[0] = '\0'; + m_sCounters[i].m_nType = COUNTER_DISPLAY_NUMBER; + m_sCounters[i].m_bCounterProcessed = false; + } + } +} + +void +COnscreenTimer::ClearClock(uint32 offset) +{ + for(uint32 i = 0; i < NUMONSCREENCLOCKS; i++) + if(offset == m_sClocks[i].m_nClockOffset) { + m_sClocks[i].m_nClockOffset = 0; + m_sClocks[i].m_aClockText[0] = '\0'; + m_sClocks[i].m_bClockProcessed = false; + m_sClocks[i].m_bClockGoingDown = true; + } +} + +void +COnscreenTimer::AddCounter(uint32 offset, uint16 type, char* text, uint16 pos) +{ + if (m_sCounters[pos].m_aCounterText[0] != '\0') + return; + + m_sCounters[pos].m_nCounterOffset = offset; + if(text) + strncpy(m_sCounters[pos].m_aCounterText, text, ARRAY_SIZE(m_sCounters[0].m_aCounterText)); + else + m_sCounters[pos].m_aCounterText[0] = '\0'; + + m_sCounters[pos].m_nType = type; +} + +void +COnscreenTimer::AddClock(uint32 offset, char* text, bool bGoingDown) +{ + for(uint32 i = 0; i < NUMONSCREENCLOCKS; i++) { + if(m_sClocks[i].m_nClockOffset == 0) { + m_sClocks[i].m_nClockOffset = offset; + m_sClocks[i].m_bClockGoingDown = bGoingDown; + if(text) + strncpy(m_sClocks[i].m_aClockText, text, ARRAY_SIZE(m_sClocks[0].m_aClockText)); + else + m_sClocks[i].m_aClockText[0] = '\0'; + break; + } + } +} + +void +COnscreenTimerEntry::Process() +{ + if(m_nClockOffset == 0) + return; + + int32* timerPtr = CTheScripts::GetPointerToScriptVariable(m_nClockOffset); + int32 oldTime = *timerPtr; + if (m_bClockGoingDown) { + int32 newTime = oldTime - int32(CTimer::GetTimeStepInMilliseconds()); + *timerPtr = newTime; + if (newTime < 0) { + *timerPtr = 0; + m_bClockProcessed = 0; + m_nClockOffset = 0; + m_aClockText[0] = 0; + } + else { + int32 oldTimeSeconds = oldTime / 1000; + if (oldTimeSeconds < 12 && newTime / 1000 != oldTimeSeconds && !TheCamera.m_WideScreenOn) { + DMAudio.PlayFrontEndSound(SOUND_CLOCK_TICK, newTime / 1000); + } + } + } + else + *timerPtr = oldTime + int32(CTimer::GetTimeStepInMilliseconds()); +} + +void +COnscreenTimerEntry::ProcessForDisplayClock() +{ + uint32 time = *CTheScripts::GetPointerToScriptVariable(m_nClockOffset); + sprintf(m_aClockBuffer, "%02d:%02d", time / 1000 / 60 % 100, + time / 1000 % 60); +} + +void +COnscreenCounterEntry::ProcessForDisplayCounter() +{ + uint32 counter = *CTheScripts::GetPointerToScriptVariable(m_nCounterOffset); + sprintf(m_aCounterBuffer, "%d", counter); +} diff --git a/src/miami/control/OnscreenTimer.h b/src/miami/control/OnscreenTimer.h new file mode 100644 index 00000000..8c049d7d --- /dev/null +++ b/src/miami/control/OnscreenTimer.h @@ -0,0 +1,57 @@ +#pragma once + +enum +{ + COUNTER_DISPLAY_NUMBER, + COUNTER_DISPLAY_BAR, +}; + +class COnscreenTimerEntry +{ +public: + uint32 m_nClockOffset; + char m_aClockText[10]; + char m_aClockBuffer[40]; + bool m_bClockProcessed; + bool m_bClockGoingDown; + + void Process(); + void ProcessForDisplayClock(); +}; + +VALIDATE_SIZE(COnscreenTimerEntry, 0x3C); + +class COnscreenCounterEntry +{ +public: + uint32 m_nCounterOffset; + char m_aCounterText[10]; + uint16 m_nType; + char m_aCounterBuffer[40]; + bool m_bCounterProcessed; + + void ProcessForDisplayCounter(); +}; + +VALIDATE_SIZE(COnscreenCounterEntry, 0x3C); + +class COnscreenTimer +{ +public: + COnscreenTimerEntry m_sClocks[NUMONSCREENCLOCKS]; + COnscreenCounterEntry m_sCounters[NUMONSCREENCOUNTERS]; + 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, uint16 pos); + void AddClock(uint32 offset, char* text, bool bGoingDown); +}; + +VALIDATE_SIZE(COnscreenTimer, 0xF4); diff --git a/src/miami/control/PathFind.cpp b/src/miami/control/PathFind.cpp new file mode 100644 index 00000000..03ff61b6 --- /dev/null +++ b/src/miami/control/PathFind.cpp @@ -0,0 +1,1989 @@ +#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 5000 +#define NUMDETACHED_CARS 1024 +#define NUMDETACHED_PEDS 1214 +#define NUMTEMPEXTERNALNODES 4600 + +CPathInfoForObject *InfoForTileCars; +CPathInfoForObject *InfoForTilePeds; + +CPathInfoForObject *DetachedInfoForTileCars; +CPathInfoForObject *DetachedInfoForTilePeds; +CTempNodeExternal *TempExternalNodes; +int32 NumTempExternalNodes; +int32 NumDetachedPedNodeGroups; +int32 NumDetachedCarNodeGroups; + +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 CBox& 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; + } + } + } + } + } +} + +// Make sure all externals link TO an internal +void +CPathInfoForObject::SwapConnectionsToBeRightWayRound(void) +{ + int e, i; + CPathInfoForObject *tile = this; + + for(e = 0; e < 12; e++) + if(tile[e].type == NodeTypeExtern && tile[e].next < 0) + for(i = 0; i < 12; i++) + if(tile[i].type == NodeTypeIntern && tile[i].next == e){ + tile[e].next = i; + tile[i].next = -1; + bool tmp = !!tile[e].crossing; + tile[e].crossing = tile[i].crossing; + tile[i].crossing = tmp; + } +} + +void +CPathFind::Init(void) +{ + int i; + + m_numPathNodes = 0; + m_numMapObjects = 0; + m_numConnections = 0; + m_numCarPathLinks = 0; + unk = 0; + NumTempExternalNodes = 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 PATHNODESIZE + InfoForTileCars = new CPathInfoForObject[12*PATHNODESIZE]; + memset(InfoForTileCars, 0, 12*PATHNODESIZE*sizeof(CPathInfoForObject)); + InfoForTilePeds = new CPathInfoForObject[12*PATHNODESIZE]; + memset(InfoForTilePeds, 0, 12*PATHNODESIZE*sizeof(CPathInfoForObject)); + + delete[] DetachedInfoForTileCars; + DetachedInfoForTileCars = nil; + delete[] DetachedInfoForTilePeds; + DetachedInfoForTilePeds = nil; + DetachedInfoForTileCars = new CPathInfoForObject[12*NUMDETACHED_CARS]; + memset(DetachedInfoForTileCars, 0, 12*NUMDETACHED_CARS*sizeof(CPathInfoForObject)); + DetachedInfoForTilePeds = new CPathInfoForObject[12*NUMDETACHED_PEDS]; + memset(DetachedInfoForTilePeds, 0, 12*NUMDETACHED_PEDS*sizeof(CPathInfoForObject)); + + delete[] TempExternalNodes; + TempExternalNodes = nil; + TempExternalNodes = new CTempNodeExternal[NUMTEMPEXTERNALNODES]; + memset(TempExternalNodes, 0, NUMTEMPEXTERNALNODES*sizeof(CTempNodeExternal)); + NumTempExternalNodes = 0; + NumDetachedPedNodeGroups = 0; + NumDetachedCarNodeGroups = 0; +} + +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, float width, bool crossing, uint8 spawnRate) +{ + int i; + + i = id*12 + node; + InfoForTilePeds[i].type = type; + InfoForTilePeds[i].next = next; + InfoForTilePeds[i].x = x/16.0f; + InfoForTilePeds[i].y = y/16.0f; + InfoForTilePeds[i].z = z/16.0f; + InfoForTilePeds[i].width = 8.0f*Min(width, 15.0f); + InfoForTilePeds[i].numLeftLanes = 0; + InfoForTilePeds[i].numRightLanes = 0; + InfoForTilePeds[i].crossing = crossing; + InfoForTilePeds[i].speedLimit = 0; + InfoForTilePeds[i].roadBlock = false; + InfoForTilePeds[i].disabled = false; + InfoForTilePeds[i].waterPath = false; + InfoForTilePeds[i].onlySmallBoats = false; + InfoForTilePeds[i].betweenLevels = false; + InfoForTilePeds[i].spawnRate = Min(spawnRate, 15); + + if(node == 11) + InfoForTilePeds[id*12].SwapConnectionsToBeRightWayRound(); +} + +void +CPathFind::StoreNodeInfoCar(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, float width, int8 numLeft, int8 numRight, + bool disabled, bool betweenLevels, uint8 speedLimit, bool roadBlock, bool waterPath, uint8 spawnRate) +{ + int i; + + i = id*12 + node; + InfoForTileCars[i].type = type; + InfoForTileCars[i].next = next; + InfoForTileCars[i].x = x/16.0f; + InfoForTileCars[i].y = y/16.0f; + InfoForTileCars[i].z = z/16.0f; + InfoForTileCars[i].width = 8.0f*Min(width, 15.0f); + InfoForTileCars[i].numLeftLanes = numLeft; + InfoForTileCars[i].numRightLanes = numRight; + InfoForTileCars[i].crossing = false; + InfoForTileCars[i].speedLimit = 0; + InfoForTileCars[i].roadBlock = false; + InfoForTileCars[i].disabled = false; + InfoForTileCars[i].waterPath = false; + InfoForTileCars[i].onlySmallBoats = false; + InfoForTileCars[i].betweenLevels = false; + InfoForTileCars[i].spawnRate = Min(spawnRate, 15); + + if(node == 11) + InfoForTileCars[id*12].SwapConnectionsToBeRightWayRound(); +} + +void +CPathFind::StoreDetachedNodeInfoPed(int32 node, int8 type, int32 next, float x, float y, float z, float width, bool crossing, + bool disabled, bool betweenLevels, uint8 spawnRate) +{ + int i; + + if(NumDetachedPedNodeGroups >= NUMDETACHED_PEDS) + return; + + i = NumDetachedPedNodeGroups*12 + node; + DetachedInfoForTilePeds[i].type = type; + DetachedInfoForTilePeds[i].next = next; + DetachedInfoForTilePeds[i].x = x/16.0f; + DetachedInfoForTilePeds[i].y = y/16.0f; + DetachedInfoForTilePeds[i].z = z/16.0f; + DetachedInfoForTilePeds[i].width = 8.0f*Min(width, 31.0f); + DetachedInfoForTilePeds[i].numLeftLanes = 0; + DetachedInfoForTilePeds[i].numRightLanes = 0; + DetachedInfoForTilePeds[i].crossing = crossing; + DetachedInfoForTilePeds[i].speedLimit = 0; + DetachedInfoForTilePeds[i].roadBlock = false; + DetachedInfoForTilePeds[i].disabled = disabled; + DetachedInfoForTilePeds[i].waterPath = false; + DetachedInfoForTilePeds[i].onlySmallBoats = false; + DetachedInfoForTilePeds[i].betweenLevels = betweenLevels; + DetachedInfoForTilePeds[i].spawnRate = Min(spawnRate, 15); + + if(node == 11){ + DetachedInfoForTilePeds[NumDetachedPedNodeGroups*12].SwapConnectionsToBeRightWayRound(); + NumDetachedPedNodeGroups++; + } +} + +void +CPathFind::StoreDetachedNodeInfoCar(int32 node, int8 type, int32 next, float x, float y, float z, float width, int8 numLeft, int8 numRight, + bool disabled, bool betweenLevels, uint8 speedLimit, bool roadBlock, bool waterPath, uint8 spawnRate, bool onlySmallBoats) +{ + int i; + + if(NumDetachedCarNodeGroups >= NUMDETACHED_CARS) + return; + + i = NumDetachedCarNodeGroups*12 + node; + DetachedInfoForTileCars[i].type = type; + DetachedInfoForTileCars[i].next = next; + DetachedInfoForTileCars[i].x = x/16.0f; + DetachedInfoForTileCars[i].y = y/16.0f; + DetachedInfoForTileCars[i].z = z/16.0f; + DetachedInfoForTileCars[i].width = 8.0f*Min(width, 15.0f); + DetachedInfoForTileCars[i].numLeftLanes = numLeft; + DetachedInfoForTileCars[i].numRightLanes = numRight; + DetachedInfoForTileCars[i].crossing = false; + DetachedInfoForTileCars[i].speedLimit = speedLimit; + DetachedInfoForTileCars[i].roadBlock = roadBlock; + DetachedInfoForTileCars[i].disabled = disabled; + DetachedInfoForTileCars[i].waterPath = waterPath; + DetachedInfoForTileCars[i].onlySmallBoats = onlySmallBoats; + DetachedInfoForTileCars[i].betweenLevels = betweenLevels; + DetachedInfoForTileCars[i].spawnRate = Min(spawnRate, 15); + + if(node == 11){ + DetachedInfoForTileCars[NumDetachedCarNodeGroups*12].SwapConnectionsToBeRightWayRound(); + NumDetachedCarNodeGroups++; + } +} + +void +CPathFind::CalcNodeCoors(float x, float y, float z, int32 id, CVector *out) +{ + CVector pos; + pos.x = x; + pos.y = y; + pos.z = z; + *out = m_mapObjects[id]->GetMatrix() * pos; +} + +bool +CPathFind::LoadPathFindData(void) +{ + CFileMgr::SetDir(""); + return false; +} + +void +CPathFind::PreparePathData(void) +{ + int i, j; + int numExtern, numIntern; + CTempNode *tempNodes; + + printf("PreparePathData\n"); + if(!CPathFind::LoadPathFindData() && // empty + InfoForTileCars && InfoForTilePeds && + DetachedInfoForTileCars && DetachedInfoForTilePeds && TempExternalNodes){ + tempNodes = new CTempNode[NUMTEMPNODES]; + + m_numConnections = 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); + } + + int numExternDetached, numInternDetached; + for(i = 0; i < NUMDETACHED_CARS; i++){ + numExternDetached = 0; + numInternDetached = 0; + for(j = 0; j < 12; j++){ + if(DetachedInfoForTileCars[i*12 + j].type == NodeTypeExtern) + numExternDetached++; + if(DetachedInfoForTilePeds[i*12 + j].type == NodeTypeIntern) + numInternDetached++; + } + // no diagnostic here + } + + 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); + } + for(i = 0; i < NUMDETACHED_CARS; i++) + for(j = 0; j < 12; j++) + if(DetachedInfoForTileCars[i*12 + j].type == NodeTypeExtern){ + // MI:%d here but no argument for it + if(DetachedInfoForTileCars[i*12 + j].numLeftLanes < 0) + printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); + if(DetachedInfoForTileCars[i*12 + j].numRightLanes < 0) + printf("ILLEGAL BLOCK. NEGATIVE NUMBER OF LANES (Obj:%d)\n", i); + if(DetachedInfoForTileCars[i*12 + j].numLeftLanes + DetachedInfoForTileCars[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, DetachedInfoForTileCars, NumDetachedCarNodeGroups); + m_numCarPathNodes = m_numPathNodes; + PreparePathDataForType(PATH_PED, tempNodes, InfoForTilePeds, 1.0f, DetachedInfoForTilePeds, NumDetachedPedNodeGroups); + m_numPedPathNodes = m_numPathNodes - m_numCarPathNodes; + + delete[] tempNodes; + + CountFloodFillGroups(PATH_CAR); + CountFloodFillGroups(PATH_PED); + + delete[] InfoForTileCars; + InfoForTileCars = nil; + delete[] InfoForTilePeds; + InfoForTilePeds = nil; + + delete[] DetachedInfoForTileCars; + DetachedInfoForTileCars = nil; + delete[] DetachedInfoForTilePeds; + DetachedInfoForTilePeds = nil; + delete[] TempExternalNodes; + TempExternalNodes = 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\n", + node->GetX(), node->GetY(), node->GetZ()); + 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, CPathInfoForObject *detachednodes, int32 numDetached) +{ + static CVector CoorsXFormed; + int i, j, k; + int l1, l2; + int start; + float posx, posy; + float dx, dy, mag; + float nearestDist; + int nearestId; + int oldNumPathNodes, oldNumLinks; + float dist; + int iseg, jseg; + int done, cont; + int tileStart; + + oldNumPathNodes = m_numPathNodes; + oldNumLinks = m_numConnections; + +#define OBJECTINDEX(n) (mapObjIndices[(n)]) + int16 *mapObjIndices = new int16[NUM_PATHNODES]; + NumTempExternalNodes = 0; + + // 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].width = objectpathinfo[start + j].width; + m_pathNodes[m_numPathNodes].speedLimit = objectpathinfo[start + j].speedLimit; + m_pathNodes[m_numPathNodes].spawnRate = objectpathinfo[start + j].spawnRate; + m_pathNodes[m_numPathNodes].bUseInRoadBlock = objectpathinfo[start + j].roadBlock; + m_pathNodes[m_numPathNodes].bDisabled = objectpathinfo[start + j].disabled; + m_pathNodes[m_numPathNodes].bWaterPath = objectpathinfo[start + j].waterPath; + m_pathNodes[m_numPathNodes].bOnlySmallBoats = objectpathinfo[start + j].onlySmallBoats; + m_pathNodes[m_numPathNodes].bBetweenLevels = objectpathinfo[start + j].betweenLevels; + m_numPathNodes++; + } + else if(objectpathinfo[start + j].type == NodeTypeExtern){ + CalcNodeCoors( + objectpathinfo[start + j].x, + objectpathinfo[start + j].y, + objectpathinfo[start + j].z, + i, + &CoorsXFormed); + TempExternalNodes[NumTempExternalNodes].pos = CoorsXFormed; + assert(objectpathinfo[start + j].next >= 0); + TempExternalNodes[NumTempExternalNodes].next = tileStart + objectpathinfo[start + j].next; + TempExternalNodes[NumTempExternalNodes].numLeftLanes = objectpathinfo[start + j].numLeftLanes; + TempExternalNodes[NumTempExternalNodes].numRightLanes = objectpathinfo[start + j].numRightLanes; + TempExternalNodes[NumTempExternalNodes].width = objectpathinfo[start + j].width; + TempExternalNodes[NumTempExternalNodes].isCross = !!objectpathinfo[start + j].crossing; + NumTempExternalNodes++; + } + } + } + + // Same thing for detached nodes + for(i = 0; i < numDetached; i++){ + tileStart = m_numPathNodes; + start = 12*i; + for(j = 0; j < 12; j++){ + if(detachednodes[start + j].type == NodeTypeIntern){ + CVector pos; + pos.x = detachednodes[start + j].x; + pos.y = detachednodes[start + j].y; + pos.z = detachednodes[start + j].z; + m_pathNodes[m_numPathNodes].SetPosition(pos); + mapObjIndices[m_numPathNodes] = -(i+1); + m_pathNodes[m_numPathNodes].width = detachednodes[start + j].width; + m_pathNodes[m_numPathNodes].speedLimit = detachednodes[start + j].speedLimit; + m_pathNodes[m_numPathNodes].spawnRate = detachednodes[start + j].spawnRate; + m_pathNodes[m_numPathNodes].bUseInRoadBlock = detachednodes[start + j].roadBlock; + m_pathNodes[m_numPathNodes].bDisabled = detachednodes[start + j].disabled; + m_pathNodes[m_numPathNodes].bWaterPath = detachednodes[start + j].waterPath; + m_pathNodes[m_numPathNodes].bOnlySmallBoats = detachednodes[start + j].onlySmallBoats; + m_pathNodes[m_numPathNodes].bBetweenLevels = detachednodes[start + j].betweenLevels; + m_numPathNodes++; + }else if(detachednodes[start + j].type == NodeTypeExtern){ + TempExternalNodes[NumTempExternalNodes].pos.x = detachednodes[start + j].x; + TempExternalNodes[NumTempExternalNodes].pos.y = detachednodes[start + j].y; + TempExternalNodes[NumTempExternalNodes].pos.z = detachednodes[start + j].z; + assert(detachednodes[start + j].next >= 0); + TempExternalNodes[NumTempExternalNodes].next = tileStart + detachednodes[start + j].next; + TempExternalNodes[NumTempExternalNodes].numLeftLanes = detachednodes[start + j].numLeftLanes; + TempExternalNodes[NumTempExternalNodes].numRightLanes = detachednodes[start + j].numRightLanes; + TempExternalNodes[NumTempExternalNodes].width = detachednodes[start + j].width; + TempExternalNodes[NumTempExternalNodes].isCross = !!detachednodes[start + j].crossing; + NumTempExternalNodes++; + } + } + } + + // Insert external nodes into TempList + TempListLength = 0; + for(i = 0; i < NumTempExternalNodes; i++){ + // 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 - TempExternalNodes[i].pos.x; + if(Abs(dx) < nearestDist){ + dy = tempnodes[k].pos.y - TempExternalNodes[i].pos.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 = TempExternalNodes[i].pos; + // link to connecting internal node + tempnodes[TempListLength].link1 = TempExternalNodes[i].next; + if(type == PATH_CAR){ + tempnodes[TempListLength].numLeftLanes = TempExternalNodes[i].numLeftLanes; + tempnodes[TempListLength].numRightLanes = TempExternalNodes[i].numRightLanes; + } + tempnodes[TempListLength].width = TempExternalNodes[i].width; + tempnodes[TempListLength].isCross = TempExternalNodes[i].isCross; + tempnodes[TempListLength++].linkState = 1; + }else{ + // Found nearest, connect it to our neighbour + tempnodes[nearestId].link2 = TempExternalNodes[i].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 + TempExternalNodes[i].pos)*0.5f; + mag = Sqrt(dx*dx + dy*dy); + tempnodes[nearestId].dirX = dx/mag * 100; + tempnodes[nearestId].dirY = dy/mag * 100; + tempnodes[nearestId].width = Max(tempnodes[nearestId].width, TempExternalNodes[i].width); + if(TempExternalNodes[i].isCross) + tempnodes[nearestId].isCross = true; // TODO: is this guaranteed to be false otherwise? + // do something when number of lanes doesn't agree + if(type == PATH_CAR) + if(tempnodes[nearestId].numLeftLanes != 0 && tempnodes[nearestId].numRightLanes != 0 && + (TempExternalNodes[i].numLeftLanes == 0 || TempExternalNodes[i].numRightLanes == 0)){ + // why switch left and right here? + tempnodes[nearestId].numLeftLanes = TempExternalNodes[i].numRightLanes; + tempnodes[nearestId].numRightLanes = TempExternalNodes[i].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] = Min(dist, 255); + if(tempnodes[j].isCross) + m_connections[j] |= 0x8000; // crosses road flag + + 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].dirX == tempnodes[j].dirX && + m_carPathLinks[k].dirY == tempnodes[j].dirY && + m_carPathLinks[k].x == (int)(tempnodes[j].pos.x*8.0f) && + m_carPathLinks[k].y == (int)(tempnodes[j].pos.y*8.0f)){ + 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].dirX = tempnodes[j].dirX; + m_carPathLinks[m_numCarPathLinks].dirY = tempnodes[j].dirY; + m_carPathLinks[m_numCarPathLinks].x = tempnodes[j].pos.x*8.0f; + m_carPathLinks[m_numCarPathLinks].y = tempnodes[j].pos.y*8.0f; + m_carPathLinks[m_numCarPathLinks].trafficLightDirection = false; + m_carPathLinks[m_numCarPathLinks].width = tempnodes[j].width; + 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++; + } + + CPathInfoForObject *tile; + if(mapObjIndices[i] < 0){ + if(type == PATH_CAR) + tile = &DetachedInfoForTileCars[12 * (-1 - mapObjIndices[i])]; + else + tile = &DetachedInfoForTilePeds[12 * (-1 - mapObjIndices[i])]; + }else{ + if(type == PATH_CAR) + tile = &InfoForTileCars[12 * m_mapObjects[mapObjIndices[i]]->GetModelIndex()]; + else + tile = &InfoForTilePeds[12 * m_mapObjects[mapObjIndices[i]]->GetModelIndex()]; + } + + // Find i inside path segment + iseg = 0; + for(j = Max(oldNumPathNodes, i-12); j < i; j++) + if(OBJECTINDEX(j) == OBJECTINDEX(i)) + iseg++; + + // 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; + + if(tile[iseg].next == jseg || + tile[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] = Min(dist, 255); + + 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; + uint8 width = Max(m_pathNodes[i].width, m_pathNodes[j].width); + 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].dirX == (int)(dx*100.0f) && + m_carPathLinks[k].dirY == (int)(dy*100.0f) && + m_carPathLinks[k].x == (int)(posx*8.0f) && + m_carPathLinks[k].y == (int)(posy*8.0f)){ + 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].dirX = dx*100.0f; + m_carPathLinks[m_numCarPathLinks].dirY = dy*100.0f; + m_carPathLinks[m_numCarPathLinks].x = posx*8.0f; + m_carPathLinks[m_numCarPathLinks].y = posy*8.0f; + m_carPathLinks[m_numCarPathLinks].trafficLightDirection = false; + m_carPathLinks[m_numCarPathLinks].width = width; + 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(tile[iseg].next == jseg && tile[iseg].crossing || + tile[jseg].next == iseg && tile[jseg].crossing) + m_connections[m_numConnections] |= 0x8000; // crosses road flag + } + + 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 < 12; 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]; + + int8 l1Left = m_carPathLinks[l1].numLeftLanes; + int8 l1Right = m_carPathLinks[l1].numRightLanes; + int8 l2Left = m_carPathLinks[l2].numLeftLanes; + int8 l2Right = m_carPathLinks[l2].numRightLanes; + int8 *l1Leftp, *l1Rightp; + int8 *l2Leftp, *l2Rightp; + if(m_carPathLinks[l1].pathNodeIndex == i){ + l1Leftp = &l1Left; + l1Rightp = &l1Right; + }else{ + l1Leftp = &l1Right; + l1Rightp = &l1Left; + } + if(m_carPathLinks[l2].pathNodeIndex == i){ + l2Leftp = &l2Left; + l2Rightp = &l2Right; + }else{ + l2Leftp = &l2Right; + l2Rightp = &l2Left; + } + if(*l1Leftp == -1 && *l2Rightp != -1){ + *l1Leftp = *l2Rightp; + done = 0; + } + if(*l1Rightp == -1 && *l2Leftp != -1){ + *l1Rightp = *l2Leftp; + done = 0; + } + if(*l2Leftp == -1 && *l1Rightp != -1){ + *l2Leftp = *l1Rightp; + done = 0; + } + if(*l2Rightp == -1 && *l1Leftp != -1){ + *l2Rightp = *l1Leftp; + done = 0; + } + if(*l1Leftp == -1 && *l2Rightp == -1) + done = 0; + if(*l2Leftp == -1 && *l1Rightp == -1) + done = 0; + m_carPathLinks[l1].numLeftLanes = l1Left; + m_carPathLinks[l1].numRightLanes = l1Right; + m_carPathLinks[l2].numLeftLanes = l2Left; + m_carPathLinks[l2].numRightLanes = l2Right; + } + } + + // 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 == -1) + m_carPathLinks[k].numLeftLanes = 0; + if(m_carPathLinks[k].numRightLanes == -1) + m_carPathLinks[k].numRightLanes = 0; + } + } + + // Set flags for car nodes + if(type == PATH_CAR){ + do{ + cont = 0; + for(i = 0; i < m_numPathNodes; i++){ + // 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; + } + + i--; + m_numPathNodes--; + } + + delete[] mapObjIndices; +} + +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; + } + } + } + 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]; + } +} + +#ifdef GTA_BRIDGE +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; + } +} +#endif + +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, bool ignoreSelected, bool bWaterPath) +{ + 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; + if(ignoreSelected && m_pathNodes[i].bSelected) continue; + if(bWaterPath != m_pathNodes[i].bWaterPath) continue; + 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; + } + } + 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++){ + 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; + } + } + } + return closestNode; +} + +void +CPathFind::FindNodePairClosestToCoors(CVector coors, uint8 type, int* node1, int* node2, float* angle, float minDist, float maxDist, bool ignoreDisabled, bool ignoreBetweenLevels, bool bWaterPath) +{ + int i, j; + int firstNode, lastNode, connectedNode; + float dist; + float closestDist = 10000.0f; + int closestNode = 0, closestConnectedNode = 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; + if (bWaterPath != m_pathNodes[i].bWaterPath) continue; + 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) { + for (j = 0; j < m_pathNodes[i].numLinks; j++) { + connectedNode = ConnectedNode(m_pathNodes[i].firstLink + j); + if (ignoreDisabled && m_pathNodes[connectedNode].bDisabled) continue; + if (ignoreBetweenLevels && m_pathNodes[connectedNode].bBetweenLevels) continue; + if (bWaterPath != m_pathNodes[connectedNode].bWaterPath) continue; + if ((m_pathNodes[connectedNode].GetPosition() - m_pathNodes[i].GetPosition()).Magnitude() > minDist) { + closestDist = dist; + closestNode = i; + closestConnectedNode = connectedNode; + } + } + } + } + if (closestDist < maxDist) { + *node1 = closestNode; + *node2 = closestConnectedNode; + CVector dir(m_pathNodes[*node2].GetX() - m_pathNodes[*node1].GetX(), m_pathNodes[*node2].GetY() - m_pathNodes[*node1].GetY(), 0.0f); + dir.Normalise(); + *angle = RADTODEG(Atan2(-dir.x, dir.y)); + } + else { + *node1 = -1; + *node2 = -1; + *angle = 0.0f; + } +} + +int32 +CPathFind::FindNthNodeClosestToCoors(CVector coors, uint8 type, float distLimit, bool ignoreDisabled, bool ignoreBetweenLevels, int N, bool bWaterPath) +{ + int i; + int firstNode, lastNode; + 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++) + m_pathNodes[i].bSelected = false; + + for (; N > 0; N--) { + i = FindNodeClosestToCoors(coors, type, distLimit, ignoreDisabled, ignoreBetweenLevels, true, bWaterPath); + if (i < 0) + return -1; + m_pathNodes[i].bSelected = true; + } + return FindNodeClosestToCoors(coors, type, distLimit, ignoreDisabled, ignoreBetweenLevels, true, bWaterPath); +} + +CVector +CPathFind::FindNodeCoorsForScript(int32 id) +{ + // the point is to return valid position in case there is a divider in the middle of the road + if (!m_pathNodes[id].HasDivider() || m_pathNodes[id].numLinks == 0) + return m_pathNodes[id].GetPosition(); + CVector2D dir(m_pathNodes[ConnectedNode(m_pathNodes[id].firstLink)].GetX() - m_pathNodes[id].GetX(), + m_pathNodes[ConnectedNode(m_pathNodes[id].firstLink)].GetY() - m_pathNodes[id].GetY()); + dir.Normalise(); + if (dir.x < 0) + dir = -dir; + return m_pathNodes[id].GetPosition() + CVector(-dir.y, dir.x, 0.0f) * (LANE_WIDTH / 2 + m_pathNodes[id].GetDividerWidth()); +} + +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::GenerateCarCreationCoors(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 < Max(spawnDist + 70.0f, spawnDist * 1.7f)){ + d1 = m_pathNodes[node1].bWaterPath ? (dist1 - spawnDist * 1.5f) : (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 = m_pathNodes[node2].bWaterPath ? (dist2 - spawnDist * 1.5f) : (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; + float node1_dist, node2_dist; + static int32 node_cnt; + + if(m_numPedPathNodes == 0) + return false; + + for(i = 0; i < 230; i++){ + if (node_cnt++ >= m_numPedPathNodes) + node_cnt = 0; + node1 = node_cnt + m_numCarPathNodes; + node1_dist = Distance2D(m_pathNodes[node1].GetPosition(), x, y); + if(node1_dist < maxDist+30.0f){ + if(m_pathNodes[node1].numLinks != 0) + break; + } + } + if (i >= 230) + return false; + + for(i = 0; i < m_pathNodes[node1].numLinks; i++){ + int link = m_pathNodes[node1].firstLink + i; + if(ConnectionCrossesRoad(link)) + continue; + node2 = ConnectedNode(link); + if(m_pathNodes[node1].bDisabled || m_pathNodes[node2].bDisabled) + continue; + node2_dist = Distance2D(m_pathNodes[node2].GetPosition(), x, y); + if ((node1_dist < maxDist || node2_dist < maxDist) && (node1_dist > minDistOffScreen || node2_dist > minDistOffScreen)) + break; + } + if(i >= m_pathNodes[node1].numLinks) + return false; + + for(i = 0; i < 5; i++){ + 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(visible && (minDist < dist && dist < maxDist) || + !visible && (minDistOffScreen < dist && dist < maxDistOffScreen)){ + *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; +} + +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){ + int32 nodeIdx = FindNodeClosestToCoors(coors, type, 999999.88f); + node = &m_pathNodes[nodeIdx]; + } + + 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[6525]; + +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 + if(startNodeId < 0) + startNodeId = FindNodeClosestToCoors(start, type, 999999.88f); + if(startNodeId < 0) { + *pNumNodes = 0; + if(pDist) *pDist = 100000.0f; + return; + } + if(startNodeId == targetNodeId){ + *pNumNodes = 0; + if(pDist) *pDist = 0.0f; + return; + } + 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; + for(i = 0; numPathsFound == 0; i = (i+1) & 0x1FF){ + CPathNode *node; + for(node = m_searchNodes[i].GetNext(); node; node = node->GetNext()){ + if(node == &m_pathNodes[startNodeId]) + numPathsFound = 1; + + 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; + curNode = &m_pathNodes[startNodeId]; + *pNumNodes = 0; + if(pDist) + *pDist = m_pathNodes[startNodeId].distance; + + nodes[(*pNumNodes)++] = curNode; + // 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; +} + +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 < 150.0f*TheCamera.GenerationDistMultiplier; + else + return dist < 100.0f*TheCamera.GenerationDistMultiplier; +#else + if(type == PATH_CAR) + return dist < 150.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; + +#ifdef SECUROM + // if pirated game + for(i = 0; i < m_numPathNodes; i++) + m_pathNodes[i].bDisabled = true; +#endif +} + +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); + } + } +} + +CVector +CPathFind::TakeWidthIntoAccountForWandering(CPathNode* nextNode, uint16 random) +{ + CVector pos = nextNode->GetPosition(); + float newX = (nextNode->GetPedNodeWidth() * ((random % 16) - 7)) + pos.x; + float newY = (nextNode->GetPedNodeWidth() * (((random / 16) % 16) - 7)) + pos.y; + return CVector(newX, newY, pos.z); +} + +void +CPathFind::TakeWidthIntoAccountForCoors(CPathNode* node1, CPathNode* node2, uint16 random, float* x, float* y) +{ + *x += (Min(node1->width, node2->width) * WIDTH_TO_PED_NODE_WIDTH * ((random % 16) - 7)); + *y += (Min(node1->width, node2->width) * WIDTH_TO_PED_NODE_WIDTH * (((random / 16) % 16) - 7)); +} + +CPathNode* +CPathFind::GetNode(int16 index) +{ + if(index < 0) + return nil; + if(index < ARRAY_SIZE(ThePaths.m_searchNodes)) + return &ThePaths.m_searchNodes[index]; + return &ThePaths.m_pathNodes[index - ARRAY_SIZE(ThePaths.m_searchNodes)]; +} +int16 +CPathFind::GetIndex(CPathNode *node) +{ + if(node == nil) + return -1; + if(node >= &ThePaths.m_searchNodes[0] && node < &ThePaths.m_searchNodes[ARRAY_SIZE(ThePaths.m_searchNodes)]) + return node - ThePaths.m_searchNodes; + else + return (node - ThePaths.m_pathNodes) + ARRAY_SIZE(ThePaths.m_searchNodes); +} diff --git a/src/miami/control/PathFind.h b/src/miami/control/PathFind.h new file mode 100644 index 00000000..99759590 --- /dev/null +++ b/src/miami/control/PathFind.h @@ -0,0 +1,304 @@ +#pragma once + +#include "Treadable.h" + +class CVehicle; +class CPtrList; + +#define LANE_WIDTH 5.0f +#define WIDTH_TO_PED_NODE_WIDTH (31.f/(500.f * 8.f)) + +enum +{ + NodeTypeExtern = 1, + NodeTypeIntern = 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); + static void AddBuildingBlockade(CEntity*, CPedPathNode(*)[40], CVector*); + static void AddBuildingBlockadeSectorList(CPtrList&, CPedPathNode(*)[40], CVector*); +}; + +struct CPathNode +{ + int16 prevIndex; + int16 nextIndex; + int16 x; + int16 y; + int16 z; + int16 distance; // in path search + int16 firstLink; + uint8 width; + int8 group; + + uint8 numLinks : 4; + uint8 bDeadEnd : 1; + uint8 bDisabled : 1; + uint8 bBetweenLevels : 1; + uint8 bUseInRoadBlock : 1; + + uint8 bWaterPath : 1; + uint8 bOnlySmallBoats : 1; + uint8 bSelected : 1; + uint8 speedLimit : 2; + //uint8 flagB20 : 1; + //uint8 flagB40 : 1; + //uint8 flagB80 : 1; + + uint8 spawnRate : 4; + uint8 flagsC : 4; + + CVector GetPosition(void) { return CVector(x/8.0f, y/8.0f, z/8.0f); } + void SetPosition(const CVector &p) { x = p.x*8.0f; y = p.y*8.0f; z = p.z*8.0f; } + float GetX(void) { return x/8.0f; } + float GetY(void) { return y/8.0f; } + float GetZ(void) { return z/8.0f; } + bool HasDivider(void) { return width != 0; } + float GetDividerWidth(void) { return width/(2*8.0f); } + float GetPedNodeWidth(void) { return width*WIDTH_TO_PED_NODE_WIDTH; } + CPathNode *GetPrev(void); + CPathNode *GetNext(void); + void SetPrev(CPathNode *node); + void SetNext(CPathNode *node); +}; + +union CConnectionFlags +{ + uint8 flags; + struct { + uint8 bCrossesRoad : 1; + uint8 bTrafficLight : 1; + }; +}; + +struct CCarPathLink +{ + int16 x; + int16 y; + int16 pathNodeIndex; + int8 dirX; + int8 dirY; + int8 numLeftLanes : 3; + int8 numRightLanes : 3; + uint8 trafficLightDirection : 1; + uint8 trafficLightType : 2; + uint8 bBridgeLights : 1; // at least in LCS... + uint8 width; + + CVector2D GetPosition(void) { return CVector2D(x/8.0f, y/8.0f); } + CVector2D GetDirection(void) { return CVector2D(dirX/100.0f, dirY/100.0f); } + float GetX(void) { return x/8.0f; } + float GetY(void) { return y/8.0f; } + float GetDirX(void) { return dirX/100.0f; } + float GetDirY(void) { return dirY/100.0f; } + float GetLaneOffset(void) { return width/(2*8.0f*LANE_WIDTH); } + + float OneWayLaneOffset() + { + if (numLeftLanes == 0) + return 0.5f - 0.5f * numRightLanes; + if (numRightLanes == 0) + return 0.5f - 0.5f * numLeftLanes; + return 0.5f + GetLaneOffset(); + } +}; + +// This is what we're reading from the files, only temporary +struct CPathInfoForObject +{ + float x; + float y; + float z; + int8 type; + int8 next; + int8 numLeftLanes; + int8 numRightLanes; + int8 speedLimit; + uint8 width; + + uint8 crossing : 1; + uint8 onlySmallBoats : 1; + uint8 roadBlock : 1; + uint8 disabled : 1; + uint8 waterPath : 1; + uint8 betweenLevels : 1; + + uint8 spawnRate : 4; + + void CheckIntegrity(void); + void SwapConnectionsToBeRightWayRound(void); +}; +extern CPathInfoForObject *InfoForTileCars; +extern CPathInfoForObject *InfoForTilePeds; + +struct CTempNode +{ + CVector pos; + int8 dirX; // *100 + int8 dirY; + int16 link1; + int16 link2; + int8 numLeftLanes; + int8 numRightLanes; + uint8 width; + bool isCross; + int8 linkState; +}; + +struct CTempNodeExternal // made up name +{ + CVector pos; + int16 next; + int8 numLeftLanes; + int8 numRightLanes; + uint8 width; + bool isCross; +}; + +// from mobile +template +class CRoute +{ + T m_node[8]; +}; + + +class CPathFind +{ +public: + CPathNode m_pathNodes[NUM_PATHNODES]; + CCarPathLink m_carPathLinks[NUM_CARPATHLINKS]; + CTreadable *m_mapObjects[NUM_MAPOBJECTS]; + uint16 m_connections[NUM_PATHCONNECTIONS]; // and flags + uint8 m_distances[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, float width, bool crossing, uint8 spawnRate); + void StoreNodeInfoCar(int16 id, int16 node, int8 type, int8 next, int16 x, int16 y, int16 z, float width, int8 numLeft, int8 numRight, + bool disabled, bool betweenLevels, uint8 speedLimit, bool roadBlock, bool waterPath, uint8 spawnRate); + void StoreDetachedNodeInfoPed(int32 node, int8 type, int32 next, float x, float y, float z, float width, bool crossing, + bool disabled, bool betweenLevels, uint8 spawnRate); + void StoreDetachedNodeInfoCar(int32 node, int8 type, int32 next, float x, float y, float z, float width, int8 numLeft, int8 numRight, + bool disabled, bool betweenLevels, uint8 speedLimit, bool roadBlock, bool waterPath, uint8 spawnRate, bool unk); + void CalcNodeCoors(float x, float y, float 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, CPathInfoForObject *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, bool ignoreSelected = false, bool bWaterPath = false); + int32 FindNodeClosestToCoorsFavourDirection(CVector coors, uint8 type, float dirX, float dirY); + void FindNodePairClosestToCoors(CVector coors, uint8 type, int* node1, int* node2, float* angle, float minDist, float maxDist, bool ignoreDisabled = false, bool ignoreBetweenLevels = false, bool bWaterPath = false); + int32 FindNthNodeClosestToCoors(CVector coors, uint8 type, float distLimit, bool ignoreDisabled, bool ignoreBetweenLevels, int N, bool bWaterPath = false); + CVector FindNodeCoorsForScript(int32 id); + float FindNodeOrientationForCarPlacement(int32 nodeId); + float FindNodeOrientationForCarPlacementFacingDestination(int32 nodeId, float x, float y, bool towards); + bool GenerateCarCreationCoors(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); + 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); + + static CVector TakeWidthIntoAccountForWandering(CPathNode*, uint16); + static void TakeWidthIntoAccountForCoors(CPathNode*, CPathNode*, uint16, float*, float*); + + CPathNode *GetNode(int16 index); + int16 GetIndex(CPathNode *node); + + uint16 ConnectedNode(int id) { return m_connections[id] & 0x3FFF; } + bool ConnectionCrossesRoad(int id) { return !!(m_connections[id] & 0x8000); } + bool ConnectionHasTrafficLight(int id) { return !!(m_connections[id] & 0x4000); } + void ConnectionSetTrafficLight(int id) { m_connections[id] |= 0x4000; } + + void DisplayPathData(void); + + // Following methods are present on mobile but are unused. TODO: implement them + void SavePathFindData(void); + void ComputeRoute(uint8, const CVector&, const CVector&, CRoute&); + void RecordNodesClosestToCoors(CVector, uint8, int, CPathNode**, float, bool, bool, bool); + void RecordNodesInCircle(const CVector&, float, uint8, int, CPathNode**, bool, bool, bool, bool); + void ArrangeOneNodeList(CPathInfoForObject*, int16); + void ArrangeNodes(int16); + void RegisterMarker(CVector*); + void Shutdown(void); +}; + +extern CPathFind ThePaths; + +inline CPathNode *CPathNode::GetPrev(void) { return ThePaths.GetNode(prevIndex); } +inline CPathNode *CPathNode::GetNext(void) { return ThePaths.GetNode(nextIndex); } +inline void CPathNode::SetPrev(CPathNode *node) { prevIndex = ThePaths.GetIndex(node); } +inline void CPathNode::SetNext(CPathNode *node) { nextIndex = ThePaths.GetIndex(node); } + +extern bool gbShowPedPaths; +extern bool gbShowCarPaths; +extern bool gbShowCarPathsLinks; diff --git a/src/miami/control/Phones.cpp b/src/miami/control/Phones.cpp new file mode 100644 index 00000000..41f9d766 --- /dev/null +++ b/src/miami/control/Phones.cpp @@ -0,0 +1,404 @@ +#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. +*/ + +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++) { + + if (gPhoneInfo.m_aPhones[phoneId].m_nState == PHONE_STATE_FREE) { + 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]; +} + +void +CPhoneInfo::Load(uint8 *buf, uint32 size) +{ +INITSAVEBUF + ReadSaveBuf(&m_nMax, buf); + ReadSaveBuf(&m_nScriptPhonesMax, buf); + for (int i = 0; i < NUMPHONES; i++) { +#ifdef COMPATIBLE_SAVES + ReadSaveBuf(&m_aPhones[i].m_vecPos, buf); + SkipSaveBuf(buf, 6 * 4); + ReadSaveBuf(&m_aPhones[i].m_repeatedMessagePickupStart, buf); + int32 tmp; + ReadSaveBuf(&tmp, buf); + // It's saved as building pool index in save file, convert it to true entity + m_aPhones[i].m_pEntity = tmp != 0 ? CPools::GetBuildingPool()->GetSlot(tmp - 1) : nil; + ReadSaveBuf(&m_aPhones[i].m_nState, buf); + ReadSaveBuf(&m_aPhones[i].m_visibleToCam, buf); + SkipSaveBuf(buf, 3); +#else + ReadSaveBuf(&m_aPhones[i], buf); + // 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; + } +} + +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); + for(int phoneId = 0; phoneId < NUMPHONES; phoneId++) { +#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/miami/control/Phones.h b/src/miami/control/Phones.h new file mode 100644 index 00000000..81b40dc2 --- /dev/null +++ b/src/miami/control/Phones.h @@ -0,0 +1,69 @@ +#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); +}; + +extern CPhoneInfo gPhoneInfo; + +void PhonePutDownCB(CAnimBlendAssociation *assoc, void *arg); +void PhonePickUpCB(CAnimBlendAssociation *assoc, void *arg); \ No newline at end of file diff --git a/src/miami/control/Pickups.cpp b/src/miami/control/Pickups.cpp new file mode 100644 index 00000000..86627713 --- /dev/null +++ b/src/miami/control/Pickups.cpp @@ -0,0 +1,1742 @@ +#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" +#include "Hud.h" +#include "Messages.h" +#include "Streaming.h" +#include "SaveBuf.h" + +#ifdef COMPATIBLE_SAVES +#define PICKUPS_SAVE_SIZE 0x4440 +#else +#define PICKUPS_SAVE_SIZE sizeof(aPickUps) +#endif + +CPickup CPickups::aPickUps[NUMPICKUPS]; +int16 CPickups::NumMessages; +int32 CPickups::aPickUpsCollected[NUMCOLLECTEDPICKUPS]; +int16 CPickups::CollectedPickUpIndex; + +int32 CPickups::PlayerOnWeaponPickup; +int32 CollectPickupBuffer; + +// unused +bool CPickups::bPickUpcamActivated; +CVehicle *CPickups::pPlayerVehicle; +CVector CPickups::StaticCamCoors; +uint32 CPickups::StaticCamStartTime; + +tPickupMessage CPickups::aMessages[NUMPICKUPMESSAGES]; + +uint16 AmmoForWeapon[WEAPONTYPE_TOTALWEAPONS + 1] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 8, 8, 8, 8, 8, 68, 24, + 32, 28, 20, 200, 120, 120, 120, 120, 120, 40, 28, 8, 300, 200, 1000, 1, 400, 36, 0 }; + +uint16 AmmoForWeapon_OnStreet[WEAPONTYPE_TOTALWEAPONS + 1] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 34, 12, + 16, 14, 10, 100, 60, 60, 60, 60, 60, 20, 14, 4, 150, 100, 500, 1, 400, 36, 0 }; + +uint16 CostOfWeapon[WEAPONTYPE_TOTALWEAPONS + 3] = { 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1000, 1000, + 1000, 500, 8000, 250, 400, 1200, 1250, 1250, 800, 800, 650, 1200, 5000, 400, + 10000, 10000, 8000, 8000, 8000, 10000, 1000, 11000, 500, 20, 10, 0 }; + +struct +{ + uint8 r,g,b; + float unk; +} aPickupColors[] = { + { 128, 128, 128, 1.0f }, + { 128, 128, 128, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 97, 194, 247, 1.0f }, + { 27, 89, 130, 1.0f }, + { 27, 89, 130, 1.0f }, + { 27, 89, 130, 1.0f }, + { 27, 89, 130, 1.0f }, + { 27, 89, 130, 1.0f }, + { 149, 194, 24, 1.0f }, + { 149, 194, 24, 1.0f }, + { 45, 155, 90, 1.0f }, + { 45, 155, 90, 1.0f }, + { 45, 155, 90, 1.0f }, + { 255, 227, 79, 1.0f }, + { 255, 227, 79, 1.0f }, + { 255, 227, 79, 1.0f }, + { 255, 227, 79, 1.0f }, + { 254, 137, 0, 1.0f }, + { 254, 137, 0, 1.0f }, + { 249, 131, 215, 1.0f }, + { 249, 131, 215, 1.0f }, + { 164, 40, 178, 1.0f }, + { 164, 40, 178, 1.0f }, + { 164, 40, 178, 1.0f }, + { 164, 40, 178, 1.0f }, + { 69, 69, 69, 1.0f }, + { 69, 69, 69, 1.0f }, + { 69, 69, 69, 1.0f }, + { 255, 100, 100, 1.0f }, + { 128, 255, 128, 1.0f }, + { 100, 100, 255, 1.0f }, + { 255, 255, 100, 1.0f }, + { 255, 100, 100, 1.0f }, + { 100, 255, 100, 1.0f }, + { 255, 255, 255, 1.0f } +}; + + +void +ModifyStringLabelForControlSetting(char *str) +{ + int len = (int)strlen(str); + if (len <= 2) + return; + + if (str[len - 2] != '_') + return; + + switch (CPad::GetPad(0)->Mode) { + case 0: + case 1: + str[len - 1] = 'L'; + break; + case 2: + str[len - 1] = 'T'; + break; + case 3: + str[len - 1] = 'C'; + break; + default: + return; + } +} + +void +CPickup::ExtractAmmoFromPickup(CPlayerPed *player) +{ + eWeaponType weaponType = CPickups::WeaponForModel(m_pObject->GetModelIndex()); + + if (m_eType == PICKUP_IN_SHOP || !CWeaponInfo::IsWeaponSlotAmmoMergeable(CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot)) + return; + + uint32 ammo = m_nQuantity; + if (ammo == 0) { + if (!m_bWasAmmoCollected) + ammo = AmmoForWeapon_OnStreet[weaponType]; + else + goto removeAmmo; + } + player->GrantAmmo(weaponType, ammo); + DMAudio.PlayOneShot(player->m_audioEntityId, SOUND_WEAPON_RELOAD, weaponType); // BUG? weapon type as volume, wtf? +removeAmmo: + m_nQuantity = 0; + m_bWasAmmoCollected = true; +} + +void +CPickup::Remove() +{ + GetRidOfObjects(); + m_bRemoved = true; + m_eType = PICKUP_NONE; +} + +CObject * +CPickup::GiveUsAPickUpObject(CObject **ppObject, CObject **ppExtraObject, int32 handle, int32 extraHandle) +{ + CObject *&object = *ppObject; + CObject *&extraObject = *ppExtraObject; + + object = extraObject = nil; + + int32 modelId = -1; + if (CModelInfo::GetModelInfo(m_eModelIndex)->GetModelType() == MITYPE_WEAPON) { + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(((CWeaponModelInfo*)CModelInfo::GetModelInfo(m_eModelIndex))->GetWeaponInfo()); + modelId = weaponInfo->m_nModelId; + if (modelId == m_eModelIndex) + modelId = weaponInfo->m_nModel2Id; + } + + if (handle >= 0) { + CPools::MakeSureSlotInObjectPoolIsEmpty(handle); + if (extraHandle >= 0) + CPools::MakeSureSlotInObjectPoolIsEmpty(extraHandle); + if (object == nil) + object = new(handle) CObject(m_eModelIndex, false); + + if (extraHandle >= 0 && modelId != -1 && extraObject == nil) + extraObject = new(extraHandle) CObject(modelId, false); + } else { + object = new CObject(m_eModelIndex, false); + if (modelId != -1) + extraObject = new CObject(modelId, 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->bAmmoCollected = m_bWasAmmoCollected; + object->bHasPreRenderEffects = true; + + if (extraObject) { + extraObject->ObjectCreatedBy = MISSION_OBJECT; + extraObject->SetPosition(m_vecPos); + extraObject->SetOrientation(0.0f, 0.0f, -HALFPI); + extraObject->GetMatrix().UpdateRW(); + extraObject->UpdateRwFrame(); + + extraObject->bAffectedByGravity = false; + extraObject->bExplosionProof = true; + extraObject->bUsesCollision = false; + extraObject->bIsPickup = true; + extraObject->bAmmoCollected = true; + extraObject->bHasPreRenderEffects = true; + extraObject->m_nBonusValue = 0; + extraObject->bPickupObjWithMessage = false; + extraObject->bOutOfStock = false; + } + + object->m_nBonusValue = (m_eModelIndex == MI_PICKUP_BONUS || m_eModelIndex == MI_PICKUP_CLOTHES) ? m_nQuantity : 0; + + switch (m_eType) + { + case PICKUP_IN_SHOP: + object->bPickupObjWithMessage = true; + object->bOutOfStock = false; + if (m_eModelIndex == MI_PICKUP_HEALTH || m_eModelIndex == MI_PICKUP_ADRENALINE) + object->m_nCostValue = 0; + else + object->m_nCostValue = CostOfWeapon[CPickups::WeaponForModel(m_eModelIndex)]; + 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, int playerId) +{ + assert(m_pObject != nil); + bool cannotBePickedUp = + (m_pObject->GetModelIndex() == MI_PICKUP_BODYARMOUR && player->m_fArmour > CWorld::Players[playerId].m_nMaxArmour - 0.2f) + || (m_pObject->GetModelIndex() == MI_PICKUP_HEALTH && player->m_fHealth > CWorld::Players[playerId].m_nMaxHealth - 0.2f) + || (m_pObject->GetModelIndex() == MI_PICKUP_BRIBE && player->m_pWanted->GetWantedLevel() == 0) + || (m_pObject->GetModelIndex() == MI_PICKUP_KILLFRENZY && (CTheScripts::IsPlayerOnAMission() || CDarkel::FrenzyOnGoing() || !CGame::nastyGame)) + || (m_eType == PICKUP_ASSET_REVENUE && m_fRevenue < 10.0f); + return !cannotBePickedUp; +} + +bool +CPickup::Update(CPlayerPed *player, CVehicle *vehicle, int playerId) +{ + bool result = false; + float waterLevel; + + if (m_pObject) { + m_pObject->GetMatrix().GetPosition() = m_vecPos; + if (m_pExtraObject) + m_pExtraObject->GetMatrix().GetPosition() = m_vecPos; + } + if (m_eType == PICKUP_ASSET_REVENUE) { + uint32 timePassed = CTimer::GetTimeInMilliseconds() - m_nTimer; + m_nTimer = CTimer::GetTimeInMilliseconds(); + + if (Distance(FindPlayerCoors(), m_vecPos) > 10.0f) + m_fRevenue += float(timePassed * m_nMoneySpeed) / SQR(1200.0f); + + m_fRevenue = Min(m_fRevenue, m_nQuantity); + + m_pObject->m_nCostValue = m_fRevenue < 10 ? 0 : m_fRevenue; + } + + 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(&m_pObject, &m_pExtraObject, -1, -1); + if (m_pObject) { + CWorld::Add(m_pObject); + m_bRemoved = false; + } + } + } + return false; + } + + if (!m_pObject) { + GiveUsAPickUpObject(&m_pObject, &m_pExtraObject, -1, -1); + if (m_pObject) + CWorld::Add(m_pObject); + if (m_pExtraObject) + CWorld::Add(m_pExtraObject); + } + + 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 (isPickupTouched) { + eWeaponType weaponType = CPickups::WeaponForModel(m_pObject->GetModelIndex()); + if (weaponType < WEAPONTYPE_TOTALWEAPONS && CDarkel::FrenzyOnGoing()) { + isPickupTouched = false; + m_bWasControlMessageShown = false; + } else if (weaponType < WEAPONTYPE_TOTALWEAPONS && weaponType != WEAPONTYPE_UNARMED) { + uint32 slot = CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot; + eWeaponType plrWeaponSlot = FindPlayerPed()->GetWeapon(slot).m_eWeaponType; + if (plrWeaponSlot != weaponType) { + if (CStreaming::ms_aInfoForModel[m_pObject->GetModelIndex()].m_loadState == STREAMSTATE_LOADED) { + if (plrWeaponSlot == WEAPONTYPE_UNARMED || (FindPlayerPed()->GetWeapon(slot).m_nAmmoTotal == 0 && !CWeaponInfo::IsWeaponSlotAmmoMergeable(slot))) { + if (CTimer::GetTimeInMilliseconds() - FindPlayerPed()->m_nPadDownPressedInMilliseconds < 1500) { + CPickups::PlayerOnWeaponPickup = 6; + isPickupTouched = false; + } + } else { + CPickups::PlayerOnWeaponPickup = 6; + if (CWeaponInfo::IsWeaponSlotAmmoMergeable(slot)) { + if (m_eType == PICKUP_ONCE_TIMEOUT || m_eType == PICKUP_ONCE || m_eType == PICKUP_ON_STREET) { + ExtractAmmoFromPickup(player); + FindPlayerPed()->GetWeapon(slot).Reload(); + } + } + if (!m_bWasControlMessageShown) { + switch (CPad::GetPad(0)->Mode) + { + case 0: + case 1: + CHud::SetHelpMessage(TheText.Get("PU_CF1"), false); + break; + case 2: + CHud::SetHelpMessage(TheText.Get("PU_CF3"), false); + break; + case 3: + CHud::SetHelpMessage(TheText.Get("PU_CF4"), false); + break; + default: + break; + } + m_bWasControlMessageShown = true; + } + if (CollectPickupBuffer == 0) + isPickupTouched = false; + if (CTimer::GetTimeInMilliseconds() - FindPlayerPed()->m_nPadDownPressedInMilliseconds < 1500) + isPickupTouched = false; + } + } else + isPickupTouched = false; + } + } + } else + m_bWasControlMessageShown = false; + + // if we didn't then we've got nothing to do + if (isPickupTouched && CanBePickedUp(player, playerId)) { + if (m_pObject->GetModelIndex() != MI_PICKUP_PROPERTY && m_pObject->GetModelIndex() != MI_PICKUP_PROPERTY_FORSALE) + CPad::GetPad(0)->StartShake(120, 100); + + eWeaponType weaponType = CPickups::WeaponForModel(m_pObject->GetModelIndex()); + switch (m_eType) + { + case PICKUP_IN_SHOP: + if (CWorld::Players[playerId].m_nMoney < CostOfWeapon[weaponType]) + CGarages::TriggerMessage("PU_MONY", -1, 6000, -1); + else { + CWorld::Players[playerId].m_nMoney -= CostOfWeapon[weaponType]; + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + if (!player->DoesPlayerWantNewWeapon(weaponType, false)) + break; + player->GiveWeapon(weaponType, AmmoForWeapon[weaponType]); + player->m_nSelectedWepSlot = player->GetWeaponSlot(weaponType); + DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON_BOUGHT, m_pObject->GetModelIndex() - MI_GRENADE); + } + result = true; + Remove(); + } + break; + case PICKUP_ON_STREET: + case PICKUP_ON_STREET_SLOW: + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + if (!player->DoesPlayerWantNewWeapon(weaponType, false)) + break; + if (weaponType != WEAPONTYPE_UNARMED) { + player->GiveWeapon(weaponType, m_nQuantity != 0 ? m_nQuantity : (m_bWasAmmoCollected ? 0 : AmmoForWeapon_OnStreet[weaponType]), true); + + if (player->m_nSelectedWepSlot == player->GetWeaponSlot(WEAPONTYPE_UNARMED)) + player->m_nSelectedWepSlot = player->GetWeaponSlot(weaponType); + + 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; + GetRidOfObjects(); + m_bRemoved = true; + break; + case PICKUP_ONCE: + case PICKUP_ONCE_TIMEOUT: + case PICKUP_ONCE_TIMEOUT_SLOW: + if (!CPickups::GivePlayerGoodiesWithPickUpMI(m_pObject->GetModelIndex(), playerId)) { + if (!player->DoesPlayerWantNewWeapon(weaponType, false)) { + ExtractAmmoFromPickup(player); + break; + } + + if (weaponType != WEAPONTYPE_UNARMED) { + player->GiveWeapon(weaponType, m_nQuantity != 0 ? m_nQuantity : (m_bWasAmmoCollected ? 0 : AmmoForWeapon[weaponType]), true); + if (player->m_nSelectedWepSlot == player->GetWeaponSlot(WEAPONTYPE_UNARMED)) + player->m_nSelectedWepSlot = player->GetWeaponSlot(weaponType); + } + if (MI_PICKUP_SAVEGAME != 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 += 100; + + 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 += 100000; + } 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); + player->Say(SOUND_PED_MUGGING); + break; + case PICKUP_ASSET_REVENUE: + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += m_fRevenue; + m_fRevenue = 0.0f; + DMAudio.PlayFrontEndSound(SOUND_PICKUP_MONEY, 0); + break; + case PICKUP_PROPERTY_LOCKED: + if (!m_bWasControlMessageShown) { + m_bWasControlMessageShown = true; + CHud::SetHelpMessage(TheText.Get(m_sTextKey), false); + } + break; + case PICKUP_PROPERTY_FORSALE: + ModifyStringLabelForControlSetting(m_sTextKey); + CMessages::InsertNumberInString(TheText.Get(m_sTextKey), m_nQuantity, + 0, 0, 0, 0, 0, gUString); + if (!CHud::IsHelpMessageBeingDisplayed()) + CHud::SetHelpMessage(gUString, false); + if (CollectPickupBuffer == 0) + break; + if (CTheScripts::IsPlayerOnAMission()) + CHud::SetHelpMessage(TheText.Get("PROP_2"), true); + else { + if (CWorld::Players[CWorld::PlayerInFocus].m_nMoney >= m_nQuantity) { + CWorld::Players[CWorld::PlayerInFocus].m_nMoney -= m_nQuantity; + CHud::SetHelpMessage(nil, true); + result = true; + Remove(); + break; + } + CHud::SetHelpMessage(TheText.Get("PROP_1"), true); + } + 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; // added break here +#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; // added break here +#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_ONCE_TIMEOUT_SLOW || m_eType == PICKUP_MONEY) && CTimer::GetTimeInMilliseconds() > m_nTimer) + Remove(); + + return result; +} + +void +CPickup::ProcessGunShot(CVector *vec1, CVector *vec2) +{ + CColLine line(*vec1, *vec2); + if (m_pObject) { + CColSphere sphere; + sphere.radius = 4.0f; + sphere.center = m_pObject->GetPosition(); + if (CCollision::TestLineSphere(line, sphere)) { + CExplosion::AddExplosion(nil, nil, EXPLOSION_MINE, m_pObject->GetPosition(), 0); + CWorld::Remove(m_pObject); + delete m_pObject; + m_pObject = nil; + m_bRemoved = true; + m_eType = PICKUP_NONE; + } + } +} + +void +CPickup::GetRidOfObjects() +{ + if (m_pObject) { + CWorld::Remove(m_pObject); + delete m_pObject; + m_pObject = nil; + } + if (m_pExtraObject) { + CWorld::Remove(m_pExtraObject); + delete m_pExtraObject; + m_pExtraObject = nil; + } +} + +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; + aPickUps[i].m_pExtraObject = nil; + } + + for (int i = 0; i < NUMCOLLECTEDPICKUPS; i++) + aPickUpsCollected[i] = 0; + + CollectedPickUpIndex = 0; +} + +bool +CPickups::TestForPickupsInBubble(CVector pos, float range) +{ + for (int i = 0; i < NUMPICKUPS; i++) { + if ((aPickUps[i].m_vecPos - pos).Magnitude() < range) + return true; + } + return false; +} + +bool +CPickups::TryToMerge_WeaponType(CVector pos, eWeaponType weapon, uint8 type, uint32 quantity, bool unused) { + for (int i = 0; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType == type && aPickUps[i].m_eModelIndex == ModelForWeapon(weapon)) + if ((aPickUps[i].m_vecPos - pos).Magnitude() < 7.5f) { + aPickUps[i].m_nQuantity += quantity; + return true; + } + } + return false; +} + +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 && aPickUps[i].m_eType != PICKUP_ASSET_REVENUE) { + 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 = CWorld::Players[playerIndex].m_nMaxArmour; + 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 = CWorld::Players[playerIndex].m_nMaxHealth; + 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 = Max(FindPlayerPed()->m_pWanted->GetWantedLevel() - 1, 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::RemovePickUp(int32 pickupIndex) +{ + int32 index = 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; + } + if (aPickUps[index].m_pExtraObject) { + CWorld::Remove(aPickUps[index].m_pExtraObject); + delete aPickUps[index].m_pExtraObject; + aPickUps[index].m_pExtraObject = nil; + } + aPickUps[index].m_eType = PICKUP_NONE; + aPickUps[index].m_bRemoved = true; +} + +int32 +CPickups::GenerateNewOne(CVector pos, uint32 modelIndex, uint8 type, uint32 quantity, uint32 rate, bool highPriority, char* pText) +{ + bool bFreeFound = false; + int32 slot = 0; + + if (type == PICKUP_FLOATINGPACKAGE || type == PICKUP_NAUTICAL_MINE_INACTIVE || highPriority) { + for (slot = NUMPICKUPS-1; slot >= 0; 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_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 || aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT_SLOW) break; + } + + if (slot >= NUMGENERALPICKUPS) return -1; + aPickUps[slot].GetRidOfObjects(); + } + } + + if (slot >= NUMPICKUPS) return -1; + + aPickUps[slot].m_eType = type; + aPickUps[slot].m_bRemoved = false; + aPickUps[slot].m_nQuantity = quantity; + aPickUps[slot].m_nMoneySpeed = rate; + aPickUps[slot].m_fRevenue = 0.0f; + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds(); + aPickUps[slot].m_bWasAmmoCollected = highPriority; + aPickUps[slot].m_bWasControlMessageShown = false; + if (type == PICKUP_ONCE_TIMEOUT) + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 20000; + else if (type == PICKUP_ONCE_TIMEOUT_SLOW) + aPickUps[slot].m_nTimer = CTimer::GetTimeInMilliseconds() + 120000; + 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; + if (pText) + strncpy(aPickUps[slot].m_sTextKey, pText, 8); + else + aPickUps[slot].m_sTextKey[0] = '\0'; + + aPickUps[slot].m_vecPos = pos; + aPickUps[slot].m_pObject = aPickUps[slot].GiveUsAPickUpObject(&aPickUps[slot].m_pObject, &aPickUps[slot].m_pExtraObject, -1, -1); + if (aPickUps[slot].m_pObject) + CWorld::Add(aPickUps[slot].m_pObject); + if (aPickUps[slot].m_pExtraObject) + CWorld::Add(aPickUps[slot].m_pExtraObject); + 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) +{ + return CWeaponInfo::GetWeaponInfo(weaponType)->m_nModelId; +} + +eWeaponType +CPickups::WeaponForModel(int32 model) +{ + if (model == MI_PICKUP_BODYARMOUR) return WEAPONTYPE_ARMOUR; + if (model == MI_PICKUP_HEALTH) return WEAPONTYPE_HEALTH; + if (model == MI_PICKUP_ADRENALINE) return WEAPONTYPE_ARMOUR; + if (model == -1) return WEAPONTYPE_UNARMED; + return ((CWeaponModelInfo*)CModelInfo::GetModelInfo(model))->GetWeaponInfo(); +} + +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 + if (CPad::GetPad(0)->CollectPickupJustDown()) + CollectPickupBuffer = 6; + else + CollectPickupBuffer = Max(0, CollectPickupBuffer - 1); + + if (PlayerOnWeaponPickup) + PlayerOnWeaponPickup = Max(0, PlayerOnWeaponPickup - 1); + +#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); + } +} + +CPickup* +CPickups::FindPickUpForThisObject(CEntity *object) +{ + for (uint32 i = 0; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType != PICKUP_NONE && (aPickUps[i].m_pObject == object || aPickUps[i].m_pExtraObject == object)) { + return &aPickUps[i]; + } + } + return &aPickUps[0]; +} + +void +CPickups::DoPickUpEffects(CEntity *entity) +{ + CPickup *pickup = FindPickUpForThisObject(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); + +#ifdef FIX_BUGS + int16 colorId = 0; +#else + int16 colorId; +#endif + bool doInnerGlow = false; + bool doOuterGlow = true; + + if (entity->GetModelIndex() == MI_PICKUP_ADRENALINE || entity->GetModelIndex() == MI_PICKUP_CAMERA) { + colorId = WEAPONTYPE_TOTALWEAPONS; + doInnerGlow = true; + doOuterGlow = false; + } else if (entity->GetModelIndex() == MI_PICKUP_BODYARMOUR) { + colorId = WEAPONTYPE_ARMOUR; + } else if (entity->GetModelIndex() == MI_PICKUP_BRIBE) { + doInnerGlow = true; + doOuterGlow = false; + } else if (entity->GetModelIndex() == MI_PICKUP_INFO || entity->GetModelIndex() == MI_PICKUP_KILLFRENZY) { + doInnerGlow = true; + doOuterGlow = false; + } else if (entity->GetModelIndex() == MI_PICKUP_HEALTH || entity->GetModelIndex() == MI_PICKUP_BONUS) { + colorId = WEAPONTYPE_HEALTH; + doInnerGlow = true; + doOuterGlow = false; + } else if (entity->GetModelIndex() == MI_PICKUP_PROPERTY) { + doInnerGlow = true; + doOuterGlow = false; + } else if (entity->GetModelIndex() == MI_PICKUP_PROPERTY_FORSALE) { + doInnerGlow = true; + doOuterGlow = false; + } else if (entity->GetModelIndex() == MI_PICKUP_REVENUE) { + doInnerGlow = true; + doOuterGlow = false; + } else if (entity->GetModelIndex() == MI_PICKUP_SAVEGAME) { + doInnerGlow = true; + doOuterGlow = false; + } else if (entity->GetModelIndex() == MI_PICKUP_CLOTHES) { + colorId = WEAPONTYPE_TOTALWEAPONS; + doOuterGlow = false; + doInnerGlow = true; + } else + colorId = WeaponForModel(entity->GetModelIndex()); + + const CVector& pos = pickup->m_vecPos; + if (doOuterGlow) { + bool corona1 = false; + bool corona2 = false; + int timerVal = (CTimer::GetTimeInMilliseconds() >> 9) & 7; + + if (timerVal < 3) + corona1 = false; + else if (timerVal == 3) + corona1 = (CGeneral::GetRandomNumber() & 3) != 0; + else + corona1 = true; + + timerVal = (timerVal - 1) & 7; + if (timerVal < 3) + corona2 = false; + else if (timerVal == 3) + corona2 = (CGeneral::GetRandomNumber() & 3) != 0; + else + corona2 = true; + + if (((CObject*)entity)->bAmmoCollected) { + corona2 = false; + corona1 = false; + } + + if (corona1) { + CCoronas::RegisterCorona((uintptr)entity, + aPickupColors[colorId].r * 0.45f, aPickupColors[colorId].g * 0.45f, aPickupColors[colorId].b * 0.45f, + 255, pos, 0.76f, 65.0f, + CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, + 0.0f, false, -0.4f); + CShadows::StoreStaticShadow((uintptr)entity, + SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, + aPickupColors[colorId].r * 0.3f, aPickupColors[colorId].g * 0.3f, aPickupColors[colorId].b * 0.3f, + 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, aPickupColors[colorId].r / 256.0f, aPickupColors[colorId].g / 256.0f, aPickupColors[colorId].b / 256.0f, CPointLights::FOG_NONE, true); + } else + CCoronas::RegisterCorona((uintptr)entity, 0, 0, 0, 255, pos, 0.57f, 65.0f, CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + + if (corona2) { + CCoronas::RegisterCorona( + (uintptr)entity + 1, + aPickupColors[colorId].r * 0.55f, aPickupColors[colorId].g * 0.55f, aPickupColors[colorId].b * 0.55f, + 255, + pos, + 0.6f, + 65.0f, + CCoronas::TYPE_RING, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, + 0.0f, false, -0.4f); + if (!corona1) + CShadows::StoreStaticShadow((uintptr)entity, SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, 2.0f, 0.0f, 0.0f, -2.0f, 0, + aPickupColors[colorId].r * 0.25f, aPickupColors[colorId].g * 0.25f, aPickupColors[colorId].b * 0.25f, + 4.0f, 1.0f, 40.0f, false, 0.0f); + } else + CCoronas::RegisterCorona((uintptr)entity + 1, 0, 0, 0, 255, pos, 0.45f, 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 || object->m_nCostValue) { + + float dist = Distance2D(pos, TheCamera.GetPosition()); + const float MAXDIST = 14.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 = aPickupColors[colorId].r; + aMessages[NumMessages].m_color.green = aPickupColors[colorId].g; + aMessages[NumMessages].m_color.blue = aPickupColors[colorId].b; + aMessages[NumMessages].m_color.alpha = (1.0f - dist / MAXDIST) * 128.0f; + aMessages[NumMessages].m_bOutOfStock = object->bOutOfStock; + aMessages[NumMessages].m_quantity = object->m_nBonusValue; + aMessages[NumMessages].money = object->m_nCostValue; + NumMessages++; + } + } + } + + uint32 model = entity->GetModelIndex(); + CColModel *colModel = entity->GetColModel(); + CVector colLength = colModel->boundingBox.max - colModel->boundingBox.min; + float maxDimension = Max(colLength.x, Max(colLength.y, colLength.z)); + + float scale = (Max(1.f, 1.2f / maxDimension) - 1.0f) * 0.6f + 1.0f; + if (model == MI_MINIGUN || model == MI_MINIGUN2) + scale = 1.2f; + + float angle = (float)(CTimer::GetTimeInMilliseconds() & 0x7FF) * DEGTORAD(360.0f / 0x800); + float c = Cos(angle) * scale; + float s = Sin(angle) * scale; + + // 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 = scale; + + if (entity->GetModelIndex() == MI_MINIGUN2) { + CMatrix matrix1; + CMatrix matrix2; // unused + entity->SetPosition(pickup->m_vecPos); + matrix1.SetRotateX(0.0f); + matrix1.Rotate(DEGTORAD(4.477f), DEGTORAD(-29.731f), DEGTORAD(-1.064f)); + matrix1.Translate(CVector(0.829f, -0.001f, 0.226f)); + entity->GetMatrix() *= matrix1; + } + + if (doOuterGlow) { + CVector scale(0.0f, 0.0f, 0.0f); + if (colLength.x == maxDimension) + scale.x = colLength.x; + else if (colLength.y == maxDimension) + scale.y = colLength.y; + else + scale.z = colLength.z; + + for (int i = 0; i < 4; i++) { + CVector pos = entity->GetMatrix() * (scale * ((float)i / 3.0f)); + CCoronas::RegisterCorona( + (uintptr)entity + 8 + i, + aPickupColors[colorId].r * 0.15f, + aPickupColors[colorId].g * 0.15f, + aPickupColors[colorId].b * 0.15f, + 255, + pos, + 1.0f, + 65.0f, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, + CCoronas::LOSCHECK_OFF, + CCoronas::STREAK_OFF, + 0.0f, + false, + -0.5f); + } + } + + if (doInnerGlow) + CCoronas::RegisterCorona( +#ifdef FIX_BUGS + (uintptr)entity + 8 + 4, +#else + (uintptr)entity + 9, +#endif + 126, 69, 121, 255, entity->GetPosition(), 1.2f, 50.0f, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + } +} + +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_HEX, 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].money != 0) { + sprintf(gString, "$%d", aMessages[i].money); + AsciiToUnicode(gString, gUString); + strToPrint = gUString; + } else { + switch (aMessages[i].m_quantity) // could use some enum maybe + { + case 0: + if (aMessages[i].m_weaponType == WEAPONTYPE_HEALTH || aMessages[i].m_weaponType == WEAPONTYPE_ARMOUR) { + strToPrint = nil; + } 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("OUTFT1"); + break; + case 2: + strToPrint = TheText.Get("OUTFT2"); + break; + case 3: + strToPrint = TheText.Get("OUTFT3"); + break; + case 4: + strToPrint = TheText.Get("OUTFT4"); + break; + case 5: + strToPrint = TheText.Get("OUTFT5"); + break; + case 6: + strToPrint = TheText.Get("OUTFT6"); + break; + case 7: + strToPrint = TheText.Get("OUTFT7"); + break; + case 8: + strToPrint = TheText.Get("OUTFT8"); + break; + case 9: + strToPrint = TheText.Get("OUTFT9"); + break; + case 10: + strToPrint = TheText.Get("OUTFT10"); + break; + case 11: + strToPrint = TheText.Get("OUTFT11"); + break; + case 12: + strToPrint = TheText.Get("OUTFT12"); + break; + case 13: + strToPrint = TheText.Get("OUTFT13"); + break; + default: + break; + } + } + if (strToPrint == nil) + continue; + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + +#ifdef FIX_BUGS + const float MAX_SCALE = SCREEN_WIDTH / DEFAULT_SCREEN_WIDTH; +#else + float MAX_SCALE = RsGlobal.width / DEFAULT_SCREEN_WIDTH; +#endif + + float fScaleY = aMessages[i].m_dist.y / 30.0f; + if (fScaleY > MAX_SCALE) fScaleY = MAX_SCALE; + + float fScaleX = aMessages[i].m_dist.x / 30.0f; + if (fScaleX > MAX_SCALE) fScaleX = MAX_SCALE; + + CFont::SetScale(fScaleX, fScaleY); // this shouldn't be scaled + 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_STANDARD); + CFont::PrintString(aMessages[i].m_pos.x, aMessages[i].m_pos.y, strToPrint); + } + NumMessages = 0; +} + +void +CPickups::CreateSomeMoney(CVector pos, int money) +{ + bool found; + + int pickupCount = Min(money / 20 + 1, 7); + int moneyPerPickup = money / pickupCount; + + for (int i = 0; i < pickupCount; i++) { + // (CGeneral::GetRandomNumber() % 256) * PI / 128 gives a float up to something TWOPI-ish. + pos.x += 1.5f * Sin((CGeneral::GetRandomNumber() % 256) * PI / 128); + pos.y += 1.5f * Cos((CGeneral::GetRandomNumber() % 256) * PI / 128); + pos.z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &found) + 0.5f; + if (found) { + CPickups::GenerateNewOne(CVector(pos.x, pos.y, pos.z), MI_MONEY, PICKUP_MONEY, moneyPerPickup + (CGeneral::GetRandomNumber() & 3)); + } + } +} + +void +CPickups::RemoveAllPickupsOfACertainWeaponGroupWithNoAmmo(eWeaponType weaponType) +{ + uint32 weaponSlot = CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot; + if (CWeaponInfo::IsWeaponSlotAmmoMergeable(weaponSlot)) { + for (int slot = 0; slot < NUMPICKUPS; slot++) { + if (aPickUps[slot].m_eType == PICKUP_ONCE || aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT || aPickUps[slot].m_eType == PICKUP_ONCE_TIMEOUT_SLOW) { + if (aPickUps[slot].m_pObject) { + if (CWeaponInfo::GetWeaponInfo(WeaponForModel(aPickUps[slot].m_pObject->GetModelIndex()))->m_nWeaponSlot == weaponSlot && + aPickUps[slot].m_nQuantity == 0) { + CWorld::Remove(aPickUps[slot].m_pObject); + delete aPickUps[slot].m_pObject; + aPickUps[slot].m_bRemoved = true; + aPickUps[slot].m_pObject = nil; + aPickUps[slot].m_eType = PICKUP_NONE; + } + } + } + } + } +} + +void +CPickups::DetonateMinesHitByGunShot(CVector *vec1, CVector *vec2) +{ + for (int i = 0; i < NUMGENERALPICKUPS; i++) { + if (aPickUps[i].m_eType == PICKUP_NAUTICAL_MINE_ARMED) + aPickUps[i].ProcessGunShot(vec1, vec2); + } +} + +void +CPickups::RemoveUnnecessaryPickups(const CVector& center, float radius) +{ + for (int i = 0; i < NUMPICKUPS; i++) { + if (aPickUps[i].m_eType == PICKUP_ONCE_TIMEOUT || aPickUps[i].m_eType == PICKUP_MONEY) { + if (Distance(center, aPickUps[i].m_vecPos) < radius) { + aPickUps[i].GetRidOfObjects(); + aPickUps[i].m_bRemoved = true; + aPickUps[i].m_eType = PICKUP_NONE; + } + } + } +} + +void +CPickups::Load(uint8 *buf, uint32 size) +{ +INITSAVEBUF + + for (int32 i = 0; i < NUMPICKUPS; i++) { +#ifdef COMPATIBLE_SAVES + ReadSaveBuf(&aPickUps[i].m_vecPos, buf); + ReadSaveBuf(&aPickUps[i].m_fRevenue, buf); + int32 tmp_pObject; + ReadSaveBuf(&tmp_pObject, buf); + int32 tmp_pExtraObject; + ReadSaveBuf(&tmp_pExtraObject, buf); + ReadSaveBuf(&aPickUps[i].m_nQuantity, buf); + ReadSaveBuf(&aPickUps[i].m_nTimer, buf); + ReadSaveBuf(&aPickUps[i].m_nMoneySpeed, buf); + ReadSaveBuf(&aPickUps[i].m_eModelIndex, buf); + ReadSaveBuf(&aPickUps[i].m_nIndex, buf); + memcpy(aPickUps[i].m_sTextKey, buf, sizeof(aPickUps[i].m_sTextKey)); + SkipSaveBuf(buf, sizeof(aPickUps[i].m_sTextKey)); + ReadSaveBuf(&aPickUps[i].m_eType, buf); + ReadSaveBuf(&aPickUps[i].m_bRemoved, buf); + uint8 flags; + ReadSaveBuf(&flags, buf); + aPickUps[i].m_bWasAmmoCollected = !!(flags & BIT(0)); + aPickUps[i].m_bWasControlMessageShown = !!(flags & BIT(1)); + SkipSaveBuf(buf, 3); + + aPickUps[i].m_pObject = aPickUps[i].m_eType != PICKUP_NONE && tmp_pObject != 0 ? CPools::GetObjectPool()->GetSlot(tmp_pObject - 1) : nil; + aPickUps[i].m_pExtraObject = aPickUps[i].m_eType != PICKUP_NONE && tmp_pExtraObject != 0 ? CPools::GetObjectPool()->GetSlot(tmp_pExtraObject - 1) : nil; +#else + ReadSaveBuf(&aPickUps[i], buf); + + if (aPickUps[i].m_eType != PICKUP_NONE) { + if (aPickUps[i].m_pObject != nil) + aPickUps[i].m_pObject = CPools::GetObjectPool()->GetSlot((uintptr)aPickUps[i].m_pObject - 1); + if (aPickUps[i].m_pExtraObject != nil) + aPickUps[i].m_pExtraObject = CPools::GetObjectPool()->GetSlot((uintptr)aPickUps[i].m_pExtraObject - 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; + *size += sizeof(uint16) + sizeof(uint16) + sizeof(aPickUpsCollected); + +INITSAVEBUF + + for (int32 i = 0; i < NUMPICKUPS; i++) { +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, aPickUps[i].m_vecPos); + WriteSaveBuf(buf, aPickUps[i].m_fRevenue); + 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); + tmp = aPickUps[i].m_eType != PICKUP_NONE && aPickUps[i].m_pExtraObject != nil ? CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(aPickUps[i].m_pExtraObject) + 1 : 0; + WriteSaveBuf(buf, tmp); + WriteSaveBuf(buf, aPickUps[i].m_nQuantity); + WriteSaveBuf(buf, aPickUps[i].m_nTimer); + WriteSaveBuf(buf, aPickUps[i].m_nMoneySpeed); + WriteSaveBuf(buf, aPickUps[i].m_eModelIndex); + WriteSaveBuf(buf, aPickUps[i].m_nIndex); + memcpy(buf, aPickUps[i].m_sTextKey, sizeof(aPickUps[i].m_sTextKey)); + SkipSaveBuf(buf, sizeof(aPickUps[i].m_sTextKey)); + WriteSaveBuf(buf, aPickUps[i].m_eType); + WriteSaveBuf(buf, aPickUps[i].m_bRemoved); + uint8 flags = 0; + if (aPickUps[i].m_bWasAmmoCollected) flags |= BIT(0); + if (aPickUps[i].m_bWasControlMessageShown) flags |= BIT(1); + WriteSaveBuf(buf, flags); + ZeroSaveBuf(buf, 3); +#else + CPickup *buf_pickup = WriteSaveBuf(buf, aPickUps[i]); + if (buf_pickup->m_eType != PICKUP_NONE) { + if (buf_pickup->m_pObject != nil) + buf_pickup->m_pObject = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(buf_pickup->m_pObject) + 1); + if (buf_pickup->m_pExtraObject != nil) + buf_pickup->m_pExtraObject = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(buf_pickup->m_pExtraObject) + 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() +{ +} + +int32 CollectGameState; +int16 ThingsToCollect; + +CPacManPickup CPacManPickups::aPMPickUps[NUMPACMANPICKUPS]; +CVector CPacManPickups::LastPickUpCoors; +int CPacManPickups::PillsEatenInRace; +bool CPacManPickups::bPMActive; + +void +CPacManPickups::Init() +{ +} + +void +CPacManPickups::Update() +{ +} + +void +CPacManPickups::GeneratePMPickUps(CVector pos, float scrambleMult, int16 count, uint8 type) +{ +} + +// 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(0.0f, 0.0f, 0.0f), +}; + +void +CPacManPickups::GeneratePMPickUpsForRace(int32 race) +{ +} + +void +CPacManPickups::GenerateOnePMPickUp(CVector pos) +{ +} + +void +CPacManPickups::Render() +{ +} + +void +CPacManPickups::ClearPMPickUps() +{ +} + +void +CPacManPickups::StartPacManRace(int32 race) +{ +} + +void +CPacManPickups::StartPacManRecord() +{ +} + +uint32 +CPacManPickups::QueryPowerPillsEatenInRace() +{ + return 0; +} + +void +CPacManPickups::ResetPowerPillsEatenInRace() +{ +} + +void +CPacManPickups::CleanUpPacManStuff() +{ +} + +void +CPacManPickups::StartPacManScramble(CVector pos, float scrambleMult, int16 count) +{ +} + +uint32 +CPacManPickups::QueryPowerPillsCarriedByPlayer() +{ + return 0; +} + +void +CPacManPickups::ResetPowerPillsCarriedByPlayer() +{ +} + +void +CPed::CreateDeadPedMoney(void) +{ + if (!CGame::nastyGame) + return; + + int mi = GetModelIndex(); + + if ((mi >= MI_COP && mi <= MI_FIREMAN) || (CharCreatedBy == MISSION_CHAR && !bMoneyHasBeenGivenByScript) || bInVehicle) + return; + + int money = m_nPedMoney; + if (money < 10) + return; + + CVector pickupPos = GetPosition(); + CPickups::CreateSomeMoney(pickupPos, money); + m_nPedMoney = 0; +} + +void +CPed::CreateDeadPedWeaponPickups(void) +{ + CVector pickupPos; + + if (bInVehicle) + return; + + for(int i = 0; i < TOTAL_WEAPON_SLOTS; i++) { + + eWeaponType weapon = GetWeapon(i).m_eWeaponType; + int weaponAmmo = GetWeapon(i).m_nAmmoTotal; + if (weapon == WEAPONTYPE_UNARMED || weapon == WEAPONTYPE_DETONATOR || (weaponAmmo == 0 && !GetWeapon(i).IsTypeMelee())) + continue; + + int quantity = Min(weaponAmmo, AmmoForWeapon_OnStreet[weapon] / 2); + CreateDeadPedPickupCoors(&pickupPos.x, &pickupPos.y, &pickupPos.z); + pickupPos.z += 0.3f; + if (!CPickups::TryToMerge_WeaponType(pickupPos, weapon, PICKUP_ONCE_TIMEOUT, quantity, false)) { + CPickups::GenerateNewOne_WeaponType(pickupPos, weapon, PICKUP_ONCE_TIMEOUT, Min(weaponAmmo, quantity)); + } + } + ClearWeapons(); +} + +void +CPed::CreateDeadPedPickupCoors(float *x, float *y, float *z) +{ + bool found = false; + CVector pickupPos; + +#define NUMBER_OF_ATTEMPTS 32 + for (int i = 0; i < NUMBER_OF_ATTEMPTS; i++) { + + pickupPos = GetPosition(); + pickupPos.x = 1.5f * Sin((CGeneral::GetRandomNumber() % 256)/256.0f * TWOPI) + GetPosition().x; + pickupPos.y = 1.5f * Cos((CGeneral::GetRandomNumber() % 256)/256.0f * TWOPI) + GetPosition().y; + pickupPos.z = CWorld::FindGroundZFor3DCoord(pickupPos.x, pickupPos.y, pickupPos.z, &found) + 0.5f; + + if (!found) + continue; + + CVector pedPos = GetPosition(); + pedPos.z += 0.3f; + + CVector pedToPickup = pickupPos - pedPos; + float distance = pedToPickup.Magnitude(); + + // outer edge of pickup + distance = (distance + 0.4f) / distance; + CVector pickupPos2 = pedPos; + pickupPos2 += distance * pedToPickup; + + if ((pickupPos - FindPlayerCoors()).Magnitude2D() > 2.0f || i > NUMBER_OF_ATTEMPTS / 2) { + + if (i > NUMBER_OF_ATTEMPTS / 2 || !CPickups::TestForPickupsInBubble(pickupPos, 1.3f)) { + + if (CWorld::GetIsLineOfSightClear(pickupPos2, pedPos, + true, i < NUMBER_OF_ATTEMPTS / 2, false, i < NUMBER_OF_ATTEMPTS / 2, false, false, false)) { + + if (i > NUMBER_OF_ATTEMPTS / 2 || !CWorld::TestSphereAgainstWorld(pickupPos, 1.2f, nil, false, true, false, false, false, false)) { + *x = pickupPos.x; + *y = pickupPos.y; + *z = pickupPos.z; + return; + } + } + } + } + } + *x = GetPosition().x; + *y = GetPosition().y; + *z = GetPosition().z + 0.4f; +#undef NUMBER_OF_ATTEMPTS +} \ No newline at end of file diff --git a/src/miami/control/Pickups.h b/src/miami/control/Pickups.h new file mode 100644 index 00000000..0de7f827 --- /dev/null +++ b/src/miami/control/Pickups.h @@ -0,0 +1,170 @@ +#pragma once +#include "Weapon.h" + +enum ePickupType +{ + PICKUP_NONE = 0, + PICKUP_IN_SHOP, + PICKUP_ON_STREET, + PICKUP_ONCE, + PICKUP_ONCE_TIMEOUT, + PICKUP_ONCE_TIMEOUT_SLOW, + 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_ASSET_REVENUE, + PICKUP_PROPERTY_LOCKED, + PICKUP_PROPERTY_FORSALE, + PICKUP_NUMOFTYPES +}; + +class CEntity; +class CObject; +class CVehicle; +class CPlayerPed; + +class CPickup +{ +public: + CVector m_vecPos; + float m_fRevenue; + CObject *m_pObject; + CObject *m_pExtraObject; + uint32 m_nQuantity; + uint32 m_nTimer; + uint16 m_nMoneySpeed; + int16 m_eModelIndex; + uint16 m_nIndex; + char m_sTextKey[8]; + uint8 m_eType; + bool m_bRemoved; + uint8 m_bWasAmmoCollected:1; + uint8 m_bWasControlMessageShown:1; + + CObject *GiveUsAPickUpObject(CObject **object, CObject **extraObject, int32 handle, int32 extraHandle); + bool Update(CPlayerPed *player, CVehicle *vehicle, int playerId); + void GetRidOfObjects(); + void ExtractAmmoFromPickup(CPlayerPed *player); + void ProcessGunShot(CVector *vec1, CVector *vec2); +private: + inline bool IsMine() { return m_eType >= PICKUP_MINE_INACTIVE && m_eType <= PICKUP_FLOATINGPACKAGE_FLOATING; } + inline bool CanBePickedUp(CPlayerPed *player, int playerId); + inline void Remove(); +}; + +VALIDATE_SIZE(CPickup, 0x1C); + +struct tPickupMessage +{ + CVector2D m_pos; + eWeaponType m_weaponType; + CVector2D m_dist; + CRGBA m_color; + uint8 m_bOutOfStock; + uint8 m_quantity; + uint16 money; +}; + +class CPickups +{ + static int32 aPickUpsCollected[NUMCOLLECTEDPICKUPS]; + static int16 CollectedPickUpIndex; + static int16 NumMessages; + static tPickupMessage aMessages[NUMPICKUPMESSAGES]; +public: + static int32 PlayerOnWeaponPickup; + + 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, uint32 rate = 0, bool highPriority = false, char* pText = nil); + static int32 GenerateNewOne_WeaponType(CVector pos, eWeaponType weaponType, uint8 type, uint32 quantity); + static void RemovePickUp(int32 pickupIndex); + static void AddToCollectedPickupsArray(int32 index); + static bool IsPickUpPickedUp(int32 pickupId); + static int32 ModelForWeapon(eWeaponType weaponType); + static enum eWeaponType WeaponForModel(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 bool TestForPickupsInBubble(CVector pos, float range); + static bool TryToMerge_WeaponType(CVector pos, eWeaponType weapon, uint8 type, uint32 quantity, bool unused); + static void CreateSomeMoney(CVector, int); + static void DetonateMinesHitByGunShot(CVector *vec1, CVector *vec2); + static void RemoveUnnecessaryPickups(const CVector& center, float radius); + 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; + + static void RemoveAllPickupsOfACertainWeaponGroupWithNoAmmo(eWeaponType); + static CPickup *FindPickUpForThisObject(CEntity*); +}; + +extern uint16 AmmoForWeapon[WEAPONTYPE_TOTALWEAPONS + 1]; +extern uint16 AmmoForWeapon_OnStreet[WEAPONTYPE_TOTALWEAPONS + 1]; +extern uint16 CostOfWeapon[WEAPONTYPE_TOTALWEAPONS + 3]; + +extern int32 CollectPickupBuffer; + +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/miami/control/PowerPoints.cpp b/src/miami/control/PowerPoints.cpp new file mode 100644 index 00000000..9a74e8d9 --- /dev/null +++ b/src/miami/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/miami/control/PowerPoints.h b/src/miami/control/PowerPoints.h new file mode 100644 index 00000000..ee3750cd --- /dev/null +++ b/src/miami/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/miami/control/Record.cpp b/src/miami/control/Record.cpp new file mode 100644 index 00000000..5e6c7cdb --- /dev/null +++ b/src/miami/control/Record.cpp @@ -0,0 +1,107 @@ +#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" + +uint16 CRecordDataForGame::RecordingState; + +void CRecordDataForGame::Init(void) +{ + RecordingState = STATE_NONE; +} + +void CRecordDataForGame::SaveOrRetrieveDataForThisFrame(void) +{ +} + +uint8* CRecordDataForGame::PackCurrentPadValues(uint8* buf, CControllerState* os, CControllerState* ns) +{ + return nil; +} + +uint8* CRecordDataForGame::UnPackCurrentPadValues(uint8* buf, uint8 total, CControllerState* state) +{ + return nil; +} + +uint16 CRecordDataForGame::CalcGameChecksum(void) +{ + return 0; +} + +uint8 CRecordDataForChase::Status; + +void CRecordDataForChase::Init(void) +{ + Status = STATE_NONE; +} + +void CRecordDataForChase::SaveOrRetrieveDataForThisFrame(void) +{ +} + +void CRecordDataForChase::SaveOrRetrieveCarPositions(void) +{ +} + +void CRecordDataForChase::StoreInfoForCar(CAutomobile* pCar, CCarStateEachFrame* pState) +{ +} + +void CRecordDataForChase::RestoreInfoForMatrix(CMatrix& matrix, CCarStateEachFrame* pState) +{ +} + +void CRecordDataForChase::RestoreInfoForCar(CAutomobile* pCar, CCarStateEachFrame* pState, bool stop) +{ +} + +void CRecordDataForChase::ProcessControlCars(void) +{ +} + +bool CRecordDataForChase::ShouldThisPadBeLeftAlone(uint8 pad) +{ + return false; +} + +void CRecordDataForChase::GiveUsACar(int32 mi, CVector pos, float angle, CAutomobile** ppCar, uint8 colour1, uint8 colour2) +{ +} + +void RemoveUnusedCollision(void) +{ +} + +void CRecordDataForChase::StartChaseScene(float startTime) +{ +} + +void CRecordDataForChase::CleanUpChaseScene(void) +{ +} + +void CRecordDataForChase::SetUpCarsForChaseScene(void) +{ +} + +void CRecordDataForChase::CleanUpCarsForChaseScene(void) +{ +} + +void CRecordDataForChase::RemoveCarFromChase(int32 i) +{ +} + +CVehicle* CRecordDataForChase::TurnChaseCarIntoScriptCar(int32 i) +{ + return nil; +} + diff --git a/src/miami/control/Record.h b/src/miami/control/Record.h new file mode 100644 index 00000000..6a94c408 --- /dev/null +++ b/src/miami/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/miami/control/Remote.cpp b/src/miami/control/Remote.cpp new file mode 100644 index 00000000..047b19f3 --- /dev/null +++ b/src/miami/control/Remote.cpp @@ -0,0 +1,58 @@ +#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); + if (car->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE || car->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI) { + TheCamera.TakeControl(car, CCam::MODE_CAM_ON_A_STRING, INTERPOLATION, CAMCONTROL_SCRIPT); + TheCamera.SetZoomValueCamStringScript(0); + } else + TheCamera.TakeControl(car, CCam::MODE_BEHINDCAR, INTERPOLATION, CAMCONTROL_SCRIPT); +} + +void +CRemote::TakeRemoteControlledCarFromPlayer(bool blowUp) +{ + if (CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle->VehicleCreatedBy == MISSION_VEHICLE) { + 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].field_D5 = blowUp; + CWorld::Players[CWorld::PlayerInFocus].field_D6 = true; +} diff --git a/src/miami/control/Remote.h b/src/miami/control/Remote.h new file mode 100644 index 00000000..72cabb7c --- /dev/null +++ b/src/miami/control/Remote.h @@ -0,0 +1,8 @@ +#pragma once + +class CRemote +{ +public: + static void GivePlayerRemoteControlledCar(float, float, float, float, uint16); + static void TakeRemoteControlledCarFromPlayer(bool blowUp = true); +}; diff --git a/src/miami/control/Replay.cpp b/src/miami/control/Replay.cpp new file mode 100644 index 00000000..71b28f7a --- /dev/null +++ b/src/miami/control/Replay.cpp @@ -0,0 +1,1911 @@ +#include "common.h" +#ifdef GTA_REPLAY +#include "AnimBlendAssocGroup.h" +#include "AnimBlendAssociation.h" +#include "Bike.h" +#include "Boat.h" +#include "SpecialFX.h" +#include "CarCtrl.h" +#include "CivilianPed.h" +#include "CopPed.h" +#include "Wanted.h" +#include "Clock.h" +#include "DMAudio.h" +#include "Draw.h" +#include "Explosion.h" +#include "FileMgr.h" +#include "Fire.h" +#include "Frontend.h" +#include "Garages.h" +#include "Heli.h" +#include "main.h" +#include "Matrix.h" +#include "ModelIndices.h" +#include "ModelInfo.h" +#include "Object.h" +#include "Pad.h" +#include "Particle.h" +#include "PedAttractor.h" +#include "Phones.h" +#include "Pickups.h" +#include "Plane.h" +#include "Pools.h" +#include "Population.h" +#include "Projectile.h" +#include "ProjectileInfo.h" +#include "Replay.h" +#include "References.h" +#include "Pools.h" +#include "RpAnimBlend.h" +#include "RwHelper.h" +#include "CutsceneMgr.h" +#include "Skidmarks.h" +#include "Stinger.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" +#include "Fluff.h" +#include "WaterCreatures.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; +CCutsceneObject *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; +int CReplay::ms_nNumCivMale_Stored; +int CReplay::ms_nNumCivFemale_Stored; +int CReplay::ms_nNumCop_Stored; +int CReplay::ms_nNumEmergency_Stored; +int CReplay::ms_nNumGang1_Stored; +int CReplay::ms_nNumGang2_Stored; +int CReplay::ms_nNumGang3_Stored; +int CReplay::ms_nNumGang4_Stored; +int CReplay::ms_nNumGang5_Stored; +int CReplay::ms_nNumGang6_Stored; +int CReplay::ms_nNumGang7_Stored; +int CReplay::ms_nNumGang8_Stored; +int CReplay::ms_nNumGang9_Stored; +int CReplay::ms_nNumDummy_Stored; +int CReplay::ms_nTotalCarPassengerPeds_Stored; +int CReplay::ms_nTotalCivPeds_Stored; +int CReplay::ms_nTotalGangPeds_Stored; +int CReplay::ms_nTotalPeds_Stored; +int CReplay::ms_nTotalMissionPeds_Stored; +uint8* CReplay::pGarages; +CFire* CReplay::FireArray; +uint32 CReplay::NumOfFires; +uint8* CReplay::paProjectileInfo; +uint8* CReplay::paProjectiles; +uint8 CReplay::CurrArea; +#ifdef FIX_BUGS +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, +#ifdef GTA_TRAIN + &CPed::PedSetOutTrainCB, +#endif + &CPed::FinishedAttackCB, + &CPed::FinishFightMoveCB, &PhonePutDownCB, &PhonePickUpCB, &CPed::PedAnimDoorCloseRollingCB, &CPed::FinishJumpCB, + &CPed::PedLandCB, &CPed::RestoreHeadingRateCB, &CPed::PedSetQuickDraggedOutCarPositionCB, &CPed::PedSetDraggedOutCarPositionCB, + &CPed::PedSetPreviousStateCB, &CPed::FinishedReloadCB, &CPed::PedSetGetInCarPositionCB, + &CPed::PedAnimShuffleCB, &CPed::DeleteSunbatheIdleAnimCB, &StartTalkingOnMobileCB, &FinishTalkingOnMobileCB +}; + +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; + MarkEverythingAsNew(); +} + +void CReplay::DisableReplays(void) +{ + bReplayEnabled = false; +} + +void CReplay::EnableReplays(void) +{ + bReplayEnabled = true; +} + +void PlayReplayFromHD(void); +void CReplay::Update(void) +{ + if (CCutsceneMgr::IsCutsceneProcessing() || CPad::GetPad(0)->ArePlayerControlsDisabled() || CScriptPaths::IsOneActive() || FrontEndMenuManager.GetIsMenuActive()) { + Init(); + 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) +{ + uint32 memory_required = sizeof(tGeneralPacket) + sizeof(tClockPacket) + sizeof(tWeatherPacket) + sizeof(tTimerPacket) + sizeof(tMiscPacket); + 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) { + if (v->IsBike()) + memory_required += sizeof(tBikeUpdatePacket); + else + 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 - 16) + GoToNextBlock(); + 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) { + if (v->IsBike()) + StoreBikeUpdate(v, i); + else + 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->inf = CBulletTraces::aTraces[i].m_vecStartPos; + bt->sup = CBulletTraces::aTraces[i].m_vecEndPos; + Record.m_nOffset += sizeof(*bt); + } + tMiscPacket* misc = (tMiscPacket*)&Record.m_pBase[Record.m_nOffset]; + misc->type = REPLAYPACKET_MISC; + misc->cam_shake_start = TheCamera.m_uiCamShakeStart; + misc->cam_shake_strength = TheCamera.m_fCamShakeForce; + misc->cur_area = CGame::currArea; + misc->video_cam = CSpecialFX::bVideoCam; + misc->lift_cam = CSpecialFX::bLiftCam; + Record.m_nOffset += sizeof(*misc); + 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; +} + +void CReplay::GoToNextBlock(void) +{ + 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(); +} + +void CReplay::RecordParticle(tParticleType type, const CVector& vecPos, const CVector& vecDir, float fSize, const RwRGBA& color) +{ + if (Record.m_nOffset > REPLAYBUFFERSIZE - 16 - sizeof(tParticlePacket)) + GoToNextBlock(); + tParticlePacket* pp = (tParticlePacket*)&Record.m_pBase[Record.m_nOffset]; + pp->type = REPLAYPACKET_PARTICLE; + pp->particle_type = type; + pp->pos_x = 4.0f * vecPos.x; + pp->pos_y = 4.0f * vecPos.y; + pp->pos_z = 4.0f * vecPos.z; + pp->dir_x = 120.0f * Clamp(vecDir.x, -1.0f, 1.0f); + pp->dir_y = 120.0f * Clamp(vecDir.y, -1.0f, 1.0f); + pp->dir_z = 120.0f * Clamp(vecDir.z, -1.0f, 1.0f); + pp->size = fSize; + pp->r = color.red; + pp->g = color.green; + pp->b = color.blue; + pp->a = color.alpha; + Record.m_nOffset += sizeof(tParticlePacket); + Record.m_pBase[Record.m_nOffset] = REPLAYPACKET_END; +} + +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; + pp->is_visible = ped->bIsVisible; + /* 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); + state->groupId = main->groupId; + }else{ + state->animId = 3; + state->time = 0; + state->speed = 85; + state->groupId = 0; + } + 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); + state->secGroupId = second->groupId; + }else{ + state->secAnimId = 0; + state->secTime = 0; + state->secSpeed = 0; + state->blendAmount = 0; + state->secGroupId = 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); + state->partGroupId = partial->groupId; + }else{ + state->partAnimId = 0; + state->partAnimTime = 0; + state->partAnimSpeed = 0; + state->partBlendAmount = 0; + state->partGroupId = 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); + state->aBlendDelta[i] = 127.0f / 32.0f * Clamp(assoc->blendDelta, -16.0f, 16.0f); + state->aFlags[i] = assoc->flags; + state->aGroupId[i] = assoc->groupId; + 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; + state->aGroupId[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); + state->aBlendDelta2[i] = 127.0f / 16.0f * Clamp(assoc->blendDelta, -16.0f, 16.0f); + state->aFlags2[i] = assoc->flags; + state->aGroupId2[i] = assoc->groupId; + 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; + state->aGroupId2[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 = true; + } + 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(); + } + ped->bIsVisible = pp->is_visible; + if (FramesActiveLookAroundCam && ped->m_nPedType == PEDTYPE_PLAYER1) + ped->bIsVisible = true; + RetrievePedAnimation(ped, &pp->anim_state); + ped->RemoveWeaponModel(-1); + if (pp->weapon_model != (uint16)-1) { + if (CStreaming::HasModelLoaded(pp->weapon_model)) + ped->AddWeaponModel(pp->weapon_model); + else + CStreaming::RequestModel(pp->weapon_model, 0); + } + CWorld::Remove(ped); + CWorld::Add(ped); + buffer->m_nOffset += sizeof(tPedUpdatePacket); +} + +bool HasAnimGroupLoaded(uint8 group) +{ + CAnimBlendAssocGroup* pGroup = &CAnimManager::GetAnimAssocGroups()[group]; + return pGroup->animBlock && pGroup->animBlock->isLoaded; +} + +void CReplay::RetrievePedAnimation(CPed *ped, CStoredAnimationState *state) +{ + CAnimBlendAssociation* anim1; + if (state->animId <= ANIM_STD_IDLE) + anim1 = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, ped->m_animGroup, (AnimationId)state->animId, 100.0f); + else if (HasAnimGroupLoaded(state->groupId)) + anim1 = CAnimManager::BlendAnimation((RpClump*)ped->m_rwObject, (AssocGroupId)state->groupId, (AnimationId)state->animId, 100.0f); + else + anim1 = CAnimManager::BlendAnimation((RpClump*)ped->m_rwObject, ASSOCGRP_STD, ANIM_STD_WALK, 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 > ANIM_STD_IDLE) ? (AssocGroupId)state->secGroupId : 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 && HasAnimGroupLoaded(state->partGroupId)){ + CAnimBlendAssociation* anim3 = CAnimManager::BlendAnimation( + (RpClump*)ped->m_rwObject, (AssocGroupId)state->partGroupId, (AnimationId)state->partAnimId, 1000.0f); + anim3->SetCurrentTime(time); + anim3->speed = speed; + anim3->SetBlend(blend, 0.0f); + } + } +} + +void CReplay::RetrieveDetailedPedAnimation(CPed *ped, CStoredDetailedAnimationState *state) +{ + 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); + for (int i = 0; i < NUM_MAIN_ANIMS_IN_REPLAY; i++) { + if (state->aAnimId[i] == ANIM_STD_NUM) + continue; + CAnimBlendAssociation* anim = CAnimManager::AddAnimation(ped->GetClump(), + state->aAnimId[i] > ANIM_STD_IDLE ? (AssocGroupId)state->aGroupId[i] : ped->m_animGroup, + (AnimationId)state->aAnimId[i]); + anim->SetCurrentTime(state->aCurTime[i] * 4.0f / 255.0f); + anim->speed = state->aSpeed[i] * 3.0f / 255.0f; + anim->SetBlend(state->aBlendAmount[i] * 2.0f / 255.0f, state->aBlendDelta[i] * 16.0f / 127.0f); + 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; + CAnimBlendAssociation* anim = CAnimManager::AddAnimation(ped->GetClump(), + state->aAnimId2[i] > ANIM_STD_IDLE ? (AssocGroupId)state->aGroupId2[i] : ped->m_animGroup, + (AnimationId)state->aAnimId2[i]); + anim->SetCurrentTime(state->aCurTime2[i] * 4.0f / 255.0f); + anim->speed = state->aSpeed2[i] * 3.0f / 255.0f; + anim->SetBlend(state->aBlendAmount2[i] * 2.0f / 255.0f, state->aBlendDelta2[i] * 16.0f / 127.0f); + 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); + } + } + if (vehicle->GetModelIndex() == MI_SKIMMER) + vp->skimmer_speed = 50.0f * ((CBoat*)vehicle)->m_fMovingSpeed; + vp->render_scorched = vehicle->bRenderScorched; + vp->vehicle_type = vehicle->m_vehType; + Record.m_nOffset += sizeof(tVehicleUpdatePacket); +} + +void CReplay::StoreBikeUpdate(CVehicle* vehicle, int id) +{ + CBike* bike = (CBike*)vehicle; + tBikeUpdatePacket* vp = (tBikeUpdatePacket*)&Record.m_pBase[Record.m_nOffset]; + vp->type = REPLAYPACKET_BIKE; + 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; +#ifdef FIX_BUGS // originally it's undefined behaviour - different fields are copied on PC and mobile + for (int i = 0; i < 2; i++) + vp->wheel_rotation[i] = 128.0f / PI * bike->m_aWheelRotation[i]; + for (int i = 0; i < 2; i++) + vp->wheel_rotation[i + 2] = 128.0f / PI * bike->m_aWheelSpeed[i]; + for (int i = 0; i < 4; i++) + vp->wheel_susp_dist[i] = 50.0f * bike->m_aSuspensionSpringRatio[i]; +#else + for (int i = 0; i < 4; i++) { + vp->wheel_susp_dist[i] = 50.0f * bike->m_aSuspensionSpringRatio[i]; + vp->wheel_rotation[i] = 128.0f / PI * bike->m_aWheelRotation[i]; + } +#endif + 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; + vp->wheel_state = 50.0f * vehicle->m_fSteerAngle; + vp->lean_angle = 50.0f * bike->m_fLeanLRAngle; + vp->wheel_angle = 50.0f * bike->m_fWheelAngle; + Record.m_nOffset += sizeof(tBikeUpdatePacket); +} + +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; + vehicle->bRenderScorched = vp->render_scorched; + if (vehicle->GetModelIndex() == MI_SKIMMER) + ((CBoat*)vehicle)->m_fMovingSpeed = vp->skimmer_speed / 50.0f; +} + +void CReplay::ProcessBikeUpdate(CVehicle* vehicle, float interpolation, CAddressInReplayBuffer* buffer) +{ + CBike* bike = (CBike*)vehicle; + tBikeUpdatePacket* vp = (tBikeUpdatePacket*)&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; + vehicle->m_vecMoveSpeed = CVector(vp->velocityX / 8000.0f, vp->velocityY / 8000.0f, vp->velocityZ / 8000.0f); + vehicle->m_fSteerAngle = vp->wheel_state / 50.0f; + vehicle->bEngineOn = true; +#ifdef FIX_BUGS + for (int i = 0; i < 2; i++) + bike->m_aWheelRotation[i] = vp->wheel_rotation[i] / (128.0f / PI); + for (int i = 0; i < 2; i++) + bike->m_aWheelSpeed[i] = vp->wheel_rotation[i + 2] / (128.0f / PI); + for (int i = 0; i < 4; i++) + bike->m_aSuspensionSpringRatio[i] = vp->wheel_susp_dist[i] / 50.0f; +#else + for (int i = 0; i < 4; i++) { + bike->m_aSuspensionSpringRatio[i] = vp->wheel_susp_dist[i] / 50.0f; + bike->m_aWheelRotation[i] = vp->wheel_rotation[i] / (128.0f / PI); + } +#endif + bike->m_fLeanLRAngle = vp->lean_angle / 50.0f; + bike->m_fWheelAngle = vp->wheel_angle / 50.0f; + bike->bLeanMatrixClean = false; + bike->CalculateLeanMatrix(); + CWorld::Remove(vehicle); + CWorld::Add(vehicle); +} + +bool CReplay::PlayBackThisFrameInterpolation(CAddressInReplayBuffer *buffer, float interpolation, uint32 *pTimer) +{ + 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 { + switch (vp->vehicle_type) { + case VEHICLE_TYPE_CAR: + new_v = new(vp->index << 8) CAutomobile(mi, 2); + break; + case VEHICLE_TYPE_BOAT: + new_v = new(vp->index << 8) CBoat(mi, 2); + break; + case VEHICLE_TYPE_TRAIN: + new_v = new(vp->index << 8) CTrain(mi, 2); + break; + case VEHICLE_TYPE_HELI: + new_v = new(vp->index << 8) CHeli(mi, 2); + break; + case VEHICLE_TYPE_PLANE: + new_v = new(vp->index << 8) CPlane(mi, 2); + break; + case VEHICLE_TYPE_BIKE: // not possible + new_v = new(vp->index << 8) CBike(mi, 2); + break; + } + 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_BIKE: + { + tBikeUpdatePacket* vp = (tBikeUpdatePacket*)&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 { + new_v = new(vp->index << 8) CBike(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); + } + } + ProcessBikeUpdate(CPools::GetVehiclePool()->GetSlot(vp->index), interpolation, buffer); + buffer->m_nOffset += sizeof(tBikeUpdatePacket); + break; + } + case REPLAYPACKET_PED_HEADER: + { + tPedHeaderPacket* ph = (tPedHeaderPacket*)&ptr[offset]; + if (!CPools::GetPedPool()->GetSlot(ph->index)) { + if (!CStreaming::HasModelLoaded(ph->mi) || (ph->mi >= MI_SPECIAL01 && ph->mi < MI_LAST_PED)) { + CStreaming::RequestModel(ph->mi, 0); + } + else { + CPed* new_p; + if (ph->pedtype != PEDTYPE_PLAYER1) + new_p = new(ph->index << 8) CCivilianPed((ePedType)ph->pedtype, ph->mi); + else + new_p = new(ph->index << 8) CPlayerPed(); + 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_vecStartPos = pb->inf; + CBulletTraces::aTraces[pb->index].m_vecEndPos = pb->sup; + buffer->m_nOffset += sizeof(tBulletTracePacket); + break; + } + case REPLAYPACKET_PARTICLE: + { + tParticlePacket* pp = (tParticlePacket*)&ptr[offset]; + CVector pos(pp->pos_x / 4.0f, pp->pos_y / 4.0f, pp->pos_z / 4.0f); + CVector dir(pp->dir_x / 120.0f, pp->dir_y / 120.0f, pp->dir_z / 120.0f); + RwRGBA color; + color.red = pp->r; + color.green = pp->g; + color.blue = pp->b; + color.alpha = pp->a; + CParticle::AddParticle((tParticleType)pp->particle_type, pos, dir, nil, pp->size, color); + buffer->m_nOffset += sizeof(tParticlePacket); + break; + } + case REPLAYPACKET_MISC: + { + tMiscPacket* pm = (tMiscPacket*)&ptr[offset]; + TheCamera.m_uiCamShakeStart = pm->cam_shake_start; + TheCamera.m_fCamShakeForce = pm->cam_shake_strength; + CSpecialFX::bVideoCam = pm->video_cam; + CSpecialFX::bLiftCam = pm->lift_cam; + CGame::currArea = pm->cur_area; + buffer->m_nOffset += sizeof(tMiscPacket); + break; + } + 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)); +} + +extern CWeaponEffects gCrossHair; + +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); + CEscalators::Shutdown(); + CWaterCreatures::RemoveAll(); + 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; + gCrossHair.m_bActive = false; + CExplosion::ClearAllExplosions(); + CPlaneBanners::Init(); +#ifndef FIX_BUGS // this doesn't do anything useful and accesses destroyed player ped + TheCamera.Restore(); +#endif + CDraw::SetFOV(70.0f); +} + +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 + int i = CPools::GetPedPool()->GetSize(); + while (--i >= 0) { + CPed* ped = CPools::GetPedPool()->GetSlot(i); + if (!ped) + continue; + if (ped->m_attractor) + GetPedAttractorManager()->DeRegisterPed(ped, ped->m_attractor); + } + 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; + CurrArea = CGame::currArea; + TimeStepNonClipped = CTimer::GetTimeStepNonClipped(); + TimeStep = CTimer::GetTimeStep(); + TimeScale = CTimer::GetTimeScale(); + ms_nNumCivMale_Stored = CPopulation::ms_nNumCivMale; + ms_nNumCivFemale_Stored = CPopulation::ms_nNumCivFemale; + ms_nNumCop_Stored = CPopulation::ms_nNumCop; + ms_nNumEmergency_Stored = CPopulation::ms_nNumEmergency; + ms_nNumGang1_Stored = CPopulation::ms_nNumGang1; + ms_nNumGang2_Stored = CPopulation::ms_nNumGang2; + ms_nNumGang3_Stored = CPopulation::ms_nNumGang3; + ms_nNumGang4_Stored = CPopulation::ms_nNumGang4; + ms_nNumGang5_Stored = CPopulation::ms_nNumGang5; + ms_nNumGang6_Stored = CPopulation::ms_nNumGang6; + ms_nNumGang7_Stored = CPopulation::ms_nNumGang7; + ms_nNumGang8_Stored = CPopulation::ms_nNumGang8; + ms_nNumGang9_Stored = CPopulation::ms_nNumGang9; + ms_nNumDummy_Stored = CPopulation::ms_nNumDummy; + ms_nTotalCivPeds_Stored = CPopulation::ms_nTotalCivPeds; + ms_nTotalGangPeds_Stored = CPopulation::ms_nTotalGangPeds; + ms_nTotalPeds_Stored = CPopulation::ms_nTotalPeds; + ms_nTotalMissionPeds_Stored = CPopulation::ms_nTotalMissionPeds; + 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]); + } + 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)); + CScriptPaths::Save_ForReplay(); +} + +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); + for (int j = 0; j < TOTAL_WEAPON_SLOTS; j++) { + int mi1 = CWeaponInfo::GetWeaponInfo(ped->m_weapons[j].m_eWeaponType)->m_nModelId; + if (mi1 != -1) + CStreaming::RequestModel(mi1, STREAMFLAGS_DEPENDENCY); + int mi2 = CWeaponInfo::GetWeaponInfo(ped->m_weapons[j].m_eWeaponType)->m_nModel2Id; + if (mi2 != -1) + CStreaming::RequestModel(mi2, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + ped->m_weapons[j].Initialise(ped->m_weapons[j].m_eWeaponType, ped->m_weapons[j].m_nAmmoTotal); + } + if (ped->m_wepModelID >= 0) { + ped->m_pWeaponModel = nil; + if (ped->IsPlayer()) + ((CPlayerPed*)ped)->m_pMinigunTopAtomic = nil; + ped->AddWeaponModel(ped->m_wepModelID); + } + if (ped->m_nPedType == PEDTYPE_COP) + ((CCopPed*)ped)->m_pStinger = new CStinger; + } + 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 (vehicle->IsCar()) { + CAutomobile* car = (CAutomobile*)vehicle; + if (mi == MI_DODO) { + RpAtomicSetFlags((RpAtomic*)GetFirstObject(car->m_aCarNodes[CAR_WHEEL_LF]), 0); + CMatrix tmp1; + tmp1.Attach(RwFrameGetMatrix(car->m_aCarNodes[CAR_WHEEL_RF]), false); + CMatrix tmp2(RwFrameGetMatrix(car->m_aCarNodes[CAR_WHEEL_LF]), false); + tmp1.GetPosition() += CVector(tmp2.GetPosition().x + 0.1f, 0.0f, tmp2.GetPosition().z); + tmp1.UpdateRW(); + } + else if (mi == MI_HUNTER) { + RpAtomicSetFlags((RpAtomic*)GetFirstObject(car->m_aCarNodes[CAR_WHEEL_LB]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(car->m_aCarNodes[CAR_WHEEL_RB]), 0); + } + else if (vehicle->IsRealHeli()) { + RpAtomicSetFlags((RpAtomic*)GetFirstObject(car->m_aCarNodes[CAR_WHEEL_LF]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(car->m_aCarNodes[CAR_WHEEL_RF]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(car->m_aCarNodes[CAR_WHEEL_LB]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(car->m_aCarNodes[CAR_WHEEL_RB]), 0); + } + } + 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(); + object->m_rwObject = nil; + object->m_modelIndex = -1; + object->SetModelIndexNoCreate(mi); + object->GetMatrix().m_attachment = nil; + } + i = CPools::GetDummyPool()->GetSize(); + while (--i >= 0) { + CDummy* dummy = CPools::GetDummyPool()->GetSlot(i); + if (!dummy) + continue; + int mi = dummy->GetModelIndex(); + dummy->m_rwObject = nil; + dummy->m_modelIndex = -1; + dummy->SetModelIndexNoCreate(mi); + dummy->GetMatrix().m_attachment = nil; + } + ++ClockMinutes; + 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; + CGame::currArea = CurrArea; + CPopulation::ms_nNumCivMale = ms_nNumCivMale_Stored; + CPopulation::ms_nNumCivFemale = ms_nNumCivFemale_Stored; + CPopulation::ms_nNumCop = ms_nNumCop_Stored; + CPopulation::ms_nNumEmergency = ms_nNumEmergency_Stored; + CPopulation::ms_nNumGang1 = ms_nNumGang1_Stored; + CPopulation::ms_nNumGang2 = ms_nNumGang2_Stored; + CPopulation::ms_nNumGang3 = ms_nNumGang3_Stored; + CPopulation::ms_nNumGang4 = ms_nNumGang4_Stored; + CPopulation::ms_nNumGang5 = ms_nNumGang5_Stored; + CPopulation::ms_nNumGang6 = ms_nNumGang6_Stored; + CPopulation::ms_nNumGang7 = ms_nNumGang7_Stored; + CPopulation::ms_nNumGang8 = ms_nNumGang8_Stored; + CPopulation::ms_nNumGang9 = ms_nNumGang9_Stored; + CPopulation::ms_nNumDummy = ms_nNumDummy_Stored; + CPopulation::ms_nTotalCivPeds = ms_nTotalCivPeds_Stored; + CPopulation::ms_nTotalGangPeds = ms_nTotalGangPeds_Stored; + CPopulation::ms_nTotalPeds = ms_nTotalPeds_Stored; + CPopulation::ms_nTotalMissionPeds = ms_nTotalMissionPeds_Stored; + 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; + 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; + CScriptPaths::Load_ForReplay(); + CExplosion::ClearAllExplosions(); + 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_BIKE: + CStreaming::RequestModel(((tBikeUpdatePacket*)&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_BIKE: return sizeof(tBikeUpdatePacket); + 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); + case REPLAYPACKET_PARTICLE: return sizeof(tParticlePacket); + case REPLAYPACKET_MISC: return sizeof(tMiscPacket); + 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_STANDARD); + if (Mode == MODE_PLAYBACK) + CFont::PrintString(SCREEN_WIDTH/15, SCREEN_HEIGHT/10, TheText.Get("REPLAY")); +} +#endif diff --git a/src/miami/control/Replay.h b/src/miami/control/Replay.h new file mode 100644 index 00000000..5dd8b651 --- /dev/null +++ b/src/miami/control/Replay.h @@ -0,0 +1,417 @@ +#pragma once + +#include "Pools.h" +#include "World.h" +#include "WeaponEffects.h" +#include "ParticleType.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 groupId; + uint8 secAnimId; + uint8 secTime; + uint8 secSpeed; + uint8 secGroupId; + uint8 blendAmount; + uint8 partAnimId; + uint8 partAnimTime; + uint8 partAnimSpeed; + uint8 partBlendAmount; + uint8 partGroupId; +}; + +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]; + int8 aBlendDelta[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aFunctionCallbackID[NUM_MAIN_ANIMS_IN_REPLAY]; + uint16 aFlags[NUM_MAIN_ANIMS_IN_REPLAY]; + uint8 aGroupId[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]; + int8 aBlendDelta2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint8 aFunctionCallbackID2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint16 aFlags2[NUM_PARTIAL_ANIMS_IN_REPLAY]; + uint8 aGroupId2[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, + REPLAYCAMMODE_FIXED + }; + + enum { + REPLAYPACKET_END = 0, + REPLAYPACKET_VEHICLE, + REPLAYPACKET_BIKE, + REPLAYPACKET_PED_HEADER, + REPLAYPACKET_PED_UPDATE, + REPLAYPACKET_GENERAL, + REPLAYPACKET_CLOCK, + REPLAYPACKET_WEATHER, + REPLAYPACKET_ENDOFFRAME, + REPLAYPACKET_TIMER, + REPLAYPACKET_BULLET_TRACES, + REPLAYPACKET_PARTICLE, + REPLAYPACKET_MISC + }; + + 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; + uint16 weapon_model; + int8 assoc_group_id; + bool is_visible; + }; + 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; + bool render_scorched; + int8 skimmer_speed; + int8 vehicle_type; + + }; + VALIDATE_SIZE(tVehicleUpdatePacket, 52); + + struct tBikeUpdatePacket + { + uint8 type; + uint8 index; + uint8 health; + uint8 acceleration; + CCompressedMatrixNotAligned matrix; + int8 door_angles[2]; + uint16 mi; + int8 velocityX; + int8 velocityY; + int8 velocityZ; + int8 wheel_state; + uint8 wheel_susp_dist[4]; + uint8 wheel_rotation[4]; + uint8 primary_color; + uint8 secondary_color; + int8 lean_angle; + int8 wheel_angle; + + }; + VALIDATE_SIZE(tBikeUpdatePacket, 44); + + struct tParticlePacket + { + uint8 type; + uint8 particle_type; + int8 dir_x; + int8 dir_y; + int8 dir_z; + uint8 r; + uint8 g; + uint8 b; + uint8 a; + int16 pos_x; + int16 pos_y; + int16 pos_z; + float size; + }; + VALIDATE_SIZE(tParticlePacket, 20); + + struct tMiscPacket + { + uint8 type; + uint32 cam_shake_start; + float cam_shake_strength; + uint8 cur_area; + uint8 video_cam : 1; + uint8 lift_cam : 1; + }; + + VALIDATE_SIZE(tMiscPacket, 16); + +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 CCutsceneObject* 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; + static int ms_nNumCivMale_Stored; + static int ms_nNumCivFemale_Stored; + static int ms_nNumCop_Stored; + static int ms_nNumEmergency_Stored; + static int ms_nNumGang1_Stored; + static int ms_nNumGang2_Stored; + static int ms_nNumGang3_Stored; + static int ms_nNumGang4_Stored; + static int ms_nNumGang5_Stored; + static int ms_nNumGang6_Stored; + static int ms_nNumGang7_Stored; + static int ms_nNumGang8_Stored; + static int ms_nNumGang9_Stored; + static int ms_nNumDummy_Stored; + static int ms_nTotalCarPassengerPeds_Stored; + static int ms_nTotalCivPeds_Stored; + static int ms_nTotalGangPeds_Stored; + static int ms_nTotalPeds_Stored; + static int ms_nTotalMissionPeds_Stored; + static uint8* pGarages; + static CFire* FireArray; + static uint32 NumOfFires; + static uint8* paProjectileInfo; + static uint8* paProjectiles; + static uint8 CurrArea; +#ifdef FIX_BUGS + 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; + static void RecordParticle(tParticleType type, CVector const& vecPos, CVector const& vecDir, float fSize, RwRGBA const& color) 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 StoreBikeUpdate(CVehicle* vehicle, int id); + static void ProcessCarUpdate(CVehicle *vehicle, float interpolation, CAddressInReplayBuffer *buffer); + static void ProcessBikeUpdate(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); + static void GoToNextBlock(void); +#endif +}; diff --git a/src/miami/control/Restart.cpp b/src/miami/control/Restart.cpp new file mode 100644 index 00000000..af38537d --- /dev/null +++ b/src/miami/control/Restart.cpp @@ -0,0 +1,250 @@ +#include "common.h" + +#include "Restart.h" +#include "SaveBuf.h" +#include "Zones.h" +#include "PathFind.h" +#include "SaveBuf.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::GetLevelFromPosition(&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::GetLevelFromPosition(&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::GetLevelFromPosition(&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::GetLevelFromPosition(&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/miami/control/Restart.h b/src/miami/control/Restart.h new file mode 100644 index 00000000..5d84c723 --- /dev/null +++ b/src/miami/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/miami/control/RoadBlocks.cpp b/src/miami/control/RoadBlocks.cpp new file mode 100644 index 00000000..e30313bf --- /dev/null +++ b/src/miami/control/RoadBlocks.cpp @@ -0,0 +1,298 @@ +#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" +#include "Object.h" + +#define ROADBLOCKDIST (90.0f) +#define ROADBLOCK_OBJECT_WIDTH (4.0f) + +int16 CRoadBlocks::NumRoadBlocks; +int16 CRoadBlocks::RoadBlockNodes[NUMROADBLOCKS]; +bool CRoadBlocks::InOrOut[NUMROADBLOCKS]; +CScriptRoadblock CRoadBlocks::aScriptRoadBlocks[NUM_SCRIPT_ROADBLOCKS]; + +#ifdef SECUROM +uint8 roadBlocksPirateCheck = 0; +#endif + +void +CRoadBlocks::Init(void) +{ + int i; + NumRoadBlocks = 0; + for(i = 0; i < ThePaths.m_numCarPathNodes; i++){ + if(ThePaths.m_pathNodes[i].bUseInRoadBlock && ThePaths.m_pathNodes[i].numLinks == 2){ + if (NumRoadBlocks < NUMROADBLOCKS) { + InOrOut[NumRoadBlocks] = true; + RoadBlockNodes[NumRoadBlocks] = i; + NumRoadBlocks++; + } else { +#ifndef MASTER + printf("Not enough room for the potential roadblocks\n"); +#endif + // FIX: Don't iterate loop after NUMROADBLOCKS + return; + } + } + } + + ClearScriptRoadBlocks(); +} + +void +CRoadBlocks::GenerateRoadBlockCopsForCar(CVehicle* pVehicle, int32 roadBlockType) +{ + 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_FBIRANCH: + 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_nExtendedRangeTimer = CTimer::GetTimeInMilliseconds() + 10000; + pCopPed->m_nRoadblockVeh = pVehicle; + pCopPed->m_nRoadblockVeh->RegisterReference((CEntity**)&pCopPed->m_nRoadblockVeh); + pCopPed->bCrouchWhenShooting = roadBlockType == 2 ? false : true; + 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) +{ + CMatrix tmp1, tmp2; + static int16 unk; +#ifdef SQUEEZE_PERFORMANCE + if (FindPlayerPed()->m_pWanted->m_RoadblockDensity == 0) + return; +#endif + 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++) { + int16 node = RoadBlockNodes[nRoadblockNode]; + CVector2D vecDistance = FindPlayerCoors() - ThePaths.m_pathNodes[node].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) { + CCarPathLink* pLink1 = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[ThePaths.m_pathNodes[node].firstLink]]; + CCarPathLink* pLink2 = &ThePaths.m_carPathLinks[ThePaths.m_carPathConnections[ThePaths.m_pathNodes[node].firstLink + 1]]; + int lanes = Min(pLink1->numRightLanes + pLink1->numLeftLanes, pLink2->numLeftLanes + pLink2->numRightLanes); + float length = LANE_WIDTH * (lanes + 1); + CVector forward(pLink2->GetY() - pLink1->GetY(), -(pLink2->GetX() - pLink1->GetX()), 0.0f); + forward.Normalise(); + if (ThePaths.m_pathNodes[node].HasDivider()) { + CreateRoadBlockBetween2Points( + ThePaths.m_pathNodes[node].GetPosition() + (length * 0.5f + ThePaths.m_pathNodes[node].GetDividerWidth()) * forward, + ThePaths.m_pathNodes[node].GetPosition() + ThePaths.m_pathNodes[node].GetDividerWidth() * forward); + CreateRoadBlockBetween2Points( + ThePaths.m_pathNodes[node].GetPosition() - ThePaths.m_pathNodes[node].GetDividerWidth() * forward, + ThePaths.m_pathNodes[node].GetPosition() - (length * 0.5f + ThePaths.m_pathNodes[node].GetDividerWidth()) * forward); + } + else { + CreateRoadBlockBetween2Points( + ThePaths.m_pathNodes[node].GetPosition() + (length * 0.5f) * forward, + ThePaths.m_pathNodes[node].GetPosition() - (length * 0.5f) * forward); + } + } + } + } + else { + InOrOut[nRoadblockNode] = false; + } + } + int i = CTimer::GetFrameCounter() & 0xF; + if (!aScriptRoadBlocks[i].m_bInUse) + return; + if ((aScriptRoadBlocks[i].GetPosition() - FindPlayerCoors()).Magnitude() < 100.0f) { + CreateRoadBlockBetween2Points(aScriptRoadBlocks[i].m_vInf, aScriptRoadBlocks[i].m_vSup); + aScriptRoadBlocks[i].m_bInUse = false; + } +} + +void +CRoadBlocks::ClearScriptRoadBlocks(void) +{ + for (int i = 0; i < NUM_SCRIPT_ROADBLOCKS; i++) + aScriptRoadBlocks[i].m_bInUse = false; +} + +void +CRoadBlocks::RegisterScriptRoadBlock(CVector vInf, CVector vSup) +{ + int32 i; + for (i = 0; i < NUM_SCRIPT_ROADBLOCKS; i++) { + if (!aScriptRoadBlocks[i].m_bInUse) + break; + } + if (i == NUM_SCRIPT_ROADBLOCKS) + return; + aScriptRoadBlocks[i].m_bInUse = true; + aScriptRoadBlocks[i].m_vInf = vInf; + aScriptRoadBlocks[i].m_vSup = vSup; +} + +void +CRoadBlocks::CreateRoadBlockBetween2Points(CVector point1, CVector point2) +{ +#ifdef SECUROM + if (roadBlocksPirateCheck == 0) + // if not pirated game + // roadBlocksPirateCheck = 1; + // else + roadBlocksPirateCheck = 2; +#endif + CMatrix tmp; + CVector forward = (point2 - point1); + float distBetween = forward.Magnitude(); + CVector pos = (point1 + point2) / 2; + CVector right(forward.y, -forward.x, 0.0f); + forward.Normalise(); + right.Normalise(); + if (DotProduct(FindPlayerCoors() - pos, right) < 0.0f) { + right *= -1.0f; + } + int32 vehicleId = MI_POLICE; + if (FindPlayerPed()->m_pWanted->AreArmyRequired()) + vehicleId = MI_BARRACKS; + else if (FindPlayerPed()->m_pWanted->AreFbiRequired()) + vehicleId = MI_FBICAR; + else if (FindPlayerPed()->m_pWanted->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 numRoadblockVehicles = Min(6, (int16)(distBetween / fModelRadius)); + for (int16 i = 0; i < numRoadblockVehicles; i++) { + float offset = fModelRadius * (i - numRoadblockVehicles / 2); + tmp.SetTranslate(0.0f, 0.0f, 0.0f); + tmp.GetRight() = CVector(forward.y, -forward.x, 0.0f); + tmp.GetForward() = forward; + tmp.GetUp() = CVector(0.0f, 0.0f, 1.0f); + tmp.RotateZ(((CGeneral::GetRandomNumber() & 0xFF) - 128.0f) * 0.003f); + if (CGeneral::GetRandomNumber() & 1) + tmp.RotateZ(((CGeneral::GetRandomNumber() & 0xFF) - 128.0f) * 0.003f + 3.1416f); + tmp.SetTranslateOnly(offset * forward + pos); + tmp.GetPosition().z += 0.6f; + float fModelRadius = CModelInfo::GetColModel(vehicleId)->boundingSphere.radius - 0.25f; + int16 colliding = 0; + CWorld::FindObjectsKindaColliding(tmp.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? + tmp.GetPosition().z += fModelRadius - 0.6f; + pVehicle->SetMatrix(tmp); + 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_nNextLane = pVehicle->AutoPilot.m_nCurrentLane = 0; + pVehicle->AutoPilot.m_nCruiseSpeed = pVehicle->AutoPilot.m_fMaxTrafficSpeed = 0; + pVehicle->bExtendedRange = true; + if (pVehicle->UsesSiren() && 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 = DotProduct(pVehicle->GetRight(), pVehicle->GetPosition() - FindPlayerCoors()) >= 0.0f; + pVehicle->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 7000; + } + else { + delete pVehicle; + } + } + } + int numBarriers = distBetween / ROADBLOCK_OBJECT_WIDTH; + CStreaming::RequestModel(MI_ROADWORKBARRIER1, STREAMFLAGS_DONT_REMOVE); + if (!CStreaming::HasModelLoaded(MI_ROADWORKBARRIER1)) + return; + for (int i = 0; i < numBarriers; i++) { + float offset = ROADBLOCK_OBJECT_WIDTH * (i - numBarriers / 2); + tmp.SetTranslate(0.0f, 0.0f, 0.0f); + tmp.GetRight() = CVector(forward.y, -forward.x, 0.0f); + tmp.GetForward() = forward; + tmp.GetUp() = CVector(0.0f, 0.0f, 1.0f); + tmp.RotateZ(((CGeneral::GetRandomNumber() & 0xFF) - 128.0f) * 0.003f); + tmp.SetTranslateOnly(5.0f * right + offset * forward + pos); + tmp.GetPosition().x += (CGeneral::GetRandomNumber() & 0xF) * 0.1f; + tmp.GetPosition().y += (CGeneral::GetRandomNumber() & 0xF) * 0.1f; + bool found; + tmp.GetPosition().z = CWorld::FindGroundZFor3DCoord(tmp.GetPosition().x, tmp.GetPosition().y, tmp.GetPosition().z + 2.0f, &found); + if (!found) + continue; + int16 colliding = 0; + CBaseModelInfo* pMI = CModelInfo::GetModelInfo(MI_ROADWORKBARRIER1); + tmp.GetPosition().z -= pMI->GetColModel()->boundingBox.min.z; + CWorld::FindObjectsKindaColliding(tmp.GetPosition(), pMI->GetColModel()->boundingSphere.radius, 0, &colliding, 2, nil, false, true, true, false, false); + if (colliding == 0) { + CObject* pObject = new CObject(MI_ROADWORKBARRIER1, true); + pObject->GetMatrix() = tmp; + pObject->ObjectCreatedBy = TEMP_OBJECT; + pObject->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 600000; + CWorld::Add(pObject); + } + } +} diff --git a/src/miami/control/RoadBlocks.h b/src/miami/control/RoadBlocks.h new file mode 100644 index 00000000..ef614950 --- /dev/null +++ b/src/miami/control/RoadBlocks.h @@ -0,0 +1,30 @@ +#pragma once +#include "common.h" + +class CVehicle; + +class CScriptRoadblock +{ +public: + CVector m_vInf; + CVector m_vSup; + bool m_bInUse; + CVector GetPosition() { return (m_vInf + m_vSup) / 2; } +}; + +class CRoadBlocks +{ +public: + static int16 NumRoadBlocks; + static int16 RoadBlockNodes[NUMROADBLOCKS]; + static bool InOrOut[NUMROADBLOCKS]; + static CScriptRoadblock aScriptRoadBlocks[NUM_SCRIPT_ROADBLOCKS]; + + static void Init(void); + static void GenerateRoadBlockCopsForCar(CVehicle* pVehicle, int32 roadBlockType); + static void GenerateRoadBlocks(void); + + static void CreateRoadBlockBetween2Points(CVector, CVector); + static void RegisterScriptRoadBlock(CVector, CVector); + static void ClearScriptRoadBlocks(); +}; diff --git a/src/miami/control/SceneEdit.cpp b/src/miami/control/SceneEdit.cpp new file mode 100644 index 00000000..74d81327 --- /dev/null +++ b/src/miami/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_SPECIAL21) +#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_STANDARD); + 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_STANDARD); +#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_BRASSKNUCKLE; + 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_MINIGUN; + 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/miami/control/SceneEdit.h b/src/miami/control/SceneEdit.h new file mode 100644 index 00000000..7c8fb98a --- /dev/null +++ b/src/miami/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/miami/control/Script.cpp b/src/miami/control/Script.cpp new file mode 100644 index 00000000..a9edbf30 --- /dev/null +++ b/src/miami/control/Script.cpp @@ -0,0 +1,3265 @@ +#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" +#include "main.h" +#include "Ropes.h" +#include "ColStore.h" +#include "Fluff.h" +#include "GameLogic.h" +#include "MBlur.h" +#include "PedRoutes.h" +#include "RoadBlocks.h" +#include "SpecialFX.h" +#include "Timecycle.h" +#include "TxdStore.h" +#include "Bike.h" +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +#include +#endif + +uint8 CTheScripts::ScriptSpace[SIZE_SCRIPT_SPACE]; +CRunningScript CTheScripts::ScriptsArray[MAX_NUM_SCRIPTS]; +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]; +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; +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]; +uint8 CTheScripts::RiotIntensity; +uint32 CTheScripts::LastMissionPassedTime; +uint16 CTheScripts::NumberOfExclusiveMissionScripts; +bool CTheScripts::bPlayerHasMetDebbieHarry; +bool CTheScripts::bPlayerIsInTheStatium; +#if (defined GTA_PC && !defined GTAVC_JP_PATCH || defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT || defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) +int16 CTheScripts::CardStack[CARDS_IN_DECK * MAX_DECKS]; +int16 CTheScripts::CardStackPosition; +#endif + +#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", + "range", + "race", + "pizza", + "rcheli", + "rcplne1", + "rcrace1", + "cokerun", + "buypro1", + "carbuy1", + "buypro2", + "icecut", + "icecre1", + "buypro3", + "buypro4", + "buypro5", + "buypro6", + "buypro7", + "buypro8", + "buypro9", + "buypro10", + "buypro11", + "ovalrng", + "mm", + "kickst", + "heli1sc", + "heli2sc", + "heli3sc", + "heli4sc", + "carpark_1", + "bmx_1", + "bmx_2" +}; + +static const char* MissionScripts[] = { + "LAWYER1", + "LAWYER2", + "LAWYER3", + "LAWYER4", + "GENERL1", + "COL2", + "GENERL3", + "COL_4", + "COL_5", + "baron1", + "baron2", + "baron3", + "baron4", + "kent1", + "baron5", + "serg1", + "serg2", + "serg3", + "bankjo1", + "bankjo2", + "bankjo3", + "bankjo4", + "phil1", + "phil2", + "porno1", + "porno2", + "porno3", + "porno4", + "protec1", + "protec2", + "protec3", + "count1", + "count2", + "CAP_1", + "FIN_1", + "bike1", + "bike2", + "bike3", + "rockb1", + "rockb2", + "rockb3", + "cuban1", + "cuban2", + "cuban3", + "cuban4", + "hait1", + "hait2", + "hait3", + "assin1", + "assin2", + "assin3", + "assin4", + "assin5", + "taxwar1", + "taxwar2", + "taxwar3" +}; + +int AllowMissionReplay; +uint32 NextMissionDelay; +uint32 MissionStartTime; +uint32 WaitForMissionActivate; +uint32 WaitForSave; +float oldTargetX; +float oldTargetY; +int missionRetryScriptIndex; +bool doingMissionRetry; +bool gbTryingPorn4Again; +int IsInAmmunation; +int MissionSkipLevel; + +#ifdef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT +bool UsingMobileScript; +bool AlreadySavedGame; +#endif + +#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){ + switch (m_sEntities[i].type) { + case CLEANUP_CAR: + { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(m_sEntities[i].id); + if (pVehicle) { + if (pVehicle->bIsStaticWaitingForCollision) { + pVehicle->bIsStaticWaitingForCollision = false; + if (!pVehicle->GetIsStatic()) + pVehicle->AddToMovingList(); + } + } + break; + } + case CLEANUP_CHAR: + { + CPed* pPed = CPools::GetPedPool()->GetAt(m_sEntities[i].id); + if (pPed) { + if (pPed->bIsStaticWaitingForCollision) { + pPed->bIsStaticWaitingForCollision = false; + if (!pPed->GetIsStatic()) + pPed->AddToMovingList(); + } + } + break; + } + case CLEANUP_OBJECT: + { + CObject* pObject = CPools::GetObjectPool()->GetAt(m_sEntities[i].id); + if (pObject) { + if (pObject->bIsStaticWaitingForCollision) { + pObject->bIsStaticWaitingForCollision = false; + if (!pObject->GetIsStatic()) + pObject->AddToMovingList(); + } + } + break; + } + default: + break; + } + m_sEntities[i].id = 0; + m_sEntities[i].type = CLEANUP_UNUSED; + m_nCount--; + } + } +} + +void CMissionCleanup::CheckIfCollisionHasLoadedForMissionObjects() +{ + for (int i = 0; i < MAX_CLEANUP; i++) { + switch (m_sEntities[i].type) { + case CLEANUP_CAR: + { + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(m_sEntities[i].id); + if (pVehicle) { + if (pVehicle->bIsStaticWaitingForCollision) { + if (CColStore::HasCollisionLoaded(pVehicle->GetPosition())) { + pVehicle->bIsStaticWaitingForCollision = false; + if (!pVehicle->GetIsStatic()) + pVehicle->AddToMovingList(); + } + } + } + break; + } + case CLEANUP_CHAR: + { + CPed* pPed = CPools::GetPedPool()->GetAt(m_sEntities[i].id); + if (pPed) { + if (pPed->bIsStaticWaitingForCollision) { + if (CColStore::HasCollisionLoaded(pPed->GetPosition())) { + pPed->bIsStaticWaitingForCollision = false; + if (!pPed->GetIsStatic()) + pPed->AddToMovingList(); + } + } + } + break; + } + case CLEANUP_OBJECT: + { + CObject* pObject = CPools::GetObjectPool()->GetAt(m_sEntities[i].id); + if (pObject) { + if (pObject->bIsStaticWaitingForCollision) { + if (CColStore::HasCollisionLoaded(pObject->GetPosition())) { + pObject->bIsStaticWaitingForCollision = false; + if (!pObject->GetIsStatic()) + pObject->AddToMovingList(); + } + } + } + break; + } + default: + break; + } + } +} + +void CMissionCleanup::Process() +{ + CPopulation::m_AllRandomPedsThisType = -1; + CPopulation::PedDensityMultiplier = 1.0f; + CCarCtrl::CarDensityMultiplier = 1.0f; + CPed::nThreatReactionRangeMultiplier = 1; + CPed::nEnterCarRangeMultiplier = 1; + FindPlayerPed()->m_pWanted->m_fCrimeSensitivity = 1.0f; + CRoadBlocks::ClearScriptRoadBlocks(); + CRouteNode::Initialise(); + if (!CWorld::Players[CWorld::PlayerInFocus].m_pRemoteVehicle) + TheCamera.Restore(); + TheCamera.SetWideScreenOff(); + CSpecialFX::bLiftCam = false; + CSpecialFX::bVideoCam = false; + CTimeCycle::StopExtraColour(0); + for (int i = 0; i < MISSION_AUDIO_SLOTS; i++) + DMAudio.ClearMissionAudio(i); + 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; + CTheScripts::RemoveScriptTextureDictionary(); + 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); + CWorld::Players[0].m_pPed->m_nFadeDrunkenness = 1; + CWorld::Players[0].m_pPed->m_nDrunkCountdown = 0; + CPad::GetPad(0)->SetDrunkInputDelay(0); + CWorld::Players[0].m_bDriveByAllowed = true; + DMAudio.ShutUpPlayerTalking(FALSE); + CVehicle::bDisableRemoteDetonation = false; + CVehicle::bDisableRemoteDetonationOnContact = false; + CGameLogic::ClearShortCut(); + CTheScripts::RiotIntensity = 0; + 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; + } + RemoveEntityFromList(m_sEntities[i].id, m_sEntities[i].type); + } +#ifdef SECUROM + if ((myrand() & 3) == 2){ + // if pirated game + CWeather::ForceHurricaneWeather(); + } +#endif +} + +/* 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++){ + uint16 varIndex; + switch (CTheScripts::Read1ByteFromScript(pIp)) + { + case ARGUMENT_INT32: + case ARGUMENT_FLOAT: + 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; + default: + script_assert(0); + break; + } + } +} + +int32 CRunningScript::CollectNextParameterWithoutIncreasingPC(uint32 ip) +{ + uint32* pIp = &ip; + 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: + return CTheScripts::Read4BytesFromScript(pIp); + 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_bIsActive = false; + 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\\freeroam_miami.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; + LastMissionPassedTime = (uint32)-1; + 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; + NumberOfExclusiveMissionScripts = 0; + NumberOfMissionScripts = 0; + LargestMissionScriptSize = 0; + MainScriptSize = 0; + ReadMultiScriptFileOffsetsFromScript(); + FailCurrentMission = 0; + DbgFlag = false; + NumScriptDebugLines = 0; + RiotIntensity = 0; + bPlayerHasMetDebbieHarry = false; + bPlayerIsInTheStatium = false; + 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; + RemoveScriptTextureDictionary(); + 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 +#ifdef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT + UsingMobileScript = false; + AlreadySavedGame = false; +#endif +} + +void CTheScripts::RemoveScriptTextureDictionary() +{ + for (int i = 0; i < ARRAY_SIZE(CTheScripts::ScriptSprites); i++) + CTheScripts::ScriptSprites[i].Delete(); + int slot = CTxdStore::FindTxdSlot("script"); + if (slot != -1) + CTxdStore::RemoveTxd(slot); +} + +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); + pNew->m_bIsActive = true; + return pNew; +} + +void CTheScripts::Process() +{ + if (CReplay::IsPlayingBack()) + return; + CommandsExecuted = 0; + ScriptsUpdated = 0; + float timeStep = CTimer::GetTimeStepInMilliseconds(); + UpsideDownCars.UpdateTimers(); + StuckCars.Process(); + MissionCleanUp.CheckIfCollisionHasLoadedForMissionObjects(); + DrawScriptSpheres(); + if (FailCurrentMission) + --FailCurrentMission; + 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; + static bool AlreadyResetHealth; + 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; + AlreadyResetHealth = false; + TimeToWaitTill = CTimer::GetTimeInMilliseconds() + 500; + break; + case MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART: + if (!AlreadyResetHealth) { + AlreadyResetHealth = true; + CPlayerPed* pPlayerPed = FindPlayerPed(); + if (pPlayerPed) { + CPlayerInfo* pPlayerInfo = pPlayerPed->GetPlayerInfoForThisPlayerPed(); + if (pPlayerInfo) + pPlayerPed->m_fHealth = pPlayerInfo->m_nMaxHealth; + } + } + 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; + if (script && !script->m_bIsActive) + script = nil; + } + 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); + else if (command < 1100) + retval = ProcessCommands1000To1099(command); + else if (command < 1200) + retval = ProcessCommands1100To1199(command); + else if (command < 1300) + retval = ProcessCommands1200To1299(command); + else if (command < 1400) + retval = ProcessCommands1300To1399(command); + else if (command < 1500) + retval = ProcessCommands1400To1499(command); +#ifdef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT + if (!AlreadySavedGame) // we need to ignore first "fake" command which actually just saves the game +#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; + int32 *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; + } + //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; + } + //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]); + 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); + m_bIsActive = false; +#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]); + pNew->m_bIsActive = true; + 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 < NUMPLAYERS); + 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->m_pMyVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + ped->m_pMyVehicle->Teleport(pos); // removed dumb stuff that was present here + CTheScripts::ClearSpaceForMissionEntity(pos, ped->m_pMyVehicle); + return 0; + } + pos.z += ped->GetDistanceFromCentreOfMassToBaseOfModel(); + CVector vOldPos = ped->GetPosition(); + ped->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + if (ped) { // great time to check + for (int i = 0; i < ped->m_numNearPeds; i++) { + CPed* pTestedPed = ped->m_nearPeds[i]; + if (!pTestedPed || !IsPedPointerValid(pTestedPed)) + continue; + if (pTestedPed->m_pedInObjective == ped && pTestedPed->m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION) { + CVector vFollowerPos = pTestedPed->GetFormationPosition(); + CTheScripts::ClearSpaceForMissionEntity(vFollowerPos, ped); + bool bFound = false; + vFollowerPos.z = CWorld::FindGroundZFor3DCoord(vFollowerPos.x, vFollowerPos.y, vFollowerPos.z + 1.0f, &bFound) + 1.0f; + if (bFound) { + if (CWorld::GetIsLineOfSightClear(vFollowerPos, ped->GetPosition(), true, false, false, true, false, false)) { + pTestedPed->Teleport(vFollowerPos); + } + } + } + else if (pTestedPed->m_leader == ped) { + CVector vFollowerPos; + if (pTestedPed->m_pedFormation) + vFollowerPos = pTestedPed->GetFormationPosition(); + else + vFollowerPos = ped->GetPosition() + pTestedPed->GetPosition() - vOldPos; + CTheScripts::ClearSpaceForMissionEntity(vFollowerPos, ped); + bool bFound = false; + vFollowerPos.z = CWorld::FindGroundZFor3DCoord(vFollowerPos.x, vFollowerPos.y, vFollowerPos.z + 1.0f, &bFound) + 1.0f; + if (bFound) { + if (CWorld::GetIsLineOfSightClear(vFollowerPos, ped->GetPosition(), true, false, false, true, false, false)) { + pTestedPed->Teleport(vFollowerPos); + } + } + } + } + } + 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; + int32 *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; + ped->bIsPlayerFriend = 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); + if (m_bIsMissionScript) + ped->bIsStaticWaitingForCollision = true; + 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]); + CTheScripts::RemoveThisPed(ped); + 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; + } + //case COMMAND_CHAR_WANDER_RANGE: + case COMMAND_CHAR_FOLLOW_PATH: + { + CollectParameters(&m_nIp, 6); + CPed* ped = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(ped); + if (ped->GetPedState() == PED_ATTACK || ped->GetPedState() == PED_FIGHT || !ped->IsPedInControl()) + return 0; + CVector pos = *(CVector*)&ScriptParams[1]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + float radius = *(float*)&ScriptParams[4]; + eMoveState state; + switch (ScriptParams[5]) { + case 0: state = PEDMOVE_WALK; break; + case 1: state = PEDMOVE_RUN; break; + default: assert(0); + } + ped->ClearAll(); + ped->m_pathNodeTimer = 0; + ped->SetFollowPath(pos, radius, state, nil, nil, 999999); + 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); + // removed dumb stuff again + if (!vehicle) { + pos.z += ped->GetDistanceFromCentreOfMassToBaseOfModel(); + ped->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, ped); + for (int i = 0; i < ped->m_numNearPeds; i++) { + CPed* pNearPed = ped->m_nearPeds[i]; + if (pNearPed->m_leader == ped) { + pNearPed->Teleport(pos); + pNearPed->PositionAnyPedOutOfCollision(); + } + } + } + else { + pos.z += vehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + vehicle->Teleport(pos); + CTheScripts::ClearSpaceForMissionEntity(pos, vehicle); + } + 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; + boat->AutoPilot.m_nCruiseSpeed = boat->AutoPilot.m_fMaxTrafficSpeed = 20.0f; + if (m_bIsMissionScript) + boat->bIsStaticWaitingForCollision = true; + CWorld::Add(boat); + handle = CPools::GetVehiclePool()->GetIndex(boat); + } + else { + CVehicle* car; + + if (!CModelInfo::IsBikeModel(ScriptParams[0])) + car = new CAutomobile(ScriptParams[0], MISSION_VEHICLE); + else { + car = new CBike(ScriptParams[0], MISSION_VEHICLE); + ((CBike*)(car))->bIsStanding = true; + } + 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; + 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; + if (m_bIsMissionScript) + car->bIsStaticWaitingForCollision = 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(1, 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(1, 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); + car->AutoPilot.m_nCruiseSpeed = Min(*(float*)&ScriptParams[1], 60.0f * car->pHandling->Transmission.fMaxCruiseVelocity); + 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 = CTheScripts::GetTextByKeyFromScript(&m_nIp); +#ifdef MISSION_REPLAY + if (strcmp((char*)&CTheScripts::ScriptSpace[m_nIp - KEY_LENGTH_IN_SCRIPT], "M_FAIL") == 0) { + if (AllowMissionReplay == MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART) + AllowMissionReplay = MISSION_RETRY_STAGE_NORMAL; + if (CanAllowMissionReplay()) + AllowMissionReplay = MISSION_RETRY_STAGE_WAIT_FOR_SCRIPT_TO_TERMINATE; + } +#endif + CollectParameters(&m_nIp, 2); + CMessages::AddBigMessage(key, ScriptParams[0], ScriptParams[1] - 1); + return 0; + } + case COMMAND_PRINT: + { + wchar* key = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + CMessages::AddMessage(key, ScriptParams[0], ScriptParams[1]); + return 0; + } + case COMMAND_PRINT_NOW: + { + wchar* key = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + CMessages::AddMessageJumpQ(key, ScriptParams[0], ScriptParams[1]); + return 0; + } + /* + case COMMAND_PRINT_SOON: + { + wchar* key = CTheScripts::GetTextByKeyFromScript(&m_nIp); + 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; + */ + //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; + 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 && pPed->m_pMyVehicle); + return 0; + } + case COMMAND_IS_PLAYER_IN_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle); + return 0; + } + case COMMAND_IS_BUTTON_PRESSED: + { + CollectParameters(&m_nIp, 2); + UpdateCompareFlag(GetPadState(ScriptParams[0], ScriptParams[1]) != 0); + 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(); + CBaseModelInfo* pModelInfo = CModelInfo::GetModelInfo(mi); + if (pModelInfo->IsBuilding() && ((CSimpleModelInfo*)pModelInfo)->m_isBigBuilding) + pObj->SetupBigBuilding(); + 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]; + if (CWorld::Players[ScriptParams[0]].m_nMoney < 0) + CWorld::Players[ScriptParams[0]].m_nMoney = 0; + 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; + } + //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->DyingOrDead()); + 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->bIsDrowning); + 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; + } + //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; + } + //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, ZONE_DEFAULT); + if (zoneToCheck != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; /* why only if zone != -1? */ + CVector pos = pPlayer->GetPos(); + CZone* pZone = CTheZones::GetNavigationZone(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()); + 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; + } +#ifdef SUPPORT_GINPUT_SCRIPT + case COMMAND_HAS_PAD_IN_HANDS: + UpdateCompareFlag(CPad::GetPad(0)->IsAffectedByController); + return 0; +#else + //case COMMAND_ORDER_CHAR_TO_BACKDOOR: +#endif + //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->bIsPlayerFriend = false; + if (pVehicle->bIsBus) + pPed->bRenderPedInCar = 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; + pPed->AddInCarAnims(pVehicle, true); + 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->RemoveInCarAnims(); + 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->ReplaceWeaponWhenExitingVehicle(); + 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, 1000.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; + } + //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; + for (int i = 0; i < ARRAY_SIZE(MissionScripts); i++) { + if (!CGeneral::faststricmp(m_abScriptName, MissionScripts[i])) + return true; + } + return false; +} + +uint32 AddExtraDeathDelay() +{ + 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.m_bAttemptingMissionRetry = true; + 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/miami/control/Script.h b/src/miami/control/Script.h new file mode 100644 index 00000000..7f9a7717 --- /dev/null +++ b/src/miami/control/Script.h @@ -0,0 +1,639 @@ +#pragma once +#include "Font.h" +#include "PedType.h" +#include "Text.h" +#include "Sprite2d.h" + +class CEntity; +class CBuilding; +class CPhysical; +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 COP_PED_FIND_Z_OFFSET (10.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 (252) +#define SPHERE_MARKER_G (138) +#define SPHERE_MARKER_B (242) +#define SPHERE_MARKER_A (228) +#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) + +//#define GTA_SCRIPT_COLLECTIVE + +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 = 100 +}; + +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_STANDARD; + 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 = 16 +}; + +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(); + void CheckIfCollisionHasLoadedForMissionObjects(); +}; + +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 { + MAX_STACK_DEPTH = 6, + 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_bIsActive; + 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); + int8 ProcessCommands1100To1199(int32); + int8 ProcessCommands1200To1299(int32); + int8 ProcessCommands1300To1399(int32); + int8 ProcessCommands1400To1499(int32); + + void LocatePlayerCommand(int32, uint32*); + void LocatePlayerCharCommand(int32, uint32*); + void LocatePlayerCarCommand(int32, uint32*); + void LocateCharCommand(int32, uint32*); + void LocateCharCharCommand(int32, uint32*); + void LocateCharCarCommand(int32, uint32*); + void LocateCharObjectCommand(int32, uint32*); + void LocateCarCommand(int32, uint32*); + void LocateSniperBulletCommand(int32, uint32*); + void PlayerInAreaCheckCommand(int32, uint32*); + void PlayerInAngledAreaCheckCommand(int32, uint32*); + void CharInAreaCheckCommand(int32, uint32*); + void CarInAreaCheckCommand(int32, uint32*); + void LocateObjectCommand(int32, uint32*); + void ObjectInAreaCheckCommand(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 ThisIsAValidRandomCop(uint32 mi, int cop, int swat, int fbi, int army, int miami); + bool ThisIsAValidRandomPed(uint32 pedtype, int civ, int gang, int criminal); + + bool CheckDamagedWeaponType(int32 actual, int32 type); + +}; + + +enum { + VAR_LOCAL = 1, + VAR_GLOBAL = 2, +}; + +enum { +#ifdef PS2 + SIZE_MAIN_SCRIPT = 205512, +#else + SIZE_MAIN_SCRIPT = 225512, +#endif + SIZE_MISSION_SCRIPT = 35000, + SIZE_SCRIPT_SPACE = SIZE_MAIN_SCRIPT + SIZE_MISSION_SCRIPT +}; + +enum { + MAX_NUM_SCRIPTS = 128, + MAX_NUM_INTRO_TEXT_LINES = 48, + MAX_NUM_INTRO_RECTANGLES = 16, + MAX_NUM_SCRIPT_SRPITES = 16, + MAX_NUM_SCRIPT_SPHERES = 16, + MAX_NUM_USED_OBJECTS = 220, + 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 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 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 uint16 NumScriptDebugLines; + static uint16 NumberOfIntroRectanglesThisFrame; + static uint16 NumberOfIntroTextLinesThisFrame; + static uint8 UseTextCommands; + static uint16 CommandsExecuted; + static uint16 ScriptsUpdated; + static uint32 LastMissionPassedTime; + static uint16 NumberOfExclusiveMissionScripts; +#if (defined GTA_PC && !defined GTAVC_JP_PATCH || defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT || defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) +#define CARDS_IN_SUIT (13) +#define NUM_SUITS (4) +#define MAX_DECKS (6) +#define CARDS_IN_DECK (CARDS_IN_SUIT * NUM_SUITS) +#define CARDS_IN_STACK (CARDS_IN_DECK * MAX_DECKS) + static int16 CardStack[CARDS_IN_STACK]; + static int16 CardStackPosition; +#endif + static bool bPlayerIsInTheStatium; + static uint8 RiotIntensity; + static bool bPlayerHasMetDebbieHarry; + + 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 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); + static void RemoveScriptTextureDictionary(); +public: + static void RemoveThisPed(CPed* pPed); + + static uint32& GetLastMissionPassedTime() { return LastMissionPassedTime; } +#ifdef MISSION_SWITCHER + static void SwitchToMission(int32 mission); +#endif + +#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 USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT + static bool MissionSupportsMissionReplay(int index) + { + return index >= 3 && index <= 35 || index >= 51 && index <= 65 || index >= 67 && index <= 74 || index >= 83 && index <= 87; + } +#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; +extern bool gbTryingPorn4Again; +extern int IsInAmmunation; +extern int MissionSkipLevel; + +#ifdef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT +extern bool UsingMobileScript; +extern bool AlreadySavedGame; +#endif + +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/miami/control/Script2.cpp b/src/miami/control/Script2.cpp new file mode 100644 index 00000000..4e7a1c3e --- /dev/null +++ b/src/miami/control/Script2.cpp @@ -0,0 +1,1557 @@ +#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) { + //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; + */ + //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; + } + //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; + } + */ + //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; + */ + //case COMMAND_CREATE_GANG_CAR: + case COMMAND_CREATE_CAR_GENERATOR: + { + CollectParameters(&m_nIp, 12); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z > MAP_Z_LOW_LIMIT) + pos.z += 0.015f; + ScriptParams[0] = CTheCarGenerators::CreateCarGenerator( + pos.x, pos.y, pos.z, *(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++; + uint16 offset = CTheScripts::Read2BytesFromScript(&m_nIp); + CollectParameters(&m_nIp, 1); + CUserDisplay::OnscnTimer.AddClock(offset, nil, ScriptParams[0] != 0); + 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++; + int16 counter = CTheScripts::Read2BytesFromScript(&m_nIp); + CollectParameters(&m_nIp, 1); + CUserDisplay::OnscnTimer.AddCounter(counter, ScriptParams[0], nil, 0); + 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]; + int16 gangDensities[NUM_GANGS] = { 0 }; + int i; + + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 12); + for (i = 0; i < NUM_GANGS; i++) + gangDensities[i] = ScriptParams[i + 2]; + int zone = CTheZones::FindZoneByLabelAndReturnIndex(label, ZONE_INFO); + for (int i = 0; i < NUM_GANGS; i++) { + if (gangDensities[i] != 0 && CGangs::GetGangInfo(i)->m_nVehicleMI == -1) + debug("SET_ZONE_CAR_INFO - Gang %d car ratio should be 0 in %s zone\n", i + 1, label); + } + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + while (zone >= 0) { + CTheZones::SetZoneCarInfo(zone, ScriptParams[0], ScriptParams[1], ScriptParams[11], gangDensities); + zone = CTheZones::FindNextZoneByLabelAndReturnIndex(label, ZONE_INFO); + } + return 0; + } + //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, ZONE_DEFAULT); + if (zone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CVector pos = pPed->bInVehicle ? pPed->m_pMyVehicle->GetPosition() : pPed->GetPosition(); + UpdateCompareFlag(CTheZones::PointLiesWithinZone(&pos, CTheZones::GetNavigationZone(zone))); + return 0; + } + //case COMMAND_SET_CAR_DENSITY: + //case COMMAND_SET_PED_DENSITY: + 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]); + if (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]); + if (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, 12); + int16 zone = CTheZones::FindZoneByLabelAndReturnIndex(label, ZONE_INFO); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + while (zone >= 0) { + CTheZones::SetZonePedInfo(zone, ScriptParams[0], ScriptParams[1], ScriptParams[2], ScriptParams[3], + ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7], ScriptParams[8], ScriptParams[9], ScriptParams[10], ScriptParams[11]); + zone = CTheZones::FindNextZoneByLabelAndReturnIndex(label, ZONE_INFO); + } + 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); + 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); + 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); + 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); + 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; + 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(); + angle = RADTODEG(angle); + if (angle < 0.0f) + angle += 360.0f; + if (angle > 360.0f) + angle -= 360.0f; + *(float*)&ScriptParams[0] = angle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_PLAYER_HEADING: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + if (pPed->bInVehicle) + 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(); + angle = RADTODEG(angle); + if (angle < 0.0f) + angle += 360.0f; + if (angle > 360.0f) + angle -= 360.0f; + *(float*)&ScriptParams[0] = 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) + 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(); + angle = RADTODEG(angle); + if (angle < 0.0f) + angle += 360.0f; + if (angle > 360.0f) + angle -= 360.0f; + *(float*)&ScriptParams[0] = 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(); + angle = RADTODEG(angle); + if (angle < 0.0f) + angle += 360.0f; + if (angle > 360.0f) + angle -= 360.0f; + *(float*)&ScriptParams[0] = 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; + } + */ + //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: + return 0; + //case COMMAND_DECLARE_BASE_BRIEF_ID_FOR_CONTACT: + 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); + 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); + 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); + 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); + 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); + 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_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; + case SCRIPT_SOUND_AMMUNATION_BUY_WEAPON: + DMAudio.PlayFrontEndSound(SOUND_PICKUP_WEAPON_BOUGHT, 0); + return 0; + case SCRIPT_SOUND_AMMUNATION_BUY_WEAPON_DENIED: + DMAudio.PlayFrontEndSound(SOUND_GARAGE_NO_MONEY, 0); + return 0; + case SCRIPT_SOUND_IMRAN_ARM_BOMB: + DMAudio.PlayFrontEndSound(SOUND_AMMUNATION_IMRAN_ARM_BOMB, 0); + return 0; + default: + break; + } + if (!DMAudio.IsAudioInitialised()) + return 0; + 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); + if (DMAudio.IsAudioInitialised()) { + cAudioScriptObject* obj = new cAudioScriptObject(); + obj->Posn = *(CVector*)&ScriptParams[0]; + obj->AudioId = ScriptParams[3]; + obj->AudioEntity = DMAudio.CreateLoopingScriptObject(obj); + ScriptParams[0] = CPools::GetAudioScriptObjectPool()->GetIndex(obj); + } + else + ScriptParams[0] = -1; + 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]); + 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->m_pMyVehicle) + pPed->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(pPed->m_weapons[pPed->m_currentWeapon].m_eWeaponType)->m_nModelId); + return 0; + } + //case COMMAND_GIVE_WEAPON_TO_CAR: + case COMMAND_SET_PLAYER_CONTROL: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayer = &CWorld::Players[ScriptParams[0]]; + if (ScriptParams[1]){ + pPlayer->MakePlayerSafe(false); + if (strcmp(m_abScriptName, "serg1") == 0) // Four Iron + pPlayer->m_pPed->ClearFollowPath(); + }else{ + pPlayer->MakePlayerSafe(true); + } + 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 < TOTAL_WEAPON_SLOTS; 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 < TOTAL_WEAPON_SLOTS; i++) { + if (pPed->m_weapons[i].m_eWeaponType == ScriptParams[1]) + pPed->SetCurrentWeapon(i); + } + return 0; + } + //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->bIsPlayerFriend = false; + if (pVehicle->bIsBus) + pPed->bRenderPedInCar = false; + pPed->SetPosition(pVehicle->GetPosition()); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + CPopulation::ms_nTotalMissionPeds++; + CWorld::Add(pPed); + 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); + pPed->bUsesCollision = false; + pPed->AddInCarAnims(pVehicle, false); + pPed->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pPed->GetPosition()); + 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; + } + //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: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_DESTROY_OBJECT, pObject); + return 0; + } + 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; + } + */ + //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; + } + //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); + if (ScriptParams[1] != 0) { + pVehicle->bIsHeavy = true; + pVehicle->m_fMass = 3.0f * pVehicle->pHandling->fMass; + pVehicle->m_fTurnMass = 5.0f * pVehicle->pHandling->fTurnMass; + } + else { + pVehicle->bIsHeavy = false; + pVehicle->m_fMass = pVehicle->pHandling->fMass; + pVehicle->m_fTurnMass = pVehicle->pHandling->fTurnMass; + } + 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; + } + //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); + UpdateCompareFlag(pVehicle->m_nCollisionRecords == 0); + return 0; + } + default: + script_assert(0); + } + return -1; +} diff --git a/src/miami/control/Script3.cpp b/src/miami/control/Script3.cpp new file mode 100644 index 00000000..f831645e --- /dev/null +++ b/src/miami/control/Script3.cpp @@ -0,0 +1,2417 @@ +#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 "GameLogic.h" +#include "Bike.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); +#ifdef SIMPLER_MISSIONS + if (!CGeneral::faststricmp(m_abScriptName, "hait2")) + CExplosion::AddExplosion(nil, nil, (eExplosionType)ScriptParams[3], *(CVector*)&ScriptParams[0], 0, true, 11.25f); + else +#endif + CExplosion::AddExplosion(nil, nil, (eExplosionType)ScriptParams[3], *(CVector*)&ScriptParams[0], 0, true); + 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; + } + //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, 9); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float X2 = *(float*)&ScriptParams[3]; + float Y2 = *(float*)&ScriptParams[4]; + float supX = *(float*)&ScriptParams[5]; + float supY = *(float*)&ScriptParams[6]; + float supZ = *(float*)&ScriptParams[7]; + ScriptParams[0] = CGarages::AddOne(infX, infY, infZ, X2, Y2, supX, supY, supZ, ScriptParams[8], 0); + StoreParameters(&m_nIp, 1); + return 0; + } + /* + case COMMAND_SET_GARAGE_WITH_CAR_MODEL: + { + CollectParameters(&m_nIp, 10); + float infX = *(float*)&ScriptParams[0]; + float infY = *(float*)&ScriptParams[1]; + float infZ = *(float*)&ScriptParams[2]; + float X2 = *(float*)&ScriptParams[3]; + float Y2 = *(float*)&ScriptParams[4]; + float supX = *(float*)&ScriptParams[5]; + float supY = *(float*)&ScriptParams[6]; + float supZ = *(float*)&ScriptParams[7]; + ScriptParams[0] = CGarages::AddOne(infX, infY, infZ, X2, Y2, supX, supY, supZ, ScriptParams[8], ScriptParams[9]); + 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; + 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; + } + 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 = Min(ScriptParams[1], CWorld::Players[ScriptParams[0]].m_nMaxHealth); + 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(); + } + 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; + 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; + 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; + 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]); + 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: + CollectParameters(&m_nIp, 3); + CGangs::SetGangPedModels(ScriptParams[0], ScriptParams[1], ScriptParams[2]); + return 0; + 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], 450.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: + //case COMMAND_RESTORE_WEATHER: + 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); + //assert(pVehicle->m_vehType == VEHICLE_TYPE_CAR); + // they DO call this for bikes, we don't really want to destroy the structure... +#ifdef FIX_BUGS + if (pVehicle->m_vehType == VEHICLE_TYPE_CAR) +#endif + ((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); + UpdateCompareFlag(pVehicle->IsTaxi()); + 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(); + CBaseModelInfo* pModelInfo = CModelInfo::GetModelInfo(mi); + if (pModelInfo->IsBuilding() && ((CSimpleModelInfo*)pModelInfo)->m_isBigBuilding) + pObj->SetupBigBuilding(); + 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, true)]; + *(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); + *(CVector*)&ScriptParams[0] = ThePaths.FindNodeCoorsForScript(ThePaths.FindNodeClosestToCoors(pos, 0, 999999.9f, true, true)); + 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(1, 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(1, 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->GetWeapon()->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->GetWeapon()->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, ZONE_DEFAULT); + if (nZone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetNavigationZone(nZone); + CollectParameters(&m_nIp, 3); + 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->m_nWaitState != WAITSTATE_FALSE) + continue; + if (!ThisIsAValidRandomPed(pPed->m_nPedType, ScriptParams[0], ScriptParams[1], ScriptParams[2])) + 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; + bool found; + CWorld::FindRoofZFor3DCoord(pos.x, pos.y, pos.z, &found); + if (found) + 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: + //case COMMAND_SET_CUTSCENE_HEAD_ANIM: + 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]; + if (CGame::germanGame) + CStats::TotalProgressInGame -= 2; + 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(); + CTheScripts::LastMissionPassedTime = CTimer::GetTimeInMilliseconds(); + CGameLogic::RemoveShortCutDropOffPointForMission(); + 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]); + bool result = false; + if (!pPed) + printf("HAS_CHAR_BEEN_DAMAGED_BY_WEAPON - Character doesn't exist\n"); + else { + if (ScriptParams[1] == WEAPONTYPE_ANYMELEE || ScriptParams[1] == WEAPONTYPE_ANYWEAPON) + result = CheckDamagedWeaponType(pPed->m_lastWepDam, ScriptParams[1]); + else + result = ScriptParams[1] == pPed->m_lastWepDam; + } + UpdateCompareFlag(result); + return 0; + } + case COMMAND_HAS_CAR_BEEN_DAMAGED_BY_WEAPON: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + bool result = false; + if (!pVehicle) + printf("HAS_CAR_BEEN_DAMAGED_BY_WEAPON - Vehicle doesn't exist\n"); + else { + if (ScriptParams[1] == WEAPONTYPE_ANYMELEE || ScriptParams[1] == WEAPONTYPE_ANYWEAPON) + result = CheckDamagedWeaponType(pVehicle->m_nLastWeaponDamage, ScriptParams[1]); + else + result = ScriptParams[1] == pVehicle->m_nLastWeaponDamage; + } + UpdateCompareFlag(result); + 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 && !pPed->bWaitForLeaderToComeCloser); + return 0; + } + default: + script_assert(0); + } + return -1; +} diff --git a/src/miami/control/Script4.cpp b/src/miami/control/Script4.cpp new file mode 100644 index 00000000..16a4ddae --- /dev/null +++ b/src/miami/control/Script4.cpp @@ -0,0 +1,2348 @@ +#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 "Bike.h" +#include "Wanted.h" + +#ifdef FIX_BUGS +static bool IsSlideObjectUsedWrongByScript(const CVector& posTarget, const CVector& slideBy) +{ + if (posTarget == CVector(-559.476f, 784.807f, 23.279f) && slideBy == CVector(0.0f, 10.0f, 0.0f)) + return true; // G-Spotlight bottom elevator, east side + if (posTarget == CVector(-559.476f, 779.64f, 23.279f) && slideBy == CVector(0.0f, 10.0f, 0.0f)) + return true; // G-Spotlight bottom elevator, west side + if (posTarget == CVector(-553.563f, 790.595f, 97.917f) && slideBy == CVector(0.0f, 10.0f, 0.0f)) + return true; // G-Spotlight top elevator, east side + if (posTarget == CVector(-553.563f, 785.427f, 97.917f) && slideBy == CVector(0.0f, 10.0f, 0.0f)) + return true; // G-Spotlight top elevator, west side + if (posTarget == CVector(-866.689f, -572.095f, 15.573f) && slideBy == CVector(0.0f, 0.0f, 4.5f)) + return true; // Cherry Popper garage door + return false; +} +#endif + +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 && !pPed->bWaitForLeaderToComeCloser); + return 0; + } + case COMMAND_EXPLODE_CHAR_HEAD: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->InflictDamage(nil, WEAPONTYPE_SNIPERRIFLE, 1000.0f, PEDPIECE_HEAD, 0); + return 0; + } + case COMMAND_EXPLODE_PLAYER_HEAD: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->InflictDamage(nil, WEAPONTYPE_SNIPERRIFLE, 1000.0f, PEDPIECE_HEAD, 0); + 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, ZONE_INFO); + if (zone_id < 0) { + printf("Couldn't find zone - %s\n", zone); + return 0; + } + while (zone_id >= 0) { + CTheZones::SetPedGroup(zone_id, ScriptParams[0], ScriptParams[1]); + zone_id = CTheZones::FindNextZoneByLabelAndReturnIndex(zone, ZONE_INFO); + } + 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 (pVehicle->GetVehicleAppearance() != VEHICLE_APPEARANCE_CAR && pVehicle->GetVehicleAppearance() != VEHICLE_APPEARANCE_BIKE) + continue; + if (!pVehicle->bUsesCollision) + 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, ZONE_DEFAULT); + if (zone_id != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetNavigationZone(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]; + CVector slideBy = *(CVector*)&ScriptParams[4]; +#ifdef FIX_BUGS + // the check is a hack for original script, where some objects are moved + // via SLIDE_OBJECT instead of SET_OBJECT_POSITION + // assuming the slide will take exactly one frame, which is true + // only without accounting time step (which is a bug) + if (!IsSlideObjectUsedWrongByScript(posTarget, slideBy)) + slideBy *= CTimer::GetTimeStepFix(); +#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 && pPed->m_pMyVehicle) + CTheScripts::RemoveThisPed(pPed); + else{ + pPed->CharCreatedBy = RANDOM_CHAR; + pPed->bRespondsToThreats = true; + pPed->bScriptObjectiveCompleted = false; + pPed->ClearLeader(); + --CPopulation::ms_nTotalMissionPeds; + pPed->bFadeOut = true; + CWorld::RemoveReferencesToDeletedObject(pPed); + } + } + 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]); + m_nIp += KEY_LENGTH_IN_SCRIPT; + pPed->Undress(name); + return 0; + } + case COMMAND_DRESS_CHAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->Dress(); + 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, ZONE_DEFAULT); + if (zone_id != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetNavigationZone(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, CWorld::Players[ScriptParams[0]].m_nMaxArmour); + 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; + pPed->m_pMyVehicle->SetMoveSpeed(0.0f, 0.0f, -0.00001f); + pPed->m_pMyVehicle->SetTurnSpeed(0.0f, 0.0f, 0.0f); + }else{ + pPed->m_pMyVehicle->RemovePassenger(pPed); + } + if (pPed->m_vehDoor) { + if (pPed->GetPedState() == PED_EXIT_CAR || pPed->GetPedState() == PED_DRAG_FROM_CAR) { + uint8 flags = 0; + if (pPed->m_pMyVehicle->IsBike()) { + if (pPed->m_vehDoor == CAR_DOOR_LF || + pPed->m_vehDoor == CAR_DOOR_RF || + pPed->m_vehDoor == CAR_WINDSCREEN) + flags = CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_RF; + else if (pPed->m_vehDoor == CAR_DOOR_LR || + pPed->m_vehDoor == CAR_DOOR_RR) + flags = CAR_DOOR_FLAG_LR | CAR_DOOR_FLAG_RR; + } + else { + switch (pPed->m_vehDoor) { + case CAR_DOOR_LF: + flags = pPed->m_pMyVehicle->m_nNumMaxPassengers != 0 ? CAR_DOOR_FLAG_LF : CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_LR; + break; + case CAR_DOOR_LR: + flags = pPed->m_pMyVehicle->m_nNumMaxPassengers != 0 ? CAR_DOOR_FLAG_LR : CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_LR; + break; + case CAR_DOOR_RF: + flags = CAR_DOOR_FLAG_RF; + break; + case CAR_DOOR_RR: + flags = CAR_DOOR_FLAG_RR; + break; + } + } + pPed->m_pMyVehicle->m_nGettingOutFlags &= ~flags; + pPed->m_pMyVehicle->ProcessOpenDoor(pPed->m_vehDoor, ANIM_STD_NUM, 0.0f); + } + } + } + pPed->RemoveInCarAnims(); + 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->ReplaceWeaponWhenExitingVehicle(); + 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, 1000.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::GetLevelFromPosition(&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; + ped->bIsPlayerFriend = 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); + if (m_bIsMissionScript) + ped->bIsStaticWaitingForCollision = true; + 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: +#ifdef FIX_BUGS + CTimer::Suspend(); +#else + CTimer::Stop(); +#endif + CStreaming::LoadAllRequestedModels(false); +#ifdef FIX_BUGS + CTimer::Resume(); +#else + CTimer::Update(); +#endif + return 0; + case COMMAND_ADD_TO_OBJECT_VELOCITY: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->AddToMoveSpeed(*(CVector*)&ScriptParams[1] * METERS_PER_SECOND_TO_GAME_SPEED); + 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: + { + CTheScripts::RemoveScriptTextureDictionary(); + 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); + if (pVehicle->IsBike()) { + CBike* pBike = (CBike*)pVehicle; + pBike->bWaterTight = ScriptParams[1] != 0; + } + else if (pVehicle->IsCar()) { + 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::GetLevelFromPosition(&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); + 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.AddClock(var, onscreen_str, ScriptParams[0] != 0); + 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, 0); + 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, MAXVEHICLESLOADED); + for (attempts = 0; attempts < MAXVEHICLESLOADED; attempts++) { + if (model != -1) + break; + model = CStreaming::ms_vehiclesLoaded[index]; + if (model == -1) + continue; + if (CModelInfo::IsCarModel(model) || CModelInfo::IsBikeModel(model)) { + switch (model) { + case MI_LANDSTAL: + case MI_LINERUN: + case MI_RIO: + case MI_FIRETRUCK: + case MI_TRASH: + case MI_STRETCH: + case MI_VOODOO: + case MI_MULE: + case MI_AMBULAN: + case MI_FBICAR: + case MI_MRWHOOP: + case MI_BFINJECT: + case MI_HUNTER: + case MI_POLICE: + case MI_ENFORCER: + case MI_SECURICA: + case MI_PREDATOR: + case MI_BUS: + case MI_RHINO: + case MI_BARRACKS: + case MI_CUBAN: + case MI_CHOPPER: + case MI_ANGEL: + case MI_COACH: + case MI_RCBANDIT: + case MI_ROMERO: + case MI_PACKER: + case MI_SENTXS: + case MI_SQUALO: + case MI_SEASPAR: + case MI_PIZZABOY: + case MI_GANGBUR: + case MI_AIRTRAIN: + case MI_DEADDODO: + case MI_SPEEDER: + case MI_REEFER: + case MI_TROPIC: + case MI_FLATBED: + case MI_YANKEE: + case MI_CADDY: + case MI_ZEBRA: + case MI_TOPFUN: + case MI_SKIMMER: + case MI_RCBARON: + case MI_RCRAIDER: + case MI_SPARROW: + case MI_PATRIOT: + case MI_LOVEFIST: + case MI_COASTG: + case MI_DINGHY: + case MI_HERMES: + case MI_SABRETUR: + case MI_PHEONIX: + case MI_WALTON: + case MI_COMET: + case MI_DELUXO: + case MI_BURRITO: + case MI_SPAND: + case MI_MARQUIS: + case MI_BAGGAGE: + case MI_KAUFMAN: + case MI_MAVERICK: + case MI_VCNMAV: + case MI_RANCHER: + case MI_FBIRANCH: + case MI_JETMAX: + case MI_HOTRING: + case MI_SANDKING: + case MI_BLISTAC: + case MI_POLMAV: + case MI_BOXVILLE: + case MI_BENSON: + case MI_MESA: + case MI_RCGOBLIN: + case MI_HOTRINA: + case MI_HOTRINB: + case MI_BLOODRA: + case MI_BLOODRB: + case MI_VICECHEE: + model = -1; + break; + case MI_IDAHO: + case MI_STINGER: + case MI_PEREN: + case MI_SENTINEL: + case MI_MANANA: + case MI_INFERNUS: + case MI_PONY: + case MI_CHEETAH: + case MI_MOONBEAM: + case MI_ESPERANT: + case MI_TAXI: + case MI_WASHING: + case MI_BOBCAT: + case MI_BANSHEE: + case MI_CABBIE: + case MI_STALLION: + case MI_RUMPO: + case MI_ADMIRAL: + case MI_PCJ600: + case MI_FAGGIO: + case MI_FREEWAY: + case MI_GLENDALE: + case MI_OCEANIC: + case MI_SANCHEZ: + case MI_SABRE: + case MI_REGINA: + case MI_VIRGO: + case MI_GREENWOO: + 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 CBike(model, RANDOM_VEHICLE); + ((CBike*)(car))->bIsStanding = true; + } + else + 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]; +#ifdef FIX_BUGS + CTimer::Suspend(); +#else + CTimer::Stop(); +#endif + CStreaming::LoadScene(pos); +#ifdef FIX_BUGS + CTimer::Suspend(); +#else + CTimer::Update(); +#endif + 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: + { + 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; + DMAudio.PreloadMissionAudio(ScriptParams[0] - 1, str); + return 0; + } + case COMMAND_HAS_MISSION_AUDIO_LOADED: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(DMAudio.GetMissionAudioLoadingStatus(ScriptParams[0] - 1) == 1); + return 0; + } + case COMMAND_PLAY_MISSION_AUDIO: + CollectParameters(&m_nIp, 1); + DMAudio.PlayLoadedMissionAudio(ScriptParams[0] - 1); + return 0; + case COMMAND_HAS_MISSION_AUDIO_FINISHED: + { + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(DMAudio.IsMissionAudioSampleFinished(ScriptParams[0] - 1)); + 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.FindNodeCoorsForScript(node); + *(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, 4); + CVector pos = *(CVector*)&ScriptParams[1]; + DMAudio.SetMissionAudioLocation(ScriptParams[0] - 1, pos.x, pos.y, pos.z); + return 0; + } + case COMMAND_ACTIVATE_SAVE_MENU: + { + CStats::SafeHouseVisits++; + FrontEndMenuManager.m_bActivateSaveMenu = true; + FindPlayerPed()->SetMoveSpeed(0.0f, 0.0f, 0.0f); + FindPlayerPed()->SetTurnSpeed(0.0f, 0.0f, 0.0f); + return 0; + } + case COMMAND_HAS_SAVE_GAME_FINISHED: + UpdateCompareFlag(!FrontEndMenuManager.m_bMenuActive && !FrontEndMenuManager.m_bActivateSaveMenu); + 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_SET_CHAR_OBJ_LEAVE_ANY_CAR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_LEAVE_CAR, pPed->m_pMyVehicle); + 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_2C") == 0 || + strcmp((char*)&CTheScripts::ScriptSpace[m_nIp], "GUN_2D") == 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/miami/control/Script5.cpp b/src/miami/control/Script5.cpp new file mode 100644 index 00000000..e9f0967e --- /dev/null +++ b/src/miami/control/Script5.cpp @@ -0,0 +1,2810 @@ +#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" +#include "SaveBuf.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->InVehicle() ? 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); + } +} + +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); + } +} + +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); + } +} + +void CRunningScript::LocateObjectCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float X, Y, Z, dX, dY, dZ; + switch (command) { + case COMMAND_LOCATE_OBJECT_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVector pos = pObject->GetPosition(); + 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 = 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); + } +} + +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); + } +} + +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->InVehicle() ? 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::ObjectInAreaCheckCommand(int32 command, uint32* pIp) +{ + bool b3D, result, debug; + float infX, infY, infZ, supX, supY, supZ; + switch (command) { + case COMMAND_IS_OBJECT_IN_AREA_3D: + b3D = true; + break; + default: + b3D = false; + break; + } + CollectParameters(pIp, b3D ? 8 : 6); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVector pos = pObject->GetPosition(); + 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 = 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_OBJECT_IN_AREA_2D: + case COMMAND_IS_OBJECT_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()) + return; +#ifdef MISSION_REPLAY + if (AllowMissionReplay != MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART && AllowMissionReplay != MISSION_RETRY_STAGE_NORMAL) + return; + if (AllowMissionReplay == MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART) + AllowMissionReplay = MISSION_RETRY_STAGE_NORMAL; + 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]; + CMessages::ClearSmallMessagesOnly(); + *(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 + +bool CRunningScript::CheckDamagedWeaponType(int32 actual, int32 type) +{ + if (actual == -1) + return false; + + if (type == WEAPONTYPE_ANYMELEE) { + if (actual <= WEAPONTYPE_CHAINSAW) + return true; + if (actual >= WEAPONTYPE_GRENADE && actual <= WEAPONTYPE_UNIDENTIFIED) + return false; + return false; + } + + if (type != WEAPONTYPE_ANYWEAPON) + return false; + + switch (actual) { + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_BRASSKNUCKLE: + case WEAPONTYPE_SCREWDRIVER: + case WEAPONTYPE_GOLFCLUB: + case WEAPONTYPE_NIGHTSTICK: + case WEAPONTYPE_KNIFE: + case WEAPONTYPE_BASEBALLBAT: + case WEAPONTYPE_HAMMER: + case WEAPONTYPE_CLEAVER: + case WEAPONTYPE_MACHETE: + case WEAPONTYPE_KATANA: + case WEAPONTYPE_CHAINSAW: + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_DETONATOR_GRENADE: + case WEAPONTYPE_TEARGAS: + case WEAPONTYPE_MOLOTOV: + case WEAPONTYPE_ROCKET: + case WEAPONTYPE_COLT45: + case WEAPONTYPE_PYTHON: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_SPAS12_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + case WEAPONTYPE_TEC9: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SILENCED_INGRAM: + case WEAPONTYPE_MP5: + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + case WEAPONTYPE_ROCKETLAUNCHER: + case WEAPONTYPE_FLAMETHROWER: + case WEAPONTYPE_M60: + case WEAPONTYPE_MINIGUN: + case WEAPONTYPE_DETONATOR: + case WEAPONTYPE_HELICANNON: + case WEAPONTYPE_CAMERA: + case WEAPONTYPE_EXPLOSION: + case WEAPONTYPE_UZI_DRIVEBY: + return true; + case WEAPONTYPE_HEALTH: + case WEAPONTYPE_ARMOUR: + case WEAPONTYPE_RAMMEDBYCAR: + case WEAPONTYPE_RUNOVERBYCAR: + case WEAPONTYPE_DROWNING: + case WEAPONTYPE_FALL: + case WEAPONTYPE_UNIDENTIFIED: + return false; + } + + return false; +} + +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) +\ + 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); + WriteSaveBuf(buf, LastMissionPassedTime); + 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, bPlayerHasMetDebbieHarry); + WriteSaveBuf(buf, (uint16)0); + WriteSaveBuf(buf, MainScriptSize); + WriteSaveBuf(buf, LargestMissionScriptSize); + WriteSaveBuf(buf, NumberOfMissionScripts); + WriteSaveBuf(buf, NumberOfExclusiveMissionScripts); + 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); + ReadSaveBuf(&LastMissionPassedTime, 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); + ReadSaveBuf(&bPlayerHasMetDebbieHarry, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&tmp, buf); + script_assert(tmp == MainScriptSize); + ReadSaveBuf(&tmp, buf); + script_assert(tmp == LargestMissionScriptSize); + uint16 tmp16; + ReadSaveBuf(&tmp16, buf); + script_assert(tmp16 == NumberOfMissionScripts); + ReadSaveBuf(&tmp16, buf); + script_assert(tmp16 == NumberOfExclusiveMissionScripts); + 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_bIsActive); + WriteSaveBuf(buf, m_bCondResult); + WriteSaveBuf(buf, m_bIsMissionScript); + WriteSaveBuf(buf, m_bSkipWakeTime); + 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_bIsActive, buf); + ReadSaveBuf(&m_bCondResult, buf); + ReadSaveBuf(&m_bIsMissionScript, buf); + ReadSaveBuf(&m_bSkipWakeTime, buf); + 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->InVehicle()) + return IsVehicleStopped(pPed->m_pMyVehicle); + return (pPed->m_nMoveState == PEDMOVE_NONE || pPed->m_nMoveState == PEDMOVE_STILL) && + !pPed->bIsInTheAir && !pPed->bIsLanding && pPed->bIsStanding && pPed->m_vecAnimMoveDelta.x == 0.0f && pPed->m_vecAnimMoveDelta.y == 0.0f; +} + +bool CTheScripts::IsPlayerStopped(CPlayerInfo* pPlayer) +{ + CPed* pPed = pPlayer->m_pPed; + if (pPed->InVehicle()) + 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) && + !pPed->bIsInTheAir && !pPed->bIsLanding && pPed->bIsStanding && pPed->m_vecAnimMoveDelta.x == 0.0f && pPed->m_vecAnimMoveDelta.y == 0.0f; +} + +bool CTheScripts::IsVehicleStopped(CVehicle* pVehicle) +{ + return 0.01f * CTimer::GetTimeStep() >= pVehicle->m_fDistanceTravelled; +} + +void CTheScripts::RemoveThisPed(CPed* pPed) +{ + if (pPed) { + bool bWasMissionPed = pPed->CharCreatedBy == MISSION_CHAR; + if (pPed->InVehicle() && pPed->m_pMyVehicle) { + if (pPed->m_pMyVehicle->pDriver == pPed) { + 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); + } + } + CWorld::RemoveReferencesToDeletedObject(pPed); + delete pPed; + if (bWasMissionPed) + --CPopulation::ms_nTotalMissionPeds; + } +} + +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->InVehicle()) { + 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->bKindaStayInSamePlace = 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() + 20000000; + 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); + NumberOfExclusiveMissionScripts = Read2BytesFromScript(&ip); + for (int i = 0; i < NumberOfMissionScripts; i++) { + MultiScriptArray[i] = Read4BytesFromScript(&ip); + } +} diff --git a/src/miami/control/Script6.cpp b/src/miami/control/Script6.cpp new file mode 100644 index 00000000..8af32f57 --- /dev/null +++ b/src/miami/control/Script6.cpp @@ -0,0 +1,1814 @@ +#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" +#include "GameLogic.h" +#include "Sprite.h" +#include "CarAI.h" +#include "Pickups.h" +#include "Fluff.h" + +#ifdef USE_DEBUG_SCRIPT_LOADER +extern const char* scriptfile; +#endif + +bool CRunningScript::ThisIsAValidRandomCop(uint32 mi, int cop, int swat, int fbi, int army, int miami) +{ + switch (mi) + { + case MI_COP: if (cop) return true; break; + case MI_SWAT: if (swat) return true; break; + case MI_FBI: if (fbi) return true; break; + case MI_ARMY: if (army) return true; break; + default: if (mi >= MI_VICE1 && mi <= MI_VICE8 && miami) return true; break; + } + return false; +} + +bool CRunningScript::ThisIsAValidRandomPed(uint32 pedtype, int civ, int gang, int criminal) +{ + switch (pedtype) { + case PEDTYPE_CIVMALE: + case PEDTYPE_CIVFEMALE: + return civ; + 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: + return gang; + case PEDTYPE_CRIMINAL: + case PEDTYPE_PROSTITUTE: + return criminal; + default: + return false; + } +} + +int8 CRunningScript::ProcessCommands1000To1099(int32 command) +{ + 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); + 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_SET_DRUNK_INPUT_DELAY: + { + CollectParameters(&m_nIp, 2); + assert(ScriptParams[1] < CPad::DRUNK_STEERING_BUFFER_SIZE); + CPad::GetPad(ScriptParams[0])->SetDrunkInputDelay(ScriptParams[1]); + return 0; + } + case COMMAND_SET_CHAR_MONEY: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_nPedMoney = ScriptParams[1]; + pPed->bMoneyHasBeenGivenByScript = true; + return 0; + } + //case COMMAND_INCREASE_CHAR_MONEY: + case COMMAND_GET_OFFSET_FROM_OBJECT_IN_WORLD_COORDS: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + CVector result = Multiply3x3(pObject->GetMatrix(), *(CVector*)&ScriptParams[1]) + pObject->GetPosition(); + *(CVector*)&ScriptParams[0] = result; + StoreParameters(&m_nIp, 3); + 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_GET_OFFSET_FROM_CAR_IN_WORLD_COORDS: + { + CollectParameters(&m_nIp, 4); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVector result = Multiply3x3(pVehicle->GetMatrix(), *(CVector*)&ScriptParams[1]) + pVehicle->GetPosition(); + *(CVector*)&ScriptParams[0] = result; + StoreParameters(&m_nIp, 3); + 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(true); + 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: + CollectParameters(&m_nIp, 1); + DMAudio.ClearMissionAudio(ScriptParams[0] - 1); + 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: + { +#ifdef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT + uint32 oldIp = m_nIp; +#endif + CollectParameters(&m_nIp, 1); + + if (CTheScripts::NumberOfExclusiveMissionScripts > 0 && ScriptParams[0] <= UINT16_MAX - 2) + return 0; +#ifdef MISSION_REPLAY + missionRetryScriptIndex = ScriptParams[0]; +#ifdef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT + if (!UsingMobileScript && CTheScripts::MissionSupportsMissionReplay(missionRetryScriptIndex)){ + if (!AlreadySavedGame) { + m_nIp = oldIp - 2; + SaveGameForPause(SAVE_TYPE_QUICKSAVE_FOR_SCRIPT); + AlreadySavedGame = true; + return 0; + } + else { + AlreadySavedGame = false; + } + } +#endif +#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; + CGameLogic::ClearShortCut(); + 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); + ScriptParams[0] = 0; + for (int i = 0; i < TOTAL_WEAPON_SLOTS; i++) { + if (pPed->GetWeapon(i).m_eWeaponType == (eWeaponType)ScriptParams[1]) + ScriptParams[0] = pPed->GetWeapon(i).m_nAmmoTotal; + } + 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 + ((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(CGame::germanGame ? ScriptParams[0] - 2 : 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_nSexFrequency = 1000; + 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]); + UpdateCompareFlag(pPed->GetPedState() == PED_DRIVING && pPed->m_objective != OBJECTIVE_LEAVE_CAR && 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 && pPed->m_objective != OBJECTIVE_LEAVE_CAR); + 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 || CVehicle::bHoverCheat || CVehicle::bCheat8 || CVehicle::bCheat9); + 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[1] == 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_objective != OBJECTIVE_LEAVE_CAR && 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 && pPed->m_objective != OBJECTIVE_LEAVE_CAR); + 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; + } + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands1100To1199(int32 command) +{ + char tmp[48]; + switch (command) { + /* + 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_CUTSCENE_FINALE); + printf("End preload end of game audio\n"); + return 0; + /* + case COMMAND_ENABLE_PLAYER_CONTROL_CAMERA: + CPad::GetPad(0)->SetEnablePlayerControls(PLAYERCONTROL_CAMERA); + return 0; + */ + 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; + bool bTargetting = pTarget && pTarget->IsPed() && pTarget == pTestedPed; + // PC shit + static int nCounter = 0; + nCounter = Max(0, nCounter - 1); + if (!pPed->GetWeapon()->IsTypeMelee() && !bTargetting) { + if ((pTestedPed->GetPosition() - TheCamera.GetPosition()).Magnitude() < 10.0f) { + CVector vTestedPos(pTestedPed->GetPosition().x, pTestedPed->GetPosition().y, pTestedPed->GetPosition().z + 0.4); + CVector vScreenPos; + float w, h; + if (CSprite::CalcScreenCoors(vTestedPos, &vScreenPos, &w, &h, false)) { + CVector2D vCrosshairPosition(CCamera::m_f3rdPersonCHairMultX * RsGlobal.maximumWidth, CCamera::m_f3rdPersonCHairMultY * RsGlobal.maximumHeight); + float fScreenDistance = ((CVector2D)vScreenPos - vCrosshairPosition).Magnitude(); + if (SCREEN_STRETCH_X(0.45f) > fScreenDistance / w) { + CColPoint point; + CEntity* entity; + if (!CWorld::ProcessLineOfSight(TheCamera.GetPosition() + 2.0f * TheCamera.GetForward(), + vTestedPos, point, entity, true, true, true, true, true, false) || + entity == pTestedPed) { + nCounter += 2; + if (nCounter > 20) { + bTargetting = true; + nCounter = 20; + } + } + } + } + } + } + UpdateCompareFlag(bTargetting); + 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; +#ifdef MISSION_REPLAY + MissionSkipLevel = 0; +#endif + return 0; + case COMMAND_GET_CLOSEST_OBJECT_OF_TYPE: + { + return 0; +/* + 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); + CCarAI::TellOccupantsToLeaveCar(pVehicle); + return 0; + } + case COMMAND_SET_INTERPOLATION_PARAMETERS: + CollectParameters(&m_nIp, 2); + TheCamera.SetParametersForScriptInterpolation(*(float*)&ScriptParams[0], 100.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: + { + CollectParameters(&m_nIp, 8); + CPed *pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CVehicle *pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + pPed->AttachPedToEntity(pVehicle, *(CVector*)&ScriptParams[2], ScriptParams[5], DEGTORAD(*(float*)&ScriptParams[6]), (eWeaponType)ScriptParams[7]); + return 0; + } + case COMMAND_DETACH_CHAR_FROM_CAR: + { + CollectParameters(&m_nIp, 1); + CPed *pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (pPed && pPed->m_attachedTo) + pPed->DettachPedFromEntity(); + 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]); + if (pPed) + pPed->m_lastWepDam = -1; + else + debug("CLEAR_CHAR_LAST_WEAPON_DAMAGE - Character doesn't exist\n"); + return 0; + } + case COMMAND_CLEAR_CAR_LAST_WEAPON_DAMAGE: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (pVehicle) + pVehicle->m_nLastWeaponDamage = -1; + else + debug("CLEAR_CAR_LAST_WEAPON_DAMAGE - Vehicle doesn't exist\n"); + return 0; + } + case COMMAND_GET_RANDOM_COP_IN_AREA: + { + CollectParameters(&m_nIp, 9); + 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 (!ThisIsAValidRandomCop(pPed->GetModelIndex(), ScriptParams[4], ScriptParams[5], ScriptParams[6], ScriptParams[7], ScriptParams[8])) + continue; + if (pPed->CharCreatedBy != RANDOM_CHAR) + continue; + if (!pPed->IsPedInControl() && pPed->GetPedState() != PED_DRIVING && pPed->GetPedState() != PED_ABSEIL) + 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 - COP_PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + COP_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, ZONE_DEFAULT); + if (nZone != -1) + m_nIp += KEY_LENGTH_IN_SCRIPT; + CZone* pZone = CTheZones::GetNavigationZone(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 - COP_PED_FIND_Z_OFFSET > pPed->GetPosition().z) + continue; + if (pos.z + COP_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->GetWeapon()->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->GetWeapon()->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_TEMP_ACTION: + { + CollectParameters(&m_nIp, 3); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_nTempAction = (uint8)ScriptParams[1]; + pVehicle->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + ScriptParams[2]; + 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->GetVehicleAppearance() == VEHICLE_APPEARANCE_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->GetVehicleAppearance() == VEHICLE_APPEARANCE_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); + CPed::nEnterCarRangeMultiplier = *(float*)&ScriptParams[0]; + return 0; + case COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER: + CollectParameters(&m_nIp, 1); + CPed::nThreatReactionRangeMultiplier = *(float*)&ScriptParams[0]; + return 0; + case COMMAND_SET_CHAR_CEASE_ATTACK_TIMER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->m_ceaseAttackTimer = ScriptParams[1]; + return 0; + } + case COMMAND_GET_REMOTE_CONTROLLED_CAR: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CWorld::Players[ScriptParams[0]].m_pRemoteVehicle; + if (pVehicle) + ScriptParams[0] = CPools::GetVehiclePool()->GetIndex(pVehicle); + else + ScriptParams[0] = -1; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_PC_VERSION: + UpdateCompareFlag(true); + return 0; + //case COMMAND_REPLAY: + //case COMMAND_IS_REPLAY_PLAYING: + case COMMAND_IS_MODEL_AVAILABLE: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CModelInfo::GetModelInfo(ScriptParams[0]) != nil); + return 0; + case COMMAND_SHUT_CHAR_UP: + CollectParameters(&m_nIp, 2); + DMAudio.SetPedTalkingStatus(CPools::GetPedPool()->GetAt(ScriptParams[0]), ScriptParams[1] == 0); + return 0; + case COMMAND_SET_ENABLE_RC_DETONATE: + CollectParameters(&m_nIp, 1); + CVehicle::bDisableRemoteDetonation = !ScriptParams[0]; + return 0; + case COMMAND_SET_CAR_RANDOM_ROUTE_SEED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->m_nRouteSeed = ScriptParams[1]; + return 0; + } + case COMMAND_IS_ANY_PICKUP_AT_COORDS: + { + CollectParameters(&m_nIp, 3); + CVector pos = *(CVector*)&ScriptParams[0]; + CRunningScript::UpdateCompareFlag(CPickups::TestForPickupsInBubble(pos, 0.5f)); + return 0; + } + case COMMAND_GET_FIRST_PICKUP_COORDS: + case COMMAND_GET_NEXT_PICKUP_COORDS: + case COMMAND_REMOVE_ALL_CHAR_WEAPONS: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->ClearWeapons(); + return 0; + } + case COMMAND_HAS_PLAYER_GOT_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + bool bFound = false; + for (int i = 0; i < TOTAL_WEAPON_SLOTS; i++) { + if (pPed->GetWeapon(i).m_eWeaponType == ScriptParams[1]) { + bFound = true; + break; + } + } + UpdateCompareFlag(bFound); + return 0; + } + //case COMMAND_HAS_CHAR_GOT_WEAPON: + //case COMMAND_IS_PLAYER_FACING_CHAR: + case COMMAND_SET_TANK_DETONATE_CARS: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle && pVehicle->m_vehType == VEHICLE_TYPE_CAR); + ((CAutomobile*)pVehicle)->bTankDetonateCars = ScriptParams[1]; + return 0; + } + case COMMAND_GET_POSITION_OF_ANALOGUE_STICKS: + { + CollectParameters(&m_nIp, 1); + CPad* pPad = CPad::GetPad(ScriptParams[0]); + ScriptParams[0] = pPad->NewState.LeftStickX; + ScriptParams[1] = pPad->NewState.LeftStickY; + ScriptParams[2] = pPad->NewState.RightStickX; + ScriptParams[3] = pPad->NewState.RightStickY; + StoreParameters(&m_nIp, 4); + return 0; + } + case COMMAND_IS_CAR_ON_FIRE: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + bool bOnFire = false; + if (pVehicle->m_pCarFire) + bOnFire = true; + if (pVehicle->m_vehType == VEHICLE_TYPE_CAR && ((CAutomobile*)pVehicle)->Damage.GetEngineStatus() >= ENGINE_STATUS_ON_FIRE) + bOnFire = true; + if (pVehicle->m_fHealth < 250.0f) + bOnFire = true; + UpdateCompareFlag(bOnFire); + return 0; + } + case COMMAND_IS_CAR_TYRE_BURST: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + bool bIsBurst = false; + CBike* pBike = (CBike*)pVehicle; + if (pVehicle->IsBike()) { + if (ScriptParams[1] == 4) { + for (int i = 0; i < 2; i++) { + if (pBike->m_wheelStatus[i] == WHEEL_STATUS_BURST) + bIsBurst = true; + } + } + else { + if (ScriptParams[1] == 2) + ScriptParams[1] = 0; + if (ScriptParams[1] == 3) + ScriptParams[1] = 1; + bIsBurst = pBike->m_wheelStatus[ScriptParams[1]] == WHEEL_STATUS_BURST; + } + } + else { + CAutomobile* pCar = (CAutomobile*)pVehicle; + if (ScriptParams[1] == 4) { + for (int i = 0; i < 4; i++) { + if (pCar->Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST) + bIsBurst = true; + } + } + else + bIsBurst = pCar->Damage.GetWheelStatus(ScriptParams[1] == WHEEL_STATUS_BURST); + } + UpdateCompareFlag(bIsBurst); + return 0; + } + //case COMMAND_SET_CAR_DRIVE_STRAIGHT_AHEAD: + //case COMMAND_SET_CAR_WAIT: + //case COMMAND_IS_PLAYER_STANDING_ON_A_VEHICLE: + //case COMMAND_IS_PLAYER_FOOT_DOWN: + //case COMMAND_IS_CHAR_FOOT_DOWN: + case COMMAND_INITIALISE_OBJECT_PATH: { + CollectParameters(&m_nIp, 2); + int32 counter = 0; + while (counter < 3 && CScriptPaths::aArray[counter].m_state != SCRIPT_PATH_DISABLED) { + counter++; + } + CScriptPaths::aArray[counter].InitialiseOne(ScriptParams[0], *(float*)&ScriptParams[1]); + ScriptParams[0] = counter; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_START_OBJECT_ON_PATH: + { + CollectParameters(&m_nIp, 2); + CObject *pObj = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + assert(pObj); + CScriptPaths::aArray[ScriptParams[1]].SetObjectToControl(pObj); + return 0; + } + case COMMAND_SET_OBJECT_PATH_SPEED: + { + CollectParameters(&m_nIp, 2); + CScriptPaths::aArray[ScriptParams[0]].m_fSpeed = *(float*)&ScriptParams[1]; + return 0; + } + case COMMAND_SET_OBJECT_PATH_POSITION: + { + CollectParameters(&m_nIp, 2); + CScriptPaths::aArray[ScriptParams[0]].m_fPosition = *(float*)&ScriptParams[1]; + return 0; + } + //case COMMAND_GET_OBJECT_DISTANCE_ALONG_PATH: + case COMMAND_CLEAR_OBJECT_PATH: + { + CollectParameters(&m_nIp, 1); + CScriptPaths::aArray[ScriptParams[0]].Clear(); + return 0; + } + case COMMAND_HELI_GOTO_COORDS: + { + CollectParameters(&m_nIp, 5); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle && pVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI); + ((CAutomobile*)pVehicle)->TellHeliToGoToCoors(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2], *(float*)&ScriptParams[3], ScriptParams[4]); + return 0; + } + case COMMAND_IS_INT_VAR_EQUAL_TO_CONSTANT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr == ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_LVAR_EQUAL_TO_CONSTANT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr == ScriptParams[0]); + return 0; + } + case COMMAND_GET_DEAD_CHAR_PICKUP_COORDS: + { + CollectParameters(&m_nIp, 1); + CPed *pTarget = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CVector pos; + pTarget->CreateDeadPedPickupCoors(&pos.x, &pos.y, &pos.z); + *(CVector*)&ScriptParams[0] = pos; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_CREATE_PROTECTION_PICKUP: + { + CollectParameters(&m_nIp, 5); + 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_PICKUP_REVENUE, PICKUP_ASSET_REVENUE, ScriptParams[3], ScriptParams[4]); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_CHAR_IN_ANY_BOAT: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_BOAT); + return 0; + } + case COMMAND_IS_PLAYER_IN_ANY_BOAT: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_BOAT); + return 0; + } + case COMMAND_IS_CHAR_IN_ANY_HELI: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI); + return 0; + } + case COMMAND_IS_PLAYER_IN_ANY_HELI: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI); + return 0; + } + case COMMAND_IS_CHAR_IN_ANY_PLANE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE); + return 0; + } + case COMMAND_IS_PLAYER_IN_ANY_PLANE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI); + return 0; + } + case COMMAND_IS_CHAR_IN_WATER: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pPed && pPed->bIsInWater); + return 0; + } + case COMMAND_SET_VAR_INT_TO_CONSTANT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + *ptr = ScriptParams[0]; + return 0; + } + case COMMAND_SET_LVAR_INT_TO_CONSTANT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + *ptr = ScriptParams[0]; + return 0; + } + default: + script_assert(0); + } + return -1; +} diff --git a/src/miami/control/Script7.cpp b/src/miami/control/Script7.cpp new file mode 100644 index 00000000..71099cc4 --- /dev/null +++ b/src/miami/control/Script7.cpp @@ -0,0 +1,1404 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "CarCtrl.h" +#include "ColStore.h" +#include "Coronas.h" +#include "CutsceneMgr.h" +#include "DMAudio.h" +#include "Explosion.h" +#include "GameLogic.h" +#include "General.h" +#include "Glass.h" +#include "Fluff.h" +#include "Hud.h" +#include "MBlur.h" +#include "Pad.h" +#include "Pickups.h" +#include "Pools.h" +#include "Population.h" +#include "Radar.h" +#include "RoadBlocks.h" +#include "Ropes.h" +#include "SetPieces.h" +#include "SpecialFX.h" +#include "Stats.h" +#include "Streaming.h" +#include "Timecycle.h" +#include "User.h" +#include "World.h" +#include "Zones.h" + +int8 CRunningScript::ProcessCommands1200To1299(int32 command) +{ + switch (command) { + case COMMAND_IS_INT_VAR_GREATER_THAN_CONSTANT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr > ScriptParams[0]); + return 0; + } + case COMMAND_IS_INT_LVAR_GREATER_THAN_CONSTANT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr > ScriptParams[0]); + return 0; + } + case COMMAND_IS_CONSTANT_GREATER_THAN_INT_VAR: + { + CollectParameters(&m_nIp, 1); + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_GLOBAL); + UpdateCompareFlag(ScriptParams[0] > *ptr); + return 0; + } + case COMMAND_IS_CONSTANT_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_OR_EQUAL_TO_CONSTANT: + { + 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_CONSTANT: + { + int32* ptr = GetPointerToScriptVariable(&m_nIp, VAR_LOCAL); + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(*ptr >= ScriptParams[0]); + return 0; + } + case COMMAND_IS_CONSTANT_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_CONSTANT_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_GET_CHAR_WEAPON_IN_SLOT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + ScriptParams[0] = pPed->GetWeapon(ScriptParams[1] - 1).m_eWeaponType; + ScriptParams[1] = pPed->GetWeapon(ScriptParams[1] - 1).m_nAmmoTotal; + ScriptParams[2] = CPickups::ModelForWeapon((eWeaponType)ScriptParams[0]); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_GET_CLOSEST_STRAIGHT_ROAD: + { + CollectParameters(&m_nIp, 5); + int node1, node2; + float angle; + ThePaths.FindNodePairClosestToCoors(*(CVector*)&ScriptParams[0], PATH_CAR, &node1, &node2, &angle, + *(float*)&ScriptParams[3], *(float*)&ScriptParams[4], true, true); + if (node1 == -1) { + for (int i = 0; i < 7; i++) + ScriptParams[i] = 0; + } + else { + *(CVector*)&ScriptParams[0] = ThePaths.FindNodeCoorsForScript(node1); + *(CVector*)&ScriptParams[3] = ThePaths.FindNodeCoorsForScript(node2); + *(float*)&ScriptParams[6] = angle; + } + StoreParameters(&m_nIp, 7); + return 0; + } + case COMMAND_SET_CAR_FORWARD_SPEED: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + float speed = *(float*)&ScriptParams[1] / GAME_SPEED_TO_CARAI_SPEED; + pVehicle->SetMoveSpeed(pVehicle->GetForward() * speed); + if (pVehicle->IsRealHeli() && pVehicle->IsCar()) + ((CAutomobile*)pVehicle)->m_aWheelSpeed[1] = 0.22f; + return 0; + } + case COMMAND_SET_AREA_VISIBLE: + CollectParameters(&m_nIp, 1); + CGame::currArea = ScriptParams[0]; + CStreaming::RemoveBuildingsNotInArea(ScriptParams[0]); + return 0; + case COMMAND_SET_CUTSCENE_ANIM_TO_LOOP: + { + char key[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CCutsceneMgr::SetCutsceneAnimToLoop(key); + return 0; + } + case COMMAND_MARK_CAR_AS_CONVOY_CAR: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bPartOfConvoy = ScriptParams[1]; + return 0; + } + case COMMAND_RESET_HAVOC_CAUSED_BY_PLAYER: + { + CollectParameters(&m_nIp, 1); + CWorld::Players[ScriptParams[0]].m_nHavocLevel = 0; + return 0; + } + case COMMAND_GET_HAVOC_CAUSED_BY_PLAYER: + { + CollectParameters(&m_nIp, 1); + ScriptParams[0] = CWorld::Players[ScriptParams[0]].m_nHavocLevel; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CREATE_SCRIPT_ROADBLOCK: + { + CollectParameters(&m_nIp, 6); + CRoadBlocks::RegisterScriptRoadBlock(*(CVector*)&ScriptParams[0], *(CVector*)&ScriptParams[3]); + return 0; + } + case COMMAND_CLEAR_ALL_SCRIPT_ROADBLOCKS: + { + CRoadBlocks::ClearScriptRoadBlocks(); + return 0; + } + case COMMAND_SET_CHAR_OBJ_WALK_TO_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTargetPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_GOTO_CHAR_ON_FOOT_WALKING, pTargetPed); + return 0; + } + //case COMMAND_IS_PICKUP_IN_ZONE: + case COMMAND_GET_OFFSET_FROM_CHAR_IN_WORLD_COORDS: + { + CollectParameters(&m_nIp, 4); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVector result = Multiply3x3(pPed->GetMatrix(), *(CVector*)&ScriptParams[1]) + pPed->GetPosition(); + *(CVector*)&ScriptParams[0] = result; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_HAS_CHAR_BEEN_PHOTOGRAPHED: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + bool result = false; + if (pPed->bHasBeenPhotographed) { + result = true; + pPed->bHasBeenPhotographed = false; + } + UpdateCompareFlag(result); + return 0; + } + case COMMAND_SET_CHAR_OBJ_AIM_GUN_AT_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CPed* pTargetPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + script_assert(pTargetPed); + pPed->bScriptObjectiveCompleted = false; + pPed->SetObjective(OBJECTIVE_AIM_GUN_AT, pTargetPed); + return 0; + } + case COMMAND_SWITCH_SECURITY_CAMERA: + { + CollectParameters(&m_nIp, 1); + CSpecialFX::bVideoCam = ScriptParams[0] != 0; + return 0; + } + //case COMMAND_IS_CHAR_IN_FLYING_VEHICLE: + case COMMAND_IS_PLAYER_IN_FLYING_VEHICLE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && (pPed->m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI || pPed->m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE)); + return 0; + } + //case COMMAND_HAS_SONY_CD_BEEN_READ: + //case COMMAND_GET_NUMBER_OF_SONY_CDS_READ: + //case COMMAND_ADD_SHORT_RANGE_BLIP_FOR_COORD_OLD: + //case COMMAND_ADD_SHORT_RANGE_BLIP_FOR_COORD: + case COMMAND_ADD_SHORT_RANGE_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::SetShortRangeCoordBlip(BLIP_COORD, pos, 5, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[3]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_ADD_MONEY_SPENT_ON_CLOTHES: + CollectParameters(&m_nIp, 1); + CStats::MoneySpentOnFashion(ScriptParams[0]); + return 0; + + case COMMAND_SET_HELI_ORIENTATION: + { + CollectParameters(&m_nIp, 2); + CAutomobile* pHeli = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pHeli && pHeli->IsCar() && pHeli->IsRealHeli()); + float fAngle = DEGTORAD(*(float*)&ScriptParams[1] - 90.0f); + while (fAngle < 0.0f) + fAngle += TWOPI; + while (fAngle > TWOPI) + fAngle -= TWOPI; + pHeli->SetHeliOrientation(fAngle); + return 0; + } + case COMMAND_CLEAR_HELI_ORIENTATION: + { + CollectParameters(&m_nIp, 1); + CAutomobile* pHeli = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pHeli && pHeli->IsCar() && pHeli->IsRealHeli()); + pHeli->ClearHeliOrientation(); + return 0; + } + case COMMAND_PLANE_GOTO_COORDS: + { + CollectParameters(&m_nIp, 5); + CAutomobile* pPlane = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pPlane && pPlane->IsCar() && pPlane->IsRealPlane()); + pPlane->TellPlaneToGoToCoors(*(float*)&ScriptParams[1], *(float*)&ScriptParams[2], *(float*)&ScriptParams[3], ScriptParams[4]); + return 0; + } + case COMMAND_GET_NTH_CLOSEST_CAR_NODE: + { + CollectParameters(&m_nIp, 4); + CVector pos = *(CVector*)&ScriptParams[0]; + if (pos.z <= MAP_Z_LOW_LIMIT) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + *(CVector*)&ScriptParams[0] = ThePaths.FindNodeCoorsForScript(ThePaths.FindNthNodeClosestToCoors(pos, 0, 999999.9f, true, true, ScriptParams[3] - 1)); + StoreParameters(&m_nIp, 3); + return 0; + } + //case COMMAND_GET_NTH_CLOSEST_CHAR_NODE: + case COMMAND_DRAW_WEAPONSHOP_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, false, 0.2f); + return 0; + } + case COMMAND_SET_ENABLE_RC_DETONATE_ON_CONTACT: + { + CollectParameters(&m_nIp, 1); + CVehicle::bDisableRemoteDetonationOnContact = (ScriptParams[0] == 0); + return 0; + } + case COMMAND_FREEZE_CHAR_POSITION: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bIsFrozen = ScriptParams[1]; + return 0; + } + case COMMAND_SET_CHAR_DROWNS_IN_WATER: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bDrownsInWater = ScriptParams[1]; + return 0; + } + case COMMAND_SET_OBJECT_RECORDS_COLLISIONS: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->bUseCollisionRecords = ScriptParams[1]; + return 0; + } + case COMMAND_HAS_OBJECT_COLLIDED_WITH_ANYTHING: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + UpdateCompareFlag(pObject->m_nCollisionRecords != 0); + return 0; + } + case COMMAND_REMOVE_RC_BUGGY: + { + CWorld::Players[CWorld::PlayerInFocus].BlowUpRCBuggy(false); + return 0; + } + //case COMMAND_HAS_PHOTOGRAPH_BEEN_TAKEN: + case COMMAND_GET_CHAR_ARMOUR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + ScriptParams[0] = pPed->m_fArmour; + StoreParameters(&m_nIp, 1); + return 0; + } + //case COMMAND_SET_CHAR_ARMOUR: + case COMMAND_SET_HELI_STABILISER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bHeliMinimumTilt = ScriptParams[1]; + return 0; + } + case COMMAND_SET_CAR_STRAIGHT_LINE_DISTANCE: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->AutoPilot.m_nSwitchDistance = ScriptParams[1]; + return 0; + } + case COMMAND_POP_CAR_BOOT: + { + CollectParameters(&m_nIp, 1); + CAutomobile* pCar = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pCar&& pCar->IsCar()); + pCar->PopBoot(); + return 0; + } + case COMMAND_SHUT_PLAYER_UP: + { + CollectParameters(&m_nIp, 2); + DMAudio.ShutUpPlayerTalking(!!ScriptParams[1]); + return 0; + } + case COMMAND_SET_PLAYER_MOOD: + { + CollectParameters(&m_nIp, 3); + DMAudio.SetPlayersMood(ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_REQUEST_COLLISION: + { + CollectParameters(&m_nIp, 2); + CVector2D pos; + pos.x = *(float*)&ScriptParams[0]; + pos.y = *(float*)&ScriptParams[1]; + CColStore::RequestCollision(pos); + return 0; + } + case COMMAND_LOCATE_OBJECT_2D: + case COMMAND_LOCATE_OBJECT_3D: + LocateObjectCommand(command, &m_nIp); + return 0; + case COMMAND_IS_OBJECT_IN_WATER: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + UpdateCompareFlag(pObject->bIsInWater); + return 0; + } + //case COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR_EVEN_MISSION_CAR: + case COMMAND_IS_OBJECT_IN_AREA_2D: + case COMMAND_IS_OBJECT_IN_AREA_3D: + ObjectInAreaCheckCommand(command, &m_nIp); + return 0; + case COMMAND_SET_CHAR_CROUCH: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) { + pPed->bCrouchWhenShooting = true; + pPed->SetDuck(ScriptParams[2], true); + } + else { + pPed->ClearDuck(true); + pPed->bCrouchWhenShooting = false; + } + return 0; + } + case COMMAND_SET_ZONE_CIVILIAN_CAR_INFO: + { + char label[12]; + int16 carDensities[CCarCtrl::NUM_CAR_CLASSES] = { 0 }; + int16 boatDensities[CCarCtrl::NUM_BOAT_CLASSES] = { 0 }; + int i; + + CTheScripts::ReadTextLabelFromScript(&m_nIp, label); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CollectParameters(&m_nIp, 12); + for (i = 0; i < CCarCtrl::NUM_CAR_CLASSES; i++) + carDensities[i] = ScriptParams[i + 1]; + for (i = 0; i < CCarCtrl::NUM_BOAT_CLASSES; i++) + boatDensities[i] = ScriptParams[i + 1 + CCarCtrl::NUM_CAR_CLASSES]; + int zone = CTheZones::FindZoneByLabelAndReturnIndex(label, ZONE_INFO); + if (zone < 0) { + debug("Couldn't find zone - %s\n", label); + return 0; + } + while (zone >= 0) { + CTheZones::SetZoneCivilianCarInfo(zone, ScriptParams[0], carDensities, boatDensities); + zone = CTheZones::FindNextZoneByLabelAndReturnIndex(label, ZONE_INFO); + } + return 0; + } + case COMMAND_REQUEST_ANIMATION: + { + char key[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CStreaming::RequestAnim(CAnimManager::GetAnimationBlockIndex(key), STREAMFLAGS_SCRIPTOWNED); + return 0; + } + case COMMAND_HAS_ANIMATION_LOADED: + { + char key[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + UpdateCompareFlag(CAnimManager::GetAnimationBlock(key)->isLoaded); + return 0; + } + case COMMAND_REMOVE_ANIMATION: + { + char key[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CStreaming::RemoveAnim(CAnimManager::GetAnimationBlockIndex(key)); + return 0; + } + case COMMAND_IS_CHAR_WAITING_FOR_WORLD_COLLISION: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bIsStaticWaitingForCollision); + return 0; + } + case COMMAND_IS_CAR_WAITING_FOR_WORLD_COLLISION: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + UpdateCompareFlag(pVehicle->bIsStaticWaitingForCollision); + return 0; + } + case COMMAND_IS_OBJECT_WAITING_FOR_WORLD_COLLISION: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + UpdateCompareFlag(pObject->bIsStaticWaitingForCollision); + return 0; + } + case COMMAND_SET_CHAR_SHUFFLE_INTO_DRIVERS_SEAT: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + pPed->PedShuffle(); + return 0; + } + case COMMAND_ATTACH_CHAR_TO_OBJECT: + { + CollectParameters(&m_nIp, 8); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + pPed->AttachPedToEntity(pObject, *(CVector*)&ScriptParams[2], ScriptParams[5], DEGTORAD(ScriptParams[6]), (eWeaponType)ScriptParams[7]); + return 0; + } + case COMMAND_SET_CHAR_AS_PLAYER_FRIEND: + { + CollectParameters(&m_nIp, 3); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bIsPlayerFriend = ScriptParams[2]; + return 0; + } + //case COMMAND_DISPLAY_NTH_ONSCREEN_COUNTER: + case COMMAND_DISPLAY_NTH_ONSCREEN_COUNTER_WITH_STRING: + { + char onscreen_str[12]; + script_assert(CTheScripts::ScriptSpace[m_nIp++] == ARGUMENT_GLOBALVAR); + uint16 var = CTheScripts::Read2BytesFromScript(&m_nIp); + CollectParameters(&m_nIp, 2); + 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, ScriptParams[1] - 1); + return 0; + } + case COMMAND_ADD_SET_PIECE: + { + CollectParameters(&m_nIp, 13); + CSetPieces::AddOne(ScriptParams[0], + *(CVector2D*)&ScriptParams[1], *(CVector2D*)&ScriptParams[3], + *(CVector2D*)&ScriptParams[5], *(CVector2D*)&ScriptParams[7], + *(CVector2D*)&ScriptParams[9], *(CVector2D*)&ScriptParams[11]); + return 0; + } + case COMMAND_SET_EXTRA_COLOURS: + { + CollectParameters(&m_nIp, 2); + CTimeCycle::StartExtraColour(ScriptParams[0]-1, ScriptParams[1] != 0); + return 0; + } + case COMMAND_CLEAR_EXTRA_COLOURS: + { + CollectParameters(&m_nIp, 1); + CTimeCycle::StopExtraColour(ScriptParams[0]); + return 0; + } + //case COMMAND_CLOSE_CAR_BOOT: + case COMMAND_GET_WHEELIE_STATS: + { + CollectParameters(&m_nIp, 1); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + ScriptParams[0] = pPlayerInfo->m_nLastTimeCarSpentOnTwoWheels; + *(float*)&ScriptParams[1] = pPlayerInfo->m_nLastDistanceCarTravelledOnTwoWheels; + ScriptParams[2] = pPlayerInfo->m_nLastTimeSpentOnWheelie; + *(float*)&ScriptParams[3] = pPlayerInfo->m_nLastDistanceTravelledOnWheelie; + ScriptParams[4] = pPlayerInfo->m_nLastTimeSpentOnStoppie; + *(float*)&ScriptParams[5] = pPlayerInfo->m_nLastDistanceTravelledOnStoppie; + StoreParameters(&m_nIp, 6); + pPlayerInfo->m_nLastTimeCarSpentOnTwoWheels = 0; + pPlayerInfo->m_nLastDistanceCarTravelledOnTwoWheels = 0.0f; + pPlayerInfo->m_nLastTimeSpentOnWheelie = 0; + pPlayerInfo->m_nLastDistanceTravelledOnWheelie = 0.0f; + pPlayerInfo->m_nLastTimeSpentOnStoppie = 0; + pPlayerInfo->m_nLastDistanceTravelledOnStoppie = 0.0f; + return 0; + } + //case COMMAND_DISARM_CHAR: + case COMMAND_BURST_CAR_TYRE: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (pVehicle->IsBike()) { + if (ScriptParams[1] == 2) + ScriptParams[1] = 0; + else if (ScriptParams[1] == 3) + ScriptParams[1] = 1; + pVehicle->BurstTyre(ScriptParams[1], true); + } + else { + pVehicle->BurstTyre(ScriptParams[1], true); + } + return 0; + } + case COMMAND_IS_CHAR_OBJ_NO_OBJ: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->m_prevObjective == OBJECTIVE_NONE && pPed->m_objective == OBJECTIVE_NONE); + return 0; + } + case COMMAND_IS_PLAYER_WEARING: + { + CollectParameters(&m_nIp, 1); + char key[12]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + for (int i = 0; i < KEY_LENGTH_IN_SCRIPT; i++) + key[i] = tolower(key[i]); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(strcmp(key, CModelInfo::GetModelInfo(pPed->GetModelIndex())->GetModelName()) == 0); + return 0; + } + case COMMAND_SET_PLAYER_CAN_DO_DRIVE_BY: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + pPlayerInfo->m_bDriveByAllowed = ScriptParams[1]; + return 0; + } + case COMMAND_SET_CHAR_OBJ_SPRINT_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_SPRINT_TO_AREA, pos); + return 0; + } + case COMMAND_CREATE_SWAT_ROPE: + { + CollectParameters(&m_nIp, 3); + CRopes::CreateRopeWithSwatComingDown(*(CVector*)&ScriptParams[0]); + return 0; + } + //case COMMAND_SET_FIRST_PERSON_CONTROL_CAMERA: + //case COMMAND_GET_NEAREST_TYRE_TO_POINT: + case COMMAND_SET_CAR_MODEL_COMPONENTS: + { + CollectParameters(&m_nIp, 3); + CVehicleModelInfo::SetComponentsToUse(ScriptParams[1], ScriptParams[2]); + return 0; + } + case COMMAND_SWITCH_LIFT_CAMERA: + { + CollectParameters(&m_nIp, 1); + CSpecialFX::bLiftCam = ScriptParams[0] != 0; + return 0; + } + case COMMAND_CLOSE_ALL_CAR_DOORS: + { + CollectParameters(&m_nIp, 1); + CAutomobile* pCar = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pCar&& pCar->IsCar()); + pCar->CloseAllDoors(); + return 0; + } + case COMMAND_GET_DISTANCE_BETWEEN_COORDS_2D: + { + CollectParameters(&m_nIp, 4); + *(float*)&ScriptParams[0] = (*(CVector2D*)&ScriptParams[0] - *(CVector2D*)&ScriptParams[2]).Magnitude(); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_DISTANCE_BETWEEN_COORDS_3D: + { + CollectParameters(&m_nIp, 6); + *(float*)&ScriptParams[0] = (*(CVector*)&ScriptParams[0] - *(CVector*)&ScriptParams[3]).Magnitude(); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_POP_CAR_BOOT_USING_PHYSICS: + { + CollectParameters(&m_nIp, 1); + CAutomobile* pCar = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pCar && pCar->IsCar()); + pCar->PopBootUsingPhysics(); + return 0; + } + //case COMMAND_SET_FIRST_PERSON_WEAPON_CAMERA: + case COMMAND_IS_CHAR_LEAVING_VEHICLE_TO_DIE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE); + return 0; + } + case COMMAND_SORT_OUT_OBJECT_COLLISION_WITH_CAR: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->m_pCollidingEntity = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + return 0; + } + //case COMMAND_GET_MAX_WANTED_LEVEL: + case COMMAND_IS_CHAR_WANDER_PATH_CLEAR: + { + CollectParameters(&m_nIp, 5); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(CWorld::IsWanderPathClear(pPed->GetPosition(), *(CVector*)&ScriptParams[0], *(float*)&ScriptParams[3], 4)); + return 0; + } + //case COMMAND_PRINT_HELP_WITH_NUMBER: + case COMMAND_PRINT_HELP_FOREVER: + { + wchar* text = CTheScripts::GetTextByKeyFromScript(&m_nIp); + CHud::SetHelpMessage(text, false, true); + return 0; + } + //case COMMAND_PRINT_HELP_FOREVER_WITH_NUMBER: + default: + script_assert(0); + } + return -1; +} + +int8 CRunningScript::ProcessCommands1300To1399(int32 command) +{ + switch (command) { + case COMMAND_SET_CHAR_CAN_BE_DAMAGED_BY_MEMBERS_OF_GANG: + { + CollectParameters(&m_nIp, 3); + CPed* pTarget = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pTarget); + uint8 flag = 1 << (uint8)ScriptParams[1]; + if (ScriptParams[2]) + pTarget->m_gangFlags |= flag; + else + pTarget->m_gangFlags &= ~flag; + + return 0; + } + case COMMAND_LOAD_AND_LAUNCH_MISSION_EXCLUSIVE: + return 0; + //case COMMAND_IS_MISSION_AUDIO_PLAYING: + case COMMAND_CREATE_LOCKED_PROPERTY_PICKUP: + { + 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; + char key[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + // TheText.Get(key); + CPickups::GetActualPickupIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CPickups::GenerateNewOne(pos, MI_PICKUP_PROPERTY, PICKUP_PROPERTY_LOCKED, 0, 0, false, key); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_CREATE_FORSALE_PROPERTY_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; + char key[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + // TheText.Get(key); + CPickups::GetActualPickupIndex(CollectNextParameterWithoutIncreasingPC(m_nIp)); + ScriptParams[0] = CPickups::GenerateNewOne(pos, MI_PICKUP_PROPERTY_FORSALE, PICKUP_PROPERTY_FORSALE, ScriptParams[3], 0, false, key); + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_FREEZE_CAR_POSITION: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bIsFrozen = ScriptParams[1]; + pVehicle->bInfiniteMass = ScriptParams[1]; + return 0; + } + case COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_CHAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + CPed* pTestedPed = CPools::GetPedPool()->GetAt(ScriptParams[1]); + bool result = false; + if (pPed) { + if (pPed->m_lastDamEntity) { + if (pPed->m_lastDamEntity == pTestedPed) + result = true; + if (pTestedPed->bInVehicle && pPed->m_lastDamEntity == pTestedPed->m_pMyVehicle) + result = true; + } + }else + debug("HAS_CHAR_BEEN_DAMAGED_BY_CHAR - First character doesn't exist\n"); + UpdateCompareFlag(result); + return 0; + } + //case COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_CAR: + //case COMMAND_HAS_CAR_BEEN_DAMAGED_BY_CHAR: + //case COMMAND_HAS_CAR_BEEN_DAMAGED_BY_CAR: + //case COMMAND_GET_RADIO_CHANNEL: + //case COMMAND_DISPLAY_TEXT_WITH_3_NUMBERS: + //case COMMAND_IS_CAR_DROWNING_IN_WATER: + case COMMAND_IS_CHAR_DROWNING_IN_WATER: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pPed && pPed->bIsDrowning); + return 0; + } + case COMMAND_DISABLE_CUTSCENE_SHADOWS: + { + CCutsceneMgr::DisableCutsceneShadows(); + return 0; + } + case COMMAND_HAS_GLASS_BEEN_SHATTERED_NEARBY: + { + CollectParameters(&m_nIp, 3); + + bool shattered = false; + if ( CGlass::HasGlassBeenShatteredAtCoors(*(float*)&ScriptParams[0], *(float*)&ScriptParams[1], *(float*)&ScriptParams[2]) ) + shattered = true; + + UpdateCompareFlag(shattered); + return 0; + } + case COMMAND_ATTACH_CUTSCENE_OBJECT_TO_BONE: + { + CollectParameters(&m_nIp, 3); + CCutsceneMgr::AttachObjectToBone(CPools::GetObjectPool()->GetAt(ScriptParams[0]), CPools::GetObjectPool()->GetAt(ScriptParams[1]), ScriptParams[2]); + return 0; + } + case COMMAND_ATTACH_CUTSCENE_OBJECT_TO_COMPONENT: + { + CollectParameters(&m_nIp, 2); + CObject *obj1 = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + CObject *obj2 = CPools::GetObjectPool()->GetAt(ScriptParams[1]); + + char key[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + + CCutsceneMgr::AttachObjectToFrame(obj1, obj2, key); + return 0; + } + case COMMAND_SET_CHAR_STAY_IN_CAR_WHEN_JACKED: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bStayInCarOnJack = ScriptParams[1]; + return 0; + } + //case COMMAND_IS_MISSION_AUDIO_LOADING: + case COMMAND_ADD_MONEY_SPENT_ON_WEAPONS: + CollectParameters(&m_nIp, 1); + CStats::MoneySpentOnWeapons(ScriptParams[0]); + return 0; + case COMMAND_ADD_MONEY_SPENT_ON_PROPERTY: + CollectParameters(&m_nIp, 1); + CStats::MoneySpentOnProperty(ScriptParams[0]); + return 0; + //case COMMAND_ADD_MONEY_SPENT_ON_AUTO_PAINTING: + case COMMAND_SET_CHAR_ANSWERING_MOBILE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (ScriptParams[1]) + pPed->SetAnswerMobile(); + else + pPed->ClearAnswerMobile(); + return 0; + } + case COMMAND_SET_PLAYER_DRUNKENNESS: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + pPlayerInfo->m_pPed->m_nDrunkenness = ScriptParams[1]; + pPlayerInfo->m_pPed->m_nFadeDrunkenness = 0; + if (pPlayerInfo->m_pPed->m_nDrunkenness == 0) + CMBlur::ClearDrunkBlur(); + return 0; + } + //case COMMAND_GET_PLAYER_DRUNKENNESS: + //case COMMAND_SET_PLAYER_DRUG_LEVEL: + //case COMMAND_GET_PLAYER_DRUG_LEVEL: + //case COMMAND_ADD_LOAN_SHARK_VISITS: + case COMMAND_ADD_STORES_KNOCKED_OFF: + CollectParameters(&m_nIp, 1); + CStats::NumOfStoresKnockedOff(ScriptParams[0]); + return 0; + //case COMMAND_ADD_MOVIE_STUNTS: + case COMMAND_ADD_NUMBER_OF_ASSASSINATIONS: + CollectParameters(&m_nIp, 1); + CStats::NumOfAssassinations(ScriptParams[0]); + return 0; + case COMMAND_ADD_PIZZAS_DELIVERED: + CollectParameters(&m_nIp, 1); + CStats::NumOfPizzasDelivered(ScriptParams[0]); + return 0; + //case COMMAND_ADD_GARBAGE_PICKUPS: + case COMMAND_ADD_ICE_CREAMS_SOLD: + CollectParameters(&m_nIp, 1); + CStats::NumOfIceCreamSold(ScriptParams[0]); + return 0; + //case COMMAND_SET_TOP_SHOOTING_RANGE_SCORE: + //case COMMAND_ADD_SHOOTING_RANGE_RANK: + //case COMMAND_ADD_MONEY_SPENT_ON_GAMBLING: + //case COMMAND_ADD_MONEY_WON_ON_GAMBLING: + //case COMMAND_SET_LARGEST_GAMBLING_WIN: + case COMMAND_SET_CHAR_IN_PLAYERS_GROUP_CAN_FIGHT: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bDontFight = !ScriptParams[1]; + return 0; + } + case COMMAND_CLEAR_CHAR_WAIT_STATE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->ClearWaitState(); + return 0; + } + case COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA_NO_SAVE: + { + 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 (pVehicle->GetVehicleAppearance() != VEHICLE_APPEARANCE_CAR && pVehicle->GetVehicleAppearance() != VEHICLE_APPEARANCE_BIKE) + 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); + } + ScriptParams[0] = handle; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CAN_BURST_CAR_TYRES: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + pVehicle->bTyresDontBurst = !ScriptParams[1]; + return 0; + } + case COMMAND_SET_PLAYER_AUTO_AIM: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + pPed->bDoomAim = ScriptParams[1]; + return 0; + } + case COMMAND_FIRE_HUNTER_GUN: + { + CollectParameters(&m_nIp, 1); + CVehicle *pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (CTimer::GetTimeInMilliseconds() > pVehicle->m_nGunFiringTime + 150) { + CWeapon gun(WEAPONTYPE_HELICANNON, 5000); + CVector worldGunPos = (pVehicle->GetMatrix() * vecHunterGunPos) + (CTimer::GetTimeStep() * pVehicle->m_vecMoveSpeed); + gun.FireInstantHit(pVehicle, &worldGunPos); + gun.AddGunshell(pVehicle, worldGunPos, CVector2D(0.f, 0.1f), 0.025f); + DMAudio.PlayOneShot(pVehicle->m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.f); + pVehicle->m_nGunFiringTime = CTimer::GetTimeInMilliseconds(); + } + return 0; + } + case COMMAND_SET_PROPERTY_AS_OWNED: + CollectParameters(&m_nIp, 1); + CStats::AddPropertyAsOwned(ScriptParams[0]); + return 0; + case COMMAND_ADD_BLOOD_RING_KILLS: + CollectParameters(&m_nIp, 1); + CStats::AddNumBloodRingKills(ScriptParams[0]); + return 0; + case COMMAND_SET_LONGEST_TIME_IN_BLOOD_RING: + CollectParameters(&m_nIp, 1); + CStats::LongestTimeInBloodRing(ScriptParams[0]); + return 0; + case COMMAND_REMOVE_EVERYTHING_FOR_HUGE_CUTSCENE: + { + CCutsceneMgr::RemoveEverythingFromTheWorldForTheBiggestFuckoffCutsceneEver(); + return 0; + } + case COMMAND_IS_PLAYER_TOUCHING_VEHICLE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + CPhysical* pTestedEntity = pPed; + if (pPed->bInVehicle && pPed->m_pMyVehicle) + pTestedEntity = pPed->m_pMyVehicle; + UpdateCompareFlag(pTestedEntity->GetHasCollidedWith(pVehicle)); + return 0; + } + //case COMMAND_IS_CHAR_TOUCHING_VEHICLE: + case COMMAND_CHECK_FOR_PED_MODEL_AROUND_PLAYER: + { + CollectParameters(&m_nIp, 6); + CVector d1 = CWorld::Players[ScriptParams[0]].GetPos() - *(CVector*)&ScriptParams[1]; + CVector d2 = CWorld::Players[ScriptParams[0]].GetPos() + *(CVector*)&ScriptParams[1]; + int i = CPools::GetPedPool()->GetSize(); + bool result = false; + while (i--) { + CPed* pPed = CPools::GetPedPool()->GetSlot(i); + if (!pPed) + continue; + if (ScriptParams[4] != pPed->GetModelIndex() && ScriptParams[5] != pPed->GetModelIndex()) + continue; + if (pPed->IsWithinArea(d1.x, d1.y, d1.z, d2.x, d2.y, d2.z)) + result = true; + } + UpdateCompareFlag(result); + return 0; + } + case COMMAND_CLEAR_CHAR_FOLLOW_PATH: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (pPed->GetPedState() == PED_FOLLOW_PATH) { + pPed->RestorePreviousState(); + pPed->ClearFollowPath(); + } + return 0; + } + case COMMAND_SET_CHAR_CAN_BE_SHOT_IN_VEHICLE: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bCanBeShotInVehicle = ScriptParams[1]; + return 0; + } + case COMMAND_ATTACH_CUTSCENE_OBJECT_TO_VEHICLE: + { + CollectParameters(&m_nIp, 2); + CCutsceneMgr::AttachObjectToParent(CPools::GetObjectPool()->GetAt(ScriptParams[0]), CPools::GetVehiclePool()->GetAt(ScriptParams[1])); + return 0; + } + case COMMAND_LOAD_MISSION_TEXT: + { + char key[8]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + TheText.LoadMissionText(key); + return 0; + } + case COMMAND_SET_TONIGHTS_EVENT: + { + CollectParameters(&m_nIp, 1); + CScrollBar::TonightsEvent = ScriptParams[0]; + return 0; + } + case COMMAND_CLEAR_CHAR_LAST_DAMAGE_ENTITY: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + if (pPed) + pPed->m_lastDamEntity = nil; + else + debug("CLEAR_CHAR_LAST_DAMAGE_ENTITY - Character doesn't exist\n"); + return 0; + } + //case COMMAND_CLEAR_CAR_LAST_DAMAGE_ENTITY: + case COMMAND_FREEZE_OBJECT_POSITION: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->bIsFrozen = ScriptParams[1]; + pObject->bInfiniteMass = ScriptParams[1]; + return 0; + } + case COMMAND_SET_PLAYER_HAS_MET_DEBBIE_HARRY: + { + CollectParameters(&m_nIp, 1); + CTheScripts::bPlayerHasMetDebbieHarry = ScriptParams[0]; + return 0; + } + case COMMAND_SET_RIOT_INTENSITY: + { + CollectParameters(&m_nIp, 1); + CTheScripts::RiotIntensity = ScriptParams[0]; + return 0; + } + //case COMMAND_IS_CAR_IN_ANGLED_AREA_2D: + //case COMMAND_IS_CAR_IN_ANGLED_AREA_3D: + //case COMMAND_REMOVE_WEAPON_FROM_CHAR: + case COMMAND_SET_UP_TAXI_SHORTCUT: + { + CollectParameters(&m_nIp, 8); + CGameLogic::SetUpShortCut( + *(CVector*)&ScriptParams[0], *(float*)&ScriptParams[3], + *(CVector*)&ScriptParams[4], *(float*)&ScriptParams[7]); + return 0; + } + case COMMAND_CLEAR_TAXI_SHORTCUT: + CGameLogic::ClearShortCut(); + return 0; + //case COMMAND_SET_CHAR_OBJ_GOTO_CAR_ON_FOOT: + //case COMMAND_GET_CLOSEST_WATER_NODE: + case COMMAND_ADD_PORN_LEAFLET_TO_RUBBISH: + CollectParameters(&m_nIp, 1); + CStats::PamphletMissionPassed = ScriptParams[0]; + return 0; + case COMMAND_CREATE_CLOTHES_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_PICKUP_CLOTHES, PICKUP_ON_STREET, ScriptParams[3]); + StoreParameters(&m_nIp, 1); + return 0; + } + //case COMMAND_CHANGE_BLIP_THRESHOLD: + case COMMAND_MAKE_PLAYER_FIRE_PROOF: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + pPlayerInfo->m_bFireproof = ScriptParams[1]; + return 0; + } + case COMMAND_INCREASE_PLAYER_MAX_HEALTH: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + pPlayerInfo->m_nMaxHealth += ScriptParams[1]; + pPlayerInfo->m_pPed->m_fHealth = pPlayerInfo->m_nMaxHealth; + return 0; + } + case COMMAND_INCREASE_PLAYER_MAX_ARMOUR: + { + CollectParameters(&m_nIp, 2); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + pPlayerInfo->m_nMaxArmour += ScriptParams[1]; + pPlayerInfo->m_pPed->m_fArmour = pPlayerInfo->m_nMaxArmour; + return 0; + } + case COMMAND_CREATE_RANDOM_CHAR_AS_DRIVER: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + CPed* pPed = CPopulation::AddPedInCar(pVehicle, true); + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + pPed->bAllowMedicsToReviveMe = false; + pPed->bIsPlayerFriend = false; + if (pVehicle->bIsBus) + pPed->bRenderPedInCar = false; + pPed->SetPosition(pVehicle->GetPosition()); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + pPed->SetPedState(PED_DRIVING); + pPed->m_pMyVehicle = pVehicle; + pPed->m_pMyVehicle->RegisterReference((CEntity**)&pPed->m_pMyVehicle); + pVehicle->pDriver = pPed; + pVehicle->pDriver->RegisterReference((CEntity**)&pVehicle->pDriver); + pPed->bInVehicle = true; + pVehicle->SetStatus(STATUS_PHYSICS); + if (pVehicle->m_vehType == VEHICLE_TYPE_BOAT) + pVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + pVehicle->bEngineOn = true; + pVehicle->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pVehicle->GetPosition()); + CPopulation::ms_nTotalMissionPeds++; + ScriptParams[0] = CPools::GetPedPool()->GetIndex(pPed); + StoreParameters(&m_nIp, 1); + if (m_bIsMissionScript) + CTheScripts::MissionCleanUp.AddEntityToList(ScriptParams[0], CLEANUP_CHAR); + return 0; + } + case COMMAND_CREATE_RANDOM_CHAR_AS_PASSENGER: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + CPed* pPed = CPopulation::AddPedInCar(pVehicle, false); + pPed->CharCreatedBy = MISSION_CHAR; + pPed->bRespondsToThreats = false; + pPed->bAllowMedicsToReviveMe = false; + pPed->bIsPlayerFriend = false; + if (pVehicle->bIsBus) + pPed->bRenderPedInCar = false; + pPed->SetPosition(pVehicle->GetPosition()); + pPed->SetOrientation(0.0f, 0.0f, 0.0f); + CPopulation::ms_nTotalMissionPeds++; + pVehicle->m_nZoneLevel = CTheZones::GetLevelFromPosition(&pVehicle->GetPosition()); + if (ScriptParams[1] >= 0) + pVehicle->AddPassenger(pPed, ScriptParams[1]); + else + pVehicle->AddPassenger(pPed); + + pPed->m_pMyVehicle = pVehicle; + pPed->m_pMyVehicle->RegisterReference((CEntity**)&pPed->m_pMyVehicle); + pPed->bInVehicle = true; + pPed->SetPedState(PED_DRIVING); + 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_IGNORE_THREATS_BEHIND_OBJECTS: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bIgnoreThreatsBehindObjects = ScriptParams[1]; + return 0; + } + case COMMAND_ENSURE_PLAYER_HAS_DRIVE_BY_WEAPON: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + if (pPed->bInVehicle) { + if (pPed->GetWeapon(WEAPONSLOT_SUBMACHINEGUN).m_eWeaponType) { + if (pPed->GetWeapon(WEAPONSLOT_SUBMACHINEGUN).m_nAmmoTotal < ScriptParams[1]) + pPed->SetAmmo(pPed->GetWeapon(WEAPONSLOT_SUBMACHINEGUN).m_eWeaponType, ScriptParams[1]); + } + else { + pPed->GiveWeapon(WEAPONTYPE_UZI, ScriptParams[1], true); + if (pPed->m_storedWeapon == WEAPONTYPE_UNIDENTIFIED) + pPed->m_storedWeapon = pPed->GetWeapon()->m_eWeaponType; + pPed->SetCurrentWeapon(WEAPONTYPE_UZI); + } + } + return 0; + } + case COMMAND_MAKE_HELI_COME_CRASHING_DOWN: + { + CollectParameters(&m_nIp, 1); + CAutomobile* pHeli = (CAutomobile*)CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pHeli && pHeli->IsCar() && pHeli->IsRealHeli()); + pHeli->bHeliDestroyed = true; + return 0; + } + case COMMAND_ADD_EXPLOSION_NO_SOUND: + { + CollectParameters(&m_nIp, 4); + CExplosion::AddExplosion(nil, nil, (eExplosionType)ScriptParams[3], *(CVector*)&ScriptParams[0], 0, false); + return 0; + } + case COMMAND_SET_OBJECT_AREA_VISIBLE: + { + CollectParameters(&m_nIp, 2); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + script_assert(pObject); + pObject->m_area = ScriptParams[1]; + return 0; + } + //case COMMAND_WAS_VEHICLE_EVER_POLICE: + case COMMAND_SET_CHAR_NEVER_TARGETTED: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bNeverEverTargetThisPed = ScriptParams[1]; + return 0; + } + case COMMAND_LOAD_UNCOMPRESSED_ANIM: + { + char key[KEY_LENGTH_IN_SCRIPT]; + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CCutsceneMgr::LoadAnimationUncompressed(key); + return 0; + } + case COMMAND_WAS_CUTSCENE_SKIPPED: + { + UpdateCompareFlag(CCutsceneMgr::WasCutsceneSkipped()); + return 0; + } + case COMMAND_SET_CHAR_CROUCH_WHEN_THREATENED: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bCrouchWhenScared = true; + return 0; + } + case COMMAND_IS_CHAR_IN_ANY_POLICE_VEHICLE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle && + pPed->m_pMyVehicle->IsLawEnforcementVehicle() && + pPed->m_pMyVehicle->GetModelIndex() != MI_PREDATOR); + return 0; + } + case COMMAND_DOES_CHAR_EXIST: + CollectParameters(&m_nIp, 1); + UpdateCompareFlag(CPools::GetPedPool()->GetAt(ScriptParams[0]) != 0); + return 0; + //case COMMAND_DOES_VEHICLE_EXIST: + //case COMMAND_ADD_SHORT_RANGE_BLIP_FOR_CONTACT_POINT: + case COMMAND_ADD_SHORT_RANGE_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::SetShortRangeCoordBlip(BLIP_COORD, pos, 2, BLIP_DISPLAY_BOTH); + CRadar::SetBlipSprite(id, ScriptParams[3]); + ScriptParams[0] = id; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_IS_CHAR_STUCK: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->m_nWaitState == WAITSTATE_STUCK); + return 0; + } + case COMMAND_SET_ALL_TAXIS_HAVE_NITRO: + { + CollectParameters(&m_nIp, 1); + CVehicle::bAllTaxisHaveNitro = ScriptParams[0] != 0; + return 0; + } + case COMMAND_SET_CHAR_STOP_SHOOT_DONT_SEEK_ENTITY: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) { + pPed->bKindaStayInSamePlace = true; + pPed->bStopAndShoot = true; + } + else { + pPed->bKindaStayInSamePlace = false; + pPed->bStopAndShoot = false; + } + pPed->m_nLastPedState = PED_NONE; + return 0; + } + case COMMAND_FREEZE_CAR_POSITION_AND_DONT_LOAD_COLLISION: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + if (ScriptParams[1]) { + pVehicle->bIsFrozen = true; + pVehicle->bInfiniteMass = true; + if (m_bIsMissionScript) { + CWorld::Remove(pVehicle); + pVehicle->bIsStaticWaitingForCollision = true; + CWorld::Add(pVehicle); + } + } + else { + pVehicle->bIsFrozen = false; + pVehicle->bInfiniteMass = false; + } + return 0; + } + //case COMMAND_FREEZE_CHAR_POSITION_AND_DONT_LOAD_COLLISION: + //case COMMAND_FREEZE_OBJECT_POSITION_AND_DONT_LOAD_COLLISION: + //case COMMAND_SET_FADE_AND_JUMPCUT_AFTER_RC_EXPLOSION: + default: + script_assert(0); + } + return -1; +} diff --git a/src/miami/control/Script8.cpp b/src/miami/control/Script8.cpp new file mode 100644 index 00000000..98f69737 --- /dev/null +++ b/src/miami/control/Script8.cpp @@ -0,0 +1,618 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "DMAudio.h" +#if ((defined GTAVC_JP_PATCH || defined SUPPORT_JAPANESE_SCRIPT) && defined MORE_LANGUAGES) +#include "Frontend.h" +#endif +#include "GameLogic.h" +#include "Garages.h" +#ifdef MISSION_REPLAY +#include "GenericGameStorage.h" +#endif +#if (defined GTA_PC && !defined GTAVC_JP_PATCH || defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT || defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) +#include "General.h" +#include "maths.h" +#endif +#include "Hud.h" +#include "Pad.h" +#include "PedAttractor.h" +#include "Population.h" +#include "Pools.h" +#include "RpAnimBlend.h" +#include "Stats.h" +#include "VisibilityPlugins.h" +#include "Wanted.h" +#include "WaterLevel.h" +#include "World.h" +#include "Zones.h" + +int8 CRunningScript::ProcessCommands1400To1499(int32 command) +{ + switch (command) { + case COMMAND_REGISTER_VIGILANTE_LEVEL: + CollectParameters(&m_nIp, 1); + CStats::RegisterLevelVigilanteMission(ScriptParams[0]); + return 0; + case COMMAND_CLEAR_ALL_CHAR_ANIMS: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (!pPed->bInVehicle) { + pPed->m_pVehicleAnim = nil; + pPed->RestartNonPartialAnims(); + RpAnimBlendClumpRemoveAllAssociations(pPed->GetClump()); + pPed->SetPedState(PED_IDLE); + pPed->SetMoveState(PEDMOVE_STILL); + pPed->m_nLastPedState = PED_NONE; + pPed->ClearAimFlag(); + pPed->ClearLookFlag(); + pPed->bIsPointingGunAt = false; + if (pPed->IsPlayer()) + ((CPlayerPed*)pPed)->m_fMoveSpeed = 0.0f; + else + pPed->m_nStoredMoveState = PEDMOVE_STILL; + CAnimManager::AddAnimation(pPed->GetClump(), pPed->m_animGroup, ANIM_STD_IDLE); + pPed->bIsPedDieAnimPlaying = false; + } + return 0; + } + case COMMAND_SET_MAXIMUM_NUMBER_OF_CARS_IN_GARAGE: + CollectParameters(&m_nIp, 2); + CGarages::SetMaxNumStoredCarsForGarage(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_WANTED_STARS_ARE_FLASHING: + { + CWanted* pWanted = CWorld::Players[CWorld::PlayerInFocus].m_pPed->m_pWanted; + UpdateCompareFlag(pWanted->m_nMinWantedLevel - pWanted->GetWantedLevel() > 0); + return 0; + } + case COMMAND_SET_ALLOW_HURRICANES: + CollectParameters(&m_nIp, 1); + CStats::NoMoreHurricanes = ScriptParams[0]; + return 0; + case COMMAND_PLAY_ANNOUNCEMENT: + { + CollectParameters(&m_nIp, 1); + DMAudio.PlayRadioAnnouncement(ScriptParams[0] + STREAMED_SOUND_ANNOUNCE_BRIDGE_CLOSED); + return 0; + } + case COMMAND_SET_PLAYER_IS_IN_STADIUM: + { + CollectParameters(&m_nIp, 1); + CTheScripts::bPlayerIsInTheStatium = ScriptParams[0]; + return 0; + } + case COMMAND_GET_BUS_FARES_COLLECTED_BY_PLAYER: + { + CollectParameters(&m_nIp, 1); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + ScriptParams[0] = pPlayerInfo->m_pPed->m_nLastBusFareCollected; + pPlayerInfo->m_pPed->m_nLastBusFareCollected = 0; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_SET_CHAR_OBJ_BUY_ICE_CREAM: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[1]); + script_assert(pVehicle); + ScriptParams[0] = 0; + if (pPed->m_objective == OBJECTIVE_NONE && !pPed->bHasAlreadyUsedAttractor) { + C2dEffect* pEffect = (C2dEffect*)GetPedAttractorManager()->GetEffectForIceCreamVan(pVehicle, pPed->GetPosition()); // has to be casted, because inner methods are const + if (pEffect) { + CVector pos; + CPedAttractorManager::ComputeEffectPos(pEffect, pVehicle->GetMatrix(), pos); + if ((pPed->GetPosition() - pos).MagnitudeSqr() < SQR(20.0f)) { + if (GetPedAttractorManager()->HasEmptySlot(pEffect) && GetPedAttractorManager()->IsApproachable(pEffect, pVehicle->GetMatrix(), 0, pPed)) { + if (GetPedAttractorManager()->RegisterPedWithAttractor(pPed, pEffect, pVehicle->GetMatrix())) + ScriptParams[0] = 1; + } + } + } + } + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_DISPLAY_RADAR: + CollectParameters(&m_nIp, 1); + CHud::m_HideRadar = ScriptParams[0] == 0; + return 0; + case COMMAND_REGISTER_BEST_POSITION: + CollectParameters(&m_nIp, 2); + CStats::RegisterBestPosition(ScriptParams[0], ScriptParams[1]); + return 0; + case COMMAND_IS_PLAYER_IN_INFO_ZONE: + { + CollectParameters(&m_nIp, 1); + CPlayerInfo* pPlayerInfo = &CWorld::Players[ScriptParams[0]]; + char key[KEY_LENGTH_IN_SCRIPT]; + memset(key, 0, KEY_LENGTH_IN_SCRIPT); + CTheScripts::ReadTextLabelFromScript(&m_nIp, key); + m_nIp += KEY_LENGTH_IN_SCRIPT; + CVector pos = pPlayerInfo->GetPos(); + CZone* infoZone = CTheZones::FindInformationZoneForPosition(&pos); + UpdateCompareFlag(strncmp(key, infoZone->name, 8) == 0); // original code doesn't seem to be using strncmp in here and compare 2 ints instead + return 0; + } + case COMMAND_CLEAR_CHAR_ICE_CREAM_PURCHASE: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (pPed->m_attractor) + GetPedAttractorManager()->DeRegisterPed(pPed, pPed->m_attractor); + return 0; + } + case COMMAND_IS_IN_CAR_FIRE_BUTTON_PRESSED: + UpdateCompareFlag(CPad::GetPad(0)->GetCarGunFired()); + return 0; + case COMMAND_HAS_CHAR_ATTEMPTED_ATTRACTOR: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bHasAlreadyUsedAttractor); + return 0; + } + case COMMAND_SET_LOAD_COLLISION_FOR_CAR_FLAG: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + if (ScriptParams[1]) { + pVehicle->bDontLoadCollision = false; + if (m_bMissionFlag) { + CWorld::Remove(pVehicle); + pVehicle->bIsStaticWaitingForCollision = true; + CWorld::Add(pVehicle); + } + } + else { + pVehicle->bDontLoadCollision = true; + if (pVehicle->bIsStaticWaitingForCollision) { + pVehicle->bIsStaticWaitingForCollision = false; + if (!pVehicle->GetIsStatic()) + pVehicle->AddToMovingList(); + } + } + return 0; + } + case COMMAND_SET_LOAD_COLLISION_FOR_CHAR_FLAG: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + if (ScriptParams[1]) { + pPed->bDontLoadCollision = false; + if (m_bMissionFlag) { + CWorld::Remove(pPed); + pPed->bIsStaticWaitingForCollision = true; + CWorld::Add(pPed); + } + } + else { + pPed->bDontLoadCollision = true; + if (pPed->bIsStaticWaitingForCollision) { + pPed->bIsStaticWaitingForCollision = false; + if (!pPed->GetIsStatic()) + pPed->AddToMovingList(); + } + } + return 0; + } + //case COMMAND_SET_LOAD_COLLISION_FOR_OBJECT_FLAG: + case COMMAND_ADD_BIG_GUN_FLASH: + { + CollectParameters(&m_nIp, 6); + CWeapon::AddGunFlashBigGuns(*(CVector*)&ScriptParams[0], *(CVector*)&ScriptParams[3]); + return 0; + } + case COMMAND_HAS_CHAR_BOUGHT_ICE_CREAM: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(pPed->bBoughtIceCream); + return 0; + } + case COMMAND_GET_PROGRESS_PERCENTAGE: + *(float*)&ScriptParams[0] = CStats::GetPercentageProgress(); + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_SET_SHORTCUT_PICKUP_POINT: + { + CollectParameters(&m_nIp, 4); + CGameLogic::AddShortCutPointAfterDeath(*(CVector*)&ScriptParams[0], *(float*)&ScriptParams[3]); + return 0; + } + case COMMAND_SET_SHORTCUT_DROPOFF_POINT_FOR_MISSION: + { + CollectParameters(&m_nIp, 4); + CGameLogic::AddShortCutDropOffPointForMission(*(CVector*)&ScriptParams[0], *(float*)&ScriptParams[3]); + return 0; + } + case COMMAND_GET_RANDOM_ICE_CREAM_CUSTOMER_IN_AREA: + { + CollectParameters(&m_nIp, 7); + 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->m_nWaitState != WAITSTATE_FALSE) + continue; + if (pPed->bHasAlreadyUsedAttractor) + continue; + if (pPed->m_attractor) + continue; + if (!ThisIsAValidRandomPed(pPed->m_nPedType, ScriptParams[4], ScriptParams[5], ScriptParams[6])) + 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_ICE_CREAM_CUSTOMER_IN_ZONE: + case COMMAND_UNLOCK_ALL_CAR_DOORS_IN_AREA: + { + CollectParameters(&m_nIp, 4); + 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 (pVehicle->IsWithinArea(infX, infY, supX, supY)) + pVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + } + return 0; + } + case COMMAND_SET_GANG_ATTACK_PLAYER_WITH_COPS: + CollectParameters(&m_nIp, 2); + CGangs::SetWillAttackPlayerWithCops((ePedType)((int)PEDTYPE_GANG1 + ScriptParams[0]), !!ScriptParams[1]); + return 0; + case COMMAND_SET_CHAR_FRIGHTENED_IN_JACKED_CAR: + { + CollectParameters(&m_nIp, 2); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + pPed->bHeldHostageInCar = ScriptParams[1]; + return 0; + } + case COMMAND_SET_VEHICLE_TO_FADE_IN: + { + CollectParameters(&m_nIp, 2); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + script_assert(pVehicle); + CVisibilityPlugins::SetClumpAlpha(pVehicle->GetClump(), ScriptParams[1]); + return 0; + } + case COMMAND_REGISTER_ODDJOB_MISSION_PASSED: + ++CStats::MissionsPassed; + CStats::CheckPointReachedSuccessfully(); + CTheScripts::LastMissionPassedTime = CTimer::GetTimeInMilliseconds(); + CGameLogic::RemoveShortCutDropOffPointForMission(); + return 0; + case COMMAND_IS_PLAYER_IN_SHORTCUT_TAXI: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CWorld::Players[ScriptParams[0]].m_pPed; + script_assert(pPed); + UpdateCompareFlag(pPed->bInVehicle && pPed->m_pMyVehicle && pPed->m_pMyVehicle == CGameLogic::pShortCutTaxi); + return 0; + } + case COMMAND_IS_CHAR_DUCKING: + { + CollectParameters(&m_nIp, 1); + CPed* pPed = CPools::GetPedPool()->GetAt(ScriptParams[0]); + script_assert(pPed); + UpdateCompareFlag(RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_DUCK_DOWN) != nil); + return 0; + } + case COMMAND_CREATE_DUST_EFFECT_FOR_CUTSCENE_HELI: + { + CollectParameters(&m_nIp, 3); + CObject* pHeli = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + bool found = false; + float waterLevel = -1000.0f; + CVector pos = pHeli->GetPosition(); + float radius = *(float*)&ScriptParams[1]; + float ground = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &found); + if (!CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &waterLevel, false)) + waterLevel = 0.0f; + if (waterLevel > ground) + ground = waterLevel; + if (ScriptParams[2] > 8) + ScriptParams[2] = 8; + CVehicle::HeliDustGenerate(pHeli, (pos.z - ground - 1.0f - radius) * 0.3 + radius, ground, ScriptParams[2]); + return 0; + } + case COMMAND_REGISTER_FIRE_LEVEL: + CollectParameters(&m_nIp, 1); + CStats::RegisterLevelFireMission(ScriptParams[0]); + return 0; + case COMMAND_IS_AUSTRALIAN_GAME: + UpdateCompareFlag(false); // should we make some check? + return 0; + case COMMAND_DISARM_CAR_BOMB: + { + CollectParameters(&m_nIp, 1); + CVehicle* pVehicle = CPools::GetVehiclePool()->GetAt(ScriptParams[0]); + if (pVehicle->m_bombType != CARBOMB_NONE) { + pVehicle->m_bombType = CARBOMB_NONE; + pVehicle->m_pBombRigger = nil; + } + return 0; + } +#if (defined GTAVC_JP_PATCH || defined SUPPORT_JAPANESE_SCRIPT) + case COMMAND_IS_JAPANESE_GAME: +#ifdef MORE_LANGUAGES + UpdateCompareFlag(FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_JAPANESE); +#elif (defined GTAVC_JP_PATCH) + UpdateCompareFlag(true); +#else + UpdateCompareFlag(false); +#endif + return 0; +#elif (!defined GTA_PS2) + case COMMAND_SET_ONSCREEN_COUNTER_FLASH_WHEN_FIRST_DISPLAYED: + { + script_assert(CTheScripts::ScriptSpace[m_nIp++] == ARGUMENT_GLOBALVAR); + uint16 var = CTheScripts::Read2BytesFromScript(&m_nIp); + CollectParameters(&m_nIp, 1); + //CUserDisplay::OnscnTimer.SetCounterFlashWhenFirstDisplayed(var, ScriptParams[0]); + return 0; + } +#endif +#if (defined GTA_PC && !defined GTAVC_JP_PATCH || defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT || defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) + case COMMAND_SHUFFLE_CARD_DECKS: + { + CollectParameters(&m_nIp, 1); + script_assert(ScriptParams[0] >= 0 && ScriptParams[0] <= 6); + for (int i = 0; i < CARDS_IN_STACK; i++) + CTheScripts::CardStack[i] = 0; + int16 seq[CARDS_IN_STACK]; + for (int i = 0; i < MAX_DECKS * CARDS_IN_DECK; i++) + seq[i] = i; + int cards_left = CARDS_IN_DECK * ScriptParams[0]; + for (int k = 1; k < CARDS_IN_DECK + 1; k++) { + for (int deck = 0; deck < ScriptParams[0]; deck++) { + int index = CGeneral::GetRandomNumberInRange(0, cards_left); + CTheScripts::CardStack[seq[index]] = k; + for (int l = index; l < cards_left; l++) { + if (l + 1 < CARDS_IN_STACK) + seq[l] = seq[l + 1]; + else + seq[l] = 0; + } + --cards_left; + } + } + CTheScripts::CardStackPosition = 0; + return 0; + } + case COMMAND_FETCH_NEXT_CARD: + { + if (CTheScripts::CardStack[CTheScripts::CardStackPosition] == 0) + CTheScripts::CardStackPosition = 0; + ScriptParams[0] = CTheScripts::CardStack[CTheScripts::CardStackPosition++]; + if (CTheScripts::CardStackPosition == CARDS_IN_DECK * MAX_DECKS) + CTheScripts::CardStackPosition = 0; + StoreParameters(&m_nIp, 1); + return 0; + } + case COMMAND_GET_OBJECT_VELOCITY: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + *(CVector*)&ScriptParams[0] = GAME_SPEED_TO_METERS_PER_SECOND * pObject->GetMoveSpeed(); + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_IS_DEBUG_CAMERA_ON: + UpdateCompareFlag(TheCamera.WorldViewerBeingUsed); + return 0; + case COMMAND_ADD_TO_OBJECT_ROTATION_VELOCITY: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + CVector newSpeed = pObject->GetTurnSpeed() + *(CVector*)&ScriptParams[1] / GAME_SPEED_TO_METERS_PER_SECOND; + if (pObject->bIsStatic) { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + } + pObject->SetTurnSpeed(newSpeed.x, newSpeed.y, newSpeed.z); + return 0; + } + case COMMAND_SET_OBJECT_ROTATION_VELOCITY: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + CVector newSpeed = *(CVector*)&ScriptParams[1] / GAME_SPEED_TO_METERS_PER_SECOND; + if (pObject->bIsStatic) { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + } + pObject->SetTurnSpeed(newSpeed.x, newSpeed.y, newSpeed.z); + return 0; + } + case COMMAND_IS_OBJECT_STATIC: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + UpdateCompareFlag(pObject->GetIsStatic()); + return 0; + } + case COMMAND_GET_ANGLE_BETWEEN_2D_VECTORS: + { + CollectParameters(&m_nIp, 4); + CVector2D v1 = *(CVector2D*)&ScriptParams[0]; + CVector2D v2 = *(CVector2D*)&ScriptParams[2]; + float c = DotProduct2D(v1, v2) / (v1.Magnitude() * v2.Magnitude()); +#ifdef FIX_BUGS // command is a SA leftover where it was fixed to this + *(float*)&ScriptParams[0] = RADTODEG(Acos(c)); +#else + *(float*)&ScriptParams[0] = Acos(c); +#endif + return 0; + } + case COMMAND_DO_2D_RECTANGLES_COLLIDE: + { + CollectParameters(&m_nIp, 8); + float infX1 = *(float*)&ScriptParams[0] - *(float*)&ScriptParams[2] * 0.5; // NB: not float + float supX1 = *(float*)&ScriptParams[0] + *(float*)&ScriptParams[2] * 0.5; + float infX2 = *(float*)&ScriptParams[4] - *(float*)&ScriptParams[6] * 0.5; + float supX2 = *(float*)&ScriptParams[4] + *(float*)&ScriptParams[6] * 0.5; + float infY1 = *(float*)&ScriptParams[1] - *(float*)&ScriptParams[3] * 0.5; + float supY1 = *(float*)&ScriptParams[1] + *(float*)&ScriptParams[3] * 0.5; + float infY2 = *(float*)&ScriptParams[5] - *(float*)&ScriptParams[7] * 0.5; + float supY2 = *(float*)&ScriptParams[5] + *(float*)&ScriptParams[7] * 0.5; + bool collide = true; + if (infY2 > supY1) + collide = false; + if (infY1 > supY2) + collide = false; + if (infX2 > supX1) + collide = false; + if (infX1 > supX2) + collide = false; + UpdateCompareFlag(collide); + return 0; + } + case COMMAND_GET_OBJECT_ROTATION_VELOCITY: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + *(CVector*)&ScriptParams[0] = pObject->GetTurnSpeed() * GAME_SPEED_TO_METERS_PER_SECOND; + StoreParameters(&m_nIp, 3); + return 0; + } + case COMMAND_ADD_VELOCITY_RELATIVE_TO_OBJECT_VELOCITY: + { + CollectParameters(&m_nIp, 4); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + CVector vecAddition = *(CVector*)&ScriptParams[1] * CTimer::GetTimeStep() / GAME_SPEED_TO_METERS_PER_SECOND; + if (!pObject->bIsStatic) { + CVector vecCurrSpeed = pObject->GetSpeed(); + vecCurrSpeed.Normalise(); + if (vecCurrSpeed.z != 1.0) { // NB: not float! + CVector vx = CrossProduct(vecCurrSpeed, CVector(0.0f, 0.0f, 1.0f)); + vx.Normalise(); + CVector vz = CrossProduct(vx, vecCurrSpeed); + vz.Normalise(); + CVector vecNewSpeed = pObject->GetSpeed() + vecAddition.x * vx + vecAddition.y * vecCurrSpeed + vecAddition.z * vecCurrSpeed; + if (pObject->bIsStatic) { + pObject->SetIsStatic(false); + pObject->AddToMovingList(); + } + pObject->SetMoveSpeed(vecNewSpeed); + } + } + return 0; + } + case COMMAND_GET_OBJECT_SPEED: + { + CollectParameters(&m_nIp, 1); + CObject* pObject = CPools::GetObjectPool()->GetAt(ScriptParams[0]); + *(float*)&ScriptParams[0] = pObject->GetMoveSpeed().Magnitude() * GAME_SPEED_TO_METERS_PER_SECOND; + StoreParameters(&m_nIp, 1); + return 0; + } +#endif +#if (defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) + case COMMAND_IS_MISSION_SKIP: +#ifdef MISSION_REPLAY + ScriptParams[0] = MissionSkipLevel; +#else + ScriptParams[0] = 0; +#endif + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_SET_IN_AMMUNATION: + CollectParameters(&m_nIp, 1); +#ifdef MISSION_REPLAY + IsInAmmunation = ScriptParams[0]; +#endif + return 0; + case COMMAND_DO_SAVE_GAME: + CollectParameters(&m_nIp, 1); +#ifdef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT + UsingMobileScript = true; +#endif +#ifdef MISSION_REPLAY + SaveGameForPause(ScriptParams[0]); +#endif + return 0; + case COMMAND_IS_RETRY: +#ifdef MISSION_REPLAY + if (strcmp(m_abScriptName, "porno4") != 0) + ScriptParams[0] = AllowMissionReplay; +#ifdef FIX_BUGS + else + ScriptParams[0] = gbTryingPorn4Again; +#else + else if (gbTryingPorn4Again) + ScriptParams[0] = 1; +#endif +#else + ScriptParams[0] = 0; +#endif + StoreParameters(&m_nIp, 1); + return 0; + case COMMAND_DUMMY: + return 0; +#endif +#if (defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT || defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) + // it is unknown what these commands do but they don't take parameters + case COMMAND_MARK_CUTSCENE_START: + return 0; + case COMMAND_MARK_CUTSCENE_END: + return 0; + case COMMAND_CUTSCENE_SCROLL: + return 0; +#endif + default: + script_assert(0); + } + return -1; +} diff --git a/src/miami/control/ScriptCommands.h b/src/miami/control/ScriptCommands.h new file mode 100644 index 00000000..9cb39e23 --- /dev/null +++ b/src/miami/control/ScriptCommands.h @@ -0,0 +1,1508 @@ +#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, +#ifdef SUPPORT_GINPUT_SCRIPT + COMMAND_HAS_PAD_IN_HANDS, +#else + COMMAND_ORDER_CHAR_TO_BACKDOOR, +#endif + 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_SET_CHAR_OBJ_LEAVE_ANY_CAR, + 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_SET_DRUNK_INPUT_DELAY, + COMMAND_SET_CHAR_MONEY, + COMMAND_INCREASE_CHAR_MONEY, + COMMAND_GET_OFFSET_FROM_OBJECT_IN_WORLD_COORDS, + 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_GET_OFFSET_FROM_CAR_IN_WORLD_COORDS, + 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, + 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_TEMP_ACTION, + 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, + COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER, + COMMAND_SET_CHAR_CEASE_ATTACK_TIMER, + COMMAND_GET_REMOTE_CONTROLLED_CAR, + COMMAND_IS_PC_VERSION, + COMMAND_REPLAY, + COMMAND_IS_REPLAY_PLAYING, + COMMAND_IS_MODEL_AVAILABLE, + COMMAND_SHUT_CHAR_UP, + COMMAND_SET_ENABLE_RC_DETONATE, + COMMAND_SET_CAR_RANDOM_ROUTE_SEED, + COMMAND_IS_ANY_PICKUP_AT_COORDS, + COMMAND_GET_FIRST_PICKUP_COORDS, + COMMAND_GET_NEXT_PICKUP_COORDS, + COMMAND_REMOVE_ALL_CHAR_WEAPONS, + COMMAND_HAS_PLAYER_GOT_WEAPON, + COMMAND_HAS_CHAR_GOT_WEAPON, + COMMAND_IS_PLAYER_FACING_CHAR, + COMMAND_SET_TANK_DETONATE_CARS, + COMMAND_GET_POSITION_OF_ANALOGUE_STICKS, + COMMAND_IS_CAR_ON_FIRE, + COMMAND_IS_CAR_TYRE_BURST, + COMMAND_SET_CAR_DRIVE_STRAIGHT_AHEAD, + COMMAND_SET_CAR_WAIT, + COMMAND_IS_PLAYER_STANDING_ON_A_VEHICLE, + COMMAND_IS_PLAYER_FOOT_DOWN, + COMMAND_IS_CHAR_FOOT_DOWN, + COMMAND_INITIALISE_OBJECT_PATH, + COMMAND_START_OBJECT_ON_PATH, + COMMAND_SET_OBJECT_PATH_SPEED, + COMMAND_SET_OBJECT_PATH_POSITION, + COMMAND_GET_OBJECT_DISTANCE_ALONG_PATH, + COMMAND_CLEAR_OBJECT_PATH, + COMMAND_HELI_GOTO_COORDS, + COMMAND_IS_INT_VAR_EQUAL_TO_CONSTANT, + COMMAND_IS_INT_LVAR_EQUAL_TO_CONSTANT, + COMMAND_GET_DEAD_CHAR_PICKUP_COORDS, + COMMAND_CREATE_PROTECTION_PICKUP, + COMMAND_IS_CHAR_IN_ANY_BOAT, + COMMAND_IS_PLAYER_IN_ANY_BOAT, + COMMAND_IS_CHAR_IN_ANY_HELI, + COMMAND_IS_PLAYER_IN_ANY_HELI, + COMMAND_IS_CHAR_IN_ANY_PLANE, + COMMAND_IS_PLAYER_IN_ANY_PLANE, + COMMAND_IS_CHAR_IN_WATER, + COMMAND_SET_VAR_INT_TO_CONSTANT, + COMMAND_SET_LVAR_INT_TO_CONSTANT, + COMMAND_IS_INT_VAR_GREATER_THAN_CONSTANT, + COMMAND_IS_INT_LVAR_GREATER_THAN_CONSTANT, + COMMAND_IS_CONSTANT_GREATER_THAN_INT_VAR, + COMMAND_IS_CONSTANT_GREATER_THAN_INT_LVAR, + COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_CONSTANT, + COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_CONSTANT, + COMMAND_IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_VAR, + COMMAND_IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_LVAR, + COMMAND_GET_CHAR_WEAPON_IN_SLOT, + COMMAND_GET_CLOSEST_STRAIGHT_ROAD, + COMMAND_SET_CAR_FORWARD_SPEED, + COMMAND_SET_AREA_VISIBLE, + COMMAND_SET_CUTSCENE_ANIM_TO_LOOP, + COMMAND_MARK_CAR_AS_CONVOY_CAR, + COMMAND_RESET_HAVOC_CAUSED_BY_PLAYER, + COMMAND_GET_HAVOC_CAUSED_BY_PLAYER, + COMMAND_CREATE_SCRIPT_ROADBLOCK, + COMMAND_CLEAR_ALL_SCRIPT_ROADBLOCKS, + COMMAND_SET_CHAR_OBJ_WALK_TO_CHAR, + COMMAND_IS_PICKUP_IN_ZONE, + COMMAND_GET_OFFSET_FROM_CHAR_IN_WORLD_COORDS, + COMMAND_HAS_CHAR_BEEN_PHOTOGRAPHED, + COMMAND_SET_CHAR_OBJ_AIM_GUN_AT_CHAR, + COMMAND_SWITCH_SECURITY_CAMERA, + COMMAND_IS_CHAR_IN_FLYING_VEHICLE, + COMMAND_IS_PLAYER_IN_FLYING_VEHICLE, + COMMAND_HAS_SONY_CD_BEEN_READ, + COMMAND_GET_NUMBER_OF_SONY_CDS_READ, + COMMAND_ADD_SHORT_RANGE_BLIP_FOR_COORD_OLD, + COMMAND_ADD_SHORT_RANGE_BLIP_FOR_COORD, + COMMAND_ADD_SHORT_RANGE_SPRITE_BLIP_FOR_COORD, + COMMAND_ADD_MONEY_SPENT_ON_CLOTHES, + COMMAND_SET_HELI_ORIENTATION, + COMMAND_CLEAR_HELI_ORIENTATION, + COMMAND_PLANE_GOTO_COORDS, + COMMAND_GET_NTH_CLOSEST_CAR_NODE, + COMMAND_GET_NTH_CLOSEST_CHAR_NODE, + COMMAND_DRAW_WEAPONSHOP_CORONA, + COMMAND_SET_ENABLE_RC_DETONATE_ON_CONTACT, + COMMAND_FREEZE_CHAR_POSITION, + COMMAND_SET_CHAR_DROWNS_IN_WATER, + COMMAND_SET_OBJECT_RECORDS_COLLISIONS, + COMMAND_HAS_OBJECT_COLLIDED_WITH_ANYTHING, + COMMAND_REMOVE_RC_BUGGY, + COMMAND_HAS_PHOTOGRAPH_BEEN_TAKEN, + COMMAND_GET_CHAR_ARMOUR, + COMMAND_SET_CHAR_ARMOUR, + COMMAND_SET_HELI_STABILISER, + COMMAND_SET_CAR_STRAIGHT_LINE_DISTANCE, + COMMAND_POP_CAR_BOOT, + COMMAND_SHUT_PLAYER_UP, + COMMAND_SET_PLAYER_MOOD, + COMMAND_REQUEST_COLLISION, + COMMAND_LOCATE_OBJECT_2D, + COMMAND_LOCATE_OBJECT_3D, + COMMAND_IS_OBJECT_IN_WATER, + COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR_EVEN_MISSION_CAR, + COMMAND_IS_OBJECT_IN_AREA_2D, + COMMAND_IS_OBJECT_IN_AREA_3D, + COMMAND_SET_CHAR_CROUCH, + COMMAND_SET_ZONE_CIVILIAN_CAR_INFO, + COMMAND_REQUEST_ANIMATION, + COMMAND_HAS_ANIMATION_LOADED, + COMMAND_REMOVE_ANIMATION, + COMMAND_IS_CHAR_WAITING_FOR_WORLD_COLLISION, + COMMAND_IS_CAR_WAITING_FOR_WORLD_COLLISION, + COMMAND_IS_OBJECT_WAITING_FOR_WORLD_COLLISION, + COMMAND_SET_CHAR_SHUFFLE_INTO_DRIVERS_SEAT, + COMMAND_ATTACH_CHAR_TO_OBJECT, + COMMAND_SET_CHAR_AS_PLAYER_FRIEND, + COMMAND_DISPLAY_NTH_ONSCREEN_COUNTER, + COMMAND_DISPLAY_NTH_ONSCREEN_COUNTER_WITH_STRING, + COMMAND_ADD_SET_PIECE, + COMMAND_SET_EXTRA_COLOURS, + COMMAND_CLEAR_EXTRA_COLOURS, + COMMAND_CLOSE_CAR_BOOT, + COMMAND_GET_WHEELIE_STATS, + COMMAND_DISARM_CHAR, + COMMAND_BURST_CAR_TYRE, + COMMAND_IS_CHAR_OBJ_NO_OBJ, + COMMAND_IS_PLAYER_WEARING, + COMMAND_SET_PLAYER_CAN_DO_DRIVE_BY, + COMMAND_SET_CHAR_OBJ_SPRINT_TO_COORD, + COMMAND_CREATE_SWAT_ROPE, + COMMAND_SET_FIRST_PERSON_CONTROL_CAMERA, + COMMAND_GET_NEAREST_TYRE_TO_POINT, + COMMAND_SET_CAR_MODEL_COMPONENTS, + COMMAND_SWITCH_LIFT_CAMERA, + COMMAND_CLOSE_ALL_CAR_DOORS, + COMMAND_GET_DISTANCE_BETWEEN_COORDS_2D, + COMMAND_GET_DISTANCE_BETWEEN_COORDS_3D, + COMMAND_POP_CAR_BOOT_USING_PHYSICS, + COMMAND_SET_FIRST_PERSON_WEAPON_CAMERA, + COMMAND_IS_CHAR_LEAVING_VEHICLE_TO_DIE, + COMMAND_SORT_OUT_OBJECT_COLLISION_WITH_CAR, + COMMAND_GET_MAX_WANTED_LEVEL, + COMMAND_IS_CHAR_WANDER_PATH_CLEAR, + COMMAND_PRINT_HELP_WITH_NUMBER, + COMMAND_PRINT_HELP_FOREVER, + COMMAND_PRINT_HELP_FOREVER_WITH_NUMBER, + COMMAND_SET_CHAR_CAN_BE_DAMAGED_BY_MEMBERS_OF_GANG, + COMMAND_LOAD_AND_LAUNCH_MISSION_EXCLUSIVE, + COMMAND_IS_MISSION_AUDIO_PLAYING, + COMMAND_CREATE_LOCKED_PROPERTY_PICKUP, + COMMAND_CREATE_FORSALE_PROPERTY_PICKUP, + COMMAND_FREEZE_CAR_POSITION, + COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_CHAR, + COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_CAR, + COMMAND_HAS_CAR_BEEN_DAMAGED_BY_CHAR, + COMMAND_HAS_CAR_BEEN_DAMAGED_BY_CAR, + COMMAND_GET_RADIO_CHANNEL, + COMMAND_DISPLAY_TEXT_WITH_3_NUMBERS, + COMMAND_IS_CAR_DROWNING_IN_WATER, + COMMAND_IS_CHAR_DROWNING_IN_WATER, + COMMAND_DISABLE_CUTSCENE_SHADOWS, + COMMAND_HAS_GLASS_BEEN_SHATTERED_NEARBY, + COMMAND_ATTACH_CUTSCENE_OBJECT_TO_BONE, + COMMAND_ATTACH_CUTSCENE_OBJECT_TO_COMPONENT, + COMMAND_SET_CHAR_STAY_IN_CAR_WHEN_JACKED, + COMMAND_IS_MISSION_AUDIO_LOADING, + COMMAND_ADD_MONEY_SPENT_ON_WEAPONS, + COMMAND_ADD_MONEY_SPENT_ON_PROPERTY, + COMMAND_ADD_MONEY_SPENT_ON_AUTO_PAINTING, + COMMAND_SET_CHAR_ANSWERING_MOBILE, + COMMAND_SET_PLAYER_DRUNKENNESS, + COMMAND_GET_PLAYER_DRUNKENNESS, + COMMAND_SET_PLAYER_DRUG_LEVEL, + COMMAND_GET_PLAYER_DRUG_LEVEL, + COMMAND_ADD_LOAN_SHARK_VISITS, + COMMAND_ADD_STORES_KNOCKED_OFF, + COMMAND_ADD_MOVIE_STUNTS, + COMMAND_ADD_NUMBER_OF_ASSASSINATIONS, + COMMAND_ADD_PIZZAS_DELIVERED, + COMMAND_ADD_GARBAGE_PICKUPS, + COMMAND_ADD_ICE_CREAMS_SOLD, + COMMAND_SET_TOP_SHOOTING_RANGE_SCORE, + COMMAND_ADD_SHOOTING_RANGE_RANK, + COMMAND_ADD_MONEY_SPENT_ON_GAMBLING, + COMMAND_ADD_MONEY_WON_ON_GAMBLING, + COMMAND_SET_LARGEST_GAMBLING_WIN, + COMMAND_SET_CHAR_IN_PLAYERS_GROUP_CAN_FIGHT, + COMMAND_CLEAR_CHAR_WAIT_STATE, + COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA_NO_SAVE, + COMMAND_SET_CAN_BURST_CAR_TYRES, + COMMAND_SET_PLAYER_AUTO_AIM, + COMMAND_FIRE_HUNTER_GUN, + COMMAND_SET_PROPERTY_AS_OWNED, + COMMAND_ADD_BLOOD_RING_KILLS, + COMMAND_SET_LONGEST_TIME_IN_BLOOD_RING, + COMMAND_REMOVE_EVERYTHING_FOR_HUGE_CUTSCENE, + COMMAND_IS_PLAYER_TOUCHING_VEHICLE, + COMMAND_IS_CHAR_TOUCHING_VEHICLE, + COMMAND_CHECK_FOR_PED_MODEL_AROUND_PLAYER, + COMMAND_CLEAR_CHAR_FOLLOW_PATH, + COMMAND_SET_CHAR_CAN_BE_SHOT_IN_VEHICLE, + COMMAND_ATTACH_CUTSCENE_OBJECT_TO_VEHICLE, + COMMAND_LOAD_MISSION_TEXT, + COMMAND_SET_TONIGHTS_EVENT, + COMMAND_CLEAR_CHAR_LAST_DAMAGE_ENTITY, + COMMAND_CLEAR_CAR_LAST_DAMAGE_ENTITY, + COMMAND_FREEZE_OBJECT_POSITION, + COMMAND_SET_PLAYER_HAS_MET_DEBBIE_HARRY, + COMMAND_SET_RIOT_INTENSITY, + COMMAND_IS_CAR_IN_ANGLED_AREA_2D, + COMMAND_IS_CAR_IN_ANGLED_AREA_3D, + COMMAND_REMOVE_WEAPON_FROM_CHAR, + COMMAND_SET_UP_TAXI_SHORTCUT, + COMMAND_CLEAR_TAXI_SHORTCUT, + COMMAND_SET_CHAR_OBJ_GOTO_CAR_ON_FOOT, + COMMAND_GET_CLOSEST_WATER_NODE, + COMMAND_ADD_PORN_LEAFLET_TO_RUBBISH, + COMMAND_CREATE_CLOTHES_PICKUP, + COMMAND_CHANGE_BLIP_THRESHOLD, + COMMAND_MAKE_PLAYER_FIRE_PROOF, + COMMAND_INCREASE_PLAYER_MAX_HEALTH, + COMMAND_INCREASE_PLAYER_MAX_ARMOUR, + COMMAND_CREATE_RANDOM_CHAR_AS_DRIVER, + COMMAND_CREATE_RANDOM_CHAR_AS_PASSENGER, + COMMAND_SET_CHAR_IGNORE_THREATS_BEHIND_OBJECTS, + COMMAND_ENSURE_PLAYER_HAS_DRIVE_BY_WEAPON, + COMMAND_MAKE_HELI_COME_CRASHING_DOWN, + COMMAND_ADD_EXPLOSION_NO_SOUND, + COMMAND_SET_OBJECT_AREA_VISIBLE, + COMMAND_WAS_VEHICLE_EVER_POLICE, + COMMAND_SET_CHAR_NEVER_TARGETTED, + COMMAND_LOAD_UNCOMPRESSED_ANIM, + COMMAND_WAS_CUTSCENE_SKIPPED, + COMMAND_SET_CHAR_CROUCH_WHEN_THREATENED, + COMMAND_IS_CHAR_IN_ANY_POLICE_VEHICLE, + COMMAND_DOES_CHAR_EXIST, + COMMAND_DOES_VEHICLE_EXIST, + COMMAND_ADD_SHORT_RANGE_BLIP_FOR_CONTACT_POINT, + COMMAND_ADD_SHORT_RANGE_SPRITE_BLIP_FOR_CONTACT_POINT, + COMMAND_IS_CHAR_STUCK, + COMMAND_SET_ALL_TAXIS_HAVE_NITRO, + COMMAND_SET_CHAR_STOP_SHOOT_DONT_SEEK_ENTITY, + COMMAND_FREEZE_CAR_POSITION_AND_DONT_LOAD_COLLISION, + COMMAND_FREEZE_CHAR_POSITION_AND_DONT_LOAD_COLLISION, + COMMAND_FREEZE_OBJECT_POSITION_AND_DONT_LOAD_COLLISION, + COMMAND_SET_FADE_AND_JUMPCUT_AFTER_RC_EXPLOSION, + COMMAND_REGISTER_VIGILANTE_LEVEL, + COMMAND_CLEAR_ALL_CHAR_ANIMS, + COMMAND_SET_MAXIMUM_NUMBER_OF_CARS_IN_GARAGE, + COMMAND_WANTED_STARS_ARE_FLASHING, + COMMAND_SET_ALLOW_HURRICANES, + COMMAND_PLAY_ANNOUNCEMENT, + COMMAND_SET_PLAYER_IS_IN_STADIUM, + COMMAND_GET_BUS_FARES_COLLECTED_BY_PLAYER, + COMMAND_SET_CHAR_OBJ_BUY_ICE_CREAM, + COMMAND_DISPLAY_RADAR, + COMMAND_REGISTER_BEST_POSITION, + COMMAND_IS_PLAYER_IN_INFO_ZONE, + COMMAND_CLEAR_CHAR_ICE_CREAM_PURCHASE, + COMMAND_IS_IN_CAR_FIRE_BUTTON_PRESSED, + COMMAND_HAS_CHAR_ATTEMPTED_ATTRACTOR, + COMMAND_SET_LOAD_COLLISION_FOR_CAR_FLAG, + COMMAND_SET_LOAD_COLLISION_FOR_CHAR_FLAG, + COMMAND_SET_LOAD_COLLISION_FOR_OBJECT_FLAG, + COMMAND_ADD_BIG_GUN_FLASH, + COMMAND_HAS_CHAR_BOUGHT_ICE_CREAM, + COMMAND_GET_PROGRESS_PERCENTAGE, + COMMAND_SET_SHORTCUT_PICKUP_POINT, + COMMAND_SET_SHORTCUT_DROPOFF_POINT_FOR_MISSION, + COMMAND_GET_RANDOM_ICE_CREAM_CUSTOMER_IN_AREA, + COMMAND_GET_RANDOM_ICE_CREAM_CUSTOMER_IN_ZONE, + COMMAND_UNLOCK_ALL_CAR_DOORS_IN_AREA, + COMMAND_SET_GANG_ATTACK_PLAYER_WITH_COPS, + COMMAND_SET_CHAR_FRIGHTENED_IN_JACKED_CAR, + COMMAND_SET_VEHICLE_TO_FADE_IN, + COMMAND_REGISTER_ODDJOB_MISSION_PASSED, + COMMAND_IS_PLAYER_IN_SHORTCUT_TAXI, + COMMAND_IS_CHAR_DUCKING, + COMMAND_CREATE_DUST_EFFECT_FOR_CUTSCENE_HELI, + COMMAND_REGISTER_FIRE_LEVEL, + COMMAND_IS_AUSTRALIAN_GAME, + COMMAND_DISARM_CAR_BOMB, +#if (defined GTAVC_JP_PATCH || defined SUPPORT_JAPANESE_SCRIPT) + COMMAND_IS_JAPANESE_GAME, +#elif (!defined GTA_PS2) + COMMAND_SET_ONSCREEN_COUNTER_FLASH_WHEN_FIRST_DISPLAYED, +#endif +#if (defined GTA_PC && !defined GTAVC_JP_PATCH || defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT || defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) + COMMAND_SHUFFLE_CARD_DECKS, + COMMAND_FETCH_NEXT_CARD, + COMMAND_GET_OBJECT_VELOCITY, + COMMAND_IS_DEBUG_CAMERA_ON, + COMMAND_ADD_TO_OBJECT_ROTATION_VELOCITY, + COMMAND_SET_OBJECT_ROTATION_VELOCITY, + COMMAND_IS_OBJECT_STATIC, + COMMAND_GET_ANGLE_BETWEEN_2D_VECTORS, + COMMAND_DO_2D_RECTANGLES_COLLIDE, + COMMAND_GET_OBJECT_ROTATION_VELOCITY, + COMMAND_ADD_VELOCITY_RELATIVE_TO_OBJECT_VELOCITY, + COMMAND_GET_OBJECT_SPEED, +#endif +#if (defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT) + COMMAND_MARK_CUTSCENE_START, + COMMAND_MARK_CUTSCENE_END, + COMMAND_CUTSCENE_SCROLL, +#elif (defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) + COMMAND_IS_MISSION_SKIP, + COMMAND_SET_IN_AMMUNATION, + COMMAND_DO_SAVE_GAME, + COMMAND_IS_RETRY, + COMMAND_DUMMY, + COMMAND_MARK_CUTSCENE_START, + COMMAND_MARK_CUTSCENE_END, + COMMAND_CUTSCENE_SCROLL, +#endif +#ifdef USE_ADVANCED_SCRIPT_DEBUG_OUTPUT + LAST_SCRIPT_COMMAND +#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/miami/control/ScriptDebug.cpp b/src/miami/control/ScriptDebug.cpp new file mode 100644 index 00000000..1ca5f588 --- /dev/null +++ b/src/miami/control/ScriptDebug.cpp @@ -0,0 +1,1765 @@ +#include "common.h" + +#include "Script.h" +#include "ScriptCommands.h" + +#include "Debug.h" +#include "FileMgr.h" +#include "GameLogic.h" +#ifdef MISSION_REPLAY +#include "GenericGameStorage.h" +#endif +#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(ARGTYPE_INT,), 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,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_LVAR_INT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_VAR_FLOAT, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, 0, " ABS"), + REGISTER_COMMAND(COMMAND_ABS_LVAR_FLOAT, INPUT_ARGUMENTS(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,), false, -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, ""), +#ifdef SUPPORT_GINPUT_SCRIPT + REGISTER_COMMAND(COMMAND_HAS_PAD_IN_HANDS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), +#else + REGISTER_COMMAND(COMMAND_ORDER_CHAR_TO_BACKDOOR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), +#endif + 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(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_ONSCREEN_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT, 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,), 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, 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_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_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(), 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, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CHAR_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), 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(), false, -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(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_BEEN_DAMAGED_BY_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -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(), false, -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(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_DRUG_PLANE_BEEN_SHOT_DOWN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -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(), true, -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_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_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MISSION_AUDIO_LOADED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_PLAY_MISSION_AUDIO, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_MISSION_AUDIO_FINISHED, INPUT_ARGUMENTS(ARGTYPE_INT,), 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_INT, 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_SET_CHAR_OBJ_LEAVE_ANY_CAR, 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,), true, -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_SET_DRUNK_INPUT_DELAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_MONEY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_INCREASE_CHAR_MONEY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OFFSET_FROM_OBJECT_IN_WORLD_COORDS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), 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_GET_OFFSET_FROM_CAR_IN_WORLD_COORDS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), 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(ARGTYPE_INT,), 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, ""), + 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_TEMP_ACTION, INPUT_ARGUMENTS(ARGTYPE_INT, 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(), true, -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(), true, -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, ""), + REGISTER_COMMAND(COMMAND_SET_THREAT_REACTION_RANGE_MULTIPLIER, INPUT_ARGUMENTS(ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_CEASE_ATTACK_TIMER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_REMOTE_CONTROLLED_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PC_VERSION, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REPLAY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_REPLAY_PLAYING, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_MODEL_AVAILABLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SHUT_CHAR_UP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ENABLE_RC_DETONATE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_RANDOM_ROUTE_SEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_ANY_PICKUP_AT_COORDS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_FIRST_PICKUP_COORDS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NEXT_PICKUP_COORDS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_ALL_CHAR_WEAPONS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PLAYER_GOT_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_GOT_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_FACING_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TANK_DETONATE_CARS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_POSITION_OF_ANALOGUE_STICKS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_ON_FIRE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_TYRE_BURST, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_DRIVE_STRAIGHT_AHEAD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_WAIT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_STANDING_ON_A_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_FOOT_DOWN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_FOOT_DOWN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_INITIALISE_OBJECT_PATH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_START_OBJECT_ON_PATH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_PATH_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_PATH_POSITION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OBJECT_DISTANCE_ALONG_PATH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_OBJECT_PATH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HELI_GOTO_COORDS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_EQUAL_TO_CONSTANT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_EQUAL_TO_CONSTANT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DEAD_CHAR_PICKUP_COORDS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_PROTECTION_PICKUP, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_ANY_BOAT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANY_BOAT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_ANY_HELI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANY_HELI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_ANY_PLANE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_ANY_PLANE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_WATER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_VAR_INT_TO_CONSTANT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_SET_LVAR_INT_TO_CONSTANT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, 0, " ="), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_THAN_CONSTANT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_THAN_CONSTANT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_CONSTANT_GREATER_THAN_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_CONSTANT_GREATER_THAN_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >"), + REGISTER_COMMAND(COMMAND_IS_INT_VAR_GREATER_OR_EQUAL_TO_CONSTANT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_INT_LVAR_GREATER_OR_EQUAL_TO_CONSTANT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_VAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_IS_CONSTANT_GREATER_OR_EQUAL_TO_INT_LVAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, 0, " >="), + REGISTER_COMMAND(COMMAND_GET_CHAR_WEAPON_IN_SLOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_STRAIGHT_ROAD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_FORWARD_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_AREA_VISIBLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CUTSCENE_ANIM_TO_LOOP, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_CAR_AS_CONVOY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_RESET_HAVOC_CAUSED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_HAVOC_CAUSED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_SCRIPT_ROADBLOCK, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_ALL_SCRIPT_ROADBLOCKS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_WALK_TO_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PICKUP_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OFFSET_FROM_CHAR_IN_WORLD_COORDS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_BEEN_PHOTOGRAPHED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_AIM_GUN_AT_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_SECURITY_CAMERA, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_FLYING_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_FLYING_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_SONY_CD_BEEN_READ, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NUMBER_OF_SONY_CDS_READ, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SHORT_RANGE_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_ADD_SHORT_RANGE_BLIP_FOR_COORD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SHORT_RANGE_SPRITE_BLIP_FOR_COORD, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MONEY_SPENT_ON_CLOTHES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_HELI_ORIENTATION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_HELI_ORIENTATION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLANE_GOTO_COORDS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NTH_CLOSEST_CAR_NODE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NTH_CLOSEST_CHAR_NODE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DRAW_WEAPONSHOP_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_SET_ENABLE_RC_DETONATE_ON_CONTACT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_FREEZE_CHAR_POSITION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_DROWNS_IN_WATER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_RECORDS_COLLISIONS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_OBJECT_COLLIDED_WITH_ANYTHING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_RC_BUGGY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_PHOTOGRAPH_BEEN_TAKEN, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CHAR_ARMOUR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_ARMOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_HELI_STABILISER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_STRAIGHT_LINE_DISTANCE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_POP_CAR_BOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SHUT_PLAYER_UP, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_MOOD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REQUEST_COLLISION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_OBJECT_2D, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_LOCATE_OBJECT_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_OBJECT_IN_WATER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_STEAL_ANY_CAR_EVEN_MISSION_CAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_OBJECT_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_OBJECT_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_SET_CHAR_CROUCH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ZONE_CIVILIAN_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,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REQUEST_ANIMATION, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_ANIMATION_LOADED, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_ANIMATION, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_WAITING_FOR_WORLD_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_WAITING_FOR_WORLD_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_OBJECT_WAITING_FOR_WORLD_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_SHUFFLE_INTO_DRIVERS_SEAT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ATTACH_CHAR_TO_OBJECT, 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_SET_CHAR_AS_PLAYER_FRIEND, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_NTH_ONSCREEN_COUNTER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_NTH_ONSCREEN_COUNTER_WITH_STRING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SET_PIECE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, 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_SET_EXTRA_COLOURS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_EXTRA_COLOURS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLOSE_CAR_BOOT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_WHEELIE_STATS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISARM_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_BURST_CAR_TYRE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_OBJ_NO_OBJ, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_WEARING, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_CAN_DO_DRIVE_BY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_SPRINT_TO_COORD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_SWAT_ROPE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FIRST_PERSON_CONTROL_CAMERA, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_NEAREST_TYRE_TO_POINT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAR_MODEL_COMPONENTS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SWITCH_LIFT_CAMERA, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLOSE_ALL_CAR_DOORS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DISTANCE_BETWEEN_COORDS_2D, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_DISTANCE_BETWEEN_COORDS_3D, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_POP_CAR_BOOT_USING_PHYSICS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FIRST_PERSON_WEAPON_CAMERA, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_LEAVING_VEHICLE_TO_DIE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SORT_OUT_OBJECT_COLLISION_WITH_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_MAX_WANTED_LEVEL, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_WANDER_PATH_CLEAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_HELP_WITH_NUMBER, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_HELP_FOREVER, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PRINT_HELP_FOREVER_WITH_NUMBER, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_CAN_BE_DAMAGED_BY_MEMBERS_OF_GANG, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_AND_LAUNCH_MISSION_EXCLUSIVE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_MISSION_AUDIO_PLAYING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_LOCKED_PROPERTY_PICKUP, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_FORSALE_PROPERTY_PICKUP, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_FREEZE_CAR_POSITION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_BEEN_DAMAGED_BY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_BEEN_DAMAGED_BY_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CAR_BEEN_DAMAGED_BY_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RADIO_CHANNEL, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_TEXT_WITH_3_NUMBERS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_DROWNING_IN_WATER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_DROWNING_IN_WATER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DISABLE_CUTSCENE_SHADOWS, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_GLASS_BEEN_SHATTERED_NEARBY, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ATTACH_CUTSCENE_OBJECT_TO_BONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ATTACH_CUTSCENE_OBJECT_TO_COMPONENT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_STAY_IN_CAR_WHEN_JACKED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_MISSION_AUDIO_LOADING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MONEY_SPENT_ON_WEAPONS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MONEY_SPENT_ON_PROPERTY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MONEY_SPENT_ON_AUTO_PAINTING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_ANSWERING_MOBILE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_DRUNKENNESS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_DRUNKENNESS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_DRUG_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PLAYER_DRUG_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_LOAN_SHARK_VISITS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_STORES_KNOCKED_OFF, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MOVIE_STUNTS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_NUMBER_OF_ASSASSINATIONS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PIZZAS_DELIVERED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_GARBAGE_PICKUPS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_ICE_CREAMS_SOLD, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TOP_SHOOTING_RANGE_SCORE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SHOOTING_RANGE_RANK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MONEY_SPENT_ON_GAMBLING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_MONEY_WON_ON_GAMBLING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_LARGEST_GAMBLING_WIN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_IN_PLAYERS_GROUP_CAN_FIGHT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CHAR_WAIT_STATE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_CAR_OF_TYPE_IN_AREA_NO_SAVE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CAN_BURST_CAR_TYRES, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_AUTO_AIM, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FIRE_HUNTER_GUN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PROPERTY_AS_OWNED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BLOOD_RING_KILLS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_LONGEST_TIME_IN_BLOOD_RING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REMOVE_EVERYTHING_FOR_HUGE_CUTSCENE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_TOUCHING_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_TOUCHING_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CHECK_FOR_PED_MODEL_AROUND_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CHAR_FOLLOW_PATH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_CAN_BE_SHOT_IN_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ATTACH_CUTSCENE_OBJECT_TO_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_MISSION_TEXT, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_TONIGHTS_EVENT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CHAR_LAST_DAMAGE_ENTITY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CAR_LAST_DAMAGE_ENTITY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FREEZE_OBJECT_POSITION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_HAS_MET_DEBBIE_HARRY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_RIOT_INTENSITY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CAR_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_CAR_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_REMOVE_WEAPON_FROM_CHAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_UP_TAXI_SHORTCUT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_TAXI_SHORTCUT, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_GOTO_CAR_ON_FOOT, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_CLOSEST_WATER_NODE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_PORN_LEAFLET_TO_RUBBISH, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_CLOTHES_PICKUP, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CHANGE_BLIP_THRESHOLD, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_PLAYER_FIRE_PROOF, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_INCREASE_PLAYER_MAX_HEALTH, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_INCREASE_PLAYER_MAX_ARMOUR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_RANDOM_CHAR_AS_DRIVER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_RANDOM_CHAR_AS_PASSENGER, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_IGNORE_THREATS_BEHIND_OBJECTS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ENSURE_PLAYER_HAS_DRIVE_BY_WEAPON, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MAKE_HELI_COME_CRASHING_DOWN, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_EXPLOSION_NO_SOUND, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_AREA_VISIBLE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WAS_VEHICLE_EVER_POLICE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_NEVER_TARGETTED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_LOAD_UNCOMPRESSED_ANIM, INPUT_ARGUMENTS(ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WAS_CUTSCENE_SKIPPED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_CROUCH_WHEN_THREATENED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_IN_ANY_POLICE_VEHICLE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DOES_CHAR_EXIST, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DOES_VEHICLE_EXIST, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SHORT_RANGE_BLIP_FOR_CONTACT_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_SHORT_RANGE_SPRITE_BLIP_FOR_CONTACT_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_STUCK, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ALL_TAXIS_HAVE_NITRO, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_STOP_SHOOT_DONT_SEEK_ENTITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FREEZE_CAR_POSITION_AND_DONT_LOAD_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FREEZE_CHAR_POSITION_AND_DONT_LOAD_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FREEZE_OBJECT_POSITION_AND_DONT_LOAD_COLLISION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_FADE_AND_JUMPCUT_AFTER_RC_EXPLOSION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_VIGILANTE_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_ALL_CHAR_ANIMS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_MAXIMUM_NUMBER_OF_CARS_IN_GARAGE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_WANTED_STARS_ARE_FLASHING, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_ALLOW_HURRICANES, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_PLAY_ANNOUNCEMENT, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_PLAYER_IS_IN_STADIUM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_BUS_FARES_COLLECTED_BY_PLAYER, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_OBJ_BUY_ICE_CREAM, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DISPLAY_RADAR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_BEST_POSITION, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_INFO_ZONE, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_STRING,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CLEAR_CHAR_ICE_CREAM_PURCHASE, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_IN_CAR_FIRE_BUTTON_PRESSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_ATTEMPTED_ATTRACTOR, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_SET_LOAD_COLLISION_FOR_CAR_FLAG, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_LOAD_COLLISION_FOR_CHAR_FLAG, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_LOAD_COLLISION_FOR_OBJECT_FLAG, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_BIG_GUN_FLASH, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_HAS_CHAR_BOUGHT_ICE_CREAM, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_PROGRESS_PERCENTAGE, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_SHORTCUT_PICKUP_POINT, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_SHORTCUT_DROPOFF_POINT_FOR_MISSION, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_ICE_CREAM_CUSTOMER_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_RANDOM_ICE_CREAM_CUSTOMER_IN_ZONE, INPUT_ARGUMENTS(ARGTYPE_STRING, ARGTYPE_INT, ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_UNLOCK_ALL_CAR_DOORS_IN_AREA, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_GANG_ATTACK_PLAYER_WITH_COPS, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_CHAR_FRIGHTENED_IN_JACKED_CAR, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_VEHICLE_TO_FADE_IN, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_ODDJOB_MISSION_PASSED, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_PLAYER_IN_SHORTCUT_TAXI, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_IS_CHAR_DUCKING, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_CREATE_DUST_EFFECT_FOR_CUTSCENE_HELI, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_REGISTER_FIRE_LEVEL, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_AUSTRALIAN_GAME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_DISARM_CAR_BOMB, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), +#if (defined GTAVC_JP_PATCH || defined SUPPORT_JAPANESE_SCRIPT) + REGISTER_COMMAND(COMMAND_IS_JAPANESE_GAME, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), +#elif (!defined GTA_PS2) + REGISTER_COMMAND(COMMAND_SET_ONSCREEN_COUNTER_FLASH_WHEN_FIRST_DISPLAYED, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), +#endif +#if (defined GTA_PC && !defined GTAVC_JP_PATCH || defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT || defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) + REGISTER_COMMAND(COMMAND_SHUFFLE_CARD_DECKS, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_FETCH_NEXT_CARD, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OBJECT_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_DEBUG_CAMERA_ON, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_TO_OBJECT_ROTATION_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_OBJECT_ROTATION_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_INT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_OBJECT_STATIC, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_ANGLE_BETWEEN_2D_VECTORS, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DO_2D_RECTANGLES_COLLIDE, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), true, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OBJECT_ROTATION_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_ADD_VELOCITY_RELATIVE_TO_OBJECT_VELOCITY, INPUT_ARGUMENTS(ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT, ARGTYPE_FLOAT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_GET_OBJECT_SPEED, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(ARGTYPE_FLOAT,), false, -1, ""), +#endif +#if (defined GTA_XBOX || defined SUPPORT_XBOX_SCRIPT) + REGISTER_COMMAND(COMMAND_MARK_CUTSCENE_START, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_CUTSCENE_END, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CUTSCENE_SCROLL, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), +#elif (defined GTA_MOBILE || defined SUPPORT_MOBILE_SCRIPT) + REGISTER_COMMAND(COMMAND_IS_MISSION_SKIP, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_SET_IN_AMMUNATION, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_DO_SAVE_GAME, INPUT_ARGUMENTS(ARGTYPE_INT,), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_IS_RETRY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(ARGTYPE_INT,), false, -1, ""), + REGISTER_COMMAND(COMMAND_DUMMY, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_CUTSCENE_START, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_MARK_CUTSCENE_END, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), + REGISTER_COMMAND(COMMAND_CUTSCENE_SCROLL, INPUT_ARGUMENTS(), OUTPUT_ARGUMENTS(), false, -1, ""), +#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 +} + +#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 USE_ADVANCED_SCRIPT_DEBUG_OUTPUT +int CRunningScript::CollectParameterForDebug(char* buf, bool& var) +{ + uint16 varIndex; + char tmpstr[24]; + var = false; + switch (CTheScripts::Read1ByteFromScript(&m_nIp)) + { + case ARGUMENT_INT32: + case ARGUMENT_FLOAT: + 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); + 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 + +#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(); + } + + if (CTheScripts::NumberOfExclusiveMissionScripts > 0 && mission <= UINT16_MAX - 2) + return; + +#ifdef MISSION_REPLAY + missionRetryScriptIndex = mission; +#ifdef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT + if (CTheScripts::MissionSupportsMissionReplay(missionRetryScriptIndex)) { + SaveGameForPause(SAVE_TYPE_QUICKSAVE_FOR_SCRIPT); + } +#endif +#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; + CGameLogic::ClearShortCut(); +} +#endif diff --git a/src/miami/control/SetPieces.cpp b/src/miami/control/SetPieces.cpp new file mode 100644 index 00000000..5edcd335 --- /dev/null +++ b/src/miami/control/SetPieces.cpp @@ -0,0 +1,325 @@ +#include "common.h" + +#include "SetPieces.h" +#include "Automobile.h" +#include "CarAI.h" +#include "CopPed.h" +#include "GenericGameStorage.h" +#include "PlayerPed.h" +#include "Timer.h" +#include "Vehicle.h" +#include "Wanted.h" +#include "World.h" +#include "VarConsole.h" +#include "SaveBuf.h" + +#define TIME_BETWEEN_SETPIECE_SPAWNS 20000 + +bool CSetPieces::bDebug; +uint32 CSetPieces::NumSetPieces; +CSetPiece CSetPieces::aSetPieces[NUM_SETPIECES]; + +void CSetPieces::Init(void) +{ + bDebug = false; + NumSetPieces = 0; +#ifndef MASTER + VarConsole.Add("Show set pieces", &bDebug, true); +#endif +} + +void CSetPieces::AddOne(uint8 type, CVector2D vTriggerInf, CVector2D vTriggerSup, CVector2D vSpawn1, CVector2D vTarget1, CVector2D vSpawn2, CVector2D vTarget2) +{ + if (NumSetPieces >= NUM_SETPIECES) + return; + aSetPieces[NumSetPieces].m_nType = type; + aSetPieces[NumSetPieces].m_vTriggerInf.x = Min(vTriggerInf.x, vTriggerSup.x); + aSetPieces[NumSetPieces].m_vTriggerInf.y = Min(vTriggerInf.y, vTriggerSup.y); + aSetPieces[NumSetPieces].m_vTriggerSup.x = Max(vTriggerInf.x, vTriggerSup.x); + aSetPieces[NumSetPieces].m_vTriggerSup.y = Max(vTriggerInf.y, vTriggerSup.y); + aSetPieces[NumSetPieces].m_vSpawn1 = vSpawn1; + aSetPieces[NumSetPieces].m_vSpawn2 = vSpawn2; + aSetPieces[NumSetPieces].m_vTarget1 = vTarget1; + aSetPieces[NumSetPieces].m_vTarget2 = vTarget2; + ++NumSetPieces; +} + +void CSetPieces::Update(void) +{ + int nFirst = NumSetPieces * (CTimer::GetFrameCounter() % 8) / 8; + int nLast = NumSetPieces * (CTimer::GetFrameCounter() % 8 + 1) / 8; + for (int i = nFirst; i < nLast; i++) + aSetPieces[i].Update(); +#ifndef MASTER + // TODO: debug code from mobile +#endif // !MASTER +} + +void CSetPieces::Save(uint8* buf, uint32* size) +{ +INITSAVEBUF + WriteSaveBuf(buf, NumSetPieces); + for (int i = 0; i < NUM_SETPIECES; i++) + WriteSaveBuf(buf, aSetPieces[i]); + *size = sizeof(NumSetPieces) + NUM_SETPIECES * sizeof(CSetPiece); +VALIDATESAVEBUF(*size) +} + +void CSetPieces::Load(uint8* buf, uint32 size) +{ +INITSAVEBUF + ReadSaveBuf(&NumSetPieces, buf); + for (int i = 0; i < NUM_SETPIECES; i++) + ReadSaveBuf(&aSetPieces[i], buf); +VALIDATESAVEBUF(size) +} + +void CSetPiece::Update(void) +{ + if (m_nLastTimeCreated != 0 && CTimer::GetTimeInMilliseconds() <= m_nLastTimeCreated + TIME_BETWEEN_SETPIECE_SPAWNS) + return; + CVector pos = FindPlayerCoors(); + if (pos.x < m_vTriggerInf.x || pos.x > m_vTriggerSup.x || + pos.y < m_vTriggerInf.y || pos.y > m_vTriggerSup.y) + return; + switch (m_nType) { + case SETPIECE_TWOCOPCARSINALLEY: + { + if (FindPlayerPed()->m_pWanted->GetWantedLevel() < 1 || FindPlayerVehicle()) + return; + CVehicle* pVehicle1 = TryToGenerateCopCar(m_vSpawn1, m_vTarget1); + if (!pVehicle1) + return; + CVehicle* pVehicle2 = TryToGenerateCopCar(m_vSpawn2, m_vTarget2); + if (!pVehicle2) { + CWorld::Remove(pVehicle1); + delete pVehicle1; + return; + } + pVehicle1->SetStatus(STATUS_PHYSICS); + pVehicle1->AutoPilot.m_fMaxTrafficSpeed = pVehicle1->AutoPilot.m_nCruiseSpeed = 4; + pVehicle1->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_SLOW_DOWN_FOR_CARS; + pVehicle1->AutoPilot.m_nCarMission = MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_1; + pVehicle1->AutoPilot.m_vecDestinationCoors.x = m_vTarget1.x; + pVehicle1->AutoPilot.m_vecDestinationCoors.y = m_vTarget1.y; + pVehicle1->AutoPilot.m_vecDestinationCoors.z = 0.0f; + pVehicle1->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 25000; + CCarAI::AddPoliceCarOccupants(pVehicle1); + pVehicle2->SetStatus(STATUS_PHYSICS); + pVehicle2->AutoPilot.m_fMaxTrafficSpeed = pVehicle2->AutoPilot.m_nCruiseSpeed = 4; + pVehicle2->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_SLOW_DOWN_FOR_CARS; + pVehicle2->AutoPilot.m_nCarMission = MISSION_SLOWLY_DRIVE_TOWARDS_PLAYER_1; + pVehicle2->AutoPilot.m_vecDestinationCoors.x = m_vTarget2.x; + pVehicle2->AutoPilot.m_vecDestinationCoors.y = m_vTarget2.y; + pVehicle2->AutoPilot.m_vecDestinationCoors.z = 0.0f; + pVehicle2->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 25000; + CCarAI::AddPoliceCarOccupants(pVehicle2); + m_nLastTimeCreated = CTimer::GetTimeInMilliseconds(); + break; + } + case SETPIECE_CARBLOCKINGPLAYERFROMSIDE: + { + if (FindPlayerPed()->m_pWanted->GetWantedLevel() < 2) + return; + if (!FindPlayerVehicle()) + return; + if (DotProduct2D(FindPlayerSpeed(), (CVector2D)FindPlayerCoors() - m_vSpawn1) >= 0.0f) + return; + CVehicle* pVehicle1 = TryToGenerateCopCar(m_vSpawn1, m_vTarget1); + if (!pVehicle1) + return; + pVehicle1->SetStatus(STATUS_PHYSICS); + pVehicle1->AutoPilot.m_fMaxTrafficSpeed = pVehicle1->AutoPilot.m_nCruiseSpeed = 16; + pVehicle1->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_PLOUGH_THROUGH; + pVehicle1->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_FORWARDANDBACK; + pVehicle1->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + pVehicle1->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 100; + pVehicle1->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 10000; + pVehicle1->SetMoveSpeed(2.0f * pVehicle1->GetForward() / 3.0f); + CCarAI::AddPoliceCarOccupants(pVehicle1); + m_nLastTimeCreated = CTimer::GetTimeInMilliseconds(); + return; + } + case SETPIECE_CARRAMMINGPLAYERFROMSIDE: + { + if (FindPlayerPed()->m_pWanted->GetWantedLevel() < 2) + return; + if (!FindPlayerVehicle()) + return; + if (DotProduct2D(FindPlayerSpeed(), (CVector2D)FindPlayerCoors() - m_vSpawn1) >= 0.0f) + return; + CVehicle* pVehicle1 = TryToGenerateCopCar(m_vSpawn1, m_vTarget1); + if (!pVehicle1) + return; + pVehicle1->SetStatus(STATUS_PHYSICS); + pVehicle1->AutoPilot.m_fMaxTrafficSpeed = pVehicle1->AutoPilot.m_nCruiseSpeed = 16; + pVehicle1->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle1->AutoPilot.m_nCarMission = MISSION_RAMCAR_CLOSE; + pVehicle1->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + pVehicle1->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 100; + pVehicle1->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 10000; + pVehicle1->SetMoveSpeed(2.0f * pVehicle1->GetForward() / 3.0f); + CCarAI::AddPoliceCarOccupants(pVehicle1); + m_nLastTimeCreated = CTimer::GetTimeInMilliseconds(); + return; + } + case SETPIECE_CREATECOPPERONFOOT: + { + if (FindPlayerPed()->m_pWanted->GetWantedLevel() < 1 || FindPlayerVehicle()) + return; + CCopPed* pCop = TryToGenerateCopPed(m_vSpawn1); + if (!pCop) + return; + float z = CWorld::FindGroundZForCoord(m_vTarget1.x, m_vTarget1.y); + pCop->bScriptObjectiveCompleted = false; + pCop->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, CVector(m_vTarget1.x, m_vTarget1.y, z)); + pCop->m_nExtendedRangeTimer = CTimer::GetTimeInMilliseconds() + 10000; + m_nLastTimeCreated = CTimer::GetTimeInMilliseconds(); + return; + } + case SETPIECE_CREATETWOCOPPERSONFOOT: + { + if (FindPlayerPed()->m_pWanted->GetWantedLevel() < 1 || FindPlayerVehicle()) + return; + CCopPed* pCop = TryToGenerateCopPed(m_vSpawn1); + if (!pCop) + return; + float z = CWorld::FindGroundZForCoord(m_vTarget1.x, m_vTarget1.y); + pCop->bScriptObjectiveCompleted = false; + pCop->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, CVector(m_vTarget1.x, m_vTarget1.y, z)); + pCop->m_nExtendedRangeTimer = CTimer::GetTimeInMilliseconds() + 10000; + CCopPed* pCop2 = TryToGenerateCopPed(m_vSpawn2); + if (!pCop2) { + CWorld::Remove(pCop); + delete pCop; + return; + } + z = CWorld::FindGroundZForCoord(m_vTarget2.x, m_vTarget2.y); + pCop2->bScriptObjectiveCompleted = false; + pCop2->SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, CVector(m_vTarget2.x, m_vTarget2.y, z)); + pCop2->m_nExtendedRangeTimer = CTimer::GetTimeInMilliseconds() + 10000; + m_nLastTimeCreated = CTimer::GetTimeInMilliseconds(); + return; + } + case SETPIECE_TWOCARSBLOCKINGPLAYERFROMSIDE: + { + if (FindPlayerPed()->m_pWanted->GetWantedLevel() < 2) + return; + if (!FindPlayerVehicle()) + return; + if (DotProduct2D(FindPlayerSpeed(), (CVector2D)FindPlayerCoors() - m_vSpawn1) >= 0.0f) + return; + CVehicle* pVehicle1 = TryToGenerateCopCar(m_vSpawn1, m_vTarget1); + if (!pVehicle1) + return; + pVehicle1->SetStatus(STATUS_PHYSICS); + pVehicle1->AutoPilot.m_fMaxTrafficSpeed = pVehicle1->AutoPilot.m_nCruiseSpeed = 16; + pVehicle1->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_PLOUGH_THROUGH; + pVehicle1->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_FORWARDANDBACK; + pVehicle1->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + pVehicle1->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 100; + pVehicle1->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 10000; + pVehicle1->SetMoveSpeed(2.0f * pVehicle1->GetForward() / 3.0f); + CCarAI::AddPoliceCarOccupants(pVehicle1); + CVehicle* pVehicle2 = TryToGenerateCopCar(m_vSpawn2, m_vTarget2); + if (!pVehicle2) { + CWorld::Remove(pVehicle1); + delete pVehicle1; + return; + } + pVehicle2->SetStatus(STATUS_PHYSICS); + pVehicle2->AutoPilot.m_fMaxTrafficSpeed = pVehicle1->AutoPilot.m_nCruiseSpeed = 16; + pVehicle2->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_PLOUGH_THROUGH; + pVehicle2->AutoPilot.m_nCarMission = MISSION_BLOCKPLAYER_FORWARDANDBACK; + pVehicle2->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + pVehicle2->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 100; + pVehicle2->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 10000; + pVehicle2->SetMoveSpeed(2.0f * pVehicle2->GetForward() / 3.0f); + CCarAI::AddPoliceCarOccupants(pVehicle2); + m_nLastTimeCreated = CTimer::GetTimeInMilliseconds(); + return; + } + case SETPIECE_TWOCARSRAMMINGPLAYERFROMSIDE: + { + if (FindPlayerPed()->m_pWanted->GetWantedLevel() < 2) + return; + if (!FindPlayerVehicle()) + return; + if (DotProduct2D(FindPlayerSpeed(), (CVector2D)FindPlayerCoors() - m_vSpawn1) >= 0.0f) + return; + CVehicle* pVehicle1 = TryToGenerateCopCar(m_vSpawn1, m_vTarget1); + if (!pVehicle1) + return; + pVehicle1->SetStatus(STATUS_PHYSICS); + pVehicle1->AutoPilot.m_fMaxTrafficSpeed = pVehicle1->AutoPilot.m_nCruiseSpeed = 16; + pVehicle1->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle1->AutoPilot.m_nCarMission = MISSION_RAMCAR_CLOSE; + pVehicle1->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + pVehicle1->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 100; + pVehicle1->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 10000; + pVehicle1->SetMoveSpeed(2.0f * pVehicle1->GetForward() / 3.0f); + CCarAI::AddPoliceCarOccupants(pVehicle1); + CVehicle* pVehicle2 = TryToGenerateCopCar(m_vSpawn2, m_vTarget2); + if (!pVehicle2) { + CWorld::Remove(pVehicle1); + delete pVehicle1; + return; + } + pVehicle2->SetStatus(STATUS_PHYSICS); + pVehicle2->AutoPilot.m_fMaxTrafficSpeed = pVehicle2->AutoPilot.m_nCruiseSpeed = 16; + pVehicle2->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_AVOID_CARS; + pVehicle2->AutoPilot.m_nCarMission = MISSION_RAMCAR_CLOSE; + pVehicle2->AutoPilot.m_nTempAction = TEMPACT_GOFORWARD; + pVehicle2->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 100; + pVehicle2->m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 10000; + pVehicle2->SetMoveSpeed(2.0f * pVehicle2->GetForward() / 3.0f); + CCarAI::AddPoliceCarOccupants(pVehicle2); + m_nLastTimeCreated = CTimer::GetTimeInMilliseconds(); + return; + } + } +} + +CVehicle* CSetPiece::TryToGenerateCopCar(CVector2D vSpawn, CVector2D vTarget) +{ + CVehicle* pVehicle = new CAutomobile(MI_POLICE, RANDOM_VEHICLE); + CVector pos(vSpawn.x, vSpawn.y, 1000.0f); + CColPoint point; + CEntity* pEntity; + if (CWorld::ProcessVerticalLine(pos, -1000.0f, point, pEntity, true, false, false, false, true, false, nil)) + pos.z = point.point.z + pVehicle->GetHeightAboveRoad(); + CVector vDirection(vTarget.x - vSpawn.x, vTarget.y - vSpawn.y, 0.0f); + vDirection.Normalise(); + pVehicle->GetForward() = CVector(vDirection.x, vDirection.y, 0.0f); + pVehicle->GetRight() = CVector(vDirection.y, -vDirection.x, 0.0f); + pVehicle->GetUp() = CVector(0.0f, 0.0f, 1.0f); + pVehicle->SetPosition(pos); + int16 total; + CWorld::FindObjectsKindaColliding(pos, pVehicle->GetColModel()->spheres->radius, false, &total, 16, nil, false, true, true, false, false); + if (total != 0) { + delete pVehicle; + return nil; + } + pVehicle->ChangeLawEnforcerState(true); + CWorld::Add(pVehicle); + return pVehicle; +} + +CCopPed* CSetPiece::TryToGenerateCopPed(CVector2D vSpawn) +{ + CCopPed* pCop = new CCopPed(COP_STREET); + CVector pos(vSpawn.x, vSpawn.y, 1000.0f); + CColPoint point; + CEntity* pEntity; + if (CWorld::ProcessVerticalLine(pos, -1000.0f, point, pEntity, true, false, false, false, true, false, nil)) + pos.z = point.point.z + 0.9f; + pCop->SetPosition(pos); + int16 total; + CWorld::FindObjectsKindaColliding(pos, pCop->GetColModel()->spheres->radius, false, &total, 16, nil, false, true, true, false, false); + if (total != 0) { + delete pCop; + return nil; + } + CWorld::Add(pCop); + return pCop; +} \ No newline at end of file diff --git a/src/miami/control/SetPieces.h b/src/miami/control/SetPieces.h new file mode 100644 index 00000000..5c228d4c --- /dev/null +++ b/src/miami/control/SetPieces.h @@ -0,0 +1,48 @@ +#pragma once + +#include "config.h" + +class CVehicle; +class CCopPed; + +enum eSetPieceType +{ + SETPIECE_NONE = 0, + SETPIECE_TWOCOPCARSINALLEY, + SETPIECE_CARBLOCKINGPLAYERFROMSIDE, + SETPIECE_CARRAMMINGPLAYERFROMSIDE, + SETPIECE_CREATECOPPERONFOOT, + SETPIECE_CREATETWOCOPPERSONFOOT, + SETPIECE_TWOCARSBLOCKINGPLAYERFROMSIDE, + SETPIECE_TWOCARSRAMMINGPLAYERFROMSIDE +}; + +class CSetPiece +{ +public: + uint8 m_nType; + uint32 m_nLastTimeCreated; + CVector2D m_vTriggerInf; + CVector2D m_vTriggerSup; + CVector2D m_vSpawn1; + CVector2D m_vSpawn2; + CVector2D m_vTarget1; + CVector2D m_vTarget2; + + CVehicle* TryToGenerateCopCar(CVector2D, CVector2D); + CCopPed* TryToGenerateCopPed(CVector2D); + void Update(void); +}; + +class CSetPieces +{ + static bool bDebug; + static uint32 NumSetPieces; + static CSetPiece aSetPieces[NUM_SETPIECES]; +public: + static void Init(void); + static void AddOne(uint8 type, CVector2D, CVector2D, CVector2D, CVector2D, CVector2D, CVector2D); + static void Save(uint8*, uint32*); + static void Load(uint8*, uint32); + static void Update(void); +}; diff --git a/src/miami/control/TrafficLights.cpp b/src/miami/control/TrafficLights.cpp new file mode 100644 index 00000000..e484d3be --- /dev/null +++ b/src/miami/control/TrafficLights.cpp @@ -0,0 +1,536 @@ +#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" + +bool CTrafficLights::bGreenLightsCheat; + +void +CTrafficLights::DisplayActualLight(CEntity *ent) +{ + if(ent->GetUp().z < 0.96f || ent->bRenderDamaged) + return; + + int phase; + if(FindTrafficLightType(ent) == 1) + phase = LightForCars1_Visual(); + else + phase = LightForCars2_Visual(); + + int i, m = ent->GetModelIndex(); + if (MI_TRAFFICLIGHTS == m) { + 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: + r = 255; + g = 0; + pos1 = ent->GetMatrix() * CVector(x, yMax, zMax); + pos2 = ent->GetMatrix() * CVector(x, yMin, zMax); + id = 2; + break; + default: + r = 0; + g = 0; + pos1 = ent->GetMatrix() * CVector(x, yMax, (zMin + zMax) / 2.0f); + pos2 = ent->GetMatrix() * CVector(x, yMin, (zMin + zMax) / 2.0f); + id = -1; + break; + } + + if (CWeather::TrafficLightBrightness > 0.5f) + 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); + + if (CWeather::TrafficLightBrightness > 0.05f) + CShadows::StoreStaticShadow((uintptr)ent, + SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos1, + 8.0f, 0.0f, 0.0f, -8.0f, 128, + r * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 8.0f, + g * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 8.0f, + 0 * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 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); + } + else if (MI_TRAFFICLIGHTS_VERTICAL == m) { + 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; + uint8 r, g; + int id; + switch (phase) { + case CAR_LIGHTS_GREEN: + r = 0; + g = 255; + pos1 = ent->GetMatrix() * mi->Get2dEffect(2)->pos; + id = 0; + break; + case CAR_LIGHTS_YELLOW: + r = 255; + g = 128; + pos1 = ent->GetMatrix() * mi->Get2dEffect(1)->pos; + id = 1; + break; + case CAR_LIGHTS_RED: + r = 255; + g = 0; + pos1 = ent->GetMatrix() * mi->Get2dEffect(0)->pos; + id = 2; + break; + default: + r = 0; + g = 0; + pos1 = ent->GetMatrix() * mi->Get2dEffect(1)->pos; + id = -1; + break; + } + + CBrightLights::RegisterOne(pos1, ent->GetUp(), ent->GetRight(), CVector(0.0f, 0.0f, 0.0f), id + BRIGHTLIGHT_TRAFFIC_GREEN); + + if (CWeather::TrafficLightBrightness > 0.5f) + 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); + + if (CWeather::TrafficLightBrightness > 0.05f) + CShadows::StoreStaticShadow((uintptr)ent, + SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos1, + 8.0f, 0.0f, 0.0f, -8.0f, 128, + r * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 8.0f, + g * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 8.0f, + 0 * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 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 if (MI_TRAFFICLIGHTS_MIAMI == m || MI_TRAFFICLIGHTS_TWOVERTICAL == m) { + CBaseModelInfo* mi = CModelInfo::GetModelInfo(ent->GetModelIndex()); + CVector pos1, pos2; + uint8 r, g; + int id; + if (MI_TRAFFICLIGHTS_MIAMI == m) { + switch (phase) { + case CAR_LIGHTS_GREEN: + r = 0; + g = 255; + pos1 = ent->GetMatrix() * mi->Get2dEffect(4)->pos; + pos2 = ent->GetMatrix() * mi->Get2dEffect(5)->pos; + id = 0; + break; + case CAR_LIGHTS_YELLOW: + r = 255; + g = 128; + pos1 = ent->GetMatrix() * mi->Get2dEffect(2)->pos; + pos2 = ent->GetMatrix() * mi->Get2dEffect(3)->pos; + id = 1; + break; + case CAR_LIGHTS_RED: + r = 255; + g = 0; + pos1 = ent->GetMatrix() * mi->Get2dEffect(0)->pos; + pos2 = ent->GetMatrix() * mi->Get2dEffect(1)->pos; + id = 2; + break; + default: + r = 0; + g = 0; + pos1 = ent->GetMatrix() * mi->Get2dEffect(2)->pos; + pos2 = ent->GetMatrix() * mi->Get2dEffect(3)->pos; + id = -1; + break; + } + } + else { + switch (phase) { + case CAR_LIGHTS_GREEN: + r = 0; + g = 255; + pos1 = ent->GetMatrix() * mi->Get2dEffect(2)->pos; + pos2 = ent->GetMatrix() * mi->Get2dEffect(5)->pos; + id = 0; + break; + case CAR_LIGHTS_YELLOW: + r = 255; + g = 128; + pos1 = ent->GetMatrix() * mi->Get2dEffect(1)->pos; + pos2 = ent->GetMatrix() * mi->Get2dEffect(4)->pos; + id = 1; + break; + case CAR_LIGHTS_RED: + r = 255; + g = 0; + pos1 = ent->GetMatrix() * mi->Get2dEffect(0)->pos; + pos2 = ent->GetMatrix() * mi->Get2dEffect(3)->pos; + id = 2; + break; + default: + r = 0; + g = 0; + pos1 = ent->GetMatrix() * mi->Get2dEffect(1)->pos; + pos2 = ent->GetMatrix() * mi->Get2dEffect(4)->pos; + id = -1; + break; + } + } + + CVector pos = (pos1 + pos2) / 2; + if (id >= 0) { + 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); + } + + if (CWeather::TrafficLightBrightness > 0.5f) + CPointLights::AddLight(CPointLights::LIGHT_POINT, + pos, CVector(0.0f, 0.0f, 0.0f), 8.0f, + r / 255.0f, g / 255.0f, 0 / 255.0f, CPointLights::FOG_NORMAL, true); + + if (CWeather::TrafficLightBrightness > 0.05f) + CShadows::StoreStaticShadow((uintptr)ent, + SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, &pos, + 8.0f, 0.0f, 0.0f, -8.0f, 128, + r * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 8.0f, + g * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 8.0f, + 0 * CTimeCycle::GetLightOnGroundBrightness() * CWeather::TrafficLightBrightness / 8.0f, + 12.0f, 1.0f, 40.0f, false, 0.0f); + + if (id >= 0) { + 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, + 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); + } + } +} + +bool DoesLineSegmentIntersect(float l1x1, float l1y1, float l1x2, float l1y2, float l2x1, float l2y1, float l2x2, float l2y2) +{ + return ((l2y2 - l1y1) * (l1x2 - l1x1) + (l1x1 - l2x2) * (l1y2 - l1y1)) * + ((l2y1 - l1y1) * (l1x2 - l1x1) + (l1x1 - l2x1) * (l1y2 - l1y1)) <= 0.0f && + ((l1y2 - l2y1) * (l2x2 - l2x1) + (l2y2 - l2y1) * (l2x1 - l1x2)) * + ((l1y1 - l2y1) * (l2x2 - l2x1) + (l2y2 - l2y1) * (l2x1 - l1x1)) <= 0.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 (!IsTrafficLight(light->GetModelIndex())) + continue; + + CVector pos1 = light->GetMatrix() * CVector(17.0f, 0.0f, 0.0f); + CVector pos2 = light->GetMatrix() * CVector(-15.0f, 0.0f, 0.0f); + + // Check cars + for(i = 0; i < ThePaths.m_numCarPathNodes; i++){ + if ((ThePaths.m_pathNodes[i].GetPosition() - pos1).MagnitudeSqr() >= SQR(100.0f)) + continue; + for (j = 0; j < ThePaths.m_pathNodes[i].numLinks; j++){ + int con = ThePaths.ConnectedNode(ThePaths.m_pathNodes[i].firstLink + j); + if (i < con) { + CVector i_pos = ThePaths.m_pathNodes[i].GetPosition(); + CVector con_pos = ThePaths.m_pathNodes[con].GetPosition(); + if (Abs(pos1.z - (i_pos.z + con_pos.z) / 2) < 10.0f && + DoesLineSegmentIntersect(pos1.x, pos1.y, pos2.x, pos2.y, i_pos.x, i_pos.y, con_pos.x, con_pos.y)) { + //debug("Setting up light: nodes %f %f %f - %f %f %f, light %f %f %f - %f %f %f\n", i_pos.x, i_pos.y, i_pos.z, con_pos.x, con_pos.y, con_pos.z, pos1.x, pos1.y, pos1.z, pos2.x, pos2.y, pos2.z); + int link = ThePaths.m_carPathConnections[ThePaths.m_pathNodes[i].firstLink + j]; + ThePaths.m_carPathLinks[link].trafficLightType = FindTrafficLightType(light); + if (ThePaths.m_pathNodes[i].numLinks > ThePaths.m_pathNodes[con].numLinks) + con = i; + if (ThePaths.m_carPathLinks[link].pathNodeIndex != con) + ThePaths.m_carPathLinks[link].trafficLightDirection = true; + } + } + } + } + + // 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; + bool direction; + + node = vehicle->AutoPilot.m_nNextPathNodeInfo; + type = ThePaths.m_carPathLinks[node].trafficLightType; + direction = ThePaths.m_carPathLinks[node].trafficLightDirection; + + if(type){ + if((direction || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nNextRouteNode) && + (!direction || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nNextRouteNode)) + if(alwaysStop || + type == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + type == 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; + direction = ThePaths.m_carPathLinks[node].trafficLightDirection; + if(type){ + if((direction || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nCurrentRouteNode) && + (!direction || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nCurrentRouteNode)) + if(alwaysStop || + type == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + type == 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; + direction = ThePaths.m_carPathLinks[node].trafficLightDirection; + if(type){ + if((direction || ThePaths.m_carPathLinks[node].pathNodeIndex == vehicle->AutoPilot.m_nPrevRouteNode) && + (!direction || ThePaths.m_carPathLinks[node].pathNodeIndex != vehicle->AutoPilot.m_nPrevRouteNode)) + if(alwaysStop || + type == 1 && LightForCars1() != CAR_LIGHTS_GREEN || + type == 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) +{ +#ifdef GTA_BRIDGE + return ThePaths.m_carPathLinks[vehicle->AutoPilot.m_nNextPathNodeInfo].bBridgeLights && + !ThePaths.m_carPathLinks[vehicle->AutoPilot.m_nCurrentPathNodeInfo].bBridgeLights; +#else + return false; +#endif +} + +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) +{ + if (CWeather::Wind > 1.1f) + return CAR_LIGHTS_GREEN; + + if (bGreenLightsCheat) + return CAR_LIGHTS_GREEN; + + 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) +{ + if (CWeather::Wind > 1.1f) + return CAR_LIGHTS_GREEN; + + if (bGreenLightsCheat) + return CAR_LIGHTS_GREEN; + + 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; +} + +uint8 +CTrafficLights::LightForCars1_Visual(void) +{ + if (CWeather::Wind <= 1.1f) + return LightForCars1(); + return (CTimer::GetTimeInMilliseconds() & 0x400 ? CAR_LIGHTS_NONE : CAR_LIGHTS_YELLOW); +} + +uint8 +CTrafficLights::LightForCars2_Visual(void) +{ + if (CWeather::Wind <= 1.1f) + return LightForCars2(); + return (CTimer::GetTimeInMilliseconds() & 0x400 ? CAR_LIGHTS_NONE : CAR_LIGHTS_YELLOW); +} diff --git a/src/miami/control/TrafficLights.h b/src/miami/control/TrafficLights.h new file mode 100644 index 00000000..8dba45e1 --- /dev/null +++ b/src/miami/control/TrafficLights.h @@ -0,0 +1,32 @@ +#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, + CAR_LIGHTS_NONE +}; + +class CTrafficLights +{ +public: + static bool bGreenLightsCheat; + + 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 uint8 LightForCars1_Visual(void); + static uint8 LightForCars2_Visual(void); + static bool ShouldCarStopForLight(CVehicle*, bool); + static bool ShouldCarStopForBridge(CVehicle*); +}; diff --git a/src/miami/core/Accident.cpp b/src/miami/core/Accident.cpp new file mode 100644 index 00000000..c8611323 --- /dev/null +++ b/src/miami/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/miami/core/Accident.h b/src/miami/core/Accident.h new file mode 100644 index 00000000..568e1149 --- /dev/null +++ b/src/miami/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/miami/core/AnimViewer.cpp b/src/miami/core/AnimViewer.cpp new file mode 100644 index 00000000..562b9c15 --- /dev/null +++ b/src/miami/core/AnimViewer.cpp @@ -0,0 +1,437 @@ +#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 "Bike.h" +#include "PlayerSkin.h" +#include "PlayerInfo.h" +#include "World.h" +#include "Renderer.h" +#include "AnimManager.h" +#include "AnimBlendAssocGroup.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) { + if (pTarget) { +#ifdef FIX_BUGS + if(pTarget->IsPed()) + ((CPed*)pTarget)->UpdateRpHAnim(); +#endif + pTarget->Render(); + CRenderer::RenderOneNonRoad(pTarget); + } + } +} + +void +CAnimViewer::Initialise(void) { + + // we need messages, messages needs hud, hud needs those + int hudSlot = CTxdStore::AddTxdSlot("hud"); + CTxdStore::LoadTxd(hudSlot, "MODELS/HUD.TXD"); + CHud::m_Wants_To_Draw_Hud = false; + + animTxdSlot = CTxdStore::AddTxdSlot("generic"); + CTxdStore::Create(animTxdSlot); + 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\\DEFAULT.DAT"); + CFileLoader::LoadLevel("DATA\\ANIMVIEWER.DAT"); + CStreaming::Init(); + for(int i = 0; i < MODELINFOSIZE; i++) + if(CModelInfo::GetModelInfo(i)) + CModelInfo::GetModelInfo(i)->ConvertAnimFileIndex(); + CStreaming::LoadInitialPeds(); + CStreaming::RequestSpecialModel(MI_PLAYER, "player", STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + CRenderer::Init(); + CVehicleModelInfo::LoadVehicleColours(); +#ifdef FIX_BUGS + CVehicleModelInfo::LoadEnvironmentMaps(); +#endif + CAnimManager::LoadAnimFiles(); + CWorld::PlayerInFocus = 0; + CWeapon::InitialiseWeapons(); + CPed::Initialise(); + CTimer::Initialise(); + CClock::Initialise(60000); + CTimeCycle::Initialise(); + CCarCtrl::Init(); + CPlayerPed *player = new CPlayerPed(); + player->SetPosition(1000.0f, 1000.0f, 1000.0f); + 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 { + // TODO? maybe request some special models here so the thing doesn't crash + } + + // From LCS. idk if needed + int vanBlock = CAnimManager::GetAnimationBlockIndex("van"); + int bikesBlock = CAnimManager::GetAnimationBlockIndex("bikes"); + int bikevBlock = CAnimManager::GetAnimationBlockIndex("bikev"); + int bikehBlock = CAnimManager::GetAnimationBlockIndex("bikeh"); + int bikedBlock = CAnimManager::GetAnimationBlockIndex("biked"); + CStreaming::FlushRequestList(); + CStreaming::RequestAnim(vanBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestAnim(bikesBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestAnim(bikevBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestAnim(bikehBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestAnim(bikedBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + CAnimManager::AddAnimBlockRef(vanBlock); + CAnimManager::AddAnimBlockRef(bikesBlock); + CAnimManager::AddAnimBlockRef(bikevBlock); + CAnimManager::AddAnimBlockRef(bikehBlock); + CAnimManager::AddAnimBlockRef(bikedBlock); +} + +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 if (veh->m_vehicleType == VEHICLE_TYPE_BIKE) { + pTarget = new CBike(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) { + ((CPedModelInfo *)CModelInfo::GetModelInfo(pTarget->GetModelIndex()))->AnimatePedColModelSkinned(pTarget->GetClump()); + AsciiToUnicode("Ped Col model will be animated as long as you hold the button", gUString); + CMessages::AddMessage(gUString, 100, 0); + } + + // From LCS + if (CAnimManager::GetAnimAssocGroups()[animGroup].numAssociations <= animId) + animId = 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/miami/core/AnimViewer.h b/src/miami/core/AnimViewer.h new file mode 100644 index 00000000..13dbb8fb --- /dev/null +++ b/src/miami/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/miami/core/Cam.cpp b/src/miami/core/Cam.cpp new file mode 100644 index 00000000..cd6e38f4 --- /dev/null +++ b/src/miami/core/Cam.cpp @@ -0,0 +1,5422 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "World.h" +#include "Vehicle.h" +#include "Automobile.h" +#include "Boat.h" +#include "Bones.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 "Timecycle.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" +#include "Bike.h" +#include "Pickups.h" + +bool PrintDebugCode = false; +int16 DebugCamMode; + +extern float fRangePlayerRadius; +extern float fCloseNearClipLimit; + +#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_fTilt = 0.0f; + m_fTiltSpeed = 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; +} + +float PLAYERPED_LEVEL_SMOOTHING_CONST_INV = 0.6f; +float PLAYERPED_TREND_SMOOTHING_CONST_INV = 0.8f; + +void +CCam::Process(void) +{ + CVector CameraTarget; + float TargetSpeedVar = 0.0f; + float TargetOrientation = 0.0f; + + static CVector SmoothedPos(0.0f, 0.0f, 10000.0f); + static CVector SmoothedSpeed(0.0f, 0.0f, 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{ + if(CamTargetEntity == FindPlayerPed()){ + // Some fancy smoothing of player position and speed + float LevelSmoothing = 1.0f - Pow(PLAYERPED_LEVEL_SMOOTHING_CONST_INV, CTimer::GetTimeStep()); + float TrendSmoothing = 1.0f - Pow(PLAYERPED_TREND_SMOOTHING_CONST_INV, CTimer::GetTimeStep()); + + CVector NewSmoothedPos, NewSmoothedSpeed; + if((SmoothedPos - CamTargetEntity->GetPosition()).MagnitudeSqr() > SQR(3.0f) || + CTimer::GetTimeStep() < 0.2f || Using3rdPersonMouseCam()){ + // Reset values + NewSmoothedPos = CamTargetEntity->GetPosition(); + NewSmoothedSpeed = CVector(0.0f, 0.0f, 0.0f); + }else{ + NewSmoothedPos = LevelSmoothing*CamTargetEntity->GetPosition() + (1.0f-LevelSmoothing)*(SmoothedPos + SmoothedSpeed*CTimer::GetTimeStep()); + NewSmoothedSpeed = TrendSmoothing*(NewSmoothedPos-SmoothedPos)/CTimer::GetTimeStep() + (1.0f-TrendSmoothing)*SmoothedSpeed; + } + + CameraTarget = NewSmoothedPos; + SmoothedPos = NewSmoothedPos; + SmoothedSpeed = NewSmoothedSpeed; + }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: + case MODE_CAMERA: + 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: + 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: +// case MODE_FOLLOW_PED_WITH_BIND: +// case MODE_CHRIS: + 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: +// case MODE_CAM_RUNNING_SIDE_TRAIN: +// case MODE_BLOOD_ON_THE_TRACKS: +// case MODE_IM_THE_PASSENGER_WOOWOO: + 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: + 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_LIGHTHOUSE: + Process_LightHouse(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 || Mode == MODE_BEHINDCAR) && + CamTargetEntity->IsVehicle()){ + bool bDisableLR = CamTargetEntity && + (((CVehicle*)CamTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI || CamTargetEntity->GetModelIndex() == MI_RCBARON); + if(CPad::GetPad(0)->GetLookBehindForCar()){ + LookBehind(); + if(DirectionWasLooking != LOOKING_BEHIND) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_BEHIND; + }else if(bDisableLR){ + if(DirectionWasLooking != LOOKING_FORWARD) + TheCamera.m_bJust_Switched = true; + DirectionWasLooking = LOOKING_FORWARD; + }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 || Mode == MODE_CAMERA || 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; + bool StandingOnBoat = false; + static bool PreviouslyFailedRoadHeightCheck = false; + CVector CamToTarget, CamToPed; + float DistOnGround, BetaAngle; + CPed *Player; + float PedZDist; + CColPoint colPoint; + + CamToTarget = TheCamera.pTargetEntity->GetPosition() - TheCamera.GetGameCamPosition(); + DistOnGround = CamToTarget.Magnitude2D(); + BetaAngle = CGeneral::GetATanOfXY(CamToTarget.x, CamToTarget.y); + m_bTheHeightFixerVehicleIsATrain = false; + // 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; + + float FoundPedZ = -100.0f; + + // Move up the camera if there is a ped close to it + if(Mode == MODE_FOLLOWPED || Mode == MODE_FIGHT_CAM || Mode == MODE_PILLOWS_PAPS){ + // Find highest ped close to camera + for(i = 0; i < Player->m_numNearPeds; i++){ + CPed *nearPed = Player->m_nearPeds[i]; + if(nearPed && nearPed->GetPedState() != PED_DEAD){ + CamToPed = nearPed->GetPosition() - TheCamera.GetGameCamPosition(); + if(Abs(CamToPed.z) < 1.0f){ + float DistSq = CamToPed.MagnitudeSqr(); + if(DistSq < SQR(2.1f)){ + if(nearPed->GetPosition().z > FoundPedZ) + FoundPedZ = nearPed->GetPosition().z; + }else{ + float Dist = Sqrt(DistSq); + CamToPed /= Dist; + // strange calculation + CVector PlayerCamSpeed = DotProduct(Front, Player->m_vecMoveSpeed)*Front; + float SpeedDiff = DotProduct(PlayerCamSpeed - nearPed->m_vecMoveSpeed, CamToPed); + if(SpeedDiff > 0.01f && + (m_fPedBetweenCameraHeightOffset > 0.0f && (Dist-2.1f)/SpeedDiff < 75.0f || + m_fPedBetweenCameraHeightOffset <= 0.0f && (Dist-2.1f)/SpeedDiff < 75.0f * 0.1f)) + if(nearPed->GetPosition().z > FoundPedZ) + FoundPedZ = nearPed->GetPosition().z; + } + } + } + } + + if(FoundPedZ > -99.0f){ + float Offset = 0.0f; + PedZDist = 0.0f; + if(FoundPedZ > Player->GetPosition().z) + PedZDist = FoundPedZ - Player->GetPosition().z; + + if(Mode == MODE_FOLLOWPED){ + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1 && + ((CPed*)CamTargetEntity)->GetPedState() != PED_ENTER_CAR && + ((CPed*)CamTargetEntity)->GetPedState() != PED_CARJACK) + Offset = 0.45f + PedZDist; + // BUG: overrides this ^ case + if(TheCamera.PedZoomIndicator == CAM_ZOOM_2 || TheCamera.PedZoomIndicator == CAM_ZOOM_1) + Offset = 0.35f + PedZDist; + if(TheCamera.PedZoomIndicator == CAM_ZOOM_3) + Offset = 0.25f + PedZDist; + m_fPedBetweenCameraHeightOffset = Offset + 1.3f; + }else if(Mode == MODE_FIGHT_CAM) + m_fPedBetweenCameraHeightOffset = PedZDist + 1.3f + 0.5f; + else if(Mode == MODE_PILLOWS_PAPS) + m_fPedBetweenCameraHeightOffset = PedZDist + 1.3f + 0.45f; + }else{ + m_fPedBetweenCameraHeightOffset = 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; + if(m_fDimensionOfHighestNearCar > 0.0f) + TestDist += 0.3f; + 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; + } + } + + if(StandingOnBoat){ + 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); +} + +bool +CCam::GetBoatLook_L_R_HeightOffset(float &Offset) +{ + if(CamTargetEntity == nil) + return false; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(CamTargetEntity->GetModelIndex()); + tBoatHandlingData *handling = mod_HandlingManager.GetBoatPointer(mi->m_handlingId); + if(handling){ + Offset = handling->fLook_L_R_BehindCamHeight; + return true; + } + return false; // can't happen, we always get a boat pointer back +} + +void +CCam::LookBehind(void) +{ + float Dist, DeltaBeta, TargetOrientation, Angle; + CVector TargetCoors, TargetFwd, TestCoors; + + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT || Mode == MODE_BEHINDCAR) && 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; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + 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; + if(((CVehicle*)CamTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_BIKE){ + float FrontDist = 1.1f; + if(((CVehicle*)CamTargetEntity)->pDriver){ + CVector ExtraFwd(0.0f, 0.0f, 0.0f); + ((CVehicle*)CamTargetEntity)->pDriver->m_pedIK.GetComponentPosition(ExtraFwd, PED_HEAD); + ExtraFwd += ((CVehicle*)CamTargetEntity)->m_vecMoveSpeed*CTimer::GetTimeStep() - CamTargetEntity->GetPosition(); + FrontDist += 0.2f + Max(DotProduct(ExtraFwd, CamTargetEntity->GetForward()), 0.0f); + } + Source += FrontDist*Front; + Front = -Front; + }else if(((CVehicle*)CamTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI){ + Front = -1.0f*CamTargetEntity->GetUp(); + Up = CamTargetEntity->GetForward(); + Source += 0.25f*Front; + }else{ + Source += 0.25f*Front; + Front = -Front; + } + } + 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; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + Front = TargetCoors - Source; + GetVectorsReadyForRW(); + } +} + +float BOAT_1STPERSON_L_OFFSETX = 0.7f; +float BOAT_1STPERSON_R_OFFSETX = 0.3f; +float BOAT_1STPERSON_LR_OFFSETZ = 0.2f; + +void +CCam::LookLeft(void) +{ + float Dist, TargetOrientation; + CVector TargetCoors, TargetFwd; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT || Mode == MODE_BEHINDCAR) && CamTargetEntity->IsVehicle()){ + LookingLeft = true; + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + if(Mode == MODE_CAM_ON_A_STRING) + Dist = CA_MAX_DISTANCE; + else if(Mode == MODE_BEHINDBOAT){ + Dist = 9.0f; + float Offset = 0.0f; + if(GetBoatLook_L_R_HeightOffset(Offset) && !CCullZones::Cam1stPersonForPlayer()) + Source.z = TargetCoors.z + Offset; + }else + Dist = 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; + + CColModel *colModel = CamTargetEntity->GetColModel(); + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + + CVector TopRight = CamTargetEntity->GetPosition() + + CamTargetEntity->GetRight()*colModel->boundingBox.max.x + + CamTargetEntity->GetUp()*colModel->boundingBox.max.z; + float Height = Min(Max(m_cvecTargetCoorsForFudgeInter.z, TopRight.z)+0.1f, OrigSource.z); + Source.z = Max(Height, Source.z); + + 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()){ + if(((CVehicle*)CamTargetEntity)->pDriver){ + CVector neck(0.0f, 0.0f, 0.0f); + CPed *driver = ((CVehicle*)CamTargetEntity)->pDriver; + driver->SetPedPositionInCar(); + driver->GetMatrix().UpdateRW(); + driver->UpdateRwFrame(); + driver->UpdateRpHAnim(); + driver->m_pedIK.GetComponentPosition(neck, PED_NECK); + Source = neck + + BOAT_1STPERSON_L_OFFSETX*CamTargetEntity->GetRight() + + BOAT_1STPERSON_LR_OFFSETZ*CamTargetEntity->GetUp(); + }else + Source.z -= 0.5f; + } + + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Front = -CrossProduct(Front, Up); + Front.Normalise(); + if(((CVehicle*)CamTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_BIKE) + Source -= 1.45f*Front; + } +} + +void +CCam::LookRight(void) +{ + float Dist, TargetOrientation; + CVector TargetCoors, TargetFwd; + CColPoint colPoint; + + if((Mode == MODE_CAM_ON_A_STRING || Mode == MODE_BEHINDBOAT || Mode == MODE_BEHINDCAR) && CamTargetEntity->IsVehicle()){ + LookingRight = true; + TargetCoors = CamTargetEntity->GetPosition(); + Front = CamTargetEntity->GetPosition() - Source; + if(Mode == MODE_CAM_ON_A_STRING) + Dist = CA_MAX_DISTANCE; + else if(Mode == MODE_BEHINDBOAT){ + Dist = 9.0f; + float Offset = 0.0f; + if(GetBoatLook_L_R_HeightOffset(Offset) && !CCullZones::Cam1stPersonForPlayer()) + Source.z = TargetCoors.z + Offset; + }else + Dist = 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; + + CColModel *colModel = CamTargetEntity->GetColModel(); + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + + CVector TopLeft = CamTargetEntity->GetPosition() + + CamTargetEntity->GetRight()*colModel->boundingBox.min.x + + CamTargetEntity->GetUp()*colModel->boundingBox.max.z; + float Height = Min(Max(m_cvecTargetCoorsForFudgeInter.z, TopLeft.z)+0.1f, OrigSource.z); + Source.z = Max(Height, Source.z); + + 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()){ + if(((CVehicle*)CamTargetEntity)->pDriver){ + CVector neck(0.0f, 0.0f, 0.0f); + CPed *driver = ((CVehicle*)CamTargetEntity)->pDriver; + driver->SetPedPositionInCar(); + driver->GetMatrix().UpdateRW(); + driver->UpdateRwFrame(); + driver->UpdateRpHAnim(); + driver->m_pedIK.GetComponentPosition(neck, PED_NECK); + Source = neck + + BOAT_1STPERSON_R_OFFSETX*CamTargetEntity->GetRight() + + BOAT_1STPERSON_LR_OFFSETZ*CamTargetEntity->GetUp(); + }else + Source.z -= 0.5f; + } + + Up = CamTargetEntity->GetUp(); + Up.Normalise(); + Front = CamTargetEntity->GetForward(); + Front.Normalise(); + Front = CrossProduct(Front, Up); + Front.Normalise(); + if(((CVehicle*)CamTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_BIKE) + Source -= 1.45f*Front; + } +} + +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; +} + +bool +CCam::GetWeaponFirstPersonOn(void) +{ + return CamTargetEntity && CamTargetEntity->IsPed() && ((CPed*)CamTargetEntity)->GetWeapon()->m_bAddRotOffset; +} + +bool +CCam::IsTargetInWater(const CVector &CamCoors) +{ + if(CamTargetEntity){ + float WaterZ = -6000.0f; + CWaterLevel::GetWaterLevel(CamTargetEntity->GetPosition(), &WaterZ, false); + if(CamTargetEntity->IsPed()){ + if(((CPed*)CamTargetEntity)->bIsDrowning || + ((CPed*)CamTargetEntity)->bIsInWater && CamTargetEntity->GetPosition().z < WaterZ) + return true; + }else{ + assert(CamTargetEntity->IsVehicle()); + if(((CVehicle*)CamTargetEntity)->bIsDrowning || + ((CVehicle*)CamTargetEntity)->bIsInWater && CamTargetEntity->GetPosition().z < WaterZ) + return true; + } + } + m_vecLastAboveWaterCamPosition = Source; + return false; +} + +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", "Lighthouse", + "Sniper run about", "Rocket run about", + "1st Person run about", "M16 run about", "Fight run about", + "Editor", "Helicannon", "Camera" + }; + 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 = CVector(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; +} + +float DefaultAcceleration = 0.045f; +float DefaultMaxStep = 0.15f; +float fDefaultSpeedStep = 0.025f; +float fDefaultSpeedMultiplier = 0.09f; +float fDefaultSpeedLimit = 0.15f; +float fDefaultSpeedStep4Avoid = 0.02f; +float fDefaultSpeedMultiplier4Avoid = 0.05f; +float fDefaultSpeedLimit4Avoid = 0.25f; +float fAvoidGeomThreshhold = 1.5f; +float fMiniGunBetaOffset = 0.3f; + +void +CCam::Process_FollowPed(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + CVector TargetCoors, Dist, IdealSource; + float Length = 0.0f; + static bool PickedASide; + static float FixedTargetOrientation = 0.0f; + float AngleToGoTo = 0.0f; + bool StandingInTrain = false; + float ZoomGroundTarget = 0.0f; + float ZoomZTarget = 0.0f; + static int TimeIndicatedWantedToGoDown = 0; + static bool StartedCountingForGoDown = false; + static float ZoomGround = 0.0f; + static float ZoomGroundSpeed = 0.0f; + static float ZoomZ = 0.0f; + static float ZoomZSpeed = 0.0f; + float DeltaBeta; + + m_bFixingBeta = false; + bBelowMinDist = false; + bBehindPlayerDesired = false; + + FOV = DefaultFOV; + + if(ResetStatics){ + Rotating = false; + m_bCollisionChecksOn = true; + FixedTargetOrientation = 0.0f; + PickedASide = false; + StartedCountingForGoDown = false; + AngleToGoTo = 0.0f; + ZoomGround = 0.0f; + ZoomGroundSpeed = 0.0f; + ZoomZ = 0.0f; + ZoomZSpeed = 0.0f; + Distance = 500.0f; + } + + + TargetCoors = CameraTarget; + + // Take speed of thing we're standing on into account + CVector GroundMovement(0.0f, 0.0f, 0.0f); + CPhysical *ground = (CPhysical*)((CPed*)CamTargetEntity)->m_pCurSurface; + if(ground && (ground->IsVehicle() || ground->IsObject())) + GroundMovement += ground->GetSpeed(CamTargetEntity->GetPosition() - ground->GetPosition()) * CTimer::GetTimeStep(); + + Source += GroundMovement; + IdealSource = Source; + TargetCoors.z += m_fSyphonModeTargetZOffSet; + + TargetCoors.z = DoAverageOnVector(TargetCoors).z; + + 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 * m_fMinRealGroundDist; + IdealSource.z += GroundMovement.z; + }else + IdealSource = TargetCoors + CVector(1.0f, 1.0f, 0.0f); + + if(TheCamera.m_bUseTransitionBeta && ResetStatics){ + CVector VecDistance; + IdealSource.x = TargetCoors.x + m_fMinRealGroundDist*Cos(m_fTransitionBeta); + IdealSource.y = TargetCoors.y + m_fMinRealGroundDist*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; + + if(TheCamera.PedZoomIndicator == CAM_ZOOM_1 && + ((CPed*)CamTargetEntity)->GetPedState() != PED_ENTER_CAR && + ((CPed*)CamTargetEntity)->GetPedState() != PED_CARJACK){ + ZoomGroundTarget = m_fTargetZoomGroundOne; + ZoomZTarget = m_fTargetZoomOneZExtra; + }else if(TheCamera.PedZoomIndicator == CAM_ZOOM_2 || TheCamera.PedZoomIndicator == CAM_ZOOM_1){ + ZoomGroundTarget = m_fTargetZoomGroundTwo; + ZoomZTarget = m_fTargetZoomTwoZExtra; + }else if(TheCamera.PedZoomIndicator == CAM_ZOOM_3){ + ZoomGroundTarget = m_fTargetZoomGroundThree; + ZoomZTarget = m_fTargetZoomThreeZExtra; + } + if(m_fCloseInPedHeightOffset > 0.00001f){ + ZoomGroundTarget = m_fTargetCloseInDist; + ZoomZTarget = m_fTargetZoomZCloseIn; + } + if(ResetStatics){ + ZoomGround = ZoomGroundTarget; + ZoomZ = ZoomZTarget; + } + + float SpeedStep = fDefaultSpeedStep; + float SpeedMultiplier = fDefaultSpeedMultiplier; + float SpeedLimit = fDefaultSpeedLimit; + 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; + + + // Figure out if and where we want to rotate + + if(CPad::GetPad(0)->ForceCameraBehindPlayer() && !CPickups::PlayerOnWeaponPickup || Shooting){ + + // Center cam behind player + + if(PickedASide){ + if(AngleToGoTo == 0.0f){ + FixedTargetOrientation = TargetOrientation + PI; + if(Shooting && ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN) + FixedTargetOrientation -= fMiniGunBetaOffset; + } + Rotating = true; + }else{ + FixedTargetOrientation = TargetOrientation + PI; + Rotating = true; + PickedASide = true; + if(Shooting && ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN) + FixedTargetOrientation -= fMiniGunBetaOffset; + } + }else if(Abs(TheCamera.m_fAvoidTheGeometryProbsTimer) > fAvoidGeomThreshhold && !Rotating ){ + + if(TheCamera.m_fAvoidTheGeometryProbsTimer < 0.0f) + FixedTargetOrientation = TargetOrientation; + else + FixedTargetOrientation = TargetOrientation + PI; + float dist = (Source - TargetCoors).Magnitude(); + float mult = dist > 0.1f ? 1.0f/dist : 10.0f; + SpeedStep = mult * fDefaultSpeedStep4Avoid; + SpeedMultiplier = mult * fDefaultSpeedMultiplier4Avoid; + SpeedLimit = mult * fDefaultSpeedLimit4Avoid; + } + + int MoveState = ((CPed*)CamTargetEntity)->m_nMoveState; + if(MoveState != PEDMOVE_NONE && MoveState != PEDMOVE_STILL && + !(CPad::GetPad(0)->ForceCameraBehindPlayer() && !CPickups::PlayerOnWeaponPickup) && !Shooting){ + Rotating = false; + if(TheCamera.m_fAvoidTheGeometryProbsTimer <= fAvoidGeomThreshhold) + BetaSpeed = 0.0f; + } + + // Now do the Beta rotation + + float RotDistance = m_fMinRealGroundDist; + + if(Rotating || TheCamera.m_fAvoidTheGeometryProbsTimer > fAvoidGeomThreshhold){ + 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 - unfortunately modified so we can't just call it + { + DeltaBeta = FixedTargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + + // this is the added bit + if(!Rotating){ + if(TheCamera.m_nAvoidTheGeometryProbsDirn == -1 && DeltaBeta > 0.0f || + TheCamera.m_nAvoidTheGeometryProbsDirn == 1 && DeltaBeta < 0.0f) + DeltaBeta *= -1.0f; + } + + float ReqSpeed = DeltaBeta * SpeedMultiplier; + // this is also added + ReqSpeed = Clamp(ReqSpeed, -SpeedLimit, SpeedLimit); + + // 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()); + } + + 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 || + TheCamera.m_bUseTransitionBeta && ResetStatics || + Abs(TheCamera.m_fAvoidTheGeometryProbsTimer) > fAvoidGeomThreshhold){ + if(TheCamera.m_bUseTransitionBeta){ + Beta = m_fTransitionBeta; + Source.x = TargetCoors.x + RotDistance * Cos(m_fTransitionBeta); + Source.y = TargetCoors.y + RotDistance * Sin(m_fTransitionBeta); + } + 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; + } + 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; + } + Source.z = IdealSource.z; + + // Zoom out camera + Front = TargetCoors - Source; + Front.Normalise(); + WellBufferMe(ZoomGroundTarget, &ZoomGround, &ZoomGroundSpeed, 0.2f, 0.07f, false); + WellBufferMe(ZoomZTarget, &ZoomZ, &ZoomZSpeed, 0.2f, 0.07f, false); + Source.x -= Front.x*ZoomGround; + Source.y -= Front.y*ZoomGround; + Source.z += ZoomZ; + + + // Process height offset to avoid peds and cars + + float TargetZOffSet = Max(m_fDimensionOfHighestNearCar, 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 + 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; + TargetCoors.z += Min(1.0f, m_fCamBufferedHeight/2.0f); + m_cvecTargetCoorsForFudgeInter = TargetCoors; + + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + float TargetDist = (TargetCoors - Source).Magnitude(); + if(TargetDist < Distance) + Distance = TargetDist; + else{ + float f = Pow(0.97f, CTimer::GetTimeStep()); + Distance = (1.0f - f)*TargetDist + f*Distance; + if(TargetDist > 0.05f) + Source = TargetCoors + (Source-TargetCoors)*Distance/TargetDist; + float clip = Distance-fRangePlayerRadius; + if(clip < RwCameraGetNearClipPlane(Scene.camera)) + RwCameraSetNearClipPlane(Scene.camera, Max(clip, fCloseNearClipLimit)); + } + + Front = TargetCoors - Source; + m_fRealGroundDist = Front.Magnitude2D(); + m_fMinDistAwayFromCamWhenInterPolating = m_fRealGroundDist; + Front.Normalise(); + GetVectorsReadyForRW(); + TheCamera.m_bCamDirectlyBehind = false; + TheCamera.m_bCamDirectlyInFront = false; + + ResetStatics = false; +} + +float fBaseDist = 1.7f; +float fAngleDist = 2.0f; +float fFalloff = 3.0f; +float fStickSens = 0.01f; +float fTweakFOV = 1.1f; +float fTranslateCamUp = 0.8f; +int16 nFadeControlThreshhold = 45; +float fDefaultAlphaOrient = -0.22f; +float fMouseAvoidGeomReturnRate = 0.92f; + +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(); + + TargetCoors = CameraTarget; + TargetCoors.z += fTranslateCamUp; + + float AlphaOffset, BetaOffset; + if(CPad::GetPad(0)->IsPlayerControlsDisabledBy(PLAYERCONTROL_PLAYERINFO)){ + CVector ToCam = Source - TargetCoors; + ToCam.Normalise(); + if(ToCam.z < -0.9f) + BetaOffset = TargetOrientation + PI; + else + BetaOffset = Atan2(ToCam.y, ToCam.x); + BetaOffset -= Beta; + AlphaOffset = 0.0f; + }else{ + // 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(); + } + 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 || + CPad::GetPad(0)->IsPlayerControlsDisabledBy(PLAYERCONTROL_PLAYERINFO)){ + 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); + + // 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 = m_fTransitionBeta; + + if(TheCamera.m_bCamDirectlyBehind) + Beta = TheCamera.m_PedOrientForBehindOrInFront + PI; + if(TheCamera.m_bCamDirectlyInFront) + Beta = TheCamera.m_PedOrientForBehindOrInFront; + 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::CalculateAspectRatio() * 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 wrong...DEGTORAD missing + radius = Tan(FOV / 2.0f) * CDraw::CalculateAspectRatio() * fTweakFOV * 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; + } + + float TargetDist = (TargetCoors - Source).Magnitude(); + if(TargetDist < Distance) + Distance = TargetDist; + else{ + float f = Pow(fMouseAvoidGeomReturnRate, CTimer::GetTimeStep()); + Distance = (1.0f - f)*TargetDist + f*Distance; + if(TargetDist > 0.05f) + Source = TargetCoors + (Source-TargetCoors)*Distance/TargetDist; + float clip = Distance-fRangePlayerRadius; + if(clip < RwCameraGetNearClipPlane(Scene.camera)) + RwCameraSetNearClipPlane(Scene.camera, Max(clip, fCloseNearClipLimit)); + } + + TheCamera.m_bCamDirectlyInFront = false; + TheCamera.m_bCamDirectlyBehind = false; + + GetVectorsReadyForRW(); + + if(((CPed*)CamTargetEntity)->CanStrafeOrMouseControl() && CDraw::FadeValue < 250 && + (TheCamera.GetFadingDirection() != FADE_OUT || CDraw::FadeValue <= 100) && + !CPad::GetPad(0)->IsPlayerControlsDisabledBy(PLAYERCONTROL_PLAYERINFO)){ + 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); +#ifdef TOGGLEABLE_BETA_FEATURES + // 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; + + Alpha = DEGTORAD(25.0f); + Source.z = TargetCoors.z + CA_MAX_DISTANCE*Sin(Alpha); + + RotCamIfInFrontCar(TargetCoors, TargetOrientation); + m_cvecTargetCoorsForFudgeInter = TargetCoors; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, m_cvecTargetCoorsForFudgeInter, Source, FOV); + + Front = TargetCoors - Source; + ResetStatics = false; + GetVectorsReadyForRW(); +} + +float ZmOneAlphaOffset[] = { -0.01f, 0.1f, 0.125f, -0.1f, -0.06f }; +float ZmTwoAlphaOffset[] = { 0.045f, 0.12f, 0.045f, 0.045f, -0.035f }; +float ZmThreeAlphaOffset[] = { 0.005f, 0.005f, 0.15f, 0.005f, 0.12f }; +float INIT_RC_HELI_HORI_EXTRA = 6.0f; +float INIT_RC_PLANE_HORI_EXTRA = 9.5f; +float INIT_RC_HELI_ALPHA_EXTRA = 0.2f; +float INIT_RC_PLANE_ALPHA_EXTRA = 0.295f; + +void +CCam::WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight) +{ + if(!CamTargetEntity->IsVehicle()) + return; + + static float AlphaOffset = 0.0; + static float AlphaOffsetSpeed = 0.0; + static float AlphaDec = 0.0f; + + bool isHeli = false; + bool isBike = false; + int appearance = ((CVehicle*)CamTargetEntity)->GetVehicleAppearance(); + if(appearance == VEHICLE_APPEARANCE_BIKE) + isBike = true; + if(appearance == VEHICLE_APPEARANCE_HELI) + isHeli = true; + int index = 0; + TheCamera.GetArrPosForVehicleType(appearance, index); + + float ExtraOffset = 0.0f; + int id = CamTargetEntity->GetModelIndex(); + if(id == MI_RCRAIDER || id == MI_RCGOBLIN) + ExtraOffset = INIT_RC_HELI_ALPHA_EXTRA; + else if(id == MI_RCBARON) + ExtraOffset = INIT_RC_PLANE_ALPHA_EXTRA; + + if(ResetStatics){ + AlphaOffset = 0.0f; + AlphaOffsetSpeed = 0.0f; + AlphaDec = 0.0f; + + if(TheCamera.CarZoomIndicator == CAM_ZOOM_1) + AlphaOffset = ZmOneAlphaOffset[index] + ExtraOffset; + else if(TheCamera.CarZoomIndicator == CAM_ZOOM_2) + AlphaOffset = ZmTwoAlphaOffset[index] + ExtraOffset; + else if(TheCamera.CarZoomIndicator == CAM_ZOOM_3) + AlphaOffset = ZmThreeAlphaOffset[index] + ExtraOffset; + } + + if(TheCamera.CarZoomIndicator == CAM_ZOOM_1) + WellBufferMe(ZmOneAlphaOffset[index] + ExtraOffset, &AlphaOffset, &AlphaOffsetSpeed, 0.17f, 0.08f, false); + else if(TheCamera.CarZoomIndicator == CAM_ZOOM_2) + WellBufferMe(ZmTwoAlphaOffset[index] + ExtraOffset, &AlphaOffset, &AlphaOffsetSpeed, 0.17f, 0.08f, false); + else if(TheCamera.CarZoomIndicator == CAM_ZOOM_3) + WellBufferMe(ZmThreeAlphaOffset[index] + ExtraOffset, &AlphaOffset, &AlphaOffsetSpeed, 0.17f, 0.08f, false); + + float Length = (Source - TargetCoors).Magnitude2D(); + + CVector Forward = CamTargetEntity->GetForward(); + float CarAlpha = CGeneral::GetATanOfXY(Forward.Magnitude2D(), Forward.z); + // this shouldn't be necessary.... + while(CarAlpha >= PI) CarAlpha -= 2*PI; + while(CarAlpha < -PI) CarAlpha += 2*PI; + + while(Beta >= PI) Beta -= 2*PI; + while(Beta < -PI) Beta += 2*PI; + + float DeltaBeta = Beta - TargetOrientation; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + + float BehindCarNess = Cos(DeltaBeta); // 1 if behind car, 0 if side, -1 if in front + CarAlpha = -CarAlpha * BehindCarNess; + + float fwdSpeed = DotProduct(((CPhysical*)CamTargetEntity)->m_vecMoveSpeed, CamTargetEntity->GetForward())*180.0f; + if(CamTargetEntity->GetModelIndex() == MI_FIRETRUCK && CPad::GetPad(0)->GetCarGunFired()){ + CarAlpha = DEGTORAD(10.0f); + }else if(isHeli){ + CarAlpha = 0.0f; + float heliFwdZ = CamTargetEntity->GetForward().z; + float heliFwdXY = CamTargetEntity->GetForward().Magnitude2D(); + float alphaAmount = Min(Abs(fwdSpeed/90.0f), 1.0f); + if(heliFwdXY != 0.0f || heliFwdZ != 0.0f) + CarAlpha = CGeneral::GetATanOfXY(heliFwdXY, Abs(heliFwdZ)) * alphaAmount; + + CColPoint point; + CEntity *entity = nil; + CVector Test = Source; + Test.z = TargetCoors.z + 0.2f + Length*Sin(CarAlpha+AlphaOffset) + m_fCloseInCarHeightOffset; + if(CWorld::ProcessVerticalLine(Test, CamTargetEntity->GetPosition().z, point, entity, true, false, false, false, false, false, nil)){ + float sin = (point.point.z - TargetCoors.z - 0.2f - m_fCloseInCarHeightOffset)/Length; + CarAlpha = Asin(Clamp(sin, -1.0f, 1.0f)) - AlphaOffset; + if(CarAlpha < 0.0f) + AlphaOffset += CarAlpha; + } + } + + CarAlpha = CGeneral::LimitRadianAngle(CarAlpha); + if(CarAlpha < 0.0f) CarAlpha = 0.0f; + if(CarAlpha > DEGTORAD(89.0f)) CarAlpha = DEGTORAD(89.0f); + + if(ResetStatics) + Alpha = CarAlpha; + + float TargetAlpha = Alpha; + float DeltaAlpha = CarAlpha - TargetAlpha; + while(DeltaAlpha >= PI) DeltaAlpha -= 2*PI; + while(DeltaAlpha < -PI) DeltaAlpha += 2*PI; + if(Abs(DeltaAlpha) > 0.0f && !TheCamera.m_bVehicleSuspenHigh) + TargetAlpha = CarAlpha; + + if(isBike) + WellBufferMe(TargetAlpha, &Alpha, &AlphaSpeed, 0.09f, 0.04f, true); + else if(isHeli) + WellBufferMe(TargetAlpha, &Alpha, &AlphaSpeed, 0.09f, 0.04f, true); + else + WellBufferMe(TargetAlpha, &Alpha, &AlphaSpeed, 0.15f, 0.07f, true); + + Source.z = TargetCoors.z + Sin(Alpha + AlphaOffset)*Length + m_fCloseInCarHeightOffset; + AlphaOffset -= AlphaDec; +} + +// Rotate cam behind the car when the car is moving forward +bool +CCam::RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation) +{ + float BetaMaxSpeed = 0.15f; + float BetaAcceleration = 0.007f; + bool MovingForward = false; + float MaxDiffBeta = DEGTORAD(160.0f); + CPhysical *phys = (CPhysical*)CamTargetEntity; + + float ForwardSpeed = DotProduct(phys->GetForward(), phys->GetSpeed(CVector(0.0f, 0.0f, 0.0f))); + if(ForwardSpeed > 0.02f) + MovingForward = true; + + if(phys->IsVehicle() && (phys->GetModelIndex() == MI_SPARROW || phys->GetModelIndex() == MI_HUNTER)){ + MaxDiffBeta = DEGTORAD(160.0f); + BetaMaxSpeed = 0.1f; + BetaAcceleration = 0.003f; + CVector speed = phys->GetSpeed(CVector(0.0f, 0.0f, 0.0f)); + speed.z = 0.0f; + if(50.0f*speed.Magnitude() > 3.13f) + TargetOrientation = CGeneral::GetATanOfXY(speed.x, speed.y); + } + + float Dist = (Source - TargetCoors).Magnitude2D(); + + float DeltaBeta = TargetOrientation - Beta; + while(DeltaBeta >= PI) DeltaBeta -= 2*PI; + while(DeltaBeta < -PI) DeltaBeta += 2*PI; + + if(Abs(DeltaBeta) > PI-MaxDiffBeta && 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, BetaMaxSpeed, BetaAcceleration, 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; +} + +float FIRETRUCK_TRACKING_MULT = 0.1f; +float fTestShiftHeliCamTarget = 0.6f; +float TiltTopSpeed[] = { 0.035f, 0.035f, 0.001f, 0.005f, 0.035f }; +float TiltSpeedStep[] = { 0.016f, 0.016f, 0.0002f, 0.0014f, 0.016f }; +float TiltOverShoot[] = { 1.05f, 1.05f, 0.0f, 0.0f, 1.0f }; + +void +CCam::Process_Cam_On_A_String(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsVehicle()) + return; + + // unused + // ((CVehicle*)CamTargetEntity)->GetVehicleAppearance(); + + FOV = DefaultFOV; + + if(ResetStatics){ + AlphaSpeed = 0.0f; + m_fTilt = 0.0f; + m_fTiltSpeed = 0.0; + } + + CBaseModelInfo *mi = CModelInfo::GetModelInfo(CamTargetEntity->GetModelIndex()); + CVector Dimensions = mi->GetColModel()->boundingBox.max - mi->GetColModel()->boundingBox.min; + CVector TargetCoors = CameraTarget; + float BaseDist = Dimensions.Magnitude(); + + if(((CVehicle*)CamTargetEntity)->IsBike()) + BaseDist *= 1.45f; + if(((CVehicle*)CamTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI && + CamTargetEntity->GetStatus() != STATUS_PLAYER_REMOTE) + TargetCoors += fTestShiftHeliCamTarget * CamTargetEntity->GetUp() * Dimensions.z; + else + TargetCoors.z += 0.8f*Dimensions.z; + + Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y); + Alpha = CGeneral::LimitRadianAngle(Alpha); + Beta = CGeneral::LimitRadianAngle(Beta); + + if(CamTargetEntity->GetModelIndex() == MI_FIRETRUCK && CPad::GetPad(0)->GetCarGunFired() && + ((CVehicle*)CamTargetEntity)->m_vecMoveSpeed.Magnitude2D() < 0.01f){ + float TargetBeta = CamTargetEntity->GetForward().Heading() - ((CAutomobile*)CamTargetEntity)->m_fCarGunLR + HALFPI; + TargetBeta = CGeneral::LimitRadianAngle(TargetBeta); + float DeltaBeta = TargetBeta - Beta; + if(DeltaBeta > PI) DeltaBeta -= TWOPI; + else if(DeltaBeta < -PI) DeltaBeta += TWOPI; + float dist = (TargetCoors - Source).Magnitude(); + dist = FIRETRUCK_TRACKING_MULT*dist*Clamp(DeltaBeta, -0.8f, 0.8f); + Source += dist*CrossProduct(Front, CVector(0.0f, 0.0f, 1.0f)); + } + + m_fDistanceBeforeChanges = (Source - TargetCoors).Magnitude2D(); + + Cam_On_A_String_Unobscured(TargetCoors, BaseDist); + WorkOutCamHeight(TargetCoors, TargetOrientation, Dimensions.z); + RotCamIfInFrontCar(TargetCoors, TargetOrientation); + FixCamWhenObscuredByVehicle(TargetCoors); + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + CVector OrigSource = Source; + if(CWorld::GetIsLineOfSightClear(CamTargetEntity->GetPosition(), m_cvecTargetCoorsForFudgeInter, true, false, false, true, false, false, true)) + TheCamera.AvoidTheGeometry(OrigSource, m_cvecTargetCoorsForFudgeInter, Source, FOV); + else + TheCamera.AvoidTheGeometry(OrigSource, CamTargetEntity->GetPosition(), Source, FOV); + + Front = TargetCoors - Source; + Front.Normalise(); + + int appearance = ((CVehicle*)CamTargetEntity)->GetVehicleAppearance(); + int index = 0; + TheCamera.GetArrPosForVehicleType(appearance, index); + + if(appearance == VEHICLE_APPEARANCE_HELI){ + float TargetTilt = DotProduct(Front, ((CVehicle*)CamTargetEntity)->GetSpeed(CVector(0.0f, 0.0f, 0.0f))); + CVector UpTarget = CamTargetEntity->GetUp(); + UpTarget.Normalise(); + int dir = TargetTilt < 0.0f ? -1 : 1; + if(m_fTilt != 0.0f) + TargetTilt += TiltOverShoot[index]*TargetTilt/m_fTilt * dir; + WellBufferMe(TargetTilt, &m_fTilt, &m_fTiltSpeed, TiltTopSpeed[index], TiltSpeedStep[index], false); + + Up = CVector(0.0f, 0.0f, 1.0f) - (CVector(0.0f, 0.0f, 1.0f) - UpTarget)*m_fTilt; + Up.Normalise(); + Front.Normalise(); + CVector Left = CrossProduct(Up, Front); + Up = CrossProduct(Front, Left); + Up.Normalise(); + }else{ + float TargetRoll; + if(CPad::GetPad(0)->GetDPadLeft() || CPad::GetPad(0)->GetDPadRight()){ + float fwdSpeed = 180.0f*DotProduct(((CVehicle*)CamTargetEntity)->m_vecMoveSpeed, CamTargetEntity->GetForward()); + if(fwdSpeed > 210.0f) fwdSpeed = 210.0f; + if(CPad::GetPad(0)->GetDPadLeft()) + TargetRoll = DEGTORAD(10.0f)*TiltOverShoot[index] + f_max_role_angle; + else + TargetRoll = -(DEGTORAD(10.0f)*TiltOverShoot[index] + f_max_role_angle); + CVector FwdTarget = CamTargetEntity->GetForward(); + FwdTarget.Normalise(); + float AngleDiff = DotProduct(FwdTarget, Front); + AngleDiff = Acos(Min(Abs(AngleDiff), 1.0f)); + TargetRoll *= fwdSpeed/210.0f * Sin(AngleDiff); + }else{ + float fwdSpeed = 180.0f*DotProduct(((CVehicle*)CamTargetEntity)->m_vecMoveSpeed, CamTargetEntity->GetForward()); + if(fwdSpeed > 210.0f) fwdSpeed = 210.0f; + TargetRoll = CPad::GetPad(0)->GetLeftStickX()/128.0f * fwdSpeed/210.0f; + CVector FwdTarget = CamTargetEntity->GetForward(); + FwdTarget.Normalise(); + float AngleDiff = DotProduct(FwdTarget, Front); + AngleDiff = Acos(Min(Abs(AngleDiff), 1.0f)); + TargetRoll *= (DEGTORAD(10.0f)*TiltOverShoot[index] + f_max_role_angle) * Sin(AngleDiff); + } + + WellBufferMe(TargetRoll, &f_Roll, &f_rollSpeed, 0.15f, 0.07f, false); + Up = CVector(Cos(f_Roll + HALFPI), 0.0f, Sin(f_Roll + HALFPI)); + Up.Normalise(); + Front.Normalise(); + CVector Left = CrossProduct(Up, Front); + Left.Normalise(); + Up = CrossProduct(Front, Left); + Up.Normalise(); + } + + ResetStatics = false; +} + +// Basic Cam on a string algorithm +void +CCam::Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist) +{ + int id = CamTargetEntity->GetModelIndex(); + float ExtraDist = 0.0f; + if(id == MI_RCRAIDER || id == MI_RCGOBLIN) + ExtraDist = INIT_RC_HELI_HORI_EXTRA; + else if(id == MI_RCBARON) + ExtraDist = INIT_RC_PLANE_HORI_EXTRA; + + CA_MAX_DISTANCE = BaseDist + 0.1f + TheCamera.CarZoomValueSmooth + ExtraDist; + CA_MIN_DISTANCE = Min(BaseDist*0.6f, 3.5f); + if(CA_MIN_DISTANCE > CA_MAX_DISTANCE) + CA_MIN_DISTANCE = CA_MAX_DISTANCE - 0.05f; + + 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; +} + +void +CCam::Process_Rocket(const CVector &CameraTarget, float, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + float BackOffset = 0.19f; + 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(((CPed*)CamTargetEntity)->bIsDucking) + BackOffset = 0.8f; + CamTargetEntity->GetMatrix().UpdateRW(); + CamTargetEntity->UpdateRwFrame(); + CamTargetEntity->UpdateRpHAnim(); + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + Source.x -= BackOffset*Cos(m_fInitialPlayerOrientation); + Source.y -= BackOffset*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; + 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; +} + +float fDuckingBackOffset = 0.5f; +float fDuckingRightOffset = 0.18f; + +void +CCam::Process_M16_1stPerson(const CVector &CameraTarget, float, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + float BackOffset = 0.3f; + static bool FailedTestTwelveFramesAgo = false; + RwV3d HeadPos; + CVector TargetCoors; + + bool isAttached = ((CPed*)CamTargetEntity)->IsPlayer() && ((CPed*)CamTargetEntity)->m_attachedTo; + + FOV = DefaultFOV; + TargetCoors = CameraTarget; + + if(ResetStatics){ + if(isAttached) + Beta = 0.0f; + else + 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; + } + + // 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 if(Mode == MODE_HELICANNON_1STPERSON){ + LookLeftRight /= 128.0f; + LookUpDown /= 128.0f; + Beta += LookLeftRight*Abs(LookLeftRight)*0.56f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + Alpha += LookUpDown*Abs(LookUpDown)*0.48f/14.0f * FOV/80.0f * CTimer::GetTimeStep(); + }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(); + } + if (!isAttached) { + while(Beta >= TWOPI) Beta -= TWOPI; + while(Beta < 0) Beta += TWOPI; + } + if(Alpha > DEGTORAD(60.0f)) Alpha = DEGTORAD(60.0f); + else if(Alpha < -DEGTORAD(89.5f)) Alpha = -DEGTORAD(89.5f); + + if(((CPed*)CamTargetEntity)->bIsDucking) + BackOffset = 0.8f; + if(isAttached){ + CMatrix mat, rot; + CPed *TargetPed = (CPed*)CamTargetEntity; + TargetPed->PositionAttachedPed(); + CamTargetEntity->GetMatrix().UpdateRW(); + CamTargetEntity->UpdateRwFrame(); + CamTargetEntity->UpdateRpHAnim(); + + HeadPos.x = 0.0f; + HeadPos.y = 0.0f; + HeadPos.z = 0.0f; + TargetPed->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source += 0.1f*CamTargetEntity->GetUp(); + Source -= BackOffset*CamTargetEntity->GetForward(); + + if(TargetPed->m_attachRotStep < PI){ + if(Beta > TargetPed->m_attachRotStep){ + Beta = TargetPed->m_attachRotStep; + CAutomobile *heli = (CAutomobile*)TargetPed->m_attachedTo; + if(heli->IsVehicle() && heli->IsCar() && heli->IsRealHeli() && heli->m_fHeliOrientation > 0.0f){ + float heliOrient = heli->m_fHeliOrientation + CTimer::GetTimeStep()*0.01f; + if(heliOrient < 0.0f) heliOrient += TWOPI; + else if(heliOrient > TWOPI) heliOrient -= TWOPI; + heli->SetHeliOrientation(heliOrient); + } + }else if(Beta < -TargetPed->m_attachRotStep){ + Beta = -TargetPed->m_attachRotStep; + CAutomobile *heli = (CAutomobile*)TargetPed->m_attachedTo; + if(heli->IsVehicle() && heli->IsCar() && heli->IsRealHeli() && heli->m_fHeliOrientation > 0.0f){ + float heliOrient = heli->m_fHeliOrientation - CTimer::GetTimeStep()*0.01f; + if(heliOrient < 0.0f) heliOrient += TWOPI; + else if(heliOrient > TWOPI) heliOrient -= TWOPI; + heli->SetHeliOrientation(heliOrient); + } + } + }else{ + while(Beta < -PI) Beta += TWOPI; + while(Beta >= PI) Beta -= TWOPI; + } + + mat = TargetPed->m_attachedTo->GetMatrix(); + rot.SetRotateX(Alpha); + switch(TargetPed->m_attachType){ + case 0: rot.RotateZ(Beta); break; + case 1: rot.RotateZ(Beta + HALFPI); break; + case 2: rot.RotateZ(Beta + PI); break; + case 3: rot.RotateZ(Beta - HALFPI); break; + } + mat = mat * rot; + Front = mat.GetForward(); + Up = mat.GetUp(); + TargetCoors = Source + 3.0f*Front; + RwCameraSetNearClipPlane(Scene.camera, 0.4f); + + float Rotation = CGeneral::GetATanOfXY(Front.x, Front.y) - HALFPI; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationCur = Rotation; + ((CPed*)TheCamera.pTargetEntity)->m_fRotationDest = Rotation; + }else{ + CamTargetEntity->GetMatrix().UpdateRW(); + CamTargetEntity->UpdateRwFrame(); + CamTargetEntity->UpdateRpHAnim(); + 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; + if(((CPed*)CamTargetEntity)->bIsDucking){ + Source.x -= fDuckingBackOffset*CamTargetEntity->GetForward().x; + Source.y -= fDuckingBackOffset*CamTargetEntity->GetForward().y; + Source.x -= fDuckingRightOffset*CamTargetEntity->GetRight().x; + Source.y -= fDuckingRightOffset*CamTargetEntity->GetRight().y; + }else{ + Source.x -= BackOffset*CamTargetEntity->GetForward().x; + Source.y -= BackOffset*CamTargetEntity->GetForward().y; + } + + 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; + } +} + +float fBike1stPersonOffsetZ = 0.15f; + +void +CCam::Process_1stPerson(const CVector &CameraTarget, float TargetOrientation, float SpeedVar, float TargetSpeedVar) +{ + float BackOffset = 0.3f; + 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; + } + TheCamera.m_fAvoidTheGeometryProbsTimer = 0.0f; + 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; + } + + CamTargetEntity->GetMatrix().UpdateRW(); + CamTargetEntity->UpdateRwFrame(); + CamTargetEntity->UpdateRpHAnim(); + + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + if(((CPed*)CamTargetEntity)->bIsDucking){ + Source.x -= fDuckingBackOffset*CamTargetEntity->GetForward().x; + Source.y -= fDuckingBackOffset*CamTargetEntity->GetForward().y; + Source.x -= fDuckingRightOffset*CamTargetEntity->GetRight().x; + Source.y -= fDuckingRightOffset*CamTargetEntity->GetRight().y; + }else{ + Source.x -= BackOffset*CamTargetEntity->GetForward().x; + Source.y -= BackOffset*CamTargetEntity->GetForward().y; + } + + 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()); + + if(((CVehicle*)CamTargetEntity)->IsBike() && + (((CBike*)CamTargetEntity)->bWheelieCam || TheCamera.m_fAvoidTheGeometryProbsTimer > 0.0f)){ + if(CPad::GetPad(0)->GetLeftShoulder2() || CPad::GetPad(0)->GetRightShoulder2()){ + TheCamera.m_fAvoidTheGeometryProbsTimer = 0.0f; + ((CBike*)CamTargetEntity)->bWheelieCam = false; + }else if(Process_WheelCam(CameraTarget, TargetOrientation, SpeedVar, TargetSpeedVar)){ + if(((CBike*)CamTargetEntity)->bWheelieCam) + TheCamera.m_fAvoidTheGeometryProbsTimer = 50.0f; + else{ + TheCamera.m_fAvoidTheGeometryProbsTimer -= CTimer::GetTimeStep(); + ((CBike*)CamTargetEntity)->bWheelieCam = true; + } + return; + }else{ + TheCamera.m_fAvoidTheGeometryProbsTimer = 0.0f; + ((CBike*)CamTargetEntity)->bWheelieCam = false; + } + } + + CMatrix *matrix = &CamTargetEntity->GetMatrix(); + if(((CVehicle*)CamTargetEntity)->IsBike()){ + ((CBike*)CamTargetEntity)->CalculateLeanMatrix(); + matrix = &((CBike*)CamTargetEntity)->m_leanMatrix; + } + + 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(*matrix, CamPos); + Source += CamTargetEntity->GetPosition(); + if(((CVehicle*)CamTargetEntity)->IsBoat()) + Source.z += 0.5f; + else if(((CVehicle*)CamTargetEntity)->IsBike() && ((CVehicle*)CamTargetEntity)->pDriver){ + CVector Neck(0.0f, 0.0f, 0.0f); + ((CVehicle*)CamTargetEntity)->pDriver->m_pedIK.GetComponentPosition(Neck, PED_NECK); + Neck += ((CVehicle*)CamTargetEntity)->m_vecMoveSpeed * CTimer::GetTimeStep(); + Source.z = Neck.z + fBike1stPersonOffsetZ; + } + + 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 = matrix->GetForward(); + Front.Normalise(); + Up = matrix->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); + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(CamTargetEntity->GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_HEAD)); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + RwV3dTransformPoints(&HeadPos, &HeadPos, 1, &mats[idx]); + RwV3d scl = { 0.0f, 0.0f, 0.0f }; + RwMatrixScale(&mats[idx], &scl, rwCOMBINEPRECONCAT); + + 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); + + if(((CPed*)CamTargetEntity)->IsPlayer() && ((CPed*)CamTargetEntity)->m_attachedTo){ + CPed *pedTarget = ((CPed*)CamTargetEntity); + float NewBeta; + switch(pedTarget->m_attachType){ + case 0: + NewBeta = pedTarget->GetForward().Heading() + HALFPI; + break; + case 1: + NewBeta = pedTarget->GetForward().Heading() + PI; + break; + case 2: + NewBeta = pedTarget->GetForward().Heading() - HALFPI; + break; + case 3: + NewBeta = pedTarget->GetForward().Heading(); + break; + } + + float BetaOffset = Beta - NewBeta; + if(BetaOffset > PI) BetaOffset -= TWOPI; + else if(BetaOffset < PI) BetaOffset += TWOPI; + + BetaOffset = Clamp(BetaOffset, -pedTarget->m_attachRotStep, pedTarget->m_attachRotStep); + Beta = NewBeta + BetaOffset; + } + + 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); +} + +float fCameraNearClipMult = 0.15f; + +void +CCam::Process_Sniper(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + float BackOffset = 0.19f; + 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; + } + + if(((CPed*)CamTargetEntity)->bIsDucking) + BackOffset = 0.8f; + CamTargetEntity->GetMatrix().UpdateRW(); + CamTargetEntity->UpdateRwFrame(); + CamTargetEntity->UpdateRpHAnim(); + ((CPed*)CamTargetEntity)->m_pedIK.GetComponentPosition(HeadPos, PED_HEAD); + Source = HeadPos; + Source.z += 0.1f; + if(((CPed*)CamTargetEntity)->bIsDucking){ + Source.x -= fDuckingBackOffset*CamTargetEntity->GetForward().x; + Source.y -= fDuckingBackOffset*CamTargetEntity->GetForward().y; + Source.x -= fDuckingRightOffset*CamTargetEntity->GetRight().x; + Source.y -= fDuckingRightOffset*CamTargetEntity->GetRight().y; + }else{ + Source.x -= BackOffset*CamTargetEntity->GetForward().x; + Source.y -= BackOffset*CamTargetEntity->GetForward().y; + } + + // 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(Mode == MODE_CAMERA){ + if(FOV < 3.0f) + FOV = 3.0f; + }else{ + 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); + else if(Mode == MODE_CAMERA) + RwCameraSetNearClipPlane(Scene.camera, ((15.0f - Min(FOV, 15.0f))*fCameraNearClipMult + 1.0f)*DEFAULT_NEAR); + 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; +} + +float INIT_SYPHON_GROUND_DIST = 2.419f; +float INIT_SYPHON_ALPHA_OFFSET = -DEGTORAD(3.0f); +float INIT_SYPHON_DEGREE_OFFSET = -DEGTORAD(30.0f); +float FrontOffsetSyphon = -DEGTORAD(25.5f); // unused +float INIT_SYPHON_Z_OFFSET = -0.5f; + +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 = INIT_SYPHON_DEGREE_OFFSET; + // unused AngleToGoTo + // unused AngleToGoToSpeed + // unused DistBetweenPedAndPlayerPreviouslyOn + static float HeightDown = INIT_SYPHON_Z_OFFSET; + static float AlphaOffset = INIT_SYPHON_ALPHA_OFFSET; + static bool NegateBetaOffset = true; + CVector TargetCoors; + float fAimingDist; + float TargetAlpha; + + bool StandingOnMovingThing = false; + TargetCoors = CameraTarget; + AlphaOffset = INIT_SYPHON_ALPHA_OFFSET; + float GroundDist = INIT_SYPHON_GROUND_DIST; + + 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){ + BetaOffset = INIT_SYPHON_DEGREE_OFFSET; + Beta = CGeneral::GetATanOfXY(Source.x - TargetCoors.x, Source.y - TargetCoors.y); + // some unuseds + ResetStatics = false; + } + if(NegateBetaOffset) + BetaOffset = -INIT_SYPHON_DEGREE_OFFSET; + Beta = NewBeta + BetaOffset; + Source = TargetCoors; + Source.x += GroundDist*Cos(Beta); + Source.y += GroundDist*Sin(Beta); + CPhysical *ground = (CPhysical*)((CPed*)CamTargetEntity)->m_pCurSurface; + if(ground && (ground->IsVehicle() || ground->IsObject())) + StandingOnMovingThing = true; + TargetCoors.z += m_fSyphonModeTargetZOffSet; + + bool PlayerTooClose = false; + fAimingDist = (TheCamera.m_cvecAimingTargetCoors - TargetCoors).Magnitude2D(); + if(fAimingDist < 6.5f){ + fAimingDist = 6.5f; + PlayerTooClose = true; + } + TargetAlpha = CGeneral::GetATanOfXY(fAimingDist, TheCamera.m_cvecAimingTargetCoors.z - TargetCoors.z); + if(ResetStatics) // BUG: can never happen + Alpha = -TargetAlpha; + while(TargetAlpha >= PI) TargetAlpha -= 2*PI; + while(TargetAlpha < -PI) TargetAlpha += 2*PI; + while(Alpha >= PI) Alpha -= 2*PI; + while(Alpha < -PI) Alpha += 2*PI; + + // inlined + if(StandingOnMovingThing) + WellBufferMe(-TargetAlpha, &Alpha, &AlphaSpeed, 0.07f/2.0f, 0.015f/2.0f, true); + else + WellBufferMe(-TargetAlpha, &Alpha, &AlphaSpeed, 0.07f, 0.015f, true); + + Source.z += GroundDist*Sin(Alpha+AlphaOffset) + GroundDist*0.2f; + if(Source.z < TargetCoors.z + HeightDown) + Source.z = TargetCoors.z + HeightDown; + + if(!PlayerTooClose){ + CColPoint point; + CEntity *entity = nil; + CWorld::pIgnoreEntity = CamTargetEntity; + if(CWorld::ProcessLineOfSight(TheCamera.m_cvecAimingTargetCoors, Source, point, entity, true, false, false, true, false, false, true)){ + CVector TestFront = TheCamera.m_cvecAimingTargetCoors - Source; + TestFront.Normalise(); + CVector CamToPlayer = CameraTarget - Source; + CVector CamToCol = point.point - Source; + if(DotProduct(TestFront, CamToCol) > DotProduct(TestFront, CamToPlayer)){ + // collision is beyond player + float ColDist = (TheCamera.m_cvecAimingTargetCoors - point.point).Magnitude(); + CVector PlayerToTarget = TheCamera.m_cvecAimingTargetCoors - CameraTarget; + float PlayerToTargetDist = PlayerToTarget.Magnitude(); + PlayerToTarget.Normalise(); + CVector Center = TheCamera.m_cvecAimingTargetCoors - ColDist*PlayerToTarget; + float Radius = (point.point - Center).Magnitude(); + if(CWorld::TestSphereAgainstWorld(Center, Radius, nil, true, false, false, true, false, true)){ + CVector LineToCol = gaTempSphereColPoints[0].point - Center; + LineToCol -= DotProduct(LineToCol, PlayerToTarget)*PlayerToTarget; + // unused + CVector LineToPrevCol = point.point - Center; + LineToPrevCol -= DotProduct(LineToPrevCol, PlayerToTarget)*PlayerToTarget; + float LineDist = LineToCol.Magnitude(); + float NewBetaOffset = 0.0f; + if(LineDist > 0.0f && ColDist > 0.1f){ + // scale offset at center to offset at player + float DistOffset = LineDist/ColDist * PlayerToTargetDist; + // turn into an angle + NewBetaOffset = 0.9f*Asin(Min(DistOffset/GroundDist, 1.0f)); + } + if(NewBetaOffset < BetaOffset){ + float Ratio = NewBetaOffset / BetaOffset; + BetaOffset = NewBetaOffset; + Beta = NewBeta + NewBetaOffset; + GroundDist *= Max(Ratio, 0.5f); + Source.x = TargetCoors.x + GroundDist*Cos(Beta); + Source.y = TargetCoors.y + GroundDist*Sin(Beta); + Source.z += (1.0f-Ratio)*0.5f; + } + } + } + } + CWorld::pIgnoreEntity = nil; + } + + Front = TheCamera.m_cvecAimingTargetCoors - Source; + float TargetDistGround = Front.Magnitude2D(); + Front.Normalise(); + m_cvecTargetCoorsForFudgeInter = Source + TargetDistGround*Front; + m_cvecTargetCoorsForFudgeInter.z = TargetCoors.z; + + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, CameraTarget + CVector(0.0f, 0.0f, 0.75f), Source, FOV); + Source.z = OrigSource.z; + + 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; + + 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; + + TargetCoors = CameraTarget; + TargetCoors.z += m_fSyphonModeTargetZOffSet; + m_cvecTargetCoorsForFudgeInter = TargetCoors; + + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + + Front = TargetCoors - Source; + GetVectorsReadyForRW(); +} + +float MAX_HEIGHT_UP = 15.0f; +float WATER_Z_ADDITION = 2.75f; +float WATER_Z_ADDITION_MIN = 1.5f; +float SMALLBOAT_CLOSE_ALPHA_MINUS = 0.2f; +float afBoatBetaDiffMult[3] = { 0.15f, 0.07f, 0.01f }; +float afBoatBetaSpeedDiffMult[3] = { 0.02f, 0.015f, 0.005f }; + +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 float TargetWhenChecksWereOn = 0.0f; + static float CenterObscuredWhenChecksWereOn = 0.0f; + static float WaterZAddition = 2.75f; + float WaterLevel = 0.0f; + float MaxHeightUp = MAX_HEIGHT_UP; + static float WaterLevelBuffered = 0.0f; + static float WaterLevelSpeed = 0.0f; + float BetaDiffMult = 0.0f; + float BetaSpeedDiffMult = 0.0f; + + Beta = CGeneral::GetATanOfXY(TargetCoors.x - Source.x, TargetCoors.y - Source.y); + FOV = DefaultFOV; + float TargetAlpha = 0.0f; + + if(ResetStatics){ + CenterObscuredWhenChecksWereOn = 0.0f; + TargetWhenChecksWereOn = 0.0f; + }else if(DirectionWasLooking != LOOKING_FORWARD) + Beta = TargetOrientation; + + if(!CWaterLevel::GetWaterLevelNoWaves(TargetCoors.x, TargetCoors.y, TargetCoors.z, &WaterLevel)) + WaterLevel = TargetCoors.z - 0.5f; + if(ResetStatics){ + WaterLevelBuffered = WaterLevel; + WaterLevelSpeed = 0.0f; + } + WellBufferMe(WaterLevel, &WaterLevelBuffered, &WaterLevelSpeed, 0.2f, 0.07f, false); + + static float FixerForGoingBelowGround = 0.4f; + if(-FixerForGoingBelowGround < TargetCoors.z-WaterLevelBuffered+WATER_Z_ADDITION) + WaterLevelBuffered += TargetCoors.z-WaterLevelBuffered+WATER_Z_ADDITION - FixerForGoingBelowGround; + + CVector BoatDimensions = CamTargetEntity->GetColModel()->boundingBox.GetSize(); + float BoatSize = BoatDimensions.Magnitude2D(); + int index = 0; + TheCamera.GetArrPosForVehicleType(((CVehicle*)CamTargetEntity)->GetVehicleAppearance(), index); + if(TheCamera.CarZoomIndicator == CAM_ZOOM_1){ + TargetAlpha = ZmOneAlphaOffset[index]; + BetaDiffMult = afBoatBetaDiffMult[0]; + BetaSpeedDiffMult = afBoatBetaSpeedDiffMult[0]; + }else if(TheCamera.CarZoomIndicator == CAM_ZOOM_2){ + TargetAlpha = ZmTwoAlphaOffset[index]; + BetaDiffMult = afBoatBetaDiffMult[1]; + BetaSpeedDiffMult = afBoatBetaSpeedDiffMult[1]; + }else if(TheCamera.CarZoomIndicator == CAM_ZOOM_3){ + TargetAlpha = ZmThreeAlphaOffset[index]; + BetaDiffMult = afBoatBetaDiffMult[2]; + BetaSpeedDiffMult = afBoatBetaSpeedDiffMult[2]; + } + if(TheCamera.CarZoomIndicator == CAM_ZOOM_1 && BoatSize < 10.0f){ + TargetAlpha -= SMALLBOAT_CLOSE_ALPHA_MINUS; + BoatSize = 10.0f; + }else if(CCullZones::Cam1stPersonForPlayer()){ + float Water = 0.0f; + // useless call + //CWaterLevel::GetWaterLevelNoWaves(TargetCoors.x, TargetCoors.y, TargetCoors.z, &Water); + Water = (WaterLevel + WATER_Z_ADDITION_MIN - WaterLevelBuffered - WATER_Z_ADDITION)/(BoatDimensions.z/2.0f + MaxHeightUp); + TargetAlpha = Asin(Clamp(Water, -1.0f, 1.0f)); + } + + if(ResetStatics){ + Alpha = TargetAlpha; + AlphaSpeed = 0.0f; + } + WellBufferMe(TargetAlpha, &Alpha, &AlphaSpeed, 0.15f, 0.07f, true); + + if(ResetStatics){ + Beta = TargetOrientation; + DeltaBeta = 0.0f; + } + // inlined + WellBufferMe(TargetOrientation, &Beta, &BetaSpeed, BetaDiffMult * ((CVehicle*)CamTargetEntity)->m_vecMoveSpeed.Magnitude(), BetaSpeedDiffMult, true); + + Source = (TheCamera.CarZoomValueSmooth+BoatSize) * CVector(-Cos(Beta), -Sin(Beta), 0.0f) + TargetCoors; + Source.z = WaterLevelBuffered + WATER_Z_ADDITION + (BoatDimensions.z/2.0f + MaxHeightUp) * Sin(Alpha); + + m_cvecTargetCoorsForFudgeInter = TargetCoors; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + Front = TargetCoors - Source; + Front.Normalise(); + + + float TargetRoll; + if(CPad::GetPad(0)->GetDPadLeft() || CPad::GetPad(0)->GetDPadRight()){ +#ifdef FIX_BUGS + float fwdSpeed = 180.0f*DotProduct(((CVehicle*)CamTargetEntity)->m_vecMoveSpeed, CamTargetEntity->GetForward()); + if(fwdSpeed > 210.0f) fwdSpeed = 210.0f; +#endif + if(CPad::GetPad(0)->GetDPadLeft()) + TargetRoll = DEGTORAD(10.0f)*TiltOverShoot[index] + f_max_role_angle; + else + TargetRoll = -(DEGTORAD(10.0f)*TiltOverShoot[index] + f_max_role_angle); + CVector FwdTarget = CamTargetEntity->GetForward(); + FwdTarget.Normalise(); + float AngleDiff = DotProduct(FwdTarget, Front); + AngleDiff = Acos(Min(Abs(AngleDiff), 1.0f)); +#ifdef FIX_BUGS + TargetRoll *= fwdSpeed/210.0f * Sin(AngleDiff); +#else + TargetRoll *= Sin(AngleDiff); +#endif + }else{ + float fwdSpeed = 180.0f*DotProduct(((CVehicle*)CamTargetEntity)->m_vecMoveSpeed, CamTargetEntity->GetForward()); + if(fwdSpeed > 210.0f) fwdSpeed = 210.0f; + TargetRoll = CPad::GetPad(0)->GetLeftStickX()/128.0f * fwdSpeed/210.0f; + CVector FwdTarget = CamTargetEntity->GetForward(); + FwdTarget.Normalise(); + float AngleDiff = DotProduct(FwdTarget, Front); + AngleDiff = Acos(Min(Abs(AngleDiff), 1.0f)); + TargetRoll *= (DEGTORAD(10.0f)*TiltOverShoot[index] + f_max_role_angle) * Sin(AngleDiff); + } + + WellBufferMe(TargetRoll, &f_Roll, &f_rollSpeed, 0.15f, 0.07f, false); + Up = CVector(Cos(f_Roll + HALFPI), 0.0f, Sin(f_Roll + HALFPI)); + Up.Normalise(); + Front.Normalise(); + CVector Left = CrossProduct(Up, Front); + Left.Normalise(); + Up = CrossProduct(Front, Left); + Up.Normalise(); + + ResetStatics = false; +} + +float FIGHT_HORIZ_DIST = 3.0f; +float FIGHT_VERT_DIST = 1.0f; +float FIGHT_BETA_ANGLE = 125.0f; + +void +CCam::Process_Fight_Cam(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + if(!CamTargetEntity->IsPed()) + return; + + FOV = DefaultFOV; + float HorizDist = FIGHT_HORIZ_DIST; + float VertDist = FIGHT_VERT_DIST; + float BetaLeft, BetaRight, DeltaBetaLeft, DeltaBetaRight; + static bool PreviouslyFailedBuildingChecks = false; + float TargetCamHeight; + CVector TargetCoors; + + m_fMinDistAwayFromCamWhenInterPolating = FIGHT_HORIZ_DIST; + Front = Source - CameraTarget; + if(ResetStatics) + 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 - DEGTORAD(FIGHT_BETA_ANGLE); + BetaRight = TargetOrientation + DEGTORAD(FIGHT_BETA_ANGLE); + 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; + } + + WellBufferMe(m_fTargetBeta, &Beta, &BetaSpeed, 0.015f, 0.007f, true); + + Source = CameraTarget + HorizDist*CVector(Cos(Beta), Sin(Beta), 0.0f); + Source.z += VertDist; + + WellBufferMe(TargetOrientation, &m_fBufferedTargetOrientation, &m_fBufferedTargetOrientationSpeed, 0.07f, 0.004f, true); + TargetCoors = CameraTarget + 0.1f*CVector(Cos(m_fBufferedTargetOrientation), Sin(m_fBufferedTargetOrientation), 0.0f); + + TargetCamHeight = CameraTarget.z - Source.z + Max(m_fPedBetweenCameraHeightOffset, m_fDimensionOfHighestNearCar) + VertDist; + 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; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + 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; +#ifdef FIX_BUGS + // this would crash, not nice when cycling debug mode + if(TheCamera.m_arrPathArray[0].m_arr_PathData == nil) + return; +#endif + + 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; +} + +CVector vecWheelCamBoatOffset(-0.5f, -0.8f, 0.3f); +CVector vecWheelCamBoatOffsetAlt(0.2f, -0.2f, -0.3f); +float fWheelCamCarXOffset = 0.33f; +float fWheelCamBikeXOffset = 0.2f; + +bool +CCam::Process_WheelCam(const CVector&, float, float, float) +{ + FOV = DefaultFOV; + + CVector WheelPos; + 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{ + WheelPos = CamTargetEntity->GetColModel()->boundingBox.min; + WheelPos.x -= 0.33f; + WheelPos.y = -2.3f; + WheelPos.z = 0.3f; + Source = CamTargetEntity->GetMatrix() * WheelPos; + Front = CamTargetEntity->GetForward(); + } + + CVector NewUp, Right; + if(CamTargetEntity->IsVehicle() && + (((CVehicle*)CamTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI || + ((CVehicle*)CamTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE)){ + WheelPos.x = -1.55f; + Right = CamTargetEntity->GetRight(); + NewUp = CamTargetEntity->GetUp(); + Source = CamTargetEntity->GetMatrix() * WheelPos; + }else if(CamTargetEntity->IsVehicle() && ((CVehicle*)CamTargetEntity)->IsBoat()){ + NewUp = CVector(0.0f, 0.0f, 1.0f); + Right = CrossProduct(Front, NewUp); + Right.Normalise(); + NewUp = CrossProduct(Right, Front); + NewUp.Normalise(); + + CVector BoatCamPos(0.0f, 0.0f, 0.0f); + if(((CVehicle*)CamTargetEntity)->pDriver){ + ((CVehicle*)CamTargetEntity)->pDriver->m_pedIK.GetComponentPosition(BoatCamPos, PED_HEAD); + BoatCamPos += ((CVehicle*)CamTargetEntity)->m_vecMoveSpeed * CTimer::GetTimeStep(); + BoatCamPos += vecWheelCamBoatOffset.x * Right; + BoatCamPos += vecWheelCamBoatOffset.y * CamTargetEntity->GetForward(); + BoatCamPos.z += vecWheelCamBoatOffset.z; + if(CamTargetEntity->GetModelIndex() == MI_PREDATOR){ + BoatCamPos += vecWheelCamBoatOffsetAlt.x * Right; + BoatCamPos += vecWheelCamBoatOffsetAlt.y * CamTargetEntity->GetForward(); + BoatCamPos.z += vecWheelCamBoatOffsetAlt.z; + } + Source = BoatCamPos; + }else + Source.z += 2.0f*vecWheelCamBoatOffset.z; + }else if(CamTargetEntity->IsVehicle() && ((CVehicle*)CamTargetEntity)->IsBike()){ + NewUp = CVector(0.0f, 0.0f, 1.0f); + Right = CrossProduct(Front, NewUp); + Right.Normalise(); + NewUp = CrossProduct(Right, Front); + NewUp.Normalise(); + + WheelPos.z += fWheelCamCarXOffset - fWheelCamBikeXOffset; + Source = CamTargetEntity->GetPosition(); + Source += WheelPos.x * CamTargetEntity->GetRight(); + Source += WheelPos.y * Front; + Source += WheelPos.z * Up; + }else{ + NewUp = CVector(0.0f, 0.0f, 1.0f); + 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; + + CEntity *entity = nil; + CColPoint point; + CWorld::pIgnoreEntity = CamTargetEntity; + bool blocked = CWorld::ProcessLineOfSight(Source, CamTargetEntity->GetPosition(), point, entity, true, false, false, true, false, false, true); + CWorld::pIgnoreEntity = nil; + return !blocked; +} + +int BOAT_UNDERWATER_CAM_BLUR = 20; +float BOAT_UNDERWATER_CAM_COLORMAG_LIMIT = 10.0f; + +//--MIAIM: done +void +CCam::Process_Fixed(const CVector &CameraTarget, float, float, float) +{ + if(DirectionWasLooking != LOOKING_FORWARD) + DirectionWasLooking = LOOKING_FORWARD; + + Source = m_cvecCamFixedModeSource; + Front = CameraTarget - Source; + Front.Normalise(); + 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; + + float WaterZ = 0.0f; + if(CWaterLevel::GetWaterLevel(Source, &WaterZ, true) && Source.z < WaterZ){ + float WaterLum = Sqrt(SQR(CTimeCycle::GetWaterRed()) + SQR(CTimeCycle::GetWaterGreen()) + SQR(CTimeCycle::GetWaterBlue())); + if(WaterLum > BOAT_UNDERWATER_CAM_COLORMAG_LIMIT){ + float f = BOAT_UNDERWATER_CAM_COLORMAG_LIMIT/WaterLum; + TheCamera.SetMotionBlur(CTimeCycle::GetWaterRed()*f, + CTimeCycle::GetWaterGreen()*f, + CTimeCycle::GetWaterBlue()*f, BOAT_UNDERWATER_CAM_BLUR, MOTION_BLUR_LIGHT_SCENE); + }else{ + TheCamera.SetMotionBlur(CTimeCycle::GetWaterRed(), + CTimeCycle::GetWaterGreen(), + CTimeCycle::GetWaterBlue(), BOAT_UNDERWATER_CAM_BLUR, MOTION_BLUR_LIGHT_SCENE); + } + } + +#ifdef PC_PLAYER_CONTROLS + if(FrontEndMenuManager.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_LightHouse(const CVector &CameraTarget, float, float, float) +{ + static float Timer; + + Source = CameraTarget; + Source.x = 474.3f; + Source.y = -1717.6f; + + int CamMode; + if(CameraTarget.z > 57.0f && (CameraTarget-Source).Magnitude2D() > 3.2f){ + // Outside at top + if(Timer > 0.0f){ + Timer -= CTimer::GetTimeStep(); + CamMode = 1; + }else{ + Timer = -24.0f; + CamMode = 2; + } + }else if(CameraTarget.z > 57.0f){ + // Inside at top + if(Timer < 0.0f){ + Timer += CTimer::GetTimeStep(); + CamMode = 2; + }else{ + Timer = 24.0f; + CamMode = 1; + } + }else{ + Timer = 0.0f; + CamMode = 0; + } + + if(CamMode == 2){ + Source.z = 57.5f; + Front = Source - CameraTarget; + Front.Normalise(); + Source.x = CameraTarget.x - 5.0f*Front.x; + Source.y = CameraTarget.y - 5.0f*Front.y; + }else if(CamMode == 1){ + Front = CameraTarget - Source; + Front.Normalise(); + Source.x = CameraTarget.x - 2.0f*Front.x; + Source.y = CameraTarget.y - 2.0f*Front.y; + }else{ + Source.z += 4.0f; + Front = CameraTarget - Source; + Front.Normalise(); + Source -= 4.0f*Front; + Source.z = Min(Source.z, 55.0f); + 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) // uh, sure... + FOV = TheCamera.m_fFovForTrain; +} + +void +CCam::Process_Player_Fallen_Water(const CVector &CameraTarget, float TargetOrientation, float, float) +{ + CColPoint colPoint; + CEntity *entity = nil; + + FOV = DefaultFOV; + Source = m_vecLastAboveWaterCamPosition; + Source.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(); +} + +void +CCam::Process_SpecialFixedForSyphon(const CVector &CameraTarget, float, float, float) +{ + Source = m_cvecCamFixedModeSource; + m_cvecTargetCoorsForFudgeInter = CameraTarget; + m_cvecTargetCoorsForFudgeInter.z += m_fSyphonModeTargetZOffSet; + Front = CameraTarget - Source; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, m_cvecTargetCoorsForFudgeInter, Source, FOV); + 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, nil, false); + + 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, nil, 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, nil, false); + + 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(); +} + +float DEADCAM_HEIGHT_START = 2.0f; +float DEADCAM_HEIGHT_RATE = 0.04f; +float DEADCAM_WAFT_AMPLITUDE = 2.0f; +float DEADCAM_WAFT_RATE = 600.0f; +float DEADCAM_WAFT_TILT_AMP = -0.35f; + +void +CCam::ProcessPedsDeadBaby(void) +{ + CVector TargetCoors; + CVector CamPos; + + if(TheCamera.pTargetEntity->IsPed()) + ((CPed*)TheCamera.pTargetEntity)->m_pedIK.GetComponentPosition(TargetCoors, PED_MID); + else if(TheCamera.pTargetEntity->IsVehicle()){ + TargetCoors = TheCamera.pTargetEntity->GetPosition(); + TargetCoors.z += TheCamera.pTargetEntity->GetColModel()->boundingBox.max.z; + }else + return; + + if(ResetStatics){ + TheCamera.m_uiTimeLastChange = CTimer::GetTimeInMilliseconds(); + CamPos = TargetCoors; + CamPos.z += DEADCAM_HEIGHT_START; + float WaterZ = 0.0f; + if(CWaterLevel::GetWaterLevelNoWaves(TargetCoors.x, TargetCoors.y, TargetCoors.z, &WaterZ)){ + if(WaterZ + 1.5f > CamPos.z) + CamPos.z = WaterZ + 1.5f; + } + CVector Right = CrossProduct(TheCamera.pTargetEntity->GetForward(), CVector(0.0f, 0.0f, 1.0f)); + Right.z = 0.0f; + Right.Normalise(); + Front = TargetCoors - CamPos; + Front.Normalise(); + Up = CrossProduct(Right, Front); + Up.Normalise(); + ResetStatics = false; + }else{ + CamPos = Source; + if(CWorld::TestSphereAgainstWorld(CamPos+CVector(0.0f, 0.0f, 0.2f), 0.3f, TheCamera.pTargetEntity, true, true, false, true, false, true) == nil) + CamPos.z += DEADCAM_HEIGHT_RATE*CTimer::GetTimeStep(); + CVector Right = CrossProduct(TheCamera.pTargetEntity->GetForward(), CVector(0.0f, 0.0f, 1.0f)); + Right.z = 0.0f; + Right.Normalise(); + + float Time = CTimer::GetTimeInMilliseconds() - TheCamera.m_uiTimeLastChange; + CVector WaftOffset = DEADCAM_WAFT_AMPLITUDE * Min(1000.0f,Time)/1000.0f * Sin(Time/DEADCAM_WAFT_RATE) * Right; + CVector WaftPos = TargetCoors + WaftOffset; + WaftPos.z = CamPos.z; + CVector WaftFront = WaftPos - CamPos; + WaftFront.Normalise(); + if(CWorld::TestSphereAgainstWorld(CamPos+0.2f*WaftFront, 0.3f, TheCamera.pTargetEntity, true, true, false, true, false, true) == nil) + CamPos = WaftPos; + + Front = CVector(0.0f, 0.0f, -1.0f); + Front += Cos(Time/DEADCAM_WAFT_RATE) * DEADCAM_WAFT_TILT_AMP * Min(2000.0f,Time)/2000.0f * Right; + + Front.Normalise(); + Up = CrossProduct(Right, Front); + Up.Normalise(); + } + + Source = CamPos; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetCoors, Source, FOV); + TheCamera.m_bMoveCamToAvoidGeom = false; +} + +float ARRESTDIST_BEHIND_COP = 5.0f; +float ARRESTDIST_RIGHTOF_COP = 3.0f; +float ARRESTDIST_ABOVE_COP = 1.4f; // unused +float ARRESTDIST_MINFROM_PLAYER = 8.0f; +float ARRESTCAM_LAMP_BEST_DIST = 17.0f; +float ARRESTCAM_ROTATION_SPEED = 0.1f; +float ARRESTCAM_ROTATION_UP = 0.05f; +float ARRESTCAM_S_ROTATION_UP = 0.1f; +float ARRESTDIST_ALONG_GROUND = 5.0f; +float ARRESTDIST_SIDE_GROUND = 10.0f; +float ARRESTDIST_ABOVE_GROUND = 0.7f; +float ARRESTCAM_LAMPPOST_ROTATEDIST = 10.0f; +float ARRESTCAM_LAMPPOST_TRANSLATE = 0.1f; + +bool +CCam::GetLookAlongGroundPos(CEntity *Target, CPed *Cop, CVector &TargetCoors, CVector &SourceOut) +{ + if(Target == nil || Cop == nil) + return false; + CVector CopToTarget = TargetCoors - Cop->GetPosition(); + CopToTarget.z = 0.0f; + CopToTarget.Normalise(); + SourceOut = TargetCoors + ARRESTDIST_ALONG_GROUND*CopToTarget; + CVector Side = CrossProduct(CopToTarget, CVector(0.0f, 0.0f, 1.0f)); + SourceOut += ARRESTDIST_SIDE_GROUND*Side; + SourceOut.z += 5.0f; + bool found = false; + float ground = CWorld::FindGroundZFor3DCoord(SourceOut.x, SourceOut.y, SourceOut.z, &found); + if(found) + SourceOut.z = ground + ARRESTDIST_ABOVE_GROUND; + return true; +} + +bool +CCam::GetLookFromLampPostPos(CEntity *Target, CPed *Cop, CVector &TargetCoors, CVector &SourceOut) +{ + int i; + int16 NumObjects; + CEntity *Objects[16]; + CEntity *NearestLampPost = nil; + CWorld::FindObjectsInRange(TargetCoors, 30.0f, true, &NumObjects, 15, Objects, false, false, false, true, true); + float NearestDist = 10000.0f; + for(i = 0; i < NumObjects; i++){ + if(Objects[i]->GetIsStatic() && Objects[i]->GetUp().z > 0.9f && IsLampPost(Objects[i]->GetModelIndex())){ + float Dist = (Objects[i]->GetPosition() - TargetCoors).Magnitude2D(); + if(Abs(ARRESTCAM_LAMP_BEST_DIST - Dist) < NearestDist){ + CVector TestStart = Objects[i]->GetColModel()->boundingBox.max; + TestStart = Objects[i]->GetMatrix() * TestStart; + CVector TestEnd = TestStart - TargetCoors; + TestEnd.Normalise(); + TestEnd += TargetCoors; + if(CWorld::GetIsLineOfSightClear(TestStart, TestEnd, true, false, false, false, false, true, true)){ + NearestDist = Abs(ARRESTCAM_LAMP_BEST_DIST - Dist); + NearestLampPost = Objects[i]; + SourceOut = TestStart; + } + } + } + } + return NearestLampPost != nil; +} + +bool +CCam::GetLookOverShoulderPos(CEntity *Target, CPed *Cop, CVector &TargetCoors, CVector &SourceOut) +{ + if(Target == nil || Cop == nil) + return false; + CVector CopCoors = Cop->GetPosition(); + CVector CopToTarget = TargetCoors - CopCoors; + CVector Side = CrossProduct(CopToTarget, CVector(0.0f, 0.0f, 1.0f)); + Side.Normalise(); + CopCoors += ARRESTDIST_RIGHTOF_COP * Side; + CopToTarget.Normalise(); + if(CopToTarget.z < -0.7071f){ + CopToTarget.z = -0.7071f; + float GroundDist = CopToTarget.Magnitude2D(); + if(GroundDist > 0.0f){ + CopToTarget.x *= 0.7071f/GroundDist; + CopToTarget.y *= 0.7071f/GroundDist; + } + CopToTarget.Normalise(); + }else{ + if(CopToTarget.z > 0.0f){ + CopToTarget.z = 0.0f; + CopToTarget.Normalise(); + } + } + CopCoors -= ARRESTDIST_BEHIND_COP * CopToTarget; + CopToTarget = TargetCoors - CopCoors; + float Dist = CopToTarget.Magnitude(); + if(Dist < ARRESTDIST_MINFROM_PLAYER && Dist > 0.0f) + CopToTarget *= ARRESTDIST_MINFROM_PLAYER/Dist; + SourceOut = TargetCoors - CopToTarget; + return true; +} + +enum { + ARRESTCAM_OVERSHOULDER = 1, + ARRESTCAM_ALONGGROUND, + ARRESTCAM_ALONGGROUND_RIGHT, + ARRESTCAM_ALONGGROUND_RIGHT_UP, + ARRESTCAM_ALONGGROUND_LEFT, + ARRESTCAM_ALONGGROUND_LEFT_UP, + ARRESTCAM_LAMPPOST, +}; + +int nUsingWhichCamera; +CPed *pStoredCopPed; + +bool +CCam::ProcessArrestCamOne(void) +{ + CVector TargetPos; + CVector CamSource; + CPed *cop = nil; + FOV = 45.0f; + bool foundPos = false; + int ArrestModes[5] = { -1, -1, -1, -1, -1 }; + + if(ResetStatics){ + CPed *targetPed = (CPed*)TheCamera.pTargetEntity; + nUsingWhichCamera = 0; + if(TheCamera.pTargetEntity->IsPed()){ + ((CPed*)TheCamera.pTargetEntity)->m_pedIK.GetComponentPosition(TargetPos, PED_MID); + if(FindPlayerPed() && FindPlayerPed()->m_pArrestingCop) + cop = FindPlayerPed()->m_pArrestingCop; + if(cop && CGeneral::GetRandomNumberInRange(0.0f, 1.0f) > 0.5f){ + ArrestModes[0] = ARRESTCAM_OVERSHOULDER; + ArrestModes[1] = ARRESTCAM_ALONGGROUND; + ArrestModes[2] = ARRESTCAM_OVERSHOULDER; + ArrestModes[3] = ARRESTCAM_LAMPPOST; + }else{ + ArrestModes[0] = ARRESTCAM_ALONGGROUND; + ArrestModes[1] = ARRESTCAM_OVERSHOULDER; + ArrestModes[2] = ARRESTCAM_LAMPPOST; + } + }else if(TheCamera.pTargetEntity->IsVehicle()){ + CVehicle *targetVehicle = (CVehicle*)TheCamera.pTargetEntity; + if(targetVehicle->pDriver && targetVehicle->pDriver->IsPlayer()){ + targetPed = targetVehicle->pDriver; + targetPed->m_pedIK.GetComponentPosition(TargetPos, PED_MID); + }else{ + targetPed = nil; + TargetPos = targetVehicle->GetPosition(); + } + + if(FindPlayerPed() && FindPlayerPed()->m_pArrestingCop) + cop = FindPlayerPed()->m_pArrestingCop; + if(cop && CGeneral::GetRandomNumberInRange(0.0f, 1.0f) > 0.65f){ + ArrestModes[0] = ARRESTCAM_OVERSHOULDER; + ArrestModes[1] = ARRESTCAM_LAMPPOST; + ArrestModes[2] = ARRESTCAM_ALONGGROUND; + ArrestModes[3] = ARRESTCAM_OVERSHOULDER; + }else{ + ArrestModes[0] = ARRESTCAM_LAMPPOST; + ArrestModes[1] = ARRESTCAM_ALONGGROUND; + ArrestModes[2] = ARRESTCAM_OVERSHOULDER; + } + }else + return false; + + for(int i = 0; nUsingWhichCamera == 0 && i < ARRAY_SIZE(ArrestModes) && ArrestModes[i] > 0; i++){ + switch(ArrestModes[i]){ + case ARRESTCAM_OVERSHOULDER: + if(cop){ + foundPos = GetLookOverShoulderPos(TheCamera.pTargetEntity, cop, TargetPos, CamSource); + pStoredCopPed = cop; + cop = nil; + }else if(targetPed){ + for(int j = 0; j < targetPed->m_numNearPeds; j++){ + CPed *nearPed = targetPed->m_nearPeds[j]; + if(nearPed->GetPedState() == PED_ARREST_PLAYER) + foundPos = GetLookOverShoulderPos(TheCamera.pTargetEntity, nearPed, TargetPos, CamSource); + if(foundPos){ + pStoredCopPed = nearPed; + break; + } + } + } + break; + case ARRESTCAM_ALONGGROUND: + if(cop){ + foundPos = GetLookAlongGroundPos(TheCamera.pTargetEntity, cop, TargetPos, CamSource); + pStoredCopPed = cop; + cop = nil; + }else if(targetPed){ + for(int j = 0; j < targetPed->m_numNearPeds; j++){ + CPed *nearPed = targetPed->m_nearPeds[j]; + if(nearPed->GetPedState() == PED_ARREST_PLAYER) + foundPos = GetLookAlongGroundPos(TheCamera.pTargetEntity, nearPed, TargetPos, CamSource); + if(foundPos){ + pStoredCopPed = nearPed; + break; + } + } + } + break; + case ARRESTCAM_LAMPPOST: + foundPos = GetLookFromLampPostPos(TheCamera.pTargetEntity, cop, TargetPos, CamSource); + break; + } + + if(foundPos){ + if(pStoredCopPed) + pStoredCopPed->RegisterReference((CEntity**)&pStoredCopPed); + nUsingWhichCamera = ArrestModes[i]; + if(ArrestModes[i] == ARRESTCAM_ALONGGROUND){ + float rnd = CGeneral::GetRandomNumberInRange(0.0f, 5.0f); + if(rnd < 1.0f) nUsingWhichCamera = ARRESTCAM_ALONGGROUND; + else if(rnd < 2.0f) nUsingWhichCamera = ARRESTCAM_ALONGGROUND_RIGHT; + else if(rnd < 3.0f) nUsingWhichCamera = ARRESTCAM_ALONGGROUND_RIGHT_UP; + else if(rnd < 4.0f) nUsingWhichCamera = ARRESTCAM_ALONGGROUND_LEFT; + else nUsingWhichCamera = ARRESTCAM_ALONGGROUND_LEFT_UP; + } + }else + pStoredCopPed = nil; + } + + Source = CamSource; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetPos, Source, FOV); + Front = TargetPos - Source; + Front.Normalise(); + Up = CVector(0.0f, 0.0f, 1.0f); + CVector Right = CrossProduct(Front, Up); + Right.Normalise(); + Up = CrossProduct(Right, Front); + if(nUsingWhichCamera != 0) + ResetStatics = false; + return true; + } + + if(TheCamera.pTargetEntity->IsPed()){ + ((CPed*)TheCamera.pTargetEntity)->m_pedIK.GetComponentPosition(TargetPos, PED_MID); + }else if(TheCamera.pTargetEntity->IsVehicle()){ + CPed *driver = ((CVehicle*)TheCamera.pTargetEntity)->pDriver; + if(driver && driver->IsPlayer()) + driver->m_pedIK.GetComponentPosition(TargetPos, PED_MID); + else + TargetPos = TheCamera.pTargetEntity->GetPosition(); + }else + return false; + + if(nUsingWhichCamera == ARRESTCAM_OVERSHOULDER && pStoredCopPed){ + foundPos = GetLookOverShoulderPos(TheCamera.pTargetEntity, pStoredCopPed, TargetPos, CamSource); + float newZ = Source.z + ARRESTCAM_S_ROTATION_UP*CTimer::GetTimeStep(); + if(CamSource.z > newZ) + CamSource.z = newZ; + }else if(nUsingWhichCamera >= ARRESTCAM_ALONGGROUND_RIGHT && nUsingWhichCamera <= ARRESTCAM_ALONGGROUND_LEFT_UP){ + CamSource = Source; + Front = TargetPos - CamSource; + Front.Normalise(); + Up = CVector(0.0f, 0.0f, 1.0f); + CVector Right = CrossProduct(Front, Up); + if(nUsingWhichCamera == ARRESTCAM_ALONGGROUND_LEFT || nUsingWhichCamera == ARRESTCAM_ALONGGROUND_LEFT_UP) + Right *= -1.0f; + if(CWorld::TestSphereAgainstWorld(CamSource + 0.5f*Right, 0.4f, TheCamera.pTargetEntity, true, true, false, true, false, true) == nil){ + foundPos = true; + CamSource += Right*ARRESTCAM_ROTATION_SPEED*CTimer::GetTimeStep(); + if(nUsingWhichCamera == ARRESTCAM_ALONGGROUND_RIGHT_UP || nUsingWhichCamera == ARRESTCAM_ALONGGROUND_LEFT_UP){ + CamSource.z += ARRESTCAM_ROTATION_UP*CTimer::GetTimeStep(); + }else{ + bool found = false; + float ground = CWorld::FindGroundZFor3DCoord(CamSource.x, CamSource.y, CamSource.z, &found); + if(found) + CamSource.z = ground + ARRESTDIST_ABOVE_GROUND; + } + } + }else if(nUsingWhichCamera == ARRESTCAM_LAMPPOST){ + CamSource = Source; + Front = TargetPos - CamSource; + Front.z = 0.0f; + Front.Normalise(); + Up = CVector(0.0f, 0.0f, 1.0f); + CVector Right = CrossProduct(Front, Up); + Right.Normalise(); + Front = TargetPos - CamSource + Right*ARRESTCAM_LAMPPOST_ROTATEDIST; + Front.z = 0.0f; + Front.Normalise(); + if(CWorld::TestSphereAgainstWorld(CamSource + 0.5f*Front, 0.4f, TheCamera.pTargetEntity, true, true, false, true, false, true) == nil){ + foundPos = true; + CamSource += Front*ARRESTCAM_LAMPPOST_TRANSLATE*CTimer::GetTimeStep(); + } + } + + if(foundPos){ + Source = CamSource; + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetPos, Source, FOV); + Front = TargetPos - Source; + Front.Normalise(); + Up = CVector(0.0f, 0.0f, 1.0f); + CVector Right = CrossProduct(Front, Up); + Right.Normalise(); + Up = CrossProduct(Right, Front); + }else{ + CVector OrigSource = Source; + TheCamera.AvoidTheGeometry(OrigSource, TargetPos, Source, FOV); + } + + 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; +} + + +#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)); + + 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::CalculateAspectRatio() * 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->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE; + bool isHeli = car->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI; + bool isBike = car->GetVehicleAppearance() == VEHICLE_APPEARANCE_BIKE; + 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 || car->GetModelIndex() == MI_RCBARON) { + camSetArrPos = 5; + } else if (car->GetModelIndex() == MI_RCGOBLIN || car->GetModelIndex() == MI_RCRAIDER) { + camSetArrPos = 6; + } 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, 3.5f, 0.2f, 10.0f, 25.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; + + // Taken from VC CCam::Cam_On_A_String_Unobscured. If we don't this, we will end up seeing the world from the inside of RC Goblin/Raider. + // I couldn't find where SA does that. It's possible that they've increased the size of these veh.'s collision bounding box. + + if (car->m_modelIndex == MI_RCRAIDER || car->m_modelIndex == MI_RCGOBLIN) + newDistance += INIT_RC_HELI_HORI_EXTRA; + else if (car->m_modelIndex == MI_RCBARON) + newDistance += INIT_RC_PLANE_HORI_EXTRA; + + 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; + } + + if (car->m_modelIndex == MI_RCGOBLIN) + zoomModeAlphaOffset += 0.178997f; + + 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; + } 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; + + if (!TheCamera.m_bJustCameOutOfGarage) { + 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) + 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 || ((CBike*)car)->m_nWheelsOnGround > 3) + if (!isHeli && (!isPlane || ((CAutomobile*)car)->m_nWheelsOnGround)) { + + 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 checks for m_bUseMouse3rdPerson so num2 / num8 do not move camera + // when Keyboard & Mouse controls are used. To make it work better with III/VC, check for actual pad state instead + if (!CPad::IsAffectedByController && !isCar) + stickY = 0.0f; + else if (CPad::bInvertLook4Pad) + stickY = -stickY; + + 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_VOODOO) { + 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 && ((CBike*)car)->m_nWheelsOnGround > 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() && IsLightObject(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::CalculateAspectRatio() * 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; + + // CCam::FixSourceAboveWaterLevel + if (CameraTarget.z >= -2.0f) { + float level = -6000.0; + + if (CWaterLevel::GetWaterLevelNoWaves(Source.x, Source.y, Source.z, &level)) { + if (Source.z < level) + Source.z = level; + } + } + 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::GetTimeStepInSeconds(); + + 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/miami/core/Camera.cpp b/src/miami/core/Camera.cpp new file mode 100644 index 00000000..5ce205fe --- /dev/null +++ b/src/miami/core/Camera.cpp @@ -0,0 +1,4143 @@ +#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 "Particle.h" +#include "WaterLevel.h" +#include "World.h" +#include "Garages.h" +#include "Replay.h" +#include "CutsceneMgr.h" +#include "Renderer.h" +#include "Timecycle.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 "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, + // heli + OBBE_14, + OBBE_15, + OBBE_16, + OBBE_17, + OBBE_18, + OBBE_19, + OBBE_ONSTRING_HELI, + + 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; +static bool bSwitchedToObbeCam; +float CCamera::m_fMouseAccelHorzntl; +float CCamera::m_fMouseAccelVertical; +float CCamera::m_f3rdPersonCHairMultX; +float CCamera::m_f3rdPersonCHairMultY; + +#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 + +const float ZOOM_ONE_DISTANCE[] = { -0.6f, 0.05f, -3.2f, 0.05f, -2.41f }; +const float ZOOM_TWO_DISTANCE[] = { 1.9f, 1.4f, 0.65f, 1.9f, 6.49f }; +const float ZOOM_THREE_DISTANCE[] = { 15.9f, 15.9f, 15.9f, 15.9f, 25.25f }; + +#ifdef FREE_CAM +const float LCS_ZOOM_ONE_DISTANCE[] = { -1.0f, -0.2f, -3.2f, 0.05f, -2.41f }; +const float LCS_ZOOM_TWO_DISTANCE[] = { 2.0f, 2.2f, 1.65f, 2.9f, 6.49f }; +const float LCS_ZOOM_THREE_DISTANCE[] = { 6.0f, 6.0f, 15.9f, 15.9f, 15.0f }; +#endif + +CCamera::CCamera(void) +{ + Init(); +} + +void +CCamera::Init(void) +{ + memset(this, 0, sizeof(CCamera)); // this is fine, no vtable + m_pRwCamera = nil; + m_bPlayerWasOnBike = false; + 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; + m_bEnable1rstPersonCamCntrlsScript = false; + m_bAllow1rstPersonWeaponsCamera = false; + m_bVehicleSuspenHigh = false; + Cams[0].m_fMinRealGroundDist = 1.85f; + // TODO: what weird value is this? + Cams[0].m_fTargetCloseInDist = 2.0837801f - Cams[0].m_fMinRealGroundDist; + Cams[0].m_fTargetZoomGroundOne = 0.25f; + Cams[0].m_fTargetZoomGroundTwo = 1.5f; + Cams[0].m_fTargetZoomGroundThree = 4.0f; + Cams[0].m_fTargetZoomOneZExtra = -0.14f; + Cams[0].m_fTargetZoomTwoZExtra = 0.16f; + Cams[0].m_fTargetZoomThreeZExtra = 0.25f; + // TODO: another weird value + Cams[0].m_fTargetZoomZCloseIn = 0.90040702f; + m_bMoveCamToAvoidGeom = 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; + if(!FrontEndMenuManager.m_bWantToRestart){ + m_bFading = false; + CDraw::FadeValue = 0; + m_fFLOATingFade = 0.0f; + m_bMusicFading = false; + m_fTimeToFadeMusic = 0.0f; + m_fFLOATingFadeMusic = 0.0f; + m_fMouseAccelVertical = 0.003f; + m_fMouseAccelHorzntl = 0.0025f; + } + if(FrontEndMenuManager.m_bWantToRestart) + m_fTimeToFadeMusic = 0.0f; + 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; + m_uiTimeWeLeftIdle_StillNoInput = 0; + m_uiTimeWeEnteredIdle = 0; + 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; + m_f3rdPersonCHairMultX = 0.53f; + m_f3rdPersonCHairMultY = 0.4f; + m_fAvoidTheGeometryProbsTimer = 0.0f; + m_nAvoidTheGeometryProbsDirn = 0; +} + +void +CCamera::Process(void) +{ + // static bool InterpolatorNotInitialised = true; // unused + static float PlayerMinDist = 1.3f; + static bool WasPreviouslyInterSyhonFollowPed = false; // only written + 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(); + +#ifndef MASTER +#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; + } +#endif + + 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){ + if(CTimer::GetTimeInMilliseconds() > m_uiTransitionDuration+m_uiTimeTransitionStart){ + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + m_bWaitForInterpolToFinish = false; + } + } + + 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; + +#ifndef MASTER + // Debug stuff + if(!gbModelViewer) + Cams[ActiveCam].PrintMode(); // actually missing in VC + if(WorldViewerBeingUsed) + Cams[2].Process(); +#endif + + if(Cams[ActiveCam].DirectionWasLooking != LOOKING_FORWARD && pTargetEntity->IsVehicle()) + lookLRBVehicle = true; + + if(m_uiTransitionState != 0 && !lookLRBVehicle){ + // Process transition + + uint32 currentTime = CTimer::GetTimeInMilliseconds() - m_uiTimeTransitionStart; + if(currentTime >= m_uiTransitionDuration) + currentTime = m_uiTransitionDuration; + float fractionInter = (float) currentTime / m_uiTransitionDuration; + float fractionInterTarget = (float) currentTime / m_uiTransitionDurationTargetCoors; + fractionInterTarget = Clamp(fractionInterTarget, 0.0f, 1.0f); + + // Interpolate target separately + if(fractionInterTarget <= m_fFractionInterToStopMovingTarget){ + float inter; + if(m_fFractionInterToStopMovingTarget == 0.0f) + inter = 0.0f; + else + inter = (m_fFractionInterToStopMovingTarget - fractionInterTarget)/m_fFractionInterToStopMovingTarget; + inter = 0.5f - 0.5*Cos(inter*PI); // smooth it + + m_vecTargetWhenInterPol = m_cvecStartingTargetForInterPol + inter*m_cvecTargetSpeedAtStartInter; + Target = m_vecTargetWhenInterPol; + }else if(fractionInterTarget > m_fFractionInterToStopMovingTarget){ + float inter; + if(m_fFractionInterToStopCatchUpTarget == 0.0f) + inter = 0.0f; + else + inter = (fractionInterTarget - m_fFractionInterToStopMovingTarget)/m_fFractionInterToStopCatchUpTarget; + inter = 0.5f - 0.5*Cos(inter*PI); // smooth it + + if(m_fFractionInterToStopMovingTarget == 0.0f) + m_vecTargetWhenInterPol = m_cvecStartingTargetForInterPol; + Target = m_vecTargetWhenInterPol + inter*(Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter - m_vecTargetWhenInterPol); + } + + 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; + + if(m_bLookingAtPlayer){ + CVector ToCam = m_vecSourceWhenInterPol - Target; + if(ToCam.Magnitude2D() < PlayerMinDist){ + float beta = CGeneral::GetATanOfXY(ToCam.x, ToCam.y); + CamSource.x = Target.x + PlayerMinDist*Cos(beta); + CamSource.y = Target.y + PlayerMinDist*Sin(beta); + } + } + + m_vecUpWhenInterPol = m_cvecStartingUpForInterPol + inter*m_cvecUpSpeedAtStartInter; + m_fFOVWhenInterPol = m_fStartingFOVForInterPol + inter*m_fFOVSpeedAtStartInter; + + CamSource = m_vecSourceWhenInterPol; + CamFront = Target - CamSource; + StoreValuesDuringInterPol(CamSource, m_vecTargetWhenInterPol, m_vecUpWhenInterPol, m_fFOVWhenInterPol); + 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); + + if(m_bLookingAtPlayer){ + CVector ToCam = m_vecSourceWhenInterPol - Target; + if(ToCam.Magnitude2D() < PlayerMinDist){ + float beta = CGeneral::GetATanOfXY(ToCam.x, ToCam.y); + CamSource.x = Target.x + PlayerMinDist*Cos(beta); + CamSource.y = Target.y + PlayerMinDist*Sin(beta); + } + } + + FOV = m_fFOVWhenInterPol + inter*(Cams[ActiveCam].FOV - m_fFOVWhenInterPol); + CamUp = m_vecUpWhenInterPol + inter*(Cams[ActiveCam].Up - m_vecUpWhenInterPol); + deltaBeta = Cams[ActiveCam].m_fTrueBeta - m_fBetaWhenInterPol; + MakeAngleLessThan180(deltaBeta); + + 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); + }else{ + // No transition, take Cam values directly +#ifndef MASTER + if(WorldViewerBeingUsed){ + CamSource = Cams[2].Source; + CamFront = Cams[2].Front; + CamUp = Cams[2].Up; + FOV = Cams[2].FOV; + }else +#endif + { + CamSource = Cams[ActiveCam].Source; + CamUp = Cams[ActiveCam].Up; + if(m_bMoveCamToAvoidGeom){ + CamSource += m_vecClearGeometryVec; + CamFront = Cams[ActiveCam].m_cvecTargetCoorsForFudgeInter - CamSource; + CamFront.Normalise(); + CVector Right = CrossProduct(CamFront, CamUp); + Right.Normalise(); + CamUp = CrossProduct(Right, CamFront); + CamUp.Normalise(); + }else{ + CamFront = Cams[ActiveCam].Front; + CamUp = Cams[ActiveCam].Up; + } + FOV = Cams[ActiveCam].FOV; + } + WasPreviouslyInterSyhonFollowPed = false; // unused + } + + 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); + } + } + + if(CMBlur::Drunkness > 0.0f){ + static float DrunkAngle; + + int tableIndex = (int)(DEGTORAD(DrunkAngle)/TWOPI * CParticle::SIN_COS_TABLE_SIZE) & CParticle::SIN_COS_TABLE_SIZE-1; + DrunkAngle += 5.0f; +#ifndef FIX_BUGS + // This just messes up interpolation, probably not what they intended + // and multiplying the interpolated FOV is also a bit extreme + // so let's not do any of this nonsense + Cams[ActiveCam].FOV *= (1.0f + CMBlur::Drunkness); +#endif + + CamSource.x += -0.02f*CMBlur::Drunkness * CParticle::m_CosTable[tableIndex]; + CamSource.y += -0.02f*CMBlur::Drunkness * CParticle::m_SinTable[tableIndex]; + + CamUp.Normalise(); + CamUp.x += 0.05f*CMBlur::Drunkness * CParticle::m_CosTable[tableIndex]; + CamUp.y += 0.05f*CMBlur::Drunkness * CParticle::m_SinTable[tableIndex]; + CamUp.Normalise(); + + CamFront.Normalise(); + CamFront.x += -0.1f*CMBlur::Drunkness * CParticle::m_CosTable[tableIndex]; + CamFront.y += -0.1f*CMBlur::Drunkness * CParticle::m_SinTable[tableIndex]; + CamFront.Normalise(); + + CamRight = CrossProduct(CamFront, CamUp); + CamRight.Normalise(); + CamUp = CrossProduct(CamRight, CamFront); + CamUp.Normalise(); + } + + 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)); + + static bool bExtra1stPrsBlur = false; + if(Cams[ActiveCam].Mode == CCam::MODE_1STPERSON && FindPlayerVehicle() && FindPlayerVehicle()->GetUp().z < 0.2f){ + SetMotionBlur(230, 230, 230, 215, MOTION_BLUR_LIGHT_SCENE); + bExtra1stPrsBlur = true; + }else if(bExtra1stPrsBlur){ + SetMotionBlur(CTimeCycle::GetBlurRed(), CTimeCycle::GetBlurGreen(), CTimeCycle::GetBlurBlue(), m_motionBlur, MOTION_BLUR_LIGHT_SCENE); + bExtra1stPrsBlur = false; + } + + CalculateDerivedValues(); + CDraw::SetFOV(FOV); + + // Set RW camera +#ifndef MASTER + 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 +#endif + { + 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); + RwFrameOrthoNormalize(frame); + } + + 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(); + + if(GetPosition().z > 55.0f && FindPlayerVehicle() && FindPlayerVehicle()->pHandling->Flags & (HANDLING_IS_HELI|HANDLING_IS_PLANE) || + FindPlayerPed()->m_attachedTo){ + LODDistMultiplier *= 1.0f + Max((GetPosition().z - 55.0f)/60.0f, 0.0f); + float NewNear = DEFAULT_NEAR * (1.0f + Max((GetPosition().z - 55.0f)/60.0f, 0.0f)); + if(RwCameraGetNearClipPlane(Scene.camera) >= DEFAULT_NEAR) + RwCameraSetNearClipPlane(Scene.camera, NewNear); + } + if(LODDistMultiplier > 2.2f) LODDistMultiplier = 2.2f; + }else + LODDistMultiplier = 1.0f; + GenerationDistMultiplier = LODDistMultiplier; + LODDistMultiplier *= CRenderer::ms_lodDistScale; + + CDraw::SetNearClipZ(RwCameraGetNearClipPlane(m_pRwCamera)); + CDraw::SetFarClipZ(RwCameraGetFarClipPlane(m_pRwCamera)); + + // 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(); + + 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; + m_bMoveCamToAvoidGeom = false; +} + +void +CCamera::CamControl(void) +{ + static bool PlaceForFixedWhenSniperFound = false; + static int16 ReqMode; + bool switchByJumpCut = false; + bool stairs = false; + bool boatTarget = false; + int PrevMode = Cams[ActiveCam].Mode; + 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; + m_bJustJumpedOutOf1stPersonBecauseOfTarget = false; + bSwitchedToObbeCam = false; + + if(Cams[ActiveCam].CamTargetEntity == nil && pTargetEntity == nil) + pTargetEntity = PLAYER; + + 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)->DisablePlayerControls &= ~PLAYERCONTROL_CAMERA; + FindPlayerPed()->bIsVisible = true; + } + + if(!CTimer::GetIsPaused() && !m_bIdleOn){ + 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()){ +#ifdef GTA_TRAIN + if(((CVehicle*)pTargetEntity)->IsTrain()){ + if(!m_bTargetJustBeenOnTrain){ + m_bInitialNodeFound = false; + m_bInitialNoNodeStaticsSet = false; + } + Process_Train_Camera_Control(); + }else +#endif + { + if(((CVehicle*)pTargetEntity)->IsBoat() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + boatTarget = true; + + // Change user selected mode + if(CPad::GetPad(0)->CycleCameraModeUpJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn){ + CarZoomIndicator--; + // disable topdown here + if(CarZoomIndicator == CAM_ZOOM_TOPDOWN) + CarZoomIndicator--; + } + if(CPad::GetPad(0)->CycleCameraModeDownJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn){ + CarZoomIndicator++; + if(CarZoomIndicator == CAM_ZOOM_TOPDOWN) + 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; + + int vehType = ((CVehicle*)pTargetEntity)->m_vehType; + if(((CVehicle*)pTargetEntity)->IsBoat() && pTargetEntity->GetModelIndex() == MI_SKIMMER) + vehType = VEHICLE_TYPE_CAR; + + switch(vehType){ + case VEHICLE_TYPE_CAR: + case VEHICLE_TYPE_BIKE:{ + CAttributeZone *stairsZone = nil; + if(vehType == VEHICLE_TYPE_BIKE && CCullZones::CamStairsForPlayer()){ + stairsZone = CCullZones::FindZoneWithStairsAttributeForPlayer(); + if(stairsZone) + stairs = true; + } + if(CGarages::IsPointInAGarageCameraZone(pTargetEntity->GetPosition()) || stairs){ + if(!m_bGarageFixedCamPositionSet && m_bLookingAtPlayer || + WhoIsInControlOfTheCamera == CAMCONTROL_OBBE){ + if(pToGarageWeAreIn || stairsZone){ + float ground; + bool foundGround; + + if(pToGarageWeAreIn){ + // 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 + } + }else{ + assert(stairsZone); + whichDoor = 1; + garageDoorPos1 = Cams[ActiveCam].Source; + garageCenter = CVector((stairsZone->minx+stairsZone->maxx)/2.0f, (stairsZone->miny+stairsZone->maxy)/2.0f, 0.0f); + if((garageCenter-garageDoorPos1).Magnitude() > 15.0f){ + bool bClearViewOutside = true; + CVector dirOutside = pTargetEntity->GetPosition() - garageCenter; + dirOutside.z = 0.0f; + dirOutside.Normalise(); + float zoneDim = stairsZone->maxx - stairsZone->minx; + if(zoneDim < stairsZone->maxy - stairsZone->miny) + zoneDim = stairsZone->maxy - stairsZone->miny; + zoneDim *= 2.0f; + CVector posOutside = pTargetEntity->GetPosition() + zoneDim*dirOutside; + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), posOutside, true, false, false, false, false, false, true)){ + posOutside = pTargetEntity->GetPosition() - zoneDim*dirOutside; + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), posOutside, true, false, false, false, false, false, true)) + bClearViewOutside = false; + } + if(bClearViewOutside) + garageDoorPos1 = posOutside; + } + } + + if(pToGarageWeAreIn){ + garageCenter.x = pToGarageWeAreIn->GetGarageCenterX(); + garageCenter.y = pToGarageWeAreIn->GetGarageCenterY(); + garageCenter.z = 0.0f; + }else{ + garageDoorPos1.z = 0.0f; + if(stairsZone == nil) // how can this be true? + 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 && stairsZone){ + float zoneDim = stairsZone->maxx - stairsZone->minx; + if(zoneDim < stairsZone->maxy - stairsZone->miny) + zoneDim = stairsZone->maxy - stairsZone->miny; + garageCamPos = garageCenter + (0.7f*zoneDim + 3.75f)*garageCenterToDoor; + }else + 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; + } + + int vehApp = ((CVehicle*)pTargetEntity)->GetVehicleAppearance(); + int vehArrPos = 0; + GetArrPosForVehicleType(vehApp, vehArrPos); + + // 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 = LCS_ZOOM_ONE_DISTANCE[vehArrPos]; + else if (CarZoomIndicator == CAM_ZOOM_2) + CarZoomValue = LCS_ZOOM_TWO_DISTANCE[vehArrPos]; + else if (CarZoomIndicator == CAM_ZOOM_3) + CarZoomValue = LCS_ZOOM_THREE_DISTANCE[vehArrPos]; + } +#endif + else if (CarZoomIndicator == CAM_ZOOM_1) + CarZoomValue = ZOOM_ONE_DISTANCE[vehArrPos]; + else if(CarZoomIndicator == CAM_ZOOM_2) + CarZoomValue = ZOOM_TWO_DISTANCE[vehArrPos]; + else if(CarZoomIndicator == CAM_ZOOM_3) + CarZoomValue = ZOOM_THREE_DISTANCE[vehArrPos]; + + if(CarZoomIndicator == CAM_ZOOM_TOPDOWN && !m_bPlayerIsInGarage){ + CarZoomValue = 1.0f; + ReqMode = CCam::MODE_TOPDOWN; + } + + // Check if we have to go into first person + if(vehType == VEHICLE_TYPE_CAR && !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() && + pTargetEntity->GetModelIndex() != MI_SKIMMER && pTargetEntity->GetModelIndex() != MI_SEASPAR) + 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 && !m_bFirstPersonBeingUsed){ + if(FrontEndMenuManager.m_ControlMethod == CONTROL_STANDARD){ + if(PedZoomIndicator == CAM_ZOOM_3) + PedZoomIndicator = CAM_ZOOM_1; + else + PedZoomIndicator = CAM_ZOOM_3; + }else + PedZoomIndicator--; + } + if(CPad::GetPad(0)->CycleCameraModeDownJustDown() && !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn && !m_bFailedCullZoneTestPreviously && !m_bFirstPersonBeingUsed){ + if(FrontEndMenuManager.m_ControlMethod == CONTROL_STANDARD){ + if(PedZoomIndicator == CAM_ZOOM_3) + PedZoomIndicator = CAM_ZOOM_1; + else + PedZoomIndicator = CAM_ZOOM_3; + }else + PedZoomIndicator++; + } + // disabled top down and obbe's cam here + if(PedZoomIndicator < CAM_ZOOM_1) PedZoomIndicator = CAM_ZOOM_3; + else if(PedZoomIndicator > CAM_ZOOM_3) PedZoomIndicator = CAM_ZOOM_1; + + ReqMode = CCam::MODE_FOLLOWPED; + + // Check 1st person mode + if((m_bLookingAtPlayer || m_bEnable1rstPersonCamCntrlsScript) && pTargetEntity->IsPed() && + (!m_WideScreenOn || m_bEnable1rstPersonCamCntrlsScript) && !Cams[0].Using3rdPersonMouseCam() +#ifdef FREE_CAM + && (!CCamera::bFreeCam || m_bEnable1rstPersonCamCntrlsScript) +#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; + } + 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 if(CPad::GetPad(0)->TargetJustDown()){ + m_bFirstPersonBeingUsed = false; + m_bJustJumpedOutOf1stPersonBecauseOfTarget = true; + } + } + }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)->DisablePlayerControls |= 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); + } + if(PedZoomIndicator == CAM_ZOOM_3 && m_fPedZoomValue == 0.0f) + 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 + CAttributeZone *stairsZone = nil; + if(CCullZones::CamStairsForPlayer()){ + stairsZone = CCullZones::FindZoneWithStairsAttributeForPlayer(); + if(stairsZone) + stairs = true; + } + if(CGarages::IsPointInAGarageCameraZone(pTargetEntity->GetPosition()) && !m_bUseMouse3rdPerson || 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(stairsZone){ // always true + garageCenter = CVector((stairsZone->minx+stairsZone->maxx)/2, (stairsZone->miny+stairsZone->maxy)/2, 0.0f); + if(pTargetEntity->GetPosition().x > 376.0f && pTargetEntity->GetPosition().x < 383.0f && + pTargetEntity->GetPosition().y > -496.0f && pTargetEntity->GetPosition().y < -489.0f && + pTargetEntity->GetPosition().z > 11.6f && pTargetEntity->GetPosition().z < 13.6f){ + garageDoorPos1 = CVector(382.6f, -489.6f, 13.1f); + }else{ + bool bClearViewOutside = true; + CVector dirOutside = pTargetEntity->GetPosition() - garageCenter; + dirOutside.z = 0.0f; + dirOutside.Normalise(); + float zoneDim = stairsZone->maxx - stairsZone->minx; + if(zoneDim < stairsZone->maxy - stairsZone->miny) + zoneDim = stairsZone->maxy - stairsZone->miny; + zoneDim *= 2.0f; + CVector posOutside = pTargetEntity->GetPosition() + zoneDim*dirOutside; + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), posOutside, true, false, false, false, false, false, true)){ + posOutside = pTargetEntity->GetPosition() - zoneDim*dirOutside; + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), posOutside, true, false, false, false, false, false, true)) + bClearViewOutside = false; + } + if(bClearViewOutside) + garageDoorPos1 = posOutside; + } + } + } + + if(pToGarageWeAreIn){ + garageCenter.x = pToGarageWeAreIn->GetGarageCenterX(); + garageCenter.y = pToGarageWeAreIn->GetGarageCenterY(); + garageCenter.z = 0.0f; + }else{ + garageDoorPos1.z = 0.0f; + if(!stairs) // how can this be true? + 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){ + if(stairsZone){ + float zoneDim = stairsZone->maxx - stairsZone->minx; + if(zoneDim < stairsZone->maxy - stairsZone->miny) + zoneDim = stairsZone->maxy - stairsZone->miny; + garageCamPos = garageCenter + (0.7f*zoneDim + 3.75f)*garageCenterToDoor; + }else // how can this be true? + 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_fSupX) // What? + garageCamPos.z = pToGarageWeAreIn->m_fSupX; + }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; + } + + // Lighthouse + if(!m_bFirstPersonBeingUsed && (pTargetEntity->GetPosition() - CVector(474.3f, -1717.6f, 0.0f)).Magnitude2D() < 6.0f) + if((pTargetEntity->GetPosition() - CVector(474.3f, -1717.6f, 0.0f)).Magnitude2D() < 3.8f || + pTargetEntity->GetPosition().z > 50.0f) + if(!Cams[ActiveCam].Using3rdPersonMouseCam()) + ReqMode = CCam::MODE_LIGHTHOUSE; + + // 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 || + // game also checks MODE_MODELVIEW here but that does make any sense... + PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON || + PlayerWeaponMode.Mode == CCam::MODE_HELICANNON_1STPERSON || + PlayerWeaponMode.Mode == CCam::MODE_CAMERA || + 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 && PedZoomIndicator != CAM_ZOOM_3){ + // 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 = 5.6f; + 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; + } + } + } + } + } + + if(DebugCamMode) + ReqMode = DebugCamMode; + + + // Process arrested player + static int ThePickedArrestMode; + static int LastPedState; + bool startArrestCam = false; + static bool beingArrested = false; + bool stopArrestCam = false; + + if(PLAYER->GetPedState() == PED_ARRESTED) + beingArrested = true; + else if(beingArrested){ + stopArrestCam = true; + beingArrested = 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){ + ThePickedArrestMode = CCam::MODE_ARRESTCAM_ONE; + ReqMode = CCam::MODE_ARRESTCAM_ONE; + Cams[ActiveCam].ResetStatics = true; + }else if(PLAYER->GetPedState() == PED_ARRESTED) + ReqMode = ThePickedArrestMode; + + // 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 useArrestCam = false; + if(pTargetEntity->IsPed()){ + for(int i = 0; i < ((CPed*)pTargetEntity)->m_numNearPeds; i++){ + CPed *ped = ((CPed*)pTargetEntity)->m_nearPeds[i]; + if(ped && ped->GetPedState() == PED_ARREST_PLAYER) + if((ped->GetPosition() - pTargetEntity->GetPosition()).Magnitude() < 4.0f){ + ReqMode = CCam::MODE_ARRESTCAM_ONE; + Cams[ActiveCam].ResetStatics = true; + useArrestCam = true; + break; + } + } + } + if(!useArrestCam){ + ReqMode = CCam::MODE_PED_DEAD_BABY; + Cams[ActiveCam].ResetStatics = true; + } + } + } + + // Restore with a jump cut + if(m_bRestoreByJumpCut){ + if(ReqMode != CCam::MODE_FOLLOWPED && + ReqMode != CCam::MODE_BEHINDCAR && + ReqMode != CCam::MODE_CAM_ON_A_STRING && + ReqMode != CCam::MODE_M16_1STPERSON && + ReqMode != CCam::MODE_SYPHON && + ReqMode != CCam::MODE_SYPHON_CRIM_IN_FRONT && + ReqMode != CCam::MODE_SPECIAL_FIXED_FOR_SYPHON && + ReqMode != CCam::MODE_SNIPER && + ReqMode != CCam::MODE_ROCKETLAUNCHER && + ReqMode != CCam::MODE_CAMERA && + !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; + 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 || ReqMode == CCam::MODE_CAMERA || + WhoIsInControlOfTheCamera == CAMCONTROL_SCRIPT || + m_bJustCameOutOfGarage || m_bPlayerIsInGarage) + canUseObbeCam = false; + + if(m_bObbeCinematicPedCamOn && canUseObbeCam) + ProcessObbeCinemaCameraPed(); + else if(m_bObbeCinematicCarCamOn && canUseObbeCam){ + if(pTargetEntity->IsVehicle() && ((CVehicle*)pTargetEntity)->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI || + ((CVehicle*)pTargetEntity)->IsBoat()) + ProcessObbeCinemaCameraHeli(); + else + 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; + } + + // Going into Syphon mode + if(ReqMode == CCam::MODE_SYPHON || ReqMode == CCam::MODE_SYPHON_CRIM_IN_FRONT) + 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_CAMERA || + 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 || + Cams[ActiveCam].Mode == CCam::MODE_CAMERA){ + if(pTargetEntity && pTargetEntity->IsVehicle()) + switchByJumpCut = true; + } + }else if(ReqMode == CCam::MODE_FOLLOWPED){ + bool syphonJumpCut = false; + if(Cams[ActiveCam].Mode == CCam::MODE_SYPHON || Cams[ActiveCam].Mode == CCam::MODE_SYPHON_CRIM_IN_FRONT) + if(!((CPed*)pTargetEntity)->CanWeRunAndFireWithWeapon()) + syphonJumpCut = true; + 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_CAMERA || + Cams[ActiveCam].Mode == CCam::MODE_TOPDOWN || + Cams[ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED || + syphonJumpCut || stopArrestCam){ + 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 || + Cams[ActiveCam].Mode == CCam::MODE_CAMERA){ + 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; + 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; + }else if(ReqMode == CCam::MODE_LIGHTHOUSE || + ReqMode == CCam::MODE_ARRESTCAM_ONE || ReqMode == CCam::MODE_ARRESTCAM_TWO || + ReqMode == CCam::MODE_PED_DEAD_BABY) + switchByJumpCut = true; + else if(Cams[ActiveCam].Mode == CCam::MODE_PED_DEAD_BABY && ReqMode != CCam::MODE_PED_DEAD_BABY) + 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){ + 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; + + 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; + } + } + 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){ + if(m_uiTransitionState != 0) + StartTransitionWhenNotFinishedInter(ReqMode); + else + StartTransition(ReqMode); + pTargetEntity->RegisterReference(&pTargetEntity); + Cams[ActiveCam].CamTargetEntity->RegisterReference(&Cams[ActiveCam].CamTargetEntity); + } + }else{ + // not following player + bool useWeaponMode = false; + bool jumpCutTo1stPrs = false; + if(m_bEnable1rstPersonCamCntrlsScript || m_bAllow1rstPersonWeaponsCamera){ + if(ReqMode == CCam::MODE_1STPERSON){ + if(Cams[ActiveCam].Mode != ReqMode) + jumpCutTo1stPrs = true; + }else if((PlayerWeaponMode.Mode == CCam::MODE_SNIPER || PlayerWeaponMode.Mode == CCam::MODE_1STPERSON || PlayerWeaponMode.Mode == CCam::MODE_ROCKETLAUNCHER) && + CPad::GetPad(0)->GetTarget() && m_bAllow1rstPersonWeaponsCamera){ + useWeaponMode = true; + jumpCutTo1stPrs = true; + }else if(Cams[ActiveCam].Mode != m_iModeToGoTo){ + m_bStartInterScript = true; + m_iTypeOfSwitch = JUMP_CUT; + CPad::GetPad(0)->DisablePlayerControls &= ~PLAYERCONTROL_CAMERA; + } + } + + 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 || jumpCutTo1stPrs){ + m_uiTransitionState = 0; + m_vecDoingSpecialInterPolation = false; + if(m_bEnable1rstPersonCamCntrlsScript && ReqMode == CCam::MODE_1STPERSON) + Cams[ActiveCam].Mode = ReqMode; + else if(useWeaponMode) + Cams[ActiveCam].Mode = PlayerWeaponMode.Mode; + else + 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 || + Cams[ActiveCam].Mode == CCam::MODE_HELICANNON_1STPERSON || + Cams[ActiveCam].Mode == CCam::MODE_CAMERA) && pTargetEntity->IsPed() || + Cams[ActiveCam].Mode == CCam::MODE_FLYBY) + FindPlayerPed()->bIsVisible = false; + else + FindPlayerPed()->bIsVisible = true; + + bool switchedFromObbe = false; + if(!canUseObbeCam && WhoIsInControlOfTheCamera == CAMCONTROL_OBBE){ + RestoreWithJumpCut(); + switchedFromObbe = true; + SetCameraDirectlyBehindForFollowPed_CamOnAString(); + } + + if(PrevMode != Cams[ActiveCam].Mode || switchedFromObbe || + Cams[ActiveCam].Mode == CCam::MODE_FOLLOWPED || Cams[ActiveCam].Mode == CCam::MODE_CAM_ON_A_STRING) + if(CPad::GetPad(0)->CycleCameraModeJustDown() && + !CReplay::IsPlayingBack() && + (m_bLookingAtPlayer || WhoIsInControlOfTheCamera == CAMCONTROL_OBBE) && + !m_WideScreenOn && + (WhoIsInControlOfTheCamera != CAMCONTROL_OBBE || bSwitchedToObbeCam)) + DMAudio.PlayFrontEndSound(SOUND_HUD, 0); +} + +// What a mess! +void +CCamera::UpdateTargetEntity(void) +{ + bool enteringCar = false; + bool obbeCam = false; + + m_bPlayerWasOnBike = false; + if(pTargetEntity && pTargetEntity->IsVehicle() && ((CVehicle*)pTargetEntity)->IsBike()) + m_bPlayerWasOnBike = true; + + 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(); + // 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; + } + + bool cantOpen = true; + if(PLAYER){ + if(PLAYER->m_pMyVehicle){ + if(FindPlayerPed()->m_pMyVehicle->CanPedOpenLocks(PLAYER)) + cantOpen = false; + }else if(FindPlayerPed()->m_carInObjective && + (FindPlayerPed()->GetPedState() == PED_ENTER_CAR || + FindPlayerPed()->GetPedState() == PED_CARJACK || + FindPlayerPed()->GetPedState() == PED_OPEN_DOOR)){ + if(FindPlayerPed()->m_carInObjective->CanPedOpenLocks(FindPlayerPed())) + 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) + pTargetEntity = PLAYER->m_pMyVehicle; + if(PLAYER->m_pMyVehicle == nil) + pTargetEntity = PLAYER; + } + + 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_CAMERA || + 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; +} + +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(); + } +} + +bool bAvoidTest1 = false; +bool bAvoidTest2 = false; // unused +bool bAvoidTest3 = false; // unused +float fRangePlayerRadius = 0.5f; +float fCloseNearClipLimit = 0.15f; +float fAvoidTweakFOV = 1.15f; +float fAvoidProbTimerDamp = 0.9f; + +void +CCamera::AvoidTheGeometry(const CVector &Source, const CVector &TargetPos, CVector &NewSource, float FOV) +{ + float Beta = 0.0f; + float Alpha = 0.0f; + + CVector vDist = TargetPos - Source; + m_vecClearGeometryVec = CVector(0.0f, 0.0f, 0.0f); + float fDist = vDist.Magnitude(); + float fDistOnGround = vDist.Magnitude2D(); + if(vDist.x == 0.0f && vDist.y == 0.0f) + Beta = CGeneral::GetATanOfXY(GetForward().x, GetForward().y); + else + Beta = CGeneral::GetATanOfXY(vDist.x, vDist.y); + if(fDistOnGround != 0.0f || vDist.z != 0.0f) + Alpha = CGeneral::GetATanOfXY(fDistOnGround, vDist.z); + CVector Front(Cos(Alpha)*Cos(Beta), Cos(Alpha)*Sin(Beta), Sin(Alpha)); + NewSource = TargetPos - Front*fDist; + Front.Normalise(); + + // Clip camera source + CColPoint point; + CEntity *entity = nil; + CWorld::pIgnoreEntity = pTargetEntity; + if(CWorld::ProcessLineOfSight(TargetPos, NewSource, point, entity, true, false, false, true, false, false, true)){ + CVector ClipPoint1 = point.point; + NewSource = point.point; + if(!bAvoidTest1){ + if(CWorld::ProcessLineOfSight(NewSource, TargetPos, point, entity, false, true, true, true, false, false, true)){ + if((NewSource - point.point).Magnitude() < RwCameraGetNearClipPlane(Scene.camera)) + NewSource = point.point; + else if((NewSource - ClipPoint1).Magnitude() < RwCameraGetNearClipPlane(Scene.camera)) + NewSource = ClipPoint1; + } + } + } + CWorld::pIgnoreEntity = nil; + + + vDist = TargetPos - NewSource; + fDist = vDist.Magnitude(); + if(FindPlayerPed()) + if(fDist - fRangePlayerRadius < RwCameraGetNearClipPlane(Scene.camera)) + RwCameraSetNearClipPlane(Scene.camera, Max(fDist - fRangePlayerRadius, fCloseNearClipLimit)); + + + static float fClearGeomAmount; + static float fClearGeomAmountSpeed; + float Near = RwCameraGetNearClipPlane(Scene.camera); + float ViewPlaneHeight = Tan(DEGTORAD(FOV) / 2.0f); + float ViewPlaneWidth = ViewPlaneHeight * CDraw::CalculateAspectRatio() * fAvoidTweakFOV; + CVector Center = NewSource + Front*Near; + float fClearGeomTarget = 0.0f; + if(CWorld::TestSphereAgainstWorld(Center, ViewPlaneWidth, nil, true, false, false, true, false, true)){ + CVector CamToCol = gaTempSphereColPoints[0].point - NewSource; + float FrontDist = DotProduct(CamToCol, Front); + CVector CenterToCol = gaTempSphereColPoints[0].point - Center; + if(FrontDist < DEFAULT_NEAR && FrontDist > fCloseNearClipLimit){ + if(FrontDist < RwCameraGetNearClipPlane(Scene.camera)) + RwCameraSetNearClipPlane(Scene.camera, FrontDist); + }else if(FrontDist < fCloseNearClipLimit) + RwCameraSetNearClipPlane(Scene.camera, fCloseNearClipLimit); + + float ColDepth = ViewPlaneWidth - CenterToCol.Magnitude(); // amount of radius in collision + CenterToCol.Normalise(); + CVector Normal = gaTempSphereColPoints[0].normal; + Normal.Normalise(); + if(-DotProduct(CenterToCol, Normal) < 0.0f) + Normal = -Normal; // always push away from col surface + float DistToMove = DotProduct(-ColDepth*CenterToCol, Normal); + m_vecClearGeometryVec = DistToMove*Normal; // move source so this point is out of collision + + if(pTargetEntity && pTargetEntity->IsPed() && RwCameraGetNearClipPlane(Scene.camera) < 2.0f*fCloseNearClipLimit){ + float TargetNormalDir = DotProduct(Normal, pTargetEntity->GetForward()); + if(TargetNormalDir < 0.0f){ + // target looking towards collision + if(m_fAvoidTheGeometryProbsTimer < 0.0f) + m_fAvoidTheGeometryProbsTimer = 0.0f; + m_fAvoidTheGeometryProbsTimer += CTimer::GetTimeStep(); + }else if(TargetNormalDir > 0.5f){ + // target looking away from collision + if(m_fAvoidTheGeometryProbsTimer > 0.0f) + m_fAvoidTheGeometryProbsTimer = 0.0f; + m_fAvoidTheGeometryProbsTimer -= CTimer::GetTimeStep(); + } + + if(m_nAvoidTheGeometryProbsDirn == 0){ + if(CrossProduct(pTargetEntity->GetPosition() - NewSource, Normal).z > 0.0f) + m_nAvoidTheGeometryProbsDirn = -1; + else + m_nAvoidTheGeometryProbsDirn = 1; + } + } + + fClearGeomTarget = 1.0f; + } + + m_fAvoidTheGeometryProbsTimer *= Pow(fAvoidProbTimerDamp, CTimer::GetTimeStep()); + WellBufferMe(fClearGeomTarget, &fClearGeomAmount, &fClearGeomAmountSpeed, 0.2f, 0.05f, false); + m_vecClearGeometryVec *= fClearGeomAmount; + m_bMoveCamToAvoidGeom = true; +} + +void +CCamera::GetArrPosForVehicleType(int apperance, int &index) +{ + switch(apperance){ + case VEHICLE_APPEARANCE_CAR: index = 0; break; + case VEHICLE_APPEARANCE_BIKE: index = 1; break; + case VEHICLE_APPEARANCE_HELI: index = 2; break; + case VEHICLE_APPEARANCE_PLANE: index = 3; break; + case VEHICLE_APPEARANCE_BOAT: index = 4; break; + } +} + +void +CCamera::GetScreenRect(CRect &rect) +{ + rect.left = 0.0f; + rect.right = SCREEN_WIDTH; + if(m_WideScreenOn +#ifdef CUTSCENE_BORDERS_SWITCH + && CMenuManager::m_PrefsCutsceneBorders +#endif + ){ + float borderSize = (SCREEN_HEIGHT / 2) * (m_ScreenReductionPercentage / 100.f); + rect.top = borderSize - SCREEN_SCALE_Y(22.f); + rect.bottom = SCREEN_HEIGHT - borderSize - SCREEN_SCALE_Y(14.f); + }else{ + rect.top = 0.0f; + rect.bottom = SCREEN_HEIGHT; + } +} + + +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; + } +} + +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; +}; + +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_bEnable1rstPersonCamCntrlsScript = false; + m_bAllow1rstPersonWeaponsCamera = false; + m_bUseScriptZoomValuePed = false; + m_bUseScriptZoomValueCar = false; + m_bStartInterScript = true; + m_bCameraJustRestored = true; + m_fAvoidTheGeometryProbsTimer = 0.0f; +} + +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; + m_bEnable1rstPersonCamCntrlsScript = false; + m_bAllow1rstPersonWeaponsCamera = false; + + 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; + m_bGarageFixedCamPositionSet = false; +} + + +void +CCamera::StartTransition(int16 newMode) +{ + bool switchFromFixedSyphon = false; + bool switchSyphonMode = false; + bool switchPedMode = false; + bool switchPedToCar = false; + bool switchFromFight = false; + bool switchBikeToPed = false; + bool switchFromFixed = false; + bool switch1stPersonToVehicle = false; + float betaOffset, targetBeta, camBeta, deltaBeta; + int door; + bool vehicleVertical; + + 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) + switchPedMode = true; + if(newMode == CCam::MODE_CAM_ON_A_STRING) + switchPedToCar = true; + } + + if(Cams[ActiveCam].Mode == CCam::MODE_SPECIAL_FIXED_FOR_SYPHON) + switchFromFixedSyphon = true; + if(Cams[ActiveCam].Mode == CCam::MODE_CAM_ON_A_STRING && newMode == CCam::MODE_FOLLOWPED && m_bPlayerWasOnBike) + switchBikeToPed = true; + 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; + if(Cams[ActiveCam].Mode == CCam::MODE_FIXED) + switchFromFixed = true; + + 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_CAMERA || + 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; + } + + 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 || + newMode == CCam::MODE_CAMERA) + Cams[ActiveCam].Alpha = 0.0f; + + 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: + case CCam::MODE_CAMERA: + if(newMode == CCam::MODE_CAM_ON_A_STRING || newMode == CCam::MODE_BEHINDBOAT) + switch1stPersonToVehicle = true; + + switch(newMode){ + case CCam::MODE_BEHINDCAR: + Cams[ActiveCam].BetaSpeed = 0.0f; + break; + + case CCam::MODE_BEHINDBOAT: + Cams[ActiveCam].BetaSpeed = 0.0f; + break; + + case CCam::MODE_FOLLOWPED: + // Getting out of vehicle normally + betaOffset = DEGTORAD(55.0f); + if(m_bJustCameOutOfGarage){ + m_bUseTransitionBeta = true; + if(Cams[ActiveCam].Front.x != 0.0f || Cams[ActiveCam].Front.y != 0.0f) + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y) + PI; + else + Cams[ActiveCam].m_fTransitionBeta = 0.0f; + } + if(m_bTargetJustCameOffTrain) + m_bCamDirectlyInFront = true; + if(Cams[ActiveCam].Mode != CCam::MODE_CAM_ON_A_STRING) + 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; + } + camBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + 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: + case CCam::MODE_CAMERA: + 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: + 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); + if(!m_bLookingAtPlayer || m_bJustCameOutOfGarage) + break; + m_bUseTransitionBeta = true; + Cams[ActiveCam].m_fTransitionBeta = CGeneral::GetATanOfXY(Cams[ActiveCam].Front.x, Cams[ActiveCam].Front.y); + break; + + case CCam::MODE_PED_DEAD_BABY: + Cams[ActiveCam].Alpha = DEGTORAD(15.0f); + break; + + case CCam::MODE_FIGHT_CAM: + Cams[ActiveCam].Beta = 0.0f; + Cams[ActiveCam].BetaSpeed = 0.0f; + Cams[ActiveCam].Alpha = 0.0f; + Cams[ActiveCam].AlphaSpeed = 0.0f; + break; + } + + 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.1f; + m_fFractionInterToStopCatchUp = 0.9f; + m_uiTransitionDuration = 750; + }else if(switchFromFixedSyphon){ + m_fFractionInterToStopMoving = 0.0f; + m_fFractionInterToStopCatchUp = 1.0f; + m_uiTransitionDuration = 600; + }else if(switchFromFixed){ + m_fFractionInterToStopMoving = 0.05f; + m_fFractionInterToStopCatchUp = 0.95f; + }else if(switchBikeToPed){ + m_uiTransitionDuration = 800; + }else if(switch1stPersonToVehicle){ + m_fFractionInterToStopMoving = 0.0f; + m_fFractionInterToStopCatchUp = 1.0f; + m_uiTransitionDuration = 1; + }else if(switchPedMode){ + m_fFractionInterToStopMoving = 0.5f; + m_fFractionInterToStopCatchUp = 0.5f; + m_uiTransitionDuration = 350; + }else + m_uiTransitionDuration = 1350; // already set above + m_uiTransitionState = 1; + m_uiTimeTransitionStart = CTimer::GetTimeInMilliseconds(); + m_uiTransitionJUSTStarted = 1; + 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){ + if(switchPedMode) + m_uiTransitionDurationTargetCoors = 350; + else + m_uiTransitionDurationTargetCoors = 600; + m_fFractionInterToStopMovingTarget = 0.0f; + m_fFractionInterToStopCatchUpTarget = 1.0f; + }else{ + if(m_bScriptParametersSetForInterPol){ + m_fFractionInterToStopMoving = m_fScriptPercentageInterToStopMoving; + m_fFractionInterToStopCatchUp = m_fScriptPercentageInterToCatchUp; + m_uiTransitionDuration = m_fScriptTimeForInterPolation; + } + m_uiTransitionDurationTargetCoors = m_uiTransitionDuration; + m_fFractionInterToStopMovingTarget = m_fFractionInterToStopMoving; + m_fFractionInterToStopCatchUpTarget = m_fFractionInterToStopCatchUp; + } +} + +void +CCamera::StartTransitionWhenNotFinishedInter(int16 mode) +{ + m_vecDoingSpecialInterPolation = true; + StartTransition(mode); +} + +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); +} + + + +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) +{ + float bottom, top; + if (m_WideScreenOn) { + float borderSize = (SCREEN_HEIGHT / 2) * (m_ScreenReductionPercentage / 100.f); + top = borderSize - SCREEN_SCALE_Y(22.f); + bottom = SCREEN_HEIGHT - borderSize - SCREEN_SCALE_Y(14.f); + } else { + top = 0.f; + bottom = SCREEN_HEIGHT; + } + + if(m_BlurType == MOTION_BLUR_NONE || m_BlurType == MOTION_BLUR_LIGHT_SCENE) + SetMotionBlurAlpha(80); + + // top border + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, top), CRGBA(0, 0, 0, 255)); + + // bottom border + CSprite2d::DrawRect(CRect(0.0f, bottom, SCREEN_WIDTH, SCREEN_HEIGHT), 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() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + return true; + if(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() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + 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() > 40.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() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + 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() > 29.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + // too close + if(fwd.Magnitude() < 2.0f) + 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() > 48.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() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + 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() > 38.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; + + // Heli modes + case OBBE_14: + if(FindPlayerVehicle()) + if(!CWorld::GetIsLineOfSightClear(pTargetEntity->GetPosition(), Cams[ActiveCam].Source, true, false, false, false, false, false, false)) + return true; + return CTimer::GetTimeInMilliseconds() > t+8000; + case OBBE_15: + if(FindPlayerVehicle()){ + 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() > 44.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + // too close + if(fwd.Magnitude() < 2.0f) + return true; + } + return false; + case OBBE_16: + if(FindPlayerVehicle()){ + 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() > 50.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return true; + // too close + if(fwd.Magnitude() < 3.0f) + return true; + } + return false; + case OBBE_17: + if(FindPlayerVehicle()){ + 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 + if(fwd.Magnitude() > 50.0f) + return true; + // too close + if(fwd.Magnitude() < 2.0f) + return true; + } + return false; + case OBBE_18: + if(FindPlayerVehicle()){ + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), m_vecFixedModeSource, true, false, false, false, false, false, false)) + return true; + fwd = FindPlayerCoors() - m_vecFixedModeSource; + + // too far + if(fwd.Magnitude() > 57.0f) + return true; + // too close + if(fwd.Magnitude() < 1.0f) + return true; + } + return false; + case OBBE_19: + if(FindPlayerVehicle()){ + 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 + if(fwd.Magnitude() > 36.0f) + return true; + // too close + if(fwd.Magnitude() < 2.0f) + return true; + } + return false; + case OBBE_ONSTRING_HELI: + return CTimer::GetTimeInMilliseconds() > t+5000; + + default: + return false; + } +} + +bool +CCamera::TryToStartNewCamMode(int32 obbeMode) +{ + CVehicle *veh; + CVector target, camPos, playerSpeed, fwd, fwd2; + float angle; + float ground; + bool foundGround; + int i; + + if(obbeMode < 0) + return true; + switch(obbeMode){ + case OBBE_WHEEL: + veh = FindPlayerVehicle(); + if(veh == nil || (veh->IsBoat() && pTargetEntity->GetModelIndex() != MI_SKIMMER) || 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() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + 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() > 40.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return false; + // too close + if(fwd.Magnitude() < 2.5f) + 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() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + 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() > 29.0f && DotProduct(FindPlayerSpeed(), fwd) > 0.0f) + return false; + // too close + if(fwd.Magnitude() < 2.0f) + 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() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + 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() && pTargetEntity->GetModelIndex() != MI_SKIMMER) + 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; + + // Heli modes + case OBBE_14: + veh = FindPlayerVehicle(); + if(veh == nil) + return false; + target = Multiply3x3(FindPlayerVehicle()->GetMatrix(), CVector(-1.4f, -2.3f, 0.3f)); + target += FindPlayerVehicle()->GetPosition(); + if(!veh->IsBoat() && !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_15: + if(FindPlayerVehicle() == nil) + return false; + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + camPos += 34.0f*playerSpeed; + camPos.z = FindPlayerCoors().z + 0.5f; + if(FindPlayerVehicle()->IsBoat()) + camPos.z += 1.0f; + + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + fwd2 = FindPlayerCoors() - camPos; + fwd2.z = 0.0f; + // too far and driving away from cam + if(fwd.Magnitude() > 44.0f && DotProduct(FindPlayerSpeed(), fwd2) > 0.0f) + return false; + // too close + if(fwd.Magnitude() < 3.0f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_16: + if(FindPlayerVehicle() == nil) + return false; + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + angle = CGeneral::GetATanOfXY(playerSpeed.x, playerSpeed.y) + DEGTORAD(60.0f); + playerSpeed += CVector(Cos(angle), Sin(angle), 0.0f); + playerSpeed.Normalise(); + camPos += 30.0f*playerSpeed; + camPos.z = FindPlayerCoors().z - 5.5f; + + foundGround = false; + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + else if(CWaterLevel::GetWaterLevelNoWaves(camPos.x, camPos.y, camPos.z, &ground)){ + float waterOffset = 1.0f; + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + waterOffset = -2.0f; + if(camPos.z < ground + waterOffset) + camPos.z = ground + waterOffset; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + // too far + if(fwd.Magnitude() > 50.0f) + return false; + // too close + if(fwd.Magnitude() < 3.0f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_17: + if(FindPlayerVehicle() == nil) + return false; + camPos = FindPlayerCoors(); + playerSpeed = FindPlayerSpeed(); + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + angle = CGeneral::GetATanOfXY(playerSpeed.x, playerSpeed.y) + DEGTORAD(190.0f); + playerSpeed += CVector(Cos(angle), Sin(angle), 0.0f); + playerSpeed.Normalise(); + camPos += 25.0f*playerSpeed; + camPos.z = FindPlayerCoors().z - 1.0f; + + foundGround = false; + ground = CWorld::FindRoofZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); + if(foundGround) + camPos.z = ground + 0.5f; + else if(CWaterLevel::GetWaterLevelNoWaves(camPos.x, camPos.y, camPos.z, &ground)){ + float waterOffset = 1.0f; + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + waterOffset = -2.0f; + if(camPos.z < ground + waterOffset) + camPos.z = ground + waterOffset; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + fwd2 = FindPlayerCoors() - camPos; + fwd2.z = 0.0f; + // too far and driving away from cam + if(fwd.Magnitude() > 50.0f && DotProduct(FindPlayerSpeed(), fwd2) > 0.0f) + return false; + // too close + if(fwd.Magnitude() < 2.0f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_18: + camPos = FindPlayerCoors(); + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + camPos.z += 23.0f; + else + camPos.z -= 23.0f; + playerSpeed = FindPlayerSpeed(); + angle = CGeneral::GetATanOfXY(playerSpeed.x, playerSpeed.y) + DEGTORAD(145.0f); + playerSpeed += CVector(Cos(angle), Sin(angle), 0.0f); + playerSpeed.Normalise(); + camPos += 15.0f*playerSpeed; + + foundGround = false; + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); +#ifdef FIX_BUGS + if(foundGround) +#else + if(ground == true) +#endif + { + if(camPos.z < ground) + camPos.z = ground + 0.5f; + }else if(CWaterLevel::GetWaterLevelNoWaves(camPos.x, camPos.y, camPos.z, &ground)){ + float waterOffset = 1.0f; + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + waterOffset = -2.0f; + if(camPos.z < ground + waterOffset) + camPos.z = ground + waterOffset; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + // too far + if(fwd.Magnitude() > 57.0f) + return false; + // too close + if(fwd.Magnitude() < 1.0f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_19: + camPos = FindPlayerCoors(); + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + camPos.z += 4.0f; + else + camPos.z -= 1.0f; + playerSpeed = FindPlayerSpeed(); + angle = CGeneral::GetATanOfXY(playerSpeed.x, playerSpeed.y) + DEGTORAD(28.0f); + playerSpeed += CVector(Cos(angle), Sin(angle), 0.0f); + playerSpeed.Normalise(); + camPos += 12.5f*playerSpeed; + + foundGround = false; + ground = CWorld::FindGroundZFor3DCoord(camPos.x, camPos.y, camPos.z+5.0f, &foundGround); +#ifdef FIX_BUGS + if(foundGround) +#else + if(ground == true) +#endif + { + if(camPos.z < ground) + camPos.z = ground + 0.5f; + }else if(CWaterLevel::GetWaterLevelNoWaves(camPos.x, camPos.y, camPos.z, &ground)){ + float waterOffset = 1.0f; + if(FindPlayerVehicle() && FindPlayerVehicle()->IsBoat()) + waterOffset = -2.0f; + if(camPos.z < ground + waterOffset) + camPos.z = ground + waterOffset; + } + if(!CWorld::GetIsLineOfSightClear(FindPlayerCoors(), camPos, true, false, false, false, false, false, false)) + return false; + + fwd = FindPlayerCoors() - camPos; + // too far + if(fwd.Magnitude() > 36.0f) + return false; + // too close + if(fwd.Magnitude() < 2.0f) + return true; + + SetCamPositionForFixedMode(camPos, CVector(0.0f, 0.0f, 0.0f)); + TakeControl(FindPlayerEntity(), CCam::MODE_FIXED, JUMP_CUT, CAMCONTROL_OBBE); + return true; + case OBBE_ONSTRING_HELI: + TakeControl(FindPlayerEntity(), CCam::MODE_CAM_ON_A_STRING, JUMP_CUT, CAMCONTROL_OBBE); + return true; + default: + return false; + } +} + +int32 SequenceOfCarCams[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; + bSwitchedToObbeCam = true; + } + + if(!bDidWeProcessAnyCinemaCam || IsItTimeForNewcam(SequenceOfCarCams[OldMode], TimeForNext)){ + // This is very strange code... + for(OldMode = (OldMode+1) % 14; + !TryToStartNewCamMode(SequenceOfCarCams[OldMode]) && i <= 14; + OldMode = (OldMode+1) % 14) + i++; + TimeForNext = CTimer::GetTimeInMilliseconds(); + if(i >= 14){ + OldMode = 14; + TryToStartNewCamMode(SequenceOfCarCams[14]); + } + } + + m_iModeObbeCamIsInForCar = OldMode; + bDidWeProcessAnyCinemaCam = true; +} + +int32 SequenceOfHeliCams[6] = { OBBE_14, OBBE_15, OBBE_16, OBBE_17, OBBE_18, OBBE_19 }; + +void +CCamera::ProcessObbeCinemaCameraHeli(void) +{ + static int OldMode = -1; + static int32 TimeForNext = 0; + int i = 0; + + if(!bDidWeProcessAnyCinemaCam){ + OldMode = -1; + bSwitchedToObbeCam = true; + } + + if(!bDidWeProcessAnyCinemaCam || IsItTimeForNewcam(SequenceOfHeliCams[OldMode], TimeForNext)){ + // This is very strange code... + for(OldMode = (OldMode+1) % 6; + !TryToStartNewCamMode(SequenceOfCarCams[OldMode]) && i <= 6; + OldMode = (OldMode+1) % 6) + i++; + if(i >= 6){ + OldMode = 6; + if(Cams[ActiveCam].Mode != CCam::MODE_CAM_ON_A_STRING){ + TryToStartNewCamMode(OBBE_ONSTRING_HELI); + TimeForNext = CTimer::GetTimeInMilliseconds(); + } + }else + TimeForNext = CTimer::GetTimeInMilliseconds(); + } + + 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; +} + +#ifdef GTA_TRAIN +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(); + } + } +} +#endif + + +void +CCamera::LoadPathSplines(int file) +{ + bool reading = true; + char c, token[32] = { 0 }; + int i, j, n; + + n = 0; + + DeleteCutSceneCamDataMemory(); + for(i = 0; i < MAX_NUM_OF_SPLINETYPES; i++) + m_arrPathArray[i].m_arr_PathData = new float[CCamPathSplines::MAXPATHLENGTH]; + +// Why is this gone? +// 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; + if (i == MAX_NUM_OF_SPLINETYPES) + reading = false; + memset(token, 0, 32); + n = 0; + } + } +} + +void +CCamera::DeleteCutSceneCamDataMemory(void) +{ + int i; + for(i = 0; i < MAX_NUM_OF_SPLINETYPES; i++) + if(m_arrPathArray[i].m_arr_PathData){ + delete[] m_arrPathArray[i].m_arr_PathData; + m_arrPathArray[i].m_arr_PathData = nil; + } +} + +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) +{ + if (Cams[ActiveCam].CamTargetEntity->IsVehicle()) { + int vehApp = ((CVehicle*)Cams[ActiveCam].CamTargetEntity)->GetVehicleAppearance(); + int vehArrPos = 0; + GetArrPosForVehicleType(vehApp, vehArrPos); + +#ifdef FREE_CAM + if (bFreeCam) { + switch (dist) { + case 0: m_fCarZoomValueScript = LCS_ZOOM_ONE_DISTANCE[vehArrPos]; break; + case 1: m_fCarZoomValueScript = LCS_ZOOM_TWO_DISTANCE[vehArrPos]; break; + case 2: m_fCarZoomValueScript = LCS_ZOOM_THREE_DISTANCE[vehArrPos]; break; + default: break; + } + } + else +#endif + { + switch (dist) { + case 0: m_fCarZoomValueScript = ZOOM_ONE_DISTANCE[vehArrPos]; break; + case 1: m_fCarZoomValueScript = ZOOM_TWO_DISTANCE[vehArrPos]; break; + case 2: m_fCarZoomValueScript = ZOOM_THREE_DISTANCE[vehArrPos]; break; + default: break; + } + } + + m_bUseScriptZoomValueCar = true; + } else { + 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::SetNearClipScript(float clip) +{ + m_fNearClipScript = clip; + m_bUseNearClipScript = true; +} + + + +void +CCamera::ProcessFade(void) +{ + if(m_bFading){ + if(m_iFadingDirection == FADE_IN){ + if(m_fTimeToFadeOut != 0.0f) + m_fFLOATingFade -= CTimer::GetTimeStepInSeconds() * 255.0f / m_fTimeToFadeOut; + else + m_fFLOATingFade = 0.0f; + if (m_fFLOATingFade <= 0.0f) { + m_bFading = false; + m_fFLOATingFade = 0.0f; + } + }else if(m_iFadingDirection == FADE_OUT){ + if(m_fTimeToFadeOut != 0.0f) + m_fFLOATingFade += CTimer::GetTimeStepInSeconds() * 255.0f / m_fTimeToFadeOut; + else + m_fFLOATingFade = 255.0f; + if (m_fFLOATingFade >= 255.0f) { + m_bFading = false; + m_fFLOATingFade = 255.0f; + } + } + CDraw::FadeValue = m_fFLOATingFade; + } +} + +void +CCamera::ProcessMusicFade(void) +{ + if(m_bMusicFading){ + if(m_iMusicFadingDirection == FADE_IN){ + if(m_fTimeToFadeMusic == 0.0f) + m_fFLOATingFadeMusic = 0.0f; + else + m_fFLOATingFadeMusic -= 255.0f*CTimer::GetTimeStepInSeconds()/m_fTimeToFadeMusic; + if(m_fFLOATingFadeMusic <= 0.0f){ + m_bMusicFading = false; + m_fFLOATingFadeMusic = 0.0f; + } + }else if(m_iMusicFadingDirection == FADE_OUT){ + if(m_fTimeToFadeMusic == 0.0f) + m_fFLOATingFadeMusic = 255.0f; + else + m_fFLOATingFadeMusic += 255.0f*CTimer::GetTimeStepInSeconds()/m_fTimeToFadeMusic; + if(m_fFLOATingFadeMusic >= 255.0f){ + m_bMusicFading = false; + m_fFLOATingFadeMusic = 255.0f; + } + } + 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(); + } +} + +void +CCamera::SetFadeColour(uint8 r, uint8 g, uint8 b) +{ + m_FadeTargetIsSplashScreen = r == 2 && g == 2 && b == 2; + 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) +{ + SetMotionBlur(CTimeCycle::GetBlurRed(), CTimeCycle::GetBlurGreen(), CTimeCycle::GetBlurBlue(), m_motionBlur, MOTION_BLUR_LIGHT_SCENE); + PlayerWeaponMode.Mode = mode; + PlayerWeaponMode.MaxZoom = maxZoom; + PlayerWeaponMode.MinZoom = minZoom; + PlayerWeaponMode.Duration = 0.0f; +} + +void +CCamera::ClearPlayerWeaponMode(void) +{ + SetMotionBlur(CTimeCycle::GetBlurRed(), CTimeCycle::GetBlurGreen(), CTimeCycle::GetBlurBlue(), m_motionBlur, MOTION_BLUR_LIGHT_SCENE); + 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); +} + +bool +CCamera::Using1stPersonWeaponMode(void) +{ + switch(PlayerWeaponMode.Mode) + case CCam::MODE_SNIPER: + case CCam::MODE_M16_1STPERSON: + case CCam::MODE_ROCKETLAUNCHER: + case CCam::MODE_HELICANNON_1STPERSON: + case CCam::MODE_CAMERA: + return true; + return false; +} + + +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); + + CDraw::CalculateAspectRatio(); + c /= SCREEN_ASPECT_RATIO; + s /= SCREEN_ASPECT_RATIO; + // 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) +{ + return IsSphereVisible(center, radius, &GetCameraMatrix()); +} + +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) +{ + m_arr_PathData = nil; +} diff --git a/src/miami/core/Camera.h b/src/miami/core/Camera.h new file mode 100644 index 00000000..39ecb760 --- /dev/null +++ b/src/miami/core/Camera.h @@ -0,0 +1,645 @@ +#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, +}; + +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_LIGHTHOUSE, + MODE_SNIPER_RUNABOUT, + MODE_ROCKETLAUNCHER_RUNABOUT, + MODE_1STPERSON_RUNABOUT, + MODE_M16_1STPERSON_RUNABOUT, + MODE_FIGHT_CAM_RUNABOUT, + MODE_EDITOR, + MODE_HELICANNON_1STPERSON, + MODE_CAMERA, + }; + + 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_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_fTilt; + float m_fTiltSpeed; + 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; + + float m_fTargetZoomGroundOne; + float m_fTargetZoomGroundTwo; + float m_fTargetZoomGroundThree; + float m_fTargetZoomOneZExtra; + float m_fTargetZoomTwoZExtra; + float m_fTargetZoomThreeZExtra; + float m_fTargetZoomZCloseIn; + float m_fMinRealGroundDist; + float m_fTargetCloseInDist; + + 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); + void WorkOutCamHeight(const CVector &TargetCoors, float TargetOrientation, float TargetHeight); + bool RotCamIfInFrontCar(CVector &TargetCoors, float TargetOrientation); + void Cam_On_A_String_Unobscured(const CVector &TargetCoors, float BaseDist); + void FixCamWhenObscuredByVehicle(const CVector &TargetCoors); + bool GetBoatLook_L_R_HeightOffset(float &Offset); + 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); + bool 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_SpecialFixedForSyphon(const CVector &CameraTarget, float, float, float); + void Process_LightHouse(const CVector &CameraTarget, float, float, float); + void ProcessPedsDeadBaby(void); + bool ProcessArrestCamOne(void); + bool ProcessArrestCamTwo(void); + bool GetLookAlongGroundPos(CEntity *Target, CPed *Cop, CVector &TargetCoors, CVector &SourceOut); + bool GetLookFromLampPostPos(CEntity *Target, CPed *Cop, CVector &TargetCoors, CVector &SourceOut); + bool GetLookOverShoulderPos(CEntity *Target, CPed *Cop, CVector &TargetCoors, CVector &SourceOut); + + // custom stuff + void Process_FollowPed_Rotation(const CVector &CameraTarget, float TargetOrientation, float, float); + void Process_FollowCar_SA(const CVector &CameraTarget, float TargetOrientation, float, float); +}; + +class CCamPathSplines +{ +public: + enum {MAXPATHLENGTH=800}; +// float m_arr_PathData[MAXPATHLENGTH]; + float *m_arr_PathData; + 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_bJustJumpedOutOf1stPersonBecauseOfTarget; + bool m_bIdleOn; + bool m_bInATunnelAndABigVehicle; + bool m_bInitialNodeFound; + bool m_bInitialNoNodeStaticsSet; + bool m_bIgnoreFadingStuffForMusic; + bool m_bPlayerIsInGarage; + bool m_bPlayerWasOnBike; + 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_bVehicleSuspenHigh; + bool m_bEnable1rstPersonCamCntrlsScript; + bool m_bAllow1rstPersonWeaponsCamera; + 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_uiTimeWeLeftIdle_StillNoInput; + uint32 m_uiTimeWeEnteredIdle; + uint32 m_uiTimeTransitionStart; + uint32 m_uiTransitionDuration; + uint32 m_uiTransitionDurationTargetCoors; + int m_BlurBlue; + int m_BlurGreen; + int m_BlurRed; + int m_BlurType; + + 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; + float FOVDuringInter; + float LODDistMultiplier; + float GenerationDistMultiplier; + float m_fAlphaSpeedAtStartInter; + float m_fAlphaWhenInterPol; + float m_fAlphaDuringInterPol; + float m_fBetaDuringInterPol; + float m_fBetaSpeedAtStartInter; + float m_fBetaWhenInterPol; + float m_fFOVWhenInterPol; + float m_fFOVSpeedAtStartInter; + float m_fStartingBetaForInterPol; + float m_fStartingAlphaForInterPol; + float m_PedOrientForBehindOrInFront; + float m_CameraAverageSpeed; + float m_CameraSpeedSoFar; + float m_fCamShakeForce; + float m_fCarZoomValueScript; + float m_fFovForTrain; + float m_fFOV_Wide_Screen; + float m_fNearClipScript; + float m_fOldBetaDiff; + float m_fPedZoomValue; + + float m_fPedZoomValueScript; + float m_fPedZoomValueSmooth; + float m_fPositionAlongSpline; + float m_ScreenReductionPercentage; + float m_ScreenReductionSpeed; + float m_AlphaForPlayerAnim1rstPerson; + float Orientation; +#ifdef FIX_BUGS + int32 PedZoomIndicator; +#else + float PedZoomIndicator; +#endif + float PlayerExhaustion; + float SoundDistUp; + float SoundDistUpAsRead; + float SoundDistUpAsReadOld; + float m_fAvoidTheGeometryProbsTimer; + int16 m_nAvoidTheGeometryProbsDirn; + float m_fWideScreenReductionAmount; + float m_fStartingFOVForInterPol; + + static float m_fMouseAccelHorzntl;// acceleration multiplier for 1st person controls + static float m_fMouseAccelVertical;// acceleration multiplier for 1st person controls + static float m_f3rdPersonCHairMultX; + static 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; + + CVector m_cvecStartingSourceForInterPol; + CVector m_cvecStartingTargetForInterPol; + CVector m_cvecStartingUpForInterPol; + CVector m_cvecSourceSpeedAtStartInter; + CVector m_cvecTargetSpeedAtStartInter; + CVector m_cvecUpSpeedAtStartInter; + CVector m_vecSourceWhenInterPol; + CVector m_vecTargetWhenInterPol; + CVector m_vecUpWhenInterPol; + CVector m_vecClearGeometryVec; + CVector m_vecGameCamPos; + CVector SourceDuringInter; + CVector TargetDuringInter; + CVector UpDuringInter; + RwCamera *m_pRwCamera; + CEntity *pTargetEntity; + CCamPathSplines m_arrPathArray[MAX_NUM_OF_SPLINETYPES]; +#ifdef GTA_TRAIN + CTrainCamNode m_arrTrainCamNode[MAX_NUM_OF_NODES]; +#endif + 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_fFractionInterToStopMovingTarget; + float m_fFractionInterToStopCatchUpTarget; + float m_fGaitSwayBuffer; + float m_fScriptPercentageInterToStopMoving; + float m_fScriptPercentageInterToCatchUp; + + uint32 m_fScriptTimeForInterPolation; + + + int16 m_iFadingDirection; + int m_iModeObbeCamIsInForCar; + int16 m_iModeToGoTo; + int16 m_iMusicFadingDirection; + int16 m_iTypeOfSwitch; + + uint32 m_uiFadeTimeStarted; + uint32 m_uiFadeTimeStartedMusic; + + static bool m_bUseMouse3rdPerson; +#ifdef FREE_CAM + static bool bFreeCam; +#endif + + // High level and misc + CCamera(void); + 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; } + void AvoidTheGeometry(const CVector &Source, const CVector &TargetPos, CVector &NewSource, float FOV); + void GetArrPosForVehicleType(int apperance, int &index); + void GetScreenRect(CRect &rect); + + // 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 ProcessObbeCinemaCameraHeli(void); + void ProcessObbeCinemaCameraPed(void); + + // Train + void LoadTrainCamNodes(char const *name); + void Process_Train_Camera_Control(void); + + // Script + void LoadPathSplines(int file); + void DeleteCutSceneCamDataMemory(void); + 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); + bool Using1stPersonWeaponMode(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/miami/core/CdStream.cpp b/src/miami/core/CdStream.cpp new file mode 100644 index 00000000..977f16c2 --- /dev/null +++ b/src/miami/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/miami/core/CdStream.h b/src/miami/core/CdStream.h new file mode 100644 index 00000000..0a84d01a --- /dev/null +++ b/src/miami/core/CdStream.h @@ -0,0 +1,59 @@ +#pragma once +#include + +#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); + +struct AudioReadCmd { + void* dest; + int fd; + size_t size; + size_t seek; + std::function callback; +}; +void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek, std::function callback = nullptr); +void CdStreamDiscardAudioRead(int fd); + +#ifdef FLUSHABLE_STREAMING +extern bool flushStream[MAX_CDCHANNELS]; +#endif diff --git a/src/miami/core/CdStreamDC.cpp b/src/miami/core/CdStreamDC.cpp new file mode 100644 index 00000000..ab83f872 --- /dev/null +++ b/src/miami/core/CdStreamDC.cpp @@ -0,0 +1,738 @@ +#if defined RW_DC +#include "common.h" +#include "crossplatform.h" +#include +#include +#if defined(DC_SIM) +#include +#include +#endif +#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 + +#if defined(DC_SIM) +#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 +#else +#define sem_t semaphore_t +#define SEM_FAILED ((semaphore_t*)-1) + + +int sem_post(sem_t *sem) { + return sem_signal(sem); +} + +#define RE3_SEM_OPEN re3_sem_open +sem_t* +re3_sem_open(const char* format, ...) +{ + sem_t* rv = (sem_t*)malloc(sizeof(sem_t)); + assert(sem_init(rv, 1) == 0); + return rv; +} + +#define RE3_SEM_CLOSE re3_sem_close +void +re3_sem_close(sem_t* sem, const char* format, ...) +{ + assert(sem_destroy(sem) == 0); +} +#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; + // } + // RwUInt32 block_size = 2048; +#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)block_size); + // 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 + // This cancels the operation in the middle of read(), if any, does nothing otherwise + pthread_kill(pChannel->pChannelThread, SIGUSR1); + if (pChannel->bReading) { + pChannel->bLocked = true; +#else + if (pChannel->bReading) { + pChannel->bLocked = true; + // This cancels the operation in the middle of read(), if any, does nothing otherwise + 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; +} + +std::vector pendingAudioReads; +#if !defined(DC_SH4) +std::mutex pendingAudioReadsMutex; +#endif +// Will replace a previous read request for the same file descriptor +void CdStreamQueueAudioRead(int fd, void* pBuffer, size_t bytes, size_t seek, std::function callback) { + AudioReadCmd cmd = { pBuffer, fd, bytes, seek}; + if (!callback) { + cmd.callback = [](AudioReadCmd* cmd){ + lseek(cmd->fd, cmd->seek, SEEK_SET); + read(cmd->fd, cmd->dest, cmd->size); + }; + } else { + cmd.callback = callback; + } + { + #if !defined(DC_SH4) + std::lock_guard lock(pendingAudioReadsMutex); + #else + auto mask = irq_disable(); + #endif + for (auto it = pendingAudioReads.rbegin(); it != pendingAudioReads.rend(); ++it) { + if (it->fd == -1 || it->fd == fd) { + *it = cmd; + goto out; + } + } + pendingAudioReads.push_back(cmd); + out: + #if !defined(DC_SH4) + ; + #else + irq_restore(mask); + #endif + } + sem_post(gCdStreamSema); +} + + +void CdStreamDiscardAudioRead(int fd) { + #if !defined(DC_SH4) + std::lock_guard lock(pendingAudioReadsMutex); + #else + auto mask = irq_disable(); + #endif + + for (auto &pending: pendingAudioReads) { + if (pending.fd == fd) { + pending.fd = -1; + } + } + #if defined(DC_SH4) + irq_restore(mask); + #endif +} + +AudioReadCmd CdStreamNextAudioRead() { + #if !defined(DC_SH4) + std::lock_guard lock(pendingAudioReadsMutex); + #else + auto mask = irq_disable(); + #endif + AudioReadCmd cmd = { nil, -1, 0, 0}; + for (auto &pending: pendingAudioReads) { + if (pending.fd != -1) { + cmd = pending; + pending.fd = -1; + break; + } + } + #if defined(DC_SH4) + irq_restore(mask); + #endif + return cmd; +} + +int read_loop(int fd, void* pBuffer, size_t bytes) { + size_t total_read = 0; + while (total_read < bytes) { + size_t to_read = bytes - total_read; + if (to_read > 64 * 1024) + to_read = 64 * 1024; + ssize_t read_bytes = read(fd, (char*)pBuffer + total_read, to_read); + if (read_bytes == -1) { + return -1; + } + total_read += read_bytes; + auto cmd = CdStreamNextAudioRead(); + while (cmd.fd != -1) { + cmd.callback(&cmd); + cmd = CdStreamNextAudioRead(); + } + } + return total_read; +} +void *CdStreamThread(void *param) +{ + debug("Created cdstream thread\n"); + +#ifndef ONE_THREAD_PER_CHANNEL + while (gCdStreamThreadStatus != 2) { + sem_wait(gCdStreamSema); + + auto cmd = CdStreamNextAudioRead(); + while (cmd.fd != -1) { + cmd.callback(&cmd); + cmd = CdStreamNextAudioRead(); + } + + 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_loop(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); + return NULL; +} + +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/miami/core/CdStreamPosix.cpp b/src/miami/core/CdStreamPosix.cpp new file mode 100644 index 00000000..bc9129eb --- /dev/null +++ b/src/miami/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/miami/core/Clock.cpp b/src/miami/core/Clock.cpp new file mode 100644 index 00000000..a3298343 --- /dev/null +++ b/src/miami/core/Clock.cpp @@ -0,0 +1,136 @@ +#include "common.h" + +#include "Timer.h" +#include "Pad.h" +#include "Clock.h" +#include "Stats.h" +#include "VarConsole.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; + +#ifndef MASTER +bool gbFreezeTime; +#endif + +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"); +#ifndef MASTER + VarConsole.Add("Time (hour of day)", &ms_nGameClockHours, 1, 0, 23, true); + VarConsole.Add("Freeze time", &gbFreezeTime, true); +#endif +} + +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; + } + + } +#ifndef MASTER + else if (gbFreezeTime) + ms_nLastClockTick = CTimer::GetTimeInMilliseconds(); +#endif + 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) +{ + while (m >= 60) { + m -= 60; + h++; + } + ms_nGameClockMinutes = m; + while (h >= 24) + h -= 24; + ms_nGameClockHours = h; + 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/miami/core/Clock.h b/src/miami/core/Clock.h new file mode 100644 index 00000000..a611cd50 --- /dev/null +++ b/src/miami/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/miami/core/ControllerConfig.cpp b/src/miami/core/ControllerConfig.cpp new file mode 100644 index 00000000..dbb0bd87 --- /dev/null +++ b/src/miami/core/ControllerConfig.cpp @@ -0,0 +1,2918 @@ +#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; + int nVersion = 0; +#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); + CFileMgr::Read(file, (char*)&nVersion, sizeof(nVersion)); + } + } + + if (bValid && nVersion >= 3) + { + ControlsManager.MakeControllerActionsBlank(); +#ifdef BIND_VEHICLE_FIREWEAPON + skipVehicleFireWeapon = nVersion < 4; + // 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 (NETWORK_TALK, 'T', KEYBOARD); + + SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsPADEND, KEYBOARD); + SetControllerKeyAssociatedWithAction (PED_LOOKBEHIND, rsCAPSLK, OPTIONAL_EXTRA); + + SetControllerKeyAssociatedWithAction (PED_DUCK, 'C', KEYBOARD); + + 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); + + SetControllerKeyAssociatedWithAction (PED_ANSWER_PHONE, rsTAB, KEYBOARD); + + 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, 'V', 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); + + SetMouseButtonAssociatedWithAction(PED_SNIPER_ZOOM_IN, 4); + + SetMouseButtonAssociatedWithAction(PED_SNIPER_ZOOM_OUT, 5); + } +} + +#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); + SetControllerKeyAssociatedWithAction(PED_DUCK, 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_ANSWER_PHONE, 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); + SetControllerKeyAssociatedWithAction(PED_DUCK, 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_ANSWER_PHONE, 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_LOCK_TARGET); // duplicate + SETACTIONNAME(PED_CENTER_CAMERA_BEHIND_PLAYER); + SETACTIONNAME(VEHICLE_LOOKBEHIND); + SETACTIONNAME(PED_DUCK); + SETACTIONNAME(PED_ANSWER_PHONE); + 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(VEHICLE_TURRETLEFT); + SETACTIONNAME(VEHICLE_TURRETRIGHT); + SETACTIONNAME(VEHICLE_TURRETUP); + SETACTIONNAME(VEHICLE_TURRETDOWN); + 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; + case 12: +#ifndef REGISTER_START_BUTTON + if (padnumber == 1) +#endif + pad->PCTempJoyState.Start = 255; + break; + 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_CAMERA + || 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 && !FindPlayerVehicle()->IsRealHeli()) +#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; + if (button == GetControllerKeyAssociatedWithAction(PED_DUCK, type)) + state.LeftShock = 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_ANSWER_PHONE, type)) + state.LeftShoulder1 = 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 (button == GetControllerKeyAssociatedWithAction(PED_DUCK, type)) + state.LeftShock = 255; + + if (FrontEndMenuManager.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 (FrontEndMenuManager.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)) + { +#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(CAMERA_CHANGE_VIEW_ALL_SITUATIONS); + 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_CYCLE_WEAPON_LEFT); + CLEAR_ACTION_IF_NEEDED(PED_CYCLE_WEAPON_RIGHT); + CLEAR_ACTION_IF_NEEDED(PED_JUMPING); + CLEAR_ACTION_IF_NEEDED(PED_SPRINT); + CLEAR_ACTION_IF_NEEDED(PED_LOOKBEHIND); + CLEAR_ACTION_IF_NEEDED(PED_DUCK); + CLEAR_ACTION_IF_NEEDED(PED_ANSWER_PHONE); + + if (FrontEndMenuManager.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 (FrontEndMenuManager.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_ACCELERATE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_BRAKE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_CHANGE_RADIO_STATION); + CLEAR_ACTION_IF_NEEDED(VEHICLE_HORN); + CLEAR_ACTION_IF_NEEDED(TOGGLE_SUBMISSIONS); + CLEAR_ACTION_IF_NEEDED(VEHICLE_HANDBRAKE); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKLEFT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKRIGHT); + CLEAR_ACTION_IF_NEEDED(VEHICLE_LOOKBEHIND); + 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_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: + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + break; + case ACTIONTYPE_VEHICLE: + DeleteMatchingCommonControls (action, key, type); + DeleteMatchingVehicleControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + break; + case ACTIONTYPE_VEHICLE_3RDPERSON: + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingVehicleControls (action, key, type); + break; + case ACTIONTYPE_COMMON: + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rstPersonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingVehicleControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(action, key, type); + break; + case ACTIONTYPE_1RST3RDPERSON: + DeleteMatchingCommonControls (action, key, type); + DeleteMatching1rstPersonControls (action, key, type); + DeleteMatching1rst3rdPersonControls (action, key, type); + DeleteMatching3rdPersonControls (action, key, type); + DeleteMatchingVehicle_3rdPersonControls(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) + { +#ifndef BIND_VEHICLE_FIREWEAPON + case PED_FIREWEAPON: +#endif + case GO_LEFT: + case GO_RIGHT: + case CAMERA_CHANGE_VIEW_ALL_SITUATIONS: + case NETWORK_TALK: + case TOGGLE_DPAD: + case SWITCH_DEBUG_CAM_ON: + case TAKE_SCREEN_SHOT: + case SHOW_MOUSE_POINTER_TOGGLE: + return ACTIONTYPE_COMMON; + break; + + case PED_CYCLE_WEAPON_RIGHT: + case PED_CYCLE_WEAPON_LEFT: + case PED_JUMPING: + case PED_SPRINT: + case PED_LOOKBEHIND: + case PED_DUCK: + case PED_ANSWER_PHONE: + 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_ACCELERATE: + case VEHICLE_BRAKE: + case VEHICLE_CHANGE_RADIO_STATION: + case VEHICLE_HORN: + case TOGGLE_SUBMISSIONS: + case VEHICLE_HANDBRAKE: + case VEHICLE_LOOKLEFT: + case VEHICLE_LOOKRIGHT: + case VEHICLE_LOOKBEHIND: + 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 GO_FORWARD: + case GO_BACK: + case PED_1RST_PERSON_LOOK_LEFT: + case PED_1RST_PERSON_LOOK_RIGHT: + case PED_LOCK_TARGET: + case PED_1RST_PERSON_LOOK_UP: + case PED_1RST_PERSON_LOOK_DOWN: + 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, RSU, RSD, RSL, RSR) \ + {{ \ + O, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + LEFT, /* GO_LEFT */ \ + RIGHT, /* 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 */ \ + L3, /* PED_DUCK */ \ + L1, /* PED_ANSWER_PHONE */ \ + 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 */ \ + RSL, /* VEHICLE_TURRETLEFT */ \ + RSR, /* VEHICLE_TURRETRIGHT */ \ + UP, /* VEHICLE_TURRETUP */ \ + DOWN, /* 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, /* UNKNOWN_ACTION */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + O, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + LEFT, /* GO_LEFT */ \ + RIGHT, /* 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 */ \ + L3, /* PED_DUCK */ \ + L1, /* PED_ANSWER_PHONE */ \ + 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 */ \ + RSL, /* VEHICLE_TURRETLEFT */ \ + RSR, /* VEHICLE_TURRETRIGHT */ \ + UP, /* VEHICLE_TURRETUP */ \ + DOWN, /* 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, /* UNKNOWN_ACTION */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + X, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + LEFT, /* GO_LEFT */ \ + RIGHT, /* 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 */ \ + L3, /* PED_DUCK */ \ + T, /* PED_ANSWER_PHONE */ \ + 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 */ \ + RSL, /* VEHICLE_TURRETLEFT */ \ + RSR, /* VEHICLE_TURRETRIGHT */ \ + UP, /* VEHICLE_TURRETUP */ \ + DOWN, /* 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, /* UNKNOWN_ACTION */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }, \ + { \ + R1, /* PED_FIREWEAPON */ \ + R2, /* PED_CYCLE_WEAPON_RIGHT */ \ + L2, /* PED_CYCLE_WEAPON_LEFT */ \ + nil, /* GO_FORWARD */ \ + nil, /* GO_BACK */ \ + LEFT, /* GO_LEFT */ \ + RIGHT, /* 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 */ \ + L3, /* PED_DUCK */ \ + O, /* PED_ANSWER_PHONE */ \ + VFB(R1) /* VEHICLE_FIREWEAPON */ \ + RSU, /* VEHICLE_ACCELERATE */ \ + RSD, /* 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 */ \ + RSL, /* VEHICLE_TURRETLEFT */ \ + RSR, /* VEHICLE_TURRETRIGHT */ \ + UP, /* VEHICLE_TURRETUP */ \ + DOWN, /* 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, /* UNKNOWN_ACTION */ \ + nil, /* SHOW_MOUSE_POINTER_TOGGLE */ \ + }} + +#ifdef BUTTON_ICONS +#define UP "~U~" +#define DOWN "~D~" +#define LEFT "~<~" +#define RIGHT "~>~" +#else +#define UP "UP" +#define DOWN "DOWN" +#define LEFT "LEFT" +#define RIGHT "RIGHT" +#endif + +const char *XboxButtons_noIcons[][MAX_CONTROLLERACTIONS] = CONTROLLER_BUTTONS("Y", "B", "A", "X", "LB", "LT", "LS", "RB", "RT", "RS", "BACK", "right stick up", "right stick down", "right stick left", "right stick right"); + +#ifdef BUTTON_ICONS +const char *XboxButtons[][MAX_CONTROLLERACTIONS] = CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "BACK", "~H~", "~L~", "~(~", "~)~"); +#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", "right stick up", "right stick down", "right stick left", "right stick right"); + +#ifdef BUTTON_ICONS +const char *PlayStationButtons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "SELECT", "~H~", "~L~", "~(~", "~)~"); +#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", "right stick up", "right stick down", "right stick left", "right stick right"); + +#ifdef BUTTON_ICONS +const char *NintendoSwitchButtons[][MAX_CONTROLLERACTIONS] = + CONTROLLER_BUTTONS("~T~", "~O~", "~X~", "~Q~", "~K~", "~M~", "~A~", "~J~", "~V~", "~C~", "BACK", "~H~", "~L~", "~(~", "~)~"); +#endif + +#undef UP +#undef DOWN +#undef LEFT +#undef RIGHT + +#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/miami/core/ControllerConfig.h b/src/miami/core/ControllerConfig.h new file mode 100644 index 00000000..e150c1bd --- /dev/null +++ b/src/miami/core/ControllerConfig.h @@ -0,0 +1,229 @@ +#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, + PED_DUCK, + PED_ANSWER_PHONE, +#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, + UNKNOWN_ACTION, + 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/miami/core/Crime.h b/src/miami/core/Crime.h new file mode 100644 index 00000000..4c7ea315 --- /dev/null +++ b/src/miami/core/Crime.h @@ -0,0 +1,39 @@ +#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, + CRIME_EXPLOSION, + CRIME_HIT_PED_NASTYWEAPON, + CRIME_HIT_COP_NASTYWEAPON, + 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/miami/core/Debug.cpp b/src/miami/core/Debug.cpp new file mode 100644 index 00000000..e319388c --- /dev/null +++ b/src/miami/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_STANDARD); +#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_STANDARD); + 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_STANDARD); + + 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/miami/core/Debug.h b/src/miami/core/Debug.h new file mode 100644 index 00000000..4a25bf41 --- /dev/null +++ b/src/miami/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/miami/core/Directory.cpp b/src/miami/core/Directory.cpp new file mode 100644 index 00000000..05344065 --- /dev/null +++ b/src/miami/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/miami/core/Directory.h b/src/miami/core/Directory.h new file mode 100644 index 00000000..0fef080f --- /dev/null +++ b/src/miami/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/miami/core/EventList.cpp b/src/miami/core/EventList.cpp new file mode 100644 index 00000000..b7f139b4 --- /dev/null +++ b/src/miami/core/EventList.cpp @@ -0,0 +1,251 @@ +#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); + 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_EXPLOSION: crime = CRIME_EXPLOSION; 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; + case EVENT_ASSAULT_NASTYWEAPON: crime = CRIME_HIT_PED_NASTYWEAPON; break; + case EVENT_ASSAULT_NASTYWEAPON_POLICE: crime = CRIME_HIT_COP_NASTYWEAPON; break; + default: crime = CRIME_NONE; break; + } + + if (crime == CRIME_HIT_PED && IsPedPointerValid((CPed*)crimeId) && FindPlayerPed()->m_pWanted->GetWantedLevel() == 0 && ((CPed*)crimeId)->bBeingChasedByPolice) { + if (!((CPed*)crimeId)->DyingOrDead()) { + CMessages::AddBigMessage(TheText.Get("GOODBOY"), 5000, 0); + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 50; + } + return; + } + + if(crime == CRIME_NONE) + return; + +#ifdef FIX_BUGS + CVector playerPedCoors = FindPlayerCoors(); +#else + CVector playerPedCoors = FindPlayerPed()->GetPosition(); +#endif + CVector playerCoors = FindPlayerCoors(); + + if(CWanted::WorkOutPolicePresence(playerCoors, 14.0f) != 0 || + CGame::germanGame && (crime == CRIME_SHOOT_PED || crime == CRIME_SHOOT_COP || crime == CRIME_COP_BURNED || crime == CRIME_VEHICLE_BURNED)){ + FindPlayerPed()->m_pWanted->RegisterCrime_Immediately(crime, playerPedCoors, (uint32)crimeId, copsDontCare); + FindPlayerPed()->m_pWanted->SetWantedLevelNoDrop(1); + }else + FindPlayerPed()->m_pWanted->RegisterCrime(crime, playerPedCoors, (uint32)crimeId, copsDontCare); + + if(type == EVENT_ASSAULT_POLICE) + FindPlayerPed()->SetWantedLevelNoDrop(1); + if(type == EVENT_SHOOT_COP || type == EVENT_ASSAULT_NASTYWEAPON_POLICE) + FindPlayerPed()->SetWantedLevelNoDrop(2); + +} diff --git a/src/miami/core/EventList.h b/src/miami/core/EventList.h new file mode 100644 index 00000000..3e9d8fd4 --- /dev/null +++ b/src/miami/core/EventList.h @@ -0,0 +1,68 @@ +#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, + EVENT_ASSAULT_NASTYWEAPON_POLICE, + EVENT_UNK, // Not on SA it seems + EVENT_ICECREAM, + EVENT_ATM, + EVENT_SHOPSTALL, + EVENT_SHOPWINDOW, + 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/miami/core/FileLoader.cpp b/src/miami/core/FileLoader.cpp new file mode 100644 index 00000000..0a963a3b --- /dev/null +++ b/src/miami/core/FileLoader.cpp @@ -0,0 +1,1769 @@ +#include "common.h" +#include +#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" +#include "Streaming.h" +#include "ColStore.h" +#include "Occlusion.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; + bool objectsLoaded; + char *line; + char txdname[64]; + + savedTxd = RwTexDictionaryGetCurrent(); + objectsLoaded = false; + if(savedTxd == nil){ + savedTxd = RwTexDictionaryCreate(); + RwTexDictionarySetCurrent(savedTxd); + } + fd = CFileMgr::OpenFile(filename, "r"); + assert(fd > 0); + + for(line = LoadLine(fd); line; line = LoadLine(fd)){ + if(*line == '#') + continue; + + if(strncmp(line, "EXIT", 4) == 0) + 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){ + LoadingScreenLoadingFile(line+10); + LoadCollisionFile(line+10, 0); + }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){ + LoadingScreenLoadingFile("Collision"); + PUSH_MEMID(MEMID_WORLD); + CObjectData::Initialise("DATA\\OBJECT.DAT"); + CStreaming::Init(); + POP_MEMID(); + PUSH_MEMID(MEMID_COLLISION); + CColStore::LoadAllCollision(); + POP_MEMID(); + for(int i = 0; i < MODELINFOSIZE; i++) + if(CModelInfo::GetModelInfo(i)) + CModelInfo::GetModelInfo(i)->ConvertAnimFileIndex(); + objectsLoaded = true; + } + PUSH_MEMID(MEMID_WORLD); + LoadingScreenLoadingFile(line + 4); + LoadScene(line + 4); + POP_MEMID(); + }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); + + int i; + for(i = 1; i < COLSTORESIZE; i++) + if(CColStore::GetSlot(i)) + CColStore::GetBoundingBox(i).Grow(120.0f); + CWorld::RepositionCertainDynamicObjects(); + CColStore::RemoveAllCollision(); +} + +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, uint8 colSlot) +{ + 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"); + assert(fd > 0); + + while(CFileMgr::Read(fd, (char*)&header, sizeof(header))){ + assert(header.ident == 'CLOC'); + CFileMgr::Read(fd, (char*)work_buff, header.size); + memcpy(modelname, work_buff, 24); + + mi = CModelInfo::GetModelInfo(modelname, nil); + if(mi){ + if(mi->GetColModel() && mi->DoesOwnColModel()){ + LoadCollisionModel(work_buff+24, *mi->GetColModel(), modelname); + }else{ + CColModel *model = new CColModel; + model->level = colSlot; + 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(); +} + + +bool +CFileLoader::LoadCollisionFileFirstTime(uint8 *buffer, uint32 size, uint8 colSlot) +{ + uint32 modelsize; + char modelname[24]; + CBaseModelInfo *mi; + ColHeader *header; + int32 modelIndex; + + while(size > 8){ + header = (ColHeader*)buffer; + modelsize = header->size; + if(header->ident != 'CLOC') + return size-8 < CDSTREAM_SECTOR_SIZE; + memcpy(modelname, buffer+8, 24); + memcpy(work_buff, buffer+32, modelsize-24); + size -= 32 + (modelsize-24); + buffer += 32 + (modelsize-24); + if(modelsize > 15*1024) + debug("colmodel %s is huge, size %d\n", modelname, modelsize); + + mi = CModelInfo::GetModelInfo(modelname, &modelIndex); + if(mi){ + CColStore::IncludeModelIndex(colSlot, modelIndex); + CColModel *model = new CColModel; + model->level = colSlot; + LoadCollisionModel(work_buff, *model, modelname); + mi->SetColModel(model, true); + }else{ + debug("colmodel %s can't find a modelinfo\n", modelname); + } + } + return true; +} + +bool +CFileLoader::LoadCollisionFile(uint8 *buffer, uint32 size, uint8 colSlot) +{ + uint32 modelsize; + char modelname[24]; + CBaseModelInfo *mi; + ColHeader *header; + + while(size > 8){ + header = (ColHeader*)buffer; + modelsize = header->size; + if(header->ident != 'CLOC') + return size-8 < CDSTREAM_SECTOR_SIZE; + memcpy(modelname, buffer+8, 24); + memcpy(work_buff, buffer+32, modelsize-24); + size -= 32 + (modelsize-24); + buffer += 32 + (modelsize-24); + if(modelsize > 15*1024) + debug("colmodel %s is huge, size %d\n", modelname, modelsize); + + mi = CModelInfo::GetModelInfo(modelname, CColStore::GetSlot(colSlot)->minIndex, CColStore::GetSlot(colSlot)->maxIndex); + if(mi){ + if(mi->GetColModel()){ + LoadCollisionModel(work_buff, *mi->GetColModel(), modelname); + }else{ + CColModel *model = new CColModel; + model->level = colSlot; + LoadCollisionModel(work_buff, *model, modelname); + mi->SetColModel(model, true); + } + }else{ + debug("colmodel %s can't find a modelinfo\n", modelname); + } + } + return true; +} + +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.numLines = 0; + 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].SetFixed(*(int16*)buf, *(int16*)(buf+2), *(int16*)(buf+4)); +#if 0 + 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); +#endif + buf += 6; + } + }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(*(uint16*)buf, *(uint16*)(buf+2), *(uint16*)(buf+4), buf[6]); + buf += 8; + assert(model.triangles[i].a < numVertices); + assert(model.triangles[i].b < numVertices); + assert(model.triangles[i].c < numVertices); + } + }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') && isdigit(s[2])) + 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()); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + mi->SetAtomic(n, atomic); + RpClumpRemoveAtomic(clump, atomic); + RpAtomicSetFrame(atomic, RwFrameCreate()); + CVisibilityPlugins::SetAtomicModelInfo(atomic, mi); + }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; + InitClump(clump); + mi = (CClumpModelInfo*)CModelInfo::GetModelInfo(id); + mi->SetClump(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; +} + +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); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + gpRelatedModelInfo->SetAtomic(n, atomic); + RpClumpRemoveAtomic(clump, atomic); + RpAtomicSetFrame(atomic, RwFrameCreate()); + CVisibilityPlugins::SetAtomicModelInfo(atomic, gpRelatedModelInfo); + 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, // unused but enum still has it + TOBJ, + WEAP, + HIER, + CARS, + PEDS, + PATH, + TWODFX + }; + char *line; + int fd; + int section; + int pathIndex; + int id, pathType; + int minID, maxID; + + section = NONE; + minID = INT32_MAX; + maxID = -1; + pathIndex = -1; + debug("Loading object types from %s...\n", filename); + + fd = CFileMgr::OpenFile(filename, "rb"); + assert(fd > 0); + 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, 'w','e','a','p')) section = WEAP; + 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 = NONE; + }else switch(section){ + case OBJS: + id = LoadObject(line); + if(id > maxID) maxID = id; + if(id < minID) minID = id; + break; + case TOBJ: + id = LoadTimeObject(line); + if(id > maxID) maxID = id; + if(id < minID) minID = id; + break; + case WEAP: + LoadWeaponObject(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, pathType); + pathIndex = 0; + }else{ + if(pathType == 0) + LoadPedPathNode(line, id, pathIndex); + else if (pathType == 1) + LoadCarPathNode(line, id, pathIndex, false); + else if (pathType == 2) + LoadCarPathNode(line, id, pathIndex, true); + pathIndex++; + if(pathIndex == 12) + pathIndex = -1; + } + break; + case TWODFX: + Load2dEffect(line); + break; + } + } + CFileMgr::CloseFile(fd); + + for(id = minID; id <= maxID; id++){ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + if(mi && mi->IsBuilding()) + mi->SetupBigBuilding(minID, maxID); + } +} + +#ifdef RW_DC +// PH3NOM - I just searched the extracted img folder for the following DFF names: +//find . -type f -name "*tree*.dff" ! -name "*shad*" +//find . -type f -name "*palm*.dff" ! -name "*shad*" +//find . -type f -name "*fence*.dff" ! -name "*shad*" +//find . -type f -name "*bush*.dff" ! -name "*shad*" +//find . -type f -name "*rail*.dff" ! -name "*shad*" +//find . -type f -name "*veg*.dff" ! -name "*shad*" +char *AlphaTestNames[] = { + "xpolytrees2_dt", + "gf_treesfw3_01", + "ci_trees05", + "xpolytrees3_dt", + "LODrtrees5", + "LODrtrees2", + "nbt_mansiontrees04", + "malltreereflect2", + "veg_treeb1", + "LODrtrees4", + "Streetlamp2", + "nbt_mansiontrees02", + "LODmrgtrees4", + "gf_tree2_02", + "malltreereflect", + "nbt_hoteltrees02", + "veg_treea1", + "Streetlamp1", + "gf_treesfw5_01", + "nbt_mansiontrees05", + "malltrees01", + "xpolytrees4_dt", + "nbt_mansiontrees03", + "xpolytrees1_dt", + "hoteltrees01", + "hoteltrees03", + "gf_treesfw1_01", + "veg_tree3", + "ap_treeshot1_02", + "hoteltrees02", + "ci_trees03", + "veg_treea3", + "mc_treesfw3_01", + "ap_treesfw1_01", + "nbt_hoteltrees01", + "ci_trees04", + "gf_treesfw2_01", + "LODrtrees3", + "ci_trees1", + "mc_treesfw1_01", + "LODrtrees1", + "gf_treesfw4_01", + "nbt_mansiontrees01", + "hoteltrees06", + "mn_treesis_int", + "hoteltrees05", + "mc_treesfw2_01", + "gf_tree1_01", + "mn_treesisl05", + "doublestreetlght1", + "ci_trees02", + "bank_palms09", + "veg_palmkb3", + "lw_palm1", + "veg_palmkb5", + "bank_palms21", + "veg_palmkb1", + "veg_palmbig14", + "veg_palmkb14", + "veg_palm02", + "veg_palmkb2", + "veg_palmkb9", + "bank_palms28", + "veg_palmkb7", + "bank_palms23", + "bank_palms32", + "veg_palmkb8", + "bank_palms02", + "bank_palms33", + "veg_palmkbb11", + "veg_palmkb4", + "veg_palm01", + "veg_palm03", + "veg_palmkb13", + "veg_palmkb10", + "veg_palm04", + "gf_tennisfence", + "marinafence04", + "marinafence01", + "b_hse_pierfence", + "ap_wallfence5", + "dockfence", + "dtn_hospital_fence", + "mc_fence1", + "od_clubfence_dy", + "ap_wallfence1", + "const_woodfence30", + "bb_fence1", + "ap_seafences", + "marinafence03", + "nbthotel08fence02", + "dt_sheraton_fence", + "dt_scabby_fence04", + "nbt_barfence01", + "ap_wallfence2", + "od_clevelfence", + "mc_fence2", + "ci_fence2", + "dt_scabby_fence03", + "mansionfence", + "nbt_hotel07fence", + "marinafence02", + "fence01", + "compound_fence", + "ap_wallfence7", + "fencehaitism", + "od_clubfence_nt", + "marinafence05", + "ap_wallfence6", + "Mansion2_C_fence", + "dt_scabby_fence07", + "kickfence", + "pw_backfence1", + "mc_fence3", + "washfence1", + "LODst_woodfence30", + "washfence2", + "carparkfence0", + "ap_wallfence3", + "fencesmallb", + "ap_wallfence4", + "fencehaiti", + "stationfence", + "dt_scabby_fence01", + "pw_bayfence2_01", + "lha_carfence", + "tall_fence", + "ap_seaplanfence1", + "LODse_pierfence", + "marinaveg3", + "ml_vegbits05", + "svegrgedoor", + "ml_vegbits04", + "nrth3veg35", + "nrth3veg05", + "ht_veg01_nt", + "nbeachvegy1", + "odnvegbush1", + "washvegy4", + "ht_veg02_nt", + "wshotelveg1", + "dtn_veg3", + "nrth7veg09", + "washvegy238", + "veg_palwee02", + "nrth3veg50", + "washpshoutveg", + "nrth4veg08", + "washvegy3", + "washvegy241", + "washvegy2", + "washvegy239", + "odnvegbush2b", + "veg_gaz", + "nrth1veg21", + "ht_veg04_dy", + "ml_vegbits03", + "nbeachvegy2", + "nrth3veg16", + "ht_veg02_dy", + "nrth4veg05", + "ht_veg04_nt", + "veg_palwee01", + "ht_veg01_dy", + "vegeha1", + "veged", + "veg_ivy_balcny_kb3", + "nrth1veg42", + "marinaveg1", + "nrth3veg25", + "veg_fern_balcny_kb1", + "spad_veg1", + "washvegy2413", + "marinaveg2", + "washvegy240", + "vegetationb", + "nrth1veg37", + "ml_vegbits01", + "veged01", + "dtn_veg4", + "washvegy242", + "veg_palwee03", + "nrth3veg59", + "nrth4veg212", + "nrth4veg21", + "nrth7veg", + "washvegy237", + "nrth3veg08", + "nrth4veg09", + "veg_fern_balcny_kb2", + "ml_vegbits02", + "odnvegbush2", + "vegetationb03", + "veged02", + "kb_planterbox", + "plants05b", + "nbdecoshplants", + "ap_planters2_01", + "ml_planterbed", + "marinaplanter1", + "washskyplant1", + "dzplant", + "ci_planter2", + "ap_planters1_01", + "gf_planters4", + "washskyplant2", + "kb_planter+bush", + "marinaplanter2", + "ci_planter1", + "plants04", + "gf_planters2", + "kb_planterbush2", + "plants01", + "kb_planter+bush2", + "plants05", + "ci_busht_04", + "starsbush2", + "mansbushes2", + "odnvegbush1", + "odrv_bushes", + "ci_busht_02", + "mlmallbush", + "mansbushes", + "nbw_bush02", + "ci_busht_08", + "nbw_bush01", + "odnvegbush2b", + "ap_carbush2_01", + "new_bushtest42", + "new_bushsm", + "mallbushs", + "odrv_bushes01", + "kb_planter+bush", + "ci_busht_06", + "beach_bush08s", + "beach_bush06s", + "new_bushtest", + "doontoon66_bushes01", + "beach_bush04", + "starbitbush", + "beach_bush02", + "lhavnew_bush", + "kb_planterbush2", + "ci_busht_11", + "odnvegbush2", + "kb_planter+bush2", + "mallbushdense", + "dk_rail06", + "cl_railing", + "docks10rail", + "cl_railingb", + "dk_rail05", + "shpfrnts03rail01", + "dk_rail02", + "nbeachbit03rails", + "ci_mans1rail", + "dk_rail07", + "dk_rail01", + "ci_mans2rail1", + "ci_mans1rail1", + "dk_rail04", + "shpfrnts03rail02", + "dk_rail03", + "", +}; + +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 + +void +SetModelInfoFlags(CSimpleModelInfo *mi, uint32 flags) +{ + mi->m_wetRoadReflection = !!(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); + mi->m_noShadows = !!(flags & 0x80); + mi->m_ignoreDrawDist = !!(flags & 0x100); + mi->m_isCodeGlass = !!(flags & 0x200); + mi->m_isArtistGlass = !!(flags & 0x400); + +#ifdef RW_DC + if(MatchModelName(mi->GetModelName(), AlphaTestNames)) mi->m_isAlphaTest = true; +#endif +} + +int +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 0; // game returns return value + + 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); + + return id; +} + +int +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 0; // game returns return value + + 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); + + return id; +} + +int +CFileLoader::LoadWeaponObject(const char *line) +{ + int id, numObjs; + char model[24], txd[24], animFile[16]; + float dist; + CWeaponModelInfo *mi; + + sscanf(line, "%d %s %s %s %d %f", &id, model, txd, animFile, &numObjs, &dist); + + mi = CModelInfo::AddWeaponModel(id); + mi->SetModelName(model); + mi->SetNumAtomics(1); + mi->m_lodDistances[0] = dist; + mi->SetTexDictionary(txd); + mi->SetAnimFile(animFile); + mi->SetColModel(&CTempColModels::ms_colModelWeapon); + MatchModelString(model, id); + return 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], animFile[16], vehclass[12]; + uint32 frequency, comprules; + int32 level, misc; + float wheelScale; + CVehicleModelInfo *mi; + char *p; + + sscanf(line, "%d %s %s %s %s %s %s %s %d %d %x %d %f", + &id, model, txd, + type, handlingId, gamename, animFile, vehclass, + &frequency, &level, &comprules, &misc, &wheelScale); + + mi = CModelInfo::AddVehicleModel(id); + mi->SetModelName(model); + mi->SetTexDictionary(txd); + mi->SetAnimFile(animFile); + 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); + + if(strcmp(vehclass, "normal") == 0) + mi->m_vehicleClass = CCarCtrl::NORMAL; + else if(strcmp(vehclass, "poorfamily") == 0) + mi->m_vehicleClass = CCarCtrl::POOR; + else if(strcmp(vehclass, "richfamily") == 0) + mi->m_vehicleClass = CCarCtrl::RICH; + else if(strcmp(vehclass, "executive") == 0) + mi->m_vehicleClass = CCarCtrl::EXEC; + else if(strcmp(vehclass, "worker") == 0) + mi->m_vehicleClass = CCarCtrl::WORKER; + else if(strcmp(vehclass, "big") == 0) + mi->m_vehicleClass = CCarCtrl::BIG; + else if(strcmp(vehclass, "taxi") == 0) + mi->m_vehicleClass = CCarCtrl::TAXI; + else if(strcmp(vehclass, "moped") == 0) + mi->m_vehicleClass = CCarCtrl::MOPED; + else if(strcmp(vehclass, "motorbike") == 0) + mi->m_vehicleClass = CCarCtrl::MOTORBIKE; + else if(strcmp(vehclass, "leisureboat") == 0) + mi->m_vehicleClass = CCarCtrl::LEISUREBOAT; + else if(strcmp(vehclass, "workerboat") == 0) + mi->m_vehicleClass = CCarCtrl::WORKERBOAT; + else if(strcmp(vehclass, "ignore") == 0) { + mi->m_vehicleClass = -1; + return; + } + CCarCtrl::AddToCarArray(id, mi->m_vehicleClass); + mi->m_frequency = frequency; +} + +void +CFileLoader::LoadPedObject(const char *line) +{ + int id; + char model[24], txd[24]; + char pedType[24], pedStats[24], animGroup[24], animFile[16]; + int carsCanDrive; + CPedModelInfo *mi; + int animGroupId; + int radio1, radio2; + + sscanf(line, "%d %s %s %s %s %s %x %s %d %d", + &id, model, txd, + pedType, pedStats, animGroup, &carsCanDrive, + animFile, &radio1, &radio2); + + mi = CModelInfo::AddPedModel(id); + mi->SetModelName(model); + mi->SetTexDictionary(txd); + mi->SetAnimFile(animFile); + 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; + assert(animGroupId < NUM_ANIM_ASSOC_GROUPS); + mi->m_animGroup = animGroupId; + mi->m_carsCanDrive = carsCanDrive; + mi->radio1 = radio1; + mi->radio2 = radio2; +} + +int +CFileLoader::LoadPathHeader(const char *line, int &type) +{ + int id; + char modelname[32]; + + sscanf(line, "%d %d %s", &type, &id, modelname); + return id; +} + +void +CFileLoader::LoadPedPathNode(const char *line, int id, int node) +{ + int type, next, cross, numLeft, numRight, speed, flags; + float x, y, z, width, spawnRate; + + if(sscanf(line, "%d %d %d %f %f %f %f %d %d %d %d %f", + &type, &next, &cross, &x, &y, &z, &width, &numLeft, &numRight, + &speed, &flags, &spawnRate) != 12) + spawnRate = 1.0f; + + if(id == -1) + ThePaths.StoreDetachedNodeInfoPed(node, type, next, x, y, z, + width, !!cross, !!(flags&1), !!(flags&4), spawnRate*15.0f); + else + ThePaths.StoreNodeInfoPed(id, node, type, next, x, y, z, + width, !!cross, spawnRate*15.0f); +} + +void +CFileLoader::LoadCarPathNode(const char *line, int id, int node, bool waterPath) +{ + int type, next, cross, numLeft, numRight, speed, flags; + float x, y, z, width, spawnRate; + + if(sscanf(line, "%d %d %d %f %f %f %f %d %d %d %d %f", + &type, &next, &cross, &x, &y, &z, &width, &numLeft, &numRight, + &speed, &flags, &spawnRate) != 12) + spawnRate = 1.0f; + + if(id == -1) + ThePaths.StoreDetachedNodeInfoCar(node, type, next, x, y, z, width, numLeft, numRight, + !!(flags&1), !!(flags&4), speed, !!(flags&2), waterPath, spawnRate * 15, false); + else + ThePaths.StoreNodeInfoCar(id, node, type, next, x, y, z, 0, numLeft, numRight, + !!(flags&1), !!(flags&4), speed, !!(flags&2), waterPath, spawnRate * 15); +} + + +void +CFileLoader::Load2dEffect(const char *line) +{ + int id, r, g, b, a, type, ptype; + 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; + case EFFECT_PED_ATTRACTOR: + sscanf(line, "%d %f %f %f %d %d %d %d %d %d %f %f %f %f %f %f", + &id, &x, &y, &z, &r, &g, &b, &a, &type, + &ptype, + &effect->pedattr.queueDir.x, + &effect->pedattr.queueDir.y, + &effect->pedattr.queueDir.z, + &effect->pedattr.useDir.x, + &effect->pedattr.useDir.y, + &effect->pedattr.useDir.z); + effect->pedattr.type = ptype; + break; + } + + CTxdStore::PopCurrentTxd(); +} + +void +CFileLoader::LoadScene(const char *filename) +{ + enum { + NONE, + INST, + ZONE, + CULL, + OCCL, + PICK, + PATH, + }; + char *line; + int fd; + int section; + int pathType, pathIndex; + + section = NONE; + pathIndex = -1; + debug("Creating objects from %s...\n", filename); + + fd = CFileMgr::OpenFile(filename, "rb"); + assert(fd > 0); + 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(isLine4(line, 'o','c','c','l')) section = OCCL; + }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 OCCL: + LoadOcclusionVolume(line); + break; + case PICK: + // unused + LoadPickup(line); + break; + case PATH: + if(pathIndex == -1){ + LoadPathHeader(line, pathType); + pathIndex = 0; + }else{ + if(pathType == 0) + LoadPedPathNode(line, -1, pathIndex); + else if (pathType == 1) + LoadCarPathNode(line, -1, pathIndex, false); + else if (pathType == 2) + LoadCarPathNode(line, -1, pathIndex, true); + 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; + float area; + + if(sscanf(line, "%d %s %f %f %f %f %f %f %f %f %f %f %f", + &id, name, &area, + &trans.x, &trans.y, &trans.z, + &scale.x, &scale.y, &scale.z, + &axis.x, &axis.y, &axis.z, &angle) != 13){ + 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; + area = 0; + } + + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + if(mi == nil) + return; + assert(mi->IsSimple()); + + if(!CStreaming::IsObjectInCdImage(id)) + debug("Not in cdimage %s\n", mi->GetModelName()); + + angle = -RADTODEG(2.0f * Acos(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()); + entity->m_area = area; + if(mi->IsBuilding()){ + if(mi->m_isBigBuilding) + entity->SetupBigBuilding(); + if(mi->m_isSubway) + entity->bIsSubway = true; + } + if(mi->GetLargestLodDistance() < 2.0f) + entity->bIsVisible = false; + CWorld::Add(entity); + + CColModel *col = entity->GetColModel(); + if(col->numSpheres || col->numBoxes || col->numTriangles){ + if(col->level != 0) + CColStore::GetBoundingBox(col->level).ContainRect(entity->GetBoundRect()); + }else + entity->bUsesCollision = false; + + if(entity->GetPosition().z + col->boundingBox.min.z < 6.0f) + entity->bUnderwater = true; + }else{ + entity = new CDummyObject; + entity->SetModelIndexNoCreate(id); + entity->GetMatrix() = CMatrix(xform); + CWorld::Add(entity); + if(IsGlass(entity->GetModelIndex()) && !mi->m_isArtistGlass) + entity->bIsVisible = false; + entity->m_level = CTheZones::GetLevelFromPosition(&entity->GetPosition()); + entity->m_area = area; + } + + 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::LoadOcclusionVolume(const char *line) +{ + float x, y, z; + float width, length, height; + float angle; + + sscanf(line, "%f %f %f %f %f %f %f", + &x, &y, &z, + &width, &length, &height, + &angle); + COcclusion::AddOne(x, y, z + height/2.0f, width, length, height, angle); +} + + +// unused +void +CFileLoader::ReloadPaths(const char *filename) +{ + enum { + NONE, + PATH, + }; + char *line; + int section = NONE; + int id, pathType, pathIndex = -1; + debug("Reloading paths from %s...\n", filename); + + int fd = CFileMgr::OpenFile(filename, "r"); + assert(fd > 0); + 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, pathType); + pathIndex = 0; + } else { + if(pathType == 0) + LoadPedPathNode(line, id, pathIndex); + else if (pathType == 1) + LoadCarPathNode(line, id, pathIndex, false); + else if (pathType == 2) + LoadCarPathNode(line, id, pathIndex, true); + 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"); + assert(fd > 0); + 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"); + assert(fd > 0); + CFileMgr::ChangeDir("\\"); + + for (line = CFileLoader::LoadLine(fd); line; line = CFileLoader::LoadLine(fd)) { + if (*line == '#') + continue; + + if (strncmp(line, "EXIT", 4) == 0) + break; + + if (strncmp(line, "IDE", 3) == 0) { + LoadObjectTypes(line + 4); + } + } + CFileMgr::CloseFile(fd); +} diff --git a/src/miami/core/FileLoader.h b/src/miami/core/FileLoader.h new file mode 100644 index 00000000..077e7bdd --- /dev/null +++ b/src/miami/core/FileLoader.h @@ -0,0 +1,48 @@ +#pragma once + +class CFileLoader +{ + static char ms_line[256]; +public: + static void LoadLevel(const char *filename); + static char *LoadLine(int fd); + static RwTexDictionary *LoadTexDictionary(const char *filename); + static void LoadCollisionFile(const char *filename, uint8 colSlot); + static bool LoadCollisionFileFirstTime(uint8 *buffer, uint32 size, uint8 colSlot); + static bool LoadCollisionFile(uint8 *buffer, uint32 size, uint8 colSlot); + 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 int LoadObject(const char *line); + static int LoadTimeObject(const char *line); + static int LoadWeaponObject(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, int &type); + static void LoadPedPathNode(const char *line, int id, int node); + static void LoadCarPathNode(const char *line, int id, int node, bool waterPath); + 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 LoadOcclusionVolume(const char *line); + + 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/miami/core/FileMgr.cpp b/src/miami/core/FileMgr.cpp new file mode 100644 index 00000000..32aa4041 --- /dev/null +++ b/src/miami/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/miami/core/FileMgr.h b/src/miami/core/FileMgr.h new file mode 100644 index 00000000..f70451b7 --- /dev/null +++ b/src/miami/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/miami/core/Fire.cpp b/src/miami/core/Fire.cpp new file mode 100644 index 00000000..57315b15 --- /dev/null +++ b/src/miami/core/Fire.cpp @@ -0,0 +1,500 @@ +#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" +#include "GameLogic.h" +#include "CarAI.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_nExtinguishTime = 0; + m_nStartTime = 0; + m_pEntity = nil; + m_pSource = nil; + m_fStrength = 0.8f; + m_fWaterExtinguishCountdown = 1.0f; + m_bExtinguishedWithWater = false; +} + +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; + + m_fWaterExtinguishCountdown = Min(1.0f, 0.002f * CTimer::GetTimeStep() + m_fWaterExtinguishCountdown); + + if (m_pEntity) { + m_vecPos = m_pEntity->GetPosition(); + + if (((CPed *)m_pEntity)->IsPed()) { + if (ped->m_pFire != this) { + Extinguish(); + return; + } +#if defined GTAVC_JP_PATCH && !defined FIX_BUGS + if (m_pEntity == CGameLogic::pShortCutTaxi && CGameLogic::ShortCutState == CGameLogic::SHORTCUT_TRANSITION) { + Extinguish(); + return; + } +#endif + 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; + } +#ifdef FIX_BUGS + if (m_pEntity == CGameLogic::pShortCutTaxi && CGameLogic::ShortCutState == CGameLogic::SHORTCUT_TRANSITION) { + Extinguish(); + return; + } +#endif + 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() + (m_fWaterExtinguishCountdown < 0.3f ? 400 : (m_fWaterExtinguishCountdown < 0.7f ? 200 : 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.0f, 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; + m_bExtinguishedWithWater = false; + + if (m_pEntity) { + if (m_pEntity->IsPed()) { + CPed *ped = (CPed*)m_pEntity; + if (ped->CanSetPedState()) { + if (ped->m_nPedState != PED_DRIVING && ped->m_nPedState != PED_FALL) { + if (ped->IsPlayer()) { + ped->SetIdle(); + } else { + ped->m_nLastPedState = PED_NONE; + ped->SetWanderPath(0); + ped->SetWaitState(WAITSTATE_FINISH_FLEE, 0); + } + } + } + ped->m_pFire = nil; + } else if (m_pEntity->IsVehicle()) { + ((CVehicle *)m_pEntity)->m_pCarFire = nil; + } + m_pEntity = nil; + } + } +} + +void +CFireManager::StartFire(CVector pos, float size, uint8 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; + fire->m_bExtinguishedWithWater = false; + } +} + +CFire * +CFireManager::StartFire(CEntity *entityOnFire, CEntity *fleeFrom, float strength, uint8 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->m_fleeTimer = CTimer::GetTimeInMilliseconds() + 10000; + 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 (CModelInfo::IsBikeModel(veh->GetModelIndex()) || CModelInfo::IsCarModel(veh->GetModelIndex())) + CCarAI::TellOccupantsToFleeCar(veh); + + if (fleeFrom) { + CEventList::RegisterEvent(EVENT_CAR_SET_ON_FIRE, EVENT_ENTITY_VEHICLE, + entityOnFire, (CPed *)fleeFrom, 10000); + } + } + } + + fire->m_bIsOngoing = true; + fire->m_bExtinguishedWithWater = false; + 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) +{ + 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; + 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(); + } + } +} + +bool +CFireManager::ExtinguishPointWithWater(CVector point, float range) +{ + int i; + for (i = 0; i < NUM_FIRES;) { + if (m_aFires[i].m_bIsOngoing && (point - m_aFires[i].m_vecPos).MagnitudeSqr() < sq(range)) { + break; + } + if (++i >= NUM_FIRES) + return false; + } + + CFire *fireToExtinguish = &m_aFires[i]; + fireToExtinguish->m_fWaterExtinguishCountdown -= 0.012f * CTimer::GetTimeStep(); + CVector steamPos = fireToExtinguish->m_vecPos + + CVector((CGeneral::GetRandomNumber() - 128) * 3.1f / 200.f, + (CGeneral::GetRandomNumber() - 128) * 3.1f / 200.f, + CGeneral::GetRandomNumber() / 200.f); + + CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, steamPos, CVector(0.f, 0.f, 0.2f), nil, 0.5f); + CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, steamPos, CVector(0.f, 0.f, 0.1f), nil, 0.8f); + fireToExtinguish->m_bExtinguishedWithWater = true; + if (fireToExtinguish->m_fWaterExtinguishCountdown < 0.0f ) + fireToExtinguish->Extinguish(); + + return true; +} + +int32 +CFireManager::StartScriptFire(const CVector &pos, CEntity *target, float strength, uint8 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; + fire->m_bExtinguishedWithWater = false; + + if (target) + target->RegisterReference(&fire->m_pEntity); + fire->m_pSource = nil; + fire->m_nNextTimeToAddFlames = 0; + fire->m_fStrength = strength; + fire->m_fWaterExtinguishCountdown = 1.0f; + + 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) { + RemoveScriptFire(i); + } + } +} + +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/miami/core/Fire.h b/src/miami/core/Fire.h new file mode 100644 index 00000000..8126f830 --- /dev/null +++ b/src/miami/core/Fire.h @@ -0,0 +1,53 @@ +#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; + uint32 m_nNextTimeToAddFlames; + float m_fStrength; + float m_fWaterExtinguishCountdown; + bool m_bExtinguishedWithWater; + + 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, uint8 propagation); + CFire *StartFire(CEntity *entityOnFire, CEntity *fleeFrom, float strength, uint8 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); + bool ExtinguishPointWithWater(CVector point, float range); + int32 StartScriptFire(const CVector &pos, CEntity *target, float strength, uint8 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/miami/core/FrontEndControls.cpp b/src/miami/core/FrontEndControls.cpp new file mode 100644 index 00000000..18f6b3b2 --- /dev/null +++ b/src/miami/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/miami/core/FrontEndControls.h b/src/miami/core/FrontEndControls.h new file mode 100644 index 00000000..68dab90b --- /dev/null +++ b/src/miami/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/miami/core/Frontend.cpp b/src/miami/core/Frontend.cpp new file mode 100644 index 00000000..8f9d8e0e --- /dev/null +++ b/src/miami/core/Frontend.cpp @@ -0,0 +1,6755 @@ +#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 "User.h" +#include "sampman.h" +#include "../skel/dc/dc.h" + +// Similar story to Hud.cpp: +// Game has colors inlined in code. +// For easier modification we collect them here: +const CRGBA LABEL_COLOR(255, 150, 225, 255); +const CRGBA SELECTIONBORDER_COLOR(25, 130, 70, 255); +const CRGBA MENUOPTION_COLOR = LABEL_COLOR; +const CRGBA SELECTEDMENUOPTION_COLOR = LABEL_COLOR; +const CRGBA HEADER_COLOR = LABEL_COLOR; +const CRGBA DARKMENUOPTION_COLOR(195, 90, 165, 255); +const CRGBA SLIDERON_COLOR(97, 194, 247, 255); +const CRGBA SLIDEROFF_COLOR(27, 89, 130, 255); +const CRGBA LIST_BACKGROUND_COLOR(49, 101, 148, 130); +const CRGBA LIST_OPTION_COLOR(155, 155, 155, 255); +const CRGBA RADIO_SELECTOR_COLOR = SLIDEROFF_COLOR; +const CRGBA INACTIVE_RADIO_COLOR(100, 100, 255, 100); +const CRGBA SCROLLBAR_COLOR = LABEL_COLOR; + +#define MAP_MIN_SIZE 162.f +#define MAP_SIZE_TO_ALLOW_X_MOVE 297.f + +#define MAX_VISIBLE_LIST_ROW 30 +#define SCROLLBAR_MAX_HEIGHT 263.0f // not in end result +#define SCROLLABLE_PAGES + +#define hasNativeList(screen) (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; \ + } \ + } + +#define MINUS_SCROLL_OFFSET - scrollOffset +#else +#define MAX_VISIBLE_OPTION_ON_SCREEN MAX_VISIBLE_LIST_ROW +#define SETUP_SCROLLING(screen) +#define MINUS_SCROLL_OFFSET +#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 MAP_ENHANCEMENTS +CVector2D mapCrosshair; +#endif + +#ifdef CUTSCENE_BORDERS_SWITCH +bool CMenuManager::m_PrefsCutsceneBorders = true; +#endif + +bool holdingScrollBar; // *(bool*)0x7039B9; // not original name + +CMenuManager FrontEndMenuManager; +MenuTrapezoid menuBg(CGeneral::GetRandomNumber() % 40 + 65, CGeneral::GetRandomNumber() % 40 + 21, + CGeneral::GetRandomNumber() % 40 + 568, CGeneral::GetRandomNumber() % 40 + 44, + CGeneral::GetRandomNumber() % 40 + 36, CGeneral::GetRandomNumber() % 40 + 352, + CGeneral::GetRandomNumber() % 40 + 593, CGeneral::GetRandomNumber() % 40 + 312); + +MenuTrapezoid menuOptionHighlight(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); + +#ifndef MASTER +bool CMenuManager::m_PrefsMarketing = false; +bool CMenuManager::m_PrefsDisableTutorials = false; +#endif // !MASTER + +#ifdef GAMEPAD_MENU +uint32 TimeToStopPadShaking; +#endif + +const char* FrontendFilenames[][2] = { + {"background", ""}, + {"vc_logo", "vc_logom"}, + {"mouse", "mousea"}, + {"mapTop01", "mapTop01A"}, + {"mapTop02", "mapTop02A"}, + {"mapTop03", "mapTop03A"}, + {"mapMid01", "mapMid01A"}, + {"mapMid02", "mapMid02A"}, + {"mapMid03", "mapMid03A"}, + {"mapBot01", "mapBot01A"}, + {"mapBot02", "mapBot02A"}, + {"mapBot03", "mapBot03A"}, + {"wildstyle", "wildstyleA"}, + {"flash", "flashA"}, + {"kchat", "kchatA"}, + {"fever", "feverA"}, + {"vrock", "vrockA"}, + {"vcpr", "vcprA"}, + {"espantoso", "espantosoA"}, + {"emotion", "emotionA"}, + {"wave103", "wave103A"}, + {"mp3", "mp3A"}, + {"downOff", "buttonA"}, + {"downOn", "buttonA"}, + {"upOff", "buttonA"}, + {"upOn", "buttonA"}, +#ifdef GAMEPAD_MENU + {"fe_controller", "" }, + {"fe_arrows1", "" }, + {"fe_arrows2", "" }, + {"fe_arrows3", "" }, + {"fe_arrows4", "" }, +#endif +}; + +#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 XBOX_MESSAGE_SCREEN +bool CMenuManager::m_bDialogOpen = false; +uint32 CMenuManager::m_nDialogHideTimer = 0; +uint32 CMenuManager::m_nDialogHideTimerPauseMode = 0; +bool CMenuManager::m_bSaveWasSuccessful = false; +wchar* CMenuManager::m_pDialogText = nil; +#endif + +#define SET_FONT_FOR_MENU_HEADER \ + CFont::SetRightJustifyOn(); \ + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); \ + CFont::SetScale(MENU_X(MENUHEADER_WIDTH), MENU_Y(MENUHEADER_HEIGHT)); \ + CFont::SetDropShadowPosition(0); + +#define SET_FONT_FOR_LIST_ITEM \ + CFont::SetRightJustifyOff(); \ + CFont::SetScale(MENU_X(LISTITEM_X_SCALE), MENU_Y(LISTITEM_Y_SCALE)); \ + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + +#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)); + +// value must be between 0.0-1.0 +#define ProcessSlider(value, origY, increaseAction, decreaseAction, hoverEndX, onlyWhenHoveringRow) \ + do { \ + float y = origY MINUS_SCROLL_OFFSET; \ + lastActiveBarX = DisplaySlider(MENU_X_LEFT_ALIGNED(MENUSLIDER_X), MENU_Y(y), MENU_Y(MENUSLIDER_SMALLEST_BAR), MENU_Y(MENUSLIDER_BIGGEST_BAR), MENU_X(MENUSLIDER_UNK), value, MENU_X(3.0f)); \ + if (i != m_nCurrOption || !itemsAreSelectable) \ + break; \ + \ + if (CheckHover(0, lastActiveBarX - MENU_X(3.0f), MENU_Y(y), MENU_Y(MENUSLIDER_BIGGEST_BAR + y))) { \ + m_nHoverOption = decreaseAction; \ + break; \ + } \ + if (!CheckHover(MENU_X(3.0f) + lastActiveBarX, hoverEndX, MENU_Y(y), MENU_Y(MENUSLIDER_BIGGEST_BAR + y))) { \ + m_nHoverOption = HOVEROPTION_NOT_HOVERING; \ + break; \ + } \ + m_nHoverOption = increaseAction; \ + if (m_nMousePosX < MENU_X_LEFT_ALIGNED(MENUSLIDER_X)) \ + m_nHoverOption = HOVEROPTION_NOT_HOVERING; \ + \ + if (onlyWhenHoveringRow && (m_nMousePosY < MENU_Y(y) || m_nMousePosY > MENU_Y(MENUSLIDER_BIGGEST_BAR + y))) \ + m_nHoverOption = HOVEROPTION_NOT_HOVERING; \ + } while(0) + +// --- Functions not in the game/inlined starts + +inline 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--; + } +} + +inline 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++; + } + } +} + +inline void +CMenuManager::PageUpList(bool playSoundOnSuccess) +{ + if (m_nTotalListRow > MAX_VISIBLE_OPTION_ON_SCREEN) { + if (m_nFirstVisibleRowOnList > 0) { + if(playSoundOnSuccess) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_HIGHLIGHT_OPTION, 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; + } +} + +inline 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_HIGHLIGHT_OPTION, 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 +#define PLUS_LINE_HEIGHT_ON_SCREEN + (aScreens[m_nCurrScreen].layout ? aScreens[m_nCurrScreen].layout->lineHeight : MENU_DEFAULT_LINE_HEIGHT) +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; +} + +inline void +CMenuManager::ThingsToDoBeforeLeavingPage() +{ + if ((m_nCurrScreen == MENUPAGE_SKIN_SELECT) && strcmp(m_aSkinName, m_PrefsSkinFile) != 0) { + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + + } else if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) + m_nPrefsAudio3DProviderIndex = DMAudio.GetCurrent3DProviderIndex(); + + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); + + } else if (ScreenHasOption(m_nCurrScreen, "FED_RES")) { + m_nDisplayVideoMode = m_nPrefsVideoMode; + } + + if (m_nCurrScreen == MENUPAGE_SKIN_SELECT) { + CPlayerSkin::EndFrontendSkinEdit(); + } + +#ifdef SCROLLABLE_PAGES + if (SCREEN_HAS_AUTO_SCROLLBAR) { + m_nSelectedListRow = 0; + m_nFirstVisibleRowOnList = 0; + m_nScrollbarTopMargin = 0; + } +#endif + + 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(); + } +} + +inline int8 +CMenuManager::GetPreviousPageOption() +{ + int8 prevPage = !m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_PreviousPage : + (m_nCurrScreen == MENUPAGE_NEW_GAME || m_nCurrScreen == MENUPAGE_OPTIONS || m_nCurrScreen == MENUPAGE_EXIT ? MENUPAGE_START_MENU : aScreens[m_nCurrScreen].m_PreviousPage); + + 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; +} + +#else +#define PLUS_LINE_HEIGHT_ON_SCREEN + MENU_DEFAULT_LINE_HEIGHT +inline void +CMenuManager::ThingsToDoBeforeLeavingPage() +{ + switch (m_nCurrScreen) { + case MENUPAGE_SOUND_SETTINGS: + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) + m_nPrefsAudio3DProviderIndex = DMAudio.GetCurrent3DProviderIndex(); + + DMAudio.StopFrontEndTrack(); + OutputDebugString("FRONTEND AUDIO TRACK STOPPED"); + break; + case MENUPAGE_DISPLAY_SETTINGS: + m_nDisplayVideoMode = m_nPrefsVideoMode; + break; + case MENUPAGE_SKIN_SELECT: + if (strcmp(m_aSkinName, m_PrefsSkinFile) != 0) + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + + CPlayerSkin::EndFrontendSkinEdit(); + break; + } + +#ifdef SCROLLABLE_PAGES + if (SCREEN_HAS_AUTO_SCROLLBAR) { + m_nSelectedListRow = 0; + m_nFirstVisibleRowOnList = 0; + m_nScrollbarTopMargin = 0; + } +#endif +} + +inline int8 +CMenuManager::GetPreviousPageOption() +{ + return (!m_bGameNotLoaded ? aScreens[m_nCurrScreen].m_ParentEntry : + (m_nCurrScreen == MENUPAGE_NEW_GAME ? 0 : (m_nCurrScreen == MENUPAGE_OPTIONS ? 1 : (m_nCurrScreen == MENUPAGE_EXIT ? 2 : aScreens[m_nCurrScreen].m_ParentEntry)))); +} +#endif + +// ------ Functions not in the game/inlined ends + +bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha); +void DoRWStuffEndOfFrame(void); + +void +CMenuManager::SwitchToNewScreen(int8 screen) +{ + bMenuChangeOngoing = true; + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DrawBackground(true); + DoRWStuffEndOfFrame(); + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DrawBackground(true); + DoRWStuffEndOfFrame(); + m_nPrevScreen = m_nCurrScreen; + m_ShowEmptyBindingError = false; + ResetHelperText(); + + ThingsToDoBeforeLeavingPage(); + + if (screen == -2) { + int oldScreen = aScreens[m_nCurrScreen].m_PreviousPage; + int oldOption = GetPreviousPageOption(); + + m_nCurrOption = oldOption; + m_nCurrScreen = oldScreen; + } else if (screen == 0) { + m_nCurrScreen = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu; + m_nCurrOption = 0; + } else { + m_nCurrOption = 0; + m_nCurrScreen = screen; + } + SETUP_SCROLLING(m_nCurrScreen) + + if (hasNativeList(m_nPrevScreen)) + m_nTotalListRow = 0; + + if (m_nCurrScreen == MENUPAGE_CHOOSE_SAVE_SLOT) + m_nCurrOption = 8; + m_nMenuFadeAlpha = 0; + m_nOptionHighlightTransitionBlend = 0; + m_LastScreenSwitch = CTimer::GetTimeInMillisecondsPauseMode(); +} + +CMenuManager::CMenuManager() +{ + m_StatsScrollSpeed = 150.0f; + m_StatsScrollDirection = 1; + m_PrefsSfxVolume = 49; + m_PrefsMusicVolume = 49; + m_PrefsRadioStation = 0; + m_PrefsStereoMono = 1; + m_PrefsBrightness = 256; + m_PrefsLOD = CRenderer::ms_lodDistScale; + m_KeyPressedCode = -1; + m_bFrontEnd_ReloadObrTxtGxt = false; + m_PrefsMP3BoostVolume = 0; + m_PrefsShowSubtitles = 0; + m_PrefsShowLegends = 1; +#ifdef ASPECT_RATIO_SCALE + m_PrefsUseWideScreen = AR_AUTO; +#else + m_PrefsUseWideScreen = 0; +#endif + m_PrefsVsync = 0; + m_PrefsVsyncDisp = 1; + m_PrefsFrameLimiter = 1; + m_PrefsLanguage = 0; + field_54 = 0; + m_PrefsAllowNastyGame = 1; + m_PrefsSpeakers = 0; + field_8 = 0; + m_PrefsUseVibration = 0; + m_PrefsShowHud = 1; + m_PrefsRadarMode = 0; + m_DisplayControllerOnFoot = false; + m_bShutDownFrontEndRequested = false; + m_bStartUpFrontEndRequested = false; + pEditString = nil; + pControlEdit = nil; + DisplayComboButtonErrMsg = false; + m_PrefsDMA = 1; + OS_Language = LANG_ENGLISH; + m_ControlMethod = CONTROL_STANDARD; +#ifdef PC_PLAYER_CONTROLS + CCamera::m_bUseMouse3rdPerson = true; +#else + CCamera::m_bUseMouse3rdPerson = false; +#endif + m_lastWorking3DAudioProvider = 0; + m_nFirstVisibleRowOnList = 0; + m_nScrollbarTopMargin = 0.0f; + m_nSelectedListRow = 0; + m_nSkinsTotal = 0; + m_nPrefsAudio3DProviderIndex = AUDIO_PROVIDER_NOT_DETERMINED; + m_bGameNotLoaded = true; + m_nMousePosX = m_nMouseTempPosX; + m_nMousePosY = m_nMouseTempPosY; + m_nMouseOldPosX = m_nMousePosX; + m_nMouseOldPosY = m_nMousePosY; + m_bShowMouse = true; + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + + DMAudio.SetMP3BoostVolume(m_PrefsMP3BoostVolume); + m_bMenuActive = false; + m_bActivateSaveMenu = false; + m_bWantToLoad = false; + m_nMenuFadeAlpha = 0; + m_OnlySaveMenu = false; + m_fMapSize = MENU_Y(162.0f); // Y because of HOR+ + m_fMapCenterX = MENU_X_LEFT_ALIGNED(320.0f); + m_fMapCenterY = MENU_Y(225.0f); + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); + +#ifdef NO_ISLAND_LOADING + m_PrefsIslandLoading = ISLAND_LOADING_LOW; +#endif + +#ifdef GAMEPAD_MENU +#ifdef __SWITCH__ + m_PrefsControllerType = CONTROLLER_NINTENDO_SWITCH; +#else + m_PrefsControllerType = CONTROLLER_XBOXONE; +#endif +#endif + +#ifdef MISSION_REPLAY + m_bAttemptingMissionRetry = false; +#endif +} + +void +CMenuManager::SetFrontEndRenderStates(void) +{ + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); +} + +void +CMenuManager::Initialise(void) +{ + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DoRWStuffEndOfFrame(); + m_AllowNavigation = false; + m_firstStartCounter = -50; // to start from black + m_nMenuFadeAlpha = 0; + m_nCurrOption = 0; + m_nOptionHighlightTransitionBlend = 0; + CentreMousePointer(); +#ifdef GTA_HANDHELD + m_bShowMouse = false; +#else + m_bShowMouse = true; +#endif + m_fMapSize = MENU_Y(162.0f); // Y because of HOR+ + m_fMapCenterX = MENU_X_LEFT_ALIGNED(320.0f); + m_fMapCenterY = MENU_Y(225.0f); + CPad::StopPadsShaking(); +#ifdef MISSION_REPLAY + if (!m_OnlySaveMenu) { + if (m_nCurrScreen == MENUPAGE_MISSION_RETRY && m_bAttemptingMissionRetry) + m_bAttemptingMissionRetry = false; + else + m_nCurrScreen = MENUPAGE_NONE; + } +#else + if (!m_OnlySaveMenu) + m_nCurrScreen = MENUPAGE_NONE; +#endif + + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + DMAudio.Service(); + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); +#ifdef FIX_BUGS + static bool firstTime = true; + if (firstTime) { + DMAudio.SetRadioInCar(m_PrefsRadioStation); + firstTime = false; + } else +#endif + m_PrefsRadioStation = DMAudio.GetRadioInCar(); + + DMAudio.SetMP3BoostVolume(m_PrefsMP3BoostVolume); + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation < WILDSTYLE || m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = CGeneral::GetRandomNumber() % (USERTRACK + 1); + } else if (m_PrefsRadioStation < WILDSTYLE || m_PrefsRadioStation > WAVE) + m_PrefsRadioStation = CGeneral::GetRandomNumber() % (WAVE + 1); + + CFileMgr::SetDir(""); + //CFileMgr::SetDir(""); + PcSaveHelper.PopulateSlotInfo(); + CTimer::StartUserPause(); +} + +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 + } +} + +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: +#ifdef FIX_BUGS + m_PrefsBrightness += value * (384 / MENUSLIDER_LOGICAL_BARS); +#else + m_PrefsBrightness += value * 24.19f; +#endif + m_PrefsBrightness = Clamp(m_PrefsBrightness, 0, 384); + break; + case MENUACTION_DRAWDIST: + if(value > 0) + m_PrefsLOD += ((1.8f - 0.925f) / MENUSLIDER_LOGICAL_BARS); + else + m_PrefsLOD -= ((1.8f - 0.925f) / MENUSLIDER_LOGICAL_BARS); + m_PrefsLOD = Clamp(m_PrefsLOD, 0.925f, 1.8f); + CRenderer::ms_lodDistScale = m_PrefsLOD; + break; + + // I wonder the idea behind clamping those max to 65 + case MENUACTION_MUSICVOLUME: + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) { + m_PrefsMusicVolume += value * (64 / MENUSLIDER_LOGICAL_BARS); + m_PrefsMusicVolume = Clamp(m_PrefsMusicVolume, 0, 65); + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + } + break; + case MENUACTION_SFXVOLUME: + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) { + m_PrefsSfxVolume += value * (64 / MENUSLIDER_LOGICAL_BARS); + m_PrefsSfxVolume = Clamp(m_PrefsSfxVolume, 0, 65); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); + } + break; + case MENUACTION_MP3VOLUMEBOOST: + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) { + if (DMAudio.IsMP3RadioChannelAvailable()) { + m_PrefsMP3BoostVolume += value * (64 / MENUSLIDER_LOGICAL_BARS); + m_PrefsMP3BoostVolume = Clamp(m_PrefsMP3BoostVolume, 0, 65); + DMAudio.SetMP3BoostVolume(m_PrefsMP3BoostVolume); + } + } + 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; +#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(char *text) +{ + if (m_nMenuFadeAlpha != 255) + return; + + // there was a unused static bool + static uint32 LastFlash = 0; + int32 alpha = 255; + + CFont::SetRightJustifyOn(); + CFont::SetScale(SCREEN_SCALE_X(SMALLESTTEXT_X_SCALE), SCREEN_SCALE_Y(SMALLESTTEXT_Y_SCALE)); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetDropShadowPosition(0); + + // We're using SCREEN_STRETCH_FROM_RIGHT, because we also stretch black borders + if (text) { + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get(text)); + return; + } + + if (m_nHelperTextMsgId != 0 && m_nHelperTextMsgId != 1) { + if (CTimer::GetTimeInMillisecondsPauseMode() - LastFlash > 10) { + LastFlash = CTimer::GetTimeInMillisecondsPauseMode(); + m_nHelperTextAlpha -= 2; + } + + if (m_nHelperTextAlpha < 1) + ResetHelperText(); + + alpha = m_nHelperTextAlpha > 255 ? 255 : m_nHelperTextAlpha; + } + + CFont::SetColor(CRGBA(255, 255, 255, alpha)); + // TODO: name this cases? + switch (m_nHelperTextMsgId) { + case 1: + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_APP")); + break; + case 2: + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_HRD")); + break; + case 3: + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_RSO")); + break; + case 4: + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_STS")); + break; + case 5: + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), TheText.Get("FET_RSC")); + break; + default: + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_NO) + return; + + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_MUSICVOLUME || + aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_SFXVOLUME) { + + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), + m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER ? TheText.Get("FEH_NA") : TheText.Get("FET_MIG")); + return; + } + + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_KEYBOARDCTRLS) + return; + + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_SCREENRES) { + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), + m_bGameNotLoaded ? TheText.Get("FET_MIG") : TheText.Get("FEH_NA")); + return; + } + + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_AUDIOHW || + aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_SPEAKERCONF) { + + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), + m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER ? TheText.Get("FEH_NA") : TheText.Get("FET_MIG")); + return; + } + + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_RESTOREDEF) + return; + + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_MP3VOLUMEBOOST) { + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), + m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER ? TheText.Get("FEH_NA") : TheText.Get("FET_MIG")); + return; + } + + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(HELPER_TEXT_RIGHT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(HELPER_TEXT_BOTTOM_MARGIN), + m_nCurrScreen != MENUPAGE_STATS ? TheText.Get("FET_MIG") : TheText.Get("FEH_SSA")); + + break; + } +} + +int +CMenuManager::DisplaySlider(float x, float y, float mostLeftBarSize, float mostRightBarSize, float rectSize, float progress, float spacing) +{ + CRGBA color; + float maxBarHeight; + + int lastActiveBarX = 0; + float curBarX = 0.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 LEGACY_MENU_OPTIONS + if (m_PrefsVsyncDisp != m_PrefsVsync) + m_PrefsVsync = m_PrefsVsyncDisp; +#endif + DMAudio.DestroyAllGameCreatedEntities(); + DMAudio.Service(); + m_bShutDownFrontEndRequested = true; + m_bWantToRestart = true; + DMAudio.SetEffectsFadeVol(0); + DMAudio.SetMusicFadeVol(0); + for (int i = 0; i < NUM_RADIOS; i++) + CStats::FavoriteRadioStationList[i] = 0.0f; + + SwitchMenuOnAndOff(); + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); +} + +void +CMenuManager::DrawStandardMenus(bool activeScreen) +{ + float nextYToUse = 0.0f; // III leftover, set but unused in VC + bool itemsAreSelectable = true; + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetCentreOff(); + CFont::SetJustifyOn(); + CFont::SetBackGroundOnlyTextOff(); + +#ifdef CUSTOM_FRONTEND_OPTIONS + const int xMargin = aScreens[m_nCurrScreen].layout && aScreens[m_nCurrScreen].layout->xMargin != 0 ? aScreens[m_nCurrScreen].layout->xMargin : MENU_X_MARGIN; +#else + const int xMargin = MENU_X_MARGIN; +#endif + + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(xMargin)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(xMargin)); +#ifdef ASPECT_RATIO_SCALE + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH)); +#else + CFont::SetCentreSize(SCREEN_WIDTH); +#endif + + switch (m_nCurrScreen) { + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_CHOOSE_SAVE_SLOT: + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(38.0f), MENU_Y(85.0f), + MENU_X_LEFT_ALIGNED(615.0f), MENU_Y(75.0f), + MENU_X_LEFT_ALIGNED(30.0f), MENU_Y(320.0f), + MENU_X_LEFT_ALIGNED(605.0f), MENU_Y(330.0f), CRGBA(LIST_BACKGROUND_COLOR.r, LIST_BACKGROUND_COLOR.g, LIST_BACKGROUND_COLOR.b, FadeIn(LIST_BACKGROUND_COLOR.a))); + break; + case MENUPAGE_SOUND_SETTINGS: + PrintRadioSelector(); + break; + case MENUPAGE_STATS: + PrintStats(); + break; + case MENUPAGE_BRIEFS: + PrintBriefs(); + break; + } + + // Page name + if (aScreens[m_nCurrScreen].m_ScreenName[0] != '\0') { + + SET_FONT_FOR_MENU_HEADER + CFont::SetColor(CRGBA(30, 30, 30, FadeIn(255))); + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(MENUHEADER_POS_X) - MENU_X(7.f), SCREEN_SCALE_Y(MENUHEADER_POS_Y + 7.f), TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, FadeIn(255))); + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(MENUHEADER_POS_X), SCREEN_SCALE_Y(MENUHEADER_POS_Y), TheText.Get(aScreens[m_nCurrScreen].m_ScreenName)); + } + + // Label + wchar *str; + if (aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) { + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(MENULABEL_X_MARGIN)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(MENULABEL_X_MARGIN)); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetScale(MENU_X(BIGTEXT2_X_SCALE), MENU_Y(BIGTEXT2_Y_SCALE)); + CFont::SetRightJustifyOff(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + + 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_DELETE_SLOT_CONFIRM: + str = TheText.Get(aScreens[MENUPAGE_DELETE_SLOT_CONFIRM].m_aEntries[0].m_EntryName); + break; + case MENUPAGE_SAVE_OVERWRITE_CONFIRM: + if (Slots[m_nCurrSaveSlot] == SLOT_OK) + str = TheText.Get("FESZ_QO"); + else if (Slots[m_nCurrSaveSlot] == SLOT_CORRUPTED) + str = TheText.Get("FESZ_QZ"); + else + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + case MENUPAGE_EXIT: + if (m_bGameNotLoaded) + str = TheText.Get("FEQ_SRW"); + else + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + default: + str = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName); + break; + } + + CFont::PrintString(MENU_X_LEFT_ALIGNED(100.0f), MENU_Y(97.0f), str); + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(xMargin)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(xMargin)); + } + + if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + if (m_bWaitingForNewKeyBind) + itemsAreSelectable = false; + + DrawControllerScreenExtraText(-8.0f, MENU_X_LEFT_ALIGNED(350), MENU_DEFAULT_LINE_HEIGHT); + } + + wchar unicodeTemp[64]; +#ifdef ASPECT_RATIO_SCALE + char asciiTemp[32]; +#endif + + bool weHaveLabel = aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL; + uint8 section = 0; // 0: highlight trapezoid 1: texts + + while (section < 2) { +#endif + +#ifdef SCROLLABLE_PAGES + int firstOption = SCREEN_HAS_AUTO_SCROLLBAR ? m_nFirstVisibleRowOnList : 0; + int scrollOffset = aScreens[m_nCurrScreen].m_aEntries[firstOption].m_Y - aScreens[m_nCurrScreen].m_aEntries[0].m_Y; + for (int i = firstOption; i < firstOption + MAX_VISIBLE_OPTION && i < NUM_MENUROWS; ++i) { +#else + for (int i = 0; i < NUM_MENUROWS; ++i) { +#endif + 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::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetScale(MENU_X(MEDIUMTEXT_X_SCALE), MENU_Y(MEDIUMTEXT_Y_SCALE)); + CFont::SetDropShadowPosition(0); + } else { + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(BIGTEXT_X_SCALE), MENU_Y(BIGTEXT_Y_SCALE)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Align == MENUALIGN_LEFT) { + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + } else if (aScreens[m_nCurrScreen].m_aEntries[i].m_Align == MENUALIGN_RIGHT) { + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + } else { + CFont::SetRightJustifyOff(); + CFont::SetCentreOn(); + } + if (aScreens[m_nCurrScreen].m_aEntries[i].m_X == 0 && aScreens[m_nCurrScreen].m_aEntries[i].m_Y == 0) { + if (i == 0 || (i == 1 && weHaveLabel)) { +#ifdef CUSTOM_FRONTEND_OPTIONS + aScreens[m_nCurrScreen].m_aEntries[i].m_X = (aScreens[m_nCurrScreen].layout ? aScreens[m_nCurrScreen].layout->startX : MENU_DEFAULT_CONTENT_X); + aScreens[m_nCurrScreen].m_aEntries[i].m_Y = (aScreens[m_nCurrScreen].layout ? aScreens[m_nCurrScreen].layout->startY : MENU_DEFAULT_CONTENT_Y); +#else + aScreens[m_nCurrScreen].m_aEntries[i].m_X = MENU_DEFAULT_CONTENT_X; + aScreens[m_nCurrScreen].m_aEntries[i].m_Y = MENU_DEFAULT_CONTENT_Y; +#endif + + } else { + aScreens[m_nCurrScreen].m_aEntries[i].m_X = aScreens[m_nCurrScreen].m_aEntries[i-1].m_X; + aScreens[m_nCurrScreen].m_aEntries[i].m_Y = aScreens[m_nCurrScreen].m_aEntries[i-1].m_Y PLUS_LINE_HEIGHT_ON_SCREEN; + } + } +#ifdef CUSTOM_FRONTEND_OPTIONS + else if (aScreens[m_nCurrScreen].m_aEntries[i].m_Y == 0) { + aScreens[m_nCurrScreen].m_aEntries[i].m_Y = aScreens[m_nCurrScreen].m_aEntries[i-1].m_Y PLUS_LINE_HEIGHT_ON_SCREEN; + } +#endif + + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action != MENUACTION_LABEL && aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName[0] != '\0') { + + if (aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot <= SAVESLOT_8) { + CFont::SetRightJustifyOff(); + + leftText = nil; + if (Slots[i] == SLOT_OK) { + leftText = GetNameOfSavedGame(i); + rightText = GetSavedGameDateAndTime(i); + } + + if (!leftText || leftText[0] == '\0') { + sprintf(gString, "FEM_SL%d", i + 1); + leftText = TheText.Get(gString); + } + } else { + leftText = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName); + } + + if (m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER) { + if (strcmp(aScreens[m_nCurrScreen].m_aEntries[i].m_EntryName, "FEO_AUD") == 0) { + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + } + } + + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { +#ifdef GAMEPAD_MENU + 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; + // This one is still in enum and ProcessOnOffMenuOptions, but removed from other places + case MENUACTION_CTRLDISPLAY: + if (m_DisplayControllerOnFoot) + rightText = TheText.Get("FEC_ONF"); + else + rightText = TheText.Get("FEC_INC"); + break; +#endif + 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_MUSICVOLUME: + case MENUACTION_SFXVOLUME: + if (m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER) + rightText = TheText.Get("FEA_NAH"); + + break; + case MENUACTION_RADIO: + switch (m_PrefsRadioStation) { + case WILDSTYLE: + rightText = TheText.Get("FEA_FM0"); + break; + case FLASH_FM: + rightText = TheText.Get("FEA_FM1"); + break; + case KCHAT: + rightText = TheText.Get("FEA_FM2"); + break; + case FEVER: + rightText = TheText.Get("FEA_FM3"); + break; + case V_ROCK: + rightText = TheText.Get("FEA_FM4"); + break; + case VCPR: + rightText = TheText.Get("FEA_FM5"); + break; + case RADIO_ESPANTOSO: + rightText = TheText.Get("FEA_FM6"); + break; + case EMOTION: + rightText = TheText.Get("FEA_FM7"); + break; + case WAVE: + rightText = TheText.Get("FEA_FM8"); + break; + case USERTRACK: + rightText = TheText.Get("FEA_MP3"); + break; + } + break; + case MENUACTION_LEGENDS: + rightText = TheText.Get(m_PrefsShowLegends ? "FEM_ON" : "FEM_OFF"); + break; + case MENUACTION_RADARMODE: + switch (m_PrefsRadarMode) { + case 0: + rightText = TheText.Get("FED_RDM"); + break; + case 1: + rightText = TheText.Get("FED_RDB"); + break; + case 2: + rightText = TheText.Get("FEM_OFF"); + break; + } + break; + case MENUACTION_HUD: + rightText = TheText.Get(m_PrefsShowHud ? "FEM_ON" : "FEM_OFF"); + break; +#ifdef LEGACY_MENU_OPTIONS + 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_COLLISIONPOLYS: + rightText = TheText.Get(gbShowCollisionPolys ? "FEM_ON" : "FEM_OFF"); + break; +#endif + 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; + + if (!m_bGameNotLoaded) { + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + } + break; + case MENUACTION_AUDIOHW: + if (m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER) + rightText = TheText.Get("FEA_NAH"); + else if (m_nPrefsAudio3DProviderIndex == -1) + rightText = TheText.Get("FEA_ADP"); + else { + char *rawProvider = DMAudio.Get3DProviderName(m_nPrefsAudio3DProviderIndex); + AsciiToUnicode(rawProvider, unicodeTemp); + char *provider = UnicodeToAscii(unicodeTemp); // genius + strupr(provider); + if (!strcmp(provider, "DIRECTSOUND3D HARDWARE SUPPORT")) { + strcpy(provider, "DSOUND3D HARDWARE SUPPORT"); + } else if (!strcmp(provider, "DIRECTSOUND3D SOFTWARE EMULATION")) { + strcpy(provider, "DSOUND3D SOFTWARE EMULATION"); + } + AsciiToUnicode(provider, unicodeTemp); + rightText = unicodeTemp; + } + break; + case MENUACTION_SPEAKERCONF: { + if (m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER) + 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 CONTROL_STANDARD: + leftText = TheText.Get("FET_STI"); + break; + case CONTROL_CLASSIC: + leftText = TheText.Get("FET_CTI"); + 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"); + if (m_ControlMethod == CONTROL_CLASSIC) { + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + } + break; + case MENUACTION_MP3VOLUMEBOOST: + if (!DMAudio.IsMP3RadioChannelAvailable()) { + rightText = TheText.Get("FEA_NM3"); + } + 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) { + if (option.m_CFOSelect->disableIfGameLoaded && !m_bGameNotLoaded) + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + + // To whom manipulate option.m_CFO->value of static 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 (option.m_CFODynamic->drawFunc) { + bool isOptionDisabled = false; + rightText = option.m_CFODynamic->drawFunc(&isOptionDisabled, m_nCurrOption == i); + if (isOptionDisabled) + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + } + } + break; +#endif + } + + // Highlight trapezoid + if (activeScreen && i == m_nCurrOption && itemsAreSelectable && section == 0) { + + int leftXMax, rightXMin; + + // FIX: Let's don't scale those so GetStringWidth can give us unscaled width, which will be handy to other calculations below that's done without scaling in mind, + // and scaling will be done eventually. + // CFont::SetScale(MENU_X(BIGTEXT_X_SCALE), MENU_Y(BIGTEXT_Y_SCALE)); + CFont::SetScale(BIGTEXT_X_SCALE, BIGTEXT_Y_SCALE); + + wchar *curOptionName = TheText.Get(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_EntryName); + float curOptionWidth = CFont::GetStringWidth(curOptionName, true); + + if (CFont::Details.centre) { + leftXMax = Max(0, aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_X - curOptionWidth / 2.f); + rightXMin = Min(DEFAULT_SCREEN_WIDTH, curOptionWidth / 2.f + aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_X); + + } else if (!CFont::Details.rightJustify) { + leftXMax = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_X; + rightXMin = Min(DEFAULT_SCREEN_WIDTH, curOptionWidth + aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_X); + + } else { + leftXMax = Max(0, aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_X - curOptionWidth); + rightXMin = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_X; + } + CFont::SetScale(MENU_X(BIGTEXT_X_SCALE), MENU_Y(BIGTEXT_Y_SCALE)); + + int action = aScreens[m_nCurrScreen].m_aEntries[i].m_Action; + int saveSlot = aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot; + if (rightText || action == MENUACTION_DRAWDIST || action == MENUACTION_BRIGHTNESS || action == MENUACTION_MUSICVOLUME || + action == MENUACTION_SFXVOLUME || action == MENUACTION_MP3VOLUMEBOOST || action == MENUACTION_MOUSESENS || + saveSlot >= SAVESLOT_1 && saveSlot <= SAVESLOT_8 +#ifdef CUSTOM_FRONTEND_OPTIONS + || action == MENUACTION_CFO_SLIDER +#endif + ) { + rightXMin = 600; + leftXMax = 40; + } + + int y = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Y MINUS_SCROLL_OFFSET; + int topYMax = y; + uint32 bottomYMin = y + MENU_DEFAULT_LINE_HEIGHT - 7; // Decreasing is not recommended. Because this actually is dependent to font scale, not line height. + + // Actually bottomRight and bottomLeft should be exchanged here(although this is original code). + // So this shows us either R* didn't use same struct for menu BG and highlight, or they just kept fields as x1,y1 etc. Yikes. + + if (m_nOptionHighlightTransitionBlend == 0) { + if (m_firstStartCounter == 255 && m_nMenuFadeAlpha == 255 && !bMenuChangeOngoing) { + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(menuOptionHighlight.topLeft_x), MENU_Y(menuOptionHighlight.topLeft_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.topRight_x), MENU_Y(menuOptionHighlight.topRight_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.bottomRight_x), MENU_Y(menuOptionHighlight.bottomRight_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.bottomLeft_x), MENU_Y(menuOptionHighlight.bottomLeft_y), SELECTIONBORDER_COLOR); + } + menuOptionHighlight.SaveCurrentCoors(); + menuOptionHighlight.topLeft_x = leftXMax - 5 - CGeneral::GetRandomNumber() % 10; + menuOptionHighlight.topLeft_y = topYMax - CGeneral::GetRandomNumber() % 7; + menuOptionHighlight.topRight_x = rightXMin + 5 + CGeneral::GetRandomNumber() % 10; + menuOptionHighlight.topRight_y = topYMax - CGeneral::GetRandomNumber() % 7; + menuOptionHighlight.bottomLeft_x = rightXMin + 5 + CGeneral::GetRandomNumber() % 10; + menuOptionHighlight.bottomLeft_y = bottomYMin + CGeneral::GetRandomNumber() % 7; + menuOptionHighlight.bottomRight_x = leftXMax - 5 - CGeneral::GetRandomNumber() % 10; + menuOptionHighlight.bottomRight_y = bottomYMin + CGeneral::GetRandomNumber() % 7; + menuOptionHighlight.UpdateMultipliers(); + menuOptionHighlight.Translate(m_nOptionHighlightTransitionBlend); + + } else if (m_nOptionHighlightTransitionBlend < 255) { + menuOptionHighlight.Translate(m_nOptionHighlightTransitionBlend); + if (m_firstStartCounter == 255 && m_nMenuFadeAlpha == 255 && !bMenuChangeOngoing) { + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(menuOptionHighlight.topLeft_x), MENU_Y(menuOptionHighlight.topLeft_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.topRight_x), MENU_Y(menuOptionHighlight.topRight_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.bottomRight_x), MENU_Y(menuOptionHighlight.bottomRight_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.bottomLeft_x), MENU_Y(menuOptionHighlight.bottomLeft_y), SELECTIONBORDER_COLOR); + } + } else { + m_nOptionHighlightTransitionBlend = 255; + menuOptionHighlight.Translate(m_nOptionHighlightTransitionBlend); + if (m_firstStartCounter == 255 && m_nMenuFadeAlpha == 255 && !bMenuChangeOngoing) { + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(menuOptionHighlight.topLeft_x), MENU_Y(menuOptionHighlight.topLeft_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.topRight_x), MENU_Y(menuOptionHighlight.topRight_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.bottomRight_x), MENU_Y(menuOptionHighlight.bottomRight_y), + MENU_X_LEFT_ALIGNED(menuOptionHighlight.bottomLeft_x), MENU_Y(menuOptionHighlight.bottomLeft_y), SELECTIONBORDER_COLOR); + } + } + + static uint32 lastBlendChange = 0; + if (m_nOptionHighlightTransitionBlend <= 255) { + static uint32 blendChangeCounter = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastBlendChange > 20 + || blendChangeCounter > 20 + ) { + m_nOptionHighlightTransitionBlend += 50; + lastBlendChange = CTimer::GetTimeInMillisecondsPauseMode(); + blendChangeCounter = 0; + } +#ifdef FIX_BUGS + blendChangeCounter += CTimer::GetLogicalFramesPassed(); +#else + ++blendChangeCounter; +#endif + } + } + + if (section == 1) { + if (leftText) { + CFont::PrintString(MENU_X_LEFT_ALIGNED(aScreens[m_nCurrScreen].m_aEntries[i].m_X), MENU_Y(aScreens[m_nCurrScreen].m_aEntries[i].m_Y MINUS_SCROLL_OFFSET), leftText); + } + + if (rightText) { + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + if (aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot >= SAVESLOT_1 && aScreens[m_nCurrScreen].m_aEntries[i].m_SaveSlot <= SAVESLOT_8) { + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetScale(MENU_X(MEDIUMTEXT_X_SCALE), MENU_Y(MEDIUMTEXT_Y_SCALE)); + } else { + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(BIGTEXT_X_SCALE), MENU_Y(BIGTEXT_Y_SCALE)); + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(DEFAULT_SCREEN_WIDTH - RIGHT_ALIGNED_TEXT_RIGHT_MARGIN(xMargin)), MENU_Y(aScreens[m_nCurrScreen].m_aEntries[i].m_Y MINUS_SCROLL_OFFSET), rightText); + } + + 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 +#ifdef CUSTOM_FRONTEND_OPTIONS + && ScreenHasOption(m_nCurrScreen, "FEA_3DH") +#else + && m_nCurrScreen == MENUPAGE_SOUND_SETTINGS +#endif + && m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) { + + 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 +#ifdef CUSTOM_FRONTEND_OPTIONS + && ScreenHasOption(m_nCurrScreen, "FED_RES")) { +#else + && m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS) { +#endif + m_nDisplayVideoMode = m_nPrefsVideoMode; + SetHelperText(3); + } + } + +#ifdef CUSTOM_FRONTEND_OPTIONS +#define SLIDER_Y(pos) (aScreens[m_nCurrScreen].m_aEntries[i].m_Y - 5.f) +#else +#define SLIDER_Y(pos) pos +#endif + // Sliders + int lastActiveBarX; + switch (aScreens[m_nCurrScreen].m_aEntries[i].m_Action) { + case MENUACTION_BRIGHTNESS: + ProcessSlider(m_PrefsBrightness / 384.0f, SLIDER_Y(70.0f), HOVEROPTION_INCREASE_BRIGHTNESS, HOVEROPTION_DECREASE_BRIGHTNESS, SCREEN_WIDTH, true); + break; + case MENUACTION_DRAWDIST: + ProcessSlider((m_PrefsLOD - 0.925f) / 0.875f, SLIDER_Y(99.0f), HOVEROPTION_INCREASE_DRAWDIST, HOVEROPTION_DECREASE_DRAWDIST, SCREEN_WIDTH, true); + break; + case MENUACTION_MUSICVOLUME: + if(m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) + ProcessSlider(m_PrefsMusicVolume / 64.0f, SLIDER_Y(70.0f), HOVEROPTION_INCREASE_MUSICVOLUME, HOVEROPTION_DECREASE_MUSICVOLUME, SCREEN_WIDTH, true); + break; + case MENUACTION_SFXVOLUME: + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) + ProcessSlider(m_PrefsSfxVolume / 64.0f, SLIDER_Y(99.0f), HOVEROPTION_INCREASE_SFXVOLUME, HOVEROPTION_DECREASE_SFXVOLUME, SCREEN_WIDTH, true); + break; + case MENUACTION_MOUSESENS: + ProcessSlider(TheCamera.m_fMouseAccelHorzntl * 200.0f, SLIDER_Y(170.0f), HOVEROPTION_INCREASE_MOUSESENS, HOVEROPTION_DECREASE_MOUSESENS, SCREEN_WIDTH, false); + break; + case MENUACTION_MP3VOLUMEBOOST: + if(m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER && DMAudio.IsMP3RadioChannelAvailable()) + ProcessSlider(m_PrefsMP3BoostVolume / 64.f, SLIDER_Y(128.0f), HOVEROPTION_INCREASE_MP3BOOST, HOVEROPTION_DECREASE_MP3BOOST, SCREEN_WIDTH, true); + 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), SLIDER_Y(0), HOVEROPTION_INCREASE_CFO_SLIDER, HOVEROPTION_DECREASE_CFO_SLIDER, SCREEN_WIDTH, true); + break; +#endif + } + + // Not just unused, but also collides with the bug fix in Font.cpp. Yikes. +#ifndef FIX_BUGS + nextYToUse += MENU_DEFAULT_LINE_HEIGHT * CFont::GetNumberLines(MENU_X_LEFT_ALIGNED(60.0f), MENU_Y(nextYToUse), leftText); +#endif + + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_RADIO) { + nextYToUse += MENURADIO_SELECTOR_HEIGHT + 5.f; // unused + } + } + } + } + section++; + } + +#ifdef SCROLLABLE_PAGES + #define SCROLLBAR_BOTTOM_Y 105.0f // only for background, scrollbar's itself is calculated + #define SCROLLBAR_RIGHT_X 26.0f + #define SCROLLBAR_WIDTH 9.5f + #define SCROLLBAR_TOP_Y 84 + + if (activeScreen && 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(30, 30, 30, FadeIn(150))); + + float scrollbarHeight = SCROLLBAR_MAX_HEIGHT / (m_nTotalListRow / (float) MAX_VISIBLE_OPTION); + float scrollbarBottom, scrollbarTop; + + scrollbarBottom = MENU_Y(SCROLLBAR_TOP_Y - 6 + m_nScrollbarTopMargin + scrollbarHeight); + scrollbarTop = MENU_Y(SCROLLBAR_TOP_Y + 2 + 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) { +#ifdef GAMEPAD_MENU + case MENUPAGE_CONTROLLER_SETTINGS: + PrintController(); + break; +#endif + case MENUPAGE_STATS: + case MENUPAGE_CONTROLLER_PC: + case MENUPAGE_SOUND_SETTINGS: + case MENUPAGE_DISPLAY_SETTINGS: + case MENUPAGE_MOUSE_CONTROLS: + DisplayHelperText(nil); + break; + case MENUPAGE_OPTIONS: + if (m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_LOADRADIO) + DisplayHelperText("FEA_NAH"); + break; +#ifdef CUSTOM_FRONTEND_OPTIONS + default: + if (aScreens[m_nCurrScreen].layout) { + if (aScreens[m_nCurrScreen].layout->showLeftRightHelper) { + DisplayHelperText(nil); + } + } + break; +#endif + } + + if (m_nCurrScreen == MENUPAGE_DELETING_IN_PROGRESS) { + SmallMessageScreen("FEDL_WR"); + } +#ifndef XBOX_MESSAGE_SCREEN + else if (m_nCurrScreen == MENUPAGE_SAVING_IN_PROGRESS) { + SmallMessageScreen("FESZ_WR"); + } +#endif +} + +int +CMenuManager::GetNumOptionsCntrlConfigScreens(void) +{ + int number = 0; + switch (m_nCurrScreen) { +#ifdef LEGACY_MENU_OPTIONS + case MENUPAGE_CONTROLLER_PC_OLD3: + number = 2; + break; + case MENUPAGE_CONTROLLER_DEBUG: + number = 4; + break; +#endif + case MENUPAGE_KEYBOARD_CONTROLS: + switch (m_ControlMethod) { + case CONTROL_STANDARD: + number = 27; + break; + case CONTROL_CLASSIC: + number = 32; + 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; + } + + 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(255, 255, 255, FadeIn(255))); + + 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 20: + case 21: + case 22: + case 23: + 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 18: + controllerAction = PED_DUCK; + break; + case 19: + controllerAction = PED_ANSWER_PHONE; + break; + case 24: + controllerAction = PED_LOOKBEHIND; + break; + case 25: + if (m_ControlMethod == CONTROL_STANDARD) + controllerAction = -1; + else + controllerAction = PED_1RST_PERSON_LOOK_LEFT; + break; + case 26: + if (m_ControlMethod == CONTROL_STANDARD) + controllerAction = -1; + else + controllerAction = PED_1RST_PERSON_LOOK_RIGHT; + break; + case 27: + controllerAction = PED_1RST_PERSON_LOOK_UP; + break; + case 28: + controllerAction = PED_1RST_PERSON_LOOK_DOWN; + break; + case 29: + controllerAction = PED_CYCLE_TARGET_LEFT; + break; + case 30: + controllerAction = PED_CYCLE_TARGET_RIGHT; + break; + case 31: + 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 18: + case 19: + case 27: + case 28: + case 29: + case 30: + case 31: + 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 20: + controllerAction = VEHICLE_TURRETLEFT; + break; + case 21: + controllerAction = VEHICLE_TURRETRIGHT; + break; + case 22: + controllerAction = VEHICLE_TURRETUP; + break; + case 23: + controllerAction = VEHICLE_TURRETDOWN; + break; + case 24: + controllerAction = -2; + break; + case 25: + controllerAction = VEHICLE_LOOKLEFT; + break; + case 26: + controllerAction = VEHICLE_LOOKRIGHT; + break; + default: + break; + } + } + + // 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 + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(SELECTIONBORDER_COLOR.r, SELECTIONBORDER_COLOR.g, SELECTIONBORDER_COLOR.b, FadeIn(255))); +#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(SELECTIONBORDER_COLOR.r, SELECTIONBORDER_COLOR.g, SELECTIONBORDER_COLOR.b, FadeIn(255))); +#endif + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + + } else if (column == CONTSETUP_VEHICLE_COLUMN && m_nSelectedContSetupColumn == CONTSETUP_VEHICLE_COLUMN) { +#ifdef FIX_BUGS + CSprite2d::DrawRect(CRect(nextX, MENU_Y(bgY), nextX + MENU_X(CONTSETUP_BOUND_COLUMN_WIDTH), + MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), CRGBA(SELECTIONBORDER_COLOR.r, SELECTIONBORDER_COLOR.g, SELECTIONBORDER_COLOR.b, FadeIn(255))); +#else + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(410.0f), MENU_Y(bgY), MENU_X_LEFT_ALIGNED(600.0f), MENU_Y(bgY + CONTSETUP_BOUND_HIGHLIGHT_HEIGHT)), + CRGBA(SELECTIONBORDER_COLOR.r, SELECTIONBORDER_COLOR.g, SELECTIONBORDER_COLOR.b, FadeIn(255))); +#endif + CFont::SetColor(CRGBA(255, 255, 255, FadeIn(255))); + } + } + } + + // Print bindings, including seperator (-) between them + + CFont::SetScale(MENU_X(0.25f), MENU_Y(LISTITEM_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(255, 255, 255, FadeIn(255))); + CFont::PrintString(nextX, nextY, seperator); + 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(0, 0, 0, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_NUS")); // not used + + } else if (controllerAction == -2) { + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_CMP")); // combo: l+r + + } else if (bindingsForThisOpt == 0) { + m_NoEmptyBinding = false; + if (m_nSelectedListRow != optionIdx) { + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_UNB")); // unbound + + } else if (m_bWaitingForNewKeyBind) { + if (column != m_nSelectedContSetupColumn) { + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_UNB")); // unbound + } + } else { + if (column != m_nSelectedContSetupColumn) { + CFont::SetColor(CRGBA(0, 0, 0, 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(255, 255, 255, FadeIn(255))); + CFont::PrintString(nextX, nextY, TheText.Get("FEC_QUE")); // "???" + } + if (m_bKeyChangeNotProcessed) + DisplayHelperText("FET_CIG"); + else + DisplayHelperText("FET_RIG"); + + SET_FONT_FOR_LIST_ITEM + + m_bKeyIsOK = true; + } else { + DisplayHelperText("FET_CIG"); + SET_FONT_FOR_LIST_ITEM + + m_bKeyIsOK = false; + m_bKeyChangeNotProcessed = false; + } + } else if (optionIdx == m_nSelectedListRow) { + DisplayHelperText("FET_EIG"); + 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(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_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 + + // Shadow + CFont::SetColor(CRGBA(30, 30, 30, FadeIn(255))); + + if (m_ControlMethod == CONTROL_STANDARD) + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(MENUHEADER_POS_X) - MENU_X(7.f), SCREEN_SCALE_Y(MENUHEADER_POS_Y + 7.f), TheText.Get("FET_STI")); + else if (m_ControlMethod == CONTROL_CLASSIC) + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(MENUHEADER_POS_X) - MENU_X(7.f), SCREEN_SCALE_Y(MENUHEADER_POS_Y + 7.f), TheText.Get("FET_CTI")); + + // Real header + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, FadeIn(255))); + + if (m_ControlMethod == CONTROL_STANDARD) + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(MENUHEADER_POS_X), SCREEN_SCALE_Y(MENUHEADER_POS_Y), TheText.Get("FET_STI")); + else if (m_ControlMethod == CONTROL_CLASSIC) + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(MENUHEADER_POS_X), SCREEN_SCALE_Y(MENUHEADER_POS_Y), TheText.Get("FET_CTI")); + + wchar *actionTexts[33]; + 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"); + actionTexts[18] = TheText.Get("FEC_CRO"); + actionTexts[19] = TheText.Get("FEC_ANS"); + if (m_ControlMethod == CONTROL_CLASSIC) { + actionTexts[20] = TheText.Get("FEC_TFL"); + actionTexts[21] = TheText.Get("FEC_TFR"); + actionTexts[22] = TheText.Get("FEC_TFU"); + actionTexts[23] = TheText.Get("FEC_TFD"); + actionTexts[24] = TheText.Get("FEC_LBA"); + actionTexts[25] = TheText.Get("FEC_LOL"); + actionTexts[26] = TheText.Get("FEC_LOR"); + actionTexts[27] = TheText.Get("FEC_LUD"); + actionTexts[28] = TheText.Get("FEC_LDU"); + actionTexts[29] = TheText.Get("FEC_NTR"); + actionTexts[30] = TheText.Get("FEC_PTT"); + actionTexts[31] = TheText.Get("FEC_CEN"); + actionTexts[32] = nil; + } else { + actionTexts[20] = TheText.Get("FEC_TFL"); + actionTexts[21] = TheText.Get("FEC_TFR"); + actionTexts[22] = TheText.Get("FEC_TFU"); + actionTexts[23] = TheText.Get("FEC_TFD"); + actionTexts[24] = TheText.Get("FEC_LBA"); + actionTexts[25] = TheText.Get("FEC_LOL"); + actionTexts[26] = TheText.Get("FEC_LOR"); + actionTexts[27] = nil; + } + + // Blue 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::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + 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")); + CFont::SetDropShadowPosition(0); + SET_FONT_FOR_LIST_ITEM + + int yStart; + if (m_ControlMethod == CONTROL_CLASSIC) + yStart = CONTSETUP_LIST_TOP + 18; + else + yStart = CONTSETUP_LIST_TOP + 21; + + float optionYBottom = yStart + rowHeight; + for (int i = 0; i < ARRAY_SIZE(actionTexts); ++i) { + wchar *actionText = actionTexts[i]; + if (!actionText) + break; + + if (!m_bWaitingForNewKeyBind) { + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(CONTSETUP_LIST_LEFT - 10.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)) { + 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 + 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 + 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 || i == 22 || i == 23)) + CFont::SetScale(MENU_X(0.32f), MENU_Y(LISTITEM_Y_SCALE)); + else + CFont::SetScale(MENU_X(LISTITEM_X_SCALE), MENU_Y(LISTITEM_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); + + if (!m_bWaitingForNewKeyBind) { + CFont::SetScale(MENU_X(BIGTEXT_X_SCALE), MENU_Y(BIGTEXT_Y_SCALE)); + + 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 - 10.0f) && m_nMousePosX < MENU_X_LEFT_ALIGNED(CONTSETUP_COLUMN_3_X + CONTSETUP_BOUND_COLUMN_WIDTH) + && m_nMousePosY > MENU_Y(CONTSETUP_LIST_TOP - 10.0f) && m_nMousePosY < SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_LIST_BOTTOM)) { + 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(BIGTEXT_X_SCALE), MENU_Y(BIGTEXT_Y_SCALE)); + CFont::SetRightJustifyOn(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + CFont::PrintString(MENU_X_RIGHT_ALIGNED(CONTSETUP_BACK_RIGHT - 2.0f), SCREEN_SCALE_FROM_BOTTOM(CONTSETUP_BACK_BOTTOM - 4.0f), TheText.Get("FEDS_TB")); +} + +void +CMenuManager::DrawFrontEnd() +{ + CFont::SetAlphaFade(255.0f); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + SetFrontEndRenderStates(); + m_NoEmptyBinding = true; + + if (m_nCurrScreen == MENUPAGE_NONE) { + if (m_bGameNotLoaded) { + m_nCurrScreen = MENUPAGE_START_MENU; + } else { + m_nCurrScreen = MENUPAGE_PAUSE_MENU; + } + SETUP_SCROLLING(m_nCurrScreen) + } + + if (m_nCurrOption == 0 && aScreens[m_nCurrScreen].m_aEntries[0].m_Action == MENUACTION_LABEL) + m_nCurrOption = 1; + + if (m_firstStartCounter == 255 && m_nMenuFadeAlpha == 255) + bMenuChangeOngoing = false; + + DrawBackground(false); + + char strver[200]; + wchar ustr[200]; + snprintf(strver, sizeof(strver), "dca-miami: %s", getExecutableTag()); + AsciiToUnicode(strver, ustr); + + CFont::SetScale(MENU_X(MENUACTION_SCALE_MULT*3/4), MENU_Y(MENUACTION_SCALE_MULT*3/4)); + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + CFont::PrintString(MENU_X_LEFT_ALIGNED(BUILDID_TEXT_LEFT_MARGIN), SCREEN_SCALE_FROM_BOTTOM(BUILDID_TEXT_BOTTOM_MARGIN), ustr); +} + +void +CMenuManager::DrawBackground(bool transitionCall) +{ + if (!m_bSpritesLoaded) + return; + + SetFrontEndRenderStates(); + + if (m_firstStartCounter < 255) { + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(0, 0, 0, 255)); + } + + if (m_nMenuFadeAlpha != 0) { + + if (m_nMenuFadeAlpha < 255) { + + menuBg.Translate(m_nMenuFadeAlpha); + SetFrontEndRenderStates(); + m_aFrontEndSprites[MENUSPRITE_BACKGROUND].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, FadeIn(255))); + if (m_nCurrScreen == MENUPAGE_MAP) + PrintMap(); + + // Left border + CSprite2d::Draw2DPolygon(SCREEN_STRETCH_X(menuBg.bottomLeft_x), SCREEN_STRETCH_Y(menuBg.bottomLeft_y), 0.0f, SCREEN_HEIGHT, + SCREEN_STRETCH_X(menuBg.topLeft_x), SCREEN_STRETCH_Y(menuBg.topLeft_y), 0.0f, 0.0f, CRGBA(0, 0, 0, 255)); + + // Top border + CSprite2d::Draw2DPolygon(SCREEN_STRETCH_X(menuBg.topRight_x), SCREEN_STRETCH_Y(menuBg.topRight_y), + SCREEN_STRETCH_X(menuBg.topLeft_x), SCREEN_STRETCH_Y(menuBg.topLeft_y), SCREEN_WIDTH, 0.0f, 0.0f, 0.0f, CRGBA(0, 0, 0, 255)); + + // Bottom border + CSprite2d::Draw2DPolygon(SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, SCREEN_HEIGHT, SCREEN_STRETCH_X(menuBg.bottomRight_x), SCREEN_STRETCH_Y(menuBg.bottomRight_y), + SCREEN_STRETCH_X(menuBg.bottomLeft_x), SCREEN_STRETCH_Y(menuBg.bottomLeft_y), CRGBA(0, 0, 0, 255)); + + // Right border + CSprite2d::Draw2DPolygon(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_STRETCH_X(menuBg.bottomRight_x), SCREEN_STRETCH_Y(menuBg.bottomRight_y), + SCREEN_WIDTH, 0.0f, SCREEN_STRETCH_X(menuBg.topRight_x), SCREEN_STRETCH_Y(menuBg.topRight_y), CRGBA(0, 0, 0, 255)); + } else { + m_nMenuFadeAlpha = 255; + m_firstStartCounter = 255; + m_aFrontEndSprites[MENUSPRITE_BACKGROUND].Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, FadeIn(255))); + if (m_nCurrScreen == MENUPAGE_MAP) + PrintMap(); + + // Left border + CSprite2d::Draw2DPolygon(SCREEN_STRETCH_X(menuBg.bottomLeft_x), SCREEN_STRETCH_Y(menuBg.bottomLeft_y), 0.0f, SCREEN_HEIGHT, + SCREEN_STRETCH_X(menuBg.topLeft_x), SCREEN_STRETCH_Y(menuBg.topLeft_y), 0.0f, 0.0f, CRGBA(0, 0, 0, 255)); + + // Top border + CSprite2d::Draw2DPolygon(SCREEN_STRETCH_X(menuBg.topRight_x), SCREEN_STRETCH_Y(menuBg.topRight_y), + SCREEN_STRETCH_X(menuBg.topLeft_x), SCREEN_STRETCH_Y(menuBg.topLeft_y), SCREEN_WIDTH, 0.0f, 0.0f, 0.0f, CRGBA(0, 0, 0, 255)); + + // Bottom border + CSprite2d::Draw2DPolygon(SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, SCREEN_HEIGHT, SCREEN_STRETCH_X(menuBg.bottomRight_x), SCREEN_STRETCH_Y(menuBg.bottomRight_y), + SCREEN_STRETCH_X(menuBg.bottomLeft_x), SCREEN_STRETCH_Y(menuBg.bottomLeft_y), CRGBA(0, 0, 0, 255)); + + // Right border + CSprite2d::Draw2DPolygon(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_STRETCH_X(menuBg.bottomRight_x), SCREEN_STRETCH_Y(menuBg.bottomRight_y), + SCREEN_WIDTH, 0.0f, SCREEN_STRETCH_X(menuBg.topRight_x), SCREEN_STRETCH_Y(menuBg.topRight_y), CRGBA(0, 0, 0, 255)); + } + } else { + menuBg.SaveCurrentCoors(); + switch (m_nCurrScreen) { + case MENUPAGE_STATS: + menuBg.topLeft_x = 70.0f; + menuBg.topLeft_y = 75.0f; + menuBg.topRight_x = 550.0f; + menuBg.topRight_y = 16.0f; + menuBg.bottomLeft_x = 74.0f; + menuBg.bottomLeft_y = 354.0f; + menuBg.bottomRight_x = 581.0f; + menuBg.bottomRight_y = 340.0f; + break; + case MENUPAGE_SOUND_SETTINGS: + menuBg.topLeft_x = 26.0f; + menuBg.topLeft_y = 59.0f; + menuBg.topRight_x = 629.0f; + menuBg.topRight_y = 29.0f; + menuBg.bottomLeft_x = 15.0f; + menuBg.bottomLeft_y = 438.0f; + menuBg.bottomRight_x = 610.0f; + menuBg.bottomRight_y = 410.0f; + break; + case MENUPAGE_SKIN_SELECT: + case MENUPAGE_KEYBOARD_CONTROLS: + menuBg.topLeft_x = 14.0f; + menuBg.topLeft_y = 39.0f; + menuBg.topRight_x = 636.0f; + menuBg.topRight_y = 29.0f; + menuBg.bottomLeft_x = 15.0f; + menuBg.bottomLeft_y = 426.0f; + menuBg.bottomRight_x = 630.0f; + menuBg.bottomRight_y = 398.0f; + break; + case MENUPAGE_BRIEFS: + case MENUPAGE_DISPLAY_SETTINGS: + case MENUPAGE_MAP: + case MENUPAGE_CHOOSE_LOAD_SLOT: + case MENUPAGE_CHOOSE_DELETE_SLOT: + case MENUPAGE_CHOOSE_SAVE_SLOT: + case MENUPAGE_MOUSE_CONTROLS: + menuBg.topLeft_x = 26.0f; + menuBg.topLeft_y = 59.0f; + menuBg.topRight_x = 629.0f; + menuBg.topRight_y = 29.0f; + menuBg.bottomLeft_x = 15.0f; + menuBg.bottomLeft_y = 426.0f; + menuBg.bottomRight_x = 610.0f; + menuBg.bottomRight_y = 398.0f; + break; + default: +#ifdef CUSTOM_FRONTEND_OPTIONS + if (aScreens[m_nCurrScreen].layout && aScreens[m_nCurrScreen].layout->noInvasiveBorders) { + // Taken from the case above + menuBg.topLeft_x = 26.0f; + menuBg.topLeft_y = 59.0f; + menuBg.topRight_x = 629.0f; + menuBg.topRight_y = 29.0f; + menuBg.bottomLeft_x = 15.0f; + menuBg.bottomLeft_y = 426.0f; + menuBg.bottomRight_x = 610.0f; + menuBg.bottomRight_y = 398.0f; + break; + } +#endif + menuBg.topLeft_x = CGeneral::GetRandomNumber() % 40 + 65; + menuBg.topLeft_y = CGeneral::GetRandomNumber() % 40 + 21; + menuBg.topRight_x = CGeneral::GetRandomNumber() % 40 + 568; + menuBg.topRight_y = CGeneral::GetRandomNumber() % 40 + 44; + menuBg.bottomLeft_x = CGeneral::GetRandomNumber() % 40 + 36; + menuBg.bottomLeft_y = CGeneral::GetRandomNumber() % 40 + 382; + menuBg.bottomRight_x = CGeneral::GetRandomNumber() % 40 + 593; + menuBg.bottomRight_y = CGeneral::GetRandomNumber() % 40 + 342; + break; + } + + menuBg.UpdateMultipliers(); + if (m_firstStartCounter == 255) + m_nOptionHighlightTransitionBlend = 0; + } + + static uint32 LastFade = 0; + + if (m_nMenuFadeAlpha < 255) { + static uint8 forceFadeInCounter = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - LastFade > 30 + || forceFadeInCounter > 30 + ) { + m_nMenuFadeAlpha += 20; + if (m_firstStartCounter < 255) { + m_firstStartCounter = Min(m_firstStartCounter + 20, 255); + } + LastFade = CTimer::GetTimeInMillisecondsPauseMode(); +#ifdef FIX_BUGS + forceFadeInCounter = 0; +#endif + } +#ifdef FIX_BUGS + forceFadeInCounter += CTimer::GetLogicalFramesPassed(); +#else + forceFadeInCounter++; +#endif + } else if (m_nMenuFadeAlpha > 255) + m_nMenuFadeAlpha = 255; + + if (!transitionCall && m_firstStartCounter == 255) { + int actualAlpha = m_nMenuFadeAlpha; + if (actualAlpha < 255) { + int actualScreen = m_nCurrScreen; + SetFrontEndRenderStates(); + m_nCurrScreen = m_nPrevScreen; + m_nMenuFadeAlpha = 255 - m_nMenuFadeAlpha; + switch (m_nCurrScreen) { + case MENUPAGE_SKIN_SELECT: + DrawPlayerSetupScreen(false); + break; + case MENUPAGE_KEYBOARD_CONTROLS: + DrawControllerSetupScreen(); + break; + case MENUPAGE_OUTRO: + DrawQuitGameScreen(); + break; + default: + DrawStandardMenus(false); + break; + } + m_nCurrScreen = actualScreen; + m_nMenuFadeAlpha = actualAlpha; + } + } + + switch (m_nCurrScreen) { + case MENUPAGE_SKIN_SELECT: + DrawPlayerSetupScreen(true); + break; + case MENUPAGE_KEYBOARD_CONTROLS: + DrawControllerSetupScreen(); + break; + case MENUPAGE_OUTRO: + DrawQuitGameScreen(); + break; + default: + DrawStandardMenus(true); + break; + } + + CFont::DrawFonts(); + SetFrontEndRenderStates(); + + if (m_nCurrScreen != MENUPAGE_OUTRO) + if (m_firstStartCounter == 255) { + m_aFrontEndSprites[MENUSPRITE_VCLOGO].Draw(CRect(SCREEN_STRETCH_X(27.0f), MENU_Y(8.0f), SCREEN_STRETCH_X(27.0f) + MENU_X(130.f), MENU_Y(138.0f)), CRGBA(255, 255, 255, 255)); + } else { + m_aFrontEndSprites[MENUSPRITE_VCLOGO].Draw(CRect(SCREEN_STRETCH_X(27.0f), MENU_Y(8.0f), SCREEN_STRETCH_X(27.0f) + MENU_X(130.f), MENU_Y(138.0f)), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (m_ShowEmptyBindingError) { + static uint32 lastBindingError = CTimer::GetTimeInMillisecondsPauseMode(); + static bool bindingErrorShown = false; + if (bindingErrorShown) { + lastBindingError = CTimer::GetTimeInMillisecondsPauseMode(); + bindingErrorShown = false; + } + SmallMessageScreen("FEC_ERI"); + CFont::DrawFonts(); + if (CTimer::GetTimeInMillisecondsPauseMode() - lastBindingError > 4000) { + m_ShowEmptyBindingError = false; + bindingErrorShown = true; + } + } + + if (m_bShowMouse) { + CRect mouse(0.0f, 0.0f, MENU_X(35.0f), MENU_Y(35.0f)); + CRect shad(MENU_X(10.0f), MENU_Y(3.0f), MENU_X(45.0f), MENU_Y(38.0f)); + + mouse.Translate(m_nMousePosX, m_nMousePosY); + shad.Translate(m_nMousePosX, m_nMousePosY); + m_aFrontEndSprites[MENUSPRITE_MOUSE].Draw(shad, CRGBA(100, 100, 100, 50)); + m_aFrontEndSprites[MENUSPRITE_MOUSE].Draw(mouse, CRGBA(255, 255, 255, 255)); + } +} + +void +CMenuManager::DrawPlayerSetupScreen(bool activeScreen) +{ + RESET_FONT_FOR_NEW_PAGE + + // 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))); + + SET_FONT_FOR_MENU_HEADER + CFont::SetColor(CRGBA(30, 30, 30, FadeIn(255))); + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(MENUHEADER_POS_X) - MENU_X(7.f), SCREEN_SCALE_Y(MENUHEADER_POS_Y + 7.f), TheText.Get("FET_PS")); + + CFont::SetColor(CRGBA(HEADER_COLOR.r, HEADER_COLOR.g, HEADER_COLOR.b, FadeIn(255))); + CFont::PrintString(SCREEN_STRETCH_FROM_RIGHT(MENUHEADER_POS_X), SCREEN_SCALE_Y(MENUHEADER_POS_Y), TheText.Get("FET_PS")); + + // 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::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + 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")); + CFont::SetDropShadowPosition(0); + + // 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) { + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + SaveSettings(); + } + } else { + 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 - it's unchanged since III and still yellowish... + 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_aFrontEndSprites[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_aFrontEndSprites[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_aFrontEndSprites[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_aFrontEndSprites[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_aFrontEndSprites[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_aFrontEndSprites[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_aFrontEndSprites[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_aFrontEndSprites[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 + + } + if (activeScreen) + 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(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(120))); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(24.0f), MENU_Y(220.0f), TheText.Get("FET_APP")); + } + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + + CFont::SetScale(MENU_X(BIGTEXT_X_SCALE), MENU_Y(BIGTEXT_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) { + + 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) { + + 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(BIGTEXT_X_SCALE), MENU_Y(BIGTEXT_Y_SCALE)); + CFont::SetRightJustifyOn(); + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + + // Back button + CFont::PrintString(MENU_X_RIGHT_ALIGNED(PLAYERSETUP_LIST_RIGHT - 3), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 5), TheText.Get("FEDS_TB")); + CFont::SetRightJustifyOff(); + + if (!strcmp(m_aSkinName, m_PrefsSkinFile)) { + CFont::SetColor(CRGBA(DARKMENUOPTION_COLOR.r, DARKMENUOPTION_COLOR.g, DARKMENUOPTION_COLOR.b, FadeIn(255))); + } else { + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + } + + // Use skin button + CFont::PrintString(MENU_X_LEFT_ALIGNED(PLAYERSETUP_LIST_LEFT), SCREEN_SCALE_FROM_BOTTOM(PLAYERSETUP_LIST_BOTTOM - 5), TheText.Get("FES_SET")); + CFont::SetDropShadowPosition(0); +} + +int +CMenuManager::FadeIn(int alpha) +{ + return Min(m_nMenuFadeAlpha, alpha); +} + +int +CMenuManager::GetStartOptionsCntrlConfigScreens() +{ + int number = 0; + switch (m_nCurrScreen) { +#ifdef LEGACY_MENU_OPTIONS + case MENUPAGE_CONTROLLER_PC_OLD3: + number = 34; + break; + case MENUPAGE_CONTROLLER_DEBUG: + number = 35; + break; +#endif + 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; + + // First icon is hidden behind arrow + m_LeftMostRadioX = MENU_X_LEFT_ALIGNED(MENURADIO_ICON_FIRST_X - MENURADIO_ICON_SIZE); + CTimer::Stop(); + + CStreaming::MakeSpaceFor(350 * CDSTREAM_SECTOR_SIZE); // twice of it in mobile + CStreaming::ImGonnaUseStreamingMemory(); + CGame::TidyUpMemory(false, true); + CTxdStore::PushCurrentTxd(); + int frontendTxdSlot1 = CTxdStore::FindTxdSlot("frontend1"); + + if(frontendTxdSlot1 == -1) + frontendTxdSlot1 = CTxdStore::AddTxdSlot("frontend1"); + + printf("LOAD frontend1\n"); + CTxdStore::LoadTxd(frontendTxdSlot1, "MODELS/FRONTEN1.TXD"); + CTxdStore::AddRef(frontendTxdSlot1); + CTxdStore::SetCurrentTxd(frontendTxdSlot1); + + for (int i = 0; i < 3; i++) { + m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } + + CTxdStore::PopCurrentTxd(); + CStreaming::IHaveUsedStreamingMemory(); + + if (!m_OnlySaveMenu) { + CStreaming::MakeSpaceFor(692 * CDSTREAM_SECTOR_SIZE); // twice of it in mobile + CStreaming::ImGonnaUseStreamingMemory(); + CTxdStore::PushCurrentTxd(); + + int frontendTxdSlot2 = CTxdStore::FindTxdSlot("frontend2"); + + if (frontendTxdSlot2 == -1) + frontendTxdSlot2 = CTxdStore::AddTxdSlot("frontend2"); + + printf("LOAD frontend2\n"); + CTxdStore::LoadTxd(frontendTxdSlot2, "MODELS/FRONTEN2.TXD"); + CTxdStore::AddRef(frontendTxdSlot2); + CTxdStore::SetCurrentTxd(frontendTxdSlot2); + +#ifdef GAMEPAD_MENU + for (int i = 3; i < MENUSPRITE_CONTROLLER; i++) { +#else + for (int i = 3; i < NUM_MENU_SPRITES; i++) { +#endif + m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } + + CTxdStore::PopCurrentTxd(); +#ifdef GAMEPAD_MENU + LoadController(m_PrefsControllerType); +#endif + CStreaming::IHaveUsedStreamingMemory(); + } + + m_bSpritesLoaded = true; + CTimer::Update(); +} + +void +CMenuManager::LoadSettings() +{ + CFileMgr::SetDirMyDocuments(); + int fileHandle = CFileMgr::OpenFile("gta_vc.set", "r"); + + int32 prevLang = m_PrefsLanguage; + MousePointerStateHelper.bInvertVertically = true; + CMBlur::BlurOn = false; + + // 50 is silly + char headerText[50]; + int someVersion = 0; + bool fileIsValid = true; + + if (fileHandle) { + CFileMgr::Read(fileHandle, headerText, 29); + + if (strncmp(headerText, TopLineEmptyFile, sizeof(TopLineEmptyFile) - 1) == 0) { + fileIsValid = false; + } else { + CFileMgr::Seek(fileHandle, 0, 0); + CFileMgr::Read(fileHandle, (char*)&someVersion, sizeof(someVersion)); + } + if (fileIsValid && someVersion >= 3) { + 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); +#ifdef LEGACY_MENU_OPTIONS + CFileMgr::Read(fileHandle, (char*)&m_PrefsVsyncDisp, 1); + CFileMgr::Read(fileHandle, (char*)&CMBlur::BlurOn, 1); +#else + CFileMgr::Read(fileHandle, gString, 1); + CFileMgr::Read(fileHandle, gString, 1); +#endif + CFileMgr::Read(fileHandle, (char*)&TheCamera.m_bHeadBob, 1); + CFileMgr::Read(fileHandle, (char*)&TheCamera.m_fMouseAccelHorzntl, 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_PrefsMP3BoostVolume, 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, 2); + CFileMgr::Read(fileHandle, (char*)&m_PrefsLOD, sizeof(m_PrefsLOD)); + CFileMgr::Read(fileHandle, (char*)&m_PrefsShowSubtitles, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsUseWideScreen, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsFrameLimiter, 1); + CFileMgr::Read(fileHandle, (char*)&m_nDisplayVideoMode, 1); + CFileMgr::Read(fileHandle, m_PrefsSkinFile, 256); + CFileMgr::Read(fileHandle, (char*)&m_ControlMethod, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsLanguage, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsShowHud, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsRadarMode, 1); + CFileMgr::Read(fileHandle, (char*)&m_PrefsShowLegends, 1); + } + } + + CFileMgr::CloseFile(fileHandle); + CFileMgr::SetDir(""); + +#ifdef LOAD_INI_SETTINGS + if (LoadINISettings()) { + LoadINIControllerSettings(); + } +#endif + +#ifdef FIX_BUGS + TheCamera.m_fMouseAccelVertical = TheCamera.m_fMouseAccelHorzntl + 0.0005f; +#endif +#ifdef PC_PLAYER_CONTROLS + CCamera::m_bUseMouse3rdPerson = m_ControlMethod == CONTROL_STANDARD; +#endif +#ifdef LEGACY_MENU_OPTIONS + m_PrefsVsync = m_PrefsVsyncDisp; +#endif + CRenderer::ms_lodDistScale = m_PrefsLOD; + + if (m_nPrefsAudio3DProviderIndex == NO_AUDIO_PROVIDER) + m_nPrefsAudio3DProviderIndex = -2; + + m_lastWorking3DAudioProvider = m_nPrefsAudio3DProviderIndex; + + if (m_PrefsLanguage == prevLang) + m_bLanguageLoaded = false; + else { + m_bLanguageLoaded = true; + 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"; +#ifdef BIND_VEHICLE_FIREWEAPON + static int SomeVersion = 4; +#else + static int SomeVersion = 3; +#endif + + CFileMgr::SetDirMyDocuments(); + + int fileHandle = CFileMgr::OpenFile("gta_vc.set", "w+"); + if (fileHandle) { + CFileMgr::Write(fileHandle, (char*)&SomeVersion, sizeof(SomeVersion)); + 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); +#ifdef LEGACY_MENU_OPTIONS + CFileMgr::Write(fileHandle, (char*)&m_PrefsVsyncDisp, 1); + CFileMgr::Write(fileHandle, (char*)&CMBlur::BlurOn, 1); +#else + CFileMgr::Write(fileHandle, RubbishString, 1); + CFileMgr::Write(fileHandle, RubbishString, 1); +#endif + CFileMgr::Write(fileHandle, (char*)&TheCamera.m_bHeadBob, 1); + CFileMgr::Write(fileHandle, (char*)&TheCamera.m_fMouseAccelHorzntl, 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_PrefsMP3BoostVolume, 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, 2); + 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_PrefsFrameLimiter, 1); + CFileMgr::Write(fileHandle, (char*)&m_nPrefsVideoMode, 1); + CFileMgr::Write(fileHandle, m_PrefsSkinFile, 256); + CFileMgr::Write(fileHandle, (char*)&m_ControlMethod, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsLanguage, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsShowHud, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsRadarMode, 1); + CFileMgr::Write(fileHandle, (char*)&m_PrefsShowLegends, 1); + } + m_lastWorking3DAudioProvider = m_nPrefsAudio3DProviderIndex; + + CFileMgr::CloseFile(fileHandle); + CFileMgr::SetDir(""); + +#else + m_lastWorking3DAudioProvider = m_nPrefsAudio3DProviderIndex; + SaveINISettings(); +#endif +} + +void +CMenuManager::MessageScreen(const char *text, bool blackBg) +{ + CSprite2d *splash = LoadSplash(nil); + if (!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; + + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + // CRGBA unused(255, 255, 255, 255); + if (blackBg) { + CSprite2d::DrawRect(CRect(0, SCREEN_HEIGHT, SCREEN_WIDTH, 0), CRGBA(0, 0, 0, 255)); + } + + m_nMenuFadeAlpha = 255; + SmallMessageScreen(text); + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +CMenuManager::SmallMessageScreen(const char* text) +{ + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); + CSprite2d::DrawRect(CRect(SCREEN_STRETCH_X(95.0f), SCREEN_SCALE_FROM_BOTTOM(165.0f), SCREEN_STRETCH_FROM_RIGHT(95.0f), SCREEN_SCALE_Y(115.0f)), CRGBA(50, 50, 50, FadeIn(210))); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetCentreSize(SCREEN_SCALE_X(430.0f)); + CFont::SetCentreOn(); + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetScale(SCREEN_SCALE_X(SMALLTEXT_X_SCALE), SCREEN_SCALE_Y(SMALLTEXT_Y_SCALE)); + + int numOfLines = CFont::GetNumberLines(SCREEN_WIDTH / 2.f, SCREEN_SCALE_Y(135.f), TheText.Get(text)); + float y; + if (numOfLines > 1) + y = SCREEN_SCALE_Y(192.f) - numOfLines * SCREEN_SCALE_Y(8.f); + else + y = SCREEN_SCALE_Y(182.f); + + CFont::PrintString(SCREEN_WIDTH / 2.f, y, TheText.Get(text)); +} + +void +CMenuManager::PrintBriefs() +{ + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetRightJustifyOff(); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetScale(MENU_X(MEDIUMTEXT_X_SCALE), MENU_Y(MEDIUMTEXT_Y_SCALE)); + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(80.0f)); + CFont::SetDropShadowPosition(0); + + float nextY = BRIEFS_BOTTOM_MARGIN; + for (int i = 4; i >= 0; i--) { + if (nextY < BRIEFS_TOP_MARGIN) + break; + + 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); + CFont::FilterOutTokensFromString(gUString); + + nextY -= CFont::GetNumberLines(MENU_X_LEFT_ALIGNED(BRIEFS_LINE_X), nextY, gUString) * BRIEFS_LINE_HEIGHT + BRIEFS_LINE_SPACING; + CFont::PrintString(MENU_X_LEFT_ALIGNED(BRIEFS_LINE_X), MENU_Y(nextY), gUString); + } + } +} + +void +CMenuManager::PrintStats() +{ +#ifdef SECUROM + static uint8 statsPirateCheck = 0; +#endif + static float scrollY = 0; + + int rowNum = CStats::ConstructStatLine(99999); + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(90.0f), MENU_Y(142.0f), + MENU_X_LEFT_ALIGNED(543.0f), MENU_Y(142.f), + MENU_X_LEFT_ALIGNED(107.0f), MENU_Y(316.f), + MENU_X_LEFT_ALIGNED(531.f), MENU_Y(299.f), CRGBA(LIST_BACKGROUND_COLOR.r, LIST_BACKGROUND_COLOR.g, LIST_BACKGROUND_COLOR.b, FadeIn(LIST_BACKGROUND_COLOR.a))); + + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetPropOn(); + CFont::SetDropShadowPosition(0); + +#ifdef SECUROM + if (statsPirateCheck == 0) + // if not pirated game + // statsPirateCheck = 46; + // else + statsPirateCheck = 45; +#endif + + if (m_PrefsLanguage == LANGUAGE_AMERICAN) + CFont::SetScale(MENU_X(0.43f), MENU_Y(0.75f)); + else + CFont::SetScale(MENU_X(0.37f), MENU_Y(0.75f)); + + static uint32 lastCheck = 0; + + if (CTimer::GetTimeInMillisecondsPauseMode() - lastCheck > 40) { + + if (m_StatsScrollSpeed > 0.f) { + if (m_StatsScrollDirection == 0) + scrollY -= MENU_Y(100.f) / m_StatsScrollSpeed; + else + scrollY += MENU_Y(100.f) / m_StatsScrollSpeed; + } + lastCheck = CTimer::GetTimeInMillisecondsPauseMode(); + } + +#ifdef SECUROM + if (statsPirateCheck == 45) + return; +#endif + + float nextYChange, y, alpha; + + float totalHeight = (rowNum + 7) * STATS_ROW_HEIGHT; + for (int row = 0; row < rowNum; ++row) { + // Put faded away text at the top back to the bottom, in circular fashion + for (y = MENU_Y(STATS_ROW_HEIGHT) * row + MENU_Y(100.f) - scrollY; MENU_Y(STATS_FADING_AREA_LENGTH) > y; y += nextYChange) { + nextYChange = MENU_Y(totalHeight); + } + + // Put faded away text at the bottom back to the top + while (SCREEN_SCALE_FROM_BOTTOM(STATS_FADING_AREA_LENGTH) < y) { + y -= MENU_Y(totalHeight); + } + alpha = 0.f; + + // If it's still on screen + if (y > MENU_Y(STATS_VISIBLE_START_Y) && y < MENU_Y(STATS_VISIBLE_END_Y)) { + CStats::ConstructStatLine(row); + + // But about to dim from bottom + if (y < MENU_Y(STATS_BOTTOM_Y)) { + if (y > MENU_Y(STATS_BOTTOM_Y - STATS_FADING_AREA_LENGTH)) + alpha = (MENU_Y(STATS_BOTTOM_Y) - y) * 5.f; + } + + // About to dim from top + if (y > MENU_Y(STATS_TOP_Y)) { + if (y < MENU_Y(STATS_TOP_Y + STATS_FADING_AREA_LENGTH)) + alpha = (y - MENU_Y(STATS_TOP_Y)) * 5.f; + } + + // Content + if (y >= MENU_Y(STATS_TOP_Y + STATS_FADING_AREA_LENGTH) && y <= MENU_Y(STATS_BOTTOM_Y - STATS_FADING_AREA_LENGTH)) + alpha = 255.0f; + + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(Min(255.f, alpha)))); + CFont::SetRightJustifyOff(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(STATS_ROW_LEFT_MARGIN), y, gUString); + CFont::SetRightJustifyOn(); + CFont::PrintString(MENU_X_RIGHT_ALIGNED(STATS_ROW_RIGHT_MARGIN), y, gUString2); + } + } + CFont::SetColor(CRGBA(MENUOPTION_COLOR.r, MENUOPTION_COLOR.g, MENUOPTION_COLOR.b, FadeIn(255))); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetCentreOn(); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(MENU_X(0.65f), MENU_Y(1.05f)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(STATS_RATING_X), MENU_Y(STATS_RATING_Y_1), TheText.Get("CRIMRA")); + + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + + // FIX: Game does that in a weird way, alignment and spacing is now ok + + sprintf(gString, "(%d)", CStats::FindCriminalRatingNumber()); + AsciiToUnicode(gString, gUString); + + UnicodeStrcpy(gUString2, CStats::FindCriminalRatingString()); + UnicodeStrcat(gUString2, gUString); + + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); +#ifndef FIX_BUGS + CFont::SetScale(MENU_X(0.5f), MENU_Y(0.9f)); +#else + CFont::SetScale(MENU_X(SMALLTEXT_X_SCALE), MENU_Y(SMALLTEXT_Y_SCALE)); +#endif + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetDropShadowPosition(0); + + CFont::PrintString(MENU_X_LEFT_ALIGNED(STATS_RATING_X) - CFont::GetStringWidth(gUString2, true) / 2.f, MENU_Y(STATS_RATING_Y_2), gUString2); +} + +void +CMenuManager::Process(void) +{ +#ifdef XBOX_MESSAGE_SCREEN + ProcessDialogTimer(); +#endif + + if (TheCamera.GetScreenFadeStatus() != FADE_0) + return; + + InitialiseChangedLanguageSettings(); + + if (m_bMenuActive) { + UserInput(); + ProcessFileActions(); + DMAudio.Service(); +#ifdef USE_TEXTURE_POOL + // TODO +#endif + } + + SwitchMenuOnAndOff(); +} + +#ifdef MAP_ENHANCEMENTS +#define ZOOM(x, y, in) \ + do { \ + if(m_fMapSize >= MENU_Y(1000.0f) && in) \ + break; \ + float z2 = in? 1.1f : 1.f/1.1f; \ + m_fMapCenterX += (x - m_fMapCenterX) * (1.0f - z2); \ + m_fMapCenterY += (y - m_fMapCenterY) * (1.0f - z2); \ + \ + if (m_fMapSize <= MENU_Y(MAP_MIN_SIZE) && !in) \ + break; \ + \ + m_fMapSize *= z2; \ + m_fMapCenterX = Clamp(m_fMapCenterX, SCREEN_WIDTH/2 - (m_fMapSize - MENU_X(MAP_MIN_SIZE)), m_fMapSize - MENU_X(MAP_MIN_SIZE) + SCREEN_WIDTH/2); \ + m_fMapCenterY = Clamp(m_fMapCenterY, SCREEN_HEIGHT/2 - (m_fMapSize - MENU_Y(MAP_MIN_SIZE)), m_fMapSize - MENU_Y(MAP_MIN_SIZE) + SCREEN_HEIGHT/2); \ + } while(0) + +#endif + +// Handles Map, Audio and Stats +void +CMenuManager::AdditionalOptionInput(bool &goBack) +{ + switch (m_nCurrScreen) { + case MENUPAGE_MAP: + { + static uint32 lastMapTick = 0; + + // FIX: All those macros were hardcoded values originally. + +#ifndef MAP_ENHANCEMENTS + if (CPad::GetPad(0)->GetMouseWheelUpJustDown() || CPad::GetPad(0)->GetMouseWheelUpJustUp() || CPad::GetPad(0)->GetPageUp() || CPad::GetPad(0)->GetRightShoulder1()) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastMapTick > 10) { + m_fMapSize = Min(MENU_Y(1000.0f), m_fMapSize + MENU_Y(15.f)); + } + } + if (CPad::GetPad(0)->GetMouseWheelDownJustDown() || CPad::GetPad(0)->GetMouseWheelDownJustUp() || CPad::GetPad(0)->GetPageDown() || CPad::GetPad(0)->GetRightShoulder2()) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastMapTick > 10) { + if (m_fMapSize > MENU_Y(MAP_MIN_SIZE)) { + if (m_fMapCenterY > SCREEN_HEIGHT/2) + m_fMapCenterY -= (m_fMapCenterY - SCREEN_HEIGHT/2) / ((m_fMapSize - MENU_Y(MAP_MIN_SIZE)) * 1/15.f); + + if (m_fMapCenterY < SCREEN_HEIGHT/2) + m_fMapCenterY += (SCREEN_HEIGHT/2 - m_fMapCenterY) / ((m_fMapSize - MENU_Y(MAP_MIN_SIZE)) * 1/15.f); + + if (m_fMapCenterX > SCREEN_WIDTH/2) + m_fMapCenterX -= (m_fMapCenterX - SCREEN_WIDTH/2) / ((m_fMapSize - MENU_X(MAP_MIN_SIZE)) * 1/15.f); + + if (m_fMapCenterX < SCREEN_WIDTH/2) + m_fMapCenterX += (SCREEN_WIDTH/2 - m_fMapCenterX) / ((m_fMapSize - MENU_X(MAP_MIN_SIZE)) * 1/15.f); + + m_fMapSize = Max(MENU_Y(MAP_MIN_SIZE), m_fMapSize - MENU_Y(15.f)); + m_fMapCenterX = Clamp(m_fMapCenterX, SCREEN_WIDTH/2 - (m_fMapSize - MENU_X(MAP_MIN_SIZE)), m_fMapSize - MENU_X(MAP_MIN_SIZE) + SCREEN_WIDTH/2); + m_fMapCenterY = Clamp(m_fMapCenterY, SCREEN_HEIGHT/2 - (m_fMapSize - MENU_Y(MAP_MIN_SIZE)), m_fMapSize - MENU_Y(MAP_MIN_SIZE) + SCREEN_HEIGHT/2); + } else { + m_fMapSize = MENU_Y(MAP_MIN_SIZE); + } + } + } +#else + // Adding marker + if (m_nMenuFadeAlpha == 255) { + if (CPad::GetPad(0)->GetRightMouseJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + if (mapCrosshair.y > m_fMapCenterY - m_fMapSize && mapCrosshair.y < m_fMapCenterY + m_fMapSize && + mapCrosshair.x > m_fMapCenterX - m_fMapSize && mapCrosshair.x < m_fMapCenterX + m_fMapSize) { + + // Don't ask me the meanings, I don't know. Found them by trying + float diffX = m_fMapCenterX - m_fMapSize, diffY = m_fMapCenterY - m_fMapSize; + float x = ((mapCrosshair.x - diffX) / (m_fMapSize * 2)) * (WORLD_SIZE_X / MENU_MAP_WIDTH_SCALE) - (WORLD_SIZE_X / 2 + MENU_MAP_LEFT_OFFSET * MENU_MAP_LENGTH_UNIT); + float y = (WORLD_SIZE_Y / 2 - MENU_MAP_TOP_OFFSET * MENU_MAP_LENGTH_UNIT) - ((mapCrosshair.y - diffY) / (m_fMapSize * 2)) * (WORLD_SIZE_Y / MENU_MAP_HEIGHT_SCALE); + CRadar::ToggleTargetMarker(x, y); + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_ENTER_OR_ADJUST, 0); + } + } + } + + if (CPad::GetPad(0)->GetMouseWheelDown() || CPad::GetPad(0)->GetPageDown() || CPad::GetPad(0)->GetRightShoulder2()) { + if (CPad::GetPad(0)->GetMouseWheelDown() && m_fMapSize > MENU_X(MAP_SIZE_TO_ALLOW_X_MOVE)) + 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); + } + + static bool justResetPointer = false; + if (CPad::GetPad(0)->GetLeftMouse()) { + if (!justResetPointer) { + m_fMapCenterX += m_nMousePosX - m_nMouseOldPosX; + m_fMapCenterY += m_nMousePosY - m_nMouseOldPosY; + m_fMapCenterX = Clamp(m_fMapCenterX, SCREEN_WIDTH/2 - (m_fMapSize - MENU_X(MAP_MIN_SIZE)), m_fMapSize - MENU_X(MAP_MIN_SIZE) + SCREEN_WIDTH/2); + m_fMapCenterY = Clamp(m_fMapCenterY, SCREEN_HEIGHT/2 - (m_fMapSize - MENU_Y(MAP_MIN_SIZE)), m_fMapSize - MENU_Y(MAP_MIN_SIZE) + SCREEN_HEIGHT/2); + } + justResetPointer = false; + + } else +#undef ZOOM +#endif + + { + // This is else block of GetLeftMouse() if MAP_ENHANCEMENTS defined, so all of GetLeftMouse() conditions below being rendered useless. + + if (CPad::GetPad(0)->GetLeftMouse() && m_nMousePosY < m_nMouseOldPosY || CPad::GetPad(0)->GetUp() || + CPad::GetPad(0)->GetDPadUp() || CPad::GetPad(0)->GetAnalogueUpDown() < 0) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastMapTick > 10) { + if ((m_fMapSize - MENU_Y(MAP_MIN_SIZE)) + SCREEN_HEIGHT/2 > m_fMapCenterY) + m_fMapCenterY += MENU_Y(15.f); + m_bShowMouse = false; + } + } + + if (CPad::GetPad(0)->GetLeftMouse() && m_nMousePosY > m_nMouseOldPosY || CPad::GetPad(0)->GetDown() || + CPad::GetPad(0)->GetDPadDown() || CPad::GetPad(0)->GetAnalogueUpDown() > 0) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastMapTick > 10) { + if (SCREEN_HEIGHT/2 - (m_fMapSize - MENU_Y(MAP_MIN_SIZE)) < m_fMapCenterY) + m_fMapCenterY -= MENU_Y(15.f); + m_bShowMouse = false; + } + } + + if (CPad::GetPad(0)->GetLeftMouse() && m_nMousePosX < m_nMouseOldPosX || CPad::GetPad(0)->GetLeft() || + CPad::GetPad(0)->GetDPadLeft() || CPad::GetPad(0)->GetAnalogueLeftRight() < 0) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastMapTick > 10) { + if (m_fMapSize > MENU_X(MAP_SIZE_TO_ALLOW_X_MOVE) && m_fMapSize - MENU_X(MAP_MIN_SIZE) + SCREEN_WIDTH/2 > m_fMapCenterX) + m_fMapCenterX += MENU_X(15.f); + m_bShowMouse = false; + } + } + + if (CPad::GetPad(0)->GetLeftMouseJustUp()) { + // The coordinates in aScreens->MENUPAGE_MAP. + if (m_nMousePosX > MENU_X_LEFT_ALIGNED(60.0f) && m_nMousePosX < MENU_X_LEFT_ALIGNED(140.0f)) { + if (m_nMousePosY > MENU_Y(375.0f) && m_nMousePosY < MENU_Y(400.0f)) { + m_nHoverOption = HOVEROPTION_RANDOM_ITEM; + goBack = true; + } + } + } + + if (CPad::GetPad(0)->GetLeftMouse() && m_nMousePosX > m_nMouseOldPosX || CPad::GetPad(0)->GetRight() || + CPad::GetPad(0)->GetDPadRight() || CPad::GetPad(0)->GetAnalogueLeftRight() > 0) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastMapTick > 10) { + if (m_fMapSize > MENU_X(MAP_SIZE_TO_ALLOW_X_MOVE) && SCREEN_WIDTH/2 - (m_fMapSize - MENU_X(MAP_MIN_SIZE)) < m_fMapCenterX) + m_fMapCenterX -= MENU_X(15.f); + m_bShowMouse = false; + } + } + } + + + if (CTimer::GetTimeInMillisecondsPauseMode() - lastMapTick > 10) + lastMapTick = CTimer::GetTimeInMillisecondsPauseMode(); + +#ifndef MAP_ENHANCEMENTS + if (CPad::GetPad(0)->GetLeftMouseJustUp()) + CentreMousePointer(); +#endif + + if (CPad::GetPad(0)->GetLeftMouse()) { + if (m_nMousePosX < SCREEN_STRETCH_X(20.0f) || m_nMousePosX > SCREEN_STRETCH_X(620.0f) || m_nMousePosY < SCREEN_STRETCH_Y(20.0f) || m_nMousePosY > SCREEN_STRETCH_Y(428.0f)) { +#ifdef MAP_ENHANCEMENTS + justResetPointer = true; +#endif + CentreMousePointer(); + } + } + if (!CPad::GetPad(0)->GetLeftMouse() && !m_bShowMouse && (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY)) { + m_bShowMouse = true; + } + + static bool pressedL = false; + + if (!CPad::GetPad(0)->GetChar('L') && !CPad::GetPad(0)->GetChar('l')) { + pressedL = false; + } + + if (!pressedL) { + if (CPad::GetPad(0)->GetChar('L') || CPad::GetPad(0)->GetChar('l')) { + m_PrefsShowLegends = !m_PrefsShowLegends; + pressedL = true; + } + } + break; + } + case MENUPAGE_SOUND_SETTINGS: + if (CheckHover(MENU_X_LEFT_ALIGNED(177.f), MENU_X_LEFT_ALIGNED(238.f), MENU_Y(MENURADIO_SELECTOR_START_Y - 13.f), MENU_Y(MENURADIO_SELECTOR_START_Y + MENURADIO_SELECTOR_HEIGHT - 8.f))) { + m_nHoverOption = HOVEROPTION_PREV_RADIO; + } + + if (CheckHover(MENU_X_LEFT_ALIGNED(422.f), MENU_X_LEFT_ALIGNED(491.f), MENU_Y(MENURADIO_SELECTOR_START_Y - 13.f), MENU_Y(MENURADIO_SELECTOR_START_Y + MENURADIO_SELECTOR_HEIGHT - 8.f))) { + m_nHoverOption = HOVEROPTION_NEXT_RADIO; + } + break; + case MENUPAGE_STATS: + { + if (CPad::GetPad(0)->GetMouseWheelUpJustDown() || CPad::GetPad(0)->GetMouseWheelUpJustUp() || CPad::GetPad(0)->GetUp() || + CPad::GetPad(0)->GetDPadUp() || CPad::GetPad(0)->GetAnalogueUpDown() < 0) { + + m_StatsScrollSpeed = 20.0f; + m_StatsScrollDirection = 0; + + } else if (CPad::GetPad(0)->GetMouseWheelDownJustDown() || CPad::GetPad(0)->GetMouseWheelDownJustUp() || CPad::GetPad(0)->GetDown() || + CPad::GetPad(0)->GetDPadDown() || CPad::GetPad(0)->GetAnalogueUpDown() > 0) { + + m_StatsScrollSpeed = 20.0f; + m_StatsScrollDirection = 1; + + } else if (CPad::GetPad(0)->GetChar(' ')) { + m_StatsScrollSpeed = 0.0f; + } else + m_StatsScrollSpeed = 150.0f; + + static bool pressedS = false; + + if (!CPad::GetPad(0)->GetChar('S') && !CPad::GetPad(0)->GetChar('s')) { + pressedS = false; + } + + if (!pressedS) { + if (CPad::GetPad(0)->GetChar('S') || CPad::GetPad(0)->GetChar('s')) { + ExportStats(); + m_nHelperTextMsgId = 4; + m_nHelperTextAlpha = 300; + pressedS = true; + } + } + break; + } + } +} + +// Not original name +void +CMenuManager::ExportStats() +{ + char date[10]; + CFileMgr::SetDirMyDocuments(); + _strdate(date); + wchar *lastMission = TheText.Get(CStats::LastMissionPassedName[0] == '\0' ? "ITBEG" : CStats::LastMissionPassedName); + FILE *txtFile = fopen("stats.txt", "w"); + + if (txtFile) { + int statLines = CStats::ConstructStatLine(99999); + fprintf(txtFile, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n"); + fprintf(txtFile, "\t\t\tGTA VICE CITY %s\n", UnicodeToAscii(TheText.Get("FEH_STA"))); + fprintf(txtFile, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\n\n"); + fprintf(txtFile, "%s: ", UnicodeToAscii(TheText.Get("FES_CMI"))); + fprintf(txtFile, "%s\n", UnicodeToAscii(lastMission)); + fprintf(txtFile, "%s: ", UnicodeToAscii(TheText.Get("FES_DAT"))); + fprintf(txtFile, "%s\n\n\n", date); + fprintf(txtFile, "%s ", UnicodeToAscii(TheText.Get("CRIMRA"))); + UnicodeStrcpy(gUString, CStats::FindCriminalRatingString()); + fprintf(txtFile, "%s (%d)\n\n\n", UnicodeToAscii(gUString), CStats::FindCriminalRatingNumber()); + for (int i = 0; i < statLines; ++i) { + CStats::ConstructStatLine(i); + char *statKey = UnicodeToAscii(gUString); + if (statKey[0] != '\0') + fprintf(txtFile, "- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n%s\n", statKey); + + char *statValue = UnicodeToAscii(gUString2); + for (int j = 0; statValue[j] != '\0'; ++j) { + if (statValue[j] == '_') + statValue[j] = '\xBA'; // This is degree symbol, but my editors keeps messing up with it so I wrote hex representation + } + if (statValue) + fprintf(txtFile, "%s\n\n", statValue); + } + fprintf(txtFile, "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\n"); + } + fclose(txtFile); + FILE *htmlFile = fopen("stats.html", "w"); + if (htmlFile) { + int statLines = CStats::ConstructStatLine(99999); + fprintf(htmlFile, "Grand Theft Auto Vice City Stats\n"); + fprintf(htmlFile, "\n"); + fprintf(htmlFile, "\n"); + fprintf(htmlFile, " \n"); + fprintf(htmlFile, "\n"); + fprintf(htmlFile, "\n"); + fprintf(htmlFile, " \n"); + fprintf(htmlFile, + "\n", UnicodeToAscii(lastMission)); + fprintf(htmlFile, " \n"); + fprintf(htmlFile, " \n"); + fprintf(htmlFile, "\n", statKey, rating); + + for (int k = 0; k < statLines; ++k) { + CStats::ConstructStatLine(k); + statKey = UnicodeToAscii(gUString); + if (statKey[0] != '\0') + fprintf(htmlFile, "\n"); + + fprintf(htmlFile, "\n"); + fprintf(htmlFile, "
    -------------------------------------------------------------------------
    \n"); + fprintf(htmlFile, "GRAND THEFT AUTO VICE CITY "); + fprintf(htmlFile, "%s
    -------------------------------------------------------------------------
     

    " + "%s: \n", UnicodeToAscii(TheText.Get("FES_DAT"))); + fprintf(htmlFile, "%s
    %s:
    ", date, UnicodeToAscii(TheText.Get("FES_CMI"))); + fprintf(htmlFile, "%s

    %s\n", UnicodeToAscii(TheText.Get("CRIMRA"))); + + UnicodeStrcpy(gUString, CStats::FindCriminalRatingString()); + char *statKey = UnicodeToAscii(gUString); + int rating = CStats::FindCriminalRatingNumber(); + fprintf(htmlFile, "%s (%d)
    \n"); + + if (statKey[0] != '\0') + fprintf(htmlFile, "%s", statKey); + else + fprintf(htmlFile, " "); + + fprintf(htmlFile, "
    \n"); + + char *statValue = UnicodeToAscii(gUString2); + for (int l = 0; statValue[l] != '\0'; ++l) { + if (statValue[l] == '_') + statValue[l] = '\xBA'; // This is degree symbol, but my editors keeps messing up with it so I wrote hex representation + } + if (statValue) + fprintf(htmlFile, "%s", statValue); + else + fprintf(htmlFile, " "); + } + fprintf(htmlFile, "

    "); + fprintf(htmlFile, "\n"); + fprintf(htmlFile, "\n"); + fprintf(htmlFile, "\n"); + fprintf(htmlFile, "
    rockstargames.com/vicecityrockstargames.com rockstarnorth.com
    \n\n"); + } + fclose(htmlFile); + CFileMgr::SetDir(""); +} + +// Original name is unknown +void +CMenuManager::PrintRadioSelector(void) +{ + static uint32 lastRadioChange = 0; + + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(418.f), MENU_Y(MENURADIO_SELECTOR_START_Y + MENURADIO_SELECTOR_HEIGHT), + MENU_X_LEFT_ALIGNED(228.f), MENU_Y(MENURADIO_SELECTOR_START_Y + MENURADIO_SELECTOR_HEIGHT), + MENU_X_LEFT_ALIGNED(428.f), MENU_Y(MENURADIO_SELECTOR_START_Y), + MENU_X_LEFT_ALIGNED(238.f), MENU_Y(MENURADIO_SELECTOR_START_Y), CRGBA(RADIO_SELECTOR_COLOR.r, RADIO_SELECTOR_COLOR.g, RADIO_SELECTOR_COLOR.b, FadeIn(180))); + + int rightMostSprite, rightMostStation; + if (DMAudio.IsMP3RadioChannelAvailable()) { + rightMostSprite = MENUSPRITE_MP3; + rightMostStation = USERTRACK; + } else { + rightMostSprite = MENUSPRITE_WAVE; + rightMostStation = WAVE; + } + #ifdef THIS_IS_STUPID + + // First radio + if (m_ScrollRadioBy == 1) { + if (m_PrefsRadioStation == 1) { + m_aFrontEndSprites[rightMostSprite].Draw(m_LeftMostRadioX, MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else if ( m_PrefsRadioStation == 0) { + m_aFrontEndSprites[rightMostSprite - 1].Draw(m_LeftMostRadioX, MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else { + m_aFrontEndSprites[m_PrefsRadioStation + MENUSPRITE_WILDSTYLE - 2].Draw(m_LeftMostRadioX, MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } + } + + // Second + if (m_PrefsRadioStation == 0) { + m_aFrontEndSprites[rightMostSprite].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else { + m_aFrontEndSprites[m_PrefsRadioStation + MENUSPRITE_WILDSTYLE - 1].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), + MENU_Y(MENURADIO_ICON_SIZE), CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } + + // Third (middle) + int prevStation = m_PrefsRadioStation - 1; + if (prevStation == rightMostStation) { + m_aFrontEndSprites[MENUSPRITE_WILDSTYLE + 1].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 3), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else if ( prevStation == rightMostStation - 1) { + m_aFrontEndSprites[MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 3), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else { + m_aFrontEndSprites[m_PrefsRadioStation + MENUSPRITE_WILDSTYLE + 1].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 3), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } + + // Fifth + if (m_ScrollRadioBy == -1) { + int prevStation = m_PrefsRadioStation - 1; + if (prevStation == rightMostStation) { + m_aFrontEndSprites[MENUSPRITE_WILDSTYLE + 4].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 4), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else if (prevStation == rightMostStation - 1) { + m_aFrontEndSprites[MENUSPRITE_WILDSTYLE + 1].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 4), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else if ( prevStation == rightMostStation - 2) { + m_aFrontEndSprites[MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 4), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else { + m_aFrontEndSprites[m_PrefsRadioStation + MENUSPRITE_WILDSTYLE + 2].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 4), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } + } + + // Fourth + if (m_ScrollRadioBy == 0) { + m_aFrontEndSprites[m_PrefsRadioStation + MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 2 - 10.f), MENU_Y(MENURADIO_ICON_Y - 10.f), MENU_X(MENURADIO_ICON_SIZE) + MENU_X(20.f), MENU_Y(MENURADIO_ICON_SIZE) + MENU_Y(20.f), + CRGBA(255, 255, 255, FadeIn(255))); + } else { + if (m_PrefsRadioStation - 1 == rightMostStation) { + m_aFrontEndSprites[MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 2), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else { + m_aFrontEndSprites[m_PrefsRadioStation + MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 2), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } + } +#else + int first = ((m_PrefsRadioStation - 2) + rightMostStation + 1) % (rightMostStation + 1); + int second = ((m_PrefsRadioStation - 1) + rightMostStation + 1) % (rightMostStation + 1); + int third = ((m_PrefsRadioStation) + rightMostStation + 1) % (rightMostStation + 1); + int fourth = ((m_PrefsRadioStation + 1) + rightMostStation + 1) % (rightMostStation + 1); + int fifth = ((m_PrefsRadioStation + 2) + rightMostStation + 1) % (rightMostStation + 1); + + // First one is only drawn on transition to next + if (m_ScrollRadioBy == 1) { + m_aFrontEndSprites[first + MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX, MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } + + // Second + m_aFrontEndSprites[second + MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + + // Fourth + m_aFrontEndSprites[fourth + MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 3), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + + // Fifth one is only drawn on transition to prev. + if (m_ScrollRadioBy == -1) { + m_aFrontEndSprites[fifth + MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 4), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } + + // Middle one(third) is colored differently depending on if it's in transition. + // If not in transition then this icon indicates selected radio, and should be on top of all icons. thus drawn last + if (m_ScrollRadioBy != 0) { + m_aFrontEndSprites[third + MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 2), MENU_Y(MENURADIO_ICON_Y), MENU_X(MENURADIO_ICON_SIZE), MENU_Y(MENURADIO_ICON_SIZE), + CRGBA(INACTIVE_RADIO_COLOR.r, INACTIVE_RADIO_COLOR.g, INACTIVE_RADIO_COLOR.b, FadeIn(INACTIVE_RADIO_COLOR.a))); + } else { + m_aFrontEndSprites[third + MENUSPRITE_WILDSTYLE].Draw(m_LeftMostRadioX + MENU_X(MENURADIO_ICON_SIZE * 2 - 10.f), MENU_Y(MENURADIO_ICON_Y - 10.f), MENU_X(MENURADIO_ICON_SIZE) + MENU_X(20.f), MENU_Y(MENURADIO_ICON_SIZE) + MENU_Y(20.f), + CRGBA(255, 255, 255, FadeIn(255))); + } +#endif + + static bool radioChangeRequested = false; + static uint32 lastScrollCheck = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastScrollCheck > 17) { + if (m_ScrollRadioBy == 1) { + if (m_LeftMostRadioX > MENU_X_LEFT_ALIGNED(MENURADIO_ICON_FIRST_X - MENURADIO_ICON_SIZE)) { + m_LeftMostRadioX -= MENU_X(6.f); + } else { + m_ScrollRadioBy = 0; + lastRadioChange = CTimer::GetTimeInMillisecondsPauseMode(); + radioChangeRequested = true; + } + } + if (m_ScrollRadioBy == -1) { + if (m_LeftMostRadioX < MENU_X_LEFT_ALIGNED(MENURADIO_ICON_FIRST_X - MENURADIO_ICON_SIZE)) { + m_LeftMostRadioX += MENU_X(6.f); + } else { + m_ScrollRadioBy = 0; + lastRadioChange = CTimer::GetTimeInMillisecondsPauseMode(); + radioChangeRequested = true; + } + } + lastScrollCheck = CTimer::GetTimeInMillisecondsPauseMode(); + } + // Background behind arrows + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(228.f), MENU_Y(MENURADIO_SELECTOR_START_Y + MENURADIO_SELECTOR_HEIGHT), + MENU_X_LEFT_ALIGNED(168.f), MENU_Y(MENURADIO_SELECTOR_START_Y + MENURADIO_SELECTOR_HEIGHT), + MENU_X_LEFT_ALIGNED(238.f), MENU_Y(MENURADIO_SELECTOR_START_Y), + MENU_X_LEFT_ALIGNED(178.f), MENU_Y(MENURADIO_SELECTOR_START_Y), + CRGBA(RADIO_SELECTOR_COLOR.r, RADIO_SELECTOR_COLOR.g, RADIO_SELECTOR_COLOR.b, FadeIn(255))); + + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(478.f), MENU_Y(MENURADIO_SELECTOR_START_Y + MENURADIO_SELECTOR_HEIGHT), + MENU_X_LEFT_ALIGNED(418.f), MENU_Y(MENURADIO_SELECTOR_START_Y + MENURADIO_SELECTOR_HEIGHT), + MENU_X_LEFT_ALIGNED(488.f), MENU_Y(MENURADIO_SELECTOR_START_Y), + MENU_X_LEFT_ALIGNED(428.f), MENU_Y(MENURADIO_SELECTOR_START_Y), + CRGBA(RADIO_SELECTOR_COLOR.r, RADIO_SELECTOR_COLOR.g, RADIO_SELECTOR_COLOR.b, FadeIn(255))); + + // Arrows and their shadows + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(216.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 48.f), MENU_X_LEFT_ALIGNED(196.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 36.f), MENU_X_LEFT_ALIGNED(216.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 22.f), MENU_X_LEFT_ALIGNED(196.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 36.f), CRGBA(0, 0, 0, FadeIn(255))); + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(213.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 45.f), MENU_X_LEFT_ALIGNED(193.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 33.f), MENU_X_LEFT_ALIGNED(213.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 19.f), MENU_X_LEFT_ALIGNED(193.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 33.f), CRGBA(97, 194, 247, FadeIn(255))); + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(440.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 48.f), MENU_X_LEFT_ALIGNED(460.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 36.f), MENU_X_LEFT_ALIGNED(440.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 22.f), MENU_X_LEFT_ALIGNED(460.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 36.f), CRGBA(0, 0, 0, FadeIn(255))); + CSprite2d::Draw2DPolygon(MENU_X_LEFT_ALIGNED(443.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 45.f), MENU_X_LEFT_ALIGNED(463.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 33.f), MENU_X_LEFT_ALIGNED(443.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 19.f), MENU_X_LEFT_ALIGNED(463.f), MENU_Y(MENURADIO_SELECTOR_START_Y + 33.f), CRGBA(97, 194, 247, FadeIn(255))); + if (radioChangeRequested) { + if (CTimer::GetTimeInMillisecondsPauseMode() - lastRadioChange > 50) { + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("FRONTEND RADIO STATION CHANGED"); + lastRadioChange = CTimer::GetTimeInMillisecondsPauseMode(); + radioChangeRequested = false; + } + } +} + +// Original name is unknown +void +CMenuManager::ProcessList(bool &optionSelected, bool &goBack) +{ + if (m_bWaitingForNewKeyBind) + return; + + 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 ? 32 : 27; + if (m_nSelectedListRow > m_nTotalListRow) + m_nSelectedListRow = m_nTotalListRow - 1; + } + + if (CPad::GetPad(0)->GetEnterJustDown() || CPad::GetPad(0)->GetCrossJustDown()) { + m_bShowMouse = 0; + optionSelected = true; + } + if (CPad::GetPad(0)->GetBackspaceJustDown() && m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS && !field_159) { + if (m_nCurrExLayer == HOVEROPTION_LIST) { + m_nHoverOption = HOVEROPTION_NOT_HOVERING; + m_bWaitingForNewKeyBind = true; + m_bStartWaitingForKeyBind = true; + m_bKeyChangeNotProcessed = true; + pControlEdit = &m_KeyPressedCode; + } + } else { + field_159 = 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_HIGHLIGHT_OPTION, 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_HIGHLIGHT_OPTION, 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_HIGHLIGHT_OPTION, 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_HIGHLIGHT_OPTION, 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_HIGHLIGHT_OPTION, 0); + PageDownList(false); + } + } + if (CPad::GetPad(0)->GetHome()) { + m_nCurrExLayer = HOVEROPTION_LIST; + m_bShowMouse = false; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_HIGHLIGHT_OPTION, 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_HIGHLIGHT_OPTION, 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; + } + } + + if (CPad::GetPad(0)->GetEscapeJustDown() || CPad::GetPad(0)->GetBackJustDown()) { + m_bShowMouse = false; + goBack = true; + } + + 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) { + 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; + } + } + } +} + +void +CMenuManager::UserInput(void) +{ + bool goBack = false; + bool optionSelected = false; + bool goUp = false; + bool goDown = false; + int8 changeValueBy; + + if (!m_AllowNavigation && m_firstStartCounter == 255) + m_AllowNavigation = true; + if (!m_bShowMouse && m_nCurrScreen != MENUPAGE_MAP && (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY)) { + m_bShowMouse = true; + } + + static int oldOption = -99; + oldOption = m_nCurrOption; +#ifdef SCROLLABLE_PAGES + int firstOption = SCREEN_HAS_AUTO_SCROLLBAR ? m_nFirstVisibleRowOnList : 0; + int scrollOffset = aScreens[m_nCurrScreen].m_aEntries[firstOption].m_Y - aScreens[m_nCurrScreen].m_aEntries[0].m_Y; + for (int rowToCheck = firstOption; rowToCheck < firstOption + MAX_VISIBLE_OPTION && rowToCheck < NUM_MENUROWS; ++rowToCheck) { +#else + for (int rowToCheck = 0; rowToCheck < NUM_MENUROWS; ++rowToCheck) { +#endif + if (aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Action == MENUACTION_NOTHING || + aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Action == MENUACTION_LABEL) + continue; + + // unused: CFont::GetStringWidth(TheText.Get(aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_EntryName), true); + // So they also wanted the compare X, but they abandoned the idea later on + + if (m_nMousePosY > MENU_Y(aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Y MINUS_SCROLL_OFFSET) && + m_nMousePosY < MENU_Y(aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Y MINUS_SCROLL_OFFSET PLUS_LINE_HEIGHT_ON_SCREEN)) { + static int oldScreen = m_nCurrScreen; + + m_nOptionMouseHovering = rowToCheck; + if (m_nMouseOldPosX != m_nMousePosX || m_nMouseOldPosY != m_nMousePosY) { + m_nCurrOption = rowToCheck; + m_bShowMouse = true; + } + + int action = aScreens[m_nCurrScreen].m_aEntries[rowToCheck].m_Action; + if (action != MENUACTION_BRIGHTNESS && action != MENUACTION_DRAWDIST && action != MENUACTION_MUSICVOLUME + && action != MENUACTION_SFXVOLUME && action != MENUACTION_MOUSESENS && action != MENUACTION_MP3VOLUMEBOOST +#ifdef CUSTOM_FRONTEND_OPTIONS + && action != MENUACTION_CFO_SLIDER +#endif + ) + m_nHoverOption = HOVEROPTION_RANDOM_ITEM; + + break; + } + if (m_bShowMouse && m_nMenuFadeAlpha == 255) { + m_nOptionMouseHovering = oldOption; + m_nCurrOption = oldOption; + } + } + + if (m_bShowMouse) { + if (oldOption != m_nCurrOption) { + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_LABEL) { + ++m_nCurrOption; + ++m_nOptionMouseHovering; + } + m_nOptionHighlightTransitionBlend = 0; + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_HIGHLIGHT_OPTION, 0); + } + } + + 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; + + changeValueBy = 0; + if (hasNativeList(m_nCurrScreen)) { + ProcessList(optionSelected, goBack); + } else { + AdditionalOptionInput(goBack); + + if (m_AllowNavigation && + (CPad::GetPad(0)->GetDownJustDown() || CPad::GetPad(0)->GetAnaloguePadDown() || CPad::GetPad(0)->GetDPadDownJustDown())) { + m_bShowMouse = false; + goDown = true; + m_nOptionHighlightTransitionBlend = 0; + + } else if (m_AllowNavigation && + (CPad::GetPad(0)->GetUpJustDown() || CPad::GetPad(0)->GetAnaloguePadUp() || CPad::GetPad(0)->GetDPadUpJustDown())) { + m_bShowMouse = false; + goUp = true; + m_nOptionHighlightTransitionBlend = 0; + } + + 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; + } + } + + if (CPad::GetPad(0)->GetLeftMouseJustUp() && m_nCurrScreen != MENUPAGE_MAP) { + if (m_nHoverOption == HOVEROPTION_RANDOM_ITEM) + optionSelected = true; + else if (m_nHoverOption == HOVEROPTION_NEXT_RADIO) + ChangeRadioStation(1); + else if (m_nHoverOption == HOVEROPTION_PREV_RADIO) + ChangeRadioStation(-1); + } + + if (CPad::GetPad(0)->GetLeftMouse()) { + switch (m_nHoverOption) { + case HOVEROPTION_INCREASE_BRIGHTNESS: + case HOVEROPTION_INCREASE_MP3BOOST: + 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_MP3BOOST: + 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; + } + } + +#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; + + if (oldOption != m_nCurrOption) + m_nOptionHighlightTransitionBlend = 0; + } + + // 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 +#ifdef CUSTOM_FRONTEND_OPTIONS + || option == MENUACTION_CFO_SLIDER +#endif + ) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_ENTER_OR_ADJUST, 0); + else if (option == MENUACTION_SFXVOLUME) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_AUDIO_TEST, 0); + else if (option == MENUACTION_DRAWDIST || option == MENUACTION_MOUSESENS) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_ENTER_OR_ADJUST, 0); + + } + if (CPad::GetPad(0)->GetBackJustDown() || CPad::GetPad(0)->GetEscapeJustDown()) { + if (m_nCurrScreen != MENUPAGE_START_MENU && m_nCurrScreen != MENUPAGE_PAUSE_MENU && m_nCurrScreen != MENUPAGE_CHOOSE_SAVE_SLOT + && m_nCurrScreen != MENUPAGE_SAVE_CHEAT_WARNING && m_nCurrScreen != MENUPAGE_SAVING_IN_PROGRESS + && m_nCurrScreen != MENUPAGE_DELETING_IN_PROGRESS && m_nCurrScreen != MENUPAGE_OUTRO +#ifdef MISSION_REPLAY + && m_nCurrScreen != MENUPAGE_MISSION_RETRY +#endif + ) + { + m_bShowMouse = false; + goBack = true; + } + } + + if (((goDown) || (goUp)) || (optionSelected)) { + goBack = false; + } + + } + + int curAction = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; + if (CPad::GetPad(0)->GetLeft() || CPad::GetPad(0)->GetPedWalkLeftRight() < 0 || CPad::GetPad(0)->GetDPadLeft()) { + static uint32 lastSliderDecrease = 0; + if (CTimer::GetTimeInMillisecondsPauseMode() - lastSliderDecrease > 150) { + if (curAction == MENUACTION_BRIGHTNESS || curAction == MENUACTION_MUSICVOLUME || + curAction == MENUACTION_SFXVOLUME || curAction == MENUACTION_RADIO || + curAction == MENUACTION_DRAWDIST || curAction == MENUACTION_MOUSESENS || + curAction == MENUACTION_MP3VOLUMEBOOST +#ifdef CUSTOM_FRONTEND_OPTIONS + || curAction == MENUACTION_CFO_SLIDER +#endif + ) + changeValueBy = -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) { + if (curAction == MENUACTION_BRIGHTNESS || curAction == MENUACTION_MUSICVOLUME || + curAction == MENUACTION_SFXVOLUME || curAction == MENUACTION_RADIO || + curAction == MENUACTION_DRAWDIST || curAction == MENUACTION_MOUSESENS || + curAction == MENUACTION_MP3VOLUMEBOOST +#ifdef CUSTOM_FRONTEND_OPTIONS + || curAction == MENUACTION_CFO_SLIDER +#endif + ) + changeValueBy = 1; + lastSliderIncrease = CTimer::GetTimeInMillisecondsPauseMode(); + } + } + +#ifdef SCROLLABLE_PAGES + if (!SCREEN_HAS_AUTO_SCROLLBAR) +#endif + { + if (CPad::GetPad(0)->GetMouseWheelUpJustDown()) { + changeValueBy = 1; + } else if (CPad::GetPad(0)->GetMouseWheelDownJustDown()) { + changeValueBy = -1; + } + } + + if (m_AllowNavigation) { + if (CPad::GetPad(0)->GetRightJustDown() || CPad::GetPad(0)->GetAnaloguePadRight() || CPad::GetPad(0)->GetDPadRightJustDown()) { + m_bShowMouse = false; + changeValueBy = 1; + } + } + + if (m_AllowNavigation) { + if (CPad::GetPad(0)->GetLeftJustDown() || CPad::GetPad(0)->GetAnaloguePadLeft() || CPad::GetPad(0)->GetDPadLeftJustDown()) { + m_bShowMouse = false; + changeValueBy = -1; + } + } + if (changeValueBy != 0) { + if ((m_nCurrScreen == MENUPAGE_SOUND_SETTINGS || m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS || m_nCurrScreen == MENUPAGE_CONTROLLER_PC || m_nCurrScreen == MENUPAGE_MOUSE_CONTROLS) + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_NOTHING + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_LABEL + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_YES + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_NO + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_CHANGEMENU + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_KEYBOARDCTRLS + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_GOBACK + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_RESTOREDEF + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_DRAWDIST + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_MOUSESENS + && aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action != MENUACTION_MP3VOLUMEBOOST) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_ENTER_OR_ADJUST, 0); + } + } + ProcessUserInput(goDown, goUp, optionSelected, goBack, changeValueBy); +#ifdef CUSTOM_FRONTEND_OPTIONS + if (aScreens[m_nCurrScreen].m_aEntries[oldOption].m_Action < MENUACTION_NOTHING) { // CFO check + CMenuScreenCustom::CMenuEntry &oldEntry = aScreens[m_nCurrScreen].m_aEntries[oldOption]; + if (m_nCurrOption != oldOption) { + if (oldEntry.m_Action == MENUACTION_CFO_DYNAMIC) + if(oldEntry.m_CFODynamic->buttonPressFunc) + oldEntry.m_CFODynamic->buttonPressFunc(FEOPTION_ACTION_FOCUSLOSS); + + if (oldEntry.m_Action == MENUACTION_CFO_SELECT && oldEntry.m_CFOSelect->onlyApplyOnEnter) { + if (oldEntry.m_CFOSelect->displayedValue != oldEntry.m_CFOSelect->lastSavedValue) + SetHelperText(3); // Restored original value + + oldEntry.m_CFOSelect->displayedValue = oldEntry.m_CFOSelect->lastSavedValue = *(int8*)oldEntry.m_CFO->value; + } + } else if (oldEntry.m_Action == MENUACTION_CFO_SELECT && oldEntry.m_CFOSelect->onlyApplyOnEnter) { + if (oldEntry.m_CFOSelect->displayedValue != *(int8*)oldEntry.m_CFO->value) + SetHelperText(1); // Enter to apply + else if (m_nHelperTextMsgId == 1) + ResetHelperText(); // Applied + } + } +#endif +} + +void +CMenuManager::ProcessUserInput(uint8 goDown, uint8 goUp, uint8 optionSelected, uint8 goBack, int8 changeAmount) +{ + if (m_nCurrScreen == MENUPAGE_OUTRO) + return; + + 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) { + pControlEdit = nil; + m_bWaitingForNewKeyBind = false; + m_KeyPressedCode = -1; + m_bStartWaitingForKeyBind = false; + } else if (!m_bKeyChangeNotProcessed) { + if (*pControlEdit != rsNULL || MouseButtonJustClicked || JoyButtonJustClicked) + CheckCodesForControls(TypeOfControl); + + field_159 = true; + } else { + for (int i = 0; i < 4; i++) + ControlsManager.ClearSettingsAssociatedWithAction((e_ControllerAction)m_CurrCntrlAction, (eControllerType)i); + m_bKeyIsOK = false; + m_bKeyChangeNotProcessed = false; + } + } + } + + if (pEditString || pControlEdit) + return; + +#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; + } + } +#endif + + int oldOption = m_nCurrOption; + if (goDown) { + if (m_nCurrScreen != MENUPAGE_MAP) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_HIGHLIGHT_OPTION, 0); + + m_nCurrOption++; + if (m_nCurrOption == NUM_MENUROWS || (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action == MENUACTION_NOTHING)) { + m_nCurrOption = 0; + } + if (oldOption != m_nCurrOption) + m_nOptionHighlightTransitionBlend = 0; + } + if (goUp) { + if (m_nCurrScreen != MENUPAGE_MAP) + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_HIGHLIGHT_OPTION, 0); + + 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--; + } + if (oldOption != m_nCurrOption) + m_nOptionHighlightTransitionBlend = 0; + } + + if (optionSelected && m_nMenuFadeAlpha == 255) { + if (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu == MENUPAGE_NEW_GAME_RELOAD && m_bGameNotLoaded) { + DoSettingsBeforeStartingAGame(); + } else if (hasNativeList(m_nCurrScreen)) { + switch (m_nCurrExLayer) { + 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; + case HOVEROPTION_BACK: + default: + goBack = true; + break; + } + if (m_nSkinsTotal > 0) { + m_pSelectedSkin = m_pSkinListHead.nextSkin; + strcpy(m_PrefsSkinFile, m_aSkinName); + CWorld::Players[0].SetPlayerSkin(m_PrefsSkinFile); + SaveSettings(); + } + } + + int option = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action; +#ifdef FIX_BUGS + int currScreen = m_nCurrScreen; + int currOption = m_nCurrOption; +#endif + switch (option) { + case MENUACTION_CHANGEMENU: + case MENUACTION_YES: + case MENUACTION_NO: + SwitchToNewScreen(aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_TargetMenu); + break; + case MENUACTION_RADIO: + ChangeRadioStation(1); + 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_CHECKSAVE: + { + int saveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot; + + if (saveSlot >= SAVESLOT_1 && saveSlot <= SAVESLOT_8) { + m_nCurrSaveSlot = saveSlot - SAVESLOT_1; + if (Slots[m_nCurrSaveSlot] != SLOT_EMPTY && Slots[m_nCurrSaveSlot] != SLOT_CORRUPTED) { + if (m_nCurrScreen == MENUPAGE_CHOOSE_LOAD_SLOT) { + SwitchToNewScreen(MENUPAGE_LOAD_SLOT_CONFIRM); + } else if (m_nCurrScreen == MENUPAGE_CHOOSE_DELETE_SLOT) { + SwitchToNewScreen(MENUPAGE_DELETE_SLOT_CONFIRM); + } + } + } + break; + } + case MENUACTION_NEWGAME: + DoSettingsBeforeStartingAGame(); + break; +#ifdef LEGACY_MENU_OPTIONS + case MENUACTION_RELOADIDE: + CFileLoader::ReloadObjectTypes("GTA3.IDE"); + break; +#endif + case MENUACTION_RESUME_FROM_SAVEZONE: + RequestFrontEndShutDown(); + break; + case MENUACTION_LOADRADIO: + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) { + SwitchToNewScreen(MENUPAGE_SOUND_SETTINGS); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + OutputDebugString("STARTED PLAYING FRONTEND AUDIO TRACK"); + } + break; +#ifdef MISSION_REPLAY + case MENUACTION_REJECT_RETRY: + doingMissionRetry = false; + AllowMissionReplay = MISSION_RETRY_STAGE_NORMAL; + RequestFrontEndShutDown(); + break; + case MENUACTION_UNK114: + doingMissionRetry = false; + RequestFrontEndShutDown(); + RetryMission(MISSION_RETRY_TYPE_BEGIN_RESTARTING); + return; +#endif + case MENUACTION_SAVEGAME: + { + int saveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot; + + if (saveSlot >= 2 && saveSlot <= 9) { + m_nCurrSaveSlot = m_nCurrOption; + SwitchToNewScreen(MENUPAGE_SAVE_OVERWRITE_CONFIRM); + } + break; + } + case MENUACTION_RADARMODE: + if (++m_PrefsRadarMode > 2) + m_PrefsRadarMode = 0; + SaveSettings(); + break; + case MENUACTION_GOBACK: + goBack = true; + break; + case MENUACTION_KEYBOARDCTRLS: + SwitchToNewScreen(MENUPAGE_KEYBOARD_CONTROLS); + 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(); + SwitchToNewScreen(MENUPAGE_OUTRO); + break; + case MENUACTION_RESUME: +#ifdef LEGACY_MENU_OPTIONS + if (m_PrefsVsyncDisp != m_PrefsVsync) { + m_PrefsVsync = m_PrefsVsyncDisp; + } +#endif + RequestFrontEndShutDown(); + break; + case MENUACTION_DONTCANCEL: + SwitchToNewScreen(-2); + break; + case MENUACTION_SCREENRES: + if (m_nDisplayVideoMode != m_nPrefsVideoMode) { + m_nPrefsVideoMode = m_nDisplayVideoMode; + _psSelectScreenVM(m_nPrefsVideoMode); + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.Service(); + CentreMousePointer(); + m_bShowMouse = true; + m_nCurrOption = 5; // TODO(Miami): Because selected option is resetted after res. change. We'll need to revisit that. + m_nOptionHighlightTransitionBlend = 0; + SaveSettings(); + } + break; + case MENUACTION_AUDIOHW: + { + int selectedProvider = m_nPrefsAudio3DProviderIndex; + if (selectedProvider != NO_AUDIO_PROVIDER) { + if (selectedProvider == -1) + selectedProvider = m_nPrefsAudio3DProviderIndex = DMAudio.AutoDetect3DProviders(); + + m_nPrefsAudio3DProviderIndex = DMAudio.SetCurrent3DProvider(m_nPrefsAudio3DProviderIndex); + if (selectedProvider != m_nPrefsAudio3DProviderIndex) { + SetHelperText(5); + } + SaveSettings(); + } + break; + } + case MENUACTION_SPEAKERCONF: + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) { + if (--m_PrefsSpeakers < 0) + m_PrefsSpeakers = 2; + DMAudio.SetSpeakerConfig(m_PrefsSpeakers); + SaveSettings(); + } + break; + case MENUACTION_PLAYERSETUP: + CPlayerSkin::BeginFrontendSkinEdit(); + SwitchToNewScreen(MENUPAGE_SKIN_SELECT); + m_bSkinsEnumerated = false; + m_nCurrExLayer = HOVEROPTION_LIST; + break; + case MENUACTION_RESTOREDEF: + if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) { + m_nPrefsAudio3DProviderIndex = DMAudio.AutoDetect3DProviders(); + DMAudio.SetCurrent3DProvider(m_nPrefsAudio3DProviderIndex); + m_PrefsSfxVolume = 49; + m_PrefsMusicVolume = 49; + m_PrefsRadioStation = EMOTION; + m_PrefsMP3BoostVolume = 0; + m_PrefsStereoMono = 1; + m_PrefsSpeakers = 0; + DMAudio.SetMP3BoostVolume(m_PrefsMP3BoostVolume); + DMAudio.SetMusicMasterVolume(m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(m_PrefsSfxVolume); + DMAudio.SetRadioInCar(m_PrefsRadioStation); + DMAudio.PlayFrontEndTrack(m_PrefsRadioStation, TRUE); + SaveSettings(); + } else if (m_nCurrScreen == MENUPAGE_DISPLAY_SETTINGS) { + m_PrefsBrightness = 256; + m_PrefsLOD = 1.2f; +#ifdef LEGACY_MENU_OPTIONS + m_PrefsVsync = true; +#endif + CRenderer::ms_lodDistScale = m_PrefsLOD; + m_PrefsShowSubtitles = false; +#ifdef ASPECT_RATIO_SCALE + m_PrefsUseWideScreen = AR_AUTO; +#else + m_PrefsUseWideScreen = false; +#endif + m_PrefsShowLegends = true; + m_PrefsVsyncDisp = true; + m_PrefsFrameLimiter = true; + m_PrefsRadarMode = 0; + m_PrefsShowHud = true; + m_nDisplayVideoMode = m_nPrefsVideoMode; + CMBlur::BlurOn = false; +#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_CONTROLLER_PC) { + ControlsManager.MakeControllerActionsBlank(); + ControlsManager.InitDefaultControlConfiguration(); + ControlsManager.InitDefaultControlConfigMouse(MousePointerStateHelper.GetMouseSetUp()); +#if defined(RW_DC) + printf("TODO: implement this"); +#elif !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 + MousePointerStateHelper.bInvertVertically = true; + TheCamera.m_bHeadBob = false; +#ifdef FIX_BUGS + TheCamera.m_fMouseAccelVertical = 0.003f; +#endif + TheCamera.m_fMouseAccelHorzntl = 0.0025f; + CVehicle::m_bDisableMouseSteering = true; + m_ControlMethod = CONTROL_STANDARD; +#ifdef PC_PLAYER_CONTROLS + TheCamera.m_bUseMouse3rdPerson = true; +#else + TheCamera.m_bUseMouse3rdPerson = false; +#endif + SaveSettings(); +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#endif + } + SetHelperText(2); + break; + case MENUACTION_CTRLMETHOD: + if (m_ControlMethod == CONTROL_CLASSIC) { + CCamera::m_bUseMouse3rdPerson = true; + m_ControlMethod = CONTROL_STANDARD; + } else { + CCamera::m_bUseMouse3rdPerson = false; + m_ControlMethod = CONTROL_CLASSIC; + } + 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 (!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) { +#ifdef FIX_BUGS + int saveSlot = aScreens[currScreen].m_aEntries[currOption].m_SaveSlot; + if (saveSlot >= SAVESLOT_1 && saveSlot <= SAVESLOT_8 && Slots[currOption] != SLOT_OK) +#else + int saveSlot = aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_SaveSlot; + if (saveSlot >= SAVESLOT_1 && saveSlot <= SAVESLOT_8 && Slots[m_nCurrOption] != SLOT_OK) +#endif + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + else + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_ENTER_OR_ADJUST, 0); + } + } + + if (goBack) { + if (m_NoEmptyBinding) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_BACK, 0); + SwitchToNewScreen(-2); + if (hasNativeList(m_nCurrScreen)) { + m_nTotalListRow = 0; + } + } else { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_FAIL, 0); + m_ShowEmptyBindingError = true; + } + } + + if (changeAmount != 0) { + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { +#ifdef GAMEPAD_MENU + case MENUACTION_CTRLCONFIG: + CPad::GetPad(0)->Mode += changeAmount; + if (CPad::GetPad(0)->Mode > 3) + CPad::GetPad(0)->Mode = 0; + else if (CPad::GetPad(0)->Mode < 0) + CPad::GetPad(0)->Mode = 3; + SaveSettings(); + break; +#endif + case MENUACTION_RADIO: + ChangeRadioStation(changeAmount); + break; + case MENUACTION_RADARMODE: + m_PrefsRadarMode += changeAmount; + if (m_PrefsRadarMode < 0) + m_PrefsRadarMode = 2; + if (m_PrefsRadarMode > 2) + m_PrefsRadarMode = 0; + break; +#ifdef ASPECT_RATIO_SCALE + case MENUACTION_WIDESCREEN: + if (changeAmount > 0) { + m_PrefsUseWideScreen++; + if (m_PrefsUseWideScreen > AR_MAX - 1) + m_PrefsUseWideScreen = 0; + } else { + m_PrefsUseWideScreen--; + if (m_PrefsUseWideScreen < 0) + m_PrefsUseWideScreen = AR_MAX - 1; + } + SaveSettings(); + break; +#endif + case MENUACTION_SCREENRES: + if (m_bGameNotLoaded) { + RwChar** videoMods = _psGetVideoModeList(); + if (changeAmount > 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 != NO_AUDIO_PROVIDER) { + m_nPrefsAudio3DProviderIndex += changeAmount; + + bool checkIfForbidden = true; + while (checkIfForbidden) { + checkIfForbidden = false; + + if (m_nPrefsAudio3DProviderIndex < -1) + m_nPrefsAudio3DProviderIndex = DMAudio.GetNum3DProvidersAvailable() - 1; + else if (m_nPrefsAudio3DProviderIndex > DMAudio.GetNum3DProvidersAvailable() - 1) + m_nPrefsAudio3DProviderIndex = -1; + + // what a retarded move... + if (m_nPrefsAudio3DProviderIndex != -1) { + char* provider = DMAudio.Get3DProviderName(m_nPrefsAudio3DProviderIndex); + strupr(provider); + if (!strcmp(provider, "MILES FAST 2D POSITIONAL AUDIO")) { + m_nPrefsAudio3DProviderIndex += changeAmount; + checkIfForbidden = true; + + } else if (!strcmp(provider, "AUREAL A3D 2.0 (TM)")) { + m_nPrefsAudio3DProviderIndex += changeAmount; + checkIfForbidden = true; + + } else if (!strcmp(provider, "AUREAL A3D INTERACTIVE (TM)")) { + m_nPrefsAudio3DProviderIndex += changeAmount; + checkIfForbidden = true; + } + } + } + } + break; + case MENUACTION_SPEAKERCONF: + if (m_nPrefsAudio3DProviderIndex != NO_AUDIO_PROVIDER) { + m_PrefsSpeakers -= changeAmount; + m_PrefsSpeakers = Clamp(m_PrefsSpeakers, 0, 2); + DMAudio.SetSpeakerConfig(m_PrefsSpeakers); + SaveSettings(); + } + break; + case MENUACTION_CTRLMETHOD: + m_ControlMethod = !m_ControlMethod; + CCamera::m_bUseMouse3rdPerson = !m_ControlMethod; + 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 (changeAmount > 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(changeAmount > 0 ? FEOPTION_ACTION_RIGHT : FEOPTION_ACTION_LEFT); + } + break; +#endif + } + CheckSliderMovement(changeAmount); + ProcessOnOffMenuOptions(); + if (m_nCurrScreen == MENUPAGE_KEYBOARD_CONTROLS) { + if (changeAmount < 1) { + m_nSelectedContSetupColumn = CONTSETUP_PED_COLUMN; + } else { + m_nSelectedContSetupColumn = CONTSETUP_VEHICLE_COLUMN; + } + } + } +} + +void +CMenuManager::ProcessOnOffMenuOptions() +{ + switch (aScreens[m_nCurrScreen].m_aEntries[m_nCurrOption].m_Action) { +#ifdef GAMEPAD_MENU + case MENUACTION_CTRLVIBRATION: + m_PrefsUseVibration = !m_PrefsUseVibration; + if (m_PrefsUseVibration) { + CPad::GetPad(0)->StartShake(350, 150); + TimeToStopPadShaking = CTimer::GetTimeInMillisecondsPauseMode() + 500; + } + SaveSettings(); + break; +#endif + case MENUACTION_INVERTPADY: + CPad::bInvertLook4Pad = !CPad::bInvertLook4Pad; + SaveSettings(); // FIX: Why don't SaveSettings? Because of it's an hidden option? :( + break; + case MENUACTION_CTRLDISPLAY: + m_DisplayControllerOnFoot = !m_DisplayControllerOnFoot; + break; + case MENUACTION_FRAMESYNC: + m_PrefsVsyncDisp = !m_PrefsVsyncDisp; + SaveSettings(); // FIX: Again... This makes me very unhappy + break; + case MENUACTION_FRAMELIMIT: + m_PrefsFrameLimiter = !m_PrefsFrameLimiter; + SaveSettings(); + break; + case MENUACTION_TRAILS: + CMBlur::BlurOn = !CMBlur::BlurOn; + SaveSettings(); + break; + case MENUACTION_SUBTITLES: + m_PrefsShowSubtitles = !m_PrefsShowSubtitles; + SaveSettings(); + break; +#ifndef ASPECT_RATIO_SCALE + case MENUACTION_WIDESCREEN: + m_PrefsUseWideScreen = !m_PrefsUseWideScreen; + SaveSettings(); + break; +#endif + case MENUACTION_LEGENDS: + m_PrefsShowLegends = !m_PrefsShowLegends; + break; + case MENUACTION_HUD: + m_PrefsShowHud = !m_PrefsShowHud; + SaveSettings(); + break; +#ifdef LEGACY_MENU_OPTIONS + case MENUACTION_SETDBGFLAG: + CTheScripts::InvertDebugFlag(); + break; + case MENUACTION_SWITCHBIGWHITEDEBUGLIGHT: + gbBigWhiteDebugLightSwitchedOn = !gbBigWhiteDebugLightSwitchedOn; + break; + case MENUACTION_COLLISIONPOLYS: + gbShowCollisionPolys = !gbShowCollisionPolys; + break; +#endif + case MENUACTION_SHOWHEADBOB: + TheCamera.m_bHeadBob = !TheCamera.m_bHeadBob; + SaveSettings(); + break; + case MENUACTION_INVVERT: + MousePointerStateHelper.bInvertVertically = !MousePointerStateHelper.bInvertVertically; + SaveSettings(); + break; + case MENUACTION_DYNAMICACOUSTIC: + m_PrefsDMA = !m_PrefsDMA; + DMAudio.SetDynamicAcousticModelingStatus(m_PrefsDMA); + SaveSettings(); + break; + case MENUACTION_MOUSESTEER: + if (m_ControlMethod == CONTROL_STANDARD) { + CVehicle::m_bDisableMouseSteering = !CVehicle::m_bDisableMouseSteering; + SaveSettings(); + } + break; + } +} + +void +CMenuManager::RequestFrontEndShutDown() +{ + m_bShutDownFrontEndRequested = true; +} + +void +CMenuManager::RequestFrontEndStartUp() +{ + m_bStartUpFrontEndRequested = true; +} + +void +CMenuManager::ResetHelperText() +{ + m_nHelperTextMsgId = 0; + m_nHelperTextAlpha = 300; +} + +void +CMenuManager::SetHelperText(int text) +{ + m_nHelperTextMsgId = text; + m_nHelperTextAlpha = 300; +} + +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); +} + +#ifdef XBOX_MESSAGE_SCREEN +void +CMenuManager::CloseDialog(void) +{ + // We don't have this on PC GXT :shrug: + static wchar* gameSaved = AllocUnicode("Game saved successfully!"); + + if (m_bSaveWasSuccessful && DialogTextCmp("FESZ_WR")) { + m_bSaveWasSuccessful = false; // i don't know where XBOX resets that + m_pDialogText = gameSaved; + SetDialogTimer(1000); + ProcessDialogTimer(); + } else { + ToggleDialog(false); + } + +} + +void +CMenuManager::ProcessDialogTimer(void) +{ + if (!m_bDialogOpen || m_nDialogHideTimer == 0) + return; + + // Also XBOX has unified time source for in-game/menu, but we don't have that + if (m_bMenuActive && CTimer::GetTimeInMilliseconds() > m_nDialogHideTimer || !m_bMenuActive && CTimer::GetTimeInMillisecondsPauseMode() > m_nDialogHideTimerPauseMode) { + + // This is originally activePage.funcs->closePage() + CloseDialog(); + } +} + +void +CMenuManager::SetDialogTimer(uint32 timer) +{ + // XBOX iterates some page list(actives?) and then sets timer variable of specified page to specified value. We only have dialog right now. + // Also XBOX has unified time source for in-game/menu, but we don't have that, thus 2 timer variables... + + m_nDialogHideTimer = CTimer::GetTimeInMilliseconds() + timer; + m_nDialogHideTimerPauseMode = CTimer::GetTimeInMillisecondsPauseMode() + timer; +} + +void +CMenuManager::SetDialogText(const char* key) +{ + // There are many things going around here, idk why + m_pDialogText = TheText.Get(key); +} + +bool +CMenuManager::DialogTextCmp(const char* key) +{ + wchar *value = TheText.Get(key); + wchar *i = m_pDialogText; + for (; *i != '\0' && *value != '\0'; i++, value++) { + if (*i != *value) + return false; + } + return *i == '\0' && *value == '\0'; +} + +void +CMenuManager::ToggleDialog(bool toggle) +{ + // This originally calls some mysterious function on enable and close CB on disable, along with decreasing some counter. Which is no use for dialog + + // XBOX doesn't do that + if (toggle) + m_nDialogHideTimer = 0; + + m_bDialogOpen = toggle; +} + +void +DrawDialogBg(float offset, uint8 alpha) +{ + CSprite2d::Draw2DPolygon(SCALE_AND_CENTER_X(84.f + offset), MENU_Y(126.f + offset), + SCALE_AND_CENTER_X(512.f + offset), MENU_Y(109.f + offset), + SCALE_AND_CENTER_X(100.f + offset), MENU_Y(303.f + offset), + SCALE_AND_CENTER_X(474.f + offset), MENU_Y(311.f + offset), CRGBA(107, 193, 236, alpha)); + CSprite2d::Draw2DPolygon(SCALE_AND_CENTER_X(523.f + offset), MENU_Y(108.f + offset), + SCALE_AND_CENTER_X(542.f + offset), MENU_Y(107.f + offset), + SCALE_AND_CENTER_X(485.f + offset), MENU_Y(310.f + offset), + SCALE_AND_CENTER_X(516.f + offset), MENU_Y(311.f + offset), CRGBA(107, 193, 236, alpha)); +} + +void +CMenuManager::DrawOverlays(void) +{ + // This is stripped to show only Dialog box, XBOX does much more in here. + + if (!m_bDialogOpen) + return; + + DefinedState(); + + CSprite2d::DrawRect(CRect(0, SCREEN_HEIGHT, SCREEN_WIDTH, 0), CRGBA(0, 0, 0, 160)); + + // Ofc this is not hardcoded like that on Xbox, it should be a texture + DrawDialogBg(20.f, 160); // shadow + DrawDialogBg(0.f, 255); + + CFont::SetBackgroundOff(); + CFont::SetPropOn(); + CFont::SetJustifyOn(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetCentreSize(SCREEN_SCALE_X(380.0f)); + CFont::SetCentreOn(); + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, 255)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + // Both of those are 0.9 on Xbox, which is ofcouse wrong... + CFont::SetScale(SCREEN_SCALE_X(BIGTEXT_X_SCALE), SCREEN_SCALE_Y(BIGTEXT_Y_SCALE)); + + int x = SCREEN_WIDTH / 2.f - SCREEN_SCALE_X(30.0f); + int y = SCREEN_HEIGHT / 2.f - SCREEN_SCALE_Y(30.0f); + int numOfLines = CFont::GetNumberLines(x, y, m_pDialogText); + CFont::PrintString(x, y - SCREEN_SCALE_Y(numOfLines / 2.f), m_pDialogText); + CFont::DrawFonts(); +} +#endif + +void +CMenuManager::ProcessFileActions() +{ + switch (m_nCurrScreen) { + case MENUPAGE_LOADING_IN_PROGRESS: +#ifdef MISSION_REPLAY + if (MissionSkipLevel) { + if (gGameState != GS_PLAYING_GAME) + DoSettingsBeforeStartingAGame(); + RequestFrontEndShutDown(); + break; + } + 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 XBOX_MESSAGE_SCREEN + SetDialogText("FELD_WR"); + ToggleDialog(true); +#else + if (!m_bGameNotLoaded) + MessageScreen("FELD_WR", true); +#endif + DoSettingsBeforeStartingAGame(); + m_bWantToLoad = true; + } else + SwitchToNewScreen(MENUPAGE_NEW_GAME); + + break; + case MENUPAGE_DELETING_IN_PROGRESS: + { + static bool waitedForScreen = false; + + if (waitedForScreen) { + bool SlotPopulated = false; + if (PcSaveHelper.DeleteSlot(m_nCurrSaveSlot)) { + PcSaveHelper.PopulateSlotInfo(); + SlotPopulated = true; + } + + if (SlotPopulated) { + SwitchToNewScreen(MENUPAGE_DELETE_SUCCESSFUL); + } else { + SwitchToNewScreen(MENUPAGE_SAVE_CUSTOM_WARNING); + strncpy(aScreens[m_nCurrScreen].m_ScreenName, "FES_DEL", 8); + strncpy(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName, "FES_DEE", 8); + } + waitedForScreen = false; + } else if (m_nMenuFadeAlpha >= 255) + waitedForScreen = true; + + break; + } + case MENUPAGE_SAVING_IN_PROGRESS: + { +#ifdef XBOX_MESSAGE_SCREEN + if (m_bDialogOpen && DialogTextCmp("FESZ_WR")) { + uint32 startTime = CTimer::GetTimeInMillisecondsPauseMode(); + int8 SaveSlot = PcSaveHelper.SaveSlot(m_nCurrSaveSlot); + PcSaveHelper.PopulateSlotInfo(); + + // Original code, but we don't want redundant saving text if it doesn't +#if 0 + CTimer::Update(); // not on Xbox, who updates it? + + // it compensates the lag to show saving text always one second... how cute + int dialogDur = Max(1, startTime - CTimer::GetTimeInMillisecondsPauseMode() + 1000); +#else + int dialogDur = 1; +#endif + + if (SaveSlot) { + // error. PC code + ToggleDialog(false); + SwitchToNewScreen(MENUPAGE_SAVE_CUSTOM_WARNING); + strncpy(aScreens[m_nCurrScreen].m_ScreenName, "FET_SG", 8); + strncpy(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName, "FES_CMP", 8); + + } else { + m_bSaveWasSuccessful = true; + SetDialogTimer(dialogDur); + ProcessDialogTimer(); + RequestFrontEndShutDown(); + } + + } else { + SetDialogText("FESZ_WR"); + ToggleDialog(true); + } +#else + static bool waitedForScreen = false; + + if (waitedForScreen) { + int8 SaveSlot = PcSaveHelper.SaveSlot(m_nCurrSaveSlot); + PcSaveHelper.PopulateSlotInfo(); + if (SaveSlot) { + SwitchToNewScreen(MENUPAGE_SAVE_CUSTOM_WARNING); + strncpy(aScreens[m_nCurrScreen].m_ScreenName, "FET_SG", 8); + strncpy(aScreens[m_nCurrScreen].m_aEntries[0].m_EntryName, "FES_CMP", 8); + } else + SwitchToNewScreen(MENUPAGE_SAVE_SUCCESSFUL); + + waitedForScreen = false; + } else if (m_nMenuFadeAlpha >= 255) + waitedForScreen = true; +#endif + break; + } + } +} + +void +CMenuManager::SwitchMenuOnAndOff() +{ + if (!TheCamera.m_WideScreenOn) { + + // Reminder: You need REGISTER_START_BUTTON defined to make it work. + if ((CPad::GetPad(0)->GetStartJustDown() || CPad::GetPad(0)->GetEscapeJustDown()) + && (!m_bMenuActive || m_nCurrScreen == MENUPAGE_PAUSE_MENU || m_nCurrScreen == MENUPAGE_CHOOSE_SAVE_SLOT || m_nCurrScreen == MENUPAGE_SAVE_CHEAT_WARNING) + || m_bShutDownFrontEndRequested || m_bStartUpFrontEndRequested +#ifdef REGISTER_START_BUTTON + || CPad::GetPad(0)->GetStartJustDown() && !m_bGameNotLoaded +#endif + ) { + + if (m_nCurrScreen != MENUPAGE_LOADING_IN_PROGRESS +#ifdef XBOX_MESSAGE_SCREEN + && m_nCurrScreen != MENUPAGE_SAVING_IN_PROGRESS +#endif + ) { + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DoRWStuffEndOfFrame(); + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DoRWStuffEndOfFrame(); + } + + if (m_bShutDownFrontEndRequested) + m_bMenuActive = false; + else if (m_bStartUpFrontEndRequested) + m_bMenuActive = true; + else + m_bMenuActive = !m_bMenuActive; + + if (m_bMenuActive) { + if (_InputMouseNeedsExclusive()) { + _InputShutdownMouse(); + _InputInitialiseMouse(false); + } + Initialise(); + LoadAllTextures(); +#ifdef FIX_BUGS + CPad::StopPadsShaking(); +#endif + } else { +#ifdef EXTENDED_COLOURFILTER + // we always expect CPostFX to be open + CMBlur::BlurOn = true; +#endif + if (CMBlur::BlurOn) + CMBlur::MotionBlurOpen(Scene.camera); + else + CMBlur::MotionBlurClose(); + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DoRWStuffEndOfFrame(); + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DoRWStuffEndOfFrame(); + if (_InputMouseNeedsExclusive()) { + _InputShutdownMouse(); + _InputInitialiseMouse(true); + } + + m_StatsScrollSpeed = 150.0f; +#ifdef FIX_BUGS + ThingsToDoBeforeLeavingPage(); +#endif + SaveSettings(); +#ifdef LOAD_INI_SETTINGS + SaveINIControllerSettings(); +#endif + pControlEdit = nil; + pEditString = nil; + DisplayComboButtonErrMsg = false; + m_bShutDownFrontEndRequested = false; + m_bStartUpFrontEndRequested = false; + m_bWaitingForNewKeyBind = 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 + UnloadTextures(); + CTimer::EndUserPause(); + CTimer::Update(); + m_OnlySaveMenu = false; + } + } + } + + // Just entered the save/safe zone + if (m_bActivateSaveMenu) { + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DoRWStuffEndOfFrame(); + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255); + DoRWStuffEndOfFrame(); + m_bActivateSaveMenu = false; + m_bMenuActive = true; + m_OnlySaveMenu = true; + + if (_InputMouseNeedsExclusive()) { + _InputShutdownMouse(); + _InputInitialiseMouse(false); + } + + Initialise(); + LoadAllTextures(); + + if (CPad::bHasPlayerCheated) { + m_nCurrScreen = MENUPAGE_SAVE_CHEAT_WARNING; + m_nCurrOption = 0; + } else { + m_nCurrScreen = MENUPAGE_CHOOSE_SAVE_SLOT; + m_nCurrOption = 8; + } + } + + m_bStartUpFrontEndRequested = false; + m_bShutDownFrontEndRequested = false; + +#ifdef GAMEPAD_MENU + // Reset pad shaking. + if (TimeToStopPadShaking && TimeToStopPadShaking < CTimer::GetTimeInMillisecondsPauseMode()) { + CPad::StopPadsShaking(); + TimeToStopPadShaking = 0; + } +#endif +} + +void +CMenuManager::UnloadTextures() +{ + if (m_nCurrScreen == MENUPAGE_SOUND_SETTINGS) + DMAudio.StopFrontEndTrack(); + + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_MENU_STARTING, 0); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + if (m_bSpritesLoaded) { + printf("REMOVE frontend\n"); + int frontend = CTxdStore::FindTxdSlot("frontend1"); + for (int i = 0; i < 3; ++i) + m_aFrontEndSprites[i].Delete(); + + CTxdStore::RemoveTxd(frontend); + + if (!m_OnlySaveMenu) { + int frontend2 = CTxdStore::FindTxdSlot("frontend2"); + for (int i = 3; i < NUM_MENU_SPRITES; ++i) + m_aFrontEndSprites[i].Delete(); + + CTxdStore::RemoveTxd(frontend2); + +#ifdef GAMEPAD_MENU + // Unload controller txd + int frontend_controller = CTxdStore::FindTxdSlot("frontend_controller"); + if (frontend_controller != -1) + CTxdStore::RemoveTxd(frontend_controller); +#endif + } + + m_bSpritesLoaded = false; + } + m_OnlySaveMenu = false; + CUserDisplay::PlaceName.ProcessAfterFrontEndShutDown(); +} + +void +CMenuManager::WaitForUserCD() +{ + CSprite2d *splash; + char *splashscreen = nil; + + splash = LoadSplash(splashscreen); + + if (RsGlobal.quit) + return; + + HandleExit(); + CPad::UpdatePads(); + MessageScreen("NO_PCCD", true); + + if (CPad::GetPad(0)->GetEscapeJustDown()) { + m_bQuitGameNoCD = true; + RsEventHandler(rsQUITAPP, nil); + } +} + +void +CMenuManager::DrawQuitGameScreen(void) +{ + static int32 exitSignalTimer = 0; + +#ifdef FIX_BUGS + int alpha = Clamp(m_nMenuFadeAlpha, 0, 255); +#else + int alpha = m_nMenuFadeAlpha; +#endif + +#ifndef MUCH_SHORTER_OUTRO_SCREEN + static uint32 lastTickIncrease = 0; + if (alpha == 255 && CTimer::GetTimeInMillisecondsPauseMode() - lastTickIncrease > 10) { + exitSignalTimer++; + lastTickIncrease = CTimer::GetTimeInMillisecondsPauseMode(); + } +#else + static uint32 firstTick = CTimer::GetTimeInMillisecondsPauseMode(); + if (alpha == 255 && CTimer::GetTimeInMillisecondsPauseMode() - firstTick > 750) { + exitSignalTimer = 150; + } +#endif + static CSprite2d *splash = nil; + + if (splash == nil) + splash = LoadSplash("OUTRO"); + + m_aFrontEndSprites[MENUSPRITE_VCLOGO].Draw(CRect(SCREEN_STRETCH_X(28.0f), MENU_Y(8.0f), SCREEN_STRETCH_X(27.0f) + MENU_X(130.f), MENU_Y(138.0f)), CRGBA(255, 255, 255, 255 - alpha)); + + // Or we can see menu background from sides +#ifdef ASPECT_RATIO_SCALE + CSprite2d::DrawRect(CRect(0, 0, MENU_X_LEFT_ALIGNED(0.f), SCREEN_HEIGHT), CRGBA(0, 0, 0, alpha)); + CSprite2d::DrawRect(CRect(MENU_X_RIGHT_ALIGNED(0.f), 0, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(0, 0, 0, alpha)); +#endif + + splash->Draw(CRect(MENU_X_LEFT_ALIGNED(0.f), 0, MENU_X_RIGHT_ALIGNED(0.f), SCREEN_HEIGHT), CRGBA(255, 255, 255, alpha)); + if (alpha == 255 && exitSignalTimer == 150) + RsEventHandler(rsQUITAPP, nil); + + m_bShowMouse = false; + m_AllowNavigation = false; +} + +void +CMenuManager::PrintMap(void) +{ + m_bMenuMapActive = true; + CRadar::InitFrontEndMap(); + + // Because m_fMapSize is half of the map length(hence * 2), and map consists of 3x3 tiles(hence / 3). + float halfTile = m_fMapSize * 2.f / 3.f / 2.f; + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + + if (SCREEN_WIDTH >= m_fMapCenterX - m_fMapSize || SCREEN_HEIGHT >= m_fMapCenterY - m_fMapSize) { + m_aFrontEndSprites[MENUSPRITE_MAPTOP01].Draw(CRect(m_fMapCenterX - m_fMapSize, m_fMapCenterY - m_fMapSize, + m_fMapCenterX - halfTile, m_fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= m_fMapCenterX - halfTile || SCREEN_HEIGHT >= m_fMapCenterY - m_fMapSize) { + m_aFrontEndSprites[MENUSPRITE_MAPTOP02].Draw(CRect(m_fMapCenterX - halfTile, m_fMapCenterY - m_fMapSize, + m_fMapCenterX + halfTile, m_fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= m_fMapCenterX + halfTile || SCREEN_HEIGHT >= m_fMapCenterY - m_fMapSize) { + m_aFrontEndSprites[MENUSPRITE_MAPTOP03].Draw(CRect(m_fMapCenterX + halfTile, m_fMapCenterY - m_fMapSize, + m_fMapCenterX + m_fMapSize, m_fMapCenterY - halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= m_fMapCenterX - m_fMapSize || SCREEN_HEIGHT >= m_fMapCenterY - halfTile) { + m_aFrontEndSprites[MENUSPRITE_MAPMID01].Draw(CRect(m_fMapCenterX - m_fMapSize, m_fMapCenterY - halfTile, + m_fMapCenterX - halfTile, m_fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= m_fMapCenterX - halfTile || SCREEN_HEIGHT >= m_fMapCenterY - halfTile) { + m_aFrontEndSprites[MENUSPRITE_MAPMID02].Draw(CRect(m_fMapCenterX - halfTile, m_fMapCenterY - halfTile, + m_fMapCenterX + halfTile, m_fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= m_fMapCenterX + halfTile || SCREEN_HEIGHT >= m_fMapCenterY - halfTile) { + m_aFrontEndSprites[MENUSPRITE_MAPMID03].Draw(CRect(m_fMapCenterX + halfTile, m_fMapCenterY - halfTile, + m_fMapCenterX + m_fMapSize, m_fMapCenterY + halfTile), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= m_fMapCenterX - m_fMapSize || SCREEN_HEIGHT >= m_fMapCenterY + halfTile) { + m_aFrontEndSprites[MENUSPRITE_MAPBOT01].Draw(CRect(m_fMapCenterX - m_fMapSize, m_fMapCenterY + halfTile, + m_fMapCenterX - halfTile, m_fMapCenterY + m_fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= m_fMapCenterX - halfTile || SCREEN_HEIGHT >= m_fMapCenterY + halfTile) { + m_aFrontEndSprites[MENUSPRITE_MAPBOT02].Draw(CRect(m_fMapCenterX - halfTile, m_fMapCenterY + halfTile, + m_fMapCenterX + halfTile, m_fMapCenterY + m_fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + if (SCREEN_WIDTH >= m_fMapCenterX + halfTile || SCREEN_HEIGHT >= m_fMapCenterY + halfTile) { + m_aFrontEndSprites[MENUSPRITE_MAPBOT03].Draw(CRect(m_fMapCenterX + halfTile, m_fMapCenterY + halfTile, + m_fMapCenterX + m_fMapSize, m_fMapCenterY + m_fMapSize), CRGBA(255, 255, 255, FadeIn(255))); + } + + CRadar::DrawBlips(); + if (m_PrefsShowLegends) { + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(40.0f)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(84.0f)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetColor(CRGBA(LABEL_COLOR.r, LABEL_COLOR.g, LABEL_COLOR.b, FadeIn(255))); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetCentreOn(); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(SCREEN_SCALE_X(0.65f), SCREEN_SCALE_Y(0.95f)); + + int secondColumnStart = (CRadar::MapLegendCounter - 1) / 2; + int boxBottom = MENU_Y(100.0f); + + // + 3, because we want 19*3 px padding + for (int i = 0; i < secondColumnStart + 3; i++) { + boxBottom += MENU_Y(19.f); + } + + CSprite2d::DrawRect(CRect(MENU_X_LEFT_ALIGNED(95.0f), MENU_Y(100.0f), MENU_X_LEFT_ALIGNED(555.f), boxBottom), + CRGBA(0, 0, 0, FadeIn(190))); + + CFont::PrintString(MENU_X_LEFT_ALIGNED(320.0f), MENU_Y(102.0f), TheText.Get("FE_MLG")); + CFont::SetRightJustifyOff(); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + if (m_PrefsLanguage == LANGUAGE_AMERICAN) + CFont::SetScale(SCREEN_SCALE_X(0.55f), SCREEN_SCALE_Y(0.55f)); + else + CFont::SetScale(SCREEN_SCALE_X(0.45f), SCREEN_SCALE_Y(0.55f)); + + CFont::SetColor(CRGBA(225, 225, 225, FadeIn(255))); + CFont::SetDropShadowPosition(0); + + int y = MENU_Y(127.0f); + int x = MENU_X_LEFT_ALIGNED(160.0f); + + for (int16 i = 0; i < CRadar::MapLegendCounter; i++) { + CRadar::DrawLegend(x, y, CRadar::MapLegendList[i]); + + if (i == secondColumnStart) { + x = MENU_X_LEFT_ALIGNED(350.0f); + y = MENU_Y(127.0f); + } else { + y += MENU_Y(19.0f); + } + } + } + +#ifdef MAP_ENHANCEMENTS + 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)); + +#endif + m_bMenuMapActive = false; + + CFont::SetWrapx(MENU_X_RIGHT_ALIGNED(MENU_X_MARGIN)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(MENU_X_MARGIN)); + DisplayHelperText("FEH_MPH"); +} + +void +CMenuManager::ChangeRadioStation(int8 increaseBy) +{ + if (m_ScrollRadioBy != 0) + return; + + m_PrefsRadioStation += increaseBy; + m_ScrollRadioBy = increaseBy; + if (m_ScrollRadioBy == 1) { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_ENTER_OR_ADJUST, 0); + m_LeftMostRadioX = MENU_X_LEFT_ALIGNED(MENURADIO_ICON_FIRST_X); + } else { + DMAudio.PlayFrontEndSound(SOUND_FRONTEND_ENTER_OR_ADJUST, 0); + m_LeftMostRadioX = MENU_X_LEFT_ALIGNED(MENURADIO_ICON_FIRST_X - (2 * MENURADIO_ICON_SIZE)); + } + + if (DMAudio.IsMP3RadioChannelAvailable()) { + if (m_PrefsRadioStation < WILDSTYLE) + m_PrefsRadioStation = USERTRACK; + if (m_PrefsRadioStation > USERTRACK) + m_PrefsRadioStation = WILDSTYLE; + } else { + if (m_PrefsRadioStation < WILDSTYLE) + m_PrefsRadioStation = WAVE; + if (m_PrefsRadioStation > WAVE) + m_PrefsRadioStation = WILDSTYLE; + } + DMAudio.StopFrontEndTrack(); + DMAudio.PlayFrontEndSound(SOUND_RADIO_CHANGE, 0); +} + +#if 0 +uint8 CMenuManager::GetNumberOfMenuOptions() +{ + uint8 Rows = -1; + for (int i = 0; i < NUM_MENUROWS; i++) { + if (aScreens[m_nCurrScreen].m_aEntries[i].m_Action == MENUACTION_NOTHING) + break; + + ++Rows; + } + return Rows; +} +#endif + +#ifdef GAMEPAD_MENU +const char* controllerTypesPaths[] = { + "MODELS/FRONTEND_DS2.TXD", + "MODELS/FRONTEND_DS3.TXD", + "MODELS/FRONTEND_DS4.TXD", + "MODELS/FRONTEND_X360.TXD", + "MODELS/FRONTEND_XONE.TXD", + "MODELS/FRONTEND_NSW.TXD", +}; + +void +CMenuManager::PrintController(void) +{ + // Don't print anything if controller texture is missing + if (!m_aFrontEndSprites[MENUSPRITE_CONTROLLER].m_pTexture) return; + + 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 = 220.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[MENUSPRITE_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, FadeIn(255))); + if (m_DisplayControllerOnFoot) { + if ((int)CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[MENUSPRITE_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, FadeIn(255))); + else + m_aFrontEndSprites[MENUSPRITE_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, FadeIn(255))); + } + else { + if ((int)CTimer::GetTimeInMillisecondsPauseMode() & 0x400) + m_aFrontEndSprites[MENUSPRITE_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, FadeIn(255))); + else + m_aFrontEndSprites[MENUSPRITE_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, FadeIn(255))); + } + + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.9f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.9f)); // X + + CFont::SetDropColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetDropShadowPosition(0); + CFont::SetColor(CRGBA(0, 0, 0, FadeIn(255))); + CFont::SetWrapx(SCREEN_WIDTH); + + float TEXT_L2_X = 85.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 = 27.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 = 67.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 = 170.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 = 164.0f + CONTROLLER_POS_X - centerX, TEXT_R2_Y = -14.0f + CONTROLLER_POS_Y - centerY; + float TEXT_R1_X = 242.0f + CONTROLLER_POS_X - centerX, TEXT_R1_Y = 27.0f + CONTROLLER_POS_Y - centerY; + + float TEXT_SQUARE_X = 147.0f + CONTROLLER_POS_X - centerX, TEXT_SQUARE_Y = 30.0f + CONTROLLER_POS_Y - centerY; + float TEXT_TRIANGLE_X = 242.0f + CONTROLLER_POS_X - centerX, TEXT_TRIANGLE_Y = 55.0f + CONTROLLER_POS_Y - centerY; + float TEXT_CIRCLE_X = 242.0f + CONTROLLER_POS_X - centerX, TEXT_CIRCLE_Y = 67.0f + CONTROLLER_POS_Y - centerY; + float TEXT_CROSS_X = 242.0f + CONTROLLER_POS_X - centerX, TEXT_CROSS_Y = 80.0f + CONTROLLER_POS_Y - centerY; + float TEXT_RSTICK_X = 242.0f + CONTROLLER_POS_X - centerX, TEXT_RSTICK_Y = 97.0f + CONTROLLER_POS_Y - centerY; + float TEXT_R3_X = 242.0f + CONTROLLER_POS_X - centerX, TEXT_R3_Y = 110.0f + CONTROLLER_POS_Y - centerY; + float TEXT_L3_X = 94.0f + CONTROLLER_POS_X - centerX, TEXT_L3_Y = 162.0f + CONTROLLER_POS_Y - centerY; + float TEXT_L2R2_X = 120.0f + CONTROLLER_POS_X - centerX, TEXT_L2R2_Y = -4.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 -= 2.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 -= 20.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 -= 19.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(); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + TEXT_L2_X -= 45.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)) - SCREEN_SCALE_X(85)); + break; + default: + CFont::SetRightJustifyWrap(0); + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::SetRightJustifyWrap(0); + 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::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_CR3")); + CFont::SetRightJustifyOn(); + switch (m_PrefsLanguage) + { + case LANGUAGE_GERMAN: + TEXT_SELECT_X += 20.0f; + break; + case LANGUAGE_SPANISH: + TEXT_SELECT_X += 15.0f; + break; + default: + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::SetJustifyOn(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + TEXT_R2_X += 30.0f; + CFont::SetJustifyOff(); + CFont::SetWrapx(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)) + SCREEN_SCALE_X(120)); + break; + default: + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::SetJustifyOn(); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); + 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(); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + TEXT_L2_X -= 45.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)) - SCREEN_SCALE_X(85)); + break; + default: + CFont::SetRightJustifyWrap(0); + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::SetRightJustifyWrap(0); + 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::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_CR3")); + switch (m_PrefsLanguage) + { + case LANGUAGE_GERMAN: + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_NA")); + break; + default: + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X - 50)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_NA")); + break; + } + CFont::SetJustifyOn(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + TEXT_R2_X += 30.0f; + CFont::SetJustifyOff(); + CFont::SetWrapx(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)) + SCREEN_SCALE_X(120)); + break; + default: + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::SetJustifyOn(); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); + 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(); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + TEXT_L2_X -= 45.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)) - SCREEN_SCALE_X(85)); + break; + default: + CFont::SetRightJustifyWrap(0); + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::SetRightJustifyWrap(0); + 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::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_CR3")); + CFont::SetRightJustifyOn(); + switch (m_PrefsLanguage) + { + case LANGUAGE_GERMAN: + TEXT_SELECT_X += 20.0f; + break; + case LANGUAGE_SPANISH: + TEXT_SELECT_X += 15.0f; + break; + default: + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::SetJustifyOn(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + TEXT_R2_X += 30.0f; + CFont::SetJustifyOff(); + CFont::SetWrapx(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)) + SCREEN_SCALE_X(120)); + break; + default: + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::SetJustifyOn(); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_TAR")); + CFont::SetRightJustifyOn(); + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); + 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(); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + TEXT_L2_X -= 45.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_L1_X)) - SCREEN_SCALE_X(85)); + break; + default: + CFont::SetRightJustifyWrap(0); + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L2_X)), MENU_Y(Y(TEXT_L2_Y)), TheText.Get("FEC_CWL")); + CFont::SetRightJustifyWrap(0); + 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::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_LSTICK_X)), MENU_Y(Y(TEXT_R3_Y)), TheText.Get("FEC_CR3")); + CFont::SetRightJustifyOn(); + switch (m_PrefsLanguage) + { + case LANGUAGE_GERMAN: + TEXT_SELECT_X += 20.0f; + break; + case LANGUAGE_SPANISH: + TEXT_SELECT_X += 15.0f; + break; + default: + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::SetJustifyOn(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_START_X)), MENU_Y(Y(TEXT_START_Y)), TheText.Get("FEC_PAU")); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + case LANGUAGE_SPANISH: + TEXT_R2_X += 30.0f; + CFont::SetJustifyOff(); + CFont::SetWrapx(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)) + SCREEN_SCALE_X(120)); + break; + default: + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R2_X)), MENU_Y(Y(TEXT_R2_Y)), TheText.Get("FEC_CWR")); + CFont::SetJustifyOn(); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_R1_X)), MENU_Y(Y(TEXT_R1_Y)), TheText.Get("FEC_ATT")); + CFont::SetRightJustifyOn(); + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_JUM")); + CFont::SetJustifyOn(); + 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::SetCentreOn(); + switch (m_PrefsLanguage) + { + case LANGUAGE_ITALIAN: + if (m_PrefsControllerType != CONTROLLER_XBOX360) + break; + case LANGUAGE_FRENCH: + case LANGUAGE_GERMAN: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + break; + default: + break; + } + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(0.0f)), MENU_Y(Y(TEXT_L2R2_Y)), TheText.Get("FEC_LB")); + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.9f), MENU_Y(SMALLESTTEXT_Y_SCALE* scale * 0.9f)); + switch (CPad::GetPad(0)->Mode) { + case 0: + CFont::SetRightJustifyOn(); + 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::SetRightJustifyOn(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_HO3")); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SELECT_X -= 5.0f; + break; + case LANGUAGE_GERMAN: + TEXT_SELECT_X += 20.0f; + break; + case LANGUAGE_SPANISH: + TEXT_SELECT_X += 15.0f; + break; + default: + break; + } + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)) - SCREEN_SCALE_X(80)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::SetJustifyOn(); + 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::SetJustifyOn(); + 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")); + CFont::SetRightJustifyOn(); + switch (m_PrefsControllerType) + { + case CONTROLLER_XBOXONE: + case CONTROLLER_XBOX360: + case CONTROLLER_NINTENDO_SWITCH: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SQUARE_X += 3.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(90)); + break; + case LANGUAGE_GERMAN: + case LANGUAGE_SPANISH: + TEXT_SQUARE_X += 18.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(90)); + break; + default: + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + } + break; + default: + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SQUARE_X -= 15.0f; + TEXT_SQUARE_Y += 5.0f; + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + case LANGUAGE_GERMAN: + TEXT_SQUARE_X -= 15.0f; + TEXT_SQUARE_Y += 10.0f; + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE* scale * 0.65f)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + case LANGUAGE_SPANISH: + TEXT_SQUARE_X += 15.0f; + case LANGUAGE_ITALIAN: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + default: + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(100)); + break; + } + break; + } + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + break; + case 1: + CFont::SetRightJustifyOn(); + 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::SetRightJustifyOn(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_NA")); + switch (m_PrefsLanguage) + { + case LANGUAGE_GERMAN: + TEXT_SELECT_X += 20.0f; + break; + case LANGUAGE_SPANISH: + TEXT_SELECT_X += 12.0f; + break; + default: + break; + } + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)) - SCREEN_SCALE_X(80)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_RSC")); + CFont::SetJustifyOn(); + 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::SetJustifyOn(); + 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")); + CFont::SetRightJustifyOn(); + switch (m_PrefsControllerType) + { + case CONTROLLER_XBOXONE: + case CONTROLLER_XBOX360: + case CONTROLLER_NINTENDO_SWITCH: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SQUARE_X += 3.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(90)); + break; + case LANGUAGE_GERMAN: + case LANGUAGE_SPANISH: + TEXT_SQUARE_X += 18.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(90)); + break; + default: + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + } + break; + default: + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SQUARE_X -= 15.0f; + TEXT_SQUARE_Y += 5.0f; + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + case LANGUAGE_GERMAN: + TEXT_SQUARE_X -= 15.0f; + TEXT_SQUARE_Y += 10.0f; + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + case LANGUAGE_SPANISH: + TEXT_SQUARE_X += 15.0f; + case LANGUAGE_ITALIAN: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + default: + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(100)); + break; + } + break; + } + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + break; + case 2: + CFont::SetRightJustifyOn(); + 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::SetRightJustifyOn(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_RS3")); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SELECT_X -= 5.0f; + break; + case LANGUAGE_GERMAN: + TEXT_SELECT_X += 20.0f; + break; + case LANGUAGE_SPANISH: + TEXT_SELECT_X += 15.0f; + break; + default: + break; + } + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)) - SCREEN_SCALE_X(80)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::SetJustifyOn(); + 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::SetJustifyOn(); + 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")); + CFont::SetRightJustifyOn(); + switch (m_PrefsControllerType) + { + case CONTROLLER_XBOXONE: + case CONTROLLER_XBOX360: + case CONTROLLER_NINTENDO_SWITCH: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SQUARE_X += 3.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(90)); + break; + case LANGUAGE_GERMAN: + case LANGUAGE_SPANISH: + TEXT_SQUARE_X += 18.0f; + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(90)); + break; + default: + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + } + break; + default: + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SQUARE_X -= 15.0f; + TEXT_SQUARE_Y += 5.0f; + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + case LANGUAGE_GERMAN: + TEXT_SQUARE_X -= 15.0f; + TEXT_SQUARE_Y += 10.0f; + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(60)); + break; + case LANGUAGE_SPANISH: + TEXT_SQUARE_X += 15.0f; + case LANGUAGE_ITALIAN: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + default: + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)) - SCREEN_SCALE_X(100)); + break; + } + break; + } + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_BRA")); + break; + case 3: + CFont::SetRightJustifyOn(); + 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::SetRightJustifyOn(); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_L3_X)), MENU_Y(Y(TEXT_L3_Y)), TheText.Get("FEC_HO3")); + switch (m_PrefsLanguage) + { + case LANGUAGE_FRENCH: + TEXT_SELECT_X -= 5.0f; + break; + case LANGUAGE_GERMAN: + TEXT_SELECT_X += 20.0f; + break; + case LANGUAGE_SPANISH: + TEXT_SELECT_X += 15.0f; + break; + default: + break; + } + CFont::SetRightJustifyWrap(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)) - SCREEN_SCALE_X(80)); + CFont::PrintString(MENU_X_LEFT_ALIGNED(X(TEXT_SELECT_X)), MENU_Y(Y(TEXT_SELECT_Y)), TheText.Get("FEC_CAM")); + CFont::SetJustifyOn(); + 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::SetJustifyOn(); + 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")); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0); + switch (m_PrefsControllerType) + { + case CONTROLLER_XBOXONE: + case CONTROLLER_XBOX360: + case CONTROLLER_NINTENDO_SWITCH: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_SMT")); + break; + default: + switch (m_PrefsLanguage) + { + case LANGUAGE_GERMAN: + TEXT_SQUARE_X += 5.0f; + case LANGUAGE_FRENCH: + case LANGUAGE_ITALIAN: + CFont::SetScale(MENU_X(SMALLESTTEXT_X_SCALE * 2 * scale * 0.65f), MENU_Y(SMALLESTTEXT_Y_SCALE * scale * 0.65f)); + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_SMT")); + break; + default: + CFont::PrintStringFromBottom(MENU_X_LEFT_ALIGNED(X(TEXT_SQUARE_X + 16.0f)), MENU_Y(Y(TEXT_SQUARE_Y)), TheText.Get("FEC_SMT")); + break; + } + break; + } + break; + default: + return; + } + } + + CFont::SetDropShadowPosition(0); // X + +#undef X +#undef Y +} + + +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 = MENUSPRITE_CONTROLLER; i <= MENUSPRITE_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("frontend2"); + 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 = MENUSPRITE_CONTROLLER; i <= MENUSPRITE_ARROWS4; i++) { + m_aFrontEndSprites[i].SetTexture(FrontendFilenames[i][0], FrontendFilenames[i][1]); + m_aFrontEndSprites[i].SetAddressing(rwTEXTUREADDRESSBORDER); + } +} +#endif // GAMEPAD_MENU + +#undef GetBackJustUp +#undef GetBackJustDown diff --git a/src/miami/core/Frontend.h b/src/miami/core/Frontend.h new file mode 100644 index 00000000..6fd3763e --- /dev/null +++ b/src/miami/core/Frontend.h @@ -0,0 +1,874 @@ +#pragma once +#ifdef PS2_MENU +#include "Frontend_PS2.h" +#else + +#include "Sprite2d.h" +#include "Timer.h" + +#define MENUHEADER_POS_X 10.0f +#define MENUHEADER_POS_Y 10.0f +#define MENUHEADER_HEIGHT 2.0f +#define MENUHEADER_WIDTH 1.0f + +#define MENU_X_MARGIN 10.0f + +#define MENUACTION_SCALE_MULT 0.9f + +#define MENUSLIDER_BARS 16 +#define MENUSLIDER_LOGICAL_BARS MENUSLIDER_BARS + +#define MENULABEL_X_MARGIN 80.0f +#define MENULABEL_POS_X 100.0f +#define MENULABEL_POS_Y 97.0f + +#define MENU_DEFAULT_CONTENT_X 320 +#define MENU_DEFAULT_CONTENT_Y 100 +#define MENU_DEFAULT_LINE_HEIGHT 29 + +#define RIGHT_ALIGNED_TEXT_RIGHT_MARGIN(xMargin) (xMargin + 30.0f) + +#define MENURADIO_ICON_FIRST_X 238.f +#ifdef EXTERNAL_3D_SOUND +#define MENURADIO_ICON_Y 288.0f +#else +#define MENURADIO_ICON_Y 248.0f +#endif +#define MENURADIO_ICON_SIZE 60.0f +#ifdef EXTERNAL_3D_SOUND +#define MENURADIO_SELECTOR_START_Y 285.f // other options should leave room on the screen +#else +#define MENURADIO_SELECTOR_START_Y 245.0f +#endif +#define MENURADIO_SELECTOR_HEIGHT 65.f + +#define MENUSLIDER_X 500.0f +#define MENUSLIDER_UNK 100.0f +#define MENUSLIDER_SMALLEST_BAR 8.0f +#define MENUSLIDER_BIGGEST_BAR 25.0f + +#define BIGTEXT2_X_SCALE 0.6f // For FONT_STANDARD +#define BIGTEXT2_Y_SCALE 1.2f +#define BIGTEXT_X_SCALE 0.6f // For FONT_HEADING +#define BIGTEXT_Y_SCALE 1.0f +#define MEDIUMTEXT_X_SCALE 0.48f // For FONT_STANDARD +#define MEDIUMTEXT_Y_SCALE 1.0f +#define SMALLTEXT_X_SCALE 0.42f // For FONT_STANDARD +#define SMALLTEXT_Y_SCALE 0.9f +#define SMALLESTTEXT_X_SCALE 0.3f // For FONT_STANDARD +#define SMALLESTTEXT_Y_SCALE 0.7f + +#define LISTITEM_X_SCALE 0.4f // Only unproportional and commonly used scale for FONT_STANDARD +#define LISTITEM_Y_SCALE 0.6f + +#define HELPER_TEXT_RIGHT_MARGIN MENU_X_MARGIN +#define HELPER_TEXT_BOTTOM_MARGIN 18.f + +#define BUILDID_TEXT_LEFT_MARGIN 320.0f +#define BUILDID_TEXT_BOTTOM_MARGIN 20.0f + +#define PLAYERSETUP_LIST_TOP 58.0f +#define PLAYERSETUP_LIST_BOTTOM 95.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 77 +#define PLAYERSETUP_ROW_HEIGHT 9 + +#define STATS_ROW_HEIGHT 17.0f +#define STATS_ROW_LEFT_MARGIN 110.0f +#define STATS_ROW_RIGHT_MARGIN 113.0f +#define STATS_TOP_Y 135.0f // Just faded in +#define STATS_BOTTOM_Y 300.0f // Starts to fade out after that +#define STATS_FADING_AREA_LENGTH 50.0f +#define STATS_VISIBLE_START_Y (STATS_TOP_Y - 10.f) +#define STATS_VISIBLE_END_Y (STATS_BOTTOM_Y + 21.f) +#define STATS_RATING_X 320.0f +#define STATS_RATING_Y_1 85.0f +#define STATS_RATING_Y_2 110.0f + +#define BRIEFS_TOP_MARGIN 140.0f +#define BRIEFS_BOTTOM_MARGIN 280.0f +#define BRIEFS_LINE_X 100.0f +#define BRIEFS_LINE_HEIGHT 20.0f +#define BRIEFS_LINE_SPACING 10.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_TOP 58.0f +#define CONTSETUP_LIST_RIGHT 18.0f +#define CONTSETUP_LIST_BOTTOM 78.0f +#define CONTSETUP_LIST_LEFT 30.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 82.0f +#define CONTSETUP_BACK_HEIGHT 25.0f + +enum +{ + MENUALIGN_LEFT = 1, + MENUALIGN_RIGHT, + MENUALIGN_CENTER, +}; + +enum eMenuSprites +{ + MENUSPRITE_BACKGROUND, + MENUSPRITE_VCLOGO, + MENUSPRITE_MOUSE, + MENUSPRITE_MAPTOP01, + MENUSPRITE_MAPTOP02, + MENUSPRITE_MAPTOP03, + MENUSPRITE_MAPMID01, + MENUSPRITE_MAPMID02, + MENUSPRITE_MAPMID03, + MENUSPRITE_MAPBOT01, + MENUSPRITE_MAPBOT02, + MENUSPRITE_MAPBOT03, + MENUSPRITE_WILDSTYLE, + MENUSPRITE_FLASH, + MENUSPRITE_KCHAT, + MENUSPRITE_FEVER, + MENUSPRITE_VROCK, + MENUSPRITE_VCPR, + MENUSPRITE_ESPANTOSO, + MENUSPRITE_EMOTION, + MENUSPRITE_WAVE, + MENUSPRITE_MP3, + MENUSPRITE_DOWNOFF, + MENUSPRITE_DOWNON, + MENUSPRITE_UPOFF, + MENUSPRITE_UPON, +#ifdef GAMEPAD_MENU + MENUSPRITE_CONTROLLER, + MENUSPRITE_ARROWS1, + MENUSPRITE_ARROWS2, + MENUSPRITE_ARROWS3, + MENUSPRITE_ARROWS4, +#endif + 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 +}; + +enum eMenuScreen +{ + MENUPAGE_DISABLED = -1, + MENUPAGE_STATS = 0, + MENUPAGE_NEW_GAME = 1, + MENUPAGE_BRIEFS = 2, + MENUPAGE_SOUND_SETTINGS = 3, + MENUPAGE_DISPLAY_SETTINGS = 4, + MENUPAGE_LANGUAGE_SETTINGS = 5, + MENUPAGE_MAP = 6, + MENUPAGE_NEW_GAME_RELOAD = 7, + MENUPAGE_CHOOSE_LOAD_SLOT = 8, + MENUPAGE_CHOOSE_DELETE_SLOT = 9, + MENUPAGE_LOAD_SLOT_CONFIRM = 10, + MENUPAGE_DELETE_SLOT_CONFIRM = 11, + MENUPAGE_LOADING_IN_PROGRESS = 12, + MENUPAGE_DELETING_IN_PROGRESS = 13, + MENUPAGE_DELETE_SUCCESSFUL = 14, + MENUPAGE_CHOOSE_SAVE_SLOT = 15, + MENUPAGE_SAVE_OVERWRITE_CONFIRM = 16, + MENUPAGE_SAVING_IN_PROGRESS = 17, + MENUPAGE_SAVE_SUCCESSFUL = 18, + MENUPAGE_SAVE_CUSTOM_WARNING = 19, + MENUPAGE_SAVE_CHEAT_WARNING = 20, + MENUPAGE_SKIN_SELECT = 21, + MENUPAGE_SAVE_UNUSED = 22, + MENUPAGE_SAVE_FAILED = 23, + MENUPAGE_SAVE_FAILED_2 = 24, + MENUPAGE_LOAD_FAILED = 25, + MENUPAGE_CONTROLLER_PC = 26, + MENUPAGE_OPTIONS = 27, + MENUPAGE_EXIT = 28, + MENUPAGE_START_MENU = 29, + MENUPAGE_KEYBOARD_CONTROLS = 30, + MENUPAGE_MOUSE_CONTROLS = 31, + MENUPAGE_PAUSE_MENU = 32, + MENUPAGE_NONE = 33, // Then chooses main menu or pause menu +#ifdef GAMEPAD_MENU + MENUPAGE_CONTROLLER_SETTINGS, +#endif +#ifdef LEGACY_MENU_OPTIONS + MENUPAGE_DEBUG_MENU, + MENUPAGE_CONTROLLER_PC_OLD1, + MENUPAGE_CONTROLLER_PC_OLD2, + MENUPAGE_CONTROLLER_PC_OLD3, + MENUPAGE_CONTROLLER_PC_OLD4, + MENUPAGE_CONTROLLER_DEBUG, +#endif +#ifdef CUSTOM_FRONTEND_OPTIONS + +#ifdef GRAPHICS_MENU_OPTIONS + MENUPAGE_GRAPHICS_SETTINGS, +#endif +#ifdef DETECT_JOYSTICK_MENU + MENUPAGE_DETECT_JOYSTICK, +#endif +#endif +#ifdef MISSION_REPLAY + MENUPAGE_MISSION_RETRY, +#endif + + MENUPAGE_OUTRO, // Originally 34, but CFO needs last screen to be empty to count number of menu pages + 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_YES, + MENUACTION_NO, + MENUACTION_CHANGEMENU, + MENUACTION_INVERTPADY, + MENUACTION_CTRLDISPLAY, + MENUACTION_FRAMESYNC, + MENUACTION_FRAMELIMIT, + MENUACTION_TRAILS, + MENUACTION_SUBTITLES, + MENUACTION_WIDESCREEN, + MENUACTION_BRIGHTNESS, + MENUACTION_MUSICVOLUME, + MENUACTION_SFXVOLUME, + MENUACTION_RADIO, + MENUACTION_LANG_ENG, + MENUACTION_LANG_FRE, + MENUACTION_LANG_GER, + MENUACTION_LANG_ITA, + MENUACTION_LANG_SPA, + MENUACTION_POPULATESLOTS_CHANGEMENU, + MENUACTION_CHECKSAVE, + MENUACTION_NEWGAME, + MENUACTION_RESUME_FROM_SAVEZONE, + MENUACTION_RELOADIDE, + MENUACTION_SETDBGFLAG, + MENUACTION_LOADRADIO, + MENUACTION_SAVEGAME, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, + MENUACTION_COLLISIONPOLYS, + MENUACTION_LEGENDS, + MENUACTION_RADARMODE, + MENUACTION_HUD, + MENUACTION_GOBACK, + MENUACTION_KEYBOARDCTRLS, + MENUACTION_GETKEY, + MENUACTION_SHOWHEADBOB, + MENUACTION_UNK38, // MENUACTION_PARSEHEAP? MENUACTION_DEBUGSTREAM? MENUACTION_MEMCARDSAVECONFIRM? + MENUACTION_INVVERT, + MENUACTION_CANCELGAME, + MENUACTION_RESUME, + MENUACTION_DONTCANCEL, + MENUACTION_SCREENRES, + MENUACTION_AUDIOHW, + MENUACTION_SPEAKERCONF, + MENUACTION_PLAYERSETUP, + MENUACTION_RESTOREDEF, + MENUACTION_CTRLMETHOD, + MENUACTION_DYNAMICACOUSTIC, + MENUACTION_MOUSESTEER, + MENUACTION_DRAWDIST, + MENUACTION_MOUSESENS, + MENUACTION_MP3VOLUMEBOOST, +#ifdef GAMEPAD_MENU + MENUACTION_CTRLVIBRATION, + MENUACTION_CTRLCONFIG, +#endif +#ifdef MISSION_REPLAY + MENUACTION_REJECT_RETRY, + MENUACTION_UNK114 +#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_NEXT_RADIO, + HOVEROPTION_PREV_RADIO, + 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, + HOVEROPTION_INCREASE_MP3BOOST, + HOVEROPTION_DECREASE_MP3BOOST, +#ifdef CUSTOM_FRONTEND_OPTIONS + HOVEROPTION_INCREASE_CFO_SLIDER, + HOVEROPTION_DECREASE_CFO_SLIDER, +#endif + HOVEROPTION_NOT_HOVERING, +}; + +enum +{ +#if defined LEGACY_MENU_OPTIONS || defined CUSTOM_FRONTEND_OPTIONS + NUM_MENUROWS = 18, +#else + NUM_MENUROWS = 12, +#endif +}; + +enum eControlMethod +{ + CONTROL_STANDARD = 0, + CONTROL_CLASSIC, +}; + +// Why?? +enum ControllerSetupColumn +{ + CONTSETUP_PED_COLUMN = 0, + CONTSETUP_VEHICLE_COLUMN = 16, +}; + +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 m_PreviousPage; // eMenuScreen + int32 m_ParentEntry; // row + + struct CMenuEntry + { + int32 m_Action; // eMenuAction + char m_EntryName[8]; + int32 m_SaveSlot; // eSaveSlot + int32 m_TargetMenu; // eMenuScreen + uint16 m_X; + uint16 m_Y; + uint8 m_Align; + } m_aEntries[NUM_MENUROWS]; +}; +extern CMenuScreen aScreens[MENUPAGES]; +#else +#include "frontendoption.h" +struct CCustomScreenLayout { + int startX; // not used at all if first entry has X and Y values + int startY; // not used at all if first entry has X and Y values + int lineHeight; // used to determine next entry's Y coordinate, if it has 0-0 as coordinates + bool showLeftRightHelper; + bool noInvasiveBorders; // not needed on pages already handled by game + int xMargin; // useful for two part texts - 0/empty = MENU_X_MARGIN +}; + +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; // 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 + }; + uint16 m_X; + uint16 m_Y; + uint8 m_Align; + } m_aEntries[NUM_MENUROWS]; +}; +extern CMenuScreenCustom aScreens[MENUPAGES]; +#endif + +struct MenuTrapezoid +{ + float topLeft_x; + float topLeft_y; + float topRight_x; + float topRight_y; + float bottomLeft_x; + float bottomLeft_y; + float bottomRight_x; + float bottomRight_y; + float old_topRight_x; + float old_topRight_y; + float old_topLeft_x; + float old_topLeft_y; + float old_bottomLeft_x; + float old_bottomLeft_y; + float old_bottomRight_x; + float old_bottomRight_y; + float mult_topRight_x; + float mult_topRight_y; + float mult_topLeft_x; + float mult_topLeft_y; + float mult_bottomLeft_x; + float mult_bottomLeft_y; + float mult_bottomRight_x; + float mult_bottomRight_y; + + MenuTrapezoid(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { + topLeft_x = x1; + topLeft_y = y1; + topRight_x = x2; + topRight_y = y2; + bottomLeft_x = x3; + bottomLeft_y = y3; + bottomRight_x = x4; + bottomRight_y = y4; + }; + + void SaveCurrentCoors() { + old_topLeft_x = topLeft_x; + old_topLeft_y = topLeft_y; + old_topRight_x = topRight_x; + old_topRight_y = topRight_y; + old_bottomLeft_x = bottomLeft_x; + old_bottomLeft_y = bottomLeft_y; + old_bottomRight_x = bottomRight_x; + old_bottomRight_y = bottomRight_y; + } + + void Translate(int delta) { + bottomRight_x = delta * mult_bottomRight_x + old_bottomRight_x; + bottomRight_y = delta * mult_bottomRight_y + old_bottomRight_y; + bottomLeft_x = delta * mult_bottomLeft_x + old_bottomLeft_x; + bottomLeft_y = delta * mult_bottomLeft_y + old_bottomLeft_y; + topRight_x = delta * mult_topRight_x + old_topRight_x; + topRight_y = delta * mult_topRight_y + old_topRight_y; + topLeft_x = delta * mult_topLeft_x + old_topLeft_x; + topLeft_y = delta * mult_topLeft_y + old_topLeft_y; + } + + void UpdateMultipliers() { + mult_bottomRight_x = (bottomRight_x - old_bottomRight_x) / 255.0f; + mult_bottomRight_y = (bottomRight_y - old_bottomRight_y) / 255.0f; + mult_bottomLeft_x = (bottomLeft_x - old_bottomLeft_x) / 255.0f; + mult_bottomLeft_y = (bottomLeft_y - old_bottomLeft_y) / 255.0f; + mult_topRight_x = (topRight_x - old_topRight_x) / 255.0f; + mult_topRight_y = (topRight_y - old_topRight_y) / 255.0f; + mult_topLeft_x = (topLeft_x - old_topLeft_x) / 255.0f; + mult_topLeft_y = (topLeft_y - old_topLeft_y) / 255.0f; + } +}; + +class CMenuManager +{ +public: + int8 m_StatsScrollDirection; + float m_StatsScrollSpeed; + uint8 field_8; + bool m_PrefsUseVibration; + bool m_PrefsShowHud; + int32 m_PrefsRadarMode; + bool m_DisplayControllerOnFoot; + bool m_bShutDownFrontEndRequested; + bool m_bStartUpFrontEndRequested; + int32 m_KeyPressedCode; + int32 m_PrefsBrightness; + float m_PrefsLOD; + int8 m_PrefsShowSubtitles; + int8 m_PrefsShowLegends; + int8 m_PrefsUseWideScreen; + int8 m_PrefsVsync; + int8 m_PrefsVsyncDisp; + int8 m_PrefsFrameLimiter; + int8 m_nPrefsAudio3DProviderIndex; + int8 m_PrefsSpeakers; + int8 m_PrefsDMA; + int8 m_PrefsSfxVolume; + int8 m_PrefsMusicVolume; + int8 m_PrefsRadioStation; + uint8 m_PrefsStereoMono; // unused except restore settings + int32 m_nCurrOption; + bool m_bQuitGameNoCD; + bool m_bMenuMapActive; + bool m_AllowNavigation; + uint8 field_37; + bool m_bMenuActive; + bool m_bWantToRestart; + bool m_bFirstTime; + bool m_bActivateSaveMenu; + bool m_bWantToLoad; + float m_fMapSize; + float m_fMapCenterX; + float m_fMapCenterY; + uint32 OS_Language; + int32 m_PrefsLanguage; + int32 field_54; + int8 m_bLanguageLoaded; + uint8 m_PrefsAllowNastyGame; + int8 m_PrefsMP3BoostVolume; + int8 m_ControlMethod; + int32 m_nPrefsVideoMode; + int32 m_nDisplayVideoMode; + int32 m_nMouseTempPosX; + int32 m_nMouseTempPosY; + bool m_bGameNotLoaded; + int8 m_lastWorking3DAudioProvider; + bool m_bFrontEnd_ReloadObrTxtGxt; + int32 *pEditString; + uint8 field_74[4]; + int32 *pControlEdit; + bool m_OnlySaveMenu; + int32 m_firstStartCounter; + CSprite2d m_aFrontEndSprites[NUM_MENU_SPRITES]; + bool m_bSpritesLoaded; + int32 m_LeftMostRadioX; + int32 m_ScrollRadioBy; + int32 m_nCurrScreen; + int32 m_nPrevScreen; + int32 m_nCurrSaveSlot; + uint32 m_LastScreenSwitch; + int32 m_nMenuFadeAlpha; + int32 m_nOptionHighlightTransitionBlend; + bool bMenuChangeOngoing; + int32 MouseButtonJustClicked; + int32 JoyButtonJustClicked; + bool DisplayComboButtonErrMsg; + bool m_NoEmptyBinding; + bool m_ShowEmptyBindingError; + int32 m_nHelperTextAlpha; + bool m_bPressedPgUpOnList; + bool m_bPressedPgDnOnList; + bool m_bPressedUpOnList; + bool m_bPressedDownOnList; + bool m_bPressedScrollButton; + uint8 field_129; + uint8 field_12A; + uint8 field_12B; + int32 m_nMousePosX; + int32 m_nMousePosY; + int32 m_nMouseOldPosX; + int32 m_nMouseOldPosY; + int32 m_nHoverOption; + bool m_bShowMouse; + int32 m_nOptionMouseHovering; + bool m_bStartWaitingForKeyBind; + bool m_bWaitingForNewKeyBind; + bool m_bKeyChangeNotProcessed; + int32 m_CurrCntrlAction; + uint8 field_150; + uint8 field_151; + uint8 field_152; + uint8 field_153; + int32 m_nSelectedContSetupColumn; + bool m_bKeyIsOK; + bool field_159; + uint8 m_nCurrExLayer; + char m_PrefsSkinFile[256]; + char m_aSkinName[256]; + uint8 field_35B; + int32 m_nHelperTextMsgId; + tSkinInfo m_pSkinListHead; + tSkinInfo *m_pSelectedSkin; + int32 m_nFirstVisibleRowOnList; + float m_nScrollbarTopMargin; + int32 m_nTotalListRow; + int32 m_nSkinsTotal; + uint8 field_67C[4]; + int32 m_nSelectedListRow; + bool m_bSkinsEnumerated; + +#ifdef IMPROVED_VIDEOMODE + int32 m_nPrefsWidth; + int32 m_nPrefsHeight; + int32 m_nPrefsDepth; + int32 m_nPrefsWindowed; + int32 m_nPrefsSubsystem; + int32 m_nSelectedScreenMode; +#endif +#ifdef MULTISAMPLING + int8 m_nPrefsMSAALevel; + int8 m_nDisplayMSAALevel; +#endif + +#ifdef MISSION_REPLAY + bool m_bAttemptingMissionRetry; +#endif + +#ifdef GAMEPAD_MENU + enum + { + CONTROLLER_DUALSHOCK2 = 0, + CONTROLLER_DUALSHOCK3, + CONTROLLER_DUALSHOCK4, + CONTROLLER_XBOX360, + CONTROLLER_XBOXONE, + CONTROLLER_NINTENDO_SWITCH, + }; + + int8 m_PrefsControllerType; +#endif + enum LANGUAGE + { + LANGUAGE_AMERICAN, + LANGUAGE_FRENCH, + LANGUAGE_GERMAN, + LANGUAGE_ITALIAN, + LANGUAGE_SPANISH, +#ifdef MORE_LANGUAGES + LANGUAGE_POLISH, + LANGUAGE_RUSSIAN, + LANGUAGE_JAPANESE, +#endif + }; + bool GetIsMenuActive() {return !!m_bMenuActive;} + +#ifdef CUTSCENE_BORDERS_SWITCH + static bool m_PrefsCutsceneBorders; +#endif + +#ifndef MASTER + static bool m_PrefsMarketing; + static bool m_PrefsDisableTutorials; +#endif // !MASTER + + CMenuManager(void); + ~CMenuManager(void) { UnloadTextures(); } + +#ifdef NO_ISLAND_LOADING + enum + { + ISLAND_LOADING_LOW = 0, + ISLAND_LOADING_MEDIUM, + ISLAND_LOADING_HIGH + }; + + int8 m_PrefsIslandLoading; + + #define ISLAND_LOADING_IS(p) if (FrontEndMenuManager.m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_##p) + #define ISLAND_LOADING_ISNT(p) if (FrontEndMenuManager.m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_##p) +#else + #define ISLAND_LOADING_IS(p) + #define ISLAND_LOADING_ISNT(p) +#endif + +#ifdef XBOX_MESSAGE_SCREEN + static uint32 m_nDialogHideTimer; + static uint32 m_nDialogHideTimerPauseMode; + static bool m_bDialogOpen; + static wchar *m_pDialogText; + static bool m_bSaveWasSuccessful; + + static void SetDialogText(const char*); + static bool DialogTextCmp(const char*); + static void ToggleDialog(bool); + static void SetDialogTimer(uint32); + void ProcessDialogTimer(void); + void DrawOverlays(void); + void CloseDialog(void); +#endif + + void Initialise(); + void PrintMap(); + void SetFrontEndRenderStates(); + static void CentreMousePointer(); + void CheckCodesForControls(int); + bool CheckHover(int x1, int x2, int y1, int y2); + void CheckSliderMovement(int); + void DisplayHelperText(char*); + int DisplaySlider(float, float, float, float, float, float, float); + void DoSettingsBeforeStartingAGame(); + void DrawStandardMenus(bool); + void DrawControllerBound(int32, int32, int32, int8); + void DrawControllerScreenExtraText(int, int, int); + void DrawControllerSetupScreen(); + void DrawQuitGameScreen(); + void DrawFrontEnd(); + void DrawBackground(bool transitionCall); + void DrawPlayerSetupScreen(bool); + int FadeIn(int alpha); + int GetStartOptionsCntrlConfigScreens(); + void InitialiseChangedLanguageSettings(); + void LoadAllTextures(); + void LoadSettings(); + void MessageScreen(const char *str, bool); + void SmallMessageScreen(const char *str); + void PrintBriefs(); + static void PrintErrorMessage(); + void PrintStats(); + void Process(); + void ProcessList(bool &optionSelected, bool &goBack); + void UserInput(); + void ProcessUserInput(uint8, uint8, uint8, uint8, int8); + void ChangeRadioStation(int8); + void ProcessFileActions(); + void ProcessOnOffMenuOptions(); + void RequestFrontEndShutDown(); + void RequestFrontEndStartUp(); + void ResetHelperText(); + void SaveSettings(); + void SetHelperText(int text); + float StretchX(float); + float StretchY(float); + void SwitchMenuOnAndOff(); + void UnloadTextures(); + void WaitForUserCD(); + int GetNumOptionsCntrlConfigScreens(); + void SwitchToNewScreen(int8); + void AdditionalOptionInput(bool &goBack); + void ExportStats(void); + void PrintRadioSelector(void); + + // New (not in function or inlined in the game) + void ThingsToDoBeforeLeavingPage(); + void ScrollUpListByOne(); + void ScrollDownListByOne(); + void PageUpList(bool); + void PageDownList(bool); + int8 GetPreviousPageOption(); + + // uint8 GetNumberOfMenuOptions(); +#ifdef GAMEPAD_MENU + void LoadController(int8 type); + void PrintController(void); +#endif +}; + +#ifndef IMPROVED_VIDEOMODE +VALIDATE_SIZE(CMenuManager, 0x688); +#endif + +extern CMenuManager FrontEndMenuManager; + +#endif diff --git a/src/miami/core/FrontendTriggers.h b/src/miami/core/FrontendTriggers.h new file mode 100644 index 00000000..44bae54f --- /dev/null +++ b/src/miami/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/miami/core/Frontend_PS2.cpp b/src/miami/core/Frontend_PS2.cpp new file mode 100644 index 00000000..fa238031 --- /dev/null +++ b/src/miami/core/Frontend_PS2.cpp @@ -0,0 +1,3020 @@ +#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", "" }, +}; + +#ifdef CUTSCENE_BORDERS_SWITCH +bool CMenuManager::m_PrefsCutsceneBorders = true; +#endif + +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); + + if (CStats::LongestFlightInDodo > 0) + STAT_LINE("FEST_LF", &CStats::LongestFlightInDodo, 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::DistanceTravelledByCar, 0, nil); + STAT_LINE("DISTBIM", &CStats::DistanceTravelledByBike, 0, nil); + STAT_LINE("DISTBOM", &CStats::DistanceTravelledByBoat, 0, nil); + STAT_LINE("DISTGOM", &CStats::DistanceTravelledByGolfCart, 0, nil); + STAT_LINE("DISTHEM", &CStats::DistanceTravelledByHelicoptor, 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/miami/core/Frontend_PS2.h b/src/miami/core/Frontend_PS2.h new file mode 100644 index 00000000..6311d821 --- /dev/null +++ b/src/miami/core/Frontend_PS2.h @@ -0,0 +1,247 @@ +#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; + +#ifdef CUTSCENE_BORDERS_SWITCH + static bool m_PrefsCutsceneBorders; +#endif + +#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/miami/core/Game.cpp b/src/miami/core/Game.cpp new file mode 100644 index 00000000..77c8965f --- /dev/null +++ b/src/miami/core/Game.cpp @@ -0,0 +1,1352 @@ +#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 "MemoryHeap.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 "SetPieces.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 "Occlusion.h" +#include "debugmenu.h" +#include "Ropes.h" +#include "WindModifiers.h" +#include "WaterCreatures.h" +#include "postfx.h" +#include "custompipes.h" +#include "screendroplets.h" +#include "VarConsole.h" +#ifdef USE_TEXTURE_POOL +#include "TexturePools.h" +#endif + +eLevelName CGame::currLevel; +int32 CGame::currArea; +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 +#ifndef MASTER +CVector CGame::PlayerCoords; +bool8 CGame::VarUpdatePlayerCoords; +#endif + +int gameTxdSlot; + +#ifdef SECUROM +uint8 gameProcessPirateCheck = 0; +#endif + +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) +{ + //TODO: stretch_screen + + 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); + splash->DrawRect(CRect(SCREEN_SCALE_X(20.0f), SCREEN_SCALE_Y(110.0f), SCREEN_SCALE_X(620.0f), SCREEN_SCALE_Y(300.0f)), CRGBA(50, 50, 50, 192)); + + CFont::SetFontStyle(FONT_BANK); + CFont::SetBackgroundOff(); + CFont::SetWrapx(SCREEN_SCALE_FROM_RIGHT(190.0f)); // 450.0f + CFont::SetScale(SCREEN_SCALE_X(1.0f), SCREEN_SCALE_Y(1.0f)); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(450.0f)); + CFont::SetJustifyOff(); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + CFont::SetDropColor(CRGBA(32, 32, 32, 255)); + CFont::SetDropShadowPosition(3); + CFont::SetPropOn(); + CFont::PrintString(SCREEN_SCALE_X(320.0f), SCREEN_SCALE_Y(130.0f), TheText.Get(msg)); + CFont::DrawFonts(); + + DoRWStuffEndOfFrame(); +} +#endif + +bool +CGame::InitialiseOnceBeforeRW(void) +{ + CFileMgr::Initialise(); + CdStreamInit(MAX_CDCHANNELS); + debug("size of matrix %d\n", sizeof(CMatrix)); + debug("size of placeable %d\n", sizeof(CPlaceable)); + debug("size of entity %d\n", sizeof(CEntity)); + debug("size of building %d\n", sizeof(CBuilding)); + debug("size of dummy %d\n", sizeof(CDummy)); +#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) +{ + ValidateVersion(); +#ifdef USE_TEXTURE_POOL + _TexturePoolsInitialise(); +#endif + + CTxdStore::Initialise(); + CVisibilityPlugins::Initialise(); + +#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); + 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); + + CreateDebugFont(); + +#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 = { 64, 64, 64, 255 }; + rw::MatFX::envMapColor = envcol; +#else +#ifdef PS2_MATFX + ReplaceMatFxCallback(); +#endif // PS2_MATFX +#ifdef PS2_ALPHA_TEST + ReplaceAtomicPipeCallback(); +#endif // PS2_ALPHA_TEST +#endif // LIBRW + + PUSH_MEMID(MEMID_TEXTURES); + CFont::Initialise(); + CHud::Initialise(); + CPlayerSkin::Initialise(); + POP_MEMID(); + +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeInit(); // need Scene.world for this +#endif +#ifdef SCREEN_DROPLETS + ScreenDroplets::InitDraw(); +#endif + + return (true); +} + +void CGame::ShutdownRenderWare(void) +{ +#ifdef SCREEN_DROPLETS + ScreenDroplets::Shutdown(); +#endif +#ifdef EXTENDED_PIPELINES + CustomPipes::CustomPipeShutdown(); +#endif + + DestroySplashScreen(); + CHud::Shutdown(); + CFont::Shutdown(); + + for ( int32 i = 0; i < NUMPLAYERS; i++ ) + CWorld::Players[i].DeletePlayerSkin(); + + 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 +} + +bool CGame::InitialiseOnceAfterRW(void) +{ + TheText.Load(); + CTimer::Initialise(); + CTempColModels::Initialise(); + mod_HandlingManager.Initialise(); + CSurfaceTable::Initialise("DATA\\SURFACE.DAT"); + CPedStats::Initialise(); + CTimeCycle::Initialise(); +#ifdef GTA_PS2 + LoadingScreen("Loading the Game", "Initialising audio", GetRandomSplashScreen()); +#endif + DMAudio.Initialise(); + +#ifndef GTA_PS2 +#ifdef EXTERNAL_3D_SOUND + if ( DMAudio.GetNum3DProvidersAvailable() == 0 ) + FrontEndMenuManager.m_nPrefsAudio3DProviderIndex = NO_AUDIO_PROVIDER; + + if ( FrontEndMenuManager.m_nPrefsAudio3DProviderIndex == AUDIO_PROVIDER_NOT_DETERMINED || FrontEndMenuManager.m_nPrefsAudio3DProviderIndex == -2 ) + { + FrontEndMenuManager.m_PrefsSpeakers = 0; + FrontEndMenuManager.m_nPrefsAudio3DProviderIndex = DMAudio.AutoDetect3DProviders(); + } + + DMAudio.SetCurrent3DProvider(FrontEndMenuManager.m_nPrefsAudio3DProviderIndex); + DMAudio.SetSpeakerConfig(FrontEndMenuManager.m_PrefsSpeakers); +#endif + DMAudio.SetDynamicAcousticModelingStatus(FrontEndMenuManager.m_PrefsDMA); + DMAudio.SetMusicMasterVolume(FrontEndMenuManager.m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(FrontEndMenuManager.m_PrefsSfxVolume); + DMAudio.SetEffectsFadeVol(127); + DMAudio.SetMusicFadeVol(127); +#endif + return true; +} + +void +CGame::FinalShutdown(void) +{ + CTxdStore::Shutdown(); + CPedStats::Shutdown(); + CdStreamShutdown(); +} + +bool CGame::Initialise(const char* datFile) +{ + ResetLoadingScreenBar(); + strcpy(aDatFile, datFile); + +#ifdef GTA_PS2 + // TODO: upload VU0 collision code here +#endif + + CPools::Initialise(); + +#ifndef GTA_PS2 +#ifdef PED_CAR_DENSITY_SLIDERS + // Load density values from gta3.ini only if our reVC.ini have them 0.6f + if (CIniFile::PedNumberMultiplier == 0.6f && CIniFile::CarNumberMultiplier == 0.6f) +#endif + CIniFile::LoadIniFile(); +#endif +#ifdef USE_TEXTURE_POOL + _TexturePoolsUnknown(false); +#endif + currLevel = LEVEL_BEACH; + currArea = AREA_MAIN_MAP; + + 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); + CScriptPaths::Init(); + CWeather::Init(); + CCullZones::Init(); + COcclusion::Init(); + CCollision::Init(); + CSetPieces::Init(); + CTheZones::Init(); + CUserDisplay::Init(); + CMessages::Init(); + CMessages::ClearAllMessagesDisplayedByGame(); + CRecordDataForGame::Init(); + CRestart::Initialise(); + + PUSH_MEMID(MEMID_WORLD); + CWorld::Initialise(); + POP_MEMID(); + + PUSH_MEMID(MEMID_TEXTURES); + CParticle::Initialise(); + POP_MEMID(); + + PUSH_MEMID(MEMID_ANIMATION); + CAnimManager::Initialise(); + CCutsceneMgr::Initialise(); + POP_MEMID(); + + PUSH_MEMID(MEMID_CARS); + CCarCtrl::Init(); + POP_MEMID(); + + PUSH_MEMID(MEMID_DEF_MODELS); + InitModelIndices(); + CModelInfo::Initialise(); + CPickups::Init(); + CTheCarGenerators::Init(); + + CdStreamAddImage("MODELS\\GTA3.IMG"); + + CFileLoader::LoadLevel("DATA\\DEFAULT.DAT"); + CFileLoader::LoadLevel(datFile); + + LoadingScreen("Loading the Game", "Add Particles", nil); + CWorld::AddParticles(); + CVehicleModelInfo::LoadVehicleColours(); + CVehicleModelInfo::LoadEnvironmentMaps(); + CTheZones::PostZoneCreation(); + POP_MEMID(); + + LoadingScreen("Loading the Game", "Setup paths", nil); + ThePaths.PreparePathData(); + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); + CWorld::Players[0].LoadPlayerSkin(); + TestModelIndices(); + + LoadingScreen("Loading the Game", "Setup water", nil); + CWaterLevel::Initialise("DATA\\WATER.DAT"); + TheConsole.Init(); + CDraw::SetFOV(120.0f); + CDraw::ms_fLODDistance = 500.0f; + + LoadingScreen("Loading the Game", "Setup streaming", nil); + CStreaming::LoadInitialVehicles(); + CStreaming::LoadInitialPeds(); + CStreaming::RequestBigBuildings(LEVEL_GENERIC); + CStreaming::LoadAllRequestedModels(false); + CStreaming::RemoveIslandsNotUsed(currLevel); + printf("Streaming uses %zuK of its memory", CStreaming::ms_memoryUsed / 1024); // original modifier was %d + + LoadingScreen("Loading the Game", "Load animations", GetRandomSplashScreen()); + PUSH_MEMID(MEMID_ANIMATION); + CAnimManager::LoadAnimFiles(); + POP_MEMID(); + + CStreaming::LoadInitialWeapons(); + CStreaming::LoadAllRequestedModels(0); + 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(); + 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); + CClock::Initialise(1000); + CHeli::InitHelis(); + CCranes::InitCranes(); + CMovingThings::Init(); + CDarkel::Init(); + CStats::Init(); + CPacManPickups::Init(); + CRubbish::Init(); + CClouds::Init(); + CSpecialFX::Init(); + CRopes::Init(); + CWaterCannons::Init(); + CBridge::Init(); + CGarages::Init(); + + LoadingScreen("Loading the Game", "Position dynamic objects", nil); + LoadingScreen("Loading the Game", "Initialise vehicle paths", nil); + + 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); + CCollision::ms_collisionInMemory = currLevel; + for (int i = 0; i < MAX_PADS; i++) + CPad::GetPad(i)->Clear(true); +#ifdef USE_TEXTURE_POOL + _TexturePoolsUnknown(true); +#endif + +#ifndef MASTER + PlayerCoords = FindPlayerCoors(); + VarConsole.Add("X PLAYER COORD", &PlayerCoords.x, 10.0f, -10000.0f, 10000.0f, true); + VarConsole.Add("Y PLAYER COORD", &PlayerCoords.y, 10.0f, -10000.0f, 10000.0f, true); + VarConsole.Add("Z PLAYER COORD", &PlayerCoords.z, 10.0f, -10000.0f, 10000.0f, true); + VarConsole.Add("UPDATE PLAYER COORD", &VarUpdatePlayerCoords, true); +#endif + + + DMAudio.SetStartingTrackPositions(TRUE); + DMAudio.ChangeMusicMode(MUSICMODE_GAME); + return true; +} + +bool CGame::ShutDown(void) +{ +#ifdef USE_TEXTURE_POOL + _TexturePoolsUnknown(false); +#endif + CReplay::FinishPlayback(); + CReplay::EmptyReplayBuffer(); + CPlane::Shutdown(); + CTrain::Shutdown(); + CScriptPaths::Shutdown(); + CWaterCreatures::RemoveAll(); + CSpecialFX::Shutdown(); + CGarages::Shutdown(); + CMovingThings::Shutdown(); + gPhoneInfo.Shutdown(); + CWeapon::ShutdownWeapons(); + CPedType::Shutdown(); + + 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(); + CPools::ShutDown(); + CHud::ReInitialise(); + CTxdStore::RemoveTxdSlot(gameTxdSlot); + CMBlur::MotionBlurClose(); + CdStreamRemoveImages(); +#ifdef USE_TEXTURE_POOL + _TexturePoolsFinalShutdown(); +#endif + 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(); + CCarCtrl::ReInit(); + CTimeCycle::Initialise(); + CDraw::SetFOV(120.0f); + CDraw::ms_fLODDistance = 500.0f; + CStreaming::RequestBigBuildings(LEVEL_GENERIC); + CStreaming::RemoveIslandsNotUsed(LEVEL_BEACH); + CStreaming::RemoveIslandsNotUsed(LEVEL_MAINLAND); + CStreaming::LoadAllRequestedModels(false); + currArea = AREA_MAIN_MAP; + 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; + 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(); + CSpecialFX::Init(); + CRopes::Init(); + CWaterCannons::Init(); + CScriptPaths::Init(); + CParticle::ReloadConfig(); + +#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) +{ + // Empty and unused +} + +void CGame::ShutDownForRestart(void) +{ +#ifdef USE_TEXTURE_POOL + _TexturePoolsUnknown(false); +#endif + CReplay::FinishPlayback(); + CReplay::EmptyReplayBuffer(); + DMAudio.DestroyAllGameCreatedEntities(); + CMovingThings::Shutdown(); + + for (int i = 0; i < NUMPLAYERS; i++) + CWorld::Players[i].Clear(); + + CGarages::SetAllDoorsBackToOriginalHeight(); + CTheScripts::UndoBuildingSwaps(); + CTheScripts::UndoEntityInvisibilitySettings(); + CWorld::ClearForRestart(); + CGameLogic::ClearShortCut(); + CTimer::Shutdown(); + CStreaming::ReInit(); + CRadar::RemoveRadarSections(); + FrontEndMenuManager.UnloadTextures(); + CParticleObject::RemoveAllExpireableParticleObjects(); + CWaterCreatures::RemoveAll(); + CSetPieces::Init(); + CPedType::Shutdown(); + CSpecialFX::Shutdown(); +} + +void CGame::InitialiseWhenRestarting(void) +{ + CRect rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + CRGBA color(255, 255, 255, 255); + + CTimer::Initialise(); + CSprite2d::SetRecipNearClip(); + + if (b_FoundRecentSavedGameWantToLoad || FrontEndMenuManager.m_bWantToLoad) + { + LoadSplash("splash1"); +#ifndef XBOX_MESSAGE_SCREEN + if (FrontEndMenuManager.m_bWantToLoad) + FrontEndMenuManager.MessageScreen("FELD_WR", true); +#endif + } + + b_FoundRecentSavedGameWantToLoad = false; + + TheCamera.Init(); + + if ( FrontEndMenuManager.m_bWantToLoad == true ) + { +#ifdef XBOX_MESSAGE_SCREEN + FrontEndMenuManager.SetDialogTimer(1000); + DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 0); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + FrontEndMenuManager.DrawOverlays(); + DoRWStuffEndOfFrame(); +#endif + RestoreForStartLoad(); + } + + ReInitGameObjectVariables(); + + if ( FrontEndMenuManager.m_bWantToLoad == true ) + { + FrontEndMenuManager.m_bWantToLoad = false; + InitRadioStationPositionList(); + if ( GenericLoad() == true ) + { + DMAudio.ResetTimers(CTimer::GetTimeInMilliseconds()); + CTrain::InitTrains(); + CPlane::InitPlanes(); + } + else + { + for ( int32 i = 0; i < 50; i++ ) + { + HandleExit(); + FrontEndMenuManager.MessageScreen("FED_LFL", true); // Loading save game has failed. The game will restart now. + } + + TheCamera.SetFadeColour(0, 0, 0); + ShutDownForRestart(); + CTimer::Stop(); + CTimer::Initialise(); + FrontEndMenuManager.m_bWantToLoad = false; + ReInitGameObjectVariables(); + currLevel = LEVEL_GENERIC; + CCollision::SortOutCollisionAfterLoad(); + } +#ifdef XBOX_MESSAGE_SCREEN + FrontEndMenuManager.ProcessDialogTimer(); +#endif + } + + CTimer::Update(); + + DMAudio.ChangeMusicMode(MUSICMODE_GAME); +#ifdef USE_TEXTURE_POOL + _TexturePoolsUnknown(true); +#endif +} + +void CGame::Process(void) +{ + CPad::UpdatePads(); +#ifdef USE_CUSTOM_ALLOCATOR + ProcessTidyUpMemory(); +#endif +#ifdef DEBUGMENU + DebugMenuProcess(); +#endif + CCutsceneMgr::Update(); + + if (!CCutsceneMgr::IsCutsceneProcessing() && !CTimer::GetIsCodePaused()) + FrontEndMenuManager.Process(); + + CTheZones::Update(); +#ifdef SECUROM + if (CTimer::GetTimeInMilliseconds() >= (35 * 60 * 1000) && gameProcessPirateCheck == 0){ + // if game not pirated + // gameProcessPirateCheck = 1; + // else + gameProcessPirateCheck = 2; + } +#endif + uint32 startTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + CStreaming::Update(); + uint32 processTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond() - startTime; + CWindModifiers::Number = 0; + if (!CTimer::GetIsPaused()) + { +#ifndef MASTER + if (VarUpdatePlayerCoords) { + FindPlayerPed()->Teleport(PlayerCoords); + VarUpdatePlayerCoords = false; + } +#endif + 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(); + CScriptPaths::Update(); + CTrain::UpdateTrains(); + CPlane::UpdatePlanes(); + CHeli::UpdateHelis(); + CDarkel::Update(); + CSkidmarks::Update(); + CAntennas::Update(); + CGlass::Update(); +#ifdef GTA_SCENE_EDIT + CSceneEdit::Update(); +#endif + CSetPieces::Update(); + CEventList::Update(); + CParticle::Update(); + gFireManager.Update(); + + // Otherwise even on 30 fps most probably you won't see any peds around Ocean View Hospital +#if defined FIX_BUGS && !defined SQUEEZE_PERFORMANCE + if (processTime > 2) { +#else + if (processTime >= 2) { +#endif + CPopulation::Update(false); + } else { + uint32 startTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + CPopulation::Update(true); + processTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond() - startTime; + } + 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(); + CRopes::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); + if (processTime < 2) + CCarCtrl::GenerateRandomCars(); + CRoadBlocks::GenerateRoadBlocks(); + CCarCtrl::RemoveDistantCars(); + CCarCtrl::RemoveCarsIfThePoolGetsFull(); + POP_MEMID(); + } + } +#ifdef GTA_PS2 + CMemCheck::DoTest(); +#endif +} + +#ifdef USE_CUSTOM_ALLOCATOR + +// TODO(MIAMI) + +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 +} + +void +CGame::InitAfterFocusLoss() +{ + FrontEndMenuManager.m_nPrefsAudio3DProviderIndex = FrontEndMenuManager.m_lastWorking3DAudioProvider; + DMAudio.SetCurrent3DProvider(FrontEndMenuManager.m_lastWorking3DAudioProvider); + + if (!FrontEndMenuManager.m_bGameNotLoaded && !FrontEndMenuManager.m_bMenuActive) + FrontEndMenuManager.m_bStartUpFrontEndRequested = true; +} + +bool +CGame::CanSeeWaterFromCurrArea(void) +{ + return currArea == AREA_MAIN_MAP || currArea == AREA_MANSION + || currArea == AREA_HOTEL; +} + +bool +CGame::CanSeeOutSideFromCurrArea(void) +{ + return currArea == AREA_MAIN_MAP || currArea == AREA_MALL || + currArea == AREA_MANSION || currArea == AREA_HOTEL; +} diff --git a/src/miami/core/Game.h b/src/miami/core/Game.h new file mode 100644 index 00000000..4052eb00 --- /dev/null +++ b/src/miami/core/Game.h @@ -0,0 +1,81 @@ +#pragma once + +enum eLevelName { + LEVEL_IGNORE = -1, // beware, this is only used in CPhysical's m_nZoneLevel + LEVEL_GENERIC = 0, + LEVEL_BEACH, + LEVEL_MAINLAND, + + NUM_LEVELS +}; + +enum eAreaName { + AREA_MAIN_MAP, + AREA_HOTEL, + AREA_MANSION, + AREA_BANK, + AREA_MALL, + AREA_STRIP_CLUB, + AREA_LAWYERS, + AREA_COFFEE_SHOP, + AREA_CONCERT_HALL, + AREA_STUDIO, + AREA_RIFLE_RANGE, + AREA_BIKER_BAR, + AREA_POLICE_STATION, + AREA_EVERYWHERE, + AREA_DIRT, + AREA_BLOOD, + AREA_OVALRING, + AREA_MALIBU_CLUB, + AREA_PRINT_WORKS +}; + +class CGame +{ +public: + static eLevelName currLevel; + static int32 currArea; + 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]; + +#ifndef MASTER + static CVector PlayerCoords; + static bool8 VarUpdatePlayerCoords; +#endif + + static bool InitialiseOnceBeforeRW(void); + static bool InitialiseRenderWare(void); + static void ShutdownRenderWare(void); + static bool InitialiseOnceAfterRW(void); + static void FinalShutdown(void); + static bool Initialise(const char *datFile); + static bool ShutDown(void); + static void ReInitGameObjectVariables(void); + static void ReloadIPLs(void); + static void ShutDownForRestart(void); + static void InitialiseWhenRestarting(void); + static void Process(void); + + static void InitAfterFocusLoss(void); + + static bool IsInInterior(void) { return currArea != AREA_MAIN_MAP; } + static bool CanSeeWaterFromCurrArea(void); + static bool CanSeeOutSideFromCurrArea(void); + + // NB: these do something on PS2 + static void TidyUpMemory(bool, bool); + static void DrasticTidyUpMemory(bool); + static void ProcessTidyUpMemory(void); +}; + +inline bool IsAreaVisible(int area) { return area == CGame::currArea || area == AREA_EVERYWHERE; } diff --git a/src/miami/core/General.h b/src/miami/core/General.h new file mode 100644 index 00000000..1da18cfd --- /dev/null +++ b/src/miami/core/General.h @@ -0,0 +1,180 @@ +#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'; + } + + static bool SolveQuadratic(float a, float b, float c, float &root1, float &root2) + { + float discriminant = b * b - 4.f * a * c; + if (discriminant < 0.f) + return false; + + float discriminantSqrt = Sqrt(discriminant); + root2 = (-b + discriminantSqrt) / (2.f * a); + root1 = (-b - discriminantSqrt) / (2.f * a); + return true; + } + + // 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 int GetRandomNumberInRange(int low, int high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } + +#if !defined(INT32_IS_INT) + static int32 GetRandomNumberInRange(int32 low, int32 high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } + + static int32 GetRandomNumberInRange(int low, const int32& high) + { return low + (high - low)*(GetRandomNumber()/float(MYRAND_MAX + 1)); } +#endif + + static void SetRandomSeed(int32 seed) + { mysrand(seed); } +}; diff --git a/src/miami/core/IniFile.cpp b/src/miami/core/IniFile.cpp new file mode 100644 index 00000000..5d343ec9 --- /dev/null +++ b/src/miami/core/IniFile.cpp @@ -0,0 +1,29 @@ +#include "common.h" + +#include "IniFile.h" + +#include "CarCtrl.h" +#include "FileMgr.h" +#include "main.h" +#include "Population.h" + +float CIniFile::PedNumberMultiplier = 0.6f; +float CIniFile::CarNumberMultiplier = 0.6f; + +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; + CPopulation::MaxNumberOfPedsInUseInterior = DEFAULT_MAX_NUMBER_OF_PEDS_INTERIOR * PedNumberMultiplier; + CCarCtrl::MaxNumberOfCarsInUse = DEFAULT_MAX_NUMBER_OF_CARS * CarNumberMultiplier; +} \ No newline at end of file diff --git a/src/miami/core/IniFile.h b/src/miami/core/IniFile.h new file mode 100644 index 00000000..dcaed980 --- /dev/null +++ b/src/miami/core/IniFile.h @@ -0,0 +1,14 @@ +#pragma once + +#define DEFAULT_MAX_NUMBER_OF_PEDS 25.0f +#define DEFAULT_MAX_NUMBER_OF_PEDS_INTERIOR 40.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/miami/core/Lists.cpp b/src/miami/core/Lists.cpp new file mode 100644 index 00000000..448a0ff1 --- /dev/null +++ b/src/miami/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/miami/core/Lists.h b/src/miami/core/Lists.h new file mode 100644 index 00000000..7572e882 --- /dev/null +++ b/src/miami/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/miami/core/MenuScreens.cpp b/src/miami/core/MenuScreens.cpp new file mode 100644 index 00000000..295038d9 --- /dev/null +++ b/src/miami/core/MenuScreens.cpp @@ -0,0 +1,353 @@ +#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[] = { + // MENUPAGE_STATS = 0 + { "FEH_STA", MENUPAGE_NONE, 3, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 190, 320, MENUALIGN_RIGHT, + }, + + // MENUPAGE_NEW_GAME = 1 + { "FEP_STG", MENUPAGE_NONE, 1, + MENUACTION_CHANGEMENU, "FES_NGA", SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD, 320, 155, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FES_LOA", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FES_DEL", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, 0, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, 0, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_BRIEFS = 2 + { "FEH_BRI", MENUPAGE_NONE, 4, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 190, 320, MENUALIGN_RIGHT, + }, + + // MENUPAGE_SOUND_SETTINGS = 3 + { "FEH_AUD", MENUPAGE_OPTIONS, 1, + MENUACTION_MUSICVOLUME, "FEA_MUS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 40, 76, MENUALIGN_LEFT, + MENUACTION_SFXVOLUME, "FEA_SFX", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 0, 0, MENUALIGN_LEFT, + MENUACTION_MP3VOLUMEBOOST, "FEA_MPB", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 0, 0, MENUALIGN_LEFT, +#ifdef EXTERNAL_3D_SOUND + MENUACTION_AUDIOHW, "FEA_3DH", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 0, 0, MENUALIGN_LEFT, + MENUACTION_SPEAKERCONF, "FEA_SPK", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 0, 0, MENUALIGN_LEFT, +#endif + MENUACTION_DYNAMICACOUSTIC, "FET_DAM", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 0, 0, MENUALIGN_LEFT, + MENUACTION_RADIO, "FEA_RSS", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 0, 0, MENUALIGN_LEFT, +#ifdef EXTERNAL_3D_SOUND + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 320, 367, MENUALIGN_CENTER, +#else + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 320, 327, MENUALIGN_CENTER, +#endif + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_DISPLAY_SETTINGS = 4 +#ifdef LEGACY_MENU_OPTIONS + #define Y_OFFSET 50 +#else + #define Y_OFFSET 0 +#endif + + { "FEH_DIS", MENUPAGE_OPTIONS, 2, + MENUACTION_BRIGHTNESS, "FED_BRI", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 78, MENUALIGN_LEFT, + MENUACTION_DRAWDIST, "FEM_LOD", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 103, MENUALIGN_LEFT, +#ifdef LEGACY_MENU_OPTIONS + MENUACTION_FRAMESYNC, "FEM_VSC", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 128, MENUALIGN_LEFT, +#endif + MENUACTION_FRAMELIMIT, "FEM_FRM", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 128 + Y_OFFSET/2, MENUALIGN_LEFT, +#ifdef LEGACY_MENU_OPTIONS + MENUACTION_TRAILS, "FED_TRA", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 178, MENUALIGN_LEFT, +#endif + MENUACTION_SUBTITLES, "FED_SUB", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 153 + Y_OFFSET, MENUALIGN_LEFT, + MENUACTION_WIDESCREEN, "FED_WIS", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 178 + Y_OFFSET, MENUALIGN_LEFT, + MENUACTION_LEGENDS, "MAP_LEG", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 202 + Y_OFFSET, MENUALIGN_LEFT, + MENUACTION_RADARMODE, "FED_RDR", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 228 + Y_OFFSET, MENUALIGN_LEFT, + MENUACTION_HUD, "FED_HUD", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 253 + Y_OFFSET, MENUALIGN_LEFT, + MENUACTION_SCREENRES, "FED_RES", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 40, 278 + Y_OFFSET, MENUALIGN_LEFT, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 320, 303 + Y_OFFSET, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 320, 328 + Y_OFFSET, MENUALIGN_CENTER, + }, + +#undef Y_OFFSET + + // MENUPAGE_LANGUAGE_SETTINGS = 5 + { "FEH_LAN", MENUPAGE_OPTIONS, 3, + MENUACTION_LANG_ENG, "FEL_ENG", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, 320, 132, MENUALIGN_CENTER, + MENUACTION_LANG_FRE, "FEL_FRE", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, 0, 0, MENUALIGN_CENTER, + MENUACTION_LANG_GER, "FEL_GER", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, 0, 0, MENUALIGN_CENTER, + MENUACTION_LANG_ITA, "FEL_ITA", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, 0, 0, MENUALIGN_CENTER, + MENUACTION_LANG_SPA, "FEL_SPA", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, 0, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_MAP = 6 + { "FEH_MAP", MENUPAGE_NONE, 2, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 70, 380, MENUALIGN_CENTER, + }, + + // MENUPAGE_NEW_GAME_RELOAD = 7 + { "FES_NGA", MENUPAGE_NEW_GAME, 0, + MENUACTION_LABEL, "FESZ_QR", SAVESLOT_NONE, 0, 0, 0, 0, + MENUACTION_NO, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NEW_GAME, 320, 200, MENUALIGN_CENTER, + MENUACTION_NEWGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_CHOOSE_LOAD_SLOT = 8 + { "FET_LG", MENUPAGE_NEW_GAME, 1, + MENUACTION_CHECKSAVE, "FEM_SL1", SAVESLOT_1, 0, 40, 90, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL2", SAVESLOT_2, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL3", SAVESLOT_3, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL4", SAVESLOT_4, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL5", SAVESLOT_5, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL6", SAVESLOT_6, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL7", SAVESLOT_7, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL8", SAVESLOT_8, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, 0, 320, 345, MENUALIGN_CENTER, + }, + + // MENUPAGE_CHOOSE_DELETE_SLOT = 9 + { "FES_DEL", MENUPAGE_NEW_GAME, 2, + MENUACTION_CHECKSAVE, "FEM_SL1", SAVESLOT_1, 0, 40, 90, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL2", SAVESLOT_2, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL3", SAVESLOT_3, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL4", SAVESLOT_4, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL5", SAVESLOT_5, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL6", SAVESLOT_6, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL7", SAVESLOT_7, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL8", SAVESLOT_8, 0, 0, 0, MENUALIGN_LEFT, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, 0, 320, 345, MENUALIGN_CENTER, + }, + + // MENUPAGE_LOAD_SLOT_CONFIRM = 10 + { "FET_LG", MENUPAGE_CHOOSE_LOAD_SLOT, 0, + MENUACTION_LABEL, "FESZ_QL", SAVESLOT_NONE, 0, 0, 0, 0, + MENUACTION_NO, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT, 320, 200, MENUALIGN_CENTER, + MENUACTION_YES, "FEM_YES", SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_DELETE_SLOT_CONFIRM = 11 + { "FES_DEL", MENUPAGE_CHOOSE_DELETE_SLOT, 0, + MENUACTION_LABEL, "FESZ_QD", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_NO, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT, 320, 200, MENUALIGN_CENTER, + MENUACTION_YES, "FEM_YES", SAVESLOT_NONE, MENUPAGE_DELETING_IN_PROGRESS, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_LOADING_IN_PROGRESS = 12 + { "FET_LG", MENUPAGE_CHOOSE_LOAD_SLOT, 0, + }, + + // MENUPAGE_DELETING_IN_PROGRESS = 13 + { "FES_DEL", MENUPAGE_CHOOSE_DELETE_SLOT, 0, + }, + + // MENUPAGE_DELETE_SUCCESSFUL = 14 + { "FES_DEL", MENUPAGE_NEW_GAME, 0, + MENUACTION_LABEL, "FES_DSC", SAVESLOT_NONE, 0, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEM_OK", SAVESLOT_NONE, MENUPAGE_NEW_GAME, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_CHOOSE_SAVE_SLOT = 15 + { "FET_SG", MENUPAGE_DISABLED, 0, + MENUACTION_SAVEGAME, "FEM_SL1", SAVESLOT_1, MENUPAGE_SAVE_OVERWRITE_CONFIRM, 40, 90, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL2", SAVESLOT_2, MENUPAGE_SAVE_OVERWRITE_CONFIRM, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL3", SAVESLOT_3, MENUPAGE_SAVE_OVERWRITE_CONFIRM, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL4", SAVESLOT_4, MENUPAGE_SAVE_OVERWRITE_CONFIRM, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL5", SAVESLOT_5, MENUPAGE_SAVE_OVERWRITE_CONFIRM, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL6", SAVESLOT_6, MENUPAGE_SAVE_OVERWRITE_CONFIRM, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL7", SAVESLOT_7, MENUPAGE_SAVE_OVERWRITE_CONFIRM, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL8", SAVESLOT_8, MENUPAGE_SAVE_OVERWRITE_CONFIRM, 0, 0, MENUALIGN_LEFT, + MENUACTION_RESUME_FROM_SAVEZONE,"FESZ_CA", SAVESLOT_NONE, 0, 320, 345, MENUALIGN_CENTER, + }, + + // MENUPAGE_SAVE_OVERWRITE_CONFIRM = 16 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, 0, + MENUACTION_LABEL, "FESZ_QZ", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_NO, "FEM_NO", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, 320, 200, MENUALIGN_CENTER, + MENUACTION_YES, "FEM_YES", SAVESLOT_NONE, MENUPAGE_SAVING_IN_PROGRESS, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_SAVING_IN_PROGRESS = 17 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, 0, + }, + + // MENUPAGE_SAVE_SUCCESSFUL = 18 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, 0, + MENUACTION_LABEL, "FES_SSC", SAVESLOT_LABEL, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_RESUME_FROM_SAVEZONE, "FEM_OK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_SAVE_CUSTOM_WARNING = 19 + { "FET_SG", MENUPAGE_NONE, 0, + MENUACTION_LABEL, "", SAVESLOT_NONE, 0, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEM_OK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_SAVE_CHEAT_WARNING = 20 + { "FET_SG", MENUPAGE_NEW_GAME, 0, + MENUACTION_LABEL, "FES_CHE", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEM_OK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_SKIN_SELECT = 21 + { "FET_PS", MENUPAGE_OPTIONS, 4, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_OPTIONS, 0, 0, 0, + }, + + // MENUPAGE_SAVE_UNUSED = 22 + { "FET_SG", MENUPAGE_NEW_GAME, 0, + MENUACTION_LABEL, "FED_LWR", SAVESLOT_NONE, 0, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, 0, + }, + + // MENUPAGE_SAVE_FAILED = 23 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, 0, + MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, 0, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEC_OKK", SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT, 0, 0, 0, + }, + + // MENUPAGE_SAVE_FAILED_2 = 24 + { "FET_LG", MENUPAGE_CHOOSE_SAVE_SLOT, 0, + MENUACTION_LABEL, "FEC_SVU", SAVESLOT_NONE, 0, 0, 0, 0, + }, + + // MENUPAGE_LOAD_FAILED = 25 + { "FET_LG", MENUPAGE_NEW_GAME, 0, + MENUACTION_LABEL, "FEC_LUN", SAVESLOT_NONE, 0, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NEW_GAME, 0, 0, 0, + }, + + // MENUPAGE_CONTROLLER_PC = 26 + { "FET_CTL", MENUPAGE_OPTIONS, 0, +#ifdef PC_PLAYER_CONTROLS + MENUACTION_CTRLMETHOD, "FET_STI", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, 320, 150, MENUALIGN_CENTER, + MENUACTION_KEYBOARDCTRLS,"FEC_RED", SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS, 0, 0, MENUALIGN_CENTER, +#else + MENUACTION_KEYBOARDCTRLS,"FEC_RED", SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS, 320, 150, MENUALIGN_CENTER, +#endif + MENUACTION_CHANGEMENU, "FEC_MOU", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, 0, 0, MENUALIGN_CENTER, + MENUACTION_RESTOREDEF, "FET_DEF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, 0, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, 0, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_OPTIONS = 27 + { "FET_OPT", MENUPAGE_NONE, 5, + MENUACTION_CHANGEMENU, "FEO_CON", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC, 320, 132, MENUALIGN_CENTER, + MENUACTION_LOADRADIO, "FEO_AUD", SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEO_DIS", SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEO_LAN", SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS, 0, 0, MENUALIGN_CENTER, + MENUACTION_PLAYERSETUP, "FET_PS", SAVESLOT_NONE, MENUPAGE_SKIN_SELECT, 0, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, 0, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_EXIT = 28 + { "FET_QG", MENUPAGE_NONE, 6, + MENUACTION_LABEL, "FEQ_SRE", SAVESLOT_NONE, 0, 0, 0, 0, + MENUACTION_DONTCANCEL, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE, 320, 200, MENUALIGN_CENTER, + MENUACTION_CANCELGAME, "FEM_YES", SAVESLOT_NONE, MENUPAGE_NONE, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_START_MENU = 29 + { "FEM_MM", MENUPAGE_DISABLED, 0, + MENUACTION_CHANGEMENU, "FEP_STG", SAVESLOT_NONE, MENUPAGE_NEW_GAME, 320, 170, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEP_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEP_QUI", SAVESLOT_NONE, MENUPAGE_EXIT, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_KEYBOARD_CONTROLS = 30 + { "FET_STI", MENUPAGE_CONTROLLER_PC, 1, + }, + + // MENUPAGE_MOUSE_CONTROLS = 31 + { "FEC_MOU", MENUPAGE_CONTROLLER_PC, 2, + MENUACTION_MOUSESENS, "FEC_MSH", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, 40, 170, MENUALIGN_LEFT, + MENUACTION_INVVERT, "FEC_IVV", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, 0, 0, MENUALIGN_LEFT, + MENUACTION_MOUSESTEER, "FET_MST", SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS, 0, 0, MENUALIGN_LEFT, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, 0, 320, 260, MENUALIGN_CENTER, + }, + + // MENUPAGE_PAUSE_MENU = 32 + { "FET_PAU", MENUPAGE_DISABLED, 0, + MENUACTION_RESUME, "FEP_RES", SAVESLOT_NONE, 0, 320, 120, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEH_SGA", SAVESLOT_NONE, MENUPAGE_NEW_GAME, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEH_MAP", SAVESLOT_NONE, MENUPAGE_MAP, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEP_STA", SAVESLOT_NONE, MENUPAGE_STATS, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEH_BRI", SAVESLOT_NONE, MENUPAGE_BRIEFS, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FET_OPT", SAVESLOT_NONE, MENUPAGE_OPTIONS, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEP_QUI", SAVESLOT_NONE, MENUPAGE_EXIT, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_NONE = 33 + { "", 0, 0, }, + +#ifdef LEGACY_MENU_OPTIONS + // MENUPAGE_DEBUG_MENU + { "FED_DBG", MENUPAGE_NONE, 0, + MENUACTION_RELOADIDE, "FED_RID", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_SETDBGFLAG, "FED_DFL", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, "FED_DLS", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_COLLISIONPOLYS, "FED_SCP", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + }, + + // MENUPAGE_CONTROLLER_PC_OLD1 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, 0, + MENUACTION_GETKEY, "FEC_PLB", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GETKEY, "FEC_CWL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GETKEY, "FEC_CWR", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GETKEY, "FEC_LKT", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GETKEY, "FEC_PJP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GETKEY, "FEC_PSP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GETKEY, "FEC_TLF", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GETKEY, "FEC_TRG", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GETKEY, "FEC_CCM", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + }, + + // MENUPAGE_CONTROLLER_PC_OLD2 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, 1, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD3 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, 2, + MENUACTION_GETKEY, "FEC_LUP", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, 0, 0, 0, + MENUACTION_GETKEY, "FEC_LDN", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, 0, 0, 0, + MENUACTION_GETKEY, "FEC_SMS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, 0, 0, 0, + MENUACTION_SHOWHEADBOB, "FEC_GSL", SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + }, + + // MENUPAGE_CONTROLLER_PC_OLD4 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, 3, + + }, + + // MENUPAGE_CONTROLLER_DEBUG + { "FEC_DBG", MENUPAGE_CONTROLLER_PC, 3, + MENUACTION_GETKEY, "FEC_TGD", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, 0, 0, 0, + MENUACTION_GETKEY, "FEC_TDO", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, 0, 0, 0, + MENUACTION_GETKEY, "FEC_TSS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, 0, 0, 0, + MENUACTION_GETKEY, "FEC_SMS", SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + }, +#endif + +#ifdef MISSION_REPLAY + // MENUPAGE_MISSION_RETRY = 57 on mobile + + { "M_FAIL", MENUPAGE_DISABLED, 0, + MENUACTION_LABEL, "FESZ_RM", SAVESLOT_NONE, MENUPAGE_NONE, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEM_YES", SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS, 320, 200, MENUALIGN_CENTER, + MENUACTION_REJECT_RETRY, "FEM_NO", SAVESLOT_NONE, MENUPAGE_NONE, 320, 225, MENUALIGN_CENTER, + }, +#endif + + // MENUPAGE_OUTRO - Originally 34 + { "", 0, 0, }, +}; + +#endif +#endif diff --git a/src/miami/core/MenuScreensCustom.cpp b/src/miami/core/MenuScreensCustom.cpp new file mode 100644 index 00000000..2af06c89 --- /dev/null +++ b/src/miami/core/MenuScreensCustom.cpp @@ -0,0 +1,819 @@ +#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 "DMAudio.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) }, 0, 0, MENUALIGN_LEFT, +#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) }, 0, 0, MENUALIGN_LEFT, +#else + #define MULTISAMPLING_SELECTOR +#endif + +#ifdef CUTSCENE_BORDERS_SWITCH + #define CUTSCENE_BORDERS_TOGGLE MENUACTION_CFO_SELECT, "FEM_CSB", { new CCFOSelect((int8 *)&FrontEndMenuManager.m_PrefsCutsceneBorders, "Display", "CutsceneBorders", off_on, 2, false) }, 0, 0, MENUALIGN_LEFT, +#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) }, 0, 0, MENUALIGN_LEFT, +#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) }, 0, 0, MENUALIGN_LEFT, +#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) }, 0, 0, MENUALIGN_LEFT, \ + MENUACTION_CFO_SLIDER, "FEM_CAR", { new CCFOSlider(&CIniFile::CarNumberMultiplier, "Display", "CarDensity", 0.2f, 3.4f, CarDensityChange) }, 0, 0, MENUALIGN_LEFT, +#else + #define DENSITY_SLIDERS +#endif + +#ifdef NO_ISLAND_LOADING + #define ISLAND_LOADING_SELECTOR MENUACTION_CFO_SELECT, "FEM_ISL", { new CCFOSelect((int8*)&FrontEndMenuManager.m_PrefsIslandLoading, "Graphics", "IslandLoading", islandLoadingOpts, ARRAY_SIZE(islandLoadingOpts), true, IslandLoadingAfterChange) }, 0, 0, MENUALIGN_LEFT, +#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) }, 0, 0, MENUALIGN_LEFT, \ + MENUACTION_CFO_SELECT, "FED_MBL", { new CCFOSelect((int8*)&CPostFX::MotionBlurOn, "Graphics", "MotionBlur", off_on, 2, false) }, 0, 0, MENUALIGN_LEFT, +#else + #define POSTFX_SELECTORS +#endif + +#ifdef INVERT_LOOK_FOR_PAD + #define INVERT_PAD_SELECTOR MENUACTION_CFO_SELECT, "FEC_ILU", { new CCFOSelect((int8*)&CPad::bInvertLook4Pad, "Controller", "InvertPad", off_on, 2, false) }, 0, 0, MENUALIGN_LEFT, +#else + #define INVERT_PAD_SELECTOR +#endif + +#ifdef GAMEPAD_MENU + #define SELECT_CONTROLLER_TYPE MENUACTION_CFO_SELECT, "FEC_TYP", { new CCFOSelect((int8*)&FrontEndMenuManager.m_PrefsControllerType, "Controller", "Type", controllerTypes, ARRAY_SIZE(controllerTypes), false, ControllerTypeAfterChange) }, 0, 0, MENUALIGN_LEFT, +#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; + 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 + FrontEndMenuManager.m_PrefsFrameLimiter = true; + FrontEndMenuManager.m_PrefsVsyncDisp = true; + #ifdef LEGACY_MENU_OPTIONS + FrontEndMenuManager.m_PrefsVsync = true; + #endif + FrontEndMenuManager.m_PrefsUseWideScreen = false; + FrontEndMenuManager.m_nDisplayVideoMode = FrontEndMenuManager.m_nPrefsVideoMode; + CMBlur::BlurOn = false; + FrontEndMenuManager.SaveSettings(); + #endif +} + +void RestoreDefDisplay(int8 action) { + if (action != FEOPTION_ACTION_SELECT) + return; + + #ifdef CUTSCENE_BORDERS_SWITCH + FrontEndMenuManager.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 + FrontEndMenuManager.m_PrefsBrightness = 256; + FrontEndMenuManager.m_PrefsLOD = 1.2f; + CRenderer::ms_lodDistScale = 1.2f; + FrontEndMenuManager.m_PrefsShowSubtitles = false; + FrontEndMenuManager.m_PrefsShowLegends = true; + FrontEndMenuManager.m_PrefsRadarMode = 0; + FrontEndMenuManager.m_PrefsShowHud = 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_BEACH); + CStreaming::RemoveIslandsNotUsed(LEVEL_MAINLAND); + } + if (before == FrontEndMenuManager.ISLAND_LOADING_LOW) { + 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 + 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; + CPopulation::MaxNumberOfPedsInUseInterior = DEFAULT_MAX_NUMBER_OF_PEDS_INTERIOR * 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); + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.Service(); + 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 + DMAudio.ChangeMusicMode(MUSICMODE_FRONTEND); + DMAudio.Service(); + 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[] = { + // MENUPAGE_STATS = 0 + { "FEH_STA", MENUPAGE_NONE, nil, nil, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 190, 320, MENUALIGN_RIGHT, + }, + + // MENUPAGE_NEW_GAME = 1 + { "FEP_STG", MENUPAGE_NONE, nil, nil, + MENUACTION_CHANGEMENU, "FES_NGA", {nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD}, 320, 155, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FES_LOA", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT}, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FES_DEL", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT}, 0, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, 0}, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_BRIEFS = 2 + { "FEH_BRI", MENUPAGE_NONE, nil, nil, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 190, 320, MENUALIGN_RIGHT, + }, + + // MENUPAGE_SOUND_SETTINGS = 3 + { "FEH_AUD", MENUPAGE_OPTIONS, nil, nil, + MENUACTION_MUSICVOLUME, "FEA_MUS", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 40, 76, MENUALIGN_LEFT, + MENUACTION_SFXVOLUME, "FEA_SFX", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_MP3VOLUMEBOOST, "FEA_MPB", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 0, 0, MENUALIGN_LEFT, +#ifdef EXTERNAL_3D_SOUND + MENUACTION_AUDIOHW, "FEA_3DH", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_SPEAKERCONF, "FEA_SPK", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 0, 0, MENUALIGN_LEFT, +#endif + MENUACTION_DYNAMICACOUSTIC, "FET_DAM", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_RADIO, "FEA_RSS", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 0, 0, MENUALIGN_LEFT, +#ifdef EXTERNAL_3D_SOUND + MENUACTION_RESTOREDEF, "FET_DEF", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 320, 367, MENUALIGN_CENTER, +#else + MENUACTION_RESTOREDEF, "FET_DEF", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 320, 327, MENUALIGN_CENTER, +#endif + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_DISPLAY_SETTINGS = 4 +#ifndef GRAPHICS_MENU_OPTIONS + { "FEH_DIS", MENUPAGE_OPTIONS, new CCustomScreenLayout({40, 78, 25, true}), nil, + MENUACTION_BRIGHTNESS, "FED_BRI", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_DRAWDIST, "FEM_LOD", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, + DENSITY_SLIDERS +#ifdef LEGACY_MENU_OPTIONS + MENUACTION_FRAMESYNC, "FEM_VSC", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, +#endif + MENUACTION_FRAMELIMIT, "FEM_FRM", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, +#if defined LEGACY_MENU_OPTIONS && !defined EXTENDED_COLOURFILTER + MENUACTION_TRAILS, "FED_TRA", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, +#endif + MENUACTION_SUBTITLES, "FED_SUB", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_WIDESCREEN, "FED_WIS", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_LEGENDS, "MAP_LEG", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_RADARMODE, "FED_RDR", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_HUD, "FED_HUD", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_SCREENRES, "FED_RES", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_LEFT, + 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}, 320, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 320, 0, MENUALIGN_CENTER, + }, +#else + { "FEH_DIS", MENUPAGE_OPTIONS, new CCustomScreenLayout({40, 78, 25, true}), nil, + MENUACTION_BRIGHTNESS, "FED_BRI", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, 0, 0, MENUALIGN_LEFT, + MENUACTION_DRAWDIST, "FEM_LOD", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, 0, 0, MENUALIGN_LEFT, + DENSITY_SLIDERS + CUTSCENE_BORDERS_TOGGLE + FREE_CAM_TOGGLE + MENUACTION_LEGENDS, "MAP_LEG", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, 0, 0, MENUALIGN_LEFT, + MENUACTION_RADARMODE, "FED_RDR", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, 0, 0, MENUALIGN_LEFT, + MENUACTION_HUD, "FED_HUD", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, 0, 0, MENUALIGN_LEFT, + MENUACTION_SUBTITLES, "FED_SUB", { nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS }, 0, 0, MENUALIGN_LEFT, + MENUACTION_CFO_DYNAMIC, "FET_DEF", { new CCFODynamic(nil, nil, nil, nil, RestoreDefDisplay) }, 320, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE}, 320, 0, MENUALIGN_CENTER, + }, +#endif + + // MENUPAGE_LANGUAGE_SETTINGS = 5 + { "FEH_LAN", MENUPAGE_OPTIONS, nil, nil, + MENUACTION_LANG_ENG, "FEL_ENG", {nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS}, 320, 132, MENUALIGN_CENTER, + MENUACTION_LANG_FRE, "FEL_FRE", {nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_LANG_GER, "FEL_GER", {nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_LANG_ITA, "FEL_ITA", {nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_LANG_SPA, "FEL_SPA", {nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_MAP = 6 + { "FEH_MAP", MENUPAGE_NONE, nil, nil, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 70, 380, MENUALIGN_CENTER, + }, + + // MENUPAGE_NEW_GAME_RELOAD = 7 + { "FES_NGA", MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_LABEL, "FESZ_QR", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + MENUACTION_NO, "FEM_NO", {nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME}, 320, 200, MENUALIGN_CENTER, + MENUACTION_NEWGAME, "FEM_YES", {nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME_RELOAD}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_CHOOSE_LOAD_SLOT = 8 + { "FET_LG", MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_CHECKSAVE, "FEM_SL1", {nil, SAVESLOT_1, 0}, 40, 90, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL2", {nil, SAVESLOT_2, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL3", {nil, SAVESLOT_3, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL4", {nil, SAVESLOT_4, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL5", {nil, SAVESLOT_5, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL6", {nil, SAVESLOT_6, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL7", {nil, SAVESLOT_7, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL8", {nil, SAVESLOT_8, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, 0}, 320, 345, MENUALIGN_CENTER, + }, + + // MENUPAGE_CHOOSE_DELETE_SLOT = 9 + { "FES_DEL", MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_CHECKSAVE, "FEM_SL1", {nil, SAVESLOT_1, 0}, 40, 90, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL2", {nil, SAVESLOT_2, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL3", {nil, SAVESLOT_3, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL4", {nil, SAVESLOT_4, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL5", {nil, SAVESLOT_5, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL6", {nil, SAVESLOT_6, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL7", {nil, SAVESLOT_7, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_CHECKSAVE, "FEM_SL8", {nil, SAVESLOT_8, 0}, 0, 0, MENUALIGN_LEFT, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, 0}, 320, 345, MENUALIGN_CENTER, + }, + + // MENUPAGE_LOAD_SLOT_CONFIRM = 10 + { "FET_LG", MENUPAGE_CHOOSE_LOAD_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QL", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + MENUACTION_NO, "FEM_NO", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_LOAD_SLOT}, 320, 200, MENUALIGN_CENTER, + MENUACTION_YES, "FEM_YES", {nil, SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_DELETE_SLOT_CONFIRM = 11 + { "FES_DEL", MENUPAGE_CHOOSE_DELETE_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QD", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + MENUACTION_NO, "FEM_NO", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_DELETE_SLOT}, 320, 200, MENUALIGN_CENTER, + MENUACTION_YES, "FEM_YES", {nil, SAVESLOT_NONE, MENUPAGE_DELETING_IN_PROGRESS}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_LOADING_IN_PROGRESS = 12 + { "FET_LG", MENUPAGE_CHOOSE_LOAD_SLOT, nil, nil, + }, + + // MENUPAGE_DELETING_IN_PROGRESS = 13 + { "FES_DEL", MENUPAGE_CHOOSE_DELETE_SLOT, nil, nil, + }, + + // MENUPAGE_DELETE_SUCCESSFUL = 14 + { "FES_DEL", MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_LABEL, "FES_DSC", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEM_OK", {nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_CHOOSE_SAVE_SLOT = 15 + { "FET_SG", MENUPAGE_DISABLED, nil, nil, + MENUACTION_SAVEGAME, "FEM_SL1", {nil, SAVESLOT_1, MENUPAGE_SAVE_OVERWRITE_CONFIRM}, 40, 90, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL2", {nil, SAVESLOT_2, MENUPAGE_SAVE_OVERWRITE_CONFIRM}, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL3", {nil, SAVESLOT_3, MENUPAGE_SAVE_OVERWRITE_CONFIRM}, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL4", {nil, SAVESLOT_4, MENUPAGE_SAVE_OVERWRITE_CONFIRM}, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL5", {nil, SAVESLOT_5, MENUPAGE_SAVE_OVERWRITE_CONFIRM}, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL6", {nil, SAVESLOT_6, MENUPAGE_SAVE_OVERWRITE_CONFIRM}, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL7", {nil, SAVESLOT_7, MENUPAGE_SAVE_OVERWRITE_CONFIRM}, 0, 0, MENUALIGN_LEFT, + MENUACTION_SAVEGAME, "FEM_SL8", {nil, SAVESLOT_8, MENUPAGE_SAVE_OVERWRITE_CONFIRM}, 0, 0, MENUALIGN_LEFT, + MENUACTION_RESUME_FROM_SAVEZONE,"FESZ_CA", {nil, SAVESLOT_NONE, 0}, 320, 345, MENUALIGN_CENTER, + }, + + // MENUPAGE_SAVE_OVERWRITE_CONFIRM = 16 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FESZ_QZ", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + MENUACTION_NO, "FEM_NO", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT}, 320, 200, MENUALIGN_CENTER, + MENUACTION_YES, "FEM_YES", {nil, SAVESLOT_NONE, MENUPAGE_SAVING_IN_PROGRESS}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_SAVING_IN_PROGRESS = 17 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + }, + + // MENUPAGE_SAVE_SUCCESSFUL = 18 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FES_SSC", {nil, SAVESLOT_LABEL, MENUPAGE_NONE}, 0, 0, 0, + MENUACTION_RESUME_FROM_SAVEZONE, "FEM_OK", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_SAVE_CUSTOM_WARNING = 19 + { "FET_SG", MENUPAGE_NONE, nil, nil, + MENUACTION_LABEL, "", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEM_OK", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_SAVE_CHEAT_WARNING = 20 + { "FET_SG", MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_LABEL, "FES_CHE", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEM_OK", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_SKIN_SELECT = 21 + { "FET_PS", MENUPAGE_OPTIONS, nil, nil, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_OPTIONS}, 0, 0, 0, + }, + + // MENUPAGE_SAVE_UNUSED = 22 + { "FET_SG", MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_LABEL, "FED_LWR", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEC_OKK", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT}, 0, 0, 0, + }, + + // MENUPAGE_SAVE_FAILED = 23 + { "FET_SG", MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FEC_SVU", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEC_OKK", {nil, SAVESLOT_NONE, MENUPAGE_CHOOSE_SAVE_SLOT}, 0, 0, 0, + }, + + // MENUPAGE_SAVE_FAILED_2 = 24 + { "FET_LG", MENUPAGE_CHOOSE_SAVE_SLOT, nil, nil, + MENUACTION_LABEL, "FEC_SVU", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + }, + + // MENUPAGE_LOAD_FAILED = 25 + { "FET_LG", MENUPAGE_NEW_GAME, nil, nil, + MENUACTION_LABEL, "FEC_LUN", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME}, 0, 0, 0, + }, + + // MENUPAGE_CONTROLLER_PC = 26 + { "FET_CTL", MENUPAGE_OPTIONS, new CCustomScreenLayout({0, 0, MENU_DEFAULT_LINE_HEIGHT, false, false, 150}), nil, +#ifdef PC_PLAYER_CONTROLS + MENUACTION_CTRLMETHOD, "FET_STI", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC}, 320, 150, MENUALIGN_CENTER, + MENUACTION_KEYBOARDCTRLS,"FEC_RED", {nil, SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS}, 0, 0, MENUALIGN_CENTER, +#else + MENUACTION_KEYBOARDCTRLS,"FEC_RED", {nil, SAVESLOT_NONE, MENUPAGE_KEYBOARD_CONTROLS}, 320, 150, MENUALIGN_CENTER, +#endif +#ifdef GAMEPAD_MENU + MENUACTION_CHANGEMENU, "FET_AGS", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS}, 0, 0, MENUALIGN_CENTER, +#endif +#ifdef DETECT_JOYSTICK_MENU + MENUACTION_CHANGEMENU, "FEC_JOD", {nil, SAVESLOT_NONE, MENUPAGE_DETECT_JOYSTICK}, 0, 0, MENUALIGN_CENTER, +#endif + MENUACTION_CHANGEMENU, "FEC_MOU", {nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_RESTOREDEF, "FET_DEF", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC}, 320, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, 0}, 320, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_OPTIONS = 27 + { "FET_OPT", MENUPAGE_NONE, nil, nil, +#ifdef GTA_HANDHELD + MENUACTION_CHANGEMENU, "FEO_CON", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS}, 320, 132, MENUALIGN_CENTER, +#else + MENUACTION_CHANGEMENU, "FEO_CON", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC}, 320, 132, MENUALIGN_CENTER, +#endif + MENUACTION_LOADRADIO, "FEO_AUD", {nil, SAVESLOT_NONE, MENUPAGE_SOUND_SETTINGS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEO_DIS", {nil, SAVESLOT_NONE, MENUPAGE_DISPLAY_SETTINGS}, 0, 0, MENUALIGN_CENTER, +#ifdef GRAPHICS_MENU_OPTIONS + MENUACTION_CHANGEMENU, "FET_GFX", {nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS}, 0, 0, MENUALIGN_CENTER, +#endif + MENUACTION_CHANGEMENU, "FEO_LAN", {nil, SAVESLOT_NONE, MENUPAGE_LANGUAGE_SETTINGS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_PLAYERSETUP, "FET_PS", {nil, SAVESLOT_NONE, MENUPAGE_SKIN_SELECT}, 0, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, 0}, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_EXIT = 28 + { "FET_QG", MENUPAGE_NONE, nil, nil, + MENUACTION_LABEL, "FEQ_SRE", {nil, SAVESLOT_NONE, 0}, 0, 0, 0, + MENUACTION_DONTCANCEL, "FEM_NO", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 320, 200, MENUALIGN_CENTER, + MENUACTION_CANCELGAME, "FEM_YES", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 320, 225, MENUALIGN_CENTER, + }, + + // MENUPAGE_START_MENU = 29 + { "FEM_MM", MENUPAGE_DISABLED, nil, nil, + MENUACTION_CHANGEMENU, "FEP_STG", {nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME}, 320, 170, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEP_OPT", {nil, SAVESLOT_NONE, MENUPAGE_OPTIONS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEP_QUI", {nil, SAVESLOT_NONE, MENUPAGE_EXIT}, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_KEYBOARD_CONTROLS = 30 + { "FET_STI", MENUPAGE_CONTROLLER_PC, nil, nil, + }, + + // MENUPAGE_MOUSE_CONTROLS = 31 + { "FEC_MOU", MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_MOUSESENS, "FEC_MSH", {nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS}, 40, 170, MENUALIGN_LEFT, + MENUACTION_INVVERT, "FEC_IVV", {nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS}, 0, 0, MENUALIGN_LEFT, +#ifndef GAMEPAD_MENU + INVERT_PAD_SELECTOR +#endif + MENUACTION_MOUSESTEER, "FET_MST", {nil, SAVESLOT_NONE, MENUPAGE_MOUSE_CONTROLS}, 0, 0, MENUALIGN_LEFT, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, 0}, 320, 0, MENUALIGN_CENTER, + //MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, 0}, 320, 260, MENUALIGN_CENTER, // original y + }, + + // MENUPAGE_PAUSE_MENU = 32 + { "FET_PAU", MENUPAGE_DISABLED, nil, nil, + MENUACTION_RESUME, "FEP_RES", {nil, SAVESLOT_NONE, 0}, 320, 120, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEH_SGA", {nil, SAVESLOT_NONE, MENUPAGE_NEW_GAME}, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEH_MAP", {nil, SAVESLOT_NONE, MENUPAGE_MAP}, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEP_STA", {nil, SAVESLOT_NONE, MENUPAGE_STATS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEH_BRI", {nil, SAVESLOT_NONE, MENUPAGE_BRIEFS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FET_OPT", {nil, SAVESLOT_NONE, MENUPAGE_OPTIONS}, 0, 0, MENUALIGN_CENTER, + MENUACTION_CHANGEMENU, "FEP_QUI", {nil, SAVESLOT_NONE, MENUPAGE_EXIT}, 0, 0, MENUALIGN_CENTER, + }, + + // MENUPAGE_NONE = 33 + { "", 0, nil, nil, }, + +#ifdef GAMEPAD_MENU +#ifdef GTA_HANDHELD + { "FET_AGS", MENUPAGE_OPTIONS, new CCustomScreenLayout({40, 78, 25, true, true}), nil, +#else + { "FET_AGS", MENUPAGE_CONTROLLER_PC, new CCustomScreenLayout({40, 78, 25, true, true}), nil, +#endif + MENUACTION_CTRLCONFIG, "FEC_CCF", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, 40, 76, MENUALIGN_LEFT, + MENUACTION_CTRLDISPLAY, "FEC_CDP", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, 0, 0, MENUALIGN_LEFT, + INVERT_PAD_SELECTOR + MENUACTION_CTRLVIBRATION, "FEC_VIB", { nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_SETTINGS }, 0, 0, MENUALIGN_LEFT, + SELECT_CONTROLLER_TYPE + MENUACTION_GOBACK, "FEDS_TB", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, 0, 0, MENUALIGN_LEFT, + }, +#endif +#ifdef LEGACY_MENU_OPTIONS + // MENUPAGE_DEBUG_MENU = 18 + { "FED_DBG", MENUPAGE_NONE, nil, nil, + MENUACTION_RELOADIDE, "FED_RID", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + MENUACTION_SETDBGFLAG, "FED_DFL", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + MENUACTION_SWITCHBIGWHITEDEBUGLIGHT, "FED_DLS", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + MENUACTION_COLLISIONPOLYS, "FED_SCP", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + }, + + // MENUPAGE_CONTROLLER_PC_OLD1 = 36 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_PLB", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_CWL", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_CWR", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_LKT", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_PJP", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_PSP", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_TLF", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_TRG", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_CCM", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD1}, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + }, + + // MENUPAGE_CONTROLLER_PC_OLD2 = 37 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, nil, nil, + + }, + + // MENUPAGE_CONTROLLER_PC_OLD3 = 38 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_LUP", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_LDN", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_SMS", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3}, 0, 0, 0, + MENUACTION_SHOWHEADBOB, "FEC_GSL", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_PC_OLD3}, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + }, + + // MENUPAGE_CONTROLLER_PC_OLD4 = 39 + { "FET_CTL", MENUPAGE_CONTROLLER_PC, nil, nil, + + }, + + // MENUPAGE_CONTROLLER_DEBUG = 40 + { "FEC_DBG", MENUPAGE_CONTROLLER_PC, nil, nil, + MENUACTION_GETKEY, "FEC_TGD", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_TDO", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_TSS", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG}, 0, 0, 0, + MENUACTION_GETKEY, "FEC_SMS", {nil, SAVESLOT_NONE, MENUPAGE_CONTROLLER_DEBUG}, 0, 0, 0, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 0, 0, 0, + }, +#endif + +#ifdef GRAPHICS_MENU_OPTIONS + // MENUPAGE_GRAPHICS_SETTINGS + { "FET_GFX", MENUPAGE_OPTIONS, new CCustomScreenLayout({40, 78, 25, true, true}), GraphicsGoBack, + +#ifndef GTA_HANDHELD + MENUACTION_SCREENRES, "FED_RES", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, 0, 0, MENUALIGN_LEFT, +#endif + MULTISAMPLING_SELECTOR + MENUACTION_WIDESCREEN, "FED_WIS", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, 0, 0, MENUALIGN_LEFT, + VIDEOMODE_SELECTOR +#ifdef LEGACY_MENU_OPTIONS + MENUACTION_FRAMESYNC, "FEM_VSC", {nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS}, 0, 0, MENUALIGN_LEFT, +#endif + MENUACTION_FRAMELIMIT, "FEM_FRM", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, 0, 0, MENUALIGN_LEFT, + ISLAND_LOADING_SELECTOR + DUALPASS_SELECTOR +#ifdef EXTENDED_COLOURFILTER + POSTFX_SELECTORS +#elif defined LEGACY_MENU_OPTIONS + MENUACTION_TRAILS, "FED_TRA", { nil, SAVESLOT_NONE, MENUPAGE_GRAPHICS_SETTINGS }, 0, 0, MENUALIGN_LEFT, +#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) }, 320, 0, MENUALIGN_CENTER, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 320, 0, MENUALIGN_CENTER, + }, +#endif + +#ifdef DETECT_JOYSTICK_MENU + // MENUPAGE_DETECT_JOYSTICK + { "FEC_JOD", MENUPAGE_CONTROLLER_PC, new CCustomScreenLayout({0, 0, 0, false, false, 30}), DetectJoystickGoBack, + MENUACTION_LABEL, "FEC_JPR", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, 0, 0, 0, + MENUACTION_CFO_DYNAMIC, "FEC_JDE", { new CCFODynamic(nil, nil, nil, DetectJoystickDraw, nil) }, 80, 200, MENUALIGN_LEFT, + MENUACTION_GOBACK, "FEDS_TB", {nil, SAVESLOT_NONE, MENUPAGE_NONE}, 320, 225, MENUALIGN_CENTER, + }, +#endif + + +#ifdef MISSION_REPLAY + // MENUPAGE_MISSION_RETRY = 57 on mobile + + { "M_FAIL", MENUPAGE_DISABLED, nil, nil, + MENUACTION_LABEL, "FESZ_RM", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, 0, 0, 0, + MENUACTION_CHANGEMENU, "FEM_YES", { nil, SAVESLOT_NONE, MENUPAGE_LOADING_IN_PROGRESS }, 320, 200, MENUALIGN_CENTER, + MENUACTION_REJECT_RETRY, "FEM_NO", { nil, SAVESLOT_NONE, MENUPAGE_NONE }, 320, 225, MENUALIGN_CENTER, + }, +#endif + + // MENUPAGE_OUTRO = 34 + { "", 0, nil, nil, }, +}; + +#endif +#endif diff --git a/src/miami/core/Pad.cpp b/src/miami/core/Pad.cpp new file mode 100644 index 00000000..c265b2db --- /dev/null +++ b/src/miami/core/Pad.cpp @@ -0,0 +1,4752 @@ +#define WITHDINPUT +#include "common.h" +#include "crossplatform.h" +#include "platform.h" +#ifdef RW_DC + +#include +#include +#include + +auto contMaple = maple_enum_type(0, MAPLE_FUNC_CONTROLLER); +auto state = (cont_state_t *)maple_dev_status(contMaple); + +#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 +#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 "WaterLevel.h" +#include "General.h" +#include "Fluff.h" +#include "Gangs.h" +#include "platform.h" +#include "Stats.h" +#include "CarCtrl.h" +#include "TrafficLights.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; +bool CPad::m_bDebugCamPCOn; +bool CPad::bHasPlayerCheated; +bool CPad::bInvertLook4Pad; +#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[30]; + +CMouseControllerState CPad::OldMouseControllerState; +CMouseControllerState CPad::NewMouseControllerState; +CMouseControllerState CPad::PCTempMouseControllerState; + +#ifdef DETECT_PAD_INPUT_SWITCH +bool CPad::IsAffectedByController = false; +#endif + +_TODO("gbFastTime"); +extern bool gbFastTime; + +#ifdef WALLCLIMB_CHEAT +extern bool gGravityCheat; +#endif + +void SpecialCarCheats() +{ + if ( !CVehicle::bCheat9 ) + { + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_INFERNUS))->m_wheelScale *= 1.3f; + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_CHEETAH))->m_wheelScale *= 1.3f; + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_PHEONIX))->m_wheelScale *= 1.3f; + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_DELUXO))->m_wheelScale *= 1.3f; + mod_HandlingManager.GetHandlingData(HANDLING_LANDSTAL)->Transmission.fEngineAcceleration *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_PATRIOT)->Transmission.fEngineAcceleration *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_BOBCAT)->Transmission.fEngineAcceleration *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_BFINJECT)->Transmission.fEngineAcceleration *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_RANCHER)->Transmission.fEngineAcceleration *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_DESPERAD)->Transmission.fEngineAcceleration *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_DELUXO)->Transmission.fEngineAcceleration *= 2.0f; + + mod_HandlingManager.GetHandlingData(HANDLING_BAGGAGE)->Transmission.fEngineAcceleration *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_BAGGAGE)->Transmission.fMaxVelocity *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_BAGGAGE)->Transmission.InitGearRatios(); + + mod_HandlingManager.GetHandlingData(HANDLING_GOLFCART)->Transmission.fEngineAcceleration *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_GOLFCART)->Transmission.fMaxVelocity *= 2.0f; + mod_HandlingManager.GetHandlingData(HANDLING_GOLFCART)->Transmission.InitGearRatios(); + + mod_HandlingManager.GetHandlingData(HANDLING_STINGER)->fCollisionDamageMultiplier *= 0.25f; + mod_HandlingManager.GetHandlingData(HANDLING_STINGER)->fMass *= 2.5f; + mod_HandlingManager.GetHandlingData(HANDLING_STINGER)->fTurnMass *= 4.0f; + + mod_HandlingManager.GetHandlingData(HANDLING_BANSHEE)->fCollisionDamageMultiplier *= 0.25f; + mod_HandlingManager.GetHandlingData(HANDLING_BANSHEE)->fMass *= 2.5f; + mod_HandlingManager.GetHandlingData(HANDLING_BANSHEE)->fTurnMass *= 4.0f; + + mod_HandlingManager.GetHandlingData(HANDLING_SABRETUR)->fCollisionDamageMultiplier *= 0.25f; + mod_HandlingManager.GetHandlingData(HANDLING_SABRETUR)->fMass *= 2.5f; + mod_HandlingManager.GetHandlingData(HANDLING_SABRETUR)->fTurnMass *= 4.0f; + + mod_HandlingManager.GetHandlingData(HANDLING_COMET)->fCollisionDamageMultiplier *= 0.25f; + mod_HandlingManager.GetHandlingData(HANDLING_COMET)->fMass *= 2.5f; + mod_HandlingManager.GetHandlingData(HANDLING_COMET)->fTurnMass *= 4.0f; + + mod_HandlingManager.GetHandlingData(HANDLING_DELUXO)->fCollisionDamageMultiplier *= 0.25f; + mod_HandlingManager.GetHandlingData(HANDLING_DELUXO)->fMass *= 2.5f; + mod_HandlingManager.GetHandlingData(HANDLING_DELUXO)->fTurnMass *= 4.0f; + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bCheat9 = true; + CPad::bHasPlayerCheated = true; + } +} + +void PickUpChicksCheat() +{ + if ( FindPlayerVehicle() && (FindPlayerVehicle()->IsCar() || FindPlayerVehicle()->IsBike()) ) + { + CVehicle *vehicle = FindPlayerVehicle(); + if ( FindPlayerVehicle()->IsBike() ) + { + if ( vehicle->pPassengers[0] ) + vehicle->pPassengers[0]->SetObjective(OBJECTIVE_LEAVE_CAR, vehicle); + } + CPed* someoneElse = (CPed*)CWorld::TestSphereAgainstWorld(vehicle->GetPosition(), 6.0f, FindPlayerPed(), false, false, true, false, false, false); + if ( someoneElse && someoneElse->m_nPedState != PED_DRIVING ) + { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + someoneElse->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, vehicle); + } + } +} + +void WeaponCheat1() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT2"), true); + + CStreaming::RequestModel(MI_BRASS_KNUCKLES, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_BASEBALL_BAT, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_MOLOTOV, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_COLT45, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_SHOTGUN, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_TEC9, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_RUGER, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_SNIPERRIFLE, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_FLAMETHROWER, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + + FindPlayerPed()->GiveWeapon(WEAPONTYPE_BRASSKNUCKLE, 1); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_BASEBALLBAT, 1); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_MOLOTOV, 10); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_COLT45, 100); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_SHOTGUN, 50); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_TEC9, 150); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_RUGER, 120); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_SNIPERRIFLE, 25); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_FLAMETHROWER, 200); + + CStreaming::SetModelIsDeletable(MI_BRASS_KNUCKLES); + CStreaming::SetModelIsDeletable(MI_BASEBALL_BAT); + CStreaming::SetModelIsDeletable(MI_MOLOTOV); + CStreaming::SetModelIsDeletable(MI_COLT45); + CStreaming::SetModelIsDeletable(MI_SHOTGUN); + CStreaming::SetModelIsDeletable(MI_TEC9); + CStreaming::SetModelIsDeletable(MI_RUGER); + CStreaming::SetModelIsDeletable(MI_SNIPERRIFLE); + CStreaming::SetModelIsDeletable(MI_FLAMETHROWER); +#ifdef MOBILE_IMPROVEMENTS + if (FindPlayerVehicle()) { + FindPlayerPed()->RemoveWeaponWhenEnteringVehicle(); + } +#endif +} + +void WeaponCheat2() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT2"), true); + + CStreaming::RequestModel(MI_KATANA, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_GRENADE, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_BOMB, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_PYTHON, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_STUBBY_SHOTGUN, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_SILENCEDINGRAM, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_M4, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_LASERSCOPE, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_ROCKETLAUNCHER, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + +#ifdef FIX_BUGS + FindPlayerPed()->GiveWeapon(WEAPONTYPE_KATANA, 1); +#else + FindPlayerPed()->GiveWeapon(WEAPONTYPE_KATANA, 0); +#endif + FindPlayerPed()->GiveWeapon(WEAPONTYPE_DETONATOR_GRENADE, 10); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_PYTHON, 40); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_STUBBY_SHOTGUN, 25); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_SILENCED_INGRAM, 100); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_M4, 150); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_LASERSCOPE, 21); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_ROCKETLAUNCHER, 5); + + CStreaming::SetModelIsDeletable(MI_KATANA); + CStreaming::SetModelIsDeletable(MI_GRENADE); + CStreaming::SetModelIsDeletable(MI_BOMB); + CStreaming::SetModelIsDeletable(MI_PYTHON); + CStreaming::SetModelIsDeletable(MI_STUBBY_SHOTGUN); + CStreaming::SetModelIsDeletable(MI_SILENCEDINGRAM); + CStreaming::SetModelIsDeletable(MI_M4); + CStreaming::SetModelIsDeletable(MI_LASERSCOPE); + CStreaming::SetModelIsDeletable(MI_ROCKETLAUNCHER); +#ifdef MOBILE_IMPROVEMENTS + if (FindPlayerVehicle()) { + FindPlayerPed()->RemoveWeaponWhenEnteringVehicle(); + } +#endif +} + +void WeaponCheat3() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT2"), true); + + CStreaming::RequestModel(MI_CHAINSAW, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_GRENADE, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_PYTHON, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_SPAS12_SHOTGUN, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_MP5, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_M4, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_LASERSCOPE, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_MINIGUN, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_MINIGUN2, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + +#ifdef FIX_BUGS + FindPlayerPed()->GiveWeapon(WEAPONTYPE_CHAINSAW, 1); +#else + FindPlayerPed()->GiveWeapon(WEAPONTYPE_CHAINSAW, 0); +#endif + FindPlayerPed()->GiveWeapon(WEAPONTYPE_GRENADE, 10); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_PYTHON, 40); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_SPAS12_SHOTGUN, 30); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_MP5, 100); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_M4, 150); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_LASERSCOPE, 21); + FindPlayerPed()->GiveWeapon(WEAPONTYPE_MINIGUN, 500); + + CStreaming::SetModelIsDeletable(MI_CHAINSAW); + CStreaming::SetModelIsDeletable(MI_GRENADE); + CStreaming::SetModelIsDeletable(MI_PYTHON); + CStreaming::SetModelIsDeletable(MI_SPAS12_SHOTGUN); + CStreaming::SetModelIsDeletable(MI_MP5); + CStreaming::SetModelIsDeletable(MI_M4); + CStreaming::SetModelIsDeletable(MI_LASERSCOPE); + CStreaming::SetModelIsDeletable(MI_MINIGUN); + CStreaming::SetModelIsDeletable(MI_MINIGUN2); + +#ifdef MOBILE_IMPROVEMENTS + if (FindPlayerVehicle()) { + FindPlayerPed()->RemoveWeaponWhenEnteringVehicle(); + } +#endif +} + +void HealthCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT3"), true); + FindPlayerPed()->m_fHealth = CWorld::Players[0].m_nMaxHealth; + if (FindPlayerVehicle()) { + FindPlayerVehicle()->m_fHealth = 1000.0f; + if (FindPlayerVehicle()->m_vehType == VEHICLE_TYPE_CAR) { + ((CAutomobile*)FindPlayerVehicle())->Damage.SetEngineStatus(0); + for (int32 i = 0; i < 4; i++) + ((CAutomobile*)FindPlayerVehicle())->Damage.SetWheelStatus(i, WHEEL_STATUS_OK); + } + } +} + +// TODO(Miami): this is HELLA different on mobile, although it mostly has debug oriented things like player exiting it's current car and enters spawned one etc. +void VehicleCheat(int model) +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CStreaming::RequestModel(model, STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + if (CStreaming::ms_aInfoForModel[model].m_loadState == STREAMSTATE_LOADED) { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + + if (!(CStreaming::ms_aInfoForModel[model].m_loadState & STREAMFLAGS_DONT_REMOVE)) { + CStreaming::SetModelIsDeletable(model); + CStreaming::SetModelTxdIsDeletable(model); + } + + int32 node = ThePaths.FindNodeClosestToCoors(FindPlayerCoors(), PATH_CAR, 100.0f); + if (node < 0) return; + +#ifdef FIX_BUGS + CAutomobile* vehicle = new CAutomobile(model, RANDOM_VEHICLE); +#else + CAutomobile* vehicle = new CAutomobile(model, MISSION_VEHICLE); +#endif + if (vehicle != nil) { + CVector pos = ThePaths.m_pathNodes[node].GetPosition(); + pos.z += 4.0f; + vehicle->SetPosition(pos); + vehicle->SetOrientation(0.0f, 0.0f, DEGTORAD(200.0f)); + + vehicle->SetStatus(STATUS_ABANDONED); + vehicle->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(vehicle); + } + } + CStats::CheatedCount += 1000; + CPad::bHasPlayerCheated = true; +} + + +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() +{ + // I don't know wtf is going on in here... + int modelId, anotherModelId; + + 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_PGA); + anotherModelId = modelId+1; + } while (!CModelInfo::GetModelInfo(anotherModelId)); + } while (anotherModelId >= MI_SPECIAL01 && anotherModelId <= MI_SPECIAL04 || modelId == MI_TAXI_D); + + uint8 flags = CStreaming::ms_aInfoForModel[anotherModelId].m_flags; + ped->DeleteRwObject(); + CStreaming::RequestModel(anotherModelId, STREAMFLAGS_DEPENDENCY| STREAMFLAGS_DONT_REMOVE); + CStreaming::LoadAllRequestedModels(false); + ped->m_modelIndex = -1; + ped->SetModelIndex(anotherModelId); + ped->m_animGroup = AnimGrp; + if (modelId != -1) { + if (!(flags & STREAMFLAGS_DONT_REMOVE)) + CStreaming::SetModelIsDeletable(anotherModelId); + } + } +} + +void ChangePlayerModel(const char* name) { + if (!FindPlayerVehicle()) { + FindPlayerPed()->Undress(name); + CStreaming::LoadAllRequestedModels(0); + FindPlayerPed()->Dress(); + } +} + +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 ); + CStats::CheatedCount += 1000; + CPad::bHasPlayerCheated = true; +} + +void EverybodyAttacksPlayerCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + for (int i = PEDTYPE_CIVMALE; i < PEDTYPE_SPECIAL; i++) + CPedType::AddThreat(i, PED_FLAG_PLAYER1); + + CStats::CheatedCount += 1000; + CPad::bHasPlayerCheated = true; +} + +void WeaponsForAllCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CPopulation::ms_bGivePedsWeapons = !CPopulation::ms_bGivePedsWeapons; + CStats::CheatedCount += 1000; + CPad::bHasPlayerCheated = true; +} + +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 = CWorld::Players[0].m_nMaxArmour; +} + +void WantedLevelUpCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT5"), true); + FindPlayerPed()->m_pWanted->CheatWantedLevel(Min(FindPlayerPed()->m_pWanted->GetWantedLevel() + 2, 6)); +} + +void WantedLevelDownCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT5"), true); + FindPlayerPed()->m_pWanted->CheatWantedLevel(0); +} + +void SunnyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_SUNNY); +} + +void ExtraSunnyWeatherCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT7"), true); + CWeather::ForceWeatherNow(WEATHER_EXTRA_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; + CStats::CheatedCount += 1000; + CPad::bHasPlayerCheated = true; +} + +void ChittyChittyBangBangCheat() +{ +#ifdef BETTER_ALLCARSAREDODO_CHEAT + CHud::SetHelpMessage(TheText.Get(!CVehicle::bAllDodosCheat ? "CHEAT1" : "CHEATOF"), true); +#else + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); +#endif + CVehicle::bAllDodosCheat = !CVehicle::bAllDodosCheat; + CStats::CheatedCount += 1000; + CPad::bHasPlayerCheated = true; +} + +void StrongGripCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bCheat3 = !CVehicle::bCheat3; + CStats::CheatedCount += 1000; + CPad::bHasPlayerCheated = true; +} + +void FannyMagnetCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CPed::bFannyMagnetCheat = !CPed::bFannyMagnetCheat; + CPad::bHasPlayerCheated = true; +} + +void BlackCarsCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + gbBlackCars = true; + gbPinkCars = false; +} + +void PinkCarsCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + gbBlackCars = false; + gbPinkCars = true; +} + +void TrafficLightsCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CTrafficLights::bGreenLightsCheat = true; +} + +void MadCarsCheat() +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CCarCtrl::bMadDriversCheat = true; +} + +void NoSeaBedCheat(void) +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CWaterLevel::m_bRenderSeaBed = !CWaterLevel::m_bRenderSeaBed; +} + +void RenderWaterLayersCheat(void) +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + if ( ++CWaterLevel::m_nRenderWaterLayers > 5 ) + CWaterLevel::m_nRenderWaterLayers = 0; +} + +void BackToTheFuture(void) +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bHoverCheat = !CVehicle::bHoverCheat; + CPad::bHasPlayerCheated = true; +} + +void SuicideCheat(void) { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + FindPlayerPed()->InflictDamage(nil, WEAPONTYPE_UNARMED, 1000.0f, PEDPIECE_TORSO, 0); +} + +void DoChicksWithGunsCheat(void) { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CStreaming::SetModelIsDeletable(CGangs::GetGangPedModel1(GANG_PLAYER)); + CStreaming::SetModelIsDeletable(CGangs::GetGangPedModel2(GANG_PLAYER)); + CStreaming::SetModelTxdIsDeletable(CGangs::GetGangPedModel1(GANG_PLAYER)); + CStreaming::SetModelTxdIsDeletable(CGangs::GetGangPedModel2(GANG_PLAYER)); + CStreaming::RemoveCurrentZonesModels(); + CGangs::SetGangPedModels(GANG_PLAYER, MI_HFYBE, MI_WFYBE); + CGangs::SetGangWeapons(GANG_PLAYER, WEAPONTYPE_M4, WEAPONTYPE_M4); + CStats::CheatedCount += 1000; + CPad::bHasPlayerCheated = true; +} + +////////////////////////////////////////////////////////////////////////// + +#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 RESTORE_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 WALLCLIMB_CHEAT +void WallClimbingCheat(void) +{ + wchar* string; + if (gGravityCheat) { + string = TheText.Get("CHEATOF"); + gGravityCheat = false; + } + else { + string = TheText.Get("CHEAT1"); + gGravityCheat = true; + } + CHud::SetHelpMessage(string, true); +} +#endif + +void FlyingFishCheat(void) +{ + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CVehicle::bCheat8 = !CVehicle::bCheat8; + CPad::bHasPlayerCheated = true; +} + +void DoShowChaseStatCheat(void) { + CHud::SetHelpMessage(TheText.Get("CHEAT1"), true); + CStats::ShowChaseStatOnScreen = 1; +} + +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; + m_bMapPadOneToPadTwo = false; + m_bDebugCamPCOn = 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; + + for (int32 i = 0; i < DRUNK_STEERING_BUFFER_SIZE; i++) + SteeringLeftRightBuffer[i] = 0; + + DrunkDrivingBufferUsed = 0; + + if ( bResetPlayerControls ) + DisablePlayerControls = PLAYERCONTROL_ENABLED; + + JustOutOfFrontend = 0; + 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; +} + +uint32 CPad::InputHowLongAgo() +{ + return CTimer::GetTimeInMilliseconds() - LastTimeTouched; +} + +void CPad::ClearMouseHistory() +{ + PCTempMouseControllerState.Clear(); + NewMouseControllerState.Clear(); + OldMouseControllerState.Clear(); +} + +// unused +void CPad::ClearKeyBoardHistory() +{ + NewKeyState.Clear(); + OldKeyState.Clear(); + TempKeyState.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(!FrontEndMenuManager.m_bMenuActive && _InputMouseNeedsExclusive()); + + 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; + } + } +#elif defined(RW_DC) + printf("TODO this %s\n", __func__); +#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(!FrontEndMenuManager.m_bMenuActive && _InputMouseNeedsExclusive()); + + 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; + } + } +#elif defined(RW_DC) + // Do mouse stuff here? + // printf("TODO this %s\n", __func__); +#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 ( !FrontEndMenuManager.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 ( !FrontEndMenuManager.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 ( !FrontEndMenuManager.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") ) + WeaponCheat1(); + + // "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") ) + VehicleCheat(MI_RHINO); + + // "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(); + +#undef _CHEATCMP +} +#endif + +int Cheat_strncmp(char* sourceStr, char* origCheatStr) +{ +#define ccmp(n) if((uint8)sourceStr[i] != (uint8)origCheatStr[i] - n) return 1; + int i = 0; + while(origCheatStr[i]) + { + switch(i) + { + case 0: ccmp(3); break; + case 1: ccmp(5); break; + case 2: ccmp(7); break; + case 3: ccmp(1); break; + case 4: ccmp(13); break; + case 5: ccmp(27); break; + case 6: ccmp(3); break; + case 7: ccmp(7); break; + case 8: ccmp(1); break; + case 9: ccmp(11); break; + case 10: ccmp(13); break; + case 11: ccmp(8); break; + case 12: ccmp(7); break; + case 13: ccmp(32); break; + case 14: ccmp(13); break; + case 15: ccmp(6); break; + case 16: ccmp(28); break; + case 17: ccmp(19); break; + case 18: ccmp(10); break; + case 19: ccmp(3); break; + case 20: ccmp(3); break; + case 21: ccmp(5); break; + case 22: ccmp(7); break; + case 23: ccmp(1); break; + case 24: ccmp(13); break; + case 25: ccmp(27); break; + case 26: ccmp(3); break; + case 27: ccmp(7); break; + default: return 1; + } + i++; + } + return 0; +#undef ccmp +} + +// TODO(Miami): Mobile has changed some of the cheats to include debugging things +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) + + // "THUGSTOOLS" + if (!Cheat_strncmp(KeyBoardCheatString, "VQVPanJ\\I_")) { + KeyBoardCheatString[0] = ' '; + WeaponCheat1(); + } + // "PROFESSIONALTOOLS" + else if (!Cheat_strncmp(KeyBoardCheatString, "VQVPagDUPT`[Lf\\Xl")) { + KeyBoardCheatString[0] = ' '; + WeaponCheat2(); + } + // "NUTTERTOOLS" + else if (!Cheat_strncmp(KeyBoardCheatString, "VQVPamH[U`[")) { + KeyBoardCheatString[0] = ' '; + WeaponCheat3(); + } + // "PRECIOUSPROTECTION" + else if (!Cheat_strncmp(KeyBoardCheatString, "QTPUP`WVS[`]ViPKnc")) { + KeyBoardCheatString[0] = ' '; + ArmourCheat(); + } + // "ASPIRINE" + else if (!Cheat_strncmp(KeyBoardCheatString, "HSPSVkVH")) { + KeyBoardCheatString[0] = ' '; + HealthCheat(); + } + // "YOUWONTTAKEMEALIVE" + else if (!Cheat_strncmp(KeyBoardCheatString, "H[PMN`PLLLa\\Uod[kl")) { + KeyBoardCheatString[0] = ' '; + WantedLevelUpCheat(); + } + // "LEAVEMEALONE" + else if (!Cheat_strncmp(KeyBoardCheatString, "HSVMN`PLWLRT")) { + KeyBoardCheatString[0] = ' '; + WantedLevelDownCheat(); + } + // "APLEASANTDAY" + else if (!Cheat_strncmp(KeyBoardCheatString, "\\FKU[\\VHFW]I")) { + KeyBoardCheatString[0] = ' '; + SunnyWeatherCheat(); + } + // "ALOVELYDAY" + else if (!Cheat_strncmp(KeyBoardCheatString, "\\FKZY`YVML")) { + KeyBoardCheatString[0] = ' '; + ExtraSunnyWeatherCheat(); + } + // "ABITDRIEG" + else if (!Cheat_strncmp(KeyBoardCheatString, "JJPSQoLIB")) { + KeyBoardCheatString[0] = ' '; + CloudyWeatherCheat(); + } + // "CATSANDDOGS" + else if (!Cheat_strncmp(KeyBoardCheatString, "VLVEQiDZULP")) { + KeyBoardCheatString[0] = ' '; + RainyWeatherCheat(); + } + // "CANTSEEATHING" + else if (!Cheat_strncmp(KeyBoardCheatString, "JSPIa\\HLT_[IJ")) { + KeyBoardCheatString[0] = ' '; + FoggyWeatherCheat(); + } + // "PANZER" + else if (!Cheat_strncmp(KeyBoardCheatString, "UJaONk")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_RHINO); + } + // "LIFEISPASSINGMEBY" + else if (!Cheat_strncmp(KeyBoardCheatString, "\\GLNTiLZTL][PeSOh")) { + KeyBoardCheatString[0] = ' '; + FastWeatherCheat(); + } + // "BIGBANG" + else if (!Cheat_strncmp(KeyBoardCheatString, "JSHCTdE")) { + KeyBoardCheatString[0] = ' '; + BlowUpCarsCheat(); + } + // "STILLLIKEDRESSINGUP" + else if (!Cheat_strncmp(KeyBoardCheatString, "SZNOVnVLSORSPlYReg]")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerCheat(); + } + // "FIGHTFIGHTFIGHT" + else if (!Cheat_strncmp(KeyBoardCheatString, "WMNJSoKNJQaPNiS")) { + KeyBoardCheatString[0] = ' '; + MayhemCheat(); + } + // "NOBODYLIKESME" + else if (!Cheat_strncmp(KeyBoardCheatString, "HRZFXdO`EZOWU")) { + KeyBoardCheatString[0] = ' '; + EverybodyAttacksPlayerCheat(); + } + // "OURGODGIVENRIGHTTOBEARARMS" + else if (!Cheat_strncmp(KeyBoardCheatString, "VRYB_\\HIP_aPNi_TaiSJGTNSbj")) { + KeyBoardCheatString[0] = ' '; + WeaponsForAllCheat(); + } + // "ONSPEED" + else if (!Cheat_strncmp(KeyBoardCheatString, "GJLQ`iR")) { + KeyBoardCheatString[0] = ' '; + FastTimeCheat(); + } + // "BOOOOOORING" + else if (!Cheat_strncmp(KeyBoardCheatString, "JSPS\\jRVPZO")) { + KeyBoardCheatString[0] = ' '; + SlowTimeCheat(); + } + // "WHEELSAREALLINEED" + else if (!Cheat_strncmp(KeyBoardCheatString, "GJLOVgOHF]N[SeRNs")) { + KeyBoardCheatString[0] = ' '; + OnlyRenderWheelsCheat(); + } + //COMEFLYWITHME + else if (!Cheat_strncmp(KeyBoardCheatString, "HROUVr\\SGPZWJ")) { + KeyBoardCheatString[0] = ' '; + ChittyChittyBangBangCheat(); + } + // "GRIPISEVERYTHING" + else if (!Cheat_strncmp(KeyBoardCheatString, "JSPIatULWP`QWi_M")) { + KeyBoardCheatString[0] = ' '; + StrongGripCheat(); + } + // "CHASESTAT" + else if (!Cheat_strncmp(KeyBoardCheatString, "WF[TRnDOD")) { + KeyBoardCheatString[0] = ' '; + DoShowChaseStatCheat(); + } + // "CHICKSWITHGUNS" + else if (!Cheat_strncmp(KeyBoardCheatString, "VS\\HUoL^TVPQOc")) { + KeyBoardCheatString[0] = ' '; + DoChicksWithGunsCheat(); + } + // "ICANTTAKEITANYMORE" + else if (!Cheat_strncmp(KeyBoardCheatString, "HWVNfiD[JPXI[t[G_\\")) { + KeyBoardCheatString[0] = ' '; + SuicideCheat(); + } + // "GREENLIGHT" + else if (!Cheat_strncmp(KeyBoardCheatString, "WMNJYiHLSR")) { + KeyBoardCheatString[0] = ' '; + TrafficLightsCheat(); + } + // "MIAMITRAFFIC" + else if (!Cheat_strncmp(KeyBoardCheatString, "FNMGNmWPNLVU")) { + KeyBoardCheatString[0] = ' '; + MadCarsCheat(); + } + // "AHAIRDRESSERSCAR" + else if (!Cheat_strncmp(KeyBoardCheatString, "UFJT_`VZF]QZPaUG")) { + KeyBoardCheatString[0] = ' '; + PinkCarsCheat(); + } + // "IWANTITPAINTEDBLACK" + else if (!Cheat_strncmp(KeyBoardCheatString, "NHHMO_H[OTNX[iaT]jS")) { + KeyBoardCheatString[0] = ' '; + BlackCarsCheat(); + } + // "TRAVELINSTYLE" + else if (!Cheat_strncmp(KeyBoardCheatString, "HQ`U`iLSFaNZ[")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_BLOODRA); + } + // "THELASTRIDE" + else if (!Cheat_strncmp(KeyBoardCheatString, "HIPSanDSFSa")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_ROMERO); + } + // "ROCKANDROLLCAR" + else if (!Cheat_strncmp(KeyBoardCheatString, "UFJMYjUKOLXKVr")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_LOVEFIST); + } + // "RUBBISHCAR" + else if (!Cheat_strncmp(KeyBoardCheatString, "UFJI`dEIV]")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_TRASH); + } + // "GETTHEREQUICKLY" + else if (!Cheat_strncmp(KeyBoardCheatString, "\\QRDVpTLSPU\\[eT")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_BLOODRB); + } + // "GETTHEREFAST" + else if (!Cheat_strncmp(KeyBoardCheatString, "WXHGRmHOU_RO")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_SABRETUR); + } + // "BETTERTHANWALKING" + else if (!Cheat_strncmp(KeyBoardCheatString, "JSPLY\\ZUBSaZLtaK^")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_CADDY); + } + // "GETTHEREFASTINDEED" + else if (!Cheat_strncmp(KeyBoardCheatString, "GJLE[dWZBQfZLvRXa[^WHL")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_HOTRINA); + } + // "GETTHEREAMAZINGLYFAST" + else if (!Cheat_strncmp(KeyBoardCheatString, "WXHGfgJUJeNUHe_Kdg^HJ")) { + KeyBoardCheatString[0] = ' '; + VehicleCheat(MI_HOTRINB); + } + // LOOKLIKELANCE + else if (!Cheat_strncmp(KeyBoardCheatString, "HHUBY`NPMV\\WS")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igbuddy"); + } + // IWANTBIGTITS + else if (!Cheat_strncmp(KeyBoardCheatString, "VYPUTdE[OLdQ")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igcandy"); + } + // MYSONISALAWYER + else if (!Cheat_strncmp(KeyBoardCheatString, "UJ`XNgDZJY\\[`m")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igken"); + } + // ILOOKLIKEHILARY + else if (!Cheat_strncmp(KeyBoardCheatString, "\\WHMVcHRJWXWVlV")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("ighlary"); + } + // ROCKANDROLLMAN + else if (!Cheat_strncmp(KeyBoardCheatString, "QFTMYjUKOLXKVr")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igjezz"); + } + // ONEARMEDBANDIT + else if (!Cheat_strncmp(KeyBoardCheatString, "WNKON]GLN]NMUo")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igphil"); + } + // IDONTHAVETHEMONEYSONNY + else if (!Cheat_strncmp(KeyBoardCheatString, "\\SUP`tHUPXRP[ecGdgXRGN")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igsonny"); + } + // FOXYLITTLETHING + else if (!Cheat_strncmp(KeyBoardCheatString, "JSPIa`O[UTYa_oS")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igmerc"); + } + // WELOVEOURDICK + else if (!Cheat_strncmp(KeyBoardCheatString, "NHPE_pRLWZYM^")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igdick"); + } + // CHEATSHAVEBEENCRACKED + else if (!Cheat_strncmp(KeyBoardCheatString, "GJRDNmFUFPOM]aUYpTOKF")) { + KeyBoardCheatString[0] = ' '; + ChangePlayerModel("igdiaz"); + } + // DEEPFRIEDMARSBARS + else if (!Cheat_strncmp(KeyBoardCheatString, "VWHC`mDTEPVZMpRK")) { + KeyBoardCheatString[0] = ' '; + gfTommyFatness = 0.26f; + } + // PROGRAMMER + else if (!Cheat_strncmp(KeyBoardCheatString, "UJTNNmJVS[")) { + KeyBoardCheatString[0] = ' '; + gfTommyFatness = -0.3f; + } + // SEAWAYS + else if (!Cheat_strncmp(KeyBoardCheatString, "V^HXN`V")) { + KeyBoardCheatString[0] = ' '; + BackToTheFuture(); + } + // LOADSOFLITTLETHINGS + else if (!Cheat_strncmp(KeyBoardCheatString, "VLUJUoHSU_VTMo`J]bV")) { + KeyBoardCheatString[0] = ' '; + SpecialCarCheats(); + } + // HOPINGIRL + else if (!Cheat_strncmp(KeyBoardCheatString, "OWPH[dSVI")) { + KeyBoardCheatString[0] = ' '; + PickUpChicksCheat(); + } + //CERTAINDEATH + else if (!Cheat_strncmp(KeyBoardCheatString, "KYHFQiLHU]RK")) { + KeyBoardCheatString[0] = ' '; + CSmokeTrails::CigOn = !CSmokeTrails::CigOn; + } + //AIRSHIP + else if (!Cheat_strncmp(KeyBoardCheatString, "SNOT_dD")) { + KeyBoardCheatString[0] = ' '; + FlyingFishCheat(); + } + //FANNYMAGNET + else if (!Cheat_strncmp(KeyBoardCheatString, "WJUHNh\\UOLS")) { + KeyBoardCheatString[0] = ' '; + FannyMagnetCheat(); + } + +#ifdef KANGAROO_CHEAT + // "KANGAROO" + if (!_CHEATCMP("OORAGNAK")) + KangarooCheat(); +#endif + +#ifndef MASTER + // "PEDDEBUG" + if (!_CHEATCMP("GUBEDDEP")) + CPed::SwitchDebugDisplay(); +#endif + +#ifdef RESTORE_ALLCARSHELI_CHEAT + // "CARSAREHELI" + if (!_CHEATCMP("ILEHERASRAC")) + AllCarsHeliCheat(); +#endif + +#ifdef WALLCLIMB_CHEAT + // "SPIDERCAR" + if (!_CHEATCMP("RACREDIPS")) + WallClimbingCheat(); +#endif + +#if !defined(PC_WATER) && defined(WATER_CHEATS) + // SEABEDCHEAT + if (!_CHEATCMP("TAEHCDEBAESON")) + NoSeaBedCheat(); + + // WATERLAYERSCHEAT + if (!_CHEATCMP("TAEHCSREYALRETAW")) + RenderWaterLayersCheat(); +#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() && !FrontEndMenuManager.m_bMenuActive ) + 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 + { +#ifdef RW_DC + if (((NewState.RightStickY > 64 && OldState.RightStickY > 64)) || ((NewState.RightStickY) < -64 && (OldState.RightStickY < -64))) + { + // if (contMaple == nullptr) + // CPad::GetPad(0)->IsDualAnalog = false; + // else + CPad::GetPad(0)->IsDualAnalog = true; + } + + //CPad::IsDualAnalog = cont_has_capabilities(contMaple, CONT_CAPABILITIES_DUAL_ANALOG); //Query controller about Dual analog capabilities + + if (pad == 0) + { + if (contMaple == NULL) + { + CPad::GetPad(0)->IsDualAnalog = false; + NewState.DPadUp = 0; + NewState.DPadDown = 0; + NewState.DPadLeft = 0; + NewState.DPadRight = 0; + NewState.A = 0; + NewState.B = 0; + NewState.C = 0; + NewState.D = 0; + NewState.X = 0; + NewState.Y = 0; + NewState.Z = 0; + NewState.Start = 0; + NewState.RightTrigger = 0; + NewState.LeftTrigger = 0; + NewState.LeftStickX = 0; + NewState.LeftStickY = 0; + NewState.RightStickX = 0; + NewState.RightStickY = 0; + NewState.RightShock = 0; + NewState.LeftShoulder1 = 0; + } + else + { + NewState.DPadUp = state->dpad_up; //This part could be inside a compiler directive to preserve the old code and just use this block if compil + NewState.DPadDown = state->dpad_down; //I also changed CControllerState inside Pad.h and created these values for DC controllers + NewState.DPadLeft = state->dpad_left; + NewState.DPadRight = state->dpad_right; + NewState.A = state->a; + NewState.B = state->b; + NewState.C = state->c; + NewState.D = state->d; + NewState.X = state->x; + NewState.Y = state->y; + NewState.Z = state->z; + NewState.Start = state->start; + NewState.RightTrigger = state->rtrig; + NewState.LeftTrigger = state->ltrig; + NewState.LeftStickX = state->joyx; + NewState.LeftStickY = state->joyy; + NewState.RightStickX = state->joy2x; + NewState.RightStickY = state->joy2y; + NewState.RightShock = state->dpad_left; + + NewState.LeftShoulder1 = (state->rtrig > 128 && state->ltrig > 128) ? 255 : 0; + } + + } + + else + { + NewState.DPadUp = 0; + NewState.DPadDown = 0; + NewState.DPadLeft = 0; + NewState.DPadRight = 0; + NewState.A = 0; + NewState.B = 0; + NewState.C = 0; + NewState.D = 0; + NewState.X = 0; + NewState.Y = 0; + NewState.Z = 0; + NewState.Start = 0; + NewState.RightTrigger = 0; + NewState.LeftTrigger = 0; + NewState.LeftStickX = 0; + NewState.LeftStickY = 0; + NewState.RightStickX = 0; + NewState.RightStickY = 0; + NewState.RightShock = 0; + NewState.LeftShoulder1 = 0; + } + + // if (old_contMaple == nullptr && contMaple != nullptr) + // { + // CPad::GetPad(0)->IsDualAnalog = false; + // NewState.DPadUp = 0; + // NewState.DPadDown = 0; + // NewState.DPadLeft = 0; + // NewState.DPadRight = 0; + // NewState.A = 0; + // NewState.B = 0; + // NewState.C = 0; + // NewState.D = 0; + // NewState.X = 0; + // NewState.Y = 0; + // NewState.Z = 0; + // NewState.Start = 0; + // NewState.RightTrigger = 0; + // NewState.LeftTrigger = 0; + // NewState.LeftStickX = 0; + // NewState.LeftStickY = 0; + // NewState.RightStickX = 0; + // NewState.RightStickY = 0; + // NewState.RightShock = 0; + // } + +#else + NewState = ReconcileTwoControllersInput(PCTempKeyState, PCTempJoyState); + NewState = ReconcileTwoControllersInput(PCTempMouseState, NewState); +#endif + } + + PCTempJoyState.Clear(); + PCTempKeyState.Clear(); + PCTempMouseState.Clear(); + + ProcessPCSpecificStuff(); + + if (NewState.CheckForInput()) + LastTimeTouched = CTimer::GetTimeInMilliseconds(); + + if ( ++iCurrHornHistory >= HORNHISTORY_SIZE ) + iCurrHornHistory = 0; + + bHornHistory[iCurrHornHistory] = GetHorn(); + + for (int32 i = DRUNK_STEERING_BUFFER_SIZE - 2; i >= 0; i--) { + SteeringLeftRightBuffer[i + 1] = SteeringLeftRightBuffer[i]; + } + + if ( !bDisplayNoControllerMessage ) + CGame::bDemoMode = false; + + if ( JustOutOfFrontend != 0 ) + --JustOutOfFrontend; + +#ifdef RW_DC + //auto old_contMaple = contMaple; + //auto n_dev = maple_enum_count(); + contMaple = maple_enum_type(0, MAPLE_FUNC_CONTROLLER); + state = (cont_state_t *)maple_dev_status(contMaple); +#endif +} + +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 + +#ifdef RW_DC +#define DEADZONE 10 +#endif + +//The next are the actuall functions that are checked and produce the values that are used by engine to make the char run, the car turn, etc +//Although initially I didn't want to change them, I think here is the best place to create the custom desired mapping and behavior for the DC inputs +//The switch statement using CURMODE could be used in the future to define diferent control configurations, depending on the type of controller and desired mapping (e.g. Xbox like or PS2 like) +//While i think its possible, creating a system to configure custom mappings inside the game menus like in the PC game is out of my scope in the moment, I don't know if this is really necessary +//Also, the interface controls are not defined here, they are defined in Frontend.cpp unfortunately, using CControllerState values like here; Because of that, the behavior of the Start button and the A button for selecting menu itens are not here + +#ifdef RW_DC + +bool CPad::CameraSinglePress (void) +{ + if (CPad::CameraDoublePress() == false && NewState.X == true) + return true; + return 0; +} + +bool CPad::CameraDoublePress (void) +{ + if ( ArePlayerControlsDisabled() ) //Wont work driving, camera code isnt there + return false; + + if ((OldState.X == 1) && (NewState.X == 0) //Falling edge + && (CPad::GetPad(0)->CameraIsDoublePressed == false)) //Was not in double click state + { + //CPad::GetPad(0)->CameraJustUp = true; + CPad::GetPad(0)->CameraLastPressed = psTimer(); //Set timer to run + } + + if ((OldState.X == 1) && (NewState.X == 0) //Falling edge + && (CPad::GetPad(0)->CameraIsDoublePressed == true)) //Was in double click state + { + CPad::GetPad(0)->CameraIsDoublePressed = false; //ends double click state + return 0; + } + + + if ((OldState.X == 0) && (NewState.X == 1) //Rising edge + && ((psTimer() - CPad::GetPad(0)->CameraLastPressed) < 250)) //Checks timer on the Rising edge of X press + { + CPad::GetPad(0)->CameraIsDoublePressed = true; //Define that there was a double click + return true; + } + + + if ((OldState.X == 1) && (NewState.X == 1) //Button is keep pressed + && (CameraIsDoublePressed == true)) //The last state was double click + { + CPad::GetPad(0)->CameraIsDoublePressed = true; //Keep double click state + return true; + } + + return 0; +} +#endif + +int16 CPad::GetSteeringLeftRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + int16 value = 0; +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.LeftStickX; + } + else + { + if (NewState.X) + return 0; + if (NewState.A && ((NewState.LeftStickX < -64) || (NewState.LeftStickX > 64))) + return 0; + return NewState.LeftStickX; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.LeftStickX; + } + else + { + if (NewState.LeftTrigger > 128) + return 0; + if (NewState.A && ((NewState.RightTrigger > 128) || (NewState.LeftTrigger > 128))) + return 0; + return NewState.LeftStickX; + } + } + +#else + switch (CURMODE) + { + case 0: + case 2: + { + int16 axis = NewState.LeftStickX; + int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2; + + if ( Abs(axis) > Abs(dpad) ) + value = axis; + else + value = dpad; + + SteeringLeftRightBuffer[0] = value; + value = SteeringLeftRightBuffer[DrunkDrivingBufferUsed]; + + break; + } + + case 1: + case 3: + { + SteeringLeftRightBuffer[0] = NewState.LeftStickX; + value = SteeringLeftRightBuffer[DrunkDrivingBufferUsed]; + break; + } + default: + { + value = 0; + break; + } + } +#endif + + return value; +} + +int16 CPad::GetSteeringUpDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.LeftStickY; + } + else + { + if (NewState.X) + return 0; + return NewState.LeftStickY; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.LeftStickY; + } + else + { + if (NewState.LeftTrigger > 128) + return 0; + return NewState.LeftStickY; + } + } + +#else + 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; + } + } +#endif + return 0; +} + +int16 CPad::GetCarGunUpDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.RightStickY; + } + else + { + if (!NewState.X) + return 0; + return NewState.LeftStickY; + break; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.RightStickY; + } + else + { + if (NewState.LeftTrigger > 128) + return NewState.LeftStickY; + return 0; + break; + } + + } +#else + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return NewState.RightStickY; + + break; + } + + case 3: + { + return (NewState.DPadUp - NewState.DPadDown) / 2; + + break; + } + } +#endif + + return 0; +} + +int16 CPad::GetCarGunLeftRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.RightStickX; + } + else + { + if (!NewState.X) + return 0; + return NewState.LeftStickX; + break; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.RightStickX; + } + else + { + if (NewState.LeftTrigger > 128) + return NewState.LeftStickX; + return 0; + break; + } + + } +#else + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return NewState.RightStickX; + + break; + } + + case 3: + { + return (NewState.DPadRight - NewState.DPadLeft) / 2; + + break; + } + } +#endif + + return 0; +} + +int16 CPad::GetPedWalkLeftRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + +int16 axis = 0; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + axis = NewState.LeftStickX; + } + else + { + if (CPad::CameraSinglePress()) + return 0; + axis = NewState.LeftStickX; + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + axis = NewState.LeftStickX; + } + else + { + if (NewState.LeftTrigger > 128) + return 0; + axis = NewState.LeftStickX; + } + break; + } + + if (axis > DEADZONE || axis < -DEADZONE) + { + return axis; + } +#else + 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; + } + } +#endif + + return 0; +} + +int16 CPad::GetPedWalkUpDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + +int16 axis = 0; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + axis = NewState.LeftStickY; + } + else + { + if (CPad::CameraSinglePress()) + return 0; + axis = NewState.LeftStickY; + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + axis = NewState.LeftStickY; + } + else + { + if (NewState.LeftTrigger > 128) + return 0; + axis = NewState.LeftStickY; + } + break; + } + + if (axis > DEADZONE || axis < -DEADZONE) + { + return axis; + } +#else + 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; + } + } +#endif + + return 0; +} + +int16 CPad::GetAnalogueUpDown(void) +{ + switch (CURMODE) + { + case 0: +#ifdef RW_DC + case 2: + { + return NewState.LeftStickY; + + break; + } +#else + case 2: + { + int16 axis = NewState.LeftStickY; + int16 dpad = (NewState.DPadDown - NewState.DPadUp) / 2; + + if ( Abs(axis) > Abs(dpad) ) + return axis; + else + return dpad; + + break; + } +#endif + case 1: + case 3: + { + return NewState.LeftStickY; + + break; + } + } + + return 0; +} + +int16 CPad::GetAnalogueLeftRight(void) +{ + 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; +} + +bool CPad::GetLookLeft(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.Z) + return true; + } + else + { + if (NewState.A && (NewState.LeftStickX < -64)) + return true; + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.LeftTrigger > 128) + return true; + } + else + { + if (NewState.B && (NewState.LeftTrigger > 128)) + return true; + } + break; + } + return false; +#else + return !!(NewState.LeftShoulder2 && !NewState.RightShoulder2); +#endif +} + +bool CPad::GetLookRight(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.C) + return true; + } + else + { + if (NewState.A && (NewState.LeftStickX > 64)) + return true; + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.RightTrigger > 128) + return true; + } + else + { + if (NewState.B && (NewState.RightTrigger > 128)) + return true; + } + break; + } + return false; + #else + + return !!(NewState.RightShoulder2 && !NewState.LeftShoulder2); + #endif +} + + +bool CPad::GetLookBehindForCar(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.C && NewState.Z) //Consider someway to make it work with RS or LS in dual analog + return true; + } + else + { + if (NewState.RightTrigger > 128 && NewState.LeftTrigger > 128) + return true; + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.RightTrigger > 128 && NewState.LeftTrigger > 128) //Consider someway to make it work with RS or LS in dual analog + return true; + } + else + { + if (NewState.RightTrigger > 128 && NewState.LeftTrigger > 128) + return true; + } + break; + } + return false; +#else + + return !!(NewState.RightShoulder2 && NewState.LeftShoulder2); + +#endif +} + +bool CPad::GetLookBehindForPed(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.DPadDown; //Consider someway to make it work with RS and LS + } + else + { + return NewState.DPadDown; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.DPadDown; //Consider someway to make it work with RS and LS + } + else + { + return NewState.DPadDown; + } + } + return false; +#else + + return !!NewState.RightShock; +#endif +} + +bool CPad::GetHorn(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.DPadDown; //Consider someway to make it work with RS and LS + } + else + { + return NewState.DPadDown; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.DPadDown; //Consider someway to make it work with RS and LS + } + else + { + return NewState.DPadDown; + } + } +#else + 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; + } + } +#endif + + 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; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.A; + } + else + { + return NewState.A; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.B; + } + else + { + return NewState.B; + } + } +#else + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!NewState.Circle; + + break; + } + + case 3: + { + return !!NewState.RightShoulder1; + + break; + } + } +#endif + + return false; +} + +bool CPad::CarGunJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.A; + } + else + { + return NewState.A; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.B; + } + else + { + return NewState.B; + } + } + +#else + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!(NewState.Circle && !OldState.Circle); + + break; + } + + case 3: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + } +#endif + + return false; +} + +int16 CPad::GetHandBrake(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.B; + } + else + { + return NewState.B; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.C; + } + else + { + if (NewState.RightTrigger > 128) + return true; + } + } +#else + switch (CURMODE) + { + case 0: + case 1: + { + return NewState.RightShoulder1; + + break; + } + + case 2: + { + return NewState.Triangle; + + break; + } + + case 3: + { + return NewState.LeftShoulder1; + + break; + } + } +#endif + + return 0; +} + +int16 CPad::GetBrake(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.LeftTrigger; + } + else + { + return NewState.LeftTrigger; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if(NewState.X) + return 255; + } + else + { + if(NewState.X) + return 255; + } + } +#else + 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; + } + } +#endif + + return 0; +} + +bool CPad::GetExitVehicle(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + + if ( JustOutOfFrontend != 0 ) + return false; + + switch (CURMODE) + { +#ifdef RW_DC + case 0: + case 1: + case 3: + { + return !!NewState.Y; + + break; + } + + case 2: + { + return !!NewState.Y; + + break; + } +#else + case 0: + case 1: + case 3: + { + return !!NewState.Triangle; + + break; + } + + case 2: + { + return !!NewState.LeftShoulder1; + + break; + } +#endif + } + + return false; +} + +bool CPad::ExitVehicleJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + if ( JustOutOfFrontend != 0 ) + return false; + + switch (CURMODE) + { + case 0: + case 1: +#ifdef RW_DC + case 3: + { + return !!(NewState.Y && !OldState.Y); + + break; + } + + case 2: + { + return !!(NewState.Y && !OldState.Y); + + break; + } +#else + case 3: + { + return !!(NewState.Triangle && !OldState.Triangle); + + break; + } + + case 2: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } +#endif + } + + return false; +} + +int32 CPad::GetWeapon(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.RightTrigger > 128) + return true; + } + else + { + if (NewState.RightTrigger > 128) + return true; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.B; + } + else + { + return NewState.B; + } + } + +#else + switch (CURMODE) + { + case 0: + case 1: + { + return NewState.Circle; + + break; + } + + case 2: + { + return NewState.Cross; + + break; + } + + case 3: + { + return NewState.RightShoulder1; + + break; + } + } + +#endif + + return false; +} + +bool CPad::WeaponJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.RightTrigger > 128 && !(OldState.RightTrigger > 128)) + return true; + } + else + { + if (NewState.RightTrigger > 128 && !(OldState.RightTrigger > 128)) + return true; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.B && !OldState.B; + } + else + { + return NewState.B && !OldState.B; + } + } + +#else + 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; + } + } +#endif + + return false; +} + +int16 CPad::GetAccelerate(void) +{ + if ( ArePlayerControlsDisabled() ) + return 0; + +#ifdef RW_DC + + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.RightTrigger; + } + else + { + return NewState.RightTrigger; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if(NewState.A) + return 255; + } + else + { + if(NewState.A) + return 255; + } + } + +#else + + 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; + } + } + +#endif + + return 0; +} + +bool CPad::CycleCameraModeJustDown(void) +{ + bool result; + switch (CURMODE) + { + case 0: + case 2: + case 3: + { + result = !!(NewState.Select && !OldState.Select); + + break; + } + + case 1: + { + result = !!(NewState.DPadUp && !OldState.DPadUp); + + break; + } + default: + { + result = false; + break; + } + } + + if (!result) + { + switch (CURMODE) + { + case 1: + { + result = !!(NewState.DPadDown && !OldState.DPadDown); + break; + } + default: + { + result = false; + break; + } + } + } + + return result; +} + +bool CPad::CycleCameraModeUpJustDown(void) +{ + +#ifdef RW_DC + + switch (CPad::GetPad(0)->Mode) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.DPadUp && !OldState.DPadUp); + + } + else + { + return !!(NewState.DPadUp && !OldState.DPadUp); + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.DPadUp && !OldState.DPadUp); + } + else + { + return !!(NewState.DPadUp && !OldState.DPadUp); + } + break; + } + return false; + +#else + switch (CURMODE) + { + case 0: + case 2: + case 3: + { + return !!(NewState.Select && !OldState.Select); + + break; + } + + case 1: + { + return !!(NewState.DPadUp && !OldState.DPadUp); + + break; + } + } +#endif + + 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; +#ifdef RW_DC + + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.DPadRight&& !OldState.DPadRight); + } + else + { + return !!(NewState.DPadRight && !OldState.DPadRight); + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.Z; + } + else + { + return !!(NewState.DPadRight && !OldState.DPadRight); + } + break; + } + +#else + 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; + } + } +#endif + + return false; +} + +bool CPad::CycleWeaponLeftJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; +#ifdef RW_DC + return !!(NewState.DPadLeft && !OldState.DPadLeft); +#else + return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +#endif +} + +bool CPad::CycleWeaponRightJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; +#ifdef RW_DC + return !!(NewState.DPadRight && !OldState.DPadRight); +#else + return !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +#endif +} + +bool CPad::GetTarget(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.LeftTrigger > 128) + return true; + } + else + { + if (NewState.LeftTrigger > 128) + return true; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.C; + } + else + { + if (NewState.RightTrigger > 128) + return true; + } + } + +#else + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!NewState.RightShoulder1; + + break; + } + + case 3: + { + return !!NewState.LeftShoulder1; + + break; + } + } + +#endif + return false; +} + +bool CPad::TargetJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.LeftTrigger > 128) + return true; + } + else + { + if (NewState.LeftTrigger > 128) + return true; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return NewState.C; + } + else + { + if (NewState.RightTrigger > 128) + return true; + } + } + +#else + + switch (CURMODE) + { + case 0: + case 1: + case 2: + { + return !!(NewState.RightShoulder1 && !OldState.RightShoulder1); + + break; + } + + case 3: + { + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1); + + break; + } + } +#endif + + return false; +} + +bool CPad::CollectPickupJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: + { + return !!((NewState.DPadDown && NewState.A) && !(OldState.DPadDown && OldState.A)); + + break; + } + case 2: + { + return !!((NewState.DPadDown && NewState.A) && !(OldState.DPadDown && OldState.A)); + + break; + } + + case 3: + { + return !!((NewState.DPadDown && NewState.A) && !(OldState.DPadDown && OldState.A)); + + break; + } + } + + return false; +} + +bool CPad::DuckJustDown(void) +{ + if (ArePlayerControlsDisabled()) + return false; +#ifdef RW_DC + switch (CPad::GetPad(0)->Mode) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.X&& !OldState.X); + } + else + { + return !!(NewState.X&& !OldState.X); + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.X&& !OldState.X); + } + else + { + return !!(NewState.X&& !OldState.X); + } + } + + return false; +#else + return !!(NewState.LeftShock && !OldState.LeftShock); +#endif +} + +bool CPad::JumpJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; +#ifdef RW_DC + switch (CPad::GetPad(0)->Mode) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.B && !OldState.B); + } + else + { + return !!(NewState.B && !OldState.B); + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.X && !OldState.X); + } + else + { + return !!(NewState.X && !OldState.X); + } + } + + return false; +#else + return !!(NewState.Square && !OldState.Square); +#endif +} + +bool CPad::GetSprint(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + + switch (CPad::GetPad(0)->Mode) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!NewState.A; + } + else + { + return !!NewState.A; + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!NewState.A; + } + else + { + return !!NewState.A; + } + } +#else + + switch (CURMODE) + { + case 0: + case 1: + case 3: + { + return !!NewState.Cross; + + break; + } + + case 2: + { + return !!NewState.Circle; + + break; + } + } +#endif + + return false; +} + +bool CPad::ShiftTargetLeftJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; +#ifdef RW_DC + + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.DPadLeft && !OldState.DPadLeft); + } + else + { + return !!(NewState.DPadLeft && !OldState.DPadLeft); + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.LeftTrigger > 128) + return true; + } + else + { + return !!(NewState.DPadLeft && !OldState.DPadLeft); + } + } + return 0; +#else + + return !!(NewState.LeftShoulder2 && !OldState.LeftShoulder2); +#endif +} + +bool CPad::ShiftTargetRightJustDown(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; +#ifdef RW_DC + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + return !!(NewState.DPadRight && !OldState.DPadRight); + } + else + { + return !!(NewState.DPadRight && !OldState.DPadRight); + } + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + if (NewState.RightTrigger > 128) + return true; + } + else + { + return !!(NewState.DPadRight && !OldState.DPadRight); + } + } + return 0; +#else + return !!(NewState.LeftShoulder1 && !OldState.LeftShoulder1) || !!(NewState.RightShoulder2 && !OldState.RightShoulder2); +#endif +} + +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; + } +} + +bool CPad::ForceCameraBehindPlayer(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + +#ifdef RW_DC + if (CPad::CameraDoublePress()) + return true; +#else + switch (CURMODE) + { + case 0: + case 1: + { + return !!NewState.LeftShoulder1; + + break; + } + + case 2: + { + return !!NewState.Triangle; + + break; + } + + case 3: + { + return !!NewState.Circle; + + break; + } + } +#endif + return false; +} + +bool CPad::SniperZoomIn(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: +#ifdef RW_DC + case 3: + { + return !!NewState.X; + + break; + } + + case 2: + { + return !!NewState.X; + + break; + } +#else + case 3: + { + return !!NewState.Square; + + break; + } + + case 2: + { + return !!NewState.Triangle; + + break; + } +#endif + } + + return false; +} + +bool CPad::SniperZoomOut(void) +{ + if ( ArePlayerControlsDisabled() ) + return false; + + switch (CURMODE) + { + case 0: + case 1: +#ifdef RW_DC + case 3: + { + return !!NewState.A; + + break; + } + + case 2: + { + return !!NewState.A; + + break; + } +#else + case 3: + { + return !!NewState.Cross; + + break; + } + + case 2: + { + return !!NewState.Square; + + break; + } +#endif + } + + return false; +} + +int16 CPad::SniperModeLookLeftRight(void) +{ + int16 axis = NewState.LeftStickX; + int16 dpad = (NewState.DPadRight - NewState.DPadLeft) / 2; + + if ( Abs(axis) > Abs(dpad) ) { + if ( Abs(axis) > 35.0f ) { + return (axis > 0.f ? axis - 35.f : axis + 35.f) * (128.f / (128 - 35)); + } else { + return 0; + } + } else + return dpad; +} + +int16 CPad::SniperModeLookUpDown(void) +{ + int16 axis = NewState.LeftStickY; + int16 dpad; + +#ifdef FIX_BUGS + axis = -axis; +#endif + if (CPad::bInvertLook4Pad) { + axis = -axis; + dpad = (NewState.DPadDown - NewState.DPadUp) / 2; + } else { + dpad = (NewState.DPadUp - NewState.DPadDown) / 2; + } + + if ( Abs(axis) > Abs(dpad) ) { + if ( Abs(axis) > 35.0f ) { + return (axis > 0.f ? axis - 35.f : axis + 35.f) * (128.f / (128 - 35)); + } else { + return 0; + } + } else + return dpad; +} + +int16 CPad::LookAroundLeftRight(void) +{ +#ifdef RW_DC + float axis = 0; + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + axis = NewState.RightStickX; + } + else + { + if (!CPad::CameraSinglePress()) + return 0; + axis = NewState.LeftStickX; + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + axis = NewState.RightStickX; + } + else + { + if (!(NewState.LeftTrigger > 128)) + return 0; + axis = NewState.LeftStickX; + } + break; + } + +#else + float axis = GetPad(0)->NewState.RightStickX; +#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) > 10 ) + return (int16) ( (axis + ( ( axis > 0 ) ? -10 : 10) ) + * (127.0f / 64.0f) ); // 1.984375f + + return 0; +} + +int16 CPad::LookAroundUpDown(void) +{ +#ifdef RW_DC + int16 axis = 0; + + switch (CURMODE) + { + case 0: //Xbox Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + axis = NewState.RightStickY; + } + else + { + if (!CPad::CameraSinglePress()) + return 0; + axis = NewState.LeftStickY; + } + break; + case 1: //PS2 Mode + if (CPad::GetPad(0)->IsDualAnalog) + { + axis = NewState.RightStickY; + } + else + { + if (!(NewState.LeftTrigger > 128)) + return 0; + axis = NewState.LeftStickY; + } + break; + } + +#else + int16 axis = GetPad(0)->NewState.RightStickY; +#endif +#ifdef FIX_BUGS + axis = -axis; +#endif + if (CPad::bInvertLook4Pad) + axis = -axis; + + 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; +} +#undef CURMODE + +void CPad::ResetAverageWeapon(void) +{ + AverageWeapon = GetWeapon(); + AverageEntries = 1; +} + +void CPad::PrintErrorMessage(void) +{ + if (TheCamera.m_WideScreenOn) + return; + + if ( bDisplayNoControllerMessage && !CGame::playingIntro && !FrontEndMenuManager.m_bMenuActive ) + { + CSprite2d::DrawRect(CRect(SCREEN_STRETCH_X(20.0f), SCREEN_SCALE_FROM_BOTTOM(130.0f), SCREEN_STRETCH_FROM_RIGHT(20.0f), SCREEN_SCALE_Y(140.0f)), CRGBA(50, 50, 50, 210)); +#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_STANDARD); + CFont::PrintString + ( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(40.0f), + TheText.Get("NOCONT") // Please reconnect an analog controller (DUALSHOCK@) or analog controller (DUALSHOCK@2). to controller port 1 to continue + ); + } + else if ( bObsoleteControllerMessage ) + { + CSprite2d::DrawRect(CRect(SCREEN_STRETCH_X(20.0f), SCREEN_SCALE_FROM_BOTTOM(130.0f), SCREEN_STRETCH_FROM_RIGHT(20.0f), SCREEN_SCALE_Y(140.0f)), CRGBA(50, 50, 50, 210)); +#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_STANDARD); + CFont::PrintString + ( + SCREEN_WIDTH / 2, + SCREEN_HEIGHT / 2 - SCREEN_SCALE_Y(40.0f), + 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(); + + gbFastTime = false; + CPopulation::ms_bGivePedsWeapons = false; + CTimer::SetTimeScale(1.0f); + + CVehicle::bWheelsOnlyCheat = false; + CVehicle::bAllDodosCheat = false; + CVehicle::bCheat3 = false; + CVehicle::bCheat4 = false; + CVehicle::bCheat5 = false; + CVehicle::bAllTaxisHaveNitro = false; + CVehicle::bHoverCheat = false; + CVehicle::bCheat8 = false; + CVehicle::bCheat9 = false; + CVehicle::bCheat10 = false; +#ifdef RESTORE_ALLCARSHELI_CHEAT + bAllCarCheat = false; +#endif + gbBlackCars = false; + gbPinkCars = false; + + CCarCtrl::bMadDriversCheat = false; + CTrafficLights::bGreenLightsCheat = false; + CStats::ShowChaseStatOnScreen = 0; + CPed::bNastyLimbsCheat = false; + CPed::bFannyMagnetCheat = false; + CPed::bPedCheat3 = false; + +} + +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; +} + +void CPad::FixPadsAfterSave(void) +{ + UpdatePads(); + if ( bObsoleteControllerMessage ) + { + bObsoleteControllerMessage = false; + GetPad(0)->Phase = 0; + } +} diff --git a/src/miami/core/Pad.h b/src/miami/core/Pad.h new file mode 100644 index 00000000..aa76eb1f --- /dev/null +++ b/src/miami/core/Pad.h @@ -0,0 +1,569 @@ +#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, + PLAYERCONTROL_SHORTCUT_TAXI = 256, +}; + +class CControllerState +{ +public: + int16 LeftStickX, LeftStickY; + int16 RightStickX, RightStickY; + int16 RightTrigger, LeftTrigger; + int16 LeftShoulder1, LeftShoulder2; + int16 RightShoulder1, RightShoulder2; + int16 DPadUp, DPadDown, DPadLeft, DPadRight; + int16 Start, Select; + int16 Square, Triangle, Cross, Circle; + uint32_t A, B, C, D, X, Y, Z; + 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, + DRUNK_STEERING_BUFFER_SIZE = 10, + }; + CControllerState NewState; + CControllerState OldState; + int16 SteeringLeftRightBuffer[DRUNK_STEERING_BUFFER_SIZE]; + int32 DrunkDrivingBufferUsed; + CControllerState PCTempKeyState; + CControllerState PCTempJoyState; + CControllerState PCTempMouseState; + // straight out of my IDB + int16 Phase; + int16 Mode; + int16 ShakeDur; + uint16 DisablePlayerControls; + uint8 ShakeFreq; + bool bHornHistory[HORNHISTORY_SIZE]; + uint8 iCurrHornHistory; + int8 JustOutOfFrontend; + int8 bApplyBrakes; + char CheatString[12]; + int32 LastTimeTouched; + int32 AverageWeapon; + int32 AverageEntries; + bool IsKeyboardMouse; + bool IsDualAnalog; + bool CameraJustDown; + bool CameraJustUp; + uint32 CameraJustUpTime; + uint32 CameraLastPressed; + bool CameraIsDoublePressed; + +#ifdef DETECT_PAD_INPUT_SWITCH + static bool IsAffectedByController; +#endif + CPad() { } + ~CPad() { } + + static bool bDisplayNoControllerMessage; + static bool bObsoleteControllerMessage; + static bool bOldDisplayNoControllerMessage; + static bool m_bMapPadOneToPadTwo; + static bool m_bDebugCamPCOn; + static bool bHasPlayerCheated; + static bool bInvertLook4Pad; + + static CKeyboardState OldKeyState; + static CKeyboardState NewKeyState; + static CKeyboardState TempKeyState; + static char KeyBoardCheatString[30]; + 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 ClearKeyBoardHistory(); + 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); + +#ifdef RW_DC + bool CameraSinglePress(void); //Checks if camera modifier was double clicked + bool CameraDoublePress(void); +#endif + int16 GetSteeringLeftRight(void); + int16 GetSteeringUpDown(void); + int16 GetCarGunUpDown(void); + int16 GetCarGunLeftRight(void); + int16 GetPedWalkLeftRight(void); + int16 GetPedWalkUpDown(void); + int16 GetAnalogueUpDown(void); + int16 GetAnalogueLeftRight(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 CycleCameraModeJustDown(void); + bool CycleCameraModeUpJustDown(void); + bool CycleCameraModeDownJustDown(void); + bool ChangeStationJustDown(void); + bool CycleWeaponLeftJustDown(void); + bool CycleWeaponRightJustDown(void); + bool GetTarget(void); + bool TargetJustDown(void); + bool DuckJustDown(void); + bool CollectPickupJustDown(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 FixPadsAfterSave(void); + static void PrintErrorMessage(void); + static void ResetCheats(void); + static char *EditString(char *pStr, int32 nSize); + static int32 *EditCodesForControls(int32 *pRsKeys, int32 nSize); + uint32 InputHowLongAgo(void); + void SetDrunkInputDelay(int32 delay) { DrunkDrivingBufferUsed = delay; } + +#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 + + +#ifdef RW_DC + bool GetTriangleJustDown() { return !!(NewState.Triangle && !OldState.Triangle); } + bool GetCircleJustDown() { return !!(NewState.Circle && !OldState.Circle); } + bool GetCrossJustDown() { return !!(NewState.A && !OldState.A); } //------------HACKY, CHANGE LATTER, GetCrossJustDown is used by Frontend.cpp to select items on the menus + bool GetSquareJustDown() { return !!(NewState.Square && !OldState.Square); } + 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 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 GetTriangle() { return !!NewState.Triangle; } + bool GetCircle() { return !!NewState.Circle; } + bool GetCross() { return !!NewState.Cross; } + bool GetSquare() { return !!NewState.Square; } + 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 GetAJustDown() { return !!(NewState.A && !OldState.A); } + bool GetBJustDown() { return !!(NewState.B && !OldState.B); } + bool GetCJustDown() { return !!(NewState.C && !OldState.C); } + bool GetXJustDown() { return !!(NewState.X && !OldState.X); } + bool GetYJustDown() { return !!(NewState.Y && !OldState.Y); } + bool GetZJustDown() { return !!(NewState.Z && !OldState.Z); } + 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 GetStartJustDown() { return !!(NewState.Start && !OldState.Start); } + bool GetLeftStickXJustDown() { return !!(NewState.LeftStickX && !OldState.LeftStickX); } + bool GetLeftStickYJustDown() { return !!(NewState.LeftStickY && !OldState.LeftStickY); } + + bool GetAJustUp() { return !!(!NewState.A && OldState.A); } + bool GetBJustUp() { return !!(!NewState.B && OldState.B); } + bool GetCJustUp() { return !!(!NewState.C && OldState.C); } + bool GetXJustUp() { return !!(!NewState.X && OldState.X); } + bool GetYJustUp() { return !!(!NewState.Y && OldState.Y); } + bool GetZJustUp() { return !!(!NewState.Z && OldState.Z); } + 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 GetA() { return !!NewState.A; } + bool GetB() { return !!NewState.B; } + bool GetC() { return !!NewState.C; } + bool GetX() { return !!NewState.X; } + bool GetY() { return !!NewState.Y; } + bool GetZ() { return !!NewState.Z; } + bool GetDPadUp() { return !!NewState.DPadUp; } + bool GetDPadDown() { return !!NewState.DPadDown; } + bool GetDPadLeft() { return !!NewState.DPadLeft; } + bool GetDPadRight() { return !!NewState.DPadRight; } + 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; } + +#else + + 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 GetSelectJustDown() { return !!(NewState.Select && !OldState.Select); } + 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; } + bool GetSelect() { return !!NewState.Select; } + int16 GetLeftStickX(void) { return NewState.LeftStickX; } + int16 GetLeftStickY(void) { return NewState.LeftStickY; } + int16 GetRightStickX(void) { return NewState.RightStickX; } + int16 GetRightStickY(void) { return NewState.RightStickY; } +#endif + + bool ArePlayerControlsDisabled(void) { return DisablePlayerControls != PLAYERCONTROL_ENABLED; } + void SetDisablePlayerControls(uint16 who) { DisablePlayerControls |= who; } + void SetEnablePlayerControls(uint16 who) { DisablePlayerControls &= ~who; } + bool IsPlayerControlsDisabledBy(uint16 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]; diff --git a/src/miami/core/Placeable.cpp b/src/miami/core/Placeable.cpp new file mode 100644 index 00000000..6efc1540 --- /dev/null +++ b/src/miami/core/Placeable.cpp @@ -0,0 +1,62 @@ +#include "common.h" +#include "Placeable.h" + + +CPlaceable::CPlaceable(void) +{ + m_matrix.SetScale(1.0f); +} + +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/miami/core/Placeable.h b/src/miami/core/Placeable.h new file mode 100644 index 00000000..94be3211 --- /dev/null +++ b/src/miami/core/Placeable.h @@ -0,0 +1,36 @@ +#pragma once + +class CPlaceable +{ +protected: + CMatrix m_matrix; + +public: + // disable allocation + static void *operator new(size_t); + + 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, 0x48); diff --git a/src/miami/core/PlayerInfo.cpp b/src/miami/core/PlayerInfo.cpp new file mode 100644 index 00000000..36d05b82 --- /dev/null +++ b/src/miami/core/PlayerInfo.cpp @@ -0,0 +1,880 @@ +#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" +#include "Bike.h" +#include "Automobile.h" +#include "GameLogic.h" + +CVector lastPlayerPos; + +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; + field_D5 = false; + field_D6 = 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_EC = 0; + m_nUpsideDownCounter = 0; + m_nTimeCarSpentOnTwoWheels = 0; + m_nDistanceCarTravelledOnTwoWheels = 0; + m_nTimeNotFullyOnGround = 0; + m_nTimeSpentOnWheelie = 0; + m_nDistanceTravelledOnWheelie = 0.0f; + m_nTimeSpentOnStoppie = 0; + m_nDistanceTravelledOnStoppie = 0.0f; + m_nCancelWheelStuntTimer = 0; + m_nLastTimeCarSpentOnTwoWheels = 0; + m_nLastDistanceCarTravelledOnTwoWheels = 0; + m_nLastTimeSpentOnWheelie = 0; + m_nLastDistanceTravelledOnWheelie = 0; + m_nLastTimeSpentOnStoppie = 0; + m_nLastDistanceTravelledOnStoppie = 0; + m_bInfiniteSprint = false; + m_bFastReload = false; + m_bFireproof = false; + m_nMaxHealth = m_nMaxArmour = 100; + m_bGetOutOfJailFree = false; + m_bGetOutOfHospitalFree = false; + m_bDriveByAllowed = true; + m_nPreviousTimeRewardedForExplosion = 0; + m_nExplosionsSinceLastReward = 0; + m_nHavocLevel = 0; + m_fMediaAttention = 0.0f; + m_nCurrentBustedAudio = 1; + m_nBustedAudioStatus = BUSTEDAUDIO_NONE; +} + +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->IsTaxi() + && 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(); + + if (!m_pPed->InVehicle()) { + m_nTimeCarSpentOnTwoWheels = 0; + m_nTimeNotFullyOnGround = 0; + m_nTimeSpentOnWheelie = 0; + m_nTimeSpentOnStoppie = 0; + m_nCancelWheelStuntTimer = 0; + } else if (m_pPed->m_pMyVehicle->IsCar()) { + CAutomobile *car = (CAutomobile*)m_pPed->m_pMyVehicle; + + if (car->m_nWheelsOnGround < 3) + m_nTimeNotFullyOnGround += CTimer::GetTimeStepInMilliseconds(); + else + m_nTimeNotFullyOnGround = 0; + + if (car->m_aSuspensionSpringRatioPrev[2] == 1.f && car->m_aSuspensionSpringRatioPrev[3] == 1.f) { + if (car->m_aSuspensionSpringRatioPrev[0] < 1.0f && car->m_aSuspensionSpringRatioPrev[1] < 1.0f && car->m_fDamageImpulse == 0.0f) { + m_nTimeCarSpentOnTwoWheels += CTimer::GetTimeStepInMilliseconds(); + m_nDistanceCarTravelledOnTwoWheels += car->m_fDistanceTravelled; + m_nCancelWheelStuntTimer = Max(0.0f, m_nCancelWheelStuntTimer - CTimer::GetTimeStepInMilliseconds() * 0.5f); + + } else { + if (m_nTimeCarSpentOnTwoWheels != 0 && m_nCancelWheelStuntTimer < 500) { + m_nCancelWheelStuntTimer += CTimer::GetTimeStepInMilliseconds(); + } else { + if (m_nTimeCarSpentOnTwoWheels >= 2000) { + m_nLastTimeCarSpentOnTwoWheels = m_nTimeCarSpentOnTwoWheels; + m_nLastDistanceCarTravelledOnTwoWheels = m_nDistanceCarTravelledOnTwoWheels; + if (CStats::Longest2Wheel < m_nTimeCarSpentOnTwoWheels / 1000) + CStats::Longest2Wheel = m_nTimeCarSpentOnTwoWheels / 1000; + if (CStats::Longest2WheelDist < m_nDistanceCarTravelledOnTwoWheels) + CStats::Longest2WheelDist = m_nDistanceCarTravelledOnTwoWheels; + } + m_nTimeCarSpentOnTwoWheels = 0; + m_nDistanceCarTravelledOnTwoWheels = 0; + m_nCancelWheelStuntTimer = 0; + } + } + } else if (car->m_aSuspensionSpringRatioPrev[0] == 1.0f && car->m_aSuspensionSpringRatioPrev[1] == 1.0f) { +#ifdef FIX_BUGS + if (car->m_aSuspensionSpringRatioPrev[2] < 1.f +#else + if (car->m_aSuspensionSpringRatioPrev[1] < 1.f +#endif + && car->m_aSuspensionSpringRatioPrev[3] < 1.f && 0.0f == car->m_fDamageImpulse) { + m_nTimeCarSpentOnTwoWheels += CTimer::GetTimeStepInMilliseconds(); + m_nDistanceCarTravelledOnTwoWheels += car->m_fDistanceTravelled; + m_nCancelWheelStuntTimer = Max(0.0f, m_nCancelWheelStuntTimer - CTimer::GetTimeStepInMilliseconds() * 0.2f); + + } else if (m_nTimeCarSpentOnTwoWheels != 0 && m_nCancelWheelStuntTimer < 500) { + m_nCancelWheelStuntTimer += CTimer::GetTimeStepInMilliseconds(); + + } else { + if (m_nTimeCarSpentOnTwoWheels >= 2000) { + m_nLastTimeCarSpentOnTwoWheels = m_nTimeCarSpentOnTwoWheels; + m_nLastDistanceCarTravelledOnTwoWheels = m_nDistanceCarTravelledOnTwoWheels; + if (CStats::Longest2Wheel < m_nTimeCarSpentOnTwoWheels / 1000) + CStats::Longest2Wheel = m_nTimeCarSpentOnTwoWheels / 1000; + if (CStats::Longest2WheelDist < m_nDistanceCarTravelledOnTwoWheels) + CStats::Longest2WheelDist = m_nDistanceCarTravelledOnTwoWheels; + } + m_nTimeCarSpentOnTwoWheels = 0; + m_nDistanceCarTravelledOnTwoWheels = 0; + m_nCancelWheelStuntTimer = 0; + } + } else if (m_nTimeCarSpentOnTwoWheels != 0) { + if (m_nTimeCarSpentOnTwoWheels >= 2000) { + m_nLastTimeCarSpentOnTwoWheels = m_nTimeCarSpentOnTwoWheels; + m_nLastDistanceCarTravelledOnTwoWheels = m_nDistanceCarTravelledOnTwoWheels; + if (CStats::Longest2Wheel < m_nTimeCarSpentOnTwoWheels / 1000) + CStats::Longest2Wheel = m_nTimeCarSpentOnTwoWheels / 1000; + if (CStats::Longest2WheelDist < m_nDistanceCarTravelledOnTwoWheels) + CStats::Longest2WheelDist = m_nDistanceCarTravelledOnTwoWheels; + } + m_nTimeCarSpentOnTwoWheels = 0; + m_nDistanceCarTravelledOnTwoWheels = 0; + m_nCancelWheelStuntTimer = 0; + } + m_nTimeSpentOnWheelie = 0; + m_nTimeSpentOnStoppie = 0; + } else if (m_pPed->m_pMyVehicle->IsBike()) { + CBike *bike = (CBike*)m_pPed->m_pMyVehicle; + if (bike->m_aSuspensionSpringRatioPrev[0] == 1.0f && bike->m_aSuspensionSpringRatioPrev[1] == 1.0f) { + if (bike->m_aSuspensionSpringRatioPrev[2] < 1.0f + || (bike->m_aSuspensionSpringRatioPrev[3] < 1.0f && 0.0f == bike->m_fDamageImpulse)) { + m_nTimeSpentOnWheelie += CTimer::GetTimeStepInMilliseconds(); + m_nDistanceTravelledOnWheelie += bike->m_fDistanceTravelled; + m_nCancelWheelStuntTimer = Max(0.0f, m_nCancelWheelStuntTimer - CTimer::GetTimeStepInMilliseconds() * 0.2f); + + } else { + if (m_nTimeSpentOnWheelie != 0 && m_nCancelWheelStuntTimer < 500) { + m_nCancelWheelStuntTimer += CTimer::GetTimeStepInMilliseconds(); + } else { + if (m_nTimeSpentOnWheelie >= 5000) { + m_nLastTimeSpentOnWheelie = m_nTimeSpentOnWheelie; + m_nLastDistanceTravelledOnWheelie = m_nDistanceTravelledOnWheelie; + if (CStats::LongestWheelie < m_nTimeSpentOnWheelie / 1000) + CStats::LongestWheelie = m_nTimeSpentOnWheelie / 1000; + if (CStats::LongestWheelieDist < m_nDistanceTravelledOnWheelie) + CStats::LongestWheelieDist = m_nDistanceTravelledOnWheelie; + } + m_nTimeSpentOnWheelie = 0; + m_nDistanceTravelledOnWheelie = 0; + m_nCancelWheelStuntTimer = 0; + } + } + } else if (m_nTimeSpentOnWheelie != 0) { + if (m_nTimeSpentOnWheelie >= 5000) { + m_nLastTimeSpentOnWheelie = m_nTimeSpentOnWheelie; + m_nLastDistanceTravelledOnWheelie = m_nDistanceTravelledOnWheelie; + if (CStats::LongestWheelie < m_nTimeSpentOnWheelie / 1000) + CStats::LongestWheelie = m_nTimeSpentOnWheelie / 1000; + if (CStats::LongestWheelieDist < m_nDistanceTravelledOnWheelie) + CStats::LongestWheelieDist = m_nDistanceTravelledOnWheelie; + } + m_nTimeSpentOnWheelie = 0; + m_nDistanceTravelledOnWheelie = 0; + m_nCancelWheelStuntTimer = 0; + + } else if (bike->m_aSuspensionSpringRatioPrev[2] == 1.0f && bike->m_aSuspensionSpringRatioPrev[3] == 1.0f + && 0.0f == bike->m_fDamageImpulse) { + m_nTimeSpentOnStoppie += CTimer::GetTimeStepInMilliseconds(); + m_nDistanceTravelledOnStoppie += bike->m_fDistanceTravelled; + m_nCancelWheelStuntTimer = Max(0.0f, m_nCancelWheelStuntTimer - CTimer::GetTimeStepInMilliseconds() * 0.2f); + + } else { + if (m_nTimeSpentOnStoppie != 0 && m_nCancelWheelStuntTimer < 500) { + m_nCancelWheelStuntTimer += CTimer::GetTimeStepInMilliseconds(); + } else { + if (m_nTimeSpentOnStoppie >= 2000) { + m_nLastTimeSpentOnStoppie = m_nTimeSpentOnStoppie; + m_nLastDistanceTravelledOnStoppie = m_nDistanceTravelledOnStoppie; + if (CStats::LongestStoppie < m_nTimeSpentOnStoppie / 1000) + CStats::LongestStoppie = m_nTimeSpentOnStoppie / 1000; + if (CStats::LongestStoppieDist < m_nDistanceTravelledOnStoppie) + CStats::LongestStoppieDist = m_nDistanceTravelledOnStoppie; + } + m_nTimeSpentOnStoppie = 0; + m_nDistanceTravelledOnStoppie = 0; + m_nCancelWheelStuntTimer = 0; + } + } + m_nTimeCarSpentOnTwoWheels = 0; + m_nTimeNotFullyOnGround = 0; + } else { + m_nTimeCarSpentOnTwoWheels = 0; + m_nTimeNotFullyOnGround = 0; + m_nTimeSpentOnWheelie = 0; + m_nTimeSpentOnStoppie = 0; + m_nCancelWheelStuntTimer = 0; + } + + // 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.5f, 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_ANSWER_MOBILE && 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) { + if (veh->GetStatus() != STATUS_WRECKED && veh->GetStatus() != STATUS_TRAIN_MOVING && veh->m_nDoorLock != CARLOCK_LOCKED_PLAYER_INSIDE) { + bool canJumpOff = false; + if (veh->m_vehType == VEHICLE_TYPE_BIKE) { + canJumpOff = veh->CanPedJumpOffBike(); + } else if (veh->pDriver == m_pPed) { + canJumpOff = veh->CanPedJumpOutCar(); + } + + if (canJumpOff || veh->m_vecMoveSpeed.Magnitude() < 0.1f) { + if (!veh->bIsInWater) + m_pPed->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + + } else if (veh->GetStatus() != STATUS_PLAYER && veh != CGameLogic::pShortCutTaxi) { + veh->AutoPilot.m_nTempAction = TEMPACT_WAIT; + veh->AutoPilot.m_nTimeTempAction = CTimer::GetTimeInMilliseconds() + 1000; + } + } + } else { + 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() && carBelow->m_modelIndex != MI_SKIMMER) { + weAreOnBoat = true; + m_pPed->bOnBoat = true; + if (carBelow->GetStatus() != STATUS_WRECKED && carBelow->GetUp().z > 0.3f) + 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 && field_D6) { + TheCamera.SetFadeColour(0, 0, 0); + TheCamera.Fade(1.0f, FADE_OUT); + } + if (timeWithoutRemoteCar > 2000) { + if (m_WBState == WBSTATE_PLAYING && field_D6) { + 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->bRemoveFromWorld = true; + 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->IsBoat()) && !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; + } + if(veh->m_modelIndex == MI_CADDY) + CStats::DistanceTravelledByGolfCart += veh->m_fDistanceTravelled; + else { + if(veh->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI) + CStats::DistanceTravelledByHelicoptor += veh->m_fDistanceTravelled; + if (veh->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE) + CStats::DistanceTravelledByPlane += veh->m_fDistanceTravelled; + if (veh->GetVehicleAppearance() == VEHICLE_APPEARANCE_CAR) + CStats::DistanceTravelledByCar += veh->m_fDistanceTravelled; + if (veh->GetVehicleAppearance() == VEHICLE_APPEARANCE_BIKE) + CStats::DistanceTravelledByBike += veh->m_fDistanceTravelled; + if (veh->GetVehicleAppearance() == VEHICLE_APPEARANCE_BOAT) + CStats::DistanceTravelledByBoat += veh->m_fDistanceTravelled; + + if (veh->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE) { + if (veh->m_vecMoveSpeed.Magnitude() > 0.2f) { + CStats::FlightTime += CTimer::GetTimeStep() * 16.f; // what a weird choice + } + } + } + } else { + CStats::DistanceTravelledOnFoot += FindPlayerPed()->m_fDistanceTravelled; + } + + if (m_pPed->m_pWanted->GetWantedLevel() && !CTheScripts::IsPlayerOnAMission()) { + float maxDelta = 0.0f; + static bool movedSignificantly = true; + static bool thereIsACarPathNear = true; + // there was one more guard without variable's itself??? + + if (CTimer::GetTimeInMilliseconds() / 20000 != CTimer::GetPreviousTimeInMilliseconds() / 20000) { + float posChange = (lastPlayerPos - FindPlayerCoors()).Magnitude(); + movedSignificantly = posChange >= 10.0f; + lastPlayerPos = FindPlayerCoors(); + thereIsACarPathNear = ThePaths.FindNodeClosestToCoors(FindPlayerCoors(), PATH_CAR, 60.0f, true, false, false, false) != 0; + } + switch (m_pPed->m_pWanted->GetWantedLevel()) { + case 1: + maxDelta = 31.f; + break; + case 2: + maxDelta = 62.f; + break; + case 3: + maxDelta = 125.f; + break; + case 4: + maxDelta = 250.f; + break; + case 5: + maxDelta = 500.f; + break; + case 6: + maxDelta = 1000.f; + break; + default: + break; + } + float increaseDelta = maxDelta - m_fMediaAttention; + float increaseAttentionBy = CTimer::GetTimeStep() * 0.0001f * increaseDelta; + if (increaseAttentionBy < 0.0f + || movedSignificantly && thereIsACarPathNear && !CCullZones::NoPolice() && !CCullZones::PoliceAbandonCars() && CGame::currArea == AREA_MAIN_MAP) { + m_fMediaAttention += increaseAttentionBy; + } + } else { + m_fMediaAttention = 0.0f; + } + CStats::HighestChaseValue = Max(m_fMediaAttention, CStats::HighestChaseValue); + m_nMoney = Min(999999999, m_nMoney); + m_nVisibleMoney = Min(999999999, m_nVisibleMoney); +} + +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_bFireproof); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nMaxHealth); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nMaxArmour); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfJailFree); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfHospitalFree); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bDriveByAllowed); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_aPlayerName); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nBustedAudioStatus); + CopyToBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nCurrentBustedAudio); +#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_bFireproof); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nMaxHealth); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nMaxArmour); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfJailFree); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bGetOutOfHospitalFree); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_bDriveByAllowed); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_aPlayerName); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nBustedAudioStatus); + CopyFromBuf(buf, CWorld::Players[CWorld::PlayerInFocus].m_nCurrentBustedAudio) +#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 || car->IsCar() && carCentre.z < ped->GetPosition().z && ped->GetPosition().z - 4.f < carCentre.z) { + 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(); + m_nBustedAudioStatus = BUSTEDAUDIO_NONE; + 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) { + 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 { + 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(bool actually) +{ + if (!m_pRemoteVehicle || m_pRemoteVehicle->bRemoveFromWorld) + return; + + CRemote::TakeRemoteControlledCarFromPlayer(actually); + if (actually) + m_pRemoteVehicle->BlowUpCar(FindPlayerPed()); +} + +#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); +} + +void +CPlayerInfo::DeletePlayerSkin() +{ + if (m_pSkinTexture) { + RwTextureDestroy(m_pSkinTexture); + m_pSkinTexture = nil; + } +} +#endif diff --git a/src/miami/core/PlayerInfo.h b/src/miami/core/PlayerInfo.h new file mode 100644 index 00000000..fc12267d --- /dev/null +++ b/src/miami/core/PlayerInfo.h @@ -0,0 +1,124 @@ +#pragma once + +#include "ColModel.h" + +enum eWastedBustedState +{ + WBSTATE_PLAYING, + WBSTATE_WASTED, + WBSTATE_BUSTED, + WBSTATE_FAILED_CRITICAL_MISSION, +}; + +enum eBustedAudioState +{ + BUSTEDAUDIO_NONE, + BUSTEDAUDIO_LOADING, + BUSTEDAUDIO_DONE +}; + +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; + bool field_D5; + bool field_D6; + uint32 m_nTimeLostRemoteCar; + uint32 m_nTimeLastHealthLoss; + uint32 m_nTimeLastArmourLoss; + uint32 m_nTimeTankShotGun; + int32 m_nUpsideDownCounter; + int32 field_EC; + int32 m_nTimeCarSpentOnTwoWheels; + float m_nDistanceCarTravelledOnTwoWheels; + int32 m_nTimeNotFullyOnGround; + int32 m_nTimeSpentOnWheelie; + float m_nDistanceTravelledOnWheelie; + int32 m_nTimeSpentOnStoppie; + float m_nDistanceTravelledOnStoppie; + int32 m_nCancelWheelStuntTimer; + int32 m_nLastTimeCarSpentOnTwoWheels; + float m_nLastDistanceCarTravelledOnTwoWheels; + int32 m_nLastTimeSpentOnWheelie; + float m_nLastDistanceTravelledOnWheelie; + int32 m_nLastTimeSpentOnStoppie; + float m_nLastDistanceTravelledOnStoppie; + int16 m_nTrafficMultiplier; + int16 field_12A; + float m_fRoadDensity; + uint32 m_nPreviousTimeRewardedForExplosion; + uint32 m_nExplosionsSinceLastReward; + uint32 m_nHavocLevel; + float m_fMediaAttention; + bool m_bInfiniteSprint; + bool m_bFastReload; + bool m_bFireproof; + uint8 m_nMaxHealth; + uint8 m_nMaxArmour; + bool m_bGetOutOfJailFree; + bool m_bGetOutOfHospitalFree; + bool m_bDriveByAllowed; + uint8 m_nBustedAudioStatus; + int16 m_nCurrentBustedAudio; +#ifdef GTA_PC + char m_aSkinName[32]; + RwTexture *m_pSkinTexture; +#endif + + void MakePlayerSafe(bool); + const CVector &GetPos(); + void Process(void); + void KillPlayer(void); + void ArrestPlayer(void); + bool IsPlayerInRemoteMode(void); + void PlayerFailedCriticalMission(void); + void Clear(void); + void BlowUpRCBuggy(bool); + 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); \ No newline at end of file diff --git a/src/miami/core/Pools.cpp b/src/miami/core/Pools.cpp new file mode 100644 index 00000000..e601b3c8 --- /dev/null +++ b/src/miami/core/Pools.cpp @@ -0,0 +1,647 @@ +#include "common.h" + +#include "Pools.h" + +#include "Bike.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" +#include "SaveBuf.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; +CColModelPool *CPools::ms_pColModelPool; + +#if defined GTA_PS2 && !defined MASTER // or USE_CUSTOM_ALLOCATOR +// not in VC. perhaps ifdef'ed away +#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, "PtrNode"); + CHECKMEM("after CPtrNodePool"); + ms_pEntryInfoNodePool = new CEntryInfoNodePool(NUMENTRYINFOS, "EntryInfoNode"); + CHECKMEM("after CEntryInfoNodePool"); + ms_pPedPool = new CPedPool(NUMPEDS, "Peds"); + CHECKMEM("after CPedPool"); + ms_pVehiclePool = new CVehiclePool(NUMVEHICLES, "Vehicles"); + CHECKMEM("after CVehiclePool"); + ms_pBuildingPool = new CBuildingPool(NUMBUILDINGS, "Buildings"); + CHECKMEM("after CBuildingPool"); + ms_pTreadablePool = new CTreadablePool(NUMTREADABLES, "Treadables"); + CHECKMEM("after CTreadablePool"); + ms_pObjectPool = new CObjectPool(NUMOBJECTS, "Objects"); + CHECKMEM("after CObjectPool"); + ms_pDummyPool = new CDummyPool(NUMDUMMIES, "Dummys"); + CHECKMEM("after CDummyPool"); + ms_pAudioScriptObjectPool = new CAudioScriptObjectPool(NUMAUDIOSCRIPTOBJECTS, "AudioScriptObj"); + CHECKMEM("after cAudioScriptObjectPool"); + ms_pColModelPool = new CColModelPool(NUMCOLMODELS, "ColModel"); + 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()); + debug("ColModels left %d\n", ms_pColModelPool->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; + delete ms_pColModelPool; + + 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"); +} + +// Thankfully unused, it would break the game! +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, nNumBikes; + ReadSaveBuf(&nNumCars, buf); + ReadSaveBuf(&nNumBoats, buf); + ReadSaveBuf(&nNumBikes, buf); + for (int i = 0; i < nNumCars + nNumBoats + nNumBikes; 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 if (type == VEHICLE_TYPE_BIKE) + pVehicle = new(slot) CBike(model, RANDOM_VEHICLE); + else + assert(0); + --CCarCtrl::NumRandomCars; + pVehicle->Load(buf); + CWorld::Add(pVehicle); +#else + char* vbuf = new char[Max(CBike::nSaveStructSize, 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 if (type == VEHICLE_TYPE_BIKE) { +#ifdef FIX_BUGS + memcpy(vbuf, buf, sizeof(CBike)); +#else + memcpy(vbuf, buf, sizeof(CAutomobile)); +#endif + SkipSaveBuf(buf, sizeof(CBike)); + CBike* pBike = new(slot) CBike(model, RANDOM_VEHICLE); + pVehicle = pBike; + --CCarCtrl::NumRandomCars; + } + 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; + CCarCtrl::UpdateCarCount(pVehicle, false); + CWorld::Add(pVehicle); + delete[] vbuf; +#endif + } +VALIDATESAVEBUF(size) +} + +void CPools::SaveVehiclePool(uint8* buf, uint32* size) +{ +INITSAVEBUF + int nNumCars = 0; + int nNumBoats = 0; + int nNumBikes = 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; + if (pVehicle->IsBike() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) + ++nNumBikes; +#else + if (!pVehicle->pDriver && !bHasPassenger) { + if (pVehicle->IsCar() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + ++nNumCars; + if (pVehicle->IsBoat() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + ++nNumBoats; + if (pVehicle->IsBike() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) + ++nNumBikes; +#endif + } + } + *size = nNumCars * (sizeof(uint32) + sizeof(int16) + sizeof(int32) + CAutomobile::nSaveStructSize) + sizeof(int) + + nNumBoats * (sizeof(uint32) + sizeof(int16) + sizeof(int32) + CBoat::nSaveStructSize) + sizeof(int) + + nNumBikes * (sizeof(uint32) + sizeof(int16) + sizeof(int32) + CBike::nSaveStructSize) + sizeof(int); + WriteSaveBuf(buf, nNumCars); + WriteSaveBuf(buf, nNumBoats); + WriteSaveBuf(buf, nNumBikes); + 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->IsBike()) && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) { +#else + if ((pVehicle->IsCar() || pVehicle->IsBoat() || pVehicle->IsBike()) && 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)); + } +#ifdef MISSION_REPLAY + if (pVehicle->IsBike() && (pVehicle->VehicleCreatedBy == MISSION_VEHICLE || bForceSaving)) { +#else + if (pVehicle->IsBike() && pVehicle->VehicleCreatedBy == MISSION_VEHICLE) { +#endif + WriteSaveBuf(buf, pVehicle->m_vehType); + WriteSaveBuf(buf, pVehicle->GetModelIndex()); + WriteSaveBuf(buf, GetVehicleRef(pVehicle)); + memcpy(buf, pVehicle, sizeof(CBike)); + SkipSaveBuf(buf, sizeof(CBike)); + } +#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(int16) + + + sizeof(int8) * 2 + 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_nCostValue); + CopyToBuf(buf, pObject->m_nBonusValue); + SkipSaveBuf(buf, 1); + 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_nCostValue); + CopyFromBuf(buf, pBufferObject->m_nBonusValue); + SkipSaveBuf(buf, 1); + 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; + pObject->m_nCostValue = pBufferObject->m_nCostValue; + pObject->m_nBonusValue = pBufferObject->m_nBonusValue; + 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 < TOTAL_WEAPON_SLOTS; i++) { + if (pBufferPlayer->HasWeaponSlot(i)) { + int modelId = CWeaponInfo::GetWeaponInfo(pBufferPlayer->GetWeapon(i).m_eWeaponType)->m_nModelId; + if (modelId != -1) { + CStreaming::RequestModel(modelId, STREAMFLAGS_DEPENDENCY); + int modelId2 = CWeaponInfo::GetWeaponInfo(pBufferPlayer->GetWeapon(i).m_eWeaponType)->m_nModel2Id; + if (modelId2 != -1) + CStreaming::RequestModel(modelId2, STREAMFLAGS_DEPENDENCY); + + CStreaming::LoadAllRequestedModels(false); + } + pPed->GiveWeapon(pBufferPlayer->GetWeapon(i).m_eWeaponType, pBufferPlayer->GetWeapon(i).m_nAmmoTotal, false); + } + } + + 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/miami/core/Pools.h b/src/miami/core/Pools.h new file mode 100644 index 00000000..afef1b85 --- /dev/null +++ b/src/miami/core/Pools.h @@ -0,0 +1,64 @@ +#pragma once + +#include "templates.h" +#include "Lists.h" +#include "Treadable.h" +#include "Object.h" +#include "CutsceneObject.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; +typedef CPool CColModelPool; + +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; + static CColModelPool *ms_pColModelPool; +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 CColModelPool *GetColModelPool(void) { return ms_pColModelPool; } + + 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/miami/core/Profile.cpp b/src/miami/core/Profile.cpp new file mode 100644 index 00000000..0aa18ab8 --- /dev/null +++ b/src/miami/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/miami/core/Profile.h b/src/miami/core/Profile.h new file mode 100644 index 00000000..4fe693ae --- /dev/null +++ b/src/miami/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/miami/core/Radar.cpp b/src/miami/core/Radar.cpp new file mode 100644 index 00000000..4b5cd54e --- /dev/null +++ b/src/miami/core/Radar.cpp @@ -0,0 +1,1831 @@ +#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" +#include "Font.h" +#include "SaveBuf.h" + +float CRadar::m_radarRange; +sRadarTrace CRadar::ms_RadarTrace[NUMRADARBLIPS]; +CVector2D vec2DRadarOrigin; +int32 gRadarTxdIds[64]; + +CSprite2d CRadar::CentreSprite; +CSprite2d CRadar::MapHereSprite; +CSprite2d CRadar::NorthSprite; +CSprite2d CRadar::AverySprite; +CSprite2d CRadar::BikerSprite; +CSprite2d CRadar::CortezSprite; +CSprite2d CRadar::DiazSprite; +CSprite2d CRadar::KentSprite; +CSprite2d CRadar::LawyerSprite; +CSprite2d CRadar::PhilSprite; +CSprite2d CRadar::BikersSprite; +CSprite2d CRadar::BoatyardSprite; +CSprite2d CRadar::MalibuClubSprite; +CSprite2d CRadar::CubansSprite; +CSprite2d CRadar::FilmSprite; +CSprite2d CRadar::GunSprite; +CSprite2d CRadar::HaitiansSprite; +CSprite2d CRadar::HardwareSprite; +CSprite2d CRadar::SaveHouseSprite; +CSprite2d CRadar::StripSprite; +CSprite2d CRadar::IceSprite; +CSprite2d CRadar::KCabsSprite; +CSprite2d CRadar::LovefistSprite; +CSprite2d CRadar::PrintworksSprite; +CSprite2d CRadar::PropertySprite; +CSprite2d CRadar::SunYardSprite; +CSprite2d CRadar::SpraySprite; +CSprite2d CRadar::TShirtSprite; +CSprite2d CRadar::TommySprite; +CSprite2d CRadar::PhoneSprite; +CSprite2d CRadar::RadioWildstyleSprite; +CSprite2d CRadar::RadioFlashSprite; +CSprite2d CRadar::RadioKChatSprite; +CSprite2d CRadar::RadioFeverSprite; +CSprite2d CRadar::RadioVRockSprite; +CSprite2d CRadar::RadioVCPRSprite; +CSprite2d CRadar::RadioEspantosoSprite; +CSprite2d CRadar::RadioEmotionSprite; +CSprite2d CRadar::RadioWaveSprite; +#ifdef MAP_ENHANCEMENTS +CSprite2d CRadar::WaypointSprite; +#endif + +CSprite2d *CRadar::RadarSprites[RADAR_SPRITE_COUNT] = { + nil, + &CentreSprite, + &MapHereSprite, + &NorthSprite, + &AverySprite, + &BikerSprite, + &CortezSprite, + &DiazSprite, + &KentSprite, + &LawyerSprite, + &PhilSprite, + &BikersSprite, + &BoatyardSprite, + &MalibuClubSprite, + &CubansSprite, + &FilmSprite, + &GunSprite, + &HaitiansSprite, + &HardwareSprite, + &SaveHouseSprite, + &StripSprite, + &IceSprite, + &KCabsSprite, + &LovefistSprite, + &PrintworksSprite, + &PropertySprite, + &SunYardSprite, + &SpraySprite, + &TShirtSprite, + &TommySprite, + &PhoneSprite, + &RadioWildstyleSprite, + &RadioFlashSprite, + &RadioKChatSprite, + &RadioFeverSprite, + &RadioVRockSprite, + &RadioVCPRSprite, + &RadioEspantosoSprite, + &RadioEmotionSprite, + &RadioWaveSprite, +#ifdef MAP_ENHANCEMENTS + &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) + +CRGBA CRadar::ArrowBlipColour1; +CRGBA CRadar::ArrowBlipColour2; +int16 CRadar::MapLegendCounter; +int16 CRadar::MapLegendList[NUM_MAP_LEGENDS]; +#ifdef MAP_ENHANCEMENTS +int CRadar::TargetMarkerId = -1; +CVector CRadar::TargetMarkerPos; +#endif + +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) +{ + if (FrontEndMenuManager.m_bMenuMapActive) + return 255; + + if (dist <= 1.0f) + return 255; + + if (dist <= 10.0f) + return (128.0f * ((dist - 1.0f) / 9.0f)) + ((1.0f - (dist - 1.0f) / 9.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; + ms_RadarTrace[index].m_eBlipType = BLIP_NONE; + ms_RadarTrace[index].m_eBlipDisplay = BLIP_DISPLAY_NEITHER; + ms_RadarTrace[index].m_eRadarSprite = RADAR_SPRITE_NONE; + } +} + +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_SPRAY: + case RADAR_SPRITE_GUN: + 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, CARBLIP_MARKER_COLOR_R, CARBLIP_MARKER_COLOR_G, CARBLIP_MARKER_COLOR_B, CARBLIP_MARKER_COLOR_A, 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, CHARBLIP_MARKER_COLOR_R, CHARBLIP_MARKER_COLOR_G, CHARBLIP_MARKER_COLOR_B, CHARBLIP_MARKER_COLOR_A, 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, OBJECTBLIP_MARKER_COLOR_R, OBJECTBLIP_MARKER_COLOR_G, OBJECTBLIP_MARKER_COLOR_B, OBJECTBLIP_MARKER_COLOR_A, 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, COORDBLIP_MARKER_COLOR_R, COORDBLIP_MARKER_COLOR_G, COORDBLIP_MARKER_COLOR_B, COORDBLIP_MARKER_COLOR_A, 2048, 0.2f, 0); + } + break; + } + } + } +} + +void CRadar::DrawBlips() +{ + if (!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) { +#ifdef SECUROM + extern uint8 roadBlocksPirateCheck; + if (roadBlocksPirateCheck == 2) return; +#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); + + if (!FrontEndMenuManager.m_bMenuMapActive) { + 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); + } + + 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_PROPERTY + && (!CTheScripts::bPlayerIsInTheStatium || !FrontEndMenuManager.m_bMenuMapActive)) + DrawEntityBlip(blipId); + + break; + case BLIP_COORD: + case BLIP_CONTACT_POINT: + if (ms_RadarTrace[blipId].m_eRadarSprite == RADAR_SPRITE_PHONE + && (!CTheScripts::bPlayerIsInTheStatium || !FrontEndMenuManager.m_bMenuMapActive)) + DrawCoordBlip(blipId); + + break; + default: + break; + } + } + + // New in VC: Always draw Hardware/gun/pay'n spray/save blips + for(int blipId = 0; blipId < NUMRADARBLIPS; blipId++) { + if (!ms_RadarTrace[blipId].m_bInUse) + continue; + + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SAVE && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_HARDWARE + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SPRAY && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_GUN) + continue; + + switch (ms_RadarTrace[blipId].m_eBlipType) { + case BLIP_CAR: + case BLIP_CHAR: + case BLIP_OBJECT: + if (!CTheScripts::bPlayerIsInTheStatium || !FrontEndMenuManager.m_bMenuMapActive) + DrawEntityBlip(blipId); + + break; + case BLIP_COORD: + case BLIP_CONTACT_POINT: + if (!CTheScripts::bPlayerIsInTheStatium || !FrontEndMenuManager.m_bMenuMapActive) + DrawCoordBlip(blipId); + + 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_SAVE && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_HARDWARE + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SPRAY && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_PROPERTY + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_GUN + && (!CTheScripts::bPlayerIsInTheStatium || !FrontEndMenuManager.m_bMenuMapActive)) + + DrawEntityBlip(blipId); + 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_SAVE && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_HARDWARE + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_SPRAY && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_PROPERTY + && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_GUN && ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_PHONE + && (!CTheScripts::bPlayerIsInTheStatium || !FrontEndMenuManager.m_bMenuMapActive)) + + DrawCoordBlip(blipId); + break; + default: + break; + } + } + if (FrontEndMenuManager.m_bMenuMapActive) { + CVector2D in, out; + if (!CTheScripts::bPlayerIsInTheStatium) + TransformRealWorldPointToRadarSpace(in, FindPlayerCentreOfWorld_NoSniperShift()); + else + TransformRealWorldPointToRadarSpace(in, CVector2D(-1302.5f, 1332.8f)); + + LimitRadarPoint(in); + TransformRadarPointToScreenSpace(out, in); + DrawYouAreHereSprite(out.x, out.y); + } + } +} + +void CRadar::DrawMap() +{ + if (!TheCamera.m_WideScreenOn && CHud::m_Wants_To_Draw_Hud) { + CalculateCachedSinCos(); + 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()); + if (FrontEndMenuManager.m_PrefsRadarMode != 1) + DrawRadarMap(); + } +} + +void CRadar::DrawRadarMap() +{ + 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; + + // First draw with near Z to clear nearer Z from any 3d objects + for (int i = 0; i < 4; i++) { + in.x = corners[i].x; + in.y = corners[i].y; + TransformRadarPointToScreenSpace(out[i], in); + } + CSprite2d::SetMaskVertices(4, (float *)out); + + // avoid Z fighting with the map by nudging the clear area bellow the map depth + { + auto vtx = CSprite2d::GetVertices(); + for (int i = 0; i < 4; i++) { + vtx[i].w *= 2; + } + } + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::GetVertices(), 4); + + // 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); + // Make the mask depth slightly above the map depth + // Not sure how this worked in the original code tbh + auto vtx = CSprite2d::GetVertices(); + for (int j = 0; j < 8; j++) { + vtx[j].w /= 5; + } + + 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); + + if (!CTheScripts::bPlayerIsInTheStatium && + (!(txd = CTxdStore::GetSlot(gRadarTxdIds[x + RADAR_NUM_TILES * y])->texDict) || !(texture = GetFirstTexture(txd)))) + 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]); + } + + if (CTheScripts::bPlayerIsInTheStatium) { + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + CSprite2d::SetVertices(numVertices, (float*)screenPoly, (float*)texCoords, CRGBA(204, 204, 204, 255)); + } else { + 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 MAP_ENHANCEMENTS + 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)); + + if (FrontEndMenuManager.m_bMenuMapActive) { + bool alreadyThere = false; + for (int i = 0; i < NUM_MAP_LEGENDS; i++) { + if (MapLegendList[i] == sprite) + alreadyThere = true; + } + if (!alreadyThere) { + MapLegendList[MapLegendCounter] = sprite; + MapLegendCounter++; + } + } +} + +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 MAP_ENHANCEMENTS + 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_bShortRange = 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(); + + if (FrontEndMenuManager.m_bMenuMapActive) + return dist; + + 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].m_nColor, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_Radius, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_eBlipType, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_nEntityHandle, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_vec2DPos.x, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_vec2DPos.y, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_vecPos, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_BlipIndex, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_bDim, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_bInUse, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_bShortRange, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_unused, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_wScale, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_eBlipDisplay, buf); + ReadSaveBuf(&ms_RadarTrace[i].m_eRadarSprite, buf); + } + +VALIDATESAVEBUF(size); +} + +void CRadar::SaveAllRadarBlips(uint8 *buf, uint32 *size) +{ + *size = SAVE_HEADER_SIZE + NUMRADARBLIPS * sizeof(sRadarTraceSave); + +INITSAVEBUF + WriteSaveHeader(buf, 'R', 'D', 'R', '\0', *size - SAVE_HEADER_SIZE); + +#ifdef MAP_ENHANCEMENTS + bool bWaypointDeleted = false; + if (TargetMarkerId != -1) { + ClearBlip(TargetMarkerId); + TargetMarkerId = -1; + bWaypointDeleted = true; + } +#endif + + for (int i = 0; i < NUMRADARBLIPS; i++) { + sRadarTraceSave *saveStruct = (sRadarTraceSave*) buf; + + saveStruct->m_nColor = ms_RadarTrace[i].m_nColor; + saveStruct->m_Radius = ms_RadarTrace[i].m_Radius; + saveStruct->m_eBlipType = ms_RadarTrace[i].m_eBlipType; + saveStruct->m_nEntityHandle = ms_RadarTrace[i].m_nEntityHandle; + saveStruct->m_vec2DPos = ms_RadarTrace[i].m_vec2DPos; + saveStruct->m_vecPos = ms_RadarTrace[i].m_vecPos; + saveStruct->m_BlipIndex = ms_RadarTrace[i].m_BlipIndex; + saveStruct->m_bDim = ms_RadarTrace[i].m_bDim; + saveStruct->m_bInUse = ms_RadarTrace[i].m_bInUse; + saveStruct->m_bShortRange = ms_RadarTrace[i].m_bShortRange; + saveStruct->m_unused = ms_RadarTrace[i].m_unused; + saveStruct->m_wScale = ms_RadarTrace[i].m_wScale; + saveStruct->m_eBlipDisplay = ms_RadarTrace[i].m_eBlipDisplay; + saveStruct->m_eRadarSprite = ms_RadarTrace[i].m_eRadarSprite; + + SkipSaveBuf(buf, sizeof(sRadarTraceSave)); + } + +#ifdef MAP_ENHANCEMENTS + if(bWaypointDeleted) + ToggleTargetMarker(TargetMarkerPos.x, TargetMarkerPos.y); +#endif + +VALIDATESAVEBUF(*size); +} + +void +CRadar::LoadTextures() +{ + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("hud")); + CentreSprite.SetTexture("radar_centre"); + MapHereSprite.SetTexture("arrow"); + NorthSprite.SetTexture("radar_north"); + AverySprite.SetTexture("radar_avery"); + BikerSprite.SetTexture("radar_biker"); + CortezSprite.SetTexture("radar_cortez"); + DiazSprite.SetTexture("radar_diaz"); + KentSprite.SetTexture("radar_kent"); + LawyerSprite.SetTexture("radar_lawyer"); + PhilSprite.SetTexture("radar_phil"); + BikersSprite.SetTexture("bikers"); + BoatyardSprite.SetTexture("boatyard"); + MalibuClubSprite.SetTexture("club"); + CubansSprite.SetTexture("cubans"); + FilmSprite.SetTexture("filmstudio"); + GunSprite.SetTexture("gun"); + HaitiansSprite.SetTexture("haitians"); + HardwareSprite.SetTexture("hardware"); + SaveHouseSprite.SetTexture("radar_save"); + StripSprite.SetTexture("radar_strip"); + IceSprite.SetTexture("icecream"); + KCabsSprite.SetTexture("kcabs"); + LovefistSprite.SetTexture("lovefist"); + PrintworksSprite.SetTexture("printworks"); + PropertySprite.SetTexture("property"); + SunYardSprite.SetTexture("SunYard"); + SpraySprite.SetTexture("spray"); + TShirtSprite.SetTexture("tshirt"); + TommySprite.SetTexture("tommy"); + PhoneSprite.SetTexture("phone"); + RadioWildstyleSprite.SetTexture("RWildstyle"); + RadioFlashSprite.SetTexture("RFlash"); + RadioKChatSprite.SetTexture("RKchat"); + RadioFeverSprite.SetTexture("RFever"); + RadioVRockSprite.SetTexture("RVRock"); + RadioVCPRSprite.SetTexture("RVCPR"); + RadioEspantosoSprite.SetTexture("REspantoso"); + RadioEmotionSprite.SetTexture("REmotion"); + RadioWaveSprite.SetTexture("RWave"); +#ifdef MAP_ENHANCEMENTS + WaypointSprite.SetTexture("radar_waypoint"); + if(!WaypointSprite.m_pTexture) { + // create the texture if it's missing in TXD +#define WAYPOINT_R (255>>4) +#define WAYPOINT_G (72>>4) +#define WAYPOINT_B (77>>4) + + RwRaster *raster = RwRasterCreate(16, 16, 16, rwRASTERTYPETEXTURE | rwRASTERFORMAT4444); + + RwUInt16 *pixels = (RwUInt16 *)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 + pixels[x + y * 16] = WAYPOINT_B | (WAYPOINT_G << 4) | (WAYPOINT_R << 8) | (15 << 12); + else + pixels[x + y * 16] = 0xF000; // 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::SetBlipSprite(int32 i, int32 icon) +{ + int index = CRadar::GetActualBlipArrayIndex(i); + if (index != -1) { + ms_RadarTrace[index].m_eRadarSprite = icon; + } +} + +int32 CRadar::SetCoordBlip(eBlipType type, CVector pos, uint32 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 = RADAR_TRACE_MAGENTA; + ms_RadarTrace[nextBlip].m_bDim = true; + ms_RadarTrace[nextBlip].m_bInUse = true; + ms_RadarTrace[nextBlip].m_bShortRange = false; + 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); +} + +int32 CRadar::SetShortRangeCoordBlip(eBlipType type, CVector pos, uint32 color, eBlipDisplay display) +{ + int index = SetCoordBlip(type, pos, color, display); + if (index == -1) + return -1; + ms_RadarTrace[GetActualBlipArrayIndex(index)].m_bShortRange = true; + return index; +} + +int32 CRadar::SetEntityBlip(eBlipType type, int32 handle, uint32 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 = RADAR_TRACE_YELLOW; + ms_RadarTrace[nextBlip].m_bDim = true; + ms_RadarTrace[nextBlip].m_bInUse = true; + ms_RadarTrace[nextBlip].m_bShortRange = false; + 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 (!CHud::m_Wants_To_Draw_Hud || TheCamera.m_WideScreenOn) + 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 (!CHud::m_Wants_To_Draw_Hud || TheCamera.m_WideScreenOn) + return; + + switch (mode) + { + case BLIP_MODE_TRIANGULAR_UP: + size++; + 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++; + 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() +{ + CentreSprite.Delete(); + MapHereSprite.Delete(); + NorthSprite.Delete(); + AverySprite.Delete(); + BikerSprite.Delete(); + CortezSprite.Delete(); + DiazSprite.Delete(); + KentSprite.Delete(); + LawyerSprite.Delete(); + PhilSprite.Delete(); + BikersSprite.Delete(); + BoatyardSprite.Delete(); + MalibuClubSprite.Delete(); + CubansSprite.Delete(); + FilmSprite.Delete(); + GunSprite.Delete(); + HaitiansSprite.Delete(); + HardwareSprite.Delete(); + SaveHouseSprite.Delete(); + StripSprite.Delete(); + IceSprite.Delete(); + KCabsSprite.Delete(); + LovefistSprite.Delete(); + PrintworksSprite.Delete(); + PropertySprite.Delete(); + SunYardSprite.Delete(); + SpraySprite.Delete(); + TShirtSprite.Delete(); + TommySprite.Delete(); + PhoneSprite.Delete(); + RadioWildstyleSprite.Delete(); + RadioFlashSprite.Delete(); + RadioKChatSprite.Delete(); + RadioFeverSprite.Delete(); + RadioVRockSprite.Delete(); + RadioVCPRSprite.Delete(); + RadioEspantosoSprite.Delete(); + RadioEmotionSprite.Delete(); + RadioWaveSprite.Delete(); +#ifdef MAP_ENHANCEMENTS + WaypointSprite.Delete(); +#endif + RemoveRadarSections(); +} + +void CRadar::StreamRadarSections(const CVector &posn) +{ + if (!CStreaming::ms_disableStreaming) + StreamRadarSections(Floor((RADAR_MAX_X + posn.x) / RADAR_TILE_SIZE), Ceil((RADAR_NUM_TILES - 1) - (RADAR_MAX_Y + posn.y) / RADAR_TILE_SIZE)); +} + +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; + s = -cachedSin; + c = cachedCos; + + 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) +{ + if (FrontEndMenuManager.m_bMenuMapActive) { + out.x = (FrontEndMenuManager.m_fMapCenterX - FrontEndMenuManager.m_fMapSize) + (MENU_MAP_LENGTH / 2 + MENU_MAP_LEFT_OFFSET + in.x) * FrontEndMenuManager.m_fMapSize * MENU_MAP_WIDTH_SCALE * 2.0f / MENU_MAP_LENGTH; + out.y = (FrontEndMenuManager.m_fMapCenterY - FrontEndMenuManager.m_fMapSize) + (MENU_MAP_LENGTH / 2 - MENU_MAP_TOP_OFFSET - in.y) * FrontEndMenuManager.m_fMapSize * MENU_MAP_HEIGHT_SCALE * 2.0f / MENU_MAP_LENGTH; + } else { +#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; + s = cachedSin; + c = cachedCos; + + 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 || */ + FrontEndMenuManager.m_bMenuMapActive ) { + 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()); + } +} + +void +CRadar::InitFrontEndMap() +{ + CalculateCachedSinCos(); + vec2DRadarOrigin.x = 0.0f; + vec2DRadarOrigin.y = 0.0f; + m_radarRange = MENU_MAP_LENGTH_UNIT; // just affects the multiplier in TransformRadarPointToScreenSpace + for (int i = 0; i < NUM_MAP_LEGENDS; i++) { + MapLegendList[i] = RADAR_SPRITE_NONE; + } + MapLegendCounter = 0; + ArrowBlipColour1 = CRGBA(0, 0, 0, 0); + ArrowBlipColour2 = CRGBA(0, 0, 0, 0); +} + +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) { + const float left = x - SCREEN_SCALE_X(8.0f); + const float top = y - SCREEN_SCALE_Y(40.0f); + const float right = x + SCREEN_SCALE_X(40.0); + const float bottom = y + SCREEN_SCALE_Y(8.0f); + MapHereSprite.Draw(CRect(left + SCREEN_SCALE_X(2.f), top + SCREEN_SCALE_Y(2.f), right + SCREEN_SCALE_X(2.f), bottom + SCREEN_SCALE_Y(2.f)), + CRGBA(0, 0, 0, 255)); + + MapHereSprite.Draw(CRect(left, top, right, bottom), CRGBA(255, 255, 255, 255)); + + CFont::SetWrapx(right + SCREEN_SCALE_X(28.0f)); + CFont::SetRightJustifyWrap(right); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetColor(CRGBA(255, 150, 225, 255)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(SCREEN_SCALE_X(0.65f), SCREEN_SCALE_Y(0.95f)); + CFont::PrintString(right, top, TheText.Get("MAP_YAH")); + CFont::SetDropShadowPosition(0); + CFont::DrawFonts(); + } + MapLegendList[MapLegendCounter++] = RADAR_SPRITE_MAP_HERE; +} + +#ifdef MAP_ENHANCEMENTS +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; + } + + if (nextBlip == 0) + return; + + 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, 0.0f); + 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 + +void +CRadar::DrawEntityBlip(int32 blipId) +{ + CVector2D out; + CVector2D in; + CEntity *blipEntity; + 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_bShortRange || dist <= 1.0f || FrontEndMenuManager.m_bMenuMapActive) { + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) { + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + } else { + 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); + + if (FrontEndMenuManager.m_bMenuMapActive) { + bool alreadyThere = false; + for (int i = 0; i < NUM_MAP_LEGENDS; i++) { + if (MapLegendList[i] == -2) + alreadyThere = true; + } + if (!alreadyThere) { + MapLegendList[MapLegendCounter] = -2; + MapLegendCounter++; + ArrowBlipColour2 = CRGBA((uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); + } + } + } + } + } + } +} + +void +CRadar::DrawCoordBlip(int32 blipId) +{ + CVector2D out; + CVector2D in; + if (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_bShortRange || dist <= 1.0f || FrontEndMenuManager.m_bMenuMapActive) { + if (ms_RadarTrace[blipId].m_eRadarSprite != RADAR_SPRITE_NONE) { + DrawRadarSprite(ms_RadarTrace[blipId].m_eRadarSprite, out.x, out.y, CalculateBlipAlpha(dist)); + } else { + 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); + + if (FrontEndMenuManager.m_bMenuMapActive) { + bool alreadyThere = false; + for (int i = 0; i < NUM_MAP_LEGENDS; i++) { + if (MapLegendList[i] == -1) + alreadyThere = true; + } + if (!alreadyThere) { + MapLegendList[MapLegendCounter] = -1; + MapLegendCounter++; + ArrowBlipColour1 = CRGBA((uint8)(color >> 24), (uint8)(color >> 16), (uint8)(color >> 8), 255); + } + } + } + } + } + } +} + +void +CRadar::DrawLegend(int32 x, int32 y, int32 sprite) +{ + if (sprite < 0) { + static uint32 lastChange = 0; + static int8 blipMode = 0; + + CRGBA color; + if (sprite == -1) { + color = ArrowBlipColour1; + } else { + color = ArrowBlipColour2; + } + + if (CTimer::GetTimeInMillisecondsPauseMode() - lastChange > 600) { + lastChange = CTimer::GetTimeInMillisecondsPauseMode(); + if ( blipMode == 2 ) + blipMode = 0; + else + ++blipMode; + } + + switch (blipMode) { + case BLIP_MODE_TRIANGULAR_UP: + CSprite2d::Draw2DPolygon(x + SCREEN_SCALE_X(14.0f), y + SCREEN_SCALE_Y(13.0f), x + SCREEN_SCALE_X(2.0f), y + SCREEN_SCALE_Y(13.0f), x + SCREEN_SCALE_X(8.f), y + SCREEN_SCALE_Y(2.0f), x + SCREEN_SCALE_X(8.f), y + SCREEN_SCALE_Y(2.0f), CRGBA(0, 0, 0, 255)); + CSprite2d::Draw2DPolygon(x + SCREEN_SCALE_X(12.0f), y + SCREEN_SCALE_Y(12.0f), x + SCREEN_SCALE_X(4.0f), y + SCREEN_SCALE_Y(12.0f), x + SCREEN_SCALE_X(8.f), y + SCREEN_SCALE_Y(4.0f), x + SCREEN_SCALE_X(8.f), y + SCREEN_SCALE_Y(4.0f), color); + break; + case BLIP_MODE_TRIANGULAR_DOWN: + CSprite2d::Draw2DPolygon(x + SCREEN_SCALE_X(8.0f), y + SCREEN_SCALE_Y(14.0f), x + SCREEN_SCALE_X(8.0f), y + SCREEN_SCALE_Y(14.0f), x + SCREEN_SCALE_X(2.f), y + SCREEN_SCALE_Y(3.0f), x + SCREEN_SCALE_X(2.f), y + SCREEN_SCALE_Y(3.0f), CRGBA(0, 0, 0, 255)); + CSprite2d::Draw2DPolygon(x + SCREEN_SCALE_X(8.0f), y + SCREEN_SCALE_Y(12.0f), x + SCREEN_SCALE_X(8.0f), y + SCREEN_SCALE_Y(12.0f), x + SCREEN_SCALE_X(12.f), y + SCREEN_SCALE_Y(4.0f), x + SCREEN_SCALE_X(4.f), y + SCREEN_SCALE_Y(4.0f), color); + break; + case BLIP_MODE_SQUARE: + CSprite2d::DrawRect(CRect(x + SCREEN_SCALE_X(4.0f), y + SCREEN_SCALE_Y(3.0f), SCREEN_SCALE_X(12.0f) + x, SCREEN_SCALE_Y(12.0f) + y), CRGBA(0, 0, 0, 255)); + CSprite2d::DrawRect(CRect(x + SCREEN_SCALE_X(5.0f), y + SCREEN_SCALE_Y(4.0f), SCREEN_SCALE_X(11.0f) + x, SCREEN_SCALE_Y(11.0f) + y), color); + break; + } + + } else { + RadarSprites[sprite]->Draw(CRect(x, y, x + SCREEN_SCALE_X(16.f), y + SCREEN_SCALE_X(16.f)), CRGBA(255, 255, 255, 255)); + } + + wchar *text; + switch ( sprite ) { + case RADAR_SPRITE_ENTITY_BLIP: + text = TheText.Get("LG_38"); + break; + case RADAR_SPRITE_COORD_BLIP: + text = TheText.Get("LG_35"); + break; + case RADAR_SPRITE_MAP_HERE: + text = TheText.Get("LG_01"); + break; + case RADAR_SPRITE_AVERY: + text = TheText.Get("LG_02"); + break; + case RADAR_SPRITE_BIKER: + text = TheText.Get("LG_03"); + break; + case RADAR_SPRITE_CORTEZ: + text = TheText.Get("LG_04"); + break; + case RADAR_SPRITE_DIAZ: + text = TheText.Get("LG_05"); + break; + case RADAR_SPRITE_KENT: + text = TheText.Get("LG_06"); + break; + case RADAR_SPRITE_LAWYER: + text = TheText.Get("LG_07"); + break; + case RADAR_SPRITE_PHIL: + text = TheText.Get("LG_08"); + break; + case RADAR_SPRITE_BIKERS: + text = TheText.Get("LG_03"); + break; + case RADAR_SPRITE_BOATYARD: + text = TheText.Get("LG_09"); + break; + case RADAR_SPRITE_MALIBU_CLUB: + text = TheText.Get("LG_10"); + break; + case RADAR_SPRITE_CUBANS: + text = TheText.Get("LG_11"); + break; + case RADAR_SPRITE_FILM: + text = TheText.Get("LG_12"); + break; + case RADAR_SPRITE_GUN: + text = TheText.Get("LG_13"); + break; + case RADAR_SPRITE_HAITIANS: + text = TheText.Get("LG_14"); + break; + case RADAR_SPRITE_HARDWARE: + text = TheText.Get("LG_15"); + break; + case RADAR_SPRITE_SAVE: + text = TheText.Get("LG_16"); + break; + case RADAR_SPRITE_STRIP: + text = TheText.Get("LG_37"); + break; + case RADAR_SPRITE_ICE: + text = TheText.Get("LG_17"); + break; + case RADAR_SPRITE_KCABS: + text = TheText.Get("LG_18"); + break; + case RADAR_SPRITE_LOVEFIST: + text = TheText.Get("LG_19"); + break; + case RADAR_SPRITE_PRINTWORKS: + text = TheText.Get("LG_20"); + break; + case RADAR_SPRITE_PROPERTY: + text = TheText.Get("LG_21"); + break; + case RADAR_SPRITE_SUNYARD: + text = TheText.Get("LG_36"); + break; + case RADAR_SPRITE_SPRAY: + text = TheText.Get("LG_22"); + break; + case RADAR_SPRITE_TSHIRT: + text = TheText.Get("LG_23"); + break; + case RADAR_SPRITE_TOMMY: + text = TheText.Get("LG_24"); + break; + case RADAR_SPRITE_PHONE: + text = TheText.Get("LG_25"); + break; + case RADAR_SPRITE_RADIO_WILDSTYLE: + text = TheText.Get("LG_26"); + break; + case RADAR_SPRITE_RADIO_FLASH: + text = TheText.Get("LG_27"); + break; + case RADAR_SPRITE_RADIO_KCHAT: + text = TheText.Get("LG_28"); + break; + case RADAR_SPRITE_RADIO_FEVER: + text = TheText.Get("LG_29"); + break; + case RADAR_SPRITE_RADIO_VROCK: + text = TheText.Get("LG_30"); + break; + case RADAR_SPRITE_RADIO_VCPR: + text = TheText.Get("LG_31"); + break; + case RADAR_SPRITE_RADIO_ESPANTOSO: + text = TheText.Get("LG_32"); + break; + case RADAR_SPRITE_RADIO_EMOTION: + text = TheText.Get("LG_33"); + break; + case RADAR_SPRITE_RADIO_WAVE: + text = TheText.Get("LG_34"); + break; +#ifdef MAP_ENHANCEMENTS + case RADAR_SPRITE_WAYPOINT: + text = TheText.Get("LG_38"); + break; +#endif + default: + break; + } + CFont::PrintString(SCREEN_SCALE_X(20.f) + x, SCREEN_SCALE_Y(3.0f) + y, text); +} diff --git a/src/miami/core/Radar.h b/src/miami/core/Radar.h new file mode 100644 index 00000000..afb37fe4 --- /dev/null +++ b/src/miami/core/Radar.h @@ -0,0 +1,294 @@ +#pragma once +#include "Sprite2d.h" +#include "Draw.h" + +#define CARBLIP_MARKER_COLOR_R 252 +#define CARBLIP_MARKER_COLOR_G 138 +#define CARBLIP_MARKER_COLOR_B 242 +#define CARBLIP_MARKER_COLOR_A 255 + +#define CHARBLIP_MARKER_COLOR_R 252 +#define CHARBLIP_MARKER_COLOR_G 138 +#define CHARBLIP_MARKER_COLOR_B 242 +#define CHARBLIP_MARKER_COLOR_A 255 + +#define OBJECTBLIP_MARKER_COLOR_R 252 +#define OBJECTBLIP_MARKER_COLOR_G 138 +#define OBJECTBLIP_MARKER_COLOR_B 242 +#define OBJECTBLIP_MARKER_COLOR_A 255 + +#define COORDBLIP_MARKER_COLOR_R 252 +#define COORDBLIP_MARKER_COLOR_G 138 +#define COORDBLIP_MARKER_COLOR_B 242 +#define COORDBLIP_MARKER_COLOR_A 228 + +#define NUM_MAP_LEGENDS 75 + +#define MENU_MAP_LENGTH_UNIT 1190.0f // in game unit +#define MENU_MAP_WIDTH_SCALE 1.112f // in game unit (originally 1.112494151260504f) +#define MENU_MAP_HEIGHT_SCALE 1.119f // in game unit (originally 1.118714268907563f) +#define MENU_MAP_TOP_OFFSET 0.28f // in length unit defined above - ~333 game unit +#define MENU_MAP_LEFT_OFFSET 0.185f // in length unit defined above - ~220 game unit +#define MENU_MAP_LENGTH (4000.f / MENU_MAP_LENGTH_UNIT) + +enum eBlipType +{ + BLIP_NONE, + BLIP_CAR, + BLIP_CHAR, + BLIP_OBJECT, + BLIP_COORD, + BLIP_CONTACT_POINT +}; + +enum eBlipDisplay +{ + BLIP_DISPLAY_NEITHER = 0, + BLIP_DISPLAY_MARKER_ONLY = 1, + BLIP_DISPLAY_BLIP_ONLY = 2, + BLIP_DISPLAY_BOTH = 3, +}; + +enum eRadarSprite +{ + RADAR_SPRITE_ENTITY_BLIP = -2, + RADAR_SPRITE_COORD_BLIP = -1, + RADAR_SPRITE_NONE = 0, + RADAR_SPRITE_CENTRE, + RADAR_SPRITE_MAP_HERE, + RADAR_SPRITE_NORTH, + RADAR_SPRITE_AVERY, + RADAR_SPRITE_BIKER, + RADAR_SPRITE_CORTEZ, + RADAR_SPRITE_DIAZ, + RADAR_SPRITE_KENT, + RADAR_SPRITE_LAWYER, + RADAR_SPRITE_PHIL, + RADAR_SPRITE_BIKERS, + RADAR_SPRITE_BOATYARD, + RADAR_SPRITE_MALIBU_CLUB, + RADAR_SPRITE_CUBANS, + RADAR_SPRITE_FILM, + RADAR_SPRITE_GUN, + RADAR_SPRITE_HAITIANS, + RADAR_SPRITE_HARDWARE, + RADAR_SPRITE_SAVE, + RADAR_SPRITE_STRIP, + RADAR_SPRITE_ICE, + RADAR_SPRITE_KCABS, + RADAR_SPRITE_LOVEFIST, + RADAR_SPRITE_PRINTWORKS, + RADAR_SPRITE_PROPERTY, + RADAR_SPRITE_SUNYARD, + RADAR_SPRITE_SPRAY, + RADAR_SPRITE_TSHIRT, + RADAR_SPRITE_TOMMY, + RADAR_SPRITE_PHONE, + RADAR_SPRITE_RADIO_WILDSTYLE, + RADAR_SPRITE_RADIO_FLASH, + RADAR_SPRITE_RADIO_KCHAT, + RADAR_SPRITE_RADIO_FEVER, + RADAR_SPRITE_RADIO_VROCK, + RADAR_SPRITE_RADIO_VCPR, + RADAR_SPRITE_RADIO_ESPANTOSO, + RADAR_SPRITE_RADIO_EMOTION, + RADAR_SPRITE_RADIO_WAVE, +#ifdef MAP_ENHANCEMENTS + 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; + CVector m_vec2DPos; + CVector m_vecPos; + uint16 m_BlipIndex; + bool m_bDim; + bool m_bInUse; + bool m_bShortRange; + bool m_unused; + float m_Radius; + int16 m_wScale; + uint16 m_eBlipDisplay; // eBlipDisplay + uint16 m_eRadarSprite; // eRadarSprite +}; + +// Either that was a thing while saving/loading blips, or they added sizes of each field one by one. I want to do the former. +#pragma pack(push,1) +struct sRadarTraceSave +{ + uint32 m_nColor; + float m_Radius; + uint32 m_eBlipType; // eBlipType + int32 m_nEntityHandle; + CVector2D m_vec2DPos; + CVector m_vecPos; + uint16 m_BlipIndex; + bool m_bDim; + bool m_bInUse; + bool m_bShortRange; + bool m_unused; + int16 m_wScale; + uint16 m_eBlipDisplay; // eBlipDisplay + uint16 m_eRadarSprite; // eRadarSprite +}; +#pragma pack(pop) + +// Values for screen space +#define RADAR_LEFT (40.0f) +#define RADAR_BOTTOM (40.0f) + +#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 CentreSprite; + static CSprite2d MapHereSprite; + static CSprite2d NorthSprite; + static CSprite2d AverySprite; + static CSprite2d BikerSprite; + static CSprite2d CortezSprite; + static CSprite2d DiazSprite; + static CSprite2d KentSprite; + static CSprite2d LawyerSprite; + static CSprite2d PhilSprite; + static CSprite2d BikersSprite; + static CSprite2d BoatyardSprite; + static CSprite2d MalibuClubSprite; + static CSprite2d CubansSprite; + static CSprite2d FilmSprite; + static CSprite2d GunSprite; + static CSprite2d HaitiansSprite; + static CSprite2d HardwareSprite; + static CSprite2d SaveHouseSprite; + static CSprite2d StripSprite; + static CSprite2d IceSprite; + static CSprite2d KCabsSprite; + static CSprite2d LovefistSprite; + static CSprite2d PrintworksSprite; + static CSprite2d PropertySprite; + static CSprite2d SunYardSprite; + static CSprite2d SpraySprite; + static CSprite2d TShirtSprite; + static CSprite2d TommySprite; + static CSprite2d PhoneSprite; + static CSprite2d RadioWildstyleSprite; + static CSprite2d RadioFlashSprite; + static CSprite2d RadioKChatSprite; + static CSprite2d RadioFeverSprite; + static CSprite2d RadioVRockSprite; + static CSprite2d RadioVCPRSprite; + static CSprite2d RadioEspantosoSprite; + static CSprite2d RadioEmotionSprite; + static CSprite2d RadioWaveSprite; + static CSprite2d *RadarSprites[RADAR_SPRITE_COUNT]; + static float cachedCos; + static float cachedSin; + static CRGBA ArrowBlipColour1; + static CRGBA ArrowBlipColour2; + static int16 MapLegendList[NUM_MAP_LEGENDS]; + static int16 MapLegendCounter; + +#ifdef MAP_ENHANCEMENTS + static CSprite2d WaypointSprite; + static int TargetMarkerId; + static CVector TargetMarkerPos; +#endif + + static void InitFrontEndMap(); + static void DrawYouAreHereSprite(float, float); +#ifdef MAP_ENHANCEMENTS + 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, uint32, eBlipDisplay); + static int32 SetEntityBlip(eBlipType type, int32, uint32, eBlipDisplay); + static int32 SetShortRangeCoordBlip(eBlipType type, CVector pos, uint32, 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); + static void CalculateCachedSinCos(); + static void DrawEntityBlip(int32 blipId); + static void DrawCoordBlip(int32 blipId); + static void DrawLegend(int32, int32, int32); +}; diff --git a/src/miami/core/Range2D.cpp b/src/miami/core/Range2D.cpp new file mode 100644 index 00000000..33eafd0e --- /dev/null +++ b/src/miami/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/miami/core/Range2D.h b/src/miami/core/Range2D.h new file mode 100644 index 00000000..f239e7de --- /dev/null +++ b/src/miami/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/miami/core/Range3D.cpp b/src/miami/core/Range3D.cpp new file mode 100644 index 00000000..7fa28d67 --- /dev/null +++ b/src/miami/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/miami/core/Range3D.h b/src/miami/core/Range3D.h new file mode 100644 index 00000000..f42b523b --- /dev/null +++ b/src/miami/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/miami/core/References.cpp b/src/miami/core/References.cpp new file mode 100644 index 00000000..09913817 --- /dev/null +++ b/src/miami/core/References.cpp @@ -0,0 +1,143 @@ +#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; + } +} + +// Clean up the reference from *pent -> 'this' +void +CEntity::CleanUpOldReference(CEntity **pent) +{ + CReference* ref, ** lastnextp; + lastnextp = &m_pFirstReference; + for (ref = m_pFirstReference; ref; ref = ref->next) { + if (ref->pentity == pent) { + *lastnextp = ref->next; + ref->next = CReferences::pEmptyList; + CReferences::pEmptyList = ref; + break; + } + lastnextp = &ref->next; + } +} + +// 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/miami/core/References.h b/src/miami/core/References.h new file mode 100644 index 00000000..e7a64de7 --- /dev/null +++ b/src/miami/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/miami/core/Ropes.cpp b/src/miami/core/Ropes.cpp new file mode 100644 index 00000000..71297eb1 --- /dev/null +++ b/src/miami/core/Ropes.cpp @@ -0,0 +1,176 @@ +#include "common.h" + +#include "main.h" +#include "Timer.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "CopPed.h" +#include "Population.h" +#include "RenderBuffer.h" +#include "Camera.h" +#include "Ropes.h" + +CRope CRopes::aRopes[8]; + +RwImVertexIndex RopeIndices[64] = { + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, + 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, + 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, + 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, + 31, 32 // unused +}; + +void +CRope::Update(void) +{ + int i; + float step = Pow(0.85f, CTimer::GetTimeStep()); + if(!m_bWasRegistered && CTimer::GetTimeInMilliseconds() > m_updateTimer){ + m_speed[0].z -= 0.0015f*CTimer::GetTimeStep(); + m_pos[0] += m_speed[0]*CTimer::GetTimeStep(); + } + for(i = 1; i < ARRAY_SIZE(m_pos); i++){ + CVector prevPos = m_pos[i]; + m_pos[i] += m_speed[i]*step*CTimer::GetTimeStep(); + m_pos[i].z -= 0.05f*CTimer::GetTimeStep(); + CVector dist = m_pos[i] - m_pos[i-1]; + m_pos[i] = m_pos[i-1] + (0.625f/dist.Magnitude())*dist; + m_speed[i] = (m_pos[i] - prevPos)/CTimer::GetTimeStep(); + } + if(!m_bWasRegistered && m_pos[0].z < 0.0f) + m_bActive = false; + m_bWasRegistered = false; +} + +void +CRope::Render(void) +{ + int i; + int numVerts = 0; + if(!TheCamera.IsSphereVisible(m_pos[16], 20.0f)) + return; + + for(i = 0; i < ARRAY_SIZE(m_pos); i++){ + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[i], 128, 128, 128, 100); + RwIm3DVertexSetPos(&TempBufferRenderVertices[i], m_pos[i].x, m_pos[i].y, m_pos[i].z); + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + if(RwIm3DTransform(TempBufferRenderVertices, ARRAY_SIZE(m_pos), nil, rwIM3D_VERTEXXYZ|rwIM3D_VERTEXRGBA)){ +#ifdef FIX_BUGS + RwIm3DRenderIndexedPrimitive(rwPRIMTYPELINELIST, RopeIndices, 2*(ARRAY_SIZE(m_pos)-1)); +#else + RwIm3DRenderIndexedPrimitive(rwPRIMTYPEPOLYLINE, RopeIndices, 2*(ARRAY_SIZE(m_pos)-1)); +#endif + RwIm3DEnd(); + } +} + + +void +CRopes::Init(void) +{ + int i; + for(i = 0; i < ARRAY_SIZE(aRopes); i++) + aRopes[i].m_bActive = false; +} + +void +CRopes::Update(void) +{ + int i; + for(i = 0; i < ARRAY_SIZE(aRopes); i++) + if(aRopes[i].m_bActive) + aRopes[i].Update(); +} + +void +CRopes::Render(void) +{ + int i; + PUSH_RENDERGROUP("CRopes::Render"); + for(i = 0; i < ARRAY_SIZE(aRopes); i++) + if(aRopes[i].m_bActive) + aRopes[i].Render(); + POP_RENDERGROUP(); +} + +bool +CRopes::RegisterRope(uintptr id, CVector pos, bool setUpdateTimer) +{ + int i, j; + for(i = 0; i < ARRAY_SIZE(aRopes); i++){ + if(aRopes[i].m_bActive && aRopes[i].m_id == id){ + aRopes[i].m_pos[0] = pos; + aRopes[i].m_speed[0] = CVector(0.0f, 0.0f, 0.0f); + aRopes[i].m_bWasRegistered = true; + return true; + } + } + for(i = 0; i < ARRAY_SIZE(aRopes); i++) + if(!aRopes[i].m_bActive){ + aRopes[i].m_id = id; + aRopes[i].m_pos[0] = pos; + aRopes[i].m_speed[0] = CVector(0.0f, 0.0f, 0.0f); + aRopes[i].m_unk = false; + aRopes[i].m_bWasRegistered = true; + aRopes[i].m_updateTimer = setUpdateTimer ? CTimer::GetTimeInMilliseconds() + 20000 : 0; + for(j = 1; j < ARRAY_SIZE(aRopes[0].m_pos); j++){ + if(j & 1) + aRopes[i].m_pos[j] = aRopes[i].m_pos[j-1] + CVector(0.0f, 0.0f, 0.625f); + else + aRopes[i].m_pos[j] = aRopes[i].m_pos[j-1] - CVector(0.0f, 0.0f, 0.625f); + aRopes[i].m_speed[j] = CVector(0.0f, 0.0f, 0.0f); + } + aRopes[i].m_bActive = true; + return true; + } + return false; +} + +void +CRopes::SetSpeedOfTopNode(uintptr id, CVector speed) +{ + int i; + for(i = 0; i < ARRAY_SIZE(aRopes); i++) + if(aRopes[i].m_bActive && aRopes[i].m_id == id){ + aRopes[i].m_speed[0] = speed; + return; + } +} + +bool +CRopes::FindCoorsAlongRope(uintptr id, float t, CVector *coors) +{ + int i, j; + float f; + for(i = 0; i < ARRAY_SIZE(aRopes); i++) + if(aRopes[i].m_bActive && aRopes[i].m_id == id){ + t = (ARRAY_SIZE(aRopes[0].m_pos)-1)*Clamp(t, 0.0f, 0.999f); + j = t; + f = t - j; + *coors = (1.0f-f)*aRopes[i].m_pos[j] + f*aRopes[i].m_pos[j+1]; + return true; + } + return false; +} + +bool +CRopes::CreateRopeWithSwatComingDown(CVector pos) +{ + static uint32 ropeId = 0; + + if(!CStreaming::HasModelLoaded(MI_SWAT) || !RegisterRope(ropeId+100, pos, true)) + return false; + CCopPed *swat = (CCopPed*)CPopulation::AddPed(PEDTYPE_COP, COP_HELI_SWAT, pos); + swat->bUsesCollision = false; + swat->m_pRopeEntity = (CEntity*)1; + swat->m_nRopeID = 100 + ropeId; + CAnimManager::BlendAnimation(swat->GetClump(), ASSOCGRP_STD, ANIM_STD_ABSEIL, 4.0f); + ropeId++; + return true; +} diff --git a/src/miami/core/Ropes.h b/src/miami/core/Ropes.h new file mode 100644 index 00000000..7930fa98 --- /dev/null +++ b/src/miami/core/Ropes.h @@ -0,0 +1,31 @@ +#pragma once + +class CRope +{ +public: + bool m_bActive; + bool m_bWasRegistered; + bool m_unk; + uintptr m_id; + uint32 m_updateTimer; + CVector m_pos[32]; + CVector m_speed[32]; + + void Update(void); + void Render(void); +}; + +class CRopes +{ + static CRope aRopes[8]; + +public: + + static void Init(void); + static void Update(void); + static void Render(void); + static bool RegisterRope(uintptr id, CVector pos, bool setUpdateTimer); + static void SetSpeedOfTopNode(uintptr id, CVector speed); + static bool FindCoorsAlongRope(uintptr id, float t, CVector *coors); + static bool CreateRopeWithSwatComingDown(CVector pos); +}; diff --git a/src/miami/core/Stats.cpp b/src/miami/core/Stats.cpp new file mode 100644 index 00000000..8c6137f2 --- /dev/null +++ b/src/miami/core/Stats.cpp @@ -0,0 +1,1443 @@ +#include "common.h" + +#include "Stats.h" +#include "Text.h" +#include "World.h" +#include "Pad.h" +#include "DMAudio.h" +#include "main.h" +#include "Font.h" +#include "Frontend.h" +#include "audio_enums.h" + +#include + +#ifdef USE_PRECISE_MEASUREMENT_CONVERTION +#define MILES_IN_METER 0.000621371192f +#define FEET_IN_METER 3.28084f +#else +#define MILES_IN_METER (1 / 1670.f) +#define FEET_IN_METER 3.33f +#endif + +int32 CStats::SeagullsKilled; +int32 CStats::BoatsExploded; +int32 CStats::WantedStarsAttained; +int32 CStats::WantedStarsEvaded; +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::DistanceTravelledByCar; +float CStats::DistanceTravelledByHelicoptor; +float CStats::DistanceTravelledByBike; +float CStats::DistanceTravelledByBoat; +float CStats::DistanceTravelledByPlane; +float CStats::DistanceTravelledByGolfCart; +float CStats::DistanceTravelledOnFoot; +int32 CStats::FlightTime; +int32 CStats::TimesDrowned; +int32 CStats::PhotosTaken; +float CStats::LoanSharks; +float CStats::StoresKnockedOff; +float CStats::MovieStunts; +float CStats::Assassinations; +float CStats::PizzasDelivered; +float CStats::GarbagePickups; +float CStats::IceCreamSold; +float CStats::TopShootingRangeScore; +float CStats::ShootingRank; +float CStats::ProgressMade; +float 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::LivesSavedWithAmbulance; +int32 CStats::CriminalsCaught; +int32 CStats::HighestLevelAmbulanceMission; +int32 CStats::HighestLevelVigilanteMission; +int32 CStats::HighestLevelFireMission; +int32 CStats::FiresExtinguished; +int32 CStats::TotalNumberKillFrenzies; +int32 CStats::TotalNumberMissions; +int32 CStats::RoundsFiredByPlayer; +int32 CStats::KgsOfExplosivesUsed; +int32 CStats::BulletsThatHit; +int32 CStats::BestTimeBombDefusal; +int32 CStats::FastestTimes[CStats::TOTAL_FASTEST_TIMES]; +int32 CStats::HighestScores[CStats::TOTAL_HIGHEST_SCORES]; +int32 CStats::BestPositions[CStats::TOTAL_BEST_POSITIONS]; +bool CStats::PropertyOwned[CStats::TOTAL_PROPERTIES]; +int32 CStats::NumPropertyOwned; +int32 CStats::PropertyDestroyed; +float CStats::HighestChaseValue; +int32 CStats::CheatedCount; +int32 CStats::ShowChaseStatOnScreen; +int32 CStats::PamphletMissionPassed; +bool CStats::abSonyCDs[1]; +int32 CStats::BloodRingKills; +int32 CStats::BloodRingTime; +float CStats::FavoriteRadioStationList[NUM_RADIOS]; + +int32 CStats::Sprayings; +float CStats::AutoPaintingBudget; +int32 CStats::NoMoreHurricanes; +float CStats::FashionBudget; +float CStats::PropertyBudget; +float CStats::WeaponBudget; +int32 CStats::SafeHouseVisits; +int32 CStats::TyresPopped; + +int32 CStats::LongestWheelie; +int32 CStats::LongestStoppie; +int32 CStats::Longest2Wheel; +float CStats::LongestWheelieDist; +float CStats::LongestStoppieDist; +float CStats::Longest2WheelDist; + +void CStats::Init() +{ + PeopleKilledByOthers = 0; + PeopleKilledByPlayer = 0; + CarsExploded = 0; + BoatsExploded = 0; + RoundsFiredByPlayer = 0; + for (int i = 0; i < NUM_PEDTYPES; i++) + PedsKilledOfThisType[i] = 0; + HelisDestroyed = 0; + ProgressMade = 0.0f; + KgsOfExplosivesUsed = 0; + BulletsThatHit = 0; + TyresPopped = 0; + HeadsPopped = 0; + WantedStarsAttained = 0; + WantedStarsEvaded = 0; + TimesArrested = 0; + TimesDied = 0; + DaysPassed = 0; + SafeHouseVisits = 0; + Sprayings = 0; + MaximumJumpDistance = 0; + MaximumJumpHeight = 0; + MaximumJumpFlips = 0; + MaximumJumpSpins = 0; + BestStuntJump = 0; + NumberOfUniqueJumpsFound = 0; + TotalNumberOfUniqueJumps = 0; + MissionsGiven = 0; + MissionsPassed = 0; + PassengersDroppedOffWithTaxi = 0; + MoneyMadeWithTaxi = 0; + DistanceTravelledOnFoot = 0; + DistanceTravelledByCar = 0; + DistanceTravelledByBike = 0; + DistanceTravelledByBoat = 0; + DistanceTravelledByGolfCart = 0; + DistanceTravelledByHelicoptor = 0; +#ifdef FIX_BUGS + DistanceTravelledByPlane = 0; +#endif + LivesSavedWithAmbulance = 0; + CriminalsCaught = 0; + HighestLevelVigilanteMission = 0; + HighestLevelAmbulanceMission = 0; + HighestLevelFireMission = 0; + FiresExtinguished = 0; + PhotosTaken = 0; + NumberKillFrenziesPassed = 0; + TotalNumberKillFrenzies = 0; + TotalNumberMissions = 0; + FlightTime = 0; + TimesDrowned = 0; + SeagullsKilled = 0; + WeaponBudget = 0.0f; + FashionBudget = 0.0f; + LoanSharks = 0.0f; + StoresKnockedOff = 0.0f; + MovieStunts = 0.0f; + Assassinations = 0.0f; + PizzasDelivered = 0.0f; + GarbagePickups = 0.0f; + IceCreamSold = 0.0f; + TopShootingRangeScore = 0.0f; + ShootingRank = 0.0f; + LongestWheelie = 0; + LongestStoppie = 0; + Longest2Wheel = 0; + LongestWheelieDist = 0.0f; + LongestStoppieDist = 0.0f; + Longest2WheelDist = 0.0f; + PropertyBudget = 0.0f; + AutoPaintingBudget = 0.0f; + PropertyDestroyed = 0; + HighestChaseValue = 0.0f; + CheatedCount = 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 < TOTAL_BEST_POSITIONS; i++) + BestPositions[i] = INT_MAX; + + KillsSinceLastCheckpoint = 0; + TotalLegitimateKills = 0; + + for (int i = 0; i < ARRAY_SIZE(LastMissionPassedName); i++) + LastMissionPassedName[i] = 0; + + IndustrialPassed = 0; + CommercialPassed = 0; + SuburbanPassed = 0; + PamphletMissionPassed = 0; + NoMoreHurricanes = 0; + ShowChaseStatOnScreen = 0; + for (int i = 0; i < ARRAY_SIZE(abSonyCDs); i++) + abSonyCDs[i] = 0; + PopulateFavoriteRadioStationList(); + + NumPropertyOwned = 0; + for (int i = 0; i < TOTAL_PROPERTIES; i++) + PropertyOwned[i] = false; + + BloodRingKills = 0; + BloodRingTime = 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::RegisterBestPosition(int32 index, int32 position) +{ + assert(index >= 0 && index < TOTAL_BEST_POSITIONS); + BestPositions[index] = Min(BestPositions[index], position); +} + +void CStats::AnotherLifeSavedWithAmbulance() +{ + ++LivesSavedWithAmbulance; +} + +void CStats::AnotherCriminalCaught() +{ + ++CriminalsCaught; +} + +void CStats::RegisterLevelAmbulanceMission(int32 level) +{ + HighestLevelAmbulanceMission = Max(HighestLevelAmbulanceMission, level); +} + +void CStats::RegisterLevelVigilanteMission(int32 level) +{ + HighestLevelVigilanteMission = Max(HighestLevelVigilanteMission, level); +} + +void CStats::RegisterLevelFireMission(int32 level) +{ + HighestLevelFireMission = Max(HighestLevelFireMission, level); +} + +void CStats::AnotherFireExtinguished() +{ + ++FiresExtinguished; +} + +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 < 0) { + if (rating > -500) return TheText.Get("RATNG53"); + if (rating > -2000) return TheText.Get("RATNG54"); + if (rating > -4000) return TheText.Get("RATNG55"); + if (rating > -6000) return TheText.Get("RATNG56"); + return TheText.Get("RATNG57"); + } + if (rating < 20) return TheText.Get("RATNG1"); + if (rating < 50) return TheText.Get("RATNG2"); + if (rating < 75) return TheText.Get("RATNG3"); + if (rating < 100) return TheText.Get("RATNG4"); + if (rating < 120) return TheText.Get("RATNG5"); + if (rating < 150) return TheText.Get("RATNG6"); + if (rating < 200) return TheText.Get("RATNG7"); + if (rating < 240) return TheText.Get("RATNG8"); + if (rating < 270) return TheText.Get("RATNG9"); + if (rating < 300) return TheText.Get("RATNG10"); + if (rating < 335) return TheText.Get("RATNG11"); + if (rating < 370) return TheText.Get("RATNG12"); + if (rating < 400) return TheText.Get("RATNG13"); + if (rating < 450) return TheText.Get("RATNG14"); + if (rating < 500) return TheText.Get("RATNG15"); + if (rating < 550) return TheText.Get("RATNG16"); + if (rating < 600) return TheText.Get("RATNG17"); + if (rating < 610) return TheText.Get("RATNG18"); + if (rating < 650) return TheText.Get("RATNG19"); + if (rating < 700) return TheText.Get("RATNG20"); + if (rating < 850) return TheText.Get("RATNG21"); + if (rating < 1000) return TheText.Get("RATNG22"); + if (rating < 1005) return TheText.Get("RATNG23"); + if (rating < 1150) return TheText.Get("RATNG24"); + if (rating < 1300) return TheText.Get(TimesArrested > 0 ? "RATNG25" : "RATNG24"); + if (rating < 1500) return TheText.Get("RATNG26"); + if (rating < 1700) return TheText.Get("RATNG27"); + if (rating < 2000) return TheText.Get("RATNG28"); + if (rating < 2100) return TheText.Get("RATNG29"); + if (rating < 2300) return TheText.Get("RATNG30"); + if (rating < 2500) return TheText.Get("RATNG31"); + if (rating < 2750) return TheText.Get("RATNG32"); + if (rating < 3000) return TheText.Get("RATNG33"); + if (rating < 3500) return TheText.Get("RATNG34"); + if (rating < 4000) return TheText.Get("RATNG35"); + if (rating < 5000) return TheText.Get("RATNG36"); + if (rating < 7500) return TheText.Get("RATNG37"); + if (rating < 10000) return TheText.Get("RATNG38"); + if (rating < 20000) return TheText.Get("RATNG39"); + if (rating < 30000) return TheText.Get("RATNG40"); + if (rating < 40000) return TheText.Get("RATNG41"); + if (rating < 50000) return TheText.Get("RATNG42"); + if (rating < 65000) return TheText.Get("RATNG43"); + if (rating < 80000) return TheText.Get("RATNG44"); + if (rating < 100000) return TheText.Get("RATNG45"); + if (rating < 150000) return TheText.Get("RATNG46"); + if (rating < 200000) return TheText.Get("RATNG47"); + if (rating < 300000) return TheText.Get("RATNG48"); + if (rating < 375000) return TheText.Get("RATNG49"); + if (rating < 500000) return TheText.Get(FlightTime / 60000 / 60 > 10 ? "RATNG50" : "RATNG49"); + if (rating < 1000000) return TheText.Get("RATNG51"); + return TheText.Get(CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney > 10000000 ? "RATNG52" : "RATNG51"); +} + +wchar *CStats::FindChaseString(float fMediaLevel) { + if (fMediaLevel < 20.0f) return TheText.Get("CHASE1"); + if (fMediaLevel < 50.0f) return TheText.Get("CHASE2"); + if (fMediaLevel < 75.0f) return TheText.Get("CHASE3"); + if (fMediaLevel < 100.0f) return TheText.Get("CHASE4"); + if (fMediaLevel < 150.0f) return TheText.Get("CHASE5"); + if (fMediaLevel < 200.0f) return TheText.Get("CHASE6"); + if (fMediaLevel < 250.0f) return TheText.Get("CHASE7"); + if (fMediaLevel < 300.0f) return TheText.Get("CHASE8"); + if (fMediaLevel < 350.0f) return TheText.Get("CHASE9"); + if (fMediaLevel < 400.0f) return TheText.Get("CHASE10"); + if (fMediaLevel < 500.0f) return TheText.Get("CHASE11"); + if (fMediaLevel < 600.0f) return TheText.Get("CHASE12"); + if (fMediaLevel < 700.0f) return TheText.Get("CHASE13"); + if (fMediaLevel < 800.0f) return TheText.Get("CHASE14"); + if (fMediaLevel < 900.0f) return TheText.Get("CHASE15"); + if (fMediaLevel < 1000.0f) return TheText.Get("CHASE16"); + if (fMediaLevel < 1200.0f) return TheText.Get("CHASE17"); + if (fMediaLevel < 1400.0f) return TheText.Get("CHASE18"); + if (fMediaLevel < 1600.0f) return TheText.Get("CHASE19"); + if (fMediaLevel < 1800.0f) return TheText.Get("CHASE20"); + return TheText.Get("CHASE21"); +} + +int32 CStats::FindCriminalRatingNumber() +{ + int32 rating; + + rating = FiresExtinguished + 10 * HighestLevelFireMission + 10 * HighestLevelAmbulanceMission + + CriminalsCaught + LivesSavedWithAmbulance + + 30 * HelisDestroyed + TotalLegitimateKills - 3 * TimesArrested - 3 * TimesDied + + CWorld::Players[CWorld::PlayerInFocus].m_nMoney / 5000; + if (CPad::bHasPlayerCheated || CheatedCount > 0) { + rating -= CheatedCount; + if (rating <= -10000) + rating = -10000; + + } else if (rating <= 0) { + rating = 0; + } + + if (RoundsFiredByPlayer > 100) + rating += (float)BulletsThatHit / (float)RoundsFiredByPlayer * 500.0f; + if (TotalProgressInGame) + rating += ProgressMade / TotalProgressInGame * 1000.0f; + return rating; +} + +float CStats::GetPercentageProgress() +{ + float percentCompleted = (TotalProgressInGame == 0.f ? 0.f : + 100.0f * ProgressMade / (CGame::nastyGame ? TotalProgressInGame : TotalProgressInGame - 1.0f)); + + return Min(percentCompleted, 100.0f); +} + +void CStats::MoneySpentOnWeapons(int32 money) +{ + WeaponBudget += money; +} + +void CStats::MoneySpentOnProperty(int32 money) +{ + PropertyBudget += money; +} + +void CStats::MoneySpentOnAutoPainting(int32 money) +{ + AutoPaintingBudget += money; +} + +void CStats::MoneySpentOnFashion(int32 money) +{ + FashionBudget += money; +} + +void CStats::NumOfVisitsFromLoanSharks(int32 num) +{ + LoanSharks += num; +} + +void CStats::NumOfStoresKnockedOff(int32 num) +{ + StoresKnockedOff += num; +} + +void CStats::NumOfMovieStunts(int32 num) +{ + MovieStunts += num; +} + +void CStats::NumOfAssassinations(int32 num) +{ + Assassinations += num; +} + +void CStats::NumOfPizzasDelivered(int32 num) +{ + PizzasDelivered += num; +} + +void CStats::NumOfGarbagePickups(int32 num) +{ + GarbagePickups += num; +} + +void CStats::NumOfIceCreamSold(int32 num) +{ + IceCreamSold += num; +} + +void CStats::AddNumBloodRingKills(int32 num) +{ + BloodRingKills += num; +} + +void CStats::LongestTimeInBloodRing(int32 time) +{ + if (BloodRingTime < time) + BloodRingTime = time; +} + +void CStats::AddPropertyAsOwned(int32 id) +{ + if (!PropertyOwned[id]) { + PropertyOwned[id] = true; + ++NumPropertyOwned; + } +} + +float CStats::GetFavoriteRadioStationList(int32 station) +{ + return FavoriteRadioStationList[station]; +} + +void CStats::SaveStats(uint8 *buf, uint32 *size) +{ + CheckPointReachedSuccessfully(); + uint8 *buf_start = buf; + *size = sizeof(PeopleKilledByPlayer) + + sizeof(PeopleKilledByOthers) + + sizeof(CarsExploded) + + sizeof(BoatsExploded) + + sizeof(TyresPopped) + + sizeof(RoundsFiredByPlayer) + + sizeof(PedsKilledOfThisType) + + sizeof(HelisDestroyed) + + sizeof(ProgressMade) + + sizeof(TotalProgressInGame) + + sizeof(KgsOfExplosivesUsed) + + sizeof(BulletsThatHit) + + sizeof(HeadsPopped) + + sizeof(WantedStarsAttained) + + sizeof(WantedStarsEvaded) + + sizeof(TimesArrested) + + sizeof(TimesDied) + + sizeof(DaysPassed) + + sizeof(SafeHouseVisits) + + sizeof(Sprayings) + + sizeof(MaximumJumpDistance) + + sizeof(MaximumJumpHeight) + + sizeof(MaximumJumpFlips) + + sizeof(MaximumJumpSpins) + + sizeof(BestStuntJump) + + sizeof(NumberOfUniqueJumpsFound) + + sizeof(TotalNumberOfUniqueJumps) + + sizeof(MissionsGiven) + + sizeof(PassengersDroppedOffWithTaxi) + + sizeof(MoneyMadeWithTaxi) + + sizeof(IndustrialPassed) + + sizeof(CommercialPassed) + + sizeof(SuburbanPassed) + + sizeof(PamphletMissionPassed) + + sizeof(NoMoreHurricanes) + + sizeof(DistanceTravelledOnFoot) + + sizeof(DistanceTravelledByCar) + + sizeof(DistanceTravelledByBike) + + sizeof(DistanceTravelledByBoat) + + sizeof(DistanceTravelledByGolfCart) + + sizeof(DistanceTravelledByHelicoptor) + + sizeof(DistanceTravelledByPlane) + + sizeof(LivesSavedWithAmbulance) + + sizeof(CriminalsCaught) + + sizeof(FiresExtinguished) + + sizeof(HighestLevelVigilanteMission) + + sizeof(HighestLevelAmbulanceMission) + + sizeof(HighestLevelFireMission) + + sizeof(PhotosTaken) + + sizeof(NumberKillFrenziesPassed) + + sizeof(TotalNumberKillFrenzies) + + sizeof(TotalNumberMissions) + + sizeof(FlightTime) + + sizeof(TimesDrowned) + + sizeof(SeagullsKilled) + + sizeof(WeaponBudget) + + sizeof(FashionBudget) + + sizeof(LoanSharks) + + sizeof(StoresKnockedOff) + + sizeof(MovieStunts) + + sizeof(Assassinations) + + sizeof(PizzasDelivered) + + sizeof(GarbagePickups) + + sizeof(IceCreamSold) + + sizeof(TopShootingRangeScore) + + sizeof(ShootingRank) + + sizeof(LongestWheelie) + + sizeof(LongestStoppie) + + sizeof(Longest2Wheel) + + sizeof(LongestWheelieDist) + + sizeof(LongestStoppieDist) + + sizeof(Longest2WheelDist) + + sizeof(PropertyBudget) + + sizeof(AutoPaintingBudget) + + sizeof(PropertyDestroyed) + + sizeof(NumPropertyOwned) + + sizeof(BloodRingKills) + + sizeof(BloodRingTime) + + sizeof(PropertyOwned) + + sizeof(HighestChaseValue) + + sizeof(FastestTimes) + + sizeof(HighestScores) + + sizeof(BestPositions) + + sizeof(KillsSinceLastCheckpoint) + + sizeof(TotalLegitimateKills) + + sizeof(LastMissionPassedName) + + sizeof(CheatedCount) + + sizeof(FavoriteRadioStationList); + +#define CopyToBuf(buf, data) memcpy(buf, &data, sizeof(data)); buf += sizeof(data); + CopyToBuf(buf, PeopleKilledByPlayer); + CopyToBuf(buf, PeopleKilledByOthers); + CopyToBuf(buf, CarsExploded); + CopyToBuf(buf, BoatsExploded); + CopyToBuf(buf, TyresPopped); + CopyToBuf(buf, RoundsFiredByPlayer); + CopyToBuf(buf, PedsKilledOfThisType); + CopyToBuf(buf, HelisDestroyed); + CopyToBuf(buf, ProgressMade); + CopyToBuf(buf, TotalProgressInGame); + CopyToBuf(buf, KgsOfExplosivesUsed); + CopyToBuf(buf, BulletsThatHit); + CopyToBuf(buf, HeadsPopped); + CopyToBuf(buf, WantedStarsAttained); + CopyToBuf(buf, WantedStarsEvaded); + CopyToBuf(buf, TimesArrested); + CopyToBuf(buf, TimesDied); + CopyToBuf(buf, DaysPassed); + CopyToBuf(buf, SafeHouseVisits); + CopyToBuf(buf, Sprayings); + 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, PassengersDroppedOffWithTaxi); + CopyToBuf(buf, MoneyMadeWithTaxi); + CopyToBuf(buf, IndustrialPassed); + CopyToBuf(buf, CommercialPassed); + CopyToBuf(buf, SuburbanPassed); + CopyToBuf(buf, PamphletMissionPassed); + CopyToBuf(buf, NoMoreHurricanes); + CopyToBuf(buf, DistanceTravelledOnFoot); + CopyToBuf(buf, DistanceTravelledByCar); + CopyToBuf(buf, DistanceTravelledByBike); + CopyToBuf(buf, DistanceTravelledByBoat); + CopyToBuf(buf, DistanceTravelledByGolfCart); + CopyToBuf(buf, DistanceTravelledByHelicoptor); + CopyToBuf(buf, DistanceTravelledByPlane); + CopyToBuf(buf, LivesSavedWithAmbulance); + CopyToBuf(buf, CriminalsCaught); + CopyToBuf(buf, FiresExtinguished); + CopyToBuf(buf, HighestLevelVigilanteMission); + CopyToBuf(buf, HighestLevelAmbulanceMission); + CopyToBuf(buf, HighestLevelFireMission); + CopyToBuf(buf, PhotosTaken); + CopyToBuf(buf, NumberKillFrenziesPassed); + CopyToBuf(buf, TotalNumberKillFrenzies); + CopyToBuf(buf, TotalNumberMissions); + CopyToBuf(buf, FlightTime); + CopyToBuf(buf, TimesDrowned); + CopyToBuf(buf, SeagullsKilled); + CopyToBuf(buf, WeaponBudget); + CopyToBuf(buf, FashionBudget); + CopyToBuf(buf, LoanSharks); + CopyToBuf(buf, StoresKnockedOff); + CopyToBuf(buf, MovieStunts); + CopyToBuf(buf, Assassinations); + CopyToBuf(buf, PizzasDelivered); + CopyToBuf(buf, GarbagePickups); + CopyToBuf(buf, IceCreamSold); + CopyToBuf(buf, TopShootingRangeScore); + CopyToBuf(buf, ShootingRank); + CopyToBuf(buf, LongestWheelie); + CopyToBuf(buf, LongestStoppie); + CopyToBuf(buf, Longest2Wheel); + CopyToBuf(buf, LongestWheelieDist); + CopyToBuf(buf, LongestStoppieDist); + CopyToBuf(buf, Longest2WheelDist); + CopyToBuf(buf, PropertyBudget); + CopyToBuf(buf, AutoPaintingBudget); + CopyToBuf(buf, PropertyDestroyed); + CopyToBuf(buf, NumPropertyOwned); + CopyToBuf(buf, BloodRingKills); + CopyToBuf(buf, BloodRingTime); + CopyToBuf(buf, PropertyOwned); + CopyToBuf(buf, HighestChaseValue); + CopyToBuf(buf, FastestTimes); + CopyToBuf(buf, HighestScores); + CopyToBuf(buf, BestPositions); + CopyToBuf(buf, KillsSinceLastCheckpoint); + CopyToBuf(buf, TotalLegitimateKills); + CopyToBuf(buf, LastMissionPassedName); + CopyToBuf(buf, CheatedCount); + PopulateFavoriteRadioStationList(); + CopyToBuf(buf, FavoriteRadioStationList); + + 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, BoatsExploded); + CopyFromBuf(buf, TyresPopped); + CopyFromBuf(buf, RoundsFiredByPlayer); + CopyFromBuf(buf, PedsKilledOfThisType); + CopyFromBuf(buf, HelisDestroyed); + CopyFromBuf(buf, ProgressMade); + CopyFromBuf(buf, TotalProgressInGame); + CopyFromBuf(buf, KgsOfExplosivesUsed); + CopyFromBuf(buf, BulletsThatHit); + CopyFromBuf(buf, HeadsPopped); + CopyFromBuf(buf, WantedStarsAttained); + CopyFromBuf(buf, WantedStarsEvaded); + CopyFromBuf(buf, TimesArrested); + CopyFromBuf(buf, TimesDied); + CopyFromBuf(buf, DaysPassed); + CopyFromBuf(buf, SafeHouseVisits); + CopyFromBuf(buf, Sprayings); + 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, PassengersDroppedOffWithTaxi); + CopyFromBuf(buf, MoneyMadeWithTaxi); + CopyFromBuf(buf, IndustrialPassed); + CopyFromBuf(buf, CommercialPassed); + CopyFromBuf(buf, SuburbanPassed); + CopyFromBuf(buf, PamphletMissionPassed); + CopyFromBuf(buf, NoMoreHurricanes); + CopyFromBuf(buf, DistanceTravelledOnFoot); + CopyFromBuf(buf, DistanceTravelledByCar); + CopyFromBuf(buf, DistanceTravelledByBike); + CopyFromBuf(buf, DistanceTravelledByBoat); + CopyFromBuf(buf, DistanceTravelledByGolfCart); + CopyFromBuf(buf, DistanceTravelledByHelicoptor); + CopyFromBuf(buf, DistanceTravelledByPlane); + CopyFromBuf(buf, LivesSavedWithAmbulance); + CopyFromBuf(buf, CriminalsCaught); + CopyFromBuf(buf, FiresExtinguished); + CopyFromBuf(buf, HighestLevelVigilanteMission); + CopyFromBuf(buf, HighestLevelAmbulanceMission); + CopyFromBuf(buf, HighestLevelFireMission); + CopyFromBuf(buf, PhotosTaken); + CopyFromBuf(buf, NumberKillFrenziesPassed); + CopyFromBuf(buf, TotalNumberKillFrenzies); + CopyFromBuf(buf, TotalNumberMissions); + CopyFromBuf(buf, FlightTime); + CopyFromBuf(buf, TimesDrowned); + CopyFromBuf(buf, SeagullsKilled); + CopyFromBuf(buf, WeaponBudget); + CopyFromBuf(buf, FashionBudget); + CopyFromBuf(buf, LoanSharks); + CopyFromBuf(buf, StoresKnockedOff); + CopyFromBuf(buf, MovieStunts); + CopyFromBuf(buf, Assassinations); + CopyFromBuf(buf, PizzasDelivered); + CopyFromBuf(buf, GarbagePickups); + CopyFromBuf(buf, IceCreamSold); + CopyFromBuf(buf, TopShootingRangeScore); + CopyFromBuf(buf, ShootingRank); + CopyFromBuf(buf, LongestWheelie); + CopyFromBuf(buf, LongestStoppie); + CopyFromBuf(buf, Longest2Wheel); + CopyFromBuf(buf, LongestWheelieDist); + CopyFromBuf(buf, LongestStoppieDist); + CopyFromBuf(buf, Longest2WheelDist); + CopyFromBuf(buf, PropertyBudget); + CopyFromBuf(buf, AutoPaintingBudget); + CopyFromBuf(buf, PropertyDestroyed); + CopyFromBuf(buf, NumPropertyOwned); + CopyFromBuf(buf, BloodRingKills); + CopyFromBuf(buf, BloodRingTime); + CopyFromBuf(buf, PropertyOwned); + CopyFromBuf(buf, HighestChaseValue); + CopyFromBuf(buf, FastestTimes); + CopyFromBuf(buf, HighestScores); + CopyFromBuf(buf, BestPositions); + CopyFromBuf(buf, KillsSinceLastCheckpoint); + CopyFromBuf(buf, TotalLegitimateKills); + CopyFromBuf(buf, LastMissionPassedName); + CopyFromBuf(buf, CheatedCount); + CopyFromBuf(buf, FavoriteRadioStationList); + + assert(buf - buf_start == size); +#undef CopyFromBuf +} + +void +CStats::PopulateFavoriteRadioStationList() +{ + float* pListenTimeArray = DMAudio.GetListenTimeArray(); + for (int i = 0; i < NUM_RADIOS; i++) + FavoriteRadioStationList[i] = pListenTimeArray[i]; +} + +void +CStats::BuildStatLine(Const char *text, void *stat, int displayType, void *stat2, int isTime) +{ +#define STAT_D *(int*)stat +#define STAT_F *(float*)stat +#define STAT2_D *(int*)stat2 +#define STAT2_F *(float*)stat2 + if (!text) + return; + + gString2[0] = '\0'; + if (isTime == 1) { + if (*((int*)stat2) >= 10) + sprintf(gString2, " %d:%d", STAT_D, STAT2_D); + else + sprintf(gString2, " %d:0%d", STAT_D, STAT2_D); + + } else if (stat2) { +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) { + switch (displayType) { + case 0: + case 4: + sprintf(gString2, " %d/%d", STAT_D, STAT2_D); + break; + case 1: + sprintf(gString2, " %.2f/%.2f", STAT_F, STAT2_F); + break; + case 2: + sprintf(gString2, " %d%%/%d%%", STAT_D, STAT2_D); + break; + case 3: + sprintf(gString2, " $%.2f/$%.2f", STAT_F, STAT2_F); + break; + default: + break; + } + } else +#endif + { + switch (displayType) { + case 0: + sprintf(gString2, " %d %s %d", STAT_D, UnicodeToAscii(TheText.Get("FEST_OO")), STAT2_D); + break; + case 1: + sprintf(gString2, " %.2f %s %.2f", STAT_F, UnicodeToAscii(TheText.Get("FEST_OO")), STAT2_F); + break; + case 2: + sprintf(gString2, " %d%% %s %d%%", STAT_D, UnicodeToAscii(TheText.Get("FEST_OO")), STAT2_D); + break; + case 3: + sprintf(gString2, " $%.2f %s $%.2f", STAT_F, UnicodeToAscii(TheText.Get("FEST_OO")), STAT2_F); + break; + case 4: + sprintf(gString2, " %d_ %s %d_", STAT_D, UnicodeToAscii(TheText.Get("FEST_OO")), STAT2_D); + break; + default: + break; + } + } + } else if (stat) { + switch (displayType) { + case 0: + sprintf(gString2, "%d", STAT_D); + break; + case 1: + sprintf(gString2, "%.2f", STAT_F); + break; + case 2: + sprintf(gString2, "%d%%", STAT_D); + break; + case 3: + sprintf(gString2, "$%.2f", STAT_F); + break; + case 4: +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + sprintf(gString2, "%d", STAT_D); + else +#endif + sprintf(gString2, "%d_", STAT_D); + break; + default: + break; + } + } + UnicodeStrcpy(gUString, TheText.Get(text)); + CFont::FilterOutTokensFromString(gUString); + AsciiToUnicode(gString2, gUString2); +#undef STAT_D +#undef STAT_F +#undef STAT2_D +#undef STAT2_F +} + +// rowIdx 99999 returns total numbers of rows. otherwise it returns 0. +int +CStats::ConstructStatLine(int rowIdx) +{ + +#define STAT_LINE_1(varType, left, right1, type) \ + do { \ + if(counter == rowIdx){ \ + varType a = right1; \ + BuildStatLine(left, &a, type, nil, 0); \ + return 0; \ + } counter++; \ + } while(0) + +#define STAT_LINE_2(varType, left, right1, type, right2, time) \ + do { \ + if(counter == rowIdx){ \ + varType a = right1; \ + varType b = right2; \ + BuildStatLine(left, &a, type, &b, time); \ + return 0; \ + } counter++; \ + } while(0) + +#define TEXT_ON_LEFT_GXT(name) \ + do { \ + if(counter == rowIdx){ \ + BuildStatLine(name, nil, 0, nil, 0); \ + 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) + +#define FASTEST_TIME(id, str) \ + do { \ + if(FastestTimes[id]) { \ + if(counter == rowIdx){ \ + int hour = 0, minute; \ + for (int i = FastestTimes[id]; i > 59; i -= 60) hour++; \ + for (minute = FastestTimes[id]; minute > 59; minute -= 60); \ + if (minute < 0) minute = -minute; \ + BuildStatLine(str, &hour, 0, &minute, 1); \ + return 0; \ + } \ + counter++; \ + } \ + } while(0) + + switch (rowIdx) { + case 0: { + int percentCompleted = GetPercentageProgress(); + BuildStatLine("PER_COM", &percentCompleted, 2, nil, 0); + return 0; + } + case 1: { + BuildStatLine("NMISON", &MissionsGiven, 0, nil, 0); + return 0; + } + case 2: { + int hour = (CTimer::GetTimeInMilliseconds() / 60000) / 60; + int minute = (CTimer::GetTimeInMilliseconds() / 60000) % 60; + BuildStatLine("ST_TIME", &hour, 0, &minute, 1); + return 0; + } + case 3: { + BuildStatLine("DAYSPS", &DaysPassed, 0, nil, 0); + return 0; + } + case 4: { + BuildStatLine("NUMSHV", &SafeHouseVisits, 0, nil, 0); + return 0; + } + } + int counter = 5; + + if (CGame::nastyGame) { + STAT_LINE_2(int, "FEST_RP", NumberKillFrenziesPassed, 0, TotalNumberKillFrenzies, 0); + } + + 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, 0, player.m_nTotalPackages, 0); +#else + float fPackagesPercent = 0.0f; + if (player.m_nTotalPackages != 0) + fPackagesPercent = player.m_nCollectedPackages * 100.0f / player.m_nTotalPackages; + + STAT_LINE_2(int, "PERPIC", fPackagesPercent, 0, 100, 0); +#endif + + if (CGame::nastyGame) { + STAT_LINE_1(int, "PE_WAST", PeopleKilledByPlayer, 0); + STAT_LINE_1(int, "PE_WSOT", PeopleKilledByOthers, 0); + } + STAT_LINE_1(int, "CAR_EXP", CarsExploded, 0); + STAT_LINE_1(int, "BOA_EXP", BoatsExploded, 0); + STAT_LINE_1(int, "HEL_DST", HelisDestroyed, 0); + STAT_LINE_1(int, "TYREPOP", TyresPopped, 0); + STAT_LINE_1(int, "ST_STAR", WantedStarsAttained, 0); + STAT_LINE_1(int, "ST_STGN", WantedStarsEvaded, 0); + STAT_LINE_1(int, "TM_BUST", TimesArrested, 0); + STAT_LINE_1(int, "TM_DED", TimesDied, 0); + +#ifdef MORE_LANGUAGES + // JP version removed it altogether actually + if (!CFont::IsJapanese()) +#endif + STAT_LINE_1(int, "ST_HEAD", HeadsPopped, 0); + + static uint32 lastProcessedDay = UINT32_MAX; + static uint32 lastPoliceSpending = 0; + + // What a random stat... + if (lastProcessedDay != DaysPassed) { + lastProcessedDay = DaysPassed; + lastPoliceSpending = (CTimer::GetTimeInMilliseconds() & 255 + 80) * 255.44f; + } + STAT_LINE_1(float, "DAYPLC", lastPoliceSpending, 3); + + int mostPatheticGang = 0; + int mostKill = 0; + for (int i = PEDTYPE_GANG1; i < PEDTYPE_GANG9; ++i) { + if (CStats::PedsKilledOfThisType[i] > mostKill) { + mostKill = CStats::PedsKilledOfThisType[i]; + mostPatheticGang = i; + } + } + if (mostPatheticGang > 0) { + TEXT_ON_LEFT_GXT("ST_GANG"); + + switch (mostPatheticGang) { + case PEDTYPE_GANG1: + TEXT_ON_RIGHT(TheText.Get("ST_GNG1")); + break; + case PEDTYPE_GANG2: + TEXT_ON_RIGHT(TheText.Get("ST_GNG2")); + break; + case PEDTYPE_GANG3: + TEXT_ON_RIGHT(TheText.Get("ST_GNG3")); + break; + case PEDTYPE_GANG4: + TEXT_ON_RIGHT(TheText.Get("ST_GNG4")); + break; + case PEDTYPE_GANG5: + TEXT_ON_RIGHT(TheText.Get("ST_GNG5")); + break; + case PEDTYPE_GANG6: + TEXT_ON_RIGHT(TheText.Get("ST_GNG6")); + break; + case PEDTYPE_GANG7: + TEXT_ON_RIGHT(TheText.Get("ST_GNG7")); + break; + case PEDTYPE_GANG8: + TEXT_ON_RIGHT(TheText.Get("ST_GNG8")); + break; + default: + break; + } + } + + STAT_LINE_1(int, "GNG_WST", PedsKilledOfThisType[PEDTYPE_GANG9] + PedsKilledOfThisType[PEDTYPE_GANG8] + + PedsKilledOfThisType[PEDTYPE_GANG7] + PedsKilledOfThisType[PEDTYPE_GANG6] + + PedsKilledOfThisType[PEDTYPE_GANG5] + PedsKilledOfThisType[PEDTYPE_GANG4] + + PedsKilledOfThisType[PEDTYPE_GANG3] + PedsKilledOfThisType[PEDTYPE_GANG2] + + PedsKilledOfThisType[PEDTYPE_GANG1], 0); + + STAT_LINE_1(int, "DED_CRI", PedsKilledOfThisType[PEDTYPE_CRIMINAL], 0); + STAT_LINE_1(int, "KGS_EXP", KgsOfExplosivesUsed, 0); + STAT_LINE_1(int, "BUL_FIR", RoundsFiredByPlayer, 0); + STAT_LINE_1(int, "BUL_HIT", BulletsThatHit, 0); +; + STAT_LINE_1(int, "ACCURA", RoundsFiredByPlayer == 0 ? 0 : (BulletsThatHit * 100.0f / (float)RoundsFiredByPlayer), 2); + + switch (FrontEndMenuManager.m_PrefsLanguage) { + case CMenuManager::LANGUAGE_AMERICAN: +#ifndef USE_MEASUREMENTS_IN_METERS + STAT_LINE_1(float, "FEST_DF", DistanceTravelledOnFoot * MILES_IN_METER, 1); + STAT_LINE_1(float, "FEST_DC", DistanceTravelledByCar * MILES_IN_METER, 1); + STAT_LINE_1(float, "DISTBIK", DistanceTravelledByBike * MILES_IN_METER, 1); + STAT_LINE_1(float, "DISTBOA", DistanceTravelledByBoat * MILES_IN_METER, 1); + STAT_LINE_1(float, "DISTGOL", DistanceTravelledByGolfCart * MILES_IN_METER, 1); + STAT_LINE_1(float, "DISTHEL", DistanceTravelledByHelicoptor * MILES_IN_METER, 1); +#ifdef FIX_BUGS + STAT_LINE_1(float, "TOT_DIS", (DistanceTravelledOnFoot + DistanceTravelledByCar + DistanceTravelledByBoat + DistanceTravelledByBike + + DistanceTravelledByGolfCart + DistanceTravelledByHelicoptor + DistanceTravelledByPlane) * MILES_IN_METER, 1); + STAT_LINE_1(float, "MXCARD", MaximumJumpDistance * FEET_IN_METER, 1); + STAT_LINE_1(float, "MXCARJ", MaximumJumpHeight * FEET_IN_METER, 1); +#else + STAT_LINE_1(float, "TOT_DIS", (DistanceTravelledOnFoot + DistanceTravelledByCar + DistanceTravelledByBoat + DistanceTravelledByBike + + DistanceTravelledByGolfCart + DistanceTravelledByHelicoptor) * MILES_IN_METER, 1); +#endif + break; +#endif + case CMenuManager::LANGUAGE_FRENCH: + case CMenuManager::LANGUAGE_GERMAN: + case CMenuManager::LANGUAGE_ITALIAN: + case CMenuManager::LANGUAGE_SPANISH: +#ifdef MORE_LANGUAGES + case CMenuManager::LANGUAGE_POLISH: + case CMenuManager::LANGUAGE_RUSSIAN: + case CMenuManager::LANGUAGE_JAPANESE: +#endif + STAT_LINE_1(float, "FESTDFM", DistanceTravelledOnFoot, 1); + STAT_LINE_1(float, "FESTDCM", DistanceTravelledByCar, 1); + STAT_LINE_1(float, "DISTBIM", DistanceTravelledByBike, 1); + STAT_LINE_1(float, "DISTBOM", DistanceTravelledByBoat, 1); + STAT_LINE_1(float, "DISTGOM", DistanceTravelledByGolfCart, 1); + STAT_LINE_1(float, "DISTHEM", DistanceTravelledByHelicoptor, 1); +#ifdef FIX_BUGS + STAT_LINE_1(float, "TOTDISM", DistanceTravelledOnFoot + DistanceTravelledByCar + DistanceTravelledByBoat + + DistanceTravelledByBike + DistanceTravelledByGolfCart + DistanceTravelledByHelicoptor + DistanceTravelledByPlane, 1); + STAT_LINE_1(float, "MXCARDM", MaximumJumpDistance, 1); + STAT_LINE_1(float, "MXCARJM", MaximumJumpHeight, 1); +#else + STAT_LINE_1(float, "TOTDISM", DistanceTravelledOnFoot + DistanceTravelledByCar + DistanceTravelledByBoat + + DistanceTravelledByGolfCart + DistanceTravelledByHelicoptor, 1); +#endif + break; + default: + break; + } + + // They were selecting the unit according to language in III, but they deleted the feet code in VC. Weird +#ifndef FIX_BUGS + STAT_LINE_1(float, "MXCARDM", MaximumJumpDistance, 1); + STAT_LINE_1(float, "MXCARJM", MaximumJumpHeight, 1); +#endif + STAT_LINE_1(int, "MXFLIP", MaximumJumpFlips, 0); + STAT_LINE_2(int, "NOUNIF", NumberOfUniqueJumpsFound, 0, TotalNumberOfUniqueJumps, 0); + STAT_LINE_1(int, "MXJUMP", MaximumJumpSpins, 4); + + TEXT_ON_LEFT_GXT("BSTSTU"); + switch (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, "ST_WHEE", LongestWheelie, 0); + STAT_LINE_1(float, "ST_WHED", LongestWheelieDist, 1); + STAT_LINE_1(int, "ST_STOP", LongestStoppie, 0); + STAT_LINE_1(float, "ST_STOD", LongestStoppieDist, 1); + STAT_LINE_1(int, "ST_2WHE", Longest2Wheel, 0); + STAT_LINE_1(float, "ST_2WHD", Longest2WheelDist, 1); + + if (LoanSharks > 0.0f) + STAT_LINE_1(int, "ST_LOAN", LoanSharks, 0); + + STAT_LINE_1(int, "FEST_CC", CriminalsCaught, 0); + STAT_LINE_1(int, "FEST_HV", HighestLevelVigilanteMission, 0); + STAT_LINE_1(int, "PASDRO", PassengersDroppedOffWithTaxi, 0); + STAT_LINE_1(float, "MONTAX", MoneyMadeWithTaxi, 3); + STAT_LINE_1(int, "FEST_LS", LivesSavedWithAmbulance, 0); + STAT_LINE_1(int, "FEST_HA", HighestLevelAmbulanceMission, 0); + STAT_LINE_1(int, "FEST_FE", FiresExtinguished, 0); + STAT_LINE_1(int, "FIRELVL", HighestLevelFireMission, 0); + + STAT_LINE_2(int, "ST_STOR", StoresKnockedOff, 0, 15, 0); + + if (MovieStunts > 0.0f) + STAT_LINE_1(int, "ST_MOVI", MovieStunts, 0); + + STAT_LINE_2(int, "ST_ASSI", Assassinations, 0, 5, 0); + + if (PhotosTaken > 0) + STAT_LINE_1(int, "ST_PHOT", PhotosTaken, 0); + + if (PizzasDelivered > 0.0f) + STAT_LINE_1(int, "ST_PIZZ", PizzasDelivered, 0); + + if (GarbagePickups > 0.0f) + STAT_LINE_1(int, "ST_GARB", GarbagePickups, 0); + + if (IceCreamSold > 0.0f) + STAT_LINE_1(int, "ST_ICEC", IceCreamSold, 0); + + if (HighestScores[1]) + STAT_LINE_1(int, "STHC_02", HighestScores[1], 0); + + FASTEST_TIME(0, "STFT_01"); + FASTEST_TIME(1, "STFT_02"); + FASTEST_TIME(2, "STFT_03"); + FASTEST_TIME(3, "STFT_04"); + FASTEST_TIME(4, "STFT_05"); + FASTEST_TIME(5, "STFT_06"); + FASTEST_TIME(6, "STFT_07"); + FASTEST_TIME(7, "STFT_08"); + FASTEST_TIME(8, "STFT_09"); + FASTEST_TIME(9, "STFT_10"); + FASTEST_TIME(10, "STFT_11"); + FASTEST_TIME(11, "STFT_12"); + FASTEST_TIME(12, "STFT_13"); + FASTEST_TIME(13, "STFT_14"); + FASTEST_TIME(14, "STFT_15"); + FASTEST_TIME(15, "STFT_16"); + FASTEST_TIME(16, "STFT_17"); + FASTEST_TIME(17, "STFT_18"); + FASTEST_TIME(18, "STFT_19"); + FASTEST_TIME(19, "STFT_20"); + FASTEST_TIME(22, "STFT_23"); + + if (HighestScores[0]) + STAT_LINE_1(int, "STHC_01", HighestScores[0], 0); + + if (HighestScores[3]) + STAT_LINE_1(int, "STHC_04", HighestScores[3], 0); + + if (HighestScores[2]) + STAT_LINE_1(int, "STHC_03", HighestScores[2], 0); + + if (BestPositions[0] != INT_MAX) + STAT_LINE_1(int, "STHC_05", BestPositions[0], 0); + + FASTEST_TIME(20, "STFT_21"); + + if (FastestTimes[21]) +#ifdef FIX_BUGS + STAT_LINE_1(float, "STFT_22", Floor(FastestTimes[21] / 10) / 100, 1); +#else + STAT_LINE_1(float, "STFT_22", FastestTimes[21] / 1000, 1); +#endif + + if (TopShootingRangeScore > 0.0f) + STAT_LINE_1(int, "TOP_SHO", TopShootingRangeScore, 0); + + if (ShootingRank > 0.0f) + STAT_LINE_1(int, "SHO_RAN", ShootingRank, 0); + + int flightMinute = (FlightTime / 60000) % 60; + int flightHour = (FlightTime / 60000) / 60; + STAT_LINE_2(int, "ST_FTIM", flightHour, 0, flightMinute, 1); + + // We always have pilot rank if we flew more then 5 minutes +#ifndef FIX_BUGS + if (flightHour != 0) + TEXT_ON_LEFT_GXT("ST_PRAN"); +#endif + +#ifdef FIX_BUGS +#define FL_TIME_MORE_THAN(hour, minute) (flightHour > hour || (flightHour == hour && flightMinute >= minute)) +#else +#define FL_TIME_MORE_THAN(hour, minute) (flightHour > hour || flightMinute >= minute) +#endif + + if (FL_TIME_MORE_THAN(0,5)) { + +#ifdef FIX_BUGS + TEXT_ON_LEFT_GXT("ST_PRAN"); +#endif + if (!FL_TIME_MORE_THAN(0,10)) TEXT_ON_RIGHT(TheText.Get("ST_PR01")); + else if (!FL_TIME_MORE_THAN(0,20)) TEXT_ON_RIGHT(TheText.Get("ST_PR02")); + else if (!FL_TIME_MORE_THAN(0,30)) TEXT_ON_RIGHT(TheText.Get("ST_PR03")); + else if (!FL_TIME_MORE_THAN(1,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR04")); + else if (!FL_TIME_MORE_THAN(1,30)) TEXT_ON_RIGHT(TheText.Get("ST_PR05")); + else if (!FL_TIME_MORE_THAN(2,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR06")); + else if (!FL_TIME_MORE_THAN(2,30)) TEXT_ON_RIGHT(TheText.Get("ST_PR07")); + else if (!FL_TIME_MORE_THAN(3,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR08")); + else if (!FL_TIME_MORE_THAN(3,30)) TEXT_ON_RIGHT(TheText.Get("ST_PR09")); + else if (!FL_TIME_MORE_THAN(4,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR10")); + else if (!FL_TIME_MORE_THAN(5,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR11")); + else if (!FL_TIME_MORE_THAN(10,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR12")); + else if (!FL_TIME_MORE_THAN(20,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR13")); + else if (!FL_TIME_MORE_THAN(25,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR14")); + else if (!FL_TIME_MORE_THAN(30,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR15")); + else if (!FL_TIME_MORE_THAN(49,2)) TEXT_ON_RIGHT(TheText.Get("ST_PR16")); + else if (!FL_TIME_MORE_THAN(50,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR17")); + else if (!FL_TIME_MORE_THAN(100,0)) TEXT_ON_RIGHT(TheText.Get("ST_PR18")); + else TEXT_ON_RIGHT(TheText.Get("ST_PR19")); + } +#undef FL_TIME_MORE_THAN + + if (BloodRingKills > 0) + STAT_LINE_1(int, "ST_BRK", BloodRingKills, 0); + + if (BloodRingTime > 0) + STAT_LINE_1(int, "ST_LTBR", BloodRingTime, 0); + + STAT_LINE_1(int, "ST_DRWN", TimesDrowned, 0); + + if (SeagullsKilled > 0) + STAT_LINE_1(int, "SEAGULL", SeagullsKilled, 0); + + bool playerHatesRadio = true; + float* pListenTimeArray = DMAudio.GetListenTimeArray(); + for (int i = 0; i < NUM_RADIOS; i++) { + FavoriteRadioStationList[i] = pListenTimeArray[i]; + if (FavoriteRadioStationList[i] != 0.0) // double + playerHatesRadio = false; + } + + if (!playerHatesRadio) { + // Most listened + TEXT_ON_LEFT_GXT("FST_MFR"); + float mostListenTime = FavoriteRadioStationList[0]; + int mostListenedRadio = 0; + for (int i = 0; i < NUM_RADIOS; i++) { + if (FavoriteRadioStationList[i] > mostListenTime) { + mostListenTime = FavoriteRadioStationList[i]; + mostListenedRadio = i; + } + } + switch (mostListenedRadio) { + case WILDSTYLE: + TEXT_ON_RIGHT(TheText.Get("FEA_FM0")); + break; + case FLASH_FM: + TEXT_ON_RIGHT(TheText.Get("FEA_FM1")); + break; + case KCHAT: + TEXT_ON_RIGHT(TheText.Get("FEA_FM2")); + break; + case FEVER: + TEXT_ON_RIGHT(TheText.Get("FEA_FM3")); + break; + case V_ROCK: + TEXT_ON_RIGHT(TheText.Get("FEA_FM4")); + break; + case VCPR: + TEXT_ON_RIGHT(TheText.Get("FEA_FM5")); + break; + case RADIO_ESPANTOSO: + TEXT_ON_RIGHT(TheText.Get("FEA_FM6")); + break; + case EMOTION: + TEXT_ON_RIGHT(TheText.Get("FEA_FM7")); + break; + case WAVE: + TEXT_ON_RIGHT(TheText.Get("FEA_FM8")); + break; + case USERTRACK: + TEXT_ON_RIGHT(TheText.Get("FEA_MP3")); + break; + default: + TEXT_ON_RIGHT(TheText.Get("FEA_FM8")); // heh + break; + } + + // Least listened + TEXT_ON_LEFT_GXT("FST_LFR"); + float leastListenTime = FavoriteRadioStationList[0]; + int leastListenedRadio = 0; + for (int i = 0; i < NUM_RADIOS; i++) { +#ifdef FIX_BUGS + if (!DMAudio.IsMP3RadioChannelAvailable() && i == USERTRACK) + continue; +#endif + if (FavoriteRadioStationList[i] < leastListenTime) { + leastListenTime = FavoriteRadioStationList[i]; + leastListenedRadio = i; + } + } +#ifndef FIX_BUGS + if (!DMAudio.IsMP3RadioChannelAvailable() && leastListenedRadio == USERTRACK) + leastListenedRadio = WAVE; +#endif + + switch (leastListenedRadio) { + case WILDSTYLE: + TEXT_ON_RIGHT(TheText.Get("FEA_FM0")); + break; + case FLASH_FM: + TEXT_ON_RIGHT(TheText.Get("FEA_FM1")); + break; + case KCHAT: + TEXT_ON_RIGHT(TheText.Get("FEA_FM2")); + break; + case FEVER: + TEXT_ON_RIGHT(TheText.Get("FEA_FM3")); + break; + case V_ROCK: + TEXT_ON_RIGHT(TheText.Get("FEA_FM4")); + break; + case VCPR: + TEXT_ON_RIGHT(TheText.Get("FEA_FM5")); + break; + case RADIO_ESPANTOSO: + TEXT_ON_RIGHT(TheText.Get("FEA_FM6")); + break; + case EMOTION: + TEXT_ON_RIGHT(TheText.Get("FEA_FM7")); + break; + case WAVE: + TEXT_ON_RIGHT(TheText.Get("FEA_FM8")); + break; + case USERTRACK: + TEXT_ON_RIGHT(TheText.Get("FEA_MP3")); + break; + default: + TEXT_ON_RIGHT(TheText.Get("FEA_FM8")); // heh + break; + } + } + STAT_LINE_1(int, "SPRAYIN", Sprayings, 0); + STAT_LINE_1(float, "ST_WEAP", WeaponBudget, 3); + STAT_LINE_1(float, "ST_FASH", FashionBudget, 3); + STAT_LINE_1(float, "ST_PROP", PropertyBudget, 3); + STAT_LINE_1(float, "ST_AUTO", AutoPaintingBudget, 3); + STAT_LINE_1(float, "ST_DAMA", PropertyBudget, 3); + + if (NumPropertyOwned > 0) { + STAT_LINE_1(int, "PROPOWN", NumPropertyOwned, 0); + if (PropertyOwned[0]) TEXT_ON_RIGHT(TheText.Get("STPR_1")); + if (PropertyOwned[1]) TEXT_ON_RIGHT(TheText.Get("STPR_2")); + if (PropertyOwned[2]) TEXT_ON_RIGHT(TheText.Get("STPR_3")); + if (PropertyOwned[3]) TEXT_ON_RIGHT(TheText.Get("STPR_4")); + if (PropertyOwned[4]) TEXT_ON_RIGHT(TheText.Get("STPR_5")); + if (PropertyOwned[5]) TEXT_ON_RIGHT(TheText.Get("STPR_6")); + if (PropertyOwned[6]) TEXT_ON_RIGHT(TheText.Get("STPR_7")); + if (PropertyOwned[7]) TEXT_ON_RIGHT(TheText.Get("STPR_8")); + if (PropertyOwned[8]) TEXT_ON_RIGHT(TheText.Get("STPR_9")); + if (PropertyOwned[9]) TEXT_ON_RIGHT(TheText.Get("STPR_10")); + if (PropertyOwned[10]) TEXT_ON_RIGHT(TheText.Get("STPR_11")); + if (PropertyOwned[11]) TEXT_ON_RIGHT(TheText.Get("STPR_12")); + if (PropertyOwned[12]) TEXT_ON_RIGHT(TheText.Get("STPR_13")); + if (PropertyOwned[13]) TEXT_ON_RIGHT(TheText.Get("STPR_14")); + if (PropertyOwned[14]) TEXT_ON_RIGHT(TheText.Get("STPR_15")); + } + STAT_LINE_1(int, "CHASE", HighestChaseValue, 0); + TEXT_ON_RIGHT(FindChaseString(HighestChaseValue)); + + return counter; + +#undef STAT_LINE_1 +#undef STAT_LINE_2 +#undef TEXT_ON_LEFT_GXT +#undef TEXT_ON_RIGHT +#undef FASTEST_TIME +} diff --git a/src/miami/core/Stats.h b/src/miami/core/Stats.h new file mode 100644 index 00000000..243ff0ec --- /dev/null +++ b/src/miami/core/Stats.h @@ -0,0 +1,152 @@ +#pragma once + +#include "PedType.h" +#include "audio_enums.h" + +class CStats +{ +public: + enum { + TOTAL_FASTEST_TIMES = 23, + TOTAL_HIGHEST_SCORES = 5, + TOTAL_BEST_POSITIONS = 1, + TOTAL_PROPERTIES = 15 + }; + static int32 SeagullsKilled; + 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 DistanceTravelledByCar; + static float DistanceTravelledByHelicoptor; + static float DistanceTravelledByBike; + static float DistanceTravelledByBoat; + static float DistanceTravelledByPlane; + static float DistanceTravelledByGolfCart; + static float DistanceTravelledOnFoot; + static int32 FlightTime; + static int32 TimesDrowned; + static int32 PhotosTaken; + static float LoanSharks; + static float StoresKnockedOff; + static float MovieStunts; + static float Assassinations; + static float PizzasDelivered; + static float GarbagePickups; + static float IceCreamSold; + static float TopShootingRangeScore; + static float ShootingRank; + static int32 CarsExploded; + static int32 BoatsExploded; + static int32 WantedStarsAttained; + static int32 WantedStarsEvaded; + static int32 PeopleKilledByPlayer; + static float ProgressMade; + static float 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 LivesSavedWithAmbulance; + static int32 CriminalsCaught; + static int32 HighestLevelAmbulanceMission; + static int32 HighestLevelVigilanteMission; + static int32 HighestLevelFireMission; + static int32 FiresExtinguished; + static int32 TotalNumberKillFrenzies; + static int32 TotalNumberMissions; + static int32 RoundsFiredByPlayer; + static int32 KgsOfExplosivesUsed; + static int32 BulletsThatHit; + static int32 BestTimeBombDefusal; + static int32 FastestTimes[TOTAL_FASTEST_TIMES]; + static int32 HighestScores[TOTAL_HIGHEST_SCORES]; + static int32 BestPositions[TOTAL_BEST_POSITIONS]; + static bool PropertyOwned[TOTAL_PROPERTIES]; + static int32 NumPropertyOwned; + static int32 PropertyDestroyed; + static float HighestChaseValue; + static int32 CheatedCount; + static int32 ShowChaseStatOnScreen; + static int32 PamphletMissionPassed; + static bool abSonyCDs[1]; + static int32 BloodRingKills; + static int32 BloodRingTime; + static float FavoriteRadioStationList[NUM_RADIOS]; + static int32 Sprayings; + static float AutoPaintingBudget; + static int32 NoMoreHurricanes; + static float FashionBudget; + static float PropertyBudget; + static float WeaponBudget; + static int32 SafeHouseVisits; + static int32 TyresPopped; + + static int32 LongestWheelie; + static int32 LongestStoppie; + static int32 Longest2Wheel; + static float LongestWheelieDist; + static float LongestStoppieDist; + static float Longest2WheelDist; + +public: + static void Init(void); + static void RegisterFastestTime(int32, int32); + static void RegisterHighestScore(int32, int32); + static void RegisterBestPosition(int32, int32); + static void AnotherLifeSavedWithAmbulance(); + static void AnotherCriminalCaught(); + static void RegisterLevelAmbulanceMission(int32); + static void RegisterLevelVigilanteMission(int32); + static void RegisterLevelFireMission(int32); + static void AnotherFireExtinguished(); + static wchar *FindCriminalRatingString(); + static wchar *FindChaseString(float fMediaLevel); + 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); + static float GetPercentageProgress(); + + static void MoneySpentOnWeapons(int32); + static void MoneySpentOnProperty(int32); + static void MoneySpentOnAutoPainting(int32); + static void MoneySpentOnFashion(int32); + + static void NumOfVisitsFromLoanSharks(int32); + static void NumOfStoresKnockedOff(int32); + static void NumOfMovieStunts(int32); + static void NumOfAssassinations(int32); + static void NumOfPizzasDelivered(int32); + static void NumOfGarbagePickups(int32); + static void NumOfIceCreamSold(int32); + static void AddNumBloodRingKills(int32); + + static void LongestTimeInBloodRing(int32); + static void AddPropertyAsOwned(int32); + static void PopulateFavoriteRadioStationList(); + static float GetFavoriteRadioStationList(int32); + static void BuildStatLine(Const char *, void *, int, void *, int); + static int ConstructStatLine(int); +}; diff --git a/src/miami/core/Streaming.cpp b/src/miami/core/Streaming.cpp new file mode 100644 index 00000000..5833592e --- /dev/null +++ b/src/miami/core/Streaming.cpp @@ -0,0 +1,3278 @@ +#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 "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" +#include "Replay.h" +#include "main.h" +#include "ColStore.h" +#include "DMAudio.h" +#include "Script.h" +#include "MemoryMgr.h" +#include "MemoryHeap.h" +#include "Font.h" +#include "Frontend.h" +#include "VarConsole.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_numPedsLoaded; +int32 CStreaming::ms_vehiclesLoaded[MAXVEHICLESLOADED]; +int32 CStreaming::ms_lastVehicleDeleted; +bool CStreaming::ms_bIsPedFromPedGroupLoaded[NUMMODELSPERPEDGROUP]; +CDirectory *CStreaming::ms_pExtraObjectsDir; +int32 CStreaming::ms_numPriorityRequests; +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 *pIslandLODmainlandEntity; +CEntity *pIslandLODbeachEntity; +int32 islandLODmainland; +int32 islandLODbeach; + +#ifndef MASTER +bool gbPrintStats; +bool gbPrintVehiclesInMemory; // TODO +bool gbPrintStreamingBuffer; // TODO +#endif + +#define memory_logf(...) // printf(__VA_ARGS__) + +#define STREAMING_MEM_SIZE (4 * 1024 * 1024) + +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_numPedsLoaded = 8; + + for(i = 0; i < ARRAY_SIZE(ms_bIsPedFromPedGroupLoaded); i++) + ms_bIsPedFromPedGroupLoaded[i] = false; + + ms_pExtraObjectsDir = new CDirectory(EXTRADIRSIZE); + ms_numPriorityRequests = 0; + ms_currentPedGrp = -1; + ms_lastCullZone = -1; // unused because RemoveModelsNotVisibleFromCullzone is gone + ms_loadedGangs = 0; + ms_currentPedLoading = NUMMODELSPERPEDGROUP; // 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 +#if defined(RW_DC) + ms_memoryAvailable = STREAMING_MEM_SIZE; + desiredNumVehiclesLoaded = 12; +#else +#define MB (1024*1024) +#ifdef FIX_BUGS + // do what gta3 does + extern size_t _dwMemAvailPhys; + ms_memoryAvailable = (_dwMemAvailPhys - 10*MB)/2; + if(ms_memoryAvailable < 65*MB) + ms_memoryAvailable = 65*MB; + desiredNumVehiclesLoaded = (int32)((ms_memoryAvailable / MB - 65) / 3 + 12); + if(desiredNumVehiclesLoaded > MAXVEHICLESLOADED) + desiredNumVehiclesLoaded = MAXVEHICLESLOADED; +#else + ms_memoryAvailable = 65 * MB; + desiredNumVehiclesLoaded = 25; + debug("Memory allocated to Streaming is %zuMB", ms_memoryAvailable/MB); // original modifier was %d +#endif +#undef MB +#endif +#endif + + // find island LODs + + pIslandLODmainlandEntity = nil; + pIslandLODbeachEntity = nil; + islandLODmainland = -1; + islandLODbeach = -1; + CModelInfo::GetModelInfo("IslandLODmainland", &islandLODmainland); + CModelInfo::GetModelInfo("IslandLODbeach", &islandLODbeach); + +#ifndef MASTER + VarConsole.Add("Streaming Debug", &gbPrintStats, true); + VarConsole.Add("Streaming Vehicle Debug", &gbPrintVehiclesInMemory, true); + VarConsole.Add("Printf Streaming Buffer contents", &gbPrintStreamingBuffer, true); +#endif +} + +void +CStreaming::Init(void) +{ +#ifdef USE_TXD_CDIMAGE + if(!CanVideoCardDoDXT()){ + 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(); +#else + CStreaming::Init2(); +#endif +} + +void +CStreaming::ReInit(void) +{ + int i; + CStreaming::FlushRequestList(); + CStreaming::DeleteAllRwObjects(); + CStreaming::RemoveAllUnusedModels(); + for(i = 0; i < MODELINFOSIZE; i++) + if(CModelInfo::GetModelInfo(i) && ms_aInfoForModel[i].m_flags & STREAMFLAGS_SCRIPTOWNED) + SetMissionDoesntRequireModel(i); + CStreaming::ms_disableStreaming = false; +} + +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) +{ + 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; + + LoadBigBuildingsWhenNeeded(); + if(!ms_disableStreaming && TheCamera.GetPosition().z < 55.0f) + AddModelsToRequestList(TheCamera.GetPosition(), 0); + + DeleteFarAwayRwObjects(TheCamera.GetPosition()); + + if(!ms_disableStreaming && + !CCutsceneMgr::IsCutsceneProcessing() && + ms_numModelsRequested < 5 && + !CRenderer::m_loadingPriority && + CGame::currArea == AREA_MAIN_MAP && + !CReplay::IsPlayingBack()){ + StreamVehiclesAndPeds(); + StreamZoneModels(FindPlayerCoors()); + } + + LoadRequestedModels(); + + if(CWorld::Players[0].m_pRemoteVehicle){ + CColStore::AddCollisionNeededAtPosn(FindPlayerCoors()); + CColStore::LoadCollision(CWorld::Players[0].m_pRemoteVehicle->GetPosition()); + CColStore::EnsureCollisionIsInMemory(CWorld::Players[0].m_pRemoteVehicle->GetPosition()); + }else{ + CColStore::LoadCollision(FindPlayerCoors()); + CColStore::EnsureCollisionIsInMemory(FindPlayerCoors()); + } + + // TODO: PrintRequestList + //if (CPad::GetPad(1)->GetLeftShoulder2JustDown() && CPad::GetPad(1)->GetRightShoulder1() && CPad::GetPad(1)->GetRightShoulder2()) + // PrintRequestList(); + + 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_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, int32 n) +{ + int fd, lastID, imgSelector; + int32 modelId; + 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))){ + bool bAddToStreaming = false; + + if(direntry.size > (uint32)ms_streamingBufferSize) + ms_streamingBufferSize = direntry.size; + direntry.name[23] = '\0'; + dot = strchr(direntry.name, '.'); + if(dot == nil || dot-direntry.name > 20){ + debug("%s is too long\n", direntry.name); + lastID = -1; + continue; + } + + *dot = '\0'; + + if(strncasecmp(dot+1, "DFF", 3) == 0){ + if(CModelInfo::GetModelInfo(direntry.name, &modelId)){ + bAddToStreaming = true; + }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(strncasecmp(dot+1, "TXD", 3) == 0){ + modelId = CTxdStore::FindTxdSlot(direntry.name); + if(modelId == -1) + modelId = CTxdStore::AddTxdSlot(direntry.name); + modelId += STREAM_OFFSET_TXD; + bAddToStreaming = true; + }else if(strncasecmp(dot+1, "COL", 3) == 0){ + modelId = CColStore::FindColSlot(direntry.name); + if(modelId == -1) + modelId = CColStore::AddColSlot(direntry.name); + modelId += STREAM_OFFSET_COL; + bAddToStreaming = true; + }else if(strncasecmp(dot+1, "IFP", 3) == 0){ + modelId = CAnimManager::RegisterAnimBlock(direntry.name); + modelId += STREAM_OFFSET_ANIM; + bAddToStreaming = true; + }else{ + *dot = '.'; + lastID = -1; + } + + if(bAddToStreaming){ + if(ms_aInfoForModel[modelId].GetCdSize()){ + debug("%s.%s appears more than once in %s\n", direntry.name, dot+1, 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; + } + } + } + + CFileMgr::CloseFile(fd); +} + +static char* +GetObjectName(int streamId) +{ + static char objname[32]; + if(streamId < STREAM_OFFSET_TXD) + sprintf(objname, "%s.dff", CModelInfo::GetModelInfo(streamId)->GetModelName()); + else if(streamId >= STREAM_OFFSET_TXD && streamId < STREAM_OFFSET_COL) + sprintf(objname, "%s.txd", CTxdStore::GetTxdName(streamId-STREAM_OFFSET_TXD)); + else if(streamId >= STREAM_OFFSET_COL && streamId < STREAM_OFFSET_ANIM) + sprintf(objname, "%s.col", CColStore::GetColName(streamId-STREAM_OFFSET_COL)); + else{ + assert(streamId < NUMSTREAMINFO); + sprintf(objname, "%s.ifp", CAnimManager::GetAnimationBlock(streamId-STREAM_OFFSET_ANIM)->name); + } + return objname; +} + +#ifdef USE_CUSTOM_ALLOCATOR +RpAtomic* +RegisterAtomicMemPtrsCB(RpAtomic *atomic, void *data) +{ + // empty because we expect models to be pre-instanced + 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 and anim have to be loaded + int animId = mi->GetAnimFileIndex(); +#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 + animId != -1 && !CAnimManager::GetAnimationBlock(animId)->isLoaded){ + RemoveModel(streamId); + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + + // Set Txd and anims to use + CTxdStore::AddRef(mi->GetTxdSlot()); +#if GTA_VERSION > GTAVC_PS2 + if(animId != -1) + CAnimManager::AddAnimBlockRef(animId); +#endif + + PUSH_MEMID(MEMID_STREAM_MODELS); + CTxdStore::SetCurrentTxd(mi->GetTxdSlot()); + if(mi->IsSimple()){ + success = CFileLoader::LoadAtomicFile(stream, streamId); + // TODO(MIAMI)? complain if file is not pre-instanced. we hardly are interested in that + } 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 and anims no longer needed unless we only read part of the file + if(ms_aInfoForModel[streamId].m_loadState != STREAMSTATE_STARTED){ + CTxdStore::RemoveRefWithoutDelete(mi->GetTxdSlot()); +#if GTA_VERSION > GTAVC_PS2 + if(animId != -1) + CAnimManager::RemoveAnimBlockRefWithoutDelete(animId); +#endif + } + + if(!success){ + debug("Failed to load %s\n", CModelInfo::GetModelInfo(streamId)->GetModelName()); + RemoveModel(streamId); + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + }else if(streamId >= STREAM_OFFSET_TXD && streamId < STREAM_OFFSET_COL){ + // Txd + 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; + } + }else if(streamId >= STREAM_OFFSET_COL && streamId < STREAM_OFFSET_ANIM){ + PUSH_MEMID(MEMID_STREAM_COLLISION); + bool success = CColStore::LoadCol(streamId-STREAM_OFFSET_COL, mem.start, mem.length); + POP_MEMID(); + if(!success){ + debug("Failed to load %s.col\n", CColStore::GetColName(streamId - STREAM_OFFSET_COL)); + RemoveModel(streamId); + ReRequestModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + }else if(streamId >= STREAM_OFFSET_ANIM){ + assert(streamId < NUMSTREAMINFO); + if((ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_KEEP_IN_MEMORY) == 0 && + !AreAnimsUsedByRequestedModels(streamId - STREAM_OFFSET_ANIM)){ + RemoveModel(streamId); + RwStreamClose(stream, &mem); + return false; + } + PUSH_MEMID(MEMID_STREAM_ANIMATION); + CAnimManager::LoadAnimFile(stream, true, nil); + CAnimManager::CreateAnimAssocGroups(); + POP_MEMID(); + } + + RwStreamClose(stream, &mem); + + 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(CanRemoveModel(streamId)) + ms_aInfoForModel[streamId].AddToList(&ms_startLoadedList); + } + }else if(streamId >= STREAM_OFFSET_TXD && streamId < STREAM_OFFSET_COL || + streamId >= STREAM_OFFSET_ANIM){ + assert(streamId < NUMSTREAMINFO); + // Txd and anims + if(CanRemoveModel(streamId)) + 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 + if (streamId < STREAM_OFFSET_TXD || streamId >= STREAM_OFFSET_COL) { + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + memory_logf("ConvertBufferToObject: Memory used: %d\n", ms_memoryUsed); + } +#endif + } + + endTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + timeDiff = endTime - startTime; + if(timeDiff > 5) + debug("%s took %d ms\n", GetObjectName(streamId), 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()); +#if GTA_VERSION > GTAVC_PS2 + if(mi->GetAnimFileIndex() != -1) + CAnimManager::RemoveAnimBlockRefWithoutDelete(mi->GetAnimFileIndex()); +#endif + }else if(streamId >= STREAM_OFFSET_TXD && streamId < STREAM_OFFSET_COL){ + // 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); + }else{ + assert(0 && "invalid streamId"); + } + + RwStreamClose(stream, &mem); + + ms_aInfoForModel[streamId].m_loadState = STREAMSTATE_LOADED; +#ifndef USE_CUSTOM_ALLOCATOR + if (streamId < STREAM_OFFSET_TXD || streamId >= STREAM_OFFSET_COL) { + ms_memoryUsed += ms_aInfoForModel[streamId].GetCdSize() * CDSTREAM_SECTOR_SIZE; + memory_logf("FinishLoadingLargeFile: Memory used: %d\n", ms_memoryUsed); + } +#endif + + if(!success){ + RemoveModel(streamId); + ReRequestModel(streamId); + UpdateMemoryUsed(); + return false; + } + + UpdateMemoryUsed(); + + endTime = CTimer::GetCurrentTimeInCycles() / CTimer::GetCyclesPerMillisecond(); + timeDiff = endTime - startTime; + if(timeDiff > 5) + debug("%s took %d ms\n", GetObjectName(streamId), 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(CanRemoveModel(id)) + 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){ + mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + RequestTxd(mi->GetTxdSlot(), flags); + int anim = mi->GetAnimFileIndex(); + if(anim != -1) + RequestAnim(anim, STREAMFLAGS_DEPENDENCY); + } + 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; + } +} + +#define BIGBUILDINGFLAGS STREAMFLAGS_DONT_REMOVE + +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 + && (((FrontEndMenuManager.m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_LOW) && (b != pIslandLODmainlandEntity) && + (b != pIslandLODbeachEntity)) || + (b->m_level == level)) +#else + && b->m_level == level +#endif + ) + if(!b->bStreamBIGBuilding) + RequestModel(b->GetModelIndex(), BIGBUILDINGFLAGS); + } + RequestIslands(level); +} + +void +CStreaming::RequestBigBuildings(eLevelName level, const CVector &pos) +{ + 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 + && (((FrontEndMenuManager.m_PrefsIslandLoading != CMenuManager::ISLAND_LOADING_LOW) && (b != pIslandLODmainlandEntity) && (b != pIslandLODbeachEntity) + ) || (b->m_level == level)) +#else + && b->m_level == level +#endif + ) + if(b->bStreamBIGBuilding){ + if(CRenderer::ShouldModelBeStreamed(b, pos)) + RequestModel(b->GetModelIndex(), 0); + }else + RequestModel(b->GetModelIndex(), BIGBUILDINGFLAGS); + } + RequestIslands(level); +} + +void +CStreaming::InstanceBigBuildings(eLevelName level, const CVector &pos) +{ + 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 && b->m_level == level && + b->bStreamBIGBuilding && b->m_rwObject == nil) + if(CRenderer::ShouldModelBeStreamed(b, pos)) + b->CreateRwObject(); + } +} + +void +CStreaming::InstanceLoadedModelsInSectorList(CPtrList &list) +{ + CPtrNode *node; + CEntity *e; + for(node = list.first; node; node = node->next) { + e = (CEntity *)node->item; + if(IsAreaVisible(e->m_area) && e->m_rwObject == nil) + e->CreateRwObject(); + } +} + +void +CStreaming::InstanceLoadedModels(const CVector &pos) +{ + int minX = CWorld::GetSectorIndexX(pos.x - 80.0f); + if(minX <= 0) minX = 0; + + int minY = CWorld::GetSectorIndexY(pos.y - 80.0f); + if(minY <= 0) minY = 0; + + int maxX = CWorld::GetSectorIndexX(pos.x + 80.0f); + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; + + int maxY = CWorld::GetSectorIndexY(pos.y + 80.0f); + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; + + int x, y; + for(y = minY; y <= maxY; y++){ + for(x = minX; x <= maxX; x++){ + CSector *sector = CWorld::GetSector(x, y); + InstanceLoadedModelsInSectorList(sector->m_lists[ENTITYLIST_BUILDINGS]); + InstanceLoadedModelsInSectorList(sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP]); + InstanceLoadedModelsInSectorList(sector->m_lists[ENTITYLIST_OBJECTS]); + InstanceLoadedModelsInSectorList(sector->m_lists[ENTITYLIST_DUMMIES]); + } + } +} + +void +CStreaming::RequestIslands(eLevelName level) +{ + ISLAND_LOADING_ISNT(HIGH) + switch(level){ + case LEVEL_MAINLAND: + if(islandLODbeach != -1) + RequestModel(islandLODbeach, BIGBUILDINGFLAGS); + break; + case LEVEL_BEACH: + if(islandLODmainland != -1) + RequestModel(islandLODmainland, BIGBUILDINGFLAGS); + break; + default: break; + } +} + +static char *IGnames[] = { + "player", + "player2", + "player3", + "player4", + "player5", + "player6", + "player7", + "player8", + "player9", + "play10", + "play11", + "igken", + "igcandy", + "igsonny", + "igbuddy", + "igjezz", + "ighlary", + "igphil", + "igmerc", + "igdick", + "igdiaz", + "" +}; + +static char *CSnames[] = { + "csplay", + "csplay2", + "csplay3", + "csplay4", + "csplay5", + "csplay6", + "csplay7", + "csplay8", + "csplay9", + "csplay10", + "csplay11", + "csken", + "cscandy", + "cssonny", + "csbuddy", + "csjezz", + "cshlary", + "csphil", + "csmerc", + "csdick", + "csdiaz", + "" +}; + +void +CStreaming::RequestSpecialModel(int32 modelId, const char *modelName, int32 flags) +{ + CBaseModelInfo *mi; + int txdId; + char oldName[48]; + uint32 pos, size; + int i, n; + + mi = CModelInfo::GetModelInfo(modelId); + if(strncasecmp("CSPlay", modelName, 6) == 0){ + char *curname = CModelInfo::GetModelInfo(MI_PLAYER)->GetModelName(); + for(int i = 0; CSnames[i][0]; i++){ + if(strcasecmp(curname, IGnames[i]) == 0){ + modelName = CSnames[i]; + break; + } + } + } + if(!CGeneral::faststrcmp(mi->GetModelName(), modelName)){ + // Already have the correct name, just request it + RequestModel(modelId, flags); + return; + } + + if(mi->GetNumRefs() > 0){ + n = CPools::GetPedPool()->GetSize()-1; + for(i = n; i >= 0 && mi->GetNumRefs() > 0; i--){ + CPed *ped = CPools::GetPedPool()->GetSlot(i); + if(ped && ped->GetModelIndex() == modelId && + !ped->IsPlayer() && ped->CanBeDeletedEvenInVehicle()) + CTheScripts::RemoveThisPed(ped); + } + n = CPools::GetObjectPool()->GetSize()-1; + for(i = n; i >= 0 && mi->GetNumRefs() > 0; i--){ + CObject *obj = CPools::GetObjectPool()->GetSlot(i); + if(obj && obj->GetModelIndex() == modelId && obj->CanBeDeleted()){ + CWorld::Remove(obj); + CWorld::RemoveReferencesToDeletedObject(obj); + delete obj; + } + } + } + + 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 if(id >= STREAM_OFFSET_TXD && id < STREAM_OFFSET_COL) + CTxdStore::RemoveTxd(id - STREAM_OFFSET_TXD); + else if(id >= STREAM_OFFSET_COL && id < STREAM_OFFSET_ANIM) + CColStore::RemoveCol(id - STREAM_OFFSET_COL); + else if(id >= STREAM_OFFSET_ANIM){ + assert(id < NUMSTREAMINFO); + CAnimManager::RemoveAnimBlock(id - STREAM_OFFSET_ANIM); + } + if (id < STREAM_OFFSET_TXD || id >= STREAM_OFFSET_COL) { + ms_memoryUsed -= ms_aInfoForModel[id].GetCdSize()*CDSTREAM_SECTOR_SIZE; + memory_logf("Remove Model: %d\n", ms_memoryUsed); + } + } + + 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 if(id >= STREAM_OFFSET_TXD && id < STREAM_OFFSET_COL) + CTxdStore::RemoveTxd(id - STREAM_OFFSET_TXD); + else if(id >= STREAM_OFFSET_COL && id < STREAM_OFFSET_ANIM) + CColStore::RemoveCol(id - STREAM_OFFSET_COL); + else if(id >= STREAM_OFFSET_ANIM){ + assert(id < NUMSTREAMINFO); + CAnimManager::RemoveAnimBlock(id - STREAM_OFFSET_ANIM); + } + } + + ms_aInfoForModel[id].m_loadState = STREAMSTATE_NOTLOADED; +} + +void +CStreaming::RemoveUnusedBuildings(eLevelName level) +{ + if(level != LEVEL_BEACH) + RemoveBuildings(LEVEL_BEACH); + if(level != LEVEL_MAINLAND) + RemoveBuildings(LEVEL_MAINLAND); +} + +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::RemoveBuildingsNotInArea(int32 area) +{ + int i, n; + CEntity *e; + + n = CPools::GetBuildingPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetBuildingPool()->GetSlot(i); + if(e && e->m_rwObject && !IsAreaVisible(area) && + (!e->bIsBIGBuilding || e->bStreamBIGBuilding)){ + if(e->bIsBIGBuilding) + RequestModel(e->GetModelIndex(), 0); + if(!e->bImBeingRendered) + e->DeleteRwObject(); + } + } + + n = CPools::GetTreadablePool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetTreadablePool()->GetSlot(i); + if(e && e->m_rwObject && !IsAreaVisible(area) && + (!e->bIsBIGBuilding || e->bStreamBIGBuilding)){ + if(e->bIsBIGBuilding) + RequestModel(e->GetModelIndex(), 0); + if(!e->bImBeingRendered) + e->DeleteRwObject(); + } + } + + n = CPools::GetObjectPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetObjectPool()->GetSlot(i); + if(e && e->m_rwObject && !IsAreaVisible(area) && + (!e->bIsBIGBuilding || e->bStreamBIGBuilding)){ + if(e->bIsBIGBuilding) + RequestModel(e->GetModelIndex(), 0); + if(!e->bImBeingRendered) + e->DeleteRwObject(); + } + } + + n = CPools::GetDummyPool()->GetSize()-1; + for(i = n; i >= 0; i--){ + e = CPools::GetDummyPool()->GetSlot(i); + if(e && e->m_rwObject && !IsAreaVisible(area) && + (!e->bIsBIGBuilding || e->bStreamBIGBuilding)){ + if(e->bIsBIGBuilding) + RequestModel(e->GetModelIndex(), 0); + if(!e->bImBeingRendered) + e->DeleteRwObject(); + } + } +} + +void +CStreaming::RemoveUnusedBigBuildings(eLevelName level) +{ + ISLAND_LOADING_IS(LOW) + { + if(level != LEVEL_BEACH) + RemoveBigBuildings(LEVEL_BEACH); + if(level != LEVEL_MAINLAND) + RemoveBigBuildings(LEVEL_MAINLAND); + } + 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) +{ + int i; + if(pIslandLODmainlandEntity == nil) + for(i = CPools::GetBuildingPool()->GetSize()-1; i >= 0; i--){ + CBuilding *building = CPools::GetBuildingPool()->GetSlot(i); + if(building == nil) + continue; + if(building->GetModelIndex() == islandLODmainland) + pIslandLODmainlandEntity = building; + if(building->GetModelIndex() == islandLODbeach) + pIslandLODbeachEntity = building; + } +#ifdef NO_ISLAND_LOADING + if(FrontEndMenuManager.m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_HIGH) { + DeleteIsland(pIslandLODmainlandEntity); + DeleteIsland(pIslandLODbeachEntity); + } else +#endif + switch(level){ + case LEVEL_MAINLAND: + DeleteIsland(pIslandLODmainlandEntity); + break; + case LEVEL_BEACH: + DeleteIsland(pIslandLODbeachEntity); + + 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 && CanRemoveModel(id) && 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; + CVehicleModelInfo* pVehicleInfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + if (pVehicleInfo->m_vehicleClass != -1) + CCarCtrl::RemoveFromLoadedVehicleArray(id, pVehicleInfo->m_vehicleClass); + return true; +} + +bool re3RemoveLeastUsedModel() { + return CStreaming::RemoveLeastUsedModel(STREAMFLAGS_20); +} + +bool re3EmergencyRemoveModel() { + auto usedmem = CStreaming::ms_memoryUsed; + CStreaming::DeleteRwObjectsBehindCamera(CStreaming::ms_memoryUsed-1); + return usedmem != CStreaming::ms_memoryUsed; +} + +bool +CStreaming::RemoveLeastUsedModel(uint32 excludeMask) +{ + CStreamingInfo *si; + int streamId; + + for(si = ms_endLoadedList.m_prev; si != &ms_startLoadedList; si = si->m_prev){ + if(si->m_flags & excludeMask) + continue; + streamId = si - ms_aInfoForModel; + if(streamId < STREAM_OFFSET_TXD){ + if (CModelInfo::GetModelInfo(streamId)->GetNumRefs() == 0) { + RemoveModel(streamId); + return true; + } + }else if(streamId >= STREAM_OFFSET_TXD && streamId < STREAM_OFFSET_COL){ + if(CTxdStore::GetNumRefs(streamId - STREAM_OFFSET_TXD) == 0 && + !IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)){ + RemoveModel(streamId); + return true; + } + }else if(streamId >= STREAM_OFFSET_ANIM){ + assert(streamId < NUMSTREAMINFO); + if(CAnimManager::GetNumRefsToAnimBlock(streamId - STREAM_OFFSET_ANIM) == 0 && + !AreAnimsUsedByRequestedModels(streamId - STREAM_OFFSET_ANIM)){ + RemoveModel(streamId); + return true; + } + } + } + return (ms_numVehiclesLoaded > 7 || CGame::currArea != AREA_MAIN_MAP && ms_numVehiclesLoaded > 4) && 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 && + CModelInfo::GetModelInfo(i)->GetNumRefs() == 0) { + RemoveModel(i); + ms_aInfoForModel[i].m_loadState = STREAMSTATE_NOTLOADED; + } + } +} + +void +CStreaming::RemoveUnusedModelsInLoadedList(void) +{ + // empty +} + +bool +CStreaming::RemoveLoadedZoneModel(void) +{ + int i; + + if(ms_currentPedGrp == -1) + return false; + + for(i = 0; i < NUMMODELSPERPEDGROUP; i++){ + int mi = CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]; + if(mi != -1 && ms_bIsPedFromPedGroupLoaded[i] && + HasModelLoaded(mi) && CanRemoveModel(mi) && + CModelInfo::GetModelInfo(mi)->GetNumRefs() == 0){ + RemoveModel(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]); + ms_numPedsLoaded--; + ms_bIsPedFromPedGroupLoaded[i] = false; + return true; + } + } + + return false; +} + +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; +} + +bool +CStreaming::AreAnimsUsedByRequestedModels(int32 animId) +{ + 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)->GetAnimFileIndex() == animId) + return true; + } + + for(i = 0; i < 4; i++){ + streamId = ms_channel[0].streamIds[i]; + if(streamId != -1 && streamId < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(streamId)->GetAnimFileIndex() == animId) + return true; + streamId = ms_channel[1].streamIds[i]; + if(streamId != -1 && streamId < STREAM_OFFSET_TXD && + CModelInfo::GetModelInfo(streamId)->GetAnimFileIndex() == animId) + 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 && CanRemoveModel(id) && + 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); + CVehicleModelInfo* pVehicleInfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + if (pVehicleInfo->m_vehicleClass != -1) + CCarCtrl::RemoveFromLoadedVehicleArray(id, pVehicleInfo->m_vehicleClass); + } + } + + ms_vehiclesLoaded[ms_lastVehicleDeleted++] = modelId; + if(ms_lastVehicleDeleted == MAXVEHICLESLOADED) + ms_lastVehicleDeleted = 0; + CVehicleModelInfo* pVehicleInfo = (CVehicleModelInfo*)CModelInfo::GetModelInfo(modelId); + if (pVehicleInfo->m_vehicleClass != -1) + CCarCtrl::AddToLoadedVehicleArray(modelId, pVehicleInfo->m_vehicleClass, pVehicleInfo->m_frequency); + return true; +} + +bool +CStreaming::IsObjectInCdImage(int32 id) +{ + uint32 posn, size; + return ms_aInfoForModel[id].GetCdPosnAndSize(posn, size); +} + +void +CStreaming::SetModelIsDeletable(int32 id) +{ + ms_aInfoForModel[id].m_flags &= ~STREAMFLAGS_DONT_REMOVE; + if ((id >= STREAM_OFFSET_TXD && id < STREAM_OFFSET_COL || 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::LoadInitialWeapons(void) +{ + CStreaming::RequestModel(MI_NIGHTSTICK, STREAMFLAGS_DONT_REMOVE); + CStreaming::RequestModel(MI_MISSILE, STREAMFLAGS_DONT_REMOVE); +} + +void +CStreaming::LoadInitialVehicles(void) +{ + ms_numVehiclesLoaded = 0; + ms_lastVehicleDeleted = 0; + + RequestModel(MI_POLICE, 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_FBIRANCH, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_FBI, STREAMFLAGS_DONT_REMOVE); + }else{ + SetModelIsDeletable(MI_FBIRANCH); + if(!HasModelLoaded(MI_FBIRANCH)) + 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 (FindPlayerPed()->m_pWanted->AreMiamiViceRequired()) { + SetModelIsDeletable(MI_VICE1); + SetModelIsDeletable(MI_VICE2); + SetModelIsDeletable(MI_VICE3); + SetModelIsDeletable(MI_VICE4); + SetModelIsDeletable(MI_VICE5); + SetModelIsDeletable(MI_VICE6); + SetModelIsDeletable(MI_VICE7); + SetModelIsDeletable(MI_VICE8); + RequestModel(MI_VICECHEE, STREAMFLAGS_DONT_REMOVE); + if(CPopulation::NumMiamiViceCops == 0) + switch (CCarCtrl::MiamiViceCycle) { + case 0: + RequestModel(MI_VICE1, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_VICE2, STREAMFLAGS_DONT_REMOVE); + break; + case 1: + RequestModel(MI_VICE3, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_VICE4, STREAMFLAGS_DONT_REMOVE); + break; + case 2: + RequestModel(MI_VICE5, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_VICE6, STREAMFLAGS_DONT_REMOVE); + break; + case 3: + RequestModel(MI_VICE7, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_VICE8, STREAMFLAGS_DONT_REMOVE); + break; + } + } + else { + SetModelIsDeletable(MI_VICECHEE); + SetModelIsDeletable(MI_VICE1); + SetModelIsDeletable(MI_VICE2); + SetModelIsDeletable(MI_VICE3); + SetModelIsDeletable(MI_VICE4); + SetModelIsDeletable(MI_VICE5); + SetModelIsDeletable(MI_VICE6); + SetModelIsDeletable(MI_VICE7); + SetModelIsDeletable(MI_VICE8); + } + + if(timeBeforeNextLoad >= 0) + timeBeforeNextLoad--; + else if(ms_numVehiclesLoaded <= desiredNumVehiclesLoaded){ + CZoneInfo zone; + CVector coors = FindPlayerCoors(); + CTheZones::GetZoneInfoForTimeOfDay(&coors, &zone); + int32 maxReq = -1; + int32 mostRequestedRating = 0; + for(i = 0; i < CCarCtrl::TOTAL_CUSTOM_CLASSES; i++){ + if(CCarCtrl::NumRequestsOfCarRating[i] > maxReq && + ((i == 0 && zone.carThreshold[0] != 0) || +#ifdef FIX_BUGS + (i < CCarCtrl::NUM_CAR_CLASSES && zone.carThreshold[i] != zone.carThreshold[i-1]) || + (i == CCarCtrl::NUM_CAR_CLASSES && zone.boatThreshold[i - CCarCtrl::NUM_CAR_CLASSES] != 0) || + (i > CCarCtrl::NUM_CAR_CLASSES && i < CCarCtrl::TOTAL_CUSTOM_CLASSES && zone.boatThreshold[i - CCarCtrl::NUM_CAR_CLASSES] != zone.boatThreshold[i - CCarCtrl::NUM_CAR_CLASSES - 1]))) { +#else + (i != 0 && zone.carThreshold[i] != zone.carThreshold[i-1]))) { +#endif + maxReq = CCarCtrl::NumRequestsOfCarRating[i]; + mostRequestedRating = i; + } + } + model = CCarCtrl::ChooseCarModelToLoad(mostRequestedRating); + if(!HasModelLoaded(model)){ + RequestModel(model, STREAMFLAGS_DEPENDENCY); + timeBeforeNextLoad = 350; + } + CCarCtrl::NumRequestsOfCarRating[mostRequestedRating] = 0; + } +} + +void +CStreaming::StreamZoneModels(const CVector &pos) +{ + int i, j; + uint16 gangsToLoad, gangCarsToLoad, bit; + CZoneInfo info; + static int timeBeforeNextLoad = 0; + + CTheZones::GetZoneInfoForTimeOfDay(&pos, &info); + + if(info.pedGroup != ms_currentPedGrp){ + + // unload pevious group + if(ms_currentPedGrp != -1) + for(i = 0; i < NUMMODELSPERPEDGROUP; i++){ + ms_bIsPedFromPedGroupLoaded[i] = false; + 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 < MAXZONEPEDSLOADED; i++){ + do + j = CGeneral::GetRandomNumberInRange(0, NUMMODELSPERPEDGROUP); + while(ms_bIsPedFromPedGroupLoaded[j]); + ms_bIsPedFromPedGroupLoaded[j] = true; + if(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[j] != -1) + RequestModel(CPopulation::ms_pPedGroups[ms_currentPedGrp].models[j], STREAMFLAGS_DEPENDENCY); + } + ms_numPedsLoaded = MAXZONEPEDSLOADED; + timeBeforeNextLoad = 300; + } + + if(timeBeforeNextLoad >= 0) + timeBeforeNextLoad--; + else{ + // Switch a ped + int oldMI; + // Find a ped to unload + for(i = 0; i < NUMMODELSPERPEDGROUP; i++) + if(ms_bIsPedFromPedGroupLoaded[i]){ + oldMI = CPopulation::ms_pPedGroups[ms_currentPedGrp].models[i]; + if(oldMI != -1 && CModelInfo::GetModelInfo(oldMI)->GetNumRefs() == 0) + break; + } + // And load a new one + if(i != NUMMODELSPERPEDGROUP || ms_numPedsLoaded < MAXZONEPEDSLOADED){ + do + j = CGeneral::GetRandomNumberInRange(0, NUMMODELSPERPEDGROUP); + while(ms_bIsPedFromPedGroupLoaded[j]); + if(ms_numPedsLoaded == MAXZONEPEDSLOADED) + ms_bIsPedFromPedGroupLoaded[i] = false; + ms_bIsPedFromPedGroupLoaded[j] = true; + int newMI = CPopulation::ms_pPedGroups[ms_currentPedGrp].models[j]; + if(newMI != oldMI){ + RequestModel(newMI, STREAMFLAGS_DEPENDENCY); + debug("Request Ped %s\n", CModelInfo::GetModelInfo(newMI)->GetModelName()); + if(ms_numPedsLoaded == MAXZONEPEDSLOADED){ + SetModelIsDeletable(oldMI); + SetModelTxdIsDeletable(oldMI); + debug("Remove Ped %s\n", CModelInfo::GetModelInfo(oldMI)->GetModelName()); + }else + ms_numPedsLoaded++; + timeBeforeNextLoad = 300; + } + } + } + + RequestModel(MI_MALE01, STREAMFLAGS_DONT_REMOVE); + RequestModel(MI_TAXI_D, STREAMFLAGS_DONT_REMOVE); + + gangsToLoad = 0; + gangCarsToLoad = 0; + if(info.gangPedThreshold[0] != info.copPedThreshold) + gangsToLoad = 1; + for(i = 1; i < NUM_GANGS; i++) + if(info.gangPedThreshold[i] != info.gangPedThreshold[i-1]) + gangsToLoad |= 1< 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); } +inline bool AnimNotLoaded(int32 animId) { return animId != -1 && ModelNotLoaded(animId + STREAM_OFFSET_ANIM); } + +// 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 Txds or anims if necessary + if(streamId < STREAM_OFFSET_TXD){ + int txdId = CModelInfo::GetModelInfo(streamId)->GetTxdSlot(); + if(TxdNotLoaded(txdId)){ + ReRequestTxd(txdId); + continue; + } + int animId = CModelInfo::GetModelInfo(streamId)->GetAnimFileIndex(); + if(AnimNotLoaded(animId)){ + ReRequestAnim(animId); + continue; + } + }else if(streamId >= STREAM_OFFSET_ANIM && CCutsceneMgr::IsCutsceneProcessing()) + 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); + + // remove Txds and Anims that aren't requested anymore + while(streamId != -1){ + if(ms_aInfoForModel[streamId].m_flags & STREAMFLAGS_KEEP_IN_MEMORY) + break; + if(streamId >= STREAM_OFFSET_TXD && streamId < STREAM_OFFSET_COL){ + if(IsTxdUsedByRequestedModels(streamId - STREAM_OFFSET_TXD)) + break; + }else if(streamId >= STREAM_OFFSET_ANIM){ + assert(streamId < NUMSTREAMINFO); + if(AreAnimsUsedByRequestedModels(streamId - STREAM_OFFSET_ANIM)) + break; + }else + 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()) || + AnimNotLoaded(CModelInfo::GetModelInfo(streamId)->GetAnimFileIndex())) + 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() && + (CanRemoveModel(id) || GetAvailableVehicleSlot() == -1)){ + // can't load vehicle + RemoveModel(id); + if(!CanRemoveModel(id)) + 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; + bInsideLoadAll = true; + + 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; + + int numRequests = 4*ms_numModelsRequested; + + if(bInsideLoadAll) + return; + bInsideLoadAll = true; + + if(priority) + numRequests = ms_numPriorityRequests; + + FlushChannels(); + imgOffset = GetCdImageOffset(CdStreamGetLastPosn()); + + while(ms_endRequestedList.m_prev != &ms_startRequestedList && numRequests > 0){ + numRequests--; + streamId = GetNextFileOnCd(0, priority); + if(streamId == -1) + break; + + ms_aInfoForModel[streamId].RemoveFromList(); + ms_channel[0].streamIds[0] = streamId; + 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) + + gMainHeap.GetMemoryUsed(MEMID_STREAM_COLLISION) + + gMainHeap.GetMemoryUsed(MEMID_STREAM_ANIMATION); +#endif +} + +#define STREAM_DIST 80.0f + +void +CStreaming::AddModelsToRequestList(const CVector &pos, int32 flags) +{ + 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 <= 0){ + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], flags); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], flags); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], flags); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], flags); + }else if(d <= 3*3){ + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS], pos.x, pos.y, xmin, ymin, xmax, ymax, flags); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], pos.x, pos.y, xmin, ymin, xmax, ymax, flags); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_OBJECTS], pos.x, pos.y, xmin, ymin, xmax, ymax, flags); + ProcessEntitiesInSectorList(sect->m_lists[ENTITYLIST_DUMMIES], pos.x, pos.y, xmin, ymin, xmax, ymax, flags); + } + } + } +} + +void +CStreaming::ProcessEntitiesInSectorList(CPtrList &list, float x, float y, float xmin, float ymin, float xmax, float ymax, int32 flags) +{ + 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 && IsAreaVisible(e->m_area) && !e->bDontStream && e->bIsVisible){ + 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) + RequestModel(e->GetModelIndex(), flags); + } + } + } +} + +void +CStreaming::ProcessEntitiesInSectorList(CPtrList &list, int32 flags) +{ + 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 && IsAreaVisible(e->m_area) && !e->bDontStream && e->bIsVisible){ + CTimeModelInfo *mi = (CTimeModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex()); + if (mi->GetModelType() != MITYPE_TIME || CClock::GetIsTimeInRange(mi->GetTimeOn(), mi->GetTimeOff())) + RequestModel(e->GetModelIndex(), flags); + } + } +} + +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; + } + } + + + while(RemoveLoadedZoneModel()) + if(ms_memoryUsed < 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; + } + } + + // 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; + } + } + + while(RemoveLoadedZoneModel()) + if(ms_memoryUsed < 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; + } + } + +// this is gone in mobile together with RemoveReferencedTxds +// 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; + } + } + } + + while(ms_memoryUsed >= mem && RemoveLeastUsedModel(0)); +} + +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) >= 1.6f) +// { + e->DeleteRwObject(); +// return; // BUG? +// } + else // FIX? + if(Abs(CWorld::GetSectorIndexY(e->GetPosition().y) - y) >= 1.6f) + 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 && + FindPlayerPed()->m_pCurSurface != e){ + 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() || e->bOffscreen) && 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 + if(ms_memoryAvailable == 0) { + ms_memoryAvailable = STREAMING_MEM_SIZE; + } +#endif + while(ms_memoryUsed >= ms_memoryAvailable - size) + if(!RemoveLeastUsedModel(STREAMFLAGS_20)){ + 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; + DeleteAllRwObjects(); + if(level == LEVEL_GENERIC) + level = CGame::currLevel; + CGame::currLevel = level; + RemoveUnusedBigBuildings(level); + RequestBigBuildings(level, pos); + RequestBigBuildings(LEVEL_GENERIC, pos); + RemoveIslandsNotUsed(level); + LoadAllRequestedModels(false); + InstanceBigBuildings(level, pos); + InstanceBigBuildings(LEVEL_GENERIC, pos); + AddModelsToRequestList(pos, STREAMFLAGS_20); + CRadar::StreamRadarSections(pos); + + if (!CGame::IsInInterior()) { + for (int i = 0; i < 5; i++) { + CZoneInfo zone; + CTheZones::GetZoneInfoForTimeOfDay(&pos, &zone); + int32 model = CCarCtrl::ChooseCarModelToLoad(CCarCtrl::ChooseCarRating(&zone)); + CStreaming::RequestModel(model, STREAMFLAGS_DEPENDENCY); + } + } + LoadAllRequestedModels(false); + InstanceLoadedModels(pos); + + for(int i = 0; i < NUMSTREAMINFO; i++) + ms_aInfoForModel[i].m_flags &= ~STREAMFLAGS_20; + debug("End load scene\n"); +} + +void +CStreaming::LoadSceneCollision(const CVector &pos) +{ + CColStore::LoadCollision(pos); + CStreaming::LoadAllRequestedModels(false); +} + +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), 0); + CStreaming::LoadRequestedModels(); + // original modifier was %d + sprintf(gString, "Requested %d, memory size %zuK\n", CStreaming::ms_numModelsRequested, 2 * CStreaming::ms_memoryUsed); + } + 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/miami/core/Streaming.h b/src/miami/core/Streaming.h new file mode 100644 index 00000000..b3198d80 --- /dev/null +++ b/src/miami/core/Streaming.h @@ -0,0 +1,221 @@ +#pragma once + +#include "Game.h" + +enum { + STREAM_OFFSET_TXD = MODELINFOSIZE, + STREAM_OFFSET_COL = STREAM_OFFSET_TXD+TXDSTORESIZE, + STREAM_OFFSET_ANIM = STREAM_OFFSET_COL+COLSTORESIZE, + NUMSTREAMINFO = STREAM_OFFSET_ANIM+NUMANIMBLOCKS +}; + +enum StreamFlags +{ + STREAMFLAGS_DONT_REMOVE = 0x01, + STREAMFLAGS_SCRIPTOWNED = 0x02, + STREAMFLAGS_DEPENDENCY = 0x04, // Is this right? + STREAMFLAGS_PRIORITY = 0x08, + STREAMFLAGS_NOFADE = 0x10, + STREAMFLAGS_20 = 0x20, // TODO(MIAMI): what's this + + 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_numPedsLoaded; + static int32 ms_vehiclesLoaded[MAXVEHICLESLOADED]; + static int32 ms_lastVehicleDeleted; + static bool ms_bIsPedFromPedGroupLoaded[NUMMODELSPERPEDGROUP]; + static CDirectory *ms_pExtraObjectsDir; + static int32 ms_numPriorityRequests; + 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 ReInit(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 HasColLoaded(int32 id) { return HasModelLoaded(id+STREAM_OFFSET_COL); } + static bool HasAnimLoaded(int32 id) { return HasModelLoaded(id+STREAM_OFFSET_ANIM); } + 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 bool CanRemoveCol(int32 id) { return CanRemoveModel(id+STREAM_OFFSET_COL); } + static bool CanRemoveAnim(int32 id) { return CanRemoveModel(id+STREAM_OFFSET_ANIM); } + 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 RequestCol(int32 col, int32 flags) { RequestModel(col + STREAM_OFFSET_COL, flags); } + static void ReRequestCol(int32 col) { ReRequestModel(col + STREAM_OFFSET_COL); } + static void RequestAnim(int32 col, int32 flags) { RequestModel(col + STREAM_OFFSET_ANIM, flags); } + static void ReRequestAnim(int32 col) { ReRequestModel(col + STREAM_OFFSET_ANIM); } + static void RequestBigBuildings(eLevelName level); + static void RequestBigBuildings(eLevelName level, const CVector &pos); + static void InstanceBigBuildings(eLevelName level, const CVector &pos); + static void InstanceLoadedModelsInSectorList(CPtrList &list); + static void InstanceLoadedModels(const CVector &pos); + 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); + #if !defined(DC_TEXCONV) + static void RemoveTxd(int32 id) { RemoveModel(id + STREAM_OFFSET_TXD); } + static void RemoveCol(int32 id) { RemoveModel(id + STREAM_OFFSET_COL); } + static void RemoveAnim(int32 id) { RemoveModel(id + STREAM_OFFSET_ANIM); } + #endif + static void RemoveUnusedBuildings(eLevelName level); + static void RemoveBuildings(eLevelName level); + static void RemoveBuildingsNotInArea(int32 area); + static void RemoveUnusedBigBuildings(eLevelName level); + static void RemoveIslandsNotUsed(eLevelName level); + static void RemoveBigBuildings(eLevelName level); + static bool RemoveLoadedVehicle(void); + static bool RemoveLeastUsedModel(uint32 excludeMask); + static void RemoveAllUnusedModels(void); + static void RemoveUnusedModelsInLoadedList(void); + static bool RemoveLoadedZoneModel(void); + static int32 GetAvailableVehicleSlot(void); + static bool IsTxdUsedByRequestedModels(int32 txdId); + static bool AreAnimsUsedByRequestedModels(int32 animId); + static bool AddToLoadedVehiclesList(int32 modelId); + static bool IsObjectInCdImage(int32 id); + static void SetModelIsDeletable(int32 id); + static void SetModelTxdIsDeletable(int32 id); + static void SetMissionDoesntRequireModel(int32 id); + static void LoadInitialPeds(void); + static void LoadInitialWeapons(void); + static void LoadInitialVehicles(void); + static void StreamVehiclesAndPeds(void); + static void StreamZoneModels(const CVector &pos); + static void RemoveCurrentZonesModels(void); + static void LoadBigBuildingsWhenNeeded(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, int32 flags); + static void ProcessEntitiesInSectorList(CPtrList &list, float x, float y, float xmin, float ymin, float xmax, float ymax, int32 flags); + static void ProcessEntitiesInSectorList(CPtrList &list, int32 flags); + 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 LoadSceneCollision(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/miami/core/SurfaceTable.cpp b/src/miami/core/SurfaceTable.cpp new file mode 100644 index 00000000..c4b184bf --- /dev/null +++ b/src/miami/core/SurfaceTable.cpp @@ -0,0 +1,156 @@ +#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_SAND; + 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; + case SURFACE_SAND_BEACH: return ADHESIVE_SAND; + case SURFACE_CONCRETE_BEACH: return ADHESIVE_ROAD; + 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: + case SURFACE_CONCRETE_BEACH: + 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; + + case SURFACE_SAND: + case SURFACE_SAND_BEACH: + return 1.0f + CWeather::WetRoads*0.5f; + + default: + return 1.0f; + } +} + +float +CSurfaceTable::GetAdhesiveLimit(CColPoint &colpoint) +{ + return ms_aAdhesiveLimitTable[GetAdhesionGroup(colpoint.surfaceB)][GetAdhesionGroup(colpoint.surfaceA)]; +} + +bool +CSurfaceTable::IsSoftLanding(uint8 surf) +{ + return surf == SURFACE_GRASS || surf == SURFACE_SAND || surf == SURFACE_SAND_BEACH; +} diff --git a/src/miami/core/SurfaceTable.h b/src/miami/core/SurfaceTable.h new file mode 100644 index 00000000..8ff43106 --- /dev/null +++ b/src/miami/core/SurfaceTable.h @@ -0,0 +1,101 @@ +#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, + SURFACE_SAND_BEACH, + SURFACE_CONCRETE_BEACH, +}; + +enum +{ + ADHESIVE_RUBBER, + ADHESIVE_HARD, + ADHESIVE_ROAD, + ADHESIVE_LOOSE, + ADHESIVE_SAND, + ADHESIVE_WET, + + NUMADHESIVEGROUPS +}; + +struct CColPoint; + +inline bool +IsSeeThrough(uint8 surfType) +{ + switch(surfType) + case SURFACE_GLASS: + case SURFACE_TRANSPARENT_CLOTH: + case SURFACE_METAL_CHAIN_FENCE: + case SURFACE_TRANSPARENT_STONE: + case SURFACE_SCAFFOLD_POLE: + return true; + return false; +} + +// I think the necessity of this function is really a bug +inline bool +IsSeeThroughVertical(uint8 surfType) +{ + switch(surfType) + case SURFACE_GLASS: + case SURFACE_TRANSPARENT_CLOTH: + return true; + return false; +} + +inline bool +IsShootThrough(uint8 surfType) +{ + switch(surfType) + case SURFACE_TRANSPARENT_CLOTH: + case SURFACE_METAL_CHAIN_FENCE: + case SURFACE_TRANSPARENT_STONE: + case SURFACE_SCAFFOLD_POLE: + 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); + static bool IsSoftLanding(uint8 surf); +}; diff --git a/src/miami/core/TimeStep.cpp b/src/miami/core/TimeStep.cpp new file mode 100644 index 00000000..09dae911 --- /dev/null +++ b/src/miami/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/miami/core/TimeStep.h b/src/miami/core/TimeStep.h new file mode 100644 index 00000000..6101b4c2 --- /dev/null +++ b/src/miami/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/miami/core/Timer.cpp b/src/miami/core/Timer.cpp new file mode 100644 index 00000000..77f26a8b --- /dev/null +++ b/src/miami/core/Timer.cpp @@ -0,0 +1,331 @@ +#define WITHWINDOWS +#include "common.h" +#include "crossplatform.h" + +#include "DMAudio.h" +#include "Record.h" +#include "Timer.h" +#include "SpecialFX.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; + + float updInCyclesScaled = GetIsPaused() ? updInCycles : updInCycles * ms_fTimeScale; + + frameTime = updInCyclesScaled / (double)_nCyclesPerMS; + + dblUpdInMs = (double)updInCycles / (double)_nCyclesPerMS; + } + else +#endif + { + RsTimerType timer = RsTimer(); + + RsTimerType updInMs = timer - oldPcTimer; + + frameTime = (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() && !CSpecialFX::bSnapShotActive) + 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 = GetIsPaused() ? updInCycles : 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() && !CSpecialFX::bSnapShotActive) + 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/miami/core/Timer.h b/src/miami/core/Timer.h new file mode 100644 index 00000000..819bd30c --- /dev/null +++ b/src/miami/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/miami/core/User.cpp b/src/miami/core/User.cpp new file mode 100644 index 00000000..53196d03 --- /dev/null +++ b/src/miami/core/User.cpp @@ -0,0 +1,135 @@ +#include "common.h" + +#include "GameLogic.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::FindSmallestNavigationZoneForPosition(&pos, false, true); + CZone *defaultZone = CTheZones::FindSmallestNavigationZoneForPosition(&pos, true, false); + + 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); +} + +void +CPlaceName::ProcessAfterFrontEndShutDown(void) +{ + CHud::m_pLastZoneName = nil; + CHud::m_ZoneState = 0; + m_nAdditionalTimer = 250; +} + +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 && m_pCurrentVehicle != CGameLogic::pShortCutTaxi) + 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/miami/core/User.h b/src/miami/core/User.h new file mode 100644 index 00000000..dd53a40a --- /dev/null +++ b/src/miami/core/User.h @@ -0,0 +1,42 @@ +#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(); + void ProcessAfterFrontEndShutDown(); +}; + +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/miami/core/Wanted.cpp b/src/miami/core/Wanted.cpp new file mode 100644 index 00000000..65bc84d8 --- /dev/null +++ b/src/miami/core/Wanted.cpp @@ -0,0 +1,510 @@ +#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" +#include "Stats.h" + +int32 CWanted::MaximumWantedLevel = 6; +int32 CWanted::nMaximumWantedLevel = 9600; + +void +CWanted::Initialise() +{ + m_nChaos = 0; + m_nMinChaos = 0; + m_nLastUpdateTime = 0; + m_nLastWantedLevelChange = 0; + m_nLastTimeSuspended = 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_nMinWantedLevel = 0; + m_CopsBeatingSuspect = 0; + + for (int i = 0; i < ARRAY_SIZE(m_pCops); i++) + m_pCops[i] = nil; + + ClearQdCrimes(); +} + +bool +CWanted::AreMiamiViceRequired() +{ + return m_nWantedLevel >= 3; +} + +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 1; + 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 = 70; + break; + case 2: + m_nChaos = 200; + break; + case 3: + m_nChaos = 570; + break; + case 4: + m_nChaos = 1220; + break; + case 5: + m_nChaos = 2420; + break; + case 6: + m_nChaos = 4820; + break; + default: + break; + } + UpdateWantedLevel(); +} + +void +CWanted::SetWantedLevelNoDrop(int32 level) +{ + if (m_nWantedLevel < m_nMinWantedLevel) + SetWantedLevel(m_nMinWantedLevel); + + if (level > m_nWantedLevel) + SetWantedLevel(level); +} + +void +CWanted::CheatWantedLevel(int32 level) +{ + SetWantedLevel(level); + UpdateWantedLevel(); +} + +void +CWanted::SetMaximumWantedLevel(int32 level) +{ + switch(level){ + case 0: + nMaximumWantedLevel = 0; + MaximumWantedLevel = 0; + break; + case 1: + nMaximumWantedLevel = 115; + MaximumWantedLevel = 1; + break; + case 2: + nMaximumWantedLevel = 365; + MaximumWantedLevel = 2; + break; + case 3: + nMaximumWantedLevel = 875; + MaximumWantedLevel = 3; + break; + case 4: + nMaximumWantedLevel = 1800; + MaximumWantedLevel = 4; + break; + case 5: + nMaximumWantedLevel = 3600; + MaximumWantedLevel = 5; + break; + case 6: + nMaximumWantedLevel = 7200; + 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) +{ +#ifdef FIX_SIGNIFICANT_BUGS + 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: + 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; + case CRIME_EXPLOSION: + m_nChaos += 25.0f * chaos; + break; + case CRIME_HIT_PED_NASTYWEAPON: + m_nChaos += 35.0f * chaos; + break; + case CRIME_HIT_COP_NASTYWEAPON: + m_nChaos += 100.0f * chaos; + break; + default: + // Error("Undefined crime type, RegisterCrime, Crime.cpp"); // different file for some reason + Error("Undefined crime type, RegisterCrime, Wanted.cpp"); + } + m_nChaos = Max(m_nChaos, m_nMinChaos); + 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 < 50) { + if (m_nWantedLevel == 1) + ++CStats::WantedStarsEvaded; + m_nWantedLevel = 0; + m_MaximumLawEnforcerVehicles = 0; + m_MaxCops = 0; + m_RoadblockDensity = 0; + } else if (m_nChaos >= 50 && m_nChaos < 180) { + CStats::WantedStarsAttained += 1 - m_nWantedLevel; + m_nWantedLevel = 1; + m_MaximumLawEnforcerVehicles = 1; + m_MaxCops = 1; + m_RoadblockDensity = 0; + } else if (m_nChaos >= 180 && m_nChaos < 550) { + CStats::WantedStarsAttained += 2 - m_nWantedLevel; + m_nWantedLevel = 2; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 3; + m_RoadblockDensity = 0; + } else if (m_nChaos >= 550 && m_nChaos < 1200) { + CStats::WantedStarsAttained += 3 - m_nWantedLevel; + m_nWantedLevel = 3; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 4; + m_RoadblockDensity = 12; + } else if (m_nChaos >= 1200 && m_nChaos < 2400) { + CStats::WantedStarsAttained += 4 - m_nWantedLevel; + m_nWantedLevel = 4; + m_MaximumLawEnforcerVehicles = 2; + m_MaxCops = 6; + m_RoadblockDensity = 18; + } else if (m_nChaos >= 2400 && m_nChaos < 4800) { + CStats::WantedStarsAttained += 5 - m_nWantedLevel; + m_nWantedLevel = 5; + m_MaximumLawEnforcerVehicles = 3; + m_MaxCops = 8; + m_RoadblockDensity = 24; + } else if (m_nChaos >= 4800) { + CStats::WantedStarsAttained += 6 - m_nWantedLevel; + m_nWantedLevel = 6; + m_MaximumLawEnforcerVehicles = 3; + m_MaxCops = 10; + m_RoadblockDensity = 30; + } + + 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_nLastTimeSuspended + 20000) { + m_nMinChaos = 0; + m_nMinWantedLevel = 0; + } + 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(); +} + +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) { + 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; + } + } +} + +void +CWanted::Suspend(void) +{ + CStats::WantedStarsEvaded += m_nWantedLevel; + m_nMinChaos = m_nChaos; + m_nMinWantedLevel = m_nWantedLevel; + m_nLastTimeSuspended = CTimer::GetTimeInMilliseconds(); + m_nChaos = 0; + m_nWantedLevel = 0; + ResetPolicePursuit(); +} diff --git a/src/miami/core/Wanted.h b/src/miami/core/Wanted.h new file mode 100644 index 00000000..f2da23e3 --- /dev/null +++ b/src/miami/core/Wanted.h @@ -0,0 +1,65 @@ +#pragma once + +#include "Crime.h" + +class CEntity; +class CCopPed; + +class CWanted +{ +public: + int32 m_nChaos; + int32 m_nMinChaos; + int32 m_nLastUpdateTime; + uint32 m_nLastWantedLevelChange; + uint32 m_nLastTimeSuspended; + 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; + int32 m_nMinWantedLevel; + CCrimeBeingQd m_aCrimes[16]; + CCopPed *m_pCops[10]; + + static int32 MaximumWantedLevel; + static int32 nMaximumWantedLevel; + +public: + void Initialise(); + bool AreMiamiViceRequired(); + bool AreSwatRequired(); + bool AreFbiRequired(); + bool AreArmyRequired(); + int32 NumOfHelisRequired(); + void SetWantedLevel(int32); + void SetWantedLevelNoDrop(int32 level); + int32 GetWantedLevel() { return m_nWantedLevel; } + void CheatWantedLevel(int32 level); + 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(); + + void Suspend(); + + 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/miami/core/World.cpp b/src/miami/core/World.cpp new file mode 100644 index 00000000..549c2cc0 --- /dev/null +++ b/src/miami/core/World.cpp @@ -0,0 +1,2312 @@ +#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 "Pickups.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; +bool CWorld::bIncludeBikers; + +CColPoint CWorld::m_aTempColPts[MAX_COLLISION_POINTS]; + +void +CWorld::Initialise() +{ + pIgnoreEntity = nil; + bDoingCarCollisions = false; + bSecondShift = false; + bNoMoreCollisionTorque = false; + bProcessCutsceneOnly = false; + bIncludeDeadPeds = false; + bForceProcessControl = false; + bIncludeCarTyres = false; + bIncludeBikers = 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(); + } + CPickups::RemoveUnnecessaryPickups(pos, radius); +} + +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, bool ignoreShootThrough) +{ + 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, ignoreShootThrough + + 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, bool ignoreShootThrough) +{ + float mindist = dist; + bool deadPeds = !!bIncludeDeadPeds; + bool bikers = !!bIncludeBikers; + bIncludeDeadPeds = false; + bIncludeBikers = false; + + if(checkBuildings) { + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_BUILDINGS], line, point, mindist, entity, + ignoreSeeThrough, false, ignoreShootThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_BUILDINGS_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, false, ignoreShootThrough); + } + + if(checkVehicles) { + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_VEHICLES], line, point, mindist, entity, + ignoreSeeThrough, false, ignoreShootThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_VEHICLES_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, false, ignoreShootThrough); + } + + if(checkPeds) { + if(deadPeds) bIncludeDeadPeds = true; + if(bikers) bIncludeBikers = true; + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_PEDS], line, point, mindist, entity, + ignoreSeeThrough, false, ignoreShootThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_PEDS_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, false, ignoreShootThrough); + bIncludeDeadPeds = false; + bIncludeBikers = false; + } + + if(checkObjects) { + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_OBJECTS], line, point, mindist, entity, + ignoreSeeThrough, ignoreSomeObjects, ignoreShootThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_OBJECTS_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, ignoreSomeObjects, ignoreShootThrough); + } + + if(checkDummies) { + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_DUMMIES], line, point, mindist, entity, + ignoreSeeThrough, false, ignoreShootThrough); + ProcessLineOfSightSectorList(sector.m_lists[ENTITYLIST_DUMMIES_OVERLAP], line, point, mindist, entity, + ignoreSeeThrough, false, ignoreShootThrough); + } + + bIncludeDeadPeds = deadPeds; + bIncludeBikers = bikers; + + 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 ignoreShootThrough) +{ + bool deadPeds = false; + bool bikers = false; + bool carTyres = false; + float mindist = dist; + CPtrNode *node; + CEntity *e; + CColModel *colmodel; + CColModel tyreCol; + CColSphere tyreSpheres[6]; + CColPoint tyreColPoint; + float tyreDist; + + if(bIncludeCarTyres && list.first && ((CEntity*)list.first->item)->IsVehicle()){ + carTyres = true; + tyreCol.numTriangles = 0; + tyreCol.numBoxes = 0; + tyreCol.numLines = 0; + tyreCol.spheres = tyreSpheres; + tyreCol.numSpheres = ARRAY_SIZE(tyreSpheres); + } + if(list.first && bIncludeDeadPeds && ((CEntity *)list.first->item)->IsPed()) deadPeds = true; + if(list.first && bIncludeBikers && ((CEntity *)list.first->item)->IsPed()) bikers = true; + + for(node = list.first; node; node = node->next) { + e = (CEntity *)node->item; + if(e->m_scanCode != GetCurrentScanCode() && e != pIgnoreEntity && (e->bUsesCollision || deadPeds || bikers) && + !(ignoreSomeObjects && CameraToIgnoreThisObject(e))) { + colmodel = nil; + tyreDist = mindist; + e->m_scanCode = GetCurrentScanCode(); + + if(e->IsPed()) { + if(e->bUsesCollision || deadPeds && ((CPed *)e)->m_nPedState == PED_DEAD || bikers && ((CPed*)e)->InVehicle() && (((CPed*)e)->m_pMyVehicle->IsBike() || ((CPed*)e)->m_pMyVehicle->IsBoat())) { + colmodel = ((CPedModelInfo *)CModelInfo::GetModelInfo(e->GetModelIndex()))->AnimatePedColModelSkinned(e->GetClump()); + } else + colmodel = nil; + + } else if(e->bUsesCollision) + colmodel = CModelInfo::GetColModel(e->GetModelIndex()); + + if(colmodel && CCollision::ProcessLineOfSight(line, e->GetMatrix(), *colmodel, point, mindist, + ignoreSeeThrough, ignoreShootThrough)) + entity = e; + if(carTyres && ((CVehicle*)e)->SetUpWheelColModel(&tyreCol) && CCollision::ProcessLineOfSight(line, e->GetMatrix(), tyreCol, tyreColPoint, tyreDist, false, ignoreShootThrough)){ + float dp1 = DotProduct(line.p1 - line.p0, e->GetRight()); + float dp2 = DotProduct(point.point - e->GetPosition(), e->GetRight()); + if(tyreDist < mindist || dp1 < -0.85f && dp2 > 0.0f || dp1 > 0.85f && dp2 < 0.0f){ + mindist = tyreDist; + point = tyreColPoint; + entity = e; + } + } + } + } + tyreCol.spheres = nil; + + 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); + int secX = GetSectorIndexX(point1.x); + int secY = GetSectorIndexY(point1.y); + secX = Clamp(secX, 0, NUMSECTORS_X-1); + secY = Clamp(secY, 0, NUMSECTORS_Y-1); + return ProcessVerticalLineSector(*GetSector(secX, secY), + 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, false, 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, false)) + 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); + if(maxX >= NUMSECTORS_X) maxX = NUMSECTORS_X - 1; + + int maxY = GetSectorIndexY(centre.y + radius); + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; + + 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); +#ifdef FIX_BUGS + const int32 nEndX = Min(GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1); +#else + const int32 nEndX = Min(GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X); + const int32 nEndY = Min(GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y); +#endif + 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; + CColSphere sphere; + + 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; + sphere.Set(radius, CVector(0.0f, 0.0f, 0.0f)); + OurColModel.spheres = &sphere; + 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))) { + CVector diff = spherePos - e->GetBoundCentre(); + 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); +#ifdef FIX_BUGS + const int32 nEndX = Min(GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y - 1); +#else + const int32 nEndX = Min(GetSectorIndexX(vecSectorEndPos.x), NUMSECTORS_X); + const int32 nEndY = Min(GetSectorIndexY(vecSectorEndPos.y), NUMSECTORS_Y); +#endif + 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); +#ifdef FIX_BUGS + const int32 nEndX = Min(GetSectorIndexX(vecStartPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecStartPos.y), NUMSECTORS_Y - 1); +#else + const int32 nEndX = Min(GetSectorIndexX(vecStartPos.x), NUMSECTORS_X); + const int32 nEndY = Min(GetSectorIndexY(vecStartPos.y), NUMSECTORS_Y); +#endif + 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 CBox &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); +#ifdef FIX_BUGS + const int32 nEndX = Min(GetSectorIndexX(fEndX), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(fEndY), NUMSECTORS_Y - 1); +#else + const int32 nEndX = Min(GetSectorIndexX(fEndX), NUMSECTORS_X); + const int32 nEndY = Min(GetSectorIndexY(fEndY), NUMSECTORS_Y); +#endif + 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 CBox &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); +#ifdef FIX_BUGS + const int32 nEndX = Min(GetSectorIndexX(vecEndPos.x), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(vecEndPos.y), NUMSECTORS_Y - 1); +#else + const int32 nEndX = Min(GetSectorIndexX(vecEndPos.x), NUMSECTORS_X); + const int32 nEndY = Min(GetSectorIndexY(vecEndPos.y), NUMSECTORS_Y); +#endif + 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); +#ifdef FIX_BUGS + const int32 nEndX = Min(GetSectorIndexX(fEndX), NUMSECTORS_X - 1); + const int32 nEndY = Min(GetSectorIndexY(fEndY), NUMSECTORS_Y - 1); +#else + const int32 nEndX = Min(GetSectorIndexX(fEndX), NUMSECTORS_X); + const int32 nEndY = Min(GetSectorIndexY(fEndY), NUMSECTORS_Y); +#endif + 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->bRenderScorched || 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(); +} + +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 (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_FIRE_HYDRANT || modelId == MI_BOLLARDLIGHT || + modelId == MI_PARKTABLE || modelId == MI_PARKINGMETER2 || modelId == MI_TELPOLE02 || + modelId == MI_PARKBENCH || modelId == MI_BARRIER1 || IsTreeModel(modelId) + ) { + CVector& position = pEntity->GetMatrix().GetPosition(); + CColModel* pColModel = pEntity->GetColModel(); + float fBoundingBoxMinZ = pColModel->boundingBox.min.z; + float fHeight = pColModel->boundingBox.max.z - pColModel->boundingBox.min.z; + if (fHeight < OBJECT_REPOSITION_OFFSET_Z) fHeight = OBJECT_REPOSITION_OFFSET_Z; + position.z = FindGroundZFor3DCoord(position.x, position.y, + position.z + fHeight, nil) - + fBoundingBoxMinZ; + pEntity->GetMatrix().UpdateRW(); + pEntity->UpdateRwFrame(); + } else if(IsLightThatNeedsRepositioning(modelId)) { + CVector position = pEntity->GetMatrix().GetPosition(); + CColModel* pColModel = pEntity->GetColModel(); + float fBoundingBoxMinZ = pColModel->boundingBox.min.z; + float fHeight = pColModel->boundingBox.max.z - pColModel->boundingBox.min.z; + if (fHeight < OBJECT_REPOSITION_OFFSET_Z) fHeight = OBJECT_REPOSITION_OFFSET_Z; + if (pColModel->numBoxes == 1) + position = pEntity->GetMatrix() * CVector( + (pColModel->boxes[0].min.x + pColModel->boxes[0].max.x) / 2, + (pColModel->boxes[0].min.y + pColModel->boxes[0].max.y) / 2, + pColModel->boxes[0].min.z); + else if (pColModel->numSpheres > 0) { + position.z = 1000.0f; + for (int i = 0; i < pColModel->numSpheres; i++) { + if (pColModel->spheres[i].center.z < position.z) + position = pColModel->spheres[i].center; + } + if (position.z < 1000.0f) + position = pEntity->GetMatrix() * position; + } + pEntity->GetMatrix().GetPosition().z = FindGroundZFor3DCoord(position.x, position.y, pEntity->GetMatrix().GetPosition().z + fHeight, nil) - fBoundingBoxMinZ; + pEntity->GetMatrix().UpdateRW(); + pEntity->UpdateRwFrame(); + + } + if(modelId == MI_BUOY) { + bool bFound = true; + const CVector &position = pEntity->GetPosition(); + float fGroundZ = FindGroundZFor3DCoord(position.x, position.y, + position.z + OBJECT_REPOSITION_OFFSET_Z, &bFound); + CColModel *pColModel = pEntity->GetColModel(); + float fHeight = pColModel->boundingBox.max.z - pColModel->boundingBox.min.z; + pEntity->GetMatrix().GetPosition().z = 0.2f * fHeight + 6.0f - 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::SetPedsChoking(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); + // suspicious copypaste + if (pPed && pPed->m_nPedState != PED_DEAD && !pPed->bInVehicle && !pPed->m_pFire && !pPed->bFireProof && pPed->CharCreatedBy != MISSION_CHAR) { + if (Abs(pPed->GetPosition().z - z) < 5.0f && Abs(pPed->GetPosition().x - x) < radius && + Abs(pPed->GetPosition().y - y) < radius) { + if (!pPed->IsPlayer()) + pPed->SetFlee(CVector2D(x, y), 10000); +#ifdef FIX_BUGS + pPed->InflictDamage(reason, WEAPONTYPE_TEARGAS, 1.0f, PEDPIECE_TORSO, 0); +#else + pPed->InflictDamage(nil, WEAPONTYPE_TEARGAS, 1.0f, PEDPIECE_TORSO, 0); +#endif + } + } + } +} + +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())) { + if (csObj->IsObject()) + RpAnimBlendClumpUpdateAnimations(csObj->GetClump(), CTimer::GetTimeStepNonClippedInSeconds()); + else { + if (!csObj->bOffscreen) + csObj->bOffscreen = !csObj->GetIsOnScreen(); + RpAnimBlendClumpUpdateAnimations(csObj->GetClump(), CTimer::GetTimeStepInSeconds(), !csObj->bOffscreen); + } + } + 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; + if(!movingEnt->bRemoveFromWorld && movingEnt->m_rwObject && RwObjectGetType(movingEnt->m_rwObject) == rpCLUMP && + RpAnimBlendClumpGetFirstAssociation(movingEnt->GetClump())) { + if (movingEnt->IsObject()) + RpAnimBlendClumpUpdateAnimations(movingEnt->GetClump(), CTimer::GetTimeStepNonClippedInSeconds()); + else { + if (!movingEnt->bOffscreen) + movingEnt->bOffscreen = !movingEnt->GetIsOnScreen(); + RpAnimBlendClumpUpdateAnimations(movingEnt->GetClump(), CTimer::GetTimeStepInSeconds(), !movingEnt->bOffscreen); + } + } + } + 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 *= Pow(0.707f, CTimer::GetTimeStep()); + 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) { +#ifdef GTA_TRAIN + if(movingCar->IsTrain()) { + movingPed->SetPedPositionInTrain(); + } else +#endif + { + 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(); + } + } else if (movingPed->m_attachedTo) { + movingPed->PositionAttachedPed(); + movingPed->GetMatrix().UpdateRW(); + movingPed->UpdateRwFrame(); + } + } + } + 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 && modelId != MI_PETROLPUMP2) + 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) { +#ifdef FIX_BUGS + CVehicle* pVehicle = CPools::GetVehiclePool()->GetSlot(i); +#else + CAutomobile *pVehicle = (CAutomobile *)CPools::GetVehiclePool()->GetSlot(i); +#endif + if(pVehicle && 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); + } + } + CProjectileInfo::RemoveDetonatorProjectiles(); +} + +bool +CWorld::IsWanderPathClear(CVector const& point1, CVector const& point2, float distance, int maxSteps) +{ + if (Abs(point1.z - point2.z) > distance) + return false; + if (!GetIsLineOfSightClear(point1, point2, true, false, false, false, false, false, false)) + return false; + CVector vecBetween = point2 - point1; + uint32 nSteps = Max(vecBetween.Magnitude(), maxSteps); + if (nSteps == 0) + return true; + vecBetween.Normalise(); + uint32 step = 1; + for (step = 1; step < nSteps; step++) { + CVector posThisStep = point1 + vecBetween * step; + float level; + if (!CWaterLevel::GetWaterLevel(posThisStep, &level, false)) + continue; + posThisStep.z = level; + AdvanceCurrentScanCode(); + + CVector vecCheckedPos(posThisStep.x, posThisStep.y, Max(point1.z, point2.z)); + CColPoint colpoint; + CEntity* entity; + if (!ProcessVerticalLineSector(*GetSector(GetSectorIndexX(posThisStep.x), GetSectorIndexY(posThisStep.y)), + CColLine(posThisStep, vecCheckedPos), colpoint, entity, true, false, false, false, false, false, nil)) + return false; + } + + CVector posThisStep = point1; + AdvanceCurrentScanCode(); + CVector vecCheckedPos(posThisStep.x, posThisStep.y, point1.z - 5.0f); + + CColPoint colpoint; + CEntity* entity; + if (!ProcessVerticalLineSector(*GetSector(GetSectorIndexX(posThisStep.x), GetSectorIndexY(posThisStep.y)), + CColLine(posThisStep, vecCheckedPos), colpoint, entity, true, false, false, false, false, false, nil)) + return false; + + float heightNextStep = colpoint.point.z + 0.5f; + for (step = 1; step < nSteps; step++) { + CVector posThisStep = point1 + vecBetween * step; + posThisStep.z = heightNextStep; + AdvanceCurrentScanCode(); + CVector vecCheckedPos(posThisStep.x, posThisStep.y, heightNextStep - 2.0f); + if (!ProcessVerticalLineSector(*GetSector(GetSectorIndexX(posThisStep.x), GetSectorIndexY(posThisStep.y)), + CColLine(posThisStep, vecCheckedPos), colpoint, entity, true, false, false, false, false, false, nil)) + return false; + if (Abs(colpoint.point.z - heightNextStep) > 1.0f) + return false; + heightNextStep = colpoint.point.z + 0.5f; + } + return true; +} diff --git a/src/miami/core/World.h b/src/miami/core/World.h new file mode 100644 index 00000000..81eb5d4c --- /dev/null +++ b/src/miami/core/World.h @@ -0,0 +1,176 @@ +#pragma once + +#include "Game.h" +#include "Lists.h" +#include "PlayerInfo.h" +#include "Collision.h" + +/* Sectors span from -2400 to 1600 in x and -2000 to 2000 y. + * With 80x80 sectors, each is 50x50 units. */ + +#define SECTOR_SIZE_X (50.0f) +#define SECTOR_SIZE_Y (50.0f) + +#define NUMSECTORS_X (80) +#define NUMSECTORS_Y (80) + +#define WORLD_SIZE_X (NUMSECTORS_X * SECTOR_SIZE_X) +#define WORLD_SIZE_Y (NUMSECTORS_Y * SECTOR_SIZE_Y) + +#define WORLD_MIN_X (-2400.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 bool bIncludeBikers; + static CColPoint m_aTempColPts[MAX_COLLISION_POINTS]; + + static void Remove(CEntity *entity); + static void Add(CEntity *entity); + + static CSector *GetSector(int x, int y) { if (x > NUMSECTORS_X - 1 || y > NUMSECTORS_Y - 1) return &ms_aSectors[0][0]; 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, bool ignoreShootThrough = 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, bool ignoreShootThrough); + static bool ProcessLineOfSightSectorList(CPtrList &list, const CColLine &line, CColPoint &point, float &dist, CEntity *&entity, bool ignoreSeeThrough, bool ignoreSomeObjects, bool ignoreShootThrough); + 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 CBox &, const CMatrix &, const CVector &, float, float, float, float, int16*, int16, CEntity **, bool, bool, bool, bool, bool); + static void FindObjectsIntersectingAngledCollisionBoxSectorList(CPtrList& list, const CBox& 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 bool IsWanderPathClear(CVector const&, CVector const&, float, int); + + 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 SetPedsChoking(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); + + // NB: following functions are unused (TODO?) + static void CastShadow(float, float, float, float); + static void CastShadowSectorList(CPtrList&, float, float, float, float); + static void FindLowestZForCoord(float, float); + static void CheckBlockListIntegrity(void); + static void ProcessVerticalLineSectorList_FillGlobeColPoints(CPtrList&, const CColLine&, CEntity*&, bool, CStoredCollPoly*); + static void ProcessVerticalLineSector_FillGlobeColPoints(CSector&, const CColLine&, CEntity*&, bool, bool, bool, bool, bool, bool, CStoredCollPoly*); + static void ProcessVerticalLine_FillGlobeColPoints(const CVector&, float, CEntity*&, bool, bool, bool, bool, bool, bool, CStoredCollPoly*); + static void PrintCarChanges(void); + static void TestForBuildingsOnTopOfEachOther(CPtrList&); + static void TestForBuildingsOnTopOfEachOther(void); + static void TestForUnusedModels(CPtrList&, int*); + static void TestForUnusedModels(void); + static void HandleCollisionZoneChange(eLevelName, eLevelName); + static void DoZoneTestForChaser(class CPhysical*); + static void FindPlayerSlotWithPedPointer(void*); +}; + +extern CColPoint gaTempSphereColPoints[MAX_COLLISION_POINTS]; diff --git a/src/miami/core/ZoneCull.cpp b/src/miami/core/ZoneCull.cpp new file mode 100644 index 00000000..db3577ad --- /dev/null +++ b/src/miami/core/ZoneCull.cpp @@ -0,0 +1,172 @@ +#include "common.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" + +int32 CCullZones::NumAttributeZones; +CAttributeZone CCullZones::aAttributeZones[NUMATTRIBZONES]; + +int32 CCullZones::CurrentWantedLevelDrop_Player; +int32 CCullZones::CurrentFlags_Camera; +int32 CCullZones::CurrentFlags_Player; +bool CCullZones::bCurrentSubwayIsInvisible; +bool CCullZones::bAtBeachForAudio; + +void +CCullZones::Init(void) +{ + NumAttributeZones = 0; + CurrentWantedLevelDrop_Player = 0; + CurrentFlags_Camera = 0; + CurrentFlags_Player = 0; + bCurrentSubwayIsInvisible = false; +} + +void +CCullZones::Update(void) +{ + bool invisible; + + switch(CTimer::GetFrameCounter() & 7){ + case 0: + case 4: + UpdateAtBeachForAudio(); + 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; + } +} + +// TODO? put somewhere else? +bool +IsPointWithinArbitraryArea(float px, float py, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) +{ + if((px-x1)*(x2-x1) - (py-y1)*(y2-y1) < 0.0f) return false; + if((px-x2)*(x3-x2) - (py-y2)*(y3-y2) < 0.0f) return false; + if((px-x3)*(x4-x3) - (py-y3)*(y4-y3) < 0.0f) return false; + if((px-x4)*(x1-x4) - (py-y4)*(y1-y4) < 0.0f) return false; + return true; +} + +void +CCullZones::UpdateAtBeachForAudio(void) +{ + bAtBeachForAudio = IsPointWithinArbitraryArea(TheCamera.GetPosition().x, TheCamera.GetPosition().y, + 400.0f, -1644.4f, + 751.9f, 1267.8f, + 971.9f, 1216.2f, + 840.0f, -1744.0f); +} + +void +CCullZones::ForceCullZoneCoors(CVector coors) +{ +} + +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) +{ + CAttributeZone *attrib; + + assert(NumAttributeZones < NUMATTRIBZONES); + 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; +} diff --git a/src/miami/core/ZoneCull.h b/src/miami/core/ZoneCull.h new file mode 100644 index 00000000..d7780caf --- /dev/null +++ b/src/miami/core/ZoneCull.h @@ -0,0 +1,65 @@ +class CEntity; + +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, + ATTRZONE_POLICEABANDONCARS = 0x100, + ATTRZONE_ROOMFORAUDIO = 0x200, + ATTRZONE_WATERFUDGE = 0x400, +}; + +struct CAttributeZone +{ + int16 minx; + int16 maxx; + int16 miny; + int16 maxy; + int16 minz; + int16 maxz; + int16 attributes; + int16 wantedLevel; +}; + +class CCullZones +{ +public: + static int32 NumAttributeZones; + static CAttributeZone aAttributeZones[NUMATTRIBZONES]; + + static int32 CurrentWantedLevelDrop_Player; + static int32 CurrentFlags_Camera; + static int32 CurrentFlags_Player; + static bool bCurrentSubwayIsInvisible; + static bool bAtBeachForAudio; + + static void Init(void); + static void Update(void); + static void UpdateAtBeachForAudio(void); + static void ForceCullZoneCoors(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 bool PoliceAbandonCars(void) { return (CurrentFlags_Camera & ATTRZONE_POLICEABANDONCARS) != 0; } + static bool InRoomForAudio(void) { return (CurrentFlags_Camera & ATTRZONE_ROOMFORAUDIO) != 0; } + static bool WaterFudge(void) { return (CurrentFlags_Camera & ATTRZONE_WATERFUDGE) != 0; } + static int32 GetWantedLevelDrop(void) { return CurrentWantedLevelDrop_Player; } +}; diff --git a/src/miami/core/Zones.cpp b/src/miami/core/Zones.cpp new file mode 100644 index 00000000..93eca199 --- /dev/null +++ b/src/miami/core/Zones.cpp @@ -0,0 +1,800 @@ +#include "common.h" + +#include + +#include "Zones.h" + +#include "Clock.h" +#include "Text.h" +#include "World.h" +#include "Timer.h" +#include "SaveBuf.h" + +eLevelName CTheZones::m_CurrLevel; +int16 CTheZones::FindIndex; + +uint16 CTheZones::NumberOfAudioZones; +int16 CTheZones::AudioZoneArray[NUMAUDIOZONES]; +uint16 CTheZones::TotalNumberOfMapZones; +uint16 CTheZones::TotalNumberOfInfoZones; +uint16 CTheZones::TotalNumberOfNavigationZones; +CZone CTheZones::InfoZoneArray[NUMINFOZONES]; +CZone CTheZones::MapZoneArray[NUMMAPZONES]; +CZone CTheZones::NavigationZoneArray[NUMNAVIGZONES]; +uint16 CTheZones::TotalNumberOfZoneInfos; +CZoneInfo CTheZones::ZoneInfoArray[2*NUMINFOZONES]; + + +#define SWAPF(a, b) { float t; t = a; a = b; b = t; } + +wchar* +CZone::GetTranslatedName(void) +{ + return TheText.Get(name); +} + +void +CTheZones::Init(void) +{ + int i, j; + for(i = 0; i < NUMAUDIOZONES; i++) + AudioZoneArray[i] = -1; + NumberOfAudioZones = 0; + + for(i = 0; i < NUMNAVIGZONES; i++) + memset(&NavigationZoneArray[i], 0, sizeof(CZone)); + + for(i = 0; i < NUMINFOZONES; i++) + memset(&InfoZoneArray[i], 0, sizeof(CZone)); + + int x = 1000/9; + for(i = 0; i < 2*NUMINFOZONES; i++){ + // Cars + + ZoneInfoArray[i].carDensity = 10; + ZoneInfoArray[i].carThreshold[0] = x; + ZoneInfoArray[i].carThreshold[1] = ZoneInfoArray[i].carThreshold[0] + x; + ZoneInfoArray[i].carThreshold[2] = ZoneInfoArray[i].carThreshold[1] + x; + ZoneInfoArray[i].carThreshold[3] = ZoneInfoArray[i].carThreshold[2] + x; + ZoneInfoArray[i].carThreshold[4] = ZoneInfoArray[i].carThreshold[3] + x; + ZoneInfoArray[i].carThreshold[5] = ZoneInfoArray[i].carThreshold[4] + x; + ZoneInfoArray[i].carThreshold[6] = ZoneInfoArray[i].carThreshold[5] + x; + ZoneInfoArray[i].carThreshold[7] = ZoneInfoArray[i].carThreshold[6] + x; + ZoneInfoArray[i].carThreshold[8] = 1000; + + ZoneInfoArray[i].boatThreshold[0] = 500; + ZoneInfoArray[i].boatThreshold[1] = 1000; + + // What's going on here? this looks more like density + ZoneInfoArray[i].copThreshold = 50; + for(j = 0; j < NUM_GANGS; j++) + ZoneInfoArray[i].gangThreshold[j] = ZoneInfoArray[i].copThreshold; + + // Peds + + ZoneInfoArray[i].pedDensity = 12; + + // What's going on here? this looks more like density + ZoneInfoArray[i].copPedThreshold = 50; + for(j = 0; j < NUM_GANGS; j++) + ZoneInfoArray[i].gangPedThreshold[j] = ZoneInfoArray[i].copPedThreshold; + + ZoneInfoArray[i].pedGroup = 0; + } + + TotalNumberOfZoneInfos = 1; // why 1? + TotalNumberOfNavigationZones = 1; + TotalNumberOfInfoZones = 1; + + strcpy(InfoZoneArray[0].name, "CITYINF"); + InfoZoneArray[0].minx = -2400.0f; + InfoZoneArray[0].miny = -2000.0f; + InfoZoneArray[0].minz = -500.0f; + InfoZoneArray[0].maxx = 1600.0f; + InfoZoneArray[0].maxy = 2000.0f; + InfoZoneArray[0].maxz = 500.0f; + InfoZoneArray[0].level = LEVEL_GENERIC; + InfoZoneArray[0].type = ZONE_INFO; + + strcpy(NavigationZoneArray[0].name, "VICE_C"); + NavigationZoneArray[0].minx = -2400.0f; + NavigationZoneArray[0].miny = -2000.0f; + NavigationZoneArray[0].minz = -500.0f; + NavigationZoneArray[0].maxx = 1600.0f; + NavigationZoneArray[0].maxy = 2000.0f; + NavigationZoneArray[0].maxz = 500.0f; + NavigationZoneArray[0].level = LEVEL_GENERIC; + NavigationZoneArray[0].type = ZONE_DEFAULT; + + m_CurrLevel = 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 = -2400.0f; + MapZoneArray[0].miny = -2000.0f; + MapZoneArray[0].minz = -500.0f; + MapZoneArray[0].maxx = 1600.0f; + MapZoneArray[0].maxy = 2000.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_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 tmpname[24]; + 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); + + strncpy(tmpname, name, 7); + tmpname[7] = '\0'; + + // add zone + switch(type){ + case ZONE_DEFAULT: + case ZONE_NAVIG: + assert(TotalNumberOfNavigationZones < NUMNAVIGZONES); + strcpy(NavigationZoneArray[TotalNumberOfNavigationZones].name, tmpname); + NavigationZoneArray[TotalNumberOfNavigationZones].type = type; + NavigationZoneArray[TotalNumberOfNavigationZones].minx = minx; + NavigationZoneArray[TotalNumberOfNavigationZones].miny = miny; + NavigationZoneArray[TotalNumberOfNavigationZones].minz = minz; + NavigationZoneArray[TotalNumberOfNavigationZones].maxx = maxx; + NavigationZoneArray[TotalNumberOfNavigationZones].maxy = maxy; + NavigationZoneArray[TotalNumberOfNavigationZones].maxz = maxz; + NavigationZoneArray[TotalNumberOfNavigationZones].level = level; + TotalNumberOfNavigationZones++; + break; + case ZONE_INFO: + assert(TotalNumberOfInfoZones < NUMINFOZONES); + strcpy(InfoZoneArray[TotalNumberOfInfoZones].name, tmpname); + InfoZoneArray[TotalNumberOfInfoZones].type = type; + InfoZoneArray[TotalNumberOfInfoZones].minx = minx; + InfoZoneArray[TotalNumberOfInfoZones].miny = miny; + InfoZoneArray[TotalNumberOfInfoZones].minz = minz; + InfoZoneArray[TotalNumberOfInfoZones].maxx = maxx; + InfoZoneArray[TotalNumberOfInfoZones].maxy = maxy; + InfoZoneArray[TotalNumberOfInfoZones].maxz = maxz; + InfoZoneArray[TotalNumberOfInfoZones].level = level; + InfoZoneArray[TotalNumberOfInfoZones].zoneinfoDay = TotalNumberOfZoneInfos++; + InfoZoneArray[TotalNumberOfInfoZones].zoneinfoNight = TotalNumberOfZoneInfos++; + TotalNumberOfInfoZones++; + break; + case ZONE_MAPZONE: + assert(TotalNumberOfMapZones < NUMMAPZONES); + strcpy(MapZoneArray[TotalNumberOfMapZones].name, tmpname); + MapZoneArray[TotalNumberOfMapZones].type = type; + MapZoneArray[TotalNumberOfMapZones].minx = minx; + MapZoneArray[TotalNumberOfMapZones].miny = miny; + MapZoneArray[TotalNumberOfMapZones].minz = minz; + MapZoneArray[TotalNumberOfMapZones].maxx = maxx; + MapZoneArray[TotalNumberOfMapZones].maxy = maxy; + MapZoneArray[TotalNumberOfMapZones].maxz = maxz; + MapZoneArray[TotalNumberOfMapZones].level = level; + TotalNumberOfMapZones++; + break; + } +} + +void +CTheZones::PostZoneCreation(void) +{ + int i; + for(i = 1; i < TotalNumberOfNavigationZones; i++) + InsertZoneIntoZoneHierarchy(&NavigationZoneArray[i]); + InitialiseAudioZoneArray(); +#ifndef MASTER + CheckZonesForOverlap(); +#endif +} + +void +CTheZones::CheckZonesForOverlap(void) +{ + int i, j; + char str[116]; + + for(i = 1; i < TotalNumberOfInfoZones; i++){ + ZoneIsEntirelyContainedWithinOtherZone(&InfoZoneArray[i], &InfoZoneArray[0]); + + for(j = 1; j < TotalNumberOfInfoZones; j++) + if(i != j && ZoneIsEntirelyContainedWithinOtherZone(&InfoZoneArray[i], &InfoZoneArray[j])) + sprintf(str, "Info zone %s contains %s\n", InfoZoneArray[j].name, InfoZoneArray[i].name); + } +} + +void +CTheZones::InsertZoneIntoZoneHierarchy(CZone *zone) +{ + zone->child = nil; + zone->parent = nil; + zone->next = nil; + InsertZoneIntoZoneHierRecursive(zone, &NavigationZoneArray[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::FindInformationZoneForPosition(const CVector *v) +{ + int i; +// char tmp[116]; +// if(!PointLiesWithinZone(v, &InfoZoneArray[0])) +// sprintf(tmp, "x = %.3f y= %.3f z = %.3f\n", v.x, v.y, v.z); + for(i = 1; i < TotalNumberOfInfoZones; i++) + if(PointLiesWithinZone(v, &InfoZoneArray[i])) + return &InfoZoneArray[i]; + return &InfoZoneArray[0]; +} + +CZone* +CTheZones::FindSmallestNavigationZoneForPosition(const CVector *v, bool findDefault, bool findNavig) +{ + CZone *best = nil; + if(findDefault && NavigationZoneArray[0].type == ZONE_DEFAULT || + findNavig && NavigationZoneArray[0].type == ZONE_NAVIG) + best = &NavigationZoneArray[0]; + // zone to test next + CZone *zone = NavigationZoneArray[0].child; + while(zone) + // if in zone, descent into children + if(PointLiesWithinZone(v, zone)){ + if(findDefault && zone->type == ZONE_DEFAULT || + findNavig && zone->type == ZONE_NAVIG) + best = zone; + zone = zone->child; + // otherwise try next zone + }else + zone = zone->next; + return best; +} + +int16 +CTheZones::FindZoneByLabelAndReturnIndex(char *name, eZoneType type) +{ + char str[8]; + memset(str, 0, 8); + strncpy(str, name, 8); + switch(type){ + case ZONE_DEFAULT: + case ZONE_NAVIG: + for(FindIndex = 0; FindIndex < TotalNumberOfNavigationZones; FindIndex++) + if(strcmp(GetNavigationZone(FindIndex)->name, name) == 0) + return FindIndex; + break; + + case ZONE_INFO: + for(FindIndex = 0; FindIndex < TotalNumberOfInfoZones; FindIndex++) + if(strcmp(GetInfoZone(FindIndex)->name, name) == 0) + return FindIndex; + break; + + case ZONE_MAPZONE: + for(FindIndex = 0; FindIndex < TotalNumberOfMapZones; FindIndex++) + if(strcmp(GetMapZone(FindIndex)->name, name) == 0) + return FindIndex; + break; + } + return -1; +} + +int16 +CTheZones::FindNextZoneByLabelAndReturnIndex(char *name, eZoneType type) +{ + char str[8]; + ++FindIndex; + memset(str, 0, 8); + strncpy(str, name, 8); + switch(type){ + case ZONE_DEFAULT: + case ZONE_NAVIG: + for(; FindIndex < TotalNumberOfNavigationZones; FindIndex++) + if(strcmp(GetNavigationZone(FindIndex)->name, name) == 0) + return FindIndex; + break; + + case ZONE_INFO: + for(; FindIndex < TotalNumberOfInfoZones; FindIndex++) + if(strcmp(GetInfoZone(FindIndex)->name, name) == 0) + return FindIndex; + break; + + case ZONE_MAPZONE: + for(; FindIndex < TotalNumberOfMapZones; FindIndex++) + if(strcmp(GetMapZone(FindIndex)->name, name) == 0) + return FindIndex; + break; + } + return -1; +} + +CZoneInfo* +CTheZones::GetZoneInfo(const CVector *v, uint8 day) +{ + CZone *zone; + zone = FindInformationZoneForPosition(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; + int i; + + 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; + } +#ifdef FIX_BUGS + info->carDensity = day->carDensity * d + night->carDensity * n; + for(i = 0; i < ARRAY_SIZE(info->carThreshold); i++) + info->carThreshold[i] = day->carThreshold[i] * d + night->carThreshold[i] * n; + for(i = 0; i < ARRAY_SIZE(info->boatThreshold); i++) + info->boatThreshold[i] = day->boatThreshold[i] * d + night->boatThreshold[i] * n; + for(i = 0; i < ARRAY_SIZE(info->gangThreshold); i++) + info->gangThreshold[i] = day->gangThreshold[i] * d + night->gangThreshold[i] * n; + + info->copThreshold = day->copThreshold * d + night->copThreshold * n; + info->pedDensity = day->pedDensity * d + night->pedDensity * n; + info->copPedThreshold = day->copPedThreshold * d + night->copPedThreshold * n; + for(i = 0; i < ARRAY_SIZE(info->gangPedThreshold); i++) + info->gangPedThreshold[i] = day->gangPedThreshold[i] * d + night->gangPedThreshold[i] * n; +#else + // This is a complete mess. + info->carDensity = day->carDensity * n + night->carDensity * d; + for(i = 0; i < ARRAY_SIZE(info->carThreshold); i++) + info->carThreshold[i] = night->carThreshold[i] * d + night->carThreshold[i] * n; + for(i = 0; i < ARRAY_SIZE(info->boatThreshold); i++) + info->boatThreshold[i] = night->boatThreshold[i] * d + night->boatThreshold[i] * n; + for(i = 0; i < ARRAY_SIZE(info->gangThreshold); i++) + info->gangThreshold[i] = night->gangThreshold[i] * d + night->gangThreshold[i] * n; + + info->copThreshold = night->copThreshold * d + night->copThreshold * n; + info->pedDensity = night->pedDensity * d + night->pedDensity * n; + info->copPedThreshold = night->copPedThreshold * d + night->copPedThreshold * n; + for(i = 0; i < ARRAY_SIZE(info->gangPedThreshold); i++) + info->gangPedThreshold[i] = night->gangPedThreshold[i] * d + night->gangPedThreshold[i] * n; +#endif + } + if(CClock::GetIsTimeInRange(5, 19)) + info->pedGroup = day->pedGroup; + else + info->pedGroup = night->pedGroup; +} + +void +CTheZones::SetZoneCarInfo(uint16 zoneid, uint8 day, int16 carDensity, + int16 copCarDensity, const int16 *gangCarDensities) +{ + CZone *zone; + CZoneInfo *info; + zone = GetInfoZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + + info->carDensity = carDensity; + info->copThreshold = copCarDensity; + info->gangThreshold[0] = gangCarDensities[0] + copCarDensity; + info->gangThreshold[1] = gangCarDensities[1] + info->gangThreshold[0]; + info->gangThreshold[2] = gangCarDensities[2] + info->gangThreshold[1]; + info->gangThreshold[3] = gangCarDensities[3] + info->gangThreshold[2]; + info->gangThreshold[4] = gangCarDensities[4] + info->gangThreshold[3]; + info->gangThreshold[5] = gangCarDensities[5] + info->gangThreshold[4]; + info->gangThreshold[6] = gangCarDensities[6] + info->gangThreshold[5]; + info->gangThreshold[7] = gangCarDensities[7] + info->gangThreshold[6]; + info->gangThreshold[8] = gangCarDensities[8] + info->gangThreshold[7]; +} + +void CTheZones::SetZoneCivilianCarInfo(uint16 zoneid, uint8 day, + const int16* carDensities, const int16* boatDensities) +{ + CZone* zone; + CZoneInfo* info; + zone = GetInfoZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + info->carThreshold[0] = carDensities[0]; + for (int i = 1; i < CCarCtrl::NUM_CAR_CLASSES; i++) + info->carThreshold[i] = carDensities[i] + info->carThreshold[i-1]; + info->boatThreshold[0] = boatDensities[0]; + for (int i = 1; i < CCarCtrl::NUM_BOAT_CLASSES; i++) + info->boatThreshold[i] = boatDensities[i] + info->boatThreshold[i - 1]; +} + +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 = GetInfoZone(zoneid); + info = &ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight]; + info->pedDensity = pedDensity; + info->copPedThreshold = copDensity; + info->gangPedThreshold[0] = gang0Density; + info->gangPedThreshold[1] = gang1Density; + info->gangPedThreshold[2] = gang2Density; + info->gangPedThreshold[3] = gang3Density; + info->gangPedThreshold[4] = gang4Density; + info->gangPedThreshold[5] = gang5Density; + info->gangPedThreshold[6] = gang6Density; + info->gangPedThreshold[7] = gang7Density; + info->gangPedThreshold[8] = gang8Density; + + info->gangPedThreshold[0] += info->copPedThreshold; + info->gangPedThreshold[1] += info->gangPedThreshold[0]; + info->gangPedThreshold[2] += info->gangPedThreshold[1]; + info->gangPedThreshold[3] += info->gangPedThreshold[2]; + info->gangPedThreshold[4] += info->gangPedThreshold[3]; + info->gangPedThreshold[5] += info->gangPedThreshold[4]; + info->gangPedThreshold[6] += info->gangPedThreshold[5]; + info->gangPedThreshold[7] += info->gangPedThreshold[6]; + info->gangPedThreshold[8] += info->gangPedThreshold[7]; +} + +// unused +void +CTheZones::SetCarDensity(uint16 zoneid, uint8 day, uint16 cardensity) +{ + CZone *zone; + zone = GetInfoZone(zoneid); + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].carDensity = cardensity; +} + +// unused +void +CTheZones::SetPedDensity(uint16 zoneid, uint8 day, uint16 peddensity) +{ + CZone *zone; + zone = GetInfoZone(zoneid); + ZoneInfoArray[day ? zone->zoneinfoDay : zone->zoneinfoNight].pedDensity = peddensity; +} + +void +CTheZones::SetPedGroup(uint16 zoneid, uint8 day, uint16 pedgroup) +{ + CZone *zone; + zone = GetInfoZone(zoneid); + 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; +} + +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 < NUMNAVIGZONES; i++) + if(&NavigationZoneArray[i] == zone) + z = i; + assert(NumberOfAudioZones < NUMAUDIOZONES); + AudioZoneArray[NumberOfAudioZones++] = z; +} + +void +CTheZones::InitialiseAudioZoneArray(void) +{ + bool gonext; + CZone *zone; + + gonext = false; + zone = &NavigationZoneArray[0]; + // Go deep first, + // set gonext when backing up a level to visit the next child + while(zone) + if(gonext){ + AddZoneToAudioZoneArray(zone); + if(zone->next){ + gonext = false; + zone = zone->next; + }else + zone = zone->parent; + }else if(zone->child) + zone = zone->child; + else{ + AddZoneToAudioZoneArray(zone); + if(zone->next) + zone = zone->next; + else{ + gonext = true; + zone = zone->parent; + } + } +} + +void +CTheZones::SaveAllZones(uint8 *buffer, uint32 *size) +{ + INITSAVEBUF + int i; + +#define CZONE_SAVE_SIZE (sizeof(char)*8+sizeof(float)+sizeof(float)+sizeof(float)+sizeof(float)+sizeof(float)+sizeof(float)+sizeof(eZoneType)+sizeof(eLevelName)+sizeof(int16)+sizeof(int16)+sizeof(int32)+sizeof(int32)+sizeof(int32)) + + *size = SAVE_HEADER_SIZE + + sizeof(m_CurrLevel) + sizeof(FindIndex) + + sizeof(int16) // padding + + CZONE_SAVE_SIZE * ARRAY_SIZE(NavigationZoneArray) + CZONE_SAVE_SIZE * ARRAY_SIZE(InfoZoneArray) + sizeof(ZoneInfoArray) + + sizeof(TotalNumberOfNavigationZones) + sizeof(TotalNumberOfInfoZones) + sizeof(TotalNumberOfZoneInfos) + + sizeof(int16) // padding + + CZONE_SAVE_SIZE * ARRAY_SIZE(MapZoneArray) + sizeof(AudioZoneArray) + + sizeof(TotalNumberOfMapZones) + sizeof(NumberOfAudioZones); +#undef CZONE_SAVE_SIZE + + uint32 length = 0; + WriteSaveHeaderWithLength(buffer, length, 'Z', 'N', 'S', '\0', *size - SAVE_HEADER_SIZE); + + WriteSaveBuf(buffer, length, m_CurrLevel); + WriteSaveBuf(buffer, length, FindIndex); + WriteSaveBuf(buffer, length, (int16)0); // padding + + for(i = 0; i < ARRAY_SIZE(NavigationZoneArray); i++) + SaveOneZone(&NavigationZoneArray[i], &buffer, &length, ZONE_NAVIG); + + for(i = 0; i < ARRAY_SIZE(InfoZoneArray); i++) + SaveOneZone(&InfoZoneArray[i], &buffer, &length, ZONE_INFO); + + for(i = 0; i < ARRAY_SIZE(ZoneInfoArray); i++) + WriteSaveBuf(buffer, length, ZoneInfoArray[i]); + + WriteSaveBuf(buffer, length, TotalNumberOfNavigationZones); + WriteSaveBuf(buffer, length, TotalNumberOfInfoZones); + WriteSaveBuf(buffer, length, TotalNumberOfZoneInfos); + WriteSaveBuf(buffer, length, (int16)0); // padding + + for(i = 0; i < ARRAY_SIZE(MapZoneArray); i++) + SaveOneZone(&MapZoneArray[i], &buffer, &length, ZONE_MAPZONE); + + for(i = 0; i < ARRAY_SIZE(AudioZoneArray); i++) + WriteSaveBuf(buffer, length, AudioZoneArray[i]); + + WriteSaveBuf(buffer, length, TotalNumberOfMapZones); + WriteSaveBuf(buffer, length, NumberOfAudioZones); + + VALIDATESAVEBUF(*size) +} + +void +CTheZones::SaveOneZone(CZone *zone, uint8 **buffer, uint32 *length, eZoneType zoneType) +{ + WriteSaveBuf(*buffer, *length, *(uint32*)&zone->name[0]); + WriteSaveBuf(*buffer, *length, *(uint32*)&zone->name[4]); + + WriteSaveBuf(*buffer, *length, zone->minx); + WriteSaveBuf(*buffer, *length, zone->miny); + WriteSaveBuf(*buffer, *length, zone->minz); + WriteSaveBuf(*buffer, *length, zone->maxx); + WriteSaveBuf(*buffer, *length, zone->maxy); + WriteSaveBuf(*buffer, *length, zone->maxz); + + WriteSaveBuf(*buffer, *length, zone->type); + WriteSaveBuf(*buffer, *length, zone->level); + WriteSaveBuf(*buffer, *length, zone->zoneinfoDay); + WriteSaveBuf(*buffer, *length, zone->zoneinfoNight); + + int32 zoneId; + zoneId = GetIndexForNavigationZonePointer(zone->child); + WriteSaveBuf(*buffer, *length, zoneId); + zoneId = GetIndexForNavigationZonePointer(zone->parent); + WriteSaveBuf(*buffer, *length, zoneId); + zoneId = GetIndexForNavigationZonePointer(zone->next); + WriteSaveBuf(*buffer, *length, zoneId); +} + +void +CTheZones::LoadAllZones(uint8 *buffer, uint32 size) +{ + INITSAVEBUF + int i; + + uint32 length = 0; + CheckSaveHeaderWithLength(buffer, length, 'Z', 'N', 'S', '\0', size - SAVE_HEADER_SIZE); + + ReadSaveBuf(&m_CurrLevel, buffer); + ReadSaveBuf(&FindIndex, buffer); + SkipSaveBuf(buffer, 2); + + for(i = 0; i < ARRAY_SIZE(NavigationZoneArray); i++) + LoadOneZone(&NavigationZoneArray[i], &buffer, &length, ZONE_NAVIG); + + for (i = 0; i < ARRAY_SIZE(InfoZoneArray); i++) + LoadOneZone(&InfoZoneArray[i], &buffer, &length, ZONE_INFO); + + for(i = 0; i < ARRAY_SIZE(ZoneInfoArray); i++) + ReadSaveBuf(&ZoneInfoArray[i], buffer); + + ReadSaveBuf(&TotalNumberOfNavigationZones, buffer); + ReadSaveBuf(&TotalNumberOfInfoZones, buffer); + ReadSaveBuf(&TotalNumberOfZoneInfos, buffer); + SkipSaveBuf(buffer, 2); + + for(i = 0; i < ARRAY_SIZE(MapZoneArray); i++) + LoadOneZone(&MapZoneArray[i], &buffer, &length, ZONE_MAPZONE); + + for(i = 0; i < ARRAY_SIZE(AudioZoneArray); i++) + ReadSaveBuf(&AudioZoneArray[i], buffer); + + ReadSaveBuf(&TotalNumberOfMapZones, buffer); + ReadSaveBuf(&NumberOfAudioZones, buffer); + + VALIDATESAVEBUF(size) +} + +void +CTheZones::LoadOneZone(CZone *zone, uint8 **buffer, uint32 *length, eZoneType zoneType) +{ +#ifdef THIS_IS_STUPID + uint32 part1, part2; + ReadSaveBuf(&part1, *buffer, *length); + ReadSaveBuf(&part2, *buffer, *length); + + *(uint64 *)&zone->name[0] = (uint64)part2; + *(uint64 *)&zone->name[0] <<= 32; + *(uint64 *)&zone->name[0] |= (uint64)part1; +#else + for(int i = 0; i < sizeof(zone->name); i++) + ReadSaveBuf(&zone->name[i], *buffer, *length); +#endif + + ReadSaveBuf(&zone->minx, *buffer, *length); + ReadSaveBuf(&zone->miny, *buffer, *length); + ReadSaveBuf(&zone->minz, *buffer, *length); + ReadSaveBuf(&zone->maxx, *buffer, *length); + ReadSaveBuf(&zone->maxy, *buffer, *length); + ReadSaveBuf(&zone->maxz, *buffer, *length); + + ReadSaveBuf(&zone->type, *buffer, *length); + ReadSaveBuf(&zone->level, *buffer, *length); + ReadSaveBuf(&zone->zoneinfoDay, *buffer, *length); + ReadSaveBuf(&zone->zoneinfoNight, *buffer, *length); + + int32 zoneId; + ReadSaveBuf(&zoneId, *buffer, *length); + zone->child = GetPointerForNavigationZoneIndex(zoneId); + ReadSaveBuf(&zoneId, *buffer, *length); + zone->parent = GetPointerForNavigationZoneIndex(zoneId); + ReadSaveBuf(&zoneId, *buffer, *length); + zone->next = GetPointerForNavigationZoneIndex(zoneId); +} \ No newline at end of file diff --git a/src/miami/core/Zones.h b/src/miami/core/Zones.h new file mode 100644 index 00000000..2316eeef --- /dev/null +++ b/src/miami/core/Zones.h @@ -0,0 +1,114 @@ +#pragma once + +#include "Game.h" +#include "Gangs.h" +#include "CarCtrl.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[CCarCtrl::NUM_CAR_CLASSES]; + int16 boatThreshold[CCarCtrl::NUM_BOAT_CLASSES]; + int16 gangThreshold[NUM_GANGS]; + int16 copThreshold; + + // Ped data + uint16 pedDensity; + uint16 gangPedThreshold[NUM_GANGS]; + uint16 copPedThreshold; + uint16 pedGroup; +}; + + +class CTheZones +{ + static int16 FindIndex; + + static uint16 NumberOfAudioZones; + static int16 AudioZoneArray[NUMAUDIOZONES]; + static uint16 TotalNumberOfMapZones; + static uint16 TotalNumberOfInfoZones; + static uint16 TotalNumberOfNavigationZones; + static CZone InfoZoneArray[NUMINFOZONES]; + static CZone MapZoneArray[NUMMAPZONES]; + static CZone NavigationZoneArray[NUMNAVIGZONES]; + static uint16 TotalNumberOfZoneInfos; + static CZoneInfo ZoneInfoArray[2*NUMINFOZONES]; +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 CZone *GetInfoZone(uint16 i) { return &InfoZoneArray[i]; } + static CZone *GetNavigationZone(uint16 i) { return &NavigationZoneArray[i]; } + static CZone *GetMapZone(uint16 i) { return &MapZoneArray[i]; } + static CZone *GetAudioZone(uint16 i) { return &NavigationZoneArray[AudioZoneArray[i]]; } + static void PostZoneCreation(void); + static void CheckZonesForOverlap(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 *FindInformationZoneForPosition(const CVector *v); + static CZone *FindSmallestNavigationZoneForPosition(const CVector *v, bool findDefault, bool findNavig); + static int16 FindZoneByLabelAndReturnIndex(char *name, eZoneType type); + static int16 FindNextZoneByLabelAndReturnIndex(char *name, eZoneType type); + 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 copCarDensity, const int16 *gangCarDensities /*[NUMGANGS]*/); + static void SetZoneCivilianCarInfo(uint16 zoneid, uint8 day, + const int16* carDensities, const int16* boatDensities); + 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 CZone *GetPointerForNavigationZoneIndex(ssize_t i) { return i == -1 ? nil : &NavigationZoneArray[i]; } + static ssize_t GetIndexForNavigationZonePointer(CZone *zone) { return zone == nil ? -1 : zone - NavigationZoneArray; } + static void AddZoneToAudioZoneArray(CZone *zone); + static void InitialiseAudioZoneArray(void); + static void SaveAllZones(uint8 *buffer, uint32 *length); + static void SaveOneZone(CZone *zone, uint8 **buffer, uint32 *length, eZoneType zoneType); + static void LoadAllZones(uint8 *buffer, uint32 length); + static void LoadOneZone(CZone *zone, uint8 **buffer, uint32 *length, eZoneType zoneType); +}; diff --git a/src/miami/core/common.h b/src/miami/core/common.h new file mode 100644 index 00000000..63b63122 --- /dev/null +++ b/src/miami/core/common.h @@ -0,0 +1,420 @@ +#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 + +#ifndef RW_DC +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; +#endif +#ifdef DC_SIM +#include "dc_hle_types.h" +#endif +typedef uintptr_t uintptr; +typedef intptr_t intptr; +// 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" + +#include +#include + +#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); +extern int strncasecmp(const char *str1, const char *str2, size_t len); +#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 + +#ifndef WITH_LOGGING +#define printf(...) +#define perror(...) +#define re3_debug(...) +#define re3_trace(...) +#define re3_usererror(...) +#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])) +#ifdef BIT +#undef BIT +#endif +#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/miami/core/config.h b/src/miami/core/config.h new file mode 100644 index 00000000..f3cf4853 --- /dev/null +++ b/src/miami/core/config.h @@ -0,0 +1,528 @@ +#pragma once + +// disables (most) stuff that wasn't in original gta-vc.exe +#ifdef __MWERKS__ +#define VANILLA_DEFINES +#endif + +enum Config { + NUMPLAYERS = 1, + +#if defined(DC_SQUEEZE_RAM) + NUMCDIMAGES = 1, // gta3.img duplicates (not used on PC) + MAX_CDIMAGES = 1, // additional cdimages + MAX_CDCHANNELS = 2, + + MODELINFOSIZE = 4900, // 4900 on PS2 +#else + NUMCDIMAGES = 6, // gta3.img duplicates (not used on PC) + MAX_CDIMAGES = 8, // additional cdimages + MAX_CDCHANNELS = 5, + + MODELINFOSIZE = 6500, // 4900 on PS2 +#endif + TXDSTORESIZE = 1385, + COLSTORESIZE = 31, + EXTRADIRSIZE = 256, + CUTSCENEDIRSIZE = 512, + + SIMPLEMODELSIZE = 3885, + TIMEMODELSIZE = 385, + CLUMPMODELSIZE = 5, + WEAPONMODELSIZE = 37, + PEDMODELSIZE = 130, + VEHICLEMODELSIZE = 110, + TWODFXSIZE = 1210, + +#if defined(DC_SQUEEZE_RAM) + MAXVEHICLESLOADED = 25, // 70 on mobile +#else + MAXVEHICLESLOADED = 50, // 70 on mobile +#endif + + NUMOBJECTINFO = 210, + + // Pool sizes + #if defined(DC_SQUEEZE_RAM) + NUMPTRNODES = 27500, + #elif defined(DC_ORIGINAL_RAM) + NUMPTRNODES = 50000, + #else + NUMPTRNODES = 40000, // This is the most important one to reduce + #endif + + #if defined(DC_SQUEEZE_RAM) + NUMENTRYINFOS = 3200, + NUMPEDS = 50, + NUMVEHICLES = 50, + #else + NUMENTRYINFOS = 3200, + NUMPEDS = 140, + NUMVEHICLES = 110, + #endif + NUMBUILDINGS = 7000, + NUMTREADABLES = 1, + NUMOBJECTS = 460, + NUMDUMMIES = 2340, + NUMAUDIOSCRIPTOBJECTS = 192, + NUMCOLMODELS = 4400, + NUMCUTSCENEOBJECTS = 50, // not a pool in VC + + NUMANIMBLOCKS = 35, + NUMANIMATIONS = 450, + + NUMTEMPOBJECTS = 40, + + // Path data + NUM_PATHNODES = 9650, + NUM_CARPATHLINKS = 3500, + NUM_MAPOBJECTS = 1250, + NUM_PATHCONNECTIONS = 20400, + + // Link list lengths + NUMALPHALIST = 20, + NUMBOATALPHALIST = 20, + NUMALPHAENTITYLIST = 200, + NUMALPHAUNTERWATERENTITYLIST = 30, + NUMCOLCACHELINKS = 50, + NUMREFERENCES = 800, + + // Zones + NUMAUDIOZONES = 14, + NUMINFOZONES = 169, + NUMMAPZONES = 39, + NUMNAVIGZONES = 20, + + // Cull zones + NUMATTRIBZONES = 704, + + NUMOCCLUSIONVOLUMES = 350, + NUMACTIVEOCCLUDERS = 48, + + PATHNODESIZE = 4500, + + NUMWEATHERS = 7, + 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, + + NUMONSCREENCLOCKS = 1, + NUMONSCREENCOUNTERS = 3, + NUMRADARBLIPS = 75, + NUMGENERALPICKUPS = 320, + NUMSCRIPTEDPICKUPS = 16, + NUMPICKUPS = NUMGENERALPICKUPS + NUMSCRIPTEDPICKUPS, + NUMCOLLECTEDPICKUPS = 20, + NUMPACMANPICKUPS = 256, + NUMEVENTS = 64, + + NUM_CARGENS = 185, + + 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 = 67, + NUMMODELSPERPEDGROUP = 16, + MAXZONEPEDSLOADED = 8, + NUMSHOTINFOS = 100, + + NUMROADBLOCKS = 300, + NUM_SCRIPT_ROADBLOCKS = 16, + + NUMVISIBLEENTITIES = 2000, + NUMINVISIBLEENTITIES = 150, + + NUM_AUDIOENTITY_EVENTS = 4, + NUM_PED_COMMENTS_SLOTS = 20, + + NUM_SOUND_QUEUES = 2, + NUM_AUDIOENTITIES = 250, + + NUM_SCRIPT_MAX_ENTITIES = 40, + + NUM_GARAGE_STORED_CARS = 4, + + NUM_CRANES = 8, + NUM_ESCALATORS = 22, + NUM_WATER_CREATURES = 8, + + NUM_EXPLOSIONS = 48, + + NUM_SETPIECES = 96, + NUM_SHORTCUT_START_POINTS = 16 +}; + +// 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 GTAVC_PS2 400 +#define GTAVC_PC_10 410 +#define GTAVC_PC_11 411 +#define GTAVC_PC_JAP 412 +// TODO? maybe something for xbox or android? + +#define GTA_VERSION GTAVC_PC_11 + +// Enable configuration for handheld console ports +#if defined(__SWITCH__) || defined(PSP2) + #define GTA_HANDHELD +#endif + +// TODO(MIAMI): someone ought to find and check out uses of these defines: +//#define GTA3_STEAM_PATCH +//#define GTAVC_JP_PATCH + +#if defined GTA_PS2 +# define GTA_PS2_STUFF +# define RANDOMSPLASH +//# define USE_CUSTOM_ALLOCATOR +# define VU_COLLISION +# define PS2_MENU +#elif defined GTA_PC +# define EXTERNAL_3D_SOUND +# define AUDIO_REVERB +# ifndef GTA_HANDHELD +// # define PC_PLAYER_CONTROLS // mouse player/cam mode +# endif +//# define GTA_REPLAY +//# define GTA_SCENE_EDIT +# define PC_MENU +# define PC_WATER +# define DISABLE_WAVY_WATER +#elif defined GTA_XBOX +#elif defined GTA_MOBILE +# define MISSION_REPLAY +# define SIMPLER_MISSIONS +#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 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 +#define AUDIO_REFLECTIONS +#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 + +// 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 +// #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 RELOADABLES // some debug menu options to reload TXD files +#define MISSION_SWITCHER // from debug menu +#endif + +// Rendering/display +#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 +#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 + +// Water & Particle +#undef PC_WATER +#define WATER_CHEATS + +//#define USE_CUTSCENE_SHADOW_FOR_PED // requires COMPATIBLE_SAVES +#define DISABLE_CUTSCENE_SHADOWS + +// 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 RESTORE_ALLCARSHELI_CHEAT +#define BETTER_ALLCARSAREDODO_CHEAT +#define WALLCLIMB_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 PC_MENU +#define FIX_RADAR // use radar size from early version before R* broke it +#define RADIO_OFF_TEXT // Won't work without FIX_BUGS + +#ifndef PC_MENU +# define PS2_MENU +//# define PS2_MENU_USEALLPAGEICONS +#else +# define MAP_ENHANCEMENTS // Adding waypoint and better mouse support +# if defined(XINPUT) || defined(GTA_HANDHELD) +# define GAMEPAD_MENU // Add gamepad menu +# endif +# define TRIANGLE_BACK_BUTTON +//# define CIRCLE_BACK_BUTTON +#define LEGACY_MENU_OPTIONS // i.e. frame sync(vsync) +#define MUCH_SHORTER_OUTRO_SCREEN +// #define XBOX_MESSAGE_SCREEN // Blue background, no "saved successfully press OK" screen etc. +# define CUSTOM_FRONTEND_OPTIONS + +# ifdef CUSTOM_FRONTEND_OPTIONS +# 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 // enable the hidden option +# 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 +#define SUPPORT_JAPANESE_SCRIPT +//#define SUPPORT_XBOX_SCRIPT +#define SUPPORT_MOBILE_SCRIPT +#define SUPPORT_GINPUT_SCRIPT +#if (defined SUPPORT_XBOX_SCRIPT && defined SUPPORT_MOBILE_SCRIPT) +static_assert(false, "SUPPORT_XBOX_SCRIPT and SUPPORT_MOBILE_SCRIPT are mutually exclusive"); +#endif +#ifdef PC_MENU +#define MISSION_REPLAY // mobile feature +//#define SIMPLER_MISSIONS // apply simplifications from mobile +#define USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT +#endif +#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 + +#ifndef MISSION_REPLAY +#undef USE_MISSION_REPLAY_OVERRIDE_FOR_NON_MOBILE_SCRIPT +#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 CPLANE_ROTORS // make the rotors of the NPC police heli rotate + +// Pickups +//#define MONEY_MESSAGES +#define CAMERA_PICKUP + +// Peds +#define CANCELLABLE_CAR_ENTER + +// Camera +#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. This is enabled in all vanilla versions +#define AUDIO_REVERB // Enable audio reverb. It was disabled in PS2 and mobile versions +#define RADIO_SCROLL_TO_PREV_STATION // Won't work without FIX_BUGS +#define AUDIO_CACHE // cache sound lengths to speed up the cold boot +#define PS2_AUDIO_CHANNELS // increases the maximum number of audio channels to PS2 value of 43 (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 // (not supported on VC yet) 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 +#endif + +// if these defines are enabled saves are not vanilla compatible without COMPATIBLE_SAVES +#ifndef COMPATIBLE_SAVES +#undef USE_CUTSCENE_SHADOW_FOR_PED +#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 diff --git a/src/miami/core/main.cpp b/src/miami/core/main.cpp new file mode 100644 index 00000000..9d63f8ee --- /dev/null +++ b/src/miami/core/main.cpp @@ -0,0 +1,2447 @@ +#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 "MemoryHeap.h" +#include "SceneEdit.h" +#include "debugmenu.h" +#include "Clock.h" +#include "Occlusion.h" +#include "Ropes.h" +#include "postfx.h" +#include "custompipes.h" +#include "screendroplets.h" +#include "VarConsole.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; +#define TOTALNUMCHUNKS 95.0f + +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 GTA_PS2 +#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; +#endif +#ifdef FIX_BUGS +// need to clear stencil for mblur fx. no idea why it works in the original game +// also for clearing out water rects in new renderer +#define CLEARMODE (rwCAMERACLEARZ | rwCAMERACLEARSTENCIL) +#else +#define CLEARMODE (rwCAMERACLEARZ) +#endif + +bool bDisplayNumOfAtomicsRendered = false; +bool bDisplayPosn = false; + +#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); + + CDraw::CalculateAspectRatio(); + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &TopColor.rwRGBA, CLEARMODE); + + if(!RsCameraBeginUpdate(Scene.camera)) + return false; + + CSprite2d::InitPerFrame(); + + if(Alpha != 0) + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), BottomColor, BottomColor, TopColor, TopColor); + + return true; +} + +bool +DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha) +{ + CDraw::CalculateAspectRatio(); + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); + 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 || FrontEndMenuManager.m_PrefsBrightness < 256){ + CSprite2d *splash = LoadSplash(nil); + + CRGBA fadeColor; + CRect rect; + int fadeValue = CDraw::FadeValue; + float brightness = Min(FrontEndMenuManager.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; + } + + TheCamera.GetScreenRect(rect); + CSprite2d::DrawRect(rect, fadeColor); + + if(CDraw::FadeValue != 0 && TheCamera.m_FadeTargetIsSplashScreen){ + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + 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); + } + } +} + +#if 0 +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; +} +#endif + +#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 0 + if (CPad::GetPad(1)->GetLeftShockJustDown() || CPad::GetPad(0)->GetFJustDown(11)) { + sprintf(s, "screen_%011lld.png", time(nil)); + RwGrabScreen(Scene.camera, s); + } +#endif +#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; + } +#ifndef LIBRW + if (!RtAnimInitialize()) + { + return FALSE; + } +#endif + 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 1800 +#define NUM_PREALLOC_CLUMPS 80 +#define NUM_PREALLOC_FRAMES 2600 +#define NUM_PREALLOC_GEOMETRIES 850 +#define NUM_PREALLOC_TEXDICTS 121 +#define NUM_PREALLOC_TEXTURES 1700 +#define NUM_PREALLOC_MATERIALS 2600 +bool preAlloc; + +void +PreAllocateRwObjects(void) +{ + int i; + + PUSH_MEMID(MEMID_PRE_ALLOC); + 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; + POP_MEMID(); +} +#endif + +static RwBool +Initialise3D(void *param) +{ + PUSH_MEMID(MEMID_RENDER); + +#ifndef MASTER + VarConsole.Add("Display number of atomics rendered", &bDisplayNumOfAtomicsRendered, true); + VarConsole.Add("Display posn and framerate", &bDisplayPosn, true); +#endif + + if (RsRwInitialize(param)) + { + POP_MEMID(); + +#ifdef DEBUGMENU + DebugMenuInit(); + DebugMenuPopulate(); +#endif // !DEBUGMENU + return CGame::InitialiseRenderWare(); + } + POP_MEMID(); + + 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[12] = { + 1, 2, + 3, 4, + 5, 11, + 6, 8, + 9, 10, + 7, 12 + }; + + index = splashIndex[2*index2 + CGeneral::GetRandomNumberInRange(0, 2)]; + 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 + splashscreen = "LOADSC0"; +#endif + + splash = LoadSplash(splashscreen); + +#ifndef GTA_PS2 + if(RsGlobal.quit) + return; +#endif + + if(DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)){ + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), CRGBA(255, 255, 255, 255)); + + if(str1){ + NumberOfChunksLoaded += 1; + +#ifndef RANDOMSPLASH + float hpos = SCREEN_SCALE_X(40); + float length = SCREEN_WIDTH - SCREEN_SCALE_X(80); + float top = SCREEN_HEIGHT - SCREEN_SCALE_Y(14); + float bottom = top + SCREEN_SCALE_Y(5); +#else + float hpos = SCREEN_STRETCH_X(40); + float length = SCREEN_STRETCH_X(440); + // this is rather weird + float top = SCREEN_STRETCH_Y(407.4f - 7.0f/3.0f); + float bottom = SCREEN_STRETCH_Y(407.4f + 7.0f/3.0f); +#endif + + CSprite2d::DrawRect(CRect(hpos-1.0f, top-1.0f, hpos+length+1.0f, bottom+1.0f), CRGBA(40, 53, 68, 255)); + + CSprite2d::DrawRect(CRect(hpos, top, hpos+length, bottom), CRGBA(155, 50, 125, 255)); + + length *= NumberOfChunksLoaded/TOTALNUMCHUNKS; + CSprite2d::DrawRect(CRect(hpos, top, hpos+length, bottom), CRGBA(255, 150, 225, 255)); + + // this is done by the game but is unused + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(2), SCREEN_SCALE_Y(2)); + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); + CFont::SetDropShadowPosition(1); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetFontStyle(FONT_HEADING); + +#ifdef CHATTYSPLASH + // my attempt + static wchar tmpstr[80]; + float yscale = SCREEN_SCALE_Y(0.9f); + top -= 45*yscale; + CFont::SetScale(SCREEN_SCALE_X(0.75f), yscale); + CFont::SetPropOn(); + CFont::SetRightJustifyOff(); + CFont::SetFontStyle(FONT_STANDARD); + CFont::SetColor(CRGBA(255, 255, 255, 255)); + AsciiToUnicode(str1, tmpstr); + CFont::PrintString(hpos, top, tmpstr); + top += 22*yscale; + if (str2) { + AsciiToUnicode(str2, tmpstr); + CFont::PrintString(hpos, top, tmpstr); + } +#endif + } + + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); + } +} + +void +LoadingIslandScreen(const char *levelName) +{ + CSprite2d *splash; + + splash = LoadSplash(nil); + if(!DoRWStuffStartOfFrame(0, 0, 0, 0, 0, 0, 255)) + return; + + CSprite2d::SetRecipNearClip(); + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + DefinedState(); + CRGBA col = CRGBA(255, 255, 255, 255); + CRGBA col2 = CRGBA(0, 0, 0, 255); + CSprite2d::DrawRect(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), col2); + splash->Draw(CRect(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT), col, col, col, col); + 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); + + } while (!CPad::GetPad(1)->GetLeftShoulder1JustDown() && !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[11]; + char area[5]; + CRect rect; +}; + +tZonePrint ZonePrint[] = +{ + { "DOWNTOWN", "GM", CRect(-1500.0f, 1500.0f, -300.0f, 980.0f)}, + { "DOWNTOWS", "KB", CRect(-1200.0f, 980.0f, -300.0f, 435.0f)}, + { "GOLF", "NT", CRect(-300.0f, 660.0f, 320.0f, -255.0f)}, + { "LITTLEHA", "AG", CRect(-1250.0f, -310.0f, -746.0f, -926.0f)}, + { "HAITI", "CJ", CRect(-1355.0f, 30.0f, -637.0f, -304.0f)}, + { "HAITIN", "SM", CRect(-1355.0f, 435.0f, -637.0f, 30.0f)}, + { "DOCKS", "AW", CRect(-1122.0f, -926.0f, -609.0f, -1575.0f)}, + { "AIRPORT", "NT", CRect(-2000.0f, 200.0f, -871.0f, -2000.0f)}, + { "STARISL", "CJ", CRect(-724.0f, -320.0f, -40.0f, -380.0f)}, + { "CENT.ISLA", "NT", CRect(-163.0f, 1260.0f, 120.0f, 830.0f)}, + { "MALL", "AW", CRect( 300.0f, 1266.0f, 483.0f, 995.0f)}, + { "MANSION", "KB", CRect(-724.0f, -500.0f, -40.0f, -670.0f)}, + { "NBEACH", "AS", CRect( 120.0f, 1340.0f, 900.0f, 600.0f)}, + { "NBEACHBT", "AS", CRect( 200.0f, 680.0f, 660.0f, -50.0f)}, + { "NBEACHW", "AS", CRect(-93.0f, 80.0f, 410.0f, -680.0f)}, + { "OCEANDRV", "AC", CRect( 200.0f, -964.0f, 955.0f, -1797.0f)}, + { "OCEANDN", "WS", CRect( 400.0f, 50.0f, 955.0f, -964.0f)}, + { "WASHINGTN", "AC", CRect(-320.0f, -487.0f, 500.0f, -1200.0f)}, + { "WASHINBTM", "AC", CRect(-255.0f, -1200.0f, 500.0f, -1690.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, "PreAlloc: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_PRE_ALLOC), gMainHeap.GetMemoryUsed(MEMID_PRE_ALLOC)); + 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 LODs: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM_LODS), gMainHeap.GetMemoryUsed(MEMID_STREAM_LODS)); + 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, "Streamed Collision: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM_COLLISION), gMainHeap.GetMemoryUsed(MEMID_STREAM_COLLISION)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Streamed Animation: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_STREAM_ANIMATION), gMainHeap.GetMemoryUsed(MEMID_STREAM_ANIMATION)); + AsciiToUnicode(gString, gUString); + CFont::PrintString(24.0f, y, gUString); + y += 12.0f; + + sprintf(gString, "Ped Attr: %d blocks, %d bytes", gMainHeap.GetBlocksUsed(MEMID_PED_ATTR), gMainHeap.GetMemoryUsed(MEMID_PED_ATTR)); + 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; +#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 bDisplayCheatStr = false; // custom + +#ifndef FINAL + { + SETTWEAKPATH("Debug"); + TWEAKBOOL(bDisplayPosn); + TWEAKBOOL(bDisplayCheatStr); + } + + 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_STANDARD); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetWrapx(SCREEN_WIDTH); + CFont::SetJustifyOff(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetColor(CRGBA(255, 108, 0, 255)); + CFont::PrintString(SCREEN_SCALE_X(10.0f), SCREEN_SCALE_Y(10.0f), ver); + } +#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 ( bDisplayPosn ) + { + 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 + sprintf(str, "X:%4.0f Y:%4.0f Z:%4.0f F-%d %s-%s", pos.x, pos.y, pos.z, (int32)FramesPerSecond, + ZonePrint[ZoneId].name, ZonePrint[ZoneId].area); + + AsciiToUnicode(str, ustr); + + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.6f), SCREEN_SCALE_Y(0.8f)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOff(); + CFont::SetJustifyOff(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetFontStyle(FONT_STANDARD); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetDropShadowPosition(2); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(41.0f, 41.0f, ustr); + + CFont::SetColor(CRGBA(205, 205, 0, 255)); + CFont::PrintString(40.0f, 40.0f, ustr); + } + + // custom + if (bDisplayCheatStr) + { + sprintf(str, "%s", CPad::KeyBoardCheatString); + AsciiToUnicode(str, ustr); + + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.6f), SCREEN_SCALE_Y(0.8f)); + CFont::SetCentreOn(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetFontStyle(FONT_STANDARD); + + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.5f)+2.f, SCREEN_SCALE_FROM_BOTTOM(20.0f)+2.f, ustr); + + CFont::SetColor(CRGBA(255, 150, 225, 255)); + CFont::PrintString(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.5f), SCREEN_SCALE_FROM_BOTTOM(20.0f), ustr); + } +} +#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 + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + CWaterLevel::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(); + + // not sure where to put these since LCS has no underwater entities +if(gbRenderBoats) + CRenderer::RenderBoats(); +if(gbRenderFadingInUnderwaterEntities) + CRenderer::RenderFadingInUnderwaterEntities(); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); +if(gbRenderWater) + CRenderer::RenderTransparentWater(); + +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(); + CCoronas::RenderSunReflection(); + CWeather::RenderRainStreaks(); + // CWeather::AddSnow + CWaterCannons::Render(); + CAntennas::Render(); + CSpecialFX::Render(); + CRopes::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(); + CRenderer::RenderEverythingBarRoads(); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + CWaterLevel::RenderWater(); + CRenderer::RenderBoats(); + CRenderer::RenderFadingInUnderwaterEntities(); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + CWaterLevel::RenderTransparentWater(); + CRenderer::RenderFadingInEntities(); + RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE); + CWeather::RenderRainStreaks(); + CCoronas::RenderSunReflection(); + 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(); + CRopes::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 || + cammode == CCam::MODE_CAMERA) + firstPersonWeapon = true; + + // Draw black border for sniper and rocket launcher + if((weaponType == WEAPONTYPE_SNIPERRIFLE || weaponType == WEAPONTYPE_ROCKETLAUNCHER || weaponType == WEAPONTYPE_LASERSCOPE) && 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(); + + CSpecialFX::Render2DFXs(); + CUserDisplay::OnscnTimer.ProcessForDisplay(); + CMessages::Display(); + CDarkel::DrawMessages(); + CGarages::PrintMessages(); + CPad::PrintErrorMessage(); + CFont::DrawFonts(); +#ifndef MASTER + COcclusion::Render(); +#endif + +#ifdef DEBUGMENU + DebugMenuRender(); +#endif + POP_RENDERGROUP(); +} + +void +RenderMenus(void) +{ + if (FrontEndMenuManager.m_bMenuActive) + { + PUSH_RENDERGROUP("RenderMenus"); + FrontEndMenuManager.DrawFrontEnd(); + POP_RENDERGROUP(); + } +#ifndef MASTER + else + VarConsole.Check(); +#endif +} + +void +Render2dStuffAfterFade(void) +{ + PUSH_RENDERGROUP("Render2dStuffAfterFade"); +#ifndef MASTER + DisplayGameDebugText(); +#endif + +#ifdef MOBILE_IMPROVEMENTS + if (CDraw::FadeValue != 0) +#endif + CHud::DrawAfterFade(); + CFont::DrawFonts(); + CCredits::Render(); + POP_RENDERGROUP(); +} + +void +Idle(void *arg) +{ + CTimer::Update(); + + tbInit(); + + CSprite2d::InitPerFrame(); + CFont::InitPerFrame(); + + 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(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 && TheCamera.GetScreenFadeStatus() != FADE_2) + { + // This is from SA, but it's nice for windowed mode +#if defined(GTA_PC) && !defined(RW_GL3) + RwV2d pos; + pos.x = SCREEN_WIDTH / 2.0f; + pos.y = SCREEN_HEIGHT / 2.0f; + RsMouseSetPos(&pos); +#endif + + tbStartTimer(0, "CnstrRenderList"); +#ifdef PC_WATER + CWaterLevel::PreCalcWaterGeometry(); +#endif +#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"); + +#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{ + CDraw::CalculateAspectRatio(); +#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; + } + + 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(); // They added it to function above and also forgot it here +#ifdef XBOX_MESSAGE_SCREEN + FrontEndMenuManager.DrawOverlays(); +#endif + + if (gbShowTimebars) + tbDisplay(); + + DoRWStuffEndOfFrame(); + + POP_MEMID(); // MEMID_RENDER + + if(g_SlowMode) + ProcessSlowMode(); + return; + +popret: POP_MEMID(); // MEMID_RENDER +} + +void +FrontendIdle(void) +{ + CDraw::CalculateAspectRatio(); + 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; + + CameraSize(Scene.camera, nil, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); + CVisibilityPlugins::SetRenderWareCamera(Scene.camera); + RwCameraClear(Scene.camera, &gColourTop, CLEARMODE); + if(!RsCameraBeginUpdate(Scene.camera)) + return; + + DefinedState(); // seems redundant, but breaks resolution change. + RenderMenus(); +#ifdef XBOX_MESSAGE_SCREEN + FrontEndMenuManager.DrawOverlays(); +#endif + DoFade(); + Render2dStuffAfterFade(); + CFont::DrawFonts(); + DoRWStuffEndOfFrame(); +} + +void +InitialiseGame(void) +{ + LoadingScreen(nil, nil, "loadsc0"); + CGame::Initialise("DATA\\GTA_VC.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: + { + FrontendIdle(); + + return rsEVENTPROCESSED; + } + + case rsACTIVATE: + { + param ? DMAudio.ReacquireDigitalHandle() : DMAudio.ReleaseDigitalHandle(); + + return rsEVENTPROCESSED; + } + + default: + { + return rsEVENTNOTPROCESSED; + } + } +} + +#if !defined(RW_DC) +#ifndef MASTER +void +TheModelViewer(void) +{ +#if (defined(GTA_PS2) || defined(GTA_XBOX)) + //TODO +#else + + // This is not original. Because; + // 1- We want 2D things to be initalized, whereas original AnimViewer doesn't use them. my additions marked with X + // 2- VC Mobile code run it like main function(as opposed to III and LCS), so it has it's own loop inside it, but our func. already called in a loop. + + CDraw::CalculateAspectRatio(); // X + CAnimViewer::Update(); + SetLightsWithTimeOfDayColour(Scene.world); + CRenderer::ConstructRenderList(); + DoRWStuffStartOfFrame(CTimeCycle::GetSkyTopRed()*0.5f, CTimeCycle::GetSkyTopGreen()*0.5f, CTimeCycle::GetSkyTopBlue()*0.5f, + CTimeCycle::GetSkyBottomRed(), CTimeCycle::GetSkyBottomGreen(), CTimeCycle::GetSkyBottomBlue(), + 255); + + CSprite2d::SetRecipNearClip(); // X + CSprite2d::InitPerFrame(); // X + CFont::InitPerFrame(); // X + DefinedState(); + CVisibilityPlugins::InitAlphaEntityList(); + CAnimViewer::Render(); + Render2dStuff(); // X + DoRWStuffEndOfFrame(); + CTimer::Update(); +#endif +} +#endif +#endif + + +#ifdef GTA_PS2 +void TheGame(void) +{ + printf("Into TheGame!!!\n"); + + PUSH_MEMID(MEMID_GAME); // NB: not popped + + CTimer::Initialise(); + + CGame::Initialise("DATA\\GTA3.DAT"); + + Const char *splash = GetRandomSplashScreen(); // inlined here + + LoadingScreen("Starting Game", NULL, splash); + +#ifdef GTA_PS2 + // TODO(MIAMI): not checked yet + 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 (FrontEndMenuManager.m_PrefsLanguage != TheMemoryCard.GetLanguageToLoad()) + { + FrontEndMenuManager.m_PrefsLanguage = TheMemoryCard.GetLanguageToLoad(); + TheText.Unload(); + TheText.Load(); + } + + CGame::currLevel = TheMemoryCard.GetLevelToLoad(); + } +#else + //TODO +#endif + + while (true) + { + if (FOUND_GAME_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); + + CRenderer::ConstructRenderList(); + + if ((!FrontEndMenuManager.m_bMenuActive || FrontEndMenuManager.m_bRenderGameInMenu == true) && TheCamera.GetScreenFadeStatus() != FADE_2 ) + { + CRenderer::PreRender(); + // TODO(MIAMI): something ps2all specific + +#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 + { + CameraSize(Scene.camera, NULL, SCREEN_VIEWWINDOW, SCREEN_ASPECT_RATIO); + 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 USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + +#ifdef GTA_PS2 + CFileMgr::InitCdSystem(); + + char path[256]; + + sprintf(path, "cdrom0:\\%s%s;1", "SYSTEM\\", "IOPRP241.IMG"); + + sceSifInitRpc(0); + + while ( !sceSifRebootIop(path) ) + ; + while( !sceSifSyncIop() ) + ; + + sceSifInitRpc(0); + + CFileMgr::InitCdSystem(); + + sceFsReset(); + + 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); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "CDSTREAM.IRX"); + LoadModule(modulepath); + + strcpy(modulepath, "cdrom0:\\"); + strcat(modulepath, "SYSTEM\\"); + strcat(modulepath, "SAMPMAN2.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; + FrontEndMenuManager.m_PrefsAllowNastyGame = true; + +#ifdef GTA_PS2 + // TODO(MIAMI): this code probably went elsewhere? + int32 lang = sceScfGetLanguage(); + if ( lang == SCE_ITALIAN_LANGUAGE ) + FrontEndMenuManager.m_PrefsLanguage = LANGUAGE_ITALIAN; + else if ( lang == SCE_SPANISH_LANGUAGE ) + FrontEndMenuManager.m_PrefsLanguage = LANGUAGE_SPANISH; + else if ( lang == SCE_GERMAN_LANGUAGE ) + { + CGame::germanGame = true; + CGame::nastyGame = false; + FrontEndMenuManager.m_PrefsAllowNastyGame = false; + FrontEndMenuManager.m_PrefsLanguage = LANGUAGE_GERMAN; + } + else if ( lang == SCE_FRENCH_LANGUAGE ) + { + CGame::frenchGame = true; + CGame::nastyGame = false; + FrontEndMenuManager.m_PrefsAllowNastyGame = false; + FrontEndMenuManager.m_PrefsLanguage = LANGUAGE_FRENCH; + } + else + FrontEndMenuManager.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(bool onlyRW) +{ + if(onlyRW) + { +#ifdef GTA_PS2 + Initialise3D(nil); +#else + Initialise3D(nil); //TODO: window parameter +#endif + gameAlreadyInitialised = true; + } + else + { + if ( !gameAlreadyInitialised ) +#ifdef GTA_PS2 + Initialise3D(nil); +#else + Initialise3D(nil); //TODO: window parameter +#endif + } + +#ifdef GTA_PS2 + char *files[] = + { + "\\ANIM\\CUTS.IMG;1", + "\\ANIM\\CUTS.DIR;1", + "\\ANIM\\PED.IFP;1", + "\\MODELS\\FRONTEN1.TXD;1", + "\\MODELS\\FRONTEN2.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 + "\\MODELS\\COLL\\GENERIC.COL;1", + "\\MODELS\\COLL\\VEHICLES.COL;1", + "\\MODELS\\COLL\\PEDS.COL;1", + "\\MODELS\\COLL\\WEAPONS.COL;1", + "\\MODELS\\GENERIC\\AIR_VLO.DFF;1", + "\\MODELS\\GENERIC\\WHEELS.DFF;1", + "\\MODELS\\GENERIC\\ARROW.DFF;1", + "\\MODELS\\GENERIC\\ZONECYLB.DFF;1", + "\\DATA\\HANDLING.CFG;1", + "\\DATA\\SURFACE.DAT;1", + "\\DATA\\PEDSTATS.DAT;1", + "\\DATA\\TIMECYC.DAT;1", + "\\DATA\\PARTICLE.CFG;1", + "\\DATA\\DEFAULT.DAT;1", + "\\DATA\\DEFAULT.IDE;1", + "\\DATA\\GTA_VC.DAT;1", + "\\DATA\\OBJECT.DAT;1", + "\\DATA\\MAP.ZON;1", + "\\DATA\\NAVIG.ZON;1", + "\\DATA\\INFO.ZON;1", + "\\DATA\\WATERPRO.DAT;1", + "\\DATA\\MAIN.SCM;1", + "\\DATA\\CARCOLS.DAT;1", + "\\DATA\\PED.DAT;1", + "\\DATA\\FISTFITE.DAT;1", + "\\DATA\\WEAPON.DAT;1", + "\\DATA\\PEDGRP.DAT;1", + "\\DATA\\PATHS\\FLIGHT.DAT;1", + "\\DATA\\PATHS\\FLIGHT2.DAT;1", + "\\DATA\\PATHS\\FLIGHT3.DAT;1", + "\\DATA\\PATHS\\SPATH0.DAT;1", + "\\DATA\\MAPS\\LITTLEHA\\LITTLEHA.IDE;1", + "\\DATA\\MAPS\\DOWNTOWN\\DOWNTOWN.IDE;1", + "\\DATA\\MAPS\\DOWNTOWS\\DOWNTOWS.IDE;1", + "\\DATA\\MAPS\\DOCKS\\DOCKS.IDE;1", + "\\DATA\\MAPS\\WASHINTN\\WASHINTN.IDE;1", + "\\DATA\\MAPS\\WASHINTS\\WASHINTS.IDE;1", + "\\DATA\\MAPS\\OCEANDRV\\OCEANDRV.IDE;1", + "\\DATA\\MAPS\\OCEANDN\\OCEANDN.IDE;1", + "\\DATA\\MAPS\\GOLF\\GOLF.IDE;1", + "\\DATA\\MAPS\\BRIDGE\\BRIDGE.IDE;1", + "\\DATA\\MAPS\\STARISL\\STARISL.IDE;1", + "\\DATA\\MAPS\\NBEACHBT\\NBEACHBT.IDE;1", + "\\DATA\\MAPS\\NBEACHW\\NBEACHW.IDE;1", + "\\DATA\\MAPS\\NBEACH\\NBEACH.IDE;1", + "\\DATA\\MAPS\\BANK\\BANK.IDE;1", + "\\DATA\\MAPS\\MALL\\MALL.IDE;1", + "\\DATA\\MAPS\\YACHT\\YACHT.IDE;1", + "\\DATA\\MAPS\\CISLAND\\CISLAND.IDE;1", + "\\DATA\\MAPS\\CLUB\\CLUB.IDE;1", + "\\DATA\\MAPS\\HOTEL\\HOTEL.IDE;1", + "\\DATA\\MAPS\\LAWYERS\\LAWYERS.IDE;1", + "\\DATA\\MAPS\\STRIPCLB\\STRIPCLB.IDE;1", + "\\DATA\\MAPS\\AIRPORT\\AIRPORT.IDE;1", + "\\DATA\\MAPS\\HAITI\\HAITI.IDE;1", + "\\DATA\\MAPS\\HAITIN\\HAITIN.IDE;1", + "\\DATA\\MAPS\\CONCERTH\\CONCERTH.IDE;1", + "\\DATA\\MAPS\\MANSION\\MANSION.IDE;1", + "\\DATA\\MAPS\\ISLANDSF\\ISLANDSF.IDE;1", + "\\DATA\\MAPS\\LITTLEHA\\LITTLEHA.IPL;1", + "\\DATA\\MAPS\\DOWNTOWN\\DOWNTOWN.IPL;1", + "\\DATA\\MAPS\\DOWNTOWS\\DOWNTOWS.IPL;1", + "\\DATA\\MAPS\\DOCKS\\DOCKS.IPL;1", + "\\DATA\\MAPS\\WASHINTN\\WASHINTN.IPL;1", + "\\DATA\\MAPS\\WASHINTS\\WASHINTS.IPL;1", + "\\DATA\\MAPS\\OCEANDRV\\OCEANDRV.IPL;1", + "\\DATA\\MAPS\\OCEANDN\\OCEANDN.IPL;1", + "\\DATA\\MAPS\\GOLF\\GOLF.IPL;1", + "\\DATA\\MAPS\\BRIDGE\\BRIDGE.IPL;1", + "\\DATA\\MAPS\\STARISL\\STARISL.IPL;1", + "\\DATA\\MAPS\\NBEACHBT\\NBEACHBT.IPL;1", + "\\DATA\\MAPS\\NBEACH\\NBEACH.IPL;1", + "\\DATA\\MAPS\\NBEACHW\\NBEACHW.IPL;1", + "\\DATA\\MAPS\\CISLAND\\CISLAND.IPL;1", + "\\DATA\\MAPS\\AIRPORT\\AIRPORT.IPL;1", + "\\DATA\\MAPS\\HAITI\\HAITI.IPL;1", + "\\DATA\\MAPS\\HAITIN\\HAITIN.IPL;1", + "\\DATA\\MAPS\\ISLANDSF\\ISLANDSF.IPL;1", + "\\DATA\\MAPS\\BANK\\BANK.IPL;1", + "\\DATA\\MAPS\\MALL\\MALL.IPL;1", + "\\DATA\\MAPS\\YACHT\\YACHT.IPL;1", + "\\DATA\\MAPS\\CLUB\\CLUB.IPL;1", + "\\DATA\\MAPS\\HOTEL\\HOTEL.IPL;1", + "\\DATA\\MAPS\\LAWYERS\\LAWYERS.IPL;1", + "\\DATA\\MAPS\\STRIPCLB\\STRIPCLB.IPL;1", + "\\DATA\\MAPS\\CONCERTH\\CONCERTH.IPL;1", + "\\DATA\\MAPS\\MANSION\\MANSION.IPL;1", + "\\DATA\\MAPS\\GENERIC.IDE;1", + "\\DATA\\OCCLU.IPL;1", + "\\DATA\\MAPS\\PATHS.IPL;1", + "\\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\\SPLASH1.TXD;1" + }; + + for ( int32 i = 0; i < ARRAY_SIZE(files); i++ ) + SkyRegisterFileOnCd([i]); +#endif + +#ifdef GTA_PS2 + AddIntcHandler(INTC_VBLANK_S, VBlankCounter, 0); +#endif + +#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; // why is this gone? + } +} + +int32 SkipAllMPEGs; +int32 gMemoryStickLoadOK; + +void PlayIntroMPEGs() +{ +#ifdef GTA_PS2 + if (gameAlreadyInitialised) + RpSkySuspend(); + + InitMPEGPlayer(); + + float skipTime; // wrong type, should be int +#ifdef GTA_PAL + if(gMemoryStickLoadOK) + skipTime = 2500000; + else + skipTime = 5300000; + + if(!SkipAllMPEGs) + PlayMPEG("cdrom0:\\MOVIES\\VCPAL.PSS;1", false, unk); + + if(!SkipAllMPEGs){ + SkipAllMPEGs = true; + PlayMPEG("cdrom0:\\MOVIES\\VICEPAL.PSS;1", true, 0); + } +#else + if(gMemoryStickLoadOK) + skipTime = 2750000; + else + skipTime = 5500000; + + if(!SkipAllMPEGs) + PlayMPEG("cdrom0:\\MOVIES\\VCNTSC.PSS;1", false, unk); + + if(!SkipAllMPEGs){ + SkipAllMPEGs = true; + PlayMPEG("cdrom0:\\MOVIES\\VICE.PSS;1", true, 0); + } +#endif + + ShutdownMPEGPlayer(); + + if ( gameAlreadyInitialised ) + RpSkyResume(); +#else + //TODO +#endif +} + +int +main(int argc, char *argv[]) +{ +#ifdef __MWERKS__ + mwInit(); // metrowerks initialisation +#endif + + SystemInit(); + + if(RsEventHandler(rsINITIALIZE, nil) == rsEVENTERROR) + return 0; + +#ifdef GTA_PS2 + int32 r = TheMemoryCard.CheckCardStateAtGameStartUp(CARD_ONE); + + if ( r == CMemoryCard::ERR_DIRNOENTRY || r == CMemoryCard::ERR_NOFORMAT ) + { + GameInit(true); + + TheText.Unload(); + TheText.Load(); + + CFont::Initialise(); + + FrontEndMenuManager.DrawMemoryCardStartUpMenus(); + }else if(r == CMemoryCard::ERR_OPENNOENTRY) + gMemoryStickLoadOK = false; + else if(r == CMemoryCard::ERR_NONE) + gMemoryStickLoadOK = true; +#endif + + PlayIntroMPEGs(); + + GameInit(false); + + frameCount = 0; + while(frameCount < 100); + + CGame::InitialiseOnceAfterRW(); + + TheGame(); + +#if 0 // maybe ifndef FINAL or MASTER? + CGame::ShutDown(); + + RwEngineStop(); + RwEngineClose(); + RwEngineTerm(); + +#ifdef __MWERKS__ + mwExit(); // metrowerks shutdown +#endif +#endif + return 0; +} +#endif diff --git a/src/miami/core/main.h b/src/miami/core/main.h new file mode 100644 index 00000000..803afb14 --- /dev/null +++ b/src/miami/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/miami/core/obrstr.cpp b/src/miami/core/obrstr.cpp new file mode 100644 index 00000000..d9f7e9b4 --- /dev/null +++ b/src/miami/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/miami/core/obrstr.h b/src/miami/core/obrstr.h new file mode 100644 index 00000000..c1633614 --- /dev/null +++ b/src/miami/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/miami/core/re3.cpp b/src/miami/core/re3.cpp new file mode 100644 index 00000000..d3a6b471 --- /dev/null +++ b/src/miami/core/re3.cpp @@ -0,0 +1,1329 @@ +#include +#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 "Occlusion.h" +#include "Credits.h" +#include "Camera.h" +#include "Weather.h" +#include "Timecycle.h" +#include "Clock.h" +#include "World.h" +#include "Vehicle.h" +#include "ModelIndices.h" +#include "Streaming.h" +#include "Boat.h" +#include "Heli.h" +#include "Automobile.h" +#include "Bike.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 "MBlur.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 + +#include + +#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 + + int fd; + // These work only if we have neo folder, so they're dynamically added +#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", 0, 0, MENUALIGN_LEFT, vehPipelineNames, ARRAY_SIZE(vehPipelineNames), (int8*)&CustomPipes::VehiclePipeSwitch, false, nil, "Graphics", "VehiclePipeline"); + FrontendOptionAddSelect("FED_PRM", 0, 0, MENUALIGN_LEFT, off_on, 2, (int8*)&CustomPipes::RimlightEnable, false, nil, "Graphics", "NeoRimLight"); + FrontendOptionAddSelect("FED_WLM", 0, 0, MENUALIGN_LEFT, off_on, 2, (int8*)&CustomPipes::LightmapEnable, false, nil, "Graphics", "NeoLightMaps"); + FrontendOptionAddSelect("FED_RGL", 0, 0, MENUALIGN_LEFT, off_on, 2, (int8*)&CustomPipes::GlossEnable, false, nil, "Graphics", "NeoRoadGloss"); +#else + FrontendOptionSetCursor(MENUPAGE_DISPLAY_SETTINGS, -3, false); + FrontendOptionAddSelect("FED_VPL", 0, 0, MENUALIGN_LEFT, vehPipelineNames, ARRAY_SIZE(vehPipelineNames), (int8*)&CustomPipes::VehiclePipeSwitch, false, nil, "Graphics", "VehiclePipeline"); + FrontendOptionAddSelect("FED_PRM", 0, 0, MENUALIGN_LEFT, off_on, 2, (int8*)&CustomPipes::RimlightEnable, false, nil, "Graphics", "NeoRimLight"); + FrontendOptionAddSelect("FED_WLM", 0, 0, MENUALIGN_LEFT, off_on, 2, (int8*)&CustomPipes::LightmapEnable, false, nil, "Graphics", "NeoLightMaps"); + FrontendOptionAddSelect("FED_RGL", 0, 0, MENUALIGN_LEFT, 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 0 + if (fd = CFileMgr::OpenFile("text/polish.gxt")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_p.txd")) { + FrontendOptionAddDynamic("FEL_POL", 0, 0, MENUALIGN_CENTER, nil, nil, LangPolSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } +#endif + + if (fd = CFileMgr::OpenFile("text/russian.gxt")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_r.txd")) { + FrontendOptionAddDynamic("FEL_RUS", 0, 0, MENUALIGN_CENTER, nil, nil, LangRusSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } + +#if 0 + if (fd = CFileMgr::OpenFile("text/japanese.gxt")) { + if (fd2 = CFileMgr::OpenFile("models/fonts_j.txd")) { + FrontendOptionAddDynamic("FEL_JAP", 0, 0, MENUALIGN_CENTER, nil, nil, LangJapSelect, nil, nil); + CFileMgr::CloseFile(fd2); + } + CFileMgr::CloseFile(fd); + } +#endif +#endif + +} +#endif + +#ifdef LOAD_INI_SETTINGS +#define MINI_CASE_SENSITIVE +#include "ini.h" + +mINI::INIFile ini("reVC.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, bool 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", "PED_DUCK", "PED_ANSWER_PHONE", +#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", "UNKNOWN_ACTION" }; + +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", "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", "MP3BoostVolume", &FrontEndMenuManager.m_PrefsMP3BoostVolume); + 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", "FrameLimiter", &FrontEndMenuManager.m_PrefsFrameLimiter); +#ifdef LEGACY_MENU_OPTIONS + ReadIniIfExists("Graphics", "VSync", &FrontEndMenuManager.m_PrefsVsyncDisp); + ReadIniIfExists("Graphics", "Trails", &CMBlur::BlurOn); +#endif + ReadIniIfExists("General", "SkinFile", FrontEndMenuManager.m_PrefsSkinFile, 256); + ReadIniIfExists("Controller", "Method", &FrontEndMenuManager.m_ControlMethod); + ReadIniIfExists("General", "Language", &FrontEndMenuManager.m_PrefsLanguage); + ReadIniIfExists("Display", "ShowHud", &FrontEndMenuManager.m_PrefsShowHud); + ReadIniIfExists("Display", "RadarMode", &FrontEndMenuManager.m_PrefsRadarMode); + ReadIniIfExists("Display", "ShowLegends", &FrontEndMenuManager.m_PrefsShowLegends); + +#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 + ReadIniIfExists("Rendering", "BackfaceCulling", &gBackfaceCulling); +#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; + CPopulation::MaxNumberOfPedsInUseInterior = DEFAULT_MAX_NUMBER_OF_PEDS_INTERIOR * 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", "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", "MP3BoostVolume", FrontEndMenuManager.m_PrefsMP3BoostVolume); + 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); +#ifdef LEGACY_MENU_OPTIONS + StoreIni("Graphics", "VSync", FrontEndMenuManager.m_PrefsVsyncDisp); + StoreIni("Graphics", "Trails", CMBlur::BlurOn); +#endif + StoreIni("Graphics", "FrameLimiter", FrontEndMenuManager.m_PrefsFrameLimiter); + StoreIni("General", "SkinFile", FrontEndMenuManager.m_PrefsSkinFile, 256); + StoreIni("Controller", "Method", FrontEndMenuManager.m_ControlMethod); + StoreIni("General", "Language", FrontEndMenuManager.m_PrefsLanguage); + StoreIni("Display", "ShowHud", FrontEndMenuManager.m_PrefsShowHud); + StoreIni("Display", "RadarMode", FrontEndMenuManager.m_PrefsRadarMode); + StoreIni("Display", "ShowLegends", FrontEndMenuManager.m_PrefsShowLegends); + +#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 + StoreIni("Rendering", "BackfaceCulling", gBackfaceCulling); +#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 WeaponCheat1(); +void WeaponCheat2(); +void WeaponCheat3(); +void HealthCheat(); +void VehicleCheat(int model); +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 SpecialCarCheats(); +void PickUpChicksCheat(); + +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 if(CModelInfo::IsBikeModel(id)) + v = new CBike(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()){ + ((CAutomobile*)veh)->Damage.SetEngineStatus(0); + ((CAutomobile*)veh)->Fix(); + }else if(veh->IsBike()){ + ((CBike*)veh)->Fix(); + } +} + +#ifdef MAP_ENHANCEMENTS +static void +TeleportToWaypoint(void) +{ + if (CRadar::TargetMarkerId == -1) + return; + CEntity* pEntityToTeleport = FindPlayerEntity(); + CVector vNewPos = CRadar::TargetMarkerPos; + CStreaming::LoadScene(vNewPos); + CStreaming::LoadSceneCollision(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 + +static const char *carnames[] = { + "landstal", "idaho", "stinger", "linerun", "peren", "sentinel", "rio", "firetruk", "trash", "stretch", "manana", + "infernus", "voodoo", "pony", "mule", "cheetah", "ambulan", "fbicar", "moonbeam", "esperant", "taxi", "washing", + "bobcat", "mrwhoop", "bfinject", "hunter", "police", "enforcer", "securica", "banshee", "predator", "bus", + "rhino", "barracks", "cuban", "chopper", "angel", "coach", "cabbie", "stallion", "rumpo", "rcbandit", "romero", + "packer", "sentxs", "admiral", "squalo", "seaspar", "pizzaboy", "gangbur", "airtrain", "deaddodo", "speeder", + "reefer", "tropic", "flatbed", "yankee", "caddy", "zebra", "topfun", "skimmer", "pcj600", "faggio", "freeway", + "rcbaron", "rcraider", "glendale", "oceanic", "sanchez", "sparrow", "patriot", "lovefist", "coastg", "dinghy", + "hermes", "sabre", "sabretur", "pheonix", "walton", "regina", "comet", "deluxo", "burrito", "spand", "marquis", + "baggage", "kaufman", "maverick", "vcnmav", "rancher", "fbiranch", "virgo", "greenwoo", "jetmax", "hotring", + "sandking", "blistac", "polmav", "boxville", "benson", "mesa", "rcgoblin", "hotrina", "hotrinb", + "bloodra", "bloodrb", "vicechee" +}; + +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 +switchWeather(void) +{ + CWeather::StreamAfterRainTimer = 0; +} + +void +DebugMenuPopulate(void) +{ + if(1){ + static const char *weathers[] = { + "Sunny", "Cloudy", "Rainy", "Foggy", "Extrasunny", "Stormy" + }; + static const char *extracols[] = { + "1 - Malibu club", + "2 - Strib club", + "3 - Hotel", + "4 - Bank", + "5 - Police HQ", + "6 - Mall", + "7 - Rifle Range", + "8 - Mansion", + "9 - Dirt ring", + "10 - Blood ring", + "11 - Hot ring", + "12 - Concert hall", + "13 - Auntie Poulets", + "14 - Intro at docks", + "15 - Biker bar", + "16 - Intro cafe", + "17 - Studio", + "18", "19", "20", "21", "22", "23", "24" + }; + 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, switchWeather, 1, 0, 5, weathers); + DebugMenuEntrySetWrap(e, true); + e = DebugMenuAddVar("Time & Weather", "New Weather", (int16*)&CWeather::NewWeatherType, switchWeather, 1, 0, 5, weathers); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddVarBool32("Time & Weather", "Extracolours On", &CTimeCycle::m_bExtraColourOn, nil); + DebugMenuAddVar("Time & Weather", "Extracolour", &CTimeCycle::m_ExtraColour, nil, 1, 0, 23, extracols); + DebugMenuAddVar("Time & Weather", "Time scale", (float*)&CTimer::GetTimeScale(), nil, 0.1f, 0.0f, 10.0f); + + DebugMenuAddCmd("Cheats", "Weapon set 1", WeaponCheat1); + DebugMenuAddCmd("Cheats", "Weapon set 2", WeaponCheat2); + DebugMenuAddCmd("Cheats", "Weapon set 3", WeaponCheat3); + DebugMenuAddCmd("Cheats", "Money", MoneyCheat); + DebugMenuAddCmd("Cheats", "Health", HealthCheat); + DebugMenuAddCmd("Cheats", "Wanted level up", WantedLevelUpCheat); + DebugMenuAddCmd("Cheats", "Wanted level down", WantedLevelDownCheat); + DebugMenuAddCmd("Cheats", "Tank", []() { VehicleCheat(MI_TAXI); }); + 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", "Special car", SpecialCarCheats); + DebugMenuAddCmd("Cheats", "Pickup chicks", PickUpChicksCheat); + + static int32 spawnCarId = MI_LANDSTAL; + e = DebugMenuAddVar("Spawn", "Spawn Car ID", &spawnCarId, nil, 1, MI_LANDSTAL, MI_VICECHEE, carnames); + DebugMenuEntrySetWrap(e, true); + DebugMenuAddCmd("Spawn", "Spawn Car", [](){ + if(spawnCarId == MI_CHOPPER || + spawnCarId == MI_AIRTRAIN || + spawnCarId == MI_DEADDODO) + 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 Phoenix", [](){ SpawnCar(MI_PHEONIX); }); + DebugMenuAddCmd("Spawn", "Spawn Banshee", [](){ SpawnCar(MI_BANSHEE); }); + DebugMenuAddCmd("Spawn", "Spawn Esperanto", [](){ SpawnCar(MI_ESPERANT); }); + DebugMenuAddCmd("Spawn", "Spawn Stallion", [](){ SpawnCar(MI_STALLION); }); + DebugMenuAddCmd("Spawn", "Spawn Admiral", [](){ SpawnCar(MI_ADMIRAL); }); + DebugMenuAddCmd("Spawn", "Spawn Washington", [](){ SpawnCar(MI_WASHING); }); + DebugMenuAddCmd("Spawn", "Spawn Taxi", [](){ SpawnCar(MI_TAXI); }); + DebugMenuAddCmd("Spawn", "Spawn Police", [](){ SpawnCar(MI_POLICE); }); + DebugMenuAddCmd("Spawn", "Spawn Enforcer", [](){ SpawnCar(MI_ENFORCER); }); + DebugMenuAddCmd("Spawn", "Spawn Cuban", [](){ SpawnCar(MI_CUBAN); }); + DebugMenuAddCmd("Spawn", "Spawn Voodoo", [](){ SpawnCar(MI_VOODOO); }); + DebugMenuAddCmd("Spawn", "Spawn BF injection", [](){ SpawnCar(MI_BFINJECT); }); + DebugMenuAddCmd("Spawn", "Spawn Maverick", [](){ SpawnCar(MI_MAVERICK); }); + DebugMenuAddCmd("Spawn", "Spawn VCN Maverick", [](){ SpawnCar(MI_VCNMAV); }); + DebugMenuAddCmd("Spawn", "Spawn Sparrow", [](){ SpawnCar(MI_SPARROW); }); + DebugMenuAddCmd("Spawn", "Spawn Sea Sparrow", [](){ SpawnCar(MI_SEASPAR); }); + DebugMenuAddCmd("Spawn", "Spawn Hunter", [](){ SpawnCar(MI_HUNTER); }); + DebugMenuAddCmd("Spawn", "Spawn Rhino", [](){ SpawnCar(MI_RHINO); }); + DebugMenuAddCmd("Spawn", "Spawn Firetruck", [](){ SpawnCar(MI_FIRETRUCK); }); + DebugMenuAddCmd("Spawn", "Spawn Predator", [](){ SpawnCar(MI_PREDATOR); }); + DebugMenuAddCmd("Spawn", "Spawn PCJ 600", [](){ SpawnCar(MI_PCJ600); }); + DebugMenuAddCmd("Spawn", "Spawn Faggio", [](){ SpawnCar(MI_FAGGIO); }); + DebugMenuAddCmd("Spawn", "Spawn Freeway", [](){ SpawnCar(MI_FREEWAY); }); + DebugMenuAddCmd("Spawn", "Spawn Squalo", [](){ SpawnCar(MI_SQUALO); }); + DebugMenuAddCmd("Spawn", "Spawn Skimmer", [](){ SpawnCar(MI_SKIMMER); }); + + 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", "Backface Culling", &gBackfaceCulling, nil); + DebugMenuAddVarBool8("Render", "PS2 Alpha test Emu", &gPS2alphaTest, nil); + DebugMenuAddVarBool8("Render", "Frame limiter", (int8_t*)&FrontEndMenuManager.m_PrefsFrameLimiter, nil); + DebugMenuAddVarBool8("Render", "VSynch", (int8_t*)&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", "Blur", &CPostFX::BlurOn, nil); + DebugMenuAddVarBool8("Render", "Motion Blur", &CPostFX::MotionBlurOn, nil); +#endif + DebugMenuAddVar("Render", "Drunkness", &CMBlur::Drunkness, nil, 0.05f, 0, 1.0f); +#ifndef MASTER + DebugMenuAddVarBool8("Render", "Occlusion debug", &bDispayOccDebugStuff, 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 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", "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 RELOADABLES +// maybe put it back if we have more to reload +// DebugMenuAddCmd("Reload", "HUD.TXD", CHud::ReloadTXD); +#endif + +#ifdef MAP_ENHANCEMENTS + 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); + + +#ifdef MISSION_SWITCHER + DebugMenuEntry *missionEntry; + static const char* missions[] = { + "Initial", "Intro", "An Old Friend", "The Party", "Back Alley Brawl", "Jury Fury", "Riot", + "Treacherous Swine", "Mall Shootout", "Guardian Angels", "Sir, Yes Sir!", "All Hands On Deck!", + "The Chase", "Phnom Penh '86", "The Fastest Boat", "Supply & Demand", "Rub Out", "Death Row", + "Four Iron", "Demolition Man", "Two Bit Hit", "No Escape?", "The Shootist", "The Driver", + "The Job", "Gun Runner", "Boomshine Saigon", "Recruitment Drive", "Dildo Dodo", "Martha's Mug Shot", + "G-spotlight", "Shakedown", "Bar Brawl", "Cop Land", "Spilling the Beans", "Hit the Courier", + "Printworks Buy", "Sunshine Autos", "Interglobal Films Buy", "Cherry Popper Icecreams Buy", + "Kaufman Cabs Buy", "Malibu Club Buy", "The Boatyard Buy", "Pole Position Club Buy", "El Swanko Casa Buy", + "Links View Apartment Buy", "Hyman Condo Buy", "Ocean Heighs Aprt. Buy", "1102 Washington Street Buy", + "Vice Point Buy", "Skumole Shack Buy", "Cap the Collector", "Keep your Friends Close...", + "Alloy Wheels of Steel", "Messing with the Man", "Hog Tied", "Stunt Boat Challenge", "Cannon Fodder", + "Naval Engagement", "Trojan Voodoo", "Juju Scramble", "Bombs Away!", "Dirty Lickin's", "Love Juice", + "Psycho Killer", "Publicity Tour", "Weapon Range", "Road Kill", "Waste the Wife", "Autocide", + "Check Out at the Check In", "Loose Ends", "V.I.P.", "Friendly Rivalry", "Cabmaggedon", "TAXI DRIVER", + "PARAMEDIC", "FIREFIGHTER", "VIGILANTE", "HOTRING", "BLOODRING", "DIRTRING", "Sunshine Autos Races", + "Distribution", "Downtown Chopper Checkpoint", "Ocean Beach Chopper Checkpoint", "Vice Point Chopper Checkpoint", + "Little Haiti Chopper Checkpoint", "Trial by Dirt", "Test Track", "PCJ Playground", "Cone Crazy", + "PIZZA BOY", "RC Raider Pickup", "RC Bandit Race", "RC Baron Race", "Checkpoint Charlie" + }; + + missionEntry = DebugMenuAddVar("Game", "Select mission", (uint32_t*)&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", "Reset Statics", ResetCamStatics); + + CTweakVars::AddDBG("Debug"); + } +} +#endif + +#ifndef __MWERKS__ +#ifndef MASTER +const int re3_buffsize = 1024; +static char re3_buff[re3_buffsize]; +#endif + +extern void stacktrace(); + +#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, "REVC 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 + fflush(stdout); + fflush(stderr); + dbglog(DBG_CRITICAL, "\nRE3 ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); + dbglog(DBG_CRITICAL, "POSIX error (may not be relevant): %s\n", strerror(errno)); + #if defined(DC_SIM) || defined(DC_TEXCONV) + for(;;); + #else + stacktrace(); + dbgio_dev_select("fb"); + sleep(1); + dbglog(DBG_CRITICAL, "RE3 ASSERT FAILED\n\tFile: %s\n\tLine: %d\n\tFunction: %s\n\tExpression: %s\n",filename,lineno,func,expr); + dbglog(DBG_CRITICAL, "POSIX error (may not be relevant): %s\n", strerror(errno)); + stacktrace(); + dbgio_flush(); + abort(); + #endif +#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, "REVC Error!", + MB_OK|MB_ICONHAND|MB_SETFOREGROUND|MB_TASKMODAL); + + raise(SIGABRT); + _exit(3); +#else + vsprintf(re3_buff, format, va); + printf("\nREVC Error!\n\t%s\n",re3_buff); + assert(false); +#endif +} +#endif +#endif + +#ifdef VALIDATE_SAVE_SIZE +int32 _saveBufCount; +#endif diff --git a/src/miami/core/templates.h b/src/miami/core/templates.h new file mode 100644 index 00000000..7bc85ee6 --- /dev/null +++ b/src/miami/core/templates.h @@ -0,0 +1,270 @@ +#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, const char *name){ + m_entries = (U*)new uint8[sizeof(U)*size]; + m_flags = new uint8[size]; + m_size = size; + m_allocPtr = -1; + 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((U*)entry == (U*)&m_entries[index]); // cast is unsafe - check required + assert(!GetIsFree(index)); + return index; + } + int32 GetJustIndex_NoFreeAssert(T* entry) { + int index = ((U*)entry - m_entries); + // Please don't add unsafe assert here, because at least one func. use this to check if entity is ped or vehicle. + 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 */ + } + int32 GetNoOfFreeSpaces() const { return GetSize() - GetNoOfUsedSpaces(); } +}; + +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/miami/core/timebars.cpp b/src/miami/core/timebars.cpp new file mode 100644 index 00000000..169fef8c --- /dev/null +++ b/src/miami/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_STANDARD); + 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/miami/core/timebars.h b/src/miami/core/timebars.h new file mode 100644 index 00000000..c4939802 --- /dev/null +++ b/src/miami/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/miami/entities/Dummy.cpp b/src/miami/entities/Dummy.cpp new file mode 100644 index 00000000..d62d2434 --- /dev/null +++ b/src/miami/entities/Dummy.cpp @@ -0,0 +1,67 @@ +#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); + } +} + +bool +IsDummyPointerValid(CDummy* pDummy) +{ + if (!pDummy) + return false; + int index = CPools::GetDummyPool()->GetJustIndex_NoFreeAssert(pDummy); +#ifdef FIX_BUGS + if (index < 0 || index >= CPools::GetDummyPool()->GetSize()) +#else + if (index < 0 || index > CPools::GetDummyPool()->GetSize()) +#endif + return false; + return pDummy->m_entryInfoList.first; +} diff --git a/src/miami/entities/Dummy.h b/src/miami/entities/Dummy.h new file mode 100644 index 00000000..9b73eefc --- /dev/null +++ b/src/miami/entities/Dummy.h @@ -0,0 +1,19 @@ +#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(); +}; + +bool IsDummyPointerValid(CDummy* pDummy); diff --git a/src/miami/entities/Entity.cpp b/src/miami/entities/Entity.cpp new file mode 100644 index 00000000..b2fcfbc7 --- /dev/null +++ b/src/miami/entities/Entity.cpp @@ -0,0 +1,906 @@ +#include "common.h" + +#include "VuVector.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 "Ped.h" +#include "Dummy.h" +#include "WindModifiers.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; + bStreamBIGBuilding = false; + + bRenderDamaged = false; + bBulletProof = false; + bFireProof = false; + bCollisionProof = false; + bMeleeProof = false; + bOnlyDamagedByPlayer = false; + bStreamingDontDelete = false; + bRemoveFromWorld = false; + + bHasHitWall = false; + bImBeingRendered = false; + bTouchingWater = false; + bIsSubway = false; + bDrawLast = false; + bNoBrightHeadLights = false; + bDoNotRender = false; + bDistanceFade = false; + + m_flagE1 = false; + bDontCastShadowsOn = false; + bOffscreen = false; + bIsStaticWaitingForCollision = false; + bDontStream = false; + bUnderwater = false; + bHasPreRenderEffects = false; + + m_scanCode = 0; + m_modelIndex = -1; + m_rwObject = nil; + m_area = AREA_MAIN_MAP; + m_randomSeed = CGeneral::GetRandomNumber(); + m_pFirstReference = nil; +} + +CEntity::~CEntity(void) +{ + DeleteRwObject(); + ResolveReferences(); +} + +void +CEntity::SetModelIndex(uint32 id) +{ + m_modelIndex = id; + bHasPreRenderEffects = HasPreRenderEffects(); + CreateRwObject(); +} + +void +CEntity::SetModelIndexNoCreate(uint32 id) +{ + m_modelIndex = id; + bHasPreRenderEffects = HasPreRenderEffects(); +} + +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(); +} + +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->currentAnim){ + RpHAnimAnimationDestroy(hier->currentAnim->pCurrentAnim); + hier->currentAnim = nil; + } +#endif + } + return atomic; +} + +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){ + if(IsClumpSkinned((RpClump*)m_rwObject)) + RpClumpForAllAtomics((RpClump*)m_rwObject, AtomicRemoveAnimFromSkinCB, nil); + 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)); +} + +void +CEntity::UpdateRpHAnim(void) +{ + if(IsClumpSkinned(GetClump())){ + 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 + } +} + +bool +CEntity::HasPreRenderEffects(void) +{ + return IsTreeModel(GetModelIndex()) || + GetModelIndex() == MI_COLLECTABLE1 || + GetModelIndex() == MI_MONEY || + GetModelIndex() == MI_CARMINE || + GetModelIndex() == MI_NAUTICALMINE || + GetModelIndex() == MI_BRIEFCASE || + GetModelIndex() == MI_GRENADE || + GetModelIndex() == MI_MOLOTOV || + GetModelIndex() == MI_MISSILE || + GetModelIndex() == MI_BEACHBALL || + IsGlass(GetModelIndex()) || + IsObject() && ((CObject*)this)->bIsPickup || + IsLightWithPreRenderEffects(GetModelIndex()); +} + +void +CEntity::PreRender(void) +{ + if (CModelInfo::GetModelInfo(GetModelIndex())->GetNum2dEffects() != 0) + ProcessLightsForEntity(); + + if(!bHasPreRenderEffects) + return; + + switch(m_type){ + case ENTITY_TYPE_BUILDING: + if(IsTreeModel(GetModelIndex())){ + float dist = (TheCamera.GetPosition() - GetPosition()).Magnitude2D(); + CObject::fDistToNearestTree = Min(CObject::fDistToNearestTree, dist); + ModifyMatrixForTreeInWind(); + } + 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(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, nil, false); + 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(); + }else 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_BEACHBALL){ + CVector pos = GetPosition(); + CShadows::StoreShadowToBeRendered(SHADOWTYPE_DARK, + gpShadowPedTex, &pos, + 0.4f, 0.0f, 0.0f, 0.4f, + CTimeCycle::GetShadowStrength(), + CTimeCycle::GetShadowStrength(), + CTimeCycle::GetShadowStrength(), + CTimeCycle::GetShadowStrength(), + 20.0f, false, 1.0f, nil, false); + } + // 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_TRAFFICLIGHTS_VERTICAL){ + CTrafficLights::DisplayActualLight(this); + }else if(GetModelIndex() == MI_TRAFFICLIGHTS_MIAMI){ + CTrafficLights::DisplayActualLight(this); + CShadows::StoreShadowForPole(this, 4.819f, 1.315f, 0.0f, 16.0f, 0.4f, 0); + }else if(GetModelIndex() == MI_TRAFFICLIGHTS_TWOVERTICAL){ + CTrafficLights::DisplayActualLight(this); + CShadows::StoreShadowForPole(this, 7.503f, 0.0f, 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); + break; + } +} + +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(mi->m_lodDistances[0] <= 2000.0f) + bStreamBIGBuilding = true; + if(mi->m_lodDistances[0] > 2500.0f || mi->m_ignoreDrawDist) + m_level = LEVEL_GENERIC; + else if(m_level == LEVEL_GENERIC) + printf("%s isn't in a level\n", mi->GetModelName()); +} + +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 + 8*CTimer::GetTimeInMilliseconds(); + f = (t & 0xFFF)/(float)0x1000; + flutter = f * WindTabel[(t>>12)+1 & 0xF] + + (1.0f - f) * WindTabel[(t>>12) & 0xF] + + 1.0f; + strength = -0.015f*CWeather::Wind; + }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; + if(IsPalmTreeModel(GetModelIndex())) + mat.GetUp().x += -0.07f*CWeather::Wind; + mat.GetUp().y = mat.GetUp().x; + + CWindModifiers::FindWindModifier(GetPosition(), &mat.GetUp().x, &mat.GetUp().y); + + 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 +}; + +// unused +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) +{ + if(((CSimpleModelInfo*)CModelInfo::GetModelInfo(m_modelIndex))->m_isArtistGlass) + return; + CGlass::AskForObjectToBeRenderedInGlass(this); + bIsVisible = false; +} + +RpMaterial* +SetAtomicAlphaCB(RpMaterial *material, void *data) +{ + ((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data; + return material; +} + +RpAtomic* +SetClumpAlphaCB(RpAtomic *atomic, void *data) +{ + RpGeometry *geometry = RpAtomicGetGeometry(atomic); + RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geometry, SetAtomicAlphaCB, (void*)data); + return atomic; +} + +void +CEntity::SetRwObjectAlpha(int32 alpha) +{ + if (m_rwObject != nil) { + switch (RwObjectGetType(m_rwObject)) { + case rpATOMIC: { + RpGeometry *geometry = RpAtomicGetGeometry((RpAtomic*)m_rwObject); + RpGeometrySetFlags(geometry, RpGeometryGetFlags(geometry) | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geometry, SetAtomicAlphaCB, (void*)alpha); + break; + } + case rpCLUMP: + RpClumpForAllAtomics((RpClump*)m_rwObject, SetClumpAlphaCB, (void*)alpha); + break; + } + } +} + +bool IsEntityPointerValid(CEntity* pEntity) +{ + if (!pEntity) + return false; + switch (pEntity->GetType()) { + case ENTITY_TYPE_NOTHING: return false; + case ENTITY_TYPE_BUILDING: return IsBuildingPointerValid((CBuilding*)pEntity); + case ENTITY_TYPE_VEHICLE: return IsVehiclePointerValid((CVehicle*)pEntity); + case ENTITY_TYPE_PED: return IsPedPointerValid((CPed*)pEntity); + case ENTITY_TYPE_OBJECT: return IsObjectPointerValid((CObject*)pEntity); + case ENTITY_TYPE_DUMMY: return IsDummyPointerValid((CDummy*)pEntity); + } + return 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 (bStreamBIGBuilding) tmp |= BIT(23); + + if (bRenderDamaged) tmp |= BIT(24); + if (bBulletProof) tmp |= BIT(25); + if (bFireProof) tmp |= BIT(26); + if (bCollisionProof) tmp |= BIT(27); + if (bMeleeProof) tmp |= BIT(28); + if (bOnlyDamagedByPlayer) tmp |= BIT(29); + if (bStreamingDontDelete) tmp |= BIT(30); + if (bRemoveFromWorld) tmp |= BIT(31); + + WriteSaveBuf(buf, tmp); + + tmp = 0; + + if (bHasHitWall) tmp |= BIT(0); + if (bImBeingRendered) tmp |= BIT(1); + if (bTouchingWater) tmp |= BIT(2); + if (bIsSubway) tmp |= BIT(3); + if (bDrawLast) tmp |= BIT(4); + if (bNoBrightHeadLights) tmp |= BIT(5); + if (bDoNotRender) tmp |= BIT(6); + if (bDistanceFade) tmp |= BIT(7); + + if (m_flagE1) tmp |= BIT(8); + if (bDontCastShadowsOn) tmp |= BIT(9); + if (bOffscreen) tmp |= BIT(10); + if (bIsStaticWaitingForCollision) tmp |= BIT(11); + if (bDontStream) tmp |= BIT(12); + if (bUnderwater) tmp |= BIT(13); + if (bHasPreRenderEffects) tmp |= BIT(14); + + 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)); + bStreamBIGBuilding = !!(tmp & BIT(23)); + + bRenderDamaged = !!(tmp & BIT(24)); + bBulletProof = !!(tmp & BIT(25)); + bFireProof = !!(tmp & BIT(26)); + bCollisionProof = !!(tmp & BIT(27)); + bMeleeProof = !!(tmp & BIT(28)); + bOnlyDamagedByPlayer = !!(tmp & BIT(29)); + bStreamingDontDelete = !!(tmp & BIT(30)); + bRemoveFromWorld = !!(tmp & BIT(31)); + + ReadSaveBuf(&tmp, buf); + + bHasHitWall = !!(tmp & BIT(0)); + bImBeingRendered = !!(tmp & BIT(1)); + bTouchingWater = !!(tmp & BIT(2)); + bIsSubway = !!(tmp & BIT(3)); + bDrawLast = !!(tmp & BIT(4)); + bNoBrightHeadLights = !!(tmp & BIT(5)); + bDoNotRender = !!(tmp & BIT(6)); + bDistanceFade = !!(tmp & BIT(7)); + + m_flagE1 = !!(tmp & BIT(8)); + bDontCastShadowsOn = !!(tmp & BIT(9)); + bOffscreen = !!(tmp & BIT(10)); + bIsStaticWaitingForCollision = !!(tmp & BIT(11)); + bDontStream = !!(tmp & BIT(12)); + bUnderwater = !!(tmp & BIT(13)); + bHasPreRenderEffects = !!(tmp & BIT(14)); +} + +#endif diff --git a/src/miami/entities/Entity.h b/src/miami/entities/Entity.h new file mode 100644 index 00000000..957ee3bf --- /dev/null +++ b/src/miami/entities/Entity.h @@ -0,0 +1,184 @@ +#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, + STATUS_GHOST +}; + +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 bStreamBIGBuilding : 1; // set when draw dist <= 2000 + + // flagsC + uint32 bRenderDamaged : 1; // use damaged LOD models for objects with applicable damage + 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 bRemoveFromWorld : 1; // remove this entity next time it should be processed + + // flagsD + 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; //-- only applies to CObjects apparently + uint32 bDistanceFade : 1; // Fade entity because it is far away + + // flagsE + uint32 m_flagE1 : 1; + uint32 bDontCastShadowsOn : 1; // Dont cast shadows on this object + uint32 bOffscreen : 1; // offscreen flag. This can only be trusted when it is set to true + uint32 bIsStaticWaitingForCollision : 1; // this is used by script created entities - they are static until the collision is loaded below them + uint32 bDontStream : 1; // tell the streaming not to stream me + uint32 bUnderwater : 1; // this object is underwater change drawing order + uint32 bHasPreRenderEffects : 1; // Object has a prerender effects attached to it + + uint16 m_scanCode; + uint16 m_randomSeed; + int16 m_modelIndex; + int8 m_level; + int8 m_area; + 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 || bIsStaticWaitingForCollision; } + 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); + virtual ~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); + bool IsEntityOccluded(void); + int16 GetModelIndex(void) const { return m_modelIndex; } + void UpdateRwFrame(void); + void SetupBigBuilding(void); + bool HasPreRenderEffects(void); + + void AttachToRwObject(RwObject *obj); + void DetachFromRwObject(void); + + void RegisterReference(CEntity **pent); + void ResolveReferences(void); + void PruneReferences(void); + void CleanUpOldReference(CEntity **pent); + + void UpdateRpHAnim(void); + + void PreRenderForGlassWindow(void); + void AddSteamsFromGround(CVector *unused); + void ModifyMatrixForTreeInWind(void); + void ModifyMatrixForBannerInWind(void); + void ProcessLightsForEntity(void); + void SetRwObjectAlpha(int32 alpha); +}; + +bool IsEntityPointerValid(CEntity*); diff --git a/src/miami/entities/Physical.cpp b/src/miami/entities/Physical.cpp new file mode 100644 index 00000000..fb796fcd --- /dev/null +++ b/src/miami/entities/Physical.cpp @@ -0,0 +1,2295 @@ +#include "common.h" + +#include "World.h" +#include "General.h" +#include "Timer.h" +#include "Stats.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 "Bike.h" +#include "Pickups.h" +#include "Physical.h" + +#ifdef WALLCLIMB_CHEAT +bool gGravityCheat; +#endif + + +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; + bJustCheckCollision = 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; + m_phy_flagA08 = false; + bIsInWater = false; + bHitByTrain = false; + bSkipLineCol = false; + + m_fDistanceTravelled = 0.0f; + + m_phy_flagA20 = false; + +#ifdef FIX_BUGS + m_nSurfaceTouched = SURFACE_DEFAULT; +#endif + m_nZoneLevel = LEVEL_GENERIC; + + bIsFrozen = false; + bDontLoadCollision = false; +} + +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) +{ + if (!bIsStaticWaitingForCollision) + 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; + this->m_nLastTimeCollided = CTimer::GetTimeInMilliseconds(); + 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; + } +} + +void +CPhysical::AddCollisionRecord_Treadable(CEntity *ent) +{ + if(ent->IsBuilding() && ((CBuilding*)ent)->GetIsATreadable()){ + } +} + +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) +{ + if(bIsFrozen) + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + else + GetMatrix().Translate(m_vecMoveSpeed * CTimer::GetTimeStep()); +} + +void +CPhysical::ApplyTurnSpeed(void) +{ + if(bIsFrozen){ + m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + }else{ + // 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; +} + +bool +CPhysical::ApplySpringCollisionAlt(float springConst, CVector &springDir, CVector &point, float springRatio, float bias, CVector &forceDir) +{ + float compression = 1.0f - springRatio; + if(compression > 0.0f){ + if(DotProduct(springDir, forceDir) > 0.0f) + forceDir *= -1.0f; + float step = Min(CTimer::GetTimeStep(), 3.0f); + float impulse = GRAVITY*m_fMass*step * springConst * compression * bias*2.0f; + if(bIsHeavy) + impulse *= 0.75f; + ApplyMoveForce(forceDir*impulse); + ApplyTurnForce(forceDir*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; + if(bIsHeavy) + impulse *= 2.0f; + + // 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) + return; +#ifdef WALLCLIMB_CHEAT + if (gGravityCheat && this == FindPlayerVehicle()) { + static CVector gravityUp(0.0f, 0.0f, 1.0f), surfaceUp(0.0f, 0.0f, 1.0f); + CVector belowCar = GetPosition() - 2.0f*GetUp(); + CColPoint point; + CEntity* entity; + if (CWorld::ProcessLineOfSight(GetPosition(), belowCar, point, entity, true, false, false, false, false, false)) + surfaceUp = point.normal; + else + surfaceUp = CVector(0.0f, 0.0f, 1.0f); + float t = Clamp(CTimer::GetTimeStep() * 0.5f, 0.05f, 0.8f); + gravityUp = gravityUp * (1.0f - t) + surfaceUp * t; + if (gravityUp.MagnitudeSqr() < 0.1f) + gravityUp = CVector(0.0f, 0.0f, 1.0f); + else + gravityUp.Normalise(); + m_vecMoveSpeed -= GRAVITY * CTimer::GetTimeStep() * gravityUp; + return; + } +#endif + 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 if(GetStatus() != STATUS_GHOST){ + float f = Pow(1.0f/Abs(1.0f + m_fAirResistance*0.5f*m_vecMoveSpeed.MagnitudeSqr()), 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 foo = false; // TODO: what does this mean? + 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 = 1.0f/(Max(B->m_fMass - 2000.0f, 0.0f)/5000.0f + 1.0f); + else + massFactorB = 10.0f; + + if(A->IsPed() && ((CPed*)A)->m_pCurrentPhysSurface == B) + ispedcontactB = true; + }else + massFactorB = B->bIsHeavy ? 2.0f : 1.0f; + + if(B->bInfiniteMass && !B->m_phy_flagA08){ + ispedcontactB = false; + foo = true; + } + + float speedA, speedB; + if(B->GetIsStatic() && !foo){ + 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); + CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 2; + CStats::PropertyDestroyed += CGeneral::GetRandomNumberInRange(30, 60); + } + }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((model == MI_PARKINGMETER || model == MI_PARKINGMETER2) && !Bobj->bHasBeenDamaged){ + CPickups::CreateSomeMoney(GetPosition(), CGeneral::GetRandomNumber()%100); + 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); + CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 2; + CStats::PropertyDestroyed += CGeneral::GetRandomNumberInRange(30, 60); + } + } + } + + if(B->GetIsStatic()) + return false; + if(!B->bInfiniteMass && !B->bIsStaticWaitingForCollision) + 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 + speedB = DotProduct(B->m_vecMoveSpeed, colpoint.normal); + + bool affectB = false; + float mA = A->m_fMass; + float mB = B->m_fMass; + float speedSum; + if(((CPed*)A)->GetPedState() == PED_FOLLOW_PATH){ + affectB = true; + speedSum = (2.0f*mA*speedA + mB*speedB)/(2.0f*mA + mB); + }else{ + speedSum = Max(speedB, 0.0f); + } + + if(speedA < speedSum){ + if(A->bHasHitWall) + eA = speedSum; + else + eA = speedSum - (speedA - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseA = (eA-speedA) * mA; + if(!A->bInfiniteMass) + A->ApplyMoveForce(colpoint.normal*impulseA); + if(affectB && speedB < speedSum){ + if(B->bHasHitWall) + eB = speedSum; + else + eB = speedSum - (speedB - speedSum) * (A->m_fElasticity+B->m_fElasticity)/2.0f; + impulseB = -(eB-speedB) * mB; + if(!B->bInfiniteMass) + B->ApplyMoveForce(colpoint.normal*-impulseB); + } + 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; + if(foo) + speedSum = speedB; + else + 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::ApplyCollision(CColPoint &colpoint, float &impulse) +{ + float speed; + if(bPedPhysics){ + speed = DotProduct(m_vecMoveSpeed, colpoint.normal); + if(speed < 0.0f){ + impulse = -speed * m_fMass; + ApplyMoveForce(colpoint.normal*impulse); + return true; + } + }else{ + CVector pointpos = colpoint.point - GetPosition(); + speed = DotProduct(GetSpeed(pointpos), colpoint.normal); + + if(speed < 0.0f){ + float mass = GetMass(pointpos, colpoint.normal); + impulse = -(m_fElasticity + 1.0f) * speed * mass; + CVector f = colpoint.normal*impulse; + if(IsVehicle()){ + f.x *= 1.4f; + f.y *= 1.4f; + if(colpoint.normal.z < 0.7f) + f.z *= 0.3f; + } + if(!bInfiniteMass){ + ApplyMoveForce(f); + if(!IsVehicle() || !CWorld::bNoMoreCollisionTorque) + ApplyTurnForce(f, pointpos); + } + return true; + } + } + + return false; +} + +bool +CPhysical::ApplyCollisionAlt(CEntity *B, CColPoint &colpoint, float &impulse, CVector &moveSpeed, CVector &turnSpeed) +{ + float normalSpeed; + CVector speed; + CVector vImpulse; + + if(GetModelIndex() == MI_BEACHBALL && B != (CEntity*)FindPlayerPed()) + ((CObject*)this)->m_nBeachballBounces = 0; + + 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){ + int16 elasticityType = 0; + float mass = GetMass(pointpos, colpoint.normal); + float minspeed = GRAVITY * CTimer::GetTimeStep(); + + if(IsObject()) + elasticityType = 1; + else if(IsVehicle() && !bIsInWater){ + if(((CVehicle*)this)->IsBike() && (GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED)){ + minspeed *= 1.3f; + elasticityType = 3; + }else if(((CVehicle*)this)->IsBoat()){ + minspeed *= 1.2f; + elasticityType = 4; + }else if(GetUp().z < -0.3f){ + minspeed *= 1.1f; + elasticityType = 2; + } + } + + if(elasticityType == 1 && !bHasContacted && + Abs(m_vecMoveSpeed.x) < minspeed && + Abs(m_vecMoveSpeed.y) < minspeed && + Abs(m_vecMoveSpeed.z) < minspeed*2.0f) + impulse = -0.98f * normalSpeed * mass; + if(elasticityType == 3 && + Abs(m_vecMoveSpeed.x) < minspeed && + Abs(m_vecMoveSpeed.y) < minspeed && + Abs(m_vecMoveSpeed.z) < minspeed*2.0f) + impulse = -0.8f * normalSpeed * mass; + else if(elasticityType == 2 && + Abs(m_vecMoveSpeed.x) < minspeed && + Abs(m_vecMoveSpeed.y) < minspeed && + Abs(m_vecMoveSpeed.z) < minspeed*2.0f) + impulse = -0.92f * normalSpeed * mass; + else if(elasticityType == 4 && + Abs(m_vecMoveSpeed.x) < minspeed && + Abs(m_vecMoveSpeed.y) < minspeed && + Abs(m_vecMoveSpeed.z) < minspeed*2.0f) + impulse = -0.8f * normalSpeed * mass; + else if(IsVehicle() && ((CVehicle*)this)->IsBoat() && + (colpoint.surfaceB == SURFACE_WOOD_SOLID || colpoint.normal.z < 0.5f)) + impulse = -(2.0f * m_fElasticity + 1.0f) * normalSpeed * mass; + else + impulse = -(m_fElasticity + 1.0f) * normalSpeed * mass; + + // ApplyMoveForce + vImpulse = colpoint.normal*impulse; + if(IsVehicle()){ + if(!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); + vImpulse *= 0.8f; + }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(IsLightWithoutShift(A->GetModelIndex()) && + (B->IsVehicle() || B->IsPed()) && + A->GetUp().z < 0.66f) + skipShift = true; + else if((A->IsVehicle() || A->IsPed()) && + B->GetUp().z < 0.66f && + IsLightWithoutShift(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; + + if(!A->bUsesCollision) + return false; + + 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->IsObject() && ((CObject*)B)->bIsStreetLight && B->GetUp().z < 0.66f) && + 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; + bool ret = false; + 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->bUsesCollision || + B->m_scanCode == CWorld::GetCurrentScanCode() || + B == A) + 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(A->IsObject() && Aobj->bIsStreetLight && + (B->IsVehicle() || B->IsPed()) && + A->GetUp().z < 0.66f){ + skipCollision = true; + A->bSkipLineCol = true; + Aobj->m_pCollidingEntity = B; + }else if(B->IsObject() && Bobj->bIsStreetLight && + (A->IsVehicle() || A->IsPed()) && + B->GetUp().z < 0.66f){ + skipCollision = true; + A->bSkipLineCol = true; + Bobj->m_pCollidingEntity = A; + }else if(A->IsObject() && B->IsVehicle()){ + if(A->GetModelIndex() == MI_CAR_BUMPER) + skipCollision = true; + else if(Aobj->ObjectCreatedBy == TEMP_OBJECT || + Aobj->bHasBeenDamaged || + !Aobj->GetIsStatic()){ + if(Aobj->m_pCollidingEntity == B) + skipCollision = true; + else if(Aobj->m_nCollisionDamageEffect < DAMAGE_EFFECT_SMASH_COMPLETELY){ + 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) + skipCollision = true; + else if(Bobj->ObjectCreatedBy == TEMP_OBJECT || + Bobj->bHasBeenDamaged || + !Bobj->GetIsStatic()){ + if(Bobj->m_pCollidingEntity == A) + skipCollision = true; + else if(Bobj->m_nCollisionDamageEffect < DAMAGE_EFFECT_SMASH_COMPLETELY){ + 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(A->GetModelIndex() == MI_GRENADE && B->IsPed() && + A->GetPosition().z < B->GetPosition().z){ + skipCollision = true; + }else if(B->GetModelIndex() == MI_GRENADE && A->IsPed() && + B->GetPosition().z < A->GetPosition().z){ + skipCollision = true; + A->bSkipLineCol = true; + }else if(A->IsPed() && Aped->m_pCollidingEntity == B){ + skipCollision = true; + if(!Aped->bKnockedUpIntoAir || Aped->bKnockedOffBike) + 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(); + numCollisions = A->ProcessEntityCollision(B, aColPoints); + if(A->bJustCheckCollision && numCollisions > 0) + return true; + if(numCollisions == 0 && A == (CEntity*)FindPlayerPed() && Aped->m_pCollidingEntity == B) + Aped->m_pCollidingEntity = nil; + }else if(B->IsBuilding() || B->bIsStuck || B->m_phy_flagA08 || 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); + float maxImpulseA = 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 > maxImpulseA) maxImpulseA = impulseA; + + if(A->IsVehicle()){ + if(!(((CVehicle*)A)->IsBoat() && aColPoints[i].surfaceB == SURFACE_WOOD_SOLID) && + impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(CSurfaceTable::GetAdhesionGroup(aColPoints[i].surfaceB) == ADHESIVE_SAND) + aColPoints[i].surfaceB = SURFACE_SAND; + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + if(A->GetUp().z < -0.6f && + Abs(A->m_vecMoveSpeed.x) < 0.05f && + Abs(A->m_vecMoveSpeed.y) < 0.05f) + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, 0.1f*impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + else + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + }else{ + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + } + } + }else{ + for(i = 0; i < numCollisions; i++){ + if(!A->ApplyCollisionAlt(B, aColPoints[i], impulseA, moveSpeed, turnSpeed)) + continue; + + numResponses++; + if(impulseA > maxImpulseA) maxImpulseA = impulseA; + float adhesion = CSurfaceTable::GetAdhesiveLimit(aColPoints[i]) / numCollisions; + + if(A->IsVehicle()){ + if(((CVehicle*)A)->IsBoat() && aColPoints[i].surfaceB == SURFACE_WOOD_SOLID) + adhesion = 0.0f; + else if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + if(CSurfaceTable::GetAdhesionGroup(aColPoints[i].surfaceB) == ADHESIVE_SAND) + aColPoints[i].surfaceB = SURFACE_SAND; + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + if(A->GetUp().z < -0.6f && + Abs(A->m_vecMoveSpeed.x) < 0.05f && + Abs(A->m_vecMoveSpeed.y) < 0.05f) + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, 0.1f*impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + else + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + + + if(A->GetModelIndex() == MI_RCBANDIT) + adhesion *= 0.2f; + else if(((CVehicle*)A)->IsBoat()){ + if(aColPoints[i].normal.z > 0.6f){ + if(CSurfaceTable::GetAdhesionGroup(aColPoints[i].surfaceB) == ADHESIVE_LOOSE || + CSurfaceTable::GetAdhesionGroup(aColPoints[i].surfaceB) == ADHESIVE_SAND) + adhesion *= 3.0f; + }else + adhesion = 0.0f; + }else 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); + }else{ + if(impulseA > A->m_fDamageImpulse) + A->SetDamagedPieceRecord(aColPoints[i].pieceA, impulseA, B, aColPoints[i].normal); + + float turnSpeedDiff = A->m_vecTurnSpeed.MagnitudeSqr(); + float moveSpeedDiff = A->m_vecMoveSpeed.MagnitudeSqr(); + + DMAudio.ReportCollision(A, B, aColPoints[i].surfaceA, aColPoints[i].surfaceB, impulseA, Max(turnSpeedDiff, moveSpeedDiff)); + } + + 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->bIsInWater){ + 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; + } + + if(B->IsObject() && Bobj->m_nCollisionDamageEffect && maxImpulseA > 20.0f) + Bobj->ObjectDamage(maxImpulseA); + + if(!CWorld::bSecondShift) + return true; + ret = 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(Bobj->m_nCollisionDamageEffect >= DAMAGE_EFFECT_SMASH_COMPLETELY){ + 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) + Bobj->ObjectDamage(50.0f); + } + }else if(A->IsObject() && A->bUsesCollision && B->IsVehicle()){ + if(Aobj->m_nCollisionDamageEffect && maxImpulseB > 20.0f) + Aobj->ObjectDamage(maxImpulseB); +#ifdef FIX_BUGS + else if(Aobj->m_nCollisionDamageEffect >= DAMAGE_EFFECT_SMASH_COMPLETELY){ +#else + else if(Bobj->m_nCollisionDamageEffect >= DAMAGE_EFFECT_SMASH_COMPLETELY){ +#endif + 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) + Aobj->ObjectDamage(50.0f); + } + } + + if(B->GetStatus() == STATUS_SIMPLE){ + B->SetStatus(STATUS_PHYSICS); + if(B->IsVehicle()) + CCarCtrl::SwitchVehicleToRealPhysics((CVehicle*)B); + } + + if(!CWorld::bSecondShift) + return true; + ret = true; + } + + } + } + + return ret; +} + +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; +} + +float PHYSICAL_SHIFT_SPEED_DAMP = 0.707f; + +void +CPhysical::ProcessShift(void) +{ + m_fDistanceTravelled = 0.0f; + if(GetStatus() == STATUS_SIMPLE){ + bIsStuck = false; + bIsInSafePosition = true; + RemoveAndAdd(); + }else{ + CPhysical *surf; + if(bHasHitWall && (IsPed() && (surf = ((CPed*)this)->m_pCurrentPhysSurface, surf == nil || !surf->bInfiniteMass || surf->m_phy_flagA08) || + CWorld::bSecondShift)){ + m_vecMoveSpeed *= Pow(PHYSICAL_SHIFT_SPEED_DAMP, CTimer::GetTimeStep()); + m_vecTurnSpeed *= Pow(PHYSICAL_SHIFT_SPEED_DAMP, CTimer::GetTimeStep()); + } + + 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(); + bool hadCollision = false; + for(node = m_entryInfoList.first; node; node = node->next) + if(ProcessCollisionSectorList(node->sector->m_lists)){ + if(!CWorld::bSecondShift){ + GetMatrix() = matrix; + return; + } + hadCollision = true; + } + if(hadCollision){ + 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))) + +float HIGHSPEED_ELASTICITY_MULT_PED = 2.0f; +float HIGHSPEED_ELASTICITY_MULT_COPCAR = 2.0f; + +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 savedElasticity = m_fElasticity; + CVector savedMoveSpeed = m_vecMoveSpeed; + 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.3f) || ped->IsPlayer())){ + if(ped->IsPlayer()){ + if(ped->m_pCurrentPhysSurface) + n = Max(NUMSTEPS(0.15f), 4.0f); + else + n = Max(NUMSTEPS(0.3f), 2.0f); + }else + n = NUMSTEPS(0.45f); + step = savedTimeStep / n; + if(!ped->IsPlayer()) + ped->m_fElasticity *= HIGHSPEED_ELASTICITY_MULT_PED; + }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; + + CVector bbox = GetColModel()->boundingBox.GetSize(); + float relDistX = Abs(DotProduct(m_vecMoveSpeed, GetRight())) * CTimer::GetTimeStep() / bbox.x; + float relDistY = Abs(DotProduct(m_vecMoveSpeed, GetForward())) * CTimer::GetTimeStep() / bbox.y; + float relDistZ = Abs(DotProduct(m_vecMoveSpeed, GetUp())) * CTimer::GetTimeStep() / bbox.z; + if(Max(relDistX, Max(relDistY, relDistZ)) < 1.0f){ + // check if we can get away with simplified processing + + ApplyMoveSpeed(); + ApplyTurnSpeed(); + GetMatrix().Reorthogonalise(); + bSkipLineCol = false; + m_bIsVehicleBeingShifted = false; + + bJustCheckCollision = true; + bool savedUsesCollision = bUsesCollision; + bUsesCollision = false; + if(!CheckCollision()){ + bJustCheckCollision = false; + bUsesCollision = savedUsesCollision; + if(IsVehicle()) + ((CVehicle*)this)->bVehicleColProcessed = true; + + bHitByTrain = false; + m_fDistanceTravelled = (GetPosition() - savedMatrix.GetPosition()).Magnitude(); + bSkipLineCol = false; + + bIsStuck = false; + bIsInSafePosition = true; + m_fElasticity = savedElasticity; + RemoveAndAdd(); + return; + } + bJustCheckCollision = false; + bUsesCollision = savedUsesCollision; + GetMatrix() = savedMatrix; + m_vecMoveSpeed = savedMoveSpeed; + if(IsVehicle() && ((CVehicle*)this)->bIsLawEnforcer) + m_fElasticity *= HIGHSPEED_ELASTICITY_MULT_COPCAR; + } + }else if(IsObject() && ((CObject*)this)->ObjectCreatedBy != TEMP_OBJECT){ + 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); + CColModel *colModel = GetColModel(); + speedUp.z = colModel->boundingBox.max.z; + speedDown.z = colModel->boundingBox.min.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); + m_fElasticity = savedElasticity; + 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() || + bHitByTrain || + GetStatus() == STATUS_PLAYER || + IsVehicle() && ((CVehicle*)this)->bRestingOnPhysical || + IsPed() && ped->IsPlayer()){ + if(IsVehicle()) + ((CVehicle*)this)->bVehicleColProcessed = true; + if(CheckCollision()){ + GetMatrix() = savedMatrix; + m_fElasticity = savedElasticity; + return; + } + } + bHitByTrain = false; + m_fDistanceTravelled = (GetPosition() - savedMatrix.GetPosition()).Magnitude(); + bSkipLineCol = false; + + bIsStuck = false; + bIsInSafePosition = true; + m_fElasticity = savedElasticity; + RemoveAndAdd(); +} diff --git a/src/miami/entities/Physical.h b/src/miami/entities/Physical.h new file mode 100644 index 00000000..f552da6c --- /dev/null +++ b/src/miami/entities/Physical.h @@ -0,0 +1,175 @@ +#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; + 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; + 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 m_phy_flagA08 : 1; + uint8 bIsInWater : 1; + uint8 m_phy_flagA20 : 1; // unused + uint8 bHitByTrain : 1; + uint8 bSkipLineCol : 1; + + uint8 bIsFrozen : 1; + uint8 bDontLoadCollision : 1; + uint8 m_bIsVehicleBeingShifted : 1; // wrong name - also used on but never set for peds + uint8 bJustCheckCollision : 1; // just see if there is a collision + + 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; + } + void AddToMoveSpeed(float x, float y, float z) { + m_vecMoveSpeed.x += x; + m_vecMoveSpeed.y += y; + m_vecMoveSpeed.z += z; + } + void AddToMoveSpeed(const CVector& addition) { + m_vecMoveSpeed += addition; + } + void AddToMoveSpeed(const CVector2D& addition) { + m_vecMoveSpeed += CVector(addition.x, addition.y, 0.0f); + } + 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 ApplySpringCollisionAlt(float springConst, CVector &springDir, CVector &point, float springRatio, float bias, CVector &forceDir); + 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 ApplyCollision(CColPoint &colpoint, float &impulse); + 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); +}; diff --git a/src/miami/extras/GitSHA1.cpp.in b/src/miami/extras/GitSHA1.cpp.in new file mode 100644 index 00000000..6168dc79 --- /dev/null +++ b/src/miami/extras/GitSHA1.cpp.in @@ -0,0 +1,2 @@ +#define GIT_SHA1 "@GIT_SHA1@" +const char* g_GIT_SHA1 = GIT_SHA1; diff --git a/src/miami/extras/GitSHA1.h b/src/miami/extras/GitSHA1.h new file mode 100644 index 00000000..359bfaff --- /dev/null +++ b/src/miami/extras/GitSHA1.h @@ -0,0 +1 @@ +extern const char* g_GIT_SHA1; \ No newline at end of file diff --git a/src/miami/extras/arrow.inc b/src/miami/extras/arrow.inc new file mode 100644 index 00000000..8ea78283 --- /dev/null +++ b/src/miami/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/miami/extras/cursor.inc b/src/miami/extras/cursor.inc new file mode 100644 index 00000000..e8afd394 --- /dev/null +++ b/src/miami/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/miami/extras/custompipes.cpp b/src/miami/extras/custompipes.cpp new file mode 100644 index 00000000..a485138e --- /dev/null +++ b/src/miami/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 = 1.0f; +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/miami/extras/custompipes.h b/src/miami/extras/custompipes.h new file mode 100644 index 00000000..7ad239f0 --- /dev/null +++ b/src/miami/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/miami/extras/custompipes_d3d9.cpp b/src/miami/extras/custompipes_d3d9.cpp new file mode 100644 index 00000000..3ad824e3 --- /dev/null +++ b/src/miami/extras/custompipes_d3d9.cpp @@ -0,0 +1,724 @@ +#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 *neoWorldVC_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(neoWorldVC_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/neoWorldVC_PS.inc" + neoWorldVC_PS = rw::d3d::createPixelShader(neoWorldVC_PS_cso); + assert(neoWorldVC_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(neoWorldVC_PS); + neoWorldVC_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; + 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); + 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){ + 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); + 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]; + + 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/miami/extras/custompipes_gl.cpp b/src/miami/extras/custompipes_gl.cpp new file mode 100644 index 00000000..d74e40db --- /dev/null +++ b/src/miami/extras/custompipes_gl.cpp @@ -0,0 +1,738 @@ +#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++; + } + + setTexture(1, nil); + + SetRenderState(SRCBLEND, BLENDSRCALPHA); + + 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/neoWorldVC_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, neoWorldVC_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; + 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); + 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){ + 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->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]; + + 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/miami/extras/debugmenu.cpp b/src/miami/extras/debugmenu.cpp new file mode 100644 index 00000000..533b97f5 --- /dev/null +++ b/src/miami/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/miami/extras/debugmenu.h b/src/miami/extras/debugmenu.h new file mode 100644 index 00000000..8aa15512 --- /dev/null +++ b/src/miami/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, (int8*)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, (int16*)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, (int32*)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, (int64*)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, (uint8*)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, (uint16*)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, (uint32*)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, (uint64*)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/miami/extras/frontendoption.cpp b/src/miami/extras/frontendoption.cpp new file mode 100644 index 00000000..2660e75e --- /dev/null +++ b/src/miami/extras/frontendoption.cpp @@ -0,0 +1,170 @@ +#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 GoBack() +{ + FrontEndMenuManager.SwitchToNewScreen(-1); +} + +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 == 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 = 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, uint16 x, uint16 y, uint8 align, 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_X = x; + option.m_Y = y; + option.m_Align = align; + option.m_Action = action; + option.m_SaveSlot = saveSlot; + option.m_TargetMenu = targetMenu; +} + +void FrontendOptionAddSelect(const char* gxtKey, uint16 x, uint16 y, uint8 align, const char** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, const char* saveCat, const char* saveName, bool disableIfGameLoaded) +{ + int8 screenOptionOrder = RegisterNewOption(); + + CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder]; + option.m_Action = MENUACTION_CFO_SELECT; + option.m_X = x; + option.m_Y = y; + option.m_Align = align; + 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 = saveName; + option.m_CFOSelect->onlyApplyOnEnter = onlyApplyOnEnter; + option.m_CFOSelect->changeFunc = changeFunc; + option.m_CFOSelect->disableIfGameLoaded = disableIfGameLoaded; +} + +void FrontendOptionAddDynamic(const char* gxtKey, uint16 x, uint16 y, uint8 align, DrawFunc drawFunc, int8 *var, ButtonPressFunc buttonPressFunc, const char* saveCat, const char* saveName) +{ + int8 screenOptionOrder = RegisterNewOption(); + + CMenuScreenCustom::CMenuEntry &option = aScreens[currentMenu].m_aEntries[screenOptionOrder]; + option.m_Action = MENUACTION_CFO_DYNAMIC; + option.m_X = x; + option.m_Y = y; + option.m_Align = align; + 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 = saveName; +} + +// lineHeight = 0 means game will use MENU_DEFAULT_LINE_HEIGHT +uint8 FrontendScreenAdd(const char* gxtKey, int prevPage, int lineHeight, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc) { + + uint8 screenOrder = RegisterNewScreen(gxtKey, prevPage, returnPrevPageFunc); + + CCustomScreenLayout *screen = new CCustomScreenLayout(); + aScreens[screenOrder].layout = screen; + screen->lineHeight = lineHeight; + screen->showLeftRightHelper = showLeftRightHelper; + + return screenOrder; +} +#endif diff --git a/src/miami/extras/frontendoption.h b/src/miami/extras/frontendoption.h new file mode 100644 index 00000000..db1b9021 --- /dev/null +++ b/src/miami/extras/frontendoption.h @@ -0,0 +1,88 @@ +#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 + +// -- 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 GoBack(void); + +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 saveCat and saveKey param. obv), otherwise pass nil/0 +void FrontendOptionAddBuiltinAction(const char* gxtKey, uint16 x, uint16 y, uint8 align, int action, int targetMenu = MENUPAGE_NONE, int saveSlot = SAVESLOT_NONE); +void FrontendOptionAddSelect(const char* gxtKey, uint16 x, uint16 y, uint8 align, 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, uint16 x, uint16 y, uint8 align, DrawFunc rightTextDrawFunc, int8 *var, ButtonPressFunc buttonPressFunc, const char* saveCat = nil, const char* saveKey = nil); + +// lineHeight = 0 means game will use MENU_DEFAULT_LINE_HEIGHT +uint8 FrontendScreenAdd(const char* gxtKey, int prevPage, int lineHeight, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc = nil); +#endif diff --git a/src/extras/ini.h b/src/miami/extras/ini.h similarity index 91% rename from src/extras/ini.h rename to src/miami/extras/ini.h index 9e5e36ea..44dd3d57 100644 --- a/src/extras/ini.h +++ b/src/miami/extras/ini.h @@ -94,10 +94,6 @@ #include #include -#ifdef RW_DC -#include -#endif - namespace mINI { namespace INIStringUtil @@ -339,7 +335,6 @@ namespace mINI private: std::ifstream fileReadStream; T_LineDataPtr lineData; - std::size_t start_offt; T_LineData readFile() { @@ -357,17 +352,7 @@ namespace mINI } std::string buffer; buffer.reserve(50); - -#ifdef RW_DC - { - vmu_pkg_t vmu_pkg; - vmu_pkg_parse(reinterpret_cast(const_cast(fileContents.c_str())), &vmu_pkg); - start_offt = reinterpret_cast(vmu_pkg.data) - - reinterpret_cast(fileContents.c_str()); - } -#endif - - for (std::size_t i = start_offt; i < fileSize; ++i) + for (std::size_t i = 0; i < fileSize; ++i) { char& c = fileContents[i]; if (c == '\n') @@ -393,8 +378,6 @@ namespace mINI { lineData = std::make_shared(); } - - start_offt = 0; } ~INIReader() { } @@ -443,54 +426,22 @@ namespace mINI { private: std::ofstream fileWriteStream; - std::stringstream memStream; public: bool prettyPrint = false; INIGenerator(std::string const& filename) - : fileWriteStream(filename, std::ios::out | std::ios::binary), - memStream(std::ios::out | std::ios::binary) { + fileWriteStream.open(filename, std::ios::out | std::ios::binary); } - - ~INIGenerator() - { - const char *buf = memStream.str().c_str(); - int buf_size = static_cast(memStream.tellg()); - -#ifdef RW_DC - uint8_t *data; - uint8_t icon_buf[512 * 3]; - vmu_pkg_t vmu_pkg = { - .desc_short = "DCA3 Settings", - .desc_long = "DCA3 Settings", - .app_id = "DCA3", - .icon_cnt = 3, - .icon_anim_speed = 8, - .data_len = buf_size, - .icon_data = icon_buf, - .data = reinterpret_cast(buf), - }; - - if (vmu_pkg_load_icon(&vmu_pkg, "settings.ico") < 0) - vmu_pkg.icon_cnt = 0; - - vmu_pkg_build(const_cast(&vmu_pkg), &data, &buf_size); - buf = reinterpret_cast(data); -#endif - - fileWriteStream.write(buf, buf_size); - } - - bool operator<<(const std::string& str) - { - memStream << str; - return true; - } + ~INIGenerator() { } bool operator<<(INIStructure const& data) { + if (!fileWriteStream.is_open()) + { + return false; + } if (!data.size()) { return true; @@ -500,13 +451,13 @@ namespace mINI { auto const& section = it->first; auto const& collection = it->second; - memStream + fileWriteStream << "[" << section << "]"; if (collection.size()) { - memStream << INIStringUtil::endl; + fileWriteStream << INIStringUtil::endl; auto it2 = collection.begin(); for (;;) { @@ -514,7 +465,7 @@ namespace mINI INIStringUtil::replace(key, "=", "\\="); auto value = it2->second; INIStringUtil::trim(value); - memStream + fileWriteStream << key << ((prettyPrint) ? " = " : "=") << value; @@ -522,26 +473,21 @@ namespace mINI { break; } - memStream << INIStringUtil::endl; + fileWriteStream << INIStringUtil::endl; } } if (++it == data.end()) { break; } - memStream << INIStringUtil::endl; + fileWriteStream << INIStringUtil::endl; if (prettyPrint) { - memStream << INIStringUtil::endl; + fileWriteStream << INIStringUtil::endl; } } return true; } - - operator bool() const - { - return fileWriteStream.is_open(); - } }; class INIWriter @@ -721,15 +667,12 @@ namespace mINI { struct stat buf; bool fileExists = (stat(filename.c_str(), &buf) == 0); - - INIGenerator generator(filename); - generator.prettyPrint = prettyPrint; - if (!fileExists) { + INIGenerator generator(filename); + generator.prettyPrint = prettyPrint; return generator << data; } - INIStructure originalData; T_LineDataPtr lineData; bool readSuccess = false; @@ -745,19 +688,20 @@ namespace mINI return false; } T_LineData output = getLazyOutput(lineData, data, originalData); - if (generator) + std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary); + if (fileWriteStream.is_open()) { if (output.size()) { auto line = output.begin(); for (;;) { - generator << *line; + fileWriteStream << *line; if (++line == output.end()) { break; } - generator << INIStringUtil::endl; + fileWriteStream << INIStringUtil::endl; } } return true; diff --git a/src/miami/extras/postfx.cpp b/src/miami/extras/postfx.cpp new file mode 100644 index 00000000..ee6c3964 --- /dev/null +++ b/src/miami/extras/postfx.cpp @@ -0,0 +1,494 @@ +#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::BlurOn = false; +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 *colourfilterVC_PS; +void *contrast_PS; +#endif +#ifdef RW_OPENGL +int32 u_blurcolor; +int32 u_contrastAdd; +int32 u_contrastMult; +rw::gl3::Shader *colourFilterVC; +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/colourfilterVC_PS.inc" + colourfilterVC_PS = rw::d3d::createPixelShader(colourfilterVC_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/colourfilterVC_frag.inc" + const char *vs[] = { shaderDecl, header_vert_src, im2d_vert_src, nil }; + const char *fs[] = { shaderDecl, header_frag_src, colourfilterVC_frag_src, nil }; + colourFilterVC = Shader::create(vs, fs); + assert(colourFilterVC); + } + + { +#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(colourfilterVC_PS){ + rw::d3d::destroyPixelShader(colourfilterVC_PS); + colourfilterVC_PS = nil; + } + if(contrast_PS){ + rw::d3d::destroyPixelShader(contrast_PS); + contrast_PS = nil; + } +#endif +#ifdef RW_OPENGL + if(colourFilterVC){ + colourFilterVC->destroy(); + colourFilterVC = 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*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex[1], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex[2], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex[3], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex2[0], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex2[1], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex2[2], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex2[3], r*2, g*2, b*2, 30); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, BlurOn ? Vertex2 : Vertex, 4, Index, 6); + + + RwIm2DVertexSetIntRGBA(&Vertex2[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[3], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, a); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, BlurOn ? Vertex2 : 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)/256.0f + 1.4f; + mult[1] = (g-64)/256.0f + 1.4f; + mult[2] = (b-64)/256.0f + 1.4f; + add[0] = r/1536.f - 0.05f; + add[1] = g/1536.f - 0.05f; + add[2] = b/1536.f - 0.05f; +#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*f/255.0f; + blurcolors[1] = g*f/255.0f; + blurcolors[2] = b*f/255.0f; + blurcolors[3] = 30/255.0f; +#ifdef RW_D3D9 + rw::d3d::d3ddevice->SetPixelShaderConstantF(10, blurcolors, 1); + rw::d3d::im2dOverridePS = colourfilterVC_PS; +#endif +#ifdef RW_OPENGL + rw::gl3::im2dOverrideShader = colourFilterVC; + colourFilterVC->use(); + glUniform4fv(colourFilterVC->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: + case POSTFX_SIMPLE: + // no actual rendering here + 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(CMBlur::Drunkness > 0.0f) + return true; + if(type == MOTION_BLUR_SNIPER) + return true; + + switch(EffectSwitch){ + case POSTFX_OFF: + case POSTFX_SIMPLE: + // no actual rendering here + return false; + case POSTFX_NORMAL: + if(MotionBlurOn) + return true; + else + return false; + case POSTFX_MOBILE: + return false; + } + 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) +{ + PUSH_RENDERGROUP("CPostFX::Render"); + + if(pFrontBuffer == nil) + Open(cam); + assert(pFrontBuffer); + assert(pBackBuffer); + + if(type == MOTION_BLUR_LIGHT_SCENE){ + SmoothColor(red, green, blue, blur); + red = AvgRed; + green = AvgGreen; + blue = AvgBlue; + blur = AvgAlpha; + } + + 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: + case POSTFX_SIMPLE: + // no actual rendering here + 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; + } + + if(!bJustInitialised) + RenderMotionBlur(cam, 175.0f * CMBlur::Drunkness); + + 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(); +} + +int CPostFX::PrevRed[NUMAVERAGE], CPostFX::AvgRed; +int CPostFX::PrevGreen[NUMAVERAGE], CPostFX::AvgGreen; +int CPostFX::PrevBlue[NUMAVERAGE], CPostFX::AvgBlue; +int CPostFX::PrevAlpha[NUMAVERAGE], CPostFX::AvgAlpha; +int CPostFX::Next; +int CPostFX::NumValues; + +// This is rather annoying...the blur color can flicker slightly +// which becomes very visible when amplified by the shader +void +CPostFX::SmoothColor(uint32 red, uint32 green, uint32 blue, uint32 alpha) +{ + PrevRed[Next] = red; + PrevGreen[Next] = green; + PrevBlue[Next] = blue; + PrevAlpha[Next] = alpha; + Next = (Next+1) % NUMAVERAGE; + NumValues = Min(NumValues+1, NUMAVERAGE); + + AvgRed = 0; + AvgGreen = 0; + AvgBlue = 0; + AvgAlpha = 0; + for(int i = 0; i < NumValues; i++){ + AvgRed += PrevRed[i]; + AvgGreen += PrevGreen[i]; + AvgBlue += PrevBlue[i]; + AvgAlpha += PrevAlpha[i]; + } + AvgRed /= NumValues; + AvgGreen /= NumValues; + AvgBlue /= NumValues; + AvgAlpha /= NumValues; +} + +#endif diff --git a/src/miami/extras/postfx.h b/src/miami/extras/postfx.h new file mode 100644 index 00000000..db702bf3 --- /dev/null +++ b/src/miami/extras/postfx.h @@ -0,0 +1,46 @@ +#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 BlurOn; // or use CMblur for that? + static bool MotionBlurOn; // or use CMblur for that? + static float Intensity; + + // smooth blur color + enum { NUMAVERAGE = 20 }; + static int PrevRed[NUMAVERAGE], AvgRed; + static int PrevGreen[NUMAVERAGE], AvgGreen; + static int PrevBlue[NUMAVERAGE], AvgBlue; + static int PrevAlpha[NUMAVERAGE], AvgAlpha; + static int Next; + static int NumValues; + + 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 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 void SmoothColor(uint32 red, uint32 green, uint32 blue, uint32 alpha); + 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/miami/extras/re3_inttypes.h b/src/miami/extras/re3_inttypes.h new file mode 100644 index 00000000..bf0c53e2 --- /dev/null +++ b/src/miami/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/miami/extras/screendroplets.cpp b/src/miami/extras/screendroplets.cpp new file mode 100644 index 00000000..cc86808f --- /dev/null +++ b/src/miami/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/miami/extras/screendroplets.h b/src/miami/extras/screendroplets.h new file mode 100644 index 00000000..090b1923 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/colourfilterVC.frag b/src/miami/extras/shaders/colourfilterVC.frag new file mode 100644 index 00000000..283aa817 --- /dev/null +++ b/src/miami/extras/shaders/colourfilterVC.frag @@ -0,0 +1,27 @@ +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 doublec = clamp(u_blurcolor*2.0, 0.0, 1.0); + 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*doublec*a; + tmp += prev*u_blurcolor; + tmp += prev*u_blurcolor; + prev = clamp(tmp, 0.0, 1.0); + } + vec4 color; + color.rgb = prev.rgb; + color.a = 1.0; + + FRAGCOLOR(color); +} + diff --git a/src/miami/extras/shaders/colourfilterVC_PS.hlsl b/src/miami/extras/shaders/colourfilterVC_PS.hlsl new file mode 100644 index 00000000..90d3b50c --- /dev/null +++ b/src/miami/extras/shaders/colourfilterVC_PS.hlsl @@ -0,0 +1,23 @@ +sampler2D tex : register(s0); +float4 blurcol : register(c10); + +//float4 blurcols[10] : register(c15); + + +float4 main(in float2 texcoord : TEXCOORD0) : COLOR0 +{ + float a = blurcol.a; + + float4 doublec = saturate(blurcol*2); + float4 dst = tex2D(tex, texcoord.xy); + float4 prev = dst; + for(int i = 0; i < 5; i++){ +// float4 doublec = saturate(blurcol*2); + float4 tmp = dst*(1-a) + prev*doublec*a; + tmp += prev*blurcol; + tmp += prev*blurcol; + prev = saturate(tmp); + } + prev.a = 1.0; + return prev; +} diff --git a/src/miami/extras/shaders/contrast.frag b/src/miami/extras/shaders/contrast.frag new file mode 100644 index 00000000..2d394f66 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/contrastPS.hlsl b/src/miami/extras/shaders/contrastPS.hlsl new file mode 100644 index 00000000..a1de1d81 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/default_UV2.vert b/src/miami/extras/shaders/default_UV2.vert new file mode 100644 index 00000000..694c012b --- /dev/null +++ b/src/miami/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/miami/extras/shaders/default_UV2_VS.hlsl b/src/miami/extras/shaders/default_UV2_VS.hlsl new file mode 100644 index 00000000..e78a9907 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/im2d.vert b/src/miami/extras/shaders/im2d.vert new file mode 100644 index 00000000..fcd81c2c --- /dev/null +++ b/src/miami/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/miami/extras/shaders/im2d_UV2.vert b/src/miami/extras/shaders/im2d_UV2.vert new file mode 100644 index 00000000..e5fd4d08 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/lighting.h b/src/miami/extras/shaders/lighting.h new file mode 100644 index 00000000..4b081962 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/make_glsl.sh b/src/miami/extras/shaders/make_glsl.sh new file mode 100644 index 00000000..0af98961 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/make_hlsl.cmd b/src/miami/extras/shaders/make_hlsl.cmd new file mode 100644 index 00000000..dee95283 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/makeinc_glsl.sh b/src/miami/extras/shaders/makeinc_glsl.sh new file mode 100644 index 00000000..2bc6a387 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/makeinc_hlsl.sh b/src/miami/extras/shaders/makeinc_hlsl.sh new file mode 100644 index 00000000..a5b12867 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoGloss.frag b/src/miami/extras/shaders/neoGloss.frag new file mode 100644 index 00000000..4f097b0b --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoGloss.vert b/src/miami/extras/shaders/neoGloss.vert new file mode 100644 index 00000000..41102f3f --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoGloss_PS.hlsl b/src/miami/extras/shaders/neoGloss_PS.hlsl new file mode 100644 index 00000000..b3c97639 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoGloss_VS.hlsl b/src/miami/extras/shaders/neoGloss_VS.hlsl new file mode 100644 index 00000000..d166171c --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoRim.vert b/src/miami/extras/shaders/neoRim.vert new file mode 100644 index 00000000..81ee1090 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoRimSkin.vert b/src/miami/extras/shaders/neoRimSkin.vert new file mode 100644 index 00000000..1515ad71 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoRimSkin_VS.hlsl b/src/miami/extras/shaders/neoRimSkin_VS.hlsl new file mode 100644 index 00000000..87cc0931 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoRim_VS.hlsl b/src/miami/extras/shaders/neoRim_VS.hlsl new file mode 100644 index 00000000..7f95166d --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoVehicle.frag b/src/miami/extras/shaders/neoVehicle.frag new file mode 100644 index 00000000..2ac24f70 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoVehicle.vert b/src/miami/extras/shaders/neoVehicle.vert new file mode 100644 index 00000000..6985a689 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoVehicle_PS.hlsl b/src/miami/extras/shaders/neoVehicle_PS.hlsl new file mode 100644 index 00000000..fa030dd6 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoVehicle_VS.hlsl b/src/miami/extras/shaders/neoVehicle_VS.hlsl new file mode 100644 index 00000000..fb730092 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/neoWorldVC.frag b/src/miami/extras/shaders/neoWorldVC.frag new file mode 100644 index 00000000..08cae743 --- /dev/null +++ b/src/miami/extras/shaders/neoWorldVC.frag @@ -0,0 +1,26 @@ +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; + color = t0*v_color*(1.0 + u_lightMap*(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/miami/extras/shaders/neoWorldVC_PS.hlsl b/src/miami/extras/shaders/neoWorldVC_PS.hlsl new file mode 100644 index 00000000..fc4f1de9 --- /dev/null +++ b/src/miami/extras/shaders/neoWorldVC_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*(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/miami/extras/shaders/screenDroplet.frag b/src/miami/extras/shaders/screenDroplet.frag new file mode 100644 index 00000000..84d30bd5 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/screenDroplet_PS.hlsl b/src/miami/extras/shaders/screenDroplet_PS.hlsl new file mode 100644 index 00000000..4d41da68 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/simple.frag b/src/miami/extras/shaders/simple.frag new file mode 100644 index 00000000..c85bf089 --- /dev/null +++ b/src/miami/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/miami/extras/shaders/standardConstants.h b/src/miami/extras/shaders/standardConstants.h new file mode 100644 index 00000000..088df7dd --- /dev/null +++ b/src/miami/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/miami/fakerw/fake.cpp b/src/miami/fakerw/fake.cpp new file mode 100644 index 00000000..53c965a9 --- /dev/null +++ b/src/miami/fakerw/fake.cpp @@ -0,0 +1,1014 @@ +#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); } +RwBool RpClumpStreamWrite(RpClump * clump, RwStream * stream) { return clump->streamWrite(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); } + +#if 0 +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 +} +#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/miami/fakerw/rpanisot.h b/src/miami/fakerw/rpanisot.h new file mode 100644 index 00000000..a886512f --- /dev/null +++ b/src/miami/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/miami/fakerw/rphanim.h b/src/miami/fakerw/rphanim.h new file mode 100644 index 00000000..63059800 --- /dev/null +++ b/src/miami/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/miami/fakerw/rpmatfx.h b/src/miami/fakerw/rpmatfx.h new file mode 100644 index 00000000..87c8fb2e --- /dev/null +++ b/src/miami/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/miami/fakerw/rpskin.h b/src/miami/fakerw/rpskin.h new file mode 100644 index 00000000..1ffc9f27 --- /dev/null +++ b/src/miami/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/miami/fakerw/rpworld.h b/src/miami/fakerw/rpworld.h new file mode 100644 index 00000000..e5d4de97 --- /dev/null +++ b/src/miami/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); +RwBool 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/miami/fakerw/rtbmp.h b/src/miami/fakerw/rtbmp.h new file mode 100644 index 00000000..896d276b --- /dev/null +++ b/src/miami/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/miami/fakerw/rtcharse.h b/src/miami/fakerw/rtcharse.h new file mode 100644 index 00000000..10eb1f32 --- /dev/null +++ b/src/miami/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/miami/fakerw/rtpng.h b/src/miami/fakerw/rtpng.h new file mode 100644 index 00000000..80f29020 --- /dev/null +++ b/src/miami/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/miami/fakerw/rtquat.h b/src/miami/fakerw/rtquat.h new file mode 100644 index 00000000..450342b2 --- /dev/null +++ b/src/miami/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/miami/fakerw/rwcore.h similarity index 100% rename from src/fakerw/rwcore.h rename to src/miami/fakerw/rwcore.h diff --git a/src/miami/fakerw/rwplcore.h b/src/miami/fakerw/rwplcore.h new file mode 100644 index 00000000..69c921cc --- /dev/null +++ b/src/miami/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/miami/math/Matrix.cpp b/src/miami/math/Matrix.cpp new file mode 100644 index 00000000..c0d909cb --- /dev/null +++ b/src/miami/math/Matrix.cpp @@ -0,0 +1,531 @@ +#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 + dst.f[3][0] = dst.f[3][1] = dst.f[3][2] = 0.0f; + + dst.f[0][0] = src.f[0][0]; + dst.f[0][1] = src.f[1][0]; + dst.f[0][2] = src.f[2][0]; + + dst.f[1][0] = src.f[0][1]; + dst.f[1][1] = src.f[1][1]; + dst.f[1][2] = src.f[2][1]; + + dst.f[2][0] = src.f[0][2]; + dst.f[2][1] = src.f[1][2]; + dst.f[2][2] = src.f[2][2]; + + + 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]; + + 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]; + + 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]; + + dst.f[3][0] = -dst.f[3][0]; + dst.f[3][1] = -dst.f[3][1]; + dst.f[3][2] = -dst.f[3][2]; + + return dst; +} + +void +CMatrix::CopyToRwMatrix(RwMatrix* matrix) +{ + matrix->right = GetRight(); + matrix->up = GetForward(); + matrix->at = GetUp(); + matrix->pos = GetPosition(); + RwMatrixUpdate(matrix); +} + +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/miami/math/Matrix.h b/src/miami/math/Matrix.h new file mode 100644 index 00000000..0adcf32c --- /dev/null +++ b/src/miami/math/Matrix.h @@ -0,0 +1,154 @@ +#pragma once + +class CMatrix +{ +public: +#ifdef GTA_PS2 + union + { + float f[4][4]; + struct + { + float rx, ry, rz; + RwMatrix *m_attachment; + float fx, fy, fz; + bool m_hasRwMatrix; // are we the owner? + float ux, uy, uz, uw; + float px, py, pz, pw; + }; + }; +#else + 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? +#endif + + 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++) + for (int j = 0; j < 3; j++) + f[i][j] *= scale; + } + void Scale(float sx, float sy, float sz) + { + for (int i = 0; i < 3; i++){ + f[i][0] *= sx; + f[i][1] *= sy; + f[i][2] *= sz; + } + } + + + 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 CopyToRwMatrix(RwMatrix* matrix); + + 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/miami/math/Quaternion.cpp b/src/miami/math/Quaternion.cpp new file mode 100644 index 00000000..b0e782e2 --- /dev/null +++ b/src/miami/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/miami/math/Quaternion.h b/src/miami/math/Quaternion.h new file mode 100644 index 00000000..47c94f7c --- /dev/null +++ b/src/miami/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/miami/math/Rect.cpp b/src/miami/math/Rect.cpp new file mode 100644 index 00000000..de6320ad --- /dev/null +++ b/src/miami/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/miami/math/Rect.h b/src/miami/math/Rect.h new file mode 100644 index 00000000..e9b25896 --- /dev/null +++ b/src/miami/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/miami/math/Vector.cpp b/src/miami/math/Vector.cpp new file mode 100644 index 00000000..ee76e555 --- /dev/null +++ b/src/miami/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/miami/math/Vector.h b/src/miami/math/Vector.h new file mode 100644 index 00000000..02128454 --- /dev/null +++ b/src/miami/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) const { + return x == right.x && y == right.y && z == right.z; + } + + const bool operator!=(CVector const &right) const { + 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/miami/math/Vector2D.h b/src/miami/math/Vector2D.h new file mode 100644 index 00000000..deabd0b1 --- /dev/null +++ b/src/miami/math/Vector2D.h @@ -0,0 +1,104 @@ +#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(); + 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); + } + CVector2D operator-() const { + return CVector2D(-x, -y); + } +}; + +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/miami/math/VuVector.h b/src/miami/math/VuVector.h new file mode 100644 index 00000000..41584095 --- /dev/null +++ b/src/miami/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/miami/math/math.cpp b/src/miami/math/math.cpp new file mode 100644 index 00000000..8cb56dab --- /dev/null +++ b/src/miami/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/miami/math/maths.h b/src/miami/math/maths.h new file mode 100644 index 00000000..6a228036 --- /dev/null +++ b/src/miami/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/miami/modelinfo/BaseModelInfo.cpp b/src/miami/modelinfo/BaseModelInfo.cpp new file mode 100644 index 00000000..709420fd --- /dev/null +++ b/src/miami/modelinfo/BaseModelInfo.cpp @@ -0,0 +1,103 @@ +#include "common.h" + +#include "templates.h" +#include "TxdStore.h" +#include "2dEffect.h" +#include "BaseModelInfo.h" +#include "ModelInfo.h" +#include "ColModel.h" + +CBaseModelInfo::CBaseModelInfo(ModelInfoType type) +{ + m_colModel = nil; + m_2dEffectsID = -1; + 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_2dEffectsID = -1; + m_num2dEffects = 0; + m_txdSlot = -1; + m_objectId = -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_2dEffectsID = -1; + m_num2dEffects = 0; +} + +void +CBaseModelInfo::Add2dEffect(C2dEffect *fx) +{ + if(m_2dEffectsID >= 0) + m_num2dEffects++; + else{ + m_2dEffectsID = CModelInfo::Get2dEffectStore().GetIndex(fx); + m_num2dEffects = 1; + } +} + +C2dEffect* +CBaseModelInfo::Get2dEffect(int n) +{ + if(m_2dEffectsID >= 0) + return CModelInfo::Get2dEffectStore().GetItem(m_2dEffectsID+n); + else + return nil; +} diff --git a/src/miami/modelinfo/BaseModelInfo.h b/src/miami/modelinfo/BaseModelInfo.h new file mode 100644 index 00000000..2d1dc8ac --- /dev/null +++ b/src/miami/modelinfo/BaseModelInfo.h @@ -0,0 +1,74 @@ +#pragma once + +struct CColModel; + +#define MAX_MODEL_NAME (21) + +enum ModelInfoType +{ + MITYPE_NA, + MITYPE_SIMPLE, + MITYPE_MLO, // unused but still in enum + MITYPE_TIME, + MITYPE_WEAPON, + MITYPE_CLUMP, + MITYPE_VEHICLE, + MITYPE_PED, + MITYPE_XTRACOMPS, // unused but still in enum + MITYPE_HAND // xbox and mobile +}; + +class C2dEffect; + +class CBaseModelInfo +{ +protected: + char m_name[MAX_MODEL_NAME]; + uint8 m_type; + uint8 m_num2dEffects; + bool m_bOwnsColModel; + CColModel *m_colModel; + int16 m_2dEffectsID; + int16 m_objectId; + uint16 m_refCount; + int16 m_txdSlot; + +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; + virtual void SetAnimFile(const char *file) {} + virtual void ConvertAnimFileIndex(void) {} + virtual int GetAnimFileIndex(void) { return -1; } + + // one day it becomes virtual + uint8 GetModelType() const { return m_type; } + bool IsBuilding(void) { return m_type == MITYPE_SIMPLE || m_type == MITYPE_TIME; } + bool IsSimple(void) { return m_type == MITYPE_SIMPLE || m_type == MITYPE_TIME || m_type == MITYPE_WEAPON; } + bool IsClump(void) { return m_type == MITYPE_CLUMP || m_type == MITYPE_PED || m_type == MITYPE_VEHICLE; } + 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; } +}; diff --git a/src/miami/modelinfo/ClumpModelInfo.cpp b/src/miami/modelinfo/ClumpModelInfo.cpp new file mode 100644 index 00000000..d13f85ba --- /dev/null +++ b/src/miami/modelinfo/ClumpModelInfo.cpp @@ -0,0 +1,208 @@ +#include "common.h" + +#include "RwHelper.h" +#include "General.h" +#include "NodeName.h" +#include "VisibilityPlugins.h" +#include "ModelInfo.h" +#include "AnimManager.h" + +void +CClumpModelInfo::DeleteRwObject(void) +{ + if(m_clump){ + RpClumpDestroy(m_clump); + m_clump = nil; + RemoveTexDictionaryRef(); + if(GetAnimFileIndex() != -1) + CAnimManager::RemoveAnimBlockRef(GetAnimFileIndex()); + } +} + +static RpAtomic* +SetHierarchyForSkinAtomic(RpAtomic *atomic, void *data) +{ + RpSkinAtomicSetHAnimHierarchy(atomic, (RpHAnimHierarchy*)data); + return nil; +} + +RwObject* +CClumpModelInfo::CreateInstance(void) +{ + if(m_clump == nil) + return nil; + RpClump *clone = RpClumpClone(m_clump); + if(IsClumpSkinned(clone)){ + RpHAnimHierarchy *hier; + RpHAnimAnimation *anim; + + hier = GetAnimHierarchyFromClump(clone); + assert(hier); + RpClumpForAllAtomics(clone, SetHierarchyForSkinAtomic, hier); + anim = HAnimAnimationCreateForHierarchy(hier); + RpHAnimHierarchySetCurrentAnim(hier, anim); + RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS)); + } + 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(); + if(GetAnimFileIndex() != -1) + CAnimManager::AddAnimBlockRef(GetAnimFileIndex()); + if(IsClumpSkinned(clump)){ + int i; + RpHAnimHierarchy *hier; + RpAtomic *skinAtomic; + RpSkin *skin; + + hier = GetAnimHierarchyFromClump(clump); + assert(hier); + RpClumpForAllAtomics(clump, SetHierarchyForSkinAtomic, hier); + #if !defined(RW_DC) + // why is this here? should be already normalized ~ skmp + skinAtomic = GetFirstAtomic(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; + } + #endif + RpHAnimHierarchySetFlags(hier, (RpHAnimHierarchyFlag)(rpHANIMHIERARCHYUPDATEMODELLINGMATRICES|rpHANIMHIERARCHYUPDATELTMS)); + } +} + +void +CClumpModelInfo::SetAnimFile(const char *file) +{ + if(strcasecmp(file, "null") == 0) + return; + + m_animFileName = new char[strlen(file)+1]; + strcpy(m_animFileName, file); +} + +void +CClumpModelInfo::ConvertAnimFileIndex(void) +{ + if(m_animFileIndex != -1){ + // we have a string pointer in that union + int32 index = CAnimManager::GetAnimationBlockIndex(m_animFileName); + delete[] m_animFileName; + m_animFileIndex = index; + } +} + +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; +} + +// unused +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/miami/modelinfo/ClumpModelInfo.h b/src/miami/modelinfo/ClumpModelInfo.h new file mode 100644 index 00000000..0113d340 --- /dev/null +++ b/src/miami/modelinfo/ClumpModelInfo.h @@ -0,0 +1,60 @@ +#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; + union { + int32 m_animFileIndex; + char *m_animFileName; + }; + + CClumpModelInfo(void) : CBaseModelInfo(MITYPE_CLUMP) { m_animFileIndex = -1; } + CClumpModelInfo(ModelInfoType id) : CBaseModelInfo(id) { m_animFileIndex = -1; } + ~CClumpModelInfo() {} + void DeleteRwObject(void); + RwObject *CreateInstance(void); + RwObject *CreateInstance(RwMatrix *); + RwObject *GetRwObject(void) { return (RwObject*)m_clump; } + + virtual void SetClump(RpClump *); + virtual void SetAnimFile(const char *file); + virtual void ConvertAnimFileIndex(void); + virtual int GetAnimFileIndex(void) { return m_animFileIndex; } + + 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); +}; +//static_assert(sizeof(CClumpModelInfo) == 0x34, "CClumpModelInfo: error"); diff --git a/src/miami/modelinfo/MloModelInfo.cpp b/src/miami/modelinfo/MloModelInfo.cpp new file mode 100644 index 00000000..fa12b900 --- /dev/null +++ b/src/miami/modelinfo/MloModelInfo.cpp @@ -0,0 +1,41 @@ +#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/miami/modelinfo/MloModelInfo.h b/src/miami/modelinfo/MloModelInfo.h new file mode 100644 index 00000000..b1ae3298 --- /dev/null +++ b/src/miami/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/miami/modelinfo/ModelIndices.cpp b/src/miami/modelinfo/ModelIndices.cpp new file mode 100644 index 00000000..98c7fb38 --- /dev/null +++ b/src/miami/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/miami/modelinfo/ModelIndices.h b/src/miami/modelinfo/ModelIndices.h new file mode 100644 index 00000000..836c4092 --- /dev/null +++ b/src/miami/modelinfo/ModelIndices.h @@ -0,0 +1,621 @@ +#pragma once + +#include "ModelInfo.h" + +#define MODELINDICES \ + X("fire_hydrant", MI_FIRE_HYDRANT) \ + 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("parkingmeterg", MI_PARKINGMETER2) \ + X("mall_fans", MI_MALLFAN) \ + X("htl_fan_rotate_nt", MI_HOTELFAN_NIGHT) \ + X("htl_fan_rotate_dy", MI_HOTELFAN_DAY) \ + X("hotroomfan", MI_HOTROOMFAN) \ + X("trafficlight1", MI_TRAFFICLIGHTS) \ + X("MTraffic4", MI_TRAFFICLIGHTS_VERTICAL) \ + X("MTraffic1", MI_TRAFFICLIGHTS_MIAMI) \ + X("MTraffic2", MI_TRAFFICLIGHTS_TWOVERTICAL) \ + X("lamppost1", MI_SINGLESTREETLIGHTS1) \ + X("lamppost2", MI_SINGLESTREETLIGHTS2) \ + X("lamppost3", MI_SINGLESTREETLIGHTS3) \ + X("doublestreetlght1", MI_DOUBLESTREETLIGHTS) \ + X("Streetlamp1", MI_STREETLAMP1) \ + X("Streetlamp2", MI_STREETLAMP2) \ + X("veg_tree3", MI_TREE2) \ + X("veg_treea1", MI_TREE3) \ + X("veg_treeb1", MI_TREE6) \ + X("veg_treea3", MI_TREE8) \ + X("doc_crane_cab0", MODELID_CRANE_1) \ + X("doc_crane_cab01", MODELID_CRANE_2) \ + X("doc_crane_cab02", MODELID_CRANE_3) \ + X("doc_crane_cab03", MODELID_CRANE_4) \ + X("boatcranelg0", MODELID_CRANE_5) \ + X("LODnetopa0", MODELID_CRANE_6) \ + X("package1", MI_COLLECTABLE1) \ + X("Money", MI_MONEY) \ + X("barrel1", MI_CARMINE) \ + X("dk_paynspraydoor", MI_GARAGEDOOR2) \ + X("dk_waretankdoor1", MI_GARAGEDOOR3) \ + X("hav_garagedoor1", MI_GARAGEDOOR4) \ + X("hav_garagedoor02", MI_GARAGEDOOR5) \ + X("hav_garagedoor03", MI_GARAGEDOOR6) \ + X("hav_garagedoor04", MI_GARAGEDOOR7) \ + X("lh_showdoor03", MI_GARAGEDOOR9) \ + X("lh_showdoor1", MI_GARAGEDOOR10) \ + X("lhtankdoor", MI_GARAGEDOOR11) \ + X("nbtgardoor", MI_GARAGEDOOR12) \ + X("dk_camjonesdoor", MI_GARAGEDOOR13) \ + X("nbtgardoor02", MI_GARAGEDOOR14) \ + X("dt_savedra", MI_GARAGEDOOR15) \ + X("dt_savedrb", MI_GARAGEDOOR16) \ + X("dk_bombdoor", MI_GARAGEDOOR18) \ + X("haiwshpnsdoor", MI_GARAGEDOOR19) \ + X("wshpnsdoor", MI_GARAGEDOOR20) \ + X("nbecpnsdoor", MI_GARAGEDOOR21) \ + X("nbtgardoor03", MI_GARAGEDOOR22) \ + X("dt_savedrc", MI_GARAGEDOOR23) \ + X("dt_savedrd", MI_GARAGEDOOR24) \ + X("man_frntstepGD", MI_GARAGEDOOR25) \ + X("svegrgedoor", MI_GARAGEDOOR26) \ + X("barrel2", MI_NAUTICALMINE) \ + X("briefcase", MI_BRIEFCASE) \ + X("wglasssmash", MI_GLASS1) \ + X("glassfx_composh", MI_GLASS8) \ + X("barrel4", MI_EXPLODINGBARREL) \ + 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("bigdollar", MI_PICKUP_REVENUE) \ + X("pickupsave", MI_PICKUP_SAVEGAME) \ + X("property_locked", MI_PICKUP_PROPERTY) \ + X("property_fsale", MI_PICKUP_PROPERTY_FORSALE) \ + X("clothesp", MI_PICKUP_CLOTHES) \ + X("bollardlight", MI_BOLLARDLIGHT) \ + X("bar_barrier10", MI_FENCE) \ + X("bar_barrier12", MI_FENCE2) \ + X("petrolpump", MI_PETROLPUMP) \ + X("washgaspump", MI_PETROLPUMP2) \ + X("bouy", MI_BUOY) \ + X("parktable1", MI_PARKTABLE) \ + X("lamppost1", MI_LAMPPOST1) \ + X("veg_palm04", MI_VEG_PALM01) \ + X("veg_palwee02", MI_VEG_PALM02) \ + X("veg_palmkbb11", MI_VEG_PALM03) \ + X("veg_palmkb4", MI_VEG_PALM04) \ + X("veg_palm02", MI_VEG_PALM05) \ + X("veg_palmkb3", MI_VEG_PALM06) \ + X("veg_palmbig14", MI_VEG_PALM07) \ + X("veg_palm01", MI_VEG_PALM08) \ + X("mlamppost", MI_MLAMPPOST) \ + X("roadworkbarrier1", MI_BARRIER1) \ + X("littleha_police", MI_LITTLEHA_POLICE) \ + X("telgrphpole02", MI_TELPOLE02) \ + X("trafficlight1", MI_TRAFFICLIGHT01) \ + X("parkbench1", MI_PARKBENCH) \ + X("plc_stinger", MI_PLC_STINGER) \ + X("od_lightbeam", MI_LIGHTBEAM) \ + X("ap_radar1_01", MI_AIRPORTRADAR) \ + X("rcbomb", MI_RCBOMB) \ + X("beachball", MI_BEACHBALL) \ + X("sandcastle1", MI_SANDCASTLE1) \ + X("sandcastle2", MI_SANDCASTLE2) \ + X("jellyfish", MI_JELLYFISH) \ + X("jellyfish01", MI_JELLYFISH01) \ + X("fish1single", MI_FISH1SINGLE) \ + X("fish1s", MI_FISH1S) \ + X("fish2single", MI_FISH2SINGLE) \ + X("fish2s", MI_FISH2S) \ + X("fish3single", MI_FISH3SINGLE) \ + X("fish3s", MI_FISH3S) \ + X("turtle", MI_TURTLE) \ + X("dolphin", MI_DOLPHIN) \ + X("shark", MI_SHARK) \ + X("submarine", MI_SUBMARINE) \ + X("Esc_step", MI_ESCALATORSTEP) \ + X("lounge_wood_up", MI_LOUNGE_WOOD_UP) \ + X("lounge_towel_up", MI_LOUNGE_TOWEL_UP) \ + X("lounge_wood_dn", MI_LOUNGE_WOOD_DN) \ + X("lotion", MI_LOTION) \ + X("beachtowel01", MI_BEACHTOWEL01) \ + X("beachtowel02", MI_BEACHTOWEL02) \ + X("beachtowel03", MI_BEACHTOWEL03) \ + X("beachtowel04", MI_BEACHTOWEL04) \ + X("blimp_night", MI_BLIMP_NIGHT) \ + X("blimp_day", MI_BLIMP_DAY) \ + X("yt_main_body", MI_YT_MAIN_BODY) \ + X("yt_main_body2", MI_YT_MAIN_BODY2) + +#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_HFYST = 9, + MI_HFOST, + MI_HMYST, + MI_HMOST, + MI_HFYRI, + MI_HFORI, + MI_HMYRI, + MI_HMORI, + MI_HFYBE, + MI_HFOBE, + MI_HMYBE, + MI_HMOBE, + MI_HFYBU, + MI_HFYMD, + MI_HFYCG, + MI_HFYPR, + MI_HFOTR, + MI_HMOTR, + MI_HMYAP, + MI_HMOCA, + MI_TAXI_D = MI_HMOCA, + MI_BMODK, + MI_BMYKR, + MI_BFYST, + MI_BFOST, + MI_BMYST, + MI_BMOST, + MI_BFYRI, + MI_BFORI, + MI_BMYRI, + MI_BFYBE, + MI_BMYBE, + MI_BFOBE, + MI_BMOBE, + MI_BMYBU, + MI_BFYPR, + MI_BFOTR, + MI_BMOTR, + MI_BMYPI, + MI_BMYBB, + MI_WMYCR, + MI_WFYST, + MI_WFOST, + MI_WMYST, + MI_WMOST, + MI_WFYRI, + MI_WFORI, + MI_WMYRI, + MI_WMORI, + MI_WFYBE, + MI_WMYBE, + MI_WFOBE, + MI_WMOBE, + MI_WMYCW, + MI_WMYGO, + MI_WFOGO, + MI_WMOGO, + MI_WFYLG, + MI_WMYLG, + MI_WFYBU, + MI_WMYBU, + MI_WMOBU, + MI_WFYPR, + MI_WFOTR, + MI_WMOTR, + MI_WMYPI, + MI_WMOCA, + MI_WFYJG, + MI_WMYJG, + MI_WFYSK, + MI_WMYSK, + MI_WFYSH, + MI_WFOSH, + MI_JFOTO, + MI_JMOTO, + + MI_CBA,// = 83, + MI_CBB, + MI_HNA, + MI_HNB, + MI_SGA, + MI_SGB, + MI_CLA, + MI_CLB, + MI_GDA, + MI_GDB, + MI_BKA, + MI_BKB, + MI_PGA, + MI_PGB, + MI_VICE1, + MI_VICE2, + MI_VICE3, + MI_VICE4, + MI_VICE5, + MI_VICE6, + MI_VICE7, + MI_VICE8, + MI_WFYG1, + MI_WFYG2,// = 106, // last regular ped + // three more peds possible + MI_SPECIAL01 = 109, + MI_SPECIAL02, + MI_SPECIAL03, + MI_SPECIAL04, + MI_SPECIAL05, + MI_SPECIAL06, + MI_SPECIAL07, + MI_SPECIAL08, + MI_SPECIAL09, + MI_SPECIAL10, + MI_SPECIAL11, + MI_SPECIAL12, + MI_SPECIAL13, + MI_SPECIAL14, + MI_SPECIAL15, + MI_SPECIAL16, + MI_SPECIAL17, + MI_SPECIAL18, + MI_SPECIAL19, + MI_SPECIAL20, + MI_SPECIAL21,// = 129, + + MI_LAST_PED = MI_SPECIAL21, + MI_FIRST_VEHICLE, + + MI_LANDSTAL = MI_FIRST_VEHICLE, + MI_IDAHO, + MI_STINGER, + MI_LINERUN, + MI_PEREN, + MI_SENTINEL, + MI_RIO, + MI_FIRETRUCK, + MI_TRASH, + MI_STRETCH, + MI_MANANA, + MI_INFERNUS, + MI_VOODOO, + MI_PONY, + MI_MULE, + MI_CHEETAH, + MI_AMBULAN, + MI_FBICAR, + MI_MOONBEAM, + MI_ESPERANT, + MI_TAXI, + MI_WASHING, + MI_BOBCAT, + MI_MRWHOOP, + MI_BFINJECT, + MI_HUNTER, + MI_POLICE, + MI_ENFORCER, + MI_SECURICA, + MI_BANSHEE, + MI_PREDATOR, + MI_BUS, + MI_RHINO, + MI_BARRACKS, + MI_CUBAN, + MI_CHOPPER, + MI_ANGEL, + MI_COACH, + MI_CABBIE, + MI_STALLION, + MI_RUMPO, + MI_RCBANDIT, + MI_ROMERO, + MI_PACKER, + MI_SENTXS, + MI_ADMIRAL, + MI_SQUALO, + MI_SEASPAR, + MI_PIZZABOY, + MI_GANGBUR, + MI_AIRTRAIN, + MI_DEADDODO, + MI_SPEEDER, + MI_REEFER, + MI_TROPIC, + MI_FLATBED, + MI_YANKEE, + MI_CADDY, + MI_ZEBRA, + MI_TOPFUN, + MI_SKIMMER, + MI_PCJ600, + MI_FAGGIO, + MI_FREEWAY, + MI_RCBARON, + MI_RCRAIDER, + MI_GLENDALE, + MI_OCEANIC, + MI_SANCHEZ, + MI_SPARROW, + MI_PATRIOT, + MI_LOVEFIST, + MI_COASTG, + MI_DINGHY, + MI_HERMES, + MI_SABRE, + MI_SABRETUR, + MI_PHEONIX, + MI_WALTON, + MI_REGINA, + MI_COMET, + MI_DELUXO, + MI_BURRITO, + MI_SPAND, + MI_MARQUIS, + MI_BAGGAGE, + MI_KAUFMAN, + MI_MAVERICK, + MI_VCNMAV, + MI_RANCHER, + MI_FBIRANCH, + MI_VIRGO, + MI_GREENWOO, + MI_JETMAX, + MI_HOTRING, + MI_SANDKING, + MI_BLISTAC, + MI_POLMAV, + MI_BOXVILLE, + MI_BENSON, + MI_MESA, + MI_RCGOBLIN, + MI_HOTRINA, + MI_HOTRINB, + MI_BLOODRA, + MI_BLOODRB, + MI_VICECHEE, + + // HACK + MI_TRAIN = -1, + MI_DODO = -2, + + MI_LAST_VEHICLE = MI_VICECHEE, + + MI_WHEEL_RIM, + MI_WHEEL_OFFROAD, + MI_WHEEL_TRUCK, + + MI_CAR_DOOR,// = 240, + MI_CAR_BUMPER, + MI_CAR_PANEL, + MI_CAR_BONNET, + MI_CAR_BOOT, + MI_CAR_WHEEL, + MI_BODYPARTA, + MI_BODYPARTB, + + MI_WHEEL_SPORT = 250, + MI_WHEEL_SALOON, + MI_WHEEL_LIGHTVAN, + MI_WHEEL_CLASSIC, + MI_WHEEL_ALLOY, + MI_WHEEL_LIGHTTRUCK, + MI_WHEEL_SMALLCAR, + + MI_AIRTRAIN_VLO, // = 257, + MI_MOBILE, + + MI_BRASS_KNUCKLES, // 259 + MI_SCREWDRIVER, + MI_GOLFCLUB, + MI_NIGHTSTICK, + MI_KNIFE, + MI_BASEBALL_BAT, + MI_HAMMER, + MI_MEAT_CLEAVER, + MI_MACHETE, + MI_KATANA, + MI_CHAINSAW, + MI_GRENADE, + MI_TEARGAS, + MI_MOLOTOV, + MI_MISSILE, + MI_COLT45, + MI_PYTHON, + MI_RUGER, + MI_SHOTGUN, + MI_SPAS12_SHOTGUN, + MI_STUBBY_SHOTGUN, + MI_M4, + MI_TEC9, + MI_UZI, + MI_SILENCEDINGRAM, + MI_MP5, + MI_SNIPERRIFLE, + MI_LASERSCOPE, + MI_ROCKETLAUNCHER, + MI_FLAMETHROWER, + MI_M60, + MI_MINIGUN, + MI_BOMB, + MI_CAMERA, + MI_FINGERS, + MI_MINIGUN2, + + MI_CUTOBJ01,// = 295, + MI_CUTOBJ02, + MI_CUTOBJ03, + MI_CUTOBJ04, + MI_CUTOBJ05, + + + NUM_DEFAULT_MODELS,// = 300 +}; + +enum{ + NUM_OF_SPECIAL_CHARS = 21, + NUM_OF_CUTSCENE_OBJECTS = 5 +}; + +void InitModelIndices(void); +void MatchModelString(const char *name, int16 id); +void TestModelIndices(void); + +inline bool +IsGlass(int16 id) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(id); + return mi->IsBuilding() && (mi->m_isCodeGlass || mi->m_isArtistGlass); +} + +inline bool +IsTrafficLight(int16 id) +{ + return id == MI_TRAFFICLIGHTS || + id == MI_TRAFFICLIGHTS_VERTICAL || + id == MI_TRAFFICLIGHTS_MIAMI || + id == MI_TRAFFICLIGHTS_TWOVERTICAL; +} + +inline bool +IsLightWithoutShift(int16 id) +{ + return id == MI_TRAFFICLIGHTS || + id == MI_SINGLESTREETLIGHTS1 || + id == MI_SINGLESTREETLIGHTS2 || + id == MI_SINGLESTREETLIGHTS3 || + id == MI_DOUBLESTREETLIGHTS; +} + +inline bool +IsLightWithPreRenderEffects(int16 id) +{ + return IsTrafficLight(id) || + id == MI_SINGLESTREETLIGHTS1 || + id == MI_SINGLESTREETLIGHTS2 || + id == MI_SINGLESTREETLIGHTS3 || + id == MI_DOUBLESTREETLIGHTS; +} + +inline bool +IsLightThatNeedsRepositioning(int16 id) +{ + return id == MI_SINGLESTREETLIGHTS1 || + id == MI_SINGLESTREETLIGHTS2 || + id == MI_SINGLESTREETLIGHTS3 || + id == MI_TRAFFICLIGHTS_MIAMI || + id == MI_TRAFFICLIGHTS_TWOVERTICAL || + id == MI_MLAMPPOST || + id == MI_STREETLAMP1 || + id == MI_STREETLAMP2; +} + +inline bool +IsLightObject(int16 id) +{ + return id == MI_TRAFFICLIGHTS_MIAMI || + id == MI_MLAMPPOST || + id == MI_SINGLESTREETLIGHTS1 || + id == MI_SINGLESTREETLIGHTS2 || + id == MI_SINGLESTREETLIGHTS3 || + id == MI_DOUBLESTREETLIGHTS || + id == MI_TRAFFICLIGHTS_TWOVERTICAL; +} + +inline bool +IsLampPost(int16 id) +{ + return id == MI_SINGLESTREETLIGHTS1 || + id == MI_SINGLESTREETLIGHTS2 || + id == MI_SINGLESTREETLIGHTS3 || + id == MI_BOLLARDLIGHT || + id == MI_MLAMPPOST || + id == MI_STREETLAMP1 || + id == MI_STREETLAMP2 || + id == MI_TELPOLE02 || + id == MI_TRAFFICLIGHTS_MIAMI || + id == MI_TRAFFICLIGHTS_TWOVERTICAL; +} + +inline bool +IsBodyPart(int16 id) +{ + return id == MI_BODYPARTA || id == MI_BODYPARTB; +} + +inline bool +IsPedModel(int16 id) +{ + return id >= MI_PLAYER && id <= MI_LAST_PED; +} +inline bool +IsPalmTreeModel(int16 id) +{ + return id == MI_VEG_PALM01 || + id == MI_VEG_PALM02 || + id == MI_VEG_PALM03 || + id == MI_VEG_PALM04 || + id == MI_VEG_PALM05 || + id == MI_VEG_PALM06 || + id == MI_VEG_PALM07 || + id == MI_VEG_PALM08; +} + +inline bool +IsTreeModel(int16 id) +{ + return id == MI_TREE2 || + id == MI_TREE3 || + id == MI_TREE6 || + id == MI_TREE8 || + IsPalmTreeModel(id); +} + +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 || + id == MI_PETROLPUMP2; +} + +inline bool +IsFence(int16 id) +{ + return id == MI_FENCE || + id == MI_FENCE2; +} diff --git a/src/miami/modelinfo/ModelInfo.cpp b/src/miami/modelinfo/ModelInfo.cpp new file mode 100644 index 00000000..14976cdb --- /dev/null +++ b/src/miami/modelinfo/ModelInfo.cpp @@ -0,0 +1,258 @@ +#include "common.h" + +#include "General.h" +#include "TempColModels.h" +#include "ModelIndices.h" +#include "ModelInfo.h" + +CBaseModelInfo *CModelInfo::ms_modelInfoPtrs[MODELINFOSIZE]; + +CStore CModelInfo::ms_simpleModelStore; +CStore CModelInfo::ms_timeModelStore; +CStore CModelInfo::ms_weaponModelStore; +CStore CModelInfo::ms_clumpModelStore; +CStore CModelInfo::ms_pedModelStore; +CStore CModelInfo::ms_vehicleModelStore; +CStore CModelInfo::ms_2dEffectStore; + +void +CModelInfo::Initialise(void) +{ + int i; + CSimpleModelInfo *m; + + debug("sizeof SimpleModelStore %d\n", sizeof(ms_simpleModelStore)); + debug("sizeof TimeModelStore %d\n", sizeof(ms_timeModelStore)); + debug("sizeof WeaponModelStore %d\n", sizeof(ms_weaponModelStore)); + debug("sizeof ClumpModelStore %d\n", sizeof(ms_clumpModelStore)); + debug("sizeof VehicleModelStore %d\n", sizeof(ms_vehicleModelStore)); + debug("sizeof PedModelStore %d\n", sizeof(ms_pedModelStore)); + debug("sizeof 2deffectsModelStore %d\n", sizeof(ms_2dEffectStore)); + + for(i = 0; i < MODELINFOSIZE; i++) + ms_modelInfoPtrs[i] = nil; + ms_2dEffectStore.Clear(); + ms_simpleModelStore.Clear(); + ms_timeModelStore.Clear(); + ms_weaponModelStore.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_timeModelStore.allocPtr; i++) + ms_timeModelStore.store[i].Shutdown(); + for(i = 0; i < ms_weaponModelStore.allocPtr; i++) + ms_weaponModelStore.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_2dEffectStore.allocPtr; i++) + ms_2dEffectStore.store[i].Shutdown(); + + ms_2dEffectStore.Clear(); + ms_simpleModelStore.Clear(); + ms_timeModelStore.Clear(); + ms_weaponModelStore.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; +} + +CTimeModelInfo* +CModelInfo::AddTimeModel(int id) +{ + CTimeModelInfo *modelinfo; + modelinfo = CModelInfo::ms_timeModelStore.Alloc(); + CModelInfo::ms_modelInfoPtrs[id] = modelinfo; + modelinfo->Init(); + return modelinfo; +} + +CWeaponModelInfo* +CModelInfo::AddWeaponModel(int id) +{ + CWeaponModelInfo *modelinfo; + modelinfo = CModelInfo::ms_weaponModelStore.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, int32 *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; +} + +CBaseModelInfo* +CModelInfo::GetModelInfo(const char *name, int minIndex, int maxIndex) +{ + if (minIndex > maxIndex) + return 0; + + CBaseModelInfo *modelinfo; + for(int i = minIndex; i <= maxIndex; i++){ + modelinfo = CModelInfo::ms_modelInfoPtrs[i]; + if(modelinfo && !CGeneral::faststricmp(modelinfo->GetModelName(), name)) + 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; +} + +bool +CModelInfo::IsCarModel(int32 id) +{ + return GetModelInfo(id)->GetModelType() == MITYPE_VEHICLE && + ((CVehicleModelInfo*)GetModelInfo(id))->m_vehicleType == VEHICLE_TYPE_CAR; +} + +bool +CModelInfo::IsHeliModel(int32 id) +{ + return GetModelInfo(id)->GetModelType() == MITYPE_VEHICLE && + ((CVehicleModelInfo*)GetModelInfo(id))->m_vehicleType == VEHICLE_TYPE_HELI; +} + +bool +CModelInfo::IsPlaneModel(int32 id) +{ + return GetModelInfo(id)->GetModelType() == MITYPE_VEHICLE && + ((CVehicleModelInfo*)GetModelInfo(id))->m_vehicleType == VEHICLE_TYPE_PLANE; +} + +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/miami/modelinfo/ModelInfo.h b/src/miami/modelinfo/ModelInfo.h new file mode 100644 index 00000000..6a6cd7ab --- /dev/null +++ b/src/miami/modelinfo/ModelInfo.h @@ -0,0 +1,52 @@ +#pragma once + +#include "2dEffect.h" +#include "SimpleModelInfo.h" +#include "MloModelInfo.h" +#include "TimeModelInfo.h" +#include "WeaponModelInfo.h" +#include "ClumpModelInfo.h" +#include "PedModelInfo.h" +#include "VehicleModelInfo.h" +#include "templates.h" + +class CModelInfo +{ + static CBaseModelInfo *ms_modelInfoPtrs[MODELINFOSIZE]; + static CStore ms_simpleModelStore; + static CStore ms_timeModelStore; + static CStore ms_weaponModelStore; + static CStore ms_clumpModelStore; + static CStore ms_pedModelStore; + static CStore ms_vehicleModelStore; + static CStore ms_2dEffectStore; + +public: + static void Initialise(void); + static void ShutDown(void); + + static CSimpleModelInfo *AddSimpleModel(int id); + static CTimeModelInfo *AddTimeModel(int id); + static CWeaponModelInfo *AddWeaponModel(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 CBaseModelInfo *GetModelInfo(const char *name, int32 *id); + static CBaseModelInfo *GetModelInfo(int32 id){ + return ms_modelInfoPtrs[id]; + } + static CBaseModelInfo *GetModelInfo(const char *name, int minIndex, int maxIndex); + static CColModel *GetColModel(int id){ + return ms_modelInfoPtrs[id]->GetColModel(); + } + + static bool IsBoatModel(int32 id); + static bool IsBikeModel(int32 id); + static bool IsCarModel(int32 id); + static bool IsHeliModel(int32 id); + static bool IsPlaneModel(int32 id); + static void ReInit2dEffects(); +}; diff --git a/src/miami/modelinfo/PedModelInfo.cpp b/src/miami/modelinfo/PedModelInfo.cpp new file mode 100644 index 00000000..25b260d3 --- /dev/null +++ b/src/miami/modelinfo/PedModelInfo.cpp @@ -0,0 +1,168 @@ +#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) +{ + CClumpModelInfo::DeleteRwObject(); + if(m_hitColModel) + delete m_hitColModel; + m_hitColModel = nil; +} + +// leftover... +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, }, +}; + +void +CPedModelInfo::SetClump(RpClump *clump) +{ +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachRimPipe(clump); +#endif + CClumpModelInfo::SetClump(clump); + SetFrameIds(m_pPedIds); // not needed in VC actually + if(m_hitColModel == nil) + CreateHitColModelSkinned(clump); + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPedCB); + if(strcmp(GetModelName(), "player") == 0) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, (void*)CVisibilityPlugins::RenderPlayerCB); +} + +struct ColNodeInfo +{ + Const char *name; + int pedNode; + int pieceType; + float x, z; + float radius; +}; + +#define NUMPEDINFONODES 10 +ColNodeInfo m_pColNodeInfos[NUMPEDINFONODES] = { + { nil, PED_HEAD, PEDPIECE_HEAD, 0.0f, 0.05f, 0.15f }, + { nil, PED_MID, PEDPIECE_TORSO, 0.0f, 0.15f, 0.2f }, + { nil, PED_MID, PEDPIECE_TORSO, 0.0f, -0.05f, 0.25f }, + { nil, PED_MID, PEDPIECE_MID, 0.0f, -0.25f, 0.25f }, + { nil, PED_UPPERARML, PEDPIECE_LEFTARM, 0.03f, -0.05f, 0.16f }, + { nil, PED_UPPERARMR, PEDPIECE_RIGHTARM, -0.03f, -0.05f, 0.16f }, + { nil, PED_LOWERLEGL, PEDPIECE_LEFTLEG, 0.0f, 0.15f, 0.2f }, + { nil, PED_LOWERLEGR, PEDPIECE_RIGHTLEG, 0.0f, 0.15f, 0.2f }, + { nil, PED_FOOTL, PEDPIECE_LEFTLEG, 0.0f, 0.15f, 0.15f }, + { nil, PED_FOOTR, PEDPIECE_RIGHTLEG, 0.0f, 0.15f, 0.15f }, +}; + +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)); + colmodel->boundingBox.Set(CVector(-0.5f, -0.5f, -1.2f), CVector(0.5f, 0.5f, 1.2f)); + 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; +} + +CColModel* +CPedModelInfo::AnimatePedColModelSkinnedWorld(RpClump *clump) +{ + if(m_hitColModel == nil) + CreateHitColModelSkinned(clump); + CColSphere *spheres = m_hitColModel->spheres; + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(clump); + RwMatrix *mat; + + for(int i = 0; i < NUMPEDINFONODES; i++){ + int id = ConvertPedNode2BoneTag(m_pColNodeInfos[i].pedNode); + int idx = RpHAnimIDGetIndex(hier, id); + + mat = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + 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); + } + return m_hitColModel; +} diff --git a/src/miami/modelinfo/PedModelInfo.h b/src/miami/modelinfo/PedModelInfo.h new file mode 100644 index 00000000..79bd7eaa --- /dev/null +++ b/src/miami/modelinfo/PedModelInfo.h @@ -0,0 +1,52 @@ +#pragma once + +#include "ClumpModelInfo.h" +#include "ColModel.h" +#include "PedType.h" + +enum PedNode { + PED_TORSO = 0, // has no bone! + PED_MID, + PED_HEAD, + PED_UPPERARML, + PED_UPPERARMR, + PED_HANDL, + PED_HANDR, + PED_UPPERLEGL, + PED_UPPERLEGR, + PED_FOOTL, + PED_FOOTR, + PED_LOWERLEGR, + PED_LOWERLEGL, + + PED_FOREARML, + PED_FOREARMR, + PED_CLAVICLEL, + PED_CLAVICLER, + PED_NECK, + + PED_NODE_MAX +}; + +class CPedModelInfo : public CClumpModelInfo +{ +public: + uint32 m_animGroup; + ePedType m_pedType; + ePedStats m_pedStatType; + uint32 m_carsCanDrive; + CColModel *m_hitColModel; + int8 radio1, radio2; + + static RwObjectNameIdAssocation m_pPedIds[PED_NODE_MAX]; + + CPedModelInfo(void) : CClumpModelInfo(MITYPE_PED) { m_hitColModel = nil; } + ~CPedModelInfo(void) { delete m_hitColModel; } + void DeleteRwObject(void); + void SetClump(RpClump *); + + void CreateHitColModelSkinned(RpClump *clump); + CColModel *GetHitColModel(void) { return m_hitColModel; } + CColModel *AnimatePedColModelSkinned(RpClump *clump); + CColModel *AnimatePedColModelSkinnedWorld(RpClump *clump); +}; diff --git a/src/miami/modelinfo/SimpleModelInfo.cpp b/src/miami/modelinfo/SimpleModelInfo.cpp new file mode 100644 index 00000000..18eb7e5f --- /dev/null +++ b/src/miami/modelinfo/SimpleModelInfo.cpp @@ -0,0 +1,194 @@ +#include "common.h" + +#include "General.h" +#include "Camera.h" +#include "Renderer.h" +#include "ModelInfo.h" +#include "AnimManager.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(); + if(GetAnimFileIndex() != -1) + CAnimManager::RemoveAnimBlockRef(GetAnimFileIndex()); + } +} + +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_wetRoadReflection = 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; + m_noShadows = 0; + m_ignoreDrawDist = 0; + m_isCodeGlass = 0; + m_isArtistGlass = 0; +} + +void +CSimpleModelInfo::SetAtomic(int n, RpAtomic *atomic) +{ + AddTexDictionaryRef(); + m_atomics[n] = atomic; + if(GetAnimFileIndex() != -1) + CAnimManager::AddAnimBlockRef(GetAnimFileIndex()); + RpGeometry *geo = RpAtomicGetGeometry(atomic); + if(m_ignoreLight) + RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) & ~rpGEOMETRYLIGHT); + if(RpGeometryGetFlags(geo) & rpGEOMETRYNORMALS && + RpGeometryGetNumTriangles(geo) > 200) + debug("%s has %d polys\n", m_name, RpGeometryGetNumTriangles(geo)); + +#ifdef EXTENDED_PIPELINES + if(m_wetRoadReflection) + CustomPipes::AttachGlossPipe(atomic); + else + 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; +} + +RpAtomic* +CSimpleModelInfo::GetFirstAtomicFromDistance(float dist) +{ + if(dist < m_lodDistances[0] * TheCamera.LODDistMultiplier) + return m_atomics[0]; + return nil; +} + +void +CSimpleModelInfo::FindRelatedModel(int32 minID, int32 maxID) +{ + int i; + CBaseModelInfo *mi; + for(i = minID; i <= maxID; i++){ + mi = CModelInfo::GetModelInfo(i); + if(mi && mi != this && + !CGeneral::faststrcmp(GetModelName()+3, mi->GetModelName()+3)){ + assert(mi->IsSimple()); + this->SetRelatedModel((CSimpleModelInfo*)mi); + return; + } + } +} + +#define NEAR_DRAW_DIST 0.0f // 100.0f in liberty city + +void +CSimpleModelInfo::SetupBigBuilding(int32 minID, int32 maxID) +{ + CSimpleModelInfo *related; + if(m_lodDistances[0] > LOD_DISTANCE && GetRelatedModel() == nil){ + m_isBigBuilding = 1; + FindRelatedModel(minID, maxID); + related = GetRelatedModel(); + if(related){ + m_lodDistances[2] = related->GetLargestLodDistance()/TheCamera.LODDistMultiplier; + if(m_drawLast){ + m_drawLast = false; + debug("%s was draw last\n", GetModelName()); + } + }else + m_lodDistances[2] = NEAR_DRAW_DIST; + } +} diff --git a/src/miami/modelinfo/SimpleModelInfo.h b/src/miami/modelinfo/SimpleModelInfo.h new file mode 100644 index 00000000..b511c235 --- /dev/null +++ b/src/miami/modelinfo/SimpleModelInfo.h @@ -0,0 +1,64 @@ +#pragma once + +#include "BaseModelInfo.h" + +class CSimpleModelInfo : public CBaseModelInfo +{ +public: + // atomics[2] is often a pointer to the non-LOD modelinfo + RpAtomic *m_atomics[3]; + // m_lodDistances[2] holds the near distance for LODs + float m_lodDistances[3]; + uint8 m_numAtomics; + uint8 m_alpha; + uint16 m_firstDamaged : 2; // 0: no damage model + // 1: 1 and 2 are damage models + // 2: 2 is damage model + uint16 m_wetRoadReflection : 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; + uint16 m_noShadows : 1; + + uint16 m_ignoreDrawDist : 1; + uint16 m_isCodeGlass : 1; + uint16 m_isArtistGlass : 1; +#ifdef RW_DC + bool m_isAlphaTest; +#endif + + 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]; } + + virtual void SetAtomic(int n, RpAtomic *atomic); + + void Init(void); + void IncreaseAlpha(void); + void SetLodDistances(float *dist); + float GetLodDistance(int i); + float GetNearDistance(void); + float GetLargestLodDistance(void); + RpAtomic *GetAtomicFromDistance(float dist); + RpAtomic *GetFirstAtomicFromDistance(float dist); + void FindRelatedModel(int32 minID, int32 maxID); + void SetupBigBuilding(int32 minID, int32 maxID); + + void SetNumAtomics(int n) { m_numAtomics = n; } + CSimpleModelInfo *GetRelatedModel(void){ + return (CSimpleModelInfo*)m_atomics[2]; } + void SetRelatedModel(CSimpleModelInfo *m){ + m_atomics[2] = (RpAtomic*)m; } +}; +//static_assert(sizeof(CSimpleModelInfo) == 0x4C, "CSimpleModelInfo: error"); diff --git a/src/miami/modelinfo/TimeModelInfo.cpp b/src/miami/modelinfo/TimeModelInfo.cpp new file mode 100644 index 00000000..0db5fb78 --- /dev/null +++ b/src/miami/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/miami/modelinfo/TimeModelInfo.h b/src/miami/modelinfo/TimeModelInfo.h new file mode 100644 index 00000000..6e3c64fb --- /dev/null +++ b/src/miami/modelinfo/TimeModelInfo.h @@ -0,0 +1,20 @@ +#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); +}; +//static_assert(sizeof(CTimeModelInfo) == 0x58, "CTimeModelInfo: error"); diff --git a/src/miami/modelinfo/VehicleModelInfo.cpp b/src/miami/modelinfo/VehicleModelInfo.cpp new file mode 100644 index 00000000..d31962ce --- /dev/null +++ b/src/miami/modelinfo/VehicleModelInfo.cpp @@ -0,0 +1,1193 @@ +#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]; +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, 0 }, + { "boat_rudder_hi", BOAT_RUDDER, 0 }, + { "boat_flap_left", BOAT_FLAP_LEFT, 0 }, + { "boat_flap_right", BOAT_FLAP_RIGHT, 0 }, + { "boat_rearflap_left", BOAT_REARFLAP_LEFT, 0 }, + { "boat_rearflap_right", BOAT_REARFLAP_RIGHT, 0 }, +#ifdef FIX_BUGS + // let's just accept both + { "windscreen", BOAT_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_DRAWLAST }, + { "windscreen_hi_ok", BOAT_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_DRAWLAST }, +#else +#ifdef GTA_PS2 + { "windscreen", BOAT_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_DRAWLAST }, +#else + { "windscreen_hi_ok", BOAT_WINDSCREEN, VEHICLE_FLAG_WINDSCREEN | VEHICLE_FLAG_DRAWLAST }, +#endif +#endif + { "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 }, + { "handlebars", BIKE_HANDLEBARS, 0 }, + { "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 *CVehicleModelInfo::ms_vehicleDescs[] = { + carIds, + boatIds, + trainIds, + heliIds, + planeIds, + bikeIds +}; + +bool gbBlackCars; +bool gbPinkCars; + +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; + m_animFileIndex = -1; +} + +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 && m_comps[comp1]){ + 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 && m_comps[comp2]){ + 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(); + SetEnvironmentMap(); +} + +void +CVehicleModelInfo::SetAnimFile(const char *file) +{ + if(strcasecmp(file, "null") == 0) + return; + + m_animFileName = new char[strlen(file)+1]; + strcpy(m_animFileName, file); +} + +void +CVehicleModelInfo::ConvertAnimFileIndex(void) +{ + if(m_animFileIndex != -1){ + // we have a string pointer in that union + int32 index = CAnimManager::GetAnimationBlockIndex(m_animFileName); + delete[] m_animFileName; + m_animFileIndex = index; + } +} + +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: nil in 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; + bool alpha; + + clump = (RpClump*)data; + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + alpha = false; + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), HasAlphaMaterialCB, &alpha); + if(strcmp(name, "boat_hi") == 0 || !CGeneral::faststrncmp(name, "extra", 5)) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailCB_Boat); + else if(strstr(name, "_hi")){ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_Boat); + 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::RenderVehicleLoDetailCB_Boat); + else{ + if(alpha) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_Boat); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + } + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_Heli(RpAtomic *atomic, void *data) +{ + char *name; + + name = GetFrameNodeName(RpAtomicGetFrame(atomic)); + if(strncmp(name, "toprotor", 8) == 0) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleRotorAlphaCB); + else if(strncmp(name, "rearrotor", 9) == 0) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleTailRotorAlphaCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + return atomic; +} + +RpAtomic* +CVehicleModelInfo::SetAtomicRendererCB_RealHeli(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(strncmp(name, "toprotor", 8) == 0) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleRotorAlphaCB); + else if(strncmp(name, "rearrotor", 9) == 0) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleTailRotorAlphaCB); + else 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: nil in gta + }else if(strstr(name, "_vlo")) + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderVehicleReallyLowDetailCB); + else + CVisibilityPlugins::SetAtomicRenderCallback(atomic, nil); + HideDamagedAtomicCB(atomic, nil); + return atomic; +} + +void +CVehicleModelInfo::SetAtomicRenderCallbacks(void) +{ +#ifdef GTA_TRAIN + if(m_vehicleType == VEHICLE_TYPE_TRAIN) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Train, nil); + else +#endif + if(m_vehicleType == VEHICLE_TYPE_HELI) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Heli, nil); + else if(m_vehicleType == VEHICLE_TYPE_PLANE) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_BigVehicle, nil); + else if(m_vehicleType == VEHICLE_TYPE_BOAT) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_Boat, m_clump); + else if(mod_HandlingManager.GetHandlingData((tVehicleType)m_handlingId)->Flags & HANDLING_IS_HELI) + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB_RealHeli, m_clump); + else + RpClumpForAllAtomics(m_clump, SetAtomicRendererCB, m_clump); +} + +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); + case 3: + n = CGeneral::GetRandomNumberInRange(0, 1+CountCompsInRule(comps)); + if(n != 0) + return COMPRULE_COMPN(comps, n-1); + return -1; + case 4: +#ifdef FIX_BUGS + return CGeneral::GetRandomNumberInRange(0, 6); +#else + return CGeneral::GetRandomNumberInRange(0, 5); +#endif + } + 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; +} + +static int maxFirstMaterials; +static int maxSecondMaterials; + +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; + maxFirstMaterials = Max(maxFirstMaterials, cbdata.numMats1); + maxSecondMaterials = Max(maxSecondMaterials, cbdata.numMats2); + m_currentColour1 = -1; + m_currentColour2 = -1; +} + +void +CVehicleModelInfo::SetVehicleColour(uint8 c1, uint8 c2) +{ + RwRGBA col, *colp; + RpMaterial **matp; + + if(c1 != m_currentColour1){ + col = ms_vehicleColourTable[c1]; + for(matp = m_materials1; *matp; matp++){ + colp = (RwRGBA*)RpMaterialGetColor(*matp); // get rid of const + colp->red = col.red; + colp->green = col.green; + colp->blue = col.blue; + } + m_currentColour1 = c1; + } + + if(c2 != m_currentColour2){ + col = ms_vehicleColourTable[c2]; + for(matp = m_materials2; *matp; matp++){ + colp = (RwRGBA*)RpMaterialGetColor(*matp); // get rid of const + colp->red = col.red; + colp->green = col.green; + colp->blue = col.blue; + } + m_currentColour2 = c2; + } +} + +void +CVehicleModelInfo::ChooseVehicleColour(uint8 &col1, uint8 &col2) +{ + if(m_numColours == 0 || gbBlackCars){ + col1 = 0; + col2 = 0; + }else if(gbPinkCars){ + col1 = 68; + col2 = 68; + }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(gbBlackCars){ + *col1 = 0; + *col2 = 0; + }else if(gbPinkCars){ + *col1 = 68; + *col2 = 68; + }else{ + 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; + } +} + +// unused +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; + 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; + 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]); + ms_colourTextureTable[i] = nil; + } + } +} + +RpMaterial* +CVehicleModelInfo::GetMatFXEffectMaterialCB(RpMaterial *material, void *data) +{ + if(RpMatFXMaterialGetEffects(material) == rpMATFXEFFECTNULL) + return material; + *(int*)data = RpMatFXMaterialGetEffects(material); + return nil; +} + +RpMaterial* +CVehicleModelInfo::SetDefaultEnvironmentMapCB(RpMaterial *material, void *data) +{ + if(RpMatFXMaterialGetEffects(material) == rpMATFXEFFECTENVMAP){ + RpMatFXMaterialSetEnvMapFrame(material, pMatFxIdentityFrame); + if(RpMaterialGetTexture(material) == nil) + RpMaterialSetTexture(material, gpWhiteTexture); + RpMatFXMaterialSetEffects(material, rpMATFXEFFECTENVMAP); +#ifndef PS2_MATFX + float coef = RpMatFXMaterialGetEnvMapCoefficient(material); + coef *= 0.25f; // Tone down a bit for PC + RpMatFXMaterialSetEnvMapCoefficient(material, coef); +#endif + } + return material; +} + +RpAtomic* +CVehicleModelInfo::SetEnvironmentMapCB(RpAtomic *atomic, void *data) +{ + int fx; + RpGeometry *geo; + + geo = RpAtomicGetGeometry(atomic); + fx = rpMATFXEFFECTNULL; + RpGeometryForAllMaterials(geo, GetMatFXEffectMaterialCB, &fx); + if(fx != rpMATFXEFFECTNULL){ + RpMatFXAtomicEnableEffects(atomic); + RpGeometryForAllMaterials(geo, SetDefaultEnvironmentMapCB, data); + } + return atomic; +} + +void +CVehicleModelInfo::SetEnvironmentMap(void) +{ + CSimpleModelInfo *wheelmi; + int32 i; + + if(pMatFxIdentityFrame == nil){ + RwV3d axis = { 1.0f, 0.0f, 0.0f }; + pMatFxIdentityFrame = RwFrameCreate(); + RwMatrixRotate(RwFrameGetMatrix(pMatFxIdentityFrame), &axis, 60.0f, rwCOMBINEREPLACE); + RwFrameUpdateObjects(pMatFxIdentityFrame); + RwFrameGetLTM(pMatFxIdentityFrame); + } + + RpClumpForAllAtomics(m_clump, SetEnvironmentMapCB, nil); + if(m_wheelId != -1){ + wheelmi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(m_wheelId); + for(i = 0; i < wheelmi->m_numAtomics; i++) + SetEnvironmentMapCB(wheelmi->m_atomics[i], nil); + } + +#ifdef EXTENDED_PIPELINES + CustomPipes::AttachVehiclePipe(m_clump); +#endif +} + +void +CVehicleModelInfo::LoadEnvironmentMaps(void) +{ + int32 txdslot; + + txdslot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(txdslot); + if(gpWhiteTexture == nil){ + gpWhiteTexture = RwTextureRead("white", nil); + RwTextureGetName(gpWhiteTexture)[0] = '@'; + RwTextureSetFilterMode(gpWhiteTexture, rwFILTERLINEAR); + } + CTxdStore::PopCurrentTxd(); +} + +void +CVehicleModelInfo::ShutdownEnvironmentMaps(void) +{ + RwTextureDestroy(gpWhiteTexture); + gpWhiteTexture = nil; + 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; + case MI_HUNTER: + n = 1; + break; + default: + n = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(id))->m_numDoors; + } + + if(n == 0) + return id == MI_RCBANDIT || id == MI_PIZZABOY || id == MI_BAGGAGE ? 0 : 1; + + if(id == MI_COACH) + return 8; + + return n - 1; +} diff --git a/src/miami/modelinfo/VehicleModelInfo.h b/src/miami/modelinfo/VehicleModelInfo.h new file mode 100644 index 00000000..c7a41126 --- /dev/null +++ b/src/miami/modelinfo/VehicleModelInfo.h @@ -0,0 +1,166 @@ +#pragma once + +#include "ClumpModelInfo.h" + +enum { + NUM_FIRST_MATERIALS = 24, + NUM_SECOND_MATERIALS = 20, + NUM_VEHICLE_COLOURS = 8, +}; + +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, + 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 = 5 +}; + +class CVehicleModelInfo : public CClumpModelInfo +{ +public: + uint8 m_lastColour1; + uint8 m_lastColour2; + char m_gameName[10]; + int32 m_vehicleType; + float m_wheelScale; + union { + int16 m_wheelId; + int16 m_planeLodId; + }; + int16 m_handlingId; + int8 m_numDoors; + int8 m_vehicleClass; + int8 m_level; + int8 m_numComps; + int16 m_frequency; + 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; + RpAtomic *m_comps[6]; + // This is stupid, CClumpModelInfo already has it! + union { + int32 m_animFileIndex; + char *m_animFileName; + }; + + static int8 ms_compsToUse[2]; + static int8 ms_compsUsed[2]; + 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 *); + void SetAnimFile(const char *file); + void ConvertAnimFileIndex(void); + int GetAnimFileIndex(void) { return m_animFileIndex; } + + 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); + static RpAtomic *SetAtomicRendererCB_RealHeli(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 *SetDefaultEnvironmentMapCB(RpMaterial *material, void *data); + static RpMaterial *GetMatFXEffectMaterialCB(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; } +}; + +extern bool gbBlackCars; +extern bool gbPinkCars; diff --git a/src/miami/modelinfo/WeaponModelInfo.cpp b/src/miami/modelinfo/WeaponModelInfo.cpp new file mode 100644 index 00000000..d9294c3f --- /dev/null +++ b/src/miami/modelinfo/WeaponModelInfo.cpp @@ -0,0 +1,53 @@ +#include "common.h" + +#include "ModelInfo.h" +#include "AnimManager.h" +#include "VisibilityPlugins.h" + +void +CWeaponModelInfo::SetAnimFile(const char *file) +{ + if(strcasecmp(file, "null") == 0) + return; + + m_animFileName = new char[strlen(file)+1]; + strcpy(m_animFileName, file); +} + +void +CWeaponModelInfo::ConvertAnimFileIndex(void) +{ + if(m_animFileIndex != -1){ + // we have a string pointer in that union + int32 index = CAnimManager::GetAnimationBlockIndex(m_animFileName); + delete[] m_animFileName; + m_animFileIndex = index; + } +} + +void +CWeaponModelInfo::Init(void) +{ + CSimpleModelInfo::Init(); + SetWeaponInfo(0); +} + +void +CWeaponModelInfo::SetWeaponInfo(int32 weaponId) +{ + m_atomics[2] = (RpAtomic*)weaponId; +} + +eWeaponType +CWeaponModelInfo::GetWeaponInfo(void) +{ + return (eWeaponType)(uintptr)m_atomics[2]; +} + +void +CWeaponModelInfo::SetAtomic(int n, RpAtomic *atomic) +{ + CSimpleModelInfo::SetAtomic(n, atomic); + CVisibilityPlugins::SetAtomicRenderCallback(atomic, CVisibilityPlugins::RenderWeaponCB); +} + diff --git a/src/miami/modelinfo/WeaponModelInfo.h b/src/miami/modelinfo/WeaponModelInfo.h new file mode 100644 index 00000000..548bf8a6 --- /dev/null +++ b/src/miami/modelinfo/WeaponModelInfo.h @@ -0,0 +1,23 @@ +#pragma once + +#include "SimpleModelInfo.h" +#include "WeaponType.h" + +class CWeaponModelInfo : public CSimpleModelInfo +{ + union { + int32 m_animFileIndex; + char *m_animFileName; + }; +public: + CWeaponModelInfo(void) : CSimpleModelInfo(MITYPE_WEAPON) { m_animFileIndex = -1; } + + virtual void SetAnimFile(const char *file); + virtual void ConvertAnimFileIndex(void); + virtual int GetAnimFileIndex(void) { return m_animFileIndex; } + virtual void SetAtomic(int n, RpAtomic *atomic); + + void Init(void); + void SetWeaponInfo(int32 weaponId); + eWeaponType GetWeaponInfo(void); +}; diff --git a/src/miami/objects/CutsceneObject.cpp b/src/miami/objects/CutsceneObject.cpp new file mode 100644 index 00000000..7d9fe640 --- /dev/null +++ b/src/miami/objects/CutsceneObject.cpp @@ -0,0 +1,182 @@ +#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 "CutsceneShadow.h" +#include "CutsceneObject.h" +#include "ModelIndices.h" +#include "RpAnimBlend.h" + + +CCutsceneObject::CCutsceneObject(void) +{ + SetStatus(STATUS_SIMPLE); + bUsesCollision = false; + bStreamingDontDelete = true; + ObjectCreatedBy = CUTSCENE_OBJECT; + m_fMass = 1.0f; + m_fTurnMass = 1.0f; + + m_pAttachTo = nil; + m_pAttachmentObject = nil; + m_pShadow = nil; +} + +CCutsceneObject::~CCutsceneObject(void) +{ + if ( m_pShadow ) + { + delete m_pShadow; + m_pShadow = nil; + } +} + +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::CreateShadow(void) +{ + if ( IsPedModel(GetModelIndex()) ) + { + m_pShadow = new CCutsceneShadow(); + if (!m_pShadow->IsInitialized()) + m_pShadow->Create(m_rwObject, 6, true, 4, true); + } +} + +void +CCutsceneObject::ProcessControl(void) +{ + CPhysical::ProcessControl(); + + if ( m_pAttachTo ) + { + if ( m_pAttachmentObject ) + GetMatrix() = CMatrix((RwMatrix*)m_pAttachTo); + else + GetMatrix() = CMatrix(RwFrameGetLTM((RwFrame*)m_pAttachTo)); + } + else + { + if(CTimer::GetTimeStep() < 1/100.0f) + m_vecMoveSpeed *= 100.0f; + else + m_vecMoveSpeed *= 1.0f/CTimer::GetTimeStep(); + + ApplyMoveSpeed(); + } +} + +static RpMaterial* +MaterialSetAlpha(RpMaterial *material, void *data) +{ + ((RwRGBA*)RpMaterialGetColor(material))->alpha = (uint8)(uintptr)data; + return material; +} + +void +CCutsceneObject::PreRender(void) +{ + if ( m_pAttachTo ) + { + if ( m_pAttachmentObject ) + { + m_pAttachmentObject->UpdateRpHAnim(); + GetMatrix() = CMatrix((RwMatrix*)m_pAttachTo); + } + else + GetMatrix() = CMatrix(RwFrameGetLTM((RwFrame*)m_pAttachTo)); + + if ( RwObjectGetType(m_rwObject) == rpCLUMP && IsClumpSkinned(GetClump()) ) + { + RpAtomic *atomic = GetFirstAtomic(GetClump()); + atomic->boundingSphere.center = (*RPANIMBLENDCLUMPDATA(GetClump()))->frames[0].hanimFrame->t; + } + } + + if ( RwObjectGetType(m_rwObject) == rpCLUMP ) + UpdateRpHAnim(); + + if(IsPedModel(GetModelIndex())) + { + if ( m_pShadow == nil ) + { + 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]); + } + else + { + if ( m_pShadow->IsInitialized() ) + m_pShadow->UpdateForCutscene(); + + CShadows::StoreShadowForCutscenePedObject(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) +{ + SetCullMode(rwCULLMODECULLNONE); + CObject::Render(); + SetCullMode(rwCULLMODECULLBACK); +} + +bool +CCutsceneObject::SetupLighting(void) +{ + ActivateDirectional(); + SetAmbientColoursForPedsCarsAndObjects(); + + if(bRenderScorched){ + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + }else{ + CVector coors = GetPosition(); + float lighting = CPointLights::GenerateLightsAffectingObject(&coors); + if(lighting != 1.0f){ + SetAmbientAndDirectionalColours(lighting); + return true; + } + } + + return false; +} + +void +CCutsceneObject::RemoveLighting(bool reset) +{ + CRenderer::RemoveVehiclePedLights(this, reset); +} diff --git a/src/miami/objects/CutsceneObject.h b/src/miami/objects/CutsceneObject.h new file mode 100644 index 00000000..af24c0a6 --- /dev/null +++ b/src/miami/objects/CutsceneObject.h @@ -0,0 +1,24 @@ +#pragma once + +#include "Object.h" + +class CCutsceneShadow; + +class CCutsceneObject : public CObject +{ +public: + CCutsceneShadow *m_pShadow; + void *m_pAttachTo; + CObject *m_pAttachmentObject; + + CCutsceneObject(void); + ~CCutsceneObject(void); + + void SetModelIndex(uint32 id); + void CreateShadow(void); + void ProcessControl(void); + void PreRender(void); + void Render(void); + bool SetupLighting(void); + void RemoveLighting(bool reset); +}; diff --git a/src/miami/objects/DummyObject.cpp b/src/miami/objects/DummyObject.cpp new file mode 100644 index 00000000..8dd1643d --- /dev/null +++ b/src/miami/objects/DummyObject.cpp @@ -0,0 +1,14 @@ +#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; + m_area = obj->m_area; +} diff --git a/src/miami/objects/DummyObject.h b/src/miami/objects/DummyObject.h new file mode 100644 index 00000000..680df685 --- /dev/null +++ b/src/miami/objects/DummyObject.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Dummy.h" + +class CObject; + +class CDummyObject : public CDummy +{ +public: + CDummyObject(void) {} + CDummyObject(CObject *obj); +}; diff --git a/src/miami/objects/Object.cpp b/src/miami/objects/Object.cpp new file mode 100644 index 00000000..64c9e256 --- /dev/null +++ b/src/miami/objects/Object.cpp @@ -0,0 +1,858 @@ +#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" +#include "WaterLevel.h" +#include "Timecycle.h" +#include "Stats.h" +#include "SpecialFX.h" + +#define BEACHBALL_MAX_SCORE 250 +// the proportion of the ball speed compared to the player speed when it hits the player +#define BEACHBALL_SPEED_PROPORTION 0.4f + +int16 CObject::nNoTempObjects; +//int16 CObject::nBodyCastHealth = 1000; +float CObject::fDistToNearestTree; + +// 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; + m_nCostValue = 0; + bIsPickup = false; + bPickupObjWithMessage = false; + bOutOfStock = false; + bGlassCracked = false; + bGlassBroken = false; + bHasBeenDamaged = false; + m_nRefModelIndex = -1; + bUseVehicleColours = false; + // bIsStreetLight = false; // duplicate + m_pCurSurface = nil; + m_pCollidingEntity = nil; + m_nBeachballBounces = 0; + bIsStreetLight = false; + m_area = AREA_EVERYWHERE; +} + +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; + m_area = dummy->m_area; +} + +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; + } + int16 mi = GetModelIndex(); + if ((mi == MI_EXPLODINGBARREL || mi == MI_PETROLPUMP || mi == MI_PETROLPUMP2) && bHasBeenDamaged && bIsVisible + && (CGeneral::GetRandomNumber() & 0x1F) == 10) { + bExplosionProof = true; + bIsVisible = false; + bUsesCollision = false; + bAffectedByGravity = false; + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + } + if (mi == MI_RCBOMB) { + float fTurnForce = -(m_fTurnMass / 20.0f); + CPhysical::ApplyTurnForce(m_vecMoveSpeed * fTurnForce, -GetForward()); + float fScalar = 1.0f - m_vecMoveSpeed.MagnitudeSqr() / 5.0f; + float fScalarTimed = Pow(fScalar, CTimer::GetTimeStep()); + m_vecMoveSpeed *= fScalarTimed; + } + if (mi == MI_BEACHBALL) { + float fTimeStep = Pow(0.95f, CTimer::GetTimeStep()); + float fPreviousVecSpeedMag = m_vecMoveSpeed.Magnitude2D(); + m_vecMoveSpeed.x *= fTimeStep; + m_vecMoveSpeed.y *= fTimeStep; + m_vecMoveSpeed.z += fPreviousVecSpeedMag - m_vecMoveSpeed.Magnitude2D(); + if (!FindPlayerVehicle()) { + CVector distance; + distance.x = FindPlayerCoors().x - GetPosition().x; + distance.y = FindPlayerCoors().y - GetPosition().y; + distance.z = FindPlayerCoors().z - GetPosition().z; + if (distance.z > 0.0 && distance.z < 1.5f && distance.Magnitude2D() < 1.0f) { + CVector playerSpeed = FindPlayerSpeed(); + if (fPreviousVecSpeedMag < 0.05f && playerSpeed.Magnitude() > 0.1f) { + playerSpeed.z = 0.0f; + playerSpeed.Normalise(); + playerSpeed.z = 0.3f; + m_vecMoveSpeed = CVector( + playerSpeed.x * BEACHBALL_SPEED_PROPORTION, + playerSpeed.y * BEACHBALL_SPEED_PROPORTION, + 0.3f * BEACHBALL_SPEED_PROPORTION + ); + PlayOneShotScriptObject(SCRIPT_SOUND_HIT_BALL, GetPosition()); + m_vecTurnSpeed += CVector( + ((CGeneral::GetRandomNumber() % 16) - 7) / 10.0f, + ((CGeneral::GetRandomNumber() % 16) - 7) / 10.0f, + 0.0f); + if (m_nBeachballBounces > 0) { + m_nBeachballBounces++; + } + if (m_nBeachballBounces > 0) { + sprintf(gString, "%d", m_nBeachballBounces); + CMoneyMessages::RegisterOne(GetPosition(), gString, 255, 50, 0, 0.6f, 0.5f); + CStats::RegisterHighestScore(3, m_nBeachballBounces); + } + } + } + if (distance.z > -1.05 && distance.z < -0.6 && m_vecMoveSpeed.z < 0.0f && distance.Magnitude2D() < 0.9f) { + m_vecMoveSpeed.x += (CGeneral::GetRandomNumber() % 8 - 3) / 100.0f; + m_vecMoveSpeed.y += (CGeneral::GetRandomNumber() % 8 - 3) / 100.0f; + m_vecMoveSpeed.z = Max(m_vecMoveSpeed.z + 0.3f, 0.2f); + PlayOneShotScriptObject(SCRIPT_SOUND_HIT_BALL, GetPosition()); + m_vecTurnSpeed.x += (CGeneral::GetRandomNumber() % 16 - 7) / 10.0f; + m_vecTurnSpeed.y += (CGeneral::GetRandomNumber() % 16 - 7) / 10.0f; + m_nBeachballBounces++; + m_nBeachballBounces = Min(m_nBeachballBounces, BEACHBALL_MAX_SCORE); + sprintf(gString, "%d", m_nBeachballBounces); + CMoneyMessages::RegisterOne(GetPosition(), gString, 255, 50, 0, 0.6f, 0.5f); + CStats::RegisterHighestScore(3, m_nBeachballBounces); + } + } + } + if (bIsBIGBuilding) { + bIsInSafePosition = true; + } +} + +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); + } + + float red = (0.8f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed_Obj()) * 165.75f; + float green = (0.8f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen_Obj()) * 165.75f; + float blue = (0.8f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue_Obj()) * 165.75f; + + red = Clamp(red, 0.0f, 255.0f); + green = Clamp(green, 0.0f, 255.0f); + blue = Clamp(blue, 0.0f, 255.0f); + + int alpha = CGeneral::GetRandomNumberInRange(196, 225); + + RwRGBA color = { (uint8)red, (uint8)green, (uint8)blue, (uint8)alpha }; + + if (this->GetModelIndex() == MI_YT_MAIN_BODY) { + float moveSpeedMagnitude = this->GetMoveSpeed().Magnitude(); + if (moveSpeedMagnitude > 0.0f) { + float scaleMax = GetColModel()->boundingBox.max.y * 0.85f; + + CVector dir = this->GetMoveSpeed() + 0.3f * this->GetRight() - 0.5f * this->GetForward(); + dir.z += 0.05f * moveSpeedMagnitude; + + CVector pos = scaleMax * this->GetForward() + 2.25f * this->GetRight() + this->GetPosition(); + + float fWaterLevel; + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true); + pos.z = fWaterLevel + 0.75f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 1.2f * moveSpeedMagnitude, color, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0); + + float scaleMin = GetColModel()->boundingBox.min.y; + + dir = this->GetMoveSpeed() - 0.5f * this->GetForward(); + dir.z += 0.05f * moveSpeedMagnitude; + + pos = scaleMin * this->GetForward() + 4.5f * this->GetRight() + this->GetPosition(); + + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true); + pos.z = fWaterLevel + 0.55f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0); + + pos = scaleMin * 1.1f * this->GetForward() + 2.25f * this->GetRight() + this->GetPosition(); + + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true); + pos.z = fWaterLevel + 0.55f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0); + + pos = scaleMin * 1.1f * this->GetForward() - 0.05f * this->GetRight() + this->GetPosition(); + + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true); + pos.z = fWaterLevel + 0.55f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0); + } + } + + if (this->GetModelIndex() == MI_YT_MAIN_BODY2) { + float moveSpeedMagnitude = this->GetMoveSpeed().Magnitude(); + if (moveSpeedMagnitude > 0.0f) { + float scaleMax = GetColModel()->boundingBox.max.y * 0.85f; + + CVector dir = this->GetMoveSpeed() - 0.3f * this->GetRight() - 0.5f * this->GetForward(); + dir.z += 0.05f * moveSpeedMagnitude; + + CVector pos = scaleMax * this->GetForward() - 2.25f * this->GetRight() + this->GetPosition(); + + float fWaterLevel; + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true); + pos.z = fWaterLevel + 0.75f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 1.2f * moveSpeedMagnitude, color, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0); + + float scaleMin = GetColModel()->boundingBox.min.y; + + dir = this->GetMoveSpeed() - 0.5f * this->GetForward(); + dir.z += 0.05f * moveSpeedMagnitude; + + pos = scaleMin * this->GetForward() - 4.5f * this->GetRight() + this->GetPosition(); + + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true); + pos.z = fWaterLevel + 0.55f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0); + + pos = scaleMin * 1.1f * this->GetForward() - 2.25f * this->GetRight() + this->GetPosition(); + + CWaterLevel::GetWaterLevel(pos.x, pos.y, pos.z, &fWaterLevel, true); + pos.z = fWaterLevel + 0.55f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, pos, dir, nil, 0.9f, color, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), 0, 0); + } + } + + CEntity::Render(); +} + +bool +CObject::SetupLighting(void) +{ + if (bRenderScorched) { + WorldReplaceNormalLightsWithScorched(Scene.world, 0.1f); + return true; + } else if (bIsPickup) { + SetFullAmbient(); + return true; + } else if (bIsWeapon) { + ActivateDirectional(); + SetAmbientColoursForPedsCarsAndObjects(); + return true; + } + return false; +} + +void +CObject::RemoveLighting(bool reset) +{ + if (reset) { + SetAmbientColours(); + DeActivateDirectional(); + } +} + +void +CObject::ObjectDamage(float amount) +{ + if (!m_nCollisionDamageEffect || !bUsesCollision) + return; + static int8 nFrameGen = 0; + bool bBodyCastDamageEffect = false; +#if 0 + 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; + } +#endif + 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; + return; + case DAMAGE_EFFECT_SPLIT_MODEL: + return; + case DAMAGE_EFFECT_SMASH_AND_DAMAGE_TRAFFICLIGHTS: + { + static RwRGBA debrisColor = { 0xc8,0xc8,0xc8,0xff }; + if (bRenderDamaged) { + break; + } + bRenderDamaged = true; + CVector min = 0.85f * GetColModel()->boundingBox.min; + CVector max = 0.85f * GetColModel()->boundingBox.max; + min.z = max.z; + min = GetMatrix() * min; + max = GetMatrix() * max; + CVector temp = (max - min) * 0.02f; + for (int32 i = 0; i < 50; i++) { + CVector vecDir = CVector( + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + CVector pos = min + temp * (float)i; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + float fColorFactor = CGeneral::GetRandomNumberInRange(0.6f, 1.2f); + RwRGBA color = debrisColor; + color.red *= fColorFactor; + color.green *= fColorFactor; + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-0.40f, 0.40f); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, pos, vecDir, nil, fSize, color, nRotationSpeed, 0, currentFrame, 0); + } + PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, min); + break; + } + case DAMAGE_EFFECT_CHANGE_THEN_SMASH: { + if (!bRenderDamaged) { + bRenderDamaged = true; + return; + } + // fall through + } + case DAMAGE_EFFECT_SMASH_COMPLETELY: { + bIsVisible = false; + bUsesCollision = false; + if (!GetIsStatic()) { + RemoveFromMovingList(); + } + 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: + case DAMAGE_EFFECT_SMASH_YELLOW_TARGET_COMPLETELY: + { + bIsVisible = false; + bUsesCollision = false; + if (!GetIsStatic()) { + RemoveFromMovingList(); + } + 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 = CVector( + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + fDirectionZ + ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + RwRGBA randomColor = color; + switch (m_nCollisionDamageEffect) { + case DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY: { + float fRandom = CGeneral::GetRandomNumberInRange(0.01f, 1.0f); + randomColor.red *= fRandom; + randomColor.green *= fRandom; + randomColor.blue *= fRandom; + break; + } + case DAMAGE_EFFECT_SMASH_YELLOW_TARGET_COMPLETELY: { + randomColor.red = 0xff; + randomColor.green = 0xfc; + break; + } + } + 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; + if (!GetIsStatic()) { + RemoveFromMovingList(); + } + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + static const RwRGBA color = { 128, 128, 128, 255 }; + CVector position = GetPosition(); + for (int32 i = 0; i < 45; i++) { + CVector vecDir = CVector( + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.10f, 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: + case DAMAGE_EFFECT_BURST_BEACHBALL: + { + bIsVisible = false; + bUsesCollision = false; + if (!GetIsStatic()) { + RemoveFromMovingList(); + } + 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 = CVector( + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.10f, 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); + } + if (m_nCollisionDamageEffect == DAMAGE_EFFECT_BURST_BEACHBALL) { + PlayOneShotScriptObject(SCRIPT_SOUND_HIT_BALL, vecPos); + } else { + PlayOneShotScriptObject(SCRIPT_SOUND_TIRE_COLLISION, vecPos); + } + break; + } + case DAMAGE_EFFECT_SMASH_BARPOST_COMPLETELY: + { + bIsVisible = false; + bUsesCollision = false; + if (!GetIsStatic()) { + RemoveFromMovingList(); + } + 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 }; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + for (int32 i = 0; i < 32; i++) { + CVector vecDir = CVector( + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + fDirectionZ + ); + ++nFrameGen; + int32 currentFrame = nFrameGen & 3; + const RwRGBA &color = nFrameGen & 1 ? color1 : color2; + 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; + } + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW1: + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW2: + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW3: + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW4: + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW5: + { + bIsVisible = false; + bUsesCollision = false; + if (!GetIsStatic()) { + RemoveFromMovingList(); + } + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + CRGBA possibleColor1; + CRGBA possibleColor2; + switch (m_nCollisionDamageEffect) { + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW1: + possibleColor1 = CRGBA(0xC0, 0x3E, 0xC, 0xFF); + possibleColor2 = possibleColor1; + break; + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW2: + possibleColor1 = CRGBA(0xA3, 0x36, 0x21, 0xFF); + possibleColor2 = possibleColor1; + break; + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW3: + possibleColor1 = CRGBA(0x12, 0x31, 0x24, 0xFF); + possibleColor2 = possibleColor1; + break; + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW4: + possibleColor1 = CRGBA(0xC0, 0xC8, 0xBE, 0xFF); + possibleColor2 = CRGBA(0x10, 0x57, 0x85, 0xFF); + break; + case DAMAGE_EFFECT_SMASH_NEWSTANDNEW5: + possibleColor1 = CRGBA(0xD0, 0x94, 0x1B, 0xFF); + possibleColor2 = possibleColor1; + break; + } + for (int32 i = 0; i < 16; i++) { + CVector vecDir( + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.10f, 0.15f) + fDirectionZ + ); + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + ++nFrameGen; + int32 nCurFrame = nFrameGen & 0x3; + CRGBA &selectedColor = nFrameGen & 0x1 ? possibleColor1 : possibleColor2; + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, selectedColor, nRotationSpeed, 0, nCurFrame, 0); + if (!(i % 7)) { + static CRGBA secondParticleColors[4] = { + CRGBA(0xA0, 0x60, 0x60, 0xFF), + CRGBA(0x60, 0xA0, 0x60, 0xFF), + CRGBA(0x60, 0x60, 0xA0, 0xFF), + CRGBA(0xA0, 0xA0, 0xA0, 0xFF) + }; + vecDir *= 0.5f; + CRGBA &secondParticleColor = secondParticleColors[nFrameGen & 3]; + int32 nSecondRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_DEBRIS, vecPos, vecDir, nil, 0.1f, secondParticleColor, nSecondRotationSpeed, 0, 1, 0); + } + } + PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, vecPos); + break; + } + case DAMAGE_EFFECT_SMASH_VEGPALM: + { + static RwRGBA primaryColor1 = { 0x39, 0x4D, 0x29, 0xff }; + static RwRGBA primaryColor2 = { 0x94, 0x7D, 0x73, 0xff }; + bIsVisible = false; + bUsesCollision = false; + if (!GetIsStatic()) { + RemoveFromMovingList(); + } + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + float fRadius = GetColModel()->boundingSphere.radius; + for (int32 i = 0; i < 32; i++) { + CVector particleDir = CVector( + CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), + CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), + CGeneral::GetRandomNumberInRange(-0.05f, 0.05f) + fDirectionZ + ); + CVector particlePos = vecPos; + particlePos.z += CGeneral::GetRandomNumberInRange(0.0f, 1.0f) * fRadius; + ++nFrameGen; + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + int32 nCurFrame = nFrameGen & 0x3; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + RwRGBA& particleColor = nFrameGen & 1 ? primaryColor1 : primaryColor2; + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, particlePos, particleDir, nil, fSize, particleColor, nRotationSpeed, 0, nCurFrame, 0); + if ((i % 7) == 0) { + static RwRGBA secondaryColor = { 0x9A, 0x99, 0x99, 0x3E }; + CParticle::AddParticle(PARTICLE_DEBRIS, particlePos, particleDir, nil, 0.3f, secondaryColor, nRotationSpeed); + } + } + PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_2, vecPos); + break; + } + case DAMAGE_EFFECT_SMASH_BLACKBAG: + case DAMAGE_EFFECT_SMASH_BEACHLOUNGE_WOOD: + case DAMAGE_EFFECT_SMASH_BEACHLOUNGE_TOWEL: + { + bIsVisible = false; + bUsesCollision = false; + if (!GetIsStatic()) { + RemoveFromMovingList(); + } + SetIsStatic(true); + bExplosionProof = true; + SetMoveSpeed(0.0f, 0.0f, 0.0f); + SetTurnSpeed(0.0f, 0.0f, 0.0f); + CRGBA possibleColor1; + CRGBA possibleColor2; + switch (m_nCollisionDamageEffect) { + case DAMAGE_EFFECT_SMASH_BLACKBAG: + possibleColor1 = CRGBA(0, 0, 0, 0xFF); + possibleColor2 = possibleColor1; + break; + case DAMAGE_EFFECT_SMASH_BEACHLOUNGE_WOOD: + possibleColor1 = CRGBA(0x8F, 0x8A, 0x8C, 0xFF); + possibleColor2 = CRGBA(0x73, 0x75, 0x7B, 0xFF); + break; + case DAMAGE_EFFECT_SMASH_BEACHLOUNGE_TOWEL: + possibleColor1 = CRGBA(0x52, 0x92, 0x4A, 0xFF); + possibleColor2 = CRGBA(0xCE, 0xCF, 0xCE, 0xFF); + break; + } + for (int32 i = 0; i < 16; i++) { + CVector vecDir( + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(-0.35f, 0.35f), + CGeneral::GetRandomNumberInRange(0.10f, 0.25f) + fDirectionZ + ); + ++nFrameGen; + int32 nCurFrame = nFrameGen & 0x3; + CRGBA &selectedColor = nFrameGen & 0x1 ? possibleColor1 : possibleColor2; + float fSize = CGeneral::GetRandomNumberInRange(0.02f, 0.20f); + int32 nRotationSpeed = CGeneral::GetRandomNumberInRange(-40, 40); + CParticle::AddParticle(PARTICLE_CAR_DEBRIS, vecPos, vecDir, nil, fSize, selectedColor, nRotationSpeed, 0, nCurFrame, 0); + } + if (m_nCollisionDamageEffect == DAMAGE_EFFECT_SMASH_BLACKBAG) { + PlayOneShotScriptObject(SCRIPT_SOUND_BOX_DESTROYED_2, vecPos); + } else if (m_nCollisionDamageEffect == DAMAGE_EFFECT_SMASH_BEACHLOUNGE_WOOD) { + PlayOneShotScriptObject(SCRIPT_SOUND_METAL_COLLISION, vecPos); + } + break; + } + default: + DEV("Unhandled collision damage effect id: %d\n", m_nCollisionDamageEffect); + return; + } + } +} + +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; + bIsWeapon = false; + m_nCostValue = 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_BUOY) + bTouchingWater = true; + + if (CModelInfo::GetModelInfo(GetModelIndex())->GetModelType() == MITYPE_WEAPON) + bIsWeapon = true; + bIsStreetLight = IsLightObject(GetModelIndex()); + + m_area = AREA_EVERYWHERE; +} + +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; + case CONTROLLED_SUB_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; + } + } +} + +bool +IsObjectPointerValid(CObject *pObject) +{ + if (!pObject) + return false; + int index = CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(pObject); +#ifdef FIX_BUGS + if (index < 0 || index >= CPools::GetObjectPool()->GetSize()) +#else + if (index < 0 || index > CPools::GetObjectPool()->GetSize()) +#endif + return false; + return pObject->bIsBIGBuilding || pObject->m_entryInfoList.first; +} diff --git a/src/miami/objects/Object.h b/src/miami/objects/Object.h new file mode 100644 index 00000000..f59379bf --- /dev/null +++ b/src/miami/objects/Object.h @@ -0,0 +1,116 @@ +#pragma once + +#include "Physical.h" + +enum { + UNKNOWN_OBJECT = 0, + GAME_OBJECT = 1, + MISSION_OBJECT = 2, + TEMP_OBJECT = 3, + CUTSCENE_OBJECT = 4, + CONTROLLED_SUB_OBJECT = 5, +}; + +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_AND_DAMAGE_TRAFFICLIGHTS, + + DAMAGE_EFFECT_SMASH_COMPLETELY = 20, + DAMAGE_EFFECT_CHANGE_THEN_SMASH, + + DAMAGE_EFFECT_SMASH_CARDBOARD_COMPLETELY = 50, + DAMAGE_EFFECT_SMASH_YELLOW_TARGET_COMPLETELY = 51, + + DAMAGE_EFFECT_SMASH_WOODENBOX_COMPLETELY = 60, + DAMAGE_EFFECT_SMASH_TRAFFICCONE_COMPLETELY = 70, + DAMAGE_EFFECT_SMASH_BARPOST_COMPLETELY = 80, + + DAMAGE_EFFECT_SMASH_NEWSTANDNEW1 = 91, + DAMAGE_EFFECT_SMASH_NEWSTANDNEW2 = 92, + DAMAGE_EFFECT_SMASH_NEWSTANDNEW3 = 93, + DAMAGE_EFFECT_SMASH_NEWSTANDNEW4 = 94, + DAMAGE_EFFECT_SMASH_NEWSTANDNEW5 = 95, + + DAMAGE_EFFECT_SMASH_BLACKBAG = 100, + DAMAGE_EFFECT_SMASH_VEGPALM = 110, + DAMAGE_EFFECT_BURST_BEACHBALL = 120, + DAMAGE_EFFECT_SMASH_BEACHLOUNGE_WOOD = 131, + DAMAGE_EFFECT_SMASH_BEACHLOUNGE_TOWEL = 132, +}; + +class CVehicle; +class CDummyObject; + +class CObject : public CPhysical +{ +public: + CMatrix m_objectMatrix; + float m_fUprootLimit; + int8 ObjectCreatedBy; + uint8 bIsPickup : 1; + uint8 bAmmoCollected : 1; + uint8 bPickupObjWithMessage : 1; + uint8 bOutOfStock : 1; + uint8 bGlassCracked : 1; + uint8 bGlassBroken : 1; + uint8 bHasBeenDamaged : 1; + uint8 bUseVehicleColours : 1; + uint8 bIsWeapon : 1; + uint8 bIsStreetLight : 1; + int8 m_nBonusValue; + uint16 m_nCostValue; + float m_fCollisionDamageMultiplier; + uint8 m_nCollisionDamageEffect; + uint8 m_nSpecialCollisionResponseCases; + bool m_bCameraToAvoidThisObject; + uint8 m_nBeachballBounces; + 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 float fDistToNearestTree; + + 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); +}; + +bool IsObjectPointerValid(CObject* pObject); diff --git a/src/miami/objects/ObjectData.cpp b/src/miami/objects/ObjectData.cpp new file mode 100644 index 00000000..8b23f0ae --- /dev/null +++ b/src/miami/objects/ObjectData.cpp @@ -0,0 +1,161 @@ +#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; + + ms_aObjectInfo[0].m_fMass = 99999.0f; + ms_aObjectInfo[0].m_fTurnMass = 99999.0f; + ms_aObjectInfo[0].m_fAirResistance = 0.99f; + ms_aObjectInfo[0].m_fElasticity = 0.1f; + ms_aObjectInfo[0].m_fBuoyancy = GRAVITY * ms_aObjectInfo[0].m_fMass * 2.0f; + ms_aObjectInfo[0].m_fUprootLimit = 0.0f; + ms_aObjectInfo[0].m_fCollisionDamageMultiplier = 1.0f; + ms_aObjectInfo[0].m_nCollisionDamageEffect = 0; + ms_aObjectInfo[0].m_nSpecialCollisionResponseCases = 0; + ms_aObjectInfo[0].m_bCameraToAvoidThisObject = false; + + ms_aObjectInfo[1].m_fMass = 99999.0f; + ms_aObjectInfo[1].m_fTurnMass = 99999.0f; + ms_aObjectInfo[1].m_fAirResistance = 0.99f; + ms_aObjectInfo[1].m_fElasticity = 0.1f; + ms_aObjectInfo[1].m_fBuoyancy = ms_aObjectInfo[0].m_fBuoyancy; + ms_aObjectInfo[1].m_fUprootLimit = 0.0f; + ms_aObjectInfo[1].m_fCollisionDamageMultiplier = 1.0f; + ms_aObjectInfo[1].m_nCollisionDamageEffect = 0; + ms_aObjectInfo[1].m_nSpecialCollisionResponseCases = 0; + ms_aObjectInfo[1].m_bCameraToAvoidThisObject = true; + + ms_aObjectInfo[2].m_fMass = 99999.0f; + ms_aObjectInfo[2].m_fTurnMass = 99999.0f; + ms_aObjectInfo[2].m_fAirResistance = 0.99f; + ms_aObjectInfo[2].m_fElasticity = 0.1f; + ms_aObjectInfo[2].m_fBuoyancy = ms_aObjectInfo[0].m_fBuoyancy; + ms_aObjectInfo[2].m_fUprootLimit = 0.0f; + ms_aObjectInfo[2].m_fCollisionDamageMultiplier = 1.0f; + ms_aObjectInfo[2].m_nCollisionDamageEffect = 0; + ms_aObjectInfo[2].m_bCameraToAvoidThisObject = false; + ms_aObjectInfo[2].m_nSpecialCollisionResponseCases = 4; + + ms_aObjectInfo[3].m_fMass = 99999.0f; + ms_aObjectInfo[3].m_fTurnMass = 99999.0f; + ms_aObjectInfo[3].m_fAirResistance = 0.99f; + ms_aObjectInfo[3].m_fElasticity = 0.1f; + ms_aObjectInfo[3].m_fBuoyancy = ms_aObjectInfo[0].m_fBuoyancy; + ms_aObjectInfo[3].m_fUprootLimit = 0.0f; + ms_aObjectInfo[3].m_fCollisionDamageMultiplier = 1.0f; + ms_aObjectInfo[3].m_nCollisionDamageEffect = 0; + ms_aObjectInfo[3].m_nSpecialCollisionResponseCases = 4; + ms_aObjectInfo[3].m_bCameraToAvoidThisObject = true; + + CFileMgr::SetDir(""); + CFileMgr::LoadFile(filename, work_buff, sizeof(work_buff), "r"); + + id = 4; + 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++; +#ifdef FIX_BUGS + *lp = '\0'; // FIX: game wrote '\n' here +#else + *lp = '\n'; +#endif + + 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) { + if (ms_aObjectInfo[0].m_fMass != ms_aObjectInfo[id].m_fMass + || ms_aObjectInfo[0].m_fCollisionDamageMultiplier != ms_aObjectInfo[id].m_fCollisionDamageMultiplier + || ms_aObjectInfo[0].m_nCollisionDamageEffect != ms_aObjectInfo[id].m_nCollisionDamageEffect + || ((ms_aObjectInfo[0].m_nSpecialCollisionResponseCases != ms_aObjectInfo[id].m_nSpecialCollisionResponseCases) + && (ms_aObjectInfo[2].m_nSpecialCollisionResponseCases != ms_aObjectInfo[id].m_nSpecialCollisionResponseCases))) { + mi->SetObjectID(id++); + } else if (ms_aObjectInfo[0].m_nSpecialCollisionResponseCases == ms_aObjectInfo[id].m_nSpecialCollisionResponseCases) { + if (ms_aObjectInfo[0].m_bCameraToAvoidThisObject == ms_aObjectInfo[id].m_bCameraToAvoidThisObject) + mi->SetObjectID(0); + else + mi->SetObjectID(1); + } else if (ms_aObjectInfo[2].m_bCameraToAvoidThisObject == ms_aObjectInfo[id].m_bCameraToAvoidThisObject) + mi->SetObjectID(2); + else + mi->SetObjectID(3); + } 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.m_phy_flagA08 = true; + object.bAffectedByGravity = false; + object.bExplosionProof = true; + } +} diff --git a/src/miami/objects/ObjectData.h b/src/miami/objects/ObjectData.h new file mode 100644 index 00000000..e25c1aeb --- /dev/null +++ b/src/miami/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/miami/objects/ParticleObject.cpp b/src/miami/objects/ParticleObject.cpp new file mode 100644 index 00000000..28c5240f --- /dev/null +++ b/src/miami/objects/ParticleObject.cpp @@ -0,0 +1,1345 @@ +#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 0x84 +#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 = nil; + } + } +} + +CParticleObject::CParticleObject() : + CPlaceable(), + m_nFrameCounter(0), + m_nState(POBJECTSTATE_INITIALISED), + m_pNext(nil), + m_pPrev(nil), + m_nRemoveTimer(0) + +{ + ; +} + +CParticleObject::~CParticleObject() +{ + +} + +void +CParticleObject::Initialise() +{ + pCloseListHead = nil; + pFarListHead = nil; + + pUnusedListHead = &gPObjectArray[0]; + + for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ ) + { + if ( i == 0 ) + gPObjectArray[i].m_pPrev = nil; + else + gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1]; + + if ( i == MAX_PARTICLEOBJECTS-1 ) + gPObjectArray[i].m_pNext = nil; + 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; + + if ( pobj == nil ) + { + printf("Error: No particle objects available!\n"); + return nil; + } + + 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 = nil; + + 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; + + switch ( type ) + { + case POBJECT_PAVEMENT_STEAM: + { + pobj->m_ParticleType = PARTICLE_STEAM_NY; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 3; + 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; + pobj->m_nSkipFrames = 3; + 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; + pobj->m_nSkipFrames = 3; + pobj->m_nCreationChance = 8; + pobj->m_Color = CRGBA(16, 16, 16, 255); + break; + } + + case POBJECT_WATER_FOUNTAIN_VERT: + { + pobj->m_ParticleType = PARTICLE_WATER_HYDRANT; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + pobj->m_vecTarget = CVector(0.0f, 0.0f, 0.1f); + break; + } + + case POBJECT_WATER_FOUNTAIN_HORIZ: + { + pobj->m_ParticleType = PARTICLE_WATER_HYDRANT; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; + break; + } + + case POBJECT_FIRE_HYDRANT: + { + pobj->m_ParticleType = PARTICLE_WATER_HYDRANT; + pobj->m_nNumEffectCycles = 1; + 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; + pobj->m_nSkipFrames = 1; + pobj->m_nCreationChance = 0; +#ifdef SCREEN_DROPLETS + ScreenDroplets::RegisterSplash(pobj); +#endif + break; + } + + case POBJECT_SPLASHES_AROUND: + { + pobj->m_ParticleType = PARTICLE_SPLASH; + pobj->m_nNumEffectCycles = 15; + pobj->m_nSkipFrames = 2; + pobj->m_nCreationChance = 0; + break; + } + + case POBJECT_SMALL_FIRE: + { + pobj->m_ParticleType = PARTICLE_FLAME; + pobj->m_nNumEffectCycles = 1; + pobj->m_nSkipFrames = 2; + 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; + pobj->m_nSkipFrames = 2; + 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; + pobj->m_nSkipFrames = 3; + 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 = 0; + 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; + } + } + + return pobj; +} + +CParticleObject * +CParticleObject::AddObject(tParticleType type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, uint8 numEffectCycles, uint8 skipFrames, uint16 creationChance, uint8 remove) +{ + CParticleObject *pobj = pUnusedListHead; + + ASSERT(pobj != nil); + + if ( pobj == nil ) + { + printf("Error: No particle objects available!\n"); + return nil; + } + + MoveToList(&pUnusedListHead, &pCloseListHead, pobj); + + pobj->m_nState = POBJECTSTATE_UPDATE_CLOSE; + pobj->m_Type = (eParticleObjectType)-1; + pobj->m_ParticleType = type; + + pobj->SetPosition(pos); + pobj->m_vecTarget = target; + + pobj->m_nNumEffectCycles = numEffectCycles; + pobj->m_nSkipFrames = skipFrames; + pobj->m_nCreationChance = creationChance; + pobj->m_nFrameCounter = 0; + + pobj->m_bRemove = remove; + + if ( lifeTime != 0 ) + pobj->m_nRemoveTimer = CTimer::GetTimeInMilliseconds() + lifeTime; + else + pobj->m_nRemoveTimer = 0; + + pobj->m_Color.alpha = 0; + + pobj->m_fSize = size; + pobj->m_fRandVal = 0.0f; + + 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 != nil ) + { + do + { + nextpobj = pobj->m_pNext; + pobj->UpdateClose(); + pobj = nextpobj; + } + while ( nextpobj != nil ); + } + } + + { + int32 frame = CTimer::GetFrameCounter() & 31; + int32 counter = 0; + + CParticleObject *pobj = pFarListHead; + CParticleObject *nextpobj; + if ( pobj != nil ) + { + do + { + nextpobj = pobj->m_pNext; + + if ( counter == frame ) + { + pobj->UpdateFar(); + frame += 32; + } + + counter++; + + pobj = nextpobj; + } + while ( nextpobj != nil ); + } + } +} + +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, nil, 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, nil, 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 == nil ) + { + 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, nil, 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, nil, size); + } + } + + break; + } + + case POBJECT_ROCKET_TRAIL: + { + if ( this->m_pParticle == nil ) + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + float size = this->m_fSize; + + this->m_pParticle = CParticle::AddParticle(PARTICLE_EXPLOSION_MEDIUM, pos, vel, nil, 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, nil, 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, nil, this->m_fSize, + CGeneral::GetRandomNumberInRange(-6.0f, 6.0f)); + } + + break; + } + + case POBJECT_PED_WATER_SPLASH: + { + 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, nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, + 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, nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, + 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, nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, + 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, nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.8f), colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, + 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, nil, + 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, nil, + 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, nil, + 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, nil, + CGeneral::GetRandomNumberInRange(0.4f, 1.0f), this->m_Color); + } + + break; + } + + case POBJECT_CAR_WATER_SPLASH: + { + 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, nil, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 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, nil, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 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, nil, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 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, nil, size, colorsmoke); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, 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, nil, 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, nil, 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, nil, 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, nil, 0.0f, this->m_Color); + } + + 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), + nil, 0.1f, this->m_Color); + } + else + { + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, splashpos, CVector(0.0f, 0.0f, 0.0f), + nil, 0.12f, this->m_Color); + } + } + + break; + } + + case POBJECT_FIRE_HYDRANT: + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + + if ( (TheCamera.GetPosition() - pos).Magnitude() > 5.0f ) + { + for ( int32 i = 0; i < 1; i++ ) + { + int32 angle = 180 * i; + + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos, splashvel; + + splashpos = pos + CVector(0.01f*fCos, 0.01f*fSin, 0.0f); + splashvel = vel + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.004f, 0.008f)); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, + CGeneral::GetRandomNumberInRange(0.005f, 0.0075f), this->m_Color, 0, 0, 1, 300); + + splashpos = pos + CVector(0.01f*fCos, 0.01f*-fSin, 0.0f); + splashvel = vel + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.004f, 0.008f)); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, + CGeneral::GetRandomNumberInRange(0.005f, 0.0075f), this->m_Color, 0, 0, 1, 300); + + splashpos = pos + CVector(0.01f*-fCos, 0.01f*fSin, 0.0f); + splashvel = vel + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.004f, 0.008f)); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, + CGeneral::GetRandomNumberInRange(0.005f, 0.0075f), this->m_Color, 0, 0, 1, 300); + + splashpos = pos + CVector(0.01f*-fCos, 0.01f*-fSin, 0.0f); + splashvel = vel + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.004f, 0.008f)); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashpos, splashvel, nil, + CGeneral::GetRandomNumberInRange(0.005f, 0.0075f), this->m_Color, 0, 0, 1, 300); + } + for ( int32 i = 0; i < this->m_nNumEffectCycles; i++ ) + { + CParticle::AddParticle(this->m_ParticleType, pos, vel, nil, 0.0f, this->m_Color); + } + } + + break; + } + + case POBJECT_WATER_FOUNTAIN_VERT: + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + + for ( int32 i = 0; i < 2; i++ ) + { + int32 angle = 180 * i; + + float fCos = CParticle::Cos(angle); + float fSin = CParticle::Sin(angle); + + CVector splashpos, splashvel; + + splashpos = pos + CVector(0.015f*fCos, 0.015f*fSin, 0.0f); + splashvel = vel + CVector(0.015f*fCos, 0.015f*fSin, CGeneral::GetRandomNumberInRange(0.004f, 0.008f)); + + CParticle::AddParticle(PARTICLE_SPLASH, splashpos, splashvel, nil, + CGeneral::GetRandomNumberInRange(0.001f, 0.005f), this->m_Color, 0, 0, 1, 1000); + + splashpos = pos + CVector(0.015f*fCos, 0.015f*-fSin, 0.0f); + splashvel = vel + CVector(0.015f*fCos, 0.015f*-fSin, CGeneral::GetRandomNumberInRange(0.004f, 0.008f)); + + CParticle::AddParticle(PARTICLE_SPLASH, splashpos, splashvel, nil, + CGeneral::GetRandomNumberInRange(0.001f, 0.005f), this->m_Color, 0, 0, 1, 1000); + + splashpos = pos + CVector(0.015f*-fCos, 0.015f*fSin, 0.0f); + splashvel = vel + CVector(0.015f*-fCos, 0.015f*fSin, CGeneral::GetRandomNumberInRange(0.004f, 0.008f)); + + CParticle::AddParticle(PARTICLE_SPLASH, splashpos, splashvel, nil, + CGeneral::GetRandomNumberInRange(0.001f, 0.005f), this->m_Color, 0, 0, 1, 1000); + + splashpos = pos + CVector(0.015f*-fCos, 0.015f*-fSin, 0.0f); + splashvel = vel + CVector(0.015f*-fCos, 0.015f*-fSin, CGeneral::GetRandomNumberInRange(0.004f, 0.008f)); + + CParticle::AddParticle(PARTICLE_SPLASH, splashpos, splashvel, nil, + CGeneral::GetRandomNumberInRange(0.001f, 0.005f), this->m_Color, 0, 0, 1, 1000); + } + + break; + } + + case POBJECT_WATER_FOUNTAIN_HORIZ: + { + CVector pos = this->GetPosition(); + CVector vel = this->m_vecTarget; + + for ( int32 i = 0; i < 3; i++ ) + { + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, vel, nil, 0.001f, this->m_Color, 0, 0, 1, 1000); + } + + 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, nil, + 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, nil, + 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 + { + 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 != nil ); + ASSERT( length != nil ); + + int32 numObjects = 0; + + for ( CParticleObject *p = pCloseListHead; p != nil; p = p->m_pNext ) + ++numObjects; + + for ( CParticleObject *p = pFarListHead; p != nil; 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 != nil; 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 != nil; 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 != nil ); + + 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 == nil ) + 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 = nil; + 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 + { + 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::RemoveAllExpireableParticleObjects(void) +{ + { + CParticleObject *pobj = pCloseListHead; + CParticleObject *nextpobj; + if ( pobj != nil ) + { + do + { + nextpobj = pobj->m_pNext; + if ( pobj->m_nRemoveTimer != 0 ) + { + MoveToList(&pCloseListHead, &pUnusedListHead, pobj); + pobj->m_nState = POBJECTSTATE_FREE; + } + pobj = nextpobj; + } + while ( nextpobj != nil ); + } + } + + { + CParticleObject *pobj = pFarListHead; + CParticleObject *nextpobj; + if ( pobj != nil ) + { + do + { + nextpobj = pobj->m_pNext; + if ( pobj->m_nRemoveTimer != 0 ) + { + MoveToList(&pFarListHead, &pUnusedListHead, pobj); + pobj->m_nState = POBJECTSTATE_FREE; + } + pobj = nextpobj; + } + while ( nextpobj != nil ); + } + } +} + +void +CParticleObject::RemoveAllParticleObjects(void) +{ + pUnusedListHead = &gPObjectArray[0]; + + pCloseListHead = nil; + pFarListHead = nil; + + for ( int32 i = 0; i < MAX_PARTICLEOBJECTS; i++ ) + { + if ( i == 0 ) + gPObjectArray[i].m_pPrev = nil; + else + gPObjectArray[i].m_pPrev = &gPObjectArray[i - 1]; + + if ( i == MAX_PARTICLEOBJECTS-1 ) + gPObjectArray[i].m_pNext = nil; + 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 != nil ); + ASSERT( to != nil ); + ASSERT( obj != nil ); + + if ( obj->m_pPrev == nil ) + { + *from = obj->m_pNext; + if ( *from ) + (*from)->m_pPrev = nil; + } + else + { + if ( obj->m_pNext == nil ) + obj->m_pPrev->m_pNext = nil; + else + { + obj->m_pNext->m_pPrev = obj->m_pPrev; + obj->m_pPrev->m_pNext = obj->m_pNext; + } + } + + obj->m_pNext = *to; + obj->m_pPrev = nil; + *to = obj; + + if ( obj->m_pNext ) + obj->m_pNext->m_pPrev = obj; +} diff --git a/src/miami/objects/ParticleObject.h b/src/miami/objects/ParticleObject.h new file mode 100644 index 00000000..f199e533 --- /dev/null +++ b/src/miami/objects/ParticleObject.h @@ -0,0 +1,112 @@ +#pragma once + +#include "AudioManager.h" +#include "ParticleType.h" +#include "Placeable.h" + +#define MAX_PARTICLEOBJECTS 70 +#define MAX_AUDIOHYDRANTS 8 + +enum eParticleObjectType +{ + POBJECT_PAVEMENT_STEAM = 0, + 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_WATER_FOUNTAIN_VERT, + POBJECT_WATER_FOUNTAIN_HORIZ, + 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); + static CParticleObject *AddObject(tParticleType type, CVector const &pos, CVector const &target, float size, uint32 lifeTime, uint8 numEffectCycles, uint8 skipFrames, uint16 creationChance, 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 RemoveAllExpireableParticleObjects(void); + 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(nil) + { } + + 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/miami/objects/Projectile.cpp b/src/miami/objects/Projectile.cpp new file mode 100644 index 00000000..fe8b0c68 --- /dev/null +++ b/src/miami/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/miami/objects/Projectile.h b/src/miami/objects/Projectile.h new file mode 100644 index 00000000..4b3eb4b8 --- /dev/null +++ b/src/miami/objects/Projectile.h @@ -0,0 +1,9 @@ +#pragma once + +#include "Object.h" + +class CProjectile : public CObject +{ +public: + CProjectile(int32); +}; diff --git a/src/miami/objects/Stinger.cpp b/src/miami/objects/Stinger.cpp new file mode 100644 index 00000000..d3eee416 --- /dev/null +++ b/src/miami/objects/Stinger.cpp @@ -0,0 +1,259 @@ +#include "common.h" +#include "Stinger.h" +#include "CopPed.h" +#include "ModelIndices.h" +#include "RpAnimBlend.h" +#include "World.h" +#include "Automobile.h" +#include "Bike.h" +#include "Particle.h" +#include "AnimBlendAssociation.h" +#include "General.h" + +uint32 NumOfStingerSegments; + +/* -- CStingerSegment -- */ + +CStingerSegment::CStingerSegment() +{ + 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(MI_PLC_STINGER); + ObjectCreatedBy = CONTROLLED_SUB_OBJECT; + NumOfStingerSegments++; +} + +CStingerSegment::~CStingerSegment() +{ + NumOfStingerSegments--; +} + +/* -- CStinger -- */ + +CStinger::CStinger() +{ + bIsDeployed = false; +} + +void +CStinger::Init(CPed *pPed) +{ + int32 i; + + pOwner = pPed; + for (i = 0; i < NUM_STINGER_SEGMENTS; i++) { + pSpikes[i] = new CStingerSegment(); +#ifdef FIX_BUGS + if (!pSpikes[i]) { + // Abort!! Pool is full + Remove(); + return; + } +#endif + pSpikes[i]->bUsesCollision = false; + } + bIsDeployed = true; + m_vPos = pPed->GetPosition(); + m_vPos.z -= 1.0f; + m_fMax_Z = Atan2(-pPed->GetForward().x, pPed->GetForward().y) + HALFPI; + + for (i = 0; i < NUM_STINGER_SEGMENTS; i++) { + pSpikes[i]->SetOrientation(0.0f, 0.0f, Atan2(-pPed->GetForward().x, pPed->GetForward().y)); + pSpikes[i]->SetPosition(m_vPos); + } + + CVector2D fwd2d(pPed->GetForward().x, pPed->GetForward().y); + + for (i = 0; i < ARRAY_SIZE(m_vPositions); i++) + m_vPositions[i] = fwd2d * 1.8f * Sin(DEGTORAD(i)); + + m_nSpikeState = STINGERSTATE_NONE; + m_nTimeOfDeploy = CTimer::GetTimeInMilliseconds(); +} + +void +CStinger::Remove() +{ + if (!bIsDeployed) return; + + for (int32 i = 0; i < NUM_STINGER_SEGMENTS; i++) { + CStingerSegment *spikeSegment = pSpikes[i]; + +#ifdef FIX_BUGS + if (spikeSegment) { + CWorld::Remove(spikeSegment); + delete spikeSegment; + pSpikes[i] = nil; + } +#else + if (spikeSegment->m_entryInfoList.first != nil) + spikeSegment->bRemoveFromWorld = true; + else + delete spikeSegment; +#endif + } + bIsDeployed = false; +} + +void +CStinger::Deploy(CPed *pPed) +{ + // So total number of stingers allowed at the same time is 2, each by different CCopPed. + if (NumOfStingerSegments < NUM_STINGER_SEGMENTS*2 && !pPed->bInVehicle && pPed->IsPedInControl()) { + if (!bIsDeployed && RpAnimBlendClumpGetAssociation(pPed->GetClump(), ANIM_STD_THROW_UNDER) == nil) { + Init(pPed); +#ifdef FIX_BUGS + // Above call won't set it to true no more when object pool is full + if (!bIsDeployed) + return; +#endif + pPed->SetPedState(PED_DEPLOY_STINGER); + CAnimManager::AddAnimation(pPed->GetClump(), ASSOCGRP_STD, ANIM_STD_THROW_UNDER); + } + } +} + +void +CStinger::CheckForBurstTyres() +{ + CVector firstPos = pSpikes[0]->GetPosition(); + firstPos.z += 0.2f; + CVector lastPos = pSpikes[NUM_STINGER_SEGMENTS - 1]->GetPosition(); + lastPos.z += 0.2f; + float dist = (lastPos - firstPos).Magnitude(); + if (dist < 0.1f) return; + + CVehicle *vehsInRange[16]; + int16 numObjects; + CEntity entity; + + CWorld::FindObjectsInRange((lastPos + firstPos) / 2.0f, + dist, true, &numObjects, 15, (CEntity**)vehsInRange, + false, true, false, false, false); + + for (int32 i = 0; i < numObjects; i++) { + CAutomobile *pAutomobile = nil; + CBike *pBike = nil; + + if (vehsInRange[i]->IsCar()) + pAutomobile = (CAutomobile*)vehsInRange[i]; + else if (vehsInRange[i]->IsBike()) + pBike = (CBike*)vehsInRange[i]; + + if (pAutomobile == nil && pBike == nil) continue; + + float maxWheelDistToSpike = sq(((CVehicleModelInfo*)CModelInfo::GetModelInfo(vehsInRange[i]->GetModelIndex()))->m_wheelScale + 0.1f); + + for (int wheelId = 0; wheelId < 4; wheelId++) { + if ((pAutomobile != nil && pAutomobile->m_aSuspensionSpringRatioPrev[wheelId] < 1.0f) || + (pBike != nil && pBike->m_aSuspensionSpringRatioPrev[wheelId] < 1.0f)) { + CVector vecWheelPos; + if (pAutomobile != nil) + vecWheelPos = pAutomobile->m_aWheelColPoints[wheelId].point; + else if (pBike != nil) + vecWheelPos = pBike->m_aWheelColPoints[wheelId].point; + + for (int32 spike = 0; spike < NUM_STINGER_SEGMENTS; spike++) { + if ((pSpikes[spike]->GetPosition() - vecWheelPos).Magnitude() < maxWheelDistToSpike) { + if (pBike) { + if (wheelId < 2) + vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_LF, true); + else + vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_LR, true); + } + else { + switch (wheelId) { + case 0: vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_LF, true); break; + case 1: vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_LR, true); break; + case 2: vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_RF, true); break; + case 3: vehsInRange[i]->BurstTyre(CAR_PIECE_WHEEL_RR, true); break; + } + } + vecWheelPos.z += 0.15f; + for (int j = 0; j < 4; j++) + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, vecWheelPos, vehsInRange[i]->GetRight() * 0.1f); + } + } + } + } + } +} + +// Only called when bIsDeployed +void +CStinger::Process() +{ + switch (m_nSpikeState) + { + case STINGERSTATE_NONE: + if (pOwner != nil + && !pOwner->bInVehicle + && pOwner->GetPedState() == PED_DEPLOY_STINGER + && RpAnimBlendClumpGetAssociation(pOwner->GetClump(), ANIM_STD_THROW_UNDER)->currentTime > 0.39f) + { + m_nSpikeState = STINGERSTATE_DEPLOYING; + for (int i = 0; i < NUM_STINGER_SEGMENTS; i++) + CWorld::Add(pSpikes[i]); + pOwner->SetIdle(); + } + break; + case STINGERSTATE_DEPLOYED: + if (pOwner != nil && pOwner->m_nPedType == PEDTYPE_COP) + ((CCopPed*)pOwner)->m_bThrowsSpikeTrap = false; + break; + case STINGERSTATE_UNDEPLOYING: + if (CTimer::GetTimeInMilliseconds() > m_nTimeOfDeploy + 2500) + m_nSpikeState = STINGERSTATE_REMOVE; + // no break + case STINGERSTATE_DEPLOYING: + if (m_nSpikeState == STINGERSTATE_DEPLOYING && CTimer::GetTimeInMilliseconds() > m_nTimeOfDeploy + 2500) + m_nSpikeState = STINGERSTATE_DEPLOYED; + else { + float progress = (CTimer::GetTimeInMilliseconds() - m_nTimeOfDeploy) / 2500.0f; + if (m_nSpikeState != STINGERSTATE_DEPLOYING) + progress = 1.0f - progress; + + float degangle = progress * ARRAY_SIZE(m_vPositions); + float angle1 = m_fMax_Z + DEGTORAD(degangle); + float angle2 = m_fMax_Z - DEGTORAD(degangle); + int pos = Clamp(degangle, 0, ARRAY_SIZE(m_vPositions)-1); + + CVector2D pos2d = m_vPositions[pos]; + CVector pos3d = m_vPos; + CColPoint colPoint; + CEntity *pEntity; + if (CWorld::ProcessVerticalLine(CVector(pos3d.x, pos3d.y, pos3d.z - 10.0f), pos3d.z, colPoint, pEntity, true, false, false, false, true, false, nil)) + pos3d.z = colPoint.point.z + 0.15f; + + angle1 = CGeneral::LimitRadianAngle(angle1); + angle2 = CGeneral::LimitRadianAngle(angle2); + + for (int spike = 0; spike < NUM_STINGER_SEGMENTS; spike++) { + if (CWorld::TestSphereAgainstWorld(pos3d + CVector(pos2d.x, pos2d.y, 0.6f), 0.3f, nil, true, false, false, true, false, false)) + pos2d = CVector2D(0.0f, 0.0f); + + if (spike % 2 == 0) { + pSpikes[spike]->SetOrientation(0.0f, 0.0f, angle1); + pos3d.x += pos2d.x; + pos3d.y += pos2d.y; + } else { + pSpikes[spike]->SetOrientation(0.0f, 0.0f, angle2); + } + pSpikes[spike]->SetPosition(pos3d); + } + } + break; + case STINGERSTATE_REMOVE: + Remove(); +#ifdef FIX_BUGS + return; +#else + break; +#endif + } + CheckForBurstTyres(); +} \ No newline at end of file diff --git a/src/miami/objects/Stinger.h b/src/miami/objects/Stinger.h new file mode 100644 index 00000000..250cf62d --- /dev/null +++ b/src/miami/objects/Stinger.h @@ -0,0 +1,40 @@ +#pragma once + +#include "Object.h" + +class CStingerSegment : public CObject +{ +public: + CStingerSegment(); + ~CStingerSegment(); +}; + +#define NUM_STINGER_SEGMENTS (12) + +enum { + STINGERSTATE_NONE = 0, + STINGERSTATE_DEPLOYING, + STINGERSTATE_DEPLOYED, + STINGERSTATE_UNDEPLOYING, + STINGERSTATE_REMOVE, +}; + +class CStinger +{ +public: + bool bIsDeployed; + uint32 m_nTimeOfDeploy; + CVector m_vPos; + float m_fMax_Z; + float m_fMin_Z; + CVector2D m_vPositions[60]; + CStingerSegment *pSpikes[NUM_STINGER_SEGMENTS]; + class CPed *pOwner; + uint8 m_nSpikeState; + CStinger(); + void Init(CPed *pPed); + void Remove(); + void Deploy(CPed *pPed); + void CheckForBurstTyres(); + void Process(); +}; \ No newline at end of file diff --git a/src/miami/peds/CivilianPed.cpp b/src/miami/peds/CivilianPed.cpp new file mode 100644 index 00000000..f3511366 --- /dev/null +++ b/src/miami/peds/CivilianPed.cpp @@ -0,0 +1,610 @@ +#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" +#include "Weather.h" +#include "PedAttractor.h" +#include "Object.h" +#include "CarCtrl.h" + +#ifndef _WIN32 +#include +#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; + } + m_bLookForVacantCars = false; + if (pedtype == PEDTYPE_CRIMINAL) + m_bLookForVacantCars = true; + + m_nLookForVacantCarsCounter = 0; + m_bJustStoleACar = false; + m_bStealCarEvenIfThereIsSomeoneInIt = false; + for (int i = 0; i < ARRAY_SIZE(m_nStealWishList); i++) { +#ifdef FIX_BUGS + uint32 randomCarModel = CGeneral::GetRandomNumberInRange(MI_LANDSTAL, MI_LAST_VEHICLE); +#else + uint32 randomCarModel = CGeneral::GetRandomNumberInRange(MI_LANDSTAL, MI_LANDSTAL + VEHICLEMODELSIZE); +#endif + if (CModelInfo::IsCarModel(randomCarModel) || CModelInfo::IsBikeModel(randomCarModel)) + m_nStealWishList[i] = randomCarModel; + else + m_nStealWishList[i] = MI_CHEETAH; + } + m_nAttractorCycleState = 0; + m_bAttractorUnk = (CGeneral::GetRandomNumberInRange(0.0f, 1.0f) < 1.25f); +} + +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; + + ScanForDelayedResponseThreats(); + if (!m_threatFlags || CTimer::GetTimeInMilliseconds() <= m_threatCheckTimer) + return; + CheckThreatValidity(); + uint32 closestThreatFlag = m_threatFlags; + m_threatFlags = 0; + m_threatCheckTimer = 0; + 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; + } + ScanForDelayedResponseThreats(); + if (!m_threatFlags || CTimer::GetTimeInMilliseconds() <= m_threatCheckTimer) + return; + CheckThreatValidity(); + uint32 closestThreatFlag = m_threatFlags; + m_threatFlags = 0; + m_threatCheckTimer = 0; + 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 (CharCreatedBy == MISSION_CHAR && bCrouchWhenScared) { + SetDuck(10000, true); + SetLookFlag(m_threatEntity, false); + SetLookTimer(500); + return; + } + 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(30.0f)) { + bMakeFleeScream = true; + 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); + bMakeFleeScream = true; + } else { + SetMoveState(PEDMOVE_WALK); + } + } else if (threatPed->IsPlayer() && IsGangMember() && bCanAttackPlayerWithCops) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + + } else if (threatPed->IsPlayer() && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) { + SetFindPathAndFlee(m_threatEntity, 5000); + if (threatDistSqr < sq(30.0f)) { + SetMoveState(PEDMOVE_RUN); + } else { + SetMoveState(PEDMOVE_WALK); + } + } else { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } + } else { + if (threatDistSqr < sq(30.0f)) { + bMakeFleeScream = true; + SetFindPathAndFlee(m_threatEntity, 10000); + SetMoveState(PEDMOVE_SPRINT); + } else { + bMakeFleeScream = false; + 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 (CharCreatedBy == MISSION_CHAR && bCrouchWhenScared && eventDistSqr < sq(5.0f)) { + SetDuck(10000, true); + + } else if (((CPed*)m_pEventEntity)->bIsDrowning || 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; + } + + if (investigateDeadPed) + SetInvestigateEvent(EVENT_DEAD_PED, CVector2D(m_pEventEntity->GetPosition()), 1.0f, 20000, 0.0f); + + } else { + SetFindPathAndFlee(m_pEventEntity, 5000); + SetMoveState(PEDMOVE_RUN); + } + } else if (closestThreatFlag == PED_FLAG_EXPLOSION) { + CVector2D eventDistVec = m_eventOrThreat - GetPosition(); + float eventDistSqr = eventDistVec.MagnitudeSqr(); + if (CharCreatedBy == MISSION_CHAR && bCrouchWhenScared && eventDistSqr < sq(20.0f)) { + SetDuck(10000, true); + + } else if (eventDistSqr < sq(20.0f)) { + bMakeFleeScream = true; + 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 { + 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()->IsTypeMelee() || !GetWeapon()->IsTypeMelee()) { + if (threatPed->IsPlayer() && IsGangMember() && bCanAttackPlayerWithCops) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + + } else if (threatPed->IsPlayer() && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) { + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS) { + SetFindPathAndFlee(m_threatEntity, 10000); + } + } else { + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, m_threatEntity); + } + } + } else { + SetFindPathAndFlee(m_threatEntity, 10000, true); + } + } + } +} + +void +CCivilianPed::ProcessControl(void) +{ + if (CharCreatedBy == UNK_CHAR) + return; + + CPed::ProcessControl(); + + if (bWasPostponed) + return; + + if (DyingOrDead()) + return; + + GetWeapon()->Update(m_audioEntityId, nil); + 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_objective == OBJECTIVE_SPRINT_TO_AREA || + IsUseAttractorObjective(m_objective)) && m_pNextPathNode) { + m_pNextPathNode = nil; + + } 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); + } + } else if (m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT) { + if (m_pedInObjective && 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 || m_objective == OBJECTIVE_SPRINT_TO_AREA || + IsUseAttractorObjective(m_objective)) { + 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); + m_pMyVehicle->pDriver->Say(SOUND_PED_PLAYER_BEFORESEX); + Say(SOUND_PED_PLAYER_BEFORESEX); + + 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(CWorld::Players[0].m_nMaxHealth + 25.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; + ClearLeader(); + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + m_pMyVehicle->pDriver->Say(SOUND_PED_PLAYER_AFTERSEX); + } + } else { + bWanderPathAfterExitingCar = true; + CWorld::Players[CWorld::PlayerInFocus].m_pHooker = nil; + m_pMyVehicle->pDriver->m_fHealth = CWorld::Players[0].m_nMaxHealth + 25.0f; + ClearLeader(); + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + m_pMyVehicle->pDriver->Say(SOUND_PED_PLAYER_AFTERSEX); + } + } 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; + ClearLeader(); + 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 (CharCreatedBy == RANDOM_CHAR) { + EnterVacantNearbyCars(); + UseNearbyAttractors(); + } + + if (CTimer::GetTimeInMilliseconds() > m_timerUnused) { + m_stateUnused = 0; + m_timerUnused = 0; + } + + if (m_moved.Magnitude() > 0.0f) + Avoid(); +} + +bool +CPed::RunToReportCrime(eCrimeType crimeToReport) +{ + // 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; + + CVector pos = GetPosition(); + int phoneId = gPhoneInfo.FindNearestFreePhone(&pos); + + if (phoneId == -1) + return false; + + CPhone* phone = &gPhoneInfo.m_aPhones[phoneId]; + if (phone->m_nState != PHONE_STATE_FREE) + return false; + + bRunningToPhone = true; + SetSeek(phone->m_vecPos, 0.3f); + SetMoveState(PEDMOVE_RUN); + m_phoneId = phoneId; + m_crimeToReportOnPhone = crimeToReport; + return true; +} + +const int32 gFrequencyOfAttractorAttempt = 11; +const float gDistanceToSeekAttractors = 50.0f; +const float gMaxDistanceToAttract = 10.0f; + +/* Probably this was inlined */ +void CCivilianPed::FindNearbyAttractorsSectorList(CPtrList& list, float& minDistance, C2dEffect*& pClosestAttractor, CEntity*& pAttractorEntity) +{ + for (CPtrNode* pNode = list.first; pNode != nil; pNode = pNode->next) { + CEntity* pEntity = (CEntity*)pNode->item; + if (pEntity->IsObject() && (!pEntity->GetIsStatic() || ((CObject*)pEntity)->bHasBeenDamaged)) + continue; + CBaseModelInfo* pModelInfo = CModelInfo::GetModelInfo(pEntity->GetModelIndex()); + for (int i = 0; i < pModelInfo->GetNum2dEffects(); i++) { + C2dEffect* pEffect = pModelInfo->Get2dEffect(i); + if (pEffect->type != EFFECT_PED_ATTRACTOR) + continue; + if (!IsAttractedTo(pEffect->pedattr.type)) + continue; + CVector pos; + CPedAttractorManager::ComputeEffectPos(pEffect, pEntity->GetMatrix(), pos); + if ((pos - GetPosition()).MagnitudeSqr() < minDistance) { + CPedAttractorManager* pManager = GetPedAttractorManager(); + if (pManager->HasEmptySlot(pEffect) && pManager->IsApproachable(pEffect, pEntity->GetMatrix(), 0, this)) { + pClosestAttractor = pEffect; + pAttractorEntity = pEntity; + minDistance = (pos - GetPosition()).MagnitudeSqr(); + } + } + } + } +} + +void CCivilianPed::UseNearbyAttractors() +{ + if (CWeather::Rain < 0.2f && !m_bAttractorUnk) + return; + if (HasAttractor()) + return; + if (m_nAttractorCycleState != gFrequencyOfAttractorAttempt) { + m_nAttractorCycleState++; + return; + } + m_nAttractorCycleState = 0; + if (!IsPedInControl()) + return; + if (m_nPedState == PED_FLEE_ENTITY) + return; + + float left = GetPosition().x - gDistanceToSeekAttractors; + float right = GetPosition().x + gDistanceToSeekAttractors; + float top = GetPosition().y - gDistanceToSeekAttractors; + float bottom = GetPosition().y + gDistanceToSeekAttractors; + 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 minDistance = SQR(gMaxDistanceToAttract); + C2dEffect* pClosestAttractor = nil; + CEntity* pAttractorEntity = nil; + + for (int y = ystart; y <= yend; y++) { + for (int x = xstart; x <= xend; x++) { + CSector* s = CWorld::GetSector(x, y); + FindNearbyAttractorsSectorList(s->m_lists[ENTITYLIST_BUILDINGS], minDistance, pClosestAttractor, pAttractorEntity); + FindNearbyAttractorsSectorList(s->m_lists[ENTITYLIST_OBJECTS], minDistance, pClosestAttractor, pAttractorEntity); + } + } + if (pClosestAttractor) + GetPedAttractorManager()->RegisterPedWithAttractor(this, pClosestAttractor, pAttractorEntity->GetMatrix()); +} + +bool CCivilianPed::IsAttractedTo(int8 type) +{ + switch (type) { + case ATTRACTOR_ATM: return true; + case ATTRACTOR_SEAT: return true; + case ATTRACTOR_STOP: return true; + case ATTRACTOR_PIZZA: return true; + case ATTRACTOR_SHELTER: return CWeather::Rain >= 0.2f; + case ATTRACTOR_ICECREAM: return false; + } + return false; +} + +void +CCivilianPed::EnterVacantNearbyCars(void) +{ + if (!m_bLookForVacantCars) + return; + + if (m_bJustStoleACar && bInVehicle && m_carInObjective == m_pMyVehicle) { + m_bJustStoleACar = false; + m_pMyVehicle->SetStatus(STATUS_PHYSICS); + m_pMyVehicle->AutoPilot.m_nCarMission = MISSION_CRUISE; + m_pMyVehicle->AutoPilot.m_nCruiseSpeed = 10; + m_pMyVehicle->bEngineOn = true; + + } else if (!bHasAlreadyStoleACar) { + if (m_nLookForVacantCarsCounter == 8) { + m_nLookForVacantCarsCounter = 0; + if (IsPedInControl() && m_objective == OBJECTIVE_NONE) { + + CVehicle *foundCar = nil; + float closestDist = FLT_MAX; + int minX = CWorld::GetSectorIndexX(GetPosition().x - 10.0f); + if (minX < 0) minX = 0; + int minY = CWorld::GetSectorIndexY(GetPosition().y - 10.0f); + if (minY < 0) minY = 0; + int maxX = CWorld::GetSectorIndexX(GetPosition().x + 10.0f); + if (maxX > NUMSECTORS_X - 1) maxX = NUMSECTORS_X - 1; + int maxY = CWorld::GetSectorIndexY(GetPosition().y + 10.0f); + if (maxY > NUMSECTORS_Y - 1) maxY = NUMSECTORS_Y - 1; + + for (int curY = minY; curY <= maxY; curY++) { + for (int curX = minX; curX <= maxX; curX++) { + CSector* sector = CWorld::GetSector(curX, curY); + for (CPtrNode* node = sector->m_lists[ENTITYLIST_VEHICLES].first; node; node = node->next) { + CVehicle* veh = (CVehicle*)node->item; + if (veh && veh->IsCar()) { + + // Looks like PARKED_VEHICLE condition isn't there in Mobile. + if (veh->VehicleCreatedBy == RANDOM_VEHICLE || veh->VehicleCreatedBy == PARKED_VEHICLE) { + if (IsOnStealWishList(veh->GetModelIndex()) && !veh->IsLawEnforcementVehicle() + && (m_bStealCarEvenIfThereIsSomeoneInIt || !veh->pDriver && !veh->m_nNumPassengers) + && !veh->m_nNumGettingIn && !veh->m_nGettingInFlags && !veh->m_nGettingOutFlags + && !veh->m_pCarFire && veh->m_fHealth > 800.0f + && !veh->IsUpsideDown() && !veh->IsOnItsSide() && veh->CanPedEnterCar()) { + float dist = (GetPosition() - veh->GetPosition()).MagnitudeSqr(); + if (dist < sq(10.0f) && dist < closestDist && veh->IsClearToDriveAway()) { + foundCar = veh; + closestDist = dist; + } + } + } + } + } + } + } + if (foundCar) { + m_bJustStoleACar = true; + bHasAlreadyStoleACar = true; + CCarCtrl::JoinCarWithRoadSystem(foundCar); + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, foundCar); + SetObjectiveTimer(10000); + } + } + } else { + ++m_nLookForVacantCarsCounter; + } + } +} + +bool +CCivilianPed::IsOnStealWishList(int32 model) +{ + for (int i = 0; i < ARRAY_SIZE(m_nStealWishList); i++) { + if (model == m_nStealWishList[i]) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/src/miami/peds/CivilianPed.h b/src/miami/peds/CivilianPed.h new file mode 100644 index 00000000..dcd49a96 --- /dev/null +++ b/src/miami/peds/CivilianPed.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Ped.h" + +class CCivilianPed : public CPed +{ + bool m_bLookForVacantCars; + uint32 m_nLookForVacantCarsCounter; + bool m_bStealCarEvenIfThereIsSomeoneInIt; // unused + bool m_bJustStoleACar; + uint32 m_nStealWishList[16]; + bool m_bAttractorUnk; + int32 m_nAttractorCycleState; +public: + CCivilianPed(ePedType, uint32); + ~CCivilianPed(void) { } + + void CivilianAI(void); + void ProcessControl(void); + void UseNearbyAttractors(void); + void FindNearbyAttractorsSectorList(CPtrList&, float&, C2dEffect*&, CEntity*&); + bool IsAttractedTo(int8); + void EnterVacantNearbyCars(void); + bool IsOnStealWishList(int32); +}; +//VALIDATE_SIZE(CCivilianPed, 0x53C); diff --git a/src/miami/peds/CopPed.cpp b/src/miami/peds/CopPed.cpp new file mode 100644 index 00000000..1efd7733 --- /dev/null +++ b/src/miami/peds/CopPed.cpp @@ -0,0 +1,874 @@ +#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" +#include "PedPlacement.h" +#include "Ropes.h" +#include "Stinger.h" + +CCopPed::CCopPed(eCopType copType, int32 modifier) : CPed(PEDTYPE_COP) +{ + m_nCopType = copType; + switch (copType) { + case COP_STREET: + SetModelIndex(MI_COP); + GiveWeapon(WEAPONTYPE_NIGHTSTICK, 1000, true); + GiveDelayedWeapon(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); + GiveDelayedWeapon(WEAPONTYPE_MP5, 1000); + SetCurrentWeapon(WEAPONTYPE_MP5); + m_fArmour = 100.0f; + m_wepSkills = 176; /* TODO: what is this? seems unused */ + m_wepAccuracy = 76; + break; + case COP_SWAT: + case COP_HELI_SWAT: + SetModelIndex(MI_SWAT); + GiveDelayedWeapon(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); + GiveDelayedWeapon(WEAPONTYPE_MP5, 1000); + SetCurrentWeapon(WEAPONTYPE_MP5); + m_fArmour = 100.0f; + m_wepSkills = 32; /* TODO: what is this? seems unused */ + m_wepAccuracy = 84; + break; + case COP_MIAMIVICE: + switch (modifier) { + case 0: SetModelIndex(MI_VICE1); break; + case 1: SetModelIndex(MI_VICE2); break; + case 2: SetModelIndex(MI_VICE3); break; + case 3: SetModelIndex(MI_VICE4); break; + case 4: SetModelIndex(MI_VICE5); break; + case 5: SetModelIndex(MI_VICE6); break; + case 6: SetModelIndex(MI_VICE7); break; + case 7: SetModelIndex(MI_VICE8); break; + default: assert(0); break; + } + GiveDelayedWeapon(WEAPONTYPE_UZI, 1000); + SetCurrentWeapon(WEAPONTYPE_UZI); + m_fArmour = 100.0f; + m_wepSkills = 176; + m_wepAccuracy = 76; + break; + } + m_bIsInPursuit = false; + field_5FE = 1; + m_bIsDisabledCop = false; + m_attackTimer = 0; + m_bBeatingSuspect = false; + m_bStopAndShootDisabledZone = false; + m_bDragsPlayerFromCar = false; + m_bZoneDisabled = false; + field_628 = -1; + m_nRoadblockVeh = nil; + m_bThrowsSpikeTrap = false; + m_pRopeEntity = nil; + m_fAbseilPos = 0.0f; + m_nHassleTimer = 0; + field_61C = 0; + field_624 = 0; + m_pStinger = new CStinger(); + SetWeaponLockOnTarget(nil); +} + +CCopPed::~CCopPed() +{ + ClearPursuit(); + m_pStinger->Remove(); + delete m_pStinger; +} + +// 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; + + player->Say(SOUND_PED_PLAYER_REACTTOCOP); + Say(SOUND_PED_ARREST_COP); + + 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 || GetWeapon()->m_eWeaponType == WEAPONTYPE_BRASSKNUCKLE) + 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_bDragsPlayerFromCar = 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) +{ + if (CTimer::GetTimeInMilliseconds() < field_61C) + return; + + 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; + 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) { + // Turn and shoot the player's vehicle, if possible + if (!m_bIsInPursuit && !GetWeapon()->IsTypeMelee() && FindPlayerVehicle() && m_fDistanceToTarget < CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_fRange) { + if (FindPlayerVehicle()->m_vecMoveSpeed.Magnitude2D() > 0.1f) { + CVector2D distToVeh = GetPosition() - FindPlayerVehicle()->GetPosition(); + distToVeh.Normalise(); + CVector2D vehSpeed = FindPlayerVehicle()->m_vecMoveSpeed; + vehSpeed.Normalise(); + + if (DotProduct2D(distToVeh, vehSpeed) > 0.8f) { + SetLookFlag(playerOrHisVeh, true); + SetMoveState(PEDMOVE_STILL); + if (TurnBody()) { + SetAttack(FindPlayerVehicle()); + SetShootTimer(CGeneral::GetRandomNumberInRange(500.0f, 1000.0f)); + SetAttackTimer(CGeneral::GetRandomNumberInRange(200.0f, 300.0f)); + } + } else if (m_nPedState == PED_ATTACK) + RestorePreviousState(); + } + } + + 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(GetWeaponSlot(WEAPONTYPE_NIGHTSTICK) >= 0 ? WEAPONTYPE_NIGHTSTICK : WEAPONTYPE_UNARMED); + } + + if (m_bBeatingSuspect && GetWeapon()->m_eWeaponType == WEAPONTYPE_NIGHTSTICK) + Say(SOUND_PED_PULLOUTWEAPON); + + if (FindPlayerVehicle()) { + if (m_bBeatingSuspect) { + --wanted->m_CopsBeatingSuspect; + m_bBeatingSuspect = false; + } + if (m_fDistanceToTarget * FindPlayerSpeed().Magnitude() > 4.0f) + ClearPursuit(); + } + return; + } + SetCurrentWeapon(WEAPONTYPE_COLT45); + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + float weaponRange = weaponInfo->m_fRange; + SetLookFlag(playerOrHisVeh, true); + TurnBody(); + if (!bIsDucking || bCrouchWhenShooting && GetCrouchFireAnim(weaponInfo)) { + 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) { + if (bIsDucking) + ClearDuck(); + + // 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 { + float dotProd; + if (m_nRoadblockVeh) { + dotProd = DotProduct2D(playerOrHisVeh->GetPosition() - m_nRoadblockVeh->GetPosition(), GetPosition() - m_nRoadblockVeh->GetPosition()); + } else + dotProd = -1.0f; + + if(dotProd < 0.0f) { + if (bIsDucking) + ClearDuck(); + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + bDuckAndCover = false; + SetPursuit(false); + } else { + bIsPointingGunAt = true; + } + } + } + } else { + if (m_fDistanceToTarget < weaponRange) { + 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); + } + } else { + if (m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && m_objective != OBJECTIVE_HASSLE_CHAR && 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; + } + } else { + if (nearPed->m_nPedType != PEDTYPE_COP && !nearPed->IsPlayer() + && nearPed->IsPedInControl() && m_nHassleTimer < CTimer::GetTimeInMilliseconds()) { + + if (nearPed->m_objective == OBJECTIVE_NONE && nearPed->m_nPedState == PED_WANDER_PATH + && !nearPed->m_pLookTarget && nearPed->m_lookTimer < CTimer::GetTimeInMilliseconds()) { + + if ((GetPosition() - nearPed->GetPosition()).MagnitudeSqr() < sq(5.0f)) { + + if (CWorld::GetIsLineOfSightClear(GetPosition(), nearPed->GetPosition(), + true, false, false, false, false, false, false)) { + Say(SOUND_PED_COP_ASK_FOR_ID); + SetObjective(OBJECTIVE_HASSLE_CHAR, nearPed); + nearPed->SetObjective(OBJECTIVE_WAIT_ON_FOOT_FOR_COP, this); + m_nHassleTimer = CTimer::GetTimeInMilliseconds() + 100000; + } + } + } + } + } + } + } + } + } + } + } + } else { + if (m_bIsInPursuit && m_nPedState != PED_AIM_GUN) + ClearPursuit(); + + m_bIsDisabledCop = false; + bKindaStayInSamePlace = false; + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + bDuckAndCover = false; + if (bIsDucking) + ClearDuck(); + if (m_pMyVehicle) + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pMyVehicle); + } + } +} + +void +CCopPed::ProcessControl(void) +{ + if (m_nCopType == COP_HELI_SWAT) + ProcessHeliSwat(); + + CPed::ProcessControl(); + + if (m_bThrowsSpikeTrap) { + if (CGame::currArea != AREA_MALL) + ProcessStingerCop(); + return; + } + + if (m_pStinger && m_pStinger->bIsDeployed && m_pStinger->m_nSpikeState == STINGERSTATE_DEPLOYED && CGame::currArea != AREA_MALL) + m_pStinger->Process(); + + 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, nil); + 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()) { + if (player->m_pWanted->m_CurrentCops == 1) { + Say(SOUND_PED_COP_ALONE); + } else { + int numCopsNear = 0; + for (int i = 0; i < player->m_numNearPeds; ++i) { + CPed *nearPed = player->m_nearPeds[i]; + if (nearPed->m_nPedType == PEDTYPE_COP && nearPed->m_nPedState != PED_DEAD) + ++numCopsNear; + } + if (numCopsNear <= 3) { + Say(SOUND_PED_COP_LITTLECOPSAROUND); + if (!player->bInVehicle) { + CVector distToPlayer = player->GetPosition() - GetPosition(); + if (distToPlayer.MagnitudeSqr() < sq(20.0f)) { + player->Say(SOUND_PED_PLAYER_FARFROMCOPS); + if (player->m_nPedState != PED_ATTACK && player->m_nPedState != PED_AIM_GUN) { + player->SetLookFlag(this, false); + player->SetLookTimer(1000); + } + } + } + } else if ((CGeneral::GetRandomNumber() % 16) == 1) { + Say(SOUND_PED_COP_MANYCOPSAROUND); + } + } + } + } + + 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) { + m_distanceToCountSeekDone = 1.3f; + } + + if (!bDuckAndCover && 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_pPointGunAt) + Say(SOUND_PED_COP_TARGETING); + + if (m_bStopAndShootDisabledZone) { + bool dontShoot = false; + if (GetIsOnScreen()) { + 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); + } + } + + if (field_624 >= 2 && m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT) { + CVector centre = GetPosition() + CVector(0.f, 0.f, 0.65f); + if (CWorld::TestSphereAgainstWorld(centre, 0.35f, this, true, false, false, false, false, false)) { + field_624 = 0; + m_bStopAndShootDisabledZone = true; + ClearPursuit(); + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumberInRange(0,8)); + field_61C = CTimer::GetTimeInMilliseconds() + 30000; + } else { + field_624 = 0; + if (GetWeapon()->IsTypeMelee()) { + // TODO(Miami): enum + for (int i = 3; i < 7; i++) { + if (HasWeaponSlot(i)) { + SetCurrentWeapon(i); + break; + } + } + SetMoveState(PEDMOVE_STILL); + bStopAndShoot = true; + } + } + } else if (CTimer::GetTimeStep() / 100.f <= m_fDistanceTravelled) + field_624 = 0; +} + +void +CCopPed::ProcessHeliSwat(void) +{ + CVector bestPos = GetPosition(); + SetPedState(PED_ABSEIL); + CPedPlacement::FindZCoorForPed(&bestPos); + if (GetPosition().z - 2.0f >= bestPos.z && m_pRopeEntity) { + m_fAbseilPos += 0.003f * CTimer::GetTimeStep(); + m_vecMoveSpeed.z = -0.03f; + m_vecTurnSpeed = CVector(0.f, 0.f, (m_randomSeed % 32) * 0.003f - 0.05f); + CPhysical::ApplyTurnSpeed(); + GetMatrix().Reorthogonalise(); + CVector posOnRope; + + if (CRopes::FindCoorsAlongRope(m_nRopeID, m_fAbseilPos, &posOnRope)) { + SetPosition(posOnRope); + } else { + bUsesCollision = true; + m_vecMoveSpeed = CVector(0.f, 0.f, 0.f); + SetPedState(PED_IDLE); + m_nCopType = COP_SWAT; + SetInTheAir(); + bKnockedUpIntoAir = true; + } + Say(SOUND_PED_COP_HELIPILOTPHRASE); + } else { + bUsesCollision = true; + m_vecMoveSpeed = CVector(0.f, 0.f, 0.f); + SetPedState(PED_IDLE); + m_nCopType = COP_SWAT; + SetInTheAir(); + bKnockedUpIntoAir = true; + } +} + +void +CCopPed::ProcessStingerCop(void) +{ + if (m_pStinger->bIsDeployed || FindPlayerVehicle() && (FindPlayerVehicle()->IsCar() || FindPlayerVehicle()->IsBike())) { + if (m_pStinger->bIsDeployed) { + m_pStinger->Process(); + } else { + CVector2D vehDist = GetPosition() - FindPlayerVehicle()->GetPosition(); + CVector2D dirVehGoing = FindPlayerVehicle()->m_vecMoveSpeed; + if (vehDist.MagnitudeSqr() < sq(30.0f)) { + if (dirVehGoing.MagnitudeSqr() > 0.0f) { + vehDist.Normalise(); + dirVehGoing.Normalise(); + if (DotProduct2D(vehDist, dirVehGoing) > 0.8f) { + float angle = (CrossProduct2D(vehDist, dirVehGoing - vehDist) < 0.0f ? + FindPlayerVehicle()->GetForward().Heading() - HALFPI : + HALFPI + FindPlayerVehicle()->GetForward().Heading()); + + SetHeading(angle); + m_fRotationCur = angle; + m_fRotationDest = angle; + m_pStinger->Deploy(this); + } + } + } + } + } else { + ClearPursuit(); + } +} diff --git a/src/miami/peds/CopPed.h b/src/miami/peds/CopPed.h new file mode 100644 index 00000000..3f5ae06d --- /dev/null +++ b/src/miami/peds/CopPed.h @@ -0,0 +1,51 @@ +#pragma once +#include "Ped.h" + +enum eCopType +{ + COP_STREET = 0, + COP_FBI = 1, + COP_SWAT = 2, + COP_HELI_SWAT = 3, + COP_ARMY = 4, + COP_MIAMIVICE = 5 +}; + +class CCopPed : public CPed +{ +public: + CVehicle* m_nRoadblockVeh; + float m_fDistanceToTarget; + bool m_bIsInPursuit; + bool m_bIsDisabledCop; + int8 field_5FE; + bool m_bBeatingSuspect; + bool m_bStopAndShootDisabledZone; + bool m_bDragsPlayerFromCar; + bool m_bZoneDisabled; + float m_fAbseilPos; + eCopType m_nCopType; + bool m_bThrowsSpikeTrap; + CEntity *m_pRopeEntity; // CHeli or 1 + uintptr m_nRopeID; + uint32 m_nHassleTimer; + uint32 field_61C; + class CStinger *m_pStinger; + int32 field_624; + int8 field_628; + + CCopPed(eCopType, int32 modifier = 0); + ~CCopPed(); + + void ClearPursuit(void); + void ProcessControl(void); + void SetArrestPlayer(CPed*); + void SetPursuit(bool); + void ArrestPlayer(void); + void ScanForCrimes(void); + void CopAI(void); + void ProcessHeliSwat(void); + void ProcessStingerCop(void); +}; + +VALIDATE_SIZE(CCopPed, 0x62C); diff --git a/src/miami/peds/DummyPed.h b/src/miami/peds/DummyPed.h new file mode 100644 index 00000000..cace4ead --- /dev/null +++ b/src/miami/peds/DummyPed.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Dummy.h" + +// actually unused +class CDummyPed : CDummy +{ + int32 pedType; + int32 unknown; +}; diff --git a/src/miami/peds/EmergencyPed.cpp b/src/miami/peds/EmergencyPed.cpp new file mode 100644 index 00000000..0257d291 --- /dev/null +++ b/src/miami/peds/EmergencyPed.cpp @@ -0,0 +1,412 @@ +#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) +{ + CPed::ProcessControl(); + if (bWasPostponed) + return; + + if(!DyingOrDead()) { + GetWeapon()->Update(m_audioEntityId, nil); + + 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; +#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; + 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; +#endif + 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()) { + ScanForDelayedResponseThreats(); + if (m_threatFlags && CTimer::GetTimeInMilliseconds() > m_threatCheckTimer) { + CheckThreatValidity(); + m_threatFlags = 0; + m_threatCheckTimer = 0; + 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); + SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, CVector((headPos + midPos) * 0.5f)); + 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); + SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, CVector((headPos + midPos) * 0.5f)); + 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_MEDIC, 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; + m_pRevivedPed->bKnockedOffBike = false; + m_pRevivedPed->Say(SOUND_PED_ACCIDENTREACTION1); + } + 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/miami/peds/EmergencyPed.h b/src/miami/peds/EmergencyPed.h new file mode 100644 index 00000000..41bc86e5 --- /dev/null +++ b/src/miami/peds/EmergencyPed.h @@ -0,0 +1,39 @@ +#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); +}; +//VALIDATE_SIZE(CEmergencyPed, 0x554); diff --git a/src/miami/peds/Gangs.cpp b/src/miami/peds/Gangs.cpp new file mode 100644 index 00000000..240f6b37 --- /dev/null +++ b/src/miami/peds/Gangs.cpp @@ -0,0 +1,125 @@ +#include "common.h" + +#include "ModelIndices.h" +#include "Gangs.h" +#include "General.h" +#include "Streaming.h" +#include "Weapon.h" +#include "SaveBuf.h" + +CGangInfo CGangs::Gang[NUM_GANGS]; +bool CGangs::GangAttackWithCops[NUM_GANGS]; + +CGangInfo::CGangInfo() : + m_nVehicleMI(-1), + m_nPedModel1MI(-1), + m_nPedModel2MI(-1), + m_nPedModelOverride(-1), + m_Weapon1(WEAPONTYPE_UNARMED), + m_Weapon2(WEAPONTYPE_UNARMED) +{} + +void CGangs::Initialise(void) +{ + SetGangPedModels(GANG_CUBAN, MI_CBA, MI_CBB); + SetGangPedModels(GANG_HAITIAN, MI_HNA, MI_HNB); + SetGangPedModels(GANG_STREET, MI_SGA, MI_SGB); + SetGangPedModels(GANG_DIAZ, MI_CLA, MI_CLB); + SetGangPedModels(GANG_SECURITY, MI_GDA, MI_GDB); + SetGangPedModels(GANG_BIKER, MI_BKA, MI_BKB); + SetGangPedModels(GANG_PLAYER, MI_PGA, MI_PGB); + SetGangPedModels(GANG_GOLFER, MI_WFOGO, MI_WMOGO); + SetGangVehicleModel(GANG_CUBAN, MI_CUBAN); + SetGangVehicleModel(GANG_HAITIAN, MI_VOODOO); + SetGangVehicleModel(GANG_STREET, MI_GANGBUR); + SetGangVehicleModel(GANG_DIAZ, -1); + SetGangVehicleModel(GANG_SECURITY, -1); + SetGangVehicleModel(GANG_BIKER, MI_ANGEL); + SetGangVehicleModel(GANG_PLAYER, -1); + SetGangVehicleModel(GANG_GOLFER, MI_CADDY); + SetGangWeapons(GANG_GOLFER, WEAPONTYPE_GOLFCLUB, WEAPONTYPE_GOLFCLUB); +#ifdef FIX_BUGS + for (int i = 0; i < NUM_GANGS; i++) + SetGangPedModelOverride(i, -1); +#endif +} + +bool CGangs::HaveGangModelsLoaded(int16 gang) +{ + CGangInfo* pGangInfo = GetGangInfo(gang); + return CStreaming::HasModelLoaded(pGangInfo->m_nPedModel1MI) && CStreaming::HasModelLoaded(pGangInfo->m_nPedModel2MI); +} + +void CGangs::SetGangPedModels(int16 gang, int32 mi1, int32 mi2) +{ + GetGangInfo(gang)->m_nPedModel1MI = mi1; + GetGangInfo(gang)->m_nPedModel2MI = mi2; +} + +void CGangs::SetWillAttackPlayerWithCops(ePedType type, bool will) +{ + if (type >= PEDTYPE_GANG1 && type <= PEDTYPE_GANG9) + GangAttackWithCops[type - PEDTYPE_GANG1] = will; +} + +bool CGangs::GetWillAttackPlayerWithCops(ePedType type) +{ + if (type >= PEDTYPE_GANG1 && type <= PEDTYPE_GANG9) + return GangAttackWithCops[type - PEDTYPE_GANG1]; + return false; +} + +int32 CGangs::ChooseGangPedModel(int16 gang) +{ + CGangInfo* pGangInfo = GetGangInfo(gang); + if (pGangInfo->m_nPedModelOverride != -1 || CGeneral::GetRandomTrueFalse()) + return pGangInfo->m_nPedModel1MI; + else + return pGangInfo->m_nPedModel2MI; +} + +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/miami/peds/Gangs.h b/src/miami/peds/Gangs.h new file mode 100644 index 00000000..c6381343 --- /dev/null +++ b/src/miami/peds/Gangs.h @@ -0,0 +1,57 @@ +#pragma once + +#include "PedType.h" + +struct CGangInfo +{ + int32 m_nVehicleMI; + int32 m_nPedModel1MI; + int32 m_nPedModel2MI; + int8 m_nPedModelOverride; + int32 m_Weapon1; + int32 m_Weapon2; + + CGangInfo(); +}; + +VALIDATE_SIZE(CGangInfo, 0x10); + +enum { + GANG_CUBAN = 0, + GANG_HAITIAN, + GANG_STREET, + GANG_DIAZ, + GANG_SECURITY, + GANG_BIKER, + GANG_PLAYER, + GANG_GOLFER, + GANG_9, + 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 void SetGangPedModels(int16, int32, int32); + static void SetWillAttackPlayerWithCops(ePedType type, bool will); + static bool GetWillAttackPlayerWithCops(ePedType type); + static int32 ChooseGangPedModel(int16); + + static bool HaveGangModelsLoaded(int16 gang); + static int32 GetGangPedModel1(int16 gang) { return Gang[gang].m_nPedModel1MI; } + static int32 GetGangPedModel2(int16 gang) { return Gang[gang].m_nPedModel2MI; } + static int32 GetGangVehicleModel(int16 gang) { return Gang[gang].m_nVehicleMI; } + static CGangInfo *GetGangInfo(int16 gang) { return &Gang[gang]; } + +private: + static CGangInfo Gang[NUM_GANGS]; + static bool GangAttackWithCops[NUM_GANGS]; +}; diff --git a/src/miami/peds/Ped.cpp b/src/miami/peds/Ped.cpp new file mode 100644 index 00000000..aace82e8 --- /dev/null +++ b/src/miami/peds/Ped.cpp @@ -0,0 +1,9662 @@ +#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 "Streaming.h" +#include "PedAttractor.h" +#include "GameLogic.h" +#include "Bike.h" +#include "WindModifiers.h" +#include "CutsceneShadow.h" +#include "Clock.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::bFannyMagnetCheat; +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); } + +float gfTommyFatness = 1.0f; + +CPed::CPed(uint32 pedType) : m_pedIK(this) +{ +#ifdef USE_CUTSCENE_SHADOW_FOR_PED + m_pRTShadow = nil; +#endif + 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_canTalk = true; + + m_type = ENTITY_TYPE_PED; + bPedPhysics = true; + bUseCollisionRecords = true; + + 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_attractorHeading = 0.0f; + m_carInObjective = nil; + m_attractorHeading = 0.0f; + bInVehicle = false; + m_pMyVehicle = nil; + m_pVehicleAnim = nil; + m_vecOffsetSeek.x = 0.0f; + m_vecOffsetSeek.y = 0.0f; + m_vecOffsetSeek.z = 0.0f; + m_attractor = nil; + m_positionInQueue = -1; + 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; + m_acceptableHeadingOffset = 0.1f; + m_followPathDestPos = CVector(0.f, 0.f, 0.f); + m_followPathAbortDist = 0.0f; + m_followPathMoveState = PEDMOVE_NONE; + bRunningToPhone = false; + m_phoneId = -1; + m_lastAccident = 0; + m_fleeFrom = nil; + m_fleeFromPos = CVector2D(0.0f, 0.0f); + m_fleeTimer = 0; + m_threatEx = nil; + m_vecSpotToGuard = CVector(0.0f, 0.0f, 0.0f); + m_radiusToGuard = 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; + + for (int i = 0; i < ARRAY_SIZE(m_pathNodesToGo); i++) { + m_pathNodesToGo[i] = nil; + } + m_nNumPathNodes = 0; + m_nCurPathNodeId = 0; + m_nPathDir = 0; + m_pLastPathNode = nil; + m_pNextPathNode = nil; + m_followPathWalkAroundEnt = nil; + m_followPathTargetEnt = nil; + m_pathNodeTimer = 0; + m_pCurPathNode = nil; + + m_threatFlags = 0; + m_threatCheckTimer = 0; + m_threatCheckInterval = CGeneral::GetRandomNumberInRange(250, 1000); + + m_routeLastPoint = -1; + m_routeStartPoint = 0; + m_routePointsPassed = 0; + m_routeType = 0; + + m_fMass = 70.0f; + m_fTurnMass = 100.0f; + m_fAirResistance = 0.4f / m_fMass; + m_fElasticity = 0.05f; + + m_ceaseAttackTimer = 0; + m_bodyPartBleeding = -1; + + 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; + 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; + bDontAcceptIKLookAts = false; + bReachedAttractorHeadingTarget = false; + bTurnedAroundOnAttractor = false; +#ifdef KANGAROO_CHEAT + m_ped_flagI80 = false; +#endif + + bHasAlreadyUsedAttractor = false; + bHasAlreadyStoleACar = false; + bCarPassenger = false; + bFleeWhenStanding = false; + bGotUpOfMyOwnAccord = false; + bMiamiViceCop = false; + bMoneyHasBeenGivenByScript = false; + bHasBeenPhotographed = false; + + bIsDrowning = false; + bDrownsInWater = true; + bWaitForLeaderToComeCloser = false; + bHeldHostageInCar = false; + bIsPlayerFriend = true; + bHeadStuckInCollision = false; + bDeadPedInFrontOfCar = false; + + m_gangFlags = ~0; + + bStayInCarOnJack = false; + + bDontFight = false; + bDoomAim = true; + bCanBeShotInVehicle = true; + bPushedAlongByCar = false; + bRemoveMeWhenIGotIntoCar = false; + bIgnoreThreatsBehindObjects = false; + + bNeverEverTargetThisPed = false; + bCrouchWhenScared = false; + bKnockedOffBike = false; + b158_8 = false; + bCollectBusFare = false; + bBoughtIceCream = false; + bDonePositionOutOfCollision = false; + bCanAttackPlayerWithCops = false; + + if (CGeneral::GetRandomNumber() & 3) + bHasACamera = false; + else + bHasACamera = true; + + if (CGeneral::GetRandomNumberInRange(0.0f, 1.0f) <= 0.95f) + bCanGiveUpSunbathing = false; + else + bCanGiveUpSunbathing = 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; + } + m_maxWeaponTypeAllowed = WEAPONTYPE_UNARMED; + m_currentWeapon = WEAPONTYPE_UNARMED; + m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + m_delayedWeapon = WEAPONTYPE_UNIDENTIFIED; + + for(int i = 0; i < TOTAL_WEAPON_SLOTS; 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 = m_lastFightMove = FIGHTMOVE_IDLE; + GiveWeapon(WEAPONTYPE_UNARMED, 0, true); + m_wepAccuracy = 60; + m_lastWepDam = -1; + m_lastDamEntity = nil; + m_attachedTo = nil; + m_attachWepAmmo = 0; + m_collPoly.valid = false; + m_fCollisionSpeed = 0.0f; + m_wepModelID = -1; + uint16 random = CGeneral::GetRandomNumber(); + m_nPedMoney = random % 25; + if (m_nPedMoney == 23) + m_nPedMoney = 400; + m_bleedCounter = 0; + m_nExtendedRangeTimer = 0; + m_vehicleInAccident = nil; + m_attractor = nil; + m_positionInQueue = -1; + m_pWeaponModel = nil; + m_delayedSoundID = -1; + m_delayedSoundTimer = 0; + CPopulation::UpdatePedCount((ePedType)m_nPedType, false); + m_lastComment = UINT32_MAX; +} + +CPed::~CPed(void) +{ +#ifdef USE_CUTSCENE_SHADOW_FOR_PED + if ( m_pRTShadow ) delete m_pRTShadow; +#endif + CWorld::Remove(this); + if (m_attractor) + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + 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 being decreased 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(); + + ClearWeapons(); + if (bCarPassenger) + CPopulation::ms_nTotalCarPassengerPeds--; + if (bMiamiViceCop) + CPopulation::NumMiamiViceCops--; + 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); + + if (!CanUseTorsoWhenLooking()) + m_pedIK.m_flags |= CPedIK::LOOKAROUND_HEAD_ONLY; + + (*RPANIMBLENDCLUMPDATA(m_rwObject))->velocity2d = &m_vecAnimMoveDelta; + + if(modelInfo->GetHitColModel() == nil) + modelInfo->CreateHitColModelSkinned(GetClump()); + + UpdateRpHAnim(); +#ifdef USE_CUTSCENE_SHADOW_FOR_PED + if (!m_pRTShadow) + { + m_pRTShadow = new CCutsceneShadow; + m_pRTShadow->Create(m_rwObject, 10, 1, 1, 1); + //m_pRTShadow->Create(m_rwObject, 8, 0, 0, 0); + } +#endif +} + +void +CPed::SetPedStats(ePedStats pedStat) +{ + m_pedStats = CPedStats::ms_apPedStats[pedStat]; +} + +void +CPed::DeleteRwObject() +{ + CEntity::DeleteRwObject(); +} + +void +CPed::BuildPedLists(void) +{ + if (((CTimer::GetFrameCounter() + m_randomSeed) % 16) == 0) { + CVector centre = CEntity::GetBoundCentre(); + int deadsRegistered = 0; + CRect rect(centre.x - 20.f * nThreatReactionRangeMultiplier, + centre.y - 20.f * nThreatReactionRangeMultiplier, + centre.x + 20.f * nThreatReactionRangeMultiplier, + centre.y + 20.f * nThreatReactionRangeMultiplier); + 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 || (ped->m_pMyVehicle && ped->m_pMyVehicle->IsBike()))) { + + if (nThreatReactionRangeMultiplier * 30.0f > (ped->GetPosition() - GetPosition()).Magnitude2D()) { + if (ped->m_nPedState == PED_DEAD) { + if (deadsRegistered > 3) + continue; + deadsRegistered++; + } +#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; + } + + 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 > sq(nThreatReactionRangeMultiplier * 30.f)) { + 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, bool shootablesDoBlock) +{ + 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 + return !CWorld::ProcessLineOfSight(GetPosition() + CVector(0.f, 0.f, 1.f), target->GetPosition() + CVector(0.f, 0.f, 1.f), + colpoint, ent, true, false, false, shootablesDoBlock, false, false, false, shootablesDoBlock); +} + +// 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() || m_attachedTo) + 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) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_MELEE_IDLE_FIGHTMODE); + + if (animAssoc && m_nPedState == PED_FIGHT) + 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, 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; + } + if (m_nPedState != PED_IDLE) { + 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 nextNode = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + CVector diff = nextNode - GetPosition(); + if (diff.MagnitudeSqr() < sq(7.0f)) { + SetMoveState(PEDMOVE_WALK); + break; + } + } + } + SetWanderPath(m_nPedState == PED_FOLLOW_PATH ? m_nPathDir : 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; + } + + if (fearFlags & PED_FLAG_GUN) { + CPed *shooter = CheckForGunShots(); + if (shooter && (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_nPedType == PEDTYPE_GANG5) { + if (m_threatEntity) + m_threatEntity->CleanUpOldReference(&m_threatEntity); + 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; + 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: 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(m_nearPeds[i]->m_nPedType); + + if (flagsOfNearPed & fearFlags) { + if (m_nearPeds[i]->m_fHealth > 0.0f) { + if (OurPedCanSeeThisOne(m_nearPeds[i], !!bIgnoreThreatsBehindObjects)) { + 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 + if (!CWorld::ProcessLineOfSight(ourPos, nearPed->GetPosition(), foundCol, foundEnt, + true, false, false, !!bIgnoreThreatsBehindObjects, 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]; + } + } + } + } + } + } + } + } + } + + 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::ScanForDelayedResponseThreats(void) +{ + if (m_threatFlags) + return; + + m_threatEntity = nil; + m_pEventEntity = nil; + m_threatFlags = ScanForThreats(); + if (m_threatFlags) { + if (m_threatEntity || m_pEventEntity) { + m_threatCheckTimer = CTimer::GetTimeInMilliseconds() + m_threatCheckInterval; + return; + } + m_threatFlags = 0; + } + m_threatCheckTimer = 0; +} + +void +CPed::CheckThreatValidity(void) +{ + if (m_threatEntity && !IsEntityPointerValid(m_threatEntity)) { + m_threatFlags = 0; + m_threatEntity = nil; + } + if (m_pEventEntity && !IsEntityPointerValid(m_pEventEntity)) { + m_threatFlags = 0; + m_pEventEntity = nil; + } + if (!m_threatEntity && !m_pEventEntity) + m_threatFlags = 0; +} + +bool +CPed::CanUseTorsoWhenLooking(void) +{ + if (m_nPedState != PED_DRIVING && m_nPedState != PED_DRAG_FROM_CAR && !bIsDucking) { + if (m_animGroup != ASSOCGRP_SEXYWOMAN && m_animGroup != ASSOCGRP_WOMAN) + return true; + } + return false; +} + +void +CPed::SetLookFlag(float direction, bool keepTryingToLook, bool cancelPrevious) +{ + if (m_lookTimer < CTimer::GetTimeInMilliseconds() || cancelPrevious) { + bIsLooking = true; + bIsRestoringLook = false; + m_fLookDirection = direction; + m_pLookTarget = nil; + m_lookTimer = 0; + bKeepTryingToLook = keepTryingToLook; + if (CanUseTorsoWhenLooking()) { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + } + } +} + +void +CPed::SetLookFlag(CEntity *target, bool keepTryingToLook, bool cancelPrevious) +{ + if (m_lookTimer < CTimer::GetTimeInMilliseconds() || cancelPrevious) { + bIsLooking = true; + bIsRestoringLook = false; + m_pLookTarget = target; + m_pLookTarget->RegisterReference((CEntity**)&m_pLookTarget); + m_fLookDirection = 999999.0f; + m_lookTimer = 0; + bKeepTryingToLook = keepTryingToLook; + if (CanUseTorsoWhenLooking()) { + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + } + } +} + +void +CPed::ClearLookFlag(void) { + if (bIsLooking) { + bIsLooking = false; + bIsRestoringLook = true; + bShakeFist = false; + + if (CanUseTorsoWhenLooking()) + 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 +CPed::MoveHeadToLook(void) +{ + CVector lookPos; + + if (m_lookTimer && CTimer::GetTimeInMilliseconds() > m_lookTimer) { + ClearLookFlag(); + } + + if (bIsLooking || bIsRestoringLook) + if (!CanUseTorsoWhenLooking()) + m_pedIK.m_flags |= CPedIK::LOOKAROUND_HEAD_ONLY; + + if (m_pLookTarget) { + 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_nPedState == PED_ANSWER_MOBILE) + return; + + if (m_lookTimer - CTimer::GetTimeInMilliseconds() >= 1000) + return; + + bool handFreeToMove = false; + AnimationId animToPlay = ANIM_STD_NUM; + + if (!GetWeapon()->IsType2Handed() && GetWeapon()->m_eWeaponType != WEAPONTYPE_ROCKETLAUNCHER) + handFreeToMove = true; + + if (IsPlayer() && handFreeToMove) { + + if (m_pLookTarget->IsPed()) { +#ifdef FIX_BUGS + if (m_pedStats->m_temper > 49 || ((CPed*)m_pLookTarget)->m_nPedType == PEDTYPE_COP) +#else + if (m_pedStats->m_temper < 49 || ((CPed*)m_pLookTarget)->m_nPedType == PEDTYPE_COP) +#endif + animToPlay = ANIM_STD_PARTIAL_FUCKU; + else if(m_pedStats->m_temper < 47) + animToPlay = ANIM_STD_PARTIAL_PUNCH; + } else { + if (m_pedStats->m_temper > 49 || m_pLookTarget->GetModelIndex() == MI_POLICE) + animToPlay = ANIM_STD_PARTIAL_FUCKU; + } + } else if (handFreeToMove && (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; + } + } + bShakeFist = false; + } else if (m_fLookDirection == 999999.0f) { + ClearLookFlag(); + } else if (!m_pedIK.LookInDirection(m_fLookDirection, 0.0f)) { + if (!bKeepTryingToLook) + ClearLookFlag(); + } +} + +void +CPed::RestoreHeadPosition(void) +{ + if(!CanUseTorsoWhenLooking()) + m_pedIK.m_flags |= CPedIK::LOOKAROUND_HEAD_ONLY; + + if (m_pedIK.RestoreLookAt()) { + bIsRestoringLook = false; + if(CanUseTorsoWhenLooking()) + m_pedIK.m_flags &= ~CPedIK::LOOKAROUND_HEAD_ONLY; + } +} + +void +CPed::SetAimFlag(float angle) +{ + bIsAimingGun = true; + bIsRestoringGun = false; + m_fLookDirection = angle; + m_lookTimer = 0; + m_pLookTarget = nil; + m_pSeekTarget = nil; + + if (bIsDucking) + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; + + 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; + if (m_pLookTarget) + m_pLookTarget->CleanUpOldReference(&m_pLookTarget); + m_pLookTarget = to; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + if (m_pSeekTarget) + m_pSeekTarget->CleanUpOldReference(&m_pSeekTarget); + 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; + m_lookTimer = 0; + } + + if (IsPlayer()) { + ((CPlayerPed*)this)->m_fFPSMoveHeading = 0.0f; +#ifdef FREE_CAM + ((CPlayerPed*)this)->m_bFreeAimActive = false; +#endif + } +} + +void +CPed::AimGun(void) +{ + CVector vector; + + if (IsPlayer() && bIsDucking) + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; + + if (m_pSeekTarget) { + if (m_pSeekTarget->IsPed()) { + ((CPed*)m_pSeekTarget)->m_pedIK.GetComponentPosition(vector, PED_MID); + } else { + vector = m_pSeekTarget->GetPosition(); + } + + if (!IsPlayer()) + Say(SOUND_PED_ATTACK); + + bCanPointGunAtTarget = m_pedIK.PointGunAtPosition(vector); + if (m_pLookTarget != m_pSeekTarget) { + SetLookFlag(m_pSeekTarget, true, 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; + } +} + +bool +CPed::CanWeRunAndFireWithWeapon(void) +{ + return CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM); +} + +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) { + 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); + } + } + } + } + } else { + m_chatTimer = CTimer::GetTimeInMilliseconds() + 200; + } + } +} + +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 (stranger->m_nPedType == PEDTYPE_COP) + return false; + if (stranger->IsPlayer()) + 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; + } + + 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()) { + + if (FindPlayerPed() == this) + FindPlayerPed()->m_fWalkAngle = 0.0f; + 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); + if(!fightAssoc) + fightAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_IDLE_TIRED); + + if(!fightAssoc) + fightAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_MELEE_IDLE_FIGHTMODE); + + if ((!idleAssoc || idleAssoc->blendAmount < 0.5f) && !fightAssoc && !bIsDucking) { + 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)) { + 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; + } + } +} + +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 = HALFPI; + else if (leftRight > 0.0f) + angle = -HALFPI; + } + + return CGeneral::LimitRadianAngle(offset + angle); +} + +void +CPed::UpdatePosition(void) +{ + if (CReplay::IsPlayingBack() || !bIsStanding || m_attachedTo) + 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 && (!m_pCurrentPhysSurface->bInfiniteMass || m_pCurrentPhysSurface->m_phy_flagA08)) { + 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_fleeFromPos = CVector2D(0.0f, 0.0f); + m_fleeFrom = nil; + m_fleeTimer = 0; + m_threatEx = nil; + bUsesCollision = true; + ClearPointGunAt(); + bIsPointingGunAt = false; + bRenderPedInCar = true; + bKnockedUpIntoAir = false; + bKnockedOffBike = false; + m_pCollidingEntity = nil; +} + +void +CPed::ProcessBuoyancy(void) +{ + float buoyancyLevel = 1.1f; + static uint32 nGenerateRaindrops = 0; + static uint32 nGenerateWaterCircles = 0; + CRGBA color; + + if (bInVehicle) + return; + + CVector buoyancyPoint; + CVector buoyancyImpulse; + + if (DyingOrDead()) + buoyancyLevel = 1.8f; + + 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() && !entity->bRenderScorched) { + bIsInWater = false; + return; + } + color.r = (0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed()) * 127.5f; + color.g = (0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue()) * 127.5f; + color.b = (0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen()) * 127.5f; + color.a = CGeneral::GetRandomNumberInRange(48.0f, 96.0f); + 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; + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, pos, CVector(0.0f, 0.0f, 0.0f), 0.0f, 50, color, true); + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + SetPedState(PED_IDLE); + return; + } + } + } + } + float speedMult = 0.0f; + if (buoyancyImpulse.z / m_fMass > GRAVITY * CTimer::GetTimeStep() + || mod_Buoyancy.m_waterlevel > GetPosition().z + 0.6f) { + speedMult = pow(0.9f, CTimer::GetTimeStep()); + m_vecMoveSpeed.x *= speedMult; + m_vecMoveSpeed.y *= speedMult; + m_vecMoveSpeed.z *= speedMult; + bIsStanding = false; + bIsDrowning = true; + 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); + 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; + } + } + 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++) { + 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); + } + } + } + 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) { + pos.z += 0.25f; + nGenerateRaindrops = 0; + CParticleObject::AddObject(POBJECT_SPLASHES_AROUND, pos, CVector(0.0f, 0.0f, 0.0f), 4.5f, 1500, CRGBA(0,0,0,0), true); + } + } + } else + bTouchingWater = false; +} + +void +CPed::ProcessControl(void) +{ + CColPoint foundCol; + CEntity *foundEnt = nil; + + if (CTimer::GetFrameCounter() + m_randomSeed % 32 == 0) + PruneReferences(); + + 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; + bDonePositionOutOfCollision = false; + BuildPedLists(); + bIsInWater = false; + bIsDrowning = false; + ProcessBuoyancy(); + + if (m_nPedState != PED_ARRESTED) { + if (m_nPedState == PED_DEAD) { + DeadPedMakesTyresBloody(); + if (CGame::nastyGame && !bIsInWater) { + 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_CAMERA + && 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(); + + if (bIsInWater) { + bIsStanding = false; + bWasStanding = false; + CPhysical::ProcessControl(); + } + 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(); + + if (bIsStanding) + bPushedAlongByCar = false; + + bCollidedWithMyVehicle = false; + + CEntity *collidingEnt = m_pDamageEntity; + if (!bUsesCollision || ((!collidingEnt || m_fDamageImpulse <= 0.0f) && (!IsPlayer() || !bIsStuck)) || m_nPedState == PED_DIE) { + bHitSomethingLastFrame = false; + if (m_nPedStateTimer <= 500 && bIsInTheAir) { + if (m_nPedStateTimer) + m_nPedStateTimer--; + } else if (m_nPedStateTimer < 1001) { + m_nPedStateTimer = 0; + } + } else if (!GetPedAttractorManager()->IsInQueue(this, m_attractor)) { + if (m_panicCounter == 50 && IsPedInControl()) { + SetWaitState(WAITSTATE_STUCK, nil); + // Leftover + /* + if (m_nPedType < PEDTYPE_COP) { + + } else { + + } + */ + } else if (collidingEnt) { + 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_nPedState == PED_FOLLOW_PATH) { + if (DotProduct(m_vecDamageNormal, GetForward()) < -0.866f && CanPedJumpThis(collidingEnt, &m_vecDamageNormal)) { + SetJump(); + } + break; + } + + 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) { + if (m_nPedType == PEDTYPE_COP && m_nWaitState != WAITSTATE_LOOK_ABOUT) + ((CCopPed*)this)->field_624++; + + 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 { + if (m_pLookTarget) + m_pLookTarget->CleanUpOldReference(&m_pLookTarget); + + 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); + + if (m_nWaitState != WAITSTATE_HITWALL) + SetWaitState(WAITSTATE_TURN180, nil); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 5000; + Flee(); + } 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]; + CVector bestCoords = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + bestCoords.x, bestCoords.y, + GetPosition().x, GetPosition().y); + + } else if (m_nPedState == PED_FOLLOW_PATH) { + CVector bestCoords = m_pathNodesToGo[m_nCurPathNodeId]->GetPosition(); + angleToFace = CGeneral::GetRadianAngleBetweenPoints( + bestCoords.x, bestCoords.y, + 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(); + posToHead.z = ThePaths.m_pathNodes[closestNodeId].GetZ(); + } + 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; + + 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_SPRINT_TO_AREA + || m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER + || IsUseAttractorObjective(m_objective))) { + + if (collidingVeh != m_pCurrentPhysSurface || IsPlayer()) { + if (!bVehEnterDoorIsBlocked) { + if (collidingVeh->GetStatus() != STATUS_PLAYER || CharCreatedBy == MISSION_CHAR) { + + if (m_nPedState == PED_SEEK_CAR) { + SetDirectionToWalkAroundObject(collidingVeh); + CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer = m_nPedStateTimer; + } else { + SetDirectionToWalkAroundVehicle(collidingVeh); + } + } else { + if (CTimer::GetTimeInMilliseconds() >= CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer + || m_nPedStateTimer >= CTimer::GetTimeInMilliseconds()) { + + if (m_nPedState == PED_SEEK_CAR) { + SetDirectionToWalkAroundObject(collidingVeh); + CWorld::Players[CWorld::PlayerInFocus].m_nLastBumpPlayerCarTimer = m_nPedStateTimer; + } else { + SetDirectionToWalkAroundVehicle(collidingVeh); + } + } 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; + uint32 weaponSlot = CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot; + if (weaponType == WEAPONTYPE_UNARMED + || weaponSlot == 3 + || weaponSlot == 5 + || weaponSlot == 1) { + 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::GetColModel(collidingVeh->m_modelIndex); + 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); + } + + if (m_pCollidingEntity != collidingEnt) + bPushedAlongByCar = true; + } + if (m_fHealth < oldHealth && playerSufferSound) + Say(SOUND_PED_HIT); + + 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 weaponType = player->GetWeapon()->m_eWeaponType; + uint32 weaponSlot = CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot; + if (weaponType == WEAPONTYPE_UNARMED + || weaponSlot == 3 + || weaponSlot == 5 + || weaponSlot == 1) { + player->bShakeFist = true; + } + } + } + } + } + } + break; + } + default: + break; + } + } + CVector forceDir; + if (!bIsInTheAir && m_nPedState != PED_JUMP && m_fDamageImpulse > 0.0f) { + forceDir = m_vecDamageNormal; + forceDir.z = 0.0f; + if (!bIsStanding) { + forceDir *= 4.0f; + } else { + forceDir *= 0.5f; + } + + ApplyMoveForce(forceDir); + } + if ((bIsInTheAir && !DyingOrDead()) || (!bIsStanding && !bWasStanding && m_nPedState == PED_FALL)) { + 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; + offsetToCheck.z = 1.0f; + 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; + } + 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 && !bHeadStuckInCollision) { + GetMatrix().SetTranslateOnly(flyDir == 2 ? obstacleForFlyingOtherDir.point : obstacleForFlying.point); + GetMatrix().GetPosition().z += FEET_OFFSET; + GetMatrix().UpdateRW(); + SetLanding(); + bIsStanding = true; + } + 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) { + 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); + } + } + } 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)) { + if (!bHeadStuckInCollision || FEET_OFFSET + foundCol.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + foundCol.point.z; + GetMatrix().UpdateRW(); + if (bHeadStuckInCollision) + bHeadStuckInCollision = false; + } + 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); + bStartWanderPathOnFoot = false; + } + } + + if (!bIsStanding && m_vecMoveSpeed.z > 0.25) { + float airResistance = Pow(0.95f, CTimer::GetTimeStep()); + + m_vecMoveSpeed *= airResistance; + } + 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); + } + + if (m_nPedState != PED_DIE || bIsPedDieAnimPlaying) { + RequestDelayedWeapon(); + PlayFootSteps(); + if (m_nPedState != PED_DEAD) { + CalculateNewVelocity(); + CalculateNewOrientation(); + } + UpdatePosition(); + if (IsPedInControl() && !bIsStanding && !m_pDamageEntity) { + if (m_attachedTo) { + bIsInTheAir = false; + } else if (CheckIfInTheAir()) { + SetInTheAir(); + bHeadStuckInCollision = false; + } + } + if (bHeadStuckInCollision) { + CVector posToCheck = GetPosition(); + posToCheck.z += 0.9f; + if (!CWorld::TestSphereAgainstWorld(posToCheck, 0.2f, this, true, true, false, true, false, false)) + bHeadStuckInCollision = false; + } + 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(); + +#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: + // III has these in here(and they were unused): + /* + WanderRange(); + CheckAroundForPossibleCollisions(); + */ + break; + case PED_WANDER_PATH: + WanderPath(); + break; + case PED_ENTER_CAR: + case PED_CARJACK: + { + break; + } + case PED_FLEE_POS: + ms_vec2DFleePosition = m_fleeFromPos; + Flee(); + break; + case PED_FLEE_ENTITY: + if (!m_fleeFrom) { + bMakeFleeScream = false; + 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_BUY_ICECREAM: + BuyIceCream(); + break; + case PED_INVESTIGATE: + InvestigateEvent(); + break; + case PED_ON_FIRE: + if (IsPlayer()) + break; + + if (CTimer::GetTimeInMilliseconds() <= m_fleeTimer) { + if (m_pFire) { + if (m_fleeFrom) { + ms_vec2DFleePosition = m_fleeFrom->GetPosition(); + } else { + ms_vec2DFleePosition = m_fleeFromPos; + } + Flee(); + } else { + m_nLastPedState = PED_NONE; + SetWanderPath(0); + SetWaitState(WAITSTATE_FINISH_FLEE, 0); + } + } else { + if (m_pFire) + m_pFire->Extinguish(); + } + break; + case PED_ANSWER_MOBILE: + AnswerMobile(); + break; + case PED_FALL: + Fall(); + break; + case PED_GETUP: + SetGetUp(); + break; +#ifdef GTA_TRAIN + case PED_ENTER_TRAIN: + EnterTrain(); + break; + case PED_EXIT_TRAIN: + ExitTrain(); + break; +#endif + case PED_DRIVING: + { + if (!m_pMyVehicle) { + bInVehicle = false; + FlagToDestroyWhenNextProcessed(); + return; + } + +#ifdef CAR_AIRBREAK + if (IsPlayer()) { + CPad* pad = CPad::GetPad(0); + 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 + + if (m_pMyVehicle->pDriver == this) { + DriveVehicle(); + if (!m_pMyVehicle) + return; + } else { + LookForSexyPeds(); + LookForSexyCars(); + } + + if (!IsPlayer() && m_pMyVehicle->IsBoat() + && FindPlayerPed()->m_pCurrentPhysSurface == m_pMyVehicle + && (CharCreatedBy != MISSION_CHAR || !bIsPlayerFriend)) { + SetObjective(OBJECTIVE_KILL_CHAR_ON_BOAT, FindPlayerPed()); + Say(SOUND_PED_CAR_JACKED); + } + + break; + } + case PED_DIE: + Die(); + break; + case PED_HANDS_UP: + if (m_pedStats->m_flags & STAT_GUN_PANIC) { + 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 || m_bleedCounter != 0) { + if (CGame::nastyGame) { + if (m_bleedCounter != 0) + m_bleedCounter--; + + 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; + + if (bHeldHostageInCar) { + if (m_pMyVehicle && m_pMyVehicle->pDriver && m_pMyVehicle->pDriver->IsPlayer()) { + Say(SOUND_PED_FLEE_SPRINT); + } + } + + if (m_delayedSoundID >= 0 && CTimer::GetTimeInMilliseconds() > m_delayedSoundTimer) { + Say(m_delayedSoundID); + m_delayedSoundID = -1; + m_delayedSoundTimer = 0; + } + + if (bFannyMagnetCheat && m_nPedType == PEDTYPE_CIVFEMALE + && m_pedStats->m_sexiness > 40 && !m_leader) { + SetLeader(FindPlayerPed()); + } + + } else { + if (bIsStanding && (!m_pCurrentPhysSurface || IsPlayer()) + || bIsInWater || !bUsesCollision) { + SetDead(); + } + m_pCurrentPhysSurface = nil; + } + } else + ServiceTalking(); +} + +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 && !bJustCheckCollision) + return 0; + + if (collidingEnt->IsVehicle() && ((CVehicle*)collidingEnt)->IsBoat()) + collidedWithBoat = true; + + // ofc we're not vehicle + if (!m_bIsVehicleBeingShifted && !bSkipLineCol && !collidingEnt->IsPed()) { + if (!bCollisionProcessed) { + m_pCurrentPhysSurface = nil; + 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; + if(!bHeadStuckInCollision || FEET_OFFSET + intersectionPoint.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; + if (bHeadStuckInCollision) + bHeadStuckInCollision = false; + } + 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, 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, false, &m_collPoly); + } + if (belowTorsoCollided) { + 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; + } + } + + if (!bHeadStuckInCollision || FEET_OFFSET + intersectionPoint.point.z < GetPosition().z) { + GetMatrix().GetPosition().z = FEET_OFFSET + intersectionPoint.point.z; + if (bHeadStuckInCollision) + bHeadStuckInCollision = false; + } + m_nSurfaceTouched = intersectionPoint.surfaceB; + if (m_nSurfaceTouched == SURFACE_STEEP_CLIFF) { + bHitSteepSlope = true; + m_vecDamageNormal = intersectionPoint.normal; + } + } + + 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); + } + if (CSurfaceTable::IsSoftLanding(intersectionPoint.surfaceB)) + damage *= 0.5f; + + 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); + } + m_vecMoveSpeed.z = 0.0f; + bIsStanding = true; + } 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; + if (sphereNormal.z >= -1.0f || !IsPlayer()) { + normalLength = sphereNormal.Magnitude2D(); + if (normalLength != 0.0f) { + sphereNormal.x = sphereNormal.x / normalLength; + sphereNormal.y = sphereNormal.y / normalLength; + } + } 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; + bHeadStuckInCollision = true; + } + 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) +{ + 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); + } +} + +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: + case SURFACE_SAND_BEACH: + case SURFACE_CONCRETE_BEACH: + 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); + // ?? + CGeneral::GetRandomNumber(); + CGeneral::GetRandomNumber(); + 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) +{ + CAnimBlendAssociation *assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); + CAnimBlendAssociation *walkRunAssoc = nil; + float walkRunAssocBlend = 0.0f, idleAssocBlend = 0.0f; + bool isSkater = m_pedStats == CPedStats::ms_apPedStats[PEDSTAT_SKATER]; + + CVector footPosL(0.0f, 0.0f, 0.0f), footPosR(0.0f, 0.0f, 0.0f); + bool footPosLok = false, footPosRok = false; + + if (bDoBloodyFootprints) { + if (m_bloodyFootprintCountOrDeathTime > 0 && m_bloodyFootprintCountOrDeathTime < 300) { + m_bloodyFootprintCountOrDeathTime--; + + if (m_bloodyFootprintCountOrDeathTime == 0) + bDoBloodyFootprints = false; + } + } + + if (!bIsStanding) + return; + + 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; + } + } + + if (walkRunAssoc && walkRunAssocBlend > 0.5f && idleAssocBlend < 1.0f) { + + float stepStart = 1.f / 15.f; + float stepEnd = walkRunAssoc->hierarchy->totalLength / 2.0f + stepStart; + float currentTime = walkRunAssoc->currentTime; + + if (isSkater) { + // both are unused + static float stepStartSection = 1.0f; + static float animSections = 15.f; + + float moveStart, soundVolume, skateTime; + if (walkRunAssoc->animId == ANIM_STD_WALK) { + moveStart = 0.0f; + skateTime = 8.f / 15.f; + } else { + moveStart = 0.0f; + skateTime = 5.f / 15.f; + } + switch (CSurfaceTable::GetAdhesionGroup(m_nSurfaceTouched)) { + case ADHESIVE_LOOSE: + if (CGeneral::GetRandomNumber() % 128) { + m_vecAnimMoveDelta *= 0.5f; + } else { + SetFall(0, ANIM_STD_HIGHIMPACT_BACK, false); + } + soundVolume = 0.5f; + break; + case ADHESIVE_SAND: + if (CGeneral::GetRandomNumber() % 64) { + m_vecAnimMoveDelta *= 0.2f; + } else { + SetFall(0, ANIM_STD_HIGHIMPACT_BACK, false); + } + soundVolume = 0.2f; + break; + case ADHESIVE_WET: + m_vecAnimMoveDelta *= 0.3f; + soundVolume = 0.2f; + break; + default: + soundVolume = 1.f; + break; + } + if (soundVolume > 0.2f && currentTime > moveStart && currentTime - walkRunAssoc->timeStep <= moveStart) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SKATING, ((int)(127.f * soundVolume) | (walkRunAssoc->animId << 8))); + } else if (soundVolume > 0.2f) { + if (currentTime > skateTime && currentTime - walkRunAssoc->timeStep <= skateTime) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SKATING, ((int)(127.f * soundVolume) | (walkRunAssoc->animId << 8))); + } + } + + } else { + int stepPart = 0; + + // This section is shortened/optimized for sanity. + + if (currentTime >= stepStart && currentTime - walkRunAssoc->timeStep < stepStart) + stepPart = 1; + else if (currentTime >= stepEnd && currentTime - walkRunAssoc->timeStep < stepEnd) + stepPart = 2; + + if (stepPart != 0) { + CVector adjustedFootPos; + if (stepPart == 1) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_STEP_START, 1.0f); + TransformToNode(footPosL, PED_FOOTL); + footPosLok = true; + adjustedFootPos = footPosL; + } else { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_STEP_END, 1.0f); + TransformToNode(footPosR, PED_FOOTR); + footPosRok = true; + adjustedFootPos = footPosR; + } + + CVector forward = GetForward(); + + adjustedFootPos.z -= 0.1f; + adjustedFootPos += 0.2f * forward; + + if (bDoBloodyFootprints) { + CVector2D top(forward * 0.26f); + CVector2D right(GetRight() * (stepPart == 1 ? 0.14f : 0.1f)); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &adjustedFootPos, + top.x, top.y, + right.x, right.y, + 255, 255, 0, 0, 4.0f, 3000, 1.0f); + + if (m_bloodyFootprintCountOrDeathTime <= 20) { + m_bloodyFootprintCountOrDeathTime = 0; + bDoBloodyFootprints = false; + } else { + m_bloodyFootprintCountOrDeathTime -= 20; + } + } + if (m_nSurfaceTouched == SURFACE_SAND || m_nSurfaceTouched == SURFACE_SAND_BEACH) { + CVector2D top(forward * -0.26f); + CVector2D right(GetRight() * (stepPart == 1 ? 0.1f : 0.14f)); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpShadowPedTex, &adjustedFootPos, + top.x, top.y, + right.x, right.y, + 120, 250, 250, 50, 4.0f, 5000, 1.0f); + } + if (CWeather::Rain <= 0.1f || CCullZones::CamNoRain() || CCullZones::PlayerNoRain()) { + if (IsPlayer()) + particleProduceFootDust(this, adjustedFootPos, 0.0f, 4); + + } else if (stepPart == 2) { + particleProduceFootSplash(this, adjustedFootPos, 0.15f, 4); + } + } + } + } + + if (IsPlayer() && !walkRunAssoc && bIsLanding) { + if (!footPosLok) + TransformToNode(footPosL, PED_FOOTL); + + CVector forward = GetForward(); + + CVector adjustedFootPosL = footPosL; + adjustedFootPosL.z -= 0.1f; + adjustedFootPosL += 0.2f * forward; + if (bDoBloodyFootprints) { + CVector2D top(forward * 0.26f); + CVector2D right(GetRight() * 0.14f); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &adjustedFootPosL, + top.x, top.y, + right.x, right.y, + 255, 255, 0, 0, 4.0f, 3000, 1.0f); + + if (m_bloodyFootprintCountOrDeathTime <= 20) { + m_bloodyFootprintCountOrDeathTime = 0; + bDoBloodyFootprints = false; + } + else { + m_bloodyFootprintCountOrDeathTime -= 20; + } + } + if (!isSkater) { + if (m_nSurfaceTouched == SURFACE_SAND || m_nSurfaceTouched == SURFACE_SAND_BEACH) { + CVector2D top(forward * -0.26f); + CVector2D right(GetRight() * 0.14f); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpShadowPedTex, &adjustedFootPosL, + top.x, top.y, + right.x, right.y, + 120, 250, 250, 50, 4.0f, 5000, 1.0f); + } + } + if(!footPosRok) + TransformToNode(footPosR, PED_FOOTR); + + CVector adjustedFootPosR = footPosR; + adjustedFootPosR.z -= 0.1f; + adjustedFootPosR += 0.2f * forward; + + if (bDoBloodyFootprints) { + CVector2D top(forward * 0.26f); + CVector2D right(GetRight() * 0.1f); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpBloodPoolTex, &adjustedFootPosR, + top.x, top.y, + right.x, right.y, + 255, 255, 0, 0, 4.0f, 3000, 1.0f); + + if (m_bloodyFootprintCountOrDeathTime <= 20) { + m_bloodyFootprintCountOrDeathTime = 0; + bDoBloodyFootprints = false; + } else { + m_bloodyFootprintCountOrDeathTime -= 20; + } + } + if (!isSkater) { + if (m_nSurfaceTouched == SURFACE_SAND || m_nSurfaceTouched == SURFACE_SAND_BEACH) { + CVector2D top(forward * -0.26f); + CVector2D right(GetRight() * 0.14f); + + CShadows::AddPermanentShadow(SHADOWTYPE_DARK, gpShadowPedTex, &adjustedFootPosR, + top.x, top.y, + right.x, right.y, + 120, 250, 250, 50, 4.0f, 5000, 1.0f); + } + } + } + + if (m_nSurfaceTouched == SURFACE_WATER) { + CRGBA rubberSmokeColor(255, 255, 255, 196); + float pedSpeed = CVector2D(m_vecMoveSpeed).Magnitude(); + if (pedSpeed > 0.03f && CTimer::GetFrameCounter() % 2 == 0 && pedSpeed > 0.13f) { + 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_CAR_SPLASH, particlePos, particleDir, nil, 0.5f * particleSize, CRGBA(0,0,0,0), 0, 0, 0, 0); + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, particlePos, particleDir, nil, particleSize, rubberSmokeColor, 0, 0, 0, 0); + } + + if (m_nPedState == PED_JUMP) { + CVector particlePos = GetPosition(); + particlePos.z -= 0.1f; + + CVector particleDir(0.0f, 0.075f, 0.0f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, particlePos, particleDir, nil, 0.005f, CRGBA(0, 0, 0, 0), 0, 0, 0, 0); + particleDir.x += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + particleDir.y += CGeneral::GetRandomNumberInRange(-0.05f, 0.05f); + particleDir.z -= CGeneral::GetRandomNumberInRange(0.025f, 0.05f); + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, particlePos, particleDir, nil, 0.5f, rubberSmokeColor, 0, 0, 0, 0); + } + } +} + +// 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; +} + +bool +CPed::SetDirectionToWalkAroundVehicle(CVehicle* veh) +{ + return SetFollowPath(m_vecSeekPos, 0.0f, m_nMoveState, veh, m_pedInObjective, m_nMoveState == PEDMOVE_WALK ? 2000 : 250); +} + +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; + + if (CharCreatedBy != MISSION_CHAR && obj->GetModelIndex() == MI_PHONEBOOTH1) { + bool isRunning = m_nMoveState == PEDMOVE_RUN || m_nMoveState == PEDMOVE_SPRINT; + SetFindPathAndFlee(obj, 5000, !isRunning); + return; + } + + 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; + float angleToFaceObjCenter = (objColCenter - GetPosition()).Heading(); + float angleDiffBtwObjCenterAndForward = CGeneral::LimitRadianAngle(dirToSet - angleToFaceObjCenter); + float objTopRightHeading = Atan2(adjustedColMax.x - adjustedColMin.x, adjustedColMax.y - adjustedColMin.y); + + 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; + + if (!obj->IsVehicle() || objUpsideDown) { + collidingThingChanged = false; + } else { + 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; + } + } + + // 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; + } + } + + // 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; + } + } + + // 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; + } + } + } + + if (!entityOnTopLeftOfObj && !entityOnTopRightOfObj) { + CVector topLeftCorner(adjustedColMin.x - 0.3f, adjustedColMax.y + 0.3f, 0.0f); + topLeftCorner = objMat * topLeftCorner; + CVector topRightCorner(adjustedColMax.x + 0.3f, adjustedColMax.y + 0.3f, 0.0f); + topRightCorner = objMat * topRightCorner; + CColPoint foundCol; + CEntity *foundEnt; + if (CWorld::ProcessLineOfSight(topLeftCorner, topRightCorner, foundCol, foundEnt, true, true, false, true, false, false, false, false)) { + switch (foundEnt->GetType()) { + case ENTITY_TYPE_VEHICLE: + entityOnTopRightOfObj = 2; + entityOnTopLeftOfObj = 2; + break; + case ENTITY_TYPE_BUILDING: + entityOnTopRightOfObj = 1; + entityOnTopLeftOfObj = 1; + break; + case ENTITY_TYPE_OBJECT: + entityOnTopRightOfObj = 3; + entityOnTopLeftOfObj = 3; + break; + } + } + } + if (!entityOnBottomRightOfObj && !entityOnBottomLeftOfObj) { + CVector bottomRightCorner(adjustedColMax.x + 0.3f, adjustedColMin.y - 0.3f, 0.0f); + bottomRightCorner = objMat * bottomRightCorner; + CVector bottomLeftCorner(adjustedColMin.x - 0.3f, adjustedColMin.y - 0.3f, 0.0f); + bottomLeftCorner = objMat * bottomLeftCorner; + CColPoint foundCol; + CEntity* foundEnt; + if (CWorld::ProcessLineOfSight(bottomRightCorner, bottomLeftCorner, foundCol, foundEnt, true, true, false, true, false, false, false, false)) { + switch (foundEnt->GetType()) { + case ENTITY_TYPE_VEHICLE: + entityOnBottomLeftOfObj = 2; + entityOnBottomRightOfObj = 2; + break; + case ENTITY_TYPE_BUILDING: + entityOnBottomLeftOfObj = 1; + entityOnBottomRightOfObj = 1; + break; + case ENTITY_TYPE_OBJECT: + entityOnBottomLeftOfObj = 3; + entityOnBottomRightOfObj = 3; + break; + } + } + } + + 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; + } + } + } + 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; + + 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; + } + } + } + } + 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 || m_nPedState == PED_ANSWER_MOBILE; +} + +void +CPed::PedSetPreviousStateCB(CAnimBlendAssociation* assoc, void* arg) +{ + CPed* ped = (CPed*)arg; + ped->RestorePreviousState(); + ped->m_pVehicleAnim = nil; +} + +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->bFleeWhenStanding && ped->m_threatEx) { + ped->SetFlee(ped->m_threatEx, 10000); + ped->Say(SOUND_PED_FLEE_SPRINT); + ped->bFleeWhenStanding = false; + ped->m_threatEx = nil; + + } else if (ped->bGotUpOfMyOwnAccord) { + ped->SetObjective(OBJECTIVE_NONE); + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.f, 8.f)); + ped->bGotUpOfMyOwnAccord = false; + + } else { + 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(); + 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); + } + + ped->bInVehicle = false; + if (veh && (veh->IsCar() || veh->IsBike())) { + CWorld::pIgnoreEntity = veh; + if (CWorld::TestSphereAgainstWorld(ped->GetPosition() - CVector(0.f, 0.f, 0.2f), + 0.4f, veh, true, true, false, false, false, false) + || CWorld::TestSphereAgainstWorld(ped->GetPosition() + CVector(0.f, 0.f, 0.2f), + 0.4f, veh, true, true, false, false, false, false) + || !CWorld::GetIsLineOfSightClear(veh->GetPosition(), ped->GetPosition(), true, false, false, true, false, false, false)) { + CWorld::pIgnoreEntity = nil; + ped->PositionPedOutOfCollision(); + } + CWorld::pIgnoreEntity = nil; + } + + if (ped->m_nPedState == PED_EXIT_CAR) { + if (ped->m_nPedType == PEDTYPE_COP) { + ped->SetIdle(); + if (((CCopPed*)ped)->m_nCopType == COP_MIAMIVICE && ped->m_pMyVehicle && ped->m_pMyVehicle->pDriver == ped) { + DMAudio.PlayOneShot(ped->m_audioEntityId, SOUND_PED_MIAMIVICE_EXITING_CAR, 0.f); + } + } 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)); + } + } else if (ped->m_nPedState == PED_DRIVING) { + ped->m_nPedState = PED_IDLE; + } + + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + + ped->RestartNonPartialAnims(); + ped->m_pVehicleAnim = nil; + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + 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; + } + } + } + if (veh && veh->IsBike()) + // BUG? + veh->m_nGettingOutFlags &= ~GetBikeDoorFlag(ped->m_vehDoor); + else + 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); + if (veh->IsBike()) { + if (Abs(veh->m_vecMoveSpeed.x) < 0.1 && Abs(veh->m_vecMoveSpeed.y) < 0.1f) { + ((CBike*)veh)->bIsStanding = true; + } + } + } 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); + } + ped->bHeldHostageInCar = false; +} + +void +CPed::PedSetDraggedOutCarCB(CAnimBlendAssociation *dragAssoc, void *arg) +{ + CAnimBlendAssociation *quickJackedAssoc; + CVehicle *vehicle; + CPed *ped = (CPed*)arg; + + uint8 exitFlags = 0; + quickJackedAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), ANIM_STD_QUICKJACKED); + if (dragAssoc && dragAssoc->animId == ANIM_BIKE_HIT && ped->m_pMyVehicle) { + if (ped->m_vehDoor == CAR_DOOR_LF || ped->m_vehDoor == CAR_DOOR_RF) { + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_BIKE_FALLOFF, 100.0f); + ped->m_pMyVehicle->m_nGettingOutFlags &= ~(CAR_DOOR_FLAG_RF | CAR_DOOR_FLAG_LF); + } else { + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_BIKE_FALLBACK, 100.0f); + ped->m_pMyVehicle->m_nGettingOutFlags &= ~(CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR); + } + ((CBike*)ped->m_pMyVehicle)->KnockOffRider(WEAPONTYPE_UNIDENTIFIED, 0, ped, true); + return; + } + + 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->IsBike()) + exitFlags = GetBikeDoorFlagInclJumpInFromFront(ped->m_vehDoor); + else + exitFlags = GetCarDoorFlag(ped->m_vehDoor); + + if (vehicle) + vehicle->m_nGettingOutFlags &= ~exitFlags; + + if (vehicle) { + 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(); + + 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; + } + + 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; + + // 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; + } + } + + if (ped->bRemoveMeWhenIGotIntoCar) { + ped->bRemoveMeWhenIGotIntoCar = false; + ped->bRemoveFromWorld = true; + } + if (ped->bCollectBusFare) { + ped->bCollectBusFare = false; + if (FindPlayerPed()) + FindPlayerPed()->m_nLastBusFareCollected += 5; + } + + 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 || ped->m_nPedState == PED_CARJACK) + 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()) { + CCarCtrl::RegisterVehicleOfInterest(veh); + 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(); + ped->RemoveWeaponWhenEnteringVehicle(); + 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 || veh->IsBike()) { + 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->bStayInCarOnJack && !passenger->bHeldHostageInCar && (passenger->m_leader != ped || !ped->bIsLeader)) { + passenger->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + passenger->m_leaveCarTimer = CTimer::GetTimeInMilliseconds(); + } + } + } + + 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_SPRINT_TO_AREA || ped->m_prevObjective == OBJECTIVE_KILL_CHAR_ON_FOOT) + ped->m_prevObjective = OBJECTIVE_NONE; + + ped->RestorePreviousObjective(); + } + + } else { + + bool slowDown = false; + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER && veh->pDriver && ped->CharCreatedBy == RANDOM_CHAR) + slowDown = true; + + // VC also has a dead condition in here. + + if (veh->IsBike()) { + veh->AddPassenger(ped, 0); + } else 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_SPRINT_TO_AREA || ped->m_prevObjective == OBJECTIVE_KILL_CHAR_ON_FOOT) + ped->m_prevObjective = OBJECTIVE_NONE; + + ped->RestorePreviousObjective(); + + // VC has conditional OBJECTIVE_LEAVE_CAR here, which runs if it entered the first dead condition. + + if(slowDown) + veh->AutoPilot.m_nCruiseSpeed = 17; + } + + int8 doorFlag; + if (veh->IsBike()) { + doorFlag = GetBikeDoorFlagInclJumpInFromFront(ped->m_vehDoor); + } else { + doorFlag = GetEnterCarDoorFlag(ped->m_vehDoor, veh->m_nNumMaxPassengers); + } + + veh->m_nGettingInFlags &= ~doorFlag; + + 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: + case OBJECTIVE_GOTO_SEAT_ON_FOOT: + case OBJECTIVE_GOTO_ATM_ON_FOOT: + case OBJECTIVE_GOTO_BUS_STOP_ON_FOOT: + case OBJECTIVE_GOTO_PIZZA_ON_FOOT: + case OBJECTIVE_GOTO_SHELTER_ON_FOOT: + case OBJECTIVE_SPRINT_TO_AREA: + case OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT: + break; + default: + ped->SetObjective(OBJECTIVE_NONE); + } + + ped->AddInCarAnims(veh, veh->pDriver == ped); + 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; + case UNK_CHAR: + return false; + default: + return true; + } +} + +bool +CPed::CanBeDeletedEvenInVehicle(void) +{ + switch (CharCreatedBy) { + case RANDOM_CHAR: + return true; + case MISSION_CHAR: + return false; + case UNK_CHAR: + return false; + default: + return true; + } +} + +void +CPed::AddWeaponModel(int id) +{ + if (id != -1) { + if (m_pWeaponModel) + RemoveWeaponModel(-1); + + m_pWeaponModel = (RpAtomic*)CModelInfo::GetModelInfo(id)->CreateInstance(); + CModelInfo::GetModelInfo(id)->AddRef(); + m_wepModelID = id; + + if (IsPlayer() && id == MI_MINIGUN) + ((CPlayerPed*)this)->m_pMinigunTopAtomic = (RpAtomic*)CModelInfo::GetModelInfo(MI_MINIGUN2)->CreateInstance(); + } +} + +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. + if(m_pWeaponModel){ + if (modelId == -1 + || CVisibilityPlugins::GetAtomicModelInfo(m_pWeaponModel) == CModelInfo::GetModelInfo(modelId)) { + CVisibilityPlugins::GetAtomicModelInfo(m_pWeaponModel)->RemoveRef(); + RwFrame* frm = RpAtomicGetFrame(m_pWeaponModel); + RpAtomicDestroy(m_pWeaponModel); + RwFrameDestroy(frm); + m_pWeaponModel = nil; + } + } + + if (IsPlayer() && (modelId == -1 || modelId == MI_MINIGUN)) { + RpAtomic* &atm = ((CPlayerPed*)this)->m_pMinigunTopAtomic; + if (atm) { + RwFrame *frm = RpAtomicGetFrame(atm); + RpAtomicDestroy(atm); + RwFrameDestroy(frm); + atm = nil; + } + } + m_wepModelID = -1; +} + +void +CPed::RequestDelayedWeapon() +{ + if (m_delayedWeapon != WEAPONTYPE_UNIDENTIFIED) { + int modelId1 = CWeaponInfo::GetWeaponInfo(m_delayedWeapon)->m_nModelId; + int modelId2 = CWeaponInfo::GetWeaponInfo(m_delayedWeapon)->m_nModel2Id; + if (modelId1 != -1) + CStreaming::RequestModel(modelId1, STREAMFLAGS_DEPENDENCY); + if (modelId2 != -1) + CStreaming::RequestModel(modelId2, STREAMFLAGS_DEPENDENCY); + + if ((modelId1 == -1 || CStreaming::HasModelLoaded(modelId1)) + && (modelId2 == -1 || CStreaming::HasModelLoaded(modelId2))) { + GiveWeapon(m_delayedWeapon, m_delayedWeaponAmmo, 1); + m_delayedWeapon = WEAPONTYPE_UNIDENTIFIED; + } + } +} + +void +CPed::GiveDelayedWeapon(eWeaponType weapon, uint32 ammo) +{ + m_delayedWeapon = weapon; + m_delayedWeaponAmmo = ammo; + if (m_delayedWeapon != WEAPONTYPE_UNIDENTIFIED) { + int modelId1 = CWeaponInfo::GetWeaponInfo(m_delayedWeapon)->m_nModelId; + int modelId2 = CWeaponInfo::GetWeaponInfo(m_delayedWeapon)->m_nModel2Id; + if (modelId1 != -1) + CStreaming::RequestModel(modelId1, STREAMFLAGS_DEPENDENCY); + if (modelId2 != -1) + CStreaming::RequestModel(modelId2, STREAMFLAGS_DEPENDENCY); + + if ((modelId1 == -1 || CStreaming::HasModelLoaded(modelId1)) + && (modelId2 == -1 || CStreaming::HasModelLoaded(modelId2))) { + GiveWeapon(m_delayedWeapon, m_delayedWeaponAmmo, true); + m_delayedWeapon = WEAPONTYPE_UNIDENTIFIED; + } + } +} + +int32 +CPed::GiveWeapon(eWeaponType weaponType, uint32 ammo, bool unused) +{ + int slot = GetWeaponSlot(weaponType); + + if (m_weapons[slot].m_eWeaponType == weaponType) { + GetWeapon(slot).m_nAmmoTotal += ammo; + if (weaponType < WEAPONTYPE_TOTALWEAPONS && weaponType > WEAPONTYPE_UNARMED && CWeaponInfo::ms_aMaxAmmoForWeapon[weaponType] >= 0) { + + // Looks like abandoned idea. This block never runs, ms_aMaxAmmoForWeapon is always -1. + GetWeapon(slot).m_nAmmoTotal = Min(CWeaponInfo::ms_aMaxAmmoForWeapon[weaponType], GetWeapon(slot).m_nAmmoTotal); + } else { + GetWeapon(slot).m_nAmmoTotal = Min(99999, GetWeapon(slot).m_nAmmoTotal); + } + GetWeapon(slot).Reload(); + if (GetWeapon(slot).m_eWeaponState == WEAPONSTATE_OUT_OF_AMMO && GetWeapon(slot).m_nAmmoTotal > 0) + GetWeapon(slot).m_eWeaponState = WEAPONSTATE_READY; + } else { + if (HasWeaponSlot(slot)) { + if (CWeaponInfo::IsWeaponSlotAmmoMergeable(slot)) + ammo += GetWeapon(slot).m_nAmmoTotal; + + RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(GetWeapon(slot).m_eWeaponType)->m_nModelId); + GetWeapon(slot).Shutdown(); + } + GetWeapon(slot).Initialise(weaponType, ammo); + if (slot == m_currentWeapon && !bInVehicle) { + AddWeaponModel(CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_nModelId); + } + } + if (GetWeapon(slot).m_eWeaponState != WEAPONSTATE_OUT_OF_AMMO) + GetWeapon(slot).m_eWeaponState = WEAPONSTATE_READY; + + return slot; +} + +int +CPed::GetWeaponSlot(eWeaponType weaponType) +{ + return CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot; +} + +void +CPed::SetCurrentWeapon(int slot) +{ + if (slot == -1) + return; + + CWeaponInfo* weaponInfo; + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(weaponInfo->m_nModelId); + } + m_currentWeapon = slot; + + if (FindPlayerPed() && IsPlayer()) + ((CPlayerPed*)this)->m_nSelectedWepSlot = m_currentWeapon; + + if (HasWeaponSlot(slot)) { + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + AddWeaponModel(weaponInfo->m_nModelId); + } +} + +void +CPed::SetCurrentWeapon(eWeaponType weaponType) +{ + SetCurrentWeapon(CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot); +} + +void +CPed::GrantAmmo(eWeaponType weaponType, uint32 ammo) +{ + int slot = CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot; + if (slot == -1) + return; + + GetWeapon(slot).m_nAmmoTotal += ammo; + if (weaponType < WEAPONTYPE_TOTALWEAPONS && weaponType > WEAPONTYPE_UNARMED && CWeaponInfo::ms_aMaxAmmoForWeapon[weaponType] >= 0) { + + // Looks like abandoned idea. This block never runs, ms_aMaxAmmoForWeapon is always -1. + GetWeapon(slot).m_nAmmoTotal = Min(CWeaponInfo::ms_aMaxAmmoForWeapon[weaponType], GetWeapon(slot).m_nAmmoTotal); + } else { + GetWeapon(slot).m_nAmmoTotal = Min(99999, GetWeapon(slot).m_nAmmoTotal); + } + + if (GetWeapon(slot).m_eWeaponState == WEAPONSTATE_OUT_OF_AMMO && GetWeapon(slot).m_nAmmoTotal > 0) + GetWeapon(slot).m_eWeaponState = WEAPONSTATE_READY; +} + +void +CPed::SetAmmo(eWeaponType weaponType, uint32 ammo) +{ + int slot = CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponSlot; + if (slot == -1) + return; + + GetWeapon(slot).m_nAmmoTotal = ammo; + if (weaponType < WEAPONTYPE_TOTALWEAPONS && weaponType > WEAPONTYPE_UNARMED && CWeaponInfo::ms_aMaxAmmoForWeapon[weaponType] >= 0) { + + // Looks like abandoned idea. This block never runs, ms_aMaxAmmoForWeapon is always -1. + GetWeapon(slot).m_nAmmoTotal = Min(CWeaponInfo::ms_aMaxAmmoForWeapon[weaponType], GetWeapon(slot).m_nAmmoTotal); + } else { + GetWeapon(slot).m_nAmmoTotal = Min(99999, GetWeapon(slot).m_nAmmoTotal); + } + int32 newClip = GetWeapon(slot).m_nAmmoTotal; + if (newClip >= GetWeapon(slot).m_nAmmoInClip) + newClip = GetWeapon(slot).m_nAmmoInClip; + GetWeapon(slot).m_nAmmoInClip = newClip; + + if (GetWeapon(slot).m_eWeaponState == WEAPONSTATE_OUT_OF_AMMO && GetWeapon(slot).m_nAmmoTotal > 0) + GetWeapon(slot).m_eWeaponState = WEAPONSTATE_READY; +} + +void +CPed::ClearWeapons(void) +{ + RemoveWeaponModel(-1); + for (int i = 0; i < ARRAY_SIZE(m_weapons); i++) { + GetWeapon(i).Shutdown(); + } + SetCurrentWeapon(WEAPONTYPE_UNARMED); +} + +void +CPed::RemoveWeaponWhenEnteringVehicle(void) +{ + if (IsPlayer() && HasWeaponSlot(5) && GetWeapon(5).m_nAmmoTotal > 0 && ((CPlayerPed*)this)->GetPlayerInfoForThisPlayerPed()->m_bDriveByAllowed) { + if (m_storedWeapon == WEAPONTYPE_UNIDENTIFIED) + m_storedWeapon = GetWeapon()->m_eWeaponType; + SetCurrentWeapon(GetWeapon(5).m_eWeaponType); + } else { + CWeaponInfo *ourWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + RemoveWeaponModel(ourWeapon->m_nModelId); + } +} +void +CPed::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() && GetWeaponSlot(weaponType) == WEAPONSLOT_SUBMACHINEGUN) { + if (m_storedWeapon != WEAPONTYPE_UNIDENTIFIED) { + SetCurrentWeapon(m_storedWeapon); + m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + } + } else { + AddWeaponModel(CWeaponInfo::GetWeaponInfo(weaponType)->m_nModelId); + } +} + +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]); + + UpdateRpHAnim(); + + bool bIsWindModifierTurnedOn = false; + float fAnyDirectionShift = 1.0f; + bool bIsPedDrivingBikeOrOpenTopCar = false; + if (IsPlayer() && CWindModifiers::FindWindModifier(GetPosition(), &fAnyDirectionShift, &fAnyDirectionShift) + && !CCullZones::PlayerNoRain() && GetPedState() != PED_DRIVING) + bIsWindModifierTurnedOn = true; + + if (GetPedState() == PED_DRIVING && m_pMyVehicle) { + if (m_pMyVehicle->m_vehType == VEHICLE_TYPE_BIKE + || (m_pMyVehicle->m_vehType == VEHICLE_TYPE_CAR && m_pMyVehicle->IsOpenTopCar())) + bIsPedDrivingBikeOrOpenTopCar = true; + } + + if (bIsWindModifierTurnedOn || bIsPedDrivingBikeOrOpenTopCar) { + float fWindMult = 0.0f; + if (bIsPedDrivingBikeOrOpenTopCar) { + fWindMult = DotProduct(m_pMyVehicle->m_vecMoveSpeed, GetForward()); + if (fWindMult > 0.4f) { + float volume = (fWindMult - 0.4f) / 0.6f; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SHIRT_WIND_FLAP, volume); + } + } + + if (bIsWindModifierTurnedOn) + fWindMult = Min(fWindMult, Abs(fAnyDirectionShift - 1.0f)); + + RpHAnimHierarchy* hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx; + RwV3d scale; + float fScaleOffset; + + fScaleOffset = fWindMult * 0.2f; + scale.x = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + scale.y = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + scale.z = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_NECK)); + RwMatrix* neck = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(neck, &scale, rwCOMBINEPRECONCAT); + + fScaleOffset = fWindMult * 0.1f; + scale.x = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + scale.y = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + scale.z = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_CLAVICLEL)); + RwMatrix* clavicleL = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(clavicleL, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_CLAVICLER)); + RwMatrix* clavicleR = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(clavicleR, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_MID)); + RwMatrix* mid = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(mid, &scale, rwCOMBINEPRECONCAT); + + fScaleOffset = fWindMult * 0.2f; + scale.x = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + scale.y = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + scale.z = CGeneral::GetRandomNumberInRange(1.0f - fScaleOffset, 1.0f + fScaleOffset); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_UPPERARML)); + RwMatrix* upperArmL = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(upperArmL, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_UPPERARMR)); + RwMatrix* upperArmR = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(upperArmR, &scale, rwCOMBINEPRECONCAT); + } + + 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); + } + + if (IsPlayer() && gfTommyFatness != 1.0f) { + RpHAnimHierarchy* hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx; + RwV3d scale; + + scale.x = 1.0f; + scale.y = 1.0f + gfTommyFatness * 0.7f; + scale.z = 1.0f + gfTommyFatness * 0.7f; + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_HEAD)); + RwMatrix* head = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(head, &scale, rwCOMBINEPRECONCAT); + + scale.y = 1.0f + gfTommyFatness * 0.2f; + scale.z = 1.0f + gfTommyFatness * 0.2f; + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_NECK)); + RwMatrix* neck = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(neck, &scale, rwCOMBINEPRECONCAT); + + scale.y = 1.0f + gfTommyFatness * 0.5f; + scale.z = 1.0f + gfTommyFatness * 0.5f; + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_MID)); + RwMatrix* mid = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(mid, &scale, rwCOMBINEPRECONCAT); + + scale.y = 1.0f + gfTommyFatness; + scale.z = 1.0f + gfTommyFatness; + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_UPPERLEGL)); + RwMatrix* upperLegL = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(upperLegL, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_UPPERLEGR)); + RwMatrix* upperLegR = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(upperLegR, &scale, rwCOMBINEPRECONCAT); + + scale.y = 1.0f + gfTommyFatness * 0.5f; + scale.z = 1.0f + gfTommyFatness * 0.5f; + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_LOWERLEGR)); + RwMatrix* lowerLegR = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(lowerLegR, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_LOWERLEGL)); + RwMatrix* lowerLegL = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(lowerLegL, &scale, rwCOMBINEPRECONCAT); + + scale.y = 1.0f + gfTommyFatness * 0.23f; + scale.z = 1.0f + gfTommyFatness * 0.23f; + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_FOOTL)); + RwMatrix* footL = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(footL, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_FOOTR)); + RwMatrix* footR = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(footR, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_UPPERARML)); + RwMatrix* upperArmL = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(upperArmL, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_UPPERARMR)); + RwMatrix* upperArmR = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(upperArmR, &scale, rwCOMBINEPRECONCAT); + + scale.y = 1.0f + gfTommyFatness * 0.2f; + scale.z = 1.0f + gfTommyFatness * 0.2f; + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_FOREARML)); + RwMatrix* foreArmL = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(foreArmL, &scale, rwCOMBINEPRECONCAT); + + idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_FOREARMR)); + RwMatrix* foreArmR = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwMatrixScale(foreArmR, &scale, rwCOMBINEPRECONCAT); + } + + 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; + } + } + } + } + } +} + +CVector vecTestTemp(-1.0f, -1.0f, -1.0f); + +void +CPed::Render(void) +{ + if (bInVehicle && m_pMyVehicle && m_nPedState != PED_EXIT_CAR && m_nPedState != PED_DRAG_FROM_CAR) { + if (!bRenderPedInCar) + return; + + if (!m_pMyVehicle->IsBike() && !IsPlayer()) { + float camDistSq = (TheCamera.GetPosition() - GetPosition()).MagnitudeSqr(); + if (camDistSq > SQR((m_pMyVehicle->IsBoat() ? 40.0f : 25.0f) * TheCamera.LODDistMultiplier)) + return; + } + } + + CEntity::Render(); + + if(m_pWeaponModel){ + 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); + if (IsPlayer()) { + CPlayerPed *player = (CPlayerPed*)this; + if (player->m_pMinigunTopAtomic) { + frame = RpAtomicGetFrame(player->m_pMinigunTopAtomic); + *RwFrameGetMatrix(frame) = *mat; + + player->m_fGunSpinAngle = player->m_fGunSpinSpeed * CTimer::GetTimeStep() + player->m_fGunSpinAngle; + if (player->m_fGunSpinAngle > TWOPI) + player->m_fGunSpinAngle -= TWOPI; + + CMatrix mgTopMat, localAdjMat; + mgTopMat.Attach(RwFrameGetMatrix(frame)); + localAdjMat.SetRotateX(player->m_fGunSpinAngle); + localAdjMat.Rotate(DEGTORAD(-4.477f)* vecTestTemp.x, DEGTORAD(29.731f) * vecTestTemp.y, DEGTORAD(1.064f) * vecTestTemp.z); + localAdjMat.GetPosition() += CVector(0.829f, -0.001f, 0.226f); + mgTopMat = mgTopMat * localAdjMat; + mgTopMat.UpdateRW(); + + RwFrameUpdateObjects(frame); + RpAtomicRender(player->m_pMinigunTopAtomic); + } + } + } +} + +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) { + if (m_nPedState == PED_AIM_GUN) + ClearPointGunAt(); + + SetPedState(PED_IDLE); + SetMoveState(PEDMOVE_STILL); + m_nLastPedState = PED_NONE; + } + 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; + } + } + } + } + + if (m_nMoveState != PEDMOVE_STILL && !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 (m_attachedTo) + return; + + if (!IsPedInControl() && (!evenIfNotInControl || DyingOrDead())) + return; + + ClearLookFlag(); + ClearAimFlag(); + SetStoredState(); + SetPedState(PED_FALL); + CAnimBlendAssociation *fallAssoc = nil; + if (animId == ANIM_STD_NUM) { + if (IsPlayer()) { + fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROLLOUT_LHS); + if (!fallAssoc) + fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROLLOUT_RHS); + } + } else { + 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 (animId == ANIM_STD_BIKE_FALLBACK) + fallAssoc->SetCurrentTime(0.4f); + } + + if (extraTime == -1) { + m_getUpTimer = UINT32_MAX; + } else if (fallAssoc) { + if (IsPlayer()) { + if (fallAssoc->animId == ANIM_STD_ROLLOUT_LHS || fallAssoc->animId == ANIM_STD_ROLLOUT_RHS) { + m_getUpTimer = 1000.0f * fallAssoc->hierarchy->totalLength + + CTimer::GetTimeInMilliseconds() + - 1000.0f * fallAssoc->currentTime + + 100.0f; + } else { + 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 && bIsStanding) + ClearFall(); + + CAnimBlendAssociation *firstPartialAssoc; + CAnimBlendAssociation *fallAssoc; + + if (IsPlayer() && (bKnockedUpIntoAir || bKnockedOffBike) && !bIsStanding) { + firstPartialAssoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_PARTIAL); + + // What??? + if (firstPartialAssoc && (firstPartialAssoc->animId == ANIM_STD_FALL_ONBACK || firstPartialAssoc->animId == ANIM_STD_FALL_ONFRONT)) + fallAssoc = firstPartialAssoc; + else + fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_ONBACK); + + if (!fallAssoc) + fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_ONFRONT); + + if (!fallAssoc && firstPartialAssoc && 0.8f * firstPartialAssoc->hierarchy->totalLength < firstPartialAssoc->currentTime) { + if (firstPartialAssoc->flags & ASSOC_FRONTAL) { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_ONFRONT, 8.0f); + } else { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FALL_ONBACK, 8.0f); + } + } else if (fallAssoc && fallAssoc->blendAmount > 0.3f && fallAssoc->blendDelta >= 0.0f) { + float time = fallAssoc->currentTime; + + if (time > 0.667f && time - fallAssoc->timeStep <= 0.667f) { + fallAssoc->SetCurrentTime(0.0f); + fallAssoc->SetRun(); + } + } + } else if ((bKnockedUpIntoAir || bKnockedOffBike) && bIsStanding && !bWasStanding) { + fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_ONBACK); + + if (!fallAssoc) + fallAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FALL_ONFRONT); + + if (fallAssoc) { + bKnockedUpIntoAir = false; + bKnockedOffBike = false; + fallAssoc->speed = 3.0f; + if (IsPlayer()) + Say(SOUND_PED_LAND); + + } else { + firstPartialAssoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_PARTIAL); + if (firstPartialAssoc && !firstPartialAssoc->IsRunning()) { + bKnockedUpIntoAir = false; + bKnockedOffBike = false; + } + } + } +} + +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 || bIsStanding) + SetLanding(); + } else if (m_nPedState != PED_ABSEIL && !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; + + if (fallAssoc && bIsDrowning) + return; + + RpAnimBlendClumpSetBlendDeltas(GetClump(), ASSOC_PARTIAL, -1000.0f); + if (fallAssoc || m_nPedType == PEDTYPE_COP && bKnockedUpIntoAir) { + 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); + + if (m_nPedType == PEDTYPE_COP) { + if (bKnockedUpIntoAir) + bKnockedUpIntoAir = false; + } + + } 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 && veh != m_attachedTo || + 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()) + InflictDamage(nil, WEAPONTYPE_RUNOVERBYCAR, 1000.0f, PEDPIECE_TORSO, 0); + return; + } + bGetUpAnimStarted = true; + m_pCollidingEntity = nil; + bKnockedUpIntoAir = false; + bKnockedOffBike = 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 (m_nWaitState == WAITSTATE_SUN_BATHE_IDLE) { + m_headingRate = 0.0f; + + // Seemingly they planned to use different getup anims for different conditions, but sadly in final game all getup anims(GETUP1, GETUP2, GETUP3) are same... + if (bFleeWhenStanding && m_threatEx) + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GET_UP, 1000.0f); + else + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_GET_UP, 1000.0f); + + } else 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(); + } +} + +// Unused +void +CPed::SetLook(float direction) +{ + if (IsPedInControl()) { + SetStoredState(); + SetPedState(PED_LOOK_HEADING); + SetLookFlag(direction, false); + } +} + +void +CPed::SetLook(CEntity* to) +{ + if (IsPedInControl()) { + SetStoredState(); + SetPedState(PED_LOOK_ENTITY); + SetLookFlag(to, false); + } +} + +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) +{ + TurnBody(); +} + +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) || m_nPedState == PED_FOLLOW_PATH) + return; + + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) { + 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 || m_nPedState == PED_FOLLOW_PATH) + 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 + 60) % 32 == 0) { + + CEntity *obstacle = CWorld::TestSphereAgainstWorld(m_vecSeekPos, 0.4f, nil, + false, true, false, false, false, false); + + if (obstacle) { + if (!obstacle->IsVehicle() || ((CVehicle*)obstacle)->IsCar()) { + 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(); + + if (m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && !m_pedInObjective) { + m_objective = OBJECTIVE_NONE; + ClearObjective(); + SetWanderPath(0); + return false; + } + + 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_SPRINT_TO_AREA) + nextMove = PEDMOVE_SPRINT; + else 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; + } + } + + CVector *nextNode = SeekFollowingPath(); + + if (nextNode || seekPosDist >= distanceToCountItDone) { + if (bIsRunning && nextMove != PEDMOVE_SPRINT) + 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.f; + m_actionY = 0.f; + } + } + + } else { + if (nextNode) { + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints( + nextNode->x, nextNode->y, + GetPosition().x, GetPosition().y); + } 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.0f && 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_nPedState != PED_FOLLOW_PATH && 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.f; + m_actionY = 0.f; + } + + if (m_objective == OBJECTIVE_GOTO_AREA_ON_FOOT || m_objective == OBJECTIVE_RUN_TO_AREA || m_objective == OBJECTIVE_SPRINT_TO_AREA || + m_objective == OBJECTIVE_GOTO_AREA_ANY_MEANS || IsUseAttractorObjective(m_objective)) { + + if (m_pNextPathNode) + m_pNextPathNode = nil; + else + bScriptObjectiveCompleted = true; + + bUsePedNodeSeek = true; + } + + 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_fleeFromPos = from; + } + + 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) { + bMakeFleeScream = false; + eMoveState moveState = m_nMoveState; + ClearFlee(); + + if (m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_TILL_SAFE || m_objective == OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS) { + bBeingChasedByPolice = false; + 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 (bMakeFleeScream && !((CTimer::GetFrameCounter() + m_randomSeed) & 7)) { + Say(SOUND_PED_FLEE_SPRINT); + bMakeFleeScream = false; + } + + 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 = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + 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() >= 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 (IsPlayer()) + return false; + + 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 = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + 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 + uint8 tryCount = 0; + + 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); + + if (((CPedModelInfo*)CModelInfo::GetModelInfo(m_modelIndex))->m_pedStatType == PEDSTAT_SKATER) { + if (m_pNextPathNode) { + CVector unpacked(m_pNextPathNode->GetPosition() / 8.f); + if (!CPopulation::IsSkateable(unpacked)) + m_pNextPathNode = nil; + } + } + + // 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; + } + } + if (((CPedModelInfo*)CModelInfo::GetModelInfo(m_modelIndex))->m_pedStatType == PEDSTAT_SKATER) { + if (m_pNextPathNode) { + CVector unpacked(m_pNextPathNode->GetPosition() / 8.f); + if (!CPopulation::IsSkateable(unpacked)) + 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)); + } + } + } + } + } + } +} + +CVector* +CPed::SeekFollowingPath(void) +{ + static CVector vecNextPathNode; + + if (m_nCurPathNodeId >= m_nNumPathNodes || m_nNumPathNodes == 0) + return nil; + + vecNextPathNode = m_pathNodesToGo[m_nCurPathNodeId]->GetPosition(); + + if ((vecNextPathNode - GetPosition()).Magnitude2D() < m_distanceToCountSeekDone) { + m_nCurPathNodeId++; + if (m_nCurPathNodeId < m_nNumPathNodes) + m_pCurPathNode = m_pathNodesToGo[m_nCurPathNodeId]; + } + if (m_nCurPathNodeId == m_nNumPathNodes) + return nil; + else + return &vecNextPathNode; +} + +bool +CPed::SetFollowPath(CVector dest, float radius, eMoveState state, CEntity* walkAroundEnt, CEntity* targetEnt, int time) +{ + if (m_nPedState == PED_FOLLOW_PATH) { + bool stopFollow = false; + if (walkAroundEnt && walkAroundEnt != m_followPathWalkAroundEnt || !walkAroundEnt && m_followPathWalkAroundEnt + || targetEnt && targetEnt != m_followPathTargetEnt || !targetEnt && m_followPathTargetEnt) { + stopFollow = true; + + } else if (targetEnt) { + if ((targetEnt->GetPosition() - m_followPathDestPos).MagnitudeSqr() > 1.f) + stopFollow = true; + + } else if (!walkAroundEnt && !targetEnt) { + if ((dest - m_followPathDestPos).MagnitudeSqr() > 1.f) + stopFollow = true; + } + + if (!stopFollow) + return false; + } + m_pathNodeTimer = CTimer::GetTimeInMilliseconds() + time; + m_followPathWalkAroundEnt = walkAroundEnt; + m_followPathTargetEnt = targetEnt; + m_distanceToCountSeekDone = 0.5f; + + bool weHaveTargetPed = targetEnt && targetEnt->IsPed(); + bool useDestVec = !weHaveTargetPed; + + if (useDestVec) + m_followPathDestPos = dest; + else + m_followPathDestPos = targetEnt->GetPosition(); + + if (targetEnt && m_nPedState == PED_SEEK_POS) { + m_followPathDestPos = m_vecSeekPos; + } + + m_followPathAbortDist = radius > 0.f ? radius : 20.f; + + bool useGivenPedMove = state == PEDMOVE_RUN || state == PEDMOVE_WALK; + if (useGivenPedMove) + m_followPathMoveState = state; + else + m_followPathMoveState = PEDMOVE_WALK; + + if (m_followPathWalkAroundEnt) + return SetFollowPathDynamic(); + else + return SetFollowPathStatic(); +} + +bool +CPed::SetFollowPathStatic(void) +{ + ClearFollowPath(); + if (sq(m_followPathAbortDist) > (GetPosition() - m_followPathDestPos).MagnitudeSqr() + && CWorld::IsWanderPathClear(GetPosition(), m_followPathDestPos, 0.5f, 4)) { + + RestorePreviousState(); + if (m_objective == OBJECTIVE_NONE) { + if (m_followPathMoveState == PEDMOVE_RUN) + SetObjective(OBJECTIVE_RUN_TO_AREA, m_followPathDestPos); + else + SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, m_followPathDestPos); + } + SetPedState(PED_NONE); + } else { + ThePaths.DoPathSearch(PATH_PED, GetPosition(), -1, m_followPathDestPos, m_pathNodesToGo, &m_nNumPathNodes, + ARRAY_SIZE(m_pathNodesToGo), nil, nil, 999999.9f, -1); + + if (m_nNumPathNodes != 0) { + if (m_nNumPathNodes > 0 && m_pathNodesToGo[0] != m_pCurPathNode) { + for (int i = 0; i < ARRAY_SIZE(m_pathNodesToGo) - 1; i++) { + m_pathNodesToGo[i] = m_pathNodesToGo[i+1]; + } + --m_nNumPathNodes; + } + for (int i = 0; i < m_nNumPathNodes; ++i) { + CVector nodePos = m_pathNodesToGo[i]->GetPosition(); + if (sq(m_followPathAbortDist) > (nodePos - m_followPathDestPos).MagnitudeSqr() + && CWorld::IsWanderPathClear(nodePos, m_followPathDestPos, 0.5f, 4)) { + + m_nNumPathNodes = i + 1; + break; + } + } + + m_nCurPathNodeId = 0; + if (m_pCurPathNode) { + for (int j = 0; j < m_nNumPathNodes; ++j) { + if (m_pathNodesToGo[j] == m_pCurPathNode) { + m_nCurPathNodeId = j; + break; + } + } + } + m_pCurPathNode = m_pathNodesToGo[m_nCurPathNodeId]; + PedState oldLastState = m_nLastPedState; + m_nLastPedState = PED_NONE; + SetStoredState(); + if (m_nLastPedState == PED_NONE) + m_nLastPedState = oldLastState; + + SetPedState(PED_FOLLOW_PATH); + SetMoveState(m_followPathMoveState); + } else { + RestorePreviousState(); + if (m_objective == OBJECTIVE_NONE) { + if (m_followPathMoveState == PEDMOVE_RUN) + SetObjective(OBJECTIVE_RUN_TO_AREA, m_followPathDestPos); + else + SetObjective(OBJECTIVE_GOTO_AREA_ON_FOOT, m_followPathDestPos); + } + SetPedState(PED_NONE); + } + } + return true; +} + +bool +CPed::SetFollowPathDynamic(void) +{ + CVector colBoxMin = m_followPathWalkAroundEnt->GetColModel()->boundingBox.min + CVector(-0.35f, -0.35f, 0.f); + CVector colBoxMax = m_followPathWalkAroundEnt->GetColModel()->boundingBox.max + CVector(0.35f, 0.35f, 0.f); + + CVector colCornerOffsets[4]; // BL, BR, TR, TL + colCornerOffsets[0] = CVector(colBoxMin.x, colBoxMin.y, 0.f); + colCornerOffsets[1] = CVector(colBoxMax.x, colBoxMin.y, 0.f); + colCornerOffsets[2] = CVector(colBoxMax.x, colBoxMax.y, 0.f); + colCornerOffsets[3] = CVector(colBoxMin.x, colBoxMax.y, 0.f); + + if (m_followPathWalkAroundEnt->IsVehicle() && ((CVehicle*)m_followPathWalkAroundEnt)->IsUpsideDown()) { + CVector old0 = colCornerOffsets[0]; + colCornerOffsets[0] = colCornerOffsets[1]; + colCornerOffsets[1] = old0; + CVector old2 = colCornerOffsets[2]; + colCornerOffsets[2] = colCornerOffsets[3]; + colCornerOffsets[3] = old2; + } + + CVector colCornerPos[4]; // global. again BL, BR, TR, TL + float dotProdCorrection[4]; + CVector colBoxPlaneNormal[4]; + + for (int i=0; i<4; i++) { + colCornerPos[i] = m_followPathWalkAroundEnt->GetMatrix() * colCornerOffsets[i]; + colCornerPos[i].z = GetPosition().z; + } + + CVector prevColCorner = colCornerPos[3]; // top left + CVector *curCornerPos; + CVector fwdToNextCorner; + + for (int i=0; i<4; i++) { + curCornerPos = &colCornerPos[i]; + fwdToNextCorner = *curCornerPos - prevColCorner; + fwdToNextCorner.Normalise(); + colBoxPlaneNormal[i] = CrossProduct(fwdToNextCorner, CVector(0.f, 0.f, 1.f)); + dotProdCorrection[i] = -DotProduct(prevColCorner, colBoxPlaneNormal[i]); // yes, dp with global coord, as if in distance to plane calculation + prevColCorner = *curCornerPos; + } + + bool weReGoingGreat = false; + CVector startVecCandidate = GetPosition(); + CVector targetVecCandidate = m_followPathDestPos; + CVector dirToGo = targetVecCandidate - startVecCandidate; + dirToGo.Normalise(); + CVector ourPos = startVecCandidate; + + for (int i=0; i<4; i++) { + CVector curPlaneNormal = colBoxPlaneNormal[i]; + float minusGlobalCornerPos = dotProdCorrection[i]; + float startVecDistToPlane = DotProduct(curPlaneNormal, startVecCandidate) + minusGlobalCornerPos; + +#define FRONT_OF_PLANE 1 +#define ON_THE_PLANE 0 +#define BEHIND_THE_PLANE -1 + + int8 startVecStatus; + int8 targetVecStatus; + + if (startVecDistToPlane > 0.1f) + startVecStatus = FRONT_OF_PLANE; + else if (startVecDistToPlane < -0.1f) + startVecStatus = BEHIND_THE_PLANE; + else + startVecStatus = ON_THE_PLANE; + + float targetVecDistToPlane = DotProduct(curPlaneNormal, targetVecCandidate) + minusGlobalCornerPos; + if (targetVecDistToPlane > 0.1f) + targetVecStatus = FRONT_OF_PLANE; + else if (targetVecDistToPlane < -0.1f) + targetVecStatus = BEHIND_THE_PLANE; + else + targetVecStatus = ON_THE_PLANE; + + + if (startVecStatus == BEHIND_THE_PLANE || targetVecStatus == BEHIND_THE_PLANE) { + if (startVecStatus == BEHIND_THE_PLANE && targetVecStatus == FRONT_OF_PLANE) { + targetVecCandidate = -(DotProduct(ourPos, curPlaneNormal) + minusGlobalCornerPos) / DotProduct(dirToGo, curPlaneNormal) * dirToGo + ourPos; + + } else if (startVecStatus == FRONT_OF_PLANE && targetVecStatus == BEHIND_THE_PLANE) { + startVecCandidate = -(DotProduct(ourPos, curPlaneNormal) + minusGlobalCornerPos) / DotProduct(dirToGo, curPlaneNormal) * dirToGo + ourPos; + } + } else { + weReGoingGreat = true; + if (startVecStatus == ON_THE_PLANE) + startVecCandidate += (0.1f - startVecDistToPlane) * curPlaneNormal; + + if (targetVecStatus == ON_THE_PLANE) + targetVecCandidate += (0.1f - targetVecDistToPlane) * curPlaneNormal; + } +#undef FRONT_OF_PLANE +#undef ON_THE_PLANE +#undef BEHIND_THE_PLANE + } + + if (!weReGoingGreat) { + CVector avgOfColPoints = (colCornerPos[0] + colCornerPos[1] + colCornerPos[2] + colCornerPos[3]) / 4.f; + float radius = 0.0f; + + // Find radius of col box of the entity we follow + for (int i=0; i<4; i++) { + float cornerDist = (colCornerPos[i] - avgOfColPoints).MagnitudeSqr(); + + if (cornerDist > radius) + radius = cornerDist; + } + CColSphere followedEntSphere; + followedEntSphere.Set(Sqrt(radius) * 1.1f, avgOfColPoints, 0, 0); + CVector distToDest = m_followPathDestPos - GetPosition(); + distToDest.z = 0.f; + + if (distToDest.Magnitude() == 0.0f) + return false; + + distToDest.Normalise(); + + // Entity we follow doesn't go toward destination anymore, abort the following. + if (!followedEntSphere.IntersectRay(GetPosition(), distToDest, startVecCandidate, targetVecCandidate)) { + m_pathNodeTimer = 0; + if (m_nPedState == PED_FOLLOW_PATH) + RestorePreviousState(); + + return false; + } + } + + int lastPlaneBehindUs = -1; + int lastPlaneInFrontOfUs = -1; + CVector oldstartVecCandidate = startVecCandidate; + CVector oldDirToGo = targetVecCandidate - startVecCandidate; + oldDirToGo.Normalise(); + + + // At least one plane should be between target and us. + for (int i=0; i<4; i++) { + CVector curPlaneNormal = colBoxPlaneNormal[i]; + float minusGlobalCornerPos = dotProdCorrection[i]; + float startVecDistToPlane = DotProduct(curPlaneNormal, startVecCandidate) + minusGlobalCornerPos; + float targetVecDistToPlane = DotProduct(curPlaneNormal, targetVecCandidate) + minusGlobalCornerPos; + + if (startVecDistToPlane > 0.0f && targetVecDistToPlane < 0.0f) { + lastPlaneInFrontOfUs = i; + startVecCandidate = -(DotProduct(oldstartVecCandidate, curPlaneNormal) + minusGlobalCornerPos) / DotProduct(oldDirToGo, curPlaneNormal) * oldDirToGo + oldstartVecCandidate; + + } else if (startVecDistToPlane < 0.0f && targetVecDistToPlane > 0.0f) { + lastPlaneBehindUs = i; + targetVecCandidate = -(DotProduct(oldstartVecCandidate, curPlaneNormal) + minusGlobalCornerPos) / DotProduct(oldDirToGo, curPlaneNormal) * oldDirToGo + oldstartVecCandidate; + } + } + + CVector destsVariant1[5]; + CVector destsVariant2[5]; + + // If not, followed entity diverged from route and we should abort the following. + if (lastPlaneBehindUs >= 0 && lastPlaneInFrontOfUs >= 0) { + + int planeInFrontCircular = (lastPlaneInFrontOfUs + 4) % -4; + int planeInFrontCircularMinusOne = (lastPlaneInFrontOfUs + 3) % -4; + int planeInBehindCircular = (lastPlaneBehindUs + 4) % -4; + int planeInBehindCircularMinusOne = (lastPlaneBehindUs + 3) % -4; + + destsVariant1[0] = GetPosition(); + destsVariant1[1] = colCornerPos[planeInFrontCircularMinusOne]; + + int destsVar1LastNode = 2; + for(; planeInFrontCircularMinusOne != planeInBehindCircular; destsVar1LastNode++) { + planeInFrontCircularMinusOne = (planeInFrontCircularMinusOne + 3) % -4; + destsVariant1[destsVar1LastNode] = colCornerPos[planeInFrontCircularMinusOne]; + } + destsVariant1[destsVar1LastNode] = m_followPathDestPos; + + destsVariant2[0] = GetPosition(); + destsVariant2[1] = colCornerPos[planeInFrontCircular]; + + int destsVar2LastNode = 2; + for (; planeInFrontCircular != planeInBehindCircularMinusOne; destsVar2LastNode++) { + planeInFrontCircular = (planeInFrontCircular + 5) % -4; + destsVariant2[destsVar2LastNode] = colCornerPos[planeInFrontCircular]; + } + destsVariant2[destsVar2LastNode] = m_followPathDestPos; + CEntity *foundEnt1 = nil; + int dests1isOk = true; + int nodeToStopDestsVar1 = destsVar1LastNode + 1; + CVector avgOfColPoints2 = (colCornerPos[0] + colCornerPos[1] + colCornerPos[2] + colCornerPos[3]) / 4.f; + + CVector prevDestVar1 = destsVariant1[0]; + + for (int i = 1; i < destsVar1LastNode + 1; i++) { + CVector *curDestVar1 = &destsVariant1[i]; + + CVector routeNormalHalf = *curDestVar1 - prevDestVar1; + routeNormalHalf.z = 0.f; + routeNormalHalf.Normalise(); + routeNormalHalf *= 0.5f; + + float oldX = -routeNormalHalf.x; + routeNormalHalf.z = 0.0f; + routeNormalHalf.x = routeNormalHalf.y; + routeNormalHalf.y = oldX; + + if (DotProduct(*curDestVar1 - avgOfColPoints2, routeNormalHalf) < 0.0f) + routeNormalHalf *= -1.f; + + CColPoint foundCol; + bool foundObstacle = CWorld::ProcessLineOfSight(prevDestVar1, *curDestVar1, foundCol, foundEnt1, + true, true, true, true, false, false, false, false); + + if (!foundObstacle) + foundObstacle = CWorld::ProcessLineOfSight(prevDestVar1 + routeNormalHalf, *curDestVar1 + routeNormalHalf, foundCol, foundEnt1, true, true, true, true, false, false, false, false); + + if (foundObstacle) { + if (foundEnt1 == m_followPathWalkAroundEnt || foundEnt1 == this || foundEnt1 == m_pSeekTarget) { + foundEnt1 = nil; + + } else { + if (!foundEnt1->IsPed()) { + dests1isOk = false; + nodeToStopDestsVar1 = i; + break; + } + if (((CPed*)foundEnt1)->m_nPedState == PED_IDLE) { + dests1isOk = false; + nodeToStopDestsVar1 = i; + break; + } + if (DotProduct(*curDestVar1 - prevDestVar1, foundEnt1->GetForward()) < 0.f) { + dests1isOk = false; + nodeToStopDestsVar1 = i; + break; + } + if (((CPed*)foundEnt1)->m_pedInObjective == this) { + dests1isOk = false; + nodeToStopDestsVar1 = i; + break; + } + } + } + prevDestVar1 = *curDestVar1; + } + CEntity *foundEnt2 = nil; + int dests2isOk = true; + int nodeToStopDestsVar2 = destsVar2LastNode + 1; + + CVector prevDestVar2 = destsVariant2[0]; + + for (int i = 1; i < destsVar2LastNode + 1; i++) { + CVector *curDestVar2 = &destsVariant2[i]; + + CVector routeNormalHalf = *curDestVar2 - prevDestVar2; + routeNormalHalf.z = 0.f; + routeNormalHalf.Normalise(); + routeNormalHalf *= 0.5f; + + float oldX = -routeNormalHalf.x; + routeNormalHalf.z = 0.0f; + routeNormalHalf.x = routeNormalHalf.y; + routeNormalHalf.y = oldX; + + if (DotProduct(*curDestVar2 - avgOfColPoints2, routeNormalHalf) < 0.0f) + routeNormalHalf *= -1.f; + + CColPoint foundCol; + bool foundObstacle = CWorld::ProcessLineOfSight(prevDestVar2, *curDestVar2, foundCol, foundEnt2, + true, true, true, true, false, false, false, false); + + if (!foundObstacle) + foundObstacle = CWorld::ProcessLineOfSight(prevDestVar2 + routeNormalHalf, *curDestVar2 + routeNormalHalf, foundCol, foundEnt2, true, true, true, true, false, false, false, false); + + if (foundObstacle) { + if (foundEnt2 == m_followPathWalkAroundEnt || foundEnt2 == this || foundEnt2 == m_pSeekTarget) { + foundEnt2 = 0; + } else { + if (!foundEnt2->IsPed()) { + dests2isOk = false; + nodeToStopDestsVar2 = i; + break; + } + if (((CPed*)foundEnt2)->m_nPedState == PED_IDLE) { + dests2isOk = false; + nodeToStopDestsVar2 = i; + break; + } + if (DotProduct(*curDestVar2 - prevDestVar2, foundEnt2->GetForward()) < 0.f) { + dests2isOk = false; + nodeToStopDestsVar2 = i; + break; + } + if (((CPed*)foundEnt2)->m_pedInObjective == this) { + dests2isOk = false; + nodeToStopDestsVar2 = i; + break; + } + } + } + prevDestVar2 = *curDestVar2; + } + + float destTotalLengthVar1 = 0.0f; + for(int i=0; i < destsVar1LastNode; i++){ + destTotalLengthVar1 += (destsVariant1[i + 1] - destsVariant1[i]).Magnitude(); + } + + float destTotalLengthVar2 = 0.0f; + for (int i = 0; i < destsVar2LastNode; i++) { + destTotalLengthVar2 += (destsVariant2[i + 1] - destsVariant2[i]).Magnitude(); + } + + int destVariantToUse; + if (dests1isOk && dests2isOk) { + if (destTotalLengthVar1 < destTotalLengthVar2) + destVariantToUse = 1; + else + destVariantToUse = 2; + + } else if (dests1isOk) { + destVariantToUse = 1; + + } else if (dests2isOk) { + destVariantToUse = 2; + + } else if (nodeToStopDestsVar1 == 1 && nodeToStopDestsVar2 > 1) { + destVariantToUse = 2; + + } else if (nodeToStopDestsVar1 > 1 && nodeToStopDestsVar2 == 1) { + destVariantToUse = 1; + + } else if (foundEnt1 == foundEnt2) { + if (destTotalLengthVar1 < destTotalLengthVar2) + destVariantToUse = 1; + else + destVariantToUse = 2; + + } else if (foundEnt1->GetColModel()->boundingSphere.radius >= foundEnt2->GetColModel()->boundingSphere.radius) { + destVariantToUse = 2; + } else { + destVariantToUse = 1; + } + + if (destVariantToUse == 1) { + ClearFollowPath(); + for (int i = 1; i < destsVar1LastNode; i++) { + CPathNode* nextNode = &m_pathNodeObjPool[m_nNumPathNodes]; + nextNode->SetPosition(destsVariant1[i]); + m_pathNodesToGo[m_nNumPathNodes++] = nextNode; + } + } else if (destVariantToUse == 2) { + ClearFollowPath(); + for (int i = 1; i < destsVar2LastNode; i++) { + CPathNode *nextNode = &m_pathNodeObjPool[m_nNumPathNodes]; + nextNode->SetPosition(destsVariant2[i]); + m_pathNodesToGo[m_nNumPathNodes++] = nextNode; + } + } + if (m_nNumPathNodes != 0) { + PedState oldLastState = m_nLastPedState; + m_nLastPedState = PED_NONE; + SetStoredState(); + if (m_nLastPedState == PED_NONE) + m_nLastPedState = oldLastState; + + SetPedState(PED_FOLLOW_PATH); + SetMoveState(m_followPathMoveState); + return true; + + } else { + m_pathNodeTimer = 0; + if (m_nPedState == PED_FOLLOW_PATH) + RestorePreviousState(); + + return false; + } + } else { + m_pathNodeTimer = 0; + if (m_nPedState == PED_FOLLOW_PATH) + RestorePreviousState(); + + return false; + } +} + +void +CPed::ClearFollowPath() +{ + for (int i = 0; i < ARRAY_SIZE(m_pathNodesToGo); i++) { + m_pathNodesToGo[i] = nil; + } + m_nNumPathNodes = 0; + m_nCurPathNodeId = 0; +} + +void +CPed::FollowPath(void) +{ + m_pCurPathNode = m_pathNodesToGo[m_nCurPathNodeId]; + if (m_pathNodeTimer > 0 && CTimer::GetTimeInMilliseconds() > m_pathNodeTimer) { + RestorePreviousState(); + ClearFollowPath(); + m_pathNodeTimer = 0; + } else { + if (m_pathNodesToGo[m_nCurPathNodeId]) { + m_vecSeekPos.x = m_pathNodesToGo[m_nCurPathNodeId]->GetPosition().x; + m_vecSeekPos.y = m_pathNodesToGo[m_nCurPathNodeId]->GetPosition().y; + m_vecSeekPos.z = GetPosition().z; + + if (Seek()) { + if (m_nCurPathNodeId == m_nNumPathNodes) { + RestorePreviousState(); + ClearFollowPath(); + SetFollowPath(m_followPathDestPos, m_followPathAbortDist, m_followPathMoveState, m_followPathWalkAroundEnt, + m_followPathTargetEnt, m_pathNodeTimer - CTimer::GetTimeInMilliseconds()); + } + } + } else { + RestorePreviousState(); + ClearFollowPath(); + m_pathNodeTimer = 0; + } + } +} + +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 select dive direction randomly. 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; + } + + // VC's primitve solution to dive direction problem, see above for better one. This part doesn't exist on III at all +#ifndef FIX_BUGS + angleToFace += HALFPI; + if (CGeneral::GetRandomNumber() & 1) + angleToFace -= PI; +#endif + 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); + } + } +} + +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) +{ + if (m_attractor) + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + CPlayerPed *player = FindPlayerPed(); + if (player == this) { + if (!player->m_bCanBeDamaged) + return; + } + + m_threatEntity = nil; + if (DyingOrDead()) + return; + + CAnimBlendAssociation *dieAssoc = nil; + 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() && (!m_pMyVehicle || !m_pMyVehicle->IsBike())) + 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 { + 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(); + if (!CGame::nastyGame && animId == ANIM_STD_HIT_FLOOR) { + if (dieAssoc) { + dieAssoc->SetCurrentTime(dieAssoc->hierarchy->totalLength - 0.01f); + dieAssoc->SetRun(); + } + } +} + +void +CPed::FinishDieAnimCB(CAnimBlendAssociation *animAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + + if (ped->bIsPedDieAnimPlaying) + ped->bIsPedDieAnimPlaying = false; +} + +void +CPed::SetDead(void) +{ + if (!RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DROWN)) + 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()) { + RemoveWeaponAnims(0, -1000.0f); + 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) { + m_nLastPedState = PED_NONE; + SetStoredState(); + } + + SetPedState(PED_CHAT); + SetMoveState(PEDMOVE_STILL); + m_lookTimer = 0; + 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(); + m_chatTimer = CTimer::GetTimeInMilliseconds() + 30000; + 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(); + if (m_objective == OBJECTIVE_BUY_ICE_CREAM) { + bBoughtIceCream = true; + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumberInRange(0, 8)); + } +} + +bool +CPed::FacePhone(void) +{ + // FIX: This function was broken since it's left unused early in development. +#ifdef FIX_BUGS + 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); + bool turnDone = TurnBody(); + if (turnDone) { + SetIdle(); + ClearLookFlag(); + m_phoneTalkTimer = CTimer::GetTimeInMilliseconds() + 10000; + } + return turnDone; +#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) +{ + if (CTimer::GetTimeInMilliseconds() <= m_phoneTalkTimer) + return false; + + SetIdle(); + gPhoneInfo.m_aPhones[m_phoneId].m_nState = PHONE_STATE_FREE; + m_phoneId = -1; + return true; +} + +void +StartTalkingOnMobileCB(CAnimBlendAssociation* assoc, void* arg) +{ + CPed* ped = (CPed*)arg; + if (ped->m_nPedState == PED_ANSWER_MOBILE) + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_TALK, 4.0f); +} + +void +FinishTalkingOnMobileCB(CAnimBlendAssociation *assoc, void *arg) +{ + CPed *ped = (CPed*)arg; + if (ped->m_storedWeapon != WEAPONTYPE_UNIDENTIFIED) { + ped->RemoveWeaponModel(MI_MOBILE); + ped->SetCurrentWeapon(ped->m_storedWeapon); + ped->m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + } + ped->m_lookTimer = 0; +} + +void +CPed::SetAnswerMobile(void) +{ + if (m_nPedState != PED_ANSWER_MOBILE && !DyingOrDead()) { + SetPedState(PED_ANSWER_MOBILE); +#ifdef FIX_BUGS + ClearLookFlag(); +#endif + RemoveWeaponAnims(GetWeapon()->m_eWeaponType, -4.0f); + CAnimBlendAssociation *assoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_IN, 4.0f); + assoc->SetFinishCallback(StartTalkingOnMobileCB, this); + m_lookTimer = INT32_MAX; + if (m_storedWeapon == WEAPONTYPE_UNIDENTIFIED) + m_storedWeapon = GetWeapon()->m_eWeaponType; + +#ifdef FIX_BUGS + SetCurrentWeapon(0); +#endif + RemoveWeaponModel(-1); + } +} + +void +CPed::ClearAnswerMobile(void) +{ + if (m_nLastPedState == PED_ANSWER_MOBILE) + m_nLastPedState = PED_NONE; + + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PHONE_TALK)) { + CAnimBlendAssociation *assoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_OUT, 8.0f); + assoc->SetFinishCallback(FinishTalkingOnMobileCB, this); + } else + FinishTalkingOnMobileCB(nil, this); + + if (m_nPedState == PED_ANSWER_MOBILE) { + m_nPedState = PED_IDLE; + RestorePreviousState(); + m_pVehicleAnim = nil; + } +} + +void +CPed::AnswerMobile(void) +{ + if (!IsPedInControl()) + return; + + CAnimBlendAssociation *phoneInAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PHONE_IN); + CAnimBlendAssociation *phoneOutAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PHONE_OUT); + CAnimBlendAssociation *phoneTalkAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PHONE_TALK); + if (phoneInAssoc || phoneTalkAssoc || phoneOutAssoc) { + if (phoneInAssoc) { + if (phoneInAssoc->currentTime >= 0.85f && !m_pWeaponModel) { + CBaseModelInfo *phoneModel = CModelInfo::GetModelInfo(MI_MOBILE); + m_pWeaponModel = (RpAtomic*)phoneModel->CreateInstance(); + phoneModel->AddRef(); + m_wepModelID = MI_MOBILE; + + // They copied AddWeaponModel and forgot that here + // bool unused = IsPlayer(); + } + } else if (phoneOutAssoc) { + if (phoneOutAssoc->currentTime >= 0.5f && phoneOutAssoc->currentTime - phoneOutAssoc->timeStep < 0.5f) { + RemoveWeaponModel(MI_MOBILE); + SetCurrentWeapon(m_storedWeapon); + m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + } + } + } else { + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_PHONE_TALK, 4.0f); + } +} + +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; + + if (!CanSetPedState() || m_nPedState == PED_DRIVING) + return; + + 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 (!vehToSeek->IsBike() && 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(); + + 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); + 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.6f + GetPosition().z > dest.z && GetPosition().z - 0.5f < dest.z) { +#ifdef GTA_TRAIN + if (vehToSeek->IsTrain()) { + SetEnterTrain(vehToSeek, m_vehDoor); + } else +#endif + { + 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->IsBike()) { + if ((!m_leader || m_leader != vehToSeek->pDriver) && + ((m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_WINDSCREEN) && vehToSeek->pDriver || + (m_vehDoor == CAR_DOOR_LR || m_vehDoor == CAR_DOOR_RR) && vehToSeek->pPassengers[0])) { + SetCarJack(vehToSeek); + } else { + SetEnterCar(vehToSeek, m_vehDoor); + } + } else 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 (vehToSeek->IsBike()) { + if ((m_vehDoor == CAR_DOOR_LR || m_vehDoor == CAR_DOOR_RR) && vehToSeek->pPassengers[0]) { + if (vehToSeek->pPassengers[0]->bDontDragMeOutCar) { + if (IsPlayer()) + SetEnterCar(vehToSeek, m_vehDoor); + } else { + SetCarJack(vehToSeek); + } + } else { + SetEnterCar(vehToSeek, m_vehDoor); + } + } else 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) +{ + int32 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) +{ + int32 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) +{ + int32 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 +{ +#if 0 + return m_nPedType == PEDTYPE_PLAYER1; // Original +#else + // We still have those in enum, so let's also check for them. + return m_nPedType == PEDTYPE_PLAYER1 || m_nPedType == PEDTYPE_PLAYER2 || + m_nPedType == PEDTYPE_PLAYER3 || m_nPedType == PEDTYPE_PLAYER4; +#endif +} + +bool +CPed::IsGangMember(void) const +{ + return m_nPedType >= PEDTYPE_GANG1 && m_nPedType <= PEDTYPE_GANG9; +} + +bool +IsPedPointerValid(CPed* pPed) +{ + if (!IsPedPointerValid_NotInWorld(pPed)) + return false; + if (pPed->bInVehicle && pPed->m_pMyVehicle) + return IsEntityPointerValid(pPed->m_pMyVehicle); + return pPed->m_entryInfoList.first || pPed == FindPlayerPed(); +} + +bool +IsPedPointerValid_NotInWorld(CPed* pPed) +{ + if (!pPed) + return false; + int index = CPools::GetPedPool()->GetJustIndex_NoFreeAssert(pPed); +#ifdef FIX_BUGS + if (index < 0 || index >= NUMPEDS) +#else + if (index < 0 || index > NUMPEDS) +#endif + return false; + return true; +} + +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) +{ + bool notYet = false; + if (CReplay::IsPlayingBack()) + return; + + if (bChangedSeat) { + if (m_pMyVehicle->IsBike()) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_JUMPON_LHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_JUMPON_RHS) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_KICK)) { + LineUpPedWithCar(LINE_UP_TO_CAR_START); + return; + } + bChangedSeat = false; + } else { + 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) + || RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_JUMP_IN_LO_LHS)) { + 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->IsBike()) + seatPos.x = -seatPos.x; + + } else if (m_pMyVehicle->pPassengers[0] == this) { + seatPos = m_pMyVehicle->IsBike() ? vehModel->m_positions[CAR_POS_BACKSEAT]: 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(); + } + } + if (m_pMyVehicle->IsBike()) { + ((CBike*)m_pMyVehicle)->CalculateLeanMatrix(); + newMat = ((CBike*)m_pMyVehicle)->m_leanMatrix; + } + 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); + tempMat.Translate(0.0f, 0.6f, 0.0f); + 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 + (m_nPedType != PEDTYPE_CIVMALE) && !IsFemale() && (m_nPedType != PEDTYPE_CRIMINAL) && !IsGangMember() +#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 + && ((IsFemale() && !nearPed->IsFemale()) || (!IsFemale() && nearPed->IsFemale()))) { +#else + && nearPed->m_nPedType == PEDTYPE_CIVFEMALE) { +#endif + + SetLookFlag(nearPed, true); + m_lookTimer = CTimer::GetTimeInMilliseconds() + 4000; +#ifdef FIX_BUGS + Say(IsFemale() ? SOUND_PED_CHAT_SEXY_FEMALE : SOUND_PED_CHAT_SEXY_MALE); +#else + Say(SOUND_PED_CHAT_SEXY_MALE); +#endif + 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 +PlayRandomAnimationsFromAnimBlock(CPed* ped, AssocGroupId animGroup, uint32 first, uint32 amount) +{ + if (!ped->IsPedInControl()) + return; + + const char *groupName = CAnimManager::GetAnimGroupName(animGroup); + CAnimBlock *animBlock = CAnimManager::GetAnimationBlock(groupName); + CAnimBlendAssociation *assoc; + for (assoc = RpAnimBlendClumpGetFirstAssociation(ped->GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + int first = animBlock->firstIndex; + int index = assoc->hierarchy - CAnimManager::GetAnimation(0); + if (index >= first && index < first + animBlock->numAnims) { + break; + } + } + + if (CTimer::GetTimeInMilliseconds() > ped->m_nWaitTimer && assoc) + assoc->flags &= ~ASSOC_REPEAT; + + if (!assoc || assoc->blendDelta < 0.0f) { + int selectedAnimOffset; + do + selectedAnimOffset = CGeneral::GetRandomNumberInRange(0, (int32)amount); + while (assoc && first + selectedAnimOffset == assoc->animId); + + assoc = CAnimManager::BlendAnimation(ped->GetClump(), animGroup, (AnimationId)(first + selectedAnimOffset), 3.0f); + + assoc->SetFinishCallback(CPed::FinishedWaitCB, ped); + if (assoc->flags & ASSOC_REPEAT) + ped->m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(3000, 8000); + else + ped->m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 8000; + } +} + +void +CPed::ClearWaitState(void) +{ + CAnimBlendAssociation *assoc; + switch (m_nWaitState) { + case WAITSTATE_PLAYANIM_CHAT: + case WAITSTATE_SIT_DOWN: + case WAITSTATE_SIT_DOWN_RVRS: + case WAITSTATE_SIT_UP: + case WAITSTATE_SIT_IDLE: + case WAITSTATE_USE_ATM: + if (CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) { + if (m_nWaitState == WAITSTATE_USE_ATM) { + assoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ATM); + if (assoc) + assoc->blendDelta = -8.0f; + if (m_attractor) + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + + } else if (m_nWaitState == WAITSTATE_PLAYANIM_CHAT) { + assoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CHAT); + if (assoc) + assoc->blendDelta = -8.0f; + if (m_attractor) + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + + } else if (m_nWaitState == WAITSTATE_SIT_DOWN || m_nWaitState == WAITSTATE_SIT_DOWN_RVRS || m_nWaitState == WAITSTATE_SIT_IDLE || m_nWaitState == WAITSTATE_SIT_UP) { + switch (m_nWaitState) { + case WAITSTATE_SIT_DOWN: + assoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_SEAT_DOWN); + if (assoc) + assoc->blendDelta = -8.0f; + break; + case WAITSTATE_SIT_IDLE: + assoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_SEAT_IDLE); + if (assoc) + assoc->blendDelta = -8.0f; + break; + case WAITSTATE_SIT_UP: + assoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_SEAT_UP); + if (assoc) + assoc->blendDelta = -8.0f; + break; + default: + break; + } + if (m_attractor) + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + } + } + break; + case WAITSTATE_RIOT: + { + CAnimBlock* riotAnimBlock = CAnimManager::GetAnimationBlock("riot"); + + for (assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + int first = riotAnimBlock->firstIndex; + int index = assoc->hierarchy - CAnimManager::GetAnimation(0); + if (index >= first && index < first + riotAnimBlock->numAnims) { + assoc->blendDelta = -1000.0f; + } + } + break; + } + case WAITSTATE_FAST_FALL: + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HIGHIMPACT_FRONT)) + SetGetUp(); + + break; + case WAITSTATE_BOMBER: + assoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DETONATE); + if (assoc) + assoc->blendDelta = -8.0f; + break; + case WAITSTATE_STRIPPER: + { + CAnimBlock* stripAnimBlock = CAnimManager::GetAnimationBlock("strip"); + + for (assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + int first = stripAnimBlock->firstIndex; + int index = assoc->hierarchy - CAnimManager::GetAnimation(0); + if (index >= first && index < first + stripAnimBlock->numAnims) { + assoc->blendDelta = -1000.0f; + } + } + break; + } + case WAITSTATE_LANCESITTING: + assoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_SUNBATHE_IDLE); + if (assoc) + assoc->blendDelta = -8.0f; + break; + case WAITSTATE_PLAYANIM_HANDSUP_SIMPLE: + assoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_HANDSUP); + if (assoc) + assoc->blendDelta = -8.0f; + break; + default: + break; + } + m_nWaitState = WAITSTATE_FALSE; +} + +void +CPed::SetWaitState(eWaitState state, void *time) +{ + AnimationId waitAnim = ANIM_STD_NUM; + CAnimBlendAssociation *animAssoc; + + if (!IsPedInControl()) + return; + + if (m_nWaitState == WAITSTATE_RIOT && state != WAITSTATE_FALSE) + 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: + case WAITSTATE_RIOT: + case WAITSTATE_STRIPPER: + 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); + 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 + + // Random char as passenger? Cop, medic etc.? + 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; + case WAITSTATE_SIT_DOWN: + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_SEAT_DOWN, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 100000; + break; + case WAITSTATE_SIT_UP: + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_SEAT_UP, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 100000; + break; + case WAITSTATE_SIT_IDLE: + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_SEAT_IDLE, 128.f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + if (time) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(25000, 30000); + break; + case WAITSTATE_USE_ATM: + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ATM, 4.0f); + animAssoc->SetFinishCallback(FinishedWaitCB, this); + if (time) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 100000; + break; + case WAITSTATE_SUN_BATHE_IDLE: + m_headingRate = 0.0f; + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_SUNBATHE, ANIM_SUNBATHE_IDLE, 4.0f); + animAssoc->SetDeleteCallback(DeleteSunbatheIdleAnimCB, this); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(50000, 100000); + break; + case WAITSTATE_FAST_FALL: + SetFall(-1, ANIM_STD_HIGHIMPACT_FRONT, true); + break; + case WAITSTATE_BOMBER: + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_DETONATE, 4.0f); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + break; + case WAITSTATE_GROUND_ATTACK: + { + CWeaponInfo* currentWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + if (!currentWeapon) + break; + if (GetFireAnimGround(currentWeapon, false)) { + if (!RpAnimBlendClumpGetAssociation(GetClump(), GetFireAnimGround(currentWeapon, false))) { + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 5000; + CAnimBlendAssociation* newAnim = CAnimManager::BlendAnimation(GetClump(), + currentWeapon->m_AnimToPlay, GetFireAnimGround(currentWeapon, false), 8.0f); + newAnim->SetDeleteCallback(FinishedWaitCB, this); + } + } + break; + } + case WAITSTATE_LANCESITTING: + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_LANCE, ANIM_SUNBATHE_IDLE, 4.0f); + break; + case WAITSTATE_PLAYANIM_HANDSUP_SIMPLE: + animAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_HANDSUP, 4.0f); + animAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->SetDeleteCallback(FinishedWaitCB, this); + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + *(int*)time; + break; + default: + ClearWaitState(); + RestoreHeadingRate(); + return; + } + m_nWaitState = state; +} + +void +CPed::Wait(void) +{ + AnimationId mustHaveAnim = ANIM_STD_NUM; + CAnimBlendAssociation *animAssoc; + CPed *pedWeLook; + + if (DyingOrDead()) { + ClearWaitState(); + RestoreHeadingRate(); + return; + } + + switch (m_nWaitState) { + + case WAITSTATE_TRAFFIC_LIGHTS: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (CTrafficLights::LightForPeds() == PED_LIGHTS_WALK) { + ClearWaitState(); + SetMoveState(PEDMOVE_WALK); + } + } + break; + + case WAITSTATE_CROSS_ROAD: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (CGeneral::GetRandomNumber() & 1 || !m_nWaitTimer) + ClearWaitState(); + 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) { + ClearWaitState(); + 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 { + ClearWaitState(); + SetMoveState(PEDMOVE_WALK); + } + break; + + case WAITSTATE_HITWALL: + if (CTimer::GetTimeInMilliseconds() <= m_nWaitTimer) { + if (m_collidingThingTimer > CTimer::GetTimeInMilliseconds()) { + m_collidingThingTimer = CTimer::GetTimeInMilliseconds() + 2500; + } + } else { + ClearWaitState(); + } + break; + + case WAITSTATE_TURN180: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + ClearWaitState(); + 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 { + ClearWaitState(); + } + } + 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); + ClearWaitState(); + 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) { + ClearWaitState(); + 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) { + + if (pedWeLook) + TurnBody(); + } else { + ClearWaitState(); + m_nWaitTimer = 0; + if (m_pLookTarget && m_pLookTarget->IsPed()) { + if (m_nPedState != PED_FLEE_ENTITY && m_nPedState != PED_ATTACK) { + if (bCrouchWhenScared) { + if (bIsDucking) { + ClearDuck(false); + SetDuck(10000, true); + } + + } else if (m_pedStats->m_fear <= 100 - pedWeLook->m_pedStats->m_temper) { + if (GetWeapon()->IsTypeMelee()) { + if(m_pedStats->m_flags & STAT_GUN_PANIC) { + 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); + } + } else { + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumberInRange(0.0f, 8.0f)); + } + } 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; + } + if (m_attractor && m_objective == OBJECTIVE_WAIT_ON_FOOT_AT_ICE_CREAM_VAN) { + GetPedAttractorManager()->BroadcastDeparture(this, m_attractor); + bBoughtIceCream = true; + } + ClearWaitState(); + } 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) { + + if (m_pLookTarget) + m_pLookTarget->CleanUpOldReference(&m_pLookTarget); + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + } + } + 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; + ClearWaitState(); + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &timer); + } + } else { + ClearWaitState(); + } + break; + case WAITSTATE_SIT_DOWN: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + ClearWaitState(); + SetWaitState(WAITSTATE_SIT_IDLE, 0); + } + break; + //case WAITSTATE_SIT_DOWN_RVRS: + case WAITSTATE_SIT_UP: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (m_attractor) + GetPedAttractorManager()->BroadcastDeparture(this, m_attractor); + ClearWaitState(); + if (bFleeWhenStanding) { + if (m_threatEx) { + SetFlee(m_threatEx, 10000); + bFleeWhenStanding = false; + m_threatEx = nil; + Say(SOUND_PED_FLEE_SPRINT); + } + } + } + break; + case WAITSTATE_SIT_IDLE: + if (bTurnedAroundOnAttractor) { + m_fRotationCur += PI; + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + m_fRotationDest = m_fRotationCur; + bTurnedAroundOnAttractor = false; + } + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + ClearWaitState(); + SetWaitState(WAITSTATE_SIT_UP, 0); + } else { + if (m_fleeFrom && m_fleeFrom->IsVehicle()) { + m_pNextPathNode = nil; + m_threatEx = m_threatEntity; + bFleeWhenStanding = true; + ClearWaitState(); + SetWaitState(WAITSTATE_SIT_UP, 0); + } else { + uint32 threatFlag = ScanForThreats(); + if (threatFlag == PED_FLAG_GUN || threatFlag == PED_FLAG_EXPLOSION || threatFlag == PED_FLAG_DEADPEDS) { + m_pNextPathNode = nil; + m_threatEx = m_threatEntity; + bFleeWhenStanding = true; + ClearWaitState(); + SetWaitState(WAITSTATE_SIT_UP, 0); + } + } + } + break; + case WAITSTATE_USE_ATM: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + if (m_attractor) + GetPedAttractorManager()->BroadcastDeparture(this, m_attractor); + ClearWaitState(); + } + break; + case WAITSTATE_SUN_BATHE_IDLE: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer && bCanGiveUpSunbathing) { + m_pNextPathNode = nil; + bGotUpOfMyOwnAccord = true; + SetGetUp(); + ClearWaitState(); + + } else if (CWeather::Rain <= 0.1f) { + if (CClock::GetHours() <= 18 || CGeneral::GetRandomNumberInRange(0.f, 1.0f) < 0.005f) { + uint32 threatFlag = ScanForThreats(); + if (threatFlag == PED_FLAG_GUN || threatFlag == PED_FLAG_EXPLOSION || threatFlag == PED_FLAG_DEADPEDS) { + // Get up in case of danger + m_pNextPathNode = nil; + m_threatEx = m_threatEntity; + bFleeWhenStanding = true; + SetGetUp(); + ClearWaitState(); + } + CPlayerPed *player = FindPlayerPed(); + if (player) { + // Get up if player coming towards us with a car + if (player->InVehicle()){ + CVector vehSpeedPerSec = player->m_pMyVehicle->m_vecMoveSpeed * GAME_SPEED_TO_METERS_PER_SECOND; + CVector vehPos = player->m_pMyVehicle->GetPosition(); + CVector ourPos = GetPosition(); + float timeUntilVehReachPed = DotProduct(ourPos - vehPos, vehSpeedPerSec) / vehSpeedPerSec.MagnitudeSqr(); + if (timeUntilVehReachPed > 0.0 && timeUntilVehReachPed < 8.0f) { + if ((ourPos - (timeUntilVehReachPed * vehSpeedPerSec + vehPos)).Magnitude() < 5.0f) { + m_pNextPathNode = nil; + m_threatEx = player; + bFleeWhenStanding = true; + SetGetUp(); + ClearWaitState(); + } + } + } + } + } else { + m_pNextPathNode = nil; + bGotUpOfMyOwnAccord = true; + SetGetUp(); + ClearWaitState(); + } + } else { + m_pNextPathNode = nil; + bGotUpOfMyOwnAccord = true; + SetGetUp(); + ClearWaitState(); + } + break; + case WAITSTATE_RIOT: + if (m_nPedState == PED_FLEE_ENTITY || m_nPedState == PED_ATTACK) { + ClearWaitState(); + break; + } + + PlayRandomAnimationsFromAnimBlock(this, ASSOCGRP_RIOT, ANIM_RIOT_ANGRY, ANIM_RIOT_FUCKYOU - ANIM_RIOT_ANGRY + 1); + if (IsPedInControl() && CGeneral::GetRandomNumberInRange(0.f,1.f) < 0.25f + && CPopulation::CanJeerAtStripper(m_modelIndex)) { + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed) { + if ((GetPosition() - nearPed->GetPosition()).MagnitudeSqr() < sq(10.f)) { + for (int anim = ANIM_STRIP_A; anim <= ANIM_STRIP_G; anim++) { + if (RpAnimBlendClumpGetAssociation(nearPed->GetClump(), anim)) + Say(SOUND_PED_JEER); + } + } + } + } + } + break; + case WAITSTATE_BOMBER: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) + ClearWaitState(); + break; + case WAITSTATE_STRIPPER: + PlayRandomAnimationsFromAnimBlock(this, ASSOCGRP_STRIP, ANIM_STRIP_A, ANIM_STRIP_G - ANIM_STRIP_A + 1); + break; + case WAITSTATE_PLAYANIM_HANDSUP_SIMPLE: + if (CTimer::GetTimeInMilliseconds() > m_nWaitTimer) + ClearWaitState(); + break; + default: + break; + } + + if(!m_nWaitState) + RestoreHeadingRate(); +} + +void +CPed::DeleteSunbatheIdleAnimCB(CAnimBlendAssociation *assoc, void *arg) +{ + CPed *ped = (CPed*) arg; + + if (CTimer::GetTimeInMilliseconds() <= ped->m_nWaitTimer + && !ped->bGotUpOfMyOwnAccord && !ped->bFleeWhenStanding && !ped->m_threatEx) { + ped->m_pNextPathNode = nil; + ped->bFleeWhenStanding = true; + ped->m_threatEx = FindPlayerPed(); + ped->SetGetUp(); + ped->ClearWaitState(); + } + ped->m_nWaitTimer = 0; + ped->RestoreHeadingRate(); + ped->Wait(); +} + +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)->RestoreHeadingRate(); +} + +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); + Say(SOUND_PED_SOLICIT); + if (FindPlayerVehicle() == m_carInObjective) { + FindPlayerPed()->Say(SOUND_PED_SOLICIT); + } + 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 += TWOPI; + } else if (m_fRotationDest > TWOPI) { + 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); + Say(SOUND_PED_SOLICIT); + } +} + +void +CPed::SetBuyIceCream(void) +{ + if (m_nPedState == PED_BUY_ICECREAM || !IsPedInControl()) + return; + + if (!m_carInObjective) + return; + + SetPedState(PED_BUY_ICECREAM); +} + +void +CPed::BuyIceCream(void) +{ + if (m_carInObjective) { + CPed *driver = m_carInObjective->pDriver; + if (driver && CTimer::GetTimeInMilliseconds() > m_chatTimer) { + SetChat(driver, 8000); + driver->SetChat(this, 8000); + return; + } + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumberInRange(0, 8)); + } else { + SetObjective(OBJECTIVE_NONE); + SetWanderPath(CGeneral::GetRandomNumberInRange(0, 8)); + } +} + +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->bIsLeader = true; + m_leader->RegisterReference((CEntity**)&m_leader); + } +} + +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 = 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); +} + +void +CPed::SetJump(void) +{ + if (!bInVehicle && m_nPedState != PED_JUMP && !RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_JUMP_LAUNCH) && + (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.09f * ped->GetForward() + ped->GetPosition()); + forward.z += CModelInfo::GetColModel(ped->GetModelIndex())->spheres[2].center.z + 0.35f; + + 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 && CCullZones::CamStairsForPlayer() && CCullZones::FindZoneWithStairsAttributeForPlayer()) + obstacle = ped; + + if (obstacle) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + CAnimBlendAssociation *handsCoverAssoc = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_WALL, 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() || ped->m_pedInObjective && ped->m_pedInObjective->IsPlayer()) + ped->ApplyMoveForce(0.0f, 0.0f, 8.5f); + else + ped->ApplyMoveForce(0.0f, 0.0f, 4.5f); + + if (sq(velocityFromAnim) > ped->m_vecMoveSpeed.MagnitudeSqr2D() || ped->m_pCurrentPhysSurface) { + +#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); + } + + if (ped->m_pCurrentPhysSurface) { + ped->m_vecMoveSpeed.x += ped->m_pCurrentPhysSurface->m_vecMoveSpeed.x; + ped->m_vecMoveSpeed.y += ped->m_pCurrentPhysSurface->m_vecMoveSpeed.y; + } + } + + 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; +} +void +CPed::SetRadioStation(void) +{ + CPedModelInfo* modelInfo = (CPedModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + if (IsPlayer() || !m_pMyVehicle || m_pMyVehicle->pDriver != this) + return; + + if (GetModelIndex() != MI_PGA && GetModelIndex() != MI_PGB) { + if (m_pMyVehicle->m_nRadioStation != modelInfo->radio1 && m_pMyVehicle->m_nRadioStation != modelInfo->radio2) { + if (CGeneral::GetRandomTrueFalse()) + m_pMyVehicle->m_nRadioStation = modelInfo->radio1; + else + m_pMyVehicle->m_nRadioStation = modelInfo->radio2; + } + } else { + m_pMyVehicle->m_nRadioStation = DMAudio.GetFavouriteRadioStation(); + } +} + +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) { + if (car->IsBike() && !car->pPassengers[0]) { + car->pPassengers[0] = this; + car->pPassengers[0]->RegisterReference((CEntity**) &car->pPassengers[0]); + } + 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); + } + + RpAnimBlendClumpSetBlendDeltas(GetClump(), ASSOC_PARTIAL, -1000.0f); + + AddInCarAnims(car, car->pDriver == this); + RemoveWeaponWhenEnteringVehicle(); + + if (car->bIsBus) + bRenderPedInCar = false; + + bChangedSeat = true; +} + +bool +CPed::HasAttractor(void) +{ + return m_attractor != nil; +} + +void +CPed::SetNewAttraction(CPedAttractor* pAttractor, const CVector& pos, float heading, float time, int qid) +{ + if (!m_attractor) + m_attractor = pAttractor; + if (m_attractor != pAttractor) + return; + switch (pAttractor->GetEffect()->pedattr.type) { + case ATTRACTOR_ATM: SetObjective(OBJECTIVE_GOTO_ATM_ON_FOOT, heading, pos); break; + case ATTRACTOR_SEAT: SetObjective(OBJECTIVE_GOTO_SEAT_ON_FOOT, heading, pos); break; + case ATTRACTOR_STOP: SetObjective(OBJECTIVE_GOTO_BUS_STOP_ON_FOOT, heading, pos); break; + case ATTRACTOR_PIZZA: SetObjective(OBJECTIVE_GOTO_PIZZA_ON_FOOT, heading, pos); break; + case ATTRACTOR_SHELTER: SetObjective(OBJECTIVE_GOTO_SHELTER_ON_FOOT, heading, pos); break; + case ATTRACTOR_ICECREAM: SetObjective(OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT, heading, pos); break; + default: return; + } + SetObjectiveTimer(time); + m_positionInQueue = qid; +} + +void +CPed::AttachPedToEntity(CEntity *ent, CVector offset, uint16 type, float rot, eWeaponType weapon) +{ + if (!ent || bInVehicle) + return; + + m_attachedTo = ent; + m_attachedTo->RegisterReference(&m_attachedTo); + m_vecAttachOffset = offset; + m_attachType = type; + m_attachRotStep = rot; + if (IsPlayer()) { + bUsesCollision = false; + } else if (ent->IsVehicle()) { + m_pCollidingEntity = ent; + } + + if (IsPlayer()) { + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; + } + SetStoredState(); + SetPedState(PED_IDLE); + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 1000.0f); + + if (m_storedWeapon == WEAPONTYPE_UNIDENTIFIED) { + m_storedWeapon = GetWeapon()->m_eWeaponType; + m_attachWepAmmo = GetWeapon()->m_nAmmoTotal; + } + if (IsPlayer()) { + GiveWeapon(weapon, 30000, 1); +#ifndef FIX_BUGS + ((CPlayerPed*)this)->m_nSelectedWepSlot = weapon; +#else + ((CPlayerPed*)this)->m_nSelectedWepSlot = GetWeaponSlot(weapon); +#endif + ((CPlayerPed*)this)->MakeChangesForNewWeapon(weapon); + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_HELICANNON_1STPERSON, 0, 0); + SetPedState(PED_SNIPER_MODE); + } else { + GiveWeapon(weapon, 30000, true); + SetCurrentWeapon(weapon); + } + + PositionAttachedPed(); +} + +void +CPed::DettachPedFromEntity(void) +{ + CEntity* pVehicleAttachedTo = m_attachedTo; + m_attachedTo = nil; + if (m_nPedState == PED_DIE) { + m_pCollidingEntity = pVehicleAttachedTo; + ApplyMoveForce(pVehicleAttachedTo->GetForward() * -4.0f + CVector(0.0f, 0.0f, 4.0f)); + bIsStanding = false; + } else if (m_nPedState != PED_DEAD) { + RestorePreviousState(); + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 1000.0f); + bUsesCollision = true; + if (m_storedWeapon != WEAPONTYPE_UNIDENTIFIED) { + GetWeapon()->m_nAmmoInClip = 0; + GetWeapon()->m_nAmmoTotal = 0; + SetCurrentWeapon(m_storedWeapon); + GetWeapon()->m_nAmmoTotal = m_attachWepAmmo; + m_storedWeapon = WEAPONTYPE_UNIDENTIFIED; + } + } +} + +void +CPed::PositionAttachedPed() +{ + if(!m_attachedTo) + return; + + CMatrix rotMatrix, targetMat; + targetMat = m_attachedTo->GetMatrix(); + targetMat.GetPosition() += Multiply3x3(m_attachedTo->GetMatrix(), m_vecAttachOffset); + float objAngle = m_attachedTo->GetForward().Heading(); + + if (!IsPlayer()) { + float targetAngle = objAngle; + switch (m_attachType) { + case 1: + targetAngle += HALFPI; + break; + case 2: + targetAngle += PI; + break; + case 3: + targetAngle -= HALFPI; + break; + default: + break; + } + targetAngle = CGeneral::LimitRadianAngle(targetAngle); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + float neededTurn = m_fRotationCur - targetAngle; + + if (neededTurn > PI) + neededTurn -= TWOPI; + else if (neededTurn < -PI) + neededTurn += TWOPI; + + if (neededTurn > m_attachRotStep) + m_fRotationCur = CGeneral::LimitRadianAngle(targetAngle + m_attachRotStep); + else if (-m_attachRotStep > neededTurn) + m_fRotationCur = CGeneral::LimitRadianAngle(targetAngle - m_attachRotStep); + else + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + } + rotMatrix.SetRotateZ(m_fRotationCur - objAngle); + targetMat = targetMat * rotMatrix; + GetMatrix() = targetMat; + if (m_attachedTo->IsVehicle() || m_attachedTo->IsObject()) { + m_vecMoveSpeed = ((CPhysical*)m_attachedTo)->m_vecMoveSpeed; + m_vecTurnSpeed = ((CPhysical*)m_attachedTo)->m_vecTurnSpeed; + } +} + +void +CPed::Undress(const char* name) +{ + int mi = GetModelIndex(); + CAnimBlendAssociation* pAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_PHONE_OUT); + if (pAnim) + FinishTalkingOnMobileCB(pAnim, this); + + DeleteRwObject(); + if (IsPlayer()) + mi = MI_PLAYER; + CStreaming::RequestSpecialModel(mi, name, STREAMFLAGS_DEPENDENCY | STREAMFLAGS_SCRIPTOWNED); + CWorld::Remove(this); +} + +void +CPed::Dress(void) +{ + int mi = GetModelIndex(); + m_modelIndex = -1; + SetModelIndex(mi); + m_nPedState = PED_IDLE; + m_nLastPedState = PED_NONE; + m_objective = OBJECTIVE_NONE; + m_prevObjective = OBJECTIVE_NONE; + m_nWaitState = WAITSTATE_FALSE; + CWorld::Add(this); + RestoreHeadingRate(); +} + +void +CPed::Say(uint16 audio, int32 time) +{ + if (m_delayedSoundID == -1) { + m_delayedSoundID = audio; + m_delayedSoundTimer = CTimer::GetTimeInMilliseconds() + time; + } +} + +#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, 499); + CopyToBuf(buf, m_fHealth); + CopyToBuf(buf, m_fArmour); + ZeroSaveBuf(buf, 172); + for (int i = 0; i < 10; i++) // has to be hardcoded + m_weapons[i].Save(buf); + ZeroSaveBuf(buf, 252); +} + +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, 499); + CopyFromBuf(buf, m_fHealth); + CopyFromBuf(buf, m_fArmour); + SkipSaveBuf(buf, 172); + m_currentWeapon = WEAPONTYPE_UNARMED; + + CWeapon bufWeapon; + for (int i = 0; i < 10; i++) { // has to be hardcoded + bufWeapon.Load(buf); + + if (bufWeapon.m_eWeaponType != WEAPONTYPE_UNARMED) { + int modelId = CWeaponInfo::GetWeaponInfo(bufWeapon.m_eWeaponType)->m_nModelId; + if (modelId != -1) { + CStreaming::RequestModel(modelId, STREAMFLAGS_DEPENDENCY); + int modelId2 = CWeaponInfo::GetWeaponInfo(bufWeapon.m_eWeaponType)->m_nModel2Id; + if (modelId2 != -1) + CStreaming::RequestModel(modelId2, STREAMFLAGS_DEPENDENCY); + + CStreaming::LoadAllRequestedModels(false); + } + GiveWeapon(bufWeapon.m_eWeaponType, bufWeapon.m_nAmmoTotal, false); + } + } + SkipSaveBuf(buf, 252); +} +#undef CopyFromBuf +#undef CopyToBuf +#endif diff --git a/src/miami/peds/Ped.h b/src/miami/peds/Ped.h new file mode 100644 index 00000000..26cdf03f --- /dev/null +++ b/src/miami/peds/Ped.h @@ -0,0 +1,1192 @@ +#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 "PathFind.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) + +class CAccident; +class CObject; +class CFire; +struct AnimBlendFrameData; +class CAnimBlendAssociation; +class CPedAttractor; + +struct PedAudioData +{ + int m_nFixedDelayTime; + int m_nOverrideFixedDelayTime; + int m_nOverrideMaxRandomDelayTime; + int m_nMaxRandomDelayTime; +}; + +enum +{ + ATTACK_IN_PROGRESS, + CANT_ATTACK, + WATCH_UNTIL_HE_DISAPPEARS, +}; + +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; + float extendReachMultiplier; + uint8 hitLevel; // FightMoveHitLevel + uint8 damage; + uint8 flags; +}; + +// TODO: This is eFightState on mobile. +enum PedFightMoves +{ + FIGHTMOVE_NULL, + // Attacker + FIGHTMOVE_STDPUNCH, + FIGHTMOVE_IDLE, + FIGHTMOVE_SHUFFLE_F, + FIGHTMOVE_KNEE, + FIGHTMOVE_PUNCHHOOK, + FIGHTMOVE_PUNCHJAB, + FIGHTMOVE_PUNCH, + FIGHTMOVE_LONGKICK, + FIGHTMOVE_ROUNDHOUSE, + // Directionals + FIGHTMOVE_FWDLEFT, + FIGHTMOVE_FWDRIGHT, + FIGHTMOVE_BACKKICK, + FIGHTMOVE_BACKFLIP, + FIGHTMOVE_BACKLEFT, + FIGHTMOVE_BACKRIGHT, + FIGHTMOVE_RIGHTSWEEP, + // Special + FIGHTMOVE_GROUNDKICK, + // Opponent + FIGHTMOVE_HITFRONT, + FIGHTMOVE_HITBACK, + FIGHTMOVE_HITRIGHT, + FIGHTMOVE_HITLEFT, + FIGHTMOVE_HITBODY, + FIGHTMOVE_HITCHEST, + FIGHTMOVE_HITHEAD, + FIGHTMOVE_HITBIGSTEP, + FIGHTMOVE_HITONFLOOR, + FIGHTMOVE_HITBEHIND, + FIGHTMOVE_MELEE1, + FIGHTMOVE_MELEE2, + FIGHTMOVE_MELEE3, + 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, + WAITSTATE_SIT_DOWN, + WAITSTATE_SIT_DOWN_RVRS, + WAITSTATE_SIT_UP, + WAITSTATE_SIT_IDLE, + WAITSTATE_USE_ATM, + WAITSTATE_SUN_BATHE_PRE, + WAITSTATE_SUN_BATHE_DOWN, + WAITSTATE_SUN_BATHE_IDLE, + WAITSTATE_RIOT, + WAITSTATE_FAST_FALL, + WAITSTATE_BOMBER, + WAITSTATE_STRIPPER, + WAITSTATE_GROUND_ATTACK, + WAITSTATE_LANCESITTING, + WAITSTATE_PLAYANIM_HANDSUP_SIMPLE, +}; + +enum eObjective { + OBJECTIVE_NONE, + OBJECTIVE_WAIT_ON_FOOT, + OBJECTIVE_WAIT_ON_FOOT_FOR_COP, + OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE, + OBJECTIVE_GUARD_SPOT, + OBJECTIVE_GUARD_AREA, + 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_GOTO_CHAR_ON_FOOT_WALKING, + OBJECTIVE_HASSLE_CHAR, + OBJECTIVE_FOLLOW_CHAR_IN_FORMATION, + OBJECTIVE_LEAVE_CAR, + OBJECTIVE_ENTER_CAR_AS_PASSENGER, + OBJECTIVE_ENTER_CAR_AS_DRIVER, + OBJECTIVE_FOLLOW_CAR_IN_CAR, + OBJECTIVE_FIRE_AT_OBJECT_FROM_VEHICLE, + OBJECTIVE_DESTROY_OBJECT, + OBJECTIVE_DESTROY_CAR, + OBJECTIVE_GOTO_AREA_ANY_MEANS, + OBJECTIVE_GOTO_AREA_ON_FOOT, + OBJECTIVE_RUN_TO_AREA, + OBJECTIVE_GOTO_AREA_IN_CAR, + OBJECTIVE_FOLLOW_CAR_ON_FOOT_WITH_OFFSET, + 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_STEAL_ANY_MISSION_CAR, + OBJECTIVE_MUG_CHAR, + OBJECTIVE_LEAVE_CAR_AND_DIE, + OBJECTIVE_GOTO_SEAT_ON_FOOT, + OBJECTIVE_GOTO_ATM_ON_FOOT, + OBJECTIVE_FLEE_CAR, + OBJECTIVE_SUN_BATHE, + OBJECTIVE_GOTO_BUS_STOP_ON_FOOT, + OBJECTIVE_GOTO_PIZZA_ON_FOOT, + OBJECTIVE_GOTO_SHELTER_ON_FOOT, + OBJECTIVE_AIM_GUN_AT, + OBJECTIVE_WANDER, + OBJECTIVE_WAIT_ON_FOOT_AT_SHELTER, + OBJECTIVE_SPRINT_TO_AREA, + OBJECTIVE_KILL_CHAR_ON_BOAT, + OBJECTIVE_SOLICIT_FOOT, + OBJECTIVE_WAIT_ON_FOOT_AT_BUS_STOP, + OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT, + OBJECTIVE_WAIT_ON_FOOT_AT_ICE_CREAM_VAN, + OBJ_55, + OBJ_56, + OBJ_57, + OBJ_58, + OBJ_59 + +}; + +enum { + RANDOM_CHAR = 1, + MISSION_CHAR, + UNK_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 + LINE_UP_TO_CAR_FALL +}; + +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_SUN_BATHE, + PED_FLASH, + PED_JOG, + PED_ANSWER_MOBILE, + + 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, + + PED_ABSEIL, + PED_SIT, + PED_JUMP, + PED_FALL, + PED_GETUP, + PED_STAGGER, + PED_DIVE_AWAY, + + PED_STATES_NO_ST, + PED_ENTER_TRAIN, + PED_EXIT_TRAIN, + PED_ARREST_PLAYER, + + 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, + PED_DEPLOY_STINGER +}; + +enum eMoveState { + PEDMOVE_NONE, + PEDMOVE_STILL, + PEDMOVE_WALK, + PEDMOVE_JOG, + PEDMOVE_RUN, + PEDMOVE_SPRINT, + PEDMOVE_THROWN +}; + +extern float gfTommyFatness; + +class CVehicle; + +class CPed : public CPhysical +{ +public: +#ifdef USE_CUTSCENE_SHADOW_FOR_PED + class CCutsceneShadow *m_pRTShadow; +#endif + // 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; + 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; + uint32 bDontAcceptIKLookAts : 1; + uint32 bReachedAttractorHeadingTarget : 1; + uint32 bTurnedAroundOnAttractor : 1; + + uint32 bHasAlreadyUsedAttractor : 1; + uint32 bHasAlreadyStoleACar : 1; + uint32 bCarPassenger : 1; + uint32 bFleeWhenStanding : 1; + uint32 bGotUpOfMyOwnAccord : 1; + uint32 bMiamiViceCop : 1; + uint32 bMoneyHasBeenGivenByScript : 1; // + uint32 bHasBeenPhotographed : 1; // + + uint32 bIsDrowning : 1; + uint32 bDrownsInWater : 1; + uint32 bWaitForLeaderToComeCloser : 1; + uint32 bHeldHostageInCar : 1; + uint32 bIsPlayerFriend : 1; + uint32 bHeadStuckInCollision : 1; + uint32 bDeadPedInFrontOfCar : 1; + uint32 bStayInCarOnJack : 1; + + uint32 bDontFight : 1; + uint32 bDoomAim : 1; + uint32 bCanBeShotInVehicle : 1; + uint32 bCanGiveUpSunbathing : 1; + uint32 bMakeFleeScream : 1; + uint32 bPushedAlongByCar : 1; + uint32 bRemoveMeWhenIGotIntoCar : 1; + uint32 bIgnoreThreatsBehindObjects : 1; + + uint32 bNeverEverTargetThisPed : 1; + uint32 bCrouchWhenScared : 1; + uint32 bKnockedOffBike : 1; + uint32 b158_8 : 1; + uint32 bCollectBusFare : 1; + uint32 bBoughtIceCream : 1; + uint32 bDonePositionOutOfCollision : 1; + uint32 bCanAttackPlayerWithCops : 1; + +#ifdef KANGAROO_CHEAT + // our own flags + uint32 m_ped_flagI80 : 1; // KANGAROO_CHEAT define makes use of this as cheat toggle +#endif + + uint8 m_gangFlags; + uint8 m_unused15D; // these 3 can't be padding but had to actually have been members ... + uint8 m_unused15E; + uint8 m_unused15F; + uint8 CharCreatedBy; + eObjective m_objective; + eObjective m_prevObjective; + CPed *m_pedInObjective; + CVehicle *m_carInObjective; + CVector m_nextRoutePointPos; + float m_attractorHeading; + 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]; + RpAtomic *m_pWeaponModel; + 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; + CPathNode* m_pathNodesToGo[8]; + int16 m_nNumPathNodes; + int16 m_nCurPathNodeId; + CEntity* m_followPathWalkAroundEnt; + CEntity* m_followPathTargetEnt; + uint32 m_pathNodeTimer; + CPathNode m_pathNodeObjPool[8]; + CPathNode* m_pCurPathNode; + int8 m_nPathDir; + CPathNode* m_pLastPathNode; + CPathNode* m_pNextPathNode; + CVector m_followPathDestPos; + float m_followPathAbortDist; + eMoveState m_followPathMoveState; + float m_fHealth; + float m_fArmour; + uint32 m_nExtendedRangeTimer; + 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; + float m_acceptableHeadingOffset; + CVehicle* m_vehicleInAccident; + CPedAttractor* m_attractor; + int32 m_positionInQueue; + bool bRunningToPhone; + int16 m_phoneId; + eCrimeType m_crimeToReportOnPhone; + uint32 m_phoneTalkTimer; + CAccident *m_lastAccident; + uint32 m_nPedType; + CPedStats *m_pedStats; + CVector2D m_fleeFromPos; + CEntity *m_fleeFrom; + uint32 m_fleeTimer; + CEntity* m_threatEx; // TODO(Miami): What is this? + CEntity* m_collidingEntityWhileFleeing; + uint32 m_collidingThingTimer; + CEntity *m_pCollidingEntity; + uint8 m_stateUnused; + uint32 m_timerUnused; + class CRange2D *m_wanderRangeBounds; + CWeapon m_weapons[TOTAL_WEAPON_SLOTS]; + eWeaponType m_storedWeapon; + eWeaponType m_delayedWeapon; + uint32 m_delayedWeaponAmmo; + uint8 m_currentWeapon; // eWeaponType + uint8 m_maxWeaponTypeAllowed; // eWeaponType + uint8 m_wepSkills; + uint8 m_wepAccuracy; + CEntity *m_pPointGunAt; + CVector m_vecHitLastPos; + uint32 m_curFightMove; + uint32 m_lastFightMove; + uint8 m_fightButtonPressure; + int8 m_fightState; + bool m_takeAStepAfterAttack; + uint8 m_bleedCounter; + 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 + uint32 m_shotTime; + uint32 m_ceaseAttackTimer; + uint8 m_panicCounter; + bool m_deadBleeding; + int8 m_bodyPartBleeding; // PedNode, but -1 if there isn't + CPed *m_nearPeds[10]; + uint16 m_numNearPeds; + uint16 m_nPedMoney; + int8 m_lastWepDam; + CEntity *m_lastDamEntity; + CEntity *m_attachedTo; + CVector m_vecAttachOffset; + uint16 m_attachType; + float m_attachRotStep; + uint32 m_attachWepAmmo; + uint32 m_threatFlags; + uint32 m_threatCheckTimer; + uint32 m_threatCheckInterval; + int32 m_delayedSoundID; + uint32 m_delayedSoundTimer; + uint32 m_lastSoundStart; + uint32 m_soundStart; + uint16 m_lastQueuedSound; + uint16 m_queuedSound; + bool m_canTalk; + uint32 m_lastComment; + CVector m_vecSpotToGuard; + float m_radiusToGuard; + + 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 DeleteRwObject(); + 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 Say(uint16 audio, int32 time); + void SetLookFlag(CEntity* target, bool keepTryingToLook, bool cancelPrevious = false); + void SetLookFlag(float direction, bool keepTryingToLook, bool cancelPrevious = false); + void SetLookTimer(int time); + void SetDie(AnimationId anim = ANIM_STD_KO_FRONT, float arg1 = 4.0f, float arg2 = 0.0f); + void SetDead(void); + void ApplyHeadShot(eWeaponType weaponType, CVector pos, bool evenOnPlayer); + void RemoveBodyPart(PedNode nodeId, int8 direction); + bool OurPedCanSeeThisOne(CEntity *target, bool shootablesDoBlock = false); + 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(eWeaponType weaponType); + void SetCurrentWeapon(int weapon); + void Duck(void); + void ClearDuck(bool = false); + 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); + int32 GiveWeapon(eWeaponType weaponType, uint32 ammo, bool unused = true); + 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, float, const CVector&); + void ClearChat(void); + void InformMyGangOfAttack(CEntity*); + void ReactToAttack(CEntity*); + void SetDuck(uint32, bool = false); + 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); + 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 dest, float radius, eMoveState state, CEntity*, CEntity*, int); + bool SetFollowPathStatic(void); + bool SetFollowPathDynamic(void); + 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&, bool); + void FightHitPed(CPed*, CVector&, CVector&, int16); + int32 ChooseAttackPlayer(uint8, bool); + int32 ChooseAttackAI(uint8, bool); + 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 RemoveDrivebyAnims(void); + void RemoveInCarAnims(void); + void CollideWithPed(CPed*); + void SetDirectionToWalkAroundObject(CEntity*); + bool SetDirectionToWalkAroundVehicle(CVehicle*); + void RemoveWeaponAnims(int, float); + void CreateDeadPedMoney(void); + void CreateDeadPedWeaponPickups(void); + void CreateDeadPedPickupCoors(float *x, float *y, float *z); + void SetAttackTimer(uint32); + void SetBeingDraggedFromCar(CVehicle*, uint32, bool); + void SetRadioStation(void); + void SetBuyIceCream(void); + void SetChat(CEntity*, uint32); + void DeadPedMakesTyresBloody(void); + void MakeTyresMuddySectorList(CPtrList&); + bool DuckAndCover(void); + void EndFight(uint8); + void EnterCar(void); + uint8 GetNearestTrainPedPosition(CVehicle*, CVector&); + uint8 GetNearestTrainDoor(CVehicle*, CVector&); + void ExitCar(void); + void Fight(void); + bool FindBestCoordsFromNodes(CVector, CVector*); + void Wait(void); + void ProcessObjective(void); + CVector *SeekFollowingPath(void); + void Flee(void); + void FollowPath(void); + CVector GetFormationPosition(void); + void GetNearestDoor(CVehicle*, CVector&); + bool GetNearestPassengerDoor(CVehicle*, CVector&); + int GetNextPointOnRoute(void); + int GetWeaponSlot(eWeaponType); + bool CanWeRunAndFireWithWeapon(void); + 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 PositionAnyPedOutOfCollision(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 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*); + void ClearFollowPath(); + void GiveDelayedWeapon(eWeaponType weapon, uint32 ammo); + void RequestDelayedWeapon(); + void AddInCarAnims(CVehicle* car, bool isDriver); + bool CanBeDamagedByThisGangMember(CPed*); + void AnswerMobile(void); + void BuyIceCream(void); + void CheckThreatValidity(void); + void ClearAnswerMobile(void); + void SetAnswerMobile(void); + void AttachPedToEntity(CEntity*, CVector, uint16, float, eWeaponType); + void DettachPedFromEntity(); + void PedShuffle(); + void DriveVehicle(); + void PositionAttachedPed(); + bool CanUseTorsoWhenLooking(); + void ScanForDelayedResponseThreats(); + + // 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); +#ifdef GTA_TRAIN + static void PedSetOutTrainCB(CAnimBlendAssociation *assoc, void *arg); +#endif + static void FinishedAttackCB(CAnimBlendAssociation *assoc, void *arg); + static void FinishedReloadCB(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); + static void DeleteSunbatheIdleAnimCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetPreviousStateCB(CAnimBlendAssociation *assoc, void *arg); + static void PedAnimShuffleCB(CAnimBlendAssociation *assoc, void *arg); + static void PedSetGetInCarPositionCB(CAnimBlendAssociation* assoc, void* arg); + + bool IsPlayer(void) const; + bool IsFemale(void) { return m_nPedType == PEDTYPE_CIVFEMALE || m_nPedType == PEDTYPE_PROSTITUTE; } + bool UseGroundColModel(void); + bool CanSetPedState(void); + bool IsPedInControl(void); + bool CanPedDriveOff(void); + bool CanBeDeleted(void); + bool CanBeDeletedEvenInVehicle(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); +#ifdef GTA_TRAIN + void EnterTrain(void); + void ExitTrain(void); + void SetExitTrain(CVehicle*); + void SetPedPositionInTrain(void); + void LineUpPedWithTrain(void); + void SetEnterTrain(CVehicle*, uint32); +#endif + void Fall(void); + bool IsPedShootable(void); + void Look(void); + void SetInTheAir(void); + void RestoreHeadPosition(void); + void PointGunAt(void); + bool ServiceTalkingWhenDead(void); + void SetShootTimer(uint32); + void SetSeekCar(CVehicle*, uint32); + void SetSeekBoatPosition(CVehicle*); + void WanderRange(void); + void SetFollowRoute(int16, int16); + void SeekBoatPosition(void); + void UpdatePosition(void); + CObject *SpawnFlyingComponent(int, int8); + void SetCarJack_AllClear(CVehicle*, uint32, uint32); + bool CanPedJumpThis(CEntity *unused, CVector *damageNormal = nil); + void SetNewAttraction(CPedAttractor* pAttractor, const CVector& pos, float, float, int); + void ClearWaitState(void); + void Undress(const char*); + void Dress(void); + int32 KillCharOnFootMelee(CVector&, CVector&, CVector&); + int32 KillCharOnFootArmed(CVector&, CVector&, CVector&); + void SetLook(CEntity* to); + void SetLook(float direction); + + bool HasWeaponSlot(uint8 slot) { return m_weapons[slot].m_eWeaponType != WEAPONTYPE_UNARMED; } + CWeapon& GetWeapon(uint8 slot) { return m_weapons[slot]; } + CWeapon *GetWeapon(void) { return &m_weapons[m_currentWeapon]; } + + PedState GetPedState(void) { return m_nPedState; } + void SetPedState(PedState state) + { + if (GetPedState() == PED_FOLLOW_PATH && state != PED_FOLLOW_PATH) + ClearFollowPath(); + 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; } + bool HasAttractor(void); + bool IsUseAttractorObjective(eObjective obj) { + return obj == OBJECTIVE_GOTO_ATM_ON_FOOT || obj == OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT || + obj == OBJECTIVE_GOTO_PIZZA_ON_FOOT || obj == OBJECTIVE_GOTO_SEAT_ON_FOOT || + obj == OBJECTIVE_GOTO_SHELTER_ON_FOOT || obj == OBJECTIVE_GOTO_BUS_STOP_ON_FOOT; + } + + void ReplaceWeaponWhenExitingVehicle(void); + void RemoveWeaponWhenEnteringVehicle(void); + bool IsNotInWreckedVehicle() + { + return m_pMyVehicle != nil && ((CEntity*)m_pMyVehicle)->GetStatus() != STATUS_WRECKED; + } + + // My names. Inlined in VC + AnimationId GetFireAnimNotDucking(CWeaponInfo* weapon) { + if (m_nPedType == PEDTYPE_COP && weapon->IsFlagSet(WEAPONFLAG_COP3_RD)) + return Get3rdFireAnim(weapon); + else + return GetPrimaryFireAnim(weapon); + } + + static AnimationId Get3rdFireAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_COP3_RD)) + return ANIM_WEAPON_FIRE_3RD; + else + return (AnimationId)0; + } + + static AnimationId GetFireAnimGround(CWeaponInfo* weapon, bool kickFloorIfNone = true) { + if (weapon->IsFlagSet(WEAPONFLAG_GROUND_2ND)) + return ANIM_WEAPON_CROUCHFIRE; + else if (weapon->IsFlagSet(WEAPONFLAG_GROUND_3RD)) + return ANIM_WEAPON_FIRE_3RD; + else if (kickFloorIfNone) + return ANIM_STD_KICKGROUND; + else + return (AnimationId)0; + } + + static AnimationId GetPrimaryFireAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_ANIMDETONATE)) + return ANIM_STD_DETONATE; + else + return ANIM_WEAPON_FIRE; + } + + static AnimationId GetCrouchReloadAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_RELOAD)) + return ANIM_WEAPON_CROUCHRELOAD; + else + return (AnimationId)0; + } + + static AnimationId GetCrouchFireAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_CROUCHFIRE)) + return ANIM_WEAPON_CROUCHFIRE; + else + return (AnimationId)0; + } + + static AnimationId GetReloadAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_RELOAD)) + return ANIM_WEAPON_RELOAD; + else + return (AnimationId)0; + } + + static AnimationId GetFightIdleWithMeleeAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_FIGHTMODE)) + return ANIM_MELEE_IDLE_FIGHTMODE; + else + return (AnimationId)0; + } + + static AnimationId GetFinishingAttackAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_FINISH_3RD)) + return ANIM_MELEE_ATTACK_FINISH; + else + return (AnimationId)0; + } + + static AnimationId GetSecondFireAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_USE_2ND)) + return ANIM_WEAPON_FIRE_2ND; + else + return (AnimationId)0; + } + + static AnimationId GetMeleeStartAnim(CWeaponInfo* weapon) { + if (weapon->IsFlagSet(WEAPONFLAG_PARTIALATTACK)) + return ANIM_MELEE_ATTACK_START; + else + return (AnimationId)0; + } + + static AnimationId GetThrowAnim(CWeaponInfo *weapon) + { + if (weapon->IsFlagSet(WEAPONFLAG_THROW)) + return ANIM_THROWABLE_START_THROW; + else + return (AnimationId)0; + } + // -- + + // 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) + { + if (m_pPointGunAt) + m_pPointGunAt->CleanUpOldReference(&m_pPointGunAt); + + 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) + { + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + pos = mats[idx].pos; + return pos; + } + void TransformToNode(CVector &pos, int32 node) + { + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, m_pFrames[node]->nodeID); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + RwV3dTransformPoints(&pos, &pos, 1, &mats[idx]); + } + + // 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 bFannyMagnetCheat; + static bool bPedCheat3; + static CVector2D ms_vec2DFleePosition; + +#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 COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif +}; + +void FinishTalkingOnMobileCB(CAnimBlendAssociation* assoc, void* arg); +void StartTalkingOnMobileCB(CAnimBlendAssociation* assoc, void* arg); +void PlayRandomAnimationsFromAnimBlock(CPed* ped, AssocGroupId animGroup, uint32 first, uint32 amount); + +VALIDATE_SIZE(CPed, 0x5F4); + +bool IsPedPointerValid(CPed*); +bool IsPedPointerValid_NotInWorld(CPed*); diff --git a/src/miami/peds/PedAI.cpp b/src/miami/peds/PedAI.cpp new file mode 100644 index 00000000..e6ffc97a --- /dev/null +++ b/src/miami/peds/PedAI.cpp @@ -0,0 +1,6926 @@ +#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" +#include "PedAttractor.h" +#include "Bike.h" +#include "Weather.h" +#include "GameLogic.h" +#include "Streaming.h" + +CVector vecPedCarDoorAnimOffset; +CVector vecPedCarDoorLoAnimOffset; +CVector vecPedVanRearDoorAnimOffset; +CVector vecPedQuickDraggedOutCarAnimOffset; +CVector vecPedDraggedOutCarAnimOffset; +CVector vecPedTrainDoorAnimOffset; +CVector vecPedStdBikeJumpRhsAnimOffset; +CVector vecPedVespaBikeJumpRhsAnimOffset; +CVector vecPedHarleyBikeJumpRhsAnimOffset; +CVector vecPedDirtBikeJumpRhsAnimOffset; +CVector vecPedBikeKickAnimOffset; + +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_GOTO_CHAR_ON_FOOT_WALKING: + case OBJECTIVE_HASSLE_CHAR: + 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: + case OBJECTIVE_GOTO_SEAT_ON_FOOT: + case OBJECTIVE_GOTO_ATM_ON_FOOT: + case OBJECTIVE_GOTO_BUS_STOP_ON_FOOT: + case OBJECTIVE_GOTO_PIZZA_ON_FOOT: + case OBJECTIVE_GOTO_SHELTER_ON_FOOT: + case OBJECTIVE_SPRINT_TO_AREA: + case OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT: + 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_GOTO_CHAR_ON_FOOT_WALKING: + case OBJECTIVE_HASSLE_CHAR: + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + case OBJECTIVE_GOTO_SEAT_ON_FOOT: + case OBJECTIVE_GOTO_ATM_ON_FOOT: + case OBJECTIVE_GOTO_BUS_STOP_ON_FOOT: + case OBJECTIVE_GOTO_PIZZA_ON_FOOT: + case OBJECTIVE_GOTO_SHELTER_ON_FOOT: + case OBJECTIVE_SPRINT_TO_AREA: + case OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT: + return; + default: + m_prevObjective = m_objective; + } +} + +bool +CPed::IsTemporaryObjective(eObjective objective) +{ + return objective == OBJECTIVE_LEAVE_CAR || objective == OBJECTIVE_SET_LEADER || + objective == OBJECTIVE_LEAVE_CAR_AND_DIE || objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || + objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER; +} + +void +CPed::SetObjective(eObjective newObj) +{ + if (DyingOrDead() || m_attachedTo) + 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 + || m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) && !IsPlayer() && !IsPedInControl()) { + + bStartWanderPathOnFoot = true; + } 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 && m_prevObjective != OBJECTIVE_NONE) + return; + + if (entity == this) + return; + + if (m_attachedTo && newObj != OBJECTIVE_KILL_CHAR_ON_FOOT && newObj != OBJECTIVE_KILL_CHAR_ANY_MEANS && newObj != OBJECTIVE_DESTROY_OBJECT && newObj != OBJECTIVE_DESTROY_CAR) + return; + + 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_GOTO_CHAR_ON_FOOT_WALKING: + case OBJECTIVE_HASSLE_CHAR: + case OBJECTIVE_FOLLOW_CHAR_IN_FORMATION: + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + case OBJECTIVE_GUARD_ATTACK: + case OBJECTIVE_KILL_CHAR_ON_BOAT: + case OBJECTIVE_SOLICIT_FOOT: + if (m_pedInObjective == entity) + return; + break; + case OBJECTIVE_LEAVE_CAR: + case OBJECTIVE_FLEE_CAR: + case OBJECTIVE_LEAVE_CAR_AND_DIE: + 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; + + if (newObj == OBJECTIVE_BUY_ICE_CREAM && bBoughtIceCream) + return; + + break; + case OBJECTIVE_SET_LEADER: + if (m_leader == entity) + return; + break; + case OBJECTIVE_AIM_GUN_AT: + if (m_pedInObjective == entity) + return; + break; + default: + break; + } + } else { + if (newObj != OBJECTIVE_WAIT_IN_CAR_THEN_GET_OUT && (newObj == OBJECTIVE_LEAVE_CAR || newObj == OBJECTIVE_LEAVE_CAR_AND_DIE) + && !bInVehicle) + return; + } + + bObjectiveCompleted = false; + ClearPointGunAt(); + m_objectiveTimer = 0; + 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_ON_FOOT_FOR_COP: + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + SetIdle(); + SetLook(m_pedInObjective); + break; + 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: + case OBJECTIVE_KILL_CHAR_ON_BOAT: + m_pNextPathNode = nil; + bUsePedNodeSeek = false; + + if (m_pedInObjective) + m_pedInObjective->CleanUpOldReference((CEntity**)&m_pedInObjective); + if (m_pLookTarget) + m_pLookTarget->CleanUpOldReference(&m_pLookTarget); + + m_pLookTarget = (CEntity*)entity; + m_pedInObjective = (CPed*)entity; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + // m_pLookTarget = (CEntity*)entity; // duplicate + 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_GOTO_CHAR_ON_FOOT_WALKING: + case OBJECTIVE_HASSLE_CHAR: + case OBJECTIVE_GUARD_ATTACK: + + if (m_pedInObjective) + m_pedInObjective->CleanUpOldReference((CEntity**)&m_pedInObjective); + m_pedInObjective = (CPed*)entity; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + 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: + case OBJECTIVE_LEAVE_CAR_AND_DIE: + 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_DESTROY_OBJECT: + SetWeaponLockOnTarget((CEntity*)entity); + break; + case OBJECTIVE_ENTER_CAR_AS_PASSENGER: + case OBJECTIVE_ENTER_CAR_AS_DRIVER: + if (m_nMoveState == PEDMOVE_STILL) + SetMoveState(PEDMOVE_RUN); + + if (((CVehicle*)entity)->IsBoat() && !IsPlayer() && m_pCurrentPhysSurface != entity) { + 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; + case OBJECTIVE_AIM_GUN_AT: + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + break; + case OBJECTIVE_SOLICIT_FOOT: + m_pedInObjective = (CPed*)entity; + m_pedInObjective->RegisterReference((CEntity**)&m_pedInObjective); + break; + default: + break; + } +} + +// 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; + + if (m_objective == newObj && newObj == OBJECTIVE_FOLLOW_ROUTE && m_routeLastPoint == routePoint && m_routeType == routeType) + return; + + ClearPointGunAt(); + SetObjectiveTimer(0); + + 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; + + if (m_objective == newObj) { + if (newObj == OBJECTIVE_GOTO_AREA_ANY_MEANS || newObj == OBJECTIVE_GOTO_AREA_ON_FOOT || newObj == OBJECTIVE_RUN_TO_AREA || newObj == OBJECTIVE_SPRINT_TO_AREA) { + if (m_nextRoutePointPos == dest) + return; + } else if (newObj == OBJECTIVE_GUARD_SPOT) { + if (m_vecSpotToGuard == dest) + return; + } + } + + ClearPointGunAt(); + m_objectiveTimer = 0; + bObjectiveCompleted = false; + switch (newObj) { + case OBJECTIVE_GUARD_SPOT: + m_vecSpotToGuard = dest; + m_radiusToGuard = 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_GOTO_CHAR_ON_FOOT_WALKING: + case OBJECTIVE_HASSLE_CHAR: + 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: + case OBJECTIVE_GOTO_AREA_IN_CAR: + case OBJECTIVE_FOLLOW_CAR_ON_FOOT_WITH_OFFSET: + case OBJECTIVE_GUARD_ATTACK: + case OBJECTIVE_SET_LEADER: + case OBJECTIVE_FOLLOW_ROUTE: + case OBJECTIVE_SOLICIT_VEHICLE: + case OBJECTIVE_HAIL_TAXI: + case OBJECTIVE_CATCH_TRAIN: + case OBJECTIVE_BUY_ICE_CREAM: + case OBJECTIVE_STEAL_ANY_CAR: + case OBJECTIVE_STEAL_ANY_MISSION_CAR: + case OBJECTIVE_MUG_CHAR: + case OBJECTIVE_LEAVE_CAR_AND_DIE: + case OBJECTIVE_FLEE_CAR: + case OBJECTIVE_SUN_BATHE: + case OBJECTIVE_AIM_GUN_AT: + case OBJECTIVE_WANDER: + case OBJECTIVE_WAIT_ON_FOOT_AT_SHELTER: + case OBJECTIVE_KILL_CHAR_ON_BOAT: + case OBJECTIVE_SOLICIT_FOOT: + case OBJECTIVE_WAIT_ON_FOOT_AT_BUS_STOP: + break; + case OBJECTIVE_GOTO_AREA_ANY_MEANS: + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_GOTO_SEAT_ON_FOOT: + case OBJECTIVE_GOTO_ATM_ON_FOOT: + case OBJECTIVE_GOTO_BUS_STOP_ON_FOOT: + case OBJECTIVE_GOTO_PIZZA_ON_FOOT: + case OBJECTIVE_GOTO_SHELTER_ON_FOOT: + case OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT: + bIsRunning = false; + m_pNextPathNode = nil; + m_nextRoutePointPos = dest; + m_vecSeekPos = m_nextRoutePointPos; + m_distanceToCountSeekDone = 0.5f; + if (newObj == OBJECTIVE_GOTO_ATM_ON_FOOT) { + m_distanceToCountSeekDone = m_attractor->GetDistanceToCountSeekDone(); + m_acceptableHeadingOffset = m_attractor->GetAcceptableHeading(); + } + if (newObj == OBJECTIVE_GOTO_SEAT_ON_FOOT) { + m_distanceToCountSeekDone = m_attractor->GetDistanceToCountSeekDone(); + m_acceptableHeadingOffset = m_attractor->GetAcceptableHeading(); + } + if (newObj == OBJECTIVE_GOTO_BUS_STOP_ON_FOOT) { + m_distanceToCountSeekDone = m_attractor->GetDistanceToCountSeekDone(); + m_acceptableHeadingOffset = m_attractor->GetAcceptableHeading(); + } + if (newObj == OBJECTIVE_GOTO_PIZZA_ON_FOOT) { + m_distanceToCountSeekDone = m_attractor->GetDistanceToCountSeekDone(); + m_acceptableHeadingOffset = m_attractor->GetAcceptableHeading(); + } + if (newObj == OBJECTIVE_GOTO_SHELTER_ON_FOOT) { + bIsRunning = true; + m_distanceToCountSeekDone = m_attractor->GetDistanceToCountSeekDone(); + m_acceptableHeadingOffset = m_attractor->GetAcceptableHeading(); + } + if (newObj == OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT) { + bIsRunning = true; + m_distanceToCountSeekDone = m_attractor->GetDistanceToCountSeekDone(); + m_acceptableHeadingOffset = m_attractor->GetAcceptableHeading(); + } + bUsePedNodeSeek = false; + if (sq(m_distanceToCountSeekDone) > (m_nextRoutePointPos - GetPosition()).MagnitudeSqr2D()) { + if (!IsUseAttractorObjective(m_objective)) + return; + if (Abs(m_fRotationCur - m_attractorHeading) < m_acceptableHeadingOffset) + return; + } + break; + case OBJECTIVE_RUN_TO_AREA: + case OBJECTIVE_SPRINT_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::SetObjective(eObjective newObj, float heading, const CVector& pos) +{ + switch (newObj) { + case OBJECTIVE_GOTO_SEAT_ON_FOOT: + case OBJECTIVE_GOTO_ATM_ON_FOOT: + case OBJECTIVE_GOTO_BUS_STOP_ON_FOOT: + case OBJECTIVE_GOTO_PIZZA_ON_FOOT: + case OBJECTIVE_GOTO_SHELTER_ON_FOOT: + case OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT: + ClearPointGunAt(); + SetObjective(newObj, pos); + m_attractorHeading = heading; + } +} + +void +CPed::ClearObjective(void) +{ + if (IsPedInControl() || m_nPedState == PED_DRIVING) { + m_objective = OBJECTIVE_NONE; + m_pedInObjective = nil; + m_carInObjective = nil; + + if (m_nPedState == PED_DRIVING && m_pMyVehicle) { + if (m_pMyVehicle->pDriver != this) { + if(!IsPlayer()) + bWanderPathAfterExitingCar = true; + + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + m_nLastPedState = PED_NONE; + } 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 (bWaitForLeaderToComeCloser) { + if (IsPedInControl()) { + SetObjective(OBJECTIVE_NONE); + SetIdle(); + SetMoveState(PEDMOVE_STILL); + } + return; + } + bWaitForLeaderToComeCloser = true; + } else + bWaitForLeaderToComeCloser = false; + + 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 && !bDontFight) { + 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) { + 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); + } + } + if (m_nPedState == PED_IDLE && m_leader->IsPlayer() && !bDontFight) { + 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_vecSpotToGuard); + 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: + case OBJECTIVE_GOTO_CHAR_ON_FOOT_WALKING: + case OBJECTIVE_HASSLE_CHAR: + 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) { + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 150; + 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) { + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 250; + 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 + && m_nPedState != PED_CARJACK) + 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_WAIT_ON_FOOT: + if (GetPedState() == PED_DRIVING) + m_objective = OBJECTIVE_NONE; + else { + SetIdle(); + if (m_attractor) { + if (m_objectiveTimer && CTimer::GetTimeInMilliseconds() > m_nWaitTimer) { + GetPedAttractorManager()->BroadcastDeparture(this, m_attractor); + m_objectiveTimer = 0; + } + } else { + m_objective = OBJECTIVE_NONE; + SetMoveState(PEDMOVE_STILL); + } + } + break; + case OBJECTIVE_WAIT_ON_FOOT_FOR_COP: + if (!m_pedInObjective) { + m_objective = OBJECTIVE_NONE; + SetWanderPath(CGeneral::GetRandomNumberInRange(0.f, 8.f)); + } else if (m_pedInObjective->m_nPedType == PEDTYPE_COP && m_pedInObjective->m_nPedState == PED_DEAD) { + m_objective = OBJECTIVE_NONE; + SetWanderPath(CGeneral::GetRandomNumberInRange(0.f, 8.f)); + m_pedInObjective = nil; + } + break; + case OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE: + if (m_leaveCarTimer >= CTimer::GetTimeInMilliseconds()) + break; + + 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_vecSpotToGuard - 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_radiusToGuard >= distWithTargetSc) { + if (m_pedInObjective && m_pedInObjective->m_nPedState != PED_DEAD) { + if (distWithTargetSc <= m_radiusToGuard) + SetIdle(); + else { + CVector seekPos = m_vecSpotToGuard; + SetSeek(seekPos, m_radiusToGuard); + } + } 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 { + CVector seekPos = m_vecSpotToGuard; + SetSeek(seekPos, m_radiusToGuard); + } + 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->AutoPilot.m_nCarMission == MISSION_NONE) || m_pMyVehicle->AutoPilot.m_nCarMission == MISSION_CRUISE) + && !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(false)) { + 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(), ENTER_CAR_MAX_DIST, 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) && nearVeh->m_fHealth > 250.f) { + + 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, &chosenCarClass); + CVehicle *newVeh = nil; + if (chosenModel != -1) { + if (CModelInfo::IsBikeModel(chosenModel)) { + newVeh = new CBike(chosenModel, RANDOM_VEHICLE); + } else { + 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: + { + if (m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT && InVehicle()) { + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + break; + } + if (!m_pedInObjective || m_pedInObjective->DyingOrDead()) { + bObjectiveCompleted = true; + ClearLookFlag(); + SetMoveAnim(); + break; + } + if (m_pedInObjective) { + int status; + if (GetWeapon()->IsTypeMelee()) + status = KillCharOnFootMelee(carOrOurPos, targetCarOrHisPos, distWithTarget); + else + status = KillCharOnFootArmed(carOrOurPos, targetCarOrHisPos, distWithTarget); + + if (status == WATCH_UNTIL_HE_DISAPPEARS) + return; + if (status == CANT_ATTACK) + break; + } + 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); + if (m_pedStats == CPedStats::ms_apPedStats[PEDSTAT_FIREMAN]) + bMakeFleeScream = true; + } + break; + } + case OBJECTIVE_GOTO_CHAR_ON_FOOT: + case OBJECTIVE_GOTO_CHAR_ON_FOOT_WALKING: + case OBJECTIVE_HASSLE_CHAR: + { + if (m_pedInObjective) { + float safeDistance = 2.0f; + if (m_pedInObjective->bInVehicle) + safeDistance = 3.0f; + if (m_objective == OBJECTIVE_HASSLE_CHAR) + safeDistance = 1.0f; + + float distWithTargetSc = distWithTarget.Magnitude(); + if (m_nPedStateTimer < CTimer::GetTimeInMilliseconds()) { + if (distWithTargetSc <= safeDistance) { + bScriptObjectiveCompleted = true; + if (m_nPedState != PED_ATTACK) { + SetIdle(); + if (m_pLookTarget) + m_pLookTarget->CleanUpOldReference(&m_pLookTarget); + m_pLookTarget = m_pedInObjective; + m_pLookTarget->RegisterReference((CEntity **) &m_pLookTarget); + TurnBody(); + } + if (distWithTargetSc > 2.0f) + SetMoveState(m_pedInObjective->m_nMoveState); + else + SetMoveState(PEDMOVE_STILL); + + if (m_objective == OBJECTIVE_HASSLE_CHAR) { + Say(SOUND_PED_COP_ASK_FOR_ID); + m_pedInObjective->Say(SOUND_PED_INNOCENT); + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 3000; + m_pedInObjective->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 3000; + SetObjective(OBJECTIVE_WANDER); + m_pedInObjective->SetObjective(OBJECTIVE_WANDER); + CVector2D dist = GetPosition() - m_pedInObjective->GetPosition(); + m_nPathDir = CGeneral::GetNodeHeadingFromVector(dist.x, dist.y); + m_pedInObjective->m_nPathDir = CGeneral::GetNodeHeadingFromVector(-dist.x, -dist.y); + } + } 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); + } + } + } + if (m_objective == OBJECTIVE_GOTO_CHAR_ON_FOOT_WALKING && m_nMoveState > PEDMOVE_STILL) + SetMoveState(PEDMOVE_WALK); + } + } 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_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->IsBike()) { + if (!m_carInObjective->pPassengers[0] && !(m_carInObjective->m_nGettingInFlags & (CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR))) { + m_vehDoor = CAR_DOOR_RR; + foundSeat = true; + } + } else { + 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) { + if (bInVehicle && m_pMyVehicle != m_carInObjective) { + SetExitCar(m_pMyVehicle, 0); + } else { + 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 && !IsPlayer()) { + 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 + && (CharCreatedBy != MISSION_CHAR || m_carInObjective->pDriver->CharCreatedBy != RANDOM_CHAR) + ) { + 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 + || IsPlayer() && !CPad::GetPad(0)->ArePlayerControlsDisabled()) { + 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_OBJECT: + if (m_pPointGunAt) { + if (m_nPedState != PED_ATTACK) + SetAttack(m_pPointGunAt); + } else { + bScriptObjectiveCompleted = true; + RestorePreviousObjective(); + } + 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() && 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) { + if (wepRange <= 5.0f) { + if (Abs(distWithTarget.x) > wepRange || Abs(distWithTarget.y) > wepRange || + (distWithTarget.z > -1.0f && distWithTarget.z < 0.3)) { + SetSeek(m_carInObjective, 3.0f); + SetMoveState(PEDMOVE_RUN); + } else { + SetIdle(); + } + } else { + float safeDistance = wepRange * 0.25f; + + SetSeek(m_carInObjective, safeDistance); + SetMoveState(PEDMOVE_RUN); + } + } + SetLookFlag(m_carInObjective, false); + TurnBody(); + break; + } + case OBJECTIVE_GOTO_AREA_ON_FOOT: + case OBJECTIVE_RUN_TO_AREA: + case OBJECTIVE_SPRINT_TO_AREA: + { + if (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) { + bool found = FindBestCoordsFromNodes(m_vecSeekPos, &bestCoords); + if (m_pNextPathNode) { + // Because it already does that if it finds better coords. + if (!found) { + bestCoords = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + } + if ((bestCoords - GetPosition()).Magnitude2D() < m_distanceToCountSeekDone) { + m_pNextPathNode = nil; + bUsePedNodeSeek = false; + } + } + } + if (m_pNextPathNode) + m_vecSeekPos = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + } + CVector seekPos = m_vecSeekPos; + 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 { + CVector seekPos = m_nextRoutePointPos; + SetSeek(seekPos, 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 && m_nPedState != PED_CHAT) + SetSeekCar(m_carInObjective, 0); + } + break; + case OBJECTIVE_STEAL_ANY_CAR: + case OBJECTIVE_STEAL_ANY_MISSION_CAR: + { + if (bInVehicle) { + bScriptObjectiveCompleted = true; + RestorePreviousObjective(); + } else if (m_carJackTimer < CTimer::GetTimeInMilliseconds()) { + CVehicle *carToSteal = nil; + float closestCarDist = nEnterCarRangeMultiplier * ENTER_CAR_MAX_DIST; + CVector pos = GetPosition(); + int16 lastVehicle; + CEntity *vehicles[8]; + + CWorld::FindObjectsInRange(pos, closestCarDist, true, &lastVehicle, 6, vehicles, false, true, false, false, false); + for(int i = 0; i < lastVehicle; i++) { + CVehicle *nearVeh = (CVehicle*)vehicles[i]; + if (m_objective == OBJECTIVE_STEAL_ANY_MISSION_CAR || 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_PARTIAL_FUCKU); + 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->GetProgress() > 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; + + // FIX: ClearObjective() clears m_pedInObjective, 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_PARTIAL_FUCKU, 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 { + m_objective = OBJECTIVE_NONE; + ClearObjective(); + } + } + // fall through + case OBJECTIVE_WANDER: + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer && !bInVehicle) { + m_leaveCarTimer = 0; + m_objective = OBJECTIVE_NONE; + SetWanderPath(m_nPathDir); + } + break; + 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; + } + 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]; + // NB: 25.0f in here is prolly a forgotten setting, all other places now use 30.0f (ENTER_CAR_MAX_DIST) + 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; + } + // Falls to different objectives in III and VC +#ifdef FIX_BUGS + break; +#else + // fall through +#endif + } + case OBJECTIVE_GOTO_SEAT_ON_FOOT: + case OBJECTIVE_GOTO_ATM_ON_FOOT: + case OBJECTIVE_GOTO_BUS_STOP_ON_FOOT: + case OBJECTIVE_GOTO_PIZZA_ON_FOOT: + case OBJECTIVE_GOTO_SHELTER_ON_FOOT: + case OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT: + if (CTimer::GetTimeInMilliseconds() > m_objectiveTimer) { + m_objectiveTimer = 0; + if (m_attractor) + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + } else { + CVector distance = m_nextRoutePointPos - GetPosition(); + distance.z = 0.0f; + if (m_objective == OBJECTIVE_GOTO_SHELTER_ON_FOOT) { + if (m_nMoveState == PEDMOVE_RUN && distance.MagnitudeSqr() < SQR(2.0f)) { + SetMoveState(PEDMOVE_WALK); + bIsRunning = false; + } + if (CWeather::Rain < 0.2f && m_attractor) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + return; + } + } + else if (m_objective == OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT) { + if (m_nMoveState == PEDMOVE_RUN && distance.MagnitudeSqr() < SQR(4.0f)) { + SetMoveState(PEDMOVE_WALK); + bIsRunning = false; + } + CVehicle* pIceCreamVan = GetPedAttractorManager()->GetIceCreamVanForEffect(m_attractor->GetEffect()); + if (0.01f * CTimer::GetTimeStep() * 5.0f < pIceCreamVan->m_fDistanceTravelled) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + return; + } + if (!pIceCreamVan->pDriver || + !pIceCreamVan->pDriver->IsPlayer() || + pIceCreamVan->pDriver->GetPedState() == PED_ARRESTED || + pIceCreamVan->pDriver->GetPedState() == PED_DEAD) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + return; + } + if (!pIceCreamVan->m_bSirenOrAlarm) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + return; + } + if (pIceCreamVan->GetStatus() == STATUS_WRECKED) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + return; + } + } + if (sq(m_distanceToCountSeekDone) < distance.MagnitudeSqr()) { + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer || GetPedState() != PED_SEEK_POS) { + m_vecSeekPos = m_nextRoutePointPos; + SetSeek(m_vecSeekPos, m_distanceToCountSeekDone); + } + } + else { + if (!bReachedAttractorHeadingTarget) { + float fHeadingDistance = m_fRotationCur - m_attractorHeading; + float fSinHeading = Sin(fHeadingDistance); + float fCosHeading = Cos(fHeadingDistance); + if (fSinHeading > 0.0f) { + if (fCosHeading > 0.0f) + m_attractorHeading = m_fRotationCur - Asin(fSinHeading); + else + m_attractorHeading = m_fRotationCur - Acos(fCosHeading); + } + else { + if (fCosHeading > 0.0f) + m_attractorHeading = m_fRotationCur - Asin(fSinHeading); + else + m_attractorHeading = m_fRotationCur + Acos(fCosHeading); + } + m_fRotationDest = m_attractorHeading; + m_headingRate = 3.5f; + bReachedAttractorHeadingTarget = true; + bTurnedAroundOnAttractor = false; + } + if (Abs(m_fRotationCur - m_attractorHeading) >= m_acceptableHeadingOffset && + Abs(m_fRotationCur - m_attractorHeading + TWOPI) >= m_acceptableHeadingOffset && + Abs(m_fRotationCur - m_attractorHeading - TWOPI) >= m_acceptableHeadingOffset) + SetMoveState(PEDMOVE_STILL); + else { + m_fRotationDest = m_fRotationCur; + bReachedAttractorHeadingTarget = false; + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + RestoreHeadingRate(); + GetPedAttractorManager()->BroadcastArrival(this, m_attractor); + if (GetPedAttractorManager()->IsAtHeadOfQueue(this, m_attractor)) { + switch (m_objective) { + case OBJECTIVE_GOTO_SEAT_ON_FOOT: + if (!bTurnedAroundOnAttractor) { + ClearObjective(); + SetWaitState(WAITSTATE_SIT_DOWN, 0); + } + else { + ClearObjective(); + SetWaitState(WAITSTATE_SIT_DOWN_RVRS, 0); + } + break; + case OBJECTIVE_GOTO_ATM_ON_FOOT: + ClearObjective(); + SetWaitState(WAITSTATE_USE_ATM, 0); + break; + case OBJECTIVE_GOTO_BUS_STOP_ON_FOOT: + ClearObjective(); + SetObjective(OBJECTIVE_WAIT_ON_FOOT_AT_BUS_STOP); + break; + case OBJECTIVE_GOTO_PIZZA_ON_FOOT: + ClearObjective(); + m_prevObjective = OBJECTIVE_NONE; + SetObjective(OBJECTIVE_WAIT_ON_FOOT); + m_objectiveTimer = CTimer::GetTimeInMilliseconds() + m_attractor->GetHeadOfQueueWaitTime(); + break; + case OBJECTIVE_GOTO_SHELTER_ON_FOOT: + m_prevObjective = OBJECTIVE_NONE; + SetObjective(OBJECTIVE_WAIT_ON_FOOT_AT_SHELTER); + break; + case OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT: + m_prevObjective = OBJECTIVE_NONE; + SetObjective(OBJECTIVE_WAIT_ON_FOOT_AT_ICE_CREAM_VAN); + break; + } + } else { + m_prevObjective = OBJECTIVE_NONE; + SetObjective(OBJECTIVE_WAIT_ON_FOOT); + m_objectiveTimer = 0; + } + } + } + } + return; + 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() && + (FindPlayerPed() != this || !CPad::GetPad(0)->GetAccelerate() || bBusJacked)) { + + if (m_nPedState != PED_EXIT_CAR && m_nPedState != PED_DRAG_FROM_CAR && m_nPedState != PED_EXIT_TRAIN + && (m_nPedType != PEDTYPE_COP + || m_pMyVehicle->IsBoat() + || m_pMyVehicle->m_vecMoveSpeed.MagnitudeSqr2D() < sq(0.005f))) { +#ifdef GTA_TRAIN + if (m_pMyVehicle->IsTrain()) + SetExitTrain(m_pMyVehicle); + else +#endif + if (m_pMyVehicle->IsBoat()) + SetExitBoat(m_pMyVehicle); + else + SetExitCar(m_pMyVehicle, 0); + } + } else { + RestorePreviousObjective(); + } + } + if (bHeldHostageInCar) { + if (CTheScripts::IsPlayerOnAMission()) { + CVehicle *playerVeh = FindPlayerVehicle(); + if (playerVeh && playerVeh->IsPassenger(this)) { + if (m_leaveCarTimer != 0) + m_leaveCarTimer = 0; + } + } + } + break; + case OBJECTIVE_AIM_GUN_AT: + if (m_pedInObjective) { + if (!bObstacleShowedUpDuringKillObjective) + SetPointGunAt(m_pedInObjective); + + if (m_nMoveState == PEDMOVE_STILL && IsPedInControl()) { + SetLookFlag(m_pedInObjective, false); + TurnBody(); + } + } else { + ClearObjective(); + } + break; + case OBJECTIVE_WAIT_ON_FOOT_AT_SHELTER: + SetIdle(); + if (m_attractor && CWeather::Rain < 0.2f) + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + break; + case OBJECTIVE_KILL_CHAR_ON_BOAT: + SetPedStats(PEDSTAT_TOUGH_GUY); + if (bInVehicle) { + if (m_pedInObjective && m_pedInObjective->m_pCurrentPhysSurface != m_pMyVehicle) { + bObjectiveCompleted = true; + ClearObjective(); + 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 { + SetObjective(OBJECTIVE_LEAVE_CAR, m_pMyVehicle); + } + } else if (m_pedInObjective && !m_pedInObjective->DyingOrDead() && + (!m_pCurrentPhysSurface || !m_pedInObjective->m_pCurrentPhysSurface || + m_pedInObjective->m_pCurrentPhysSurface == m_pCurrentPhysSurface)) { + + CBoat *boatWeAreOn = nil; + if (m_pCurrentPhysSurface && m_pCurrentPhysSurface->IsVehicle() && ((CVehicle*)m_pCurrentPhysSurface)->IsBoat()) + boatWeAreOn = (CBoat*)m_pCurrentPhysSurface; + + if (boatWeAreOn) { + SetObjective(OBJECTIVE_RUN_TO_AREA, boatWeAreOn->GetPosition() - (boatWeAreOn->GetForward() * 10.f)); + } else if (m_pedInObjective) { + SetObjective(OBJECTIVE_FLEE_CHAR_ON_FOOT_ALWAYS, m_pedInObjective); + } + SetMoveAnim(); + } else { + ClearLookFlag(); + SetMoveAnim(); + if (m_pCurrentPhysSurface && m_pCurrentPhysSurface->IsVehicle()) + SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, m_pCurrentPhysSurface); + } + break; + case OBJECTIVE_SOLICIT_FOOT: + if (m_pedInObjective) { + if (m_objectiveTimer < CTimer::GetTimeInMilliseconds()) + bScriptObjectiveCompleted = true; + } else { + bScriptObjectiveCompleted = true; + } + break; + case OBJECTIVE_WAIT_ON_FOOT_AT_BUS_STOP: + SetIdle(); + if (m_attractor) { + float left = GetPosition().x - 10.0f; + float right = GetPosition().x + 10.0f; + float top = GetPosition().y - 10.0f; + float bottom = GetPosition().y + 10.0f; + 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 minDistance = SQR(10.0f); + CVehicle* pBus = nil; + + for (int y = ystart; y <= yend; y++) { + for (int x = xstart; x <= xend; x++) { + CSector* s = CWorld::GetSector(x, y); + for (CPtrNode* pNode = s->m_lists[ENTITYLIST_VEHICLES].first; pNode != nil; pNode = pNode->next) { + CEntity* pEntity = (CEntity*)pNode->item; + if (!pEntity->IsVehicle()) + continue; + CVehicle* pVehicle = (CVehicle*)pEntity; + if (!pVehicle->bIsBus) + continue; + if (pVehicle->GetMoveSpeed().MagnitudeSqr() >= SQR(0.005f)) + continue; + float distanceSq = (GetPosition() - pVehicle->GetPosition()).MagnitudeSqr(); + if (distanceSq < minDistance) { + minDistance = distanceSq; + pBus = pVehicle; + } + } + } + } + + if (pBus) { + if (pBus->m_nNumPassengers >= pBus->m_nNumMaxPassengers - 1) + SetObjective(OBJECTIVE_WAIT_ON_FOOT); + else { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, pBus); + bDontDragMeOutCar = true; + bRemoveMeWhenIGotIntoCar = true; + CPlayerPed *player = FindPlayerPed(); + if (pBus->IsPassenger(player) || pBus->IsDriver(player)) { + bCollectBusFare = true; + } + } + } + } + break; + case OBJECTIVE_WAIT_ON_FOOT_AT_ICE_CREAM_VAN: + { + SetIdle(); + CVehicle* pIceCreamVan = GetPedAttractorManager()->GetIceCreamVanForEffect(m_attractor->GetEffect()); + if (m_attractor && m_nWaitState != WAITSTATE_PLAYANIM_CHAT && pIceCreamVan && pIceCreamVan->pDriver && pIceCreamVan->pDriver->IsPlayer()) { + int time = 5000; + SetWaitState(WAITSTATE_PLAYANIM_CHAT, &time); + break; + } + if (!m_attractor) + break; + CVehicle* pVan = GetPedAttractorManager()->GetIceCreamVanForEffect(m_attractor->GetEffect()); + if (!pVan) + break; + if (0.01f * CTimer::GetTimeStep() * 5.0f < pVan->m_fDistanceTravelled) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + break; + } + if (!pVan->pDriver || !pVan->pDriver->IsPlayer() || pVan->pDriver->GetPedState() == PED_ARRESTED || pVan->pDriver->GetPedState() == PED_DEAD) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + break; + } + if (!pVan->m_bSirenOrAlarm) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + return; // Why? + } + if (pVan->GetStatus() == STATUS_WRECKED) { + GetPedAttractorManager()->DeRegisterPed(this, m_attractor); + return; // Why? + } + break; + } + } + 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_routeType = routeType; + m_routePointsBeingPassed = 1; + m_routePointsPassed = 0; + m_objective = OBJECTIVE_FOLLOW_ROUTE; + m_routeStartPoint = CRouteNode::GetRouteStart(currentPoint); + 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) { + m_routePointsPassed += m_routePointsBeingPassed; + return true; + } + return false; +} + +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 < TOTAL_WEAPON_SLOTS; i++) { + if (GetWeapon(i).m_nAmmoTotal > 0) { + eWeaponType weaponType = GetWeapon(i).m_eWeaponType; + + if (weaponType == WEAPONTYPE_COLT45 || weaponType == WEAPONTYPE_UZI || weaponType == WEAPONTYPE_MP5 || weaponType == WEAPONTYPE_M4 || + weaponType == WEAPONTYPE_COLT45 || weaponType == WEAPONTYPE_PYTHON || weaponType == WEAPONTYPE_SHOTGUN || + weaponType == WEAPONTYPE_SPAS12_SHOTGUN || weaponType == WEAPONTYPE_STUBBY_SHOTGUN || + weaponType == WEAPONTYPE_ROCKETLAUNCHER || weaponType == WEAPONTYPE_SNIPERRIFLE || weaponType == WEAPONTYPE_FLAMETHROWER) { + 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 && !bCrouchWhenScared)) + return; + + if (m_leader == pedWithGun) + return; + + if (m_nWaitState == WAITSTATE_PLAYANIM_HANDSUP || m_nWaitState == WAITSTATE_PLAYANIM_HANDSCOWER || + (GetPosition() - pedWithGun->GetPosition()).MagnitudeSqr2D() > 225.0f || (m_nPedType == PEDTYPE_GANG7 && pedWithGun == FindPlayerPed())) + 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; + } + + if (m_nPedType == PEDTYPE_GANG7 && attacker->IsPed() && ((CPed*)attacker)->IsPlayer()) { + if (m_nPedState != PED_FALL) { + SetFall(15000, (AnimationId)(ANIM_STD_KO_FRONT + CGeneral::GetRandomNumberInRange(0, 5)), 0); + } + + } else 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 if ((IsPedInControl() || m_nPedState == PED_DRIVING) && (CharCreatedBy != MISSION_CHAR || bRespondsToThreats)) { + if (m_leader != attacker && (!m_leader || FindPlayerPed() != m_leader) && 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_nWaitState == WAITSTATE_STRIPPER) { + ClearWaitState(); + SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, attacker); + SetObjectiveTimer(20000); + + } else { + 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()) { + if (ped->m_nPedState != PED_DRIVING) + ped->QuitEnteringCar(); + + return; + } + + if (!ped->m_vehDoor) { + ped->QuitEnteringCar(); + return; + } + + if (ped->m_fHealth == 0.0f) { + ped->QuitEnteringCar(); + return; + } + bool itsVan = !!veh->bIsVan; + bool itsBus = !!veh->bIsBus; + bool itsLow = !!veh->bLowVehicle; + eDoors enterDoor; + + 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->IsBike()) { + CPed *pedToDragOut = nil; + if (veh->GetStatus() == STATUS_ABANDONED) { + if (ped->m_vehDoor == CAR_WINDSCREEN) { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ((CBike*)veh)->m_bikeAnimType, ANIM_BIKE_KICK, 6.0f); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + ((CBike*)veh)->bIsBeingPickedUp = true; + + } else if (veh->GetRight().z >= 0.5f || veh->GetRight().z <= -0.5f || veh->GetUp().z <= 0.0f) { + if (enterDoor == DOOR_FRONT_LEFT || enterDoor == DOOR_REAR_LEFT) { + if (veh->GetRight().z > 0.0f) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_BIKE_PICKUP_LHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_BIKE_PULLUP_LHS); + + } else { + if (veh->GetRight().z < 0.0f) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_BIKE_PICKUP_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_BIKE_PULLUP_RHS); + } + ped->m_pVehicleAnim->SetFinishCallback(PedAnimDoorOpenCB, ped); + + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ((CBike*)veh)->m_bikeAnimType, + enterDoor == DOOR_FRONT_LEFT || enterDoor == DOOR_REAR_LEFT ? ANIM_BIKE_JUMPON_LHS : ANIM_BIKE_JUMPON_RHS); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + ((CBike*)veh)->bIsBeingPickedUp = true; + } + } else if (ped->m_vehDoor == CAR_WINDSCREEN) { + if (veh->pDriver->m_nPedState != PED_DRIVING || veh->pDriver->bDontDragMeOutCar) { + ped->QuitEnteringCar(); + } else { + ped->m_pVehicleAnim = CAnimManager::BlendAnimation(ped->GetClump(), ((CBike*)veh)->m_bikeAnimType, ANIM_BIKE_KICK, 6.0f); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + pedToDragOut = veh->pDriver; + } + ((CBike*)veh)->bIsBeingPickedUp = true; + } else { + if (enterDoor == DOOR_FRONT_LEFT || enterDoor == DOOR_FRONT_RIGHT) { + if (veh->pDriver) { + if (veh->m_vecMoveSpeed.Magnitude() > 0.2f) { + ped->QuitEnteringCar(); + ped->SetFall(1000, ped->m_vehDoor == CAR_DOOR_LF || ped->m_vehDoor == CAR_DOOR_LR ? ANIM_STD_HIGHIMPACT_RIGHT : ANIM_STD_HIGHIMPACT_LEFT, false); + return; + } + if (veh->pDriver->m_nPedState != PED_DRIVING || veh->pDriver->bDontDragMeOutCar) { + ped->QuitEnteringCar(); + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, enterDoor == DOOR_FRONT_LEFT ? ANIM_STD_BIKE_ELBOW_LHS : ANIM_STD_BIKE_ELBOW_RHS); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimPullPedOutCB, ped); + pedToDragOut = veh->pDriver; + } + ((CBike*)veh)->bIsBeingPickedUp = true; + + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ((CBike*)veh)->m_bikeAnimType, enterDoor == DOOR_FRONT_LEFT ? ANIM_BIKE_JUMPON_LHS : ANIM_BIKE_JUMPON_RHS); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + ((CBike*)veh)->bIsBeingPickedUp = true; + } + } else { + if (veh->pPassengers[0]) { + if (veh->m_vecMoveSpeed.Magnitude() > 0.2f) { + ped->QuitEnteringCar(); + ped->SetFall(1000, ped->m_vehDoor == CAR_DOOR_LF || ped->m_vehDoor == CAR_DOOR_LR ? ANIM_STD_HIGHIMPACT_RIGHT : ANIM_STD_HIGHIMPACT_LEFT, false); + return; + } + if (veh->pPassengers[0]->m_nPedState != PED_DRIVING || veh->pPassengers[0]->bDontDragMeOutCar) { + ped->QuitEnteringCar(); + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, + enterDoor == DOOR_REAR_LEFT ? ANIM_STD_BIKE_ELBOW_LHS : ANIM_STD_BIKE_ELBOW_RHS); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimPullPedOutCB, ped); + pedToDragOut = veh->pPassengers[0]; + } + ((CBike*)veh)->bIsBeingPickedUp = true; + + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), + ((CBike*)veh)->m_bikeAnimType, enterDoor == DOOR_REAR_LEFT ? ANIM_BIKE_JUMPON_LHS : ANIM_BIKE_JUMPON_RHS); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + ((CBike*)veh)->bIsBeingPickedUp = true; + } + } + } + + if (pedToDragOut) { + pedToDragOut->SetBeingDraggedFromCar(veh, ped->m_vehDoor, false); + if (pedToDragOut->IsGangMember()) + pedToDragOut->RegisterThreatWithGangPeds(ped); + + if (ped->m_nPedType == PEDTYPE_COP && pedToDragOut == FindPlayerPed() && veh->IsBike()) + ((CCopPed*)ped)->m_bDragsPlayerFromCar = 1; + + if (pedToDragOut == veh->pDriver) { + if (veh->pPassengers[0]) + veh->pPassengers[0]->SetBeingDraggedFromCar(veh, CAR_DOOR_LR, false); + } + } + return; + } + + if (veh->IsDoorMissing(enterDoor) || veh->IsDoorFullyOpen(enterDoor)) { + + veh->AutoPilot.m_nCruiseSpeed = 0; + if (ped->m_nPedState == PED_CARJACK || veh->m_nNumMaxPassengers == 0 && veh->pDriver && enterDoor == DOOR_FRONT_RIGHT) { + ped->PedAnimDoorOpenCB(nil, ped); + return; + } + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) { + if (itsVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_VAN, ANIM_STD_VAN_GET_IN_REAR_RHS); + } else if (itsBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_COACH, ANIM_STD_COACH_GET_IN_RHS); + } else if (itsLow) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_RHS); + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_RHS); + } + } else if (itsVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_VAN, ANIM_STD_VAN_GET_IN_REAR_LHS); + } else if (itsBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_COACH, ANIM_STD_COACH_GET_IN_LHS); + } else if (itsLow) { + 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 (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_VAN, ANIM_STD_VAN_OPEN_DOOR_REAR_RHS); + } else if (itsBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_COACH, 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_VAN, ANIM_STD_VAN_OPEN_DOOR_REAR_LHS); + } else if (itsBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_COACH, 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); + + CPlayerPed *player = nil; + CCopPed *cop = nil; + veh->MakeNonDraggedPedsLeaveVehicle(veh->pDriver, ped, player, cop); + if (player && cop) { + cop->QuitEnteringCar(); + cop->SetArrestPlayer(player); + } + + if (player != veh->pDriver) { + veh->pDriver->SetBeingDraggedFromCar(veh, ped->m_vehDoor, true); + if (veh->pDriver->IsGangMember()) + veh->pDriver->RegisterThreatWithGangPeds(ped); + } + return; + } + } + if (veh->IsOpenTopCar() && !veh->pDriver && ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_JUMP_IN_LO_LHS); + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, 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()) { + if(ped->m_nPedState != PED_DRIVING) + ped->QuitEnteringCar(); + + return; + } + + eDoors door; + CPed *pedInSeat = nil; + switch (ped->m_vehDoor) { + case CAR_DOOR_RF: + door = DOOR_FRONT_RIGHT; + pedInSeat = veh->pPassengers[0]; + if (!veh->pPassengers[0] && ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER) + pedInSeat = veh->pDriver; + 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; + if (veh->bIsBus && ped->m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) + pedInSeat = nil; + 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 || + veh->IsCar() && veh->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI && ((CAutomobile*)veh)->m_nWheelsOnGround == 0) { + 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) { + + if (ped->m_vehDoor == CAR_DOOR_LF || ped->m_vehDoor == CAR_DOOR_LR) { + if (veh->IsBike()) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ((CBike*)veh)->m_bikeAnimType, ANIM_BIKE_JUMPON_LHS); + } else if (isVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_VAN, ANIM_STD_VAN_GET_IN_REAR_LHS); + } else if (isBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_COACH, ANIM_STD_COACH_GET_IN_LHS); + } 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); + } + } else { + if (veh->IsBike()) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ((CBike*)veh)->m_bikeAnimType, ANIM_BIKE_JUMPON_RHS); + } else if (isVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_VAN, ANIM_STD_VAN_GET_IN_REAR_RHS); + } else if (isBus) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_COACH, ANIM_STD_COACH_GET_IN_RHS); + } else if (isLow) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_RHS); + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_RHS); + } + + if (ped->m_vehDoor == CAR_DOOR_RF && pedInSeat && veh->IsCar()) + pedInSeat->SetObjective(OBJECTIVE_LEAVE_CAR, veh); + } + + 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); + + if (ped->m_pedInObjective->IsPlayer()) { + ((CCopPed*)ped)->SetArrestPlayer(ped->m_pedInObjective); + } else { + ped->ClearObjective(); + ped->SetWanderPath(CGeneral::GetRandomNumberInRange(0.f, 8.f)); + } + + } else if (!veh->IsDoorMissing(DOOR_FRONT_RIGHT)) { + ((CAutomobile*)veh)->Damage.SetDoorStatus(DOOR_FRONT_RIGHT, DOOR_STATUS_SWINGING); + } + } else { + if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_RHS); + + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } + } else if (pedToDragOut) { + + if (pedToDragOut->m_nPedState != PED_DRIVING || pedToDragOut->bDontDragMeOutCar) { + 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) { + CPlayerPed* player = nil; + CCopPed* cop = nil; + veh->MakeNonDraggedPedsLeaveVehicle(pedToDragOut, ped, player, cop); + if (player && cop) { + cop->QuitEnteringCar(); + veh->SetStatus(STATUS_PLAYER_DISABLED); + cop->SetArrestPlayer(player); + } + + if (player != pedToDragOut) { + pedToDragOut->SetBeingDraggedFromCar(veh, ped->m_vehDoor, false); + if (pedToDragOut->IsGangMember()) + pedToDragOut->RegisterThreatWithGangPeds(ped); + } + } + } + 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) { + if (ped->m_vehDoor == CAR_DOOR_LF || ped->m_vehDoor == CAR_DOOR_LR) { + if (veh->IsBike()) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ((CBike*)veh)->m_bikeAnimType, ANIM_BIKE_JUMPON_LHS); + 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); + } else { + if (veh->IsBike()) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ((CBike*)veh)->m_bikeAnimType, ANIM_BIKE_JUMPON_RHS); + else if (isLow) + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_RHS); + else + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_RHS); + } + ped->m_pVehicleAnim->SetFinishCallback(PedAnimGetInCB, ped); + } else { + ped->QuitEnteringCar(); + } + } else if(ped->m_nPedState != PED_DRIVING) { + 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()) { + if(ped->m_nPedState != PED_DRIVING) + ped->QuitEnteringCar(); + return; + } + + ped->RemoveWeaponWhenEnteringVehicle(); + 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() && !veh->pDriver) { + + if (!animAssoc || animAssoc->animId != ANIM_STD_CAR_JUMP_IN_LO_LHS) + 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; + } + if (veh->IsBike()) { + ped->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; + } + bool doorClosed = true; + if (veh->IsOpenTopCar() && enterDoor == DOOR_FRONT_LEFT && veh->IsDoorClosed(DOOR_FRONT_LEFT)) { + doorClosed = false; + + } else 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); + veh->pDriver->SetRadioStation(); + if (veh->m_nDoorLock == CARLOCK_UNLOCKED) + ped->Say(SOUND_PED_CAR_JACKING); + + } else { + ped->m_objective = OBJECTIVE_ENTER_CAR_AS_PASSENGER; + } + } + } + if (veh->IsDoorMissing(enterDoor) || !doorClosed || isBus) { + PedAnimDoorCloseCB(nil, ped); + } else { + if (enterDoor != DOOR_FRONT_LEFT && enterDoor != DOOR_REAR_LEFT) { + if (isVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_VAN, ANIM_STD_VAN_CLOSE_DOOR_REAR_RHS); + } else if (isLow) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_DOOR_LO_RHS); + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_DOOR_RHS); + } + } else if (isVan) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_VAN, ANIM_STD_VAN_CLOSE_DOOR_REAR_LHS); + } else if (isLow) { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_DOOR_LO_LHS); + } else { + ped->m_pVehicleAnim = CAnimManager::AddAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_CLOSE_DOOR_LHS); + } + ped->m_pVehicleAnim->SetFinishCallback(PedAnimDoorCloseCB, ped); + } +} + +void +CPed::PedShuffle(void) +{ + if (m_pMyVehicle->pPassengers[0] == this) { + CPed *driver = m_pMyVehicle->pDriver; + if (!driver || driver->m_objective == OBJECTIVE_LEAVE_CAR) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, m_pMyVehicle->bLowVehicle ? ANIM_STD_CAR_SHUFFLE_LO_RHS : ANIM_STD_CAR_SHUFFLE_RHS); + m_objective = OBJECTIVE_ENTER_CAR_AS_DRIVER; + m_pMyVehicle->RemovePassenger(this); + bInVehicle = false; + m_pVehicleAnim->SetFinishCallback(PedSetInCarCB, this); + } + } +} + +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 || veh->m_nNumMaxPassengers == 0) { + 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 + && veh->pDriver->m_objective != OBJECTIVE_LEAVE_CAR_AND_DIE + || !veh->IsRoomForPedToLeaveCar(CAR_DOOR_LF, nil))))) { + + if (ped->m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || ped->m_nPedState == PED_CARJACK) + 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 if (ped->m_nPedState != PED_DRIVING) { + ped->QuitEnteringCar(); + } +} + +void +CPed::PedAnimShuffleCB(CAnimBlendAssociation* assoc, void* arg) +{ + CPed *ped = (CPed*)arg; + if (ped->EnteringCar()) { + PedSetInCarCB(nil, ped); + } else if (ped->m_nPedState != PED_DRIVING) { + 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) + return GetPosition(); + + 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; + float offset = CGeneral::GetRandomNumberInRange(1.f, 1.25f) * 1.75f; + switch (m_pedFormation) { + case FORMATION_REAR: + formationOffset = CVector(0.0f, -offset, 0.0f); + break; + case FORMATION_REAR_LEFT: + formationOffset = CVector(-offset, -offset, 0.0f); + break; + case FORMATION_REAR_RIGHT: + formationOffset = CVector(offset, -offset, 0.0f); + break; + case FORMATION_FRONT_LEFT: + formationOffset = CVector(-offset, offset, 0.0f); + break; + case FORMATION_FRONT_RIGHT: + formationOffset = CVector(offset, offset, 0.0f); + break; + case FORMATION_LEFT: + formationOffset = CVector(-offset, 0.0f, 0.0f); + break; + case FORMATION_RIGHT: + formationOffset = CVector(offset, 0.0f, 0.0f); + break; + case FORMATION_FRONT: + formationOffset = CVector(0.0f, offset, 0.0f); + break; + default: + formationOffset = CVector(0.0f, 0.0f, 0.0f); + break; + } + return m_pedInObjective->GetMatrix() * formationOffset; +} + +void +CPed::PedAnimStepOutCarCB(CAnimBlendAssociation* animAssoc, void* arg) +{ + CPed* ped = (CPed*)arg; + + CVehicle* veh = ped->m_pMyVehicle; + if (animAssoc) { + if ((animAssoc->animId == ANIM_STD_ROLLOUT_LHS || animAssoc->animId == ANIM_STD_ROLLOUT_RHS) && ped && ped->m_nPedState == PED_FALL) { + ped->RestoreHeadingRate(); + return; + } + animAssoc->blendDelta = -1000.0f; + if (animAssoc->animId == ANIM_BIKE_GETOFF_BACK) + ped->RestoreHeadingRate(); + } + + if (!veh) { + PedSetOutCarCB(nil, ped); + return; + } + CVector posForZ = ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&posForZ); + if (ped->GetPosition().z - 0.5f > posForZ.z) { + PedSetOutCarCB(nil, ped); + return; + } + + 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; + } + } + + if (ped->m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) + closeDoor = false; + + 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; + CVector autoZPos; + + if (CReplay::IsPlayingBack()) + return; + + if (!bChangedSeat && phase != LINE_UP_TO_CAR_2) { + if (m_pMyVehicle->IsBike()) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_RIDE) || + RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_RIDE_P)) { + SetPedPositionInCar(); + return; + } + } else { + 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_FALL) { + SetPedPositionInCar(); + autoZPos = GetPosition(); + CPedPlacement::FindZCoorForPed(&autoZPos); + if (m_pVehicleAnim && (m_pVehicleAnim->animId == ANIM_STD_ROLLOUT_LHS || m_pVehicleAnim->animId == ANIM_STD_ROLLOUT_RHS) + && autoZPos.z > GetPosition().z) { + m_matrix.GetPosition().z = autoZPos.z; + } + if (m_pVehicleAnim && m_pVehicleAnim->animId == ANIM_BIKE_HIT) { + if (autoZPos.z > GetPosition().z) + m_matrix.GetPosition().z += m_pVehicleAnim->GetProgress() * (autoZPos.z - GetPosition().z); + + } else if (m_pVehicleAnim) { + if (m_pVehicleAnim->animId == ANIM_BIKE_GETOFF_BACK) { + if (autoZPos.z > GetPosition().z) { + m_matrix.GetPosition().z += (m_pVehicleAnim->currentTime * (20.f / 7.f)) * (autoZPos.z - GetPosition().z); + } + } + } + return; + } + 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 if (m_vehDoor == CAR_WINDSCREEN) { + m_fRotationDest = veh->GetForward().Heading() + PI; + } else { + m_fRotationDest = veh->GetForward().Heading(); + } + } + + bool multExtractedFromAnim = false; + bool multExtractedFromAnimBus = false; + float zBlend; + 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: + multExtractedFromAnim = true; + zBlend = Max(m_pVehicleAnim->GetProgress() - 0.3f, 0.0f) / (1.0f - 0.3f); + // fall through + + 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: + + if (!multExtractedFromAnim) { + multExtractedFromAnim = true; + zBlend = Max(m_pVehicleAnim->GetProgress() - 0.5f, 0.0f) / (1.0f - 0.5f); + } + // fall through + + 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: + case ANIM_BIKE_GETOFF_LHS: + case ANIM_BIKE_GETOFF_RHS: + seatPosMult = m_pVehicleAnim->GetProgress(); + break; + case ANIM_STD_CAR_GET_IN_RHS: + case ANIM_STD_CAR_GET_IN_LHS: + if (veh && veh->IsCar() && veh->bIsBus) { + multExtractedFromAnimBus = true; + zBlend = Min(m_pVehicleAnim->GetProgress(), 0.5f) / 0.5f; + } + // fall through + + 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_JUMP_IN_LO_LHS: + { + float animLength = m_pVehicleAnim->hierarchy->totalLength; + seatPosMult = Max(0.0f, 0.5f * animLength - m_pVehicleAnim->currentTime) / animLength; + 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: + if (veh->IsBike()) { + seatPosMult = 0.0f; + } else { + if (bInVehicle) + seatPosMult = 0.0f; + else + seatPosMult = 1.0f; + } + break; + } + } else { + if (veh->IsBike()) { + seatPosMult = 0.0f; + } else { + if (bInVehicle) + seatPosMult = 0.0f; + else + seatPosMult = 1.0f; + } + } + + CVector neededPos; + + if (phase == LINE_UP_TO_CAR_2) { + neededPos = GetPosition(); + } else { + neededPos = GetPositionToOpenCarDoor(veh, m_vehDoor, seatPosMult); + } + + 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) { + vehAnim = m_pVehicleAnim->animId; + if (veh->IsBike() && (m_pVehicleAnim && vehAnim != ANIM_BIKE_KICK)) { + float zBlend; + if (vehAnim != ANIM_BIKE_GETOFF_LHS && vehAnim != ANIM_BIKE_GETOFF_RHS) { + if (vehAnim != ANIM_BIKE_JUMPON_LHS && vehAnim != ANIM_BIKE_JUMPON_RHS) { + zBlend = 0.0f; + } else { + float animLength = m_pVehicleAnim->hierarchy->totalLength; + zBlend = Min(1.0f, 2.0f * m_pVehicleAnim->currentTime / animLength); + } + } else { + zBlend = 1.0f - seatPosMult; + } + float curZ = veh->GetPosition().z + FEET_OFFSET; + neededPos.z = ((curZ - autoZPos.z) - veh->GetHeightAboveRoad()) * zBlend + autoZPos.z; + } else if (multExtractedFromAnim) { + neededPos.z += (autoZPos.z - neededPos.z) * zBlend; + } else { + 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); + } + } + } else { + // We may need to raise up the ped + if (phase == LINE_UP_TO_CAR_START) { + currentZ = GetPosition().z; + + if (neededPos.z > currentZ) { + if (multExtractedFromAnimBus) { + neededPos.z = (neededPos.z - currentZ) * zBlend + currentZ; + } else { + 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() || m_nPedState == PED_DRIVING && veh->IsBike()) { + neededPos.z = Max(currentZ, autoZPos.z); + } + } + } + } + } + + 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 || veh->IsBike()) { + SetPosition(neededPos); + SetHeading(m_fRotationCur); + } else { + CMatrix vehDoorMat(veh->GetMatrix()); + vehDoorMat.GetPosition() += Multiply3x3(vehDoorMat, GetLocalPositionToOpenCarDoor(veh, m_vehDoor, 0.0f)); + + if (m_vehDoor == CAR_WINDSCREEN || veh->bIsBus) { + CMatrix correctionMat; + if (veh->bIsBus && (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR)) + correctionMat.SetRotateZ(-HALFPI); + else if (veh->bIsBus && (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR)) + correctionMat.SetRotateZ(HALFPI); + else + correctionMat.SetRotateZ(PI); + + vehDoorMat = vehDoorMat * correctionMat; + } + GetMatrix() = vehDoorMat; + } + +} + +void +CPed::SetCarJack(CVehicle* car) +{ + uint8 doorFlag; + eDoors door; + CPed *pedInSeat = nil; + + if (car->IsBoat()) + return; + + if (car->IsBike()) { + switch (m_vehDoor) { + case CAR_DOOR_RF: + doorFlag = CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_RF; + door = DOOR_FRONT_RIGHT; + pedInSeat = car->pDriver; + break; + case CAR_DOOR_RR: + doorFlag = CAR_DOOR_FLAG_LR | CAR_DOOR_FLAG_RR; + door = DOOR_REAR_RIGHT; + pedInSeat = car->pPassengers[0]; + break; + case CAR_DOOR_LF: + case CAR_WINDSCREEN: + doorFlag = CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_RF; + door = DOOR_FRONT_LEFT; + pedInSeat = car->pDriver; + break; + case CAR_DOOR_LR: + doorFlag = CAR_DOOR_FLAG_LR | CAR_DOOR_FLAG_RR; + door = DOOR_REAR_LEFT; + pedInSeat = car->pPassengers[0]; + break; + default: + doorFlag = CAR_DOOR_FLAG_UNKNOWN; + break; + } + } else { + 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 || + CharCreatedBy == MISSION_CHAR || (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) +{ + 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++; + + if (m_nPedType == PEDTYPE_COP) + Say(SOUND_PED_ARREST_COP); + else if (car->m_nDoorLock == CARLOCK_UNLOCKED) + Say(SOUND_PED_CAR_JACKING, 1000); + + CVector carEnterPos; + carEnterPos = GetPositionToOpenCarDoor(car, m_vehDoor); + + car->m_nGettingInFlags |= doorFlag; + m_vecOffsetSeek = carEnterPos - GetPosition(); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 600; + + if(car->IsBike()){ + bUsesCollision = false; + PedAnimAlignCB(nil, this); + } else { + 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); + + if (veh->IsBike()) { + ((CBike*)veh)->bIsBeingPickedUp = true; + if (veh->pPassengers[0] != this && (vehEnterType != CAR_WINDSCREEN || veh->pPassengers[0])) + m_vehDoor = CAR_DOOR_LF; + else + m_vehDoor = CAR_DOOR_LR; + } else { + 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(veh->IsBike() ? LINE_UP_TO_CAR_FALL : 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(); + + if(veh->IsBike()) + veh->m_nGettingOutFlags |= GetBikeDoorFlagInclJumpInFromFront(m_vehDoor); + else + veh->m_nGettingOutFlags |= GetCarDoorFlag(m_vehDoor); +} + +void +CPed::BeingDraggedFromCar(void) +{ + AnimationId enterAnim; + bool dontRunAnim = false; + + if (!m_pVehicleAnim) { + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 1000.0f); + + AssocGroupId assocGroup; + if (m_pMyVehicle && m_pMyVehicle->IsBike()) { + enterAnim = ANIM_BIKE_HIT; + assocGroup = ((CBike*)m_pMyVehicle)->m_bikeAnimType; + + } else { + if (m_vehDoor == CAR_DOOR_LF || m_vehDoor == CAR_DOOR_LR) { + if (bWillBeQuickJacked && m_vehDoor == CAR_DOOR_LF) { + 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; + + assocGroup = ASSOCGRP_STD; + } + + if (!dontRunAnim) + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), assocGroup, enterAnim); + + m_pVehicleAnim->SetFinishCallback(PedSetDraggedOutCarCB, this); + + if (m_pMyVehicle && m_pMyVehicle->IsBike()) { + LineUpPedWithCar(LINE_UP_TO_CAR_FALL); + } else { + LineUpPedWithCar(LINE_UP_TO_CAR_START); + } + return; + + } else if (m_pVehicleAnim->animId == ANIM_BIKE_HIT) { + LineUpPedWithCar(LINE_UP_TO_CAR_FALL); + + } else if (m_pVehicleAnim->currentTime <= 1.4f) { + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + LineUpPedWithCar(LINE_UP_TO_CAR_START); + + } else { + LineUpPedWithCar(LINE_UP_TO_CAR_2); + } + + static float mult = 5.f; + if (m_objective == OBJECTIVE_LEAVE_CAR_AND_DIE) { + if (m_pMyVehicle) { + m_pMyVehicle->ProcessOpenDoor(m_vehDoor, ANIM_STD_NUM, m_pVehicleAnim->currentTime * mult); + } + } +} + +void +CPed::SetEnterCar(CVehicle *car, uint32 unused) +{ + if (CCranes::IsThisCarBeingCarriedByAnyCrane(car)) { + RestorePreviousState(); + RestorePreviousObjective(); + } else { + uint8 doorFlag; + eDoors door; + if (car->IsBike()) { + switch (m_vehDoor) { + case CAR_DOOR_RF: + doorFlag = CAR_DOOR_FLAG_RF | CAR_DOOR_FLAG_LF; + door = DOOR_FRONT_RIGHT; + break; + case CAR_DOOR_RR: + doorFlag = CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR; + door = DOOR_REAR_RIGHT; + break; + case CAR_WING_LF: + case CAR_WING_LR: + case CAR_BONNET: + case CAR_BOOT: + doorFlag = CAR_DOOR_FLAG_UNKNOWN; + break; + case CAR_DOOR_LF: + case CAR_WINDSCREEN: + doorFlag = CAR_DOOR_FLAG_RF | CAR_DOOR_FLAG_LF; + door = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + doorFlag = CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR; + door = DOOR_REAR_LEFT; + break; + } + } else { + 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: + if(car->m_nNumMaxPassengers != 0) + doorFlag = CAR_DOOR_FLAG_LF; + else + doorFlag = CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_LR; + + door = DOOR_FRONT_LEFT; + break; + case CAR_DOOR_LR: + if (car->m_nNumMaxPassengers != 0) + doorFlag = CAR_DOOR_FLAG_LR; + else + doorFlag = CAR_DOOR_FLAG_LF | 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; + 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->IsBike()) { + 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()) { + if (car->pHandling->Flags & HANDLING_SIT_IN_BOAT) + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT, 100.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_DRIVE, 100.0f); + + PedSetInCarCB(nil, this); + bVehExitWillBeInstant = true; + + } else if (car->IsBike()) { + PedAnimAlignCB(0, this); + car->AutoPilot.m_nCruiseSpeed = 0; + + } else { + 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_RHS : ANIM_STD_CAR_ALIGN_DOOR_RHS, 4.0f); + else + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, zDiff > 4.4f ? ANIM_STD_CAR_ALIGNHI_DOOR_LHS : ANIM_STD_CAR_ALIGN_DOOR_LHS, 4.0f); + m_pVehicleAnim->SetFinishCallback(PedAnimAlignCB, this); + } +} + +void +CPed::EnterCar(void) +{ + if (IsNotInWreckedVehicle() && m_fHealth > 0.0f) { + CVehicle *veh = m_pMyVehicle; + + // 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); + if (veh->IsBike()) { + CBike *bike = (CBike*)veh; + if (bike->GetStatus() == STATUS_ABANDONED && !bike->bIsBeingPickedUp && m_pVehicleAnim) { + int anim = m_pVehicleAnim->animId; + + // One is pickup and other one is pullup, not same :p + if ((anim == ANIM_STD_BIKE_PICKUP_LHS || anim == ANIM_STD_BIKE_PICKUP_RHS) && m_pVehicleAnim->currentTime > 0.4667f) + bike->bIsBeingPickedUp = true; + else if ((anim == ANIM_STD_BIKE_PULLUP_LHS || anim == ANIM_STD_BIKE_PULLUP_RHS) && m_pVehicleAnim->currentTime > 0.4667f) + bike->bIsBeingPickedUp = true; + } else if (m_nPedState == PED_CARJACK && m_pVehicleAnim) { + if (m_pVehicleAnim->currentTime > 0.4f && m_pVehicleAnim->currentTime - m_pVehicleAnim->timeStep <= 0.4f) { + int anim = m_pVehicleAnim->animId; + if (anim == ANIM_BIKE_KICK) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_187, 3.0f); + } else if (anim == ANIM_STD_BIKE_ELBOW_LHS || anim == ANIM_STD_BIKE_ELBOW_RHS) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_186, 3.0f); + } + } + } + } + } else { + QuitEnteringCar(); + SetDie(); + } +} + +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--; + + if (m_objective == OBJECTIVE_ENTER_CAR_AS_DRIVER || m_objective == OBJECTIVE_ENTER_CAR_AS_PASSENGER) + RestorePreviousObjective(); + + if (veh->IsBike()) { + veh->m_nGettingInFlags &= ~GetBikeDoorFlag(m_vehDoor); + ((CBike*)veh)->bIsBeingPickedUp = false; + } else + veh->m_nGettingInFlags &= ~GetEnterCarDoorFlag(m_vehDoor, veh->m_nNumMaxPassengers); + } + + bUsesCollision = true; + + 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) { + if (veh->AutoPilot.m_nCruiseSpeed == 0 && veh->VehicleCreatedBy == RANDOM_VEHICLE) + veh->AutoPilot.m_nCruiseSpeed = 17; + } +} + +void +CPed::SetExitBoat(CVehicle *boat) +{ + SetPedState(PED_IDLE); + CVector newPos = GetPosition(); + RemoveInCarAnims(); + CColModel* boatCol = boat->GetColModel(); + if (boat->IsUpsideDown()) { + newPos = CVector(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) { + if (!boat->bIsInWater) { + 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(); + SetPosition(newPos); + if (m_pMyVehicle) { + PositionPedOutOfCollision(); + } else { + m_pMyVehicle = boat; + PositionPedOutOfCollision(); + m_pMyVehicle = nil; + } + return; + } + + 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; + } + SetPosition(newPos); + SetMoveState(PEDMOVE_STILL); + m_vecMoveSpeed = boat->m_vecMoveSpeed; +} + +// 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; + + bool canJumpOut = false; + if (!veh->CanPedExitCar(false) && !bBusJacked) { + if (IsPlayer()) { + canJumpOut = veh->IsBike() ? veh->CanPedJumpOffBike() : veh->CanPedJumpOutCar(); + } + if (!canJumpOut) { + if (veh->pDriver) { + if (veh->pDriver->IsPlayer()) { + if (veh->pDriver != this) { + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 5000; + bHeldHostageInCar = true; + } + } else { + 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->IsBike()) { + if (canJumpOut) { + optedDoorNode = veh->pPassengers[0] == this ? CAR_BUMP_REAR : CAR_BOOT; + } else if (veh->pPassengers[0] == this) { + optedDoorNode = CAR_DOOR_LR; + } else { + optedDoorNode = CAR_DOOR_LF; + } + } else if (veh->bIsBus) { + optedDoorNode = CAR_DOOR_LF; + } else 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; + if (veh->IsBike()) { + switch (optedDoorNode) { + case CAR_BUMP_REAR: + case CAR_DOOR_RR: + case CAR_DOOR_LR: + if (veh->m_nGettingInFlags & (CAR_DOOR_FLAG_LR | CAR_DOOR_FLAG_RR)) + someoneEntersFromOurExitDoor = true; + break; + case CAR_DOOR_RF: + case CAR_DOOR_LF: + case CAR_BOOT: + if (veh->m_nGettingInFlags & (CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_RF)) + someoneEntersFromOurExitDoor = true; + break; + default: + break; + } + } else { + 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) { +#if defined GTAVC_JP_PATCH || defined FIX_BUGS + if (veh->pDriver == this && !IsPlayer() && veh == CGameLogic::pShortCutTaxi) { + m_objective = OBJECTIVE_NONE; + return; + } +#endif + bool thereIsRoom; + if (canJumpOut) { + thereIsRoom = 1; + } else { + // Again, unused... + // CVector exitPos = GetPositionToOpenCarDoor(veh, optedDoorNode); + thereIsRoom = veh->IsRoomForPedToLeaveCar(optedDoorNode, nil); + } + + if (!thereIsRoom) { + bool trySideSeat = false; + CPed *pedOnSideSeat; + int firstOptedDoor = optedDoorNode; + if (veh->IsBike()) { + 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; + } + } else { + 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; + + // needed for PositionPedOutOfCollision() + optedDoorNode = firstOptedDoor; + m_vehDoor = firstOptedDoor; + PositionPedOutOfCollision(); + teleportNeeded = true; + } + } + + if (!teleportNeeded && veh->IsOnItsSide()) { + m_vehDoor = optedDoorNode; + PositionPedOutOfCollision(); + 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); + } + + 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; + RemoveInCarAnims(); + SetMoveState(PEDMOVE_NONE); + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, 100.0f); + veh->AutoPilot.m_nCruiseSpeed = 0; + + if (teleportNeeded) { + PedSetOutCarCB(nil, this); + } else { + if (veh->GetUp().z <= -0.8f && !veh->IsBike()) { + 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); + + } else if (veh->IsBike()) { + CBike* bike = (CBike*)veh; + switch (m_vehDoor) { + case CAR_BUMP_REAR: + case CAR_BOOT: + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_GETOFF_BACK); + break; + case CAR_DOOR_RF: + case CAR_DOOR_RR: + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_GETOFF_RHS); + break; + case CAR_DOOR_LF: + case CAR_DOOR_LR: + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_GETOFF_LHS); + break; + default: + break; + } + int8 exitFlags = 0; + + // Bike door flags incl. passenger jump off + switch (m_vehDoor) { + case CAR_BUMP_REAR: + case CAR_DOOR_RR: + case CAR_DOOR_LR: + exitFlags = CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR; + break; + case CAR_DOOR_RF: + case CAR_DOOR_LF: + case CAR_BOOT: + exitFlags = CAR_DOOR_FLAG_RF | CAR_DOOR_FLAG_LF; + break; + } + + // Passenger get off + if (m_vehDoor == CAR_BUMP_REAR || m_vehDoor == CAR_BOOT) { + m_pVehicleAnim->SetFinishCallback(RestoreHeadingRateCB, this); + m_headingRate = 0.0f; + + } else { + veh->m_nGettingOutFlags |= exitFlags; + m_pVehicleAnim->SetFinishCallback(PedAnimStepOutCarCB, this); + } + + } else { + switch (m_vehDoor) { + case CAR_DOOR_RF: + if (canJumpOut) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROLLOUT_RHS); + } else if (veh->bIsBus) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_COACH, 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); + } + break; + case CAR_DOOR_RR: + if (canJumpOut) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROLLOUT_RHS); + } else if (veh->bIsVan) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_VAN, 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 (canJumpOut) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROLLOUT_LHS); + } else if (veh->bIsBus) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_COACH, 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); + } + break; + case CAR_DOOR_LR: + if (canJumpOut) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_ROLLOUT_LHS); + } else if (veh->bIsVan) { + m_pVehicleAnim = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_VAN, 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 && !canJumpOut) { + veh->m_nGettingOutFlags |= GetCarDoorFlag(m_vehDoor); + } + m_pVehicleAnim->SetFinishCallback(canJumpOut ? RestoreHeadingRateCB : PedAnimStepOutCarCB, 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) { + if (InVehicle()) { + if (m_pMyVehicle->IsBike()) { + if (m_vehDoor == CAR_BOOT || m_vehDoor == CAR_BUMP_REAR) { + ((CBike*)m_pMyVehicle)->KnockOffRider(WEAPONTYPE_UNARMED, 0, this, false); + } + } else if (m_pMyVehicle->IsCar()) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROLLOUT_LHS)) { + ((CAutomobile*)m_pMyVehicle)->KnockPedOutCar(WEAPONTYPE_UNIDENTIFIED, CAR_DOOR_LF, this); + } else if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROLLOUT_RHS)) { + ((CAutomobile*)m_pMyVehicle)->KnockPedOutCar(WEAPONTYPE_UNIDENTIFIED, CAR_DOOR_RF, this); + } + } + } + return; + } + + AnimationId exitAnim = (AnimationId) m_pVehicleAnim->animId; + float animTime = m_pVehicleAnim->currentTime; + + if (exitAnim == ANIM_BIKE_GETOFF_BACK) { + if (animTime > 0.35f && m_pMyVehicle && m_pMyVehicle->IsBike()) + ((CBike*)m_pMyVehicle)->KnockOffRider(WEAPONTYPE_UNARMED, 0, this, false); + else + LineUpPedWithCar(LINE_UP_TO_CAR_FALL); + + } else if (exitAnim == ANIM_STD_ROLLOUT_LHS || exitAnim == ANIM_STD_ROLLOUT_RHS) { + if (animTime > 0.07f && m_pMyVehicle && m_pMyVehicle->IsCar()) { + if (exitAnim == ANIM_STD_ROLLOUT_LHS) { + ((CAutomobile*)m_pMyVehicle)->KnockPedOutCar(WEAPONTYPE_UNIDENTIFIED, CAR_DOOR_LF, this); + } else { + ((CAutomobile*)m_pMyVehicle)->KnockPedOutCar(WEAPONTYPE_UNIDENTIFIED, CAR_DOOR_RF, this); + } + } else { + LineUpPedWithCar(LINE_UP_TO_CAR_FALL); + } + } else { + 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() < SQR(0.2f)) { + foundPed = m_nearPeds[i]; + break; + } + } + if(foundPed && (!foundPed->IsPlayer() || m_nPedType == PEDTYPE_COP || m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS)) + if (animTime > 0.4f && foundPed->IsPedInControl()) + foundPed->SetFall(1000, ANIM_STD_HIGHIMPACT_FRONT, 1); + } + } +} + +CVector +CPed::GetPositionToOpenCarDoor(CVehicle *veh, uint32 component) +{ + CVector doorPos; + CVector vehDoorOffset; + CVehicleModelInfo* vehModel = veh->GetModelInfo(); + + if (veh->IsBike()) { + CBike* bike = (CBike*)veh; + + if (component == CAR_WINDSCREEN) { + doorPos = vehModel->GetFrontSeatPosn(); + return bike->GetMatrix() * (doorPos + + CVector(-vecPedBikeKickAnimOffset.x, vecPedBikeKickAnimOffset.y, -vecPedBikeKickAnimOffset.z)); + } else { + switch (bike->m_bikeAnimType) { + case ASSOCGRP_BIKE_VESPA: + vehDoorOffset = vecPedVespaBikeJumpRhsAnimOffset; + break; + case ASSOCGRP_BIKE_HARLEY: + vehDoorOffset = vecPedHarleyBikeJumpRhsAnimOffset; + break; + case ASSOCGRP_BIKE_DIRT: + vehDoorOffset = vecPedDirtBikeJumpRhsAnimOffset; + break; + default: + vehDoorOffset = vecPedStdBikeJumpRhsAnimOffset; + break; + } + } + + doorPos = vehModel->GetFrontSeatPosn(); + if (component == CAR_DOOR_LR || component == CAR_DOOR_RR) { + doorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + } + + if (component == CAR_DOOR_LR || component == CAR_DOOR_LF) { + vehDoorOffset.x *= -1.f; + } + + CVector correctedPos; + bike->GetCorrectedWorldDoorPosition(correctedPos, vehDoorOffset, doorPos); + return correctedPos; + } else { + float seatOffset; + if (veh->bIsVan && (component == CAR_DOOR_LR || component == CAR_DOOR_RR)) { + seatOffset = 0.0f; + vehDoorOffset = vecPedVanRearDoorAnimOffset; + } else { + seatOffset = veh->pHandling->fSeatOffsetDistance; + if (veh->bLowVehicle) { + vehDoorOffset = vecPedCarDoorLoAnimOffset; + } else { + vehDoorOffset = vecPedCarDoorAnimOffset; + } + } + + switch (component) { + case CAR_DOOR_RF: + doorPos = vehModel->GetFrontSeatPosn(); + doorPos.x += seatOffset; + vehDoorOffset.x = -vehDoorOffset.x; + break; + + case CAR_DOOR_RR: + doorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + doorPos.x += seatOffset; + vehDoorOffset.x = -vehDoorOffset.x; + break; + + case CAR_DOOR_LF: + doorPos = vehModel->GetFrontSeatPosn(); + doorPos.x += seatOffset; + doorPos.x = -doorPos.x; + break; + + case CAR_DOOR_LR: + doorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + doorPos.x += seatOffset; + doorPos.x = -doorPos.x; + break; + + default: + doorPos = vehModel->GetFrontSeatPosn(); + vehDoorOffset = CVector(0.0f, 0.0f, 0.0f); + break; + } + + CVector diffVec = doorPos - vehDoorOffset; + return Multiply3x3(veh->GetMatrix(), diffVec) + veh->GetPosition(); + + //unused + //doorPos = Multiply3x3(veh->GetMatrix(), doorPos) + veh->GetMatrix(); + } +} + +void +CPed::GetNearestDoor(CVehicle *veh, CVector &posToOpen) +{ + CVector *enterOffset = nil; + if (veh->IsBike()) { + if (m_objective != OBJECTIVE_ENTER_CAR_AS_PASSENGER) { + + // If bike didn't fall to ground + if (Abs(veh->GetRight().z) < 0.1f) { + float angleDiff = (GetPosition() - veh->GetPosition()).Heading() - veh->GetForward().Heading(); + + if (angleDiff > PI) + angleDiff -= TWOPI; + else if (angleDiff < -PI) + angleDiff += TWOPI; + + if (Abs(angleDiff) < DEGTORAD(30.f) + && (IsPlayer() && ((CPlayerPed*)this)->m_fMoveSpeed > 1.5f && !m_vehDoor || + !IsPlayer() && m_nPedType != PEDTYPE_COP && m_nMoveState == PEDMOVE_RUN + && m_pedStats->m_temper > 65 + && !m_vehDoor || m_vehDoor == CAR_WINDSCREEN)) { + m_vehDoor = CAR_WINDSCREEN; + posToOpen = GetPositionToOpenCarDoor(veh, CAR_WINDSCREEN); + return; + } + } + } + } else if (m_vehDoor == CAR_DOOR_LF && veh->pDriver && !veh->bLowVehicle && !veh->bIsBus) { + 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 && !veh->IsBike() + && (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->IsBike()) { + if (!veh->pPassengers[0] + && !(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[0] + && !(veh->m_nGettingInFlags & CAR_DOOR_FLAG_RR) + && veh->IsRoomForPedToLeaveCar(CAR_DOOR_RR, nil)) { + rrPos = GetPositionToOpenCarDoor(veh, CAR_DOOR_RR); + canEnter = true; + rrPosDist = rrPos - GetPosition(); + } + } else 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); +} + +// Unused +void CPed::PedSetGetInCarPositionCB(CAnimBlendAssociation* assoc, void* arg) +{ + CPed* pPed = (CPed*)arg; + CMatrix mat(pPed->GetMatrix()); + CVehicle* pVehicle = pPed->m_pMyVehicle; + const CVector& offset = (pVehicle->bIsVan && (pPed->m_vehDoor == CAR_DOOR_RR || pPed->m_vehDoor == CAR_DOOR_LR)) ? vecPedVanRearDoorAnimOffset : vecPedCarDoorAnimOffset; + CVector position = Multiply3x3(mat, offset) + pPed->GetPosition(); + CPedPlacement::FindZCoorForPed(&position); + pPed->SetMoveSpeed(0.0f, 0.0f, 0.0f); + pPed->SetPosition(position); +} + +void +CPed::SetAnimOffsetForEnterOrExitVehicle(void) +{ + // FIX_BUGS: If there were no translations on enter anims, there were overflows all over this function. + + int vanBlock = CAnimManager::GetAnimationBlockIndex("van"); + int bikesBlock = CAnimManager::GetAnimationBlockIndex("bikes"); + int bikevBlock = CAnimManager::GetAnimationBlockIndex("bikev"); + int bikehBlock = CAnimManager::GetAnimationBlockIndex("bikeh"); + int bikedBlock = CAnimManager::GetAnimationBlockIndex("biked"); + CStreaming::RequestAnim(vanBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestAnim(bikesBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestAnim(bikevBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestAnim(bikehBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::RequestAnim(bikedBlock, STREAMFLAGS_DEPENDENCY); + CStreaming::LoadAllRequestedModels(false); + CAnimManager::AddAnimBlockRef(vanBlock); + CAnimManager::AddAnimBlockRef(bikesBlock); + CAnimManager::AddAnimBlockRef(bikevBlock); + CAnimManager::AddAnimBlockRef(bikehBlock); + CAnimManager::AddAnimBlockRef(bikedBlock); + + CAnimBlendHierarchy *enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_JACKEDCAR_LHS)->hierarchy; + CAnimBlendSequence *seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedDraggedOutCarAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LHS)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedCarDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedCarDoorAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_CAR_GET_IN_LO_LHS)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedCarDoorLoAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedCarDoorLoAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_QUICKJACKED)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedQuickDraggedOutCarAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedQuickDraggedOutCarAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_VAN, ANIM_STD_VAN_GET_IN_REAR_LHS)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedVanRearDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedVanRearDoorAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_STD, ANIM_STD_TRAIN_GETOUT)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedTrainDoorAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedTrainDoorAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_BIKE_STANDARD, ANIM_BIKE_JUMPON_LHS)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedStdBikeJumpRhsAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedStdBikeJumpRhsAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_BIKE_VESPA, ANIM_BIKE_JUMPON_LHS)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedVespaBikeJumpRhsAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedVespaBikeJumpRhsAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_BIKE_HARLEY, ANIM_BIKE_JUMPON_LHS)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedHarleyBikeJumpRhsAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedHarleyBikeJumpRhsAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_BIKE_DIRT, ANIM_BIKE_JUMPON_LHS)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedDirtBikeJumpRhsAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedDirtBikeJumpRhsAnimOffset = seq->GetEndTranslation(); + } + } + + enterAssoc = CAnimManager::GetAnimAssociation(ASSOCGRP_BIKE_HARLEY, ANIM_BIKE_KICK)->hierarchy; + seq = enterAssoc->sequences; + if (seq->numFrames > 0) { + if (!seq->HasTranslation()) + vecPedBikeKickAnimOffset = CVector(0.0f, 0.0f, 0.0f); + else { + vecPedBikeKickAnimOffset = seq->GetEndTranslation(); + } + } + + CAnimManager::RemoveAnimBlockRef(vanBlock); + CAnimManager::RemoveAnimBlockRef(bikesBlock); + CAnimManager::RemoveAnimBlockRef(bikevBlock); + CAnimManager::RemoveAnimBlockRef(bikehBlock); + CAnimManager::RemoveAnimBlockRef(bikedBlock); +} + +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()) { + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, veh); + + } 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); + + } 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(); + + CMatrix pedMat(ped->GetMatrix()); + CVector draggedOutOffset; + if (ped->m_pMyVehicle && ped->m_pMyVehicle->IsBike()) { + AssocGroupId animGroup = ((CBike*)ped->m_pMyVehicle)->m_bikeAnimType; + switch (animGroup) { + case ASSOCGRP_BIKE_VESPA: + draggedOutOffset = vecPedVespaBikeJumpRhsAnimOffset; + break; + case ASSOCGRP_BIKE_HARLEY: + draggedOutOffset = vecPedHarleyBikeJumpRhsAnimOffset; + break; + case ASSOCGRP_BIKE_DIRT: + draggedOutOffset = vecPedDirtBikeJumpRhsAnimOffset; + break; + default: + draggedOutOffset = vecPedStdBikeJumpRhsAnimOffset; + break; + } + } else { + draggedOutOffset = CVector(vecPedDraggedOutCarAnimOffset.x, vecPedDraggedOutCarAnimOffset.y, 0.0f); + } + if (ped->m_vehDoor == CAR_DOOR_RF || ped->m_vehDoor == CAR_DOOR_RR) + draggedOutOffset.x = -draggedOutOffset.x; + + CVector posAfterBeingDragged = Multiply3x3(pedMat, draggedOutOffset); + posAfterBeingDragged += ped->GetPosition(); + CPedPlacement::FindZCoorForPed(&posAfterBeingDragged); + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + ped->SetPosition(posAfterBeingDragged); + + if (ped->m_pMyVehicle && !ped->m_pMyVehicle->IsBike() && !ped->m_pMyVehicle->IsRoomForPedToLeaveCar(ped->m_vehDoor, &draggedOutOffset)) { + 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()) { + 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 + && FindPlayerPed()->m_carInObjective == ped->m_pMyVehicle && !CTheScripts::IsPlayerOnAMission()) + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, ped->m_pMyVehicle); + else { + 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); +} + +#ifdef GTA_TRAIN +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); +} +#endif + +void +CPed::RegisterThreatWithGangPeds(CEntity *attacker) +{ + CPed *attackerPed = nil; + if ((CharCreatedBy == MISSION_CHAR && bIsPlayerFriend && (attacker == FindPlayerPed() || attacker == FindPlayerVehicle())) + || (attacker && m_leader == attacker) + || (m_nPedType == PEDTYPE_GANG7 && attacker == FindPlayerPed())) + return; + + 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()) { + attackerPed = ((CVehicle*)attacker)->pDriver; + if (!attackerPed) + return; + } else + 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->CharCreatedBy == RANDOM_CHAR && 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_TOPFUN) { + int16 lastVehicle; + CEntity *vehicles[8]; + CWorld::FindObjectsInRange(GetPosition(), 30.0f, 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 && nearVehDriver->CharCreatedBy == RANDOM_CHAR) { + + 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 = (closeNode2 ? closeNode2 : testNode); + farDist = posDiff; + } else { + ped->m_pNextPathNode = closeNode; + closeDist = 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; + + const 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); + + if (m_pNextPathNode) { + + // Function above decided that directly going to next node makes more sense then seeking the object. + CVector correctedCoords = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + + if ((correctedCoords - ourPos).MagnitudeSqr2D() < seekPosDist.MagnitudeSqr()) { + *bestCoords = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + 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 { + bKindaStayInSamePlace = false; + if (bIsDucking) + ClearDuck(true); + + bCrouchWhenShooting = 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; + } + + int16 lastVehicle = 0; + CEntity* vehicles[8]; + + bool justDucked = false; + CVehicle *foundVeh = nil; + float maxDist = 225.0f; + if (bIsDucking) + ClearDuck(true); + + bCrouchWhenShooting = false; + bool duckingWithoutVeh = false; + if (CTimer::GetTimeInMilliseconds() > m_leaveCarTimer) { + + for(int i = 0; i < 6; i++) { + CPlayerPed *player = (CPlayerPed*)m_pedInObjective; + + if (player->m_pPedAtSafePos[i] == this) { + duckingWithoutVeh = true; + CVector &safePos = player->m_vecSafePos[i]; + bool notRunningToSafePos = false; + + if (m_vecSeekPos.x != safePos.x && m_vecSeekPos.y != safePos.y && m_vecSeekPos.z != safePos.z) + notRunningToSafePos = true; + + if (!notRunningToSafePos) { + CVector target = player->m_vecSafePos[i]; + SetSeek(target, 1.0f); + duckingWithoutVeh = true; + m_attackTimer = CTimer::GetTimeInMilliseconds() + 6000; + bDuckAndCover = true; + } + break; + } + } + if (!duckingWithoutVeh) { + for (int i = 0; i < 6; i++) { + CPlayerPed* player = (CPlayerPed*)m_pedInObjective; + if (!player->m_pPedAtSafePos[i] && player->m_vecSafePos[i].x != 0.0f) { + player->m_pPedAtSafePos[i] = this; + CVector target = player->m_vecSafePos[i]; + SetSeek(target, 1.0f); + m_headingRate = 15.0f; + ClearPointGunAt(); + duckingWithoutVeh = 1; + bIsRunning = true; + bDuckAndCover = true; + justDucked = true; + m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 500; + break; + } + } + } + if (!duckingWithoutVeh) { + CVector pos = GetPosition(); + 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->IsCar() && 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)) { + 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; + m_carInObjective->RegisterReference((CEntity**)&m_carInObjective); + } + m_pSeekTarget = foundVeh; + m_pSeekTarget->RegisterReference((CEntity**)&m_pSeekTarget); + ClearPointGunAt(); + } else { + m_duckAndCoverTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(10000, 15000); + bDuckAndCover = false; + } + } else if (!duckingWithoutVeh) { + bDuckAndCover = false; + } + } + + if (!justDucked && !bDuckAndCover) + return false; + + if (!Seek()) { + if (m_nMoveState == PEDMOVE_STILL) { + bDuckAndCover = false; + return false; + } else + 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); + bCrouchWhenShooting = true; + SetDuck(CGeneral::GetRandomNumberInRange(2000, 5000), true); + return false; +} + +CVector +CPed::GetPositionToOpenCarDoor(CVehicle *veh, uint32 component, float offset) +{ + CVector doorPos; + CMatrix vehMat(veh->GetMatrix()); + + if (veh->IsBike()) { + CVehicleModelInfo* vehModel = (CVehicleModelInfo*)CModelInfo::GetModelInfo(veh->GetModelIndex()); + CVector vehDoorOffset; + CBike* bike = (CBike*)veh; + doorPos = vehModel->GetFrontSeatPosn(); + + if (component == CAR_WINDSCREEN) { +#ifdef FIX_BUGS + return bike->GetMatrix() * (doorPos + + CVector(-vecPedBikeKickAnimOffset.x, vecPedBikeKickAnimOffset.y, -vecPedBikeKickAnimOffset.z)); +#else + return bike->GetMatrix() * (doorPos + vecPedBikeKickAnimOffset); +#endif + } else { + switch (bike->m_bikeAnimType) { + case ASSOCGRP_BIKE_VESPA: + vehDoorOffset = vecPedVespaBikeJumpRhsAnimOffset; + break; + case ASSOCGRP_BIKE_HARLEY: + vehDoorOffset = vecPedHarleyBikeJumpRhsAnimOffset; + break; + case ASSOCGRP_BIKE_DIRT: + vehDoorOffset = vecPedDirtBikeJumpRhsAnimOffset; + break; + default: + vehDoorOffset = vecPedStdBikeJumpRhsAnimOffset; + break; + } + vehDoorOffset.x += offset * bike->pHandling->fSeatOffsetDistance; + if (component == CAR_DOOR_LR || component == CAR_DOOR_RR) { + doorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + } + + if (component == CAR_DOOR_LR || component == CAR_DOOR_LF) + vehDoorOffset.x *= -1.f; + + CVector correctedPos; + bike->GetCorrectedWorldDoorPosition(correctedPos, vehDoorOffset, doorPos); + return correctedPos; + } + } + 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->IsBike()) { + CBike *bike = (CBike*)veh; + vehDoorPos = vehModel->GetFrontSeatPosn(); + + if (component == CAR_WINDSCREEN) { + return bike->GetMatrix() * (vehDoorPos + vecPedBikeKickAnimOffset); + } else { + switch (bike->m_bikeAnimType) { + case ASSOCGRP_BIKE_VESPA: + vehDoorOffset = vecPedVespaBikeJumpRhsAnimOffset; + break; + case ASSOCGRP_BIKE_HARLEY: + vehDoorOffset = vecPedHarleyBikeJumpRhsAnimOffset; + break; + case ASSOCGRP_BIKE_DIRT: + vehDoorOffset = vecPedDirtBikeJumpRhsAnimOffset; + break; + default: + vehDoorOffset = vecPedStdBikeJumpRhsAnimOffset; + break; + } + float xOffsetFromAnim = vehDoorOffset.x + seatPosMult * bike->pHandling->fSeatOffsetDistance; + if (component == CAR_DOOR_LR || component == CAR_DOOR_RR) { + vehDoorPos = vehModel->m_positions[CAR_POS_BACKSEAT]; + } + + if (component == CAR_DOOR_LR || component == CAR_DOOR_LF) + xOffsetFromAnim *= -1.f; + + return bike->GetMatrix() * (vehDoorPos + CVector(xOffsetFromAnim, vehDoorOffset.y, vehDoorOffset.z)); + } + } else { + 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; + } +} + +// TODO: what is this parameter for? +void +CPed::SetDuck(uint32 time, bool sth) +{ + if (bIsDucking || CTimer::GetTimeInMilliseconds() <= m_duckTimer && !sth) { + if (sth && CTimer::GetTimeInMilliseconds() + time > m_duckTimer) + m_duckTimer = CTimer::GetTimeInMilliseconds() + time; + return; + } + + CAnimBlendAssociation *duckAssoc; + if (bCrouchWhenShooting) { + duckAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_WEAPON, 4.0f); + duckAssoc->flags &= ~ASSOC_FADEOUTWHENDONE; + 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(); + else if (bIsDucking && bCrouchWhenShooting) { + CWeaponInfo *weapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CAnimBlendAssociation *crouchAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_WEAPON); + if (!crouchAnim) { + if(GetCrouchFireAnim(weapon)) + crouchAnim = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchFireAnim(weapon)); + } + if (!crouchAnim) { + if(GetCrouchReloadAnim(weapon)) + crouchAnim = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchReloadAnim(weapon)); + } + if (!crouchAnim) { + bIsDucking = false; +#if defined FIX_BUGS || defined FREE_CAM + if (IsPlayer()) + bCrouchWhenShooting = false; +#endif + } + } +} + +void +CPed::ClearDuck(bool clearTimer) +{ + CAnimBlendAssociation *animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_LOW); + } + if (!animAssoc) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_WEAPON); + } + + if (animAssoc) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->blendDelta = -4.0f; + } + bIsDucking = false; + + if (clearTimer) { + m_duckTimer = 0; + } +} + +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 || !IsPedInControl()) + 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()); + SetMoveState(PEDMOVE_WALK); + m_vecSeekPos = boatMat * enterOffset; + 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->IsBike()) { + offset = vecPedStdBikeJumpRhsAnimOffset; + } else 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::AddInCarAnims(CVehicle* car, bool isDriver) +{ + if (car->IsBoat()) { + if (car->pHandling->Flags & HANDLING_SIT_IN_BOAT) { + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT, 100.0f); + } else { + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_DRIVE, 100.0f); + } + } else if (car->IsBike()) { + if (isDriver) { + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ((CBike*)car)->m_bikeAnimType, ANIM_BIKE_RIDE, 100.0f); + } else { + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ((CBike*)car)->m_bikeAnimType, ANIM_BIKE_RIDE_P, 100.0f); + } + } else { + if (isDriver) { + 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); + } + } else { + if (car->bLowVehicle) { + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_P_LO, 100.0f); + } else { + m_pVehicleAnim = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_SIT_P, 100.0f); + } + } + } + + StopNonPartialAnims(); +} + +void +CPed::RemoveDrivebyAnims() +{ + CAnimBlendAssociation *animAssoc; + + AnimationId LeftAnim = ANIM_STD_CAR_DRIVEBY_LEFT; + AnimationId RightAnim = ANIM_STD_CAR_DRIVEBY_RIGHT; + + if (m_pMyVehicle->pHandling->Flags & HANDLING_IS_BIKE) { + LeftAnim = ANIM_BIKE_DRIVEBY_RHS; + RightAnim = ANIM_BIKE_DRIVEBY_LHS; + } else if (m_pMyVehicle->bLowVehicle) { + LeftAnim = ANIM_STD_CAR_DRIVEBY_LEFT_LO; + RightAnim = ANIM_STD_CAR_DRIVEBY_RIGHT_LO; + } + + animAssoc = RpAnimBlendClumpGetAssociation((RpClump*)m_rwObject, ANIM_BIKE_DRIVEBY_RHS); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation((RpClump*)m_rwObject, ANIM_BIKE_DRIVEBY_LHS); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; + animAssoc = RpAnimBlendClumpGetAssociation((RpClump*)m_rwObject, ANIM_BIKE_DRIVEBY_FORWARD); + if (animAssoc) + animAssoc->blendDelta = -1000.0f; +} + +void +CPed::RemoveInCarAnims(void) +{ + CAnimBlendAssociation* assoc; + + for (assoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_DRIVING); assoc; + assoc = RpAnimBlendGetNextAssociation(assoc, ASSOC_DRIVING)) { + assoc->flags |= ASSOC_DELETEFADEDOUT; + assoc->blendDelta = -1000.0f; + } +} + +bool +CPed::PositionPedOutOfCollision(void) +{ + CVehicle *veh = m_pMyVehicle; + if (!veh) + return false; + + if (bDonePositionOutOfCollision) + return true; + + bool foundAPos = false; + CColModel *vehCol = veh->GetColModel(); + CVector vehPos = veh->GetPosition(); + CVector ourPos = GetPosition(); + CVector newPos = ourPos; + CWorld::pIgnoreEntity = veh; + bUsesCollision = false; + bJustCheckCollision = true; + m_vecMoveSpeed = CVector(0.f, 0.f, 0.f); + if (veh->IsOnItsSide()) { + // Top of the veh. + newPos = vehPos; + newPos.z = FEET_OFFSET + vehCol->boundingBox.max.x + vehPos.z; + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) + foundAPos = true; + } + + } else if (m_vehDoor != 0) { + // Try the normal way + CVector pos = GetPositionToOpenCarDoor(m_pMyVehicle, m_vehDoor); + newPos = pos; + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) + foundAPos = true; + } + } + + float vehRelativeExitX = vehCol->boundingBox.min.x - 0.355f; + if (m_vehDoor == CAR_DOOR_RF || m_vehDoor == CAR_DOOR_RR) + vehRelativeExitX = 0.355f + vehCol->boundingBox.max.x; + + if (!foundAPos) { + // Check sides of veh., respective to seat column-veh. center difference(why?) + float exitOffset = vehRelativeExitX - DotProduct(ourPos - vehPos, veh->GetRight()); + newPos = exitOffset * veh->GetRight() + ourPos; + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) + foundAPos = true; + } + } + if (!foundAPos) { + // Iterate through sections of veh. length + static offset on X + float minY = vehCol->boundingBox.min.y; + float ySection = (vehCol->boundingBox.max.y - minY) / 3.f; + for (int i = 0; i < 4; i++) { + float fwdMult = i * ySection + minY; + newPos = vehRelativeExitX * veh->GetRight() + fwdMult * veh->GetForward() + vehPos; + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) { + foundAPos = true; + break; + } + } + } + } + if (!foundAPos) { + // Back of veh. + newPos = (vehCol->boundingBox.min.y - 0.355f) * veh->GetForward() + vehPos; + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) + foundAPos = true; + } + } + if (!foundAPos) { + // Front of veh. + newPos = (0.355f + vehCol->boundingBox.max.y) * veh->GetForward() + vehPos; + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) + foundAPos = true; + } + } + if (!foundAPos) { + // Opposite X side + back + newPos = vehCol->boundingBox.min.y * veh->GetForward() + vehPos - vehRelativeExitX * veh->GetRight(); + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) + foundAPos = true; + } + } + if (!foundAPos) { + // Opposite X side + front + newPos = vehCol->boundingBox.max.y * veh->GetForward() + vehPos - vehRelativeExitX * veh->GetRight(); + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) + foundAPos = true; + } + } + if (!foundAPos) { + // Top of veh. + if (veh->m_vehType == 0) { + newPos = vehCol->boundingBox.max.z * veh->GetUp() + vehPos; + newPos.z += FEET_OFFSET; + GetMatrix().SetTranslate(newPos); + if (!CheckCollision()) { + if (CWorld::GetIsLineOfSightClear(vehPos, newPos, true, false, false, true, false, false, false)) + foundAPos = true; + } + } + } + m_vecMoveSpeed = CVector(0.f, 0.f, 0.f); + m_vecTurnSpeed = CVector(0.f, 0.f, 0.f); + veh->m_vecMoveSpeed = CVector(0.f, 0.f, 0.f); + veh->m_vecTurnSpeed = CVector(0.f, 0.f, 0.f); + CWorld::pIgnoreEntity = nil; + bUsesCollision = true; + bJustCheckCollision = false; + bDonePositionOutOfCollision = true; + if (foundAPos) + return true; + int foundNode = ThePaths.FindNodeClosestToCoors(vehPos, PATH_PED, 999999.9f, true, false, false, false); + if (foundNode < 0) + return false; + newPos = ThePaths.m_pathNodes[foundNode].GetPosition(); + CPedPlacement::FindZCoorForPed(&newPos); + GetMatrix().SetTranslate(newPos); + SetHeading(m_pMyVehicle->GetForward().Heading()); + return true; +} + +// "Any" means he shouldn't have to be in vehicle. +bool +CPed::PositionAnyPedOutOfCollision(void) +{ + CVehicle *veh; + CVector posNearVeh; + CVector posSomewhereClose; + bool putNearVeh = false; + bool putSomewhereClose = false; + int smallestDistNearVeh = 999; + int smallestDistSomewhereClose = 999; + + 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); + if (!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(); + int tryCount = Min(10, (int)halfOfDist); + for (int i = 0; i < tryCount; ++i) { + appropriatePos += halfNormalizedDist; + CVector zCorrectedPos = appropriatePos; + CPedPlacement::FindZCoorForPed(&zCorrectedPos); + + if (Abs(zCorrectedPos.z - warpToPos.z) < 3.0f || Abs(zCorrectedPos.z - appropriatePos.z) < 3.0f) { + 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(); + int tryCount = Min(10, (int)halfOfDist); + for (int i = 0; i < tryCount; ++i) { + appropriatePos += halfNormalizedDist; + CVector zCorrectedPos = appropriatePos; + CPedPlacement::FindZCoorForPed(&zCorrectedPos); + + if (Abs(zCorrectedPos.z - warpToPos.z) < 3.0f || Abs(zCorrectedPos.z - appropriatePos.z) < 3.0f) { + 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; +} + +int32 +CPed::KillCharOnFootArmed(CVector &ourPos, CVector &targetPos, CVector &distWithTarget) +{ + bool killPlayerInNoPoliceZone = false; + if (m_pedInObjective->IsPlayer() && CCullZones::NoPolice()) + killPlayerInNoPoliceZone = true; + + if (!bNotAllowedToDuck || killPlayerInNoPoliceZone) { + if (m_nPedType == PEDTYPE_COP && !m_pedInObjective->GetWeapon()->IsTypeMelee()) + bNotAllowedToDuck = true; + } else { + if (!m_pedInObjective->bInVehicle) { + if (m_pedInObjective->GetWeapon()->IsTypeMelee()) { + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + } else if (DuckAndCover()) { + return CANT_ATTACK; + } + } else { + bNotAllowedToDuck = false; + bCrouchWhenShooting = false; + } + } + if (m_leaveCarTimer > CTimer::GetTimeInMilliseconds() && !bKindaStayInSamePlace) { + SetMoveState(PEDMOVE_STILL); + return CANT_ATTACK; + } + 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(); + + return CANT_ATTACK; + } + } + if (m_pedInObjective->IsPlayer() && m_nPedType != PEDTYPE_COP + && CharCreatedBy != MISSION_CHAR && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) { + SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + + return CANT_ATTACK; + } + if (m_pedInObjective->m_fHealth <= 0.0f) { + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + return CANT_ATTACK; + } + CWeaponInfo *wepInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + float wepRange = wepInfo->m_fRange; + float wepRangeAdjusted = wepRange / 3.f; + + 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 WATCH_UNTIL_HE_DISAPPEARS; + } + SetLookFlag(vehOfTarget, false); + if (m_nMoveState == PEDMOVE_STILL && IsPedInControl()) + TurnBody(); + + if (m_nPedState != PED_CARJACK) { + if (m_pedInObjective->m_nPedState != PED_ARRESTED) { + if (m_attackTimer < CTimer::GetTimeInMilliseconds() && distWithTargetSc < wepRange && distWithTargetSc > 3.0f) { + + SetAttack(vehOfTarget); + SetWeaponLockOnTarget(vehOfTarget); + SetShootTimer(CGeneral::GetRandomNumberInRange(500, 2000)); + + CVector2D dirVehGoing = vehOfTarget->m_vecMoveSpeed; + if (dirVehGoing.Magnitude() > 0.2f) { + CVector2D vehDist = GetPosition() - vehOfTarget->GetPosition(); + vehDist.Normalise(); + dirVehGoing.Normalise(); + if (DotProduct2D(vehDist, dirVehGoing) > 0.8f) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(200, 500)); + SetMoveState(PEDMOVE_STILL); + } + return ATTACK_IN_PROGRESS; + } + if (distWithTargetSc <= m_distanceToCountSeekDone) { + SetAttackTimer(CGeneral::GetRandomNumberInRange(200, 500)); + SetMoveState(PEDMOVE_STILL); + } else { + SetAttackTimer(CGeneral::GetRandomNumberInRange(2000, 5000)); + } + return ATTACK_IN_PROGRESS; + } 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); + } + } + } + } + } + return ATTACK_IN_PROGRESS; + } + if (m_nMoveState == PEDMOVE_STILL && IsPedInControl()) { + SetLookFlag(m_pedInObjective, false); + TurnBody(); + } + if (m_nPedType == PEDTYPE_COP && m_pedInObjective->IsPlayer()) { + float maxArrestDist = 1.5f; + if (((CCopPed*)this)->m_bDragsPlayerFromCar) { + if (m_nPedState == PED_FALL) { + maxArrestDist = 3.5f; + } else if (m_nPedState != PED_DRAG_FROM_CAR) { + ((CCopPed*)this)->m_bDragsPlayerFromCar = 0; + } + } + + if (distWithTargetSc < maxArrestDist) { + if (m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() + || m_pedInObjective->m_nPedState == PED_DRAG_FROM_CAR) { + + ((CCopPed*)this)->SetArrestPlayer(m_pedInObjective); + return WATCH_UNTIL_HE_DISAPPEARS; + } + } + } + /* + if (distWithTargetSc > 0.1f) { + junk code + } */ + + if (m_shotTime != 0 && m_ceaseAttackTimer != 0) { + if (CTimer::GetTimeInMilliseconds() > m_ceaseAttackTimer + m_shotTime) { + ClearLookFlag(); + bObjectiveCompleted = true; + m_shotTime = 0; + return CANT_ATTACK; + } + } + + if (!bKindaStayInSamePlace && !bStopAndShoot && m_nPedState != PED_ATTACK && !bDuckAndCover && !killPlayerInNoPoliceZone) { + if (distWithTargetSc > wepRange + || m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() + || m_pedInObjective->m_nPedState == PED_ARRESTED + || m_pedInObjective->EnteringCar() && distWithTargetSc < 3.0f) { + + if (m_pedInObjective->EnteringCar()) + wepRangeAdjusted = 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 = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + + SetSeek(m_vecSeekPos, m_distanceToCountSeekDone); + } else { + SetSeek(m_pedInObjective, wepRangeAdjusted); + } + if (m_pedInObjective->m_pCurrentPhysSurface && distWithTargetSc < 5.0f) { + bStopAndShoot = true; + b158_8 = true; + SetMoveState(PEDMOVE_STILL); + } else if (b158_8) { + bStopAndShoot = false; + b158_8 = false; + } + return ATTACK_IN_PROGRESS; + } + } + if (m_attackTimer < CTimer::GetTimeInMilliseconds() + && distWithTargetSc < wepRange && m_pedInObjective->m_nPedState != PED_GETUP && m_pedInObjective->m_nPedState != PED_DRAG_FROM_CAR) { + + if (bIsDucking && !bCrouchWhenShooting) { + CAnimBlendAssociation* duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_DOWN); + if (!duckAnim) + duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_LOW); + if (!duckAnim) + duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_WEAPON); + + if (duckAnim) { + duckAnim->flags |= ASSOC_DELETEFADEDOUT; + duckAnim->blendDelta = -4.0f; + } + bIsDucking = false; + return CANT_ATTACK; + } + bObstacleShowedUpDuringKillObjective = false; + SetAttack(m_pedInObjective); + SetWeaponLockOnTarget(m_pedInObjective); + SetShootTimer(CGeneral::GetRandomNumberInRange(600.0f, 1500.0f)); + + int time; + if (distWithTargetSc <= wepRangeAdjusted) + time = CGeneral::GetRandomNumberInRange(100.0f, 500.0f); + else + time = CGeneral::GetRandomNumberInRange(1500.0f, 3000.0f); + + SetAttackTimer(time); + } else { + if (!m_pedInObjective->m_pCurrentPhysSurface && b158_8) { + b158_8 = false; + bStopAndShoot = false; + } + + if (m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT) { + if (bNotAllowedToDuck && bKindaStayInSamePlace && !bIsDucking && bCrouchWhenShooting) { + SetDuck(CGeneral::GetRandomNumberInRange(1000, 5000), false); + return CANT_ATTACK; + } + if (bObstacleShowedUpDuringKillObjective) { + if (m_nPedType == PEDTYPE_COP) { + if (GetWeapon()->m_eWeaponType > WEAPONTYPE_COLT45 + || m_fleeFrom && m_fleeFrom->IsObject()) { + wepRangeAdjusted = 6.0f; + } else if (m_fleeFrom && m_fleeFrom->IsVehicle()) { + wepRangeAdjusted = 4.0f; + } else { + wepRangeAdjusted = 2.0f; + } + } else { + wepRangeAdjusted = 2.0f; + } + } + if (distWithTargetSc <= wepRangeAdjusted) { + 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, wepRangeAdjusted); + bIsRunning = true; + if (m_nPedType == PEDTYPE_COP && FindPlayerPed()->m_pWanted->m_CurrentCops > 1) { + if (CGeneral::GetRandomNumber() & 1) + Say(SOUND_PED_GUNAIMEDAT3); + else + Say(SOUND_PED_GUNAIMEDAT2); + } + } + } + } + } + + if (CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) + m_fRotationDest = CGeneral::GetRadianAngleBetweenPoints(m_pedInObjective->GetPosition().x, m_pedInObjective->GetPosition().y, GetPosition().x, GetPosition().y); + + return ATTACK_IN_PROGRESS; +} + +int32 +CPed::KillCharOnFootMelee(CVector &ourPos, CVector &targetPos, CVector &distWithTarget) +{ + bool killPlayerInNoPoliceZone = false; + float distWithTargetSc = distWithTarget.Magnitude(); + CPlayerPed *victimPlayer = nil; + if (m_pedInObjective->IsPlayer()) + victimPlayer = (CPlayerPed*)m_pedInObjective; + + if (victimPlayer) { + if (CCullZones::NoPolice() + || m_pedInObjective->m_pCurrentPhysSurface + && m_pedInObjective->m_pCurrentPhysSurface != m_pCurrentPhysSurface + && distWithTargetSc < 5.f) { + + if (victimPlayer && victimPlayer->m_bSpeedTimerFlag && (IsGangMember() || m_nPedType == PEDTYPE_COP) + && CharCreatedBy != MISSION_CHAR) { + GiveWeapon(WEAPONTYPE_COLT45, 1000, 1); + SetCurrentWeapon(WEAPONTYPE_COLT45); + SetMoveState(PEDMOVE_STILL); + bStopAndShoot = true; + b158_8 = true; + return CANT_ATTACK; + } + killPlayerInNoPoliceZone = true; + } + } + bNotAllowedToDuck = false; + bStopAndShoot = false; + b158_8 = false; + if (m_leaveCarTimer > CTimer::GetTimeInMilliseconds() && !bKindaStayInSamePlace) { + SetMoveState(PEDMOVE_STILL); + return CANT_ATTACK; + } + if (victimPlayer) { + 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(); + + return CANT_ATTACK; + } + } + if (victimPlayer && m_nPedType != PEDTYPE_COP && CharCreatedBy != MISSION_CHAR + && FindPlayerPed()->m_pWanted->m_CurrentCops != 0) { + SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + + return CANT_ATTACK; + } + if (m_pedInObjective->m_fHealth <= 0.0f) { + bObjectiveCompleted = true; + bScriptObjectiveCompleted = true; + return ATTACK_IN_PROGRESS; + } + bool canReachVictim = false; + uint32 endOfAttack = 0; + CWeaponInfo *wepInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + + // Already calculated at the start + // float distWithTargetSc = distWithTarget.Magnitude(); + float maxDistToKeep = 0.3f; + float wepRange = wepInfo->m_fRange / 2.f; + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED && !IsPlayer() && !(m_pedStats->m_flags & STAT_CAN_KICK)) + wepRange -= 0.3f; + + if (distWithTargetSc <= 5.f && victimPlayer && !victimPlayer->m_bNoPosForMeleeAttack) { + + if (m_pedInObjective->EnteringCar() && wepRange > 2.f) { + m_vecSeekPos = m_pedInObjective->GetPosition(); + wepRange = 1.0f; + maxDistToKeep = 0.5f; + } else { + int8 attackDir = victimPlayer->FindMeleeAttackPoint(this, distWithTarget, endOfAttack); + if (attackDir == -1) { + m_vecSeekPos = victimPlayer->GetPosition(); + maxDistToKeep = 4.0f; + } else { + victimPlayer->GetMeleeAttackCoords(m_vecSeekPos, attackDir, wepRange); + distWithTargetSc = (m_vecSeekPos - GetPosition()).Magnitude(); + canReachVictim = true; + } + } + } else { + m_vecSeekPos = m_pedInObjective->GetPosition(); + maxDistToKeep = Max(0.8f, 0.9f * wepRange); + wepRange *= 1.1f; + if (victimPlayer && victimPlayer->m_bNoPosForMeleeAttack) + victimPlayer = nil; + } + + if (m_pedInObjective->bInVehicle && m_pedInObjective->m_nPedState != PED_DRAG_FROM_CAR) { + CVehicle *vehOfTarget = m_pedInObjective->m_pMyVehicle; + + if (vehOfTarget){ + if (vehOfTarget->bIsInWater || vehOfTarget->GetStatus() == STATUS_PLAYER_DISABLED + || m_pedInObjective->IsPlayer() && CPad::GetPad(0)->ArePlayerControlsDisabled()) { + SetIdle(); + return WATCH_UNTIL_HE_DISAPPEARS; + } + SetLookFlag(vehOfTarget, false); + + if (m_nPedState != PED_CARJACK) { + if (m_pedInObjective->m_nPedState != PED_ARRESTED) { + if (m_nPedState != PED_ATTACK && !bKindaStayInSamePlace && !killPlayerInNoPoliceZone) { + + 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); + } + } + } + } + } + return ATTACK_IN_PROGRESS; + } + if (m_nMoveState == PEDMOVE_STILL && IsPedInControl()) { + SetLookFlag(m_pedInObjective, false); + if(m_nPedState == PED_IDLE || m_nPedState == PED_ATTACK || m_nPedState == PED_FIGHT) + TurnBody(); + } + if (m_nPedType == PEDTYPE_COP && m_pedInObjective->IsPlayer()) { + float maxArrestDist = 1.5f; + if (((CCopPed*)this)->m_bDragsPlayerFromCar) { + if (m_nPedState == PED_FALL) { + maxArrestDist = 3.5f; + } else if (m_nPedState != PED_DRAG_FROM_CAR) { + ((CCopPed*)this)->m_bDragsPlayerFromCar = 0; + } + } + + if (distWithTargetSc < maxArrestDist) { + if (m_pedInObjective->m_getUpTimer > CTimer::GetTimeInMilliseconds() + || m_pedInObjective->m_nPedState == PED_DRAG_FROM_CAR) { + + ((CCopPed*)this)->SetArrestPlayer(m_pedInObjective); + return WATCH_UNTIL_HE_DISAPPEARS; + } + } + } + + if (distWithTargetSc > maxDistToKeep && !bKindaStayInSamePlace && m_nPedState != PED_ATTACK && + (m_nPedState != PED_FIGHT || m_curFightMove == FIGHTMOVE_IDLE) && !killPlayerInNoPoliceZone) { + + bool goForward = false; + + if (m_nPedState == PED_FIGHT) { + if (canReachVictim) { + CVector attackAndVictimDist = m_vecSeekPos - m_pedInObjective->GetPosition(); + CVector victimFarness = attackAndVictimDist / wepRange; + CVector distVec = GetPosition() - m_pedInObjective->GetPosition(); + float distSqr = distVec.MagnitudeSqr(); + if (sq(wepRange) > distSqr && distSqr > 0.05f) { + distVec.Normalise(); + if (DotProduct2D(victimFarness, distVec) > Cos(DEGTORAD(30.f))) + goForward = true; + } + } + } + if (goForward) { + m_curFightMove = FIGHTMOVE_SHUFFLE_F; + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_SHUFFLE_B, 16.f)->SetFinishCallback(FinishFightMoveCB,this); + m_fightState = FIGHTSTATE_NO_MOVE; + m_fightButtonPressure = 0; + m_takeAStepAfterAttack = false; + + } else if (bUsePedNodeSeek && !canReachVictim) { + CVector bestCoords(0.0f, 0.0f, 0.0f); + + if (!m_pNextPathNode) + FindBestCoordsFromNodes(m_vecSeekPos, &bestCoords); + + if (m_pNextPathNode) + m_vecSeekPos = CPathFind::TakeWidthIntoAccountForWandering(m_pNextPathNode, m_randomSeed); + + SetSeek(m_vecSeekPos, m_distanceToCountSeekDone); + } else { + if (canReachVictim) + SetSeek(m_vecSeekPos, maxDistToKeep); + else + SetSeek(m_pedInObjective, maxDistToKeep); + } + return ATTACK_IN_PROGRESS; + } + + 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 = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_LOW); + if (!duckAnim) + duckAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_DUCK_WEAPON); + + if (duckAnim) { + duckAnim->flags |= ASSOC_DELETEFADEDOUT; + duckAnim->blendDelta = -4.0f; + } + bIsDucking = false; + return CANT_ATTACK; + } + + if (canReachVictim || !victimPlayer) { + 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.f, 500.f)); + + int time; + if (endOfAttack <= CTimer::GetTimeInMilliseconds()) + time = CGeneral::GetRandomNumberInRange(100.0f, 1500.0f); + else + time = endOfAttack - CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(400.0f, 1500.0f); + + SetAttackTimer(time); + bObstacleShowedUpDuringKillObjective = false; + } + return ATTACK_IN_PROGRESS; + } else { + if (!m_pedInObjective->m_pCurrentPhysSurface && m_pCurrentPhysSurface && b158_8) { + b158_8 = false; + bStopAndShoot = false; + } + + if (m_nPedState != PED_ATTACK && m_nPedState != PED_FIGHT) { + SetMoveState(PEDMOVE_STILL); + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED) + StartFightAttack(0); + } + return ATTACK_IN_PROGRESS; + } +} + +bool +CPed::CanBeDamagedByThisGangMember(CPed* who) +{ + return m_gangFlags & (1 << (who->m_nPedType - PEDTYPE_GANG1)); +} diff --git a/src/miami/peds/PedAttractor.cpp b/src/miami/peds/PedAttractor.cpp new file mode 100644 index 00000000..427a7165 --- /dev/null +++ b/src/miami/peds/PedAttractor.cpp @@ -0,0 +1,802 @@ +#include "common.h" +#include "PedAttractor.h" + +#include "General.h" +#include "Vehicle.h" +#include "World.h" +#include "MemoryHeap.h" + +const int gcMaxSizeOfAtmQueue = 1; +const int gcMaxSizeOfSeatQueue = 1; +const int gcMaxSizeOfStopQueue = 5; +const int gcMaxSizeOfPizzaQueue = 5; +const int gcMaxSizeOfShelterQueue = 5; +const int gcMaxSizeOfIceCreamQueue = 1; + +std::vector CPedShelterAttractor::ms_displacements; + +CPedAttractorManager* GetPedAttractorManager() +{ +// mobile just has a static here: +/* + static CPedAttractorManager pedAttrMgr; + return &pedAttrMgr; +*/ + static CPedAttractorManager *pedAttrMgr; + if(pedAttrMgr == nil){ + PUSH_MEMID(MEMID_PED_ATTR); + pedAttrMgr = new CPedAttractorManager; + POP_MEMID(); + } + return pedAttrMgr; +} + +CVehicleToEffect::CVehicleToEffect(CVehicle* pVehicle) : m_pVehicle(pVehicle) +{ + m_effects[1].col = CRGBA(0, 0, 0, 0); + m_effects[1].type = EFFECT_PED_ATTRACTOR; + m_effects[1].pos = CVector(2.0f, 1.0f, 0.0f); + m_effects[1].pedattr.useDir = CVector(-1.0f, 0.0f, 0.0f); + m_effects[1].pedattr.queueDir = CVector(-1.0f, 0.0f, 0.0f); + m_effects[1].pedattr.type = ATTRACTOR_ICECREAM; + + m_effects[3].col = CRGBA(0, 0, 0, 0); + m_effects[3].type = EFFECT_PED_ATTRACTOR; + m_effects[3].pos = CVector(2.0f, -0.5f, 0.0f); + m_effects[3].pedattr.useDir = CVector(-1.0f, 0.0f, 0.0f); + m_effects[3].pedattr.queueDir = CVector(-1.0f, 0.0f, 0.0f); + m_effects[3].pedattr.type = ATTRACTOR_ICECREAM; + + m_effects[0].col = CRGBA(0, 0, 0, 0); + m_effects[0].type = EFFECT_PED_ATTRACTOR; + m_effects[0].pos = CVector(-2.0f, 1.0f, 0.0f); + m_effects[0].pedattr.useDir = CVector(1.0f, 0.0f, 0.0f); + m_effects[0].pedattr.queueDir = CVector(1.0f, 0.0f, 0.0f); + m_effects[0].pedattr.type = ATTRACTOR_ICECREAM; + + m_effects[2].col = CRGBA(0, 0, 0, 0); + m_effects[2].type = EFFECT_PED_ATTRACTOR; + m_effects[2].pos = CVector(-2.0f, -0.5f, 0.0f); + m_effects[2].pedattr.useDir = CVector(1.0f, 0.0f, 0.0f); + m_effects[2].pedattr.queueDir = CVector(1.0f, 0.0f, 0.0f); + m_effects[2].pedattr.type = ATTRACTOR_ICECREAM; +} + +CVehicleToEffect& CVehicleToEffect::From(const CVehicleToEffect& other) +{ + m_pVehicle = other.m_pVehicle; + for (int i = 0; i < NUM_ATTRACTORS_FOR_ICECREAM_VAN; i++) { + m_effects[i].col = other.m_effects[i].col; + m_effects[i].type = other.m_effects[i].type; + m_effects[i].pos = other.m_effects[i].pos; + m_effects[i].pedattr = other.m_effects[i].pedattr; + } + return *this; +} + +const C2dEffect* CVehicleToEffect::ChooseEffect(const CVector& pos) const +{ + if (!m_pVehicle) + return nil; + if (DotProduct(pos - m_pVehicle->GetPosition(), m_pVehicle->GetRight()) > 0.0f) { + if (DotProduct(pos - m_pVehicle->GetPosition(), m_pVehicle->GetForward()) > 0.0f) + return &m_effects[1]; + else + return &m_effects[3]; + } + else { + if (DotProduct(pos - m_pVehicle->GetPosition(), m_pVehicle->GetForward()) > 0.0f) + return &m_effects[0]; + else + return &m_effects[2]; + } +} + +bool CVehicleToEffect::HasThisEffect(C2dEffect* pEffect) const +{ + for (int i = 0; i < NUM_ATTRACTORS_FOR_ICECREAM_VAN; i++) { + if (pEffect == &m_effects[i]) + return true; + } + return false; +} + +const C2dEffect* CPedAttractorManager::GetEffectForIceCreamVan(CVehicle* pVehicle, const CVector& pos) +{ + if (!vVehicleToEffect.empty()) { + for (std::vector::const_iterator assoc = vVehicleToEffect.begin(); assoc != vVehicleToEffect.end(); ++assoc) { + if (assoc->GetVehicle() == pVehicle) + return assoc->ChooseEffect(pos); + } + } + PUSH_MEMID(MEMID_PED_ATTR); + CVehicleToEffect effect(pVehicle); + vVehicleToEffect.push_back(effect); + POP_MEMID(); +#ifdef FIX_BUGS + return vVehicleToEffect.back().ChooseEffect(pos); +#else + return effect.ChooseEffect(pos); +#endif +} + +CVehicle* CPedAttractorManager::GetIceCreamVanForEffect(C2dEffect* pEffect) +{ + if (vVehicleToEffect.empty()) + return nil; + for (std::vector::const_iterator assoc = vVehicleToEffect.begin(); assoc != vVehicleToEffect.end(); ++assoc) { + if (assoc->HasThisEffect(pEffect)) + return assoc->GetVehicle(); + } + return nil; +} + +const CPedAttractor* CPedAttractorManager::FindAssociatedAttractor(const C2dEffect* pEffect, std::vector& vecAttractors) +{ + if (vecAttractors.empty()) + return nil; + for (std::vector::const_iterator attractor = vecAttractors.begin(); attractor != vecAttractors.end(); ++attractor) { + if ((*attractor)->GetEffect() == pEffect) + return *attractor; + } + return nil; +} + +void CPedAttractorManager::RemoveIceCreamVanEffects(C2dEffect* pEffect) +{ + CVehicle* pVehicle = GetIceCreamVanForEffect(pEffect); + if (!pVehicle) + return; + if (vVehicleToEffect.empty()) + return; + for (std::vector::iterator assoc = vVehicleToEffect.begin(); assoc != vVehicleToEffect.end();) { + if (assoc->GetVehicle() != pVehicle) { + ++assoc; + continue; + } + uint32 total = 0; + for (uint32 j = 0; j < NUM_ATTRACTORS_FOR_ICECREAM_VAN; j++) { + if (FindAssociatedAttractor(assoc->GetEffect(j), vIceCreamAttractors)) + total++; + } + if (total > 0) + ++assoc; + else + assoc = vVehicleToEffect.erase(assoc); + } +} + +CPedAttractor::CPedAttractor(C2dEffect* pEffect, const CMatrix& matrix, int32 maxpeds, float qdist, float waitTime, float approachTime, float distance, float headingdiff, float posdisp, float headdisp) : + p2dEffect(pEffect), + m_nMaxPedsInAttractor(maxpeds), + m_fQueueDistance(qdist), + m_fTimeInWaitQueue(waitTime), + m_fTimeInApproachingQueue(approachTime), + m_fDistanceToUseAttractor(distance), + m_fAcceptableHeading(headingdiff), + m_fMaxPositionDisplacement(posdisp), + m_fMaxHeadingDisplacement(headdisp) +{ + CPedAttractorManager::ComputeEffectPos(pEffect, matrix, vecEffectPos); + CPedAttractorManager::ComputeEffectQueueDir(pEffect, matrix, vecQueueDir); + CPedAttractorManager::ComputeEffectUseDir(pEffect, matrix, vecUseDir); +} + +void CPedPizzaAttractor::UpdatePedStateOnDeparture(CPed* pPed) const +{ + if (pPed->m_nPedMoney > 10) + pPed->m_nPedMoney -= 10; + else + pPed->m_nPedMoney = 0; +} + +void CPedAtmAttractor::UpdatePedStateOnDeparture(CPed* pPed) const +{ + pPed->m_nPedMoney += 20 * CGeneral::GetRandomNumberInRange(1, 51); +}; + +float CPedAttractor::ComputeDeltaHeading() const +{ + return CGeneral::GetRandomNumberInRange(-m_fMaxHeadingDisplacement, m_fMaxHeadingDisplacement); +} + +float CPedAttractor::ComputeDeltaPos() const +{ + return CGeneral::GetRandomNumberInRange(-m_fMaxPositionDisplacement, m_fMaxPositionDisplacement); +} + +void CPedAttractor::ComputeAttractTime(int32 id, bool approacher, float& time) const +{ + if (approacher) + time = m_fTimeInApproachingQueue; + else + time = m_fTimeInWaitQueue; +} + +void CPedAttractor::ComputeAttractPos(int32 qid, CVector& pos) const +{ + if (!p2dEffect) + return; + pos = vecEffectPos - qid * vecQueueDir * m_fQueueDistance; + if (qid != 0) { + pos.x += ComputeDeltaPos(); + pos.y += ComputeDeltaPos(); + } +} + +CVector CPedShelterAttractor::GetDisplacement(int32 qid) const +{ + if (ms_displacements.empty()) { + int i = 0; + while (i < gcMaxSizeOfShelterQueue) { + float fRandomAngle = CGeneral::GetRandomNumberInRange(0.0f, TWOPI); + float fRandomOffset = CGeneral::GetRandomNumberInRange(0.0f, 2.0f); + CVector vecDisplacement(fRandomOffset * Sin(fRandomAngle), fRandomOffset * Cos(fRandomAngle), 0.0f); + bool close = false; + for (std::vector::const_iterator v = ms_displacements.begin(); v != ms_displacements.end(); ++v) { + if ((*v - vecDisplacement).Magnitude() < 1.0f) { + close = true; + break; + } + } + if (!close) { + ms_displacements.push_back(vecDisplacement); + i++; + } + } + } + return ms_displacements[qid]; +} + +void CPedShelterAttractor::ComputeAttractPos(int qid, CVector& pos) const +{ + if (!p2dEffect) + return; + pos = vecEffectPos + GetDisplacement(qid); +} + +void CPedAttractor::ComputeAttractHeading(int32 qid, float& heading) const +{ + heading = CGeneral::GetRadianAngleBetweenPoints(qid != 0 ? vecQueueDir.x : vecUseDir.x, qid != 0 ? vecQueueDir.y : vecUseDir.y, 0.0f, 0.0f); + if (qid != 0) + heading += ComputeDeltaHeading(); +} + +void CPedShelterAttractor::ComputeAttractHeading(int qid, float& heading) const +{ + heading = CGeneral::GetRandomNumberInRange(0.0f, TWOPI); +} + +bool CPedAttractor::RegisterPed(CPed* pPed) +{ + for (std::vector::iterator pPedIt = vApproachingQueue.begin(); pPedIt != vApproachingQueue.end(); ++pPedIt) { + if (*pPedIt == pPed) { + vApproachingQueue.erase(pPedIt); + return false; + } + } + if (GetNoOfRegisteredPeds() >= m_nMaxPedsInAttractor) + return 0; + vApproachingQueue.push_back(pPed); + CVector pos; + float heading; + float time; + int32 slot = ComputeFreeSlot(); + ComputeAttractPos(slot, pos); + ComputeAttractHeading(slot, heading); + ComputeAttractTime(slot, false, time); + pPed->SetNewAttraction(this, pos, heading, time, slot); + return true; +} + +static bool IsPedUsingAttractorOfThisType(int8 type, CPed* pPed) +{ + switch (type) { + case ATTRACTOR_ATM: + if (pPed->m_objective == OBJECTIVE_GOTO_ATM_ON_FOOT) + return true; + break; + case ATTRACTOR_SEAT: + if (pPed->m_objective == OBJECTIVE_GOTO_SEAT_ON_FOOT) + return true; + break; + case ATTRACTOR_STOP: + if (pPed->m_objective == OBJECTIVE_GOTO_BUS_STOP_ON_FOOT || pPed->m_objective == OBJECTIVE_WAIT_ON_FOOT_AT_BUS_STOP || pPed->m_objective == OBJECTIVE_WAIT_ON_FOOT) + return true; + break; + case ATTRACTOR_PIZZA: + if (pPed->m_objective == OBJECTIVE_GOTO_PIZZA_ON_FOOT || pPed->m_objective == OBJECTIVE_WAIT_ON_FOOT) + return true; + break; + case ATTRACTOR_SHELTER: + if (pPed->m_objective == OBJECTIVE_GOTO_SHELTER_ON_FOOT || pPed->m_objective == OBJECTIVE_WAIT_ON_FOOT_AT_SHELTER) + return true; + break; + case ATTRACTOR_ICECREAM: + if (pPed->m_objective == OBJECTIVE_GOTO_ICE_CREAM_VAN_ON_FOOT || pPed->m_objective == OBJECTIVE_WAIT_ON_FOOT_AT_ICE_CREAM_VAN) + return true; + break; + } + return false; +} + +bool CPedAttractor::DeRegisterPed(CPed* pPed) +{ + for (std::vector::iterator pPedIt = vApproachingQueue.begin(); pPedIt != vApproachingQueue.end(); ++pPedIt) { + if (*pPedIt != pPed) + continue; + pPed->m_attractor = nil; + pPed->m_positionInQueue = -1; + pPed->bHasAlreadyUsedAttractor = true; + + if (IsPedUsingAttractorOfThisType(p2dEffect->pedattr.type, pPed)) + pPed->SetObjective(OBJECTIVE_NONE); + else if (pPed->GetPedState() != PED_IDLE && pPed->GetPedState() != PED_NONE) { + vApproachingQueue.erase(pPedIt); + return true; + } + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(-vecQueueDir.x, -vecQueueDir.y)); + vApproachingQueue.erase(pPedIt); + return true; + } + return BroadcastDeparture(pPed); +} + +bool CPedAttractor::BroadcastArrival(CPed* pPed) +{ + for (std::vector::const_iterator pPedIt = vWaitingQueue.begin(); pPedIt != vWaitingQueue.end(); ++pPedIt) { + if (*pPedIt == pPed) + return false; + } + vWaitingQueue.push_back(pPed); + for (std::vector::iterator pPedIt = vApproachingQueue.begin(); pPedIt != vApproachingQueue.end(); ++pPedIt) { + if (*pPedIt == pPed) { + vApproachingQueue.erase(pPedIt); + break; + } + } + for (std::vector::iterator pPedIt = vApproachingQueue.begin(); pPedIt != vApproachingQueue.end(); ++pPedIt) { + CPed* pPed = *pPedIt; + CVector pos; + float heading; + float time; + int32 slot = ComputeFreeSlot(); + ComputeAttractPos(slot, pos); + ComputeAttractHeading(slot, heading); + ComputeAttractTime(slot, false, time); + pPed->SetNewAttraction(this, pos, heading, time, slot); + } + return true; +} + +bool CPedAttractor::BroadcastDeparture(CPed* pPed) +{ + int qid = -1; + for (uint32 i = 0; i < vWaitingQueue.size(); i++){ + if (vWaitingQueue[i] == pPed) + qid = i; + } + if (qid < 0) + return false; + for (uint32 i = qid + 1; i < vWaitingQueue.size(); i++) { + CVector pos; + float heading; + float time; + ComputeAttractPos(i - 1, pos); + ComputeAttractHeading(i - 1, heading); + ComputeAttractTime(i - 1, true, time); + pPed->SetNewAttraction(this, pos, heading, time, i - 1); + } + pPed->m_attractor = nil; + pPed->m_positionInQueue = -1; + pPed->bHasAlreadyUsedAttractor = true; + if (!IsPedUsingAttractorOfThisType(p2dEffect->pedattr.type, pPed)) { + if (pPed->GetPedState() == PED_IDLE || pPed->GetPedState() == PED_NONE) + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(-vecQueueDir.x, -vecQueueDir.y)); + } + else { + pPed->SetObjective(OBJECTIVE_NONE); + if (qid == 0) + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(vecQueueDir.x, vecQueueDir.y)); + else if (qid == vWaitingQueue.size() - 1) + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(-vecQueueDir.x, -vecQueueDir.y)); + else + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(-vecQueueDir.y, -vecQueueDir.x)); + UpdatePedStateOnDeparture(pPed); + } + vWaitingQueue.erase(vWaitingQueue.begin() + qid); + for (std::vector::iterator pPedIt = vApproachingQueue.begin(); pPedIt != vApproachingQueue.end(); ++pPedIt) { + CPed* pPed = *pPedIt; + CVector pos; + float heading; + float time; + int32 slot = ComputeFreeSlot(); + ComputeAttractPos(slot, pos); + ComputeAttractHeading(slot, heading); + ComputeAttractTime(slot, false, time); + pPed->SetNewAttraction(this, pos, heading, time, slot); + } + return true; +} + +bool CPedShelterAttractor::BroadcastDeparture(CPed* pPed) +{ + int qid = -1; + for (uint32 i = 0; i < vWaitingQueue.size(); i++) { + if (vWaitingQueue[i] == pPed) + qid = i; + } + if (qid < 0) + return false; + pPed->m_attractor = nil; + pPed->m_positionInQueue = -1; + pPed->bHasAlreadyUsedAttractor = true; + if (!IsPedUsingAttractorOfThisType(p2dEffect->pedattr.type, pPed)) { + if (pPed->GetPedState() == PED_IDLE || pPed->GetPedState() == PED_NONE) + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(-vecQueueDir.x, -vecQueueDir.y)); + } + else { + pPed->SetObjective(OBJECTIVE_NONE); + if (qid == 0) + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(vecQueueDir.x, vecQueueDir.y)); + else if (qid == vWaitingQueue.size() - 1) + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(-vecQueueDir.x, -vecQueueDir.y)); + else + pPed->SetWanderPath(CGeneral::GetNodeHeadingFromVector(-vecQueueDir.y, -vecQueueDir.x)); + UpdatePedStateOnDeparture(pPed); + } + vWaitingQueue.erase(vWaitingQueue.begin() + qid); + for (std::vector::iterator pPedIt = vApproachingQueue.begin(); pPedIt != vApproachingQueue.end(); ++pPedIt) { + CPed* pPed = *pPedIt; + CVector pos; + float heading; + float time; + int32 slot = ComputeFreeSlot(); + ComputeAttractPos(slot, pos); + ComputeAttractHeading(slot, heading); + ComputeAttractTime(slot, false, time); + pPed->SetNewAttraction(this, pos, heading, time, slot); + } + return true; +} + +bool CPedAttractor::IsRegisteredWithPed(CPed* pPed) const +{ + for (std::vector::const_iterator pPedIt = vWaitingQueue.begin(); pPedIt != vWaitingQueue.end(); ++pPedIt) { + if (*pPedIt == pPed) + return true; + } + for (std::vector::const_iterator pPedIt = vApproachingQueue.begin(); pPedIt != vApproachingQueue.end(); ++pPedIt) { + if (*pPedIt == pPed) { + return true; + } + } + return false; +} + +bool CPedAttractor::IsInQueue(CPed* pPed) const +{ + for (std::vector::const_iterator pPedIt = vWaitingQueue.begin(); pPedIt != vWaitingQueue.end(); ++pPedIt) { + if (*pPedIt == pPed) + return true; + } + return false; +} + +CPedAttractor* CPedAttractorManager::RegisterPedWithAttractor(CPed* pPed, C2dEffect* pEffect, const CMatrix& matrix) +{ + if (pEffect->type != EFFECT_PED_ATTRACTOR) + return nil; + if (IsPedRegisteredWithEffect(pPed)) + return nil; + switch (pEffect->pedattr.type) { + case ATTRACTOR_ATM: return RegisterPed(pPed, pEffect, matrix, vAtmAttractors); + case ATTRACTOR_SEAT: return RegisterPed(pPed, pEffect, matrix, vSeatAttractors); + case ATTRACTOR_STOP: return RegisterPed(pPed, pEffect, matrix, vStopAttractors); + case ATTRACTOR_PIZZA: return RegisterPed(pPed, pEffect, matrix, vPizzaAttractors); + case ATTRACTOR_SHELTER: return RegisterPed(pPed, pEffect, matrix, vShelterAttractors); + case ATTRACTOR_ICECREAM: return RegisterPed(pPed, pEffect, matrix, vIceCreamAttractors); + } + return nil; +} + +bool CPedAttractorManager::DeRegisterPed(CPed* pPed, CPedAttractor* pAttractor) +{ + if (!pAttractor) + return false; + if (pAttractor->GetEffect()->type != EFFECT_PED_ATTRACTOR) + return nil; + if (!IsPedRegisteredWithEffect(pPed)) + return nil; + switch (pAttractor->GetEffect()->pedattr.type) { + case ATTRACTOR_ATM: return DeRegisterPed(pPed, pAttractor, vAtmAttractors); + case ATTRACTOR_SEAT: return DeRegisterPed(pPed, pAttractor, vSeatAttractors); + case ATTRACTOR_STOP: return DeRegisterPed(pPed, pAttractor, vStopAttractors); + case ATTRACTOR_PIZZA: return DeRegisterPed(pPed, pAttractor, vPizzaAttractors); + case ATTRACTOR_SHELTER: return DeRegisterPed(pPed, pAttractor, vShelterAttractors); + case ATTRACTOR_ICECREAM: return DeRegisterPed(pPed, pAttractor, vIceCreamAttractors); + } + return nil; +} + +bool CPedAttractorManager::BroadcastArrival(CPed* pPed, CPedAttractor* pAttractor) +{ + if (!pAttractor) + return false; + if (pAttractor->GetEffect()->type != EFFECT_PED_ATTRACTOR) + return nil; + if (!IsPedRegisteredWithEffect(pPed)) + return nil; + switch (pAttractor->GetEffect()->pedattr.type) { + case ATTRACTOR_ATM: return BroadcastArrival(pPed, pAttractor, vAtmAttractors); + case ATTRACTOR_SEAT: return BroadcastArrival(pPed, pAttractor, vSeatAttractors); + case ATTRACTOR_STOP: return BroadcastArrival(pPed, pAttractor, vStopAttractors); + case ATTRACTOR_PIZZA: return BroadcastArrival(pPed, pAttractor, vPizzaAttractors); + case ATTRACTOR_SHELTER: return BroadcastArrival(pPed, pAttractor, vShelterAttractors); + case ATTRACTOR_ICECREAM: return BroadcastArrival(pPed, pAttractor, vIceCreamAttractors); + } + return nil; +} + +bool CPedAttractorManager::BroadcastDeparture(CPed* pPed, CPedAttractor* pAttractor) +{ + if (!pAttractor) + return false; + if (pAttractor->GetEffect()->type != EFFECT_PED_ATTRACTOR) + return nil; + if (!IsPedRegisteredWithEffect(pPed)) + return nil; + switch (pAttractor->GetEffect()->pedattr.type) { + case ATTRACTOR_ATM: return BroadcastDeparture(pPed, pAttractor, vAtmAttractors); + case ATTRACTOR_SEAT: return BroadcastDeparture(pPed, pAttractor, vSeatAttractors); + case ATTRACTOR_STOP: return BroadcastDeparture(pPed, pAttractor, vStopAttractors); + case ATTRACTOR_PIZZA: return BroadcastDeparture(pPed, pAttractor, vPizzaAttractors); + case ATTRACTOR_SHELTER: return BroadcastDeparture(pPed, pAttractor, vShelterAttractors); + case ATTRACTOR_ICECREAM: return BroadcastDeparture(pPed, pAttractor, vIceCreamAttractors); + } + return nil; +} + +bool CPedAttractorManager::IsAtHeadOfQueue(CPed* pPed, CPedAttractor* pAttractor) +{ + if (!pAttractor) + return false; + if (pAttractor->GetEffect()->type != EFFECT_PED_ATTRACTOR) + return nil; + if (!IsPedRegisteredWithEffect(pPed)) + return nil; + switch (pAttractor->GetEffect()->pedattr.type) { + case ATTRACTOR_ATM: return IsAtHeadOfQueue(pPed, pAttractor, vAtmAttractors); + case ATTRACTOR_SEAT: return IsAtHeadOfQueue(pPed, pAttractor, vSeatAttractors); + case ATTRACTOR_STOP: return IsAtHeadOfQueue(pPed, pAttractor, vStopAttractors); + case ATTRACTOR_PIZZA: return IsAtHeadOfQueue(pPed, pAttractor, vPizzaAttractors); + case ATTRACTOR_SHELTER: return IsAtHeadOfQueue(pPed, pAttractor, vShelterAttractors); + case ATTRACTOR_ICECREAM: return IsAtHeadOfQueue(pPed, pAttractor, vIceCreamAttractors); + } + return nil; +} + +bool CPedAttractorManager::IsInQueue(CPed* pPed, CPedAttractor* pAttractor) +{ + if (!pAttractor) + return false; + if (pAttractor->GetEffect()->type != EFFECT_PED_ATTRACTOR) + return nil; + if (!IsPedRegisteredWithEffect(pPed)) + return nil; + switch (pAttractor->GetEffect()->pedattr.type) { + case ATTRACTOR_ATM: return IsInQueue(pPed, pAttractor, vAtmAttractors); + case ATTRACTOR_SEAT: return IsInQueue(pPed, pAttractor, vSeatAttractors); + case ATTRACTOR_STOP: return IsInQueue(pPed, pAttractor, vStopAttractors); + case ATTRACTOR_PIZZA: return IsInQueue(pPed, pAttractor, vPizzaAttractors); + case ATTRACTOR_SHELTER: return IsInQueue(pPed, pAttractor, vShelterAttractors); + case ATTRACTOR_ICECREAM: return IsInQueue(pPed, pAttractor, vIceCreamAttractors); + } + return nil; +} + +bool CPedAttractorManager::HasEmptySlot(const C2dEffect* pEffect) +{ + if (!pEffect) + return false; + if (pEffect->type != EFFECT_PED_ATTRACTOR) + return nil; + const CPedAttractor* pAttractor; + switch (pEffect->pedattr.type) { + case ATTRACTOR_ATM: pAttractor = FindAssociatedAttractor(pEffect, vAtmAttractors); break; + case ATTRACTOR_SEAT: pAttractor = FindAssociatedAttractor(pEffect, vSeatAttractors); break; + case ATTRACTOR_STOP: pAttractor = FindAssociatedAttractor(pEffect, vStopAttractors); break; + case ATTRACTOR_PIZZA: pAttractor = FindAssociatedAttractor(pEffect, vPizzaAttractors); break; + case ATTRACTOR_SHELTER: pAttractor = FindAssociatedAttractor(pEffect, vShelterAttractors); break; + case ATTRACTOR_ICECREAM: pAttractor = FindAssociatedAttractor(pEffect, vIceCreamAttractors); break; + default: return true; + } + if (!pAttractor) + return true; + return pAttractor->GetNoOfRegisteredPeds() < pAttractor->GetMaxPedsInAttractor(); +} + +bool CPedAttractorManager::IsPedRegisteredWithEffect(CPed* pPed) +{ + return IsPedRegistered(pPed, vAtmAttractors) || + IsPedRegistered(pPed, vSeatAttractors) || + IsPedRegistered(pPed, vStopAttractors) || + IsPedRegistered(pPed, vPizzaAttractors) || + IsPedRegistered(pPed, vShelterAttractors) || + IsPedRegistered(pPed, vIceCreamAttractors); +} + +void CPedAttractorManager::ComputeEffectPos(const C2dEffect* pEffect, const CMatrix& matrix, CVector& pos) +{ + pos = matrix.GetPosition() + Multiply3x3(matrix, pEffect->pos); +} + +void CPedAttractorManager::ComputeEffectQueueDir(const C2dEffect* pEffect, const CMatrix& matrix, CVector& pos) +{ + pos = Multiply3x3(matrix, pEffect->pedattr.queueDir); +} + +void CPedAttractorManager::ComputeEffectUseDir(const C2dEffect* pEffect, const CMatrix& matrix, CVector& pos) +{ + pos = Multiply3x3(matrix, pEffect->pedattr.useDir); +} + +CPedAttractor* CPedAttractorManager::RegisterPed(CPed* pPed, C2dEffect* pEffect, const CMatrix& matrix, std::vector& vecAttractors) +{ + CPedAttractor* pRegisteredAttractor = nil; + for (std::vector::const_iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + CPedAttractor* pAttractor = *pAttractorIt; + CVector vEffectPos; + ComputeEffectPos(pAttractor->GetEffect(), matrix, vEffectPos); + if (pAttractor->GetEffect() == pEffect && vEffectPos == pAttractor->GetEffectPos()) { + if (!IsApproachable(pEffect, matrix, pAttractor->ComputeFreeSlot(), pPed)) + return nil; + pRegisteredAttractor = pAttractor; + break; + } + } + if (pRegisteredAttractor || !IsApproachable(pEffect, matrix, 0, pPed)) { + if (pRegisteredAttractor) + pRegisteredAttractor->RegisterPed(pPed); + return pRegisteredAttractor; + } + PUSH_MEMID(MEMID_PED_ATTR); + switch (pEffect->pedattr.type) { + case ATTRACTOR_ATM: pRegisteredAttractor = new CPedAtmAttractor(pEffect, matrix, gcMaxSizeOfAtmQueue, 1.0f, 30000.0f, 3000.0f, 0.2f, 0.15f, 0.1f, 0.1f); vecAttractors.push_back(pRegisteredAttractor); break; + case ATTRACTOR_SEAT: pRegisteredAttractor = new CPedSeatAttractor(pEffect, matrix, gcMaxSizeOfSeatQueue, 1.0f, 30000.0f, 3000.0f, 0.125f, 0.1f, 0.1f, 0.1f); vecAttractors.push_back(pRegisteredAttractor); break; + case ATTRACTOR_STOP: pRegisteredAttractor = new CPedStopAttractor(pEffect, matrix, gcMaxSizeOfStopQueue, 1.0f, 30000.0f, 3000.0f, 0.2f, 0.1f, 0.1f, 0.1f); vecAttractors.push_back(pRegisteredAttractor); break; + case ATTRACTOR_PIZZA: pRegisteredAttractor = new CPedPizzaAttractor(pEffect, matrix, gcMaxSizeOfPizzaQueue, 1.0f, 30000.0f, 3000.0f, 0.2f, 0.1f, 0.1f, 0.1f); vecAttractors.push_back(pRegisteredAttractor); break; + case ATTRACTOR_SHELTER: pRegisteredAttractor = new CPedShelterAttractor(pEffect, matrix, gcMaxSizeOfShelterQueue, 1.0f, 30000.0f, 3000.0f, 0.5f, 6.28f, 0.1f, 0.1f); vecAttractors.push_back(pRegisteredAttractor); break; + case ATTRACTOR_ICECREAM: pRegisteredAttractor = new CPedIceCreamAttractor(pEffect, matrix, gcMaxSizeOfIceCreamQueue, 1.0f, 30000.0f, 3000.0f, 0.2f, 0.3f, 0.1f, 0.1f); vecAttractors.push_back(pRegisteredAttractor); break; + } + POP_MEMID(); + if (pRegisteredAttractor) + pRegisteredAttractor->RegisterPed(pPed); + return pRegisteredAttractor; +} + +bool CPedAttractorManager::DeRegisterPed(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors) +{ + if (!pAttractor) + return false; + CPedAttractor* pFound = nil; + for (std::vector::const_iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + if (*pAttractorIt == pAttractor) { + pFound = *pAttractorIt; + break; + } + } + if (!pFound) + return false; + pFound->DeRegisterPed(pPed); + if (pFound->GetNoOfRegisteredPeds() != 0) + return true; + for (std::vector::iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + if (*pAttractorIt == pAttractor) { + vecAttractors.erase(pAttractorIt); + break; + } + } + delete pAttractor; + return true; +} + +bool CPedAttractorManager::BroadcastArrival(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors) +{ + if (!pAttractor) + return false; + CPedAttractor* pFound = nil; + for (std::vector::const_iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + if (*pAttractorIt == pAttractor) { + pFound = *pAttractorIt; + break; + } + } + if (!pFound) + return false; + pFound->BroadcastArrival(pPed); + return true; +} + +bool CPedAttractorManager::BroadcastDeparture(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors) +{ + if (!pAttractor) + return false; + CPedAttractor* pFound = nil; + for (std::vector::const_iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + if (*pAttractorIt == pAttractor) { + pFound = *pAttractorIt; + break; + } + } + if (!pFound) + return false; + pFound->DeRegisterPed(pPed); + if (pFound->GetNoOfRegisteredPeds() != 0) + return true; + for (std::vector::iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + if (*pAttractorIt == pAttractor) { + vecAttractors.erase(pAttractorIt); + break; + } + } + delete pAttractor; + return true; +} + +bool CPedAttractorManager::IsInQueue(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors) +{ + if (!pAttractor) + return false; + for (std::vector::const_iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + if (*pAttractorIt == pAttractor) { + return (*pAttractorIt)->IsInQueue(pPed); + } + } + return false; +} + +bool CPedAttractorManager::IsAtHeadOfQueue(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors) +{ + if (!pAttractor) + return false; + for (std::vector::const_iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + if (*pAttractorIt == pAttractor) { + return (*pAttractorIt)->IsAtHeadOfQueue(pPed); + } + } + return false; +} + +bool CPedAttractorManager::IsPedRegistered(CPed* pPed, std::vector& vecAttractors) +{ + for (std::vector::const_iterator pAttractorIt = vecAttractors.begin(); pAttractorIt != vecAttractors.end(); ++pAttractorIt) { + if ((*pAttractorIt)->IsRegisteredWithPed(pPed)) + return true; + } + return false; +} + +bool CPedAttractorManager::IsApproachable(C2dEffect* pEffect, const CMatrix& matrix, int32, CPed* pPed) +{ + if (pEffect->pedattr.type == ATTRACTOR_SHELTER) { + CVector pos; + ComputeEffectPos(pEffect, matrix, pos); + return CWorld::GetIsLineOfSightClear(pPed->GetPosition(), pos, true, false, false, false, false, false); + } + CVector vecUseDir, vecEffectPos; + ComputeEffectUseDir(pEffect, matrix, vecUseDir); + ComputeEffectPos(pEffect, matrix, vecEffectPos); + float dp = -DotProduct(vecUseDir, vecEffectPos); + if (pEffect->pedattr.type == ATTRACTOR_ATM || pEffect->pedattr.type == ATTRACTOR_PIZZA || pEffect->pedattr.type == ATTRACTOR_ICECREAM) { + vecUseDir = -vecUseDir; + dp = -dp; + } + if (dp + DotProduct(vecEffectPos, pPed->GetPosition()) > 0.0f) { + CVector vecPedToAttractor = pPed->GetPosition() - vecEffectPos; + vecPedToAttractor.Normalise(); + if (DotProduct(vecPedToAttractor, vecUseDir) > 0.25f && CWorld::IsWanderPathClear(pPed->GetPosition(), vecEffectPos, 2.0f, 0)) + return true; + } + return false; +} diff --git a/src/miami/peds/PedAttractor.h b/src/miami/peds/PedAttractor.h new file mode 100644 index 00000000..c55e4028 --- /dev/null +++ b/src/miami/peds/PedAttractor.h @@ -0,0 +1,196 @@ +#pragma once +#include "common.h" +#include + +#include "2dEffect.h" +#include "Ped.h" + +#define NUM_ATTRACTORS_FOR_ICECREAM_VAN 4 + +class CPedAttractor; + +class CVehicleToEffect +{ + CVehicle* m_pVehicle; + C2dEffect m_effects[NUM_ATTRACTORS_FOR_ICECREAM_VAN]; + +public: + CVehicleToEffect(CVehicle* pVehicle); + const C2dEffect* ChooseEffect(const CVector& pos) const; + CVehicleToEffect& From(const CVehicleToEffect& other); + CVehicleToEffect& operator=(const CVehicleToEffect& other) { return From(other); } + ~CVehicleToEffect() { m_pVehicle = nil; } + CVehicle* GetVehicle() const { return m_pVehicle; } + bool HasThisEffect(C2dEffect* pEffect) const; + const C2dEffect* GetEffect(int32 i) const { return &m_effects[i]; } +}; + +class CPedAttractorManager +{ + std::vector vAtmAttractors; + std::vector vSeatAttractors; + std::vector vStopAttractors; + std::vector vPizzaAttractors; + std::vector vShelterAttractors; + std::vector vIceCreamAttractors; + std::vector vVehicleToEffect; + +public: + CPedAttractor* RegisterPedWithAttractor(CPed* pPed, C2dEffect* pEffect, const CMatrix& matrix); + CPedAttractor* RegisterPed(CPed* pPed, C2dEffect* pEffect, const CMatrix& matrix, std::vector& vecAttractors); + bool BroadcastArrival(CPed* pPed, CPedAttractor* pAttractor); + bool BroadcastArrival(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors); + const C2dEffect* GetEffectForIceCreamVan(CVehicle* pVehicle, const CVector& pos); + bool IsApproachable(C2dEffect* pEffect, const CMatrix& matrix, int32, CPed* pPed); + void RemoveIceCreamVanEffects(C2dEffect* pEffect); + bool HasEmptySlot(const C2dEffect* pEffect); + const CPedAttractor* FindAssociatedAttractor(const C2dEffect* pEffect, std::vector& vecAttractors); + bool IsInQueue(CPed* pPed, CPedAttractor* pAttractor); + bool IsInQueue(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors); + bool IsAtHeadOfQueue(CPed* pPed, CPedAttractor* pAttractor); + bool IsAtHeadOfQueue(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors); + bool BroadcastDeparture(CPed* pPed, CPedAttractor* pAttractor); + bool BroadcastDeparture(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors); + bool DeRegisterPed(CPed* pPed, CPedAttractor* pAttractor); + bool DeRegisterPed(CPed* pPed, CPedAttractor* pAttractor, std::vector& vecAttractors); + bool IsPedRegisteredWithEffect(CPed* pPed); + bool IsPedRegistered(CPed* pPed, std::vector& vecAttractors); + CVehicle* GetIceCreamVanForEffect(C2dEffect* pEffect); + + static void ComputeEffectPos(const C2dEffect* pEffect, const CMatrix& matrix, CVector& pos); + static void ComputeEffectQueueDir(const C2dEffect* pEffect, const CMatrix& matrix, CVector& pos); + static void ComputeEffectUseDir(const C2dEffect* pEffect, const CMatrix& matrix, CVector& pos); + +}; + +CPedAttractorManager* GetPedAttractorManager(); + +enum ePedAttractorType +{ + ATTRACTOR_ATM = 0, + ATTRACTOR_SEAT, + ATTRACTOR_STOP, + ATTRACTOR_PIZZA, + ATTRACTOR_SHELTER, + ATTRACTOR_ICECREAM +}; + +class CPedAttractor +{ +protected: + C2dEffect* p2dEffect; + std::vector vApproachingQueue; + std::vector vWaitingQueue; + int32 m_nMaxPedsInAttractor; + float m_fQueueDistance; + float m_fTimeInWaitQueue; + float m_fTimeInApproachingQueue; + float m_fDistanceToUseAttractor; + float m_fAcceptableHeading; + float m_fMaxPositionDisplacement; + float m_fMaxHeadingDisplacement; + CVector vecEffectPos; + CVector vecQueueDir; + CVector vecUseDir; + +public: + virtual float GetHeadOfQueueWaitTime() { return 0.0f; } + virtual ~CPedAttractor() {}; + virtual ePedAttractorType GetType() const = 0; + virtual void UpdatePedStateOnDeparture(CPed* pPed) const = 0; + virtual bool IsAtHeadOfQueue(CPed* pPed) const { return vWaitingQueue.front() == pPed; } + virtual void ComputeAttractPos(int32 id, CVector& pos) const; + virtual void ComputeAttractHeading(int32 id, float& pHeading) const; + virtual bool BroadcastDeparture(CPed* pPed); + + bool IsRegisteredWithPed(CPed* pPed) const; + bool DeRegisterPed(CPed* pPed); + float ComputeDeltaHeading() const; + float ComputeDeltaPos() const; + void ComputeAttractTime(int32 id, bool, float& time) const; + int32 GetNoOfRegisteredPeds() const { return (int32)(vWaitingQueue.size() + vApproachingQueue.size()); } + int32 ComputeFreeSlot() const { return (int32)vWaitingQueue.size(); } + bool IsInQueue(CPed*) const; + bool RegisterPed(CPed*); + bool BroadcastArrival(CPed*); + + CPedAttractor(C2dEffect* pEffect, const CMatrix& matrix, int32 maxpeds, float qdist, float waitTime, float approachTime, float distance, float headingdiff, float posdisp, float headdisp); + + C2dEffect* GetEffect() const { return p2dEffect; } + const CVector& GetEffectPos() const { return vecEffectPos; } + int32 GetMaxPedsInAttractor() const { return m_nMaxPedsInAttractor; } + float GetDistanceToCountSeekDone() const { return m_fDistanceToUseAttractor; } + float GetAcceptableHeading() const { return m_fAcceptableHeading; } +}; + +class CPedAtmAttractor : public CPedAttractor +{ +public: + virtual ePedAttractorType GetType() const { return ATTRACTOR_ATM; }; + virtual void UpdatePedStateOnDeparture(CPed* pPed) const; + CPedAtmAttractor(C2dEffect* pEffect, const CMatrix& matrix, int32 maxpeds, float qdist, float waitTime, float approachTime, float distance, float headingdiff, float posdisp, float headdisp) : + CPedAttractor(pEffect, matrix, maxpeds, qdist, waitTime, approachTime, distance, headingdiff, posdisp, headdisp) + {}; +}; + +class CPedIceCreamAttractor : public CPedAttractor +{ +public: + virtual ~CPedIceCreamAttractor() { GetPedAttractorManager()->RemoveIceCreamVanEffects(p2dEffect); } + virtual ePedAttractorType GetType() const { return ATTRACTOR_ICECREAM; } + virtual void UpdatePedStateOnDeparture(CPed* pPed) const {}; + CPedIceCreamAttractor(C2dEffect* pEffect, const CMatrix& matrix, int32 maxpeds, float qdist, float waitTime, float approachTime, float distance, float headingdiff, float posdisp, float headdisp) : + CPedAttractor(pEffect, matrix, maxpeds, qdist, waitTime, approachTime, distance, headingdiff, posdisp, headdisp) + {}; +}; + +class CPedPizzaAttractor : public CPedAttractor +{ +public: + virtual float GetHeadOfQueueWaitTime() { return 2000.0f; } + virtual ePedAttractorType GetType() const { return ATTRACTOR_PIZZA; } + virtual void UpdatePedStateOnDeparture(CPed* pPed) const; + CPedPizzaAttractor(C2dEffect* pEffect, const CMatrix& matrix, int32 maxpeds, float qdist, float waitTime, float approachTime, float distance, float headingdiff, float posdisp, float headdisp) : + CPedAttractor(pEffect, matrix, maxpeds, qdist, waitTime, approachTime, distance, headingdiff, posdisp, headdisp) + {}; +}; + +class CPedSeatAttractor : public CPedAttractor +{ +public: + virtual ePedAttractorType GetType() const { return ATTRACTOR_SEAT; } + virtual void UpdatePedStateOnDeparture(CPed* pPed) const {}; + CPedSeatAttractor(C2dEffect* pEffect, const CMatrix& matrix, int32 maxpeds, float qdist, float waitTime, float approachTime, float distance, float headingdiff, float posdisp, float headdisp) : + CPedAttractor(pEffect, matrix, maxpeds, qdist, waitTime, approachTime, distance, headingdiff, posdisp, headdisp) + {}; +}; + +class CPedShelterAttractor : public CPedAttractor +{ + static std::vector ms_displacements; +public: + virtual ePedAttractorType GetType() const { return ATTRACTOR_SHELTER; } + virtual bool BroadcastDeparture(CPed*); + virtual void UpdatePedStateOnDeparture(CPed* pPed) const {}; + virtual bool IsAtHeadOfQueue(CPed* pPed) const { return true; } + virtual void ComputeAttractPos(int qid, CVector& pos) const; + virtual void ComputeAttractHeading(int qid, float& heading) const; + + CPedShelterAttractor(C2dEffect* pEffect, const CMatrix& matrix, int32 maxpeds, float qdist, float waitTime, float approachTime, float distance, float headingdiff, float posdisp, float headdisp) : + CPedAttractor(pEffect, matrix, maxpeds, qdist, waitTime, approachTime, distance, headingdiff, posdisp, headdisp) + {}; + + + CVector GetDisplacement(int32 qid) const; +}; + +class CPedStopAttractor : public CPedAttractor +{ +public: + virtual ePedAttractorType GetType() const { return ATTRACTOR_STOP; } + virtual void UpdatePedStateOnDeparture(CPed* pPed) const {}; + + CPedStopAttractor(C2dEffect* pEffect, const CMatrix& matrix, int32 maxpeds, float qdist, float waitTime, float approachTime, float distance, float headingdiff, float posdisp, float headdisp) : + CPedAttractor(pEffect, matrix, maxpeds, qdist, waitTime, approachTime, distance, headingdiff, posdisp, headdisp) + {}; +}; \ No newline at end of file diff --git a/src/miami/peds/PedChat.cpp b/src/miami/peds/PedChat.cpp new file mode 100644 index 00000000..ec6719c6 --- /dev/null +++ b/src/miami/peds/PedChat.cpp @@ -0,0 +1,153 @@ +#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[56] = { + { 500, 800, 500, 2 }, + { 500, 800, 500, 2 }, + { 500, 800, 500, 2 }, + { 500, 800, 500, 2 }, + { 100, 2, 100, 2 }, + { 500, 500, 2000, 1000 }, + { 2000, 50, 2050, 1000 }, + { 5000, 2000, 7000, 3000 }, + { 5000, 2000, 7000, 3000 }, + { 300, 200, 500, 200 }, + { 3000, 1000, 4000, 1000 }, + { 6000, 6000, 6000, 6000 }, + { 4000, 1000, 5000, 1000 }, + { 3000, 1000, 4000, 1000 }, + { 1000, 1000, 2000, 2000 }, + { 1000, 500, 2000, 1500 }, + { 1700, 1000, 3000, 1000 }, + { 800, 200, 1000, 500 }, + { 800, 200, 1000, 500 }, + { 800, 400, 2000, 1000 }, + { 800, 400, 2000, 1000 }, + { 2000, 2000, 4000, 4000 }, + { 2000, 2000, 4000, 1000 }, + { 4000, 1000, 5000, 1000 }, + { 800, 400, 1200, 500 }, + { 5000, 1000, 6000, 2000 }, + { 5000, 1000, 6000, 2000 }, + { 5000, 1000, 6000, 2000 }, + { 5000, 1000, 6000, 2000 }, + { 5000, 1000, 6000, 2000 }, + { 5000, 1000, 6000, 2000 }, + { 5000, 1000, 6000, 2000 }, + { 4000, 2000, 7000, 2000 }, + { 1000, 300, 2000, 1000 }, + { 1500, 1000, 2500, 1000 }, + { 200, 200, 200, 200 }, + { 2000, 1000, 4000, 1000 }, + { 2000, 1000, 4000, 1000 }, + { 1000, 500, 3000, 1000 }, + { 1000, 500, 1000, 1000 }, + { 3000, 2000, 5000, 1000 }, + { 3000, 2000, 5000, 1000 }, + { 3000, 2000, 3000, 2000 }, + { 2000, 1000, 3000, 1000 }, + { 2500, 1000, 5000, 5000 }, + { 2000, 1000, 3000, 2000 }, + { 4000, 1000, 5000, 1000 }, + { 1000, 500, 2000, 4000 }, + { 1000, 500, 2000, 5000 }, + { 2000, 500, 2500, 500 }, + { 1000, 500, 3000, 2000 }, + { 1600, 1000, 2000, 2000 }, + { 2000, 1000, 4000, 2000 }, + { 1500, 1000, 2500, 1000 }, + { 1000, 1000, 5000, 5000 }, + { 0, 0, 0, 0 } +}; + +bool +CPed::ServiceTalkingWhenDead(void) +{ + return m_queuedSound == SOUND_PED_DEATH; +} + +void +CPed::ServiceTalking(void) +{ + if (bBodyPartJustCameOff && m_bodyPartBleeding == PED_HEAD) + return; + + if (!CGame::germanGame && m_pFire) + 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); + + if (m_queuedSound == SOUND_PED_PLAYER_BEFORESEX && IsPlayer()) + m_soundStart += 2000; + + m_lastQueuedSound = m_queuedSound; + m_queuedSound = SOUND_NO_SOUND; + } + } +} + +void +CPed::Say(uint16 audio) +{ + if (3.0f + TheCamera.GetPosition().z < GetPosition().z) + return; + + if (TheCamera.m_CameraAverageSpeed > 1.65f) { + if (audio != SOUND_PED_DAMAGE && audio != SOUND_PED_HIT && audio != SOUND_PED_LAND) + return; + + } else if (TheCamera.m_CameraAverageSpeed > 1.25f) { + if (audio != SOUND_PED_DEATH && + audio != SOUND_PED_DAMAGE && audio != SOUND_PED_HIT && audio != SOUND_PED_LAND && + audio != SOUND_PED_TAXI_WAIT && audio != SOUND_PED_EVADE) + return; + + } else if (TheCamera.m_CameraAverageSpeed > 0.9f) { + switch (audio) { + case SOUND_PED_DEATH: + case SOUND_PED_DAMAGE: + case SOUND_PED_HIT: + case SOUND_PED_LAND: + case SOUND_PED_BURNING: + case SOUND_PED_FLEE_SPRINT: + case SOUND_PED_TAXI_WAIT: + case SOUND_PED_EVADE: + case SOUND_PED_CRASH_VEHICLE: + case SOUND_PED_CRASH_CAR: + case SOUND_PED_ANNOYED_DRIVER: + break; + default: + return; + } + } + + if (audio < m_queuedSound) { + if (audio != m_lastQueuedSound || audio == SOUND_PED_DEATH + + // See VC Ped Speech patch +#ifdef FIX_BUGS + || CommentWaitTime[audio - SOUND_PED_DEATH].m_nOverrideMaxRandomDelayTime + + (uint32)CGeneral::GetRandomNumberInRange(0, CommentWaitTime[audio - SOUND_PED_DEATH].m_nMaxRandomDelayTime) +#else + || CommentWaitTime[m_queuedSound - SOUND_PED_DEATH].m_nOverrideMaxRandomDelayTime + + (uint32)CGeneral::GetRandomNumberInRange(0, CommentWaitTime[m_queuedSound - SOUND_PED_DEATH].m_nMaxRandomDelayTime) +#endif + + m_lastSoundStart <= CTimer::GetTimeInMilliseconds()) { + m_queuedSound = audio; + } + } +} \ No newline at end of file diff --git a/src/miami/peds/PedDebug.cpp b/src/miami/peds/PedDebug.cpp new file mode 100644 index 00000000..0dbabb58 --- /dev/null +++ b/src/miami/peds/PedDebug.cpp @@ -0,0 +1,349 @@ +#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", + "Wait on Foot for cop", + "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", + "GoTo Char on Foot walking", + "Hassle char", + "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 vehicle", + "Take Taxi", + "Catch Train", + "Buy IceCream", + "Steal Any Car", + "Steal any mission car", + "Mug Char", + "Lv car die", + "Goto seat", + "Goto atm", + "Flee car", + "Sunbathe", + "Goto bus stop", + "Goto pizza", + "Goto shelter", + "Aim gun at", + "Wander", + "Wait on foot at shltr", + "Sprint to area", + "Kill char on boat", + "Solicit ped", + "Wait at bus stop", + "Goto ice cream van foot", + "Wait foot icecream van" +}; + +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", + "Bathe", + "Flash", + "Jog", + "Answer mobile", + "Hang out", + "STATES_NO_AI", + "Abseil", + "Sit", + "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", + "Deply stgr" +}; + +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", +#ifdef FIX_BUGS // there's male and female ones + "Tramp", +#endif + "Tourist", + "Prostitute", + "Criminal", + "Busker", + "Taxi Driver", + "Psycho", + "Steward", + "Sports Fan", +}; + +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", + "Sit down", + "Sit down rvrs", + "Sit up", + "Sit idle", + "Use atm", + "Sunbth pre", + "Sunbth down", + "Sunbth idle", + "Riot", + "Fast fall", + "Bomber", + "Stripper", + "Ground attack", + "Lance sitting", + "Handsup simple" +}; + +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(1); + 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/miami/peds/PedFight.cpp b/src/miami/peds/PedFight.cpp new file mode 100644 index 00000000..43ded57a --- /dev/null +++ b/src/miami/peds/PedFight.cpp @@ -0,0 +1,4203 @@ +#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" +#include "Bike.h" +#include "Glass.h" +#include "SpecialFX.h" + +uint16 nPlayerInComboMove; +RpClump* flyingClumpTemp; + +FightMove tFightMoves[NUM_FIGHTMOVES] = +{ + { ANIM_STD_NUM, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_PUNCH, 0.2f, 8.f/30.f, 0.0f, 0.3f, 1.0f, HITLEVEL_HIGH, 1, 0 }, + { ANIM_STD_FIGHT_IDLE, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_FIGHT_SHUFFLE_F, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_FIGHT_KNEE, 4.f/30.f, 0.2f, 0.0f, 0.6f, 1.0f, HITLEVEL_LOW, 2, 0 }, + { ANIM_STD_FIGHT_LHOOK, 8.f/30.f, 10.f/30.f, 0.0f, 0.4f, 1.0f, HITLEVEL_HIGH, 3, 0 }, + { ANIM_STD_FIGHT_JAB, 4.f/30.f, 0.2f, 0.0f, 0.7f, 1.0f, HITLEVEL_HIGH, 3, 0 }, + { ANIM_STD_FIGHT_PUNCH, 4.f/30.f, 7.f/30.f, 10.f/30.f, 0.4f, 1.0f, HITLEVEL_HIGH, 1, 0 }, + { ANIM_STD_FIGHT_LONGKICK, 8.f/30.f, 10.f/30.f, 0.0f, 0.5f, 1.0f, HITLEVEL_MEDIUM, 4, 0 }, + { ANIM_STD_FIGHT_ROUNDHOUSE, 8.f/30.f, 10.f/30.f, 0.0f, 0.6f, 1.0f, HITLEVEL_MEDIUM, 4, 0 }, + { ANIM_STD_FIGHT_KICK, 8.f/30.f, 10.f/30.f, 0.0f, 0.5f, 1.0f, HITLEVEL_HIGH, 2, 0 }, + { ANIM_STD_FIGHT_HEAD, 8.f/30.f, 10.f/30.f, 0.0f, 0.5f, 1.0f, HITLEVEL_MEDIUM, 2, 0 }, + { ANIM_STD_FIGHT_BKICK_L, 8.f/30.f, 10.f/30.f, 0.0f, 0.5f, 1.0f, HITLEVEL_LOW, 2, 0 }, + { ANIM_STD_FIGHT_BKICK_L, 8.f/30.f, 10.f/30.f, 0.0f, 0.5f, 1.0f, HITLEVEL_LOW, 2, 0 }, + { ANIM_STD_FIGHT_ELBOW_L, 8.f/30.f, 10.f/30.f, 0.0f, 0.5f, 1.0f, HITLEVEL_MEDIUM, 2, 0 }, + { ANIM_STD_FIGHT_BKICK_R, 8.f/30.f, 10.f/30.f, 0.0f, 0.5f, 1.0f, HITLEVEL_MEDIUM, 2, 0 }, + { ANIM_STD_FIGHT_ELBOW_R, 8.f/30.f, 10.f/30.f, 0.0f, 0.5f, 1.0f, HITLEVEL_HIGH, 2, 0 }, + { ANIM_STD_KICKGROUND, 10.f/30.f, 14.f/30.f, 0.0f, 0.4f, 1.0f, HITLEVEL_GROUND, 1, 0 }, + { ANIM_STD_HIT_FRONT, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_BACK, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_RIGHT, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_LEFT, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_BODYBLOW, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_CHEST, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_HEAD, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_WALK, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_FLOOR, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_STD_HIT_BEHIND, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, HITLEVEL_NULL, 0, 0 }, + { ANIM_ATTACK_1, 4.f/30.f, 7.f/30.f, 10.f/30.f, 0.4f, 1.0f, HITLEVEL_HIGH, 1, 0 }, + { ANIM_ATTACK_2, 4.f/30.f, 7.f/30.f, 10.f/30.f, 0.4f, 1.0f, HITLEVEL_HIGH, 1, 0 }, + { ANIM_ATTACK_3, 4.f / 30.f, 7.f / 30.f, 10.f / 30.f, 0.4f, 1.0f, HITLEVEL_HIGH, 1, 0 }, + { ANIM_STD_FIGHT_2IDLE, 0.0f, 0.0f, 0.0f, 0.0f, 1.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, true); + SetAimFlag(to); + SetLookTimer(INT32_MAX); + } + + CWeaponInfo* curWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + if (m_nPedState == PED_AIM_GUN || (bIsDucking && !IsPlayer()) || m_nWaitState == WAITSTATE_PLAYANIM_DUCK || curWeapon->m_AnimToPlay == ASSOCGRP_STD) + return; + + if (m_nPedState != PED_ATTACK) + SetStoredState(); + + SetPedState(PED_AIM_GUN); + bIsPointingGunAt = true; + SetMoveState(PEDMOVE_STILL); + + CAnimBlendAssociation *aimAssoc; + + if (bCrouchWhenShooting && bIsDucking && GetCrouchFireAnim(curWeapon)) { + aimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchFireAnim(curWeapon)); + } else { + aimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_WEAPON_FIRE); + } + + if (!aimAssoc || aimAssoc->blendDelta < 0.0f) { + if (bCrouchWhenShooting && bIsDucking && GetCrouchFireAnim(curWeapon)) { + aimAssoc = CAnimManager::BlendAnimation(GetClump(), curWeapon->m_AnimToPlay, GetCrouchFireAnim(curWeapon), 4.0f); + } else { + aimAssoc = CAnimManager::AddAnimation(GetClump(), curWeapon->m_AnimToPlay, ANIM_WEAPON_FIRE); + } + + aimAssoc->blendAmount = 0.0f; + aimAssoc->blendDelta = 8.0f; + } + if (to && !IsPlayer()) + Say(SOUND_PED_ATTACK); +} + +void +CPed::PointGunAt(void) +{ + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + float animLoopStart = weaponInfo->m_fAnimLoopStart; + CAnimBlendAssociation *weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_WEAPON_FIRE); + if (!weaponAssoc || weaponAssoc->blendDelta < 0.0f) { + if (weaponInfo->IsFlagSet(WEAPONFLAG_CROUCHFIRE)) { + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchFireAnim(weaponInfo)); + animLoopStart = weaponInfo->m_fAnim2LoopStart; + } + } + + if (weaponAssoc && weaponAssoc->currentTime > animLoopStart * 0.4f) { + weaponAssoc->SetCurrentTime(animLoopStart); + weaponAssoc->flags &= ~ASSOC_RUNNING; + + if (bIsDucking) + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; + + 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; + if (m_nPedState == PED_AIM_GUN || m_nPedState == PED_ATTACK) { + SetPedState(PED_IDLE); + RestorePreviousState(); + } + weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_WEAPON_FIRE); + if (!animAssoc || animAssoc->blendDelta < 0.0f) { + if (weaponInfo->IsFlagSet(WEAPONFLAG_CROUCHFIRE)) { + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchFireAnim(weaponInfo)); + } + } + if (animAssoc) { + animAssoc->flags |= ASSOC_DELETEFADEDOUT; + animAssoc->blendDelta = -4.0f; + } +} + +void +CPed::SetAttack(CEntity *victim) +{ + CPed *victimPed = nil; + CWeaponInfo *curWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CAnimBlendAssociation *animAssoc; + + if (victim && victim->IsPed()) + victimPed = (CPed*)victim; + + if (m_attackTimer > CTimer::GetTimeInMilliseconds() || m_nWaitState == WAITSTATE_SURPRISE || (bIsDucking && !bCrouchWhenShooting)) + return; + + if (curWeapon->IsFlagSet(WEAPONFLAG_RELOAD) && + (RpAnimBlendClumpGetAssociation(GetClump(), GetReloadAnim(curWeapon)) || RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchReloadAnim(curWeapon)))) { + if (!IsPlayer() || m_nPedState != PED_ATTACK || ((CPlayerPed*)this)->m_bHaveTargetSelected) + bIsAttacking = false; + else + bIsAttacking = true; + + return; + } + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_UNARMED || curWeapon->IsFlagSet(WEAPONFLAG_FIGHTMODE) || GetWeapon()->m_eWeaponType == WEAPONTYPE_BRASSKNUCKLE) { + if (IsPlayer() || + (m_nPedState != PED_FIGHT && m_nMoveState != PEDMOVE_NONE && m_nMoveState != PEDMOVE_STILL + && !(m_pedStats->m_flags & STAT_SHOPPING_BAGS) && curWeapon->IsFlagSet(WEAPONFLAG_PARTIALATTACK))) { + + if (m_nPedState != PED_ATTACK) { + SetPedState(PED_ATTACK); + bIsAttacking = false; + + CAnimBlendAssociation *animAssoc = CAnimManager::BlendAnimation(GetClump(), curWeapon->m_AnimToPlay, ANIM_MELEE_ATTACK_START, 8.0f); + animAssoc->SetRun(); + if (animAssoc->currentTime == animAssoc->hierarchy->totalLength) + animAssoc->SetCurrentTime(0.0f); + + animAssoc->SetFinishCallback(FinishedAttackCB, this); + } + } else { + StartFightAttack(CGeneral::GetRandomNumber()); + } + return; + } + + if (curWeapon->IsFlagSet(WEAPONFLAG_PARTIALATTACK) && + (IsPlayer() && ((CPlayerPed*)this)->m_fMoveSpeed >= 1.0f || + m_nMoveState == PEDMOVE_WALK || m_nMoveState == PEDMOVE_RUN)) { + + if (m_nPedState != PED_ATTACK) { + SetPedState(PED_ATTACK); + bIsAttacking = false; + CAnimBlendAssociation* animAssoc = CAnimManager::BlendAnimation(GetClump(), curWeapon->m_AnimToPlay, ANIM_MELEE_ATTACK_START, 8.0f); + animAssoc->SetRun(); + if (animAssoc->currentTime == animAssoc->hierarchy->totalLength) + animAssoc->SetCurrentTime(0.0f); + + animAssoc->SetFinishCallback(FinishedAttackCB, this); + } + return; + } + + if (m_pSeekTarget) + m_pSeekTarget->CleanUpOldReference(&m_pSeekTarget); + 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(); + aimPos += GetUp() * 0.35f; + CEntity *obstacle = CWorld::TestSphereAgainstWorld(aimPos, 0.2f, nil, true, false, false, true, false, false); + if (obstacle) { + if(gaTempSphereColPoints[0].surfaceB != SURFACE_TRANSPARENT_CLOTH && gaTempSphereColPoints[0].surfaceB != SURFACE_METAL_CHAIN_FENCE && + gaTempSphereColPoints[0].surfaceB != SURFACE_WOOD_BENCH && gaTempSphereColPoints[0].surfaceB != SURFACE_SCAFFOLD_POLE) { + if (!IsPlayer()) { + bObstacleShowedUpDuringKillObjective = true; + m_shootTimer = 0; + SetAttackTimer(1500); + m_shotTime = CTimer::GetTimeInMilliseconds(); + } + 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 + if (this == FindPlayerPed() && TheCamera.Cams[0].Using3rdPersonMouseCam()) { + SetAimFlag(m_fRotationCur); + ((CPlayerPed*)this)->m_fFPSMoveHeading = TheCamera.Find3rdPersonQuickAimPitch(); + } else if (curWeapon->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) { + SetAimFlag(m_fRotationCur); + } + } + } +#ifdef FIX_BUGS + // fix aiming for flamethrower and minigun while using PC controls + else if (curWeapon->m_AnimToPlay == ASSOCGRP_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 && (IsPlayer() || !m_carInObjective)) + 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 && bIsDucking && curWeapon->IsFlagSet(WEAPONFLAG_CROUCHFIRE)) { + CAnimBlendAssociation* curMoveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchFireAnim(curWeapon)); + if (curMoveAssoc) { + if (strcmp(CAnimManager::GetAnimAssociation(curWeapon->m_AnimToPlay, GetCrouchFireAnim(curWeapon))->hierarchy->name, curMoveAssoc->hierarchy->name) != 0) { + delete curMoveAssoc; + } + } + animAssoc = CAnimManager::BlendAnimation(GetClump(), curWeapon->m_AnimToPlay, GetCrouchFireAnim(curWeapon), 8.0f); + } else { + float animDelta = 8.0f; + if (curWeapon->m_eWeaponFire == WEAPON_FIRE_MELEE) + animDelta = 1000.0f; + + AnimationId fireAnim; + if (curWeapon->IsFlagSet(WEAPONFLAG_THROW)) + fireAnim = ANIM_THROWABLE_START_THROW; + else if (CGame::nastyGame && (curWeapon->IsFlagSet(WEAPONFLAG_GROUND_2ND) || curWeapon->IsFlagSet(WEAPONFLAG_GROUND_3RD))) { + PedOnGroundState pedOnGround = CheckForPedsOnGroundToAttack(this, nil); + if (pedOnGround > PED_IN_FRONT_OF_ATTACKER || pedOnGround == NO_PED && bIsStanding && m_pCurSurface && m_pCurSurface->IsVehicle()) { + fireAnim = GetFireAnimGround(curWeapon, false); + } else { + fireAnim = GetFireAnimNotDucking(curWeapon); + } + } else { + fireAnim = GetFireAnimNotDucking(curWeapon); + } + + CAnimBlendAssociation* curFireAssoc = RpAnimBlendClumpGetAssociation(GetClump(), fireAnim); + if (curFireAssoc) { + if (strcmp(CAnimManager::GetAnimAssociation(curWeapon->m_AnimToPlay, fireAnim)->hierarchy->name, curFireAssoc->hierarchy->name) != 0) { + delete curFireAssoc; + } + } + animAssoc = CAnimManager::BlendAnimation(GetClump(), curWeapon->m_AnimToPlay, fireAnim, 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, true, true); + SetLookTimer(100); +} + +void +CPed::ClearAttack(void) +{ + if (m_nPedState != PED_ATTACK || (bIsDucking && !IsPlayer()) || m_nWaitState == WAITSTATE_PLAYANIM_DUCK) + return; + + if (FindPlayerPed() == this && TheCamera.Using1stPersonWeaponMode()) { + SetPointGunAt(nil); + } else 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) + return; + + CWeaponInfo *weapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CAnimBlendAssociation *weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetPrimaryFireAnim(weapon)); + + if (!weaponAssoc) { + if (GetCrouchFireAnim(weapon)) + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchFireAnim(weapon)); + } + if (!weaponAssoc) { + if(GetFinishingAttackAnim(weapon)) + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetFinishingAttackAnim(weapon)); + } + if (!weaponAssoc) { + if(GetSecondFireAnim(weapon)) + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetSecondFireAnim(weapon)); + } + if (!weaponAssoc) { + if(Get3rdFireAnim(weapon)) + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), Get3rdFireAnim(weapon)); + } + if (weaponAssoc) { + weaponAssoc->blendDelta = -8.0f; + weaponAssoc->flags &= ~ASSOC_RUNNING; + weaponAssoc->flags |= ASSOC_DELETEFADEDOUT; + weaponAssoc->SetDeleteCallback(FinishedAttackCB, this); + } else { + ClearAttack(); + } +} + +void +CPed::FinishedAttackCB(CAnimBlendAssociation *attackAssoc, void *arg) +{ + CAnimBlendAssociation *newAnim, *reloadAnimAssoc = nil; + CPed *ped = (CPed*)arg; + CWeaponInfo *currentWeapon = CWeaponInfo::GetWeaponInfo(ped->GetWeapon()->m_eWeaponType); + + if (ped->m_nPedState != PED_ATTACK) { + if (ped->bIsDucking && ped->IsPedInControl()) { + if (GetCrouchReloadAnim(currentWeapon)) { + reloadAnimAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), GetCrouchReloadAnim(currentWeapon)); + } + if (GetCrouchFireAnim(currentWeapon) && attackAssoc) { + if (attackAssoc->animId == GetCrouchFireAnim(currentWeapon) && !reloadAnimAssoc) { + newAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_WEAPON, 8.0f); + newAnim->SetCurrentTime(newAnim->hierarchy->totalLength); + newAnim->flags &= ~ASSOC_RUNNING; + } + } + } + } else if (attackAssoc && attackAssoc->animId == ANIM_THROWABLE_START_THROW && currentWeapon->m_AnimToPlay == ASSOCGRP_THROW) { + if ((!ped->IsPlayer() || ((CPlayerPed*)ped)->m_bHaveTargetSelected) && ped->IsPlayer()) { + attackAssoc->blendDelta = -1000.0f; + newAnim = CAnimManager::AddAnimation(ped->GetClump(), currentWeapon->m_AnimToPlay, ANIM_THROWABLE_THROWU); + } else { + attackAssoc->blendDelta = -1000.0; + newAnim = CAnimManager::AddAnimation(ped->GetClump(), currentWeapon->m_AnimToPlay, ANIM_THROWABLE_THROW); + } + newAnim->SetFinishCallback(FinishedAttackCB, ped); + + } else if (ped->bIsDucking && ped->bCrouchWhenShooting) { + if (GetCrouchReloadAnim(currentWeapon)) { + reloadAnimAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), GetCrouchReloadAnim(currentWeapon)); + } + if (GetCrouchFireAnim(currentWeapon) && attackAssoc) { + if (attackAssoc->animId == GetCrouchFireAnim(currentWeapon) && !reloadAnimAssoc) { + newAnim = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_WEAPON, 8.0f); + newAnim->SetCurrentTime(newAnim->hierarchy->totalLength); + newAnim->flags &= ~ASSOC_RUNNING; + } + } + + if (!ped->bIsAttacking) + ped->ClearAttack(); + + } else if (GetSecondFireAnim(currentWeapon) && ped->bIsAttacking && currentWeapon->m_AnimToPlay != ASSOCGRP_THROW) { + AnimationId groundAnim = GetFireAnimGround(currentWeapon); + CAnimBlendAssociation *groundAnimAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), groundAnim); + if (!(groundAnimAssoc && (groundAnimAssoc->blendAmount > 0.95f || groundAnimAssoc->blendDelta > 0.0f))) { + if (attackAssoc && attackAssoc->animId == ANIM_MELEE_ATTACK) { + newAnim = CAnimManager::BlendAnimation( + ped->GetClump(), currentWeapon->m_AnimToPlay, GetSecondFireAnim(currentWeapon), 8.0f); + } else { + newAnim = CAnimManager::BlendAnimation( + ped->GetClump(), currentWeapon->m_AnimToPlay, ANIM_MELEE_ATTACK, 8.0f); + } + newAnim->SetFinishCallback(FinishedAttackCB, ped); + } + } else { + if (attackAssoc && attackAssoc->animId == ANIM_MELEE_ATTACK && currentWeapon->m_AnimToPlay == ASSOCGRP_UNARMED) { + attackAssoc->blendDelta = -8.0f; + attackAssoc->flags |= ASSOC_DELETEFADEDOUT; + ped->ClearAttack(); + return; + } + if (attackAssoc) { + if (currentWeapon->m_AnimToPlay == ASSOCGRP_THROW) { + if ((attackAssoc->animId == ANIM_THROWABLE_THROW || attackAssoc->animId == ANIM_THROWABLE_THROWU) && ped->GetWeapon()->m_nAmmoTotal > 0) { + ped->RemoveWeaponModel(currentWeapon->m_nModelId); + ped->AddWeaponModel(currentWeapon->m_nModelId); + } + } + } + + if (!ped->bIsAttacking) + ped->ClearAttack(); + } +} + +void +CPed::FinishedReloadCB(CAnimBlendAssociation *reloadAssoc, void *arg) +{ + CPed *ped = (CPed*)arg; + CWeaponInfo *weapon = CWeaponInfo::GetWeaponInfo(ped->GetWeapon()->m_eWeaponType); + + if (ped->DyingOrDead()) + return; + + if (ped->bIsDucking && ped->bCrouchWhenShooting) { + CAnimBlendAssociation *crouchFireAssoc = nil; + if (weapon->IsFlagSet(WEAPONFLAG_CROUCHFIRE)) { + crouchFireAssoc = RpAnimBlendClumpGetAssociation(ped->GetClump(), GetCrouchFireAnim(weapon)); + } + if (weapon->IsFlagSet(WEAPONFLAG_RELOAD) && reloadAssoc) { + if (reloadAssoc->animId == GetCrouchReloadAnim(weapon) && !crouchFireAssoc) { + CAnimBlendAssociation *crouchAssoc = CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_DUCK_WEAPON, 8.0f); + crouchAssoc->SetCurrentTime(crouchAssoc->hierarchy->totalLength); + crouchAssoc->flags &= ~ASSOC_RUNNING; + } + } + } else if (weapon->IsFlagSet(WEAPONFLAG_RELOAD_LOOP2START) && ped->bIsAttacking) { + CAnimBlendAssociation *fireAssoc = + CAnimManager::BlendAnimation(ped->GetClump(), weapon->m_AnimToPlay, GetPrimaryFireAnim(weapon), 8.0f); + fireAssoc->SetFinishCallback(FinishedAttackCB, ped); + fireAssoc->SetRun(); + if (fireAssoc->currentTime == reloadAssoc->hierarchy->totalLength) + fireAssoc->SetCurrentTime(Max(weapon->m_fAnimLoopStart - 0.04f, 0.0f)); + else if (fireAssoc->currentTime < weapon->m_fAnimLoopStart) + fireAssoc->SetCurrentTime(Max(weapon->m_fAnimLoopStart - 0.04f, 0.0f)); + } +} + +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.MagnitudeSqr() < SQR(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; + + PedState nearPedState = nearPed->m_nPedState; + + if (nearPedState == PED_FALL || nearPedState == PED_GETUP || nearPedState == PED_DIE || nearPedState == PED_DEAD || nearPedState == 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 weaponAnimTime; + float animLoopEnd; + CWeaponInfo *ourWeapon; + bool attackShouldContinue; + CAnimBlendAssociation *reloadAnimAssoc; + CAnimBlendAssociation *throwAssoc; + float delayBetweenAnimAndFire; + float animLoopStart; + CVector firePos; + + ourWeapon = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + weaponAnimAssoc = nil; + attackShouldContinue = !!bIsAttacking; + reloadAnimAssoc = nil; + throwAssoc = nil; + animLoopStart = ourWeapon->m_fAnimLoopStart; + animLoopEnd = ourWeapon->m_fAnimLoopEnd; + delayBetweenAnimAndFire = ourWeapon->m_fAnimFrameFire; + weaponAnim = ourWeapon->m_AnimToPlay; + + if (bIsDucking) { + if(GetCrouchFireAnim(ourWeapon) && bCrouchWhenShooting) { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchFireAnim(ourWeapon)); + if (weaponAnimAssoc) { + animLoopStart = ourWeapon->m_fAnim2LoopStart; + animLoopEnd = ourWeapon->m_fAnim2LoopEnd; + delayBetweenAnimAndFire = ourWeapon->m_fAnim2FrameFire; + } + } + } else if (m_nPedType == PEDTYPE_COP && Get3rdFireAnim(ourWeapon)){ + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), Get3rdFireAnim(ourWeapon)); + if (weaponAnimAssoc) { + animLoopStart = 11.f/30.f; + animLoopEnd = 19.f/30.f; + delayBetweenAnimAndFire = 14.f/30.f; + } + } else { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetPrimaryFireAnim(ourWeapon)); + } + + if (GetReloadAnim(ourWeapon)) { + reloadAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetReloadAnim(ourWeapon)); + } + + if (GetCrouchReloadAnim(ourWeapon) && !reloadAnimAssoc) { + reloadAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetCrouchReloadAnim(ourWeapon)); + } + + if ( reloadAnimAssoc && reloadAnimAssoc->IsRunning() ) { + if (!IsPlayer() || ((CPlayerPed*)this)->m_bHaveTargetSelected) + ClearAttack(); + return; + } + + if ( reloadAnimAssoc ) { + reloadAnimAssoc->flags |= ASSOC_DELETEFADEDOUT; + if ( reloadAnimAssoc->blendDelta >= 0.0f ) + reloadAnimAssoc->blendDelta = -8.0f; + } + + if (GetThrowAnim(ourWeapon)) { + throwAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetThrowAnim(ourWeapon)); + } + + if ( CTimer::GetTimeInMilliseconds() < m_shootTimer ) + attackShouldContinue = true; + + bool meleeAttackStarted = false; + if ( !weaponAnimAssoc ) { + if (GetMeleeStartAnim(ourWeapon)) { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetMeleeStartAnim(ourWeapon)); + if ( weaponAnimAssoc ) { + if ( IsPlayer() ) + meleeAttackStarted = true; + + switch ( ourWeapon->m_AnimToPlay ) { + case ASSOCGRP_UNARMED: + case ASSOCGRP_SCREWDRIVER: + case ASSOCGRP_KNIFE: + case ASSOCGRP_BASEBALLBAT: + case ASSOCGRP_GOLFCLUB: + case ASSOCGRP_CHAINSAW: + delayBetweenAnimAndFire = 0.2f; + animLoopStart = 0.1f; + break; + default: + break; + } + animLoopEnd = 99.9f; + } + } + } + if (!weaponAnimAssoc) { + if (GetSecondFireAnim(ourWeapon)) { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetSecondFireAnim(ourWeapon)); + if (weaponAnimAssoc) { + animLoopStart = ourWeapon->m_fAnim2LoopStart; + animLoopEnd = ourWeapon->m_fAnim2LoopEnd; + delayBetweenAnimAndFire = ourWeapon->m_fAnim2FrameFire; + } + } + } + if (!weaponAnimAssoc) { + weaponAnimAssoc = RpAnimBlendClumpGetAssociation(GetClump(), GetFireAnimGround(ourWeapon)); + if (weaponAnimAssoc) { + animLoopStart = ourWeapon->m_fAnim2LoopStart; + animLoopEnd = ourWeapon->m_fAnim2LoopEnd; + delayBetweenAnimAndFire = ourWeapon->m_fAnim2FrameFire; + } + } + + if (!weaponAnimAssoc) { + if (!throwAssoc) { + if (attackShouldContinue) { + if (ourWeapon->m_eWeaponFire != WEAPON_FIRE_PROJECTILE || !IsPlayer() || ((CPlayerPed*)this)->m_bHaveTargetSelected) { + if (bCrouchWhenShooting && bIsDucking && GetCrouchFireAnim(ourWeapon)) { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ourWeapon->m_AnimToPlay, GetCrouchFireAnim(ourWeapon), 8.0f); + + } else if(GetSecondFireAnim(ourWeapon) && CGeneral::GetRandomNumber() & 1){ + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ourWeapon->m_AnimToPlay, GetSecondFireAnim(ourWeapon), 8.0f); + + } else if(!CGame::nastyGame || ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE || + !GetFireAnimGround(ourWeapon, false) || + CheckForPedsOnGroundToAttack(this, nil) < PED_ON_THE_FLOOR) { + + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ourWeapon->m_AnimToPlay, GetFireAnimNotDucking(ourWeapon), 8.0f); + + } else { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ourWeapon->m_AnimToPlay, GetFireAnimGround(ourWeapon, false), 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; + } + + if (meleeAttackStarted && IsPlayer()) { + if (((CPlayerPed*)this)->m_bHaveTargetSelected || ((CPlayerPed*)this)->m_fMoveSpeed < 0.5f) { + weaponAnimAssoc->SetRun(); + } else { + if (weaponAnimAssoc->currentTime > animLoopStart && weaponAnimAssoc->currentTime - weaponAnimAssoc->timeStep <= animLoopStart) + weaponAnimAssoc->flags &= ~ASSOC_RUNNING; + } + } + + float animStart = animLoopStart * 0.4f; + weaponAnimTime = weaponAnimAssoc->currentTime; + if (weaponAnimTime > animStart && weaponAnimTime - weaponAnimAssoc->timeStep <= animStart) { + if (!bIsDucking && !GetFireAnimNotDucking(ourWeapon) && ourWeapon->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) + m_pedIK.m_flags |= CPedIK::AIMS_WITH_ARM; + else +#ifdef FREE_CAM + if (!IsPlayer() || !((CPlayerPed*)this)->m_bFreeAimActive) +#endif + m_pedIK.m_flags &= ~CPedIK::AIMS_WITH_ARM; + } + + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_CHAINSAW + || !meleeAttackStarted && delayBetweenAnimAndFire - 0.5f >= weaponAnimAssoc->currentTime + || weaponAnimAssoc->currentTime - weaponAnimAssoc->timeStep > delayBetweenAnimAndFire) { + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_CHAINSAW) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_CHAINSAW_IDLE, 0.0f); + } else if (weaponAnimTime <= delayBetweenAnimAndFire || weaponAnimTime - weaponAnimAssoc->timeStep > delayBetweenAnimAndFire || !weaponAnimAssoc->IsRunning()) { + if (weaponAnimAssoc->speed < 1.0f) + weaponAnimAssoc->speed = 1.0f; + + } else { + firePos = ourWeapon->m_vecFireOffset; + + if(ourWeapon->m_AnimToPlay != ASSOCGRP_BASEBALLBAT && ourWeapon->m_AnimToPlay != ASSOCGRP_GOLFCLUB) { + if (ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE) { + TransformToNode(firePos, (weaponAnimAssoc->animId == ANIM_MELEE_ATTACK_2ND && ourWeapon->m_AnimToPlay == ASSOCGRP_UNARMED) ? PED_FOOTR : PED_HANDR); + } else { + firePos = GetMatrix() * firePos; + } + } else { + if (weaponAnimAssoc->animId == ANIM_MELEE_ATTACK_2ND) + firePos.z = 0.7f * ourWeapon->m_fRadius - 1.0f; + + firePos = GetMatrix() * firePos; + } + + GetWeapon()->Fire(this, &firePos); + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_MOLOTOV || GetWeapon()->m_eWeaponType == WEAPONTYPE_GRENADE || GetWeapon()->m_eWeaponType == WEAPONTYPE_DETONATOR_GRENADE || + GetWeapon()->m_eWeaponType == WEAPONTYPE_TEARGAS) { + RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_nModelId); + } + if (GetWeapon()->m_nAmmoTotal == 0 && ourWeapon->m_eWeaponFire != WEAPON_FIRE_MELEE && FindPlayerPed() != this) { + SelectGunIfArmed(); + } + + if (GetWeapon()->m_eWeaponState == WEAPONSTATE_MELEE_MADECONTACT) { + int damagerType = ENTITY_TYPE_NOTHING; + if (m_pDamageEntity && (m_fDamageImpulse == 0.0f || !m_pDamageEntity->IsBuilding())) { + damagerType = m_pDamageEntity->GetType(); + } + switch (ourWeapon->m_AnimToPlay) { + case ASSOCGRP_UNARMED: + if (weaponAnimAssoc->animId == ANIM_MELEE_ATTACK || weaponAnimAssoc->animId == ANIM_MELEE_ATTACK_START) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_FIGHT_46, (damagerType | (GetWeapon()->m_eWeaponType << 8))); + break; + case ASSOCGRP_KNIFE: + case ASSOCGRP_BASEBALLBAT: + case ASSOCGRP_GOLFCLUB: + case ASSOCGRP_CHAINSAW: + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_BAT_ATTACK, (damagerType | (GetWeapon()->m_eWeaponType << 8))); + break; + default: + break; + } + + if (bIsAttacking || CTimer::GetTimeInMilliseconds() < m_shootTimer) { + weaponAnimAssoc->callbackType = 0; + } + } + + attackShouldContinue = false; + } + } else { + CVector firePos = ourWeapon->m_vecFireOffset; + + if (weaponAnimAssoc->animId == ANIM_MELEE_ATTACK_2ND) + firePos.z = 0.7f * ourWeapon->m_fRadius - 1.0f; + + firePos = GetMatrix() * firePos; + GetWeapon()->Fire(this, &firePos); + if (GetWeapon()->m_eWeaponState == WEAPONSTATE_MELEE_MADECONTACT) { + int damagerType = ENTITY_TYPE_PED; + if (m_pDamageEntity) + damagerType = m_pDamageEntity->GetType(); + + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_CHAINSAW_MADECONTACT, (float)damagerType); + if (IsPlayer()) { + CPad::GetPad(0)->StartShake(240, 180); + } + } else { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_CHAINSAW_ATTACK, 0.0f); + if (IsPlayer()) { + CPad::GetPad(0)->StartShake(240, 90); + } + } + attackShouldContinue = false; + } + + if (ourWeapon->m_eWeaponFire == WEAPON_FIRE_INSTANT_HIT && ourWeapon->m_AnimToPlay == ASSOCGRP_SHOTGUN) { + weaponAnimTime = weaponAnimAssoc->currentTime; + + if (weaponAnimTime > 1.0f && weaponAnimTime - weaponAnimAssoc->timeStep <= 1.0f && weaponAnimAssoc->IsRunning()) { + firePos = ourWeapon->m_vecFireOffset; + 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); + } + } + + if (IsPlayer()) { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT || GetWeapon()->m_eWeaponType == WEAPONTYPE_GOLFCLUB || GetWeapon()->m_eWeaponType == WEAPONTYPE_KATANA) { + float loopEndWithDelay = animLoopEnd; + if (loopEndWithDelay >= 98.0f) + loopEndWithDelay = (14.0f / 30.0f) + delayBetweenAnimAndFire; + if (weaponAnimAssoc->flags & ASSOC_RUNNING) { + if (weaponAnimAssoc->currentTime >= animLoopStart && weaponAnimAssoc->currentTime <= loopEndWithDelay) + CSpecialFX::AddWeaponStreak(GetWeapon()->m_eWeaponType); + } + } + } + + // Anim breakout on running + if (IsPlayer()) { + if (CPad::GetPad(0)->GetSprint()) { + if (!attackShouldContinue && weaponAnimAssoc->currentTime > ourWeapon->m_fAnimBreakout) { + weaponAnimAssoc->blendDelta = -4.0f; + FinishedAttackCB(nil, this); + return; + } + } + } + + 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 (GetWeapon()->m_eWeaponState == WEAPONSTATE_RELOADING) { + if (GetReloadAnim(ourWeapon) && !reloadAnimAssoc) { + if (!CWorld::Players[CWorld::PlayerInFocus].m_bFastReload) { + CAnimBlendAssociation *newReloadAssoc = CAnimManager::BlendAnimation( + GetClump(), ourWeapon->m_AnimToPlay, + bIsDucking && GetCrouchReloadAnim(ourWeapon) ? GetCrouchReloadAnim(ourWeapon) : GetReloadAnim(ourWeapon), + 8.0f); + newReloadAssoc->SetFinishCallback(FinishedReloadCB, this); + } + ClearLookFlag(); + ClearAimFlag(); + bIsAttacking = false; + bIsPointingGunAt = false; + m_shootTimer = CTimer::GetTimeInMilliseconds(); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_AK47_BULLET_ECHO, GetWeapon()->m_eWeaponType); + return; + } + } + if (weaponAnimTime - 2.0f * weaponAnimAssoc->timeStep <= animLoopEnd + && (bIsAttacking || CTimer::GetTimeInMilliseconds() < m_shootTimer) + && (GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING + || GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN)) { + + PedOnGroundState pedOnGroundState; + if (ourWeapon->m_eWeaponFire == WEAPON_FIRE_MELEE && + (CGame::nastyGame && ((pedOnGroundState = CheckForPedsOnGroundToAttack(this, nil)) > PED_IN_FRONT_OF_ATTACKER) + || GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT && pedOnGroundState == NO_PED && bIsStanding && m_pCurSurface && m_pCurSurface->IsVehicle())) { + + AnimationId fireAnim = GetFireAnimGround(ourWeapon, false); + if (weaponAnimAssoc->animId == fireAnim) + weaponAnimAssoc->SetCurrentTime(0.1f); + else { + if (GetFireAnimGround(ourWeapon, false)) { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ourWeapon->m_AnimToPlay, fireAnim, 8.0f); + } else { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_KICKGROUND, 8.0f); + } + } + weaponAnimAssoc->SetFinishCallback(FinishedAttackCB, this); + } else if (GetSecondFireAnim(ourWeapon)) { + if (weaponAnimAssoc->animId == GetSecondFireAnim(ourWeapon)) { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ourWeapon->m_AnimToPlay, ANIM_WEAPON_FIRE, 8.0f); + } else { + weaponAnimAssoc = CAnimManager::BlendAnimation(GetClump(), ourWeapon->m_AnimToPlay, GetSecondFireAnim(ourWeapon), 8.0f); + } + weaponAnimAssoc->SetFinishCallback(FinishedAttackCB, this); + } else { + weaponAnimAssoc->SetCurrentTime(animLoopStart); + weaponAnimAssoc->SetRun(); + } + } else if (IsPlayer() && m_pPointGunAt && bIsAimingGun && GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING) { + weaponAnimAssoc->SetCurrentTime(animLoopEnd); + weaponAnimAssoc->flags &= ~ASSOC_RUNNING; + SetPointGunAt(m_pPointGunAt); +#ifdef FREE_CAM + } else if (IsPlayer() && ((CPlayerPed*)this)->m_bFreeAimActive && GetWeapon()->m_eWeaponState != WEAPONSTATE_RELOADING) { + float limitedCam = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + SetLookFlag(limitedCam, true, 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 < animLoopEnd) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_AK47_BULLET_ECHO, GetWeapon()->m_eWeaponType); + + // 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() && buttonPressure != 0)) + return; + + if (m_nPedState == PED_FIGHT) { + m_fightButtonPressure = buttonPressure; + return; + } + + if (m_nPedState != PED_AIM_GUN) + SetStoredState(); + + if (m_nWaitState != WAITSTATE_FALSE) { + ClearWaitState(); + RestoreHeadingRate(); + } + + CAnimBlendAssociation* animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP1); + if (!animAssoc) + animAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_RUNSTOP2); + + if (animAssoc) { + RestoreHeadingRate(); + } + SetMoveState(PEDMOVE_NONE); + m_nStoredMoveState = PEDMOVE_NONE; + bool fightWithWeapon = false; + CAnimBlendAssociation *fightIdleAssoc; + + CWeaponInfo* weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + if (GetFightIdleWithMeleeAnim(weaponInfo)) { + fightIdleAssoc = CAnimManager::BlendAnimation(GetClump(), weaponInfo->m_AnimToPlay, GetFightIdleWithMeleeAnim(weaponInfo), 1000.0f); + fightWithWeapon = true; + } else { + fightIdleAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_IDLE, 1000.0f); + } + } else { + fightIdleAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_IDLE, 1000.0f); + } + m_lastFightMove = FIGHTMOVE_IDLE; + m_curFightMove = IsPlayer() ? ChooseAttackPlayer(buttonPressure, fightWithWeapon) : ChooseAttackAI(buttonPressure, fightWithWeapon); + + SetPedState(PED_FIGHT); + m_fightButtonPressure = 0; + + if (m_curFightMove > FIGHTMOVE_NULL && m_curFightMove != FIGHTMOVE_IDLE) { + animAssoc = CAnimManager::BlendAnimation(GetClump(), m_curFightMove < FIGHTMOVE_MELEE1 ? ASSOCGRP_STD : weaponInfo->m_AnimToPlay, + tFightMoves[m_curFightMove].animId, 8.0f); + + if (weaponInfo->m_AnimToPlay == ASSOCGRP_KNIFE && m_curFightMove >= FIGHTMOVE_MELEE1) { + switch (GetWeapon()->m_eWeaponType) { + case WEAPONTYPE_SCREWDRIVER: + case WEAPONTYPE_KNIFE: + animAssoc->speed = 1.05f; + break; + case WEAPONTYPE_GOLFCLUB: + case WEAPONTYPE_NIGHTSTICK: + case WEAPONTYPE_BASEBALLBAT: + case WEAPONTYPE_HAMMER: + case WEAPONTYPE_KATANA: + animAssoc->speed = 0.8f; + break; + case WEAPONTYPE_CLEAVER: + case WEAPONTYPE_MACHETE: + animAssoc->speed = 0.9f; + break; + } + } else { + if (m_curFightMove == FIGHTMOVE_BACKKICK) + animAssoc->speed = 1.15f; + else + animAssoc->speed = 0.8f; + } + if (IsPlayer()) + animAssoc->SetCurrentTime(0.08f); + + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + } else { + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2000; + } + + 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)) { + + if (hitLevel != HITLEVEL_HIGH && hitLevel != HITLEVEL_LOW || (IsPlayer() || CGeneral::GetRandomNumber() & 1) && CGeneral::GetRandomNumber() & 7) { + if (IsPlayer() || CGeneral::GetRandomNumber() & 1) { + 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 { + 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; + } + } + Say(SOUND_PED_DEFEND); + } else { + Say(SOUND_PED_DEFEND); + switch (hitLevel) { + case HITLEVEL_GROUND: + m_curFightMove = FIGHTMOVE_HITONFLOOR; + break; + case HITLEVEL_LOW: + if (direction == 2 && (!IsPlayer() || ((CGeneral::GetRandomNumber() & 1) && m_fHealth < 30.0f))) { + SetFall(1000, ANIM_STD_HIGHIMPACT_BACK, false); + Say(SOUND_PED_DEFEND); + return; + } else if (direction != 2 && !IsPlayer() && (CGeneral::GetRandomNumber() & 1) && m_fHealth < 30.0f) { + SetFall(1000, ANIM_STD_KO_SHOT_STOMACH, false); + Say(SOUND_PED_DEFEND); + return; + } + 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.2f; + + m_takeAStepAfterAttack = false; + m_fightButtonPressure = 0; + + } else if (IsPlayer() && GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && GetWeapon()->m_eWeaponType != WEAPONTYPE_BRASSKNUCKLE && + !CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_FIGHTMODE)) { + CAnimBlendAssociation *moveAssoc = CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, tFightMoves[m_curFightMove].animId, 4.0f); + moveAssoc->SetCurrentTime(0.0f); + moveAssoc->speed = 1.2f; + + } else { + if (m_nPedState != PED_AIM_GUN && m_nPedState != PED_ATTACK) + SetStoredState(); + + if (m_nWaitState != WAITSTATE_FALSE) { + ClearWaitState(); + RestoreHeadingRate(); + } + SetPedState(PED_FIGHT); + m_fightButtonPressure = 0; + m_lastFightMove = FIGHTMOVE_IDLE; + 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; + CAnimBlendAssociation *fightIdleAssoc; + + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + if (GetFightIdleWithMeleeAnim(weaponInfo)) { + fightIdleAssoc = CAnimManager::AddAnimation(GetClump(), weaponInfo->m_AnimToPlay, GetFightIdleWithMeleeAnim(weaponInfo)); + } else { + fightIdleAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_IDLE); + } + } else { + fightIdleAssoc = CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_FIGHT_IDLE); + } + fightIdleAssoc->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; + } + + if (m_pedInObjective && m_pedInObjective->IsPlayer() && !IsPlayer()) + ((CPlayerPed*)m_pedInObjective)->RemovePedFromMeleeList(this); + } + } +} + +void +CPed::Fight(void) +{ + CAnimBlendAssociation *currentAssoc, *animAssoc; + bool fightWithWeapon = false; + + eWeaponType weapon = GetWeapon()->m_eWeaponType; + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(weapon); + + if (weaponInfo->IsFlagSet(WEAPONFLAG_FIGHTMODE) && weapon != WEAPONTYPE_UNARMED) { + fightWithWeapon = true; + tFightMoves[FIGHTMOVE_MELEE1].startFireTime = weaponInfo->m_fAnimFrameFire; + tFightMoves[FIGHTMOVE_MELEE1].endFireTime = weaponInfo->m_fAnimLoopEnd; + tFightMoves[FIGHTMOVE_MELEE2].startFireTime = weaponInfo->m_fAnim2FrameFire; + tFightMoves[FIGHTMOVE_MELEE2].endFireTime = weaponInfo->m_fAnim2LoopEnd; + tFightMoves[FIGHTMOVE_MELEE3].startFireTime = weaponInfo->m_fAnim2FrameFire; + tFightMoves[FIGHTMOVE_MELEE3].endFireTime = weaponInfo->m_fAnim2LoopEnd; + } + + 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 (m_curFightMove == FIGHTMOVE_SHUFFLE_F && !currentAssoc) + currentAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_SHUFFLE_B); + + if (IsPlayer() && currentAssoc && weapon == WEAPONTYPE_KATANA) { + if (m_curFightMove == FIGHTMOVE_MELEE1 || m_curFightMove == FIGHTMOVE_MELEE2) { + static float streakDelay = 0.2f; + + if (tFightMoves[m_curFightMove].startFireTime - streakDelay < currentAssoc->currentTime && + streakDelay + tFightMoves[m_curFightMove].endFireTime > currentAssoc->currentTime) { + CSpecialFX::AddWeaponStreak(GetWeapon()->m_eWeaponType); + } + } + } + + 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) { + + if (animTime > curMove.startFireTime && animTime - currentAssoc->timeStep < curMove.startFireTime && + (IsPlayer() || weapon != WEAPONTYPE_UNARMED)) { + + DMAudio.PlayOneShot(m_audioEntityId, SOUND_MELEE_ATTACK_START, weapon << 8); + } + + CVector touchingNodePos(0.0f, 0.0f, 0.0f); + + switch (m_curFightMove) { + case FIGHTMOVE_KNEE: + TransformToNode(touchingNodePos, PED_LOWERLEGR); + break; + case FIGHTMOVE_PUNCHHOOK: + case FIGHTMOVE_PUNCHJAB: + TransformToNode(touchingNodePos, PED_HANDL); + break; + case FIGHTMOVE_LONGKICK: + case FIGHTMOVE_ROUNDHOUSE: + case FIGHTMOVE_FWDLEFT: + case FIGHTMOVE_BACKRIGHT: + case FIGHTMOVE_GROUNDKICK: + TransformToNode(touchingNodePos, PED_FOOTR); + break; + case FIGHTMOVE_FWDRIGHT: + TransformToNode(touchingNodePos, PED_HEAD); + break; + case FIGHTMOVE_BACKKICK: + case FIGHTMOVE_BACKFLIP: + TransformToNode(touchingNodePos, PED_FOOTL); + break; + case FIGHTMOVE_BACKLEFT: + TransformToNode(touchingNodePos, PED_UPPERARML); + break; + default: + TransformToNode(touchingNodePos, PED_HANDR); + break; + } + + FightStrike(touchingNodePos, fightWithWeapon); + m_fightButtonPressure = 0; + return; + } + + if (curMove.hitLevel != HITLEVEL_NULL) { + if (animTime > curMove.endFireTime && weaponInfo->m_AnimToPlay != ASSOCGRP_KNIFE) { + if (IsPlayer()) + currentAssoc->speed = 1.0f; + else + currentAssoc->speed = 0.8f; + } + + if (IsPlayer() && !nPlayerInComboMove && !fightWithWeapon) { + if (curMove.comboFollowOnTime > 0.0f && m_fightButtonPressure != 0 && animTime > curMove.comboFollowOnTime) { + + m_lastFightMove = m_curFightMove; + // 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); + animAssoc->speed = 0.8f; + m_fightButtonPressure = 0; + nPlayerInComboMove = 1; + } + } + } + + } else if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && GetWeapon()->m_eWeaponType != WEAPONTYPE_BRASSKNUCKLE && !fightWithWeapon) { + EndFight(ENDFIGHT_FAST); + + } else if (m_fightButtonPressure != 0) { + if (!IsPlayer()) + Say(SOUND_PED_ATTACK); + + if (m_curFightMove != FIGHTMOVE_IDLE) + m_lastFightMove = m_curFightMove; + + m_curFightMove = IsPlayer() ? ChooseAttackPlayer(m_fightButtonPressure, fightWithWeapon) : ChooseAttackAI(m_fightButtonPressure, fightWithWeapon); + + if (m_curFightMove != FIGHTMOVE_IDLE) { + + animAssoc = CAnimManager::BlendAnimation(GetClump(), m_curFightMove < FIGHTMOVE_MELEE1 ? ASSOCGRP_STD : weaponInfo->m_AnimToPlay, + tFightMoves[m_curFightMove].animId, 8.0f); + + if (weaponInfo->m_AnimToPlay != ASSOCGRP_KNIFE || m_curFightMove < FIGHTMOVE_MELEE1) { + if (m_curFightMove == FIGHTMOVE_BACKKICK) + animAssoc->speed = 1.15f; + else + animAssoc->speed = 0.8f; + } else { + switch (GetWeapon()->m_eWeaponType) { + case WEAPONTYPE_SCREWDRIVER: + case WEAPONTYPE_KNIFE: + animAssoc->speed = 1.05f; + break; + case WEAPONTYPE_GOLFCLUB: + case WEAPONTYPE_NIGHTSTICK: + case WEAPONTYPE_BASEBALLBAT: + case WEAPONTYPE_HAMMER: + case WEAPONTYPE_KATANA: + animAssoc->speed = 0.8f; + break; + case WEAPONTYPE_CLEAVER: + case WEAPONTYPE_MACHETE: + animAssoc->speed = 0.9f; + break; + } + } + + if (m_fightState == FIGHTSTATE_MOVE_FINISHED && animAssoc->currentTime != 0.0f) { + animAssoc->SetRun(); + if (!IsPlayer()) + animAssoc->SetCurrentTime(0.0f); + } + if (IsPlayer()) + animAssoc->SetCurrentTime(0.08f); + + animAssoc->SetFinishCallback(FinishFightMoveCB, this); + 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_lastFightMove = m_curFightMove; + 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_lastFightMove = m_curFightMove; + m_curFightMove = FIGHTMOVE_IDLE; + if (IsPlayer()) + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 500; + else + m_nWaitTimer = CTimer::GetTimeInMilliseconds() + 2000; + } +} + +int32 +CPed::ChooseAttackAI(uint8 buttonPressure, bool fightWithWeapon) +{ + eWeaponType weapon = GetWeapon()->m_eWeaponType; + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(weapon); + if (!fightWithWeapon && weapon != WEAPONTYPE_UNARMED && weapon != WEAPONTYPE_BRASSKNUCKLE) { + return FIGHTMOVE_PUNCH; + } + + if (!m_pedInObjective) + return FIGHTMOVE_IDLE; + if (buttonPressure == 0) + return FIGHTMOVE_IDLE; + + uint16 pedFeatures = m_pedStats->m_flags; + bool punchOnly = !!(pedFeatures & STAT_PUNCH_ONLY); + bool canRoundhouse = !!(pedFeatures & STAT_CAN_ROUNDHOUSE); + bool canKneeHead = !!(pedFeatures & STAT_CAN_KNEE_HEAD); + bool canKick = !!(pedFeatures & STAT_CAN_KICK); + bool hasShoppingBags = !!(pedFeatures & STAT_SHOPPING_BAGS); + + CVector distVec(m_pedInObjective->GetPosition() - GetPosition()); + float dist = distVec.Magnitude(); + m_fRotationDest = CGeneral::LimitRadianAngle(distVec.Heading()); + m_fRotationCur = m_fRotationDest; + + if (fightWithWeapon) { + if (m_pedInObjective->OnGroundOrGettingUp()) { + if (CGame::nastyGame && dist < 1.2f && !m_pedInObjective->IsPlayer() + && (m_pedInObjective->m_nPedState == PED_DEAD || !m_pedInObjective->IsPedHeadAbovePos(-0.3f))) { + if (weaponInfo->IsFlagSet(WEAPONFLAG_GROUND_2ND)) + return FIGHTMOVE_MELEE2; + if (weaponInfo->IsFlagSet(WEAPONFLAG_GROUND_3RD)) + return FIGHTMOVE_MELEE3; + + return FIGHTMOVE_GROUNDKICK; + } else { + return FIGHTMOVE_IDLE; + } + } + if (dist < 2.f) { + if (m_curFightMove == FIGHTMOVE_MELEE1) { + if (GetSecondFireAnim(weaponInfo)) + return FIGHTMOVE_MELEE2; + } + if (m_curFightMove == FIGHTMOVE_MELEE2) { + if (GetFinishingAttackAnim(weaponInfo)) + return FIGHTMOVE_MELEE3; + } + return FIGHTMOVE_MELEE1; + } + return FIGHTMOVE_SHUFFLE_F; + } + if (!hasShoppingBags) { + if (punchOnly) { + if (dist < 1.4f) + return FIGHTMOVE_PUNCH; + } else { + if (m_pedInObjective->OnGroundOrGettingUp()) { + if (CGame::nastyGame && dist < 1.2f && !m_pedInObjective->IsPlayer() + && (m_pedInObjective->m_nPedState == PED_DEAD || !m_pedInObjective->IsPedHeadAbovePos(-0.3f))) { + + return FIGHTMOVE_GROUNDKICK; + } else { + return FIGHTMOVE_IDLE; + } + } + if (dist < 0.95f && canKneeHead) + return FIGHTMOVE_KNEE; + if (dist < 1.4f) + return FIGHTMOVE_PUNCH; + if (dist < 2.f && canKick) { + int nextMove = FIGHTMOVE_LONGKICK; + if (canRoundhouse && CGeneral::GetRandomNumber() & 1) + nextMove = FIGHTMOVE_ROUNDHOUSE; + return nextMove; + } + } + return FIGHTMOVE_SHUFFLE_F; + } + if (dist < 2.f) + return FIGHTMOVE_ROUNDHOUSE; + else + return FIGHTMOVE_SHUFFLE_F; +} + +int32 +CPed::ChooseAttackPlayer(uint8 buttonPressure, bool fightWithWeapon) +{ + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + const float maxAttackDist = 2.7f; + float weaponAttackDist = 2.0f; + CPed *victimPed = nil; + CPed *walkUpTo = nil; + CPed *groundAttackDeadPed = nil; + CPed *groundAttackAlivePed = nil; + if (fightWithWeapon) + weaponAttackDist = weaponInfo->m_fRange; + + bool willWalkUp = false; + PedFightMoves choosenMove = FIGHTMOVE_IDLE; + int numPedsWeCanReach = 0; + if (m_takeAStepAfterAttack) + willWalkUp = true; + + float groundAttackDeadAngle, groundAttackAliveAngle, walkAngle, victimAngle, distToVictim; + + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + CVector distVec(nearPed->GetPosition() - GetPosition()); + float dist = distVec.Magnitude(); + if (dist < maxAttackDist) { + float nearPedAngle = CGeneral::LimitRadianAngle(distVec.Heading()); + m_fRotationCur = CGeneral::LimitRadianAngle(m_fRotationCur); + float neededTurn = Abs(nearPedAngle - m_fRotationCur); + if (neededTurn > PI) + neededTurn = TWOPI - neededTurn; + + if (!nearPed->OnGroundOrGettingUp() && nearPed->m_nWaitState != WAITSTATE_SUN_BATHE_IDLE) { + if (!willWalkUp || neededTurn <= DEGTORAD(45.0f)) { + + if (neededTurn <= DEGTORAD(30.0f) || nearPed->m_pedInObjective == this + && (nearPed->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || nearPed->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS)) { + + if (dist < weaponAttackDist) { + if (!victimPed + || nearPed->m_attackTimer < victimPed->m_attackTimer && nearPed->m_attackTimer > CTimer::GetTimeInMilliseconds() - 100) { + victimPed = nearPed; + victimAngle = nearPedAngle; + distToVictim = dist; + } + ++numPedsWeCanReach; + + } else { + if (neededTurn < DEGTORAD(30.0f)) { + walkUpTo = nearPed; + walkAngle = nearPedAngle; + } + } + } + } + } else if (CGame::nastyGame && dist < 1.2f && neededTurn < DEGTORAD(55.0f)) { + if (!nearPed->DyingOrDead() || groundAttackDeadPed) { + if (!nearPed->IsPedHeadAbovePos(-0.3f)) { + groundAttackAlivePed = nearPed; + groundAttackAliveAngle = nearPedAngle; + } + } else { + groundAttackDeadPed = nearPed; + groundAttackDeadAngle = nearPedAngle; + } + ++numPedsWeCanReach; + + } else if (dist > 1.4f && dist < maxAttackDist && neededTurn < DEGTORAD(30.0f)) { + if (!walkUpTo) { + walkUpTo = nearPed; + walkAngle = nearPedAngle; + } +#ifdef FIX_BUGS + if(dist < 2.1f) +#endif + ++numPedsWeCanReach; + } + } + } + + if (victimPed) { + float adjustedAngleDiff = victimAngle - m_fRotationCur + DEGTORAD(30.0f); + if (adjustedAngleDiff < 0.0f) + adjustedAngleDiff += TWOPI; + + int16 dir = Floor(adjustedAngleDiff / DEGTORAD(60.0f)); + + // Just focus on who we're fighting with, don't care peds on ground + if (numPedsWeCanReach < 2 || fightWithWeapon) { + float angleDiff = Abs(victimAngle - m_fRotationCur); + if (angleDiff > PI) + angleDiff = TWOPI - angleDiff; + + if (angleDiff < DEGTORAD(60.0f)) + dir = 0; // forward + } + int16 randVal = CGeneral::GetRandomNumber() & 3; + switch (dir) { + case 0: // forward + if (fightWithWeapon) { + if (distToVictim < 0.95f - 0.2f && m_nPedState == PED_FIGHT) { + choosenMove = FIGHTMOVE_KNEE; + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_CLEAVER) { + if (distToVictim < 0.85f * weaponInfo->m_fRange) + choosenMove = FIGHTMOVE_MELEE1; + else + choosenMove = FIGHTMOVE_SHUFFLE_F; + } else { + float weaponRange = weaponInfo->m_fRange; + if (distToVictim < 0.75f * weaponRange && GetWeapon()->m_eWeaponType != WEAPONTYPE_SCREWDRIVER) { + if (m_lastFightMove == FIGHTMOVE_MELEE1 && GetFinishingAttackAnim(weaponInfo)) { + choosenMove = FIGHTMOVE_MELEE2; + } else if (m_lastFightMove == FIGHTMOVE_MELEE2 && GetFinishingAttackAnim(weaponInfo)) { + choosenMove = FIGHTMOVE_MELEE3; + } else { + choosenMove = FIGHTMOVE_MELEE1; + } + } else if (distToVictim < weaponRange && GetFinishingAttackAnim(weaponInfo)) { + choosenMove = FIGHTMOVE_MELEE3; + } else { + choosenMove = FIGHTMOVE_SHUFFLE_F; + } + } + } + } else if (distToVictim < 0.95f && m_nPedState == PED_FIGHT) { + choosenMove = FIGHTMOVE_KNEE; + + } else if (distToVictim < 1.4f) { + if (m_curFightMove == FIGHTMOVE_PUNCHJAB) { + choosenMove = FIGHTMOVE_PUNCH; + + } else if (m_curFightMove != FIGHTMOVE_PUNCH || randVal != 1) { + if (randVal == 2) + choosenMove = FIGHTMOVE_PUNCH; + else + choosenMove = FIGHTMOVE_PUNCHJAB; + } else { + choosenMove = FIGHTMOVE_LONGKICK; + } + } else { + choosenMove = FIGHTMOVE_LONGKICK; + } + break; + case 1: + choosenMove = FIGHTMOVE_FWDLEFT; + break; + case 2: + choosenMove = FIGHTMOVE_BACKLEFT; + break; + case 3: + choosenMove = FIGHTMOVE_BACKKICK; + break; + case 4: + choosenMove = FIGHTMOVE_BACKRIGHT; + break; + default: + choosenMove = FIGHTMOVE_FWDRIGHT; + break; + } + + // forward + if (dir == 0) { + m_fRotationDest = CGeneral::LimitRadianAngle(victimAngle); + } else { + m_fRotationDest = victimAngle - dir * DEGTORAD(60.0f); + m_fRotationDest = CGeneral::LimitRadianAngle(m_fRotationDest); + } + + m_fRotationCur = m_fRotationDest; + Say(SOUND_PED_ATTACK); + + } else if (groundAttackAlivePed || groundAttackDeadPed) { + if (fightWithWeapon && weaponInfo->IsFlagSet(WEAPONFLAG_GROUND_2ND)) { + choosenMove = FIGHTMOVE_MELEE2; + } else if (fightWithWeapon && weaponInfo->IsFlagSet(WEAPONFLAG_GROUND_3RD)) { + choosenMove = FIGHTMOVE_MELEE3; + } else { + choosenMove = FIGHTMOVE_GROUNDKICK; + } + if (groundAttackAlivePed) + m_fRotationDest = groundAttackAliveAngle; + else + m_fRotationDest = groundAttackDeadAngle; + + m_fRotationCur = m_fRotationDest; + m_lookTimer = 0; + if (groundAttackAlivePed) + SetLookFlag(groundAttackAlivePed, 1, 0); + else + SetLookFlag(groundAttackDeadPed, 1, 0); + + SetLookTimer(1500u); + + } else if (walkUpTo) { + choosenMove = FIGHTMOVE_SHUFFLE_F; + m_fRotationCur = m_fRotationDest = walkAngle; + m_lookTimer = 0; + SetLookFlag(walkUpTo, true); + SetLookTimer(1500); + + } else if (fightWithWeapon) { + // No enemy, fight with space + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_SCREWDRIVER) { + choosenMove = FIGHTMOVE_MELEE3; + } else { + if (m_lastFightMove == FIGHTMOVE_MELEE1 && GetFinishingAttackAnim(weaponInfo)) { + choosenMove = FIGHTMOVE_MELEE2; + } else if (m_lastFightMove == FIGHTMOVE_MELEE2 && GetFinishingAttackAnim(weaponInfo)) { + choosenMove = FIGHTMOVE_MELEE3; + } else { + choosenMove = FIGHTMOVE_MELEE1; + } + } + } else { + // Max number GetRandomNumberInRange returns is max-1 +#ifdef FIX_BUGS + switch (CGeneral::GetRandomNumberInRange(0,4)) { +#else + switch (CGeneral::GetRandomNumberInRange(0,3)) { +#endif + case 0: + choosenMove = FIGHTMOVE_PUNCHJAB; + break; + case 1: + choosenMove = FIGHTMOVE_PUNCH; + break; + case 2: + choosenMove = FIGHTMOVE_LONGKICK; + break; + case 3: + choosenMove = FIGHTMOVE_KNEE; + break; + default: + break; + } + } + return choosenMove; +} + +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 = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_MELEE_IDLE_FIGHTMODE); + + 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 17, total 17), second is the one of we fight with (from 18 to 27, total 10). + enum { + S37 = SOUND_FIGHT_37, + S38 = SOUND_FIGHT_38, + S39 = SOUND_FIGHT_39, + S40 = SOUND_FIGHT_40, + S41 = SOUND_FIGHT_41, + S42 = SOUND_FIGHT_42, + S43 = SOUND_FIGHT_43, + S44 = SOUND_FIGHT_44, + S45 = SOUND_FIGHT_45, + S46 = SOUND_FIGHT_46, + S47 = SOUND_FIGHT_47, + S48 = SOUND_FIGHT_48, + NO_SND = SOUND_NO_SOUND + }; + const uint16 hitSoundsByFightMoves[17][10] = { + { S37, S46, S41, S41, S46, S46, S40, S41, S43, S40 }, + { 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 }, + { S46, S46, S46, S46, S37, S47, S37, S38, S43, S38 }, + { S46, S46, S46, S46, S46, S46, S40, S41, S43, S46 }, + { S46, S46, S46, S46, S46, S46, S40, S41, S43, S40 }, + { S46, S46, S46, S46, S46, S46, S40, S41, S43, S40 }, + { S46, S46, S37, S46, S37, S47, S40, S47, S43, S37 }, + { S46, S46, S46, S46, S46, S46, S43, S44, S43, S43 }, + { S37, S46, S46, S46, S38, S47, S40, S38, S43, S46 }, + { S46, S37, S46, S37, S39, S46, S40, S39, S43, S37 }, + { S46, S37, S46, S46, S38, S47, S40, S38, S43, S46 }, + { S37, S37, S46, S46, S38, S47, S48, S38, S43, S37 }, + { S46, S46, S46, S46, S37, S46, S40, S38, S43, S46 }, + { S46, S46, S46, S37, S39, S46, S40, S39, S43, S46 }, + { S37, S46, S46, S46, S37, S46, S40, S37, S43, S46 }, + { S43, S43, S43, S43, S43, S43, S43, S43, S43, S43 } + }; + + eWeaponType weapon = GetWeapon()->m_eWeaponType; + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(weapon); + if (weaponInfo->m_AnimToPlay == ASSOCGRP_KNIFE) { + if (m_curFightMove >= FIGHTMOVE_MELEE1) { + if (m_curFightMove == FIGHTMOVE_MELEE3) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_BAT_ATTACK, (weapon << 8) | ENTITY_TYPE_PED); + } else { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_KNIFE_ATTACK, (weapon << 8) | ENTITY_TYPE_PED); + } + return; + } + } + + // This is why first dimension is between FightMove 1 and 17. + if (m_curFightMove <= FIGHTMOVE_NULL || m_curFightMove >= FIGHTMOVE_HITFRONT) + return; + + uint16 soundId; + + // And this is why second dimension is between 18 and 27. + 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, (weapon << 8) | ENTITY_TYPE_PED); +} + +bool +CPed::FightStrike(CVector &touchedNodePos, bool fightWithWeapon) +{ + CColModel *hisCol; + CVector attackDistance; + float maxDistanceToBeat; + CPed *nearPed; + CVector extendedTouchPoint; + + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + float radius = tFightMoves[m_curFightMove].strikeRadius; + if (fightWithWeapon) + radius = weaponInfo->m_fRadius; + + if (m_fightState == FIGHTSTATE_JUST_ATTACKED) + return false; + + if (this == FindPlayerPed() && fightWithWeapon && GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) + CGlass::BreakGlassPhysically(touchedNodePos, radius); + + for (int i = 0; i < m_numNearPeds; i++) { + int8 pedFound = 0; + nearPed = m_nearPeds[i]; + if (!fightWithWeapon && GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && GetWeapon()->m_eWeaponType != WEAPONTYPE_BRASSKNUCKLE) + maxDistanceToBeat = nearPed->GetBoundRadius() + radius + 0.1f; + else + maxDistanceToBeat = nearPed->GetBoundRadius() + radius; + + if ((nearPed->bUsesCollision || nearPed->m_nPedState == PED_DEAD) && (m_pedInObjective != FindPlayerPed() || nearPed == FindPlayerPed())) { + CVector nearPedCentre; + + // Have to animate a skinned clump because the initial col model is useless + hisCol = ((CPedModelInfo*)CModelInfo::GetModelInfo(nearPed->GetModelIndex()))->AnimatePedColModelSkinnedWorld(nearPed->GetClump()); + + nearPed->GetBoundCentre(nearPedCentre); + CVector potentialAttackDistance = nearPedCentre - touchedNodePos; + + // He can beat us + if (sq(maxDistanceToBeat) > potentialAttackDistance.MagnitudeSqr()) { + + for (int j = 0; j < hisCol->numSpheres; j++) { + attackDistance = hisCol->spheres[j].center; + attackDistance -= touchedNodePos; + CColSphere *hisPieces = hisCol->spheres; + maxDistanceToBeat = hisPieces[j].radius + radius; + + // We can beat him too + if (sq(maxDistanceToBeat) > attackDistance.MagnitudeSqr()) { + FightHitPed(nearPed, touchedNodePos, attackDistance, hisPieces[j].piece); + pedFound = 1; + break; + } + } + } + if (!pedFound && !fightWithWeapon) { + extendedTouchPoint = touchedNodePos - GetPosition(); + if (DotProduct(touchedNodePos - GetPosition(), nearPed->GetPosition() - GetPosition()) > 0.f) { + if (m_curFightMove == FIGHTMOVE_GROUNDKICK) { + extendedTouchPoint += tFightMoves[FIGHTMOVE_GROUNDKICK].extendReachMultiplier * GetForward(); + } else { + extendedTouchPoint.x *= tFightMoves[m_curFightMove].extendReachMultiplier; + extendedTouchPoint.y *= tFightMoves[m_curFightMove].extendReachMultiplier; + } + pedFound = -1; + extendedTouchPoint += GetPosition(); + } + } + if (pedFound == -1) { + CVector nearPedCentre = nearPed->GetBoundCentre(); + if (sq(maxDistanceToBeat) > (nearPedCentre - extendedTouchPoint).MagnitudeSqr()) { + + for (int j = 0; j < hisCol->numSpheres; j++) { + attackDistance = hisCol->spheres[j].center; + attackDistance -= extendedTouchPoint; + CColSphere* hisPieces = hisCol->spheres; + float maxDistanceToBeat2 = hisPieces[j].radius + tFightMoves[m_curFightMove].strikeRadius; + + // We can beat him too + if (sq(maxDistanceToBeat2) > attackDistance.MagnitudeSqr()) { + FightHitPed(nearPed, extendedTouchPoint, attackDistance, hisPieces[j].piece); + break; + } + } + } + } + } + } + + + if (m_fightState == FIGHTSTATE_NO_MOVE) + m_fightState = FIGHTSTATE_1; + + m_vecHitLastPos = touchedNodePos; + return false; +} + +void +CPed::FightHitPed(CPed *victim, CVector &touchPoint, CVector &dir, int16 piece) +{ + if (victim->IsPlayer() && victim->m_nPedState == PED_GETUP) + return; + + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + bool fightingWithWeapon = false; + int damageMult = tFightMoves[m_curFightMove].damage * ((CGeneral::GetRandomNumber() & 1) + 2) + 1; + + if (weaponInfo->IsFlagSet(WEAPONFLAG_FIGHTMODE)) { + fightingWithWeapon = true; + if (m_curFightMove >= FIGHTMOVE_MELEE1) { + damageMult = weaponInfo->m_nDamage; + if (m_curFightMove == FIGHTMOVE_MELEE3 && GetWeapon()->m_eWeaponType != WEAPONTYPE_SCREWDRIVER) + damageMult *= 5; + } + } + + if (IsPlayer()) { + if (((CPlayerPed*)this)->m_bAdrenalineActive) + damageMult = 20; + } else if (!fightingWithWeapon) { + damageMult *= m_pedStats->m_attackStrength; + } + + float oldVictimHealth = victim->m_fHealth; + CVector bloodPos = 0.5f * dir + touchPoint; + CVector2D diff(GetPosition() - victim->GetPosition()); + int direction = victim->GetLocalDirection(diff); + + bool brassKnucklePunch = false; + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BRASSKNUCKLE) { + if (m_curFightMove == FIGHTMOVE_PUNCHHOOK || m_curFightMove == FIGHTMOVE_PUNCHJAB || m_curFightMove == FIGHTMOVE_BACKLEFT || + m_curFightMove == FIGHTMOVE_STDPUNCH || m_curFightMove == FIGHTMOVE_PUNCH) { + brassKnucklePunch = true; + damageMult *= 1.5f; + } + } + victim->ReactToAttack(this); + + // Mostly unused. if > 5, ANIM_HIT_BIGSTEP will be run, that's it. + int unk2; + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && GetWeapon()->m_eWeaponType != WEAPONTYPE_BRASSKNUCKLE && + !victim->IsPlayer() && !fightingWithWeapon) + unk2 = 101; + else + unk2 = damageMult; + + victim->StartFightDefend(direction, tFightMoves[m_curFightMove].hitLevel, unk2); + PlayHitSound(victim); + m_fightState = FIGHTSTATE_JUST_ATTACKED; + RpAnimBlendClumpGetAssociation(GetClump(), tFightMoves[m_curFightMove].animId)->speed = 0.6f; + + if (!victim->DyingOrDead()) { + if(fightingWithWeapon) + victim->InflictDamage(this, GetWeapon()->m_eWeaponType, damageMult, (ePedPieceTypes)piece, direction); + else + victim->InflictDamage(this, WEAPONTYPE_UNARMED, damageMult * 3.0f, (ePedPieceTypes)piece, direction); + } + + if (CGame::nastyGame && weaponInfo->m_AnimToPlay == ASSOCGRP_KNIFE && m_curFightMove >= FIGHTMOVE_MELEE1 + && victim->GetIsOnScreen()) { + + static float particleRightLen = 0.05f; + static float particleUpLen = 0.05f; + + // Just for particles. We will restore it below. + dir /= (20.0f * dir.Magnitude()); + if (m_curFightMove == FIGHTMOVE_MELEE1) { + float rightMult = -particleRightLen; + dir += particleUpLen * GetUp() + rightMult * GetRight(); + + } else if (m_curFightMove == FIGHTMOVE_MELEE2) { + float upMult = 2.0f * particleUpLen; + dir += particleRightLen * GetRight() + upMult * GetUp(); + } + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, dir, nil, 0.0f, 0, 0, 0, 0); + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, dir, nil, 0.0f, 0, 0, 0, 0); + if (IsPlayer()) { + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, dir, nil, 0.0f, 0, 0, 0, 0); + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, dir, nil, 0.0f, 0, 0, 0, 0); + } + if (!(CGeneral::GetRandomNumber() & 3)) { + CParticle::AddParticle(PARTICLE_TEST, bloodPos, dir, nil, 0.0f, 0, 0, 0, 0); + } + } else if (CGame::nastyGame && (tFightMoves[m_curFightMove].hitLevel > HITLEVEL_MEDIUM || fightingWithWeapon) + && victim->GetIsOnScreen()) { + + // Just for particles. We will restore it below. + dir /= (10.0f * dir.Magnitude()); + for (int i = 0; i < 4; i++) { + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, dir, nil, 0.0f, 0, 0, 0, 0); + } + } + + eWeaponType weaponType = GetWeapon()->m_eWeaponType; + if (!fightingWithWeapon) { + if (!victim->OnGround()) { + float curVictimHealth = victim->m_fHealth; + if (curVictimHealth > 0.0f + && (curVictimHealth < 30.0f && oldVictimHealth > 30.0f + || weaponType != WEAPONTYPE_UNARMED && weaponType != WEAPONTYPE_BRASSKNUCKLE && IsPlayer() + || victim->m_pedStats->m_flags & STAT_ONE_HIT_KNOCKDOWN || brassKnucklePunch)) { + + victim->SetFall(0, (AnimationId)(direction + ANIM_STD_HIGHIMPACT_FRONT), 0); + if (victim->m_nPedState == PED_FALL) + victim->bIsStanding = false; + } + } + } + + if (victim->m_nPedState == PED_DIE || !victim->bIsStanding) { + dir = victim->GetPosition() - GetPosition(); + dir.Normalise(); + dir.z = 1.0f; + victim->bIsStanding = false; + + float moveMult; + if (fightingWithWeapon) { + moveMult = Min(damageMult * 0.02f, 1.0f); + } else if (m_curFightMove == FIGHTMOVE_GROUNDKICK) { + moveMult = Min(damageMult * 0.6f, 4.0f); + } else { + if (victim->m_nPedState != PED_DIE || damageMult >= 20) { + moveMult = damageMult; + } else { + moveMult = Min(damageMult * 2.0f, 14.0f); + } + } + + victim->ApplyMoveForce(moveMult * 0.6 * dir); + } + + if (weaponType != WEAPONTYPE_KNIFE && weaponType != WEAPONTYPE_MACHETE + && weaponType != WEAPONTYPE_KATANA && weaponType != WEAPONTYPE_CHAINSAW) { + + if (victim->m_nPedType == PEDTYPE_COP) + CEventList::RegisterEvent(EVENT_ASSAULT_POLICE, EVENT_ENTITY_PED, victim, this, 2000); + else + CEventList::RegisterEvent(EVENT_ASSAULT, EVENT_ENTITY_PED, victim, this, 2000); + } else { + if (victim->m_nPedType == PEDTYPE_COP) + CEventList::RegisterEvent(EVENT_ASSAULT_NASTYWEAPON_POLICE, EVENT_ENTITY_PED, victim, this, 2000); + else + CEventList::RegisterEvent(EVENT_ASSAULT_NASTYWEAPON, EVENT_ENTITY_PED, victim, this, 2000); + } +} + +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, extendReachMultiplier; + 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 %f %c %s %d %d", + moveName, + &startFireTime, + &endFireTime, + &comboFollowOnTime, + &strikeRadius, + &extendReachMultiplier, + &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].extendReachMultiplier = extendReachMultiplier; + 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, "default") != 0) { + 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_UNK) + 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)); + if (!CGame::germanGame) + 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)); + } + if (!CGame::germanGame) + 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; + } + + bool willStandStill = false; + for (int i = 0; i < m_numNearPeds; i++) { + if ((m_eventOrThreat - m_nearPeds[i]->GetPosition()).MagnitudeSqr() < sq(0.4f)) { + SetMoveState(PEDMOVE_STILL); + willStandStill = true; + break; + } + } + + if (!willStandStill) + 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 (damagedBy == FindPlayerPed() && damagedBy != this && damage > 3.0f) + ++CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel; + + if (player == this) { + if (!player->m_bCanBeDamaged) + return false; + + if (damagedBy && damagedBy->IsPed() && ((CPed*)damagedBy)->m_nPedType == PEDTYPE_GANG7) + return false; + + if ((method == WEAPONTYPE_FLAMETHROWER || method == WEAPONTYPE_MOLOTOV) && CWorld::Players[CWorld::PlayerInFocus].m_bFireproof) + return false; + + player->AnnoyPlayerPed(false); + } + + if (DyingOrDead()) + return false; + + if (method == WEAPONTYPE_DROWNING && !bDrownsInWater) + return false; + + if (!bUsesCollision && (!bInVehicle || m_nPedState != PED_DRIVING) && 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; + + if (!IsPlayer() && + (method == WEAPONTYPE_SCREWDRIVER || method == WEAPONTYPE_KNIFE || (method >= WEAPONTYPE_CLEAVER && method <= WEAPONTYPE_CHAINSAW))) + m_bleedCounter = 200; + + bool detectDieAnim = true; + if (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) { + CAnimBlendAssociation *fallAssoc = RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_PARTIAL); + if (!fallAssoc || fallAssoc->IsRunning()) { + if (fallAssoc && fallAssoc->blendDelta >= 0.0f) + dieAnim = ANIM_STD_NUM; + else + dieAnim = ANIM_STD_KO_FRONT; + } else { + if (fallAssoc->flags & ASSOC_FRONTAL) + dieAnim = ANIM_STD_HIT_FLOOR_FRONT; + else + dieAnim = ANIM_STD_HIT_FLOOR; + + dieDelta *= 2.0f; + dieSpeed = 0.5f; + } + detectDieAnim = false; + } + + if (detectDieAnim) { + switch (method) { + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_BRASSKNUCKLE: + 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_SCREWDRIVER: + case WEAPONTYPE_GOLFCLUB: + case WEAPONTYPE_NIGHTSTICK: + case WEAPONTYPE_KNIFE: + case WEAPONTYPE_BASEBALLBAT: + case WEAPONTYPE_HAMMER: + case WEAPONTYPE_CLEAVER: + case WEAPONTYPE_MACHETE: + case WEAPONTYPE_KATANA: + case WEAPONTYPE_CHAINSAW: + if (bMeleeProof) + return false; + + if (method != WEAPONTYPE_KATANA || + damagedBy != FindPlayerPed() + || FindPlayerPed()->m_nPedState != PED_FIGHT + || FindPlayerPed()->m_curFightMove != FIGHTMOVE_MELEE1 && FindPlayerPed()->m_curFightMove != FIGHTMOVE_MELEE2 + || CGeneral::GetRandomNumber() & 3) { + + 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 if (damagedBy != FindPlayerPed() || FindPlayerPed()->m_curFightMove != FIGHTMOVE_MELEE2) { + if (damagedBy != FindPlayerPed() || FindPlayerPed()->m_curFightMove != FIGHTMOVE_MELEE3) { + 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_SHOT_STOMACH; + } + } else { + dieAnim = ANIM_STD_KO_SHOT_FACE; + } + } else { + dieAnim = ANIM_STD_KO_SHOT_FACE; + RemoveBodyPart(PED_HEAD, direction); + headShot = true; + willLinger = true; + } + break; + case WEAPONTYPE_COLT45: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + case WEAPONTYPE_SPAS12_SHOTGUN: + case WEAPONTYPE_TEC9: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SILENCED_INGRAM: + case WEAPONTYPE_MP5: + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + case WEAPONTYPE_M60: + case WEAPONTYPE_MINIGUN: + case WEAPONTYPE_UZI_DRIVEBY: + + if (bBulletProof) + return false; + + bool dontRemoveLimb; + if (IsPlayer() || bNoCriticalHits) + dontRemoveLimb = true; + else if (method != WEAPONTYPE_M4 && method != WEAPONTYPE_RUGER && method != WEAPONTYPE_SNIPERRIFLE && + method != WEAPONTYPE_LASERSCOPE) { + if (method == WEAPONTYPE_SHOTGUN) + dontRemoveLimb = CGeneral::GetRandomNumber() & 7; + else + dontRemoveLimb = CGeneral::GetRandomNumber() & 15; + } else + dontRemoveLimb = false; + + 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_GRENADE: + case WEAPONTYPE_ROCKETLAUNCHER: + 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 && pedPiece != PEDPIECE_TORSO) { + 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; + m_lastDamEntity = damagedBy; + } + + if (method == WEAPONTYPE_FALL) { + if (RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_ROLLOUT_LHS)) { + if (m_fHealth >= 1.0 && m_fHealth - healthImpact < 5.0f) { + m_fHealth = Min(m_fHealth, 5.0f); + return false; + } + } + } + + if (m_fHealth - healthImpact >= 1.0f && !willLinger) { + m_fHealth -= healthImpact; + return false; + } + + if (bInVehicle) { + if (method != WEAPONTYPE_DROWNING) { + if (m_pMyVehicle) { + CVehicle* pVehicle = m_pMyVehicle; + bool bDone = false; + if (m_pMyVehicle->IsBike()) { + m_fHealth = 0.0f; + ((CBike*)m_pMyVehicle)->KnockOffRider(method, direction, this, false); + bDone = true; + } else { + if (m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_CAR) { + if (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(true)) { + 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) { + CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 10; + CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention += 5.f; + } + } + } + for (int i = 0; i < ARRAY_SIZE(pVehicle->pPassengers); i++) { + CPed* passenger = pVehicle->pPassengers[i]; + if (passenger && passenger != this && damagedBy) + passenger->ReactToAttack(damagedBy); + } + + CPed *driverOfVeh = pVehicle->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); + } + if (bDone) + return true; + } + 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()) { + CDarkel::RegisterKillByPlayer(this, method, headShot); + CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 10; + CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention += 5.f; + m_threatEntity = player; + } else { + CDarkel::RegisterKillNotByPlayer(this, method); + } + if (method == WEAPONTYPE_DROWNING) { + bIsInTheAir = false; + if (FindPlayerPed() == this) + CStats::TimesDrowned++; + } + + 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) { + if (CEntity::GetIsOnScreen()) { + m_pedIK.GetComponentPosition(pos, nodeId); + 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) +{ + // VC doesn't have detachable limbs :shrug: + return nil; +} + +// III leftover and unused +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(); + // } + + bBodyPartJustCameOff = true; + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 150; + + RemoveBodyPart(PED_HEAD, 0); + 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); + } + } + } +} + +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) { + if (m_nWaitState == WAITSTATE_SUN_BATHE_IDLE) { + SetGetUp(); + return; + } + if (collideWith->m_nWaitState == WAITSTATE_SUN_BATHE_IDLE) { + collideWith->SetGetUp(); + return; + } + + 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 + || m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && m_pedInObjective == collideWith + || collideWith->m_objective == OBJECTIVE_FOLLOW_CHAR_IN_FORMATION && collideWith->m_pedInObjective == this + )) { + + 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 (FindPlayerPed() != m_pedInObjective + || m_objective != OBJECTIVE_KILL_CHAR_ANY_MEANS && m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT + || collideWith == m_pedInObjective) { + + 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 (collideWith->m_nMoveState != PEDMOVE_STILL && GetWeapon()->IsTypeMelee() + && collideWith->m_pedInObjective == m_pedInObjective) { + + int colliderIndexAtPlayersKillList = -1; + int ourIndexAtPlayersKillList = -1; + for (int i = 0; i < ARRAY_SIZE(((CPlayerPed*)m_pedInObjective)->m_pMeleeList); i++) { + CPed *pedInKillList = ((CPlayerPed*)m_pedInObjective)->m_pMeleeList[i]; + if (pedInKillList == this) { + ourIndexAtPlayersKillList = i; + } else if (pedInKillList == collideWith) { + colliderIndexAtPlayersKillList = i; + } + } + bool weAreCloserToTargetThenCollider = false; + if ((GetPosition() - m_vecSeekPos).MagnitudeSqr2D() < (collideWith->GetPosition() - m_vecSeekPos).MagnitudeSqr2D()) + weAreCloserToTargetThenCollider = true; + + if (ourIndexAtPlayersKillList > 0 && !weAreCloserToTargetThenCollider) { + if (colliderIndexAtPlayersKillList > 0) { + int time = 300; + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &time); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + time; + + } else if (collideWith->m_pedInObjective == FindPlayerPed()) { + ((CPlayerPed*)m_pedInObjective)->RemovePedFromMeleeList(this); + int time = 500; + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &time); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + time; + } + } else if (!weAreCloserToTargetThenCollider) { + int time = 300; + SetWaitState(WAITSTATE_CROSS_ROAD_LOOK, &time); + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + time; + } + } else { + SetDirectionToWalkAroundObject(collideWith); + } + } + } 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); + m_fRotationCur = 0.3f + m_fRotationCur; + m_fRotationDest = m_fRotationCur; + } + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(250, 450); + } + } + } else { + if (m_pedInObjective && (collideWith == m_pedInObjective || collideWith->m_pedInObjective == m_pedInObjective) && CTimer::GetTimeInMilliseconds() > m_nPedStateTimer) { + 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 || collideWith->IsPlayer() + || CTimer::GetTimeInMilliseconds() < m_nPedStateTimer) { + + 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; + m_nPedStateTimer = CTimer::GetTimeInMilliseconds() + 2000; + 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 || + m_pedStats->m_defendWeakness <= collideWith->m_pedStats->m_defendWeakness) { + + // 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_49, 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_49, 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 (!m_pCollidingEntity || car->GetStatus() == STATUS_PLAYER) + m_pCollidingEntity = car; + return; + } + + if (m_nPedState == PED_DEAD) + return; + + if (m_pCurSurface) { + if (m_pCurSurface->IsVehicle() && (((CVehicle*)m_pCurSurface)->IsBoat()|| 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 really distVec. + 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->IsCar()) { + 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(); + + distVec *= Min(car->m_fMass / 1400.0f, 1.0f); + 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(); + distVec *= Min(car->m_fMass / 1400.0f, 1.0f); + car->ApplyMoveForce(distVec * -60.0f); + Say(SOUND_PED_DEFEND); + } + + 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); + } + } +} + +void +CPed::DriveVehicle(void) +{ + if (bOffscreen) + return; + + CVehicle *veh = m_pMyVehicle; + if (veh->IsBike()) { + CBike *bike = (CBike*)veh; + float blendDelta = 1.0f; + float targetUDLean = 0.0f; + CAnimBlendAssociation *leftAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_LEFT); + CAnimBlendAssociation *rightAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_RIGHT); + CAnimBlendAssociation *stillAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_READY); + CAnimBlendAssociation *fwdAssoc, *backAssoc; + if (IsPlayer()) { + fwdAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_LEANF); + backAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_LEANB); + } + CAnimBlendAssociation *walkbackAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_WALKBACK); + CAnimBlendAssociation *drivebyAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_DRIVEBY_LHS); + if (!drivebyAssoc) + drivebyAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_DRIVEBY_RHS); + if (!drivebyAssoc) + drivebyAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_BIKE_DRIVEBY_FORWARD); + + float velocityFwdDotProd = DotProduct(bike->m_vecMoveSpeed, bike->GetForward()); + if (m_vecTurnSpeed.MagnitudeSqr() > 0.09f) { + bike->KnockOffRider(WEAPONTYPE_FALL, 2, this, false); + if (bike->pPassengers[0]) + bike->KnockOffRider(WEAPONTYPE_FALL, 2, bike->pPassengers[0], false); + return; + } + if (!drivebyAssoc && Abs(velocityFwdDotProd) < 0.02f) { + if (!stillAssoc || stillAssoc->blendAmount < 1.0 && stillAssoc->blendDelta <= 0.0) { + stillAssoc = CAnimManager::BlendAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_READY, 2.0f); + } + } else { + if (velocityFwdDotProd >= 0.0f) { + if (stillAssoc && stillAssoc->blendDelta >= 0.0f) + stillAssoc->blendDelta = -4.0f; + if (walkbackAssoc && walkbackAssoc->blendDelta >= 0.0f) + walkbackAssoc->blendDelta = -4.0f; + } else { + float maxReverseSpeed = bike->pHandling->Transmission.fMaxReverseVelocity; + if (3.5f * maxReverseSpeed > velocityFwdDotProd && (bike->m_nWheelsOnGround || bike->GetUp().z < -0.5f)) { + bike->KnockOffRider(WEAPONTYPE_FALL, 2, this, false); + if (bike->pPassengers[0]) + bike->KnockOffRider(WEAPONTYPE_FALL, 2, bike->pPassengers[0], false); + return; + } + if (bike->m_fGasPedal >= 0.0 || velocityFwdDotProd <= maxReverseSpeed * 1.5) { + if (IsPlayer() && velocityFwdDotProd < maxReverseSpeed * 1.5) + targetUDLean = -1.0f; + + if (stillAssoc && stillAssoc->blendDelta >= 0.0f) + stillAssoc->blendDelta = -4.0f; + + if (walkbackAssoc && walkbackAssoc->blendDelta >= 0.0f) { + walkbackAssoc->blendDelta = -4.0f; + } + } else if (!walkbackAssoc || walkbackAssoc->blendAmount < 1.0f && walkbackAssoc->blendDelta <= 0.0f) { + walkbackAssoc = CAnimManager::BlendAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_WALKBACK, 4.0f); + } + } + } + if (stillAssoc) + blendDelta -= Min(1.0f, CTimer::GetTimeStepNonClipped() * 0.02f * stillAssoc->blendDelta + stillAssoc->blendAmount); + + if (drivebyAssoc) + blendDelta -= Min(blendDelta, CTimer::GetTimeStepNonClipped() * 0.02f * drivebyAssoc->blendDelta + drivebyAssoc->blendAmount); + + if (walkbackAssoc) + blendDelta -= Min(blendDelta, CTimer::GetTimeStepNonClipped() * 0.02f * walkbackAssoc->blendDelta + walkbackAssoc->blendAmount); + + float targetLRLean, timeBlend, neededAngForWheelie, stoppieAng; + + // Smooth the lean amount + if (targetUDLean == -1.0f) { + targetLRLean = 0.0f; + timeBlend = Pow(0.86f, CTimer::GetTimeStep()); + } else { + targetLRLean = Clamp(bike->m_fLeanLRAngle / bike->pBikeHandling->fFullAnimLean, -1.0f, 1.0f); + timeBlend = Pow(0.86f, CTimer::GetTimeStep()); + } + + bike->m_fPedLeanAmountLR = bike->m_fPedLeanAmountLR * timeBlend + (1.0 - timeBlend) * targetLRLean; + + if (!IsPlayer()) { + targetUDLean = 0.0f; + + } else if (targetUDLean > -1.0f) { + targetUDLean = bike->m_fLeanInput; + bike->bWheelieCam = false; + neededAngForWheelie = 1.0f; + if (bike->m_aWheelTimer[0] != 0.0f || bike->m_aWheelTimer[1] != 0.0f || bike->GetForward().z <= 0.0f || + (0.0f == bike->m_aWheelTimer[2] && 0.0f == bike->m_aWheelTimer[3])) { + + if (0.0f == bike->m_aWheelTimer[2] && 0.0f == bike->m_aWheelTimer[3] && + (bike->GetForward().z < 0.0f && (bike->m_aWheelTimer[0] != 0.0f || bike->m_aWheelTimer[1] != 0.0f))) { + + stoppieAng = bike->pBikeHandling->fStoppieAng; + if (stoppieAng - bike->GetForward().z > 0.6f * stoppieAng) + bike->bWheelieCam = true; + } + } else { + float wheelieAng = bike->pBikeHandling->fWheelieAng; + neededAngForWheelie = wheelieAng - bike->GetForward().z; + if (neededAngForWheelie < wheelieAng / 2.f) + bike->bWheelieCam = true; + } + if (neededAngForWheelie >= 0.15f) { + if (bike->m_fBrakePedal <= 0.5f || velocityFwdDotProd <= 0.01f) { + if (bike->m_fGasPedal > 0.5f && targetUDLean <= 0.0f && 0.3f * bike->pHandling->Transmission.fMaxCruiseVelocity > velocityFwdDotProd) { + targetUDLean = Min(0.1f, targetUDLean); + } + } else { + targetUDLean = Max(0.1f, targetUDLean); + } + } else { + targetUDLean = Max(0.25f, targetUDLean); + } + float targetLRLeanABS = Abs(targetLRLean); + if (targetLRLeanABS > 0.3f) { + // Yes, UD + targetUDLean *= Max(0.0f, 1.0f - (targetLRLeanABS - 0.3f) * 50.f / 13.f); + } + } + if (IsPlayer()) { + float timeBlend = Pow(0.89f, CTimer::GetTimeStep()); + bike->m_fPedLeanAmountUD = (timeBlend * bike->m_fPedLeanAmountUD) + ((1.0f - timeBlend) * targetUDLean); + } else { + bike->m_fPedLeanAmountUD = 0.0f; + } + + float fwdBackLeanAmount, leftRightLeanAmount; + if (Abs(bike->m_fPedLeanAmountLR) <= 0.56f && IsPlayer()) { + + if (Abs(bike->m_fPedLeanAmountUD) <= 0.56f) { + CVector2D smoothedLean(bike->m_fPedLeanAmountLR, bike->m_fPedLeanAmountUD); + float smoothLeanMag = smoothedLean.Magnitude(); + if (smoothLeanMag <= 0.01f) { + fwdBackLeanAmount = Abs(smoothedLean.y); + leftRightLeanAmount = Abs(smoothedLean.x); + } else { + fwdBackLeanAmount = Abs(smoothedLean.y / smoothLeanMag); + leftRightLeanAmount = Abs(smoothedLean.x / smoothLeanMag); + } + } else { + fwdBackLeanAmount = 1.0f; + leftRightLeanAmount = 0.0f; + } + } else { + fwdBackLeanAmount = 0.0f; + leftRightLeanAmount = 1.0f; + } + float fwdBackBlend = fwdBackLeanAmount * blendDelta; + float leftRightBlend = leftRightLeanAmount * blendDelta; + if (IsPlayer()) { + if (!fwdAssoc) + fwdAssoc = CAnimManager::AddAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_LEANF); + if (!backAssoc) + backAssoc = CAnimManager::AddAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_LEANB); + + if (bike->m_fPedLeanAmountUD < 0.0f) { + backAssoc->blendAmount = fwdBackBlend; + backAssoc->SetCurrentTime(-(bike->m_fPedLeanAmountUD * backAssoc->hierarchy->totalLength)); + backAssoc->flags &= ~ASSOC_RUNNING; + fwdAssoc->blendAmount = 0.0f; + } else { + fwdAssoc->blendAmount = fwdBackBlend; + fwdAssoc->SetCurrentTime(bike->m_fPedLeanAmountUD* fwdAssoc->hierarchy->totalLength); + fwdAssoc->flags &= ~ASSOC_RUNNING; + backAssoc->blendAmount = 0.0f; + } + } + if (!leftAssoc) + leftAssoc = CAnimManager::AddAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_LEFT); + if (!rightAssoc) + rightAssoc = CAnimManager::AddAnimation(GetClump(), bike->m_bikeAnimType, ANIM_BIKE_RIGHT); + + if (bike->m_fPedLeanAmountLR < 0.0f) { + leftAssoc->blendAmount = leftRightBlend; + leftAssoc->SetCurrentTime(-(bike->m_fPedLeanAmountLR * leftAssoc->hierarchy->totalLength)); + leftAssoc->flags &= ~ASSOC_RUNNING; + rightAssoc->blendAmount = 0.0f; + } else { + rightAssoc->blendAmount = leftRightBlend; + rightAssoc->SetCurrentTime(bike->m_fPedLeanAmountLR* rightAssoc->hierarchy->totalLength); + rightAssoc->flags &= ~ASSOC_RUNNING; + leftAssoc->blendAmount = 0.0f; + } + if (velocityFwdDotProd > 0.3f) { + RwV3d Xaxis = { 1.0f, 0.0f, 0.0f }; + RwV3d Yaxis = { 0.0f, 1.0f, 0.0f }; + RtQuatRotate(&m_pFrames[PED_HEAD]->hanimFrame->q, &Xaxis, CGeneral::GetRandomNumberInRange(-6.0f * velocityFwdDotProd, 6.0f * velocityFwdDotProd), rwCOMBINEPOSTCONCAT); + RtQuatRotate(&m_pFrames[PED_HEAD]->hanimFrame->q, &Yaxis, CGeneral::GetRandomNumberInRange(-6.0f * velocityFwdDotProd, 6.0f * velocityFwdDotProd), rwCOMBINEPOSTCONCAT); + bDontAcceptIKLookAts = true; + } + return; + } + + if (!IsPlayer()) + return; + + float steerAngle = m_pMyVehicle->m_fSteerAngle; + CAnimBlendAssociation* lDriveAssoc; + CAnimBlendAssociation* rDriveAssoc; + CAnimBlendAssociation* lbAssoc; + CAnimBlendAssociation* sitAssoc; + if (m_pMyVehicle->IsBoat() && !(m_pMyVehicle->pHandling->Flags & HANDLING_SIT_IN_BOAT)) { + sitAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_BOAT_DRIVE); + + if (!sitAssoc || sitAssoc->blendAmount < 1.0f) { + return; + } + + lDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_BOAT_DRIVE_LEFT); + rDriveAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_BOAT_DRIVE_RIGHT); + lbAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_BOAT_LOOKBEHIND); + } else if (m_pMyVehicle->bLowVehicle) { + sitAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_SIT_LO); + + if (!sitAssoc || sitAssoc->blendAmount < 1.0f) { + return; + } + + 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) { + return; + } + + 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 (!driveByAssoc) + driveByAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_LEFT_LO); + if (!driveByAssoc) + driveByAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_CAR_DRIVEBY_RIGHT_LO); + + if (m_pMyVehicle->bLowVehicle || m_pMyVehicle->m_fGasPedal >= 0.0f || driveByAssoc || + m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI || m_pMyVehicle->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE) { + 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->IsBoat() && !(m_pMyVehicle->pHandling->Flags & HANDLING_SIT_IN_BOAT)) + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_DRIVE_RIGHT); + 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->IsBoat() && !(m_pMyVehicle->pHandling->Flags & HANDLING_SIT_IN_BOAT)) + CAnimManager::AddAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_DRIVE_LEFT); + 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 && lbAssoc->blendDelta <= 0.0f)) { + + if (m_pMyVehicle->IsBoat() && !(m_pMyVehicle->pHandling->Flags & HANDLING_SIT_IN_BOAT)) + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_BOAT_LOOKBEHIND, 4.0f); + else + CAnimManager::BlendAnimation(GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_LOOKBEHIND, 4.0f); + } + } +} + +void +CPed::RemoveWeaponAnims(int unused, float animDelta) +{ + CAnimBlendAssociation *weaponAssoc; + //CWeaponInfo::GetWeaponInfo(unused); + + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_WEAPON_FIRE); + if (weaponAssoc) { + weaponAssoc->blendDelta = animDelta; + weaponAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_WEAPON_FIRE_2ND); + if (weaponAssoc) { + weaponAssoc->blendDelta = animDelta; + weaponAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_WEAPON_FIRE_3RD); + if (weaponAssoc) { + weaponAssoc->blendDelta = animDelta; + weaponAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_WEAPON_RELOAD); + if (weaponAssoc) { + weaponAssoc->blendDelta = animDelta; + weaponAssoc->flags |= ASSOC_DELETEFADEDOUT; + } + weaponAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_MELEE_IDLE_FIGHTMODE); + if (weaponAssoc) { + weaponAssoc->flags |= ASSOC_DELETEFADEDOUT; + if (weaponAssoc->flags & ASSOC_PARTIAL) + weaponAssoc->blendDelta = animDelta; + else + CAnimManager::BlendAnimation(GetClump(), m_animGroup, ANIM_STD_IDLE, -animDelta); + } +} diff --git a/src/miami/peds/PedIK.cpp b/src/miami/peds/PedIK.cpp new file mode 100644 index 00000000..ea3cf1aa --- /dev/null +++ b/src/miami/peds/PedIK.cpp @@ -0,0 +1,375 @@ +#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(8.0f), DEGTORAD(45.0f), DEGTORAD(-45.0f), DEGTORAD(5.0f) }; +LimbMovementInfo CPedIK::ms_headInfo = { DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(15.0f), DEGTORAD(45.0f), DEGTORAD(-45.0f), DEGTORAD(8.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(5.0f), DEGTORAD(-120.0f), DEGTORAD(20.0f), DEGTORAD(70.0f), DEGTORAD(-70.0f), DEGTORAD(20.0f) }; +LimbMovementInfo CPedIK::ms_lowerArmInfo = { DEGTORAD(60.0f), DEGTORAD(0.0f), DEGTORAD(15.0f), DEGTORAD(90.0f), DEGTORAD(-90.0f), DEGTORAD(10.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; +} + +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); +} + +void +CPedIK::RotateTorso(AnimBlendFrameData *node, LimbOrientation *limb, bool changeRoll) +{ + RtQuat *q = &node->hanimFrame->q; + RtQuatRotate(q, &XaxisIK, RADTODEG(limb->yaw), rwCOMBINEREPLACE); + RtQuatRotate(q, &ZaxisIK, RADTODEG(limb->pitch), rwCOMBINEPRECONCAT); + m_ped->bDontAcceptIKLookAts = true; +} + +void +CPedIK::GetComponentPosition(RwV3d &pos, uint32 node) +{ + pos = GetComponentMatrix(m_ped, node)->pos; +} + +LimbMoveStatus +CPedIK::MoveLimb(LimbOrientation &limb, float targetYaw, float targetPitch, LimbMovementInfo &moveInfo) +{ + LimbMoveStatus result = ONE_ANGLE_COULDNT_BE_SET_EXACTLY; + + // yaw + + if(Abs(limb.yaw-targetYaw) < moveInfo.yawD){ + limb.yaw = targetYaw; + result = ANGLES_SET_EXACTLY; + }else{ + if (limb.yaw > targetYaw) { + limb.yaw -= moveInfo.yawD; + } else if (limb.yaw < targetYaw) { + limb.yaw += moveInfo.yawD; + } + } + + 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 (Abs(limb.pitch - targetPitch) < moveInfo.pitchD){ + limb.pitch = targetPitch; + }else{ + if (limb.pitch > targetPitch) { + limb.pitch -= moveInfo.pitchD; + } else if (limb.pitch < targetPitch) { + limb.pitch += moveInfo.pitchD; + } + 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; +} + +bool +CPedIK::LookInDirection(float targetYaw, float targetPitch) +{ + bool success = true; + float yaw, pitch; + if (!(m_ped->m_pFrames[PED_HEAD]->flag & AnimBlendFrameData::IGNORE_ROTATION)) { + m_ped->m_pFrames[PED_HEAD]->flag |= AnimBlendFrameData::IGNORE_ROTATION; + RwMatrix *m = GetComponentMatrix(m_ped, PED_NECK); + m_headOrient.yaw = Atan2(-m->at.y, -m->at.x); + m_headOrient.yaw -= m_ped->m_fRotationCur; + m_headOrient.yaw = CGeneral::LimitRadianAngle(m_headOrient.yaw); + float up = Clamp(m->up.z, -1.0f, 1.0f); + m_headOrient.pitch = Atan2(-up, Sqrt(1.0f - SQR(-up))); + } + + // parent of head is neck + RwMatrix *m = GetComponentMatrix(m_ped, PED_NECK); + yaw = CGeneral::LimitRadianAngle(Atan2(-m->at.y, -m->at.x)); + float up = Clamp(m->up.z, -1.0f, 1.0f); + pitch = Atan2(-up, Sqrt(1.0f - SQR(-up))); + float headYaw = CGeneral::LimitRadianAngle(targetYaw - (yaw + m_torsoOrient.yaw)); + float headPitch = CGeneral::LimitRadianAngle(targetPitch - pitch) * Cos(Min(Abs(headYaw), HALFPI)); + + LimbMoveStatus headStatus = MoveLimb(m_headOrient, headYaw, headPitch, ms_headInfo); + if (headStatus == ANGLES_SET_TO_MAX) + success = false; + + if (headStatus != ANGLES_SET_EXACTLY && !(m_flags & LOOKAROUND_HEAD_ONLY)) + if (MoveLimb(m_torsoOrient, CGeneral::LimitRadianAngle(targetYaw-m_ped->m_fRotationCur), targetPitch, ms_torsoInfo)) + success = true; + + // This was RotateHead + RtQuat *q = &m_ped->m_pFrames[PED_HEAD]->hanimFrame->q; + RtQuatRotate(q, &ZaxisIK, RADTODEG(m_headOrient.pitch), rwCOMBINEREPLACE); + RtQuatRotate(q, &XaxisIK, RADTODEG(m_headOrient.yaw), rwCOMBINEPRECONCAT); + m_ped->bDontAcceptIKLookAts = true; + + if (!(m_flags & LOOKAROUND_HEAD_ONLY)) + RotateTorso(m_ped->m_pFrames[PED_MID], &m_torsoOrient, false); + return success; +} + +bool +CPedIK::LookAtPosition(CVector const &pos) +{ + RwV3d *pedpos = &GetComponentMatrix(m_ped, PED_MID)->pos; + float yawToFace = CGeneral::GetRadianAngleBetweenPoints( + pos.x, pos.y, + pedpos->x, pedpos->y); + + float pitchToFace = CGeneral::GetRadianAngleBetweenPoints( + // BUG? not using pedpos here + pos.z, (m_ped->GetPosition() - pos).Magnitude2D(), + pedpos->z, 0.0f); + + return LookInDirection(yawToFace, pitchToFace); +} + +bool +CPedIK::PointGunInDirection(float targetYaw, float targetPitch) +{ + bool result = true; + bool armPointedToGun = false; + targetYaw = CGeneral::LimitRadianAngle(targetYaw - m_ped->GetForward().Heading()); + m_flags &= ~GUN_POINTED_SUCCESSFULLY; + m_flags |= LOOKAROUND_HEAD_ONLY; + if (m_flags & AIMS_WITH_ARM) { + armPointedToGun = PointGunInDirectionUsingArm(targetYaw, targetPitch); + targetYaw = CGeneral::LimitRadianAngle(targetYaw - (m_upperArmOrient.yaw + m_lowerArmOrient.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; + matrix = RwMatrixCreate(); + *matrix = *GetComponentMatrix(m_ped, PED_CLAVICLER); + ExtractYawAndPitchWorld(matrix, &yaw, &pitch); + RwMatrixDestroy(matrix); + + if(m_flags & AIMS_WITH_ARM){ + if(targetPitch > 0.0f) + targetPitch = Max(targetPitch - Abs(targetYaw), 0.0f); + else + targetPitch = Min(targetPitch + Abs(targetYaw), 0.0f); + } + LimbMoveStatus status = MoveLimb(m_torsoOrient, targetYaw, targetPitch, ms_torsoInfo); + if (status == ANGLES_SET_TO_MAX) + result = false; + else if (status == ANGLES_SET_EXACTLY) + m_flags |= GUN_POINTED_SUCCESSFULLY; + } + + // Game uses index 2 directly, which happens to be identical to BONE_spine +#ifdef FIX_BUGS + RwMatrix *m = GetBoneMatrix(m_ped, BONE_spine); +#else + RpHAnimHierarchy* hier = GetAnimHierarchyFromSkinClump(m_ped->GetClump()); + RwMatrix *mats = RpHAnimHierarchyGetMatrixArray(hier); + RwMatrix *m = &mats[2]; +#endif + RwV3d axis = { 0.0f, 0.0f, 0.0f }; + float axisangle = -CGeneral::LimitRadianAngle(Atan2(-m->at.y, -m->at.x) - m_ped->m_fRotationCur); + axis.y = -Sin(axisangle); + axis.z = Cos(axisangle); + + // this was RotateTorso + RtQuat *q = &m_ped->m_pFrames[PED_MID]->hanimFrame->q; + RtQuatRotate(q, &axis, RADTODEG(m_torsoOrient.pitch), rwCOMBINEPOSTCONCAT); + RtQuatRotate(q, &XaxisIK, RADTODEG(m_torsoOrient.yaw), rwCOMBINEPOSTCONCAT); + m_ped->bDontAcceptIKLookAts = true; + + return result; +} + +bool +CPedIK::PointGunInDirectionUsingArm(float targetYaw, float targetPitch) +{ + bool result = false; + RwMatrix *matrix; + float yaw, pitch; + + float uaRoll = 45.0f; + float handRoll = 30.0f; + + matrix = GetComponentMatrix(m_ped, PED_CLAVICLER); + yaw = CGeneral::LimitRadianAngle(Atan2(matrix->right.y, matrix->right.x) - m_ped->m_fRotationCur); + pitch = Atan2(matrix->up.z, Sqrt(1.0f - SQR(matrix->up.z))); + + float uaYaw, uaPitch; + uaYaw = CGeneral::LimitRadianAngle(targetYaw - yaw - DEGTORAD(15.0f)); + uaPitch = CGeneral::LimitRadianAngle(targetPitch - pitch + DEGTORAD(10.0f)); + LimbMoveStatus uaStatus = MoveLimb(m_upperArmOrient, uaYaw, uaPitch, ms_upperArmInfo); + if (uaStatus == ANGLES_SET_EXACTLY) { + m_flags |= GUN_POINTED_SUCCESSFULLY; + result = true; + } + + if (uaStatus == ANGLES_SET_TO_MAX) { + float laYaw = uaYaw - m_upperArmOrient.yaw; + + LimbMoveStatus laStatus; + if (laYaw > 0.0f){ + float rollReduce = laYaw/DEGTORAD(30.0f); + uaRoll *= 1.0f - Min(rollReduce, 1.0f); + handRoll *= 1.0f - Min(rollReduce, 1.0f); + + laYaw *= 1.9f; + laStatus = MoveLimb(m_lowerArmOrient, laYaw, 0.0f, ms_lowerArmInfo); + + // some unused statics here + float uaPitchAmount = 1.0f - (m_lowerArmOrient.yaw + m_upperArmOrient.yaw) * 0.34f; + float f1 = ms_upperArmInfo.maxPitch * Max(uaPitchAmount, 0.0f); + float f2 = 0.2f*m_lowerArmOrient.yaw + m_upperArmOrient.pitch; + m_upperArmOrient.pitch = Min(f1, f2); + }else + laStatus = MoveLimb(m_lowerArmOrient, laYaw, 0.0f, ms_lowerArmInfo); + + if (laStatus == ANGLES_SET_EXACTLY) { + m_flags |= GUN_POINTED_SUCCESSFULLY; + result = true; + } + + // game does this stupidly by going through the clump extension... + RtQuat *q = &m_ped->m_pFrames[PED_FOREARMR]->hanimFrame->q; + RtQuatRotate(q, &ZaxisIK, -RADTODEG(m_lowerArmOrient.yaw), rwCOMBINEREPLACE); + RtQuatRotate(q, &XaxisIK, -RADTODEG(m_lowerArmOrient.pitch), rwCOMBINEPOSTCONCAT); + m_ped->bDontAcceptIKLookAts = true; + } + + RtQuat *q = &m_ped->m_pFrames[PED_UPPERARMR]->hanimFrame->q; + RtQuatRotate(q, &XaxisIK, uaRoll, rwCOMBINEREPLACE); + RtQuatRotate(q, &YaxisIK, -RADTODEG(m_upperArmOrient.pitch), rwCOMBINEPOSTCONCAT); + RtQuatRotate(q, &ZaxisIK, -RADTODEG(m_upperArmOrient.yaw+HALFPI), rwCOMBINEPOSTCONCAT); + m_ped->bDontAcceptIKLookAts = true; + + q = &m_ped->m_pFrames[PED_HANDR]->hanimFrame->q; + RtQuatRotate(q, &XaxisIK, handRoll, rwCOMBINEPRECONCAT); + + return result; +} + +bool +CPedIK::PointGunAtPosition(CVector const& position) +{ + CVector startPoint; + if (m_ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_SPAS12_SHOTGUN || m_ped->GetWeapon()->m_eWeaponType == WEAPONTYPE_STUBBY_SHOTGUN) + startPoint = m_ped->GetPosition(); + else { + RwV3d armPos; + GetComponentPosition(armPos, PED_UPPERARMR); + startPoint.x = m_ped->GetPosition().x; + startPoint.y = m_ped->GetPosition().y; + startPoint.z = armPos.z; + } + + return PointGunInDirection( + CGeneral::GetRadianAngleBetweenPoints(position.x, position.y, startPoint.x, startPoint.y), + CGeneral::GetRadianAngleBetweenPoints(position.z, Distance2D(m_ped->GetPosition(), position.x, position.y), startPoint.z, 0.0f)); +} + +bool +CPedIK::RestoreLookAt(void) +{ + bool result = false; + float yaw, pitch; + + 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; + } + + // This was RotateHead + 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), rwCOMBINEPRECONCAT); + m_ped->bDontAcceptIKLookAts = true; + + 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; +} + +void +CPedIK::ExtractYawAndPitchLocalSkinned(AnimBlendFrameData *node, float *yaw, float *pitch) +{ + RwMatrix *mat = RwMatrixCreate(); + RtQuatConvertToMatrix(&node->hanimFrame->q, mat); + ExtractYawAndPitchLocal(mat, yaw, pitch); + RwMatrixDestroy(mat); +} diff --git a/src/miami/peds/PedIK.h b/src/miami/peds/PedIK.h new file mode 100644 index 00000000..3011dd5f --- /dev/null +++ b/src/miami/peds/PedIK.h @@ -0,0 +1,66 @@ +#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); + 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); + bool LookInDirection(float targetYaw, float targetPitch); + bool LookAtPosition(CVector const& pos); + bool RestoreLookAt(void); +}; + +VALIDATE_SIZE(CPedIK, 0x28); diff --git a/src/miami/peds/PedPlacement.cpp b/src/miami/peds/PedPlacement.cpp new file mode 100644 index 00000000..840d33fc --- /dev/null +++ b/src/miami/peds/PedPlacement.cpp @@ -0,0 +1,58 @@ +#include "common.h" + +#include "Ped.h" +#include "PedPlacement.h" +#include "World.h" + +bool +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; + return true; + } + return false; +} + +CEntity* +CPedPlacement::IsPositionClearOfCars(Const CVector *pos) +{ + return CWorld::TestSphereAgainstWorld(*pos, 0.25f, nil, true, true, false, false, false, false); +} + +bool +CPedPlacement::IsPositionClearForPed(const CVector& pos, float radius, int total, CEntity** entities) +{ + int16 count; + if (radius == -1.0f) + radius = 0.75f; + if (total == -1) + total = 2; + CWorld::FindObjectsKindaColliding(pos, radius, true, &count, total, entities, false, true, true, false, false); + return count == 0; +} diff --git a/src/miami/peds/PedPlacement.h b/src/miami/peds/PedPlacement.h new file mode 100644 index 00000000..d1b0cd16 --- /dev/null +++ b/src/miami/peds/PedPlacement.h @@ -0,0 +1,8 @@ +#pragma once + +class CPedPlacement { +public: + static bool FindZCoorForPed(CVector* pos); + static CEntity* IsPositionClearOfCars(Const CVector*); + static bool IsPositionClearForPed(const CVector& pos, float radius = -1.0f, int total = -1, CEntity** entities = nil); +}; \ No newline at end of file diff --git a/src/miami/peds/PedRoutes.cpp b/src/miami/peds/PedRoutes.cpp new file mode 100644 index 00000000..3ff080e6 --- /dev/null +++ b/src/miami/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/miami/peds/PedRoutes.h b/src/miami/peds/PedRoutes.h new file mode 100644 index 00000000..c478e38d --- /dev/null +++ b/src/miami/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/miami/peds/PedType.cpp b/src/miami/peds/PedType.cpp new file mode 100644 index 00000000..dcd4c717 --- /dev/null +++ b/src/miami/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/miami/peds/PedType.h b/src/miami/peds/PedType.h new file mode 100644 index 00000000..a4698bbb --- /dev/null +++ b/src/miami/peds/PedType.h @@ -0,0 +1,178 @@ +#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, // Security - hardcoded + PEDTYPE_GANG6, + PEDTYPE_GANG7, // Vercetti gang - hardcoded + 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, + PEDSTAT_BEACH_GUY, + PEDSTAT_BEACH_GIRL, + PEDSTAT_SKATER, + PEDSTAT_STD_MISSION, + PEDSTAT_COWARD, + + 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); diff --git a/src/miami/peds/PlayerPed.cpp b/src/miami/peds/PlayerPed.cpp new file mode 100644 index 00000000..37618eda --- /dev/null +++ b/src/miami/peds/PlayerPed.cpp @@ -0,0 +1,2267 @@ +#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 "MBlur.h" +#include "Streaming.h" +#include "Population.h" +#include "Script.h" +#include "Replay.h" +#include "PedPlacement.h" +#include "VarConsole.h" +#include "SaveBuf.h" + +#define PAD_MOVE_TO_GAME_WORLD_MOVE 60.0f + +bool CPlayerPed::bDontAllowWeaponChange; +#ifndef MASTER +bool CPlayerPed::bDebugPlayerInfo; +#endif + +const uint32 CPlayerPed::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1752; +#else + sizeof(CPlayerPed); +#endif + +int32 idleAnimBlockIndex; + +CPad* +GetPadFromPlayer(CPlayerPed*) +{ + return CPad::GetPad(0); +} + +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 = WEAPONSLOT_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; + m_pEvadingFrom = nil; + m_nHitAnimDelayTimer = 0; + m_fAttackButtonCounter = 0.0f; + m_bHaveTargetSelected = false; + m_bHasLockOnTarget = false; + m_bCanBeDamaged = true; + m_bNoPosForMeleeAttack = false; + m_fWalkAngle = 0.0f; + m_fFPSMoveHeading = 0.0f; + m_pMinigunTopAtomic = nil; + m_fGunSpinSpeed = 0.0; + m_fGunSpinAngle = 0.0; + m_nPadDownPressedInMilliseconds = 0; + m_nTargettableObjects[0] = m_nTargettableObjects[1] = m_nTargettableObjects[2] = m_nTargettableObjects[3] = -1; + unk1 = false; + for (int i = 0; i < 6; i++) { + m_vecSafePos[i] = CVector(0.0f, 0.0f, 0.0f); + m_pPedAtSafePos[i] = nil; + m_pMeleeList[i] = nil; + } + m_nAttackDirToCheck = 0; + m_nLastBusFareCollected = 0; + idleAnimBlockIndex = CAnimManager::GetAnimationBlockIndex("playidles"); +#ifdef FREE_CAM + m_bFreeAimActive = false; +#endif +} + +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 (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; + +#ifndef MASTER + VarConsole.Add("Debug PlayerPed", &CPlayerPed::bDebugPlayerInfo, true); + VarConsole.Add("Tweak Vehicle Handling", &CVehicle::m_bDisplayHandlingInfo, true); +#endif +} + +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(eWeaponType weapon) +{ + if (m_nPedState == PED_SNIPER_MODE) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + } + SetCurrentWeapon(weapon); + m_nSelectedWepSlot = m_currentWeapon; + + GetWeapon()->m_nAmmoInClip = Min(GetWeapon()->m_nAmmoTotal, CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_nAmountofAmmunition); + + if (CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM)) + ClearWeaponTarget(); + + // WEAPONTYPE_SNIPERRIFLE? Wut? + CAnimBlendAssociation* weaponAnim = RpAnimBlendClumpGetAssociation(GetClump(), GetPrimaryFireAnim(CWeaponInfo::GetWeaponInfo(WEAPONTYPE_SNIPERRIFLE))); + if (weaponAnim) { + weaponAnim->SetRun(); + weaponAnim->flags |= ASSOC_FADEOUTWHENDONE; + } + TheCamera.ClearPlayerWeaponMode(); +} + +void +CPlayerPed::MakeChangesForNewWeapon(int32 slot) +{ + if(slot != -1) + MakeChangesForNewWeapon(m_weapons[slot].m_eWeaponType); +} + +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_nDrunkenness = 0; + m_nFadeDrunkenness = 0; + CMBlur::ClearDrunkBlur(); + m_nDrunkCountdown = 0; + m_bAdrenalineActive = false; + m_nAdrenalineTime = 0; + CTimer::SetTimeScale(1.0f); + m_pSeekTarget = nil; + m_vecSeekPos = CVector(0.0f, 0.0f, 0.0f); + m_fleeFromPos = CVector2D(0.0f, 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 = WEAPONSLOT_UNARMED; + m_nEvadeAmount = 0; + m_pEvadingFrom = nil; + bIsPedDieAnimPlaying = false; + SetRealMoveAnim(); + m_bCanBeDamaged = true; + m_pedStats->m_temper = 50; + m_fWalkAngle = 0.0f; + if (m_attachedTo && !bUsesCollision) + bUsesCollision = true; + + m_attachedTo = nil; + m_attachWepAmmo = 0; +} + +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 (!curIdleAssoc) + curIdleAssoc = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_MELEE_IDLE_FIGHTMODE); + + 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 && !bIsAimingGun && !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 && !bIsAimingGun && !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 || bIsAimingGun) && 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 && !bIsAimingGun && 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); + CAnimBlendAssociation *fightIdleAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_STD_FIGHT_IDLE); + if (!fightIdleAnim) + fightIdleAnim = RpAnimBlendClumpGetAssociation(GetClump(), ANIM_MELEE_IDLE_FIGHTMODE); + delete fightIdleAnim; + 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->GetProgress() < 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) { // this condition is absent on mobile + // 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; + } + curWalkAssoc->blendDelta = 0.0f; + curRunAssoc->blendDelta = 0.0f; + } + } + } + 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; + } + } else if (curSprintAssoc) { + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_FIXED) { + curSprintAssoc->speed = 0.7f; + } else + curSprintAssoc->speed = 1.0f; + } +} + +void +CPlayerPed::RestoreSprintEnergy(float restoreSpeed) +{ + if (m_fCurrentStamina < m_fMaxStamina) + m_fCurrentStamina += restoreSpeed * CTimer::GetTimeStep() * 0.5f; +} + +float +CPlayerPed::DoWeaponSmoothSpray(void) +{ + if (m_nPedState == PED_ATTACK && !m_pPointGunAt) { + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + switch (GetWeapon()->m_eWeaponType) { + case WEAPONTYPE_GOLFCLUB: + case WEAPONTYPE_NIGHTSTICK: + case WEAPONTYPE_BASEBALLBAT: + if (GetFireAnimGround(weaponInfo, false) && RpAnimBlendClumpGetAssociation(GetClump(), GetFireAnimGround(weaponInfo, false))) + return PI / 176.f; + else + return -1.0f; + + case WEAPONTYPE_CHAINSAW: + if (GetMeleeStartAnim(weaponInfo) && RpAnimBlendClumpGetAssociation(GetClump(), GetMeleeStartAnim(weaponInfo))) { +#ifdef FREE_CAM + if (TheCamera.Cams[0].Using3rdPersonMouseCam()) return -1.0f; +#endif + return PI / 128.0f; + } + else if (GetFireAnimGround(weaponInfo, false) && RpAnimBlendClumpGetAssociation(GetClump(), GetFireAnimGround(weaponInfo, false))) + return PI / 176.f; + else + return PI / 80.f; + + case WEAPONTYPE_PYTHON: + return PI / 112.f; + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_SPAS12_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + return PI / 112.f; + case WEAPONTYPE_UZI: + case WEAPONTYPE_MP5: + return PI / 112.f; + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + return PI / 112.f; + case WEAPONTYPE_FLAMETHROWER: + return PI / 80.f; + case WEAPONTYPE_M60: + case WEAPONTYPE_MINIGUN: + case WEAPONTYPE_HELICANNON: + return PI / 176.f; + default: + return -1.0f; + } + } else if (bIsDucking) + return PI / 112.f; + else + return -1.0f; +} + +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; + + 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::IsThisPedAnAimingPriority(CPed *suspect) +{ + if (!suspect->bIsPlayerFriend) + return true; + + 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 suspect->m_nPedState == PED_ABSEIL; +} + +void +CPlayerPed::PlayerControlSniper(CPad *padUsed) +{ + ProcessWeaponSwitch(padUsed); + TheCamera.PlayerExhaustion = (1.0f - (m_fCurrentStamina - -150.0f) / 300.0f) * 0.9f + 0.1f; + + if (padUsed->DuckJustDown() && !bIsDucking && m_nMoveState != PEDMOVE_SPRINT) { + bCrouchWhenShooting = true; + SetDuck(60000, true); + } else if (bIsDucking && (padUsed->DuckJustDown() || m_nMoveState == PEDMOVE_SPRINT)) { + ClearDuck(true); + bCrouchWhenShooting = false; + } + + if (!padUsed->GetTarget() && !m_attachedTo) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + return; + } + + int firingRate = GetWeapon()->m_eWeaponType == WEAPONTYPE_LASERSCOPE ? 333 : 266; + if (padUsed->WeaponJustDown() && CTimer::GetTimeInMilliseconds() > GetWeapon()->m_nTimer) { + CVector firePos(0.0f, 0.0f, 0.6f); + firePos = GetMatrix() * firePos; + GetWeapon()->Fire(this, &firePos); + m_nPadDownPressedInMilliseconds = CTimer::GetTimeInMilliseconds(); + } else if (CTimer::GetTimeInMilliseconds() > m_nPadDownPressedInMilliseconds + firingRate && + CTimer::GetTimeInMilliseconds() - CTimer::GetTimeStepInMilliseconds() < m_nPadDownPressedInMilliseconds + firingRate && padUsed->GetWeapon()) { + + if (GetWeapon()->m_nAmmoTotal > 0) { + DMAudio.PlayFrontEndSound(SOUND_WEAPON_AK47_BULLET_ECHO, GetWeapon()->m_eWeaponType); + } + } + GetWeapon()->Update(m_audioEntityId, nil); +} + +// I think R* also used goto in here. +void +CPlayerPed::ProcessWeaponSwitch(CPad *padUsed) +{ + if (CDarkel::FrenzyOnGoing() || m_attachedTo) + goto switchDetectDone; + + if (!m_pPointGunAt && !bDontAllowWeaponChange && GetWeapon()->m_eWeaponType != WEAPONTYPE_DETONATOR) { + if (padUsed->CycleWeaponRightJustDown()) { + + 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 + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_CAMERA) { + + for (m_nSelectedWepSlot = m_currentWeapon + 1; m_nSelectedWepSlot < TOTAL_WEAPON_SLOTS; ++m_nSelectedWepSlot) { + if (HasWeaponSlot(m_nSelectedWepSlot) && GetWeapon(m_nSelectedWepSlot).HasWeaponAmmoToBeUsed()) { +#ifdef FIX_BUGS + goto switchDetectDone; +#else + goto spentAmmoCheck; +#endif + } + } + m_nSelectedWepSlot = 0; +#ifdef FIX_BUGS + goto switchDetectDone; +#endif + } + } else if (padUsed->CycleWeaponLeftJustDown()) { + if (TheCamera.PlayerWeaponMode.Mode != CCam::MODE_M16_1STPERSON + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_SNIPER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_ROCKETLAUNCHER + && TheCamera.PlayerWeaponMode.Mode != CCam::MODE_CAMERA) { + + // I don't know what kind of loop that was + m_nSelectedWepSlot = m_currentWeapon - 1; + do { + if (m_nSelectedWepSlot < 0) + m_nSelectedWepSlot = TOTAL_WEAPON_SLOTS - 1; + + if (m_nSelectedWepSlot == WEAPONSLOT_UNARMED) + break; + + if (HasWeaponSlot(m_nSelectedWepSlot) && GetWeapon(m_nSelectedWepSlot).HasWeaponAmmoToBeUsed()) + break; + + --m_nSelectedWepSlot; + } while (m_nSelectedWepSlot != WEAPONSLOT_UNARMED); +#ifdef FIX_BUGS + goto switchDetectDone; +#endif + + } + } + } + +spentAmmoCheck: + if (CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_eWeaponFire != WEAPON_FIRE_MELEE + && (!padUsed->GetWeapon() || GetWeapon()->m_eWeaponType != WEAPONTYPE_MINIGUN)) { + if (GetWeapon()->m_nAmmoTotal <= 0) { + if (TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON + || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_SNIPER + || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_ROCKETLAUNCHER) + return; + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_DETONATOR + && GetWeapon(WEAPONSLOT_PROJECTILE).m_eWeaponType == WEAPONTYPE_DETONATOR_GRENADE) + m_nSelectedWepSlot = WEAPONSLOT_PROJECTILE; + else + m_nSelectedWepSlot = m_currentWeapon - 1; + + for (; m_nSelectedWepSlot >= WEAPONSLOT_UNARMED; --m_nSelectedWepSlot) { + + // BUG: m_nSelectedWepSlot and GetWeapon(..) takes slot in VC but they compared them against weapon types in whole condition! jeez +#ifdef FIX_BUGS + if (m_nSelectedWepSlot == WEAPONSLOT_MELEE || + GetWeapon(m_nSelectedWepSlot).m_nAmmoTotal > 0 && (m_nSelectedWepSlot != WEAPONSLOT_PROJECTILE || GetWeapon(WEAPONSLOT_PROJECTILE).m_eWeaponType == WEAPONTYPE_DETONATOR_GRENADE)) { +#else + if (m_nSelectedWepSlot == WEAPONTYPE_BASEBALLBAT && GetWeapon(WEAPONTYPE_BASEBALLBAT).m_eWeaponType == WEAPONTYPE_BASEBALLBAT + || GetWeapon(m_nSelectedWepSlot).m_nAmmoTotal > 0 + && m_nSelectedWepSlot != WEAPONTYPE_MOLOTOV && m_nSelectedWepSlot != WEAPONTYPE_GRENADE && m_nSelectedWepSlot != WEAPONTYPE_TEARGAS) { +#endif + goto switchDetectDone; + } + } + m_nSelectedWepSlot = WEAPONSLOT_UNARMED; + } + } + +switchDetectDone: + if (m_nSelectedWepSlot != m_currentWeapon) { + if (m_nPedState != PED_ATTACK && m_nPedState != PED_AIM_GUN && m_nPedState != PED_FIGHT) { + RemoveWeaponAnims(m_currentWeapon, -1000.0f); + 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->DuckJustDown() && !bIsDucking && m_nMoveState != PEDMOVE_SPRINT) { + bCrouchWhenShooting = true; + SetDuck(60000, true); + } else if (bIsDucking && (padUsed->DuckJustDown() || m_nMoveState == PEDMOVE_SPRINT)) { + ClearDuck(true); + bCrouchWhenShooting = false; + } + + if (!padUsed->GetTarget() && !m_attachedTo) { + RestorePreviousState(); + TheCamera.ClearPlayerWeaponMode(); + } + + if (padUsed->GetWeapon() && CTimer::GetTimeInMilliseconds() > GetWeapon()->m_nTimer) { + if (GetWeapon()->m_eWeaponState == WEAPONSTATE_OUT_OF_AMMO) { + DMAudio.PlayFrontEndSound(SOUND_WEAPON_SNIPER_SHOT_NO_ZOOM, 0.f); + GetWeapon()->m_nTimer = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_nFiringRate + CTimer::GetTimeInMilliseconds(); + } else { + CVector firePos(0.0f, 0.0f, 0.6f); + firePos = GetMatrix() * firePos; + GetWeapon()->Fire(this, &firePos); + m_nPadDownPressedInMilliseconds = CTimer::GetTimeInMilliseconds(); + } + } else if (CTimer::GetTimeInMilliseconds() > GetWeapon()->m_nTimer && + CTimer::GetTimeInMilliseconds() - CTimer::GetTimeStepInMilliseconds() < GetWeapon()->m_nTimer && GetWeapon()->m_eWeaponState != WEAPONSTATE_OUT_OF_AMMO) { + DMAudio.PlayFrontEndSound(SOUND_WEAPON_AK47_BULLET_ECHO, GetWeapon()->m_eWeaponType); + } + GetWeapon()->Update(m_audioEntityId, nil); +} + +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 (m_nPedState == PED_ANSWER_MOBILE) { + SetRealMoveAnim(); + return; + } + + 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(); + } + } + + // FIX: Fact that PlayIdleAnimations only called through PlayerControlZelda was making it visible to only Classic control players. This isn't fair! +#ifdef FIX_BUGS + if (m_nPedState != PED_FIGHT) + PlayIdleAnimations(padUsed); +#endif +} + +void +CPlayerPed::KeepAreaAroundPlayerClear(void) +{ + BuildPedLists(); + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed->CharCreatedBy == RANDOM_CHAR && nearPed->m_nPedState != PED_DRIVING && !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, bool priority) +{ + // priority param is unused + CVector distVec = candidate->GetPosition() - GetPosition(); + if (distVec.Magnitude2D() <= distLimit) { + if (!DoesTargetHaveToBeBroken(candidate->GetPosition(), GetWeapon())) { + float angleBetweenUs = CGeneral::GetATanOfXY(candidate->GetPosition().x - TheCamera.GetPosition().x, + candidate->GetPosition().y - TheCamera.GetPosition().y); + + 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 += 30.0f; + } + + if (closeness > *lastCloseness) { + *targetPtr = candidate; + *lastCloseness = closeness; + } + } + } +} + +bool +CPlayerPed::CanIKReachThisTarget(CVector target, CWeapon* weapon, bool zRotImportant) +{ + float angleToFace = CGeneral::GetRadianAngleBetweenPoints(target.x, target.y, GetPosition().x, GetPosition().y); + float angleDiff = CGeneral::LimitRadianAngle(angleToFace - m_fRotationCur); + + return (!zRotImportant || CWeaponInfo::GetWeaponInfo(weapon->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM) || Abs(angleDiff) <= HALFPI) && + (CWeaponInfo::GetWeaponInfo(weapon->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM) || Abs(target.z - GetPosition().z) <= (target - GetPosition()).Magnitude2D()); +} + +void +CPlayerPed::RotatePlayerToTrackTarget(void) +{ + if (CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM)) + return; + + float angleToFace = CGeneral::GetRadianAngleBetweenPoints( + m_pPointGunAt->GetPosition().x, m_pPointGunAt->GetPosition().y, + GetPosition().x, GetPosition().y); + + float angleDiff = CGeneral::LimitRadianAngle(m_fRotationCur - angleToFace); + if (angleDiff < -DEGTORAD(25.0f)) { + m_fRotationCur -= angleDiff + DEGTORAD(25.0f); + m_fRotationDest -= angleDiff + DEGTORAD(25.0f); + + } else if (angleDiff > DEGTORAD(25.0f)) { + m_fRotationCur -= angleDiff - DEGTORAD(25.0f); + m_fRotationDest -= angleDiff - DEGTORAD(25.0f); + } +} + +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 + CVector distVec = previousTarget->GetPosition() - TheCamera.GetPosition(); + 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 != this && pedToCheck != previousTarget) { + if (!pedToCheck->DyingOrDead() +#ifndef AIMING_VEHICLE_OCCUPANTS // Mobile thing + && (!pedToCheck->bInVehicle || (pedToCheck->m_pMyVehicle && pedToCheck->m_pMyVehicle->IsBike())) +#endif + && pedToCheck->m_leader != this && !pedToCheck->bNeverEverTargetThisPed + && OurPedCanSeeThisOne(pedToCheck, true) && CanIKReachThisTarget(pedToCheck->GetPosition(), GetWeapon(), true)) { + + EvaluateNeighbouringTarget(pedToCheck, &nextTarget, &lastCloseness, + weaponRange, referenceBeta, lookToLeft, IsThisPedAnAimingPriority(pedToCheck)); + } + } + } + } + for (int i = 0; i < ARRAY_SIZE(m_nTargettableObjects); i++) { + CObject *obj = CPools::GetObjectPool()->GetAt(m_nTargettableObjects[i]); + if (obj && !obj->bHasBeenDamaged && CanIKReachThisTarget(obj->GetPosition(), GetWeapon(), true)) + EvaluateNeighbouringTarget(obj, &nextTarget, &lastCloseness, weaponRange, referenceBeta, lookToLeft, true); + } + if (!nextTarget) + return false; + + SetWeaponLockOnTarget(nextTarget); + bDontAllowWeaponChange = true; + 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 != this) { + if (!pedToCheck->DyingOrDead() +#ifndef AIMING_VEHICLE_OCCUPANTS // Mobile thing + && (!pedToCheck->bInVehicle || (pedToCheck->m_pMyVehicle && pedToCheck->m_pMyVehicle->IsBike())) +#endif + && pedToCheck->m_leader != this && !pedToCheck->bNeverEverTargetThisPed + && OurPedCanSeeThisOne(pedToCheck) && CanIKReachThisTarget(pedToCheck->GetPosition(), GetWeapon(), true)) { + + EvaluateTarget(pedToCheck, &nextTarget, &lastCloseness, + weaponRange, referenceBeta, IsThisPedAnAimingPriority(pedToCheck)); + } + } + } + } + for (int i = 0; i < ARRAY_SIZE(m_nTargettableObjects); i++) { + CObject *obj = CPools::GetObjectPool()->GetAt(m_nTargettableObjects[i]); + if (obj && !obj->bHasBeenDamaged && CanIKReachThisTarget(obj->GetPosition(), GetWeapon(), true)) + EvaluateTarget(obj, &nextTarget, &lastCloseness, weaponRange, referenceBeta, true); + } + if (!nextTarget) + return false; + + SetWeaponLockOnTarget(nextTarget); + bDontAllowWeaponChange = true; + SetPointGunAt(nextTarget); + Say(SOUND_PED_AIMING); + 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 if (GetWeapon()->m_eWeaponType == WEAPONTYPE_CHAINSAW || + GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER || + GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN) + groupToSet = ASSOCGRP_CHAINSAWLEFT; + else + groupToSet = ASSOCGRP_PLAYERLEFT; + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + groupToSet = ASSOCGRP_ROCKETRIGHT; + else if (GetWeapon()->m_eWeaponType == WEAPONTYPE_CHAINSAW || + GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER || + GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN) + groupToSet = ASSOCGRP_CHAINSAWRIGHT; + else + groupToSet = ASSOCGRP_PLAYERRIGHT; + } + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) + groupToSet = ASSOCGRP_ROCKETBACK; + else if (GetWeapon()->m_eWeaponType == WEAPONTYPE_CHAINSAW || + GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER || + GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN) + groupToSet = ASSOCGRP_CHAINSAWBACK; + else + groupToSet = ASSOCGRP_PLAYERBACK; + } + } else +#endif + { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER) { + groupToSet = ASSOCGRP_PLAYERROCKET; + } else { + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_BASEBALLBAT + || GetWeapon()->m_eWeaponType == WEAPONTYPE_MACHETE) + groupToSet = ASSOCGRP_PLAYERBBBAT; + else if (GetWeapon()->m_eWeaponType == WEAPONTYPE_CHAINSAW || + GetWeapon()->m_eWeaponType == WEAPONTYPE_FLAMETHROWER || + GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN) + groupToSet = ASSOCGRP_PLAYERCHAINSAW; + else if (GetWeapon()->m_eWeaponType != WEAPONTYPE_COLT45 && GetWeapon()->m_eWeaponType != WEAPONTYPE_UZI + // I hope this is an inlined function... + && GetWeapon()->m_eWeaponType != WEAPONTYPE_PYTHON && GetWeapon()->m_eWeaponType != WEAPONTYPE_TEC9 + && GetWeapon()->m_eWeaponType != WEAPONTYPE_SILENCED_INGRAM && GetWeapon()->m_eWeaponType != WEAPONTYPE_MP5 + && GetWeapon()->m_eWeaponType != WEAPONTYPE_GOLFCLUB && GetWeapon()->m_eWeaponType != WEAPONTYPE_KATANA + && GetWeapon()->m_eWeaponType != WEAPONTYPE_CAMERA) { + 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 (padUsed->DuckJustDown() && !bIsDucking && m_nMoveState != PEDMOVE_SPRINT) { +#ifdef FIX_BUGS + // fix tommy being locked into looking at the same spot if you duck just after starting to shoot + if(!m_pPointGunAt) + ClearPointGunAt(); +#endif + bCrouchWhenShooting = true; + SetDuck(60000, true); + } else if (bIsDucking && (padUsed->DuckJustDown() || m_nMoveState == PEDMOVE_SPRINT || + padUsed->GetSprint() || padUsed->JumpJustDown() || padUsed->ExitVehicleJustDown())) { + +#ifdef FIX_BUGS + // same fix as above except for standing up + if(!m_pPointGunAt) + ClearPointGunAt(); +#endif + ClearDuck(true); + bCrouchWhenShooting = false; + } + + if(weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM)) + m_wepAccuracy = 95; + else + m_wepAccuracy = 100; + + if (!m_pFire) { + eWeaponType weapon = GetWeapon()->m_eWeaponType; + if (weapon == WEAPONTYPE_ROCKETLAUNCHER || weapon == WEAPONTYPE_SNIPERRIFLE || + weapon == WEAPONTYPE_LASERSCOPE || weapon == WEAPONTYPE_M4 || + weapon == WEAPONTYPE_RUGER || weapon == WEAPONTYPE_M60 || + weapon == WEAPONTYPE_CAMERA) { + + if (padUsed->TargetJustDown() || TheCamera.m_bJustJumpedOutOf1stPersonBecauseOfTarget) { +#ifdef FREE_CAM + if (CCamera::bFreeCam && TheCamera.Cams[0].Using3rdPersonMouseCam()) { + m_fRotationCur = CGeneral::LimitRadianAngle(-TheCamera.Orientation); + SetHeading(m_fRotationCur); + } +#endif + if (weapon == WEAPONTYPE_ROCKETLAUNCHER) + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_ROCKETLAUNCHER, 0, 0); + else if (weapon == WEAPONTYPE_SNIPERRIFLE || weapon == WEAPONTYPE_LASERSCOPE) + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_SNIPER, 0, 0); + else if (weapon == WEAPONTYPE_CAMERA) + TheCamera.SetNewPlayerWeaponMode(CCam::MODE_CAMERA, 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); + SetPedState(PED_SNIPER_MODE); + return; + } + if (!TheCamera.Using1stPersonWeaponMode()) + if (weapon == WEAPONTYPE_ROCKETLAUNCHER || weapon == WEAPONTYPE_SNIPERRIFLE || weapon == WEAPONTYPE_LASERSCOPE || weapon == WEAPONTYPE_CAMERA) + return; + } + } + + if (padUsed->GetWeapon() && m_nMoveState != PEDMOVE_SPRINT) { + if (m_nSelectedWepSlot == m_currentWeapon) { + if (m_pPointGunAt) { + if (m_nPedState == PED_ATTACK) { + m_fAttackButtonCounter *= Pow(0.94f, CTimer::GetTimeStep()); + } else { + m_fAttackButtonCounter = 0.0f; + } + SetAttack(m_pPointGunAt); + } else { + 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; + } + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED && GetWeapon()->m_eWeaponType != WEAPONTYPE_BRASSKNUCKLE && + !weaponInfo->IsFlagSet(WEAPONFLAG_FIGHTMODE)) { + + if (GetWeapon()->m_eWeaponType != WEAPONTYPE_DETONATOR && GetWeapon()->m_eWeaponType != WEAPONTYPE_DETONATOR_GRENADE || + padUsed->WeaponJustDown()) + SetAttack(nil); + + } else if (padUsed->WeaponJustDown()) { + if (m_fMoveSpeed < 1.0f || m_nPedState == PED_FIGHT) + 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) { + +#define CAN_AIM_WITH_ARM (weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM) && !bIsDucking && !bCrouchWhenShooting) + // Weapons except throwable and melee ones + if (weaponInfo->m_nWeaponSlot > 2) { + if ((padUsed->GetTarget() && CAN_AIM_WITH_ARM) || 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 (CAN_AIM_WITH_ARM) { + pointedGun = 2; + m_bFreeAimActive = true; + SetLookFlag(limitedCam, true, true); + SetAimFlag(limitedCam); + SetLookTimer(INT32_MAX); + ((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; + } + } + } + } +#undef CAN_AIM_WITH_ARM + } + 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 && !TheCamera.Using1stPersonWeaponMode() && weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM)) { + if (m_pPointGunAt) { + // what?? + if (!m_pPointGunAt +#ifdef FREE_CAM + || (!CCamera::bFreeCam && CCamera::m_bUseMouse3rdPerson) +#else + || CCamera::m_bUseMouse3rdPerson +#endif + ) { + ClearWeaponTarget(); + return; + } + + if (m_pPointGunAt->IsPed() && ( +#ifndef AIMING_VEHICLE_OCCUPANTS + (((CPed*)m_pPointGunAt)->bInVehicle && (!((CPed*)m_pPointGunAt)->m_pMyVehicle || !((CPed*)m_pPointGunAt)->m_pMyVehicle->IsBike())) || +#endif + !CGame::nastyGame && ((CPed*)m_pPointGunAt)->DyingOrDead())) { + ClearWeaponTarget(); + return; + } + if (CPlayerPed::DoesTargetHaveToBeBroken(m_pPointGunAt->GetPosition(), GetWeapon()) || + (!bCanPointGunAtTarget && !weaponInfo->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM))) { // this line isn't on Mobile, idk why + ClearWeaponTarget(); + return; + } + + if (m_pPointGunAt) { + RotatePlayerToTrackTarget(); + } + + 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()); + + } else if (!CCamera::m_bUseMouse3rdPerson) { + if (padUsed->TargetJustDown() || TheCamera.m_bJustJumpedOutOf1stPersonBecauseOfTarget) + FindWeaponLockOnTarget(); + } + } else if (m_pPointGunAt) { + ClearWeaponTarget(); + } + + if (m_pPointGunAt) { + CVector markPos; + if (m_pPointGunAt->IsPed()) { + ((CPed*)m_pPointGunAt)->m_pedIK.GetComponentPosition(markPos, PED_MID); + } else { + markPos = m_pPointGunAt->GetPosition(); + } + 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; +} + +bool +CPlayerPed::MovementDisabledBecauseOfTargeting(void) +{ + return m_pPointGunAt && !CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_CANAIM_WITHARM); +} + +void +CPlayerPed::PlayerControlZelda(CPad *padUsed) +{ + float smoothSprayRate = DoWeaponSmoothSpray(); + float camOrientation = TheCamera.Orientation; + float leftRight = padUsed->GetPedWalkLeftRight(); + float upDown = padUsed->GetPedWalkUpDown(); + float padMoveInGameUnit; + bool smoothSprayWithoutMove = false; + + if (MovementDisabledBecauseOfTargeting()) { + upDown = 0.0f; + leftRight = 0.0f; + } + + if (smoothSprayRate > 0.0f && upDown > 0.0f) { + padMoveInGameUnit = 0.0f; + smoothSprayWithoutMove = true; + } else { + padMoveInGameUnit = CVector2D(leftRight, upDown).Magnitude() / PAD_MOVE_TO_GAME_WORLD_MOVE; + } + +#ifdef FREE_CAM + if (TheCamera.Cams[0].Using3rdPersonMouseCam() && smoothSprayRate > 0.0f) { + 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 (smoothSprayRate > 0.0f) { + m_fRotationDest = m_fRotationCur - leftRight / 128.0f * smoothSprayRate * 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 (m_nPedState == PED_ANSWER_MOBILE) { + SetRealMoveAnim(); + return; + } + + if (!CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->IsFlagSet(WEAPONFLAG_HEAVY) && padUsed->GetSprint()) { + if (!m_pCurrentPhysSurface || (!m_pCurrentPhysSurface->bInfiniteMass || m_pCurrentPhysSurface->m_phy_flagA08)) + 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(); + } + } + PlayIdleAnimations(padUsed); +} + +// Finds nice positions for peds to duck and shoot player. And it's inside PlayerPed, this is treachery! +void +CPlayerPed::FindNewAttackPoints(void) +{ + for (int i=0; im_nPedState == PED_DEAD || safeNeighbour->m_pedInObjective != this) { + m_vecSafePos[i].x = 0.0f; + m_vecSafePos[i].y = 0.0f; + m_vecSafePos[i].z = 0.0f; + m_pPedAtSafePos[i] = nil; + } + } else { + m_vecSafePos[i].x = 0.0f; + m_vecSafePos[i].y = 0.0f; + m_vecSafePos[i].z = 0.0f; + } + } + CEntity *entities[6]; + int16 numEnts; + float rightMult, fwdMult; + CWorld::FindObjectsInRange(GetPosition(), 18.0f, true, &numEnts, 6, entities, true, false, false, true, false); + for (int i = 0; i < numEnts; ++i) { + CEntity *ent = entities[i]; + int16 mi = ent->GetModelIndex(); + if (!ent->IsObject() || ((CObject*)ent)->m_nSpecialCollisionResponseCases == COLLRESPONSE_FENCEPART) + if (!IsTreeModel(mi)) + continue; + + if (mi == MI_TRAFFICLIGHTS) { + rightMult = 2.957f; + fwdMult = 0.147f; + + } else if (mi == MI_SINGLESTREETLIGHTS1) { + rightMult = 0.744f; + fwdMult = 0.0f; + + } else if (mi == MI_SINGLESTREETLIGHTS2) { + rightMult = 0.043f; + fwdMult = 0.0f; + + } else if (mi == MI_SINGLESTREETLIGHTS3) { + rightMult = 1.143f; + fwdMult = 0.145f; + + } else if (mi == MI_DOUBLESTREETLIGHTS) { + rightMult = 0.744f; + fwdMult = 0.0f; + + } else if (mi == MI_LAMPPOST1) { + rightMult = 0.744f; + fwdMult = 0.0f; + + } else if (mi == MI_TRAFFICLIGHT01) { + rightMult = 2.957f; + fwdMult = 0.147f; + + } else if (mi == MI_LITTLEHA_POLICE) { + rightMult = 0.0f; + fwdMult = 0.0f; + + } else if (mi == MI_PARKBENCH) { + rightMult = 0.0f; + fwdMult = 0.0f; + + } else if (IsTreeModel(mi)) { + rightMult = 0.0f; + fwdMult = 0.0f; + } else + continue; + + CVector entAttackPoint(rightMult * ent->GetRight().x + fwdMult * ent->GetForward().x + ent->GetPosition().x, + rightMult * ent->GetRight().y + fwdMult * ent->GetForward().y + ent->GetPosition().y, + ent->GetPosition().z); + CVector attackerPos = GetPosition() - entAttackPoint; // for now it's dist, not attackerPos + CVector dirTowardsUs = attackerPos; + dirTowardsUs.Normalise(); + dirTowardsUs *= 2.0f; + attackerPos = entAttackPoint - dirTowardsUs; // to make cop farther from us + CPedPlacement::FindZCoorForPed(&attackerPos); + if (CPedPlacement::IsPositionClearForPed(attackerPos)) + m_vecSafePos[i] = attackerPos; + } +} + +void +CPlayerPed::ProcessControl(void) +{ + // Mobile has some debug/abandoned cheat thing in here: "gbFrankenTommy" + + if (m_nEvadeAmount != 0) + --m_nEvadeAmount; + + if (m_nEvadeAmount == 0) + m_pEvadingFrom = nil; + + if (m_pWanted->GetWantedLevel() > 0) + FindNewAttackPoints(); + + UpdateMeleeAttackers(); + + 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; + } + + if (m_nFadeDrunkenness) { + if (m_nDrunkenness - 1 > 0) { + --m_nDrunkenness; + } else { + m_nDrunkenness = 0; + CMBlur::ClearDrunkBlur(); + m_nFadeDrunkenness = 0; + } + } + if (m_nDrunkenness != 0) { + CMBlur::SetDrunkBlur(m_nDrunkenness / 255.f); + } + CPed::ProcessControl(); + SetNearbyPedsToInteractWithPlayer(); + if (bWasPostponed) + return; + + CPad *padUsed = GetPadFromPlayer(this); + m_pWanted->Update(); + PruneReferences(); + + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN) { + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType); + CAnimBlendAssociation *fireAnim = RpAnimBlendClumpGetAssociation(GetClump(), GetPrimaryFireAnim(weaponInfo)); + if (fireAnim && fireAnim->currentTime - fireAnim->timeStep < weaponInfo->m_fAnimLoopEnd && m_nPedState == PED_ATTACK) { + if (m_fGunSpinSpeed < 0.45f) { + m_fGunSpinSpeed = Min(0.45f, m_fGunSpinSpeed + CTimer::GetTimeStep() * 0.013f); + } + + if (padUsed->GetWeapon() && GetWeapon()->m_nAmmoTotal > 0 && fireAnim->currentTime >= weaponInfo->m_fAnimLoopStart) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_MINIGUN_ATTACK, 0.0f); + } else { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_MINIGUN_2, m_fGunSpinSpeed * (20.f / 9)); + } + } else { + if (m_fGunSpinSpeed > 0.0f) { + if (m_fGunSpinSpeed >= 0.45f) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_MINIGUN_3, 0.0f); + } + m_fGunSpinSpeed = Max(0.0f, m_fGunSpinSpeed - CTimer::GetTimeStep() * 0.003f); + } + } + } + if (GetWeapon()->m_eWeaponType == WEAPONTYPE_CHAINSAW && m_nPedState != PED_ATTACK && !bInVehicle) { + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_CHAINSAW_IDLE, 0.0f); + } + + 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 (!CReplay::IsPlayingBack() || m_pMyVehicle) { + 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() && !TheCamera.Using1stPersonWeaponMode()) { + // ...Really? + eWeaponType playerWeapon = FindPlayerPed()->GetWeapon()->m_eWeaponType; + if (playerWeapon == WEAPONTYPE_SNIPERRIFLE || playerWeapon == WEAPONTYPE_LASERSCOPE) { + 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: + case PED_ANSWER_MOBILE: + if (!RpAnimBlendClumpGetFirstAssociation(GetClump(), ASSOC_BLOCK) && !m_attachedTo) { + if (TheCamera.Using1stPersonWeaponMode()) { + if (padUsed) + PlayerControlSniper(padUsed); + + } else 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() && m_nPedState != PED_ANSWER_MOBILE && 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 (GetWeapon()->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE || GetWeapon()->m_eWeaponType == WEAPONTYPE_LASERSCOPE) { + if (padUsed) + PlayerControlSniper(padUsed); + + } else if (padUsed) { + PlayerControlM16(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() && m_nPedState != PED_ANSWER_MOBILE && m_nLastPedState != PED_ANSWER_MOBILE) { + ProcessWeaponSwitch(padUsed); + GetWeapon()->Update(m_audioEntityId, this); + } + 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 || bIsDucking) + 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; + } + + if (bDontAllowWeaponChange && FindPlayerPed() == this) { + if (!CPad::GetPad(0)->GetTarget()) + bDontAllowWeaponChange = false; + } + + if (m_nPedState != PED_SNIPER_MODE && (GetWeapon()->m_eWeaponState == WEAPONSTATE_FIRING || m_nPedState == PED_ATTACK)) + m_nPadDownPressedInMilliseconds = CTimer::GetTimeInMilliseconds(); + + if (!bIsVisible) + UpdateRpHAnim(); +} + +bool +CPlayerPed::DoesPlayerWantNewWeapon(eWeaponType weapon, bool onlyIfSlotIsEmpty) +{ + // GetPadFromPlayer(); // unused + uint32 slot = CWeaponInfo::GetWeaponInfo(weapon)->m_nWeaponSlot; + + if (!HasWeaponSlot(slot) || GetWeapon(slot).m_eWeaponType == weapon) + return true; + + if (onlyIfSlotIsEmpty) + return false; + + // Check if he's using that slot right now. + return m_nPedState != PED_ATTACK && m_nPedState != PED_AIM_GUN || slot != m_currentWeapon; +} + +void +CPlayerPed::PlayIdleAnimations(CPad *padUsed) +{ + CAnimBlendAssociation* assoc; + + if (TheCamera.m_WideScreenOn || bIsDucking) + return; + + struct animAndGroup { + AnimationId animId; + AssocGroupId groupId; + }; + + const animAndGroup idleAnims[] = { + {ANIM_PLAYER_IDLE1, ASSOCGRP_PLAYER_IDLE}, + {ANIM_PLAYER_IDLE2, ASSOCGRP_PLAYER_IDLE}, + {ANIM_PLAYER_IDLE3, ASSOCGRP_PLAYER_IDLE}, + {ANIM_PLAYER_IDLE4, ASSOCGRP_PLAYER_IDLE}, + {ANIM_STD_XPRESS_SCRATCH, ASSOCGRP_STD}, + }; + + static int32 lastTime = 0; + static int32 lastAnim = -1; + + bool hasIdleAnim = false; + CAnimBlock *idleAnimBlock = CAnimManager::GetAnimationBlock(idleAnimBlockIndex); + uint32 sinceLastInput = padUsed->InputHowLongAgo(); + if (sinceLastInput <= 30000) { + if (idleAnimBlock->isLoaded) { + for (assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + if (assoc->flags & ASSOC_IDLE) { + hasIdleAnim = true; + assoc->blendDelta = -8.0f; + } + } + if (!hasIdleAnim) + CStreaming::RemoveAnim(idleAnimBlockIndex); + } else { + lastTime = 0; + } + } else { + CStreaming::RequestAnim(idleAnimBlockIndex, STREAMFLAGS_DONT_REMOVE); + if (idleAnimBlock->isLoaded) { + for(CAnimBlendAssociation *assoc = RpAnimBlendClumpGetFirstAssociation(GetClump()); assoc; assoc = RpAnimBlendGetNextAssociation(assoc)) { + int firstIdle = idleAnimBlock->firstIndex; + int index = assoc->hierarchy - CAnimManager::GetAnimation(0); + if (index >= firstIdle && index < firstIdle + idleAnimBlock->numAnims) { + hasIdleAnim = true; + break; + } + } + + if (!hasIdleAnim && !bIsLooking && !bIsRestoringLook && sinceLastInput - lastTime > 25000) { + int anim; + do + anim = CGeneral::GetRandomNumberInRange(0, (int32)ARRAY_SIZE(idleAnims)); + while (lastAnim == anim); + + assoc = CAnimManager::BlendAnimation(GetClump(), idleAnims[anim].groupId, idleAnims[anim].animId, 8.0f); + assoc->flags |= ASSOC_IDLE; + lastAnim = anim; + lastTime = sinceLastInput; + } + } + } +} + +void +CPlayerPed::SetNearbyPedsToInteractWithPlayer(void) +{ + if (CGame::noProstitutes) + return; + + for (int i = 0; i < m_numNearPeds; ++i) { + CPed *nearPed = m_nearPeds[i]; + if (nearPed && nearPed->m_objectiveTimer < CTimer::GetTimeInMilliseconds() && !CTheScripts::IsPlayerOnAMission()) { + int mi = nearPed->GetModelIndex(); + if (CPopulation::CanSolicitPlayerOnFoot(mi)) { + CVector distToMe = nearPed->GetPosition() - GetPosition(); + CVector dirToMe = GetPosition() - nearPed->GetPosition(); + dirToMe.Normalise(); + if (DotProduct(dirToMe, nearPed->GetForward()) > 0.707 && DotProduct(GetForward(), nearPed->GetForward()) < -0.707 // those are double + && distToMe.MagnitudeSqr() < 9.0f && nearPed->m_objective == OBJECTIVE_NONE) { + nearPed->SetObjective(OBJECTIVE_SOLICIT_FOOT, this); + nearPed->m_objectiveTimer = CTimer::GetTimeInMilliseconds() + 10000; + nearPed->Say(SOUND_PED_SOLICIT); + } + } else if (CPopulation::CanSolicitPlayerInCar(mi)) { + if (InVehicle() && m_pMyVehicle->IsVehicleNormal()) { + if (m_pMyVehicle->IsCar()) { + CVector distToVeh = nearPed->GetPosition() - m_pMyVehicle->GetPosition(); + if (distToVeh.MagnitudeSqr() < 25.0f && m_pMyVehicle->IsRoomForPedToLeaveCar(CAR_DOOR_LF, nil) && nearPed->m_objective == OBJECTIVE_NONE) { + nearPed->SetObjective(OBJECTIVE_SOLICIT_VEHICLE, m_pMyVehicle); + } + } + } + } + } + } +} + +void +CPlayerPed::UpdateMeleeAttackers(void) +{ + CVector attackCoord; + if (((CTimer::GetFrameCounter() + m_randomSeed + 7) & 3) == 0) { + GetMeleeAttackCoords(attackCoord, m_nAttackDirToCheck, 2.0f); + + // Check if there is any vehicle/building inbetween us and m_nAttackDirToCheck. Peds will be able to attack us from those available directions. + if (CWorld::GetIsLineOfSightClear(GetPosition(), attackCoord, true, true, false, true, false, false, false) + && !CWorld::TestSphereAgainstWorld(attackCoord, 0.4f, m_pMeleeList[m_nAttackDirToCheck], true, true, false, true, false, false)) { + if (m_pMeleeList[m_nAttackDirToCheck] == this) + m_pMeleeList[m_nAttackDirToCheck] = nil; // mark it as available + } else { + m_pMeleeList[m_nAttackDirToCheck] = this; // slot not available. useful for m_bNoPosForMeleeAttack + } + if (++m_nAttackDirToCheck >= ARRAY_SIZE(m_pMeleeList)) + m_nAttackDirToCheck = 0; + } + // 6 directions + for (int i = 0; i < ARRAY_SIZE(m_pMeleeList); ++i) { + CPed *victim = m_pMeleeList[i]; + if (victim && victim != this) { + if (victim->m_nPedState != PED_DEAD && victim->m_pedInObjective == this) { + if (victim->m_objective == OBJECTIVE_KILL_CHAR_ON_FOOT || victim->m_objective == OBJECTIVE_KILL_CHAR_ANY_MEANS || victim->m_objective == OBJECTIVE_KILL_CHAR_ON_BOAT) { + GetMeleeAttackCoords(attackCoord, i, 2.0f); + if ((attackCoord - GetPosition()).MagnitudeSqr() > 12.25f) + m_pMeleeList[i] = nil; + } else { + m_pMeleeList[i] = nil; + } + } else { + m_pMeleeList[i] = nil; + } + } + } + m_bNoPosForMeleeAttack = m_pMeleeList[0] == this && m_pMeleeList[1] == this && m_pMeleeList[2] == this +#ifdef FIX_BUGS + && m_pMeleeList[3] == this +#endif + && m_pMeleeList[4] == this && m_pMeleeList[5] == this; +} + +void +CPlayerPed::RemovePedFromMeleeList(CPed *ped) +{ + for (uint16 i = 0; i < ARRAY_SIZE(m_pMeleeList); i++) { + if (m_pMeleeList[i] == ped) { + m_pMeleeList[i] = nil; + ped->m_attackTimer = 0; + return; + } + } +} + +void +CPlayerPed::GetMeleeAttackCoords(CVector& coords, int8 dir, float dist) +{ + coords = GetPosition(); + switch (dir) { + case 0: + coords.y += dist; + break; + case 1: + coords.x += Sqrt(3.f / 4.f) * dist; + coords.y += 0.5f * dist; + break; + case 2: + coords.x += Sqrt(3.f / 4.f) * dist; + coords.y -= 0.5f * dist; + break; + case 3: + coords.y -= dist; + break; + case 4: + coords.x -= Sqrt(3.f / 4.f) * dist; + coords.y -= 0.5f * dist; + break; + case 5: + coords.x -= Sqrt(3.f / 4.f) * dist; + coords.y += 0.5f * dist; + break; + default: + break; + } +} + +int32 +CPlayerPed::FindMeleeAttackPoint(CPed *victim, CVector &dist, uint32 &endOfAttackOut) +{ + endOfAttackOut = 0; + bool thereIsAnEmptySlot = false; + int dirToAttack = -1; + for (int i = 0; i < ARRAY_SIZE(m_pMeleeList); i++) { + CPed* pedAtThisDir = m_pMeleeList[i]; + if (pedAtThisDir) { + if (pedAtThisDir == victim) { + dirToAttack = i; + } else { + if (pedAtThisDir->m_attackTimer > endOfAttackOut) + endOfAttackOut = pedAtThisDir->m_attackTimer; + } + } else { + thereIsAnEmptySlot = true; + } + } + + // We don't have victim ped in our melee list + if (dirToAttack == -1 && thereIsAnEmptySlot) { + float angle = Atan2(-dist.x, -dist.y); + float adjustedAngle = angle + DEGTORAD(30.0f); + if (adjustedAngle < 0.f) + adjustedAngle += TWOPI; + + int wantedDir = Floor(adjustedAngle / DEGTORAD(60.0f)); + + // And we have another ped at the direction of victim ped, so store victim to next empty direction to it's real direction. (Bollocks) + if (m_pMeleeList[wantedDir]) { + int closestDirToPreferred = -99; + int preferredDir = wantedDir; + + for (int i = 0; i < ARRAY_SIZE(m_pMeleeList); i++) { + if (!m_pMeleeList[i]) { + if (Abs(i - preferredDir) < Abs(closestDirToPreferred - preferredDir)) + closestDirToPreferred = i; + } + } + if (closestDirToPreferred > 0) + dirToAttack = closestDirToPreferred; + } else { + + // Luckily the direction of victim ped is already empty, good + dirToAttack = wantedDir; + } + + if (dirToAttack != -1) { + m_pMeleeList[dirToAttack] = victim; + victim->RegisterReference((CEntity**) &m_pMeleeList[dirToAttack]); + if (endOfAttackOut > CTimer::GetTimeInMilliseconds()) + victim->m_attackTimer = endOfAttackOut + CGeneral::GetRandomNumberInRange(1000, 2000); + else + victim->m_attackTimer = CTimer::GetTimeInMilliseconds() + CGeneral::GetRandomNumberInRange(500, 1000); + } + } + return dirToAttack; +} + +#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, 164); +} + +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, 164); +} +#undef CopyFromBuf +#undef CopyToBuf +#endif diff --git a/src/miami/peds/PlayerPed.h b/src/miami/peds/PlayerPed.h new file mode 100644 index 00000000..30b67199 --- /dev/null +++ b/src/miami/peds/PlayerPed.h @@ -0,0 +1,120 @@ +#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; + bool m_bSpeedTimerFlag; + uint8 m_nEvadeAmount; + uint32 m_nSpeedTimer; // m_nStandStillTimer? + uint32 m_nHitAnimDelayTimer; // m_nShotDelay? + float m_fAttackButtonCounter; + bool m_bHaveTargetSelected; // may have better name + CEntity *m_pEvadingFrom; // is this CPhysical? + int32 m_nTargettableObjects[4]; + uint32 m_nAdrenalineTime; + uint8 m_nDrunkenness; // Needed to work out whether we lost target this frame + uint8 m_nFadeDrunkenness; + uint8 m_nDrunkCountdown; //countdown in frames when the drunk effect ends + bool m_bAdrenalineActive; + bool m_bHasLockOnTarget; + bool m_bCanBeDamaged; + bool m_bNoPosForMeleeAttack; + bool unk1; + CVector m_vecSafePos[6]; // safe places from the player, for example behind a tree + CPed *m_pPedAtSafePos[6]; + CPed *m_pMeleeList[6]; // reachable peds at each direction(6) + int16 m_nAttackDirToCheck; + float m_fWalkAngle; //angle between heading and walking direction + float m_fFPSMoveHeading; + RpAtomic* m_pMinigunTopAtomic; //atomic for the spinning part of the minigun model + float m_fGunSpinSpeed; // for minigun + float m_fGunSpinAngle; + unsigned int m_nPadDownPressedInMilliseconds; + unsigned int m_nLastBusFareCollected; +#ifdef FREE_CAM + bool m_bFreeAimActive; + CVector m_cachedCamSource; + CVector m_cachedCamFront; + CVector m_cachedCamUp; +#endif + + static bool bDontAllowWeaponChange; +#ifndef MASTER + static bool bDebugPlayerInfo; +#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(int32); + void MakeChangesForNewWeapon(eWeaponType); + void SetInitialState(void); + void ProcessControl(void); + void ClearAdrenaline(void); + void UseSprintEnergy(void); + class CPlayerInfo *GetPlayerInfoForThisPlayerPed(); + void SetRealMoveAnim(void); + void RestoreSprintEnergy(float); + float DoWeaponSmoothSpray(void); + void DoStuffToGoOnFire(void); + bool DoesTargetHaveToBeBroken(CVector, CWeapon*); + void RunningLand(CPad*); + bool IsThisPedAnAimingPriority(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, 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*); + bool DoesPlayerWantNewWeapon(eWeaponType, bool); + void PlayIdleAnimations(CPad*); + void RemovePedFromMeleeList(CPed*); + void GetMeleeAttackCoords(CVector&, int8, float); + int32 FindMeleeAttackPoint(CPed*, CVector&, uint32&); + bool CanIKReachThisTarget(CVector, CWeapon*, bool); + void RotatePlayerToTrackTarget(void); + bool MovementDisabledBecauseOfTargeting(void); + void FindNewAttackPoints(void); + void SetNearbyPedsToInteractWithPlayer(void); + void UpdateMeleeAttackers(void); + + 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; +}; + +//VALIDATE_SIZE(CPlayerPed, 0x5F0); diff --git a/src/miami/peds/Population.cpp b/src/miami/peds/Population.cpp new file mode 100644 index 00000000..248482a7 --- /dev/null +++ b/src/miami/peds/Population.cpp @@ -0,0 +1,1815 @@ +#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 "SurfaceTable.h" +#include "Weather.h" +#include "Darkel.h" +#include "Streaming.h" +#include "Clock.h" +#include "WaterLevel.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 + +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; +int32 CPopulation::MaxNumberOfPedsInUseInterior = DEFAULT_MAX_NUMBER_OF_PEDS_INTERIOR; +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; + +uint32 CPopulation::ms_nTotalCarPassengerPeds; +uint32 CPopulation::NumMiamiViceCops; + +uint32 gLastSelectedCivilianIndex; +CEntity *gSunbatheObstacles[2]; +CEntity *gCoupleObstacles[3]; + +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; + + ms_nTotalCarPassengerPeds = 0; + ms_nTotalCivPeds = 0; + ms_nTotalGangPeds = 0; + ms_nTotalPeds = 0; + ms_nTotalMissionPeds = 0; + m_CountDownToPedsAtStart = 2; + bZoneChangeHasHappened = false; // III leftover + + m_AllRandomPedsThisType = -1; + PedDensityMultiplier = 1.0f; + + + LoadPedGroups(); + + debug("CPopulation ready\n"); +} + +void +CPopulation::RemovePed(CPed *ent) +{ + CWorld::Remove((CEntity*)ent); + delete ent; +} + +int32 +CPopulation::ChooseCivilianOccupation(int32 group) +{ + if (CWeather::Rain > 0.1f) { + int32 lastModel; + for (int i = 0; i < 8; i++) { + gLastSelectedCivilianIndex = CGeneral::GetRandomNumberInRange(0, NUMMODELSPERPEDGROUP); + lastModel = ms_pPedGroups[group].models[gLastSelectedCivilianIndex]; + + if (!CPopulation::IsSunbather(lastModel)) + break; + } + return lastModel; + + } else { + gLastSelectedCivilianIndex = CGeneral::GetRandomNumberInRange(0, NUMMODELSPERPEDGROUP); + return ms_pPedGroups[group].models[gLastSelectedCivilianIndex]; + } +} + +int32 +CPopulation::ChooseNextCivilianOccupation(int32 group) +{ + if (CWeather::Rain > 0.1f) { + int32 lastModel; + for (int i = 0; i < NUMMODELSPERPEDGROUP; i++) { + ++gLastSelectedCivilianIndex; + if (gLastSelectedCivilianIndex >= NUMMODELSPERPEDGROUP) + gLastSelectedCivilianIndex = 0; + lastModel = ms_pPedGroups[group].models[gLastSelectedCivilianIndex]; + + if (!CPopulation::IsSunbather(ms_pPedGroups[group].models[gLastSelectedCivilianIndex])) + break; + } + return lastModel; + + } else { + ++gLastSelectedCivilianIndex; + if (gLastSelectedCivilianIndex >= NUMMODELSPERPEDGROUP) + gLastSelectedCivilianIndex = 0; + return ms_pPedGroups[group].models[gLastSelectedCivilianIndex]; + } +} + +// 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; + } + } +} + +int32 +CPopulation::ChooseGangOccupation(int gangId) +{ + return CGangs::ChooseGangPedModel(gangId); +} + +void +CPopulation::DealWithZoneChange(eLevelName oldLevel, eLevelName newLevel, bool forceIndustrialZone) +{ +} + +void +CPopulation::Update(bool addPeds) +{ + if (!CReplay::IsPlayingBack()) { + ManagePopulation(); + RemovePedsIfThePoolGetsFull(); + 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; + ms_nTotalPeds -= ms_nTotalCarPassengerPeds; + if (!CCutsceneMgr::IsRunning() && addPeds) { + 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 < 100; 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; + ms_nTotalPeds -= ms_nTotalCarPassengerPeds; + + // 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)); + } +} + +// 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, int32 modifier) +{ + 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; + + switch (CGeneral::GetRandomNumber() & 3) { + case 0: + weapon = WEAPONTYPE_COLT45; + break; + case 1: + weapon = WEAPONTYPE_NIGHTSTICK; + break; + case 2: + weapon = WEAPONTYPE_GOLFCLUB; + break; + case 3: + weapon = WEAPONTYPE_TEC9; + break; + default: + break; + } + if (weapon != WEAPONTYPE_UNARMED) { + ped->GiveDelayedWeapon(weapon, 25001); + ped->SetCurrentWeapon(CWeaponInfo::GetWeaponInfo(weapon)->m_nWeaponSlot); + } + } + return ped; + } + case PEDTYPE_COP: + { + CCopPed *ped = new CCopPed((eCopType)miOrCopType, modifier); + 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); + + eWeaponType weapon; + if (CGeneral::GetRandomNumberInRange(0, 100) >= 50) + weapon = (eWeaponType)CGangs::GetGangInfo(pedType - PEDTYPE_GANG1)->m_Weapon2; + else + weapon = (eWeaponType)CGangs::GetGangInfo(pedType - PEDTYPE_GANG1)->m_Weapon1; + + ped->GiveDelayedWeapon(weapon, 25001); + ped->SetCurrentWeapon(CWeaponInfo::GetWeaponInfo(weapon)->m_nWeaponSlot); + 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; + int32 man = -1, woman = -1; + CPed *gangLeader = nil; + bool addCop = false; + bool isSecurityGuard = false; + bool forceAddingCop = 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 (!CGame::IsInInterior() && (CGeneral::GetRandomNumber() % 32 == 0) && FindPlayerVehicle()) + forceAddingCop = true; + + uint32 maxCops = CGame::IsInInterior() ? wantedInfo->m_MaxCops * 1.6f : wantedInfo->m_MaxCops; + if ((ms_nNumCop < maxCops || forceAddingCop) && + (!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) || forceAddingCop)) { + addCop = true; + minDist = PedCreationDistMultiplier() * MIN_CREATION_DIST; + maxDist = PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE); + } + } + float missionAndWeatherMult = -0.8f * Sqrt(CWeather::Rain) + 1.0f; + + // Taxi side mission + if (CTheScripts::IsPlayerOnAMission()) { + CPed *player = FindPlayerPed(); + if (player && player->InVehicle() && player->m_pMyVehicle->IsTaxi()) + missionAndWeatherMult = 1.0f; + } + if (CDarkel::FrenzyOnGoing()) + missionAndWeatherMult = 1.0f; + int selectedMaxPeds = CGame::IsInInterior() ? CPopulation::MaxNumberOfPedsInUseInterior : CPopulation::MaxNumberOfPedsInUse; + + // Yeah, float + float maxPossiblePedsForArea = (zoneInfo.pedDensity + zoneInfo.carDensity) * playerInfo->m_fRoadDensity * PedDensityMultiplier + * (CDarkel::FrenzyOnGoing() ? 1.f : CIniFile::PedNumberMultiplier) * missionAndWeatherMult; + maxPossiblePedsForArea = Min(maxPossiblePedsForArea, selectedMaxPeds); + + if (ms_nTotalPeds < maxPossiblePedsForArea || addCop) { + int decisionThreshold = CGeneral::GetRandomNumberInRange(0, 1000); + if (decisionThreshold < zoneInfo.copPedThreshold || addCop) { + pedTypeToAdd = PEDTYPE_COP; + modelToAdd = ChoosePolicePedOccupation(); + } else { + int i = 0; + for (i = 0; i < NUM_GANGS; i++) { + if (decisionThreshold < zoneInfo.gangPedThreshold[i]) { + break; + } + } + + if (i == NUM_GANGS) { + if (CGeneral::GetRandomNumberInRange(0.0f, 1.0f) <= 0.95f) { + modelToAdd = ChooseCivilianOccupation(zoneInfo.pedGroup); + + if (modelToAdd == -1) + return; + pedTypeToAdd = ((CPedModelInfo*)CModelInfo::GetModelInfo(modelToAdd))->m_pedType; + + } else { + ChooseCivilianCoupleOccupations(zoneInfo.pedGroup, man, woman); + if (man == -1 || woman == -1) + return; + pedTypeToAdd = ((CPedModelInfo*)CModelInfo::GetModelInfo(woman))->m_pedType; + } + } else { + pedTypeToAdd = PEDTYPE_GANG1 + i; + + if (IsSecurityGuard((ePedType)pedTypeToAdd)) { + isSecurityGuard = true; + modelToAdd = ChooseGangOccupation(pedTypeToAdd - PEDTYPE_GANG1); + + 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 && !isSecurityGuard) { + minDist += 30.0f; + maxDist += 30.0f; + pedAmount = ComputeRandomisedGangSize(); + } else + pedAmount = 1; + + CVector generatedCoors; + int32 node1, node2; + float randomPos; + bool foundCoors = !!ThePaths.GeneratePedCreationCoors(playerCentreOfWorld.x, playerCentreOfWorld.y, minDist, maxDist, minDistOffScreen, maxDistOffScreen, + &generatedCoors, &node1, &node2, &randomPos, nil); + + if (!foundCoors) + return; + + uint8 nodeSpawnRate = Min(ThePaths.m_pathNodes[node1].spawnRate, ThePaths.m_pathNodes[node2].spawnRate); + int randomRate = CGeneral::GetRandomNumber() & 0xF; + if (randomRate > nodeSpawnRate) + return; + + CPathFind::TakeWidthIntoAccountForCoors(&ThePaths.m_pathNodes[node1], &ThePaths.m_pathNodes[node2], CGeneral::GetRandomNumber(), &generatedCoors.x, &generatedCoors.y); + if (CGame::currArea == AREA_MALL && (pedTypeToAdd == PEDTYPE_CIVMALE || pedTypeToAdd == PEDTYPE_CIVFEMALE || pedTypeToAdd == PEDTYPE_CRIMINAL) && + CGeneral::GetRandomNumberInRange(0.f, 1.f) > 0.5f) { + + PlaceMallPedsAsStationaryGroup(generatedCoors, zoneInfo.pedGroup); + return; + } + + if (pedTypeToAdd >= PEDTYPE_GANG1 && pedTypeToAdd <= PEDTYPE_GANG9 && !isSecurityGuard) { + PlaceGangMembers((ePedType)pedTypeToAdd, pedAmount, generatedCoors); + return; + } + + if (man > -1 && woman > -1) { + PlaceCouple(PEDTYPE_CIVMALE, man, PEDTYPE_CIVFEMALE, woman, generatedCoors); + return; + } + + for (int i = 0; i < pedAmount; ++i) { + + if (pedTypeToAdd == PEDTYPE_COP) { + // Unused code, ChoosePolicePedOccupation returns COP_STREET. Spawning FBI/SWAT/Army done in somewhere else. + if (modelToAdd == COP_STREET) { + if (!CStreaming::HasModelLoaded(MI_COP)) + return; + + } else if (modelToAdd == COP_FBI) { + if (!CStreaming::HasModelLoaded(MI_COP) || !CStreaming::HasModelLoaded(CWeaponInfo::GetWeaponInfo(WEAPONTYPE_MP5)->m_nModelId)) + return; + + } else if (modelToAdd == COP_SWAT) { + if (!CStreaming::HasModelLoaded(MI_SWAT) || !CStreaming::HasModelLoaded(CWeaponInfo::GetWeaponInfo(WEAPONTYPE_UZI)->m_nModelId)) + return; + + } else if (modelToAdd == COP_ARMY) { + if (!CStreaming::HasModelLoaded(MI_ARMY) || + !CStreaming::HasModelLoaded(CWeaponInfo::GetWeaponInfo(WEAPONTYPE_MP5)->m_nModelId) || !CStreaming::HasModelLoaded(CWeaponInfo::GetWeaponInfo(WEAPONTYPE_GRENADE)->m_nModelId)) + return; + } + } else if (!CStreaming::HasModelLoaded(modelToAdd)) { + return; + } + generatedCoors.z += 0.7f; + + // What? How can this not be met? + if (i < pedAmount) { + // rand() + // III leftover, unused + 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 surfaceAndDistIsOk = true; + if (TheCamera.IsSphereVisible(generatedCoors, 2.0f)) { + if (PedCreationDistMultiplier() * MIN_CREATION_DIST > (generatedCoors - playerCentreOfWorld).Magnitude2D()) + surfaceAndDistIsOk = false; + } + + // Place skaters if only they're on tarmac. + if (((CPedModelInfo*)CModelInfo::GetModelInfo(modelToAdd))->m_pedStatType == PEDSTAT_SKATER) { + CEntity* foundEnt = nil; + CColPoint foundCol; + CWorld::ProcessVerticalLine(generatedCoors + CVector(0.f, 0.f, 2.f), generatedCoors.z - 2.0f, foundCol, foundEnt, true, false, false, false, false, false, nil); + if (foundEnt) { + if (foundCol.surfaceB == SURFACE_TARMAC || foundCol.surfaceB == SURFACE_PAVEMENT) + surfaceAndDistIsOk = true; + else + surfaceAndDistIsOk = false; + + } else { + surfaceAndDistIsOk = false; + } + } + if (!surfaceAndDistIsOk) + break; + CPed *newPed = AddPed((ePedType)pedTypeToAdd, modelToAdd, generatedCoors); + if (forceAddingCop && newPed->m_nPedType == PEDTYPE_COP) + ((CCopPed*)newPed)->m_bThrowsSpikeTrap = true; + + bool gonnaSunbathe = false; + if (CPopulation::IsSunbather(modelToAdd)) { + CEntity* foundEnt = nil; + CColPoint foundCol; + CWorld::ProcessVerticalLine(generatedCoors + CVector(0.f, 0.f, 2.f), generatedCoors.z - 2.0f, foundCol, foundEnt, true, false, false, false, false, false, nil); + if (foundEnt) { + if ((foundCol.surfaceB == SURFACE_CONCRETE_BEACH || foundCol.surfaceB == SURFACE_SAND) + && CClock::GetHours() >= 10 && CClock::GetHours() <= 18 && 0.0f == CWeather::Rain) { + gonnaSunbathe = true; + if (CPedPlacement::IsPositionClearForPed(generatedCoors, 3.0f, ARRAY_SIZE(gSunbatheObstacles), gSunbatheObstacles)) { + for (int j = 0; j < ARRAY_SIZE(gSunbatheObstacles); j++) { + if (gSunbatheObstacles[j] && gSunbatheObstacles[j] != newPed) + gonnaSunbathe = false; + } + } + } + } + } + if (gonnaSunbathe) { + float heading = CGeneral::GetRandomNumberInRange(0.f, 1.f) * TWOPI; + newPed->m_fRotationDest = heading; + newPed->m_fRotationCur = heading; + // unused + // v61 = CGeneral::GetRandomTrueFalse(); + newPed->SetWaitState(WAITSTATE_SUN_BATHE_IDLE, nil); + CVector toyPos(newPed->GetPosition()); + float waterLevel; + if (CWaterLevel::GetGroundLevel(toyPos, &waterLevel, nil, 30.0f)) { + toyPos.z = 0.04f + waterLevel; + CEntity *toy = CWaterLevel::CreateBeachToy(toyPos, BEACHTOY_ANY_TOWEL); + if (toy) + toy->SetHeading(heading); + + if (!(CGeneral::GetRandomNumber() & 3)) { + CWaterLevel::CreateBeachToy(toyPos + CVector(CGeneral::GetRandomNumberInRange(-2.f, 2.f), CGeneral::GetRandomNumberInRange(-2.f, 2.f), 0.f), BEACHTOY_LOTION); + } + } + } else { + newPed->SetWanderPath(CGeneral::GetRandomNumberInRange(0, 8)); + } + + if (i != 0) { + // Gang member + newPed->SetLeader(gangLeader); + + newPed->SetPedState(PED_UNKNOWN); + gangLeader->SetPedState(PED_UNKNOWN); + + } 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, bool isDriver) +{ + const int defaultModel = MI_MALE01; + int miamiViceIndex = 0; + 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_POLICE: + case MI_PREDATOR: + 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_FBIRANCH: + preferredModel = COP_FBI; + pedType = PEDTYPE_COP; + break; + default: + if (car->IsTaxi()) { + if (isDriver) { + pedType = PEDTYPE_CIVMALE; + preferredModel = MI_TAXI_D; + break; + } + // fall through if not + } else if (car->GetModelIndex() == MI_VICECHEE) { + if (car->bIsLawEnforcer) { + preferredModel = COP_MIAMIVICE; + pedType = PEDTYPE_COP; + miamiViceIndex = (isDriver ? 2 * CCarCtrl::MiamiViceCycle : 2 * CCarCtrl::MiamiViceCycle + 1); + break; + } + // fall through if not + } + + 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 *carModel = ((CVehicleModelInfo *)CModelInfo::GetModelInfo(car->GetModelIndex())); + preferredModel = ChooseCivilianOccupation(zoneInfo.pedGroup); + int i = 15; + for(; i >= 0; i--) { + CPedModelInfo* pedModel = (CPedModelInfo*)CModelInfo::GetModelInfo(preferredModel); + + if (pedModel->GetRwObject()) { + if (!car->IsPassenger(preferredModel) && !car->IsDriver(preferredModel)) { + if (((CPedModelInfo*)CModelInfo::GetModelInfo(preferredModel))->m_carsCanDrive & (1 << carModel->m_vehicleClass)) + break; + } + } + + preferredModel = ChooseNextCivilianOccupation(zoneInfo.pedGroup); + } + 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(), miamiViceIndex); + newPed->bUsesCollision = false; + + if (newPed->GetWeapon()->m_eWeaponType != WEAPONTYPE_UNARMED) { + newPed->RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(newPed->GetWeapon()->m_eWeaponType)->m_nModelId); + } + + newPed->AddInCarAnims(car, isDriver); + return newPed; +} + +void +CPopulation::MoveCarsAndPedsOutOfAbandonedZones() +{ +} + +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); + + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(obj->GetModelIndex()); + if (IsGlass(obj->GetModelIndex()) && !mi->m_isArtistGlass) { + 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(); + + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(obj->GetModelIndex()); + if (IsGlass(obj->GetModelIndex()) && !mi->m_isArtistGlass) + dummy->bIsVisible = false; + + CWorld::Remove(obj); + delete obj; + CWorld::Add(dummy); +} + +bool +CPopulation::TestRoomForDummyObject(CObject *obj) +{ + int16 collidingObjs; + CWorld::FindObjectsKindaColliding(obj->m_objectMatrix.GetPosition(), obj->GetBoundRadius(), + 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()) { + float objPlayerDist = (obj->GetPosition() - playerPos).Magnitude(); + if (obj->ObjectCreatedBy == TEMP_OBJECT) { + if (obj->GetModelIndex() != MI_ROADWORKBARRIER1 && obj->GetModelIndex() != MI_BEACHBALL) { + if (objPlayerDist > 51.0f || objPlayerDist > 25.0f && !obj->GetIsOnScreen() || CTimer::GetTimeInMilliseconds() > obj->m_nEndOfLifeTime) { + CWorld::Remove(obj); + delete obj; + } + } else if (objPlayerDist > 120.0f) { + CWorld::Remove(obj); + delete obj; + } + + } else if (objPlayerDist > 80.0f && (obj->m_objectMatrix.GetPosition() - playerPos).Magnitude() > 80.0f) { + 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 && (dummy->m_area == CGame::currArea || dummy->m_area == AREA_EVERYWHERE)) { + 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) { + uint32 timeSinceDeath = CTimer::GetTimeInMilliseconds() - ped->m_bloodyFootprintCountOrDeathTime; + if (ped->m_nPedState == PED_DEAD && (timeSinceDeath > 30000 || CDarkel::FrenzyOnGoing() && timeSinceDeath > 15000)) + ped->bFadeOut = true; + + if (ped->bFadeOut && CVisibilityPlugins::GetClumpAlpha(ped->GetClump()) == 0) { + RemovePed(ped); + continue; + } + + float dist = (ped->GetPosition() - playerPos).Magnitude2D(); + + bool pedIsFarAway = false; + + if (ped->IsGangMember()) + dist -= 30.0f; + else if (ped->bDeadPedInFrontOfCar && ped->m_vehicleInAccident) + dist = 0.0f; + + if (PedCreationDistMultiplier() * (PED_REMOVE_DIST_SPECIAL * TheCamera.GenerationDistMultiplier) < dist || + (!ped->bCullExtraFarAway && PedCreationDistMultiplier() * PED_REMOVE_DIST * TheCamera.GenerationDistMultiplier < dist)) { + pedIsFarAway = true; + } +#ifndef EXTENDED_OFFSCREEN_DESPAWN_RANGE + else if (PedCreationDistMultiplier() * (MIN_CREATION_DIST + CREATION_RANGE) * OFFSCREEN_CREATION_MULT < dist) { + if (CTimer::GetTimeInMilliseconds() > ped->m_nExtendedRangeTimer && !ped->GetIsOnScreen()) { + if (TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_SNIPER + && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_SNIPER_RUNABOUT + && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_CAMERA + && !TheCamera.Cams[TheCamera.ActiveCam].LookingLeft + && !TheCamera.Cams[TheCamera.ActiveCam].LookingRight + && !TheCamera.Cams[TheCamera.ActiveCam].LookingBehind) { + pedIsFarAway = true; + } + } + + } +#endif + else { + ped->m_nExtendedRangeTimer = ped->m_nPedType == PEDTYPE_COP ? CTimer::GetTimeInMilliseconds() + 10000 : CTimer::GetTimeInMilliseconds() + 4000; + } + + 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); + } + } +} + +CPed* +CPopulation::AddDeadPedInFrontOfCar(const CVector& pos, CVehicle* pCulprit) +{ + if (TheCamera.IsSphereVisible(pos, 2.0f) && MIN_CREATION_DIST * PedCreationDistMultiplier() > (pos - FindPlayerPed()->GetPosition()).Magnitude2D()) { + return nil; + } + + bool found; + float z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &found) + 1.0f; + if (!found) + return nil; + z = Max(z, pos.z); + if (!CModelInfo::GetModelInfo(MI_MALE01)->GetRwObject()) + return nil; + CPed* pPed = CPopulation::AddPed(PEDTYPE_CIVMALE, MI_MALE01, pos); + pPed->SetDie(); + pPed->m_nPedMoney = 0; + pPed->bDeadPedInFrontOfCar = true; + pPed->m_vehicleInAccident = pCulprit; + pCulprit->RegisterReference((CEntity**)&pPed->m_vehicleInAccident); + CEntity* pEntities[3] = { 0 }; + if (!CPedPlacement::IsPositionClearForPed(pos, 2.0f, 3, pEntities)) { + for (int i = 0; i < 3; i++) { + if (pEntities[i] && pEntities[i] != pCulprit && pEntities[i] != pPed) { + RemovePed(pPed); + return nil; + } + } + } + CColPoint colpts[MAX_COLLISION_POINTS]; + if (CCollision::ProcessColModels(pCulprit->GetMatrix(), *pCulprit->GetColModel(), pPed->GetMatrix(), *pPed->GetColModel(), colpts, nil, nil)) { + RemovePed(pPed); + return nil; + } + CVisibilityPlugins::SetClumpAlpha(pPed->GetClump(), 0); + return pPed; +} + +bool +CPopulation::IsSkateable(CVector const& pos) +{ + CColPoint foundCol; + CEntity* foundEnt = nil; + CWorld::ProcessVerticalLine(pos + CVector(0.f, 0.f, 2.f), pos.z - 2.0f, foundCol, foundEnt, true, false, false, false, false, false, nil); + if (!foundEnt) + return false; + + return foundCol.surfaceB == SURFACE_TARMAC || foundCol.surfaceB == SURFACE_PAVEMENT; +} + +bool +CPopulation::CanJeerAtStripper(int32 model) +{ + return model == MI_WMOBE || model == MI_WMYBE || model == MI_WMOST || model == MI_BMYBB; +} + +void +CPopulation::RemovePedsIfThePoolGetsFull(void) +{ + if ((CTimer::GetFrameCounter() & 7) == 5) { + if (CPools::GetPedPool()->GetNoOfFreeSpaces() < 8) { + CPed *closestPed = nil; + float closestDist = 10000000.0; + int poolSize = CPools::GetPedPool()->GetSize(); + for (int i = poolSize - 1; i >= 0; i--) { + CPed* ped = CPools::GetPedPool()->GetSlot(i); + if (ped && ped->CanBeDeleted()) { + float dist = (TheCamera.GetPosition() - ped->GetPosition()).Magnitude(); + if (dist < closestDist) { + closestDist = dist; + closestPed = ped; + } + } + } + if (closestPed) { + RemovePed(closestPed); + } + } + } +} + +bool +CPopulation::IsMale(int32 model) +{ + switch (model) { + case MI_HMYST: + case MI_HMOST: + case MI_HMYRI: + case MI_HMORI: + case MI_HMYBE: + case MI_HMOBE: + case MI_HMOTR: + case MI_HMYAP: + case MI_HMOCA: + case MI_BMODK: + case MI_BMYKR: + case MI_BMYST: + case MI_BMOST: + case MI_BMYRI: + case MI_BMYBE: + case MI_BMOBE: + case MI_BMYBU: + case MI_BMOTR: + case MI_BMYPI: + case MI_BMYBB: + case MI_WMYCR: + case MI_WMYST: + case MI_WMOST: + case MI_WMYRI: + case MI_WMORI: + case MI_WMYBE: + case MI_WMOBE: + case MI_WMYCW: + case MI_WMYGO: + case MI_WMOGO: + case MI_WMYLG: + case MI_WMYBU: + case MI_WMOBU: + case MI_WMOTR: + case MI_WMYPI: + case MI_WMOCA: + case MI_WMYJG: + case MI_WMYSK: + + // BUG? Why no JMOTO? + return true; + default: + return false; + } +} + +bool +CPopulation::IsFemale(int32 model) +{ + switch (model) { + case MI_HFYST: + case MI_HFOST: + case MI_HFYRI: + case MI_HFORI: + case MI_HFYBE: + case MI_HFOBE: + case MI_HFYBU: + case MI_HFYMD: + case MI_HFYCG: + case MI_HFYPR: + case MI_HFOTR: + case MI_BFYST: + case MI_BFOST: + case MI_BFYRI: + case MI_BFORI: + case MI_BFYBE: + case MI_BFOBE: + case MI_BFYPR: + case MI_BFOTR: + case MI_WFYST: + case MI_WFOST: + case MI_WFYRI: + case MI_WFORI: + case MI_WFYBE: + case MI_WFOBE: + case MI_WFOGO: + case MI_WFYLG: + case MI_WFYBU: + case MI_WFYPR: + case MI_WFOTR: + case MI_WFYJG: + case MI_WFYSK: + case MI_WFYSH: + case MI_WFOSH: + case MI_JFOTO: + return true; + default: + return false; + } +} + +bool +CPopulation::IsSunbather(int32 model) +{ + switch (model) { + case MI_HFYBE: + case MI_HFOBE: + case MI_HMYBE: + case MI_HMOBE: + case MI_BFYBE: + case MI_BMYBE: + case MI_BFOBE: + case MI_BMOBE: + case MI_WFYBE: + case MI_WMYBE: + case MI_WFOBE: + case MI_WMOBE: + return true; + default: + return false; + } +} + +int32 +CPopulation::ComputeRandomisedGangSize(void) +{ + return CGeneral::GetRandomNumberInRange(3, 6); +} + +bool +CPopulation::CanSolicitPlayerInCar(int32 model) +{ + return model == MI_HFYPR || model == MI_BFYPR || model == MI_WFYPR; +} + +bool +CPopulation::CanSolicitPlayerOnFoot(int32 model) +{ + return model == MI_HFYMD || model == MI_HFYCG || model == MI_BFOTR || model == MI_BMOTR || model == MI_WFOTR || model == MI_WMOTR; +} + +bool +CPopulation::IsSecurityGuard(ePedType pedType) +{ + return pedType == PEDTYPE_GANG5; +} + +void +CPopulation::ChooseCivilianCoupleOccupations(int32 group, int32& man, int32& woman) +{ + man = -1; + woman = -1; + + for (int i = 0; i < 8; i++) { + if (man > -1) + break; + + int32 model = ms_pPedGroups[group].models[CGeneral::GetRandomNumberInRange(0, NUMMODELSPERPEDGROUP)]; + if (man == -1 && IsMale(model) && ((CPedModelInfo*)CModelInfo::GetModelInfo(model))->m_pedType == PEDTYPE_CIVMALE) { + man = model; + } + } + + if (man != -1) { + int32 model; + for (int i = 0; i < NUMMODELSPERPEDGROUP; i++) { + model = ms_pPedGroups[group].models[i]; + if (IsFemale(model)) { + CPedModelInfo* womanModelInfo = (CPedModelInfo*)CModelInfo::GetModelInfo(model); + if (womanModelInfo->m_pedType == PEDTYPE_CIVFEMALE) { + CPedModelInfo* manModelInfo = (CPedModelInfo*)CModelInfo::GetModelInfo(man); + + // If both are skater or not, finalize the decision + if (manModelInfo && womanModelInfo) { + if (manModelInfo->m_animGroup == womanModelInfo->m_animGroup) { + if (manModelInfo->m_pedStatType != PEDSTAT_SKATER && womanModelInfo->m_pedStatType != PEDSTAT_SKATER) + break; + + if (manModelInfo->m_pedStatType == PEDSTAT_SKATER && womanModelInfo->m_pedStatType == PEDSTAT_SKATER) + break; + } + } + } + } + } + woman = model; + } +} + +void +CPopulation::PlaceGangMembers(ePedType pedType, int32 pedAmount, CVector const& coors) +{ + if (CGeneral::GetRandomNumberInRange(0.f, 1.f) < 0.333f) { + PlaceGangMembersInFormation(pedType, pedAmount, coors); + } else { + PlaceGangMembersInCircle(pedType, pedAmount, coors); + } +} + +void +CPopulation::PlaceGangMembersInFormation(ePedType pedType, int32 pedAmount, CVector const& coors) +{ + CPed *createdPeds[5]; + + if (!TheCamera.IsSphereVisible(coors, 3.0f) || MIN_CREATION_DIST * PedCreationDistMultiplier() <= (coors - FindPlayerPed()->GetPosition()).Magnitude2D()) { + if (CPedPlacement::IsPositionClearForPed(coors, 3.0f, -1, nil)) { + bool leaderFoundGround; + float leaderGroundZ = CWorld::FindGroundZFor3DCoord(coors.x, coors.y, coors.z, &leaderFoundGround) + 1.0f; + if (leaderFoundGround) { + float finalZ = coors.z > leaderGroundZ ? coors.z : leaderGroundZ; + int leaderModel = ChooseGangOccupation(pedType - PEDTYPE_GANG1); + if (((CPedModelInfo*)CModelInfo::GetModelInfo(leaderModel))->GetRwObject()) { + CPed *leader = AddPed(pedType, leaderModel, CVector(coors.x, coors.y, finalZ)); + if (leader) { + leader->SetObjective(OBJECTIVE_NONE); + leader->SetWanderPath(CGeneral::GetRandomNumberInRange(0, 8)); + leader->bIsLeader = true; + if (CGangs::GetWillAttackPlayerWithCops(pedType)) + leader->bCanAttackPlayerWithCops = true; + + int pedIdx = 1; + createdPeds[0] = leader; + for (int i = 1; i < pedAmount; ++i) { + int memberModel = ChooseGangOccupation(pedType - PEDTYPE_GANG1); + if (!((CPedModelInfo*)CModelInfo::GetModelInfo(memberModel))->GetRwObject()) + continue; + + CPed* memberPed = AddPed(pedType, memberModel, CVector(coors.x, coors.y, finalZ)); + if (!memberPed) + continue; + + memberPed->SetObjective(OBJECTIVE_FOLLOW_CHAR_IN_FORMATION, leader); + memberPed->SetFormation((eFormation)i); + CVector formationPos = memberPed->GetFormationPosition(); + CVector finalFormationPos = formationPos; + bool formationFoundGround; + float formationGroundZ = CWorld::FindGroundZFor3DCoord(formationPos.x, formationPos.y, 1.0f + formationPos.z, &formationFoundGround) + 1.0f; + + finalFormationPos.z = Max(finalFormationPos.z, formationGroundZ); + if (formationFoundGround) { + if (Abs(finalFormationPos.z - leader->GetPosition().z) <= 1.0f) { + if (CWorld::GetIsLineOfSightClear(finalFormationPos, leader->GetPosition(), true, false, false, false, false, false, false)) { + memberPed->SetPosition(finalFormationPos); + createdPeds[pedIdx++] = memberPed; + if (CGangs::GetWillAttackPlayerWithCops(pedType)) + leader->bCanAttackPlayerWithCops = true; + + CVisibilityPlugins::SetClumpAlpha(memberPed->GetClump(), 0); + continue; + } + } + } + RemovePed(memberPed); + } + if (pedIdx >= 3) { + for (int j = 1; j < pedIdx; ++j) + createdPeds[j]->SetLeader(createdPeds[0]); + + } else { + for (int k = 0; k < pedIdx; ++k) { + RemovePed(createdPeds[k]); + } + } + } + } + } + } + } +} + +void +CPopulation::PlaceGangMembersInCircle(ePedType pedType, int32 pedAmount, CVector const& coors) +{ + CPed *createdPeds[5]; + + if (pedAmount < 2) + return; + + float circleSector = TWOPI / pedAmount; + + float circleR = Sqrt(0.5f / (1.0f - Cos(circleSector))); + + if (!TheCamera.IsSphereVisible(coors, circleR) || + MIN_CREATION_DIST * PedCreationDistMultiplier() <= (coors - FindPlayerPed()->GetPosition()).Magnitude2D()) { + + if (CPedPlacement::IsPositionClearForPed(coors, circleR, -1, nil)) { + int pedIdx = 0; + CVector leaderPos; +#ifdef FIX_BUGS + bool createLeader = true; +#endif + + for (int i = 0; i < pedAmount; i++) { + float angleMult = i + CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + float randomR = circleR + CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * circleR; + float xOffset = randomR * Cos(angleMult * circleSector); + float yOffset = randomR * Sin(angleMult * circleSector); + bool foundGround; + float groundZ = CWorld::FindGroundZFor3DCoord(xOffset + coors.x, yOffset + coors.y, coors.z + 1.0, &foundGround) + 1.0f; + if (foundGround) { + CVector finalPos(coors.x + xOffset, coors.y + yOffset, coors.z > groundZ ? coors.z : groundZ); +#ifndef FIX_BUGS + const bool createLeader = i == 0; +#endif + if (createLeader) + leaderPos = finalPos; + + int gangModel = ChooseGangOccupation(pedType - PEDTYPE_GANG1); + if (((CPedModelInfo*)CModelInfo::GetModelInfo(gangModel))->GetRwObject()) { + CEntity* obstacles[6] = { nil, nil, nil, nil, nil, nil }; + CPedPlacement::IsPositionClearForPed(finalPos, CModelInfo::GetColModel(gangModel)->boundingSphere.radius, ARRAY_SIZE(obstacles), obstacles); + bool foundObstacle = false; + for (int m = 0; m < ARRAY_SIZE(obstacles); m++) { + CEntity* obstacle = obstacles[m]; + if (obstacle) { + int n = 0; + bool obstacleIsHarmless = false; + for (int n = 0; n < pedIdx; n++) { + if (obstacle == createdPeds[n]) + obstacleIsHarmless = true; + } + if (!obstacleIsHarmless) { + foundObstacle = true; + break; + } + } + } + bool memberCanSeeLeader = createLeader ? true : CWorld::GetIsLineOfSightClear(finalPos, leaderPos, true, false, false, false, false, false, false); + + bool notTooHighFromLeader = createLeader ? true : !(Abs(finalPos.z - leaderPos.z) >= 1.0f); + + if (!foundObstacle && memberCanSeeLeader && notTooHighFromLeader) { + CPed* newPed = AddPed(pedType, gangModel, finalPos); + if (newPed) { + createdPeds[pedIdx++] = newPed; + float angle = CGeneral::GetRadianAngleBetweenPoints( + coors.x, coors.y, + finalPos.x, finalPos.y); + newPed->m_fRotationDest = angle; + newPed->m_fRotationCur = angle; + if (CGangs::GetWillAttackPlayerWithCops(pedType)) + newPed->bCanAttackPlayerWithCops = true; + + CVisibilityPlugins::SetClumpAlpha(newPed->GetClump(), 0); +#ifdef FIX_BUGS + createLeader = false; +#endif + } + // No. +#ifndef FIX_BUGS + else + CWorld::Remove(nil); +#endif + } + } + } + } + if (pedIdx >= 3) { + for (int j = 0; j < pedIdx / 2; ++j) { + createdPeds[j]->SetChat(createdPeds[pedIdx - 1 - j], 100000); + createdPeds[pedIdx - 1 - j]->SetChat(createdPeds[j], 100000); + } + + // Make that extra guy in the middle stand there(PED_UNKNOWN locks him) and do nothing :lmao: + if (pedIdx % 2 != 0) { + CPed *tmim = createdPeds[(pedIdx - 1) / 2]; + float angle = CGeneral::GetRadianAngleBetweenPoints( + tmim->GetPosition().x, tmim->GetPosition().y, + createdPeds[0]->GetPosition().x, createdPeds[0]->GetPosition().y); + tmim->SetHeading(angle); + tmim->SetPedState(PED_UNKNOWN); + } + createdPeds[0]->bIsLeader = true; + + for (int l = 1; l < pedIdx; ++l) + createdPeds[l]->SetLeader(createdPeds[0]); + + } else { + for (int k = 0; k < pedIdx; ++k) { + RemovePed(createdPeds[k]); + } + } + } + } +} + +void +CPopulation::PlaceCouple(ePedType manType, int32 manModel, ePedType womanType, int32 womanModel, CVector coors) +{ + // Homosexuality filter!!!! Homophobic R* >>>:( + if (manType != PEDTYPE_CIVMALE || womanType != PEDTYPE_CIVFEMALE) + return; + + if (!TheCamera.IsSphereVisible(coors, 1.5f) || MIN_CREATION_DIST * PedCreationDistMultiplier() <= (coors - FindPlayerPed()->GetPosition()).Magnitude2D()) { + if (CPedPlacement::IsPositionClearForPed(coors, CModelInfo::GetColModel(manModel)->boundingSphere.radius, -1, nil)) { + bool manFoundGround; + float manGroundZ = CWorld::FindGroundZFor3DCoord(coors.x, coors.y, coors.z, &manFoundGround) + 1.0f; + if (manFoundGround) { + CVector correctedManPos = coors; + correctedManPos.z = Max(coors.z, manGroundZ); + if (((CPedModelInfo*)CModelInfo::GetModelInfo(manModel))->GetRwObject()) { + CPed *man = AddPed(PEDTYPE_CIVMALE, manModel, correctedManPos); + if (man) { + man->SetObjective(OBJECTIVE_NONE); + man->SetWanderPath(CGeneral::GetRandomNumberInRange(0, 8)); + man->bIsLeader = true; + CVisibilityPlugins::SetClumpAlpha(man->GetClump(), 0); + + if (((CPedModelInfo*)CModelInfo::GetModelInfo(womanModel))->GetRwObject()) { + CPed* woman = AddPed(PEDTYPE_CIVFEMALE, womanModel, correctedManPos); // will set the correct position later + if (woman) { + woman->SetObjective(OBJECTIVE_FOLLOW_CHAR_IN_FORMATION, man); + woman->SetFormation(FORMATION_RIGHT); + + CVector formationPos = woman->GetFormationPosition(); + CVector womanPos = formationPos; + bool womanFoundGround; + float formationGroundZ = CWorld::FindGroundZFor3DCoord(formationPos.x, formationPos.y, 1.0f + formationPos.z, &womanFoundGround) + 1.0f; + + if (womanFoundGround) { + CVector correctedWomanPos = womanPos; + correctedWomanPos.z = Max(womanPos.z, formationGroundZ); + woman->SetPosition(correctedWomanPos); + + // What's the point of this?? + CEntity* obstacles[3]; + memcpy(obstacles, gCoupleObstacles, sizeof(gCoupleObstacles)); + + CPedPlacement::IsPositionClearForPed(womanPos, CModelInfo::GetColModel(womanModel)->boundingSphere.radius, ARRAY_SIZE(obstacles), obstacles); + for (int i = 0; i < ARRAY_SIZE(obstacles); i++) { + CEntity *obstacle = obstacles[i]; + if (obstacle) { + + // We found a real obstacle, so let's break and we can delete them... + if (obstacle != man && obstacle != woman) + break; + } + if (i == ARRAY_SIZE(obstacles) - 1) { + CVisibilityPlugins::SetClumpAlpha(woman->GetClump(), 0); + return; + } + } + } + RemovePed(woman); + RemovePed(man); + } + } + } + } + } + } + } +} + +// Mostly copy paste of PlaceGangMembersInFormation. +void +CPopulation::PlaceMallPedsAsStationaryGroup(CVector const& coors, int32 group) +{ +#ifdef FIX_BUGS + CPed *createdPeds[6]; +#else + CPed *createdPeds[5]; +#endif + + if (CGame::currArea != AREA_MALL) + return; + + int pedAmount = CGeneral::GetRandomNumberInRange(0, 4) + 3; + + float circleSector = TWOPI / pedAmount; + + float circleR = Sqrt(0.5f / (1.0f - Cos(circleSector))); + + if (!TheCamera.IsSphereVisible(coors, circleR) || + MIN_CREATION_DIST * PedCreationDistMultiplier() <= (coors - FindPlayerPed()->GetPosition()).Magnitude2D()) { + + if (CPedPlacement::IsPositionClearForPed(coors, circleR, -1, nil)) { + int pedIdx = 0; + CVector leaderPos; +#ifdef FIX_BUGS + bool createLeader = true; +#endif + + for (int i = 0; i < pedAmount; i++) { + float angleMult = i + CGeneral::GetRandomNumberInRange(-0.2f, 0.2f); + float randomR = circleR + CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * circleR; + float xOffset = randomR * Cos(angleMult * circleSector); + float yOffset = randomR * Sin(angleMult * circleSector); + bool foundGround; + float groundZ = CWorld::FindGroundZFor3DCoord(xOffset + coors.x, yOffset + coors.y, coors.z + 1.0, &foundGround) + 1.0f; + if (foundGround) { + CVector finalPos(coors.x + xOffset, coors.y + yOffset, coors.z > groundZ ? coors.z : groundZ); + +#ifndef FIX_BUGS + const bool createLeader = i == 0; +#endif + if (createLeader) + leaderPos = finalPos; + + int pedModel = ChooseCivilianOccupation(group); + CPedModelInfo *pedModelInfo = (CPedModelInfo*)CModelInfo::GetModelInfo(pedModel); + + if (pedModelInfo->GetRwObject()) { + CEntity* obstacles[6] = { nil, nil, nil, nil, nil, nil }; + CPedPlacement::IsPositionClearForPed(finalPos, CModelInfo::GetColModel(pedModel)->boundingSphere.radius, ARRAY_SIZE(obstacles), obstacles); + bool foundObstacle = false; + for (int m = 0; m < ARRAY_SIZE(obstacles); m++) { + CEntity* obstacle = obstacles[m]; + if (obstacle) { + int n = 0; + bool obstacleIsHarmless = false; + for (int n = 0; n < pedIdx; n++) { + if (obstacle == createdPeds[n]) + obstacleIsHarmless = true; + } + if (!obstacleIsHarmless) { + foundObstacle = true; + break; + } + } + } + bool memberCanSeeLeader = createLeader ? true : CWorld::GetIsLineOfSightClear(finalPos, leaderPos, true, false, false, false, false, false, false); + + bool notTooHighFromLeader = createLeader ? true : !(Abs(finalPos.z - leaderPos.z) >= 1.0f); + + if (!foundObstacle && memberCanSeeLeader && notTooHighFromLeader) { + CPed *newPed = AddPed(pedModelInfo->m_pedType, pedModel, finalPos); + if (newPed) { + createdPeds[pedIdx++] = newPed; + float angle = CGeneral::GetRadianAngleBetweenPoints( + coors.x, coors.y, + finalPos.x, finalPos.y); + newPed->m_fRotationDest = angle; + newPed->m_fRotationCur = angle; + newPed->m_fearFlags = 0; + CVisibilityPlugins::SetClumpAlpha(newPed->GetClump(), 0); +#ifdef FIX_BUGS + createLeader = false; +#endif + } + // No. +#ifndef FIX_BUGS + else + CWorld::Remove(nil); +#endif + } + } + } + } + if (pedIdx >= 3) { + for (int j = 0; j < pedIdx / 2; ++j) { + createdPeds[j]->SetChat(createdPeds[pedIdx - 1 - j], 100000); + createdPeds[pedIdx - 1 - j]->SetChat(createdPeds[j], 100000); + } + + // Make that extra guy in the middle stand there(PED_UNKNOWN locks him) and do nothing :lmao: + if (pedIdx % 2 != 0) { + CPed *tmim = createdPeds[(pedIdx - 1) / 2]; + float angle = CGeneral::GetRadianAngleBetweenPoints( + tmim->GetPosition().x, tmim->GetPosition().y, + createdPeds[0]->GetPosition().x, createdPeds[0]->GetPosition().y); + tmim->SetHeading(angle); + tmim->SetPedState(PED_UNKNOWN); + } + createdPeds[0]->bIsLeader = true; + + for (int l = 1; l < pedIdx; ++l) + createdPeds[l]->SetLeader(createdPeds[0]); + + } else { + for (int k = 0; k < pedIdx; ++k) { + RemovePed(createdPeds[k]); + } + } + } + } +} diff --git a/src/miami/peds/Population.h b/src/miami/peds/Population.h new file mode 100644 index 00000000..8c58f1b6 --- /dev/null +++ b/src/miami/peds/Population.h @@ -0,0 +1,88 @@ +#pragma once + +#include "Game.h" +#include "PedType.h" + +class CPed; +class CVehicle; +class CDummyObject; +class CObject; + +struct PedGroup +{ + int32 models[NUMMODELSPERPEDGROUP]; +}; + +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 int32 MaxNumberOfPedsInUseInterior; + 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 uint32 ms_nTotalCarPassengerPeds; + static uint32 NumMiamiViceCops; + + static void Initialise(); + static void Update(bool); + static void LoadPedGroups(); + static void UpdatePedCount(ePedType, bool); + static void DealWithZoneChange(eLevelName oldLevel, eLevelName newLevel, bool); + static CPed *AddPedInCar(CVehicle *car, bool isDriver); + static void RemovePed(CPed *ent); + static int32 ChooseCivilianOccupation(int32); + static int32 ChooseNextCivilianOccupation(int32); + static void ChooseCivilianCoupleOccupations(int32, int32&, int32&); + static int32 ChoosePolicePedOccupation(); + static int32 ChooseGangOccupation(int); + static void GeneratePedsAtStartOfGame(); + static float PedCreationDistMultiplier(); + static CPed *AddPed(ePedType pedType, uint32 mi, CVector const &coors, int32 modifier = 0); + 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*); + static bool IsSkateable(CVector const&); + static bool CanJeerAtStripper(int32 model); + static void RemovePedsIfThePoolGetsFull(void); + static bool IsMale(int32); + static bool IsFemale(int32); + static bool IsSunbather(int32); + static int32 ComputeRandomisedGangSize(void); + static bool CanSolicitPlayerInCar(int32); + static bool CanSolicitPlayerOnFoot(int32); + static bool IsSecurityGuard(ePedType); + static void PlaceGangMembers(ePedType, int32, CVector const&); + static void PlaceGangMembersInFormation(ePedType, int32, CVector const&); + static void PlaceGangMembersInCircle(ePedType, int32, CVector const&); + static void PlaceCouple(ePedType, int32, ePedType, int32, CVector); + static void PlaceMallPedsAsStationaryGroup(CVector const&, int32); + static CPed* AddDeadPedInFrontOfCar(const CVector& pos, CVehicle* pCulprit); +}; diff --git a/src/miami/prof/profiler.cpp b/src/miami/prof/profiler.cpp new file mode 100644 index 00000000..b7ca8e4d --- /dev/null +++ b/src/miami/prof/profiler.cpp @@ -0,0 +1,376 @@ +/* + profiler.cpp + Copyright (C) 2020 Luke Benstead + Copyright (C) 2023, 2024 Ruslan Rostovtsev +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +perf_cntr_event_t profilerEvent = PMCR_PARALLEL_INSTRUCTION_ISSUED_MODE; +// perf_cntr_event_t profilerMode = PMCR_PIPELINE_FREEZE_BY_DCACHE_MISS_MODE; + +FILE *kernel_file = NULL; +static char KERNEL_OUTPUT_FILENAME[128]; +static char SECOND_OUTPUT_FILENAME[128]; +static kthread_t* THREAD; +static volatile bool PROFILER_RUNNING = false; +static volatile bool PROFILER_RECORDING = false; +// static tid_t KERNEL_TID = 0; +// static tid_t SECOND_TID = 0; +static int prof_count = 0; + +#define BASE_ADDRESS 0x8c010000 +#define BUCKET_SIZE 10000 + +#define INTERVAL_IN_MS 1 + +/* Simple hash table of samples. An array of Samples + * but, each sample in that array can be the head of + * a linked list of other samples */ +typedef struct Arc { + uint32_t pc; + uint32_t pr; // Caller return address + uint32_t count; + // tid_t tid; + struct Arc* next; +} Arc; + +static Arc ARCS[BUCKET_SIZE]; + +/* Hashing function for two uint32_ts */ +#define HASH_PAIR(x, y) ((x * 0x1f1f1f1f) ^ y) + +#define BUFFER_SIZE (1024 * 64) // 64K buffer + +const static size_t MAX_ARC_COUNT = BUFFER_SIZE / sizeof(Arc); +Arc arcbuf[MAX_ARC_COUNT]; + +static size_t ARC_COUNT = 0; + +static bool write_samples(tid_t tid, FILE *out); +static void clear_samples(); + +static Arc* new_arc(tid_t tid, uint32_t PC, uint32_t PR, uint32_t counter) { + Arc* s = &arcbuf[ARC_COUNT]; + s->count = counter; + s->pc = PC; + s->pr = PR; + // s->tid = tid; + s->next = NULL; + + ++ARC_COUNT; + + return s; +} + +static void record_thread(tid_t tid, uint32_t PC, uint32_t PR, uint32_t counter) { + uint32_t bucket = HASH_PAIR(PC, PR) % BUCKET_SIZE; + + Arc* s = &ARCS[bucket]; + // profileTick = (profileTick*3 + counter)/4; + + if(s->pc) { + /* Initialized sample in this bucket, + * does it match though? */ + while(s->pc != PC || s->pr != PR) { + if(s->next) { + s = s->next; + } else { + s->next = new_arc(tid, PC, PR, counter); + return; // We're done + } + } + + s->count+=counter; + } else { + /* Initialize this sample */ + s->count = counter; + s->pc = PC; + s->pr = PR; + // s->tid = tid; + s->next = NULL; + // ++ARC_COUNT; + } +} + + + +#define GMON_COOKIE "gmon" +#define GMON_VERSION 1 + +typedef struct { + char cookie[4]; // 'g','m','o','n' + int32_t version; // 1 + char spare[3 * 4]; // Padding +} GmonHeader; + +typedef struct { + uint32_t low_pc; + uint32_t high_pc; + uint32_t hist_size; + uint32_t prof_rate; + char dimen[15]; /* phys. dim., usually "seconds" */ + char dimen_abbrev; /* usually 's' for "seconds" */ +} GmonHistHeader; + +typedef struct { + unsigned char tag; // GMON_TAG_TIME_HIST = 0, GMON_TAG_CG_ARC = 1, GMON_TAG_BB_COUNT = 2 + size_t ncounts; // Number of address/count pairs in this sequence +} GmonBBHeader; + +typedef struct { + uint32_t from_pc; /* address within caller's body */ + uint32_t self_pc; /* address within callee's body */ + uint32_t count; /* number of arc traversals */ +} GmonArc; + +static bool init_sample_file(const char* path) { + + kernel_file = fopen(path, "w"); + if(!kernel_file) { + return false; + } + + /* Write the GMON header */ + + GmonHeader header; + memcpy(&header.cookie[0], GMON_COOKIE, sizeof(header.cookie)); + header.version = 1; + memset(header.spare, '\0', sizeof(header.spare)); + + fwrite(&header, sizeof(header), 1, kernel_file); + + return true; +} + +#define ROUNDDOWN(x,y) (((x)/(y))*(y)) +#define ROUNDUP(x,y) ((((x)+(y)-1)/(y))*(y)) + +static bool write_samples(tid_t tid, FILE *out) { + /* Appends the samples to the output file in gmon format + * + * We iterate the data twice, first generating arcs, then generating + * basic block counts. While we do that though we calculate the data + * for the histogram so we don't need a third iteration */ + + + // Seek to the end of the file + fseek(out, 0, SEEK_END); + + uint8_t tag = 1; + size_t written = 0; + + /* Write arcs */ + Arc* root = ARCS; + for(int i = 0; i < BUCKET_SIZE; ++i) { + if(root->pc) { + GmonArc arc; + arc.from_pc = root->pr; + arc.self_pc = root->pc; + arc.count = root->count; + + /* Write the root sample if it has a program counter */ + fwrite(&tag, sizeof(tag), 1, out); + fwrite(&arc, sizeof(GmonArc), 1, out); + + ++written; + + /* If there's a next pointer, traverse the list */ + Arc* s = root->next; + while(s) { + arc.from_pc = s->pr; + arc.self_pc = s->pc; + arc.count = s->count; + + /* Write the root sample if it has a program counter */ + fwrite(&tag, sizeof(tag), 1, out); + fwrite(&arc, sizeof(GmonArc), 1, out); + + ++written; + s = s->next; + } + } + + root++; + } + + + dbglog(DBG_INFO, "-- Written %d arcs\n", written); + + return true; +} + +static void prof_thd_timer_hnd(irq_context_t *context) { + + if (PROFILER_RECORDING) { + record_thread(1, context->pc, context->pr, 1); + + if(ARC_COUNT >= MAX_ARC_COUNT) { + PROFILER_RECORDING = 0; + } + } + + // replicate thd timer behaviour + { + /* Get the system time */ + uint64_t now = timer_ms_gettime64(); + + (void)context; + + //printf("timer woke at %d\n", (uint32_t)now); + + thd_schedule(0, now); + timer_primary_wakeup(PROFILER_RECORDING ? 1 : 10); + } +} + + +static void* run(void* args) { + dbglog(DBG_INFO, "-- Entered profiler thread!\n"); + + while(PROFILER_RUNNING){ + auto mask = irq_disable(); + if(!PROFILER_RECORDING) { + if (ARC_COUNT > 0) { + if(!write_samples(1, kernel_file)) { + dbglog(DBG_ERROR, "Error writing samples\n"); + } + fclose(kernel_file); + kernel_file = nullptr; + clear_samples(); + dbglog(DBG_INFO, "-- Profiler thread stopped recording\n"); + } + } else { + uint64_t counter = perf_cntr_count(PRFC1); + assert(counter <= UINT32_MAX); + + perf_cntr_clear(PRFC1); + + perf_cntr_resume(PRFC1); + + dbglog(DBG_INFO, "-- Profiler thread recording..., %d arcs, %u events\n", ARC_COUNT, (unsigned)counter); + } + irq_restore(mask); + + usleep(1000 * 1000); //usleep takes microseconds + } + + dbglog(DBG_INFO, "-- Profiler thread finished!\n"); + + return NULL; +} + +void profiler_init(const char* output) { + + timer_primary_set_callback(prof_thd_timer_hnd); + + /* Store the filenames */ + sprintf(KERNEL_OUTPUT_FILENAME, "%s/kernel_gmon_%d.out", output, ++prof_count); + // sprintf(SECOND_OUTPUT_FILENAME, "%s/second_gmon_%d.out", output, prof_count); + + /* Initialize the file */ + dbglog(DBG_INFO, "Creating profiler samples file for kernel thread...\n"); + if(!init_sample_file(KERNEL_OUTPUT_FILENAME)) { + dbglog(DBG_ERROR, "Can't create %s\n", KERNEL_OUTPUT_FILENAME); + return; + } + + // dbglog(DBG_INFO, "Creating profiler samples file for video thread...\n"); + // if(!init_sample_file(SECOND_OUTPUT_FILENAME)) { + // dbglog(DBG_ERROR, "Can't create %s\n", SECOND_OUTPUT_FILENAME); + // return; + // } + + dbglog(DBG_INFO, "Creating profiler thread...\n"); + // Initialize the samples to zero + memset(ARCS, 0, sizeof(ARCS)); + + PROFILER_RUNNING = true; + THREAD = thd_create(0, run, NULL); + + // /* Lower priority is... er, higher */ + thd_set_prio(THREAD, PRIO_DEFAULT / 2); + + dbglog(DBG_INFO, "Profiler thread started.\n"); +} + +void profiler_start() { + assert(PROFILER_RUNNING); + + if(PROFILER_RECORDING) { + return; + } + + auto mask = irq_disable(); + dbglog(DBG_INFO, "Starting profiling...\n"); + if (profilerEvent != PMCR_INIT_NO_MODE) { + dbglog(DBG_INFO, "Using event profiling...\n"); + perf_cntr_start(PRFC1, profilerEvent, PMCR_COUNT_CPU_CYCLES); + } + PROFILER_RECORDING = true; + clear_samples(); + irq_restore(mask); +} + +static void clear_samples() { + /* Free the samples we've collected to start again */ + + // Wipe the lot + memset(ARCS, 0, sizeof(ARCS)); + ARC_COUNT = 0; +} + +bool profiler_stop() { + if(!PROFILER_RECORDING) { + return false; + } + + bool rv = true; + + auto mask = irq_disable(); + + if (PROFILER_RECORDING) { + dbglog(DBG_INFO, "profiler_stop: Stopping profiling...\n"); + + PROFILER_RECORDING = false; + + if(!write_samples(1, kernel_file)) { + dbglog(DBG_ERROR, "ERROR WRITING SAMPLES (RO filesystem?)!\n"); + rv = false; + } + clear_samples(); + fclose(kernel_file); + kernel_file = nullptr; + } + irq_restore(mask); + + return rv; +} + +bool profiler_recording() { + return PROFILER_RECORDING; +} + +void profiler_clean_up() { + profiler_stop(); // Make sure everything is stopped + + PROFILER_RUNNING = false; + thd_join(THREAD, NULL); + + if(kernel_file != NULL) { + fclose(kernel_file); + } +} diff --git a/src/miami/prof/profiler.h b/src/miami/prof/profiler.h new file mode 100644 index 00000000..fc82be8b --- /dev/null +++ b/src/miami/prof/profiler.h @@ -0,0 +1,27 @@ + +/* + profiler.h + Copyright (C) 2020 Luke Benstead + Copyright (C) 2023 Ruslan Rostovtsev +*/ + +/** \file src/profiler.h + \brief gprof compatible sampling profiler. + + The Dreamcast doesn't have any kind of profiling support from GCC + so this is a cumbersome sampling profiler that runs in a background thread. + Once profiling has begun, the background thread will regularly gather the PC and PR registers stored by + the other threads. + The way thread scheduling works is that when other threads are blocked their current program counter is stored + in a context. If the profiler thread is doing work then all the other threads aren't and so the stored program + counters will be up-to-date. + The profiling thread gathers PC/PR pairs and how often that pairing appears. +*/ + +#pragma once + +void profiler_init(const char* output); +void profiler_start(); +void profiler_stop(); +void profiler_clean_up(); +bool profiler_recording(); diff --git a/src/miami/renderer/2dEffect.h b/src/miami/renderer/2dEffect.h new file mode 100644 index 00000000..8ad2b946 --- /dev/null +++ b/src/miami/renderer/2dEffect.h @@ -0,0 +1,99 @@ +#pragma once + +enum { + EFFECT_LIGHT, + EFFECT_PARTICLE, + EFFECT_ATTRACTOR, + EFFECT_PED_ATTRACTOR, + EFFECT_SUNGLARE +}; + +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_HIDE_OBJECT = 8, // hide the object instead of rendering light (???) + LIGHTFLAG_LONG_DIST = 16, + 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; + }; + struct PedAttractor { + CVector queueDir; + CVector useDir; + int8 type; + }; + + CVector pos; + CRGBA col; + uint8 type; + union { + Light light; + Particle particle; + Attractor attractor; + PedAttractor pedattr; + }; + + C2dEffect(void) {} + void Shutdown(void){ + if(type == EFFECT_LIGHT){ + if(light.corona) + RwTextureDestroy(light.corona); + light.corona = nil; + if(light.shadow) + RwTextureDestroy(light.shadow); + light.shadow = nil; + } + } +}; + +VALIDATE_SIZE(C2dEffect, 0x34); diff --git a/src/miami/renderer/Antennas.cpp b/src/miami/renderer/Antennas.cpp new file mode 100644 index 00000000..5e30aca2 --- /dev/null +++ b/src/miami/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/miami/renderer/Antennas.h b/src/miami/renderer/Antennas.h new file mode 100644 index 00000000..47cb1dad --- /dev/null +++ b/src/miami/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/miami/renderer/Clouds.cpp b/src/miami/renderer/Clouds.cpp new file mode 100644 index 00000000..7ef72b56 --- /dev/null +++ b/src/miami/renderer/Clouds.cpp @@ -0,0 +1,473 @@ +#include "common.h" + +#include "main.h" +#include "Sprite.h" +#include "Sprite2d.h" +#include "General.h" +#include "Game.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; +float CClouds::ms_HorizonTilt; +CRGBA CClouds::ms_colourTop; +CRGBA CClouds::ms_colourBottom; +CRGBA CClouds::ms_colourBkGrd; + +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]); + gpCloudTex[0] = nil; + RwTextureDestroy(gpCloudTex[1]); + gpCloudTex[1] = nil; + RwTextureDestroy(gpCloudTex[2]); + gpCloudTex[2] = nil; + RwTextureDestroy(gpCloudTex[3]); + gpCloudTex[3] = nil; + RwTextureDestroy(gpCloudTex[4]); + gpCloudTex[4] = nil; +} + +void +CClouds::Update(void) +{ + float s = Sin(TheCamera.Orientation - 0.85f); +#ifdef FIX_BUGS + CloudRotation += CWeather::Wind*s*0.001f*CTimer::GetTimeStepFix(); + IndividualRotation += (CWeather::Wind*CTimer::GetTimeStep()*0.5f + 0.3f*CTimer::GetTimeStepFix()) * 60.0f; +#else + CloudRotation += CWeather::Wind*s*0.001f; + IndividualRotation += (CWeather::Wind*CTimer::GetTimeStep()*0.5f + 0.3f) * 60.0f; +#endif +} + +float StarCoorsX[9] = { 0.0f, 0.05f, 0.13f, 0.4f, 0.7f, 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; + + if(!CGame::CanSeeOutSideFromCurrArea()) + return; + + PUSH_RENDERGROUP("CClouds::Render"); + + CCoronas::SunBlockedByClouds = false; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + CSprite::InitSpriteBuffer(); + + float minute = CClock::GetHours()*60 + CClock::GetMinutes() + CClock::GetSeconds()/60.0f; + RwV3d campos = TheCamera.GetPosition(); + + // Moon + float moonfadeout = Abs(minute - 180.0f); // fully visible at 3AM + if((int)moonfadeout < 180){ // fade in/out 3 hours + float coverage = Max(CWeather::Foggyness, CWeather::CloudCoverage); + int brightness = (1.0f - coverage) * (180 - (int)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])); + szx *= CCoronas::MoonSize*2.0f + 4.0f; + szy *= CCoronas::MoonSize*2.0f + 4.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(Max(CWeather::Foggyness, CWeather::CloudCoverage), CWeather::ExtraSunnyness); + 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 - Max(CWeather::Foggyness, CWeather::ExtraSunnyness)); + if(fluffyalpha != 0){ + static bool bCloudOnScreen[37]; + float sundist, 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)){ + 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(); + int distLimit = (3*SCREEN_WIDTH)/4; + if(sundist < distLimit){ + hilight = (1.0f - Max(CWeather::Foggyness, CWeather::CloudCoverage)) * (1.0f - sundist/(float)distLimit); + 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/65536.0f * 6.28f + ms_cameraRoll, //was: 65336 + 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)){ + if(sundist < SCREEN_WIDTH/3){ + 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 right = CrossProduct(TheCamera.GetUp(), TheCamera.GetForward()); + right.Normalise(); + float c = right.Magnitude2D(); + if(c > 1.0f) + c = 1.0f; + ms_cameraRoll = Acos(c); + if(right.z < 0.0f) + ms_cameraRoll = -ms_cameraRoll; + + ms_HorizonTilt = SCREEN_WIDTH/2.0f * Tan(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, DRAW_FAR); + }else{ + ms_horizonZ = CSprite::CalcHorizonCoors(); + + int fogr = (topred + 2 * botred) / 3; + int fogg = (topgreen + 2 * botgreen) / 3; + int fogb = (topblue + 2 * botblue) / 3; + + // Draw top/bottom gradient + float gradheight = SCREEN_HEIGHT/2.0f; + + 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; + + float botright = ms_horizonZ - ms_HorizonTilt; + float botleft = ms_horizonZ + ms_HorizonTilt; + float topright = botright - gradheight; + float topleft = botleft - gradheight; + + CSprite2d::DrawAnyRect(0.0f, topleft, SCREEN_WIDTH, topright, 0.0f, botleft, SCREEN_WIDTH, botright, + ms_colourTop, ms_colourTop, ms_colourBottom, ms_colourBottom, DRAW_FAR); + + // draw the small stripe (whatever it's supposed to be) + ms_colourTop.r = fogr; + ms_colourTop.g = fogg; + ms_colourTop.b = fogb; + ms_colourTop.a = alpha; + topright = ms_horizonZ - ms_HorizonTilt; + topleft = ms_horizonZ + ms_HorizonTilt; + botright = topright + SMALLSTRIPHEIGHT; + botleft = topleft + SMALLSTRIPHEIGHT; + CSprite2d::DrawAnyRect(0.0f, topleft, SCREEN_WIDTH, topright, 0.0f, botleft, SCREEN_WIDTH, botright, + ms_colourTop, ms_colourTop, ms_colourTop, ms_colourTop, DRAW_FAR); + + // Only top + if(ms_horizonZ + ms_HorizonTilt - gradheight > 0.0f || + ms_horizonZ - ms_HorizonTilt - gradheight > 0.0f){ + ms_colourTop.r = topred; + ms_colourTop.g = topgreen; + ms_colourTop.b = topblue; + ms_colourTop.a = alpha; + + if(ms_horizonZ - Abs(ms_HorizonTilt) - gradheight > SCREEN_HEIGHT){ + // only top is visible + topleft = 0.0f; + topright = 0.0f; + botleft = SCREEN_HEIGHT; + botright = SCREEN_HEIGHT; + }else{ + botright = ms_horizonZ - ms_HorizonTilt - gradheight; + botleft = ms_horizonZ + ms_HorizonTilt - gradheight; + topright = Min(ms_horizonZ - ms_HorizonTilt - 2*SCREEN_HEIGHT, 0.0f); + topleft = Min(ms_horizonZ + ms_HorizonTilt - 2*SCREEN_HEIGHT, 0.0f); + } + + CSprite2d::DrawAnyRect(0.0f, topleft, SCREEN_WIDTH, topright, 0.0f, botleft, SCREEN_WIDTH, botright, + ms_colourTop, ms_colourTop, ms_colourTop, ms_colourTop, DRAW_FAR); + } + + // Set both to fog colour for RenderHorizon + ms_colourTop.r = fogr; + ms_colourTop.g = fogg; + ms_colourTop.b = fogb; + ms_colourBottom.r = fogr; + ms_colourBottom.g = fogg; + ms_colourBottom.b = fogb; + } + + POP_RENDERGROUP(); +} + +void +CClouds::RenderHorizon(void) +{ + if(UseDarkBackground()) + return; + + PUSH_RENDERGROUP("CClouds::RenderHorizon"); + + ms_colourBottom.a = 230; + ms_colourTop.a = 80; + + float topright = ms_horizonZ - ms_HorizonTilt; + float topleft = ms_horizonZ + ms_HorizonTilt; + float botright = topright + SMALLSTRIPHEIGHT; + float botleft = topleft + SMALLSTRIPHEIGHT; + + CSprite2d::DrawAnyRect(0.0f, topleft, SCREEN_WIDTH, topright, 0.0f, botleft, SCREEN_WIDTH, botright, + ms_colourTop, ms_colourTop, ms_colourBottom, ms_colourBottom, DRAW_FAR); + + + ms_colourBkGrd.r = 128.0f*CTimeCycle::GetAmbientRed(); + ms_colourBkGrd.g = 128.0f*CTimeCycle::GetAmbientGreen(); + ms_colourBkGrd.b = 128.0f*CTimeCycle::GetAmbientBlue(); + ms_colourBkGrd.a = 255; + + float horzstrip = SCREEN_STRETCH_Y(HORIZSTRIPHEIGHT); + topright = botright; + topleft = botleft; + botright = topright + horzstrip; + botleft = topleft + horzstrip; + + CSprite2d::DrawAnyRect(0.0f, topleft, SCREEN_WIDTH, topright, 0.0f, botleft, SCREEN_WIDTH, botright, + ms_colourBottom, ms_colourBottom, ms_colourBkGrd, ms_colourBkGrd, DRAW_FAR); + + + topright = botright; + topleft = botleft; + botright = Max(topright, SCREEN_HEIGHT); + botleft = Max(topleft, SCREEN_HEIGHT); + + CSprite2d::DrawAnyRect(0.0f, topleft, SCREEN_WIDTH, topright, 0.0f, botleft, SCREEN_WIDTH, botright, + ms_colourBkGrd, ms_colourBkGrd, ms_colourBkGrd, ms_colourBkGrd, DRAW_FAR); + + POP_RENDERGROUP(); +} diff --git a/src/miami/renderer/Clouds.h b/src/miami/renderer/Clouds.h new file mode 100644 index 00000000..ef33030b --- /dev/null +++ b/src/miami/renderer/Clouds.h @@ -0,0 +1,23 @@ +#pragma once + +class CClouds +{ +public: + static float CloudRotation; + static uint32 IndividualRotation; + + static float ms_cameraRoll; + static float ms_horizonZ; + static float ms_HorizonTilt; + static CRGBA ms_colourTop; + static CRGBA ms_colourBottom; + static CRGBA ms_colourBkGrd; + + 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/miami/renderer/Console.cpp b/src/miami/renderer/Console.cpp new file mode 100644 index 00000000..244bfb17 --- /dev/null +++ b/src/miami/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_STANDARD); +#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/miami/renderer/Console.h b/src/miami/renderer/Console.h new file mode 100644 index 00000000..9f22236f --- /dev/null +++ b/src/miami/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/miami/renderer/Coronas.cpp b/src/miami/renderer/Coronas.cpp new file mode 100644 index 00000000..d9bf88d1 --- /dev/null +++ b/src/miami/renderer/Coronas.cpp @@ -0,0 +1,959 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Entity.h" +#include "RenderBuffer.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; +int CCoronas::MoonSize; +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, + bool useNearDist, float nearDist) +{ + int i; + + if(sq(drawDist) < (TheCamera.GetPosition() - coors).MagnitudeSqr2D()) + return; + + if(useNearDist){ + float dist = (TheCamera.GetPosition() - coors).Magnitude(); + if(dist < 35.0f) + return; + if(dist < 50.0f) + alpha *= (dist - 35.0f)/(50.0f - 35.0f); + } + + 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; + aCoronas[i].useNearDist = useNearDist; + aCoronas[i].nearDist = nearDist; +} + +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, + bool useNearDist, float nearDist) +{ + RegisterCorona(id, red, green, blue, alpha, coors, size, drawDist, + gpCoronaTexture[type], flareType, reflection, LOScheck, drawStreak, someAngle, + useNearDist, nearDist); +} + +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 -= aCoronas[i].nearDist; + + 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*)FALSE); + 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"); + + CSprite::InitSpriteBuffer(); + + 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::RenderSunReflection(void) +{ + float sunZDir = CTimeCycle::GetSunDirection().z; + if(sunZDir > -0.05f){ + float intensity = (0.3f - Abs(sunZDir - 0.25f))/0.3f * + (1.0f - CWeather::CloudCoverage) * + (1.0f - CWeather::Foggyness) * + (1.0f - CWeather::Wind); + if(intensity > 0.0f){ + int r = (CTimeCycle::GetSunCoreRed() + CTimeCycle::GetSunCoronaRed())*intensity*0.25f; + int g = (CTimeCycle::GetSunCoreGreen() + CTimeCycle::GetSunCoronaGreen())*intensity*0.25f; + int b = (CTimeCycle::GetSunCoreBlue() + CTimeCycle::GetSunCoronaBlue())*intensity*0.25f; + + CVector sunPos = 40.0f*CTimeCycle::GetSunDirection() + TheCamera.GetPosition(); + sunPos.z = 0.5f*CWeather::Wind + 6.1f; + CVector sunDir = CTimeCycle::GetSunDirection(); + sunDir.z = 0.0; + sunDir.Normalise(); + + TempBufferIndicesStored = 6; + TempBufferRenderIndexList[0] = 2; + TempBufferRenderIndexList[1] = 1; + TempBufferRenderIndexList[2] = 0; + TempBufferRenderIndexList[3] = 2; + TempBufferRenderIndexList[4] = 3; + TempBufferRenderIndexList[5] = 1; + + // 60 unit square in sun direction + TempBufferVerticesStored = 4; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[0], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[0], + sunPos.x + 30.0f*sunDir.y, + sunPos.y - 30.0f*sunDir.x, + sunPos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[1], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[1], + sunPos.x - 30.0f*sunDir.y, + sunPos.y + 30.0f*sunDir.x, + sunPos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[2], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[2], + sunPos.x + 60.0f*sunDir.x + 30.0f*sunDir.y, + sunPos.y + 60.0f*sunDir.y - 30.0f*sunDir.x, + sunPos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[3], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[3], + sunPos.x + 60.0f*sunDir.x - 30.0f*sunDir.y, + sunPos.y + 60.0f*sunDir.y + 30.0f*sunDir.x, + sunPos.z); + + RwIm3DVertexSetU(&TempBufferRenderVertices[0], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[0], 1.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[1], 1.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[1], 1.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[2], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[2], 0.5f); + RwIm3DVertexSetU(&TempBufferRenderVertices[3], 1.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[3], 0.5f); + + int timeInc = 0; + int sideInc = 0; + int fwdInc = 0; + for(int i = 0; i < 20; i++){ + TempBufferRenderIndexList[TempBufferIndicesStored + 0] = TempBufferVerticesStored; + TempBufferRenderIndexList[TempBufferIndicesStored + 1] = TempBufferVerticesStored-1; + TempBufferRenderIndexList[TempBufferIndicesStored + 2] = TempBufferVerticesStored-2; + TempBufferRenderIndexList[TempBufferIndicesStored + 3] = TempBufferVerticesStored; + TempBufferRenderIndexList[TempBufferIndicesStored + 4] = TempBufferVerticesStored+1; + TempBufferRenderIndexList[TempBufferIndicesStored + 5] = TempBufferVerticesStored-1; + TempBufferIndicesStored += 6; + + // What a weird way to do it... + float fwdLen = fwdInc/20 + 60; + float sideLen = sideInc/20 + 30; + sideLen += 10.0f*Sin((float)(CTimer::GetTimeInMilliseconds()+timeInc & 0x7FF)/0x800*TWOPI); + timeInc += 900; + sideInc += 970; + fwdInc += 1440; + + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+0], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+0], + sunPos.x + fwdLen*sunDir.x + sideLen*sunDir.y, + sunPos.y + fwdLen*sunDir.y - sideLen*sunDir.x, + sunPos.z); + + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+1], r, g, b, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+1], + sunPos.x + fwdLen*sunDir.x - sideLen*sunDir.y, + sunPos.y + fwdLen*sunDir.y + sideLen*sunDir.x, + sunPos.z); + + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored+0], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored+0], 0.5f); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored+1], 1.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored+1], 0.5f); + TempBufferVerticesStored += 2; + } + + + 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(gpCoronaTexture[4])); + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){ + 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 +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 && !CGame::IsInInterior()) + 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); + + switch(effect->type){ + case EFFECT_LIGHT: + 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; + } + + if(effect->light.flags & LIGHTFLAG_HIDE_OBJECT){ + if(lightOn) + bDoNotRender = false; + else + bDoNotRender = true; + return; + } + + // 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, + !!(effect->light.flags&LIGHTFLAG_LONG_DIST)); + 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, + !!(effect->light.flags&LIGHTFLAG_LONG_DIST)); + + // Pointlight + bool alreadyProcessedFog; + alreadyProcessedFog = false; + if(effect->light.range != 0.0f && lightOn){ + 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, + (effect->light.flags & LIGHTFLAG_FOG) >> 1, + true); + alreadyProcessedFog = true; + } + } + + if(!alreadyProcessedFog){ + if(effect->light.flags & LIGHTFLAG_FOG_ALWAYS){ + CPointLights::AddLight(CPointLights::LIGHT_FOGONLY_ALWAYS, + pos, CVector(0.0f, 0.0f, 0.0f), + 0.0f, + 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), + 0.0f, + effect->col.r/255.0f, effect->col.g/255.0f, effect->col.b/255.0f, + CPointLights::FOG_NORMAL, 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); + } + } + break; + + case EFFECT_SUNGLARE: + if(CWeather::SunGlare >= 0.0f){ + CVector pos = GetMatrix() * effect->pos; + CVector glareDir = pos - GetPosition(); + glareDir.Normalise(); + CVector camDir = TheCamera.GetPosition() - pos; + float dist = camDir.Magnitude(); + camDir *= 2.0f/dist; + glareDir += camDir; + glareDir.Normalise(); + float camAngle = -DotProduct(glareDir, CTimeCycle::GetSunDirection()); + if(camAngle > 0.0f){ + float intens = Sqrt(camAngle) * CWeather::SunGlare; + pos += camDir; + CCoronas::RegisterCorona((uintptr)this + 33 + i, + intens * (CTimeCycle::GetSunCoreRed() + 2*255)/3.0f, + intens * (CTimeCycle::GetSunCoreGreen() + 2*255)/3.0f, + intens * (CTimeCycle::GetSunCoreBlue() + 2*255)/3.0f, + 255, + pos, 0.5f*CWeather::SunGlare*Sqrt(dist), 120.0f, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, + CCoronas::STREAK_OFF, 0.0f); + } + } + break; + } + } +} \ No newline at end of file diff --git a/src/miami/renderer/Coronas.h b/src/miami/renderer/Coronas.h new file mode 100644 index 00000000..45f027d8 --- /dev/null +++ b/src/miami/renderer/Coronas.h @@ -0,0 +1,105 @@ +#pragma once + +extern RwTexture *gpCoronaTexture[9]; + +struct CRegisteredCorona +{ + CVector coors; + uint32 id; + uint32 lastLOScheck; + RwTexture *texture; + float size; + float someAngle; + float drawDist; + float nearDist; + float heightAboveRoad; + uint8 red; + uint8 green; + uint8 blue; + uint8 alpha; // alpha when fully visible + uint8 fadeAlpha; // actual value used for rendering, faded + bool registeredThisFrame; + int8 flareType; + int8 reflection; + + uint8 LOScheck : 1; + uint8 offScreen : 1; + uint8 firstUpdate : 1; + uint8 drawStreak : 1; + uint8 sightClear : 1; + uint8 useNearDist : 1; + uint8 renderReflection : 1; + + int16 prevX[6]; + int16 prevY[6]; + uint8 prevRed[6]; + uint8 prevGreen[6]; + uint8 prevBlue[6]; + bool hasValue[6]; + + void Update(void); +}; + +VALIDATE_SIZE(CRegisteredCorona, 0x68); + +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 int MoonSize; + 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, + bool useNearDist = false, float nearDist = 1.5f); + 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, + bool useNearDist = false, float nearDist = 1.5f); + static void UpdateCoronaCoors(uint32 id, const CVector &coors, float drawDist, float someAngle); + static void Render(void); + static void RenderReflections(void); + static void RenderSunReflection(void); + static void DoSunAndMoon(void); +}; diff --git a/src/miami/renderer/Credits.cpp b/src/miami/renderer/Credits.cpp new file mode 100644 index 00000000..81e76625 --- /dev/null +++ b/src/miami/renderer/Credits.cpp @@ -0,0 +1,820 @@ +#include "common.h" + +#include "Timer.h" +#include "Font.h" +#include "Frontend.h" +#include "RwHelper.h" +#include "Camera.h" +#include "Text.h" +#include "Credits.h" +#include "Pad.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) +{ + CPad::UpdatePads(); + if (CPad::GetPad(0)->GetCrossJustDown()) + bCreditsGoing = false; + else { + float start = DEFAULT_SCREEN_HEIGHT + 20.0f; + float y = lineoffset + start - scrolloffset; + if (y > 20.0f && DEFAULT_SCREEN_HEIGHT - 20.0f > y) { + CFont::SetScale(SCREEN_SCALE_X(scaleX), SCREEN_SCALE_Y(scaleY)); + CFont::SetColor(CRGBA(0, 0, 0, 255)); + CFont::PrintString(SCREEN_WIDTH / 2.0f, SCREEN_SCALE_Y(y), (uint16*)text); + CFont::SetColor(CRGBA(220, 220, 220, 220)); + CFont::PrintString(SCREEN_WIDTH / 2.0f - SCREEN_SCALE_X(1.0f), SCREEN_SCALE_Y(y - 1.0f), (uint16*)text); + } + 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(); + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.75f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_STANDARD); + + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED001"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED002"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED003"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED004"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED005"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED006"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED007"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED008"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED025"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED026"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED027"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED028"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED029"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED030"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED031"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD031A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD031B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD031C"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD031D"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD031E"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD024A"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED024"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED023"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD023A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD023B"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED018"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED019"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD018A"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD019A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD019B"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED020"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED021"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD022A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED022"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD022B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD022C"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED032"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED033"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD032A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED034"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED035"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED036"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED037"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD037A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD037B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD037C"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD041B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED042"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED039"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED044"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED040"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD042A"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED142"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD142A"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED009"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED010"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED011"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED012"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED013"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD013A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD013B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD013C"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED089"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED090"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED347"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED047"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED048"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED049"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED348"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED050"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED051"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED052"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED053"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED054"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED055"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED056"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD056A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD056B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD056C"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD056D"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED349"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED350"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED351"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED352"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED353"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED354"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED355"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED356"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED357"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED359"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED360"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED361"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED362"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED363"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED364"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED365"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED366"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED367"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED368"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED369"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED370"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED371"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED372"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED373"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED256"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED257"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED258"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED057"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED058"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD057A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED059"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD060A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD060B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD060C"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD002A"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED003"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD001A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD001B"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED060"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED061"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED062"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED063"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED064"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED069"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED070"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED065"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED066"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED067"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED068"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD071A"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD072A"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED091"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED094"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED095"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED097"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED098"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD098A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD098B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD098C"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED099"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED096"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED273"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD092A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED092"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD092B"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED073"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED074"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED076"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED075"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED077"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED078"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED081"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED082"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED079"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED080"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED083"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED084"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD084A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD084B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD084C"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD084D"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD084E"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED085"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED086"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD086A"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED087"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED088"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD088A"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD088B"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD088C"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD088D"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD088E"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD088F"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD088G"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED107"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED108"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED109"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED110"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD110A"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED111"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED112"), lineoffset, scrolloffset); + + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED113"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED114"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED115"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED116"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED117"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED118"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED119"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED120"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED121"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED122"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED123"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED124"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED125"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED126"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED127"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED128"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED129"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.8f)); + + PrintCreditText(1.1f, 0.8f, TheText.Get("CRD111A"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED130"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED131"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED132"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED133"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED134"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134A"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134B"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134C"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134D"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134E"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134F"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134G"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134H"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD134I"), lineoffset, scrolloffset); + + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.7f)); + + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED135"), lineoffset, scrolloffset); + + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD136A"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD137A"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED138"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED066"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD138B"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED139"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(1.1f, 0.8f, TheText.Get("CRED140"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_FRENCH) + PrintCreditSpace(0.5f, lineoffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140A"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140B"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140C"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140D"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140E"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140F"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140G"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140H"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140I"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140J"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140K"), lineoffset, scrolloffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD140L"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.85f)); + + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED259"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED260"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED261"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED262"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED263"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED264"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED265"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED266"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED141"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD141A"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD141B"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED143"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED144"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED145"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED146"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED147"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED148"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED149"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED150"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED151"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED152"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED153"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED154"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED155"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED156"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED157"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED158"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED159"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED160"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED161"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED162"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED163"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED164"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED165"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED166"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED167"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED168"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED169"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED170"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED171"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED172"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.75f)); + + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED217"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED218"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRD218A"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED219"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED220"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED221"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED222"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED223"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(1.1f, 1.1f, TheText.Get("CRED224"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED227"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED228"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED229"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD229A"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD229B"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED274"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED275"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED276"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED277"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED278"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED279"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED280"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED281"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED282"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED283"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED284"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED285"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED286"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED287"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED288"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED289"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED290"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED291"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED292"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED293"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED294"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED295"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED296"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED297"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED298"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED299"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED300"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED301"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED302"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED303"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED304"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED305"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED306"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED307"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED308"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED309"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED310"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED314"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED315"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED316"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED317"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED318"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED319"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED320"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED321"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED322"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED323"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED324"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED325"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED326"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED327"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED328"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED329"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED330"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED331"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED332"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.8f)); + + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED333"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED334"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED335"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED336"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED337"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED338"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED339"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED340"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED341"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED342"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD344A"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED344"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED345"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRD345A"), lineoffset, scrolloffset); + PrintCreditSpace(1.0f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED346"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(1.0f, lineoffset); + + PrintCreditSpace(1.5f, lineoffset); + + CFont::SetCentreSize(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH * 0.75f)); + + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED267"), lineoffset, scrolloffset); + PrintCreditSpace(0.5f, lineoffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED268"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED269"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED270"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED271"), lineoffset, scrolloffset); + PrintCreditText(0.65f, 0.65f, TheText.Get("CRED272"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditText(0.95f, 0.7f, TheText.Get("CRED230"), lineoffset, scrolloffset); + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_ITALIAN) + PrintCreditSpace(0.5f, lineoffset); + + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED231"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED232"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED233"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED234"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED235"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED236"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED237"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED238"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED239"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED240"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED241"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED242"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED243"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED244"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED245"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED246"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED247"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED248"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED249"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED358"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED250"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED251"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED252"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRD251A"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRD252A"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED253"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED254"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED374"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED375"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED376"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED377"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED378"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED379"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED380"), lineoffset, scrolloffset); + PrintCreditText(0.95f, 0.95f, TheText.Get("CRED381"), lineoffset, scrolloffset); + PrintCreditSpace(1.5f, lineoffset); + PrintCreditSpace(1.5f, lineoffset); + CFont::DrawFonts(); +#ifdef CUTSCENE_BORDERS_SWITCH + if (CMenuManager::m_PrefsCutsceneBorders) +#endif + 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/miami/renderer/Credits.h b/src/miami/renderer/Credits.h new file mode 100644 index 00000000..e049ce76 --- /dev/null +++ b/src/miami/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/miami/renderer/CutsceneShadow.cpp b/src/miami/renderer/CutsceneShadow.cpp new file mode 100644 index 00000000..8cb33896 --- /dev/null +++ b/src/miami/renderer/CutsceneShadow.cpp @@ -0,0 +1,269 @@ +#include "common.h" +#include "main.h" +#include "rwcore.h" +#include "rwplcore.h" +#include "CutsceneShadow.h" +#include "RwHelper.h" + +#define DLIGHT_VALUE 0.8f /* Directional light intensity */ + + +CCutsceneShadow::CCutsceneShadow() +{ + m_pAtomic = nil; + m_nRwObjectType = -1; + m_pLight = nil; + m_nBlurPasses = 0; + m_bResample = false; + m_bGradient = false; +} + +CCutsceneShadow::~CCutsceneShadow() +{ + Destroy(); +} + +bool +CCutsceneShadow::Create(RwObject *object, int32 rasterSize, bool resample, int32 blurPasses, bool gradient) +{ + ASSERT(object != nil); + + RwRGBAReal color; + RwFrame *frame; + + if (!object) + return false; + + m_pLight = RpLightCreate(rpLIGHTDIRECTIONAL); + ASSERT(m_pLight != nil); + + if (!m_pLight) + return false; + + color.red = color.green = color.blue = DLIGHT_VALUE; + color.alpha = 0.0f; + + RpLightSetColor(m_pLight, &color); + + frame = RwFrameCreate(); + ASSERT(frame != nil); + + RpLightSetFrame(m_pLight, frame); + + SetLightProperties(180.0f, 90.0f, false); + + m_pObject = object; + m_nRwObjectType = RwObjectGetType(m_pObject); + + switch ( m_nRwObjectType ) + { + case rpCLUMP: + { + RpClumpGetBoundingSphere(m_pClump, &m_BoundingSphere, 1); + m_BaseSphere.radius = m_BoundingSphere.radius; + RwV3dTransformPoints(&m_BaseSphere.center, &m_BoundingSphere.center, 1, RwFrameGetMatrix(RpClumpGetFrame(m_pClump))); + break; + } + + case rpATOMIC: + { + m_BoundingSphere = *RpAtomicGetBoundingSphere(m_pAtomic); + m_BaseSphere.radius = m_BoundingSphere.radius; + RwV3dTransformPoints(&m_BaseSphere.center, &m_BoundingSphere.center, 1, RwFrameGetMatrix(RpAtomicGetFrame(m_pAtomic))); + break; + } + + default: + { + Destroy(); + return false; + break; + } + } + + if ( !m_Camera.Create(rasterSize) ) + { + Destroy(); + return false; + } + + m_nBlurPasses = blurPasses; + m_bResample = resample; + m_bGradient = gradient; + + if ( m_bResample && !m_ResampleCamera.Create(rasterSize - 1) ) + { + Destroy(); + return false; + } + + if ( m_nBlurPasses != 0 ) + { + if ( !m_BlurCamera.Create(resample ? rasterSize - 1 : rasterSize) ) + { + Destroy(); + return false; + } + } + + if ( m_bGradient ) + { + if ( !m_GradientCamera.Create(resample ? rasterSize - 1 : rasterSize) ) + { + Destroy(); + return false; + } + + m_GradientCamera.MakeGradientRaster(); + } + + m_Camera.SetLight(m_pLight); + + switch ( m_nRwObjectType ) + { + case rpATOMIC: + m_Camera.SetFrustum(1.1f * m_BoundingSphere.radius); + break; + + case rpCLUMP: + m_Camera.SetFrustum(1.1f * m_BoundingSphere.radius); + break; + } + + m_Camera.SetCenter(&m_BaseSphere.center); + return true; +} + +RwFrame * +CCutsceneShadow::SetLightProperties(float angleY, float angleX, bool setLight) +{ + ASSERT(m_pLight != nil); + + RwFrame *frame; + static RwV3d Xaxis = { 1.0f, 0.0f, 0.0f }; + static RwV3d Yaxis = { 0.0f, 1.0f, 0.0f }; + + frame = RpLightGetFrame(m_pLight); + ASSERT(frame != nil); + + if ( !frame ) + return nil; + + RwFrameRotate(frame, &Yaxis, angleY, rwCOMBINEREPLACE); + RwFrameRotate(frame, &Xaxis, angleX, rwCOMBINEPOSTCONCAT); + + if ( setLight ) + m_Camera.SetLight(m_pLight); + + return frame; +} + +bool +CCutsceneShadow::IsInitialized() +{ + return m_pObject != nil; +} + +void +CCutsceneShadow::Destroy() +{ + m_Camera.Destroy(); + m_ResampleCamera.Destroy(); + m_BlurCamera.Destroy(); + m_GradientCamera.Destroy(); + + m_pAtomic = nil; + + m_nRwObjectType = -1; + + if (m_pLight) + { + RwFrame *frame = RpLightGetFrame(m_pLight); + RpLightSetFrame(m_pLight, nil); + RwFrameDestroy(frame); + RpLightDestroy(m_pLight); + m_pLight = nil; + } +} + +RwRaster * +CCutsceneShadow::Update() +{ + switch ( m_nRwObjectType ) + { + case rpCLUMP: + ASSERT(m_pClump != nil); + RwV3dTransformPoints(&m_BaseSphere.center, &m_BoundingSphere.center, 1, RwFrameGetMatrix(RpClumpGetFrame(m_pClump))); + break; + + case rpATOMIC: + ASSERT(m_pAtomic != nil); + RwV3dTransformPoints(&m_BaseSphere.center, &m_BoundingSphere.center, 1, RwFrameGetMatrix(RpAtomicGetFrame(m_pAtomic))); + break; + } + + m_Camera.SetCenter(&m_BaseSphere.center); + + switch ( m_nRwObjectType ) + { + case rpCLUMP: + m_Camera.Update(m_pClump); + break; + + case rpATOMIC: + m_Camera.Update(m_pAtomic); + break; + } + + RwRaster *raster = m_Camera.GetRwRenderRaster(); + ASSERT(raster != nil); + + if ( m_bResample ) + return m_ResampleCamera.RasterResample(raster); + + if ( m_nBlurPasses ) + return m_BlurCamera.RasterBlur(raster, m_nBlurPasses); + + if ( m_bGradient ) + return m_GradientCamera.RasterGradient(raster); + + return raster; +} + +RwTexture * +CCutsceneShadow::UpdateForCutscene() +{ + Update(); + return GetShadowRwTexture(); +} + +CShadowCamera * +CCutsceneShadow::GetShadowCamera(int32 camType) +{ + switch ( camType ) + { + case RESAMPLE: return &m_ResampleCamera; + case BLUR: return &m_BlurCamera; + case GRADIENT: return &m_GradientCamera; + } + + return &m_Camera; +} + +RwTexture * +CCutsceneShadow::GetShadowRwTexture() +{ + if ( m_bResample ) + return m_ResampleCamera.GetRwRenderTexture(); + else + return m_Camera.GetRwRenderTexture(); +} + +void +CCutsceneShadow::DrawBorderAroundTexture(RwRGBA const& color) +{ + if ( m_bResample ) + m_ResampleCamera.DrawOutlineBorder(color); + else + m_Camera.DrawOutlineBorder(color); +} \ No newline at end of file diff --git a/src/miami/renderer/CutsceneShadow.h b/src/miami/renderer/CutsceneShadow.h new file mode 100644 index 00000000..a59fe78f --- /dev/null +++ b/src/miami/renderer/CutsceneShadow.h @@ -0,0 +1,52 @@ +#pragma once +#include "ShadowCamera.h" + +class CCutsceneShadow +{ +public: + enum + { + RASTER = 0, + RESAMPLE, + BLUR, + GRADIENT, + }; + + CShadowCamera m_Camera; + bool m_bResample; + CShadowCamera m_ResampleCamera; + int32 m_nBlurPasses; + CShadowCamera m_BlurCamera; + bool m_bGradient; + CShadowCamera m_GradientCamera; + + union + { + RwObject *m_pObject; + RpAtomic *m_pAtomic; + RpClump *m_pClump; + }; + + int32 m_nRwObjectType; + RpLight *m_pLight; + RwSphere m_BoundingSphere; + RwSphere m_BaseSphere; + + CCutsceneShadow(); + ~CCutsceneShadow(); + + RwSphere GetBaseSphere() + { + return m_BaseSphere; + } + + bool Create(RwObject *object, int32 rasterSize, bool resample, int32 blurPasses, bool gradient); + RwFrame *SetLightProperties(float angleY, float angleX, bool setLight); + bool IsInitialized(); + void Destroy(); + RwRaster *Update(); + RwTexture *UpdateForCutscene(); + CShadowCamera *GetShadowCamera(int32 camType = RASTER); + RwTexture *GetShadowRwTexture(); + void DrawBorderAroundTexture(RwRGBA const& color); +}; diff --git a/src/miami/renderer/Draw.cpp b/src/miami/renderer/Draw.cpp new file mode 100644 index 00000000..a5e7504b --- /dev/null +++ b/src/miami/renderer/Draw.cpp @@ -0,0 +1,113 @@ +#include "common.h" + +#include "Draw.h" +#include "Frontend.h" +#include "Camera.h" +#include "CutsceneMgr.h" + +float CDraw::ms_fAspectRatio = DEFAULT_ASPECT_RATIO; +#ifdef ASPECT_RATIO_SCALE +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 + +#ifdef ASPECT_RATIO_SCALE +float +FindAspectRatio(void) +{ + 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 + +float +CDraw::CalculateAspectRatio(void) +{ +#ifdef ASPECT_RATIO_SCALE + if (TheCamera.m_WideScreenOn) + CDraw::ms_fAspectRatio = (5.f / 3.f) * FindAspectRatio() / (16.f / 9.f); // It's used on theatrical showings according to Wiki + else + CDraw::ms_fAspectRatio = FindAspectRatio(); +#else + if(FrontEndMenuManager.m_PrefsUseWideScreen) { + if (TheCamera.m_WideScreenOn) + CDraw::ms_fAspectRatio = 5.f / 3.f; // It's used on theatrical showings according to Wiki + else + CDraw::ms_fAspectRatio = 16.f / 9.f; + } else if (TheCamera.m_WideScreenOn) { + CDraw::ms_fAspectRatio = 5.f/4.f; + } else { + CDraw::ms_fAspectRatio = 4.f/3.f; + } +#endif + return CDraw::ms_fAspectRatio; +} + +#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/miami/renderer/Draw.h b/src/miami/renderer/Draw.h new file mode 100644 index 00000000..b96fa813 --- /dev/null +++ b/src/miami/renderer/Draw.h @@ -0,0 +1,70 @@ +#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; + // we use this variable to scale a lot of 2D elements + // so better cache it + static float ms_fAspectRatio; +#ifdef ASPECT_RATIO_SCALE + // 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 CalculateAspectRatio(void); +#ifdef ASPECT_RATIO_SCALE + static float ConvertFOV(float fov); +#endif + static float GetAspectRatio(void) { return ms_fAspectRatio; } + static void SetAspectRatio(float ratio) { ms_fAspectRatio = ratio; } +#ifdef PROPER_SCALING + static float ScaleY(float y); +#endif +}; diff --git a/src/miami/renderer/Fluff.cpp b/src/miami/renderer/Fluff.cpp new file mode 100644 index 00000000..7574a16d --- /dev/null +++ b/src/miami/renderer/Fluff.cpp @@ -0,0 +1,1364 @@ +#include "common.h" +#include "main.h" + +#include "RenderBuffer.h" +#include "Entity.h" +#include "Fluff.h" +#include "Camera.h" +#include "Sprite.h" +#include "Coronas.h" +#include "PointLights.h" +#include "Rubbish.h" +#include "Timecycle.h" +#include "General.h" +#include "Timer.h" +#include "Clock.h" +#include "Weather.h" +#include "Stats.h" +#include "maths.h" +#include "Frontend.h" +#include "CutsceneMgr.h" +#include "PlayerPed.h" +#include "Bones.h" +#include "World.h" +#include "Replay.h" +#include "Coronas.h" +#include "SaveBuf.h" + +#ifdef COMPATIBLE_SAVES +#define SCRIPTPATHS_SAVE_SIZE 0x9C +#else +#define SCRIPTPATHS_SAVE_SIZE sizeof(aArray) +#endif + +CPlaneTrail CPlaneTrails::aArray[6]; +RwImVertexIndex TrailIndices[32] = { + 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16 +}; + +void +CPlaneTrail::Init(void) +{ + int i; + for(i = 0; i < ARRAY_SIZE(m_time); i++) + m_time[i] = 0; +} + +void +CPlaneTrail::Render(float visibility) +{ + int i; + int numVerts = 0; + if(!TheCamera.IsSphereVisible(m_pos[0], 1000.0f)) + return; + + int alpha = visibility*110.0f; + if(alpha == 0) + return; + + for(i = 0; i < ARRAY_SIZE(m_pos); i++){ + int32 time = CTimer::GetTimeInMilliseconds() - m_time[i]; + if(time > 30000) + m_time[i] = 0; + if(m_time[i] != 0){ + float fade = (30000.0f - time) / 10000.0f; + fade = Min(fade, 1.0f); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[numVerts], 255, 255, 255, (int)(alpha*fade)); + RwIm3DVertexSetPos(&TempBufferRenderVertices[numVerts], m_pos[i].x, m_pos[i].y, m_pos[i].z); + numVerts++; + } + } + if(numVerts > 1){ + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + if(RwIm3DTransform(TempBufferRenderVertices, numVerts, nil, rwIM3D_VERTEXXYZ|rwIM3D_VERTEXRGBA)){ + RwIm3DRenderIndexedPrimitive(rwPRIMTYPELINELIST, TrailIndices, (numVerts-1)*2); + RwIm3DEnd(); + } + } +} + +void +CPlaneTrail::RegisterPoint(CVector pos) +{ + int i; + bool bNewPoint = false; + if(m_time[0] != 0 && CTimer::GetTimeInMilliseconds() - m_time[0] > 2000){ + bNewPoint = true; + for(i = ARRAY_SIZE(m_pos)-1; i > 0; i--){ + m_pos[i] = m_pos[i-1]; + m_time[i] = m_time[i-1]; + } + } + m_pos[0] = pos; + if(bNewPoint || m_time[0] == 0) + m_time[0] = CTimer::GetTimeInMilliseconds(); +} + +void +CPlaneTrails::Init(void) +{ + int i; + for(i = 0; i < ARRAY_SIZE(aArray); i++) + aArray[i].Init(); +} + +void +CPlaneTrails::Update(void) +{ + CVector planePos; + + planePos.x = 1590.0f * Sin((float)(CTimer::GetTimeInMilliseconds() & 0x1FFFF)/0x20000 * TWOPI); + planePos.y = 1200.0f * Cos((float)(CTimer::GetTimeInMilliseconds() & 0x1FFFF)/0x20000 * TWOPI); + planePos.z = 550.0f; + RegisterPoint(planePos, 3); + if(CClock::GetHours() > 22 || CClock::GetHours() < 7){ + if(CTimer::GetTimeInMilliseconds() & 0x200) + CCoronas::RegisterCorona(101, 255, 0, 0, 255, planePos, 5.0f, 2000.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::UpdateCoronaCoors(101, planePos, 2000.0f, 0.0f); + } + + planePos.x = 1000.0f * Sin((float)(CTimer::GetTimeInMilliseconds() & 0x1FFFF)/0x20000 * TWOPI); + planePos.y = -1600.0f * Cos((float)(CTimer::GetTimeInMilliseconds() & 0x1FFFF)/0x20000 * TWOPI); + planePos.z = 500.0f; + RegisterPoint(planePos, 4); + if(CClock::GetHours() > 22 || CClock::GetHours() < 7){ + if(CTimer::GetTimeInMilliseconds() & 0x200) + CCoronas::RegisterCorona(102, 255, 0, 0, 255, planePos, 5.0f, 2000.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::UpdateCoronaCoors(102, planePos, 2000.0f, 0.0f); + } + + planePos.x = 1100.0f * Cos((float)(CTimer::GetTimeInMilliseconds() & 0x1FFFF)/0x20000 * TWOPI); + planePos.y = 700.0f * Sin((float)(CTimer::GetTimeInMilliseconds() & 0x1FFFF)/0x20000 * TWOPI); + planePos.z = 600.0f; + RegisterPoint(planePos, 5); + if(CClock::GetHours() > 22 || CClock::GetHours() < 7){ + if(CTimer::GetTimeInMilliseconds() & 0x200) + CCoronas::RegisterCorona(103, 255, 0, 0, 255, planePos, 5.0f, 2000.0f, + CCoronas::TYPE_NORMAL, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + else + CCoronas::UpdateCoronaCoors(103, planePos, 2000.0f, 0.0f); + } +} + +void +CPlaneTrails::Render(void) +{ + int i; + float visibility = Min(1.0f-CWeather::Foggyness, 1.0f-CWeather::CloudCoverage); + visibility = Min(visibility, 1.0f-CWeather::Rain); + visibility = Min(Max(Max(CTimeCycle::GetSkyTopRed(), CTimeCycle::GetSkyTopGreen()), CTimeCycle::GetSkyTopBlue())/256.0f, visibility); + if(visibility > 0.0001f) + for(i = 0; i < ARRAY_SIZE(aArray); i++) + aArray[i].Render(visibility); +} + +void +CPlaneTrails::RegisterPoint(CVector pos, uint32 id) +{ + aArray[id].RegisterPoint(pos); +} + + + +CPlaneBanner CPlaneBanners::aArray[5]; + +void +CPlaneBanner::Init(void) +{ + int i; + for(i = 0; i < ARRAY_SIZE(m_pos); i++){ + m_pos[i].x = i; + m_pos[i].y = 0.0f; + m_pos[i].z = -60.0f; + } +} + +void +CPlaneBanner::Update(void) +{ + int i; + if(m_pos[0].z > -50.0f){ + m_pos[0].z -= 0.05f*CTimer::GetTimeStep(); + m_pos[0].z = Max(m_pos[0].z, -100.0f); + for(i = 1; i < ARRAY_SIZE(m_pos); i++){ + CVector dist = m_pos[i] - m_pos[i-1]; + float len = dist.Magnitude(); + if(len > 8.0f) + m_pos[i] = m_pos[i-1] + dist/len*8.0f; + } + } +} + +void +CPlaneBanner::Render(void) +{ + int i; + if(m_pos[0].z > -50.0f){ + float camDist = (TheCamera.GetPosition() - m_pos[0]).Magnitude(); + if(TheCamera.IsSphereVisible(m_pos[4], 32.0f) && camDist < 300.0f){ + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + int alpha = camDist < 250.0f ? 160 : (300.0f-camDist)/(300.0f-250.0f)*160; + + TempBufferVerticesStored += 2; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[0], 255, 255, 255, alpha); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[1], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[0], m_pos[2].x, m_pos[2].y, m_pos[2].z); + RwIm3DVertexSetPos(&TempBufferRenderVertices[1], m_pos[2].x, m_pos[2].y, m_pos[2].z - 4.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[0], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[0], 0.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[1], 0.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[1], 1.0f); + for(i = 2; i < 8; i++){ + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+0], 255, 255, 255, alpha); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[TempBufferVerticesStored+1], 255, 255, 255, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+0], m_pos[i].x, m_pos[i].y, m_pos[i].z); + RwIm3DVertexSetPos(&TempBufferRenderVertices[TempBufferVerticesStored+1], m_pos[i].x, m_pos[i].y, m_pos[i].z - 4.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored+0], (i-2)/5.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored+0], 0.0f); + RwIm3DVertexSetU(&TempBufferRenderVertices[TempBufferVerticesStored+1], (i-2)/5.0f); + RwIm3DVertexSetV(&TempBufferRenderVertices[TempBufferVerticesStored+1], 1.0f); + TempBufferRenderIndexList[TempBufferIndicesStored+0] = TempBufferVerticesStored-2; + TempBufferRenderIndexList[TempBufferIndicesStored+1] = TempBufferVerticesStored-1; + TempBufferRenderIndexList[TempBufferIndicesStored+2] = TempBufferVerticesStored+1; + TempBufferRenderIndexList[TempBufferIndicesStored+3] = TempBufferVerticesStored-2; + TempBufferRenderIndexList[TempBufferIndicesStored+4] = TempBufferVerticesStored+1; + TempBufferRenderIndexList[TempBufferIndicesStored+5] = TempBufferVerticesStored; + TempBufferVerticesStored += 2; + TempBufferIndicesStored += 6; + } + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRubbishTexture[2])); + +#ifdef FIX_BUGS + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXXYZ|rwIM3D_VERTEXUV|rwIM3D_VERTEXRGBA)){ +#else + if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, 0)){ +#endif + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored); + RwIm3DEnd(); + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + } + } +} + +void +CPlaneBanner::RegisterPoint(CVector pos) +{ + m_pos[0] = pos; +} + +void +CPlaneBanners::Init(void) +{ + int i; + for(i = 0; i < ARRAY_SIZE(aArray); i++) + aArray[i].Init(); +} + +void +CPlaneBanners::Update(void) +{ + int i; + for(i = 0; i < ARRAY_SIZE(aArray); i++) + aArray[i].Update(); +} + +void +CPlaneBanners::Render(void) +{ + int i; + for(i = 0; i < ARRAY_SIZE(aArray); i++) + aArray[i].Render(); +} + +void +CPlaneBanners::RegisterPoint(CVector pos, uint32 id) +{ + aArray[id].RegisterPoint(pos); +} + +bool CSmokeTrails::CigOn = false; +CSmokeTrail CSmokeTrails::aSmoke[3]; + +RwImVertexIndex SmokeTrailIndices[32] = { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, +9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16 }; + +float RandomSmoke[16] = { 10.0f, 5.0f, -1.0f, -9.0f, -7.0f, -1.0f, 0.0f, 3.0f, 6.0f, 7.0f, 4.0f, 2.0f, +5.0f, 7.0f }; + +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_ARENA_STRING +}; + +CScrollBar aScrollBars[1]; + +CMovingThing CMovingThings::StartCloseList; +CMovingThing CMovingThings::EndCloseList; +int16 CMovingThings::Num; +CMovingThing CMovingThings::aMovingThings[NUMMOVINGTHINGS]; + +int CScrollBar::TonightsEvent; + +void CMovingThings::Init() +{ + StartCloseList.m_pNext = &CMovingThings::EndCloseList; + StartCloseList.m_pPrev = nil; + EndCloseList.m_pNext = nil; + EndCloseList.m_pPrev = &CMovingThings::StartCloseList; + + CPlaneTrails::Init(); + CSmokeTrails::Init(); + CPlaneBanners::Init(); + CPointLights::Init(); + + Num = 0; + + for (int32 i = 0; i < NUMMOVINGTHINGS; i++) { + aMovingThings[i].m_nType = 0; + aMovingThings[i].m_farAway = 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; + PossiblyAddThisEntity(pEntity); + } + } + } + + for (int32 i = 0; i < NUM_LEVELS; i++) { + for (CPtrNode *pNode = CWorld::GetBigBuildingList((eLevelName)i).first; pNode; pNode = pNode->next) { + CEntity *pEntity = (CEntity *)pNode->item; + PossiblyAddThisEntity(pEntity); + } + } + + CEscalators::Init(); + aScrollBars[0].Init(CVector(-1069.209f, 1320.126f, 18.848f), CVector(-1069.209f, 1342.299f, 22.612f), SCROLL_ARENA_STRING, 128, 255, 0, 0.3f); +} + +void CMovingThings::Shutdown() +{ + + aScrollBars[0].SetVisibility(false); + CEscalators::Shutdown(); +} + +void CMovingThings::Update() +{ + CPlaneBanners::Update(); + CPlaneTrails::Update(); + CEscalators::Update(); + + const int TIME_SPAN = 8; // frames to process all aMovingThings + + int16 i; + + int block = CTimer::GetFrameCounter() % TIME_SPAN; + + for (i = (block * NUMMOVINGTHINGS) / TIME_SPAN; i < ((block + 1) * NUMMOVINGTHINGS) / TIME_SPAN; i++) { + if (aMovingThings[i].m_farAway == 1) + aMovingThings[i].Update(); + } + + for (i = 0; i < CMovingThings::Num; i++) { + if (aMovingThings[i].m_farAway == 0) + aMovingThings[i].Update(); + } + + for (i = 0; i < ARRAY_SIZE(aScrollBars); ++i) + { + if (aScrollBars[i].IsVisible() || (CTimer::GetFrameCounter() + i) % 8 == 0) + aScrollBars[i].Update(); + } +} + +void CMovingThings::Render() +{ + PUSH_RENDERGROUP("CMovingThings::Render"); + CSmokeTrails::Update(); + + int i; + for (i = 0; i < ARRAY_SIZE(aScrollBars); ++i) + { + if (aScrollBars[i].IsVisible()) + aScrollBars[i].Render(); + } + + CPlaneTrails::Render(); + CSmokeTrails::Render(); + CPlaneBanners::Render(); + POP_RENDERGROUP(); +} + +void CMovingThings::RegisterOne(CEntity *pEnt, uint16 nType) { + if (Num >= NUMMOVINGTHINGS) + return; + + aMovingThings[Num].m_pEntity = pEnt; + aMovingThings[Num].m_nType = nType; + aMovingThings[Num].m_farAway = 0; + aMovingThings[Num].m_vecPosn = pEnt->GetPosition(); + aMovingThings[Num].AddToList(&CMovingThings::StartCloseList); + Num++; +} + +void CMovingThings::PossiblyAddThisEntity(CEntity *pEnt) { + if (pEnt->GetModelIndex() == MI_LIGHTBEAM) { + RegisterOne(pEnt, 1); + } + else if (pEnt->GetModelIndex() == MI_AIRPORTRADAR) { + RegisterOne(pEnt, 2); + } + else if (pEnt->GetModelIndex() == MI_MALLFAN || pEnt->GetModelIndex() == MI_HOTELFAN_NIGHT + || pEnt->GetModelIndex() == MI_HOTELFAN_DAY || pEnt->GetModelIndex() == MI_HOTROOMFAN) { + RegisterOne(pEnt, 3); + } + else if (pEnt->GetModelIndex() == MI_BLIMP_NIGHT || pEnt->GetModelIndex() == MI_BLIMP_DAY) { + RegisterOne(pEnt, 4); + } +} + +// ---------- CMovingThing ---------- +static float maxUpdateDists[5] = { 100.0f, 1500.0f, 400.0f, 100.0f, 2000.0f }; + +void CMovingThing::Update() +{ + switch (m_nType) { + case 1: { + float angle = (CTimer::GetTimeInMilliseconds() % 0x3FFF) * TWOPI / 0x3FFF; + float s = Sin(angle); + float c = Cos(angle); + m_pEntity->GetRight() = CVector(-s, c, 0.0f); + m_pEntity->GetForward() = CVector(0.0f, 0.0f, 1.0f); + m_pEntity->GetUp() = CVector(c, s, 0.0f); + + if (CClock::GetHours() >= 20 || CClock::GetHours() < 5) { + if (Abs(TheCamera.GetPosition().x - m_pEntity->GetPosition().x) < 600.0f && + Abs(TheCamera.GetPosition().y - m_pEntity->GetPosition().y) < 600.0f) { + CVector delta = m_pEntity->GetPosition() - TheCamera.GetPosition(); + delta /= delta.Magnitude(); + + if (DotProduct(delta, CVector(c, s, 0.0f)) < -0.92f) { + CVector coors = m_pEntity->GetPosition() - 10.0f * delta; + CCoronas::RegisterCorona(43, 128, 128, 100, 255, coors, 70.0f, 600.0f, 0.0f, CCoronas::TYPE_STAR, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, 0.0f); + } + } + } + } + break; + case 2: { + float angle = (CTimer::GetTimeInMilliseconds() % 0x7FF) * TWOPI / 0x7FF; + float s = Sin(angle); + float c = Cos(angle); + m_pEntity->GetRight() = CVector(c, s, 0.0f); + m_pEntity->GetForward() = CVector(-s, c, 0.0f); + m_pEntity->GetUp() = CVector(0.0f, 0.0f, 1.0f); + } + break; + case 3: { + float angle = (CTimer::GetTimeInMilliseconds() % 0x3FF) * TWOPI / 0x3FF; + float s = Sin(angle); + float c = Cos(angle); + m_pEntity->GetRight() = CVector(c, s, 0.0f); + m_pEntity->GetForward() = CVector(-s, c, 0.0f); + m_pEntity->GetUp() = CVector(0.0f, 0.0f, 1.0f); + } + break; + case 4: { + float angle = (CTimer::GetTimeInMilliseconds() % 0x3FFFF) * TWOPI / 0x3FFFF; + float s = Sin(angle); + float c = Cos(angle); + m_pEntity->GetRight() = CVector(-c, -s, 0.0f); + m_pEntity->GetForward() = CVector(s, -c, 0.0f); + m_pEntity->GetUp() = CVector(0.0f, 0.0f, 1.0f); + m_pEntity->SetPosition(CVector(350.0f * c - 465.0f, 350.0f * s + 1163.0f, 260.0f)); + } + break; + default: + break; + } + + m_pEntity->GetMatrix().UpdateRW(); + m_pEntity->UpdateRwFrame(); + + if (SQR(m_pEntity->GetPosition().x - TheCamera.GetPosition().x) + SQR(m_pEntity->GetPosition().y - TheCamera.GetPosition().y) < SQR(maxUpdateDists[m_nType])) { + if (m_farAway == 1) { + AddToList(&CMovingThings::StartCloseList); + m_farAway = 0; + } + } + else { + if (m_farAway == 0) { + RemoveFromList(); + m_farAway = 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; +} + +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; +} + +// ---------- CScrollBar ---------- +void CScrollBar::Init(CVector pos1, CVector pos2, uint8 type, 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 = strlen(m_pMessage); + + m_Counter = 0; + m_bVisible = false; + m_Position = pos1; + m_Type = type; + m_Size.x = (pos2.x - pos1.x) * 0.025f; + m_Size.y = (pos2.y - pos1.y) * 0.025f; + m_Size.z = (pos2.z - pos1.z) * 0.2f; + 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; + if (m_Type == SCROLL_ARENA_STRING) { + while (previousMessage == m_pMessage) + { + switch (CGeneral::GetRandomNumber() % 4) + { + case 0: + switch (TonightsEvent) { + case 0: + m_pMessage = "MAIN EVENT TONIGHT: CAR RACING . . . "; + break; + case 1: + m_pMessage = "MAIN EVENT TONIGHT: DESTRUCTION DERBY . . . "; + break; + case 2: + m_pMessage = "MAIN EVENT TONIGHT: BIKE RACING . . . "; + break; + } + break; + case 1: + switch (TonightsEvent) { + case 0: + m_pMessage = "FOR TICKETS TO THE HOT RING EVENT CALL 555-3764 . . . "; + break; + case 1: + m_pMessage = "FOR TICKETS TO THE BLOOD RING EVENT CALL 555-3765 . . . "; + break; + case 2: + m_pMessage = "FOR TICKETS TO THE DIRT RING EVENT CALL 555-3766 . . . "; + break; + } + break; + case 2: + m_pMessage = "HYMAN MEMORIAL STADIUM. HOME TO SOME OF THE BIGGEST EVENTS OF" + " THE WESTERN HEMISPHERE. ALSO AVAILABLE FOR CHILDREN PARTIES. . . "; + break; + case 3: + m_pMessage = FindTimeMessage(); + break; + default: + 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(); +} + +void +CSmokeTrail::RegisterPoint(CVector regPosition, float opacity) { + bool bAddedNewPoint = false; + + if (m_time[0] && CTimer::GetTimeInMilliseconds() - m_time[0] > 150) { + bAddedNewPoint = true; + for (int32 i = 15; i > 0; i--) { + m_pos[i] = m_pos[i - 1]; + m_time[i] = m_time[i - 1]; + m_opacity[i] = m_opacity[i - 1]; + } + ++m_seed; + } + m_pos[0] = regPosition; + + if (bAddedNewPoint || !m_time[0]) { + m_time[0] = CTimer::GetTimeInMilliseconds(); + float density = 0.1f / (m_pos[1] - m_pos[2]).Magnitude(); + m_opacity[1] = opacity * Min(density, 1.0f); + } + m_opacity[0] = 0.0f; +} + +void +CSmokeTrail::Init(int num) { + for (int32 i = 0; i < 16; i++) + m_time[i] = 0; + m_seed = num * 2; +} + +void +CSmokeTrails::Init(void) { + for (int32 i = 0; i < 3; i++) + aSmoke[i].Init(i); +} + +void +CSmokeTrails::Render(void) { + for (int32 i = 0; i < 3; i++) + aSmoke[i].Render(); +} + +void +CSmokeTrail::Render(void) { + int numVerts = 0; + + if (TheCamera.IsSphereVisible(m_pos[0], 10.0f)) { + for (int32 i = 0; i < 16; i++) { + int timeSinceSpawned = CTimer::GetTimeInMilliseconds() - m_time[i]; + + if (timeSinceSpawned > 2250) + m_time[i] = 0; + + if (m_time[i]) { + int alpha = (1.0f - timeSinceSpawned / 2250.0f) * 110.0f * m_opacity[i]; + float offset = timeSinceSpawned * CWeather::Wind * 0.0001f; + float posX = (m_pos[i].x + timeSinceSpawned * RandomSmoke[(i - m_seed) & 0xF] * 0.00001f) - offset; + float posY = (m_pos[i].y + timeSinceSpawned * RandomSmoke[(i - m_seed + 5) & 0xF] * 0.00001f) - offset; + float posZ = m_pos[i].z + timeSinceSpawned * 0.0004f; + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[i], 200, 200, 200, alpha); + RwIm3DVertexSetPos(&TempBufferRenderVertices[i], posX, posY, posZ); + numVerts++; + } + } + } + + if (numVerts > 1) { + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + if (RwIm3DTransform(TempBufferRenderVertices, numVerts, nil, rwIM3D_VERTEXXYZ | rwIM3D_VERTEXRGBA)) { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPEPOLYLINE, SmokeTrailIndices, 2 * (numVerts - 1)); + RwIm3DEnd(); + } + } +} + +void +CSmokeTrails::Update(void) { + + if (!CSmokeTrails::CigOn || TheCamera.Using1stPersonWeaponMode() || !FindPlayerPed() || + FindPlayerVehicle() || CCutsceneMgr::IsRunning() || !FindPlayerPed()->GetClump()) + return; + + RwV3d startPos = { 0.026f, 0.15f, 0.02f }; + RwV3d endPos = { 0.026f, 0.05f, 0.02f }; + + RpHAnimHierarchy *hier = GetAnimHierarchyFromSkinClump(FindPlayerPed()->GetClump()); + int32 idx = RpHAnimIDGetIndex(hier, ConvertPedNode2BoneTag(PED_HEAD)); + RwMatrix *head = &RpHAnimHierarchyGetMatrixArray(hier)[idx]; + RwV3dTransformPoints(&startPos, &startPos, 1, head); + RwV3dTransformPoints(&endPos, &endPos, 1, head); + + aSmoke[0].RegisterPoint(startPos, 1.0f); + aSmoke[1].RegisterPoint(startPos, 0.75f); + aSmoke[2].RegisterPoint(startPos, 0.5f); + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[0], 255, 255, 255, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[0], startPos.x, startPos.y, startPos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[1], 255, 255, 255, 255); + RwIm3DVertexSetPos(&TempBufferRenderVertices[1], endPos.x, endPos.y, endPos.z); + + if (RwIm3DTransform(TempBufferRenderVertices, 2, nil, rwIM3D_VERTEXXYZ | rwIM3D_VERTEXRGBA)) { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPEPOLYLINE, SmokeTrailIndices, 2); + RwIm3DEnd(); + } +} + +CEscalator CEscalators::aEscalators[NUM_ESCALATORS]; +int32 CEscalators::NumEscalators; + +CEscalator::CEscalator() { + m_bIsActive = false; + + for (int i = 0; i < 24; i++) { + m_pSteps[i] = nil; + } +} + +void +CEscalator::AddThisOne(CVector pos0, CVector pos1, CVector pos2, CVector pos3, bool b_isMovingDown) { + m_pos0 = pos0; + m_pos1 = pos1; + m_pos2 = pos2; + m_pos3 = pos3; + + float escalatorStepHeight = CModelInfo::GetColModel(MI_ESCALATORSTEP)->boundingBox.max.z; + m_pos0.z -= escalatorStepHeight; + m_pos1.z -= escalatorStepHeight; + m_pos2.z -= escalatorStepHeight; + m_pos3.z -= escalatorStepHeight; + + float magnitudes[3]; + magnitudes[0] = (m_pos0 - m_pos1).Magnitude(); + magnitudes[1] = (m_pos1 - m_pos2).Magnitude(); + magnitudes[2] = (m_pos2 - m_pos3).Magnitude(); + + float length = magnitudes[0] + magnitudes[1] + magnitudes[2]; + + m_lowerEnd = magnitudes[0] / length; + m_upperEnd = (magnitudes[0] + magnitudes[1]) / length; + + m_stepsCount = Min(24.0f, length / 0.6f); + + CVector direction(m_pos0.x - m_pos1.x, m_pos0.y - m_pos1.y, 0.0f); + direction.Normalise(); + + m_matrix.GetUp() = CVector(0.0f, 0.0f, 1.0f); + m_matrix.GetForward() = CVector(direction.x, direction.y, 0.0f); + m_matrix.GetRight() = CVector(direction.y, -direction.x, 0.0f); + m_matrix.GetPosition() = CVector(0.0f, 0.0f, 0.0f); + + m_bIsMovingDown = b_isMovingDown; + + m_midPoint = (m_pos0 + m_pos3) / 2.0f; + + m_radius = (m_pos0 - m_midPoint).Magnitude(); +} + +void +CEscalator::Update(void) { + if (!m_bIsActive) { + if ((TheCamera.GetPosition() - m_midPoint).Magnitude() < 25.0f) { + if (TheCamera.IsSphereVisible(m_midPoint, m_radius) && (m_stepsCount + 10 < CPools::GetObjectPool()->GetNoOfFreeSpaces())) { + m_bIsActive = true; + for (int i = 0; i < m_stepsCount; i++) { + m_pSteps[i] = new CObject(MI_ESCALATORSTEP, TRUE); + if (m_pSteps[i]) { + m_pSteps[i]->SetPosition(m_pos1); + CWorld::Add(m_pSteps[i]); + m_pSteps[i]->ObjectCreatedBy = CONTROLLED_SUB_OBJECT; + } + } + } + } + } + + if (m_bIsActive) { + float time = (CTimer::GetTimeInMilliseconds() % 16384) / 16384.0f; + for (int i = 0; i < m_stepsCount; i++) { + if (m_pSteps[i]) { + float t = i / (float)m_stepsCount + time; + + if (t > 1.0f) + t -= 1.0f; + + if (m_bIsMovingDown) + t = 1.0f - t; + + CVector oldPosition = m_pSteps[i]->GetPosition(); + m_pSteps[i]->GetMatrix() = m_matrix; + + CVector newPosition; + if (t < m_lowerEnd) { + float ratio = t / m_lowerEnd; + newPosition = (ratio * m_pos1) + ((1.0f - ratio) * m_pos0); + } + else if (t < m_upperEnd) { + float ratio = (t - m_lowerEnd) / (m_upperEnd - m_lowerEnd); + newPosition = (ratio * m_pos2) + ((1.0f - ratio) * m_pos1); + } + else { + float ratio = (t - m_upperEnd) / (1.0f - m_upperEnd); + newPosition = (ratio * m_pos3) + ((1.0f - ratio) * m_pos2); + } + + m_pSteps[i]->SetPosition(newPosition); + m_pSteps[i]->m_vecMoveSpeed = (newPosition - oldPosition) / Max(CTimer::GetTimeStep(), 1.0f); + m_pSteps[i]->GetMatrix().UpdateRW(); + m_pSteps[i]->UpdateRwFrame(); + } + if ((TheCamera.GetPosition() - m_midPoint).Magnitude() > 28.0f || !TheCamera.IsSphereVisible(m_midPoint, m_radius)) + SwitchOff(); + } + } +} + +bool deletingEscalator; + +void +CEscalator::SwitchOff(void) { + if (m_bIsActive) { + for (int i = 0; i < m_stepsCount; i++) { + if (m_pSteps[i]) { + CWorld::Remove(m_pSteps[i]); + deletingEscalator = true; + delete m_pSteps[i]; + m_pSteps[i] = nil; + deletingEscalator = false; + } + } + m_bIsActive = false; + } +} + +void +CEscalators::AddOne(CVector pos0, CVector pos1, CVector pos2, CVector pos3, bool b_isMovingDown) { + aEscalators[NumEscalators++].AddThisOne(pos0, pos1, pos2, pos3, b_isMovingDown); +} + +void +CEscalators::Init(void) { + Shutdown(); + NumEscalators = 0; + + AddOne(CVector(-9.82999f, -938.04498f, 9.4219f), CVector(-8.573f, -938.04498f, 9.4219f), + CVector(-0.747f, -938.045f, 15.065f), CVector(0.88f, -938.045f, 15.065f), TRUE); + + AddOne(CVector(-9.83f, -939.966f, 9.422f), CVector(-8.573f, -939.966f, 9.422f), + CVector(-0.747f, -939.966f, 15.065f), CVector(0.880f, -939.966f, 15.065f), FALSE); + + AddOne(CVector(408.116f, 1058.36f, 18.261f), CVector(408.094f, 1057.04f, 18.261f), + CVector(408.116f, 1048.0f, 24.765f), CVector(408.094f, 1046.57f, 24.799f), TRUE); + + AddOne(CVector(406.195f, 1058.36f, 18.261f), CVector(406.173f, 1057.04f, 18.261f), + CVector(406.195f, 1048.0f, 24.729f), CVector(406.173f, 1046.57f, 24.79f), FALSE); + + AddOne(CVector(421.729f, 1058.3789f, 18.075f), CVector(421.707f, 1057.052f, 18.099f), + CVector(421.729f, 1048.016f, 24.604f), CVector(421.707f, 1046.589f, 24.637f), TRUE); + + AddOne(CVector(419.808f, 1058.378f, 18.099f), CVector(419.786f, 1057.052f, 18.099f), + CVector(419.808f, 1048.016f, 24.568f), CVector(419.786f, 1046.589f, 24.637f), FALSE); + + AddOne(CVector(412.69901f, 1102.729f, 17.569f), CVector(412.72198f, 1104.057f, 17.57f), + CVector(412.69901f, 1113.092f, 24.073f), CVector(412.72198f, 1114.3201f, 24.108f), TRUE); + + AddOne(CVector(414.62f, 1102.729f, 17.569f), CVector(414.64301f, 1104.057f, 17.57f), + CVector(414.62f, 1113.092f, 24.037001f), CVector(414.64301f, 1114.3201f, 24.099001f), FALSE); + + AddOne(CVector(414.64301f, 1145.589f, 17.57f), CVector(414.62f, 1144.261f, 17.569f), + CVector(414.64301f, 1135.226f, 24.073999f), CVector(414.62f, 1133.798f, 24.107f), TRUE); + + AddOne(CVector(412.72198f, 1145.589f, 17.57f), CVector(412.69901f, 1144.261f, 17.569f), + CVector(412.72198f, 1135.226f, 24.038f), CVector(412.69901f, 1133.798f, 24.098f), FALSE); + + AddOne(CVector(406.05099f, 1193.4771f, 18.016001f), CVector(406.07401f, 1194.8051f, 18.017f), + CVector(406.05099f, 1203.84f, 24.52f), CVector(406.07401f, 1205.2679f, 24.555f), TRUE); + + AddOne(CVector(407.97198f, 1193.4771f, 18.016001f), CVector(407.995f, 1194.8051f, 18.017f), + CVector(407.97198f, 1203.84f, 24.483999f), CVector(407.995f, 1205.2679f, 24.546f), FALSE); + + AddOne(CVector(419.659f, 1193.479f, 17.979f), CVector(419.68201f, 1194.807f, 17.98f), + CVector(419.659f, 1203.842f, 24.483f), CVector(419.68201f, 1205.27f, 24.518f), TRUE); + + AddOne(CVector(421.57999f, 1193.479f, 17.979f), CVector(421.603f, 1194.807f, 17.98f), + CVector(421.57999f, 1203.842f, 24.447001f), CVector(421.603f, 1205.27f, 24.509001f), FALSE); + + AddOne(CVector(406.23199f, 1022.857f, 17.917f), CVector(406.23199f, 1024.1851f, 17.917f), + CVector(406.23199f, 1033.22f, 24.521f), CVector(406.23199f, 1034.647f, 24.555f), TRUE); + + AddOne(CVector(408.15302f, 1022.857f, 17.917f), CVector(408.15302f, 1024.1851f, 17.916f), + CVector(408.15302f, 1033.22f, 24.486f), CVector(408.15302f, 1034.647f, 24.52f), FALSE); + + AddOne(CVector(-1506.39f, -813.13f, 13.834f), CVector(-1506.177f, -814.51703f, 13.834f), + CVector(-1504.566f, -823.20898f, 19.836f), CVector(-1504.329f, -824.48499f, 19.837f), FALSE); + + AddOne(CVector(-1481.951f, -859.05402f, 13.834f), CVector(-1482.7791f, -858.22498f, 13.834f), + CVector(-1489.03f, -851.974f, 19.836f), CVector(-1489.948f, -851.05701f, 19.837f), TRUE); + + AddOne(CVector(-1461.743f, -871.35901f, 13.834f), CVector(-1460.62f, -871.69202f, 13.834f), + CVector(-1452.144f, -874.20203f, 19.836f), CVector(-1450.9f, -874.57098f, 19.837f), FALSE); + + AddOne(CVector(-1409.889f, -871.41498f, 13.834f), CVector(-1411.0129f, -871.74701f, 13.834f), + CVector(-1419.489f, -874.258f, 19.836f), CVector(-1420.733f, -874.62701f, 19.837f), TRUE); + + AddOne(CVector(-1389.577f, -858.89301f, 13.834f), CVector(-1388.7271f, -858.08698f, 13.834f), + CVector(-1382.314f, -852.00201f, 19.836f), CVector(-1381.373f, -851.10797f, 19.837f), FALSE); + + AddOne(CVector(-1364.981f, -813.13f, 13.834f), CVector(-1365.204f, -814.28003f, 13.834f), + CVector(-1366.891f, -822.95801f, 19.83f), CVector(-1367.139f, -824.23199f, 19.837f), TRUE); + + for (int i = 0; i < NUM_ESCALATORS; i++) { + aEscalators[i].SwitchOff(); + } +} + +void +CEscalators::Update(void) { + if (CReplay::IsPlayingBack()) + return; + + for (int i = 0; i < NUM_ESCALATORS; i++) { + aEscalators[i].Update(); + } +} + +void +CEscalators::Shutdown(void) { + for (int i = 0; i < NUM_ESCALATORS; i++) { + aEscalators[i].SwitchOff(); + } + NumEscalators = 0; +} + + +CScriptPath CScriptPaths::aArray[3]; + +void CScriptPath::FindCoorsFromDistanceOnPath(float t, float *pX, float *pY, float *pZ) +{ + int32 i; + for (i = 0; m_pNode[i + 1].t < t; i++) + if (i == m_numNodes - 1) { + // don't go beyond last node + *pX = m_pNode[m_numNodes - 1].p.x; + *pY = m_pNode[m_numNodes - 1].p.y; + *pZ = m_pNode[m_numNodes - 1].p.z; + return; + } + float f = (t - m_pNode[i].t) / (m_pNode[i + 1].t - m_pNode[i].t); + *pX = (1.0f - f)*m_pNode[i].p.x + f*m_pNode[i + 1].p.x; + *pY = (1.0f - f)*m_pNode[i].p.y + f*m_pNode[i + 1].p.y; + *pZ = (1.0f - f)*m_pNode[i].p.z + f*m_pNode[i + 1].p.z; +} + +void CScriptPath::Update(void) { + if (m_state != SCRIPT_PATH_ACTIVE) + return; + + m_fPosition += m_fSpeed * CTimer::GetTimeStepInSeconds(); + m_fPosition = Clamp(m_fPosition, 0.0f, m_fTotalLength); + + if (m_pObjects[0] || m_pObjects[1] || m_pObjects[2] || m_pObjects[3] + || m_pObjects[4] || m_pObjects[5]) { + + float t1, t2; + CVector pos1, pos2; + + t1 = Max(m_fPosition - m_fObjectLength / 2.0f, 0.0f); + FindCoorsFromDistanceOnPath(t1, &pos1.x, &pos1.y, &pos1.z); + t2 = Min(m_fPosition + m_fObjectLength / 2.0f, m_fTotalLength); + FindCoorsFromDistanceOnPath(t2, &pos2.x, &pos2.y, &pos2.z); + + CVector newForward, newUp(0.0f, 0.0f, 1.0f), newRight; + + newForward = pos2 - pos1; + newForward.Normalise(); + newRight = CrossProduct(newForward, newUp); + newRight.Normalise(); + newUp = CrossProduct(newRight, newForward); + + for (int i = 0; i < 6; i++) { + if (m_pObjects[i]) { + CMatrix prevMat(m_pObjects[i]->GetMatrix()); + CVector prevPosition = m_pObjects[i]->GetPosition(); + + m_pObjects[i]->SetPosition((pos1 + pos2) / 2.0f); + m_pObjects[i]->GetRight() = newRight; + m_pObjects[i]->GetUp() = newUp; + m_pObjects[i]->GetForward() = newForward; + m_pObjects[i]->GetMatrix().UpdateRW(); + m_pObjects[i]->UpdateRwFrame(); + + if (!m_pObjects[i]->bIsBIGBuilding && prevPosition != m_pObjects[i]->GetPosition()) + m_pObjects[i]->RemoveAndAdd(); + + m_pObjects[i]->GetMatrix().UpdateRW(); + m_pObjects[i]->UpdateRwFrame(); + + m_pObjects[i]->m_vecMoveSpeed = (m_pObjects[i]->GetPosition() - prevMat.GetPosition()) / CTimer::GetTimeStep(); + + float deltaAngle = m_pObjects[i]->GetForward().Heading() - prevMat.GetForward().Heading(); + while (deltaAngle < (float)PI) deltaAngle += (float)TWOPI; + while (deltaAngle > (float)PI) deltaAngle -= (float)TWOPI; + float zTurnSpeed = deltaAngle / CTimer::GetTimeStep(); + + m_pObjects[i]->m_vecTurnSpeed = CVector(0.0f, 0.0f, zTurnSpeed); + m_pObjects[i]->m_vecMoveFriction = CVector(0.0f, 0.0f, 0.0f); + m_pObjects[i]->m_vecTurnFriction = CVector(0.0f, 0.0f, 0.0f); + } + } + } +} + +void CScriptPath::Clear(void) { + if (m_pNode) + delete[] m_pNode; + m_pNode = nil; + m_numNodes = 0; + for (int i = 0; i < 6; i++) + m_pObjects[i] = nil; + m_state = SCRIPT_PATH_DISABLED; +} + +void CScriptPath::InitialiseOne(int32 numNodes, float length) { + char Dest[32]; + sprintf(Dest, "data\\paths\\spath%d.dat", numNodes); + m_pNode = CPlane::LoadPath(Dest, m_numNodes, m_fTotalLength, false); + m_fSpeed = 1.0f; + m_fPosition = 0.0f; + m_fObjectLength = length; + m_state = SCRIPT_PATH_INITIALIZED; +} + +void CScriptPath::SetObjectToControl(CObject *pObj) { + int32 i = 0; + while (i < 6 && m_pObjects[i]) + i++; + m_pObjects[i] = pObj; + pObj->RegisterReference((CEntity**)&m_pObjects[i]); + pObj->m_phy_flagA08 = false; + m_state = SCRIPT_PATH_ACTIVE; +} + +void CScriptPaths::Init(void) { + for (int i = 0; i < 3; i++) + aArray[i].Clear(); +} + +void CScriptPaths::Shutdown(void) { + for (int i = 0; i < 3; i++) + aArray[i].Clear(); +} + +void CScriptPaths::Update(void) { + for (int i = 0; i < 3; i++) + aArray[i].Update(); +} + +bool CScriptPaths::IsOneActive(void) { + for (int i = 0; i < 3; i++) + if (aArray[i].m_state == SCRIPT_PATH_ACTIVE && aArray[i].m_fSpeed != 0.0f) + return true; + + return false; +} + +void CScriptPaths::Load(uint8 *buf, uint32 size) { +INITSAVEBUF + for (int32 i = 0; i < 3; i++) + aArray[i].Clear(); + + for (int32 i = 0; i < 3; i++) { +#ifdef COMPATIBLE_SAVES + ReadSaveBuf(&aArray[i].m_numNodes, buf); + SkipSaveBuf(buf, 4); + ReadSaveBuf(&aArray[i].m_fTotalLength, buf); + ReadSaveBuf(&aArray[i].m_fSpeed, buf); + ReadSaveBuf(&aArray[i].m_fPosition, buf); + ReadSaveBuf(&aArray[i].m_fObjectLength, buf); + ReadSaveBuf(&aArray[i].m_state, buf); +#else + ReadSaveBuf(&aArray[i], buf); +#endif + + for (int32 j = 0; j < 6; j++) { +#ifdef COMPATIBLE_SAVES + aArray[i].m_pObjects[j] = nil; + int32 tmp; + ReadSaveBuf(&tmp, buf); + if (tmp != 0) { + aArray[i].m_pObjects[j] = CPools::GetObjectPool()->GetSlot(tmp - 1); + aArray[i].m_pObjects[j]->m_phy_flagA08 = false; + } +#else + CScriptPath *pPath = &aArray[i]; + if (pPath->m_pObjects[j] != nil) { + pPath->m_pObjects[j] = CPools::GetObjectPool()->GetSlot((uintptr)pPath->m_pObjects[j] - 1); + pPath->m_pObjects[j]->m_phy_flagA08 = false; + } +#endif + } + + aArray[i].m_pNode = new CPlaneNode[aArray[i].m_numNodes]; + for (int32 j = 0; j < aArray[i].m_numNodes; j++) { + ReadSaveBuf(&aArray[i].m_pNode[j], buf); + } + } +VALIDATESAVEBUF(size) +} + +void CScriptPaths::Save(uint8 *buf, uint32 *size) { + *size = SCRIPTPATHS_SAVE_SIZE; +INITSAVEBUF + for (int32 i = 0; i < 3; i++) { +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, aArray[i].m_numNodes); + ZeroSaveBuf(buf, 4); + WriteSaveBuf(buf, aArray[i].m_fTotalLength); + WriteSaveBuf(buf, aArray[i].m_fSpeed); + WriteSaveBuf(buf, aArray[i].m_fPosition); + WriteSaveBuf(buf, aArray[i].m_fObjectLength); + WriteSaveBuf(buf, aArray[i].m_state); +#else + CScriptPath *pPath = WriteSaveBuf(buf, aArray[i]); +#endif + + for (int32 j = 0; j < 6; j++) { +#ifdef COMPATIBLE_SAVES + WriteSaveBuf(buf, aArray[i].m_pObjects[j] != nil ? CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(aArray[i].m_pObjects[j]) + 1 : 0); +#else + if (pPath->m_pObjects[j] != nil) + pPath->m_pObjects[j] = (CObject*)(CPools::GetObjectPool()->GetJustIndex_NoFreeAssert(pPath->m_pObjects[j]) + 1); +#endif + } + + for (int32 j = 0; j < aArray[i].m_numNodes; j++) { + WriteSaveBuf(buf, aArray[i].m_pNode[j]); + *size += sizeof(aArray[i].m_pNode[j]); + } + } +VALIDATESAVEBUF(*size); +} + +CObject *g_pScriptPathObjects[18]; + +void CScriptPaths::Load_ForReplay(void) { + for (int i = 0; i < 3; i++) { + for (int32 j = 0; j < 6; j++) { + aArray[i].m_pObjects[j] = g_pScriptPathObjects[6 * i + j]; + } + } +} + +void CScriptPaths::Save_ForReplay(void) { + for (int i = 0; i < 3; i++) { + for (int32 j = 0; j < 6; j++) { + g_pScriptPathObjects[6 * i + j] = aArray[i].m_pObjects[j]; + } + } +} diff --git a/src/miami/renderer/Fluff.h b/src/miami/renderer/Fluff.h new file mode 100644 index 00000000..58c8410c --- /dev/null +++ b/src/miami/renderer/Fluff.h @@ -0,0 +1,205 @@ +#pragma once +#include "common.h" +#include "Vector.h" +#include "Object.h" +#include "Plane.h" + +enum { + SCRIPT_PATH_DISABLED = 0, + SCRIPT_PATH_INITIALIZED, + SCRIPT_PATH_ACTIVE +}; + +class CScriptPath +{ +public: + int32 m_numNodes; + CPlaneNode *m_pNode; + float m_fTotalLength; + float m_fSpeed; + float m_fPosition; + float m_fObjectLength; + int32 m_state; + CObject *m_pObjects[6]; + + void Clear(void); + void Update(void); + void InitialiseOne(int32 numNodes, float length); + void FindCoorsFromDistanceOnPath(float t, float *pX, float *pY, float *pZ); + void SetObjectToControl(CObject *pObj); +}; + +class CScriptPaths +{ +public: + static CScriptPath aArray[3]; + static void Init(void); + static void Shutdown(void); + static void Update(void); + static bool IsOneActive(void); + static void Save(uint8 *buf, uint32 *size); + static void Load(uint8 *buf, uint32 size); + static void Save_ForReplay(); + static void Load_ForReplay(); +}; + +class CPlaneTrail +{ + CVector m_pos[16]; + int32 m_time[16]; +public: + void Init(void); + void Render(float visibility); + void RegisterPoint(CVector pos); +}; + +class CPlaneTrails +{ + static CPlaneTrail aArray[6]; // NB: 3 CPlanes and 3 hardcoded far away ones +public: + static void Init(void); + static void Update(void); + static void Render(void); + static void RegisterPoint(CVector pos, uint32 id); +}; + +class CPlaneBanner +{ + CVector m_pos[8]; +public: + void Init(void); + void Update(void); + void Render(void); + void RegisterPoint(CVector pos); +}; + +class CPlaneBanners +{ + static CPlaneBanner aArray[5]; +public: + static void Init(void); + static void Update(void); + static void Render(void); + static void RegisterPoint(CVector pos, uint32 id); +}; + +class CEscalator +{ + CVector m_pos0; + CVector m_pos1; + CVector m_pos2; + CVector m_pos3; + CMatrix m_matrix; + bool m_bIsActive; + bool m_bIsMovingDown; + int32 m_stepsCount; + float m_lowerEnd; + float m_upperEnd; + CVector m_midPoint; + float m_radius; + CObject *m_pSteps[24]; +public: + CEscalator(); + void Update(void); + void SwitchOff(void); + void AddThisOne(CVector pos0, CVector pos1, CVector pos2, CVector pos3, bool b_isMovingDown); + bool IsActive() const { return m_bIsActive; }; + const CVector& GetPosition() const { return m_midPoint; }; +}; + +class CEscalators +{ + static CEscalator aEscalators[NUM_ESCALATORS]; +public: + static int32 NumEscalators; + static void Init(void); + static void Update(void); + static void AddOne(CVector pos0, CVector pos1, CVector pos2, CVector pos3, bool b_isMovingDown); + static void Shutdown(void); + static const CEscalator& GetEscalator(int ind) { return aEscalators[ind]; }; +}; + +class CMovingThing +{ +public: + CMovingThing *m_pNext; + CMovingThing *m_pPrev; + int16 m_nType; + int16 m_farAway; + CVector m_vecPosn; + CEntity* m_pEntity; + + void Update(); + void AddToList(CMovingThing *pThing); + void RemoveFromList(); + int16 SizeList(); +}; + +#define NUMMOVINGTHINGS 48 + +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(); + static void PossiblyAddThisEntity(CEntity *pEnt); + static void RegisterOne(CEntity *pEnt, uint16 nType); +}; + +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: + static int TonightsEvent; + +public: + void SetVisibility(bool visible) { m_bVisible = visible; } + bool IsVisible() { return m_bVisible; } + + void Init(CVector pos1, CVector pos2, uint8 type, uint8 red, uint8 green, uint8 blue, float scale); + void Update(); + void Render(); +}; + +class CSmokeTrail { + CVector m_pos[16]; + float m_opacity[16]; + int m_time[16]; + char m_unused[536]; + int m_seed; +public: + void Render(void); + void RegisterPoint(CVector position, float a); + void Init(int num); +}; + +class CSmokeTrails { + static CSmokeTrail aSmoke[3]; +public: + static bool CigOn; + static void Update(void); + static void Render(void); + static void Init(void); +}; \ No newline at end of file diff --git a/src/miami/renderer/Font.cpp b/src/miami/renderer/Font.cpp new file mode 100644 index 00000000..606193b9 --- /dev/null +++ b/src/miami/renderer/Font.cpp @@ -0,0 +1,1938 @@ +#include "common.h" + +#include "Sprite2d.h" +#include "TxdStore.h" +#include "Font.h" +#ifdef BUTTON_ICONS +#include "FileMgr.h" +#endif +#include "Timer.h" + +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; +} + +void +UnicodeMakeUpperCase(wchar *dst, const wchar *src) //idk what to do with it, seems to be incorrect implementation by R* +{ + while (*src != '\0') { + if (*src < 'a' || *src > 'z') + *dst = *src; + else + *dst = *src - 32; + dst++; + src++; + } + *dst = '\0'; +} + +CFontDetails CFont::Details; +bool16 CFont::NewLine; +CSprite2d CFont::Sprite[MAX_FONTS]; +CFontRenderState CFont::RenderState; + +#ifdef MORE_LANGUAGES +uint8 CFont::LanguageSet = FONT_LANGSET_EFIGS; +int32 CFont::Slot = -1; +#define JAP_TERMINATION (0x8000 | '~') + +int16 CFont::Size[LANGSET_MAX][MAX_FONTS][210] = { + { +#else +int16 CFont::Size[MAX_FONTS][210] = { +#endif + { + //FONT2 EFIGS + //SPC,!, $, %, &, ', [, ], +, , -, ., + 12, 9, 22, 17, 19, 19, 25, 4, 33, 33, 25, 35, 11, 10, 6, 33, + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ??, + 18, 10, 17, 17, 17, 17, 17, 15, 12, 16, 5, 30, 30, 30, 30, 30, + // A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, + 12, 16, 19, 16, 19, 18, 18, 17, 22, 11, 17, 18, 18, 30, 22, 19, + //P, Q, R, S, T, U, V, W, X, Y, Z, ??, ??, ??, �, \, + #ifdef FIX_BUGS + 22, 19, 19, 20, 18, 19, 19, 29, 19, 18, 19, 19, 33, 33, 10, 19, + #else + 22, 19, 19, 20, 18, 19, 19, 29, 19, 18, 19, 19, 33, 33, 19, 19, + #endif + //??,a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, + 12, 14, 11, 11, 16, 11, 12, 14, 14, 10, 13, 12, 10, 19, 18, 12, + //p, q, r, s, t, u, v, w, x, y, z, ??, ??, ??, ??, ??, + 16, 13, 13, 11, 12, 15, 12, 15, 13, 12, 12, 37, 33, 37, 35, 37, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 16, 16, 16, 16, 33, 17, 18, 18, 18, 18, 11, 11, 11, 11, 19, 19, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 19, 19, 19, 19, 19, 19, 15, 14, 14, 14, 14, 20, 14, 11, 11, 11, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + #ifdef FIX_BUGS + 11, 10, 10, 10, 10, 12, 12, 12, 12, 15, 15, 15, 15, 22, 18, 21, + #else + 11, 10, 10, 10, 10, 12, 12, 12, 12, 15, 15, 15, 15, 24, 18, 21, + #endif + //i,BLANKS + 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + //space, unprop + 19, 16 + }, + { + //FONT1 EFIGS + //Characters with a '2' refer to the Pricedown font. + //Characters that are referred as '*I' are characters that contain icons for PS2/XBOX, but contain regular characters on PC + //in order to display them properly in the Keyboard controls menu. + //!2,!, *I,(R), $, %, &, ', [, ], *I, +, , -, ., *I, + 15, 7, 31, 25, 20, 23, 21, 7, 11, 10, 26, 14, 6, 12, 6, 26, + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, *I, *I, *I, *I, ?, + 20, 7, 20, 20, 21, 20, 20, 19, 21, 20, 8, 30, 24, 30, 24, 19, + //TM,A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, + 20, 22, 22, 21, 22, 18, 18, 22, 22, 9, 14, 21, 18, 27, 21, 24, + //P, Q, R, S, T, U, V, W, X, Y, Z, *I, \, *I, �, �, + #ifdef FIX_BUGS + 22, 22, 23, 20, 19, 23, 22, 31, 23, 23, 21, 25, 13, 30, 7, 19, + #else + 22, 22, 23, 20, 19, 23, 22, 31, 23, 23, 21, 25, 13, 30, 10, 19, + #endif + //(C),a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, + 10, 17, 17, 16, 17, 17, 11, 17, 17, 7, 7, 18, 7, 25, 17, 17, + //p, q, r, s, t, u, v, w, x, y, z, *I, *I, $2, (2, )2, + 17, 17, 11, 17, 11, 17, 18, 25, 19, 18, 17, 28, 26, 20, 15, 15, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 20, 20, 20, 20, 29, 22, 19, 19, 19, 19, 9, 9, 9, 9, 23, 23, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 23, 23, 24, 24, 24, 24, 20, 19, 17, 17, 17, 30, 16, 17, 17, 17, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + #ifdef FIX_BUGS + 17, 11, 11, 15, 12, 17, 17, 17, 17, 17, 17, 17, 17, 21, 17, 19, + #else + 17, 11, 11, 15, 12, 17, 17, 17, 17, 17, 17, 17, 17, 19, 20, 20, + #endif + //02,12,22, 32, 42, 52, 62, 72, 82, 92, :2, A2, B2, C2, D2, E2, + 20, 18, 19, 19, 21, 19, 19, 19, 19, 19, 16, 19, 19, 19, 20, 19, + //F2,G2,H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2, U2, + 16, 19, 19, 9, 19, 20, 14, 29, 19, 19, 19, 19, 19, 19, 21, 19, + //V2,W2,X2, Y2, Z2, �2, �2, �2, �2, �2, �2, �2, �2, �2, �2, �2, + 20, 32, 20, 19, 19, 19, 19, 19, 19, 29, 19, 19, 19, 19, 19, 9, + //�2,�2,�2, �2, �2, �2, �2, �2, �2, �2, �2, �2, �2, �2, '2, .2, + #ifdef FIX_BUGS + 9, 9, 9, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 9, + #else + 9, 9, 9, 19, 19, 19, 19, 19, 19, 19, 19, 19, 21, 21, 10, 9, + #endif + //space, unprop + 10, 20 + } + +#ifdef MORE_LANGUAGES + }, + { + { + 5, 9, 9, 0, 17, 17, 23, 3, 21, 18, 0, 8, 3, 8, 3, 0, + 16, 9, 16, 16, 15, 19, 15, 14, 17, 17, 4, 4, 0, 0, 0, 17, + 19, 17, 19, 15, 21, 18, 19, 16, 21, 13, 15, 21, 20, 28, 21, 18, + 22, 17, 21, 20, 18, 18, 20, 26, 22, 18, 18, 0, 8, 0, 9, 8, + 0, 14, 11, 12, 16, 11, 13, 13, 15, 10, 14, 15, 11, 21, 17, 10, + 20, 15, 12, 12, 16, 17, 13, 16, 13, 21, 11, 0, 0, 0, 0, 0, + 20, 19, 19, 22, 27, 15, 18, 18, 20, 26, 21, 23, 17, 22, 21, 17, + 26, 25, 26, 17, 20, 26, 17, 16, 11, 12, 13, 21, 11, 17, 17, 12, + 21, 17, 17, 15, 24, 16, 10, 20, 23, 16, 7, 9, 16, 23, 12, 11, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, + 19, 16 + }, + { + 11, 5, 10, 15, 19, 22, 20, 5, 9, 8, 11, 12, 5, 12, 6, 12, + 19, 5, 18, 19, 20, 18, 19, 18, 20, 19, 5, 6, 26, 12, 30, 19, + 23, 21, 20, 20, 20, 16, 16, 21, 19, 5, 13, 19, 16, 24, 20, 21, + 20, 21, 20, 19, 17, 20, 21, 30, 22, 21, 20, 25, 13, 30, 5, 9, + 10, 15, 15, 14, 15, 16, 10, 15, 15, 5, 5, 15, 5, 23, 15, 16, + 15, 15, 9, 16, 10, 15, 17, 24, 18, 15, 15, 27, 5, 19, 2, 2, + 20, 20, 16, 23, 30, 19, 20, 20, 21, 24, 19, 19, 20, 23, 22, 19, + 27, 29, 25, 20, 20, 28, 24, 16, 16, 14, 19, 25, 16, 16, 16, 17, + 19, 16, 16, 17, 25, 19, 15, 23, 26, 21, 16, 14, 22, 20, 16, 19, + 15, 14, 15, 16, 17, 15, 15, 15, 15, 15, 7, 15, 15, 15, 15, 15, + 13, 15, 15, 7, 15, 16, 13, 23, 15, 15, 15, 15, 15, 15, 17, 15, + 16, 24, 17, 17, 17, 15, 15, 13, 20, 23, 15, 17, 17, 16, 24, 15, + 15, 15, 23, 18, 15, 23, 26, 23, 16, 15, 23, 15, 15, 19, 2, 2, + 10, 20 + }, + }, + { + { + //FONT2 EFIGS + //SPC,!, $, %, &, ', [, ], +, , -, ., + 12, 9, 22, 17, 19, 19, 25, 4, 33, 33, 25, 35, 11, 10, 6, 33, + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ??, + 18, 10, 17, 17, 17, 17, 17, 15, 12, 16, 5, 30, 30, 30, 30, 30, + // A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, + 12, 16, 19, 16, 19, 18, 18, 17, 22, 11, 17, 18, 18, 30, 22, 19, + //P, Q, R, S, T, U, V, W, X, Y, Z, ??, ??, ??, �, \, + 22, 19, 19, 20, 18, 19, 19, 29, 19, 18, 19, 19, 33, 33, 10, 19, + //??,a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, + 12, 14, 11, 11, 16, 11, 12, 14, 14, 10, 13, 12, 10, 19, 18, 12, + //p, q, r, s, t, u, v, w, x, y, z, ??, ??, ??, ??, ??, + 16, 13, 13, 11, 12, 15, 12, 15, 13, 12, 12, 37, 33, 37, 35, 37, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 16, 16, 16, 16, 33, 17, 18, 18, 18, 18, 11, 11, 11, 11, 19, 19, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 19, 19, 19, 19, 19, 19, 15, 14, 14, 14, 14, 20, 14, 11, 11, 11, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 11, 10, 10, 10, 10, 12, 12, 12, 12, 15, 15, 15, 15, 22, 18, 21, + //i,BLANKS + 10, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, + //space, unprop + 19, 16 + }, + { + //FONT1 EFIGS + //Characters with a '2' refer to the Pricedown font. + //Characters that are referred as '*I' are characters that contain icons for PS2/XBOX, but contain regular characters on PC + //in order to display them properly in the Keyboard controls menu. + //!2,!, *I,(R), $, %, &, ', [, ], *I, +, , -, ., *I, + 15, 7, 31, 25, 20, 23, 21, 7, 11, 10, 26, 14, 6, 12, 6, 26, + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, *I, *I, *I, *I, ?, + 20, 7, 20, 20, 21, 20, 20, 19, 21, 20, 8, 30, 24, 30, 24, 19, + //TM,A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, + 20, 22, 22, 21, 22, 18, 18, 22, 22, 9, 14, 21, 18, 27, 21, 24, + //P, Q, R, S, T, U, V, W, X, Y, Z, *I, \, *I, �, �, + 22, 22, 23, 20, 19, 23, 22, 31, 23, 23, 21, 25, 13, 30, 7, 19, + //(C),a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, + 10, 17, 17, 16, 17, 17, 11, 17, 17, 7, 7, 18, 7, 25, 17, 17, + //p, q, r, s, t, u, v, w, x, y, z, *I, *I, $2, (2, )2, + 17, 17, 11, 17, 11, 17, 18, 25, 19, 18, 17, 28, 26, 20, 15, 15, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 20, 20, 20, 20, 29, 22, 19, 19, 19, 19, 9, 9, 9, 9, 23, 23, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 23, 23, 24, 24, 24, 24, 20, 19, 17, 17, 17, 30, 16, 17, 17, 17, + //�, �, �, �, �, �, �, �, �, �, �, �, �, �, �, �, + 17, 11, 11, 15, 12, 17, 17, 17, 17, 17, 17, 17, 17, 21, 17, 19, + //02,12,22, 32, 42, 52, 62, 72, 82, 92, :2, A2, B2, C2, D2, E2, + 20, 18, 19, 19, 21, 19, 19, 19, 19, 19, 16, 19, 19, 19, 20, 19, + //F2,G2,H2, I2, J2, K2, L2, M2, N2, O2, P2, Q2, R2, S2, T2, U2, + 16, 19, 19, 9, 19, 20, 14, 29, 19, 19, 19, 19, 19, 19, 21, 19, + //V2,W2,X2, Y2, Z2, �2, �2, �2, �2, �2, �2, �2, �2, �2, �2, �2, + 20, 32, 20, 19, 19, 19, 19, 19, 19, 29, 19, 19, 19, 19, 19, 9, + //�2,�2,�2, �2, �2, �2, �2, �2, �2, �2, �2, �2, �2, �2, '2, .2, + 9, 9, 9, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 10, 9, + //space, unprop + 10, 20 + } + } +#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, +}; + +union tFontRenderStatePointer +{ + CFontRenderState *pRenderState; + wchar *pStr; + + void Align() + { + if ((uintptr)pStr % 4) + pStr++; + } +}; + +tFontRenderStatePointer FontRenderStatePointer; +uint8 FontRenderStateBuf[1024]; + +#ifdef BUTTON_ICONS +CSprite2d CFont::ButtonSprite[MAX_BUTTON_ICONS]; +int CFont::PS2Symbol = BUTTON_NONE; +int32 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", "font2m"); +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + Sprite[1].SetTexture("FONTJAP", "FONTJAP_mask"); + Sprite[3].SetTexture("FONTJAP", "FONTJAP_mask"); + } +#endif // MORE_LANGUAGES + Sprite[1].SetTexture("font1", "font1m"); + SetScale(1.0f, 1.0f); + SetSlantRefPoint(SCREEN_WIDTH, 0.0f); + SetSlant(0.0f); + SetColor(CRGBA(255, 255, 255, 0)); + SetJustifyOff(); + SetCentreOff(); + SetWrapx(SCREEN_WIDTH); + SetCentreSize(SCREEN_WIDTH); + 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); + ButtonSprite[BUTTON_UP].SetTexture("thumblyu"); + ButtonSprite[BUTTON_DOWN].SetTexture("thumblyd"); + ButtonSprite[BUTTON_LEFT].SetTexture("thumblxl"); + ButtonSprite[BUTTON_RIGHT].SetTexture("thumblxr"); + 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"); + ButtonSprite[BUTTON_RSTICK_UP].SetTexture("thumbryu"); + ButtonSprite[BUTTON_RSTICK_DOWN].SetTexture("thumbryd"); + ButtonSprite[BUTTON_RSTICK_LEFT].SetTexture("thumbrxl"); + ButtonSprite[BUTTON_RSTICK_RIGHT].SetTexture("thumbrxr"); + 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(); + if (IsJapanese()) + Sprite[2].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[2].SetTexture("FONTJAP", "FONTJAP_mask"); + } + Sprite[1].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(); +#ifdef MORE_LANGUAGES + if (IsJapanese()) + Sprite[3].Delete(); + CTxdStore::RemoveTxdSlot(Slot); + Slot = -1; +#else + CTxdStore::RemoveTxdSlot(CTxdStore::FindTxdSlot("fonts")); +#endif +} + +void +CFont::InitPerFrame(void) +{ + RenderState.style = -1; + Details.anonymous_25 = 0; + FontRenderStatePointer.pRenderState = (CFontRenderState*)FontRenderStateBuf; + 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 = RenderState.scaleY + RenderState.scaleY + y; + rect.right = RenderState.scaleY * 17.0f + x; + rect.bottom = RenderState.scaleY * 19.0f + y; + + int vertexAlphaState; + void *raster; + RwRenderStateGet(rwRENDERSTATEVERTEXALPHAENABLE, &vertexAlphaState); + RwRenderStateGet(rwRENDERSTATETEXTURERASTER, &raster); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + if (RenderState.bIsShadow) + ButtonSprite[PS2Symbol].Draw(rect, RenderState.color); + else + ButtonSprite[PS2Symbol].Draw(rect, CRGBA(255, 255, 255, RenderState.color.a)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, raster); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)vertexAlphaState); + } +} +#endif + +void +CFont::PrintChar(float x, float y, wchar c) +{ + bool bDontPrint = false; + if(x <= 0.0f || x > SCREEN_WIDTH || + y <= 0.0f || y > SCREEN_HEIGHT) // BUG: game uses SCREENW again + return; + + bDontPrint = c == '\0'; + float w = GetCharacterWidth(c) / 32.0f; + if (Details.bFontHalfTexture && c == 208) + c = '\0'; + 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(RenderState.style == FONT_BANK || RenderState.style == FONT_STANDARD){ + if (bDontPrint) return; + if (RenderState.slant == 0.0f) { +#ifdef FIX_BUGS + if (c < 192) { +#else + if (c < 193) { +#endif + CSprite2d::AddToBuffer( + CRect(x, y, + x + 32.0f * RenderState.scaleX * 1.0f, + y + 40.0f * RenderState.scaleY * 0.5f), + RenderState.color, + xoff / 16.0f, yoff / 12.8f + 0.0021f, + (xoff + 1.0f) / 16.0f - 0.001f, yoff / 12.8f + 0.0021f, + xoff / 16.0f, (yoff + 1.0f) / 12.8f - 0.0021f, + (xoff + 1.0f) / 16.0f - 0.001f, (yoff + 1.0f) / 12.8f - 0.0021f); + } else { + CSprite2d::AddToBuffer( + CRect(x, y, + x + 32.0f * RenderState.scaleX * 1.0f, + y + 33.0f * RenderState.scaleY * 0.5f), + RenderState.color, + xoff / 16.0f, yoff / 12.8f + 0.0021f, + (xoff + 1.0f) / 16.0f - 0.001f, yoff / 12.8f + 0.0021f, + xoff / 16.0f, (yoff + 1.0f) / 12.8f - 0.017f, + (xoff + 1.0f) / 16.0f - 0.001f, (yoff + 1.0f) / 12.8f - 0.017f); + } + } else + CSprite2d::AddToBuffer( + CRect(x, y, + x + 32.0f * RenderState.scaleX * 1.0f, + y + 40.0f * RenderState.scaleY * 0.5f), + RenderState.color, + xoff / 16.0f, yoff / 12.8f + 0.00055f, + (xoff + 1.0f) / 16.0f - 0.001f, yoff / 12.8f + 0.0021f + 0.01f, + xoff / 16.0f, (yoff + 1.0f) / 12.8f - 0.009f, + (xoff + 1.0f) / 16.0f - 0.001f, (yoff + 1.0f) / 12.8f - 0.0021f + 0.01f); +#ifdef MORE_LANGUAGES + /*}else if (IsJapaneseFont()) { + if (Details.dropShadowPosition != 0) { + CSprite2d::AddSpriteToBank(Details.bank + Details.style, // BUG: game doesn't add bank + 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), + 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 { + if (bDontPrint) return; + CSprite2d::AddToBuffer( + CRect(x, y, + x + 32.0f * RenderState.scaleX * w, + y + 32.0f * RenderState.scaleY * 0.5f), + RenderState.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::RenderFontBuffer() +{ + if (FontRenderStatePointer.pRenderState == (CFontRenderState*)FontRenderStateBuf) return; + + float textPosX; + float textPosY; + CRGBA color; + bool bBold = false; + bool bFlash = false; + + Sprite[RenderState.style].SetRenderState(); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RenderState = *(CFontRenderState*)&FontRenderStateBuf[0]; + textPosX = RenderState.fTextPosX; + textPosY = RenderState.fTextPosY; + color = RenderState.color; + tFontRenderStatePointer pRenderStateBufPointer; + pRenderStateBufPointer.pRenderState = (CFontRenderState*)&FontRenderStateBuf[0]; + for (++pRenderStateBufPointer.pRenderState; pRenderStateBufPointer.pStr < FontRenderStatePointer.pStr; pRenderStateBufPointer.pStr++) { + if (*pRenderStateBufPointer.pStr == '\0') { + tFontRenderStatePointer tmpPointer = pRenderStateBufPointer; + tmpPointer.pStr++; + tmpPointer.Align(); + if (tmpPointer.pStr >= FontRenderStatePointer.pStr) + break; + + RenderState = *(tmpPointer.pRenderState++); + + pRenderStateBufPointer = tmpPointer; + + textPosX = RenderState.fTextPosX; + textPosY = RenderState.fTextPosY; + color = RenderState.color; + } + if (*pRenderStateBufPointer.pStr == '~') { +#ifdef BUTTON_ICONS + PS2Symbol = BUTTON_NONE; +#endif + pRenderStateBufPointer.pStr = ParseToken(pRenderStateBufPointer.pStr, color, bFlash, bBold); +#ifdef BUTTON_ICONS + if(PS2Symbol != BUTTON_NONE) { + DrawButton(textPosX, textPosY); + textPosX += RenderState.scaleY * 17.0f; + PS2Symbol = BUTTON_NONE; + } +#endif + if (bFlash) { + if (CTimer::GetTimeInMilliseconds() - Details.nFlashTimer > 300) { + Details.bFlashState = !Details.bFlashState; + Details.nFlashTimer = CTimer::GetTimeInMilliseconds(); + } + Details.color.alpha = Details.bFlashState ? 0 : 255; + } + if (!RenderState.bIsShadow) + RenderState.color = color; + } + wchar c = *pRenderStateBufPointer.pStr; + c -= ' '; + if (RenderState.bFontHalfTexture) + c = FindNewCharacter(c); + else if (c > 155) + c = '\0'; + + if (RenderState.slant != 0.0f) + textPosY = (RenderState.slantRefX - textPosX) * RenderState.slant + RenderState.slantRefY; + PrintChar(textPosX, textPosY, c); + if (bBold) { + PrintChar(textPosX + 1.0f, textPosY, c); + PrintChar(textPosX + 2.0f, textPosY, c); + textPosX += 2.0f; + } +#ifdef FIX_BUGS + // PS2 uses different chars for some symbols + if (!RenderState.bFontHalfTexture && c == 30) c = 61; // wanted star +#endif + textPosX += RenderState.scaleX * GetCharacterWidth(c); + if (c == '\0') + textPosX += RenderState.fExtraSpace; + } + CSprite2d::RenderVertexBuffer(); + FontRenderStatePointer.pRenderState = (CFontRenderState*)FontRenderStateBuf; +} + +#if 0 //def 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; + + 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, uint32, wchar *start, wchar *end, float spwidth) +{ + wchar *s; + + if (RenderState.style != Details.style) { + RenderFontBuffer(); + RenderState.style = Details.style; + } + + float dropShadowPosition = Details.dropShadowPosition; + if (dropShadowPosition != 0.0f && (Details.style == FONT_BANK || Details.style == FONT_STANDARD)) { + CRGBA color = Details.color; + Details.color = Details.dropColor; + Details.dropShadowPosition = 0; + Details.bIsShadow = true; + if (Details.slant != 0.0f) { + Details.slantRefX += SCREEN_SCALE_X(dropShadowPosition); + Details.slantRefY += SCREEN_SCALE_Y(dropShadowPosition); + PrintString(SCREEN_SCALE_X(dropShadowPosition) + x, SCREEN_SCALE_Y(dropShadowPosition) + y, Details.anonymous_25, start, end, spwidth); + Details.slantRefX -= SCREEN_SCALE_X(dropShadowPosition); + Details.slantRefY -= SCREEN_SCALE_Y(dropShadowPosition); + } else { + PrintString(SCREEN_SCALE_X(dropShadowPosition) + x, SCREEN_SCALE_Y(dropShadowPosition) + y, Details.anonymous_25, start, end, spwidth); + } + Details.color = color; + Details.dropShadowPosition = dropShadowPosition; + Details.bIsShadow = false; + } + if (FontRenderStatePointer.pStr >= (wchar*)&FontRenderStateBuf[ARRAY_SIZE(FontRenderStateBuf)] - (end - start + 26)) // why 26? + RenderFontBuffer(); + CFontRenderState *pRenderState = FontRenderStatePointer.pRenderState; + pRenderState->fTextPosX = x; + pRenderState->fTextPosY = y; + pRenderState->scaleX = Details.scaleX; + pRenderState->scaleY = Details.scaleY; + pRenderState->color = Details.color; + pRenderState->fExtraSpace = spwidth; + pRenderState->slant = Details.slant; + pRenderState->slantRefX = Details.slantRefX; + pRenderState->slantRefY = Details.slantRefY; + pRenderState->bFontHalfTexture = Details.bFontHalfTexture; + pRenderState->proportional = Details.proportional; + pRenderState->style = Details.style; + pRenderState->bIsShadow = Details.bIsShadow; + FontRenderStatePointer.pRenderState++; + + for(s = start; s < end;){ + if (*s == '~') { + for (wchar *i = ParseToken(s); s != i; FontRenderStatePointer.pStr++) { + *FontRenderStatePointer.pStr = *(s++); + } + if (Details.bFlash) { + if (CTimer::GetTimeInMilliseconds() - Details.nFlashTimer > 300) { + Details.bFlashState = !Details.bFlashState; + Details.nFlashTimer = CTimer::GetTimeInMilliseconds(); + } + Details.color.a = Details.bFlashState ? 0 : 255; + } + } else + *(FontRenderStatePointer.pStr++) = *(s++); + } + *(FontRenderStatePointer.pStr++) = '\0'; + FontRenderStatePointer.Align(); +} +#endif + +void +CFont::PrintStringFromBottom(float x, float y, wchar *str) +{ + y -= (32.0f * Details.scaleY / 2.0f + 2.0f * Details.scaleY) * GetNumberLines(x, y, str); + if (Details.slant != 0.0f) + y -= ((Details.slantRefX - x) * Details.slant + Details.slantRefY); + PrintString(x, y, str); +} + +void +CFont::PrintString(float xstart, float ystart, wchar *s) +{ + CRect rect; + int numSpaces; + float lineLength; + float x, y; + bool first; + wchar *start, *t; + + Details.bFlash = false; + + if(*s == '*') + return; + + Details.anonymous_25++; + if(Details.background){ + RenderState.color = Details.color; + 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; +#if 0//def MORE_LANGUAGES + PrintString(xleft, y, start, s, spaceWidth, xstart); +#else + PrintString(xleft, y, Details.anonymous_25, 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 * Details.scaleY / 2.75f + 2.0f * Details.scaleY; + else +#endif + y += 32.0f * Details.scaleY * 0.5f + 2.0f * 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; +#if 0 //def 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 * Details.scaleY / 2.75f + 2.0f * 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; +#if 0 //def 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 * Details.scaleY / 2.75f + 2.0f * Details.scaleY; + numSpaces = 0; + first = true; + lineLength = 0.0f; + } +#else + PrintString(xleft, y, Details.anonymous_25, start, s, 0.0f); +#endif + } +} + +int +CFont::GetNumberLines(float xstart, float ystart, wchar *s) +{ + int n; + float x, y; + wchar *t; + n = 0; + +#if 0//def 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 * Details.scaleY / 2.75f + 2.0f * Details.scaleY; + else +#endif + y += 32.0f * Details.scaleY * 0.5f + 2.0f * 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 + if(Details.centre || Details.rightJustify) + 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; + if(Details.centre || Details.rightJustify) + x = 0.0f; + else + x = xstart; + numLines++; + y += 32.0f * Details.scaleY * 0.5f + 2.0f * 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 * Details.scaleY / 2.75f + 2.0f * Details.scaleY) * numLines + ystart + (4.0f / 2.75f); + rect->top = ystart - (4.0f / 2.75f); + } else { +#endif + rect->bottom = (32.0f * Details.scaleY * 0.5f + 2.0f * 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 * Details.scaleY / 2.75f + 2.0f * Details.scaleY) * numLines + ystart + (4.0f / 2.75f); + rect->top = ystart - (4.0f / 2.75f); + } else { +#endif + rect->bottom = (32.0f * Details.scaleY * 0.5f + 2.0f * 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 * Details.scaleY / 2.75f + 2.0f * Details.scaleY) * numLines + ystart + 2.0f + (4.0f / 2.75f); + else +#endif + rect->top = (32.0f * Details.scaleY * 0.5f + 2.0f * Details.scaleY) * numLines + ystart + 2.0f + 2.0f; + } +} + +float +CFont::GetCharacterWidth(wchar c) +{ +#ifdef MORE_LANGUAGES + if (IsJapanese()) { + if (!RenderState.proportional) + return Size[0][Details.style][192]; + if (c <= 94 || Details.style == FONT_HEADING || RenderState.style == FONT_BANK) { + switch (RenderState.style) + { + case FONT_JAPANESE: + return Size_jp[c]; + default: + return Size[0][RenderState.style][c]; + } + } + + switch (RenderState.style) + { + case FONT_JAPANESE: + return 29.4f; + case FONT_BANK: + return 10.0f; + default: + return Size[0][RenderState.style][c]; + } + } + + else if (RenderState.proportional) + return Size[LanguageSet][RenderState.style][c]; + else + return Size[LanguageSet][RenderState.style][209]; +#else + + if (RenderState.proportional) + return Size[RenderState.style][c]; + else + return Size[RenderState.style][209]; +#endif // MORE_LANGUAGES +} + +float +CFont::GetCharacterSize(wchar c) +{ +#ifdef MORE_LANGUAGES + + if (IsJapanese()) + { + if (!Details.proportional) + return Size[0][Details.style][209] * 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; + } + } + + switch (Details.style) + { + case FONT_JAPANESE: + return 29.4f * Details.scaleX; + case FONT_BANK: + return 10.0f * Details.scaleX; + default: + return Size[0][Details.style][c] * Details.scaleX; + } + } + else + { + if (!Details.bFontHalfTexture && c == 30) c = 61; // wanted star + if (Details.bFontHalfTexture) + c = FindNewCharacter(c); + if (Details.proportional) + return Size[LanguageSet][Details.style][c] * Details.scaleX; + else + return Size[LanguageSet][Details.style][209] * Details.scaleX; + } +#else + +#ifdef FIX_BUGS + // PS2 don't call FindNewCharacter in here at all, and also uses different chars for some symbols + if (!Details.bFontHalfTexture && c == 30) c = 61; // wanted star +#endif + if (Details.bFontHalfTexture) + c = FindNewCharacter(c); + if (Details.proportional) + return Size[Details.style][c] * Details.scaleX; + else + return Size[Details.style][209] * 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) { + case 'U': + case 'D': + case '<': + case '>': + case 'X': + case 'O': + case 'Q': + case 'T': + case 'K': + case 'M': + case 'A': + case 'J': + case 'V': + case 'C': + case '(': + case ')': + 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 (wchar c = *s; (c != ' ' || spaces) && c != '\0'; c = *(++s)) { + if (c == '~') { + s++; +#ifdef BUTTON_ICONS + switch (*s) { + case 'U': + case 'D': + case '<': + case '>': + case 'X': + case 'O': + case 'Q': + case 'T': + case 'K': + case 'M': + case 'A': + case 'J': + case 'V': + case 'C': + case '(': + case ')': + w += 17.0f * Details.scaleY; + break; + default: + break; + } +#endif + while (*s != '~') { + s++; + } + } + else { + w += GetCharacterSize(c - ' '); + } + } + } + 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++; + } + } + return s; +} + +wchar* +CFont::ParseToken(wchar* str, CRGBA &color, bool &flash, bool &bold) +{ + Details.anonymous_23 = false; + wchar *s = str + 1; + if (Details.color.r || Details.color.g || Details.color.b) + { + switch (*s) + { + case 'B': + bold = !bold; + break; + case 'b': + color.r = 27; + color.g = 89; + color.b = 130; + break; + case 'f': + flash = !flash; + break; + case 'g': + color.r = 255; + color.g = 150; + color.b = 225; + break; + case 'h': + color.r = 225; + color.g = 225; + color.b = 225; + break; + case 'l': + color.r = 0; + color.g = 0; + color.b = 0; + break; + case 'o': + color.r = 229; + color.g = 125; + color.b = 126; + break; + case 'p': + color.r = 168; + color.g = 110; + color.b = 252; + break; + case 'q': + color.r = 199; + color.g = 144; + color.b = 203; + break; + case 'r': + color.r = 255; + color.g = 150; + color.b = 225; + break; + case 't': + color.r = 86; + color.g = 212; + color.b = 146; + break; + case 'w': + color.r = 175; + color.g = 175; + color.b = 175; + break; +#ifdef FIX_BUGS + case 'x': + color.r = 0; + color.g = 255; + color.b = 255; + break; +#else + case 'x': + color.r = 132; + color.g = 146; + color.b = 197; + break; +#endif + case 'y': + color.r = 255; + color.g = 227; + color.b = 79; + break; +#ifdef BUTTON_ICONS + case 'U': PS2Symbol = BUTTON_UP; break; + case 'D': PS2Symbol = BUTTON_DOWN; break; + case '<': PS2Symbol = BUTTON_LEFT; break; + case '>': PS2Symbol = BUTTON_RIGHT; break; + 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; + case 'H': PS2Symbol = BUTTON_RSTICK_UP; break; + case 'L': PS2Symbol = BUTTON_RSTICK_DOWN; break; + case '(': PS2Symbol = BUTTON_RSTICK_LEFT; break; + case ')': PS2Symbol = BUTTON_RSTICK_RIGHT; break; +#endif + default: + break; + } + } + while (*s != '~') + ++s; + if (*(++s) == '~') + s = ParseToken(s, color, flash, bold); + return s; +} + +#if 0//def MORE_LANGUAGES +wchar* +CFont::ParseToken(wchar *s, 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 + case 'U': PS2Symbol = BUTTON_UP; break; + case 'D': PS2Symbol = BUTTON_DOWN; break; + case '<': PS2Symbol = BUTTON_LEFT; break; + case '>': PS2Symbol = BUTTON_RIGHT; break; + 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; + case 'H': PS2Symbol = BUTTON_RSTICK_UP; break; + case 'L': PS2Symbol = BUTTON_RSTICK_DOWN; break; + case '(': PS2Symbol = BUTTON_RSTICK_LEFT; break; + case ')': PS2Symbol = BUTTON_RSTICK_RIGHT; break; +#endif + } + } else if (IsJapanese()) { + if ((*s & 0x7FFF) == 'N' || (*s & 0x7FFF) == 'n') + NewLine = true; + } + while ((!IsJapanese() || (*s != JAP_TERMINATION)) && *s != '~') s++; + return s + 1; +} +#else +wchar* +CFont::ParseToken(wchar *s) +{ + Details.anonymous_23 = false; + s++; + if(Details.color.r || Details.color.g || Details.color.b) + switch(*s){ + case 'B': + Details.bBold = !Details.bBold; + break; + case 'N': + case 'n': + NewLine = true; + break; + case 'b': + Details.color.r = 27; + Details.color.g = 89; + Details.color.b = 130; + Details.anonymous_23 = true; + break; + case 'f': + Details.bFlash = !Details.bFlash; + if (!Details.bFlash) + Details.color.a = 255; + break; + case 'g': + Details.color.r = 255; + Details.color.g = 150; + Details.color.b = 225; + Details.anonymous_23 = true; + break; + case 'h': + Details.color.r = 225; + Details.color.g = 225; + Details.color.b = 225; + Details.anonymous_23 = true; + break; + case 'l': + Details.color.r = 0; + Details.color.g = 0; + Details.color.b = 0; + Details.anonymous_23 = true; + break; + case 'o': + Details.color.r = 229; + Details.color.g = 125; + Details.color.b = 126; + Details.anonymous_23 = true; + break; + case 'p': + Details.color.r = 168; + Details.color.g = 110; + Details.color.b = 252; + Details.anonymous_23 = true; + break; + case 'q': + Details.color.r = 199; + Details.color.g = 144; + Details.color.b = 203; + Details.anonymous_23 = true; + break; + case 'r': + Details.color.r = 255; + Details.color.g = 150; + Details.color.b = 225; + Details.anonymous_23 = true; + break; + case 't': + Details.color.r = 86; + Details.color.g = 212; + Details.color.b = 146; + Details.anonymous_23 = true; + break; + case 'w': + Details.color.r = 175; + Details.color.g = 175; + Details.color.b = 175; + Details.anonymous_23 = true; + break; + case 'x': +#ifdef FIX_BUGS + Details.color.r = 0; + Details.color.g = 255; + Details.color.b = 255; +#else + Details.color.r = 132; + Details.color.g = 146; + Details.color.b = 197; +#endif + Details.anonymous_23 = true; + break; + case 'y': + Details.color.r = 255; + Details.color.g = 227; + Details.color.b = 79; + Details.anonymous_23 = true; + break; +#ifdef BUTTON_ICONS + case 'U': PS2Symbol = BUTTON_UP; break; + case 'D': PS2Symbol = BUTTON_DOWN; break; + case '<': PS2Symbol = BUTTON_LEFT; break; + case '>': PS2Symbol = BUTTON_RIGHT; break; + 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; + case 'H': PS2Symbol = BUTTON_RSTICK_UP; break; + case 'L': PS2Symbol = BUTTON_RSTICK_DOWN; break; + case '(': PS2Symbol = BUTTON_RSTICK_LEFT; break; + case ')': PS2Symbol = BUTTON_RSTICK_RIGHT; break; +#endif + } + while(*s != '~') s++; + if (*(++s) == '~') + s = ParseToken(s); + return s; +} +#endif + +void +CFont::FilterOutTokensFromString(wchar *str) +{ + int newIdx = 0; + wchar copy[256], *c; + UnicodeStrcpy(copy, str); + + for (c = copy; *c != '\0'; c++) { + if (*c == '~') { + c++; + while (*c != '~') c++; + } else { + str[newIdx++] = *c; + } + } + str[newIdx] = '\0'; +} + +void +CFont::DrawFonts(void) +{ + RenderFontBuffer(); +} + +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::SetPropOff(void) +{ + Details.proportional = false; +} + +void +CFont::SetPropOn(void) +{ + Details.proportional = true; +} + +void +CFont::SetFontStyle(int16 style) +{ + if (style == FONT_HEADING) { + Details.style = FONT_STANDARD; + Details.bFontHalfTexture = true; + } else { + Details.style = style; + Details.bFontHalfTexture = false; + } +} + +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::FindNewCharacter(wchar c) +{ + if (c >= 16 && c <= 26) return c + 128; + if (c >= 8 && c <= 9) return c + 86; + if (c == 4) return c + 89; + if (c == 7) return 206; + if (c == 14) return 207; + if (c >= 33 && c <= 58) return c + 122; + if (c >= 65 && c <= 90) return c + 90; + if (c >= 96 && c <= 118) return c + 85; + if (c >= 119 && c <= 140) return c + 62; + if (c >= 141 && c <= 142) return 204; + if (c == 143) return 205; + if (c == 1) return 208; + return c; +} + +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/miami/renderer/Font.h b/src/miami/renderer/Font.h new file mode 100644 index 00000000..02e7df3b --- /dev/null +++ b/src/miami/renderer/Font.h @@ -0,0 +1,217 @@ +#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); +void UnicodeMakeUpperCase(wchar *dst, const wchar *src); + +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; + bool8 bIsShadow; + bool8 bFlash; + bool8 bBold; + float alphaFade; + CRGBA backgroundColor; + float wrapX; + float centreSize; + float rightJustifyWrap; + int16 style; + bool8 bFontHalfTexture; + uint32 bank; + int16 dropShadowPosition; + CRGBA dropColor; + bool8 bFlashState; + int nFlashTimer; + bool8 anonymous_23; + uint32 anonymous_25; +}; + +struct CFontRenderState +{ + uint32 anonymous_0; + float fTextPosX; + float fTextPosY; + float scaleX; + float scaleY; + CRGBA color; + float fExtraSpace; + float slant; + float slantRefX; + float slantRefY; + bool8 bIsShadow; + bool8 bFontHalfTexture; + bool8 proportional; + bool8 anonymous_14; + int16 style; +}; + +class CSprite2d; + +enum { + FONT_BANK, + FONT_STANDARD, + FONT_HEADING, +#ifdef MORE_LANGUAGES + FONT_JAPANESE, +#endif + MAX_FONTS = FONT_HEADING +}; + +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, + BUTTON_UP, + BUTTON_DOWN, + BUTTON_LEFT, + BUTTON_RIGHT, + BUTTON_CROSS, + BUTTON_CIRCLE, + BUTTON_SQUARE, + BUTTON_TRIANGLE, + BUTTON_L1, + BUTTON_L2, + BUTTON_L3, + BUTTON_R1, + BUTTON_R2, + BUTTON_R3, + BUTTON_RSTICK_UP, + BUTTON_RSTICK_DOWN, + BUTTON_RSTICK_LEFT, + BUTTON_RSTICK_RIGHT, + MAX_BUTTON_ICONS +}; +#endif // BUTTON_ICONS + + +class CFont +{ +#ifdef MORE_LANGUAGES + static int16 Size[LANGSET_MAX][MAX_FONTS][210]; + static uint8 LanguageSet; + static int32 Slot; +#else + static int16 Size[MAX_FONTS][210]; +#endif + static bool16 NewLine; +public: + static CSprite2d Sprite[MAX_FONTS]; + static CFontDetails Details; + static CFontRenderState RenderState; + +#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); +#ifdef XBOX_SUBTITLES + static void PrintStringFromBottom(float x, float y, wchar *str); + 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, uint32, wchar *start, wchar *end, float spwidth); +//#endif + static void PrintStringFromBottom(float x, float y, wchar *str); + 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, bool japShit = false); +//#else + static uint16 *ParseToken(wchar *s); + static uint16 *ParseToken(wchar *s, CRGBA &color, bool &flash, bool &bold); +//#endif + static void DrawFonts(void); + static void RenderFontBuffer(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); + static wchar FindNewCharacter(wchar c); + static void FilterOutTokensFromString(wchar*); +#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); } +#endif +}; diff --git a/src/miami/renderer/Glass.cpp b/src/miami/renderer/Glass.cpp new file mode 100644 index 00000000..40a5fa0d --- /dev/null +++ b/src/miami/renderer/Glass.cpp @@ -0,0 +1,1027 @@ +#include "common.h" + +#include "Glass.h" +#include "Timer.h" +#include "Object.h" +#include "Vehicle.h" +#include "Pools.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" +#include "SurfaceTable.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 256 +#define TEMPBUFFERINDEXHILIGHTSIZE 512 + +#define TEMPBUFFERVERTSHATTEREDOFFSET TEMPBUFFERVERTHILIGHTSIZE +#define TEMPBUFFERINDEXSHATTEREDOFFSET TEMPBUFFERINDEXHILIGHTSIZE +#define TEMPBUFFERVERTSHATTEREDSIZE 384 +#define TEMPBUFFERINDEXSHATTEREDSIZE 768 + +#define TEMPBUFFERVERTREFLECTIONOFFSET TEMPBUFFERVERTSHATTEREDSIZE +#define TEMPBUFFERINDEXREFLECTIONOFFSET TEMPBUFFERINDEXSHATTEREDSIZE +#define TEMPBUFFERVERTREFLECTIONSIZE 512 +#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 + if ( m_bCarGlass ) + GetPosition() += m_vecMoveSpeed * CTimer::GetTimeStep() * 0.35f; + else + GetPosition() += m_vecMoveSpeed * CTimer::GetTimeStep(); + + // Apply Gravity + if ( m_bCarGlass ) + m_vecMoveSpeed.z -= 0.01f * CTimer::GetTimeStep(); + else + 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 }; + + if ( !m_bCarGlass ) + { + 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 + + if ( m_bCarGlass && color < 64 ) + color = 64; + + 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 = 140; + if ( distToCamera > 30.0f ) + shatteredColor = int32((1.0f - (distToCamera - 30.0f) * 4.0f / 40.0f) * 140); + + 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 center, + float moveSpeed, bool cracked, bool explosion, int32 stepmul, bool carGlass) +{ + 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 = stepmul * (uint32)upSteps; + if ( ysteps > 3 ) ysteps = 3; + + uint32 xsteps = stepmul * (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; + pane->GetUp() = (up * upScl) / upLen; + + 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() - center; + 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: + case 2: + pane->m_nTimer = CTimer::GetTimeInMilliseconds(); + break; + case 1: + float dist = (pane->GetPosition() - center).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_bCarGlass = carGlass; + 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); + + if ( col->numTriangles == 2 ) + { + CVector a = col->vertices[0].Get(); + CVector b = col->vertices[1].Get(); + CVector c = col->vertices[2].Get(); + CVector d = 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)); + + CVector pa = object->GetMatrix() * CVector(minx, miny, minz); + CVector pb = object->GetMatrix() * CVector(maxx, maxy, minz); + + if ( amount > 300.0f ) + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_BREAK_L, object->GetPosition()); + + GeneratePanesForWindow(0, + pa, + CVector(0.0f, 0.0f, maxz-minz), + pb - pa, + speed, point, 0.1f, !!object->bGlassCracked, explosion, 1, false); + } + else + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_BREAK_S, object->GetPosition()); + + GeneratePanesForWindow(1, + pa, + CVector(0.0f, 0.0f, maxz-minz), + pb - pa, + speed, point, 0.1f, !!object->bGlassCracked, explosion, 1, false); + } + } + + object->bGlassBroken = true; + object->bIsVisible = false; + object->bUsesCollision = false; +} + +void +CGlass::WindowRespondsToSoftCollision(CEntity *entity, float amount) +{ + ASSERT(entity!=nil); + + CObject *object = (CObject *)entity; + + if ( entity->bUsesCollision && 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->bUsesCollision ) + { + 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; + + if ( object->bUsesCollision ) + { + 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; + } + } +} + + +void +CGlass::CarWindscreenShatters(CVehicle *vehicle, bool unk) +{ + ASSERT(vehicle!=nil); + + CColModel *col = vehicle->GetColModel(); + ASSERT(col!=nil); + + if ( col->numTriangles < 2 ) + return; + + CColTriangle *tria = nil; + int32 triIndex = -1; + CColTriangle *trib = nil; + + for ( int32 i = 0; i < col->numTriangles; i++ ) + { + CColTriangle *tri = &col->triangles[i]; + if ( tri->surface == SURFACE_GLASS ) + { + if ( tria ) + { + trib = tri; + break; + } + + triIndex = i; + tria = tri; + } + } + + if ( trib == nil ) + return; + + CCollision::CalculateTrianglePlanes(col); + + CColTrianglePlane *triPlanes = col->trianglePlanes; + + if ( triPlanes == nil ) + return; + + CVector planeNormal; + triPlanes[triIndex].GetNormal(planeNormal); + planeNormal = Multiply3x3(vehicle->GetMatrix(), planeNormal); + + CVector vec1 = CrossProduct(vehicle->GetRight(), planeNormal); + vec1.Normalise(); + + CVector vec2 = CrossProduct(planeNormal, vehicle->GetUp()); + vec2.Normalise(); + + CVector v[6]; + float proj1[6]; + float proj2[6]; + + v[0] = col->vertices[tria->a].Get(); + v[1] = col->vertices[tria->b].Get(); + v[2] = col->vertices[tria->c].Get(); + + v[3] = col->vertices[trib->a].Get(); + v[4] = col->vertices[trib->b].Get(); + v[5] = col->vertices[trib->c].Get(); + + v[0] = vehicle->GetMatrix() * v[0]; + v[1] = vehicle->GetMatrix() * v[1]; + v[2] = vehicle->GetMatrix() * v[2]; + v[3] = vehicle->GetMatrix() * v[3]; + v[4] = vehicle->GetMatrix() * v[4]; + v[5] = vehicle->GetMatrix() * v[5]; + + proj1[0] = DotProduct(v[0], vec1); + proj2[0] = DotProduct(v[0], vec2); + proj1[1] = DotProduct(v[1], vec1); + proj2[1] = DotProduct(v[1], vec2); + proj1[2] = DotProduct(v[2], vec1); + proj2[2] = DotProduct(v[2], vec2); + + proj1[3] = DotProduct(v[3], vec1); + proj2[3] = DotProduct(v[3], vec2); + proj1[4] = DotProduct(v[4], vec1); + proj2[4] = DotProduct(v[4], vec2); + proj1[5] = DotProduct(v[5], vec1); + proj2[5] = DotProduct(v[5], vec2); + + int32 originIndex = 0; + float max1 = proj1[0]; + float max2 = proj2[0]; + float origin = proj1[0] + proj2[0]; + + for ( int32 i = 1; i < 6; i++ ) + { + float o = proj1[i] + proj2[i]; + if ( o < origin ) + { + origin = o; + originIndex = i; + } + + if ( proj1[i] > max1 ) + max1 = proj1[i]; + if ( proj2[i] > max2 ) + max2 = proj2[i]; + } + + float bound1 = max1 - proj1[originIndex]; + float bound2 = max2 - proj2[originIndex]; + + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_BREAK_L, vehicle->GetPosition()); + + CVector center = v[originIndex] + ((0.5f*bound1) * vec1) + ((0.5f*bound2) * vec2); + CVector speed = vehicle->m_vecMoveSpeed; + CVector right = bound2 * vec2; + CVector up = bound1 * vec1; + CVector pos = v[originIndex]; + + GeneratePanesForWindow(2, pos, up, right, speed, center, 0.1f, false, false, 2, true); +} + +bool +CGlass::HasGlassBeenShatteredAtCoors(float x, float y, float z) +{ + CEntity *entity = nil; + float dist = 20.0f; + + int32 nStartX = Max(CWorld::GetSectorIndexX(x - 30.0f), 0); + int32 nStartY = Max(CWorld::GetSectorIndexY(y - 30.0f), 0); + int32 nEndX = Min(CWorld::GetSectorIndexX(x + 30.0f), NUMSECTORS_X-1); + int32 nEndY = Min(CWorld::GetSectorIndexY(y + 30.0f), NUMSECTORS_Y-1); + + CWorld::AdvanceCurrentScanCode(); + + for ( int32 ys = nStartY; ys <= nEndY; ys++ ) + { + for ( int32 xs = nStartX; xs <= nEndX; xs++ ) + { + CSector *sector = CWorld::GetSector(xs, ys); + + ASSERT(sector != nil); + + FindWindowSectorList(sector->m_lists[ENTITYLIST_OBJECTS], &dist, &entity, x, y, z); + FindWindowSectorList(sector->m_lists[ENTITYLIST_DUMMIES], &dist, &entity, x, y, z); + } + } + + if ( entity ) + { + if ( entity->GetType() == ENTITY_TYPE_DUMMY ) + return false; + + return !!((CObject*)entity)->bGlassBroken; + } + + return false; +} + +void +CGlass::FindWindowSectorList(CPtrList &list, float *dist, CEntity **entity, float x, float y, float z) +{ + ASSERT(dist!=nil); + ASSERT(entity!=nil); + + CPtrNode *node = list.first; + + while ( node != nil ) + { + CEntity *ent = (CEntity *)node->item; + uint16 scanCode = ent->m_scanCode; + node = node->next; + + ASSERT(ent!=nil); + + if ( IsGlass(ent->GetModelIndex()) ) + { + if ( scanCode != CWorld::GetCurrentScanCode() ) + { + ent->m_scanCode = CWorld::GetCurrentScanCode(); + + float dst = (CVector(x,y,z) - ent->GetPosition()).Magnitude(); + + if ( dst < *dist ) + { + *dist = dst; + *entity = ent; + } + } + } + } +} + +void +CGlass::BreakGlassPhysically(CVector pos, float radius) +{ + static uint32 breakTime = 0; + + if ( CTimer::GetTimeInMilliseconds() < breakTime + 1000 && CTimer::GetTimeInMilliseconds() >= breakTime ) + return; + + CColSphere sphere; + sphere.piece = 0; + sphere.radius = radius; + sphere.surface = 0; + + for ( int32 i = CPools::GetObjectPool()->GetSize() - 1; i >= 0; i-- ) + { + CObject *object = CPools::GetObjectPool()->GetSlot(i); + if (object) + { + if ( IsGlass(object->GetModelIndex()) ) + { + if ( object->bUsesCollision ) + { + CColModel *col = object->GetColModel(); + ASSERT(col!=nil); + + if ( col->numTriangles < 2 ) + continue; + + bool hit = false; + + CVector dist = pos - object->GetPosition(); + + sphere.center.x = DotProduct(dist, object->GetRight()); + sphere.center.y = DotProduct(dist, object->GetForward()); + sphere.center.z = DotProduct(dist, object->GetUp()); + + CCollision::CalculateTrianglePlanes(col); + + for ( int32 j = 0; j < col->numTriangles; j++ ) + { + if ( CCollision::TestSphereTriangle(sphere, + col->vertices, col->triangles[j], col->trianglePlanes[j]) ) + { + hit = true; + } + } + + if ( hit ) + { + breakTime = CTimer::GetTimeInMilliseconds(); + + if ( object->bGlassCracked ) + { + CVector a = col->vertices[0].Get(); + CVector b = col->vertices[1].Get(); + CVector c = col->vertices[2].Get(); + CVector d = 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)); + + CVector pa = object->GetMatrix() * CVector(minx, miny, minz); + CVector pb = object->GetMatrix() * CVector(maxx, maxy, minz); + + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_BREAK_S, object->GetPosition()); + + GeneratePanesForWindow(1, + pa, + CVector(0.0f, 0.0f, maxz-minz), + pb - pa, + CVector(0.0f, 0.0f, 0.0f), pos, 0.1f, !!object->bGlassCracked, false, 1, false); + + object->bGlassBroken = true; + object->bIsVisible = false; + object->bUsesCollision = false; + } + else + { + PlayOneShotScriptObject(SCRIPT_SOUND_GLASS_CRACK, object->GetPosition()); + object->bGlassCracked = true; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/miami/renderer/Glass.h b/src/miami/renderer/Glass.h new file mode 100644 index 00000000..f1c85779 --- /dev/null +++ b/src/miami/renderer/Glass.h @@ -0,0 +1,59 @@ +#pragma once + +class CEntity; +class CVehicle; +class CPtrList; + +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; + bool m_bCarGlass; + + 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 center, float moveSpeed, bool cracked, bool explosion, int32 stepmul, bool carGlass); + 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); + static void CarWindscreenShatters(CVehicle *vehicle, bool unk); + static bool HasGlassBeenShatteredAtCoors(float x, float y, float z); + static void FindWindowSectorList(CPtrList &list, float *dist, CEntity **entity, float x, float y, float z); + static void BreakGlassPhysically(CVector pos, float radius); +}; \ No newline at end of file diff --git a/src/miami/renderer/Hud.cpp b/src/miami/renderer/Hud.cpp new file mode 100644 index 00000000..45ea3278 --- /dev/null +++ b/src/miami/renderer/Hud.cpp @@ -0,0 +1,2091 @@ +#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" +#include "CutsceneMgr.h" +#include "Stats.h" +#include "main.h" +#include "General.h" +#include "VarConsole.h" + +#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(0, 207, 133, 255); +CRGBA AMMO_COLOR(255, 150, 225, 255); +CRGBA HEALTH_COLOR(255, 150, 225, 255); +CRGBA ARMOUR_COLOR(185, 185, 185, 255); +CRGBA NOTWANTED_COLOR(27, 89, 130, 255); +CRGBA WANTED_COLOR_FLASH(62, 141, 181, 255); +CRGBA WANTED_COLOR(97, 194, 247, 255); +CRGBA ZONE_COLOR(45, 155, 90, 255); +CRGBA VEHICLE_COLOR(97, 194, 247, 255); +CRGBA CLOCK_COLOR(97, 194, 247, 255); +CRGBA TIMER_COLOR(97, 194, 247, 255); +CRGBA COUNTER_COLOR(97, 194, 247, 255); +CRGBA PAGER_COLOR(32, 162, 66, 205); +CRGBA RADARDISC_COLOR(255, 255, 255, 255); +CRGBA BIGMESSAGE_COLOR(255, 150, 225, 255); +CRGBA WASTEDBUSTED_COLOR(0, 207, 133, 255); +CRGBA ODDJOB_COLOR(0, 207, 133, 255); +CRGBA ODDJOB2_COLOR(97, 194, 247, 255); +CRGBA MISSIONTITLE_COLOR(220, 172, 2, 255); + +wchar CHud::m_HelpMessage[HELP_MSG_LENGTH]; +wchar CHud::m_LastHelpMessage[HELP_MSG_LENGTH]; +uint32 CHud::m_HelpMessageState; +uint32 CHud::m_HelpMessageTimer; +int32 CHud::m_HelpMessageFadeTimer; +wchar CHud::m_HelpMessageToPrint[HELP_MSG_LENGTH]; +float CHud::m_HelpMessageDisplayTime; +bool CHud::m_HelpMessageDisplayForever; +bool CHud::m_HelpMessageQuick; +uint32 CHud::m_ZoneState; +int32 CHud::m_ZoneFadeTimer; +uint32 CHud::m_ZoneNameTimer; +wchar *CHud::m_pZoneName; +wchar *CHud::m_pLastZoneName; +wchar *CHud::m_ZoneToPrint; +uint32 CHud::m_VehicleState; +int32 CHud::m_VehicleFadeTimer; +uint32 CHud::m_VehicleNameTimer; +wchar *CHud::m_VehicleName; +wchar *CHud::m_pLastVehicleName; +wchar *CHud::m_pVehicleNameToPrint; +wchar CHud::m_Message[256]; +wchar CHud::m_PagerMessage[256]; +bool CHud::m_Wants_To_Draw_Hud; +bool CHud::m_Wants_To_Draw_3dMarkers; +wchar CHud::m_BigMessage[6][128]; +int16 CHud::m_ItemToFlash; +bool CHud::m_HideRadar; +int32 CHud::m_ClockState; + +// These aren't really in CHud +float CHud::BigMessageInUse[6]; +float CHud::BigMessageAlpha[6]; +float CHud::BigMessageX[6]; +float CHud::OddJob2OffTimer; +bool CHud::CounterOnLastFrame[NUMONSCREENCOUNTERS]; +float CHud::OddJob2XOffset; +uint16 CHud::CounterFlashTimer[NUMONSCREENCOUNTERS]; +uint16 CHud::OddJob2Timer; +bool CHud::TimerOnLastFrame; +int16 CHud::OddJob2On; +uint16 CHud::TimerFlashTimer; +int16 CHud::PagerSoundPlayed; +int32 CHud::SpriteBrightness; +float CHud::PagerXOffset; +int16 CHud::PagerTimer; +int16 CHud::PagerOn; + +wchar *prevChaseString; + +uint32 CHud::m_WantedFadeTimer; +uint32 CHud::m_WantedState; +uint32 CHud::m_WantedTimer; +uint32 CHud::m_EnergyLostFadeTimer; +uint32 CHud::m_EnergyLostState; +uint32 CHud::m_EnergyLostTimer; +uint32 CHud::m_DisplayScoreFadeTimer; +uint32 CHud::m_DisplayScoreState; +uint32 CHud::m_DisplayScoreTimer; +uint32 CHud::m_WeaponFadeTimer; +uint32 CHud::m_WeaponState; +uint32 CHud::m_WeaponTimer; + +uint32 CHud::m_LastDisplayScore; +uint32 CHud::m_LastWanted; +uint32 CHud::m_LastWeapon; +uint32 CHud::m_LastTimeEnergyLost; + +CSprite2d CHud::Sprites[NUM_HUD_SPRITES]; + +struct +{ + const char *name; + const char *mask; +} WeaponFilenames[] = { + { "fist", "fistm" }, + { "brassk", "brasskA" }, + { "screw", "screwA" }, + { "golf", "golfA" }, + { "nightstick", "nightstickA" }, + { "knife", "knifeA" }, + { "bat", "batm" }, + { "hammer", "hammerA" }, + { "cleaver", "cleaverA" }, + { "machete", "macheteA" }, + { "sword", "swordA" }, + { "chainsaw", "chainsawA" }, + { "grenade", "grenadeA" }, + { "grenade", "grenadeA" }, + { "teargas", "teargasA" }, + { "molotov", "molotovA" }, + { "rocket", "rocketA" }, + { "handGun1", "handGun1A" }, + { "", "" }, + { "python", "pythonA" }, + { "chromegun", "chromegunA" }, + { "spasshotGun", "spasshotGunA" }, + { "stubshotGun", "stubshotGunA" }, + { "tec9", "tec9A" }, + { "uzi1", "uzi1A" }, + { "uzi2", "uzi2A" }, + { "mp5", "mp5A" }, + { "", "" }, + { "m4", "m4A" }, + { "ruger", "rugerA" }, + { "sniper", "sniperA" }, + { "laserscope", "laserscopeA" }, + { "", "" }, + { "rocket", "rocketA" }, + { "flamer", "flamerA" }, + { "m60", "m60A" }, + { "minigun", "minigunA" }, + { "bomb", "bombA" }, + { "", "" }, + { "camera", "cameraA" }, + { "", "" }, + { "siterocket", "siterocket" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "radardisc", "radardisc" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "", "" }, + { "sitesniper", "sitesniperm" }, + { "siteM16", "siteM16m" }, + { "sitelaser", "sitelaserm" }, + { "laserdot", "laserdotm" }, + { "viewfinder_128", "viewfinder_128m" }, + { "bleeder", "" } +}; + +RwTexture *gpSniperSightTex; +RwTexture *gpRocketSightTex; +RwTexture *gpLaserSightTex; +RwTexture *gpLaserDotTex; +RwTexture *gpViewFinderTex; + +void CHud::Draw() +{ + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + + // disable hud via second controller + if (CPad::GetPad(1)->GetStartJustDown()) + m_Wants_To_Draw_Hud = !m_Wants_To_Draw_Hud; + + if (CReplay::IsPlayingBack()) + return; + + if (m_Wants_To_Draw_Hud && !TheCamera.m_WideScreenOn) { + // unused statics in here + bool DrawCrossHair = false; + bool CrossHairHidesHud = false; + bool DrawCrossHairPC = false; + + CPlayerPed *playerPed = FindPlayerPed(); + eWeaponType WeaponType = playerPed->GetWeapon()->m_eWeaponType; + int32 Mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + + if ((Mode == CCam::MODE_SNIPER || Mode == CCam::MODE_ROCKETLAUNCHER || Mode == CCam::MODE_M16_1STPERSON || Mode == CCam::MODE_HELICANNON_1STPERSON || Mode == CCam::MODE_CAMERA) + && playerPed && !playerPed->GetWeapon()->IsTypeMelee()) + DrawCrossHair = true; + + if (Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT || Mode == CCam::MODE_SNIPER_RUNABOUT) + DrawCrossHairPC = true; + if (TheCamera.Cams[TheCamera.ActiveCam].Using3rdPersonMouseCam() && (!CPad::GetPad(0)->GetLookBehindForPed() || TheCamera.m_bPlayerIsInGarage) + || Mode == CCam::MODE_1STPERSON_RUNABOUT) { + if (playerPed) { + if (playerPed->m_nPedState != PED_ENTER_CAR && playerPed->m_nPedState != PED_CARJACK) { + + if (WeaponType >= WEAPONTYPE_COLT45 && WeaponType <= WEAPONTYPE_RUGER + || WeaponType == WEAPONTYPE_M60 || WeaponType == WEAPONTYPE_MINIGUN + || WeaponType == WEAPONTYPE_FLAMETHROWER) { + DrawCrossHairPC = 1; + } + } + } + } + + if (DrawCrossHair || DrawCrossHairPC) { + 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 * 0.03f * (0.25f * fStep + 0.75f); + CRect rect; + 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 (playerPed && (WeaponType == WEAPONTYPE_M4 || WeaponType == WEAPONTYPE_RUGER || WeaponType == WEAPONTYPE_M60)) { + 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 { + if (Mode == CCam::MODE_M16_1STPERSON || + Mode == CCam::MODE_M16_1STPERSON_RUNABOUT || + Mode == CCam::MODE_HELICANNON_1STPERSON) { + 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); + } + 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); + } + else if (Mode == CCam::MODE_ROCKETLAUNCHER || Mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT) { + 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(40.0f), SCREEN_SCALE_Y(40.0f), (100.0f * fMultBright), (200.0f * fMultBright), (100.0f * fMultBright), 255, 1.0f, 255); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + } + else { + int sprite = HUD_SITESNIPER; + float xOffset = SCREEN_SCALE_X(210.0f); + float yOffset = SCREEN_SCALE_Y(210.0f); + + if (FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_LASERSCOPE) + sprite = HUD_SITELASER; + + if (FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_CAMERA) { + sprite = HUD_VIEWFINDER; + CrossHairHidesHud = true; + xOffset = SCREEN_SCALE_X(256.0f); + yOffset = SCREEN_SCALE_Y(192.0f); + } + + rect.left = SCREEN_WIDTH/2 - xOffset; + rect.top = SCREEN_HEIGHT/2 - yOffset; + rect.right = SCREEN_WIDTH/2; + rect.bottom = SCREEN_HEIGHT/2; + Sprites[sprite].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 - yOffset; + rect.right = SCREEN_WIDTH/2 + xOffset; + rect.bottom = SCREEN_HEIGHT/2; + Sprites[sprite].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 - xOffset; + rect.top = SCREEN_HEIGHT/2; + rect.right = SCREEN_WIDTH/2; + rect.bottom = SCREEN_HEIGHT/2 + yOffset; + Sprites[sprite].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 + xOffset; + rect.bottom = SCREEN_HEIGHT/2 + yOffset; + Sprites[sprite].Draw(CRect(rect), CRGBA(255, 255, 255, 255), + 0.99f, 0.99f, 0.01f, 0.99f, 0.99f, 0.01f, 0.01f, 0.01f); + + CVector dotPos; + float size = 25.0f; + if (FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_LASERSCOPE && FindPlayerPed()->GetWeapon()->LaserScopeDot(&dotPos, &size)) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); +#ifdef FIX_BUGS + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); +#else + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVDESTALPHA); +#endif + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpLaserDotTex)); +#ifdef FIX_BUGS + int intensity = CGeneral::GetRandomNumberInRange(0, 37); +#else + int intensity = CGeneral::GetRandomNumberInRange(0, 35); +#endif + CSprite::RenderOneXLUSprite(dotPos.x, dotPos.y, dotPos.z, + SCREEN_SCALE_X(size), SCREEN_SCALE_Y(size), intensity - 36, 0, 0, intensity - 36, 1.0f, 127); + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + } + } + } + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + } + else { + SpriteBrightness = 0; + } + + if (CrossHairHidesHud) + return; + + /* + DrawMoneyCounter + */ + + wchar sPrint[16]; + wchar sPrintIcon[16]; + char sTemp[16]; + float alpha; + + if (m_LastDisplayScore == CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney) { + alpha = DrawFadeState(HUD_SCORE_FADING, 0); + } else { + alpha = DrawFadeState(HUD_SCORE_FADING, 1); + m_LastDisplayScore = CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney; + } + if (m_DisplayScoreState != FADED_OUT) { + sprintf(sTemp, "$%08d", CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney); + AsciiToUnicode(sTemp, sPrint); + + CFont::SetPropOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetPropOff(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, alpha)); + MONEY_COLOR.a = alpha; + CFont::SetColor(MONEY_COLOR); + + if (FrontEndMenuManager.m_PrefsShowHud) { + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(110.0f), SCREEN_SCALE_Y(43.0f), sPrint); + } + } + + /* + DrawAmmo + */ + if (m_LastWeapon == playerPed->GetWeapon()->m_eWeaponType) { + alpha = CHud::DrawFadeState(HUD_WEAPON_FADING, 0); + } else { + alpha = CHud::DrawFadeState(HUD_WEAPON_FADING, 1); + m_LastWeapon = playerPed->GetWeapon()->m_eWeaponType; + } + if (m_WeaponState != FADED_OUT) { + CWeapon *weapon = playerPed->GetWeapon(); + int32 AmmoAmount = CWeaponInfo::GetWeaponInfo((eWeaponType)WeaponType)->m_nAmountofAmmunition; + int32 AmmoInClip = weapon->m_nAmmoInClip; + int32 TotalAmmo = weapon->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); + CWeaponInfo *weaponInfo = CWeaponInfo::GetWeaponInfo((eWeaponType)WeaponType); + /* + DrawWeaponIcon + */ + + if (FrontEndMenuManager.m_PrefsShowHud) { + if (weaponInfo->m_nModelId <= 0) { + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + if (FrontEndMenuManager.m_PrefsShowHud) + Sprites[WeaponType].Draw( + CRect(SCREEN_SCALE_FROM_RIGHT(99.0f), SCREEN_SCALE_Y(27.0f), SCREEN_SCALE_FROM_RIGHT(35.0f), SCREEN_SCALE_Y(91.0f)), + CRGBA(255, 255, 255, alpha), + 0.015f, 0.015f, + 1.0f, 0.0f, + 0.015f, 1.0f, + 1.0f, 1.0f); + } else { + CBaseModelInfo *weaponModel = CModelInfo::GetModelInfo(weaponInfo->m_nModelId); + RwTexDictionary *weaponTxd = CTxdStore::GetSlot(weaponModel->GetTxdSlot())->texDict; + if (weaponTxd) { + RwTexture *weaponIcon = RwTexDictionaryFindNamedTexture(weaponTxd, weaponModel->GetModelName()); + if (weaponIcon) { + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); +#ifndef FIX_BUGS + const float xSize = SCREEN_SCALE_X(64.0f / 2.0f); + const float ySize = SCREEN_SCALE_Y(64.0f / 2.0f); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(weaponIcon)); + CSprite::RenderOneXLUSprite(SCREEN_SCALE_FROM_RIGHT(99.0f) + xSize, SCREEN_SCALE_Y(25.0f) + ySize, 1.0f, xSize, ySize, + 255, 255, 255, 255, 1.0f, 255); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); +#else + static CSprite2d sprite; + sprite.m_pTexture = weaponIcon; + sprite.Draw( + CRect(SCREEN_SCALE_FROM_RIGHT(99.0f), SCREEN_SCALE_Y(27.0f), SCREEN_SCALE_FROM_RIGHT(35.0f), SCREEN_SCALE_Y(91.0f)), + CRGBA(255, 255, 255, alpha), + 0.015f, 0.015f, + 1.0f, 0.0f, + 0.015f, 1.0f, + 1.0f, 1.0f); + sprite.m_pTexture = nil; +#endif + } + } + } + + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(0.5f), SCREEN_SCALE_Y(0.8f)); + CFont::SetJustifyOff(); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetPropOn(); + CFont::SetDropShadowPosition(0); + CFont::SetFontStyle(FONT_STANDARD); + + if (Min(9999, TotalAmmo - AmmoInClip) != 9999 && !CDarkel::FrenzyOnGoing() && weaponInfo->m_nWeaponSlot > 1 && weapon->m_eWeaponType != WEAPONTYPE_DETONATOR) { + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, alpha)); + AMMO_COLOR.a = alpha; + CFont::SetColor(AMMO_COLOR); + if (FrontEndMenuManager.m_PrefsShowHud) + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(66.0f), SCREEN_SCALE_Y(90.0f), sPrint); + CFont::SetDropShadowPosition(0); + } + } + } + + /* + DrawHealth + */ + if ( m_LastTimeEnergyLost == CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss ) { + CHud::DrawFadeState(HUD_ENERGY_FADING, 0); + } else { + CHud::DrawFadeState(HUD_ENERGY_FADING, 1); + m_LastTimeEnergyLost = CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss; + } + + if (m_EnergyLostState != FADED_OUT) { + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetRightJustifyOn(); + CFont::SetPropOff(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + + if (m_ItemToFlash == ITEM_HEALTH && FRAMECOUNTER & 8 + || m_ItemToFlash != ITEM_HEALTH + || playerPed->m_fHealth < 10 + && FRAMECOUNTER & 8) { + if (playerPed->m_fHealth >= 10 + || playerPed->m_fHealth < 10 && FRAMECOUNTER & 8) { + + AsciiToUnicode("{", sPrintIcon); +#ifdef FIX_BUGS + sprintf(sTemp, "%03d", int32(playerPed->m_fHealth + 0.5f)); +#else + sprintf(sTemp, "%03d", (int32)playerPed->m_fHealth); +#endif + AsciiToUnicode(sTemp, sPrint); + + CFont::SetColor(HEALTH_COLOR); + if (FrontEndMenuManager.m_PrefsShowHud) { + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(110.0f), 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::SetColor(HEALTH_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(110.0f + 54.0f), SCREEN_SCALE_Y(65.0f), sPrintIcon); + } + } + } + } + + /* + DrawArmour + */ + if (m_ItemToFlash == ITEM_ARMOUR && FRAMECOUNTER & 8 || m_ItemToFlash != ITEM_ARMOUR) { + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + if (playerPed->m_fArmour > 1.0f) { + AsciiToUnicode("<", sPrintIcon); +#ifdef FIX_BUGS + sprintf(sTemp, "%03d", int32(playerPed->m_fArmour + 0.5f)); +#else + sprintf(sTemp, "%03d", (int32)playerPed->m_fArmour); +#endif + AsciiToUnicode(sTemp, sPrint); + + CFont::SetColor(ARMOUR_COLOR); + if (FrontEndMenuManager.m_PrefsShowHud) { + + 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 & 4) { + // CFont::SetColor(ARMOUR_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(182.0f + 52.0f), SCREEN_SCALE_Y(65.0f), sPrintIcon); + } + } + } + } + } + + /* + DrawWantedLevel + */ + if (m_LastWanted == playerPed->m_pWanted->GetWantedLevel()) { + alpha = DrawFadeState(HUD_WANTED_FADING, 0); + } else { + alpha = DrawFadeState(HUD_WANTED_FADING, 1); + m_LastWanted = playerPed->m_pWanted->GetWantedLevel(); + } + + if (m_WantedState != FADED_OUT) { + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_STANDARD); + + AsciiToUnicode(">", sPrintIcon); + + for (int i = 0; i < 6; i++) { + if (FrontEndMenuManager.m_PrefsShowHud) { + if (playerPed->m_pWanted->GetWantedLevel() > i + && (CTimer::GetTimeInMilliseconds() > playerPed->m_pWanted->m_nLastWantedLevelChange + + 2000 || FRAMECOUNTER & 4)) { + + WANTED_COLOR.a = alpha; + CFont::SetColor(WANTED_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(110.0f + 23.0f * i), SCREEN_SCALE_Y(87.0f), sPrintIcon); + + } else if (playerPed->m_pWanted->m_nMinWantedLevel > i && FRAMECOUNTER & 4) { + WANTED_COLOR_FLASH.a = alpha; + CFont::SetColor(WANTED_COLOR_FLASH); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(110.0f + 23.0f * i), SCREEN_SCALE_Y(87.0f), sPrintIcon); + + } else if (playerPed->m_pWanted->GetWantedLevel() <= i) { + NOTWANTED_COLOR.a = alpha; + CFont::SetColor(NOTWANTED_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(110.0f + 23.0f * i), SCREEN_SCALE_Y(87.0f), sPrintIcon); + } + } + } + } + + static int32 nMediaLevelCounter = 0; + if (CStats::ShowChaseStatOnScreen != 0) { + float fCurAttentionLevel = CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention; + if (0.7f * CStats::HighestChaseValue > fCurAttentionLevel + || fCurAttentionLevel <= 40.0f || CTheScripts::IsPlayerOnAMission()) { + nMediaLevelCounter = 0; + } + else { + if (fCurAttentionLevel == CStats::HighestChaseValue) { + sprintf(gString, "%s %d", UnicodeToAscii(TheText.Get("CHSE")), (int32)fCurAttentionLevel); + } + else { + sprintf(gString, "%s %d" "-%d-", UnicodeToAscii(TheText.Get("CHSE")), (int32)fCurAttentionLevel, (int32)CStats::HighestChaseValue); + } + AsciiToUnicode(gString, gUString); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetPropOff(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + + CRGBA colour; + if (CTimer::GetTimeInMilliseconds() & 0x200) + colour = CRGBA(204, 0, 185, 180); + else + colour = CRGBA(178, 0, 162, 180); + CFont::SetColor(colour); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(110.0f), SCREEN_SCALE_Y(113.0f), gUString); + + if (CStats::FindChaseString(fCurAttentionLevel) != prevChaseString) { + prevChaseString = CStats::FindChaseString(fCurAttentionLevel); + nMediaLevelCounter = 100; + } + + if (nMediaLevelCounter != 0) { + nMediaLevelCounter--; + UnicodeMakeUpperCase(gUString, CStats::FindChaseString(fCurAttentionLevel)); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(110.0f), SCREEN_SCALE_Y(138.0f), gUString); + } + } + } + + /* + DrawZoneName + */ + if (m_pZoneName) { + + if (m_pZoneName != m_pLastZoneName) { + switch (m_ZoneState) { + case 0: + m_ZoneState = 2; + m_ZoneToPrint = m_pZoneName; + m_ZoneNameTimer = 0; + m_ZoneFadeTimer = 0; + if (m_VehicleState == 1 || m_VehicleState == 2) + m_VehicleState = 3; + break; + case 1: + case 2: + case 3: + case 4: + m_ZoneNameTimer = 5; + m_ZoneState = 4; + break; + default: + break; + } + m_pLastZoneName = m_pZoneName; + } + + float fZoneAlpha = 255.0f; + if (m_ZoneState) { + switch (m_ZoneState) { + case 1: + fZoneAlpha = 255.0f; + m_ZoneFadeTimer = 1000; + if (m_ZoneNameTimer > 10000.0f) { + m_ZoneFadeTimer = 1000; + m_ZoneState = 3; + } + break; + case 2: + m_ZoneFadeTimer += CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer > 1000.0f) { + m_ZoneState = 1; + m_ZoneFadeTimer = 1000; + } + fZoneAlpha = m_ZoneFadeTimer * 0.001f * 255.0f; + break; + case 3: + m_ZoneFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer < 0.0f) { + m_ZoneState = 0; + m_ZoneFadeTimer = 0; + } + fZoneAlpha = m_ZoneFadeTimer * 0.001f * 255.0f; + break; + case 4: + m_ZoneFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_ZoneFadeTimer < 0.0f) { + m_ZoneFadeTimer = 0; + m_ZoneToPrint = m_pLastZoneName; + m_ZoneState = 2; + } + fZoneAlpha = m_ZoneFadeTimer * 0.001f * 255.0f; + break; + default: + break; + + } + + if (!m_Message[0] && BigMessageInUse[1] == 0.0f && BigMessageInUse[2] == 0.0f) { + + m_ZoneNameTimer += CTimer::GetTimeStepInMilliseconds(); + CFont::SetJustifyOff(); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + + if (FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + CFont::SetScale(SCREEN_SCALE_X(1.7f * 0.8f), SCREEN_SCALE_Y(1.8f)); + else + CFont::SetScale(SCREEN_SCALE_X(1.7f), SCREEN_SCALE_Y(1.8f)); + + CFont::SetSlantRefPoint(SCREEN_SCALE_FROM_RIGHT(32.0f), SCREEN_SCALE_FROM_BOTTOM(128.0f)); + CFont::SetSlant(0.15f); + + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, fZoneAlpha)); + CFont::SetFontStyle(FONT_BANK); + CFont::SetColor(CRGBA(ZONE_COLOR.r, ZONE_COLOR.g, ZONE_COLOR.b, fZoneAlpha)); + + if (!CTheScripts::bPlayerIsInTheStatium) + CFont::PrintStringFromBottom(SCREEN_SCALE_FROM_RIGHT(32.0f), SCREEN_SCALE_FROM_BOTTOM(128.0f), m_ZoneToPrint); + + CFont::SetSlant(0.f); + } else { + m_ZoneState = 3; + } + } + } + + /* + DrawVehicleName + */ + if (m_VehicleName) { + float fVehicleAlpha = 0.0f; + + if (m_VehicleName != m_pLastVehicleName) { + switch (m_VehicleState) { + case 0: + m_VehicleState = 2; + m_pVehicleNameToPrint = m_VehicleName; + m_VehicleNameTimer = 0; + m_VehicleFadeTimer = 0; + if (m_ZoneState == 1 || m_ZoneState == 2) + m_ZoneState = 3; + break; + case 1: + case 2: + case 3: + case 4: + m_VehicleNameTimer = 0; + m_VehicleState = 4; + break; + default: + break; + } + m_pLastVehicleName = m_VehicleName; + } + + 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 * 0.001f * 255.0f; + break; + case 3: + m_VehicleFadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (m_VehicleFadeTimer < 0) { + m_VehicleState = 0; + m_VehicleFadeTimer = 0; + } + fVehicleAlpha = m_VehicleFadeTimer * 0.001f * 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 * 0.001f * 255.0f; + break; + default: + break; + } + + if (!m_Message[0]) { + 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.7f), SCREEN_SCALE_Y(1.8f)); + else + CFont::SetScale(SCREEN_SCALE_X(1.7f * 0.85f), SCREEN_SCALE_Y(1.8f)); + + CFont::SetSlantRefPoint(SCREEN_SCALE_FROM_RIGHT(32.0f), SCREEN_SCALE_FROM_BOTTOM(105.0f)); + CFont::SetSlant(0.15f); + + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetDropShadowPosition(2); + CFont::SetColor(CRGBA(VEHICLE_COLOR.r, VEHICLE_COLOR.g, VEHICLE_COLOR.b, fVehicleAlpha)); + CFont::SetDropColor(CRGBA(0, 0, 0, fVehicleAlpha)); + + CFont::PrintStringFromBottom(SCREEN_SCALE_FROM_RIGHT(32.0f), SCREEN_SCALE_FROM_BOTTOM(105.0f), m_pVehicleNameToPrint); + + CFont::SetSlant(0.f); + } + } + } + else { + m_pLastVehicleName = nil; + m_VehicleState = 0; + m_VehicleFadeTimer = 0; + m_VehicleNameTimer = 0; + } + + /* + DrawClock + */ + if (m_ClockState) { + CFont::SetJustifyOff(); + CFont::SetCentreOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetPropOff(); + CFont::SetFontStyle(FONT_HEADING); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + + sprintf(sTemp, "%02d:%02d", CClock::GetHours(), CClock::GetMinutes()); + AsciiToUnicode(sTemp, sPrint); + + CFont::SetColor(CLOCK_COLOR); + if (FrontEndMenuManager.m_PrefsShowHud) + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(111.0f), SCREEN_SCALE_Y(22.0f), sPrint); + } + + /* + DrawOnScreenTimer + */ + + wchar sTimer[16]; + + if (!CUserDisplay::OnscnTimer.m_sClocks[0].m_bClockProcessed) + TimerOnLastFrame = false; + + for(uint32 i = 0; i < NUMONSCREENCOUNTERS; i++) { + if (!CUserDisplay::OnscnTimer.m_sCounters[0].m_bCounterProcessed) + CounterOnLastFrame[i] = false; + } + + if (CUserDisplay::OnscnTimer.m_bProcessed) { + if (CUserDisplay::OnscnTimer.m_sClocks[0].m_bClockProcessed) { + if (!TimerOnLastFrame) + TimerFlashTimer = 1; + + TimerOnLastFrame = true; + + if (TimerFlashTimer != 0) { + if (++TimerFlashTimer > 50) + TimerFlashTimer = 0; + } + + if (FRAMECOUNTER & 4 || TimerFlashTimer == 0) { + AsciiToUnicode(CUserDisplay::OnscnTimer.m_sClocks[0].m_aClockBuffer, sTimer); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetPropOff(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetColor(TIMER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(37.0f), SCREEN_SCALE_Y(110.0f), sTimer); + CFont::SetPropOn(); + + if (CUserDisplay::OnscnTimer.m_sClocks[0].m_aClockText[0]) { + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(TIMER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(37.0f) - SCREEN_SCALE_X(80.0f), SCREEN_SCALE_Y(110.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sClocks[0].m_aClockText)); + } + } + } + + for(uint32 i = 0; i < NUMONSCREENCOUNTERS; i++) { + if (CUserDisplay::OnscnTimer.m_sCounters[i].m_bCounterProcessed) { + if (!CounterOnLastFrame[i]) + CounterFlashTimer[i] = 1; + + CounterOnLastFrame[i] = true; + + if (CounterFlashTimer[i] != 0) { + if (++CounterFlashTimer[i] > 50) + CounterFlashTimer[i] = 0; + } + + if (FRAMECOUNTER & 4 || CounterFlashTimer[i] == 0) { + if (CUserDisplay::OnscnTimer.m_sCounters[i].m_nType == COUNTER_DISPLAY_NUMBER) { + AsciiToUnicode(CUserDisplay::OnscnTimer.m_sCounters[i].m_aCounterBuffer, sTimer); + CFont::SetPropOn(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetCentreOff(); + CFont::SetRightJustifyOn(); + CFont::SetRightJustifyWrap(0.0f); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetWrapx(SCREEN_STRETCH_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetPropOn(); + CFont::SetBackGroundOnlyTextOn(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(COUNTER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(37.0f), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y * 20.f * i) + SCREEN_SCALE_Y(132.0f), sTimer); + } else { + int counter = atoi(CUserDisplay::OnscnTimer.m_sCounters[i].m_aCounterBuffer); + + const float barWidth = SCREEN_SCALE_X(100.f / 2.f); + const float right = SCREEN_SCALE_FROM_RIGHT(37.0f); + const float left = right - barWidth; + + const float barHeight = SCREEN_SCALE_Y(11.0f); + const float top = SCREEN_SCALE_Y(132.0f) + SCREEN_SCALE_Y(8.0f) + SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y * 20.f * i); + const float bottom = top + barHeight; + + // shadow + CSprite2d::DrawRect(CRect(left + SCREEN_SCALE_X(6.0f), top + SCREEN_SCALE_Y(2.0f), right + SCREEN_SCALE_X(6.0f), bottom + SCREEN_SCALE_Y(2.0f)), CRGBA(0, 0, 0, 255)); + + CSprite2d::DrawRect(CRect(left + SCREEN_SCALE_X(4.0f), top, right + SCREEN_SCALE_X(4.0f), bottom), CRGBA(27, 89, 130, 255)); + CSprite2d::DrawRect(CRect(left + SCREEN_SCALE_X(4.0f), top, left + SCREEN_SCALE_X(counter) / 2.0f + SCREEN_SCALE_X(4.0f), bottom), CRGBA(97, 194, 247, 255)); + } + + if (CUserDisplay::OnscnTimer.m_sCounters[i].m_aCounterText[0]) { + CFont::SetPropOn(); + CFont::SetFontStyle(FONT_LOCALE(FONT_HEADING)); + CFont::SetScale(SCREEN_SCALE_X(HUD_TEXT_SCALE_X), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(COUNTER_COLOR); + CFont::PrintString(SCREEN_SCALE_FROM_RIGHT(37.0f) - SCREEN_SCALE_X(61.0f), SCREEN_SCALE_Y(HUD_TEXT_SCALE_Y * 20.f * i) + SCREEN_SCALE_Y(132.0f), TheText.Get(CUserDisplay::OnscnTimer.m_sCounters[i].m_aCounterText)); + } + // unused/leftover color. I wonder what was it for + CFont::SetColor(CRGBA(244, 225, 91, 255)); + } + } + } + } + + /* + DrawRadar + */ + if (FrontEndMenuManager.m_PrefsRadarMode != 2 && + !m_HideRadar && (m_ItemToFlash == ITEM_RADAR && FRAMECOUNTER & 8 || m_ItemToFlash != ITEM_RADAR)) { + + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + CRadar::DrawMap(); + if (FrontEndMenuManager.m_PrefsRadarMode != 1) { + 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 FIX_BUGS + rect.Grow(SCREEN_SCALE_X(6.0f), SCREEN_SCALE_X(6.0f), SCREEN_SCALE_Y(6.0f), SCREEN_SCALE_Y(6.0f)); +#else + rect.Grow(6.0f); +#endif + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + rect.Translate(SCREEN_SCALE_X_FIX(0.0f), SCREEN_SCALE_Y_FIX(2.0f)); + Sprites[HUD_RADARDISC].Draw(rect, CRGBA(0, 0, 0, 255)); + rect.Translate(SCREEN_SCALE_X_FIX(0.0f), SCREEN_SCALE_Y_FIX(-2.0f)); + Sprites[HUD_RADARDISC].Draw(rect, RADARDISC_COLOR); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + } + 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(CTheScripts::IntroTextLines[i].m_fScaleX), SCREEN_SCALE_Y(CTheScripts::IntroTextLines[i].m_fScaleY * 0.5f)); + 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(CTheScripts::IntroTextLines[i].m_fWrapX)); + CFont::SetCentreSize(SCREEN_SCALE_X(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)); + CFont::PrintString(SCREEN_WIDTH - SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH - CTheScripts::IntroTextLines[i].m_fAtX), SCREEN_HEIGHT - SCREEN_SCALE_Y(DEFAULT_SCREEN_HEIGHT - CTheScripts::IntroTextLines[i].m_fAtY), CTheScripts::IntroTextLines[i].m_Text); + } + } + 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]) { + if (m_VehicleState != 0) + m_VehicleState = 3; + if (m_ZoneState != 0) + m_ZoneState = 3; + + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetBackgroundColor(CRGBA(0, 0, 0, 128)); + CFont::SetCentreOn(); + CFont::SetPropOn(); +#ifdef CUTSCENE_BORDERS_SWITCH + if (!FrontEndMenuManager.m_PrefsCutsceneBorders) { + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetDropShadowPosition(2); + } + else +#endif + CFont::SetDropShadowPosition(0); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetColor(CRGBA(225, 225, 225, 255)); + + static bool onceItWasWidescreen = false; + + if (TheCamera.m_WideScreenOn) { + onceItWasWidescreen = true; + + if (FrontEndMenuManager.m_PrefsShowSubtitles || !CCutsceneMgr::IsRunning()) { + CFont::SetCentreSize(SCREEN_WIDTH - SCREEN_SCALE_X(60.0f)); + CFont::SetScale(SCREEN_SCALE_X(0.58f), SCREEN_SCALE_Y(1.2f)); + CFont::PrintString(SCREEN_WIDTH / 2.f, SCREEN_SCALE_FROM_BOTTOM(80.0f), m_Message); + } + } else { + if (onceItWasWidescreen) + m_Message[0] = '\0'; + + onceItWasWidescreen = false; + CFont::DrawFonts(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetScale(SCREEN_SCALE_X(0.58f), SCREEN_SCALE_Y(1.22f)); + + float radarBulge = SCREEN_SCALE_X(140.0f) + SCREEN_SCALE_X(8.0f); + float rectWidth = SCREEN_WIDTH - SCREEN_SCALE_X(20.0f) - SCREEN_SCALE_X(8.0f) - radarBulge; + CFont::SetCentreSize(rectWidth); + + CFont::PrintString(rectWidth / 2.0f + radarBulge, SCREEN_SCALE_FROM_BOTTOM(105.f + 2.0f), m_Message); + } + CFont::SetDropShadowPosition(0); + } + + /* + HelpMessage + */ + + 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) * 0.05f + 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_HelpMessageDisplayForever && m_HelpMessageTimer > m_HelpMessageDisplayTime * 1000.0f || + m_HelpMessageQuick && m_HelpMessageTimer > 1500.0f) { + + m_HelpMessageFadeTimer = 600; + m_HelpMessageState = 3; + } + break; + case 2: + if (TheCamera.m_WideScreenOn) + break; + + m_HelpMessageFadeTimer += 2 * CTimer::GetTimeStepInMilliseconds(); + if (m_HelpMessageFadeTimer > 0) { + m_HelpMessageState = 1; + m_HelpMessageFadeTimer = 0; + } + fAlpha = m_HelpMessageFadeTimer * 0.001f * 225.0f; + break; + case 3: + m_HelpMessageFadeTimer -= 2 * CTimer::GetTimeStepInMilliseconds(); + if (m_HelpMessageFadeTimer < 0 || TheCamera.m_WideScreenOn) { + m_HelpMessageState = 0; + m_HelpMessageFadeTimer = 0; + } + fAlpha = m_HelpMessageFadeTimer * 0.001f * 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 * 0.001f * 225.0f; + break; + default: + break; + } + + if (!TheCamera.m_WideScreenOn) { + 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::DrawFonts(); + CFont::SetColor(CRGBA(175, 175, 175, 255)); + CFont::SetJustifyOff(); +#ifdef MORE_LANGUAGES + if (CFont::IsJapanese()) + CFont::SetWrapx(SCREEN_SCALE_X(229.0f + 34.0f - 4.0f)); + else +#endif + CFont::SetWrapx(SCREEN_SCALE_X(200.0f + 34.0f - 4.0f)); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetBackgroundOn(); + CFont::SetBackGroundOnlyTextOff(); + CFont::SetDropShadowPosition(0); + CFont::SetBackgroundColor(CRGBA(0, 0, 0, fAlpha * 0.9f)); + CFont::SetColor(CRGBA(175, 175, 175, 255)); + CFont::PrintString(SCREEN_SCALE_X(34.0f), SCREEN_SCALE_Y(28.0f + (150.0f - PagerXOffset) * 0.6f), m_HelpMessageToPrint); + CFont::SetAlphaFade(255.0f); + CFont::SetWrapx(SCREEN_WIDTH); + } + } + } else + m_HelpMessageState = 0; + + /* + 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(1.6f), SCREEN_SCALE_Y(1.8f)); + } else { + CFont::SetScale(SCREEN_SCALE_X(1.8f), SCREEN_SCALE_Y(1.8f)); + } + CFont::SetPropOn(); + CFont::SetCentreOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(590.0f)); + CFont::SetColor(CRGBA(255, 255, 0, BigMessageAlpha[0])); // unused color + CFont::SetFontStyle(FONT_HEADING); + + // Appearently sliding text in here was abandoned very early, since this text is centered now. + + if (BigMessageX[0] >= SCALE_AND_CENTER_X(620.0f)) { + 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((CTimer::GetTimeStepInMilliseconds() * 0.3f)); + BigMessageAlpha[0] += (CTimer::GetTimeStepInMilliseconds() * 0.3f); + + if (BigMessageAlpha[0] > 255.0f) + BigMessageAlpha[0] = 255.0f; + } + CFont::DrawFonts(); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, BigMessageAlpha[0])); + CFont::SetColor(CRGBA(BIGMESSAGE_COLOR.r, BIGMESSAGE_COLOR.g, BIGMESSAGE_COLOR.b, BigMessageAlpha[0])); + CFont::PrintString(SCREEN_WIDTH / 2, (SCREEN_HEIGHT / 2) - SCREEN_SCALE_Y(18.0f), m_BigMessage[0]); + } + else { + BigMessageAlpha[0] = 0.0f; + BigMessageX[0] = SCALE_AND_CENTER_X(-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(1.4f), SCREEN_SCALE_Y(1.4f)); + else + CFont::SetScale(SCREEN_SCALE_X(2.0f), SCREEN_SCALE_Y(2.0f)); + + CFont::SetPropOn(); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_HEADING); + + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, BigMessageAlpha[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(90.0f), m_BigMessage[2]); + } + else { + BigMessageInUse[2] = 1.0f; + BigMessageAlpha[2] = 0.0f; + if (m_VehicleState != 0) // Hide vehicle name if wasted/busted text is displaying + m_VehicleState = 0; + if (m_ZoneState != 0) + m_ZoneState = 0; + } + } + else { + BigMessageInUse[2] = 0.0f; + } + } +} + +void CHud::DrawAfterFade() +{ + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERNEAREST); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void*)rwTEXTUREADDRESSCLAMP); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + + if (CTimer::GetIsUserPaused() || CReplay::IsPlayingBack()) + return; + + 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(line.m_fScaleX), SCREEN_SCALE_Y(line.m_fScaleY) / 2); + + 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(line.m_fWrapX)); + CFont::SetCentreSize(SCREEN_SCALE_X(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); + CFont::PrintString(SCREEN_WIDTH - SCALE_AND_CENTER_X(DEFAULT_SCREEN_WIDTH - line.m_fAtX), SCREEN_HEIGHT - SCREEN_SCALE_Y(DEFAULT_SCREEN_HEIGHT - line.m_fAtY), line.m_Text); + } + } + 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(1.2f), SCREEN_SCALE_Y(1.5f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(600.0f)); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(ODDJOB_COLOR); + CFont::PrintString((SCREEN_WIDTH / 2), SCREEN_SCALE_Y(140.0f) - SCREEN_SCALE_Y(16.0f), m_BigMessage[3]); + } + + if (!m_BigMessage[1][0] && m_BigMessage[4][0]) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetScale(SCREEN_SCALE_X(1.2f), SCREEN_SCALE_Y(1.5f)); + CFont::SetCentreOn(); + CFont::SetPropOn(); + CFont::SetCentreSize(SCREEN_SCALE_X(580.0f)); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(ODDJOB_COLOR); + CFont::PrintString((SCREEN_WIDTH / 2), SCREEN_SCALE_Y(140.0f), 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(); + CFont::SetCentreSize(SCREEN_SCALE_X(560.0f)); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetColor(ODDJOB2_COLOR); + CFont::PrintString(SCREEN_WIDTH / 2, SCREEN_SCALE_Y(217.0f), m_BigMessage[5]); + } + } + + /* + DrawMissionTitle + */ + if (m_BigMessage[1][0]) { + if (BigMessageInUse[1] != 0.0f) { + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + + // will be overwritten below + if (CGame::frenchGame || FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_SPANISH) + CFont::SetScale(SCREEN_SCALE_X(0.884f), SCREEN_SCALE_Y(1.36f)); + else + CFont::SetScale(SCREEN_SCALE_X(1.04f), SCREEN_SCALE_Y(1.6f)); + + CFont::SetPropOn(); + CFont::SetRightJustifyWrap(SCALE_AND_CENTER_X(0.0f)); + CFont::SetRightJustifyOn(); + CFont::SetFontStyle(FONT_BANK); + CFont::SetScale(FrontEndMenuManager.m_PrefsLanguage == CMenuManager::LANGUAGE_AMERICAN ? SCREEN_SCALE_X(1.7f) : SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.8f)); + + if (BigMessageX[1] >= SCREEN_SCALE_FROM_RIGHT(20.0f)) { + BigMessageInUse[1] += CTimer::GetTimeStep(); + + if (BigMessageInUse[1] >= 120.0f) { + BigMessageInUse[1] = 120.0f; + BigMessageAlpha[1] -= CTimer::GetTimeStepInMilliseconds(); + } + if (BigMessageAlpha[1] <= 0.0f) { + m_BigMessage[1][0] = 0; + BigMessageInUse[1] = 0.0f; + BigMessageAlpha[1] = 0.0f; + } + } else { + BigMessageX[1] += SCREEN_SCALE_X((CTimer::GetTimeStepInMilliseconds() * 0.3f)); + BigMessageAlpha[1] += CTimer::GetTimeStepInMilliseconds(); + + if (BigMessageAlpha[1] > 255.0f) + BigMessageAlpha[1] = 255.0f; + } + + CFont::SetColor(CRGBA(40, 40, 40, BigMessageAlpha[1])); // what was that for? + + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, BigMessageAlpha[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(140.0f), m_BigMessage[1]); + } else { + m_ZoneFadeTimer = 0; + BigMessageX[1] = SCREEN_SCALE_FROM_RIGHT(DEFAULT_SCREEN_WIDTH + 60.0f); + BigMessageInUse[1] = 1.0f; + m_ZoneState = 0; + } + } else { + BigMessageInUse[1] = 0.0f; + } +} + +void CHud::GetRidOfAllHudMessages() +{ + m_ZoneNameTimer = 0; + m_pZoneName = nil; + m_ZoneState = 0; + + 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_HelpMessageDisplayForever = false; + m_HelpMessageDisplayTime = 1.0f; + m_VehicleName = 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; + } +} + +#ifdef RELOADABLES +void CHud::ReloadTXD() +{ + for (int i = 0; i < NUM_HUD_SPRITES; ++i) { + Sprites[i].Delete(); + } + + int HudTXD = CTxdStore::FindTxdSlot("hud"); + CTxdStore::RemoveTxdSlot(HudTXD); + + debug("Reloading HUD.TXD...\n"); + + 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); + } +} +#endif + +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); + } + + m_pLastZoneName = nil; + GetRidOfAllHudMessages(); + m_pLastVehicleName = nil; + + if (gpSniperSightTex == nil) + gpSniperSightTex = RwTextureRead("sitesniper", nil); // unused + if (gpRocketSightTex == nil) + gpRocketSightTex = RwTextureRead("siterocket", nil); + if (gpLaserSightTex == nil) + gpLaserSightTex = RwTextureRead("sitelaser", nil); // unused + if (gpLaserDotTex == nil) + gpLaserDotTex = RwTextureRead("laserdot", "laserdotm"); + if (gpViewFinderTex == nil) + gpViewFinderTex = RwTextureRead("viewfinder_128", "viewfinder_128m"); // unused + + m_ClockState = 1; + CounterOnLastFrame[0] = false; + CounterOnLastFrame[1] = false; + CounterOnLastFrame[2] = false; + + m_ItemToFlash = ITEM_NONE; + OddJob2Timer = 0; + OddJob2OffTimer = 0.0f; + OddJob2On = 0; + OddJob2XOffset = 0.0f; + CounterFlashTimer[0] = 0; + CounterFlashTimer[1] = 0; + CounterFlashTimer[2] = 0; + TimerOnLastFrame = false; + TimerFlashTimer = 0; + SpriteBrightness = 0; + PagerOn = 0; + PagerTimer = 0; + PagerSoundPlayed = 0; + PagerXOffset = 150.0f; + +#ifdef HUD_AUTO_FADE + m_EnergyLostState = START_FADE_OUT; + m_WantedState = START_FADE_OUT; + m_DisplayScoreState = START_FADE_OUT; + m_WeaponState = START_FADE_OUT; +#else + m_EnergyLostState = FADE_DISABLED; + m_WantedState = FADE_DISABLED; + m_DisplayScoreState = FADE_DISABLED; + m_WeaponState = FADE_DISABLED; +#endif + m_WantedFadeTimer = 0; + m_WantedTimer = 0; + m_EnergyLostFadeTimer = 0; + m_EnergyLostTimer = 0; + m_DisplayScoreFadeTimer = 0; + m_DisplayScoreTimer = 0; + m_WeaponFadeTimer = 0; + m_WeaponTimer = 0; + + m_HideRadar = false; + m_LastDisplayScore = CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney; + m_LastTimeEnergyLost = CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss; + m_LastWanted = 0; + m_LastWeapon = 0; + +#ifndef MASTER + VarConsole.Add("Draw HUD", &m_Wants_To_Draw_Hud, false); +#endif + CTxdStore::PopCurrentTxd(); +} + +void CHud::ReInitialise() { + m_Wants_To_Draw_Hud = true; + m_Wants_To_Draw_3dMarkers = true; + + m_pLastZoneName = nil; + GetRidOfAllHudMessages(); + m_pLastVehicleName = nil; + + CounterOnLastFrame[0] = false; + CounterOnLastFrame[1] = false; + CounterOnLastFrame[2] = false; + m_ItemToFlash = ITEM_NONE; + m_ClockState = 1; + OddJob2Timer = 0; + OddJob2OffTimer = 0.0f; + OddJob2On = 0; + OddJob2XOffset = 0.0f; + CounterFlashTimer[0] = 0; + CounterFlashTimer[1] = 0; + CounterFlashTimer[2] = 0; + TimerOnLastFrame = false; + TimerFlashTimer = 0; + SpriteBrightness = 0; + PagerOn = 0; + PagerTimer = 0; + PagerSoundPlayed = 0; + PagerXOffset = 150.0f; + +#ifdef HUD_AUTO_FADE + m_EnergyLostState = START_FADE_OUT; + m_WantedState = START_FADE_OUT; + m_DisplayScoreState = START_FADE_OUT; + m_WeaponState = START_FADE_OUT; +#else + m_EnergyLostState = FADE_DISABLED; + m_WantedState = FADE_DISABLED; + m_DisplayScoreState = FADE_DISABLED; + m_WeaponState = FADE_DISABLED; +#endif + m_WantedFadeTimer = 0; + m_WantedTimer = 0; + m_EnergyLostFadeTimer = 0; + m_EnergyLostTimer = 0; + m_DisplayScoreFadeTimer = 0; + m_DisplayScoreTimer = 0; + m_WeaponFadeTimer = 0; + m_WeaponTimer = 0; + + m_HideRadar = false; + m_LastDisplayScore = CWorld::Players[CWorld::PlayerInFocus].m_nVisibleMoney; + m_LastTimeEnergyLost = CWorld::Players[CWorld::PlayerInFocus].m_nTimeLastHealthLoss; + m_LastWanted = 0; + m_LastWeapon = 0; +} + +wchar LastBigMessage[6][128]; + +void CHud::SetBigMessage(wchar *message, uint16 style) +{ + int i = 0; + + if (BigMessageInUse[style] != 0.0f) + return; + + 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; +} + +void CHud::SetHelpMessage(wchar *message, bool quick, bool displayForever) +{ + if (!CReplay::IsPlayingBack()) { + for (int i = 0; i < HELP_MSG_LENGTH; i++) { + m_HelpMessage[i] = 0; + } + for (int i = 0; i < HELP_MSG_LENGTH; i++) { + m_LastHelpMessage[i] = 0; + } + for (int i = 0; i < HELP_MSG_LENGTH; i++) { + m_HelpMessageToPrint[i] = 0; + } + + CMessages::WideStringCopy(m_HelpMessage, message, HELP_MSG_LENGTH); + CMessages::InsertPlayerControlKeysInString(m_HelpMessage); + if (m_HelpMessageState == 0 || !CMessages::WideStringCompare(m_HelpMessage, m_HelpMessageToPrint, HELP_MSG_LENGTH)) { + for (int i = 0; i < HELP_MSG_LENGTH; i++) { + m_LastHelpMessage[i] = 0; + } + + if (!message) { + m_HelpMessage[0] = 0; + m_HelpMessageToPrint[0] = 0; + } + if (!displayForever) { + m_HelpMessageState = displayForever; + } else { + m_HelpMessageState = 1; + CMessages::WideStringCopy(m_HelpMessageToPrint, m_HelpMessage, HELP_MSG_LENGTH); + CMessages::WideStringCopy(m_LastHelpMessage, m_HelpMessage, HELP_MSG_LENGTH); + } + + m_HelpMessageQuick = quick; + m_HelpMessageDisplayForever = displayForever; + } + + } +} + +bool CHud::IsHelpMessageBeingDisplayed(void) +{ + return m_HelpMessageState != 0; +} + +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::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; +} + +void CHud::SetVehicleName(wchar *name) +{ + m_VehicleName = name; +} + +void CHud::SetZoneName(wchar *name) +{ + m_pZoneName = name; +} + +void CHud::Shutdown() +{ + for (int i = 0; i < NUM_HUD_SPRITES; ++i) { + Sprites[i].Delete(); + } + + RwTextureDestroy(gpSniperSightTex); + gpSniperSightTex = nil; + + RwTextureDestroy(gpRocketSightTex); + gpRocketSightTex = nil; + + RwTextureDestroy(gpLaserSightTex); + gpLaserSightTex = nil; + + RwTextureDestroy(gpLaserDotTex); + gpLaserDotTex = nil; + + RwTextureDestroy(gpViewFinderTex); + gpViewFinderTex = nil; + + int HudTXD = CTxdStore::FindTxdSlot("hud"); + CTxdStore::RemoveTxdSlot(HudTXD); +} + +float CHud::DrawFadeState(DRAW_FADE_STATE fadingElement, int forceFadingIn) +{ + float alpha = 255.0f; + uint32 operation, timer; + int32 fadeTimer; + + switch (fadingElement) { + case HUD_WANTED_FADING: + fadeTimer = m_WantedFadeTimer; + operation = m_WantedState; + timer = m_WantedTimer; + break; + case HUD_ENERGY_FADING: + fadeTimer = m_EnergyLostFadeTimer; + operation = m_EnergyLostState; + timer = m_EnergyLostTimer; + break; + case HUD_SCORE_FADING: + fadeTimer = m_DisplayScoreFadeTimer; + operation = m_DisplayScoreState; + timer = m_DisplayScoreTimer; + break; + case HUD_WEAPON_FADING: + fadeTimer = m_WeaponFadeTimer; + operation = m_WeaponState; + timer = m_WeaponTimer; + break; + default: + break; + } + if (forceFadingIn) { + switch (operation) { + case FADED_OUT: + fadeTimer = 0; + case START_FADE_OUT: + case FADING_OUT: + timer = 5; + operation = FADING_IN; + break; + default: + break; + } + } + if (operation != FADED_OUT && operation != FADE_DISABLED) { + switch (operation) { + case START_FADE_OUT: + fadeTimer = 1000; + alpha = 255.0f; + if (timer > 10000) { + fadeTimer = 3000; + operation = FADING_OUT; + } + break; + case FADING_IN: + fadeTimer += CTimer::GetTimeStepInMilliseconds(); + if (fadeTimer > 1000.0f) { + operation = START_FADE_OUT; + fadeTimer = 1000; + } + alpha = fadeTimer / 1000.0f * 255.0f; + break; + case FADING_OUT: + fadeTimer -= CTimer::GetTimeStepInMilliseconds(); + if (fadeTimer < 0.0f) { + fadeTimer = 0; + operation = FADED_OUT; + } + alpha = fadeTimer / 1000.0f * 255.0f; + break; + default: + break; + } + timer += CTimer::GetTimeStepInMilliseconds(); + } + + switch (fadingElement) { + case HUD_WANTED_FADING: + m_WantedFadeTimer = fadeTimer; + m_WantedState = operation; + m_WantedTimer = timer; + break; + case HUD_ENERGY_FADING: + m_EnergyLostFadeTimer = fadeTimer; + m_EnergyLostState = operation; + m_EnergyLostTimer = timer; + break; + case HUD_SCORE_FADING: + m_DisplayScoreFadeTimer = fadeTimer; + m_DisplayScoreState = operation; + m_DisplayScoreTimer = timer; + break; + case HUD_WEAPON_FADING: + m_WeaponFadeTimer = fadeTimer; + m_WeaponState = operation; + m_WeaponTimer = timer; + break; + default: + break; + } + + return Clamp(alpha, 0.0f, 255.0f); +} + +void +CHud::ResetWastedText(void) +{ + BigMessageInUse[2] = 0.0f; + BigMessageInUse[0] = 0.0f; + m_BigMessage[2][0] = 0; + m_BigMessage[0][0] = 0; +} diff --git a/src/miami/renderer/Hud.h b/src/miami/renderer/Hud.h new file mode 100644 index 00000000..a4b9609a --- /dev/null +++ b/src/miami/renderer/Hud.h @@ -0,0 +1,139 @@ +#pragma once +#include "Sprite2d.h" + +#define HELP_MSG_LENGTH 256 + +#define HUD_TEXT_SCALE_X 0.7f +#define HUD_TEXT_SCALE_Y 1.25f + +enum eItems +{ + ITEM_NONE = -1, + ITEM_ARMOUR = 3, + ITEM_HEALTH = 4, + ITEM_RADAR = 8 +}; + +// Thanks for vague name, R* +enum DRAW_FADE_STATE +{ + HUD_WANTED_FADING = 0, + HUD_ENERGY_FADING, + HUD_SCORE_FADING, + HUD_WEAPON_FADING, +}; + +// My name +enum eFadeOperation +{ + FADED_OUT = 0, + START_FADE_OUT, + FADING_IN, + FADING_OUT, + FADE_DISABLED = 5, +}; + +enum eSprites +{ + HUD_FIST, + HUD_SITEROCKET = 41, + HUD_RADARDISC = 50, + HUD_SITESNIPER = 63, + HUD_SITEM16, + HUD_SITELASER, + HUD_LASERDOT, + HUD_VIEWFINDER, + HUD_BLEEDER, + NUM_HUD_SPRITES = 69, +}; + +class CHud +{ +public: + static CSprite2d Sprites[NUM_HUD_SPRITES]; + static wchar m_HelpMessage[HELP_MSG_LENGTH]; + static wchar m_LastHelpMessage[HELP_MSG_LENGTH]; + static uint32 m_HelpMessageState; + static uint32 m_HelpMessageTimer; + static int32 m_HelpMessageFadeTimer; + static wchar m_HelpMessageToPrint[HELP_MSG_LENGTH]; + static float m_HelpMessageDisplayTime; + static bool m_HelpMessageDisplayForever; + static bool m_HelpMessageQuick; + static uint32 m_ZoneState; + static int32 m_ZoneFadeTimer; + static uint32 m_ZoneNameTimer; + static wchar *m_pZoneName; + static wchar *m_pLastZoneName; + static wchar *m_ZoneToPrint; + static wchar *m_VehicleName; + static wchar *m_pLastVehicleName; + static wchar *m_pVehicleNameToPrint; + static uint32 m_VehicleState; + static int32 m_VehicleFadeTimer; + static uint32 m_VehicleNameTimer; + static wchar m_Message[256]; + static wchar m_PagerMessage[256]; + static bool m_Wants_To_Draw_Hud; + static bool m_Wants_To_Draw_3dMarkers; + static wchar m_BigMessage[6][128]; + static int16 m_ItemToFlash; + static bool m_HideRadar; + static int32 m_ClockState; + + // These aren't really in CHud + static float BigMessageInUse[6]; + static float BigMessageAlpha[6]; + static float BigMessageX[6]; + static float OddJob2OffTimer; + static bool CounterOnLastFrame[NUMONSCREENCOUNTERS]; + static float OddJob2XOffset; + static uint16 CounterFlashTimer[NUMONSCREENCOUNTERS]; + static uint16 OddJob2Timer; + static bool TimerOnLastFrame; + static int16 OddJob2On; + static uint16 TimerFlashTimer; + static int16 PagerSoundPlayed; + static int32 SpriteBrightness; + static float PagerXOffset; + static int16 PagerTimer; + static int16 PagerOn; + + static uint32 m_WantedFadeTimer; + static uint32 m_WantedState; + static uint32 m_WantedTimer; + static uint32 m_EnergyLostFadeTimer; + static uint32 m_EnergyLostState; + static uint32 m_EnergyLostTimer; + static uint32 m_DisplayScoreFadeTimer; + static uint32 m_DisplayScoreState; + static uint32 m_DisplayScoreTimer; + static uint32 m_WeaponFadeTimer; + static uint32 m_WeaponState; + static uint32 m_WeaponTimer; + + static uint32 m_LastDisplayScore; + static uint32 m_LastWanted; + static uint32 m_LastWeapon; + static uint32 m_LastTimeEnergyLost; + +public: + static void Draw(); + static void DrawAfterFade(); + static void GetRidOfAllHudMessages(); +#ifdef RELOADABLES + static void ReloadTXD(); +#endif + static void Initialise(); + static void ReInitialise(); + static void SetBigMessage(wchar *message, uint16 style); + static void SetHelpMessage(wchar *message, bool quick, bool displayForever = false); + static bool IsHelpMessageBeingDisplayed(void); + static void SetMessage(wchar *message); + static void SetPagerMessage(wchar *message); + static void SetVehicleName(wchar *name); + static void SetZoneName(wchar *name); + static void Shutdown(); + static float DrawFadeState(DRAW_FADE_STATE, int); + static void ResetWastedText(void); +}; diff --git a/src/miami/renderer/Instance.cpp b/src/miami/renderer/Instance.cpp new file mode 100644 index 00000000..be6d73d6 --- /dev/null +++ b/src/miami/renderer/Instance.cpp @@ -0,0 +1,9 @@ +#include "common.h" + +#include "Instance.h" + +void +CInstance::Shutdown() +{ + GetMatrix().Detach(); +} diff --git a/src/miami/renderer/Instance.h b/src/miami/renderer/Instance.h new file mode 100644 index 00000000..693cfdf1 --- /dev/null +++ b/src/miami/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/miami/renderer/Lines.cpp b/src/miami/renderer/Lines.cpp new file mode 100644 index 00000000..b5c85149 --- /dev/null +++ b/src/miami/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/miami/renderer/Lines.h b/src/miami/renderer/Lines.h new file mode 100644 index 00000000..f2694fc0 --- /dev/null +++ b/src/miami/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/miami/renderer/MBlur.cpp b/src/miami/renderer/MBlur.cpp new file mode 100644 index 00000000..04314a83 --- /dev/null +++ b/src/miami/renderer/MBlur.cpp @@ -0,0 +1,802 @@ +#ifndef LIBRW +#define WITHD3D +#endif +#include "common.h" +#ifndef LIBRW +#include +#endif + +#include "main.h" +#include "General.h" +#include "RwHelper.h" +#include "Camera.h" +#include "Timecycle.h" +#include "Particle.h" +#include "Timer.h" +#include "Hud.h" +#include "Frontend.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; +float CMBlur::Drunkness; + +int32 CMBlur::pBufVertCount; + +static RwIm2DVertex Vertex[4]; +static RwIm2DVertex Vertex2[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; + + // MBlur disabled for now (skmp) + pFrontBuffer = nullptr; // 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); + + + 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); +} + +void +CMBlur::CreateImmediateModeData(RwCamera *cam, RwRect *rect, RwIm2DVertex *verts, RwRGBA color, float u1Off, float v1Off, float u2Off, float v2Off, float z, int fullTexture) +{ + float x1 = rect->x; + float y1 = rect->y; + float x2 = rect->w; + float y2 = rect->h; + + float u1, v1, u2, v2; + if(fullTexture){ + u1 = 0.0f; + v1 = 0.0f; + u2 = 1.0f; + v2 = 1.0f; + }else{ + if(RwRasterGetDepth(RwCameraGetRaster(cam)) == 16){ + x1 += HALFPX; + y1 += HALFPX; + x2 += HALFPX; + y2 += HALFPX; + }else{ + x1 -= HALFPX; + y1 -= HALFPX; + x2 -= HALFPX; + y2 -= HALFPX; + } + + int32 width = Pow(2.0f, int32(log2(RwRasterGetWidth (RwCameraGetRaster(cam))))+1); + int32 height = Pow(2.0f, int32(log2(RwRasterGetHeight(RwCameraGetRaster(cam))))+1); + u1 = x1/width + u1Off; + v1 = y1/height + v1Off; + u2 = x2/width + u2Off; + v2 = y2/height + v2Off; + u1 = Clamp(u1, 0.0f, 1.0f); + v1 = Clamp(v1, 0.0f, 1.0f); + u2 = Clamp(u2, 0.0f, 1.0f); + v2 = Clamp(v2, 0.0f, 1.0f); + } + + float recipz = 1.0f/z; + // TODO: CameraZ is wrong, what should we do? + RwIm2DVertexSetScreenX(&verts[0], x1); + RwIm2DVertexSetScreenY(&verts[0], y1); + RwIm2DVertexSetScreenZ(&verts[0], z); + RwIm2DVertexSetCameraZ(&verts[0], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&verts[0], recipz); + RwIm2DVertexSetU(&verts[0], u1, recipz); + RwIm2DVertexSetV(&verts[0], v1, recipz); + RwIm2DVertexSetIntRGBA(&verts[0], color.red, color.green, color.blue, color.alpha); + + RwIm2DVertexSetScreenX(&verts[1], x1); + RwIm2DVertexSetScreenY(&verts[1], y2); + RwIm2DVertexSetScreenZ(&verts[1], z); + RwIm2DVertexSetCameraZ(&verts[1], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&verts[1], recipz); + RwIm2DVertexSetU(&verts[1], u1, recipz); + RwIm2DVertexSetV(&verts[1], v2, recipz); + RwIm2DVertexSetIntRGBA(&verts[1], color.red, color.green, color.blue, color.alpha); + + RwIm2DVertexSetScreenX(&verts[2], x2); + RwIm2DVertexSetScreenY(&verts[2], y2); + RwIm2DVertexSetScreenZ(&verts[2], z); + RwIm2DVertexSetCameraZ(&verts[2], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&verts[2], recipz); + RwIm2DVertexSetU(&verts[2], u2, recipz); + RwIm2DVertexSetV(&verts[2], v2, recipz); + RwIm2DVertexSetIntRGBA(&verts[2], color.red, color.green, color.blue, color.alpha); + + RwIm2DVertexSetScreenX(&verts[3], x2); + RwIm2DVertexSetScreenY(&verts[3], y1); + RwIm2DVertexSetScreenZ(&verts[3], z); + RwIm2DVertexSetCameraZ(&verts[3], RwCameraGetNearClipPlane(cam)); + RwIm2DVertexSetRecipCameraZ(&verts[3], recipz); + RwIm2DVertexSetU(&verts[3], u2, recipz); + RwIm2DVertexSetV(&verts[3], v1, recipz); + RwIm2DVertexSetIntRGBA(&verts[3], color.red, color.green, color.blue, color.alpha); +} + +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(ms_bJustInitialised) + ms_bJustInitialised = false; + else + OverlayRender(cam, pFrontBuffer, color, type, bluralpha); + if(BlurOn){ + RwRasterPushContext(pFrontBuffer); + RwRasterRenderFast(RwCameraGetRaster(cam), 0, 0); + RwRasterPopContext(); + } +#endif + POP_RENDERGROUP(); +#endif +} + +static uint8 DrunkBlurRed = 128; +static uint8 DrunkBlurGreen = 128; +static uint8 DrunkBlurBlue = 128; +static int32 DrunkBlurIncrement = 1; + +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){ + // gta clamps these to 255 (probably a macro or inlined function) + int ovR = r * 0.6f; + int ovG = g * 0.6f; + int ovB = b * 0.6f; + int ovA = type == MOTION_BLUR_SNIPER ? a : a*0.6f; + RwIm2DVertexSetIntRGBA(&Vertex[0], ovR, ovG, ovB, ovA); + RwIm2DVertexSetIntRGBA(&Vertex[1], ovR, ovG, ovB, ovA); + RwIm2DVertexSetIntRGBA(&Vertex[2], ovR, ovG, ovB, ovA); + RwIm2DVertexSetIntRGBA(&Vertex[3], ovR, ovG, ovB, ovA); + }else{ + RwIm2DVertexSetIntRGBA(&Vertex2[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[3], 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, raster); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + if(BlurOn){ + if(type == MOTION_BLUR_SNIPER){ + RwIm2DVertexSetIntRGBA(&Vertex2[0], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex2[1], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex2[2], r, g, b, 80); + RwIm2DVertexSetIntRGBA(&Vertex2[3], r, g, b, 80); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + pBufVertCount = 0; + }else{ + RwIm2DVertexSetIntRGBA(&Vertex2[0], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex2[1], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex2[2], r*2, g*2, b*2, 30); + RwIm2DVertexSetIntRGBA(&Vertex2[3], r*2, g*2, b*2, 30); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex2, 4, Index, 6); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + RwIm2DVertexSetIntRGBA(&Vertex2[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[0], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[1], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[2], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex2[3], r, g, b, a); + RwIm2DVertexSetIntRGBA(&Vertex[3], r, g, b, a); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex2, 4, Index, 6); + } + } + + int DrunkBlurAlpha = 175.0f * Drunkness; + if(DrunkBlurAlpha != 0){ + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + if(BlurOn){ + RwIm2DVertexSetIntRGBA(&Vertex[0], 255, 255, 255, DrunkBlurAlpha); + RwIm2DVertexSetIntRGBA(&Vertex[1], 255, 255, 255, DrunkBlurAlpha); + RwIm2DVertexSetIntRGBA(&Vertex[2], 255, 255, 255, DrunkBlurAlpha); + RwIm2DVertexSetIntRGBA(&Vertex[3], 255, 255, 255, DrunkBlurAlpha); + }else{ + RwIm2DVertexSetIntRGBA(&Vertex[0], DrunkBlurRed, DrunkBlurGreen, DrunkBlurBlue, DrunkBlurAlpha); + RwIm2DVertexSetIntRGBA(&Vertex[1], DrunkBlurRed, DrunkBlurGreen, DrunkBlurBlue, DrunkBlurAlpha); + RwIm2DVertexSetIntRGBA(&Vertex[2], DrunkBlurRed, DrunkBlurGreen, DrunkBlurBlue, DrunkBlurAlpha); + RwIm2DVertexSetIntRGBA(&Vertex[3], DrunkBlurRed, DrunkBlurGreen, DrunkBlurBlue, DrunkBlurAlpha); + if(DrunkBlurIncrement){ + if(DrunkBlurRed < 255) DrunkBlurRed++; + if(DrunkBlurGreen < 255) DrunkBlurGreen++; + if(DrunkBlurBlue < 255) DrunkBlurBlue++; + if(DrunkBlurRed == 255) + DrunkBlurIncrement = 0; + }else{ + if(DrunkBlurRed > 128) DrunkBlurRed--; + if(DrunkBlurGreen > 128) DrunkBlurGreen--; + if(DrunkBlurBlue > 128) DrunkBlurBlue--; + if(DrunkBlurRed == 128) + DrunkBlurIncrement = 1; + } + } + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, Vertex, 4, Index, 6); + } + + if(type != MOTION_BLUR_SNIPER) + OverlayRenderFx(cam, pFrontBuffer); + + 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); +} + +void +CMBlur::SetDrunkBlur(float drunkness) +{ + Drunkness = Clamp(drunkness, 0.0f, 1.0f); +} + +void +CMBlur::ClearDrunkBlur() +{ + Drunkness = 0.0f; + CTimer::SetTimeScale(1.0f); +} + +#define NUM_RENDER_FX 64 + +static RwRect fxRect[NUM_RENDER_FX]; +static FxType fxType[NUM_RENDER_FX]; +static float fxZ[NUM_RENDER_FX]; + +bool +CMBlur::PosInside(RwRect *rect, float x1, float y1, float x2, float y2) +{ + if((rect->x < x1 - 10.0f || rect->x > x2 + 10.0f || rect->y < y1 - 10.0f || rect->y > y2 + 10.0f) && + (rect->w < x1 - 10.0f || rect->w > x2 + 10.0f || rect->h < y1 - 10.0f || rect->h > y2 + 10.0f) && + (rect->x < x1 - 10.0f || rect->x > x2 + 10.0f || rect->h < y1 - 10.0f || rect->h > y2 + 10.0f) && + (rect->w < x1 - 10.0f || rect->w > x2 + 10.0f || rect->y < y1 - 10.0f || rect->y > y2 + 10.0f)) + return false; + return true; +} + +bool +CMBlur::AddRenderFx(RwCamera *cam, RwRect *rect, float z, FxType type) +{ + if(pBufVertCount >= NUM_RENDER_FX) + return false; + + rect->x = Max(rect->x, 0); + rect->y = Max(rect->y, 0); + rect->w = Min(rect->w, SCREEN_WIDTH); + rect->h = Min(rect->h, SCREEN_HEIGHT); + if(rect->x >= rect->w || rect->y >= rect->h) + return false; + + switch(type){ + case FXTYPE_WATER1: + case FXTYPE_WATER2: + case FXTYPE_BLOOD1: + case FXTYPE_BLOOD2: + case FXTYPE_HEATHAZE: // code seems to be duplicated for this case + for(int i = 0; i < pBufVertCount; i++) + if(fxType[i] == type && PosInside(rect, fxRect[i].x-10.0f, fxRect[i].y-10.0f, fxRect[i].w+10.0f, fxRect[i].h+10.0f)) + return false; + // TODO: fix aspect ratio scaling + // radar + if(PosInside(rect, 40.0f, SCREEN_SCALE_FROM_BOTTOM(116.0f), 40.0f + SCREEN_SCALE_X(94.0f), SCREEN_SCALE_FROM_BOTTOM(116.0f - 76.0f))) + return false; + // HUD + if(PosInside(rect, 400.0f, 0.0f, SCREEN_WIDTH, 90.0f)) + return false; + // vehicle name + if(CHud::m_VehicleState != 0 && PosInside(rect, SCREEN_WIDTH/2, 350.0f, SCREEN_WIDTH, SCREEN_HEIGHT)) + return false; + // zone name + if(CHud::m_ZoneState != 0 && PosInside(rect, SCREEN_WIDTH/2, 350.0f, SCREEN_WIDTH, SCREEN_HEIGHT)) + return false; + break; + } + + fxRect[pBufVertCount] = *rect; + fxZ[pBufVertCount] = z; + fxType[pBufVertCount] = type; + pBufVertCount++; + + return true; +} + +void +CMBlur::OverlayRenderFx(RwCamera *cam, RwRaster *frontBuf) +{ + bool drawWaterDrops = false; + RwIm2DVertex verts[4]; + int red = (0.75f*CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed())*0.55f * 255; + int green = (0.75f*CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen())*0.55f * 255; + int blue = (0.75f*CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue())*0.55f * 255; + red = Clamp(red, 0, 255); + green = Clamp(green, 0, 255); + blue = Clamp(blue, 0, 255); + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + +#ifdef LIBRW + rw::SetRenderState(rw::STENCILENABLE, TRUE); +#else + RwD3D8SetRenderState(D3DRS_STENCILENABLE, TRUE); +#endif + + for(int i = 0; i < pBufVertCount; i++) + switch(fxType[i]){ + case FXTYPE_WATER1: + case FXTYPE_WATER2: + case FXTYPE_BLOOD1: + case FXTYPE_BLOOD2: { + drawWaterDrops = true; + int32 width = Pow(2.0f, int32(log2(RwRasterGetWidth (RwCameraGetRaster(cam))))+1); + int32 height = Pow(2.0f, int32(log2(RwRasterGetHeight(RwCameraGetRaster(cam))))+1); + + float u1Off = (fxRect[i].w - fxRect[i].x)/width; + float u2Off = u1Off - (fxRect[i].w - fxRect[i].x + 0.5f)*0.66f/width; + float halfHeight = (fxRect[i].h - fxRect[i].y + 0.5f)*0.25f/height; + + if(RwRasterGetDepth(RwCameraGetRaster(cam)) == 16){ + if(fxType[i] == FXTYPE_BLOOD1 || fxType[i] == FXTYPE_BLOOD2) + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(255, 0, 0, 128), 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + else + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(32, 32, 32, 225), 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + }else{ + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(32, 32, 32, 225), 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + } + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpDotRaster); +#ifdef LIBRW + rw::SetRenderState(rw::STENCILFUNCTION, rw::STENCILALWAYS); + rw::SetRenderState(rw::STENCILFUNCTIONREF, 1); + rw::SetRenderState(rw::STENCILFUNCTIONMASK, 0xFFFFFFFF); + rw::SetRenderState(rw::STENCILFUNCTIONWRITEMASK, 0xFFFFFFFF); + rw::SetRenderState(rw::STENCILZFAIL, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILFAIL, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILPASS, rw::STENCILREPLACE); +#else + RwD3D8SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); + RwD3D8SetRenderState(D3DRS_STENCILREF, 1); + RwD3D8SetRenderState(D3DRS_STENCILMASK, 0xFFFFFFFF); + RwD3D8SetRenderState(D3DRS_STENCILWRITEMASK, 0xFFFFFFFF); + RwD3D8SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP); + RwD3D8SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); + RwD3D8SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE); +#endif + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + + if(RwRasterGetDepth(RwCameraGetRaster(cam)) != 16){ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, frontBuf); +#ifdef LIBRW + rw::SetRenderState(rw::STENCILFUNCTION, rw::STENCILEQUAL); + rw::SetRenderState(rw::STENCILPASS, rw::STENCILKEEP); +#else + RwD3D8SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); + RwD3D8SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); +#endif + if(BlurOn){ + if(fxType[i] == FXTYPE_BLOOD1 || fxType[i] == FXTYPE_BLOOD2) + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(255, 0, 0, 255), u1Off, 0.0f+halfHeight, u2Off, 0.0f-halfHeight, fxZ[i], false); + else + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(225, 225, 225, 160), u1Off, 0.0f+halfHeight, u2Off, 0.0f-halfHeight, fxZ[i], false); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDDESTALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVDESTALPHA); + }else{ + if(fxType[i] == FXTYPE_BLOOD1 || fxType[i] == FXTYPE_BLOOD2) + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(255, 0, 0, 128), u1Off, 0.0f+halfHeight, u2Off, 0.0f-halfHeight, fxZ[i], false); + else + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(128, 128, 128, 32), u1Off, 0.0f+halfHeight, u2Off, 0.0f-halfHeight, fxZ[i], false); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + } + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + } + break; + } + case FXTYPE_SPLASH1: + case FXTYPE_SPLASH2: + case FXTYPE_SPLASH3: + drawWaterDrops = true; + break; + + case FXTYPE_HEATHAZE: + if(TheCamera.GetScreenFadeStatus() == FADE_0 && frontBuf){ + int alpha = FrontEndMenuManager.m_PrefsBrightness > 255 ? + FrontEndMenuManager.m_PrefsBrightness - 90 : + FrontEndMenuManager.m_PrefsBrightness - 130; + alpha = Clamp(alpha, 16, 200)/2; + + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(0, 0, 0, alpha), 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpHeatHazeRaster); +#ifdef LIBRW + rw::SetRenderState(rw::STENCILFUNCTION, rw::STENCILALWAYS); + rw::SetRenderState(rw::STENCILFUNCTIONREF, 1); + rw::SetRenderState(rw::STENCILFUNCTIONMASK, 0xFFFFFFFF); + rw::SetRenderState(rw::STENCILFUNCTIONWRITEMASK, 0xFFFFFFFF); + rw::SetRenderState(rw::STENCILZFAIL, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILFAIL, rw::STENCILKEEP); + rw::SetRenderState(rw::STENCILPASS, rw::STENCILREPLACE); +#else + RwD3D8SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); + RwD3D8SetRenderState(D3DRS_STENCILREF, 1); + RwD3D8SetRenderState(D3DRS_STENCILMASK, 0xFFFFFFFF); + RwD3D8SetRenderState(D3DRS_STENCILWRITEMASK, 0xFFFFFFFF); + RwD3D8SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP); + RwD3D8SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); + RwD3D8SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE); +#endif + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(255, 255, 255, alpha), + CGeneral::GetRandomNumberInRange(-0.002f, 0.002f), + CGeneral::GetRandomNumberInRange(-0.002f, 0.002f), + CGeneral::GetRandomNumberInRange(-0.002f, 0.002f), + CGeneral::GetRandomNumberInRange(-0.002f, 0.002f), + fxZ[i], false); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, frontBuf); +#ifdef LIBRW + rw::SetRenderState(rw::STENCILFUNCTION, rw::STENCILEQUAL); + rw::SetRenderState(rw::STENCILPASS, rw::STENCILKEEP); +#else + RwD3D8SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); + RwD3D8SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); +#endif + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + } + break; + } +#ifdef LIBRW + rw::SetRenderState(rw::STENCILENABLE, FALSE); +#else + RwD3D8SetRenderState(D3DRS_STENCILENABLE, FALSE); +#endif + + if(drawWaterDrops){ + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + + // Draw drops + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpRainDripRaster[0]); + for(int i = 0; i < pBufVertCount; i++) + if(fxType[i] == FXTYPE_WATER1 || fxType[i] == FXTYPE_BLOOD1){ + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(red, green, blue, fxType[i] == FXTYPE_BLOOD1 ? 255 : 192), + 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + } + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpRainDripRaster[1]); + for(int i = 0; i < pBufVertCount; i++) + if(fxType[i] == FXTYPE_WATER2 || fxType[i] == FXTYPE_BLOOD2){ + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(red, green, blue, fxType[i] == FXTYPE_BLOOD2 ? 255 : 192), + 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + } + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpCarSplashRaster[0]); + for(int i = 0; i < pBufVertCount; i++) + if(fxType[i] == FXTYPE_SPLASH1 || fxType[i] == FXTYPE_SPLASH2 || fxType[i] == FXTYPE_SPLASH3){ + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(200, 200, 200, 255), + 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + } + + // Darken the water drops + int alpha = 192*0.5f; + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpRainDripDarkRaster[0]); + for(int i = 0; i < pBufVertCount; i++) + if(fxType[i] == FXTYPE_WATER1){ + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(red, green, blue, alpha), + 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + } + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpRainDripDarkRaster[1]); + for(int i = 0; i < pBufVertCount; i++) + if(fxType[i] == FXTYPE_WATER2){ + CreateImmediateModeData(cam, &fxRect[i], verts, CRGBA(red, green, blue, alpha), + 0.0f, 0.0f, 0.0f, 0.0f, fxZ[i], true); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, verts, 4, Index, 6); + } + } + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)FALSE); + pBufVertCount = 0; +} diff --git a/src/miami/renderer/MBlur.h b/src/miami/renderer/MBlur.h new file mode 100644 index 00000000..3dc53082 --- /dev/null +++ b/src/miami/renderer/MBlur.h @@ -0,0 +1,39 @@ +#pragma once + +enum FxType +{ + FXTYPE_WATER1, + FXTYPE_WATER2, + FXTYPE_BLOOD1, + FXTYPE_BLOOD2, + FXTYPE_HEATHAZE, + FXTYPE_SPLASH1, + FXTYPE_SPLASH2, + FXTYPE_SPLASH3 +}; + +class CMBlur +{ +public: + static RwRaster *pFrontBuffer; + static bool ms_bJustInitialised; + static bool ms_bScaledBlur; + static bool BlurOn; + static float Drunkness; + + static int32 pBufVertCount; + +public: + static RwBool MotionBlurOpen(RwCamera *cam); + static RwBool MotionBlurClose(void); + static void CreateImmediateModeData(RwCamera *cam, RwRect *rect); + static void CreateImmediateModeData(RwCamera *cam, RwRect *rect, RwIm2DVertex *verts, RwRGBA color, float u1Off, float v1Off, float u2Off, float v2Off, float z, int fullTexture); + 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); + static void SetDrunkBlur(float drunkness); + static void ClearDrunkBlur(); + + static bool PosInside(RwRect *rect, float x1, float y1, float x2, float y2); + static bool AddRenderFx(RwCamera *cam, RwRect *rect, float z, FxType type); + static void OverlayRenderFx(RwCamera *cam, RwRaster *frontBuf); +}; diff --git a/src/miami/renderer/Occlusion.cpp b/src/miami/renderer/Occlusion.cpp new file mode 100644 index 00000000..ec7101a6 --- /dev/null +++ b/src/miami/renderer/Occlusion.cpp @@ -0,0 +1,530 @@ +#include "common.h" + +#include "main.h" +#include "Entity.h" +#include "Occlusion.h" +#include "Game.h" +#include "Camera.h" +#include "Vector.h" +#include "Draw.h" +#include "Timer.h" +#include "RwHelper.h" +#include "VarConsole.h" + +int32 COcclusion::NumOccludersOnMap; +int16 COcclusion::FarAwayList; +int16 COcclusion::NearbyList; +int16 COcclusion::ListWalkThroughFA; +int16 COcclusion::PreviousListWalkThroughFA; +int16 COcclusion::NumActiveOccluders; +COccluder COcclusion::aOccluders[NUMOCCLUSIONVOLUMES]; +CActiveOccluder COcclusion::aActiveOccluders[NUMACTIVEOCCLUDERS]; + +CVector gCenterOnScreen; + +float gMinYInOccluder; +float gMinXInOccluder; +float gMaxYInOccluder; +float gMaxXInOccluder; + +bool gOccluderCoorsValid[8]; +CVector gOccluderCoorsOnScreen[8]; +CVector gOccluderCoors[8]; + +#ifndef MASTER +bool bDispayOccDebugStuff; // disPAY, yeah +#endif + +void +COcclusion::Init(void) +{ + NumOccludersOnMap = 0; +#ifndef MASTER + VarConsole.Add("Occlusion debug", &bDispayOccDebugStuff, true); +#endif + FarAwayList = -1; + NearbyList = -1; + ListWalkThroughFA = -1; + PreviousListWalkThroughFA = -1; +} + +void +COcclusion::AddOne(float x, float y, float z, float width, float length, float height, float angle) +{ + if(NumOccludersOnMap >= NUMOCCLUSIONVOLUMES) + return; + + aOccluders[NumOccludersOnMap].x = x; + aOccluders[NumOccludersOnMap].y = y; + aOccluders[NumOccludersOnMap].z = z; + aOccluders[NumOccludersOnMap].width = width; + aOccluders[NumOccludersOnMap].length = length; + aOccluders[NumOccludersOnMap].height = height; + while(angle < 0.0f) angle += 360.0f; + while(angle > 360.0f) angle -= 360.0f; + aOccluders[NumOccludersOnMap].angle = angle/360.0f * UINT16_MAX; + aOccluders[NumOccludersOnMap].listIndex = FarAwayList; + FarAwayList = NumOccludersOnMap++; +} + +bool +COccluder::NearCamera() { + return (TheCamera.GetPosition() - CVector(x, y, z)).Magnitude() - (Max(width, length) / 2.0f) < 250.0f; +} + +bool +DoesInfiniteLineCrossFiniteLine(float p1X, float p1Y, float p2X, float p2Y, float lineX, float lineY, float lineDX, float lineDY) +{ + float side1 = (p1X - lineX) * lineDY - (p1Y - lineY) * lineDX; + float side2 = (p2X - lineX) * lineDY - (p2Y - lineY) * lineDX; + return side1 * side2 < 0.0f; // if points lie on opposite sides of the infinte line, the line between them crosses it +} + +bool DoesInfiniteLineTouchScreen(float lineX, float lineY, float lineDX, float lineDY) { + if (lineX > 0.0f && lineY > 0.0f && SCREEN_WIDTH > lineX && SCREEN_HEIGHT > lineY) + return true; + + return (DoesInfiniteLineCrossFiniteLine(0.0f, 0.0f, SCREEN_WIDTH, 0.0f, lineX, lineY, lineDX, lineDY) || + DoesInfiniteLineCrossFiniteLine(0.0f, 0.0f, 0.0f, SCREEN_HEIGHT, lineX, lineY, lineDX, lineDY) || + DoesInfiniteLineCrossFiniteLine(SCREEN_WIDTH, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, lineX, lineY, lineDX, lineDY) || + DoesInfiniteLineCrossFiniteLine(0.0f, SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT, lineX, lineY, lineDX, lineDY)); +} + +bool IsPointInsideLine(float lineX, float lineY, float lineDX, float lineDY, float pX, float pY, float area = 0.0f) { + return (pX - lineX) * lineDY - (pY - lineY) * lineDX >= area; +} + +bool CalcScreenCoors(CVector const &in, CVector *out, float *outw, float *outh) { + *out = TheCamera.m_viewMatrix * in; + + if (out->z <= 1.0f) return false; + + float recip = 1.0f / out->z; + out->x *= SCREEN_WIDTH * recip; + out->y *= SCREEN_HEIGHT * recip; + + float fovScale = DefaultFOV / CDraw::GetFOV(); + + *outw = fovScale * recip * SCREEN_WIDTH; + *outh = fovScale * recip * SCREEN_HEIGHT; + + return true; +} + +bool CalcScreenCoors(CVector const &in, CVector *out) { + *out = TheCamera.m_viewMatrix * in; + + if (out->z <= 1.0f) return false; + + float recip = 1.0f / out->z; + out->x *= SCREEN_WIDTH * recip; + out->y *= SCREEN_HEIGHT * recip; + + return true; +} + +bool +COccluder::ProcessLineSegment(int corner1, int corner2, CActiveOccluder *occl) { + if (!gOccluderCoorsValid[corner1] && !gOccluderCoorsValid[corner2]) + return false; + + float x1, y1, x2, y2; + + CVector p1, p2; + if (!gOccluderCoorsValid[corner1]) { + float clipDist1 = Abs((TheCamera.m_viewMatrix * gOccluderCoors[corner1]).z - 1.1f); + float clipDist2 = Abs((TheCamera.m_viewMatrix * gOccluderCoors[corner2]).z - 1.1f); + float ratio = clipDist2 / (clipDist1 + clipDist2); + CVector clippedCoors = (1.0f - ratio) * gOccluderCoors[corner2] + ratio * gOccluderCoors[corner1]; + + if (!CalcScreenCoors(clippedCoors, &p1, &x1, &y1)) + return true; + } + else { + p1 = gOccluderCoorsOnScreen[corner1]; + } + + if (!gOccluderCoorsValid[corner2]) { + float clipDist1 = Abs((TheCamera.m_viewMatrix * gOccluderCoors[corner1]).z - 1.1f); + float clipDist2 = Abs((TheCamera.m_viewMatrix * gOccluderCoors[corner2]).z - 1.1f); + float ratio = clipDist1 / (clipDist1 + clipDist2); + CVector clippedCoors = (1.0f - ratio) * gOccluderCoors[corner2] + ratio * gOccluderCoors[corner1]; + + if (!CalcScreenCoors(clippedCoors, &p2, &x2, &y2)) + return true; + } + else { + p2 = gOccluderCoorsOnScreen[corner2]; + } + + gMinXInOccluder = Min(Min(gMinXInOccluder, p1.x), p2.x); + gMaxXInOccluder = Max(Max(gMaxXInOccluder, p1.x), p2.x); + gMinYInOccluder = Min(Min(gMinYInOccluder, p1.y), p2.y); + gMaxYInOccluder = Max(Max(gMaxYInOccluder, p1.y), p2.y); + + CVector2D origin = p1; + CVector2D direction = p2 - p1; + + // Make sure lines are counter-clockwise around center + if (!IsPointInsideLine(origin.x, origin.y, direction.x, direction.y, gCenterOnScreen.x, gCenterOnScreen.y, 0.0f)) { + origin += direction; + direction *= -1.0f; + } + + float magnitude = direction.Magnitude(); + + occl->lines[occl->linesCount].origin = origin; + occl->lines[occl->linesCount].direction = direction / magnitude; + occl->lines[occl->linesCount].length = magnitude; + + if (!DoesInfiniteLineTouchScreen(origin.x, origin.y, direction.x, direction.y)) + return !IsPointInsideLine(origin.x, origin.y, direction.x, direction.y, SCREEN_WIDTH / 2.0f, SCREEN_HEIGHT / 2.0f, 0.0f); + + occl->linesCount++; + + return false; +} + +bool +COccluder::ProcessOneOccluder(CActiveOccluder *occl) { + float outX, outY; + + occl->linesCount = 0; + CVector pos(x, y, z); + + if (!CalcScreenCoors(pos, &gCenterOnScreen, &outX, &outY) || gCenterOnScreen.z < -150.0f || gCenterOnScreen.z > 300.0f) { + return false; + } + + occl->radius = Max(width, length) * 0.35f + gCenterOnScreen.z; + + CVector vec[3]; + + vec[0].x = length / 2.0f * Sin(GetAngle()); + vec[0].y = -length / 2.0f * Cos(GetAngle()); + vec[0].z = 0.0f; + + vec[1].x = width / 2.0f * Cos(GetAngle()); + vec[1].y = width / 2.0f * Sin(GetAngle()); + vec[1].z = 0.0f; + + vec[2].x = 0.0f; + vec[2].y = 0.0f; + vec[2].z = height / 2.0f; + + // Figure out if we see the front or back of a face + bool bFrontFace[6]; + for (int i = 0; i < 3; i++) { + bFrontFace[i*2+0] = DotProduct((pos + vec[i] - TheCamera.GetPosition()), vec[i]) < 0.0f; + bFrontFace[i*2+1] = DotProduct((pos - vec[i] - TheCamera.GetPosition()), -vec[i]) < 0.0f; + } + + //calculating vertices of a box + gOccluderCoors[0] = pos + vec[0] + vec[1] + vec[2]; + gOccluderCoors[1] = pos - vec[0] + vec[1] + vec[2]; + gOccluderCoors[2] = pos + vec[0] - vec[1] + vec[2]; + gOccluderCoors[3] = pos - vec[0] - vec[1] + vec[2]; + gOccluderCoors[4] = pos + vec[0] + vec[1] - vec[2]; + gOccluderCoors[5] = pos - vec[0] + vec[1] - vec[2]; + gOccluderCoors[6] = pos + vec[0] - vec[1] - vec[2]; + gOccluderCoors[7] = pos - vec[0] - vec[1] - vec[2]; + + for(int i = 0; i < 8; i++) + gOccluderCoorsValid[i] = CalcScreenCoors(gOccluderCoors[i], &gOccluderCoorsOnScreen[i], &outX, &outY); + + gMinYInOccluder = 999999.875f; + gMinXInOccluder = 999999.875f; + gMaxYInOccluder = -999999.875f; + gMaxXInOccluder = -999999.875f; + + // Between two differently facing sides we see an edge, so process those + if (bFrontFace[2] != bFrontFace[0] && ProcessLineSegment(0, 4, occl)) + return false; + if (bFrontFace[3] != bFrontFace[0] && ProcessLineSegment(2, 6, occl)) + return false; + if (bFrontFace[4] != bFrontFace[0] && ProcessLineSegment(0, 2, occl)) + return false; + if (bFrontFace[5] != bFrontFace[0] && ProcessLineSegment(4, 6, occl)) + return false; + if (bFrontFace[2] != bFrontFace[1] && ProcessLineSegment(1, 5, occl)) + return false; + if (bFrontFace[3] != bFrontFace[1] && ProcessLineSegment(3, 7, occl)) + return false; + if (bFrontFace[4] != bFrontFace[1] && ProcessLineSegment(1, 3, occl)) + return false; + if (bFrontFace[5] != bFrontFace[1] && ProcessLineSegment(5, 7, occl)) + return false; + if (bFrontFace[4] != bFrontFace[2] && ProcessLineSegment(0, 1, occl)) + return false; + if (bFrontFace[3] != bFrontFace[4] && ProcessLineSegment(2, 3, occl)) + return false; + if (bFrontFace[5] != bFrontFace[3] && ProcessLineSegment(6, 7, occl)) + return false; + if (bFrontFace[2] != bFrontFace[5] && ProcessLineSegment(4, 5, occl)) + return false; + + if (gMaxXInOccluder - gMinXInOccluder < SCREEN_WIDTH * 0.1f || + gMaxYInOccluder - gMinYInOccluder < SCREEN_HEIGHT * 0.07f) + return false; + + return true; +} + +bool +COcclusion::OccluderHidesBehind(CActiveOccluder *occl1, CActiveOccluder *occl2) { + for (int i = 0; i < occl1->linesCount; i++) { + for (int j = 0; j < occl2->linesCount; j++) { + if (!IsPointInsideLine(occl2->lines[j].origin.x, occl2->lines[j].origin.y, occl2->lines[j].direction.x, + occl2->lines[j].direction.y, occl1->lines[i].origin.x, occl1->lines[i].origin.y, 0.0f)) + return false; + + + if (!IsPointInsideLine(occl2->lines[j].origin.x, occl2->lines[j].origin.y, occl2->lines[j].direction.x, + occl2->lines[j].direction.y, (occl1->lines[i].origin.x + occl1->lines[i].direction.x * occl1->lines[i].length), + (occl1->lines[i].origin.y + occl1->lines[i].direction.y * occl1->lines[i].length), 0.0f)) + return false; + } + } + + return true; +} + +void +COcclusion::ProcessBeforeRendering(void) +{ + NumActiveOccluders = 0; + +#ifndef MASTER + if (gbModelViewer) + return; +#endif + + if (CGame::currArea != AREA_MAIN_MAP) + return; + + if (ListWalkThroughFA == -1) { + PreviousListWalkThroughFA = -1; + ListWalkThroughFA = FarAwayList; + } + + int i; + for (i = 0; i < 16 && ListWalkThroughFA != -1; i++) { + if (aOccluders[ListWalkThroughFA].NearCamera()) { + int prevListWalkThroughFA = ListWalkThroughFA; + + if (PreviousListWalkThroughFA == -1) { + FarAwayList = aOccluders[ListWalkThroughFA].listIndex; + } + else { + aOccluders[PreviousListWalkThroughFA].listIndex = aOccluders[ListWalkThroughFA].listIndex; + } + + int prevNearbyList = NearbyList; + ListWalkThroughFA = aOccluders[ListWalkThroughFA].listIndex; + NearbyList = prevListWalkThroughFA; + aOccluders[prevListWalkThroughFA].listIndex = prevNearbyList; + } + else { + PreviousListWalkThroughFA = ListWalkThroughFA; + ListWalkThroughFA = aOccluders[ListWalkThroughFA].listIndex; + } + } + + int prevNearbyList = -1; + int tmpNearbyList = NearbyList; + int indexTmpNearbyList, storeTmpNearbyList, prevFarAwayList; + while (tmpNearbyList != -1) + { + if (NumActiveOccluders < NUMACTIVEOCCLUDERS && aOccluders[tmpNearbyList].ProcessOneOccluder(&aActiveOccluders[NumActiveOccluders])) + ++NumActiveOccluders; + + indexTmpNearbyList = tmpNearbyList; + if (aOccluders[indexTmpNearbyList].NearCamera()) + { + prevNearbyList = tmpNearbyList; + tmpNearbyList = aOccluders[indexTmpNearbyList].listIndex; + + } + else + { + storeTmpNearbyList = tmpNearbyList; + if (prevNearbyList == -1) { + NearbyList = aOccluders[indexTmpNearbyList].listIndex; + } + else { + aOccluders[prevNearbyList].listIndex = aOccluders[indexTmpNearbyList].listIndex; + } + tmpNearbyList = aOccluders[indexTmpNearbyList].listIndex; + prevFarAwayList = FarAwayList; + FarAwayList = storeTmpNearbyList; + aOccluders[storeTmpNearbyList].listIndex = prevFarAwayList; + } + } + + for (i = 0; i < NumActiveOccluders; i++) { + for (int j = 0; j < NumActiveOccluders; j++) { + if (i != j && aActiveOccluders[j].radius < aActiveOccluders[i].radius) { + if (OccluderHidesBehind(&aActiveOccluders[i], &aActiveOccluders[j])) { + for (int k = i; k < NumActiveOccluders - 1; k++) { + for (int l = 0; l < aActiveOccluders[k + 1].linesCount; l++) + aActiveOccluders[k].lines[l] = aActiveOccluders[k + 1].lines[l]; + aActiveOccluders[k].linesCount = aActiveOccluders[k + 1].linesCount; + aActiveOccluders[k].radius = aActiveOccluders[k + 1].radius; + } + NumActiveOccluders--; + i--; + // Taken from Mobile! +#ifdef FIX_BUGS + if (i == -1) { + i = 0; + } +#endif + } + } + } + } +} + +bool CActiveOccluder::IsPointWithinOcclusionArea(float pX, float pY, float area) { + for (int i = 0; i < linesCount; i++) { + if (!IsPointInsideLine(lines[i].origin.x, lines[i].origin.y, lines[i].direction.x, lines[i].direction.y, pX, pY, area)) + return false; + } + + return true; +} + +bool COcclusion::IsAABoxOccluded(CVector pos, float width, float length, float height) { + + CVector coors; + float outW, outH; + + if (!NumActiveOccluders || !CalcScreenCoors(pos, &coors, &outW, &outH)) + return false; + + float side = CVector(width, length, height).Magnitude() / 4.0f; + float area = Max(outW, outH) * side; + + CVector minCorner, maxCorner; + + minCorner.x = pos.x - width / 2.0f; + minCorner.y = pos.y - length / 2.0f; + minCorner.z = pos.z - height / 2.0f; + + maxCorner.x = pos.x + width / 2.0f; + maxCorner.y = pos.y + length / 2.0f; + maxCorner.z = pos.z + height / 2.0f; + + for (int i = 0; i < NumActiveOccluders; i++) { + if (coors.z - (side * 0.85f) > aActiveOccluders[i].radius) { + if (aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, area)) + return true; + + if (aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) { + if (CalcScreenCoors(minCorner, &coors) && !aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) continue; + if (CalcScreenCoors(CVector(maxCorner.x, maxCorner.y, minCorner.z), &coors) && !aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) continue; + if (CalcScreenCoors(CVector(maxCorner.x, minCorner.y, maxCorner.z), &coors) && !aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) continue; + if (CalcScreenCoors(CVector(minCorner.x, maxCorner.y, maxCorner.z), &coors, &outW, &outH) && !aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) continue; + + return true; + } + } + } + + return false; +} + +bool COcclusion::IsPositionOccluded(CVector pos, float side) { + + CVector coors; + float width, height; + + if (!NumActiveOccluders || !CalcScreenCoors(pos, &coors, &width, &height)) + return false; + + float area = Max(width, height) * side; + + for (int i = 0; i < NumActiveOccluders; i++) { + if (coors.z - (side * 0.85f) > aActiveOccluders[i].radius) + if (aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, area)) + return true; + } + + return false; +} + +#ifndef MASTER +#include "Lines.h" + +RwIm2DVertex vertexbufferT[2]; + +void COcclusion::Render() { + if (!bDispayOccDebugStuff || !(CTimer::GetTimeInMilliseconds() & 0x200)) + return; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, FALSE); + + float recipz = 1.0f/RwCameraGetNearClipPlane(Scene.camera); + for (int i = 0; i < NumActiveOccluders; i++) { + for (int j = 0; j < aActiveOccluders[i].linesCount; j++) { + RwIm2DVertexSetScreenX(&vertexbufferT[0], aActiveOccluders[i].lines[j].origin.x); + RwIm2DVertexSetScreenY(&vertexbufferT[0], aActiveOccluders[i].lines[j].origin.y); + RwIm2DVertexSetScreenZ(&vertexbufferT[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&vertexbufferT[0], RwCameraGetNearClipPlane(Scene.camera)); + RwIm2DVertexSetRecipCameraZ(&vertexbufferT[0], recipz); + + RwIm2DVertexSetScreenX(&vertexbufferT[1], + aActiveOccluders[i].lines[j].origin.x + aActiveOccluders[i].lines[j].direction.x * aActiveOccluders[i].lines[j].length); + RwIm2DVertexSetScreenY(&vertexbufferT[1], + aActiveOccluders[i].lines[j].origin.y + aActiveOccluders[i].lines[j].direction.y * aActiveOccluders[i].lines[j].length); + RwIm2DVertexSetScreenZ(&vertexbufferT[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetCameraZ(&vertexbufferT[1], RwCameraGetNearClipPlane(Scene.camera)); + RwIm2DVertexSetRecipCameraZ(&vertexbufferT[1], recipz); + + RwIm2DVertexSetIntRGBA(&vertexbufferT[0], 255, 255, 0, 255); + RwIm2DVertexSetIntRGBA(&vertexbufferT[1], 255, 255, 0, 255); + RwIm2DRenderLine(vertexbufferT, 2, 0, 1); + } + } + + DefinedState(); +} +#endif + +bool CEntity::IsEntityOccluded(void) { + + CVector coors; + float width, height; + + if (COcclusion::NumActiveOccluders == 0 || !CalcScreenCoors(GetBoundCentre(), &coors, &width, &height)) + return false; + + float area = Max(width, height) * GetBoundRadius() * 0.9f; + + for (int i = 0; i < COcclusion::NumActiveOccluders; i++) { + if (coors.z - (GetBoundRadius() * 0.85f) > COcclusion::aActiveOccluders[i].radius) { + if (COcclusion::aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, area)) { + return true; + } + + if (COcclusion::aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) { + CVector min = m_matrix * CModelInfo::GetColModel(m_modelIndex)->boundingBox.min; + CVector max = m_matrix * CModelInfo::GetColModel(m_modelIndex)->boundingBox.max; + + if (CalcScreenCoors(min, &coors) && !COcclusion::aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) continue; + if (CalcScreenCoors(CVector(max.x, max.y, min.z), &coors) && !COcclusion::aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) continue; + if (CalcScreenCoors(CVector(max.x, min.y, max.z), &coors) && !COcclusion::aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) continue; + if (CalcScreenCoors(CVector(min.x, max.y, max.z), &coors) && !COcclusion::aActiveOccluders[i].IsPointWithinOcclusionArea(coors.x, coors.y, 0.0f)) continue; + + return true; + } + } + } + + return false; +} \ No newline at end of file diff --git a/src/miami/renderer/Occlusion.h b/src/miami/renderer/Occlusion.h new file mode 100644 index 00000000..e0edef53 --- /dev/null +++ b/src/miami/renderer/Occlusion.h @@ -0,0 +1,62 @@ +#pragma once + +struct ActiveOccluderLine { + CVector2D origin; + CVector2D direction; + float length; +}; + +class CActiveOccluder { + +public: + ActiveOccluderLine lines[6]; + int32 linesCount; + float radius; + + bool IsPointWithinOcclusionArea(float x, float y, float area); +}; + +class COccluder +{ +public: + int16 length, width, height; + int16 x, y, z; + uint16 angle; + int16 listIndex; + + bool NearCamera(); + bool ProcessOneOccluder(CActiveOccluder *occl); + bool ProcessLineSegment(int corner1, int corner2, CActiveOccluder* occl); + float GetAngle(void) { return angle*TWOPI/UINT16_MAX; } +}; + +class COcclusion +{ +public: + static int32 NumOccludersOnMap; + static int16 FarAwayList; + static int16 NearbyList; + static int16 ListWalkThroughFA; + static int16 PreviousListWalkThroughFA; + static int16 NumActiveOccluders; + + static COccluder aOccluders[NUMOCCLUSIONVOLUMES]; + static CActiveOccluder aActiveOccluders[NUMACTIVEOCCLUDERS]; + + static void Init(void); + static void AddOne(float x, float y, float z, float width, float length, float height, float angle); + static void ProcessBeforeRendering(void); + static bool OccluderHidesBehind(CActiveOccluder *occl1, CActiveOccluder *occl2); + static bool IsAABoxOccluded(CVector pos, float width, float length, float height); + static bool IsPositionOccluded(CVector pos, float side); +#ifndef MASTER + static void Render(); +#endif +}; + +bool CalcScreenCoors(CVector const &in, CVector *out, float *outw, float *outh); +bool CalcScreenCoors(CVector const &in, CVector *out); + +#ifndef MASTER +extern bool bDispayOccDebugStuff; +#endif \ No newline at end of file diff --git a/src/miami/renderer/Particle.cpp b/src/miami/renderer/Particle.cpp new file mode 100644 index 00000000..461a10a6 --- /dev/null +++ b/src/miami/renderer/Particle.cpp @@ -0,0 +1,2534 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Timer.h" +#include "TxdStore.h" +#include "Sprite.h" +#include "Camera.h" +#include "Clock.h" +#include "Collision.h" +#include "World.h" +#include "Shadows.h" +#include "Replay.h" +#include "Stats.h" +#include "Weather.h" +#include "MBlur.h" +#include "main.h" +#include "AudioScriptObject.h" +#include "ParticleObject.h" +#include "Particle.h" +#include "soundlist.h" +#include "SaveBuf.h" +#include "debugmenu.h" + +#define MAX_PARTICLES_ON_SCREEN (750) + + +//(5) +#define MAX_SMOKE_FILES ARRAY_SIZE(SmokeFiles) + +//(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) +//(8) +#define MAX_BOAT_FILES ARRAY_SIZE(BoatFiles) +//(4) +#define MAX_CARDEBRIS_FILES ARRAY_SIZE(CardebrisFiles) +//(4) +#define MAX_CARSPLASH_FILES ARRAY_SIZE(CarsplashFiles) + +#define MAX_RAINDRIP_FILES (2) + +#define MAX_LEAF_FILES (2) + + +const char SmokeFiles[][6+1] = +{ + "smoke1", + "smoke2", + "smoke3", + "smoke4", + "smoke5" +}; + + +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 RainSplashupFiles[][10+1] = +{ + "splash_up1", + "splash_up2" +}; + +const char BirdfrontFiles[][8+1] = +{ + "birdf_01", + "birdf_02", + "birdf_03", + "birdf_04" +}; + +const char BoatFiles[][8+1] = +{ + "boats_01", + "boats_02", + "boats_03", + "boats_04", + "boats_05", + "boats_06", + "boats_07", + "boats_08" +}; + +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; +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 *gpBoatTex[MAX_BOAT_FILES]; +RwTexture *gpCarDebrisTex[MAX_CARDEBRIS_FILES]; +RwTexture *gpCarSplashTex[MAX_CARSPLASH_FILES]; + +RwTexture *gpBoatWakeTex; +RwTexture *gpFlame1Tex; +RwTexture *gpFlame5Tex; +RwTexture *gpRainDropSmallTex; +RwTexture *gpBloodTex; +RwTexture *gpLeafTex[MAX_LEAF_FILES]; +RwTexture *gpCloudTex1; +RwTexture *gpCloudTex4; +RwTexture *gpBloodSmallTex; +RwTexture *gpGungeTex; +RwTexture *gpCollisionSmokeTex; +RwTexture *gpBulletHitTex; +RwTexture *gpGunShellTex; +RwTexture *gpPointlightTex; + +RwRaster *gpSmokeRaster[MAX_SMOKE_FILES]; +RwRaster *gpSmoke2Raster; +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 *gpBoatRaster[MAX_BOAT_FILES]; +RwRaster *gpCarDebrisRaster[MAX_CARDEBRIS_FILES]; +RwRaster *gpCarSplashRaster[MAX_CARSPLASH_FILES]; + +RwRaster *gpBoatWakeRaster; +RwRaster *gpFlame1Raster; +RwRaster *gpFlame5Raster; +RwRaster *gpRainDropSmallRaster; +RwRaster *gpBloodRaster; +RwRaster *gpLeafRaster[MAX_LEAF_FILES]; +RwRaster *gpCloudRaster1; +RwRaster *gpCloudRaster4; +RwRaster *gpBloodSmallRaster; +RwRaster *gpGungeRaster; +RwRaster *gpCollisionSmokeRaster; +RwRaster *gpBulletHitRaster; +RwRaster *gpGunShellRaster; +RwRaster *gpPointlightRaster; + +RwTexture *gpRainDropTex; +RwRaster *gpRainDropRaster; + +RwTexture *gpSparkTex; +RwTexture *gpNewspaperTex; +RwTexture *gpGunSmokeTex; +RwTexture *gpDotTex; +RwTexture *gpHeatHazeTex; +RwTexture *gpBeastieTex; +RwTexture *gpRainDripTex[MAX_RAINDRIP_FILES]; +RwTexture *gpRainDripDarkTex[MAX_RAINDRIP_FILES]; + +RwRaster *gpSparkRaster; +RwRaster *gpNewspaperRaster; +RwRaster *gpGunSmokeRaster; +RwRaster *gpDotRaster; +RwRaster *gpHeatHazeRaster; +RwRaster *gpBeastieRaster; +RwRaster *gpRainDripRaster[MAX_RAINDRIP_FILES]; +RwRaster *gpRainDripDarkRaster[MAX_RAINDRIP_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 PARTICLE_WIND_TEST_SCALE = 0.002f; +float fParticleScaleLimit = 0.5f; + +bool clearWaterDrop; +int32 numWaterDropOnScreen; + +#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]); + } + + gpSmoke2Tex = RwTextureRead("smokeII_3", nil); + gpSmoke2Raster = RwTextureGetRaster(gpSmoke2Tex); + + 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], nil); + gpGunFlashRaster[i] = RwTextureGetRaster(gpGunFlashTex[i]); + } + + gpRainDropTex = RwTextureRead("raindrop4", nil); + gpRainDropRaster = RwTextureGetRaster(gpRainDropTex); + + + 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], nil); + gpBirdfrontRaster[i] = RwTextureGetRaster(gpBirdfrontTex[i]); + } + + for ( int32 i = 0; i < MAX_BOAT_FILES; i++ ) + { + gpBoatTex[i] = RwTextureRead(BoatFiles[i], nil); + gpBoatRaster[i] = RwTextureGetRaster(gpBoatTex[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]); + } + + gpBoatWakeTex = RwTextureRead("boatwake2", nil); + gpBoatWakeRaster = RwTextureGetRaster(gpBoatWakeTex); + + gpFlame1Tex = RwTextureRead("flame1", nil); + 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[0] = RwTextureRead("gameleaf01_64", nil); + gpLeafRaster[0] = RwTextureGetRaster(gpLeafTex[0]); + + gpLeafTex[1] = RwTextureRead("letter", nil); + gpLeafRaster[1] = RwTextureGetRaster(gpLeafTex[1]); + + 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); + + gpPointlightTex = RwTextureRead("pointlight", nil); + gpPointlightRaster = RwTextureGetRaster(gpPointlightTex); + + gpSparkTex = RwTextureRead("spark", nil); + gpSparkRaster = RwTextureGetRaster(gpSparkTex); + + gpNewspaperTex = RwTextureRead("newspaper02_64", nil); + gpNewspaperRaster = RwTextureGetRaster(gpNewspaperTex); + + gpGunSmokeTex = RwTextureRead("gunsmoke3", nil); + gpGunSmokeRaster = RwTextureGetRaster(gpGunSmokeTex); + + gpDotTex = RwTextureRead("dot", nil); + gpDotRaster = RwTextureGetRaster(gpDotTex); + + gpHeatHazeTex = RwTextureRead("heathaze", nil); + gpHeatHazeRaster = RwTextureGetRaster(gpHeatHazeTex); + + gpBeastieTex = RwTextureRead("beastie", nil); + gpBeastieRaster = RwTextureGetRaster(gpBeastieTex); + + gpRainDripTex[0] = RwTextureRead("raindrip64", nil); + gpRainDripRaster[0] = RwTextureGetRaster(gpRainDripTex[0]); + + gpRainDripTex[1] = RwTextureRead("raindripb64", nil); + gpRainDripRaster[1] = RwTextureGetRaster(gpRainDripTex[1]); + + gpRainDripDarkTex[0] = RwTextureRead("raindrip64_d", nil); + gpRainDripDarkRaster[0] = RwTextureGetRaster(gpRainDripDarkTex[0]); + + gpRainDripDarkTex[1] = RwTextureRead("raindripb64_d", nil); + gpRainDripDarkRaster[1] = RwTextureGetRaster(gpRainDripDarkTex[1]); + + CTxdStore::PopCurrentTxd(); + + for ( int32 i = 0; i < MAX_PARTICLES; i++ ) + { + tParticleSystemData *entry = &mod_ParticleSystemManager.m_aParticles[i]; + + switch ( i ) + { + case PARTICLE_SPARK: + case PARTICLE_SPARK_SMALL: + case PARTICLE_RAINDROP_SMALL: + case PARTICLE_HELI_ATTACK: + entry->m_ppRaster = &gpRainDropSmallRaster; + break; + + case PARTICLE_WATER_SPARK: + entry->m_ppRaster = &gpSparkRaster; + break; + + case PARTICLE_WHEEL_DIRT: + case PARTICLE_SAND: + case PARTICLE_STEAM2: + case PARTICLE_STEAM_NY: + case PARTICLE_STEAM_NY_SLOWMOTION: + case PARTICLE_GROUND_STEAM: + case PARTICLE_ENGINE_STEAM: + case PARTICLE_PEDFOOT_DUST: + case PARTICLE_CAR_DUST: + case PARTICLE_EXHAUST_FUMES: + entry->m_ppRaster = &gpSmoke2Raster; + break; + + case PARTICLE_WHEEL_WATER: + case PARTICLE_WATER: + case PARTICLE_SMOKE: + case PARTICLE_SMOKE_SLOWMOTION: + case PARTICLE_DRY_ICE: + case PARTICLE_GARAGEPAINT_SPRAY: + case PARTICLE_STEAM: + 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_BLOOD: + entry->m_ppRaster = &gpBloodRaster; + break; + + case PARTICLE_BLOOD_SMALL: + case PARTICLE_BLOOD_SPURT: + entry->m_ppRaster = &gpBloodSmallRaster; + break; + + case PARTICLE_DEBRIS: + case PARTICLE_TREE_LEAVES: + entry->m_ppRaster = gpLeafRaster; + break; + + case PARTICLE_DEBRIS2: + entry->m_ppRaster = &gpGungeRaster; + break; + + case PARTICLE_FLYERS: + entry->m_ppRaster = &gpNewspaperRaster; + break; + + case PARTICLE_FLAME: + case PARTICLE_CARFLAME: + entry->m_ppRaster = &gpFlame1Raster; + break; + + case PARTICLE_FIREBALL: + entry->m_ppRaster = &gpFlame5Raster; + break; + + case PARTICLE_GUNFLASH: + case PARTICLE_GUNFLASH_NOANIM: + entry->m_ppRaster = gpGunFlashRaster; + break; + + + case PARTICLE_GUNSMOKE: + case PARTICLE_WATERDROP: + case PARTICLE_BLOODDROP: + case PARTICLE_HEATHAZE: + case PARTICLE_HEATHAZE_IN_DIST: + entry->m_ppRaster = nil; + break; + + case PARTICLE_GUNSMOKE2: + case PARTICLE_BOAT_THRUSTJET: + case PARTICLE_RUBBER_SMOKE: + entry->m_ppRaster = gpRubberRaster; + break; + + case PARTICLE_CIGARETTE_SMOKE: + entry->m_ppRaster = &gpGunSmokeRaster; + break; + + case PARTICLE_TEARGAS: + entry->m_ppRaster = &gpHeatHazeRaster; + break; + + case PARTICLE_SHARD: + case PARTICLE_RAINDROP: + case PARTICLE_RAINDROP_2D: + entry->m_ppRaster = &gpRainDropRaster; + break; + + case PARTICLE_SPLASH: + case PARTICLE_PED_SPLASH: + case PARTICLE_CAR_SPLASH: + case PARTICLE_WATER_HYDRANT: + entry->m_ppRaster = gpCarSplashRaster; + 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_EXPLOSION_MEDIUM: + case PARTICLE_EXPLOSION_LARGE: + case PARTICLE_EXPLOSION_MFAST: + case PARTICLE_EXPLOSION_LFAST: + entry->m_ppRaster = gpExplosionMediumRaster; + break; + + case PARTICLE_BOAT_SPLASH: + entry->m_ppRaster = &gpBoatWakeRaster; + break; + + case PARTICLE_ENGINE_SMOKE: + case PARTICLE_ENGINE_SMOKE2: + case PARTICLE_CARFLAME_SMOKE: + case PARTICLE_FIREBALL_SMOKE: + case PARTICLE_ROCKET_SMOKE: + case PARTICLE_TEST: + entry->m_ppRaster = &gpCloudRaster4; + break; + + case PARTICLE_CARCOLLISION_DUST: + case PARTICLE_BURNINGRUBBER_SMOKE: + entry->m_ppRaster = &gpCollisionSmokeRaster; + break; + + case PARTICLE_CAR_DEBRIS: + case PARTICLE_HELI_DEBRIS: + case PARTICLE_BIRD_DEBRIS: + entry->m_ppRaster = gpCarDebrisRaster; + break; + + case PARTICLE_GUNSHELL_FIRST: + case PARTICLE_GUNSHELL: + case PARTICLE_GUNSHELL_BUMP1: + case PARTICLE_GUNSHELL_BUMP2: + entry->m_ppRaster = &gpGunShellRaster; + break; + + + case PARTICLE_BIRD_FRONT: + entry->m_ppRaster = gpBirdfrontRaster; + break; + + case PARTICLE_SHIP_SIDE: + entry->m_ppRaster = gpBoatRaster; + break; + + case PARTICLE_BEASTIE: + entry->m_ppRaster = &gpBeastieRaster; + break; + } + } + + debug("CParticle ready"); +} + +void CParticle::Shutdown() +{ + debug("Shutting down CParticle..."); + + for ( int32 i = 0; i < MAX_SMOKE_FILES; i++ ) + { + RwTextureDestroy(gpSmokeTex[i]); + gpSmokeTex[i] = nil; + } + + RwTextureDestroy(gpSmoke2Tex); + gpSmoke2Tex = nil; + + for ( int32 i = 0; i < MAX_RUBBER_FILES; i++ ) + { + RwTextureDestroy(gpRubberTex[i]); + gpRubberTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_RAINSPLASH_FILES; i++ ) + { + RwTextureDestroy(gpRainSplashTex[i]); + gpRainSplashTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_WATERSPRAY_FILES; i++ ) + { + RwTextureDestroy(gpWatersprayTex[i]); + gpWatersprayTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_EXPLOSIONMEDIUM_FILES; i++ ) + { + RwTextureDestroy(gpExplosionMediumTex[i]); + gpExplosionMediumTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_GUNFLASH_FILES; i++ ) + { + RwTextureDestroy(gpGunFlashTex[i]); + gpGunFlashTex[i] = nil; + } + + RwTextureDestroy(gpRainDropTex); + gpRainDropTex = nil; + + for ( int32 i = 0; i < MAX_RAINSPLASHUP_FILES; i++ ) + { + RwTextureDestroy(gpRainSplashupTex[i]); + gpRainSplashupTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_BIRDFRONT_FILES; i++ ) + { + RwTextureDestroy(gpBirdfrontTex[i]); + gpBirdfrontTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_BOAT_FILES; i++ ) + { + RwTextureDestroy(gpBoatTex[i]); + gpBoatTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_CARDEBRIS_FILES; i++ ) + { + RwTextureDestroy(gpCarDebrisTex[i]); + gpCarDebrisTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_CARSPLASH_FILES; i++ ) + { + RwTextureDestroy(gpCarSplashTex[i]); + gpCarSplashTex[i] = nil; + } + + for ( int32 i = 0; i < MAX_RAINDRIP_FILES; i++ ) + { + RwTextureDestroy(gpRainDripTex[i]); + gpRainDripTex[i] = nil; + + RwTextureDestroy(gpRainDripDarkTex[i]); + gpRainDripDarkTex[i] = nil; + } + + RwTextureDestroy(gpBoatWakeTex); + gpBoatWakeTex = nil; + + RwTextureDestroy(gpFlame1Tex); + gpFlame1Tex = nil; + + RwTextureDestroy(gpFlame5Tex); + gpFlame5Tex = nil; + + RwTextureDestroy(gpRainDropSmallTex); + gpRainDropSmallTex = nil; + + RwTextureDestroy(gpBloodTex); + gpBloodTex = nil; + + RwTextureDestroy(gpLeafTex[0]); + gpLeafTex[0] = nil; + + RwTextureDestroy(gpLeafTex[1]); + gpLeafTex[1] = nil; + + RwTextureDestroy(gpCloudTex1); + gpCloudTex1 = nil; + + RwTextureDestroy(gpCloudTex4); + gpCloudTex4 = nil; + + RwTextureDestroy(gpBloodSmallTex); + gpBloodSmallTex = nil; + + RwTextureDestroy(gpGungeTex); + gpGungeTex = nil; + + RwTextureDestroy(gpCollisionSmokeTex); + gpCollisionSmokeTex = nil; + + RwTextureDestroy(gpBulletHitTex); + gpBulletHitTex = nil; + + RwTextureDestroy(gpGunShellTex); + gpGunShellTex = nil; + + RwTextureDestroy(gpPointlightTex); + gpPointlightTex = nil; + + RwTextureDestroy(gpSparkTex); + gpSparkTex = nil; + + RwTextureDestroy(gpNewspaperTex); + gpNewspaperTex = nil; + + RwTextureDestroy(gpGunSmokeTex); + gpGunSmokeTex = nil; + + RwTextureDestroy(gpDotTex); + gpDotTex = nil; + RwTextureDestroy(gpHeatHazeTex); + gpHeatHazeTex = nil; + + RwTextureDestroy(gpBeastieTex); + gpBeastieTex = nil; + + int32 slot; + + slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::RemoveTxdSlot(slot); + + debug("CParticle shut down"); +} + + +void CParticle::AddParticlesAlongLine(tParticleType type, CVector const &vecStart, CVector const &vecEnd, CVector const &vecDir, float fPower, CEntity *pEntity, float fSize, int32 nRotationSpeed, int32 nRotation, int32 nCurFrame, int32 nLifeSpan) +{ + CVector vecDist = vecEnd - vecStart; + float fDist = vecDist.Magnitude(); + float fSteps = Max(fDist / fPower, 1.0f); + int32 nSteps = (int32)fSteps; + + CVector vecStep = vecDist * (1.0f / (float)nSteps); + + for ( int32 i = 0; i < nSteps; i++ ) + { + CVector vecPos = float(i) * vecStep + vecStart; + AddParticle(type, vecPos, vecDir, pEntity, fSize, nRotationSpeed, nRotation, nCurFrame, nLifeSpan); + } +} + +void CParticle::AddParticlesAlongLine(tParticleType type, CVector const &vecStart, CVector const &vecEnd, CVector const &vecDir, float fPower, CEntity *pEntity, float fSize, RwRGBA const &color, int32 nRotationSpeed, int32 nRotation, int32 nCurFrame, int32 nLifeSpan) +{ + CVector vecDist = vecEnd - vecStart; + float fDist = vecDist.Magnitude(); + float fSteps = Max(fDist / fPower, 1.0f); + int32 nSteps = (int32)fSteps; + + CVector vecStep = vecDist * (1.0f / (float)nSteps); + + for ( int32 i = 0; i < nSteps; i++ ) + { + CVector vecPos = float(i) * vecStep + vecStart; + + 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, 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 nil; + + 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; + } + + if ( !CReplay::IsPlayingBack() ) + CReplay::RecordParticle(type, vecPos, vecDir, fSize, color); + + 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_nFadeToBlackTimer = psystem->m_nFadeToBlackAmount; + + if ( psystem->m_nFadeToBlackTime ) + pParticle->m_nFadeToBlackTimer /= psystem->m_nFadeToBlackTime; + + pParticle->m_nAlpha = psystem->m_nFadeAlphaInitialIntensity; + + pParticle->m_nFadeAlphaTimer = psystem->m_nFadeAlphaAmount; + + if ( psystem->m_nFadeAlphaTime ) + pParticle->m_nFadeAlphaTimer /= psystem->m_nFadeAlphaTime; + + 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_nZRotationTimer = 0; + pParticle->m_nZRadiusTimer = 0; + pParticle->m_nAnimationSpeedTimer = 0; + pParticle->m_fZGround = 0.0f; + + if ( type != PARTICLE_HEATHAZE ) + pParticle->m_vecPosition = vecPos; + else + { + CVector screen; + float w, h; + + if ( !CSprite::CalcScreenCoors(vecPos, &screen, &w, &h, true) ) + return nil; + + pParticle->m_vecPosition = screen; + psystem->m_vecTextureStretch.x = w; + psystem->m_vecTextureStretch.y = h; + } + + 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; + + if ( nRotationSpeed != 0 ) + pParticle->m_nRotationStep = nRotationSpeed; + else + pParticle->m_nRotationStep = psystem->m_nRotationSpeed; + + if ( CGeneral::GetRandomNumber() & 1 ) + pParticle->m_nRotationStep = -pParticle->m_nRotationStep; + + 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 && !(psystem->Flags & SCREEN_TRAIL) ) + 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(); + + // ejaculation at 23:00, 23:15, 23:30, 23:45 + if ( CClock::ms_nGameClockHours == 23 && + ( CClock::ms_nGameClockMinutes == 0 + || CClock::ms_nGameClockMinutes == 15 + || CClock::ms_nGameClockMinutes == 30 + || CClock::ms_nGameClockMinutes == 45 ) ) + { + AddParticle(PARTICLE_CAR_SPLASH, + CVector(557.03f, -4.0f, 151.46f), + CVector(0.0f, 0.0f, 2.5f), + NULL, + 2.0f, + CRGBA(255, 255, 255, 255), + 0, + 0, + 1, + 1000); + } + + 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) ) + { + CVector vecWind(0.0f, 0.0f, 0.0f); + + bRemoveParticle = false; + + CVector vecMoveStep = particle->m_vecVelocity * CTimer::GetTimeStep(); + CVector vecPos = particle->m_vecPosition; + + if ( numWaterDropOnScreen == 0 ) + clearWaterDrop = false; + + if ( psystem->m_Type == PARTICLE_WATERDROP ) + { + if ( CGame::IsInInterior() || clearWaterDrop == true ) + { + bRemoveParticle = true; + continue; + } + + static uint8 nWaterDropCount; + + if ( nWaterDropCount == 5 ) + { + vecMoveStep = CVector(0.0f, 0.0f, 0.0f); + particle->m_nTimeWhenWillBeDestroyed += 1250; + nWaterDropCount = 0; + } + else + { + if ( TheCamera.m_CameraAverageSpeed > 0.35f ) + { + if ( vecMoveStep.Magnitude() > 0.5f ) + { + if ( vecMoveStep.Magnitude() > 0.4f && vecMoveStep.Magnitude() < 0.8f ) + { + vecMoveStep.x += TheCamera.m_CameraAverageSpeed * 1.5f; + vecMoveStep.y += TheCamera.m_CameraAverageSpeed * 1.5f; + } + else if ( vecMoveStep.Magnitude() != 0.0f ) + { + vecMoveStep.x += CGeneral::GetRandomNumberInRange(0.01f, 0.05f); + vecMoveStep.y += CGeneral::GetRandomNumberInRange(0.01f, 0.05f); + } + } + } + + nWaterDropCount++; + } + + if ( vecPos.z <= 1.5f ) + vecMoveStep.z = 0.0f; + } + + if ( psystem->m_Type == PARTICLE_HEATHAZE || psystem->m_Type == PARTICLE_HEATHAZE_IN_DIST ) + { +#ifdef FIX_BUGS + int32 nSinCosIndex = (int32(DEGTORAD((float)particle->m_nRotation) * float(SIN_COS_TABLE_SIZE) / TWOPI) + SIN_COS_TABLE_SIZE) % SIN_COS_TABLE_SIZE; +#else + int32 nSinCosIndex = int32(DEGTORAD((float)particle->m_nRotation) * float(SIN_COS_TABLE_SIZE) / TWOPI) % SIN_COS_TABLE_SIZE; +#endif + vecMoveStep.x = Sin(nSinCosIndex); + vecMoveStep.y = Sin(nSinCosIndex); + + if ( psystem->m_Type == PARTICLE_HEATHAZE_IN_DIST ) + particle->m_nRotation = int16((float)particle->m_nRotation + 0.75f); + else + particle->m_nRotation = int16((float)particle->m_nRotation + 1.0f); + } + + if ( psystem->m_Type == PARTICLE_BEASTIE ) + { +#ifdef FIX_BUGS + int32 nSinCosIndex = (int32(DEGTORAD((float)particle->m_nRotation) * float(SIN_COS_TABLE_SIZE) / TWOPI) + SIN_COS_TABLE_SIZE) % SIN_COS_TABLE_SIZE; +#else + int32 nSinCosIndex = int32(DEGTORAD((float)particle->m_nRotation) * float(SIN_COS_TABLE_SIZE) / TWOPI) % SIN_COS_TABLE_SIZE; +#endif + particle->m_vecVelocity.x = 0.50f * Cos(nSinCosIndex); + particle->m_vecVelocity.y = Cos(nSinCosIndex); + particle->m_vecVelocity.z = 0.25f * Sin(nSinCosIndex); + + if ( particle->m_vecVelocity.Magnitude() > 2.0f + || vecPos.z > 40.0f + || (TheCamera.GetPosition() - vecPos).Magnitude() < 60.0f + ) + { + bRemoveParticle = true; + continue; + } + } + + vecPos += vecMoveStep; + + if ( psystem->m_Type == PARTICLE_FIREBALL ) + { + AddParticle(PARTICLE_HEATHAZE, particle->m_vecPosition, CVector(0.0f, 0.0f, 0.0f), + nil, particle->m_fSize * 5.0f); + } + + if ( psystem->m_Type == PARTICLE_GUNSMOKE2 ) + { + if ( CTimer::GetFrameCounter() & 10 ) + { +#ifdef FIX_BUGS + if ( FindPlayerPed() && FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN ) +#else + if ( FindPlayerPed()->GetWeapon()->m_eWeaponType == WEAPONTYPE_MINIGUN ) +#endif + { + AddParticle(PARTICLE_HEATHAZE, particle->m_vecPosition, CVector(0.0f, 0.0f, 0.0f)); + } + } + } + + if ( CWeather::Wind > 0.0f ) + { + if ( vecMoveStep.Magnitude() != 0.0f ) + { + vecWind.x = CGeneral::GetRandomNumberInRange(0.75f, 1.25f) * -CWeather::Wind; + vecWind.y = CGeneral::GetRandomNumberInRange(0.75f, 1.25f) * -CWeather::Wind; + vecWind *= PARTICLE_WIND_TEST_SCALE * psystem->m_fWindFactor * CTimer::GetTimeStep(); + particle->m_vecVelocity += vecWind; + } + } + + if ( psystem->m_Type == PARTICLE_RAINDROP + || psystem->m_Type == PARTICLE_RAINDROP_SMALL + || psystem->m_Type == PARTICLE_RAIN_SPLASH + || psystem->m_Type == PARTICLE_RAIN_SPLASH_BIGGROW + || psystem->m_Type == PARTICLE_CAR_SPLASH + || psystem->m_Type == PARTICLE_BOAT_SPLASH + || psystem->m_Type == PARTICLE_RAINDROP_2D ) + { + int32 nMaxDrops = int32(6.0f * TheCamera.m_CameraAverageSpeed + 1.0f); + float fDistToCam = 0.0f; + + if ( psystem->m_Type == PARTICLE_BOAT_SPLASH || psystem->m_Type == PARTICLE_CAR_SPLASH ) + { + if ( vecPos.z + particle->m_fSize < 5.0f ) + { + bRemoveParticle = true; + continue; + } + + switch ( TheCamera.GetLookDirection() ) + { + case LOOKING_LEFT: + case LOOKING_RIGHT: + case LOOKING_FORWARD: + nMaxDrops /= 2; + break; + + default: + nMaxDrops = 0; + break; + } + + fDistToCam = (TheCamera.GetPosition() - vecPos).Magnitude(); + } + + if ( numWaterDropOnScreen < nMaxDrops && numWaterDropOnScreen < 63 + && fDistToCam < 10.0f + && clearWaterDrop == false + && !CGame::IsInInterior() ) + { + CVector vecWaterdropTarget + ( + CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), + CGeneral::GetRandomNumberInRange(0.1f, 0.75f), + -0.01f + ); + + CVector vecWaterdropPos; + + if ( TheCamera.m_CameraAverageSpeed < 0.35f ) + vecWaterdropPos.x = (float)CGeneral::GetRandomNumberInRange(50, int32(SCREEN_WIDTH) - 50); + else + vecWaterdropPos.x = (float)CGeneral::GetRandomNumberInRange(200, int32(SCREEN_WIDTH) - 200); + + if ( psystem->m_Type == PARTICLE_BOAT_SPLASH || psystem->m_Type == PARTICLE_CAR_SPLASH ) + vecWaterdropPos.y = (float)CGeneral::GetRandomNumberInRange(SCREEN_HEIGHT / 2, SCREEN_HEIGHT); + else + { + if ( TheCamera.m_CameraAverageSpeed < 0.35f ) + vecWaterdropPos.y = (float)CGeneral::GetRandomNumberInRange(0, int32(SCREEN_HEIGHT)); + else + vecWaterdropPos.y = (float)CGeneral::GetRandomNumberInRange(150, int32(SCREEN_HEIGHT) - 200); + } + + vecWaterdropPos.z = 2.0f; + + if ( AddParticle(PARTICLE_WATERDROP, + vecWaterdropPos, + vecWaterdropTarget, + nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.15f), + 0, + 0, + CGeneral::GetRandomNumber() & 1, + 0) != nil ) + { + numWaterDropOnScreen++; + } + } + } + + 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; + } + } + + if ( !(psystem->Flags & SCREEN_TRAIL) ) + { + float size; + + if ( particle->m_fExpansionRate > 0.0f ) + { + float speed = Max(vecWind.Magnitude(), vecMoveStep.Magnitude()); + + if ( psystem->m_Type == PARTICLE_EXHAUST_FUMES || psystem->m_Type == PARTICLE_ENGINE_STEAM ) + speed *= 2.0f; + + if ( ( psystem->m_Type == PARTICLE_BOAT_SPLASH || psystem->m_Type == PARTICLE_CAR_SPLASH ) + && particle->m_fSize > 1.2f ) + { + size = particle->m_fSize - (1.0f + speed) * particle->m_fExpansionRate; + particle->m_vecVelocity.z -= 0.15f; + } + else + size = particle->m_fSize + (1.0f + speed) * particle->m_fExpansionRate; + } + else + size = particle->m_fSize + particle->m_fExpansionRate; + + if ( psystem->m_Type == PARTICLE_WATERDROP ) + size = (size - Abs(vecMoveStep.x) * 0.000150000007f) + (Abs(vecMoveStep.z) * 0.0500000007f); //TODO: + + 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(SHADOWTYPE_DARK, 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(SHADOWTYPE_DARK, 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, vecPos.z, point, entity, + true, true, false, false, true, false, nil) ) + { + if ( vecPos.z <= point.point.z ) + { + vecPos.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, vecPos.z, point, entity, + true, false, false, false, true, false, nil) ) + { + if ( vecPos.z <= point.point.z ) + { + vecPos.z = point.point.z; + if ( psystem->m_Type == PARTICLE_HELI_ATTACK ) + { + bRemoveParticle = true; + AddParticle(PARTICLE_STEAM, vecPos, CVector(0.0f, 0.0f, 0.05f), nil, 0.2f, 0, 0, 0, 0); + continue; + } + } + } + } + } + } + + if ( particle->m_nFadeToBlackTimer != 0 ) + { + particle->m_nColorIntensity = Clamp(particle->m_nColorIntensity - particle->m_nFadeToBlackTimer, + 0, 255); + } + + if ( particle->m_nFadeAlphaTimer != 0 ) + { + particle->m_nAlpha = Clamp(particle->m_nAlpha - particle->m_nFadeAlphaTimer, + 0, 255); + if ( particle->m_nAlpha == 0 ) + { + bRemoveParticle = true; + continue; + } + } + + 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 ) +#ifdef FIX_BUGS + particle->m_nRotation = CGeneral::LimitAngle(particle->m_nRotation + particle->m_nRotationStep); +#else + particle->m_nRotation += particle->m_nRotationStep; +#endif + + if ( particle->m_fCurrentZRadius != 0.0f ) + { + int32 nSinCosIndex = particle->m_nCurrentZRotation % SIN_COS_TABLE_SIZE; + + float fX = (Cos(nSinCosIndex) - Sin(nSinCosIndex)) * particle->m_fCurrentZRadius; + + float fY = (Sin(nSinCosIndex) + Cos(nSinCosIndex)) * particle->m_fCurrentZRadius; + + vecPos -= particle->m_vecParticleMovementOffset; + + vecPos += CVector(fX, fY, 0.0f); + + particle->m_vecParticleMovementOffset = CVector(fX, fY, 0.0f); + } + + particle->m_vecPosition = vecPos; + } + } +} + +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]; + bool particleBanned = false; + CParticle *particle = psystem->m_pParticles; + + RwRaster **frames = psystem->m_ppRaster; + 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; + } + + 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; + + if ( particle->m_nAlpha == 0 ) + canDraw = false; + + 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 ) + { + float screenZ; +#ifdef FIX_BUGS + bool zIsZero = true; + if ( particle->m_vecPosition.z != 0.0f ) { +#endif + screenZ = (particle->m_vecPosition.z - CDraw::GetNearClipZ()) + * (CSprite::GetFarScreenZ() - CSprite::GetNearScreenZ()) + * CDraw::GetFarClipZ() + / ( (CDraw::GetFarClipZ() - CDraw::GetNearClipZ()) * particle->m_vecPosition.z ) + + CSprite::GetNearScreenZ(); +#ifdef FIX_BUGS + zIsZero = false; + } +#endif + + float stretchTexW; + float stretchTexH; + + if ( i == PARTICLE_RAINDROP || i == PARTICLE_RAINDROP_SMALL || i == PARTICLE_RAINDROP_2D ) + { + stretchTexW = CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.x * (float)particle->m_nCurrentFrame + 63.0f; + stretchTexH = CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.y * (float)particle->m_nCurrentFrame + 63.0f; + } + else + { + stretchTexW = CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.x + 63.0f; + stretchTexH = CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.y + 63.0f; + } + +#ifdef FIX_BUGS + if (!zIsZero) { +#endif + + if ( i == PARTICLE_WATERDROP ) + { + int32 timeLeft = (particle->m_nTimeWhenWillBeDestroyed - CTimer::GetTimeInMilliseconds()) / particle->m_nTimeWhenWillBeDestroyed; + + stretchTexH += (1.0f - (float)timeLeft ) * psystem->m_vecTextureStretch.y; + + RwRect rect; + + rect.x = int32(particle->m_vecPosition.x - SCREEN_STRETCH_X(particle->m_fSize * stretchTexW)); + rect.y = int32(particle->m_vecPosition.y - SCREEN_STRETCH_Y(particle->m_fSize * stretchTexH)); + rect.w = int32(particle->m_vecPosition.x + SCREEN_STRETCH_X(particle->m_fSize * stretchTexW)); + rect.h = int32(particle->m_vecPosition.y + SCREEN_STRETCH_Y(particle->m_fSize * stretchTexH)); + + FxType fxtype; + + if ( particle->m_nCurrentFrame != 0 ) + fxtype = FXTYPE_WATER2; + else + fxtype = FXTYPE_WATER1; + + CMBlur::AddRenderFx(Scene.camera, &rect, screenZ, fxtype); + + canDraw = false; + } + + if ( i == PARTICLE_BLOODDROP ) + { + int32 timeLeft = (particle->m_nTimeWhenWillBeDestroyed - CTimer::GetTimeInMilliseconds()) / particle->m_nTimeWhenWillBeDestroyed; + + stretchTexH += (1.0f + (float)timeLeft) * psystem->m_vecTextureStretch.y; + stretchTexW += (1.0f - (float)timeLeft) * psystem->m_vecTextureStretch.x; + + RwRect rect; + + rect.x = int32(particle->m_vecPosition.x - SCREEN_STRETCH_X(particle->m_fSize * stretchTexW)); + rect.y = int32(particle->m_vecPosition.y - SCREEN_STRETCH_Y(particle->m_fSize * stretchTexH)); + rect.w = int32(particle->m_vecPosition.x + SCREEN_STRETCH_X(particle->m_fSize * stretchTexW)); + rect.h = int32(particle->m_vecPosition.y + SCREEN_STRETCH_Y(particle->m_fSize * stretchTexH)); + + FxType fxtype; + + if ( particle->m_nCurrentFrame ) + fxtype = FXTYPE_BLOOD2; + else + fxtype = FXTYPE_BLOOD1; + + CMBlur::AddRenderFx(Scene.camera, &rect, screenZ, fxtype); + + canDraw = false; + } + + if ( i == PARTICLE_HEATHAZE_IN_DIST ) + { + RwRect rect; + + rect.x = int32(particle->m_vecPosition.x - SCREEN_STRETCH_X(particle->m_fSize * stretchTexW)); + rect.y = int32(particle->m_vecPosition.y - SCREEN_STRETCH_Y(particle->m_fSize * stretchTexH * 0.15f)); + rect.w = int32(particle->m_vecPosition.x + SCREEN_STRETCH_X(particle->m_fSize * stretchTexW)); + rect.h = int32(particle->m_vecPosition.y + SCREEN_STRETCH_Y(particle->m_fSize * stretchTexH * 0.15f)); + + CMBlur::AddRenderFx(Scene.camera, &rect, screenZ, FXTYPE_HEATHAZE); + + canDraw = false; + } + + if ( i == PARTICLE_HEATHAZE ) + { + RwRect rect; + + switch ( TheCamera.GetLookDirection() ) + { + case LOOKING_LEFT: + rect.x = int32(particle->m_vecPosition.x - SCREEN_STRETCH_X(particle->m_fSize * psystem->m_vecTextureStretch.x * 2.0f)); + rect.y = int32(particle->m_vecPosition.y - SCREEN_STRETCH_Y(particle->m_fSize * psystem->m_vecTextureStretch.y)); + rect.w = int32(particle->m_vecPosition.x - SCREEN_STRETCH_X(particle->m_fSize * psystem->m_vecTextureStretch.x)); + rect.h = int32(particle->m_vecPosition.y + SCREEN_STRETCH_Y(particle->m_fSize * psystem->m_vecTextureStretch.y)); + + break; + + case LOOKING_RIGHT: + rect.x = int32(particle->m_vecPosition.x + SCREEN_STRETCH_X(particle->m_fSize * psystem->m_vecTextureStretch.x)); + rect.y = int32(particle->m_vecPosition.y - SCREEN_STRETCH_Y(particle->m_fSize * psystem->m_vecTextureStretch.y)); + rect.w = int32(particle->m_vecPosition.x + SCREEN_STRETCH_X(particle->m_fSize * psystem->m_vecTextureStretch.x * 4.0f)); + rect.h = int32(particle->m_vecPosition.y + SCREEN_STRETCH_Y(particle->m_fSize * psystem->m_vecTextureStretch.y)); + + break; + + default: + rect.x = int32(particle->m_vecPosition.x - SCREEN_STRETCH_X(particle->m_fSize * psystem->m_vecTextureStretch.x)); + rect.y = int32(particle->m_vecPosition.y - SCREEN_STRETCH_Y(particle->m_fSize * psystem->m_vecTextureStretch.y)); + rect.w = int32(particle->m_vecPosition.x + SCREEN_STRETCH_X(particle->m_fSize * psystem->m_vecTextureStretch.x)); + rect.h = int32(particle->m_vecPosition.y + SCREEN_STRETCH_Y(particle->m_fSize * psystem->m_vecTextureStretch.y)); + + break; + } + + CMBlur::AddRenderFx(Scene.camera, &rect, screenZ, FXTYPE_HEATHAZE); + + canDraw = false; + } +#ifdef FIX_BUGS + } + if ( !(zIsZero && (i == PARTICLE_WATERDROP || i == PARTICLE_BLOODDROP || i == PARTICLE_HEATHAZE_IN_DIST || i == PARTICLE_HEATHAZE) ) ) +#endif + if ( canDraw ) + { + if ( particle->m_nRotation != 0 ) + { + CSprite::RenderBufferedOneXLUSprite2D_Rotate_Dimension( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + particle->m_fSize * stretchTexW, + particle->m_fSize * stretchTexH, + particle->m_Color, + particle->m_nColorIntensity, + DEGTORAD((float)particle->m_nRotation), + particle->m_nAlpha); + } + else + { + CSprite::RenderBufferedOneXLUSprite2D( + particle->m_vecPosition.x, + particle->m_vecPosition.y, + particle->m_fSize * stretchTexW, + particle->m_fSize * stretchTexH, + 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) ) + { + + if ( i == PARTICLE_ENGINE_STEAM + || i == PARTICLE_ENGINE_SMOKE + || i == PARTICLE_ENGINE_SMOKE2 + || i == PARTICLE_CARFLAME_SMOKE + || i == PARTICLE_CARCOLLISION_DUST + || i == PARTICLE_EXHAUST_FUMES + || i == PARTICLE_RUBBER_SMOKE + || i == PARTICLE_BURNINGRUBBER_SMOKE ) + { + switch ( TheCamera.GetLookDirection() ) + { + case LOOKING_LEFT: + case LOOKING_RIGHT: + w += CGeneral::GetRandomNumberInRange(1.0f, 7.5f) * psystem->m_vecTextureStretch.x; + h += CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.y; + break; + + default: + w += CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.x; + h += CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.y; + break; + } + } + else if ( i == PARTICLE_WATER_HYDRANT ) + { + int32 timeLeft = (particle->m_nTimeWhenWillBeDestroyed - CTimer::GetTimeInMilliseconds()) / particle->m_nTimeWhenWillBeDestroyed; + + w += (1.0f - (float)timeLeft) * psystem->m_vecTextureStretch.x; + h += (1.0f - (float)timeLeft) * psystem->m_vecTextureStretch.y; + } + else if ( i == PARTICLE_FLYERS ) + { + w += psystem->m_vecTextureStretch.x; + h += psystem->m_vecTextureStretch.y; + + w = Max(w, 12.0f); + h = Max(h, 12.0f); + } + else + { + w += CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.x; + h += CGeneral::GetRandomNumberInRange(0.1f, 1.0f) * psystem->m_vecTextureStretch.y; + } + + if ( i == PARTICLE_WATER_HYDRANT + || (!particleBanned || SCREEN_WIDTH * fParticleScaleLimit >= w) + && SCREEN_HEIGHT * fParticleScaleLimit >= h ) + { + if ( i == PARTICLE_WATER_HYDRANT ) + { + RwRect rect; + + if ( w > 0.0f ) + { + rect.x = int32(coors.x - SCREEN_STRETCH_X(particle->m_fSize * w)); + rect.w = int32(coors.x + SCREEN_STRETCH_X(particle->m_fSize * w)); + } + else + { + rect.w = int32(coors.x - SCREEN_STRETCH_X(particle->m_fSize * w)); + rect.x = int32(coors.x + SCREEN_STRETCH_X(particle->m_fSize * w)); + } + + if ( h > 0.0f ) + { + rect.y = int32(coors.y - SCREEN_STRETCH_Y(particle->m_fSize * h)); + rect.h = int32(coors.y + SCREEN_STRETCH_Y(particle->m_fSize * h)); + } + else + { + rect.h = int32(coors.y - SCREEN_STRETCH_Y(particle->m_fSize * h)); + rect.y = int32(coors.y + SCREEN_STRETCH_Y(particle->m_fSize * h)); + } + + float screenZ = (coors.z - CDraw::GetNearClipZ()) + * (CSprite::GetFarScreenZ() - CSprite::GetNearScreenZ()) * CDraw::GetFarClipZ() + / ( (CDraw::GetFarClipZ() - CDraw::GetNearClipZ()) * coors.z ) + CSprite::GetNearScreenZ(); + + CMBlur::AddRenderFx(Scene.camera, &rect, screenZ, FXTYPE_SPLASH1); + } + else + { + if ( particle->m_nRotation != 0 && i != PARTICLE_BEASTIE ) + { + 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, + DEGTORAD((float)particle->m_nRotation), + particle->m_nAlpha); + } + else if ( psystem->Flags & SCREEN_TRAIL ) + { + float fRotation; + float fTrailLength; + + if ( particle->m_fZGround == 0.0f ) + { + fTrailLength = 0.0f; + fRotation = 0.0f; + } + else + { + CVector2D vecDist + ( + coors.x - particle->m_fZGround, + coors.y - particle->m_fExpansionRate + ); + + 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); + + 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_fZGround = coors.x; // WTF ? + particle->m_fExpansionRate = coors.y; // WTF ? + } + else if ( psystem->Flags & SPEED_TRAIL ) + { + CVector vecPrevPos = particle->m_vecPosition - particle->m_vecVelocity; + float fRotation; + float fTrailLength; + CVector vecScreenPosition; + + if ( CSprite::CalcScreenCoors(vecPrevPos, &vecScreenPosition, &fTrailLength, &fRotation, true) ) + { + CVector2D vecDist + ( + coors.x - vecScreenPosition.x, + coors.y - 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); + } + 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 )*/ + 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 ( pPSystemData->m_Type == PARTICLE_WATERDROP ) + --numWaterDropOnScreen; + + 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); + } +} + +void CParticle::CalWindDir(CVector *vecDirIn, CVector *vecDirOut) +{ + vecDirOut->x = (Cos(128) * vecDirIn->x) + (Sin(128) * vecDirIn->y); + + vecDirOut->x = (Cos(128) * vecDirIn->x) + (Sin(128) * vecDirIn->y) * CWeather::Wind; + vecDirOut->y = (Sin(128) * vecDirIn->x) - (Cos(128) * vecDirIn->y) * CWeather::Wind; +} + +void CParticle::HandleShipsAtHorizonStuff() +{ + tParticleSystemData *psystemdata = &mod_ParticleSystemManager.m_aParticles[PARTICLE_SHIP_SIDE]; + + for ( CParticle *particle = psystemdata->m_pParticles; particle; particle = particle->m_pNext ) + { + if ( CTimer::GetTimeInMilliseconds() > particle->m_nTimeWhenWillBeDestroyed - 32000 + && CTimer::GetTimeInMilliseconds() < particle->m_nTimeWhenWillBeDestroyed - 22000 ) + { + particle->m_nAlpha = Min(particle->m_nAlpha + 1, 96); + } + if ( CTimer::GetTimeInMilliseconds() > particle->m_nTimeWhenWillBeDestroyed - 10000 ) + particle->m_nFadeAlphaTimer = 1; + } +} + +void CParticle::HandleShootableBirdsStuff(CEntity *entity, CVector const&camPos) +{ + float fHeadingRad = entity->GetForward().Heading(); + float fHeading = RADTODEG(fHeadingRad); + float fBirdAngle = ::Cos(DEGTORAD(1.5f)); + + tParticleSystemData *psystem = &mod_ParticleSystemManager.m_aParticles[PARTICLE_BIRD_FRONT]; + CParticle *particle = psystem->m_pParticles; + CParticle *prevParticle = nil; + bool bRemoveParticle; + + for ( ; particle != nil; _Next(particle, prevParticle, psystem, bRemoveParticle) ) + { + bRemoveParticle = false; + + CVector2D vecPos(particle->m_vecPosition.x, particle->m_vecPosition.y); + CVector2D vecCamPos(camPos.x, camPos.y); + + CVector2D vecDist = vecPos - vecCamPos; + vecDist.Normalise(); + + float fHead = DEGTORAD(fHeading); + + CVector2D vecDir(-::Sin(fHead), ::Cos(fHead)); + vecDir.Normalise(); + + float fDot = DotProduct2D(vecDir, vecDist); + + if ( fDot > 0.0f && fDot > fBirdAngle ) + { + if ( (camPos - particle->m_vecPosition).MagnitudeSqr() < 40000.0f ) + { + CStats::SeagullsKilled++; + + bRemoveParticle = true; + + for ( int32 i = 0; i < 8; i++ ) + { + CParticle *pBirdDerbis = AddParticle(PARTICLE_BIRD_DEBRIS, + particle->m_vecPosition, + CVector + ( + CGeneral::GetRandomNumberInRange(-3.0f, 3.0f), + CGeneral::GetRandomNumberInRange(-3.0f, 3.0f), + CGeneral::GetRandomNumberInRange(-3.0f, 3.0f) + ), + nil, + 0.3f, + particle->m_Color, + CGeneral::GetRandomNumberInRange(20, 40), + 0, + CGeneral::GetRandomNumber() & 3, + 200); + if ( pBirdDerbis ) + pBirdDerbis->m_nAlpha = particle->m_nAlpha; + } + } + } + } + +} + +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; + case 5: + CParticleObject::AddObject(POBJECT_WATER_FOUNTAIN_VERT, pos, effect->particle.dir, effect->particle.scale, false); + break; + case 6: + CParticleObject::AddObject(POBJECT_WATER_FOUNTAIN_HORIZ, pos, effect->particle.dir, effect->particle.scale, false); + break; + } + } +} diff --git a/src/miami/renderer/Particle.h b/src/miami/renderer/Particle.h new file mode 100644 index 00000000..5542dc02 --- /dev/null +++ b/src/miami/renderer/Particle.h @@ -0,0 +1,108 @@ +#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; + uint32 m_nTimeWhenWillBeDestroyed; + uint32 m_nTimeWhenColorWillBeChanged; + float m_fZGround; + CVector m_vecParticleMovementOffset; + int16 m_nCurrentZRotation; + uint16 m_nZRotationTimer; + float m_fCurrentZRadius; + uint16 m_nZRadiusTimer; + uint8 m_nColorIntensity; + uint8 m_nAlpha; + float m_fSize; + float m_fExpansionRate; + int16 m_nFadeToBlackTimer; + int16 m_nFadeAlphaTimer; + int16 m_nAnimationSpeedTimer; + int16 m_nRotationStep; + int16 m_nRotation; + uint8 m_nCurrentFrame; + 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 void AddParticlesAlongLine(tParticleType type, CVector const &vecStart, CVector const &vecEnd, CVector const &vecDir, float fPower, CEntity *pEntity = nil, float fSize = 0.0f, int32 nRotationSpeed = 0, int32 nRotation = 0, int32 nCurFrame = 0, int32 nLifeSpan = 0); + static void AddParticlesAlongLine(tParticleType type, CVector const &vecStart, CVector const &vecEnd, CVector const &vecDir, float fPower, CEntity *pEntity, float fSize, RwRGBA const&color, 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 = 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); + static void CalWindDir(CVector *vecDirIn, CVector *vecDirOut); + + static void HandleShipsAtHorizonStuff(); + static void HandleShootableBirdsStuff(CEntity *entity, CVector const&camPos); +}; + +extern bool clearWaterDrop; +extern int32 numWaterDropOnScreen; +extern RwRaster *gpCarSplashRaster[]; +extern RwRaster *gpHeatHazeRaster; +extern RwRaster *gpDotRaster; +extern RwRaster *gpRainDripRaster[]; +extern RwRaster *gpRainDripDarkRaster[]; + +VALIDATE_SIZE(CParticle, 0x58); diff --git a/src/miami/renderer/ParticleMgr.cpp b/src/miami/renderer/ParticleMgr.cpp new file mode 100644 index 00000000..f6919435 --- /dev/null +++ b/src/miami/renderer/ParticleMgr.cpp @@ -0,0 +1,255 @@ +#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_STRETCH_VALUE_X: + entry->m_vecTextureStretch.x = atof(value); + break; + + case CFG_PARAM_STRETCH_VALUE_Y: + entry->m_vecTextureStretch.y = atof(value); + break; + + case CFG_PARAM_WIND_FACTOR: + entry->m_fWindFactor = 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/miami/renderer/ParticleMgr.h b/src/miami/renderer/ParticleMgr.h new file mode 100644 index 00000000..f4afc018 --- /dev/null +++ b/src/miami/renderer/ParticleMgr.h @@ -0,0 +1,138 @@ +#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; + int16 m_nFadeToBlackTime; + uint8 m_nFadeToBlackInitialIntensity; + int16 m_nFadeToBlackAmount; + uint8 m_nFadeAlphaInitialIntensity; + int16 m_nFadeAlphaTime; + int16 m_nFadeAlphaAmount; + uint8 m_nStartAnimationFrame; + uint8 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; + CRGBA m_RenderColouring; + uint8 m_InitialColorVariation; + CRGBA m_FadeDestinationColor; + uint32 m_ColorFadeTime; + + CVector2D m_vecTextureStretch; + float m_fWindFactor; + + RwRaster **m_ppRaster; + CParticle *m_pParticles; +}; + +VALIDATE_SIZE(tParticleSystemData, 0x94); + +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_STRETCH_VALUE_X, + CFG_PARAM_STRETCH_VALUE_Y, + CFG_PARAM_WIND_FACTOR, + + 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, 0x2FFC); + +extern cParticleSystemMgr mod_ParticleSystemManager; diff --git a/src/miami/renderer/ParticleType.h b/src/miami/renderer/ParticleType.h new file mode 100644 index 00000000..9578083d --- /dev/null +++ b/src/miami/renderer/ParticleType.h @@ -0,0 +1,92 @@ +#pragma once + +enum tParticleType +{ + PARTICLE_SPARK = 0, + PARTICLE_SPARK_SMALL, + PARTICLE_WATER_SPARK, + PARTICLE_WHEEL_DIRT, + PARTICLE_SAND, + PARTICLE_WHEEL_WATER, + PARTICLE_BLOOD, + PARTICLE_BLOOD_SMALL, + PARTICLE_BLOOD_SPURT, + PARTICLE_DEBRIS, + PARTICLE_DEBRIS2, + PARTICLE_FLYERS, + PARTICLE_WATER, + PARTICLE_FLAME, + PARTICLE_FIREBALL, + PARTICLE_GUNFLASH, + PARTICLE_GUNFLASH_NOANIM, + PARTICLE_GUNSMOKE, + PARTICLE_GUNSMOKE2, + PARTICLE_CIGARETTE_SMOKE, + PARTICLE_SMOKE, + PARTICLE_SMOKE_SLOWMOTION, + PARTICLE_DRY_ICE, + PARTICLE_TEARGAS, + PARTICLE_GARAGEPAINT_SPRAY, + PARTICLE_SHARD, + PARTICLE_SPLASH, + PARTICLE_CARFLAME, + PARTICLE_STEAM, + PARTICLE_STEAM2, + PARTICLE_STEAM_NY, + PARTICLE_STEAM_NY_SLOWMOTION, + PARTICLE_GROUND_STEAM, + PARTICLE_ENGINE_STEAM, + PARTICLE_RAINDROP, + PARTICLE_RAINDROP_SMALL, + PARTICLE_RAIN_SPLASH, + PARTICLE_RAIN_SPLASH_BIGGROW, + PARTICLE_RAIN_SPLASHUP, + PARTICLE_WATERSPRAY, + PARTICLE_WATERDROP, + PARTICLE_BLOODDROP, + PARTICLE_EXPLOSION_MEDIUM, + PARTICLE_EXPLOSION_LARGE, + PARTICLE_EXPLOSION_MFAST, + PARTICLE_EXPLOSION_LFAST, + PARTICLE_CAR_SPLASH, + PARTICLE_BOAT_SPLASH, + PARTICLE_BOAT_THRUSTJET, + PARTICLE_WATER_HYDRANT, + PARTICLE_WATER_CANNON, + PARTICLE_EXTINGUISH_STEAM, + PARTICLE_PED_SPLASH, + PARTICLE_PEDFOOT_DUST, + PARTICLE_CAR_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_BIRD_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_ROCKET_SMOKE, + PARTICLE_TEST, + PARTICLE_BIRD_FRONT, + PARTICLE_SHIP_SIDE, + PARTICLE_BEASTIE, + PARTICLE_RAINDROP_2D, + PARTICLE_HEATHAZE, + PARTICLE_HEATHAZE_IN_DIST, + + MAX_PARTICLES, + PARTICLE_FIRST = PARTICLE_SPARK, + PARTICLE_LAST = PARTICLE_HEATHAZE_IN_DIST +}; \ No newline at end of file diff --git a/src/miami/renderer/PlayerSkin.cpp b/src/miami/renderer/PlayerSkin.cpp new file mode 100644 index 00000000..8ef7e099 --- /dev/null +++ b/src/miami/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) +{ + // empty on PS2 + m_txdSlot = CTxdStore::AddTxdSlot("skin"); + CTxdStore::Create(m_txdSlot); + CTxdStore::AddRef(m_txdSlot); +} + +void +CPlayerSkin::Shutdown(void) +{ + // empty on PS2 + 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 || texName[0] == '\0') + sprintf(gString, "models\\generic\\player.bmp"); + else + sprintf(gString, "skins\\%s.bmp", texName); + +#if 0 // we don't support .bmp custom skins in DCA3 + 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); + RwTextureSetFilterMode(tex, rwFILTERLINEAR); + RwTexDictionaryAddTexture(CTxdStore::GetSlot(m_txdSlot)->texDict, tex); + + RwImageDestroy(image); + } +#endif + 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 axis = { 0.0f, 1.0f, 0.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, &axis, rotation, rwCOMBINEPRECONCAT); + RwFrameUpdateObjects(frame); + SetAmbientColours(&AmbientColor); + RpClumpRender(gpPlayerClump); +} diff --git a/src/miami/renderer/PlayerSkin.h b/src/miami/renderer/PlayerSkin.h new file mode 100644 index 00000000..e0214ce0 --- /dev/null +++ b/src/miami/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/miami/renderer/PointLights.cpp b/src/miami/renderer/PointLights.cpp new file mode 100644 index 00000000..13872401 --- /dev/null +++ b/src/miami/renderer/PointLights.cpp @@ -0,0 +1,330 @@ +#include "common.h" + +#include "main.h" +#include "CutsceneMgr.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]; +CVector CPointLights::aCachedMapReads[32]; +float CPointLights::aCachedMapReadResults[32]; +int32 CPointLights::NextCachedValue; + +void +CPointLights::Init(void) +{ + for(int i = 0; i < ARRAY_SIZE(aCachedMapReads); i++){ + aCachedMapReads[i] = CVector(0.0f, 0.0f, 0.0f); + aCachedMapReadResults[i] = 0.0f; + } + NextCachedValue = 0; +} + +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; + + if(CCutsceneMgr::IsRunning()) + return; + + PUSH_RENDERGROUP("CPointLights::RenderFogEffect"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, gpPointlightRaster); + + CSprite::InitSpriteBuffer(); + + 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::RenderBufferedOneXLUSprite_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){ + float groundZ; + if(ProcessVerticalLineUsingCache(aLights[i].coors, &groundZ)){ + 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, groundZ + 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::RenderBufferedOneXLUSprite_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); + } + } + } + } + } + } + } + } + + CSprite::FlushSpriteBuffer(); + + POP_RENDERGROUP(); +} + +bool +CPointLights::ProcessVerticalLineUsingCache(CVector coors, float *groundZ) +{ + for(int i = 0; i < ARRAY_SIZE(aCachedMapReads); i++) + if(aCachedMapReads[i] == coors){ + *groundZ = aCachedMapReadResults[i]; + return true; + } + + CColPoint point; + CEntity *entity; + if(CWorld::ProcessVerticalLine(coors, coors.z - 20.0f, point, entity, true, false, false, false, true, false, nil)){ + aCachedMapReads[NextCachedValue] = coors; + aCachedMapReadResults[NextCachedValue] = point.point.z; + NextCachedValue = (NextCachedValue+1) % ARRAY_SIZE(aCachedMapReads); + *groundZ = point.point.z; + return true; + } + return false; +} diff --git a/src/miami/renderer/PointLights.h b/src/miami/renderer/PointLights.h new file mode 100644 index 00000000..827200b9 --- /dev/null +++ b/src/miami/renderer/PointLights.h @@ -0,0 +1,50 @@ +#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]; + static CVector aCachedMapReads[32]; + static float aCachedMapReadResults[32]; + static int32 NextCachedValue; + + 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 Init(void); + 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); + static bool ProcessVerticalLineUsingCache(CVector coors, float *groundZ); +}; diff --git a/src/miami/renderer/RenderBuffer.cpp b/src/miami/renderer/RenderBuffer.cpp new file mode 100644 index 00000000..687cc76b --- /dev/null +++ b/src/miami/renderer/RenderBuffer.cpp @@ -0,0 +1,52 @@ +#include "common.h" + +#include "RenderBuffer.h" + +int32 TempBufferVerticesStored; +int32 TempBufferIndicesStored; + +VertexBufferUnion TempVertexBuffer; +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/miami/renderer/RenderBuffer.h b/src/miami/renderer/RenderBuffer.h new file mode 100644 index 00000000..a0f3e7b9 --- /dev/null +++ b/src/miami/renderer/RenderBuffer.h @@ -0,0 +1,25 @@ +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 512 +#define TEMPBUFFERINDEXSIZE 1024 + +struct VertexBufferUnion +{ + RwIm2DVertex im2d[TEMPBUFFERVERTSIZE]; + RwIm3DVertex im3d[TEMPBUFFERVERTSIZE]; +}; + +extern int32 TempBufferVerticesStored; +extern int32 TempBufferIndicesStored; +extern VertexBufferUnion TempVertexBuffer; +#define TempBufferRenderVertices (TempVertexBuffer.im3d) +extern RwImVertexIndex TempBufferRenderIndexList[TEMPBUFFERINDEXSIZE]; \ No newline at end of file diff --git a/src/miami/renderer/Renderer.cpp b/src/miami/renderer/Renderer.cpp new file mode 100644 index 00000000..96e3a329 --- /dev/null +++ b/src/miami/renderer/Renderer.cpp @@ -0,0 +1,1687 @@ +#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 "Bike.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 "Occlusion.h" +#include "Renderer.h" +#include "custompipes.h" +#include "Frontend.h" + +bool gbShowPedRoadGroups; +bool gbShowCarRoadGroups; +bool gbShowCollisionPolys; +bool gbShowCollisionPolysReflections; +bool gbShowCollisionPolysNoShadows; +bool gbShowCollisionLines; +bool gbBigWhiteDebugLightSwitchedOn; + +bool gbDontRenderBuildings; +bool gbDontRenderBigBuildings; +bool gbDontRenderPeds; +bool gbDontRenderObjects; +bool gbDontRenderVehicles; + +// 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]; +#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(); +} + +void +CRenderer::Shutdown(void) +{ + gSortedVehiclesAndPeds.Shutdown(); +} + +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(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 FINAL + if(gbDontRenderBuildings) + return; +#endif +#ifndef MASTER + if(gbShowCollisionPolys || gbShowCollisionPolysReflections || gbShowCollisionPolysNoShadows) + CCollision::DrawColModel_Coloured(e->GetMatrix(), *CModelInfo::GetColModel(e->GetModelIndex()), e->GetModelIndex()); + else +#endif + { + PUSH_RENDERGROUP(CModelInfo::GetModelInfo(e->GetModelIndex())->GetModelName()); + + e->Render(); + + POP_RENDERGROUP(); + } +} + +void +CRenderer::RenderOneNonRoad(CEntity *e) +{ + CPed *ped; + CVehicle *veh; + int i; + bool resetLights; + +#ifndef MASTER + if(gbShowCollisionPolys || gbShowCollisionPolysReflections || gbShowCollisionPolysNoShadows){ + if(!e->IsVehicle()){ + CCollision::DrawColModel_Coloured(e->GetMatrix(), *CModelInfo::GetColModel(e->GetModelIndex()), e->GetModelIndex()); + return; + } + }else +#endif +#ifndef FINAL + if(e->IsBuilding()){ + if(e->bIsBIGBuilding){ + if(gbDontRenderBigBuildings) + return; + }else{ + if(gbDontRenderBuildings) + return; + } + }else +#endif + if(e->IsPed()){ +#ifndef FINAL + if(gbDontRenderPeds) + return; +#endif + ped = (CPed*)e; + if(ped->m_nPedState == PED_DRIVING) + return; + } +#ifndef FINAL + 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()){ + // unfortunately can't use GetClump here + CVisibilityPlugins::SetupVehicleVariables((RpClump*)e->m_rwObject); + 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(); + SetCullMode(rwCULLMODECULLNONE); + } + e->Render(); + + if(e->IsVehicle()){ + e->bImBeingRendered = true; + CVisibilityPlugins::RenderAlphaAtomics(); + e->bImBeingRendered = false; + SetCullMode(rwCULLMODECULLBACK); + } + + 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() && ((CSimpleModelInfo*)CModelInfo::GetModelInfo(e->GetModelIndex()))->m_wetRoadReflection; } + +void +CRenderer::RenderRoads(void) +{ + int i; + CEntity *e; + + PUSH_RENDERGROUP("CRenderer::RenderRoads"); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + SetCullMode(rwCULLMODECULLBACK); + DeActivateDirectional(); + SetAmbientColours(); + + for(i = 0; i < ms_nNoOfVisibleEntities; i++){ + e = ms_aVisibleEntityPtrs[i]; + if(IsRoad(e)) + RenderOneRoad(e); + } + POP_RENDERGROUP(); +} + +inline bool PutIntoSortedVehicleList(CVehicle *veh) +{ + if(veh->IsBoat()){ + int mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if(mode == CCam::MODE_WHEELCAM || + mode == CCam::MODE_1STPERSON && TheCamera.GetLookDirection() != LOOKING_FORWARD && TheCamera.GetLookDirection() != LOOKING_BEHIND || + CVisibilityPlugins::GetClumpAlpha(veh->GetClump()) != 255) + return false; + return true; + }else + return veh->bTouchingWater; +} + +void +CRenderer::RenderEverythingBarRoads(void) +{ + int i; + CEntity *e; + EntityInfo ei; + + PUSH_RENDERGROUP("CRenderer::RenderEverythingBarRoads"); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + SetCullMode(rwCULLMODECULLBACK); + 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() && PutIntoSortedVehicleList((CVehicle*)e)){ + ei.ent = e; + ei.sort = (ms_vecCameraPosition - e->GetPosition()).MagnitudeSqr(); + gSortedVehiclesAndPeds.InsertSorted(ei); + }else{ + if(!CVisibilityPlugins::InsertEntityIntoSortedList(e, (ms_vecCameraPosition - e->GetPosition()).Magnitude())){ + printf("Ran out of space in alpha entity list"); + RenderOneNonRoad(e); + } + } + }else + RenderOneNonRoad(e); + } + POP_RENDERGROUP(); +} + +void +CRenderer::RenderBoats(void) +{ + CLink *node; + + PUSH_RENDERGROUP("CRenderer::RenderBoats"); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + SetCullMode(rwCULLMODECULLBACK); + +#ifdef NEW_RENDERER + int i; + CEntity *e; + EntityInfo ei; + if(gbNewRenderer){ + gSortedVehiclesAndPeds.Clear(); + // not the real thing + for(i = 0; i < ms_nNoOfVisibleVehicles; i++){ + e = ms_aVisibleVehiclePtrs[i]; + if(e->IsVehicle() && PutIntoSortedVehicleList((CVehicle*)e)){ + ei.ent = e; + ei.sort = (ms_vecCameraPosition - e->GetPosition()).MagnitudeSqr(); + gSortedVehiclesAndPeds.InsertSorted(ei); + } + } + } +#endif + + for(node = gSortedVehiclesAndPeds.tail.prev; + node != &gSortedVehiclesAndPeds.head; + node = node->prev){ + CVehicle *v = (CVehicle*)node->item.ent; + 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 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()); + + 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); + + 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); + SetCullMode(rwCULLMODECULLBACK); + 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(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); + } + 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(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::RenderTransparentWater(void) +{ + int i; + CEntity *e; + + PUSH_RENDERGROUP("CRenderer::RenderTransparentWater"); + 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::RenderTransparentWater(); + + SetStencilState(0); + POP_RENDERGROUP(); +} + +void +CRenderer::ClearForFrame(void) +{ + ms_nNoOfVisibleEntities = 0; + ms_nNoOfVisibleVehicles = 0; + ms_nNoOfVisibleBuildings = 0; + ms_nNoOfInVisibleEntities = 0; + gSortedVehiclesAndPeds.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); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + SetCullMode(rwCULLMODECULLBACK); + DeActivateDirectional(); + SetAmbientColours(); + CVisibilityPlugins::RenderFadingEntities(); + POP_RENDERGROUP(); +} + +void +CRenderer::RenderFadingInUnderwaterEntities(void) +{ + PUSH_RENDERGROUP("CRenderer::RenderFadingInUnderwaterEntities"); + DeActivateDirectional(); + SetAmbientColours(); + CVisibilityPlugins::RenderFadingUnderwaterEntities(); + 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()); + } +} + +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){ + ent->DeleteRwObject(); + 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 && mi->GetModelType() != MITYPE_WEAPON){ + if(FindPlayerVehicle() == ent && + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON && + !(FindPlayerVehicle()->IsBike() && ((CBike*)FindPlayerVehicle())->bWheelieCam)){ + // Player's vehicle in first person mode + CVehicle *veh = (CVehicle*)ent; + int model = veh->GetModelIndex(); + int direction = TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking; + if(direction == LOOKING_FORWARD || + ent->GetModelIndex() == MI_RHINO || + ent->GetModelIndex() == MI_COACH || + TheCamera.m_bInATunnelAndABigVehicle || + direction == LOOKING_BEHIND && veh->pHandling->Flags & HANDLING_UNKNOWN){ + ent->bNoBrightHeadLights = true; + return VIS_OFFSCREEN; + } + + if(direction != LOOKING_BEHIND || + !veh->IsBoat() || model == MI_REEFER || model == MI_TROPIC || model == MI_PREDATOR || model == MI_SKIMMER){ + 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() || ent->IsEntityOccluded()) + 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->bDontStream){ + if(ent->m_rwObject == nil || !ent->bIsVisible) + return VIS_INVISIBLE; + if(!ent->GetIsOnScreen() || ent->IsEntityOccluded()) + 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; + } + } + + // Simple ModelInfo + + if(!IsAreaVisible(ent->m_area)) + return VIS_INVISIBLE; + + dist = (ent->GetPosition() - ms_vecCameraPosition).Magnitude(); + +#ifndef FIX_BUGS + // Whatever this is supposed to do, it breaks fading for objects + // whose draw dist is > LOD_DISTANCE-FADE_DISTANCE, i.e. 280 + // because decreasing dist here makes the object visible above LOD_DISTANCE + // before fading normally once below LOD_DISTANCE. + // aha! this must be a workaround for the fact that we're not taking + // the LOD multiplier into account here anywhere + if(LOD_DISTANCE < dist && dist < mi->GetLargestLodDistance() + FADE_DISTANCE) + dist += mi->GetLargestLodDistance() - LOD_DISTANCE; +#endif + + 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() || ent->IsEntityOccluded()){ + 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){ + if(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() || ent->IsEntityOccluded()){ + mi->m_alpha = 255; + return VIS_OFFSCREEN; + }else{ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + return VIS_OFFSCREEN; // Why this? + } +} + +int32 +CRenderer::SetupBigBuildingVisibility(CEntity *ent) +{ + CSimpleModelInfo *mi = (CSimpleModelInfo*)CModelInfo::GetModelInfo(ent->m_modelIndex); + CTimeModelInfo *ti; + int32 other; + + if(!IsAreaVisible(ent->m_area)) + return VIS_INVISIBLE; + + 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){ + ent->DeleteRwObject(); + 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_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){ + // 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->GetFirstAtomicFromDistance(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?) + mi->IncreaseAlpha(); + if(!ent->IsVisible() || !ent->GetIsOnScreenComplex() || ent->IsEntityOccluded()){ + mi->m_alpha = 255; + return VIS_INVISIBLE; + } + + if(mi->m_alpha != 255){ + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + 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->GetFirstAtomicFromDistance(dist - FADE_DISTANCE); + if(a == nil){ + if(ent->bStreamBIGBuilding && dist-STREAM_DISTANCE < mi->GetLodDistance(0) && request){ + return ent->GetIsOnScreen() ? VIS_STREAMME : VIS_INVISIBLE; + }else{ + 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?) + mi->IncreaseAlpha(); + if(!ent->IsVisible() || !ent->GetIsOnScreenComplex() || ent->IsEntityOccluded()){ + mi->m_alpha = 255; + return VIS_INVISIBLE; + } + CVisibilityPlugins::InsertEntityIntoSortedList(ent, dist); + ent->bDistanceFade = true; + return VIS_INVISIBLE; +} + +void +CRenderer::ConstructRenderList(void) +{ + COcclusion::ProcessBeforeRendering(); +#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]; + + 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(); + + // unused + static CVector prevPos; + static CVector prevFwd; + static bool smallMovement; + smallMovement = (TheCamera.GetPosition() - prevPos).MagnitudeSqr() < SQR(4.0f) && + DotProduct(TheCamera.GetForward(), prevFwd) > 0.98f; + prevPos = TheCamera.GetPosition(); + prevFwd = TheCamera.GetForward(); + + 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{ +#ifdef GTA_TRAIN + 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 +#endif + { + 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 (FrontEndMenuManager.m_PrefsIslandLoading == CMenuManager::ISLAND_LOADING_HIGH) { + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_BEACH)); + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_MAINLAND)); + } else +#endif + { +#ifdef FIX_BUGS + if(CCollision::ms_collisionInMemory != LEVEL_GENERIC) +#endif + ScanBigBuildingList(CWorld::GetBigBuildingList(CGame::currLevel)); + } + ScanBigBuildingList(CWorld::GetBigBuildingList(LEVEL_GENERIC)); + } + } +} + +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(); + ms_vecCameraPosition = TheCamera.GetPosition(); + + 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) +{ + 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 (lightMult != 1.0f) { + SetAmbientAndDirectionalColours(lightMult); + return true; + } + } + return false; +} + +void +CPed::RemoveLighting(bool reset) +{ + if (!bRenderScorched) { + CRenderer::RemoveVehiclePedLights(this, reset); + if (reset) + ReSetAmbientAndDirectionalColours(); + } + SetAmbientColours(); + DeActivateDirectional(); +} + +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()) + ms_aVisibleBuildingPtrs[ms_nNoOfVisibleBuildings++] = ent; + else +#endif + ms_aVisibleEntityPtrs[ms_nNoOfVisibleEntities++] = ent; +} + +void +CRenderer::ScanBigBuildingList(CPtrList &list) +{ + CPtrNode *node; + CEntity *ent; + int vis; + + int f = CTimer::GetFrameCounter() & 3; + for(node = list.first; node; node = node->next){ + ent = (CEntity*)node->item; + if(ent->bOffscreen || (ent->m_randomSeed&3) != f){ + ent->bOffscreen = true; + vis = SetupBigBuildingVisibility(ent); + }else + vis = VIS_VISIBLE; + switch(vis){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + ent->bOffscreen = false; + break; + case VIS_STREAMME: + if(!CStreaming::ms_disableStreaming) + CStreaming::RequestModel(ent->GetModelIndex(), 0); + break; + } + } +} + +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(); + ent->bOffscreen = false; + + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_INVISIBLE: + if(!IsGlass(ent->GetModelIndex())) + break; + // fall through + case VIS_OFFSCREEN: + ent->bOffscreen = true; + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -30.0f && dx < 30.0f && + dy > -30.0f && dy < 30.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; + } + } + } +} + +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(); + ent->bOffscreen = false; + + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_INVISIBLE: + if(!IsGlass(ent->GetModelIndex())) + break; + // fall through + case VIS_OFFSCREEN: + ent->bOffscreen = true; + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -30.0f && dx < 30.0f && + dy > -30.0f && dy < 30.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; + } + } + } +} + +#ifdef GTA_TRAIN +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(); + ent->bOffscreen = false; + switch(SetupEntityVisibility(ent)){ + case VIS_VISIBLE: + InsertEntityIntoList(ent); + break; + case VIS_OFFSCREEN: + ent->bOffscreen = true; + dx = ms_vecCameraPosition.x - ent->GetPosition().x; + dy = ms_vecCameraPosition.y - ent->GetPosition().y; + if(dx > -30.0f && dx < 30.0f && + dy > -30.0f && dy < 30.0f && + ms_nNoOfInVisibleEntities < NUMINVISIBLEENTITIES - 1) + ms_aInVisibleEntityPtrs[ms_nNoOfInVisibleEntities++] = ent; + break; + } + } + } +} +#endif + +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(ShouldModelBeStreamed(ent, ms_vecCameraPosition)) + 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, const CVector &campos) +{ + if(!IsAreaVisible(ent->m_area)) + return false; + CTimeModelInfo *mi = (CTimeModelInfo *)CModelInfo::GetModelInfo(ent->GetModelIndex()); + if(mi->GetModelType() == MITYPE_TIME) + if(!CClock::GetIsTimeInRange(mi->GetTimeOn(), mi->GetTimeOff())) + return false; + float dist = (ent->GetPosition() - campos).Magnitude(); + if(mi->m_noFade) + return dist - STREAM_DISTANCE < mi->GetLargestLodDistance(); + else + return dist - FADE_DISTANCE - STREAM_DISTANCE < mi->GetLargestLodDistance(); +} + +void +CRenderer::RemoveVehiclePedLights(CEntity *ent, bool reset) +{ + if(!ent->bRenderScorched){ + CPointLights::RemoveLightsAffectingObject(); + if(reset) + ReSetAmbientAndDirectionalColours(); + } + SetAmbientColours(); + DeActivateDirectional(); +} diff --git a/src/miami/renderer/Renderer.h b/src/miami/renderer/Renderer.h new file mode 100644 index 00000000..9b202098 --- /dev/null +++ b/src/miami/renderer/Renderer.h @@ -0,0 +1,105 @@ +#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 + +extern bool gbShowPedRoadGroups; +extern bool gbShowCarRoadGroups; +extern bool gbShowCollisionPolys; +extern bool gbShowCollisionLines; +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 RenderFadingInUnderwaterEntities(void); + static void RenderEverythingBarRoads(void); + static void RenderBoats(void); + static void RenderOneRoad(CEntity *); + static void RenderOneNonRoad(CEntity *); + static void RenderFirstPersonVehicle(void); + + static void RenderCollisionLines(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, const CVector &campos); + + 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 RenderTransparentWater(void); // keep-out polys and transparent water +#endif + static void InsertEntityIntoList(CEntity *ent); +}; diff --git a/src/miami/renderer/Rubbish.cpp b/src/miami/renderer/Rubbish.cpp new file mode 100644 index 00000000..147c97b1 --- /dev/null +++ b/src/miami/renderer/Rubbish.cpp @@ -0,0 +1,423 @@ +#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 "Stats.h" +#include "TxdStore.h" +#include "RenderBuffer.h" +#include "Rubbish.h" + +#define RUBBISH_MAX_DIST (23.0f) +#define RUBBISH_FADE_DIST (20.0f) + +RwTexture *gpRubbishTexture[4]; +RwImVertexIndex RubbishIndexList[6]; +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; + + if(RubbishVisibility == 0) + return; + + PUSH_RENDERGROUP("CRubbish::Render"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE); + + for(type = 0; type < 4; type++){ + if(type < 3 || CStats::PamphletMissionPassed) + 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 = 100; + 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 = 100 * (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 vx1, vy1, vx2, vy2; + if(type == 0 || type == 1){ + vx1 = 0.9f*Sin(sheet->m_angle); + vy1 = 0.9f*Cos(sheet->m_angle); + vx2 = 0.3f*Cos(sheet->m_angle); + vy2 = -0.3f*Sin(sheet->m_angle); + }else{ + vx1 = 0.3f*Sin(sheet->m_angle); + vy1 = 0.3f*Cos(sheet->m_angle); + vx2 = 0.3f*Cos(sheet->m_angle); + vy2 = -0.3f*Sin(sheet->m_angle); + } + + int v = TempBufferVerticesStored; + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+0], pos.x + vx1 + vx2, pos.y + vy1 + vy2, pos.z); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+1], pos.x + vx1 - vx2, pos.y + vy1 - vy2, pos.z); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+2], pos.x - vx1 + vx2, pos.y - vy1 + vy2, pos.z); + RwIm3DVertexSetPos(&TempBufferRenderVertices[v+3], pos.x - vx1 - vx2, pos.y - vy1 - vy2, pos.z); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+0], 255, 255, 255, alpha); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+1], 255, 255, 255, alpha); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+2], 255, 255, 255, alpha); + 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; + + 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]); + gpRubbishTexture[0] = nil; + RwTextureDestroy(gpRubbishTexture[1]); + gpRubbishTexture[1] = nil; + RwTextureDestroy(gpRubbishTexture[2]); + gpRubbishTexture[2] = nil; + RwTextureDestroy(gpRubbishTexture[3]); + gpRubbishTexture[3] = nil; +} diff --git a/src/miami/renderer/Rubbish.h b/src/miami/renderer/Rubbish.h new file mode 100644 index 00000000..5a4e479b --- /dev/null +++ b/src/miami/renderer/Rubbish.h @@ -0,0 +1,57 @@ +#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); +}; + +extern RwTexture *gpRubbishTexture[4]; diff --git a/src/miami/renderer/ShadowCamera.cpp b/src/miami/renderer/ShadowCamera.cpp new file mode 100644 index 00000000..f69c234f --- /dev/null +++ b/src/miami/renderer/ShadowCamera.cpp @@ -0,0 +1,549 @@ +#include "common.h" +#include "rwcore.h" +#include "ShadowCamera.h" +#include "RwHelper.h" + +#define TEXELOFFSET 0.5f + +RpAtomic *ShadowRenderCallBack(RpAtomic *atomic, void *data) +{ + RpAtomicCallBackRender savedCB = RpAtomicGetRenderCallBack(atomic); + RpAtomicSetRenderCallBack(atomic, AtomicDefaultRenderCallBack); + RpAtomicRender(atomic); + RpAtomicSetRenderCallBack(atomic, savedCB); + return atomic; +} + +CShadowCamera::CShadowCamera() +{ + m_pCamera = nil; + m_pTexture = nil; +} + +CShadowCamera::~CShadowCamera() +{ + Destroy(); +} + +void +CShadowCamera::Destroy() +{ + if ( m_pCamera ) + { + RwRaster *raster; + RwFrame *frame; + + frame = RwCameraGetFrame(m_pCamera); + + if ( frame ) + { + RwCameraSetFrame(m_pCamera, nil); + RwFrameDestroy(frame); + } + + raster = RwCameraGetZRaster(m_pCamera); + if ( raster ) + { + RwCameraSetZRaster(m_pCamera, nil); + RwRasterDestroy(raster); + } + + raster = RwCameraGetRaster(m_pCamera); + if ( raster ) + { + RwCameraSetRaster(m_pCamera, nil); + RwRasterDestroy(raster); + } + + if ( m_pTexture ) + { + RwTextureSetRaster(m_pTexture, nil); + RwTextureDestroy(m_pTexture); + m_pTexture = nil; + } + + RwCameraDestroy(m_pCamera); + m_pCamera = nil; + } + return; +} + +RwCamera * +CShadowCamera::Create(int32 rasterSize) +{ + int32 size = 1 << rasterSize; + + m_pCamera = RwCameraCreate(); + ASSERT(m_pCamera != nil); + + if ( m_pCamera ) + { + RwCameraSetFrame(m_pCamera, RwFrameCreate()); + + if( RwCameraGetFrame(m_pCamera) ) + { + RwRaster *zRaster = RwRasterCreate(size, size, 0, rwRASTERTYPEZBUFFER); + ASSERT(zRaster != nil); + + if ( zRaster ) + { + RwCameraSetZRaster(m_pCamera, zRaster); + + RwRaster *raster = RwRasterCreate(size, size, 0, rwRASTERTYPECAMERATEXTURE); + ASSERT(raster != nil); + + if ( raster ) + { + RwCameraSetRaster(m_pCamera, raster); + m_pTexture = RwTextureCreate(raster); + ASSERT(m_pTexture != nil); + + if ( m_pTexture ) + { + RwTextureSetAddressing(m_pTexture, rwTEXTUREADDRESSCLAMP); + RwTextureSetFilterMode(m_pTexture, rwFILTERLINEAR); + RwCameraSetProjection(m_pCamera, rwPARALLEL); + return (m_pCamera); + } + } + } + } + } + + Destroy(); + + return (nil); +} + +RwCamera * +CShadowCamera::SetFrustum(float objectRadius) +{ + ASSERT(m_pCamera != nil); + + RwV2d vw; + + RwCameraSetFarClipPlane (m_pCamera, 2.0f * objectRadius); + RwCameraSetNearClipPlane(m_pCamera, 0.001f * objectRadius); + + vw.x = objectRadius; + vw.y = objectRadius; + RwCameraSetViewWindow(m_pCamera, &vw); + + return m_pCamera; +} + +RwCamera * +CShadowCamera::SetLight(RpLight *light) +{ + ASSERT(light != nil); + ASSERT(m_pCamera != nil); + + RwFrame *camFrame = RwCameraGetFrame(m_pCamera); + RwMatrix *camMatrix = RwFrameGetMatrix(camFrame); + RwFrame *lightFrame = RpLightGetFrame(light); + RwMatrix *lightMatrix = RwFrameGetMatrix(lightFrame); + + *RwMatrixGetRight(camMatrix) = *RwMatrixGetRight(lightMatrix); + *RwMatrixGetUp(camMatrix) = *RwMatrixGetUp(lightMatrix); + *RwMatrixGetAt(camMatrix) = *RwMatrixGetAt(lightMatrix); + + RwMatrixUpdate(camMatrix); + RwFrameUpdateObjects(camFrame); + + return m_pCamera; +} + +RwCamera * +CShadowCamera::SetCenter(RwV3d *center) +{ + ASSERT(center != nil); + ASSERT(m_pCamera != nil); + + RwFrame *camFrame = RwCameraGetFrame(m_pCamera); + RwMatrix *camMatrix = RwFrameGetMatrix(camFrame); + + *RwMatrixGetPos(camMatrix) = *center; + + RwV3dIncrementScaled(RwMatrixGetPos(camMatrix), RwMatrixGetAt(camMatrix), -0.5f * RwCameraGetFarClipPlane(m_pCamera)); + + RwMatrixUpdate(camMatrix); + RwFrameUpdateObjects(camFrame); + RwFrameOrthoNormalize(camFrame); + + return m_pCamera; +} + +RwCamera * +CShadowCamera::Update(RpClump *clump) +{ + ASSERT(clump != nil); + ASSERT(m_pCamera != nil); + + RwUInt32 flags; + RpGeometry *geometry; + + RwRGBA bgColor = { 255, 255, 255, 0 }; + + RwCameraClear(m_pCamera, &bgColor, rwCAMERACLEARZ | rwCAMERACLEARIMAGE); + + if ( RwCameraBeginUpdate(m_pCamera) ) + { + geometry = RpAtomicGetGeometry(GetFirstAtomic(clump)); + ASSERT(geometry != nil); + + flags = RpGeometryGetFlags(geometry); + + RpGeometrySetFlags(geometry, flags & ~(rpGEOMETRYPRELIT|rpGEOMETRYLIGHT + |rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2|rpGEOMETRYMODULATEMATERIALCOLOR)); + + RpClumpForAllAtomics(clump, ShadowRenderCallBack, nil); + + RpGeometrySetFlags(geometry, flags); + + InvertRaster(); + RwCameraEndUpdate(m_pCamera); + } + + return m_pCamera; +} + +RwCamera * +CShadowCamera::Update(RpAtomic *atomic) +{ + ASSERT(atomic != nil); + ASSERT(m_pCamera != nil); + + RwUInt32 flags; + RpGeometry *geometry; + + RwRGBA bgColor = { 255, 255, 255, 0 }; + + RwCameraClear(m_pCamera, &bgColor, rwCAMERACLEARZ | rwCAMERACLEARIMAGE); + + if ( RwCameraBeginUpdate(m_pCamera) ) + { + geometry = RpAtomicGetGeometry(atomic); + ASSERT(geometry != nil); + flags = RpGeometryGetFlags(geometry); + + RpGeometrySetFlags(geometry, flags & ~(rpGEOMETRYPRELIT|rpGEOMETRYLIGHT + |rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2|rpGEOMETRYMODULATEMATERIALCOLOR|rpGEOMETRYNORMALS)); + + ShadowRenderCallBack(atomic, nil); + + RpGeometrySetFlags(geometry, flags); + + InvertRaster(); + RwCameraEndUpdate(m_pCamera); + } + + return m_pCamera; +} + +void +CShadowCamera::InvertRaster() +{ + ASSERT(m_pCamera != nil); + + RwIm2DVertex vx[4]; + float crw, crh; + RwRaster *raster; + float recipZ; + + raster = RwCameraGetRaster(m_pCamera); + ASSERT(raster != nil); + + crw = (float)RwRasterGetWidth(raster); + crh = (float)RwRasterGetHeight(raster); + + recipZ = 1.0f / RwCameraGetNearClipPlane(m_pCamera); + + RwIm2DVertexSetScreenX (&vx[0], 0.0f); + RwIm2DVertexSetScreenY (&vx[0], 0.0f); + RwIm2DVertexSetScreenZ (&vx[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetRecipCameraZ(&vx[0], recipZ); + RwIm2DVertexSetIntRGBA (&vx[0], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX (&vx[1], 0.0f); + RwIm2DVertexSetScreenY (&vx[1], crh); + RwIm2DVertexSetScreenZ (&vx[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetRecipCameraZ(&vx[1], recipZ); + RwIm2DVertexSetIntRGBA (&vx[1], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX (&vx[2], crw); + RwIm2DVertexSetScreenY (&vx[2], 0.0f); + RwIm2DVertexSetScreenZ (&vx[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetRecipCameraZ(&vx[2], recipZ); + RwIm2DVertexSetIntRGBA (&vx[2], 255, 255, 255, 255); + + RwIm2DVertexSetScreenX (&vx[3], crw); + RwIm2DVertexSetScreenY (&vx[3], crh); + RwIm2DVertexSetScreenZ (&vx[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetRecipCameraZ(&vx[3], recipZ); + RwIm2DVertexSetIntRGBA (&vx[3], 255, 255, 255, 255); + + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)nil); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDINVDESTCOLOR); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO); + + RwIm2DRenderPrimitive(rwPRIMTYPETRISTRIP, vx, 4); + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); +} + +RwRaster * +CShadowCamera::MakeGradientRaster() +{ + ASSERT(m_pCamera != nil); + + RwIm2DVertex vx[2]; + + if ( !m_pCamera ) + return nil; + + float recipCamZ = 1.0f / RwCameraGetNearClipPlane(m_pCamera); + + RwRaster *raster = RwCameraGetRaster(m_pCamera); + ASSERT(raster != nil); + + float width = (float)RwRasterGetWidth(raster); + float height = (float)RwRasterGetHeight(raster); + + if ( height < 1 ) + return nil; + + if ( RwCameraBeginUpdate(m_pCamera) ) + { + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)rwFILTERNAFILTERMODE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDINVDESTCOLOR); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void *)rwSHADEMODEFLAT); + + float color = 255.0f; + float step = (-191.0f / height); + + for ( int32 i = 0; i < height; i++ ) + { + RwIm2DVertexSetScreenX (&vx[0], 0.0f); + RwIm2DVertexSetScreenY (&vx[0], i); + RwIm2DVertexSetScreenZ (&vx[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetRecipCameraZ(&vx[0], recipCamZ); + RwIm2DVertexSetIntRGBA (&vx[0], (uint32)color, (uint32)color, (uint32)color, (uint32)color); + + RwIm2DVertexSetScreenX (&vx[1], width - 1); + RwIm2DVertexSetScreenY (&vx[1], i); + RwIm2DVertexSetScreenZ (&vx[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetRecipCameraZ(&vx[1], recipCamZ); + RwIm2DVertexSetIntRGBA (&vx[1], (uint32)color, (uint32)color, (uint32)color, (uint32)color); + + RwIm2DRenderLine(vx, 2, 0, 1); + + color += step; + } + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void *)rwSHADEMODEGOURAUD); + + RwCameraEndUpdate(m_pCamera); + } + + return raster; +} + +RwRaster * +CShadowCamera::RasterResample(RwRaster *dstRaster) +{ + ASSERT(dstRaster != nil); + ASSERT(m_pCamera != nil); + + if ( !m_pCamera ) + return nil; + + RwRaster *raster = RwCameraGetRaster(m_pCamera); + ASSERT(raster != nil); + + float size = (float) RwRasterGetWidth(raster); + float uvOffset = TEXELOFFSET / size; + float recipCamZ = 1.0f / RwCameraGetNearClipPlane(m_pCamera); + + if ( RwCameraBeginUpdate(m_pCamera) ) + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)dstRaster); + + Im2DRenderQuad(0.0f, 0.0f, size, size, RwIm2DGetNearScreenZ(), recipCamZ, uvOffset); + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + RwCameraEndUpdate(m_pCamera); + } + + return raster; +} + +RwRaster * +CShadowCamera::RasterBlur(RwRaster *dstRaster, int32 numPasses) +{ + ASSERT(dstRaster != nil); + ASSERT(m_pCamera != nil); + + if ( !m_pCamera ) + return nil; + + RwRaster *raster = RwCameraGetRaster(m_pCamera); + ASSERT(raster != nil); + + float size = (float) RwRasterGetWidth(dstRaster); + float recipCamZ = 1.0f / RwCameraGetNearClipPlane(m_pCamera); + + for (int i = 0; i < numPasses; i++ ) + { + RwCameraSetRaster(m_pCamera, raster); + + if ( RwCameraBeginUpdate(m_pCamera) ) + { + if ( i == 0 ) + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + } + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)dstRaster); + Im2DRenderQuad(0.0f, 0.0f, size, size, RwIm2DGetNearScreenZ(), recipCamZ, 1.0f / size); + RwCameraEndUpdate(m_pCamera); + } + + RwCameraSetRaster(m_pCamera, dstRaster); + + if ( RwCameraBeginUpdate(m_pCamera) ) + { + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)raster); + Im2DRenderQuad(0.0f, 0.0f, size, size, RwIm2DGetNearScreenZ(), recipCamZ, 0); + + if ( i == numPasses - 1 ) + { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + } + + RwCameraEndUpdate(m_pCamera); + } + } + + RwCameraSetRaster(m_pCamera, raster); + + return dstRaster; +} + +RwRaster * +CShadowCamera::RasterGradient(RwRaster *dstRaster) +{ + ASSERT(dstRaster != nil); + ASSERT(m_pCamera != nil); + + RwRaster *raster = RwCameraGetRaster(m_pCamera); + ASSERT(raster != nil); + + float size = (float)RwRasterGetWidth(dstRaster); + float recipCamZ = 1.0f / RwCameraGetNearClipPlane(m_pCamera); + + RwCameraSetRaster(m_pCamera, dstRaster); + + if ( RwCameraBeginUpdate(m_pCamera) ) + { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDZERO); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDSRCCOLOR); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void *)rwFILTERLINEAR); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)raster); + + Im2DRenderQuad(0, 0, size, size, RwIm2DGetNearScreenZ(), recipCamZ, 0); + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + RwCameraEndUpdate(m_pCamera); + } + + RwCameraSetRaster(m_pCamera, raster); + + return dstRaster; +} + +RwRaster *CShadowCamera::DrawOutlineBorder(RwRGBA const& color) +{ + ASSERT(m_pCamera != nil); + + RwIm2DVertex vx[4]; + RwImVertexIndex ix[5]; + + RwRaster *raster = RwCameraGetRaster(m_pCamera); + ASSERT(raster != nil); + + float size = (float)RwRasterGetWidth(raster) - 1.0f; + float recipCamZ = 1.0f / RwCameraGetNearClipPlane(m_pCamera); + + RwIm2DVertexSetScreenX (&vx[0], 0.0f); + RwIm2DVertexSetScreenY (&vx[0], 0.0f); + RwIm2DVertexSetScreenZ (&vx[0], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetIntRGBA (&vx[0], color.red, color.green, color.blue, color.alpha); + RwIm2DVertexSetRecipCameraZ(&vx[0], recipCamZ); + + RwIm2DVertexSetScreenX (&vx[1], size); + RwIm2DVertexSetScreenY (&vx[1], 0.0f); + RwIm2DVertexSetScreenZ (&vx[1], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetIntRGBA (&vx[1], color.red, color.green, color.blue, color.alpha); + RwIm2DVertexSetRecipCameraZ(&vx[1], recipCamZ); + + RwIm2DVertexSetScreenX (&vx[2], size); + RwIm2DVertexSetScreenY (&vx[2], size); + RwIm2DVertexSetScreenZ (&vx[2], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetIntRGBA (&vx[2], color.red, color.green, color.blue, color.alpha); + RwIm2DVertexSetRecipCameraZ(&vx[2], recipCamZ); + + RwIm2DVertexSetScreenX (&vx[3], 0.0f); + RwIm2DVertexSetScreenY (&vx[3], size); + RwIm2DVertexSetScreenZ (&vx[3], RwIm2DGetNearScreenZ()); + RwIm2DVertexSetIntRGBA (&vx[3], color.red, color.green, color.blue, color.alpha); + RwIm2DVertexSetRecipCameraZ(&vx[3], recipCamZ); + + ix[0] = 0; + ix[4] = 0; + ix[1] = 1; + ix[2] = 2; + ix[3] = 3; + + if ( RwCameraBeginUpdate(m_pCamera) ) + { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)nil); + + RwIm2DRenderIndexedPrimitive(rwPRIMTYPEPOLYLINE, vx, 4, ix, 5); + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + + RwCameraEndUpdate(m_pCamera); + } + + return raster; +} \ No newline at end of file diff --git a/src/miami/renderer/ShadowCamera.h b/src/miami/renderer/ShadowCamera.h new file mode 100644 index 00000000..a2149db7 --- /dev/null +++ b/src/miami/renderer/ShadowCamera.h @@ -0,0 +1,54 @@ +#pragma once + + +class CShadowCamera +{ +public: + RwCamera *m_pCamera; + RwTexture *m_pTexture; + + CShadowCamera(); + ~CShadowCamera(); + + RwCamera *Create(int32 rasterSize); + void Destroy(); + + RwCamera *SetFrustum(float objectRadius); + RwCamera *SetLight(RpLight *light); + RwCamera *SetCenter(RwV3d *center); + + RwCamera *Update(RpClump *clump); + RwCamera *Update(RpAtomic *atomic); + + void InvertRaster(); + + RwRaster* GetRwRenderRaster() + { + return RwCameraGetRaster(m_pCamera); + } + + // ShadowRasterRender(RwV2d *) + // ApplyAlphaMapToRaster(void) + + RwRaster *MakeGradientRaster(); + + RwTexture *GetRwRenderTexture() + { + return m_pTexture; + } + + RwRaster* GetRwZRaster() + { + return RwCameraGetZRaster(m_pCamera); + } + + RwRaster *RasterResample(RwRaster *dstRaster); + RwRaster *RasterBlur(RwRaster *dstRaster, int32 numPasses); + RwRaster *RasterGradient(RwRaster *dstRaster); + RwRaster *DrawOutlineBorder(RwRGBA const& color); + + RwCamera *GetRwCamera() + { + return m_pCamera; + } +}; \ No newline at end of file diff --git a/src/miami/renderer/Shadows.cpp b/src/miami/renderer/Shadows.cpp new file mode 100644 index 00000000..5339916a --- /dev/null +++ b/src/miami/renderer/Shadows.cpp @@ -0,0 +1,2515 @@ +#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 "Bike.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 "Script.h" +#include "TimeStep.h" +#include "Shadows.h" +#include "CutsceneObject.h" +#include "CutsceneShadow.h" +#include "Clock.h" +#include "VarConsole.h" + +#ifdef DEBUGMENU +//SETTWEAKPATH("Shadows"); +//TWEAKBOOL(gbPrintShite); +#endif + +RwImVertexIndex ShadowIndexList[24]; + +RwTexture *gpShadowCarTex; +RwTexture *gpShadowPedTex; +RwTexture *gpShadowHeliTex; +RwTexture *gpShadowBikeTex; +RwTexture *gpShadowBaronTex; +RwTexture *gpShadowExplosionTex; +RwTexture *gpShadowHeadLightsTex; +RwTexture *gpOutline1Tex; +RwTexture *gpOutline2Tex; +RwTexture *gpOutline3Tex; +RwTexture *gpBloodPoolTex; +RwTexture *gpReflectionTex; +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]; + +#ifndef MASTER +bool gbCountPolysInShadow; +#endif + +void +CShadows::Init(void) +{ + CTxdStore::PushCurrentTxd(); + + int32 slut = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slut); + + gpShadowCarTex = RwTextureRead("shad_car", nil); + gpShadowPedTex = RwTextureRead("shad_ped", nil); + gpShadowHeliTex = RwTextureRead("shad_heli", nil); + gpShadowBikeTex = RwTextureRead("shad_bike", nil); + gpShadowBaronTex = RwTextureRead("shad_rcbaron", nil); + gpShadowExplosionTex = RwTextureRead("shad_exp", nil); + gpShadowHeadLightsTex = RwTextureRead("headlight", nil); + gpOutline1Tex = RwTextureRead("outline_64", nil); + gpOutline2Tex = RwTextureRead("outline2_64", nil); + gpOutline3Tex = RwTextureRead("outline3_64", nil); + gpBloodPoolTex = RwTextureRead("bloodpool_64", nil); + gpReflectionTex = RwTextureRead("reflection01", nil); + gpWalkDontTex = RwTextureRead("walk_dont", nil); + gpCrackedGlassTex = RwTextureRead("wincrack_32", nil); + gpPostShadowTex = RwTextureRead("lamp_shad_64", nil); + + CTxdStore::PopCurrentTxd(); + + ASSERT(gpShadowCarTex != nil); + ASSERT(gpShadowPedTex != nil); + ASSERT(gpShadowHeliTex != nil); + ASSERT(gpShadowBikeTex != nil); + ASSERT(gpShadowBaronTex != nil); + ASSERT(gpShadowExplosionTex != nil); + ASSERT(gpShadowHeadLightsTex != nil); + ASSERT(gpOutline1Tex != nil); + ASSERT(gpOutline2Tex != nil); + ASSERT(gpOutline3Tex != nil); + ASSERT(gpBloodPoolTex != nil); + ASSERT(gpReflectionTex != nil); + ASSERT(gpWalkDontTex != nil); + ASSERT(gpCrackedGlassTex != nil); + ASSERT(gpPostShadowTex != nil); + + + 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 = nil; + } + + pEmptyBunchList = &aPolyBunches[0]; + + for ( int32 i = 0; i < MAX_POLYBUNCHES; i++ ) + { + if ( i == MAX_POLYBUNCHES - 1 ) + aPolyBunches[i].m_pNext = nil; + else + aPolyBunches[i].m_pNext = &aPolyBunches[i + 1]; + } + + for ( int32 i = 0; i < MAX_PERMAMENTSHADOWS; i++ ) + { + aPermanentShadows[i].m_nType = SHADOWTYPE_NONE; + } + +#ifndef MASTER + VarConsole.Add("Count polys in shadow", &gbCountPolysInShadow, true); +#endif +} + +void +CShadows::Shutdown(void) +{ + ASSERT(gpShadowCarTex != nil); + ASSERT(gpShadowPedTex != nil); + ASSERT(gpShadowHeliTex != nil); + ASSERT(gpShadowBikeTex != nil); + ASSERT(gpShadowBaronTex != nil); + ASSERT(gpShadowExplosionTex != nil); + ASSERT(gpShadowHeadLightsTex != nil); + ASSERT(gpOutline1Tex != nil); + ASSERT(gpOutline2Tex != nil); + ASSERT(gpOutline3Tex != nil); + ASSERT(gpBloodPoolTex != nil); + ASSERT(gpReflectionTex != nil); + ASSERT(gpWalkDontTex != nil); + ASSERT(gpCrackedGlassTex != nil); + ASSERT(gpPostShadowTex != nil); + + RwTextureDestroy(gpShadowCarTex); + RwTextureDestroy(gpShadowPedTex); + RwTextureDestroy(gpShadowHeliTex); + RwTextureDestroy(gpShadowBikeTex); + RwTextureDestroy(gpShadowBaronTex); + RwTextureDestroy(gpShadowExplosionTex); + RwTextureDestroy(gpShadowHeadLightsTex); + RwTextureDestroy(gpOutline1Tex); + RwTextureDestroy(gpOutline2Tex); + RwTextureDestroy(gpOutline3Tex); + RwTextureDestroy(gpBloodPoolTex); + RwTextureDestroy(gpReflectionTex); + 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 != nil); + ASSERT(pPosn != nil); + + + // 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(); + } +} + +bool +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 != nil); + + float fDistToCamSqr = (*pPosn - TheCamera.GetPosition()).MagnitudeSqr2D(); + + if ( SQR(fDrawDistance) > fDistToCamSqr || fDrawDistance == 0.0f ) + { + if ( fDrawDistance != 0.0f ) + { + 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 != nil) ) + 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(); + + return true; + } + 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(); + + return true; + } + 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); + + return aStaticShadows[nSlot].m_pPolyBunch != nil; + } + } + else + { + nSlot = 0; + while ( nSlot < MAX_STATICSHADOWS && aStaticShadows[nSlot].m_pPolyBunch != nil ) + 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); + + return aStaticShadows[nSlot].m_pPolyBunch != nil; + } + } + } + + return true; +} + +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 != nil); + + 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, nil, false); + + break; + } + + case SHADOWTEX_PED: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowPedTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f, nil, false); + + break; + } + + case SHADOWTEX_EXPLOSION: + { + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowExplosionTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f, nil, false); + + break; + } + + case SHADOWTEX_HELI: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpShadowHeliTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f, nil, false); + + break; + } + + case SHADOWTEX_HEADLIGHTS: + { + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, gpShadowHeadLightsTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + 15.0f, false, 1.0f, nil, false); + + break; + } + + case SHADOWTEX_BLOOD: + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, gpBloodPoolTex, pPosn, + fFrontX, fFrontY, fSideX, fSideY, + nIntensity, nRed, 150, 0, + 15.0f, false, 1.0f, nil, false); + + 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, CCutsceneShadow *pShadow, bool bDrawOnBuildings) +{ + ASSERT(pTexture != nil); + ASSERT(pPosn != nil); + + 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_nFlags.bDrawOnBuildings = bDrawOnBuildings; + asShadowsStored[ShadowsStoredToBeRendered].m_fScale = fScale; + asShadowsStored[ShadowsStoredToBeRendered].m_pCutsceneShadow = pShadow; + + ShadowsStoredToBeRendered++; + } +} + + +void +CShadows::StoreShadowForVehicle(CVehicle *pCar, VEH_SHD_TYPE type) +{ + ASSERT(pCar != nil); + + 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; + switch ( type ) + { + case VEH_SHD_TYPE_SEAPLANE: + case VEH_SHD_TYPE_RCPLANE: + fDrawDistance = 144.0f; + break; + + case VEH_SHD_TYPE_HELI: + fDrawDistance = 144.0f; + break; + + default: + fDrawDistance = 18.0f; + break; + } + + if ( fDistToCamSqr < SQR(fDrawDistance) ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + //fDistToCam == 0 -> 4 + //fDistToCam == fDrawDistance -> 0 + float fMult = 1.0f - (fDistToCam - (fDrawDistance*(1.0f-(1.0f/4.0f)))) / (fDrawDistance*(1.0f/4.0f)); + + int32 nColorStrength; + + if ( fDistToCam >= (fDrawDistance*(1.0f-(1.0f/4.0f))) ) + nColorStrength = (int32)(fMult * CTimeCycle::GetShadowStrength()); + else + nColorStrength = CTimeCycle::GetShadowStrength(); + + + float fVehicleHeight = pCar->GetColModel()->boundingBox.GetSize().y; + float fVehicleWidth = pCar->GetColModel()->boundingBox.GetSize().x; + + float size = 1.0f; + + switch ( pCar->GetModelIndex() ) + { + case MI_PIZZABOY: + case MI_PCJ600: + case MI_FAGGIO: + { + fVehicleHeight *= 1.2f; + size = 0.05f; + break; + } + + case MI_ANGEL: + case MI_FREEWAY: + case MI_SANCHEZ: + { + fVehicleHeight *= 1.5f; + size *= 0.03f; + break; + } + + case MI_HUNTER: + case MI_SEASPAR: + case MI_SPARROW: + case MI_MAVERICK: + case MI_VCNMAV: + case MI_POLMAV: + { + fVehicleWidth *= 3.0f; + fVehicleHeight *= 1.4f; + size *= 0.5f; + break; + } + + case MI_RCGOBLIN: + case MI_RCRAIDER: + { + fVehicleHeight *= 1.5f; + fVehicleWidth *= 2.0f; + size *= 0.2f; + break; + } + + case MI_DODO: + { + fVehicleHeight *= 0.9f; + fVehicleWidth *= 0.4f; + break; + } + } + + + CarPos.x -= pCar->GetForward().x * (((fVehicleHeight/2) - pCar->GetColModel()->boundingBox.max.y)*size); + CarPos.y -= pCar->GetForward().y * (((fVehicleHeight/2) - pCar->GetColModel()->boundingBox.max.y)*size); + + RwTexture *tex = gpShadowCarTex; + switch ( type ) + { + case VEH_SHD_TYPE_BIKE: + { + float wheelZ = Abs(((CBike*)pCar)->m_fLeanLRAngle); + float mul = 5.092958f * wheelZ + 1.0f; + if (pCar->GetStatus() == STATUS_PHYSICS) + { + float z = pCar->GetRight().z; + if (z > 0.6f) + mul += 4.0f * z; + } + fVehicleWidth *= mul; + tex = gpShadowBikeTex; + break; + } + + case VEH_SHD_TYPE_HELI: + tex = gpShadowHeliTex; + break; + + case VEH_SHD_TYPE_SEAPLANE: + nColorStrength = CTimeCycle::GetShadowStrength(); + tex = gpShadowBaronTex; + break; + + case VEH_SHD_TYPE_RCPLANE: + tex = gpShadowBaronTex; + fVehicleHeight *= 1.5f; + fVehicleWidth *= 2.2f; + break; + + case VEH_SHD_TYPE_CAR: + tex = gpShadowCarTex; + break; + } + + float frontx = pCar->GetForward().x; + float fronty = pCar->GetForward().y; + float sidex = pCar->GetRight().x; + float sidey = pCar->GetRight().y; + + switch ( type ) + { + case VEH_SHD_TYPE_BIKE: + if ( Abs(pCar->GetRight().z) > 0.6f ) + { + sidex = pCar->GetUp().x; + sidey = pCar->GetUp().y; + } + break; + + case VEH_SHD_TYPE_HELI: + if ( Abs(pCar->GetRight().z) > 0.57f ) + { + sidex = pCar->GetUp().x; + sidey = pCar->GetUp().y; + } + if ( Abs(pCar->GetForward().z) > 0.57f ) + { + frontx = pCar->GetUp().x; + fronty = pCar->GetUp().y; + } + break; + } + + bool bDrawOnBuildings = false; + if ( pCar->GetModelIndex() == MI_RCBANDIT + || pCar->GetModelIndex() == MI_RCBARON + || pCar->GetModelIndex() == MI_RCRAIDER + || pCar->GetModelIndex() == MI_RCGOBLIN + || pCar == FindPlayerVehicle() ) + { + bDrawOnBuildings = true; + } + + if ( pCar->m_vecMoveSpeed.Magnitude() * CTimeStep::ms_fTimeStep > 0.1f || bDrawOnBuildings ) + { + if ( pCar->GetUp().z > 0.0f ) + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, tex, &CarPos, + frontx * (fVehicleHeight / 2), + fronty * (fVehicleHeight / 2), + sidex * (fVehicleWidth / 2), + sidey * (fVehicleWidth / 2), + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.5f, false, 1.0f, nil, bDrawOnBuildings); + } + else + { + StoreShadowToBeRendered(SHADOWTYPE_DARK, tex, &CarPos, + frontx * (fVehicleHeight / 2), + fronty * (fVehicleHeight / 2), + -sidex * (fVehicleWidth / 2), + -sidey * (fVehicleWidth / 2), + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.5f, false, 1.0f, nil, bDrawOnBuildings); + } + } + else + { + if ( pCar->GetUp().z > 0.0f ) + { + StoreStaticShadow((uintptr)pCar + 1, SHADOWTYPE_DARK, tex, &CarPos, + frontx * (fVehicleHeight / 2), + fronty * (fVehicleHeight / 2), + sidex * (fVehicleWidth / 2), + sidey * (fVehicleWidth / 2), + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.5f, 1.0f, 0.0f, false, 0.1f); + } + else + { + StoreStaticShadow((uintptr)pCar + 1, SHADOWTYPE_DARK, tex, &CarPos, + frontx * (fVehicleHeight / 2), + fronty * (fVehicleHeight / 2), + -sidex * (fVehicleWidth / 2), + -sidey * (fVehicleWidth / 2), + nColorStrength, nColorStrength, nColorStrength, nColorStrength, + 4.5f, 1.0f, 0.0f, false, 0.1f); + } + } + } + } +} + +void +CShadows::StoreCarLightShadow(CVehicle *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 != nil); + ASSERT(pPosn != nil); + + 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); + } + + if ( pCar->m_vecMoveSpeed.Magnitude() * CTimeStep::ms_fTimeStep > 0.4f || pCar == FindPlayerVehicle() ) + { + StoreShadowToBeRendered(SHADOWTYPE_ADDITIVE, pTexture, pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + 128, nRed, nGreen, nBlue, + 6.0f, false, 1.0f, + nil, pCar == FindPlayerVehicle()); + } + else + { + StoreStaticShadow((uintptr)pCar + nID, SHADOWTYPE_ADDITIVE, pTexture, pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + 128, nRed, nGreen, nBlue, + 6.0f, 1.0f, 27.0f, + false, 0.4f); + } + } + } +} + + +#ifdef USE_CUTSCENE_SHADOW_FOR_PED +void +StoreShadowForCutscenePedObject(CPed *pObject, float fDisplacementX, float fDisplacementY, + float fFrontX, float fFrontY, float fSideX, float fSideY) +{ + ASSERT(pObject != nil); + + CCutsceneShadow *shadow = pObject->m_pRTShadow; + + if ( shadow == nil ) + return; + + if ( !shadow->IsInitialized() ) + return; + + CVector pos = pObject->GetPosition(); + + float fDistToCamSqr = (pos - TheCamera.GetPosition()).MagnitudeSqr2D(); + + float fDrawDistance = 100.0f; + + if ( fDistToCamSqr < SQR(fDrawDistance*0.5f) ) + { + if ( (CEntity*)pObject == FindPlayerPed() || TheCamera.IsSphereVisible(pos, 2.0f) ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + float fMult = 1.0f - (4.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f/4.0f))); + int32 nColorStrength; + + if ( fDistToCam >= (fDrawDistance*(1.0f/4.0f)) ) + nColorStrength = (int32)(CTimeCycle::GetShadowStrength() * fMult); + else + nColorStrength = CTimeCycle::GetShadowStrength(); + + int32 color = int32(nColorStrength * 0.8f); + + pos.x += fDisplacementX; + pos.y += fDisplacementY; + + RwTexture *texture = shadow->GetShadowRwTexture(); + ASSERT(texture); + RwRGBA bordercolor = {0, 0, 0, 0}; + shadow->DrawBorderAroundTexture(bordercolor); + + pos.x -= fDisplacementX; + pos.y -= fDisplacementY; + + float angleY = 360.0f - RADTODEG((CClock::ms_nGameClockMinutes + +60*CClock::ms_nGameClockHours+CClock::ms_nGameClockSeconds/60)*(HALFPI/360.0f)); + + RwFrame *frame = shadow->SetLightProperties(angleY, -85.0f, true); + ASSERT(frame); + CVector at(RwFrameGetMatrix(frame)->at); + at.Normalise(); + + CShadows::CalcPedShadowValues(at, &fFrontX, &fFrontY, &fSideX, &fSideY, &fDisplacementX, &fDisplacementY); + + pos.x -= 2.5f * fDisplacementX; + pos.y -= 2.5f * fDisplacementY; + + CShadows::StoreShadowToBeRendered(SHADOWTYPE_INVCOLOR, texture, &pos, + fFrontX * 1.5f, fFrontY * 1.5f, + fSideX * 1.5f, fSideY * 1.5f, + color, color, color, color, + 4.0f, false, 1.0f, shadow, false); + } + } +} +#endif + + +void +CShadows::StoreShadowForPed(CPed *pPed, float fDisplacementX, float fDisplacementY, + float fFrontX, float fFrontY, float fSideX, float fSideY) +{ + ASSERT(pPed != nil); + + if ( pPed->bIsVisible ) + { + if ( !(pPed->bInVehicle && pPed->m_nPedState != PED_DRAG_FROM_CAR && pPed->m_nPedState != PED_EXIT_CAR) ) + { + if ( CTimeCycle::GetShadowStrength() != 0 ) + { +#ifdef USE_CUTSCENE_SHADOW_FOR_PED + CCutsceneShadow *pShadow = pPed->m_pRTShadow; + + if (pShadow) + { + if (pShadow->IsInitialized()) + pShadow->UpdateForCutscene(); + ::StoreShadowForCutscenePedObject(pPed, fDisplacementX, fDisplacementY, fFrontX, fFrontY, fSideX, fSideY); + } + + return; +#endif + + 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 != nil); + + 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, nil, pPedObject == FindPlayerPed()); + } + } +} + + +void +CShadows::StoreShadowForCutscenePedObject(CCutsceneObject *pObject, float fDisplacementX, float fDisplacementY, + float fFrontX, float fFrontY, float fSideX, float fSideY) +{ +#ifdef DISABLE_CUTSCENE_SHADOWS + return; +#endif + ASSERT(pObject != nil); + + CCutsceneShadow *shadow = pObject->m_pShadow; + + if ( shadow == nil ) + return; + + if ( !shadow->IsInitialized() ) + return; + + CVector pos = pObject->GetPosition(); + + float fDistToCamSqr = (pos - TheCamera.GetPosition()).MagnitudeSqr2D(); + + float fDrawDistance = 100.0f; + + if ( fDistToCamSqr < SQR(fDrawDistance*0.5f) ) + { + if ( (CEntity*)pObject == FindPlayerPed() || TheCamera.IsSphereVisible(pos, 2.0f) ) + { + float fDistToCam = Sqrt(fDistToCamSqr); + + float fMult = 1.0f - (4.0f / fDrawDistance) * (fDistToCam - (fDrawDistance*(1.0f/4.0f))); + int32 nColorStrength; + + if ( fDistToCam >= (fDrawDistance*(1.0f/4.0f)) ) + nColorStrength = (int32)(CTimeCycle::GetShadowStrength() * fMult); + else + nColorStrength = CTimeCycle::GetShadowStrength(); + + int32 color = int32(nColorStrength * 0.8f); + + pos.x += fDisplacementX; + pos.y += fDisplacementY; + + RwTexture *texture = shadow->GetShadowRwTexture(); + ASSERT(texture); + RwRGBA bordercolor = {0, 0, 0, 0}; + shadow->DrawBorderAroundTexture(bordercolor); + + pos.x -= fDisplacementX; + pos.y -= fDisplacementY; + + float angleY = 360.0f - RADTODEG((CClock::ms_nGameClockMinutes+60* + CClock::ms_nGameClockHours+CClock::ms_nGameClockSeconds/60)*(HALFPI/360.0f)); + + RwFrame *frame = shadow->SetLightProperties(angleY, -85.0f, true); + ASSERT(frame); + CVector at(RwFrameGetMatrix(frame)->at); + at.Normalise(); + + CalcPedShadowValues(at, &fFrontX, &fFrontY, &fSideX, &fSideY, &fDisplacementX, &fDisplacementY); + + pos.x -= 2.5f * fDisplacementX; + pos.y -= 2.5f * fDisplacementY; + + StoreShadowToBeRendered(SHADOWTYPE_INVCOLOR, texture, &pos, + fFrontX * 1.5f, fFrontY * 1.5f, + fSideX * 1.5f, fSideY * 1.5f, + color, color, color, color, + 4.0f, false, 1.0f, shadow, false); + } + } +} + +void +CShadows::StoreShadowForTree(CEntity *pTree) +{ + ASSERT(pTree != nil); +} + + +void +CShadows::StoreShadowForPole(CEntity *pPole, float fOffsetX, float fOffsetY, float fOffsetZ, + float fPoleHeight, float fPoleWidth, uint32 nID) +{ + ASSERT(pPole != nil); + + 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); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void *)rwTEXTUREADDRESSCLAMP); + + + 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 != nil); + + 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 != nil); + + if ( asShadowsStored[j].m_pCutsceneShadow ) + { + CastCutsceneShadowSectorList(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, + nil, + asShadowsStored[j].m_pCutsceneShadow); + + CastCutsceneShadowSectorList(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, + nil, + asShadowsStored[j].m_pCutsceneShadow); + } + else if ( asShadowsStored[j].m_nFlags.bDrawOnBuildings ) + { + CastPlayerShadowSectorList(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, + nil); + + CastPlayerShadowSectorList(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, + nil); + } + else + { + 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, + nil); + + 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, + nil); + } + } + } + + asShadowsStored[j].m_nFlags.bRendered = true; + } + } + + RenderBuffer::RenderStuffInBuffer(); + } + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, (void *)rwTEXTUREADDRESSWRAP); + + 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 != nil + && aStaticShadows[i].m_nType == aStaticShadows[j].m_nType + && aStaticShadows[i].m_pTexture == aStaticShadows[j].m_pTexture ) + { + for ( CPolyBunch *bunch = aStaticShadows[j].m_pPolyBunch; bunch != nil; bunch = bunch->m_pNext ) + { + RwImVertexIndex *pIndexes; + RwIm3DVertex *pVerts; + + RenderBuffer::StartStoring(3 * (bunch->m_nNumVerts - 2), bunch->m_nNumVerts, &pIndexes, &pVerts); + + ASSERT(pIndexes != nil); + ASSERT(pVerts != nil); + + 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 != nil); + + 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 != nil); + + CPtrNode *pNode = PtrList.first; + + CRect Bound; + + while ( pNode != nil ) + { + CEntity *pEntity = (CEntity *)pNode->item; + uint16 nScanCode = pEntity->m_scanCode; + pNode = pNode->next; + + ASSERT( pEntity != nil ); + + if ( nScanCode != CWorld::GetCurrentScanCode() ) + { + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + + if ( pEntity->bUsesCollision && !pEntity->bDontCastShadowsOn) + { + if ( IsAreaVisible(pEntity->m_area) ) + { + 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 ) + { + CastShadowEntityXY(pEntity, + fStartX, fStartY, + fEndX, fEndY, + pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + fZDistance, fScale, ppPolyBunch); + } + } + } + } + } + } +} + + +void +CShadows::CastPlayerShadowSectorList(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 != nil); + + CPtrNode *pNode = PtrList.first; + + CRect Bound; + + while ( pNode != nil ) + { + CEntity *pEntity = (CEntity *)pNode->item; + uint16 nScanCode = pEntity->m_scanCode; + pNode = pNode->next; + + ASSERT( pEntity != nil ); + + if ( nScanCode != CWorld::GetCurrentScanCode() ) + { + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + + if ( pEntity->bUsesCollision ) + { + if ( IsAreaVisible(pEntity->m_area) ) + { + 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 ) + { + CastShadowEntityXY(pEntity, + fStartX, fStartY, + fEndX, fEndY, + pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + fZDistance, fScale, ppPolyBunch); + } + } + } + } + } + } +} + + +void +CShadows::CastCutsceneShadowSectorList(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, CCutsceneShadow *pShadow) +{ + ASSERT(pPosn != nil); + ASSERT(pShadow != nil); + + CPtrNode *pNode = PtrList.first; + + CRect Bound; + + while ( pNode != nil ) + { + CEntity *pEntity = (CEntity *)pNode->item; + uint16 nScanCode = pEntity->m_scanCode; + pNode = pNode->next; + + ASSERT( pEntity != nil ); + + if ( nScanCode != CWorld::GetCurrentScanCode() ) + { + pEntity->m_scanCode = CWorld::GetCurrentScanCode(); + + if ( pEntity->bUsesCollision ) + { + if ( IsAreaVisible(pEntity->m_area) ) + { + 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 ) + { + CastShadowEntityXYZ(pEntity, pPosn, + fFrontX, fFrontY, + fSideX, fSideY, + nIntensity, nRed, nGreen, nBlue, + fZDistance, fScale, ppPolyBunch, pShadow); + } + } + } + } + } + } +} + +void +CShadows::CastShadowEntityXY(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 != nil); + ASSERT(pPosn != nil); + + static CVector List [20]; + static CVector Texture[20]; + static CVector Points [4]; + + CColModel *pCol = pEntity->GetColModel(); + ASSERT(pCol != nil); + +#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 != nil); + + CVector normal; + pColTriPlanes[i].GetNormal(normal); + if ( Abs(normal.z) > 0.1f ) + { + CColTriangle *pColTri = pCol->triangles; + ASSERT(pColTri != nil); + + 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 != nil ) + { + if ( pEmptyBunchList != nil ) + { + CPolyBunch *pBunch = pEmptyBunchList; + ASSERT(pBunch != nil); + 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 != nil); + ASSERT(pVerts != nil); + + + 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(); + } + } + } + } + } +} + + +typedef struct _ProjectionParam +{ + RwV3d at; /* Camera at vector */ + RwMatrix invMatrix; /* Transforms to shadow camera space */ + RwUInt8 shadowValue; /* Shadow opacity value */ + RwBool fade; /* Shadow fades with distance */ + RwUInt32 numIm3DBatch; /* Number of buffer flushes */ + RwMatrix entityMatrix; +} +ProjectionParam; + +RwV3d *ShadowRenderTriangleCB(RwV3d *points, RwV3d *normal, ProjectionParam *param) +{ + RwV3d vIn[3]; + RwV3d vShad[3]; + + RwV3dTransformPoints(&vIn[0], points, 3, ¶m->entityMatrix); + + /* + * Reject backfacing triangles + * This reject the triangles parallel to the light as well + */ + if (RwV3dDotProduct(normal, ¶m->at) > 0.0f) + { + return points; + } + + RwV3dTransformPoints(&vShad[0], &vIn[0], 3, ¶m->invMatrix); + + /* + * Reject triangles behind the camera (z test). Note that any world + * triangles lying in front of the camera but before the object may + * have a shadow applied. To minimize such artefacts, this test could + * be modified to use a specific value rather than 0.0f, perhaps + * to reject triangles behind the center plane of the object. + * + * Reject triangles that lie entirely outside the shadow texture range + * (x,y test). + */ + if (((vShad[0].z < 0.0f) && (vShad[1].z < 0.0f) + && (vShad[2].z < 0.0f)) || ((vShad[0].x < 0.0f) + && (vShad[1].x < 0.0f) + && (vShad[2].x < 0.0f)) + || ((vShad[0].x > 1.0f) && (vShad[1].x > 1.0f) + && (vShad[2].x > 1.0f)) || ((vShad[0].y < 0.0f) + && (vShad[1].y < 0.0f) + && (vShad[2].y < 0.0f)) + || ((vShad[0].y > 1.0f) && (vShad[1].y > 1.0f) + && (vShad[2].y > 1.0f))) + { + return points; + } + + RwIm3DVertex *imv = nil; + RwImVertexIndex *imi = nil; + + RenderBuffer::StartStoring(3, 3, &imi, &imv); + + /* + * Set the immediate mode vertices for this triangle + */ + + RwIm3DVertexSetPos(imv, vIn[0].x, vIn[0].y, vIn[0].z); + RwIm3DVertexSetPos(imv + 1, vIn[1].x, vIn[1].y, vIn[1].z); + RwIm3DVertexSetPos(imv + 2, vIn[2].x, vIn[2].y, vIn[2].z); + + RwIm3DVertexSetU(imv, vShad[0].x); + RwIm3DVertexSetU(imv + 1, vShad[1].x); + RwIm3DVertexSetU(imv + 2, vShad[2].x); + + RwIm3DVertexSetV(imv, vShad[0].y); + RwIm3DVertexSetV(imv + 1, vShad[1].y); + RwIm3DVertexSetV(imv + 2, vShad[2].y); + + /* + * Do we fade out the shadow with distance? + */ + if (param->fade) + { + RwReal fadeVal; + RwUInt8 val; + + fadeVal = 1.0f - vShad[0].z * vShad[0].z; + val = + (fadeVal < + 0.0f) ? 0 : (RwUInt8) (fadeVal * param->shadowValue); + RwIm3DVertexSetRGBA(imv, val, val, val, val); + + fadeVal = 1.0f - vShad[1].z * vShad[1].z; + val = + (fadeVal < + 0.0f) ? 0 : (RwUInt8) (fadeVal * param->shadowValue); + RwIm3DVertexSetRGBA(imv + 1, val, val, val, val); + + fadeVal = 1.0f - vShad[2].z * vShad[2].z; + val = + (fadeVal < + 0.0f) ? 0 : (RwUInt8) (fadeVal * param->shadowValue); + RwIm3DVertexSetRGBA(imv + 2, val, val, val, val); + } + else + { + RwUInt8 val = param->shadowValue; + + RwIm3DVertexSetRGBA(imv, val, val, val, val); + RwIm3DVertexSetRGBA(imv + 1, val, val, val, val); + RwIm3DVertexSetRGBA(imv + 2, val, val, val, val); + } + + /* + * Update buffer position + */ + imi[0] = 0; + imi[1] = 1; + imi[2] = 2; + + RenderBuffer::StopStoring(); + + return points; +} + +void +CShadows::CastShadowEntityXYZ(CEntity *pEntity, CVector *pPosn, + float fFrontX, float fFrontY, float fSideX, float fSideY, + int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, + float fZDistance, float fScale, CPolyBunch **ppPolyBunch, CCutsceneShadow *pShadow) +{ + ASSERT(pEntity != nil); + ASSERT(pPosn != nil); + + if ( pShadow ) + { + ProjectionParam proj; + RwV3d scl; + RwV3d tr; + + CShadowCamera *shadow = pShadow->GetShadowCamera(); + CColModel *collision = pEntity->GetColModel(); + + CCollision::CalculateTrianglePlanes(collision); + + RwMatrix mat; + mat = *RwFrameGetMatrix(RwCameraGetFrame(shadow->GetRwCamera())); + + RwV3d Xaxis = { 1.0f, 0.0f, 0.0f }; + + RwMatrixRotate(&mat, &Xaxis, -45.0f, rwCOMBINEPRECONCAT); + + proj.at = mat.at; + pEntity->GetMatrix().CopyToRwMatrix(&proj.entityMatrix); + + RwMatrixInvert(&proj.invMatrix, &mat); + RwReal radius = RwCameraGetViewWindow(shadow->GetRwCamera())->x; + + scl.x = scl.y = -0.5f / (radius*0.9f); + scl.z = 1.0f / (radius*0.8f); + RwMatrixScale(&proj.invMatrix, &scl, rwCOMBINEPOSTCONCAT); + + tr.x = 0.5f; + tr.y = tr.z = 0.0f; + RwMatrixTranslate(&proj.invMatrix, &tr, rwCOMBINEPOSTCONCAT); + + proj.shadowValue = nIntensity; + proj.fade = 0; + + RwMatrix matrix; + pEntity->GetMatrix().CopyToRwMatrix(&matrix); + RwMatrix invMatrix; + RwMatrixInvert(&invMatrix, &matrix); + + + CVector center(pShadow->GetBaseSphere().center); + center += CVector(-fFrontX * 1.1f, -fFrontY * 1.1f, -0.5f); + + CSphere sphere; + sphere.Set(2.0f, center); + + RwV3d point; + RwV3dTransformPoints(&point, ¢er, 1, &invMatrix); + + CColSphere colSphere; + colSphere.Set(2.0f, CVector(point), 0, 0); + + int i = 0; + while ( i < collision->numTriangles ) + { + CVector p[3]; + + collision->GetTrianglePoint(p[0], collision->triangles[i].a); + collision->GetTrianglePoint(p[1], collision->triangles[i].b); + collision->GetTrianglePoint(p[2], collision->triangles[i].c); + + if ( CCollision::TestSphereTriangle(colSphere, collision->vertices, collision->triangles[i], collision->trianglePlanes[i]) ) + { + CVector n(collision->trianglePlanes[i].GetNormalX(), collision->trianglePlanes[i].GetNormalY(), collision->trianglePlanes[i].GetNormalZ()); + CVector offset = n * 0.028f; + + p[0] += offset; + p[1] += offset; + p[2] += offset; + + if ( !ShadowRenderTriangleCB(p, &n, &proj) ) + break; + } + i++; + } + } +} + +void +CShadows::UpdateStaticShadows(void) +{ + for ( int32 i = 0; i < MAX_STATICSHADOWS; i++ ) + { + if ( aStaticShadows[i].m_pPolyBunch != nil && !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 + { + bool bOk; + 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); + + bOk = 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 + { + bOk = 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); + } + + if ( !bOk ) + aPermanentShadows[i].m_nType = SHADOWTYPE_NONE; + } + } + } +} + +void +CStaticShadow::Free(void) +{ + if ( m_pPolyBunch != nil ) + { + CPolyBunch *pFree = CShadows::pEmptyBunchList; + CShadows::pEmptyBunchList = m_pPolyBunch; + + CPolyBunch *pUsed = m_pPolyBunch; + while (pUsed->m_pNext != nil) + pUsed = pUsed->m_pNext; + + pUsed->m_pNext = pFree; + } + + m_pPolyBunch = nil; + + 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 == nil ) + ; // R* cut it out for playerped + else + { + if ( pCar->GetModelIndex() != MI_RCBANDIT + && pCar->GetVehicleAppearance() != VEHICLE_APPEARANCE_BIKE + && !pCar->IsBike() && !pCar->IsPlane() && !pCar->IsBoat() ) + { + 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, nil, false); + } + 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, nil, false); + } + } + } + } + } + } + } +} + +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 != nil); + + C3dMarkers::PlaceMarkerSet(nID, MARKERTYPE_CYLINDER, *pPosn, Max(fFrontX, -fSideY), + SPHERE_MARKER_R, SPHERE_MARKER_G, SPHERE_MARKER_B, + SPHERE_MARKER_A, SPHERE_MARKER_PULSE_PERIOD, 0.2f, 0); +} diff --git a/src/miami/renderer/Shadows.h b/src/miami/renderer/Shadows.h new file mode 100644 index 00000000..937ff4eb --- /dev/null +++ b/src/miami/renderer/Shadows.h @@ -0,0 +1,210 @@ +#pragma once + +#define MAX_STOREDSHADOWS 48 +#define MAX_POLYBUNCHES 380 +#define MAX_STATICSHADOWS 48 +#define MAX_PERMAMENTSHADOWS 48 + + + +class CEntity; +class CPtrList; +class CAutomobile; +class CVehicle; +class CPed; +class CCutsceneShadow; +class CCutsceneObject; + +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 +}; + +enum VEH_SHD_TYPE +{ + VEH_SHD_TYPE_CAR = 0, + VEH_SHD_TYPE_BIKE, + VEH_SHD_TYPE_HELI, + VEH_SHD_TYPE_SEAPLANE, + VEH_SHD_TYPE_RCPLANE, +}; + + +class CStoredShadow +{ +public: + CVector m_vecPos; + CVector2D m_vecFront; + CVector2D m_vecSide; + float m_fZDistance; + float m_fScale; + RwTexture *m_pTexture; + CCutsceneShadow *m_pCutsceneShadow; + 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; + + + CStoredShadow() + { } +}; + +VALIDATE_SIZE(CStoredShadow, 0x30); + +class CPolyBunch +{ +public: + CVector m_aVerts[7]; + CPolyBunch *m_pNext; + int16 m_nNumVerts; + uint8 m_aU[7]; + uint8 m_aV[7]; + + 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; + RwTexture *m_pTexture; + int16 m_nIntensity; // unsigned ? + uint8 m_nType; + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + bool m_bJustCreated; + bool m_bRendered; + bool m_bTemp; + + + 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; + uint32 m_nTimeCreated; + uint32 m_nLifeTime; + RwTexture *m_pTexture; + int16 m_nIntensity; + uint8 m_nType; // eShadowType + uint8 m_nRed; + uint8 m_nGreen; + uint8 m_nBlue; + + CPermanentShadow() + { } +}; + +VALIDATE_SIZE(CPermanentShadow, 0x38); + +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 bool 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, CCutsceneShadow *pShadow, bool bDrawOnBuildings); + static void StoreShadowForVehicle (CVehicle *pCar, VEH_SHD_TYPE type); + static void StoreCarLightShadow (CVehicle *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 StoreShadowForCutscenePedObject(CCutsceneObject *pObject, 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 CastPlayerShadowSectorList (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 CastCutsceneShadowSectorList (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, CCutsceneShadow *pShadow); + + static void CastShadowEntityXY (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 CastShadowEntityXYZ (CEntity *pEntity, CVector *pPosn, float fFrontX, float fFrontY, float fSideX, float fSideY, int16 nIntensity, uint8 nRed, uint8 nGreen, uint8 nBlue, float fZDistance, float fScale, CPolyBunch **ppPolyBunch, CCutsceneShadow *pShadow); + + 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 *gpShadowBikeTex; +extern RwTexture *gpShadowBaronTex; +extern RwTexture *gpShadowExplosionTex; +extern RwTexture *gpShadowHeadLightsTex; +extern RwTexture *gpOutline1Tex; +extern RwTexture *gpOutline2Tex; +extern RwTexture *gpOutline3Tex; +extern RwTexture *gpBloodPoolTex; +extern RwTexture *gpReflectionTex; +extern RwTexture *gpWalkDontTex; +extern RwTexture *gpCrackedGlassTex; +extern RwTexture *gpPostShadowTex; +extern RwTexture *gpGoalTex; diff --git a/src/miami/renderer/Skidmarks.cpp b/src/miami/renderer/Skidmarks.cpp new file mode 100644 index 00000000..08df330d --- /dev/null +++ b/src/miami/renderer/Skidmarks.cpp @@ -0,0 +1,259 @@ +#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; + +void +CSkidmarks::Init(void) +{ + int i, ix, slot; + CTxdStore::PushCurrentTxd(); + slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + gpSkidTex = RwTextureRead("particleskid", 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; + } +} + +void +CSkidmarks::Shutdown(void) +{ + RwTextureDestroy(gpSkidTex); + gpSkidTex = nil; +} + +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; + + PUSH_RENDERGROUP("CSkidmarks::Render"); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSkidTex)); + + for(i = 0; i < NUMSKIDMARKS; i++){ + if(aSkidmarks[i].m_state == 0 || aSkidmarks[i].m_last < 1) + continue; + + CRGBA color(0, 0, 0, 255); + switch(aSkidmarks[i].m_type){ + case SKIDMARK_NORMAL: color = CRGBA(0, 0, 0, 255); break; + case SKIDMARK_MUDDY: color = CRGBA(90, 62, 9, 255); break; + case SKIDMARK_SANDY: color = CRGBA(108, 108, 96, 255); break; + case SKIDMARK_BLOODY: color = CRGBA(132, 34, 11, 255); break; + } + + 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]; + p1.x += aSkidmarks[i].m_sideX[j]; + p1.y += aSkidmarks[i].m_sideY[j]; + CVector p2 = aSkidmarks[i].m_pos[j]; + p2.x -= aSkidmarks[i].m_sideX[j]; + p2.y -= aSkidmarks[i].m_sideY[j]; + RwIm3DVertexSetRGBA(&SkidmarkVertices[j*2+0], color.red, color.green, color.blue, alpha); + RwIm3DVertexSetPos(&SkidmarkVertices[j*2+0], p1.x, p1.y, p1.z+0.1f); + RwIm3DVertexSetU(&SkidmarkVertices[j*2+0], 0.0f); + RwIm3DVertexSetV(&SkidmarkVertices[j*2+0], j*5.01f); + RwIm3DVertexSetRGBA(&SkidmarkVertices[j*2+1], color.red, color.green, color.blue, alpha); + RwIm3DVertexSetPos(&SkidmarkVertices[j*2+1], p2.x, p2.y, p2.z+0.1f); + RwIm3DVertexSetU(&SkidmarkVertices[j*2+1], 1.0f); + RwIm3DVertexSetV(&SkidmarkVertices[j*2+1], j*5.01f); + } + + 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, const CVector &pos, float fwdX, float fwdY, bool *isMuddy, bool *isBloody) +{ + eSkidmarkType type; + if(*isBloody) + type = SKIDMARK_BLOODY; + else if(*isMuddy) + type = SKIDMARK_MUDDY; + else + type = SKIDMARK_NORMAL; + RegisterOne(id, pos, fwdX, fwdY, type, isBloody); +} + +void +CSkidmarks::RegisterOne(uintptr id, const CVector &pos, float fwdX, float fwdY, eSkidmarkType type, 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_type==SKIDMARK_BLOODY) != *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.Normalise(); + fwd.Normalise(); + float turn = DotProduct2D(fwd, right); + turn = Abs(turn) + 1.0f; + aSkidmarks[i].m_sideX[aSkidmarks[i].m_last] = right.x * turn * 0.125f; + aSkidmarks[i].m_sideY[aSkidmarks[i].m_last] = right.y * turn * 0.125f; + if(aSkidmarks[i].m_last == 1){ + aSkidmarks[i].m_sideX[0] = aSkidmarks[i].m_sideX[1]; + aSkidmarks[i].m_sideY[0] = aSkidmarks[i].m_sideY[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_sideX[0] = 0.0f; + aSkidmarks[i].m_sideY[0] = 0.0f; + aSkidmarks[i].m_wasUpdated = true; + aSkidmarks[i].m_last = 0; + aSkidmarks[i].m_lastUpdate = CTimer::GetTimeInMilliseconds() - 1000; + if(*isBloody) + aSkidmarks[i].m_type = SKIDMARK_BLOODY; + else + aSkidmarks[i].m_type = type; + }else + *isBloody = false; // stop blood marks if no space +} diff --git a/src/miami/renderer/Skidmarks.h b/src/miami/renderer/Skidmarks.h new file mode 100644 index 00000000..28082f08 --- /dev/null +++ b/src/miami/renderer/Skidmarks.h @@ -0,0 +1,41 @@ +#pragma once + +enum { SKIDMARK_LENGTH = 16 }; + +enum eSkidmarkType +{ + SKIDMARK_NORMAL, + SKIDMARK_MUDDY, + SKIDMARK_SANDY, + SKIDMARK_BLOODY +}; + +class CSkidmark +{ +public: + CVector m_pos[SKIDMARK_LENGTH]; + float m_sideX[SKIDMARK_LENGTH]; + float m_sideY[SKIDMARK_LENGTH]; + uintptr m_id; + uint32 m_lastUpdate; + uint32 m_fadeStart; + uint32 m_fadeEnd; + uint32 m_type; + int16 m_last; + uint8 m_state; + bool m_wasUpdated; +}; + +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, const CVector &pos, float fwdX, float fwdY, eSkidmarkType type, bool *isBloody); + static void RegisterOne(uintptr id, const CVector &pos, float fwdX, float fwdY, bool *isMuddy, bool *isBloody); +}; diff --git a/src/miami/renderer/SpecialFX.cpp b/src/miami/renderer/SpecialFX.cpp new file mode 100644 index 00000000..61750f85 --- /dev/null +++ b/src/miami/renderer/SpecialFX.cpp @@ -0,0 +1,1493 @@ +#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" +#include "ColStore.h" +#include "Coronas.h" +#include "Script.h" +#include "DMAudio.h" + +RwIm3DVertex StreakVertices[4]; +RwImVertexIndex StreakIndexList[12]; + +RwIm3DVertex TraceVertices[10]; +static RwImVertexIndex TraceIndexList[48] = {0, 5, 7, 0, 7, 2, 0, 7, 5, 0, 2, 7, 0, 4, 9, 0, + 9, 5, 0, 9, 4, 0, 5, 9, 0, 1, 6, 0, 6, 5, 0, 6, + 1, 0, 5, 6, 0, 3, 8, 0, 8, 5, 0, 8, 3, 0, 5, 8 }; + +bool CSpecialFX::bVideoCam; +bool CSpecialFX::bLiftCam; +bool CSpecialFX::bSnapShotActive; +int32 CSpecialFX::SnapShotFrames; +static RwTexture* gpSmokeTrailTexture; + + +void +CSpecialFX::Init(void) +{ + CBulletTraces::Init(); + + RwIm3DVertexSetU(&TraceVertices[0], 0.0); + RwIm3DVertexSetV(&TraceVertices[0], 0.0); + RwIm3DVertexSetU(&TraceVertices[1], 1.0); + RwIm3DVertexSetV(&TraceVertices[1], 0.0); + RwIm3DVertexSetU(&TraceVertices[2], 1.0); + RwIm3DVertexSetV(&TraceVertices[2], 0.0); + RwIm3DVertexSetU(&TraceVertices[3], 1.0); + RwIm3DVertexSetV(&TraceVertices[3], 0.0); + RwIm3DVertexSetU(&TraceVertices[4], 1.0); + RwIm3DVertexSetV(&TraceVertices[4], 0.0); + RwIm3DVertexSetU(&TraceVertices[5], 0.0); + RwIm3DVertexSetU(&TraceVertices[6], 1.0); + RwIm3DVertexSetU(&TraceVertices[7], 1.0); + RwIm3DVertexSetU(&TraceVertices[8], 1.0); + RwIm3DVertexSetU(&TraceVertices[9], 1.0); + + 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; + + CMotionBlurStreaks::Init(); + CBrightLights::Init(); + CShinyTexts::Init(); + CMoneyMessages::Init(); + C3dMarkers::Init(); + CSpecialFX::bSnapShotActive = false; + CSpecialFX::bVideoCam = false; + CSpecialFX::SnapShotFrames = 0; + CSpecialFX::bLiftCam = false; + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); + if(gpSmokeTrailTexture == nil) + gpSmokeTrailTexture = RwTextureRead("smoketrail", 0); + CTxdStore::PopCurrentTxd(); +} + +void +CSpecialFX::AddWeaponStreak(int type) +{ + static CMatrix matrix; + CVector start; + CVector end; + + if (FindPlayerPed() != nil && FindPlayerPed()->m_pWeaponModel != nil) { + switch (type) { + case WEAPONTYPE_BASEBALLBAT: + matrix = RwFrameGetLTM(RpAtomicGetFrame(FindPlayerPed()->m_pWeaponModel)); + start = matrix * CVector(0.02f, 0.05f, 0.07f); + end = matrix * CVector(0.246f, 0.0325f, 0.796f); + break; + case WEAPONTYPE_GOLFCLUB: + matrix = RwFrameGetLTM(RpAtomicGetFrame(FindPlayerPed()->m_pWeaponModel)); + start = matrix * CVector(0.02f, 0.05f, 0.07f); + end = matrix * CVector(-0.054f, 0.0325f, 0.796f); + break; + case WEAPONTYPE_KATANA: + matrix = RwFrameGetLTM(RpAtomicGetFrame(FindPlayerPed()->m_pWeaponModel)); + start = matrix * CVector(0.02f, 0.05f, 0.07f); + end = matrix * CVector(0.096f, -0.0175f, 1.096f); + break; + default: + return; + } + CMotionBlurStreaks::RegisterStreak((uintptr)FindPlayerPed()->m_pWeaponModel, 100, 100, 100, start, end); + } +} + +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(); +} + +void +CSpecialFX::Shutdown(void) +{ + C3dMarkers::Shutdown(); + if (gpSmokeTrailTexture) { + RwTextureDestroy(gpSmokeTrailTexture); + gpSmokeTrailTexture = nil; + } +} + +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(); +} + +void +CSpecialFX::Render2DFXs(void) +{ + if (CSpecialFX::bVideoCam) { + CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f)); + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetCentreSize(SCREEN_SCALE_X(620.0f)); // unused + CFont::SetCentreOff(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(0, 255, 0, 200)); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + sprintf(gString, "%d", CTimer::GetFrameCounter() & 0x3F); // mb % 63 + AsciiToUnicode(gString, gUString); + CFont::PrintString(SCREEN_WIDTH * 8 / 10, SCREEN_HEIGHT * 8 / 10, gUString); + for (int32 i = 0; i < SCREEN_HEIGHT; i += 4) { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDONE); + CSprite2d::Draw2DPolygon(0.0f, i, SCREEN_WIDTH, i, 0.0f, i+1, SCREEN_WIDTH, i+1, CRGBA(0, 100, 0, 100)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + CSprite2d::Draw2DPolygon(0.0f, i+2, SCREEN_WIDTH, i+2, 0.0f, i+3, SCREEN_WIDTH, i+3, CRGBA(0, 0, 0, 150)); + } + int32 tmp = (CTimer::GetTimeInMilliseconds() & 0x7ff) * (SCREEN_HEIGHT + 70.0f) / 2048 - 70.0f; //mb % 2048 + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + CSprite2d::Draw2DPolygon(0.0, tmp, SCREEN_WIDTH, tmp, 0.0, tmp + 70.0f, SCREEN_WIDTH, tmp + 70.0f , CRGBA(0, 100, 0, 60)); + } + if (CSpecialFX::bLiftCam) { + CFont::SetScale(SCREEN_SCALE_X(1.5f), SCREEN_SCALE_Y(1.5f)); + CFont::SetJustifyOff(); + CFont::SetBackgroundOff(); + CFont::SetCentreSize(SCREEN_SCALE_X(620.0f)); // unused + CFont::SetCentreOff(); + CFont::SetPropOn(); + CFont::SetColor(CRGBA(100, 100, 100, 200)); + CFont::SetFontStyle(FONT_LOCALE(FONT_STANDARD)); + for (int32 i = 0; i < SCREEN_HEIGHT; i += 4) { + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + CSprite2d::Draw2DPolygon(0.0f, i, SCREEN_WIDTH, i, 0.0f, i + 1, SCREEN_WIDTH, i + 1, CRGBA(100, 100, 100, 100)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + CSprite2d::Draw2DPolygon(0.0f, i + 2, SCREEN_WIDTH, i + 2, 0.0f, i + 3, SCREEN_WIDTH, i + 3, CRGBA(0, 0, 0, 150)); + } + int32 tmp = (CTimer::GetTimeInMilliseconds() & 0x7ff) * (SCREEN_HEIGHT + 70.0f) / 2048 - 70.0f; //mb % 2048 + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + CSprite2d::Draw2DPolygon(0.0, tmp, SCREEN_WIDTH, tmp, 0.0, tmp + 70.0f, SCREEN_WIDTH, tmp + 70.0f, CRGBA(100, 100, 100, 60)); + for (int32 i = 0; i < 200; i++) { + int32 posX = CGeneral::GetRandomNumber() % (int32)SCREEN_WIDTH; + int32 posY = CGeneral::GetRandomNumber() % (int32)SCREEN_HEIGHT; + CSprite2d::DrawRect(CRect(posX, posY + 2, posX+20, posY), CRGBA(255, 255, 255, 64)); + } + } + if (CSpecialFX::bSnapShotActive) { + if (++CSpecialFX::SnapShotFrames > 20) { + CSpecialFX::bSnapShotActive = false; + CTimer::SetTimeScale(1.0f); + } else { + CTimer::SetTimeScale(0.0f); //in andro it's 0.00001 + if (CSpecialFX::SnapShotFrames < 10) { + int32 tmp = (255 - 255 * CSpecialFX::SnapShotFrames / 10) * 0.65f; + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDONE); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDONE); + CSprite2d::Draw2DPolygon(0.0f, 0.0f, SCREEN_WIDTH, 0.0f, 0.0f, SCREEN_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT, CRGBA(tmp, tmp, tmp, tmp)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + } + } + } +} + +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* start, CVector* end, float thickness, uint32 lifeTime, uint8 visibility) +{ + int32 enabledCount; + uint32 modifiedLifeTime; + int32 nextSlot; + + enabledCount = 0; + for (int i = 0; i < NUMBULLETTRACES; i++) + if (aTraces[i].m_bInUse) + enabledCount++; + if (enabledCount >= 10) + modifiedLifeTime = lifeTime / 4; + else if (enabledCount >= 5) + modifiedLifeTime = lifeTime / 2; + else + modifiedLifeTime = lifeTime; + + nextSlot = 0; + for (int i = 0; nextSlot < NUMBULLETTRACES && aTraces[i].m_bInUse; i++) + nextSlot++; + if (nextSlot < 16) { + aTraces[nextSlot].m_vecStartPos = *start; + aTraces[nextSlot].m_vecEndPos = *end; + aTraces[nextSlot].m_bInUse = true; + aTraces[nextSlot].m_nCreationTime = CTimer::GetTimeInMilliseconds(); + aTraces[nextSlot].m_fVisibility = visibility; + aTraces[nextSlot].m_fThickness = thickness; + aTraces[nextSlot].m_nLifeTime = modifiedLifeTime; + } + + float startProjFwd = DotProduct(TheCamera.GetForward(), *start - TheCamera.GetPosition()); + float endProjFwd = DotProduct(TheCamera.GetForward(), *end - TheCamera.GetPosition()); + if (startProjFwd * endProjFwd < 0.0f) { //if one of point behind us and second before us + float fStartDistFwd = Abs(startProjFwd) / (Abs(startProjFwd) + Abs(endProjFwd)); + + float startProjUp = DotProduct(TheCamera.GetUp(), *start - TheCamera.GetPosition()); + float endProjUp = DotProduct(TheCamera.GetUp(), *end - TheCamera.GetPosition()); + float distUp = (endProjUp - startProjUp) * fStartDistFwd + startProjUp; + + float startProjRight = DotProduct(TheCamera.GetRight(), *start - TheCamera.GetPosition()); + float endProjRight = DotProduct(TheCamera.GetRight(), *end - TheCamera.GetPosition()); + float distRight = (endProjRight - startProjRight) * fStartDistFwd + startProjRight; + + float dist = Sqrt(SQR(distUp) + SQR(distRight)); + if (dist < 2.0f) { + if(distRight < 0.0f) + DMAudio.PlayFrontEndSound(SOUND_BULLETTRACE_2, 127 * (1.0f - dist * 0.5f)); + else + DMAudio.PlayFrontEndSound(SOUND_BULLETTRACE_1, 127 * (1.0f - dist * 0.5f)); + } + } +} + +void CBulletTraces::AddTrace(CVector* start, CVector* end, int32 weaponType, class CEntity* shooter) +{ + CPhysical* player; + float speed; + int16 camMode; + + if (shooter == (CEntity*)FindPlayerPed() || (FindPlayerVehicle() != nil && FindPlayerVehicle() == (CVehicle*)shooter)) { + camMode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if (camMode == CCam::MODE_M16_1STPERSON + || camMode == CCam::MODE_CAMERA + || camMode == CCam::MODE_SNIPER + || camMode == CCam::MODE_M16_1STPERSON_RUNABOUT + || camMode == CCam::MODE_ROCKETLAUNCHER + || camMode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT + || camMode == CCam::MODE_SNIPER_RUNABOUT + || camMode == CCam::MODE_HELICANNON_1STPERSON) { + + player = FindPlayerVehicle() ? (CPhysical*)FindPlayerVehicle() : (CPhysical*)FindPlayerPed(); + speed = player->m_vecMoveSpeed.Magnitude(); + if (speed < 0.05f) + return; + } + } + + switch (weaponType) { + case WEAPONTYPE_PYTHON: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_SPAS12_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + CBulletTraces::AddTrace(start, end, 0.7f, 1000, 200); + break; + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + case WEAPONTYPE_M60: + case WEAPONTYPE_MINIGUN: + case WEAPONTYPE_HELICANNON: + CBulletTraces::AddTrace(start, end, 1.0f, 2000, 220); + break; + default: + CBulletTraces::AddTrace(start, end, 0.4f, 750, 150); + break; + } +} + +void CBulletTraces::Render(void) +{ + for (int i = 0; i < NUMBULLETTRACES; i++) { + if (!aTraces[i].m_bInUse) + continue; + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpSmokeTrailTexture)); + + float timeAlive = CTimer::GetTimeInMilliseconds() - aTraces[i].m_nCreationTime; + + float traceThickness = aTraces[i].m_fThickness * timeAlive / aTraces[i].m_nLifeTime; + CVector horizontalOffset = aTraces[i].m_vecEndPos - aTraces[i].m_vecStartPos; + horizontalOffset.Normalise(); + horizontalOffset *= traceThickness; + + //then closer trace to die then it more transparent + uint8 nAlphaValue = aTraces[i].m_fVisibility * (aTraces[i].m_nLifeTime - timeAlive) / aTraces[i].m_nLifeTime; + + CVector start = aTraces[i].m_vecStartPos; + CVector end = aTraces[i].m_vecEndPos; + float startProj = DotProduct(start - TheCamera.GetPosition(), TheCamera.GetForward()) - 0.7f; + float endProj = DotProduct(end - TheCamera.GetPosition(), TheCamera.GetForward()) - 0.7f; + if (startProj < 0.0f && endProj < 0.0f) //we dont need render trace behind us + continue; + + if (startProj < 0.0f) { //if strat behind us move it closer + float absStartProj = Abs(startProj); + float absEndProj = Abs(endProj); + start = (absEndProj * start + absStartProj * end) / (absStartProj + absEndProj); + } else if (endProj < 0.0f) { + float absStartProj = Abs(startProj); + float absEndProj = Abs(endProj); + end = (absEndProj * start + absStartProj * end) / (absStartProj + absEndProj); + } + + //we divide trace at three parts + CVector start2 = (7.0f * start + end) / 8; + CVector end2 = (7.0f * end + start) / 8; + + RwIm3DVertexSetV(&TraceVertices[5], 10.0f); + RwIm3DVertexSetV(&TraceVertices[6], 10.0f); + RwIm3DVertexSetV(&TraceVertices[7], 10.0f); + RwIm3DVertexSetV(&TraceVertices[8], 10.0f); + RwIm3DVertexSetV(&TraceVertices[9], 10.0f); + + RwIm3DVertexSetRGBA(&TraceVertices[0], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[1], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[2], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[3], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[4], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[5], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[6], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[7], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[8], 255, 255, 255, nAlphaValue); + RwIm3DVertexSetRGBA(&TraceVertices[9], 255, 255, 255, nAlphaValue); + //two points in center + RwIm3DVertexSetPos(&TraceVertices[0], start2.x, start2.y, start2.z); + RwIm3DVertexSetPos(&TraceVertices[5], end2.x, end2.y, end2.z); + //vertical planes + RwIm3DVertexSetPos(&TraceVertices[1], start2.x, start2.y, start2.z + traceThickness); + RwIm3DVertexSetPos(&TraceVertices[3], start2.x, start2.y, start2.z - traceThickness); + RwIm3DVertexSetPos(&TraceVertices[6], end2.x, end2.y, end2.z + traceThickness); + RwIm3DVertexSetPos(&TraceVertices[8], end2.x, end2.y, end2.z - traceThickness); + //horizontal planes + RwIm3DVertexSetPos(&TraceVertices[2], start2.x + horizontalOffset.y, start2.y - horizontalOffset.x, start2.z); + RwIm3DVertexSetPos(&TraceVertices[7], end2.x + horizontalOffset.y, end2.y - horizontalOffset.x, end2.z); +#ifdef FIX_BUGS //this point calculated wrong for some reason + RwIm3DVertexSetPos(&TraceVertices[4], start2.x - horizontalOffset.y, start2.y + horizontalOffset.x, start2.z); + RwIm3DVertexSetPos(&TraceVertices[9], end2.x - horizontalOffset.y, end2.y + horizontalOffset.x, end2.z); +#else + RwIm3DVertexSetPos(&TraceVertices[4], start2.x - horizontalOffset.y, start2.y - horizontalOffset.y, start2.z); + RwIm3DVertexSetPos(&TraceVertices[9], end2.x - horizontalOffset.y, end2.y - horizontalOffset.y, end2.z); +#endif + + if (RwIm3DTransform(TraceVertices, ARRAY_SIZE(TraceVertices), nil, 1)) { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TraceIndexList, ARRAY_SIZE(TraceIndexList)); + RwIm3DEnd(); + } + + RwIm3DVertexSetV(&TraceVertices[5], 2.0f); + RwIm3DVertexSetV(&TraceVertices[6], 2.0f); + RwIm3DVertexSetV(&TraceVertices[7], 2.0f); + RwIm3DVertexSetV(&TraceVertices[8], 2.0f); + RwIm3DVertexSetV(&TraceVertices[9], 2.0f); + RwIm3DVertexSetRGBA(&TraceVertices[0], 255, 255, 255, 0); + RwIm3DVertexSetRGBA(&TraceVertices[1], 255, 255, 255, 0); + RwIm3DVertexSetRGBA(&TraceVertices[2], 255, 255, 255, 0); + RwIm3DVertexSetRGBA(&TraceVertices[3], 255, 255, 255, 0); + RwIm3DVertexSetRGBA(&TraceVertices[4], 255, 255, 255, 0); + + RwIm3DVertexSetPos(&TraceVertices[0], start.x, start.y, start.z); + RwIm3DVertexSetPos(&TraceVertices[1], start.x, start.y, start.z + traceThickness); + RwIm3DVertexSetPos(&TraceVertices[3], start.x, start.y, start.z - traceThickness); + RwIm3DVertexSetPos(&TraceVertices[2], start.x + horizontalOffset.y, start.y - horizontalOffset.x, start.z); + + RwIm3DVertexSetPos(&TraceVertices[5], start2.x, start2.y, start2.z); + RwIm3DVertexSetPos(&TraceVertices[6], start2.x, start2.y, start2.z + traceThickness); + RwIm3DVertexSetPos(&TraceVertices[8], start2.x, start2.y, start2.z - traceThickness); + RwIm3DVertexSetPos(&TraceVertices[7], start2.x + horizontalOffset.y, start2.y - horizontalOffset.x, start2.z); +#ifdef FIX_BUGS + RwIm3DVertexSetPos(&TraceVertices[4], start.x - horizontalOffset.y, start.y + horizontalOffset.x, start.z); + RwIm3DVertexSetPos(&TraceVertices[9], start2.x - horizontalOffset.y, start2.y + horizontalOffset.x, start2.z); +#else + RwIm3DVertexSetPos(&TraceVertices[4], start.x - horizontalOffset.y, start.y - horizontalOffset.y, start.z); + RwIm3DVertexSetPos(&TraceVertices[9], start2.x - horizontalOffset.y, start2.y - horizontalOffset.y, start2.z); +#endif + + if (RwIm3DTransform(TraceVertices, ARRAY_SIZE(TraceVertices), nil, rwIM3D_VERTEXUV)) { + RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TraceIndexList, ARRAY_SIZE(TraceIndexList)); + RwIm3DEnd(); + } + + RwIm3DVertexSetPos(&TraceVertices[1], end.x, end.y, end.z); + RwIm3DVertexSetPos(&TraceVertices[2], end.x, end.y, end.z + traceThickness); + RwIm3DVertexSetPos(&TraceVertices[4], end.x, end.y, end.z - traceThickness); + RwIm3DVertexSetPos(&TraceVertices[3], end.x + horizontalOffset.y, end.y - horizontalOffset.x, end.z); + + RwIm3DVertexSetPos(&TraceVertices[5], end2.x, end2.y, end2.z); + RwIm3DVertexSetPos(&TraceVertices[6], end2.x, end2.y, end2.z + traceThickness); + RwIm3DVertexSetPos(&TraceVertices[8], end2.x, end2.y, end2.z - traceThickness); + RwIm3DVertexSetPos(&TraceVertices[7], end2.x + horizontalOffset.y, end2.y - horizontalOffset.x, end2.z); +#ifdef FIX_BUGS + RwIm3DVertexSetPos(&TraceVertices[5], end.x - horizontalOffset.y, end.y + horizontalOffset.x, end.z); + RwIm3DVertexSetPos(&TraceVertices[9], end2.x - horizontalOffset.y, end2.y + horizontalOffset.x, end2.z); +#else + RwIm3DVertexSetPos(&TraceVertices[5], end.x - horizontalOffset.y, end.y - horizontalOffset.y, end.z); + RwIm3DVertexSetPos(&TraceVertices[9], end2.x - horizontalOffset.y, end2.y - horizontalOffset.y, end2.z); +#endif + + 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 (CTimer::GetTimeInMilliseconds() - m_nCreationTime >= m_nLifeTime) + m_bInUse = false; +} + +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_bFindZOnNextPlacement = 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_bFindZOnNextPlacement = 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 < 150.0f) { + m_aMarkerArray[i].Render(); + if (m_aMarkerArray[i].m_nType == MARKERTYPE_ARROW) { + CCoronas::RegisterCorona((uintptr)&m_aMarkerArray[i], + SPHERE_MARKER_R, SPHERE_MARKER_G, SPHERE_MARKER_B, 192, + m_aMarkerArray[i].m_Matrix.GetPosition(), 1.2f * m_aMarkerArray[i].m_fSize, 50.0f * TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f, false); + } + } + 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; + CVector2D playerPos = FindPlayerCentreOfWorld(0); + pMarker = nil; + float dist = ((CVector2D)pos - playerPos).Magnitude(); + + 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 != 0) { + 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; + + if (pMarker->m_bFindZOnNextPlacement) { + if ((playerPos - pos).MagnitudeSqr() < sq(100.f) && CColStore::HasCollisionLoaded(CVector2D(pos))) { + float z = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z + 1.0f, nil); + if (z != 0.0f) + pMarker->m_Matrix.GetPosition().z = z - 0.05f * size; + pMarker->m_bFindZOnNextPlacement = false; + } + } + 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) { + if ((playerPos - pos).MagnitudeSqr() < sq(100.f) && CColStore::HasCollisionLoaded(CVector2D(pos))) { + 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_bFindZOnNextPlacement = false; + } else { + pMarker->m_bFindZOnNextPlacement = true; + } + } + 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; +#ifdef FIX_BUGS //just to make sure that color never will be undefined + default: + return; +#endif + } + + 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(); + FONT_LOCALE(FONT_STANDARD); + 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/miami/renderer/SpecialFX.h b/src/miami/renderer/SpecialFX.h new file mode 100644 index 00000000..f163d8ca --- /dev/null +++ b/src/miami/renderer/SpecialFX.h @@ -0,0 +1,249 @@ +#pragma once + +//file done + +class CSpecialFX +{ +public: + static bool bVideoCam; + static bool bLiftCam; + static bool bSnapShotActive; + static int32 SnapShotFrames; + + static void Render(void); + static void Update(void); + static void Init(void); + static void Shutdown(void); + static void AddWeaponStreak(int type); + static void Render2DFXs(); +}; + + +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_vecStartPos; + CVector m_vecEndPos; + bool m_bInUse; + uint32 m_nCreationTime; + uint32 m_nLifeTime; + float m_fThickness; + uint8 m_fVisibility; + + void Update(void); +}; + + +class CBulletTraces +{ +public: + static CBulletTrace aTraces[NUMBULLETTRACES]; + + static void Init(void); + static void Render(void); + static void Update(void); + static void AddTrace(CVector* start, CVector* end, float thickness, uint32 lifeTime, uint8 visibility); + static void AddTrace(CVector* start, CVector* end, int32 weaponType, class CEntity* shooter); +}; + +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; + bool m_bFindZOnNextPlacement; + 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); //not used + 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); //not used + static void StartBoatFoamAnimation(); //not used + static void UpdateBoatFoamAnimation(CMatrix*); //not used +}; diff --git a/src/miami/renderer/Sprite.cpp b/src/miami/renderer/Sprite.cpp new file mode 100644 index 00000000..ecfd3fdc --- /dev/null +++ b/src/miami/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 < 2.3f){ + if(z < 1.3f) + return; + int f = (z - 1.3f)/(2.3f-1.3f) * 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(rotation); + float s = Sin(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(rotation); + float s = Sin(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/miami/renderer/Sprite.h b/src/miami/renderer/Sprite.h new file mode 100644 index 00000000..fae6684e --- /dev/null +++ b/src/miami/renderer/Sprite.h @@ -0,0 +1,31 @@ +#pragma once + +class CSprite +{ + static float m_f2DNearScreenZ; + static float m_f2DFarScreenZ; + static float m_fRecipNearClipPlane; + static int32 m_bFlushSpriteBufferSwitchZTest; +public: + static float GetNearScreenZ(void) { return m_f2DNearScreenZ; } + static float GetFarScreenZ(void) { return m_f2DFarScreenZ; } + + 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/miami/renderer/Sprite2d.cpp b/src/miami/renderer/Sprite2d.cpp new file mode 100644 index 00000000..6f20cdbb --- /dev/null +++ b/src/miami/renderer/Sprite2d.cpp @@ -0,0 +1,470 @@ +#include "common.h" + +#include "main.h" +#include "Draw.h" +#include "Camera.h" +#include "Sprite2d.h" +#include "Font.h" +#include "RenderBuffer.h" + +float CSprite2d::RecipNearClip; +float CSprite2d::NearScreenZ; +float CSprite2d::NearCamZ; +float CSprite2d::RecipFarClip; +float CSprite2d::FarScreenZ; +float CSprite2d::FarCamZ; +int CSprite2d::nextBufferVertex; +int CSprite2d::nextBufferIndex; +RwIm2DVertex CSprite2d::maVertices[8]; + +void +CSprite2d::SetRecipNearClip(void) +{ + // Used but empty in VC, instead they set in InitPerFrame. Isn't that great? +} + +void +CSprite2d::InitPerFrame(void) +{ + nextBufferVertex = 0; + nextBufferIndex = 0; + RecipNearClip = 1.0f / RwCameraGetNearClipPlane(Scene.camera); + NearScreenZ = RwIm2DGetNearScreenZ(); + // not original but you're supposed to set camera z too + // wrapping all this in FIX_BUGS is too ugly + NearCamZ = RwCameraGetNearClipPlane(Scene.camera); + + RecipFarClip = 1.0f / RwCameraGetFarClipPlane(Scene.camera); + FarScreenZ = RwIm2DGetFarScreenZ(); + FarCamZ = RwCameraGetFarClipPlane(Scene.camera); +} +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); + SetRenderState(); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); +} + +void +CSprite2d::Draw(const CRect &rect, const CRGBA &col) +{ + SetVertices(rect, col, col, col, col); + 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); + 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) +{ + float offset = 1.0f/1024.0f; + + // Workaround for PVR Z-Equality not being exact + NearCamZ /= 1.1f; + + // This is what we draw: + // 0---1 + // | / | + // 3---2 + RwIm2DVertexSetScreenX(&maVertices[0], r.left); + RwIm2DVertexSetScreenY(&maVertices[0], r.top); + RwIm2DVertexSetScreenZ(&maVertices[0], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[0], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], 0.0f+offset, RecipNearClip); + RwIm2DVertexSetV(&maVertices[0], 0.0f+offset, RecipNearClip); + + RwIm2DVertexSetScreenX(&maVertices[1], r.right); + RwIm2DVertexSetScreenY(&maVertices[1], r.top); + RwIm2DVertexSetScreenZ(&maVertices[1], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[1], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], 1.0f+offset, RecipNearClip); + RwIm2DVertexSetV(&maVertices[1], 0.0f+offset, RecipNearClip); + + RwIm2DVertexSetScreenX(&maVertices[2], r.right); + RwIm2DVertexSetScreenY(&maVertices[2], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[2], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[2], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], 1.0f+offset, RecipNearClip); + RwIm2DVertexSetV(&maVertices[2], 1.0f+offset, RecipNearClip); + + RwIm2DVertexSetScreenX(&maVertices[3], r.left); + RwIm2DVertexSetScreenY(&maVertices[3], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[3], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[3], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], 0.0f+offset, RecipNearClip); + RwIm2DVertexSetV(&maVertices[3], 1.0f+offset, RecipNearClip); +} + +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) +{ + // Workaround for PVR Z-Equality not being exact + NearCamZ /= 1.1f; + + // This is what we draw: + // 0---1 + // | / | + // 3---2 + RwIm2DVertexSetScreenX(&maVertices[0], r.left); + RwIm2DVertexSetScreenY(&maVertices[0], r.top); + RwIm2DVertexSetScreenZ(&maVertices[0], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[0], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], u0, RecipNearClip); + RwIm2DVertexSetV(&maVertices[0], v0, RecipNearClip); + + RwIm2DVertexSetScreenX(&maVertices[1], r.right); + RwIm2DVertexSetScreenY(&maVertices[1], r.top); + RwIm2DVertexSetScreenZ(&maVertices[1], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[1], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], u1, RecipNearClip); + RwIm2DVertexSetV(&maVertices[1], v1, RecipNearClip); + + RwIm2DVertexSetScreenX(&maVertices[2], r.right); + RwIm2DVertexSetScreenY(&maVertices[2], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[2], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[2], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], u2, RecipNearClip); + RwIm2DVertexSetV(&maVertices[2], v2, RecipNearClip); + + RwIm2DVertexSetScreenX(&maVertices[3], r.left); + RwIm2DVertexSetScreenY(&maVertices[3], r.bottom); + RwIm2DVertexSetScreenZ(&maVertices[3], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[3], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], u3, RecipNearClip); + RwIm2DVertexSetV(&maVertices[3], v3, RecipNearClip); +} + +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, DrawType dt) +{ + // Workaround for PVR Z-Equality not being exact + if (dt == DRAW_NEAR) { + NearCamZ /= 1.1f; + } + + float screenZ = dt == DRAW_FAR ? FarScreenZ : NearScreenZ; + float camZ = dt == DRAW_FAR ? FarCamZ : NearCamZ; + float recipClip = dt == DRAW_FAR ? RecipFarClip : RecipNearClip; + + RwIm2DVertexSetScreenX(&maVertices[0], x3); + RwIm2DVertexSetScreenY(&maVertices[0], y3); + RwIm2DVertexSetScreenZ(&maVertices[0], screenZ); + RwIm2DVertexSetCameraZ(&maVertices[0], camZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[0], recipClip); + RwIm2DVertexSetIntRGBA(&maVertices[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&maVertices[0], 0.0f, recipClip); + RwIm2DVertexSetV(&maVertices[0], 0.0f, recipClip); + + RwIm2DVertexSetScreenX(&maVertices[1], x4); + RwIm2DVertexSetScreenY(&maVertices[1], y4); + RwIm2DVertexSetScreenZ(&maVertices[1], screenZ); + RwIm2DVertexSetCameraZ(&maVertices[1], camZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[1], recipClip); + RwIm2DVertexSetIntRGBA(&maVertices[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&maVertices[1], 1.0f, recipClip); + RwIm2DVertexSetV(&maVertices[1], 0.0f, recipClip); + + RwIm2DVertexSetScreenX(&maVertices[2], x2); + RwIm2DVertexSetScreenY(&maVertices[2], y2); + RwIm2DVertexSetScreenZ(&maVertices[2], screenZ); + RwIm2DVertexSetCameraZ(&maVertices[2], camZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[2], recipClip); + RwIm2DVertexSetIntRGBA(&maVertices[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&maVertices[2], 1.0f, recipClip); + RwIm2DVertexSetV(&maVertices[2], 1.0f, recipClip); + + RwIm2DVertexSetScreenX(&maVertices[3], x1); + RwIm2DVertexSetScreenY(&maVertices[3], y1); + RwIm2DVertexSetScreenZ(&maVertices[3], screenZ); + RwIm2DVertexSetCameraZ(&maVertices[3], camZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[3], recipClip); + RwIm2DVertexSetIntRGBA(&maVertices[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&maVertices[3], 0.0f, recipClip); + RwIm2DVertexSetV(&maVertices[3], 1.0f, recipClip); +} + +void +CSprite2d::SetVertices(int n, float *positions, float *uvs, const CRGBA &col) +{ + // Workaround for PVR Z-Equality not being exact + NearCamZ /= 1.1f; + + int i; + + for(i = 0; i < n; i++){ + RwIm2DVertexSetScreenX(&maVertices[i], positions[i*2 + 0]); + RwIm2DVertexSetScreenY(&maVertices[i], positions[i*2 + 1]); + RwIm2DVertexSetScreenZ(&maVertices[i], NearScreenZ + 0.0001f); + RwIm2DVertexSetCameraZ(&maVertices[i], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[i], RecipNearClip); + RwIm2DVertexSetIntRGBA(&maVertices[i], col.r, col.g, col.b, col.a); + RwIm2DVertexSetU(&maVertices[i], uvs[i*2 + 0], RecipNearClip); + RwIm2DVertexSetV(&maVertices[i], uvs[i*2 + 1], RecipNearClip); + } +} + +void +CSprite2d::SetMaskVertices(int n, float *positions) +{ + int i; + + for(i = 0; i < n; i++){ + RwIm2DVertexSetScreenX(&maVertices[i], positions[i*2 + 0]); + RwIm2DVertexSetScreenY(&maVertices[i], positions[i*2 + 1]); + RwIm2DVertexSetScreenZ(&maVertices[i], NearScreenZ); + RwIm2DVertexSetCameraZ(&maVertices[i], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&maVertices[i], RecipNearClip); +#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) +{ + // Workaround for PVR Z-Equality not being exact + NearCamZ /= 1.1f; + + RwIm2DVertexSetScreenX(&verts[0], r.left); + RwIm2DVertexSetScreenY(&verts[0], r.top); + RwIm2DVertexSetScreenZ(&verts[0], NearScreenZ); + RwIm2DVertexSetCameraZ(&verts[0], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&verts[0], RecipNearClip); + RwIm2DVertexSetIntRGBA(&verts[0], c2.r, c2.g, c2.b, c2.a); + RwIm2DVertexSetU(&verts[0], u0, RecipNearClip); + RwIm2DVertexSetV(&verts[0], v0, RecipNearClip); + + RwIm2DVertexSetScreenX(&verts[1], r.right); + RwIm2DVertexSetScreenY(&verts[1], r.top); + RwIm2DVertexSetScreenZ(&verts[1], NearScreenZ); + RwIm2DVertexSetCameraZ(&verts[1], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&verts[1], RecipNearClip); + RwIm2DVertexSetIntRGBA(&verts[1], c3.r, c3.g, c3.b, c3.a); + RwIm2DVertexSetU(&verts[1], u1, RecipNearClip); + RwIm2DVertexSetV(&verts[1], v1, RecipNearClip); + + RwIm2DVertexSetScreenX(&verts[2], r.right); + RwIm2DVertexSetScreenY(&verts[2], r.bottom); + RwIm2DVertexSetScreenZ(&verts[2], NearScreenZ); + RwIm2DVertexSetCameraZ(&verts[2], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&verts[2], RecipNearClip); + RwIm2DVertexSetIntRGBA(&verts[2], c1.r, c1.g, c1.b, c1.a); + RwIm2DVertexSetU(&verts[2], u2, RecipNearClip); + RwIm2DVertexSetV(&verts[2], v2, RecipNearClip); + + RwIm2DVertexSetScreenX(&verts[3], r.left); + RwIm2DVertexSetScreenY(&verts[3], r.bottom); + RwIm2DVertexSetScreenZ(&verts[3], NearScreenZ); + RwIm2DVertexSetCameraZ(&verts[3], NearCamZ); + RwIm2DVertexSetRecipCameraZ(&verts[3], RecipNearClip); + RwIm2DVertexSetIntRGBA(&verts[3], c0.r, c0.g, c0.b, c0.a); + RwIm2DVertexSetU(&verts[3], u3, RecipNearClip); + RwIm2DVertexSetV(&verts[3], v3, RecipNearClip); +} + +void +CSprite2d::DrawRect(const CRect &r, const CRGBA &col) +{ + SetVertices(r, col, col, col, col); + 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, DrawType dt) +{ + SetVertices(r, c0, c1, c2, c3); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + if (dt == DRAW_FAR) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + } else { + 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); + 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::DrawAnyRect(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, DrawType dt) +{ + SetVertices(x1, y1, x2, y2, x3, y3, x4, y4, c0, c1, c2, c3, dt); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); + if (dt == DRAW_FAR) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + } else { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)FALSE); + } + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)(c0.alpha != 255 || c1.alpha != 255 || c2.alpha != 255 || c3.alpha != 255)); + RwIm2DRenderPrimitive(rwPRIMTYPETRIFAN, CSprite2d::maVertices, 4); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + RwRenderStateSet(rwRENDERSTATESHADEMODE, (void*)rwSHADEMODEGOURAUD); +} + +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); +} + +void +CSprite2d::AddToBuffer(const CRect &r, const CRGBA &c, float u0, float v0, float u1, float v1, float u3, float v3, float u2, float v2) +{ + SetVertices(&TempVertexBuffer.im2d[nextBufferVertex], r, c, c, c, c, u0, v0, u1, v1, u3, v3, u2, v2); + RwImVertexIndex *pIndexList = &TempBufferRenderIndexList[nextBufferIndex]; + pIndexList[0] = nextBufferVertex; + pIndexList[1] = nextBufferVertex + 1; + pIndexList[2] = nextBufferVertex + 2; + pIndexList[3] = nextBufferVertex + 3; + pIndexList[4] = nextBufferVertex; + pIndexList[5] = nextBufferVertex + 2; + nextBufferIndex += 6; + nextBufferVertex += 4; + if (IsVertexBufferFull()) + RenderVertexBuffer(); +} + +bool +CSprite2d::IsVertexBufferFull() +{ + return (nextBufferVertex > TEMPBUFFERVERTSIZE-128-4 || nextBufferIndex > ARRAY_SIZE(TempBufferRenderIndexList)-6); +} + +void +CSprite2d::RenderVertexBuffer() +{ + if (nextBufferVertex > 0) { + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR); + RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempVertexBuffer.im2d, nextBufferVertex, TempBufferRenderIndexList, nextBufferIndex); + nextBufferVertex = 0; + nextBufferIndex = 0; + } +} \ No newline at end of file diff --git a/src/miami/renderer/Sprite2d.h b/src/miami/renderer/Sprite2d.h new file mode 100644 index 00000000..f37bfdf0 --- /dev/null +++ b/src/miami/renderer/Sprite2d.h @@ -0,0 +1,64 @@ +#pragma once + +enum DrawType { + DRAW_FAR, + DRAW_NEAR +}; + +class CSprite2d +{ + static float RecipNearClip; + static float NearScreenZ; + static float NearCamZ; // not original + + static float RecipFarClip; + static float FarScreenZ; + static float FarCamZ; // not original + + static int nextBufferVertex; + static int nextBufferIndex; + static RwIm2DVertex maVertices[8]; +public: + RwTexture *m_pTexture; + + static void SetRecipNearClip(void); + static void InitPerFrame(void); + + 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); + 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, DrawType dt = DRAW_NEAR); + 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, DrawType dt = DRAW_NEAR); + 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 DrawAnyRect(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, DrawType dt = DRAW_NEAR); + + 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; }; + + static bool IsVertexBufferFull(); + static void AddToBuffer(const CRect &a1, const CRGBA &a2, float a3, float a4, float a5, float a6, float a7, float a8, float a9, float a10); + static void RenderVertexBuffer(); +}; diff --git a/src/miami/renderer/TexList.cpp b/src/miami/renderer/TexList.cpp new file mode 100644 index 00000000..1689837f --- /dev/null +++ b/src/miami/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/miami/renderer/TexList.h b/src/miami/renderer/TexList.h new file mode 100644 index 00000000..7e042211 --- /dev/null +++ b/src/miami/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/miami/renderer/Timecycle.cpp b/src/miami/renderer/Timecycle.cpp new file mode 100644 index 00000000..95d9fe3c --- /dev/null +++ b/src/miami/renderer/Timecycle.cpp @@ -0,0 +1,526 @@ +#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" + +uint8 CTimeCycle::m_nAmbientRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientRed_Obj[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientGreen_Obj[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientBlue_Obj[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientRed_Bl[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientGreen_Bl[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientBlue_Bl[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientRed_Obj_Bl[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientGreen_Obj_Bl[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nAmbientBlue_Obj_Bl[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nDirectionalRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nDirectionalGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nDirectionalBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSkyTopRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSkyTopGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSkyTopBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSkyBottomRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSkyBottomGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSkyBottomBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSunCoreRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSunCoreGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSunCoreBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSunCoronaRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSunCoronaGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nSunCoronaBlue[NUMHOURS][NUMWEATHERS]; +int8 CTimeCycle::m_fSunSize[NUMHOURS][NUMWEATHERS]; +int8 CTimeCycle::m_fSpriteSize[NUMHOURS][NUMWEATHERS]; +int8 CTimeCycle::m_fSpriteBrightness[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nShadowStrength[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nLightShadowStrength[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nPoleShadowStrength[NUMHOURS][NUMWEATHERS]; +int16 CTimeCycle::m_fFogStart[NUMHOURS][NUMWEATHERS]; +int16 CTimeCycle::m_fFarClip[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_fLightsOnGroundBrightness[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nLowCloudsRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nLowCloudsGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nLowCloudsBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nFluffyCloudsTopRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nFluffyCloudsTopGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nFluffyCloudsTopBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nFluffyCloudsBottomRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nFluffyCloudsBottomGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_nFluffyCloudsBottomBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_fBlurRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_fBlurGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_fBlurBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_fWaterRed[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_fWaterGreen[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_fWaterBlue[NUMHOURS][NUMWEATHERS]; +uint8 CTimeCycle::m_fWaterAlpha[NUMHOURS][NUMWEATHERS]; + + +float CTimeCycle::m_fCurrentAmbientRed; +float CTimeCycle::m_fCurrentAmbientGreen; +float CTimeCycle::m_fCurrentAmbientBlue; +float CTimeCycle::m_fCurrentAmbientRed_Obj; +float CTimeCycle::m_fCurrentAmbientGreen_Obj; +float CTimeCycle::m_fCurrentAmbientBlue_Obj; +float CTimeCycle::m_fCurrentAmbientRed_Bl; +float CTimeCycle::m_fCurrentAmbientGreen_Bl; +float CTimeCycle::m_fCurrentAmbientBlue_Bl; +float CTimeCycle::m_fCurrentAmbientRed_Obj_Bl; +float CTimeCycle::m_fCurrentAmbientGreen_Obj_Bl; +float CTimeCycle::m_fCurrentAmbientBlue_Obj_Bl; +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_nCurrentPoleShadowStrength; +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_fCurrentWaterRed; +float CTimeCycle::m_fCurrentWaterGreen; +float CTimeCycle::m_fCurrentWaterBlue; +float CTimeCycle::m_fCurrentWaterAlpha; +int32 CTimeCycle::m_nCurrentFogColourRed; +int32 CTimeCycle::m_nCurrentFogColourGreen; +int32 CTimeCycle::m_nCurrentFogColourBlue; + +int32 CTimeCycle::m_FogReduction; +int32 CTimeCycle::m_bExtraColourOn; +int32 CTimeCycle::m_ExtraColour; +float CTimeCycle::m_ExtraColourInter; + +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 ambobjR, ambobjG, ambobjB; + int ambblR, ambblG, ambblB; + int ambobjblR, ambobjblG, ambobjblB; + 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, poleShad; + float farClp, fogSt, lightGnd; + int cloudR, cloudG, cloudB; + int fluffyTopR, fluffyTopG, fluffyTopB; + int fluffyBotR, fluffyBotG, fluffyBotB; + float blurR, blurG, blurB; + float waterR, waterG, waterB, waterA; + + 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] == '/' || work_buff[bi] == '\n' || + work_buff[bi] == '\0' || work_buff[bi] == ' ' || work_buff[bi] == '\r'){ + while(work_buff[bi] != '\n' && work_buff[bi] != '\0' && work_buff[bi] != '\r') + bi++; + bi++; + } + while(work_buff[bi] != '\n' +#ifdef FIX_BUGS + && work_buff[bi] != '\0' +#endif + ) + 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 %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 %f %f %f", + &ambR, &ambG, &ambB, + &ambobjR, &ambobjG, &ambobjB, + &ambblR, &ambblG, &ambblB, + &ambobjblR, &ambobjblG, &ambobjblB, + &dirR, &dirG, &dirB, + &skyTopR, &skyTopG, &skyTopB, + &skyBotR, &skyBotG, &skyBotB, + &sunCoreR, &sunCoreG, &sunCoreB, + &sunCoronaR, &sunCoronaG, &sunCoronaB, + &sunSz, &sprSz, &sprBght, + &shad, &lightShad, &poleShad, + &farClp, &fogSt, &lightGnd, + &cloudR, &cloudG, &cloudB, + &fluffyTopR, &fluffyTopG, &fluffyTopB, + &fluffyBotR, &fluffyBotG, &fluffyBotB, + &blurR, &blurG, &blurB, + &waterR, &waterG, &waterB, &waterA); + + m_nAmbientRed[h][w] = ambR; + m_nAmbientGreen[h][w] = ambG; + m_nAmbientBlue[h][w] = ambB; + m_nAmbientRed_Obj[h][w] = ambobjR; + m_nAmbientGreen_Obj[h][w] = ambobjG; + m_nAmbientBlue_Obj[h][w] = ambobjB; + m_nAmbientRed_Bl[h][w] = ambblR; + m_nAmbientGreen_Bl[h][w] = ambblG; + m_nAmbientBlue_Bl[h][w] = ambblB; + m_nAmbientRed_Obj_Bl[h][w] = ambobjblR; + m_nAmbientGreen_Obj_Bl[h][w] = ambobjblG; + m_nAmbientBlue_Obj_Bl[h][w] = ambobjblB; + 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 * 10.0f; + m_fSpriteSize[h][w] = sprSz * 10.0f; + m_fSpriteBrightness[h][w] = sprBght * 10.0f; + m_nShadowStrength[h][w] = shad; + m_nLightShadowStrength[h][w] = lightShad; + m_nPoleShadowStrength[h][w] = poleShad; + m_fFarClip[h][w] = farClp; + m_fFogStart[h][w] = fogSt; + m_fLightsOnGroundBrightness[h][w] = lightGnd * 10.0f; + 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_fWaterRed[h][w] = waterR; + m_fWaterGreen[h][w] = waterG; + m_fWaterBlue[h][w] = waterB; + m_fWaterAlpha[h][w] = waterA; + } + + m_FogReduction = 0; + + debug("CTimeCycle ready\n"); +} + +static float interp_c0, interp_c1, interp_c2, interp_c3; + +float CTimeCycle::Interpolate(int8 *a, int8 *b) +{ + return a[CWeather::OldWeatherType] * interp_c0 + + b[CWeather::OldWeatherType] * interp_c1 + + a[CWeather::NewWeatherType] * interp_c2 + + b[CWeather::NewWeatherType] * interp_c3; +} + +float CTimeCycle::Interpolate(uint8 *a, uint8 *b) +{ + return a[CWeather::OldWeatherType] * interp_c0 + + b[CWeather::OldWeatherType] * interp_c1 + + a[CWeather::NewWeatherType] * interp_c2 + + b[CWeather::NewWeatherType] * interp_c3; +} + +float CTimeCycle::Interpolate(int16 *a, int16 *b) +{ + return a[CWeather::OldWeatherType] * interp_c0 + + b[CWeather::OldWeatherType] * interp_c1 + + a[CWeather::NewWeatherType] * interp_c2 + + b[CWeather::NewWeatherType] * interp_c3; +} + +void +CTimeCycle::StartExtraColour(int32 c, bool fade) +{ + m_bExtraColourOn = true; + m_ExtraColour = c; + if(fade) + m_ExtraColourInter = 0.0f; + else + m_ExtraColourInter = 1.0f; +} + +void +CTimeCycle::StopExtraColour(bool fade) +{ + m_bExtraColourOn = false; + if(!fade) + m_ExtraColourInter = 0.0f; +} + +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() + CClock::GetSeconds()/60.0f)/60.0f; + // coefficients for a bilinear interpolation + interp_c0 = (1.0f-timeInterp) * (1.0f-CWeather::InterpolationValue); + interp_c1 = timeInterp * (1.0f-CWeather::InterpolationValue); + interp_c2 = (1.0f-timeInterp) * CWeather::InterpolationValue; + interp_c3 = timeInterp * CWeather::InterpolationValue; + +#define INTERP(v) Interpolate(v[h1], v[h2]) + + 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_Obj = INTERP(m_nAmbientRed_Obj); + m_fCurrentAmbientGreen_Obj = INTERP(m_nAmbientGreen_Obj); + m_fCurrentAmbientBlue_Obj = INTERP(m_nAmbientBlue_Obj); + + m_fCurrentAmbientRed_Bl = INTERP(m_nAmbientRed_Bl); + m_fCurrentAmbientGreen_Bl = INTERP(m_nAmbientGreen_Bl); + m_fCurrentAmbientBlue_Bl = INTERP(m_nAmbientBlue_Bl); + + m_fCurrentAmbientRed_Obj_Bl = INTERP(m_nAmbientRed_Obj_Bl); + m_fCurrentAmbientGreen_Obj_Bl = INTERP(m_nAmbientGreen_Obj_Bl); + m_fCurrentAmbientBlue_Obj_Bl = INTERP(m_nAmbientBlue_Obj_Bl); + + m_fCurrentDirectionalRed = INTERP(m_nDirectionalRed); + m_fCurrentDirectionalGreen = INTERP(m_nDirectionalGreen); + m_fCurrentDirectionalBlue = INTERP(m_nDirectionalBlue); + + 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)/10.0f; + m_fCurrentSpriteSize = INTERP(m_fSpriteSize)/10.0f; + m_fCurrentSpriteBrightness = INTERP(m_fSpriteBrightness)/10.0f; + m_nCurrentShadowStrength = INTERP(m_nShadowStrength); + m_nCurrentLightShadowStrength = INTERP(m_nLightShadowStrength); + m_nCurrentPoleShadowStrength = INTERP(m_nPoleShadowStrength); + m_fCurrentFarClip = INTERP(m_fFarClip); + m_fCurrentFogStart = INTERP(m_fFogStart); + m_fCurrentLightsOnGroundBrightness = INTERP(m_fLightsOnGroundBrightness)/10.0f; + + 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_fCurrentWaterRed = INTERP(m_fWaterRed); + m_fCurrentWaterGreen = INTERP(m_fWaterGreen); + m_fCurrentWaterBlue = INTERP(m_fWaterBlue); + m_fCurrentWaterAlpha = INTERP(m_fWaterAlpha); +#undef INTERP + + if(m_FogReduction != 0) + m_fCurrentFarClip = Max(m_fCurrentFarClip, m_FogReduction/64.0f * 650.0f); + + m_CurrentStoredValue = (m_CurrentStoredValue+1)&0xF; + + float sunAngle = 2*PI*(CClock::GetSeconds()/60.0f + 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(); + + if(m_bExtraColourOn) + m_ExtraColourInter = Min(1.0f, m_ExtraColourInter + CTimer::GetTimeStep()/120.0f); + else + m_ExtraColourInter = Max(-.0f, m_ExtraColourInter - CTimer::GetTimeStep()/120.0f); + if(m_ExtraColourInter > 0.0f){ +#define INTERP(extra,cur) (m_ExtraColourInter*extra[m_ExtraColour][WEATHER_EXTRACOLOURS] + (1.0f-m_ExtraColourInter)*cur) +#define INTERPscl(extra,scl,cur) (m_ExtraColourInter*extra[m_ExtraColour][WEATHER_EXTRACOLOURS]/scl + (1.0f-m_ExtraColourInter)*cur) + if(m_nSkyTopRed[m_ExtraColour][WEATHER_EXTRACOLOURS] != 0 || + m_nSkyTopGreen[m_ExtraColour][WEATHER_EXTRACOLOURS] != 0 || + m_nSkyTopBlue[m_ExtraColour][WEATHER_EXTRACOLOURS] != 0){ + m_nCurrentSkyTopRed = INTERP(m_nSkyTopRed,m_nCurrentSkyTopRed); + m_nCurrentSkyTopGreen = INTERP(m_nSkyTopGreen,m_nCurrentSkyTopGreen); + m_nCurrentSkyTopBlue = INTERP(m_nSkyTopBlue,m_nCurrentSkyTopBlue); + + m_nCurrentSkyBottomRed = INTERP(m_nSkyBottomRed,m_nCurrentSkyBottomRed); + m_nCurrentSkyBottomGreen = INTERP(m_nSkyBottomGreen,m_nCurrentSkyBottomGreen); + m_nCurrentSkyBottomBlue = INTERP(m_nSkyBottomBlue,m_nCurrentSkyBottomBlue); + + m_nCurrentSunCoreRed = INTERP(m_nSunCoreRed,m_nCurrentSunCoreRed); + m_nCurrentSunCoreGreen = INTERP(m_nSunCoreGreen,m_nCurrentSunCoreGreen); + m_nCurrentSunCoreBlue = INTERP(m_nSunCoreBlue,m_nCurrentSunCoreBlue); + + m_nCurrentSunCoronaRed = INTERP(m_nSunCoronaRed,m_nCurrentSunCoronaRed); + m_nCurrentSunCoronaGreen = INTERP(m_nSunCoronaGreen,m_nCurrentSunCoronaGreen); + m_nCurrentSunCoronaBlue = INTERP(m_nSunCoronaBlue,m_nCurrentSunCoronaBlue); + + m_fCurrentSunSize = INTERPscl(m_fSunSize,10.0f,m_fCurrentSunSize); + + m_nCurrentLowCloudsRed = INTERP(m_nLowCloudsRed,m_nCurrentLowCloudsRed); + m_nCurrentLowCloudsGreen = INTERP(m_nLowCloudsGreen,m_nCurrentLowCloudsGreen); + m_nCurrentLowCloudsBlue = INTERP(m_nLowCloudsBlue,m_nCurrentLowCloudsBlue); + + m_nCurrentFluffyCloudsTopRed = INTERP(m_nFluffyCloudsTopRed,m_nCurrentFluffyCloudsTopRed); + m_nCurrentFluffyCloudsTopGreen = INTERP(m_nFluffyCloudsTopGreen,m_nCurrentFluffyCloudsTopGreen); + m_nCurrentFluffyCloudsTopBlue = INTERP(m_nFluffyCloudsTopBlue,m_nCurrentFluffyCloudsTopBlue); + + m_nCurrentFluffyCloudsBottomRed = INTERP(m_nFluffyCloudsBottomRed,m_nCurrentFluffyCloudsBottomRed); + m_nCurrentFluffyCloudsBottomGreen = INTERP(m_nFluffyCloudsBottomGreen,m_nCurrentFluffyCloudsBottomGreen); + m_nCurrentFluffyCloudsBottomBlue = INTERP(m_nFluffyCloudsBottomBlue,m_nCurrentFluffyCloudsBottomBlue); + + m_fCurrentWaterRed = INTERP(m_fWaterRed,m_fCurrentWaterRed); + m_fCurrentWaterGreen = INTERP(m_fWaterGreen,m_fCurrentWaterGreen); + m_fCurrentWaterBlue = INTERP(m_fWaterBlue,m_fCurrentWaterBlue); + m_fCurrentWaterAlpha = INTERP(m_fWaterAlpha,m_fCurrentWaterAlpha); + } + + m_fCurrentAmbientRed = INTERP(m_nAmbientRed,m_fCurrentAmbientRed); + m_fCurrentAmbientGreen = INTERP(m_nAmbientGreen,m_fCurrentAmbientGreen); + m_fCurrentAmbientBlue = INTERP(m_nAmbientBlue,m_fCurrentAmbientBlue); + + m_fCurrentAmbientRed_Obj = INTERP(m_nAmbientRed_Obj,m_fCurrentAmbientRed_Obj); + m_fCurrentAmbientGreen_Obj = INTERP(m_nAmbientGreen_Obj,m_fCurrentAmbientGreen_Obj); + m_fCurrentAmbientBlue_Obj = INTERP(m_nAmbientBlue_Obj,m_fCurrentAmbientBlue_Obj); + + m_fCurrentAmbientRed_Bl = INTERP(m_nAmbientRed_Bl,m_fCurrentAmbientRed_Bl); + m_fCurrentAmbientGreen_Bl = INTERP(m_nAmbientGreen_Bl,m_fCurrentAmbientGreen_Bl); + m_fCurrentAmbientBlue_Bl = INTERP(m_nAmbientBlue_Bl,m_fCurrentAmbientBlue_Bl); + + m_fCurrentAmbientRed_Obj_Bl = INTERP(m_nAmbientRed_Obj_Bl,m_fCurrentAmbientRed_Obj_Bl); + m_fCurrentAmbientGreen_Obj_Bl = INTERP(m_nAmbientGreen_Obj_Bl,m_fCurrentAmbientGreen_Obj_Bl); + m_fCurrentAmbientBlue_Obj_Bl = INTERP(m_nAmbientBlue_Obj_Bl,m_fCurrentAmbientBlue_Obj_Bl); + + m_fCurrentDirectionalRed = INTERP(m_nDirectionalRed,m_fCurrentDirectionalRed); + m_fCurrentDirectionalGreen = INTERP(m_nDirectionalGreen,m_fCurrentDirectionalGreen); + m_fCurrentDirectionalBlue = INTERP(m_nDirectionalBlue,m_fCurrentDirectionalBlue); + + m_fCurrentSpriteSize = INTERPscl(m_fSpriteSize,10.0f,m_fCurrentSpriteSize); + m_fCurrentSpriteBrightness = INTERPscl(m_fSpriteBrightness,10.0f,m_fCurrentSpriteBrightness); + m_nCurrentShadowStrength = INTERP(m_nShadowStrength,m_nCurrentShadowStrength); + m_nCurrentLightShadowStrength = INTERP(m_nLightShadowStrength,m_nCurrentLightShadowStrength); + m_nCurrentPoleShadowStrength = INTERP(m_nPoleShadowStrength,m_nCurrentPoleShadowStrength); + m_fCurrentFarClip = INTERP(m_fFarClip,m_fCurrentFarClip); + m_fCurrentFogStart = INTERP(m_fFogStart,m_fCurrentFogStart); + m_fCurrentLightsOnGroundBrightness = INTERPscl(m_fLightsOnGroundBrightness,10.0f,m_fCurrentLightsOnGroundBrightness); + + m_fCurrentBlurRed = INTERP(m_fBlurRed,m_fCurrentBlurRed); + m_fCurrentBlurGreen = INTERP(m_fBlurGreen,m_fCurrentBlurGreen); + m_fCurrentBlurBlue = INTERP(m_fBlurBlue,m_fCurrentBlurBlue); + +#undef INTERP +#undef INTERPscl + } + + if(TheCamera.m_BlurType == MOTION_BLUR_NONE || TheCamera.m_BlurType == MOTION_BLUR_LIGHT_SCENE) + TheCamera.SetMotionBlur(m_fCurrentBlurRed, m_fCurrentBlurGreen, m_fCurrentBlurBlue, 5, MOTION_BLUR_LIGHT_SCENE); + + 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_fCurrentAmbientRed /= 255.0f; + m_fCurrentAmbientGreen /= 255.0f; + m_fCurrentAmbientBlue /= 255.0f; + m_fCurrentAmbientRed_Obj /= 255.0f; + m_fCurrentAmbientGreen_Obj /= 255.0f; + m_fCurrentAmbientBlue_Obj /= 255.0f; + m_fCurrentAmbientRed_Bl /= 255.0f; + m_fCurrentAmbientGreen_Bl /= 255.0f; + m_fCurrentAmbientBlue_Bl /= 255.0f; + m_fCurrentAmbientRed_Obj_Bl /= 255.0f; + m_fCurrentAmbientGreen_Obj_Bl /= 255.0f; + m_fCurrentAmbientBlue_Obj_Bl /= 255.0f; + m_fCurrentDirectionalRed /= 255.0f; + m_fCurrentDirectionalGreen /= 255.0f; + m_fCurrentDirectionalBlue /= 255.0f; + + 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/miami/renderer/Timecycle.h b/src/miami/renderer/Timecycle.h new file mode 100644 index 00000000..da911b75 --- /dev/null +++ b/src/miami/renderer/Timecycle.h @@ -0,0 +1,201 @@ +#pragma once + +class CTimeCycle +{ + static uint8 m_nAmbientRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientRed_Obj[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientGreen_Obj[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientBlue_Obj[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientRed_Bl[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientGreen_Bl[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientBlue_Bl[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientRed_Obj_Bl[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientGreen_Obj_Bl[NUMHOURS][NUMWEATHERS]; + static uint8 m_nAmbientBlue_Obj_Bl[NUMHOURS][NUMWEATHERS]; + static uint8 m_nDirectionalRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nDirectionalGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nDirectionalBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSkyTopRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSkyTopGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSkyTopBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSkyBottomRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSkyBottomGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSkyBottomBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSunCoreRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSunCoreGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSunCoreBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSunCoronaRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSunCoronaGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nSunCoronaBlue[NUMHOURS][NUMWEATHERS]; + static int8 m_fSunSize[NUMHOURS][NUMWEATHERS]; + static int8 m_fSpriteSize[NUMHOURS][NUMWEATHERS]; + static int8 m_fSpriteBrightness[NUMHOURS][NUMWEATHERS]; + static uint8 m_nShadowStrength[NUMHOURS][NUMWEATHERS]; + static uint8 m_nLightShadowStrength[NUMHOURS][NUMWEATHERS]; + static uint8 m_nPoleShadowStrength[NUMHOURS][NUMWEATHERS]; + static int16 m_fFogStart[NUMHOURS][NUMWEATHERS]; + static int16 m_fFarClip[NUMHOURS][NUMWEATHERS]; + static uint8 m_fLightsOnGroundBrightness[NUMHOURS][NUMWEATHERS]; + static uint8 m_nLowCloudsRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nLowCloudsGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nLowCloudsBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_nFluffyCloudsTopRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nFluffyCloudsTopGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nFluffyCloudsTopBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_nFluffyCloudsBottomRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_nFluffyCloudsBottomGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_nFluffyCloudsBottomBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_fBlurRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_fBlurGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_fBlurBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_fWaterRed[NUMHOURS][NUMWEATHERS]; + static uint8 m_fWaterGreen[NUMHOURS][NUMWEATHERS]; + static uint8 m_fWaterBlue[NUMHOURS][NUMWEATHERS]; + static uint8 m_fWaterAlpha[NUMHOURS][NUMWEATHERS]; + + static float m_fCurrentAmbientRed; + static float m_fCurrentAmbientGreen; + static float m_fCurrentAmbientBlue; + static float m_fCurrentAmbientRed_Obj; + static float m_fCurrentAmbientGreen_Obj; + static float m_fCurrentAmbientBlue_Obj; + static float m_fCurrentAmbientRed_Bl; + static float m_fCurrentAmbientGreen_Bl; + static float m_fCurrentAmbientBlue_Bl; + static float m_fCurrentAmbientRed_Obj_Bl; + static float m_fCurrentAmbientGreen_Obj_Bl; + static float m_fCurrentAmbientBlue_Obj_Bl; + 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_nCurrentPoleShadowStrength; + 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_fCurrentWaterRed; + static float m_fCurrentWaterGreen; + static float m_fCurrentWaterBlue; + static float m_fCurrentWaterAlpha; + static int32 m_nCurrentFogColourRed; + static int32 m_nCurrentFogColourGreen; + static int32 m_nCurrentFogColourBlue; + + static int32 m_FogReduction; + +public: + static int32 m_bExtraColourOn; + static int32 m_ExtraColour; + static float m_ExtraColourInter; + 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 GetAmbientRed_Obj(void) { return m_fCurrentAmbientRed_Obj; } + static float GetAmbientGreen_Obj(void) { return m_fCurrentAmbientGreen_Obj; } + static float GetAmbientBlue_Obj(void) { return m_fCurrentAmbientBlue_Obj; } + static float GetAmbientRed_Bl(void) { return m_fCurrentAmbientRed_Bl; } + static float GetAmbientGreen_Bl(void) { return m_fCurrentAmbientGreen_Bl; } + static float GetAmbientBlue_Bl(void) { return m_fCurrentAmbientBlue_Bl; } + static float GetAmbientRed_Obj_Bl(void) { return m_fCurrentAmbientRed_Obj_Bl; } + static float GetAmbientGreen_Obj_Bl(void) { return m_fCurrentAmbientGreen_Obj_Bl; } + static float GetAmbientBlue_Obj_Bl(void) { return m_fCurrentAmbientBlue_Obj_Bl; } + 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 int32 GetBlurRed(void) { return m_fCurrentBlurRed; } + static int32 GetBlurGreen(void) { return m_fCurrentBlurGreen; } + static int32 GetBlurBlue(void) { return m_fCurrentBlurBlue; } + static int32 GetWaterRed(void) { return m_fCurrentWaterRed; } + static int32 GetWaterGreen(void) { return m_fCurrentWaterGreen; } + static int32 GetWaterBlue(void) { return m_fCurrentWaterBlue; } + static int32 GetWaterAlpha(void) { return m_fCurrentWaterAlpha; } + + static void Initialise(void); + static void Update(void); + static float Interpolate(int8 *a, int8 *b); + static float Interpolate(uint8 *a, uint8 *b); + static float Interpolate(int16 *a, int16 *b); + static void StartExtraColour(int32 c, bool fade); + static void StopExtraColour(bool fade); + 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/miami/renderer/VarConsole.cpp b/src/miami/renderer/VarConsole.cpp new file mode 100644 index 00000000..372a091a --- /dev/null +++ b/src/miami/renderer/VarConsole.cpp @@ -0,0 +1,786 @@ +#include "common.h" +#include "VarConsole.h" +#include "Font.h" +#include "Pad.h" + +#define VAR_CONSOLE_PAD 1 + +CVarConsole VarConsole; + +void +CVarConsole::Initialise() +{ + m_nCountEntries = 0; + m_nCurPage = 1; + m_bIsOpen = false; + m_nCurEntry = 0; + m_nFirstEntryOnPage = 0; +} + +void +CVarConsole::Add(char *text, int8 *pVal, uint8 step, int8 min, int8 max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pInt8Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_INT8; + m_aEntries[i].I8_step = step; + m_aEntries[i].I8_min = min; + m_aEntries[i].I8_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, int16 *pVal, uint16 step, int16 min, int16 max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pInt16Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_INT16; + m_aEntries[i].I16_step = step; + m_aEntries[i].I16_min = min; + m_aEntries[i].I16_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, int32 *pVal, uint32 step, int32 min, int32 max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pInt32Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_INT32; + m_aEntries[i].I32_step = step; + m_aEntries[i].I32_min = min; + m_aEntries[i].I32_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, int64 *pVal, uint64 step, int64 min, int64 max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pInt64Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_INT64; + m_aEntries[i].I64_step = step; + m_aEntries[i].I64_min = min; + m_aEntries[i].I64_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, uint8 *pVal, uint8 step, int8 min, int8 max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pUint8Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_UINT8; + m_aEntries[i].I8_step = step; + m_aEntries[i].I8_min = min; + m_aEntries[i].I8_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, uint16 *pVal, uint16 step, int16 min, int16 max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pUint16Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_UINT16; + m_aEntries[i].I16_step = step; + m_aEntries[i].I16_min = min; + m_aEntries[i].I16_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, uint32 *pVal, uint32 step, int32 min, int32 max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pUint32Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_UINT32; + m_aEntries[i].I32_step = step; + m_aEntries[i].I32_min = min; + m_aEntries[i].I32_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, uint64 *pVal, uint64 step, int64 min, int64 max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pUint64Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_UINT64; + m_aEntries[i].I64_step = step; + m_aEntries[i].I64_min = min; + m_aEntries[i].I64_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, float *pVal, float step, float min, float max, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pFloatValue = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_FLOAT; + m_aEntries[i].F_step = step; + m_aEntries[i].F_min = min; + m_aEntries[i].F_max = max; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, bool *pVal, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pBoolValue = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_BOOL; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, bool8 *pVal, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pUint8Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_BOOL8; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, bool16 *pVal, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pUint16Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_BOOL16; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, bool32 *pVal, bool8 isVar) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pUint32Value = pVal; + m_aEntries[i].bAllowExceedBounds = isVar; + m_aEntries[i].VarType = VCE_TYPE_BOOL32; + m_nCountEntries++; +} + +void +CVarConsole::Add(char *text, void (*pCallback)(void)) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + return; + } + + m_aEntries[i].text = text; + m_aEntries[i].pCallback = pCallback; + m_aEntries[i].VarType = VCE_TYPE_FUNCTION; + m_nCountEntries++; +} + +void +CVarConsole::Remove(char *text) +{ + int i; + for (i = 0; i < m_nCountEntries; i++) { + if (m_aEntries[i].text == text) + { + for (int j = i; j < m_nCountEntries-1; j++) + m_aEntries[j] = m_aEntries[j+1]; + m_nCountEntries--; + return; + } + } +} + +void +CVarConsole::SortPages() +{ + m_nNumPages = m_nCountEntries / 30 + 1; +} + +void +CVarConsole::Display() +{ + char s[256]; + wchar ws[256]; + + CFont::SetColor(CRGBA(200, 200, 200, 255)); + CFont::SetFontStyle(FONT_STANDARD); + CFont::SetScale(SCREEN_SCALE_X(0.5f), SCREEN_SCALE_Y(0.6f)); + CFont::SetDropShadowPosition(2); + CFont::SetDropColor(CRGBA(0, 0, 0, 255)); + CFont::SetPropOn(); + CFont::SetWrapx(SCREEN_SCALE_X(DEFAULT_SCREEN_WIDTH)); + CFont::SetRightJustifyWrap(0.0f); + sprintf(s, "PAGE %d", m_nCurPage); + AsciiToUnicode(s, ws); + CFont::SetRightJustifyOn(); + CFont::PrintString(SCREEN_SCALE_X(310.0f), SCREEN_SCALE_Y(30.0f), ws); + CFont::SetRightJustifyOff(); + int y = 45; + for (int i = m_nFirstEntryOnPage; i < m_nCountEntries && i < m_nFirstEntryOnPage + 30; i++) + { + switch (m_aEntries[i].VarType) + { + case VCE_TYPE_INT8: + sprintf(s, "(%d) %s:I8:%d", i + 1, m_aEntries[i].text, *m_aEntries[i].pInt8Value); + break; + case VCE_TYPE_INT16: + sprintf(s, "(%d) %s:I16:%d", i + 1, m_aEntries[i].text, *m_aEntries[i].pInt16Value); + break; + case VCE_TYPE_INT32: + sprintf(s, "(%d) %s:I32:%d", i + 1, m_aEntries[i].text, *m_aEntries[i].pInt32Value); + break; + case VCE_TYPE_INT64: +#ifdef FIX_BUGS + sprintf(s, "(%d) %s:I64:%lld", i + 1, m_aEntries[i].text, *m_aEntries[i].pInt64Value); +#else + sprintf(s, "(%d) %s:I64:%d", i + 1, m_aEntries[i].text, (int32)*m_aEntries[i].pInt64Value); +#endif + break; + case VCE_TYPE_UINT8: + sprintf(s, "(%d) %s:U8:%d", i + 1, m_aEntries[i].text, *m_aEntries[i].pUint8Value); + break; + case VCE_TYPE_UINT16: + sprintf(s, "(%d) %s:U6:%d", i + 1, m_aEntries[i].text, *m_aEntries[i].pUint16Value); + break; + case VCE_TYPE_UINT32: + sprintf(s, "(%d) %s:U32:%d", i + 1, m_aEntries[i].text, *m_aEntries[i].pUint32Value); + break; + case VCE_TYPE_UINT64: +#ifdef FIX_BUGS + sprintf(s, "(%d) %s:U64:%llu", i + 1, m_aEntries[i].text, *m_aEntries[i].pUint64Value); +#else + sprintf(s, "(%d) %s:U64:%d", i + 1, m_aEntries[i].text, (uint32)*m_aEntries[i].pUint64Value); +#endif + break; + case VCE_TYPE_FLOAT: + sprintf(s, "(%d) %s:F:%f", i + 1, m_aEntries[i].text, *m_aEntries[i].pFloatValue); + break; + case VCE_TYPE_BOOL: + if (*m_aEntries[i].pBoolValue) + sprintf(s, "(%d) %s:B:TRUE", i + 1, m_aEntries[i].text); + else + sprintf(s, "(%d) %s:B : FALSE", i + 1, m_aEntries[i].text); + break; + case VCE_TYPE_BOOL8: + if (*m_aEntries[i].pUint8Value == FALSE) + sprintf(s, "(%d) %s:B8:FALSE", i + 1, m_aEntries[i].text); + else + sprintf(s, "(%d) %s:B8:TRUE", i + 1, m_aEntries[i].text); + break; + case VCE_TYPE_BOOL16: + if (*m_aEntries[i].pUint16Value == FALSE) + sprintf(s, "(%d) %s:B16:FALSE", i + 1, m_aEntries[i].text); + else + sprintf(s, "(%d) %s:B16:TRUE", i + 1, m_aEntries[i].text); + break; + case VCE_TYPE_BOOL32: + if (*m_aEntries[i].pUint32Value == FALSE) + sprintf(s, "(%d) %s:B32:FALSE", i + 1, m_aEntries[i].text); + else + sprintf(s, "(%d) %s:B32:TRUE", i + 1, m_aEntries[i].text); + break; + case VCE_TYPE_FUNCTION: + sprintf(s, "(%d) %s:FUNCTION:call this function?", i + 1, m_aEntries[i].text); + break; + } + AsciiToUnicode(s, ws); + if (m_nCurEntry == i) { + CFont::SetBackgroundOn(); +#ifdef FIX_BUGS + CFont::SetBackgroundColor(CRGBA(128, 128, 128, 128)); +#endif + } +#ifdef FIX_BUGS + else + CFont::SetBackgroundColor(CRGBA(128, 128, 128, 0)); +#endif + + CFont::SetColor(CRGBA(200, 200, 200, 255)); + CFont::PrintString(SCREEN_SCALE_X(30.0f), SCREEN_SCALE_Y(y), ws); + if (m_nCurEntry == i) + CFont::SetBackgroundOff(); + y += 12; + } +} + +void +CVarConsole::ModifyLeft() +{ + CVarConsoleEntry &entry = m_aEntries[m_nCurEntry]; + switch (entry.VarType) + { + case VCE_TYPE_INT8: + *entry.pInt8Value -= entry.I8_step; + if (entry.bAllowExceedBounds) { + if (*entry.pInt8Value < entry.I8_min) + *entry.pInt8Value = entry.I8_max; + } else { + if (*entry.pInt8Value < entry.I8_min) + *entry.pInt8Value = entry.I8_min; + } + break; + case VCE_TYPE_INT16: + *entry.pInt16Value -= entry.I16_step; + if (entry.bAllowExceedBounds) { + if (*entry.pInt16Value < entry.I16_min) + *entry.pInt16Value = entry.I16_max; + } + else { + if (*entry.pInt16Value < entry.I16_min) + *entry.pInt16Value = entry.I16_min; + } + break; + case VCE_TYPE_INT32: + *entry.pInt32Value -= entry.I32_step; + if (entry.bAllowExceedBounds) { + if (*entry.pInt32Value < entry.I32_min) + *entry.pInt32Value = entry.I32_max; + } + else { + if (*entry.pInt32Value < entry.I32_min) + *entry.pInt32Value = entry.I32_min; + } + break; + case VCE_TYPE_INT64: + *entry.pInt64Value -= entry.I64_step; + if (entry.bAllowExceedBounds) { + if (*entry.pInt64Value < entry.I64_min) + *entry.pInt64Value = entry.I64_max; + } + else { + if (*entry.pInt64Value < entry.I64_min) + *entry.pInt64Value = entry.I64_min; + } + break; + case VCE_TYPE_UINT8: + *entry.pUint8Value -= entry.I8_step; + if (entry.bAllowExceedBounds) { + if (*(int8*)entry.pUint8Value < entry.I8_min) + *entry.pUint8Value = entry.I8_max; + } + else { + if (*(int8*)entry.pUint8Value < entry.I8_min) + *entry.pUint8Value = entry.I8_min; + } + break; + case VCE_TYPE_UINT16: + *entry.pUint16Value -= entry.I16_step; + if (entry.bAllowExceedBounds) { + if (*(int16*)entry.pUint16Value < entry.I16_min) + *entry.pUint16Value = entry.I16_max; + } + else { + if (*(int16*)entry.pUint16Value < entry.I16_min) + *entry.pUint16Value = entry.I16_min; + } + break; + case VCE_TYPE_UINT32: + *entry.pUint32Value -= entry.I32_step; + if (entry.bAllowExceedBounds) { + if (*(int32*)entry.pUint32Value < entry.I32_min) + *entry.pUint32Value = entry.I32_max; + } + else { + if (*(int32*)entry.pUint32Value < entry.I32_min) + *entry.pUint32Value = entry.I32_min; + } + break; + case VCE_TYPE_UINT64: + *entry.pUint64Value -= entry.I64_step; + if (entry.bAllowExceedBounds) { + if (*(int64*)entry.pUint64Value < entry.I64_min) + *entry.pUint64Value = entry.I64_max; + } + else { + if (*(int64*)entry.pUint64Value < entry.I64_min) + *entry.pUint64Value = entry.I64_min; + } + break; + case VCE_TYPE_FLOAT: + *entry.pFloatValue -= entry.F_step; + if (entry.bAllowExceedBounds) { + if (*entry.pFloatValue < entry.F_min) + *entry.pFloatValue = entry.F_max; + } + else { + if (*entry.pFloatValue < entry.F_min) + *entry.pFloatValue = entry.F_min; + } + break; + case VCE_TYPE_BOOL: + if (entry.bAllowExceedBounds) + *entry.pBoolValue ^= true; + else + *entry.pBoolValue = false; + break; + case VCE_TYPE_BOOL8: + if (entry.bAllowExceedBounds) + *entry.pUint8Value = *entry.pUint8Value == false; + else + *entry.pUint8Value = false; + break; + case VCE_TYPE_BOOL16: + if (entry.bAllowExceedBounds) + *entry.pUint16Value = *entry.pUint16Value == false; + else + *entry.pUint16Value = false; + break; + case VCE_TYPE_BOOL32: + if (entry.bAllowExceedBounds) + *entry.pUint32Value = *entry.pUint32Value == false; + else + *entry.pUint32Value = false; + break; + case VCE_TYPE_FUNCTION: + entry.pCallback(); + break; + default: + return; + } +} + +void +CVarConsole::ModifyRight() +{ + CVarConsoleEntry &entry = m_aEntries[m_nCurEntry]; + switch (entry.VarType) + { + case VCE_TYPE_INT8: + *entry.pInt8Value += entry.I8_step; + if (entry.bAllowExceedBounds) { + if (*entry.pInt8Value > entry.I8_max) + *entry.pInt8Value = entry.I8_min; + } + else { + if (*entry.pInt8Value > entry.I8_max) + *entry.pInt8Value = entry.I8_max; + } + break; + case VCE_TYPE_INT16: + *entry.pInt16Value += entry.I16_step; + if (entry.bAllowExceedBounds) { + if (*entry.pInt16Value > entry.I16_max) + *entry.pInt16Value = entry.I16_min; + } + else { + if (*entry.pInt16Value > entry.I16_max) + *entry.pInt16Value = entry.I16_max; + } + break; + case VCE_TYPE_INT32: + *entry.pInt32Value += entry.I32_step; + if (entry.bAllowExceedBounds) { + if (*entry.pInt32Value > entry.I32_max) + *entry.pInt32Value = entry.I32_min; + } + else { + if (*entry.pInt32Value > entry.I32_max) + *entry.pInt32Value = entry.I32_max; + } + break; + case VCE_TYPE_INT64: + *entry.pInt64Value += entry.I64_step; + if (entry.bAllowExceedBounds) { + if (*entry.pInt64Value > entry.I64_max) + *entry.pInt64Value = entry.I64_min; + } + else { + if (*entry.pInt64Value > entry.I64_max) + *entry.pInt64Value = entry.I64_max; + } + break; + case VCE_TYPE_UINT8: + *entry.pUint8Value += entry.I8_step; + if (entry.bAllowExceedBounds) { + if (*entry.pUint8Value > (uint8)entry.I8_max) + *entry.pUint8Value = entry.I8_min; + } + else { + if (*entry.pUint8Value > (uint8)entry.I8_max) + *entry.pUint8Value = entry.I8_max; + } + break; + case VCE_TYPE_UINT16: + *entry.pUint16Value += entry.I16_step; + if (entry.bAllowExceedBounds) { + if (*entry.pUint16Value > (uint16)entry.I16_max) + *entry.pUint16Value = entry.I16_min; + } + else { + if (*entry.pUint16Value > (uint16)entry.I16_max) + *entry.pUint16Value = entry.I16_max; + } + break; + case VCE_TYPE_UINT32: + *entry.pUint32Value += entry.I32_step; + if (entry.bAllowExceedBounds) { + if (*entry.pUint32Value > (uint32)entry.I32_max) + *entry.pUint32Value = entry.I32_min; + } + else { + if (*entry.pUint32Value > (uint32)entry.I32_max) + *entry.pUint32Value = entry.I32_max; + } + break; + case VCE_TYPE_UINT64: + *entry.pUint64Value += entry.I64_step; + if (entry.bAllowExceedBounds) { + if (*entry.pUint64Value > (uint64)entry.I64_max) + *entry.pUint64Value = entry.I64_min; + } + else { + if (*entry.pUint64Value > (uint64)entry.I64_max) + *entry.pUint64Value = entry.I64_max; + } + break; + case VCE_TYPE_FLOAT: + *entry.pFloatValue += entry.F_step; + if (entry.bAllowExceedBounds) { + if (*entry.pFloatValue > entry.F_max) + *entry.pFloatValue = entry.F_min; + } + else { + if (*entry.pFloatValue > entry.F_max) + *entry.pFloatValue = entry.F_max; + } + break; + case VCE_TYPE_BOOL: + if (entry.bAllowExceedBounds) + *entry.pBoolValue ^= true; + else + *entry.pBoolValue = true; + break; + case VCE_TYPE_BOOL8: + if (entry.bAllowExceedBounds) + *entry.pUint8Value = *entry.pUint8Value == false; + else + *entry.pUint8Value = true; + break; + case VCE_TYPE_BOOL16: + if (entry.bAllowExceedBounds) + *entry.pUint16Value = *entry.pUint16Value == false; + else + *entry.pUint16Value = true; + break; + case VCE_TYPE_BOOL32: + if (entry.bAllowExceedBounds) + *entry.pUint32Value = *entry.pUint32Value == false; + else + *entry.pUint32Value = true; + break; + case VCE_TYPE_FUNCTION: + entry.pCallback(); + break; + default: + return; + } +} + +void +CVarConsole::Enter() +{ + m_bIsOpen = true; +} + +void +CVarConsole::Exit() +{ + m_bIsOpen = false; +} + +void +CVarConsole::Input() +{ + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetDPadDownJustDown() || CPad::GetPad(VAR_CONSOLE_PAD)->GetAnaloguePadDown()) + { + m_nCurEntry++; + if (m_nCurEntry < m_nCountEntries) + { + if (m_nCurEntry > m_nFirstEntryOnPage + 29) + { + m_nFirstEntryOnPage = m_nCurEntry; + ++m_nCurPage; + } + } + else + { + m_nCurEntry = m_nCountEntries - 1; + } + } + + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetDPadUpJustDown() || CPad::GetPad(VAR_CONSOLE_PAD)->GetAnaloguePadUp()) + { + m_nCurEntry--; + if (m_nCurEntry < m_nFirstEntryOnPage) + { + m_nFirstEntryOnPage = m_nCurEntry - 29; + --m_nCurPage; + } + if (m_nFirstEntryOnPage < 0) + { + m_nCurEntry = 0; + m_nFirstEntryOnPage = 0; + m_nCurPage = 1; + } + } + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetSquare()) + ModifyLeft(); + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetTriangle()) + ModifyRight(); + + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetDPadLeftJustDown() || CPad::GetPad(VAR_CONSOLE_PAD)->GetAnaloguePadLeft()) + ModifyLeft(); + + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetDPadRightJustDown() || CPad::GetPad(VAR_CONSOLE_PAD)->GetAnaloguePadRight()) + ModifyRight(); + + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetLeftShoulder2JustDown()) + { + if (m_nCurPage > 1) + { + m_nCurPage--; + m_nFirstEntryOnPage -= 30; + m_nCurEntry = m_nFirstEntryOnPage; + if (m_nFirstEntryOnPage < 0) + { + m_nFirstEntryOnPage = 0; + m_nCurEntry = m_nFirstEntryOnPage; + m_nCurPage = 1; + } + } + } + + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetRightShoulder2JustDown()) + { + if (m_nCurPage < m_nNumPages) + { + m_nCurPage++; + m_nFirstEntryOnPage += 30; + m_nCurEntry = m_nFirstEntryOnPage; + if (m_nFirstEntryOnPage >= m_nCountEntries) + { + m_nFirstEntryOnPage -= 30; + m_nCurEntry = m_nFirstEntryOnPage; + m_nCurPage--; + } + } + } + + if (CPad::GetPad(VAR_CONSOLE_PAD)->GetRightShoulder1JustDown() && CPad::GetPad(VAR_CONSOLE_PAD)->GetLeftShoulder1JustDown()) + Exit(); +} + +void +CVarConsole::Process() +{ + Input(); + SortPages(); + Display(); +} + +bool8 +CVarConsole::Open() +{ + return m_bIsOpen; +} + +void +CVarConsole::Check() +{ + if (Open()) + Process(); + else if (CPad::GetPad(VAR_CONSOLE_PAD)->GetRightShoulder1JustDown() && CPad::GetPad(VAR_CONSOLE_PAD)->GetLeftShoulder1JustDown()) + Enter(); +} \ No newline at end of file diff --git a/src/miami/renderer/VarConsole.h b/src/miami/renderer/VarConsole.h new file mode 100644 index 00000000..5179a10d --- /dev/null +++ b/src/miami/renderer/VarConsole.h @@ -0,0 +1,92 @@ +#pragma once + +enum eVarConsoleEntryType +{ + VCE_TYPE_INT8, + VCE_TYPE_INT16, + VCE_TYPE_INT32, + VCE_TYPE_INT64, + VCE_TYPE_UINT8, + VCE_TYPE_UINT16, + VCE_TYPE_UINT32, + VCE_TYPE_UINT64, + VCE_TYPE_FLOAT, + VCE_TYPE_BOOL, + VCE_TYPE_BOOL8, + VCE_TYPE_BOOL16, + VCE_TYPE_BOOL32, + VCE_TYPE_FUNCTION, +}; + +struct CVarConsoleEntry +{ + char *text; + int8 *pInt8Value; + int16 *pInt16Value; + int32 *pInt32Value; + int64 *pInt64Value; + uint8 *pUint8Value; + uint16 *pUint16Value; + uint32 *pUint32Value; + uint64 *pUint64Value; + float *pFloatValue; + bool *pBoolValue; + void (*pCallback)(void); + int8 I8_step, I8_max, I8_min; + int16 I16_step, I16_max, I16_min; + int32 I32_step, I32_max, I32_min; + int64 I64_step, I64_max, I64_min; + float F_step, F_max, F_min; + bool8 bAllowExceedBounds; + uint8 VarType; +}; + + +class CVarConsole +{ + int32 m_nCountEntries; + bool8 m_bIsOpen; + int32 m_nCurEntry; + int32 m_nFirstEntryOnPage; + int32 m_nCurPage; + int32 m_nNumPages; + CVarConsoleEntry m_aEntries[91]; +public: +#ifdef FIX_BUGS + CVarConsole() { Initialise(); } +#endif + void Initialise(); + void Add(char *text, int8 *pVal, uint8 step, int8 min, int8 max, bool8 isVar); + void Add(char *text, int16 *pVal, uint16 step, int16 min, int16 max, bool8 isVar); + void Add(char *text, int32 *pVal, uint32 step, int32 min, int32 max, bool8 isVar); + void Add(char *text, int64 *pVal, uint64 step, int64 min, int64 max, bool8 isVar); + void Add(char *text, uint8 *pVal, uint8 step, int8 min, int8 max, bool8 isVar); + void Add(char *text, uint16 *pVal, uint16 step, int16 min, int16 max, bool8 isVar); + void Add(char *text, uint32 *pVal, uint32 step, int32 min, int32 max, bool8 isVar); + void Add(char *text, uint64 *pVal, uint64 step, int64 min, int64 max, bool8 isVar); + void Add(char *text, float *pVal, float step, float min, float max, bool8 isVar); + void Add(char *text, bool *pVal, bool8 isVar); + void Add(char *text, bool8 *pVal, bool8 isVar); + void Add(char *text, bool16 *pVal, bool8 isVar); + void Add(char *text, bool32 *pVal, bool8 isVar); + void Add(char *text, void (*pVar)(void)); + + void Remove(char *text); + + void SortPages(); + void Display(); + + void ModifyLeft(); + void ModifyRight(); + + void Enter(); + void Exit(); + + void Input(); + void Process(); + + bool8 Open(); + void Check(); +}; + +extern CVarConsole VarConsole; \ No newline at end of file diff --git a/src/miami/renderer/WaterCannon.cpp b/src/miami/renderer/WaterCannon.cpp new file mode 100644 index 00000000..4976f8a3 --- /dev/null +++ b/src/miami/renderer/WaterCannon.cpp @@ -0,0 +1,317 @@ +#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" +#include "Particle.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(); + } + } + + for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ ) + { + if ( m_abUsed[i] && gFireManager.ExtinguishPointWithWater(m_avecPos[i], 4.0f) ) + { + break; + } + } + + 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; + + float pedSpeed2D = ped->m_vecMoveSpeed.Magnitude2D(); + + if ( pedSpeed2D > 0.2f ) { + ped->m_vecMoveSpeed.x *= (0.2f / pedSpeed2D); + ped->m_vecMoveSpeed.y *= (0.2f / pedSpeed2D); + } + ped->SetFall(2000, (AnimationId)(localDir + ANIM_STD_HIGHIMPACT_FRONT), 0); + CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, ped->GetPosition(), ped->m_vecMoveSpeed * 0.3f, 0, 0.5f); + CParticle::AddParticle(PARTICLE_CAR_SPLASH, ped->GetPosition(), ped->m_vecMoveSpeed * -0.3f + CVector(0.f, 0.f, 0.5f), 0, 0.5f, + CGeneral::GetRandomNumberInRange(0.f, 10.f), CGeneral::GetRandomNumberInRange(0.f, 90.f), 1); + + 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/miami/renderer/WaterCannon.h b/src/miami/renderer/WaterCannon.h new file mode 100644 index 00000000..a37bdd12 --- /dev/null +++ b/src/miami/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/miami/renderer/WaterCreatures.cpp b/src/miami/renderer/WaterCreatures.cpp new file mode 100644 index 00000000..9d591787 --- /dev/null +++ b/src/miami/renderer/WaterCreatures.cpp @@ -0,0 +1,275 @@ +#include "common.h" +#include "WaterCreatures.h" +#include "ModelIndices.h" +#include "World.h" +#include "WaterLevel.h" +#include "Camera.h" +#include "PlayerPed.h" +#include "General.h" +#include "Object.h" + +int32 CWaterCreatures::nNumActiveSeaLifeForms; +CWaterCreature CWaterCreatures::aWaterCreatures[NUM_WATER_CREATURES]; + +struct WaterCreatureProperties aProperties[65] = { + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_JELLYFISH, 0.01f, 2.2f, 0.0005f, 3.5f }, + { &MI_JELLYFISH01, 0.01f, 2.2f, 0.0005f, 3.5f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_TURTLE, 0.01f, 2.0f, 0.0005f, 4.0f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_DOLPHIN, 0.03f, 1.5f, 0.0005f, 4.0f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_SHARK, 0.03f, 0.4f, 0.0005f, 4.0f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH1SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH2SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH2S, 0.04f, 1.5f, 0.0008f, 3.0f }, + { &MI_FISH3SINGLE, 0.04f, 1.0f, 0.0008f, 3.0f }, + { &MI_FISH3S, 0.04f, 1.5f, 0.0008f, 3.0f }, +}; + +CWaterCreature::CWaterCreature() { + Free(); +} + +void CWaterCreature::Initialise(CObject *pObj, float fFwdSpeed, float fZTurnSpeed, float fWaterDepth, uint32 alpha, eFishSlotState state) { + this->m_pObj = pObj; + this->m_fFwdSpeed = fFwdSpeed; + this->m_fZTurnSpeed = fZTurnSpeed; + this->m_fWaterDepth = fWaterDepth; + this->m_alpha = alpha; + this->m_state = state; +} + +void CWaterCreature::Allocate(CObject *pObj, float fFwdSpeed, float fZTurnSpeed, float fWaterDepth, uint32 alpha, eFishSlotState state) { + CWaterCreature::Initialise(pObj, fFwdSpeed, fZTurnSpeed, fWaterDepth, alpha, state); +} + +void CWaterCreature::Free() { + CWaterCreature::Initialise(nil, 0.0f, 0.0f, 0.0f, 0, WATER_CREATURE_DISABLED); +} + +CWaterCreature *CWaterCreatures::GetFishStructSlot() { + for (int i = 0; i < NUM_WATER_CREATURES; i++) + if (aWaterCreatures[i].m_state == WATER_CREATURE_DISABLED) + return &aWaterCreatures[i]; + + return nil; +} + +CObject *CWaterCreatures::CreateSeaLifeForm(CVector const& pos, int16 modelID, int32 zRotAngle) { + if (CObject::nNoTempObjects >= NUMTEMPOBJECTS) + return nil; + + CObject *pObj = new CObject(modelID, true); + + if (!pObj) return nil; + + pObj->SetPosition(pos); + pObj->GetMatrix().UpdateRW(); + pObj->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + pObj->m_vecTurnSpeed = CVector(0.0f, 0.0f, 0.0f); + pObj->GetMatrix().SetRotateZOnly(DEGTORAD(zRotAngle)); + pObj->GetMatrix().UpdateRW(); + pObj->ObjectCreatedBy = CONTROLLED_SUB_OBJECT; + pObj->bIsStatic = false; + + if (pObj->ObjectCreatedBy == TEMP_OBJECT) { + CObject::nNoTempObjects++; + pObj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 60000; + } + + pObj->bTouchingWater = true; + pObj->bUnderwater = true; + CWorld::Add(pObj); + + return pObj; +} + +bool CWaterCreatures::IsSpaceForMoreWaterCreatures() { + return nNumActiveSeaLifeForms < NUM_WATER_CREATURES; +} + +float CWaterCreatures::CalculateFishHeading(CVector const& pos1, CVector const& pos2) { + CVector delta = pos1 - pos2; + delta.Normalise(); + + return CGeneral::GetRandomNumberInRange(-90, 90) + + RADTODEG(delta.Heading() + HALFPI + PI); +} + +void CWaterCreatures::CreateOne(CVector const& pos, int32 modelID) { + if (!IsSpaceForMoreWaterCreatures()) + return; + + CVector playerPos = FindPlayerPed()->GetPosition(); + CVector fishPos = pos; + float fDepth, fLevelNoWaves; + if (!TheCamera.IsSphereVisible(fishPos, 3.0f) + && CWaterLevel::GetWaterDepth(fishPos, &fDepth, &fLevelNoWaves, nil) && fDepth > 4.5f) { + + if (modelID == -1 || modelID < 0 || modelID > 64) + modelID = CGeneral::GetRandomNumberInRange(0, 64); + + WaterCreatureProperties *creature = &aProperties[modelID]; + fishPos.z = fLevelNoWaves - creature->fLevel; + float fFwdSpeed = CGeneral::GetRandomNumberInRange(0.0f, creature->fFwdSpeed) + 0.01f; + float angle = CWaterCreatures::CalculateFishHeading(playerPos, fishPos); + + CObject *fish = CreateSeaLifeForm(fishPos, *creature->modelID, angle); + if (!fish) return; + + fish->SetRwObjectAlpha(255); + CWaterCreature *wc = GetFishStructSlot(); + wc->Allocate(fish, fFwdSpeed, 0.0f, creature->fWaterDepth, 255, WATER_CREATURE_INIT); + nNumActiveSeaLifeForms++; + } +} + +void CWaterCreatures::FreeFishStructSlot(CWaterCreature *wc) { + wc->Free(); +} + +void CWaterCreatures::UpdateAll() { + if (nNumActiveSeaLifeForms == 0) + return; + + CVector playerPos = FindPlayerPed()->GetPosition(); + for (int i = 0; i < NUM_WATER_CREATURES; i++) { + switch (aWaterCreatures[i].m_state) { + case WATER_CREATURE_ACTIVE: + // is this even reachable? + aWaterCreatures[i].m_pObj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 40000; + if (!aWaterCreatures[i].m_pObj->GetIsOnScreen()) { + aWaterCreatures[i].m_pObj->SetRwObjectAlpha(0); + aWaterCreatures[i].m_state = WATER_CREATURE_REMOVE; + break; + } + // fall through + case WATER_CREATURE_INIT: { + if ((playerPos - aWaterCreatures[i].m_pObj->GetPosition()).MagnitudeSqr() < SQR(75.0f)) { + if (aWaterCreatures[i].m_alpha < 255) + aWaterCreatures[i].m_alpha = Min(aWaterCreatures[i].m_alpha + 4, 255); + aWaterCreatures[i].m_pObj->SetRwObjectAlpha(aWaterCreatures[i].m_alpha); + CVector fwd = aWaterCreatures[i].m_pObj->GetRight(); // for some reason they used x for forward + fwd.Normalise(); + aWaterCreatures[i].m_pObj->m_vecMoveSpeed = fwd * aWaterCreatures[i].m_fFwdSpeed; + aWaterCreatures[i].m_pObj->m_vecTurnSpeed = CVector(0.0f, 0.0f, aWaterCreatures[i].m_fZTurnSpeed); + aWaterCreatures[i].m_pObj->bIsStatic = false; + float fDepth = 0.0; + CWaterLevel::GetWaterDepth(aWaterCreatures[i].m_pObj->GetPosition(), &fDepth, nil, nil); + if (aWaterCreatures[i].m_fWaterDepth < fDepth) { + // it looks like this can never be true initially, looks like a BUG + if (aWaterCreatures[i].m_pObj->m_nEndOfLifeTime - 40000 <= CTimer::GetTimeInMilliseconds()) + aWaterCreatures[i].m_state = WATER_CREATURE_ACTIVE; + } + else { + // creature is deeper than water + aWaterCreatures[i].m_state = WATER_CREATURE_FADE_OUT; + } + + } + else { + aWaterCreatures[i].m_state = WATER_CREATURE_REMOVE; + } + break; + } + case WATER_CREATURE_FADE_OUT: { + aWaterCreatures[i].m_pObj->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 40000; + if (aWaterCreatures[i].m_alpha <= 0) { + aWaterCreatures[i].m_state = WATER_CREATURE_REMOVE; + } + else { + aWaterCreatures[i].m_alpha = Max(aWaterCreatures[i].m_alpha - 6, 0); + aWaterCreatures[i].m_pObj->SetRwObjectAlpha(aWaterCreatures[i].m_alpha); + CVector speed = aWaterCreatures[i].m_pObj->GetRight(); + speed.Normalise(); + speed.x *= aWaterCreatures[i].m_fFwdSpeed; + speed.y *= aWaterCreatures[i].m_fFwdSpeed; + speed.z = -0.015f; + aWaterCreatures[i].m_pObj->m_vecMoveSpeed = speed; + + if (!aWaterCreatures[i].m_pObj->GetIsOnScreen()) + aWaterCreatures[i].m_state = WATER_CREATURE_REMOVE; + } + break; + } + case WATER_CREATURE_REMOVE: + if (aWaterCreatures[i].m_pObj){ + CWorld::Remove(aWaterCreatures[i].m_pObj); + delete aWaterCreatures[i].m_pObj; + } + FreeFishStructSlot(&aWaterCreatures[i]); + nNumActiveSeaLifeForms--; + aWaterCreatures[i].m_state = WATER_CREATURE_DISABLED; + break; + default: + break; + } + } +} + +void CWaterCreatures::RemoveAll() { + for (int i = 0; i < NUM_WATER_CREATURES; i++) { + if (aWaterCreatures[i].m_state != WATER_CREATURE_DISABLED) { + if (aWaterCreatures[i].m_pObj){ + CWorld::Remove(aWaterCreatures[i].m_pObj); + delete aWaterCreatures[i].m_pObj; + } + FreeFishStructSlot(&aWaterCreatures[i]); + aWaterCreatures[i].m_state = WATER_CREATURE_DISABLED; + nNumActiveSeaLifeForms--; + } + } +} \ No newline at end of file diff --git a/src/miami/renderer/WaterCreatures.h b/src/miami/renderer/WaterCreatures.h new file mode 100644 index 00000000..32754a10 --- /dev/null +++ b/src/miami/renderer/WaterCreatures.h @@ -0,0 +1,49 @@ +#pragma once + +class CObject; + +enum eFishSlotState { + WATER_CREATURE_INIT = 0, + WATER_CREATURE_ACTIVE, + WATER_CREATURE_FADE_OUT, + WATER_CREATURE_REMOVE, + WATER_CREATURE_DISABLED +}; + +class CWaterCreature { +public: + CObject *m_pObj; + float m_fFwdSpeed; + float m_fZTurnSpeed; + int32 m_alpha; + float m_fWaterDepth; + int32 m_state; + + CWaterCreature(); + void Allocate(CObject *pObj, float fFwdSpeed, float fZTurnSpeed, float fWaterDepth, uint32 alpha, eFishSlotState state); + void Free(); + void Initialise(CObject *pObj, float fFwdSpeed, float fZTurnSpeed, float fWaterDepth, uint32 alpha, eFishSlotState state); +}; + +class CWaterCreatures { + +public: + static CWaterCreature aWaterCreatures[NUM_WATER_CREATURES]; + static int32 nNumActiveSeaLifeForms; + static CObject *CreateSeaLifeForm(CVector const& pos, int16 modelID, int32 zRotAngle); + static void CreateOne(CVector const& pos, int32 modelID); + static void UpdateAll(); + static void FreeFishStructSlot(CWaterCreature *wc); + static bool IsSpaceForMoreWaterCreatures(); + static float CalculateFishHeading(CVector const& pos1, CVector const& pos2); + static void RemoveAll(); + static CWaterCreature* GetFishStructSlot(); +}; + +struct WaterCreatureProperties { + int16 *modelID; + float fFwdSpeed; + float fLevel; + float fUnknown; //unused + float fWaterDepth; +}; \ No newline at end of file diff --git a/src/miami/renderer/WaterLevel.cpp b/src/miami/renderer/WaterLevel.cpp new file mode 100644 index 00000000..c0df745c --- /dev/null +++ b/src/miami/renderer/WaterLevel.cpp @@ -0,0 +1,3382 @@ +#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 "PlayerPed.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 "ColStore.h" +#include "CdStream.h" +#include "Pad.h" +#include "RenderBuffer.h" +#include +#include +#include +#include "Occlusion.h" +#include "Replay.h" +#include "WaterLevel.h" +#include "SurfaceTable.h" +#include "WaterCreatures.h" + +#define RwIm3DVertexSet_RGBA(vert, rgba) RwIm3DVertexSetRGBA(vert, rgba.red, rgba.green, rgba.blue, rgba.alpha) // (RwRGBAAssign(&(_dst)->color, &_src)) + +float TEXTURE_ADDU; +float TEXTURE_ADDV; + +float _TEXTURE_MASK_ADDU; +float _TEXTURE_MASK_ADDV; + +float _TEXTURE_WAKE_ADDU; +float _TEXTURE_WAKE_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; + + +bool CWaterLevel::RequireWavySector; +bool CWaterLevel::MaskCalculatedThisFrame; +CVector CWaterLevel::PreCalculatedMaskPosn; +bool CWaterLevel::m_bRenderSeaBed; +int32 CWaterLevel::m_nRenderWaterLayers; + +RpAtomic *CWaterLevel::ms_pWavyAtomic; +RpAtomic *CWaterLevel::ms_pMaskAtomic; +//"Custom" Don't Render Water Toggle +bool gbDontRenderWater; + + +RwTexture *gpWaterTex; +RwTexture *gpWaterEnvTex; +RwTexture *gpWaterEnvBaseTex; +RwTexture *gpWaterWakeTex; + +RwRaster *gpWaterRaster; +RwRaster *gpWaterEnvRaster; +RwRaster *gpWaterEnvBaseRaster; +RwRaster *gpWaterWakeRaster; + +bool _bSeaLife; +float _fWaterZOffset = WATER_Z_OFFSET; + +#ifdef PC_WATER +float fEnvScale = 0.25f; +#else +float fEnvScale = 0.5f; +#endif +float fWave2InvLength = 0.03f; +float fWave2NormScale = 0.5f; +float fWave2Ampl = 0.1f; +uint8 nWaterAlpha = 192; +uint8 nWakeAlpha = 192; +float fUnder1 = 4.0; +float fUnder2 = 2.5; +float fUnder3 = 1.5; +int nMaskAlpha = 230; +float fAdd1 = 180.0f; +float fAdd2 = 80.0; +float fRedMult = 0.6f; +float fGreenMult = 1.0f; +float fBlueMult = 1.4f; +float fAlphaMult = 500.0f; +float fAlphaBase = 30.0f; +float fRandomMoveDiv = 8.0f; +float fRandomDamp = 0.99f; +float fNormMult = 2.0f; +float fNormMultB = 1.0f; +float fBumpScale = 1.5; +float fBumpTexRepeat = 2.0; +float fNormalDirectionScalar1 = 2.0f; +float fNormalDirectionScalar2 = 1.0f; +bool bTestDoNormals = true; +float fSeaBedZ = 25.0f; +float aAlphaFade[5] = { 0.4f, 1.0f, 0.2f, 1.0f, 0.4f}; //CWaterLevel::RenderWakeSegment +float fFlatWaterBlendRange = 0.05f; +float fStartBlendDistanceAdd = 64.0f; +float fMinWaterAlphaMult = -30.0f; + + +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"); + + // collision is streamed in VC + CColStore::LoadAllCollision(); + + CFileMgr::SetDir(""); + hFile = CFileMgr::OpenFile(pWaterDat, "r"); + + char *line; + + while ((line = CFileLoader::LoadLine(hFile))) + { + if (*line && *line != ';' && !strstr(line, "* ;end of file")) + { + 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 + WATER_X_OFFSET); + int32 r = WATER_HUGE_X(ms_aWaterRects[i].right + WATER_X_OFFSET) + 1.0f; + int32 t = WATER_HUGE_Y(ms_aWaterRects[i].top); + int32 b = WATER_HUGE_Y(ms_aWaterRects[i].bottom) + 1.0f; + + 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); + + 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 - WATER_X_OFFSET; + + for (int32 y = 0; y < MAX_SMALL_SECTORS; y++) + { + if (CWaterLevel::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); + } + + // collision is streamed in VC + CColStore::RemoveAllCollision(); + } +#endif + + CTxdStore::PushCurrentTxd(); + + int32 slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + + if ( gpWaterTex == nil ) + gpWaterTex = RwTextureRead("waterclear256", nil); + gpWaterRaster = RwTextureGetRaster(gpWaterTex); + + if ( gpWaterEnvTex == nil ) + gpWaterEnvTex = RwTextureRead("waterreflection2", nil); + gpWaterEnvRaster = RwTextureGetRaster(gpWaterEnvTex); + +#ifdef PC_WATER + if ( gpWaterEnvBaseTex == nil ) + gpWaterEnvBaseTex = RwTextureRead("sandywater", nil); + gpWaterEnvBaseRaster = RwTextureGetRaster(gpWaterEnvBaseTex); +#endif + + if ( gpWaterWakeTex == nil ) + gpWaterWakeTex = RwTextureRead("waterwake", nil); + gpWaterWakeRaster = RwTextureGetRaster(gpWaterWakeTex); + + CTxdStore::PopCurrentTxd(); + + CreateWavyAtomic(); + + printf("Done Initing waterlevels\n"); +} + +void +CWaterLevel::Shutdown() +{ + DestroyWavyAtomic(); + +#define _DELETE_TEXTURE(t) if ( t ) \ + { \ + RwTextureDestroy(t); \ + t = nil; \ + } + + _DELETE_TEXTURE(gpWaterTex); + _DELETE_TEXTURE(gpWaterEnvTex); + _DELETE_TEXTURE(gpWaterWakeTex); + _DELETE_TEXTURE(gpWaterEnvBaseTex); + +#undef _DELETE_TEXTURE +} + +void +CWaterLevel::CreateWavyAtomic() +{ + RpGeometry *wavyGeometry; + RpGeometry *maskGeometry; + RpMaterial *wavyMaterial; + RpMaterial *maskMaterial; + + RpTriangle *wavytlist; + RpTriangle *masktlist; + + RpMorphTarget *wavyMorphTarget; + RpMorphTarget *maskMorphTarget; + + RwSphere boundingSphere; + + RwV3d *wavyVert; + RwV3d *wavyNormal; + + RwV3d *maskVert; + RwV3d *maskNormal; + + RwFrame *wavyFrame; + RwFrame *maskFrame; + + { + wavyGeometry = RpGeometryCreate(17*17, 512, rpGEOMETRYTRISTRIP + |rpGEOMETRYTEXTURED + |rpGEOMETRYPRELIT + |rpGEOMETRYNORMALS + |rpGEOMETRYMODULATEMATERIALCOLOR + |rw::Geometry::HAS_TRIANGLES/* RW_DC specific */); +#ifdef PC_WATER + RpGeometryAddMorphTarget(wavyGeometry); +#endif + } + + { + maskGeometry = RpGeometryCreate(33*33, 2048, rpGEOMETRYTRISTRIP + |rpGEOMETRYTEXTURED + |rpGEOMETRYPRELIT + |rpGEOMETRYNORMALS + |rpGEOMETRYMODULATEMATERIALCOLOR + |rw::Geometry::HAS_TRIANGLES/* RW_DC specific */); +#ifdef PC_WATER + RpGeometryAddMorphTarget(maskGeometry); +#endif + } + + { + wavyMaterial = RpMaterialCreate(); + RpMaterialSetTexture(wavyMaterial, gpWaterTex); + RwRGBA watercolor = { 255, 255, 255, 192 }; + RpMaterialSetColor(wavyMaterial, &watercolor); + } + + { + maskMaterial = RpMaterialCreate(); +#ifdef PC_WATER + RpMaterialSetTexture(maskMaterial, gpWaterEnvBaseTex); +#else + RpMaterialSetTexture(maskMaterial, gpWaterTex); +#endif + RwRGBA watercolor = { 255, 255, 255, 192 }; + RpMaterialSetColor(maskMaterial, &watercolor); + } + + { + wavytlist = RpGeometryGetTriangles(wavyGeometry); + + for ( int32 i = 0; i < 16; i++ ) + { + for ( int32 j = 0; j < 16; j++ ) + { + const RwUInt16 base = (RwUInt16)((16 + 1)*i+j); + + RpGeometryTriangleSetVertexIndices(wavyGeometry, + wavytlist, (RwInt16)base, (RwInt16)(base+1), (RwInt16)(base+16+2)); + + RpGeometryTriangleSetVertexIndices(wavyGeometry, + (wavytlist+1), (RwInt16)base, (RwInt16)(base+16+2), (RwInt16)(base+16+1)); + + RpGeometryTriangleSetMaterial(wavyGeometry, wavytlist, wavyMaterial); + + RpGeometryTriangleSetMaterial(wavyGeometry, (wavytlist+1), wavyMaterial); + + wavytlist+=2; + } + } + } + + { + masktlist = RpGeometryGetTriangles(maskGeometry); + + for ( int32 i = 0; i < 32; i++ ) + { + for ( int32 j = 0; j < 32; j++ ) + { + const RwUInt16 base = (RwUInt16)((32 + 1)*i+j); + + RpGeometryTriangleSetVertexIndices(maskGeometry, + masktlist, (RwInt16)base, (RwInt16)(base+1), (RwInt16)(base+32+2)); + + RpGeometryTriangleSetVertexIndices(maskGeometry, + (masktlist+1), (RwInt16)base, (RwInt16)(base+32+2), (RwInt16)(base+32+1)); + + RpGeometryTriangleSetMaterial(maskGeometry, masktlist, maskMaterial); + + RpGeometryTriangleSetMaterial(maskGeometry, (masktlist+1), maskMaterial); + + masktlist+=2; + } + } + } + + { + wavyMorphTarget = RpGeometryGetMorphTarget(wavyGeometry, 0); + wavyVert = RpMorphTargetGetVertices(wavyMorphTarget); + wavyNormal = RpMorphTargetGetVertexNormals(wavyMorphTarget); + + for ( int32 i = 0; i < 17; i++ ) + { + for ( int32 j = 0; j < 17; j++ ) + { + (*wavyVert).x = (float)i * 2.0f; + (*wavyVert).y = (float)j * 2.0f; + (*wavyVert).z = 0.0f; + + (*wavyNormal).x = 0.0f; + (*wavyNormal).y = 0.0f; + (*wavyNormal).z = 1.0f; + + wavyVert++; + wavyNormal++; + } + } + + RpMorphTargetCalcBoundingSphere(wavyMorphTarget, &boundingSphere); + RpMorphTargetSetBoundingSphere(wavyMorphTarget, &boundingSphere); + RpGeometryUnlock(wavyGeometry); + } + + { + maskMorphTarget = RpGeometryGetMorphTarget(maskGeometry, 0); + maskVert = RpMorphTargetGetVertices(maskMorphTarget); + maskNormal = RpMorphTargetGetVertexNormals(maskMorphTarget); + + for ( int32 i = 0; i < 33; i++ ) + { + for ( int32 j = 0; j < 33; j++ ) + { + (*maskVert).x = (float)i * 2.0f; + (*maskVert).y = (float)j * 2.0f; + (*maskVert).z = 0.0f; + + (*maskNormal).x = 0.0f; + (*maskNormal).y = 0.0f; + (*maskNormal).z = 1.0f; + + maskVert++; + maskNormal++; + } + } + + RpMorphTargetCalcBoundingSphere(maskMorphTarget, &boundingSphere); + RpMorphTargetSetBoundingSphere(maskMorphTarget, &boundingSphere); + RpGeometryUnlock(maskGeometry); + } + + { + wavyFrame = RwFrameCreate(); + ms_pWavyAtomic = RpAtomicCreate(); + RpAtomicSetGeometry(ms_pWavyAtomic, wavyGeometry, 0); + RpAtomicSetFrame(ms_pWavyAtomic, wavyFrame); + RpMaterialDestroy(wavyMaterial); + RpGeometryDestroy(wavyGeometry); + } + + { + maskFrame = RwFrameCreate(); + ms_pMaskAtomic = RpAtomicCreate(); + RpAtomicSetGeometry(ms_pMaskAtomic, maskGeometry, 0); + RpAtomicSetFrame(ms_pMaskAtomic, maskFrame); + RpMaterialDestroy(maskMaterial); + RpGeometryDestroy(maskGeometry); + } + + static RwFrame *wakeEnvFrame; + + if ( wakeEnvFrame == nil ) + { + wakeEnvFrame = RwFrameCreate(); + RwMatrixSetIdentity(RwFrameGetMatrix(wakeEnvFrame)); + RwFrameUpdateObjects(wakeEnvFrame); + } + + RpMatFXMaterialSetEffects(maskMaterial, rpMATFXEFFECTENVMAP); + RpMatFXMaterialSetupEnvMap(maskMaterial, gpWaterEnvTex, wakeEnvFrame, TRUE, fEnvScale); + RpMatFXAtomicEnableEffects(ms_pMaskAtomic); +} + +void +CWaterLevel::DestroyWavyAtomic() +{ +#define _DELETE_ATOMIC(a) \ + { \ + RwFrame *frame; \ + frame = RpAtomicGetFrame(a); \ + RpAtomicDestroy(a); \ + RwFrameDestroy(frame); \ + } + + _DELETE_ATOMIC(ms_pWavyAtomic); + _DELETE_ATOMIC(ms_pMaskAtomic); + +#undef _DELETE_ATOMIC +} + +#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]] == 6.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_TO_SMALL_SECTOR_X(fX + WATER_X_OFFSET); + int32 y = WATER_TO_SMALL_SECTOR_Y(fY); + +#ifdef FIX_BUGS + if ( x < 0 || x >= MAX_SMALL_SECTORS ) return false; + if ( y < 0 || y >= MAX_SMALL_SECTORS ) return false; +#endif + + 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) - y*SMALL_SECTOR_SIZE + + WATER_UNSIGN_X(fX + WATER_X_OFFSET) - x*SMALL_SECTOR_SIZE ) + + * (TWOPI / SMALL_SECTOR_SIZE ) + fAngle + ); + + float fWindFactor = CWeather::WindClipped * 0.4f + 0.2f; + + *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_TO_SMALL_SECTOR_X(fX + WATER_X_OFFSET); + int32 y = WATER_TO_SMALL_SECTOR_Y(fY); + +#ifdef FIX_BUGS + if ( x < 0 || x >= MAX_SMALL_SECTORS ) return false; + if ( y < 0 || y >= MAX_SMALL_SECTORS ) return false; +#endif + + int8 nBlock = aWaterFineBlockList[x][y]; + + if ( nBlock == NO_WATER ) + return false; + + ASSERT( pfOutLevel != nil ); + *pfOutLevel = ms_aWaterZs[nBlock]; + + return true; +} + +float +CWaterLevel::GetWaterWavesOnly(short x, short y) +{ + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + + float fWindFactor = CWeather::WindClipped * 0.7f + 0.3f; + + float fWave = Sin( float(float(4 * y + 4 * x) * (TWOPI / SMALL_SECTOR_SIZE )) + fAngle ); + + return fWave * fWindFactor; +} + +CVector +CWaterLevel::GetWaterNormal(float fX, float fY) +{ + //TODO: BUG ? no x offset + + int32 x = WATER_TO_SMALL_SECTOR_X(fX); + int32 y = WATER_TO_SMALL_SECTOR_Y(fY); + + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + float fWindFactor = CWeather::WindClipped * 0.4f + 0.2f; + + float _fWave = (WATER_UNSIGN_Y(fY) - y*SMALL_SECTOR_SIZE + WATER_UNSIGN_X(fX) - x*SMALL_SECTOR_SIZE) + * (TWOPI / SMALL_SECTOR_SIZE ) + fAngle; + + CVector vA(1.0f, 0.0f, fWindFactor * (TWOPI / SMALL_SECTOR_SIZE ) * Cos(_fWave)); + CVector vB(0.0f, 1.0f, fWindFactor * (TWOPI / SMALL_SECTOR_SIZE ) * Cos(_fWave)); + + CVector norm = CrossProduct(vA, vB); + + norm.Normalise(); + + return norm; +} + + +inline float +_GetWaterDrawDist() +{ + if ( TheCamera.GetPosition().z < 15.0f ) return 1200.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() +{ +#ifdef DISABLE_WAVY_WATER + return 0.0; +#endif + 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 bool +_IsColideWithBlock(int32 x, int32 y, int32 &block) +{ + block = CWaterLevel::aWaterFineBlockList[x + 0][y + 0]; + if (block >= 0) + return true; + + block = CWaterLevel::aWaterFineBlockList[x + 0][y + 1]; + if (block >= 0) + { + block = CWaterLevel::aWaterFineBlockList[x + 0][y + 2]; + if (block >= 0) + return true; + } + + block = CWaterLevel::aWaterFineBlockList[x + 1][y + 0]; + if (block >= 0) + return true; + + block = CWaterLevel::aWaterFineBlockList[x + 1][y + 1]; + if (block >= 0) + { + block = CWaterLevel::aWaterFineBlockList[x + 1][y + 2]; + if (block >= 0) + return true; + } + + block = CWaterLevel::aWaterFineBlockList[x + 2][y + 0]; + if (block >= 0) + return true; + + block = CWaterLevel::aWaterFineBlockList[x + 2][y + 1]; + if (block >= 0) + { + block = CWaterLevel::aWaterFineBlockList[x + 2][y + 2]; + if (block >= 0) + return true; + } + + return false; +} + +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 + bool bUseCamEndX = false; + bool bUseCamStartY = false; + + bool bUseCamStartX = false; + bool bUseCamEndY = false; + + if ( !CGame::CanSeeWaterFromCurrArea() ) + return; + + _GetCamBounds(&bUseCamStartY, &bUseCamEndY, &bUseCamStartX, &bUseCamEndX); + + float fHugeSectorMaxRenderDist = _GetWaterDrawDist(); + float fHugeSectorMaxRenderDistSqr = SQR(fHugeSectorMaxRenderDist); + + float windAddUV = CWeather::WindClipped * 0.0005f + 0.0006f; + + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + + if ( !CTimer::GetIsPaused() ) + { + TEXTURE_ADDU += windAddUV; + TEXTURE_ADDV += windAddUV; + + _TEXTURE_MASK_ADDU += Sin(fAngle) * 0.0005f + 1.1f * windAddUV; + _TEXTURE_MASK_ADDV -= Cos(fAngle * 1.3f) * 0.0005f + 1.2f * windAddUV; + + _TEXTURE_WAKE_ADDU -= Sin(fAngle) * 0.0003f + windAddUV; + _TEXTURE_WAKE_ADDV += Cos(fAngle * 0.7f) * 0.0003f + windAddUV; + } + + if ( _TEXTURE_MASK_ADDU >= 1.0f ) + _TEXTURE_MASK_ADDU = 0.0f; + if ( _TEXTURE_MASK_ADDV >= 1.0f ) + _TEXTURE_MASK_ADDV = 0.0f; + + if ( _TEXTURE_WAKE_ADDU >= 1.0f ) + _TEXTURE_WAKE_ADDU = 0.0f; + if ( _TEXTURE_WAKE_ADDV >= 1.0f ) + _TEXTURE_WAKE_ADDV = 0.0f; + + if ( TEXTURE_ADDU >= 1.0f ) + TEXTURE_ADDU = 0.0f; + if ( TEXTURE_ADDV >= 1.0f ) + TEXTURE_ADDV = 0.0f; + +#ifdef PC_WATER + _fWaterZOffset = CWeather::WindClipped * 0.5f + 0.25f; +#endif + + RwRGBA color = { 0, 0, 0, 255 }; + + color.red = CTimeCycle::GetWaterRed(); + color.green = CTimeCycle::GetWaterGreen(); + color.blue = CTimeCycle::GetWaterBlue(); + +#ifndef PC_WATER + RwRGBA colorUnderwater = { 0, 0, 0, 255 }; + colorUnderwater.red = (uint32)(0.8f * (float)colorUnderwater.red); + colorUnderwater.green = (uint32)(0.8f * (float)colorUnderwater.green); + colorUnderwater.blue = (uint32)(0.8f * (float)colorUnderwater.blue); +#endif + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + +#ifndef PC_WATER + WavesCalculatedThisFrame = false; +#endif + + 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 + WATER_X_OFFSET); + int32 nEndX = WATER_TO_HUGE_SECTOR_X(camPos.x + fHugeSectorMaxRenderDist + WATER_X_OFFSET) + 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 + WATER_X_OFFSET); + if ( bUseCamEndX ) + nEndX = WATER_TO_HUGE_SECTOR_X(camPos.x + WATER_X_OFFSET); + 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) - WATER_X_OFFSET; + 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)) ) + { +#ifndef PC_WATER + WavesCalculatedThisFrame = true; +#endif + + + 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] ]; + + if ( fHugeSectorDistToCamSqr >= SQR(500.0f) ) + { + RenderOneFlatHugeWaterPoly(fX, fY, fZ, color); + } + else + { +#ifndef PC_WATER + if (m_bRenderSeaBed) + RenderOneSlopedUnderWaterPoly(fX, fY, fZ, colorUnderwater); +#endif + // see RenderTransparentWater() + ; + } + } + } + } + } + } + + /* + ----------- ---------------------- ---------------------- + | [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 + */ + + 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 - WATER_X_OFFSET; + float fY = WATER_SIGN_Y(float(y) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f; + + if ( !bUseCamStartY ) + { + 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 ( !bUseCamEndY ) + { + 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 - WATER_X_OFFSET; + float fX2 = WATER_SIGN_X(float(x) * EXTRAHUGE_SECTOR_SIZE) - 1280.0f + WATER_X_OFFSET; + 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.x, 0.0f), SectorRadius(EXTRAHUGE_SECTOR_SIZE)) ) + { + RenderOneFlatExtraHugeWaterPoly( + vecExtraHugeSectorCentre.x - EXTRAHUGE_SECTOR_SIZE/2, + vecExtraHugeSectorCentre.y - EXTRAHUGE_SECTOR_SIZE/2, + 0.0f, + color); + } + } + } + } + } + + RenderAndEmptyRenderBuffer(); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); + + if ( WavesCalculatedThisFrame ) + { + RenderSeaBirds(); + RenderShipsOnHorizon(); + CParticle::HandleShipsAtHorizonStuff(); + HandleBeachToysStuff(); + } + + if ( _bSeaLife ) + HandleSeaLifeForms(); + + DefinedState(); +} + + +void +CWaterLevel::RenderTransparentWater(void) +{ + bool bUseCamEndX = false; + bool bUseCamStartY = false; + + bool bUseCamStartX = false; + bool bUseCamEndY = false; + + _bSeaLife = false; + + if ( !CGame::CanSeeWaterFromCurrArea() ) + return; + + PUSH_RENDERGROUP("CWaterLevel::RenderTransparentWater"); + + float fWaterDrawDist = _GetWavyDrawDist(); + float fWaterDrawDistLarge = fWaterDrawDist + 90.0f; + float fWavySectorMaxRenderDistSqr = SQR(fWaterDrawDist); + + _GetCamBounds(&bUseCamStartY, &bUseCamEndY, &bUseCamStartX, &bUseCamEndX); + + float fHugeSectorMaxRenderDist = _GetWaterDrawDist(); + float fHugeSectorMaxRenderDistSqr = SQR(fHugeSectorMaxRenderDist); + + RenderBoatWakes(); + + RwRGBA color; + + color.red = CTimeCycle::GetWaterRed(); + color.green = CTimeCycle::GetWaterGreen(); + color.blue = CTimeCycle::GetWaterBlue(); + color.alpha = 255; + + RwRGBA colorTrans; + + colorTrans.red = CTimeCycle::GetWaterRed(); + colorTrans.green = CTimeCycle::GetWaterGreen(); + colorTrans.blue = CTimeCycle::GetWaterBlue(); + colorTrans.alpha = CTimeCycle::GetWaterAlpha(); + + TempBufferVerticesStored = 0; + TempBufferIndicesStored = 0; + +#ifndef PC_WATER + WavesCalculatedThisFrame = false; +#endif + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpWaterRaster); +#ifndef PC_WATER + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); +#endif + + CVector2D camPos(TheCamera.GetPosition().x, TheCamera.GetPosition().y); + + int32 nStartX = WATER_TO_HUGE_SECTOR_X(camPos.x - fHugeSectorMaxRenderDist + WATER_X_OFFSET); + int32 nEndX = WATER_TO_HUGE_SECTOR_X(camPos.x + fHugeSectorMaxRenderDist + WATER_X_OFFSET) + 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 + WATER_X_OFFSET); + if ( bUseCamEndX ) + nEndX = WATER_TO_HUGE_SECTOR_X(camPos.x + WATER_X_OFFSET); + 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) - WATER_X_OFFSET; + 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) ) + { + // see RenderWater() + ; + } + 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) - WATER_X_OFFSET; + 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)) ) + { + // Render four small(32x32) sectors, or one large(64x64). + + // + // [N] + // --------- + // |0x1|1x1| + // [W] --------- [E] + // |0x0|1x0| + // --------- + // [S] + // + + float fLargeSectorDrawDistSqr = SQR((fWaterDrawDistLarge + 16.0f)); + + if ( fLargeSectorDistToCamSqr < fLargeSectorDrawDistSqr ) + { + _bSeaLife = true; + + 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, colorTrans); + else + RenderOneFlatSmallWaterPolyBlended(fSmallX, fSmallY, fZ, camPos.x, camPos.y, color, colorTrans, fWaterDrawDist); + } + + // 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, colorTrans); + else + RenderOneFlatSmallWaterPolyBlended(fSmallX, fSmallY, fZ, camPos.x, camPos.y, color, colorTrans, fWaterDrawDist); + } + + // 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, colorTrans); + else + RenderOneFlatSmallWaterPolyBlended(fSmallX, fSmallY, fZ, camPos.x, camPos.y, color, colorTrans, fWaterDrawDist); + } + + //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, colorTrans); + else + RenderOneFlatSmallWaterPolyBlended(fSmallX, fSmallY, fZ, camPos.x, camPos.y, color, colorTrans, fWaterDrawDist); + } + } + else + { + float fZ; + + fZ = ms_aWaterZs[ aWaterBlockList[x2][y2] ]; + + RenderOneFlatLargeWaterPoly(fLargeX, fLargeY, fZ, color); + } + } + } + } + } + } + } + } + } + } + } + } + + RenderAndEmptyRenderBuffer(); + +#ifdef PC_WATER + if ( MaskCalculatedThisFrame + && (m_nRenderWaterLayers == 0 || m_nRenderWaterLayers == 2 || m_nRenderWaterLayers == 3) ) + { + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + + pos.x = PreCalculatedMaskPosn.x; + pos.y = PreCalculatedMaskPosn.y; + pos.z = PreCalculatedMaskPosn.z; + + RpMatFXMaterialSetEnvMapFrame(RpGeometryGetMaterial(RpAtomicGetGeometry(ms_pMaskAtomic), 0), + RwCameraGetFrame(RwCameraGetCurrentCamera())); + + RwFrameTranslate(RpAtomicGetFrame(ms_pMaskAtomic), &pos, rwCOMBINEREPLACE); + + RpAtomicRender(ms_pMaskAtomic); + } +#else + if (!CCullZones::WaterFudge()) + { + int32 signX = 0; + int32 signY = 0; + + float fCamX = camPos.x - SMALL_SECTOR_SIZE; + float fCamY = camPos.y - SMALL_SECTOR_SIZE; + + if (TheCamera.GetForward().x > 0.3f) + signX = 1; + else if (TheCamera.GetForward().x < -0.3f) + signX = -1; + + fCamX += 0.3f * (float)signX * float(SMALL_SECTOR_SIZE * 2.0f); // 19.2f + + if (TheCamera.GetForward().y > 0.3f) + signY = 1; + else if (TheCamera.GetForward().y < -0.3f) + signY = -1; + + fCamY += 0.3f * (float)signY * float(SMALL_SECTOR_SIZE * 2.0f); // 19.2f + + int32 nBlock; + + int32 BlockX = WATER_TO_SMALL_SECTOR_X(fCamX + WATER_X_OFFSET) + 1; + int32 BlockY = WATER_TO_SMALL_SECTOR_Y(fCamY) + 1; + + if (_IsColideWithBlock(BlockX, BlockY, nBlock)) + { + if (m_nRenderWaterLayers != 1 && m_nRenderWaterLayers != 6) + { + float fMaskX = Floor(fCamX / 2.0f) * 2.0f; + float fMaskY = Floor(fCamY / 2.0f) * 2.0f; + float fWaterZ = CWaterLevel::ms_aWaterZs[nBlock]; + float fSectorX = WATER_FROM_SMALL_SECTOR_X(BlockX) - WATER_X_OFFSET; + float fSectorY = WATER_FROM_SMALL_SECTOR_Y(BlockY); + + RenderWavyMask(fMaskX, fMaskY, fWaterZ, + fSectorX, fSectorY, + signX, signY, colorTrans); + } + } + } + + DefinedState(); +#endif + + 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 - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 0], color); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + SMALL_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 1.0f); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 1], color); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + SMALL_SECTOR_SIZE, fY + SMALL_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 1.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 1.0f); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 2], color); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + SMALL_SECTOR_SIZE, fY, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 1.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 3], color); + + + 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 - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 0], color); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + LARGE_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 2.0f); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 1], color); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + LARGE_SECTOR_SIZE, fY + LARGE_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 2.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 2.0f); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 2], color); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + LARGE_SECTOR_SIZE, fY, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 2.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 3], color); + + + 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; + + RwRGBA c; + + c.red = color.red; + c.green = color.green; + c.blue = color.blue; + c.alpha = 255; + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 0], c); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + HUGE_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 4.0f); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 1], c); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + HUGE_SECTOR_SIZE, fY + HUGE_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 4.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 4.0f); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 2], c); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + HUGE_SECTOR_SIZE, fY, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 4.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 3], c); + + + 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; + + RwRGBA c; + + c.red = color.red; + c.green = color.green; + c.blue = color.blue; + c.alpha = 255; + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 0], c); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + EXTRAHUGE_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 8.0f); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 1], c); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + EXTRAHUGE_SECTOR_SIZE, fY + EXTRAHUGE_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDU + 8.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], TEXTURE_ADDV + 8.0f); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 2], c); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + EXTRAHUGE_SECTOR_SIZE, fY, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 8.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSet_RGBA(&TempBufferRenderVertices[vidx + 3], c); + + + 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 bDontRender) +{ + CVector vecSectorPos(fX + (SMALL_SECTOR_SIZE/2), fY + (SMALL_SECTOR_SIZE/2), fZ + 2.0f); + + if ( COcclusion::IsAABoxOccluded(vecSectorPos, SMALL_SECTOR_SIZE, SMALL_SECTOR_SIZE, 4.0f) ) + return; + +#ifdef PC_WATER + RequireWavySector = true; +#else + if (!WavesCalculatedThisFrame) + { + WavesCalculatedThisFrame = true; + + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + + RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic); + RwTexCoords *wavyTexCoords = RpGeometryGetVertexTexCoords(wavyGeometry, rwTEXTURECOORDINATEINDEX0); + RpMorphTarget *wavyMorph = RpGeometryGetMorphTarget(wavyGeometry, 0); + RwRGBA *wavyPreLight = RpGeometryGetPreLightColors(wavyGeometry); + RwV3d *wavyMorphVerts = RpMorphTargetGetVertices(wavyMorph); + RwV3d *wavyMorphNormals = RpMorphTargetGetVertexNormals(wavyMorph); + + RpGeometryLock(wavyGeometry, rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKNORMALS | rpGEOMETRYLOCKPRELIGHT | rpGEOMETRYLOCKTEXCOORDS); + + RwMatrix *camMat = RwFrameGetLTM(RwCameraGetFrame(RwCameraGetCurrentCamera())); //or curWorld + + float randomDampInv2 = (1.0f - fRandomDamp) * 2.0f; + + float move = 1.0f / 16.0f; + float randomMove = 1.0f / (16.0f * fRandomMoveDiv); + + float vertMul = 0.5f; + + float wind = CWeather::WindClipped * 0.4f + 0.2f; + float waveWind = CWeather::WindClipped * fWave2Ampl + 0.05f; + + float waveA = (TWOPI / 16.0f) + * ((fNormalDirectionScalar1 * Abs(camMat->at.x + camMat->at.y) + fNormMult) * (CWeather::WindClipped * 0.4f + 0.2f)); + + float waveB = TWOPI / (16.0f * fWave2NormScale) + * ((fNormalDirectionScalar2 * Abs(camMat->at.y - camMat->at.x) + fNormMultB) * (CWeather::WindClipped * 0.2f + 0.1f)); + + CVector vA(1.0f, 0.0f, 0.0f); + CVector vB(0.0f, 1.0f, 0.0f); + + for ( int32 i = 0; i < 17; i++ ) + { + for ( int32 j = 0; j < 17; j++ ) + { + wavyTexCoords->u = float(i) * move + TEXTURE_ADDV; + wavyTexCoords->v = float(j) * move + TEXTURE_ADDU; + + RwRGBAAssign(wavyPreLight, &color); + + if (i > 0 && i < 16 && j > 0 && j < 16) + { + wavyMorphVerts->x += CGeneral::GetRandomNumberInRange(-1.0f, 1.0f) * randomMove; + wavyMorphVerts->x *= fRandomDamp; + wavyMorphVerts->x += float(i) * randomDampInv2; + + wavyMorphVerts->y += CGeneral::GetRandomNumberInRange(-1.0f, 1.0f) * randomMove; + wavyMorphVerts->y *= fRandomDamp; + wavyMorphVerts->y += float(j) * randomDampInv2; + } + + float morphVertXHalf = (i == 16) ? 0.0f : vertMul * wavyMorphVerts->x; + float morphVertYHalf = (j == 16) ? 0.0f : vertMul * wavyMorphVerts->y; + + float waveMulA = (morphVertYHalf + morphVertXHalf) * (TWOPI / 16.0f) + fAngle; + float waveMulB = (morphVertYHalf - morphVertXHalf) * (TWOPI / (16.0f * fWave2InvLength)) + fAngle; + + wavyMorphVerts->z = wind * Sin(waveMulA) + waveWind * Sin(waveMulB); + + vA.z = (waveA * Cos(waveMulA)) - (waveB * Cos(waveMulB)); + vB.z = (waveA * Cos(waveMulA)) + (waveB * Cos(waveMulB)); + + CVector norm = CrossProduct(vA, vB); + norm.Normalise(); + + wavyMorphNormals->x = norm.x; + wavyMorphNormals->y = norm.y; + wavyMorphNormals->z = norm.z; + + ++wavyPreLight; + ++wavyTexCoords; + + ++wavyMorphVerts; + ++wavyMorphNormals; + } + } + + RpGeometryUnlock(wavyGeometry); + } + + float fCentreX = fX + (SMALL_SECTOR_SIZE / 2); + float fCentreY = fY + (SMALL_SECTOR_SIZE / 2); +#endif + +#ifdef PC_WATER + if ( WavesCalculatedThisFrame ) +#endif + { + if (bDontRender == false + && m_nRenderWaterLayers != 2 + && m_nRenderWaterLayers != 4 + && m_nRenderWaterLayers != 6 ) + { + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + + pos.x = fX; + pos.y = fY; + pos.z = fZ; + + RwFrameTranslate(RpAtomicGetFrame(ms_pWavyAtomic), &pos, rwCOMBINEREPLACE); + + RpAtomicRender(ms_pWavyAtomic); + } + } +} + +int16 +_RoundValue(int32 v) +{ + int16 result = v; + + while ( result < 0 ) result += 16; + while ( result > 16 ) result -= 16; + + return result; +} + +void +CWaterLevel::RenderWavyMask(float fX, float fY, float fZ, + float fSectorX, float fSectorY, +#ifdef PC_WATER + float fCamPosX, float fCamPosY, + float fCamDirX, float fCamDirY, RwRGBA const&color) +#else + int32 nCamDirX, int32 nCamDirY, RwRGBA const&color) +#endif +{ +#ifndef PC_WATER + bool bRender = true; + if (m_nRenderWaterLayers != 0 && m_nRenderWaterLayers != 2 && m_nRenderWaterLayers != 3) + bRender = false; +#endif + CVector vecSectorPos(fX + (LARGE_SECTOR_SIZE/2), fY + (LARGE_SECTOR_SIZE/2), fZ + 2.0f); + + if ( COcclusion::IsAABoxOccluded(vecSectorPos, LARGE_SECTOR_SIZE, LARGE_SECTOR_SIZE, 4.0f) ) + return; + +#ifndef PC_WATER + float fUOffset = fX - (MAX_LARGE_SECTORS * (int32)Floor(fX / MAX_LARGE_SECTORS)); + float fVOffset = fY - (MAX_LARGE_SECTORS * (int32)Floor(fY / MAX_LARGE_SECTORS)); + + int32 nSecsX = (int32)((fX - fSectorX) / 2.0f); + int32 nSecsY = (int32)((fY - fSectorY) / 2.0f); +#endif + + RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic); + RpMorphTarget *wavyMorph = RpGeometryGetMorphTarget(wavyGeometry, 0); + RwV3d *wavyMorphVerts = RpMorphTargetGetVertices(wavyMorph); + RwV3d *wavyMorphNormals = RpMorphTargetGetVertexNormals(wavyMorph); + + RpGeometry *maskGeometry = RpAtomicGetGeometry(ms_pMaskAtomic); + RwTexCoords *maskTexCoords = RpGeometryGetVertexTexCoords(maskGeometry, rwTEXTURECOORDINATEINDEX0); + RwRGBA *maskPreLight = RpGeometryGetPreLightColors(maskGeometry); + RpMorphTarget *maskMorph = RpGeometryGetMorphTarget(maskGeometry, 0); + RwV3d *maskMorphVerts = RpMorphTargetGetVertices(maskMorph); + RwV3d *maskMorphNormals = RpMorphTargetGetVertexNormals(maskMorph); + + RpGeometryLock(maskGeometry, rpGEOMETRYLOCKVERTICES|rpGEOMETRYLOCKNORMALS|rpGEOMETRYLOCKPRELIGHT|rpGEOMETRYLOCKTEXCOORDS); + +#ifndef PC_WATER + RpMaterial *maskMat = RpGeometryGetMaterial(maskGeometry, 0); + RpMatFXMaterialSetEnvMapFrame(maskMat, RwCameraGetFrame(RwCameraGetCurrentCamera())); + RpMatFXMaterialSetEnvMapCoefficient(maskMat, fEnvScale); + RpMatFXMaterialSetEnvMapFrameBufferAlpha(maskMat, TRUE); +#endif + +#ifndef PC_WATER + float fMinSparkZ = (CWeather::WindClipped * fWave2Ampl + 0.05f + + CWeather::WindClipped * 0.4f + 0.2) * (1.0f - 0.04f * CWeather::SunGlare); + + int32 randval = CGeneral::GetRandomNumber(); + + float fUVStep = 0.125f; + float f27 = 2.0f; + + float fMinU = (fUOffset / 16.0f) + _TEXTURE_MASK_ADDU; + float fMinV = (fVOffset / 16.0f) + _TEXTURE_MASK_ADDV; + + float fAlphaMul = ((float)color.alpha * 0.4f) / 16.0f; + + float fXOffset = 16.0f; + if (nCamDirX > 0) + fXOffset = 6.4f; + else if (nCamDirX < 0) + fXOffset = 25.6f; + + float fYOffset = 16.0f; + if (nCamDirY > 0) + fYOffset = 6.4f; + else if (nCamDirY < 0) + fYOffset = 25.6f; + + int16 nX = _RoundValue(nSecsX - 1); + int16 nY = _RoundValue(nSecsY - 1); +#else + float fMinSparkZ = (fWave2Ampl * CWeather::WindClipped + 0.05f + + 0.4f * CWeather::WindClipped + 0.2) * (1.0f - 0.02f * CWeather::SunGlare); + + int32 randval = CGeneral::GetRandomNumber() & 255; + + int16 nX = _RoundValue((int32)((fX - fSectorX) * 0.5f) - 1); + int16 nY = _RoundValue((int32)((fY - fSectorY) * 0.5f) - 1); +#endif + int16 idxX = nX; + + for ( int32 i = 0; i < 17; i++ ) + { + int16 idxY = nY; + + if ( ++idxX > 16 ) + idxX -= 16; + + for ( int32 j = 0; j < 17; j++ ) + { + if ( ++idxY > 16 ) + idxY -= 16; + + const int32 a = (0*16); + const int32 b = (1*16); + const int32 c = (33*16); + const int32 d = (34*16); + + int32 base = (i*33+j); + +#ifndef PC_WATER + maskTexCoords[base + a].u = fMinU + ((float)i * fUVStep); + maskTexCoords[base + a].v = fMinV + ((float)j * fUVStep); + + maskTexCoords[base + b].u = maskTexCoords[base + a].u; + maskTexCoords[base + b].v = maskTexCoords[base + a].v + (16.0f * fUVStep); + + maskTexCoords[base + c].u = maskTexCoords[base + a].u + (16.0f * fUVStep); + maskTexCoords[base + c].v = maskTexCoords[base + a].v; + + maskTexCoords[base + d].u = maskTexCoords[base + a].u + (16.0f * fUVStep); + maskTexCoords[base + d].v = maskTexCoords[base + a].v + (16.0f * fUVStep); +#else + maskTexCoords[base+a].v = float(j) / SMALL_SECTOR_SIZE + ((fCamPosY - fY) / 64); + maskTexCoords[base+c].v = maskTexCoords[base+a].v; + maskTexCoords[base+d].v = maskTexCoords[base+a].v + 0.5f; + maskTexCoords[base+b].v = maskTexCoords[base+d].v; + + maskTexCoords[base+a].u = float(i) / SMALL_SECTOR_SIZE + ((fCamPosX - fX) / 64); + maskTexCoords[base+b].u = maskTexCoords[base+a].u; + maskTexCoords[base+d].u = maskTexCoords[base+a].u + 0.5f; + maskTexCoords[base+c].u = maskTexCoords[base+d].u; +#endif + + maskMorphVerts[base+a].x = (wavyMorphVerts[idxY + (17 * idxX)].x - (float)idxX * 2.0f) + (float(i) * 2.0f); + maskMorphVerts[base+b].x = maskMorphVerts[base+a].x; + maskMorphVerts[base+c].x = maskMorphVerts[base+a].x + SMALL_SECTOR_SIZE; + maskMorphVerts[base+d].x = maskMorphVerts[base+c].x; + + maskMorphVerts[base+a].y = (wavyMorphVerts[idxY + (17 * idxX)].y - (float)idxY * 2.0f) + (float(j) * 2.0f); + maskMorphVerts[base+c].y = maskMorphVerts[base+a].y; + maskMorphVerts[base+b].y = maskMorphVerts[base+a].y + SMALL_SECTOR_SIZE; + maskMorphVerts[base+d].y = maskMorphVerts[base+b].y; + + maskMorphVerts[base+a].z = wavyMorphVerts[idxY + (17 * idxX)].z; + maskMorphVerts[base+d].z = maskMorphVerts[base+a].z; + maskMorphVerts[base+c].z = maskMorphVerts[base+d].z; + maskMorphVerts[base+b].z = maskMorphVerts[base+c].z; + +#ifndef PC_WATER + if (maskMorphVerts[base].z >= fMinSparkZ) +#else + if ( maskMorphVerts[base].z > fMinSparkZ ) +#endif + { + switch ( (i + j + randval) & 3 ) + { + case 0: + { + CVector vecPos + ( + fX + maskMorphVerts[base+a].x, + fY + maskMorphVerts[base+a].y, + fZ + maskMorphVerts[base+a].z + 0.12f + ); + + vecPos -= 0.05f * TheCamera.GetForward(); + + CParticle::AddParticle(PARTICLE_WATER_SPARK, + vecPos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.0f, + 15, + CGeneral::GetRandomNumberInRange(-90, 90), + 0, + 0); + } + break; + + case 1: + { + CVector vecPos + ( + fX + maskMorphVerts[base+c].x, + fY + maskMorphVerts[base+c].y, + fZ + maskMorphVerts[base+c].z + 0.12f + ); + + vecPos -= 0.05f * TheCamera.GetForward(); + + CParticle::AddParticle(PARTICLE_WATER_SPARK, + vecPos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.0f, + 15, + CGeneral::GetRandomNumberInRange(-90, 90), + 0, + 0); + } + break; + + case 2: + { + CVector vecPos + ( + fX + maskMorphVerts[base+b].x, + fY + maskMorphVerts[base+b].y, + fZ + maskMorphVerts[base+b].z + 0.12f + ); + + vecPos -= 0.05f * TheCamera.GetForward(); + + CParticle::AddParticle(PARTICLE_WATER_SPARK, + vecPos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.0f, + 15, + CGeneral::GetRandomNumberInRange(-90, 90), + 0, + 0); + } + break; + + case 3: + { + CVector vecPos + ( + fX + maskMorphVerts[base+d].x, + fY + maskMorphVerts[base+d].y, + fZ + maskMorphVerts[base+d].z + 0.12f + ); + + vecPos -= 0.05f * TheCamera.GetForward(); + + CParticle::AddParticle(PARTICLE_WATER_SPARK, + vecPos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.0f, + 15, + CGeneral::GetRandomNumberInRange(-90, 90), + 0, + 0); + } + break; + } + } + + maskMorphNormals[base+a].x = wavyMorphNormals[idxY + (17 * idxX)].x; + maskMorphNormals[base+a].y = wavyMorphNormals[idxY + (17 * idxX)].y; + maskMorphNormals[base+a].z = wavyMorphNormals[idxY + (17 * idxX)].z; + + maskMorphNormals[base+d].x = maskMorphNormals[base+a].x; + maskMorphNormals[base+d].y = maskMorphNormals[base+a].y; + maskMorphNormals[base+d].z = maskMorphNormals[base+a].z; + + maskMorphNormals[base+c].x = maskMorphNormals[base+d].x; + maskMorphNormals[base+c].y = maskMorphNormals[base+d].y; + maskMorphNormals[base+c].z = maskMorphNormals[base+d].z; + + maskMorphNormals[base+b].x = maskMorphNormals[base+c].x; + maskMorphNormals[base+b].y = maskMorphNormals[base+c].y; + maskMorphNormals[base+b].z = maskMorphNormals[base+c].z; + + maskPreLight[base+a].red = color.red; + maskPreLight[base+a].green = color.green; + maskPreLight[base+a].blue = color.blue; + maskPreLight[base+a].alpha = color.alpha; + + maskPreLight[base+d].red = maskPreLight[base+a].red; + maskPreLight[base+d].green = maskPreLight[base+a].green; + maskPreLight[base+d].blue = maskPreLight[base+a].blue; + maskPreLight[base+d].alpha = maskPreLight[base+a].alpha; + + maskPreLight[base+c].red = maskPreLight[base+d].red; + maskPreLight[base+c].green = maskPreLight[base+d].green; + maskPreLight[base+c].blue = maskPreLight[base+d].blue; + maskPreLight[base+c].alpha = maskPreLight[base+d].alpha; + + maskPreLight[base+b].red = maskPreLight[base+c].red; + maskPreLight[base+b].green = maskPreLight[base+c].green; + maskPreLight[base+b].blue = maskPreLight[base+c].blue; + maskPreLight[base+b].alpha = maskPreLight[base+c].alpha; + +#ifndef PC_WATER + maskPreLight[base + a].alpha = Max(0, (int32)((float)color.alpha - (fAlphaMul * (Abs((float)i - fXOffset) + Abs((float)j - fYOffset))))); + maskPreLight[base + b].alpha = Max(0, (int32)((float)color.alpha - (fAlphaMul * (Abs((float)i - fXOffset) + Abs(16.0f + (float)j - fYOffset))))); + maskPreLight[base + c].alpha = Max(0, (int32)((float)color.alpha - (fAlphaMul * (Abs(16.0f + (float)i - fXOffset) + Abs((float)j - fYOffset))))); + maskPreLight[base + d].alpha = Max(0, (int32)((float)color.alpha - (fAlphaMul * (Abs(16.0f + (float)i - fXOffset) + Abs(16.0f + (float)j - fYOffset))))); +#endif + } + } + + RpGeometryUnlock(maskGeometry); + +#ifndef PC_WATER + { + RwV3d pos = { 0.0f, 0.0f, 0.0f }; + + pos.x = fX; + pos.y = fY; + pos.z = fZ + 0.05f; + + RwFrameTranslate(RpAtomicGetFrame(ms_pMaskAtomic), &pos, rwCOMBINEREPLACE); + + if (bRender) + { +#ifdef PS2 + RpSkyTexCacheFlush(); +#endif + RpAtomicRender(ms_pMaskAtomic); + } + } +#endif +} + +#ifdef PC_WATER +void +CWaterLevel::PreCalcWaterGeometry(void) +{ + if ( !RequireWavySector ) + { + WavesCalculatedThisFrame = false; + MaskCalculatedThisFrame = false; + return; + } + + RequireWavySector = false; + WavesCalculatedThisFrame = true; + + RwRGBA color; + + color.red = CTimeCycle::GetWaterRed(); + color.green = CTimeCycle::GetWaterGreen(); + color.blue = CTimeCycle::GetWaterBlue(); + color.alpha = CTimeCycle::GetWaterAlpha(); + + PreCalcWavySector(color); + + if ( CCullZones::WaterFudge() ) + { + MaskCalculatedThisFrame = false; + return; + } + + CVector CamFwdDir = TheCamera.GetForward(); + CamFwdDir.z = 0.0f; + CamFwdDir.Normalise(); + + float fCamX = TheCamera.GetPosition().x - SMALL_SECTOR_SIZE; + float fCamY = TheCamera.GetPosition().y - SMALL_SECTOR_SIZE; + + //1.4144272f; 1.4144f; + float signX = CamFwdDir.x * 1.4144272f; + float signY = CamFwdDir.y * 1.4144272f; + + signX = Clamp(signX, -1.0f, 1.0f); + fCamX += 0.4f * signX * float(SMALL_SECTOR_SIZE * 2.0f); + + signY = Clamp(signY, -1.0f, 1.0f); + fCamY += 0.4f * signY * float(SMALL_SECTOR_SIZE * 2.0f); + + int32 nBlock; + + int32 BlockX = WATER_TO_SMALL_SECTOR_X(fCamX + WATER_X_OFFSET) + 1; + int32 BlockY = WATER_TO_SMALL_SECTOR_Y(fCamY ) + 1; + + ASSERT( BlockX >= 0 && BlockX < MAX_SMALL_SECTORS ); + ASSERT( BlockY >= 0 && BlockY < MAX_SMALL_SECTORS ); + + if ( _IsColideWithBlock(BlockX, BlockY, nBlock) ) + { + float fMaskX = Floor(fCamX / 2.0f) * 2.0f; + float fMaskY = Floor(fCamY / 2.0f) * 2.0f; + + float fSectorX = WATER_FROM_SMALL_SECTOR_X(BlockX) - WATER_X_OFFSET; + float fSectorY = WATER_FROM_SMALL_SECTOR_Y(BlockY); + + if ( PreCalcWavyMask( fMaskX, fMaskY, ms_aWaterZs[nBlock], + fSectorX, fSectorY, fCamX, fCamY, CamFwdDir.x, CamFwdDir.y, color ) ) + { + PreCalculatedMaskPosn.x = fMaskX; + PreCalculatedMaskPosn.y = fMaskY; + PreCalculatedMaskPosn.z = ms_aWaterZs[nBlock] + 0.05f; + + MaskCalculatedThisFrame = true; + } + else + MaskCalculatedThisFrame = false; + } + else + MaskCalculatedThisFrame = false; +} + +bool +CWaterLevel::PreCalcWavySector(RwRGBA const &color) +{ + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + + RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic); + + RwTexCoords *wavyTexCoords = RpGeometryGetVertexTexCoords(wavyGeometry, rwTEXTURECOORDINATEINDEX0); + RpMorphTarget *wavyMorph = RpGeometryGetMorphTarget(wavyGeometry, 0); + + RwRGBA *wavyPreLight = RpGeometryGetPreLightColors(wavyGeometry); + RwV3d *wavyMorphVerts = RpMorphTargetGetVertices(wavyMorph); + RwV3d *wavyMorphNormals = RpMorphTargetGetVertexNormals(wavyMorph); + + if ( !m_bRenderSeaBed ) + RpGeometryLock(wavyGeometry, rpGEOMETRYLOCKVERTICES + |rpGEOMETRYLOCKNORMALS + |rpGEOMETRYLOCKPRELIGHT + |rpGEOMETRYLOCKTEXCOORDS); + + CVector camPosUp = TheCamera.GetForward(); + + float randomDampInv2 = (1.0f - fRandomDamp) * 2.0f; + + float randomMove = 1.0f / (16.0f * fRandomMoveDiv); + + float wind = CWeather::WindClipped * 0.4f + 0.2f; + float waveWind = CWeather::WindClipped * fWave2Ampl + 0.05f; + + float waveA = (TWOPI / 16.0f) + * ((CWeather::WindClipped * 0.4f + 0.2f) * (fNormalDirectionScalar1 * Abs(camPosUp.x + camPosUp.y) + fNormMult)); + + float waveB = TWOPI / (16.0f * fWave2NormScale) + * ((CWeather::WindClipped * 0.2f + 0.1f) * (fNormalDirectionScalar2 * Abs(camPosUp.y - camPosUp.x) + fNormMultB)); + + + CVector vA(1.0f, 0.0f, 0.0f); + CVector vB(0.0f, 1.0f, 0.0f); + + for ( int32 i = 0; i < 17; i++ ) + { + for ( int32 j = 0; j < 17; j++ ) + { + wavyTexCoords->u = (float(i) / 16.0f) + TEXTURE_ADDV; + wavyTexCoords->v = (float(j) / 16.0f) + TEXTURE_ADDU; + + RwRGBAAssign(wavyPreLight, &color); + + if ( i > 0 && i < 16 && j > 0 && j < 16 ) + { + wavyMorphVerts->x += CGeneral::GetRandomNumberInRange(-1.0f, 1.0f) * randomMove; + wavyMorphVerts->x *= fRandomDamp; + wavyMorphVerts->x += float(i) * randomDampInv2; + + wavyMorphVerts->y += CGeneral::GetRandomNumberInRange(-1.0f, 1.0f) * randomMove; + wavyMorphVerts->y *= fRandomDamp; + wavyMorphVerts->y += float(j) * randomDampInv2; + } + + float morphVertXHalf = ( i == 16 ) ? 0.0f : 0.5f * wavyMorphVerts->x; + float morphVertYHalf = ( j == 16 ) ? 0.0f : 0.5f * wavyMorphVerts->y; + + float waveMulA = (morphVertYHalf + morphVertXHalf) * (TWOPI / 16.0f) + fAngle; + float waveMulB = (morphVertYHalf - morphVertXHalf) * (TWOPI / (16.0f * fWave2InvLength)) + fAngle; + + wavyMorphVerts->z = wind * Sin(waveMulA) + waveWind * Sin(waveMulB); + + vA.z = (waveA * Cos(waveMulA)) - (waveB * Cos(waveMulB)); + vB.z = (waveA * Cos(waveMulA)) + (waveB * Cos(waveMulB)); + + CVector norm = CrossProduct(vA, vB); + norm.Normalise(); + + wavyMorphNormals->x = norm.x; + wavyMorphNormals->y = norm.y; + wavyMorphNormals->z = norm.z; + + ++wavyPreLight; + ++wavyTexCoords; + + ++wavyMorphVerts; + ++wavyMorphNormals; + } + } + + RpGeometryUnlock(wavyGeometry); + + return true; +} + +bool +CWaterLevel::PreCalcWavyMask(float fX, float fY, float fZ, + float fSectorX, float fSectorY, + float fCamPosX, float fCamPosY, + float fCamDirX, float fCamDirY, + RwRGBA const&color) +{ + CVector vecSectorPos(fX + (MAX_LARGE_SECTORS/2), fY + (MAX_LARGE_SECTORS/2), fZ + 2.0f); + + if ( COcclusion::IsAABoxOccluded(vecSectorPos, MAX_LARGE_SECTORS, MAX_LARGE_SECTORS, 4.0f) ) + return false; + + Floor(fX / MAX_LARGE_SECTORS); + Floor(fY / MAX_LARGE_SECTORS); + + RpGeometry *wavyGeometry = RpAtomicGetGeometry(ms_pWavyAtomic); + RpMorphTarget *wavyMorph = RpGeometryGetMorphTarget(wavyGeometry, 0); + RwV3d *wavyMorphVerts = RpMorphTargetGetVertices(wavyMorph); + RwV3d *wavyMorphNormals = RpMorphTargetGetVertexNormals(wavyMorph); + + RpGeometry *maskGeometry = RpAtomicGetGeometry(ms_pMaskAtomic); + RwTexCoords *maskTexCoords = RpGeometryGetVertexTexCoords(maskGeometry, rwTEXTURECOORDINATEINDEX0); + RwRGBA *maskPreLight = RpGeometryGetPreLightColors(maskGeometry); + RpMorphTarget *maskMorph = RpGeometryGetMorphTarget(maskGeometry, 0); + RwV3d *maskMorphVerts = RpMorphTargetGetVertices(maskMorph); + RwV3d *maskMorphNormals = RpMorphTargetGetVertexNormals(maskMorph); + + if ( !m_bRenderSeaBed ) + RpGeometryLock(maskGeometry, rpGEOMETRYLOCKVERTICES | rpGEOMETRYLOCKNORMALS | rpGEOMETRYLOCKPRELIGHT | rpGEOMETRYLOCKTEXCOORDS); + + + float fMinSparkZ = (fWave2Ampl * CWeather::WindClipped + 0.05f + + 0.4f * CWeather::WindClipped + 0.2) * (1.0f - 0.02f * CWeather::SunGlare); + + int32 randval = CGeneral::GetRandomNumber() & 255; + + int16 nX = _RoundValue((int32)((fX - fSectorX) * 0.5f) - 1); + int16 nY = _RoundValue((int32)((fY - fSectorY) * 0.5f) - 1); + + int16 idxX = nX; + + for ( int32 i = 0; i < 17; i++ ) + { + int16 idxY = nY; + + if ( ++idxX > 16 ) + idxX -= 16; + + for ( int32 j = 0; j < 17; j++ ) + { + if ( ++idxY > 16 ) + idxY -= 16; + + const int32 a = (0*16); + const int32 b = (1*16); + const int32 c = (33*16); + const int32 d = (34*16); + + int32 base = (i*33+j); + + maskTexCoords[base+a].v = float(j) / 32 + ((fCamPosY - fY) / 64); + maskTexCoords[base+c].v = maskTexCoords[base+a].v; + maskTexCoords[base+d].v = maskTexCoords[base+a].v + 0.5f; + maskTexCoords[base+b].v = maskTexCoords[base+d].v; + + maskTexCoords[base+a].u = float(i) / 32 + ((fCamPosX - fX) / 64); + maskTexCoords[base+b].u = maskTexCoords[base+a].u; + maskTexCoords[base+d].u = maskTexCoords[base+a].u + 0.5f; + maskTexCoords[base+c].u = maskTexCoords[base+d].u; + + maskMorphVerts[base+a].x = (wavyMorphVerts[idxY + (17 * idxX)].x - (float)idxX * 2.0f) + (float(i) * 2.0f); + maskMorphVerts[base+b].x = maskMorphVerts[base+a].x; + maskMorphVerts[base+c].x = maskMorphVerts[base+a].x + SMALL_SECTOR_SIZE; + maskMorphVerts[base+d].x = maskMorphVerts[base+c].x; + + maskMorphVerts[base+a].y = (wavyMorphVerts[idxY + (17 * idxX)].y - (float)idxY * 2.0f) + (float(j) * 2.0f); + maskMorphVerts[base+c].y = maskMorphVerts[base+a].y; + maskMorphVerts[base+b].y = maskMorphVerts[base+a].y + SMALL_SECTOR_SIZE; + maskMorphVerts[base+d].y = maskMorphVerts[base+b].y; + + maskMorphVerts[base+a].z = wavyMorphVerts[idxY + (17 * idxX)].z; + maskMorphVerts[base+d].z = maskMorphVerts[base+a].z; + maskMorphVerts[base+c].z = maskMorphVerts[base+d].z; + maskMorphVerts[base+b].z = maskMorphVerts[base+c].z; + + if ( maskMorphVerts[base].z > fMinSparkZ ) + { + switch ( (i + j + randval) & 3 ) + { + case 0: + { + CVector vecPos + ( + fX + maskMorphVerts[base+a].x, + fY + maskMorphVerts[base+a].y, + fZ + maskMorphVerts[base+a].z + 0.12f + ); + + vecPos -= 0.05f * TheCamera.GetForward(); + + CParticle::AddParticle(PARTICLE_WATER_SPARK, + vecPos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.0f, + 15, + CGeneral::GetRandomNumberInRange(-90, 90), + 0, + 0); + } + break; + + case 1: + { + CVector vecPos + ( + fX + maskMorphVerts[base+c].x, + fY + maskMorphVerts[base+c].y, + fZ + maskMorphVerts[base+c].z + 0.12f + ); + + vecPos -= 0.05f * TheCamera.GetForward(); + + CParticle::AddParticle(PARTICLE_WATER_SPARK, + vecPos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.0f, + 15, + CGeneral::GetRandomNumberInRange(-90, 90), + 0, + 0); + } + break; + + case 2: + { + CVector vecPos + ( + fX + maskMorphVerts[base+b].x, + fY + maskMorphVerts[base+b].y, + fZ + maskMorphVerts[base+b].z + 0.12f + ); + + vecPos -= 0.05f * TheCamera.GetForward(); + + CParticle::AddParticle(PARTICLE_WATER_SPARK, + vecPos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.0f, + 15, + CGeneral::GetRandomNumberInRange(-90, 90), + 0, + 0); + } + break; + + case 3: + { + CVector vecPos + ( + fX + maskMorphVerts[base+d].x, + fY + maskMorphVerts[base+d].y, + fZ + maskMorphVerts[base+d].z + 0.12f + ); + + vecPos -= 0.05f * TheCamera.GetForward(); + + CParticle::AddParticle(PARTICLE_WATER_SPARK, + vecPos, + CVector(0.0f, 0.0f, 0.0f), + nil, + 0.0f, + 15, + CGeneral::GetRandomNumberInRange(-90, 90), + 0, + 0); + } + break; + } + } + + maskMorphNormals[base+a].x = wavyMorphNormals[idxY + (17 * idxX)].x; + maskMorphNormals[base+a].y = wavyMorphNormals[idxY + (17 * idxX)].y; + maskMorphNormals[base+a].z = wavyMorphNormals[idxY + (17 * idxX)].z; + + maskMorphNormals[base+d].x = maskMorphNormals[base+a].x; + maskMorphNormals[base+d].y = maskMorphNormals[base+a].y; + maskMorphNormals[base+d].z = maskMorphNormals[base+a].z; + + maskMorphNormals[base+c].x = maskMorphNormals[base+d].x; + maskMorphNormals[base+c].y = maskMorphNormals[base+d].y; + maskMorphNormals[base+c].z = maskMorphNormals[base+d].z; + + maskMorphNormals[base+b].x = maskMorphNormals[base+c].x; + maskMorphNormals[base+b].y = maskMorphNormals[base+c].y; + maskMorphNormals[base+b].z = maskMorphNormals[base+c].z; + + maskPreLight[base+a].red = color.red; + maskPreLight[base+a].green = color.green; + maskPreLight[base+a].blue = color.blue; + maskPreLight[base+a].alpha = color.alpha; + + maskPreLight[base+d].red = maskPreLight[base+a].red; + maskPreLight[base+d].green = maskPreLight[base+a].green; + maskPreLight[base+d].blue = maskPreLight[base+a].blue; + maskPreLight[base+d].alpha = maskPreLight[base+a].alpha; + + maskPreLight[base+c].red = maskPreLight[base+d].red; + maskPreLight[base+c].green = maskPreLight[base+d].green; + maskPreLight[base+c].blue = maskPreLight[base+d].blue; + maskPreLight[base+c].alpha = maskPreLight[base+d].alpha; + + maskPreLight[base+b].red = maskPreLight[base+c].red; + maskPreLight[base+b].green = maskPreLight[base+c].green; + maskPreLight[base+b].blue = maskPreLight[base+c].blue; + maskPreLight[base+b].alpha = maskPreLight[base+c].alpha; + } + } + + RpGeometryUnlock(maskGeometry); + return true; +} +#endif + +void +CWaterLevel::RenderBoatWakes(void) +{ + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)gpWaterWakeRaster); +#ifndef PC_WATER + RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); +#endif + +#ifdef _XBOX + // TODO save and restore rwRENDERSTATESRCBLEND rwRENDERSTATEDESTBLEND +#endif + + CBoat::FillBoatList(); + + float fWakeZ = 5.97f; + float fWakeLifeTimeMult = 0.01f / CBoat::WAKE_LIFETIME; + + for ( int32 idx = 0; idx < ARRAY_SIZE(CBoat::apFrameWakeGeneratingBoats); idx++ ) + { + CBoat *pBoat = CBoat::apFrameWakeGeneratingBoats[idx]; + + if ( pBoat == nil ) + break; + + CVector2D vecDistA(pBoat->GetForward().x, pBoat->GetForward().y); + + + float fSize = pBoat->GetColModel()->boundingBox.max.z + * 0.65f; + + if ( pBoat->GetModelIndex() == MI_SKIMMER) + fSize *= 0.4f; + + float fAplhaA = 255.0f; + float fSizeA = fSize; + float fAplhaB; + float fSizeB; + + for ( int32 wake = 1; wake < pBoat->m_nNumWakePoints; wake++ ) + { + bool bRender = true; + + float fTimeleft = CBoat::WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[wake]; + + float fWakeSizeB = ((float)wake * 0.19f) + fSize - fWakeLifeTimeMult * Max(fTimeleft, 0.0f); + + fSizeB = fWakeSizeB / CBoat::MIN_WAKE_INTERVAL; + if ( fSizeB < 0.0f ) + fSizeB = 1.0f; + + if ( wake == pBoat->m_nNumWakePoints - 1 ) + { + // set alpha to 0 if it's last point + fAplhaB = 0.0f; + } + else + { + // clip (-100, 500), less lifetime - less val + float val = 500.0f - (CBoat::WAKE_LIFETIME - pBoat->m_afWakePointLifeTime[wake]) + * 600.0f / CBoat::WAKE_LIFETIME; + + fAplhaB = Clamp(val, 0.0f, 255.0f); + } + + CVector2D vecDistB = pBoat->m_avec2dWakePoints[wake - 1] - pBoat->m_avec2dWakePoints[wake]; + + float fScal = vecDistB.MagnitudeSqr(); + + // normalize if distance between points is greater than 3 + + if ( fScal > SQR(3.0f) ) + { + float fNorm = 1.0f / sqrt(fScal); + + vecDistB.x *= fNorm; + vecDistB.y *= fNorm; + + // disable render if distance between points too big + + if ( sqrt(fScal) > 13.0f ) + bRender = false; + } + + CVector2D vecAA + ( + pBoat->m_avec2dWakePoints[wake - 1].x - (fSizeA * vecDistA.y), + pBoat->m_avec2dWakePoints[wake - 1].y + (fSizeA * vecDistA.x) + ); + CVector2D vecAB + ( + pBoat->m_avec2dWakePoints[wake - 1].x + (fSizeA * vecDistA.y), + pBoat->m_avec2dWakePoints[wake - 1].y - (fSizeA * vecDistA.x) + ); + CVector2D vecBA + ( + pBoat->m_avec2dWakePoints[wake].x + (fSizeB * vecDistB.y), + pBoat->m_avec2dWakePoints[wake].y - (fSizeB * vecDistB.x) + ); + CVector2D vecBB + ( + pBoat->m_avec2dWakePoints[wake].x - (fSizeB * vecDistB.y), + pBoat->m_avec2dWakePoints[wake].y + (fSizeB * vecDistB.x) + ); + + if ( bRender ) + RenderWakeSegment(vecAA, vecAB, vecBA, vecBB, fSizeA, fSizeB, fAplhaA, fAplhaB, fWakeZ); + + vecDistA = vecDistB; + fSizeA = fSizeB; + + fAplhaB = fAplhaA; + } + } + + RenderAndEmptyRenderBuffer(); +} + +inline float +_GetWindedWave(float fX, float fY) +{ + float fAngle = (CTimer::GetTimeInMilliseconds() & 4095) * (TWOPI / 4096.0f); + float x = WATER_HUGE_X(fX + WATER_X_OFFSET); + float y = WATER_HUGE_Y(fY); + + float fWindFactor (CWeather::WindClipped * 0.4f + 0.2f); + float fWave = Sin(( (x - Floor(x)) + (y - Floor(y)) ) * TWOPI + fAngle); + + return fWindFactor * fWave; +} + +void +CWaterLevel::RenderWakeSegment(CVector2D &vecA, CVector2D &vecB, CVector2D &vecC, CVector2D &vecD, + float &fSizeA, float &fSizeB, + float &fAlphaA, float &fAlphaB, + float &fWakeZ) +{ + for ( int32 i = 0; i < 4; i++ ) + { + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + float fCurStep = (float)i / 4; + float fNxtStep = (float)(i + 1) / 4; + + float fLeftCurStep = 1.0f - fCurStep; + float fLeftNxtStep = 1.0f - fNxtStep; + + uint8 AlphaA = (uint32)(fAlphaA * aAlphaFade[i] ); + uint8 AlphaB = (uint32)(fAlphaA * aAlphaFade[i + 1]); + uint8 AlphaC = (uint32)(fAlphaB * aAlphaFade[i + 1]); + uint8 AlphaD = (uint32)(fAlphaB * aAlphaFade[i] ); + + CVector2D PosA = vecB*fCurStep + vecA*fLeftCurStep; + CVector2D PosB = vecB*fNxtStep + vecA*fLeftNxtStep; + CVector2D PosC = vecC*fNxtStep + vecD*fLeftNxtStep; + CVector2D PosD = vecC*fCurStep + vecD*fLeftCurStep; + + float fUA = (PosA.x / 4) + _TEXTURE_WAKE_ADDU; + float fVA = (PosA.y / 4) + _TEXTURE_WAKE_ADDV; + + float fUB = (PosB.x / 4) + _TEXTURE_WAKE_ADDU; + float fVB = (PosB.y / 4) + _TEXTURE_WAKE_ADDV; + + float fUC = (PosC.x / 4) + _TEXTURE_WAKE_ADDU; + float fVC = (PosC.y / 4) + _TEXTURE_WAKE_ADDV; + + float fUD = (PosD.x / 4) + _TEXTURE_WAKE_ADDU; + float fVD = (PosD.y / 4) + _TEXTURE_WAKE_ADDV; + +#define MIN4(a, b, c, d) (Min((a), Min((b), Min((c), (d))))) + float fMinU = Floor(MIN4(fUA, fUB, fUC, fUD)); + float fMinV = Floor(MIN4(fVA, fVB, fVC, fVD)); +#undef MIN4 + + float fZA = _GetWindedWave(PosA.x, PosA.y) + fWakeZ; + float fZB = _GetWindedWave(PosB.x, PosB.y) + fWakeZ; + float fZC = _GetWindedWave(PosC.x, PosC.y) + fWakeZ; + float fZD = _GetWindedWave(PosD.x, PosD.y) + fWakeZ; + + int32 vidx = TempBufferVerticesStored; + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], PosA.x, PosA.y, fZA); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], fUA - fMinU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], fVA - fMinV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 0], 255, 255, 255, AlphaA); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], PosB.x, PosB.y, fZB); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], fUB - fMinU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], fVB - fMinV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 1], 255, 255, 255, AlphaB); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], PosC.x, PosC.y, fZC); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], fUC - fMinU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], fVC - fMinV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 2], 255, 255, 255, AlphaC); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], PosD.x, PosD.y, fZD); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], fUD - fMinU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], fVD - fMinV); + RwIm3DVertexSetRGBA (&TempBufferRenderVertices[vidx + 3], 255, 255, 255, AlphaD); + + 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::RenderOneSlopedUnderWaterPoly(float fX, float fY, float fZ, RwRGBA const&color) +{ + CVector2D camPos(TheCamera.GetPosition().x, TheCamera.GetPosition().y); + + float fDistA = (CVector2D(fX, fY) - camPos).Magnitude() + -140.0f; + float fDistB = (CVector2D(fX, fY + HUGE_SECTOR_SIZE) - camPos).Magnitude() + -140.0f; + float fDistC = (CVector2D(fX + HUGE_SECTOR_SIZE, fY + HUGE_SECTOR_SIZE) - camPos).Magnitude() + -140.0f; + float fDistD = (CVector2D(fX + HUGE_SECTOR_SIZE, fY) - camPos).Magnitude() + -140.0f; + +#ifndef PC_WATER +#define CALCSEABED(v, d) \ + { \ + if ( d < 0.0f ) \ + v = 0.1f + fSeaBedZ; \ + else if ( d > 240.0f ) \ + v = 0.1f; \ + else \ + v = 0.1f + ((fSeaBedZ * (240.0f - d)) / 240.0f); \ + } +#else + #define CALCSEABED(v, d) \ + { \ + v = 0.1f; \ + if ( d < 0.0f ) \ + v += fSeaBedZ; \ + else if ( d <= 240.0f ) \ + v += (fSeaBedZ / 240.0f) * (240.0f - d); \ + } +#endif + float fSeaBedA, fSeaBedB, fSeaBedC, fSeaBedD; + + CALCSEABED(fSeaBedA, fDistA); + CALCSEABED(fSeaBedB, fDistB); + CALCSEABED(fSeaBedC, fDistC); + CALCSEABED(fSeaBedD, fDistD); + + #undef CALCSEABED + + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - _fWaterZOffset - fSeaBedA); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], 0.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], 0.0f); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + HUGE_SECTOR_SIZE, fZ - _fWaterZOffset - fSeaBedB); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], 0.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], 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 - _fWaterZOffset - fSeaBedC); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 2], 4.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 2], 4.0f); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[vidx + 2], color.red, color.green, color.blue, 255); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + HUGE_SECTOR_SIZE, fY, fZ - _fWaterZOffset - fSeaBedD); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], 4.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], 0.0f); + 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::RenderOneFlatSmallWaterPolyBlended(float fX, float fY, float fZ, float fCamX, float fCamY, + RwRGBA const &color, RwRGBA const &colorTrans, + float fDrawDist) +{ + if ( TempBufferIndicesStored >= TEMPBUFFERINDEXSIZE-6 || TempBufferVerticesStored >= TEMPBUFFERVERTSIZE-4 ) + RenderAndEmptyRenderBuffer(); + + int32 vidx = TempBufferVerticesStored; + + float fBlendDrawDist = fDrawDist + fStartBlendDistanceAdd; + + float fDistStartX = SQR(fX - fCamX); + float fDistStartY = SQR(fY - fCamY); + float fDistEndX = SQR((fX + SMALL_SECTOR_SIZE) - fCamX); + float fDistEndY = SQR((fY + SMALL_SECTOR_SIZE) - fCamY); + + + float fAlphaBlendMulA + = Min(fFlatWaterBlendRange * Max(sqrt(fDistStartX + fDistStartY) - fBlendDrawDist, fMinWaterAlphaMult), 1.0f); + float fAlphaBlendMulB + = Min(fFlatWaterBlendRange * Max(sqrt(fDistStartX + fDistEndY ) - fBlendDrawDist, fMinWaterAlphaMult), 1.0f); + float fAlphaBlendMulC + = Min(fFlatWaterBlendRange * Max(sqrt(fDistEndX + fDistEndY ) - fBlendDrawDist, fMinWaterAlphaMult), 1.0f); + float fAlphaBlendMulD + = Min(fFlatWaterBlendRange * Max(sqrt(fDistEndX + fDistStartY) - fBlendDrawDist, fMinWaterAlphaMult), 1.0f); + + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 0], fX, fY, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 0], TEXTURE_ADDV); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[vidx + 0], color.red, color.green, color.blue, + (colorTrans.alpha + (color.alpha - colorTrans.alpha) * (uint8)(int32)fAlphaBlendMulA)); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 1], fX, fY + SMALL_SECTOR_SIZE, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDU); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 1], TEXTURE_ADDV + 1.0f); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[vidx + 1], color.red, color.green, color.blue, + (colorTrans.alpha + (color.alpha - colorTrans.alpha) * (uint8)(int32)fAlphaBlendMulB)); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 2], fX + SMALL_SECTOR_SIZE, fY + SMALL_SECTOR_SIZE, fZ - _fWaterZOffset); + 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, + (colorTrans.alpha + (color.alpha - colorTrans.alpha) * (uint8)(int32)fAlphaBlendMulC)); + + RwIm3DVertexSetPos (&TempBufferRenderVertices[vidx + 3], fX + SMALL_SECTOR_SIZE, fY, fZ - _fWaterZOffset); + RwIm3DVertexSetU (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDU + 1.0f); + RwIm3DVertexSetV (&TempBufferRenderVertices[vidx + 3], TEXTURE_ADDV); + RwIm3DVertexSetRGBA(&TempBufferRenderVertices[vidx + 3], color.red, color.green, color.blue, + (colorTrans.alpha + (color.alpha - colorTrans.alpha) * (uint8)(int32)fAlphaBlendMulD)); + + + 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; +} + +float +CWaterLevel::CalcDistanceToWater(float fX, float fY) +{ + const float fSectorMaxRenderDist = 250.0f; + + int32 nStartX = WATER_TO_SMALL_SECTOR_X(fX - fSectorMaxRenderDist + WATER_X_OFFSET) - 1; + int32 nEndX = WATER_TO_SMALL_SECTOR_X(fX + fSectorMaxRenderDist + WATER_X_OFFSET) + 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) - WATER_X_OFFSET; + 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; +} + +bool +CWaterLevel::GetGroundLevel(CVector const &vecPosn, float *pfOutLevel, ColData *pData, float fDistance) +{ + CColPoint point; + CEntity *entity; + + if ( !CWorld::ProcessVerticalLine(vecPosn + CVector(0.0f, 0.0f, fDistance), + -fDistance, point, entity, true, false, false, false, true, false, nil) ) + return false; + + *pfOutLevel = point.point.z; + + if ( pData != nil ) + { + pData->SurfaceType = point.surfaceB; + pData->PieceType = point.pieceB; + } + + return true; +} + +bool +CWaterLevel::IsLocationOutOfWorldBounds_WS(CVector const &vecPosn, int nOffset) +{ + int32 x = int32((vecPosn.x / 50.0f) + 48.0f); + int32 y = int32((vecPosn.y / 50.0f) + 40.0f); + + return x < nOffset || x >= 80 - nOffset || y < nOffset || y >= 80 - nOffset; +} + +bool +CWaterLevel::GetGroundLevel_WS(CVector const &vecPosn, float *pfOutLevel, ColData *pData, float fDistance) +{ + if ( IsLocationOutOfWorldBounds_WS(vecPosn, 0) ) + return false; + else + return GetGroundLevel(vecPosn, pfOutLevel, pData, fDistance); +} + +bool +CWaterLevel::GetWaterDepth(CVector const &vecPosn, float *pfDepth, float *pfLevelNoWaves, float *pfGroundLevel) +{ + float fLevelNoWaves; + float fGroundLevel; + + if ( !GetWaterLevelNoWaves(vecPosn.x, vecPosn.y, vecPosn.z, &fLevelNoWaves) ) + return false; + + if ( !GetGroundLevel(vecPosn, &fGroundLevel, nil, 30.0f) ) + fGroundLevel = -100.0; + + if ( pfDepth != nil ) + *pfDepth = fLevelNoWaves - fGroundLevel; + + if ( pfLevelNoWaves != nil ) + *pfLevelNoWaves = fLevelNoWaves; + + if ( pfGroundLevel != nil ) + *pfGroundLevel = fGroundLevel; + + return true; +} + +void +CWaterLevel::RenderSeaBirds() +{ + CVector cur_pos = TheCamera.GetPosition(); + + if ( !CCullZones::CamNoRain() + && !CCullZones::PlayerNoRain() + && (CWeather::NewWeatherType == WEATHER_SUNNY || CWeather::NewWeatherType == WEATHER_EXTRA_SUNNY) + && CClock::ms_nGameClockHours > 6 && CClock::ms_nGameClockHours < 20 ) + { + 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); + + uint16 nSinCosIdx = CGeneral::GetRandomNumber() % (CParticle::SIN_COS_TABLE_SIZE-1); + + float fCos = CParticle::Cos(nSinCosIdx); + float fSin = CParticle::Sin(nSinCosIdx); + + 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, nil, 0.0f, 0, 0, 0, 0); + } + } + } +} + +void +CWaterLevel::RenderShipsOnHorizon() +{ +#ifdef FIX_BUGS + CVector cur_pos = FindPlayerCoors(); +#else + CVector cur_pos = FindPlayerPed()->GetPosition(); +#endif + + 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 shipgenTime = 0; + + if ( (CTimer::GetTimeInMilliseconds() - shipgenTime) > 4000 ) + { + shipgenTime = CTimer::GetTimeInMilliseconds(); + + CVector vecPos = cur_pos; + + float fAngle = CGeneral::GetRandomNumberInRange(450.0f, 750.0f); + + uint16 nSinCosIdx = CGeneral::GetRandomNumber() % (CParticle::SIN_COS_TABLE_SIZE-1); + + float fCos = CParticle::Cos(nSinCosIdx); + float fSin = CParticle::Sin(nSinCosIdx); + + vecPos.x += (fCos - fSin) * fAngle; + vecPos.y += (fSin + fCos) * fAngle; + + float fLevelNoWaves; + + if ( GetWaterLevelNoWaves(vecPos.x, vecPos.y, vecPos.z, &fLevelNoWaves) ) + { + if ( IsLocationOutOfWorldBounds_WS(vecPos, 1) ) + { + vecPos.z = fLevelNoWaves + 9.5f; + + CVector vecDir + ( + CGeneral::GetRandomNumberInRange(-0.1f, 0.1f), + 0.0f, + 0.0f + ); + + CParticle::AddParticle(PARTICLE_SHIP_SIDE, vecPos, vecDir, + nil, 0.0f, 0, 0, CGeneral::GetRandomNumber() & 7, 0); + } + } + } + } +} + +void +CWaterLevel::HandleSeaLifeForms() +{ + if ( CReplay::IsPlayingBack() ) + return; + + CVector cur_pos = FindPlayerPed()->GetPosition(); + + static CVector prev_pos(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 ) + { + if ( CWaterCreatures::IsSpaceForMoreWaterCreatures() ) + { + for ( int32 i = 0; i < 3; i++ ) + { + CVector vecPos = cur_pos; + + float fAngle = CGeneral::GetRandomNumberInRange(15.0f, 30.0f); + + uint16 nSinCosIdx = CGeneral::GetRandomNumber() % (CParticle::SIN_COS_TABLE_SIZE-1); + + float fCos = CParticle::Cos(nSinCosIdx); + float fSin = CParticle::Sin(nSinCosIdx); + + vecPos.x += (fCos - fSin) * fAngle; + vecPos.y += (fSin + fCos) * fAngle; + + CWaterCreatures::CreateOne(vecPos, -1); + } + } + } + + CWaterCreatures::UpdateAll(); +} + +void +CWaterLevel::HandleBeachToysStuff(void) +{ +#ifdef FIX_BUGS + CVector cur_pos = FindPlayerCoors(); +#else + CVector cur_pos = FindPlayerPed()->GetPosition(); +#endif + + static bool bBeachBallInit = true; + static CVector FirstBeachBallPos = cur_pos; + static bool bLoungeInit = true; + static CVector FirstLoungePos = cur_pos; + static CVector prev_pos(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 toygenTime = CTimer::GetTimeInMilliseconds(); + + if ( (CTimer::GetTimeInMilliseconds() - toygenTime) > 20000 ) + { + toygenTime = CTimer::GetTimeInMilliseconds(); + + if ( bBeachBallInit || (cur_pos - FirstBeachBallPos).MagnitudeSqr() > 6400.0f ) + { + for ( int32 i = 0; i < 3; i++ ) + { + CVector vecPos = cur_pos; + + float fAngle = CGeneral::GetRandomNumberInRange(20.0f, 35.0f); + + uint16 nSinCosIdx = CGeneral::GetRandomNumber() % (CParticle::SIN_COS_TABLE_SIZE-1); + + float fCos = CParticle::Cos(nSinCosIdx); + float fSin = CParticle::Sin(nSinCosIdx); + + vecPos.x += (fCos - fSin) * fAngle; + vecPos.y += (fSin + fCos) * fAngle; + + if ( TheCamera.IsSphereVisible(vecPos, 1.0f) ) + { + float fWaterLevel; + + if ( !GetWaterLevel(vecPos.x, vecPos.y, vecPos.z, &fWaterLevel, false) ) + { + float fGroundLevel; + ColData coldata; + + if ( GetGroundLevel(vecPos, &fGroundLevel, &coldata, 30.0f) ) + { + if ( coldata.SurfaceType == SURFACE_SAND ) + { + CEntity *toy = CreateBeachToy(vecPos, BEACHTOY_BALL); + + if ( toy ) + { + FirstBeachBallPos = cur_pos; + bBeachBallInit = false; + i = 10; + } + } + } + } + } + } + } + + if ( bLoungeInit || (cur_pos - FirstLoungePos).MagnitudeSqr() > 6400.0f ) + { + for ( int32 i = 0; i < 5; i++ ) + { + CVector vecPos = cur_pos; + + float fAngle = CGeneral::GetRandomNumberInRange(20.0f, 35.0f); + + uint16 nSinCosIdx = CGeneral::GetRandomNumber() % (CParticle::SIN_COS_TABLE_SIZE-1); + + float fCos = CParticle::Cos(nSinCosIdx); + float fSin = CParticle::Sin(nSinCosIdx); + + vecPos.x += (fCos - fSin) * fAngle; + vecPos.y += (fSin + fCos) * fAngle; + + if ( TheCamera.IsSphereVisible(vecPos, 2.0f) ) + { + float fWaterLevel; + + if ( !GetWaterLevel(vecPos.x, vecPos.y, vecPos.z, &fWaterLevel, false) ) + { + float fGroundLevel; + ColData coldata; + + if ( GetGroundLevel(vecPos, &fGroundLevel, &coldata, 30.0f) ) + { + if ( coldata.SurfaceType == SURFACE_SAND ) + { + CEntity *toy = CreateBeachToy(vecPos, BEACHTOY_ANY_LOUNGE); + if ( toy ) + { + toy->SetHeading(DEGTORAD(CGeneral::GetRandomNumberInRange(0.0f, 359.0f))); + FirstLoungePos = cur_pos; + bLoungeInit = false; + } + } + } + } + } + } + } + } + } +} + +CEntity * +CWaterLevel::CreateBeachToy(CVector const &vec, eBeachToy beachtoy) +{ + if (CObject::nNoTempObjects >= NUMTEMPOBJECTS) + return nil; + + int finalToy = beachtoy; + bool isStatic = false; + int model = MI_BEACHBALL; + switch (beachtoy) { + case BEACHTOY_ANY_LOUNGE: + switch ( CGeneral::GetRandomNumber() & 7 ) { + case 1: + case 7: + finalToy = BEACHTOY_LOUNGE_WOOD_UP; + break; + case 3: + case 5: + finalToy = BEACHTOY_LOUNGE_TOWEL_UP; + break; + default: + finalToy = BEACHTOY_LOUNGE_WOOD_ON; + break; + } + break; + case BEACHTOY_ANY_TOWEL: + switch ( CGeneral::GetRandomNumber() & 7 ) { + case 1: + case 7: + finalToy = BEACHTOY_TOWEL2; + break; + case 2: + case 6: + finalToy = BEACHTOY_TOWEL3; + break; + case 3: + case 5: + finalToy = BEACHTOY_TOWEL4; + break; + default: + finalToy = BEACHTOY_TOWEL1; + break; + } + if (CObject::nNoTempObjects >= 35) { + return nil; + } + default: + break; + } + switch (finalToy) { + case BEACHTOY_BALL: + isStatic = false; + model = MI_BEACHBALL; + break; + case BEACHTOY_LOUNGE_WOOD_UP: + isStatic = false; + model = MI_LOUNGE_WOOD_UP; + break; + case BEACHTOY_LOUNGE_TOWEL_UP: + isStatic = false; + model = MI_LOUNGE_TOWEL_UP; + break; + case BEACHTOY_LOUNGE_WOOD_ON: + isStatic = false; + model = MI_LOUNGE_WOOD_DN; + break; + case BEACHTOY_LOTION: + model = MI_LOTION; + isStatic = true; + break; + case BEACHTOY_TOWEL1: + model = MI_BEACHTOWEL01; + isStatic = true; + break; + case BEACHTOY_TOWEL2: + model = MI_BEACHTOWEL02; + isStatic = true; + break; + case BEACHTOY_TOWEL3: + model = MI_BEACHTOWEL03; + isStatic = true; + break; + case BEACHTOY_TOWEL4: + model = MI_BEACHTOWEL04; + isStatic = true; + break; + default: + break; + } + CObject *toy = new CObject(model, true); + if (toy) { + toy->SetPosition(vec); + toy->GetMatrix().UpdateRW(); + toy->m_vecMoveSpeed = CVector(0.f, 0.f, 0.f); + toy->m_vecTurnSpeed = CVector(0.f, 0.f, 0.f); + toy->ObjectCreatedBy = TEMP_OBJECT; + toy->bIsStatic = isStatic; + CObject::nNoTempObjects++; + toy->m_nEndOfLifeTime = CTimer::GetTimeInMilliseconds() + 43200000; + CWorld::Add(toy); + return toy; + } else + return nil; +} \ No newline at end of file diff --git a/src/miami/renderer/WaterLevel.h b/src/miami/renderer/WaterLevel.h new file mode 100644 index 00000000..6d6614d8 --- /dev/null +++ b/src/miami/renderer/WaterLevel.h @@ -0,0 +1,184 @@ +#pragma once + +#define WATER_X_OFFSET (400.0f) + +#define WATER_Z_OFFSET (0.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) ) + +// 64x64 Large blocks 64x64 each +#define WATER_TO_BLOCK_X(x) ( WATER_UNSIGN_X(x) / WATER_BLOCK_SECTORS ) +#define WATER_TO_BLOCK_Y(x) ( WATER_UNSIGN_Y(x) / WATER_BLOCK_SECTORS ) + +// 128x128 Small blocks 32x32 each +#define WATER_TO_FINEBLOCK_X(x) ( WATER_UNSIGN_X(x) / WATER_FINEBLOCK_SECTORS ) +#define WATER_TO_FINEBLOCK_Y(x) ( WATER_UNSIGN_Y(x) / WATER_FINEBLOCK_SECTORS ) + +// 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 ) + +struct ColData +{ + uint8 SurfaceType; + uint8 PieceType; +}; + +enum eBeachToy +{ + BEACHTOY_0 = 0, + BEACHTOY_BALL, + BEACHTOY_LOUNGE_WOOD_UP, + BEACHTOY_LOUNGE_TOWEL_UP, + BEACHTOY_LOUNGE_WOOD_ON, + BEACHTOY_ANY_LOUNGE, + BEACHTOY_LOTION, + BEACHTOY_TOWEL1, + BEACHTOY_TOWEL2, + BEACHTOY_TOWEL3, + BEACHTOY_TOWEL4, + BEACHTOY_ANY_TOWEL, +}; + +extern RwRaster* gpWaterRaster; +extern bool gbDontRenderWater; + +class CEntity; + +class CWaterLevel +{ +public: + static int32 ms_nNoOfWaterLevels; + static float ms_aWaterZs[48]; + static CRect ms_aWaterRects[48]; + static int8 aWaterBlockList[MAX_LARGE_SECTORS][MAX_LARGE_SECTORS]; // 64x64 Large blocks 64x64 each + static int8 aWaterFineBlockList[MAX_SMALL_SECTORS][MAX_SMALL_SECTORS]; // 128x128 Small blocks 32x32 each + static bool WavesCalculatedThisFrame; + + static bool RequireWavySector; + static bool MaskCalculatedThisFrame; + static CVector PreCalculatedMaskPosn; + static bool m_bRenderSeaBed; + static int32 m_nRenderWaterLayers; + + static RpAtomic *ms_pWavyAtomic; + static RpAtomic *ms_pMaskAtomic; + + 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 float GetWaterWavesOnly(short x, short y); // unused + static CVector GetWaterNormal(float fX, float fY); + + static void RenderWater(); + static void RenderTransparentWater(void); + // unused + static void RenderOneFlatSmallWaterPoly (float fX, float fY, float fZ, RwRGBA const &color); + // inlined + 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); + // inlined + static void RenderOneWavySector (float fX, float fY, float fZ, RwRGBA const &color, bool bDontRender = false); + // unused +#ifdef PC_WATER + static void RenderWavyMask(float fX, float fY, float fZ, float fSectorX, float fSectorY, float fCamPosX, float fCamPosY, float fCamDirX, float fCamDirY, RwRGBA const&color); +#else + static void RenderWavyMask(float fX, float fY, float fZ, float fSectorX, float fSectorY, int32 nCamDirX, int32 nCamDirY, RwRGBA const&color); +#endif + +#ifdef PC_WATER + static void PreCalcWaterGeometry(void); + static bool PreCalcWavySector(RwRGBA const &color); //fucked up + static bool PreCalcWavyMask(float fX, float fY, float fZ, float fSectorX, float fSectorY, float fCamPosX, float fCamPosY, float fCamDirX, float fCamDirY, RwRGBA const&color); +#endif + + + static void RenderBoatWakes(void); + static void RenderWakeSegment(CVector2D &vecA, CVector2D &vecB, CVector2D &vecC, CVector2D &vecD, float &fSizeA, float &fSizeB, float &fAlphaA, float &fAlphaB, float &fWakeZ); + + // unused + static void RenderOneSlopedUnderWaterPoly(float fX, float fY, float fZ, RwRGBA const&color); // UNUSED + static void RenderOneFlatSmallWaterPolyBlended(float fX, float fY, float fZ, float fCamX, float fCamY, RwRGBA const &color, RwRGBA const &colorTrans, float fDrawDist); + static float CalcDistanceToWater(float fX, float fY); + static void RenderAndEmptyRenderBuffer(); + + static bool GetGroundLevel(CVector const &vecPosn, float *pfOutLevel, ColData *pData, float fDistance); + + // unused + static bool IsLocationOutOfWorldBounds_WS(CVector const &vecPosn, int nOffset); + // unused + static bool GetGroundLevel_WS(CVector const & vecPosn, float *pfOutLevel, ColData *pData, float fDistance); + static bool GetWaterDepth(CVector const &vecPosn, float *pfDepth, float *pfLevelNoWaves, float *pfGroundLevel); + + static void RenderSeaBirds(); + static void RenderShipsOnHorizon(); + + static void HandleSeaLifeForms(); + + static void HandleBeachToysStuff(void); + static CEntity *CreateBeachToy(CVector const &vec, eBeachToy beachtoy); +}; diff --git a/src/miami/renderer/Weather.cpp b/src/miami/renderer/Weather.cpp new file mode 100644 index 00000000..9f925a8c --- /dev/null +++ b/src/miami/renderer/Weather.cpp @@ -0,0 +1,662 @@ +#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 "PlayerPed.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" +#include "SpecialFX.h" +#include "Replay.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; +int32 CWeather::StreamAfterRainTimer; + +float CWeather::ExtraSunnyness; +float CWeather::Foggyness; +float CWeather::CloudCoverage; +float CWeather::Wind; +float CWeather::Rain; +float CWeather::InterpolationValue; +float CWeather::WetRoads; +float CWeather::Rainbow; +float CWeather::SunGlare; +float CWeather::WindClipped; +float CWeather::TrafficLightBrightness; + +bool CWeather::bScriptsForceRain; + +tRainStreak Streaks[NUM_RAIN_STREAKS]; + +int16 WeatherTypesList[] = { + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_CLOUDY, + WEATHER_RAINY, WEATHER_RAINY, WEATHER_RAINY, WEATHER_RAINY, + WEATHER_CLOUDY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY +}; + +int16 WeatherTypesList_WithHurricanes[] = { + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_CLOUDY, + WEATHER_HURRICANE, WEATHER_HURRICANE, WEATHER_CLOUDY, WEATHER_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_CLOUDY, WEATHER_HURRICANE, WEATHER_HURRICANE, WEATHER_HURRICANE, + WEATHER_CLOUDY, WEATHER_SUNNY, WEATHER_SUNNY, WEATHER_SUNNY, + WEATHER_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, + WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY, WEATHER_EXTRA_SUNNY +}; + +const float Windyness[] = { + 0.25f,// WEATHER_SUNNY + 0.7f, // WEATHER_CLOUDY + 1.0f, // WEATHER_RAINY + 0.0f, // WEATHER_FOGGY + 0.0f, // WEATHER_EXTRA_SUNNY + 2.0f, // WEATHER_HURRICANE + 0.0f +}; + +#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_EXTRA_SUNNY; + bScriptsForceRain = false; + OldWeatherType = WEATHER_EXTRA_SUNNY; + 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) +{ + if(!CReplay::IsPlayingBack()){ + float fNewInterpolation = (CClock::GetMinutes() + CClock::GetSeconds()/60.0f)/60.0f; + if (fNewInterpolation < InterpolationValue) { + // new hour + OldWeatherType = NewWeatherType; + if (ForcedWeatherType >= 0) + NewWeatherType = ForcedWeatherType; + else { + WeatherTypeInList = (WeatherTypeInList + 1) % ARRAY_SIZE(WeatherTypesList); + NewWeatherType = CStats::NoMoreHurricanes ? WeatherTypesList[WeatherTypeInList] : WeatherTypesList_WithHurricanes[WeatherTypeInList]; + } + } + InterpolationValue = fNewInterpolation; + } + +#ifndef FINAL + if (CPad::GetPad(1)->GetRightShockJustDown()) { + NewWeatherType = (NewWeatherType + 1) % WEATHER_TOTAL; + OldWeatherType = NewWeatherType; + } +#endif + + // 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 || OldWeatherType == WEATHER_HURRICANE) { + if (NewWeatherType == WEATHER_RAINY || NewWeatherType == WEATHER_HURRICANE) + WetRoads = 1.0f; + else + WetRoads = 1.0f - InterpolationValue; + } + else { + if (NewWeatherType == WEATHER_RAINY || NewWeatherType == WEATHER_HURRICANE) + WetRoads = InterpolationValue; + else + WetRoads = 0.0f; + } + + // Rain + float fNewRain; + if (NewWeatherType == WEATHER_RAINY || NewWeatherType == WEATHER_HURRICANE) { + // 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 && OldWeatherType != WEATHER_HURRICANE) { + 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; + + // Clouds + if (OldWeatherType != WEATHER_SUNNY && OldWeatherType != WEATHER_EXTRA_SUNNY) + CloudCoverage = 1.0f - InterpolationValue; + else + CloudCoverage = 0.0f; + if (NewWeatherType != WEATHER_SUNNY && OldWeatherType != WEATHER_EXTRA_SUNNY) + CloudCoverage += InterpolationValue; + + // Fog + if (OldWeatherType == WEATHER_FOGGY) + Foggyness = 1.0f - InterpolationValue; + else + Foggyness = 0.0f; + if (NewWeatherType == WEATHER_FOGGY) + Foggyness += InterpolationValue; + + // Extra Sunnyness + if (OldWeatherType == WEATHER_EXTRA_SUNNY) + ExtraSunnyness = 1.0f - InterpolationValue; + else + ExtraSunnyness = 0.0f; + if (NewWeatherType == WEATHER_EXTRA_SUNNY) + ExtraSunnyness += InterpolationValue; + + // Rainbow + if (OldWeatherType == WEATHER_CLOUDY && (NewWeatherType == WEATHER_SUNNY || NewWeatherType == WEATHER_EXTRA_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; + + // Sun Glare + if (OldWeatherType == WEATHER_EXTRA_SUNNY) + SunGlare = 1.0f - InterpolationValue; + else + SunGlare = 0.0f; + if (NewWeatherType == WEATHER_EXTRA_SUNNY) + SunGlare += InterpolationValue; + + if (SunGlare > 0.0f) { + SunGlare *= Min(1.0f, 7.0 * CTimeCycle::GetSunDirection().z); + SunGlare = Clamp(SunGlare, 0.0f, 1.0f); + if (!CSpecialFX::bSnapShotActive) + SunGlare *= (1.0f - (CGeneral::GetRandomNumber()&0x1F)*0.007f); + } + + Wind = InterpolationValue * Windyness[NewWeatherType] + (1.0f - InterpolationValue) * Windyness[OldWeatherType]; + WindClipped = Min(1.0f, Wind); + + if (CClock::GetHours() > 20) + TrafficLightBrightness = 1.0f; + else if (CClock::GetHours() > 19) + TrafficLightBrightness = CClock::GetMinutes() / 60.0f; + else if (CClock::GetHours() > 6) + TrafficLightBrightness = 0.0f; + else if (CClock::GetHours() > 5) + TrafficLightBrightness = 1.0f - CClock::GetMinutes() / 60.0f; + else + TrafficLightBrightness = 1.0f; + TrafficLightBrightness = Max(WetRoads, TrafficLightBrightness); + TrafficLightBrightness = Max(Foggyness, TrafficLightBrightness); + TrafficLightBrightness = Max(Rain, TrafficLightBrightness); + + AddRain(); + + if ((NewWeatherType == WEATHER_SUNNY || NewWeatherType == WEATHER_EXTRA_SUNNY) && + !CGame::IsInInterior() && !CCutsceneMgr::IsRunning() && (CTimer::GetFrameCounter() & 7) == 0) { +#ifdef FIX_BUGS + if (FindPlayerPed() && (!FindPlayerPed()->CheckIfInTheAir() || FindPlayerPed()->CheckIfInTheAir() && FindPlayerPed()->GetPosition().z < 7.5f && + CClock::GetHours() > 6 && CClock::GetHours() < 18)) +#else + if (!FindPlayerPed()->CheckIfInTheAir() || FindPlayerPed()->CheckIfInTheAir() && FindPlayerPed()->GetPosition().z < 7.5f && + CClock::GetHours() > 6 && CClock::GetHours() < 18) +#endif + AddHeatHaze(); + } + + if ((NewWeatherType == WEATHER_SUNNY || NewWeatherType == WEATHER_EXTRA_SUNNY) && !CGame::IsInInterior() && !CCutsceneMgr::IsRunning()) + AddBeastie(); +} + +void CWeather::AddHeatHaze() +{ + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || + TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOP_DOWN_PED) + return; + CVector pos; + pos.x = SCREEN_WIDTH*0.5f; + if(TheCamera.GetLookingForwardFirstPerson()) + pos.y = CGeneral::GetRandomNumberInRange(SCREEN_HEIGHT*0.25f, SCREEN_HEIGHT*0.9f); + else + pos.y = CGeneral::GetRandomNumberInRange(SCREEN_HEIGHT*0.4f, SCREEN_HEIGHT*0.9f); + pos.z = 100.0f; + CParticle::AddParticle(PARTICLE_HEATHAZE_IN_DIST, pos, CVector(0.0f, 0.0f, 0.0f)); +} + +void CWeather::AddBeastie() +{ + if(FindPlayerVehicle() || CTimer::GetFrameCounter()%10 || (CGeneral::GetRandomNumber()&5) == 0) + return; + CVector pos = TheCamera.GetPosition(); + float dist = CGeneral::GetRandomNumberInRange(90.0f, 60.0f); + int angle = CGeneral::GetRandomNumber() % CParticle::SIN_COS_TABLE_SIZE; + float c = CParticle::m_CosTable[angle]; + float s = CParticle::m_SinTable[angle]; + pos.x += dist*(c - s); + pos.y += dist*(c + s); + pos.z += CGeneral::GetRandomNumberInRange(7.5f, 30.0f); + CParticle::AddParticle(PARTICLE_BEASTIE, pos, CVector(0.0f, 0.0f, 0.0f)); +} + +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::AddSplashesDuringHurricane() +{ + RwRGBA colour = { 255, 255, 255, 32 }; + CVector pos = TheCamera.pTargetEntity ? TheCamera.pTargetEntity->GetPosition() : TheCamera.GetPosition(); + bool foundGround; + float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &foundGround) + 0.1f; + if(!foundGround) + groundZ = pos.z + 0.5f; + for(int i = 0; i < 20; i++){ + float dist = (CGeneral::GetRandomNumber()&0xFF)/255.0f + + CGeneral::GetRandomNumberInRange(-10.0f, 30.0f); + float angle; + uint8 rnd = CGeneral::GetRandomNumber(); + if(rnd&1) + angle = (CGeneral::GetRandomNumber()&0x7F)/128.0f * TWOPI; + else + angle = TheCamera.Orientation + (rnd-128)/160.0f; + pos.x = TheCamera.GetPosition().x + dist*Sin(angle); + pos.y = TheCamera.GetPosition().y + dist*Cos(angle); + pos.z = groundZ; + if(foundGround) + CParticle::AddParticle(PARTICLE_GROUND_STEAM, pos, CVector(-0.002f, -0.002f, 0.015f), nil, 0.0f, colour); + } +} + +static int startStreamAfterRain; + +void CWeather::AddStreamAfterRain() +{ + if(CClock::GetHours() > 6 && CClock::GetHours() < 18){ + RwRGBA colour = { 255, 255, 255, 24 }; + CVector pos = TheCamera.pTargetEntity ? TheCamera.pTargetEntity->GetPosition() : TheCamera.GetPosition(); + bool foundGround; + float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, &foundGround) + 0.2f; + if(!foundGround) + groundZ = pos.z + 0.75f; + for(int i = 0; i < 20; i++){ + float dist = (CGeneral::GetRandomNumber()&0xFF)/255.0f + + CGeneral::GetRandomNumberInRange(-10.0f, 30.0f); + float angle; + uint8 rnd = CGeneral::GetRandomNumber(); + if(rnd&1) + angle = (CGeneral::GetRandomNumber()&0x7F)/128.0f * TWOPI; + else + angle = TheCamera.Orientation + (rnd-128)/160.0f; + pos.x = TheCamera.GetPosition().x + dist*Sin(angle); + pos.y = TheCamera.GetPosition().y + dist*Cos(angle); + pos.z = groundZ; + CParticle::AddParticle(PARTICLE_GROUND_STEAM, pos, CVector(0.0f, 0.0f, 0.015f), nil, 0.0f, colour); + } + }else{ + startStreamAfterRain = 0; + StreamAfterRainTimer = 800; + } +} + +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.0){ + startStreamAfterRain = 1; + StreamAfterRainTimer = 800; + }else if(startStreamAfterRain){ + if(StreamAfterRainTimer > 0){ + AddStreamAfterRain(); + StreamAfterRainTimer--; + }else{ + startStreamAfterRain = 0; + StreamAfterRainTimer = 800; + } + } + + if (Wind > 1.1f) + AddSplashesDuringHurricane(); + + if (Rain <= 0.1f) + return; + static RwRGBA colour; + int numDrops = 5.0f * Rain; + int numSplashes = 2.0f * Rain; + CVector pos, dir; + for(int i = 0; i < numDrops; i++){ + pos.x = CGeneral::GetRandomNumberInRange(0, (int)SCREEN_WIDTH); + pos.y = CGeneral::GetRandomNumberInRange(0, (int)SCREEN_HEIGHT/5); + pos.z = 0.0f; + dir.x = 0.0f; + dir.y = CGeneral::GetRandomNumberInRange(30.0f, 40.0f); + dir.z = 0.0f; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, pos, dir, nil, CGeneral::GetRandomNumberInRange(0.1f, 0.75f), 0, 0, (int)Rain&3, 0); + + pos.x = CGeneral::GetRandomNumberInRange(0, (int)SCREEN_WIDTH); + pos.y = CGeneral::GetRandomNumberInRange((int)SCREEN_HEIGHT/5, (int)SCREEN_HEIGHT/2); + pos.z = 0.0f; + dir.x = 0.0f; + dir.y = CGeneral::GetRandomNumberInRange(30.0f, 40.0f); + dir.z = 0.0f; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, pos, dir, nil, CGeneral::GetRandomNumberInRange(0.1f, 0.75f), 0, 0, (int)Rain&3, 0); + + pos.x = CGeneral::GetRandomNumberInRange(0, (int)SCREEN_WIDTH); + pos.y = 0.0f; + pos.z = 0.0f; + dir.x = 0.0f; + dir.y = CGeneral::GetRandomNumberInRange(30.0f, 40.0f); + dir.z = 0.0f; + CParticle::AddParticle(PARTICLE_RAINDROP_2D, pos, dir, nil, CGeneral::GetRandomNumberInRange(0.1f, 0.75f), 0, 0, (int)Rain&3, 0); + + float dist = CGeneral::GetRandomNumberInRange(0.0f, Max(10.0f*Rain, 40.0f)/2.0f); + float angle; + uint8 rnd = CGeneral::GetRandomNumber(); + if(rnd&1) + angle = (CGeneral::GetRandomNumber()&0x7F)/128.0f * TWOPI; + else + angle = TheCamera.Orientation + (rnd-128)/160.0f; + pos.x = TheCamera.GetPosition().x + dist*Sin(angle); + pos.y = TheCamera.GetPosition().y + dist*Cos(angle); + pos.z = 0.0f; + CColPoint point; + CEntity *ent; + if(CWorld::ProcessVerticalLine(pos+CVector(0.0f, 0.0f, 40.0f), -40.0f, point, ent, true, false, false, false, true, false, nil)){ + pos.z = point.point.z; + for(int j = 0; j < numSplashes+15; j++){ + CVector pos2 = pos; + pos2.x += CGeneral::GetRandomNumberInRange(-15.0f, 15.0f); + pos2.y += CGeneral::GetRandomNumberInRange(-15.0f, 15.0f); + if(CGeneral::GetRandomNumber() & 1) + CParticle::AddParticle(PARTICLE_RAIN_SPLASH, pos2, CVector(0.0f, 0.0f, 0.0f), nil, 0.0f, colour); + else + CParticle::AddParticle(PARTICLE_RAIN_SPLASHUP, pos2, 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 = 0.0f; + RandomTexX = 0.0f; + RandomTexY = 0.0f; + } + 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; + if (TheCamera.m_CameraAverageSpeed > 1.75f) + 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.25f * secondsElapsed / STREAK_INTEROLATION_TIME; + else if (secondsElapsed > (STREAK_LIFETIME - STREAK_INTEROLATION_TIME)) + intensity = (STREAK_LIFETIME - secondsElapsed) * 0.25f * base_intensity / STREAK_INTEROLATION_TIME; + else + intensity = base_intensity * 0.25f; + 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(0.0f, 0.0f, -12.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.04f; + Streaks[i].position.y += ((CGeneral::GetRandomNumber() & 255) - 128) * 0.04f; + 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)); + 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; +} + +#ifdef SECUROM +void CWeather::ForceHurricaneWeather() +{ + for (int i = 0; i < ARRAY_SIZE(WeatherTypesList_WithHurricanes); i++) + { + WeatherTypesList[i] = WEATHER_HURRICANE; + WeatherTypesList_WithHurricanes[i] = WEATHER_HURRICANE; + } + + CWeather::OldWeatherType = WEATHER_HURRICANE; + CWeather::NewWeatherType = WEATHER_HURRICANE; + CWeather::ForcedWeatherType = WEATHER_HURRICANE; +} +#endif diff --git a/src/miami/renderer/Weather.h b/src/miami/renderer/Weather.h new file mode 100644 index 00000000..bda57d55 --- /dev/null +++ b/src/miami/renderer/Weather.h @@ -0,0 +1,73 @@ +enum { + WEATHER_RANDOM = -1, + WEATHER_SUNNY = 0, + WEATHER_CLOUDY, + WEATHER_RAINY, + WEATHER_FOGGY, + WEATHER_EXTRA_SUNNY, + WEATHER_HURRICANE, + WEATHER_TOTAL, + + WEATHER_EXTRACOLOURS = 6 +}; + +class CWeather +{ +public: + static int32 SoundHandle; + + static int32 WeatherTypeInList; + static int16 OldWeatherType; + static int16 NewWeatherType; + static int16 ForcedWeatherType; + + static bool LightningFlash; + static bool LightningBurst; + static uint32 LightningStart; + static uint32 LightningFlashLastChange; + static uint32 WhenToPlayLightningSound; + static uint32 LightningDuration; + static int32 StreamAfterRainTimer; + + static float ExtraSunnyness; + static float Foggyness; + static float CloudCoverage; + static float Wind; + static float Rain; + static float InterpolationValue; + static float WetRoads; + static float Rainbow; + static float SunGlare; + static float WindClipped; + static float TrafficLightBrightness; + + static bool bScriptsForceRain; + + 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 AddSplashesDuringHurricane(); + static void AddStreamAfterRain(); + static void AddRain(); + static void AddHeatHaze(); + static void AddBeastie(); + + static void ForceHurricaneWeather(); +}; + +enum { + NUM_RAIN_STREAKS = 35 +}; + +struct tRainStreak +{ + CVector position; + CVector direction; + uint32 timer; +}; + +extern RwTexture* gpRainDropTex; \ No newline at end of file diff --git a/src/miami/renderer/WindModifiers.cpp b/src/miami/renderer/WindModifiers.cpp new file mode 100644 index 00000000..3bd6ac9c --- /dev/null +++ b/src/miami/renderer/WindModifiers.cpp @@ -0,0 +1,52 @@ +#include "common.h" +#include "WindModifiers.h" +#include "Camera.h" +#include "General.h" + +#define MAX_HEIGHT_DIST 40.0f +#define MIN_FADE_DIST 20.0f +#define MAX_FADE_DIST 50.0f + +CWindModifiers Array[16]; +int32 CWindModifiers::Number; + +void +CWindModifiers::RegisterOne(CVector pos, int32 type = 1) +{ + if (CWindModifiers::Number < 16 && (pos - TheCamera.GetPosition()).Magnitude() < 100.0f) { + Array[Number].m_pos = pos; + Array[Number].m_type = type; + Number++; + } +} + +bool +CWindModifiers::FindWindModifier(CVector pos, float *x, float *y) +{ + bool bWasWindModifierFound = false; + CVector2D dir; + for (int i = 0; i < Number; i++) { + if (Array[i].m_type == 1) { + float zDist = Abs(15.0f + pos.z - Array[i].m_pos.z); + + if (zDist < MAX_HEIGHT_DIST) { + float dist = (pos - Array[i].m_pos).Magnitude(); + if (dist < MAX_FADE_DIST) { + float distFade = dist < MIN_FADE_DIST ? 1.0f : 1.0f - (dist - MIN_FADE_DIST) / (MAX_FADE_DIST - MIN_FADE_DIST); + float heightFade = 1.0f - zDist / MAX_HEIGHT_DIST; + float fade = distFade * heightFade * 0.5f; + dir = (pos - Array[i].m_pos) * fade / dist; + bWasWindModifierFound = true; + } + } + } + } + + if (bWasWindModifierFound) { + float directionMult = ((CGeneral::GetRandomNumber() & 0x1F) - 16) * 0.0035f + 1.0f; + *x += dir.x * directionMult; + *y += dir.y * directionMult; + } + + return bWasWindModifierFound; +} diff --git a/src/miami/renderer/WindModifiers.h b/src/miami/renderer/WindModifiers.h new file mode 100644 index 00000000..7c2e57bd --- /dev/null +++ b/src/miami/renderer/WindModifiers.h @@ -0,0 +1,11 @@ +#pragma once + +class CWindModifiers +{ + CVector m_pos; + int32 m_type; +public: + static int32 Number; + static void RegisterOne(CVector pos, int32 windSourceType); + static bool FindWindModifier(CVector pos, float *x, float *y); +}; diff --git a/src/miami/rw/ClumpRead.cpp b/src/miami/rw/ClumpRead.cpp new file mode 100644 index 00000000..9c027dc5 --- /dev/null +++ b/src/miami/rw/ClumpRead.cpp @@ -0,0 +1,223 @@ +#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/miami/rw/Lights.cpp b/src/miami/rw/Lights.cpp new file mode 100644 index 00000000..772e1961 --- /dev/null +++ b/src/miami/rw/Lights.cpp @@ -0,0 +1,389 @@ +#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" +#include "MBlur.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; + +#ifdef EXTENDED_COLOURFILTER +#include "postfx.h" +#define USEBLURCOLORS CPostFX::UseBlurColours() +#else +#define USEBLURCOLORS CMBlur::BlurOn +#endif + +void +SetLightsWithTimeOfDayColour(RpWorld *) +{ + CVector vec1, vec2, vecsun; + RwMatrix mat; + + if(pAmbient){ + if(USEBLURCOLORS){ + AmbientLightColourForFrame.red = CTimeCycle::GetAmbientRed_Bl() * CCoronas::LightsMult; + AmbientLightColourForFrame.green = CTimeCycle::GetAmbientGreen_Bl() * CCoronas::LightsMult; + AmbientLightColourForFrame.blue = CTimeCycle::GetAmbientBlue_Bl() * CCoronas::LightsMult; + }else{ + AmbientLightColourForFrame.red = CTimeCycle::GetAmbientRed() * CCoronas::LightsMult; + AmbientLightColourForFrame.green = CTimeCycle::GetAmbientGreen() * CCoronas::LightsMult; + AmbientLightColourForFrame.blue = CTimeCycle::GetAmbientBlue() * CCoronas::LightsMult; + } + + if(USEBLURCOLORS){ + AmbientLightColourForFrame_PedsCarsAndObjects.red = CTimeCycle::GetAmbientRed_Obj_Bl() * CCoronas::LightsMult; + AmbientLightColourForFrame_PedsCarsAndObjects.green = CTimeCycle::GetAmbientGreen_Obj_Bl() * CCoronas::LightsMult; + AmbientLightColourForFrame_PedsCarsAndObjects.blue = CTimeCycle::GetAmbientBlue_Obj_Bl() * CCoronas::LightsMult; + }else{ + AmbientLightColourForFrame_PedsCarsAndObjects.red = CTimeCycle::GetAmbientRed_Obj() * CCoronas::LightsMult; + AmbientLightColourForFrame_PedsCarsAndObjects.green = CTimeCycle::GetAmbientGreen_Obj() * CCoronas::LightsMult; + AmbientLightColourForFrame_PedsCarsAndObjects.blue = CTimeCycle::GetAmbientBlue_Obj() * CCoronas::LightsMult; + } + + if(CWeather::LightningFlash && !CCullZones::CamNoRain()){ + AmbientLightColourForFrame.red = 1.0f; + AmbientLightColourForFrame.green = 1.0f; + AmbientLightColourForFrame.blue = 1.0f; + + AmbientLightColourForFrame_PedsCarsAndObjects.red = 1.0f; + AmbientLightColourForFrame_PedsCarsAndObjects.green = 1.0f; + AmbientLightColourForFrame_PedsCarsAndObjects.blue = 1.0f; + } + 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(FrontEndMenuManager.m_PrefsBrightness > 256){ + float f1 = 2.0f * (FrontEndMenuManager.m_PrefsBrightness/256.0f - 1.0f) * 0.6f + 1.0f; + float f2 = 3.0f * (FrontEndMenuManager.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); +} + +RwRGBAReal FullLight = { 1.0f, 1.0f, 1.0f, 1.0f }; + +void +SetFullAmbient(void) +{ + RpLightSetColor(pAmbient, &FullLight); +} + +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/miami/rw/Lights.h b/src/miami/rw/Lights.h new file mode 100644 index 00000000..ad355adb --- /dev/null +++ b/src/miami/rw/Lights.h @@ -0,0 +1,27 @@ +#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); +void SetFullAmbient(void); diff --git a/src/miami/rw/MemoryHeap.cpp b/src/miami/rw/MemoryHeap.cpp new file mode 100644 index 00000000..285a7c70 --- /dev/null +++ b/src/miami/rw/MemoryHeap.cpp @@ -0,0 +1,499 @@ +#include "common.h" +#include "main.h" +#include "FileMgr.h" +#include "Timer.h" +#include "ModelInfo.h" +#include "Streaming.h" +#include "FileLoader.h" +#include "MemoryHeap.h" + +// TODO(MIAMI) + +#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])); + } +} + +// MIAMI: this is empty +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/miami/rw/MemoryHeap.h b/src/miami/rw/MemoryHeap.h new file mode 100644 index 00000000..1a9a51f8 --- /dev/null +++ b/src/miami/rw/MemoryHeap.h @@ -0,0 +1,204 @@ +#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" + MEMID_STREAM_LODS = 8, // "Streamed LODs" + MEMID_STREAM_TEXUTRES = 9, // "Streamed Textures" + MEMID_STREAM_COLLISION = 10, // "Streamed Collision" + MEMID_STREAM_ANIMATION = 11, // "Streamed Animation" + MEMID_TEXTURES = 12, // "Textures" + MEMID_COLLISION = 13, // "Collision" + MEMID_PRE_ALLOC = 14, // "PreAlloc" + MEMID_GAME_PROCESS = 15, // "Game Process" + MEMID_SCRIPT = 16, // "Script" + MEMID_CARS = 17, // "Cars" + MEMID_RENDER = 18, // "Render" + MEMID_PED_ATTR = 19, // "Ped Attr" + 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/miami/rw/MemoryMgr.cpp b/src/miami/rw/MemoryMgr.cpp new file mode 100644 index 00000000..b9cff043 --- /dev/null +++ b/src/miami/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/miami/rw/MemoryMgr.h b/src/miami/rw/MemoryMgr.h new file mode 100644 index 00000000..e2962806 --- /dev/null +++ b/src/miami/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/miami/rw/NodeName.cpp b/src/miami/rw/NodeName.cpp new file mode 100644 index 00000000..a7185e4f --- /dev/null +++ b/src/miami/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/miami/rw/NodeName.h b/src/miami/rw/NodeName.h new file mode 100644 index 00000000..1a3e057b --- /dev/null +++ b/src/miami/rw/NodeName.h @@ -0,0 +1,4 @@ +#pragma once + +bool NodeNamePluginAttach(void); +char *GetFrameNodeName(RwFrame *frame); diff --git a/src/miami/rw/RwHelper.cpp b/src/miami/rw/RwHelper.cpp new file mode 100644 index 00000000..d5d0885d --- /dev/null +++ b/src/miami/rw/RwHelper.cpp @@ -0,0 +1,829 @@ +#define WITHD3D +#include "common.h" +#include + +#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 = false; // TODO: Investigate why this needs to be off (skmp) + +#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 +} + +#if !defined(DC_TEXCONV) +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::ALPHATESTREF, 3); + + rw::SetRenderState(rw::GSALPHATEST, gPS2alphaTest); +#else + // D3D stuff + RwD3D8SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); + RwD3D8SetRenderState(D3DRS_ALPHAREF, 2); +#endif +} +#else +void +DefinedState(void) { assert(false); } +#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; +} + +bool +IsClumpSkinned(RpClump *clump) +{ + RpAtomic *atomic = GetFirstAtomic(clump); + return atomic ? RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic)) : nil; +} + +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; +} + +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 + 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; +} + +#if !defined(DC_TEXCONV) +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]; + } +} +#else +void +RenderSkeleton(RpHAnimHierarchy *hier) { assert(false); } +#endif + + +RwBool Im2DRenderQuad(RwReal x1, RwReal y1, RwReal x2, RwReal y2, RwReal z, RwReal recipCamZ, RwReal uvOffset) +{ + RwIm2DVertex vx[4]; + + /* + * Render an opaque white 2D quad at the given coordinates and + * spanning a whole texture. + */ + + RwIm2DVertexSetScreenX(&vx[0], x1); + RwIm2DVertexSetScreenY(&vx[0], y1); + RwIm2DVertexSetScreenZ(&vx[0], z); + RwIm2DVertexSetIntRGBA(&vx[0], 255, 255, 255, 255); + RwIm2DVertexSetRecipCameraZ(&vx[0], recipCamZ); + RwIm2DVertexSetU(&vx[0], uvOffset, recipCamZ); + RwIm2DVertexSetV(&vx[0], uvOffset, recipCamZ); + + RwIm2DVertexSetScreenX(&vx[1], x1); + RwIm2DVertexSetScreenY(&vx[1], y2); + RwIm2DVertexSetScreenZ(&vx[1], z); + RwIm2DVertexSetIntRGBA(&vx[1], 255, 255, 255, 255); + RwIm2DVertexSetRecipCameraZ(&vx[1], recipCamZ); + RwIm2DVertexSetU(&vx[1], uvOffset, recipCamZ); + RwIm2DVertexSetV(&vx[1], 1.0f + uvOffset, recipCamZ); + + RwIm2DVertexSetScreenX(&vx[2], x2); + RwIm2DVertexSetScreenY(&vx[2], y1); + RwIm2DVertexSetScreenZ(&vx[2], z); + RwIm2DVertexSetIntRGBA(&vx[2], 255, 255, 255, 255); + RwIm2DVertexSetRecipCameraZ(&vx[2], recipCamZ); + RwIm2DVertexSetU(&vx[2], 1.0f + uvOffset, recipCamZ); + RwIm2DVertexSetV(&vx[2], uvOffset, recipCamZ); + + RwIm2DVertexSetScreenX(&vx[3], x2); + RwIm2DVertexSetScreenY(&vx[3], y2); + RwIm2DVertexSetScreenZ(&vx[3], z); + RwIm2DVertexSetIntRGBA(&vx[3], 255, 255, 255, 255); + RwIm2DVertexSetRecipCameraZ(&vx[3], recipCamZ); + RwIm2DVertexSetU(&vx[3], 1.0f + uvOffset, recipCamZ); + RwIm2DVertexSetV(&vx[3], 1.0f + uvOffset, recipCamZ); + + RwIm2DRenderPrimitive(rwPRIMTYPETRISTRIP, vx, 4); + + return TRUE; +} + +bool b_cbsUseLTM = true; + +RpAtomic *cbsCalcMeanBSphereRadiusCB(RpAtomic *atomic, void *data) +{ + RwV3d atomicPos; + + if ( b_cbsUseLTM ) + RwV3dTransformPoints(&atomicPos, &RpAtomicGetBoundingSphere(atomic)->center, 1, RwFrameGetLTM(RpClumpGetFrame(atomic->clump))); + else + RwV3dTransformPoints(&atomicPos, &RpAtomicGetBoundingSphere(atomic)->center, 1, RwFrameGetMatrix(RpClumpGetFrame(atomic->clump))); + + RwV3d temp; + RwV3dSub(&temp, &atomicPos, &((RwSphere *)data)->center); + RwReal radius = RwV3dLength(&temp) + RpAtomicGetBoundingSphere(atomic)->radius; + + if ( ((RwSphere *)data)->radius < radius ) + ((RwSphere *)data)->radius = radius; + + return atomic; +} + +RpAtomic *cbsCalcMeanBSphereCenterCB(RpAtomic *atomic, void *data) +{ + RwV3d atomicPos; + + if ( b_cbsUseLTM ) + RwV3dTransformPoints(&atomicPos, &RpAtomicGetBoundingSphere(atomic)->center, 1, RwFrameGetLTM(RpClumpGetFrame(atomic->clump))); + else + RwV3dTransformPoints(&atomicPos, &RpAtomicGetBoundingSphere(atomic)->center, 1, RwFrameGetMatrix(RpClumpGetFrame(atomic->clump))); + + RwV3dAdd(&((RwSphere *)data)->center, &((RwSphere *)data)->center, &atomicPos); + + return atomic; +} + +RpClump *RpClumpGetBoundingSphere(RpClump *clump, RwSphere *sphere, bool useLTM) +{ + RwMatrix matrix; + RwSphere result = { 0.0f, 0.0f, 0.0f, 0.0f }; + + b_cbsUseLTM = useLTM; + + if ( clump == nil || sphere == nil ) + return nil; + + sphere->radius = 0.0f; + sphere->center.x = 0.0f; + sphere->center.y = 0.0f; + sphere->center.z = 0.0f; + + RwInt32 numAtomics = RpClumpGetNumAtomics(clump); + if ( numAtomics < 1.0f ) + return nil; + + RpClumpForAllAtomics(clump, cbsCalcMeanBSphereCenterCB, &result); + + RwV3dScale(&result.center, &result.center, 1.0f/numAtomics); + + RpClumpForAllAtomics(clump, cbsCalcMeanBSphereRadiusCB, &result); + + if ( b_cbsUseLTM ) + RwMatrixInvert(&matrix, RwFrameGetLTM(RpClumpGetFrame(clump))); + else + RwMatrixInvert(&matrix, RwFrameGetMatrix(RpClumpGetFrame(clump))); + + RwV3dTransformPoints(&result.center, &result.center, 1, &matrix); + + *sphere = result; + + return clump; +} + +#if !defined(DC_TEXCONV) +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; +} +#else +void +CameraSize(RwCamera * camera, RwRect * rect, + RwReal viewWindow, RwReal aspectRatio) { assert(false); } +#endif + +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; +} + +#if !defined(DC_TEXCONV) +// 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), CVehicleModelInfo::GetMatFXEffectMaterialCB, &fx); + if(fx != rpMATFXEFFECTNULL) + RpMatFXAtomicEnableEffects(atomic); + } +} +#else +void +attachPipe(rw::Atomic *atomic) { assert(false); } +#endif + +// 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/miami/rw/RwHelper.h b/src/miami/rw/RwHelper.h new file mode 100644 index 00000000..45ce4c3f --- /dev/null +++ b/src/miami/rw/RwHelper.h @@ -0,0 +1,65 @@ +#pragma once + +extern bool bDebugRenderGroups; +extern bool gPS2alphaTest; +extern bool gBackfaceCulling; + +void OpenCharsetSafe(); +void CreateDebugFont(); +void DestroyDebugFont(); +void ObrsPrintfString(const char *str, short x, short y); +void FlushObrsPrintfs(); +void DefinedState(void); +void SetCullMode(uint32 mode); +RwFrame *GetFirstChild(RwFrame *frame); +RwObject *GetFirstObject(RwFrame *frame); +RpAtomic *GetFirstAtomic(RpClump *clump); +RwTexture *GetFirstTexture(RwTexDictionary *txd); + +bool IsClumpSkinned(RpClump *clump); +RpHAnimHierarchy *GetAnimHierarchyFromSkinClump(RpClump *clump); // get from atomic +RpHAnimHierarchy *GetAnimHierarchyFromClump(RpClump *clump); // get from frame +void SkinGetBonePositionsToTable(RpClump *clump, RwV3d *boneTable); +RpHAnimAnimation *HAnimAnimationCreateForHierarchy(RpHAnimHierarchy *hier); +RpAtomic *AtomicRemoveAnimFromSkinCB(RpAtomic *atomic, void *data); +void RenderSkeleton(RpHAnimHierarchy *hier); + +RwBool Im2DRenderQuad(RwReal x1, RwReal y1, RwReal x2, RwReal y2, RwReal z, RwReal recipCamZ, RwReal uvOffset); +RpClump *RpClumpGetBoundingSphere(RpClump *clump, RwSphere *sphere, bool useLTM); + +RwTexDictionary *RwTexDictionaryGtaStreamRead(RwStream *stream); +RwTexDictionary *RwTexDictionaryGtaStreamRead1(RwStream *stream); +RwTexDictionary *RwTexDictionaryGtaStreamRead2(RwStream *stream, RwTexDictionary *texDict); +void RwTexDictionaryGtaStreamWrite(RwStream *stream, RwTexDictionary *texDict); +void ReadVideoCardCapsFile(uint32&, uint32&, uint32&, uint32&); +bool CheckVideoCardCaps(void); +void WriteVideoCardCapsFile(void); +bool CanVideoCardDoDXT(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/miami/rw/RwMatFX.cpp b/src/miami/rw/RwMatFX.cpp new file mode 100644 index 00000000..c8384b0f --- /dev/null +++ b/src/miami/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/miami/rw/RwPS2AlphaTest.cpp b/src/miami/rw/RwPS2AlphaTest.cpp new file mode 100644 index 00000000..c0d68355 --- /dev/null +++ b/src/miami/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/miami/rw/TexRead.cpp b/src/miami/rw/TexRead.cpp new file mode 100644 index 00000000..da6ee7a0 --- /dev/null +++ b/src/miami/rw/TexRead.cpp @@ -0,0 +1,515 @@ +#pragma warning( push ) +#pragma warning( disable : 4005) +#pragma warning( pop ) +#define FORCE_PC_SCALING +#ifndef LIBRW +#define WITHD3D +#endif +#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 + +void RwTextureGtaStreamWrite(RwStream *stream, RwTexture* texture) +{ + auto fheader = stream->tell(); + // size will be written later + rw::writeChunkHeader(stream, rwID_TEXTURENATIVE, 0); + auto fbegin = stream->tell(); + + texture->streamWriteNative(stream); + + // rewrite header with correct size + auto fend = stream->tell(); + stream->seek(fheader, 0); + rw::writeChunkHeader(stream, rwID_TEXTURENATIVE, fend - fbegin); + stream->seek(fend, 0); +} + +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; +} + +void +RwTexDictionaryGtaStreamWrite(RwStream *stream, RwTexDictionary *texDict) { + rw::writeChunkHeader(stream, rwID_STRUCT, 4); + stream->writeI32(texDict->count()); + + RwTexDictionaryForAllTextures(texDict, [](RwTexture *texture, void* vstream) { + RwTextureGtaStreamWrite((RwStream*)vstream, texture); + return texture; + }, stream); +} + + +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); +extern "C" RwBool _rwD3D8CheckValidTextureFormat(RwInt32 format); +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 + +bool +CanVideoCardDoDXT(void) +{ +#ifdef LIBRW + // TODO +#ifdef RW_OPENGL + return false; +#else + return true; +#endif +#else + return _rwD3D8CheckValidTextureFormat(D3DFMT_DXT1) && _rwD3D8CheckValidTextureFormat(D3DFMT_DXT3); +#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)); +#ifdef FIX_BUGS + 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, 150, 225, 255)); +#else + 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)); +#endif + 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(); +#ifdef FIX_BUGS + CFont::SetColor(CRGBA(255, 150, 225, 255)); +#else + CFont::SetColor(CRGBA(255, 217, 106, 255)); +#endif + CFont::SetBackGroundOnlyTextOff(); + CFont::SetFontStyle(FONT_STANDARD); + 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 = FrontEndMenuManager.m_PrefsVsync; + int8 frameLimiterState = FrontEndMenuManager.m_PrefsFrameLimiter; + FrontEndMenuManager.m_PrefsVsync = 0; + FrontEndMenuManager.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 + FrontEndMenuManager.m_PrefsVsync = vsyncState; + FrontEndMenuManager.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/miami/rw/TexturePools.cpp b/src/miami/rw/TexturePools.cpp new file mode 100644 index 00000000..c2ba6cf9 --- /dev/null +++ b/src/miami/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/miami/rw/TexturePools.h b/src/miami/rw/TexturePools.h new file mode 100644 index 00000000..75187432 --- /dev/null +++ b/src/miami/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/miami/rw/TxdStore.cpp b/src/miami/rw/TxdStore.cpp new file mode 100644 index 00000000..28f1acc3 --- /dev/null +++ b/src/miami/rw/TxdStore.cpp @@ -0,0 +1,211 @@ +#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, "TexDictionary"); +} + +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 !defined(DC_TEXCONV) + if(--GetSlot(slot)->refCount <= 0) + CStreaming::RemoveTxd(slot); + #else + assert(false); + #endif +} + +void +CTxdStore::RemoveRefWithoutDelete(int slot) +{ + GetSlot(slot)->refCount--; +} + +void StoreTxd(RwTexDictionary *texDict, RwStream *stream) { + auto fheader = stream->tell(); + // size will be written later + writeChunkHeader(stream, rwID_TEXDICTIONARY, 4); + auto fbegin = stream->tell(); + + RwTexDictionaryGtaStreamWrite(stream, texDict); + + // rewrite header for correct length + auto fend = stream->tell(); + stream->seek(fheader, 0); + writeChunkHeader(stream, rwID_TEXDICTIONARY, fend - fbegin); + stream->seek(fend, 0); +} +RwTexDictionary * +LoadTxd(RwStream *stream) +{ + RwUInt32 len; + if(RwStreamFindChunk(stream, rwID_TEXDICTIONARY, &len, nil)){ + return RwTexDictionaryGtaStreamRead(stream); + } + return nullptr; +} + +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/miami/rw/TxdStore.h b/src/miami/rw/TxdStore.h new file mode 100644 index 00000000..937fd1b7 --- /dev/null +++ b/src/miami/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/miami/rw/VisibilityPlugins.cpp b/src/miami/rw/VisibilityPlugins.cpp new file mode 100644 index 00000000..c428b909 --- /dev/null +++ b/src/miami/rw/VisibilityPlugins.cpp @@ -0,0 +1,1045 @@ +#include "common.h" + +#include "RwHelper.h" +#include "templates.h" +#include "main.h" +#include "Entity.h" +#include "ModelInfo.h" +#include "Lights.h" +#include "RwHelper.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_alphaBoatAtomicList; +CLinkList CVisibilityPlugins::m_alphaEntityList; +CLinkList CVisibilityPlugins::m_alphaUnderwaterEntityList; +#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_pedLod1Dist; +float CVisibilityPlugins::ms_pedFadeDist; + +#define RENDERCALLBACK AtomicDefaultRenderCallBack + +void +CVisibilityPlugins::Initialise(void) +{ + m_alphaList.Init(NUMALPHALIST); + m_alphaList.head.item.sort = 0.0f; + m_alphaList.tail.item.sort = 100000000.0f; + + m_alphaBoatAtomicList.Init(NUMBOATALPHALIST); + m_alphaBoatAtomicList.head.item.sort = 0.0f; + m_alphaBoatAtomicList.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; + + m_alphaUnderwaterEntityList.Init(NUMALPHAUNTERWATERENTITYLIST); + m_alphaUnderwaterEntityList.head.item.sort = 0.0f; + m_alphaUnderwaterEntityList.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_alphaBoatAtomicList.Shutdown(); + m_alphaEntityList.Shutdown(); + m_alphaUnderwaterEntityList.Shutdown(); +#ifdef NEW_RENDERER + m_alphaBuildingList.Shutdown(); +#endif +} + +void +CVisibilityPlugins::InitAlphaEntityList(void) +{ + m_alphaEntityList.Clear(); + m_alphaBoatAtomicList.Clear(); + m_alphaUnderwaterEntityList.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 + if(e->bUnderwater && m_alphaUnderwaterEntityList.InsertSorted(item)) + return true; + return !!m_alphaEntityList.InsertSorted(item); +} + +void +CVisibilityPlugins::InitAlphaAtomicList(void) +{ + m_alphaList.Clear(); +} + +bool +CVisibilityPlugins::InsertAtomicIntoSortedList(RpAtomic *a, float dist) +{ + AlphaObjectInfo item; + item.atomic = a; + item.sort = dist; + return !!m_alphaList.InsertSorted(item); +} + +bool +CVisibilityPlugins::InsertAtomicIntoBoatSortedList(RpAtomic *a, float dist) +{ + AlphaObjectInfo item; + item.atomic = a; + item.sort = dist; + return !!m_alphaBoatAtomicList.InsertSorted(item); +} + +// 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) + +#if !defined(DC_TEXCONV) +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_pedLod1Dist = sq(60.0f * TheCamera.LODDistMultiplier); + ms_pedFadeDist = sq(70.0f * TheCamera.LODDistMultiplier); +} +#else +void +CVisibilityPlugins::SetRenderWareCamera(RwCamera *camera) { assert(false); } +#endif + +static float DistToCameraSq; +static float PitchToCamera; + +void +CVisibilityPlugins::SetupVehicleVariables(RpClump *vehicle) +{ + if (RwObjectGetType((RwObject*)vehicle) != rpCLUMP) + return; + DistToCameraSq = GetDistanceSquaredFromCamera(RpClumpGetFrame(vehicle)); + RwV3d distToCam; + RwV3dSub(&distToCam, ms_pCameraPosn, &RwFrameGetMatrix(RpClumpGetFrame(vehicle))->pos); + float dist2d = Sqrt(SQR(distToCam.x) + SQR(distToCam.y)); + PitchToCamera = Atan2(distToCam.z, dist2d); +} + +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::RenderAtomicList(CLinkList &list) +{ + CLink *node; + for(node = list.tail.prev; node != &list.head; node = node->prev) + RENDERCALLBACK(node->item.atomic); +} + +void +CVisibilityPlugins::RenderAlphaAtomics(void) +{ + RenderAtomicList(m_alphaList); +} + +void +CVisibilityPlugins::RenderBoatAlphaAtomics(void) +{ + SetCullMode(rwCULLMODECULLNONE); + RenderAtomicList(m_alphaBoatAtomicList); + SetCullMode(rwCULLMODECULLBACK); +} + +#if !defined(DC_TEXCONV) +void +CVisibilityPlugins::RenderFadingEntities(CLinkList &list) +{ + CLink *node; + CSimpleModelInfo *mi; + for(node = list.tail.prev; node != &list.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()); + if(mi->GetModelType() == MITYPE_SIMPLE && mi->m_noZwrite) + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, FALSE); + + if(mi->m_isAlphaTest && !mi->IsClump()) { + rw::SetRenderState(rw::ALPHATESTREF, 129); + } + + 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); + + if(mi->m_isAlphaTest && !mi->IsClump()) { + rw::SetRenderState(rw::ALPHATESTREF, 3); + } + + if(mi->GetModelType() == MITYPE_SIMPLE && mi->m_noZwrite) + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)TRUE); + } +} +#else +void +CVisibilityPlugins::RenderFadingEntities(CLinkList &list) +{ + assert(false); +} +#endif + +void +CVisibilityPlugins::RenderFadingEntities(void) +{ + RenderFadingEntities(m_alphaEntityList); + RenderBoatAlphaAtomics(); +} + +void +CVisibilityPlugins::RenderFadingUnderwaterEntities(void) +{ + RenderFadingEntities(m_alphaUnderwaterEntityList); +} + +#if !defined(DC_TEXCONV) +RpAtomic* +CVisibilityPlugins::RenderWheelAtomicCB(RpAtomic *atomic) +{ + RpAtomic *lodatm; + float len; + CSimpleModelInfo *mi; + + mi = GetAtomicModelInfo(atomic); + len = Sqrt(DistToCameraSq); + lodatm = mi->GetAtomicFromDistance(len * TheCamera.LODDistMultiplier / VEHICLE_LODDIST_MULTIPLIER); + if(lodatm){ + if(RpAtomicGetGeometry(lodatm) != RpAtomicGetGeometry(atomic)) + RpAtomicSetGeometry(atomic, RpAtomicGetGeometry(lodatm), rpATOMICSAMEBOUNDINGSPHERE); + RENDERCALLBACK(atomic); + } + return atomic; +} +#else +RpAtomic* +CVisibilityPlugins::RenderWheelAtomicCB(RpAtomic *atomic) { assert(false); } +#endif + +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; +} + +#if !defined(DC_TEXCONV) +RpAtomic* +CVisibilityPlugins::RenderWeaponCB(RpAtomic *atomic) +{ + RwMatrix *m; + RwV3d view; + float maxdist, distsq; + CSimpleModelInfo *mi; + + mi = GetAtomicModelInfo(atomic); + m = RwFrameGetLTM(RpAtomicGetFrame(atomic)); + RwV3dSub(&view, RwMatrixGetPos(m), ms_pCameraPosn); + maxdist = mi->GetLodDistance(0); + distsq = RwV3dDotProduct(&view, &view); + if(distsq < maxdist*maxdist) + RENDERCALLBACK(atomic); + return atomic; +} +#else +RpAtomic* +CVisibilityPlugins::RenderWeaponCB(RpAtomic *atomic) { assert(false); } +#endif + +#if !defined(DC_TEXCONV) +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); + + 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); + } + + if(mi->m_additive) + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA); + + return atomic; +} +#else +RpAtomic* +CVisibilityPlugins::RenderFadingAtomic(RpAtomic *atomic, float camdist) +{ + assert(false); +} +#endif + + + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq < ms_vehicleLod0Dist){ + flags = GetAtomicId(atomic); + if(DistToCameraSq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0 && PitchToCamera < 0.2f){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*DistToCameraSq < dot*dot)) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq < ms_vehicleLod0Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(DistToCameraSq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0 && PitchToCamera < 0.2f) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*DistToCameraSq < dot*dot)) + return atomic; + + if(flags & ATOMIC_FLAG_DRAWLAST){ + // sort before clump + if(!InsertAtomicIntoSortedList(atomic, DistToCameraSq - 0.0001f)) + RENDERCALLBACK(atomic); + }else{ + if(!InsertAtomicIntoSortedList(atomic, DistToCameraSq + dot)) + RENDERCALLBACK(atomic); + } + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq < ms_bigVehicleLod0Dist){ + flags = GetAtomicId(atomic); + if(DistToCameraSq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0 && PitchToCamera < 0.2f){ + 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 dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq < ms_bigVehicleLod0Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(DistToCameraSq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0 && PitchToCamera < 0.2f) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*DistToCameraSq < dot*dot)) + return atomic; + + if(!InsertAtomicIntoSortedList(atomic, DistToCameraSq + dot)) + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailCB_Boat(RpAtomic *atomic) +{ + if(DistToCameraSq < ms_vehicleLod0Dist) + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleHiDetailAlphaCB_Boat(RpAtomic *atomic) +{ + if(DistToCameraSq < ms_vehicleLod0Dist){ + if(GetAtomicId(atomic) & ATOMIC_FLAG_DRAWLAST){ + if(!InsertAtomicIntoBoatSortedList(atomic, DistToCameraSq)) + RENDERCALLBACK(atomic); + }else + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleLoDetailCB_Boat(RpAtomic *atomic) +{ + RpClump *clump; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + if(DistToCameraSq >= ms_vehicleLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleLowDetailCB_BigVehicle(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq >= ms_bigVehicleLod0Dist && + DistToCameraSq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + if(DistToCameraSq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0 && PitchToCamera < 0.2f){ + 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 dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq >= ms_bigVehicleLod0Dist && + DistToCameraSq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f) + if(DistToCameraSq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0 && PitchToCamera < 0.2f) + return atomic; + + if(!InsertAtomicIntoSortedList(atomic, DistToCameraSq + dot)) + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleReallyLowDetailCB(RpAtomic *atomic) +{ + RpClump *clump; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + if(DistToCameraSq >= ms_vehicleLod0Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; + +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleReallyLowDetailCB_BigVehicle(RpAtomic *atomic) +{ + if(DistToCameraSq >= ms_bigVehicleLod1Dist) + RENDERCALLBACK(atomic); + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderTrainHiDetailCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + if(DistToCameraSq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0 && PitchToCamera < 0.2f){ + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*DistToCameraSq < dot*dot)) + return atomic; + } + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderTrainHiDetailAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float dot; + uint32 flags; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq < ms_bigVehicleLod1Dist){ + flags = GetAtomicId(atomic); + dot = GetDotProductWithCameraVector(RwFrameGetLTM(RpAtomicGetFrame(atomic)), + RwFrameGetLTM(clumpframe), flags); + if(DistToCameraSq > ms_cullCompsDist && (flags & ATOMIC_FLAG_NOCULL) == 0 && PitchToCamera < 0.2f) + if(dot > 0.0f && ((flags & ATOMIC_FLAG_ANGLECULL) || 0.1f*DistToCameraSq < dot*dot)) + return atomic; + + if(flags & ATOMIC_FLAG_DRAWLAST){ + if(!InsertAtomicIntoSortedList(atomic, DistToCameraSq)) + RENDERCALLBACK(atomic); + }else{ + if(!InsertAtomicIntoSortedList(atomic, DistToCameraSq + dot)) + RENDERCALLBACK(atomic); + } + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleRotorAlphaCB(RpAtomic *atomic) +{ + RwFrame *clumpframe; + float dot; + RwV3d cam2atm; + + clumpframe = RpClumpGetFrame(RpAtomicGetClump(atomic)); + if(DistToCameraSq < ms_bigVehicleLod1Dist){ + RwV3dSub(&cam2atm, &RwFrameGetLTM(RpAtomicGetFrame(atomic))->pos, ms_pCameraPosn); + dot = RwV3dDotProduct(&cam2atm, &RwFrameGetLTM(clumpframe)->at); + if(!InsertAtomicIntoSortedList(atomic, DistToCameraSq + dot*20.0f)) + RENDERCALLBACK(atomic); + } + return atomic; +} + +RpAtomic* +CVisibilityPlugins::RenderVehicleTailRotorAlphaCB(RpAtomic *atomic) +{ + RwMatrix *clumpMat, *atmMat; + float dot; + RwV3d cam2atm; + + if(DistToCameraSq < ms_bigVehicleLod0Dist){ + atmMat = RwFrameGetLTM(RpAtomicGetFrame(atomic)); + clumpMat = RwFrameGetLTM(RpClumpGetFrame(RpAtomicGetClump(atomic))); + RwV3dSub(&cam2atm, &atmMat->pos, ms_pCameraPosn); + dot = RwV3dDotProduct(&cam2atm, &clumpMat->up) + RwV3dDotProduct(&cam2atm, &clumpMat->right); + if(!InsertAtomicIntoSortedList(atomic, DistToCameraSq - dot)) + RENDERCALLBACK(atomic); + } + return atomic; +} + +#if !defined(DC_TEXCONV) +RpAtomic* +CVisibilityPlugins::RenderPlayerCB(RpAtomic *atomic) +{ + if(CWorld::Players[0].m_pSkinTexture) + RpGeometryForAllMaterials(RpAtomicGetGeometry(atomic), SetTextureCB, CWorld::Players[0].m_pSkinTexture); + RENDERCALLBACK(atomic); + return atomic; +} +#else +RpAtomic* +CVisibilityPlugins::RenderPlayerCB(RpAtomic *atomic) +{ + assert(false); +} +#endif + +RpAtomic* +CVisibilityPlugins::RenderPedCB(RpAtomic *atomic) +{ + RpClump *clump; + float dist; + int32 alpha; + + clump = RpAtomicGetClump(atomic); + dist = GetDistanceSquaredFromCamera(RpClumpGetFrame(clump)); + if(dist < ms_pedLod1Dist){ + alpha = GetClumpAlpha(clump); + if(alpha == 255) + RENDERCALLBACK(atomic); + else + RenderAlphaAtomic(atomic, alpha); + } + return atomic; +} + +float +CVisibilityPlugins::GetDistanceSquaredFromCamera(RwV3d *pos) +{ + RwV3d dist; + RwV3dSub(&dist, pos, ms_pCameraPosn); + return RwV3dDotProduct(&dist, &dist); +} + +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); + 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, RwInt32, RwInt32) +{ + ATOMICEXT(object)->modelInfo = nil; + return object; +} + +void* +CVisibilityPlugins::AtomicDestructor(void *object, RwInt32, RwInt32) +{ + return object; +} + +void* +CVisibilityPlugins::AtomicCopyConstructor(void *dst, const void *src, RwInt32, RwInt32) +{ + *ATOMICEXT(dst) = *ATOMICEXT(src); + return dst; +} + +void +CVisibilityPlugins::SetAtomicModelInfo(RpAtomic *atomic, + CSimpleModelInfo *modelInfo) +{ + AtomicExt *ext = ATOMICEXT(atomic); + ext->modelInfo = modelInfo; +} + +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; // not necessary + RpAtomicSetRenderCallBack(atomic, cb); +} + +// +// Frame +// + +void* +CVisibilityPlugins::FrameConstructor(void *object, RwInt32, RwInt32) +{ + FRAMEEXT(object)->id = 0; + return object; +} + +void* +CVisibilityPlugins::FrameDestructor(void *object, RwInt32, RwInt32) +{ + return object; +} + +void* +CVisibilityPlugins::FrameCopyConstructor(void *dst, const void *src, RwInt32, RwInt32) +{ + *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, RwInt32, RwInt32) +{ + ClumpExt *ext = CLUMPEXT(object); + ext->visibilityCB = DefaultVisibilityCB; + ext->alpha = 0xFF; + return object; +} + +void* +CVisibilityPlugins::ClumpDestructor(void *object, RwInt32, RwInt32) +{ + return object; +} + +void* +CVisibilityPlugins::ClumpCopyConstructor(void *dst, const void *src, RwInt32, RwInt32) +{ + 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/miami/rw/VisibilityPlugins.h b/src/miami/rw/VisibilityPlugins.h new file mode 100644 index 00000000..4a53b7f3 --- /dev/null +++ b/src/miami/rw/VisibilityPlugins.h @@ -0,0 +1,152 @@ +#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_alphaBoatAtomicList; + static CLinkList m_alphaEntityList; + static CLinkList m_alphaUnderwaterEntityList; +#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_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 bool InsertAtomicIntoBoatSortedList(RpAtomic *a, float dist); + + static void SetRenderWareCamera(RwCamera *camera); + static void SetupVehicleVariables(RpClump *vehicle); + + static RpAtomic *RenderWheelAtomicCB(RpAtomic *atomic); + static RpAtomic *RenderObjNormalAtomic(RpAtomic *atomic); + static RpAtomic *RenderAlphaAtomic(RpAtomic *atomic, int alpha); + static RpAtomic *RenderWeaponCB(RpAtomic *atomic); + 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 *RenderVehicleHiDetailAlphaCB_Boat(RpAtomic *atomic); + static RpAtomic *RenderVehicleLoDetailCB_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 *RenderVehicleRotorAlphaCB(RpAtomic *atomic); + static RpAtomic *RenderVehicleTailRotorAlphaCB(RpAtomic *atomic); + + static RpAtomic *RenderPlayerCB(RpAtomic *atomic); + static RpAtomic *RenderPedCB(RpAtomic *atomic); // for skinned models with only one clump + + static void RenderAtomicList(CLinkList &list); + static void RenderAlphaAtomics(void); + static void RenderBoatAlphaAtomics(void); + static void RenderFadingEntities(CLinkList &list); + static void RenderFadingEntities(void); + static void RenderFadingUnderwaterEntities(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(RwV3d *pos); + 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, RwInt32 offset, RwInt32 len); + static void *AtomicDestructor(void *object, RwInt32 offset, RwInt32 len); + static void *AtomicCopyConstructor(void *dst, const void *src, + RwInt32 offset, RwInt32 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, RwInt32 offset, RwInt32 len); + static void *FrameDestructor(void *object, RwInt32 offset, RwInt32 len); + static void *FrameCopyConstructor(void *dst, const void *src, + RwInt32 offset, RwInt32 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, RwInt32 offset, RwInt32 len); + static void *ClumpDestructor(void *object, RwInt32 offset, RwInt32 len); + static void *ClumpCopyConstructor(void *dst, const void *src, + RwInt32 offset, RwInt32 len); + static int32 ms_clumpPluginOffset; + + static bool PluginAttach(void); +}; + +RpMaterial *SetAlphaCB(RpMaterial *material, void *data); diff --git a/src/miami/save/Date.cpp b/src/miami/save/Date.cpp new file mode 100644 index 00000000..ca75bb5e --- /dev/null +++ b/src/miami/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/miami/save/Date.h b/src/miami/save/Date.h new file mode 100644 index 00000000..15646c23 --- /dev/null +++ b/src/miami/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/miami/save/GenericGameStorage.cpp b/src/miami/save/GenericGameStorage.cpp new file mode 100644 index 00000000..0baf799f --- /dev/null +++ b/src/miami/save/GenericGameStorage.cpp @@ -0,0 +1,1273 @@ +#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 "SetPieces.h" +#include "Stats.h" +#include "Streaming.h" +#include "Timer.h" +#include "TimeStep.h" +#include "Weather.h" +#include "World.h" +#include "Zones.h" +#include "Timecycle.h" +#include "Fluff.h" + +#include "vmu/vmu.h" + +#define BLOCK_COUNT 22 +#define SIZE_OF_SIMPLEVARS 0xE4 + +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]; + +bool b_FoundRecentSavedGameWantToLoad; +bool JustLoadedDontFadeInYet; +bool StillToFadeOut; +uint32 TimeStartedCountingForFade; +uint32 TimeToStayFadedBeforeFadeOut = 1750; + +int32 RadioStationPosition[NUM_RADIOS]; + +void +InitRadioStationPositionList() +{ + for (int i = 0; i < NUM_RADIOS; i++) + RadioStationPosition[i] = -1; +} + +int32 +GetSavedRadioStationPosition(int32 station) +{ + return RadioStationPosition[station]; +} + +void +PopulateRadioStationPositionList() +{ + for (int i = 0; i < NUM_RADIOS; i++) + RadioStationPosition[i] = DMAudio.GetRadioPosition(i); +} + +#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 {\ + size = C_PcSave::PcClassLoadRoutine(file, work_buff); \ + if (!size) {\ + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; \ + if (!CloseFile(file)) { \ + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; \ + } \ + 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, msg)\ +do {\ + size = 0;\ + buf = work_buff;\ + reserved = 0;\ + MakeSpaceForSizeInBufferPointer(presize, buf, postsize);\ + save_func(buf, &size);\ + debug(msg"== %i \n", size);\ + CopySizeAndPreparePointer(presize, buf, postsize, reserved, size);\ + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff)) { \ + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; \ + if (!CloseFile(file)) \ + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; \ + 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[0] ? CStats::LastMissionPassedName : "ITBEG"); + AsciiToUnicode("...'", suffix); + suffix[3] = L'\0'; +#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, 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 + WriteDataToBufferPointer(buf, CGame::currArea); + WriteDataToBufferPointer(buf, CVehicle::bAllTaxisHaveNitro); + WriteDataToBufferPointer(buf, CPad::bInvertLook4Pad); + WriteDataToBufferPointer(buf, CTimeCycle::m_ExtraColour); + WriteDataToBufferPointer(buf, CTimeCycle::m_bExtraColourOn); + WriteDataToBufferPointer(buf, CTimeCycle::m_ExtraColourInter); + PopulateRadioStationPositionList(); + WriteDataToBufferPointer(buf, RadioStationPosition); + 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); + debug("ScriptSize== %i \n", size); + CopySizeAndPreparePointer(presize, buf, postsize, reserved, size); + if (!PcSaveHelper.PcClassSaveRoutine(file, work_buff, buf - work_buff)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; + return false; + } + + totalSize = buf - work_buff; + + // Save the rest + WriteSaveDataBlock(CPools::SavePedPool, "PedPoolSize"); + WriteSaveDataBlock(CGarages::Save, "GaragesSize"); + WriteSaveDataBlock(CGameLogic::Save, "GameLogicSize"); + WriteSaveDataBlock(CPools::SaveVehiclePool, "VehPoolSize"); + WriteSaveDataBlock(CPools::SaveObjectPool, "ObjectPoolSize"); + WriteSaveDataBlock(ThePaths.Save, "ThePathsSize"); + WriteSaveDataBlock(CCranes::Save, "CranesSize"); + WriteSaveDataBlock(CPickups::Save, "PickUpsSize"); + WriteSaveDataBlock(gPhoneInfo.Save, "PhoneInfoSize"); + WriteSaveDataBlock(CRestart::SaveAllRestartPoints, "RestartPointsBufferSize"); + WriteSaveDataBlock(CRadar::SaveAllRadarBlips, "RadarBlipsBufferSize"); + WriteSaveDataBlock(CTheZones::SaveAllZones, "AllZonesBufferSize"); + WriteSaveDataBlock(CGangs::SaveAllGangData, "AllGangDataSize"); + WriteSaveDataBlock(CTheCarGenerators::SaveAllCarGenerators, "AllCarGeneratorsSize"); + WriteSaveDataBlock(CParticleObject::SaveParticle, "ParticlesSize"); + WriteSaveDataBlock(cAudioScriptObject::SaveAllAudioScriptObjects, "AllAudioScriptObjectsSize"); + WriteSaveDataBlock(CScriptPaths::Save, "ScriptPathsSize"); + WriteSaveDataBlock(CWorld::Players[CWorld::PlayerInFocus].SavePlayerInfo, "PlayerInfoSize"); + WriteSaveDataBlock(CStats::SaveStats, "StatsSize"); + WriteSaveDataBlock(CSetPieces::Save, "SetPiecesSize"); + WriteSaveDataBlock(CStreaming::MemoryCardSave, "StreamingSize"); + WriteSaveDataBlock(CPedType::Save, "PedTypeSize"); + + // 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)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; + return false; + } + totalSize += size; + } + } + + // Write checksum and close + bool err = CFileMgr::Write(file, (const char *) &CheckSum, sizeof(CheckSum)) != sizeof(CheckSum); + if (err || CFileMgr::GetErrorReadWrite(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + if (!CloseFile(file)) + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CLOSE; + + return false; + } + + CPad::FixPadsAfterSave(); + 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(); + + file = CFileMgr::OpenFile(LoadFileName, "rb"); + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } + size = C_PcSave::PcClassLoadRoutine(file, work_buff); + if (!size) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_READ; + if (!CloseFile(file)) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_CLOSE; + } + return false; + } + 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); +#ifdef SECUROM + if (CTimer::m_FrameCounter > 72000){ + buf += align4bytes(4); + } +#endif + ReadDataFromBufferPointer(buf, CWeather::InterpolationValue); + 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 + ReadDataFromBufferPointer(buf, CGame::currArea); + ReadDataFromBufferPointer(buf, CVehicle::bAllTaxisHaveNitro); +#ifdef LOAD_INI_SETTINGS + buf += align4bytes(sizeof(CPad::bInvertLook4Pad)); +#else + ReadDataFromBufferPointer(buf, CPad::bInvertLook4Pad); +#endif + ReadDataFromBufferPointer(buf, CTimeCycle::m_ExtraColour); + ReadDataFromBufferPointer(buf, CTimeCycle::m_bExtraColourOn); + ReadDataFromBufferPointer(buf, CTimeCycle::m_ExtraColourInter); + ReadDataFromBufferPointer(buf, RadioStationPosition); + 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 GameLogic \n", CGameLogic::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 ScriptPaths \n", CScriptPaths::Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Player Info \n", CWorld::Players[CWorld::PlayerInFocus].LoadPlayerInfo); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Stats \n", CStats::LoadStats); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Set Pieces \n", CSetPieces::Load); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading Streaming Stuff \n", CStreaming::MemoryCardLoad); + LoadSaveDataBlock(); + ReadDataFromBlock("Loading PedType Stuff \n", CPedType::Load); + + DMAudio.SetMusicMasterVolume(FrontEndMenuManager.m_PrefsMusicVolume); + DMAudio.SetEffectsMasterVolume(FrontEndMenuManager.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() +{ + CCollision::SortOutCollisionAfterLoad(); + CStreaming::LoadSceneCollision(TheCamera.GetPosition()); + CStreaming::LoadScene(TheCamera.GetPosition()); + CGame::TidyUpMemory(true, false); + StillToFadeOut = true; + JustLoadedDontFadeInYet = true; + TheCamera.Fade(0.0f, FADE_OUT); + 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", slot==7?"GTAVCSF":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; + #if !defined(RW_DC) + sprintf(filename, "%s%i%s", DefaultPCSaveFileName, slot + 1, ".b"); + #else + sprintf(filename, "%s%i%s", slot==7?"GTAVCSF":DefaultPCSaveFileName, slot + 1, ".b"); + #endif + 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; + blocksize = C_PcSave::PcClassLoadRoutine(file, work_buff); + if (blocksize == 0) { + 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() +{ + int file = CFileMgr::OpenFile(LoadFileName, "rb"); + if (file == 0) { + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_LOAD_OPEN; + return false; + } + + uint32_t size = C_PcSave::PcClassLoadRoutine(file, work_buff); + uint8 *buf = work_buff; + + if (size == 0 || 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); + CStreaming::RemoveUnusedBigBuildings(CGame::currLevel); + CStreaming::RemoveUnusedBuildings(CGame::currLevel); + + 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, + SAVE_TYPE_STEAM = 16, +}; + +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 + + buf += 0x40 + sizeof(int32) + sizeof(int32) + sizeof(float) * 3; + + int8 steam_byte; + ReadDataFromBufferPointer(buf, steam_byte); + + if (steam_byte == -3) + save_type |= SAVE_TYPE_STEAM; + + 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); // game logic + 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 == 1000) + 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 += 1964; // skip everything before the first garage + buf += save_type & SAVE_TYPE_32_BIT ? 28 : 40; // skip first garage up to m_vecCorner1 + + CVector2D vecCorner1; + float fInfZ, fSupZ; + + ReadBuf(buf, vecCorner1); + ReadBuf(buf, fInfZ); + SkipBuf(buf, sizeof(CVector2D)); + SkipBuf(buf, sizeof(CVector2D)); + ReadBuf(buf, fSupZ); + + // SET_GARAGE -914.129028 -1263.540039 10.706000 -907.137024 -1246.625977 -906.299988 -1266.900024 14.421000 + if (vecCorner1.x == -914.129028f && vecCorner1.y == -1263.540039f && + fInfZ == 10.706000f && fSupZ == 14.421000f) + save_type |= SAVE_TYPE_MSVC; + else + save_type |= SAVE_TYPE_GCC; + + return save_type; +} + +static void +FixSimpleVarsAndScripts(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = *size; + uint32 written = *size - (sizeof(int8) + 3); + + uint32 pre_steam = 0x40 + sizeof(int32) + sizeof(int32) + sizeof(float) * 3; + uint32 post_steam = *size - (sizeof(int8) + 3) - pre_steam; + + CopyBuf(buf, buf2, pre_steam); + SkipBuf(buf, sizeof(int8) + 3); + CopyBuf(buf, buf2, post_steam); + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixGarages(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + // hardcoded: 7876 + // x86 msvc: 7340 + // x86 gcc: 7020 + // amd64 msvc: 7852 + // amd64 gcc: 7660 + + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read; + uint32 written = 7340; + + if (save_type & SAVE_TYPE_32_BIT && save_type & SAVE_TYPE_GCC) + read = 7020; + else if (save_type & SAVE_TYPE_64_BIT && save_type & SAVE_TYPE_GCC) + read = 7660; + else + read = 7852; + + 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++) + { + for (int32 j = 0; j < TOTAL_HIDEOUT_GARAGES; j++) + { + 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); + } + } + } + else + { + CopyBuf(buf, buf2, sizeof(CStoredCar) * NUM_GARAGE_STORED_CARS * TOTAL_HIDEOUT_GARAGES); + } + + for (int32 i = 0; i < NUM_GARAGES; i++) + { + CopyBuf(buf, buf2, 1 * 7); + SkipBoth(buf, buf2, 1); + 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, sizeof(CVector2D) * 3 + 4 * 17 + 1); + SkipBoth(buf, buf2, 3); + SkipBuf(buf, ptrsize); + SkipBuf(buf2, 4); + + 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); + + *size = 7876; +} + +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) + 0x3E0; // 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, 14 * 4 + sizeof(CVector) * 3 + sizeof(CVector2D)); + SkipBuf(buf, 4); + 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 = 0x5400 + sizeof(uint16) + sizeof(uint16) + sizeof(int32) * NUMCOLLECTEDPICKUPS; // sizeof(aPickUps) + uint32 written = 0x4440 + sizeof(uint16) + sizeof(uint16) + sizeof(int32) * NUMCOLLECTEDPICKUPS; // see PICKUPS_SAVE_SIZE + + for (int32 i = 0; i < NUMPICKUPS; i++) + { + CopyBuf(buf, buf2, sizeof(CVector) + 4); + CopyPtr(buf, buf2); + CopyPtr(buf, buf2); + CopyBuf(buf, buf2, 4 * 2 + 2 * 3 + 8 + 1 * 3); + SkipBuf(buf, 7); + SkipBuf(buf2, 3); + } + + 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 +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 = 0x98 * (numObjects + 1) + 4; // sizeof(CParticleObject) + uint32 written = 0x84 * (numObjects + 1) + 4; // see PARTICLE_OBJECT_SIZEOF + + for (int32 i = 0; i < numObjects; i++) + { + // CPlaceable + 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, 0x98); // sizeof(CParticleObject) + SkipBuf(buf2, 0x84); // see PARTICLE_OBJECT_SIZEOF + + *size = 0; + + assert(buf - buf_start == read); + assert(buf2 - buf2_start == written); + + *size = written; +} + +static void +FixScriptPaths(uint8 save_type, uint8 *buf, uint8 *buf2, uint32 *size) +{ + uint8 *buf_start = buf; + uint8 *buf2_start = buf2; + uint32 read = 0x108; // sizeof(CScriptPath) * 3 + uint32 written = 0x9C; // see SCRIPTPATHS_SAVE_SIZE + + for (int32 i = 0; i < 3; i++) + { + int32 numNodes; + ReadBuf(buf, numNodes); + WriteBuf(buf2, numNodes); + SkipBuf(buf, 4); + SkipPtr(buf, buf2); + CopyBuf(buf, buf2, 4 * 5); + SkipBuf(buf, 4); + + for (int32 i = 0; i < 6; i++) + { + CopyPtr(buf, buf2); + } + + for (int32 i = 0; i < numNodes; i++) + { + CopyBuf(buf, buf2, sizeof(CPlaneNode)); + read += sizeof(CPlaneNode); + written += sizeof(CPlaneNode); + } + } + + *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 && !(save_type & SAVE_TYPE_STEAM)) + 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 + + if (save_type & SAVE_TYPE_STEAM && save_type & SAVE_TYPE_MSVC && save_type & SAVE_TYPE_32_BIT) { + memset(work_buff2, 0, sizeof(work_buff2)); + buf2 = work_buff2; + FixSimpleVarsAndScripts(save_type, buf, buf2, &size); + if (!PcSaveHelper.PcClassSaveRoutine(file_out, work_buff2, size)) + goto fail; + totalSize += size; + } else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // ped pool + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // garages + if (!(save_type & SAVE_TYPE_STEAM && save_type & SAVE_TYPE_MSVC && save_type & SAVE_TYPE_32_BIT)) + FixSaveDataBlock(FixGarages, file_out, size); + else + WriteSavaDataBlockNoFunc(buf, file_out, size); + + LoadSaveDataBlockNoCheck(buf, file_in, size); // game logic + WriteSavaDataBlockNoFunc(buf, file_out, size); + + 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 + 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); // script paths + if (save_type & SAVE_TYPE_64_BIT) + FixSaveDataBlock(FixScriptPaths, file_out, size); + else + 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); // set pieces + 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 && AllowMissionReplay != MISSION_RETRY_STAGE_WAIT_FOR_TIMER_AFTER_RESTART) { + debug("SaveGameForPause failed during AllowMissionReplay %d", AllowMissionReplay); + return false; + } + if (type != SAVE_TYPE_QUICKSAVE_FOR_MISSION_REPLAY && WaitForSave > CTimer::GetTimeInMilliseconds()) { + debug("SaveGameForPause failed WaitForSave"); + return false; + } + WaitForSave = 0; + if (gGameState != GS_PLAYING_GAME || (CTheScripts::bAlreadyRunningAMissionScript && type != SAVE_TYPE_QUICKSAVE_FOR_SCRIPT_ON_A_MISSION)) { + DisplaySaveResult(3, CStats::LastMissionPassedName); + return false; + } + debug("SaveGameForPause ******************************** %s doSave %d", CStats::LastMissionPassedName, !CTheScripts::bAlreadyRunningAMissionScript); + 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/miami/save/GenericGameStorage.h b/src/miami/save/GenericGameStorage.h new file mode 100644 index 00000000..bebf426a --- /dev/null +++ b/src/miami/save/GenericGameStorage.h @@ -0,0 +1,66 @@ +#pragma once + +#include "Game.h" +#include "PCSave.h" + +#define SLOT_COUNT (8) + +void InitRadioStationPositionList(); +int32 GetSavedRadioStationPosition(int32 station); +void PopulateRadioStationPositionList(); +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 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]; + +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, + SAVE_TYPE_QUICKSAVE_FOR_SCRIPT, + SAVE_TYPE_QUICKSAVE_FOR_SCRIPT_ON_A_MISSION +}; + +#endif diff --git a/src/miami/save/MemoryCard.cpp b/src/miami/save/MemoryCard.cpp new file mode 100644 index 00000000..d6e95d33 --- /dev/null +++ b/src/miami/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/miami/save/MemoryCard.h b/src/miami/save/MemoryCard.h new file mode 100644 index 00000000..bae605ff --- /dev/null +++ b/src/miami/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/miami/save/PCSave.cpp b/src/miami/save/PCSave.cpp new file mode 100644 index 00000000..6d6fac90 --- /dev/null +++ b/src/miami/save/PCSave.cpp @@ -0,0 +1,252 @@ +#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" + +#include "minilzo.h" +#include "main.h" + +#include "vmu/vmu.h" + +const char* _psGetUserFilesFolder(); + +C_PcSave PcSaveHelper; + +void +C_PcSave::SetSaveDirectory(const char *path) +{ + #if defined(RW_DC) + sprintf(DefaultPCSaveFileName, "%s/%s", path, "GTAVCsf"); + #else + sprintf(DefaultPCSaveFileName, "%s\\%s", path, "GTAVCsf"); + #endif +} + +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; +} + +int8 +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 2; + } + return 0; + } + + return 2; + } + PcSaveHelper.nErrorCode = SAVESTATUS_ERR_SAVE_CREATE; + return 2; +} + +bool +C_PcSave::PcClassSaveRoutine(int32 file, uint8 *data, uint32 size) +{ + void* wrkmem = malloc(LZO1X_1_MEM_COMPRESS); + uint8* compressed = (uint8*)malloc(size*2); + lzo_uint compressed_size; + int crv = lzo1x_1_compress(data, size, compressed, &compressed_size, wrkmem); + free(wrkmem); + + if (crv == LZO_E_OK) { + uint32_t compressed_size32 = compressed_size | 0x80000000; + bool err = CFileMgr::Write(file, (const char*)&compressed_size32, sizeof(compressed_size32)) != sizeof(compressed_size32); + if (err || CFileMgr::GetErrorReadWrite(file)) { + free(compressed); + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + + err = CFileMgr::Write(file, (const char*)compressed, compressed_size) != compressed_size; + free(compressed); + if (err || CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + } else if (crv == LZO_E_NOT_COMPRESSIBLE) { + free(compressed); + uint32_t compressed_size32 = size; + bool err = CFileMgr::Write(file, (const char*)&compressed_size32, sizeof(compressed_size32)) != sizeof(compressed_size32); + if (err || CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + err = CFileMgr::Write(file, (const char*)data, align4bytes(size)) != align4bytes(size); + if (err || CFileMgr::GetErrorReadWrite(file)) { + nErrorCode = SAVESTATUS_ERR_SAVE_WRITE; + strncpy(SaveFileNameJustSaved, ValidSaveName, sizeof(ValidSaveName) - 1); + return false; + } + } else { + free(compressed); + return false; + } + + 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; +} + +uint32_t C_PcSave::PcClassLoadRoutine(int32 file, uint8 *data) { + uint32 size; + bool err = CFileMgr::Read(file, (char*)&size, sizeof(size)) != sizeof(size); + if (err) { + return 0; + } + + assert(data == work_buff); + + if (!(size & 0x80000000)) { + assert(align4bytes(size) == size); + err = CFileMgr::Read(file, (char*)data, align4bytes(size)) != align4bytes(size); + if (err || CFileMgr::GetErrorReadWrite(file)) { + return 0; + } + return size; + } else { + size &= ~0x80000000; + uint8* compressed = (uint8*)malloc(size); + err = CFileMgr::Read(file, (const char*)compressed, size) != size; + if (err || CFileMgr::GetErrorReadWrite(file)) { + free(compressed); + return 0; + } + + lzo_uint decompressed_size = 0; + auto crv = lzo1x_decompress(compressed, size, data, &decompressed_size, NULL); + free(compressed); + if (crv != LZO_E_OK) { + return 0; + } + + if (align4bytes(decompressed_size) != decompressed_size) { + return 0; + } + + return decompressed_size; + } +} + +void +C_PcSave::PopulateSlotInfo() +{ + for (int i = 0; i < SLOT_COUNT; i++) { + Slots[i] = 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 header_t { + wchar FileName[24]; + SYSTEMTIME SaveDateTime; + } header; + sprintf(savename, "%s%i%s", i==7?"GTAVCSF":DefaultPCSaveFileName, i + 1, ".b"); + int file = CFileMgr::OpenFile(savename, "rb"); + if (file != 0) { + if (C_PcSave::PcClassLoadRoutine(file, (uint8*)work_buff)) { + header = *(header_t*)work_buff; + + Slots[i] = SLOT_OK; + memcpy(SlotFileName[i], &header.FileName, sizeof(header.FileName)); + + SlotFileName[i][24] = '\0'; + } + CFileMgr::CloseFile(file); + } + if (Slots[i] == 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] = 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] = SLOT_CORRUPTED; + } + } + } +} \ No newline at end of file diff --git a/src/miami/save/PCSave.h b/src/miami/save/PCSave.h new file mode 100644 index 00000000..2205105b --- /dev/null +++ b/src/miami/save/PCSave.h @@ -0,0 +1,41 @@ +#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); + int8 SaveSlot(int32 slot); + bool PcClassSaveRoutine(int32 file, uint8 *data, uint32 size); + static uint32_t PcClassLoadRoutine(int32 file, uint8 *data); + static void SetSaveDirectory(const char *path); +}; + +extern C_PcSave PcSaveHelper; diff --git a/src/miami/save/SaveBuf.h b/src/miami/save/SaveBuf.h new file mode 100644 index 00000000..2a7fb315 --- /dev/null +++ b/src/miami/save/SaveBuf.h @@ -0,0 +1,123 @@ +#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 +} + +inline void +SkipSaveBuf(uint8*& buf, uint32 &length, int32 skip) +{ + buf += skip; + length += skip; +#ifdef VALIDATE_SAVE_SIZE + _saveBufCount += skip; +#endif +} + +template +inline void +ReadSaveBuf(T *out, uint8 *&buf) +{ + memcpy(out, buf, sizeof(T)); + SkipSaveBuf(buf, sizeof(T)); +} + +template +inline void +ReadSaveBuf(T *out, uint8 *&buf, uint32 &length) +{ + memcpy(out, buf, sizeof(T)); + SkipSaveBuf(buf, length, sizeof(T)); +} + +template +inline T * +WriteSaveBuf(uint8 *&buf, const T &value) +{ + T *p = (T*)buf; + memcpy(p, &value, sizeof(T)); + SkipSaveBuf(buf, sizeof(T)); + return p; +} + +template +inline T * +WriteSaveBuf(uint8 *&buf, uint32 &length, const T &value) +{ + T *p = (T*)buf; + memcpy(p, &value, sizeof(T)); + SkipSaveBuf(buf, length, 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, size); + +#define WriteSaveHeaderWithLength(buf,len,a,b,c,d,size) \ + WriteSaveBuf(buf, len, a);\ + WriteSaveBuf(buf, len, b);\ + WriteSaveBuf(buf, len, c);\ + WriteSaveBuf(buf, len, d);\ + WriteSaveBuf(buf, len, (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) + +#define CheckSaveHeaderWithLength(buf,len,a,b,c,d,size) do { \ + char _c; uint32 _size;\ + ReadSaveBuf(&_c, buf, len);\ + assert(_c == a);\ + ReadSaveBuf(&_c, buf, len);\ + assert(_c == b);\ + ReadSaveBuf(&_c, buf, len);\ + assert(_c == c);\ + ReadSaveBuf(&_c, buf, len);\ + assert(_c == d);\ + ReadSaveBuf(&_size, buf, len);\ + assert(_size == size);\ + } while(0) +#else +#define CheckSaveHeader(buf, a, b, c, d, size) SkipSaveBuf(buf, 8); +#define CheckSaveHeaderWithLength(buf, len, a, b, c, d, size) SkipSaveBuf(buf, 8); +#endif diff --git a/src/miami/skel/crossplatform.cpp b/src/miami/skel/crossplatform.cpp new file mode 100644 index 00000000..729ca163 --- /dev/null +++ b/src/miami/skel/crossplatform.cpp @@ -0,0 +1,453 @@ +#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)) { + if (d != NULL) { + closedir((DIR*)d); + } + 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); + printf("TODO: FIXME %s\n",__func__); + strcpy(out,"abc"); +} + +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) +{ + //TODO: Implement this + bool access_ok = false; //access(path, F_OK) != -1 + // printf("TODO: FIXME %s\n", __func__); + + if (checkPathFirst && access_ok ) { + // File path is correct + return nil; + } + + size_t l = strlen(path); + if (l > 2 && path[0] == '.' && (path[1] == '/' || path[1] == '\\')) { + // remove ./ from the start of the path + path += 2; + } + 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 + +#if !defined(_MSC_VER) && !defined(__CWCC__) +char *strdate(char *buf) { + time_t timestamp; + time(×tamp); + tm *localTm = localtime(×tamp); + strftime(buf, 10, "%m/%d/%y", localTm); + return buf; +} + +char *_strdate(char *buf) { + return strdate(buf); +} +#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/miami/skel/crossplatform.h b/src/miami/skel/crossplatform.h new file mode 100644 index 00000000..1ceb8ceb --- /dev/null +++ b/src/miami/skel/crossplatform.h @@ -0,0 +1,207 @@ +#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, +}; + +#if !defined(_MSC_VER) && !defined(__CWCC__) +char *_strdate(char *buf); +#endif + +#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 // DreamSDK doesn't have IntSafe.h + #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 RW_DC +typedef struct +{ + 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/miami/skel/dc/dc.cpp b/src/miami/skel/dc/dc.cpp new file mode 100644 index 00000000..5868c3bd --- /dev/null +++ b/src/miami/skel/dc/dc.cpp @@ -0,0 +1,2879 @@ +#if defined RW_DC + +#include "vmu/vmu.h" +#include +#include +#include + +#if !defined(DC_SIM) +#include +KOS_INIT_FLAGS(INIT_IRQ | INIT_CONTROLLER | INIT_CDROM | INIT_VMU); +#include "../prof/profiler.h" +#endif + +#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" +#include "git-version.h" +#include "dc.h" + +#include + +// 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 + +#include + +#if !defined(DC_SIM) +# if defined(WITH_IDE) +#include +#include +#include +# endif + +#include +#include +#endif + +// //TODO: these are somehow missing +#ifndef CLOCK_MONOTONIC +#define CLOCK_MONOTONIC ((clockid_t) 4) +#endif +// #define CLOCK_MONOTONIC_RAW ((clockid_t) 5) +// #define CLOCK_MONOTONIC_COARSE ((clockid_t) 6) + + +#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 + + +#if !defined(DC_SIM) && defined(WITH_IDE) +static kos_blockdev_t ide_rv; + +static void ide_fat_init(void) +{ + uint8_t type; + int err; + + err = g1_ata_init(); + if (err) + return; + + err = g1_ata_blockdev_for_partition(0, 1, &ide_rv, &type); + if (err) + return; + + printf("Found IDE partition 0\n"); + + err = fs_fat_init(); + if (err) + return; + + err = fs_fat_mount("/ide", &ide_rv, FS_FAT_MOUNT_READONLY); + if (err) + return; + + printf("Mounted IDE partition 0 to /ide\n"); +} +#endif + +#if !defined(DC_SIM) +#if defined(WITH_PROF) +static bool profiling = false; +#endif +#if defined(WITH_SD) +static kos_blockdev_t sd_rv; + +static void sd_fat_init(void) +{ + uint8_t type; + int err; + + err = sd_init(); + if (err) + return; + + err = sd_blockdev_for_partition(0, &sd_rv, &type); + if (err) + return; + + printf("Found SD partition 0\n"); + + err = fs_fat_init(); + if (err) + return; + + err = fs_fat_mount("/sd", &sd_rv, FS_FAT_MOUNT_READWRITE); + if (err) + return; + + printf("Mounted SD partition 0 to /sd\n"); +} +#endif +#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 (stat(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, "\\GTA Vice City User Files"); + _psCreateFolder(szUserFiles); + return szUserFiles; + } + + RegCloseKey(hKey); + } + + strcpy(szUserFiles, "data"); + return szUserFiles; +#else + static char szUserFiles[256]; + strcpy(szUserFiles, "/vmu/" VMU_DEFAULT_PATH); + _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) +{ +#ifdef LEGACY_MENU_OPTIONS + if (FrontEndMenuManager.m_PrefsVsync || FrontEndMenuManager.m_bMenuActive) +#else + if (FrontEndMenuManager.m_PrefsFrameLimiter || FrontEndMenuManager.m_bMenuActive) +#endif + RwCameraShowRaster(camera, NULL /*PSGLOBAL(window)*/, rwRASTERFLIPWAITVSYNC); + else + RwCameraShowRaster(camera, NULL /*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) +{ + #if defined(DC_SH4) + // Clock off AICA + // + // according to purist, sh4 is 199.5MHz (KOS assumes 200 mhz) + // and the sh4 has a different clock domain from AICA + // + // This solves the sound drift issue in the part 2 of the intro + // + // N.B. This depends on the jiffies per second from AICA + // and only works after AICA has been initialized + #define AICA_MEM_CLOCK 0x021000 /* 4 bytes */ + uint32_t jiffies = g2_read_32(SPU_RAM_UNCACHED_BASE + AICA_MEM_CLOCK); + return jiffies / 4.410f; +#else + 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 +} +#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() {} +#ifndef __DREAMCAST__ +static void _psHandleVibration() {} +#endif +#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(); + + CPad::Initialise(); + CPad::GetPad(0)->Mode = 0; + +#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(); + +#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 + FrontEndMenuManager.LoadSettings(); +#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 +#ifndef __APPLE__ + // struct sysinfo systemInfo; + // sysinfo(&systemInfo); + _dwMemAvailPhys = 10 * 1024 * 1024 ; //systemInfo.freeram; + // debug("Physical memory size %u\n", systemInfo.totalram); + // debug("Available physical memory %u\n", systemInfo.freeram); +#else + 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); +#endif + _dwOperatingSystemVersion = OS_WINXP; // To fool other classes +#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 = 640; //mode->width; + FrontEndMenuManager.m_nPrefsHeight = 480; //mode->height; + FrontEndMenuManager.m_nPrefsDepth = 16; + 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); + bestWndMode = GcurSelVM; + bestWidth = vm.width; + bestHeight = vm.height; + bestDepth = vm.depth; + bestFsMode = GcurSelVM; + break; + } + + 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 = fcaseopen(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 reVC/gamefiles to GTA: Vice City 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); +// } +} + +// int lastCursorMode = GLFW_CURSOR_HIDDEN; +long _InputInitialiseMouse(bool exclusive) +{ + // Disabled = keep cursor centered and hide + // lastCursorMode = exclusive ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_HIDDEN; + // glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, lastCursorMode); + return 0; +} + +void _InputShutdownMouse() +{ + // Not needed +} + +// Not "needs exclusive" on GLFW, but more like "needs to change mode" +bool _InputMouseNeedsExclusive() +{ + // That was the cause of infamous mouse bug on Win. + + RwVideoMode vm; + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + // If windowed, free the cursor on menu(where this func. is called and DISABLED-HIDDEN transition is done accordingly) + // If it's fullscreen, be sure that it didn't stuck on HIDDEN. + return !(vm.flags & rwVIDEOMODEEXCLUSIVE); //|| lastCursorMode == GLFW_CURSOR_HIDDEN; +} + +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(false); + + // 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; + FrontEndMenuManager.m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + FrontEndMenuManager.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; + FrontEndMenuManager.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; + } + } + + FrontEndMenuManager.OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + FrontEndMenuManager.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 +#if !defined(DC_SIM) +extern "C" const char etext[]; + +__attribute__((noinline)) void stacktrace() { + uint32 sp=0, pr=0; + __asm__ __volatile__( + "mov r15,%0\n" + "sts pr,%1\n" + : "+r" (sp), "+r" (pr) + : + : ); + dbglog(DBG_CRITICAL, "DCA3: %s\n", getExecutableTag()); + dbglog(DBG_CRITICAL, "Stack trace: %p ", (void*)pr); + int found = 0; + if(!(sp & 3) && sp > 0x8c000000 && sp < _arch_mem_top) { + char** sp_ptr = (char**)sp; + for (int so = 0; so < 16384; so++) { + if (uintptr_t(&sp_ptr[so]) >= _arch_mem_top) { + dbglog(DBG_CRITICAL, "(@@%p) ", &sp_ptr[so]); + break; + } + if (sp_ptr[so] > (char*)0x8c000000 && sp_ptr[so] < etext) { + uintptr_t addr = uintptr_t(sp_ptr[so]); + // candidate return pointer + if (addr & 1) { + // dbglog(DBG_CRITICAL, "Stack trace: %p (@%p): misaligned\n", (void*)sp_ptr[so], &sp_ptr[so]); + continue; + } + + uint16_t* instrp = (uint16_t*)addr; + + uint16_t instr = instrp[-2]; + // BSR or BSRF or JSR @Rn ? + if (((instr & 0xf000) == 0xB000) || ((instr & 0xf0ff) == 0x0003) || ((instr & 0xf0ff) == 0x400B)) { + dbglog(DBG_CRITICAL, "%p ", instrp); + if (found++ > 24) { + dbglog(DBG_CRITICAL, "(@%p) ", &sp_ptr[so]); + break; + } + } else { + // dbglog(DBG_CRITICAL, "%p:%04X ", instrp, instr); + } + } else { + // dbglog(DBG_CRITICAL, "Stack trace: %p (@%p): out of range\n", (void*)sp_ptr[so], &sp_ptr[so]); + } + } + dbglog(DBG_CRITICAL, "end\n"); + } else { + dbglog(DBG_CRITICAL, "(@%p)\n", (void*)sp); + } +} + +#include +#include +#include +#include +#include + +#endif + +const char* getExecutableTag() { + return GIT_VERSION ":" CI_JOB_ID; +} + +int +main(int argc, char *argv[]) +{ + dbglog(DBG_CRITICAL, "DCA3: %s\n", getExecutableTag()); + #if !defined(DC_SIM) + std::set_terminate([]() { + fflush(stdout); + fflush(stderr); + dbglog(DBG_CRITICAL, "std::terminate() called\n"); + dbglog(DBG_CRITICAL, "POSIX error (may not be relevant): %s\n", strerror(errno)); + stacktrace(); + dbgio_dev_select("fb"); + sleep(1); + dbglog(DBG_CRITICAL, "std::terminate() called\n"); + dbglog(DBG_CRITICAL, "POSIX error (may not be relevant): %s\n", strerror(errno)); + stacktrace(); + dbgio_flush(); + + + abort(); + }); + #endif + + #if !defined(DC_SIM) + #if defined(WITH_IDE) + ide_fat_init(); + #ifndef DC_CHDIR + #define DC_CHDIR /ide/gta3 + #endif + #endif + + #ifndef DC_CHDIR + #define DC_CHDIR /cd + #endif + + /* Disable printf() buffering, because it's slow with latest Newlib/toolchain combo. */ + setvbuf(stdout, NULL, _IONBF, 0); +#if defined(MASTER) || defined(NDEBUG) + dbglog_set_level(DBG_CRITICAL); +#endif + + printf("sbrk: %p\n", sbrk(0)); + printf("Loading game assets from %s\n", STR(DC_CHDIR)); + chdir(STR(DC_CHDIR)); + +#if defined(WITH_PROF) + // dbgio_dev_select("null"); + #if defined(WITH_SD) + sd_fat_init(); + #endif + cont_btn_callback(0, CONT_A | CONT_B | CONT_X, [](uint8_t, uint32_t btns) { + if (profiler_recording()) + return; + if(profiling) { + profiling = false; + profiler_clean_up(); + } + else { + profiling = true; + #if defined(WITH_SD) + profiler_init("/sd"); + #else + profiler_init(WITH_PROF); + #endif + profiler_start(); + } + }); +#endif + + cont_btn_callback(0, CONT_RESET_BUTTONS, [](uint8_t, uint32_t) { + exit(EXIT_SUCCESS); + }); + + #else + chdir("./repack-data/miami"); + #endif + +#endif + RwV2d pos; + RwInt32 i; + +#ifdef USE_CUSTOM_ALLOCATOR + InitMemoryMgr(); +#endif + +#if !defined(_WIN32) && !defined(__SWITCH__) && !defined(RW_DC) + 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"); + // LoadingScreen(nil, nil, "loadsc0"); // duplicate +#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"); + // LoadingScreen(nil, nil, "loadsc0"); // duplicate + + FrontEndMenuManager.m_bGameNotLoaded = true; + + FrontEndMenuManager.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 (!FrontEndMenuManager.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 + { +#if !defined(RW_DC) +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif +#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 + } + + #if !defined(RW_DC) +#ifndef MASTER + if ( gbModelViewer ) + CAnimViewer::Shutdown(); + else +#endif +#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 + +#if defined(WITH_PROF) && !defined(DC_SIM) + if(profiling) { + profiling = false; + profiler_clean_up(); + } +#endif + + VmuProfiler::destroyInstance(); + 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; +} +#if 0 +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; + } +} +#endif + +#if (defined(_MSC_VER)) +int strcasecmp(const char* str1, const char* str2) +{ + return _strcmpi(str1, str2); +} +int strncasecmp(const char *str1, const char *str2, size_t len) +{ + return _strnicmp(str1, str2, len); +} +#endif +#endif diff --git a/src/miami/skel/dc/dc.h b/src/miami/skel/dc/dc.h new file mode 100644 index 00000000..a843f115 --- /dev/null +++ b/src/miami/skel/dc/dc.h @@ -0,0 +1,7 @@ +#pragma once +#include + +std::string getBuildId(); +const char* getSourceId(); +const char* getCIJobId(); +const char* getExecutableTag(); \ No newline at end of file diff --git a/src/miami/skel/events.cpp b/src/miami/skel/events.cpp new file mode 100644 index 00000000..87447819 --- /dev/null +++ b/src/miami/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/miami/skel/events.h b/src/miami/skel/events.h new file mode 100644 index 00000000..ef812ebc --- /dev/null +++ b/src/miami/skel/events.h @@ -0,0 +1,7 @@ +#ifndef EVENTS_H +#define EVENTS_H + +#include +#include "skeleton.h" + +#endif /* EVENTS_H */ diff --git a/src/miami/skel/glfw/glfw.cpp b/src/miami/skel/glfw/glfw.cpp new file mode 100644 index 00000000..f1f97ee3 --- /dev/null +++ b/src/miami/skel/glfw/glfw.cpp @@ -0,0 +1,2609 @@ +#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, "\\GTA Vice City 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) +{ +#ifdef LEGACY_MENU_OPTIONS + if (FrontEndMenuManager.m_PrefsVsync || FrontEndMenuManager.m_bMenuActive) +#else + if (FrontEndMenuManager.m_PrefsFrameLimiter || FrontEndMenuManager.m_bMenuActive) +#endif + 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(); + +#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 + FrontEndMenuManager.LoadSettings(); +#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 +#ifndef __APPLE__ + struct sysinfo systemInfo; + sysinfo(&systemInfo); + _dwMemAvailPhys = systemInfo.freeram; + debug("Physical memory size %u\n", systemInfo.totalram); + debug("Available physical memory %u\n", systemInfo.freeram); +#else + 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); +#endif + _dwOperatingSystemVersion = OS_WINXP; // To fool other classes +#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 reVC/gamefiles to GTA: Vice City 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); + } +} + +int lastCursorMode = GLFW_CURSOR_HIDDEN; +long _InputInitialiseMouse(bool exclusive) +{ + // Disabled = keep cursor centered and hide + lastCursorMode = exclusive ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_HIDDEN; + glfwSetInputMode(PSGLOBAL(window), GLFW_CURSOR, lastCursorMode); + return 0; +} + +void _InputShutdownMouse() +{ + // Not needed +} + +// Not "needs exclusive" on GLFW, but more like "needs to change mode" +bool _InputMouseNeedsExclusive() +{ + // That was the cause of infamous mouse bug on Win. + + RwVideoMode vm; + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + // If windowed, free the cursor on menu(where this func. is called and DISABLED-HIDDEN transition is done accordingly) + // If it's fullscreen, be sure that it didn't stuck on HIDDEN. + return !(vm.flags & rwVIDEOMODEEXCLUSIVE) || lastCursorMode == GLFW_CURSOR_HIDDEN; +} + +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(false); + + 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; + FrontEndMenuManager.m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + FrontEndMenuManager.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; + FrontEndMenuManager.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; + } + } + + FrontEndMenuManager.OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + FrontEndMenuManager.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"); + // LoadingScreen(nil, nil, "loadsc0"); // duplicate +#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"); + // LoadingScreen(nil, nil, "loadsc0"); // duplicate + + FrontEndMenuManager.m_bGameNotLoaded = true; + + FrontEndMenuManager.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 (!FrontEndMenuManager.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); +} +int strncasecmp(const char *str1, const char *str2, size_t len) +{ + return _strnicmp(str1, str2, len); +} +#endif +#endif diff --git a/src/miami/skel/platform.h b/src/miami/skel/platform.h new file mode 100644 index 00000000..0475d20a --- /dev/null +++ b/src/miami/skel/platform.h @@ -0,0 +1,61 @@ +#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(bool exclusive); // returns HRESULT on Windows actually +extern void _InputShutdownMouse(); +extern bool _InputMouseNeedsExclusive(); +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/miami/skel/skeleton.cpp b/src/miami/skel/skeleton.cpp new file mode 100644 index 00000000..2bb23460 --- /dev/null +++ b/src/miami/skel/skeleton.cpp @@ -0,0 +1,428 @@ +#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; + + /* + * 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(); + + RwTextureSetAutoMipmapping(TRUE); + RwTextureSetMipmapping(FALSE); + + return TRUE; +} + +/* + ***************************************************************************** + */ +void +RsTerminate(void) +{ + psTerminate(); + + return; +} + +/* + ***************************************************************************** + */ +RwBool +RsInitialize(void) +{ + /* + * Initialize Platform independent data... + */ + RwBool result; + + RsGlobal.appName = RWSTRING("GTA: Vice City"); + 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/miami/skel/skeleton.h b/src/miami/skel/skeleton.h new file mode 100644 index 00000000..380b6c05 --- /dev/null +++ b/src/miami/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/miami/skel/win/gtavc.ico b/src/miami/skel/win/gtavc.ico new file mode 100644 index 0000000000000000000000000000000000000000..7bfcc5a5751ad1bedab31b8c5a771a15bdccf065 GIT binary patch literal 81817 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 +#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, "\\GTA Vice City 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) +{ +#ifdef LEGACY_MENU_OPTIONS + if (FrontEndMenuManager.m_PrefsVsync || FrontEndMenuManager.m_bMenuActive) +#else + if (FrontEndMenuManager.m_PrefsFrameLimiter || FrontEndMenuManager.m_bMenuActive) +#endif + 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 + } +} + +#ifdef __MWERKS__ +#pragma dont_inline on +#endif +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"); +} +#ifdef __MWERKS__ +#pragma dont_inline off +#endif +#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(); +#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 + FrontEndMenuManager.LoadSettings(); +#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; + } + +#ifdef FIX_BUGS // game turns on menu when focus is re-gained rather than lost + case WM_KILLFOCUS: +#else + case WM_SETFOCUS: +#endif + { + CGame::InitAfterFocusLoss(); + 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; + windowClass.hIcon = LoadIcon((HINSTANCE)instance, (LPCSTR)IDI_MAIN_ICON); + 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 ) { + d3d->Release(); + return 0; + } + + if ( mode.RefreshRate < refreshRate && mode.RefreshRate >= 60 ) + refreshRate = mode.RefreshRate; + } + } + + d3d->Release(); + + 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", "GTA: Vice City", 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", "GTA: Vice City", 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; + FrontEndMenuManager.m_PrefsAllowNastyGame = false; + CGame::germanGame = true; + } + + if ( primUserLCID == LANG_FRENCH + || primSystemLCID == LANG_FRENCH + || primLayout == LANG_FRENCH ) + { + CGame::nastyGame = false; + FrontEndMenuManager.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; + FrontEndMenuManager.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; + } + } + + FrontEndMenuManager.OS_Language = primUserLCID; + + switch ( lang ) + { + case LANG_GERMAN: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_GERMAN; + break; + } + case LANG_SPANISH: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_SPANISH; + break; + } + case LANG_FRENCH: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_FRENCH; + break; + } + case LANG_ITALIAN: + { + FrontEndMenuManager.m_PrefsLanguage = CMenuManager::LANGUAGE_ITALIAN; + break; + } + default: + { + FrontEndMenuManager.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; iNewState.CheckForInput() ) + ++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_INTRO_MPEG: + { +#ifdef NO_MOVIES + if (!gbNoMovies) +#endif + CloseClip(); +#ifndef FIX_BUGS + CoUninitialize(); +#endif + + if ( FrontEndMenuManager.OS_Language == LANG_FRENCH || FrontEndMenuManager.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)->NewState.CheckForInput() ) + ++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"); + // LoadingScreen(nil, nil, "loadsc0"); // duplicate +#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"); + // LoadingScreen(nil, nil, "loadsc0"); // duplicate + + FrontEndMenuManager.m_bGameNotLoaded = true; + + FrontEndMenuManager.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 (!FrontEndMenuManager.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(bool exclusive) +{ + 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), (exclusive ? DISCL_EXCLUSIVE : 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)); +} + +void _InputShutdownMouse() +{ + if (PSGLOBAL(mouse) == nil) + return; + + PSGLOBAL(mouse)->Unacquire(); + SAFE_RELEASE(PSGLOBAL(mouse)); +} + +bool _InputMouseNeedsExclusive(void) +{ + // FIX: I don't know why R* needed that, but it causes infamous mouse bug on modern systems. + // Probably DirectInput bug, since Acquire() and GetDeviceState() reports everything A-OK. +#ifdef FIX_BUGS + return false; +#endif + RwVideoMode vm; + RwEngineGetVideoModeInfo(&vm, GcurSelVM); + + return vm.flags & rwVIDEOMODEEXCLUSIVE; +} + +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); +} +int strncasecmp(const char *str1, const char *str2, size_t len) +{ + return _strnicmp(str1, str2, len); +} +#endif +#endif diff --git a/src/miami/skel/win/win.h b/src/miami/skel/win/win.h new file mode 100644 index 00000000..be840898 --- /dev/null +++ b/src/miami/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/miami/skel/win/win.rc b/src/miami/skel/win/win.rc new file mode 100644 index 00000000..9b5aa305 --- /dev/null +++ b/src/miami/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 "gtavc.ico" + +///////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/src/miami/text/Messages.cpp b/src/miami/text/Messages.cpp new file mode 100644 index 00000000..81339ae0 --- /dev/null +++ b/src/miami/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; + 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/miami/text/Messages.h b/src/miami/text/Messages.h new file mode 100644 index 00000000..e8ba1bf7 --- /dev/null +++ b/src/miami/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/miami/text/Pager.cpp b/src/miami/text/Pager.cpp new file mode 100644 index 00000000..609c6860 --- /dev/null +++ b/src/miami/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/miami/text/Pager.h b/src/miami/text/Pager.h new file mode 100644 index 00000000..16307971 --- /dev/null +++ b/src/miami/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/miami/text/Text.cpp b/src/miami/text/Text.cpp new file mode 100644 index 00000000..1369d1db --- /dev/null +++ b/src/miami/text/Text.cpp @@ -0,0 +1,530 @@ +#include "common.h" + +#include "FileMgr.h" +#ifdef MORE_LANGUAGES +#include "Game.h" +#endif +#include "Frontend.h" +#include "Messages.h" +#include "Text.h" +#include "Timer.h" + +wchar WideErrorString[25]; + +CText TheText; + +CText::CText(void) +{ + encoding = 'e'; + bHasMissionTextOffsets = false; + bIsMissionTextLoaded = false; + memset(szMissionTableName, 0, sizeof(szMissionTableName)); + memset(WideErrorString, 0, sizeof(WideErrorString)); +} + +void +CText::Load(void) +{ + char filename[32]; + size_t offset; + int file; + bool tkey_loaded = false, tdat_loaded = false; + ChunkHeader m_ChunkHeader; + + bIsMissionTextLoaded = false; + bHasMissionTextOffsets = false; + + Unload(); + + CFileMgr::SetDir("TEXT"); + switch(FrontEndMenuManager.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 + } + + file = CFileMgr::OpenFile(filename, "rb"); + + offset = 0; + while (!tkey_loaded || !tdat_loaded) { + ReadChunkHeader(&m_ChunkHeader, file, &offset); + if (m_ChunkHeader.size != 0) { + if (strncmp(m_ChunkHeader.magic, "TABL", 4) == 0) { + MissionTextOffsets.Load(m_ChunkHeader.size, file, &offset, 0x58000); + bHasMissionTextOffsets = true; + } else if (strncmp(m_ChunkHeader.magic, "TKEY", 4) == 0) { + this->keyArray.Load(m_ChunkHeader.size, file, &offset); + tkey_loaded = true; + } else if (strncmp(m_ChunkHeader.magic, "TDAT", 4) == 0) { + this->data.Load(m_ChunkHeader.size, file, &offset); + tdat_loaded = true; + } else { + CFileMgr::Seek(file, m_ChunkHeader.size, SEEK_CUR); + offset += m_ChunkHeader.size; + } + } + } + + keyArray.Update(data.chars); + CFileMgr::CloseFile(file); + CFileMgr::SetDir(""); +} + +void +CText::Unload(void) +{ + CMessages::ClearAllMessagesDisplayedByGame(); + keyArray.Unload(); + data.Unload(); + mission_keyArray.Unload(); + mission_data.Unload(); + bIsMissionTextLoaded = false; + memset(szMissionTableName, 0, sizeof(szMissionTableName)); +} + +wchar* +CText::Get(const char *key) +{ + uint8 result = false; +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + wchar *outstr = keyArray.Search(key, data.chars, &result); +#else + wchar *outstr = keyArray.Search(key, &result); +#endif + + if (!result && bHasMissionTextOffsets && bIsMissionTextLoaded) +#if defined (FIX_BUGS) || defined(FIX_BUGS_64) + outstr = mission_keyArray.Search(key, mission_data.chars, &result); +#else + outstr = mission_keyArray.Search(key, &result); +#endif + return outstr; +} + +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 +CText::GetNameOfLoadedMissionText(char *outName) +{ + strcpy(outName, szMissionTableName); +} + +void +CText::ReadChunkHeader(ChunkHeader *buf, int32 file, size_t *offset) +{ +#ifdef THIS_IS_STUPID + char *_buf = (char*)buf; + for (int i = 0; i < sizeof(ChunkHeader); i++) { + CFileMgr::Read(file, &_buf[i], 1); + (*offset)++; + } +#else + // original code loops 8 times to read 1 byte with CFileMgr::Read, that's retarded + CFileMgr::Read(file, (char*)buf, sizeof(ChunkHeader)); + *offset += sizeof(ChunkHeader); +#endif +} + +void +CText::LoadMissionText(char *MissionTableName) +{ + char filename[32]; + CMessages::ClearAllMessagesDisplayedByGame(); + + mission_keyArray.Unload(); + mission_data.Unload(); + + bool search_result = false; + int missionTableId = 0; + + for (missionTableId = 0; missionTableId < MissionTextOffsets.size; missionTableId++) { + if (strncmp(MissionTextOffsets.data[missionTableId].szMissionName, MissionTableName, strlen(MissionTextOffsets.data[missionTableId].szMissionName)) == 0) { + search_result = true; + break; + } + } + + if (!search_result) { + printf("CText::LoadMissionText - couldn't find %s", MissionTableName); + return; + } + + CFileMgr::SetDir("TEXT"); + switch (FrontEndMenuManager.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 + } + CTimer::Suspend(); + int file = CFileMgr::OpenFile(filename, "rb"); + CFileMgr::Seek(file, MissionTextOffsets.data[missionTableId].offset, SEEK_SET); + + char TableCheck[8]; + CFileMgr::Read(file, TableCheck, 8); + if (strncmp(TableCheck, MissionTableName, 8) != 0) + printf("CText::LoadMissionText - expected to find %s in the text file", MissionTableName); + + bool tkey_loaded = false, tdat_loaded = false; + ChunkHeader m_ChunkHeader; + while (!tkey_loaded || !tdat_loaded) { + size_t bytes_read = 0; + ReadChunkHeader(&m_ChunkHeader, file, &bytes_read); + if (m_ChunkHeader.size != 0) { + if (strncmp(m_ChunkHeader.magic, "TKEY", 4) == 0) { + size_t bytes_read = 0; + mission_keyArray.Load(m_ChunkHeader.size, file, &bytes_read); + tkey_loaded = true; + } else if (strncmp(m_ChunkHeader.magic, "TDAT", 4) == 0) { + size_t bytes_read = 0; + mission_data.Load(m_ChunkHeader.size, file, &bytes_read); + tdat_loaded = true; + } else + CFileMgr::Seek(file, m_ChunkHeader.size, SEEK_CUR); + } + } + + mission_keyArray.Update(mission_data.chars); + CFileMgr::CloseFile(file); + CTimer::Resume(); + CFileMgr::SetDir(""); + strcpy(szMissionTableName, MissionTableName); + bIsMissionTextLoaded = true; +} + + +void +CKeyArray::Load(size_t length, int file, size_t* offset) +{ + char *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 = (char*)entries; + +#ifdef THIS_IS_STUPID + for (uint32 i = 0; i < length; i++) { + CFileMgr::Read(file, &rawbytes[i], 1); + (*offset)++; + } +#else + CFileMgr::Read(file, rawbytes, length); + *offset += length; +#endif +} + +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, uint8 *result) +#else +CKeyArray::Search(const char *key, uint8 *result) +#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) { + *result = true; + return (wchar*)((uint8*)data + found->valueOffset); + } +#else + found = BinarySearch(key, entries, 0, numEntries-1); + if (found) { + *result = true; + return found->value; + } +#endif + *result = false; +#ifdef MASTER + sprintf(errstr, ""); +#else + sprintf(errstr, "%s missing", key); +#endif // MASTER + for(i = 0; i < 25; i++) + WideErrorString[i] = errstr[i]; + return WideErrorString; +} + +void +CData::Load(size_t length, int file, size_t * offset) +{ + char *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 = (char*)chars; + +#ifdef THIS_IS_STUPID + for(uint32 i = 0; i < length; i++){ + CFileMgr::Read(file, &rawbytes[i], 1); + (*offset)++; + } +#else + CFileMgr::Read(file, rawbytes, length); + *offset += length; +#endif +} + +void +CData::Unload(void) +{ + delete[] chars; + chars = nil; + numChars = 0; +} + +void +CMissionTextOffsets::Load(size_t table_size, int file, size_t *offset, int) +{ +#ifdef THIS_IS_STUPID + size_t num_of_entries = table_size / sizeof(CMissionTextOffsets::Entry); + for (size_t mi = 0; mi < num_of_entries; mi++) { + for (uint32 i = 0; i < sizeof(data[mi].szMissionName); i++) { + CFileMgr::Read(file, &data[i].szMissionName[i], 1); + (*offset)++; + } + char* _buf = (char*)&data[mi].offset; + for (uint32 i = 0; i < sizeof(data[mi].offset); i++) { + CFileMgr::Read(file, &_buf[i], 1); + (*offset)++; + } + } + size = (uint16)num_of_entries; +#else + // not exact VC code but smaller and better :P + + // You can make this size_t if you want to exceed 32-bit boundaries, everything else should be ready. + size = (uint16) (table_size / sizeof(CMissionTextOffsets::Entry)); + CFileMgr::Read(file, (char*)data, sizeof(CMissionTextOffsets::Entry) * size); + *offset += sizeof(CMissionTextOffsets::Entry) * size; +#endif +} + +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; + // convert to CP1252 + else if(*src <= 131) + aStr[len] = *src + 64; + else if (*src <= 141) + aStr[len] = *src + 66; + else if (*src <= 145) + aStr[len] = *src + 68; + else if (*src <= 149) + aStr[len] = *src + 71; + else if (*src <= 154) + aStr[len] = *src + 73; + else if (*src <= 164) + aStr[len] = *src + 75; + else if (*src <= 168) + aStr[len] = *src + 77; + else if (*src <= 204) + aStr[len] = *src + 80; + else switch (*src) { + case 205: aStr[len] = 209; break; + case 206: aStr[len] = 241; break; + case 207: aStr[len] = 191; break; + default: aStr[len] = '#'; break; + } + 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/miami/text/Text.h b/src/miami/text/Text.h new file mode 100644 index 00000000..1174216c --- /dev/null +++ b/src/miami/text/Text.h @@ -0,0 +1,99 @@ +#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, int file, size_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, uint8 *result); +#else + wchar *Search(const char *key, uint8* result); +#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, int file, size_t* offset); + void Unload(void); +}; + +class CMissionTextOffsets +{ +public: + struct Entry + { + char szMissionName[8]; + uint32 offset; + }; + + enum {MAX_MISSION_TEXTS = 90}; // beware that LCS has more + + Entry data[MAX_MISSION_TEXTS]; + uint16 size; // You can make this size_t if you want to exceed 32-bit boundaries, everything else should be ready. + + CMissionTextOffsets(void) : size(0) {} + void Load(size_t table_size, int file, size_t* bytes_read, int); +}; + +struct ChunkHeader +{ + char magic[4]; + int size; +}; + +class CText +{ + CKeyArray keyArray; + CData data; + CKeyArray mission_keyArray; + CData mission_data; + char encoding; + bool bHasMissionTextOffsets; + bool bIsMissionTextLoaded; + char szMissionTableName[8]; + CMissionTextOffsets MissionTextOffsets; +public: + CText(void); + void Load(void); + void Unload(void); + wchar *Get(const char *key); + wchar GetUpperCase(wchar c); + void UpperCase(wchar *s); + void GetNameOfLoadedMissionText(char *outName); + void ReadChunkHeader(ChunkHeader *buf, int32 file, size_t *bytes_read); + void LoadMissionText(char *MissionTableName); +}; + +extern CText TheText; diff --git a/src/miami/vehicles/Automobile.cpp b/src/miami/vehicles/Automobile.cpp new file mode 100644 index 00000000..815b2534 --- /dev/null +++ b/src/miami/vehicles/Automobile.cpp @@ -0,0 +1,5900 @@ +#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 "Glass.h" +#include "Antennas.h" +#include "Skidmarks.h" +#include "WindModifiers.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 "Stats.h" +#include "Garages.h" +#include "PathFind.h" +#include "Replay.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "Object.h" +#include "Automobile.h" +#include "Bike.h" +#include "Wanted.h" +#include "SaveBuf.h" + +bool bAllCarCheat; + +RwObject *GetCurrentAtomicObjectCB(RwObject *object, void *data); + +bool CAutomobile::m_sAllTaxiLights; + +const uint32 CAutomobile::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1500; +#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_doingBurnout = 0; + bTaxiLight = m_sAllTaxiLights; + bFixedColour = false; + bBigWheels = false; + bWaterTight = false; + + SetModelIndex(id); + + // Already done in CVehicle... + switch(GetModelIndex()){ + case MI_HUNTER: + case MI_ANGEL: + case MI_FREEWAY: + m_nRadioStation = V_ROCK; + break; + case MI_RCBARON: + case MI_RCBANDIT: + case MI_RCRAIDER: + case MI_RCGOBLIN: + case MI_TOPFUN: + case MI_CADDY: + case MI_BAGGAGE: + m_nRadioStation = RADIO_OFF; + break; + } + + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + pFlyingHandling = mod_HandlingManager.GetFlyingPointer((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_fOrientation = m_fPlaneSteer = 0.0f; + + 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; + bTankDetonateCars = true; + bStuckInSand = false; + bHeliDestroyed = 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_aWheelSkidmarkType[i] = SKIDMARK_NORMAL; + m_aWheelSkidmarkBloody[i] = false; + } + + m_nWheelsOnGround = 0; + m_nDriveWheelsOnGround = 0; + m_nDriveWheelsOnGroundPrev = 0; + m_fHeightAboveRoad = 0.0f; + m_fTraction = 1.0f; + m_fTireTemperature = 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; + + 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_fHeliOrientation = -1.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_HUNTER){ + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0); + }else if(IsRealHeli()){ + 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(); +} + +#define SAND_SLOWDOWN (0.01f) +float CAR_BALANCE_MULT = 0.3f; +float HELI_ROTOR_DOTPROD_LIMIT = 0.95f; +CVector vecSeaSparrowGunPos(-0.5f, 2.4f, -0.785f); +CVector vecHunterGunPos(0.0f, 4.8f, -1.3f); +CVector vecHunterRocketPos(2.5f, 1.0f, -0.5f); +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) // a workaround for another compiler bug + +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(); + bool drivingInSand = false; + bWarnedPeds = false; + m_doingBurnout = 0; + bStuckInSand = false; + bRestingOnPhysical = false; + + bool carHasNitro = bAllTaxisHaveNitro && GetStatus() == STATUS_PLAYER && IsTaxi(); + + if(CReplay::IsPlayingBack()) + return; + + // Heli wind + if(IsRealHeli()) + if((GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PHYSICS) && m_aWheelSpeed[1] > 0.075f || + GetStatus() == STATUS_SIMPLE) + CWindModifiers::RegisterOne(GetPosition(), 1); + + UpdatePassengerList(); + + // 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; + }else if(GetModelIndex() == MI_RCBANDIT && GetStatus() != STATUS_PLAYER_REMOTE) + strongGrip1 = 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_FBIRANCH: + case MI_POLICE: + case MI_ENFORCER: + case MI_SECURICA: + case MI_RHINO: + case MI_BARRACKS: + ScanForCrimes(); + } + + // Process driver + if(pDriver) + if(IsUpsideDown() && CanPedEnterCar()){ + if(!pDriver->IsPlayer() && + !(pDriver->m_leader && pDriver->m_leader->bInVehicle) && + pDriver->CharCreatedBy != MISSION_CHAR) + pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this); + } + + ActivateBombWhenEntered(); + + // 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); + + UpdateClumpAlpha(); + + 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 = pHandling->CentreOfMass; + + // Park car + if(bCanPark && !bParking && VehicleCreatedBy != MISSION_VEHICLE && AutoPilot.m_nCarMission == MISSION_CRUISE && + ((CTimer::GetFrameCounter() + m_randomSeed)&0xF) == 0 && !IsTaxi()){ + CVector parkPosition = GetPosition() + 3.0f*GetRight() + 10.0f*GetForward(); + CEntity *ent = nil; + CColPoint colpoint; + if(!CWorld::ProcessLineOfSight(GetPosition(), parkPosition, colpoint, ent, true, true, true, false, false, false) || + ent == this) + CCarAI::GetCarToParkAtCoors(this, &parkPosition); + } + + // Process depending on status + + bool playerRemote = false; + switch(GetStatus()){ + case STATUS_PLAYER_REMOTE: +#ifdef FIX_BUGS + if(CPad::GetPad(0)->CarGunJustDown() && !bDisableRemoteDetonation){ +#else + if(CPad::GetPad(0)->WeaponJustDown() && !bDisableRemoteDetonation){ +#endif + BlowUpCar(FindPlayerPed()); + CRemote::TakeRemoteControlledCarFromPlayer(); + } + + if(GetModelIndex() == MI_RCBANDIT && !bDisableRemoteDetonationOnContact){ + //CVector pos = GetPosition(); + // FindPlayerCoors unused + if(RcbanditCheckHitWheels() || bIsInWater){ + 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 && pDriver->GetPedState() != PED_ARRESTED){ + // process control input if controlled by player + if(playerRemote || pDriver->m_nPedType == PEDTYPE_PLAYER1) + ProcessControlInputs(0); + + PruneReferences(); + + if(GetStatus() == STATUS_PLAYER && !CRecordDataForChase::IsRecording()) + DoDriveByShootings(); + + // Tweak center on mass when driving on two wheels + int twoWheelTime = CWorld::Players[CWorld::PlayerInFocus].m_nTimeNotFullyOnGround; + if(twoWheelTime > 500 && !IsRealHeli() && !IsRealPlane()){ + float tweak = Min(twoWheelTime-500, 1000)/500.0f; + if(GetUp().z > 0.0f){ + // positive when on left wheels, negative on right wheels + if(GetRight().z <= 0.0f) + tweak *= -1.0f; + m_vecCentreOfMass.z = pHandling->CentreOfMass.z + + CPad::GetPad(0)->GetSteeringLeftRight()/128.0f * + CAR_BALANCE_MULT * tweak * colModel->boundingBox.max.z; + } + }else + m_vecCentreOfMass.z = pHandling->CentreOfMass.z; + + if(bHoverCheat) + DoHoverSuspensionRatios(); + + if(m_aSuspensionSpringRatio[0] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[0].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[1] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[1].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[2] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[2].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[3] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[3].surfaceB) == ADHESIVE_SAND){ + if(GetModelIndex() != MI_RCBANDIT && GetModelIndex() != MI_RHINO){ + float slowdown; + CVector parallelSpeed = m_vecMoveSpeed - DotProduct(m_vecMoveSpeed, GetUp())*GetUp(); + float fSpeed = parallelSpeed.MagnitudeSqr(); + if(fSpeed > SQR(0.3f)){ + fSpeed = Sqrt(fSpeed); + parallelSpeed *= 0.3f / fSpeed; + slowdown = SAND_SLOWDOWN * Max(1.0f - 2.0f*fSpeed, 0.2f); + }else{ + bStuckInSand = true; + slowdown = SAND_SLOWDOWN; + } + if(pHandling->Flags & HANDLING_GOOD_INSAND) + slowdown *= 0.5f; + if(CWeather::WetRoads > 0.2f) + slowdown *= (1.2f - CWeather::WetRoads); + ApplyMoveForce(parallelSpeed * -CTimer::GetTimeStep()*slowdown*m_fMass); + drivingInSand = true; + } + } + }else if(pDriver && pDriver->IsPlayer() && + (pDriver->GetPedState() == PED_ARRESTED || + pDriver->GetPedState() == PED_DRAG_FROM_CAR || + (pDriver->GetPedState() == PED_EXIT_CAR || pDriver->m_objective == OBJECTIVE_LEAVE_CAR) && !CanPedJumpOutCar())){ + bIsHandbrakeOn = true; + m_fBrakePedal = 1.0f; + m_fGasPedal = 0.0f; + } + if(CPad::GetPad(0)->CarGunJustDown()) + ActivateBomb(); + 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; + bAudioChangingGear = false; + // that's all we do for simple vehicles + return; + + case STATUS_PHYSICS: + CCarAI::UpdateCarAI(this); + CCarCtrl::SteerAICarWithPhysics(this); + PlayHornIfNecessary(); + + if(bIsBeingCarJacked){ + m_fGasPedal = 0.0f; + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + } + + if(m_aSuspensionSpringRatio[0] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[0].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[1] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[1].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[2] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[2].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[3] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[3].surfaceB) == ADHESIVE_SAND){ + if(GetModelIndex() != MI_RCBANDIT && GetModelIndex() != MI_SANDKING && GetModelIndex() != MI_BFINJECT){ + bStuckInSand = true; + if(CWeather::WetRoads > 0.0f) + ApplyMoveForce(m_vecMoveSpeed * -CTimer::GetTimeStep()*SAND_SLOWDOWN*m_fMass * (1.0f-CWeather::WetRoads)); + else + ApplyMoveForce(m_vecMoveSpeed * -CTimer::GetTimeStep()*SAND_SLOWDOWN*m_fMass); + } + } + break; + + case STATUS_ABANDONED: + if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.1f)) + m_fBrakePedal = 0.2f; + else + m_fBrakePedal = 0.0f; + bIsHandbrakeOn = false; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + if(!IsAlarmOn()) + m_nCarHornTimer = 0; + + if(bIsBeingCarJacked){ + m_fGasPedal = 0.0f; + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + } + break; + + case STATUS_WRECKED: + m_fBrakePedal = 0.05f; + bIsHandbrakeOn = true; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + if(!IsAlarmOn()) + m_nCarHornTimer = 0; + break; + + case STATUS_PLAYER_DISABLED: + if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.1f) || + (pDriver && pDriver->IsPlayer() && + (pDriver->GetPedState() == PED_ARRESTED || + pDriver->GetPedState() == PED_DRAG_FROM_CAR || + (pDriver->GetPedState() == PED_EXIT_CAR || pDriver->m_objective == OBJECTIVE_LEAVE_CAR) && !CanPedJumpOutCar()))){ + bIsHandbrakeOn = true; + m_fBrakePedal = 1.0f; + m_fGasPedal = 0.0f; + }else{ + m_fBrakePedal = 0.0f; + bIsHandbrakeOn = false; + } + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; + if(!IsAlarmOn()) + m_nCarHornTimer = 0; + break; + default: break; + } + + // 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 && + !(m_fDamageImpulse > 0.0f && m_pDamageEntity && m_pDamageEntity->IsPed()) || + 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; + if(IsRealHeli() && m_aWheelSpeed[1] > 0.0f){ + skipPhysics = false; + m_nStaticFrames = 0; + } + } + + // Postpone + for(i = 0; i < 4; i++) + if(m_aGroundPhysical[i]){ + bRestingOnPhysical = true; + if(!CWorld::bForceProcessControl && m_aGroundPhysical[i]->bIsInSafePosition){ + bWasPostponed = true; + return; + } + } + + if(bRestingOnPhysical){ + skipPhysics = false; + m_nStaticFrames = 0; + } + + VehicleDamage(0.0f, 0); + + // special control + switch(GetModelIndex()){ + case MI_FIRETRUCK: + FireTruckControl(); + break; + case MI_RHINO: + TankControl(); + BlowUpCarsInPath(); + break; + case MI_VOODOO: + HydraulicControl(); + break; + default: + if(CVehicle::bCheat3 || carHasNitro){ + // 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_fTurnMass*0.01f, GetForward()*1.0f); + } + } + break; + } + + if(GetStatus() == STATUS_PHYSICS || GetStatus() == STATUS_SIMPLE) + if(AutoPilot.m_nCarMission == MISSION_HELI_FLYTOCOORS || + AutoPilot.m_nCarMission == MISSION_PLANE_FLYTOCOORS) + skipPhysics = true; + + if(skipPhysics){ + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + m_nCollisionRecords = 0; + bHasCollided = false; + bVehicleColProcessed = false; + bAudioChangingGear = 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); + m_fTireTemperature = 1.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; + } + }else if(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[i].surfaceB) == ADHESIVE_SAND && + GetModelIndex() != MI_RHINO){ + fwdSpeed *= 0.7f; + float f = 1.0f - fwdSpeed/0.3f - 0.7f*CWeather::WetRoads; + f = Max(f, 0.4f); + m_aSuspensionSpringRatio[i] += 0.35f*f*(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; + + ApplySpringCollisionAlt(pHandling->fSuspensionForceLevel, + springDirections[i], contactPoints[i], + m_aSuspensionSpringRatio[i], bias, m_aWheelColPoints[i].normal); + + m_aWheelSkidmarkUnk[i] = false; + if(m_aWheelColPoints[i].surfaceB == SURFACE_GRASS || + m_aWheelColPoints[i].surfaceB == SURFACE_MUD_DRY) + m_aWheelSkidmarkType[i] = SKIDMARK_MUDDY; + else if(m_aWheelColPoints[i].surfaceB == SURFACE_SAND || + m_aWheelColPoints[i].surfaceB == SURFACE_SAND_BEACH){ + m_aWheelSkidmarkType[i] = SKIDMARK_SANDY; + m_aWheelSkidmarkUnk[i] = true; + }else + m_aWheelSkidmarkType[i] = SKIDMARK_NORMAL; + }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 + } + if(m_aSuspensionSpringRatio[i] < 1.0f && m_aWheelColPoints[i].normal.z > 0.35f) + springDirections[i] = -m_aWheelColPoints[i].normal; + } + + // dampen springs + for(i = 0; i < 4; i++) + if(m_aSuspensionSpringRatio[i] < 0.99999f) + 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; + + if(IsRealHeli() || IsRealPlane()) + acceleration = 0.0f; + + if(bAudioChangingGear && m_fGasPedal > 0.4f && m_fBrakePedal < 0.1f && fwdSpeed > 0.15f && + this == FindPlayerVehicle() && TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_1STPERSON){ + if(GetStatus() == STATUS_PLAYER && !(pHandling->Flags & HANDLING_IS_BUS)){ + if(m_nBusDoorTimerEnd == 0) + m_nBusDoorTimerEnd = 1000; + else { + uint32 timeStepInMs = CTimer::GetTimeStepInMilliseconds(); + if(m_nBusDoorTimerEnd > timeStepInMs) + m_nBusDoorTimerEnd -= timeStepInMs; + else + m_nBusDoorTimerEnd = 0; + } + } + + if((m_aSuspensionSpringRatio[0] < 1.0f || m_aSuspensionSpringRatio[2] < 1.0f) && + (m_aSuspensionSpringRatio[1] < 1.0f || m_aSuspensionSpringRatio[3] < 1.0f)) + ApplyTurnForce(-GRAVITY*Min(m_fTurnMass, 2500.0f)*GetUp(), -1.0f*GetForward()); + } + + brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep(); + bool neutralHandling = GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && (pHandling->Flags & HANDLING_NEUTRALHANDLING); + float brakeBiasFront = neutralHandling ? 1.0f : 2.0f*pHandling->fBrakeBias; + float brakeBiasRear = neutralHandling ? 1.0f : 2.0f-pHandling->fBrakeBias; // looks like a bug, but it was correct in III... + 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() != this && (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]; + + bool rearWheelsFirst = !!(pHandling->Flags & HANDLING_REARWHEEL_1ST); + + // Process front wheels on ground - first try + + if(!rearWheelsFirst){ + 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, wheelRight, tmp; + + if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + wheelFwd = GetForward(); + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal)*m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal; + wheelFwd.Normalise(); + wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal); + wheelRight.Normalise(); + tmp = c*wheelFwd - s*wheelRight; + wheelRight = s*wheelFwd + c*wheelRight; + wheelFwd = tmp; + + 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; + + wheelFwd = GetForward(); + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal)*m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal; + wheelFwd.Normalise(); + wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal); + wheelRight.Normalise(); + tmp = c*wheelFwd - s*wheelRight; + wheelRight = s*wheelFwd + c*wheelRight; + wheelFwd = tmp; + + 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(!IsRealHeli()){ + 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(); // overwritten for resp. wheel + + float rearBrake = brake; + float rearTraction = traction; + if(bIsHandbrakeOn){ +#ifdef FIX_BUGS + // Not sure if this is needed, but brake usually has timestep as a factor + rearBrake = 20000.0f * CTimer::GetTimeStepFix(); +#else + rearBrake = 20000.0f; +#endif + if(fwdSpeed > 0.1f && pHandling->Flags & HANDLING_HANDBRAKE_TYRE){ + m_fTireTemperature += 0.005*CTimer::GetTimeStep(); + if(m_fTireTemperature > 2.0f) + m_fTireTemperature = 2.0f; + } + }else if(m_doingBurnout && mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)){ + rearBrake = 0.0f; + rearTraction = 0.0f; + // BUG: missing timestep + ApplyTurnForce(contactPoints[CARWHEEL_REAR_LEFT], -0.001f*m_fTurnMass*m_fSteerAngle*GetRight()); + }else if(m_fTireTemperature > 1.0f){ + rearTraction *= m_fTireTemperature; + } + + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] > 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_REAR_LEFT].normal)*m_aWheelColPoints[CARWHEEL_REAR_LEFT].normal; + wheelFwd.Normalise(); + wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_REAR_LEFT].normal); + wheelRight.Normalise(); + + m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_REAR_LEFT])*rearTraction; + 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, + rearBrake*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, + rearBrake*brakeBiasRear, + adhesion*tractionBiasRear, + CARWHEEL_REAR_LEFT, + &m_aWheelSpeed[CARWHEEL_REAR_LEFT], + &WheelState[CARWHEEL_REAR_LEFT], + WHEEL_STATUS_OK); + } + +#ifdef FIX_BUGS + // Shouldn't we reset these after the left wheel? + wheelFwd = GetForward(); + wheelRight = GetRight(); // actually useless +#endif + + if(m_aWheelTimer[CARWHEEL_REAR_RIGHT] > 0.0f){ + if(mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_REAR_RIGHT].normal)*m_aWheelColPoints[CARWHEEL_REAR_RIGHT].normal; + wheelFwd.Normalise(); + wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_REAR_RIGHT].normal); + wheelRight.Normalise(); + + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[CARWHEEL_REAR_RIGHT])*rearTraction; + 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, + rearBrake*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, + rearBrake*brakeBiasRear, + adhesion*tractionBiasRear, + CARWHEEL_REAR_RIGHT, + &m_aWheelSpeed[CARWHEEL_REAR_RIGHT], + &WheelState[CARWHEEL_REAR_RIGHT], + WHEEL_STATUS_OK); + } + } + + if(m_doingBurnout && mod_HandlingManager.HasRearWheelDrive(pHandling->nIdentifier) && + (m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SPINNING || m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SPINNING)){ + m_fTireTemperature += 0.001f*CTimer::GetTimeStep(); + if(m_fTireTemperature > 3.0f) + m_fTireTemperature = 3.0f; + }else if(m_fTireTemperature > 1.0f){ + m_fTireTemperature = (m_fTireTemperature - 1.0f)*Pow(0.995f, CTimer::GetTimeStep()) + 1.0f; + } + + // Process rear wheels off ground + + if(!IsRealHeli()){ + if(m_aWheelTimer[CARWHEEL_REAR_LEFT] <= 0.0f){ + if(bIsHandbrakeOn) + m_aWheelSpeed[CARWHEEL_REAR_LEFT] = 0.0f; + else 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(bIsHandbrakeOn) + m_aWheelSpeed[CARWHEEL_REAR_RIGHT] = 0.0f; + else 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]; + } + } + + // Process front wheels on ground - second try + + if(rearWheelsFirst){ + 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, wheelRight, tmp; + + if(m_aWheelTimer[CARWHEEL_FRONT_LEFT] > 0.0f){ + if(mod_HandlingManager.HasFrontWheelDrive(pHandling->nIdentifier)) + fThrust = acceleration; + else + fThrust = 0.0f; + + wheelFwd = GetForward(); + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal)*m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal; + wheelFwd.Normalise(); + wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].normal); + wheelRight.Normalise(); + tmp = c*wheelFwd - s*wheelRight; + wheelRight = s*wheelFwd + c*wheelRight; + wheelFwd = tmp; + + 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; + + wheelFwd = GetForward(); + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal)*m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal; + wheelFwd.Normalise(); + wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].normal); + wheelRight.Normalise(); + tmp = c*wheelFwd - s*wheelRight; + wheelRight = s*wheelFwd + c*wheelRight; + wheelFwd = tmp; + + 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 (!IsRealHeli()) { + 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]; + } + } + } + + 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]; + if(GetModelIndex() == MI_VOODOO && bUsingSpecialColModel) + m_aWheelPosition[i] = wheelPos; + else + m_aWheelPosition[i] += (wheelPos - m_aWheelPosition[i])*0.75f; + } + for(i = 0; i < 4; i++) + m_aWheelState[i] = WheelState[i]; + if(m_fGasPedal < 0.0f){ + if(m_aWheelState[CARWHEEL_REAR_LEFT] == WHEEL_STATE_SPINNING) + m_aWheelState[CARWHEEL_REAR_LEFT] = WHEEL_STATE_NORMAL; + if(m_aWheelState[CARWHEEL_REAR_RIGHT] == WHEEL_STATE_SPINNING) + m_aWheelState[CARWHEEL_REAR_RIGHT] = WHEEL_STATE_NORMAL; + } + + // Process horn + + if(GetStatus() != STATUS_PLAYER){ + if(!IsAlarmOn()) + ReduceHornCounter(); + }else{ + if(UsesSiren()){ + 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_VOODOO && !CVehicle::bCheat3 && !carHasNitro){ + if(!IsAlarmOn()){ + if(Pads[0].GetHorn()) + m_nCarHornTimer = 1; + else + m_nCarHornTimer = 0; + } + } + } + + // Flying + + bool playRotorSound = false; + bool isPlane = GetModelIndex() == MI_DODO || bAllDodosCheat; +#ifdef FIX_BUGS + isPlane = isPlane && !IsRealHeli(); +#endif + if(GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && GetStatus() != STATUS_PHYSICS){ + if(IsRealHeli()){ + bEngineOn = false; + m_aWheelSpeed[1] = Max(m_aWheelSpeed[1]-0.0005f, 0.0f); + if(GetModelIndex() != MI_RCRAIDER && GetModelIndex() != MI_RCGOBLIN) + if(m_aWheelSpeed[1] < 0.154f && m_aWheelSpeed[1] > 0.0044f) + playRotorSound = true; + } + }else if(isPlane && m_vecMoveSpeed.Magnitude() > 0.0f && CTimer::GetTimeStep() > 0.0f){ + if(GetModelIndex() == MI_DODO) + FlyingControl(FLIGHT_MODEL_DODO); + else + FlyingControl(FLIGHT_MODEL_PLANE); + }else if(GetModelIndex() == MI_RCBARON){ + FlyingControl(FLIGHT_MODEL_RCPLANE); + }else if(IsRealHeli() || bAllCarCheat){ +#ifdef RESTORE_ALLCARSHELI_CHEAT + if (bAllCarCheat) + FlyingControl(FLIGHT_MODEL_HELI); + else +#endif + { + // Speed up rotor + if (m_aWheelSpeed[1] < 0.22f && !bIsInWater) { + if (GetModelIndex() == MI_RCRAIDER || GetModelIndex() == MI_RCGOBLIN) + m_aWheelSpeed[1] += 0.003f; + else + m_aWheelSpeed[1] += 0.001f; + } + + // Fly + if (m_aWheelSpeed[1] > 0.15f) { + if (GetModelIndex() == MI_RCRAIDER || GetModelIndex() == MI_RCGOBLIN) + FlyingControl(FLIGHT_MODEL_RCHELI); + else if (m_nWheelsOnGround < 4 && !(GetModelIndex() == MI_SEASPAR && bTouchingWater) || + CPad::GetPad(0)->GetAccelerate() != 0 || +#ifndef FREE_CAM + CPad::GetPad(0)->GetCarGunUpDown() > 1.0f || +#else + ((!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController)) && CPad::GetPad(0)->GetCarGunUpDown() > 1.0f) || +#endif + Abs(m_vecMoveSpeed.x) > 0.02f || + Abs(m_vecMoveSpeed.y) > 0.02f || + Abs(m_vecMoveSpeed.z) > 0.02f) + FlyingControl(FLIGHT_MODEL_HELI); + } + } + + // Blade collision + if(m_aWheelSpeed[1] > 0.015f && m_aCarNodes[CAR_BONNET]){ + CMatrix mat; + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BONNET])); + if(GetModelIndex() == MI_RCRAIDER || GetModelIndex() == MI_RCGOBLIN) + DoBladeCollision(mat.GetPosition(), GetMatrix(), ROTOR_TOP, 0.72f, 0.9f); + else if(GetModelIndex() == MI_SPARROW || GetModelIndex() == MI_SEASPAR) + DoBladeCollision(mat.GetPosition(), GetMatrix(), ROTOR_TOP, 5.15f, 0.8f); + else if(GetModelIndex() == MI_HUNTER) + DoBladeCollision(mat.GetPosition(), GetMatrix(), ROTOR_TOP, 6.15f, 0.5f); + else + DoBladeCollision(mat.GetPosition(), GetMatrix(), ROTOR_TOP, 6.15f, 1.0f); + } + + // Heli weapons + if(GetModelIndex() == MI_HUNTER && GetStatus() == STATUS_PLAYER){ + // Hunter rockets + if(CPad::GetPad(0)->CarGunJustDown() && CTimer::GetTimeInMilliseconds() > m_nGunFiringTime+350){ + CWeapon gun(WEAPONTYPE_ROCKETLAUNCHER, 100); + CVector source = vecHunterRocketPos; + source = GetMatrix()*source + Max(DotProduct(m_vecMoveSpeed, GetForward()), 0.0f)*GetForward()*CTimer::GetTimeStep(); + gun.FireProjectile(this, &source, 0.0f); + + source = vecHunterRocketPos; + source.x = -source.x; + source = GetMatrix()*source + Max(DotProduct(m_vecMoveSpeed, GetForward()), 0.0f)*GetForward()*CTimer::GetTimeStep(); + gun.FireProjectile(this, &source, 0.0f); + + CStats::RoundsFiredByPlayer++; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + m_nGunFiringTime = CTimer::GetTimeInMilliseconds(); + // Hunter gun + }else if(CPad::GetPad(0)->GetHandBrake() && CTimer::GetTimeInMilliseconds() > m_nGunFiringTime+60){ + CWeapon gun(WEAPONTYPE_HELICANNON, 5000); + CVector source = vecHunterGunPos; + source = GetMatrix()*source + m_vecMoveSpeed*CTimer::GetTimeStep(); + gun.FireInstantHit(this, &source); + gun.AddGunshell(this, source, CVector2D(0.0f, 0.1f), 0.025f); + CStats::RoundsFiredByPlayer++; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + m_nGunFiringTime = CTimer::GetTimeInMilliseconds(); + } + }else if(GetModelIndex() == MI_SEASPAR && GetStatus() == STATUS_PLAYER){ + // Sea sparrow gun + if(CPad::GetPad(0)->GetHandBrake() && CTimer::GetTimeInMilliseconds() > m_nGunFiringTime+40){ + CWeapon gun(WEAPONTYPE_M4, 5000); + CVector source = vecSeaSparrowGunPos; + source = GetMatrix()*source + m_vecMoveSpeed*CTimer::GetTimeStep(); + gun.FireInstantHit(this, &source); + gun.AddGunshell(this, source, CVector2D(0.0f, 0.1f), 0.025f); + CStats::RoundsFiredByPlayer++; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + m_nGunFiringTime = CTimer::GetTimeInMilliseconds(); + } + } + + if(GetModelIndex() != MI_RCRAIDER && GetModelIndex() != MI_RCGOBLIN) + if(m_aWheelSpeed[1] < 0.154f && m_aWheelSpeed[1] > 0.0044f) + playRotorSound = true; + } + + // Play rotor sound + if(playRotorSound && m_aCarNodes[CAR_BONNET]){ + CVector camDist = TheCamera.GetPosition() - GetPosition(); + float distSq = camDist.MagnitudeSqr(); + if(distSq < SQR(20.0f) && Abs(m_fPropellerRotation - m_aWheelRotation[1]) > DEGTORAD(30.0f)){ + CMatrix mat; + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BONNET])); + CVector blade = mat.GetRight(); + blade = Multiply3x3(GetMatrix(), blade); + camDist /= Max(Sqrt(distSq), 0.01f); + if(Abs(DotProduct(camDist, blade)) > HELI_ROTOR_DOTPROD_LIMIT){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_HELI_BLADE, 0.0f); + m_fPropellerRotation = m_aWheelRotation[1]; + } + } + } + } + + + + // 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.63f); + + 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) + 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() != 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; + float speedsq = m_vecMoveSpeed.MagnitudeSqr(); + for(i = 0; i < 4; i++){ + float suspChange = m_aSuspensionSpringRatioPrev[i] - m_aSuspensionSpringRatio[i]; + if(suspChange > 0.3f && !drivingInSand && speedsq > SQR(0.2f)){ + if(Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP_2, suspChange); + else + 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){ + if(surfShake < 0.1f) + surfShake = 0.2f; + }else if(surf == SURFACE_GRASS){ + if(surfShake < 0.05f) + surfShake = 0.1f; + } + + if(this == FindPlayerVehicle()) +// BUG: this only observes one of the wheels + TheCamera.m_bVehicleSuspenHigh = Abs(suspChange) > 0.05f; + + m_aSuspensionSpringRatioPrev[i] = m_aSuspensionSpringRatio[i]; + m_aSuspensionSpringRatio[i] = 1.0f; + } + + // Shake pad + + if(!drivingInSand && (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; + bAudioChangingGear = false; + + if(!bWarnedPeds && GetVehicleAppearance() != VEHICLE_APPEARANCE_HELI && GetVehicleAppearance() != VEHICLE_APPEARANCE_PLANE) + CCarCtrl::ScanForPedDanger(this); + + + // Turn around at the edge of the world + // TODO: make the numbers defines + + float heading; + if(GetPosition().x > 1950.0f-400.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 < -1950.0f-400.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 > 1950.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 < -1950.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_fDamageImpulse > 0.0f && m_pDamageEntity == FindPlayerPed()) && + (m_aSuspensionSpringRatioPrev[0] < 1.0f || m_aSuspensionSpringRatioPrev[1] < 1.0f || + m_aSuspensionSpringRatioPrev[2] < 1.0f || m_aSuspensionSpringRatioPrev[3] < 1.0f)){ + m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.0f); + m_vecTurnSpeed.z = 0.0f; + } + } + + if(IsRealHeli() && bHeliDestroyed && !bRenderScorched){ + ApplyMoveForce(0.0f, 0.0f, -2.0f*CTimer::GetTimeStep()); + m_vecTurnSpeed.z += -0.002f*CTimer::GetTimeStep(); + m_vecTurnSpeed.x += -0.0002f*CTimer::GetTimeStep(); + + RwRGBA col = { 84, 84, 84, 255 }; + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, GetMatrix()*CVector(0.0f, 0.0f, -10.0f), + CVector(0.0f, 0.0f, 0.0f), nil, 0.7f, col, 0, 0, 0, 3000); + + if(CWorld::TestSphereAgainstWorld(GetPosition(), 10.0f, this, true, false, false, false, false, false) || + GetPosition().z < 6.0f) + if(!bRenderScorched){ // we already know this is true... + CExplosion::AddExplosion(this, nil, EXPLOSION_CAR, GetPosition(), 0); + bRenderScorched = true; + } + } +} + +#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_RHINO && m_aCarNodes[CAR_WINDSCREEN]){ + // Rotate Rhino turret + CMatrix m; + CVector p; + m.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN])); + p = m.GetPosition(); + m.SetRotateZ(m_fCarGunLR); + m.Translate(p); + m.UpdateRW(); + } + + 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 || GetVehicleAppearance() != VEHICLE_APPEARANCE_CAR){ + ; // 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++){ + if(m_aSuspensionSpringRatioPrev[i] < 1.0f && m_aWheelColPoints[i].surfaceB != SURFACE_WATER) + 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_aWheelSkidmarkType[i], &m_aWheelSkidmarkBloody[i]); + break; + + case WHEEL_STATE_SKIDDING: + if(i == CARWHEEL_REAR_LEFT || i == CARWHEEL_REAR_RIGHT || rearSkidding){ + // same as below + + if(Abs(fwdSpeed) > 5.0f){ + 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_aWheelSkidmarkType[i], &m_aWheelSkidmarkBloody[i]); + } + break; + + case WHEEL_STATE_FIXED: + if(Abs(fwdSpeed) > 5.0f){ + 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_aWheelSkidmarkType[i], &m_aWheelSkidmarkBloody[i]); + break; + + default: + if(Abs(fwdSpeed) > 5.0f) + AddWheelDirtAndWater(&m_aWheelColPoints[i], drawParticles); + if((m_aWheelSkidmarkBloody[i] || m_aWheelSkidmarkUnk[i]) && m_aWheelTimer[i] > 0.0f) + CSkidmarks::RegisterOne((uintptr)this + i, m_aWheelColPoints[i].point, + GetForward().x, GetForward().y, + m_aWheelSkidmarkType[i], &m_aWheelSkidmarkBloody[i]); + } + + // Sparks for friction of burst wheels + if(Damage.GetWheelStatus(i) == WHEEL_STATUS_BURST && m_aSuspensionSpringRatioPrev[i] < 1.0f){ + static float speedSq; + speedSq = m_vecMoveSpeed.MagnitudeSqr(); + if(speedSq > SQR(0.1f) && + m_aWheelColPoints[i].surfaceB != SURFACE_GRASS && + m_aWheelColPoints[i].surfaceB != SURFACE_MUD_DRY && + m_aWheelColPoints[i].surfaceB != SURFACE_SAND && + m_aWheelColPoints[i].surfaceB != SURFACE_SAND_BEACH && + m_aWheelColPoints[i].surfaceB != SURFACE_WATER){ + CVector normalSpeed = m_aWheelColPoints[i].normal * DotProduct(m_aWheelColPoints[i].normal, m_vecMoveSpeed); + CVector frictionSpeed = m_vecMoveSpeed - normalSpeed; + if(i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_REAR_LEFT) + frictionSpeed -= 0.05f*GetRight(); + else + frictionSpeed += 0.05f*GetRight(); + CVector unusedRight = 0.15f*GetRight(); + CVector sparkDir = 0.25f*frictionSpeed; + CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[i].point, sparkDir); + + if(speedSq > 0.04f) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[i].point, sparkDir); + if(speedSq > 0.16f){ + CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[i].point, sparkDir); + CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[i].point, sparkDir); + } + } + } + } + } + } + + 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_aWheelSkidmarkType[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_aWheelSkidmarkType[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 && !(pHandling->Flags & HANDLING_NO_EXHAUST) && fwdSpeed < 130.0f){ + CVector exhaustPos = mi->m_positions[CAR_POS_EXHAUST]; + CVector pos1, pos2, dir1, dir2; + + if(exhaustPos != CVector(0.0f, 0.0f, 0.0f)){ + dir1.z = 0.0f; + dir2.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); + dir1.x = steerFwd.x * r; + dir1.y = steerFwd.y * r; + }else{ + dir1.x = m_vecMoveSpeed.x; + dir1.y = m_vecMoveSpeed.y; + } + + pos1 = GetMatrix() * exhaustPos; + if(pHandling->Flags & HANDLING_DBL_EXHAUST){ + pos2 = exhaustPos; + pos2.x = -pos2.x; + pos2 = GetMatrix() * pos2; + dir2 = dir1; + } + + static float fumesLimit = 2.0f; + if(CGeneral::GetRandomNumberInRange(1.0f, 3.0f)*(m_fGasPedal+1.1f) > fumesLimit) + for(i = 0; i < 4;){ + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir1); + if(pHandling->Flags & HANDLING_DBL_EXHAUST) + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos2, dir2); + + static float extraFumesLimit = 0.5f; + if(m_fGasPedal > extraFumesLimit && m_nCurrentGear < 3){ + if(CGeneral::GetRandomNumber() & 1) + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir1); + else if(pHandling->Flags & HANDLING_DBL_EXHAUST) + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos2, dir2); + } + + // Fire on Cuban hermes + if(GetModelIndex() == MI_CUBAN && i == 1 && m_fGasPedal > 0.9f){ + if(m_nCurrentGear == 1 || m_nCurrentGear == 3 && (CTimer::GetTimeInMilliseconds()%1500) > 750){ + if(CGeneral::GetRandomNumber() & 1){ + CParticle::AddParticle(PARTICLE_FIREBALL, pos1, dir1, nil, 0.05f, 0, 0, 2, 200); + CParticle::AddParticle(PARTICLE_FIREBALL, pos1, dir1, nil, 0.05f, 0, 0, 2, 200); + }else{ + CParticle::AddParticle(PARTICLE_FIREBALL, pos2, dir2, nil, 0.05f, 0, 0, 2, 200); + CParticle::AddParticle(PARTICLE_FIREBALL, pos2, dir2, nil, 0.05f, 0, 0, 2, 200); + } + } + } + + if(GetStatus() == STATUS_PLAYER && (CTimer::GetFrameCounter()&3) == 0 && + CWeather::Rain == 0.0f && i == 0){ + CVector camDist = GetPosition() - TheCamera.GetPosition(); + if(DotProduct(GetForward(), camDist) > 0.0f || + TheCamera.GetLookDirection() == LOOKING_LEFT || + TheCamera.GetLookDirection() == LOOKING_RIGHT){ + CParticle::AddParticle(PARTICLE_HEATHAZE, pos1, CVector(0.0f, 0.0f, 0.0f)); + if(pHandling->Flags & HANDLING_DBL_EXHAUST) + CParticle::AddParticle(PARTICLE_HEATHAZE, pos2, CVector(0.0f, 0.0f, 0.0f)); + + CParticle::AddParticle(PARTICLE_HEATHAZE, pos1, CVector(0.0f, 0.0f, 0.0f)); + if(pHandling->Flags & HANDLING_DBL_EXHAUST) + CParticle::AddParticle(PARTICLE_HEATHAZE, pos2, CVector(0.0f, 0.0f, 0.0f)); + } + } + + if(GetModelIndex() == MI_CUBAN && i < 1){ + i = 1; + pos1 = GetMatrix() * CVector(1.134f, -1.276f, -0.56f); + pos2 = GetMatrix() * CVector(-1.134f, -1.276f, -0.56f); + dir1 += 0.05f*GetRight(); + dir2 -= 0.05f*GetRight(); + }else + i = 99; + } + } + } + + + // 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_FBIRANCH: + case MI_VICECHEE: + if(m_bSirenOrAlarm){ + CVector pos = GetMatrix() * CVector(0.4f, 0.6f, 0.3f); + if(CTimer::GetTimeInMilliseconds() & 0x100 && + DotProduct(GetForward(), GetPosition() - TheCamera.GetPosition()) < 0.0f) + if(GetModelIndex() == MI_VICECHEE) + CCoronas::RegisterCorona((uintptr)this + 21, + 255, 70, 70, 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::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_ZEBRA: + case MI_KAUFMAN: + 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 && GetModelIndex() != MI_RCBARON && + GetVehicleAppearance() != VEHICLE_APPEARANCE_HELI) { + // 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 + 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(DotProduct(lightR-TheCamera.GetPosition(), GetForward()) < 0.0f && + (TheCamera.Cams[TheCamera.ActiveCam].Mode != CCam::MODE_1STPERSON || this != FindPlayerVehicle())){ + // 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(DotProduct(lightR-TheCamera.GetPosition(), GetForward()) > 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, 5.5f*fwd.y, -5.5f*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 + } + + if (IsRealHeli()) + CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_HELI); + else if ( GetModelIndex() == MI_RCBARON) + CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_RCPLANE); + else + CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_CAR); + + DoSunGlare(); + + // Heli dust + if(IsRealHeli() && m_aWheelSpeed[1] > 0.1125f && GetPosition().z < 30.0f){ + bool foundGround = false; + float waterZ = -1000.0f; + float groundZ = CWorld::FindGroundZFor3DCoord(GetPosition().x, GetPosition().y, GetPosition().z, &foundGround); + if(!CWaterLevel::GetWaterLevel(GetPosition(), &waterZ, false)) + waterZ = 0.0f; + groundZ = Max(groundZ, waterZ); + float rnd = (m_aWheelSpeed[1]-0.1125f)*((int)Max(16.0f-4.0f*CTimer::GetTimeStep(),2.0f))*400.0f/43.0f; + float radius = 10.0f; + if(GetModelIndex() == MI_RCGOBLIN || GetModelIndex() == MI_RCRAIDER) + radius = 3.0f; + if(GetPosition().z - groundZ < radius) + HeliDustGenerate(this, radius-(GetPosition().z - groundZ), groundZ, Ceil(rnd)); + } + + CMatrix mat; + CVector pos; + + bool onlyFrontWheels = false; + if(IsRealHeli()){ + // top rotor + m_aWheelRotation[1] += m_aWheelSpeed[1]*CTimer::GetTimeStep(); + while(m_aWheelRotation[1] > TWOPI) m_aWheelRotation[1] -= TWOPI; + // rear rotor + m_aWheelRotation[3] += m_aWheelSpeed[1]*CTimer::GetTimeStep(); + while(m_aWheelRotation[3] > TWOPI) m_aWheelRotation[3] -= TWOPI; + onlyFrontWheels = true; + } + + 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 && (!onlyFrontWheels || i == CARWHEEL_FRONT_LEFT || i == CARWHEEL_FRONT_RIGHT)) { + 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]; + } + } + + RwRGBA hoverParticleCol = { 255, 255, 255, 32 }; + + // Rear right wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); + pos = mat.GetPosition(); + 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]); + if(GetStatus() == STATUS_PLAYER){ + if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_REAR_RIGHT] < 1.0f && + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB == SURFACE_WATER){ + // hovering on water + mat.RotateY(-HALFPI); + if((CTimer::GetFrameCounter()+CARWHEEL_REAR_RIGHT) & 1){ + CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point, + 0.5f*m_vecMoveSpeed+0.1f*GetRight(), nil, 0.4f, hoverParticleCol); + }else{ + CParticle::AddParticle(PARTICLE_CAR_SPLASH, m_aWheelColPoints[CARWHEEL_REAR_RIGHT].point, + 0.3f*m_vecMoveSpeed+0.15f*GetRight()+CVector(0.0f, 0.0f, 0.1f), nil, 0.15f, hoverParticleCol, + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), + CGeneral::GetRandomNumberInRange(0.0f, 90.0f), 1); + } +#ifdef BETTER_ALLCARSAREDODO_CHEAT + } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) { + mat.RotateY(-HALFPI); +#endif + }else{ + // tilt wheel depending oh how much it presses on ground + float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale; + if(GetModelIndex() == MI_VOODOO) + groundOffset *= 0.6f; + mat.RotateY(Asin(Clamp(-groundOffset, -1.0f, 1.0f))); + } + } + if(pHandling->Flags & HANDLING_FAT_REARW) + mat.Scale(1.15f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale); + else + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + + // Rear left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); + pos = mat.GetPosition(); + 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); + if(GetStatus() == STATUS_PLAYER){ + if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_REAR_LEFT] < 1.0f && + m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB == SURFACE_WATER){ + // hovering on water + mat.RotateY(HALFPI); + if((CTimer::GetFrameCounter()+CARWHEEL_REAR_LEFT) & 1){ + CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, m_aWheelColPoints[CARWHEEL_REAR_LEFT].point, + 0.5f*m_vecMoveSpeed-0.1f*GetRight(), nil, 0.4f, hoverParticleCol); + }else{ + CParticle::AddParticle(PARTICLE_CAR_SPLASH, m_aWheelColPoints[CARWHEEL_REAR_LEFT].point, + 0.3f*m_vecMoveSpeed-0.15f*GetRight()+CVector(0.0f, 0.0f, 0.1f), nil, 0.15f, hoverParticleCol, + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), + CGeneral::GetRandomNumberInRange(0.0f, 90.0f), 1); + } +#ifdef BETTER_ALLCARSAREDODO_CHEAT + } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) { + mat.RotateY(HALFPI); +#endif + }else{ + // tilt wheel depending oh how much it presses on ground + float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale; + if(GetModelIndex() == MI_VOODOO) + groundOffset *= 0.6f; + mat.RotateY(Asin(Clamp(groundOffset, -1.0f, 1.0f))); + } + } + if(pHandling->Flags & HANDLING_FAT_REARW) + mat.Scale(1.15f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale); + else + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + + // Mid right wheel + if(m_aCarNodes[CAR_WHEEL_RM]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM])); + pos = mat.GetPosition(); + 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]); + if(GetStatus() == STATUS_PLAYER){ + if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_REAR_RIGHT] < 1.0f && + m_aWheelColPoints[CARWHEEL_REAR_RIGHT].surfaceB == SURFACE_WATER){ + // hovering on water + mat.RotateY(-HALFPI); +#ifdef BETTER_ALLCARSAREDODO_CHEAT + } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) { + mat.RotateY(-HALFPI); +#endif + }else{ + // tilt wheel depending oh how much it presses on ground + float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale; + if(GetModelIndex() == MI_VOODOO) + groundOffset *= 0.6f; + mat.RotateY(Asin(Clamp(-groundOffset, -1.0f, 1.0f))); + } + } + if(pHandling->Flags & HANDLING_FAT_REARW) + mat.Scale(1.15f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale); + else + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + } + + // Mid left wheel + if(m_aCarNodes[CAR_WHEEL_LM]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LM])); + pos = mat.GetPosition(); + 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); + if(GetStatus() == STATUS_PLAYER){ + if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_REAR_LEFT] < 1.0f && + m_aWheelColPoints[CARWHEEL_REAR_LEFT].surfaceB == SURFACE_WATER){ + // hovering on water + mat.RotateY(HALFPI); +#ifdef BETTER_ALLCARSAREDODO_CHEAT + } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) { + mat.RotateY(HALFPI); +#endif + }else{ + // tilt wheel depending oh how much it presses on ground + float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale; + if(GetModelIndex() == MI_VOODOO) + groundOffset *= 0.6f; + mat.RotateY(Asin(Clamp(groundOffset, -1.0f, 1.0f))); + } + } + if(pHandling->Flags & HANDLING_FAT_REARW) + mat.Scale(1.15f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale); + else + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + } + + if(GetModelIndex() == MI_DODO){ + // Front wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos = mat.GetPosition(); + 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(); + + // 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 = mat.GetPosition(); + 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(); + + // Front left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + pos = mat.GetPosition(); + 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(); + }else if(IsRealHeli()){ + // Top rotor + if(m_aCarNodes[CAR_BONNET]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BONNET])); + pos = mat.GetPosition(); + mat.SetRotateZ(m_aWheelRotation[1]); + mat.Translate(pos); + mat.UpdateRW(); + } + // Blurred top rotor + if(m_aCarNodes[CAR_WINDSCREEN]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WINDSCREEN])); + pos = mat.GetPosition(); + mat.SetRotateZ(-m_aWheelRotation[1]); + mat.Translate(pos); + mat.UpdateRW(); + } + // Rear rotor + if(m_aCarNodes[CAR_BOOT]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BOOT])); + pos = mat.GetPosition(); + mat.SetRotateX(m_aWheelRotation[3]); + mat.Translate(pos); + mat.UpdateRW(); + } + // Blurred rear rotor + if(m_aCarNodes[CAR_BUMP_REAR]){ + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_BUMP_REAR])); + pos = mat.GetPosition(); + mat.SetRotateX(-m_aWheelRotation[3]); + mat.Translate(pos); + mat.UpdateRW(); + } + }else{ + // Front right wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + pos = mat.GetPosition(); + 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); + if(GetStatus() == STATUS_PLAYER){ + if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_FRONT_RIGHT] < 1.0f && + m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].surfaceB == SURFACE_WATER){ + // hovering on water + mat.RotateY(-HALFPI); + if((CTimer::GetFrameCounter()+CARWHEEL_FRONT_RIGHT) & 1){ + CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].point, + 0.5f*m_vecMoveSpeed+0.1f*GetRight(), nil, 0.4f, hoverParticleCol); + }else{ + CParticle::AddParticle(PARTICLE_CAR_SPLASH, m_aWheelColPoints[CARWHEEL_FRONT_RIGHT].point, + 0.3f*m_vecMoveSpeed+0.15f*GetRight()+CVector(0.0f, 0.0f, 0.1f), nil, 0.15f, hoverParticleCol, + CGeneral::GetRandomNumberInRange(0.0f, 90.0f), + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), 1); + } +#ifdef BETTER_ALLCARSAREDODO_CHEAT + } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) { + mat.RotateY(-HALFPI); +#endif + }else{ + // tilt wheel depending oh how much it presses on ground + float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale; + if(GetModelIndex() == MI_VOODOO) + groundOffset *= 0.6f; + mat.RotateY(Asin(Clamp(-groundOffset, -1.0f, 1.0f))); + } + } + if(pHandling->Flags & HANDLING_NARROW_FRONTW) + mat.Scale(0.7f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale); + else + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + + // Front left wheel + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LF])); + pos = mat.GetPosition(); + 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); + if(GetStatus() == STATUS_PLAYER){ + if(bHoverCheat && m_aSuspensionSpringRatioPrev[CARWHEEL_FRONT_LEFT] < 1.0f && + m_aWheelColPoints[CARWHEEL_FRONT_LEFT].surfaceB == SURFACE_WATER){ + // hovering on water + mat.RotateY(HALFPI); + if((CTimer::GetFrameCounter()+CARWHEEL_FRONT_LEFT) & 1){ + CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].point, + 0.5f*m_vecMoveSpeed-0.1f*GetRight(), nil, 0.4f, hoverParticleCol); + }else{ + CParticle::AddParticle(PARTICLE_CAR_SPLASH, m_aWheelColPoints[CARWHEEL_FRONT_LEFT].point, + 0.3f*m_vecMoveSpeed-0.15f*GetRight()+CVector(0.0f, 0.0f, 0.1f), nil, 0.15f, hoverParticleCol, + CGeneral::GetRandomNumberInRange(0.0f, 90.0f), + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), 1); + } +#ifdef BETTER_ALLCARSAREDODO_CHEAT + } else if (bAllDodosCheat && m_nDriveWheelsOnGround == 0 && m_nDriveWheelsOnGroundPrev == 0) { + mat.RotateY(HALFPI); +#endif + }else{ + // tilt wheel depending oh how much it presses on ground + float groundOffset = pos.z + m_fHeightAboveRoad - 0.5f*mi->m_wheelScale; + if(GetModelIndex() == MI_VOODOO) + groundOffset *= 0.6f; + mat.RotateY(Asin(Clamp(groundOffset, -1.0f, 1.0f))); + } + } + if(pHandling->Flags & HANDLING_NARROW_FRONTW) + mat.Scale(0.7f*mi->m_wheelScale, mi->m_wheelScale, mi->m_wheelScale); + else + mat.Scale(mi->m_wheelScale); + mat.Translate(pos); + mat.UpdateRW(); + + 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); + } + + if((GetModelIndex() == MI_PHEONIX || GetModelIndex() == MI_BFINJECT) && + GetStatus() == STATUS_PLAYER && m_aCarNodes[CAR_WING_LR]){ + float rotation = 0.0f; + + if(GetModelIndex() == MI_BFINJECT) + if(m_fPropellerRotation > TWOPI) m_fPropellerRotation -= TWOPI; + + if(Abs(m_fGasPedal) > 0.0f){ + if(GetModelIndex() == MI_BFINJECT){ + m_fPropellerRotation += 0.2f*CTimer::GetTimeStep(); + rotation = m_fPropellerRotation; + }else{ + if(m_fPropellerRotation < 1.3f){ + m_fPropellerRotation = Min(m_fPropellerRotation+0.1f*CTimer::GetTimeStep(), 1.3f); + rotation = m_fPropellerRotation; + }else{ + float wave = Sin((CTimer::GetTimeInMilliseconds()%10000)/70.0f); + rotation = m_fPropellerRotation + 0.13*wave; + } + } + }else{ + if(GetModelIndex() == MI_BFINJECT){ + m_fPropellerRotation += 0.1f*CTimer::GetTimeStep(); + rotation = m_fPropellerRotation; + }else{ + if(m_fPropellerRotation > 0.0f){ + m_fPropellerRotation = Max(m_fPropellerRotation-0.05f*CTimer::GetTimeStep(), 0.0f); + rotation = m_fPropellerRotation; + } + } + } + + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WING_LR])); + pos = mat.GetPosition(); + if(GetModelIndex() == MI_BFINJECT) + mat.SetRotateY(rotation); + else + mat.SetRotateX(rotation); + mat.Translate(pos); + mat.UpdateRW(); + } +} + +void +CAutomobile::Render(void) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + mi->SetVehicleColour(m_currentColour1, m_currentColour2); + + if(IsRealHeli()){ + RpAtomic *atomic = nil; + int rotorAlpha = (1.5f - Min(1.7f*Max(m_aWheelSpeed[1],0.0f)/0.22f, 1.5f))*255.0f; + rotorAlpha = Min(rotorAlpha, 255); + int blurAlpha = Max(1.5f*m_aWheelSpeed[1]/0.22f - 0.4f, 0.0f)*150.0f; + blurAlpha = Min(blurAlpha, 150); + + // Top rotor + if(m_aCarNodes[CAR_BONNET]){ + RwFrameForAllObjects(m_aCarNodes[CAR_BONNET], GetCurrentAtomicObjectCB, &atomic); + if(atomic) + SetComponentAtomicAlpha(atomic, rotorAlpha); + } + atomic = nil; + // Rear rotor + if(m_aCarNodes[CAR_BOOT]){ + RwFrameForAllObjects(m_aCarNodes[CAR_BOOT], GetCurrentAtomicObjectCB, &atomic); + if(atomic) + SetComponentAtomicAlpha(atomic, rotorAlpha); + } + atomic = nil; + // Blurred top rotor + if(m_aCarNodes[CAR_WINDSCREEN]){ + RwFrameForAllObjects(m_aCarNodes[CAR_WINDSCREEN], GetCurrentAtomicObjectCB, &atomic); + if(atomic) + SetComponentAtomicAlpha(atomic, blurAlpha); + } + atomic = nil; + // Blurred rear rotor + if(m_aCarNodes[CAR_BUMP_REAR]){ + RwFrameForAllObjects(m_aCarNodes[CAR_BUMP_REAR], GetCurrentAtomicObjectCB, &atomic); + if(atomic) + SetComponentAtomicAlpha(atomic, blurAlpha); + } + } + + if(CVehicle::bWheelsOnlyCheat){ + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB])); + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB])); + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RF])); + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LF])); + if(m_aCarNodes[CAR_WHEEL_RM]) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RM])); + if(m_aCarNodes[CAR_WHEEL_LM]) + RpAtomicRender((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LM])); + }else + 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]; + + if(m_bIsVehicleBeingShifted || bSkipLineCol || ent->IsPed() || + GetModelIndex() == MI_DODO && ent->IsVehicle()) + colModel->numLines = 0; + + 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(colModel->numLines){ + 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(); + } + + m_nSurfaceTouched = m_aWheelColPoints[i].surfaceB; + if(ent->IsBuilding()) + m_pCurGroundEntity = ent; + } + }else + colModel->numLines = 4; + + 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() || + pDriver && pDriver->m_pVehicleAnim && (pDriver->m_pVehicleAnim->animId == ANIM_STD_ROLLOUT_LHS || + pDriver->m_pVehicleAnim->animId == ANIM_STD_ROLLOUT_RHS)) + bIsHandbrakeOn = !!CPad::GetPad(pad)->GetHandBrake(); + else + bIsHandbrakeOn = true; + + // 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 + if(CPad::GetPad(pad)->GetAccelerate() > 150.0f && CPad::GetPad(pad)->GetBrake() > 150.0f){ + m_fGasPedal = CPad::GetPad(pad)->GetAccelerate()/255.0f; + m_fBrakePedal = CPad::GetPad(pad)->GetBrake()/255.0f; + m_doingBurnout = 1; + }else{ + 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){ + 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; + + // Brake if player isn't in control + // BUG: game always uses pad 0 here +#ifdef FIX_BUGS + if(CPad::GetPad(pad)->ArePlayerControlsDisabled()){ +#else + if(CPad::GetPad(0)->ArePlayerControlsDisabled()){ +#endif + 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()){ + if(!CPad::GetPad(0)->GetCarGunFired()) + 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); + } +} + +#define HYDRAULIC_UPPER_EXT (-0.16f) +#define HYDRAULIC_LOWER_EXT (0.16f) + +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; + 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; + } + 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 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; + } + } + }else{ + if(m_hydraulicState < 104) + m_hydraulicState++; + + 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; + 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; + + bool heliHitWaterHard = false; + if(IsRealHeli() && m_aWheelSpeed[1] > 0.15f){ + if(GetModelIndex() == MI_SEASPAR){ + if(impulseRatio > 3.0f){ + m_aWheelSpeed[1] = 0.0f; + heliHitWaterHard = true; + } + }else{ + float strength = Max(8.0f*impulseRatio, 1.0f); + ApplyMoveForce(-2.0f*impulse/strength); + ApplyTurnForce(-impulse/strength, point); + if(impulseRatio > 0.9f){ + m_aWheelSpeed[1] = 0.0f; + heliHitWaterHard = true; + }else + return; + } + } + + bTouchingWater = true; + ApplyMoveForce(impulse); + ApplyTurnForce(impulse, point); + CVector initialSpeed = m_vecMoveSpeed; + + if(m_modelIndex == MI_SEASPAR && impulseRatio < 3.0f && (GetUp().z > -0.5f || impulseRatio < 0.6f) || + CVehicle::bHoverCheat && GetStatus() == STATUS_PLAYER && GetUp().z > 0.1f){ + bIsInWater = false; + bIsDrowning = false; + }else if(heliHitWaterHard || impulseRatio > 1.0f || + impulseRatio > 0.6f && (m_aSuspensionSpringRatio[0] == 1.0f || + m_aSuspensionSpringRatio[1] == 1.0f || + m_aSuspensionSpringRatio[2] == 1.0f || + m_aSuspensionSpringRatio[3] == 1.0f)){ + bIsInWater = true; + bIsDrowning = 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; + bIsDrowning = false; + } + + static uint32 nGenerateRaindrops = 0; + static uint32 nGenerateWaterCircles = 0; + + if(initialSpeed.z < -0.1f && impulse.z > 0.3f || heliHitWaterHard){ + RwRGBA color; + color.red = (0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed_Obj())*0.45f*255; + color.green = (0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen_Obj())*0.45f*255; + color.blue = (0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue_Obj())*0.45f*255; + color.alpha = CGeneral::GetRandomNumberInRange(0, 32) + 128; + CVector target = CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.15f, 0.45f)); + CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, GetPosition(), + target, 0.0f, 75, color, true); + + nGenerateRaindrops = CTimer::GetTimeInMilliseconds() + 300; + nGenerateWaterCircles = CTimer::GetTimeInMilliseconds() + 60; + + if(heliHitWaterHard){ + CVector right = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f)); + CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, GetPosition() + right, + target, 0.0f, 75, color, true); + CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, GetPosition() - right, + target, 0.0f, 75, color, true); + } + + 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; + bIsDrowning = 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; + float fSpeed = vSpeed.MagnitudeSqr(); + 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); + } + } + } + } +} + +void +CAutomobile::DoDriveByShootings(void) +{ + CAnimBlendAssociation *anim = nil; + CPlayerInfo* playerInfo = ((CPlayerPed*)pDriver)->GetPlayerInfoForThisPlayerPed(); + if (playerInfo && !playerInfo->m_bDriveByAllowed) + return; + + CWeapon *weapon = pDriver->GetWeapon(); + if(CWeaponInfo::GetWeaponInfo(weapon->m_eWeaponType)->m_nWeaponSlot != WEAPONSLOT_SUBMACHINEGUN) + return; + + weapon->Update(pDriver->m_audioEntityId, nil); + + bool lookingLeft = false; + bool lookingRight = false; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || + TheCamera.m_bObbeCinematicCarCamOn){ + 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; + } + + AnimationId rightAnim = ANIM_STD_CAR_DRIVEBY_RIGHT; + AnimationId leftAnim = ANIM_STD_CAR_DRIVEBY_LEFT; + if (pDriver->m_pMyVehicle->bLowVehicle) { + rightAnim = ANIM_STD_CAR_DRIVEBY_RIGHT_LO; + leftAnim = ANIM_STD_CAR_DRIVEBY_LEFT_LO; + } + + if(lookingLeft || lookingRight){ + if(lookingLeft){ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), rightAnim); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), leftAnim); + if(anim == nil || anim->blendDelta < 0.0f) + anim = CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, leftAnim); + }else if(pDriver->m_pMyVehicle->pPassengers[0] == nil || TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_1STPERSON){ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), leftAnim); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), rightAnim); + if(anim == nil || anim->blendDelta < 0.0f) + anim = CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, rightAnim); + } + + if (!anim || !anim->IsRunning()) { + if (CPad::GetPad(0)->GetCarGunFired() && CTimer::GetTimeInMilliseconds() > weapon->m_nTimer) { + weapon->FireFromCar(this, lookingLeft, true); + weapon->m_nTimer = CTimer::GetTimeInMilliseconds() + 70; + } + } + }else{ + weapon->Reload(); + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), leftAnim); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), rightAnim); + 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); + } +} + +void +CAutomobile::DoHoverSuspensionRatios(void) +{ + int i; + + if(GetUp().z < 0.1f) + return; + + CColModel *colmodel = GetColModel(); + for(i = 0; i < 4; i++){ + float z, waterZ; + CVector upper = GetMatrix() * colmodel->lines[i].p0; + CVector lower = GetMatrix() * colmodel->lines[i].p1; + if(m_aSuspensionSpringRatio[i] < 1.0f) + z = m_aWheelColPoints[i].point.z; + else + z = -100.0f; + // see if touching water + if(CWaterLevel::GetWaterLevel(lower, &waterZ, false) && + waterZ > z && lower.z-1.0f < waterZ){ + // compress spring + if(lower.z < waterZ){ + if(upper.z < waterZ) + m_aSuspensionSpringRatio[i] = 0.0f; + else + m_aSuspensionSpringRatio[i] = (upper.z - waterZ)/(upper.z - lower.z); + }else + m_aSuspensionSpringRatio[i] = 0.99999f; + + m_aWheelColPoints[i].point = CVector((lower.x - upper.x)*m_aSuspensionSpringRatio[i] + upper.x, + (lower.y - upper.y)*m_aSuspensionSpringRatio[i] + upper.y, + waterZ); + m_aWheelColPoints[i].normal = CVector(0.0f, 0.0f, 1.0f); + m_aWheelColPoints[i].surfaceB = SURFACE_WATER; + } + } +} + +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->GetModelIndex() != MI_RCBANDIT && + 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.333f; + + if(impulse == 0.0f){ + impulse = m_fDamageImpulse; + damagedPiece = m_nDamagePieceType; + damageMultiplier = 1.0f; + } + + if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f) + impulse *= 0.5f; + + CVector pos(0.0f, 0.0f, 0.0f); + + if(!bCanBeDamaged) + return; + + if(m_pDamageEntity && m_pDamageEntity->IsPed() && ((CPed*)m_pDamageEntity)->bIsStanding){ + float speed = ((CPed*)m_pDamageEntity)->m_vecAnimMoveDelta.y * DotProduct(GetForward(), m_vecDamageNormal); + if(speed < 0.0f) + impulse = Max(impulse + ((CPed*)m_pDamageEntity)->m_fMass * speed, 0.0f); + } + + // damage flipped over car + if(GetUp().z < 0.0f && this != FindPlayerVehicle()){ + if(bNotDamagedUpsideDown || GetStatus() == STATUS_PLAYER_REMOTE || bIsInWater) + return; + if(GetStatus() != STATUS_WRECKED) + m_fHealth = Max(m_fHealth - 4.0f*CTimer::GetTimeStep(), 0.0f); + } + + float minImpulse = GetModelIndex() == MI_RCRAIDER || GetModelIndex() == MI_RCGOBLIN ? 1.0f : 25.0f; + if(impulse > minImpulse && 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(GetStatus() != STATUS_PLAYER && bOnlyDamagedByPlayer){ + if(m_pDamageEntity != FindPlayerPed() && + m_pDamageEntity != FindPlayerVehicle()) + return; + } + + if(m_pDamageEntity && m_pDamageEntity->IsVehicle()){ + m_nLastWeaponDamage = WEAPONTYPE_RAMMEDBYCAR; + m_pLastDamageEntity = m_pDamageEntity; + } + + 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*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_BUMPER_FRONT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetBumperDamage(CAR_BUMP_FRONT, VEHBUMPER_FRONT); + if(m_aCarNodes[CAR_BONNET] && Damage.GetPanelStatus(VEHBUMPER_FRONT) == PANEL_STATUS_MISSING){ + case CAR_PIECE_BONNET: + GetComponentWorldPosition(CAR_BONNET, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(GetModelIndex() != MI_DODO) + if(Damage.ApplyDamage(COMPONENT_DOOR_BONNET, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetDoorDamage(CAR_BONNET, DOOR_BONNET); + } + break; + + case CAR_PIECE_BUMP_REAR: + GetComponentWorldPosition(CAR_BUMP_REAR, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_BUMPER_REAR, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetBumperDamage(CAR_BUMP_REAR, VEHBUMPER_REAR); + if(m_aCarNodes[CAR_BOOT] && Damage.GetPanelStatus(VEHBUMPER_REAR) == PANEL_STATUS_MISSING){ + case CAR_PIECE_BOOT: + GetComponentWorldPosition(CAR_BOOT, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_DOOR_BOOT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetDoorDamage(CAR_BOOT, DOOR_BOOT); + } + break; + + case CAR_PIECE_DOOR_LF: + GetComponentWorldPosition(CAR_DOOR_LF, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_DOOR_FRONT_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetDoorDamage(CAR_DOOR_LF, DOOR_FRONT_LEFT); + break; + case CAR_PIECE_DOOR_RF: + GetComponentWorldPosition(CAR_DOOR_RF, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_DOOR_FRONT_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetDoorDamage(CAR_DOOR_RF, DOOR_FRONT_RIGHT); + break; + case CAR_PIECE_DOOR_LR: + GetComponentWorldPosition(CAR_DOOR_LR, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_DOOR_REAR_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetDoorDamage(CAR_DOOR_LR, DOOR_REAR_LEFT); + break; + case CAR_PIECE_DOOR_RR: + GetComponentWorldPosition(CAR_DOOR_RR, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_DOOR_REAR_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetDoorDamage(CAR_DOOR_RR, DOOR_REAR_RIGHT); + break; + + case CAR_PIECE_WING_LF: + GetComponentWorldPosition(CAR_WING_LF, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_PANEL_FRONT_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetPanelDamage(CAR_WING_LF, VEHPANEL_FRONT_LEFT); + break; + case CAR_PIECE_WING_RF: + GetComponentWorldPosition(CAR_WING_RF, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_PANEL_FRONT_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetPanelDamage(CAR_WING_RF, VEHPANEL_FRONT_RIGHT); + break; + case CAR_PIECE_WING_LR: + GetComponentWorldPosition(CAR_WING_LR, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_PANEL_REAR_LEFT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetPanelDamage(CAR_WING_LR, VEHPANEL_REAR_LEFT); + break; + case CAR_PIECE_WING_RR: + GetComponentWorldPosition(CAR_WING_RR, pos); + dmgDrawCarCollidingParticles(pos, impulse*damageMultiplier); + if(Damage.ApplyDamage(COMPONENT_PANEL_REAR_RIGHT, impulse*impulseMult, pHandling->fCollisionDamageMultiplier)) + SetPanelDamage(CAR_WING_RR, VEHPANEL_REAR_RIGHT); + 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); + } + } + break; + } + } + + float damage = (impulse-minImpulse)*pHandling->fCollisionDamageMultiplier*0.6f*damageMultiplier; + + if(GetModelIndex() == MI_SECURICA && m_pDamageEntity && m_pDamageEntity->GetStatus() == STATUS_PLAYER) + damage *= 7.0f; + + if(GetModelIndex() == MI_RCGOBLIN || GetModelIndex() == MI_RCRAIDER) + damage *= 30.0f; + + if(damage > 0.0f){ + if(damage > 5.0f && + pDriver && + m_pDamageEntity && m_pDamageEntity->IsVehicle() && + (this != FindPlayerVehicle() || ((CVehicle*)m_pDamageEntity)->VehicleCreatedBy == MISSION_VEHICLE) && + ((CVehicle*)m_pDamageEntity)->pDriver){ + if(GetVehicleAppearance() == VEHICLE_APPEARANCE_CAR) + pDriver->Say(SOUND_PED_CRASH_CAR); + else + pDriver->Say(SOUND_PED_CRASH_VEHICLE); + } + + int oldHealth = m_fHealth; + if(this == FindPlayerVehicle()) + m_fHealth -= bTakeLessDamage ? damage/6.0f : damage/2.0f; + else if(bTakeLessDamage) + m_fHealth -= damage/12.0f; + else if(m_pDamageEntity && m_pDamageEntity == FindPlayerVehicle()) + m_fHealth -= damage/1.5f; + else + m_fHealth -= 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)); +} + +float fDamagePosSpeedShift = 0.4f; +float fSpeedMult[] = { + 0.8f, + 0.75f, + 0.85f, + 0.9f, + 0.85f, + 0.85f +}; + +void +CAutomobile::AddDamagedVehicleParticles(void) +{ + int i, n; + + if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson()) + return; + if(this != FindPlayerVehicle() && (CTimer::GetFrameCounter() + m_randomSeed) & 1) + return; + if(m_fHealth >= 650.0f) + return; + + CVector direction = fSpeedMult[5]*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); + else if(GetModelIndex() == MI_CADDY) + damagePos = CVector(0.6f, -1.0f, -0.25f); + else if(IsRealHeli()){ + damagePos.x = 0.4f*GetColModel()->boundingBox.max.x; + damagePos.y = 0.2f*GetColModel()->boundingBox.min.y; + damagePos.z = 0.3f*GetColModel()->boundingBox.max.z; + }else + damagePos.z += fDamagePosSpeedShift*(GetColModel()->boundingBox.max.z-damagePos.z) * DotProduct(GetForward(), m_vecMoveSpeed); + damagePos = GetMatrix()*damagePos; + damagePos.z += 0.15f; + + bool electric = pHandling->Transmission.nEngineType == 'E'; + + if(electric && m_fHealth < 320.0f && m_fHealth > 1.0f){ + direction = 0.85f*m_vecMoveSpeed; + direction += GetRight() * CGeneral::GetRandomNumberInRange(0.0f, 0.04f) * (1.0f - 2.0f*m_vecMoveSpeed.Magnitude()); + direction.z += 0.001f; + n = (CGeneral::GetRandomNumber() & 7) + 2; + for(i = 0; i < n; i++) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, damagePos, direction); + if(((CTimer::GetFrameCounter() + m_randomSeed) & 7) == 0) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, 0.8f*m_vecMoveSpeed, nil, 0.1f, 0, 0, 0, 1000); + }else if(electric && m_fHealth < 460.0f){ + direction = 0.85f*m_vecMoveSpeed; + direction += GetRight() * CGeneral::GetRandomNumberInRange(0.0f, 0.04f) * (1.0f - 2.0f*m_vecMoveSpeed.Magnitude()); + direction.z += 0.001f; + n = (CGeneral::GetRandomNumber() & 3) + 1; + for(i = 0; i < n; i++) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, damagePos, direction); + if(((CTimer::GetFrameCounter() + m_randomSeed) & 0xF) == 0) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, 0.8f*m_vecMoveSpeed, nil, 0.1f, 0, 0, 0, 1000); + }else if(m_fHealth < 250.0f){ + // nothing + }else if(m_fHealth < 320.0f){ + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, fSpeedMult[0]*direction); + }else if(m_fHealth < 390.0f){ + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, fSpeedMult[1]*direction); + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, fSpeedMult[2]*direction); + }else if(m_fHealth < 460.0f){ + if(((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 0 || + ((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 2) + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, fSpeedMult[3]*direction); + }else{ + int rnd = CTimer::GetFrameCounter() + m_randomSeed; + if(rnd < 10 || + rnd < 70 && rnd > 25 || + rnd < 160 && rnd > 100 || + rnd < 200 && rnd > 175 || + rnd > 235) + return; + direction.z += 0.05f*Max(1.0f - 1.6f*m_vecMoveSpeed.Magnitude(), 0.0f); + if(electric){ + direction = 0.85f*m_vecMoveSpeed; + direction += GetRight() * CGeneral::GetRandomNumberInRange(0.0f, 0.04f) * (1.0f - 2.0f*m_vecMoveSpeed.Magnitude()); + direction.z += 0.001f; + n = (CGeneral::GetRandomNumber() & 2) + 2; + for(i = 0; i < n; i++) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, damagePos, direction); + if(((CTimer::GetFrameCounter() + m_randomSeed) & 0xF) == 0) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, 0.8f*m_vecMoveSpeed, nil, 0.1f, 0, 0, 0, 1000); + }else{ + if(TheCamera.GetLookDirection() != LOOKING_FORWARD) + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction); + else if(((CTimer::GetFrameCounter() + m_randomSeed) & 1) == 0) + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, fSpeedMult[4]*m_vecMoveSpeed); + } + } +} + +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 sandCol = { 170, 165, 140, 255 }; + static RwRGBA waterCol = { 48, 48, 64, 0 }; + + if(!belowEffectSpeed && + colpoint->surfaceB != SURFACE_SAND && colpoint->surfaceB != SURFACE_SAND_BEACH) + 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.05f, 0.09f), 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; + case SURFACE_SAND: + case SURFACE_SAND_BEACH: + if(CTimer::GetFrameCounter() & 2 || + CWeather::WetRoads > 0.0f && CGeneral::GetRandomNumberInRange(CWeather::WetRoads, 1.01f) > 0.5f) + return 0; + dir.x = 0.5f*m_vecMoveSpeed.x; + dir.y = 0.5f*m_vecMoveSpeed.y; + for(i = 0; i < 1; i++){ + dir.z = CGeneral::GetRandomNumberInRange(0.02f, 0.055f); + CParticle::AddParticle(PARTICLE_SAND, colpoint->point, dir, nil, + 2.0f*m_vecMoveSpeed.Magnitude(), sandCol); + } + return 0; + default: + if(CWeather::WetRoads > 0.01f){ + if(CTimer::GetFrameCounter() & 1) + CParticle::AddParticle( + PARTICLE_WATERSPRAY, + colpoint->point + CVector(0.0f, 0.0f, 0.25f+0.25f), + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.005f, 0.04f)), + 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.41f, 0.89f); + 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.45f); + 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; +} + +bool +CAutomobile::IsDoorReady(uint32 door) +{ + switch(door){ + case CAR_DOOR_RF: return IsDoorReady(DOOR_FRONT_RIGHT); + case CAR_DOOR_RR: return IsDoorReady(DOOR_REAR_RIGHT); + case CAR_DOOR_LF: return IsDoorReady(DOOR_FRONT_LEFT); + case CAR_DOOR_LR: return IsDoorReady(DOOR_REAR_LEFT); + default: + return false; + } +} + +bool +CAutomobile::IsDoorMissing(uint32 door) +{ + switch(door){ + case CAR_DOOR_RF: return IsDoorMissing(DOOR_FRONT_RIGHT); + case CAR_DOOR_RR: return IsDoorMissing(DOOR_REAR_RIGHT); + case CAR_DOOR_LF: return IsDoorMissing(DOOR_FRONT_LEFT); + case CAR_DOOR_LR: return IsDoorMissing(DOOR_REAR_LEFT); + default: + return false; + } +} + +bool +CAutomobile::IsOpenTopCar(void) +{ + return GetModelIndex() == MI_STINGER || + // component 0 is assumed to be a roof + GetModelIndex() == MI_COMET && m_aExtras[0] != 0 && m_aExtras[1] != 0 || + GetModelIndex() == MI_STALLION && m_aExtras[0] != 0 && m_aExtras[1] != 0; +} + +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) +{ + RpAtomic *atomic; + + if(!bCanBeDamaged) + return; + + if(culprit == FindPlayerPed() || culprit == FindPlayerVehicle()){ + CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 20; + CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention += 10.0f; + CStats::PropertyDestroyed += CGeneral::GetRandomNumber()%6000 + 4000; + } + + // 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); + + KillPedsInVehicle(); + + 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(); + + if(GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI || + GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE) + return false; + + 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 / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LF); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_LB])); + colModel->spheres[1].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RF])); + colModel->spheres[2].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RF); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RB])); + colModel->spheres[3].Set(mi->m_wheelScale / 2, 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 / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR); + mat.Attach(RwFrameGetMatrix(m_aCarNodes[CAR_WHEEL_RM])); + colModel->spheres[5].Set(mi->m_wheelScale / 2, mat.GetPosition(), SURFACE_RUBBER, CAR_PIECE_WHEEL_RR); + colModel->numSpheres = 6; + }else + colModel->numSpheres = 4; + + return true; +} + +float fBurstForceMult = 0.03f; + +void +CAutomobile::BurstTyre(uint8 wheel, bool applyForces) +{ + if(GetModelIndex() == MI_RHINO || bTyresDontBurst) + return; + + 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); + CStats::TyresPopped++; + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_TYRE_POP, 0.0f); + + if(GetStatus() == STATUS_SIMPLE){ + SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(this); + } + + if(applyForces){ + 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 (IsAlarmOn() || m_nCarHornTimer != 0) + return; + + if (m_nCarHornDelay) { + m_nCarHornDelay--; + return; + } + + m_nCarHornDelay = (CGeneral::GetRandomNumber() & 0x7F) + 150; + r = m_nCarHornDelay & 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 = m_aSuspensionSpringLength[0]*(1.0f - 1.0f/(4.0f*pHandling->fSuspensionForceLevel)) + - colModel->lines[0].p0.z + mi->m_wheelScale*0.5f; + 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 && bTankDetonateCars) + 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){ + if(this == FindPlayerVehicle()) + CEventList::RegisterEvent(EVENT_EXPLOSION, EVENT_ENTITY_VEHICLE, m_aCollisionRecords[i], FindPlayerPed(), 2000); + ((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) +{ + CAutomobile *car = nil; + CBike *bike = nil; + for (CPtrNode *node = list.first; node; node = node->next) { + CVehicle *veh = (CVehicle*)node->item; + if (veh->m_scanCode != CWorld::GetCurrentScanCode()) { + veh->m_scanCode = CWorld::GetCurrentScanCode(); + + if (Abs(GetPosition().x - veh->GetPosition().x) < 10.0f && Abs(GetPosition().y - veh->GetPosition().y) < 10.0f) { + if (veh->IsCar()) { + bike = nil; + car = (CAutomobile*)veh; + } else if (veh->IsBike()) { + bike = (CBike*)veh; + car = nil; + } + if (veh->m_vecMoveSpeed.MagnitudeSqr2D() > 0.05f) { + if (car) { + for (int wheel = 0; wheel < 4; wheel++) { + if (!car->m_aWheelSkidmarkBloody[wheel] && car->m_aSuspensionSpringRatio[wheel] < 1.0f) { + + CColModel* vehCol = car->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 = car->GetMatrix() * approxWheelOffset; + if (Abs(wheelPos.z - GetPosition().z) < 2.0f) { + + if ((wheelPos - GetPosition()).MagnitudeSqr2D() < 1.0f) { + if (CGame::nastyGame) { + car->m_aWheelSkidmarkBloody[wheel] = true; + DMAudio.PlayOneShot(car->m_audioEntityId, SOUND_SPLATTER, 0.0f); + } + if (car->m_fMass > 500.f) { + car->ApplyMoveForce(CVector(0.0f, 0.0f, 50.0f * Min(1.0f, m_fMass * 0.001f))); + + CVector vehAndWheelDist = wheelPos - car->GetPosition(); + car->ApplyTurnForce(CVector(0.0f, 0.0f, 50.0f * Min(1.0f, m_fTurnMass * 0.0005f)), vehAndWheelDist); + if (car == FindPlayerVehicle()) { + CPad::GetPad(0)->StartShake(300, 70); + } + } + } + } + } + } + } else if (bike) { + for (int wheel = 0; wheel < 2; wheel++) { + if (!bike->m_aWheelSkidmarkBloody[wheel] && bike->m_aSuspensionSpringRatio[wheel] < 1.0f) { + + CColModel* vehCol = bike->GetModelInfo()->GetColModel(); + CVector approxWheelOffset; + switch (wheel) { + case 0: + approxWheelOffset = CVector(0.0f, 0.8f * vehCol->boundingBox.max.y, 0.0f); + break; + case 1: + approxWheelOffset = CVector(0.0f, 0.8f * vehCol->boundingBox.min.y, 0.0f); + default: + break; + } + + // I hope so + CVector wheelPos = bike->GetMatrix() * approxWheelOffset; + if (Abs(wheelPos.z - GetPosition().z) < 2.0f) { + + if ((wheelPos - GetPosition()).MagnitudeSqr2D() < 1.0f) { + if (CGame::nastyGame) { + bike->m_aWheelSkidmarkBloody[wheel] = true; + DMAudio.PlayOneShot(bike->m_audioEntityId, SOUND_SPLATTER, 0.0f); + } + if (bike->m_fMass > 100.0f) { + bike->ApplyMoveForce(CVector(0.0f, 0.0f, 10.0f)); + + CVector vehAndWheelDist = wheelPos - bike->GetPosition(); + bike->ApplyTurnForce(CVector(0.0f, 0.0f, 10.0f), vehAndWheelDist); + + if (bike == 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; + + if (m_aCarNodes[component] == nil) + 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(); + + // make wind rip off bonnet + if(door == DOOR_BONNET && Doors[door].m_nDoorState == DOORST_OPEN && + DotProduct(m_vecMoveSpeed, GetForward()) > 0.4f){ +#ifdef FIX_BUGS + CObject *comp = SpawnFlyingComponent(CAR_BONNET, COMPGROUP_BONNET); +#else + CObject *comp = 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); + + if(comp){ + if(CGeneral::GetRandomNumber() & 1) + comp->m_vecMoveSpeed = 0.4f*m_vecMoveSpeed + 0.1f*GetRight() + 0.5f*GetUp(); + else + comp->m_vecMoveSpeed = 0.4f*m_vecMoveSpeed - 0.1f*GetRight() + 0.5f*GetUp(); + comp->ApplyTurnForce(10.0f*GetUp(), GetForward()); + } + } +} + +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(); + } + } + + for(component = 0; component < 4; component++) + Damage.SetWheelStatus(component, WHEEL_STATUS_OK); + + if(GetModelIndex() == MI_HUNTER){ + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_LB]), 0); + RpAtomicSetFlags((RpAtomic*)GetFirstObject(m_aCarNodes[CAR_WHEEL_RB]), 0); + }else if(IsRealHeli()){ + 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); + } +} + +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; +} + +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); + obj->bDrawLast = true; + 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); + obj->bDontStream = true; + + // 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(GetStatus() == STATUS_WRECKED && IsVisible() && DotProduct(dist, TheCamera.GetPosition() - GetPosition()) > -0.5f){ + dist = TheCamera.GetPosition() - GetPosition(); + dist.Normalise(); + dist.z += 0.3f; + ApplyMoveForce(5.0f*dist); + } + + if(CCollision::ProcessColModels(obj->GetMatrix(), *obj->GetColModel(), + this->GetMatrix(), *this->GetColModel(), + CWorld::m_aTempColPts, 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){ + if(panel == VEHPANEL_WINDSCREEN) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_WINDSHIELD_CRACK, 0.0f); + // show damaged part + SetComponentVisibility(m_aCarNodes[component], ATOMIC_FLAG_DAM); + }else if(status == PANEL_STATUS_MISSING){ + if(!noFlyingComponents) + SpawnFlyingComponent(component, COMPGROUP_PANEL); + else if(panel == VEHPANEL_WINDSCREEN) + CGlass::CarWindscreenShatters(this, false); + // 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(!CanDoorsBeDamaged() && status > DOOR_STATUS_SMASHED && door != DOOR_BONNET && door != DOOR_BOOT){ + Damage.SetDoorStatus(door, DOOR_STATUS_SMASHED); + status = DOOR_STATUS_SMASHED; + } + + 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; +} + +void +CAutomobile::TellHeliToGoToCoors(float x, float y, float z, uint8 speed) +{ + AutoPilot.m_nCarMission = MISSION_HELI_FLYTOCOORS; + AutoPilot.m_vecDestinationCoors.x = x; + AutoPilot.m_vecDestinationCoors.y = y; + AutoPilot.m_vecDestinationCoors.z = z; + AutoPilot.m_nCruiseSpeed = speed; + SetStatus(STATUS_PHYSICS); + + if(m_fOrientation == 0.0f){ + m_fOrientation = CGeneral::GetATanOfXY(GetForward().x, GetForward().y) + PI; + while(m_fOrientation > TWOPI) m_fOrientation -= TWOPI; + } +} + +void +CAutomobile::TellPlaneToGoToCoors(float x, float y, float z, uint8 speed) +{ + AutoPilot.m_nCarMission = MISSION_PLANE_FLYTOCOORS; + AutoPilot.m_vecDestinationCoors.x = x; + AutoPilot.m_vecDestinationCoors.y = y; + AutoPilot.m_vecDestinationCoors.z = z; + AutoPilot.m_nCruiseSpeed = speed; + SetStatus(STATUS_PHYSICS); + + if(m_fOrientation == 0.0f) + m_fOrientation = CGeneral::GetATanOfXY(GetForward().x, GetForward().y); +} + +void +CAutomobile::PopBoot(void) +{ + switch(Damage.GetDoorStatus(DOOR_BOOT)){ + case DOOR_STATUS_OK: + case DOOR_STATUS_SMASHED: + Doors[DOOR_BOOT].m_fAngle = Doors[DOOR_BOOT].m_fMinAngle; + CMatrix mat(RwFrameGetMatrix(m_aCarNodes[CAR_BOOT])); + CVector pos = mat.GetPosition(); + float axes[3] = { 0.0f, 0.0f, 0.0f }; + axes[Doors[DOOR_BOOT].m_nAxis] = Doors[DOOR_BOOT].m_fAngle; + mat.SetRotate(axes[0], axes[1], axes[2]); + mat.Translate(pos); + mat.UpdateRW(); + } +} + +void +CAutomobile::PopBootUsingPhysics(void) +{ + switch(Damage.GetDoorStatus(DOOR_BOOT)) + case DOOR_STATUS_OK: + case DOOR_STATUS_SMASHED: + Damage.SetDoorStatus(DOOR_BOOT, DOOR_STATUS_SWINGING); + Doors[DOOR_BOOT].m_fAngVel = -2.0f; +} + +void +CAutomobile::CloseAllDoors(void) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + if(!IsDoorMissing(DOOR_FRONT_LEFT)) + OpenDoor(CAR_DOOR_LF, DOOR_FRONT_LEFT, 0.0f); + if(mi->m_numDoors > 1){ + if(!IsDoorMissing(DOOR_FRONT_RIGHT)) + OpenDoor(CAR_DOOR_RF, DOOR_FRONT_RIGHT, 0.0f); + if(mi->m_numDoors > 2){ + if(!IsDoorMissing(DOOR_REAR_LEFT)) + OpenDoor(CAR_DOOR_LR, DOOR_REAR_LEFT, 0.0f); + if(!IsDoorMissing(DOOR_REAR_RIGHT)) + OpenDoor(CAR_DOOR_RR, DOOR_REAR_RIGHT, 0.0f); + } + } +} + +CPed* +CAutomobile::KnockPedOutCar(eWeaponType weapon, uint16 door, CPed *ped) +{ + AnimationId anim = ANIM_STD_KO_FRONT; + if(ped == nil) + return nil; + + ped->m_vehDoor = door; + ped->SetPedState(PED_IDLE); + CAnimManager::BlendAnimation(ped->GetClump(), ped->m_animGroup, ANIM_STD_IDLE, 100.0f); + CPed::PedSetOutCarCB(nil, ped); + ped->SetMoveState(PEDMOVE_STILL); + if(GetUp().z < 0.0f) + ped->SetHeading(CGeneral::LimitRadianAngle(GetForward().Heading() + PI)); + else + ped->SetHeading(GetForward().Heading()); + + switch(weapon){ + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_UNIDENTIFIED: + ped->m_vecMoveSpeed = m_vecMoveSpeed; + ped->m_pCollidingEntity = this; + anim = ANIM_STD_NUM; + break; + + case WEAPONTYPE_BASEBALLBAT: + case WEAPONTYPE_RAMMEDBYCAR: + case WEAPONTYPE_FALL: + ped->m_vecMoveSpeed = m_vecMoveSpeed; + anim = ANIM_STD_SPINFORWARD_LEFT; + ApplyMoveForce(4.0f*GetUp() + 8.0f*GetRight()); + break; + } + + if(weapon != WEAPONTYPE_UNARMED){ + ped->SetFall(1000, anim, 0); + ped->bIsStanding = false; + ped->m_headingRate = 0.0f; + } + ped->m_pMyVehicle = nil; + return ped; +} + +#ifdef COMPATIBLE_SAVES +void +CAutomobile::Save(uint8*& buf) +{ + CVehicle::Save(buf); + WriteSaveBuf(buf, Damage); + ZeroSaveBuf(buf, 1500 - 672 - sizeof(CDamageManager)); +} + +void +CAutomobile::Load(uint8*& buf) +{ + CVehicle::Load(buf); + ReadSaveBuf(&Damage, buf); + SkipSaveBuf(buf, 1500 - 672 - sizeof(CDamageManager)); + SetupDamageAfterLoad(); +} +#endif diff --git a/src/miami/vehicles/Automobile.h b/src/miami/vehicles/Automobile.h new file mode 100644 index 00000000..f4046e9e --- /dev/null +++ b/src/miami/vehicles/Automobile.h @@ -0,0 +1,175 @@ +#pragma once + +#include "Vehicle.h" +#include "DamageManager.h" +#include "Door.h" +#include "Skidmarks.h" + +class CObject; + +enum { + CARWHEEL_FRONT_LEFT, + CARWHEEL_REAR_LEFT, + CARWHEEL_FRONT_RIGHT, + CARWHEEL_REAR_RIGHT +}; + + +class CAutomobile : public CVehicle +{ +public: + 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; + eSkidmarkType m_aWheelSkidmarkType[4]; + bool m_aWheelSkidmarkBloody[4]; + bool m_aWheelSkidmarkUnk[4]; + float m_aWheelRotation[4]; + float m_aWheelPosition[4]; + float m_aWheelSpeed[4]; + uint8 m_auto_unused2; +#if (defined GTA_PS2 && !defined FIX_BUGS) + uint8 m_bombType : 3; +#endif + uint8 bTaxiLight : 1; + uint8 bFixedColour : 1; + uint8 bBigWheels : 1; + uint8 bWaterTight : 1; // no damage for non-player peds + uint8 bNotDamagedUpsideDown : 1; + uint8 bMoreResistantToDamage : 1; + uint8 bTankDetonateCars : 1; + uint8 bStuckInSand : 1; + uint8 bHeliDestroyed : 1; +#if (defined GTA_PS2 && !defined FIX_BUGS) + CEntity* m_pBombRigger; +#endif + int16 m_doingBurnout; + 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_fTireTemperature; + float m_fOrientation; // for heli and plane go-to + float m_fPlaneSteer; // related to the above + 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_fHeliOrientation; + 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); + bool IsDoorReady(uint32 door); + bool IsDoorMissing(uint32 door); + bool IsOpenTopCar(void); + void RemoveRefsToVehicle(CEntity *ent); + void BlowUpCar(CEntity *ent); + bool SetUpWheelColModel(CColModel *colModel); + void BurstTyre(uint8 tyre, bool applyForces); + 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); + void DoHoverSuspensionRatios(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 TellHeliToGoToCoors(float x, float y, float z, uint8 speed); + void TellPlaneToGoToCoors(float x, float y, float z, uint8 speed); + void SetHeliOrientation(float orient) { m_fHeliOrientation = orient; } + void ClearHeliOrientation(void) { m_fHeliOrientation = -1.0f; } + + 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); + + void PopBoot(void); + void PopBootUsingPhysics(void); + void CloseAllDoors(void); + CPed *KnockPedOutCar(eWeaponType weapon, uint16 door, CPed *ped); + +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + static const uint32 nSaveStructSize; + + static void SetAllTaxiLights(bool set); +}; + +extern CVector vecHunterGunPos; +extern bool bAllCarCheat; \ No newline at end of file diff --git a/src/miami/vehicles/Bike.cpp b/src/miami/vehicles/Bike.cpp new file mode 100644 index 00000000..a65b64d2 --- /dev/null +++ b/src/miami/vehicles/Bike.cpp @@ -0,0 +1,2962 @@ +#include "common.h" +#include "General.h" +#include "Pad.h" +#include "DMAudio.h" +#include "Clock.h" +#include "Timecycle.h" +#include "ZoneCull.h" +#include "Camera.h" +#include "Darkel.h" +#include "Rubbish.h" +#include "Explosion.h" +#include "Particle.h" +#include "ParticleObject.h" +#include "Shadows.h" +#include "PointLights.h" +#include "Coronas.h" +#include "SpecialFX.h" +#include "WaterLevel.h" +#include "Floater.h" +#include "World.h" +#include "SurfaceTable.h" +#include "Weather.h" +#include "Record.h" +#include "CarCtrl.h" +#include "CarAI.h" +#include "Script.h" +#include "Stats.h" +#include "Replay.h" +#include "AnimManager.h" +#include "RpAnimBlend.h" +#include "AnimBlendAssociation.h" +#include "Ped.h" +#include "PlayerPed.h" +#include "DamageManager.h" +#include "Vehicle.h" +#include "Automobile.h" +#include "Bike.h" +#include "Debug.h" +#include "SaveBuf.h" + +const uint32 CBike::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1260; +#else + sizeof(CBoat); +#endif + + +// TODO: maybe put this somewhere else +inline void +GetRelativeMatrix(RwMatrix *mat, RwFrame *frm, RwFrame *end) +{ + *mat = *RwFrameGetMatrix(frm); + frm = RwFrameGetParent(frm); + while(frm){ + RwMatrixTransform(mat, RwFrameGetMatrix(frm), rwCOMBINEPOSTCONCAT); + frm = RwFrameGetParent(frm); + if(frm == end) + frm = nil; + } +} + +#define FAKESUSPENSION (99999.992f) + +CBike::CBike(int32 id, uint8 CreatedBy) + : CVehicle(CreatedBy) +{ + int i; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(id); + switch(id){ + case MI_ANGEL: + case MI_FREEWAY: + m_bikeAnimType = ASSOCGRP_BIKE_HARLEY; + break; + case MI_PIZZABOY: + case MI_FAGGIO: + m_bikeAnimType = ASSOCGRP_BIKE_VESPA; + break; + case MI_PCJ600: + m_bikeAnimType = ASSOCGRP_BIKE_STANDARD; + break; + case MI_SANCHEZ: + m_bikeAnimType = ASSOCGRP_BIKE_DIRT; + break; + default: assert(0 && "invalid bike model ID"); + } + m_vehType = VEHICLE_TYPE_BIKE; + + m_fFireBlowUpTimer = 0.0f; + m_doingBurnout = 0; + m_bike_flag01 = false; + + SetModelIndex(id); + + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)mi->m_handlingId); + pBikeHandling = mod_HandlingManager.GetBikePointer((tVehicleType)mi->m_handlingId); + pFlyingHandling = mod_HandlingManager.GetFlyingPointer((tVehicleType)mi->m_handlingId); + + m_bike_unused1 = 20.0f; + m_bike_unused2 = 0; + + mi->ChooseVehicleColour(m_currentColour1, m_currentColour2); + + m_fRearForkLength = 0.0f; + m_fFrontForkY = 0.0; + m_fFrontForkZ = 0.0; + m_fFrontForkSlope = Tan(DEGTORAD(mi->m_bikeSteerAngle)); + + m_fMass = pHandling->fMass; + m_fTurnMass = pHandling->fTurnMass; + m_vecCentreOfMass = pHandling->CentreOfMass; + m_vecCentreOfMass.z = 0.1f; + m_fAirResistance = pHandling->Dimension.x*pHandling->Dimension.z/m_fMass; + m_fElasticity = 0.05f; + m_fBuoyancy = pHandling->fBuoyancy; + + m_fSteerAngle = 0.0f; + m_fWheelAngle = 0.0f; + m_fLeanLRAngle = 0.0f; + m_fLeanLRAngle2 = 0.0f; + m_fGasPedal = 0.0f; + m_fBrakePedal = 0.0f; + m_fLeanInput = 0.0f; + m_fPedLeanAmountLR = 0.0f; + m_fPedLeanAmountUD = 0.0f; + m_pSetOnFireEntity = nil; + m_pBombRigger = nil; + m_fGasPedalAudio = 0.0f; + m_bike_flag02 = false; + bWaterTight = false; + bIsBeingPickedUp = false; + bIsStanding = false; + bExtraSpeed = false; + bIsOnFire = false; + bWheelieCam = false; + + m_fTireTemperature = 1.0f; + m_fBrakeDestabilization = 0.0f; + m_fVelocityChangeForAudio = 0; + + for(i = 0; i < 2; i++){ + m_aWheelRotation[i] = 0.0f; + m_aWheelSpeed[i] = 0.0f; + m_aWheelState[i] = WHEEL_STATE_NORMAL; + m_aWheelSkidmarkType[i] = SKIDMARK_NORMAL; + m_aWheelSkidmarkBloody[i] = false; + m_aWheelSkidmarkUnk[0] = false; + m_wheelStatus[i] = WHEEL_STATUS_OK; + } + + 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_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; + } + // BUG? this would make more sense in the if above + colModel->lines[0].p0.z = FAKESUSPENSION; + + SetupSuspensionLines(); + + AutoPilot.m_nCarMission = MISSION_NONE; + AutoPilot.m_nTempAction = TEMPACT_NONE; + AutoPilot.m_nTimeToStartMission = CTimer::GetTimeInMilliseconds(); + AutoPilot.m_bStayInCurrentLevel = false; + + SetStatus(STATUS_SIMPLE); + bUseCollisionRecords = true; + m_nNumPassengers = 0; + bIsVan = false; + bIsBus = false; + bIsBig = false; + bLowVehicle = false; + bPedPhysics = false; + + bLeanMatrixClean = false; + m_leanMatrix = GetMatrix(); +} + +void +CBike::SetModelIndex(uint32 id) +{ + CVehicle::SetModelIndex(id); + SetupModelNodes(); +} + +#define SAND_SLOWDOWN (0.02f) +CVector vecTestResistance(0.9995f, 0.9f, 0.95f); +float fDAxisX = 1.0f; +float fDAxisXExtra = 100.0f; +float fDAxisY = 1000.0f; +float fInAirXRes = 0.98f; +float fFlySpeedMult = -0.6f; + +#pragma optimize("", off) // a workaround for another compiler bug =P, original had optimize off for this function too though + +void +CBike::ProcessControl(void) +{ + int i; + float wheelRot; + float acceleration = 0.0f; + bool bBalancedByRider = false; + bool bStuckInSand = false; + float brake = 0.0f; + CColModel *colModel = GetColModel(); + float wheelScale = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_wheelScale; + bWarnedPeds = false; + bLeanMatrixClean = false; + m_doingBurnout = 0; + bExtraSpeed = false; + bRestingOnPhysical = false; + + if(CReplay::IsPlayingBack()) + return; + + ProcessCarAlarm(); + + ActivateBombWhenEntered(); + + CRubbish::StirUp(this); + + UpdateClumpAlpha(); + + AutoPilot.m_bSlowedDownBecauseOfCars = false; + AutoPilot.m_bSlowedDownBecauseOfPeds = false; + + switch(GetStatus()){ + case STATUS_PLAYER: + bBalancedByRider = true; + bIsBeingPickedUp = false; + if(FindPlayerPed()->GetPedState() != PED_EXIT_CAR && FindPlayerPed()->GetPedState() != PED_DRAG_FROM_CAR){ + ProcessControlInputs(0); + + if(m_fLeanInput < 0.0f){ + m_vecCentreOfMass.y = pHandling->CentreOfMass.y + pBikeHandling->fLeanBakCOM*m_fLeanInput; + CVector com = m_vecCentreOfMass; +#ifdef FIX_BUGS + // center of mass has to have world space orientation. unfortunately we can't do wheelies + // at high speed then, flipping y here is like riding south without this fix where wheelies work + com.y = -com.y; + com = Multiply3x3(GetMatrix(), com); +#endif + if(m_fBrakePedal == 0.0f && !bIsHandbrakeOn || m_nWheelsOnGround == 0){ + if(GetModelIndex() == MI_SANCHEZ){ + float force = m_fLeanInput*m_fTurnMass*pBikeHandling->fLeanBackForce*Min(m_vecMoveSpeed.Magnitude(), 0.1f); + force *= 0.7f*m_fGasPedal + 0.3f; + ApplyTurnForce(-force*CTimer::GetTimeStep()*GetUp(), com+GetForward()); + }else{ + float force = m_fLeanInput*m_fTurnMass*pBikeHandling->fLeanBackForce*Min(m_vecMoveSpeed.Magnitude(), 0.1f); + force *= 0.5f*m_fGasPedal + 0.5f; + ApplyTurnForce(-force*CTimer::GetTimeStep()*GetUp(), com+GetForward()); + } + } + }else{ + m_vecCentreOfMass.y = pHandling->CentreOfMass.y + pBikeHandling->fLeanFwdCOM*m_fLeanInput; + CVector com = m_vecCentreOfMass; +#ifdef FIX_BUGS + // see above + com.y = -com.y; + com = Multiply3x3(GetMatrix(), com); +#endif + if(m_fBrakePedal < 0.0f || m_nWheelsOnGround == 0){ + float force = m_fLeanInput*m_fTurnMass*pBikeHandling->fLeanFwdForce*Min(m_vecMoveSpeed.Magnitude(), 0.1f); + ApplyTurnForce(-force*CTimer::GetTimeStep()*GetUp(), com+GetForward()); + } + } + + PruneReferences(); + + if(GetStatus() == STATUS_PLAYER && !CRecordDataForChase::IsRecording()) + DoDriveByShootings(); + + if(m_aSuspensionSpringRatio[0] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[0].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[1] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[1].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[2] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[2].surfaceB) == ADHESIVE_SAND || + m_aSuspensionSpringRatio[3] < 1.0f && CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[3].surfaceB) == ADHESIVE_SAND){ + CVector parallelSpeed = m_vecMoveSpeed - DotProduct(m_vecMoveSpeed, GetUp())*GetUp(); + if(m_fGasPedal > 0.3f){ + if(parallelSpeed.MagnitudeSqr() < SQR(0.3f)) + bStuckInSand = true; + parallelSpeed -= DotProduct(parallelSpeed, GetForward())*GetForward(); + } + ApplyMoveForce(parallelSpeed * -CTimer::GetTimeStep()*SAND_SLOWDOWN*m_fMass); + } + } + if(CPad::GetPad(0)->WeaponJustDown()) + ActivateBomb(); + break; + + case STATUS_PLAYER_PLAYBACKFROMBUFFER: + bBalancedByRider = true; + break; + + case STATUS_SIMPLE: + CCarAI::UpdateCarAI(this); + CPhysical::ProcessControl(); + CCarCtrl::UpdateCarOnRails(this); + + m_nWheelsOnGround = 2; + m_nDriveWheelsOnGroundPrev = m_nDriveWheelsOnGround; + m_nDriveWheelsOnGround = 2; + + pHandling->Transmission.CalculateGearForSimpleCar(AutoPilot.m_fMaxTrafficSpeed/50.0f, m_nCurrentGear); + + wheelRot = ProcessWheelRotation(WHEEL_STATE_NORMAL, GetForward(), m_vecMoveSpeed, 0.5f*wheelScale); + for(i = 0; i < 2; i++) + m_aWheelRotation[i] += wheelRot; + + PlayHornIfNecessary(); + ReduceHornCounter(); + bVehicleColProcessed = false; + bAudioChangingGear = false; + bWheelieCam = false; + // that's all we do for simple vehicles + return; + + case STATUS_PHYSICS: + CCarAI::UpdateCarAI(this); + CCarCtrl::SteerAICarWithPhysics(this); + PlayHornIfNecessary(); + + bBalancedByRider = true; + bWheelieCam = false; + + if(bIsBeingCarJacked){ + m_fGasPedal = 0.0f; + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + }else + bIsBeingPickedUp = false; + break; + + case STATUS_ABANDONED: + m_fBrakePedal = 0.0f; + if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.1f) || bIsStanding) + bIsHandbrakeOn = true; + else + bIsHandbrakeOn = false; + + m_fGasPedal = 0.0f; +#ifdef FIX_BUGS + if(!IsAlarmOn()) +#endif + m_nCarHornTimer = 0; + + bBalancedByRider = (pDriver || pPassengers[0] || bIsBeingCarJacked) && !bIsStanding; + m_fPedLeanAmountLR = 0.0f; + m_fPedLeanAmountUD = 0.0f; + bWheelieCam = false; + + if(bIsBeingCarJacked){ + m_fGasPedal = 0.0f; + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + } + break; + + case STATUS_WRECKED: + m_fBrakePedal = 0.05f; + bIsHandbrakeOn = true; + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; +#ifdef FIX_BUGS + if(!IsAlarmOn()) +#endif + m_nCarHornTimer = 0; + + bBalancedByRider = false; + bWheelieCam = false; + m_fPedLeanAmountLR = 0.0f; + m_fPedLeanAmountUD = 0.0f; + break; + + case STATUS_PLAYER_DISABLED: + if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.1f)){ + m_fBrakePedal = 1.0f; + bIsHandbrakeOn = true; + }else{ + m_fBrakePedal = 0.0f; + bIsHandbrakeOn = false; + } + + m_fSteerAngle = 0.0f; + m_fGasPedal = 0.0f; +#ifdef FIX_BUGS + if(!IsAlarmOn()) +#endif + m_nCarHornTimer = 0; + + bBalancedByRider = true; + bWheelieCam = false; + break; + } + + if(bIsStanding) + if(Abs(GetRight().z) > 0.35f || Abs(GetForward().z) > 0.5f) + bIsStanding = false; + + if(bBalancedByRider || bIsBeingPickedUp || bIsStanding){ + float fDx = fDAxisX; + CVector res = vecTestResistance; + CVector localTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); + + if(GetStatus() == STATUS_PLAYER){ + if(m_aWheelTimer[BIKESUSP_F1] == 0.0f && m_aWheelTimer[BIKESUSP_F2] == 0.0f){ + fDx = fDAxisXExtra; + if(!(m_aWheelTimer[BIKESUSP_R1] == 0.0f && m_aWheelTimer[BIKESUSP_R2] == 0.0f) && + GetForward().z > 0.0f) + res.x -= Min(0.25f*Abs(pBikeHandling->fWheelieAng-GetForward().z), 0.07f); + else + res.x = fInAirXRes; + }else if(m_aWheelTimer[BIKESUSP_R1] == 0.0f && m_aWheelTimer[BIKESUSP_R2] == 0.0f){ + fDx = fDAxisXExtra; + if(GetForward().z < 0.0f) + res.x *= Min(0.3f*Abs(pBikeHandling->fStoppieAng-GetForward().z), 0.1f) + 0.9f; + } + } + + res.x *= 1.0f/(fDx*SQR(localTurnSpeed.x) + 1.0f); + res.y *= 1.0f/(fDAxisY*SQR(localTurnSpeed.y) + 1.0f); + res.x = Pow(res.x, CTimer::GetTimeStep()); + res.y = Pow(res.y, CTimer::GetTimeStep()); + float turnX = localTurnSpeed.x*(res.x - 1.0f); + float turnY = localTurnSpeed.y*(res.y - 1.0f); + + res = -GetUp() * turnY * m_fTurnMass; + ApplyTurnForce(res, GetRight() + Multiply3x3(GetMatrix(), m_vecCentreOfMass)); + + res = GetUp() * turnX * m_fTurnMass; + ApplyTurnForce(res, GetForward() + Multiply3x3(GetMatrix(), m_vecCentreOfMass)); + + if(GetStatus() != STATUS_PLAYER) + m_vecCentreOfMass = pHandling->CentreOfMass; + }else{ + m_vecCentreOfMass = pHandling->CentreOfMass; + m_vecCentreOfMass.z = pBikeHandling->fNoPlayerCOMz; + } + + // Skip physics if object is found to have been static recently + bool skipPhysics = false; + if(!bIsStuck && (GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED) && !bIsBeingPickedUp){ + 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]){ + bRestingOnPhysical = true; + if(!CWorld::bForceProcessControl && m_aGroundPhysical[i]->bIsInSafePosition){ + bWasPostponed = true; + return; + } + } + + if(bRestingOnPhysical){ + skipPhysics = false; + m_nStaticFrames = 0; + } + + VehicleDamage(); + + if(skipPhysics){ + bHasContacted = false; + bIsInSafePosition = false; + bWasPostponed = false; + bHasHitWall = false; + m_nCollisionRecords = 0; + bHasCollided = false; + bVehicleColProcessed = false; + bAudioChangingGear = 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); +// missing. BUG? +// m_fTireTemperature = 1.0f; + + if(bIsStanding && m_fWheelAngle < DEGTORAD(20.0f)) + m_fWheelAngle += DEGTORAD(1.0f)*CTimer::GetTimeStep(); + if(bIsStanding){ + float f = Pow(0.97f, CTimer::GetTimeStep()); + m_fLeanLRAngle2 = m_fLeanLRAngle2*f - (Asin(Clamp(GetRight().z,-1.0f,1.0f))+DEGTORAD(15.0f))*(1.0f-f); + m_fLeanLRAngle = m_fLeanLRAngle2; + } + }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; + } + + if(!(bBalancedByRider || bIsBeingPickedUp || bIsStanding)){ + if(GetRight().z < 0.0f){ + if(m_fSteerAngle > -DEGTORAD(25.0f)) + m_fSteerAngle -= DEGTORAD(0.5f)*CTimer::GetTimeStep(); + }else{ + if(m_fSteerAngle < DEGTORAD(25.0f)) + m_fSteerAngle += DEGTORAD(0.5f)*CTimer::GetTimeStep(); + } + } + + // Lean forward speed up + float savedAirResistance = m_fAirResistance; + if(GetStatus() == STATUS_PLAYER && pDriver){ + CAnimBlendAssociation *assoc = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_LEANF); + if(assoc && assoc->blendAmount > 0.5f && + assoc->currentTime > 0.06f && assoc->currentTime < 0.14f){ + m_fAirResistance *= 0.6f; + if(m_fGasPedal > 0.5f && DotProduct(m_vecMoveSpeed, GetForward()) > 0.25f){ + ApplyMoveForce(0.2f*m_fMass*GRAVITY*CTimer::GetTimeStep()*GetForward()); + bExtraSpeed = true; + } + } + } + + CPhysical::ProcessControl(); + m_fAirResistance = savedAirResistance; + + 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); + } + + int rnd = 0; + 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 model space + + for(i = 0; i < 4; i++){ + // Set spring under certain circumstances + if(m_wheelStatus[i/2] == WHEEL_STATUS_MISSING) + m_aSuspensionSpringRatio[i] = 1.0f; + else if(m_wheelStatus[i/2] == WHEEL_STATUS_BURST){ + // wheel more bumpy the faster we are + if(i == BIKESUSP_F1 || i == BIKESUSP_R1) + rnd = CGeneral::GetRandomNumberInRange(0, (uint16)(40*fwdSpeed) + 98) < 100; + if(rnd){ + 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(); + } + } + + m_aWheelSkidmarkType[0] = m_aWheelSkidmarkType[1] = SKIDMARK_NORMAL; + m_aWheelSkidmarkUnk[0] = m_aWheelSkidmarkUnk[1] = false; + + // Make springs push up vehicle + for(i = 0; i < 4; i++){ + if(m_aSuspensionSpringRatio[i] < 1.0f){ + float bias = pHandling->fSuspensionBias; + if(i == BIKESUSP_R1 || i == BIKESUSP_R2) + bias = 1.0f - bias; + + if(m_aWheelColPoints[i].normal.z > 0.35f) + ApplySpringCollisionAlt(pHandling->fSuspensionForceLevel, + springDirections[i], contactPoints[i], + m_aSuspensionSpringRatio[i], bias, m_aWheelColPoints[i].normal); + else + ApplySpringCollision(pHandling->fSuspensionForceLevel, + springDirections[i], contactPoints[i], + m_aSuspensionSpringRatio[i], bias); + + if(m_aWheelColPoints[i].surfaceB == SURFACE_GRASS || + m_aWheelColPoints[i].surfaceB == SURFACE_MUD_DRY){ + if(i < 2) + m_aWheelSkidmarkType[0] = SKIDMARK_MUDDY; + else + m_aWheelSkidmarkType[1] = SKIDMARK_MUDDY; + }else if(m_aWheelColPoints[i].surfaceB == SURFACE_SAND || + m_aWheelColPoints[i].surfaceB == SURFACE_SAND_BEACH){ + if(i < 2){ + m_aWheelSkidmarkType[0] = SKIDMARK_SANDY; + m_aWheelSkidmarkUnk[0] = true; + }else{ + m_aWheelSkidmarkType[1] = SKIDMARK_SANDY; + m_aWheelSkidmarkUnk[1] = true; + } + } + }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 + } + } + + CVector normal; + if(m_aSuspensionSpringRatio[0] < 1.0f || m_aSuspensionSpringRatio[1] < 1.0f){ + normal = m_aSuspensionSpringRatio[0] < 1.0f ? m_aWheelColPoints[0].normal : m_aWheelColPoints[1].normal; + if(normal.z > 0.35f) + springDirections[0] = -normal; + normal = m_aSuspensionSpringRatio[1] < 1.0f ? m_aWheelColPoints[1].normal : m_aWheelColPoints[0].normal; + if(normal.z > 0.35f) + springDirections[1] = -normal; + } + if(m_aSuspensionSpringRatio[2] < 1.0f || m_aSuspensionSpringRatio[3] < 1.0f){ + normal = m_aSuspensionSpringRatio[2] < 1.0f ? m_aWheelColPoints[2].normal : m_aWheelColPoints[3].normal; + if(normal.z > 0.35f) + springDirections[2] = -normal; + normal = m_aSuspensionSpringRatio[3] < 1.0f ? m_aWheelColPoints[3].normal : m_aWheelColPoints[2].normal; + if(normal.z > 0.35f) + springDirections[3] = -normal; + } + + // game has dead code here if m_vecMoveSpeed.Magnitude() < 0.01f + + // 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(!CVehicle::bCheat3) + gripCheat = false; + acceleration = pHandling->Transmission.CalculateDriveAcceleration(m_fGasPedal, m_nCurrentGear, m_fChangeGearTime, fwdSpeed, gripCheat); + acceleration /= m_fForceMultiplier; + + brake = m_fBrakePedal * pHandling->fBrakeDeceleration * CTimer::GetTimeStep(); + bool neutralHandling = GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE && (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++; + if(i == BIKESUSP_R1 || i == BIKESUSP_R2) + m_nDriveWheelsOnGround = 1; + if(m_nWheelsOnGround == 1) + m_vecAvgSurfaceNormal = m_aWheelColPoints[i].normal; + else + m_vecAvgSurfaceNormal += m_aWheelColPoints[i].normal; + } + } + + if(m_nWheelsOnGround == 0) + m_vecAvgSurfaceNormal = CVector(0.0f, 0.0f, 1.0f); + else{ + m_vecAvgSurfaceNormal /= m_nWheelsOnGround; + if(DotProduct(m_vecAvgSurfaceNormal, GetUp()) < -0.5f) + m_vecAvgSurfaceNormal *= -1.0f; + } + + // Find contact points for wheel processing + int frontLine = m_aSuspensionSpringRatio[BIKESUSP_F1] < m_aSuspensionSpringRatio[BIKESUSP_F2] ? + BIKESUSP_F1 : BIKESUSP_F2; + CVector frontContact(0.0f, + colModel->lines[BIKESUSP_F1].p0.y, + colModel->lines[BIKESUSP_F1].p0.z - m_aSuspensionSpringRatio[frontLine]*m_aSuspensionSpringLength[BIKESUSP_F1] - 0.5f*wheelScale); + frontContact = Multiply3x3(GetMatrix(), frontContact); + + int rearLine = m_aSuspensionSpringRatio[BIKESUSP_R1] < m_aSuspensionSpringRatio[BIKESUSP_R2] ? + BIKESUSP_R1 : BIKESUSP_R2; + CVector rearContact(0.0f, + colModel->lines[BIKESUSP_R1].p0.y, + colModel->lines[BIKESUSP_R1].p0.z - m_aSuspensionSpringRatio[rearLine]*m_aSuspensionSpringLength[BIKESUSP_R1] - 0.5f*wheelScale); + rearContact = Multiply3x3(GetMatrix(), rearContact); + + float traction = 0.004f * m_fTraction; + traction *= pHandling->fTractionMultiplier / 4.0f; + + // Turn wheel + if(GetStatus() == STATUS_PLAYER || !bIsStanding || bIsBeingPickedUp){ + if(Abs(m_vecMoveSpeed.x) < 0.01f && Abs(m_vecMoveSpeed.y) < 0.01f && m_fSteerAngle == 0.0f){ + m_fWheelAngle *= Pow(0.96f, CTimer::GetTimeStep()); + }else{ + float f; + if(fwdSpeed > 0.01f && m_aWheelTimer[BIKESUSP_F1] > 0.0f && m_aWheelTimer[BIKESUSP_F2] > 0.0f && GetStatus() == STATUS_PLAYER){ + CColPoint point; + point.surfaceA = SURFACE_WHEELBASE; + point.surfaceB = SURFACE_TARMAC; + float steer = CSurfaceTable::GetAdhesiveLimit(point)*4.0f*pBikeHandling->fSpeedSteer*traction; + if(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[rearLine].surfaceB) == ADHESIVE_LOOSE || + CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[rearLine].surfaceB) == ADHESIVE_SAND) + steer *= pBikeHandling->fSlipSteer; + f = Asin(Min(steer/SQR(fwdSpeed), 1.0))/DEGTORAD(pHandling->fSteeringLock); + if(m_fSteerAngle < 0.0f && m_fLeanLRAngle < 0.0f || + m_fSteerAngle > 0.0f && m_fLeanLRAngle > 0.0f) + f *= 2.0f; + f = Min(f, 1.0f); + }else{ + f = 1.0f; + } + if(GetStatus() != STATUS_PLAYER) + f = 1.0f; + m_fWheelAngle = m_fSteerAngle*f; + } + }else if(m_fWheelAngle < DEGTORAD(20.0f)) + m_fWheelAngle += DEGTORAD(1.5f)*CTimer::GetTimeStep(); + + static float fThrust; + static tWheelState WheelState[2]; + CVector initialMoveSpeed = m_vecMoveSpeed; + bool rearWheelsFirst = !!(pHandling->Flags & HANDLING_REARWHEEL_1ST); + + // Process front wheel - first try + + if(!rearWheelsFirst){ + if(m_aWheelTimer[BIKESUSP_F1] > 0.0f || m_aWheelTimer[BIKESUSP_F2] > 0.0f){ + // Wheel on ground + eBikeWheelSpecial spec; + if(m_aWheelTimer[BIKESUSP_R1] > 0.0f || m_aWheelTimer[BIKESUSP_R2] > 0.0f) + spec = BIKE_WHEELSPEC_0; + else + spec = BIKE_WHEELSPEC_2; + CVector wheelFwd = Multiply3x3(GetMatrix(), CVector(-Sin(m_fWheelAngle), Cos(m_fWheelAngle), 0.0f)); + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[frontLine].normal)*m_aWheelColPoints[frontLine].normal; + wheelFwd.Normalise(); + CVector wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[frontLine].normal); + wheelRight.Normalise(); + + fThrust = 0.0f; + m_aWheelColPoints[frontLine].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[frontLine])*traction; + float adhesionDestab = 1.0f; + if(m_fBrakeDestabilization > 0.0f) + switch(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[frontLine].surfaceB)){ + case ADHESIVE_HARD: + case ADHESIVE_LOOSE: + adhesionDestab = 0.9f; + break; + case ADHESIVE_ROAD: + adhesionDestab = 0.7f; + break; + } + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[frontLine].surfaceB); + if(m_wheelStatus[BIKEWHEEL_FRONT] == WHEEL_STATUS_BURST) + adhesion *= 0.4f; + WheelState[BIKEWHEEL_FRONT] = m_aWheelState[BIKEWHEEL_FRONT]; + CVector contactSpeed = GetSpeed(frontContact); + ProcessBikeWheel(wheelFwd, wheelRight, + contactSpeed, frontContact, + 2, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront, adhesionDestab, + BIKEWHEEL_FRONT, + &m_aWheelSpeed[BIKEWHEEL_FRONT], + &WheelState[BIKEWHEEL_FRONT], + spec, + m_wheelStatus[BIKEWHEEL_FRONT]); + if(bStuckInSand && (WheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SPINNING || WheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SKIDDING)) + WheelState[BIKEWHEEL_FRONT] = WHEEL_STATE_NORMAL; + }else{ + // Wheel in the air + m_aWheelSpeed[BIKEWHEEL_FRONT] *= 0.95f; + m_aWheelRotation[BIKEWHEEL_FRONT] += m_aWheelSpeed[BIKEWHEEL_FRONT]; + } + } + + // Process rear wheel + + if(m_aWheelTimer[BIKESUSP_R1] > 0.0f || m_aWheelTimer[BIKESUSP_R2] > 0.0f){ + // Wheel on ground + float rearBrake = brake; + float rearTraction = traction; + + CVector wheelFwd = GetForward(); + CVector wheelRight = GetRight(); + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[rearLine].normal)*m_aWheelColPoints[rearLine].normal; + wheelFwd.Normalise(); + wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[rearLine].normal); + wheelRight.Normalise(); + + if(bIsHandbrakeOn){ +#ifdef FIX_BUGS + // Not sure if this is needed, but brake usually has timestep as a factor + rearBrake = 20000.0f * CTimer::GetTimeStepFix(); +#else + rearBrake = 20000.0f; +#endif + m_fTireTemperature = 1.0f; + }else if(m_doingBurnout){ + rearBrake = 0.0f; + rearTraction = 0.0f; + ApplyTurnForce(contactPoints[BIKESUSP_R1], -0.0007f*m_fTurnMass*m_fSteerAngle*GetRight()*CTimer::GetTimeStep()); + }else if(m_fTireTemperature < 1.0f && m_fGasPedal > 0.75f){ + rearTraction *= m_fTireTemperature; + ApplyTurnForce(contactPoints[BIKESUSP_R1], (1.0f-m_fTireTemperature)*-0.0007f*m_fTurnMass*m_fSteerAngle*GetRight()*CTimer::GetTimeStep()); + } + + if(fThrust > 0.0f && brake > 0.0f) + brake = 0.0f; // only affects next front wheel. is this intended? + fThrust = acceleration; + m_aWheelColPoints[rearLine].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[rearLine])*rearTraction; + float adhesionDestab = 1.0f; + if(m_fBrakeDestabilization > 0.0f) + switch(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[rearLine].surfaceB)){ + case ADHESIVE_HARD: + case ADHESIVE_LOOSE: + adhesionDestab = 0.9f; + break; + case ADHESIVE_ROAD: + adhesionDestab = 0.7f; + break; + } + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[rearLine].surfaceB); + if(m_wheelStatus[BIKEWHEEL_REAR] == WHEEL_STATUS_BURST) + adhesion *= 0.4f; + WheelState[BIKEWHEEL_REAR] = m_aWheelState[BIKEWHEEL_REAR]; + CVector contactSpeed = GetSpeed(rearContact); + ProcessBikeWheel(wheelFwd, wheelRight, + contactSpeed, rearContact, + 2, fThrust, + rearBrake*brakeBiasRear, + adhesion*tractionBiasRear, adhesionDestab, + BIKEWHEEL_REAR, + &m_aWheelSpeed[BIKEWHEEL_REAR], + &WheelState[BIKEWHEEL_REAR], + BIKE_WHEELSPEC_1, + m_wheelStatus[BIKEWHEEL_REAR]); + if(bStuckInSand && (WheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING || WheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SKIDDING)) + WheelState[BIKEWHEEL_REAR] = WHEEL_STATE_NORMAL; + }else{ + // Wheel in the air + if(bIsHandbrakeOn) + m_aWheelSpeed[BIKEWHEEL_REAR] = 0.0f; + else{ + if(acceleration > 0.0f){ + if(m_aWheelSpeed[BIKEWHEEL_REAR] < 2.0f) + m_aWheelSpeed[BIKEWHEEL_REAR] -= 0.2f; + }else{ + if(m_aWheelSpeed[BIKEWHEEL_REAR] > -2.0f) + m_aWheelSpeed[BIKEWHEEL_REAR] += 0.1f; + } + } + m_aWheelRotation[BIKEWHEEL_REAR] += m_aWheelSpeed[BIKEWHEEL_REAR]; + } + + if(m_doingBurnout && m_aWheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING){ + m_fTireTemperature += 0.001f*CTimer::GetTimeStep(); + if(m_fTireTemperature > 3.0f) + m_fTireTemperature = 3.0f; + }else if(m_fTireTemperature > 1.0f){ + m_fTireTemperature = (m_fTireTemperature - 1.0f)*Pow(0.995f, CTimer::GetTimeStep()) + 1.0f; + } + + // Process front wheel - second try + + if(rearWheelsFirst){ + if(m_aWheelTimer[BIKESUSP_F1] > 0.0f || m_aWheelTimer[BIKESUSP_F2] > 0.0f){ + // Wheel on ground + eBikeWheelSpecial spec; + if(m_aWheelTimer[BIKESUSP_R1] > 0.0f || m_aWheelTimer[BIKESUSP_R2] > 0.0f) + spec = BIKE_WHEELSPEC_0; + else + spec = BIKE_WHEELSPEC_2; + CVector wheelFwd = GetMatrix() * CVector(-Sin(m_fWheelAngle), Cos(m_fWheelAngle), 0.0f); + wheelFwd -= DotProduct(wheelFwd, m_aWheelColPoints[frontLine].normal)*m_aWheelColPoints[frontLine].normal; + wheelFwd.Normalise(); + CVector wheelRight = CrossProduct(wheelFwd, m_aWheelColPoints[frontLine].normal); + wheelRight.Normalise(); + + fThrust = 0.0f; + m_aWheelColPoints[frontLine].surfaceA = SURFACE_WHEELBASE; + float adhesion = CSurfaceTable::GetAdhesiveLimit(m_aWheelColPoints[frontLine])*traction; + float adhesionDestab = 1.0f; + if(m_fBrakeDestabilization > 0.0f) + switch(CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[frontLine].surfaceB)){ + case ADHESIVE_HARD: + case ADHESIVE_LOOSE: + adhesionDestab = 0.9f; + break; + case ADHESIVE_ROAD: + adhesionDestab = 0.7f; + break; + } + if(GetStatus() == STATUS_PLAYER) + adhesion *= CSurfaceTable::GetWetMultiplier(m_aWheelColPoints[frontLine].surfaceB); + if(m_wheelStatus[BIKEWHEEL_FRONT] == WHEEL_STATUS_BURST) + adhesion *= 0.4f; + WheelState[BIKEWHEEL_FRONT] = m_aWheelState[BIKEWHEEL_FRONT]; + CVector contactSpeed = GetSpeed(frontContact); + ProcessBikeWheel(wheelFwd, wheelRight, + contactSpeed, frontContact, + 2, fThrust, + brake*brakeBiasFront, + adhesion*tractionBiasFront, adhesionDestab, + BIKEWHEEL_FRONT, + &m_aWheelSpeed[BIKEWHEEL_FRONT], + &WheelState[BIKEWHEEL_FRONT], + spec, + m_wheelStatus[BIKEWHEEL_FRONT]); + if(bStuckInSand && (WheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SPINNING || WheelState[BIKEWHEEL_FRONT] == WHEEL_STATE_SKIDDING)) + WheelState[BIKEWHEEL_FRONT] = WHEEL_STATE_NORMAL; + }else{ + // Wheel in the air + m_aWheelSpeed[BIKEWHEEL_FRONT] *= 0.95f; + m_aWheelRotation[BIKEWHEEL_FRONT] += m_aWheelSpeed[BIKEWHEEL_FRONT]; + } + } + + // Process leaning + float idleAngle = 0.0f; + if(pDriver){ + CAnimBlendAssociation *assoc = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_READY); + if(assoc) + idleAngle = DEGTORAD(10.0f) * assoc->blendAmount; + } + if(bBalancedByRider || bIsBeingPickedUp){ + m_vecAvgSurfaceRight = CrossProduct(GetForward(), m_vecAvgSurfaceNormal); + m_vecAvgSurfaceRight.Normalise(); + float lean; + if(m_nWheelsOnGround == 0) + lean = -(m_fSteerAngle/DEGTORAD(pHandling->fSteeringLock))*0.5f*GRAVITY*CTimer::GetTimeStep(); + else + lean = DotProduct(m_vecMoveSpeed-initialMoveSpeed, m_vecAvgSurfaceRight); + lean /= GRAVITY*Max(CTimer::GetTimeStep(), 0.01f); + if(m_wheelStatus[BIKEWHEEL_FRONT] == WHEEL_STATUS_BURST) + lean = Clamp(lean, -0.4f*pBikeHandling->fMaxLean, 0.4f*pBikeHandling->fMaxLean); + else + lean = Clamp(lean, -pBikeHandling->fMaxLean, pBikeHandling->fMaxLean); + float f = Pow(pBikeHandling->fDesLean, CTimer::GetTimeStep()); + m_fLeanLRAngle2 = (Asin(lean) - idleAngle)*(1.0f-f) + m_fLeanLRAngle2*f; + }else{ + if(bIsStanding){ + float f = Pow(0.97f, CTimer::GetTimeStep()); + m_fLeanLRAngle2 = m_fLeanLRAngle2*f - (Asin(GetRight().z) + DEGTORAD(15.0f) + idleAngle)*(1.0f-f); + }else{ + float f = Pow(0.95f, CTimer::GetTimeStep()); + m_fLeanLRAngle2 = m_fLeanLRAngle2*f; + } + } + m_fLeanLRAngle = m_fLeanLRAngle2; + + // Destabilize steering when braking + if((m_aSuspensionSpringRatio[BIKESUSP_F1] < 1.0f || m_aSuspensionSpringRatio[BIKESUSP_F2] < 1.0f) && + m_fBrakePedal - m_fGasPedal > 0.9f && + fwdSpeed > 0.02f && + !bIsHandbrakeOn){ + m_fBrakeDestabilization += CGeneral::GetRandomNumberInRange(0.5f, 1.0f)*0.2f*CTimer::GetTimeStep(); + if(m_aSuspensionSpringRatio[BIKESUSP_R1] < 1.0f || m_aSuspensionSpringRatio[BIKESUSP_R2] < 1.0f){ + // BUG: this clamp makes no sense and the arguments seem swapped too + ApplyTurnForce(contactPoints[BIKESUSP_R1], + m_fTurnMass*Sin(m_fBrakeDestabilization)*Clamp(fwdSpeed, 0.5f, 0.2f)*0.013f*GetRight()*CTimer::GetTimeStep()); + }else{ + // BUG: this clamp makes no sense and the arguments seem swapped too + ApplyTurnForce(contactPoints[BIKESUSP_R1], + m_fTurnMass*Sin(m_fBrakeDestabilization)*Clamp(fwdSpeed, 0.5f, 0.2f)*0.003f*GetRight()*CTimer::GetTimeStep()); + } + }else + m_fBrakeDestabilization = 0.0f; + + // Update wheel positions from suspension + float frontWheelPos = colModel->lines[frontLine].p0.z; + if(m_aSuspensionSpringRatio[frontLine] > 0.0f) + frontWheelPos -= m_aSuspensionSpringRatio[frontLine]*m_aSuspensionSpringLength[frontLine]; + m_aWheelPosition[BIKEWHEEL_FRONT] += (frontWheelPos - m_aWheelPosition[BIKEWHEEL_FRONT])*0.75f; + + float rearWheelPos = colModel->lines[rearLine].p0.z; + if(m_aSuspensionSpringRatio[rearLine] > 0.0f) + rearWheelPos -= m_aSuspensionSpringRatio[rearLine]*m_aSuspensionSpringLength[rearLine]; + m_aWheelPosition[BIKEWHEEL_REAR] += (rearWheelPos - m_aWheelPosition[BIKEWHEEL_REAR])*0.75f; + + for(i = 0; i < 2; i++) + m_aWheelState[i] = WheelState[i]; + // never spin when moving backwards + if(m_fGasPedal < 0.0f && m_aWheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING) + m_aWheelState[BIKEWHEEL_REAR] = WHEEL_STATE_NORMAL; + + // Process horn + + if(GetStatus() != STATUS_PLAYER){ +#ifdef FIX_BUGS + if(!IsAlarmOn()) +#endif + ReduceHornCounter(); + }else{ +#ifdef FIX_BUGS + if(!IsAlarmOn()) +#endif + { + if(Pads[0].GetHorn()) + m_nCarHornTimer = 1; + else + m_nCarHornTimer = 0; + } + } + } + + if(m_fHealth < 250.0f && GetStatus() != STATUS_WRECKED){ + // Car is on fire + + CVector damagePos, fireDir; + + // move fire forward if in first person + if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson()){ + damagePos = CVector(0.0f, 1.2f, -0.4f); + fireDir = CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.01125f, 0.09f)); + }else{ + damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->m_positions[CAR_POS_BACKSEAT]; + damagePos.z -= 0.3f; + fireDir = CGeneral::GetRandomNumberInRange(0.02025f, 0.09f) * GetRight(); + fireDir -= CGeneral::GetRandomNumberInRange(0.02025f, 0.18f) * GetForward(); + fireDir.z = CGeneral::GetRandomNumberInRange(0.00225f, 0.018f); + } + + damagePos = GetMatrix()*damagePos; + CParticle::AddParticle(PARTICLE_CARFLAME, damagePos, fireDir, + nil, 0.9f); + + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, CVector(0.0f, 0.0f, 0.0f), nil, 0.5f); + + damagePos.x += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f), + damagePos.y += CGeneral::GetRandomNumberInRange(-0.5625f, 0.5625f), + damagePos.z += CGeneral::GetRandomNumberInRange(0.5625f, 2.25f); + CParticle::AddParticle(PARTICLE_CARFLAME_SMOKE, damagePos, CVector(0.0f, 0.0f, 0.0f)); + + // Blow up car after 5 seconds + m_fFireBlowUpTimer += CTimer::GetTimeStepInMilliseconds(); + if(m_fFireBlowUpTimer > 5000.0f) + BlowUpCar(m_pSetOnFireEntity); + }else + m_fFireBlowUpTimer = 0.0f; + + ProcessDelayedExplosion(); + + // Find out how much to shake the pad depending on suspension and ground surface + + float suspShake = 0.0f; + float surfShake = 0.0f; + float speedsq = m_vecMoveSpeed.MagnitudeSqr(); + for(i = 0; i < 4; i++){ + float suspChange = m_aSuspensionSpringRatioPrev[i] - m_aSuspensionSpringRatio[i]; + if(suspChange > 0.3f && (i == BIKESUSP_F1 || i == BIKESUSP_R1) && speedsq > 0.04f){ + if(GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PHYSICS){ +#ifdef FIX_BUGS + // only two wheels but 4 suspensions + if(m_wheelStatus[i/2] == WHEEL_STATUS_BURST) +#else + if(m_wheelStatus[i] == WHEEL_STATUS_BURST) +#endif + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP_2, suspChange); + else + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_JUMP, suspChange); + if(suspChange > suspShake) + suspShake = suspChange; + } + } + + if(this == FindPlayerVehicle()){ + 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 || surf == SURFACE_SAND_BEACH){ + if(surfShake < 0.1f) + surfShake = 0.2f; + }else if(surf == SURFACE_GRASS){ + if(surfShake < 0.05f) + surfShake = 0.1f; + } + +// BUG: this only observes one of the wheels + TheCamera.m_bVehicleSuspenHigh = Abs(suspChange) > 0.05f; + } + + 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; + bAudioChangingGear = false; + + if(!bWarnedPeds) + CCarCtrl::ScanForPedDanger(this); + + 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 && + (acceleration == 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; + } + } + + // Balance bike + if(bBalancedByRider || bIsBeingPickedUp || bIsStanding){ + float onSideness = DotProduct(GetRight(), m_vecAvgSurfaceNormal); + onSideness = Clamp(onSideness, -1.0f, 1.0f); + CVector worldCOM = Multiply3x3(GetMatrix(), m_vecCentreOfMass); + // Keep bike upright + if(bBalancedByRider){ + ApplyTurnForce(-0.07f*onSideness*m_fTurnMass*GetUp()*CTimer::GetTimeStep(), worldCOM+GetRight()); + bIsStanding = false; + }else + ApplyTurnForce(-0.1f*onSideness*m_fTurnMass*GetUp()*CTimer::GetTimeStep(), worldCOM+GetRight()); + + // Wheelie/Stoppie stabilization + if(GetStatus() == STATUS_PLAYER){ + if(m_aWheelTimer[BIKESUSP_F1] == 0.0f && m_aWheelTimer[BIKESUSP_F2] == 0.0f && GetForward().z > 0.0 && + !(m_aWheelTimer[BIKESUSP_R1] == 0.0f && m_aWheelTimer[BIKESUSP_R2] == 0.0f)){ + // Wheelie + float wheelie = pBikeHandling->fWheelieAng - GetForward().z; + if(wheelie > 0.15f) + // below wheelie angle + wheelie = Max(0.3f - wheelie, 0.0f); + else if(wheelie < -0.08f) + // above wheelie angle + wheelie = Min(-0.15f - wheelie, 0.0f); + float wheelieStab = pBikeHandling->fWheelieStabMult * Min(m_vecMoveSpeed.Magnitude(), 0.1f) * wheelie; + ApplyTurnForce(0.5f*CTimer::GetTimeStep()*wheelieStab*m_fTurnMass*GetUp(), worldCOM+GetForward()); + ApplyTurnForce(0.5f*CTimer::GetTimeStep()*m_fWheelAngle*pBikeHandling->fWheelieSteer*m_fTurnMass*GetRight(), worldCOM+GetForward()); + }else if(m_aWheelTimer[BIKESUSP_R1] == 0.0f && m_aWheelTimer[BIKESUSP_R2] == 0.0f && GetForward().z < 0.0 && + !(m_aWheelTimer[BIKESUSP_F1] == 0.0f && m_aWheelTimer[BIKESUSP_F2] == 0.0f)){ + // Stoppie + float stoppie = pBikeHandling->fStoppieAng - GetForward().z; + if(stoppie > 0.15f) + // below stoppie angle + stoppie = Max(0.3f - stoppie, 0.0f); + else if(stoppie < -0.15f) + // above stoppie angle + stoppie = Min(-0.3f - stoppie, 0.0f); + float speed = m_vecMoveSpeed.Magnitude(); + float stoppieStab = pBikeHandling->fStoppieStabMult * Min(speed, 0.1f) * stoppie; + ApplyTurnForce(0.5f*CTimer::GetTimeStep()*stoppieStab*m_fTurnMass*GetUp(), worldCOM+GetForward()); + ApplyTurnForce(0.5f*Min(5.0f*speed,1.0f)*CTimer::GetTimeStep()*m_fWheelAngle*pBikeHandling->fWheelieSteer*m_fTurnMass*GetRight(), worldCOM+GetForward()); + } + } + } +} + +#pragma optimize("", on) + +void +CBike::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 +CBike::PreRender(void) +{ + int i; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + // Wheel particles + + if(m_aWheelState[BIKEWHEEL_REAR] != WHEEL_STATE_NORMAL && + m_aWheelColPoints[BIKESUSP_R2].surfaceB != SURFACE_WATER && m_aWheelTimer[BIKESUSP_R2] > 0.0f){ + static float smokeSize = 0.2f; + CVector groundPos = m_aWheelColPoints[BIKESUSP_R2].point; + if(m_aSuspensionSpringRatioPrev[BIKESUSP_R1] < 1.0f) + groundPos = (groundPos + m_aWheelColPoints[BIKESUSP_R1].point)/2.0f; + groundPos += Sin(m_fLeanLRAngle) * 0.8f*GetColModel()->boundingBox.min.z * GetRight(); + CParticle::AddParticle(PARTICLE_RUBBER_SMOKE, + groundPos + CVector(0.0f, 0.0f, 0.25f), CVector(0.0f, 0.0f, 0.0f), + nil, smokeSize); + + CSkidmarks::RegisterOne((uintptr)this, groundPos, GetForward().x, GetForward().y, + m_aWheelSkidmarkType[BIKEWHEEL_REAR], &m_aWheelSkidmarkBloody[BIKEWHEEL_REAR]); + + if(m_aWheelState[BIKEWHEEL_REAR] == WHEEL_STATE_SPINNING && + (CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[BIKESUSP_R2].surfaceB) == ADHESIVE_HARD || + CSurfaceTable::GetAdhesionGroup(m_aWheelColPoints[BIKESUSP_R2].surfaceB) == ADHESIVE_ROAD)){ + CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE, + groundPos + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.0f)); + CParticle::AddParticle(PARTICLE_BURNINGRUBBER_SMOKE, + groundPos + CVector(0.0f, 0.0f, 0.25f), + CVector(0.0f, 0.0f, 0.05f)); + } + }else if(m_aWheelSkidmarkBloody[BIKEWHEEL_REAR] || m_aWheelSkidmarkUnk[BIKEWHEEL_REAR]){ + CVector groundPos = m_aWheelColPoints[BIKESUSP_R2].point; + groundPos += Sin(m_fLeanLRAngle) * 0.8f*GetColModel()->boundingBox.min.z * GetRight(); + + CSkidmarks::RegisterOne((uintptr)this, groundPos, GetForward().x, GetForward().y, + m_aWheelSkidmarkType[BIKEWHEEL_REAR], &m_aWheelSkidmarkBloody[BIKEWHEEL_REAR]); + } + + // 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){ + CalculateLeanMatrix(); + 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 + float behindness = DotProduct(lookVector, GetForward()); + // 0.0 if behind car, PI if in front + float angle = Abs(Acos(Abs(behindness))); + + // Headlight + + CMatrix mat; + CVector headLightPos = mi->m_positions[CAR_POS_HEADLIGHTS]; + if(GetModelIndex() == 152){ // this is the bobcat in VC, but we don't want that effect anyway + mat.SetUnity(); + mat.RotateZ(m_fWheelAngle); + mat = m_leanMatrix * mat; + }else + mat = m_leanMatrix; + CVector light = mat * headLightPos; + if(behindness < 0.0f){ + // In front of bike + 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){ + CCoronas::RegisterCorona((uintptr)this + 6, 150, 150, 195, 255, + light, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + }else{ + CCoronas::RegisterCorona((uintptr)this + 6, 160, 160, 140, 255, + light, 1.2f, 45.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_HEADLIGHT, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_OFF, angle); + } + } + + if(alarmOff){ + CCoronas::RegisterCorona((uintptr)this, 0, 0, 0, 0, + light, 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){ + CCoronas::RegisterCorona((uintptr)this + 1, 190*intensity, 190*intensity, 255*intensity, 255, + light, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + CCoronas::RegisterCorona((uintptr)this + 1, 210*intensity, 210*intensity, 195*intensity, 255, + light, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + } + } + }else{ + CCoronas::UpdateCoronaCoors((uintptr)this, light, 50.0f*TheCamera.LODDistMultiplier, angle); + } + + // bright light + CBrightLights::RegisterOne(light, GetUp(), GetRight(), GetForward(), pHandling->FrontLights + BRIGHTLIGHT_FRONT); + + // Taillight + + CVector tailLightPos = mi->m_positions[CAR_POS_TAILLIGHTS]; + light = m_leanMatrix * tailLightPos; + + // Taillight corona + if(behindness > 0.0f){ + // Behind car + float intensity = 0.4f*behindness + 0.4f; + float size = (behindness + 1.0f)/2.0f; + + if(m_fGasPedal < 0.0f){ + // reversing + // no lights in this case + }else{ + if(m_fBrakePedal > 0.0f){ + intensity += 0.4f; + size += 0.3f; + } + + if(alarmOff){ + CCoronas::RegisterCorona((uintptr)this + 14, 0, 0, 0, 0, + light, size, 0.0f, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + }else{ + CCoronas::RegisterCorona((uintptr)this + 14, 128*intensity, 0, 0, 255, + light, size, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STREAK, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, angle); + } + } + }else{ + CCoronas::UpdateCoronaCoors((uintptr)this + 14, light, 50.0f*TheCamera.LODDistMultiplier, angle); + } + + // bright light + CBrightLights::RegisterOne(light, 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); + CShadows::StoreCarLightShadow(this, (uintptr)this + 22, gpShadowExplosionTex, &pos, + 7.0f*fwd.x, 7.0f*fwd.y, 3.5f*fwd.y, -3.5f*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); + 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){ + 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(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 + CalculateLeanMatrix(); + + CVector tailLightPos = mi->m_positions[CAR_POS_TAILLIGHTS]; + CVector light = m_leanMatrix * tailLightPos; + + 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 + // no lights in this case + }else{ + // braking + CCoronas::RegisterCorona((uintptr)this + 14, 120, 0, 0, 255, + light, 1.2f, 50.0f*TheCamera.LODDistMultiplier, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, + CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + CBrightLights::RegisterOne(light, GetUp(), GetRight(), GetForward(), pHandling->RearLights + BRIGHTLIGHT_REAR); + } + }else{ + CCoronas::UpdateCoronaCoors((uintptr)this + 14, light, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + } + }else{ + CCoronas::UpdateCoronaCoors((uintptr)this + 14, light, 50.0f*TheCamera.LODDistMultiplier, 0.0f); + } + } + + + // Wheel particles + + float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward())*180.0f; + int drawParticles = Abs(fwdSpeed) < 90.0f; + int susp = BIKESUSP_F1; + for(i = 0; i < 2; i++){ + if(i == BIKEWHEEL_REAR) + susp = BIKESUSP_R1; + + static float speedSq; + // Sparks for friction of burst wheels + if(m_wheelStatus[i] == WHEEL_STATUS_BURST && m_aSuspensionSpringRatioPrev[susp] < 1.0f && + (speedSq = m_vecMoveSpeed.MagnitudeSqr(), speedSq > SQR(0.1f)) && + m_aWheelColPoints[susp].surfaceB != SURFACE_GRASS && + m_aWheelColPoints[susp].surfaceB != SURFACE_MUD_DRY && + m_aWheelColPoints[susp].surfaceB != SURFACE_SAND && + m_aWheelColPoints[susp].surfaceB != SURFACE_SAND_BEACH){ + CVector normalSpeed = m_aWheelColPoints[susp].normal * DotProduct(m_aWheelColPoints[susp].normal, m_vecMoveSpeed); + CVector frictionSpeed = m_vecMoveSpeed - normalSpeed; + CVector sparkDir = 0.25f*frictionSpeed; + CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[susp].point, sparkDir); + + if(speedSq > 0.04f) + CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[susp].point, sparkDir); + if(speedSq > 0.16f){ + CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[susp].point, sparkDir); + CParticle::AddParticle(PARTICLE_SPARK_SMALL, m_aWheelColPoints[susp].point, sparkDir); + } + }else if(m_aSuspensionSpringRatioPrev[i] < 1.0f && + (fwdSpeed > 0.2f || m_aWheelState[i] == WHEEL_STATE_SPINNING)){ + if(m_aWheelColPoints[susp].surfaceB == SURFACE_GRASS || + m_aWheelColPoints[susp].surfaceB == SURFACE_MUD_DRY || + m_aWheelColPoints[susp].surfaceB == SURFACE_SAND || + m_aWheelColPoints[susp].surfaceB == SURFACE_SAND_BEACH) + AddWheelDirtAndWater(&m_aWheelColPoints[susp], drawParticles); + } + } + + AddDamagedVehicleParticles(); + CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_BIKE); + + CMatrix mat; + CVector pos; + CColModel *colModel = mi->GetColModel(); + + // Wheel rotation + CVector frontWheelFwd = Multiply3x3(GetMatrix(), CVector(-Sin(m_fSteerAngle), Cos(m_fSteerAngle), 0.0f)); + CVector rearWheelFwd = GetForward(); + if(m_aWheelTimer[BIKESUSP_F1] > 0.0f || m_aWheelTimer[BIKESUSP_F2] > 0.0f){ + float springRatio = Min(m_aSuspensionSpringRatioPrev[BIKESUSP_F1], m_aSuspensionSpringRatioPrev[BIKESUSP_F2]); + CVector contactPoint(0.0f, + (colModel->lines[BIKESUSP_F1].p0.y - colModel->lines[BIKESUSP_F2].p0.y)/2.0f, + colModel->lines[BIKESUSP_F1].p0.z - m_aSuspensionSpringLength[BIKESUSP_F1]*springRatio - 0.5f*mi->m_wheelScale); + CVector contactSpeed = GetSpeed(contactPoint); + // Why is wheel state always normal? + m_aWheelSpeed[BIKEWHEEL_FRONT] = ProcessWheelRotation(WHEEL_STATE_NORMAL, frontWheelFwd, contactSpeed, 0.5f*mi->m_wheelScale); + m_aWheelRotation[BIKEWHEEL_FRONT] += m_aWheelSpeed[BIKEWHEEL_FRONT]; + } + if(m_aWheelTimer[BIKESUSP_R1] > 0.0f || m_aWheelTimer[BIKESUSP_R2] > 0.0f){ + float springRatio = Min(m_aSuspensionSpringRatioPrev[BIKESUSP_R1], m_aSuspensionSpringRatioPrev[BIKESUSP_R2]); + CVector contactPoint(0.0f, + (colModel->lines[BIKESUSP_R1].p0.y - colModel->lines[BIKESUSP_R2].p0.y)/2.0f, + colModel->lines[BIKESUSP_R1].p0.z - m_aSuspensionSpringLength[BIKESUSP_R1]*springRatio - 0.5f*mi->m_wheelScale); + CVector contactSpeed = GetSpeed(contactPoint); + m_aWheelSpeed[BIKEWHEEL_REAR] = ProcessWheelRotation(m_aWheelState[BIKEWHEEL_REAR], rearWheelFwd, contactSpeed, 0.5f*mi->m_wheelScale); + m_aWheelRotation[BIKEWHEEL_REAR] += m_aWheelSpeed[BIKEWHEEL_REAR]; + } + + // Front fork + if(m_aBikeNodes[BIKE_FORKS_FRONT]){ + mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_FORKS_FRONT])); + pos = mat.GetPosition(); + + RwMatrix rwrot; + // TODO: this looks like some weird ctor we don't have + CMatrix rot; + rot.m_attachment = &rwrot; + rot.SetUnity(); + rot.UpdateRW(); + + // Make rotation matrix with front fork as axis + CVector forkAxis(0.0f, Sin(DEGTORAD(mi->m_bikeSteerAngle)), -Cos(DEGTORAD(mi->m_bikeSteerAngle))); + forkAxis.Normalise(); // as if that's not already the case + CQuaternion quat; + quat.Set(&forkAxis, -m_fWheelAngle); + quat.Get(rot.m_attachment); + rot.Update(); + + // Transform fork + mat.SetUnity(); + mat = mat * rot; + mat.Translate(pos); + mat.UpdateRW(); + + if(m_aBikeNodes[BIKE_HANDLEBARS]){ + // Transform handle + mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_HANDLEBARS])); + pos = mat.GetPosition(); + if(GetStatus() == STATUS_ABANDONED || GetStatus() == STATUS_WRECKED){ + mat.SetUnity(); + mat = mat * rot; + mat.Translate(pos); + }else + mat.SetTranslate(mat.GetPosition()); + mat.UpdateRW(); + } + } + + // Rear fork + if(m_aBikeNodes[BIKE_FORKS_REAR]){ + float sine = (m_aWheelPosition[BIKEWHEEL_REAR] - m_aWheelBasePosition[BIKEWHEEL_REAR])/m_fRearForkLength; + mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_FORKS_REAR])); + pos = mat.GetPosition(); + mat.SetRotate(-Asin(sine), 0.0f, 0.0f); + mat.Translate(pos); + mat.UpdateRW(); + } + + // Front wheel + mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_WHEEL_FRONT])); + pos.x = mat.GetPosition().x; + pos.z = m_aWheelPosition[BIKEWHEEL_FRONT] - m_fFrontForkZ; + float y = (colModel->lines[BIKESUSP_F1].p0.y+colModel->lines[BIKESUSP_F2].p0.y)/2.0f - m_fFrontForkY; + pos.y = y - (m_aWheelPosition[BIKEWHEEL_FRONT] - m_aWheelBasePosition[BIKEWHEEL_FRONT])*m_fFrontForkSlope; + if(m_wheelStatus[BIKEWHEEL_FRONT] == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[BIKEWHEEL_FRONT], 0.0f, 0.05f*Sin(m_aWheelRotation[BIKEWHEEL_FRONT])); + else + mat.SetRotateX(m_aWheelRotation[BIKEWHEEL_FRONT]); + mat.Translate(pos); + mat.UpdateRW(); + // and mudguard + mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_MUDGUARD])); + mat.SetTranslateOnly(pos); + mat.UpdateRW(); + + // Rear wheel + mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_WHEEL_REAR])); + pos = mat.GetPosition(); + if(m_wheelStatus[BIKEWHEEL_REAR] == WHEEL_STATUS_BURST) + mat.SetRotate(m_aWheelRotation[BIKEWHEEL_REAR], 0.0f, 0.07f*Sin(m_aWheelRotation[BIKEWHEEL_REAR])); + else + mat.SetRotateX(m_aWheelRotation[BIKEWHEEL_REAR]); + mat.Translate(pos); + mat.UpdateRW(); + + // Chassis + if(m_aBikeNodes[BIKE_CHASSIS]){ + mat.Attach(RwFrameGetMatrix(m_aBikeNodes[BIKE_CHASSIS])); + pos = mat.GetPosition(); + pos.z = (1.0f - Cos(m_fLeanLRAngle)) * (0.9*colModel->boundingBox.min.z); + mat.SetRotateX(-0.05f*Abs(m_fLeanLRAngle)); + mat.RotateY(m_fLeanLRAngle); + mat.Translate(pos); + mat.UpdateRW(); + } + + // Exhaust smoke + if(bEngineOn && !(pHandling->Flags & HANDLING_NO_EXHAUST) && fwdSpeed < 130.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; + } + + static float fumesLimit = 2.0f; + if(CGeneral::GetRandomNumberInRange(1.0f, 3.0f)*(m_fGasPedal+1.1f) > fumesLimit){ + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos1, dir); + if(dblExhaust) + CParticle::AddParticle(PARTICLE_EXHAUST_FUMES, pos2, dir); + + if(GetStatus() == STATUS_PLAYER && (CTimer::GetFrameCounter()&3) == 0 && + CWeather::Rain == 0.0f){ + CVector camDist = GetPosition() - TheCamera.GetPosition(); + if(DotProduct(GetForward(), camDist) > 0.0f || + TheCamera.GetLookDirection() == LOOKING_LEFT || + TheCamera.GetLookDirection() == LOOKING_RIGHT){ + if(dblExhaust) + pos1 = 0.5f*pos1 + 0.5f*pos2; + + if(TheCamera.GetLookDirection() == LOOKING_LEFT || + TheCamera.GetLookDirection() == LOOKING_RIGHT) + pos1 -= 0.2f*GetForward(); + + CParticle::AddParticle(PARTICLE_HEATHAZE, pos1, CVector(0.0f, 0.0f, 0.0f)); + } + } + } + } + } +} + +void +CBike::Render(void) +{ + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + + m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 3000; + mi->SetVehicleColour(m_currentColour1, m_currentColour2); + CEntity::Render(); +} + +int32 +CBike::ProcessEntityCollision(CEntity *ent, CColPoint *colpoints) +{ + int i; + CColModel *colModel; + + if(GetStatus() != STATUS_SIMPLE) + bVehicleColProcessed = true; + + 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]; + + if(m_bIsVehicleBeingShifted || bSkipLineCol || ent->IsPed() || + GetModelIndex() == MI_DODO && ent->IsVehicle()) + colModel->numLines = 0; + + 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(colModel->numLines){ + 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(); + } + + m_nSurfaceTouched = m_aWheelColPoints[i].surfaceB; + if(ent->IsBuilding()) + m_pCurGroundEntity = ent; + } + } + }else + colModel->numLines = 4; + + 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 +CBike::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 +#ifdef FIX_BUGS + 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 +#endif + { + 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); + + // Lean forward/backward + float updown; +#ifdef FREE_CAM + if (CCamera::bFreeCam) updown = CPad::IsAffectedByController ? -CPad::GetPad(pad)->GetSteeringUpDown()/128.0f : CPad::GetPad(pad)->GetCarGunUpDown()/128.0f; + else +#endif + updown = -CPad::GetPad(pad)->GetSteeringUpDown()/128.0f + CPad::GetPad(pad)->GetCarGunUpDown()/128.0f; + m_fLeanInput += (updown - m_fLeanInput)*0.2f*CTimer::GetTimeStep(); + m_fLeanInput = Clamp(m_fLeanInput, -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 + if(CPad::GetPad(pad)->GetAccelerate() > 150.0f && CPad::GetPad(pad)->GetBrake() > 150.0f){ + m_fGasPedal = CPad::GetPad(pad)->GetAccelerate()/255.0f; + m_fBrakePedal = CPad::GetPad(pad)->GetBrake()/255.0f; + m_doingBurnout = 1; + }else{ + 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(((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; + } + + // 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 +CBike::ProcessBuoyancy(void) +{ + int i; + CVector impulse, point; + + if(mod_Buoyancy.ProcessBuoyancy(this, m_fBuoyancy, &point, &impulse)){ + bTouchingWater = true; + ApplyMoveForce(impulse); + ApplyTurnForce(impulse, point); + + 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.8f || + impulseRatio > 0.4f && (m_aSuspensionSpringRatio[0] == 1.0f || + m_aSuspensionSpringRatio[1] == 1.0f || + m_aSuspensionSpringRatio[2] == 1.0f || + m_aSuspensionSpringRatio[3] == 1.0f)){ + bIsInWater = true; + bIsDrowning = true; + if(m_vecMoveSpeed.z < -0.1f) + m_vecMoveSpeed.z = -0.1f; + + if(pDriver){ + pDriver->bIsInWater = true; + if(pDriver->IsPlayer() || !bWaterTight){ + if(m_aSuspensionSpringRatio[0] < 1.0f || + m_aSuspensionSpringRatio[1] < 1.0f || + m_aSuspensionSpringRatio[2] < 1.0f || + m_aSuspensionSpringRatio[3] < 1.0f) + pDriver->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + else + KnockOffRider(WEAPONTYPE_DROWNING, 0, pDriver, false); + } + } + for(i = 0; i < m_nNumMaxPassengers; i++) + if(pPassengers[i]){ + pPassengers[i]->bIsInWater = true; + if(pPassengers[i]->IsPlayer() || !bWaterTight){ + if(m_aSuspensionSpringRatio[0] < 1.0f || + m_aSuspensionSpringRatio[1] < 1.0f || + m_aSuspensionSpringRatio[2] < 1.0f || + m_aSuspensionSpringRatio[3] < 1.0f) + pPassengers[i]->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + else + KnockOffRider(WEAPONTYPE_DROWNING, 0, pPassengers[i], false); + } + } + }else{ + bIsInWater = false; + bIsDrowning = false; + } + }else{ + bIsInWater = false; + bIsDrowning = false; + bTouchingWater = false; + } +} + +void +CBike::DoDriveByShootings(void) +{ + CAnimBlendAssociation *anim; + CPlayerInfo* playerInfo = ((CPlayerPed*)pDriver)->GetPlayerInfoForThisPlayerPed(); + if (playerInfo && !playerInfo->m_bDriveByAllowed) + return; + + CWeapon *weapon = pDriver->GetWeapon(); + if(CWeaponInfo::GetWeaponInfo(weapon->m_eWeaponType)->m_nWeaponSlot != 5) + return; + + weapon->Update(pDriver->m_audioEntityId, nil); + + bool lookingLeft = false; + bool lookingRight = false; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || + TheCamera.m_bObbeCinematicCarCamOn){ + 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 || CPad::GetPad(0)->GetCarGunFired()){ + if(lookingLeft){ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_RHS); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_FORWARD); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_LHS); + if(anim == nil || anim->blendDelta < 0.0f) + anim = CAnimManager::AddAnimation(pDriver->GetClump(), m_bikeAnimType, ANIM_BIKE_DRIVEBY_LHS); + }else if(lookingRight){ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_LHS); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_FORWARD); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_RHS); + if(anim == nil || anim->blendDelta < 0.0f) + anim = CAnimManager::AddAnimation(pDriver->GetClump(), m_bikeAnimType, ANIM_BIKE_DRIVEBY_RHS); + }else{ + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_RHS); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_LHS); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_FORWARD); + if(anim == nil || anim->blendDelta < 0.0f) + anim = CAnimManager::AddAnimation(pDriver->GetClump(), m_bikeAnimType, ANIM_BIKE_DRIVEBY_FORWARD); + } + + if (!anim || !anim->IsRunning()) { + if (CPad::GetPad(0)->GetCarGunFired() && CTimer::GetTimeInMilliseconds() > weapon->m_nTimer) { + weapon->FireFromCar(this, lookingLeft, lookingRight); + weapon->m_nTimer = CTimer::GetTimeInMilliseconds() + 70; + } + } + }else{ + weapon->Reload(); + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_LHS); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_RHS); + if(anim) + anim->blendDelta = -1000.0f; + anim = RpAnimBlendClumpGetAssociation(pDriver->GetClump(), ANIM_BIKE_DRIVEBY_FORWARD); + if(anim) + anim->blendDelta = -1000.0f; + } +} + +void +CBike::VehicleDamage(void) +{ + float impulse = m_fDamageImpulse; + float colSpeed = 800.0f*impulse/m_fMass; + if(GetStatus() == STATUS_PLAYER) + colSpeed *= 0.65f; + else if(VehicleCreatedBy == MISSION_VEHICLE) + colSpeed *= 0.4f; + + if(!bCanBeDamaged) + return; + + if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f) + impulse *= 0.5f; + + if(bIsStanding && impulse > 20.0f) + bIsStanding = false; + + // Inflict damage on the driver and passenger + if(pDriver && pDriver->GetPedState() == PED_DRIVING && colSpeed > 10.0f){ + float fwd = 0.6f; + if(Abs(DotProduct(m_vecDamageNormal, GetForward())) > 0.85f){ + float u = Max(DotProduct(m_vecDamageNormal, CVector(0.0f, 0.0f, 1.0f)), 0.0f); + if(u < 0.85f) + u = 0.0f; + fwd += 7.0f * SQR(u); + } + float up = 0.05f; + + if(GetModelIndex() == MI_SANCHEZ){ + fwd *= 0.65f; + up *= 0.75f; + } + + float total = fwd*Abs(DotProduct(m_vecDamageNormal, GetForward())) + + 0.45f*Abs(DotProduct(m_vecDamageNormal, GetRight())) + + up*Max(DotProduct(m_vecDamageNormal, GetUp()), 0.0f); + float damage = (total - 1.5f*Min(DotProduct(m_vecDamageNormal, GetUp()), 0.0f))*colSpeed; + + if(pDriver->IsPlayer() && CCullZones::CamStairsForPlayer() && CCullZones::FindZoneWithStairsAttributeForPlayer()) + damage = 0.0f; + + if(damage > 75.0f){ + int dir = -10; + if(pDriver){ + dir = pDriver->GetLocalDirection(-m_vecDamageNormal); + if(pDriver->m_fHealth > 0.0f) + pDriver->InflictDamage(m_pDamageEntity, WEAPONTYPE_RAMMEDBYCAR, 0.05f*damage, PEDPIECE_TORSO, dir); + if(pDriver && pDriver->GetPedState() == PED_DRIVING) + KnockOffRider(WEAPONTYPE_RAMMEDBYCAR, dir, pDriver, false); + } + if(pPassengers[0]){ + dir = pPassengers[0]->GetLocalDirection(-m_vecDamageNormal); + if(pPassengers[0]->m_fHealth > 0.0f) + pPassengers[0]->InflictDamage(m_pDamageEntity, WEAPONTYPE_RAMMEDBYCAR, 0.05f*damage, PEDPIECE_TORSO, dir); + if(pPassengers[0] && pPassengers[0]->GetPedState() == PED_DRIVING) + KnockOffRider(WEAPONTYPE_RAMMEDBYCAR, dir, pPassengers[0], false); + } + } + } + + if(impulse > 25.0f && GetStatus() != STATUS_WRECKED){ + float damage = (impulse-25.0f)*pHandling->fCollisionDamageMultiplier; + if(damage > 0.0f){ + if(damage > 5.0f && + pDriver && + m_pDamageEntity && m_pDamageEntity->IsVehicle() && + (this != FindPlayerVehicle() || ((CVehicle*)m_pDamageEntity)->VehicleCreatedBy == MISSION_VEHICLE) && + ((CVehicle*)m_pDamageEntity)->pDriver) + pDriver->Say(SOUND_PED_CRASH_VEHICLE); + + int oldHealth = m_fHealth; + if(this == FindPlayerVehicle()) + m_fHealth -= bTakeLessDamage ? damage/6.0f : damage/2.0f; + else if(bTakeLessDamage) + m_fHealth -= damage/12.0f; + else if(m_pDamageEntity && m_pDamageEntity == FindPlayerVehicle()) + m_fHealth -= damage/1.5f; + else + m_fHealth -= damage/4.0f; + if(m_fHealth <= 0.0f && oldHealth > 0) + m_fHealth = 1.0f; + } + } + + if(m_fHealth < 250.0f){ + // Car is on fire + if(!bIsOnFire){ + // Set engine on fire and remember who did this + bIsOnFire = true; + m_fFireBlowUpTimer = 0.0f; + m_pSetOnFireEntity = m_pDamageEntity; + if(m_pSetOnFireEntity) + m_pSetOnFireEntity->RegisterReference(&m_pSetOnFireEntity); + } + } +} + +void +CBike::AddDamagedVehicleParticles(void) +{ + if(this == FindPlayerVehicle() && TheCamera.GetLookingForwardFirstPerson()) + return; + if(this != FindPlayerVehicle() && (CTimer::GetFrameCounter() + m_randomSeed) & 1) + return; + if(m_fHealth >= 650.0f) + return; + + CVector direction = 0.5f*m_vecMoveSpeed; + CVector damagePos = ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->GetFrontSeatPosn(); + + damagePos.z -= 0.4f; + damagePos = GetMatrix()*damagePos; + + CalculateLeanMatrix(); + + if(m_fHealth < 250.0f){ + // fire, done in processControl + }else if(m_fHealth < 320.0f){ + direction *= 0.2f; + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE2, damagePos, direction + 0.02f*m_leanMatrix.GetRight()); + }else if(m_fHealth < 390.0f){ + if(((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 0 || + ((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 2) + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction + 0.05f*m_leanMatrix.GetRight()); + direction *= 0.3f; + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, damagePos, direction + 0.04f*m_leanMatrix.GetRight()); + }else if(m_fHealth < 460.0f){ + int rnd = CTimer::GetFrameCounter() + m_randomSeed; + if(rnd < 10 || + rnd < 70 && rnd > 25 || + rnd < 160 && rnd > 100 || + rnd < 200 && rnd > 175 || + rnd > 235) + return; + direction.z += 0.05f; + if(TheCamera.GetLookDirection() != LOOKING_FORWARD){ + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction + 0.08f*m_leanMatrix.GetRight(), nil, 0.1f, 0, 0, 0, 1000); + }else if(((CTimer::GetFrameCounter() + m_randomSeed) & 1) == 0){ + direction = 0.8f*m_vecMoveSpeed; + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos, direction + 0.07f*m_leanMatrix.GetRight(), nil, 0.1f, 0, 0, 0, 1000); + } + }else if(((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 0 || + ((CTimer::GetFrameCounter() + m_randomSeed) & 3) == 2){ + CParticle::AddParticle(PARTICLE_ENGINE_STEAM, damagePos + 0.06f*m_leanMatrix.GetRight(), direction); + } +} + +int32 +CBike::AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed) +{ + int i; + CVector dir; + static float minSize = 0.02f; + static float maxSize = 0.04f; + static RwRGBA grassCol = { 8, 24, 8, 255 }; + static RwRGBA gravelCol = { 64, 64, 64, 255 }; + static RwRGBA mudCol = { 64, 32, 16, 255 }; + static RwRGBA sandCol = { 170, 165, 140, 255 }; + static RwRGBA waterCol = { 48, 48, 64, 0 }; + + if(!belowEffectSpeed && + colpoint->surfaceB != SURFACE_SAND && colpoint->surfaceB != SURFACE_SAND_BEACH) + 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.04f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(minSize, maxSize), 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.04f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(minSize, maxSize), 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.04f); + CParticle::AddParticle(PARTICLE_WHEEL_DIRT, colpoint->point, dir, nil, + CGeneral::GetRandomNumberInRange(minSize, maxSize), mudCol); + } + return 0; + case SURFACE_SAND: + case SURFACE_SAND_BEACH: + if(CTimer::GetFrameCounter() & 2) + return 0; + dir.x = 0.75f*m_vecMoveSpeed.x; + dir.y = 0.75f*m_vecMoveSpeed.y; + for(i = 0; i < 1; i++){ + dir.z = CGeneral::GetRandomNumberInRange(0.02f, 0.055f); + CParticle::AddParticle(PARTICLE_SAND, colpoint->point, dir, nil, + 0.8f*m_vecMoveSpeed.Magnitude(), sandCol); + } + return 0; + default: + if(CWeather::WetRoads > 0.01f){ + CParticle::AddParticle( + PARTICLE_WATERSPRAY, + colpoint->point + CVector(0.0f, 0.0f, 0.25f+0.25f), + CVector(0.0f, 0.0f, CGeneral::GetRandomNumberInRange(0.005f, 0.04f)), + nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.5f), waterCol); + return 0; + } + return 1; + } + + return 0; +} + +void +CBike::GetComponentWorldPosition(int32 component, CVector &pos) +{ + if(m_aBikeNodes[component] == nil){ + printf("BikeNode missing: %d %d\n", GetModelIndex(), component); + return; + } + RwMatrix *ltm = RwFrameGetLTM(m_aBikeNodes[component]); + pos = *RwMatrixGetPos(ltm); +} + +bool +CBike::IsComponentPresent(int32 component) +{ + return m_aBikeNodes[component] != nil; +} + +void +CBike::SetComponentRotation(int32 component, CVector rotation) +{ + CMatrix mat(RwFrameGetMatrix(m_aBikeNodes[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(); +} + +bool +CBike::IsDoorReady(eDoors door) +{ + return true; +} + +bool +CBike::IsDoorFullyOpen(eDoors door) +{ + return false; +} + +bool +CBike::IsDoorClosed(eDoors door) +{ + return false; +} + +bool +CBike::IsDoorMissing(eDoors door) +{ + return true; +} + +void +CBike::RemoveRefsToVehicle(CEntity *ent) +{ + int i; + for(i = 0; i < 4; i++) + if(m_aGroundPhysical[i] == ent) + m_aGroundPhysical[i] = nil; +} + +void +CBike::BlowUpCar(CEntity *culprit) +{ + if(!bCanBeDamaged) + return; + +#ifdef FIX_BUGS + // taken from CAutomobile. maybe tweak values? + if(culprit == FindPlayerPed() || culprit == FindPlayerVehicle()){ + CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 20; + CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention += 10.0f; + CStats::PropertyDestroyed += CGeneral::GetRandomNumber()%6000 + 4000; + } +#endif + + // explosion pushes vehicle up + m_vecMoveSpeed.z += 0.13f; + SetStatus(STATUS_WRECKED); + bRenderScorched = true; + + m_fHealth = 0.0f; + m_nBombTimer = 0; + + TheCamera.CamShake(0.7f, GetPosition().x, GetPosition().y, GetPosition().z); + + KillPedsInVehicle(); + + bEngineOn = false; + bLightsOn = false; + ChangeLawEnforcerState(false); + + CExplosion::AddExplosion(this, culprit, EXPLOSION_CAR, GetPosition(), 0); + CDarkel::RegisterCarBlownUpByPlayer(this); +} + +bool +CBike::SetUpWheelColModel(CColModel *colModel) +{ + RwMatrix *mat = RwMatrixCreate(); + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + CColModel *vehColModel = mi->GetColModel(); + + colModel->boundingSphere = vehColModel->boundingSphere; + colModel->boundingBox = vehColModel->boundingBox; + + GetRelativeMatrix(mat, m_aBikeNodes[BIKE_WHEEL_FRONT], m_aBikeNodes[BIKE_CHASSIS]); + colModel->spheres[0].Set(0.5f*mi->m_wheelScale, *RwMatrixGetPos(mat), SURFACE_RUBBER, CAR_PIECE_WHEEL_LF); + GetRelativeMatrix(mat, m_aBikeNodes[BIKE_WHEEL_REAR], m_aBikeNodes[BIKE_CHASSIS]); + colModel->spheres[1].Set(0.5f*mi->m_wheelScale, *RwMatrixGetPos(mat), SURFACE_RUBBER, CAR_PIECE_WHEEL_LR); + colModel->numSpheres = 2; +#ifdef FIX_BUGS + RwMatrixDestroy(mat); +#endif + return true; +} + +float fBikeBurstForceMult = 0.02f; +float fBikeBurstFallSpeed = 0.3f; +float fBikeBurstFallSpeedPlayer = 0.55f; + +void +CBike::BurstTyre(uint8 wheel, bool applyForces) +{ + if(bTyresDontBurst) + return; + + switch(wheel){ + case CAR_PIECE_WHEEL_LF: wheel = BIKEWHEEL_FRONT; break; + case CAR_PIECE_WHEEL_LR: wheel = BIKEWHEEL_REAR; break; + } + + if(m_wheelStatus[wheel] == WHEEL_STATUS_OK){ + m_wheelStatus[wheel] = WHEEL_STATUS_BURST; +#ifdef FIX_BUGS + CStats::TyresPopped++; +#endif + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_TYRE_POP, 0.0f); + + if(GetStatus() == STATUS_SIMPLE){ + SetStatus(STATUS_PHYSICS); + CCarCtrl::SwitchVehicleToRealPhysics(this); + } + + if(applyForces){ + ApplyMoveForce(GetRight() * m_fMass * CGeneral::GetRandomNumberInRange(-0.02f, 0.02f)); + ApplyTurnForce(GetRight() * m_fTurnMass * CGeneral::GetRandomNumberInRange(-0.02f, 0.02f), GetForward()); + } + + // This code checks piece types originally so it is never triggered + // as we have converted them to wheel indices above already. + if(pDriver){ +#ifdef FIX_SIGNIFICANT_BUGS + if(wheel == BIKEWHEEL_FRONT && (m_aSuspensionSpringRatioPrev[BIKESUSP_F1] < 1.0f || m_aSuspensionSpringRatioPrev[BIKESUSP_F2] < 1.0f) || + wheel == BIKEWHEEL_REAR && (m_aSuspensionSpringRatioPrev[BIKESUSP_R1] < 1.0f || m_aSuspensionSpringRatioPrev[BIKESUSP_R2] < 1.0f)){ +#else + if(wheel == CAR_PIECE_WHEEL_LF && (m_aSuspensionSpringRatioPrev[BIKESUSP_F1] < 1.0f || m_aSuspensionSpringRatioPrev[BIKESUSP_F2] < 1.0f) || + wheel == CAR_PIECE_WHEEL_LR && (m_aSuspensionSpringRatioPrev[BIKESUSP_R1] < 1.0f || m_aSuspensionSpringRatioPrev[BIKESUSP_R2] < 1.0f)){ +#endif + float speedSq = m_vecMoveSpeed.MagnitudeSqr(); + if(speedSq > fBikeBurstFallSpeed && + (GetStatus() != STATUS_PLAYER || speedSq > fBikeBurstFallSpeedPlayer)){ +#ifdef FIX_SIGNIFICANT_BUGS + if(wheel == BIKEWHEEL_FRONT){ +#else + if(wheel == CAR_PIECE_WHEEL_LF){ +#endif + KnockOffRider(WEAPONTYPE_RAMMEDBYCAR, 0, pDriver, false); + if(pPassengers[0]) + KnockOffRider(WEAPONTYPE_RAMMEDBYCAR, 0, pPassengers[0], false); + }else + ApplyTurnForce(2.0f*fBikeBurstForceMult*m_fTurnMass*GetRight(), GetForward()); + } + } + } + } +} + +bool +CBike::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 = mi->GetFrontSeatPosn(); + if(component == CAR_DOOR_RR || component == CAR_DOOR_LR) + seatPos = mi->m_positions[CAR_POS_BACKSEAT]; + if(component == CAR_DOOR_LF || component == CAR_DOOR_LR) + seatPos.x = -seatPos.x; + 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 thiProcessEntityCollisions 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 +CBike::GetHeightAboveRoad(void) +{ + return m_fHeightAboveRoad; +} + +void +CBike::PlayCarHorn(void) +{ + uint32 r; + + if (IsAlarmOn() || m_nCarHornTimer != 0) + return; + + if (m_nCarHornDelay) { + m_nCarHornDelay--; + return; + } + + m_nCarHornDelay = (CGeneral::GetRandomNumber() & 0x7F) + 150; + r = m_nCarHornDelay & 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 +CBike::KnockOffRider(eWeaponType weapon, uint8 direction, CPed *ped, bool bGetBackOn) +{ + AnimationId anim = ANIM_STD_KO_FRONT; + if(ped == nil) + return; + + if(!ped->IsPlayer()){ + if(bGetBackOn){ + if(ped->m_pedStats->m_temper > ped->m_pedStats->m_fear && + ped->CharCreatedBy != MISSION_CHAR && ped->m_pMyVehicle->VehicleCreatedBy != MISSION_VEHICLE && + FindPlayerPed()->m_carInObjective == ped->m_pMyVehicle && + !CTheScripts::IsPlayerOnAMission()) + 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 && + !CTheScripts::IsPlayerOnAMission()) + 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 && + !CTheScripts::IsPlayerOnAMission()){ + ped->SetObjective(OBJECTIVE_WANDER, ped->m_pMyVehicle); + ped->m_nPathDir = CGeneral::GetRandomNumberInRange(0, 8); + } + }else if(ped->m_leader == nil){ + if(pDriver == ped) + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_DRIVER, this); + else + ped->SetObjective(OBJECTIVE_ENTER_CAR_AS_PASSENGER, this); + } + } + + if(ped->IsPed()){ + CAnimBlendAssociation *assoc; + for(assoc = RpAnimBlendClumpGetFirstAssociation(ped->GetClump(), ASSOC_DRIVING); + assoc; + assoc = RpAnimBlendGetNextAssociation(assoc)) + assoc->flags |= ASSOC_DELETEFADEDOUT; + } + + ped->SetPedState(PED_IDLE); + CAnimManager::BlendAnimation(ped->GetClump(), ped->m_animGroup, ANIM_STD_IDLE, 100.0f); + ped->m_vehDoor = CAR_DOOR_LF; + CPed::PedSetOutCarCB(nil, ped); + ped->SetMoveState(PEDMOVE_STILL); + if(GetUp().z < 0.0f) + ped->SetHeading(CGeneral::LimitRadianAngle(GetForward().Heading() + PI)); + else + ped->SetHeading(GetForward().Heading()); + + switch(weapon){ + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_UNIDENTIFIED: + ped->m_vecMoveSpeed = m_vecMoveSpeed; + ped->m_pCollidingEntity = this; + anim = ANIM_STD_NUM; + break; + + case WEAPONTYPE_BASEBALLBAT: + default: + switch(direction){ + case 0: + anim = ANIM_STD_BIKE_FALLBACK; + ped->m_vecMoveSpeed = CVector(0.0f, 0.0f, 0.1f); + if(m_vecMoveSpeed.MagnitudeSqr() < SQR(0.3f)) + ped->ApplyMoveForce(5.0f*GetUp() - 6.0f*GetForward()); + ped->m_pCollidingEntity = this; + break; + case 1: + case 2: + if(m_vecMoveSpeed.MagnitudeSqr() > SQR(0.3f)){ + anim = ANIM_STD_HIGHIMPACT_LEFT; + ped->m_vecMoveSpeed = 0.3f*m_vecMoveSpeed; + ped->ApplyMoveForce(5.0f*GetUp() + 6.0f*GetRight()); + }else{ + anim = ANIM_STD_SPINFORWARD_LEFT; + ped->m_vecMoveSpeed = m_vecMoveSpeed; + ped->ApplyMoveForce(4.0f*GetUp() + 8.0f*GetRight()); + } + // BUG or is it intentionally missing? + //ped->m_pCollidingEntity = this; + break; + case 3: + if(m_vecMoveSpeed.MagnitudeSqr() > SQR(0.3f)){ + anim = ANIM_STD_HIGHIMPACT_RIGHT; + ped->m_vecMoveSpeed = 0.3f*m_vecMoveSpeed; + ped->ApplyMoveForce(5.0f*GetUp() - 6.0f*GetRight()); + }else{ + anim = ANIM_STD_SPINFORWARD_RIGHT; + ped->m_vecMoveSpeed = m_vecMoveSpeed; + ped->ApplyMoveForce(4.0f*GetUp() - 8.0f*GetRight()); + } + // BUG or is it intentionally missing? + //ped->m_pCollidingEntity = this; + break; + } + break; + + case WEAPONTYPE_DROWNING:{ + RwRGBA color; + anim = ANIM_STD_FALL; + ped->m_vecMoveSpeed = m_vecMoveSpeed*0.2f; + ped->m_vecMoveSpeed.z = 0.0f; + ped->m_pCollidingEntity = this; + color.red = (0.5f * CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed_Obj())*0.45f*255; + color.green = (0.5f * CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen_Obj())*0.45f*255; + color.blue = (0.5f * CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue_Obj())*0.45f*255; + color.alpha = CGeneral::GetRandomNumberInRange(48, 96); + DMAudio.PlayOneShot(m_audioEntityId, SOUND_SPLASH, 0.0f); + CVector splashPos = ped->GetPosition() + 2.2f*ped->m_vecMoveSpeed; + float waterZ = 0.0f; + if(CWaterLevel::GetWaterLevel(splashPos, &waterZ, false)) + splashPos.z = waterZ; + CParticleObject::AddObject(POBJECT_PED_WATER_SPLASH, splashPos, CVector(0.0f, 0.0f, 0.1f), + 0.0f, 200, color, true); + break; + } + + case WEAPONTYPE_FALL: { + ped->m_vecMoveSpeed = ped->m_pMyVehicle->m_vecMoveSpeed; + float forceXY = -0.6*m_fDamageImpulse * ped->m_fMass / m_fMass; + ped->ApplyMoveForce(m_vecDamageNormal.x*forceXY, m_vecDamageNormal.y*forceXY, + CGeneral::GetRandomNumberInRange(3.0f, 7.0f)); + ped->m_pCollidingEntity = this; + switch(direction){ + case 0: anim = ANIM_STD_HIGHIMPACT_BACK; break; + case 1: anim = ANIM_STD_SPINFORWARD_RIGHT; break; + case 2: anim = ANIM_STD_BIKE_FALLBACK; break; + case 3: anim = ANIM_STD_SPINFORWARD_LEFT; break; + } + if(m_nWheelsOnGround == 0) + ped->bKnockedOffBike = true; + break; + } + + case WEAPONTYPE_RAMMEDBYCAR: { + ped->m_vecMoveSpeed = ped->m_pMyVehicle->m_vecMoveSpeed; + static float minForceZ = 8.0f; + static float maxForceZ = 15.0f; + float forceXY = -0.6*m_fDamageImpulse * ped->m_fMass / m_fMass; + ped->ApplyMoveForce(m_vecDamageNormal.x*forceXY, m_vecDamageNormal.y*forceXY, + CGeneral::GetRandomNumberInRange(minForceZ, maxForceZ)); + ped->m_pCollidingEntity = this; + switch(direction){ + case 0: anim = ANIM_STD_HIGHIMPACT_BACK; break; + case 1: anim = ANIM_STD_SPINFORWARD_RIGHT; break; + case 2: anim = ANIM_STD_HIGHIMPACT_FRONT; break; + case 3: anim = ANIM_STD_SPINFORWARD_LEFT; break; + } + ped->bKnockedOffBike = true; + if(ped->IsPlayer()) + ped->Say(SOUND_PED_DAMAGE); + break; + } + } + + if(weapon == WEAPONTYPE_DROWNING){ + ped->bIsStanding = false; + ped->bWasStanding = false; + ped->bIsInTheAir = true; + ped->bIsInWater = true; + ped->bTouchingWater = true; + CAnimManager::BlendAnimation(ped->GetClump(), ASSOCGRP_STD, ANIM_STD_FALL, 4.0f); + }else if(weapon != WEAPONTYPE_UNARMED){ + if(ped->m_fHealth > 0.0f) + ped->SetFall(1000, anim, 0); + else + ped->SetDie(anim); + ped->bIsStanding = false; + } + + CEntity *ent = CWorld::TestSphereAgainstWorld(ped->GetPosition()+CVector(0.0f, 0.0, 0.5f), 0.4f, nil, true, false, false, false, false, false); + if(ent == nil) + ent = CWorld::TestSphereAgainstWorld(ped->GetPosition()+CVector(0.0f, 0.0, 0.8f), 0.4f, nil, true, false, false, false, false, false); + if(ent == nil) + ent = CWorld::TestSphereAgainstWorld(ped->GetPosition()+CTimer::GetTimeStep()*ped->m_vecMoveSpeed+CVector(0.0f, 0.0, 0.5f), 0.4f, nil, true, false, false, false, false, false); + if(ent == nil) + ent = CWorld::TestSphereAgainstWorld(ped->GetPosition()+CTimer::GetTimeStep()*ped->m_vecMoveSpeed+CVector(0.0f, 0.0, 0.8f), 0.4f, nil, true, false, false, false, false, false); + if(ent){ + CColPoint point; + ent = nil; + if(CWorld::ProcessVerticalLine(ped->GetPosition(), ped->GetPosition().z-2.0f, point, ent, true, false, false, false, false, false, nil)){ + if(ped->m_pMyVehicle == nil){ + ped->m_pMyVehicle = this; + ped->PositionPedOutOfCollision(); + ped->m_pMyVehicle = nil; + }else + ped->PositionPedOutOfCollision(); + }else + ped->GetMatrix().Translate(CVector(0.0f, 0.0f, -2.0f)); + ped->m_pCollidingEntity = ped->m_pMyVehicle; + ped->bKnockedOffBike = true; + ped->bHeadStuckInCollision = true; + }else if(weapon == WEAPONTYPE_RAMMEDBYCAR){ + if(CWorld::TestSphereAgainstWorld(ped->GetPosition()+CVector(0.0f, 0.0, 1.3f), 0.6f, nil, true, false, false, false, false, false) == nil) + ped->GetMatrix().Translate(CVector(0.0f, 0.0f, 0.5f)); + } + ped->m_pMyVehicle = nil; +} + +void +CBike::PlayHornIfNecessary(void) +{ + if(AutoPilot.m_bSlowedDownBecauseOfPeds || + AutoPilot.m_bSlowedDownBecauseOfCars) + PlayCarHorn(); +} + +void +CBike::ResetSuspension(void) +{ + int i; + for(i = 0; i < 2; i++){ + m_aWheelRotation[i] = 0.0f; + m_aWheelState[i] = WHEEL_STATE_NORMAL; + } + for(i = 0; i < 4; i++){ + m_aSuspensionSpringRatio[i] = 1.0f; + m_aWheelTimer[i] = 0.0f; + } +} + +void +CBike::SetupSuspensionLines(void) +{ + int i; + CVector posn; + float suspOffset = 0.0f; + RwFrame *node = nil; + CVehicleModelInfo *mi = (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); + CColModel *colModel = mi->GetColModel(); + RwMatrix *mat = RwMatrixCreate(); + + bool initialized = colModel->lines[0].p0.z != FAKESUSPENSION; + + for(i = 0; i < 4; i++){ + if(initialized){ + posn = colModel->lines[i].p0; + if(i < 2) + posn.z = m_aWheelBasePosition[0]; + else + posn.z = m_aWheelBasePosition[1]; + }else{ + switch(i){ + case BIKESUSP_F1: + node = m_aBikeNodes[BIKE_WHEEL_FRONT]; + suspOffset = 0.25f*mi->m_wheelScale; + break; + case BIKESUSP_F2: + node = m_aBikeNodes[BIKE_WHEEL_FRONT]; + suspOffset = -0.25f*mi->m_wheelScale; + break; + case BIKESUSP_R1: + node = m_aBikeNodes[BIKE_WHEEL_REAR]; + suspOffset = 0.25f*mi->m_wheelScale; + break; + case BIKESUSP_R2: + node = m_aBikeNodes[BIKE_WHEEL_REAR]; + suspOffset = -0.25f*mi->m_wheelScale; + break; + } + + GetRelativeMatrix(mat, node, node); + posn = *RwMatrixGetPos(mat); + if(i == BIKESUSP_F1) + m_aWheelBasePosition[BIKEWHEEL_FRONT] = posn.z; + else if(i == BIKESUSP_R1){ + m_aWheelBasePosition[BIKEWHEEL_REAR] = posn.z; + + GetRelativeMatrix(mat, m_aBikeNodes[BIKE_FORKS_REAR], m_aBikeNodes[BIKE_FORKS_REAR]); + float dz = posn.z - RwMatrixGetPos(mat)->z; + float dy = posn.y - RwMatrixGetPos(mat)->y; + m_fRearForkLength = Sqrt(SQR(dy) + SQR(dz)); + assert(m_fRearForkLength != 0.0f); // we want to divide by this + } + posn.y += suspOffset; + } + + // 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; + } + + if(!initialized){ + GetRelativeMatrix(mat, m_aBikeNodes[BIKE_FORKS_FRONT], m_aBikeNodes[BIKE_FORKS_FRONT]); + m_fFrontForkY = RwMatrixGetPos(mat)->y; + m_fFrontForkZ = RwMatrixGetPos(mat)->z; + } + + // Compress spring somewhat to get normal height on road + m_fHeightAboveRoad = m_aSuspensionSpringLength[0]*(1.0f - 1.0f/(4.0f*pHandling->fSuspensionForceLevel)) + - colModel->lines[0].p0.z + mi->m_wheelScale*0.5f; + for(i = 0; i < 2; 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; + +#ifdef FIX_BUGS + RwMatrixDestroy(mat); +#endif +} + +void +CBike::CalculateLeanMatrix(void) +{ + if(bLeanMatrixClean) + return; + + CMatrix mat; + mat.SetRotateX(-0.05f*Abs(m_fLeanLRAngle)); + mat.RotateY(m_fLeanLRAngle); + m_leanMatrix = GetMatrix(); + m_leanMatrix = m_leanMatrix * mat; + // place wheel back on ground + m_leanMatrix.GetPosition() += GetUp()*(1.0f-Cos(m_fLeanLRAngle))*GetColModel()->boundingBox.min.z; + bLeanMatrixClean = true; +} + +void +CBike::GetCorrectedWorldDoorPosition(CVector &pos, CVector p1, CVector p2) +{ + CVector &fwd = GetForward(); + CVector rightWorld = CrossProduct(fwd, CVector(0.0f, 0.0f, 1.0f)); + CVector upWorld = CrossProduct(rightWorld, fwd); + CColModel *colModel = GetColModel(); + float onSide = DotProduct(GetUp(), rightWorld); + float diff = Max(colModel->boundingBox.max.z-colModel->boundingBox.max.x, 0.0f); + pos = CVector(0.0f, 0.0f, 0.0f); + float y = p2.y - p1.y; + float x = onSide*diff + p2.x + p1.x; + float z = p2.z - p1.z; + pos = x*rightWorld + y*fwd + z*upWorld + GetPosition(); +} + +void +CBike::Fix(void) +{ + bIsDamaged = false; + bIsOnFire = false; + m_wheelStatus[0] = WHEEL_STATUS_OK; + m_wheelStatus[1] = WHEEL_STATUS_OK; +} + +void +CBike::SetupModelNodes(void) +{ + int i; + for(i = 0; i < BIKE_NUM_NODES; i++) + m_aBikeNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aBikeNodes); +} + +void +CBike::ReduceHornCounter(void) +{ + if(m_nCarHornTimer != 0) + m_nCarHornTimer--; +} + +#ifdef COMPATIBLE_SAVES +void +CBike::Save(uint8*& buf) +{ + CVehicle::Save(buf); + ZeroSaveBuf(buf, 1260 - 672); +} + +void +CBike::Load(uint8*& buf) +{ + CVehicle::Load(buf); + SkipSaveBuf(buf, 1260 - 672); +} +#endif diff --git a/src/miami/vehicles/Bike.h b/src/miami/vehicles/Bike.h new file mode 100644 index 00000000..219d8872 --- /dev/null +++ b/src/miami/vehicles/Bike.h @@ -0,0 +1,172 @@ +#pragma once + +#include "Vehicle.h" +#include "Skidmarks.h" +#include "AnimManager.h" + +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 +}; + +enum { + BIKEWHEEL_FRONT, + BIKEWHEEL_REAR, +}; + +enum { + BIKESUSP_F1, + BIKESUSP_F2, + BIKESUSP_R1, + BIKESUSP_R2, +}; + +class CBike : public CVehicle +{ +public: + RwFrame *m_aBikeNodes[BIKE_NUM_NODES]; + bool bLeanMatrixClean; + CMatrix m_leanMatrix; + CVector m_vecAvgSurfaceNormal; + CVector m_vecAvgSurfaceRight; + tBikeHandlingData *pBikeHandling; + AssocGroupId m_bikeAnimType; + uint8 m_wheelStatus[2]; + CColPoint m_aWheelColPoints[4]; + float m_aSuspensionSpringRatio[4]; + float m_aSuspensionSpringRatioPrev[4]; + float m_aWheelTimer[4]; + float m_bike_unused1; + eSkidmarkType 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; + float m_fRearForkLength; + float m_fFrontForkY; + float m_fFrontForkZ; + float m_fFrontForkSlope; + float m_fWheelAngle; + float m_fLeanLRAngle; + float m_fLeanLRAngle2; + float m_fLeanInput; + float m_fPedLeanAmountLR; + float m_fPedLeanAmountUD; + uint8 m_bike_unused2; + uint8 unused[3]; // looks like padding..but for what? + uint8 m_bike_flag01 : 1; + uint8 m_bike_flag02 : 1; + uint8 bWaterTight : 1; + uint8 bIsBeingPickedUp : 1; + uint8 bIsStanding : 1; + uint8 bExtraSpeed : 1; // leaning forward + uint8 bIsOnFire : 1; + uint8 bWheelieCam : 1; + int16 m_doingBurnout; + float m_fTireTemperature; + float m_fBrakeDestabilization; + float m_fVelocityChangeForAudio; + float m_fFireBlowUpTimer; + CPhysical *m_aGroundPhysical[4]; + CVector m_aGroundOffset[4]; + CEntity *m_pSetOnFireEntity; + uint8 m_nWheelsOnGround; + uint8 m_nDriveWheelsOnGround; + uint8 m_nDriveWheelsOnGroundPrev; + float m_fGasPedalAudio; + tWheelState m_aWheelState[2]; + + CBike(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); + 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 applyForces); + bool IsRoomForPedToLeaveCar(uint32 component, CVector *doorOffset); + float GetHeightAboveRoad(void); + void PlayCarHorn(void); + + void KnockOffRider(eWeaponType weapon, uint8 direction, CPed *ped, bool bGetBackOn); + void VehicleDamage(void); + void ProcessBuoyancy(void); + void DoDriveByShootings(void); + void AddDamagedVehicleParticles(void); + int32 AddWheelDirtAndWater(CColPoint *colpoint, uint32 belowEffectSpeed); + void PlayHornIfNecessary(void); + void ResetSuspension(void); + void SetupSuspensionLines(void); + void CalculateLeanMatrix(void); + void GetCorrectedWorldDoorPosition(CVector &pos, CVector p1, CVector p2); + + void Fix(void); + void SetupModelNodes(void); + void ReduceHornCounter(void); + +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + static const uint32 nSaveStructSize; +}; + +// These functions and function names are made up + +inline int8 GetBikeDoorFlag(int32 carnode) { + switch (carnode) { + case CAR_DOOR_RR: + case CAR_DOOR_LR: + return CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR; + case CAR_DOOR_RF: + case CAR_DOOR_LF: + return CAR_DOOR_FLAG_RF | CAR_DOOR_FLAG_LF; + default: + return CAR_DOOR_FLAG_UNKNOWN; + } +} + +// for m_nGettingOutFlags +inline int8 GetBikeDoorFlagInclJumpInFromFront(int32 carnode) { + switch (carnode) { + case CAR_DOOR_RR: + case CAR_DOOR_LR: + return CAR_DOOR_FLAG_RR | CAR_DOOR_FLAG_LR; + case CAR_DOOR_RF: + case CAR_DOOR_LF: + case CAR_WINDSCREEN: + return CAR_DOOR_FLAG_RF | CAR_DOOR_FLAG_LF; + default: + return CAR_DOOR_FLAG_UNKNOWN; + } +} \ No newline at end of file diff --git a/src/miami/vehicles/Boat.cpp b/src/miami/vehicles/Boat.cpp new file mode 100644 index 00000000..5da8e1cc --- /dev/null +++ b/src/miami/vehicles/Boat.cpp @@ -0,0 +1,1489 @@ +#include "common.h" + +#include "main.h" +#include "General.h" +#include "Timecycle.h" +#include "Weather.h" +#include "HandlingMgr.h" +#include "CarAI.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 "ParticleObject.h" +#include "WaterLevel.h" +#include "Floater.h" +#include "World.h" +#include "Stats.h" +#include "Pools.h" +#include "Pad.h" +#include "Boat.h" +#include "AnimBlendAssociation.h" +#include "RpAnimBlend.h" +#include "Record.h" +#include "Shadows.h" +#include "Wanted.h" +#include "SaveBuf.h" + +#define INVALID_ORIENTATION (-9999.99f) + +float CBoat::MAX_WAKE_LENGTH = 50.0f; +float CBoat::MIN_WAKE_INTERVAL = 2.0f; +float CBoat::WAKE_LIFETIME = 150.0f; + +float fShapeLength = 0.4f; +float fShapeTime = 0.05f; +float fRangeMult = 0.6f; +float fTimeMult = 1.2f/CBoat::WAKE_LIFETIME; + +CBoat *CBoat::apFrameWakeGeneratingBoats[4]; + +const uint32 CBoat::nSaveStructSize = +#ifdef COMPATIBLE_SAVES + 1216; +#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; + m_fMovingSpeed = 0.0f; + m_skimmerThingTimer = 0.0f; + m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds(); + SetModelIndex(mi); + + pHandling = mod_HandlingManager.GetHandlingData((tVehicleType)minfo->m_handlingId); + pFlyingHandling = mod_HandlingManager.GetFlyingPointer((tVehicleType)minfo->m_handlingId); + pBoatHandling = mod_HandlingManager.GetBoatPointer((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_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_phys_unused1 = 0.0f; + 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; + + if(GetModelIndex() == MI_MARQUIS) + m_boom.Init(-PI/10.0f, PI/10.0f, 0, 2); + else + m_boom.Init(-PI/5.0f, PI/5.0f, 0, 2); +} + +void +CBoat::SetModelIndex(uint32 id) +{ + CVehicle::SetModelIndex(id); + SetupModelNodes(); +} + +void +CBoat::GetComponentWorldPosition(int32 component, CVector &pos) +{ + pos = *RwMatrixGetPos(RwFrameGetLTM(m_aBoatNodes[component])); +} + +void +CBoat::ProcessControl(void) +{ + bool onLand = m_fDamageImpulse > 0.0f && m_vecDamageNormal.z > 0.1f; + + PruneWakeTrail(); + + if(bRenderScorched) + m_fBuoyancy *= 0.99f; + +#ifdef FIX_BUGS + if(FindPlayerPed() && FindPlayerPed()->m_pWanted->GetWantedLevel() > 0 && GetModelIndex() == MI_PREDATOR && pDriver && IsPolicePedModel(pDriver->GetModelIndex())) { +#else + if(FindPlayerPed()->m_pWanted->GetWantedLevel() > 0 && GetModelIndex() == MI_PREDATOR){ +#endif + CVehicle *playerVeh = FindPlayerVehicle(); + if(playerVeh && playerVeh->GetVehicleAppearance() == VEHICLE_APPEARANCE_BOAT && + (AutoPilot.m_nCarMission == MISSION_RAMPLAYER_FARAWAY || + AutoPilot.m_nCarMission == MISSION_RAMPLAYER_CLOSE || + AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_FARAWAY || + AutoPilot.m_nCarMission == MISSION_BLOCKPLAYER_CLOSE || + AutoPilot.m_nCarMission == MISSION_ATTACKPLAYER) && + CTimer::GetTimeInMilliseconds() > m_nPoliceShoutTimer){ + DMAudio.PlayOneShot(m_audioEntityId, SOUND_PED_VCPA_PLAYER_FOUND, 0.0f); + m_nPoliceShoutTimer = CTimer::GetTimeInMilliseconds() + 4500 + (CGeneral::GetRandomNumber()&0xFFF); + } + } + + int r, g, b; + RwRGBA dropColor = { 0, 0, 0, 0 }; + RwRGBA splashColor, jetColor; + r = 127.5f*(CTimeCycle::GetAmbientRed_Obj() + 0.5f*CTimeCycle::GetDirectionalRed()); + g = 127.5f*(CTimeCycle::GetAmbientGreen_Obj() + 0.5f*CTimeCycle::GetDirectionalGreen()); + b = 127.5f*(CTimeCycle::GetAmbientBlue_Obj() + 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(160, 196); + + r = 229.5f*(CTimeCycle::GetAmbientRed() + 0.85f*CTimeCycle::GetDirectionalRed()); + g = 229.5f*(CTimeCycle::GetAmbientGreen() + 0.85f*CTimeCycle::GetDirectionalGreen()); + b = 229.5f*(CTimeCycle::GetAmbientBlue() + 0.85f*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(196, 228); + + CGeneral::GetRandomNumber(); // unused + + UpdateClumpAlpha(); + ProcessCarAlarm(); + + switch(GetStatus()){ + case STATUS_PLAYER: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + ProcessControlInputs(0); + if(GetModelIndex() == MI_PREDATOR) + DoFixedMachineGuns(); + + if (!CRecordDataForChase::IsRecording()) + DoDriveByShootings(); + break; + case STATUS_SIMPLE: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + CCarAI::UpdateCarAI(this); + CPhysical::ProcessControl(); + bBoatInWater = true; + bPropellerInWater = true; + bIsInWater = true; + return; + case STATUS_PHYSICS: + m_bIsAnchored = false; + m_fOrientation = INVALID_ORIENTATION; + CCarAI::UpdateCarAI(this); + CCarCtrl::SteerAICarWithPhysics(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() - FindPlayerCentreOfWorld_NoSniperShift()).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(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f) + collisionDamage *= 0.5f; + if (collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED && !bCollisionProof) { +#else + if(collisionDamage > 25.0f && GetStatus() != STATUS_WRECKED){ +#endif + float prevHealth = m_fHealth; + if(prevHealth >= 250.0f){ +#ifndef FIX_BUGS + // if collisionDamage < 50 we actually increase health here... + if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f) + collisionDamage *= 0.5f; +#endif + 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 <= 460.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 < 250.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 <= 390.0f) + CParticle::AddParticle(PARTICLE_ENGINE_SMOKE, smokePos, 1.25f*smokeDir); + } + + bool bSeparateTurnForce = bHasHitWall; + CPhysical::ProcessControl(); + + CVector buoyanceImpulse(0.0f, 0.0f, 0.0f); + CVector buoyancePoint(0.0f, 0.0f, 0.0f); + if(mod_Buoyancy.ProcessBuoyancyBoat(this, pHandling->fBuoyancy, &buoyancePoint, &buoyanceImpulse, bSeparateTurnForce)){ + // Process boat in water + if(0.1f * m_fMass * GRAVITY*CTimer::GetTimeStep() < buoyanceImpulse.z){ + bBoatInWater = true; + bIsInWater = true; + if (GetUp().z < -0.6f && Abs(GetMoveSpeed().x) < 0.05 && Abs(GetMoveSpeed().y) < 0.05) { + bIsDrowning = true; + if (pDriver){ + pDriver->bTouchingWater = true; + pDriver->InflictDamage(nil, WEAPONTYPE_DROWNING, CTimer::GetTimeStep(), PEDPIECE_TORSO, 0); + } + } + else + bIsDrowning = false; + }else{ + bBoatInWater = false; + bIsInWater = false; + bIsDrowning = false; + } + + m_fVolumeUnderWater = mod_Buoyancy.m_volumeUnderWater; + m_vecBuoyancePoint = buoyancePoint; + if(GetModelIndex() == MI_SKIMMER && GetUp().z < -0.5f && Abs(m_vecMoveSpeed.x) < 0.2f && Abs(m_vecMoveSpeed.y) < 0.2f) + ApplyMoveForce(0.03f*buoyanceImpulse); + else + ApplyMoveForce(buoyanceImpulse); + if(bSeparateTurnForce) + ApplyTurnForce(0.4f*buoyanceImpulse, buoyancePoint); + + // TODO: what is this? + if(GetModelIndex() == MI_SKIMMER) + if(m_skimmerThingTimer != 0.0f || + GetForward().z < -0.5f && GetUp().z > -0.5f && m_vecMoveSpeed.z < -0.15f && + buoyanceImpulse.z > 0.01f*m_fMass * GRAVITY*CTimer::GetTimeStep() && + buoyanceImpulse.z < 0.4f*m_fMass * GRAVITY*CTimer::GetTimeStep()){ + float turnImpulse = -0.00017f*GetForward().z*buoyanceImpulse.z * m_fMass*CTimer::GetTimeStep(); + ApplyTurnForce(turnImpulse*GetForward(), GetUp()); + bBoatInWater = false; + //BUG? aren't we forgetting the timestep here? + float moveImpulse = -0.5f*DotProduct(m_vecMoveSpeed, GetForward()) * m_fMass; + ApplyMoveForce(moveImpulse*GetForward()); + if(m_skimmerThingTimer == 0.0f) + m_skimmerThingTimer = CTimer::GetTimeInMilliseconds() + 300.0f; + else if(m_skimmerThingTimer < CTimer::GetTimeInMilliseconds()) + m_skimmerThingTimer = 0.0f; + } + + if(!onLand && bBoatInWater && GetUp().z > 0.0f){ + float impulse = m_vecMoveSpeed.MagnitudeSqr()*pBoatHandling->fAqPlaneForce*buoyanceImpulse.z*CTimer::GetTimeStep()*0.5f; + if(GetModelIndex() == MI_SKIMMER) + impulse *= 1.0f + m_fGasPedal; + else if(m_fGasPedal > 0.05f) + impulse *= m_fGasPedal; + else + impulse = 0.0f; + impulse = Min(impulse, GRAVITY*pBoatHandling->fAqPlaneLimit*m_fMass*CTimer::GetTimeStep()); + ApplyMoveForce(impulse*GetUp()); + ApplyTurnForce(impulse*GetUp(), buoyancePoint - pBoatHandling->fAqPlaneOffset*GetForward()); + } + + // Handle boat moving forward + float fwdSpeed = 1.0f; + if(Abs(m_fGasPedal) > 0.05f || (fwdSpeed = m_vecMoveSpeed.Magnitude2D()) > 0.01f){ + if(bBoatInWater && fwdSpeed > 0.05f) + AddWakePoint(GetPosition()); + + float steerFactor = 1.0f; + if(GetStatus() == STATUS_PLAYER){ + float steerLoss = DotProduct(m_vecMoveSpeed, GetForward())*pHandling->fTractionBias; + if(CPad::GetPad(0)->GetHandBrake()) + steerLoss *= 0.5f; + steerFactor -= steerLoss; + steerFactor = Clamp(steerFactor, 0.0f, 1.0f); + } + + CVector boundMin = GetColModel()->boundingBox.min; + CVector propeller(0.0f, boundMin.y*pBoatHandling->fThrustY, boundMin.z*pBoatHandling->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; + + bool bSlowAhead = false; + if(Abs(m_fGasPedal) > 0.01f && GetModelIndex() != MI_SKIMMER){ + if(Abs(m_fGasPedal) < 0.05f) + bSlowAhead = true; + + 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 - pBoatHandling->fThrustAppZ*GetUp()); + float rightForce = -DotProduct(GetRight(), force)*pHandling->fTractionMultiplier; + ApplyTurnForce(rightForce*GetRight() * CTimer::GetTimeStep(), GetUp()); + } + + // Spray some particles + CVector jetDir = -0.04f * force; + if(m_fGasPedal > 0.0f){ + if(GetStatus() == STATUS_PLAYER){ + CVector sternPos = GetColModel()->boundingBox.min; + sternPos.x = 0.0f; + sternPos.z = 0.0f; + sternPos = Multiply3x3(GetMatrix(), sternPos); + + CVector wakePos = GetPosition() + sternPos; + // no actual particles for player... + }else if(IsVisible() && ((CTimer::GetFrameCounter() + m_randomSeed) & 1) && + CVisibilityPlugins::GetDistanceSquaredFromCamera(&propellerWorld) < SQR(70.0f * TheCamera.GenerationDistMultiplier)){ + jetDir.z = 0.015f; + jetDir.x *= 3.5f; + jetDir.y *= 3.5f; + propellerWorld.z += 0.5f; + + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, propellerWorld, jetDir, nil, 1.25f, jetColor, + CGeneral::GetRandomNumberInRange(0, 5), + CGeneral::GetRandomNumberInRange(0, 90), 1, 500); + + CParticle::AddParticle(PARTICLE_CAR_SPLASH, propellerWorld, 0.75f * jetDir, nil, 0.5f, splashColor, + CGeneral::GetRandomNumberInRange(0, 30), + CGeneral::GetRandomNumberInRange(0, 45), 3, 500); + } + } + }else + bSlowAhead = true; + + if(!onLand && bSlowAhead){ + float force = pHandling->fTractionLoss*DotProduct(m_vecMoveSpeed, GetForward()); + force = Min(force, 0.01f*m_fTurnMass); + if(m_fGasPedal > 0.01f){ + if(GetStatus() == STATUS_PLAYER) + force *= (0.55f - Abs(m_fGasPedal)) * 1.3f; + else + force *= (0.55f - Abs(m_fGasPedal)) * 2.5f; + } + if(m_fGasPedal < 0.0f && force > 0.0f || m_fGasPedal > 0.0f && force < 0.0f) + force *= -1.0f; + CVector propellerForce = propellerDepth * Multiply3x3(GetMatrix(), force*CVector(-steerSin, 0.0f, 0.0f)); + ApplyMoveForce(propellerForce * CTimer::GetTimeStep()); + ApplyTurnForce(propellerForce * CTimer::GetTimeStep(), propeller); + float rightForce = -steerSin * force * 0.75f/steerFactor * Max(CTimer::GetTimeStep(), 0.01f); + ApplyTurnForce(GetRight() * rightForce, GetUp()); + } + }else + bPropellerInWater = false; + + if(pHandling->fSuspensionBias != 0.0f){ + CVector right = CrossProduct(GetForward(), CVector(0.0f, 0.0f, 1.0f)); + float rightSpeed = DotProduct(m_vecMoveSpeed, right); + float impulse = 0.1f*pHandling->fSuspensionBias * m_fMass * m_fVolumeUnderWater * rightSpeed * CTimer::GetTimeStep(); + ApplyMoveForce(right - impulse * 0.3f * CVector(-right.y, right.x, 0.0f)); + } + + if(GetStatus() == STATUS_PLAYER && CPad::GetPad(0)->GetHandBrake()){ + float fwdSpeed = DotProduct(m_vecMoveSpeed, GetForward()); + if(fwdSpeed > 0.0f){ + float impulse = -0.1f*pHandling->fSuspensionLowerLimit * m_fMass * m_fVolumeUnderWater * fwdSpeed * CTimer::GetTimeStep(); + ApplyMoveForce(impulse * GetForward()); + } + } + } + + // Slow down or push down boat as it approaches the world limits + m_vecMoveSpeed.x = Min(m_vecMoveSpeed.x, -(GetPosition().x - (WORLD_MAX_X-100.0f))*0.01f); // east + m_vecMoveSpeed.x = Max(m_vecMoveSpeed.x, -(GetPosition().x - (WORLD_MIN_X+100.0f))*0.01f); // west + m_vecMoveSpeed.y = Min(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MAX_Y-100.0f))*0.01f); // north + m_vecMoveSpeed.y = Max(m_vecMoveSpeed.y, -(GetPosition().y - (WORLD_MIN_Y+100.0f))*0.01f); // south + + if(!onLand && bBoatInWater && !bSeparateTurnForce) + ApplyWaterResistance(); + + if((GetModelIndex() != MI_SKIMMER || m_skimmerThingTimer == 0.0f) && !bSeparateTurnForce){ + // No idea what exactly is going on here besides drag in YZ + float fx = Pow(pBoatHandling->vecTurnRes.x, CTimer::GetTimeStep()); + float fy = Pow(pBoatHandling->vecTurnRes.y, CTimer::GetTimeStep()); + float fz = Pow(pBoatHandling->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(forceUp*GetUp(), com + GetForward()); + } + + m_nDeltaVolumeUnderWater = (m_fVolumeUnderWater-m_fPrevVolumeUnderWater)*10000; + + // Falling into water + if(!onLand && bBoatInWater && GetUp().z > 0.0f){ + float splashVol = m_nDeltaVolumeUnderWater*pBoatHandling->fWaveAudioMult; + if(splashVol > 200.0f) + DMAudio.PlayOneShot(m_audioEntityId, SOUND_CAR_SPLASH, splashVol); + + if(m_nDeltaVolumeUnderWater > 200){ + float speedUp = m_vecMoveSpeed.MagnitudeSqr() * m_nDeltaVolumeUnderWater * 0.001f; + 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->fBrakeBias; + CVector speed = speedFwd*GetForward() + CVector(0.0f, 0.0f, speedUp); + CVector splashImpulse = speed * m_fMass; + ApplyMoveForce(splashImpulse); + ApplyTurnForce(splashImpulse, buoyancePoint); + } + } + + // Splashes + float speed = m_vecMoveSpeed.Magnitude(); + if(speed > 0.05f && GetUp().x > 0.0f && !TheCamera.GetLookingForwardFirstPerson() && IsVisible() && + (AutoPilot.m_nCarMission != MISSION_CRUISE || (CTimer::GetFrameCounter()&2) == 0)){ + CVector splashPos, splashDir; + float splashSize, front, waterLevel; + + switch(GetModelIndex()){ + case MI_RIO: + splashSize = speed; + front = 0.9f * GetColModel()->boundingBox.max.y; + splashDir = -0.5f * m_vecMoveSpeed; + splashDir.z += 0.25f*speed; + splashDir += 0.35f*speed*GetRight(); + splashPos = GetPosition() + 1.85f*GetRight() + front*GetForward(); + splashPos.z += 0.5f; + break; + case MI_SQUALO: + splashSize = speed; + front = 0.75f * GetColModel()->boundingBox.max.y; + splashDir = -0.125f * m_vecMoveSpeed; + splashDir.z += 0.15f*speed; + splashDir += 0.25f*speed*GetRight(); + splashPos = GetPosition() + m_vecBuoyancePoint + 0.5f*GetRight() + front*GetForward(); + splashPos.z += 0.5f; + break; + case MI_REEFER: + splashSize = speed; + front = 0.75f * GetColModel()->boundingBox.max.y; + splashDir = -0.5f * m_vecMoveSpeed; + splashDir.z += 0.15f*speed; + splashDir += 0.5f*speed*GetRight(); + splashPos = GetPosition() + m_vecBuoyancePoint + 1.3f*GetRight() + front*GetForward(); + break; + case MI_COASTG: + splashSize = 0.25f*speed; + front = 0.8f * GetColModel()->boundingBox.max.y; + splashDir = 0.165f * m_vecMoveSpeed; + splashDir.z += 0.25f*speed; + splashDir += 0.15f*speed*GetRight(); + splashPos = GetPosition() + 0.65f*GetRight() + front*GetForward(); + splashPos.z += 0.5f; + break; + case MI_DINGHY: + splashSize = 0.25f*speed; + front = 0.9f * GetColModel()->boundingBox.max.y; + splashDir = 0.35f * m_vecMoveSpeed; + splashDir.z += 0.25f*speed; + splashDir += 0.25f*speed*GetRight(); + splashPos = GetPosition() + 0.6f*GetRight() + front*GetForward(); + splashPos.z += 0.5f; + break; + default: + splashSize = speed; + front = 0.9f * GetColModel()->boundingBox.max.y; + splashDir = -0.5f * m_vecMoveSpeed; + splashDir.z += 0.25f*speed; + splashDir += 0.35f*speed*GetRight(); + splashPos = GetPosition() + m_vecBuoyancePoint + 0.5f*GetRight() + front*GetForward(); + break; + } + if(splashSize > 0.75f) splashSize = 0.75f; + if(AutoPilot.m_nCarMission == MISSION_CRUISE) + splashDir *= 1.5f; + static float lifeMult = 1000.0f; + static float lifeBase = 300.0f; + splashDir.z += 0.0003f*m_nDeltaVolumeUnderWater; + CWaterLevel::GetWaterLevel(splashPos, &waterLevel, true); + if(splashPos.z-waterLevel < 3.0f && + CVisibilityPlugins::GetDistanceSquaredFromCamera(&splashPos) < SQR(70.0f * TheCamera.GenerationDistMultiplier)){ + splashPos.z = waterLevel + 0.1f; + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashPos, 0.75f*splashDir, nil, splashSize+0.1f, splashColor, + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), CGeneral::GetRandomNumberInRange(0.0f, 90.0f), + 1, lifeBase + splashDir.z*lifeMult); + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, splashPos, splashDir, nil, splashSize, jetColor, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), + 0, lifeBase + splashDir.z*lifeMult); + } + + switch(GetModelIndex()){ + case MI_RIO: + splashDir = -0.5f * m_vecMoveSpeed; + splashDir.z += 0.25f*speed; + splashDir -= 0.35f*speed*GetRight(); + splashPos = GetPosition() - 1.85f*GetRight() + front*GetForward(); + splashPos.z += 0.5f; + break; + case MI_SQUALO: + splashDir = -0.125f * m_vecMoveSpeed; + splashDir.z += 0.15f*speed; + splashDir -= 0.25f*speed*GetRight(); + splashPos = GetPosition() + m_vecBuoyancePoint - 0.5f*GetRight() + front*GetForward(); + splashPos.z += 0.5f; + break; + case MI_REEFER: + splashDir = -0.5f * m_vecMoveSpeed; + splashDir.z += 0.15f*speed; + splashDir -= 0.5f*speed*GetRight(); + splashPos = GetPosition() + m_vecBuoyancePoint - 1.3f*GetRight() + front*GetForward(); + break; + case MI_COASTG: + splashDir = 0.165f * m_vecMoveSpeed; + splashDir.z += 0.25f*speed; + splashDir -= 0.15f*speed*GetRight(); + splashPos = GetPosition() - 0.65f*GetRight() + front*GetForward(); + splashPos.z += 0.5f; + break; + case MI_DINGHY: + splashDir = 0.35f * m_vecMoveSpeed; + splashDir.z += 0.25f*speed; + splashDir -= 0.25f*speed*GetRight(); + splashPos = GetPosition() - 0.6f*GetRight() + front*GetForward(); + splashPos.z += 0.5f; + break; + default: + splashDir = -0.5f * m_vecMoveSpeed; + splashDir.z += 0.25f*speed; + splashDir -= 0.35f*speed*GetRight(); + splashPos = GetPosition() + m_vecBuoyancePoint - 0.5f*GetRight() + front*GetForward(); + break; + } + if(AutoPilot.m_nCarMission == MISSION_CRUISE) + splashDir *= 1.5f; + splashDir.z += 0.0003f*m_nDeltaVolumeUnderWater; + CWaterLevel::GetWaterLevel(splashPos, &waterLevel, true); + if(splashPos.z-waterLevel < 3.0f && + CVisibilityPlugins::GetDistanceSquaredFromCamera(&splashPos) < SQR(70.0f * TheCamera.GenerationDistMultiplier)){ + splashPos.z = waterLevel + 0.1f; + CParticle::AddParticle(PARTICLE_CAR_SPLASH, splashPos, 0.75f*splashDir, nil, splashSize+0.1f, splashColor, + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), CGeneral::GetRandomNumberInRange(0.0f, 90.0f), + 1, lifeBase + splashDir.z*lifeMult); + CParticle::AddParticle(PARTICLE_BOAT_SPLASH, splashPos, splashDir, nil, splashSize, jetColor, + CGeneral::GetRandomNumberInRange(0.0f, 0.4f), CGeneral::GetRandomNumberInRange(0.0f, 45.0f), + 0, lifeBase + splashDir.z*lifeMult); + } + } + + // Spray waterdrops on screen + if(TheCamera.GetLookingForwardFirstPerson() && FindPlayerVehicle() && FindPlayerVehicle()->IsBoat() && + m_nDeltaVolumeUnderWater > 0 && numWaterDropOnScreen < 20){ + CVector dropPos; + CVector dropDir(CGeneral::GetRandomNumberInRange(-0.25f, 0.25f), CGeneral::GetRandomNumberInRange(1.0f, 0.75f), 0.0f); + + int frm = CGeneral::GetRandomNumber() & 1; + if(TheCamera.m_CameraAverageSpeed < 0.35f){ + dropPos.x = CGeneral::GetRandomNumberInRange(50, (int)SCREEN_WIDTH-50); + dropPos.y = CGeneral::GetRandomNumberInRange(50, (int)SCREEN_HEIGHT-50); + }else{ + dropPos.x = CGeneral::GetRandomNumberInRange(200, (int)SCREEN_WIDTH-200); + dropPos.y = CGeneral::GetRandomNumberInRange(150, (int)SCREEN_HEIGHT-150); + } + dropPos.z = 1.0f; + + if(TheCamera.m_CameraAverageSpeed > 0.35f){ + if((int)SCREEN_WIDTH / 2 < dropPos.x) + dropPos.x += CGeneral::GetRandomNumberInRange(0.35f, TheCamera.m_CameraAverageSpeed)*7.5f; + else + dropPos.x -= CGeneral::GetRandomNumberInRange(0.35f, TheCamera.m_CameraAverageSpeed)*7.5f; + + if((int)SCREEN_HEIGHT / 2 < dropPos.y) + dropPos.y += CGeneral::GetRandomNumberInRange(0.35f, TheCamera.m_CameraAverageSpeed)*7.5f; + else + dropPos.y -= CGeneral::GetRandomNumberInRange(0.35f, TheCamera.m_CameraAverageSpeed)*7.5f; + } + + if(CParticle::AddParticle(PARTICLE_WATERDROP, dropPos, dropDir, nil, + CGeneral::GetRandomNumberInRange(0.1f, 0.15f), dropColor, 0, 0, frm)) + numWaterDropOnScreen++; + } + + if(m_fPrevVolumeUnderWater == 0.0f && m_fVolumeUnderWater > 0.0f && GetModelIndex() == MI_SKIMMER){ + CVector splashDir(0.0f, 0.0f, 0.25f*speed); + CVector splashPos = GetPosition(); + float level; + CWaterLevel::GetWaterLevel(splashPos, &level, true); + splashPos.z = level; + CParticleObject::AddObject(POBJECT_CAR_WATER_SPLASH, splashPos, splashDir, 0.0f, 65, splashColor, true); + } + + m_fPrevVolumeUnderWater = m_fVolumeUnderWater; + }else{ + bBoatInWater = false; + bIsInWater = false; +#ifdef FIX_BUGS + bIsDrowning = false; +#endif + } + + if(m_modelIndex == MI_SKIMMER && CTimer::GetTimeStep() > 0.0f){ + if(GetStatus() == STATUS_PLAYER){ + if(m_fMovingSpeed < 0.22f) + m_fMovingSpeed += 0.001f*CTimer::GetTimeStep(); + FlyingControl(FLIGHT_MODEL_SEAPLANE); + }else{ + if(m_fMovingSpeed > 0.0005f*CTimer::GetTimeStep()) + m_fMovingSpeed -= 0.0005f*CTimer::GetTimeStep(); + else + m_fMovingSpeed = 0.0f; + } + }else if(bCheat8) + FlyingControl(FLIGHT_MODEL_PLANE); + + 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.3f; + + 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; +} + +float fSeaPlaneWaterResistance = 30.0f; + +void +CBoat::ApplyWaterResistance(void) +{ + // TODO: figure out how this works + float resistance = 0.001f * pHandling->fSuspensionForceLevel * SQR(m_fVolumeUnderWater) * m_fMass; + if(GetModelIndex() == MI_SKIMMER) + resistance *= fSeaPlaneWaterResistance; + float fwdSpeed = DotProduct(GetMoveSpeed(), GetForward()); + float magic = (SQR(fwdSpeed) + 0.05f) * resistance + 1.0f; + magic = Abs(magic); + float fx = Pow(pBoatHandling->vecMoveRes.x/magic, 0.5f*CTimer::GetTimeStep()); + float fy = Pow(pBoatHandling->vecMoveRes.y/magic, 0.5f*CTimer::GetTimeStep()); + float fz = Pow(pBoatHandling->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); + + KillPedsInVehicle(); + + bEngineOn = false; + bLightsOn = false; + ChangeLawEnforcerState(false); + + CExplosion::AddExplosion(this, culprit, EXPLOSION_BOAT, GetPosition(), 0); + CDarkel::RegisterCarBlownUpByPlayer(this); + 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); + obj->bDontStream = true; + + // 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); +} + +void +CBoat::PreRender(void) +{ + CMatrix matrix; + CVector pos; + RpAtomic *atomic; + + if(GetModelIndex() == MI_SKIMMER){ + m_fMovingRotation += m_fMovingSpeed*CTimer::GetTimeStep(); + if(m_fMovingRotation > TWOPI) m_fMovingRotation -= TWOPI; + int alpha = (1.0f - Min(2.0f*m_fMovingSpeed*8.0f/PI, 1.0f))*255.0f; + if(GetStatus() == STATUS_PLAYER || GetStatus() == STATUS_PLAYER_REMOTE || GetStatus() == STATUS_PLAYER_PLAYBACKFROMBUFFER){ + if(m_aBoatNodes[BOAT_RUDDER]){ + float sine = Sin(m_fSteerAngle); + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_RUDDER])); + pos = matrix.GetPosition(); + matrix.SetRotate(0.0f, 0.0f, -m_fSteerAngle); + matrix.Rotate(0.0f, DEGTORAD(22.0f)*sine, 0.0f); + matrix.Translate(pos); + matrix.UpdateRW(); + } + if(m_aBoatNodes[BOAT_FLAP_LEFT]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_FLAP_LEFT])); + pos = matrix.GetPosition(); + matrix.SetRotateX(-m_fSteerAngle); + matrix.Translate(pos); + matrix.UpdateRW(); + } + if(m_aBoatNodes[BOAT_FLAP_RIGHT]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_FLAP_RIGHT])); + pos = matrix.GetPosition(); + matrix.SetRotateX(m_fSteerAngle); + matrix.Translate(pos); + matrix.UpdateRW(); + } + // FIX: Planes can also be controlled with GetCarGunUpDown +#ifdef FIX_BUGS + static float steeringUpDown = 0.0f; +#ifdef FREE_CAM + if(!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController)) +#endif + steeringUpDown += ((Abs(CPad::GetPad(0)->GetCarGunUpDown()) > 1.0f ? (-CPad::GetPad(0)->GetCarGunUpDown()/128.0f) : (-CPad::GetPad(0)->GetSteeringUpDown()/128.0f)) - steeringUpDown) * Min(1.f, CTimer::GetTimeStep()/5.f); +#ifdef FREE_CAM + else + steeringUpDown = -CPad::GetPad(0)->GetSteeringUpDown()/128.0f; +#endif +#else + float steeringUpDown = -CPad::GetPad(0)->GetSteeringUpDown()/128.0f; +#endif + if(m_aBoatNodes[BOAT_REARFLAP_LEFT]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_REARFLAP_LEFT])); + pos = matrix.GetPosition(); + matrix.SetRotateX(steeringUpDown); + matrix.Translate(pos); + matrix.UpdateRW(); + } + if(m_aBoatNodes[BOAT_REARFLAP_RIGHT]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_REARFLAP_RIGHT])); + pos = matrix.GetPosition(); + matrix.SetRotateX(steeringUpDown); + matrix.Translate(pos); + matrix.UpdateRW(); + } + } + if(m_aBoatNodes[BOAT_MOVING]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING])); + pos = matrix.GetPosition(); + matrix.SetRotateY(m_fMovingRotation); + matrix.Translate(pos); + matrix.UpdateRW(); + + atomic = nil; + RwFrameForAllObjects(m_aBoatNodes[BOAT_MOVING], GetBoatAtomicObjectCB, &atomic); + if(atomic) + SetComponentAtomicAlpha(atomic, alpha); + } + if(m_aBoatNodes[BOAT_WINDSCREEN]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_WINDSCREEN])); + pos = matrix.GetPosition(); + matrix.SetRotateY(-m_fMovingRotation); + matrix.Translate(pos); + matrix.UpdateRW(); + + atomic = nil; + RwFrameForAllObjects(m_aBoatNodes[BOAT_WINDSCREEN], GetBoatAtomicObjectCB, &atomic); + if(atomic) + SetComponentAtomicAlpha(atomic, Max(150-alpha, 0)); + } + CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_SEAPLANE); + }else if(GetModelIndex() == MI_COASTG || GetModelIndex() == MI_DINGHY || GetModelIndex() == MI_RIO || + GetModelIndex() == MI_SQUALO || GetModelIndex() == MI_MARQUIS){ + if(m_aBoatNodes[BOAT_RUDDER]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_RUDDER])); + pos = matrix.GetPosition(); + matrix.SetRotateZ(-m_fSteerAngle); + matrix.Translate(pos); + matrix.UpdateRW(); + } + if(m_aBoatNodes[BOAT_REARFLAP_LEFT]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_REARFLAP_LEFT])); + pos = matrix.GetPosition(); + matrix.SetRotateZ(-m_fSteerAngle); + matrix.Translate(pos); + matrix.UpdateRW(); + } + if(m_aBoatNodes[BOAT_REARFLAP_RIGHT]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_REARFLAP_RIGHT])); + pos = matrix.GetPosition(); + matrix.SetRotateZ(-m_fSteerAngle); + matrix.Translate(pos); + matrix.UpdateRW(); + } + } + + if(GetModelIndex() == MI_RIO || GetModelIndex() == MI_MARQUIS){ + float axes[3] = { 0.0f, 0.0f, 0.0f }; + m_boom.Process(this); + axes[m_boom.m_nAxis] = m_boom.m_fAngle; + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_FLAP_LEFT])); + pos = matrix.GetPosition(); + matrix.SetRotate(axes[0], axes[1], axes[2]); + matrix.Translate(pos); + matrix.UpdateRW(); + } + + if(GetModelIndex() == MI_RIO){ + // That little wind propeller + if(m_aBoatNodes[BOAT_FLAP_RIGHT]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_FLAP_RIGHT])); + pos = matrix.GetPosition(); + + float flapHeading = matrix.GetForward().Heading(); + float boatHeading = GetForward().Heading(); + float rot = -DEGTORAD(45.0f) - (flapHeading + boatHeading); + // eh what? + rot = CGeneral::LimitRadianAngle(rot); + if(rot > HALFPI) rot = PI; + else if(rot < -HALFPI) rot = -PI; + rot = Clamp(rot, -DEGTORAD(63.0f), DEGTORAD(63.0f)); + m_fMovingSpeed += (0.008f * CWeather::Wind + 0.002f) * rot; + m_fMovingSpeed *= Pow(0.9985f, CTimer::GetTimeStep())/(500.0f*SQR(m_fMovingSpeed) + 1.0f); + + matrix.SetRotateZ(flapHeading + m_fMovingSpeed*CTimer::GetTimeStep()); + matrix.Translate(pos); + matrix.UpdateRW(); + } + if(m_aBoatNodes[BOAT_MOVING]){ + matrix.Attach(RwFrameGetMatrix(m_aBoatNodes[BOAT_MOVING])); + pos = matrix.GetPosition(); + matrix.SetRotateY(m_fMovingRotation); + matrix.Translate(pos); + matrix.UpdateRW(); + + CVector wind = CVector(0.707f, 0.707f, 0.0f) * (CWeather::Wind + 0.15f)*0.4f; + m_fMovingRotation += (m_vecMoveSpeed + wind).Magnitude()*CTimer::GetTimeStep(); + } + }else if(GetModelIndex() == MI_PREDATOR || GetModelIndex() == MI_REEFER){ + 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.02f * CTimer::GetTimeStep(); + } +} + +RwIm3DVertex KeepWaterOutVertices[4]; +RwImVertexIndex KeepWaterOutIndices[6]; + +void +CBoat::Render() +{ + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()))->SetVehicleColour(m_currentColour1, m_currentColour2); + m_nSetPieceExtendedRangeTime = CTimer::GetTimeInMilliseconds() + 3000; + if (!CVehicle::bWheelsOnlyCheat) + CEntity::Render(); +#ifdef NEW_RENDERER + if(!gbNewRenderer) +#endif + RenderWaterOutPolys(); // not separate function in VC +} + +void +CBoat::RenderWaterOutPolys(void) +{ + if(GetModelIndex() == MI_SKIMMER) + return; + 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_RIO: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.3f, -1.016f, 0.51f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.3f, -1.016f, 0.51f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.3f, -2.832f, 0.51f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.3f, -2.832f, 0.51f); + break; + case MI_SQUALO: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.222f, 2.004f, 1.409f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.222f, 2.004f, 1.409f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.24f, -1.367f, 0.846f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.24f, -1.367f, 0.846f); + break; + 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: + 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; + case MI_TROPIC: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.886f, -2.347f, 0.787f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.886f, -2.347f, 0.787f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.886f, -4.67f, 0.842f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.886f, -4.67f, 0.842f); + break; + case MI_COASTG: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -0.663f, 3.565f, 0.382f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 0.663f, 3.565f, 0.382f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.087f, 0.83f, 0.381f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.087f, 0.83f, 0.381f); + break; + case MI_DINGHY: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -0.797f, 1.641f, 0.573f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 0.797f, 1.641f, 0.573f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -0.865f, -1.444f, 0.509f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 0.865f, -1.444f, 0.509f); + break; + case MI_MARQUIS: + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.246f, -1.373f, 0.787f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.246f, -1.373f, 0.787f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.023f, -5.322f, 0.787f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.023f, -5.322f, 0.787f); + break; + default: + return; + } + 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(); + } + bool drawAnotherRect = false; + if(GetModelIndex() == MI_COASTG){ + drawAnotherRect = true; + RwIm3DVertexSetPos(&KeepWaterOutVertices[0], -1.087f, 0.831f, 0.381f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[1], 1.087f, 0.831f, 0.381f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[2], -1.097f, -2.977f, 0.381f); + RwIm3DVertexSetPos(&KeepWaterOutVertices[3], 1.097f, -2.977f, 0.381f); + } + if(drawAnotherRect){ + 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; + 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); +} + +// unused +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; +} + +// unused +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; + CVector2D camPos = TheCamera.GetPosition(); + CVector2D camFwd = TheCamera.GetForward(); + float camDist = camFwd.Magnitude(); + if(camDist > 0.0f) + camFwd /= camDist; + 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) { + if (boat->m_nNumWakePoints != 0) { + CVector2D camToBoat = CVector2D(boat->GetPosition()) - camPos; + float distToCam = DotProduct2D(camFwd, camToBoat); + if(distToCam > 100.0f || distToCam < -15.0f) + continue; + float distSq = camToBoat.MagnitudeSqr(); + if(distSq > SQR(70.0f)) + continue; + if (frameId >= ARRAY_SIZE(apFrameWakeGeneratingBoats)) { + float nearest = 999999.88f; + int16 frameId2 = -1; + for (int16 j = 0; j < ARRAY_SIZE(apFrameWakeGeneratingBoats); j++) { + float tmpDistSq = (CVector2D(apFrameWakeGeneratingBoats[j]->GetPosition()) - camPos).MagnitudeSqr(); + if (tmpDistSq < nearest) { + nearest = tmpDistSq; + frameId2 = j; + } + } + + if (frameId2 != -1 && + (distSq < nearest || boat->GetStatus() == STATUS_PLAYER)) + 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(2.0f)) { + if(GetStatus() == STATUS_PLAYER){ + if(m_nNumWakePoints >= 31) + m_nNumWakePoints = 31; + }else if(VehicleCreatedBy == MISSION_VEHICLE){ + if(m_nNumWakePoints >= 20) + m_nNumWakePoints = 20; + }else{ + if(m_nNumWakePoints >= 15) + m_nNumWakePoints = 15; + } + for(i = m_nNumWakePoints; i != 0; i--){ + m_avec2dWakePoints[i] = m_avec2dWakePoints[i-1]; + m_afWakePointLifeTime[i] = m_afWakePointLifeTime[i-1]; + } + m_avec2dWakePoints[0] = point; + m_afWakePointLifeTime[0] = 150.0f; + if(m_nNumWakePoints < ARRAY_SIZE(m_afWakePointLifeTime)) + m_nNumWakePoints++; + } + }else{ + m_avec2dWakePoints[0] = point; + m_afWakePointLifeTime[0] = 150.0f; + m_nNumWakePoints = 1; + } +} + +void +CBoat::DoDriveByShootings(void) +{ + CAnimBlendAssociation *anim = nil; + CPlayerInfo* playerInfo = ((CPlayerPed*)pDriver)->GetPlayerInfoForThisPlayerPed(); + if (playerInfo && !playerInfo->m_bDriveByAllowed) + return; + + CWeapon *weapon = pDriver->GetWeapon(); + if(CWeaponInfo::GetWeaponInfo(weapon->m_eWeaponType)->m_nWeaponSlot != 5) + return; + + weapon->Update(pDriver->m_audioEntityId, nil); + + bool lookingLeft = false; + bool lookingRight = false; + if(TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_TOPDOWN || + TheCamera.m_bObbeCinematicCarCamOn){ + 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) + anim = CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVEBY_LEFT); + }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) + anim = CAnimManager::AddAnimation(pDriver->GetClump(), ASSOCGRP_STD, ANIM_STD_CAR_DRIVEBY_RIGHT); + } + + if (!anim || !anim->IsRunning()) { + if (CPad::GetPad(0)->GetCarGunFired() && CTimer::GetTimeInMilliseconds() > weapon->m_nTimer) { + weapon->FireFromCar(this, lookingLeft, true); + 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; + } +} + +#ifdef COMPATIBLE_SAVES +void +CBoat::Save(uint8*& buf) +{ + CVehicle::Save(buf); + ZeroSaveBuf(buf, 1216 - 672); +} + +void +CBoat::Load(uint8*& buf) +{ + CVehicle::Load(buf); + SkipSaveBuf(buf, 1216 - 672); +} +#endif diff --git a/src/miami/vehicles/Boat.h b/src/miami/vehicles/Boat.h new file mode 100644 index 00000000..5d866c48 --- /dev/null +++ b/src/miami/vehicles/Boat.h @@ -0,0 +1,85 @@ +#pragma once + +#include "Vehicle.h" +#include "Door.h" + +enum eBoatNodes +{ + BOAT_MOVING = 1, + BOAT_WINDSCREEN, + BOAT_RUDDER, + BOAT_FLAP_LEFT, + BOAT_FLAP_RIGHT, + BOAT_REARFLAP_LEFT, + BOAT_REARFLAP_RIGHT, + NUM_BOAT_NODES +}; + +class CBoat : public CVehicle +{ +public: + float m_fMovingRotation; + float m_fMovingSpeed; + int32 m_boat_unused1; + RwFrame *m_aBoatNodes[NUM_BOAT_NODES]; + CDoor m_boom; + tBoatHandlingData *pBoatHandling; + uint8 bBoatInWater : 1; + uint8 bPropellerInWater : 1; + bool m_bIsAnchored; + float m_fOrientation; + uint32 m_nPoliceShoutTimer; + int32 m_boat_unused2; + float m_fDamage; + CEntity *m_pSetOnFireEntity; + float m_skimmerThingTimer; + 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]; + + static float MAX_WAKE_LENGTH; + static float MIN_WAKE_INTERVAL; + static float WAKE_LIFETIME; + + 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); + void DoDriveByShootings(void); + + 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; + +}; \ No newline at end of file diff --git a/src/miami/vehicles/CarGen.cpp b/src/miami/vehicles/CarGen.cpp new file mode 100644 index 00000000..bce8cdab --- /dev/null +++ b/src/miami/vehicles/CarGen.cpp @@ -0,0 +1,309 @@ +#include "common.h" + +#include "CarGen.h" + +#include "Automobile.h" +#include "Bike.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 "VisibilityPlugins.h" +#include "World.h" +#include "Zones.h" +#include "Occlusion.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() +{ + int mi; + if (CCarCtrl::NumParkedCars >= 10) + return; + if (m_nModelIndex >= 0) { + if (CheckForBlockage(m_nModelIndex)) { + m_nTimer += 4; + return; + } + CStreaming::RequestModel(m_nModelIndex, STREAMFLAGS_DEPENDENCY); + mi = m_nModelIndex; + } + else { + mi = -m_nModelIndex; + if (m_nModelIndex == -1 || !CStreaming::HasModelLoaded(mi)) { + CZoneInfo pZone; + CVector pos = FindPlayerCoors(); + CTheZones::GetZoneInfoForTimeOfDay(&pos, &pZone); + mi = CCarCtrl::ChooseCarModel(CCarCtrl::ChooseCarRating(&pZone)); + if (mi < 0) + return; + m_nModelIndex = -mi; + m_nColor1 = -1; + m_nColor2 = -1; + } + if (CheckForBlockage(mi)) { + m_nTimer += 4; + return; + } + } + if (!CStreaming::HasModelLoaded(mi)) + return; + CVehicle* pVehicle; + + CVector pos; + if (CModelInfo::IsBoatModel(mi)){ + CBoat* pBoat = new CBoat(mi, PARKED_VEHICLE); + pos = m_vecPos; + pVehicle = pBoat; + if (pos.z <= -100.0f) + pos.z = CWorld::FindGroundZForCoord(pos.x, pos.y); + pBoat->bExtendedRange = true; + }else{ + bool groundFound; + 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); + return; + } + if (((CVehicleModelInfo*)CModelInfo::GetModelInfo(mi))->m_vehicleType == VEHICLE_TYPE_BIKE) { + CBike* pBike = new CBike(mi, PARKED_VEHICLE); + pBike->bIsStanding = true; + pVehicle = pBike; + } + else { + CAutomobile* pCar = new CAutomobile(mi, PARKED_VEHICLE); + pVehicle = pCar; + } + // pVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + pVehicle->bLightsOn = false; + } + pVehicle->bIsStatic = false; + pVehicle->bEngineOn = false; + pos.z += pVehicle->GetDistanceFromCentreOfMassToBaseOfModel(); + pVehicle->SetPosition(pos); + pVehicle->SetOrientation(0.0f, 0.0f, DEGTORAD(m_fAngle)); + pVehicle->SetStatus(STATUS_ABANDONED); + pVehicle->m_nDoorLock = CARLOCK_UNLOCKED; + CWorld::Add(pVehicle); + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nAlarm) + pVehicle->m_nAlarmState = -1; + if (CGeneral::GetRandomNumberInRange(0, 100) < m_nDoorlock) + pVehicle->m_nDoorLock = CARLOCK_LOCKED; + if (m_nColor1 != -1 && m_nColor2 != -1) { + pVehicle->m_currentColour1 = m_nColor1; + pVehicle->m_currentColour2 = m_nColor2; + } + else if (m_nModelIndex < -1) { + m_nColor1 = pVehicle->m_currentColour1; + m_nColor2 = pVehicle->m_currentColour2; + } + CVisibilityPlugins::SetClumpAlpha(pVehicle->GetClump(), 0); + m_nVehicleHandle = CPools::GetVehiclePool()->GetIndex(pVehicle); + /* I don't think this is a correct comparasion */ +#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 && CheckIfWithinRangeOfAnyPlayers()) + 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; + if (m_nModelIndex < 0) + m_nModelIndex = -1; +} + +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; +} + +bool CCarGenerator::CheckForBlockage(int32 mi) +{ + int16 entities; + CEntity* pEntities[8]; + CColModel* pColModel = CModelInfo::GetColModel(mi); + CWorld::FindObjectsKindaColliding(CVector(m_vecPos), pColModel->boundingSphere.radius, 1, &entities, 8, pEntities, false, true, true, false, false); + for (int i = 0; i < entities; i++) { + if (m_vecPos.z + pColModel->boundingBox.min.z < pEntities[i]->GetPosition().z + pEntities[i]->GetColModel()->boundingBox.max.z + 1.0f && + m_vecPos.z + pColModel->boundingBox.max.z > pEntities[i]->GetPosition().z + pEntities[i]->GetColModel()->boundingBox.min.z - 1.0f) { + m_bIsBlocking = true; + return true; + } + } + return false; +} + +bool CCarGenerator::CheckIfWithinRangeOfAnyPlayers() +{ + CVector2D direction = FindPlayerCentreOfWorld(CWorld::PlayerInFocus) - m_vecPos; + float distance = direction.Magnitude(); + float farclip = 110.0f * TheCamera.GenerationDistMultiplier; + float nearclip = farclip - 20.0f; + bool canBeRemoved = (m_nModelIndex > 0 && CModelInfo::IsBoatModel(m_nModelIndex) && 165.0f * TheCamera.GenerationDistMultiplier > distance && + TheCamera.IsSphereVisible(m_vecPos, 0.0f) && !COcclusion::IsPositionOccluded(m_vecPos, 0.0f)); + if (distance >= farclip && !canBeRemoved){ + if (m_bIsBlocking) + m_bIsBlocking = false; + return false; + } + if (CTheCarGenerators::GenerateEvenIfPlayerIsCloseCounter) + return true; + if (m_bIsBlocking) + return false; + if (distance < nearclip && !m_bForceSpawn) + 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) +{ + if (NumOfCarGenerators < NUM_CARGENS) + CarGeneratorArray[NumOfCarGenerators++].Setup(x, y, z, angle, mi, color1, color2, force, alarm, lock, min_delay, max_delay); + return NumOfCarGenerators - 1; +} + +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) +{ + NumOfCarGenerators = 0; + GenerateEvenIfPlayerIsCloseCounter = 0; + CurrentActiveCount = 0; + ProcessCounter = 0; + + 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/miami/vehicles/CarGen.h b/src/miami/vehicles/CarGen.h new file mode 100644 index 00000000..fccbee96 --- /dev/null +++ b/src/miami/vehicles/CarGen.h @@ -0,0 +1,51 @@ +#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; +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(int32 mi); + bool CheckIfWithinRangeOfAnyPlayers(); + 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/miami/vehicles/Cranes.cpp b/src/miami/vehicles/Cranes.cpp new file mode 100644 index 00000000..934ccb08 --- /dev/null +++ b/src/miami/vehicles/Cranes.cpp @@ -0,0 +1,740 @@ +#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 0x3E0 +#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() || + MODELID_CRANE_4 == pEntity->GetModelIndex() || + MODELID_CRANE_5 == pEntity->GetModelIndex() || + MODELID_CRANE_6 == pEntity->GetModelIndex()) + AddThisOneCrane(pEntity); + } + } + } + for (CPtrNode* pNode = CWorld::GetBigBuildingList(LEVEL_MAINLAND).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() || + MODELID_CRANE_4 == pEntity->GetModelIndex() || + MODELID_CRANE_5 == pEntity->GetModelIndex() || + MODELID_CRANE_6 == 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_bIsTop = (MODELID_CRANE_1 != pEntity->GetModelIndex()); + 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; + } + } + } + } + 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 && +#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_FBIRANCH || + 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_FBIRANCH: 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_FBIRANCH: 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_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_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 + } + + VALIDATESAVEBUF(size); +} diff --git a/src/miami/vehicles/Cranes.h b/src/miami/vehicles/Cranes.h new file mode 100644 index 00000000..e842ed3f --- /dev/null +++ b/src/miami/vehicles/Cranes.h @@ -0,0 +1,96 @@ +#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; + 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/miami/vehicles/DamageManager.cpp b/src/miami/vehicles/DamageManager.cpp new file mode 100644 index 00000000..8ba235b7 --- /dev/null +++ b/src/miami/vehicles/DamageManager.cpp @@ -0,0 +1,233 @@ +#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.5f; + field_18 = 1; +} + +void +CDamageManager::ResetDamageStatus(void) +{ + int i; + m_fWheelDamageEffect = 0.0f; + m_engineStatus = 0; + for(i = 0; i < ARRAY_SIZE(m_wheelStatus); i++) m_wheelStatus[i] = 0; + for(i = 0; i < ARRAY_SIZE(m_doorStatus); i++) m_doorStatus[i] = 0; + m_lightStatus = 0; + m_panelStatus = 0; +} + +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(component == COMPONENT_PANEL_WINDSCREEN) + damage *= 0.6f; + 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) +{ + // gone in VC + return false; +} diff --git a/src/miami/vehicles/DamageManager.h b/src/miami/vehicles/DamageManager.h new file mode 100644 index 00000000..312006e3 --- /dev/null +++ b/src/miami/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/miami/vehicles/Door.cpp b/src/miami/vehicles/Door.cpp new file mode 100644 index 00000000..1b3f9e8f --- /dev/null +++ b/src/miami/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/miami/vehicles/Door.h b/src/miami/vehicles/Door.h new file mode 100644 index 00000000..567d3263 --- /dev/null +++ b/src/miami/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/miami/vehicles/Floater.cpp b/src/miami/vehicles/Floater.cpp new file mode 100644 index 00000000..08688a3c --- /dev/null +++ b/src/miami/vehicles/Floater.cpp @@ -0,0 +1,324 @@ +#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.4f, 0.7f, 0.4f + // bow +}; +float fBoatVolumeDistributionCat[9] = { + 0.9f, 0.3f, 0.9f, + 1.0f, 0.5f, 1.0f, + 0.95f, 0.4f, 0.95f +}; +float fBoatVolumeDistributionSail[9] = { + 0.55f, 0.95f, 0.55f, + 0.75f, 1.1f, 0.75f, + 0.3f, 0.8f, 0.3f +}; +float fBoatVolumeDistributionDinghy[9] = { + 0.65f, 0.85f, 0.65f, + 0.85f, 1.1f, 0.85f, + 0.65f, 0.95f, 0.65f +}; +float fBoatVolumeDistributionSpeed[9] = { + 0.7f, 0.9f, 0.7f, + 0.95f, 1.0f, 0.95f, + 0.6f, 0.7f, 0.6f +}; + +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; +} + +bool +cBuoyancy::ProcessBuoyancyBoat(CVehicle *veh, float buoyancy, CVector *point, CVector *impulse, bool bNoTurnForce) +{ + m_numSteps = 2.0f; + + if(!CWaterLevel::GetWaterLevel(veh->GetPosition(), &m_waterlevel, veh->bTouchingWater)) + return false; + m_matrix = veh->GetMatrix(); + PreCalcSetup(veh, buoyancy); + + + float x, y; + int ix, i; + tWaterLevel waterPosition; + CVector waterNormal; + + // Floater is divided into 3x3 parts. Process and sum each of them + float volDiv = 1.0f/((m_dimMax.z - m_dimMin.z)*sq(m_numSteps+1.0f)); + 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); + FindWaterLevelNorm(m_positionZ, &waterLevel, &waterPosition, &waterNormal); + switch(veh->GetModelIndex()){ + case MI_RIO: + fVolMultiplier = fBoatVolumeDistributionCat[i]; + break; + case MI_SQUALO: + case MI_SPEEDER: + case MI_JETMAX: + fVolMultiplier = fBoatVolumeDistributionSpeed[i]; + break; + case MI_COASTG: + case MI_DINGHY: + fVolMultiplier = fBoatVolumeDistributionDinghy[i]; + break; + case MI_MARQUIS: + fVolMultiplier = fBoatVolumeDistributionSail[i]; + break; + case MI_PREDATOR: + case MI_SKIMMER: + case MI_REEFER: + case MI_TROPIC: + default: + fVolMultiplier = fBoatVolumeDistribution[i]; + break; + } + if(waterPosition != FLOATER_ABOVE_WATER){ + float volume = SimpleSumBuoyancyData(waterLevel, waterPosition); + float upImpulse = volume * volDiv * buoyancy * CTimer::GetTimeStep(); + CVector speed = veh->GetSpeed(Multiply3x3(veh->GetMatrix(), CVector(x, y, 0.0f))); + float damp = 1.0f - DotProduct(speed, waterNormal)*veh->pHandling->fSuspensionDampingLevel; + float finalImpulse = upImpulse*Max(damp, 0.0f); + impulse->z += finalImpulse; + if(!bNoTurnForce) + veh->ApplyTurnForce(finalImpulse*waterNormal, Multiply3x3(m_matrix, waterLevel)); + } + i += 3; + } + ix++; + } + + m_volumeUnderWater *= volDiv; + + *point = Multiply3x3(m_matrix, m_impulsePoint); + return m_isBoat || m_haveVolume; + +} + +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){ + switch(phys->GetModelIndex()){ + case MI_PREDATOR: + default: + m_dimMax.y *= 1.05f; + m_dimMin.y *= 0.9f; + break; + case MI_SPEEDER: + m_dimMax.y *= 1.25f; + m_dimMin.y *= 0.83f; + break; + case MI_REEFER: + m_dimMin.y *= 0.9f; + break; + case MI_RIO: + m_dimMax.y *= 0.9f; + m_dimMin.y *= 0.9f; + m_dimMax.z += 0.25f; + m_dimMin.z -= 0.2f; + break; + case MI_SQUALO: + m_dimMax.y *= 0.9f; + m_dimMin.y *= 0.9f; + break; + case MI_TROPIC: + m_dimMax.y *= 1.3f; + m_dimMin.y *= 0.82f; + m_dimMin.z -= 0.2f; + break; + case MI_SKIMMER: + m_dimMin.y = -m_dimMax.y; + m_dimMax.y *= 1.2f; + break; + case MI_COASTG: + m_dimMax.y *= 1.1f; + m_dimMin.y *= 0.9f; + m_dimMin.z -= 0.3f; + break; + case MI_DINGHY: + m_dimMax.y *= 1.3f; + m_dimMin.y *= 0.9f; + m_dimMin.z -= 0.2f; + break; + case MI_MARQUIS: + m_dimMax.y *= 1.3f; + m_dimMin.y *= 0.9f; + break; + case MI_JETMAX: + m_dimMin.y *= 0.9f; + break; + } + } + + 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; + tWaterLevel waterPosition; + + // Floater is divided into 3x3 parts. Process and sum each of them + for(x = m_dimMin.x; x <= m_dimMax.x; x += m_step.x){ + 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 = 1.0f; + if(waterPosition != FLOATER_ABOVE_WATER) + SimpleSumBuoyancyData(waterLevel, waterPosition); + } + } + + 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; + 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; + } +} + +// Same as above but also get normal +void +cBuoyancy::FindWaterLevelNorm(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition, CVector *normal) +{ + *waterPosition = FLOATER_IN_WATER; + 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_dimMin.z) + *normal = CWaterLevel::GetWaterNormal(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y); + 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/miami/vehicles/Floater.h b/src/miami/vehicles/Floater.h new file mode 100644 index 00000000..91ab70ae --- /dev/null +++ b/src/miami/vehicles/Floater.h @@ -0,0 +1,47 @@ +#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); + bool ProcessBuoyancyBoat(CVehicle *phys, float buoyancy, CVector *point, CVector *impulse, bool bNoTurnForce); + void PreCalcSetup(CPhysical *phys, float buoyancy); + void SimpleCalcBuoyancy(void); + float SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition); + void FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition); + void FindWaterLevelNorm(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition, CVector *normal); + bool CalcBuoyancyForce(CPhysical *phys, CVector *impulse, CVector *point); +}; +extern cBuoyancy mod_Buoyancy; diff --git a/src/miami/vehicles/HandlingMgr.cpp b/src/miami/vehicles/HandlingMgr.cpp new file mode 100644 index 00000000..8438c5c9 --- /dev/null +++ b/src/miami/vehicles/HandlingMgr.cpp @@ -0,0 +1,434 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "Physical.h" +#include "HandlingMgr.h" + +cHandlingDataMgr mod_HandlingManager; + +const char *HandlingFilename = "HANDLING.CFG"; + +const char VehicleNames[NUMHANDLINGS][14] = { + "LANDSTAL", + "IDAHO", + "STINGER", + "LINERUN", + "PEREN", + "SENTINEL", + "PATRIOT", + "FIRETRUK", + "TRASH", + "STRETCH", + "MANANA", + "INFERNUS", + "PONY", + "MULE", + "CHEETAH", + "AMBULAN", + "FBICAR", + "MOONBEAM", + "ESPERANT", + "TAXI", + "KURUMA", + "BOBCAT", + "MRWHOOP", + "BFINJECT", + "POLICE", + "ENFORCER", + "SECURICA", + "BANSHEE", + "BUS", + "RHINO", + "BARRACKS", + "TRAIN", + "HELI", + "DODO", + "COACH", + "CABBIE", + "STALLION", + "RUMPO", + "RCBANDIT", + "MAFIA", + "AIRTRAIN", + "DEADDODO", + "FLATBED", + "YANKEE", + "GOLFCART", + "VOODOO", + "WASHING", + "CUBAN", + "ROMERO", + "PACKER", + "ADMIRAL", + "GANGBUR", + "ZEBRA", + "TOPFUN", + "GLENDALE", + "OCEANIC", + "HERMES", + "SABRE1", + "SABRETUR", + "PHEONIX", + "WALTON", + "REGINA", + "COMET", + "DELUXO", + "BURRITO", + "SPAND", + "BAGGAGE", + "KAUFMAN", + "RANCHER", + "FBIRANCH", + "VIRGO", + "GREENWOO", + "HOTRING", + "SANDKING", + "BLISTAC", + "BOXVILLE", + "BENSON", + "DESPERAD", + "LOVEFIST", + "BLOODRA", + "BLOODRB", + "BIKE", + "MOPED", + "DIRTBIKE", + "ANGEL", + "FREEWAY", + "PREDATOR", + "SPEEDER", + "REEFER", + "RIO", + "SQUALO", + "TROPIC", + "COASTGRD", + "DINGHY", + "MARQUIS", + "CUPBOAT", + "SEAPLANE", + "SPARROW", + "SEASPAR", + "MAVERICK", + "COASTMAV", + "POLMAV", + "HUNTER", + "RCBARON", + "RCGOBLIN", + "RCCOPTER" +}; + +cHandlingDataMgr::cHandlingDataMgr(void) +{ + memset(this, 0, sizeof(*this)); +} + +void +cHandlingDataMgr::Initialise(void) +{ + LoadHandlingData(); + field_0 = 0.1f; + fWheelFriction = 0.9f; + field_8 = 1.0f; + field_C = 0.8f; + field_10 = 0.98f; +} + +void +cHandlingDataMgr::LoadHandlingData(void) +{ + char *start, *end; + char line[201]; // weird value + char delim[4]; // not sure + char *word; + int field, handlingId; + int keepGoing; + tHandlingData *handling; + tFlyingHandlingData *flyingHandling; + tBoatHandlingData *boatHandling; + tBikeHandlingData *bikeHandling; + + CFileMgr::SetDir("DATA"); + CFileMgr::LoadFile(HandlingFilename, work_buff, sizeof(work_buff), "r"); + CFileMgr::SetDir(""); + + start = (char*)work_buff; + end = start+1; + handling = nil; + flyingHandling = nil; + boatHandling = nil; + bikeHandling = nil; + keepGoing = 1; + + while(keepGoing){ + // find end of line + while(*end != '\n') end++; + + // get line + strncpy(line, start, end - start); + line[end - start] = '\0'; + start = end+1; + end = start+1; + + // yeah, this is kinda crappy + if(strcmp(line, ";the end") == 0) + keepGoing = 0; + else if(line[0] != ';'){ + if(line[0] == '!'){ + // Bike data + field = 0; + strcpy(delim, " \t"); + // FIX: game seems to use a do-while loop here + for(word = strtok(line, delim); word; word = strtok(nil, delim)){ + switch(field){ + case 0: break; + case 1: + handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS); + assert(handlingId >= 0 && handlingId < NUMHANDLINGS); + bikeHandling = GetBikePointer(handlingId); + bikeHandling->nIdentifier = (tVehicleType)handlingId; + break; + case 2: bikeHandling->fLeanFwdCOM = atof(word); break; + case 3: bikeHandling->fLeanFwdForce = atof(word); break; + case 4: bikeHandling->fLeanBakCOM = atof(word); break; + case 5: bikeHandling->fLeanBackForce = atof(word); break; + case 6: bikeHandling->fMaxLean = atof(word); break; + case 7: bikeHandling->fFullAnimLean = atof(word); break; + case 8: bikeHandling->fDesLean = atof(word); break; + case 9: bikeHandling->fSpeedSteer = atof(word); break; + case 10: bikeHandling->fSlipSteer = atof(word); break; + case 11: bikeHandling->fNoPlayerCOMz = atof(word); break; + case 12: bikeHandling->fWheelieAng = atof(word); break; + case 13: bikeHandling->fStoppieAng = atof(word); break; + case 14: bikeHandling->fWheelieSteer = atof(word); break; + case 15: bikeHandling->fWheelieStabMult = atof(word); break; + case 16: bikeHandling->fStoppieStabMult = atof(word); break; + } + field++; + } + ConvertBikeDataToGameUnits(bikeHandling); + }else if(line[0] == '$'){ + // Flying data + field = 0; + strcpy(delim, " \t"); + // FIX: game seems to use a do-while loop here + for(word = strtok(line, delim); word; word = strtok(nil, delim)){ + switch(field){ + case 0: break; + case 1: + handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS); + assert(handlingId >= 0 && handlingId < NUMHANDLINGS); + flyingHandling = GetFlyingPointer(handlingId); + flyingHandling->nIdentifier = (tVehicleType)handlingId; + break; + case 2: flyingHandling->fThrust = atof(word); break; + case 3: flyingHandling->fThrustFallOff = atof(word); break; + case 4: flyingHandling->fYaw = atof(word); break; + case 5: flyingHandling->fYawStab = atof(word); break; + case 6: flyingHandling->fSideSlip = atof(word); break; + case 7: flyingHandling->fRoll = atof(word); break; + case 8: flyingHandling->fRollStab = atof(word); break; + case 9: flyingHandling->fPitch = atof(word); break; + case 10: flyingHandling->fPitchStab = atof(word); break; + case 11: flyingHandling->fFormLift = atof(word); break; + case 12: flyingHandling->fAttackLift = atof(word); break; + case 13: flyingHandling->fMoveRes = atof(word); break; + case 14: flyingHandling->vecTurnRes.x = atof(word); break; + case 15: flyingHandling->vecTurnRes.y = atof(word); break; + case 16: flyingHandling->vecTurnRes.z = atof(word); break; + case 17: flyingHandling->vecSpeedRes.x = atof(word); break; + case 18: flyingHandling->vecSpeedRes.y = atof(word); break; + case 19: flyingHandling->vecSpeedRes.z = atof(word); break; + } + field++; + } + }else if(line[0] == '%'){ + // Boat data + field = 0; + strcpy(delim, " \t"); + // FIX: game seems to use a do-while loop here + for(word = strtok(line, delim); word; word = strtok(nil, delim)){ + switch(field){ + case 0: break; + case 1: + handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS); + assert(handlingId >= 0 && handlingId < NUMHANDLINGS); + boatHandling = GetBoatPointer(handlingId); + boatHandling->nIdentifier = (tVehicleType)handlingId; + break; + case 2: boatHandling->fThrustY = atof(word); break; + case 3: boatHandling->fThrustZ = atof(word); break; + case 4: boatHandling->fThrustAppZ = atof(word); break; + case 5: boatHandling->fAqPlaneForce = atof(word); break; + case 6: boatHandling->fAqPlaneLimit = atof(word); break; + case 7: boatHandling->fAqPlaneOffset = atof(word); break; + case 8: boatHandling->fWaveAudioMult = atof(word); break; + case 9: boatHandling->vecMoveRes.x = atof(word); break; + case 10: boatHandling->vecMoveRes.y = atof(word); break; + case 11: boatHandling->vecMoveRes.z = atof(word); break; + case 12: boatHandling->vecTurnRes.x = atof(word); break; + case 13: boatHandling->vecTurnRes.y = atof(word); break; + case 14: boatHandling->vecTurnRes.z = atof(word); break; + case 15: boatHandling->fLook_L_R_BehindCamHeight = atof(word); break; + } + field++; + } + }else{ + field = 0; + strcpy(delim, " \t"); + // FIX: game seems to use a do-while loop here + for(word = strtok(line, delim); word; word = strtok(nil, delim)){ + switch(field){ + case 0: + handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS); + assert(handlingId >= 0 && handlingId < NUMHANDLINGS); + handling = &HandlingData[handlingId]; + handling->nIdentifier = (tVehicleType)handlingId; + break; + case 1: handling->fMass = atof(word); break; + case 2: handling->Dimension.x = atof(word); break; + case 3: handling->Dimension.y = atof(word); break; + case 4: handling->Dimension.z = atof(word); break; + case 5: handling->CentreOfMass.x = atof(word); break; + case 6: handling->CentreOfMass.y = atof(word); break; + case 7: handling->CentreOfMass.z = atof(word); break; + case 8: handling->nPercentSubmerged = atoi(word); break; + case 9: handling->fTractionMultiplier = atof(word); break; + case 10: handling->fTractionLoss = atof(word); break; + case 11: handling->fTractionBias = atof(word); break; + case 12: handling->Transmission.nNumberOfGears = atoi(word); break; + case 13: handling->Transmission.fMaxVelocity = atof(word); break; + case 14: handling->Transmission.fEngineAcceleration = atof(word) * 0.4; break; + case 15: handling->Transmission.nDriveType = word[0]; break; + case 16: handling->Transmission.nEngineType = word[0]; break; + case 17: handling->fBrakeDeceleration = atof(word); break; + case 18: handling->fBrakeBias = atof(word); break; + case 19: handling->bABS = !!atoi(word); break; + case 20: handling->fSteeringLock = atof(word); break; + case 21: handling->fSuspensionForceLevel = atof(word); break; + case 22: handling->fSuspensionDampingLevel = atof(word); break; + case 23: handling->fSeatOffsetDistance = atof(word); break; + case 24: handling->fCollisionDamageMultiplier = atof(word); break; + case 25: handling->nMonetaryValue = atoi(word); break; + case 26: handling->fSuspensionUpperLimit = atof(word); break; + case 27: handling->fSuspensionLowerLimit = atof(word); break; + case 28: handling->fSuspensionBias = atof(word); break; + case 29: handling->fSuspensionAntidiveMultiplier = atof(word); break; + case 30: + sscanf(word, "%x", &handling->Flags); + handling->Transmission.Flags = handling->Flags; + break; + case 31: handling->FrontLights = atoi(word); break; + case 32: handling->RearLights = atoi(word); break; + } + field++; + } + ConvertDataToGameUnits(handling); + } + } + } +} + +int +cHandlingDataMgr::FindExactWord(const char *word, const char *words, int wordLen, int numWords) +{ + int i; + + for(i = 0; i < numWords; i++){ + // BUG: the game does something really stupid here, it's fixed here + if(strncmp(word, words, wordLen) == 0) + return i; + words += wordLen; + } + return numWords; +} + + +void +cHandlingDataMgr::ConvertDataToGameUnits(tHandlingData *handling) +{ + // convert distance to m, time to 1/50s + float velocity, a, b; + + handling->Transmission.fEngineAcceleration *= 1.0f/(50.0f*50.0f); + handling->Transmission.fMaxVelocity *= 1000.0f/(60.0f*60.0f * 50.0f); + handling->fBrakeDeceleration *= 1.0f/(50.0f*50.0f); + handling->fTurnMass = (sq(handling->Dimension.x) + sq(handling->Dimension.y)) * handling->fMass / 12.0f; + if(handling->fTurnMass < 10.0f) + handling->fTurnMass *= 5.0f; + handling->fInvMass = 1.0f/handling->fMass; + handling->fCollisionDamageMultiplier *= 2000.0f/handling->fMass; + handling->fBuoyancy = 100.0f/handling->nPercentSubmerged * GRAVITY*handling->fMass; + + // Don't quite understand this. What seems to be going on is that + // we calculate a drag (air resistance) deceleration for a given velocity and + // find the intersection between that and the max engine acceleration. + // at that point the car cannot accelerate any further and we've found the max velocity. + a = 0.0f; + b = 100.0f; + velocity = handling->Transmission.fMaxVelocity; + while(a < b && velocity > 0.0f){ + velocity -= 0.01f; + // what's the 1/6? + a = handling->Transmission.fEngineAcceleration/6.0f; + // no density or drag coefficient here... + float a_drag = 0.5f*SQR(velocity) * handling->Dimension.x*handling->Dimension.z / handling->fMass; + // can't make sense of this... maybe v - v/(drag + 1) ? but that doesn't make so much sense either + b = -velocity * (1.0f/(a_drag + 1.0f) - 1.0f); + } + + if(handling->nIdentifier == HANDLING_RCBANDIT){ + handling->Transmission.fMaxCruiseVelocity = handling->Transmission.fMaxVelocity; + handling->Transmission.fMaxReverseVelocity = -handling->Transmission.fMaxVelocity; + }else if(handling->nIdentifier >= HANDLING_BIKE && handling->nIdentifier <= HANDLING_FREEWAY){ + handling->Transmission.fMaxCruiseVelocity = velocity; + handling->Transmission.fMaxVelocity = velocity * 1.2f; + handling->Transmission.fMaxReverseVelocity = -0.05f; + }else{ + handling->Transmission.fMaxCruiseVelocity = velocity; + handling->Transmission.fMaxVelocity = velocity * 1.2f; + handling->Transmission.fMaxReverseVelocity = -0.2f; + } + + if(handling->Transmission.nDriveType == '4') + handling->Transmission.fEngineAcceleration /= 4.0f; + else + handling->Transmission.fEngineAcceleration /= 2.0f; + + handling->Transmission.InitGearRatios(); +} + +void +cHandlingDataMgr::ConvertBikeDataToGameUnits(tBikeHandlingData *handling) +{ + handling->fMaxLean = Sin(DEGTORAD(handling->fMaxLean)); + handling->fFullAnimLean = DEGTORAD(handling->fFullAnimLean); + handling->fWheelieAng = Sin(DEGTORAD(handling->fWheelieAng)); + handling->fStoppieAng = Sin(DEGTORAD(handling->fStoppieAng)); +} + +int32 +cHandlingDataMgr::GetHandlingId(const char *name) +{ + int i; + for(i = 0; i < NUMHANDLINGS; i++) + if(strncmp(VehicleNames[i], name, 14) == 0) + break; + return i; +} + +tFlyingHandlingData* +cHandlingDataMgr::GetFlyingPointer(uint8 id) +{ + if(id >= HANDLING_SEAPLANE && id <= HANDLING_RCCOPTER) + return &FlyingHandlingData[id-HANDLING_SEAPLANE]; + return &FlyingHandlingData[0]; +} + +tBoatHandlingData* +cHandlingDataMgr::GetBoatPointer(uint8 id) +{ + if(id >= HANDLING_PREDATOR && id <= HANDLING_SEAPLANE) + return &BoatHandlingData[id-HANDLING_PREDATOR]; + return &BoatHandlingData[0]; +} diff --git a/src/miami/vehicles/HandlingMgr.h b/src/miami/vehicles/HandlingMgr.h new file mode 100644 index 00000000..8d290f7d --- /dev/null +++ b/src/miami/vehicles/HandlingMgr.h @@ -0,0 +1,279 @@ +#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_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_BUS, + HANDLING_RHINO, + HANDLING_BARRACKS, + HANDLING_TRAIN, + HANDLING_HELI, + HANDLING_DODO, + HANDLING_COACH, + HANDLING_CABBIE, + HANDLING_STALLION, + HANDLING_RUMPO, + HANDLING_RCBANDIT, + HANDLING_MAFIA, + HANDLING_AIRTRAIN, + HANDLING_DEADDODO, + HANDLING_FLATBED, + HANDLING_YANKEE, + HANDLING_GOLFCART, + HANDLING_VOODOO, + HANDLING_WASHING, + HANDLING_CUBAN, + HANDLING_ROMERO, + HANDLING_PACKER, + HANDLING_ADMIRAL, + HANDLING_GANGBUR, + HANDLING_ZEBRA, + HANDLING_TOPFUN, + HANDLING_GLENDALE, + HANDLING_OCEANIC, + HANDLING_HERMES, + HANDLING_SABRE1, + HANDLING_SABRETUR, + HANDLING_PHEONIX, + HANDLING_WALTON, + HANDLING_REGINA, + HANDLING_COMET, + HANDLING_DELUXO, + HANDLING_BURRITO, + HANDLING_SPAND, + HANDLING_BAGGAGE, + HANDLING_KAUFMAN, + HANDLING_RANCHER, + HANDLING_FBIRANCH, + HANDLING_VIRGO, + HANDLING_GREENWOO, + HANDLING_HOTRING, + HANDLING_SANDKING, + HANDLING_BLISTAC, + HANDLING_BOXVILLE, + HANDLING_BENSON, + HANDLING_DESPERAD, + HANDLING_LOVEFIST, + HANDLING_BLOODRA, + HANDLING_BLOODRB, + + HANDLING_BIKE, + HANDLING_MOPED, + HANDLING_DIRTBIKE, + HANDLING_ANGEL, + HANDLING_FREEWAY, + + HANDLING_PREDATOR, + HANDLING_SPEEDER, + HANDLING_REEFER, + HANDLING_RIO, + HANDLING_SQUALO, + HANDLING_TROPIC, + HANDLING_COASTGRD, + HANDLING_DINGHY, + HANDLING_MARQUIS, + HANDLING_CUPBOAT, + HANDLING_SEAPLANE, // both boat and plane! + HANDLING_SPARROW, + HANDLING_SEASPAR, + HANDLING_MAVERICK, + HANDLING_COASTMAV, + HANDLING_POLMAV, + HANDLING_HUNTER, + HANDLING_RCBARON, + HANDLING_RCGOBLIN, + HANDLING_RCCOPTER, + + NUMHANDLINGS, + + NUMBIKEHANDLINGS = HANDLING_FREEWAY+1 - HANDLING_BIKE, + NUMFLYINGHANDLINGS = HANDLING_RCCOPTER+1 - HANDLING_SEAPLANE, + NUMBOATHANDLINGS = HANDLING_SEAPLANE+1 - HANDLING_PREDATOR, +}; + +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, + HANDLING_IS_BIKE = 0x10000, + HANDLING_IS_HELI = 0x20000, + HANDLING_IS_PLANE = 0x40000, + HANDLING_IS_BOAT = 0x80000, + HANDLING_NO_EXHAUST = 0x100000, + HANDLING_REARWHEEL_1ST = 0x200000, + HANDLING_HANDBRAKE_TYRE = 0x400000, + HANDLING_SIT_IN_BOAT = 0x800000, + HANDLING_FAT_REARW = 0x1000000, + HANDLING_NARROW_FRONTW = 0x2000000, + HANDLING_GOOD_INSAND = 0x4000000, + HANDLING_UNKNOWN = 0x8000000, // something for helis and planes +}; + +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 fSuspensionAntidiveMultiplier; + float fCollisionDamageMultiplier; + uint32 Flags; + float fSeatOffsetDistance; + int32 nMonetaryValue; + int8 FrontLights; + int8 RearLights; +}; + +struct tBikeHandlingData +{ + tVehicleType nIdentifier; + float fLeanFwdCOM; + float fLeanFwdForce; + float fLeanBakCOM; + float fLeanBackForce; + float fMaxLean; + float fFullAnimLean; + float fDesLean; + float fSpeedSteer; + float fSlipSteer; + float fNoPlayerCOMz; + float fWheelieAng; + float fStoppieAng; + float fWheelieSteer; + float fWheelieStabMult; + float fStoppieStabMult; +}; + +struct tBoatHandlingData +{ + tVehicleType nIdentifier; + float fThrustY; + float fThrustZ; + float fThrustAppZ; + float fAqPlaneForce; + float fAqPlaneLimit; + float fAqPlaneOffset; + float fWaveAudioMult; + float fLook_L_R_BehindCamHeight; + CVector vecMoveRes; + CVector vecTurnRes; +}; + +struct tFlyingHandlingData +{ + tVehicleType nIdentifier; + float fThrust; + float fThrustFallOff; + float fYaw; + float fYawStab; + float fSideSlip; + float fRoll; + float fRollStab; + float fPitch; + float fPitchStab; + float fFormLift; + float fAttackLift; + float fMoveRes; + CVector vecTurnRes; + CVector vecSpeedRes; +}; + +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]; + tBikeHandlingData BikeHandlingData[NUMBIKEHANDLINGS]; + tFlyingHandlingData FlyingHandlingData[NUMFLYINGHANDLINGS]; + tBoatHandlingData BoatHandlingData[NUMBOATHANDLINGS]; + +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 ConvertBikeDataToGameUnits(tBikeHandlingData *handling); + int32 GetHandlingId(const char *name); + tHandlingData *GetHandlingData(tVehicleType id) { return &HandlingData[id]; } + tBikeHandlingData *GetBikePointer(uint8 id) { return &BikeHandlingData[id-HANDLING_BIKE]; } + tFlyingHandlingData *GetFlyingPointer(uint8 id); + tBoatHandlingData *GetBoatPointer(uint8 id); + bool HasRearWheelDrive(tVehicleType id) { return HandlingData[id].Transmission.nDriveType != 'F'; } + bool HasFrontWheelDrive(tVehicleType id) { return HandlingData[id].Transmission.nDriveType != 'R'; } +}; +extern cHandlingDataMgr mod_HandlingManager; diff --git a/src/miami/vehicles/Heli.cpp b/src/miami/vehicles/Heli.cpp new file mode 100644 index 00000000..f51c8481 --- /dev/null +++ b/src/miami/vehicles/Heli.cpp @@ -0,0 +1,1049 @@ +#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 "WindModifiers.h" +#include "Timecycle.h" +#include "TempColModels.h" +#include "World.h" +#include "WaterLevel.h" +#include "Population.h" +#include "PlayerPed.h" +#include "CopPed.h" +#include "Wanted.h" +#include "DMAudio.h" +#include "Object.h" +#include "HandlingMgr.h" +#include "Ropes.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; +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_numSwat = 4; + + 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; + + m_aSwatState[0] = m_aSwatState[1] = m_aSwatState[2] = m_aSwatState[3] = 0; + + // 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; + + CWindModifiers::RegisterOne(GetPosition(), 1); + + // 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; + } + if(m_numSwat) + SendDownSwat(); + break; + 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; + } + break; + } + + // 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; + + 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(); + } + } + } + } + + // Process ropes + for(i = 0; i < 4; i++){ + if(m_aSwatState[i] == 0) + continue; + + m_aSwatState[i]--; + CRopes::RegisterRope((uintptr)this + i, GetMatrix()*FindSwatPositionRelativeToHeli(i), false); + if(m_aSwatState[i] == 0){ + CVector speed = Multiply3x3(GetMatrix(), 0.05f*FindSwatPositionRelativeToHeli(i)); + speed.z = 0.0f; + CRopes::SetSpeedOfTopNode((uintptr)this + i, speed); + } + } + + RemoveAndAdd(); + bIsInSafePosition = true; + GetMatrix().UpdateRW(); + UpdateRwFrame(); +} + +void +CHeli::PreRender(void) +{ + float radius = (GetPosition().z - FindPlayerCoors().z - 10.0f - 1.0f) * 0.3f + 10.0f; + HeliDustGenerate(this, radius, FindPlayerCoors().z, Max(16.0f - 4.0f*CTimer::GetTimeStep(), 2.0f)); + CShadows::StoreShadowForVehicle(this, VEH_SHD_TYPE_HELI); +} + +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, nil, false); + + 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); + obj->bDontStream = true; + + // 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; +} + +CVector +CHeli::FindSwatPositionRelativeToHeli(int n) +{ + switch(n){ + case 0: return CVector(-1.2f, -1.0f, -0.5f); + case 1: return CVector( 1.2f, -1.0f, -0.5f); + case 2: return CVector(-1.2f, 1.0f, -0.5f); + case 3: return CVector( 1.2f, 1.0f, -0.5f); + default: return CVector(0.0f, 0.0f, 0.0f); + } +} + +bool +CHeli::SendDownSwat(void) +{ + if(m_numSwat == 0 || !CStreaming::HasModelLoaded(MI_SWAT) || + CGeneral::GetRandomNumber() & 0x7F || (GetPosition() - FindPlayerCoors()).Magnitude() > 50.0f) + return false; + + CMatrix mat(GetMatrix()); + CVector pos = Multiply3x3(mat, FindSwatPositionRelativeToHeli(m_numSwat-1)) + GetPosition(); + + float groundZ = CWorld::FindGroundZFor3DCoord(pos.x, pos.y, pos.z, nil); + if(Abs(FindPlayerCoors().z - groundZ) < 2.5f && CRopes::RegisterRope((uintptr)this + m_numSwat-1, pos, false)){ + CCopPed *swat = (CCopPed*)CPopulation::AddPed(PEDTYPE_COP, COP_HELI_SWAT, pos); + swat->bUsesCollision = false; + swat->m_pRopeEntity = this; + RegisterReference(&swat->m_pRopeEntity); + m_numSwat--; + swat->m_nRopeID = (uintptr)this + m_numSwat; + m_aSwatState[m_numSwat] = 255; + CAnimManager::BlendAnimation(swat->GetClump(), ASSOCGRP_STD, ANIM_STD_ABSEIL, 4.0f); + return true; + } + return false; +} + + +void +CHeli::InitHelis(void) +{ + int i; + + NumRandomHelis = 0; + TestForNewRandomHelisTimer = 0; + CatalinaHeliOn = false; + ScriptHeliOn = false; + for(i = 0; i < NUM_HELIS; i++) + pHelis[i] = nil; + + ((CVehicleModelInfo*)CModelInfo::GetModelInfo(MI_CHOPPER))->SetColModel(&CTempColModels::ms_colModelPed1); +} + +CHeli* +CHeli::GenerateHeli(bool catalina) +{ + CHeli *heli; + CVector heliPos; + int i; + + if(catalina) + assert(0 && "can't create catalina's heli"); + 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-400.0f || heliPos.x > 2000.0f-400.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(CCullZones::PlayerNoRain() || CGame::IsInInterior()) + numHelisRequired = 0; + 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; + } + + // 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, 224, 224, 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&7], 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::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_HELI2, 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; +} + +bool +CHeli::TestSniperCollision(CVector *line0, CVector *line1) +{ + int i; + bool hit = false; + + for(i = 0; i < NUM_HELIS; i++){ + if(pHelis[i] && !pHelis[i]->bBulletProof) { + CVector pilotPos = pHelis[i]->GetMatrix() * CVector(-0.43f, 1.49f, 1.5f); + if(CCollision::DistToLine(line0, line1, &pilotPos) < 0.8f){ + pHelis[i]->m_fAngularSpeed = CGeneral::GetRandomTrueFalse() ? 0.05f : -0.05f; + pHelis[i]->m_heliStatus = HELI_STATUS_SHOT_DOWN; + pHelis[i]->m_nExplosionTimer = CTimer::GetTimeInMilliseconds() + 9999999; + pHelis[i]->m_numSwat = 0; + + 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/miami/vehicles/Heli.h b/src/miami/vehicles/Heli.h new file mode 100644 index 00000000..c5ae08e2 --- /dev/null +++ b/src/miami/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, // TODO 2 in VC + NUM_HELIS +}; + +enum +{ + HELI_TYPE_RANDOM, + HELI_TYPE_SCRIPT, + HELI_TYPE_CATALINA, +}; + + +class CHeli : public CVehicle +{ +public: + 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; + int8 m_numSwat; + uint8 m_aSwatState[4]; + 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 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); + CVector FindSwatPositionRelativeToHeli(int n); + bool SendDownSwat(void); + + 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 bool TestSniperCollision(CVector *line0, CVector *line1); + + 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); +}; diff --git a/src/miami/vehicles/Plane.cpp b/src/miami/vehicles/Plane.cpp new file mode 100644 index 00000000..0b40ca7e --- /dev/null +++ b/src/miami/vehicles/Plane.cpp @@ -0,0 +1,1069 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "CutsceneMgr.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 "Fluff.h" +#include "World.h" +#include "HandlingMgr.h" +#include "Heli.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[5]; +float PlanePath3Position[4]; +float PlanePath2Speed[5]; +float PlanePath3Speed[4]; + + +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; + m_bTempPlane = false; + + SetStatus(STATUS_PLANE); + bIsBIGBuilding = true; + m_level = LEVEL_GENERIC; + + m_isFarAway = false; +#ifdef CPLANE_ROTORS + m_fRotorRotation = 0.0f; +#endif +} + +CPlane::~CPlane() +{ + DeleteRwObject(); +} + +void +CPlane::SetModelIndex(uint32 id) +{ + CVehicle::SetModelIndex(id); +#ifdef CPLANE_ROTORS + int i; + for(i = 0; i < NUM_PLANE_NODES; i++) + m_aPlaneNodes[i] = nil; + if(GetModelIndex() == MI_CHOPPER){ + // This is surprisingly annoying... + RwFrame *heliNodes[NUM_HELI_NODES]; + for(i = 0; i < NUM_HELI_NODES; i++) + heliNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), heliNodes); + m_aPlaneNodes[PLANE_TOPROTOR] = heliNodes[HELI_TOPROTOR]; + m_aPlaneNodes[PLANE_BACKROTOR] = heliNodes[HELI_BACKROTOR]; + }else + CClumpModelInfo::FillFrameArray(GetClump(), m_aPlaneNodes); +#endif +} + +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; + + if(CReplay::IsPlayingBack()) + return; + + if(GetModelIndex() == MI_AIRTRAIN){ + if(GetPosition().z > 100.0f) + CPlaneTrails::RegisterPoint(GetPosition(), m_nPlaneId); + }else if(GetModelIndex() == MI_DEADDODO) + CPlaneBanners::RegisterPoint(GetPosition(), m_nPlaneId); + + // 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(GetModelIndex() == MI_CHOPPER){ + planePathPosition = PlanePath3Position[m_nPlaneId]; + totalLengthOfFlightPath = TotalLengthOfFlightPath3; + pathNodes = pPath3Nodes; + planePathSpeed = PlanePath3Speed[m_nPlaneId]; + numPathNodes = NumPath3Nodes; + }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); + } + +#ifdef CPLANE_ROTORS + CMatrix mat; + CVector pos; + m_fRotorRotation += 3.14f/6.5f; + if(m_fRotorRotation > 6.28f) + m_fRotorRotation -= 6.28f; + + if(m_aPlaneNodes[PLANE_TOPROTOR]){ + mat.Attach(RwFrameGetMatrix(m_aPlaneNodes[PLANE_TOPROTOR])); + pos = mat.GetPosition(); + mat.SetRotateZ(m_fRotorRotation); + mat.Translate(pos); + mat.UpdateRW(); + } + if(m_aPlaneNodes[PLANE_BACKROTOR]){ + mat.Attach(RwFrameGetMatrix(m_aPlaneNodes[PLANE_BACKROTOR])); + pos = mat.GetPosition(); + mat.SetRotateX(m_fRotorRotation); + mat.Translate(pos); + mat.UpdateRW(); + } +#endif +} + +void +CPlane::Render(void) +{ + if(!CCutsceneMgr::IsRunning()) + 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 + for(i = 0; i < NumPathNodes; i++){ + if(pPathNodes[i].p.z < 14.0f){ + pPathNodes[i].p.z = 14.0f; + 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-500.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/500.0f; + time += 500.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f); + position += 500.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/500.0f; + time += 500.0f/((CRUISE_SPEED+TAXI_SPEED)/2.0f); + position += 500.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; + } + + // Heli + if(pPath3Nodes == nil){ + pPath3Nodes = LoadPath("data\\paths\\flight3.dat", NumPath3Nodes, TotalLengthOfFlightPath3, false); + TotalDurationOfFlightPath3 = TotalLengthOfFlightPath3/CRUISE_SPEED; + } + + CStreaming::LoadAllRequestedModels(false); + CStreaming::RequestModel(MI_AIRTRAIN, 0); + CStreaming::LoadAllRequestedModels(false); + + // NB: 3 hardcoded also in CPlaneTrails + 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); + } +} + +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++){ + for(lp = 0; work_buff[bp] != '\n' && work_buff[bp] != '\0'; 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; +} + +int32 LastTimeInPlane, LastTimeNotInPlane; +bool bCesnasActivated; +bool bHelisActivated; + +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/5) & 0x7FFFF)*t; + PlanePath2Position[2] = CRUISE_SPEED * ((time + 0x80000/5*2) & 0x7FFFF)*t; + PlanePath2Position[3] = CRUISE_SPEED * ((time + 0x80000/5*3) & 0x7FFFF)*t; + PlanePath2Position[4] = CRUISE_SPEED * ((time + 0x80000/5*4) & 0x7FFFF)*t; + PlanePath2Speed[0] = CRUISE_SPEED*t; + PlanePath2Speed[1] = CRUISE_SPEED*t; + PlanePath2Speed[2] = CRUISE_SPEED*t; + PlanePath2Speed[3] = CRUISE_SPEED*t; + PlanePath2Speed[4] = CRUISE_SPEED*t; + + t = TotalDurationOfFlightPath3/0x80000; + PlanePath3Position[0] = CRUISE_SPEED * (time & 0x7FFFF)*t; + PlanePath3Position[1] = CRUISE_SPEED * ((time + 0x80000/4) & 0x7FFFF)*t; + PlanePath3Position[2] = CRUISE_SPEED * ((time + 0x80000/4*2) & 0x7FFFF)*t; + PlanePath3Position[3] = CRUISE_SPEED * ((time + 0x80000/4*3) & 0x7FFFF)*t; + PlanePath3Speed[0] = CRUISE_SPEED*t; + PlanePath3Speed[1] = CRUISE_SPEED*t; + PlanePath3Speed[2] = CRUISE_SPEED*t; + PlanePath3Speed[3] = CRUISE_SPEED*t; + + if(FindPlayerVehicle() && (FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_HELI || + FindPlayerVehicle()->GetVehicleAppearance() == VEHICLE_APPEARANCE_PLANE)) + LastTimeInPlane = CTimer::GetTimeInMilliseconds(); + else + LastTimeNotInPlane = CTimer::GetTimeInMilliseconds(); + + if(CTimer::GetTimeInMilliseconds() - LastTimeNotInPlane > 10000){ + if(!bCesnasActivated){ + if(CStreaming::HasModelLoaded(MI_DEADDODO)){ + for(i = 0; i < 5; 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; + plane->m_bTempPlane = true; + CWorld::Add(plane); + } + bCesnasActivated = true; + }else + CStreaming::RequestModel(MI_DEADDODO, 0); + } + + if(!bHelisActivated){ + if(CStreaming::HasModelLoaded(MI_CHOPPER)){ + for(i = 0; i < 4; i++){ + CPlane *plane = new CPlane(MI_CHOPPER, 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; + plane->m_bTempPlane = true; + CWorld::Add(plane); + } + bHelisActivated = true; + }else + CStreaming::RequestModel(MI_CHOPPER, 0); + } + }else if(CTimer::GetTimeInMilliseconds() - LastTimeInPlane > 10000) + RemoveTemporaryPlanes(); +} + +void +CPlane::RemoveTemporaryPlanes(void) +{ + int i; + if(!bHelisActivated && !bCesnasActivated) + return; + + i = CPools::GetVehiclePool()->GetSize(); + while(--i >= 0){ + CPlane *plane = (CPlane*)CPools::GetVehiclePool()->GetSlot(i); + if(plane && plane->IsPlane() && plane->m_bTempPlane){ + CWorld::Remove(plane); + delete plane; + } + } + bCesnasActivated = false; + bHelisActivated = false; +} + +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; +} + +// unused +// 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"); +} + +// unused +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"); +} + +// all unused +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; } + +void +CPlane::Load(void) +{ + RemoveTemporaryPlanes(); +} + +void +CPlane::Save(void) +{ + RemoveTemporaryPlanes(); +} diff --git a/src/miami/vehicles/Plane.h b/src/miami/vehicles/Plane.h new file mode 100644 index 00000000..c8f02048 --- /dev/null +++ b/src/miami/vehicles/Plane.h @@ -0,0 +1,81 @@ +#pragma once + +#include "Vehicle.h" + +enum ePlaneNodes +{ +#ifdef CPLANE_ROTORS + // for heli + PLANE_TOPROTOR, + PLANE_BACKROTOR, +#endif + 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: +#ifdef CPLANE_ROTORS + RwFrame *m_aPlaneNodes[NUM_PLANE_NODES]; + float m_fRotorRotation; +#endif + int16 m_nPlaneId; + int16 m_isFarAway; + int16 m_nCurPathNode; + float m_fSpeed; + uint32 m_nFrameWhenHit; + bool m_bHasBeenHit; + bool m_bIsDrugRunCesna; + bool m_bIsDropOffCesna; + bool m_bTempPlane; + + 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 RemoveTemporaryPlanes(void); + 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); + static void Load(void); + static void Save(void); +}; + +extern float LandingPoint; +extern float TakeOffPoint; +extern float PlanePathPosition[3]; diff --git a/src/miami/vehicles/Train.cpp b/src/miami/vehicles/Train.cpp new file mode 100644 index 00000000..3a04b614 --- /dev/null +++ b/src/miami/vehicles/Train.cpp @@ -0,0 +1,766 @@ +#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) +{ +#ifdef GTA_TRAIN + 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 +#else + assert(0 && "No trains in this game"); +#endif +} + +void +CTrain::SetModelIndex(uint32 id) +{ +#ifdef GTA_TRAIN + int i; + + CVehicle::SetModelIndex(id); + for(i = 0; i < NUM_TRAIN_NODES; i++) + m_aTrainNodes[i] = nil; + CClumpModelInfo::FillFrameArray(GetClump(), m_aTrainNodes); +#endif +} + +void +CTrain::ProcessControl(void) +{ +#ifdef GTA_TRAIN + 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]); + } + } +#endif // GTA_TRAIN +} + +void +CTrain::PreRender(void) +{ +#ifdef GTA_TRAIN + 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); + } +#endif +} + +void +CTrain::Render(void) +{ +#ifdef GTA_TRAIN + CEntity::Render(); +#endif +} + +void +CTrain::TrainHitStuff(CPtrList &list) +{ +#ifdef GTA_TRAIN + 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; + } +#endif +} + +void +CTrain::AddPassenger(CPed *ped) +{ +#ifdef GTA_TRAIN + 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; + } + } +#endif +} + +void +CTrain::OpenTrainDoor(float ratio) +{ +#ifdef GTA_TRAIN + 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(); +#endif +} + + + +void +CTrain::InitTrains(void) +{ +#ifdef GTA_TRAIN + 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; + } +#endif +} + +void +CTrain::Shutdown(void) +{ +#ifdef GTA_TRAIN + delete[] pTrackNodes; + delete[] pTrackNodes_S; + pTrackNodes = nil; + pTrackNodes_S = nil; +#endif +} + +void +CTrain::ReadAndInterpretTrackFile(Const char *filename, CTrainNode **nodes, int16 *numNodes, int32 numStations, float *stationDists, + float *totalLength, float *totalDuration, CTrainInterpolationLine *interpLines, bool rightRail) +{ +#ifdef GTA_TRAIN + 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; +#endif +} + +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) +{ +#ifdef GTA_TRAIN + 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; + } + } + } + } +#endif +} + +void +CTrain::UpdateTrains(void) +{ +#ifdef GTA_TRAIN + 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; + } +#endif +} diff --git a/src/miami/vehicles/Train.h b/src/miami/vehicles/Train.h new file mode 100644 index 00000000..57cd28de --- /dev/null +++ b/src/miami/vehicles/Train.h @@ -0,0 +1,84 @@ +#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); +}; diff --git a/src/miami/vehicles/Transmission.cpp b/src/miami/vehicles/Transmission.cpp new file mode 100644 index 00000000..1aeabfe0 --- /dev/null +++ b/src/miami/vehicles/Transmission.cpp @@ -0,0 +1,130 @@ +#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++; + return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false); + } + }else if(fVelocity < pGearRatio->fShiftDownVelocity && gear != 0){ + if(gear != 1 || gasPedal < 0.0f){ + gear--; + return CalculateDriveAcceleration(gasPedal, gear, time, fVelocity, false); + } + } + + 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) ? 2.0f : 1.6f; + else if(gear == 2) + accelMul = 1.3f; + else + accelMul = 1.0f; + }else if(Flags & HANDLING_1G_BOOST && gear == 1){ + accelMul = 2.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/miami/vehicles/Transmission.h b/src/miami/vehicles/Transmission.h new file mode 100644 index 00000000..a3d15513 --- /dev/null +++ b/src/miami/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/miami/vehicles/Vehicle.cpp b/src/miami/vehicles/Vehicle.cpp new file mode 100644 index 00000000..d1054191 --- /dev/null +++ b/src/miami/vehicles/Vehicle.cpp @@ -0,0 +1,2518 @@ +#include "common.h" +#include "main.h" + +#include "General.h" +#include "Timer.h" +#include "Pad.h" +#include "Vehicle.h" +#include "Bike.h" +#include "Automobile.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 "VisibilityPlugins.h" +#include "DMAudio.h" +#include "Radar.h" +#include "Fire.h" +#include "Darkel.h" +#include "Streaming.h" +#include "Camera.h" +#include "Stats.h" +#include "Garages.h" +#include "Wanted.h" +#include "SurfaceTable.h" +#include "Particle.h" +#include "WaterLevel.h" +#include "Timecycle.h" +#include "Weather.h" +#include "Coronas.h" +#include "SaveBuf.h" + +bool CVehicle::bWheelsOnlyCheat; +bool CVehicle::bAllDodosCheat; +bool CVehicle::bCheat3; +bool CVehicle::bCheat4; +bool CVehicle::bCheat5; +bool CVehicle::bCheat8; +bool CVehicle::bCheat9; +bool CVehicle::bCheat10; +bool CVehicle::bHoverCheat; +bool CVehicle::bAllTaxisHaveNitro; +bool CVehicle::m_bDisableMouseSteering = true; +bool CVehicle::bDisableRemoteDetonation; +bool CVehicle::bDisableRemoteDetonationOnContact; +#ifndef MASTER +bool CVehicle::m_bDisplayHandlingInfo; +#endif + +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; + m_nRouteSeed = 0; + 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_nCarHornDelay = 0; + bPartOfConvoy = false; + bHeliMinimumTilt = false; + bAudioChangingGear = false; + bIsDrowning = false; + bTyresDontBurst = false; + bCreatedAsPoliceVehicle = false; + bRestingOnPhysical = false; + bParking = false; + bCanPark = CGeneral::GetRandomNumberInRange(0.0f, 1.0f) < 0.0f; // never true. probably doesn't work very well + bIsVan = false; + bIsBus = false; + bIsBig = false; + bLowVehicle = false; + + m_bombType = CARBOMB_NONE; + bDriverLastFrame = false; + m_pBombRigger = nil; + + m_nSetPieceExtendedRangeTime = 0; + m_nAlarmState = 0; + m_nDoorLock = CARLOCK_UNLOCKED; + m_nLastWeaponDamage = -1; + m_pLastDamageEntity = nil; + 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() % NUM_RADIOS; + switch(GetModelIndex()){ + case MI_HUNTER: + case MI_ANGEL: + case MI_FREEWAY: + m_nRadioStation = V_ROCK; + break; + case MI_RCBARON: + case MI_RCBANDIT: + case MI_RCRAIDER: + case MI_RCGOBLIN: + case MI_TOPFUN: + case MI_CADDY: + case MI_BAGGAGE: + m_nRadioStation = RADIO_OFF; + break; + default: + m_nRadioStation = CGeneral::GetRandomNumber() % NUM_RADIOS; + break; + } + 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; + AutoPilot.m_nSwitchDistance = 20; +} + +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(lighting != 1.0f){ + SetAmbientAndDirectionalColours(lighting); + return true; + } + } + + return false; +} + +void +CVehicle::RemoveLighting(bool reset) +{ + CRenderer::RemoveVehiclePedLights(this, reset); +} + +bool +CVehicle::IsClearToDriveAway(void) +{ + CColPoint point; + float length = GetColModel()->boundingBox.GetSize().y; + CEntity *ent = nil; + CVector front = GetForward() * (length*0.5f + 3.0f); + return !CWorld::ProcessLineOfSight(GetPosition() + front, GetPosition(), + point, ent, true, true, false, false, false, true, true) || + ent == this; +} + +float +CVehicle::GetHeightAboveRoad(void) +{ + return -1.0f * GetColModel()->boundingBox.min.z; +} + +void +CVehicle::FlyingControl(eFlightModel flightModel) +{ + if(pFlyingHandling == nil) + return; + + 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: + case FLIGHT_MODEL_PLANE_UNUSED: + case FLIGHT_MODEL_PLANE: + { + float fSteerLR = CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f; + float fSteerUD = CPad::GetPad(0)->GetSteeringUpDown() / 128.0f; + float fGunUD = Abs(CPad::GetPad(0)->GetCarGunUpDown()); +#ifdef FREE_CAM + if(!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController)) +#endif + if(fGunUD > 1.0f) + fSteerUD = -CPad::GetPad(0)->GetCarGunUpDown() / 128.0f; + + float fSteerAngle = Atan2(fSteerUD, fSteerLR); + float fSteerMult = 1.0f; + if(fSteerAngle > -PI/4.0f && fSteerAngle <= PI/4.0f) + fSteerMult = 1.0f/Cos(fSteerAngle); + else if(fSteerAngle > PI/4.0f && fSteerAngle <= PI*3.0f/4.0f) + fSteerMult = 1.0f/Cos(fSteerAngle - HALFPI); + else if(fSteerAngle > PI*3.0f/4.0f) + fSteerMult = 1.0f/Cos(fSteerAngle - PI); + else if(fSteerAngle <= -PI*3.0f/4.0f) + fSteerMult = 1.0f/Cos(fSteerAngle + PI); + else if(fSteerAngle > -PI*3.0f/4.0f && fSteerAngle < -PI/4.0f) + fSteerMult = 1.0f/Cos(fSteerAngle + HALFPI); + + fSteerLR *= fSteerMult; + fSteerUD *= -fSteerMult; + + // thrust + float fThrust = pFlyingHandling->fThrust; + float fThrustFallOff = pFlyingHandling->fThrustFallOff; + float fThrustFallOffBack = pFlyingHandling->fThrustFallOff * 8.0f; +#ifdef BETTER_ALLCARSAREDODO_CHEAT + if (bAllDodosCheat && !IsRealPlane()) { + fThrust = pHandling->Transmission.fEngineAcceleration + * (pHandling->Transmission.nDriveType == '4' ? 4.0f : 2.0f); + fThrust = 5.0f * Max(fThrust, pFlyingHandling->fThrust); //tweak: (cars engines too weak to thrust car on air) + fThrustFallOff = Min(0.7f / pHandling->Transmission.fMaxVelocity, fThrustFallOff); //tweak: (use 0.7 instead of 1.0 to make cars 30% faster) + fThrustFallOffBack = -1.0f / pHandling->Transmission.fMaxReverseVelocity; + } +#endif + float fForwSpeed = DotProduct(GetMoveSpeed(), GetForward()); + CVector vecTail = GetColModel()->boundingBox.min.y * GetForward(); + float fPedalState = (CPad::GetPad(0)->GetAccelerate() - CPad::GetPad(0)->GetBrake()) / 255.0f; + float fThrustAccel; + if(fForwSpeed > 0.0f || fPedalState > 0.0f) + fThrustAccel = (fPedalState - fThrustFallOff * fForwSpeed) * fThrust; + else + fThrustAccel = Min(fPedalState - fThrustFallOffBack * fForwSpeed, 0.0f) * fThrust; + if(flightModel == FLIGHT_MODEL_PLANE_UNUSED) + fThrustAccel *= 0.3f; + else if(flightModel == FLIGHT_MODEL_PLANE) + fThrustAccel *= 0.1f; + ApplyMoveForce(fThrustAccel * GetForward() * m_fMass * CTimer::GetTimeStep()); + + // left/right + float fSideSpeed = -DotProduct(GetMoveSpeed(), GetRight()); + float fSideSlipAccel = pFlyingHandling->fSideSlip * fSideSpeed * Abs(fSideSpeed); + ApplyMoveForce(m_fMass * GetRight() * fSideSlipAccel * CTimer::GetTimeStep()); + + float fYaw = -DotProduct(GetSpeed(vecTail), GetRight()); + float fYawAccel = pFlyingHandling->fYawStab * fYaw * Abs(fYaw) + pFlyingHandling->fYaw * 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) * pFlyingHandling->fRoll; + } + else + fRollAccel = fSteerLR * pFlyingHandling->fRoll; + 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 = pFlyingHandling->fRollStab * fStabiliseDirection * (1.0f - DotProduct(GetRight(), vecStabilise)) * (1.0f - Abs(GetForward().z)); + ApplyTurnForce(fStabiliseSpeed * m_fTurnMass * GetRight(), GetUp()); // no CTimer::GetTimeStep(), is it right? + + // up/down + float fTail = -DotProduct(GetSpeed(vecTail), GetUp()); + float fPitchAccel = pFlyingHandling->fPitchStab * fTail * Abs(fTail) + pFlyingHandling->fPitch * fSteerUD * fForwSpeed; + ApplyTurnForce(fPitchAccel * m_fTurnMass * GetUp() * CTimer::GetTimeStep(), vecTail); + + float fLift = DotProduct(GetMoveSpeed(), GetUp()) / Max(0.01f, GetMoveSpeed().Magnitude()); //accel*angle + float fLiftAccel = (pFlyingHandling->fFormLift - pFlyingHandling->fAttackLift * 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; +#ifdef BETTER_ALLCARSAREDODO_CHEAT + else if(bAllDodosCheat && GetPosition().z > 170.0f) + fLiftImpulse = CTimer::GetTimeStep() * 0.9f * GRAVITY * m_fMass; +#endif + } + ApplyMoveForce(fLiftImpulse * GetUp()); + + CVector vecResistance; + vecResistance = pFlyingHandling->vecTurnRes; + 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 / (pFlyingHandling->vecSpeedRes.y * 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)); + + + float fMoveSpeed = m_vecMoveSpeed.MagnitudeSqr(); + if(fMoveSpeed > SQR(1.5f)) + m_vecMoveSpeed *= 1.5f/Sqrt(fMoveSpeed); + + float fTurnSpeed = m_vecTurnSpeed.MagnitudeSqr(); + if(fTurnSpeed > SQR(0.2f)) + m_vecTurnSpeed *= 0.2f/Sqrt(fTurnSpeed); + break; + } + case FLIGHT_MODEL_RCHELI: + case FLIGHT_MODEL_HELI: + { +#ifdef RESTORE_ALLCARSHELI_CHEAT + tFlyingHandlingData* flyingHandling = bAllCarCheat && !IsRealHeli() ? mod_HandlingManager.GetFlyingPointer(HANDLING_MAVERICK) : pFlyingHandling; +#else + tFlyingHandlingData* flyingHandling = pFlyingHandling; +#endif + float rm = Pow(flyingHandling->fMoveRes, CTimer::GetTimeStep()); + m_vecMoveSpeed *= rm; + if (GetStatus() != STATUS_PLAYER && GetStatus() != STATUS_PLAYER_REMOTE) + return; + float fUpSpeed = DotProduct(m_vecMoveSpeed, GetUp()); + float fThrust = (CPad::GetPad(0)->GetAccelerate() - CPad::GetPad(0)->GetBrake()) / 255.0f; + if(fThrust < 0.0f) + fThrust *= 2.0f; + if(flightModel == FLIGHT_MODEL_RCHELI){ + fThrust = flyingHandling->fThrust * fThrust + 0.45f; + ApplyMoveForce(GRAVITY * CVector(0.0f, 0.0f, 0.5f) * m_fMass * CTimer::GetTimeStep()); + }else + fThrust = flyingHandling->fThrust * fThrust + 0.95f; + fThrust -= flyingHandling->fThrustFallOff * fUpSpeed; + if(flightModel == FLIGHT_MODEL_RCHELI && GetPosition().z > 40.0f) + fThrust *= 10.0f/(GetPosition().z - 30.0f); + else if(GetPosition().z > 80.0f) + fThrust *= 10.0f/(GetPosition().z - 70.0f); + ApplyMoveForce(GRAVITY * GetUp() * fThrust * m_fMass * CTimer::GetTimeStep()); + + if (GetUp().z > 0.0f){ + float upRight = Clamp(GetRight().z, -flyingHandling->fFormLift, flyingHandling->fFormLift); + float upImpulseRight = -upRight * flyingHandling->fAttackLift * m_fTurnMass * CTimer::GetTimeStep(); + ApplyTurnForce(upImpulseRight * GetUp(), GetRight()); + + float upFwd = Clamp(GetForward().z, -flyingHandling->fFormLift, flyingHandling->fFormLift); + float upImpulseFwd = -upFwd * flyingHandling->fAttackLift * m_fTurnMass * CTimer::GetTimeStep(); + ApplyTurnForce(upImpulseFwd * GetUp(), GetForward()); + }else{ + float upRight = GetRight().z < 0.0f ? -flyingHandling->fFormLift : flyingHandling->fFormLift; + float upImpulseRight = -upRight * flyingHandling->fAttackLift * m_fTurnMass * CTimer::GetTimeStep(); + ApplyTurnForce(upImpulseRight * GetUp(), GetRight()); + + float upFwd = GetForward().z < 0.0f ? -flyingHandling->fFormLift : flyingHandling->fFormLift; + float upImpulseFwd = -upFwd * flyingHandling->fAttackLift * m_fTurnMass * CTimer::GetTimeStep(); + ApplyTurnForce(upImpulseFwd * GetUp(), GetForward()); + } + + float fRoll, fPitch, fYaw; + if (bCheat5) { + 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; + } else { + fPitch = CPad::GetPad(0)->GetSteeringUpDown() / 128.0f; + fRoll = -CPad::GetPad(0)->GetSteeringLeftRight() / 128.0f; + fYaw = CPad::GetPad(0)->GetLookRight(); + if (CPad::GetPad(0)->GetLookLeft()) + fYaw = -1.0f; +#ifdef FREE_CAM + if (!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController)) +#endif + if(Abs(CPad::GetPad(0)->GetCarGunLeftRight()) > 1.0f) + fYaw = CPad::GetPad(0)->GetCarGunLeftRight() / 128.0f; + } +#ifdef FREE_CAM + if(!CCamera::bFreeCam || (CCamera::bFreeCam && !CPad::IsAffectedByController)) +#endif + if(Abs(CPad::GetPad(0)->GetCarGunUpDown()) > 1.0f) + fPitch = -CPad::GetPad(0)->GetCarGunUpDown() / 128.0f; + if (CPad::GetPad(0)->GetHorn()) { + fYaw = 0.0f; + fPitch = Clamp(flyingHandling->fPitchStab * DotProduct(m_vecMoveSpeed, GetForward()), -200.0f, 1.3f); + fRoll = Clamp(flyingHandling->fRollStab * DotProduct(m_vecMoveSpeed, GetRight()), -200.0f, 1.3f); + } + ApplyTurnForce(fPitch * GetUp() * flyingHandling->fPitch * m_fTurnMass * CTimer::GetTimeStep(), GetForward()); + ApplyTurnForce(fRoll * GetUp() * flyingHandling->fRoll * m_fTurnMass * CTimer::GetTimeStep(), GetRight()); + + float fSideSpeed = -DotProduct(GetMoveSpeed(), GetRight()); + float fSideSlipAccel = flyingHandling->fSideSlip * fSideSpeed * Abs(fSideSpeed); + ApplyMoveForce(m_fMass * GetRight() * fSideSlipAccel * CTimer::GetTimeStep()); + float fYawAccel = flyingHandling->fYawStab * fSideSpeed * Abs(fSideSpeed) + flyingHandling->fYaw * fYaw; + ApplyTurnForce(fYawAccel * GetRight() * m_fTurnMass * CTimer::GetTimeStep(), -GetForward()); + + ApplyTurnForce(fYaw * GetForward() * flyingHandling->fYaw * m_fTurnMass * CTimer::GetTimeStep(), GetRight()); + + float rX = Pow(flyingHandling->vecTurnRes.x, CTimer::GetTimeStep()); + float rY = Pow(flyingHandling->vecTurnRes.y, CTimer::GetTimeStep()); + float rZ = Pow(flyingHandling->vecTurnRes.z, CTimer::GetTimeStep()); + CVector vecTurnSpeed = Multiply3x3(m_vecTurnSpeed, GetMatrix()); + float fResistanceMultiplier = Pow(1.0f / (flyingHandling->vecSpeedRes.z * 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; + } + } +} + +static CColModel rotorColModel; +static CColSphere rotorColSphere; +float ROTOR_SEMI_THICKNESS = 0.05f; +float ROTOR_TURN_SPEED = 0.2f; +float ROTOR_DISGUARD_MULT = 0.3f; +float ROTOR_COL_ELASTICITY = 1.0f; +float ROTOR_COL_TURNMULT = -0.001f; +float ROTOR_DEFAULT_DAMAGE = 100.0f; + +bool +CVehicle::DoBladeCollision(CVector pos, CMatrix &matrix, int16 rotorType, float radius, float damageMult) +{ + CVector max(radius, radius, radius); + CVector min(-radius, -radius, -radius); + + switch(rotorType){ + case ROTOR_TOP: + case ROTOR_BOTTOM: + min.z = -ROTOR_SEMI_THICKNESS; + max.z = ROTOR_SEMI_THICKNESS; + break; + case ROTOR_FRONT: + case ROTOR_BACK: + min.y = -ROTOR_SEMI_THICKNESS; + max.y = ROTOR_SEMI_THICKNESS; + break; + case ROTOR_RIGHT: + case ROTOR_LEFT: + min.x = -ROTOR_SEMI_THICKNESS; + max.x = ROTOR_SEMI_THICKNESS; + break; + } + + min += pos; + max += pos; + rotorColModel.boundingBox.Set(min, max); + rotorColModel.boundingSphere.Set(radius, pos); + rotorColSphere.Set(radius, pos, 0, 0); + rotorColModel.spheres = &rotorColSphere; + rotorColModel.numSpheres = 1; + + pos = matrix * pos; + bool hadCollision = false; + int minX = CWorld::GetSectorIndexX(pos.x - radius); + if(minX <= 0) minX = 0; + + int minY = CWorld::GetSectorIndexY(pos.y - radius); + if(minY <= 0) minY = 0; + + int maxX = CWorld::GetSectorIndexX(pos.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(pos.y + radius); +#ifdef FIX_BUGS + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y - 1; +#else + if(maxY >= NUMSECTORS_Y) maxY = NUMSECTORS_Y; +#endif + + CWorld::AdvanceCurrentScanCode(); + for(int curY = minY; curY <= maxY; curY++) { + for(int curX = minX; curX <= maxX; curX++) { + CSector *sector = CWorld::GetSector(curX, curY); + if(BladeColSectorList(sector->m_lists[ENTITYLIST_BUILDINGS], rotorColModel, matrix, rotorType, damageMult)) + hadCollision = true; + if(BladeColSectorList(sector->m_lists[ENTITYLIST_BUILDINGS_OVERLAP], rotorColModel, matrix, rotorType, damageMult)) + hadCollision = true; + if(BladeColSectorList(sector->m_lists[ENTITYLIST_VEHICLES], rotorColModel, matrix, rotorType, damageMult)) + hadCollision = true; + if(BladeColSectorList(sector->m_lists[ENTITYLIST_VEHICLES_OVERLAP], rotorColModel, matrix, rotorType, damageMult)) + hadCollision = true; + if(BladeColSectorList(sector->m_lists[ENTITYLIST_PEDS], rotorColModel, matrix, rotorType, 0.0f)) + hadCollision = true; + if(BladeColSectorList(sector->m_lists[ENTITYLIST_PEDS_OVERLAP], rotorColModel, matrix, rotorType, 0.0f)) + hadCollision = true; + if(BladeColSectorList(sector->m_lists[ENTITYLIST_OBJECTS], rotorColModel, matrix, rotorType, damageMult)) + hadCollision = true; + if(BladeColSectorList(sector->m_lists[ENTITYLIST_OBJECTS_OVERLAP], rotorColModel, matrix, rotorType, damageMult)) + hadCollision = true; + } + } + rotorColModel.spheres = nil; + rotorColModel.numSpheres = 0; + + return hadCollision; +} + +bool +CVehicle::BladeColSectorList(CPtrList &list, CColModel &rotorColModel, CMatrix &matrix, int16 rotorType, float damageMult) +{ + int i; + CVector axis; + CVector turnSpeed(0.0f, 0.0f, 0.0f); + switch(rotorType){ + case ROTOR_TOP: + turnSpeed.z = -ROTOR_TURN_SPEED; + axis = -matrix.GetUp(); + break; + case ROTOR_BOTTOM: + turnSpeed.z = ROTOR_TURN_SPEED; + axis = matrix.GetUp(); + break; + + case ROTOR_FRONT: + turnSpeed.y = -ROTOR_TURN_SPEED; + axis = -matrix.GetForward(); + break; + case ROTOR_BACK: + turnSpeed.y = ROTOR_TURN_SPEED; + axis = matrix.GetForward(); + break; + + case ROTOR_RIGHT: + turnSpeed.x = -ROTOR_TURN_SPEED; + axis = -matrix.GetRight(); + break; + case ROTOR_LEFT: + turnSpeed.x = ROTOR_TURN_SPEED; + axis = matrix.GetRight(); + break; + } + turnSpeed = Multiply3x3(matrix, turnSpeed); + CVector center = rotorColModel.boundingSphere.center; + center = matrix*center; + + for(CPtrNode *node = list.first; node; node = node->next) { + CEntity *entity = (CEntity *)node->item; + if(entity == (CEntity*)this || + !entity->bUsesCollision || + entity->m_scanCode == CWorld::GetCurrentScanCode()) + continue; + + entity->m_scanCode = CWorld::GetCurrentScanCode(); + + int numCollisions; + CColModel *entityCol; + if(entity->IsPed()) + entityCol = ((CPedModelInfo*)CModelInfo::GetModelInfo(entity->GetModelIndex()))->AnimatePedColModelSkinned(entity->GetClump()); + else + entityCol = entity->GetColModel(); + if(entityCol) + numCollisions = CCollision::ProcessColModels(matrix, rotorColModel, entity->GetMatrix(), *entityCol, + CWorld::m_aTempColPts, nil, nil); + else + numCollisions = 0; + + if(numCollisions > 0 && entity->IsPed()){ + CPed *ped = (CPed*)entity; + CVector2D dirToRotor = GetPosition() - entity->GetPosition(); + dirToRotor.Normalise(); + int localDir = ped->GetLocalDirection(dirToRotor); + if(ped->m_attachedTo == nil){ + ped->bIsStanding = false; + ped->ApplyMoveForce(-5.0f*dirToRotor.x, -5.0f*dirToRotor.y, 5.0f); + } + ped->InflictDamage(this, WEAPONTYPE_RUNOVERBYCAR, 1000.0f, PEDPIECE_TORSO, localDir); + + if(CGame::nastyGame && ped->GetIsOnScreen()){ + for(i = 0; i < 16; i++) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, ped->GetPosition(), CVector(dirToRotor.x, dirToRotor.y, 1.0f) * 0.01f); + CParticle::AddParticle(PARTICLE_TEST, ped->GetPosition(), CVector(0.0f, 0.0f, 0.02f), nil, 0.1f); + CParticle::AddParticle(PARTICLE_TEST, ped->GetPosition()+CVector(0.0f, 0.0f, 0.2f), CVector(0.0f, 0.0f, -0.01f), nil, 0.1f); + } + }else if(numCollisions > 0 && entity->GetModelIndex() != MI_MISSILE){ + float impulse = 0.0f; + bool hadCollision = false; + float savedElasticity = m_fElasticity; + m_fElasticity = ROTOR_COL_ELASTICITY; + + for(i = 0; i < numCollisions; i++){ + CVector colpos = CWorld::m_aTempColPts[i].point; + CVector localColpos = colpos - center; + float axisDir = DotProduct(axis, localColpos); + float colDir = DotProduct(CWorld::m_aTempColPts[i].normal, localColpos); + + if(2.0f*ROTOR_SEMI_THICKNESS < Abs(axisDir) && + ROTOR_DISGUARD_MULT*Abs(colDir) < Abs(axisDir)) + continue; + + hadCollision = true; + colpos -= axisDir*axis; // get rid of axis component + + CVector tangentSpeed = CrossProduct(turnSpeed, colpos - center); + + // Particles + for(int j = 0; j < 4; j++){ + CParticle::AddParticle(PARTICLE_SPARK_SMALL, colpos, (tangentSpeed+m_vecMoveSpeed)/2.0f); + CParticle::AddParticle(PARTICLE_SPARK, colpos, 0.1f*CWorld::m_aTempColPts[i].normal); + } + + // Apply Collision + if(IsCar()){ + CAutomobile *heli = (CAutomobile*)this; + if(heli->m_aWheelSpeed[1] > 0.15f){ + ApplyCollision(CWorld::m_aTempColPts[i], impulse); + ApplyTurnForce(m_fTurnMass*ROTOR_COL_TURNMULT*tangentSpeed, colpos - center); + heli->m_aWheelSpeed[1] = 0.15f; + }else if(heli->m_aWheelSpeed[1] < 0.075f && heli->m_aWheelSpeed[1] > 0.0f) + heli->m_aWheelSpeed[1] *= -1.0f; + } + + float damageImpulse = damageMult * Max(impulse, ROTOR_DEFAULT_DAMAGE*m_fMass/3000.0f); + if(damageImpulse > m_fDamageImpulse) + SetDamagedPieceRecord(0, damageImpulse, entity, CWorld::m_aTempColPts[i].normal); + + } + + if(hadCollision && !entity->IsPed()) + DMAudio.ReportCollision(this, entity, SURFACE_CAR_PANEL, SURFACE_TARMAC, 50.0f, 0.09f); + m_fElasticity = savedElasticity; + } + } + return false; +} + + +float fBurstSpeedMax = 0.3f; +float fBurstTyreMod = 0.13f; + +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(IsBike()) + brake = 0.6f * mod_HandlingManager.fWheelFriction / (pHandling->fMass + 200.0f); + else if(pHandling->fMass < 500.0f) + brake = 0.2f * mod_HandlingManager.fWheelFriction / pHandling->fMass; + else 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 totalSpeed = fwd*wheelFwd + right*wheelRight; + + CVector turnDirection = totalSpeed; + bool separateTurnForce = false; // BUG: not initialized on PC + if(pHandling->fSuspensionAntidiveMultiplier > 0.0f){ + if(bBraking){ + separateTurnForce = true; + turnDirection = totalSpeed - pHandling->fSuspensionAntidiveMultiplier*fwd*wheelFwd; + }else if(bDriving){ + separateTurnForce = true; + turnDirection = totalSpeed - 0.5f*pHandling->fSuspensionAntidiveMultiplier*fwd*wheelFwd; + } + } + + CVector direction = totalSpeed; + + float speed = totalSpeed.Magnitude(); + float turnSpeed; + if(separateTurnForce) + turnSpeed = turnDirection.Magnitude(); + else + turnSpeed = speed; + direction.Normalise(); + if(separateTurnForce) + turnDirection.Normalise(); + else + turnDirection = direction; + + float impulse = speed*m_fMass; + float turnImpulse = turnSpeed*GetMass(wheelContactPoint, turnDirection); + + ApplyMoveForce(impulse * direction); + ApplyTurnForce(turnImpulse * turnDirection, wheelContactPoint); + } +} + +float fBurstBikeSpeedMax = 0.12f; +float fBurstBikeTyreMod = 0.05f; +float fTweakBikeWheelTurnForce = 2.0f; + +void +CVehicle::ProcessBikeWheel(CVector &wheelFwd, CVector &wheelRight, CVector &wheelContactSpeed, CVector &wheelContactPoint, + int32 wheelsOnGround, float thrust, float brake, float adhesion, float destabTraction, int8 wheelId, float *wheelSpeed, tWheelState *wheelState, eBikeWheelSpecial special, 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; + static bool bReversing; + +#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; + bReversing = thrust < 0.0f; + + float contactSpeedFwd = DotProduct(wheelContactSpeed, wheelFwd); + float contactSpeedRight; + + if(*wheelState != WHEEL_STATE_NORMAL) + bAlreadySkidding = true; + *wheelState = WHEEL_STATE_NORMAL; + + adhesion *= CTimer::GetTimeStep(); + if(bAlreadySkidding) + adhesion *= pHandling->fTractionLoss; + + if(special == BIKE_WHEELSPEC_2 || special == BIKE_WHEELSPEC_3) + contactSpeedRight = 0.0f; + else + contactSpeedRight = DotProduct(wheelContactSpeed, wheelRight); + + // moving sideways + if(contactSpeedRight != 0.0f){ + // exert opposing force + right = -contactSpeedRight/wheelsOnGround; +#ifdef FIX_BUGS + // contactSpeedRight is independent of framerate but right has timestep as a factor + // so we probably have to fix this + // see above + //right *= CTimer::GetTimeStepFix(); +#endif + + if(wheelStatus == WHEEL_STATUS_BURST){ + float fwdspeed = Min(contactSpeedFwd, fBurstBikeSpeedMax); + right += fwdspeed * CGeneral::GetRandomNumberInRange(-fBurstBikeTyreMod, fBurstBikeTyreMod); + } + } + + 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 + // see above + //fwd *= CTimer::GetTimeStepFix(); +#endif + + if(!bBraking){ + if(m_fGasPedal < 0.01f){ + if(IsBike()) + brake = 0.6f * mod_HandlingManager.fWheelFriction / (pHandling->fMass + 200.0f); + else if(pHandling->fMass < 500.0f) + brake = mod_HandlingManager.fWheelFriction / m_fMass; + else if(GetModelIndex() == MI_RCBANDIT) + brake = 0.2f * mod_HandlingManager.fWheelFriction / m_fMass; + else + brake = mod_HandlingManager.fWheelFriction / m_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(destabTraction < 1.0f) + right *= destabTraction; + }else if(destabTraction < 1.0f){ + if(!bAlreadySkidding) + destabTraction *= pHandling->fTractionLoss; + if(sq(adhesion*destabTraction) < speedSq){ + float l = Sqrt(speedSq); + right *= adhesion * destabTraction / 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); + CVector vTurnImpulse = turnImpulse * direction; + ApplyMoveForce(impulse * direction); + + float turnRight = DotProduct(vTurnImpulse, GetRight()); + float contactRight = DotProduct(wheelContactPoint, GetRight()); + float contactFwd = DotProduct(wheelContactPoint, GetForward()); + + if(wheelId != BIKEWHEEL_REAR || !bBraking && !bReversing) + ApplyTurnForce((vTurnImpulse - turnRight*GetRight()) * fTweakBikeWheelTurnForce, + wheelContactPoint - contactRight*GetRight()); + + ApplyTurnForce(turnRight*GetRight(), contactFwd*GetForward()); + } +} + +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(); +} + +int +CVehicle::FindTyreNearestPoint(float x, float y) +{ + CVector pos = CVector(x - GetPosition().x, y - GetPosition().y, 0.0f); + float fwd = DotProduct(GetForward(), pos); + float right = DotProduct(GetRight(), pos); + + int piece; + if(IsBike()){ + piece = fwd > 0.0f ? CAR_PIECE_WHEEL_LF : CAR_PIECE_WHEEL_LR; + }else{ + piece = fwd > 0.0f ? + right > 0.0f ? CAR_PIECE_WHEEL_RF : CAR_PIECE_WHEEL_LF : + right > 0.0f ? CAR_PIECE_WHEEL_RR : CAR_PIECE_WHEEL_LR; + } + return piece - CAR_PIECE_WHEEL_LF; +} + +void +CVehicle::InflictDamage(CEntity *damagedBy, eWeaponType weaponType, float damage, CVector pos) +{ + if (!bCanBeDamaged) + return; + if(GetStatus() == STATUS_PLAYER && CStats::GetPercentageProgress() >= 100.0f) + damage *= 0.5f; + if (GetStatus() != STATUS_PLAYER && bOnlyDamagedByPlayer && (damagedBy != FindPlayerPed() && damagedBy != FindPlayerVehicle())) + return; + + if(damage > 10.0f && (damagedBy == FindPlayerPed() || damagedBy == FindPlayerVehicle()) && GetStatus() != STATUS_WRECKED){ + CWorld::Players[CWorld::PlayerInFocus].m_nHavocLevel += 2; + CWorld::Players[CWorld::PlayerInFocus].m_fMediaAttention += 1.0f; + CStats::PropertyDestroyed += CGeneral::GetRandomNumberInRange(5, 25); + } + + bool bFrightensDriver = false; + switch (weaponType) { + case WEAPONTYPE_UNARMED: + case WEAPONTYPE_BRASSKNUCKLE: + case WEAPONTYPE_SCREWDRIVER: + case WEAPONTYPE_GOLFCLUB: + case WEAPONTYPE_NIGHTSTICK: + case WEAPONTYPE_KNIFE: + case WEAPONTYPE_BASEBALLBAT: + case WEAPONTYPE_HAMMER: + case WEAPONTYPE_CLEAVER: + case WEAPONTYPE_MACHETE: + case WEAPONTYPE_KATANA: + case WEAPONTYPE_CHAINSAW: + if (bMeleeProof) + return; + break; + case WEAPONTYPE_COLT45: + case WEAPONTYPE_PYTHON: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_SPAS12_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + case WEAPONTYPE_TEC9: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SILENCED_INGRAM: + case WEAPONTYPE_MP5: + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + case WEAPONTYPE_M60: + case WEAPONTYPE_MINIGUN: + case WEAPONTYPE_HELICANNON: + case WEAPONTYPE_UZI_DRIVEBY: + if (bBulletProof) + return; + bFrightensDriver = true; + break; + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_MOLOTOV: + case WEAPONTYPE_ROCKET: + 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(bFrightensDriver && GetStatus() == STATUS_PLAYER && m_fHealth < 250.0f) + return; + + // Pop tires + if(damagedBy && damagedBy->IsPed() && (IsCar() || IsBike())){ + int accuracy = 0; + switch(weaponType){ + case WEAPONTYPE_COLT45: + accuracy = 10; + break; + case WEAPONTYPE_PYTHON: + if(!((CPed*)damagedBy)->IsPlayer()) + accuracy = 64; + break; + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + case WEAPONTYPE_M60: + case WEAPONTYPE_HELICANNON: + accuracy = 25; + break; + case WEAPONTYPE_TEC9: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SILENCED_INGRAM: + case WEAPONTYPE_MP5: + case WEAPONTYPE_UZI_DRIVEBY: + accuracy = 15; + break; + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + if(!((CPed*)damagedBy)->IsPlayer()) + accuracy = 15; + break; + } + + if(((CPed*)damagedBy)->IsPlayer() && (CCamera::m_bUseMouse3rdPerson || TheCamera.Using1stPersonWeaponMode())) + accuracy = 0; + + if(accuracy != 0 && !bTyresDontBurst && (CGeneral::GetRandomNumber()&0x7F) < accuracy){ + if(IsBike()) + BurstTyre(FindTyreNearestPoint(pos.x, pos.y) + CAR_PIECE_WHEEL_LF, false); + else if(GetVehicleAppearance() == VEHICLE_APPEARANCE_CAR) + BurstTyre(FindTyreNearestPoint(pos.x, pos.y) + CAR_PIECE_WHEEL_LF, true); + } + } + + 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; + m_pLastDamageEntity = damagedBy; + float oldHealth = m_fHealth; + if (m_fHealth > damage) { + m_fHealth -= damage; + if (VehicleCreatedBy == RANDOM_VEHICLE && !IsBoat()){ + switch (GetStatus()) { + case STATUS_SIMPLE: + case STATUS_PHYSICS: + if(AutoPilot.m_nDrivingStyle == DRIVINGSTYLE_PLOUGH_THROUGH || + CGeneral::GetRandomNumberInRange(0.0f, 1.0f) > 0.5f && AutoPilot.m_nCarMission == MISSION_CRUISE){ + // Drive away like a maniac + if(pDriver && pDriver->m_objective != OBJECTIVE_LEAVE_CAR){ + if(AutoPilot.m_nDrivingStyle != DRIVINGSTYLE_PLOUGH_THROUGH) + AutoPilot.m_nCruiseSpeed *= 1.5f; + AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_PLOUGH_THROUGH; + } + }else{ + // Leave vehicle + if (pDriver && pDriver->CharCreatedBy != MISSION_CHAR) { + SetStatus(STATUS_ABANDONED); + pDriver->bFleeAfterExitingCar = true; + pDriver->SetObjective(OBJECTIVE_LEAVE_CAR, this); + pDriver->Say(SOUND_PED_FLEE_SPRINT); + } + int time = 200; + for (int i = 0; i < m_nNumMaxPassengers; i++) { + if (pPassengers[i] && + pPassengers[i]->m_objective != OBJECTIVE_LEAVE_CAR && + pPassengers[i]->CharCreatedBy != MISSION_CHAR) { + pPassengers[i]->bFleeAfterExitingCar = true; + pPassengers[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this); + pPassengers[i]->m_objectiveTimer = CTimer::GetTimeInMilliseconds() + time; + pPassengers[i]->Say(SOUND_PED_FLEE_SPRINT); + time += 200; + } + } + } + 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(TheCamera.Cams[TheCamera.ActiveCam].DirectionWasLooking == LOOKING_FORWARD){ + if(CPad::GetPad(0)->GetCarGunFired() && !bGunSwitchedOff){ + FireFixedMachineGuns(); + }else{ + if(CTimer::GetTimeInMilliseconds() > m_nGunFiringTime + 1400) + m_nAmmoInClip = 20; + } + } +} + +void +CVehicle::FireFixedMachineGuns(void) +{ + if (CTimer::GetTimeInMilliseconds() <= m_nGunFiringTime + 150) + return; + 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; + } +} + +void +CVehicle::ActivateBomb(void) +{ + 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); + } +} + +void +CVehicle::ActivateBombWhenEntered(void) +{ + 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; + }else + bDriverLastFrame = false; +} + +void +CVehicle::ExtinguishCarFire(void) +{ + if(GetStatus() != STATUS_WRECKED) + 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::MakeNonDraggedPedsLeaveVehicle(CPed *ped1, CPed *ped2, CPlayerPed *&player, CCopPed *&cop) +{ + int i; + player = nil; + cop = nil; + + if(ped1->IsPlayer() && ped2->m_nPedType == PEDTYPE_COP && + ((CPlayerPed*)ped1)->m_pWanted->GetWantedLevel() > 0 && + ped2->m_pedInObjective == ped1){ + player = (CPlayerPed*)ped1; + cop = (CCopPed*)ped2; + return; + } + + bool ped1IsDriver = ped1 == pDriver; + + // Just what the hell is this weird code? + CPed *peds[9]; + CPed *peds2[9]; + int numPeds = 0; + int numPeds2 = 0; + for(i = 0; i < m_nNumMaxPassengers; i++){ + CPed *p = pPassengers[i]; + if(p && p != ped1 && !p->bStayInCarOnJack){ + peds[numPeds++] = p; + // uhh what? + if(i > 0 || ped1IsDriver) + peds2[numPeds2++] = p; + } + } + + // So we're copying this array for no reason... + CPed *peds3[9]; + int numPeds3 = 0; + for(i = 0; i < numPeds; i++){ + if(peds[i]->IsPlayer() && ped2->m_nPedType == PEDTYPE_COP && + ((CPlayerPed*)peds[i])->m_pWanted->GetWantedLevel() > 0 && + ped2->m_pedInObjective == peds[i]){ + player = (CPlayerPed*)peds[i]; + cop = (CCopPed*)ped2; + return; + } + peds3[numPeds3++] = peds[i]; + } + + int time = 1800; + for(i = 0; i < numPeds3; i++){ + peds3[i]->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + time; + peds3[i]->SetObjective(OBJECTIVE_LEAVE_CAR, this); + time += CGeneral::GetRandomNumberInRange(300.0f, 600.0f); + } + + if(IsCar() && numPeds2 > 0 && CGeneral::GetRandomTrueFalse()) + for(i = 0; i < numPeds2; i++) + if(peds2[i]->IsFemale() || CGeneral::GetRandomTrueFalse()){ + peds2[i]->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 10000; + peds2[i]->bHeldHostageInCar = true; + peds2[i]->bFleeAfterExitingCar = true; + } +} + +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) + BlowUpCar(m_pBlowUpEntity); +} + +bool +CVehicle::IsLawEnforcementVehicle(void) +{ + switch(GetModelIndex()){ + case MI_POLICE: + case MI_ENFORCER: + case MI_PREDATOR: + case MI_RHINO: + case MI_BARRACKS: + case MI_FBIRANCH: + case MI_VICECHEE: + return true; + default: + return false; + } +} + +bool +CVehicle::UsesSiren(void) +{ + switch(GetModelIndex()){ + case MI_FIRETRUCK: + case MI_AMBULAN: + case MI_FBICAR: + case MI_MRWHOOP: + case MI_POLICE: + case MI_ENFORCER: + case MI_PREDATOR: + case MI_FBIRANCH: + case MI_VICECHEE: + return true; + default: + return false; + } +} + +bool +CVehicle::IsVehicleNormal(void) +{ + if (!pDriver || m_nNumPassengers != 0 || GetStatus() == STATUS_WRECKED) + return false; + return GetModelInfo()->m_vehicleClass != -1; +} + +bool +CVehicle::CarHasRoof(void) +{ + if((pHandling->Flags & HANDLING_HAS_NO_ROOF) == 0) + return true; + // component 0 is assumed to be a roof + return m_aExtras[0] == 0 || m_aExtras[1] == 0; +} + +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 || + m_nDoorLock == CARLOCK_LOCKED_BUT_CAN_BE_DAMAGED) + return false; + if(ped->IsPlayer() && m_nDoorLock == CARLOCK_LOCKOUT_PLAYER_ONLY) + return false; + return true; +} + +bool +CVehicle::CanDoorsBeDamaged(void) +{ + return m_nDoorLock == CARLOCK_NOT_USED || + m_nDoorLock == CARLOCK_UNLOCKED || + m_nDoorLock == CARLOCK_LOCKED_BUT_CAN_BE_DAMAGED; +} + +bool +CVehicle::CanPedEnterCar(void) +{ + // can't enter when car is on side + if(IsBike() || 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(bool jumpExit) +{ + CVector up = GetUp(); + if(up.z > 0.1f || up.z < -0.1f){ + if (IsBoat()) + return true; + // can't exit when car is moving too fast + if(m_vecMoveSpeed.MagnitudeSqr() > 0.005f && !jumpExit) + 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; + } +} + +bool +CVehicle::CanPedJumpOutCar(void) +{ + if(GetUp().z < 0.3f) + return false; + float speed = m_vecMoveSpeed.MagnitudeSqr(); + return speed < 0.1f || speed > 0.5f ? false : true; +} + +bool +CVehicle::CanPedJumpOffBike(void) +{ + if(pPassengers[0]) + return false; + return m_vecMoveSpeed.MagnitudeSqr() < 0.07f ? false : 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, true); + 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) +{ + int i; + + if(pPassengers[n]) + return pPassengers[n]; + + if((IsTaxi() || IsLimo()) && n == 0) + pPassengers[0] = nil; + else{ + CPed *passenger = CPopulation::AddPedInCar(this, false); + pPassengers[n] = passenger; + passenger->m_pMyVehicle = this; + passenger->m_pMyVehicle->RegisterReference((CEntity**)&pPassengers[n]->m_pMyVehicle); + passenger->bInVehicle = true; + passenger->SetPedState(PED_DRIVING); + + if(passenger->m_nPedType == PEDTYPE_CIVMALE || passenger->m_nPedType == PEDTYPE_CIVFEMALE) + for(i = 0; i < n; i++) + if(pPassengers[i] && pPassengers[n] && + (pPassengers[i]->m_nPedType == PEDTYPE_CIVMALE || pPassengers[i]->m_nPedType == PEDTYPE_CIVFEMALE) && + passenger->GetModelIndex() == pPassengers[i]->GetModelIndex()){ + pPassengers[n] = nil; + CPopulation::RemovePed(passenger); + } + } + if(bIsBus && pPassengers[n]) + pPassengers[n]->bRenderPedInCar = false; + ++m_nNumPassengers; + return pPassengers[n]; +} + +void +CVehicle::SetDriver(CPed *driver) +{ + pDriver = driver; + pDriver->RegisterReference((CEntity**)&pDriver); + + if(bFreebies && driver == FindPlayerPed()){ + bFreebies = false; + switch(GetModelIndex()){ + case MI_AMBULAN: + FindPlayerPed()->m_fHealth = Max(FindPlayerPed()->m_fHealth, Min(FindPlayerPed()->m_fHealth + 20.0f, CWorld::Players[0].m_nMaxHealth)); + break; + + case MI_TAXI: + case MI_CABBIE: + case MI_ZEBRA: + case MI_KAUFMAN: + CWorld::Players[CWorld::PlayerInFocus].m_nMoney += 12; + break; + + case MI_POLICE: + CStreaming::RequestModel(MI_SHOTGUN, STREAMFLAGS_DONT_REMOVE); + bFreebies = true; + break; + + case MI_ENFORCER: + driver->m_fArmour = Max(driver->m_fArmour, CWorld::Players[0].m_nMaxArmour); + break; + + case MI_CADDY: + if(!(driver->IsPlayer() && ((CPlayerPed*)driver)->DoesPlayerWantNewWeapon(WEAPONTYPE_GOLFCLUB, true))) + CStreaming::RequestModel(MI_GOLFCLUB, STREAMFLAGS_DONT_REMOVE); + break; + } + } + + if(IsBike()) + ApplyMoveForce(-0.02f*driver->m_fMass * GetUp()); + else + ApplyTurnForce(0.0f, 0.0f, -0.02f*driver->m_fMass, + driver->GetPosition().x - GetPosition().x, + driver->GetPosition().y - GetPosition().y, + 0.0f); +} + +bool +CVehicle::AddPassenger(CPed *passenger) +{ + int i; + + if(IsBike()) + ApplyTurnForce(-0.02f*passenger->m_fMass * GetUp(), -0.1f*GetForward()); + else + 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); + + if(IsBike()) + ApplyTurnForce(-0.02f*passenger->m_fMass * GetUp(), -0.1f*GetForward()); + else + 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); + if(pDriver == FindPlayerPed()){ + if(GetModelIndex() == MI_POLICE && CStreaming::HasModelLoaded(MI_SHOTGUN)){ + if(bFreebies){ + if(((CPlayerPed*)pDriver)->DoesPlayerWantNewWeapon(WEAPONTYPE_SHOTGUN, true)) + pDriver->GiveWeapon(WEAPONTYPE_SHOTGUN, 5, true); + else + pDriver->GrantAmmo(WEAPONTYPE_SHOTGUN, 5); + bFreebies = false; + } + CStreaming::SetModelIsDeletable(MI_SHOTGUN); + }else if(GetModelIndex() == MI_CADDY && CStreaming::HasModelLoaded(MI_GOLFCLUB)){ + if(((CPlayerPed*)pDriver)->DoesPlayerWantNewWeapon(WEAPONTYPE_GOLFCLUB, true)) + pDriver->GiveWeapon(WEAPONTYPE_GOLFCLUB, 1, true); + CStreaming::SetModelIsDeletable(MI_GOLFCLUB); + } + } + 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; + } + } +} + +bool +CVehicle::IsDriver(CPed *ped) +{ + return ped && ped == pDriver; +} + +bool +CVehicle::IsDriver(int32 model) +{ + return pDriver && pDriver->GetModelIndex() == model; +} + +bool +CVehicle::IsPassenger(CPed *ped) +{ + int i; + if(ped == nil) + return false; + for(i = 0; i < 8; i++) + if(pPassengers[i] == ped) + return true; + return false; +} + +bool +CVehicle::IsPassenger(int32 model) +{ + int i; + for(i = 0; i < 8; i++) + if(pPassengers[i] && pPassengers[i]->GetModelIndex() == model) + return true; + return false; +} + +void +CVehicle::UpdatePassengerList(void) +{ + int i; + bool hasPassenger = false; + if(m_nNumPassengers) + for(i = 0; i < 8; i++) + if(pPassengers[i]){ + hasPassenger = true; + break; + } + if(!hasPassenger) + m_nNumPassengers = 0; +} + +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; + m_nCarHornTimer = 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; +} + +RpMaterial* +SetCompAlphaCB(RpMaterial *material, void *data) +{ + uint32 alpha = (uint32)(uintptr)data; + RwRGBA *col = (RwRGBA*)RpMaterialGetColor(material); // get rid of const + col->alpha = alpha; + return material; +} + +void +CVehicle::SetComponentAtomicAlpha(RpAtomic *atomic, int32 alpha) +{ + RpGeometry *geo = RpAtomicGetGeometry(atomic); + RpGeometrySetFlags(geo, RpGeometryGetFlags(geo) | rpGEOMETRYMODULATEMATERIALCOLOR); + RpGeometryForAllMaterials(geo, SetCompAlphaCB, (void*)alpha); +} + +void +CVehicle::UpdateClumpAlpha(void) +{ + 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); +} + +void +CVehicle::HeliDustGenerate(CEntity *heli, float radius, float ground, int rnd) +{ + int i; + float angle; + CColPoint point; + CEntity *entity; + uint8 r, g, b; + + if(heli == nil) + return; + + uint8 surface = SURFACE_TARMAC; + int frm = CTimer::GetFrameCounter() & 7; + float testLowZ = ground - 10.0f; + float dustSize = 0.0f; + float baseSize = 1.0f; + float offset = 1.0f; // when heli is tilted + float particleZ = -101.0f; + int n = 0; + + if(heli->GetModelIndex() == MI_RCGOBLIN || heli->GetModelIndex() == MI_RCRAIDER){ + radius = 3.0f; + dustSize = 0.04f; + baseSize = 0.07f; + offset = 0.3f; + } + + CVector heliPos = heli->GetPosition(); + + if(heli->IsVehicle() && ((CVehicle*)heli)->IsCar()){ + heliPos.x -= (heliPos.z - ground)*heli->GetUp().x*offset*0.5f; + heliPos.y -= (heliPos.z - ground)*heli->GetUp().y*offset*0.5f; + } + + float steamSize = 0.25f * radius * baseSize; + float splashSize = 0.3f * radius * baseSize; + + i = 0; + for(i = 0; i < 32+rnd; i++){ + angle = i * TWOPI/32.0f; + CVector pos(radius*Cos(angle), radius*Sin(angle), 0.0f); + CVector dir = CVector(pos.x, pos.y, 1.0f)*0.01f; + pos += heliPos; + + if(i < 32 && i == 4*frm){ + if(CWorld::ProcessVerticalLine(pos, testLowZ, point, entity, true, false, false, false, true, false, nil)){ + n = rnd; + particleZ = point.point.z; + surface = point.surfaceB; + }else + n = 0; + + float waterLevel = 0.0f; + if(CWaterLevel::GetWaterLevel(pos, &waterLevel, false) && waterLevel > particleZ){ + surface = SURFACE_WATER; + n = rnd; + particleZ = waterLevel; + } + } + + if(n){ + pos.z = particleZ; + if(surface == SURFACE_WATER){ + float red = (0.3*CTimeCycle::GetDirectionalRed() + CTimeCycle::GetAmbientRed_Obj())*255.0f/4.0f; + float green = (0.3*CTimeCycle::GetDirectionalGreen() + CTimeCycle::GetAmbientGreen_Obj())*255.0f/4.0f; + float blue = (0.3*CTimeCycle::GetDirectionalBlue() + CTimeCycle::GetAmbientBlue_Obj())*255.0f/4.0f; + r = Clamp(red, 0.0f, 255.0f); + g = Clamp(green, 0.0f, 255.0f); + b = Clamp(blue, 0.0f, 255.0f); + RwRGBA col1 = { r, g, b, (RwUInt8)CGeneral::GetRandomNumberInRange(8, 32) }; + RwRGBA col2 = { 255, 255, 255, 32 }; + + if(n&1) + CParticle::AddParticle(PARTICLE_STEAM_NY_SLOWMOTION, pos, dir, nil, steamSize, col2); + else + CParticle::AddParticle(PARTICLE_CAR_SPLASH, pos, dir, nil, splashSize, col1, + CGeneral::GetRandomNumberInRange(0.0f, 10.0f), + CGeneral::GetRandomNumberInRange(0.0f, 90.0f), 1); + }else{ + switch(surface){ + default: + case SURFACE_TARMAC: + r = 10; + g = 10; + b = 10; + break; + case SURFACE_GRASS: + r = 10; + g = 10; + b = 3; + break; + case SURFACE_GRAVEL: + r = 10; + g = 8; + b = 7; + break; + case SURFACE_MUD_DRY: + r = 10; + g = 6; + b = 3; + break; + case SURFACE_SAND: + case SURFACE_SAND_BEACH: + r = 10; + g = 10; + b = 7; + break; + } + RwRGBA col = { r, g, b, 32 }; + if(heliPos.z - pos.z < 20.0f) + CParticle::AddParticle(PARTICLE_HELI_DUST, pos, dir, nil, dustSize, col); + } + + n--; + } + } +} + +#define GLARE_MIN_DIST (13.0f) +#define GLARE_FULL_DIST (30.0f) +#define GLARE_MIN_ANGLE (0.99f) +#define GLARE_FULL_ANGLE (0.995f) + +void +CVehicle::DoSunGlare(void) +{ + if(bRenderScorched || GetPosition().z < 0.0f || + GetVehicleAppearance() != VEHICLE_APPEARANCE_CAR || CWeather::SunGlare <= 0.0f) + return; + + CVector camDir = TheCamera.GetPosition() - GetPosition(); + float dist = camDir.Magnitude(); + camDir *= 2.0f/dist; + CVector glareVec = camDir + CTimeCycle::GetSunDirection(); + CVector localGlareVec; + localGlareVec.x = DotProduct(glareVec, GetRight()); + localGlareVec.y = DotProduct(glareVec, GetForward()); + localGlareVec.z = 0.0; + localGlareVec.Normalise(); + + CVector2D fwd2D = GetForward(); + fwd2D.Normalise(); + CVector2D camDir2D = camDir; + camDir2D.Normalise(); + float fwdness = Abs(DotProduct2D(fwd2D, camDir2D)); + + // check angle + float strength; + if(fwdness > GLARE_FULL_ANGLE) + strength = 1.0f; + else if(fwdness > GLARE_MIN_ANGLE) + strength = (fwdness - GLARE_MIN_ANGLE)/(GLARE_FULL_ANGLE-GLARE_MIN_ANGLE); + else + return; + // check distance + if(dist > GLARE_FULL_DIST){ + // no max distance + }else if(dist > GLARE_MIN_DIST) + strength *= (dist - GLARE_MIN_DIST)/(GLARE_FULL_DIST - GLARE_MIN_DIST); + else + return; + + float intens = 0.8f * strength * CWeather::SunGlare; + int r = intens * (CTimeCycle::GetSunCoreRed() + 2*255)/3.0f; + int g = intens * (CTimeCycle::GetSunCoreGreen() + 2*255)/3.0f; + int b = intens * (CTimeCycle::GetSunCoreBlue() + 2*255)/3.0f; + + CColModel *colmodel = GetColModel(); + CCollision::CalculateTrianglePlanes(colmodel); + + int i; + for(i = 0; i < colmodel->numTriangles-2; i += 2){ + int a1 = colmodel->triangles[i].a; + int b1 = colmodel->triangles[i].b; + int c1 = colmodel->triangles[i].c; + int a2 = colmodel->triangles[i+1].a; + int b2 = colmodel->triangles[i+1].b; + int c2 = colmodel->triangles[i+1].c; + CVector vert1 = colmodel->vertices[a1].Get(); + CVector vert4; + // Need an upward surface + if(vert1.z <= 0.0f) + continue; + + // trying to find a quad here + int numTri2Verts = 0; + if(a2 != a1 && a2 != b1 && a2 != c1){ + // a2 is not in tri1 + numTri2Verts++; + vert4 = colmodel->vertices[a2].Get(); + } + if(b2 != a1 && b2 != b1 && b2 != c1){ + // b2 is not in tri1 + numTri2Verts++; + vert4 = colmodel->vertices[b2].Get(); + } + if(c2 != a1 && c2 != b1 && c2 != c1){ + // c2 is not in tri1 + numTri2Verts++; + vert4 = colmodel->vertices[c2].Get(); + } + // Need exactly one vertex from tri2 for a quad with tri1 + if(numTri2Verts != 1) + continue; + + CVector mid = (vert1 + colmodel->vertices[b1].Get() + colmodel->vertices[c1].Get() + vert4)/4.0f; + float dy = mid.y - vert1.y; + float dx = mid.x - vert1.x; + float dist = 1.4f * Min(Abs(dx), Abs(dy)); + if(dist > 0.6f){ + CVector pos = GetMatrix() * (dist * localGlareVec + mid) + camDir; + CCoronas::RegisterCorona((uintptr)this + 27 + i, + r, g, b, 255, + pos, 0.9f*CWeather::SunGlare, 90.0f, + CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, + CCoronas::REFLECTION_OFF, CCoronas::LOSCHECK_OFF, + CCoronas::STREAK_OFF, 0.0f); + } + } +} + +void +CVehicle::KillPedsInVehicle(void) +{ + int i; + if(pDriver){ + CDarkel::RegisterKillByPlayer(pDriver, WEAPONTYPE_EXPLOSION); + if(pDriver->GetPedState() == PED_DRIVING){ + pDriver->SetDead(); + if(!pDriver->IsPlayer()) + pDriver->FlagToDestroyWhenNextProcessed(); + }else + pDriver->SetDie(); + } + 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(); + } + } +} + +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, 208); + AutoPilot.Save(buf); + WriteSaveBuf(buf, m_currentColour1); + WriteSaveBuf(buf, m_currentColour2); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_nAlarmState); + ZeroSaveBuf(buf, 42); + WriteSaveBuf(buf, m_nNumMaxPassengers); + ZeroSaveBuf(buf, 3); + 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, 12); + WriteSaveBuf(buf, m_nTimeOfDeath); + ZeroSaveBuf(buf, 2); + WriteSaveBuf(buf, m_nBombTimer); + ZeroSaveBuf(buf, 12); + WriteSaveBuf(buf, m_nDoorLock); + ZeroSaveBuf(buf, 108); +} + +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, 208); + AutoPilot.Load(buf); + ReadSaveBuf(&m_currentColour1, buf); + ReadSaveBuf(&m_currentColour2, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_nAlarmState, buf); + SkipSaveBuf(buf, 42); + ReadSaveBuf(&m_nNumMaxPassengers, buf); + SkipSaveBuf(buf, 3); + 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, 12); + ReadSaveBuf(&m_nTimeOfDeath, buf); + SkipSaveBuf(buf, 2); + ReadSaveBuf(&m_nBombTimer, buf); + SkipSaveBuf(buf, 12); + ReadSaveBuf(&m_nDoorLock, buf); + SkipSaveBuf(buf, 108); +} +#endif + +eVehicleAppearance +CVehicle::GetVehicleAppearance(void) +{ + uint32 flags = pHandling->Flags & 0xF0000; + if (flags == 0) + return VEHICLE_APPEARANCE_CAR; + if (flags == HANDLING_IS_BIKE) + return VEHICLE_APPEARANCE_BIKE; + if (flags == HANDLING_IS_HELI) + return VEHICLE_APPEARANCE_HELI; + if (flags == HANDLING_IS_PLANE) + return VEHICLE_APPEARANCE_PLANE; + if (flags == HANDLING_IS_BOAT) + return VEHICLE_APPEARANCE_BOAT; + return VEHICLE_APPEARANCE_NONE; +} + +bool +IsVehiclePointerValid(CVehicle* pVehicle) +{ + if (!pVehicle) + return false; + int index = CPools::GetVehiclePool()->GetJustIndex_NoFreeAssert(pVehicle); +#ifdef FIX_BUGS + if (index < 0 || index >= NUMVEHICLES) +#else + if (index < 0 || index > NUMVEHICLES) +#endif + return false; + return pVehicle->m_vehType == VEHICLE_TYPE_PLANE || pVehicle->m_entryInfoList.first; +} diff --git a/src/miami/vehicles/Vehicle.h b/src/miami/vehicles/Vehicle.h new file mode 100644 index 00000000..f83e1bb0 --- /dev/null +++ b/src/miami/vehicles/Vehicle.h @@ -0,0 +1,451 @@ +#pragma once + +#include "Physical.h" +#include "AutoPilot.h" +#include "ModelIndices.h" +#include "AnimationId.h" +#include "WeaponType.h" +#include "Collision.h" +#include "HandlingMgr.h" + +class CPed; +class CPlayerPed; +class CCopPed; +class CFire; + +enum { + RANDOM_VEHICLE = 1, + MISSION_VEHICLE = 2, + PARKED_VEHICLE = 3, + PERMANENT_VEHICLE = 4, +}; + +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 { + 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 +}; + +enum eCarLock { + CARLOCK_NOT_USED, + CARLOCK_UNLOCKED, + CARLOCK_LOCKED, + CARLOCK_LOCKOUT_PLAYER_ONLY, + CARLOCK_LOCKED_PLAYER_INSIDE, + CARLOCK_LOCKED_INITIALLY, + CARLOCK_FORCE_SHUT_DOORS, + CARLOCK_LOCKED_BUT_CAN_BE_DAMAGED +}; + +enum eBombType +{ + CARBOMB_NONE, + CARBOMB_TIMED, + CARBOMB_ONIGNITION, + CARBOMB_REMOTE, + CARBOMB_TIMEDACTIVE, + CARBOMB_ONIGNITIONACTIVE, +}; + +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, + FLIGHT_MODEL_RCPLANE, + FLIGHT_MODEL_RCHELI, + FLIGHT_MODEL_SEAPLANE, + FLIGHT_MODEL_PLANE_UNUSED, + FLIGHT_MODEL_PLANE, + FLIGHT_MODEL_HELI +}; + +enum eVehicleAppearance +{ + VEHICLE_APPEARANCE_NONE, + VEHICLE_APPEARANCE_CAR, + VEHICLE_APPEARANCE_BIKE, + VEHICLE_APPEARANCE_HELI, + VEHICLE_APPEARANCE_BOAT, + VEHICLE_APPEARANCE_PLANE, +}; + +// 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 +}; + +enum +{ + ROTOR_TOP = 3, + ROTOR_FRONT = 4, + ROTOR_RIGHT = 5, + ROTOR_LEFT = 7, + ROTOR_BACK = 8, + ROTOR_BOTTOM = 9, +}; + +class CVehicle : public CPhysical +{ +public: + tHandlingData *pHandling; + tFlyingHandlingData *pFlyingHandling; + CAutoPilot AutoPilot; + uint8 m_currentColour1; + uint8 m_currentColour2; + int8 m_aExtras[2]; + int16 m_nAlarmState; + int16 m_nRouteSeed; + 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 + uint8 bPartOfConvoy : 1; + uint8 bHeliMinimumTilt : 1; // This heli should have almost no tilt really + uint8 bAudioChangingGear : 1; // sounds like vehicle is changing gear + + uint8 bIsDrowning : 1; // is vehicle occupants taking damage in water (i.e. vehicle is dead in water) + uint8 bTyresDontBurst : 1; // If this is set the tyres are invincible + uint8 bCreatedAsPoliceVehicle : 1;// True if this guy was created as a police vehicle (enforcer, policecar, miamivice car etc) + uint8 bRestingOnPhysical : 1; // Dont go static cause car is sitting on a physical object that might get removed + uint8 bParking : 1; + uint8 bCanPark : 1; +#if (!defined GTA_PS2 || defined FIX_BUGS) + uint8 m_bombType : 3; +#endif + uint8 bDriverLastFrame : 1; + + 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; + float m_fHealth; // 1000.0f = full health. 250.0f = fire. 0 -> explode + uint8 m_nCurrentGear; + float m_fChangeGearTime; +#if (!defined GTA_PS2 || defined FIX_BUGS) + CEntity* m_pBombRigger; +#endif + uint32 m_nSetPieceExtendedRangeTime; + 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 + CEntity *m_pLastDamageEntity; + uint8 m_nRadioStation; + uint8 m_bRainAudioCounter; + uint8 m_bRainSamplesCounter; + uint32 m_nCarHornTimer; + uint8 m_nCarHornPattern; + bool m_bSirenOrAlarm; + uint8 m_nCarHornDelay; + 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 bool IsDoorReady(uint32 door) { return false; } + virtual bool IsDoorMissing(uint32 door) { return false; } + virtual bool IsOpenTopCar(void) { return false; } + virtual void RemoveRefsToVehicle(CEntity *ent) {} + virtual void BlowUpCar(CEntity *ent) {} + virtual bool SetUpWheelColModel(CColModel *colModel) { return false; } + virtual void BurstTyre(uint8 tyre, bool applyForces) {} + virtual bool IsRoomForPedToLeaveCar(uint32 component, CVector *forcedDoorPos) { return false; } + virtual bool IsClearToDriveAway(void); + virtual float GetHeightAboveRoad(void); + virtual void PlayCarHorn(void) {} +#ifdef COMPATIBLE_SAVES + virtual void Save(uint8*& buf); + virtual void Load(uint8*& buf); +#endif + + eVehicleAppearance GetVehicleAppearance(void); + 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); + bool DoBladeCollision(CVector pos, CMatrix &matrix, int16 rotorType, float radius, float damageMult); + bool BladeColSectorList(CPtrList &list, CColModel &rotorColModel, CMatrix &matrix, int16 rotorType, float damageMult); + + 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, float destabTraction, 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); + int FindTyreNearestPoint(float x, float y); + bool IsLawEnforcementVehicle(void); + void ChangeLawEnforcerState(uint8 enable); + bool UsesSiren(void); + bool IsVehicleNormal(void); + bool CarHasRoof(void); + bool IsUpsideDown(void); + bool IsOnItsSide(void); + bool CanBeDeleted(void); + bool CanPedOpenLocks(CPed *ped); + bool CanDoorsBeDamaged(void); + bool CanPedEnterCar(void); + bool CanPedExitCar(bool jumpExit); + bool CanPedJumpOutCar(void); + bool CanPedJumpOffBike(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); + bool IsDriver(CPed *ped); + bool IsDriver(int32 model); + bool IsPassenger(CPed *ped); + bool IsPassenger(int32 model); + void UpdatePassengerList(void); + void ProcessCarAlarm(void); + bool IsSphereTouchingVehicle(float sx, float sy, float sz, float radius); + bool ShufflePassengersToMakeSpace(void); + void MakeNonDraggedPedsLeaveVehicle(CPed *ped1, CPed *ped2, CPlayerPed *&player, CCopPed *&cop); + void InflictDamage(CEntity *damagedBy, eWeaponType weaponType, float damage, CVector pos = CVector(0.0f, 0.0f, 0.0f)); + void DoFixedMachineGuns(void); + void FireFixedMachineGuns(void); + void ActivateBomb(void); + void ActivateBombWhenEntered(void); + void KillPedsInVehicle(void); + + void SetComponentAtomicAlpha(RpAtomic *atomic, int32 alpha); + void UpdateClumpAlpha(void); + + static void HeliDustGenerate(CEntity *heli, float radius, float ground, int rnd); + void DoSunGlare(void); + + bool IsAlarmOn(void) { return m_nAlarmState != 0 && m_nAlarmState != -1 && GetStatus() != STATUS_WRECKED; } + CVehicleModelInfo* GetModelInfo() { return (CVehicleModelInfo*)CModelInfo::GetModelInfo(GetModelIndex()); } + bool IsTaxi(void) { return GetModelIndex() == MI_TAXI || GetModelIndex() == MI_CABBIE || GetModelIndex() == MI_ZEBRA || GetModelIndex() == MI_KAUFMAN; } + bool IsLimo(void) { return GetModelIndex() == MI_STRETCH || GetModelIndex() == MI_LOVEFIST; } + bool IsRealHeli(void) { return !!(pHandling->Flags & HANDLING_IS_HELI); } + bool IsRealPlane(void) { return !!(pHandling->Flags & HANDLING_IS_PLANE); } + + static bool bWheelsOnlyCheat; + static bool bAllDodosCheat; + static bool bCheat3; + static bool bCheat4; + static bool bCheat5; + static bool bCheat8; + static bool bCheat9; + static bool bCheat10; + static bool bHoverCheat; + static bool bAllTaxisHaveNitro; + static bool m_bDisableMouseSteering; + static bool bDisableRemoteDetonation; + static bool bDisableRemoteDetonationOnContact; +#ifndef MASTER + static bool m_bDisplayHandlingInfo; +#endif +}; + +void DestroyVehicleAndDriverAndPassengers(CVehicle* pVehicle); +bool IsVehiclePointerValid(CVehicle* pVehicle); + +// Names of functions below are made up by us. + +// Used in III and VC. +inline int8 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; + } +} + +// VC. Accounts the case numMaxPassengers == 0, only for m_nGettingInFlags. +inline int8 GetEnterCarDoorFlag(int32 carnode, uint8 numMaxPassengers) { + switch (carnode) { + case CAR_DOOR_RF: + return CAR_DOOR_FLAG_RF; + case CAR_DOOR_RR: + return CAR_DOOR_FLAG_RR; + case CAR_DOOR_LF: + if (numMaxPassengers != 0) + return CAR_DOOR_FLAG_LF; + else + return CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_LR; + case CAR_DOOR_LR: + if (numMaxPassengers != 0) + return CAR_DOOR_FLAG_LR; + else + return CAR_DOOR_FLAG_LF | CAR_DOOR_FLAG_LR; + default: + return CAR_DOOR_FLAG_UNKNOWN; + } +} diff --git a/src/miami/weapons/BulletInfo.cpp b/src/miami/weapons/BulletInfo.cpp new file mode 100644 index 00000000..8bd4e5f1 --- /dev/null +++ b/src/miami/weapons/BulletInfo.cpp @@ -0,0 +1,341 @@ +#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" +#include "Heli.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_TYRE_POP_SMOKES (4) +#define NUM_OTHER_SPARKS (8) +#define BULLET_HIT_FORCE (7.5f) + +#define BULLET_BOUNDARY_MIN_X -2400.0f +#define BULLET_BOUNDARY_MAX_X 1600.0f +#define BULLET_BOUNDARY_MIN_Y -2000.0f +#define BULLET_BOUNDARY_MAX_Y 2000.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 + 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; + + if ( vecNewPos.x <= BULLET_BOUNDARY_MIN_X || vecNewPos.x >= BULLET_BOUNDARY_MAX_X || vecNewPos.y <= BULLET_BOUNDARY_MIN_Y || vecNewPos.y >= BULLET_BOUNDARY_MAX_Y ) { + pBullet->m_bInUse = false; + continue; + } + CWorld::bIncludeDeadPeds = true; + CWorld::bIncludeBikers = true; + CWorld::bIncludeCarTyres = true; + CWorld::pIgnoreEntity = pBullet->m_pSource; + CColPoint point; + CEntity* pHitEntity; + if (CWorld::ProcessLineOfSight(vecOldPos, vecNewPos, point, pHitEntity, true, true, true, true, true, false, false, true)) { + + CWeapon::CheckForShootingVehicleOccupant(&pHitEntity, &point, pBullet->m_eWeaponType, vecOldPos, vecNewPos); + if (pHitEntity->IsPed()) { + CPed* pPed = (CPed*)pHitEntity; + if (!pPed->DyingOrDead() && pPed != pBullet->m_pSource) { + 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); + + 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()) { + CEntity *source = pBullet->m_pSource; + if ( !source || !source->IsPed() || ((CPed*)source)->m_attachedTo != pHitEntity) { + if ( point.pieceB >= CAR_PIECE_WHEEL_LF && point.pieceB <= CAR_PIECE_WHEEL_RR ) { + ((CVehicle*)pHitEntity)->BurstTyre(point.pieceB, true); + for (int j=0; jInflictDamage(source, pBullet->m_eWeaponType, pBullet->m_nDamage); + if ( pBullet->m_eWeaponType == WEAPONTYPE_FLAMETHROWER ) { + gFireManager.StartFire(pHitEntity, pBullet->m_pSource, 0.8f, 1); + } else { + for (int j=0; jm_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); + CEntity *source = pBullet->m_pSource; + if ( !source || !source->IsPed() || ((CPed*)source)->m_attachedTo != pHitEntity) { + if (pHitEntity->IsObject()) { + CObject *pHitObject = (CObject*)pHitEntity; + if ( !pHitObject->bInfiniteMass && pHitObject->m_fCollisionDamageMultiplier < 99.9f) { + bool notStatic = !pHitObject->GetIsStatic(); + if (notStatic && pHitObject->m_fUprootLimit <= 0.0f) { + pHitObject->bIsStatic = false; + pHitObject->AddToMovingList(); + } + + notStatic = !pHitObject->GetIsStatic(); + if (!notStatic) { + CVector moveForce = point.normal * -BULLET_HIT_FORCE; + pHitObject->ApplyMoveForce(moveForce.x, moveForce.y, moveForce.z); + } + } else if (pHitObject->m_nCollisionDamageEffect >= DAMAGE_EFFECT_SMASH_COMPLETELY) { + pHitObject->ObjectDamage(50.f); + } + } + } +#ifdef FIX_BUGS + pBullet->m_bInUse = false; +#ifdef SQUEEZE_PERFORMANCE + bulletInfoInUse--; +#endif + vecNewPos = point.point; +#endif + } + if (pBullet->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE || pBullet->m_eWeaponType == WEAPONTYPE_LASERSCOPE) { + cAudioScriptObject* pAudio; + switch (pHitEntity->GetType()) { + case ENTITY_TYPE_BUILDING: + if (!DMAudio.IsAudioInitialised()) + break; + + pAudio = new cAudioScriptObject(); + if (pAudio) + pAudio->Reset(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_1; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_OBJECT: + if (!DMAudio.IsAudioInitialised()) + break; + + pAudio = new cAudioScriptObject(); + if (pAudio) + pAudio->Reset(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_2; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_DUMMY: + if (!DMAudio.IsAudioInitialised()) + break; + + pAudio = new cAudioScriptObject(); + if (pAudio) + pAudio->Reset(); + pAudio->Posn = pHitEntity->GetPosition(); + pAudio->AudioId = SCRIPT_SOUND_BULLET_HIT_GROUND_3; + pAudio->AudioEntity = AEHANDLE_NONE; + DMAudio.CreateOneShotScriptObject(pAudio); + break; + case ENTITY_TYPE_PED: + ++CStats::BulletsThatHit; + DMAudio.PlayOneShot(((CPed*)pHitEntity)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)pHitEntity)->Say(SOUND_PED_BULLET_HIT); + break; + case ENTITY_TYPE_VEHICLE: + ++CStats::BulletsThatHit; + 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; + CWorld::bIncludeBikers = false; + if (pBullet->m_eWeaponType == WEAPONTYPE_SNIPERRIFLE || pBullet->m_eWeaponType == WEAPONTYPE_LASERSCOPE) { + bPlayerSniperBullet = true; + PlayerSniperBulletStart = pBullet->m_vecPosition; + PlayerSniperBulletEnd = vecNewPos; + } + pBullet->m_vecPosition = vecNewPos; + CHeli::TestSniperCollision(&PlayerSniperBulletStart, &PlayerSniperBulletEnd); + } +} + +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/miami/weapons/BulletInfo.h b/src/miami/weapons/BulletInfo.h new file mode 100644 index 00000000..cf1dd27f --- /dev/null +++ b/src/miami/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/miami/weapons/Explosion.cpp b/src/miami/weapons/Explosion.cpp new file mode 100644 index 00000000..7683ed97 --- /dev/null +++ b/src/miami/weapons/Explosion.cpp @@ -0,0 +1,514 @@ +#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 }; + +const RwRGBA colAddExplosion = { 160, 160, 160, 255 }; +const RwRGBA colGrenade = { 96, 96, 96, 255 }; + +int AudioHandle = AEHANDLE_NONE; + +void +CExplosion::Initialise() +{ + debug("Initialising CExplosion...\n"); + ClearAllExplosions(); + AudioHandle = DMAudio.CreateEntity(AUDIOTYPE_EXPLOSION, (void*)1); + if (AudioHandle >= 0) + DMAudio.SetEntityStatus(AudioHandle, TRUE); + debug("CExplosion ready\n"); +} + +void +CExplosion::ClearAllExplosions() +{ + 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; + gaExplosion[i].m_bMakeSound = true; + } +} + +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; +} + +bool +CExplosion::DoesExplosionMakeSound(uint8 id) +{ + return gaExplosion[id].m_bMakeSound; +}; + +CVector * +CExplosion::GetExplosionPosition(uint8 id) +{ + return &gaExplosion[id].m_vecPosition; +} + +bool +#ifdef SIMPLER_MISSIONS +CExplosion::AddExplosion(CEntity* explodingEntity, CEntity* culprit, eExplosionType type, const CVector& pos, uint32 lifetime, bool makeSound, float radius) +#else +CExplosion::AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32 lifetime, bool makeSound) +#endif +{ + CVector pPosn; + CVector posGround; + + RwRGBA colorMedium = colMedExpl; + RwRGBA color = colAddExplosion; + RwRGBA colorGrenade = colGrenade; + bool bDontExplode = false; + 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_bMakeSound = makeSound; + explosion.m_nParticlesExpireTime = lifetime != 0 ? CTimer::GetTimeInMilliseconds() + lifetime : 0; + switch (type) + { + case EXPLOSION_GRENADE: +#ifdef SIMPLER_MISSIONS + explosion.m_fRadius = (radius == -1.0f ? 9.0f : radius); +#else + explosion.m_fRadius = 9.0f; +#endif + 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) { + uint8 tmp = CGeneral::GetRandomNumberInRange(0, 64) - 64; + colorGrenade.green += tmp; + colorGrenade.blue += tmp; + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, explosion.m_vecPosition, CVector(0.0f, 0.0f, 0.0f), nil, 4.5f, colorGrenade); + } + 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; + float tmp = CWorld::FindGroundZFor3DCoord(posGround.x, posGround.y, posGround.z + 3.0f, &found); + if (found) + posGround.z = tmp; + + 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 if (found) { + gFireManager.StartFire(posGround, 1.8f, false); + } + 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: + case EXPLOSION_BOAT: + 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) { + CVehicle *veh = (CVehicle*)explosion.m_pVictimEntity; + CVector componentPos; + + if (veh->IsBike()) { + veh->GetComponentWorldPosition(BIKE_FORKS_REAR, componentPos); + } else if (veh->IsComponentPresent(CAR_BUMP_REAR) && veh->IsComponentPresent(CAR_WHEEL_LB)) { //mb it's another enum + CVector tmpVec; + veh->GetComponentWorldPosition(CAR_BUMP_REAR, componentPos); + veh->GetComponentWorldPosition(CAR_WHEEL_LB, tmpVec); + componentPos += tmpVec; + componentPos /= 2.0f; + } else if (veh->IsComponentPresent(CAR_BOOT)) { + veh->GetComponentWorldPosition(CAR_BOOT, componentPos); + } + if (componentPos.x != 0.0f) { + int rn = (CGeneral::GetRandomNumber() & 1) + 1; + for (int i = 0; i < rn; i++) + CParticle::AddJetExplosion(componentPos, (CGeneral::GetRandomNumber() & 7) / 7.0f + 1.5f, 0.5f); + } + } + break; + case EXPLOSION_HELI: + case EXPLOSION_HELI2: + if (type == EXPLOSION_HELI2) { + explosion.m_fRadius = 12.0f; + explosion.m_fPower = 500.0f; + } else { + 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; + + randpos.x = CGeneral::GetRandomNumber(); + randpos.y = CGeneral::GetRandomNumber(); + randpos.z = CGeneral::GetRandomNumber(); + randpos -= CVector(128, 128, 128); + randpos /= 20.0f; + randpos += pos; + + CParticle::AddParticle(PARTICLE_EXPLOSION_MFAST, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 2.5f, color); + + randpos.x = CGeneral::GetRandomNumber(); + randpos.y = CGeneral::GetRandomNumber(); + randpos.z = CGeneral::GetRandomNumber(); + randpos -= CVector(128, 128, 128); + randpos /= 20.0f; + randpos += pos; + + CParticle::AddParticle(PARTICLE_EXPLOSION_LFAST, randpos, CVector(0.0f, 0.0f, 0.0f), nil, 5.0f, color); + + randpos.x = CGeneral::GetRandomNumber(); + randpos.y = CGeneral::GetRandomNumber(); + randpos.z = CGeneral::GetRandomNumber(); + randpos -= CVector(128, 128, 128); + randpos /= 20.0f; + randpos += pos; + + 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; + randpos.x = CGeneral::GetRandomNumber(); + randpos.y = CGeneral::GetRandomNumber(); + randpos.z = CGeneral::GetRandomNumber(); + randpos -= CVector(128, 128, 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; + default: + debug("Undefined explosion type, AddExplosion, Explosion.cpp"); + 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)); + + if (type == EXPLOSION_MOLOTOV) { + TheCamera.CamShake(0.2f, pos.x, pos.y, pos.z); + } else { + 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_HELI2: + 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; + if (CWorld::ProcessVerticalLine(point1, -1000.0f, colPoint, pEntity, true, false, false, false, true, false, nil)) + explosion.m_fZshift = colPoint.point.z; + else + explosion.m_fZshift = explosion.m_vecPosition.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 = 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: + case EXPLOSION_BOAT: + if (someTime >= 3500) { + if (explosion.m_pVictimEntity != nil) { + if ((CGeneral::GetRandomNumber() & 0xF) == 0 && !explosion.m_bIsBoat) { + 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, 0.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); + } + } + 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 *= (explosion.m_nIteration + 1) * 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; + default: + 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/miami/weapons/Explosion.h b/src/miami/weapons/Explosion.h new file mode 100644 index 00000000..e76c99ea --- /dev/null +++ b/src/miami/weapons/Explosion.h @@ -0,0 +1,58 @@ +#pragma once + +class CEntity; +class CVector; + +enum eExplosionType +{ + EXPLOSION_GRENADE, + EXPLOSION_MOLOTOV, + EXPLOSION_ROCKET, + EXPLOSION_CAR, + EXPLOSION_CAR_QUICK, + EXPLOSION_BOAT, + EXPLOSION_HELI, + EXPLOSION_HELI2, + 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; + bool m_bIsBoat; + bool m_bMakeSound; + float m_fStartTime; + uint32 m_nParticlesExpireTime; + float m_fPower; + float m_fZshift; +public: +#ifdef SIMPLER_MISSIONS + static bool AddExplosion(CEntity *explodingEntity, CEntity *culprit, eExplosionType type, const CVector &pos, uint32 lifetime, bool makeSound = true, float radius = -1.0f); +#else + static bool AddExplosion(CEntity* explodingEntity, CEntity* culprit, eExplosionType type, const CVector& pos, uint32 lifetime, bool makeSound = true); +#endif + static void ClearAllExplosions(); //done + static bool DoesExplosionMakeSound(uint8 id); //done + static int8 GetExplosionActiveCounter(uint8 id); //done + static CVector *GetExplosionPosition(uint8 id); //done + static uint8 GetExplosionType(uint8 id); //done, mb need change type to tExplosionType + static void Initialise(); //done + static void RemoveAllExplosionsInArea(CVector pos, float radius); //done + static void ResetExplosionActiveCounter(uint8 id); //done + static void Shutdown(); //done + static void Update(); //done + static bool TestForExplosionInArea(eExplosionType type, float x1, float x2, float y1, float y2, float z1, float z2); //done, not used +}; + +extern CExplosion gaExplosion[NUM_EXPLOSIONS]; \ No newline at end of file diff --git a/src/miami/weapons/ProjectileInfo.cpp b/src/miami/weapons/ProjectileInfo.cpp new file mode 100644 index 00000000..10aa3ef5 --- /dev/null +++ b/src/miami/weapons/ProjectileInfo.cpp @@ -0,0 +1,437 @@ +#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]; + +#define PROJECTILE_BOUNDARY_MIN_X -2390.0f +#define PROJECTILE_BOUNDARY_MAX_X 1590.0f +#define PROJECTILE_BOUNDARY_MIN_Y -1990.0f +#define PROJECTILE_BOUNDARY_MAX_Y 1990.0f + +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_ROCKET: + { + float vy = 0.35f; + time = CTimer::GetTimeInMilliseconds() + 2000; + if (entity->GetModelIndex() == MI_SPARROW || entity->GetModelIndex() == MI_HUNTER || entity->GetModelIndex() == MI_SENTINEL) { + matrix = ped->GetMatrix(); + matrix.GetPosition() = pos; + CVector vecSpeed = ((CPhysical*)entity)->m_vecMoveSpeed; + vy += Max(0.0f, DotProduct(vecSpeed, entity->GetForward())) + Max(0.0f, DotProduct(vecSpeed, entity->GetUp())); + } else { + 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_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_TEARGAS: + { + time = CTimer::GetTimeInMilliseconds() + 20000; + 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; + } + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_DETONATOR_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: + Error("Undefined projectile type, AddProjectile, ProjectileInfo.cpp"); + 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_ROCKET: + ms_apProjectile[i] = new CProjectile(MI_MISSILE); + break; + case WEAPONTYPE_TEARGAS: + ms_apProjectile[i] = new CProjectile(MI_TEARGAS); + break; + case WEAPONTYPE_MOLOTOV: + ms_apProjectile[i] = new CProjectile(MI_MOLOTOV); + break; + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_DETONATOR_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(); + + if (entity && entity->IsPed() && !ped->m_pCollidingEntity) { + ped->m_pCollidingEntity = ms_apProjectile[i]; + } + return true; +} + +void +CProjectileInfo::RemoveProjectile(CProjectileInfo *info, CProjectile *projectile) +{ + // TODO(Miami): New parameter: 1 + switch (info->m_eWeaponType) { + case WEAPONTYPE_GRENADE: + CExplosion::AddExplosion(nil, info->m_pSource, EXPLOSION_GRENADE, projectile->GetPosition(), 0); + break; + case WEAPONTYPE_MOLOTOV: + CExplosion::AddExplosion(nil, info->m_pSource, EXPLOSION_MOLOTOV, projectile->GetPosition(), 0); + break; + case WEAPONTYPE_ROCKET: + CExplosion::AddExplosion(nil, info->m_pSource->IsVehicle() ? ((CVehicle*)info->m_pSource)->pDriver : info->m_pSource, EXPLOSION_ROCKET, projectile->GetPosition(), 0); + break; + } +#ifdef SQUEEZE_PERFORMANCE + projectileInUse--; +#endif + + info->m_bInUse = false; + CWorld::Remove(projectile); + delete projectile; +} + +void +CProjectileInfo::RemoveNotAdd(CEntity *entity, eWeaponType weaponType, CVector pos) +{ + // TODO(Miami): New parameter: 1 + 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_ROCKET: + CExplosion::AddExplosion(nil, entity, EXPLOSION_ROCKET, pos, 0); + break; + } +} + +void +CProjectileInfo::Update() +{ +#ifdef SQUEEZE_PERFORMANCE + if (projectileInUse == 0) + return; +#endif + + int tearGasOffset = -0.0f; // unused + + 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_DETONATOR_GRENADE || gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_TEARGAS) && ms_apProjectile[i]->m_fElasticity > 0.1f ) { + if ( Abs(ms_apProjectile[i]->m_vecMoveSpeed.x) < 0.05f && Abs(ms_apProjectile[i]->m_vecMoveSpeed.y) < 0.05f && Abs(ms_apProjectile[i]->m_vecMoveSpeed.z) < 0.05f ) { + ms_apProjectile[i]->m_fElasticity = 0.03f; + } + } + const CVector &projectilePos = ms_apProjectile[i]->GetPosition(); + CVector nextPos = CTimer::GetTimeStep() * ms_apProjectile[i]->m_vecMoveSpeed + projectilePos; + + if ( nextPos.x <= PROJECTILE_BOUNDARY_MIN_X || nextPos.x >= PROJECTILE_BOUNDARY_MAX_X || nextPos.y <= PROJECTILE_BOUNDARY_MIN_Y || nextPos.y >= PROJECTILE_BOUNDARY_MAX_Y ) { + // Not RemoveProjectile, because we don't want no explosion + gaProjectileInfo[i].m_bInUse = false; + CWorld::Remove(ms_apProjectile[i]); + delete ms_apProjectile[i]; + continue; + } + if ( gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_TEARGAS && CTimer::GetTimeInMilliseconds() > gaProjectileInfo[i].m_nExplosionTime - 19500 ) { + CParticle::AddParticle(PARTICLE_TEARGAS, projectilePos, CVector(0.2f, tearGasOffset, 0.0f), 0, 0.0f, 0, 0, 0, 0); + CParticle::AddParticle(PARTICLE_TEARGAS, projectilePos, CVector(-0.2f, tearGasOffset, 0.0f), 0, 0.0f, 0, 0, 0, 0); + CParticle::AddParticle(PARTICLE_TEARGAS, projectilePos, CVector(tearGasOffset, tearGasOffset, 0.0f), 0, 0.0f, 0, 0, 0, 0); + + if ( CTimer::GetTimeInMilliseconds() & 0x200 ) + CWorld::SetPedsChoking(projectilePos.x, projectilePos.y, projectilePos.z, 6.0f, gaProjectileInfo[i].m_pSource); + } + + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKET) { + CParticle::AddParticlesAlongLine(PARTICLE_ROCKET_SMOKE, gaProjectileInfo[i].m_vecPos, projectilePos, CVector(0.0f, 0.0f, 0.0f), 0.7f, 0, 0, 0, 3000); + } + + if (CTimer::GetTimeInMilliseconds() <= gaProjectileInfo[i].m_nExplosionTime || gaProjectileInfo[i].m_nExplosionTime == 0) { + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_ROCKET) { + 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) + || CHeli::TestRocketCollision(&pos) || CPlane::TestRocketCollision(&pos)) { + RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]); + } + CWorld::pIgnoreEntity = nil; + ms_apProjectile[i]->m_vecMoveSpeed *= 1.07f; + + } 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)) { + RemoveProjectile(&gaProjectileInfo[i], ms_apProjectile[i]); + } + } + CWorld::pIgnoreEntity = nil; + } + } else { + if (gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_DETONATOR_GRENADE) { + CEntity *ent = gaProjectileInfo[i].m_pSource; + if (ent->IsPed() && ((CPed*)ped)->IsPlayer()) { + CPed *ped = (CPed*)ent; + if (ped->GetWeapon(ped->GetWeaponSlot(WEAPONTYPE_DETONATOR)).m_eWeaponType != WEAPONTYPE_DETONATOR + || ped->GetWeapon(ped->GetWeaponSlot(WEAPONTYPE_DETONATOR)).m_nAmmoTotal == 0) { + gaProjectileInfo[i].m_nExplosionTime = 0; + } + } + } 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_ROCKET || 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::RemoveDetonatorProjectiles() +{ + for (int i = 0; i < ARRAY_SIZE(ms_apProjectile); i++) { + if (gaProjectileInfo[i].m_bInUse && gaProjectileInfo[i].m_eWeaponType == WEAPONTYPE_DETONATOR_GRENADE) { + CExplosion::AddExplosion(nil, gaProjectileInfo[i].m_pSource, EXPLOSION_GRENADE, gaProjectileInfo[i].m_vecPos, 0); // TODO(Miami): New parameter: 1 + gaProjectileInfo[i].m_bInUse = false; + CWorld::Remove(ms_apProjectile[i]); + delete ms_apProjectile[i]; + } + } +} + +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/miami/weapons/ProjectileInfo.h b/src/miami/weapons/ProjectileInfo.h new file mode 100644 index 00000000..d1688948 --- /dev/null +++ b/src/miami/weapons/ProjectileInfo.h @@ -0,0 +1,34 @@ +#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 RemoveDetonatorProjectiles(); + 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/miami/weapons/ShotInfo.cpp b/src/miami/weapons/ShotInfo.cpp new file mode 100644 index 00000000..788bcbe1 --- /dev/null +++ b/src/miami/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); + } +} diff --git a/src/miami/weapons/ShotInfo.h b/src/miami/weapons/ShotInfo.h new file mode 100644 index 00000000..db6158c2 --- /dev/null +++ b/src/miami/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/miami/weapons/Weapon.cpp b/src/miami/weapons/Weapon.cpp new file mode 100644 index 00000000..2d219e2d --- /dev/null +++ b/src/miami/weapons/Weapon.cpp @@ -0,0 +1,3384 @@ +#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 "SurfaceTable.h" +#include "Bike.h" +#include "Glass.h" +#include "Sprite.h" +#include "Pickups.h" +#include "SaveBuf.h" + +float fReloadAnimSampleFraction[5] = { 0.5f, 0.7f, 0.75f, 0.75f, 0.7f }; +float fSeaSparrowAimingAngle = 10.0f; +float fHunterAimingAngle = 30.0f; +float fPlayerAimScaleDist = 5.0f; +float fPlayerAimScale = 2.5f; + +bool CWeapon::bPhotographHasBeenTaken; + +#ifdef SECUROM +int32 sniperPirateCheck = 0x00797743; // 'Cwy\0' ??? +#endif + +#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((CCamera::m_f3rdPersonCHairMultX - 0.5f) * 1.8f * 0.5f * TheCamera.Cams[TheCamera.ActiveCam].FOV * CDraw::GetAspectRatio()); + float angleY = DEGTORAD((0.5f - CCamera::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; +} + +CWeapon::CWeapon(eWeaponType type, int32 ammo) +{ + m_eWeaponType = type; + m_eWeaponState = WEAPONSTATE_READY; + m_nAmmoTotal = Min(ammo, 99999); + m_nAmmoInClip = 0; + Reload(); + m_nTimer = 0; + m_bAddRotOffset = false; +} + +void +CWeapon::InitialiseWeapons(void) +{ + CWeaponInfo::Initialise(); + CShotInfo::Initialise(); + CExplosion::Initialise(); + CProjectileInfo::Initialise(); + CBulletInfo::Initialise(); + bPhotographHasBeenTaken = false; +} + +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; + m_nAmmoTotal = Min(ammo, 99999); + m_nAmmoInClip = 0; + Reload(); + m_nTimer = 0; + int32 modelId = CWeaponInfo::GetWeaponInfo(m_eWeaponType)->m_nModelId; + int32 model2Id = CWeaponInfo::GetWeaponInfo(m_eWeaponType)->m_nModel2Id; + + if ( modelId != -1 ) + CModelInfo::GetModelInfo(modelId)->AddRef(); + if ( model2Id != -1 ) + CModelInfo::GetModelInfo(model2Id)->AddRef(); +} + +void +CWeapon::Shutdown() +{ + int32 modelId = CWeaponInfo::GetWeaponInfo(m_eWeaponType)->m_nModelId; + if (modelId != -1) + CModelInfo::GetModelInfo(modelId)->RemoveRef(); + + int32 model2Id = CWeaponInfo::GetWeaponInfo(m_eWeaponType)->m_nModel2Id; + if (model2Id != -1) + CModelInfo::GetModelInfo(model2Id)->RemoveRef(); + + m_eWeaponType = WEAPONTYPE_UNARMED; + m_eWeaponState = WEAPONSTATE_READY; + m_nAmmoInClip = 0; + m_nAmmoTotal = 0; + m_nTimer = 0; +} + +bool +CWeapon::Fire(CEntity *shooter, CVector *fireSource) +{ + ASSERT(shooter!=nil); + + CVector fireOffset(0.0f, 0.0f, 0.6f); + CVector *source = fireSource; +#ifdef FIX_BUGS + static CVector shooterSource; +#else + CVector shooterSource; +#endif + + if ( !fireSource ) + { + shooterSource = shooter->GetMatrix() * fireOffset; + source = &shooterSource; + } + + 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; + bool addFireRateAsDelay = true; + + if ( GetInfo()->m_eWeaponFire != WEAPON_FIRE_MELEE ) + { + if (m_nAmmoInClip <= 0) { + if (m_nAmmoTotal <= 0 || m_eWeaponState == WEAPONSTATE_RELOADING) + return false; + + Reload(); + } + + switch ( m_eWeaponType ) + { + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_SPAS12_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + { + addFireRateAsDelay = true; + fired = FireShotgun(shooter, source); + + break; + } + + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + { + if (shooter == FindPlayerPed()) + fired = FireSniper(shooter); + else + fired = FireInstantHit(shooter, source); + + break; + } + + case WEAPONTYPE_COLT45: + case WEAPONTYPE_PYTHON: + case WEAPONTYPE_UZI: + case WEAPONTYPE_TEC9: + case WEAPONTYPE_SILENCED_INGRAM: + case WEAPONTYPE_MP5: + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + case WEAPONTYPE_M60: + case WEAPONTYPE_MINIGUN: + case WEAPONTYPE_HELICANNON: + { + if ((TheCamera.PlayerWeaponMode.Mode == CCam::MODE_HELICANNON_1STPERSON || TheCamera.PlayerWeaponMode.Mode == CCam::MODE_M16_1STPERSON) + && shooter == FindPlayerPed()) { + addFireRateAsDelay = true; + fired = FireM16_1stPerson(shooter); + } else { + addFireRateAsDelay = false; + 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: + case WEAPONTYPE_DETONATOR_GRENADE: + case WEAPONTYPE_TEARGAS: + { + if ( shooter == FindPlayerPed() ) + { + fired = FireProjectile(shooter, source, ((CPlayerPed*)shooter)->m_fAttackButtonCounter*0.0375f); + } + 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); + + if (m_eWeaponType == WEAPONTYPE_DETONATOR_GRENADE) { + ((CPed*)shooter)->GiveWeapon(WEAPONTYPE_DETONATOR, 1, true); + ((CPed*)shooter)->GetWeapon(((CPed*)shooter)->GetWeaponSlot(WEAPONTYPE_DETONATOR)).m_eWeaponState = WEAPONSTATE_READY; + ((CPed*)shooter)->SetCurrentWeapon(WEAPONTYPE_DETONATOR); + } + 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_CAMERA: + { + fired = TakePhotograph(shooter); + + break; + } + + default: + { + debug("Unknown weapon type, Weapon.cpp"); + break; + } + } + + if (fired) + { + bool isPlayer = false; + + if (shooter->IsPed()) + { + CPed* shooterPed = (CPed*)shooter; + + if ( m_eWeaponType != WEAPONTYPE_CAMERA ) + { + shooterPed->bIsShooting = true; + + if (shooterPed->IsPlayer()) + isPlayer = true; + } + + DMAudio.PlayOneShot(shooterPed->m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + + if ( isPlayer ) + { + CPed *aimPed = (CPed *)shooterPed->m_pSeekTarget; + if ( aimPed ) + { + if ( aimPed->IsPed() ) + shooterPed->Say(SOUND_PED_ON_FIRE); + } + } + } + + switch ( m_eWeaponType ) + { + case WEAPONTYPE_COLT45: + case WEAPONTYPE_PYTHON: + case WEAPONTYPE_SHOTGUN: + case WEAPONTYPE_SPAS12_SHOTGUN: + case WEAPONTYPE_STUBBY_SHOTGUN: + case WEAPONTYPE_TEC9: + case WEAPONTYPE_UZI: + case WEAPONTYPE_SILENCED_INGRAM: + case WEAPONTYPE_MP5: + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + case WEAPONTYPE_M60: + case WEAPONTYPE_MINIGUN: + CStats::RoundsFiredByPlayer++; + break; + + case WEAPONTYPE_GRENADE: + case WEAPONTYPE_DETONATOR_GRENADE: + case WEAPONTYPE_MOLOTOV: + case WEAPONTYPE_ROCKET: + case WEAPONTYPE_ROCKETLAUNCHER: + case WEAPONTYPE_DETONATOR: + case WEAPONTYPE_HELICANNON: + CStats::KgsOfExplosivesUsed++; + break; + } + + + if (m_nAmmoInClip > 0) + m_nAmmoInClip--; + + if (m_nAmmoTotal > 0 && (m_nAmmoTotal < 25000 || isPlayer) && (!isPlayer || CStats::GetPercentageProgress() < 100.0f || m_eWeaponType == WEAPONTYPE_DETONATOR)) + 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) { + if (TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_CAMERA) + CPad::GetPad(0)->Clear(false); + + 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; + } + + if ( addFireRateAsDelay ) + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nFiringRate; + else + m_nTimer = CTimer::GetTimeInMilliseconds(); + } + } + else + { + if ( m_eWeaponState != WEAPONSTATE_FIRING ) + { + m_nTimer = CTimer::GetTimeInMilliseconds() + GetInfo()->m_nReload; + m_eWeaponState = WEAPONSTATE_FIRING; + + if (shooter->IsPed() && m_eWeaponType != WEAPONTYPE_CHAINSAW) + { + DMAudio.PlayOneShot(((CPed*)shooter)->m_audioEntityId, SOUND_MELEE_ATTACK_START, m_eWeaponType << 8); + } + } + + fired = FireMelee(shooter, *source); + } + + if ( m_eWeaponType == WEAPONTYPE_UNARMED || m_eWeaponType == WEAPONTYPE_BASEBALLBAT ) + return true; + else + return fired; +} + +bool +CWeapon::FireFromCar(CVehicle *shooter, bool left, bool right) +{ + ASSERT(shooter!=nil); + + if ( m_eWeaponState != WEAPONSTATE_READY && m_eWeaponState != WEAPONSTATE_FIRING ) + return false; + + if ( m_nAmmoInClip <= 0 ) + return false; + + if ( FireInstantHitFromCar(shooter, left, right) ) + { + DMAudio.PlayOneShot(shooter->m_audioEntityId, SOUND_WEAPON_SHOT_FIRED, 0.0f); + + if ( m_nAmmoInClip > 0 ) + m_nAmmoInClip--; + + if ( m_nAmmoTotal < 25000 && m_nAmmoTotal > 0 && (!shooter || shooter->GetStatus() != STATUS_PLAYER || CStats::GetPercentageProgress() < 100.f)) + 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; + } + + return true; +} + +bool +CWeapon::FireMelee(CEntity *shooter, CVector &fireSource) +{ + ASSERT(shooter!=nil); + + CWeaponInfo *info = GetInfo(); + + bool anim2Playing = false; + + if ( CPed::GetFireAnimGround(info, false) != (AnimationId)0 ) + { + if ( RpAnimBlendClumpGetAssociation(shooter->GetClump(), CPed::GetFireAnimGround(info, false)) ) + anim2Playing = true; + } + + ASSERT(shooter->IsPed()); + + CPed *shooterPed = (CPed*)shooter; + + if (shooterPed == FindPlayerPed()) + { + if (m_eWeaponType == WEAPONTYPE_GOLFCLUB || m_eWeaponType == WEAPONTYPE_NIGHTSTICK || + (m_eWeaponType >= WEAPONTYPE_BASEBALLBAT && m_eWeaponType <= WEAPONTYPE_CHAINSAW)) + { + CGlass::BreakGlassPhysically(fireSource, info->m_fRadius); + + if (m_eWeaponType == WEAPONTYPE_CHAINSAW) + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, FindPlayerPed(), FindPlayerPed(), 1000); + } + } + + int damageEntityRegistered = 0; + + 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) + && (!shooterPed->IsGangMember() || victimPed->CanBeDamagedByThisGangMember(shooterPed)) ) + { + bool collided = false; + + if (victimPed->m_nPedState == PED_DRIVING && (m_eWeaponType == WEAPONTYPE_UNARMED || m_eWeaponType == WEAPONTYPE_BRASSKNUCKLE + || info->IsFlagSet(WEAPONFLAG_FIGHTMODE))) + continue; + + 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; + CColModel* victimPedCol = &CTempColModels::ms_colModelPed1; + bool useLocalPos = false; + if (victimPed->m_nPedState == PED_FALL + || victimPed->m_nPedState == PED_DIE && victimPed->bIsPedDieAnimPlaying + || victimPed->m_nWaitState == WAITSTATE_SIT_IDLE + || victimPed->m_nWaitState == WAITSTATE_SUN_BATHE_IDLE) + { + useLocalPos = true; + victimPedCol = ((CPedModelInfo*)CModelInfo::GetModelInfo(victimPed->GetModelIndex()))->AnimatePedColModelSkinnedWorld(victimPed->GetClump()); + } else if (victimPed->DyingOrDead()) { + victimPedCol = &CTempColModels::ms_colModelPedGroundHit; + } + + int32 s = 0; + while ( s < victimPedCol->numSpheres ) + { + CColSphere *sphere = &victimPedCol->spheres[s]; + + if (useLocalPos) + collisionDist = sphere->center - fireSource; + else + 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 isHeavy = m_eWeaponType >= WEAPONTYPE_GOLFCLUB && m_eWeaponType <= WEAPONTYPE_KATANA && m_eWeaponType != WEAPONTYPE_HAMMER; + + if (shooterPed->m_fDamageImpulse == 0.0f) + { + shooterPed->m_pDamageEntity = victimPed; + victimPed->RegisterReference(&shooterPed->m_pDamageEntity); + } + + damageEntityRegistered = 3; + if (victimPed->bInVehicle) + { + CVehicle *victimVeh = victimPed->m_pMyVehicle; + if (victimVeh) + { + if (victimVeh->IsBike()) + { + CBike *victimBike = (CBike*)victimVeh; + victimBike->KnockOffRider(m_eWeaponType, localDir, victimPed, false); + if (victimBike->pDriver) + victimBike->pDriver->ReactToAttack(shooterPed); + else + { + if (victimVeh->pPassengers[0]) + victimVeh->pPassengers[0]->ReactToAttack(shooterPed); + } + continue; + } + } + } + + if ( !victimPed->DyingOrDead() ) + victimPed->ReactToAttack(shooterPed); + + uint8 hitLevel = HITLEVEL_HIGH; + if ( isHeavy && (victimPed->OnGround() || victimPed->m_nWaitState == WAITSTATE_SUN_BATHE_IDLE)) + hitLevel = HITLEVEL_GROUND; + + victimPed->StartFightDefend(localDir, hitLevel, 10); + + if ( !victimPed->DyingOrDead() ) + { + if ( shooterPed->IsPlayer() && isHeavy && 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() && isHeavy ) // 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 && 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 ( isHeavy ) + { + 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 (m_eWeaponType == WEAPONTYPE_CHAINSAW) + { + if (victimPed->m_nPedState != PED_DEAD && !((CTimer::GetFrameCounter() + 17) & 1) + || victimPed->m_nPedState == PED_DEAD && !((CTimer::GetFrameCounter() + 17) & 3)) + { + CParticle::AddParticle(PARTICLE_TEST, bloodPos, CVector(0.0f, 0.0f, 0.0f), nil, 0.2f); + } + CVector newDir(dir); + newDir.z += 0.2f; + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, bloodPos, newDir); + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, newDir); + newDir.z = dir.z + 0.1f; + CParticle::AddParticle(PARTICLE_BLOOD, bloodPos, newDir); + newDir.x = 0.0f; + newDir.y = 0.0f; + newDir.z = 0.01f; + CParticle::AddParticle(PARTICLE_DEBRIS2, bloodPos, newDir); + + CVector dropDir(CGeneral::GetRandomNumberInRange(-0.15f, 0.15f), CGeneral::GetRandomNumberInRange(0.1f, 0.35f), 0.f); + CVector dropPos(CGeneral::GetRandomNumberInRange(SCREEN_STRETCH_X(50.0f), SCREEN_STRETCH_FROM_RIGHT(50.0f)), + CGeneral::GetRandomNumberInRange(SCREEN_STRETCH_Y(50.0f), SCREEN_STRETCH_FROM_BOTTOM(50.0f)), 1.f); + CParticle::AddParticle(PARTICLE_BLOODDROP, dropPos, dropDir, nil, CGeneral::GetRandomNumberInRange(0.1f, 0.15f), + CRGBA(0, 0, 0, 0), 0, 0, CGeneral::GetRandomNumber() & 1, 0); + + } + if (info->m_AnimToPlay == ASSOCGRP_KNIFE) + { + dir += 0.1f * shooterPed->GetUp() + 0.05f * shooterPed->GetRight(); + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, dir); + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, dir); + CParticle::AddParticle(PARTICLE_BLOOD_SPURT, bloodPos, dir); + } + } + + if ( !victimPed->OnGround() ) + { + if ( victimPed->m_fHealth > 0.0f + && (victimPed->m_fHealth < 30.0f && victimPedHealth > 30.0f || + (isHeavy || m_eWeaponType == WEAPONTYPE_BRASSKNUCKLE) && !victimPed->IsPlayer()) ) + { + posOffset.Normalise(); + victimPed->bIsStanding = false; + if(m_eWeaponType == WEAPONTYPE_CHAINSAW) + victimPed->ApplyMoveForce(posOffset.x*-2.0f, posOffset.y*-2.0f, 2.0f); + else + victimPed->ApplyMoveForce(posOffset.x*-5.0f, posOffset.y*-5.0f, 3.0f); + + if ( isHeavy && 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; + if(m_eWeaponType == WEAPONTYPE_CHAINSAW) + victimPed->ApplyMoveForce(posOffset.x*-1.0f, posOffset.y*-1.0f, 1.0f); + else + victimPed->ApplyMoveForce(posOffset.x*-5.0f, posOffset.y*-5.0f, 3.0f); + } + + m_eWeaponState = WEAPONSTATE_MELEE_MADECONTACT; + + if (m_eWeaponType != WEAPONTYPE_KNIFE && m_eWeaponType != WEAPONTYPE_MACHETE + && m_eWeaponType != WEAPONTYPE_KATANA && m_eWeaponType != WEAPONTYPE_CHAINSAW) { + + 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); + } else { + if (victimPed->m_nPedType == PEDTYPE_COP) + CEventList::RegisterEvent(EVENT_ASSAULT_NASTYWEAPON_POLICE, EVENT_ENTITY_PED, victimPed, shooterPed, 2000); + else + CEventList::RegisterEvent(EVENT_ASSAULT_NASTYWEAPON, EVENT_ENTITY_PED, victimPed, shooterPed, 2000); + } + } + } + } + } + } + } + CVehicle *nearVeh = (CVehicle*)CWorld::TestSphereAgainstWorld(fireSource, info->m_fRadius, nil, false, true, false, false, false, false); + if (nearVeh && nearVeh->IsCar()) + { + CAutomobile *nearCar = (CAutomobile*)nearVeh; + m_eWeaponState = WEAPONSTATE_MELEE_MADECONTACT; + if (shooterPed == FindPlayerPed()) + { + if (nearCar->IsLawEnforcementVehicle()) + { + FindPlayerPed()->SetWantedLevelNoDrop(1); + } + CEventList::RegisterEvent(EVENT_ASSAULT, EVENT_ENTITY_VEHICLE, nearCar, shooterPed, 2000); + } + float oldHealth = nearCar->m_fHealth; + if (m_eWeaponType == WEAPONTYPE_CHAINSAW) + { + for( int32 i=0; i<4; i++ ) + { + CParticle::AddParticle(PARTICLE_SPARK_SMALL, gaTempSphereColPoints[0].point, CVector(0.0f, 0.0f, 0.3f)); + CParticle::AddParticle(PARTICLE_SPARK, gaTempSphereColPoints[0].point, gaTempSphereColPoints[0].normal * 0.1f); + } + } + if (m_eWeaponType == WEAPONTYPE_CHAINSAW) + { + nearCar->VehicleDamage(info->m_nDamage * (0.00075f * nearCar->pHandling->fMass), gaTempSphereColPoints[0].pieceB); + + CParticle::AddParticle(PARTICLE_HEATHAZE, gaTempSphereColPoints[0].point, CVector(0.0f, 0.0f, 0.0f), 0, 0.0f, 0, 0, 0, 0); + } + else + { + nearCar->VehicleDamage(info->m_nDamage* (0.01f * nearCar->pHandling->fMass), gaTempSphereColPoints[0].pieceB); + } + if (nearCar->m_fHealth < oldHealth) + { + nearCar->m_nLastWeaponDamage = m_eWeaponType; + nearCar->m_pLastDamageEntity = shooterPed; + } + if (shooterPed->m_fDamageImpulse == 0.0f) + { + shooterPed->m_pDamageEntity = nearCar; + nearCar->RegisterReference(&shooterPed->m_pDamageEntity); + } + damageEntityRegistered = 2; + if (FindPlayerPed()->GetWeapon() == this && nearCar->VehicleCreatedBy != MISSION_VEHICLE) + { + if (nearCar->AutoPilot.m_nDrivingStyle != DRIVINGSTYLE_PLOUGH_THROUGH + && (CGeneral::GetRandomTrueFalse() || nearCar->AutoPilot.m_nCarMission != MISSION_CRUISE)) + { + int leaveCarDelay = 200; + CPed *driver = nearCar->pDriver; + if (driver && driver->CharCreatedBy != MISSION_CHAR) + { + if (driver->m_pedStats->m_temper <= driver->m_pedStats->m_fear) + { + driver->SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + } + else + { + driver->SetObjective(OBJECTIVE_KILL_CHAR_ON_FOOT, FindPlayerPed()); + driver->m_objectiveTimer = CTimer::GetTimeInMilliseconds() + 10000; + driver->m_prevObjective = OBJECTIVE_KILL_CHAR_ON_FOOT; + } + driver->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + 200; + leaveCarDelay = 400; + } + for (int j = 0; j < nearCar->m_nNumPassengers; ++j) + { + CPed *passenger = nearCar->pPassengers[j]; + if (passenger && passenger->CharCreatedBy != MISSION_CHAR) + { + nearCar->pPassengers[j]->SetObjective(OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE); + passenger->m_leaveCarTimer = CTimer::GetTimeInMilliseconds() + leaveCarDelay; + leaveCarDelay += 200; + } + } + } + else + { + CPed *driver = nearCar->pDriver; + if (driver) + { + if (driver->m_objective != OBJECTIVE_LEAVE_CAR && driver->m_objective != OBJECTIVE_KILL_CHAR_ON_FOOT && + driver->m_objective != OBJECTIVE_FLEE_ON_FOOT_TILL_SAFE) + { + if (nearCar->AutoPilot.m_nDrivingStyle != DRIVINGSTYLE_PLOUGH_THROUGH) + nearCar->AutoPilot.m_nCruiseSpeed = nearCar->AutoPilot.m_nCruiseSpeed * 1.5f; + + nearCar->AutoPilot.m_nDrivingStyle = DRIVINGSTYLE_PLOUGH_THROUGH; + } + } + } + } + } + if (m_eWeaponType == WEAPONTYPE_CHAINSAW) + { + CEntity *nearStatic = (CObject*)CWorld::TestSphereAgainstWorld(fireSource, info->m_fRadius, nil, true, false, false, true, false, false); + if (nearStatic) + { + for(int i=0; i < 4; i++) { + CParticle::AddParticle(PARTICLE_SPARK_SMALL, gaTempSphereColPoints[0].point, CVector(0.0f, 0.0f, 0.3f), 0, 0.0f, 0, 0, 0, 0); + CParticle::AddParticle(PARTICLE_SPARK, gaTempSphereColPoints[0].point, 0.1f * gaTempSphereColPoints[0].normal, 0, 0.0f, 0, 0, 0, 0); + } + + CParticle::AddParticle(PARTICLE_HEATHAZE, gaTempSphereColPoints[0].point, CVector(0.0f, 0.0f, 0.0f), 0, 0.0f, 0, 0, 0, 0); + + if (!damageEntityRegistered) + { + m_eWeaponState = WEAPONSTATE_MELEE_MADECONTACT; + if (shooterPed->m_fDamageImpulse == 0.0f) + { + shooterPed->m_pDamageEntity = nearStatic; + nearStatic->RegisterReference(&shooterPed->m_pDamageEntity); + } + } + if (nearStatic->IsObject() && ((CObject*)nearStatic)->m_nCollisionDamageEffect >= DAMAGE_EFFECT_SMASH_COMPLETELY) + ((CObject*)nearStatic)->ObjectDamage(200.0f); + } + } + + 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; + + 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; + float distToTarget = Max(target.Magnitude(), 0.01f); + target *= info->m_fRange / distToTarget; + target += *fireSource; + + if (shooter == FindPlayerPed() && inaccuracy != 0.f) + { + float newInaccuracy = fPlayerAimScale * FindPlayerPed()->m_fAttackButtonCounter * (inaccuracy * Min(1.f, fPlayerAimScaleDist / distToTarget)); + if (FindPlayerPed()->bIsDucking) + newInaccuracy *= 0.4f; + + target.x += CGeneral::GetRandomNumberInRange(-0.15f, 0.15f) * newInaccuracy; + target.y += CGeneral::GetRandomNumberInRange(-0.15f, 0.15f) * newInaccuracy; + target.z += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * newInaccuracy; + FindPlayerPed()->m_fAttackButtonCounter += info->m_nDamage * 0.04f; + } + else if (inaccuracy > 0.f) + { + if (threatAttack == FindPlayerPed()) + { + float speed = Min(0.33f, FindPlayerPed()->m_vecMoveSpeed.Magnitude()); + inaccuracy *= (0.3f * speed * 100.f / 33.f + 0.8f); + } + 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; + } + + if (shooter == FindPlayerPed()) + CWorld::bIncludeDeadPeds = true; + + CWorld::bIncludeBikers = true; + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, false, false); + CWorld::bIncludeDeadPeds = false; + CWorld::bIncludeBikers = false; + } + else + { + target.x = info->m_fRange; + target.y = 0.0f; + target.z = 0.0f; + + shooterPed->TransformToNode(target, PED_HANDR); + + CWorld::bIncludeBikers = true; + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, false, false); + CWorld::bIncludeBikers = false; + } +#ifdef FIX_BUGS + // fix muzzleflash rotation + heading = CGeneral::GetAngleBetweenPoints(source.x, source.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() ) + { +#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); + + if ((shooterPed->m_pedIK.m_flags & CPedIK::GUN_POINTED_SUCCESSFULLY) == 0) { + target.x = info->m_fRange; + target.y = 0.0f; + target.z = 0.0f; + + shooterPed->TransformToNode(target, PED_HANDR); + } + } else +#endif + { + TheCamera.Find3rdPersonCamTargetVector(info->m_fRange, *fireSource, source, target); + } + +#ifdef FIX_BUGS + // fix muzzleflash rotation + heading = CGeneral::GetAngleBetweenPoints(source.x, source.y, target.x, target.y); + angle = DEGTORAD(heading); + + ahead = CVector2D(-Sin(angle), Cos(angle)); + ahead.Normalise(); +#endif + CWorld::bIncludeBikers = true; + CWorld::bIncludeDeadPeds = true; + CWorld::bIncludeCarTyres = true; + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, false, false); + CWorld::bIncludeBikers = false; + CWorld::bIncludeDeadPeds = false; + CWorld::bIncludeCarTyres = false; + + if (victim) + CheckForShootingVehicleOccupant(&victim, &point, m_eWeaponType, source, target); + + int32 rotSpeed = 1; + if ( m_eWeaponType == WEAPONTYPE_M4 ) + rotSpeed = 4; + + 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), nil, 0.0f, rotSpeed); + } + } + else + { + uint32 model = shooter->GetModelIndex(); + if (model == MI_HUNTER || model == MI_SEASPAR || model == MI_SPARROW) + { + float inaccuracyMult = 0.6f; + target = shooter->GetForward(); + if (shooter->GetStatus() == STATUS_PLAYER) + { + target *= info->m_fRange; + target += *fireSource; + CWeapon::DoDriveByAutoAiming(FindPlayerPed(), (CVehicle*)shooter, fireSource, &target); + target -= *fireSource; + target.Normalise(); + if (model == MI_SEASPAR || model == MI_SPARROW) + inaccuracyMult = 0.1f; + else + inaccuracyMult = 0.3f; + } + target.x += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * inaccuracyMult; + target.y += CGeneral::GetRandomNumberInRange(-0.2f, 0.2f) * inaccuracyMult; + target.z += CGeneral::GetRandomNumberInRange(-0.1f, 0.1f) * inaccuracyMult; + + target.Normalise(); + target *= info->m_fRange; + target += *fireSource; + CWorld::pIgnoreEntity = shooter; + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + CWorld::pIgnoreEntity = nil; + + int32 rotSpeed = 1; + if (m_eWeaponType == WEAPONTYPE_M4) + 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); + } + } + 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; + + CParticle::HandleShootableBirdsStuff(shooter, *fireSource); + if (shooter->IsPed() && ((CPed*)shooter)->bDoomAim && (shooter != FindPlayerPed() || !info->IsFlagSet(WEAPONFLAG_CANAIM))) + { + CWeapon::DoDoomAiming(shooter, fireSource, &target); + } + + CWorld::bIncludeBikers = true; + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, false, false); + CWorld::bIncludeBikers = false; + + int32 rotSpeed = 1; + if (m_eWeaponType == WEAPONTYPE_M4) + 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 ( shooter->IsPed() && victim) + { + if (victim == ((CPed*)shooter)->m_leader) + return false; + + if (victim->IsPed() && ((CPed*)shooter)->IsGangMember() && !((CPed*)victim)->CanBeDamagedByThisGangMember((CPed*)shooter)) + return false; + } + + if (shooter->IsPed()) + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, shooter, (CPed*)shooter, 1000); + else if (shooter->IsVehicle() && ((CVehicle*)shooter)->pDriver) + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_VEHICLE, shooter, ((CVehicle*)shooter)->pDriver, 1000); + + if ( shooter == FindPlayerPed() ) + { + if ( !(CTimer::GetFrameCounter() & 3) ) + MakePedsJumpAtShot((CPhysical*)shooter, fireSource, &target); + } + + switch ( m_eWeaponType ) + { + case WEAPONTYPE_M4: + case WEAPONTYPE_RUGER: + case WEAPONTYPE_M60: + case WEAPONTYPE_MINIGUN: + case WEAPONTYPE_HELICANNON: + { + static uint8 counter = 0; + + if ( info->m_nFiringRate >= 50 || !(++counter & 1) ) + { +#ifdef FIX_BUGS + AddGunFlashBigGuns(*fireSource, target); +#else + AddGunFlashBigGuns(*fireSource, *fireSource + target); +#endif + + 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: + case WEAPONTYPE_TEC9: + case WEAPONTYPE_SILENCED_INGRAM: + case WEAPONTYPE_MP5: + { + 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: + case WEAPONTYPE_PYTHON: + case WEAPONTYPE_SNIPERRIFLE: + case WEAPONTYPE_LASERSCOPE: + { + 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::AddGunFlashBigGuns(CVector start, CVector end) +{ + CPointLights::AddLight(CPointLights::LIGHT_POINT, + start, CVector(0.0f, 0.0f, 0.0f), 5.0f, + 1.0f, 0.8f, 0.0f, CPointLights::FOG_NONE, false); + CVector gunflashPos = start; + + CVector shootVec = end - start; + + // Wtf did you do there R*? + shootVec.Normalise(); + CVector2D ahead = shootVec; + ahead.Normalise(); + + 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 = start; + 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 = start; + 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 = start; + 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 = start; + 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 = start; + float rnd = CGeneral::GetRandomNumberInRange(0.05f, 0.25f); + CParticle::AddParticle(PARTICLE_GUNSMOKE2, gunsmokePos, CVector(ahead.x * rnd, ahead.y * rnd, 0.0f)); +} + +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 ) + { + if (shooter) + { + if (shooter && shooter->IsPed() && ((CPed*)shooter)->m_attachedTo == victim) + return; + + if (shooter->IsPed() && !((CPed*)shooter)->IsPlayer()) + { + CPed* shooterPed = (CPed*)shooter; + CEntity* guyWePointGun = shooterPed->m_pPointGunAt; + if (guyWePointGun) + { + if (victim != guyWePointGun) + { + float distWithAim = (guyWePointGun->GetPosition() - shooter->GetPosition()).Magnitude(); + float distWithBullet = (point->point - shooter->GetPosition()).Magnitude(); + if (distWithAim > 0.1f && distWithBullet > 0.1f) + { + // Normalize + CVector aimDir = (guyWePointGun->GetPosition() - shooter->GetPosition()) * (1.0f / distWithAim); + CVector bulletDir = (point->point - shooter->GetPosition()) * (1.0f / distWithBullet); + + float dotProd = DotProduct(aimDir, bulletDir); + float aimAndBulletAngle; + if (dotProd <= 0.35f) + aimAndBulletAngle = PI; + else + aimAndBulletAngle = Acos(dotProd); + + if (aimAndBulletAngle <= DEGTORAD(45.0f) && (aimAndBulletAngle <= DEGTORAD(15.0f) || distWithBullet / distWithAim >= 0.75f) && distWithBullet / distWithAim >= 0.99f) + { + shooterPed->bObstacleShowedUpDuringKillObjective = false; + shooterPed->m_shotTime = 0; + } + else + { + shooterPed->bObstacleShowedUpDuringKillObjective = true; + shooterPed->m_shootTimer = 0; + shooterPed->m_shotTime = CTimer::GetTimeInMilliseconds(); + if (distWithAim < 10.0f) + shooterPed->SetAttackTimer(1500); + else + shooterPed->SetAttackTimer(3000); + } + } + } + } + } + } + CGlass::WasGlassHitByBullet(victim, point->point); + + CVector traceTarget = point->point; + CBulletTraces::AddTrace(source, &traceTarget, m_eWeaponType, shooter); + + if (victim->IsPed() && shooter->IsVehicle() && ((CVehicle*)shooter)->pDriver) + shooter = ((CVehicle*)shooter)->pDriver; + + if ( victim->IsPed() && shooter->IsPed() && + (((CPed*)shooter)->m_nPedType != ((CPed*)victim)->m_nPedType || ((CPed*)shooter)->m_nPedType == PEDTYPE_PLAYER2 || + !((CPed*)shooter)->IsGangMember() && ((CPed*)shooter)->m_nPedType != PEDTYPE_COP)) + { + CPed *victimPed = (CPed *)victim; + if ( !victimPed->DyingOrDead() && victim != shooter ) + { + 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 ( victimPed->bCanBeShotInVehicle && (IsShotgun(m_eWeaponType) || + (!victimPed->IsPlayer() && (m_eWeaponType == WEAPONTYPE_HELICANNON || m_eWeaponType == WEAPONTYPE_M60 || m_eWeaponType == WEAPONTYPE_PYTHON)))) + { + 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->m_nPedState != PED_DRIVING ) + { + 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_M4 ) + 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 ( IsShotgun(m_eWeaponType) || 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); + } + + if (m_eWeaponType == WEAPONTYPE_MINIGUN) + { + CParticle::AddParticle(PARTICLE_TEST, point->point, CVector(0.f, 0.f, 0.f), nil, 0.f, 0, 0, 0, 0); + CParticle::AddParticle(PARTICLE_TEST, point->point + CVector(0.2f, -0.2f, 0.f), CVector(0.f, 0.f, 0.f), nil, 0.f, 0, 0, 0, 0); + CParticle::AddParticle(PARTICLE_TEST, point->point + CVector(-0.2f, 0.2f, 0.f), CVector(0.f, 0.f, 0.f), nil, 0.f, 0, 0, 0, 0); + } + } + } + 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); + float distMagnitude = dist.Magnitude(); + CVector smokePos = point->point - Max(distMagnitude / 10.0f, 0.2f) * dist / distMagnitude; +#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: + { + if (point->pieceB >= CAR_PIECE_WHEEL_LF && point->pieceB <= CAR_PIECE_WHEEL_RR) { + ((CVehicle*)victim)->BurstTyre(point->pieceB, true); + + for (int32 i = 0; i < 4; i++) + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, point->point, point->normal * 0.05f); + } + else + { + ((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 + + 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 && victimObject->m_fCollisionDamageMultiplier < 99.9f) + { + bool notStatic = !victimObject->GetIsStatic(); + if (notStatic && victimObject->m_fUprootLimit <= 0.0f) + { + victimObject->SetIsStatic(false); + victimObject->AddToMovingList(); + } + + notStatic = !victimObject->GetIsStatic(); + if (!notStatic) + { + CVector moveForce = point->normal * -4.0f; + victimObject->ApplyMoveForce(moveForce.x, moveForce.y, moveForce.z); + } + } else if (victimObject->m_nCollisionDamageEffect >= DAMAGE_EFFECT_SMASH_COMPLETELY) { + victimObject->ObjectDamage(50.f); + } + + break; + } + default: break; + } + } + + switch ( victim->GetType() ) + { + case ENTITY_TYPE_BUILDING: + { + PlayOneShotScriptObject(SCRIPT_SOUND_BULLET_HIT_GROUND_1, point->point); + break; + } + case ENTITY_TYPE_VEHICLE: + { + CStats::BulletsThatHit++; + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + CStats::BulletsThatHit++; + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + CStats::BulletsThatHit++; + 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, m_eWeaponType, shooter); + + 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()); + + int shootsAtOnce; + int checkObstacleOnShootNo; + float angleRange; + switch (m_eWeaponType) { + case WEAPONTYPE_SHOTGUN: + angleRange = DEGTORAD(9.0f); + checkObstacleOnShootNo = 1; + shootsAtOnce = 3; + break; + case WEAPONTYPE_SPAS12_SHOTGUN: + angleRange = DEGTORAD(6.0f); + checkObstacleOnShootNo = 1; + shootsAtOnce = 3; + break; + case WEAPONTYPE_STUBBY_SHOTGUN: + angleRange = DEGTORAD(18.0f); + checkObstacleOnShootNo = 2; + shootsAtOnce = 5; + break; + default: + break; + } + bool statUpdated = false; + float halfAngleRange = angleRange / 2.f; + float angleBetweenTwoShot = angleRange / (shootsAtOnce - 1.f); + + for ( int32 i = 0; i < shootsAtOnce; i++ ) + { + float shootAngle = DEGTORAD(RADTODEG(halfAngleRange - angleBetweenTwoShot * i) + shooterAngle); + 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 = (i - (shootsAtOnce / 2)) * angleBetweenTwoShot; + target = f * Left + target - source; + target *= info->m_fRange; + target += source; + CWorld::bIncludeCarTyres = true; + CWorld::bIncludeBikers = true; + CWorld::bIncludeDeadPeds = true; + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, false, false); + CWorld::bIncludeDeadPeds = false; + CWorld::bIncludeCarTyres = 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 + { + CVector pos; + if (shooterPed->m_pPointGunAt->IsPed()) { + ((CPed*)shooterPed->m_pPointGunAt)->m_pedIK.GetComponentPosition(pos, PED_MID); + } else { + pos = ((CPed*)shooterPed->m_pPointGunAt)->GetPosition(); + } + + float distToTarget = (pos - (*fireSource)).Magnitude2D(); + target.z += info->m_fRange / distToTarget * (pos.z - target.z); + } + } + if (shooter == FindPlayerPed()) + CWorld::bIncludeDeadPeds = true; + + CWorld::bIncludeBikers = true; + ProcessLineOfSight(*fireSource, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, false, false); + CWorld::bIncludeDeadPeds = false; + } + CWorld::bIncludeBikers = false; + + if ( victim ) + { + CGlass::WasGlassHitByBullet(victim, point.point); + CWeapon::BlowUpExplosiveThings(victim); + if (i == checkObstacleOnShootNo) + { + if (shooter) + { + if (shooter->IsPed() && !((CPed*)shooter)->IsPlayer()) + { + CPed *shooterPed = (CPed*)shooter; + CEntity *guyWePointGun = shooterPed->m_pPointGunAt; + if (guyWePointGun) + { + if (victim != guyWePointGun) + { + float distWithAim = (guyWePointGun->GetPosition() - shooter->GetPosition()).Magnitude(); + float distWithBullet = (point.point - shooter->GetPosition()).Magnitude(); + if (distWithAim > 0.1f && distWithBullet > 0.1f) + { + // Normalize + CVector aimDir = (guyWePointGun->GetPosition() - shooter->GetPosition()) * (1.0f / distWithAim); + CVector bulletDir = (point.point - shooter->GetPosition()) * (1.0f / distWithBullet); + + float dotProd = DotProduct(aimDir, bulletDir); + float aimAndBulletAngle; + if (dotProd <= 0.35f) + aimAndBulletAngle = PI; + else + aimAndBulletAngle = Acos(dotProd); + + if (aimAndBulletAngle <= DEGTORAD(45.0f) && (aimAndBulletAngle <= DEGTORAD(15.0f) || distWithBullet / distWithAim >= 0.75f) && distWithBullet / distWithAim >= 0.99f) + { + shooterPed->bObstacleShowedUpDuringKillObjective = false; + shooterPed->m_shotTime = 0; + } + else + { + shooterPed->bObstacleShowedUpDuringKillObjective = true; + shooterPed->m_shootTimer = 0; + shooterPed->m_shotTime = CTimer::GetTimeInMilliseconds(); + if (distWithAim < 10.0f) + shooterPed->SetAttackTimer(1500); + else + shooterPed->SetAttackTimer(3000); + } + } + } + } + } + } + } + CBulletTraces::AddTrace(fireSource, &point.point, m_eWeaponType, shooter); + + if ( victim->IsPed() ) + { + CPed *victimPed = (CPed *)victim; + if ( !victimPed->DyingOrDead() && victim != shooter ) + { + 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) || + !victimPed->bCanBeShotInVehicle) + 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 ( IsShotgun(m_eWeaponType) ) + 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 (victimPed->GetIsOnScreen()) + { + for (uint8 i = 0; i < 8; i++) + CParticle::AddParticle(PARTICLE_BLOOD_SMALL, point.point + CVector(0.0f, 0.0f, 0.15f), dir); + } + if (victimPed->Dead()) + { + CAnimBlendAssociation *hitAssoc; + if (RpAnimBlendClumpGetFirstAssociation(victimPed->GetClump(), ASSOC_FRONTAL)) + { + hitAssoc = CAnimManager::BlendAnimation(victimPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR_FRONT, 8.0f); + } + else + { + hitAssoc = CAnimManager::BlendAnimation(victimPed->GetClump(), ASSOCGRP_STD, ANIM_STD_HIT_FLOOR, 8.0f); + } + if (hitAssoc) + { + hitAssoc->SetCurrentTime(0.0f); + hitAssoc->SetRun(); + hitAssoc->flags &= ~ASSOC_DELETEFADEDOUT; + } + } + } + } + } + else + { + switch ( victim->GetType() ) + { + case ENTITY_TYPE_VEHICLE: + { + if (point.pieceB >= CAR_PIECE_WHEEL_LF && point.pieceB <= CAR_PIECE_WHEEL_RR) { + ((CVehicle*)victim)->BurstTyre(point.pieceB, true); + + for (int32 i = 0; i < 4; i++) + CParticle::AddParticle(PARTICLE_BULLETHIT_SMOKE, point.point, point.normal * 0.05f); + } + else + { + ((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 ) + { + bool notStatic = !victimObject->GetIsStatic(); + if ( notStatic && victimObject->m_fUprootLimit <= 0.0f ) + { + victimObject->SetIsStatic(false); + victimObject->AddToMovingList(); + } + + notStatic = !victimObject->GetIsStatic(); + if ( !notStatic ) + { + 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: + { + if (!statUpdated) { + CStats::BulletsThatHit++; + statUpdated = true; + } + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + if (!statUpdated) { + CStats::BulletsThatHit++; + statUpdated = true; + } + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + if (!statUpdated) { + CStats::BulletsThatHit++; + statUpdated = true; + } + 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, m_eWeaponType, shooter); + } + } + + 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; + eWeaponType projectileType = m_eWeaponType; + + if ( m_eWeaponType == WEAPONTYPE_ROCKETLAUNCHER ) + { + source = *fireSource; + projectileType = WEAPONTYPE_ROCKET; + + 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, projectileType, *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, WEAPONTYPE_GRENADE, source, 0.0f); + else + CProjectileInfo::RemoveNotAdd(shooter, WEAPONTYPE_GRENADE, *fireSource); + } + } + } + else + CProjectileInfo::AddProjectile(shooter, projectileType, *fireSource, power); + + + CWorld::pIgnoreEntity = nil; + + if ( shooter->IsPed() ) + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, shooter, (CPed *)shooter, 1000); + else if ( shooter->IsVehicle() && ((CVehicle*)shooter)->pDriver ) + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_VEHICLE, shooter, ((CVehicle*)shooter)->pDriver, 1000); + + 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); + + if ( shooter == (CEntity *)FindPlayerPed() ) + { + for ( int32 i = 0; i < FindPlayerPed()->m_numNearPeds; i++ ) + { + if ( FindPlayerPed()->m_nearPeds[i]->CharCreatedBy == RANDOM_CHAR ) + { + if ( FindPlayerPed()->m_nearPeds[i]->IsPedInControl() && FindPlayerPed()->m_nearPeds[i]->m_nPedState != PED_FLEE_ENTITY ) + FindPlayerPed()->m_nearPeds[i]->SetFlee(shooter, 10000); + } + } + } + + return true; +} + +bool +CWeapon::LaserScopeDot(CVector *pOutPos, float *pOutSize) +{ + CWeaponInfo *info = GetInfo(); + + float range = info->m_fRange; + + CVector source, target; + CEntity *foundEnt = nil; + CColPoint foundCol; + + source = 0.5f * TheCamera.Cams[TheCamera.ActiveCam].Front + TheCamera.Cams[TheCamera.ActiveCam].Source; + target = TheCamera.Cams[TheCamera.ActiveCam].Front; + target.Normalise(); + target *= range; + target += source; + + if ( CWorld::ProcessLineOfSight(source, target, foundCol, foundEnt, true, true, true, true, false, false, false) ) + { + CVector pos = foundCol.point; + float w, h; + + if ( CSprite::CalcScreenCoors(foundCol.point, &pos, &w, &h, true) ) + { + *pOutPos = pos; + *pOutSize = w * 0.05f; + + CCoronas::RegisterCorona((uintptr)this + 7, 128, 0, 0, 255, pos, 1.2f, 50.0f, CCoronas::TYPE_STAR, CCoronas::FLARE_NONE, CCoronas::REFLECTION_ON, CCoronas::LOSCHECK_OFF, CCoronas::STREAK_ON, 0.0f); + + return true; + } + } + + return false; +} + +bool +CWeapon::FireSniper(CEntity *shooter) +{ + ASSERT(shooter!=nil); + + if ( (CEntity *)FindPlayerPed() == shooter ) + { + int16 mode = TheCamera.Cams[TheCamera.ActiveCam].Mode; + if (!( mode == CCam::MODE_M16_1STPERSON + || mode == CCam::MODE_SNIPER + || mode == CCam::MODE_CAMERA + || mode == CCam::MODE_ROCKETLAUNCHER + || mode == CCam::MODE_M16_1STPERSON_RUNABOUT + || mode == CCam::MODE_SNIPER_RUNABOUT + || mode == CCam::MODE_ROCKETLAUNCHER_RUNABOUT) ) + { + return false; + } + } + +#ifdef SECUROM + if (sniperPirateCheck){ + // if not pirated game + // sniperPirateCheck = 0; + } +#endif + +#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::MoonSize = (CCoronas::MoonSize+1) & 7; + + dir.Normalise(); + dir *= 16.0f; + +#ifdef SECUROM + if (sniperPirateCheck) return true; +#endif + + CBulletInfo::AddBullet(shooter, m_eWeaponType, source, dir); + + if ( shooter == FindPlayerPed() ) + { + CPad::GetPad(0)->StartShake_Distance(240, 128, + FindPlayerPed()->GetPosition().x, + FindPlayerPed()->GetPosition().y, + FindPlayerPed()->GetPosition().z); + + CParticle::HandleShootableBirdsStuff(shooter, source); + + CamShakeNoPos(&TheCamera, 0.2f); + } + + if ( shooter->IsPed() ) + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, shooter, (CPed*)shooter, 1000); + else if ( shooter->IsVehicle() && ((CVehicle*)shooter)->pDriver ) + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_VEHICLE, shooter, ((CVehicle*)shooter)->pDriver, 1000); + + return true; +} + +bool +CWeapon::TakePhotograph(CEntity *shooter) +{ + if ( TheCamera.Cams[TheCamera.ActiveCam].Mode == CCam::MODE_CAMERA ) + { + CSpecialFX::bSnapShotActive = true; + CSpecialFX::SnapShotFrames = 0; + CStats::PhotosTaken++; + bPhotographHasBeenTaken = true; + + for ( int32 i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--) + { + CPed *ped = CPools::GetPedPool()->GetSlot(i); + if ( ped ) + { + if ( (ped->GetPosition() - TheCamera.GetPosition()).Magnitude() < 125.0f ) + { + CVector pedPos = ped->GetPosition(); + pedPos.z += 0.8f; + + CVector pos; + float w, h; + + if ( CSprite::CalcScreenCoors(pedPos, &pos, &w, &h, false) ) + { + if ( SCREEN_WIDTH * 0.1f < pos.x && SCREEN_WIDTH * 0.9f > pos.x + && SCREEN_HEIGHT * 0.1f < pos.y && SCREEN_HEIGHT * 0.9f > pos.y ) + { + CVector source, target; + CEntity *foundEnt = nil; + CColPoint foundCol; + + target = pedPos; + source = TheCamera.GetForward() * 2.0f + TheCamera.GetPosition(); + + if ( CWorld::ProcessLineOfSight(source, target, foundCol, foundEnt, true, true, true, true, true, true, false) ) + { + if ( foundEnt != (CEntity*)ped ) + continue; + } + + ped->bHasBeenPhotographed = true; + } + } + } + } + } + + return true; + } + + return false; +} + +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_CAMERA + || 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(); + + CWorld::bIncludeCarTyres = true; + CWorld::bIncludeBikers = true; + + CColPoint point; + CEntity *victim; + + 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; + + if (ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false)) { + CheckForShootingVehicleOccupant(&victim, &point, m_eWeaponType, source, target); + } + CWorld::pIgnoreEntity = nil; + CWorld::bIncludeDeadPeds = false; + CWorld::bIncludeBikers = false; + 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, (m_eWeaponType == WEAPONTYPE_M60 || m_eWeaponType == WEAPONTYPE_HELICANNON ? 20 : 4)) ) + { + for ( int32 i = 0; i < 16; i++ ) + CParticle::AddParticle(PARTICLE_SPARK, bulletPos, CVector(0.0f, 0.0f, 0.0f)); + } + + if ( shooter == FindPlayerPed() ) + { + float mult; + switch (m_eWeaponType) { + case WEAPONTYPE_M4: + mult = 0.0003f; + break; + case WEAPONTYPE_RUGER: + mult = 0.00015f; + break; + case WEAPONTYPE_HELICANNON: + case WEAPONTYPE_M60: + mult = 0.0003f; + break; + default: + mult = 0.0002f; + break; + } + + if (FindPlayerPed()->bIsDucking || FindPlayerPed()->m_attachedTo) + mult *= 0.3f; + + TheCamera.Cams[TheCamera.ActiveCam].Beta += float((CGeneral::GetRandomNumber() & 127) - 64) * mult; + TheCamera.Cams[TheCamera.ActiveCam].Alpha += float((CGeneral::GetRandomNumber() & 127) - 64) * mult; + + // yes, double + double notFiringRate = (20.0 - info->m_nFiringRate) / 80.0; + double raisedNotFiringRate = Max(1.0, Max(0.0, notFiringRate)); + + uint8 shakeFreq = 80.0 * raisedNotFiringRate + 130.0; + CPad::GetPad(0)->StartShake(20000.0f * CTimer::GetTimeStep() / shakeFreq, shakeFreq); + } + + return true; +} + +bool +CWeapon::FireInstantHitFromCar(CVehicle *shooter, bool left, bool right) +{ + CWeaponInfo *info = GetInfo(); + + CVehicleModelInfo *modelInfo = shooter->GetModelInfo(); + + CVector source, target; + + if ( shooter->IsBike() ) + { + if ( shooter->pDriver ) + { + source = info->m_vecFireOffset; + + shooter->pDriver->TransformToNode(source, PED_HANDR); + source += CTimer::GetTimeStep() * shooter->m_vecMoveSpeed; + + if ( left ) + target = source - info->m_fRange * shooter->GetRight(); + else if ( right ) + target = source + info->m_fRange * shooter->GetRight(); + else + target = source + info->m_fRange * shooter->GetForward(); + + } + else if ( left ) + { + source = shooter->GetMatrix() * CVector(-shooter->GetColModel()->boundingBox.max.x + -0.25f, + float(CGeneral::GetRandomNumber() & 255) * 0.001f + modelInfo->GetFrontSeatPosn().y - 0.05f, + modelInfo->GetFrontSeatPosn().z + 0.63f); + source += CTimer::GetTimeStep() * shooter->m_vecMoveSpeed; + + + target = shooter->GetMatrix() * CVector(-info->m_fRange, + modelInfo->GetFrontSeatPosn().y, + modelInfo->GetFrontSeatPosn().z + 0.6f); + } + else if ( right ) + { + source = shooter->GetMatrix() * CVector(shooter->GetColModel()->boundingBox.max.x + 0.25f, + float(CGeneral::GetRandomNumber() & 255) * 0.001f + modelInfo->GetFrontSeatPosn().y - 0.18f, + modelInfo->GetFrontSeatPosn().z + 0.52f); + 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(float(CGeneral::GetRandomNumber() & 255) * 0.001f + -0.4f, + modelInfo->GetFrontSeatPosn().y + shooter->GetColModel()->boundingBox.max.y + 0.2f, + modelInfo->GetFrontSeatPosn().z + 0.55f); + source += CTimer::GetTimeStep() * shooter->m_vecMoveSpeed; + + target = shooter->GetMatrix() * CVector(0.0f, + info->m_fRange, + modelInfo->GetFrontSeatPosn().z + 0.5f); + } + } + else + { + if ( left ) + source = info->m_vecFireOffset; + else + { + source = 1.8f * info->m_vecFireOffset; + source.z -= 0.1f; + } + + shooter->pDriver->TransformToNode(source, PED_HANDR); + source += CTimer::GetTimeStep() * shooter->m_vecMoveSpeed; + + if ( left ) + target = source - info->m_fRange * shooter->GetRight(); + else + target = source + info->m_fRange * shooter->GetRight(); + } + + 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(), shooter, &source, &target); + + CEventList::RegisterEvent(EVENT_GUNSHOT, EVENT_ENTITY_PED, FindPlayerPed(), FindPlayerPed(), 1000); + + if ( !TheCamera.GetLookingLRBFirstPerson() ) + { + if ( !shooter->IsBike() ) + CParticle::AddParticle(PARTICLE_GUNFLASH, source, CVector(0.0f, 0.0f, 0.0f)); + else + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, source, 1.4f*shooter->m_vecMoveSpeed); + } + else + CParticle::AddParticle(PARTICLE_GUNFLASH_NOANIM, source, 1.6f*shooter->m_vecMoveSpeed, nil, 0.18f); + + 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; + + CWorld::bIncludeBikers = true; + CWorld::pIgnoreEntity = shooter; + ProcessLineOfSight(source, target, point, victim, m_eWeaponType, shooter, true, true, true, true, true, true, false); + CWorld::pIgnoreEntity = NULL; + CWorld::bIncludeBikers = false; + + if ( !(CTimer::GetFrameCounter() & 3) ) + MakePedsJumpAtShot(shooter, &source, &target); + + if ( victim ) + { + CVector traceTarget = point.point; + CBulletTraces::AddTrace(&source, &traceTarget, m_eWeaponType, shooter); + + 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, m_eWeaponType, shooter); + } + + 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; + + 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 + || victim->GetStatus() == STATUS_WRECKED) ) + { + 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; + } + } + } + } + } + + CColPoint foundCol; + CEntity *foundEnt; + if (closestEntityDist < DOOMAUTOAIMING_MAXDIST + && !CWorld::ProcessLineOfSight(*source, entities[closestEntity]->GetPosition(), foundCol, foundEnt, true, false, false, false, false, false, false, true)) + { + 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 *driver, CVehicle *vehicle, CVector *source, CVector *target) +{ + ASSERT(driver!=nil); + ASSERT(source!=nil); + ASSERT(target!=nil); + +#ifndef FIX_BUGS + CEntity entity; // unused +#endif + + CPed *shooterPed = (CPed*)driver; + + int16 lastEntity; + CEntity *peds[16]; + CWorld::FindObjectsInRange(*source, (*target-*source).Magnitude(), true, &lastEntity, 15, peds, false, false, true, false, false); + + float closestEntityDist = 10000.0f; + int16 closestEntity; + + for ( int32 i = 0; i < lastEntity; i++ ) + { + CPed *victim = (CPed*)peds[i]; + ASSERT(victim!=nil); + + if (driver != victim && !victim->DyingOrDead() && victim->m_attachedTo != vehicle) + { + float lineDist = CCollision::DistToLine(source, target, &victim->GetPosition()); + + uint32 model = vehicle->GetModelIndex(); + float pedDist; + if (model == MI_HUNTER || model == MI_SEASPAR || model == MI_SPARROW) + { + float distToVictim = (victim->GetPosition() - vehicle->GetPosition()).Magnitude(); + pedDist = lineDist / Max(5.f, distToVictim); + } + else + { + float distToVictim = (victim->GetPosition() - driver->GetPosition()).Magnitude(); + pedDist = 0.15f * distToVictim + lineDist; + } + + if ( DotProduct((*target-*source), victim->GetPosition()-*source) > 0.0f && pedDist < closestEntityDist) + { + closestEntity = i; + closestEntityDist = pedDist; + } + } + } + uint32 model = vehicle->GetModelIndex(); + float maxAimDistance = CAR_DRIVEBYAUTOAIMING_MAXDIST; + if (model == MI_HUNTER) + { + maxAimDistance = Tan(DEGTORAD(fHunterAimingAngle)); + } + else if (model == MI_SEASPAR || model == MI_SPARROW) + { + maxAimDistance = Tan(DEGTORAD(fSeaSparrowAimingAngle)); + } + + if ( closestEntityDist < maxAimDistance ) + { + CEntity *victim = peds[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, CPed *pedToAdjustSound) +{ + CWeaponInfo *info = GetInfo(); + + switch ( m_eWeaponState ) + { + case WEAPONSTATE_MELEE_MADECONTACT: + { + m_eWeaponState = WEAPONSTATE_READY; + break; + } + + case WEAPONSTATE_FIRING: + { + if ( IsShotgun(m_eWeaponType) && AEHANDLE_IS_OK(audioEntity) ) + { + uint32 timePassed = m_nTimer - CWeaponInfo::ms_aReloadSampleTime[m_eWeaponType]; + 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; + CPickups::RemoveAllPickupsOfACertainWeaponGroupWithNoAmmo(m_eWeaponType); + } else + m_eWeaponState = WEAPONSTATE_READY; + } + + break; + } + + case WEAPONSTATE_RELOADING: + { + if ( AEHANDLE_IS_OK(audioEntity) && m_eWeaponType < WEAPONTYPE_TOTALWEAPONS) + { + CAnimBlendAssociation *reloadAssoc = nil; + if (pedToAdjustSound) { + if (CPed::GetReloadAnim(info) && (!CWorld::Players[CWorld::PlayerInFocus].m_bFastReload || !pedToAdjustSound->IsPlayer())) { + reloadAssoc = RpAnimBlendClumpGetAssociation(pedToAdjustSound->GetClump(), CPed::GetReloadAnim(info)); + if (!reloadAssoc) { + reloadAssoc = RpAnimBlendClumpGetAssociation(pedToAdjustSound->GetClump(), CPed::GetCrouchReloadAnim(info)); + } + } + } + if (reloadAssoc && reloadAssoc->IsRunning() && reloadAssoc->blendAmount > 0.2f) { + float soundStart = 0.75f; + switch (info->m_AnimToPlay) { + case ASSOCGRP_PYTHON: + soundStart = fReloadAnimSampleFraction[0]; + break; + case ASSOCGRP_COLT: + case ASSOCGRP_TEC: + soundStart = fReloadAnimSampleFraction[1]; + break; + case ASSOCGRP_UZI: + soundStart = fReloadAnimSampleFraction[2]; + break; + case ASSOCGRP_RIFLE: + soundStart = fReloadAnimSampleFraction[3]; + break; + case ASSOCGRP_M60: + soundStart = fReloadAnimSampleFraction[4]; + break; + default: + break; + } + if (reloadAssoc->GetProgress() >= soundStart && (reloadAssoc->currentTime - reloadAssoc->timeStep) / reloadAssoc->hierarchy->totalLength < soundStart) + DMAudio.PlayOneShot(audioEntity, SOUND_WEAPON_RELOAD, m_eWeaponType); + if (CTimer::GetTimeInMilliseconds() > m_nTimer && reloadAssoc->GetProgress() < 0.9f) { + m_nTimer = CTimer::GetTimeInMilliseconds(); + } + } else { + uint32 timePassed = m_nTimer - CWeaponInfo::ms_aReloadSampleTime[m_eWeaponType]; + if (CTimer::GetPreviousTimeInMilliseconds() < timePassed && CTimer::GetTimeInMilliseconds() >= timePassed) + DMAudio.PlayOneShot(audioEntity, SOUND_WEAPON_RELOAD, m_eWeaponType); + } + } + + 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: + { + CStats::BulletsThatHit++; + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_VEHICLE, 1.0f); + break; + } + case ENTITY_TYPE_PED: + { + CStats::BulletsThatHit++; + DMAudio.PlayOneShot(((CPhysical*)victim)->m_audioEntityId, SOUND_WEAPON_HIT_PED, 1.0f); + ((CPed*)victim)->Say(SOUND_PED_BULLET_HIT); + break; + } + case ENTITY_TYPE_OBJECT: + { + CStats::BulletsThatHit++; + 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 CWeaponInfo::GetWeaponInfo(m_eWeaponType)->m_eWeaponFire == WEAPON_FIRE_MELEE; +} + +bool +CWeapon::IsType2Handed(void) +{ + return m_eWeaponType == WEAPONTYPE_FLAMETHROWER || m_eWeaponType == WEAPONTYPE_HELICANNON || m_eWeaponType == WEAPONTYPE_M60 || + m_eWeaponType == WEAPONTYPE_M4 || IsShotgun(m_eWeaponType) || + m_eWeaponType == WEAPONTYPE_RUGER || m_eWeaponType == WEAPONTYPE_SNIPERRIFLE || m_eWeaponType == WEAPONTYPE_LASERSCOPE; +} + +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->IsObject() ) + { + 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.55f; + 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) +{ + // FIX: This is better (not bug tho) +//#if 0 + if (m_eWeaponType <= WEAPONTYPE_CHAINSAW) +//#else +// if (CWeaponInfo::GetWeaponInfo(m_eWeaponType)->m_eWeaponFire == WEAPON_FIRE_MELEE) +//#endif + return true; + else + return m_nAmmoTotal != 0; +} + +bool +CPed::IsPedDoingDriveByShooting(void) +{ +#ifdef FIX_BUGS + if (FindPlayerPed() == this && CWeaponInfo::GetWeaponInfo(GetWeapon()->m_eWeaponType)->m_nWeaponSlot == WEAPONSLOT_SUBMACHINEGUN) { +#else + if (FindPlayerPed() == this && GetWeapon()->m_eWeaponType == WEAPONTYPE_UZI) { +#endif + 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, false, ignoreSomeObjects, true); +} + + +void +CWeapon::CheckForShootingVehicleOccupant(CEntity **victim, CColPoint *point, eWeaponType weapon, CVector const& source, CVector const& target) +{ + if (!(*victim)->IsVehicle()) + return; + + CColSphere headSphere; + + CVehicle *veh = (CVehicle*)*victim; + CColPoint origPoint(*point); + float radius = 1.0f; + bool found = false; + CColLine shootLine(source, target); + + if (veh->pDriver && veh->pDriver->bCanBeShotInVehicle) { + CVector pos(0.f, 0.f, 0.f); + veh->pDriver->TransformToNode(pos, PED_HEAD); + headSphere.Set(0.2f, pos + CVector(0.f, 0.f, 0.1f), 0, PEDPIECE_HEAD); + if (CCollision::ProcessLineSphere(shootLine, headSphere, *point, radius)) { + *victim = veh->pDriver; + found = true; + } + } + + for(int i = 0; i < ARRAY_SIZE(veh->pPassengers); i++) { + CPed *passenger = veh->pPassengers[i]; + if (passenger && passenger->bCanBeShotInVehicle) { + CVector pos(0.f, 0.f, 0.f); + passenger->TransformToNode(pos, PED_HEAD); + headSphere.Set(0.2f, pos + CVector(0.f, 0.f, 0.1f), 0, PEDPIECE_HEAD); + if (CCollision::ProcessLineSphere(shootLine, headSphere, *point, radius)) { + *victim = passenger; + found = true; + } + } + } + if (veh->IsCar()) { + CVector distVec = target - source; + if (DotProduct(distVec, veh->GetForward()) < 0.0f && DotProduct(distVec, veh->GetUp()) <= 0.0f) { + CColModel *colModel = veh->GetColModel(); + if (colModel->numTriangles > 0) { + bool passesGlass = false; + CMatrix invVehMat; + Invert(veh->GetMatrix(), invVehMat); + shootLine.p0 = invVehMat * shootLine.p0; + shootLine.p1 = invVehMat * shootLine.p1; + CCollision::CalculateTrianglePlanes(colModel); + for (int i = 0; i < colModel->numTriangles; i++) { + if (colModel->triangles[i].surface == SURFACE_GLASS && + CCollision::TestLineTriangle(shootLine, colModel->vertices, colModel->triangles[i], colModel->trianglePlanes[i])) { + passesGlass = true; + break; + } + } + CAutomobile *car = (CAutomobile*)veh; + + // No need to damage windscreen if there isn't one. + if (passesGlass && car->Damage.ProgressPanelDamage(VEHPANEL_WINDSCREEN)) { + if (car->Damage.GetPanelStatus(VEHPANEL_WINDSCREEN) == PANEL_STATUS_SMASHED2) + car->Damage.ProgressPanelDamage(VEHPANEL_WINDSCREEN); + + car->SetPanelDamage(CAR_WINDSCREEN, VEHPANEL_WINDSCREEN, true); + DMAudio.PlayOneShot(veh->m_audioEntityId, SOUND_CAR_WINDSHIELD_CRACK, 0.f); + } + } + } + } + + if (!found) { + *victim = veh; + *point = origPoint; + } +} + +#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/miami/weapons/Weapon.h b/src/miami/weapons/Weapon.h new file mode 100644 index 00000000..f720b312 --- /dev/null +++ b/src/miami/weapons/Weapon.h @@ -0,0 +1,90 @@ +#pragma once + +#include "WeaponType.h" + +#define CAR_DRIVEBYAUTOAIMING_MAXDIST (2.5f) +#define DOOMAUTOAIMING_MAXDIST (9000.0f) + +class CEntity; +class CPhysical; +class CVehicle; +class CPed; +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; + + static bool bPhotographHasBeenTaken; + + CWeapon() { + m_bAddRotOffset = false; + } + CWeapon(eWeaponType type, int32 ammo); + + CWeaponInfo *GetInfo(); + + static void InitialiseWeapons(void); + static void ShutdownWeapons (void); + static void UpdateWeapons (void); + + void Initialise(eWeaponType type, int32 ammo); + void Shutdown(); + + bool Fire (CEntity *shooter, CVector *fireSource); + bool FireFromCar (CVehicle *shooter, bool left, bool right); + bool FireMelee (CEntity *shooter, CVector &fireSource); + bool FireInstantHit(CEntity *shooter, CVector *fireSource); + + static void AddGunFlashBigGuns(CVector start, CVector end); + 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 LaserScopeDot (CVector *pOutPos, float *pOutSize); + bool FireSniper (CEntity *shooter); + bool TakePhotograph (CEntity *shooter); + bool FireM16_1stPerson (CEntity *shooter); + bool FireInstantHitFromCar(CVehicle *shooter, bool left, bool right); + + static void DoDoomAiming (CEntity *shooter, CVector *source, CVector *target); + static void DoTankDoomAiming (CEntity *shooter, CEntity *driver, CVector *source, CVector *target); + static void DoDriveByAutoAiming(CEntity *driver, CVehicle *vehicle, CVector *source, CVector *target); + + void Reload(void); + void Update(int32 audioEntity, CPed *pedToAdjustSound); + 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 IsShotgun(int weapon) { return weapon == WEAPONTYPE_SHOTGUN || weapon == WEAPONTYPE_SPAS12_SHOTGUN || weapon == WEAPONTYPE_STUBBY_SHOTGUN; } + + 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); + + static void CheckForShootingVehicleOccupant(CEntity **victim, CColPoint *point, eWeaponType weapon, CVector const& source, CVector const& target); + +#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/miami/weapons/WeaponEffects.cpp b/src/miami/weapons/WeaponEffects.cpp new file mode 100644 index 00000000..bb95ea85 --- /dev/null +++ b/src/miami/weapons/WeaponEffects.cpp @@ -0,0 +1,133 @@ +#include "common.h" + +#include "main.h" +#include "WeaponEffects.h" +#include "TxdStore.h" +#include "Sprite.h" +#include "PlayerPed.h" +#include "World.h" +#include "WeaponType.h" + +RwTexture *gpCrossHairTex; + +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 = 255; + gCrossHair.m_nGreen = 0; + gCrossHair.m_nBlue = 0; + gCrossHair.m_nAlpha = 127; + gCrossHair.m_fSize = 1.0f; + gCrossHair.m_fRotation = 0.0f; + + + CTxdStore::PushCurrentTxd(); + int32 slot = CTxdStore::FindTxdSlot("particle"); + CTxdStore::SetCurrentTxd(slot); + + gpCrossHairTex = RwTextureRead("target256", "target256m"); + + CTxdStore::PopCurrentTxd(); +} + +void +CWeaponEffects::Shutdown(void) +{ + RwTextureDestroy(gpCrossHairTex); + gpCrossHairTex = nil; +} + +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_fSize = size; +} + +void +CWeaponEffects::ClearCrossHair(void) +{ + gCrossHair.m_bActive = false; +} + +void +CWeaponEffects::Render(void) +{ + static float aCrossHairSize[WEAPONTYPE_TOTALWEAPONS] = + { + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + 0.4f, 0.4f, + 0.5f, + 0.3f, + 0.9f, 0.9f, 0.9f, + 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, + 0.1f, 0.1f, + 1.0f, + 0.6f, + 0.7f, + 0.0f, 0.0f + }; + + + + if ( gCrossHair.m_bActive ) + { + float size = aCrossHairSize[FindPlayerPed()->GetWeapon()->m_eWeaponType]; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATESRCBLEND, (void *)rwBLENDSRCALPHA); +#ifdef FIX_BUGS + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVSRCALPHA); +#else + RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void *)rwBLENDINVDESTALPHA); +#endif + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, (void *)RwTextureGetRaster(gpCrossHairTex)); + + 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_Rotate_Aspect(pos.x, pos.y, pos.z, + w, h, + 255, 88, 100, 158, + recipz, gCrossHair.m_fRotation, gCrossHair.m_nAlpha); + + float recipz2 = 1.0f / pos.z; + + CSprite::RenderOneXLUSprite_Rotate_Aspect(pos.x, pos.y, pos.z, + size*w, size*h, + 107, 134, 247, 158, + recipz2, TWOPI - gCrossHair.m_fRotation, gCrossHair.m_nAlpha); + + gCrossHair.m_fRotation += 0.02f; + if ( gCrossHair.m_fRotation > TWOPI ) + gCrossHair.m_fRotation = 0.0; + + POP_RENDERGROUP(); + } + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void *)TRUE); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, (void *)TRUE); + } +} \ No newline at end of file diff --git a/src/miami/weapons/WeaponEffects.h b/src/miami/weapons/WeaponEffects.h new file mode 100644 index 00000000..f6592b3e --- /dev/null +++ b/src/miami/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/miami/weapons/WeaponInfo.cpp b/src/miami/weapons/WeaponInfo.cpp new file mode 100644 index 00000000..1f78b7d4 --- /dev/null +++ b/src/miami/weapons/WeaponInfo.cpp @@ -0,0 +1,295 @@ +#include "common.h" + +#include "main.h" +#include "FileMgr.h" +#include "WeaponInfo.h" +#include "AnimManager.h" +#include "AnimBlendAssociation.h" +#include "Weapon.h" +#include "ModelInfo.h" +#include "ModelIndices.h" + +uint16 CWeaponInfo::ms_aReloadSampleTime[WEAPONTYPE_TOTALWEAPONS] = +{ + 0, // UNARMED + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, // GRENADE + 0, // DETONATEGRENADE + 0, // TEARGAS + 0, // MOLOTOV + 0, // ROCKET + 250, // COLT45 + 250, // PYTHON + 650, // SHOTGUN + 650, // SPAS12 SHOTGUN + 650, // STUBBY SHOTGUN + 400, // TEC9 + 400, // UZIhec + 400, // SILENCED_INGRAM + 400, // MP5 + 300, // M16 + 300, // AK47 + 423, // SNIPERRIFLE + 423, // LASERSCOPE + 400, // ROCKETLAUNCHER + 0, // FLAMETHROWER + 0, // M60 + 0, // MINIGUN + 0, // DETONATOR + 0, // HELICANNON + 0 // CAMERA +}; + +// Yeah... +int32 CWeaponInfo::ms_aMaxAmmoForWeapon[WEAPONTYPE_TOTALWEAPONS] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +CWeaponInfo aWeaponInfo[WEAPONTYPE_TOTALWEAPONS]; +char CWeaponInfo::ms_aWeaponNames[WEAPONTYPE_TOTALWEAPONS][32] = +{ + "Unarmed", + "BrassKnuckle", + "ScrewDriver", + "GolfClub", + "NightStick", + "Knife", + "BaseballBat", + "Hammer", + "Cleaver", + "Machete", + "Katana", + "Chainsaw", + "Grenade", + "DetonateGrenade", + "TearGas", + "Molotov", + "Rocket", + "Colt45", + "Python", + "Shotgun", + "Spas12Shotgun", + "StubbyShotgun", + "Tec9", + "Uzi", + "SilencedIngram", + "Mp5", + "m4", + "Ruger", + "SniperRifle", + "LaserScope", + "RocketLauncher", + "FlameThrower", + "M60", + "Minigun", + "Detonator", + "HeliCannon", + "Camera", +}; + +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_fRange = 0.0f; + aWeaponInfo[i].m_nFiringRate = 0; + aWeaponInfo[i].m_nReload = 0; + aWeaponInfo[i].m_nAmountofAmmunition = 0; + aWeaponInfo[i].m_nDamage = 0; + aWeaponInfo[i].m_fSpeed = 0.0f; + aWeaponInfo[i].m_fRadius = 0.0f; + aWeaponInfo[i].m_fLifespan = 0.0f; + aWeaponInfo[i].m_fSpread = 0.0f; + aWeaponInfo[i].m_vecFireOffset = CVector(0.0f, 0.0f, 0.0f); + aWeaponInfo[i].m_AnimToPlay = ASSOCGRP_UNARMED; + aWeaponInfo[i].m_fAnimLoopStart = 0.0f; + aWeaponInfo[i].m_fAnimLoopEnd = 0.0f; + aWeaponInfo[i].m_fAnimFrameFire = 0.0f; + aWeaponInfo[i].m_fAnim2LoopStart = 0.0f; + aWeaponInfo[i].m_fAnim2LoopEnd = 0.0f; + aWeaponInfo[i].m_fAnim2FrameFire = 0.0f; + aWeaponInfo[i].m_fAnimBreakout = 0.0f; + aWeaponInfo[i].m_Flags = WEAPONFLAG_USE_GRAVITY | WEAPONFLAG_SLOWS_DOWN | WEAPONFLAG_RAND_SPEED | WEAPONFLAG_EXPANDS | WEAPONFLAG_EXPLODES; + aWeaponInfo[i].m_nWeaponSlot = WEAPONSLOT_UNARMED; + } + 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 anim2LoopStart, anim2LoopEnd, delayBetweenAnim2AndFire, animBreakout; + float delayBetweenAnimAndFire, animLoopStart, animLoopEnd; + int flags, ammoAmount, damage, reload, weaponType; + int firingRate, modelId, modelId2, weaponSlot; + char line[256], weaponName[32], fireType[32]; + char animToPlay[32]; + + size_t bp, buflen; + int lp, linelen; + + CFileMgr::SetDir("DATA"); + buflen = CFileMgr::LoadFile("WEAPON.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; + + 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; + sscanf( + &line[lp], + "%s %s %f %d %d %d %d %f %f %f %f %f %f %f %s %f %f %f %f %f %f %f %d %d %x %d", + weaponName, + fireType, + &range, + &firingRate, + &reload, + &ammoAmount, + &damage, + &speed, + &radius, + &lifeSpan, + &spread, + &fireOffsetX, + &fireOffsetY, + &fireOffsetZ, + animToPlay, + &animLoopStart, + &animLoopEnd, + &delayBetweenAnimAndFire, + &anim2LoopStart, + &anim2LoopEnd, + &delayBetweenAnim2AndFire, + &animBreakout, + &modelId, + &modelId2, + &flags, + &weaponSlot); + + if (strncmp(weaponName, "ENDWEAPONDATA", 13) == 0) + return; + + weaponType = FindWeaponType(weaponName); + + 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_fAnimLoopStart = animLoopStart / 30.0f; + aWeaponInfo[weaponType].m_fAnimLoopEnd = animLoopEnd / 30.0f; + aWeaponInfo[weaponType].m_fAnim2LoopStart = anim2LoopStart / 30.0f; + aWeaponInfo[weaponType].m_fAnim2LoopEnd = anim2LoopEnd / 30.0f; + aWeaponInfo[weaponType].m_fAnimFrameFire = delayBetweenAnimAndFire / 30.0f; + aWeaponInfo[weaponType].m_fAnim2FrameFire = delayBetweenAnim2AndFire / 30.0f; + aWeaponInfo[weaponType].m_fAnimBreakout = animBreakout / 30.0f; + aWeaponInfo[weaponType].m_nModelId = modelId; + aWeaponInfo[weaponType].m_nModel2Id = modelId2; + aWeaponInfo[weaponType].m_Flags = flags; + aWeaponInfo[weaponType].m_nWeaponSlot = weaponSlot; + + if (animLoopEnd < 98.0f && weaponType != WEAPONTYPE_FLAMETHROWER && !CWeapon::IsShotgun(weaponType)) + aWeaponInfo[weaponType].m_nFiringRate = ((aWeaponInfo[weaponType].m_fAnimLoopEnd - aWeaponInfo[weaponType].m_fAnimLoopStart) * 900.0f); + + if (weaponType == WEAPONTYPE_DETONATOR || weaponType == WEAPONTYPE_HELICANNON) + modelId = -1; + else if (weaponType == WEAPONTYPE_DETONATOR_GRENADE) + modelId = MI_BOMB; + + if (modelId != -1) + ((CWeaponModelInfo*)CModelInfo::GetModelInfo(modelId))->SetWeaponInfo(weaponType); + + for (int i = 0; i < NUM_ANIM_ASSOC_GROUPS; i++) { + if (!strcmp(animToPlay, CAnimManager::GetAnimGroupName((AssocGroupId)i))) { + aWeaponInfo[weaponType].m_AnimToPlay = (AssocGroupId)i; + break; + } + } + } +} + +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; + if (strcmp(name, "CAMERA") == 0) return WEAPON_FIRE_CAMERA; + 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/miami/weapons/WeaponInfo.h b/src/miami/weapons/WeaponInfo.h new file mode 100644 index 00000000..d7f1563d --- /dev/null +++ b/src/miami/weapons/WeaponInfo.h @@ -0,0 +1,74 @@ +#pragma once + +#include "AnimManager.h" +#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, + WEAPONFLAG_RELOAD_LOOP2START = 1 << 11, + WEAPONFLAG_USE_2ND = 1 << 12, + WEAPONFLAG_GROUND_2ND = 1 << 13, + WEAPONFLAG_FINISH_3RD = 1 << 14, + WEAPONFLAG_RELOAD = 1 << 15, + WEAPONFLAG_FIGHTMODE = 1 << 16, + WEAPONFLAG_CROUCHFIRE = 1 << 17, + WEAPONFLAG_COP3_RD = 1 << 18, + WEAPONFLAG_GROUND_3RD = 1 << 19, + WEAPONFLAG_PARTIALATTACK = 1 << 20, + WEAPONFLAG_ANIMDETONATE = 1 << 21, +}; + +class CWeaponInfo { + static char ms_aWeaponNames[WEAPONTYPE_TOTALWEAPONS][32]; +public: + static uint16 ms_aReloadSampleTime[WEAPONTYPE_TOTALWEAPONS]; + static int32 ms_aMaxAmmoForWeapon[WEAPONTYPE_TOTALWEAPONS]; + + 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; + AssocGroupId m_AnimToPlay; + float m_fAnimLoopStart; + float m_fAnimLoopEnd; + float m_fAnimFrameFire; + float m_fAnim2LoopStart; + float m_fAnim2LoopEnd; + float m_fAnim2FrameFire; + float m_fAnimBreakout; + int32 m_nModelId; + int32 m_nModel2Id; + uint32 m_Flags; + + uint32 m_nWeaponSlot; + + 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); + static bool IsWeaponSlotAmmoMergeable(uint32 slot) { return slot == WEAPONSLOT_SHOTGUN || slot == WEAPONSLOT_SUBMACHINEGUN || slot == WEAPONSLOT_RIFLE; } + bool IsFlagSet(uint32 flag) const { return (m_Flags & flag) != 0; } +}; + +VALIDATE_SIZE(CWeaponInfo, 0x64); diff --git a/src/miami/weapons/WeaponType.h b/src/miami/weapons/WeaponType.h new file mode 100644 index 00000000..1220196f --- /dev/null +++ b/src/miami/weapons/WeaponType.h @@ -0,0 +1,86 @@ +#pragma once + +enum eWeaponType +{ + WEAPONTYPE_UNARMED, + WEAPONTYPE_BRASSKNUCKLE, + WEAPONTYPE_SCREWDRIVER, + WEAPONTYPE_GOLFCLUB, + WEAPONTYPE_NIGHTSTICK, + WEAPONTYPE_KNIFE, + WEAPONTYPE_BASEBALLBAT, + WEAPONTYPE_HAMMER, + WEAPONTYPE_CLEAVER, + WEAPONTYPE_MACHETE, + WEAPONTYPE_KATANA, + WEAPONTYPE_CHAINSAW, + WEAPONTYPE_GRENADE, + WEAPONTYPE_DETONATOR_GRENADE, + WEAPONTYPE_TEARGAS, + WEAPONTYPE_MOLOTOV, + WEAPONTYPE_ROCKET, + WEAPONTYPE_COLT45, + WEAPONTYPE_PYTHON, + WEAPONTYPE_SHOTGUN, + WEAPONTYPE_SPAS12_SHOTGUN, + WEAPONTYPE_STUBBY_SHOTGUN, + WEAPONTYPE_TEC9, + WEAPONTYPE_UZI, + WEAPONTYPE_SILENCED_INGRAM, + WEAPONTYPE_MP5, + WEAPONTYPE_M4, + WEAPONTYPE_RUGER, + WEAPONTYPE_SNIPERRIFLE, + WEAPONTYPE_LASERSCOPE, + WEAPONTYPE_ROCKETLAUNCHER, + WEAPONTYPE_FLAMETHROWER, + WEAPONTYPE_M60, + WEAPONTYPE_MINIGUN, + WEAPONTYPE_DETONATOR, + WEAPONTYPE_HELICANNON, + WEAPONTYPE_CAMERA, + WEAPONTYPE_TOTALWEAPONS = 37, + WEAPONTYPE_HEALTH = 37, + WEAPONTYPE_ARMOUR, + WEAPONTYPE_RAMMEDBYCAR, + WEAPONTYPE_RUNOVERBYCAR, + WEAPONTYPE_EXPLOSION, + WEAPONTYPE_UZI_DRIVEBY, + WEAPONTYPE_DROWNING, + WEAPONTYPE_FALL, + WEAPONTYPE_UNIDENTIFIED, + WEAPONTYPE_ANYMELEE, + WEAPONTYPE_ANYWEAPON +}; + +enum { + WEAPONSLOT_UNARMED = 0, + WEAPONSLOT_MELEE, + WEAPONSLOT_PROJECTILE, + WEAPONSLOT_HANDGUN, + WEAPONSLOT_SHOTGUN, + WEAPONSLOT_SUBMACHINEGUN, + WEAPONSLOT_RIFLE, + WEAPONSLOT_HEAVY, + WEAPONSLOT_SNIPER, + WEAPONSLOT_OTHER, + TOTAL_WEAPON_SLOTS +}; + +enum eWeaponFire { + WEAPON_FIRE_MELEE, + WEAPON_FIRE_INSTANT_HIT, + WEAPON_FIRE_PROJECTILE, + WEAPON_FIRE_AREA_EFFECT, + WEAPON_FIRE_CAMERA +}; + +// 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/src/tools/adf2mp3.cpp b/src/tools/adf2mp3.cpp new file mode 100644 index 00000000..269ae98e --- /dev/null +++ b/src/tools/adf2mp3.cpp @@ -0,0 +1,50 @@ +#include +#include + +int main(int argc, char* argv[]) { + // Check for correct number of arguments. + if (argc != 3) { + std::cerr << "Usage: adf2mp3 " << std::endl; + return 1; + } + + // Get file names from command line arguments. + const char* inputFileName = argv[1]; + const char* outputFileName = argv[2]; + + // Open the input file in binary mode. + std::ifstream inputFile(inputFileName, std::ios::binary); + if (!inputFile) { + std::cerr << "Error: Unable to open input file " << inputFileName << std::endl; + return 1; + } + + // Open the output file in binary mode. + std::ofstream outputFile(outputFileName, std::ios::binary); + if (!outputFile) { + std::cerr << "Error: Unable to open output file " << outputFileName << std::endl; + return 1; + } + + // Buffer to hold file data. + const size_t bufferSize = 4096; + char buffer[bufferSize]; + + // Process the file in chunks. + while (inputFile.read(buffer, bufferSize) || inputFile.gcount() > 0) { + // Get the number of bytes actually read. + std::streamsize bytesRead = inputFile.gcount(); + // XOR each byte with 0x22. + for (std::streamsize i = 0; i < bytesRead; ++i) { + buffer[i] ^= 0x22; + } + // Write the modified buffer to the output file. + outputFile.write(buffer, bytesRead); + } + + // Close files. + inputFile.close(); + outputFile.close(); + + return 0; +} diff --git a/dreamcast/analyze-profile.cpp b/src/tools/analyze-profile.cpp similarity index 100% rename from dreamcast/analyze-profile.cpp rename to src/tools/analyze-profile.cpp diff --git a/src/tools/animtool.cpp b/src/tools/animtool.cpp new file mode 100644 index 00000000..c218a138 --- /dev/null +++ b/src/tools/animtool.cpp @@ -0,0 +1,1029 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846264338327950288 +#endif + +struct Writer: std::vector { + template + void write(float v); +}; + + +template<> +void Writer::write(float v) { + assert(v >= -127 && v <= 127); + push_back(static_cast(v)); +} + +template<> +void Writer::write(float v) { + assert(v >= 0 && v <= 255); + push_back(static_cast(v)); +} + +template<> +void Writer::write(float v) { + assert(v >= -32767 && v <= 32767); + int16_t fx = static_cast(v); + insert(end(), reinterpret_cast(&fx), reinterpret_cast(&fx) + sizeof(fx)); +} + +template<> +void Writer::write(float v) { + assert(v >= 0 && v <= 65535); + int16_t fx = static_cast(v); + insert(end(), reinterpret_cast(&fx), reinterpret_cast(&fx) + sizeof(fx)); +} + +template<> +void Writer::write(float v) { + insert(end(), reinterpret_cast(&v), reinterpret_cast(&v) + sizeof(float)); +} + +// Example Reader class interface (you need to implement this as appropriate) +class Reader { +public: + Reader(const std::vector& data) : buffer(data), offset(0) {} + + template + T read() { + if (offset + sizeof(T) > buffer.size()) { + fprintf(stderr, "Reader error: trying to read past end of buffer.\n"); + assert(false); + } + T value; + memcpy(&value, buffer.data() + offset, sizeof(T)); + offset += sizeof(T); + return value; + } +private: + const std::vector& buffer; + size_t offset; +}; + +#define FLAGS_KF_ROT ( 1 << 0 ) +#define FLAGS_KF_TRANS ( 1 << 1 ) + +#define FLAGS_HAS_ROT_Y ( 1 << 8 ) +#define FLAGS_HAS_ROT_P ( 1 << 9 ) +#define FLAGS_HAS_ROT_R ( 1 << 10 ) + +#define FLAGS_HAS_TRANS_X ( 1 << 11 ) +#define FLAGS_HAS_TRANS_Y ( 1 << 12 ) +#define FLAGS_HAS_TRANS_Z ( 1 << 13 ) +#define FLAGS_HAS_TRANS_ANY ( 7 << 11 ) +#define FLAGS_HAS_TRANS_LARGE ( 1 << 14 ) +#define FLAGS_QUAT0_NEG ( 1 << 15 ) + +using uint32 = uint32_t; + +#define RwStreamRead(f, p, s) fread(p, s, 1, f) +#define debug(...) // printf(__VA_ARGS__) +#define RwMalloc malloc + +struct rotation_t { + float y; // Theta, in [0, π/2] + float p; // phi, typically in [-π, π] + float r; // psi, typically in [-π, π] + + uint16_t fixed_y() { + return fixed(y); + } + + uint16_t fixed_p() { + return fixed(p); + } + + uint16_t fixed_r() { + return fixed(r); + } + + static uint16_t fixed(float a) { + // we use int16_t bellow instead since overflows are 2 * M_PI + // if (a < 0) + // a += 2 * M_PI; + // if (a > 2 * M_PI) + // a -= 2 * M_PI; + // assert(a >= 0 && a < 2 * M_PI); + return static_cast(a * 65536 / (2 * M_PI)); + } +}; + +struct quaternion_t { + float x, y, z, w; + + quaternion_t inverted() { + return {-x, -y, -z, w}; + } + + quaternion_t operator-() const { + return {-x, -y, -z, -w}; + } + + rotation_t toSpherical() const { + rotation_t rot; + // Compute the magnitude of the first complex component (w, x) + float A = sqrt(w * w + x * x); + // Compute the magnitude of the second complex component (y, z) + float B = sqrt(y * y + z * z); + + // Calculate theta from the ratio B/A. + // A is non-negative by definition, and so is B. + rot.y = atan2(B, A); + + // phi is the argument (angle) of the complex number A = w + i*x. + rot.p = atan2(x, w); + + // psi is the argument of the complex number B = y + i*z. + rot.r = atan2(z, y); + return rot; + } + + // Convert from spherical (yaw, pitch, roll) to quaternion + static quaternion_t fromSpherical(const rotation_t& rot) { + quaternion_t q; + q.w = cos(rot.y) * cos(rot.p); + q.x = cos(rot.y) * sin(rot.p); + q.y = sin(rot.y) * cos(rot.r); + q.z = sin(rot.y) * sin(rot.r); + return q; + } + + static quaternion_t fromSphericalFixed(uint16_t y, uint16_t p, uint16_t r) { + quaternion_t q; + q.w = cos((y / 65536.0f) * 2 * M_PI) * cos((p / 65536.0f) * 2 * M_PI); + q.x = cos((y / 65536.0f) * 2 * M_PI) * sin((p / 65536.0f) * 2 * M_PI); + q.y = sin((y / 65536.0f) * 2 * M_PI) * cos((r / 65536.0f) * 2 * M_PI); + q.z = sin((y / 65536.0f) * 2 * M_PI) * sin((r / 65536.0f) * 2 * M_PI); + return q; + } +}; +struct translation_t { + float x, y, z; +}; + +void assert_float(float v, float exp, float espilon = 0.01) { + assert(fabs(v - exp) < espilon); +} + +void assert_trans(const translation_t& v, const translation_t& exp, float espilon = 2/128.f) { + assert(fabs(v.x - exp.x) < espilon); + assert(fabs(v.y - exp.y) < espilon); + assert(fabs(v.z - exp.z) < espilon); +} + +void assert_quat(const quaternion_t& v, const quaternion_t& exp, float espilon = 0.04) { + assert(fabs(v.x - exp.x) < espilon); + assert(fabs(v.y - exp.y) < espilon); + assert(fabs(v.z - exp.z) < espilon); + if (fabs(exp.x) < 0.0001f && fabs(exp.y) < 0.0001f && fabs(exp.z) < 0.0001f) + ; + else + assert(fabs(v.w - exp.w) < espilon); +} + +float dot(const quaternion_t &a, const quaternion_t &b) { + return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; +} + +struct sequence_t { + std::vector rotations; + std::vector translations; + std::vector times; + std::string name; + int boneTag = -1; + + void removeQuaternionFlips() { + quaternion_t q1 = rotations.front(); + for(size_t i = 1; i < rotations.size(); i++){ + auto q2 = rotations[i]; + float dot = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + if(dot < 0.0f) + rotations[i] = -q2; + q1 = rotations[i]; + } + } + + // This function is assumed to be a member of your sequence_t type. + void decompressDeltaCompressedData(const std::vector& compData, sequence_t ref) { + Reader reader(compData); + + // Read the flags written at the start. + uint16_t flags = reader.read(); + + // Read initial and final timestamps. + float initial_ts = reader.read(); + float final_ts = reader.read(); + + // Assume that the number of frames was set before calling this function. + int nFrames = times.size(); + if (nFrames == 0) + return; + + rotations.resize(nFrames); + + // Set first frame time. + times[0] = initial_ts; + float predicted_ts = initial_ts; + + // --- Translations --- + float predicted_tx = 0, predicted_ty = 0, predicted_tz = 0; + if (flags & FLAGS_KF_TRANS) { + translation_t initTrans, finalTrans; + translations.resize(nFrames); + if (flags & FLAGS_HAS_TRANS_LARGE) { + initTrans.x = reader.read(); + initTrans.y = reader.read(); + initTrans.z = reader.read(); + predicted_tx = initTrans.x; + predicted_ty = initTrans.y; + predicted_tz = initTrans.z; + translations[0] = initTrans; + // Read final translation (may be used for verification or ignored) + finalTrans.x = reader.read(); + finalTrans.y = reader.read(); + finalTrans.z = reader.read(); + } else { + initTrans.x = reader.read() / 128.f; + initTrans.y = reader.read() / 128.f; + initTrans.z = reader.read() / 128.f; + predicted_tx = initTrans.x; + predicted_ty = initTrans.y; + predicted_tz = initTrans.z; + translations[0] = initTrans; + // Read final translation (for completeness) + finalTrans.x = reader.read() / 128.f; + finalTrans.y = reader.read() / 128.f; + finalTrans.z = reader.read() / 128.f; + } + + assert_trans(initTrans, ref.translations.front()); + + assert_trans(finalTrans, ref.translations.back()); + } + + // --- Rotations --- + // Read the absolute fixed‑point rotation values for the first frame. + uint16_t fixed_y = reader.read(); + uint16_t fixed_p = reader.read(); + uint16_t fixed_r = reader.read(); + rotations[0] = quaternion_t::fromSphericalFixed(fixed_y, fixed_p, fixed_r); + uint16_t predicted_y = fixed_y; + uint16_t predicted_p = fixed_p; + uint16_t predicted_r = fixed_r; + + if (flags & FLAGS_QUAT0_NEG) { + rotations[0] = -rotations[0]; + } + + assert_quat(rotations[0], ref.rotations.front()); + + // --- Delta decoding for each subsequent frame --- + for (int frameId = 1; frameId < nFrames; frameId++) { + // --- Rotations --- + // For rotation Y: + if (flags & FLAGS_HAS_ROT_Y) { + uint8_t byteVal = reader.read(); + if (byteVal == 128) { + predicted_y = reader.read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_y += diff * 8; + } + } + // For rotation P: + if (flags & FLAGS_HAS_ROT_P) { + uint8_t byteVal = reader.read(); + if (byteVal == 128) { + predicted_p = reader.read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_p += diff * 8; + } + } + // For rotation R: + if (flags & FLAGS_HAS_ROT_R) { + uint8_t byteVal = reader.read(); + if (byteVal == 128) { + predicted_r = reader.read(); + } else { + int8_t diff = static_cast(byteVal); + predicted_r += diff * 8; + } + } + if (frameId < rotations.size()) { + rotations[frameId] = quaternion_t::fromSphericalFixed(predicted_y, predicted_p, predicted_r); + } + + // --- Translations --- + // Translation X: + if (flags & FLAGS_HAS_TRANS_X) { + uint8_t byteVal = reader.read(); + if (byteVal == 128) { + uint16_t diff = reader.read(); + if (diff != 32768) { + predicted_tx += static_cast(diff) / 128.f; + } else { + predicted_tx = reader.read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_tx += diff / 127.f; + } + } + // Translation Y: + if (flags & FLAGS_HAS_TRANS_Y) { + uint8_t byteVal = reader.read(); + if (byteVal == 128) { + uint16_t diff = reader.read(); + if (diff != 32768) { + predicted_ty += static_cast(diff) / 128.f; + } else { + predicted_ty = reader.read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_ty += diff / 127.f; + } + } + // Translation Z: + if (flags & FLAGS_HAS_TRANS_Z) { + uint8_t byteVal = reader.read(); + if (byteVal == 128) { + uint16_t diff = reader.read(); + if (diff != 32768) { + predicted_tz += static_cast(diff) / 128.f; + } else { + predicted_tz = reader.read(); + } + } else { + int8_t diff = static_cast(byteVal); + predicted_tz += diff / 127.f; + } + } + if (frameId < translations.size()) { + translations[frameId] = { predicted_tx, predicted_ty, predicted_tz }; + assert_trans(translations[frameId], ref.translations[frameId]); + } + + // --- Time Stamps --- + { + uint8_t byteValPacked = reader.read(); + uint8_t byteVal = byteValPacked & 127; + float diff; + if (byteVal == 127) { + uint16_t fixed_diff = reader.read(); + diff = fixed_diff / 256.f; + } else { + diff = byteVal / 256.f; + } + predicted_ts += diff; + times[frameId] = predicted_ts; + assert_float(times[frameId], ref.times[frameId]); + + if (byteValPacked & 128) { + rotations[frameId] = -rotations[frameId]; + } + } + + // assert for quaternion now that flip has been done, if needed + assert_quat(rotations[frameId], ref.rotations[frameId]); + } + + // Optionally verify that the final reconstructed timestamp matches the stored final timestamp. + if (fabs(predicted_ts - final_ts) > 0.001f) { + fprintf(stderr, "Warning: final timestamp mismatch: %f vs %f\n", predicted_ts, final_ts); + } + } + + Writer getDeltaCompressedData() { + Writer writer; + uint16_t flags = 0; + + std::vector diffs_ts(times.size()), ts(times.size()); + for (size_t i = 1; i < times.size(); i++) { + float diff = times[i] - times[i-1]; + assert (diff >=0 && diff < 256); + diffs_ts[i] = diff; + ts[i] = times[i]; + } + + diffs_ts[0] = ts[0] = times[0]; + + translation_t maxMag_trans = { 0, 0, 0}; + std::vector diffs_trans(translations.size()), trans(translations.size()); + if (translations.size() > 0) { + for (size_t i = translations.size() - 1; i != 0 ; i--) { + translation_t prev = translations[i-1]; + translation_t curr = translations[i]; + + translation_t diff; + diff.x = curr.x - prev.x; + diff.y = curr.y - prev.y; + diff.z = curr.z - prev.z; + + diffs_trans[i] = diff; + trans[i] = curr; + + maxMag_trans.x = std::max(maxMag_trans.x, std::abs(diff.x)); + maxMag_trans.y = std::max(maxMag_trans.y, std::abs(diff.y)); + maxMag_trans.z = std::max(maxMag_trans.z, std::abs(diff.z)); + } + + diffs_trans[0] = trans[0] = translations[0]; + } + + + std::vector diffs_rots(rotations.size()), rots(rotations.size()); + + rotation_t maxMag_rots = { 0, 0, 0}; + for (size_t i = rotations.size() - 1; i != 0 ; i--) { + rotation_t prev = rotations[i-1].toSpherical(); + rotation_t curr = rotations[i].toSpherical(); + + rotation_t diff; + diff.y = curr.y - prev.y; + diff.p = curr.p - prev.p; + diff.r = curr.r - prev.r; + + diffs_rots[i] = diff; + rots[i] = curr; + + maxMag_rots.y = std::max(maxMag_rots.y, std::abs(diff.y)); + maxMag_rots.p = std::max(maxMag_rots.p, std::abs(diff.p)); + maxMag_rots.r = std::max(maxMag_rots.r, std::abs(diff.r)); + } + diffs_rots[0] = rots[0] = rotations[0].toSpherical(); + + // okay, now we have the diffs, the max magnitudes, and the original values + // do the write out + flags = FLAGS_KF_ROT; // always have rotations + + if (translations.size() > 0) { + flags |= FLAGS_KF_TRANS; + } + + { + quaternion_t q1 = quaternion_t::fromSphericalFixed(rots.front().fixed_y(), rots.front().fixed_p(), rots.front().fixed_r()); + if (dot(rotations.front(), q1) < 0) { + flags |= FLAGS_QUAT0_NEG; + } + } + + if (maxMag_rots.p > 0.0001) { + flags |= FLAGS_HAS_ROT_P; + } + if (maxMag_rots.y > 0.0001) { + flags |= FLAGS_HAS_ROT_Y; + } + if (maxMag_rots.r > 0.0001) { + flags |= FLAGS_HAS_ROT_R; + } + + if (maxMag_trans.x > 0.0001f) { + flags |= FLAGS_HAS_TRANS_X; + } + if (maxMag_trans.y > 0.0001f) { + flags |= FLAGS_HAS_TRANS_Y; + } + if (maxMag_trans.z > 0.0001f) { + flags |= FLAGS_HAS_TRANS_Z; + } + + if (trans.size() > 0) { + if (fabs(trans.front().x) > 127 || fabs(trans.front().y) > 127 || fabs(trans.front().z) > 127) { + flags |= FLAGS_HAS_TRANS_LARGE; + } + + if (fabs(trans.back().x) > 127 || fabs(trans.back().y) > 127 || fabs(trans.back().z) > 127) { + flags |= FLAGS_HAS_TRANS_LARGE; + } + } + + assert(ts[0] < 256); + + writer.write(flags); + + writer.write(ts.front()); + size_t end_time_offset = writer.size(); + writer.write(0); // filler for end timestamp + + float predicted_ts = ts.front(); + + float predicted_tx = 0, predicted_ty = 0, predicted_tz = 0; + + if (flags & FLAGS_KF_TRANS) { + + if (flags & FLAGS_HAS_TRANS_LARGE) { + writer.write(trans.front().x); + writer.write(trans.front().y); + writer.write(trans.front().z); + + predicted_tx = trans.front().x; + predicted_ty = trans.front().y; + predicted_tz = trans.front().z; + + writer.write(trans.back().x); + writer.write(trans.back().y); + writer.write(trans.back().z); + } else { + writer.write(trans.front().x * 128); + writer.write(trans.front().y * 128); + writer.write(trans.front().z * 128); + + predicted_tx = int16_t(trans.front().x * 128) / 128.f; + predicted_ty = int16_t(trans.front().y * 128) / 128.f; + predicted_tz = int16_t(trans.front().z * 128) / 128.f; + + writer.write(trans.back().x * 128); + writer.write(trans.back().y * 128); + writer.write(trans.back().z * 128); + } + + if (flags & FLAGS_HAS_TRANS_ANY) { + translation_t t1 { predicted_tx, predicted_ty, predicted_tz }; + assert_trans(t1, trans[0]); + } + } + + writer.write(rots.front().fixed_y()); + writer.write(rots.front().fixed_p()); + writer.write(rots.front().fixed_r()); + + uint16_t predicted_y = rots.front().fixed_y(); + uint16_t predicted_p = rots.front().fixed_p(); + uint16_t predicted_r = rots.front().fixed_r(); + + for (int frameId = 1; frameId < times.size(); frameId++) { + if (flags & FLAGS_HAS_ROT_Y) { + int16_t diff = rots[frameId].fixed_y() - predicted_y; + if (abs(diff) > 127*8) { + writer.write(128); + writer.write(rots[frameId].fixed_y()); // special case: wraps around + predicted_y = rots[frameId].fixed_y(); + } else { + writer.write(diff/8); + predicted_y += int8_t(diff/8) * 8; + } + } + if (flags & FLAGS_HAS_ROT_P) { + int16_t diff = rots[frameId].fixed_p() - predicted_p; + if (abs(diff) > 127*8) { + writer.write(128); + writer.write(rots[frameId].fixed_p()); // special case: wraps around + predicted_p = rots[frameId].fixed_p(); + } else { + writer.write(diff/8); + predicted_p += int8_t(diff/8) * 8; + } + } + if (flags & FLAGS_HAS_ROT_R) { + int16_t diff = rots[frameId].fixed_r() - predicted_r; + if (abs(diff) > 127*8) { + writer.write(128); + writer.write(rots[frameId].fixed_r()); // special case: wraps around + predicted_r = rots[frameId].fixed_r(); + } else { + writer.write(diff/8); + predicted_r += int8_t(diff/8) * 8; + } + } + + bool quat_flip = false; + { + quaternion_t q1 = quaternion_t::fromSphericalFixed(predicted_y, predicted_p, predicted_r); + if (dot(rotations[frameId], q1) < 0) { + q1 = -q1; + quat_flip = true; + } + assert_quat(q1, rotations[frameId]); + } + + if (flags & FLAGS_HAS_TRANS_X) { + float diff = trans[frameId].x - predicted_tx; + if (fabs(diff) > 1) { + if (fabs(diff) < 128) { + writer.write(128); + writer.write(diff * 128); + predicted_tx += int16_t(diff * 128) / 128.f; + } else { + writer.write(128); + writer.write(32768); + writer.write(trans[frameId].x); + predicted_tx = trans[frameId].x; + } + } else { + int8_t fixed_diff = diff * 127; + writer.write(fixed_diff); + predicted_tx += fixed_diff / 127.f; + } + } + if (flags & FLAGS_HAS_TRANS_Y) { + float diff = trans[frameId].y - predicted_ty; + if (fabs(diff) > 1) { + if (fabs(diff) < 128) { + writer.write(128); + writer.write(diff * 128); + predicted_ty += int16_t(diff * 128) / 128.f; + } else { + writer.write(128); + writer.write(32768); + writer.write(trans[frameId].y); + predicted_ty = trans[frameId].y; + } + } else { + int8_t fixed_diff = diff * 127; + writer.write(fixed_diff); + predicted_ty += fixed_diff / 127.f; + } + } + if (flags & FLAGS_HAS_TRANS_Z) { + float diff = trans[frameId].z - predicted_tz; + if (fabs(diff) > 1) { + if (fabs(diff) < 128) { + writer.write(128); + writer.write(diff * 128); + predicted_tz += int16_t(diff * 128) / 128.f; + } else { + writer.write(128); + writer.write(32768); + writer.write(trans[frameId].z); + predicted_tz = trans[frameId].z; + } + } else { + int8_t fixed_diff = diff * 127; + writer.write(fixed_diff); + predicted_tz += fixed_diff / 127.f; + } + } + + if (flags & FLAGS_HAS_TRANS_ANY) { + translation_t t1 { predicted_tx, predicted_ty, predicted_tz }; + assert_trans(t1, trans[frameId]); + } + + { + float diff = ts[frameId] - predicted_ts; + uint16_t fixed_diff = diff * 256; + if (fixed_diff >= 127) { + writer.write(127 | quat_flip << 7); + writer.write(fixed_diff); + predicted_ts += fixed_diff / 256.f; + } else { + writer.write(fixed_diff | quat_flip << 7); + predicted_ts += fixed_diff / 256.f; + } + + assert_float(predicted_ts, ts[frameId]); + } + } + + memcpy(writer.data() + end_time_offset, &predicted_ts, sizeof(float)); + return writer; + } +}; + +struct animation_t { + std::vector sequences; + std::string name; +}; + +struct block_t { + std::vector animations; + std::string name; +}; + +#define ROUNDSIZE(x) if((x) & 3) (x) += 4 - ((x)&3) +struct IfpHeader { + uint32_t ident; + uint32 size; +}; + +block_t LoadAnimFile(FILE *stream) +{ + block_t block; + + size_t totalsize = 0; + size_t totalframes = 0; + + IfpHeader anpk, info, name, dgan, cpan, anim; + char buf[256]; + int j, k, l; + float *fbuf = (float*)buf; + + // block name + RwStreamRead(stream, &anpk, sizeof(IfpHeader)); + ROUNDSIZE(anpk.size); + RwStreamRead(stream, &info, sizeof(IfpHeader)); + ROUNDSIZE(info.size); + RwStreamRead(stream, buf, info.size); + int numAnims = *(int*)buf; + + debug("Loading ANIMS %s\n", buf+4); + block.name = buf+4; + + for(j = 0; j < numAnims; j++){ + + // animation name + RwStreamRead(stream, &name, sizeof(IfpHeader)); + ROUNDSIZE(name.size); + RwStreamRead(stream, buf, name.size); + + debug("Animation: %s\n", buf); + block.animations.push_back(animation_t()); + animation_t &animation = block.animations.back(); + animation.name = buf; + + // DG info has number of nodes/sequences + RwStreamRead(stream, (char*)&dgan, sizeof(IfpHeader)); + ROUNDSIZE(dgan.size); + RwStreamRead(stream, (char*)&info, sizeof(IfpHeader)); + ROUNDSIZE(info.size); + RwStreamRead(stream, buf, info.size); + int numSequences = *(int*)buf; + + for(k = 0; k < numSequences; k++){ + animation.sequences.push_back(sequence_t()); + sequence_t &seq = animation.sequences.back(); + + // Each node has a name and key frames + RwStreamRead(stream, &cpan, sizeof(IfpHeader)); + ROUNDSIZE(dgan.size); + RwStreamRead(stream, &anim, sizeof(IfpHeader)); + ROUNDSIZE(anim.size); + RwStreamRead(stream, buf, anim.size); + int numFrames = *(int*)(buf+28); + + debug("Sequence: %s\n", buf); + seq.name = buf; + if(anim.size == 44) + seq.boneTag = *(int*)(buf+40); + if(numFrames == 0) + continue; + + bool hasScale = false; + bool hasTranslation = false; + RwStreamRead(stream, &info, sizeof(info)); + size_t framesize = 0; + if(strncmp((char*)&info.ident, "KRTS", 4) == 0){ + hasScale = true; + // seq->SetNumFrames(numFrames, true, compressHier); + framesize = 3 * 2 + 4 * 2 + 2; + }else if(strncmp((char*)&info.ident, "KRT0", 4) == 0){ + hasTranslation = true; + // seq->SetNumFrames(numFrames, true, compressHier); + framesize = 3 * 2 + 4 * 2 + 2; + }else if(strncmp((char*)&info.ident, "KR00", 4) == 0){ + // seq->SetNumFrames(numFrames, false, compressHier); + framesize = 4 * 2 + 2; + } + + size_t animsize = numFrames * framesize; + totalsize += animsize; + totalframes += numFrames; + + // float *frameTimes = (float*)RwMalloc(sizeof(float) * numFrames); + debug("%d frames, %zu bytes\n", numFrames, animsize); + for(l = 0; l < numFrames; l++){ + if(hasScale){ + RwStreamRead(stream, buf, 0x2C); + seq.rotations.push_back(quaternion_t{fbuf[0], fbuf[1], fbuf[2], fbuf[3]}.inverted()); + seq.translations.push_back(translation_t{fbuf[4], fbuf[5], fbuf[6]}); + seq.times.push_back(fbuf[10]); + // CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + // rot.Invert(); + // CVector trans(fbuf[4], fbuf[5], fbuf[6]); + + // seq->SetRotation(l, rot); + // seq->SetTranslation(l, trans); + // // scaling ignored + // frameTimes[l] = fbuf[10]; // absolute time here + }else if(hasTranslation){ + RwStreamRead(stream, buf, 0x20); + seq.rotations.push_back(quaternion_t{fbuf[0], fbuf[1], fbuf[2], fbuf[3]}.inverted()); + seq.translations.push_back(translation_t{fbuf[4], fbuf[5], fbuf[6]}); + seq.times.push_back(fbuf[7]); + // CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + // rot.Invert(); + // CVector trans(fbuf[4], fbuf[5], fbuf[6]); + + // seq->SetRotation(l, rot); + // seq->SetTranslation(l, trans); + // frameTimes[l] = fbuf[7]; // absolute time here + }else{ + RwStreamRead(stream, buf, 0x14); + seq.rotations.push_back(quaternion_t{fbuf[0], fbuf[1], fbuf[2], fbuf[3]}.inverted()); + seq.times.push_back(fbuf[4]); + // CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); + // rot.Invert(); + + // seq->SetRotation(l, rot); + // frameTimes[l] = fbuf[4]; // absolute time here + } + } + debug("Start time: %f, end time: %f\n", seq.times.front(), seq.times.back()); + + seq.removeQuaternionFlips(); + } + } + return block; +} + +void StoreAnimComprFile(FILE* stream, block_t block) { + IfpHeader anpv = { 0, (uint32_t)block.name.size() + 1 }; + memcpy(&anpv.ident, "ANPV", 4); + + fwrite(&anpv, sizeof(IfpHeader), 1, stream); + fwrite(block.name.c_str(), block.name.size() + 1, 1, stream); + int numAnims = block.animations.size(); + fwrite(&numAnims, sizeof(numAnims), 1, stream); + + for (int animId = 0; animId < numAnims; animId++) { + auto &anim = block.animations[animId]; + int animNameLength = anim.name.size() + 1; + fwrite(&animNameLength, sizeof(animNameLength), 1, stream); + fwrite(anim.name.c_str(), animNameLength, 1, stream); + int numSeqs = anim.sequences.size(); + fwrite(&numSeqs, sizeof(numSeqs), 1, stream); + + for (int seqId = 0; seqId < numSeqs; seqId++) { + auto &seq = anim.sequences[seqId]; + int seqNameLength = seq.name.size() + 1; + fwrite(&seqNameLength, sizeof(seqNameLength), 1, stream); + fwrite(seq.name.c_str(), seqNameLength, 1, stream); + int numFrames = seq.times.size(); + fwrite(&numFrames, sizeof(numFrames), 1, stream); + int boneTag = seq.boneTag; + fwrite(&boneTag, sizeof(boneTag), 1, stream); + + if (numFrames == 0) + continue; + + auto data = seq.getDeltaCompressedData(); + + uint32_t dataSize = data.size(); + fwrite(&dataSize, sizeof(dataSize), 1, stream); + fwrite(data.data(), dataSize, 1, stream); + } + } +} + +block_t LoadAnimComprFile(FILE* stream, block_t ref) { + block_t block; + + // Read and check header. + IfpHeader header; + if (fread(&header, sizeof(IfpHeader), 1, stream) != 1) { + fprintf(stderr, "Error reading IFP header.\n"); + exit(EXIT_FAILURE); + } + if (memcmp(&header.ident, "ANPV", 4) != 0) { + fprintf(stderr, "Invalid file header. Expected 'ANPV'.\n"); + exit(EXIT_FAILURE); + } + + // Read block name. (header.size equals block.name.size() + 1) + std::vector blockName(header.size); + if (fread(blockName.data(), header.size, 1, stream) != 1) { + fprintf(stderr, "Error reading block name.\n"); + exit(EXIT_FAILURE); + } + block.name = std::string(blockName.data()); + + // Read number of animations. + int numAnims = 0; + if (fread(&numAnims, sizeof(numAnims), 1, stream) != 1) { + fprintf(stderr, "Error reading number of animations.\n"); + exit(EXIT_FAILURE); + } + block.animations.resize(numAnims); + + // For each animation... + for (int animId = 0; animId < numAnims; animId++) { + animation_t anim; + + // Read animation name. + int animNameLength = 0; + if (fread(&animNameLength, sizeof(animNameLength), 1, stream) != 1) { + fprintf(stderr, "Error reading animation name length.\n"); + exit(EXIT_FAILURE); + } + std::vector animName(animNameLength); + if (fread(animName.data(), animNameLength, 1, stream) != 1) { + fprintf(stderr, "Error reading animation name.\n"); + exit(EXIT_FAILURE); + } + anim.name = std::string(animName.data()); + + // Read number of sequences. + int numSeqs = 0; + if (fread(&numSeqs, sizeof(numSeqs), 1, stream) != 1) { + fprintf(stderr, "Error reading number of sequences.\n"); + exit(EXIT_FAILURE); + } + anim.sequences.resize(numSeqs); + + // For each sequence... + for (int seqId = 0; seqId < numSeqs; seqId++) { + sequence_t seq; + + // Read sequence name. + int seqNameLength = 0; + if (fread(&seqNameLength, sizeof(seqNameLength), 1, stream) != 1) { + fprintf(stderr, "Error reading sequence name length.\n"); + exit(EXIT_FAILURE); + } + std::vector seqName(seqNameLength); + if (fread(seqName.data(), seqNameLength, 1, stream) != 1) { + fprintf(stderr, "Error reading sequence name.\n"); + exit(EXIT_FAILURE); + } + seq.name = std::string(seqName.data()); + + // Read number of frames. + int numFrames = 0; + if (fread(&numFrames, sizeof(numFrames), 1, stream) != 1) { + fprintf(stderr, "Error reading number of frames.\n"); + exit(EXIT_FAILURE); + } + // Prepare to store times (and, by convention, other per-frame data). + seq.times.resize(numFrames); + + // Read bone tag. + int boneTag = 0; + if (fread(&boneTag, sizeof(boneTag), 1, stream) != 1) { + fprintf(stderr, "Error reading bone tag.\n"); + exit(EXIT_FAILURE); + } + seq.boneTag = boneTag; + + // If there are keyframes, read the delta compressed data. + if (numFrames > 0) { + uint32_t dataSize = 0; + if (fread(&dataSize, sizeof(dataSize), 1, stream) != 1) { + fprintf(stderr, "Error reading delta compressed data size.\n"); + exit(EXIT_FAILURE); + } + std::vector compData(dataSize); + if (fread(compData.data(), dataSize, 1, stream) != 1) { + fprintf(stderr, "Error reading delta compressed data.\n"); + exit(EXIT_FAILURE); + } + + // Decompress the data. + seq.decompressDeltaCompressedData(compData, ref.animations[animId].sequences[seqId]); + } + + anim.sequences[seqId] = seq; + } + + block.animations[animId] = anim; + } + + return block; +} + + +int main(int argc, char **argv) +{ + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + FILE *stream = fopen(argv[1], "rb"); + if (!stream) { + fprintf(stderr, "Error: Unable to open input file %s\n", argv[1]); + return 1; + } + + auto block = LoadAnimFile(stream); + + fclose(stream); + + + stream = fopen(argv[2], "wb"); + if (!stream) { + fprintf(stderr, "Error: Unable to open output file %s\n", argv[2]); + return 2; + } + StoreAnimComprFile(stream, block); + fclose(stream); + stream = fopen(argv[2], "rb"); + if (!stream) { + fprintf(stderr, "Error: Unable to open output file %s\n", argv[2]); + return 2; + } + auto decompressedBlock = LoadAnimComprFile(stream, block); + fclose(stream); + + return 0; +} \ No newline at end of file diff --git a/dreamcast/aud2adpcm.c b/src/tools/aud2adpcm.c similarity index 98% rename from dreamcast/aud2adpcm.c rename to src/tools/aud2adpcm.c index 368d7b7d..52c9fe12 100644 --- a/dreamcast/aud2adpcm.c +++ b/src/tools/aud2adpcm.c @@ -558,6 +558,7 @@ void usage() { printf("based on wav2adpcm: 16bit mono wav to aica adpcm and vice-versa (c)2002 BERO\n" " wav2adpcm -q (To adpcm long stream)\n" " wav2adpcm -t (To adpcm long stream)\n" + " wav2adpcm -m (To adpcm MONO long stream)\n" " wav2adpcm -raw (To adpcm sfx)\n" "\n" "If you are having trouble with your input wav file you can run it" @@ -571,8 +572,10 @@ int main(int argc, char **argv) { if (argc == 4) { if (!strcmp(argv[1], "-t")) { return aud2adpcm(argv[2], argv[3], 1, 0, 0); + } else if (!strcmp(argv[1], "-m")) { + return aud2adpcm(argv[2], argv[3], 1, 1, 0); } else if (!strcmp(argv[1], "-q")) { - return aud2adpcm(argv[2], argv[3], 1, 1, 1); + return aud2adpcm(argv[2], argv[3], 1, 1, 1); } else if (!strcmp(argv[1], "-raw")) { return aud2adpcm(argv[2], argv[3], 0, 0, 0); } else { diff --git a/src/tools/coltool.cpp b/src/tools/coltool.cpp new file mode 100644 index 00000000..864f36dd --- /dev/null +++ b/src/tools/coltool.cpp @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include +#include + +// Our collision file header. In the original code the header is read from the file. +// Here we assume it contains an identifier and a size. +struct ColHeader { + int32_t ident; // should be 'LLOC' + int32_t size; // size of the following collision data chunk +}; + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "Usage: repack_collision \n"; + return 1; + } + const char* inputFile = argv[1]; + const char* outputFile = argv[2]; + + std::ifstream infile(inputFile, std::ios::binary); + if (!infile) { + std::cerr << "Error: Cannot open input file: " << inputFile << "\n"; + return 1; + } + std::ofstream outfile(outputFile, std::ios::binary); + if (!outfile) { + std::cerr << "Error: Cannot open output file: " << outputFile << "\n"; + return 1; + } + + // Process each collision model in the file. + // Each chunk starts with a ColHeader followed by header.size bytes. + while (infile.peek() != EOF) { + ColHeader header; + infile.read(reinterpret_cast(&header), sizeof(header)); + if (!infile) break; + + // Check that the header ident is correct (should equal 'LLOC') + if (header.ident != 'LLOC') { + auto curpos = infile.tellg(); + infile.seekg(0, infile.end); + auto remaining = infile.tellg() - curpos; + + if (remaining < 2048) { + return 0; + } else { + std::cerr << "Error: Invalid header identifier encountered.\n"; + return 1; + } + } + + header.ident = 'CLOC'; + + // Read the collision chunk into a work buffer. + std::vector work_buff(header.size); + infile.read(work_buff.data(), header.size); + if (!infile) break; + + // The first 24 bytes of work_buff are the model name. + // The rest of the buffer is the collision model data. + // We now “repackage” the collision model data into a new vector. + std::vector out_buff; + // Copy model name (first 24 bytes) + out_buff.insert(out_buff.end(), work_buff.begin(), work_buff.begin() + 24); + + // Set an offset into work_buff where the collision model data begins. + size_t offset = 24; + + // === Process Collision Model Data === + // Copy first 44 bytes (bounding sphere, bounding box, and extra bytes) + out_buff.insert(out_buff.end(), work_buff.begin() + offset, work_buff.begin() + offset + 44); + // We need to extract numSpheres which is stored at offset 40 (within collision data) + int16_t numSpheres = *reinterpret_cast(&work_buff[24 + 40]); + offset += 44; + + assert(work_buff.size() > offset); + // Copy each sphere data block (20 bytes per sphere) + for (int i = 0; i < numSpheres; i++) { + out_buff.insert(out_buff.end(), work_buff.begin() + offset, work_buff.begin() + offset + 20); + offset += 20; + } + + // Process lines: read 4 bytes (includes numLines) + int16_t numLines = *reinterpret_cast(&work_buff[offset]); + out_buff.insert(out_buff.end(), work_buff.begin() + offset, work_buff.begin() + offset + 4); + offset += 4; + // For each line, copy 24 bytes + for (int i = 0; i < numLines; i++) { + out_buff.insert(out_buff.end(), work_buff.begin() + offset, work_buff.begin() + offset + 24); + offset += 24; + } + + // Process boxes: next 4 bytes hold numBoxes + int16_t numBoxes = *reinterpret_cast(&work_buff[offset]); + out_buff.insert(out_buff.end(), work_buff.begin() + offset, work_buff.begin() + offset + 4); + offset += 4; + // For each box, copy 28 bytes + for (int i = 0; i < numBoxes; i++) { + assert(work_buff.size() > offset); + out_buff.insert(out_buff.end(), work_buff.begin() + offset, work_buff.begin() + offset + 28); + offset += 28; + } + + // Process vertices: next 4 bytes (numVertices) + int16_t numVertices = *reinterpret_cast(&work_buff[offset]); + out_buff.insert(out_buff.end(), work_buff.begin() + offset, work_buff.begin() + offset + 4); + offset += 4; + assert(work_buff.size() > offset); + + // For each vertex, convert to fixed point (6 bytes for every 12 bytes in) + for (int i = 0; i < numVertices; i++) { + for (int v = 0; v < 3; v++) { + assert(work_buff.size() > offset); + float d = *(float*)(work_buff.data() + offset); + offset += 4; + auto fix = llround(d * 128); + assert(fix >= -32768 && fix <32767); + int16_t fix16 = int16_t(fix); + out_buff.insert(out_buff.end(), (uint8_t*)(&fix16), (uint8_t*)(&fix16 + 1)); + } + } + + // Process triangles: next 4 bytes (numTriangles) + int16_t numTriangles = *reinterpret_cast(&work_buff[offset]); + out_buff.insert(out_buff.end(), work_buff.begin() + offset, work_buff.begin() + offset + 4); + offset += 4; + // For each triangle, copy 16 bytes + for (int i = 0; i < numTriangles; i++) { + for (int v = 0; v < 3; v++) { + int32_t d = *(int32_t*)(work_buff.data() + offset); + offset += 4; + assert(d >= -32768 && d <32767); + assert(d < numVertices); + int16_t d16 = int16_t(d); + out_buff.insert(out_buff.end(), (uint8_t*)(&d16), (uint8_t*)(&d16 + 1)); + } + + out_buff.push_back(work_buff[offset]); + out_buff.push_back(work_buff[offset+1]); // 'piece', not actually used + offset += 4; + } + // === End repackaging of collision model data === + + // align to 4 bytes + while (out_buff.size() & 3) { + out_buff.push_back(0); + } + + // Update header.size to match our repackaged chunk size. + header.size = static_cast(out_buff.size()); + + // Write the header and the repackaged collision model data to the output file. + outfile.write(reinterpret_cast(&header), sizeof(header)); + outfile.write(out_buff.data(), out_buff.size()); + } + + return 0; +} diff --git a/dreamcast/extract-sfx.cpp b/src/tools/extract-sfx.cpp similarity index 100% rename from dreamcast/extract-sfx.cpp rename to src/tools/extract-sfx.cpp diff --git a/dreamcast/imgtool.cpp b/src/tools/imgtool.cpp similarity index 93% rename from dreamcast/imgtool.cpp rename to src/tools/imgtool.cpp index 416064e1..9901b242 100644 --- a/dreamcast/imgtool.cpp +++ b/src/tools/imgtool.cpp @@ -5,6 +5,7 @@ #include #include #include +#include // Sector size constant const size_t SECTOR_SIZE = 2048; @@ -27,7 +28,12 @@ std::vector readDirFile(const std::string& dirFilePath) { DirRecord record; while (dirFile.read(reinterpret_cast(&record), sizeof(DirRecord))) { - records.push_back(record); + // assert(record.size != 0); // plaster.dff is size zero on liberty + if (record.size > 0) { + records.push_back(record); + } else { + std::cerr << "Skipping record: " << record.name << " size is zero." << std::endl; + } } return records; diff --git a/dreamcast/pack-sfx.cpp b/src/tools/pack-sfx.cpp similarity index 100% rename from dreamcast/pack-sfx.cpp rename to src/tools/pack-sfx.cpp diff --git a/src/tools/streamheaderpack.cpp b/src/tools/streamheaderpack.cpp new file mode 100644 index 00000000..689bf783 --- /dev/null +++ b/src/tools/streamheaderpack.cpp @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include + +#include +#include + +struct WavHeader { + // RIFF Header + char riff[4]; // RIFF Header Magic header + uint32_t chunkSize; // RIFF Chunk Size + char wave[4]; // WAVE Header + // "fmt" sub-chunk + char fmt[4]; // FMT header + uint32_t subchunk1Size; // Size of the fmt chunk + uint16_t audioFormat; // Audio format 1=PCM, other values indicate compression + uint16_t numOfChan; // Number of channels 1=Mono, 2=Stereo + uint32_t samplesPerSec; // Sampling Frequency in Hz + uint32_t bytesPerSec; // bytes per second + uint16_t blockAlign; // 2=16-bit mono, 4=16-bit stereo + uint16_t bitsPerSample; // Number of bits per sample + // "data" sub-chunk + char data[4]; // "data" string + uint32_t dataSize; // Size of the data section +}; + +#define DCStreamedNameTable DCStreamedNameTable_liberty +#include "../liberty/audio/sampman_dc_streams.h" +#undef DCStreamedNameTable +#define DCStreamedNameTable DCStreamedNameTable_miami +#include "../miami/audio/sampman_dc_streams.h" +#undef DCStreamedNameTable + +#ifdef _WIN32 +#define fcaseopen fopen +#else +// Case-insensitivity on linux (from https://github.com/OneSadCookie/fcaseopen) +// Returned string should freed manually (if exists) +char* casepath(char const* path, bool checkPathFirst = true) +{ + //TODO: Implement this + bool access_ok = false; //access(path, F_OK) != -1 + // printf("TODO: FIXME %s\n", __func__); + + if (checkPathFirst && access_ok ) { + // File path is correct + return nullptr; + } + + size_t l = strlen(path); + if (l > 2 && path[0] == '.' && (path[1] == '/' || path[1] == '\\')) { + // remove ./ from the start of the path + path += 2; + } + char* p = (char*)alloca(l + 1); + char* out = (char*)malloc(l + 3); // for extra ./ + strcpy(p, path); + + 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; +} + +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; +} +#endif + +int main(int argc, const char** argv) { + + size_t table_len = 0; + const char (* table)[25] = nullptr; + + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << "liberty|miami " << std::endl; + return 1; + } + + if (strcmp(argv[1], "miami") == 0) { + table = DCStreamedNameTable_miami; + table_len = sizeof(DCStreamedNameTable_miami)/sizeof(DCStreamedNameTable_miami[0]); + } else if (strcmp(argv[1], "liberty") == 0) { + table = DCStreamedNameTable_liberty; + table_len = sizeof(DCStreamedNameTable_liberty)/sizeof(DCStreamedNameTable_liberty[0]); + } else { + std::cerr << "Invalid game name" << std::endl; + return 1; + } + + FILE* fout=fcaseopen(argv[3], "wb"); + if (!fout) { + std::cerr << "Failed to open " << argv[3] << " for writing" << std::endl; + return 1; + } + + for (int i = 0; i < table_len; i++) { + std::string filename = std::string(argv[2]) + "/" + table[i]; + + FILE* f = fcaseopen(filename.c_str(), "rb"); + if (!f) { + std::cerr << "Failed to open " << filename << std::endl; + fclose(fout); + return 1; + } + WavHeader hdr; + auto rv = fread(&hdr, sizeof(hdr), 1, f); + if (rv != 1) { + std::cerr << "Failed to read header from " << filename << std::endl; + fclose(f); + fclose(fout); + return 1; + } + + uint64_t sl64 = (uint64_t)hdr.dataSize * 2000 / hdr.numOfChan / hdr.samplesPerSec; + + assert(sl64 <= INT32_MAX); + + int32_t sl32 = (int32_t)sl64; + + rv = fwrite(&sl32, sizeof(sl32), 1, fout); + if (rv != 1) { + std::cerr << "Failed to write to " << argv[2] << std::endl; + fclose(f); + fclose(fout); + return 1; + } + fclose(f); + } + fclose(fout); + + return 0; +} \ No newline at end of file diff --git a/dreamcast/texconv.cpp b/src/tools/texconv.cpp similarity index 79% rename from dreamcast/texconv.cpp rename to src/tools/texconv.cpp index 286d3eb2..fd281a1a 100644 --- a/dreamcast/texconv.cpp +++ b/src/tools/texconv.cpp @@ -3,6 +3,7 @@ #include #include +#include #include using namespace std; @@ -51,6 +52,7 @@ void pvrRegWrite(uint32 A, uint32 D) {} uint32_t pvr_map32(uint32_t offset32) {return 0;} void Hackpresent() { } void re3RemoveLeastUsedModel() { assert(false); } +void re3EmergencyRemoveModel() { assert(false); } void RwTexDictionaryGtaStreamRead1(rw::Stream*){ assert(false); } void RwTexDictionaryGtaStreamRead2(rw::Stream*, rw::TexDictionary*) { assert(false); } void pvr_ta_data(void* data, int size) { @@ -238,6 +240,40 @@ PluginAttach(void) const char* currentFile; +namespace rw { + Image* readBMP(const char *filename); + Image* readPNG(const char *filename); +} + +void InsertImage(RwTexDictionary* texDict, const char* file, const char* texName) { + RwTexture *tex; + RwRaster *raster; + RwInt32 width, height, depth, format; + RwImage *image = rw::readBMP(file); + if (!image) { + image = rw::readPNG(file); + } + + assert(image); + + RwImageFindRasterFormat(image, rwRASTERTYPETEXTURE, &width, &height, &depth, &format); + raster = RwRasterCreate(width, height, depth, format); + RwRasterSetFromImage(raster, image); + + tex = RwTextureCreate(raster); + RwTextureSetName(tex, texName); + + RwTextureSetFilterMode(tex, rwFILTERLINEAR); + + RwTexDictionaryAddTexture(texDict, tex); + + RwImageDestroy(image); +} + +std::vector> ImagesToAdd; +std::vector ImagesToRemove; +bool listTextures = false; + int main(int argc, const char** argv) { if (argc >= 5) { int width = atoi(argv[3]); @@ -247,7 +283,7 @@ int main(int argc, const char** argv) { if(height >= 16 && height <= 1024) rw::dc::maxRasterHeight = height; } - for (int i = 0; i < argc; i++) { + for (int i = 5; i < argc; i++) { if (argv[i] != nullptr) { // Downsample Parameter if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "-D") == 0) { @@ -271,6 +307,22 @@ int main(int argc, const char** argv) { } } } + + if (strcmp(argv[i], "--include-tex") == 0) { + assert(i + 2 < argc); + ImagesToAdd.emplace_back(argv[i+1], argv[i+2]); + i += 2; + } + + if (strcmp(argv[i], "--delete-tex") == 0) { + assert(i + 1 < argc); + ImagesToRemove.emplace_back(argv[i+1]); + i += 1; + } + + if (strcmp(argv[i], "--list-tex") == 0) { + listTextures = true; + } } } @@ -298,6 +350,31 @@ int main(int argc, const char** argv) { RwStreamClose(stream, nil); + if (listTextures) { + fprintf(stdout, "Incoming textures:\n"); + FORLIST(lnk, texDict->textures) { + auto tex = rw::Texture::fromDict(lnk); + fprintf(stdout, "texture: '%s'\n", tex->name); + } + } + + for (auto&& removedTextureName: ImagesToRemove) { + auto removedTexture = texDict->find(removedTextureName); + assert(removedTexture); + texDict->remove(removedTexture); + } + for (auto&& extraTexture: ImagesToAdd) { + InsertImage(texDict, extraTexture.first, extraTexture.second); + } + + if (listTextures) { + fprintf(stdout, "Processing textures:\n"); + FORLIST(lnk, texDict->textures) { + auto tex = rw::Texture::fromDict(lnk); + fprintf(stdout, "texture: '%s'\n", tex->name); + } + } + auto streamOut = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMWRITE, argv[2]); assert(streamOut && "failed to open output"); diff --git a/vendor/dc-load-ser/loader.elf b/vendor/dc-load-ser/loader.elf new file mode 100755 index 0000000000000000000000000000000000000000..ce3e146fd9def9fa9d1eaf13a6c70ade929a8ab4 GIT binary patch literal 17280 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 b1A_z15U86#GJ> 3) & 0x1F) << 0) | (((src[1] >> 2) & 0x3F) << 5) | (((src[2] >> 3) & 0x1F) << 11); @@ -461,6 +461,11 @@ void RenderCORE() { dst += bpp; src += 4; // skip alpha + + // TODO: Actually do AA + if (SCALER_CTL.hscale) { + src += 4; + } } } } diff --git a/vendor/koshle/hlekos.cpp b/vendor/koshle/hlekos.cpp index 50f934f6..ef1f66c7 100644 --- a/vendor/koshle/hlekos.cpp +++ b/vendor/koshle/hlekos.cpp @@ -28,10 +28,6 @@ maple_device_t * maple_enum_type(int n, uint32 func) { int sem_wait_timed(semaphore_t *sem, int timeout) { auto count = sem->count.load(); - // This is a hack til vlbanks are raised in a nicer way - Hackpresent(); - pvr_queue_interrupt(ASIC_EVT_PVR_VBLANK_BEGIN); - while(count <= 0 || !sem->count.compare_exchange_strong(count, count-1)) { count = sem->count.load(); } diff --git a/vendor/koshle/hlematrix3d.cpp b/vendor/koshle/hlematrix3d.cpp index caa62509..af5530cc 100644 --- a/vendor/koshle/hlematrix3d.cpp +++ b/vendor/koshle/hlematrix3d.cpp @@ -9,10 +9,10 @@ */ -#include "common.h" #include #include #include +#include matrix_t XMTRX = { { 0.0f, 0.0f, 0.0f, 0.0f }, diff --git a/vendor/koshle/hlepvr_scene.cpp b/vendor/koshle/hlepvr_scene.cpp index ca619f90..269ae5be 100644 --- a/vendor/koshle/hlepvr_scene.cpp +++ b/vendor/koshle/hlepvr_scene.cpp @@ -123,7 +123,7 @@ void pvr_scene_begin(void) { /* Currently the resize functionality is not implemented, so make sure that rx and ry are appropriate (i.e. *rx = 1024 and *ry = 512 for 640x480). Also, note that this probably won't work with DMA mode for now... */ -void pvr_scene_begin_txr(pvr_ptr_t txr, uint32 *rx, uint32 *ry) { +void pvr_scene_begin_txr(pvr_ptr_t txr, uint32_t *rx, uint32_t *ry) { int buf = pvr_state.view_target ^ 1; (void)ry; @@ -340,11 +340,22 @@ int pvr_scene_finish(void) { return 0; } +#include "emu/emu.h" +#include "dc/asic.h" +#include "refsw/refsw_tile.h" + + int pvr_wait_ready(void) { int t; assert(pvr_state.valid); + // This is a hack til vlbanks are raised in a nicer way + if (!pvr_state.to_texture[pvr_state.view_target^1]) { + Hackpresent(); + } + pvr_queue_interrupt(ASIC_EVT_PVR_VBLANK_BEGIN); + t = sem_wait_timed((semaphore_t *)&pvr_state.ready_sem, 100); if(t < 0) { diff --git a/vendor/librw/src/camera.cpp b/vendor/librw/src/camera.cpp index 34cb8213..b0204585 100644 --- a/vendor/librw/src/camera.cpp +++ b/vendor/librw/src/camera.cpp @@ -440,6 +440,50 @@ Camera::frustumTestSphere(const Sphere *s) const return res; } +int32 +Camera::frustumTestSphereNear(const Sphere *s) const +{ + int32 res = SPHEREINSIDE; + const FrustumPlane *p = this->frustumPlanes; + + // far + float32 dist = dot(p->plane.normal, s->center) - p->plane.distance; + if(s->radius < dist) + return SPHEREOUTSIDE; + p++; + + // near + dist = dot(p->plane.normal, s->center) - p->plane.distance; + if(s->radius < dist) + return SPHEREOUTSIDE; + if(s->radius > -dist) + res = SPHEREBOUNDARY_NEAR; + p++; + + // others + dist = dot(p->plane.normal, s->center) - p->plane.distance; + if(s->radius < dist) + return SPHEREOUTSIDE; + p++; + + dist = dot(p->plane.normal, s->center) - p->plane.distance; + if(s->radius < dist) + return SPHEREOUTSIDE; + p++; + + dist = dot(p->plane.normal, s->center) - p->plane.distance; + if(s->radius < dist) + return SPHEREOUTSIDE; + p++; + + dist = dot(p->plane.normal, s->center) - p->plane.distance; + if(s->radius < dist) + return SPHEREOUTSIDE; + p++; + + return res; +} + struct CameraChunkData { V2d viewWindow; diff --git a/src/core/common_defines.h b/vendor/librw/src/common_defines.h similarity index 100% rename from src/core/common_defines.h rename to vendor/librw/src/common_defines.h diff --git a/vendor/librw/src/dc/rwdc.cpp b/vendor/librw/src/dc/rwdc.cpp index fd5b47aa..48549bc0 100644 --- a/vendor/librw/src/dc/rwdc.cpp +++ b/vendor/librw/src/dc/rwdc.cpp @@ -16,7 +16,7 @@ extern const char* currentFile; #define texconvf(...) // printf(__VA_ARGS__) #endif -#include "../../../src/vmu/vmu.h" +#include "vmu/vmu.h" #include "../rwbase.h" #include "../rwerror.h" #include "../rwplg.h" @@ -37,12 +37,15 @@ extern const char* currentFile; #include #include +#define errorf(...) dbglog(DBG_CRITICAL, __VA_ARGS__) #define logf(...) // printf(__VA_ARGS__) bool re3RemoveLeastUsedModel(); +bool re3EmergencyRemoveModel(); // #include "rwdcimpl.h" #include +#include #include "alloc.h" #undef PVR_TXRFMT_STRIDE @@ -167,18 +170,15 @@ static_assert(alignof(pvr_vertex16_t) == 32, "pvr_vertex16_t alignof mismatch"); #define MATH_Fast_Invert(x) ({ (((x) < 0.0f)? -1.0f : 1.0f) * frsqrt((x) * (x)); }) -#define logf(...) // printf(__VA_ARGS__) - static pvr_dr_state_t drState; #include +float VIDEO_MODE_SCALE_X; + #if !defined(DC_TEXCONV) && !defined(DC_SIM) #include -#define VIDEO_MODE_WIDTH vid_mode->width -#define VIDEO_MODE_HEIGHT vid_mode->height - #define mat_trans_nodiv_nomod(x, y, z, x2, y2, z2, w2) do { \ register float __x __asm__("fr12") = (x); \ register float __y __asm__("fr13") = (y); \ @@ -288,8 +288,6 @@ void rw_mat_load_4x4(rw::Matrix* mtx) { } #include -#define VIDEO_MODE_WIDTH 640 -#define VIDEO_MODE_HEIGHT 480 #define frsqrt(a) (1.0f/sqrt(a)) #define dcache_pref_block(a) __builtin_prefetch(a) @@ -305,6 +303,8 @@ void rw_mat_load_4x4(rw::Matrix* mtx) { #define mat_identity(a) #define pvr_fog_table_color(a,r,g,b) #define pvr_fog_table_linear(s,e) +#define pvr_fog_table_exp(d) +#define pvr_fog_table_custom(d) #endif #define mat_trans_single3_nomod(x_, y_, z_, x2, y2, z2) do { \ @@ -416,6 +416,66 @@ void leave_oix_() { #endif } +void enter_ocr_() { + #if defined(DC_SH4) + auto mask = irq_disable(); + dcache_purge_all(); + volatile uint32_t * CCN_CCR = (uint32_t *)0xFF00001C; + *CCN_CCR |= (1 << 5); // enable OCR (ORA) + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + + irq_restore(mask); + #endif +} + +void leave_ocr_() { + #if defined(DC_SH4) + auto mask = irq_disable(); + dcache_inval_range(0x92000000, 8192); + dcache_purge_all(); + volatile uint32_t * CCN_CCR = (uint32_t *)0xFF00001C; + *CCN_CCR &= ~( 1 << 5); // disable OCR (ORA) + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + __asm__ __volatile__ ("nop"); + irq_restore(mask); + #endif +} + #if defined(DC_SH4) #define FLUSH_TA_DATA(src) do { __asm__ __volatile__("ocbwb @%0" : : "r" (src) : "memory"); } while(0) #else @@ -483,7 +543,7 @@ void DCE_MatrixViewport(float x, float y, float width, float height) { DCE_MAT_SCREENVIEW[1][1] = height * 0.5f; DCE_MAT_SCREENVIEW[2][2] = 1; DCE_MAT_SCREENVIEW[3][0] = -DCE_MAT_SCREENVIEW[0][0] + x; - DCE_MAT_SCREENVIEW[3][1] = VIDEO_MODE_HEIGHT - (DCE_MAT_SCREENVIEW[1][1] + y); + DCE_MAT_SCREENVIEW[3][1] = height - (DCE_MAT_SCREENVIEW[1][1] + y); } void DCE_InitMatrices() { @@ -491,8 +551,6 @@ void DCE_InitMatrices() { mat_identity(); mat_store(&DCE_MAT_SCREENVIEW); - - DCE_MatrixViewport(0, 0, VIDEO_MODE_WIDTH, VIDEO_MODE_HEIGHT); } } @@ -663,7 +721,10 @@ void malloc_stats() { } #define UNIMPL_LOGV(...) #endif +Camera* rwdcCam; + void beginUpdate(Camera* cam) { + rwdcCam = cam; float view[16], proj[16]; // View Matrix @@ -723,6 +784,8 @@ void beginUpdate(Camera* cam) { proj[14] = -cam->nearPlane*proj[10]; memcpy4(&cam->devProj, proj, sizeof(RawMatrix)); + DCE_MatrixViewport(0, 0, cam->frameBuffer->width * VIDEO_MODE_SCALE_X, cam->frameBuffer->height); + mat_load((matrix_t*)&DCE_MAT_SCREENVIEW); mat_apply((matrix_t*)&cam->devProj); mat_store((matrix_t*)&cam->devProjScreen); @@ -763,7 +826,7 @@ void dcMotionBlur_v1(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { auto doquad = [=](float x, float y, float w, float h, float tx, float ty, float tw, float th) { auto vtx = reinterpret_cast(pvr_dr_target(drState)); vtx->flags = PVR_CMD_VERTEX; - vtx->x = x; + vtx->x = x * VIDEO_MODE_SCALE_X; vtx->y = y; vtx->z = 1000000.0f; vtx->u = tx/1024.f; @@ -773,7 +836,7 @@ void dcMotionBlur_v1(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { vtx = reinterpret_cast(pvr_dr_target(drState)); vtx->flags = PVR_CMD_VERTEX; - vtx->x = x+w; + vtx->x = (x+w) * VIDEO_MODE_SCALE_X; vtx->y = y; vtx->z = 1000000.0f; vtx->u = (tx+tw)/1024.f; @@ -783,7 +846,7 @@ void dcMotionBlur_v1(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { vtx = reinterpret_cast(pvr_dr_target(drState)); vtx->flags = PVR_CMD_VERTEX; - vtx->x = x; + vtx->x = x * VIDEO_MODE_SCALE_X; vtx->y = y+h; vtx->z = 1000000.0f; vtx->u = tx/1024.f; @@ -793,7 +856,7 @@ void dcMotionBlur_v1(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { vtx = reinterpret_cast(pvr_dr_target(drState)); vtx->flags = PVR_CMD_VERTEX_EOL; - vtx->x = x+w; + vtx->x = (x+w) * VIDEO_MODE_SCALE_X; vtx->y = y+h; vtx->z = 1000000.0f; vtx->u = (tx+tw)/1024.f; @@ -867,7 +930,7 @@ void dcMotionBlur_v3(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { float umin, float umax, float vmin, float vmax, uint32_t col) { auto vtx = reinterpret_cast(pvr_dr_target(drState)); vtx->flags = PVR_CMD_VERTEX; - vtx->x = x; + vtx->x = x * VIDEO_MODE_SCALE_X; vtx->y = y; vtx->z = z; vtx->u = umin; @@ -877,7 +940,7 @@ void dcMotionBlur_v3(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { vtx = reinterpret_cast(pvr_dr_target(drState)); vtx->flags = PVR_CMD_VERTEX; - vtx->x = x+w; + vtx->x = (x+w) * VIDEO_MODE_SCALE_X; vtx->y = y; vtx->z = z; vtx->u = umax; @@ -887,7 +950,7 @@ void dcMotionBlur_v3(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { vtx = reinterpret_cast(pvr_dr_target(drState)); vtx->flags = PVR_CMD_VERTEX; - vtx->x = x; + vtx->x = x * VIDEO_MODE_SCALE_X; vtx->y = y+h; vtx->z = z; vtx->u = umin; @@ -897,7 +960,7 @@ void dcMotionBlur_v3(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { vtx = reinterpret_cast(pvr_dr_target(drState)); vtx->flags = PVR_CMD_VERTEX_EOL; - vtx->x = x+w; + vtx->x = (x+w) * VIDEO_MODE_SCALE_X; vtx->y = y+h; vtx->z = z; vtx->u = umax; @@ -1012,15 +1075,29 @@ void dcMotionBlur_v3(uint8_t a, uint8_t r, uint8_t g, uint8_t b) { void allocDefrag(int iterations); -void endUpdate(Camera*) { +void endUpdate(Camera* cam) { + // TODO: Fix KOS RTT instead + if (cam->frameBuffer->type != Raster::CAMERATEXTURE) { #if !defined(DC_SIM) && defined(SKIP_FRAMES) if (pvr_check_ready() >= 0) #endif { pvr_set_zclip(0.0f); pvr_wait_ready(); - pvr_scene_begin(); + pvr_set_bg_color(cam->clearColor.red / 255.0f, cam->clearColor.green / 255.0f, cam->clearColor.blue / 255.0f); + + if (cam->frameBuffer->type == Raster::CAMERATEXTURE) { + auto natras = GETDCRASTEREXT(cam->frameBuffer); + uint32 rx = cam->frameBuffer->width; + uint32 ry = cam->frameBuffer->height; + pvr_scene_begin_txr(natras->raster->texaddr, &rx, &ry); + } else if (cam->frameBuffer->type == Raster::CAMERA) { + pvr_scene_begin(); + } else { + assert(false && "invalid cam->frameBuffer type"); + } + pvr_dr_init(&drState); pvr_list_begin(PVR_LIST_OP_POLY); enter_oix(); @@ -1031,7 +1108,7 @@ void endUpdate(Camera*) { } pvr_list_finish(); if (ptCallbacks.size()) { - PVR_SET(0x11C, 128); // PT Alpha test value + PVR_SET(0x11C, 64); // PT Alpha test value pvr_dr_init(&drState); pvr_list_begin(PVR_LIST_PT_POLY); for (auto&& cb: ptCallbacks) { @@ -1058,6 +1135,7 @@ void endUpdate(Camera*) { pvr_scene_finish(); leave_oix(); } + } opCallbacks.clear(); ptCallbacks.clear(); blendCallbacks.clear(); @@ -1067,7 +1145,10 @@ void endUpdate(Camera*) { matfxContexts.clear(); } -void clearCamera(Camera*,RGBA*,uint32) { +void clearCamera(Camera* cam,RGBA* col,uint32 flags) { + if (flags & rwCAMERACLEARIMAGE) { + cam->clearColor = *col; + } UNIMPL_LOG(); } @@ -1098,6 +1179,7 @@ static bool doAlphaTest; static uint8_t fogFuncPvr = PVR_FOG_DISABLE; static uint32_t fogColor = 0; +static float fogStart = 0.0f; static uint32 cullModePvr; static inline unsigned pvrCullMode(uint32_t cullMode) { @@ -1198,8 +1280,9 @@ setRenderState(int32 state, void *pvalue) fogFuncPvr = value ? PVR_FOG_TABLE : PVR_FOG_DISABLE; break; case FOGCOLOR: +#if !defined(DC_TEXCONV) // Set fog color when state changes - if(fogColor != value) { + if(fogColor != value || fogStart != RwCameraGetFogDistance(rwdcCam)) { fogColor = value; RGBA c; c.red = value; @@ -1207,9 +1290,22 @@ setRenderState(int32 state, void *pvalue) c.blue = value>>16; c.alpha = value>>24; pvr_fog_table_color(c.alpha / 255.0f, c.red / 255.0f, c.green / 255.0f, c.blue / 255.0f); - pvr_fog_table_linear(50.0f, 450.0f); + + fogStart = RwCameraGetFogDistance(rwdcCam); + float fogEnd = RwCameraGetFarClipPlane(rwdcCam); + float fogIntensity[129]; + uint8_t idx = 0; + float startIntensity = (-fogStart) / (fogEnd - fogStart); //interpolate between start and end to get initial intensity + float step = (1.0f - startIntensity) / 129; // we have 129 entries, create a step such that start + (step*129) = 1.0 + for(int i = 128; i >= 0; i--) { + fogIntensity[i] = startIntensity + (idx++ * step); + } + pvr_fog_far_depth(fogEnd); + pvr_fog_table_custom(fogIntensity); } +#endif break; + // case CULLMODE: // if(rwStateCache.cullmode != value){ // rwStateCache.cullmode = value; @@ -1407,7 +1503,7 @@ void im2DRenderPrimitive(PrimitiveType primType, void *vertices, int32_t numVert { auto *pvrVert = pvr_dr_target(drState); pvrVert->flags = flags; - pvrVert->x = gtaVert.x; + pvrVert->x = gtaVert.x * VIDEO_MODE_SCALE_X; pvrVert->y = gtaVert.y; pvrVert->z = MATH_Fast_Invert(gtaVert.w); // this is perfect for almost every case... pvrVert->u = gtaVert.u; @@ -2342,6 +2438,36 @@ __attribute__ ((noinline)) void submitMeshlet(uint8_t* OCR, const int8_t* indexD } while(--indexCount); } +template +__attribute__ ((noinline)) void submitMeshletFallback(uint8_t* OCR, const int8_t* indexData, uint32_t indexCount) { + struct SQBUF { + union { + uint32_t flags; + uint64_t data[4]; + uint8_t data8[32]; + }; + }; + + SQBUF* sq = (SQBUF*)pvr_dr_target(drState); + + static_assert(sizeof(SQBUF) == 32); + + do { + auto idx = *indexData++; + auto flags = idx & 0x80 ? PVR_CMD_VERTEX_EOL : PVR_CMD_VERTEX; + auto lookup_idx = idx & 0x7F; + + auto src = (SQBUF*)(OCR + lookup_idx * 64); + src[0].flags = flags; + *sq = src[0]; + pvr_dr_commit(sq); + if (textured) { + *sq = src[1]; + pvr_dr_commit(sq); + } + } while(--indexCount); +} + #if defined(DC_SH4) template<> @@ -2421,7 +2547,7 @@ __attribute__ ((noinline)) void submitMeshlet(uint8_t* OCR, const int8_t* // 8 kb in total #if defined(DC_SH4) -uint8_t* OCR_SPACE = (uint8_t*)0x92000000; +uint8_t* OCR_SPACE; #else uint8_t OCR_SPACE[32 * 256] __attribute__((aligned(32))); #endif @@ -2652,6 +2778,159 @@ __attribute__ ((noinline)) void clipAndsubmitMeshlet(uint8_t* vertexData, const } }; } while(indexCount != 0); + + #undef FILLVERT + #undef SUBMIT_VTX + #undef SUBMIT_INTERPOLATE +} + + +template +__attribute__ ((noinline)) void clipAndsubmitMeshletFallback(uint8_t* vertexData, const int8_t* indexData, uint32_t indexCount) { + + struct SQBUF { + union { + uint32_t flags; + uint64_t data[4]; + uint8_t data8[32]; + }; + }; + + static_assert(sizeof(SQBUF) == 32); + + SQBUF* sq = (SQBUF*)pvr_dr_target(drState); + + constexpr int8_t VERTEX = 0; + constexpr int8_t VERTEX_EOL = 0x80; + + #define FILLVERT(n) \ + do { \ + auto idx = *indexData++; \ + auto local_idx = idx & 0x7f; \ + eol_now = idx & 0x80; \ + auto local_ptr = (vertexData + local_idx * 64); \ + vpp[n] = local_ptr; \ + auto v = (const pvr_vertex64_t*)local_ptr; \ + vismask >>= 1; \ + if((textured?v->tex_z:v->o_b) >= -v->o_g) vismask |= 0b100; \ + indexCount--; \ + currentCount++; \ + } while(0) + + #define SUBMIT_VTX(vid, eolf) \ + do { \ + auto src = (SQBUF*) vpp[vid]; \ + src[0].flags = eolf ? PVR_CMD_VERTEX_EOL : PVR_CMD_VERTEX; \ + *sq = src[0]; \ + pvr_dr_commit(sq); \ + if (textured) { \ + *sq = src[1]; \ + pvr_dr_commit(sq); \ + } \ + } while(0) + + #define SUBMIT_INTERPOLATE(vid1, vid2, eolf) \ + do { \ + sq = (SQBUF*)interpolateAndSubmit(sq, vpp[vid1], vpp[vid2], eolf ? PVR_CMD_VERTEX_EOL : PVR_CMD_VERTEX); \ + } while(0) + + uint32_t vismask = 0; + + uint8_t* vpp[3]; + + int8_t eol = 0; + int8_t eol_now = 0; + + do { + uint32_t currentCount = -1; + + FILLVERT(0); + FILLVERT(1); + FILLVERT(2); + + if (vismask & 1) { + SUBMIT_VTX(0, VERTEX); + if (vismask & 2) { + // both first verts visible + SUBMIT_VTX(1, VERTEX); + } else { + // 0 visible, 1 hidden + SUBMIT_INTERPOLATE(0, 1, VERTEX); + } + } else if (vismask & 2) { + // 0 hidden, 1 visible + SUBMIT_INTERPOLATE(1, 0, VERTEX); + SUBMIT_VTX(1, VERTEX); + } + + eol = 0; + // each remaining vertex of the strip + while(!eol) { + // "ring buffery" indices + uint8_t vertZeroIdx = (currentCount - 2) % 3; + uint8_t vertOneIdx = (currentCount - 1) % 3; + uint8_t vertTwoIdx = currentCount % 3; + //dcache_pref_block(&vph[vertZeroIdx]); not sure where to put this honestly -jaxyn + + eol = eol_now; + + if (!vismask) { + if (!eol) { + // "ring buffery" filling + FILLVERT(vertZeroIdx); + } + continue; + } + + if (vismask == 7) { + // all visible + SUBMIT_VTX(vertTwoIdx, eol); + if (!eol) { + // "ring buffery" filling + FILLVERT(vertZeroIdx); + } + continue; + } + + switch (vismask) { + case 1: // 0 visible, 1 and 2 hidden + // pause strip + SUBMIT_INTERPOLATE(vertZeroIdx, vertTwoIdx, VERTEX_EOL); + break; + case 3: // 0 and 1 visible, 2 hidden + SUBMIT_INTERPOLATE(vertZeroIdx, vertTwoIdx, VERTEX); + SUBMIT_VTX(vertOneIdx, VERTEX); + case 2: // 0 hidden, 1 visible, 2 hidden + SUBMIT_INTERPOLATE(vertOneIdx, vertTwoIdx, eol); + break; + case 4: // 0 and 1 hidden, 2 visible + SUBMIT_INTERPOLATE(vertTwoIdx, vertZeroIdx, VERTEX); + if (currentCount & 0x01) { // flip directionality + case 5: // 0 visible, 1 hidden, 2 visible + SUBMIT_VTX(vertTwoIdx, VERTEX); + } + SUBMIT_INTERPOLATE(vertTwoIdx, vertOneIdx, VERTEX); + SUBMIT_VTX(vertTwoIdx, eol); + break; + case 6: // 0 hidden, 1 and 2 visible + SUBMIT_INTERPOLATE(vertTwoIdx, vertZeroIdx, VERTEX); + SUBMIT_VTX(vertOneIdx, VERTEX); + SUBMIT_VTX(vertTwoIdx, eol); + break; + default: + break; + } + + if (!eol) { + // "ring buffery" filling + FILLVERT(vertZeroIdx); + } + }; + } while(indexCount != 0); + + #undef FILLVERT + #undef SUBMIT_VTX + #undef SUBMIT_INTERPOLATE } @@ -2711,7 +2990,7 @@ void tnlMeshletSkinVertices(uint8_t *OCR, uint8_t *OCR_normal, const uint8_t* ve } while(--count != 0); } } else if (!(flags & 0x80)) { - int count = flags & 0x7FFF; + int count = (flags & 0x7F) + 1; uint8_t* dstVertexBytes = dest + *skinningIndexData++; do { @@ -2802,7 +3081,7 @@ void tnlMeshletSkinVertices(uint8_t *OCR, uint8_t *OCR_normal, const uint8_t* ve } while(--count != 0); } } else if (!(flags & 0x80)) { - int count = flags & 0x7FFF; + int count = (flags & 0x7F) + 1; uint8_t* dstNormalBytes = destNormal + *skinningIndexData++; do { @@ -2878,9 +3157,9 @@ void tnlMeshletEnvMap(uint8_t* OCR, uint8_t* normal, int vertexCount, int vertex } -inline __attribute__((always_inline)) RwFrustumTestResult AtomicFrustumSphereCB(Atomic *atomic, rw::Camera *cam) +inline __attribute__((always_inline)) int32 AtomicFrustumSphereNearCB(Atomic *atomic, rw::Camera *cam) { - return RwCameraFrustumTestSphere(cam, atomic->getWorldBoundingSphere()); + return cam->frustumTestSphereNear(atomic->getWorldBoundingSphere()); } static constexpr void (*tnlMeshletTransformSelector[6])(uint8_t* dst, const uint8_t* vertexData, uint32_t vertexCount, uint32_t vertexSize) { @@ -2918,16 +3197,26 @@ static constexpr void (*tnlMeshletDiffuseColorSelector[8])(uint8_t* dstCol, cons &tnlMeshletDiffuseColor<4, true>, }; -static constexpr void (*submitMeshletSelector[2])(uint8_t* OCR, const int8_t* indexData, uint32_t indexCount) = { +static void (*submitMeshletSelector[2])(uint8_t* OCR, const int8_t* indexData, uint32_t indexCount) = { &submitMeshlet, &submitMeshlet, }; -static constexpr void (*clipAndsubmitMeshletSelector[2])(uint8_t* OCR, const int8_t* indexData, uint32_t indexCount) = { +static void (*submitMeshletSelectorFallback[2])(uint8_t* OCR, const int8_t* indexData, uint32_t indexCount) = { + &submitMeshletFallback, + &submitMeshletFallback, +}; + +static void (*clipAndsubmitMeshletSelector[2])(uint8_t* OCR, const int8_t* indexData, uint32_t indexCount) = { &clipAndsubmitMeshlet, &clipAndsubmitMeshlet, }; +static void (*clipAndsubmitMeshletSelectorFallback[2])(uint8_t* OCR, const int8_t* indexData, uint32_t indexCount) = { + &clipAndsubmitMeshletFallback, + &clipAndsubmitMeshletFallback, +}; + static constexpr void(*tnlMeshletSkinVerticesSelector[4])(uint8_t *OCR, uint8_t *OCR_normal, const uint8_t* vertex, const uint8_t* normals, const uint8_t* skinWeights, const uint8_t* skinIndexes, int vertexCount, int vertexSize, Matrix* skinMatrices) = { &tnlMeshletSkinVertices, &tnlMeshletSkinVertices, @@ -3219,11 +3508,20 @@ void pvr_poly_cxt_txr_fast(pvr_poly_hdr_t *hdr, pvr_list_t list, +size_t vertexBufferFree() { + size_t end = PVR_GET(PVR_TA_VERTBUF_END); + size_t pos = PVR_GET(PVR_TA_VERTBUF_POS); + + size_t free = end - pos; + + return free; +} + void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) { rw::Camera *cam = engine->currentCamera; // Frustum Culling - auto global_frustumTestResult = AtomicFrustumSphereCB(atomic, cam); + auto global_frustumTestResult = AtomicFrustumSphereNearCB(atomic, cam); if (global_frustumTestResult == rwSPHEREOUTSIDE) { return; @@ -3285,6 +3583,8 @@ void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) { for (int16_t n = 0; n < numMeshes; n++) { bool doBlend = meshes[n].material->color.alpha != 255; // TODO: check all vertexes for alpha? + bool doBlendMaterial = doBlend; + bool textured = geo->numTexCoordSets && meshes[n].material->texture; if (textured) { doBlend |= Raster::formatHasAlpha(meshes[n].material->texture->raster->format); @@ -3334,7 +3634,7 @@ void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) { pvr_poly_cxt_t cxt; int pvrList; if (doBlend || isMatFX) { - if (doAlphaTest) { + if (doAlphaTest && !doBlendMaterial) { pvrList = PVR_LIST_PT_POLY; } else { pvrList = PVR_LIST_TR_POLY; @@ -3400,6 +3700,9 @@ void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) { // clipping performed per meshlet auto renderCB = [contextId, n] { + if (vertexBufferFree() < (128 * 1024)) { + return; + } const atomic_context_t* acp = &atomicContexts[contextId]; auto geo = acp->geo; auto mesh = geo->meshHeader->getMeshes() + n; @@ -3456,25 +3759,21 @@ void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) { unsigned clippingRequired = 0; if (!global_needsNoClip) { - RwSphere sphere = meshlet->boundingSphere; - RwV3dTransformPoints(&sphere.center, &sphere.center, 1, atomic->getFrame()->getLTM()); - auto local_frustumTestResult = RwCameraFrustumTestSphere(cam, &sphere); - if ( local_frustumTestResult == rwSPHEREOUTSIDE) { - // printf("Outside frustum cull\n"); - continue; - } - - if (local_frustumTestResult == rwSPHEREBOUNDARY) { - // printf("meshlet %d, vertexOffset %d, indexOffset %d, vertexCount %d, indexCount %d\n", meshletNum, meshlet->vertexOffset, meshlet->indexOffset, meshlet->vertexCount, meshlet->indexCount); - mat_load(&worldView); // Number of cycles: ~11. + if (!skin) { + RwSphere sphere = meshlet->boundingSphere; + RwV3dTransformPoints(&sphere.center, &sphere.center, 1, atomic->getFrame()->getLTM()); - float x, y, z, w; - - mat_trans_nodiv_nomod(meshlet->boundingSphere.center.x, meshlet->boundingSphere.center.y, meshlet->boundingSphere.center.z, x, y, z, w); - - if (z < meshlet->boundingSphere.radius) { + auto local_frustumTestResult = cam->frustumTestSphereNear(&sphere);; + if ( local_frustumTestResult == Camera::SPHEREOUTSIDE) { + // printf("Outside frustum cull\n"); + continue; + } + + if (local_frustumTestResult == Camera::SPHEREBOUNDARY_NEAR) { clippingRequired = 1 + textured; } + } else { + clippingRequired = 1 + textured; } } @@ -3634,70 +3933,73 @@ void defaultRenderCB(ObjPipeline *pipe, Atomic *atomic) { } } } else if (geo->meshHeader->flags & rw::MeshHeader::TRISTRIP) { - auto numIndices = mesh->numIndices; - auto vertices = geo->morphTargets[0].vertices; - auto texcoords = geo->texCoords[0]; - auto colors = geo->colors; + if (geo->numVertices <= 128) { + auto numIndices = mesh->numIndices; + auto vertices = geo->morphTargets[0].vertices; + auto texcoords = geo->texCoords[0]; + auto colors = geo->colors; - assert(numIndices >= 3); - assert(geo->numVertices <= 128); - bool isPrelit = !!(geo->flags & Geometry::PRELIT); - assert(isPrelit); - assert(textured); - bool isNormaled = !!(geo->flags & Geometry::NORMALS); - assert(!isNormaled); + assert(numIndices >= 3); + bool isPrelit = !!(geo->flags & Geometry::PRELIT); + assert(isPrelit); + assert(textured); + bool isNormaled = !!(geo->flags & Geometry::NORMALS); + assert(!isNormaled); - std::vector indices(numIndices); - for (int i = 0; i < numIndices; i++) { - auto idx = mesh->indices[i]; - assert(idx < 128); - indices[i] = idx; + std::vector indices(numIndices); + for (int i = 0; i < numIndices; i++) { + auto idx = mesh->indices[i]; + assert(idx < 128); + indices[i] = idx; + } + indices.back() |= 0x80; + + pvr_vertex64_t *vd = (pvr_vertex64_t *)OCR_SPACE; + mat_load(&mtx); // Number of cycles: ~11 + + for (int idx = 0; idx < geo->numVertices; idx++) { + auto& vert = vertices[idx]; + auto& c = colors[idx]; + auto& t = texcoords[idx]; + + float x, y, z, w; + mat_trans_nodiv_nomod(vert.x, vert.y, vert.z, + x, y, z, w); + + vd->o_a = x; + vd->o_r = y; + vd->tex_z = z; + vd->o_g = w; + + w = frsqrt(w * w); + + vd->x = x * w; + vd->y = y * w; + vd->z = w; + + vd->a = c.alpha * (1/255.0f); + vd->r = c.red * (1/255.0f); + vd->g = c.green * (1/255.0f); + vd->b = c.blue * (1/255.0f); + + float16 u = texcoords[idx].u; + float16 v = texcoords[idx].v; + vd->u = u.raw; + vd->v = v.raw; + vd++; + } + + clipAndsubmitMeshletSelector[textured](OCR_SPACE, indices.data(), indices.size()); + } else { + // TODO: Fix this for large meshes (water in miami) } - indices.back() |= 0x80; - - pvr_vertex64_t *vd = (pvr_vertex64_t *)OCR_SPACE; - mat_load(&mtx); // Number of cycles: ~11 - - for (int idx = 0; idx < geo->numVertices; idx++) { - auto& vert = vertices[idx]; - auto& c = colors[idx]; - auto& t = texcoords[idx]; - - float x, y, z, w; - mat_trans_nodiv_nomod(vert.x, vert.y, vert.z, - x, y, z, w); - - vd->o_a = x; - vd->o_r = y; - vd->tex_z = z; - vd->o_g = w; - - w = frsqrt(w * w); - - vd->x = x * w; - vd->y = y * w; - vd->z = w; - - vd->a = c.alpha * (1/255.0f); - vd->r = c.red * (1/255.0f); - vd->g = c.green * (1/255.0f); - vd->b = c.blue * (1/255.0f); - - float16 u = texcoords[idx].u; - float16 v = texcoords[idx].v; - vd->u = u.raw; - vd->v = v.raw; - vd++; - } - - clipAndsubmitMeshletSelector[textured](OCR_SPACE, indices.data(), indices.size()); } else { // no trilist assets anymore assert(false && "Unsupported geometry type"); } }; if (doBlend || isMatFX) { - if (doAlphaTest) { + if (doAlphaTest && !doBlendMaterial) { ptCallbacks.emplace_back(std::move(renderCB)); } else { blendCallbacks.emplace_back(std::move(renderCB)); @@ -3728,6 +4030,10 @@ pvr_ptr_t allocTexture(DcRaster* ctx, size_t size) { break; } dbglog(DBG_CRITICAL, "failed to free or defrag vram, sz: %lu, cont: %lu, free: %lu\n", size, alloc_count_continuous(), alloc_count_free()); + if (re3EmergencyRemoveModel()) { + dbglog(DBG_CRITICAL, "Managed to re3EmergencyRemoveModel, sz: %lu, cont: %lu, free: %lu\n", size, alloc_count_continuous(), alloc_count_free()); + continue; + } return 0; } rv = alloc_malloc(ctx, size); @@ -3746,31 +4052,58 @@ rasterCreate(Raster* raster) { auto natras = GETDCRASTEREXT(raster); - if (raster->type != Raster::TEXTURE) { - printf("rasterCreate: unsupported type %d\n", raster->type); + if (raster->type != Raster::TEXTURE && raster->type != Raster::CAMERATEXTURE && raster->type != Raster::ZBUFFER) { + logf("rasterCreate: unsupported type %d\n", raster->type); } + if (raster->width < 8) { + logf("rasterCreate: Increasing width to 8 from %d\n", raster->width); + raster->width = 8; + } + + if (raster->height < 8) { + logf("rasterCreate: Increasing height to 8 from %d\n", raster->height); + raster->height = 8; + } + + if (raster->type == Raster::CAMERATEXTURE) { + logf("CameraTexture: %d x %d\n", raster->width, raster->height); + } else if (raster->type == Raster::CAMERA) { + logf("Camera: %d x %d (ignored)\n", raster->width, raster->height); + raster->flags |= Raster::DONTALLOCATE; + raster->stride = 0; + return raster; + } else if (raster->type == Raster::ZBUFFER) { + logf("ZBuffer: %d x %d (ignored)\n", raster->width, raster->height); + raster->flags |= Raster::DONTALLOCATE; + raster->stride = 0; + return raster; + } + if(raster->width == 0 || raster->height == 0){ raster->flags |= Raster::DONTALLOCATE; raster->stride = 0; return raster; } - if (raster->width < 8) { - printf("rasterCreate: Increasing width to 8 from %d\n", raster->width); - raster->width = 8; + + auto rasterFmt = raster->format & 0x0F00; + + if (raster->type == Raster::CAMERATEXTURE) { + if (rasterFmt == Raster::DEFAULT && raster->depth == 0) { + logf("CameraTexture: Default means 565?\n"); + raster->depth = 16; + raster->format |= Raster::C565; + } } - if (raster->height < 8) { - printf("rasterCreate: Increasing height to 8 from %d\n", raster->height); - raster->height = 8; - } - auto rasterFmt = raster->format & 0x0F00; + rasterFmt = raster->format & 0x0F00; + // assert(raster->depth == 16); if (raster->depth != 16) { - raster->depth = 16; // TODO: stop this from happening - printf("rasterCreate: Usupported raster depth: this raster will be corrupted\n"); + errorf("rasterCreate: Usupported raster depth %d: this raster will be corrupted\n", raster->depth); + raster->depth = 16; } natras->raster = (DcRaster*)malloc(sizeof(DcRaster)); @@ -3779,6 +4112,9 @@ rasterCreate(Raster* raster) natras->raster->u = __builtin_ctz(raster->width) - 3; natras->raster->v = __builtin_ctz(raster->height) - 3; + assert(raster->width == 1 << (natras->raster->u + 3)); + assert(raster->height = 1 << (natras->raster->v + 3)); + if (rasterFmt == Raster::C565) { natras->raster->pvr_flags |= PVR_TXRFMT_RGB565; } else if (rasterFmt == Raster::C1555) { @@ -3787,11 +4123,17 @@ rasterCreate(Raster* raster) natras->raster->pvr_flags |= PVR_TXRFMT_ARGB4444; } else { // TODO: stop this from happening - printf("rasterCreate: Usupported raster depth: this raster will be corrupted\n"); + printf("rasterCreate: Usupported raster rasterFmt %X: this raster will be corrupted\n", rasterFmt); // assert(false && "unsupported rasterFmt"); } + raster->stride = raster->width * 2; + + if (raster->type == Raster::CAMERATEXTURE) { + natras->raster->texaddr = allocTexture(natras->raster, raster->width * raster->height * 2); + natras->raster->pvr_flags |= PVR_TXRFMT_NONTWIDDLED; + } return raster; } @@ -4017,7 +4359,7 @@ rasterFromImage(Raster* raster, Image* image) std::vector imageData; if (image->depth == 32) { - assert(rasterFmt == Raster::C4444 || rasterFmt == Raster::C1555); + assert(rasterFmt == Raster::C4444 || rasterFmt == Raster::C1555 || rasterFmt == Raster::C565 /* DXT compression */); imageData = createImageFromData_ARGB8888(image->pixels, image->width, image->height, image->stride); } else if (image->depth == 24) { assert(rasterFmt == Raster::C565); @@ -4061,7 +4403,7 @@ rasterFromImage(Raster* raster, Image* image) #if defined(_WIN32) || defined(_WIN64) case PVRTEX: snprintf(encodeCommand, sizeof(encodeCommand), - "pvrtex\\pvrtex.exe -i %s -o %s -c small -d", filename_tga, filename_pvr); + "..\\vendor\\pvrtex\\pvrtex.exe -i %s -o %s -c small -d", filename_tga, filename_pvr); break; case PVRTOOL: snprintf(encodeCommand, sizeof(encodeCommand), @@ -4071,7 +4413,7 @@ rasterFromImage(Raster* raster, Image* image) #else case PVRTEX: snprintf(encodeCommand, sizeof(encodeCommand), - "./pvrtex/pvrtex -i %s -o %s -c small -d", filename_tga, filename_pvr); + "../vendor/pvrtex/pvrtex -i %s -o %s -c small -d", filename_tga, filename_pvr); break; case PVRTOOL: snprintf(encodeCommand, sizeof(encodeCommand), @@ -4234,6 +4576,14 @@ rasterToImage(Raster*) return nil; } +static pvr_init_params_t pvr_params = { + .opb_sizes = { + PVR_BINSIZE_16, PVR_BINSIZE_0, PVR_BINSIZE_8, PVR_BINSIZE_0, + PVR_BINSIZE_8 + }, + .autosort_disabled = true +}; + int deviceSystem(DeviceReq req, void *arg0, int32 n) { @@ -4269,15 +4619,22 @@ deviceSystem(DeviceReq req, void *arg0, int32 n) rwmode->flags = VIDEOMODEEXCLUSIVE; return 1; } - - case DEVICEGETMAXMULTISAMPLINGLEVELS: - { - return 1; - } + return 2; case DEVICEGETMULTISAMPLINGLEVELS: - return 1; + return 1 << pvr_params.fsaa_enabled; case DEVICESETMULTISAMPLINGLEVELS: + if (n == 1) { + VIDEO_MODE_SCALE_X = 1; + pvr_params.fsaa_enabled = 0; + pvr_params.vertex_buf_size = (1024 + 1024) * 1024; + pvr_params.opb_overflow_count = 7; // 268800 bytes + } else { + VIDEO_MODE_SCALE_X = 2; + pvr_params.fsaa_enabled = 1; + pvr_params.vertex_buf_size = (1024 + 768) * 1024; + pvr_params.opb_overflow_count = 4; // 307200 bytes + } return 1; case DEVICESETSUBSYSTEM: return 1; @@ -4314,16 +4671,6 @@ Device renderdevice = { deviceSystem }; -static pvr_init_params_t pvr_params = { - .opb_sizes = { - PVR_BINSIZE_16, PVR_BINSIZE_0, PVR_BINSIZE_8, PVR_BINSIZE_0, - PVR_BINSIZE_8 - }, - .vertex_buf_size = (1024 + 1024) * 1024, - .autosort_disabled = true, - .opb_overflow_count = 7 // 268800 bytes -}; - void defaultInstance(ObjPipeline *pipe, Atomic *atomic) { #if defined(DC_TEXCONV) processGeom(atomic->geometry); @@ -4348,6 +4695,34 @@ ObjPipeline* makeDefaultPipeline(void) static void* driverOpen(void *o, int32, int32) { + #if defined(DC_SH4) + OCR_SPACE = (uint8_t*)0x92000000; + + bool has_oix = true; + enter_oix(); + *(volatile uint8_t*)OCR_SPACE = 1; + if (*(volatile uint8_t*)OCR_SPACE != 1) { + has_oix = false; + } + leave_oix(); + + if (!has_oix) { + dbglog(DBG_CRITICAL, "You appear to be using an emulator that does not support OIX. Attempting fallback to OCR\n"); + OCR_SPACE = (uint8_t*)0x7c001000; + enter_oix = (void(*)())(((uintptr_t)&enter_ocr_) - 0x8c000000 + 0xAc000000); + leave_oix = (void(*)())(((uintptr_t)&leave_ocr_) - 0x8c000000 + 0xAc000000); + + for (size_t i = 0; i < ARRAY_SIZE(submitMeshletSelector); i++) { + submitMeshletSelector[i] = submitMeshletSelectorFallback[i]; + } + + for (size_t i = 0; i < ARRAY_SIZE(clipAndsubmitMeshletSelector); i++) { + clipAndsubmitMeshletSelector[i] = clipAndsubmitMeshletSelectorFallback[i]; + } + } + #endif + + pvr_init(&pvr_params); fake_tex = pvr_mem_malloc(sizeof(fake_tex_data)); @@ -4383,6 +4758,8 @@ driverClose(void *o, int32, int32) { pvr_mem_free(fake_tex); + pvr_shutdown(); + return o; } @@ -4442,7 +4819,7 @@ readNativeTexture(Stream *stream) cached->second->refs++; natras->raster = cached->second; stream->seek(pvr_size); - printf("Raster reused for texture %s\n", tex->name); + logf("Raster reused for texture %s\n", tex->name); } else { natras->raster = (DcRaster*)malloc(sizeof(DcRaster)); memset(natras->raster, 0, sizeof(DcRaster)); @@ -4508,7 +4885,7 @@ readNativeTexture(Stream *stream) } } else { stream->seek(pvr_size); - printf("Failed to allocate raster pixels for texture %s\n", tex->name); + errorf("Failed to allocate raster pixels for texture %s\n", tex->name); } } @@ -4563,7 +4940,7 @@ writeNativeTexture(Texture *tex, Stream *stream) } #endif -#define DC_MODEL_VERSION 5 +#define DC_MODEL_VERSION 6 void* destroyNativeData(void *object, int32, int32) @@ -5354,8 +5731,9 @@ void processGeom(Geometry *geo) { } assert(spanCount); + assert(spanCount < 0x80); - skinningIndexData.write(0x8000 | spanCount); // count + clear flag + skinningIndexData.write(0x8000 | (spanCount-1)); // count + clear flag skinningIndexData.write(spanStartIdx * 64); // dst offset assert(spanStartIdx + spanCount <= meshlet.vertices.size()); @@ -5384,7 +5762,8 @@ void processGeom(Geometry *geo) { spanCount++; } if (spanCount) { - skinningIndexData.write(0x8000 | spanCount); // count + clear flag + assert(spanCount <= 0x80); + skinningIndexData.write(0x8000 | (spanCount - 1)); // count + clear flag skinningIndexData.write(spanStartIdx * 64); // dst offset } @@ -5410,7 +5789,7 @@ void processGeom(Geometry *geo) { assert(skinMatrix0Only[currentMtx0Idx++] == (startVtx + k)); } } else if (!(flags & 0x80)) { - int count = flags & 0x7FFF; + int count = (flags & 0x7F) + 1; int dstVertex = skinningIndexData[skinningIndexDataStart] | (skinningIndexData[skinningIndexDataStart + 1] << 8); skinningIndexDataStart += 2; texconvf("%s: Clear: count %d, dst %d\n", currentFile, count, dstVertex/64); diff --git a/vendor/librw/src/dc/rwdc.h b/vendor/librw/src/dc/rwdc.h index 2d18e0c0..449e751c 100644 --- a/vendor/librw/src/dc/rwdc.h +++ b/vendor/librw/src/dc/rwdc.h @@ -46,7 +46,7 @@ struct Im2DVertex void setScreenZ(float32 z) { this->z = z; } // This is a bit unefficient but we have to counteract GL's divide, so multiply void setCameraZ(float32 z) { this->w = z; } - void setRecipCameraZ(float32 recipz) { this->w = 1.0f/recipz; } + void setRecipCameraZ(float32 recipz) { /* don't change w here, as recipz is not always the correct one, see Coronas.cpp */ } void setColor(uint8 r, uint8 g, uint8 b, uint8 a) { this->r = r; this->g = g; this->b = b; this->a = a; } void setU(float32 u, float recipz) { this->u = u; } diff --git a/vendor/librw/src/engine.cpp b/vendor/librw/src/engine.cpp index bf73bafa..c49e4b08 100644 --- a/vendor/librw/src/engine.cpp +++ b/vendor/librw/src/engine.cpp @@ -10,7 +10,7 @@ #include "rwpipeline.h" #include "rwobjects.h" #include "rwengine.h" -// #include "ps2/rwps2.h" +#include "ps2-x/rwps2.h" // #include "d3d/rwxbox.h" // #include "d3d/rwd3d.h" #include "d3d-x/rwd3d8.h" @@ -143,8 +143,7 @@ void *mustmalloc_h(size_t sz, uint32 hint) ret = Engine::memfuncs.rwmalloc(sz, hint); if(ret || sz == 0) return ret; - fprintf(stderr, "Error: out of memory\n"); - exit(1); + assert(ret != 0); return nil; } void *mustrealloc_h(void *p, size_t sz, uint32 hint) @@ -153,8 +152,7 @@ void *mustrealloc_h(void *p, size_t sz, uint32 hint) ret = Engine::memfuncs.rwrealloc(p, sz, hint); if(ret || sz == 0) return ret; - fprintf(stderr, "Error: out of memory\n"); - exit(1); + assert(ret != 0); return nil; } @@ -240,6 +238,7 @@ Engine::init(MemoryFunctions *memfuncs) gl3::registerPlatformPlugins(); #else #if defined(DC_TEXCONV) + ps2::registerPlatformPlugins(); d3d8::registerPlatformPlugins(); #endif dc::registerPlatformPlugins(); diff --git a/src/skel/fcaseopen.h b/vendor/librw/src/fcaseopen.h similarity index 100% rename from src/skel/fcaseopen.h rename to vendor/librw/src/fcaseopen.h diff --git a/src/math/float16.h b/vendor/librw/src/float16.h similarity index 100% rename from src/math/float16.h rename to vendor/librw/src/float16.h diff --git a/vendor/librw/src/rwobjects.h b/vendor/librw/src/rwobjects.h index 88559908..51a97119 100644 --- a/vendor/librw/src/rwobjects.h +++ b/vendor/librw/src/rwobjects.h @@ -744,7 +744,7 @@ struct Camera enum { PERSPECTIVE = 1, PARALLEL }; enum { CLEARIMAGE = 0x1, CLEARZ = 0x2, CLEARSTENCIL = 0x4 }; // return value of frustumTestSphere - enum { SPHEREOUTSIDE, SPHEREBOUNDARY, SPHEREINSIDE }; + enum { SPHEREOUTSIDE, SPHEREBOUNDARY, SPHEREINSIDE, SPHEREBOUNDARY_NEAR /* frustumTestSphereEx only */}; ObjectWithFrame object; void (*beginUpdateCB)(Camera*); @@ -779,6 +779,7 @@ struct Camera /* RW: frustum sectors, space, position */ World *world; ObjectWithFrame::Sync originalSync; + RGBA clearColor; void (*originalBeginUpdate)(Camera*); void (*originalEndUpdate)(Camera*); @@ -801,6 +802,9 @@ struct Camera void setViewOffset(const V2d *offset); void setProjection(int32 proj); int32 frustumTestSphere(const Sphere *s) const; + + //considers SPHEREBOUNDARY as SPHEREINSIDE, returns SPHEREBOUNDARY_NEAR for near plane + int32 frustumTestSphereNear(const Sphere *s) const; static Camera *streamRead(Stream *stream); bool streamWrite(Stream *stream); uint32 streamGetSize(void); diff --git a/vendor/librw/src/texture.cpp b/vendor/librw/src/texture.cpp index aca7e2f5..0b2cd374 100644 --- a/vendor/librw/src/texture.cpp +++ b/vendor/librw/src/texture.cpp @@ -10,7 +10,7 @@ #include "rwpipeline.h" #include "rwobjects.h" #include "rwengine.h" -// #include "ps2/rwps2.h" +#include "ps2-x/rwps2.h" // #include "d3d-x/rwd3d.h" // #include "d3d/rwxbox.h" #include "d3d-x/rwd3d8.h" @@ -473,8 +473,6 @@ Texture::streamReadNative(Stream *stream) uint32 platform = stream->readU32(); stream->seek(-16); #if !defined(RW_DC) - if(platform == FOURCC_PS2) - return ps2::readNativeTexture(stream); if(platform == PLATFORM_D3D8) return d3d8::readNativeTexture(stream); if(platform == PLATFORM_D3D9) @@ -485,6 +483,8 @@ Texture::streamReadNative(Stream *stream) return gl3::readNativeTexture(stream); #else #if defined(DC_TEXCONV) + if(platform == FOURCC_PS2) + return ps2::readNativeTexture(stream); if(platform == PLATFORM_D3D8) return d3d8::readNativeTexture(stream); #endif diff --git a/dreamcast/pvrtex/.gitignore b/vendor/pvrtex/.gitignore similarity index 100% rename from dreamcast/pvrtex/.gitignore rename to vendor/pvrtex/.gitignore diff --git a/dreamcast/pvrtex/Makefile b/vendor/pvrtex/Makefile similarity index 100% rename from dreamcast/pvrtex/Makefile rename to vendor/pvrtex/Makefile diff --git a/dreamcast/pvrtex/README b/vendor/pvrtex/README similarity index 100% rename from dreamcast/pvrtex/README rename to vendor/pvrtex/README diff --git a/dreamcast/pvrtex/README.md b/vendor/pvrtex/README.md similarity index 100% rename from dreamcast/pvrtex/README.md rename to vendor/pvrtex/README.md diff --git a/dreamcast/pvrtex/avstring.c b/vendor/pvrtex/avstring.c similarity index 100% rename from dreamcast/pvrtex/avstring.c rename to vendor/pvrtex/avstring.c diff --git a/dreamcast/pvrtex/bprint.c b/vendor/pvrtex/bprint.c similarity index 100% rename from dreamcast/pvrtex/bprint.c rename to vendor/pvrtex/bprint.c diff --git a/dreamcast/pvrtex/compat/va_copy.h b/vendor/pvrtex/compat/va_copy.h similarity index 100% rename from dreamcast/pvrtex/compat/va_copy.h rename to vendor/pvrtex/compat/va_copy.h diff --git a/dreamcast/pvrtex/config.h b/vendor/pvrtex/config.h similarity index 100% rename from dreamcast/pvrtex/config.h rename to vendor/pvrtex/config.h diff --git a/dreamcast/pvrtex/crc.c b/vendor/pvrtex/crc.c similarity index 100% rename from dreamcast/pvrtex/crc.c rename to vendor/pvrtex/crc.c diff --git a/dreamcast/pvrtex/dither.cpp b/vendor/pvrtex/dither.cpp similarity index 100% rename from dreamcast/pvrtex/dither.cpp rename to vendor/pvrtex/dither.cpp diff --git a/dreamcast/pvrtex/elbg.c b/vendor/pvrtex/elbg.c similarity index 100% rename from dreamcast/pvrtex/elbg.c rename to vendor/pvrtex/elbg.c diff --git a/dreamcast/pvrtex/elbg.h b/vendor/pvrtex/elbg.h similarity index 100% rename from dreamcast/pvrtex/elbg.h rename to vendor/pvrtex/elbg.h diff --git a/dreamcast/pvrtex/file_common.c b/vendor/pvrtex/file_common.c similarity index 100% rename from dreamcast/pvrtex/file_common.c rename to vendor/pvrtex/file_common.c diff --git a/dreamcast/pvrtex/file_common.h b/vendor/pvrtex/file_common.h similarity index 100% rename from dreamcast/pvrtex/file_common.h rename to vendor/pvrtex/file_common.h diff --git a/dreamcast/pvrtex/file_dctex.c b/vendor/pvrtex/file_dctex.c similarity index 100% rename from dreamcast/pvrtex/file_dctex.c rename to vendor/pvrtex/file_dctex.c diff --git a/dreamcast/pvrtex/file_dctex.h b/vendor/pvrtex/file_dctex.h similarity index 100% rename from dreamcast/pvrtex/file_dctex.h rename to vendor/pvrtex/file_dctex.h diff --git a/dreamcast/pvrtex/file_pvr.c b/vendor/pvrtex/file_pvr.c similarity index 100% rename from dreamcast/pvrtex/file_pvr.c rename to vendor/pvrtex/file_pvr.c diff --git a/dreamcast/pvrtex/file_pvr.h b/vendor/pvrtex/file_pvr.h similarity index 100% rename from dreamcast/pvrtex/file_pvr.h rename to vendor/pvrtex/file_pvr.h diff --git a/dreamcast/pvrtex/file_tex.c b/vendor/pvrtex/file_tex.c similarity index 100% rename from dreamcast/pvrtex/file_tex.c rename to vendor/pvrtex/file_tex.c diff --git a/dreamcast/pvrtex/file_tex.h b/vendor/pvrtex/file_tex.h similarity index 100% rename from dreamcast/pvrtex/file_tex.h rename to vendor/pvrtex/file_tex.h diff --git a/dreamcast/pvrtex/lfg.c b/vendor/pvrtex/lfg.c similarity index 100% rename from dreamcast/pvrtex/lfg.c rename to vendor/pvrtex/lfg.c diff --git a/dreamcast/pvrtex/libavcodec/elbg.h b/vendor/pvrtex/libavcodec/elbg.h similarity index 100% rename from dreamcast/pvrtex/libavcodec/elbg.h rename to vendor/pvrtex/libavcodec/elbg.h diff --git a/dreamcast/pvrtex/libavutil/attributes.h b/vendor/pvrtex/libavutil/attributes.h similarity index 100% rename from dreamcast/pvrtex/libavutil/attributes.h rename to vendor/pvrtex/libavutil/attributes.h diff --git a/dreamcast/pvrtex/libavutil/attributes_internal.h b/vendor/pvrtex/libavutil/attributes_internal.h similarity index 100% rename from dreamcast/pvrtex/libavutil/attributes_internal.h rename to vendor/pvrtex/libavutil/attributes_internal.h diff --git a/dreamcast/pvrtex/libavutil/avassert.h b/vendor/pvrtex/libavutil/avassert.h similarity index 100% rename from dreamcast/pvrtex/libavutil/avassert.h rename to vendor/pvrtex/libavutil/avassert.h diff --git a/dreamcast/pvrtex/libavutil/avconfig.h b/vendor/pvrtex/libavutil/avconfig.h similarity index 100% rename from dreamcast/pvrtex/libavutil/avconfig.h rename to vendor/pvrtex/libavutil/avconfig.h diff --git a/dreamcast/pvrtex/libavutil/avstring.h b/vendor/pvrtex/libavutil/avstring.h similarity index 100% rename from dreamcast/pvrtex/libavutil/avstring.h rename to vendor/pvrtex/libavutil/avstring.h diff --git a/dreamcast/pvrtex/libavutil/avutil.h b/vendor/pvrtex/libavutil/avutil.h similarity index 100% rename from dreamcast/pvrtex/libavutil/avutil.h rename to vendor/pvrtex/libavutil/avutil.h diff --git a/dreamcast/pvrtex/libavutil/bprint.h b/vendor/pvrtex/libavutil/bprint.h similarity index 100% rename from dreamcast/pvrtex/libavutil/bprint.h rename to vendor/pvrtex/libavutil/bprint.h diff --git a/dreamcast/pvrtex/libavutil/bswap.h b/vendor/pvrtex/libavutil/bswap.h similarity index 100% rename from dreamcast/pvrtex/libavutil/bswap.h rename to vendor/pvrtex/libavutil/bswap.h diff --git a/dreamcast/pvrtex/libavutil/common.h b/vendor/pvrtex/libavutil/common.h similarity index 100% rename from dreamcast/pvrtex/libavutil/common.h rename to vendor/pvrtex/libavutil/common.h diff --git a/dreamcast/pvrtex/libavutil/crc.h b/vendor/pvrtex/libavutil/crc.h similarity index 100% rename from dreamcast/pvrtex/libavutil/crc.h rename to vendor/pvrtex/libavutil/crc.h diff --git a/dreamcast/pvrtex/libavutil/dynarray.h b/vendor/pvrtex/libavutil/dynarray.h similarity index 100% rename from dreamcast/pvrtex/libavutil/dynarray.h rename to vendor/pvrtex/libavutil/dynarray.h diff --git a/dreamcast/pvrtex/libavutil/error.h b/vendor/pvrtex/libavutil/error.h similarity index 100% rename from dreamcast/pvrtex/libavutil/error.h rename to vendor/pvrtex/libavutil/error.h diff --git a/dreamcast/pvrtex/libavutil/internal.h b/vendor/pvrtex/libavutil/internal.h similarity index 100% rename from dreamcast/pvrtex/libavutil/internal.h rename to vendor/pvrtex/libavutil/internal.h diff --git a/dreamcast/pvrtex/libavutil/intfloat.h b/vendor/pvrtex/libavutil/intfloat.h similarity index 100% rename from dreamcast/pvrtex/libavutil/intfloat.h rename to vendor/pvrtex/libavutil/intfloat.h diff --git a/dreamcast/pvrtex/libavutil/intreadwrite.h b/vendor/pvrtex/libavutil/intreadwrite.h similarity index 100% rename from dreamcast/pvrtex/libavutil/intreadwrite.h rename to vendor/pvrtex/libavutil/intreadwrite.h diff --git a/dreamcast/pvrtex/libavutil/lfg.c b/vendor/pvrtex/libavutil/lfg.c similarity index 100% rename from dreamcast/pvrtex/libavutil/lfg.c rename to vendor/pvrtex/libavutil/lfg.c diff --git a/dreamcast/pvrtex/libavutil/lfg.h b/vendor/pvrtex/libavutil/lfg.h similarity index 100% rename from dreamcast/pvrtex/libavutil/lfg.h rename to vendor/pvrtex/libavutil/lfg.h diff --git a/dreamcast/pvrtex/libavutil/libm.h b/vendor/pvrtex/libavutil/libm.h similarity index 100% rename from dreamcast/pvrtex/libavutil/libm.h rename to vendor/pvrtex/libavutil/libm.h diff --git a/dreamcast/pvrtex/libavutil/log.h b/vendor/pvrtex/libavutil/log.h similarity index 100% rename from dreamcast/pvrtex/libavutil/log.h rename to vendor/pvrtex/libavutil/log.h diff --git a/dreamcast/pvrtex/libavutil/macros.h b/vendor/pvrtex/libavutil/macros.h similarity index 100% rename from dreamcast/pvrtex/libavutil/macros.h rename to vendor/pvrtex/libavutil/macros.h diff --git a/dreamcast/pvrtex/libavutil/mathematics.c b/vendor/pvrtex/libavutil/mathematics.c similarity index 100% rename from dreamcast/pvrtex/libavutil/mathematics.c rename to vendor/pvrtex/libavutil/mathematics.c diff --git a/dreamcast/pvrtex/libavutil/mathematics.h b/vendor/pvrtex/libavutil/mathematics.h similarity index 100% rename from dreamcast/pvrtex/libavutil/mathematics.h rename to vendor/pvrtex/libavutil/mathematics.h diff --git a/dreamcast/pvrtex/libavutil/md5.h b/vendor/pvrtex/libavutil/md5.h similarity index 100% rename from dreamcast/pvrtex/libavutil/md5.h rename to vendor/pvrtex/libavutil/md5.h diff --git a/dreamcast/pvrtex/libavutil/mem.c b/vendor/pvrtex/libavutil/mem.c similarity index 100% rename from dreamcast/pvrtex/libavutil/mem.c rename to vendor/pvrtex/libavutil/mem.c diff --git a/dreamcast/pvrtex/libavutil/mem.h b/vendor/pvrtex/libavutil/mem.h similarity index 100% rename from dreamcast/pvrtex/libavutil/mem.h rename to vendor/pvrtex/libavutil/mem.h diff --git a/dreamcast/pvrtex/libavutil/pixfmt.h b/vendor/pvrtex/libavutil/pixfmt.h similarity index 100% rename from dreamcast/pvrtex/libavutil/pixfmt.h rename to vendor/pvrtex/libavutil/pixfmt.h diff --git a/dreamcast/pvrtex/libavutil/rational.c b/vendor/pvrtex/libavutil/rational.c similarity index 100% rename from dreamcast/pvrtex/libavutil/rational.c rename to vendor/pvrtex/libavutil/rational.c diff --git a/dreamcast/pvrtex/libavutil/rational.h b/vendor/pvrtex/libavutil/rational.h similarity index 100% rename from dreamcast/pvrtex/libavutil/rational.h rename to vendor/pvrtex/libavutil/rational.h diff --git a/dreamcast/pvrtex/libavutil/thread.h b/vendor/pvrtex/libavutil/thread.h similarity index 100% rename from dreamcast/pvrtex/libavutil/thread.h rename to vendor/pvrtex/libavutil/thread.h diff --git a/dreamcast/pvrtex/libavutil/version.h b/vendor/pvrtex/libavutil/version.h similarity index 100% rename from dreamcast/pvrtex/libavutil/version.h rename to vendor/pvrtex/libavutil/version.h diff --git a/dreamcast/pvrtex/log.c b/vendor/pvrtex/log.c similarity index 100% rename from dreamcast/pvrtex/log.c rename to vendor/pvrtex/log.c diff --git a/dreamcast/pvrtex/main.c b/vendor/pvrtex/main.c similarity index 100% rename from dreamcast/pvrtex/main.c rename to vendor/pvrtex/main.c diff --git a/dreamcast/pvrtex/md5.c b/vendor/pvrtex/md5.c similarity index 100% rename from dreamcast/pvrtex/md5.c rename to vendor/pvrtex/md5.c diff --git a/dreamcast/pvrtex/mem.c b/vendor/pvrtex/mem.c similarity index 100% rename from dreamcast/pvrtex/mem.c rename to vendor/pvrtex/mem.c diff --git a/dreamcast/pvrtex/mycommon.c b/vendor/pvrtex/mycommon.c similarity index 100% rename from dreamcast/pvrtex/mycommon.c rename to vendor/pvrtex/mycommon.c diff --git a/dreamcast/pvrtex/mycommon.h b/vendor/pvrtex/mycommon.h similarity index 100% rename from dreamcast/pvrtex/mycommon.h rename to vendor/pvrtex/mycommon.h diff --git a/dreamcast/pvrtex/nvmath.h b/vendor/pvrtex/nvmath.h similarity index 100% rename from dreamcast/pvrtex/nvmath.h rename to vendor/pvrtex/nvmath.h diff --git a/dreamcast/pvrtex/optparse.h b/vendor/pvrtex/optparse.h similarity index 100% rename from dreamcast/pvrtex/optparse.h rename to vendor/pvrtex/optparse.h diff --git a/dreamcast/pvrtex/optparse_impl.c b/vendor/pvrtex/optparse_impl.c similarity index 100% rename from dreamcast/pvrtex/optparse_impl.c rename to vendor/pvrtex/optparse_impl.c diff --git a/dreamcast/pvrtex/pixel.h b/vendor/pvrtex/pixel.h similarity index 100% rename from dreamcast/pvrtex/pixel.h rename to vendor/pvrtex/pixel.h diff --git a/dreamcast/pvrtex/pvr_texture.c b/vendor/pvrtex/pvr_texture.c similarity index 100% rename from dreamcast/pvrtex/pvr_texture.c rename to vendor/pvrtex/pvr_texture.c diff --git a/dreamcast/pvrtex/pvr_texture.h b/vendor/pvrtex/pvr_texture.h similarity index 100% rename from dreamcast/pvrtex/pvr_texture.h rename to vendor/pvrtex/pvr_texture.h diff --git a/dreamcast/pvrtex/pvr_texture_encoder.c b/vendor/pvrtex/pvr_texture_encoder.c similarity index 100% rename from dreamcast/pvrtex/pvr_texture_encoder.c rename to vendor/pvrtex/pvr_texture_encoder.c diff --git a/dreamcast/pvrtex/pvr_texture_encoder.h b/vendor/pvrtex/pvr_texture_encoder.h similarity index 100% rename from dreamcast/pvrtex/pvr_texture_encoder.h rename to vendor/pvrtex/pvr_texture_encoder.h diff --git a/dreamcast/pvrtex/readme_unformatted.txt b/vendor/pvrtex/readme_unformatted.txt similarity index 100% rename from dreamcast/pvrtex/readme_unformatted.txt rename to vendor/pvrtex/readme_unformatted.txt diff --git a/dreamcast/pvrtex/stb_image.h b/vendor/pvrtex/stb_image.h similarity index 100% rename from dreamcast/pvrtex/stb_image.h rename to vendor/pvrtex/stb_image.h diff --git a/dreamcast/pvrtex/stb_image_impl.c b/vendor/pvrtex/stb_image_impl.c similarity index 100% rename from dreamcast/pvrtex/stb_image_impl.c rename to vendor/pvrtex/stb_image_impl.c diff --git a/dreamcast/pvrtex/stb_image_resize.h b/vendor/pvrtex/stb_image_resize.h similarity index 100% rename from dreamcast/pvrtex/stb_image_resize.h rename to vendor/pvrtex/stb_image_resize.h diff --git a/dreamcast/pvrtex/stb_image_resize_impl.c b/vendor/pvrtex/stb_image_resize_impl.c similarity index 100% rename from dreamcast/pvrtex/stb_image_resize_impl.c rename to vendor/pvrtex/stb_image_resize_impl.c diff --git a/dreamcast/pvrtex/stb_image_write.h b/vendor/pvrtex/stb_image_write.h similarity index 100% rename from dreamcast/pvrtex/stb_image_write.h rename to vendor/pvrtex/stb_image_write.h diff --git a/dreamcast/pvrtex/stb_image_write_impl.c b/vendor/pvrtex/stb_image_write_impl.c similarity index 100% rename from dreamcast/pvrtex/stb_image_write_impl.c rename to vendor/pvrtex/stb_image_write_impl.c diff --git a/dreamcast/pvrtex/tddither.c b/vendor/pvrtex/tddither.c similarity index 100% rename from dreamcast/pvrtex/tddither.c rename to vendor/pvrtex/tddither.c diff --git a/dreamcast/pvrtex/tddither.h b/vendor/pvrtex/tddither.h similarity index 100% rename from dreamcast/pvrtex/tddither.h rename to vendor/pvrtex/tddither.h diff --git a/dreamcast/pvrtex/vqcompress.c b/vendor/pvrtex/vqcompress.c similarity index 100% rename from dreamcast/pvrtex/vqcompress.c rename to vendor/pvrtex/vqcompress.c diff --git a/dreamcast/pvrtex/vqcompress.h b/vendor/pvrtex/vqcompress.h similarity index 100% rename from dreamcast/pvrtex/vqcompress.h rename to vendor/pvrtex/vqcompress.h

    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 z zB0C2*#(-sbwjFf7+)EpLR(L>yKmy$g)Hz@1Fb3v`PXV}*cjy#2pMW^BT??ovLQaSF=*`FHThUCs zwG$TVSe!8tGEc|@b_UW!trS|j>GgSjc?QIJKH&3CK?i+-4$=z^2A!Sf(X9{e22BNB z<*^@phBiO=zHGn zCqV|#4Uz9(_?v-z2tP1;LOATeY&OshF5R#Lvpp{V``>b)Gz8QFSTh&Ywm>>C8+sm- zM>p1U#alc;qc+F~Y(v$<&e9>}cfkv;F=}tATZy4ZH=jrA$r4QuYs*r>*IG#NWocO=?a}Qi;L&=r^b^<@;PWCr zgN{T5g`CrR{&gFnADG~IrmMi^acbk7H!-tN;|qauKEWVwdnffoPi(fxNd{QrVs7XxVFU`dt7@uUBt?(*nn@aT3B@aUHC zXuVx3^x7Yq{ye&)1wa>j+xv7IJ2upLFqFRa?4AZ%uHf0-2im#=zJL1pi#_rG|9e=z zs5=kR;M@7#qw^@ppp&rkk3rYOa=d8U3AVX(BIuxT&@H??;4}DrI)9Y%qg}%LLhJ00 z{|Ns#?4NH3ihS^W48aZi=Ye=7nLgdqL1(IVPmchtGzA~@>e2c8So?0!C0wBEdL{VV zCNeNEH2+}ZZvmZm)ck{;zjZmNLDRh(bODNE=Le7O&7iAIJUef8Ui9g_^nx*kk>OY~ z$OMLH`1#EZ`)7k}EY)xR$H?C@4|Jn?_jJ&Wj}80hfFw%lx~I1=GBALLU-{)3z$qSl zt`DP6_jJ(dgQZvbw@>c?DGB-iA9RIqsj>&e4W6AhK({4cd%+%yWHa>K?rv*Pm+UjB zD0)$D{2y|YC)|1P`x?5ZH-L=(2|NGDr+Yf+ei6_a>fn6m(aqkl1GIINp(F$vmOkD7 z0Uo{n6+YeVpbKM5WIVd33xIlWy{6kffjV`^4G+8wF@&T7% zK;_mkHpbUYpnGw5?*ti9y5(gdO8Mg0dE_N%Z4)d%Ht(L{$iTqF-wL`1lhO1+4&_HR7ZFI z2Hz(EDkY!EfGSe3m%cgF2=KRxg3?>_UeLx|2FGJ8Dh$V(H-X$=VhcLw*B*3I+o=Pf zHNaazN<4aP8(xEQg2(Y@*zH0uChhq5A2Lu565?+GonPm1ycKk`GRRRL$6GT%=bD37 z*&dJj|G#-RXw&w8k52GPERW8~phAql1#~*nKG51}kTdON{{PVp?1$tl-G<%ZGRmjhJHVsYyTYft8Jw?5Jh~@C^Htn? zkn3Ok*Zu$hg;f-Ifgz|Ug}MTAxdx>Eiw0X^cmQ$`z5@gJJmJ^(;rShO9|*)f-~or; zbOwgj10}0o?}er_SPBwv*bOeYO5{AcCxi0q%TiFfnBdVp8I(_7ro+qs$)J4tG91be z4fg2GuK*Ue$Bf%9bT;1r$#*w{jx9dc{1Vi^0iF2>vVp@BWW#;KZ!h-A!dOp0DXbaN?gG`*kY#!x zT`xfcJn-;?h=bx7eB}59Q2pxJ`5)Bm0JS+Tdo;cQRVg0b^$Fm+k4;oSEBXaM7dCKs z9B)wpwK+iUM5=vTR6tb`=$z>m6___bN*93CF)%P3Yf!?b84WC>&#E0N)?W#L2+0?|>r%!+wzK|BJ5L3+_pPM$lUFctGcT&O}$6z3Og1*KY#PS*gB&Q?%T>}&;95-&F7Gcdf^9S#bzR#4~7 zv%4L1hNeg7RM63jj@{EiE9QJUYZ;EWf=*5c=Ze+?ol`-jREb)H1w)Bq^Dm}jt#d(@ zEV$iPE_tjKRAMn29(XMZt`8ux42B0F5)2)!puFMP-3~ey1XMT+gUsQBC}TVZmSuG0 z-_{N~I>57eKloT#@ODg4NOiP=3PzALXhxiWYAfivbI<1epxqY?9j$BZ!1pG%gBBa{ zPi+O=z}eBd7AgqZV$#v71m%Icq8+UYP#)-u14Bn^5|jtp4cO6|0Of&t z@g1#+cA(1|+Ce9wdUUpa09}t2d>qv1FVXU7-kSkxj+QEbUA-T)i{iiN*WJ)aYXwR2 zx0r%bd9N*B)&Ku5K=*@s^x862fg=Lc9(@re%?N6ld33gd8?%sBDt`+bNLTBB{+7R- zAp7?>fQGYM|CfmU7o7vQ0Cag5f6En41_sCOcF;kMp56OFiK?@8iX$j^wzq>;$fk9+ zPH+Sn(hizn^XPQ7@azT;5qorkvMP&;N2kV%`oo|t6Nf#!_k-?r_%G@XvZV1h=n(Q9 zAj0!_J4gbQm_7KNZg?JVhh6&M(R`Q*)Q1w40cmMH;MsZ1vlFbCzeNS)_)f4=u#%s< zz+nTr>87(4bizM>%RTU>$yQLi%A?nI{UsrWW1uQvJ4j`(?fgqZ3?7}3y!T&p5k#;H zDhSGP|3!O2g3Ws%+0(Q0C`dUd|M5>b;L*GXk}dhC9OAqHx~XT%A&v_jE-D)Qt#zOP z?S*7ppWZcK?H;{T!1>juw?_qJgh%H)pU(fDodGJ-T;*Lh41`p&y_+tAzO&C@S`Yj!ObH zNF@J@p4o{MD4^R4_*>qvGcYvl1*Lcf{#F-G1_s~m?V$V$x?M-qr}O)N(dlqa{4GvA zAO*`o+03`~Nr|XW=kx!fWiSOt_*=|D=W6%bwmlJKc+s{T+#Uyc$AZ750Te;q+d;=7 zfbPHm6|t(5KzBg#x2)v^^&Y9L)gD{rua(#X}lsKnz@L@VA@>?Vsv~1rlS4K*L^83Bpjqehi%189jPiRQkaU z^}UevVCwM#)G7sETzkmyz>A$nfBbL$!RXPw6(nEE*RThyK9;|w4U}Jcr>KAu)qc>i z@Gp9g{P^E`fWLn$D1mi@4cuo1@?grrAOAt!r0#am30t70V=aOl3=ED9`whIo{S9W& zMbX<2__jWQl%D)8t)MgbL2VVNi}_p3L8cyWQ31IFlqrt4sDLUj5Ywl330Syc?-o$) zSz_zhupe|555rDJ1_p*w0mp{@pk1+yk|&!VvNj)dfvNZC6%~Cf$gqzGbbi1A(3$sM zJ^%hYHtcTz)wdqKrjyPKF?jT{rrc!*g*_-$xpen}ig%x0)`cHHvtGR*=Xmsr#yt{b z@Zfhj*06UG$O)xvo}C{&IzM@IZv~}L&`647NVrF@?Yc*T3;~7*Ji5(Z*scHf-=q16 zf(JMU^S5NOf`bISv$2F1TCn^V)!%N)&<(Crd^^DvnB%c_kQ&Cc39Sbz1wA0q%fH>B z01|ev61zm-qZ^V(|BJrf22MPnlKj8uxowEl56S2JEpJ&E7(AQ7`|TJY8X;MI6GS6u zN!owWsVEvj3iw;LKs18aJwY^rvhjb>N{B{~_p@}G zdVs(65a?v4?x~;()~9<7xJdBnUIH#sV7>v}BeKnu;hR&90)K0f2m=FjV;cCL8~&E1 zETByE{J-e)t)>j18-74@NX>h}&1e3WS)emuKtq@lSwMkZ%fL_~;{zJo+zC<*jtGy= zR?yvb{4F<`LFGm}=)@v$l0DAFz~I?=v@=GrM|ql*cCGxvmLG{-CP8St}|Dvi}O&PjfR18`Vl!*E=et|mRJ@^s=aR2jIE4UeI z`2WA?&n>2q(H`IKT#jR{;O;Z%#x9PQ1N^PFpxM$G6^EB!I6((pF~EY_)$qw~&}Iv8 zh-}{i>JmVT^Z%kNp*&EN?7!&jEzkmL5}5J*zi8(cQ-;>trLi8}EGiz|7O)Yx<_Zah zQX!A-c5qb-;_toq~Ggm z_{7!l8~FaHmlB|{z-|{6fd&f({#M8kZ;1-WixryFsZ0bnRW|;nzK$-P=J! zL7<)rsEGl(I}>ziB&dlc0IC4EJ*{0-IQZMHf=-%ku2E3{6&D~KCHx?tDtK7C8}PSH z1j%;3^Xz>4;*mWlmA-R4=IX)d)A|3!r_Z3v_O^pg81`uW=GnP@f)4`&f2*P>I9q~N zt}*bpSc`(v?EWTD;Z!2)*}cER2UKzMZ3bm-unv#bZ~U#lL_q0wKSqOfSc-|s)xamf7{d@j-c>(4!Wz|;>Fi#psF2mO|nO)D}zU~Jp+G>mk8+4 zHg^F=&(7KiNPHrtTUWzxFI~YW9lL;B3~f_=-e}5z)Q&=GLm_E}qz4Z06eD=V((oH3 zp}e>w1iH%-Jmv{aHZM*>gg~jl<9I8mJqb!-9>>9LNf7hDXx&Co#@P#MD*hKO1aUwG zOvXl2cs_eM8#McQyp@3oqyv-%A>%Dbj)JGt0`Mi~5P4`t53lyY%@s&Z&hXL@eEKQ4 z9PsD{cTqgMr-Rz29=*2DZiDJ71H%I^PObo#I-Z@!_*;Jb1CN_3I5zyTDp7u+cksu5 zpUzL<`{+14nh!BLb@MFj@B$63DuI-gzHn^V52|?>kZPxi8%!C{>ZO;d-$27zpz&vL zYwm?;H>e56-?H)>XlE~^R0rJ_2+E8vCxDMRhWHPZm4bbHOH>#^H;aM>7r?in`*ePP z@p1Q${~osg|AP*~<>X~x@Uh+kzDAqB#UE6XgIWq6oyT9C0v(RO{Q#sUe0c{PBo5H( z^W|AE7v!#9+po6-8D6}Z3z}kfQL%WL^$k2e-)mZUUy#A4^S|K%ND=@Shc8m(5e|QO zAAILIIGjD3_k-GT4E!y(K@GyzZ>43vtxrk|q2Ui}X83kK^XN9<-zL^_sYC-bj>Nz3 zkVo?|#^x6dwxHeM{B0{i*Ag`!V|45Y`wv!EdS_QfB?E)waklt^qI}0=Zfp!`j-6pF zpf-vb$kpJgioazeFL;z5H1KQjUzBA%s3d^55nE0RF@U;(`d@U$I#ZC@{4M36 zb7gu#-BzE@PmuQe3+KZB{~bGzfUV76X9^nI=Wo3TI>xGbFX&=s29NGm@Od#UYeCgB zq{9E^RKZ*--mn+c3uP$f^XTP`i2zmMy`a{BM=P|G%gEny4|EF`q%-@H9kiFIw~p~x zE2!PYxGxq|>UVFEsWd@3=I{GR>v3^e0mcY`CE#{r}$)qT=JxS)$?qy1w|u4^WlOzYin^8r?5Zap-25 z;Mm~?iAVlcfq(!1ztsPSNI<(mhXK8u3E80@dIOr=UNFdljzR+Mk?u9MhlzsKzIZBw z(ET#u$N&GH%|{qP@nPlJTcg5Q5(92!gOWz~RpibXE&?Er3E#zwWR3e@f&|j!QcP?T{=I*OoC+imki%QNufrC zk%7N857f^G6)lkV_sgCy;NAQ1Vi=rKOM*O__kw0Q82DS$z?DcV_>Q^my`TvXkKP<8e;Mn}{KYvRqxJPDs9zC`xrbx zqm-bsox!8Eb%q@S17=6nr#qDabTt}ic>KSp^eRwMwnqijss~*;1`-8@;eSy^kWB9$ z6;S!?()qyixQhz-GDBol;G0Q*^mc$og$)ltE+z%ZgO<#JlI3ymKr3iK5L%FXbO&&N z4l@%0Ef%~4>Zh23j^Bl(I*;bPpkY6TN^XyChXUvzphqujy&ULl577z*P(IoZI(XZo zvlVppfJbL5XvZUHpsfKMn^QqYY`@U0O5hQ z5qNgCM?iR>)Av0)+kZfKpq00to$VPQUS~V#;y%yrcF-ZtpdliNpa3hViPR3-u;ba; zUI7y9ZU;JTIk`~*=_)l1FgvS>};O_k!t|Cth*m{M4)H) zbkLdGo}KL$5S5_CmY$vc2f!-(L8p~_c23^`;&o0>08{M|AeEi%pz8=ddRrwxe(&s0 z010;YgH~93fG&P^Q3>$u>^}k0(cKT)9P8QHp8-(=x+BQ5v;P7_4zzgHv$MYdA_qDj z)3dYx21E|D)XlTAzk-#4;l*Kb(2+6xE#06KZhKoJK-Tu|2VIu|wJ+nv0diJPjQlO(P$m<9%NDTRE-C_`Ic?D4w=OCX{4L+X<4rCq2FF}f92h{?Gbezu zWoHbi#&l6Bc%AClQ0u|K-}(R?L*Q2GH-{>gQfbHTy`XXQ=2QQ{H=GOcL7I%9lbSud zLqXTO-@gK`Dt$X^84M4+P!#$9A3O%^)A{~|3uwn~bB#&_1Aog>K~U$xMJ2)qw0g)v zz_Ys--1qEsQHk*E2DjLKx)UXQm=h%UTR>MXfc$z0)CU2@O3S4ZF2{zwpqODOUE|oW zcPpryQ|j!|ZQ#+(2)9zJU|0^O;|zYW|1vjCI)t)TTF9*|}d z)a-7j8e_+r6^teF-IWzDT(|xBZ|zjU-}?0*sQTIqDnuFhTb_e>uyH|4@bDmi>!p97 zi)+D!H-GC(P)gsNz$Aa`T(Bl^jlkbJg^z*3wdGqWH#9aI zK({z_Zw2*uz(I5nRIKd+73iSi)ME#X<*7UsG%oI;3?3f$usp=y>iQ4V5(9UZz(HdA z@BjZ7CF>y}p#e5`E~x$2?Od~>q0>dBrqs-{dn>5i_vi)>>b!7=D*pW!WYShppUR_q zFNk`f1C@I47vy7z<6AoU7#O~}sMIj>x7P82syT2i&ff|;1RdPqjpqYp-hYhzt)YC7 zv;>;y=5GO=NZ@ICgulrj z_>-5mUqD6Cv6tqcjqJUq1?NBx`EPjO#cm-)OA*qZ0e4_tYQF=84WhmK`V%OXz(!y1 ze)|8PAJ*P|+5HM+$y?A^5V)oK9(;BUxJ3==o|jdFhpSryU;h99k{xsuF>JugH2V_F zC5;0A|GzW>?G)@a6+Z_WQl1MsyxXI5E~w0bG+r1zI&VQjFBqiS9MqEQHQjv{X2x4k znCgP&O1A|tdh|vxwp;?Wz4==%Gcho{=->3?KmRsxyW7CC^B!n;-GG1Fp>Hm3i~^-1 z4Zp1TryOkfrN=+z0LMZ8md~&r7NmQ1&A0QTN3UtsX{cR3ojxiCFV66Tg3m|Az^C&( zxGDR>Y8AK*%in7K_5XiRtM+B!S5R64&3-ZPw>E-~JnFTTIV*@Z3Mmgd61Ufu7bGDX zyAafk0r&P_7D9B?p8;9??8PnvNI3_df9AOAhNr-1v#pymo|gB3%mDx}w3F6-EN?B$d%AP+)%;P4R! zK1eLRzW{R5C&L4vD7pRO03WpJxqg8u10=o}Km&^l7J%CRxA~_Wc)9R3r~&=h)$j?T z4gKxq{@4HizqqvG2dEO^Z(aNg6mHpzTx8yt_y5RjZr+_rR19dM! zgMm+f{Qv**&`+?f|3x3qGi7+W7d+Mo4)n~WJfOv73Le%T2K;TeL8l+~n)ZPc3@F^D@PN7$(AvC~k-z24D{%P&T80Xq zYuW*t#CB}h54!0MEU+CU&`_hI!NA`#{}rgAY#MM1>K16h1scD7VFfa|^ZkoM#ULA4 zRA33s12VAtGUz4PBG6za1ApsFut~OxV3S_loBHoRbm;RX!|VV5q2rtkFNI(K{|}wV zf^;TJ6g_%P+accZ>3n2(U^i$y`9&@-$Wq8SI->xm#7GM z^!9)=uV?2}P?rMKw2=(`2bzE5Zvmaj)%@chXch$2gXeEK3>xVGrIa_XKv5T>BH__% zs(liaiT}fVuo@J6-4GwVJOYlCz2J)9w|6P1r3iKtXdnh`y<<1nT9_+9*3Sw7TVIll zWOa!TsHSc$MY8t%37EB@#U3wAkgfFvMM}4eiU2I`An|*Qf#Kzp=l}ojmSJFEc&PzX z1oPi92F91dAR8b<5-;!k`~M%jVk6@%D8}s=O8LN}OM-Ju8D55ft0-vts}U%D3K~eP z1&xp}@VEHA{r|rkZG38R94JG4?{oppte%?s8{<+H=@|Nrki{89&WHC*#vP-BIG zzoimXF%i|AY4Q91|NqNEEF)ru$Tr3x+4$f3wV2kdsqX3Vb?qR1#kDg2%l~i(q9}cZ`b93m#5z$pG$Oz?!0Q02>Jw_e6|{dv=0FJi5WPre`OZ@;D9_1Znp`DCOTa6*La+(ap*_ z%aozzl4A#$&))(nB0RhIf(B#$U-0Y(kI{G{hGYJVUY!XVMc4}(hw(fP9-9Ff{a_r)(QP<;Vzh&vu@1QCWyMC<2gz>%S-~L>$}=L4@6Z(YMpV?g6EF!*3qPTR{py!SmwJ z53uV&{X@fVFWy0T_@;IiLrem9;Esb=8-dLEFWLIzhxx@Xd12+G)(t;K4@|f-0BgXQ3NVC{=G0z z2Mrd0t8$;t1P7na43C$Q??H0^U$B7oH@6-rDfQ?)?$c>u`0d4~v!FUG}Bo z{}&$(Koij+Dh8h2Au0l{hTlA!Py7e9xix&cm0k<@cI&8sJA)wX0)6Vc4XclMo+5i7v-hTA|zi)RdXz=mnXHXBX*Yq*0{&?ZZ^8f$Kcc22dy98v} zRL~%uXLm1X0V!yfL&K-jMaAL8CQv)7+eO8}$MQg3uP^&)%(|TFs*uT!6g@ zT?s1h_*)*n2N#Rr6)zTGlE3vV=+>OQpjith{#FA}KMFFM_syY(k-vo*)WQLkZl1kc zL4!jtgjgXB)@dN4n)muBfR^>P{P^(y|I233`KyqLu04m*Q%tGANT@fah+%f+C>*ke?DUIu|1zu;QSqubh}+u+3-6;KPeRM!*YmhOs* z7dgv7ZF=y62yp*i6y$&gSpS~=J*YDRZVL0ab~Au_vygFCVMj=Qg$%Uvx84JH0Kp@@ z&|c{ad$48vEr;Lz|L+JJmIV#}^S5pQpJxen86-TOfVwl?H7Ws~y<0(JbT61#AmPyl z(%ihah6x-Vdq4dD-{GR-@G>7_9jK&$wcal%gZ#$d8ukDG|Cj#nK>d&$l>|_=1zIK6 z>iYBl|ArbB2L}F@O`xt;!~V&jbAtI>KzHkT^qQ_b3QPLxOrTa7)N!E3=7F#O|MPEi zQK<$kGke+k72G@B=A%*#5_I5qIS6*rRB#+@1=V97z2NHXh3Rr|`e^<0?*IRnd%$f7 zNGnR^7|gO=jQ{_?j0e@V$6ZuFw}COdTny?i!FWEsti}5Tp@NWZ@XOV2KzRo|(gYiP z>JF6fU=EM~4K;Cic3yee3$m>B5`W9XPyheFbbJfaP@e!E&w7zQ57Gvh46>)!RNx5M zEv|;&Ug&}x)BfiF|57eTX!w*$di0teg%t}RMI0bSJ`lY%Fo_qR82+(Ql9|c zvHjxzT!=kp5VaQ|YC%RE1u2#X2_r{2KNBR%LGE&dNBP&c|Np;icmta7bzlJRo8f4% z4Vh}nz)<4ZVCw~9lp1;T+J2m3%CHY~eaQ=Teg@E*ss~d*onqF7lS~<2gb4lne~eXj zswo4bMc(y2n_#KsNAe zvVzP{>ty{k#gt($=$c)RUfB;*Oc`G61P$S|9w-s?Xg>uQw9YO&7&TShhN+i z{P*9ZSM>1_(CEZL$fAG4125duK#rL>#gu{ZC20E)(t7;{+o~xbpYeP2vNlXHWjG97 zZvtA!ZQC>j*<}z%G{50U>ts!xV#;ugH36g*e0P zT0mTB3(^ESVEdg%=Y7KiFBYbPT={x3D9DADWaXM@3W~JT6F|C-a6tE6 zcyzOQShAOJfcJ^GFfj0McjHL2bmu622U^|@+foMJ?bTf`@WNLCR0NcA8XkZ~uSa*O zghzL^#0z7vOvy8k?otJh?rMb>N)Umo9^IuD9^KUzF9g8?{8JCSM%rHjvgJ2u?^^R4 z4*qTKoE>hQ{M+0)I@~xsy4gLt>m@uo*+6}jmdy;Hg)&$BK|>b@JUU$^_**~=nLN6y z1;Fx)!SY+-@&Y9#AX~w5)4_5x;c^xw5gy&u24Fc*k^5h?7A|K{V&l&X*>L0b>H>m~k+p6UZNk*Wnejyr>G z{4cr#B4Xij+!<`=f6-+S5d)9o&R|>ri%x-vXm}iV2HX2zvjo)P4UQZ~>bE~Ty>%||3ar3cKNj1YHb{Q!|)kP|^bTOeL)iv0f%-#>y& zJ$Mh%OFI!rt%KN41=?Q%-CWZA#-e#YxOwN(t6Vc>58b%A`lQwu;t3EYm~SX2b~TTMXYHNCArKsRi4 zx~NEacDI5qv;vRHfd*wjyH_+oOG6YqI)A%@mXZAjho^7%Qqb)JzKmZyn`=}g7)o+I zyFq(h-oMx-1u7Ct!k`<7K*KN`B^98Jway;hyFu-z7t10*Z)@+3$XWWOEAvjQI77B99+f&ziR#rHp`W#^(I0P15{cvuSXw}L8Q zNB-?0Dh_Fu0UZ3TS_ma2Djpu)6#_55Lv`~bO9gmzS4h0L1C{y$wiIk|iAsb=cZI@> zgHWlP$WjF!-4zxumO-WVfu%q#!fqyz<2gW02#-!C&rTN=4bY5^f+u(pDClCB5ETv3 zh&w+>M8Ttz$?(8S&}HX_2VPDE2Lfb$8@Q|iRZF04CZIa!h1MT%IT-*ecRiqG=YLU) zPH>6r0lW3>zo;5Sq`>32vj?aY|1T;65y|j4?py#7VS54 zzil$ez1^)K4|p{105v@re0nn~N&>(+@wE`NzV~cCTH)DTE5T4=?%D0D;bZx+md&&C zoKNR#aMH6pUV7J~^FC;jXx4K47roL39{E&o0U6=hnakkYnaa_6z_l~fgui7! zXb2mUn;kn{4LrNS+kQQJK@GAR6%LPXknsX9D!%>y-+9pRK+8$~*8QLy0~&#Hg*GCJ z+e{%FS+d%|4Mz*`4ojp(-H`A_DOXfLhV*U+l`Eh~1s7G_`yu5^i3%caTfqvOk6@Y! z-|z1TDc@h(fSW%K44%ygGhQZxGbFga;%@;JVxHYoK@sEAn^p00&p+h*dcgZ@{{(|J zVS|c%P=`$tRFOdIFwSob0{pG);JEEk0mU}xkolARE%!l-J>(r2JUU-^9DKmyq4~n2 zSLP6?r3-Eug6=(gVE`U&sz&7e=A(>=+;7zi&b3InUjrh7l>5aXB1pNP1tNl!`#-gS z%|Xij55OWwx&J&!06F*XYXPMXh=)9mLrMY9?kOsuK=QCW%ip95Drq~f{}-JCQhB@u zECVWSqJ27_$2!Km-0}^yrrCkP1LP-=4$v^q>oXG!542t?5$K){x*5Is7jubx^Dm|n zE>QCUQa*OKgVum_vVn8O;j{^GJ_7?I!{M|EFSEZfFd&s59?9Q4dPT*4|NHNec?{gX zVf=Bd0o1%p*J1I)=C!J^^)l_k+TQq0|V}7wP=H547S8T&zSlgUgu`HIL5Kp!DI<*$h(Yal9E+ zM1YbuY$H3ko#40!G~*5vN}KRf8`Zx%KxGe-`@rqr>wiJJlA)al&~99Wf13CI@MU0N zC`oq=3H9jw8|=}&pTUoTq0YeYwyWWjv1)<#4fH3^EVo{+Ep)_k+y^$sTV8Nq|@{K@B2k`3velAeB&u zLG=^J55Ap8L4Nq_(Rtk?`JPAf8x4<69~BNzT_f>gd*_e;e!Za5_n2pIi3*bk=v+bTCanvZC7o&d!bVm}SYf1aJ!Kwk9hJOlFLZ;#INApdoi zsPMeF+X31T@ZGcXs845!iolDr9Y6ki_U5oMfL4w7mco20@n3Xu12~<7?>Pne)CaUK ztOPV8Df)RKI|IzaFJ6cI`w#P>Pv^52);&M|J05et-rZS{Z%`gE$i0Q>X;l2767;g{N( z(fq~&X`i`^iU#=f9ncy!aQ~OTr3=(+Xuagwd7Zyy6_|a}v-2$EoD$E@KOVidI-3O< zc7oEiN9Whi%lM}skYNCAw>rzfz_1_GIswlW{})ZI2Zxqt=dlt?!;|36UZ7pCFMPY1 z7(9A?R1|!AT~s7?fpqwEegiEVaOPrQ=sXOb@KmS=ZL}&e0VOBU;VvTJ!(Bkzrno_a z*3CyOkbDkJ*Sn(_7#LokodD9K;L&TEvI#V%_}uWoi(79%#o_xGpW1%>2Q7^UuaG`k z2XkL(EojVL0qX9hb*2oj+d<U;O;2|E%)zy{z9zn$A1rN7Zr&T zbx`)>IOfV=4cf`Y2T=-Y1n%?c&QVc#9S#jIFb^Cr&p?ylr5zy8lpdM@-WNB&7F3eB zK$@H2DCw;=Wq6qbTH@dN{Kbk^sBK_F_*+1`*uWw1`XI>CmmNR;8-hRxth-RR`G*&J+AC3qhit zFL!}z5}(d*9-YU*R=wEV`{RG}3r7Cd4$!o@;-Qx zIIQ?vlEB6s^3i-$!qrgmpNqdW03^~ICFRj;TD?J#!L!#!=Ec1pu=0{JAI%4#5@HhPAE#U&S|2sH5dszfLnh!I2THY(a=g}LY zqTtb6q9Wkac^n)|E-D-^{(*LGg1WMxlz#lbs96mtrI)h%SmvlGlmvl&_Hz4Q+7l>;=kUujbpAlC&oTL+ zrda)9*m*M^;0D6~|NrYf6n`8}o6z|KO}-N(ifA7YG7nQ8)jjC;d0?22YCj?KFx;Db z9I3UF1{tsHwPnpvz%@3rb4^IG%triD+bh~SS1~yv{l!$qB z*DHWd7KV;U9(M*6s-Rx31V}Bv;kOq*ZZR;xd*9&R@4iY<28XnJK_kTfMb|(?!1VxZ ztP|2=hKy*!&Se7CC%&D>Aj#%0s6PD)9*Jwcw}mp4H1lsOW$gTT@EOyAmn_ZCY&@EOGx4`DF)}dl zZ>weUXnxJ?(R`HIaVKcFq4P)cbNd4?*f@{91f7;Q!K3rCN3RIzs6hrq0O0U?D<7yA z(tJb$oCQ(pJO&5;ZT~$$u5W(B;mE(w#g+l$dXHYwYpcP&0s8{vfdIn;FG77ldm?W~ zMDe$pf(!+fL5|1R84UmXbSnH8T~`k7#J)IJ4?4ww4Kz{;IYbEBkV51qk6zLA)q)IQ z!$GSk54`y0{qO(F%ita-sQub}l*#bF<4(}FPs7_U(_!MCoyT8y^2;-T(hqbz#Iy4% zsAKWQ0y-e+aU7(H0W>Tk@xrAZv`zf*4qH&bgJQsqk%8gcVP^hTTTl%0Z(|27UcAM~ zz+hv`-*Sl&bdut0&a??Fmr8^@I{!KHZwoPJ^ysZ)Z1`nc`pWP?!!KL@mNrl_wEWHA zx(viP_?pS5*Cg6g^BAb@rvcgt_Fr^O8MLH72C8}$_*+^*N zKFvjiBW;3bZ$1mS;{R`W(uIGUyko;}TmF`Hpn-*k-;TSS7#JXX)FJ63ZGxl4=$%2s+d|6$~B1LYTwgD(a6 zxBYx^8?@TAgKY;WartOIfV9~>8IO5b=csUacE0Ct%>+kqiHZVvT*UDh8-quu0f?;u zTE7G?q>&p?(D8|b57|I_?q1e_8z&6>+kS#_vcQXM@G4aw6#>T%HzxjVe<7lwNTQ4{ z6To`F0 z{Y?xnL;wB%Z(~~`15N-hJwYS7pgzV+&^7{(&SNhX{(~CM;O*D1cOv(fdu=^e3NpM5 z0q?nWVDRX*y}3e=;iU_Vf4mK}02EXJ9R`;pt^Z53p@Wv7iLw$6|@Rovwpv4W4;|V=Fzm}@Pj>^k{4(^o}dvtFHjqAS_M9Pod+d<23Ji4cYmQ#B4 z+V-pf&%PYV24(mI9-W6vd_b+sFV&#@bkC#ny+`Mv7ip=WUBUM~I**4&1D&~+YhpiI}j4eVJSaJYMPZ-e-A zGSr_-mV^CyCkyOP@V+6?`Z17SIlz7ec@)$Et4aa+^(eU6f2;_UyWjG+fOAxLt$;`8 zEsySUju)wy7(nNqWqEeHT6lD>1|<=XW>*eIkKO=A&(2T_k51Sr-Ps?rzD7s1P^Jvv4f?k;=gEnj7kFYmE|VPy=%^D9N@SD1GN9h{2jZ>*%0NfMmiWWYeccVd zy~>rtqxl6R_;^8A4v*u_AhSVbP>BJkm}m2B&QUR71eMXCl*Iz>@0JFDPR;?PDo~5m zquZ6kqxC@P1(5n{9`LY=Nr8?-cHVzc07~ht2TC@2SZkEN^FRujZwU+xucy9{uYmdz zQGPgvfpRan6jH$S?EjK5&(3EaonJjWulRI+d+{zAG}7|@1zQRzvX6pJTQT+Qj%5HX zeEXCP7W3$K2A%#250BQ{B{x88zd$>Lq6HlPUupiqSo#vptGqC;g4!F0UzC@F+zGBv zKqoXScyvDY=)8ySycY?{3=FSDq3NU>dhRS}13{@KD8(cugL6#j6HuNF0q0rBNJ1&- zj93LoyVj$d;q}88Y~?>dQ?f1$pgxSHT?r?smq)=q}@Uv5MpWe-F*W9<8@MI&YN3gElFE21R%vi6jCt zn}g^dbNDIH;jn{3U@(6r*FCWMM{|+s0K}{Rz%oC`G3aYa?N*ck{ zxCi9Szt=Nfd@B9%A2j~R|Cv9(y$jU16>O~V1yyxm3M4uUBH95G?e1R%Ds?(N6uSF2 zF)}c8dT8)(pT3O|bf4x7*JWsFW9&Nq@fo~fsDBTG3EeBw7dTS+?W?|W8T1x znGV`12cD$k>JF;tobUmxX*wv1JUTmGfOwq~R)DDP=^wx*O?cqT09wWb8E=8rSE0ck zjc-89agk!MMMVPCjy%x3AJofbf^0}=*biFr%iz)Z+oxNLlIs+6OyF*koIs-Jov&K4UouJ^I3<(!z zk8b2}nT#!5CL@K5M>oXNDIUgHPNDV|C~I7hzQ(gG;mcw}ZCh z_JTt_4ZK0P2b40Je{ez8KcqGPU@R#()(naj29M*-An${U*%z!x4zh$eXcwx3l5hR{ z|MD%QYH?v;cyYM^;RSYxjs#R4h9DiF`qZ=6M}^6wSMDGR^_7CV(fij3=d^#UN z9Mc0ajK4(?Wa;)66;SiSr&}B0B#;>|K}QOC_Lf0h0@4h2i9bYh>2Yv>^@Tv;kN=^; zj^HUUkIvt_K;svk-$BC}`#>vXJi4_(L)9$DT~t^=Qb;9*iwcL~ffp0id+p!< z*9Tsd6~O8vM0;fZ3NEzzMgm-Ul$3aM{`Kgt&+zR00vhoN_2?Bf1MQ`2JPula?g6U5 zWmJy2sBkd6;QIkyg3Q5C;tpy@uy}ULsB|}jW+Xw?3aIS?tyWB76*6qz%cHwC!K3wm z=^}o}yfSzg?)AwDpc9foCnJJJ-(SDjmk+AAk>|%ip@|-F{M*_=6PPUrN*%lRgDh|U z|Gz{EG+4?G9yb6-#EVZk;IUZHtY3EsI5?rTw*+W-0_Lt4ryqk?)=WL{`o;@;u=Oqs zNaKy6!JVK1Dh^P{=y){OTYyUP5(&%gE=&vzrG^dr13@XHMB`XHm;pI$-@NAE|IYU> z78LvdonFWR8VZ5g2T>qa1B&Eo2aj%N3s8Tk^Idf8{|E5?&^|#11_o%ms`EFfq5(Ap zy7iAUfo3;dy4Qp17nklj7Nn4AQ2`yK3Ysw!QE_bk%jD9zM+LOe!KHJJN(dtZLkWvZ zcNtH2n1D;?78THld*d%q$;;oa1*#>dsKhXWx}QtHdOW(z6n27SL5JrEys#()EwFFp z0679Q4+*->$fJ9&05hmD_sH3sI$;6Kn} zWYa^lKp5&o8$j0_CNn!y~fN$*}v zulo1jrStuZ|9Le{um=h|c%Q8E1%E3SXv+A-o~TXG<*)IXXMlQ3*`EmOaK0N zw}3;v`N4NkmDCFA-!|_7M;ar4OE9Pr(%lNWV-mcGPMec~!SE7j`#v)RgG=YT{}(`- zT8=P6dPXs93=GFyRFoM|3@!D9#N@gI3=E)Ec^=)&F5OeWwu0uCz-y&^x@UmT^98L- zbK&2<0lX`v^)`R&Nl-Mof_BR8XJUYy@eB&Qq%RB%FaG}h_rH1f1<+PikIv=;cA&2E zGlb#W7tEEAECKRT zumyDB57h61d%czQKlDHgkoAv0GcdfU!)1M44>%A&?a$3Oz^;1#;$qXk|KP*7LHVp< zFCVCkhO9?w*qZ_BD)YC3c941?0=soGsDlSu00W;NMZ~j@ijYUIsKOj*Jp|qq{zBLM z-~X2q;3NU5&k*WMR74=@0#smt(gmX`Bwe(=_zOBE09=8BS^mO7mhn28Ndl;QCxT-??;ta_Rj4Vpq|R|BjtUUq=7={~y#| zDpB!(R9U+?84%HV+y#8dB!f@q#}_{`K=l%T2l&L9?!BPnJ72Io|NkGN&8PF@F3>tu za2@B;`M%Rd#pA_MsFo7YaUI=zD>xV!UR->JtOc~|6-CP=h?dSCP~W}RGTUoXNo{z^qqjyS0NiBs1$B%iYg7a}T~t&&Ixl?oSk2$!$OJ0i?t_+! zY4~);s93ysp8+~y4|LuZXgQb$f4dy0t=kRK+`C5wv<+*g9H=DYe}BNIJ4VFl zI$mJ67=Tbr{+2>k&}>PJiUEI9C>uzO3FJZv{?;T`(CSGa&}vN3inLx+ z?`eVzuALWrI&T>s02yKc@|whpFK7S#2e}#4CTKqSf0raIZTNKF|Fq7f^Sxu|37^iJ zj^8i5xS0k@9$>{FOAJaJK}J}-c$oU*e>VrH(SEMeM@8a=;7^E=7TuuD^1Y^<(?BOI z)__Kt4}fyZtrzuY|NRFW;n@6(i@)U>=$OLhUrhWh>8#-8ilDJA25A4hGes$W5t>6L$;xol*f((xQ+pifOaBM!t_(BO}c;^RD!!E7UMJ1>kyiB!3 zMF3QKq~?PBP_Ywqh$85I5@`1UH0$c((Ve5Baok141ysm;bbkNru^OaBC=aBCzas>k z5g}IYfm``v^O=ADk2P-wm4q*E`~oez25D!2_>7f_0mNcD?xF(r6Mw4?Xha%R5`ct2 zJr|H016+{Y`;znj|Nj%ZAvH6oS^+!!PcCSV_CJ3s*LaYJzB%HQ%Ibh`kgrgZ_A>wC38XIpC&gG_(_ zVnX_l|1bM~fWn61#pdK6|9!e^R5+mhJjc%UAnRY;{swkoF~o(S?%sdH1ABEr<}jRv z)%23kn!dCWTFZkvZD2=0Yxu225G!TVfBbL$%eVvN6MmN$F9pE1gZgL2$*^{GCd5Xs zNuV$}23xc`_JDZ4@p{FrQ9CJ!9t)!1uiuWFZqDZe7WxfT8s_Kq7vZz{z4P9U8>tf#lxrD zMMVRgk3Cus_;iAG^S6Na4EOec%PNmqw^eS@J4fJ#A0h`E!0K-Q*}uzU1+FdhRH&!9nx?h+LZaFY=f#u6UA z0UR$9zy)nxz)nyH2~;$6`ltkeJ6e!r=Kn@jgj4<-hN zvtE+Vix`u@O)@@G{}t7w~o#K|L+1904<<-b7+0i zTnp|oSnq}O7`A{~t9!vk2!9LcUan)UV2(@YKabA$ogpe5FB0=WWy=2-CzF2s2lXgl zdx6G9yURgNFVS~3{MKB@!US@;Bmb0RZoMX7K{@dyXu2G%4(uBKR#(utgFFM+K_Hj$ zw}LK60(D6{kr(8_$G<@~d4Tfq`xg&B{{P?ngT3=m$wtr4*o@Z)_~jXp+;=b+l)Gvj zTsr^#548a4d%Xg>J_oVB)Tdkhg~un*y)SE2Km*XA8MQvpVr6^*$nClc!F-3 z01q8>gVs=kw_SJ~ZwAF5DB3+boikpj7J}SboZ-=2r@>Id>~Zn0N9SsAL%nx3Na1S< z=s1B#cW{PBcaeri>oHfj`@38S-&(160Az;w4oF3is$C!|Z@>{@1)EFM{NIuqCqQcRzhaGfb69Xve3Y3^0 zV`7AKIzf#*lujq;q(+a%BOo&&wmQavhfYD~b$9-KG2sI!&{l(@&9n0Wcxf#-`VN3w zB+Zc61J4&6_2Bn^0m{@89^I2c(dN^6+@rf26p^6XK;gwykWrdPJzC#-bmpiy@b~F} z+KIg`Dg~e-T*2ewUsq6plA{vf)0v_Y0dl#9N3V-Yj;rB+!?z$aJfN!!4M4)+ol6$Z z-BXr=BEq%vS@W;|keUR1h^s+sLZnfZ~Z>*q5|HubKFHG2h<69p_2eEs#;q>yrdar3%FR1bW?F!~T{O>j(4P%?jUh4(KVoBuGDW`Qm_ z2JOf5Y_2U}D9LtgfE-E0-?Ehnbb`fF&{V5SC)iD^-y+=9YkIUB>>Ti_P>_qx9{%_L z0|U6p=?N~Im0hj3Lds?lX7EW-;PRNig$=}m zmd-w%?_D~-y*Qo$ilXl?^kaYgZ+*+(dIsSi!?!Odf;zsi@^l3Yf6L#$|NoPe$c}@| zJ;2|Z&cwjr0!m~rcY%BBE}%|Vjf#Y4_f*i(hi7*$Xz0VEyGF&sr}N(n18~$#1;sb$ zyy)HqjG(i|pYXTj|N9TR0P`h3!kr%7t3mk}I`0N5o?g7P2901R9|M^u;M1L=65-h! z(#p`?12$68oH#TBFDF%}iZQr>-_d z1~pPbR3s2V@RA2y#5*wfb}t3b=N|(%R2&#Ql0#G!9Gmw{1%=Lke%GU&M_)F8t{mt! zHS7S5&w+e=f8W3V;PnO1LF2Le^FZC9lVG2M?ksuN0WDMZf~tuZNeBM@f2j)Y`GMB$ zfa+iHy4v?IKqq%K|6ni40;TiLqrRP~1^g``pg|uY&>C8Bf7Z8qDQLOHOEswbUQ7-H zhvUlthR|GYFvUG^XDt*EFRF3h)?05RUXJ018#yik_n)shhoAmkO>iBUxU&O=>Ent z!JuW2oqs(V-$2rEJ7_%7r8^9D030;m1)9>hn{-{pp5^ADz7pwV=fPGOf$ zXOiFWCzHU0CunHhqw^MI zXq`m`w39ikdn%~C0Nx+Q+HB2LqUzB-1$^sIiia_DW+T|6@dzk@K#d3JdV|hjjn~)s zbLqVPqBRaQXvm`Sy5)sd81ntM z&HornGz(hrW`4zhUk`hD~1b>}M0cl>scqeK&QYqZ!)&>bvLcX>7+VDxBy z$yie7*kHj>`T@iSi8+2dC{Q8?QqKQ6nO~j(BnFZz2}7uJY<|cDTI~OmvChGx`6ZKw z<~h)OjS+Z#PV-O3IxYU~^<3cfi$1;Kj6RkZ`TIckV0ttkXY$be>B;!xWg56Rb75cr zt2&$py)W_~XtD@=)gNdf{yNace2>o0j^GX|sQ2a3-3AhK>E8DMJiW}K@?Vt6#}u^I z1-$Yt3^e)Exfpal=<#OI`9Gjr1HotDfaWM7a{m5r{=w+jd4RvI2h<_^@wZgZwe@X@ zCP>z6!e7uCG0{ST3_A-zqlO>B>oecKNX+;PZsvjZgMycJb$EkzT22NP`{3J&F7db2 zf}GQ9dafB%%|96VTR;VBcQeSxpjoR|;M=<2z7zr7I|}bFdi0t$LM&gj>)-#E zb+GW=2{O^A^AWh!sd><+^ScZ9$ci~fL2XyZ&Z91%gDXm=fjzd~3v^J=`xo{oE(WhU z_2_)>xEHkY^o0_tBxnE-+%IHNap~L*awO=EQBVy7igI{<1e@;Y>H$TB$MH4?P!KaPFzfJ6g)U|6*PyH02)T zZv*X3YlfuUC!myj|HXt}aLQfCkD7AtzgV6M4(Aqs(C9MgW}!cq2I3W`J(~?Y!@}2b^rJdjI}+g!l;@V&HTOzKR*X6b4kUb%R|CNw}c1svs%1 z0JQ6XzeNlbF5T@Q&x4Y#JtL^q{^X?)ICX-82-N;K*#Nfv)%Jh?UpK(ox1Qa|u7unt z?9pqQ3swtt;g0S9{=ZBCo5cW5=N_G};OYFmOQ(+t&x zg1VIlb&o2_~gZ&Zd7N12J1lej|V8bJozum;%>_D zG6P{AN=E4X?9;mpl*mANA;qH`vZM*r`Qv`O+YsLKpK>jqTRLfaAhK&3j!wC*HG zm^U*rFhELYQ0DY#{>#bVza6xcU^U3^piHyS6`D`MS1t3m7&0<2bWaA!dcb^q{6$RQ z4`?9|>N~m`-UiRmBG#8e%ORi6hq%fiEJe_F(BX*i^5-Xn2P%JF@E!$adD|DYpr-pV z$VSv&(fQnh44;>QYcKGQ)D;cjeAQ zqw^lPKLYmbi_{;W+jI`U1l zGdSTvOnlh_>YxOA|M)*4*rQv%yV(c4dZLsm`mjguZqTZk7bWeWFb9c)3USa{U&wqu zbgPHOf6-k|Aj_*Y{)=vauq6JAE`zWX{)^6musHsUPJ*xm{)=`%SONb<>mV$T|Dq)z z7I=$DmXj$%+63@=t!{abZawH(7$vbUD!f4b+FA+ForktnV69*B5qxd4l_PZDNw4kmG2UmkRiGsxt}t!7?hcOdUO9Db5St@4?J@q#%V#U0+7ob zd^$gPcE0oIJPPXnb9nTcUat^j0PXk!&Ch@I=zM>y1$<2u!;33E;BnSIb#PDN`->;; zptKO8!qIKy(R_%dx9|VIfB(K;Q~cQYu%V%$q2xv9xr6Tnz8`F8U|`^PIp@(^#N@&6 za;@{61H(Cw=3C4j+94_&9=#=^pvDycHa`AVAwywmNB(UkZ2vua1O7WS{NpT9 za%lLcP$Kh^nTr9GKlrz~i2e8IE&6YxP{LxvStTKZ~<|Dq@CKuw?b|3%k>7@!+{oF({MqCoPU$6tWPa=;rjoE7+6TtU}k zw>~M!aBQ$qVJJy-1Rb=>=-Hd2!q{@4RO)3Z6KLB7sN8Kn$_Q~Ee+%exD;tH9H#VFl zFTjxx>R#P;121C)b<M_#P5{()<=^Hm&~l(u z$g!bbfdO>DRkZ;F(xN^bb{s_UJF9TC45_-c!CDF zpO+YUbh{h)bgO$>9<6%`Iv4MWhvj?z7SNc1Z>O4PbG`)Qi?m;$qwk-1cI*3eJ}*7z z(fYR3?Bzv928IcsjS%Fck|vP6h~o3n$3t3!zmJimK%yMlVb&~lqdXhKAZm2L6^qpo9gwj_$<)(0YRI9&jN6pZDknuL#WgpuEufVuuz&=2P7&=l$A)?VhSErn&R7P|W_Jlj@cCx^@(jM6>MtEY$+^24 zG~>eG()IWMf5UGte9nNI$j{l+)KpapRXvSp90gglbEwg`v#>~HVgs2$3 z^!)=`kLS^AyS!MC0X)U;)AB8#<% zKjo++|I{NMov#f4zmP#P6Ld$NYs(YUoqA7zHXyL%|7cWkL7jz_t2{C{Q(O3La zogEDSzj){j_6+F4pFvmT*Ma?Zu8Q=lK-^vAA^afh4>d|ZaB#xco z|0R!J+uL!V!*q^-&OG=p$_8>R$PQ-(A5hPezr}(XJO=}F^lKg${%tub#*iTfk6zOl zPly{`Aa2|rig06u;r|!5NN$W{16}d;guex};?Ae@xZyW&Y5O7*6a>AdEuli7%TPL< z4Lmwuy73qUkahG^z*0mUaM1-4uR70i1;3n4suO`nB8T=xomI8+)W$$)HX z{RT-1?EEcezd$E%gGb^Sz}dUImcyfy`M>CXb5PT_R^Y$rRtQVuzvyZROX9!iLI_LY zzvy%b%i_OiFNEdsU$hy*3ivNtX%4=Wf!U+;KEI$V=za!g0e(SOfzSN;&I+2W4m;C!55%n?!JaOZ|;L}~Aq5wXXU*+fj z|NHEr87TMyf_89LDi$vS-}Ie9e@6h z|Nmchfb{j+s`?2r{1-KVn<&8FvhXM9F2c9`Egc|rlYfF%*MZBW5>WffRv}*yQjQga z2QIO|^x_-$qbA0-rF;-vXKt1T}mu zK!v^pc=^+dHx8h7ZmA4N+yNpEDpy}z0gFR66M%(5roOiaDdul62aR}l*QkIN1Zd}h zdf%YzyKvDz@S*$0V7I>mosbV|2K*OoFfnC#`5ELFs2_Ucgcx32It=l{36KlBt3CdU zMwmcD#U8`}hl&S(>r&AC*i&$;4>S(|8hv>=6I7G-nl5n>WY`H>*6!1J98pfTfR>|z z3hzsh=BWgK3uxRG)C-FP-<@W9GXPuwfs!2m)Ps;h=#}CB4j&bc7v6TDJLEt|xwRhf z>^uM}=^Yi$@V8_!GJqyea#W089t9=N?hq9VkIr|HP+SHHm{vKEyShVE96&PQaO{D~ zfNu1H#C?%5)R&PU2H2Mx{NNkEjyXFpzT5`N4!yP+IiNc0-HSEgaY_DG3y{gZtSnyz zz`YxZm&PE0&i608tw9ak8Wjmp2gl=u4Tx*$qT<2d9tBo$&_mk?bSy4tH85!Y*hMA6 zqt}EBwCXP4g&)N97SLtXpcD|{VHpCd_k+L@?4lw8T8R?SP{Z+GkH3`*v<}b$Jd6rD zC&F~HzYqhY5)`-n@&6?UQX<`wEy(cV<3UgampFQKR|otTtu}&sI|;-9dpm%?^~C4@ z|6g+a{r~^PW~(3n!P)2KoKe!)~(8BYzo@>z|Y{@`oys#R)HU!lcD|MNQk8#org;tCx8|oJ=zEA zFoP!O=3olEsSP6$?Lk;_Xu995vEZFN4kbVW|em(}Ufp7U+ z%t419)Tr=-P7((Xv4ak$knlm9Wdb*#J$h{=v!GWPdUk?*2bxa;`wrw_5QFjc!L$jW zPJJtA2FIhAg^Z^+ar|2DyvOC>4|zZ6Q?8-8(? zN~KNkXgl86p26_HE9i3Zqx#T@^6Unkj=N1CG%o@gdi-A^3N6p& zU)%uQivikIaliC~4=5q_>zgvX1ns2(^+2saE}IA|zaZmX>mP7{n*DG2Tjqc&++J1R zOhE?lNqNvo$louFxBvUkFVDcgt@|QqprCv52auM|`ySn;3@^+;qlm2sKzS>d19XBY zNUrs5=|zw3-5{f1i0%aM4+XJ7<9QyP_rV5(I$JL#xBmle-iM6ud31*|_(0amKb!+v zZkNje>P>dM-U>-SARWl(=Y#SOXbu6CbN(9s_h^2TfixiGq7ngWWq=Mc4^ffm@KF(a zvD*riJm2!SOak?Jn`;>u_*>eU7#Ixydv+duF&88YK9=}6XaLT|)$rSEe$e=uN9%1* z&{EXha5avsgO5{xfGL3e0EqTqrD=pgmp z5EUCp+011HsHBo^itCy(lqmUhes}zT(WBS)KoC2_f6-4m zFat`we7bo*>wu1~`VJn>d%QpS%N@_rd;)!{59s;_9chh6~rUsOQHl;O1o#GN3ISiJoIA9S`u=lv1^k8b%F z8gPE;2gBQ-DlP(bcmlK~+N1eM1}O2v$0i{8#RqhjfKTUlU(4$y+|CU)cKZCSphZEB z|1W~e1Ms}!e$Y_pf6)SMaL9wkd>cHwK@IZO|0TlzMH67+t`__)pi|C3lAr+>kfbwQ zQiH$6?my@nAW-EF(WwTPl;Cesgi3-2W-Bcj7K0?df)aE$~rqCf*B9-Xf{T%%sD1kJC24qmJ<0ySxDR6IJ5zQ_Tw zEsyfIcY_vnLT-Ln0NtJDsR=qD)vMRU;6=wyP{t~e^XLYhA!&J)zhy4C)xe_S)KFva zUyr|~7u2Kx=S==qr@x@(Vl^rnFOxy4I%64L?f`AaFf#f9I==%nJO{ep#Syd+>knwU z1e9!ZVDrtOP43;GR>x{hP@fPq9kLL@lK3w=9l}!hFWL)Xar_r;hOh+wi&jEd0slqw zAuNyoqNyMjXz{@7|Dur~cI(^HIiAN^R6rTX^Eh<-6et}+=Osalc#zvOpm?YT6~@0o zCqIMse`yH zvJP}C1L(XjPs=+#{4P&@EI;#u@89w41Wg24{1^2G*#>sMBZMXKU(_7JQur^b3t@5m z7gdI^1pbRkLRbO+Mfo8tkN={qAQsg9Kh;6E@8wD`g63Z&J({Zx7(BYQ`CBSMjosF_ z;0vQ)mp=FHOl1HCI4I*Ep5WQ}?j__Z9Z3Gi>R(8DfgB6kUC;4fv=3xew~PuX>12Y- z1$eqCRtLv{#(&XF2utF>Xgq|a@Lx0(!s7TZ>J4ED{1IdUZ9-1sPt4PVnt~;nDf3p*jjQ(|R^ukRc%4qu2IvydZ;P zkYlh1zspy{1259$!21gnz%AwD{4Jmsnn&j`Q0wT-I?y=ffs%C4j9s@hD8LFly1g|( z^HuLbXCwJ^drSCszV_%9{pigOI;NAur#l!_BYW_>9BrtMVDRO4z31C{(ud#WR6{lB zvN+Irehrp-9=)QsT!k2XJ3n~vyIg$<>X7rxGk_1rey<9u1E0Tmzy2SnnFmVGKAp%* zR*=SX!y%h{kj^ZZ@Mzwj0qS)^)~$GS!&-ACg+9Hi&GE=BxfeNWLCrYOxHh(S9H^55 z9;*ViD?GYU+Hz-MZ8^{NC~Y}#Lk`p#2weN`KXRM#{)_)>KrOh{kSKi%nue&AVBl{B zZTJK&U-R+kb}sPgUJQ{xP{QV8nGjH720Gco5LDuG{1?5d0&3vesDO$U5pX@-t^eZ3 zLs0$s)}!-1sD1nVzvy}u(Co(%3FKxaD7!euzudD1;@{S9B{Ck}`#}v*kXZsRnn4kL zycr|_D%(&U?zZONe{c*M5%@)fuh4N5%>9^K8LH~@|Pv8cQV zGXT{XpxZ5ufNXPhHT?ft0G#i9TfcdB{`cwp4rxdFbelulkw@yDcvyqlkx`&k>Ye70 zb|mjZ(8LPpV#?AB9^E!79=)a#F3_GFQa7#)+&*K~R|Sn{+U|%IWY`5dhX!2vdw{NF zV^Mh#WCC(iE(5rg2U?bM%teKt(Wh56z#5c~Y@ZtoGQ3Xk?F6-I8fwA$r#A+ge-_6; z@{cFK%NN4~FVv-x^3O5;mhGSn331)sLeMzDA;SYNHpzjOzkL5MDyR%9VZaubWO#J@ zg7eZlk8W21pKf0XpI+GvV?hRwUeSdfp!`(J;nN)o%1@sBE=L+_LHWt$J}f`gg7On6 z=RopPmlMe4tQy9G44(WhS6)K)c7fvq(j9A^`uG3;m!LiktiJT=EQM^-htFSt1NBZc zXw>M%KiPkvD<1h7N-90NCxar?qu2Cj49H8M-5mZC{{4Rms;?3G5gI9fyx19DJkSMo zEe?2evZ$0C@aVNQ0Bbymh>92IK&^-FTu5IV6cMd8pgv@`jmnF~nm_)(da z{>fD;0XmP0jemQI3Ln3q14p;7Kw4)3Q(7m7N4KklZ|8HrZW9g9=A#^7j8=N0HIQO9mRkjHpBJy;O_dZ`RrYYNVHpey_Si?)HR>ITgv*MM2s zpfj%ii{^tkov%SH|LdR?U!al%yc!x(h=A4ygUTa`|Dvu6;F9{ksEvXt!|QXP`M3Wi z)+p^A(2a6(AOpenh=5s8d)Pso*0&y=uS>6k3i;^R!`SLWkIoa&E-mO92hc))(6Jsq zpfhy3A?N#mM$04&PlC1?3%r=A59$|nx~Q=5w}MwpcE_j)fOl_x1$Q067g4~{f44IW zcnuI}Wew=qtya)YFOK_U7#JA9+bBFB6U*lr7#O;p1zHdAw}5sSdRV^TZvsWzeo);1 z7j=_|#^-VV7E?yh*{JVNe&)|-_5ke%DiH8E?f@FJV&E5K;1~3e_%A93Q}3<-IS|^T z+grk;+t8!a0CcWkjo}ZE&KiRs9=!z|9?j2wcxZlLJnEr&u=(ka=D!?8V$Dx~7#`?6 z-h6<=r<=vn@`X<)i(S!sP?Um7SKoJahKq4nFtve^W}GW4m#kl*0-}(!?*LV z2V_4gf9p?hma74U(SQC{Q1JS;J^?Lp{Lk;|qQU|SNYI8c4xdhiZWk36P%9pEvJzyC zKR@`IYS8@+ur?WZhaiLFK2X-!3nE^E%z}ltZ|7go0v!fV&?(=dY;vIF^Z&o-Us+I@ z2ysJ+(Tg&@AOBx#gX%X>I#c*BdRG=y?}0nqm%z*z6%Ekfp6C$}uk#LQ+twA(!B*cu zFAYF4PgOw&JGp=cdH$EMzFhYE|9`^+ zpmYstO?Y;G1NSbj1b6;*>^!^Q0h9%s-w6B{jRQFoln6i@dcmiA9{w+C2odz`JW?Xw z{FjNp1+?#@`7c+AGI;V@2_)Tl|G%iDEa)(9@ZEy3pp}e{afj0;fQulH-e83tprGOx zbXMqe7V+qHHsIg?7P5EWqchmRr&GBz8+6kns7PY~%_YKabbRsp7ibFiIcSK0>#d;m2lxMr#!G`@?)5UzxZ{D>^It4i2BjWwz3Ld|2)d2Mv-7KC2&jVr zF2jS5u`z&ah5w>FAXk9{q$FWq9_UsP&;-eU(JxZq912?M)CpRk1FDmEfTVmnpQm-Y zGQ1F01|2OQ!06HZE2rGj1AHhvNZz&On@6WNORpDGr?)_YIC2FAakl}?i z{Cq?3`iS`;%OG<-Q^3p{j>lXXz)SQ%B|0eGd-T>agU1;Uy@u zIl-gXMf|^qZq?AA&2VI--K@uEB{4Hgmp-Iq{HqZZy9)<{lM&DT*EEr1Vz&_jy5``_w zgJdp9IRu_D1r5h3gN%g^$NBWKHim$DNxYR{N;C^h$)bIzd%mj>}~^J4aBKE&K|vh*0JJuB;>c>}uO$yyQQ zy#Pk&Uc5Idpm@9Q(fRtn=nn}~hL>jG2^e02zaE{Rz{`=ZE#cqh#n^JP^qJ!^S4IZU z&c7hON9U)P7eMQJCU{!D_2?~PF4J&qsQs_++3WkiRP=QTQv0&`C1Zo7eo3N-<=N6t zo}F(&DH#-6%pfyBXE#B@%hU4hF()Q{&^o^|&^7l^vjkt8K;%Jtp+M%SqMO46GRFlp zQr3C)^&-RDFPDM`w$8r32Ts?p^a@@_igEi;>$gfh&}vlB)qe02$fxrGf2$y(`_vu2Wjml-}(|L`E93y=cPr4}vE zKtuSwE^_}pdUcltfVwL&DjcAQ>10uXxW|=$o1J5C_~nLQf#uHp+b($|yG&*9V0`ia z0Yu)tqehhhv>6?=pw-9HM5Xks^D!4y&;=$(FlOXH@d&PYnh!I1SRO8u_5iI;dhY=l z4&``Zeh}2qIN;fNw8Y)Jw?-AzyJHUkoes6_Ja{{^;Q`O)Bg`-AU;h37x(}SrJd!VY z^hQAv!wV6(A6wO+`O4Ms?Mu)>9j=B?UM7RZAmd-1pFBGM8UBB{1scw<@B~k$x^$ie z8`}KF1HCf$Xuago$)f^VO3YEp!PCJ3hL^+s0e@-^O}NMBNCuJsSKXYf0@esUQAW_@!yeuTa5}NWmbSv=D!j?SJ3(o zCZ}&M9*hE|Ha?xNA&C=w`Rx%#kPXfqE_VOd^AG)Y+%= z_3KNJ_;TndQ85LjO9^m-1TBdWaOv<-G4pIb!gS0<#hk&XH~a64zo1&6^Z1L@8UO#k z-p&siZ~FdUv|iYh0X&@znrlF6I=Fi@AMt?Ib&&eRyO+i8803r`&?y2X%s!nKpy6(x zP6NXOulG*?Uv2VwJA6GSXr$x(i!y20`KQo(%QR5JUjnp?)&ta=0}Z`YN_%wP@aT>K zpYSaMITkw}(!xGG!K3pm=&CURP*;b;)$pW8@}(D`^Zi;6l%52Qo9ctYK)|CBwBNqI zOc2E5_%Aw7$duvrN>DlRV)pd^|2th7VC_ASJshw5UR;raMejS18@M{p9nf^9I}(R`DFui|6S(=P{$55I1Wjl zp!KiH!5*Na=Ib*4i?V{ugkF5>0p51-Sr9Zf$^hG+3$p)3yEu3tp+u;`f}w=hqw_e( z%>Sx81VPO*5F24WsO$L|bbu^)^t6uSzvv=CP}c%<@(ZN#-1@(S>wl<(2jn&&*j?!$ zQ$P-g{jXXm2;QsJ;~n!y8p)))DHk{w1Q)Cn51AH^Oyhn1l0;r$Q0ckn?59RRbHaYCsEe9H= z5PWS74X5sCP+z}9$D^Csr}Lpt=PQry0s)Wi3XcDw0-&bYJxD7b6nr1@LB)Bl2IGFv zA$b2q8~C9K{4jqDXp1$tgP9Ex^z1xdQtZ?D$Op7N;rI(vY0%)IiwcKlcNyp=2%pYs z4xi3s0Uyv!lFTojscGX}6pDG7BP|2tB2a>m8?ycr<1uX*k%?ItreBv`@ zczpq$KS4dm`yipcprT|4qTmrQt>Hx^d5`0+;2aB0 z`k**N^ru??mn3()E3_V{i*XEdHT>on>T38FylSNLH)y|Ux3FKgiV^s-_||VF@*drJ z0v?^WKvOA=xuD?9)?fsma`dkBNw>3tWAjnaJ?|WjmPc!jfGh&{s`%v*+M?e{*6EwLf0k1$nwHOC9+&sF4eY#Cf`F7iZMh_i8a|tn^dcd=r-LpGi z0MsV$HgNoZrS(9GFxZ{{Ksn=D4#KIPo$pI8b~`J8P3=4dS`gHHh!LCfl3G55^N7 z{QLa>dl;VZu>4p0)bNt$_YWSO7Y?T(-;ZQ@u7n#rbOR}`L06A~ihfW#=-7)qF;M3o zwCu9^2nYYR1EBiT?zkJIA~@~_8ggL(^?s4lr@H|77L)@eBH$ZGK<%P%ff9C)ZYGe* zZhsD!ZZ`!-%M*1W9^Gah$p?LUd5(fgN{?=qV;nBmcR z$dmDa$M;*Fy&@Mq7_Yq+1%P`5>c5XSjez zH&drS2U7V89<`x>AG{AV5#rJL8$3|)Uo?jcT;`RCdvptXbT&zVR{k7_M%1U^_6!og z8+P9_^nPcLZs_?MHH@Xqh6fJ6Jl%?NUwp7f=O@s~U7!>y@Zwz!c+;jRbRyGlei8BE)6EFVT!wEwyJvtL=h5v9Zg-`5@UQ>WTwTHF*?GpJ^Q$BO zlm=nYC_#6zfQRLa`UucM=n0~rLfW~&qdOa{CmDQrTcp%PahI4sl)p?}SED51N77I{_5vt^Z5fUnB{D)+!z0pL5`)Be+8XuJ^%Z6=;Oa zF~kwFo)dKZB8Nw}yifOPP>{UfI{N>=M|blKCeTKRw;r9X4fdeuXa*g1?$O!WVb8!& zQt8{h7i1i$43qWjw*N1Bh8;R`e3;#o!Si@4NR|OKNZ$+&+#k#(waq_RN+MiapSW~B zD7A8J`BrM&uy+M0&`Px$b_;?gh)WecAcw;Opf$KxqI} zGJ_jEV5>a4T@66GTw9-bbT)%Vd|f&pl+1j65n8^%$Kf)6xwt^{Y*%P3j3ZwDl+zTN|CpMdM%ui#zCo!?)?fjrcC#G{+p zqk9IZM+K?>jS%7K(H#w1Q3k3QtwD{E&UY_5r-7FTJ@x2j28A=kUC`7H+JEfP2{->A z?~nftyXS&j3|^1GT*BF~;{s@VcBvWE#@Etde|U7mjMoAiU#bSRx%4U2XjuDe2I#oL z*H5A0i*N*_J^|%l{%!0YoxfWSl=8mj2erfbw=uUIC}FX&D*f1Upu~cITQ;LlZ!)6; zX#ea<(3z^sMM2ltJ_g;zBn&gR`7k5@Hf@KNw$O*UTVuF{+2(WE?W2QAB>jE@8%zjCAyBlB_=~Z-8RT#N zR?tMMZ9m90eG>ow|A%@9bS?_ijV7RlsNGXkK*~LOO{ao$HK-MMy6E5k9iY$w4~0$e z=B>F@Fcd{8Ha<2buU=b6{p40y7l& zTi=5Y4*{7i12Ovm!fcQU$Yw)sK?RqC9><$O3PGdG9><$u3i-E91w|VuLO^E=f)9s) z<~N9=q3U6de(4Dwu5w{ufQIB-1_lObNPYpGgu5H$DF*(Q)qns0hhB*X$)Di%$;)Z* z{g{yC(C_>2|Nn;F7eHR%Zw2k^gr*n%7SO&7iv=@96^3TVo2{SVsLu>rKA91?b*)+@3dybvi+G(e@`#+QJ+2GIrT z#Utyw4q93PaTHVvuFDRg3pC?|tZN}c7gP$aivv7<5327yA@x193lD1fX?S$&qmOv; zZ)*o#mFd~}26Xc+=pLws{h&m{z~2HI5BKPv3QDn{J;Z_@&3i$Nu@@P$68dt@o#wGwKpW5c0=PS=Kufy$6CRXP>T!? zyjGv!+1;W7aiO+H^ADzyau4v)U!ZXqkYN`=DH3#lUkP}0&jdWW$KMLxLV$kfCOCgt zbgu;6UeXGh#rEiK1^EwTE`Q5sP(cSa`y?X+!?D(nAd_G6gIiwU^R_-3egpNcJMX_Z z;RR|Mc=YoAwi0A0ZT9E}r){5J)-*98&`|WRtDyBprjqucf%5mDv)B1u4;UVJQIPWw zG-wM6g60p5jvXy3AP*QGcxer@rvVZkrQ#spfkOj)@)CUf)2Dkas2$)7IziSHw7I6& z^njHh18DUZ*nhs=xg4NX3G$xZwG6xXK!aPI-(Hk~!@uN_Cul!H=@QU5ZMQbOf9u)3 zbpdz>ujrO&&=n5dpaR~fyP1KBfnf)O186+bMMVHqV0m`$JpkIu-ulgh-{phhffuru z{{Hvv-U<=~^>f#;g2uRgR0Kd{svI7m?JVCwr$)AQfOB|j2q-5s*Qf|E@V6L&ipB2T z684}P#P_cW185IXTKlaB18C23 zFRy~7AOnBv5>Qdl-JAgqXx4TSki$g1EWy!hWh)3ey$2D!6SJVv3(6+Y=mn*sm#6-K z3Quq=i@ybQOi{yLa2t!iMHpmo_g+wo$+z`?DfeqV-|l)2P^(|LV*D(D>f zx5>BM=5GPjc^k3M*B~2c^ zroXIUPUOh=_a79lpgNurJe!4R|9SM<`j`na>;jd~Am85U1|=*07F*E%iDq90MvrD+ z0Y;DJS`J43R?ra;9>*aU6ThAV4sTFMMnFRnRG+-u2)e}wVg5b|_?isWgr6o19WGjUCZ!abk7eHP}}6c=w=WDba_LHhcTpjaq0ZP z-wN4hd<6NTba41I?*`c|0!bqubiE^Pq?2ArF4{3!a)68}|PI z#Up=f8`#~T9pQ{@;HqIOXpI`9uYsrK5zq=r{uWSs9yBD?Yx?W$|NkD0$2~QVcyxDx zhH5-JeIq=&yFf!Vo}Io4p549zo}IoKp54A2o}Io09-W&&sysSpK~&TV_;#jpcy_J@ z)i;jET|uX%GrYVBE>1!1xr?BK>jgYJZ}@cn0uP&jwq8lRNUj9k7UZKM0lH;Fz_;~e zsR`ImFI_<1g^w?LS{^C!cHCzU>ZZ5;`~M$g0jQA&F4Dp6Z)@*f5qY1^lb)S7z;nGI z;{{%@fUg&GQBeTx-x7ef)5 z6F|FOG?%D=&MfBFT%!US58~I{q5?X&m0xp@3TUA|Mt1IPQ2|{^1G?@}06gyvngKUB z?xG?9YJwbhQ4s;H5e3O8yzqPX_dj^k`(e;{1t{OLe1+!Q-(O(ygtB;~)ZC--2*@Yk z4LzXrdi=$YKi~g@uiyY(viaf_sLt;M52gMW-TK9Z0W_BSdglwKzu*6-LGRlD-3`$R z8aDC(-v`R^Vk_8)caRgwKpQ<-T0w-ubT+66{Pz1hXt3gSANagsP~i((Kjz`njoQ<&Fg)M@S~3@+ z!olAf#KgeRupiXzVDRj21=ZR7t+}9fT_@JL6=+si9z1*1=I0i$cnfBqIwf8@mkP*bIw z;k5|(JmzDJpfxC9Sw_D!p#Ax+2TFAi-7X7JqeI+*EE9?7JnFI7)U;#C^O0_^;6;PZ&)@+0B6V*qF z6;PoHt-rh1gE~i`s-=5B=mJB}&MzLF&t9Z-fJ_42%>Y^#2)aS9+eJkLG`gK!q9V{c zA%~rTq1W+$>q+o=AN(!{J$iXB%r;^0nBmdQbFiC5B>+?`fe48Jk6u>M*(MA@F5NCF zIv%~E5{95daCkl#9@zKao`K;-#gV`NyPH972i@8W>P2fd|6ndj0rhrU4MFLy*OXHi z)Ovgcxow&K*Z=>WM_-CTq}Y(9-uwhh{RT%TsDAA=-J=8Q=YsaH*vJ3-|MCH-?**^V zJ$g+a=pY%r2BIHyE%u8I-@pF{wf7vsb7zo{_h^1ofj+pAVR)cJFdo zr4>G)t2)+#axADH8xQK89&=GiX8>&zImX7w-+u`-{8+J~p!t!#pz(|UqDS6=dNR8~w)_`e4&s2uHCsWF{4M366KaW`Q z^qTr<3o^Xshm1?Y+%FGuKWG>Y0{1f`;}dcyvQ7=(Uy91{aaqPoY&h*hcIWZ{4ERz|m(Cyjtp!4$$@L=@kSY#*bUdPbJ>CvVIG`T%>m#7{ zOvB!f;3jiRDX73{J?Xd?G<3?}S_B&4-1`9}=h4fXAOPyavf672GI;R29t0H`954Mr zJsp?M7tP=?ZT_hTUY-W$0#G{`G+qf0q3+%U(7B1-%pR>LK@qP24X2VIU(o7b_?;JE+~>dcda}oL2Z-6eJ-dGN55b zP@3UysfU>OR8x=vw!R5&ZZa#xTu|c)WNtRhT+lc|iz{f{r+c>mIKof71~qk?K^$;Q zgCckXB!a*3w`74Wv0VY_O;Ajk zs%i)_yh!-=|9?lTAgKM(%X*6mbXt!K=wj5}pvwQh=*w52^vvvWycxs?`4Ac};8>|+ z2HlTsx=vk?;k7s-{lLR94s_{Jw>`Au{OyG@)IGl4=HUHocFgeNg}+q{WLW3-7kp1a zJ4p|K&$zGx4P9?z0A;qrAg_1+|1TN_a#F9Ur8@jvr|+QE@CRhT$~J&{iU`;8Z)*m% zv|0{yxTwhUw{(Fsa5Jdxg9^3ggEVwc2CZxM>0S)V?mpdHK~pq7mXG*b_JOXv?z{$G zQWc_N@InyMQUE({5l9Yv(QW7T7cV}32OTU9O7#XWZi6}BJv)!QxbX4&f6v|;7Df;4 z8WjW3#bFj8H4gtpr@aLA^bde{`Sw^VNRrW`*EUQYKCtub#m6de3JPu5YXKVTDbe!i zHMLO()lJV|u>J(Mn)qA(^Dr>*Zwp{+xm3c{P{Gc?-*O5p(hM4=hJ!bfq1q}dr z_KL81fX)cJ#@})jR9IOa=Wp7>12@3K@;ZNCC8$!<{O$<8-oc~sKWO};e&vg2IsgBA zb{>B{e*!exJ-Wg5?Tfu-AYb#ho(GL8LT;6Cg z|G(T0K0OayUaw#*VRLN$5m2%mVeoR0LA|V}sK0AC9H{QtjU?`u$9#NYaw9dwc~tV!|Bp@O-j8d`~!6nQl71+6yuUz*Xd8&r2Q z@V9OQ#qHi09|i_4{uV}V1_sE86c>NX7jWs&YQe(5P|pRKMtKMlYCTZG<YINl1%Zy*ow%QH0WZS(=nrKKYp z7A?FGYZ*&|5EUo5j)K7B%sIYnTnldPX%4pE7&G~OZC*>UnUGLcX`73Br1ymt2l<+p}233O${4Kt0 zpd@B{_ZP^mdqGWC(BX9FehD&^h(a5-9=)vlK+>)MJ@{RIcyzadGN(_k>`JiUff6au zqIglC?%AMrkVh};#9yG6`E4J5mm84sxO+1wae4HzmVz=|3WqsbS4JaB#-XR zpbX{FJr&gV^XLX^>h`F(__FySBP8HWR6tk6cy9XtA3X5v*?HUpw6B##<%MYGzyH3i z|9v}u^0!DbGcdSzKKJarsHLY@J9|2CxG=h18XUj`C{ z-(JLDhxO$^EpGl+E6~xVMxXv8-A`(G@})2INW;SCpl0VY@T~Fo7s)TcdHp3nxMct< z!a-e4W1n8r!=S+60i81roiCO1?6!wa;eC72o(~QSa6g&9bt9-+ykZY ztsS5SRIhE4EXb)ZwqFA`7ChJJ(B!fB~-Qa@VqkAf-PWI@ASkii+B*3GWN5G@kwn719n@4Zp{}-+Z8^O(! z)L;Mqzm)h0$~Mrt&KhL?ad15b%2zKXe*XXO+xg|C8t9zaUfYx4Kz{M}D%|C-x53tu zwjSW`TLRkDtqpMysC@xmmCE1W3li=%od;^-biOw{;J6>uzk1Q;`|tnDg&#ooy)@UT zC@_>{di0vE1X}^h3g4fD(lj{j)PTaESG4A8nf=93EXLgX!KuXU%2L<1s z|BjuTXE^MNO~ie`zE~ zuss8r%HINN8#{I$f4S^ED5`DGOMz;dZ-xh6EWPsg|I1C^K~)TR-rl2|88U|kT^`8~ zzC+xndk1J_(YN)#Pp@i*v>-#N;A>q-K7h7sO2Fw7bU`I(Q~CQGF)dW!Xo?M|6Y&%4L>=`g~5{p9+w}i$(H7yXYx9msM<(A^*4_R_2G z|M$g$2Mqqdxbzs@c;au}|Ly<(mwQ1?hu#{M_}BA6N8mVi9{1^d{Nmrn|Nmbe1o^QW zbjIXk*UmpLUTysU-vg3r^FXPp#StXmE4ooakl|%Cm~HAO1IokT^ezcnOfk*t-~X2z zK@*|9rb!4nDUe(hNNy@bP6r&0V0~b@IFMZB+yDPxe0lU8e9d&L%-{e2Uz&r4p6te2T7t9;viSvfAQlo zRQxY!7^&Cvoft&?0ZjY?SX}fTNW9ndJc#N%{vz=yX!s)mRI+uqLe`NV;BWcL3hM%h zl!!I#1xYiO@On1>`Tw7RfxoW@G>p;>TCvml43wuj9R)mkS+AW3MOY%oamNbCkojD2 zpRltP)De7f!V-Ke_*x019JS>UI7hXDW;k38-+J^;_3#Ba6cC6d!<; zID*E!o4~e$s+;Bt&|08$5Wo3=0cZne^BWHE;S7e~0>Bf*9?hrzdvxCS>2=U}!8h&S z|HwFyuz*K*fq_TsH~v=8a4fWa!QXoKHK^+Z4r>0E3$H;-%D|kLM?ey=7y=y*;nNL| zAyBx0Rq?k>U;%a8pYXSUX4Jt&&^^$Jn%Qr@gRZ)4y#TsLxz}{FIFiqIJp_4+zjX`v z)PGPQzF2Mn3Lsbr7@mX%fWm!{N^k&Nf!PDPXdCPwu-6Y5y!-HIE^k!m2*_*Cktgc7`53v7f>Z-NDAz3*>uJBGg%wBXsJ8=N4)&2nh) zcyu}jc=WQeZUrr!Sc;Ttp-CCC-5L?I6JJ4M_8YijEKvynO}HL+Q3(K5x-a;)F@UbY z;BPq%3b^hNl>qQDe&1gl1xF=+>wd5{(?z0431THwZZW8Q@6J&X@aTN+*3g%U%0JeV0g(0I>xp64Tod%$^Tj4JLeTZnF5qQ zpc%#ka!2BeN#H9Fu7X-l&2KnBDGM~g0$Kgijg?>+fl zzr46&{O>0S=HycT7#i-+L>(9#I-VjA#m1_~gq4Y;-E+g-}w+qoRH5C*(r!>98( zXq0^29dH}Tv$Gv^RV{zZH3864ouiie>^*{d30X;FFN5Oc&LZJ#hn{;3bu`cZ#US*zMb14 zwzY#y@a)_VG69?qU%l|T`~AOXcRNVLlM#HcuLt7`&rYzwF$MeDd?VsUq zeFJJLb%O^G9FMny%m$5NfCiVD`CGX-7#LPEFt9N2x3CL=2C_ltDff1RHf;FzwnMj? zOcsD8zzui5|8M@m%-?!TkO8zR$l-W1Xxt1mV!0DU^s+QC9$;(+X?N^i57O?~-3}Uw zb?ofd@MU0V{0rLQ;@G)f!Iy!7fBvD?10^h$C+gT8JNv<^8uo+t`jl|AT;gwC3>wG% z$;IEo#l^tT{8NO#MUfNKrP=QSswhi2oByWrx9~v~^&e$tU~udPJK6ROXw_AVH5&ti zE$DVF{uTpB@U(+YdiU)Hhu~{d=sqg6U;~9K$o8$^j?@uG*G_PJIPz};>uxzv%IDay zpC2;PEddJKUfV_eNIvvD4$c+~pri&#dyb%c z{P;|Xum!hCqK6wX*mXrLg z1)xS=uOFjtXFteS(D;FD^5So20_{Eek;30132LGLNab(+0PeFwBACDByC4ID`AE?C`VLSsVeFm&N@1lujlV#tiogFBXshuzM2JlXWgXA%^&qP~I`2F1Pd(;wxp|4IR^ zRblYpcRu9V*$%3NU)Y|6mZAKuXF;nq8^Pv4HlBgjIl`)pZQ!l9rIii4XM$#NN((^K zpaGyn*4++T!_oYLsrd&}d7@*(eo#Y@p)}mFVgE}|)bqD=fYxX9nsNz3^5Op%8rJ{* zJ9cjewGlycV5|8+`Jx}xa%AF%Smb!T9V82iUugV*?0WzT--i9*5an+Lt@Cqi*bkoa z=Wj^?HE%&zARc32^z1IL@M!+`pTC6Z2$(?JEeXXg!%&X?eQpP)`}l;6MqFBgE5i3QqI`6-bsQdr_Vp?2VnK-m zay}NsMqy~i1hFhF&^ zGzRA?@O+OW%yTW%K+VeI2SAMs&rU?pg5sdTf`Pwf1E^8C7`(#5g@K{*7pTF{-(tfI zVng;V8Zd(jz3HHo>(Oi4#0~T84y%9v9ru9u1X_S?c41)X1{c+y-QW@lbTDa)5;JHL zl$*bW6I_gmF8<8M;L&TkoKukDh1j+K|3SN`wy`iUfQOPn!S=8QbU6XIfO?q-zBLWB z{;Bglyx4l_4HgCa)3ba123AnOye*Y{2^!airbFqf%J_ZyKC8vEMk%U%O&;0%Wf6FCEhX%eL!?V|o(X+E1l<6HC>KM8B zTU&pF=3bAzEcy+)j}WwK8dL*o{&0wdIcFW>$A54vgv6fV8C$2bHTcJYIo<=^+tp^-Cr-K>*{H?m6rX?izKw1JV-#~Yz^@8hc&;kSvkKS%j)$Y-2TEGD* zcD{RdzId_jDyRj(2$@d>KgA{kWsMt6*gNtvbUX}*OULKcDuqphlZ@>Tl z53a+XKpN)rcwlKY#_Zq!Zm_DCC%*sxKLJ`m^0(@Nx>L=&LAi^8zeNGW0}Z46e-S16 z50o}rc|k&u>~|bg%rLzC4KaKtJR|%!1r3Zs!-NSO|FC=%!-~j96F}orJ3zZd82DRu zf|}6F{=mYoVK;b+jlXp>c#KMP5j$vT(zKCHkm1FLOW<76+Q9*?!a-^7~pE8>oE;DVH=r&TZZgp5^6l2?w>lASE*+f2${0-2(7nt1TM?!(I^cr7)5^O($|f z6Nlp-Y0!Y=OHkJ}AM8N~Pzkk>3o8FY-sInZaJ>R5o}4QF|9|=8D>NJs8(wPf*gYL|?_TRA{?;3y8nV~c`JW&I_@W!o z`r#a~vJwl&4PFKjPBlB^s%MwP%_ zY5|&$ho<+ISD-SYSG4ynxK`)}X=Z?Go`I>Ei@)VQsK34ioGd`~I;6<<=(X))5oFi} zngMuG5C#f=Xw2}p8iI3_?FT04^6D1>V0r%5XP~8T&HKTVqEOW(3Ld?-58eqt?i+id z23462N@;MLj6gQ=fszaxM7{-dg{dQa*)pi<{t(ipa%BVENcDp60%+%hi;4p*Ie>1* z^ysbNd$A;hfx)q1KUleEXZrz028I%D&(3xOP}fBsl)oVvFX9U*!GY4Hts%&Hpw*P1 zcxed*`IWx~lm?)24QkVPG#_PoIRV`CfRyjeH7Yht&}x~#Wgn<7+G{Gt3@UR$N5j}& z0Cnsk{VdRed+} zjLYy^5WFAa2xv>9jtWSM9lYwl^tDH??Y3Xg%m|u~x*rS*0REPbpoTFd^MEoWtT9mY z2{dZwq9Wi3DGfmp=xX@Hv(uH~<$aJ`ukA8M=mI-XoKJun_W-mGA7UIxi%0WOwwH52 z16H6j)&9R&DFCj(TCRd-GGP@Ms2TY3Fu3#r`ybYcgA82wFn$1!RTP4<40Nnw6R7=& zGFIW)y&g2K;L&**K3s9bL-Vgk=e_@;PYyuGDrSR6scbZ$V-=pA^RbOpd;$f}f-|5B z3o;VHxE<61^ zEf?5-uzepbhe1;x-P)j8hJq8{|AU7~!26J#f$uID9_250cHR{o8 z`|rO1%c<_k7Wg2L?U4tp2O3*0O-a;ZO~poaD4=8b%RF3I9{X`LQ*1! zN3ZEEW^ncA(FwW)L{=Bk1~~iv|NjYw-#{KP1F7#dwPyh<1@(Wx9XLplwG=Yh>-=8; zvJDoLbgu@00-L|}%mOD8u#@<=W%2p+Ch>uGPIA21c@C6~L93>^ zIY9e!G+wGP!PAXH!(T1_7E>lr&k>Qs`CC9onmF)pJNnJVk&&b1CiJdq(1gjZli&Y) zTDOB*Bc7e@pzY`Ut)MYp$7AiFgE$#sxxMe*|NoE{$4jR7pc!~jK)%ff58cjVf(7In zZA5^_yaOdc@OY9(ukGQ#0?>M{!ygowB|^{v59kpj-Jl^mcwMy|q*@qMS4DwUL)DgW zLr*D*Wn^H`)d6kc_>s)t#sSLrkOn)ryW3Lx6XaM>W%Dxh9rAh-cyq`Y;&KtV%Rgx$ zT)qfAy=(j6j{u}40m|){{6MbeZ^=Y$Nx*^!wDt^fi4Q3H8(seW_vvo7uxDWK?d}In znS+lo-3j(5xb^GVy&Y7E?*g?*d^-Po^m=f=sIG>tP68QG=Ynt(-`oHHJ72vt0!{Px zn!f%GZxn-czcA7I_uuh&J819^RHi~(t0>(jpSS=2^KWNNQ)S>t1C>djBUU_*gCibP zs=r+O1|;to0#fk$E;KuWgWG|B%F!1|+rXoA;Dvjj)A}9ww|#}|XM8dJ0BFs|5ueWQ z|3w$?HeqlD)vLci*G7Y`0tM$+k6znPzo8il6q+r*;LvQ{@&=KiP^*!bb3jYbdToC_ z6=2u}ib;?*Ux+sFjaf*|23RD#dX1do%0U)#g3^!}RND>Es7v#H(0zss{H>=zJV=lK zMax#O?>st>^S6kBX6r2&N;Ce8UfqQlrRaSPS}M^DS~maE8eF;99{(i(I~d^Ei;X@I zFQxqd|NrHmZ~yH4f|h#+FhQV z?I5S|x9kI5t0C&K3tT68cD93-JMy>GfyY+vzmV7n8&qqV3R(+M{2J7zfRz8o-9cBW zFnAny2k$gG?hY#584&x;AoCC4^AJ2cLB}Cmhgj^FF}g;KXVh6i5PLe|HC#xgvTC(m(UV6frk zZ%JihV6frhZvmad)ZIM8fq}vD0)PK?CeWHS{+8*?3=E71Ji2#JaA08Q1WPv`VD?D< z@L!Z?CpZlGTbM!hXlFA>1=QS^^O-JgBEfhtPf)>gD>MfU(Nd--Od_5nm53kmz_2ETR~H($DI{G6~xPh;6X1)d_%%> zJ199eS8Fivw{kNvFdTPQ041m6&7j}|=WWnn8aN+1@^9nwNOqR#Jk|W@e)B8GhF`2@ z*}Z`m8h&w>C4<)fTY`&H&`|-s)f~rMm^m202YkPn3`)NHK+UlK|Nqx}bUJHzSbpGd z0iFMK+!<7BFuddhrE30dY@i4OEujF14+GpeFJJ!s|G)VMKYxE1$d2R-%?Chdu`+h} z2y=i^L8r6K%VXfCKX`npMFrFW1nog=xehMpnfP1m7$Fh93*_Hk)A{#6Er}N|)c*b7 z0rK!mThQJ@us@GGL-T<%Bp*0~^TEqLnE5umr5vDS2eSCMGiWG_;ic!_|NmPrm6UjN zgOf?G7h`8PCd!lEe2>39<)CInj2mog^rJ)y8#q$9XtXr&%)$8FM4!-0@r3QI1IrBPfOmP|IjPL zP2uNr9d}j$)dw%9!t`~Qs0jFU-tz6-q5_)h1Ff9tj8Tzz@i`Avy2YqSfD%E;-~az# zZvnZ>r}L%{)E>Cf83v$=Ek;G+_2mgKUhn??zu7{GzxCVS|Np@k$-LYSwhNLzkArg~ zs7brOnE`Z?Pxn?6hL>5OHXb;hKp76JdxEo}?g3vn>5&YM325-c8h@h773)9X;xccIC9 zTxCO;4?b<8n;q0g>9yVc0am^AE~o&VD#vQ!&A;e_Jd=%k2{OZ!Qs_a}cA!eTet5jxjIiWyAWz(K~Dz z7#JYO7Gm5F(fJKDUmXg%FUA9X^DSb(zXT+I`t&XbE%x^4o)0-=Xg}zTA&=fx(78Gu z-TOguKD}$f-T|#Z1Z@`(_%E8X*@VHPm(}I8AcIHiZBN)~0N_Og{4O^@ODOqWet<{f zK$Gzt{4HFd{kj!W4E(L2W~xW$`TwHgn@t!zI}iJ|-sW!sU*Fm5qGI3+K54|G^PX?# zOOMX?0ia2Ag8l~I=Yy(#pYHvj@k*cGwV?R- z=-v;O1BKqpz2LgQf#H~g1cTx2*Hd1CEXXoLUTR|tH=pzy-W-Y-8m{QpdEzDl07K_1&@!BFFBY$Wu3bOM z-!dC4ZTjw;00XFX&j4Pc{{Kb722knq9-JU2P@8;4MU;PWqW0;27kedcdQXSL_66Jn&-fPS7%*@1TwN|3y94n=r)1ceAK;f{v4Z|6kO6Jv2~DJUn_$XMPi4 za0CtgKQ}yJc>BdA&?UQ__g~Bcwd}iNR4P&sQ}rMCTlqi}k&Q<{=@V(Teu78mEpVmk z)A|2}z-~}GyL5?1uj#|LpwPVkf_EoaXd)=EBRqSjsDM_ng7(t?^XR<)B7Q4KmcKRs z|NsAn|6eG8ZFW)7I1WzUpsLC6+b$3V>UV##hlF)sBs&8`Lyd|v1AmJc3+N287Vs&i zp51d)Ku1IPbpAi?q5{4E8+J&F<6e*<9-S^K9-z`J<(P|#6N3k+8V8AiOSl3M%i={X zAE<%=Evd}~cRO3aMtZO^fKKhRJj~y$0~+W>I4Tz6C{QP_&k!`Z4VrKS4f;aU^(|1i zywLskzxhWXe~UV}IaX>1T3(&Oz~5&Np33;k$ltOTRKS7O1j2j)+QrZk4sK1qU<9{n zL1!!Up9LLo*ZKd&RbG%gA-5qqHttbT@nv8DjhFJbazot>%B2l8Dozait#Y7w15h&s z%;<)fAE4r*l@-)U>W%>y*z8{fpv&f-A8!VgFyM^MDgE!iXEP+Du2E6&Z2b2Ew1WxM7}*J8bzbx2_dEB(Lj)9_|M^?iGcz!_ zw!Y<`deEcMMg?Na0mXx_XCca8kUcM!Lz3B1aQ|gLc*TTgH>Bv|Z@CUC)j{QqIw&?8 z_ka@(!tN*mTYp0S3?G?Vzj*3M`+_=b)3~z$fq-9(Zw1 z5^{1JxC`ibtQ~S_FK9g7)$rR(&|)~(*0-=!%0Kns%e&y}3Dll%*rNhIjLaiBMn#3c zRRy{lWseGY0S~Bvmhb?zHUIl`egdsUX+FmELSOCQe^16aDxlRSAR4q#2jsIepdlDY zM`OxC8!rAS2RIJ$w}Rsbv^)T`vHLZ2u<00!1cOKCu@@q?pafPT?AQ&-UC==%TTq;K zm#9d1^xB$$^niz)p4*@)hRoZ6CNM$mRghxNR|1g9>leGgiuqeMfno!C0&#DX#eYy= zCz-!34;!g4$92@(+<9z15Q2;4BaIv5umu= z69bu`x&~Ut*$xg}NO}cb!U!%~z>CTMgDOYxiTW?NK7d^EpT7k(I1Wu+(B{=kJ9s|< zl<7bVB|tufIflQ*kCB1F@Fb`qqv6>37E+9XQqAkV@cFjx8WjofAQAtR!!PWXfXg)S zN-oe@dHmZxL+iqlb&$HyMMdJjX#Gm?safEz#xGD-={)u_61=hiRDi#912duR?d}j2 z3E$2i9-TL#Qo zbkIi4mwK>yJda+}%nzVmD5yw}mjErt{{MoV6O?}VTYrK^i}zW87Ef1!H8_B~hLD-` zR?ylNP{}F4z~34I$_^(RYEjSyZ~u312DL3;e6auj|9CTq2`a(CxdP$`P)X_7 zupiv?#2ygUq=Gyvrxk6zJvPe3g< z)5^yJ3@^591jRw;Vg8mq44_p^kdw`k$`vC}$<=H62i$mjZg}8@kq9Ipe!c$xzavH^ z{^bJDlv;O)ihxJ2sVpoEI(SsTso({($iM&m+jvx7%E2m3pH7%Z+3o-ThZP8*ngii! z{+2zUT!5%OL3a&%^qTH|BLJG32ZaD5rSCeYjv6MySXaBxCOZO6_QaMR%B5%4UX>6zEy z#9(;fg^BRL|Ar@D27>(DYZ?Y_mm-qrF_C}&Uw;1zo~uYglUoInyZ!b5{}&ExzyEg( z358yN(D(*)EFscZXp0Ky+6mBEQ=q0hQ;7|9S2<{vJa`dIX%y(R(H^ie_z^_>t)Qdn zJsOXIOarwvA@?o#BtLU%*rN`)3}TyC?-Vfeg{?TaY6qWRcdQvyvopS418Qf0c5e$9 z9@q(5DB#g;47vf-W3^BBGSI>zkIwrZozp-p!*4I5!$6x7XM=h{C0WgX89llugC;%s z!PinZ?4ARX;BT1QLb>Y(q6*@4fW!T{8J9}vN*u+n}Kv} z4c~e+zKLLDKprZGj8_^S0AC62(fRwu!ByY?do=&x_v}1UW?=Xhw7DKMq6tqKpkv`c zxyz&R2*@Z<+5n#w3NB|2FM+E&@Qu(fzJv3^f5*i_f$qLX>f({Iz@ss_avFQJzjSIk26D86f#~nbYA~Se&MhJMoZ+PSY)hf{t z-y-spV~A(xl~CvvE&D;*{)<|G4C*`x319<{Zf6OfPDPK_1EoeDo!lPB9l<7f9Cw5c zmUwe`G#_B}=ya9?zGhQ=nfU|=*;Bs=nND9vs*8f@OgB4ad>q43cLpQ zdptV5IbMHAn*cg{3}ypJHCPe%>$&juVe7Y&Y>(!835Jps@Bku*hqbv!x0?WHe9RBL zF$ly19U>Cq(dohA(FwjyvfIp~^E&7TLHMna5Kna8_c-nh_Q!uw%lRe@9?iKDj7W!= z9tL{}>Ao(HPJfQqs}SJ_?s~G92zVTK0Gs%_50c(Jj=O{TFRM}SUjd&bwjNXk!)^d{ zmjF$ewH_!b@&Mm+^|qAz^*_XTo3*w_x2r%&x<_{?hexNA0Eh?f{5FBwFP#A#9^Iv& zbK(C73wRudT>0p69CSzxXs6D559n#y|3#zcfes>YmH?f+19r5tL}?%NGO7Qnw)0FF zAmitt&HKzH5^(=4fcOViUc#<>f87BQ_vj4fc!_kskw>??#9^fS-9U-xTS<&hx4Q)B z0E=J`OY@RypKd<^k8U>(pH2?}5Zkx)WQm<;rz3}F^9x3wPIn29ZYK#)f;05!H1a^Z z@6DsL2%6xWAPEk1H#vhxr#r{%1EBO%Dd5owN(glV9^I7!pwd&@qqCgDqq9)p_3;VN z#NpA&UZMi`V+T@vb{28GzKq0&#bY8kr!sgXAM@jDV0iK8G!sMXfeH?f8KA7@z+l@j{r~@6pyQ=?1cK@(*F&Am9*|Y&AO#Gd^53)b zVrZ~O@;#5t+fYNnw?lDwcK-DN6>MjHJCD3zUIyCBzDE{h3+SNN&SNhE4lyx!%xFDO zCj?5dpnQiY@4?Ge8h+4m3=+ z9(3j1F&C8th7K1M1JCYu(4J?|Xp#c-bWIRj!XvpIR8fQ28XnENCF~d&nE0n00BxfX z1C8<|yQr}EWPX5zkBf?hSML<#2Ox>g>!4v^ju*o7KzqVkj5|PcfuMs}TvP;JtZM){ z1hllJ*L2cC@cwX6Yf!-O!P-D{`(Ie;0L!dKv!@aJ`ZtfHVY(P6u{Z#xJTzvP!JouP=3$E0P6B6fQo8i zP-nCA{fl?6K)Hv%6}&DJbdENt9S=If;L39W2Cv>V#uxZO=hXRh{`c$MVhqX(FS^-5 z+rZzyxO{<$0kp`ndN~t=2Wa8n_ZJJ6LreqBavPpByabvYc>m(o1&Ap4K9FA1>KCBi z{`(h~*D*0b?_T3?;RB_t?#ZB3=+XR(oxfcaJbulq2U48OGSP$a1n43G7m#C`_q#AL zF#PAAdH`A|H1A>eQ2-_17SNSU{M*_SAi1KzjsctnJi1qBKypR|XyI*ly8&oO2h>LJ z=xzqJ=s*D^;nBQ10+b0nx|=~K-h(p`NJ0aWD_UNGx>>!ZDGx!>3hJI4as2zgLm5;l zboYR>D7<0F-zorde&Z2PW`-0K;8ROM?PkLR&ATz&&kS{c5qLq%_6(5wK&cV5%*3;M zdIc*3!^>PqzZ0AS5$VOjr}Np16Dz<;1(Zrc9||z|c0TX`9TW~qD+dh^yy#^A_rIY= z#ejjo-3vGyD4xp_spi%zn2cUHP2w~hSHn{VIK})Pb7X=~b z57YXmupr;h1`YC;9pK>!7X}9YZODZnsKf)6lo%llO(Crx{(`pkLZiT=do@y#iJYLI z5z=}d93jXhD0ul0C|yY4i^%OMsnUIL9n)^LDEK0vo4 zf(LZJztCO*Dc;^e#&h020wr&d!;_dm1?Bx0QXHUxs1Owc(9kmnXdQ6IDx{*%8#F)C zydP9+Fz`^5P&!Y3F;xOAziN z5VzNq8?Lhvq%#qm5kcpiy$26mLe#cF)ULk^vn3Ry))X`@+KH?#4x|o6#0DhNYkC&0 zQyQf6H++22qt`SLylM!PZ`L!x@=XOeXMx<0uY8>Xx*Mu_KPdfz!un-D*zw@}2)kP0 z{R^)ZObjn;!7?tOSz}X2xPA6;`#3=3SDm0sBv@WpvVkx81QnO>AkKzX(gJYhH-G;B z|6<{+@BiWVFTmP+CqT!?cB`Yb_dthJf=V%H8wE5k;@iDO#Q-!*o&3(Dm)HF3f6x@V z0E18FUr=)J=w*5B(b*9JYKrypp8WD3R9#zxs%wwVjttOoMV;SYJle#>V9PN5|9}1# zaYhD6Lz%w?w9X4OCG!NT>J23Pm)-@{(uW{JfiwR9{SQit3&2ScbWB*wQwF4l4`_uF z|F&jOV-&Qh5_Imj3EAZD5*0uFfbTi>b&65Jw*i+J}Dl?ki+@; zTjfEGhsGnIkVL6$!SM!N$pvGu(od5%Wiz>L3r~{oV zFU82fuup@DfdPD~{(sS1{b+WBClEW2zxXJ@%;4EwCg9l}#sSJLiVPsjJ$g+)+yPBl zfOeJp^lniBt>yIW-4ALA?NS4!#?Egq!ajf-lYO9F(@??Z9?2&(5BYSy^GLqr!}#gN zVt&v_r%&g9(5MN2OFJV2!;AG2%%Giq|3%~aO&CCt_|hAekMP!K;JKh}&7gMM%U$4_ z2owON#vYk(A<5hH6fBJ#Z_x%N2he)r7Yu*@{x`hz@;Rh%fR=x~rY~=UN*&OUdjG$_ z|93@!>bJvSQ^EN;MnwS8e&PYOcRJ>S>q#EaIgg;l7o9hKI-f)Hun))xa23`7vSxxu zuj$TPF!S~Q{r&$EbfG0md&LrNfBc`n|6gi=%2xP(2#;RVlW_g-|Ni~|@;#_B?=`Ky z4XHJszqt7qoDg0libF$FpwEN>;*CFub$7j{k#KXC|NZ-amkr3Dw?PZzG+-L9_nI)g z1g$N7p*jsTeC5Kx0P4IWuMb1*2t)3g?%g8~>MXrrI0P#H_Q-;IST8P%F*A7d?vVxc z+g==lazLGo7e60?s_Z?opuWP3oZ}DLb25R0KX-fdga+s3#d6`*QsR z(3&HZ`#C`4YcJeE`a17N|5p|51&xy+osR%2H^CS4G{50^G5-n^1OK)wj-3Y%zL0S2 zyy$rFg@8w|=$hLC44nr(z{BFM{M$fRGlnMq{SWH2d(4QAJ&bvt3v@m8-_T&s&Tk%# zZ#Y2h_ue%s;F`EMMnwRndg_Yk}6k9BWZ2 z0JSP!h?jw~Q%Nx>yMaU;JC8tE}f4+d(&@%Qr5@sw|u|{XI=OWI{5GY ziw=B)DhF!C zm_bd{>jyO;Uav=_U!UG3U`IpU2g=-_E=KAccowk&JMy7V=X+=t(fs}QKltpy7yBne zQyIcMr16dBHx@|2=%NA|D^5NJ8ch)J$h-m0=%6KW9*n=6cY}_1fvy+s?gnLekIui2 zonL*r&0V_pw1cMbT2Jz~ya)BZGGkOYK$?3U|2yshPwX+ieq9C|zl1N?`ToLD20XdV z-vR2DbvJ`d?G5Cc@PTTw<1MTp2Y{NH;Cc?!%?B-9=K zb<{@ixyzl0N;g9s1->x(7!#x6rPm7)ApMU@F0Z5#vFSQYnFmN2xC{hcZVerW1bNBu(lG`G@a^@Gwrwx(T+p76%wwSVINrhv zjzy6FdtJUVAQ~tdAVrXd31~z@;1~lV*sVU@so?o_NKQIR&e?A9y}mXBLDpcXkXbikbjOkYg;1KP$|$_w(E0w}XT{QvJiR2F<_pdtf!jY6l3 zip&esKj2o2OzS27mJeVf@BbH#=m0OtaRmn?s6P7t{V%9(BlD6Ev@~<#1ZaEE@RB3s zmb&IQ8YnqL!tj7c@-cYkU{OKN8<#)_@If;JBsIhH2xx89ZBV#p%BX;%0+BD$kcYwwhyElW9V{aHZKWH`oV@Ayn9-RkWI-k9Wdk@JF*T5M9bV3X??WukRwFWv5 zmD)p`(i`x<`4>~!#O5C?{4H$Y<^QbwE#Rr3Oct=43@;&N0YrHL$_Engat)LZwu0J9 z=y&}h=YvcMus$-VVJTec0!WA`%pJnjq<(&gM#1GySw}bFNP4JhX3&2oQ_*PH~ z7XTFj&=juUhL*zLfs@kl7tbbxQaB{wkP|rHQc&j*n!tZv200j#z^8tNCGgc?Bi_GA z{s~Fo0?;J<{>8?hhy*SGO5hBCQ4_esUr-PBJ*Wx-4 zbM02@u2Fg6(s{GnMdgiScM*$gx0irpcb1H6ca@G~?-Z7bC0W-ye;D3yQM_UJgYiP| z6uYqP$6vT~-aB}J-SD0Z;|;}Qh6fGrF^x-nN%557A;w>hy?Yqf^VfXs{BZCTm*EN5ZZ8qWQ;HW1 zKQR7q?A>Fib*$aJ^Bl;UYp&g15{wrVj~Sk0{O8!ahf~)EyJjrkPiSZ-Cnj143G7;9OAX~HkyGu9J8n_kRRT^Ngxb(L0 zZ;bx`zw@HuL68+Lj0atNTg3NWkLiS3;@Vxs0u-=wkTNh4G_n?-r3&$G5%gJaq6NALBv8 zQ!a|HKz{w?-n&IVRzUw6*h(JP?kWMsQ;vpL9Tkr{8lH7!{OZ!Xg*{lrg$aw5F1=g$ z8lNq^0kIO@I`I>~FFyxc=h|JRf^Oxrvkw*FR`Q@)>Ds%6@fy4NVJudHeA&)>^$WyG z*X}9>3~S$tM7hGOH9@zQ_2uK0aBDd*tY!bP`28PZU+)(dp`X!U}vN4DCr^W4ExU|(Ky?fmL!c+{2gFUawZ2amEiGG2A<-NWAa zt+3tI@SQ8;KiAIpjt7r%JNLSnGPrgZ={Oo5ckJ!qQ%anh>)QFx(eRpM?-W5Ei>H4b z8P7Q$JSO1Cc+A!CyCdUum(EX)2hVdl_wv|*QnXv|7Sjap-QNsPf!y!%YTpva&Z}^{ z1t4~x1*N=|=kIyD7`|dW*JJ;SdBEQrZzH&TxjKz`h zn2X^@7si*a-B~;b4{|&9itxE~esu5MW7}%*{)*v6$Ii2GM@cvyJSyPGcopO*R@n{~ z7sHp3!~=5JNly3PJ?54duH1BCd}(;XvGW+%wtud@djy`&y!{Mf+c_7*5AMB7Ok9*S zzql~|aO>Tp_oHT!{J{eto7r4CKNw!ngyX@3{NFEt%7u&HFS>SD@wjlF zFucI{;@}Bx7seAH<4;1%r%7jc`dvCt9X!bP{SwG7P;Ncs*4x5g`{n(ABuiX6&%1Ve zv2b4R-6Hnjna)4M^Dd0XU3*&?FUGdCcAhwRod5d;aQS+j^9LvaA8_q$;q7do z;0f;U7dwA3{`h{$wcE>t^Pu5R7si87zZo84{N&hOBysQ%7vrhkJ$53_jEg&uIrg?N zE)?YO`+m)_^Cagr$L|+G#W} zhiCI42g47HCmg%I-W)u@#`uHLMdeLzkD%!%tG%6{zF*=zWcZ8mlw-Ho36PbC7(-NE z^lmZSed)SZ=g;pKIWHO>1Q(%C4j$xZyx6bnRI~*7o7=pXSAWK1fdXpuZAK82K z7BL=k5teB1==Bkn^w9j^(fos*zwId_0|R)9+(Yw|;s54e9Gw^Vn?Wnenjf+7x4MJ( z2bXZKU^>Rgz~2fwc)gps*XAgw$<%4x_#D)cdvS{K|Njo>fNo~~ZGT?uW&Ho2e_J(E zhjZYIjg0^QcUyNj2OWGb@nQ)?f~CVb_{9teo3+C^% zU^X-VwrZXi8eldH|F&x07cyWrEC04?z83;uHXHx8YW^22U^YAdwrYVFKN$Z1?{MYd z-&QU7;tiP1$-k{y=*0stn~Q&2weX88U^X}ZwrY_VC%|kT{%zHwFZO`hy!_j$#a?Ux zv-$Y9Rg1q^0_v>vR&#W?^22VsKHdx(`U8a}=oWzIKRh&l_jZH2S_fY;fA&}nN&$x_ zc=Q%>bYAd)Y%KRU?!pKPa|Vy*XFohNKlZY4H9z>#{DXtPDFt+T;e#KbRZN|Snh$VT zp77|j5i9x(9RXx8{MP(~qu6o<3xDfO2GBqRJXYs3Fff3F8N99W{tGGa$-fW5sp91e z*tlKiA%q(QA#UgmzQ}O!l`P|_<_Gqj z;VdO#od>|a_Ru`!(ObhV;bD21zv&!kSp63Ve=}(4_uC;B{?-`K7M)8aeEi$kz8zxf zVCQd*1#P+M&6V(EyyBsG*`xD4*ci~o-VeZ+?fkaqZ=VM;=Ql^^y&`|lZWa~Li9Ve- zJi9|!et33SDub>;AAe(=Ln^Qcp=iMFrio91Ufn*VVWi8Vj_VR)(YV)G#m&u$wY z%TFGiHXKFoK_h3N4xcM;)e=BG`RqMBs7mobfSX3Oo9bBzs`#pGX_ zW2cMhzt^Xs;{?q|I6OLUL3F>=YyA#8Kd|!_IKJO|G(S4f{Mi15@|XYrJv)!QbOnz@ zFnDycd3JvA=wt^C`TMZ_@aXhm|KZW=4tBSP=5bHWAB@+VpZ{q7&%xjH7L@p&gJS+% z^Fa>ZZXN^6pFW*D21V~*UIO?192gwCSyUW5T^Ss^T}=LfT=?gu8fc|hHyg-`ZXdQk zoj&Y;UV_&2cV6J%?!)#kt<#77-^)p$(Ocy8I-#Hyd|yG){Tp;Sc;g$;rWR1Cxaqh6 z>T*(0Rx&)mugRhUy4)OGTDqu!i^~^j4WRua$6v(#{rA6HM8&c5NH32-<3n&_In~Ld z!rutKceD&yj>Y;hTqqjy>!qf5ye^U?mv{?@R zW@}~!22d_L%nZ(D{EqzFLK#*tb%^t~UI9&%^p;A16Ab9kdd8!knnzx&`~=!2@yDLO z9dw|AM{ljfPS9u~_!RVaFMcVm#&|0xHK#L?t~mPe96XRVD_89iXL7 z9-1c%|2O~O=sd*VynzwolpmmC{89oTGC#l z!r0-W67b^FZwQa6!$l?V#S%Hip7W`5xV)`ms6nSpGU7B2dJzCZL{fk$iBb+*qzH|o9&tP$TZy2!5Ch}@1+!I3dpnuIuZ{msQ&H;4KjhM zrT?O5ieW=cCDPrs68}Ya!vvf;N~AnGwLLnI{|{vVmG~Z=H=<)<<1QYZ_YH6V7i}#z zVR-!rsXX-P`~fo9qr0BNqnqJ>00*coafIVAY&;D#p93l$ye=vVpq*O^4E!xy!169C4j#QZDi+6G zR6r~8LD!z?fe2v+SHu5~dtlpjB_K;_K>L06fmYMMh^>We=Wba88l?n@d-R&_I0&-q z)r$)?U`fx;W2L#Tk3#B0$L9Y`kV~%{YE*<7_**W6c5;NMh=2~%oXZG0VMDRV1hlg5 zzbHo$G_IKWTih5KAnt*VgMrpT928(M{0}-sF&%CZe@hg2h|(Ig4p+Rvo}om@v)dIk zdtJf_I(4~~1+?|D`9C9nYY|vsjS2^74oZ{}w1t(?Rr0^%_v?<$&zV{e>;N^CT=-od zyq*kRUu66A0BC&i8{}%g7mpc0`?WxGOTD(24}d0=GeJk_Y=ye7p+-fIp~MojxwrK| zDSJbWiatY0k!N=;!+%kSLZ}DT3&9=)HBU-1j=8AlF}zd(SO1`OX|}Bp`(9LnZ93+n zBFxC&$^vqAuWfK28w1GQv0yp=7SJwZkP1*3=`p^%2dcHf?LM%nuvmS0476DhGD*~3 zqoVL4uL5+`Mi2O;8u*|y_#}8^P<$e9>;SLe16ld%|6kDJ3t@&%7Zr_{C7|KeUfb*$ zYz!|N;=lvZ;1i?!|_bo*c0gwWv5ub`Vx!0UoQB}M1&7c0uX|8IR;D%0@fJE)xLyx(~! zI(8puO5gCn%kDBz$;HrchU4`k(7o{C@Es|T!zlQ-$)n7XRDfn;Yg9CjyQqNn?1J{% z+kj>PkGrTifasRn{4JnUPCYyS8NPjCTMr6T7Zr{jAi3i%D&PsGP8Ssk&@>LH&;T7( z(CMP00!_!D2`UbcZUOKW=Ad(5n~#Cc-IN0v3z{+E-*%XPJB!l_j*_R}jaT%+Oua*zuHc)kR5KK+a8lJEaP>&ZM^x)nfES)fD2AbUnYOHJNZe*fPs z>}YwVgxRH2x%E))aDmn`gZgAGpu6k3LsS$%MN16mH1rx3iT@==;CumH6AxN%4LVrC2HHwk23mTG zajHVE+yCYta{TSepd-UN&vlllNPvP4R4O|1ZrL1!%-X#BYmbPsZ04rtqT zjf#Xke?O=o>vmBQX#T;?-|q{$Lo(S##lo@qhd6&fDAju;*QnSu|F`Dv2kmof{%6DA zul^r23+32(kiSEn1vIk_vYgSSyGBLEakqTy>p9*d3 z-}lft@VHF3WHgJ<(` z9#6~T{NVc!d^*9WX59Ss_kZhwlDm%C?i@CtEac1h43sQhJ^`(s?Y!gIEu!Mte1xU9 z>Hoif|Gr;RyrA09(9lo<8UTMKu(F|nfq~!UkY{g*3RCBCAAXld4h+XVo9{4tYTkjk zu@$r=*rS)XKnyh9#JW-pbVSb|W)FV%gPxtoJ9AW2Tn*p)bpG|~E_n;yEbb0=5r69` z@G+W489kf-vX?#tC5HdqIVzy;7^s^0&)>=iYV(2fgR9|xpI+T{q9zQlg+TuGY(CED z+5De}zvb1x|Np(aOa6jQe)|*b*M}~hH7Wuxg~62-sIvsRE=B<~?YiX8|NqT3Dl-51 zTW9|H|G)8HHai1DXNihTy;yHs4m$%w@3af-3=GZx-1&VDfyx|1P!5s!@7Z~df9e6y zqV$(5A&Yz#&?>wa~`U;ft9zyJU5 z{ICm@TRc0@eSg(?q4U$hAFKy|$!lK7K5&EaL-q%o&JW*jI5hm%=65{?itm@-!3$8q z;o#F-qv8Tuq;3OBi58$`;h=Tt>^nip(YN!bPv=AM3A5i{IF*4m{V_vUBZ5mx{?>b- zWtF|A_jW*3|3Ih~0jp->Z`}=2lI)`50xIA^<*Fy+v6p4P|NjT)6>#m=>7ydh>7ydk z>7ydj=>smSKwVRY<1Q+oMN$lg-(Y%Q7J_^azT9Wm>Q$>&xpbZc6$;-U9el;i7@{K6 z%Mx_(0h8uIN6nKj*MYVsGhTRk@)tOO)^^{{aP0i>{gvavAM%c!ANNB;?BFjR7tKSN zhXm9a7&=2#WE?dQx`2kZf69M;B0qX z*o5Jw3aB0jc@*TqslWb%ldcOWwZEJVYT^eKfKGPuXnrFBX$JS!sBpaaTL9jR$tZci zqxpzL^8;<9t#sgY4r*U}cK!o3v0V+{f_ew19Xr2xbYAsnexuf(Gle|mJ@ z^5}f$()s*_>hr&#BWFquv>qr`1T~@sJS=aOIDu}^HU*7hF<$j#y!v`k+JqgT+Im|u~H3><|7)APAj+-2Fjm1Kz6>qHUXrR!-w&;N2lcL6EE!YL5&CG^=%;kdL*9& z`PZ}aKWKf|7st+PP%n5iz5$Ij`*v&ibRI@(RkWypwtyHOaO`dXos8(%y`}@SUNe$N4+jSr`}^AP35q=rz}@Yp!1-tJ$g-V?h;^d1s#j@ z(1YLQg5iM|9#{VUH~iN5{>6RJO=i8OFLr^FyG!RsaOd{@3ymv(L1hRFo8xX4M$p=@ z4{0u)F)9TeDJn%S9VIHo{H>tVfP7m)?a&Ga{+9pX#k>Ds@aKL1?_moXI|Qv&ZFc}w zA-w^Npu1cvKnD#K_;mi;`46-iviTS%s9nk7oBZ#EWZ`#+b@1f~{4Iw;y`t_A6%J4a zR`BWk30n7bACv|pUUc3DMX+P%G5*$dps`5H8Wjcp*2SO(GpLAVD76L$MX3qsNR66Y z$gL+5KD`x;KAn%C=KOr&3^s+oH5*|BXuUJYnHoObH7Xo%*EsG0?d$|Q7qs8d8X9>o z3co?40kq!;mIgcDcV2t3{S3&3{C(`8005n-0S?%Io$p`FeF8~`J3womUw}R7qM`vB zL2Ci;PXZO;7eTd#<`GZE;~tDIDgv*2K>BytoCr?iX}k8faHnto+~q9=)b^UD~JDlY#04_YOq0dnzbxxfG6 zDYG>VREYJus1$(0qXHaf zr{+n==7XHR$v88MOKPM=0?*oxwyL>vIA9qnHVPIfjeDNs@R5Tu7^y=lY z07(jXH2wswOychYwR_;H?Imc425CJ~uj$9_;IIIlmjVi^7nYa){(lKtHwH=H_-~kT)?FRx}__y__w1d{aA8_H{)}jKsy7tAE zi+}$|#=W!yj}n2`HJi%Kg1Zw`h`yM3@$dhacfbJz>Q9?i!u;`~93;aGZa;!ffCilr z=9~NtJRa|(BH`6*WALIS_xpcPB9-v$Jpb|&sD9su|CdG}uOhqB1LVpWVqKYZ;qU)=$B37&K@BNnV)o~IDac>Uyx(-UsnF9hddiW zcRn-lPd}h=s3Sy0oWIo!JlgY@m4Awhia4~~1eMBWAHhYZXXjD=jv&x}0#H}3^Zg6I z5C8vz&j|MEeA)S`^ZSe3}XNh2-=GD`8~L!13KaV#CA}9=L0HCpTDp^`}aR6Rf80P zDw_mo4FNtI&x6UM7h*CfopyqDKXG3LmF=bCFg@V&ldN4-68KwAK*mox!RZ`yc+7rq ze(gL5DxT(m!xE{O{K4OPoP&YEqwxqRHc*O5P=4wLRo&CTDeRZwffrNG{QduO7dW?p z(=Vu2@#r;m1uOUj*}+i&Qd0m{1KPvZHw~0L!R`R(N5*r1|GxxvcR}Tb2iW{R@KA~) z#GMb%{{0V~V02&r@9XV+|6PUh z=Rt728h;cN_WXVSK>^+E1KHB106ID2{R`*2Aa@0pYy;J-_rMBVR5Cm*kCw!MM`%Ge zEPHAm1y!jEAi@D!uY$wDW}^Uu<9=``-3A4sFQ{ySxyz&1lyM^{#X!SpCrE`hIG7z6 ze3)}o96X!=zH0*&SME+LLjucoT0c}e<#lXN|!^Gbb0XmADkso}4rVXe+ z1sd`IA3F!S^#wdgx&kEBYq|m)x!?!{CnN`u#hXB-j65iQJ-5Q+7c8v^lAa8fc3^-?=Np`mZ#(y>Qn3;2SzUps@rZNzV$&Bs}fwWw@nU|?YO=&fLV zA)WsHKez(u&1drI2Iac$5ETn>Cgk6C#;K#l29z_MI(lqC8QiDy<%`aXpj6=4dAyX{ z$1(?$@j#bgc=VbEpk+J(kb|2b+gp6VB|0K3znuE}|799T4wR0-EltQa1#f}B|C`f=92Ph(~V#hll0GGC_}CKNgSP00|Gvi~LQ}tPBjF z`STq(_yt?Qf#=a{`W_t9kbPQ?`$5C3FIrB33Y8L-3{Y{pUG(pN@ID)mV?hUL@V9&b zwJn=#R5Td)TR?m3KqGbqNuWBlMn%K3^XQA@r0@TITi<$i9)Omej4mB(z(r+=DyZVM z0J&Adr}Gskk1BZdRx<4b9lQP_I~CHPR`Bd~W%g}7;MUQi11fPndwCcN1@5oG`0zuX4uo_3e0 zSb&NW@D^D|@FfT!E~t&pfYLs{_zGH%*fKFNpp+xx2SF)_zYlbXB*>)(;N6#?9?{<$ zkWvKPK4%3vr5jp`gn`q*C(u4P&7+=-$3SHW$Lk58`<5U{%cm1mPR~J(7kLqI>$wB0 z09r^_pZNRVaX+XD{qi~J0(a&d6%Nnlzg*z*19I&=q6}FLK4t@wU2K^7TegGB4i^3v zb8zDgQnP`|mLp(wQ^47(;TI!+%ME4*hK66v{4JoCc*8FiNYTaLS^_#oa9fYcR?vX( zwkfa#CbkvY!~osx>(luh*7O6FU!V!101)FA0|Nt?@esxUE!+o-fHuN_8Q)-1piUrI z1k{5BGe8YcFhhtDYK|m~0cu!4nwg*zVGSY8Of}GsKHuZueNzQ5cO%XREJRBhn#ceC ze>wZdf5=+zW6tzk#a!2ykiR)A`Y(H-PI! z)O*k%ZYj5qb%{y@f6G+Rm}sx5%x2W|xaHX2|1Z@*tJr!?H*7?en*oyJ0?C1jGO!Ih zLHjwu*1Y)j4s3ojXaX>TsWccAw4fpm6pzb5+laSK0hgnoe%S^A2L5d)z_mLh24C2K z%w7k%h|yyMs=W#zxk(VYBkNJ+SU_@R-~a!I7DbGZQphv~;&yOH3z8hbeXS|b6cWJJ zF-2t?II%de`*i+t+z-lU9W5%`!To_3e-Hot|FY!w|Nk$X<3RHYrYdNW({uFi|Cj&1 zfmX#qinHbe?9D&8e3D=A_m_jNpWOp4a~SwruY;nc*HmZ~)Zs4{9!4~~O`rY$|8hIn zm;jCrxI;NR;11=2J2Zg1V+z=P{M!O}I;NzJYfx@wqz zTL6E@6qVgz{{;wiOi=+HmdL*?K(J$q3g`%8{%rw59aB{HK@(K$NUh?QQ zE!rr+;B*Wk&Hx&cvheAA4(?!qQUfHq4?f`VXgB!&zFSmRJg(OD)eYOFb ze?e{now*Bc(ZO;txB~|oT7%}SW8n7i;lH3WN|V1L-);n&FZ1a9>j64M>pN(+?7ygV z2xxZjfJf)S5<$p#8mKCH8V{PsItZFR^XNPQo~DP47dwKEx%w0A(fRqm=!IaA*4rMP zM@#rXr#o@_bUyztdI%)i`2y77J)Aaye;d1vZHZ3Hff5a$-ZoIf?lmu{eQIM{BJN{( ztVGD8`8cCT^D)K_xBvXx*jo;iK0U_9V0fUx-nNAK^Wh2n+sYXo8vZHqw@d=3A9qGq z!;>D3#~E0_7ae$ZUMR@{$@_F3D$%b~bZGdeQxfjb@GquR(Y58IXKw&wsf^<>h8V}4 zpf1R3F^7hKTqR}>4gYvc3_!Au9R?r+n-4JZZ!>Uc`Boz5VR_uM^FryW*CHTyg4MHu z)R&fljzv2LI`DvT2gtC)AbI|6Z%ew44apDSpER{XAV?n=`q)qlOt~O~ zH{cq#yF~@GEDF>UEfdQH36fmTf1e-W1m zw#uj5RwmGdfxkr$T(5etH~duMZ%Jfj0A0J@63GhMKndPU%gDe0Jtv^$WQmMN^Dle; z7B0{M106hK9?gds`M3E9fHapr@L~Mq(QB%`PJjWNLhir#oB+0pzeS3bfq{P;)DCGj zh#kV9W7RK#Hp3b)F)+NAfbXAd_^DJP?a}uH@HILpcDxiz0Uh_-<@x?#^RU&{CMyxF- zOP_jl-apnN0#e7|`2V^`udUcx0fwERu{58~XP`?54>#Oq;NN$<^)}?P7?0jPU{n8# z#`}W~X$GI=Wv~Nu00Q{bz3YF$=e6F+6J!7_QaJ=kMxa59mTjQRv3phbtr1{&t;i4R z?=+ug^yoF+kS7RT(lQ0Ey2Qt)^BL&;g2H>CR%7W^k6zQRHK0oB|BE=V5PyprXcF@@ z)9XDGK!daYuY(gQ_}F95-Z1-@OyJHUxZm#4U87>~Uv#gZ2}AQ^M*bFEkR!pv&AlZm z1>k$iUTElnmQM1wg6aOv(* z0o_Glc)$fJ)ZGIf$a`^13DgL+{rtn2;YHpp(3a7|9-!+<_*+0zyr7|O0a!W%`O-TK zRI(p`;d~i%9Pq!+L!kZI(?I%qO|S8TQxy21l^0F7Kr7UrcZ+*8AIBI=u}-f*VQV;=l2zZ@8jc{JZ=_R)O);?^b5F2DPrOUa+T(1lo(%P+tHwgqIl zB-o<&FE)lkT(%OS;oXZrH$gtS@6-9ur?W=I;sp=b09#g&ZyP{zpz{q6^S3Mo^#MR5 zyWmOV<1dbY2Wqh$v;qQMR%fF>}KxtOO z@DgZ20jXHDyRfxc&QB< z1C|FL?riX1wAR~%;pIorA?>|uRKWE{=ld5Ef*}c{;{qr}z4HJq?0IqfIw$~4rFoH3 z)bZ<}u&M!7ZvS6IL3IUPKnl&JU=@(i1SMq9Vmt7;ey@Fcb>+*X2vw{d%Pz6V|L@(LW99?eIYJem(Pz6h3wgyubuUfcAq#tbhWT?4Py^5{HV z>Ix3w|DwyiVBsbG6SOA!Nc~IwU!bd~LR9Qu_JTUUy-QR;!SPZ8$^n(xKAn$V7Jz1@ zyGv9Yc7YZYd2}B4Xg>QNdl5W3?}LNw#d@d+C=Yq|#w)zocNJ73q^MZ< zbf>5UID*bQ2hV|dbV6d+v-v2KXY&!p7p1bG*nM4k2c*yfQdrx5SSG;mzeL5uG2Ex~ zBRFS+(#0dkaG!1;l?>20NC0Sj#Nfqr2hdWs!ye5?6u|jrz6ZGB%-`}JvJ>8l7kvg1QSdkpJ7o6?L`1{mIOGPs|DuOLBFCM<%UnIWs}25(ZUBk&x`0ACM+KzP z0Mza~?hG2WWH{~&I;5E4n6nIMZKW895C^wICHO(*i7WrMS_apa|0QZK9)^QPm>3-S zw_X3{62PcXA`B|VK!u(LOjRl9WD`(ibUp$dB9&{et$rS}{g{_F6!1pfd3|D`@+J&#APsXx>$KOr*?b{@T^ zPEes|U?Ipc>>l0Kp!?6RfU7I91ud6e1ac_&SXqS^Jy$>x57MAedf21abOA*2MG07(f18Vnm?OXI z(GC|CvDYiWne~&43F!JIaR&YtP)p3CyISMF=v^1kQZd`uMFI@}MGt^Dpn4H>5$B1& z|Npm{f>0`+PU{mD>|&Zn?4#3bau5* zXDjHO6xVJ?3y;pN7eGGf>;>QJaJ==wKk$ShgGXm8_#TLER|${ftuGLw8~*+Ok5pQ@ zo-jP%+Ipar!|}Ky$b1IRX7G_@p!-`ryTb%Lx*-P6O1KYW-g#;?Zkrv_OF21?MGDwDPz9V`gCRZT-*RG66Jo z(hYVVxSjj{#kZ3n7yb9?-3uyn{)^^0nJ_>+u@__qXx%dZHeUvhZZOxQS2RZtbg4Mh ztw$MO92EnNtG0p^GkJ7_X-~@|r6+y6{WLtg#e6|K7dC?2-u#BcvD?qWx7&@wqkAhT zBpky%dTsB{2Ycb3C1?=IqZQO3D&1ik$yn! zrC!mqx}eZ}4-ZXfn@L^_6q;{JZ+Uc21*IJDiF5qhm|6~$O1;+R2bKReN+rUO4lomF zq^RXUiLgyf=}UwVNb%eMq9KmZ;^r`a3%G9UZUvbL9?`nVzm1{gK#2o1`MehAmuIkv zDUkpVJ2CNZV{183qU_Om%0|hf*Nd_A$uXuF{%vd^S&v>XrVb}Yu#(cd#~2wryWKRv zDG{`g2XuR&NAnvF-)=t+pKfqAa10Od=(UZXFTmi~?PlTG&F9g1FTn7?i>VfnwBXTe zsyR;pwEacF6Lel5cy6SG)v?*lg3+V%{(sRH2hibs|4ZsUyTcSf%hyyrdTo>EfeM6w zh6i5wT7X*;;DRaA0p#V|B_W=j_dUDA3}C8V;i~14RBJ<3t9f?b_voGr%8x#s|9v{G zUs!s8>eacRyy(%%YKMuYWfG+Gk2#O<^_q`fc%K4|2gSwnx9Wkazix1$^RD1q=_ocwz=FR`^>!gS^qb6qM*aL1$eb15L<<9Ruah_y0w&+L5acyK#XR~VGy-j3}>5xZr42G)A`>Q zG@4psXTtCjbeR`)+y%Eml3lN@)m+d~%|AUl z?-?F=AqqD2_{$l8|Nnnc;Rag82&&8&!1qmfbld+I<+cMi_7JVVU$$TYq}JaHhzL^a z?;1n|)TRUVOBnu(9s!AZ275rZz9Ll*kUfWn2SEF- zI)WKNrRM?ATGP(2o}jfYpkwv8)k_FFM5rl$j5BbUF+0w@e2KcfNuSWkiDWr?Y@>>yu+F8Vnwt*%B_D*&HP-o{Tyw zp51Jqb%7Ngof4j%a#-&d0&f*+MB16@80Q#&80sF+W(x-X&eQPpyF;6Sf#LN|_;@5J zPfNIf`rSugf*Q8qLh8OXv>*F=0@%{mopAM#;Q-KeJ&^VpIGC#iK#o4{%mHeK^0&Hx z+QQD@wlHYX`+w1Xka-}BUxJ#fFK##^(kCb_cy@vhPWZ!X3HbO?e$6%Dp(=h&=n@9aJ>c0Lq_pLt z0y+$>w?zds65!GK%%k%<_^Mn`*PjEl_)p<^R-a5w8N5|M=KpH@oa`x6Sb%Zg3)s)^j#?Ii`dCa3%G-L*-9eEHu^aDCz ztQXWF?*!c^%ky9KhXrWZ`Tc*<_ZHv|IFCp35dpY^AmRL9^bklDC~S}Nx444Bw)410 zx9l1V6NVS}!5QcH|H~eohrwQ323C8UzeNVD@Gj#1%Y91FaN-693#h?$jKB3NsIu(5 z0q!|L^DC?^9vTe3Y00DU4QTokB_V@$to632d;l9*0~)3&0p0sB1)LQ;IwALu?E{St zg3@pXXapL>@#$;OfVbz|Kep-Bk4g zvepgc8dwm4=4X6*RWD8l9SaY-DZQ%5u9aZmZ#luvzyMy1 z=F!dSV-9M{|8A(}U?>p>_3AHpbhDb9gYF{(Ee&8e=FGv+P_4jFBI(gxE%6^DD+!i` z4L>L_G*nwaqz%B*zsx|Iok4k>;h3`pLql}}1AmJQXuDQ-b;SP*9><-*OH__ICop() zM^|`svmOU&0Zm_&f_fmG|6g-Y@aSgU2oeP~y5cMTU+`$XUHTe)D>o$lfc(Sa(Ok>G z0J%@iwdJi(=Z8|c?huuP=3n~!?YbNc44oG|m|au~nh$YwhNvWXbTfe0s{8;QBHQW8 z@NzA9`T#m zxAPxpIZgm*2nW0^szxQ^C8%Wx@sDS79Vlw{fY#A8*J&_#cE>rCaDF??$ltmVR3UcW zXnw%y(fosxzhxmrjRymN%QH}@f~2@SntyQdx3n=bFnl{KSi;DoN z(E2S0&{VmQPv?J7E#~lIw+*QE?E@N!a})r{TQr<*@a(+d+xph0Gf~2$Gc&`d(=ouK z(@_H?Yv9r8XaHhpcyu~ifEWrMosJG5hJ;6_V+4pH;L+)rz~AD?2J)t>hexNQ2gv1~ zoxUF6gcHcdz~I|mRsp_U8^i%QJixQ_tY@) znhR=6Oav{dhRk<(G}nUmia-7R|35|+04=@4%u1cE0xz?F|A(F%af48PO@&tAANFnn`$;NovhgIWI_DJOSgF~|Zulc~oH+LvEx z1Wh-t68tUxptWhuz79;sToo9>GbNDx<9?i8L4E(JJeqjkD1~zPlfGTd#Dcc^Mp!DP_@zN4J;p4yn4rF9sK4rqxZ{g8g z8^OTe>i_Hi|No*B4PpL_;BQF>tuS>>V0`%kv~o=z)4Yc`%mbA&>`dSQXXJ1B3Ysbc zjio#WA90`uvjTL9cndRV1v;ou1XpGMMLD3dpo>2ETke4C$p8tD?$8R4;|>m>HmXNw zK!r!AYlTPW(PIt@j4!<*R|2~*K-`BM=;>J8$KcUi3*M#ya#g2m!Aovb{m9`Zf<-?l zybKulTl_)ApsNETe~T!{B#{3=e)%uzX8;XC7ZrgLb#Pq{^_vb<2JE+1eQ=R`{lBQL zfe8a7rCtCpv;)_p4j#?584UcbJAPnE2hNO`@d&ARKq2YT>6+ou=_>Kk5NwFLV%_bbfgZN&`nT-5mn*N4cDj@OJ^*=%PTtF(6)-9k_ zuiZ5&5gy&F+jLDBKwGDwO9bUX_qg0>eqi9y{KJ616|{u%=tC z7#&pmN11|J0;0kDFNa?IyDYNm?E)I^m&t)Ti^EXRnRUi_ATs z@!68Cj-B^CdRcGZFlGSF11WeOe9q$4?P9?Jsx1Xz=g)v9<$>U`>ygv_WBE4J#ZXkm5i7&L?Z#4ns&j`?! z$Eb-z5VU6$Rp>W3iXisE*8g?OgIcl*uic^j67Ve51@P#=L2XbTd<*J{y>71NVDjkJ z_UL9^t!=_kb`zxFf=BDy(#KGJp3tl2L4Ah9{PLiDT*?i;oDyPRDENS%<~JN3-PIl* z-P$j1gL+Qg&H*b}O8FpX{RD#S1Bpa(|S`mcE-D$Go!7vZDS33B z1)s9vqGIrZn+r66%HP@zIkzXLy989q*z$CP2jqQ0i+w=J47?ahqa?$lm-S?q00XG$ z+Z&@2;n6F)suMKs_rrtV{oH z{H>{=E=0E<_zD`&&KKZKO`wdm?KNl(Hdu)h$d+Ew{x&2NmLp712AkjpY8aOkf-*D5 ze^CZaP}qQ0DRkGU2!LbgMF|HaTJ#}JLs+zwg5CXMn*wwg=pZOCIQUyKKyxd-qRYV2 zP_G*zY+Vbwd8gaW!?*J*G#`M<(H47L}%w}SSGyvznIodoSu5`CP8WS0+Ek7MVN(lek= z(h6q&mWg1`hJt2t|F}bkRYCWYFXNvM8CGrm4%$Q1ZL6ROYSjPdZv}0C0L2CPwpSs~ zZm?cvu+v^hPX}#`aZ!~Qxj{y)ALE}?kO9S~^-h-~<>Mn}_4@7~Mo`ZTM zFOS2^#T5djB_5rJ8Xtj5#T5=E?w~_aTJ*rF!7szJJ1hZ|o_PtV>;@Yn0Wt=(IRLZ+8g#x-x1WJ$cbLZO z=aBIC=rv{X1fAM+{e`0uczPQ={rwMQlqD;lh6zK7jz{P57b<$+|APX;v-5@F$$j9) zR5Iv%$rX(JEufvYFF_+p{PGMgohd3dphYSUug$>W0Xl2$pik$ceIQ4G{4(nW$SNa7fl5z?ygbM_%9lzYQpf+5_G->xM+GA37XoNtNZ=GD`*_n8+0~IbDal6 zi3(_K4Tnds=&cr{bh-o{qpb>mL9G`vP;a=^72FQ;QPJ?|72S-iz67EE1GHI}0Ll&u zV262hGXEDnsRAlCz;4?IW`dh7F`xlBiI)?g{UuO+VhBAh;tS}wh^ro*zYPC-G``VL z0NwFZqQc?X`2cA=uSG>d0klW=MbS@Cyg`SGyJJ*1K+WuyC7|OCdP`IUp!203-PsZz z-NgbvozFcwUpfB2>S1{la*l#Ww=`IX7U)nHXO89<%*{U-%g?(S{%@}4VCHXm#Q>U6 z`On`H2{ICt2o&In;QNaupr#6Bl(Q9dtS>0VOwj)RAGB=n8)!$u|Er*Z=vkmJ0lBQy z7m~ig=V*ZL69V}He&hy!YaXbmYdiw-G*T@C+TT?z@E_8Ss8og|j6Em`BUu?WVSug@ z2kob|Wl&&XC>Qkrtuuu=$nph$i#%i~?!{xR@Bd$d?$?003)S8HEua(KKn3HKmsX%N zdl2_&K|{J!_O%TtKOrUDn@SM(si3&;uo9~Kpyj*aftTMv>kB~oJ$g;~T-g|21n7Y? z7AV8FcK`eT|K)x}K7xm%=F6RsLJQI!0_lf_Pb9>C{+1b_Oop6!>_Kk0>d|>0G~W2O z^zq9w(8`BKt?&OQc(i^4=im}qk4|&grRIhQJdV4A#(Eh%j=O`0YCzX$fb%o#bTprC zM#I~_pkV-zI~=ONQ#+Q!6MCPyZ|ecyPFIE!Q^)2C4yJNF&+Y;N!;_xf0TP~_?>rdK zd1~JC?6r~c&^+a1`KfHHPv<9(&et!zbie=iWh~_YA0G*siAI`3Z2rMq;_cDxDFCVo zQ$g$aK;;#v7<*(0nmz%ojV?Xn*QwAcljUp#tEb(KvRz&_|a z{z6p?bhyS3mQwa(tP&t?E}h?BBxrvB51x8-VPF8yTOJ1W@4uC>dbD0DH3RjsBtc>A zH!bS*Zh+XDO@NK z2Gznx)V}}s=yhTK4=SH0flgE60N)(<872B1z_I)Ra(ohpN4KmhD9`tr$a{cJBs%`W z4iqe%viu;?<|7=22f#bE(9b6V&7nd%FU$<6M@UyNgM8%C_zQAZdyR?*f4eET>AFYd z0wV*%P7_eethYoZz@ziB2WX!CKd67>;L};6BJkN`HRwpeSF4y9JUVaqbpGE7IuFL< z;0q3q=Hn9Jl{fD?T2vk}GB9+^Q2`Ci9cxi}&d9)E@PbYA`+v}x)^?!4>UEX`Z6EwHbdlAH008-TH0ya<>? z;BC8GREog#9u?5p8Q>j%C7@%LKq^2ds=0vlmVrVgL`4t8t^_M@Q7MP0nxj$;VJuOp zfiTvn)Iu0rRO%r5bV2&6Kx`2e5VIcSJJ4>zgO4~onvV+ja9;4?{Nw=@@!>q-!}$uq zJK)3l2*&f^{NclS3)G4cKnQ(+^rS%AIZuFAMT4Y#IDdF_vZ(9;RTLhar}($|sOWlt z?1QlNpls043#pG75&|Gn!6kLKS39{g^HJvuLXXdVVdASiBL#Dnf(1kIPN z1Jz*Npi^_ZV^j>%`14yJVXDc(%)c#0Mcwm}oR%|8q}VpR0`TR}%P zfT~La&{&87I0<=l-t*~v@6mi%;l=C~Obnp*u!m=F4rtx^O^?o7VBh(4zV~cCBJkqY z0VW2pjR!!7nSfRjeDJh-ear$=u+_#9)Q0I+}pNJYSlqGjN8;Gz=1FUSlU=Ez9#Fov9NUdrpye53#t z)rVi0D}olNK-x#p^J6_buYil#6QJPh4wpa*+ZGj2i_7qUS1*f_hb<^f`P*AT%0S`i z(fQl(z)sNYmuKg9(6Qs4?>>91_UPUXN-qB=gKo`jJpeun4Ro-RZ|i@b?rez?PVib2 zP`dzpfWte`@(>}=crE|?1D>5vJ$hkr^B#0P0LXs=AVPu0Q3E=V51hBaQFC12McrOd z)I0}A4T}nD%($o+fE)*oGy!l#fRm2^SU>?R06tVy;l+(5(3nv;?hM-R!T^q-9&oV2 zyA&>+A4;7(8jpY?02Wh@@rS*7Yjh!p#sBkVeENb{`TPHupw+tY@x6WET)V+>-x*Nu zUGXAX{`-HV{Nos&#{d2RDA)LQo_7oh_2|vl0A)|l&NCjJ?|nOudNjZBK#FV^6$`@y zps)sy0djbBil~70TtI^V#lFR$y7E0}R|tnkw}^^QXSKv}X9dt%jvk$xL6PmzIT;l5 zKAqpdq4nayEK9dU4p6Z0%MFEof_fj1! z1?vBSit(2+|Nj5qaSGJg76OfU^h<(@6O{YELE~?T{tyF7f5=4zv~&(un1FT^ce^WC zx(j$fN)gc2-JMrKF$*cpK`aCQeo+6W+Z|MVw1U%8>sz1RWDU>G=Z6-HT^0$UU%LY*4=HS_z3v1nh^BTA|PN1<*;01$E=W}quV7--z!PD{`G!;953kJw? zO>m9}M}-5pSU_&gfdvx4Dj)^Ni^+4K1%iu80=zV+Q2{MtgcSv#6JbEjdIpqcJ#4(t z5PZuy&o^+L&?5Tp|NqzfK<5>U`*!~D>=f$+UFRbH!?V*z?1yKs9|vf(3YPpYAd)}m zz9;15&tm~f{yd=M4>~Sqf@8P1BginvZXfYKj-5VYe_qBTkN-i^zc}a?EFZCdFP-5D z9<;ac#XK=kg2z#RcK!w({LyQAQ;&_oqwx)Bmmf-H3hKQZ9(eIf20C`dqQc+ufQf+t zQhS2xQBaBpx0bcHO*IwlQLb+$@_7A*Bb3I|YO32r$!?g!PykXjO4ae8(hrB?z1 z)sz1=f$GWs(0cMXI4gk8F$C3Eq{{$K?%?r^8PF?b7JOWBvNNrNkz4DhJ`RB|hQ2qh!$UXn}|Nqw;J-fwxK<%JBm;S9UPol0SkMI!p!&nH^HcLN4!>?0am#Cdoic(&@4#0UK>Nv#-C~ZN zAVVFyeZ)W+N%YT4CHP%NV&MEE`tK$GzyJUDf_(N8v^n-goG`BZsuJJ-dp7<71vGy5?N_4kCo^%^)6nts;;Z3KS*LLcG|(5MZ6izW-+tgH;q>ELQi0&EIo z1a8n|W#jdjS-BrttMM~~Cg;ITUU*g(2HhnBx_}NdE9V>dcK!inVEScc(4_?6d?LEW|h@ZP|0kR%B?&*+8BOHkt*G#mv@ zub{IGKr?F45oA!~8#Gu9qCrEzG%m8?4KZ*$9~XF0vj!B;kP#4w3&BM;tPc$?vLU?| zMh3_*B*?X3;RKMIA-wz+6_De>!UZ5<5bwo-$%hR^)2sXM+5i8L`@o=+0w5(I|AQtcK%+-5awah`c=WPPO&5R-PQ3H! z{O;4aMg=rz3OaQRbSf5TtrBRe8FWR50cf=4MGIJ;DKAJLsF@BLT`>T!T>+0;m*|7e zMF6=NUcI?={@`zI0-fC3cm(7@q)dUR-;TSesDR39kQ+f!2cp5TquByJf9u5=@X93q z7SPd}5TCuC3Z7T)HT|C|0NH(j93DQsx<8-(|Nml&5NJ^O{Q=Nv|HoOSI3Pg;G9DvH zA}4}_q$!QWASndvGyRn+z_1GzB(h-Vf`fz~+{A$`T$pQ=>#;jQ&it|wE zc8}g_jTdM7Kpvh9$-FnxCLCj603EyNVR@nS3&^Hc(8YO%2f#&+N9X;|9;-p|=hIuQ z;c=XmnH7>Pd^-PwyaNgX5DoJ13umywtUAe{jn@BRK?iXx;o>3`tlRWl5>nn^1TUoG zZ@mQCi(z=c5IMkJxPUVbf2+iw|No(R=Ea5QkURmpQxiReUkF1rtOZSl^_sfCYyzcQ zb8g5a`*Hr(KCpDP#*4+hpnw8T4I$47fx`%NfUm3J|CbivT`8buB!fqoZ8u?D+ftKSt2}0I$8`Z=L=3|NockK?6J6*g-z9{bwo2@Pdo&`~Ur* zTkc*Muzvsl(gidP0$;u}0klfuEO_$z`#vqu>|662ju-75phkEs=okjboH=4XA5@=# zsxojr20C^Hr3wSpScV6>+d=zuTMzK}y#txp>!M=k+4;w(cROgqD`ymJ-r{cut;=%hJmu4w16nU% zqN30dqGIpU`5fH&=yXvL0qvINaOnt9v3tRy&d2~Vf&(-cA>q^c+^6%@i}z}b;FD?% zx?NO6JQ>3TUV;XM!0v5*Tgn5fr*?oAxbTC{Y3oq|g$BIISQ-qv>3|Vs=me}TL`B1= z^PJ&H$n8cV9Wg2#FU<5o7P+WsfR9&q+zGliz=eNX2xyPO$`P_ z`~<@$R6Bor9B=;tTA&GvgXZHL9-TKmKzHRHF#P7B`LppLXiDH9_@;SK-EJbr$k5#k z3hL$`!u33N*wB7Y*4pUrO~P?C}d zBq^0wcy|8l21l7^=U30(b)Zt#v-8SsMn(pP39bM6TR;~OfvP>wp@*Q!@8hL`j@|n} zaoqa9uC6!yzh`e7Xm`8eNl11pxe7T<6jVO>bUSePSTgWCUG!)^py6Tpq4aI*rBcu4 z*Nml09QPjq`2}2_ffkH-z?XA?+xAbu1<8>JNHPQ;lHs@?6vnSFA^ZnwZFqD(24{os zpdJXgF$5yO18)W|vXvPbJbOWf_D4fdQPlkWhv#w7W;X`U<87c*KSB8(TtI<51Sz0E zo}Dbn$N(-4Ax6JQR%B!_JPB&Rf^r(D2xHV@WN_@B2GZ7gsZOo=6(ckpK%wB-JLMWE zAUz=K;US*CQ2N0KbP^tDG0Y1gmhb;xvVvA8RxlvtFYx&de?c?Do!>$0*LF*QM~6Xm z2~zB}s6Y;%eQ^iWb^uM4UInd|>D~)+jc4y#kky`@N5F9i4qs3_f|g5xHd(Fs0jk^` zJCBt{Izn``{^#!lof8d?%U0}h3CaIH-3&gK4jlaMDDfExiqAQY`w;O7n)qP&|Lwm= z_Z+ZOk)rkkKe*B8(Rc*pDOl_tcH9S!+hdUNeQ>i}Av)AODYmTo=^ab7J!%r zFCKv6viXO@4$#HFF8r<^UTy>3U%d~+eF-|U?}gvLZ~sBIBGNx-C4^_^S5SKCHT95S zV+aN{eLCNJG`>k-K^qN?0F8bgc(IZNIvNT((hW4+2_Fpw%@G8=&}0U+#@@dWWCRU~ zdUPHJsd@iG2qfJ28&n(ew@(GFD+X<4xCtE!Wnlo_VgBMQNRj1X{?;R)%-i|?#h*Xl z{(Ch4cIdpp-wsN$h$_ja^EskQVwGbA-R})vC4cM1cUchc9ViXG0k2Z%Q31IfUOn-* zg04sNXgmV)B0K?s)450I%UvKRf`c8DkE>a~|M##334u<2H2~Fk86Y?H=BPj_&+pL6 z)1&j1XXm>Y+G^0s^Cjqr$O$0z7Jt6|2gk`v&>iZK^a=U;I8 zZG5u=Jq~7o^ReN9 zouK**ytNFpQmOO)iyJ(Q4Bai@b}XoI)prh50e9YqTxxCpev6-?PZW#J0Yq;$G3tuuQ)>PX*~FV*`sp{3#hRJ zS~kiDx{msI3k#^#1ImgX$6HuHO&?It;&=-SsQCjD-XY?^zyP`}1f>22n+!BVg4c=N zfALKkm2U>!YXO=g0{4AELF3u{hB1x*b&CqfZJ=-g34&!o0S1w2F}hG^+Qy zMFketpfCmN0R<{p4=nUS0{pF@otz%MQ&d1^d-V3GOkiPPc+Cqs?#{#V6n{$yXi&n6 zq4XUja3Ot#-pP>UcAsCK0p4D`Uizi=ZHXLc!}MEF-srWxpd-le;?9q6(B2Z$4^U~j zMFo^XkOsLP@PoHwc{CmYr6ovWKMZLqg9Z>l<&nq1N6a3bJtE*JINl zTSP!XkJMa-L?(aBBhXL*=x%FX&`HP8HghK=`G8CYZ8E`aHn{x^F&h-E{H>r1v7mtg zD$ifO19y;ZT~`V)ym# z(D2K+)QW%K6z~!5y-pWwILrO{_w|6!HU@8u=`FnA(D0A5T(q}klEDA}|9e>+n}0a) z&pW`s?ZEdNpdm<<8Maah=-e7;vjC)i1RL}E>Wd{mASE@TeD>@-6WlF(=7TYVN9Wb% z-=K3Q85kH^ZGOf92EpZoeI99d*a=Z~t2l@OOX;S&tcpw>>*qR61`!q8@zm z5@>%-uW6JJ8)z6|uNWHxY}f9Kv;V$i2K|Jv()fjfIqJFJ}My|KIR->jC~LhdX8EJ{U8+jD$4OApU!y{r%hj-Jp8@HTb>_ z$BTRVjW|Ux;l{Zy7dyu{$f!1 z2^qHT{0chQ`x~Sa#PK5e$G873oew~lFB=|l)I8$a`P8xTX~Ta8hLUQ>#=i{-4AA={ z92@^KB!V~(K`o-jzYSo{7YK(T33RXzKd6-J6%_~qZ4ChJ#xOkaqWL>0!WbHA85l~n ze0o_omV<8bed%!=RQvu1*K!`KXq3FOdXQ6!qVrp?|c* z%tQ0ANAqu%az2mdBOH*p1MO7s=&fO8@aPpSI4Z#4)tjTs3_cIgqxm)S3&!u?{yR4R zQQ&Vm&&|N#*!)L{zhx@i?qC6cYd2tl^Mr8dp zAo=g$RZL+0AoqgYA?eX8`T*IWV35LdpcPV$e;JGz7)n7Z-XW_n1gQWWF$^*Trs5B> z3Vx7^E~pvCP&1g2!|&~vZ~tFrKvXoqR0treI1N(a22sIa0yRSpS;b0_3efH1Aa}r2 zs35Co2dQ9!n9<-5HA4wmMJh8Y3s2OI+DilE~=0H?1 zz*N{EtN8!<+y9qU5ETu+P&1s6Ron%sh=8bIfT?gmRqx z;u^9FMv#gchzbS~s2P8dRowsh?f=Uth>8Z73Mu4Ld@o3a1yqGS)C`+Kqyk_zNQD?g z1p`dQKV(}fK`Ordg!tADYK9E5E#V*)mmn$_U@DZ5RhWWQY=Ed}u!Wj&sQ}3x!XOoW z5ETqC6=#uEeEjh3|H~YRiUu2~8F!FfdLE?03!;Jnrh*CC9cw`Ja{3@{aD$Yx}MR6O|s2@fl%88OHz+(9Z1K~ylnRAeEmPz9-208!Ck2{mH^vI=I9 ziaLl22AGOv$SNMb|Mvf73`9kPJk*Rc$SU@OR9Hb&Fu+vYLRK*sq(TCsqCpO72177X zyi|i!eESaeEdxvi8?uT>kcul16%G1OGfa?Gn1fVof~a7Cso>8?vPBf6Vgf`(gC5ij z1!OZmzx($8WgbKY15AY-vWkl!6+RFZ4GvHHRBwzii02(HV_pJFcoi*Rm=yekbcAQeBp zLVQ~YRUv?^A{wOP8bn0{OobS-3QLd*(8YnEvakqhh6=I@agd5hP%~gEjF44)ee>=A z%L0fBhGM807RV|tgH-rIR5ZX;*deRf2vVT~QNd6GH6sRDML$Rd7eqw^Ohp#5id>M2 z7hfRZQ3^F<1+ofnkcwjv6%8;I=a5xsf>bPls9-3An(+o%1v^MZ6GTM=OodP=QoKBU z{q6tD1c(ZTa;O;^$SMwlRM(MK!7Yg2UJpJ`Ia6_g7YLGv& zK~*4wzJ3OKxuF8oT{r+b*t1tO5Ls0SNY(kz|Nq0+Ly0OTB01OuB%l8US-vtGNnQjb z?~jmoZ2pr1ZbBv_oAU0}xBo9SL7r;~--0G|(VZ2pr`D&^5D zIvZKxGLXWDpF#I2H2=v0%P&TjZv)96gv#fD-GL&X29jS03X|qPT43oT$lBdO(oGO) z9kBFSWN8hMbTUMm2`qgLS(+0h?E;Zz0Zac!mVWi}+y9rU5NS5Bv_KM4K%NFkvq7Xe zz|w}u(rZA{&p-VK^{lwS(oV?Iy&&lm5NRH;bT+bd9!Pp6M4AsQJq20XA0*uckrn_; zFGZF%07+*WUAn6MbX@0PDH?njYNO~hg+5{|(;_q;f^dyM18CZHQvTh5I zbTLHQ0xZ1^Sy~b#9RiWI0!yDqmj3np+y9rQ5NRW@^bKU`+aPH%h_nq@`X#dT9+33+ z4`6@Wfu(;TOV0&K-+)LvfTfv1%Uxg{`#O;HPKdNESXu;GIvylF3nFa~mexgGmxcUKmGRqr5Z%q2Q0l3S^5k}njIqT2bMmB zEWH*a{o)-sSOUP(w~?j$K+-26(m`P9C&<$IAn8>QX=kuBYYI}Z1c0QwA=0j3X)$DJ zLy&Y1L|P9l{VEknw-89$7b49KmX<)){rSna|1b3*(gt8@MP%tKAZdPxv<81G>pReW zYocn%N;ZO&ymlj7f>d0A zs9>mrx+4c!g*iyYCWwj#n2JfrDnvmlCO}j$)I-hKfUM&4BT)MvqM`w&VjZ%Iiy##~ zP!;)5GtMBZSPxR61yR8OQ}GH}MK4GN2Si0f9@GpDdmQsA$N9nxTWNLL8)G5<~?9Ohpm0imwkq?SF`hh770~704H< z*&9JBbRa4k(xGOc^xOMED!3pj7+@-@kZs8Ysd(`M?Arzfs2M2zc5jf1V-OV#icl5X zkj>BpsaOJ0(EwAi1z80Ceu7XqqKvXoqR74=F*bGvk2T{SG z4mG0Uea&^?rU_JEGH(1))l><$+2=q{G%JpSU+W02kw)9zY^7h?B7 zbHULXE}aiNT5p#qb_erx7jtx1v%HuCwxQFNq4KClH%P$?(7C_&LCdEkKv%E2sPMdq zc=GLkw~Go7=%7WUHGM)Jh&6p+cfG#%;_q$H{14K4PS|=e@cuf`F}T0MD=EO?h${$J z-23+5@KW<%MsUD^RzC1gIn;az5}3_@8Tnlgf)41^bWs5Z9BP>Hw+Mn~<9t*kJbFV^ z6nsFJf4_h6QD!5PvIZy-MrbQju=!ec<)X8}38IK@&q@H~Dn_?*s+-^GC1%j{z^Dgd9U$T8s#A z@ai>CdUff1;oDuW0rE(z2Pi3QgUq{jTfb1e1DfG{@%lbU9q9Nh&}99~N8q_ChHmQ@ zj9~c>ulIxGyRBb*y#4L}jvXK~)`Fw+Wi4nmiuSENOKVC%L0~^}08Dv6@iU4D&HRv{xsI=x96$K`bZWk2}{+6R4Gn;Kx z_&ts@fL509w}H;~`V2a7NW+m|kVWOS7BpN#R7AS1eLDXg2kmbGMR*n@c3VI$blkTM zls~)YfPI9zMokx#EE|u27D++IkHB&0f$#|E6qtW6W`h%jW2Y+vf2Z&N|NpzKp|>i& z*m(Wh|8GY)N@l$%zxM5avjw-~E{5s<|L*`<>B#SL$g%U->lTk@8x>}c5LT+^DA;SYNXI(>H(SfSI4x;`H2a@_M6!jj>Z!|o1-Zt5Gz3HB` zv*9)Pz0A8 z6|B*qdHrr{n4foD{r11}@Qdav-~Kl|=jawF;cEWHy#o~UC0Cn&DU_%*|H>$lZ~n!y zW6S^l|G`Ou-{nv=s0ss}o3Z=<|Nqg4cWeMtX%k@OF?hW-=rSjlZha&_w}7vP0^QR3 zmcIjhP*HD*ih@VyZ^zE_9-#99uNWTK3CbWoo!|d__E_zi{P#uXHBdS8au=w00cCXo zkIRodI75yoxOD#5b=fybz_GWM(Wmp<_eTd`v2|X^yz~Nma*vA&hfC*=&-`^RDjb>@ zG7o@6U0eTyHc@bZ9LB&e=Ay#k!|!sy@TB1-P^BpFVjVb$_*<4SFfdfQyvV-{$_Fg` z;1$JS?}L`qKu!@dHUXdA&fhu@l(id=fQ&@Scd+%~-PW!DE3&|=Xy3h{ZcnUUd`GZVuxXAql(;g~Zk!!c(zhGWj`;Oo4OIdd``bLL_= z=FH7-%$bMbm@_ZKF=sx8W6t~xpgaN&FVI1@$md}+|7Y^(Wo@f4W+;&=Q)>Rt?9t0A z0pjsA|7Y>&WnEKl%up%@(gj&R()^#%qnC9XNX09|1CaCij6GVvmFRgi+cT7EfHqEi z1)Y)C{D$Ly80Z*zk4|Tf5;2cXZP0Re&{7=mx}_Zr2mk*+yrbd3|NpOFAe}D;@_#Vc zC|}1g&(3e4+w`u2s?Rf_KAlHF0rFpzq1+gB^9tBtU5{ST6=k3`UBW({20on`96p^1 z0v??i5}?CBp~vBZ?r%l1?>U-%!qD*M_ULwy===`4SdGE(z+ve6sQ>&epxrSbt38_E zC_vSwgU-{H^5}L@0L4@a=;j5`b@tK!A3)MAXwR*WiUg=q7XY6C*;%5(^1{!H1?2J) zP0-N?j$teepxqG?{M%0Wfa-3KZf4LqksRG6DlD&;K+FM$6WHX#I~WfB|Nr_kSmZD) zKO&V+&2J<;S}%br=m4Ki78TIxkKp^dIQU!KKy_(1lSk)e(Aldkp!4J%JO6`%xATlo z=h^=O5}+mQ2B0-s&Hvd-qQGZ=gWHAO%wQd?AOk>GbzT9L4iI1SxBLflsyTc>jRN1! zm!KouJerSy&f<@D>3kLo34X_TaG*fa56I+}?O=V(;6Q6VP$Kz1K)|E<0Q;_nJ^%lE z9DKp-X?e6L0wnvtM1{k#+l|HWlHp0iZ;qWWT{|BHICirI{4Y_Fc#+Tm+6;L_q4hxL zp;8S{=(Qdwsp}L`0c#Bq=oV1{84ERW2gC9I|3N1=HXo7byns@)xiB!ebUuHv!2=RI z(xBK`9KyoDzwL!bH}gv#a8(X*|1MBEdc7Oof7k_%l+|#);epqa(~#1aV@N3IY)J)= zR>+yD;G;MBTR`VRgHlrGZ&30n0wpF@k8TGI2(P=E!>2O^bWnPZiUT34jZs2cUK@ zB=v#SgYz*czap!*AVR%2^w4K#P`P2}0lBf&%%j`G0Td3p%@(XB5+0U^N+mqH%Q(PR zKZJ*GvlU~>3z+qw`Wh4;NQ)Xl?m^1W^&I~b1VHXa%FiX*Q2!}=bUQ?V+^YaOofvG< z1xO(UY79W)8;gHEdRce)3ow-MK)u1@(e1VIGUV%qvodC!VP`e7MpAn`XWEczo6p&%y`UJ!W`3UY_(A6}ZKR{O~ z2S|WtfI*%?tRM!JSD=#@LmfjL!AHn|x708kEz0F|2@KAk)&|AQrb zx_MNBLJ3sn#)~Ez{bUO=x3ZH1#&KF1}54aRZwI6hH zWhkgl0Ucq}c@^9%JLe0!)B|*_j{W~&2@lH`paR92!=u|8CWNV`3Y2Y34jeLl>%+76@Y5L2#&3n$|(#C;CgsJs(w(_;RCvz&8It_!?#ls z6sH1?ohQMSreo&^ux_8uOCFuaUe*_Z%s>ebA4`9Za;MN>pUwk5oo5|8KY+A0zu^GY z7Xd+rmpnRuzc|qXIu0i)vXsZeGMi%;xE^pi1d2BB@!;TeyaQDC98Q}6iLY(Upo^HJ zU!FVu4Q;;QdlnM@LLQyq8;Cew%-+I`6!sHdT0db%3Ge1N68jMP&z?co zzk3Oaez1GCgTy-TN58bgaL>!rb5P6&yuH=tGX9=$Q3o=J#` z04R1iP&zRc;PC{|_*IFDj8FF#$cYNwdsIND$G*@2TmKHU=Wgv{W`@=Sokv0K7D!?# zl>%Am+xeoqL`BC3wC4H#3-dY544p?KqCh%+F9!Li2Yfg$@BtM_uONe$FL+?i?L1n#sqrY-m8)K`od6Abg6=`v2ihD0 zPD?Kjp8EEG0%Wu()T8rnFzD*&#y6miG9KM_C@$6jZ({l%CgIWT3$86ZdPUBHwtsk7 z9^!AE$pG48lg}FHDZl{QqpaZB`NX5!8ng!v+$;PaECITA_<-Rh&(33xJ3$TrXEMi7 zk8W$nkpIB~tq0=bOH@I9Ly%4I%`l~&9*svp?t%IEaM}bP>r##)El`RxJPA4_{(#{( zh>@W6`2LO1jK>L5R<;h5{~7lE|NnZ)3l?Zf2Kg6!+%tG1!(UKa$pFc(E-D&^2Yh-{ zRKO<(NPzAUQ}AHC2HvN3=*9awCWh7nCEB{61)QKESPl3P5`Sw9D7KrA7{DD1t_eV` zVhJDS98gwx1>WZW?gd99$e7Zd-L4Fv?Lm^D`(AoW1t6y?`gDGJ(cT17(0RC2)T1|4 z10w#S2r5`21M=#UW(J1dW`@K6|Gz9o-ml@&8|nbn;0V=FdfuZqGy*E90~6fk(HmL- z6%>aFF7@c03K|84ivI^YwDWLjuSf6H1)!@4I>DRtotZ8Ck*f9$vRs&K7&VQiP0N+F3_(lW$R5;LiYK8}z?b-NS zzJkL#M@0d&^-%(}IaYc-sJw_3@aT5s01ZfsfKK@7j8T#BU_9*6dGAGZJt!_p^Fe1K zG{0bM{=vlGz7e$Nt#^$I=m2nVVuI`$_UL@|;!F)lIe*I$kVRmhalBZuo*Cpb4)8TR zV8E=+=@)44_89`xjau+l@imKQOX@6CCLD9nd*2pmSL~ zEL&7SJ7-IefkbYA(y%3DFIedo&tA}8+ix!_4uJery2PWmRKc^iR^dfDL}0Qfc6 zZ>`0PFo-~tNAK1dpq-+JfV-@ou!$IQ^p z47zBdn;F!vRAUBhPJREvU>!5VYY9mI1GEoS;>EoLNC^C5VqkFWW>M)bQITjpP{LvP zfxq<<`0VNDFHSMAFuV)|kHWh!F!*%u0Y|eBbB&4wc(%v`G!FdTr}N*72UVbO;cpcI zZFy^ksADhX1+|1E8ZETiJkFIEgOb>awag4V8jk({|MJ&g&}nI)a@Yf!HhR`FGjt1s z8YP_}DiSYGf)q4Xu-Gz|9&M;#V=UbdI*Jk0tMuvo2TqxwORYLjf!jJS;=$z+XxfK^ zzx5PoUtj0>7g}4H7<{a2R3t!$Z-Jtgzg3-yfdQIMUqO=U3l*r_BS42Kqxcat_>b`8 zpGt@yb3mqdPXUJosL>?>P6+$9LS+5^{{O!dt`Y3e?iPqzMi&){7u%qws3DmGN;qJX zUQ|F$;Q*Neo~Hl>F{UX6U{gQ=jAS<0tNg7lpeY8Z3!q+f1S{un-T3GKf8XBP08sV= z=cMis&_Kz1P!#Sx461qgTR`(hwhbHq|L1Ro3`4l6NbqlCXnw(D!^q$M51giJIbQgI zQ)2+5hczT5_*)-?a#=TMGOP0t=uYw8LdF*>wm?D!bQAzMF@VxC|2Bi>cT671-(Fa5 zgh+$NL7^!hJP`jv0V>fCx(N)Dg&q230D!~g%cUgB@P z{QLiZ(2Zbvn?dK>L-Oy-Q@{WJpRfa@*f?XvxkjMI7&vJ`3gJKC(FgDal?tE+pa!T0 z)$m}v=FxfIqw^I?P3vKJ;Kkf>P*{{`f{Qy)LI#})0V^DHK*!iMqZ|k^0aTwVfa+5X zP+=|O(Rtse^BpvOmF{e=QITN;R~e<75a|)zk~9X}R65PKcZ&*WlPswFpa4pL8lXDr z>n4yp_*+32LxQ4A!GqaFMZ?GP1!%ZigTDnNz7tdff^!Dgz(-|ZE#P^j<{$GwvuUNJ zI~OuAFd)?IgR1!rn%nv@&$D-q3aHx)R_p26>!T9j3cB9r0O+t{(3L6AU+A0wCCE|@ zpH9#vYAr65m& z^l|XFf)Xs)98jP*fI`T^)A9g_qX9Vp06drb|3wD)ya~u1kT2ap_rG<6YFy?V6%CKh zS77tsy-0>sl&wsl!m}GRzuxVmq5!ViJ$g;JJT1TSx4s5lA_xsLa1-p_3!Vd@04Xs5 z9kQeW$+0b1u35vfJbkziSbRV3Np1=4D&hKE&{NQ`rUhm#957e5N z4{BlD-}CK1*ahGQ2iP2}>AM0nD~OuDLDP-k7BSd^DCs=I@W6|$C7^IDF$6mcl+H8Y zeG~rHgP>Zs`A7v)3jp3fM@{dbDSuFUKL$=$r5n3lK=l`Yt0g0-3I6^7s8wSCipT&T zaL)pCj1}nk5(aP|;TNbmZtx-#+@dKFwQaCsU?|bk1+AWcnE|d)9T-6Mq>NAJ{}+!V zAXTLlBLf3CTk*Gm26E7H)!kx{zxZ2KK$SUY!j^%*PAk+MVk-r5r^ws==3B+!n1}bYI*IRslA@>b*c0s9(C+Iw6 zM2>yY_8TP5-vUbPKHzM@^TOyk*hHR}b3qM5So^WtMa7``2WtsS>w!`pkLDT`3kLoc zQ2Sy>L&yLBFAG8EcY~Xl>EH`XYgE9!Oi01-0<_*A)S&PN9X39FM+2y}Z4c710F-1* zL9B+h|NmctZa{jmXxF#@koh~$&hwDc@Ly=~PS7rV&?a^c&?eqF&~wO2R4hCguOX$9 z7L^VT@L*o&ec#ptpwl^9fAdKO z1SO>s8&KKd;nDnqi@&86lp>mcaDv8MT~q=*c4@VFw0-AgWc9EV;BNul7Yk`HDT0$F zsGSEIWBKOW`7-!_iHd`7=a2s-DgoU-Dxjep571Rn7B3i!SU@MrDS%=OG~R8?@Ca0& zgKn1G(QxGd|Cd)l5yk*wfldH~whOzFt>^Ku6ew*?Ypmd4WaMwH1bGc?uTSU8V9*eR zgHPu#P;h{4)Og`v3i4Q7Jb!EDzyJRm_JCv8pmZW=T)R62blcTEQ021Y_y7OkE5Z5Y z85$}$c==mFmtulO8hkom`gHz+7;X3s$>i1=kjYVzCEINoE`fX@0lJ^k6dW3$rZNL~ zocJZE@O+`U1Kha>Wg-R!NWudRH1?WmUSb8EJpi)k#gcvCfgk>s!=NkMzj<^X2Co1B z%{*xC{r2CZ^ZpCL-QWIuH2?VT*?EM&WeG?b^sHfyz2E+O_LitH`gEp3PDp*xx(jTa zCukW&3;1vr(0J5M*kI+0ulv9K_po#YjcS$Jym-Ft8~CCMk8W26k6zPIO96&sEKCfZ zprudV`$3C|K%?TU9?h-{jG$pM*mTL^v#tJWPLt3dViy0UgbU`JuW9Na=N$~O?+?D_>OM77iu8KjfISNV; zIiSP|D*rv0nH_h`I`aR&r{xjI4HDfoDk3i?KY|v%paR^Z^BAZFxBn5S*mMNB9dz6} zxJ&zku_PZ__O@LDt?L07b3UE#UBJEaI}iW=cL8_He}jtE37{Me>LR~590W?C{H?n| zO`94O5k~&j0MK$67ZnAR@>T;{-a77RIQswp%dP+Z{|CnhBn&({?}P2}?9SzQF(DTe z;$YiKbwP!Ng9kG+MB4I5sjd%rj06-GpfQqUaQOpPTvDtH+R55_pj6YxQklO6)Omvh z$xD;}pes^JIea?*gAICd27GRIiLhhmftTW-b!1@gzLW!({<$0;-NHVd4}E&Kf|_X_ znioAl2iJakF>O1z)e-K~sSMHxDpDXLGa%QzxKRLiH)KS_$66U|8-FWkp#iA0OjQF7 zc$8X$iUZCSOblRmdvv;TyaXNZ20rBV{fmU%AX|JomBF(7EuDY=|9=?_TIA)n<=cP9 z9SooVOak3vix^)AwNE@d|ASJuN9T9&5&1^vSQ$K;-&mj|Y7N5!FBU*DEq@DW{10@< z6Z{~k7pgl!Wx)FvyxSpVfJf(H{+7ky8Yr955L^zt_w2m);_nVnU*H|c0EQP0+rEM7 zcW`+Dy43+ZCJny^=fy`*d184Od`!M6=$w-Wo4$drB@HtL9Rd3slmP`CkFm%wyl@Ap zY&`%{+0qSKv&9z0!r18Gj4t_%M%76BW>DTa4d8{UEtN|Nnb* zzIW_A_QE-tg~7M=ZHbg;uZs+$Pv;{@`F;#^K*=FS(DdFHQ2Qfi18BvMj|yny4${wX z>HNUo`Vw3p9kD=df`QKEu8ssvb>8N0JwtkC{)MwJI7YP|aE$ip z7EvkjvTbtGb1jRCc3uts19MdJU z!LeM@WebksR#2%8N^GD+#NXNt8e9S=CDRZ?P*M^#hFSv4jn6$gU%uEC%EA!WdLSNR zHfU8S)U*qr15p~R82DQq;U6RLhbS8?lFF4bI6cs~LSvW|M3&>6I{w!L3!E}O^0dz56^Baj5wOhbNI)4jjxCyn2 zVA>3+B3^+?P;fKxD1Qs6cjMU`3#%et+yxc!o%bNgIRT`y^#Hhjcm__<$H5`@3NqYv zaqGALo|Z>Jr9OB8{fh%@!1aTfKDd5xKE|TM@Iq%R*g>BBE_Z!fzm+6_yvDH>R3v`% z>^uND)fUuUb@>en;oBwBK8%kcg+HV{3@`t0m4eEDaGNG$6?FM_^AQQ80v{Yc;KQ#G z#=iaa|Nrad{PGMhl-GbPef~mw)wlmUKxOgk0}yH_s2FEm0~1{Z;(lBG?LTC#*=(?> z==Jq=Ncs9N6nv0Y;~UUuIjBBFo{EAU`3W{J2voWKFX3@+sImFazz<#z4O+4z;oWOu z1DbTx0F})W;Fc9=^)RSW3%<7U`3tKQQ0t_`u6G8*VQ_8vLL!ER0W=s2n(2WLLv-HY zZ|ws$Xd91!T!S7ip!M$>;6ZRu5nTXU^bhJg26%vmJ)eVGwh16R!Q;}P`)+)omo>fk zod>o)9^AI=JPheDzAl9I&!P3_t!to)({wwSa#7KMu&SHKe_+gHqN3{uV7z-2o|-%dUfRAjmgsK+0TH zM4Epvmc+r@(V+G5yFsG`pwv3++W-GATVXYA<8MgORRStNt-zw-{v4W0gqyOXkLU)XqK-5HC#)=LF-~9IuC7+(7+{9v!w-m2c_YG7yEDh{|_2;0qu(R|=xd4a#_90LP`hvs#U&Uc=jwG1!*um1Mm zBRK{%=F|gr6H0Cb-CO8^d^09!UeKdEmjgV^`X1B)d7)dv1hKeck4JB)07T}6IC!+N zBG_XFM8Kn02ehr=#rYzb>b_l|jwnR-MFw~fuR;ZMsr?KO#v82%IuG-AfJOv8l5f0B z08KB$>$A?oFa1FRu>8Aw%K!h67U_$H%R%c2!X3jr7(YSEH_&WA=U0!$Hz!apdTdbv zRq3EZ5JOZrURd6Q#scUtQlHM}kXTR#i!$-I{sIpO)~IlRia7&NRA?Yag$AhD@kK<% z?G>O@(s>w^nn0s@mVf!%%|YcFxC-t3;i35g92KDZwmdYigU3lh3BDLqDa5E~wBF|L z01asPB)J#4fqclW##Z}KFa6;8e|Ot-NgI-n8Sb2J;5(> zz}?c$!xe6j9XF7+FB^Ec&av}2e+Ov72$U6^Z-Dl^g@9T>B`To(V4s}9Mt4LmH2OLKVH7_U+CAg;a?O2c+2z%6u=_!Pa*kuGr|=8!7;ic<~AB z(uye{kIn$4NXDb!n|ofpSWy5=CXL`^0@dIM*1+Ep4;s;DeC?C`!jti?YwKH=&e!}M zpj&Nylixz`c6MOsJp9rT)PDhaHRAez(4`X1KbT4iK*J~kpa!J`)YNQnTIKKH1eu!r z*aPGgU&g1t$VEGWFt6~_&e%Bb1lgSAm;%?(svoC zEqibYIq6&CMdUST`VN51FhkO}Ggy>~zjYNe0|PwPz}2_`mh}C=r}LvHWLN@JVt!o; zD{#y~gP#%}pk%%Qv~L3~na=@>2o$G@n!DC3BhdiK(JR2ki zPUO{$;5G#`kxTe=K8GaoUoYD8pb3P(;|&u710tnAe<1@NHRJDK0bl=i9~_;YjK{$h z#jh8B;Kl%`blC#Z2bnAb7w%8MGN7TUj)&mM`C9N)go01!Cs2=TsUs+JxOB6CPDbhW zQ9+8h=P!N~fCf}pR9X+Xbh4=McYtmI^hy2zj@IX(v7+nXfu+ua{2g;a)?1^Px1@b*0=l};4wc?knI93%7iF7#=!ET z1e%O_Kt)aRWshVZ6&9al9~BNy#_Jx*A}Zj30EGhMFCWHF9*n1aKMpWcd!nxi5ZzO$1$P-;oND1hoXAlDDCf$07TRJbOb6pfU%+GN4cl2kQ!rfJlJj z2P_AUKF|(M&)!f6s3a(a+o7@t`8z=U3Q(B@m*seo36|yW0G-kbFP}hDojfo6auFf_ z8Z>Tj38L=>E4a$y?*MPSP3BSYNWScu{LO>$nrHHLj~O0}pTN_F?>rb!fn&mh@uEjE zs49lgJRXdvJQ=Tgf=awsFLq`_!@fcZDcn67@4oQQfyr?AFun%2~ zuD!>&v=-9;=sf)T45)qDdHD4aD7^*+3? zuU}8(TgzgA+2f1X}9>9v(aHq5|3i#(>h~`2m?< z>TOm7%|w9~%e**n5n3>ngX$B|C=#Uo0~Th2p4#7C%K<6|z%xY_$OV%Hq~d+x0c`^% zEd*7(pj!rDMbdr#re08X=nhe_0By+AyaBFxD?wsNMbghj-~PLFm#A2{wtnO9*auqt zko*i(_ey~4-do_h*9D{rTrB0NNO(3MVe|l<;R9L70V$Rqz4!ob*gJL}tEhn%N#KhY zIpB(zo+x0BGJu;Ke6D7SJGHL=-5AfI=IdM8G4AeINrHkANZz zsm`9@)13-#*hqjUsNZ|`hNx(~2nV|uJ45BtP(FJmcE>#I^Goe+Os`+Bf;h z%XVm~kFff4g*tsPL59!`8}KfI`op+u{WqIP@U0QlN^}0#vaY zK>YL~A{$gbhp6zh9;i_FNj?Blpa4>!0a4)68KT1TA}I&t{z^W`cpS)n(7e89@;j(M z-*_bFsPOo7zV~5#>cMyvG)4129i#>9WsuX6y*vdR8WlTylh64uo@IRTQXT9!2FHEi z1}o^+%NHN!eESa??*uhgVVOxo9FmzpVGLTk!U0+rj(U%xggAJnwzpYdjDZ2v^941f z89*bH7B6zoL9qudbf*;g>Uj-q&#H+($pL9ADWdcK>5hC*PRhGcVz)m2P!2%gDca)okz#cqx>Cp z%<%gD9!N?D90mLxQ@~b)s7UxG9|cF4Pv?Ih#zP*A7hndx2t=4-0Wt+r`-4q+29-L< z-%-rWz~IOee}_7#-O@V+vP1~9 zBqFh26>1uc?)^g!YO8?K7DUgBgD_eC z4$#Gzp2^327$5j1KlNlh@)ES*9$Y^`%99#UfeUh%XYUkn_u&N#)Qd;?J9dL>?}M4NRt3$#)}hRS^f^tdQnh$Q~+@hD0RG81C|2?Ea0MMEP1yJ=O z@nW(EsCofya_H_+0Tn?=)yoU`1u3B73#ocZo8Z`a^z~VOc?J-D6h!X@)$y-)!TRqY z@wNQ&46dDLUoQo%Tj(`a1i7cz)=FA{;bjVVX3B+u!Ljq~>sGLSaQRvO|Nnp2&Z{r; z!SvIYpcTH3okw4S`i3uNOb2bNfV6)+S}&CtgQg{Q#6bHCn%{6h%J&z^^FcF>hZ#GZ z|9e=zE`1N0_cuHc9ShnD2j16q7@VHb%V*Hdub_p62|m3`z{@Q_mz980GHPRo5nM!f z-UnUHc>px}*SkjrRHVPqsbyjSm8>dY`uz(TF#Y|72$=r=f~OX=-nc~tG_Vbx&va3- zc=5dkA_!`{f(2PrUOa}1fd(zWVxV&B98?f|=PhVOgo}#7i=9xx22cwCA}H`;2~-e# zrw2q(;zd7H@Bl)k#)~SbAn5u}s6`wvQo(|*5uio<;H8zvK=qUds1kB`p)ntnWcgb_ z9tZ8QaZz!25mE#3w-1ErvJY%0U6j`qhj$w3(T@nd7%hq)Tk)D5Qnl1 zUhqO$0xy`MEQuFCtC<);oluPz@1ZP?7f-91Kug{odMAMPU)X{Oe%C|bmJuW9mLm89 zcu=X~fpMWPV!Yg=*VO?u(gkkMgFP7l@?^vd=XqdHwn~ECSO2$ zUT8qY7l1W@3YYE@Pyzl66bc3}JXb=rfhNE})sz89*utmt*^Bf0ATsG-jU_4(AQ=fz z|8FZ)#ur&e15|U(hRPU&WkBU;cZmvk{Sn8D`A;F{f$lf(=q(lS=)C{p^aQXN_{aqi zKe`GM@xfsF{R?j}{r!bAnEwC5x(X6|7B39JEE|;<8ej$};^m+$gBK!DmcR=xC`;l6 z1C*ul;#(!e5RMmbDw!B|fTH^)KWNOPcQR_L!HYi?5T6UY_ylE1ym$d+X}q`xWpTW?QUS>jFMs^`k9=Ma^k@dp&L1AV^&CE( z2SFQxpZ@?~PzTy}3Av!ov2%k5XtR9h1Rq8QhQmnvDLc1=Bs!;q#X;pWO8>C&4d`wl zl%f%I#hKxO=AZSQpqA3KLyn*wE%Od=9O7>Uj~}&u^X&Y_-?9YM4(j{^y7oQ6rSq_7 zZ;napvUP*g$%IbIxq3PQ>u@P0*u7pu#e7(9DH5ug0o zvo}U10%WSj3za62u#buWXmdU2cp}j1Xa~@Al7(k)5BN$s5XS+;fmq_B;sN4-(g;W< zfWH-V%s@BzNU0Q+2=E+{OK%nJ@}_LV5!zs z;Bg8hsrN6gL#03q`=Jg{0Xu-d6TFNIG>m-zg<~xf!^;?Oh3fz=PzAsR7ibfb*bI=_ zr9q&KApsSA_ksZ;Y6B`{BtY}?0w8&g7aCxnl&XNl!S~B*fSg`*4rB&@ivei(9zLEA z3Z{t8Lm-U;ogpd;FSN@b30mTXB9z7PLLAC6c)?o+il#j(pnc&l7@!=`ISwyAmqKJf z73Yg*P!1^dy|@YGfW{|YoGFDEAMoN3l;!ba2bAUTVjYOp`ToT+Fn#~UJP-}8m8L*h z8ZWxQEE|;cmzZtwTi&$06a6(T>=_O ztWmK52g3{h2cRJ8JjCDn23p#JHyB%hLdW5S;7*wAIjAf+(801EFFx;p$!-A4f_8|5 zmwdeVc^@V-4J>1$(g|uSG&Mp(0(=w&_{<3KVB>1gj7~RbsCFTg#qnY~lx6Uuw*=zn zm%Pw)va4bL|Nk%lgUVXPzHk30cyzmiP6*)#-yh`B9c}??fK`2CU@R&9#>7}s5bDvJ zF96!)3!ZrGeE;Hi189ZRkNPrKP(k??#It2kU|=XW^=Lk<0V*hOg2bACFqSDp({4r= z6NBNU)&rHG6XrNTi#0)wHfVdV`3T1@Q27rLo&aSd&1XQ}2U_n4bsv8VXwmmK21dv} zoNr9fCE=hgnlRUPzJC!2aYubwG{~jSARfe}-afs_;3TdO5(Bx^5;{z{uM-sXm0BLn z)fo&W${x+TL8TT$iKIt!bpS(&kVofzP`Y?I?dyNgm3}S^4AJ1q3v@;Wq@4=pBA@pF z3*TT+8x0b^kn>K!?zd%RU;w+{1XNsyfkqa5dea3!qYJ-1I=_PQVOKpQ!1>!iSp_6g z0TKawg}?m`sPusbd;&-uG(XvP4b+4MpHBGVbqB~x{GFh6ydK@IkjUN!($yUbifsOt zl?)6FV8?*UNw8z4gS7YN3V>Qi+z>@k$sdoRsiz1f>u0uAn)CU zkH>N!Lq-Zz{o%aSU{(T2BqXCp$J0ZJYw-kWe2Rxv~>v8}8|JMcGtnm^wtM0zf0Z1)iPfJwcbT-GA{vAGDq9{fqDUkfzf6d{FR)arkup0~HJhz}o{{dUr7F z{r|tj6(sZ)s^ckC$6XM=8&ZEGby$D!w_1TL##n1I!L!#5)Qt52?L%@=5qR;Z6&&WE zg2-|IpFRKozvu!P+4=rO6V!^Td?tq1vtj*N(7}@}?x4PZ^FC15jG@HTv$qXYU4fe= zhoA;4ykMRLGMK*wbniRV3oos}g(@VRdfPzNns4tukly1iDmvVtJO&!`0QXBhyWKcE zKnI0%z5>+_5+1z;|6lZ<2ANs90aWpVYYxzG18Bj5!iz7*z*1A8Es6gxl-oc8{4L)= z`)hJkbQnw5IPPz#`2Qb#4VaFo;Byzi;*n5IVo-4f4^ZZ~|AG;!{vWtj0?kB2#5JMnL6^XR;v>SdcN!>R z`S!MfDpIiS_a2=GLCbtztO4)cga#mg>yN*ny_R(ap1spRrH^kfxR3*n2tD`d{Q065 zten5K3)};H1g%iM_dr|@S`_2iI}OsL16^hJA{N|m=WmSyyHP|1s-X?KQ_>l1%rA(O zUKl_X>w*-6wvd8XXV2<}7!K+rfffiGdG<~N1sqt@{TIKX-3yS*A@K$Y1!bre55UXw z!ock)4p0(5RRhWt_Z>TrzxWTDAL@Mn;(HD>f8;PRxPoq8<^(MzYs07ihsK;Objm# zK$X2o8)%HhfdMo=0$tAsE)u{o)D1bY6EuDeJ`BvD(?`Vyd`4HN4`^Y43&`{q6-X$6 zPe1HtQ3Wle0BsotHKITr1V;Xr^`Io*T%#huz~2Hop3|{=Kd7Ja;`?7_(6M6Rg?*s> zJKsUx43}wG6VRaoY(u{`N_Z6$CLpS&m11zCZH9(KAk5)=aXo>5Mp3q@aVkZ zp$*ww3>tdV@Icz}tl-&O!}iZZ^F3%uP=F`nPtV>;M#p1}j4v!YAS)vq{xUNdUIHx; z?|lDa^FL+=k6!2h;FWUSH7WteTU2^Lqx_&+SM!5M=V1>}e-z|Z3D9M>pvCQey(tEa zAPy++fR^`ZfHWq;+7viOMd>PU`nB=7I%E zK#Au4iv$0dK}+oUTV8?6*Irl9ZXrJYHceIrhR=@t`Je^SFFX#uU`gYD-=YHYk4N(l zM)2vZC4L^wKbSy@TvPufc1a^ z04#3c)A`Dy`OybY%d4KfEF%0ZVW0`|ZCgY@mO2<1c=Q%Af;N!0fv#8O-?l|$A_D`1 zgFykKM{f}mSV9%5VG=}w$)mT387u*s{{!im43S{==q+LaOMC{M5xs4T$P|bKi$`w} zD_8=wo*kqEbbG6VK>@2rZxI_<;viH9sB!CHP{8KVTf`2Q04+xY=>Rp$91IHBJ$j2c zz!ISTAxHvLuR9nNaCr0k=q&>6jx9?C34*S8cQ7d6^5`w% z2B{1H34*eugFykeM{f}iNYEN2I0xcx9*^E4UXY*~NDw@I-h7D9!JvTG)ADMWApf>0 z;6OA8fX>7DGQMeOQ2{xO(O2_5f9oF5GVbmi6$Q}pOAl;`8D}ztx z2W;urm7xT*Z4sP)T~U&+t3Zh!EcLpgBwkmA5)n|^bwx?Kt_Gl;-r$sb&9m1Y5gi*|p4772g{zTUt1oC0e5hJ)Jy5}>qp z2)taZ^Kgj`i1#H0*0*@+_Y>Tig!G4fI$s!GdMORsI|ptz`gA@py!0~QH&_X{-w+S$ zUIz*90(`WWgirSx@Kg+cOB|@G2OZm)3}S!-6Lh{{Zx}b|q^NdZ zkf`Q|){~CMTtU<95r2JqOPTmv!RK?f-gZ3Z%E-ju5(UnkEOOv%zdMD2f#J0jy#8)} z&B)&ZS}6oNIf)&#R0^~X3vyb*|ErD-b_^wVKvJN7v}f}%Mjz`E6^_!KplTMpfEm&( z_vo!)1Xbe-#~2u1R5n1WZ^2|p41<@1zJKuuysQwk4-T|!0u-hK$*?$n9RTZZctD)Z zSRxEQo#oa(&>5wkjQ3tFt_6*JgId*~&0LaaAO|dg%m$5rfCfUq`$RxTs59`l9Aal+fV4+J?KcH*a(of{8QL;E#^17#9kj3# zv11=LDZD2alEJ_q=`vHhUP>`_5$uNL8GuY!i*97DT( zRCGXP5O_dQ;6?eL|No;RU+(~wW4$u+hL=FD`3~}?0mz#QKAksxI%8A>Uc`N6W&pWE z0hGQqe7a*)1VAn_@XqV+z0Ke95>ycNIzt-(KUiQ5 z08slMO9Pgg4FJ&oz2-+BJS;Cm8vvl<4pgCmLJ`^k@UZ;M-{u6Z&XF4cUCTAVK5?fQRMfGEt-k z0Al{wlkqOB9pI^X2hx58o!qAY8u51kbwLW7)Ga!4!}nav}x^`hx_K6ER583M7b}h(Us?AVK6r4C;UhASGgOdl9Ms z<)ir;l72x+R{@lC!G)UCTX17mru9IH6Q~XW4{X4i7|&mve)<3Z%WO!K6m*^)xcvz_ zAptVK0coGsz}ussR#5>cAu>mST0`K?E#SfG%ix86orglT^JfOqZ5}+Ihq;_zN3I{7_l~MCgM*bGiX{ex8v7qB~KqK#zxJUfr_gAWe_pB~pOqSAV@B+#c9vh)bNa~@Pu zyx@lHxNcDbE!*zy0b7bR4yFSVZae~VJyKH<;a~7tDbTQ`aRg|(F%&j-4_gm-yhIly zYZ}4C;Mm&^nj3jN5i~yvA4gOB|NlQYl3q80#C>{eLF4nFv-1+cTMQ~hLHfUj!=g6= zGVTTn4Dh<0-d0ez1?Hdo;6dYZ@B$^r&f}%cpu!O}4+cKUA2f1x3>0(*FH+J$v3tMs z=!;q5Obo4W`CA*ojkEVYy^v#eUKD+01|2WMzwJQh(M}f?pBId;AWa$YY`n>9P^tL{ z+%b6X)A`edf7{a+`fotRtqY=klK{#^2FG1gYCt1%9=#d=LG2h&x#6Sv)W5ex6+9XT z@&HI2w15mmgZtB(EGjSFe*u-pPtrhRU;HhKY@p$c6QDJGHK2h_P@6LXR2_gb&w2nRKp!IssRcJkf;G@2?e-O0~(JLM~ugTY=c$@9+qGE;p1_jp)uHa z99RN29tS!p%)y`lJRS#@fQ`q2raNKP0ayYy9(M#Xt_2>C153ch<3LRq*mxXR0%R$u z%smFt0UnP7OTfnCK<72W#^b;epkXhN4$wwB*m#@=Y&;Hh%@u4s4kQR3j{|MqgN?_5 z1i|BRry&-C$KybP;PJRK5JB*G97qs69tSQ)pyP2Ku<oR4b4Ts5;Z8nf=mF>p!fyRpx6Y_p!j>C^$AoQz2$FV zWMKe}-oF6#B@94`H^8&=y-(*?kLCm5HopQW|NC0L_vpOu+xgCy-|hPgzZZ~nsQ~WG z-hYt?g z-H?+gSOV0QLQkTeme6rGd<8H`F6eI{9cSBC1o|e#Y zH&7rN1n}|8Gx#um1*cD6&F}oJ=RtE>o%ccMG2lhf6Hr=lQ8DmseZs%32fV`8tG7gf zu|(1E0DOGMk$;;AyO9VRDA93V^t3!u`qqc>S+|Rd52$0S!Fa$^^Pp$*OD0Iw>CyS! zlkp??9B^nk$|dg6e4MfK!uK1T7Y@8&Z~nl@zwLl0;{hMb^QDhHdmR`(89#V19&-d8 z?)rg|;{<;zDAjm$e(=zCQ856upENu)4}s2i&0q!9+u%(Kpsf?2?B(FmDe%H8@7sUF z11}4HfG%2Q@BokRZ3bsVd2svchvB7{4Dj}pXXl3WaNaES z162ZskD&nw3q6nC0>+o16Ggz6B)q)$9W-1CX+J=#eHRsjmnVMw|G$F)v`T32_y7N2 z{LTa&vf-mr16njE0h%HLMILCn=;eRV85*7UUz~V^W)V1=UWWbq|G&2t)I@lh{T()b z32INl=O2AKk9st|@n8Y1@I{(Yv;ZF;-~5x&r+bM?0}BHKe+w(91Ku6a;nC~-0oFuv zWMW`wJ;~o%2|jiTyrR|zG*tEdMY%r{_~s%G&+c#z@FwMRFPvjRO+wI~lorr!0-%!? z3j9Hh7_bXl50uvMZ>v!;~&@GU_3UV!Qwyv)I*Te z)_JP=6C?k&OP-7uJS>luK5^ozVKMUVEn#JLX!+0I3fec}(fPqg^M!}zVGrv$DjlHx zGo82i+oywk(-{k1SgxG|N*eI_V1uM_ikV49D5e%6s%rwll3Jpe0&|;RDD^C;DhcDd6^3uD+T9) zWYFaK_cTzi0KCovH2wyO1JFs50-zx@(23v{pz}P0!Py41F#)l@%`w<9$T83ndT#HF zwV(h02VIy1Rs^aaL5!Er|A5K{u=$|;0qShH!a5s`Z!ACwp}R$;07Q3!&Oqu6QL*Us zQE>qUh6bp=0ZP#z4(hEL8jSG%##Qj}Fl1&P)c*xFw_QOesDaL{ZUyygyCIzopL?JZ z1k%|6&BB2We6)B49tH)^e0X-=W4zt#%;aHtg1;Tq-T@U*9H2%vXf)5G^OWJY7dO(t z6ZD`#2+&$gQ2PKfJ6{AI_XN$(gPexcE13oAFJauJ2?`&V&f_n3+y%R-L>c6m)i0SD zT==&=0N=ESG=DD#PLR;~`#JFX!K2qz0KAgNMJ3>cJ*ZF93CiH09oZhuFPK34;h0@i zI2@b*ad`B4Fdk!I1eJ3h{LQC9`^vr@W8rT-$-=d3SCqT3{Qf>I{`G_db~viG(^Avj@cKCE7(Ed zEWtnJAgHAVs-%2+9sYyFI6wlRwuP_e2T#otKD{a&zMV%wf}oBRhz1P=z6b)hw%+o$ z6fuF@b>NX#kPV>TM}$x3TafPzK)n)>;UJC%zdVB{xP9l@`PP@;?fr{9kZxLzu&uZk2!K2Yp6ti6Kmg=VupHU~0g%7J z;us4AVB=FDOX01(cl^z;@hQ*=ZHU(1SN=BG_!Q_A7g&b`JVXQ=p8}oZ0qc-}C1B%I zPaq9@aEAme0m`4CCfZYo1Y&^zsNw}lfX>E3Ss(x#pL!0_fmk2_8=nHL@_}_oKx0YZ z@hQ+5hOiC^NDw?e1zODx>yUs1!Q)e)t@OwX1i<4{pcyjo zJou1V^WYB9WZC5(3e2En?csEcrC?9Di;7F@0nj-ipiSeT8AOlHo1UFFKqEd3*Z=>2 z*#|y*p24$Q+@rUG)1xRcpeJ@^gn?>1&-w()okG z)eU42{HAsADZrq9P;ZHf1E?jm+XX2xeSgst1WHU`^CyCKgm->_vD1Z#!Lji@Xu!L1ShYg8>7BSFa66TBkUu|776N z>(747o!Q`+J1d)GuZtK*M>t!DJ5#R{ONToPD4kn?VwM>c7u`M}r-9B@U_9v9{DZ@z z^ZZU$1_p+-PX1%=Oe`L~{wyvX<;=yFj^7Tj@VD}VYwiR5EiC{3|99ly#-if*?Eq7U zI}3lyr@x>LsRx|-x4W?XOLOXUVf^><9(d^{i;73{aUSrhJog_iod-Ocpa1ae4q^G> z(HX)B+GypWdCrCL;K7&7ohLyh1n4|Q#P}&_r#?6yKRZF<5xltl`->w1pm;0+oyXY8 zqVnRa6L;5?9q7+)Wv$C8wt_|S~$$#c@VUM4}9xT zsD=-0m=w|`W>I+&+yzouIv+GJ3O=Xn`-^}eP`Lm;fwUE3U+aO=9$3FJl*8~F=%|bq z8IZZ1H}`>V0QdNQv-u&vN3SElNAnLs4}Pa(9=%2XJuE-)w|@XB1KlCS2WlDdZ!=MG zFf#UN{>kXr`GLP>FX#{+Pqez-75k%f!oNL8zRcX z-;w}R%VXrs0}dHu&P!?h z?@xMap7QOL;qmCT;r8jg3c4K7&}Gr}Lsm^TQ8m{KYpoK%yRuC(`)y4}$nCAU=%4gv?GiW_Z!~&H(FRlN94?8hD@X`<@#>T+F06sg! zr}I6;pck59-~PY+46=e9B*_I*2I>U7WB{=^L1N$j{{O#&3&aAQuebxWb>QVAkQfh0 z>;{O%3u2uEvG_nN(E79&@e!bU8Dl+`W9KPQ7t*uyD&)M-&fnN-X-F>?)ER@+&uhT- zbL)W;A5dRb-*GRf{|lP>j(`tJxOBeYZ>f1JPNAGo^z29|sA048yazr~adw6y0H>_BPo&VUVPAa(sw z{uWknUH|`uZwRQa|Ng=?!M^A|_FK$!zP`~+G* z3R3aW22_u~1=Zs(L5s7%<0CE*uStN;xDfy?+Tj45QTAf%IdJXm*?AmvrWt6|fdh1U zjsU0?C&AwW+9iTgn}a&B0w6Oud^%-RUOf8>s@wTnr!g`xKsVA? zTn__XuwcNz;K_I_&9PGqT+RFT`msA6b7MAeJm$vA=F@rJvDZbM!?7cb&9TD`RNFgt zxIwD>7vi9Z=&k|P^(-pj3LfGX#^az$A70tJv$%BBGlN}NY!0dMd0{m^E2ucxCJ3(Y z!&&%SzC!AIC;sgrjG+2HgyG-IhoIr+Zb49W@6j3l!=>{OsIK?uE)o3U(ODw!!$b3j zN9Q@mULFPy&13NTel@5MfGE#k@y%=viEqd;sxPj1fa1Hv0(#~*t2Ls&=WhWGl!H#c z09`)>9e;D&3*P6^1MY6PfEGJ?3xLlsfV2Zt13|`t+5r{rKAq1U_kzpU^;S#_FH6zf zlMT`by7cEGxXHoaF%z6tVS1`TdQ`#NNt`)6dV@85dYeHLB;Yo~bI=Uq3l8x0j{L0- z;5DM4>;}GDiqQ+yoOljuzL;5oy#pGC)C4OEhBYPxK&^z%mxLM5rl^IgovhcSALIqijToDZkH>e=1kt?Dd zVGR{zGjc^VBYdHP>_)DLR)iB&ki*Co(TFgI3UV5`BH9o-P(dyuS40y6+7NQ%HgZL@ zAjF_5d5l~U4G3a=h0ik%iz;_8Pb4w&5||& zw5FT|z5VbRRR2@G{U8BpKlrdRI2!RlCS=ZeSRN{WgWP~9iF4cwZanP!1#RylrNEbv zL4Rr>925Q92K#P6CK$RPa``j5+I)U0|EiqtQ?!OQ* z1I?{~N84KuloTPxW4j@CA`Sjt1!-wK0&+CcLM zZBYS@jDe4e|NeseD5#Pyo$l1IM`b?)1H&HAUKf@Bpgybwe=BHRj%(}N5(Sse_b(EU zgX>iOmKmVn>OB4;;4)~O`v7PWPxl@b&=Q#!wh%R*oyYlG3Zcq0FM~#@d_WgI9`Ni1 zT}98L0vgt+01w4#9t73B4lgb{g0^jg$7Mk$g7LR7fwV*23R>kM;nVpP0dad#~e=;brg`aqS6EuUte0G$0Hb z84ft^qEZ5Khv)Z8K8&E%6?&inUQkj2*##a72CcaR34)q^AR08x|03iPC@|jgwt_hI|Peu9R7A@lo)btoV!an0|8J%nX`AM8Ov z)cJjoXCd?ZupMD&^ZOu+pd-HE(OB60K4|wlY{b{c@*RIOY+nuNlrNMKU)cP9Gh{Rt zu_6&PL=76h1?}{3FepHb_`>G*LC1x|mY{$wht2P|L5u<~K>rBb)eao-YwuQC@=OLWdmc3(9W(}7G6D$z(45rE@1XlvA?pK*L2Wv4 ze*iK+c?{Ht2J?Nv17PR8PeHNpzeGhM>woJ3M}C(B0sl)> zG+r2h_D?k*(Lg@02sGXdsoz{dBhH?ke_X&-Tn4B#?e0-o0IC7H_o%FZEFzGBR%~-r zB0v?}?dBhhpm8+*7Es!S%$9p2>b9$(6$A(PTfrm3y`W*w&hIa*^g(qScp)q3O5qn= zwxD1J1yD;pxF!Alg}FX*<(36%_kbqwK~}>1%KWYOL2cZ|BOsSz)@zX7H>hw2o$?KF z8DeRJ4alNi7tl<>Pf%CUY#+E%n*};=A)ui~C4d1`tASTpY4Ep#hAtXvR00`6=Oi(K z&qv~K1s#G28ng*&IZ)!{*;^v>-^21Be_IbD1B1y|kxGNm=(9w^w! zVCyBpCY7c_FSz^#vE>C5MA*;AIz>gH)EzW(q5;`64xZap_5_s)rTRXMk3B8#mZ}>b zczFUengh-)`{4NHG;9Rs(sLhJ&mFMt2}|Nq5ZS5OXTK#YGw@;7MD8Or(y2hf-jw)GL+?Vz&2 zF&JJFI0n7Y_=>Vf0#uoRF6!iX`5sn^BJ2Z|51x?w>^wWafewz07w~94Cg9ndui@L9 z?%>hU^#v~`Bg=r! zoAT`41_|i>AnHXiviyDU#=>o&!1nCj529WKBg=z2YM#B@KoQ{CyB|coFh-W2577^b z56|BHAnJuMvOMUD2G8DYpg8jE-4CK(eAY(tYdl0hCF7fs3-3Ce+ zKAp#3Y)6(;hUf*wxo7Ww5cOg@vOF`Wli9rulqDRy`@pFkatNPeXFrJd5_Cf&ynh5f zYO8lY$fg$;G?*AXdUu13JOW{ZowftQ276--gbnuc0tg!%JX0WSa8PzY*x+EV0kf+M zUKB$Z5ihbJ42Ktq5QfH!2na(U`lX)+69cGrL&|TVjv+g>7#J8rgB?M~{fTxTC{w?%_XEX2=h0GmkKSqx zk6zLDC!vPDP=QMEw}9`q>)j0wk$vij5ZMG_gF|E)gbfanSr9fjMEW3XaELTP*x(Q; zgRsFNk_Bd07raP>Fd|+=Ko|}${2>gD7j6)SzzbUtqk9V2PbnV8BH-o*XjJ?gXyHTS z5l{#~D*MBb@fnZaY6p*A(Og(4z3}#h_?f>|9W-**TOHxiD;jnT=5HOiGzUbwz@t}m zKU|s@E)ANM@aWwQ@`6XNsOoW;0k3^v=7a7x@#x(R@~}s*Xg6HNNw|uopo4FEcY}h* zqgV7JT*Y#@3eYHrYwMFzd5_NfFD`8S4{p|8Wn^Fg9cak#@*`+8;-)=lg{Z0J34X|B zzlH~1OmhWok-q<89z;iLFKFJr^ZpCHO<)sRYeD>8(@2CC(6PLr18-kMgS2+DHlE-I zT}sN~(QEp0`Tzf)`SV%hPVh4ro;1AV+xgOQhXkmfquHVYI>;Jy3e60#iEC6&fauQq zp)WQc23h48rLDe*WEBM%(UR#Ai0S2GmB`To9 zd|#;8f&*nLl9i1vpt*ua`#??ym7R*x3=E+jy{4SY|NnOk0WHcr33iC%4$x2&D8Tif zfvk>-EDiAJHRVGx%FN~4|4^tN&{!WrOU;k}|2vPizKw_~mGbh&5;T;6EjBKNk~ z%iqcdYWU?87K*KH`oj*b6vVzW310TxTd@u%bBI{x3c$P=& z{}OSJUiTO%{}?+1sA=tCdA#(?=fi0eKJ(|Z@e8tl=FfK%0PU;+F%lXqt|R|R zNB+~Uo!20`PPlZ&sO;&?Q900^XTg86yUfO=+nl zMrA{1iOQDlcpjJTaDnb}377785m2}02gv9Ppn0Yboh2$yTsp70bl!C_yyw_?yE8}S zi{d?(&eM(u53;&+o(G-%{{&>?8^d>M_nDxui@Ds@N&RdR(uR)>*&oVh0{_fpl0aEPBc-j@D{*$BO zD@Vm!j)tEc8GpHS-amMp_26kHN5)^B?+x!ecK+si#bTMBjZPx z&T9vcu{tt-?7Zhy8IcF&W-x{O;0uu6K?sNaElz*6tjY z9U!55E{4}!6wiVD?P7S(y?2fbNCD%$&ffAdV>cv|uI!ShTmhL_!Yr-*~3 zT^MgWc7Ap|c$n3N@wRK{X-C7~j^Ge*H9YO!+rtY|1NPA^kYB+pNC?~odGFv+u(u3< z_3i-&Xm^gv0awOTu8gl7JHI*_-gWJK>C&B}vIp+fkB*E#T{^Ej_HHo+nSAg#D@cBW zBjeA``!0s};a-IL4&<$K2amCWXsGufUIY6bq;(72e_(%u{pe_T0_;s!!v_$bih}HN zG<*T|?8O5({mTJT3HBwbe;pNXfurgd$akGLK?%^6@f0Wl9&t3h=xX@W#n?w>gX6`6 zuAOHcJ3l%a-gJZp2`D)qaaFu&c+l8K<$|$~$`(h%k06(yfd<9F(@daXF#KTbqOu1X z5U(yCbTPbd9HO$rMe%y?7DJG$AaQT(qH+Wj8lX^dF+2of-n)3n#qe7194U~hiw6)% zz^!+V#KPA%^WZ@QNdhj$IVvYy4WAi;lLBh|zjicy?b3PH(eSk4U&o7w4Zj{d$K>8S zMHplZD5srq?ELD&c-65J96TrUCdXE}@ z1?znZ*8A5TYOUd2kSTjW$&T@@OXpXxhPR*`W}Kt4#W+Xh3@9Ei9tG?DYk1eacZo9C zy3QDt11^SNK|$lf_|&!YjpM;1td5Ltx^qA-IO=G4!o~2aagNFkN5da3#x5!cz-Dyb zb-W1D=H9!<8fMG|7sI!pFmqx2>)QF;5tK|B|GIS7sB8g6$q`q>^B{VUmP{hNr>F zp?8ifNTJ~gN5fw(#vv*f4xVIkH2lbTqw_MTfY{<{_!(3d-*D-??b`YJ;$cU_)2@b( z+LE1WFK*{=Y=V8Z-hYp@%a%H>$N}q-=9T{)9bY6DsJmuCq2b>~7Iql*>N5ey% zF)CLMo?z;ZQ90qtcnef~cV2YuJnGon!woVJl&cONWd()O9aqDHu8gl-7*87B2W7t< zE{6X>*~qo?sv{_6fdk;Bi{Uxr5|tyyB`Q~3JFi|m?y7jSvqt4a?;i0xd!+k2PaQnO z%6Q$S^U}p*E}Vxz1(M<+7fw)F-+7Mlx(nyE&QmUm$6ORIfeO7HoSz(fx3GdVf{MF6 z2Tw9Fe(L<^V)z;yDW4gyxftH={Lp!;J4WTe#X|=Vuy%&1Y;fwG!UWRd*!crgh$^0N z?L5tRsPl)b;bRx$7?l&8CtMUSfT}Rg1CE^^7%w<-{^ezYJ zrSp(u=K;ryM;#SEIQI7Ng3JK<<^aeKTNqDu9z1x0iSwi4PZ!REj2B%DUw0mIG`tCl z+zl?B7dS7t8vX{k^NM5V0hi7TAm$!N#Se-XK!N4bdD?~Z0BBk6k&B0W_gI6>cD#7F z6IA~7&M^jYT@8=AcHU(C(Rm)!DmmWy@8B`k&JUgETsW^W-st??dG6pzCKttPikCS* zGal}CQMuz{_yUxL9XtOzDxL-T`~;}=_Mr1v=MT;k7mv9pUg#`QIpWB9w6{eJWI^XW zN5-SBhTl7HKwWwP*PCeGuIinkQcyLMjY{0>SHhg=LFfkM&65L_v4aO}L`0%|m!W#YW$c=3R%;YUyq z9Z@{w$a&eZcM1o{$j+bOW}J)47EqAvab&#g+Ih~A@pR`;$BV}td$+KG6uETXaP9nb z@Bpi;;UCA&f4w4Xu7(df!R6KsSH*WOoG%n_xO9Fyc#?_pm}}=9m(ClGif1@aFkW-* zJm#YK!nN}u$S}@>-8Cu)9D7^jKxTHws9XWHyj(lKI5HmW`~VJtgPkwBb5!ma-f-zm zQMm)IW43huxOl*^^O#$2iv~!aOXms4&aWW1aNc!cJmA{-!m;zKtKwZp#S^ZaFB};U zbbfH@{CDsKli@p1spVpL4b%#{c+|D?J*Zj0dDNxz9f$!c6cmrTfSR(eTsx0~^9eu5 zg3g*Te*E?T<8iuZlN5Sd)hzqEWzTEi`oL3!tr!a#I z>ihvpJ~td0uO2+W3aYIf8IK-3$J7aO38+pryxkq6vH_H8I&WM&;MThY5{WJ!C(6XW%)P(GO!1w`_AzeD38NPO5Jm6w@8I<`!4!q;oc>~;HxOf1x z=zW80Z;Jrf`G>$QbZ~Cud%f{F1TC`LOEyD)-c9~4wK94{VqF+2>a$DMok zsDhPu=BOMnyzFXt8kEFNF*!nNlpTz}L7H6+FS;Vq;I|SHruGoo_nt zf+Dnq0c3_N<55uA3rgjTM_oH_I9@#9X!z&gc_zo+DFPq`pisX6%CUD0|2i^W0T-ih z4<2F#MW-XI_lY(mTZnV$L&AvbuPH z@han2P&d!;o1@`JP&4wNi{U#^Yq0Zk=R3#VDUu-7pz^=-H7FLrwZ~&eMo>9o0g`fL z{M=omvIo>AHGJjLdE1fk61d(x=K_wiEsl)04xVLl>3rVJGgrpDpxE933IbzLUD0{R)$k9r@#|># z+416GP!k=Z=@GO6%{~RSWbT6UwsDNg z9#Cc0`PtDBRJDQfh2djRn7jAxu>_g#g4~h=H3WBn8h{5}4S#?*2V4ygg9^oqhat^7 zV~`d{aP!X5@VG1EF~`mupnyGifYrtD97x9zSH@$Yq_hW=JdU^+9(V10;Ar^E(eQ=i z#X~Njmf{EZ-Zfevb3l;*PQ;Fke_a`mUOWb}`GT>F$`;4oJtiO(o$n0qxpuyAG5iK9 zI>60e!=H@*Tnuj-9&%wk25K-}>JCwP(YuEito){9=XV#wkDzub$Tq{5pcD+6HvGZ( z(}nS;Tkjqru)0&sJ}Pe<8SjI71=@@5e>e^4ejd>E8`aPu8IR7Z!QJe;96%SEgU$x~3|d*; z{M6n>^A+O@$Ife=?8Pk2?ktR;6-}%@mS2m#JevR7^S6Ocqw?r=J>b#&;DAqW39|#g zJOgBJnn&|thZmgh{{Q!}{9O8``Ki4}>)X;Cm{UOa#eas5BYF0gsK|ISer^86!QTv8 zpYrVx3xBIFBLhS0r4lLrZOqM&m>fG?8A|w^__w=x(NPz8TlWyS`f5;qMO;Hld;AAe(=Ln^Qa5sD__kQ&5wS7!l6kXWZ)x^f#;hKa`<-h zSXf^4>Ey8}djAr1g;ck2r(oxyZWrM{oi2iZUV=u~yO|w3T^XFZU5x*L0_e}nJOBRw zM~-jE{N*>tP|y&Z2Y98vMz?r(JqyaFItjxAy(WJ_1B{&)9Xqcad@1YL{KTH~W3LHl zV>g3i=ZTIS6`>9v6=BDNFC;oYfL2_JyL7my2sn10==^x_rG(?b7XlqADq0cDx3HjJe&X8m#KO7h92-}essX6w?@=q0%X;U zNAppK7ZYED(%s+EH;&Cu?Lqf0wEkygVCWVGTi5x$^J4QMj^>BBZwnT8>mT9szHO*Y)1O&%od83N{?H8(s=z_%~4CiW+R-XJ~%L0m(yALbO$?)$$SE}gH6f;_rI z89X~hR6r|Dx_v}{cy{`T`~a;-GF7QKJj4Ju9eA@2225%BGN z;L*wD+3lkP+R?1_!=u*=9B>|*r#v+;F#dv>`1vnR6G3~8x_ucOJ9$*PeMJ9&LgmlP zh(G`Tcl$DQa&`OI{^|6w`tx$@AJA4R9+gfm$8H~;Kb<~We_qc0`~QEpsPsC12FFej z6{qeH+ds~oAvS+rJ_QBpb|2e+X`Mb+|6W#t^zv`_(E;(b{=H27`~N>^UxqEH5(%;S z_j2ps|Nmb!D}z^-fXc_6pmoup64J91bSQA=RgcCukUB0#MFBM7(Y}-q_40DitSETO z*`xK_XOGpOdjm8;`;9>}AjiQIHvHfVWsbS1uroOB2bFiOhHsC#sIW2|V^Lu+{N`$S z;FyaF8v|%Y=)bGsrQ!%LtwRcwyUf6ZMwrhu0aI&|z&0WA)7>F7}btr>FZ zXi)*Bb%%~EDxd|O9W5%Lm7x6FSX4S%R6r{^K}N84w5WgYCLMe_kh(7m_5mVb)48){V88Tea|fOhconr>YKDsKPSm#Kpmae-@k-`*NF zhY66&lRTS`IlNGO_W!@Hb+~eaWjMexCSVy(kPKgkD?eC<2P|U&mf;1-2z9s$gJlFd z4|()D$AD!7!TIADGlNI-VMY(j!=+z9C#HZ>2)vl(Z(Yp-z8(aWs*pt4-}3g~|Nk!!{R6dR7#y4bb9nWJXv_HYn!Z@Y&j8vQ-YgB;`|<5K3xDfAkR1m~ z1o^k|o?6AvumW6$?*}gf2V3IH_|I4KkE`L^7uHYy|9|-rwB`#^vc7n^9dwyY^B;)u zE}akfn?U>NJ-gXFI+fA*f8-#R~amZ&K3w>5%F|6Vs}Ld;=tfYndnebo0o zn~ylWnD`i!9^aI{0UcD>U814_$#}gyU%?F<&|s9r!56ZQomV+e^zuL&HqB4%JCAi9 z?{HCJJNQoG;ClgA&G(L-pZK?Z0gbnO>^yhyg~Y*k0> z9mO*IEuw$^|99u#o+9%v&8;&<;@?Y_KhSu4!om;ENT5b5Xe!;2e;Xe-ep(iStm5D9 z!v-qYnEt(-2%2c`=JV*}=)CCBUBd^eMmTSErA{5073CXx(vw z@sNk+aai3U1~Tr^55ohU$C?jtfQ`H0)5!ud4zwcBw_8R9v~AJGwez!gw~q|SFo_>N zz0nFjnm0fa0-l|RJvEQGGJXbag5Yp$e*D9+`8NlD(_+xVDf{ZalF6&<&1!bgeK5!k!_NUW_>Ca2h zt|iCrP=-zc=k5?y(1s#~KQEa<)uULaKz9h=pUx1DKQCE+|Nr03E4ZAW0a>pjTJkXb z^Z&my|Mn17@P0&ve=m3c{{P>#+eF2svslKxJ4NOXDCPZm+4b}PfBx+ue4rCKIR3p{ z4q92ADg&w=LHBHjJBGpQNAQhKpwr1g3vUBJ>vTbDtw2p=)avmCXl?WXpU(f_6x4x1c&yqWQPE zOUE4Wh4r9X7j$MWs8$B87<1`Zqq3WUfdN!2gYHJ?=uz3rz`)SaqOuQEns0MaQRrv^ zZxG^dD_~||0N22pH$VeiM>}&=0>Bk8Xi;>hi%N*EQ*#IrXZ_**x!fZZ7Z+L8gjK2@OeP{$IL zLy!msoudh=^{0S0E`Zwv;F^Dm3TT4`SS9Fu%#Icn&;l-q$|DR6khVwysEsg11$43| zSS9GR%Z?Tm(D{}Sl_y{-Qy?l&g83awRL((cKhMCx08t6r)eE*g1ELbN79DKnC5Xz) zaFw97iJgZasxE>S4tBG7bcbvmDm;BQ?6I)J9} z2&kMuYU@DPBZ2m#7=S~*`3FbmA^s-N7FkfY%%f8nbnUN?D5&iq{KEql*_x+&MI<~l z@51VbkbnRGcV0F;(ERvE=b`2Up!;t`87yyjbeaejy?bc}ZvD70Fzf)eRy1$C>;@Hz z&3|E8=>>n&M9^Wcpd`fP()j{c73T>$rRfDE1MxRmfa)ad%@tX2bH#zdqn8ET%<$-y z-MN6Dp)*7!z(ezcNAoXxk6xBzKD{Oq{B5AKJ3M-w4tR7P1r>J=9tYpDdGy9IdK`Sr z=EL|I+>S)bPX}*-^3%Q2H{C8OAs(IYx^q+lAZ3`x!Dno}BA_(I;Mn;VwCvmQ;9J>V z8PMri3@)6nIxjeOUg*4e@P&k9=hcoF6$Qu6!~ENhfvPwM$Ie5YH$bB`pq9-`0msgR z{M(LqzHWYO4?0fAQS(mgZBV79@1gmLzvbI6&^hS!GCrN(I($_8T{?VJ{9HOhRD3}L zJU%WRF)9u(PDy_I@4~+=M#aGeRA~CR@NWYt;NRw>;s+x9!5e40T~t78ErsVpmq_*o zFd80saZdW%|Cd6bV>7_r=nbH5^a1{s6TiX7=|G!3yTCj|vu73Z`nVM={H^IAZI}34 zEJ01EZLXl+cLy_nix!Bx-9;2M#3%gkr8MZ?!wKLPb}>}LYw&?tE-D7ikC^zkF@QAP zfC=$$chLap)c*JK2zXlnsL#c~;P~wb3xBH=$bbXv zn2;K2Q$fuRdC*~F8m%WwcszP-BiscTUZe!Thb?e*FIr z%1NLGVC|p((502hLf`%$cToY|ipcOX4b)l#?P`0u?a%-Jy)xj&Z07|?>Nxm97FLfr zb{_4#0Zs^@Q~(-~gGBdbjOd0}fiFQjfIWI`MUL<@yx0e|6tsTz&?E{f}K?LZ6#?CjNJyv`6y32rakOXWL+OszX)Fl9) zcl*qz^FO#h(0Cn`f9`OlE63`ir zr3}pvnH)L{_**A|YBSJvOQ5T~K;3SzAunEB10BK^qN3p2n{H>rP`#|TX`}_yz0~Qs~hDHg8Z%3Ir82MYw{{R2q`4+SrSHh8hy9;QT!$tq! zOVHqP+62#TsA}*iHsV|<&)#qakIoYwnim+4f_5)H{Q;_Fn_hv&exCj?JOJ*kdv=@f zSibP+G~p_G4?b2%Ti z^D^|`|Nq@SDj}UOdPTrR+QApH9U&?TF1sId#l z-yYEB=y7+@MPCe<>uqzeueZ&C)^8N7w*@V{4d54a5Af)AOYrD)Pv~~b=ycBj&6*&_ z_gcR}U09+DY7^(q<7enB&)^qy=Kyi@=J7M|3%W~yCRrf<0f}m$D$;~00*%9h#=m@e zd0FT3Gk`|GLW6yK3mrh?UyKY4pZW9MQBQmY{b`$@$EmpC^#UfBRw@f8j-rH2&LP z)A$Roa)7LnNaIgGo5r928e~dA8h`pVmu?r88jvZU`SU^Rerr?;x^q-2K!IeC#((=u z8h_yx4%nDccZ^C&8vpGVY5av3IMVpjAEfc;9|oNf=-K%xjX(V>$mBHs+XrD5-vR4R zpaop2na4K8^qV?KJ-MGim(!57J!t^UtR7Cp=E$&;OO? z!k=&&Byuo~KjU>8f5zc7{)FFY{P|zf_%kkpWDlpg^54y+CF8uk|L97R9 z{F%QYO8ASugAV0-4mOGdB>9P7a|!sWQ;@6qH6fdj)A;khrtv2}PUBDfoW`Gd7-AED z=4+5+B+~d_KTG4!|Cq*K^g$pEE}j1!JC7VZ&wcP5o2%j-Ly*A1!A^=dpw5c|ofP7!Ml$>pXt&IG5q~&SNf|_Y|+Wa6V)F*LmKN@q=S` z5sM>pA#?PJSTnzsy z{&O+>$N0JP-@$WihVMX2FWC*>bzWt>*LnHiSzgc*KgQ37uRE_EJjw-HFnQQf@e1c< z#ltR~j~zR&Fur!_yvz96vAc-Jk-3oP;8|`*#XF!?>x|z!&l&!6QM_k(&V})xYv+B( zgXh^C4d1(f)@8Cg8ouwm>&W>1;8|X$UK5MX8=xgUFF;aUhDSRuaNclHJmA9lz_mL` zz?C^sfbm7=Ul+z7;Pr!d9Tkr%el`5-(yOA``LXls!L!^4PqG=l1TEiWH+<>XtHaZI z(6RG~AcB#Q1PM*=S#=Vqb`gOLE-Y!u{%q|kvUVu z@!%P5Mvw~)zq%+MbuqjPS`d5u;CVI|!{@Fb6OXXF7(RFGJj3|DSI4UJ)WK6sAX`Ao z!VDidcAkM)ai#MR=P6L2zXDDEF+Otae8c$5vAasbk-1U=WQC*RRY%6(pqcUGiti1- zgV^sOmNgg@4Vo`c|-Ap3+DsIAB-P-Ezj|{GK1zQ zwy1#agaU0EXJBA(1TFZPqOt@uR?xi!d_A&D_ZDy-?VbZ38S5-j(dn#F0f}w_-O95E zeD1PKXNZcAOZOBN(3r7f=Uvd!mjLj}z6YHpDxhLlp?eGXYU1uaDxeVvkOmEq2G9r# z=#&Bpm(CKEkj@eno6Z^)f$l9TYapgWGH8g3h-2ra&Jq=e&Keb!?ky^y5$oxD9WCc{wT)2M@Cw-s*hqVtCp`@wee? z##PWhD9z1volwb`{ftUxmz_bhJO&3sV z{RncX0CSCs3*$vc<{T9l#+w)pcI^D-c<>zi!B<=$H+%+jkMS{HcLB8zz8T(fJot_o zNio_9QW4xAajx^})Zyy|%H7&~Zv zOXoSpzn$knYn2#Zb^Zdecnz;R9z4dyT%zLOV))&)^O!5MkBWyQ=Q*%R#~c;UflHuI zj-AICe>omJ#@BiL;CXhJ&d-dm41ag$s7Q1kKlq;6vGW{g*G1>+&UXh7^B+9MYk05o zwMVy(%E8xMhWEN-R181~`M-8Poh2$JE{fMXOFdi+|GO}rckR6Bc<>~fOXoQV ziyO55AGFPb-O&)V9isEPqvAEtayiD+%q}W6o&P$oyE50P_#8aXYxo_c4pau82&PEqeM7D+doAWOwQO0pW3jR_+*{ z2kmKK{NMTA@!&x&uv>q0{^UUXFa2+EEpKx@r(K&9djm(KI9 zptxXnJoug&lo-z+JkDo$ACwZiYg9Z8f4egN11$zJaq0Zdc;B`2pKIq&$Aias9S30gsT8Y~9#!)eCfE}hq14DWZw2Do${bx}O-%ABJTaPTafi{W)g#=9fa_KyO@D#}1hSyy>!Ezcd%swgt2VXH8{&ML&&v?D_*1<#k zuFNqi5ssY~K^$He!&C5tatdV1e~^=Vd4wH1Un{vgr!OlL-H7iX zaGTB;6&pv!1E78Mp!MM%p!MMvj*K6=OH^Dunpsp7ASJ(xicYr+s8G#OaR4bX0V#F? zu>?RY1#qBBAqcR8Xmo}OD6I&cv{}!Z+-j| zbdWdb2qqVmh&28@9+gfWl{Ef58x_z&l4<;TEGjOYGAe2ON$0_$ojM>{(2@%qkl^t& z{=6EMfHeN3>uLNs2f+roz)S#X`plnS!I8$Fa|+ZGNJ!)VcN}z(YyrQZ1E`}=A(6)a z?>xVthlEErWOHr`Y%L4Frj1IekVoT@8H@}JNCS^AW-@>K4=FH#W%EG_hnXki@ zwcDAeBb5zAy0Y_ca~AG!<>24$EY#u3+3hUQk;(-kUAg(UIg59=^6+nW7VB{3?RFOF zNaX{OuKfJloTWQl1^Bl+OLe#kb~{USqzZvZS7H8b&hj0uBK+H(z7&MFFNMJ~5e&`$I6yZ&3Gg?AE<<$q23q3g z2U+3>#sGf|t*I?BWNFL_(I&X@VRKo*M1E@5}hzSMw`q`P{#^ z|Np;S@)vx0Lkl>+psttm>=yUw`~h7t2foPy*NQp#YO)^CTnW;OITj1(g1PrE3qkYU z;DLV7*>#ZBK+I<;p^r6zyjM{#6b(@Ttxo8w1v*Ecy{xsfEPM=c9-ygj?V=x zbbzlQ=rv&i4Qf97(fp5tzp3Ky|NosIAPXI!>q|`F>r0}+1~Pbbi(c&HX8|DlOLX%Uz~bH6p_}+?FEgMVy?~w4R9_(S|=9*IzQ>XN2h>ycL->$ zLkMWCLjZIM@dXdf$=QQGoh+aQ#2-Kb(ajH9C+FDhBL2s* z(?#UZOMTF6R=0>sr+{O3h{>PM5Q9H2yTD>-O9nQA`k|ulJ0NT2oV!EJL2Knq{=EDF zGM9gQhzV%6-{9X%(DD_OMFQo2A*%+=!K>v={=Gc*_y7MFp?|*q2hUG|#tT6Mg|MY^ zou_t!P7w9%{0bU01kE*KoX-O4Sb`VJwO#_Pm1~{L#J~U=mYmqh&*0nowlhSff?v=@ zMFJ$&+X))L1WzM^l(`xnfUK}^HT(veseb~RrGE>Wt-l1Gxd%{0}@? z4?6e-G${{RGB*XhWUgb13TTNOXwDv#$~#)X3+G%qmVlSbb@V`1%C&!mmD8OUTr@v8 zb{=K?0lL}(JXOyFSt}>vYx%c`)3xOQf9o%B$*kJl4lS8=VYB1Dy)~f82#`&nGrmDb zdxPgACcXld&(D#{XHL-MtBZ;N`uw;6czztVUQPr&LGIC8!mI%+pH)G*xfimq23|y8 z;BWS0hOU=823jw7poEuyo8Stj4l({#G01W`3D7b&wB>R#powCv%jFcnGT4^Oseom$ zEtk^(%U~>*>vn-I%M%2-K&-=6{NO8zj!+Tsgf?VZo@nPGu$Tmh7U&3->`0aBaFyL^%Owf1) zG^T>oL&X3us371W3H|Xy=K8F9cj$zd7)3vH^wx2J?&oX<9l-L#L-Qcx1z34L8^sYm-7JRSrEum&?_V~6W^B7zW1B(i z>z;dnd+7JE1m+5!m+L|2D|NSkM-e@G{Xkbyf|uxZvUzvAsQvKjbW!^O za;hfk5<3x)iP)Fe{RCAH&3`#Odc8sMP;3EN-@b`~0d#y~iw{Vef18NP3Z{-=4gMAz zFn@apDC9u5Iq5)}5-to3;I$R7OKlu`UH^l3%E;y1zHNw&DP20-0h+Ux?Dr;&r3H@RmaQH1YHaV)j9>_GEhw!tl`)l z!UDO3su!{%&P5HhBF;ta-%Bn~Vg{es-xR+( z|0Cw#K=mKE#sgJ?;Pr5z6Rtr=K!Uo{9LHT$K!+=#E{6l1w*;;LA0AOH;Z5Vu=hbL} zm1BZFDi-`LAf+C?q7OmtNSm6qQU`MPUH`Am5T}sWQBtQ$b-#4*!Wvz{{R0ES`Gp_ zob)W{3dy&i0vtAfin5NnM+Fpi@byKYgV1V0iHPlLV>M`s6Po`!53zz41T;Ugw|x#; z65yhu0A3QH!q|GraSuq!r}Ha+Ybt0_QLl>%c!9D{Zw;#h)IZ4klOBQABfJNd|4;1s zTOWZY2b$k9^0&+a&3C(~D7>E3d8pTy(WBS2td^hQ;3L-kpmi692VN}y@%8^p@PS#q zwo=zM z6lk#lX#GCu!beZgeksOB|Nr}1zA1g<(fO+Rkv)Hl5U5=E1zbzEBTpX19K{v=a@^24e1+^s^|Gm8Q_dj&8%C`4k|G!lG2i_m^2Yd?z zfAb`ef5EF~gg}?Fv_9c)`3qVDz`u>%@!Jum4rdMimN%fI?mM4=Zp~5$=TW79FK>Wq zWcYm;;N=^d7d(1Hgf*Ifa5#1zDXsyn0lEe*p-TARtEF3SfE%OW+Qz$AN9~2s!~g$x zfFn%vfoJEDm%bo#C%AMzaBP0V=-KVV{sXk+(xcOd=?8xk=vr3C<|jxCBluk`AAnaz zynE>e?t?+<2hVOk@Oo)3?`|JZiR+_;yzHIvf`{gHSnZGxT_Ay_AP)g8^yp@H?R?Vd ztl`}4q7EwCl>WTD^B1%YhOLtcR^BQ7d8zRq+(3sGVoLvBii7f#j|vC)$UM-U7i&Rv zO|Nax0)B=Uf8Krl|8fp!<@W{8?h;l|&x!FzuM?y=Rz#_jR!$70bMmQ0eU`JJ4g(){s6hX zM&kKopZW9M!0RPIj0Ao`_XH55fM3wP;4^=I8>l0|FW3&EKwMBk%P-gtqCi~GBpAP7 zJBR{tK?MT8U^|EcaY5^n`32iSlt*_P$Y&m%U=JRKufOm(?haZ%%1~14vE#-()`PKg zcc1Ru4pHhZ;IV`KYK@te=By--PIn299pZNj45An#Cwg?cD|qa%jO6=%f;2#Q8zw0mvo^XPQ9@Yu1u|7QxX9?xx$PIm{79ZURw?UVJ` z>h96$?g3ij0ot#+gX83$&*{%Nvyd&fUVf^jbzS;yh~?Tpru-}VoM!{E{G*v{ywz9# zr4Y+6Ha%NgAH)|3v0OgKPP-?AbvDHE?7fb^SKn-whge>zSSKub_CYkva!C3I)rZik zu=B4+ z%R4^v=f^pKj5(jiUw9ofdYl)K#-IKNv@`;w9;%c7_Tx1E!pj^Wl}A7G=a(gbSEZ!! zr{Dd|pC49`#-INJWD$4~$bmHe+wUN2MqK%C-vl}6B3S1k*!q!AY5e&YKWn;f);+{-$>(6e+stKg+Kii$Wiyf&QIgN{Ss_6 zWbI7)`859gA8GuzuR&bn$e;c?jsG@iVF<_wkbn5o?}0+?2FP~Ms+jx_Y5eKGz-Bn| z-~N>52nt|FuyL;Z=?6j11qDg^EwB<7{@Z6EZsfmx9qNMg_i129fqck+`wGP2{OKU0 z@-IOGz=c2m5m-O}ZIE};pCH!zxbWw{1l>iK=E$G_IE_F30%(yAh;tjVY{!K^{Y4so z#?3VT_XpDW6CQ$=4}l!@8>HrD8h_#s5bsVJf5Oc)7ygWs{F*f?1t1~tLL-PG$hw=v z8=w_77eK@he$6@H$@Vn<{CjEq86REvH9^-|fX&E1m&Tv@0koC|q#+UH`~1uNnk6a% zY5e)0T=+G6z^hr(_!B;+@n>92pLqkU-Vqd(F8mOykHK7X0Hh97lz`Ph%>)@|kj9^X zHH|;xCCo6;!bylCka+$(unGK$FF*!-fV2QV!5jrLzyai*E1=+e2r~k7z%AGaNB(?} zO#U;lAujxh4?w1X?l1;R!=153MFDhlyJm<=0LZkfAk)AOa^ct91MZ7}gh8vhK7nHA zRT_9LR3<2N6JLPL22JRIu9!ZV#-9jE1S~3P{8$#sMS)TJm%VVh=)307ymvO^+*o{y&hyk043V8Yqy1@-L^k@F)EQiGzxU;%^3N z{7Dzn9QljCgUYJn&lYK}{I5TQv{-;vYh4E|{Q3)0{t~7!e-3ytEJ)o~xRPHWC7_}M zY!>c1bfmIw0;a^g9cnV*a0WuCSFeig_|RP@kdZ8`uZc-J)ng78f-r(S7m`N zbo_1r(*kkx2MLfXKxsL@MFpG;K-Pelq=CcvZyJ9V)ZP5WUqO-Z8sy~S?*ed_`~f-q zA;efm{`@zf=ve|@w35bO{1sGiLbZSvujRi2Nne3zb^!$(fALp?H2$oIpriq+NI+41 zDUHAQ3n&HrfdnJ}>#J$}poE|GF^xY7R75TT@6$-*fBi2FBKbOvKkFt)(gl>D0~kRy zRPkp}`uGiU28eMLWb_*laR;OjQpAXI_TVSdG8z`;)18D&IGPMUIy{*LDas!oW`FI^3_d{4p2ye1VCW~765r1to$}adGTkE zzrZ4JH6TxbMc~GO911e#9w>Z3Zfk(J6C`#Y1`4oy;Kh*$Lq35V2GSn_DvgprY2y>fWe_0;aEZZR z{0ZbVh!AM99IOiLHjtARfXZKaP~!Gk^YsivOU=p5o8CY5cG6q=75z_ovhNi?1-I@#o)4<9`n-2`+;g zOCCGY_^)&nfYo0CUHWZn>s%E;V;}~eorgd3*QFYy zdGOaA_{?7y>i}Xs1QGW@#3`Rn7Vy$H0nmzL1yKLNv-6y1=ULCr^PUIK@p&FRE980b zxVW$3SKn?UA7*1;!@s_a?|eJYdK!N9R0NT}j9)!FulX{*_UydsdGMH==fSfwK8*K$ z4c~zUj!HsUzMMyWJKuwZ6p#BV9`)t?3)cA0m+`f4cNT{ab2bOaLLbI=o}FiX4bOTi z{`ECH3+Da>xdLp;U(bU_!EWii=gIihr}LiY!E-Vm2an15Fuw3H{N@W1I40?7_|2p9 znh)naAH`#yo!>!h#d|)S|9lv~`7plo?amVLVa^utWqb+pr{P~u#iO2vXZ?FkG(9^H zdUhTK+2(ohB%iP0OE3>)<5|znk6`zH^E`M^4rHq@;|pKIkDi_1JP#g|^ff%^+j-NI z^P_L)c@RVKx~JktU(TPtj30d&U;1`uN%$~lOZb3n{Of6W)E8vqU%y@xO`pz#z7QKd z4xZ%mF?kGC~!>99xZ|7Z5sCpbc!RKT60?Y&1c+{iw z2iV4cpm;tAipm3WK87DWJO4racnq|`g7b%u;t5aCEvqLy6o2?|{_tV^0J5e#%fN>@ z+rXFcg=gngU&FheieEhqulo0zXn1yh@a+5wveEP42|i!L7hoPJ9*=r<9st{T%=6#@ zs2^YYf^0knveDP@m}ln?PfqX+tDQeQ70-Jr9`NP-0Xl`rv-yyNFXIPLymx2)@L|sW z;lua}%!-h>8Qh7-;5} zMddSpU5JWMgptb(s?gT`;0l%1wiUHUR(EYR^Gr%R2psNFTi4drl ziZpuoqU=6+NCVWL22D%XgNCZY97A`2ide^xP|!hH9^K-e&_lAWIyOJ8chP*|0Xk~> zm`A60F{?+jI|u4HS@ryFpuq)?Ue^YX<_8Tvy<3<;roqn1dfo8i;0O3Rp?c6cStT$R zfp&Z#&dCZ<32^Cr>A`prG=km?+E@x2?_vZ`Wt1rJZ!=!OXTPTOGOZ%G90IXc0=-G>=EM-l*%mUm$A=w|llt>WX%|Jt3%$-b~C%S#08_GbJA$K!(GCG0Iz5;pr&r1Q&C|Wr5aI;{KPUw0c3y*Gb zkM45hoh&XY8iofvpeI9kbYAc{_`=qM^MnU2@j=xSc) z0N2*Hpn8|1gwxmZ3x9J5GXsN9uWe*5KZ9rUpL)=K?+}#+kLE`WzP($-LB=808@+FM z!T1g|ehgl3^rRkq{>yyO*nBrL*k;gW#|H$OAJuzwGHYJ+X#TM+)x3D4p$y1Q=r3@7s}-5aODFrJHV@w)FG>q)H+-R zJ-VG0AjSxRR}l$A1tdCLMW9T94p&hqlcU2`48-gJFI3WpEL76#aFz7vcGiFxBh~G! z)8Q%&6_Ds~m4Pw^I$UL;OpXp$IS{kMRi1yFvw4TB0{?brvkq59k8WoJh%rjt&L$nM z%1{A`4p$W@Q=r3D70Tr3a8&~_J6zTIw>jH)xN7ikced+r)%56gwtyI;)$MH4;i?T4 zkmzvLfieX;Ty>#Ljt*Bn5VONozZ-lFG-%SN4}3y`vwMfD0snSqw+>fBk8WoNh@nQ^ z&MqCU#!vx?4p$Q>Q=r4u6w2i2a5V!lJ6z5Aw>kTFxLWXUclPUWwe;wA_J9~;)$Q!l z;c5*Pkmzu=fieX;Ty3FDjt*Bl5VOP8-UGDX7G4MOw}P*aZa!iGE0G=JVfSk|?g8}* zJvGm~oC)4f+zeWTm7^lS-@FPmyAE1{^$%1up9G)Z;|f}U1xl-g{v=guX z{|BdXAI%5+EmnX3LzZ`ysBpYw0ZmAP%12MeqhO;PqW!K!^9lz54(Er2)vHu>Jf#%sHU*(Ef0^bUrM$a%={jHlqwS zlfT6gG~~~}P5j$ICdZCY2L2Y1PS6%pe(-86-hVIUAx60{FnD&0d-m3H_;mgQmHVEZ zp$smak6^2dxJmJx4B3|?!d?*U&+{tdyl^LLQTqtgV zE#oo;ImCtWCBhw`*(6ZE0Tkl`2#0_s%t4E_AQR?3o$ov}e=r{O0AI@vs(>6oGgaN< zj-8T&e;hkqc>lZ<08dA=sDL-Giu~zx5&QEp8!YC^0G^`;o#5i4_U9$zKhV)- z3>o|kAQNEAu}uHG^aPp9zuiRywAEbf-%H)!kjWkJnJu6h>feD#u+^ zKnrIX3{Qeeepmi&-(2~(-FD^QcFC20+eugcZ3i6qxBchecFBQ%+c%Gck2pLIKH%`- z`~abDK&T54>I8&30HQoNe>n1Q`|HBLEk{KID&Wz{qT<8(3Tz7JC;n|EDrQJ*QzW(t z5?dXKEdgbFHvi8DUCjsDlgpz58b$_>n6@tgRndDvRkW|>UmwPopu7DXiupik(E@Z9 z45%{p@U?te1Uik)oPobp6LhU?uj%nrP>uYzzD&~-+MM+5UBU(mYWR6^1tL0AeMp~?`JL`SF! zgeA}sstRI(7R*6RQv;i(4>3&xBnLK46T;Hy2-SkH6gon+AuNfGP#p+Mpd(b*qw`QV zWat-So*t<7UIdy~hb=k+#j-iXd;^F+gN{%`2uq_Q)Cj^-=m<52up~M{O&~0Rj!;t& z3$%>`WFk1C&A<+}hZtx9k_J2262j8x2(^N+6gon!AuNfGP#XwKpd-{4!s6%%we#q0 z22C+fKtGocRQMrP^v00&fS`gBsjhe23##OOHUGX0{{tE>%V%v(#acGjr6QG$jZhA_ zY-|B@pk-sr6VN#-&425CI&)MU_}kt<7Yo7bGroY< zi#)C8Z`}@VLingSfby3Iq@?xeJOwHop$`&-X!g;}?R|I@cf@AaZdj4%+ zJUWkib{_ET1PwERx-=dK-x+|%jt+s2@m2Teyyn??*yG?k4bRTM9V{v!K@*S8YoJcc z!FLMa-VvzZQ=(!D5(Rf+-br{Ke4*ia@TCGswHe5WgRdk!555)vIY+|d;5*QvwXT|f zTwDKxL^MEK-3&m-FqpqI{smu_2rd3sJx5p*_*i;4kg35QRw>9hpU4RRsO0ieUPnt8y>5<%y- z`~|giAj=Yq6JW~{LG3k<-eQm)zKqX&HJ`pvfB66ZaTgVEmG!du7kIgnfnzghSsd8R z7qE44MPT#zTc(2-j=|39>jd)<2i8=AkAQ*9mxGQ;?5*Me9is_u0ihj2)Oo=}^M?oc z@S%e*pq*bUP~rg{>j7Ga=-KT9UNZ<4v%SrMm8=SzsJ;el_5SV}m%**e)l3%x)m_muGPZ|7l4>*Qev zUD4e9h>^bqbSobJHW9WC5%!nAKy6giBWI+3f)>e$sC0@!SNgI2d07D#LtQAi1-wwO zIu5!}5PISs6Uco?=khL)SR6{d+0<^Z)-Bu4j?f!|w#OxS)$5 z8s9{KD&5`~72N9qJy@X)Fvxnqmr+RT0YMW?pd2FsS}sRmB_L>}A=*m7KcF>Rpp}52 zNpw)FO@Y4;WMuPSCQw_hE&;SscL{iA3nUAgXko2PfD~d5AQd%G4!G%859K8Aw}1@s z=oQ@?&ClS0x<+sp+8V*Ccj#*bSwL$9o6o`429oL8qAH z%N~Q!s^{WCLE)of0G=!7*NjmK;MYu1N#Ji~fUX<_O+(^cIS4JsI}dq47Y^2YfL9KJ zB2@v>*3y8l9rW$|&fnSzUONaKY4Pk`10HFC_t!ch`@7yZyqI_gG+qck-~35Ef9ogE zASHPHU>#^7U^i$1VJB<>p=l^+0U-1uwY~X=pW(&R6JP(o1YN%B zk$s@hr&pI1oCZTw6d=n6>&whNnh!yTewK)$)K8H0gKryNFx~;RH(~1s>-k&0fmZi| z+K7;h{;r7Q;49|Ni=RD~8eUof%I0F7;=4eJb_EkSmy4x!@`!shA7Wa;$ltOP)briO zBG$no{&FpRAz|+Eum8clCs=rZ3V4w5MFKvZ4?&A7Aq9y?=U2l6ptHG3xVl{#prr|D zSz!q;Xn`U_TBiuOJaKG(!pPrp^xyyg{M$lU89*h<4$wM4#QH*zUp;z#gab4$fCizC z7MreMDNSfSP{QTNzfBBueL!gvw6=h(|Mcy((eu=N^WxL3|Nmc_fJ_GW-@C;?tuB7b^)D>rTOReJW#J&57Z~r{P%idH}c}X zFQ9StUfa?NeufwFpwM{?x+i_>321rL%_?=oh@tgBX|hMJt^N@s29I7-k0O4CojM0V zgL~};Obp-~mm7a_9$;W7@$=|q^~^J3c+Cw>x0a_$Ogx$o7<3D?UMl_M(R_@ho3Hg` z39FCg@uJg^wSLE0Hy<_vl?MzSy{u;r8!>>!tD7qXv`Q{?@_RHNXL)@BG5#DH47$}7 zwCc~+qw}vvuk7tyBZk+)X|VY8Xg+Mv&D?sRM9!nTn!}^>-HTJFKm|qVGmqvY93GuF zqGJz(`s;?b5q#wP4I2N0{Nd5d+MH{|P|EvS9_Ei5nh)d;uz$5%-fFY4k~9Ec7Rp{dt83>?NDE-=(l5RJD3<47)rSf4>Ug#;GcTn-~%R*2N@9l zgX1*#S2uzbw2Uwwe>9GXYlEK{$lDD&_Zn1 zs3K4^L{z<=pJC@6AyBQj59A?_&i5~j13~xLyC(Q_KKJO|u!V(z;l+k6|NnzhlW(t& z3WrZGt7Q>CLzaR^ujunSeukYdgcun1fl3#UZqZ;ShKQ)v+aA55&kDg-`tZB{0xi=o z03HAN>@esAHrE7@X`a2c7Fj&LogW={fR@gCbl&vo{Iw6%_WM6UfDM#sSf3Pvo!J1g z<_pLgW(EcZ-_DO7o$o!4v#9VeFfhDW9K;0n+3U^!|3}7o^g1heblyyxaEy_`qxm4C zhvmW2FFw7j>meqY)$ubJ9@zN{WERZBP5~efw}8)kf%i~Kr9B#tfPxp4HXLKY*SYv~ z=Lfu83mOL6e*~KA5%C3{t@<16+4;qz`ACCD=VOn~`$*B^qEcaa;Dz{RaOj&r9J8{P zpJAss_{d&(=x_B0ImR^sbkZy|^o>B8IzhYHUMxQZi3p!wRyK$kskNY}@a=rKQ-+0s z0d%@J11K8$K#|xh>Rt<449fbs0PJg7kP4WueLxm<-hZ)W)BpbwQJ}6(1;X>39?fqW zKt4bG;>ux=R~^9nxiQio=)_Wt^ar}o9h&}v;OXyVJ~%PFaJ>u;fr0{X#4fMlXV}Rr zjFSE~`$0kglKvD}85mw9T!wfXl>P*KdRhGo_!+W5x38yz^h*jOrN5~G@bs4ku@aL0 zAgjATNig^+)+7i@ci56(Vm{cVf2;W!cB+DG!jc3P0w76n#wBPHH1O!W2`$gi(xE!U zxOLUw7|{k92#b+jz95gcsDQ!`5fz|x2nuFUY#^sY&?Pi4%nyMg2UK%1Af>;?HxED^ zd-NpOq5|re8ylHm%1x|&zd0;;qfmFc!e9VW50W>lncM*~bA#OzY zp2wr{2*@)a-yeSAaS-HPXn7y*80Oje9oqWugp`LI;3jc7YI*pB0hGRcTfgzQfQoXT zURG;}t2M#y0$nyUUkFs(q^J}a9)RaWZ68pizJC$y$_y!Anjz^1be?+e1_n^M*x|~| z;L!~$DkDHbqAifH@$K~mm6XqO`5CeVK<)QReukavLB$TNq)bdv!V(PXEa2hxPG8dKx4tYV+Jt&?KF%L@jpy&j}JUrc( z2fVZemB04;K?%Wy0ja)q40R0g>1Cal!_N>J?9nUgQ^C)$^A|G%gJD>U1b@`_M|3RBIJD0z2N1!==h1is z21&_{V@YvyDSbNr|mvw6vIE;hJ_!)No<%BxI@W9JN4SZwm%{&Nr*n)~|{&qc(mfktwg`_^6hkQE!fozrV>b21Zoik_f;`#os|3OF43V zeL6pR^xAO0xVZo8e^5!r;L-UGl*I$hm>F6RICdWQ0o^TAV(QU7MFr%@6c1w&RnWP` zpgTxfL5HDuG#&wY85GCRJcZ~Fbcd*DfUHsQ>3r_Xcnp#q+AKgNT#1T-Pv<9)RbsZF z0*nj{-n}lS44@H539xS8&i6juTfhoDIzh)!ce|)q_-MWW zaRgo)L(Z{p25oc`1P$k%EaB(h=E?wC_`(J`*VotbpLef|EdzhsTJY4`age=}LH2fs zsDPHffcVWeASZzXVuuYV+@HTFum#be)C^LP1Xcic^K=_#24Bm6KAo?MIeb7TRd{~? z3Obl*8mRpv06K2vqF1kp1%Fc-WCbMyc(@JRj|6W;_F)Chn1XIu`3UL)YyJc~57emy zjk@_-{`>5)l)pXr|NsA>v&|$xwzn8FGkEkigCiW&KSdcw>2-zQr*g~{bin&jMi0xQ zrJr9~g61tjbD5ndxzZSzV~B6(kZ;bcAx37=d^-9?d^^J3@I%R6x91kLDkI9ihA>Vjy0fNAnMXj!?c*4v^*b9j^Qy z%|``%Ex+Jz6z8OH$1AptAzo3Rd z2xu0lLenA!$Cj%E-fgOUN61&%i9dtthOM#0)j4gvlZv~@=<#m2=o$lK!W6!^> zhNaNSphgxX#RQg8Wn^H0Nfp7Qn88w9NK(ZxDHgEQd(f}|)W8y$6f0Qj3X)VQOo|OG zwG&CI3?{`6mYRnoRSuKl084=mw1K&>0w%@jVR^kc2PRwz6XpU5hr)!bV8YxWVLO;` zHB6WXB&-e-uJLF-zzdPGyihFQ+snhwzs-fEz{$XcmmzI}XY&zw)OdD&=Wk*LRT-d! z(|NzsM}^}Bw=Og2@&gXf-l?F<$EWiJxST@1M+B4;BtV5GzdVB@r~>kAKFa86d9?J? z%fI01DjyXO575Tu?Vy7>wt*(gn}0HbN`RK7P?189=ATTUvY=%$RHVqG`6n}|RA_00 ziWGY^|6~D`4=wpnkrI#QpRAygq9qn8QtHwClMPg6w0J{B$~>BXvUj+$L9UnZ=sfXq zC-VIhJHZuU*suTpcQSygJs*&RLB$_9SG@cNI=c(h<-76^bYRRs{xY}E;8g2t`N^mA z0#b72Z`lY^(ksFaN{$7f_5FUf}gKpc;NNr7yCDXs#nzh z2ckdf+j$mzKn19p0JSO&JbEEj6{r$KUJVQB>l+>bH#2=y6hN&%jTev1Kn3se|8Dv7|`xm=QKsO;diyUhK&yq15YfZA#|Ax|kpfvcTpJOd5|DgQ;AR1bZBDd2F4`8$fz^mCo5scIh zc(GywC|N-IOOW=^bx?cAwew79Fz5)x&aV@lSkT81h7&IdG((C+xr1NP!y218?Wem>${|||O@c5Kx=iks^@OV9_%j(mO zHa-RFk%7ibzwx&effs_OsDK7JnPR}b*_Lz=m7`Ly(+4q54;p*vwuFobY&HTH7caaE zK`w<2MuEHbFN{E51r6i9_kfO6lompUBw!;?Zqd*IDw%YChMn=CaT!pH2xNhmH4}pm zWEjf?H2d5u`Xh~>VP^`G8u<8>oHb~C%A=Rn38K;=nxCQdHfUa3q!Khv_1uHs^^j-t zF$?fCB&@9R=sX5m`v=;m2P)}6G^iU1qCsmNUhJ@BX4nN9m<3}8#Az=~TOjL@Vk^Mj067h-Q}-I8I`uE8VFhwJ)TwX5tuY@J4roLBxku-@7dI?G zIqx5+o46O$Oz(XELbL$YnMWJ{|KEk=%&p+@C=*EI3*?U_ji8&N%E9AJ9iSn~<~N`b z#Z?iYWFzX93L0)~L3#-)IV%z8oo1dS_#Jeg$%8hwH+yG0Zr zpm9Y|Hbg2ze7eg6Ubcc}bDysQ6(*qO5_rDDrMzyWjit?g7_uFMjHQLjV4YiMgPh3oemB z`4pUWb@f467c6!kmUnkT)kT2Qu}9~7Q2yEi+T7V&U;#>*AX`8*XqP&O292D9)GR0k zt*&SR+X@m#PQfpN%@8RUl%Sl=K#B4lh^YZew4Lu?JkWzU25zxOZ-K=NDTKyqQ83R%;y_f^yLwYjbK)QMm1{Hz~f@F&eq2T1Ap9IPlpkdfo%+PT{ zP$rB3rI=n(!6fjA=H5_nntBaV0ZCKOUx=B2qwqzZE+p#Ma$v)-pg2dQGSG3@pmH54 zmA&X%fpVT%NGP~X(0Cji-?AVBdv}AYqK(<0qG&g`{t*QY-}mkYRVyzncYwsJ1zt=@ z2MrhR1`UtDNXY}G^4*}TE?>-83L5g*T>4rT_17sep5?%klx zgD=#yy&Q9=**AxFG8w zkjocPD51~4gX&M8URKpmc>b$O0O!Bgpp~PYzab;z&2KD_avpeh*^440CI*k@Y7Ykf zmUa%%s%Yl|@BnQUn9~57)b;3|908sx>IM}BVA&kdcuVsej_wQ=pI%n81b&7fk6ux> z1b&8{-#|n4pjr`>u1rjr7@GgGJ4QN2mB=>#W#VuB1X_32?d;*w-2l3N#1*u3DvgPO zp}AUufxo4Ng@J*;j~{HebAd7)_A06t^TT$`is5+>s0!x6-;MfH!q`?wv5E7t- zvjPZ~n^;+K1W+=J2=tW@2FI z_5IcS@GpPMbOr{{8F;0Z;3KkpI#0d?UD6CrhoJFApI+9panJw;h4zd%gq<%=8-bb> zKE14l!Tbz6rPx5p2DDGKSF|t=lri^##+^Yi(qzEIumjWw1dWtyz}KYlx2^__)}d}D zLd@46cTwR1o6ybR(aqf*EWy9cL8tivlMSOscQYuCJS5*XKjrY~X7Fe}$^mLqOLsSe zjM=oms!3C!TpT_oYr>&R$$$>aMs(3*s9 z@N|i%<`o~yXZ$Vt%%FoJpBY|yEe4xE@qnoCfF=pX3m(mf7(FeoICPkB6hHFlZU&_k z&>W0McX5GlZw?EiM|YTnPv>foIDa1>I1?~?G#@kY=&rE%A7B8=23g=SH&E){4W7s8 z4v_HZEiib&zZ_IhIT!eJgSGoW({OiygkLX@mJcK;c{U$m^yy{gjs?ZGV+bgLl*jNh z?7YSbEwVw8->S#N;27c2U0vYY`O&xY1*i|y3)<(|9WLR~TW;XdcpQ}B177sVF@YvY zA|0dnTMvL1H(Ta_o7tBG_!)LSVP#<8Z(RslMprGt$ltdPbkO=+{+2Z$g}tnaf#6!> zXEewqJ5|{j7$Buj=lvI~dQ1$42VOS9#|IEm0LoRptUNLN3|XMbz1teha9ID>rB z;_?6g|Cg6QOX%V2>p(#Si`?$Xpd!PiyBn0AeW1y`w>-e3`FO<(=a2vYzq|(-Y60ap zs5HbkKm8Fo6qGeOUAPQ9y20~!J1fCS06e(o(JkcBDF8~EKHc4rn1@c=^@?te;%5kW z@m3F%-a#nS0%&H|dgW`HJ^7d1K{13}5)r{fOL$=E)fFTp|BsiOi)2#z7pPy_|8MgXXKdBLFw zsws|uljZ#Ms8+Vvl_Z`<`^^9!c$7eGY?sK|JE z2CN92k9~SsGyOo@)DD7<7TswJ3d~;7wn%;kctQ74hl!!vMTO_3Fleh=Cs?6RFRKyA zo%dK77+#1FNn1E*ErNPdQ$jG&YXu2DMg8y zU{WA;(-k2`M8i{W6)zPA9=#htE#DU_7k&Nz(i9qBj_~lZ z_2p;SISCZ6v%$qEWW2_M5nL2O!^G5=pCJ@f|3~mMfR?q)2jyB&@PNu3Lmegt$LLs> z?i3XsSI}{qH-G>C@6*fL;lt0cQ<05<;br*G|No(dns4XJmqrlz43PYL76yiw<{Z~@x>=FyV(M~Ae8~R z9p=#;8v(Mz+@rfX1GIFM@5NC$Mg|b?h0SbGkzHN!!WhKpZ3e9}1ho^<^e2Gy^MFE0 z`YUKSF*d=Yc`wNApr#OLy1ThHf`Pwf7Kqt;o4@5fxY-3$0oo?w(H$D$0Xhug14s&V z#KCb;+lRs9coWF^pv>iJ_yn{8!l(0rhvu)|E|62etC3fGbk7CZ<AX@Y{>3Zm_@$rZxZngUbX^m*wdiP@jO6?=6U`7C6of3M-G!<{zMS za~9zBSCD=JPZB6gx-uMZp1{n&016oXZ4IDS%nGKG1dndt0*~&{gysi~9-UpFxjxWI z5+`Y8n5K#ca{hp#tuH9Wa97`)8t#TPY@J$u1-4_q?* z_J1l!qu~M2B4SW8-!b;^K2UIg7s$VGpZE1YcsScNfxq?ZUvMtUhIG+-Lco>nQV|AN z0r>vK46xa?2`|>q03}9ns(G;(%&v`iF$=^v?wSCK3XkKhpo9Qo?qC27nH+bG0C8TH z{Qv)dA1L-A4mdXV>wgbWlJw{VYv6B9`}_a@K2VB+C|C+o(CHfS5_CZZ^7>~`1>)1o zn&AoWpWF%t_fNhtLYoJk;Bz@UK$S`Nc93g)y3-SodMhp9l@Xw^W%meB`%=L0z>96F zObnjgY~2+CFJ5ypK^B&XbAc8y9R~}Gyl4>uPgR06Nbt8T;bLHL>~@s^EkM}?+8+9Y ziNC)P)b8zN&3f?vzh`HQ3TQ2@M=$Gb(5>d3Eh-EU**!NPvY>(=+>mhW-lEdN1zLn~ zz@qgV$b1g|R#5fl**ixi0i+335cP_F4&rBU{J%v-f(?}3Ston&GwgH$?W^m&39hR_ zNt|CD~_Zp$4JM{!>*u3#h@vuP8JpZRt|QMaV=mMgZmT61R-_0bWH1@l6J(@b&0?>C^24I%%gyMZ%+3bfr5e+#p_R zJrEbq-&zdn$%5K&-4z@!-dq5u{)z73>bozHpTV>9;LbTrptXzeaGkCUYOsI@_^crc zQv*ROFF_Vb>;zrh0ax0i3L3p-{kRiso*me{mpkV%LD$NHRxuuDQ2||w^db?Y6ztSq zQPDtf=9YHnXV^I(O)8%{NS%^rymD@D;F#1-~u+!<8Gj#OosiSh1;IT-6Wv20;pz1^uHue8h&ej$l=j^ zP{6a(PXH9SKE14mA&u~o0DgwBaKi(EV4v;;m1Lk4SE~e0aSR^KM-)7}{WQLT%Qi-j zUKURN7SO%(z!RJ#i{?GjS-?KZE!K1qg zbcv`(>wywk*KRiskM3QdF+I=DmmoiRfR1ng&9(i%;MqG5RBJ)Uz8Kg+-2(pB9%fLc zXamI)SmwA#=g}9B*&vedKzrc3!#Q?>dcDxeo^Yr}P^IPA9nJw-|0Q8~(xdbJ3mdQ$ z=&p-a(1@pFb3F?af6ED0(1rsQ70=!l$dQcj{)HpxlHFrFF z_kmWqc=k@azzE6`p2xw$pfcU_c-sMx;h^cN=I1|rIuGsxHMTu8e>NX1fK5>(ZI z+EE_8tUDh=RMr>T(E#afP=GiNH0udk-UlC3ap}Cl z-^u~%nlv5(Rcw$N9W-WfxcLXOPcLh$BR|7VV^GVo1(a|-dPR@;APhD<@Zzc*6NBS! z&}hx;h5VrPaFAqfq5{f;KD{bz9=$SrK8y!I*%LJI=%OM~3eJ{Zy(SFM%m>Pm;Psgb zFOE(7`X7|_K$!`YV?BFKG(anIBwnnU2F;zIYau}yQ-i-H9n=@->t+GvlDY~=rhKuO zA2hffrs3JU57blvt)HI2&&=>L=MQL9o~^k;gq^>o8Qd0S-43lZp)tg94iY8M7>Yat z;eumm#gG609Xk(pvZ(mVv2~a`7?%7!ummX033ztCfHibHdPVC%T931U_qTg= z-iI}CS`Roz#d<=sDSr#d+3+~Zx94ZrnF)$f(1{41on-=`0P*Pj`10uwaQgJ=W&Lc& z&#)7;0okS71GLv5&=V!gB}s#`9BAFB_MiX%LHb{UQm04feem!jXd&B+zu@A&^RP>I zj0y*TD`*-PEa?F%kz-UiUfe-Z(DEJ9gN^e77bH8UfvUV-(d!-vn_pPTFfllG-hR35 zH_V|sJoy=RvM@0)xO97nybJ;@0O@7@W(N+>IUeBXS&CK_yjUW|#IVoTm4N}$9v7Vi z?lE|Fwt^}YPzlQ4y5=W1o=-yz3ijY8B^;9zL{|No^5*hSz5 z|6cIcvu*}ZHs1lBujO}n@lp`9?5CG?yA40XPIFKOF@}t|`1G>ofc(%8665**|No1v zeP2Oc|6Y*FAE0RBg!J`OJ;A;@25LuvrcIz7w)chyUR;s{J7nVj|1W1lierC}ZZD9T zr+@tiUGd<;0G@wz40Q|vFCOq{exu>hosZmma8Z!}bsgA!y0=zi@VA2cMLxZIlR$NJ zw}*vKw}XL4Z;6V4NAF(HT+>b^(7`mFzg{qeLz)zzi3xC#;M2=`)Rv!N=XB6a2{dzo z28UV`K^5k~R~#KvK|KiHZr=otUeS$ipmU>i*gYE$u3%zdc(GBIiQ%<4bluGk1_K5L zkH({u*ccenI$h6!y4#vBJU~T4A847rN3SoFN9T=>SVqwJx~Jut4p#>LrZCV(GtD!g z12CIFn;tznZ+Uc{_v!rhV#x&1YNvys9POcb(4%*X~NgL=2 z-$ZamKcazDvq9FWdvxCL=F;w1RvEcHs#R%NzVn=Rs|d&Rd`*Sm$4? z>IF3!btGO|{{R1ff@kNomo-S^O+LM>l{Vna10L_|2lWMdMW?wUWuTuD(DA--a85ys z7f{!tw^m>mBvSspsCEZMN@+YSLWDtys*$J&QE-Jv2&hs>;~=M zsqY5Ov!kw`1MS}Fu!an~^}Fyh1cPSKfA6dXZR7wAYP^^w2I~A&D=_f4tOdnVcQdHE z@aa`e1U0o<50t9Cj7OZ$>;aMX(0l@zUA+?0v-MN0+bLB^UE{r2VHvdS`ofJ13K#N(d%RMzk!jFzZJ9}r};gj zN4K!yNeBLI-x0pypL)=P@uY|4x$;vjmr7W{TbDg7k3mWRpI%lakXw31rJO;>Vca)7 zVEA_DN_eQNh%zxC?~wNCW&LEy4+;j4&ijUMcXq;6{t`h_2|Aw?(g$NI?R!1TrrY>j9a)Yp`G_;r3{*PGI0~c>^v{ zLjQPlUi3KlQlP^FG_eVuT>%Slba*g9b@lrG>F{9o=!V=^i4j)U9_ z-kSBwqx1g%382wMB=s%JL0d3jZk+`>9|Pf5P`Y&KUJY`rOXrK`2Y;J?h?G|D1J%2b zD&46ITBRT3ZwUccJ>UUuu%u7tzZXg{c{cu*o1nf2#9=<2zdSmRzc|wM^}lOJo`7ff z6cx}q1mDg&4$#m7xR=*@iNDp12{hBX1uO|#pU_-gz)%7@H`D{%KW&KywKE~p9Nm*a zuIv!-=r)E2SeA+~@Lgh%&ekW!D%gB}N8a&&M(`5jJ-pfi1;D-(KIPlFP_A~#~L z6I#STN*Kul;56R+;D<-AivX`j_heAn;?W5%=b(<^-_DSB@Bjz@_5%U@+nks{1Y>{; z|28g+} zh>95?6^D>Am*IgIO9Vir{t*sH83f(6#xKv%3`_pn|Nj4f`3E%D39g@fdRb*4rg_0S(K)V|or4;uM>%ijt~E#P^nmyuxW9T+^IQ>4vDIXrsHSv;DL^Sqb< z${~;vz@r&FC&tlW!NA`N8cRCnz`_8{=f@maVN6%UlW8uU9&E=PIAB8j+XEQWIs-WQ zw*@eF1aNymav3Q5w=(|u|NkWqsPilj*)8?*#xGbqwe#Hfd(A(Yc7ldn_+8I6zhe4+ zsl$V*BY^Sc4M?Ow=CgcyS;I`g0k8?u?Aa-d951{AObjnUm%s5t@Bj1Y4o>Kv1lo_& zQNRM?Ks^B!3jm!ZoY33=Qo#694blK%Hw0S{2x|GP0+qASC^bCrB9;$S#ek{}(Ed13 zIRNn=Xe_Vu`HQ+X&^Q8k1{XAuX9qTi(*_(v}Ld8HBg&KAEW`AXI`*?(r>qO0i=5G-2sYIpUwp!s&fUX<>1q~0@SWO-T)eD z0d-qmT+9WHCdE5O@VA2c%pTp&1&|JnPcLgK#A$gTdmKUbKpp0y-2u{XcmOsZp2Lgo z&>&=o>Vgb#0T}>uXf!`KXDl=V`<)jg?*@`bWL8ODCWgqkmsyZ`8c;g+=oNip1-_#=QysX=brhmB1!8LB|NlE5A)C5~7Zl;3-X1T+=z55H6R>(v z?Xz<)%xFj}#EOTB;q@|D`UB1Hc=Ym``atG)LcxpmI_i*Ij4jyDq zJNSaV`2o8}>;G<+#u5SkZTou|7#O;3c3S@M;CDanVR^Jn&=FRxw{n7dM4d-Hnsbod@DQ-hYCgR?uN!5|J=HTt+2?A;9p;CLyu1gA;lR)kOYVo+*&QN;}|%V9RP zSb-L=bY~}kPlCD1#K7R#u=ffx0|R3TFRbuvQAQS)ap?vZsxF;BAk{Q~OB?uBInX#A zq*gKCT{;q=^`;>xm;JvEsyF#t%}K5`r-ITg&1%i> z=Ah7jA;1L=X{@zoqdxJq<_dFAt@#KPZ=LsF{Q3m)G-z=TXlT`^mo--(9OX^s{0uwM z>dih*P+J@108sw`UMqsu>vZ0Ku>ovS$vvN5Rsnr*_5_Xb>`akj04+o^G6%Qw_kl9& zi>X}TJ|+j6QE3xkNu(9DuMArDA~!FEKvO!sAu2?)FCBY*RM1UoCFX*^|Np;q1y_iOc0e>p5VZ9=4b~2Lri<`9sIu|s6*V)1_OC&yY$hkf z?~pDnXw|%u9zVm*0x1TDm!NH9U~>?XM|Jrbz>SI5+xX=f@YH(}fB*lV0L_&?y{vk= z;Aq@!3Q9X@^*(rkrcd{3aJ|2iorwX|+HVC`P8?D-4v+ zA?-j<&|z(`c3?*XXjuffD~i+(EGYpspP=m~f^8{iR(CbLo#xURL1Y8b3DiLJ1vU5J z{c*H*=j-L5(}Ex^;EhJ$^nl!Ac(IoS9Be{b;9%=F0*&EfHWi+;f`*YmBc~pJ{{Q#w z+zLv%FB3uIzo3oPpyjoiU<3R>O^(j{NUet$kOiIJUzC)AtJ8LfIyHzz7_EmoutlKO z!+pqjt|LVKH$#xau(clkFoRkTKE13>YG4w)P%G*JbFdD3_Cy?+JIYP&2J=LOkf60cDpAu?10qg?moS&(;yBE zHRNa5X#i90)A`@7qva-Yus9^Df_LFUmp;Q?qZzxpoF(7b!Xu?Da~KHUpI!k{v! zg$LBr?$tdDZnMk&GvH@<3A&CNKK|s}oeybQTm}zKKwB1kj?Mc)eP}WM7SL=Yyu~5u z+T9Kkcj-I;Yg!b8R;tP~csAFA`s1G6?$G}DVP?=;fp$Ca?kGRAWwL9wu1^OsB3Wc z$w8x?w_a>x1jQ^ns5XY$>f7B9GR3EtRb3sqWr59dSbx0pR=|rMX3+M)|1Xxj0!{55 zaczAP5yjuy1}Z0eS%uZXi4)YQ@aVh+?wWwY#sX}+djfJgU;#&Ojf#MnXSaz8f6Efk zNPG8wP=D1EbRxhD=StAp1s4?#5LW`!De~>^2TOOp*y#gGiJ&fuPv?KnPVf+%Bj{uX z0mn{odhi4vM>GMH>aut|I&ZzW$^K+j?u8z zZfg!`3KN$2T9^F=CG2*PGr)}&&*pm2nAj3k`gea+pDhU7uC#c;9QgVv{JTfBT()j?T5ePBA!A^v~ zH5fFH)%pL0{Wef=n5evT0*#r1+ckLFFPr~>?~jLO_jYjGXYW5oNZW@8 zG+YOX=a*l>r6j~Zp3pScd<-$}F$0|Rz!B)#3{G5-aSzbyUr_x6jZ4tD2b76C?g14- z8~5<+hR1NL9B60>G?NZGAECLPLx8{KEfWI+q_kx6Y~BwVdSxha=$>%_)C}tty{-*j zJ>{SXT^i}r`5(G2(y{aAi<^HL89MKIcJBu*j_ka3@Ew!m|LGtje0o{mD1*avnl?Yf z&R9?^g9>{{@xACTBY5Sx04VO@^{Z!hIAVMR#DRJTTF(ctg0dEDeB>WE_CRY884&GA zmrfRy7e`B=&BP-v-7G5nEt;T}e2}qnaC_1PG&==uRI;eNm;qDFqT&Kth|b>vx<(ST zED0KF9-YU*8eZs?e*F(t0@^z2dE7-s0yG}$+5tM40Mg*}1a(O~S}&DyzFf)3z%T)| zeVHWj^6^j5;bRO?lfmuF?=P;EeEsj+-49J!4|KpO3)HX#r7UPU^P=z%BLlPy0u@I- zy{z{^N%cRt@Bp_eL8?IwHc*O9{>RAR*<*x@fO{ziA-b|m+K24z6HVeUKpmk~7R$f?NB0B%8o zOKXqLTad)j`kN6nCUXih#v!i*PDKHl{0xWz8HmD|-(X`lDu7k!ftnd;V=X#2y+ z;J63W@OoJX&V10s&ipb0Ty{Ay_;xM@wIE))`~VNwXhO`pr-9ing4p}y7ua5Lh`McH zb)Xfwh|wC5y?hXRK@Bc8NQ28-7VOYg4St55@J1BG$cewejs&%%Zh|JzyLmusZ05^@ zHH2t@Ca3-*wfvGmF6eyzqAKU>|Cfc}ky+6EI^?{-E(K5{%cECRMg!C<{taqweF3GK zoohj@dQdKWq4*1QmSMF91Ahx>5uHc(WYD$a9@>*Z_YCd;^*1~;FZftK;BNvgk3*gp z0WYp<1~0p3;BR>jYEm`t2CevFfK6pIgU+h(>F$03nqg@@P@?W*d4j+F2B@CWJm}kc z(x($F#oy`zn$9^6Ru0NaASe3tvfhGtI$oWh!7;?}z|L1NM}zvenLj~m_nSdR`}DG& zkmqOE`H_Qx0km}yl|T;b1~2IcEj<+S=-v$~qW)iK{=vlG@)R`LR1GTp z96)a9c202VUH~rex|<;Z(F_WRU7&#Q>1C~zhZcSwo%cc2ngAyQgNNoJQ2XYpN3ZA> zHGYN{=YN1)=4{~6y%}smceMq06XthNG1~dcrSswc3m(ZYe0o`3As(z#1JyMzcS>*~ zhTC7<{K*KKC~cX@%)rq2mtn^L|0Rj7Z!5GtI#)yed_@k_p9eXhSJYY!(G>&DalY^Z zIieXcvvAm_mvxgII1%xy@iXj9WI)Wl7#?^b{ezK#A3XlQbq6149OV_HjiP|;q;KCr zNd*$&;KlMsRlzlao-|URAQsCX2PuaHHt6D}|C1*`!`h>pnZFgZQWMhpVB8Dx9e<1O z|NsA+t9h9DTR~MUD2`blgPOCQNBLW3f#wT)S#2Ob@Kpr`*ukBaAlHFf3?RiTelRk; zjV32@cbE#|Tf$H~ei2Kn;st z-Dp{UhL^3N>+UCjHc@thTI=ALLo(f?SM-1i$aKR4zMU_2Cc}&cIq3K|Mo>sDPyq*0 zB*+n+FL$OOEBW#r6v+`${4J|MllvWBpapcyV8{4aK7~d#e=|Qw7@marTXMlYe&l2f zs%1R7H-l0J=zcxW2qZHk^=ZBW&E$g;U@Pdp%m2X|9?37jt2RLM1kKe34E(L^kOFp_ zDnG-{O`y%Zy`ry_5o1Y)2f*{!FTQ%n1so`Cl2q$K+oB z2#f7CkcRvYX>fXtR|dznN9X;Wx}d-VMfi(dpuj~=-{5wiPcLhsI#`pIGGvYU1Eey; z@W2aWkX_xoA^CDMsCe?|o(v8(#(yu=3c%w#AnwaN&>lf}d&Hyj-ODB5W(K6a2HNnk zx1sm{|LD#av5qm0vGDWTnO(XUfD+2fJs<;MFQ8oZ zh67q|`t-71k^-eLk6zJGkaFZS2I~KQ0aro4q`=-b2C0B2F~^;toCONw?yrmt;A|Ed z_Y!nzo=-392T*M9;9y{Q$qP>JtjokeEA1fB{~FZD0|hjA_Fyk)N$ZR6pBWimu7Ffv zmqD73f;3|AYX!tzvq2TSIxP0UhQ3(-8S18& z!I0WG4IbX0@dD8LFv!G2^BW0|Zg!vUeo)gFwNrWtw1mZ@`4Eq%%3{vM|*$j3{(>iw0PIyr5Aq7gZ-OdTV zpv@}0;QTomth9SG$T{Ezkd62*LC>sMqgB>$} z%O24D3}jgDC#0Xlq`=P*Xn0_!ArDgB!}|%6BVFHta%8My41dcm(AtGwR#gdbUhHI1 z*=foH9&kShs>3?(gZp+bR6a8@fbJA(K7#D}v*osRxg#fZO>#y{s1@?st+0w~~F3o$vP% z6m!lAKHa;)QP6t8w>SL@f6H%>AG&9N4tMYcZO=;s*Z9q#_;KvEwzS}HUCYM6;KIMH zfQ^5f2fHuheg173pebXngAX|z4?Yt(_=w}+QvpZLbDUTCw|Q{zZwufCb9|HEc_hE} z>~+y)@U%R_-=qc#=3ZAG&}CPkyXCvV$;HF+IDh*J&>n-%7!?k1CDP0KN(HhgtO`__ zpXLUwnepv>32g@7f6@Gbk-?`sMn%G-8|+Sx&R5>OHQGELy#+ikmSueX@6!1Rl!rm9 z;+r3_dmMbv48CC`GLFAxEfWJn=OGWvtNiUcpfU2+wlPDbL69 zKv}p)cQ>d)@Xv#GPxx3K z;BV&!osIr76Er*xNzO1UK!-zb0428$t2qQf!(Oew|Nnov6ja^7%TJ$P z*3F{eP*Rcshtf6VP||zH$gl%cWIMvw(1UCY;9+2RsRbJ6p5XZXnkVB`56!EN&Cl4u ztMz>t|94(+>HK*40T;V#>$egw$6mkxo!2fuq&msLmlj({aOd#Gk3mge#Xw*j)gRhu1Ke}`t^6q63zx=54BbPw)OU9CF*Ur<9nx|blZ@P9KcD($! z^P>yrt>%|ZB}uNGr$9oQr(8NOA|#kgJh4l#lo()_U@Z}M)I14t%H;K-LS zhd5e6DTKcz2$a*iCxa4|tKrFRu1*gA?LjPQodGPK-C!k1D`P;(5uC8lMu|Y@l>0!+ zNocY2@;UgjIR+R0?RAW4E|zti{H+xrp>1x=7H-`9t=Yf+|2I6~(R#AP*Q0qS$SDl` zt)TNOL5Y;V_3w}W|6huM$2?%=w<4q|-y{hNkDY>`)DDW`7ZYED5@+B4|1W?10QCnr z1o&IufwUj{0Xf{wrP?{XkkER_Iz?k6awVODH>1@hmt|Nmdsfi9g3@$GyW>eI{G z1uFmdb2Bi!p6k=g$^z=nK89GA05$|{7s!c)ApKw`+Cl6p6$EE=OR%z?tB~z-1*rwO z?4=My?Gebh1Fr->!_Ed|mC_)UU{hZ}T5+!h_!)Li;AUWWSqWKw^i2S4%_VVgVC=8KIY*@P_`7F-hEza1=e6MTD<0j}9^KgzFOEcm*0109=zRTRCy3el zw)6@3oI=m$J>c`N8M-Hcnl~QD8$gDD3P6wJ4WI#a5VLavsFhP<=hMr&ng{GU6LC<0 z!!~S#n|YvpdDDR}{221=JY@TjkiiM+MaO5-SmejD0eI+9{9|n?UO> zL0PH6N~H7=M3xyLi)gQg27{(BAZ_{$pz5jf_fC*ypi17On;q;Bm+lVG;#|;G11_B% zkdDxc#V{chIqn z&Q}NDF+uv*AVZpWqxgpj)LV%LU3kUd0Vx?k-gyl54qOt;e4QjOe7^3MC}=1NG|urG zX_YUi+Uf?+R=>FU98~qfSJ}4w1Xb3sRkNVoT#m3+vq&p+9x{R!?m$=mmPn$nq~&j6 zW(J*%1Um5wG>-JYYp4h)ExgEk1`cXaqZ(sf>wRwI zc{Kd%TG>QED|8${NwD+&i-_~Eb*=oZpo0p1dRb3GlHfgI%r!c%o`MoCVtfg_O%k5J z;p=OhAtM){$q?|O&C{S!xnApsk~gea6V}QVu+n1gXE_{wW3-qx~PYJ`}PVy;TcR zM5eNXja)B;JgN)n>+XI6>g$3Ab&vl8t)J;-RfE{yBm}bmAYz^gRL0+Z%E<6?9@2ON zVqW9LM{qfLz_arhf6G+Rs8~1Zg7S+nc{ctQDR3@w0wpi- zfmtutfKv=?9T2FU*ZKT~UNm@|2sHZT)5~fh4EEtp0k982(>1Vi`o+;lj0`@I!>>R! zfln{1u`tp)pf*q)i?R-A%M(z}hOYwxPcn9{2hFgZV+>Jxi4SZU#+n)} zuw|eo&@<4iO)u+PRWm6aThCb_m7daqyW0gvrXk&6CaJ;4=Y_gAX}8I6rxCp7QAK z2c47PVR^Ag&O`H}NApknasiL-cF;ygkIOHbA25Euad^l7|NsAkE_MXvFZOQ@VB7gy zKn)|0?&%O0I5F{W^J4Zm_&~$s;6nuu&J+CGyx1WWi^su-5)dXU|F$4DkAn{dJPtnM z@ZdZFv!3&UhvmUyEsxG);69{><~fh%fA$`oAIr=GV+4r9(1+S5`O3q*bCnKplk>!T8@LOe^A%Lr?#5Zw0z+7Cs_z90M5u=dehaQy-fAZUAtQ2XdIA1&KQrre-1_=VqHa9Cq) zAMIx%zJ2tJ3)DVR040Xb`!6hxA=*bQpvG-4>q;hY9^c1>**-dT2V6u|vw&610jbys zOJ9&C!HPRrT2Rn&QFwbp8KwOM-~M(M)G#Hp83mf5g|2G_)pIQe2sEP>fObT{no*Pg z{{R0n^DnIZ2K82CW?>dKVlBDsG40}C4gG%*0=nn3e+~CF@()y7Xok7rn6&&rXOqe{@b{2Oq{&toixSf>- zZ!dKo@<7>-1v<+B|2pgd$&zOs3r%s zp4VY(kIf52ZjY^iv^X^(K0s-YnchTik1ggzYL9(mLtmGzavM~V5ZNC4Zh_n$!`R%^ z0ddzvkR^EAWBoVKU0I6k$^ei7pmj9RBP{N}sJjKuW=H;l1JD2@kJcWuxdCpE1wuml z8V5MbvV#<&wXB3ecK5PoK#Y3I3U2I7{Qn=dWn~MF4^YcW4%|S8?r+uujX{IwVSPIx zTT)*pL)uvnAaOet)FwjT`Ly6VBZE)p_ZK@n!R@Tm5T#iVt1;SHhi-t90;rv}7&1`R z3Q=zbR*$Wn2QrZdPi(%~qP(RfJdipPM`-4}5(k6T@GPYriHyj>h?+@l>29<6v)UJYJ zkQr+`VfrsVqW1^yV*<4ket;sc^ZpBgeHiV8#$VusG!4}D04+;K>M1R{0?xa^kW^d( zQi0Y^Xt+XtJK^Ynwi7_7O+xxNKE13dKfz%zi4jx}p|umI$KD zLWUpqb^^7=18}qx61@=ZgkCJ;F^({KHh$3Qmaw4&AINyjV=w&e1kRTiv5vzpI7whw-T+6R}B+XwT$gX87be?A7sPPFzx=w(L8zGY|~@6*d_2T^|dKOg9-dC;p_49kl-$(i-K2_yDDSAbJtK=51y~Y9Czt$A`9Wi{&z?1R=70aNPiJ`yd12u0oI{ zc-sg07tmc9itI`QkO3&|gSboJGG@tFZ~zK`bODqUK<$IokoG|;M7h-jd?k<07RU_Kffr}afzkzJtx)TM5&@5H@B$YP z%M1KX2SFQEkAvkvlUSg|knp1?JUZ{&FnUOS^Jsqd!=w2a?+fEAusZalBxo3o*$1*# z!J}8y;}0Kbu|Ux`K8Bt1c%dhs;;AW{5AX)O2)+Pv7-WfWcQfb+1&?lS7yfNrE}h+= zNh9bE9_SSi{M!$Bbb?n>fOezsx31*`FONxL5Nde9F=Xd* zkYkuZ<0GB-UmQBi$j}Wo8gzJRuj8BMAI$vipycUdnJmKJ`d1p{Eo=U5?S+gC43;nW zdtwzqBH$TplvT*xn?Y-YJTxzWPWNdK0jqM(04;26u>~!H?RF3V$uxB+f!1V6Fz~mg zfmZ&qg67jf67AEK85p2TuXeD4R!_BNfD|x-@5ViK2^P>B!7dW^fGpbi)&ySq#osa$ zv@ovu059nHX^`JQtEk{{vF8^bLx4{&YwlM*hMfU?h{$;H2$T|0S&Dif(@a zu=ne@85le*&vZMm@Hbh2eE)-;zXdeu-0K0psif6c6cnWF{M!O_Am=|x9%z2R(R_dd zbQDx~xP-6dTMzAU1;^&69G=ZbIlOv#w0tZd^0!|V0qJW74F!5?Uh(MW_SJmj16hXF z9W3F~33UeeH2r|?W>5m^a8ZeXrk-wR5&qUclHdzOB0EA=~% z22DcA@AyC$h$MUTiWdLmW60tK4a7Esf(5iq02C~9&Vqu)Im4rSGx+4qZVv?y@O2wC z91b3ur(Vo)2G#w}89u$NChx$doYqf12FJ*qT}Y<BFMf0mKKyO%}Hre9OnMb1`_Gm}uP( zKG-qf_YDuc=syK6V=sILdnEA(xQw0h|NqW3P{M)@oa{UeD(I$wrobQ*+bJH#kaONT zU+}js0i~A4BcSpLG+zx`#ON4*7-_YUPcJLmXFi7ikWAGp`s+I%!_HRF7%BAN#1|T8 z7#ZRnBRsmh!HErYQ)-_m1H9sa}Lswcs~09EYK`O)xF^8+USsfRomFL_vAEI(s-spUWkE95F5%ZvOi&lJGJ zv^PJ2gCXiWII{>KE$4lab`o3>f+pypDQWj7BrmChyp#-C!ia=%E37e zTx5E5PX?E-2VZb>fX0Nu97sLT5x@uwC+6f3Dn)UPl{}qh< zElEHB|8J;b)Z}lCfYjIbKJqc_tm9{3c&Q=7z>qefj^Bfo!I6OhT+YsrMJs1pW`M4* zg(M07?OJIEk8(8s6ye|gmcON#je(*0r+BFpXq`}rhzI!iHgFQRbmri1ohb*ZaJF}Y zj#+lGJj>tG3>t9;6}`Qz&@lzauY3%lpmhoNcPfD58l?G!&vB6NnL*o_AayIq7v=(> ziUG8qNLPRXw6~&F6(j;#EWzI@AplAQyFp6@dHLJ8K)i17CYXjQUPk`bpZwtDxCqiw zJN*TmBn)9Tf(ohY$3T{Hdvt>r2YPg`1}z=*>0Sw12;$S-37S^{pH^~ChJnEsl%0-( zVz&8*4S(A*K?a6~S_U!x);VAnGXsC?BruDGfxoo_%o1kcZ>Ty6OTa8q2L9G8 zK~SzLwf5)@IP770jlVq-ED&O94Uuf4jRN1A|ZZL{Mqq)4h8FXdb<{#MIWO zdvgb9AiH}p=t4^0&L2LV*L^xgRD3!=I{v>1TF5+yi-F-Yf4(cjzJLEgH!)680c9AU zZV?r*>W|`V24TvOw(zXv%(Z2^^^KA(7T&3=9k}S-@Qh$UJ`OdvN(+ z^%)!?>p^-!NdOcfZbv}{ur&jJi?bxyuIq2WYWW~);X^qfwNfCpkW;3RCU`pUdm!e2 zjxm6;F=#2nF$YGj7dP&}S`2xhKF)()O5EJ+0&y++myA?F2;?#Uomd01X3m-Em( zrDa7Q%rURHqi zrA~lOj+WGWba#U)HvVmn(5YF_#3*QL7PPZ~-2=SA06H}b5<#1q{Q#aD?S^>!1b-Xo zj3$rHr&{BsN>bAfBgE*jL=3m?@f^a~g@j`bwBLheV z%xO^p9m6{T{XG5ZiWfVLK~1=3&@iP(uW0sra1(hC zXhi5ZSP+~m++aQcEsE}Rt^nUszjco&160Aw zH9tT}$c2HS+nJ;J1y}Qfzx*w8AvKy5s2jxz3in>ockfU#-G5L!4(a^JQ{WvhGyng8 z*$7UOF5uQTf74!Qus;ST%$4u>7w?{^c9c z)kMc#R4hOt25J|1K-ZY}x~MpSnyenZK({VAgIoja9Kzb zoEb&-^cE)2eoT*Uu+<=sdxGvYh3eJa&cxtr`MmT2SRN`E0dp!SntE9?pM#U3(i=Vo z-_DOa&w^4Sv>gn(?9vOA2)ms#d?3wxpI+A85BL}y<9(RBLAH7HioSTw$FTD}JNRx) zSlqr4hHHatM}g?}=q}KRi1KLt#@~_wI@F@q?Y~F!4?h03S0bPjmfj!m?EL82Ys2Ar z@Fk1k0mDlZKJ(}Eu!HVkOmpOa&%@@?d_cg*@>ClC>w`s4()jaFcyxa9Xnyn|jlcLN z2T0Vz@)}4O#AgBVVH_r84kL&Iy52?rd;qdA8V&UIr!w)h)nDd8^b=VVHTtMoFfdH; z>1F-+1e`vbUh*-3%Ra}QpwbIc6?Wc#acd8#1muPeCGfZKfzGJk2U7A9bea#eKJ@8j zJq{^utw6=7;mMsSjmN1VL!djDBZl8Rjyq?7gglNrgRY2S0Pl-`F;^c{cV}0Cu1PNP=-mr4j2cCVeshAQL*spWtDme>g6;{{r?{&q55b(?R9+u>Ly8h z^twI=Z9zNuf&;`C2NC=rv4gJ!Kx_#RA>h&L`q{A~L`4il@Oku#T0i4s09BqKS#GeT ziwe(+sXIX_c{3=*f;WMKb|EVu9pTVg@fp-=nEL1vRJ*ApHeJi26-Wq50Xh^CLVn^Mc)h$jpY{LG74N4Ug&@ ze}T5A@V6T9gBxYN0biRR{pD}T;bCCto(MWSr}a{aV7Idj=*;p%oz61+ZG61o#;w(R zK8Brwpb>~`pTK8z+_?{SuMyO})yN&e-W`k#FJFHKMH!_35a83xYVekiAqcb)s|Yd* z_u~n)lLKz6z2Mvqo)QTH%~?!X!NlKs`Sbt(FIm8G$^be#qE+AfX$G7#i zPbYYMj=$vzXjHnp88klX+5DT4f15x@xC4LNS{}9?pfl& zzb$~vv-vl7N4QssHE8RsXY+5Kj&Sc1J^pP0Jf6+Jc{{>=N)-9G1@L+{|K{rm_bn0O z-xk2<+5DToBiyfqoqwAHe}}t2xJe6b!h)N$kbzg<&Wk=EbSvOR-BM_47Bv3Tx(?Jk z2aUV(xBLUm&-SwJz0Sw5Q-+;^;br|RP?~UH2=MKE=m^@w6AWtffVNA&duYuk13sf?79^45!qaL1b zLqYCPfaLFvYkUklqu3Z2UV6O*ISjIkR`ZZY=Vb8saIXpbi>G>^{v)IT32B8cxQ#R* zI}KDsbl%^&0_0avTlB?|t&ol8JO@BWDI9f-d8zjj?09R4t5Tq@5(BkfVWrQ0kn6jC z|NjpfO>0el`TzgRHQzx_0=J;SXDPi0*AIxct;;L0Cq*BEI(9pwKm)n32`a_SpfO=& zH`~8La`U4Hd<;8dK^i~~1{Hn+AUAhAXF$4My*oe^oKNQp5arUj0F?WEIyXFEWMDYn z@B-vy2L^^0bL_!Wzu^11r-JjTbB5zi@K{ZA$ z9LS)`+xw70KOLkJA|Np~Aa9W=}|NsBxyXXJ^gL1!5 zFYD{)d<;APu`w_>2ETNGEVvK63HGHS$VHt8cRoUPPR}MrhL@M0{r}(XEaA}|oB_UY zX9;K>IHZ>gofvWE-&Vr--?P`_zfW%(qepjv>xE^69ZoW>Y~EvqxsOYw?^f^r{w|u$p<_dkAWiD)$o#s8@+k!^%O0W7_K6T`ua@3W7+go47t3I7CJPtnK^wE6bsd&U!^TBH| z&<*v=SQr>w4d41$CrLQ+PdnPEHK9={&0$upGxo~m&^hWW3grlLt zR$yTspWY~5kZ=rCSQ#t~76SLOT{sVTSY9jR;ort1XyhXj(0K~f!vxji3Mj z&%cdD6|@|JpW6kpro*QjvN)i(JOw(g25E!6xSjw1|I56$VDpZEoYTP%o=KPtG7nVu zfNPW&OI9;7ytID!|37$t(92Dbl3X0*06RVghL`!TKx-*qN4`P1N5=g zbC>`Be>wHR|Nkza`4rGH4~RpKflAF}Bohq}>}}}y|Nq6=Rg4TTQyzdFEpnNUVdp(a zXimKW3(cobL5)JtDF?O?WtwOC7&dL1u5o1anK9LRg4Uff#J&#gPSk$G3@*daS#jKK_O4!b?e1TV8dQq z=VJ&2^^crTZU3;6k-;&}5j+;-82^$H;sOVVrp?#EjpBavMlm-ievrn{|33lwA9QP@ z?S068*f$sX7mM)q!GIw z63eEbhJ8LC=nx%v8`g0psOUxXJ=;KchwcOQI$w5w1c&r@h|_<78n{`Yz=NjGy$$XE z|G!{d$;j|>#yzmg=b)e~0(CkKL7QTFSxxToG3*ovrPQO(K?h(MUIGnszHEQ}|G%r@ zlb3xE*ZH32W7t^+id+7B5LKX3`sKx6|Np-Tk_E47tA_MmOF(YmMrn1uXjlP?ZSbXF z$DV^i4RqEP`#n$(`s>U8cR?K(2FEZ*&><4Fm-rYQLqa`zMYXT-F$8;bw}VC>cD_e= z>aRz)gyDe~Ix83%x}7DOe{l1+fzIFY==NoR47D-xx3q>KFdq;5A4>pb7WxrVbDV zYV|OI7|j+8{4G;JovCin#R3740Nf7B=gI;MuramwFP^Reg}rM6c$Lx*u(1trlR#}- zpKc9MBfjMWXvDQUl>>C8G=pd3pKtaI4CQ5x`~HGvBtiF)cTWWkRQYs1^yrOX2H*I_ z!{mVw1YI@d16pMQR_4-?!o=j!y%(b8k!N?QfCuCK7v=Jx<5kxxFflNA^|B~~EnW;h zkT;dXr}LAC<{=-;N5xY>O(fr59~NfN{V!Me7&1UDIG@gs1|+?P2VR_83h~fO9q?>1XnwlemBFLC0W`h|N(-$l z|Nj3s{BL;4@FY0KJ-WAooY~>W;?dm;W(BfBW7nahip`@F?0l$`AWj!!U|=}LzzqsF z(1qUKd%@-Y;ZjqdUe*ie!C|cqYNUeVYG*jieJ_lcGBO~AGHiSibmYouu%=I-EYx|> zqx1dFV300Qsr$lr8H%p72_D^zFu$~b?!y5GeoGT5j#@AAw>Ek{9@3hV{1js=GgSkj~QK&eT0sRXEn=LnjcZoO1;?)BER2_C%xKYJ^FdURgo z-*yd@{9m2~%}c}EcVPQl8~*?Q|N4MWFYA(XV85t=iYr-|FTlz6h0zj5hTRQa|Np=2 z2QQTdHC!O~v4ZYxJO^Lz`syNhy=yW^HzZYn?j4kX7AZd61|Hq){M%N5lBeN;7k3sj zGB|d-NjMyM%hddo8C;z`yOSOZSQipp(~||1!JqyS#O6eOt=W za*4n7E2wbky#JyWyy_Kvj6D;0g9>xAGY2DNmKVDAVFe?9YZ@~H19;LGv@->Iu2+LS zH-Bp-SQGf9Ebx79zLEz#n;-n}Y(Bte!{{q{(zE&D56|XJ_A|D_yV-} zsr42MXsJzgglBg>cqm%zMZyfwP_%P|M|VZWi_0RQ5t8bNeV_(B_#CGf3R2Jw1nwF9 zEpDK($j;+0eotp$cpZz_9|&1=*ptxeV=ZRj29BiL4~>Yw^mS`GnR5SSMxCPw`763n5}R5TT;Nq4d_Uz z4GTb~gVHb?C}6rLtN`&ruITOs%`N(LH-m2T0y)U1*Q4NnX~F-}j2AyAF)%=`)#Gnn z2Gsx_V)5y&$nfk1pUMAXiSxhztp_~1jZ0EJy4ygj!aP973U>>%UMf-a?Cu8@YTcZc z7fZQbyMX%BmKRHGJem)%fE1K|0qYp}uy{tb@ z@iFYo0xfFm73Dj}2fr59@PMn~lNT}z7#ToW473#r^~8P1I>l~piPn?+tye(>T=QQ86f3~R8+c}`!Ge*$1#}%G=!(Tt;ILi*YS|tDl_OA(86J4CW(2lGU-y9G37Y>2LqYxn`3ofO(JNYY2I4O!P<%oI3Y@+!&Szxkc2VJQ>^$-^7Swj__LFG- z$;IF9z{tP=9!v7L{9r{Be=BIp>e~@E{#Iiq28Ik^3-vX)|96<>fp+fDQIGCI0gvtqffpGf zU;l$ztZ@)~R)U(=@`4NuFJFVr0{h4C8$2KX?16_(i3r&5kjy*dI3L4HJCGN8O?!?b zg_T4vNCSUMGN>j${|{7PK=zNnUfoauS}Xe#GH`wZ)GVF@Nr+QUfwDQgZ?bd_s7Q5= zaBP0dc-*1D0W=ZQ0?JK}dm6g`|9_baYae(r{@~xv8+M0}Ah49W;n! z;lae;DhgS}R0FbWBPiib^66!regGV_@~8M19Cv~$oSjdQbB+2OQ1F6|tAEMz=l}nG zp!xBaZ$QnCZejTS%8boF*!f$XfBgU7a*4mi8q%RzeT78^`4eW_D8FU3PBdAnEY_K%}n+2bnX^{ctSJ>GG z|3Rg81MDy|Uf7=c78Vq78O#l~cDU~mybP|%nu$7xqn??8r2BWbr-R$Ppm}LV+(%JR14^wGSIPopkwK<-aic*Yk(!vS${zTYv2+Lvi_N8AJ}JB z#~{To%6-67A)eQSC=rJ$0WBwo7ePfc85v&w1(#kf42b&%jX~Cc4#j}fUyyaDtz3|E z&T4motvr4db0_BI>Cjy%uiHWOI%vb_Ua;a>M?vmG+h6*12I%~W|Ns9Zoo^<{4{Z+P zICtYCA5Sfa57bHNjsUeE zdRZfmfqi)MFxZDE`(>X_WkfsQEch5`?i8fcqgQmiwGm`I+9@2pQ@{>*v=gi%{t&4C2G6mgTrIqPDk!gkcG*gR zQ!n!QX2p>9KnBDF*+bx&U(mL?!>}{>Uub|V>HPk}fekzY84OYS=^)s$M)Vtpy}*_s z?W^U5s6PZ&k8NM=iOHaSwUF`yd_HOvq?Dd;5R{NSIzi(J96P7N3gZ_uCPRks8;-!L zH8w_2i3Dmlitx8af?9i!z6T?Jt2BrQIT9VzR%?|NRG5p@yJ| z+btjwt*;=F>I85_7wyq_T!EE=;pG%DP&3^DG)V5#%PPJXJV@@I;L$7k3lvG6e|P%A zb{M#HFKFQc%_O||KY@{Ax8QsmG#ate( z|2x}2j1s;VS#6+EpX!Vk51By4cy+~#8z4q6_#UDO$Cw#Bnh!I2SRO9@;?Z3j@M1Se z5@aH1ptQ3zz~i`U1Ssx3x&u5sS}&Ezc{JAvFqDY9wmvBp^60Jz_^9zwHnxDS!uAjbI`7;#@z-tXk;qk`K|K zK5}isi|dS_kch4DIPMDGT>=WdY>!^>@ue?Lae-Ed?gp)*c~R#8W`kFYUMc~t5Zw)0 z()!{Um|b1*g0~j5T68zai7yzz>}Jrm5=44~hwlw=_*T4#2HV{V%AG#Ftf|NO7(9-H zd7wbv3EEcT(dk;@(JT51RG)Xg2OpMcc;E$dKV&G|qw~E_FRR#YKJekt9=)Qc_V6+6 zY=L>%@W9KPec&?tI;7HA4U#SZNrN&dWaxE!A0q?!FpIPa;PUVeL}>%q#^0bjKi+Wc z>;~xpl?*RB`annggVy`Sfg6mFqN}?=!b7`2!Gn1-sQ>TL&Fs+$Zb^bR#55mc;omj^ zG&~2&3(b!h|4-lm?XRqsVDy1(ZS&}z3|eaJ*;~NkA$hX-p@c{CL4oFK2}Tdlj&KQ& z<^wz)oz0-d%%0s9EFPV^L8?2C`*!$Q+OE-~x}%2_L|n zt;rf7*L4edbWZpI7H9?u7+yN=018%yw9WuFk8ZG{1}8?w(jq8_e;ZTt0mhaC9ZaS1 zKE13bA=h9%2E|e5{hgOU&HzOvq=|AYT=(u$A14bK0P++n)AK>bA z=6DG@8+d|8H`o!-^4tSn4HbX}zC1deJ&>xQR?y{0pz*)*C6J1+)X=Aw^&rGO!4UTt z9@zOAfWn^~HOT4v-&S)PlkhxxVn|1}7)zakmZ(@UzOD z6MTAE{Xk`7ujuuid<;851q3*Acy!)(fgfXjryCR=;0(zCiV&n@%mvqj-7#w?I68xo zt{rpieEDK=H{oNn`Zv&l$@56Y` zqw~;b{(Lr%gRfXTntw5+@xOOb5h&&V%%ATkkjDSsM@0d&><6@g26R?u^F!oKHU~k% zL~XKRQQ_YvqQc(-s(|>n+3-0S)$j;-G#_N-{N-VJkiWSYv|9lt&iTuux02Dr@&|ug zG-T0^k0GOrkq?g`Xx|M;k_jwn2a{w1NeaOvnZc5ZFiB>Rq%cg91uO|VkJE*Jn~xz2 zNKynQ$qJSP-2@1hWCclz!X(+ilAvw{SdtASDF%~d2TOt${edOfL6YJyNe-|isP+L% za)2ZyV3M33mOskM!4jaoMLd!)2`-RC99V)2q(cfO!3~md1xs*)B&1;yJRk`jumlfC zLdK){ATLDH@Mh~t}T321fV6kM=$755eARWZqOEz z&V!!aB|JYoI=65i0hN=z-B&93HZz$&{-9b+3O=7khyPg`xRzx=c(pLj19jSB`-BU`rUkpzxg3kXD~;} zOrP!rpe7Dzf2ta&z3ka-qvG40qayL5=;xRJKAq2fJKwxW|M>;9M7Z=3$ju(mZ9qia zCt>B&%No53?1?8^K&2L}nFK5MUc7As`2%BzkaI%wZcuMUti&EtWHEq;Q$hDB^|DUf z2sUxj7I1mIlM56wpu!kbki2bYWO(@)JO~3BA4R&SVJpNa=9i#_1n}~Wf18LQc;%=M z<2j=c6@i!if6(@CY}v%euu~p%fJ?8a>=xwtkG&0z|Np=v)oj8|u;7 z4Q`Tk-uLM21}$Rn=-dq26Y9}98N4*K6I^A168X!&QJ`)Js2)Q#q4@x_NApo;54iE5 zmWv0}ZsLuf7lpLXVA2Y3x|0G$FDMf_?gWL$&VR5dexc9;PRyVHdtvhJ%YTpK;GJ%u zNZ0|I900i(avKUFenW#jI{$)3=RKg;L>~uj>tkRjG4|*cUB40B(U4dU?P$QVkKutA zTbe<3q4hF6x?4e(dvxywMG0hMohx)>T`Q=?33AHuR#4Xf!h-LnYd*kxycM>M&f|D1 zXiWx4iAQHEXrkHhK*L_}zO}T@R&b{twC%1HltV#^_yt=*ty_=dt)M9>kch|eR?z7m zASSkd#ACfEupH2DQvA^YxHK{rN%aaZV8J6G^lyBFp^zWn#--U|vVsFD{dV4>Iw zP*eniwWlMxQq^E~b;XNA`G~I6HZU99m4c27!23~ymD zgQSnULbly??*$!40_vDS1M$TYuyiX(7#doj(N8e5`3Q$c=lkf`mm9+W|L^?3zim4x z3pYPtY<~8S^O!>impgAZ7mpS=gG0CBn+}7{BPh@C=nOA_ zxyPfk+`ywV+yaTO;L#bbfyB4)=nPlz==9g{>2xJUWlRxcTkNfAD=&#~VPcD^LyqRhF=eG!KL909{xAV!{Vd7KCiW1>I8*TC)Kv zqa9-p?_lWv|Nr$VaIyUw!h@t7a79_p;n7(x;L%wwak%*xa|t&%av|xt`6qMv2an{F z9-SuV!SUO<9aMlGhPO9B_j!E@-Uqtq-lOw3*fSoTS6`%j1=qJFz8>A(pu*RuQ^BJ% zgTv#vg8(Qqc(k4@Wq+*%O8*xi(s`-wvu$d^!(!9DD&j%@14!c{ct4tvcmz2W?jXO_uw19s~7hJv+aGs@~roomU}Q zZ9k-i;G@C;&XSZ9bb`Ar7Yux|;|6i7ZSHQS1Fn|;6;j{@po&R6#c#ClM8W8(9xakFUHo^yx zv@sVv3V;;epkkt9F+u*&U+BgPx=H-(H@Vw|qL^f%2*0XQ&rBpqUQT{{Z{r z^(*-NAjFloz`~%E;n7)M;n6uAR1kpc6YwJK^`Mv!_Uya@8rbgq zXgmyRNW8f68(fx%_;fyk$aUU-aR4kCTdjCblRR+*PAs&zBBLX0& zq1=ZO;?uhvWF08=db33R3hTyk;D=7kJ;y=M2o!>n=KfjO!rBqw+ z6`Gw#%k4aR3m73W!T_oeKuNVl1+;9i#27qJeh(BEOFw@3e~d+g0h&({=>Z(@_~RqO zqcc1KR^LMM)*nddfNqTGwgc5q9-f_F4R5~|pWxB>`~QCihH^#F@tB_7wG1Fxtrw}F zWXgEhqxl7+hwVQG28Ob0pix1v?Vu=t-wW>9xkd%tC1^gv0dcHjEOPvSmen#cFu=pd zp<_F!DcCU`bSUcKv69Vea$j+zx7%dvt=YP4ejM2XzRM{O8fR z9nuw?4(bXXo&c)Z_k$`-h7xv6t?+%xMw9J@FG@E}hdGxZb zT*Svv;??|%*`t?r*&=YSaRrFtcRd92XY(&ck6zYBkV3^eEr_<+Af7NpTLFm8?$OH{ z@QWAHc!absS}%F@vSuyfV<>$LcDYA)xQ9o#dqH;)PwOTA*14eeRO^9~0FUkta6h~C zWJyeSAWQQvrV^c^pypR>&A*t+y*#=D1i;JA8$b~YUfB&AY5&pdBESoYr|v)n59R=k z*EgZ1Ew99qdCUP@}l{4Ts@@o$eCQE%+~ZOF{RUg3eZLKEeSCxgDUZ zQIX@bo86p)6bQ)eYsd=h zx(tub&!FOo@5QrBQ1i4p<3;QERfxeSM+y%V$Uad=?c@1KW{5 z-$ez|#0E1VjchOz(#!@kAq{OX6VlWMGa-#_FcZ?;cI3}*Q2|we&<3|>cZ&*Ai`%og zMkRxx!~vjBGx4>Ny# zZZBwXAHY`xn$3YPM*xpg z-G5Q>n~9_*?cf!9B^}Vhqa3 zo%dgOM=&$A9w@okdDN$uH4kEJ`8+;`ooG=3I(NyZm$d<+B6J>T@c8A zvNOopr_j?R5YxBE7#LpMYKMd#=%)Fpd<;7eNiZ;!YJm2?gWLfcXoMJdC?6>;!RuGh zsu+m;qBItW_N0_x3ymutMZ3vvUpdqJM~@dG*Q@VD&!_y7OP4d4N3(7JTZIbi=7 z&BY8vyL@m61H_X{Kmk59t`FF5mIk=YMP9^h?TpaErA zo=O6_swoVbr!M>SvMz;~wGQMIP!J$u^Tn<_up72RRP@XN`T8Zu*Q&6Hdhr}BsG#|d z;Rjm2oAkO3w7&sjk`u_?Xo5{ctY z8<-iusr#icL=sY8A8!KPx(!NUFMt05?KuVQ)C~fkdkJC*g2NBB{Q=s73K|09Z;1z; zst+!)LRlFYJi%8fJ(&d#qtoZP)=i{*95pWhZF!S2c$Kf6IAL{4|4(`8@?X(W94D5Mr77Y~&IW zl0}VkVX?Xrl)1Z^!Lj;)6)B5?`pO^&tO$X|?0KJF*3&b=?!Ph%4GLJ0)~~sY4AGr0VnNl4V;oZX z-(1bZ%-?bkc?OHw;rgM@#x+S z8WDhtCxgZL`zAvcN?%z9_69G+8xF{Bk<5V%w3tyUmLH3#gNUJuB4 z7k`T|s6K%0i)VyYJYwKF5*#isUqJ7R_Jp)ogJxisZ*f_S46cUXUT#FJj{x0QIU5p@ z{4K8k|Npmb=mj0tb{SGPvOo%7)*0Y<-PsO{R8agSWHU0n1YO+(8Mgto(HQt!et`Fd zF#GhfI`o3W_9VzXo%g|G$lxL6?iHXhX~P4cWgTZSu`lZYI}v1V=lvHiQeo~h1?`~u z0-C^twI^V+wfrsjA)Aa&_kb-8fmjNgQ3Bg*c;H2BCaC-X4`G8BuT2Iy`sETtI`ilj z?(PD0@mf#vcO-%KNCyivA7Zq;*cmLq-wVAKFMbIh!%jBP?JT{bucsl;n7;Uv0hC3%Q{GQoV%Eh zVdrMh#V);~m0*LwB?u@My1-_@i^QhA44`fQ=b`JxA?5i&(Ajk0^lSL` zu$T!A-&S2l256Ff84OwP-C)U3D${we`5{xoPo&LmJRX+apmC=XL7!gM8;kfDb}o}- zV0an#|Nno;<-`spV%?KL6Khe2NJ zy#HcvI;hl@VA!PrTK)qn@=FBx`>r!FFziYJi7 zABXvJ6=)My=lvJ&gP^KfZa{Wesr7*qhxt@UYSsqjb(oQpGC;>SK+Y9mn*vtH4{EQV zRhiQ1pa=%lZJxj2nPnLy?OmS;)^=kG#Q5pRQEdz|-tyP~|IH7WZJ0{gk2{0gt3JJ~ z%O-<$Et&#OAMoZUC}!5BL5kXMHc*eW;%b4mfQkp?7N{A-R3DH}u(Uu!(m(+XsuUrW zCZu`;bqt}67XFrYXjq^0MXKK3OaeRh>txKrg)t4>aQO#Oad|Q*5YSqndg-tlkiP{~ zxP$h7LXUIq@F2|qom57Kmvcb-YI<20ECBoc zC&&T;P|`stV+AQ|`UAQI1KeKXM`|yn{`>zQshJe^59Z*tkW>@V4mNfX$k_Wx-Zwn( zVqFR-v4O5D`wu;*-!aUmmsM#JWH+2==M~Tq9vPGP7=pq5I?v9pJD0*-_M#vKw2|aC ze@iR41!D}_OxaQis)WF;A<#j=KE15GkVLBtvL4>7g?L^+1(rxa2OYz@DUOJ43TV9l z{)-jf&;$Z%*`A&N_RW=vmiNrouLmlR^3L1#c=NN;?O9KD)+S z)R-@s^11_7{)5^-F8tfJfd=UKTfmbb-;O!(w_F4zUhFNP%3Ii5K#5>+{=R9DBKH!g zd(tb)JrSHOQCdLa$&hU6{}GxkTg)M~r9Q-K*C&9y25tqdg?r5?1;uO7aVMW%)|vg_ z5L+|>Y!N(hg7be8sD5B};or6a7=-C^h^53(!lo8~P)1lBX=_~mE|35?p zL-SF_mx=If?#zz<&W0lr1X$fuX}AEZi9 z=?91Y15k89v-JyuL`DYC`0Eh?(BKQSz65t4K%G0EUe=8r;Fx>S2iA*nOURc5P|Sfl z$B^zY_yiTSTS7qlK|q83#~c_LUTmrW1zf3}PcLge#M}ve$oGRlqHsR?4t^Z57Umnwjty==gprwxzOR#&O$7=As z;0%QhZZ=#5^$n^kUX+6vkfkQb>r6oQfG0<9F)=WJ7v6t|G$z@5K@kk{Wb+%2ow?Gm z9s++nIFdn&kwBS7*`s#?Xgv;SL<@PG6nYf+3kHz2-QYD-Amu7>cB%*TaP8SQLGb}OT-%=?6d!v*nf%3K zXHa}V4%eOzX4h7{xaSRu5Aary7ni_n=;7K7=;vTpyf6aW2EG}DzXjB!n8Nlk<~K?2DMXZR3KPykyCae@e#U0d-Y8f-1Z3I1R<)CrLIM{`GZ1jr_H#Bts- z!Jx>l&3G~SJSgyLD_(Sg7?9(=)&pp?*h zphVV#@dadE5t_ds$~)h`ICBosGcsOke(;C$2j_*>ZyvpqLB(wcSc<>(3uxQMgU|c^|NsBLp&K+K#@Kwq z<9IWus|A|NKHdyc4U&cE@aSdz$O&rrHG|hG^|F43@Id`F{%y^m+OYYAO$V6au>-WC zJ>eB-*0~w9+$ycJh~=0Q+y7(D>7dfA`6m;9i#4dv{g?4LxXNN?W@ap9KL%cW&h)~A z9b}QSgh!{dfJdh@hes!9V(Zcp&>|@CStTC5tRW{189X|{w=TWN*ultfycsl)4ccPn z0omjSUR+~%06ap2JU(;60k0={Al`y{k`KZ|^iod;K9f2(TN^lji3oK$hJj~URLJg@Bs7ZWqo=K%mYpBg8~XMv+dC< zI&(T719;5>$a)^Iv$}m$c=%gEEryq%g~Ukfdq5NKzdag{gMHWhhT}!|76t~7-rXQ? zy{HGVp&Mvg50sR8bWaAYK>OhD}8;8nfgOmn;$WE7a$y&F^-f+l)|LFa$v3%pHDST zN6eiUKrI#I`Vh1u1hhVtp@hfqfJ4K7X8tJ$4nAN4Rhys^kD>WJBUqfZoX?||Rc{7( zlKy`;%MOqV(E5P)%!UV=|Ff2JgW_}s9|Qk377L{HV4$h2?g`*{ZM{??-^~S1ldUI9 z6fDn`vb~nD{7@p|(R_%do2m6c=_jxxcwWKq0LTJR`;*0F*Wdh$(J{oc z^9(p$lmvp0uz3;g4ocgdM?HE?d7AhbN|m6aocCY!f@L7{$dG9|=sF0;MjjQ=AxwKg zMbPW>@c9SG89HSehTlNt>pwS;!6gAGsy%v5=Qi>&yq*P0U*NC?pR-dc^X&*5_^6c9 zBZl8r$T;v%J$CRJ`|Iuepz&VtIR-D*fz2!3VEERf*EFJ$kKttyc);9&0c?W+0|VGS zF9ZL>+B?m^7)!VvIxHPJ3K>E3&7B{1fn5CkM)N-=ewRbd@0q?|?{H*v=&*#$7dHQ5 zol_eKnqnBI?EM0!&T6| z0xmB)e}huS^A|0rzWfK<%`eF8(d`0uB;>N^QZ|p~BMQ-nUrapt<^Ke*dFUo}yQ{Pw zC@JxP8D@S8RM3b(!$(^f5-O))p@Il7P}uyl2VaE=3na!<-QFr5mKV!-x}6n3*GNx0 zz`yMPTDS>7oWw8B0M3!GAH4W-0u)pZ44@^1pfygR!5)ooK;ua%9^EqF!1m~zqXKF< z8s2XG-&v#Lz~AZto?&KMN~@a>fzz3m`r5ZePpaDaVcc>6`f2~hMx7BzsyK(Z1Z-7VlS0dW*w zn4I|X9~4TUrhrHH8nBE{ca4g{YffML4ukYUbBPx)9`pT0{a3rJbX02`Dp&c6R#eYXOZF+ zd=YiVix)>J3J?BfQ12fcCj3pHQ@^@%R3LtO(RcLAf5Y48x%%K+rq|ri{s$s{lfbT^ z$Ug<3_$_!Lfou{uX~L4dwvUPgzRW+%E)nKgsFoWzimx7r-5^?iLjg4RMJF=LJw< z$6^t-f85q(!-FcAog4)xd_3xd3Ap?4Q zL0h_B?Ai-Tw|ha&2T&hNACj(py3-9jdO`b+et_#otfcD#Vrzd#yhG%$mV$tW1qOVo}q)E}E+m!<}94G9-_~RHmg9qrgP0Qn;QM+1! z7qVc-tp!bddbGafZ*>QaX?NcD=w1&N>YWbSrRdT51Kz(tcBhBs{Swv}xAuS*AA&oJ zCqR`6sFjaM|0wMo?ENFZ?Zcz{d`PF4z1I+7EKvMFp~r z?zoEzc*CH_aY$nX)DQ+;i|GI&68JSC%P?Py@yj!KcFza<0Cd9m2`12Do%@d6?gp&~ zN}qbbwu&?y1iAbGBzL#uftDt8z5*YQa2#}Ef`mtJjEcewsY74>dv@k?crYII>~t4^ zUh(?K1GH)4{);(fpdJ=~YYj+uw~LAbXbr#SArI>i6$$=UOHk9T^POj>y9Pqv{TJzA zO`v=CazP3}+q*rOb5s;Od&3Pt$DMS(@YHtK@BnL6crmjAWJQTc^N)G_EnAov7<3tw zK*c_2X3V!Y-vV?Gj040uKAj(ZI$ymw?gLtb$KM(bI^VI|-JltCix!h(r@H}ve-H}; zgJ*Al4_g{Z6+4kOMkdRQOx3f#yCy=6GtmCwTTkmyv^}fIK_hBVPOo zgsWTwaS~`9ImE6P_ra1S{Jj&BR2UdQ_vf{O=Z&p%R22AIj)P3@4N-vvEmR8+*lGMN z|3JfMKj!hb27|WnHP;(3@wY4lX|M$81#ips?9S)#WX>1x==D)a@aUZaUQ-JW{&z20 zy+O|6Zxsc(sXN@k!+Hv&=xtrc3|gjJ?*XfVUfkLTnx5ltoeE9?Au1lAxX<8kZ3XdL zFO`Ucf~Nq~9uIi24yra2WJvFH&=4ln-63HR3zvY3ZIByFl0jRC*8cwge}~k)|Nmcy zK+27MpbQBvXrmp8S5tlcr|7I@yKlSKt& z6z}i<|GQaKUT=eq4}k=K{QCdjvA0D9WW(z@5O+HscL&{#%<$6d@BjZh7;gOk|GH^M z!}b6FUsnA8|KAZDL9JQ;{{Q#voemnAGCT;L~9AWL7WfQm6tqVwr4QPJ>ZJmzEhfxr3dUr@#1qGI6NyB@OX&P9d8qt`{n z!lM(^xerm{0H<`&ElHjCUmPp~rE>mO(3(J>?i5h!4wdleyzj`rjYY+`GgZK`6Lj{! zi;96K;}OqJR}P=fXD`-xK;rQZ*ejt1;Ns-|3sHZF@KNw$BTz8`yT{to@<_R^r{yvJ zHqdFr`#>9`LE+5s;t{x9J>c2tn!w+p2TF&XM_+=DDdXP;DTF*bT_YgIJ9dV8c=m<{ zyx;>nx${toT=TDay$uYvK`Wy`6A_>efhWxF*AqaEGzH)8)BsP$BR-Z7iWFZ;L&Fgy zW8uNV)e}Li|Br^H$|NoxwlfPV46kc+|qo_AU#lpk#Xt~5|ThRC;C^|s)dsrR? z+h3**wHH?8YC^2O3bvZRMGLgXDi`EL%cJ})G9X?!D2;&=S18masUVj$-2DIlCFkG& z|6gp|0Xj?L{|i5`Fenv%0v|Mp7*7E2M>Krv(RtYe$ALNupmJBjqq9bZ15__}K7Sz) z&&1%-db`xv@BrxC_?Mu3*zE&869h8;!{7P}B-RW&5d@sIAo-*7g=6RY<_C<94K=d= z8A>EPntwBbjt^ldV`({1y3nQb@9UMI_67K8f?mR%u^x}^5ETtjv7`VhmN+~#4|_5m zeG&KrG@;Ai>H!*$?|kkENrtP_nIMO8zXUB`nE)An=XfzCoe9)eSf*A#K+*FdDyq}q2U3j5nzKo zI`8>(et+Q$Hif_AC`iu?56c_;&7e_HaEu=F=nc()s$&fTmp>h#v-v$B>L!7-fMWhw zcPr@TRG(hf>gs;6VTlX8Y>9sZr#Gbz!1>g3fcu0(A^5!!V%Ej3aTtXe%in3 z%YRT1wjSW`0G-tiwFz`UH^fEVtydt6RZ8*L1e%Qq=x#j&u?f`I4d`wKjn4#hw}Ki6 zAe%ywY&r>A%aZ)V)ABxlGbqdWX#VzP{O8llI<*lr4Ajzq z1hY1X2@2-pp1q*z;~uD}oCb{!K9D3t9W#gtR_EC5>H^BksC*yIC-cSp03j6;;IT)0} zD(pdeZWLR9EbO&m_vjUUehg%hN9Vm47dC^E$8W|mDbTV19?h>AL33-G@T-9w;}1^& ziR@^&_WwUp;{!RrAyV}%ggsbJ7l2pg$KRhLDj?I6BOWhrt|AN_ng-fkHx{DP&tVt2^JnuRM$qjrt?3}q9j=DoKx^2* z=Zk^@4zjoVh26$4{|!%iH2-BP{1D#)KsAJSCee2PBU?-^b zD|y^ttygl-@X~8us67of!g?iApbH#9OG!S0RY48l2hBTyhGClDGJ06PD1GeH%Oec3 z?I0unHW5BY{%tj!{~bFoI2!(Nu=rIX=-BW>p>$vKqx;RT92&31+e?f=OfX1m_@T~*))j?$rDB)`uF*$`gVYdjMo1pQ2TJ}Ge^-!%(wvST2Mf& z1>GnH9v1-J0s-n8b*~2t^-c$u>773&V2%fzS_A5qfX4$afHE=Wcz|c;b|O_Qfe&aW=v)4lrQmk)JI8J}P}lwu z=!hlX)^Et;10Xi2wgMLw$6HiDwF`(18!Z6Qpw&wt8dSURx6EQ;U;verEfZNlOAP*j z+d)2^H7W@&{6M8E=&TeMl??utYOwq-@G?X2h(Ur!Z;ndFix(Te{P*pw`6KK=u(rO8SIwVe4|(?Pnsb3pq&OH>kkI)C_pu8A#C5%945z~6KVe6+_u-_ATp z_tvNL5lFKxShHj2asF1&8O@;9dIG4mp0N|;2anG8o}I@%wcRv)I{$fgx+!?{wy1z+ zK|%M)cDkrYym*@mw!8&&-wmh})dCu~@ag>C>7pX=;=!N)|3Smbppt>x<9IVDpg}?9 zV_l-cQ6d0Q#_{6iDsbb8!=rl&ID%34C06Df$2Z|4UG#6=V-+m>2A0W*3zV&)zTtkIwfVoyWjEi5LFqAm4-L z78Uqg!R;SwAC(OL)_0%*qTVnIA5f$3{fie?psI|&^~t~g|9yM?0(^QQV*?Or@I9cX zd^$r^G<-UJR1{uBT7#7Pbo;0%_<$PL{H?3Ox7oNs#$A{lJKYTU`$2Pdp1pk{V5_DH zfNtmOZ37iLKAqQmI?unj>;^KW6Et?)$_{Fpfwls}dOi@FJv-ehUTCL)`~Xo0y1&Dt zyF?`cH29L?**gt9$k_S*#W6dCVY@)31jsN?ZMO=rf#4=H+{l6#H8TAi#ItuAXe}w+V9?0+3*IlFhCP34=HLJSL7BSw1run3zYXLxAL|-WWPviX z4|9%+fJbkS3OLpGPEi5nT+m6zo$p?pvIO~^zf}#?(dz=Sd?jcpzjY3%lmK0k2THX~pzbkfDCgxIa9UDzymbU zvAveOzyJTgOaU3v`Td2*PjJ3ec$xeM+GBbd31WeJxc*Q@%1}kR4T=m5FQ35W!=Uoa zAnQR{=A|rD&kKpX=Q2fP%h(?`VuG>jMl5_a&kJkH;C5mXd{iUV+#gP4B2MFpf6RHlNe zC6HDa8#HqRVjp)=0UwWk+(jh@#D8I(0M4APSzz-(IkG!NrC=u;sIcq~QGu3Oo{UHM zw=r}=%zwcS)nf+N)A`e*vqc4Di3f9w3dmv)&4ZvqF#~jRQ(rUGGzai_()$ouZOaQsn^}kN_!sp#lz)5))9R*SZ$uLb#M= zBE&1x!6U`{KxQ$Na63XQ1WgzDfDP02?41U32{Zthp}G=bw(+-uj-7FA20NG0r}M$f zFW^FK+5yleH5Zkb)X~16=NdR&7Jf0!M#`kBY~O>oH){w)v=dKyGnqImzFd@bCZshM!F(hVUwt5p0i( zO2+GX9><+QC#Es*xA23Dbx^#4MuuN*_zt>lxmtjsM9rhQT7rSUl@)ZcpaKH}WPb1k z_X=q3?<`Rg?a_G{)P%_ZH6=h{a-11_(qv{eUG0f1CF9&ZC3o(WP6+HdiLnZG?5G@J<< z#Rcu}0^Ot8%X1VY4+`$fj~qFtfi%3nYztbmSCVQA+M3SaY5~#&8eJmNVfD~7#}Cri zya(L0V=Q$6MG(kY{M#5DJK8{tsb5|J4?j=4z{tP=E-D}vyx;>j9{5|&fux)FJpiR{ z{?_H-fniW^&H)A2+po~z1P3Olh1CQa(7W~(G+z({nt7=O?Of~p{$l;7|Np@g0xwT~ z{r?}FwKjbH|NmtWRK6c14<2=03Ddj-G$#GB>nm&?*)bg29S1Emt3Yb}yQpMCQi%onJjWuX}c$0}ai(sBlbh>@GI|ZDRfd>Iig)M|fIK1vMo2TVI0( zwt|`~p565cFN~K$Y7EdmVaIQ+piIe9BIVc(J_dCK6MqXA69dCa#s*O9=Rc?<=-v;i z8^DXhI$w0&;$>i9V(9h%fBBJPt61kPewP=H-R+=pkzSAgoiBJ97+4rUQeiCoE-zk8 zlLCzdAK-7<3U)=ff@gO*XaXJ7^aQ7AffvcLObk#HJbS^HJA-a76ack$THL`!02_Y` z=-hB{6Nrhw9dy9BXE*qE<8F@TmyDMmJGRDg@V7XFZ)E>>)VA{Moeyf#z4+_I%+UGDvv)tJiPU^bq4fa2>#G;rok5}v=UBkp*F2zg zLEX^_pngQU2WVMe>w%Iu&+ZTv2hcf}PdvJdH9Wg(R6JhHTMUYn(n}t_)dnvXI)U0w zB}bdjG9mhVpb*P|kI;6$C@u16K2m|wxd5%|V1UkZ`GOY0z4-h8|LbFradnT*>!2GV zB|xh+1YYo6`upFp+m8q2QvrydK_jl912-Dag7W0cP0)Fe=CcB?n+@N-1P!9RXkP#t z#s=F1O0S-s|3Lj$kH+Jm1`uex_p4{;8JEtZ&?ShV{0Ls}-5H`{;nL}&BID6L2VD8W zYIRVtCIC_fE?zoaK!ecTkmZ-4dKwhCh6i3ua0CYkb2B%iXYW2xgY1PbIE=cPOIRDu zFm*CJcJBlABtT7kSlSd(DP?Uq;{cM|2I_1;Rx-x?LnaJ^1qr&0Y3%(=sMg1Gd41+im14HXu$L?5zQhrdf;PA8# z<=}4#0B!aB%~WUX*u5203xo1C=tN}9?8?I50?NnG>M3u^Wc84K+z7JFizA#(>@;HCX zPH>w6+;n{C)ADgq4rE!`l~dVN%4AobWg&{T{DsG1h|f1&k2NjNC!uV7$cNbxX+tjpzZ1s^!k zh`#y|l3#th!Glhq@t4*E(53|_xM1_k5Nk^$JUVZJ)@po1wXSqxw~I;)C$r&!*XYwUU$#L zz|g%LlpH;}MO1cxDs@mr^`MoBzvT}|t{IfldOaYErY=8dbpqudP*Q#;49@?aoyYlG zK*c<0G#NCl82~DxJwO*he|)h^jESKeY=Q@*o&r}97VAG``qiasg^pn_WCMU*_KzVcBq z=xP8JnVJ<$Rxe0_Sif=V*@`XpbLqV$yQUbbNRCHXr zd%*2BP{pPI@`8a&uLC1!pkyJa%hP;Bq4hwirf=)p5;337huxrr*?A9?$|W5CUu`{5 z%Gd3p;sf%6A^0RR&63uv^%5f*Afj@{5uE6E3!;iVb>OH?ErLm}N{P+-LbbhoI0auhhw zK(Vd>3NZsvh&j0QdN95y0(Anw!NuQl7IY5JN6>jP;QY|Q!0=K5oWmjeQXCi$7 zd4kSAz6v>)si8*fKLda3WpKG}YLW_C?s)%2%6xFQjlTtSpOr`RUq=2mP%#c#C#T@k zo1-Fe+(iX+%`)h?p5rbmppgOw&~k)7woDA5oZ!(b$_g^7*Yr^eXsR5tX2GNLCP>k9 zThI^zsL2UFFB4QZgF;-vL-VKy<6%(us}(f=3sNfM&cpzo;dgEQ=ECptq1i=+1GL7K zv3w$UzNp(p1=I=!t9NbvR;mC^_m2A-Kx>W!LCf<%Q|JF*6g~g{-?8%;C{=-OZGo)N zaOwQO51wT9z*wXKO2G^awhgaAYig!4fSB+9|1ar-tcc)w(c}g?#Vtfd0+fF|G_QIv zUPg8!cEkP^twAJWvV#QXRC1YJv-B%MnB` z*nf`uKx?O7y8Q>8<#PT1fBx3b|B%l^b?iL$QWLcF;__5bn-zI~Oy^(FfyB*kB5*9M z@Gv~^qV_JRD0S>SUZMsH7!P={;?ntpztsTL6Kg&afzVgKuxzf7t!PrgxGc>56AAG+lwxn`iSsM*cR?ECDE88GzE2 z1}t5H!Ua?}f)c@UYgnQ(odPnaS9E0(MxvTz4N6pzagg&9KrIsRk{1I{&10}c^&PYr zq4W8R<1X+-_1}@-^?0+33JarS=W&pG%KO0K-W{UC0rsd%=MS)7UAk*jSXwWEJXLDh zdcbiPD1Eu`yIyeZy#87PUZ41Yj=#PKD&Co%g6f|V6-ZDyb{+?5^=fGo%dgqPyF)#WezBwCqUC5Xhs^mzK6dh2{fM%J<0l?Gb9ay%WzN4Yv449 z;-!}wpwT9I22eR_0diLM6GS@P?+j0eGN5n=r$f+Y3XoGB_k&ZO7b4|>MiIba;@Ei{ z93~)T;MC{19~2rdl|c#7YcjU>6L|8^qwx)>GV$oNQSs>fj?yLqwH^!)yy%?-O&)Hb z)^qP170@`mPp@tG24)6OLq)-}^N8VFP?`{)4pj;2rh!YG<^#ezL8op9_;$YZ?fe05 zHtz!oKu)F*ngCh@_Q?ZwKKh|)U;a1$_#YL?-*TJ*yrq4t2=WAJPQx)ll( zcOH#LKt4yxNYMBP#fyMPw+(nNox}^>U!Xyk5ETJX9V-D^)g}9e@7EST{&ExPcQ4;IMDhN-_DQ7>vf=d`Ji^VA@o|q^?E># z6NV~1tOKz|8le=_JoN-S$ETOI505p=dqJVW-*F$5h-ZMk0=k;BCJ*RtR$&A!)M^Hu^%2nBECbdnB7&AU28Mu?e(JH=w)O24Xg7mqI{yvk8O;>bZbyTGNBD z2{iZN0kH`*{|xcIPcLhBEbbV}>45|tcwIC^FKC5McQ>dz;M3g=>Kk}!9&zbz2E~RC z;|q`8-~jMw=W$SDmg7aY9w^=_(tVO&_*%Xxi}&aSUt#6bTLW6Ff8L{$N99HMfxrLv z34t0sorgfnv#x`a6sQAHVh_rAp!R_cbc%Z=2WY1~$_h?!yBKsJ255gOXzkMLQ}FWN zSMv>MvXaB2^Zbhm`~QN@!QvB@X-hOgzFvQ^#9pzelm z=W}1iyS|$Dza3%ZZv~y-xI%%ybrB;2!%9$xmA@5~?pLtzx3aS^Fnl}0!QT=FiNw<} zSOfSUD4TJEt>f=-1~rCf_*nknZw3uZdukpBWwXY^pdneWxRfwF;M@7orxR@c>MoFXq9XY_9)gCMAvRnCF}tHBptgV- z468wPaj&Y!dR_+T`fEcCPz7Ex)uWg7NffBH;G!b&x(8ZLc5eptEIfKe`Ia*?c=WRJ zEC*ExtX|Q4pi?0bxxMups87M~dI;oJu;W0fQVi^8v?$E?oJd)J;G^V*Fd$mc7j5QzXPP)0}@7%9Z!=%(>*@Dtli*~k8Cy9 z@iIW%2I~HS&e84m;_&D-Vf3;5$KPBIn$y($4%+Rb0={3qM9T1F=eg#CjIR9KR191! zPr7!bm@*W-@#yyA07Wn8+yjt7pmPnp1U#FMGV*WZVFb-tICl8{_po;2D0=469V7wX zvF+LU!=v?5X|E$FsY6oa%Vx0mK=n*7s|X(dE$x8#?=Gm*2Js)Ld*i~tZ82!R0^%i~ zUfw0M_!wMUp73}41^2R#IFvd&emkO2TJHGmhyZ`1}?`<=K3c%cqx@3#1#g#{gdb`1G8s-a}S9ZtKXnpuoyI| z0S^pb&@s8+jx_MMg04k%{C32FzZKL>2gMD4>nG532{?XKK`{U-d0MAJSSD9#owxk$YAcEy@%lP#iy5bb~qn{WAjT!^wt5mSP5wZ1wJ_2 zcK(8e-%HR=Sf5^2NwANxM7A7Q7r2lMhU@a_<-N3um*M4F&}xNV*5~0kLj7SY$V#wg zF=WkCL7J-|n)l(-yab{7=1;hLBOtnJap}rK=voZd<Vhg ze_J|8Z}WRW&*r0oKE1q8R`N2uH2?P>R2eDavifri+_xGa?cn;rr zT$=wk!~JZ6ta&p?vmQiq2rkWs5SqV#NAhzSNHb`b+^3h72bbnngyvnynk_+^Pe6*W zSD`o}+5@4v7OwdvXbIAbv#p>OKGJz2p!U5-FKcNSqGT1vPrn7W0FrFWkZQRvd=dy2nHF=<5Zrb^>^yw)p`|iB;!^?&+XXP4!>>|Nor_ z6)#$zC{^hEzg1|ls#fHpfYfNqDG03K^XIzOlzY@RA;8lS%fbf39n zD5#h86*LQY#iMsVsJR3Rrq`m7`3H~YzbqwI9^KIrpxMw!kM3*^AIqnuFF-5D!D~X_ zy_nes3boR!kmJrkC#ECqDfZ}g7HG5xl~rXZ-33x$cmUF8gq@26b{eQ1(0qgg>{hrm zWV{MAPw^S#Adkkwpl(JWvBzl zoy8g+-L)PsKDUBhc^y1e`%MMZs4h7L>ZLQhX62WMoIA+@zB3-BKk3nWlE39R=&Y4+ z$V9(q=T%Txo$={B4RS*B8wuam1CF4*oS@^-OjP(=Hi24#pt%PZ6%HTh%;D9hFaIHv z_6nYz$3erv9Q-X`L4Bmo7_cgEGpY0NYg1^vdo=%JDwFo?b`wZzw9wKk74Ym069A2+ zSYHH2|COHfZT;`t?I%#8173~KBgnsvMY!|4;c<(D zB?92Z_{(97@yq9e7Oi_)UN5}{8v6jb6f{c+nmEwd^7ntQ$A54u1GFXpG~?O=>Q^@Z zVB~KB9Y@;?nmMrmO?N_PPMko!O3axP$5zleB`qqTqhPviAhQ;rWkJwc3(!7i$T&2} zTE+7qpYgYX_MJH*<|{z!tl%T>Qt**?{ua=Ay5P}w{?=`vzEATT4#=qN{}Po15ccVO z80-U@WdpBUW_k!(*#n-Q;AysIlsxeLf-1vJu$TlS(Sf(KLBk&!@}VrHt_>AT%%%38 z-DL`(Mk;t-6C67_p51O5kl0c5>Zv~C-`}Bf0+=F$1`it=PkZ12UP&NSd@;;sgg;!~=V>ftd zS?ht4B*$-UphhE0iI8JAc!|#nmJ(lZ7?ryHFHwmAVOPVG!C4Hxoew}mH3~kRFTvfF z_b(nj28RV`Nkj{1_a|s43ud_ZLKZcH9M%dta2Av`5uxtW`4AHDuY$n?;4hoOJq{NJ zhDL5sV+oXiTK9u0qR#Ix95*7*T;BQn|9>Ml18Cv^RKY?LvIc)k%HRM0!HJm#WowB9 zC^hr97K83CfNn40ZwHjYB*4?7w0otn2 ztGi)_A%jn^>dGmG3?99@iza|5+1WiHLD?zoAWF2i8AS26)f+N&vZ!>osF<)YFm$@8 zuynVmxIma3-7PAh!v#PCmWBuTxACy|if}r1UUO{zCf#zBH*W9K>kZJ#<%IUal_ zaPSd_;YSz4PX`}L7=AK5)p^meGo+Q_;6V<@PM(&|iw93}bc!@P@^2GiICxN?)2Ef8 z^%8$eI3oi?ch3}11a+6FurQwUNWSLS`OPEqQe#6ygX6(}@*W3YDKLII_>0{|^ApJK zgU>h|HLtm7e)CBF)*Z4%05t33(raRP@DYb+@;BGc?;e@oAbS7HdmMbH;KKOr;6HX3 z&3}%a-yIJ==Wx{g?yC9EH~E`WcgYrkgURR&?(Xg>fC#P7ohUD)`P}Ax?5C0StP~77_tDVG{mFv2q+gJ6<%q2@bwqHyelgW z861zZfYuCyXwZsH5ZwsM%*R>46tX2!|Nj4fJ@bWgHMk{;*uMuFiv(%G+`k7JvqaAS z;vT*2ATdxo5Wb)Hc`Q2H=T*p=#}eJFODaI8vGKQ*urn|;{spaV z;BRRLvq7nvzoiAt2Ic$`&{PM{1JD6EE-IiqMv-f?3CEd1XXE(vW((|RU|?YK>CEPM z5upuQ9|}sp{H;FVEdV|$4xO(KzGC&XJnPZx9pJz}`Ix8W8UD!!9XQVMw@C0XFf{+- z=WpZZVPI&zbknMGlYVgAQpf{I`##@fTj>_{^Ul z`5=uy{ahM<{@2g^`IR5i9Qo4^fVHRb-~OD&UwD}Vr2Kdqf8lkGH2(DaY5e(rL5e=W z6#Yo!FT4O&1Tiy>Km8cUIFO3^9{G{d5|C{tb})b&$#rY5WOyK)jP_ zF8m2sK&*Qp))^4%Fo*?`O+1vwpLr|Il|TP+nhS{K$Y1n9AdNrcOPULR;-fVFqR#?p zj{HTRIno^YU*AsS&o~0&e&I-S<$rwyr0)?(-zBK(j6Wb{AoC!S4?&WrV3KD*T0csF zl-vX{zes>h;TMbt1^x{eenEF|NF7V#PrL=v1ajOVh#UAbKY_G-0J{V1%j0Pfr)K_w zDlPiPk;eb}7|3|Af`e)NuaBqkXM*hq8S_yhjsNvQurXjq+~5~<|G+O8|G?4m1An{V zzyJSTJ1;smKm6cod9ftdtyiWOltB#t`*hy)Wqj&Vnw$n4U2(z7?Gm%*3u zkuT!|AI4|!_6=zJ=nl|m->!oX1so4P;c(%62;%N`;e6=AzfEPIFF!mO3G(6dP@!(4d7yfN5eEi!^IUal<;A(gf%mqmZ@NYZmc<_M)Osv~m z#PQ$*4iMMyq)Vr_f@9~2PHz=Q!yhh&hg>?HHC&8&IZF*0__rPF4Ayb%yaY0d&DHRP zW9LQ3gD)lAK(;#`d??^*c*2E$+bI}d021JR*VF}2tBOp(KYym|9nk}yAwj6u~vI1^1$S;naKU~1xaWFjPXn3O2 z+Xd`?AD2#N53mPZjCrj~3>h3dFTveOkyir1UJ2=}j(~V2#>JTRUNJFVaXk1+02Vr+ zKtuK(idPaG4Nn-J>hw+ldnL!E(>cS%n76jrkOAx?s(GaV?3I$v>I#TgYFvz2rK#$b zZcs@NiI{Fs#~9?H$)HP2T{^)BcDoq!t}CLlx0*q#Exf`@l2<*Sfa~K#H zT#Q*Ail`Y|-3!2`^1drHWPthhpi?0_cY{umc)1;X@EgO6;4lCGLn_|mtipwmvv~Pi zK|2SIGxIPoFuYvx>`ceYA*wSq_UVFTaZDjAn8>p$uGLIpSW~8|8eP# zegLXRT{;hWHXmZ~Xg;jKzwH|THXbpT&O--ZN$_tI5j*%u!0@6Y|F#P*hBsaKx5+@H z1o*dIay`wiSo ziB6E27eN*n{xUq}(s`19+gbi?HheCfCl9^^6^aL+a~S>ti5z?`V0hip@NZ}J2Uo*) zE}hw5Tn#_CbQb?`>2&_%Yk8f&)%O4Y|1V>{|NrmO?X2R{E2}yYR2B;u+uq3sg@U(4 zcd>#?=K=n0S6n(zf||p^j-8(zJHHrShj)pX3 zoz)tKzl?2L@(mdbFLnm&bb1>Yd&@Y2iUJc?Lr}SC;nI1)rPJAlf7=mP{%s=6E{4}# zI}bS?d?n!yGRX1ZBLR>Q|F#P-z66L5Rst$IFTsR4Tnw)}cAj!H{Au{Bv)aMc@RY0J zna*Grm(E}x*Um#Oo!%a-S>j{Mtnvu*Z~Fx*;J_lT{M#Tl9egff_}B5^BMz6&^NxnETnsO{bOuMb7>lmRGh{Hl z4)SG8XLW*0=W$2FGlu6oy;EE|lQTMhbXMoUta35D>evY~8rh3iUHG?MhxtVUt_0nS zR~|_ z;a|g7ovT4h!Hsp#<{B~>UUM|OV0f`}GpI~*>AdFBdBE^oC%6RcgcMJkLB$jQwl6N7 z&7k55WT3H2=P{VAhVNZEkAkfJ?#RFG1K3`Wb>G2C1VG(Os2GRgKTr^VH2l=L8B`*< z8lG`6Jl{DPR2I1yKJDBME`&O}!6niQm(IoDl4Lcg1mWNI$JkUk*N_2ZxwYZ<&UY}| z!4^C4Z~F?hM8fdhSxhMg9;^3eY_jg z2XHhzu3n}Eoe)zi{Ukw&Th~M z6R1_R8MNZT#qe0?WYF?im(K1x3=9mNo1cJ+zy8nQbJPsKLHhHK{M&rCFoDjr*ur@5 zp}@iC9GpKv4%z~yYPK*Rd@jNH)3MWMD}zV#F-FTznD`?8kqwxsHN~8`W=okTThsm*%MdgLM9H_&@-}(^LF9%r+ z+AHAOo1>y|@Bx#@44+<^CdY1O&*s+xj-Z1G9J`%GKnxMbZf6M)L&CA!Sq8+AaqMPM z0S$un6)`d}xb!Cc^XV0pECr!^QPy) zCoDdogBxR1ID8rJ@XIrF3wkua6!2(1Cg8%q?GyjDdv5&OYz!PbuN{0P;CS$z1pl^6 zZv5L!4EVQ+2)K9K>~c4@+34E&&xL=RjfA_g$qpA|lj$ye$T;I`q!5+nL3& z)0M@s+nK|$)0M-q+nL9))0GFj+QGBCMuo$%8*&=F#EWiu28NeQ|Nj5~!ZG{He`tT* zv-2wGMqQ8Ya1NJlwr)3;ZahRzU`FQCy%pI%w9CPM~~gO6B!dPTYG3>kbnb5wqK_S%T~c7F8i z{0pl8J3l${Z~NiWdHCQf3CC`mJqKS)7}rc?;NNz|vGcg2v85xZ5;3-!+nTg@4-tM`MxMou3cB7U17@+tFBL7XP-J2VY7! zcFW9hGLG5H;0iJ@lEd-fdr&p-zcWPTL+2+@3CX|hnq#-g7UPm$21jF=$u6CT9E}Tk z__v*NHkR4d9isBV(b%Nhfq&b1M`M#F*KU@c&YvK2JFjWvw*bn$OHeekt}V>eHald;cU2FK3-j>eupK<)!M+|ih4GRQ@){M-H;m&|48{OxSa zv&+%A@DKmC|Ddd4Y_c2FJ~B3$u;0mX(V<26XIyLKLg z20N(#1`T&`c-lE)hB+v}os2_z85}!5IvR(uI39c{019$Yr;dNyO=n|~F2~M;px_3X zcJQUd!50Ef#xWp|A8_qF=x7|r0n!3e?$~+1^E1frAb)|voqyYX(EdNiZkgGjVa4Ch z#xh-<=Uh4u@^3o}3Vo1`JWU?GtVh}n89w?XH>gRcY*zLRh@b`#;>b_t?S!qM;p z|F-WC4u_-R2ap{n(arh5zwPtE*AgIAj>cu6UZaQr|27^DM`J%3$Aga~K(!A4HW3YQ z1IO?hq?rOre4w&RL;%tnyXL~bO+=#eH~+T(E}f_Ox7k?mZ(|W~fhPF-t{uJ%jvb*) zVDmZ;Id;S{9(*O>Vt5hUpg8Q<5z1!x^WXyk{%!X=Ke=|evN(3cvKpQMMSt0cvDno{s%fPFSKusv<`gWx9q?@nXjm4$g z4OE^Ob3uy}7h@-s^5h%5Jh=nzOz2)sK`Bp;<-p66-5K!mLhxoVsbnN^N zPS-MfTsn^%$4q7D`~)sWBtiKO%mU|fScxJ7Dp5eG+c;z|1OK+apyI^QSY&tScTf@q zHRrlbwt!o6#v;2w#TBTaax$&~<<9e;@{??@&Qz=IDyI@$IcIq#-MWLg#f5*aWrO`?8?9G0Lbmm#w<-B z>sl`l0Xh1jXo2A3-+>lYw%3ui&gDE@*Qw4I+lJFkFdZC?ZmgIcwvw#~m8eL7!( z*Brku5r2^dT1wY^gac`%256rtGw2WsWMN3VAGD?eJcsUq;Yyi3lMI z^ys|h0$Ppy{l#5D28Ndm;LQ=BUN!>*=zNsc*`N;DDFz0HvR~`dr9{z2vyavv^{M%gl44nD+ zx4H5gIP=3IB#l3xM}=R|m!ZT293!6{`SV3o_%%gTUfd9X_$~mnWE-@D?h)u#itjHN zK%VmidyWA#h6YQ4$m=p*RDb#Z|K&f>O#Z$9|NkEb^&fpZkAsqxZ|6BsvH~S;iPmo( z-I9=fIX<9`PdO?9B_)P$K`Vy9m)3B2bV^{c!U2+cUYJ2#*g6k1C*N(O;?w!dr}N*7 z|DQqUdyB|}F2!hm%+J4#$J-8Vhh8G<>e4r)>cv`;UZvmab0y5f{`W)J^1VUR_Jha5XZFsv%kLs%2R2MW)U zJ!sZ6@-Z;H-U>C}5d8|T49L}Ipyh4I=@C{oy?^oO*<^D)G)C2J85xXuf9 zz+p58Fn}De=f%w!(A8%0ko=95zC%Gr%xHjizIIE2j+ztzoe3fVT4t2M-vZh${WGn}N>sye5?iv*VaJqX2QSsvgXeXeH3WsBZiwZwu>1+_q$5=WUMDsG1c7tdh z#?odG&CST)3QBJvHWwp*Yc}Y({00{lPDcLLcqp5Lk-s$<%4TPT+~NUJ&&J5#Y7G@< zW#n(whq751`CCDI^FV5t8Tnhqq2f%8{H@$jHX|c{>wnNWh7Bw#j4!{!)*(UmeSkB+ z2Db7Av|UEPr}L*z=Ob|Xdy&n)8B^(Xls{>EL5F*VaoVS{}WuyY=}PJbHOI=<_kSf^L2_U8m2-(0L2A zqMgBm-}Qvyffp$WphWckfNSfw&RgKMSU#Qq`87eO0bc}Nmeca)|Nq?r>I@98CBQ43 z!262dftHnJeg6Ny`88vSGB{1V(B=lG)%&1p_g;E{+6n((gmW`6G{0snz3Z8L)T5Vo zhcO?6$H50I9*ieExqS1r-rBDl!cGt$l0^pgWahKr5L% zd#8gIMfiX&f^lF#IfoT`QVIN4hi(@Y8UEHR(EV_YM?e7$X=Whj!xx+H!8{}m>fa#z z^yt%hhy_o#@+}f(2+IZ2tNRh@Kd_r2;Z*@ig3`~m6vqU zBfK~c65$@*E-DtTt#6TIoxu@Qyn-vUm! z&952L_}@c(kOrx^r+~c!N~oZe&EK+!1=`k1j+E*#Ey! z=VV}bz4(Q|U8v2jhHpU!GRL6UzvvBUt0=^J(0OX0ZA{It8GSm}fDQNQ+@b;!^5}%P z%A<3N3g{MY5FZr2AU@~}sS+>G&ZC~4*V6c*UI9lt+%quuzX%4qlfR|FWU-QAJL4QOd3MI{C73{20B8=!llzhrJF^?v-2IK-Ozcs zyF^8%`2pj_-;SL}U3$HlTsqGle9G4Pt;D$XfJZOyW^GWCVBG+wOs{M6F?7B{DgZ!7 zw`jbGi}~^&lzKHhdRZ^&@iBNL-*)O1ISWpQpd*4jI`8cSE$d6;&j)QnI}cKF@ENN| z@+A+(W1ye{rQjD4kzf9UGd{Q+dEt5c|9@A|WlOBgpcQ{NJbF#bbom%Od-EJT4nAP> z?fhzZ+k^1~$V`wCpwyDapYI?5;((TygE$Hx4(Rwc5XS(-0d1oK@4W8YP?9s~$$`791Y@Xd=93IA^J3*Pmn9+mt5PxeA zXkSscn}BC`n1qKh>oTyE=yDJRlFo%lD|mK?X?Pg3P6vq_i_QR1AZb5{w1H=Ln1zQi zYX?Z&ShN#Ffu!|8m&W+0$UsYkigrGR7cIeG{x`FzFoMVT+4x&+U}b_&=T}&HrkZpo7&Q z;@u@G;AjBVKQBr^E1`Q?&uT(Sm}Hq5{H+0?`0X{lsmaH%6SUA1+}3&T!}#olTR3Q= z`0bKpk6ss92G7oSu8{NWEI{=c$Oe$V3_ynJgJzl7>fC@GK)?1(hfI1(6_d5T7@!|RZ|1SeUhdlMNnrQPer19s= z+Gv9|aDH_)d<#ndp1p1kY5e)BW}sXOIwlRYM$S^3j{y`Dpp3zp14m0tDe1m(alnxG;| z43q&qyZtyk7(amPUuXdZTDIWX9VXz}?I+=39001DCBP+D3TT@U_^32L4G-f01rK8n z4X}haM8d$c+t0$oIKaTe*uw%WVFWr$@!ua%T>(1Z9uhum%#iT81X}1151$Lq{{Mdo zYQI4gJp`Sac7VS%7Q9ygdV`ArsPbL@?En9U7L|TR1_nm{)^mTsygmpIbcj|%i%Ktq z2ijEH(4qo5^^cLibqPczXipy_ zPJTm+3N);pAac;KHi7Vp!5QU0YP3Vf_xmgM>ABlQyVP z1=`(U0B_zc1&xr*1Z7oBxlWMW&wrp}L>|JL{h(A?2of*)i6I^d5(k}a?a^yn)5yp0 zqQm#g|Cfp2lc5>9S^e@28CnmNss$fswa*N{(45k1K@k$vE2s6zs-#wbo#SG2a5{-HYa{iFPeXw8()Wu3M2nE zClD>jzs-%e!$pOOf14AC7UJLL#?#@V!py(T2}BF?Z*$}Ba8Y65-{u6OMfkV5ado(; zu<~zn0@0%U+uS%iTvXWjw>g1mG5&3C933ty?EKrDK(sjjHaGST&}N}+P9R!>f14Xy zhl>g)|28KOEy=&ljkUu?g^Pci6Nr}L-{!{B;iAILzs(6mOY?7Y0}V6q@NaVh(K7to z+?YCCRCxKfIe}g97kg=d49Pq&?9WE;J9-R+9^XD^s=FjI! z>Je}5&7 z|2=3KF-Yg5H2&g`jA{JEpFqTA`!xRIEB0yp`LCghi~oSM-36_=J)Oq?`brxA>#GP$ z^IxX%7k>t6`eK*HU;NcFjsN|fH2(K@p}N7=9E3O!to8!PmMctY{KZ$<)A-*XN#lQi zlsMyVgIsTu@;(TW}Jd>~q( z!<`@8he0gpX9XQ*b@;_W?=Sx+9DKwA$`BsOhddZRfTGK%*Wm+b8M2S%vEoqBJrSUb z>%HE49DK>*(Rs?_`z_GyzejJ0zyqJo10J2fpyMJSt!exgw-0>guV?aTej(t|dq-UPnfdIp9SD7d#lRgGMeH_yyS^`KR$0Xdw=N%VJP(9kR!SzvURHSZzMkP$IAc zv=AR@|Kn%=e74X0_3W>8)A$Qc{!in7e)_y-GC1~}`2R3A>`x4M6V@x=+y8CoDF z(~G?@Cd-RWFeb-~inY^rFLy5QuLO z{cBYJ)cFad@n7TjXg<#I+8yM=&@}${m|lU4fHDI+$dzgQ1s6d6hkN1_%p928*TLMw z^kOlL$?{?bjLGq$7seEL(F9{EyZ{Z2LER3jMZwI57l{yWfcnSX?4F$;JT(vawjSW` zd&U473FT-xSt0M!>%i#K$`o64L_Mn zDjR;Xl%{w#|6t+oTLd~B%7f9T*MXJ)Wamkc?oRgCd!hNF;U{CMwP*7W#*$k8ZD3VQ zP!+Sm<3yg#KbY#0__u?ln9~j(0h`(A+-lksnJU~D;1Vd&ZG!Q|1&?+D_1bTWBp zo&()Z(~bxyuzSIqkRj>6`9Zx6Q;BDpx(#zlwhc?^eUIj!Ec|_-acakI2PV&64>tZ2 zpn?Qs?CbRtY#2-Tcr^cHEGYxo0g`71+0)7YdNMe?Jeq$p)kQh-ZwKpTNjrFegMa%0 zh?1AN2>*IC|75N+0-NE;zs-Z$#SSfLt04JXJr0W_Qt z`BkE!&cDr(*%KTr z4>&wI5AbhuWbx!Y;L+>I>e0#m5;|`LPM=aP{M*5Hv3PW{r#T)xz~Rcj{lH650~3FE z)iE+KG*~i$!;8OlE2vcOb!0>hvnbHiBsk3YJHYcpSi%i-e;&x=DeFtp|>S zc3m=*gn;sEZvi97%b;L1FLC1ER={}hAqVG2kKO_%kn)4&a!mh~r9K|b{}}mOK>G?kdka95%NZb>Uw4Aq_hA1al{cWB@8sRIQW3Ww(q4XxWWPT*F8|m2hjOi4VKK%^bX3Tm}P_!JiS*G`Sdz6fh!$Q6N$;A z^P`XEK@ZD={7t-|))45b1r`va`30j#C&+yq4L{jCyg14=LDn`OWc29d_vrKz*fk3@ zxXR4#Vfn$MlO43;nE`ZI1vq>_^#@Y;K*Ap*{esR}?RI4H>2-t^$3C6x(2Mdw<6sQ% z^jpTi4Xh4SEQ8d5Ziok+I{>Q3qCmxjPp>0Vu?$k80#4*e>DLHk1}F`Ki)BdtbpXU? z@!>q+11^9-!SeDoI1PjC2Z#G%(0QrIuZYtpjPWFyL>M1&uX<%G=IA z9-Zc(8+Sm*i~Z;CTLKR00!D|HZ~Psg{nj465lo()GAf|r*rW4>hvp4XOQER-l%5&y zd02ieaeg_U3A8E&>~D|WVkXZ{6BYh#&Ws+NP8?99Esygzd4jZb772JVv#5AjzVPTg z>iOi+73EgKQAdzdW`^0#J!)b)alednQh45SzAC(wO) zAlq6FR5pQQmA~aZ0|P@@t_^dEp$!XvD`@93q+|l0|LM%+(OV3SStkyVTfs#Nw0sAZ zBm8}!`ip-XXgx?VIB9zve8%Cy`GJ31F^dQ12an!jR*>JodLbo0gAHS;45)l7NdxKg z=q+Z3s{jq5K*}qR<|6{2DZ0aH6PlmZJ2d=b;%~8LWMC-EbZGd;%-`~zfq}uH;U7!s zMUUqHEOpY|1xy~j5p3Y#KiB!~Wep-9fs6vjIn+FGdHITeJJ@%Kd<*qp8R(2zaJ#{w z;U6P^YaXbaY5vbxvcsb{f*Dk!g6w!HgJwRcH3>8SxD(`JpjOZ^E+E?>v&69TYW9Jq zOThCiouFOrogykPe%gZ$N)=J@Xg$yk7ITvD=sd*VDge448q}qE30jm65BLAf{C%L7 z2*@@3+bWnndLvjp4nF7bIQUG!gYzH%wh9)IBRx3(y(|H*=YpiS=KoCmeW0N({_Wt< z0xL>8c#MO8`?r_ge_`XXJ3vM2%W8NycyzOScK!q%oUC~qbb5AQF{p_H${Lrz$*cm> zy7}SRX$fu-hJe#y1*1dDfBp_|{n%T<52d3G9tgg{Bi zr}Mgp=64TJ1_EtF@c?HaMjy)yV9lVrh(J|4|26?o3)BaE=OF0p%T7U$-arn|PDb#0 z6VMe)p520=lP^6w0|h*~135gI1$`_pd3FkdD;@Abibps=m%BpB_vYvI4h{dA_*+2b zXqlly!+&P}7SJ&j4h{cV_*+49*Pzm}&fcZlfytw{g3Ytj64H8o35tvf4h{c7L(kB5 za*2dTZw06o>i`O~muC>^44j5R^+B(r1gM@n#>fCVp~J)SMClj914!-x^*{JqL_q=D z_MCx%!G@W?WjTmuDK!SWWNWuOlSgkkQqDGfxgQ?i;Hn&{yv0)fGV%9;@`VeiPvX%F zN^O>iHnJqx$qt}aKQn(H=$LL$%rk)!Dx~fGoWqmz2RM;({_yCnVD;>@eE9^CPC?~6 ze;;V61phYBfKfSWxl#@(SIVK~is4IEsC_n!{H>s&Vo03>I++=y-=nu2TD};*ybVg@ z6F~J1f8RQguI=D(KrI6dUv2@N`hw{Hdo;h%0IkqKT8ZHTUfBd{SAO&W9jgMGD+0}r zgZhxIC;9t8!`R@;19UH*3ad3iJm9u(xs%A@fYsCFq=2Q}d9 z5^ul6Mp{qS33-5e5?~X+Q&gb#HOMX`KZ4X- zekigC_UJt9q50Xv@^q1k2k7X#pB|mpK#gS3I?Ur9os2IwnSc4;c_SLx-sr;nQxMtxpS0gdA}|6;1k!l4wj>fzuk4sb-eGQboabNJu1 z3lxQ3Nz6^6reg)mrj-S+&g;B*eUN&Nx16p;tj!@chRJvy0>x&LSI=w^l_ zb#S6)0!s$`ciam~WFDQu#~l8%dURWRTK?g0e+tSl5Q8j_^0!|DaW&tA7e!jX;4t~} zzx8eB3;qt!UUrXeW}oDv5QCfl*7Ns+)_a3(04)rHBm`lHjtp3~VfJi3fShZ<_s#Ws z{P$rz)OyLIlN%HXofj%XK(0-`5RS{wkYUp*Utfb(ZMB!AuoCn8YEC=9X*Px4`}BxG*>f2Re;h8Bv12z(+YzJs9dmu z%Az_EzkblkhhY1UaDdYqDA7B{9tPz{c=;aGdI^-0`KKQGehX1iz_Y(G#9B~=!wA*~ zD;K1YB;b_^?)(q4kH4=E9NZrNK?wm;FNu3}UaZUV(EQk7#o!38WWWYi=zFxDtV{Cf z^#avkE*(z)J-R_kp%s%SRN|P^ealk|mf!fBm_emo=Wh?q7qEKr zl}9JDXXlw0k$Rv;wJ<30dvr4QvVitvFt}(w0B0IVjqu8|`ColmvS;HzP$^bk1u4Zq zCzC*Ovam}>!GA2J7)Yr3C=J5{uo+$2C^0Aa-Yr*9tU4=K%EUQhM{eoLP&WC&aba~5axBWdyti1m33*7{#XWk z`U`gqgWm7!+4(&**t7GhXXhE8&ZFQ1(Yx(m{QmU+zenoL2X`EgXjExTpnMOb(czC0?RwZt4RfVY{KZ2RP`(10fBwZ)l`sEaf~FgSJv)DSG`}%G886ZR-ID&F-=h1N6<=jdC>UTO99Ywc?s}_BG6rQ3g9D2 zd<_qHbhCMUf9%nGjKhQRgiq%|kLF7Z6FeB%Jp?a;7N(3!54T|H^Z{MfR0BG&s6<7= z*YYNR>)pTq|2sB6;`eO+#m?Ubx?0(@8Pul-B{0x-yU_&Ge4N7rE#N>$BZIFm>n1Ch zP)+j$_1{}S_bHH>O)-pP;%})1RpL}~9W#FmxI0KC<5>7xz|%QYGLDtM1#}b}wZe|A z#FZ*WfecXqEguzt_76R}1w0r(fi^D+K=PF3DF^;Z$2^)3aClgr0+r|r;6>s^*Fd^7 z5Q|RPJerRfL?3?Pt@!0X^7vu5JLnRS5?h~ce*uqP5zxdGgHNy156^BN!5<#2CreFz zdYwMJ7KhsJ-Oa=3*&6_sk@oDo;L-eovGkQkx0`@Rvjsz`1b7*h3uy2wOu(brilLOR z8??}rp_}ddjl=x&510|!@OLdQ%e=_p7 zfDWDuHoVmQlgX#^G=B?dT?V*6?b+>f~667!>yP2J3yx_ zcv>6qHwl22HG-PChYb(Bo&;8f7*BRYZf~6d4eNY1{N~a8#sZX_3P95=5?;_9cc9JF z3Lf1SFE|vw{P*n6QQ?57vDEhIwg4>!;CEy8XnpI`S)-x>YV!#2x5RO+xgBHbQ>;<$^?)R5+2>5;0rbdJh~11x@|uBbiM*@5%K7j^XdHQ$-n-- zCvz-=2mksQ6@?elpc{6&OF%oiUwJkkf=BDy5_iK(pu@z$<7yt=EEio3PkLB>aO9tK5H#-I$#T)Bo8`Js z=Q$tC10K!S7(k0fJ6SG*Pw}$6T6z|=W4@Q=WvQe`^AQVJXT~ueGOhs5{GdVtR3Jd+ z*E&6ZcvyZadWxnTGXGR=2O61c0VOSu?wEr=JS=05|KM*5{r~@enJCCV-7&{Of(L)_ zw>$j*|G$V26au9$UK&ICH=y_`@MwMO(QU!s0@@1?@f(9jH_LT&e_i+KX1R#&uj^?3 zdS5CCzNZ#i-jM1akK-)hd#^#a40emCyfg;Qa3Pg z%YP5hjTuKgz~#;V|NrelE5X{eL0iCjbs#5yA8`D3)P{fR0ndYP**qDKdN%$AZ8_&} z2i<<>(_PKsYx$SI9dy5mZ|i^l7J1NZkKNeLe4o8K@x@$d6dW$1Oh(0R(G*KxiL zCx2TsBLhS8E60Xkto&`D&az{}4_5v*&{Dx2zrd?Sj-Z^MzYmo4d^#b{;FkmUU_3hy z@V5km9a}5_T5ABh1v$|3;B#j1RVv4QI)A-bBKPILZ|i^0&I2F?C6d03-@)#4JjTWV z%A3BH|4KjmbQcTwfWj1XMhU1RXTPzN35Z#e|&xr1iRK;hK-zl0a8;*U7UL>?87{AKsyIso&uQz?_c#gUhtUldL6v~-cZY+%imh?@Bjbf%%FP7@W9KNAPunb+8uxX z|NsB84I%`ZulfMuBi8YN?u!>Y&JJ?A$8mR%!x=n|yMwMHVE|1fGJxcR!D+L*UI4_= z_5fYeb?^bRN9%zSNe{*!kbH}L?*eFY@5N)WFaIIQ2NX06pujus3^o^BpMa;#K=pFt zKS+`V9WM*I0m|?IX#UWrJDtPR@=FnTnG>jo3|k-M(fQT0^Dj7>K(|xhm4-&pv62AK z?pOiPET@U*!6(cfpyQH``*c2fQ6&RPDNjm7JsIz$!RAYSEFYJC0c+^I|H4A-%YV<# zTn^vPQUSp$}^A?9YaDrJO2cGbmwz;bbj`*{L0@3ib_xw)BJ+5 zMBM|_bZNFZ$WS8u5;SiEso!lbGL#B>bY1}MeQAEhSo#)REqYiUhIoeG`LIXlanH`9 zFF4_T;%@=1L-6d5Q2~3*#;aEZJU9jMoX5eZ%rEAE6~8T!@L&X=I|rW+ehu;;NMq;y z7vZ8`{`+?3sBrjpmZ%_mF>QheB>cf_PXdYK=Yp~Xz}t2g9c|Hx~tnd6Spzwtim7bm8>7Bozi5(AcRd|xW1-ve{JB;HsFC;yB zG=suB5mfz^eg>TiyBn0mJ6#!kx=T4cE$1I~N?RCY}eMF++mYr}NW`6Oy15`>jOOm+>9=GF8x)ZePoHrJq0=K*8B2 z45}SsIea>61w1?Ny+rI+0Ii>C@dp*Oi2Ulx;Xxq3)(X5Z7XI@8xGSifX7D)f3N8ph z7i+qLv+T>ipal$|_07oXzw;}oJ<oO!x5&n^;?OW zPq(*#XLHTR9}FcTFJFT#Wq|Nu_Pt{)1#i6v4;i%nFVP1zFF0V`5>UHZ0({-N0|UJL zgWk6;@ZymmsAh;!0j=wi0bdU70y+;F+~;gQq5w-wuzBy!^B$bX`CH^bQD=FOzg-lR z6`DY)6Etqz0-AUB=ynA)Lr?thu&g=vgTLt$==hzz-#|?xxz|&m>At(>97z7e5B~N` zph#@$BpA9`~8%%9Kh(d{n4FUarF9VP%?Z`K(u0BS!Acyzi6fFceQ6OcQM z_yyTYLAxF`4!<}pgwp?n6*Ay;GZG%%aU9T`&EIYXN?P6J9FCA>6`g;5EI;$NgYKnv z>HG!u_xD?!=evCwTsVInc*V}Y?U3eiNB;FJD*WruGoJ8hz1```P-5%R?JMBh?V}># zlYAU>t=4T=ys_N$>^8aS$-n-92mks{paTXvS#E+3= zd6+(t8y@`YFM4!}+yE)|u#Dv>e+4eDklR`C@(NxKEfxWnLmr(6OHx3^3aEH@LoJ6K zK-EU;H;@u3U&eRf@(igw`U)?PKm$n}FFx{t5m@9|^S6N3`=JKBPj?;%v^85U@6%n+;n;Z%w3emwx3A^jaw*To|De*Azik&I z0|Ufl9?;r8c?PimKt<$XP!S0lhd)?y&!anA0MyZO1+^hPK<9gd+mHz&pmG+}%r24i zWPA_yAL#CCkLII{K9+w=KZEsw+NM4#94~}m-UBrnJvzMwz{k}1fcCkp2i>gE`Kg;5 z+&lPw%aQXM=zwd-1Mfgb86ML-*7==({RRH@KN){@3WH?$*B|6xe~s~iZ|hso0q7+G zKHbg&kbYb8H&^~`-wY2x^QC9E2&jd7+_UqdC;$3$KK$#?dGfEn=EHc>vy%rbwm z;eZ60$H9lppe?_kvH2G&px_0c)6;sgM8=~xgcnl(!@|$Q@@na4P$ygf)J(g^18;zU z&N}PP<^ZqV_33=++xhq94sf=IkTws0rnrbA^&10s2~Q-q#P+x1T{@TtytC< z&OBfKgIcDbW`R#op_kIOHbA25QN%24-%j^P9!wGX=T_264J(7aH?FGl`$CUEhQ&H=iF478cbr}F{m zG!YNW4`o)Mj*54$$-Cx<_8!L%`~%eimq7>TUiDCB^SJ!TgY$w%CmWVHbzm?&&~mBd zxo@v4qi5p}aGq#i%)r3lYk8!26=?L0!>98&I4pcX{hxK9zyk%vG5!|NOr%eDwSZ4I ziwZcFeOv#R1cTbnpuQ^TuFXd;>;yqMIPXZdtGbNF-?3%ry94fKHfBcRSYIHX@{f@Y79`oo~{G|FMO87euX`b@nUwp0jEhz9AK#OL4Ef1EkdUPIq zeFmJ$!S$R+w?7ByoW+wRsi2Xv?r_M+n;a}Zcyt@>2Th^9mW7t5pcecq50IU$-%37$ zW2m>n!SJL9cq9>nngV6~4b7^Kj zH)^WiZ2LB%S3z7pJSz6wf_pk+cWpabz5>=^i4w=*&@7~X!(#V^m`*kB{W zP#OuTO750^03UYKdXm2tw4n!_kRX{V1ymY>QqocWmL|}Idv_hUd&v6Y6~~wVp2yw5 zyFUKNUT7J0fWJk6k%7UtI}hA=jsz!ONb4D;h-w5m5mZEh1_@eEmdJTRN-1!d|d;2vk(Q1?Pe01MY^GJV4?g4>Y_Mf!0sp(Xxy0Jeq$wl(2fV{wsOsp?Lw606jVn z?*NU0dsyBriU7?IoCoz7b3p||>upez!qG$Xu1Dt$!%H5FXA$GQkV>%g8Z@LlnxBEM z3jhUCw~4vO<#!GpC9MBJt}MM`2)^qY(k}KlhM_uzLJ+bn-4MhT3#+n1dZ(Qd31+}|L|yj%jne^!vDil^MJ>9P+T2+ z!0g+3(1Y=y2jel%i!VJGe|j+fGduuKpP-}97(t1GUyy|jbnb_z<;C(>prb*WAN&K| z^=^5fL=)sw%^M!wF2_Lw-0#8Gcv!wJ23P7R5}kKFpkp3}-(DDSfNG<+CC;AKF)AFz zHsCOD+yN@ST{~a8YQ8Z1Ho>D?%8XC$#UQ0@;gwe25C*- zz{p|H^z3T*&EeZIdq@7M$2<B`u-xC6I?8L zbRLB4%Of2Kq;p4kVkKY!^>{a`AfYn;y*lk{eM8s2MxW0y9O^fKzpG+ zyj%$`IT$=z-}1Mt2RHRm1UXc?M zJiA2>d3K5%0JW56eSkqMpZDKM9-w`o3=EJ_PH=((U2qR^$3D=&1f;68WQV4ZqsZ;g!;Fabrw44%`1L$+tLXI> zczyzhL+hmyO>oPzx#q$Th7$3Yp!Ks5dpy8G{H>rVJ@8=HJJ4W^;U!SGmwR-Vfksu% z{a`5Jf2|5`w|O+zocqC0DghaP0kw=Sm2!g{3()YeJOv(?EPq`T0}2*n@L*Yp^kgKOd&R zfBY@e89-+TyMYFAKtoL2uN5bFHiO5P@c8dyQA9Uid3ZN_>w&tM){_+lAa4eMyy@9p za{=njv#|DIksruUB?g||B_O$yi$BWVd`4OC>c;{0UkT`fVD`hECm{36h6h0N#YnHx9!>0bJjL&YVWRhRa0-)LaFvwFzk!C#T@7!8gAzQv32HNYKxA@({#!w?nTRz@~tYx@kV~|0QT6&7E7JMeqdS|!5jL>d9Sj-=C>43R6+Bu1nty113p!;Tq_(-{ z)enY}cOI}wG;VMsvzEit@;!euXj~DTr$GfkI;dm@HGYpkM(BOJO9eo4RMw#J1;iW` z0}H6W`d=dB-OFMJEstUIQ=XQ`K;?ZYXnyL&_P?M1`*x-ZcytE~fF>dgZ^Qflh9^Cl zWsZ1u${Ybr$1=+t0x>p)g*}^Bk0V2V%V1==bOUpv(^%C52@E z(eY~J@#>i4|;N5EZOH_d7*q~ks+w* zzX{|fpYD)@KYT1h4*e)!1nN%}iGliDCDVMmT|nY4hklg3dEEiJ59O0b=RZ)PI0-zS zU)ucrc4sWZ%Sv$f(*fL$qMQHu!A`*R|4&e`(A@vO!L|_N|2i7QKR?(BnEw9(b{VbX z{}mm?z{j#6%{ml2Ocf*0FUP$0@V`&9-4;Q3$3;wRph;Qbh2PH6%twU&PJ==|vlYU7?P z;dE^|z~9maYRq�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

  2. va$+*YP<@I*Xq!C>1H_i0#Zpe|M<_} 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