From 6d73e6e79d3dc537a573ad0a85c1f35eff52fe1a Mon Sep 17 00:00:00 2001 From: Stefanos Kornilios Mitsis Poiitidis Date: Sun, 29 Dec 2024 20:59:26 +0200 Subject: [PATCH 1/8] vmu icon wip --- dreamcast/Makefile | 18 +++++--- dreamcast/save.ico | Bin 0 -> 1526 bytes dreamcast/settings.ico | Bin 0 -> 766 bytes src/extras/ini.h | 94 ++++++++++++++++++++++++++++++++--------- 4 files changed, 86 insertions(+), 26 deletions(-) create mode 100644 dreamcast/save.ico create mode 100644 dreamcast/settings.ico diff --git a/dreamcast/Makefile b/dreamcast/Makefile index 9930fbfa..5370d3ce 100644 --- a/dreamcast/Makefile +++ b/dreamcast/Makefile @@ -165,6 +165,10 @@ $(REPACK_GTA_DIR)/GTA3SF8.b: GTA3SF8.b mkdir -p $(@D) cp $< $@ +$(REPACK_GTA_DIR)/%.ico: %.ico + mkdir -p $(@D) + cp $< $@ + 1ST_READ.BIN: $(TARGET) rm -f $(TARGET).bin rm -f 1ST_READ.BIN @@ -180,20 +184,20 @@ 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)/GTA3SF8.b +$(PROJECT_NAME).iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico 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)/GTA3SF8.b +$(PROJECT_NAME)-no-repack.iso: IP.BIN 1ST_READ.BIN $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico 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)/GTA3SF8.b +$(PROJECT_NAME).ds.iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico rm -f $(PROJECT_NAME).ds.iso rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN cp $(TARGET).bin $(REPACK_GTA_DIR)/1ST_READ.BIN @@ -204,7 +208,7 @@ $(PROJECT_NAME).ds.iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_ $(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)/GTA3SF8.b +$(PROJECT_NAME)-prebuilt.iso: IP.BIN 1ST_READ_PREBUILT.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico 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) @@ -223,15 +227,15 @@ $(PROJECT_NAME)-prebuilt.cdi: $(PROJECT_NAME)-prebuilt.iso rm 1ST_READ_PREBUILT.BIN @echo && echo && echo "*** CDI Baked Successfully ***" && echo && echo else -$(PROJECT_NAME).cdi: $(TARGET) $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b +$(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 -$(PROJECT_NAME)-no-repack.cdi: $(TARGET) $(REPACK_GTA_DIR)/GTA3SF8.b +$(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 -$(PROJECT_NAME)-prebuilt.cdi: $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b +$(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 endif diff --git a/dreamcast/save.ico b/dreamcast/save.ico new file mode 100644 index 0000000000000000000000000000000000000000..64f7c6745e51c8842c7279f8ba754eda2b1e2dd3 GIT binary patch literal 1526 zcmc(eziZTB7{{M$8`GR=?Q=*$$1397F4dxGM;ReAe;HFbI=lZ<)ao181UGxdb@yYl5Jn!?qc@Cff3$V4dM!!q& zV*%hMzy)4i1lZ>9=yvGlzTZWo(FAGnC2Visf@{({xO4vz?Cd;&2ajLC?yJx6=Hn53 z`0^9Z&dx5CF-^ll4N=hoMMK66EJLvZ#qxr`wLTbhbBD?#AfmfG%Y zPOj30b`YR~*zcCC*a4Y!9L#l1?Zl|o`L@qlKgYgm;Nf|0-PVV!Q}^flElgF$x~8#f zE4JdJ(Wm}7BifdQ*d7ovW}2F<(B803?t+bHmgS??FS*MNnjZV5ohKN+ALB+T^NOaT zV>&Fd%L*@8m!6ZXEJgN=UVw`LadACJSejMYxfN8X2{V?Ub z<3VyCiLpvB&GrF51+VVk*cc$LuU9%P)^C-cvCKBw2Q7XmMKS6P`vcQ7Z?-E%A? zN#waA?5&>i3%n!&0mNpnWWh@$5i%Ifb!kb?U_7fA5ks~==SZB&)A@Q)1S==Be%)Ub zHOJF1413IZ?FpU`!AKNK2Jl=LU`#~8nB)nxH{fc)PUN}~NF`DMGDvbpn=ONmB#N@y z&R1Z94EfhurxoE?p3a!DCdkKmUgkJ@rMP5h3`K;egS2Ca037U literal 0 HcmV?d00001 diff --git a/dreamcast/settings.ico b/dreamcast/settings.ico new file mode 100644 index 0000000000000000000000000000000000000000..5230be86eb1657d75fc63aab39c391fe0e821995 GIT binary patch literal 766 zcmc(dy-EW?6os!Lf)I;{D1t?nsbp+~Ez_h32x=v!4}vy=q}xJ$g7{NgI^|TG9}>vH z_p$Z`;yJrfl!&co_73+ubMM_@S+f5bwr*?GvXm|Yw>%G3)d_G?q<727dDDNCenR6jJAGk-yMTNjeE;)Gb znTpx-ouiNPbzbyqSPYz7OjflLVJWiLbV=_j%1Vr!^#e7zyvm}WXi!d #include +#ifdef RW_DC +#include +#endif + namespace mINI { namespace INIStringUtil @@ -335,6 +339,7 @@ namespace mINI private: std::ifstream fileReadStream; T_LineDataPtr lineData; + std::size_t start_offt; T_LineData readFile() { @@ -352,7 +357,17 @@ namespace mINI } std::string buffer; buffer.reserve(50); - for (std::size_t i = 0; i < fileSize; ++i) + +#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) { char& c = fileContents[i]; if (c == '\n') @@ -378,6 +393,8 @@ namespace mINI { lineData = std::make_shared(); } + + start_offt = 0; } ~INIReader() { } @@ -426,22 +443,54 @@ 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() { } + + ~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; + } bool operator<<(INIStructure const& data) { - if (!fileWriteStream.is_open()) - { - return false; - } if (!data.size()) { return true; @@ -451,13 +500,13 @@ namespace mINI { auto const& section = it->first; auto const& collection = it->second; - fileWriteStream + memStream << "[" << section << "]"; if (collection.size()) { - fileWriteStream << INIStringUtil::endl; + memStream << INIStringUtil::endl; auto it2 = collection.begin(); for (;;) { @@ -465,7 +514,7 @@ namespace mINI INIStringUtil::replace(key, "=", "\\="); auto value = it2->second; INIStringUtil::trim(value); - fileWriteStream + memStream << key << ((prettyPrint) ? " = " : "=") << value; @@ -473,21 +522,26 @@ namespace mINI { break; } - fileWriteStream << INIStringUtil::endl; + memStream << INIStringUtil::endl; } } if (++it == data.end()) { break; } - fileWriteStream << INIStringUtil::endl; + memStream << INIStringUtil::endl; if (prettyPrint) { - fileWriteStream << INIStringUtil::endl; + memStream << INIStringUtil::endl; } } return true; } + + operator bool() const + { + return fileWriteStream.is_open(); + } }; class INIWriter @@ -667,12 +721,15 @@ 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; @@ -688,20 +745,19 @@ namespace mINI return false; } T_LineData output = getLazyOutput(lineData, data, originalData); - std::ofstream fileWriteStream(filename, std::ios::out | std::ios::binary); - if (fileWriteStream.is_open()) + if (generator) { if (output.size()) { auto line = output.begin(); for (;;) { - fileWriteStream << *line; + generator << *line; if (++line == output.end()) { break; } - fileWriteStream << INIStringUtil::endl; + generator << INIStringUtil::endl; } } return true; From 2365fe663bd087b6e3784f66645cf0357025b9bb Mon Sep 17 00:00:00 2001 From: Falco Girgis Date: Sun, 16 Mar 2025 10:23:55 -0500 Subject: [PATCH 2/8] 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 zcmc(eziZTB7{{M$8`GR=?Q=*$$1397F4dxGM;ReAe;HFbI=lZ<)ao181UGxdb@yYl5Jn!?qc@Cff3$V4dM!!q& zV*%hMzy)4i1lZ>9=yvGlzTZWo(FAGnC2Visf@{({xO4vz?Cd;&2ajLC?yJx6=Hn53 z`0^9Z&dx5CF-^ll4N=hoMMK66EJLvZ#qxr`wLTbhbBD?#AfmfG%Y zPOj30b`YR~*zcCC*a4Y!9L#l1?Zl|o`L@qlKgYgm;Nf|0-PVV!Q}^flElgF$x~8#f zE4JdJ(Wm}7BifdQ*d7ovW}2F<(B803?t+bHmgS??FS*MNnjZV5ohKN+ALB+T^NOaT zV>&Fd%L*@8m!6ZXEJgN=UVw`LadACJSejMYxfN8X2{V?Ub z<3VyCiLpvB&GrF51+VVk*cc$LuU9%P)^C-cvCKBw2Q7XmMKS6P`vcQ7Z?-E%A? zN#waA?5&>i3%n!&0mNpnWWh@$5i%Ifb!kb?U_7fA5ks~==SZB&)A@Q)1S==Be%)Ub zHOJF1413IZ?FpU`!AKNK2Jl=LU`#~8nB)nxH{fc)PUN}~NF`DMGDvbpn=ONmB#N@y z&R1Z94EfhurxoE?p3a!DCdkKmUgkJ@rMP5h3`K;egS2Ca037U 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 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�+2R;6 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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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

pIxZP|@F>_uaxu55JOrV+PWS=nlaZR#GV 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 {!p5y2|Vf-v$nSqSl#i><-_xxV}34@Z$RM;d?T! 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≻c~&EMBgANE&l&cZoz_96c~2 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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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%0DXXl>($<@2r9p04tpIUQH! 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/TEXT/spanish.gxt b/gamefiles/miami/TEXT/spanish.gxt new file mode 100644 index 0000000000000000000000000000000000000000..101e97c6a1865466100de3d29c7dfd873131c995 GIT binary patch literal 455414 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/data/freeroam_miami.scm b/gamefiles/miami/data/freeroam_miami.scm new file mode 100644 index 0000000000000000000000000000000000000000..36f5dfd0b02b2f4b8a4de399eb6b173d94e21db6 GIT binary patch literal 38197 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 Q{hNO{w0t7oiYn3mAD!EYUH||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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�AU);`GlqzeOU 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

b%}d=&U71g)ecH;X0_Vj3cdlg@v-O_!2chwT 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 l@fiuQ{G)#I@`&~kLNXb6XYh?~ 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/models/frontend_ds3.txd b/gamefiles/miami/models/frontend_ds3.txd new file mode 100644 index 0000000000000000000000000000000000000000..06518848e6f069e39798eb58cba692e583c402be GIT binary patch literal 524968 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 literal 0 HcmV?d00001 diff --git a/gamefiles/miami/models/frontend_ds4.txd b/gamefiles/miami/models/frontend_ds4.txd new file mode 100644 index 0000000000000000000000000000000000000000..96193714680acd99f778a6d7413c20bb8703713f GIT binary patch literal 524968 zcmeIb3zQwzdGEQqC4tZbj?BY$j8PlgV8;P9FGHL_i#Gy;BwluGED1390Y*k(i0$hU z4kQrUTw!1CT>Fl%#l4PaCd`^E>@3G#m#mR;XO>~IW+LO61Sc~Ikjahha2KryNUcZr zp6^%Zd{w(jRp(jVUFY;yy>{=~yQ=p7_J4n`{ir%8Ip@ATywG_+uQnv-^CfV;K;jc6 z+%GwL?Uu&*LdhRq=G>iktiOHZJ@;?gc=z3RY?8c=52=JY<=@cA5H%C*Fa!(%L%3 z!tD}PN?0tx4nv?_1m??)=SWyD0Y3Vegi#6P+fh9P76?hV4-F0dW35*6BO@dJ^wUrG z?|%2Y{jdGnulWTF795uA|3<=b)g#>~st^M3(S5_i!+TCV@kIa5cfK>?qf<{k)t_*} z310H|%JoMi)G9>tTqornBKLpy_P4*?UwGk#{)#KEI3iqj*=7E;(@vxR#s#bua~&{i ze=Y)VlH8xY?QL)KD_5>Of`{O%OD?%2vMKZD&qohjnOllw&LjdSN?+_b_uO-H`zxyp ze)Bhf(;s!zQTv3zk(ngY>Z&{fM~e-4@r*OhXp4`qGm955_R^}0D&MlfCUDDp-}~OS z`b>4fiWMv9w?7?hSgds&B5?O9r<~FeA7M+Rj=$@$9=p9f1m=m({;x&8n#y}NzWnmb z{h~#S=(~59M~aoy#|RuHeDvBy7hTkm{KHpEmMnpfPU+*e_cQ6g%^XcuzS~Lv#&7%v z{q{fgbK{Nn(h&Ig$tR!O6(2FbEL424G)kz1?){xqX!i~r z0>{mpH*fFQ*jQJ5#B(yI{Fy^!N}?0w!n=bwMR7r*asg=AaPowLYZ}cy7K7!Lj1CEt7FCT%6k3II-o@@v_ z^xMDv+xP(gEL9wnUybFKjKHenjytYr*{AO=z4THK4+wpimQ14+S8W8|%DOSK+pe8& zb|3rAq`6svkkfb8+b4~zPNxdH5Y7}&2Na?Y6FA9Ktx(SA&Mmj~Vv9*EY4L%!jhx#EQj7e@Rg^M%p6 zAJ6gpS!bQ)FTVI<-`H33ms0+4)j{Y}`reBC9)JAto<3loKYM=J|AQ`QcOL-uA05x0 zLFRtm@|L$mdVxIwQtpIcv$Rj~F|u1D@PAzhSDNW<{4(Zg&=L4%zy9mL9{G3pc$|~RHRg7j zjTIPQ2#Nn7;l%DpwWlhE0R8ya@DDtaC%ei!=zr#e;1}K(g?AWVl4iaDzrPvpQ1(eD zodo~zj<6l&An6QUM=vn<2VXD;1m6_# z4txgR=seUr-tmrTJj#5M^5*;B|9*IfVEcl5@AW?bOE71A&U`ELe#km~5y~?8jNe%w zKyNe0i(C^pCyzd7y?}n_SzRxn-&sE(Ft3YFfM=v`)&$<(do!$rSwMh(qyIefM(op5 z`BmP5kLYuFhkj>27<_{5SKh(?lZUMHEt!yaRA<1S*cNyXAA<6zR|5QNM|TLUm&YO* zMHcBNbTxg(+%2{rtXuhGs+aR(#uJPO=xbyiJ)k;)`lw$5dxRHCu%mwvpuhe`Lc}ld zjoNK&1nWN7lV0YL88cw(bqxuA!7rdL>TTE$&NoP~qca3fmfH`5ApyC>cH_e}n*+#` z@lNy5C#>Z*_I0Zc`2Ehvx4UN{gMKe-uU|8M;+p{Y3fO<;U7>?-&y7g1 zqkRPaL~f0Jt(oY%Y`?N52o3lf64QsrN|=d*ZytNTVBxY<&;Cp80 z2H_GpXFqcou9Gz1L~S>4fA~D-lpEGVy-LG~oGWe6sWd}B?I6&unK-EX5dDTPhmV-; z+v!fjJM0@`d?2(Cw$DVv2c(WO!Hh8)HXqy1KA-9Cvuyb~kHfeSdL%HXtNgFBft(;y zAC+)tBQRe{cuc~-l<==5d{@FBN%(OiJTLK12`@_cg@j*9(0b-VOnYfJ?FR?&P;9^n zyuc0o9+RM$D#qXq?%=O|p?yO?Me{t^;Mw3E<`>~x=9h_~M*{lNj@Ah9o!)4C&)ACj zI_6{X$+I##ooVKbpo9HJ#4{Nfs}dMfVncMEN#~~Ft7XfUMS-~xXbl6Hg@Ji1^e`qbTKAg{{^8vd+Rr$Exd!GE8*TrV z#C9}CfPE9}!Hw4A*ssByD(fro6_~cWPqW*;rF`TB9)}L*f8k^1eN_fjHkc#eUgnLN zA0|K}f#(RUCu0xLBkVOLF!zLi!Q3P3j{1HHYg1(f)-mCG0&TkT$}6LA%{ABfqM-M1 zzdp-+NLe&d4t=R@rv2am9*PY(ffu+bhKi|T4Bp@l{@NGXH}n&JKXXO8zRP&Lm$|-n zd5Jv&$Ub8Z^bd0nv=u&*z*+?SZAUQz=gFmL?gZT61#~!be)J!9i8TCC_BKCo_uqfNf8c=!qVW08f8Kxbi(ibw=FOY^bOo*B z3t#v`TsL$;4|G8vbVBc)citJ%f9tKcM(v?pH{X17)Xra)2L)b3#8u|#nmhk>!*0(Eyzz-xub0_#t@C1G)=h|<` zCH;zDhwjHtfI0HpuAbKWux-dZ{e-MzJ9LeKz9gUZ9PCt?{7G5JIPx3HuFBV3h-o+N zM?aK}EqzLVVuR^F%4ZH4et~bWBiN8`=anfBTaOxJ9~*`b0uRI24gQwM zjvNF=Bro!{Sw~_16glS{xg?Dos_bGrkbU}+u>m@kJs8|eKV!F%U-cQ)U&K$OPw?pp zr0Fl^1^SjU2$aj-p)&cCdf*k-q}0Ei%Q5@1v5{qBOj*b#^ApS=qXQUcz&r2_bz)PH zQPv6YDUnflN&P+ehG$8W&vkr5<})ZyeT;DLA7d^p7yW=BL7)FCQbjt=Mvy|I}!vwEvJ!wE|M>>2Y2)!t(@9+=u8u9}9$}^e|P0Bai4?hsYYm}jOi23R) z|Ag(p|DY@h@Vgy#1m;N;?SJW1KCqo&4&UHwVYiWE_y>8_fUc+CbxxLd2k_~zJ;4}0Hea|>Dq_NdG_0hHlzr*)- zICrst5cORv{Hkyrc}J$vC+K~A2W$a$fIwTa0e__mwD$uI$hi7ySsWE(@K&E2xnvyI zsVvSG7U-x(yWo2}>ia$;8$MfoGM)9}BVt#kvyMS48~>fL^`Nzj_UIT#1fTuRbNzUm z01)|ZWzDl!jb~`zbnPFiT3gGahq*v}tFp?gQaOxG7^}b+65#*2eEhC7J{q@IsgKH} zh4vN6|C%*xqP=tMpPOreeS$?e53>BzW}V{@-r&8axq9$EDDQwpb7)1p(C_)QuSovc z_sd?{xfpJ{?Y229|I8u57ZRBFo~ieurK(CD{8mb|HrDSl-i&_QSS0_=o}0_p2HpPC zx6E&d)U`Rtj|&xWog-3XI`u{a6dFqH|1!#>|tRIOY5V4JtyWG^Y_p|%(Ge+ zv9=ppwB2D$ouuc2{IlK&Z%9B6=Hl^}(&&g+=<`7hv{B`sJp;%X>xZww&_?u{HFW5^dWgWQpaJhHEWd-Yi@i?XSMI-x=9)-*IwH*0Cs z&2!XEIg|$t=o+PgdZ}OQhAwo9-itn9om|Tz&bD81AciKcgU4KufBFyJkboS_wS#Z6 zNAsYAD)-th+NtudcCVT3MGlZtLN9IS6T^ohtv>RAdYr%ROrENF#F^tKG!rBGbqVa)x{$ z`^Xi#Kz#tsLnm-AH1I6+X<1r!o(+a3@}U75ky)POdCH+Y^aSN2%hZQXqJFI#U7&Sq z%(Lha%2MAVY`@|_Og?pkOP>6BKOD{vPb=@12j){gz5~H zUt~|qRvn~uYdWNX`;m8^hX%@n2IL?Bj*Sb|VKqvR|3}qnyv`1+GD{X%mlMjvH zl9m54aC=_Y2RjUAs0FkYC;pf!}$KwyOLi z4?K%Ja83C=%;!2X$MeXM`rybDGDn@r9WtRhK;;qHgQrwpd6u#%i#k=uP)DBZsqW!^ zXyAF3Wy+;|bWccwo)dG8xLF5)WMV7GN z$T_k{naFpR1|1KO&vowOesmLcqc&LHmC;}L-F(A=Z&$8bwaT-nhVRQ=cinYyAWi<& zS6?07$9vwreAkjWb7WWLJsTr4%nQ&y+N$!8?2xB1a-wPM9K249JRyt77xIIQA%|yC`-%MI`d+sf%~C>x+#b9D3|h~MeCz}JtyWG&v36k zt7Q>uyP-wf9mdp2I+XuTu;$rLgMKgKs;jPw_yb$I-C`@sYr zk+4I;pdWnqAbL*@UdzLyojkt9Kz||QS=s9(&GYmr{n<{vdJpo9Ei0-E+shtL?x(#f z|GYbj9B7(vNN^8nWR>g0=oVy-I*==DII>2$$QH7TtPqeT%|i$1z1Uoy)v~l~>H>S# zpQ)Rew9+SMvXmf#EI4Sj)JBR|L#HW(iO*(7kTdECRj z=n#FDx+qIy>^U^##o>L>K;4w1<>k?!btC6mx5hk6ncALq+6`^fA^*rIx}J9~LKzOv z;Q?qyck?|T{hlQKP20nJv*+jnzAx2{U%aUz0_Vswwin*T?yK!ZKVZ+1&9H7{QpYF6q@h9U=6;@|ZfrQ^Q7+|E5A{*6`XR*7 z75W}L8`h2dP>0qGOU4u%B9; zqX&xmv0M2^j%cIC@LZVId}8DQ9z-u_j65Sp=mum|gXST>+@sG>hL)veYn|CxuZQ<* zImDE!VKKG5m35uEcd2=nN>c64@LA0X!+`p}1t2%U@t z!gBHJvSWg7<)80DA{Y8S5cYr2ZqmpW*O4P^GCW2-$O?88+l!rm2k`^2xy%P(tH{Il zVuSG+xRQ{Q0i$IR_ zUi1skQaA0POvx9V?sAE~$jK1JG>>aYBwig~EpEU9l+F)daGO)qu zFmR(>>@D@vHUe#7E?#w<`U*UY{b!B={-$i|;JbzB2d$f!w9){L+|P5=O*v}Q(XC1Y zxFQEyH*|%*2X>WyLAJqOY0$c~-5Qgx{y-@IonVa(DC(2Ed@!aim1|=B{{MN~d;Gff zb(w$Q!MZL8#{t-W_zl~hS8iS&eA;Y0K>s%6A2}JcgWo)fzMV^d=Y@9tsVuhe2Xi*) zj<6q#&hhj39w$7VEvub0Ism!CZm_2y`wabzFQ@{I~wQGyU&9eC`o> z*>M0oN4vGGc4BN>GabOUrQsI|gZ3LichN4@@9;nvz@$^VuXytvSmZ$au$`E(Kqvm2 z)}=AJinhQ5tRrha`T#r2?}ljmLixw`LaScqdF~?(FF_OXi!H`BVS}-w*kEYJwlnT$ z9st{m?MMHShh4@#tG!nH4L#Uk%BBv!ZH;Vd-NdBH=Q=cEt7#u~Qx3Y4av9@8gW7!L zN9iF?{S>7EyH7iyfil(C(Dswo_Co`8lMdydbStb`gV#RjBnCTt7UdcB`$JpLwVQ9g zI2s42Pe2~D)22@9<6i7Tc0P$VYC9Y9KWM)X@B;0EKeeqIg9Vs$ilg3-9nQ|(Xnz*P z__gr8meFa9u3{aCF#vlv;01UWpAgwa#B?R;RR0SKV3pr=B9L@%uXM zKfNCxjs8*@-}e2}{r#W2Kz^Hb>jkL?S;>|M{O@Lli*jL8<*eq->0lTkx*k|sAuk~5Vf-WtaI-!C6E!0hnd@*-I-Q36hJV)J> zLwWcCjPV)EsK0^z)Vj&T?(4lugO;T?#Tc@PH`>W(yrS(2V{8aKBjGIrq5vY_A0IGmC+FzhcCc!8 z4|_83!-{NuUOs#bKQ}A4oohvX7`8K%|1jUq(__F8k0SXG@8P^x+d&^Qo>3nlk5{sJ zj8&0+<|W$Uk!JUVYf9QS}7aGLS(Yz?+ zt@<8W`wxxSYGfj`|M(T?O?;3%`_K4a?LYT2XMpdZ_8%RMZ$aJa|BwbR((Ty)5Zi8j z?`&V=$AJZSQD!r~!QbFFy}=+`PB&@l4%@^zejYs2kehzr3v@1R$=mbO&3tVh#*Dr3 zHJkNY_L|Q74do%`@%L+>ZT6b~^2=qWf8&M`tmn z$2MS_LY_fyqI2=h(X~b8bd#@Z$|27L-v&m0`uX^-5FcR|wy!MP58l`q>{8fYtoNfJTy=qZ6V zouTa~t?ehDx=FW^|6b<&^7>En;7i(zoD+B-fcahY0yZDM(tFE_^Lzkw4SXX3`RUhV zsStjc-*SYnLYXKNNB9pPx0TH6b@VOoa=_zd^&$IC!uEydfYy=@C`cU6J6tDT#DX?*P8je*?9ou zrSTi6hlPKTqkcSY6QX0C9?C?kJ^?Fi3$~qrZ^qaXy{K#7*nDt9eQ;Y2eGh2Brc*BVUuj?rMg8gv z&>#3!+zY+ff1ago+JpV4eOedoR~$5-y2)=Q|3$tdJ{a{dpHKfWufaSWzrV^k^pr8* z)Jzwkm%s(z0Uw@rK@&Vg9_<2$Y#Dj;$n*!iBLO+;$K!8==!j?NQw{WAw*6t6IT+*) z-hrp!Ie3h58Q9|c;)`*e{Zh>1qX+QOkbxrE$L`Y?_`A&6A?Nf5<2iT(T>!7rKkz7Y zav%B|T>xK)bY{m38i(h44{^J7w7M^>n{vbY!gJ`! z-b=cZ_9`vSKdw53za@23f`-I>K4{eXe$Z8RW&vj_zezpJ5p!T2gl?Ljieyv;W zKe`lpL;Fuz$Tnpo8*29z2V(NM4o%JEKkG*$ORCee{hm#uC-4ozJ)6z?wB2j)G&T!c zL7i7!zQJF2*&q3}mp>^JvJ@ zluhHW(Bi|ANarh<`2AZL?EB*^ea4>mAz07J3Yt$fL44| z%KzYHf9P+#n z0UHFZFA4487cA9$`%kV6+u4QT2&_X-1wwHWp04L-a|AFV>LCV9gf?pY9p>L^= z`jsB?xTg0i4YUIqD3h|OTk#+b4$wwE_>iU_=_}5&A>1pj`*7$hTwk zGkC0(AFBP@Dn<_?|Il#t<@br~|D(wL*Cc&qik2&{xYn<^^iMmzK@U0OcR zXWI&P$Pw}+0lDgj<9Z=7vbovzkj~oBkAC!{(RX@ozWL^;{fcE){@O|7H!v1}m(a^< zE2tNqqVL-&C+{BoW8?un#dn3ETlOHh(HGQ{A2B_cIBV@ zDZ8w7eDorICFQQYbi2s-k7AmXHjS^mVztQs|118$*Q0yTL+TF~ts&vNB2N;KtA041 z6CxvgvwcT8l>b|T?{p#m@Dt-v#jBf`{=hbcvdOp=8`DjB`V6*;a#a4&0U!I=$Kra5 z)&QUfJalZ1Y+?_=LhT=E_#T@}jO_*s_zGFV?jygni*nTNGWO=2JbVIt1Z=SS4Cn;P zLO*EP@IErBbrX{&Uuoce=%#M`3Cg2f$`5Ivex-+)IRU*_X`n1*(iX;RF8wpV`qJ-5 z_h1{CH>Ca|`KN#39|_M(sQR&72#kDg`ZKTpLiy)gU3{AlSwjAi4g9_kuTIaAb9~aU zPvL8@#%6R{p5BkYjQ^|h|M8E1JQ@pVJw^7PHZcal|A1$~1X;<8DIeU3c@BAlc4D4I zrjRG(68(UD>p9oB2R)(BPzG|OWl|?)H;eUtVl9W5a;ckosGEAVZep%!-5OH{G=%NS zt6TA)os8SCdEA#56mRCsLSL~H`A4s!&$O&wV)P{X89rgI@4CyjHs!JFFa1BGxk2t% z|AqRCE)$XAt*%|cvkTk`rZZ|)U4iu^MsLrzrYI*s8Y)`8#+`j~Mhwgui~ zof{eMb_{@CLcWmy+ittf-*CeXDgI%sk=I`G7#lN=W4wy|z%%4ygNR8ZC)iqIXoqI( zDKd_|;yGQbRJ%)iDI591P9S&q0mviIBB$^ zQx9^Dj@7!M2ib)_?uA~SMJ{NEmPsAdt?dtE%3$snpO|N$EidT!AAK9n6?Y>4=ojeh zMeg-k`bGVaYcKn^;sgAxzv_w)_2mXu90p19+f6LQ%M$8bL$V;1_EzC`{vUih^C#Mr++ zB9PB@?n5upm*M)m%5pa`bsr8@30#a^)5ByK|TQ`$*Q2ch>xFyyX9r_^(6< zJSseQW#n7o3x+y~x$9F zf1R5`_VFF_p2P3gF#tX`{l)kIpO!Rzhc3zPJ)k^%2K9a#$L&N~nFUKW4GclheqNNhH?9iIZs z(B1SQv+rqFb|3UMfv!hlufzp8K5or>C{1=;=5<*4AbrO zUmgyYmp{gDh6_46#GZ4;W%$$Z0Am83k6?a;IRwC}V1K;L428RxN%jIE{3$d1mlWuKXjH1?nQQRJR}Ko2nXE8-{aZwC+BgPcjY zr$WLXf8z%qsGWA?>F{p*0Ur|G-%RGS*PxSUx~((&T&KQ&I~etH4?YfjLQFY~ckyQk zoRinhJ-yIW=Ckx?whzimqaPVdfDd*8J%SH0UA_Z)i7`w&_`tUf`TqxrtMYB2v!Dqd zx}7%YJ-Roq*M39K;`3!?yjhwtCw<#%-8_#i(l&)LHV55=Jz|`J9U>-8KKvBkU*hzv6=?lxEFPOh$?J&d%A0Ax=o#FkQBQIs!bLM?`?|^Xv@(xYR z=_7OW9b*Ur^_RgnT1L@(BiS}<8aZp|fU_m8!trl}h1-8)w_?ys%=ii)tC_rK zuOScgb1(Jkv-q5}6?uk#@hh04z}7OSz#Il+Lh^}8(}y~@fPK+*@>ZC2dL8+p9)j9^ z^&yEVubq23t+(0z=pg)J=3R8Yg}%@=9K{J)1V{D)gBNqP=uXuK;ryBQMYl1&Ib%q4 zPL@}~H1i3}F`yH=tt)&E9md$U8D80I=qBV%0&+Lo2k#^tfY&>n$E982UceBSPR`NI z_-kQ5wK~UsFh|o#J$d&sp2N-}!^jx24J~SWvwfFMb073GH^lSA_%qr2x=kZ1?0+Jt z+^}YX>=RRNJNI;3zdlDD=xKZ|WF8r3K3n|}y)Q53S>z4AXC9b2FzCmJ(Y-)sw*O23f_UPcIzm*k9n}XHaE+IPUH=_lQ7%w1g#bt zBED@#bI~<)U$1@0x-oWF`>CCnK8KHr`lX$GbT>Q=KSKljSk%sT^6?+g9oRbf99z@Q zbM4$i*~kn*#~s*G^bIj(X~NO(HZyxI_KL?Up2c23>bUB zH@Dw>mH%J<;5vWjhwA%wl$UP2_DcWE=4<^UpQwKmjPV=m_q~ik7&l^vnzgm)8Z;w! z64pwX&0~jD9`jHUz0LCRpPThht80w?;bHBoc4O#nRd%!M$UMFv*gA^k+NB* z>{YK&KQcpk1hu)~7QSbfcTYF8>ob&xKZI|OU#{$Jm;>(?3-}%Wm9Rs? zY#s6M`t+*P+gT4R!czD2Y5%no)8F_|-S{5#9kRn*5aY&f`>@q>%u!&k@ip;(Tisu! z>+mOj5%V&=@=oaj#U`^j)7K`t$O`eVs+P<>k`{%&Ucc)9ReD zFY`-zNhgihn}G>%I~$Uoji{$ zp#yZR-pR9h_aZasK=8n~D62ilK6Cc1-r>oksh2$F#29OIx+VZ$FfS3xHopP>_}$m| z|0F*D9WoDq{1aAZ<^|yeaLmegvvgUq4_5Fw^4E~VncfS)+GD&P+DqSMpY3KWfL%tH zqm$_ud_v~ap%Fh+*Wu9h=!i~j1$qfSVLaZhKg)g4gv>IYM>eP%nd?SAm}g+TOPN{B z+DYTDu&fm9K`KtVPeO5O7jN1}-*>XN48B3H z@+>;w8nN@=`P>Knw*Tf@e}l~Vk%#U7=B5w&KY8d{|Do0OJm&4-tD^mUSslQbu~(lz z#7obagJ|f0b0nV0BUH}nUx)2fZwA?5|q6^%6-?VlrJ z_>Mhh@Oi6p^*VE2*w;?S9{3N)Ci*dN-vvGtIthM4hC1~JcounRS2rQk=tAbP(P_fC zg#q1!u40cwt9DZ^6ZkOBL$9@JGj;~Q4w2eQTD6~f zTKop&x0NqRoAKM|Z`yT8!jvh;SF z#y@L?d&t*(qlfQ{=6$Otye~XQ&(QDaZ1g;Kp&7l{>{fn0*Rj9&5yY+T=e}0FjSX!_ z|6(Vx5AE2h5C`-Henzvl>osJe)fk5BIv-Oc@6f3@wHgyp2KO}Edx5XPI1Jy7{$%d0 z6%NR~exC(hz#1>@gD#!>331Av^G?f5zYB&OHZc9q5>NLLt@(pJ_$m*~WQ<S--ixk5 zcZ6kf-b@doqrgD%Xcg0be<=49^j08PSvOJXW;l z7P)|r!9oM_74m7f=ZrB!UzzhF`GOAo-*(!|Tmb=F7s`6}9D9wuY-etVc}nbYQT>eh z6(3?S!=LXKoA9}S-=jLvS4BPmdoS9VD=BJ6KjdR4;DrX>$mw((4@ses&1rXTs2{M) zqY=3)T4TZnfrAFd$sup{dJeW)HZkLaJQ~TP&s(h*a38XP4di}!g?TgDhfU*o#yiY) zvHoAA_p&rFwq|}Uo3CkXgth}b%aVP?H`x0U)q_8zb%!zS$<6_U*9YO8zNIf3ct0$0 z*~hRH_^O0x9+a`$AmEnQ2FCWy>{@7h;49_FGUK2xn~t@ZKWlf-4Q*xKkbN}h7}|l% zBd5$$cDi4mb+V#4Lh1?q80>GE_)r!;KfVe)seGB&pUh=)U*5Gr$)iufxPirgl^A&~ z^Egimi}*#yZ-atewjIs*1v{#IjX#?=Z=J{I+=q;zH`|#bz{d#J%Zh9~Hn7BCll~{NB)R3DF*L^h`VRL;Z-O(hM)7@U^ZsX8U8Ke zea6D@yY3^;K2MsyR9}O7m9{WuoKoZ)g!fIyd9(39wj6$8z9Fw|ng=f-`$bsdYvQ}3 zBlMYU4A#^|$iss1KXpt8230AOF(6}6bX@}rDA8)yy20LX0_#}}oLct1#yZ1e+8?b66P z9DJ3%AD;j$;9ZsXFjih|CHvts_^*t6!#q9LJpuG@mX>zc|Fh4PnTF@`)+eDKoUqN> zmM{ii>`0i`?3}R@brs^eB%WuU%`xXp z^cQxOHe$Q7^?@^36xnR_LXpp3#NWt1IyLV)azcH1z6gFso{q}WJe}zx+0yZUkzPZV zTaD|pcz}5k?&y*t`NvlSU;1@Av6#)W!#OMUQH5`QBB9rVF~ni!qP2bCIjC4--|;o2 z&C%F9+fJST(>0JRkHXj4ymr#mnI{iAznvZb)8F(7;{$jfUBy_MH6Q|dLjy9(d_y)c z=EhbKXRndwIW2=&%cdUcqh9ES2IUQn(Mw>MH;<1W--+#4{J@90G#$p!M15Mn#^D|$ zXw93`(R=$n4%^>YJ31_Rz0LugO1box;)DF>`Fwi4pJQZ!_Dc8#adz*G%0KcD@*n5O z5@WAyemiO837N~@TO|L;RfEn-#M${^WEJ}Ic% zqkH<9KZ|HbuZ8O$$ZLZ?-YIb}$0hP`WCyfvbZ{qoPV~8|$JqMDJkpf3d8LHIq{Fdy zGyfmnLXMRuJBgVG!`^0}LC=7HIRB@<9{CckkU$^5Tf$iq&TWMACB8HhZjkhiLD(SY zcS;}*^SLkkEahq0)I)vL`)LW0T@L5om~$gPZ(hHst=YEcrQyB2IRM68tTlu@7Od6$ zQfPUfgsE(QhWGdLx$=azLHS17jQsX;+$s;pbAOc2SgL3}TI=t}n6V{omGD>tu-_w% z4GG%=59P@@yxxsp3!hUSwm!@Ax(9&%RT{9J4LY|r=$y@n?@s{TegBMmr_!m9SU>$I%l0UP5aA7#S}b=NIK?`8b<~SM$dI*gLK%u4-3>rq?7K zC!q=lzQ7dzr}`p!>z#k5eGb9o5w!UAY zx|Q+1@}I`&hP-jSUeAjuFK=E8eE?r$L%}%ny^)JTA}eryx6lyTAe|cm3)ZCa+RPkM zo*$ogujV1s_zs%ZnDrLw);+283uy`LfjM46czi+n!V9~PX@k1GG@La>*hW7!-i+RBQ?!1$e7HoApzsiylq#`ZR_x~>T# zk$uC*g%@c*@>n##-R?X9GLR<+@Hyi-))T-U-P52Ef2^v+0t?7$?0T?bLB%5ndmb3#<)9^L6NgqV+y(H#}Rk{{=q*yOrldpv#$Kp&oPtaU)Fk zx1FS#o)2_DfJZ`nf@M_ zaV+*G^k>kSv`+%^+smBjQ7Lx<$RBX3nlc^0pGCyB*F>*rKr8gHW+~k`U0HL z3CK2loj1=C>PYkq^+@=cgttg&=b*o%{_poaLC}vc%ABjp9lS^TB&=%(j3)PtQ4Tz= zWfSW@%rLLt&VzZPKW)%4i`_?0hP0zYX`6(0?OG9TzeYLe=CFL`!LoIR*QM|{k?<; zYcN;->?TIX2C|QjaZWcxRqPpj?GH+rkPu;>=gYT?7dipHqpU{QA#uAlUv5nu^m{~8 z-h0DU=V7G}TMRCmCdQwlZ4ypQwYc-dusrexd>QURVXqgybKZL?AstmbhxTB-4?EuL zf$hNtMYtFFxkWsVO$_EB>9eo(N>Bx#XWspPlMwMGGF&v~FTx4GfceBm8Q^`hgkF!x zk7HaG@^=;aHjgI#25d+>=K>1h4GG9^ugCu=1sw7Fbo3Lr< z&z~xBG%p}=G{#3S7mZyR12AUD$UWnH_SO#a@eyfUG%tWnVg3()pvc!pH{w?X^EB`^ zojspM+bZm=@@K7zvuwoZvdxo-VfOe#{S4{8OJeF z$N}^<{zfR@tOdf)y6-c*R<(2H-ry~13t?OW?V64QU-xneKah~p1(MeK56Jnm68J6E zxgz({)YNgTq;8k+yo6X6$hl&q{q$YQd%uJ-d|4^xhyy-=AENad=3%O?1K3}MPn7)* z7xLMdLxR7`?ZDr@McOk{$5N^26bbKc#Qi#- zKb?4C)A7aMApxA;CZSCJmExKn@I&O!GB?E7t*SbJ{h;_td44xGj&?{Wdk*QXQfBo2 zWZt?zz6dli7OB#DDt(APXPn7=Qv;j7mUy~si4<)|A0hxh{FQ`geU@g#CxJtv{iMd1l5#oCo9X3(5w@ z${Pp-;EQO!20xm)BF2l1ah1nsD@NX-1v{qiS7m9@y_V93uS`>KER=GicPFyzjUg?J z2ar4b0rt5fd*St&IA<(^yfc4Heaw^6w-Q*(u)`22LLge}f?wbp*0^}LD185ACis^+ z&_VdD%!#s(lzIK4x{*g|5aAyrAg9xHoGk@M@53VJMf8x*-T<9%M<1})K3u14_H4J; zD4%sTd=UL+9($F9W&-P>%Ou!g2oxaze?)V(`p&DqTaO=^o&WBZPm7*o?98|xTaMmg z?Aq#EJIG;U&W|}%OOl(sS-kFSO7vX;^5K1|-l$H&LsqgTSQ8hsax`O9AGpDCQJ zp4ma*^YAzPqdEYd#%5uY(F4r2hx}H=^I<;xk3Gl!;is~H2AhbzXROhQe}UWuvj2Ot z0MqBdh(KG=kzrd~oijg(Z$-O#p1BQl0|EsfnE`SkKm_> zzsj(2oyq|6gzbZdMnK+>yDA;*dHs6{5sm06e55k;UJ+jSPV@&pg4QcB*qkWA4nv?U z1mLIG#=&p!U8lZ#5g+H}GltMHhSaV6jePN&4^=(bM|HJ?zm%XfNB$6gIqLwu&iga| zVcrwJig}ozp1+avD)>V}h8+Wn0Q~i$goy8S{t15Ubp9(V^T-AAft)aY&^j71@`PL= zU;T2hmwU5>Ur0!`2f1aP0~u!SA6?3Lp1Bjo{{;L7=Kk<~xQFo-W2(klXIS12DgP1) z{e+E>Ye%0V0H48cJ0!$>2w%dVx}K$T*YL6Ow9a9}@9;GIj~ry|n3jp$Y?go=_4~10 z8o)ai-m}SL;YkVKmjI4C8{vPIm@(J4Brp$qgM`y093{aHL!fF1z?bkRd zcSDZzAvwRLalTN_hnG2b=N;>B-Lz@rgZG`)c&O7zDx+NaH&h#%uc=N;sn-3YwZl*N zU0#Vg&-ulEvme*A#fCsn2&`S>cl$SD{yM?=`F^=y-BX$NaA^qa@=y5MVYf>Q^y7Yu zKiR}z@vyiz1ortoeuH=WwG_Wzq<_IiO$yZWD|Ej9#tLSUb(|1}C( z@9KX&m1z%`hk(g{d01LWMG~*k~;O{uo*R8`|DWk@jqv2u!%wW%mCmEz%F|bQ|3YO}RXe7;fk&>nG@Wf%g6K>s69OOMCA9Fu-;EdMe$yVxJS zv;W&^EieQOftCo2`daj-H!hXgGyd#IA&{EGN?n=m#Lsv5*ZuxjKYz)Ih938En(AMB zt-SQ3^Crfp#3m%oS~cXihiT_dbN=zfx7h9;{HbVFMLzFznvTZLb^f6 zr;~l2sy&(PA8>B;#gPARJA{Xn;32Q(x@VtF(louN^w&qGUJc6s>X1LvZ%f{feM8na zYX=j6<<=!zqnCv1d1*?Vyqlcd%US2?2|w}pJL5vUA72&Dsct_ls64*QPWl!B?;Z?k z_x_uIkxozf+Vx3}zsvhydACV(hTOJ!e&*l#_{lg>=YfI|!q#yZ7n1k%EP08Q! z%fiNazf9izPma#h_Xck-ioEWWHyDo!Ye>qrGee+i2&|k;ufO?qQ~vATy^+wl&}|c+ zHA&ln z1V-Fw(xMUnIhj#U)(aN7Tju+PK3rl-ZZ7R?^u?g48f$uSy7uHcnQ?z9PDdxtK0AZy zZ;bj^6MEdYhgktk*Ka99U@#CkAiG(1`)6W)A9q{bYI$d`v?I_pl-YVcPt?)ohu3NS ztoMa0dNYx})w_R+>mO$YaQrAuTU-JHqwc3Alww7+i9l_5O(Fko!1teYu{~bwj0bFZ^kg#L92S0CQl`BrgT?_WZt*#_ee2ncV(+&p9A(5Kvck7R3!0R@=^UDNGP}QU z;NK?*cL+N7h;cv^d;jHFqG|U2hiG@2X0NmN6G{2o zNsq76^4aU3-t*gEd;Jf{T+?Nf?vS=e=9-S(IDfe&E$$V8-Llv3lB5v0=g{VfG0j;a zZxf%ZiT;cqcK0QOk32Ue?@QKxs5!KkL(}8uM9IfAeNL2qtT6p>bo=x1^M0d$No0Jh zJ|FJ=CokOlulMv4hkN}gXPPHsdm**EXAg<&^Vc|*i|@~$9n*ur#8~}rdP=WesTcn$ zP1Cr$J)O}aH8SPy4R7&p|J14wC1-bY0d^t zh2M~F-=lw=PCxy$rt9)QIi_ig4S_*M;0-@A>0U^_sdKV>X~-|0+|pNV-cXCjM{doR zr({kj9)I%s`}n7mQ6aw3xq%KP9qLs1zWdmz!&Phj=r03`YMTy+{@nM3V!t!EE~mEt zCyj@t+C?DxHQGaNkKR8|=Ds(&@hUk4BkmPZCf6VLxe59DMqK|!HzvQnw4&m0 zj+|a`0?Q^Z_Rs1CC*N0mO1{m^4j`>i^!GN#D4Le_tL%E#K&5LwYEInV@0>vE!}i}b zEITGr^EcU>z}i*|6wE9ghQ)S)LVc-3R;z7WIvGqy7bR6`49W!;xOTyd`mRgT_@ka+%`E@J1My***RCw8?^NE zKD`6TZ?zpjN$(H9naTeEbfPuE5a<#Clm9M(v&Rg90YJdye*iksnqUZYiGay}m%!O$ zhQI(IVDdizooG!k1iD1P@h=N01z4^o3ad{c^v;AM5Q0cRKAyyWbG#27y_Y|JskzM1LTiw3H!W z2uvpevn>D9*)%KL5HJKSIzz{G53;{#H5HJJ` z0YktLFa!(%L%|yXYX!lobzka!^@nz^N#hm zZrZf*!TZkcuoAspDvN$jZ^#YRhURNZul8p=T08uN-xYuV>OAKc`^|p5KO19IR}O)- zYy9ru{q7TZyL-7`T{$}EC_%gY6TWuX?b7D^algf%Y~rtYSlk-|`{eEJ4c_h7Qv7<6 z{skK~Y4L0$uuEj0zs51{>VKNfi*LB;VC*k&{id73-y65|T*o8DGy7cquaUXl)&ElW zSYq}N=urL-`h!~zt~j_t3HJWZJN2B{(sK=ueKLAX#*ZN{MCT^|M?g}kj85fWWIvSu zO0~s^nQ;V=eUtwghrPTCP5#Tnat@Yc^55s3F7p4~gS+GLJfnaA6~86ZDp@_LA9d@0 zXtk#6akMIu&a%`XBT&=E)#X3opL%}GF3#;+HgU-Bi}`E4+q}$=+4o%)zuAs=_|N(g z?~?Ctj``#KqTu&$rrZJddNM!g8;8=p+%0iOIs0m#;xX{?F87pM<6`;G@`IOOPg^g) zp{BLks~!pcsKsUyJ_qj-@g~!=D`~ z1mg9GRPIb9e!j!M?)S&~`AbeT^muwd{A;h3mwt5K#Q2ohgrsS!hWz$0?c8b3Kb~CY z%+}}9c=S)IF8#H*zmfe5-<0le=f=N~Zqf1SWS^&MPbT{ZoEv>H$g8>T z*=Lh9P46lF^^vJpgYv&Rl!31Erb;;IXJ4~7q{JG>_&N@#|_=(5g z85iRH_^NPDb^B>Sn0HiWJ8!+z1!@`;UMo}C*4hQQoFV9(B%{Zo9gCq9PUJhvcD*@+=w2n;R) zE9G6C5#9)E97oH3@MD}!4=5fM8v+A{z=#`7S~TK6Co}5Fdch)h%Y47khf7S!&83}< zz8Dl$V@)qk*PdJ_Gwv_N>FDIyXJ;_|jZyzWQs|jk?c`6kf9n`3T4!;P3)_fWvgJ=j{1Sm$7PK)X)a;HFt~8sqI_O zZWQ^A2u)kuGXf*B!nZ?9nUuZh9GZ4AyT5SY-zNxn2s-zOeB-X8qbA?L3XbEQ=O=}t zgT4Rk0oF8o|K(VsY4-kyXm^@sue0|PN%`AJkFV15+3TO)^V?s0{SU}o(`A$HkhVwW znvUH#f4L?t?iGREve)mDq!760(B_FT%~>ID6Q8Sz{)``X_a%gnJU1opOV)m>-hT{u;+}@%{O;V|oyn7_0wHPwCYw_2OTpX&QI8r!!ilMyA}o;Vu5{pBfn7J8aji zogX3p+nx^fwR2B@C7u4{yV7arwx6Ep_j)@%Gi%MMdc|{1{;S>}bDq9a(+HfJO=o_q z^~N8k@1M%P4|b3D-|+4S$z2!9Xw7d=(wq&P3cn%UzDNH!oqqahP1ogra!k_}8v=uj zz#D#K(!G#;Q|Dy&(vV*~xuvh#yrCA4kKCFsPsyB6JpSbM_wi3Bqe6V6a|0bpI@GE1 z?ftP+hpX25(O(7>)ixat{kiW6#eQdST~2NPPZ|$PwTnRXYqW>l9=(5_%zbZk<5hA9 zM%*jzu+sN4KQ-=8-Wk)kbtsW_y{E6@HEC+6d}{As^u4pShZ22_{_?AzyhxuSY5RW^ z4U0QQ;0ZSpKI4z_OAn8|G28w?O=;R7tDy(B_;>>Jl);#^T8 z9$zn!AG0`aCf6VLxe59DMqK|!HzvQnw4&m0j+|a`0?Q^Z_Rs1CC*N0mO1{m^4j`>i z^!GN#D4Le_tL%E#K&5LwYEInV@0>vE!}i}bEITGr^EcU>z}i*|6wE9ghQ)S)LVc-3R;z7WIvGqy7bR6`49W! z;xOTyd`mRgT_@ka+%`E@J1My***RCw8?^NEKD`6TZ?zpjN$(H9naTeEbfPt3pb@CK zho-H4CjZk0trctt^o#)SuN;&)y2df&=F9h}O8oh8lYfQQVnd*E2(0uE`IqGv=9RE9 zx7oeqe0j49CjSbp#fCsN5D4Z|_xr>CwcvY{et};+zO=;8E13LOqtA^BLm&qMzD3KQ zURosIqUEo&!{om-D6Jqvplk$8{>w(x$}1E z|Aap%`$u1h$2WetU*V7S_Jcdq%T`u;pCB;k@?ZN=x+6Z2PFl(kn9~Rhy8O@S&b7uH z0xb|Q`ELP`T{Z*;7=am(|3l9N?{ALD*MR(dU*ZPC0qz`YnjtXF2u!#9ACTWx*yGns zX)|Q@ZG~Twd~ZwhEj9!Uftg2Oy5--Um2OkzK8=|s&r*iKoI+sQ<$q40X^k}m`U?S* z|NiPPqt*~869JR|GSRfM41xYaz~sNb`pc*_1jEiwcQ z0YktLFa!(%L%{j zan7$v4=;1>&O6rMx@ptK2k$$l%VtYu(TDGc+)!<3zNWe?tRjz%)($`6cg5eoI?wsV zezPC12z5qAzay}Ajo%%--+cmacQ5y=`@LC%LjNxRgs&ZTyR=N1MuW^jK`k$sPHUxS?V4ti1H40hp>VG|zX%Cl& zfXRP(SXxO(B4F}=BqmkLG5N1jZwwwSCjZs$Qw(yUok|n0?>1D*d;^f0keOOuoN4=8yA>?Dub~(m(x9 z#~%Mv;jqzI{^i}#`F?17zc*`e=$~+}%k2MC+8{r)Q@+5xLX#F70=*$HKrKBU-}AH8#6nrjU)1Pp;<1V()= z`qMj>%Iq0`cBBwU&0(dkOn2hvJN)Z@f2^OshYu>`AL|A?5|D9-|@@B#(BR?-uzFF&eQh> zZ!e0x?vytej|yu@%C<8@plS%LoJ_C3`E^tN>)ySQ(7DiU6Q4Cn+nFI?2n;L&E7L9W zTlV@j-USwPfjdPsf*po{AutCJc*Si9Tj_`WqN(K*8^b(1Hv|lUxq-l*oiF>R_+n3d z47qu3L7cJ^L%lqqgv9*$;ktx3Lr8pg(Jp1enroS=jUrp$7-yUWKFkOGO zQbX<+7gUQPqo!XFI3T-OcKc^yejj&R-D-Jfue2l3HI&(UJx|op=7-m5{jB$eD|$1L zzSX;bit8U|1#tW*O}CB@%I@uO|27l{`b~BKl$qXNFD~xCZ6{{SzkAs4ygRiaD3xg4 zpz`k+bw4e?P%E(rf!gqzLjK)=??35cd%V~kWxK}$AUj?8Ke(d)7ZfqAr~CacE(mGd z#*ZRY`PeJphMr8uo5R9yOUkr2Rc`w%(Du~gUCZ~bp4eJg^?h!n@S0u7M?m%fhZoob ztm&DSf33G);tk?!?iQa@>0Qrm6#0z^O9KJwg@yf0b%q2@%= zDE~A)eomD0n5NH((vKCUAC7K+K7QVB6bT>qTlM*H?>~9r-ha|&|3RV2aY$sJzs9j# ze1HDzm>vWs#_E65Q+oAEz4%vYn#SGj>5LYsktug?c#D7gr^4ZNcq#XM=H>q!xBolI z`-l)V-}#TGiR-8d=|{^>9U=eQo<`*+)ja)`bo!I;N~fLMetLL6PkttO+|E)6%r*H> zHGUQnGdugw`-B|(;{i^T+`cLn_?J%iUpgs;!z|DKpPC}ZotjOboxXnKkJIU??E7H% zc>fLWevsUCp^Vo2_9V?&3`aVCoj^cNcPwM zAEn&lN+9rr8wu&~NBO0P$KIH2f1su`ZIIQ_16%xZO`js`b9Ph*fmjh&ha{t<2LyJH z^9x8TeNx1*yzLe*?M$xo$PiNP&1}a_iQFG$C}>rRk^Mma>(W2R zi^GI-@-5L|cb%*+Z<`#e1q;Z@&bj(?gO+~Yr*{DPt)1=wO5K2En$f41tOvVDewF4l!~Jff+==K2En$f z41tOvVDewF4l!~Jff+=g0`kAo?}_)@u=~e;k8cM1->RD12+XqlPsq2S_l4h&TQ1*= zJJ#C|?#ykjwARlQ0<$dt?nmi{ejuH+lp$aUOeX@fE&tQmHY?i@Fa(+-VDjG_AG>M@ z3^D=}?$9&A`Ad)cn-0eQ z0@rW4Dg3>0OV4#YQarQI)&Clq>s|dXb&n-x4}lKl|DZp(<=~2gE0kdG@4Qpbi7h?X z@YpA#$7K8%@qopxzP~xcz{k7XQ*MomT$ok*VcD-!zY$Grc zN%vDqh9BA~U*L|X_ndns>}{Ffdr8&@$0qIj+zQwnj}MD&7a1Q{7?!xB)7GGFKX&VN zbi-1)$Bv315a@C!TIv?o_c#@hxBr+u^IslXQ*hO?=NbYJZ5bVN$BGMBbYMS%L-*?i zS$1j&7y^cXAz%m?0!Jcn_V?vCij(Z2=ogZbw4E6OhQPoeP)p|4XcO~mlYVb3|1vkb z*dM)fV47pVT-Cmw%iT!{DMtHL?e?WYBm$9LID-y-1MgCXtS zfAcTW=_y~kKFRTSdH*Z#Hfhd~+cwYdoJw)sJh!cXnB-w?Nj;wQBR>gqko~nO`8$4D z*f{T(1)GtOw@%ad25&FQ^E>4Y#-qX-LTc>X5U3UcD<{+IZ+_jB|GIZ?BvdYx9Xoze zlD0ELzz`T%1XiY7=C|ziYrG3A=mK|&XaqY90YhL8An=OY5Vq0}`$bdBCpLz8c5Vn5 z0&@d_Jv(3aPw~Z`_!x5Y+=4h|Cx(C_Ft`Y;ly`MTcq6QF94-67k8w6Vpm+B@c%9bEdSAGLHxubwz5A!Q{&7|S$BW;0(i`JgzL=NVzYWEKev5W^mY3dNFFx+S zZ70?JL4oy3m-CsEH?-PVO1f6?CzH!&lQIl_A1;_Eu^OHi+!QOxN0Bc&lkHc|toL=GO zZ|3a3*4r=f0hw#MY|jL(QSR9GV_KCrUo1>2sp=V}x7Lz?tQ|2Um~`e{wq z<$rQa(-sdF0uM~{PK|y;>kU6L>0U^_sdKV>X~-|0+|pO=*iehdM{doRr{w+jc>Kxh z@8h3NIvss~ZlFU+hdNchy+3y9EUWw@u^;_qkXPGuIP~YfClvdg!F4&c{Xc0uEY&Uo z(XY`Sa(ndtc{2CC(T!KhAsBJ5xWh`{&-~Q5KY3?N-`1f-*7csgir1v6o${%@f06&X z_E4g)(O-V`lNaezB>QXsk5X=NB@lSRjf8agqx{mtV{gp1KTuPeHppt|fh|6s06j(4 z=j^Bs0x{Me-EOZIFLM>c?76k0uMqfN zn%b+=$-z&Z->*%SFYB@2H6C9tksq@-ZYI|s_qhrA`bJ#;MmHwEzO%;cnH7q+OQu8WBs8E;Fw*FNWe826`p$e%bFVxzXz8=%>(rq*d;fT9!1|F8 z3mbsIZUpXjqbd6(3b~7I%b9AL0 zCjX^DX$2VqWh1cCKjdGQUzpc&$J}Q3lJn)wDwzB$v=$oz)j%MaPu=ei``3c+QThdb z@%YmC^9sGwYx3VahE{?hFii;XEn5Dx_(k$9TK-BqO#VxQ(h4#J%0|HCzidRUJVT&= z5SR<{zcQHLVE2#x9$)|Pr%^h+2n@RXPsq2S_xl&(@r_?D--|oe+YjzcFI!pZeS*NC z%YW@h>5lk7I%z3GU```2=<+|OJJ%X-2(&=J< zeTf?k2e@;rX@`R}j(GHMNh zG7+fDfAW3q`rCht%dEo6G6W2PDj_i9MwFb_<@dqE@6B#^@&$lnlHXs~`z$sD41vKz z;IRL5?}Fa|_4C#Z-miBKUbk6`3;{#H5HJJ`0YktLFa!(%L%YOhlhcXR4`3Shn7qf zLdzr(YDyE8jzX)_QB$>5lc-Zot2UZeZPlucGSyURltiskWvWC?m1t0q@2t<^{toNz z|K9uTea=4T>~rpKOY81^_t}rNe(Se>-`{Jk{rHP6y6DN5JoBQHlS>}`fO7o6QXD_1 z?5`;0PhIrr3=rQB1>%Sw4%DY+#Dk^)JAO)2p3@__Fu<=v(HFQxo^DgS#ZKU>O= zmh$FOo>IyKO35uLkQA7f0uL;YdqpWF~@{Up-T1sw7fuz7{3Ou)5Lht^= zQihJTb*}sM^8C9>Nj<#~A-AN!Iivs^gRTCLN?AvS487-1%JcalUs6iO2nrE$OA1sJ zpnKn2%6}|n<(Rtk(o0Vs|Mzaq`S(KJ(#jR_#VcNM^4;J4-6t=5*~?Cz_q^wwTzTb{Cl7hZLsmZL^j-g@?Eh3L@&$5B3Y=F8 zTwEId-KG4kQuxYx$Il}l`N)%Nuf29L?%wvcx1GHG?QdUMzV~~-_v8gHc)>y+xa5*c zdY@n3Pe=c3DVLU#TT&n?aGVsltu(9GxA=euJmBQ9kA3XPbD#U%lh?lXwI@U6&Ff$P z`o%r&^^EeKRpph|fZtu-eyhxZ*KGV3zVL-7-}imrx3Rpr{`%`r zzVkc3^W-s)c}(m38ugIhBZ=!aejlE1^4s@+ z|MxG(`J*5G=&ri_jbrP8Y)?|)oKwJD;S0w5!yfjqljl6=IVW#^^P4xu{4SaFhBv%n z5jz+dljIY4dnxIMJm(C11kD%Mzps>)wmzR3-5WM{jiYUxf7PpA)up4~eFXGBv?kCK z$-OA@rXST&tO4<)j&f}|-&USo&db5-D%W+~=ULa3()B*MBVG06+l}_v=eBzan7e-_ z#wcF@q8GjB(1njcfI`OFJHtXFTVKV3v`iE$NC(fz>k;mv{G_Q3LFvzUQjCg zH%mD)=F0yye=lfJboavQl8%lS_eh-&qQb zH2?AqrCeXit*7O#vX`s*(Ng~OY59q=|57PGQ_3%#mcL*2zj9jsW!eAR(_%jL?^Z?L zMU(dD<^TV$q63=zS}8#%c3{x&@0R1B>ra(qw1y2q|BsYoee!`)qMzPWj`f{B)R)gI zMc?XUeXY;+Js!Xd$GSbDH0-~YvgE0UKJ=j{Z+XjG_9}C{Uwq&Zk9fqgUUeNC$NyLP z|N2toOy!mom@5V3^ZkobdU+bwzT_n@*{SZmN*6F5TXJ}Rv$W+QdnPvdOop*B$!#+H zyGnUQDdhVtrQBIc;PFqEe4>>1mh$FOURDbI;R&UP`#hkOO}Bf>1I~;eHg`Sd=XA^l zF`rQ0bFDd3as!6 zh!H)fivW2(DX=99&^zhvt@CK{EIB&GyuDqHIq#ASKxZj!s|&yAlS`>L{>?vMir?yM z58@aWm3YTD;ZvCh@l7~ZaPyn`tzhAwz(%?dd&wFK)>%@>CH_kDqOGr>m@lOi9q;~| zZo28Dl>2YH?Y0x;J@0wX$(?uJxhx<0(1%Wb;0JzSQ9k_P51)ME6Q4NOC1_!P_uY3d z>4auM!*{>?-DtWfWghGOXw6m(`txVkYhdXoeWvf^w(HB%r;EPT$NE~IpXw&x(6|{Lg2STT)=#6rflC z%Tg}jN4|4&_so6W^*EXGea2jiO#Jty%?~^!uAHhrXrsZ@=Sn^9xAu{F#N;-7e)`j& zzE~H5d@ni7_m`aF`%CQNet1xD;eHqq82sM%zITBs_uO;O0(0-X@4j=5x945o=xaaT zpoicU`Ul>jgWx4Rg}3mSIGH?lyoU$B<2$}%!IO9skDAMaXZdrc9uFPQlhgA4Qer#f zlO&huM^nm(T|djbDC?b?A6**RR{m=_|Arty?vnx&Q{dwAypNaC>dQ9n$%b8zi*3En z8lR=D&GLnE?&g;B&Fe1spFf+Q*!n}yc*ZjpwzaiHty}!M*S&5z|M*?+de_MvcieHp zPEM>o-@bu9tUi?AKK}8KpWJ)zy$hb^$1q;`F8BuVzBzbe-o}I+IAg?|{FXeN3&vE- zIQ(omcVqLsHN>_hCwBc@K8)ha>7&ojmG9Ph;x2-o!AOlLXS7@`9pA3qzJ6oe`a|_PU~(44NV}e49brBXvT81JGwYGM#(CiZq1b>&&6W^K|6z`mXO*9@YAtKx5Xmd;?0`_X3=|`R1Dw_s@C$-g&{=xwccR ztV;)g^=xtDUwpD@N5D;f1hR!35??p&*&rb|;I$I}`&|iN0UL+S z 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 z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## zAOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;| zfB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U< z00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa z0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV= z5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz z00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_< z0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$## 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 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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≪zV?fJG;?kd ze+HX9^p*6J+2|koL>J&~BiIM3sCAzn}+;IB;S#b{?U~}+etLZHE0&vWldBP|CA2{|2y+gzJu%|NwE3`My&NzxR>;4}||`Oi4?=a-dKejUH(UJK>?>>MXJU?w4S}Rxk?dyA(doV|aANc&|Kku^} z^izIa5c)8D7?dM`O)h&obbXoQF|^N`2Jou{j|Bh2`&ma7?*r%;yxm4p*_%!N*GKwq z`1wC=gt;y48+%n`NItOUnf`z_mP_Z?S9&h?dF0~A2$7?q`}cwV!22MgFJoR_j(`lo z%Ndg1$6N^cM4S$>XNL9*EgP}`WzZAM4P+BmX0K zKb%3LO#hGpWZUPSH9npXN52O>Ci!UfWnrv`Zhe2@k4gGHWPbDox%vU>41NPS4SK_D zfi=s>M3DhBOTZoz8rFy(uVTYkeISr`H#%O>4El0xYT|Yz$lalTcx~T)`}>HT51Tji zj_@o5_yF=nz{b_{0l|eb{DXFoCnl}8!S@+_H+0PSO<#}=BXpad-itZX4`j2AgpXj} z&s+^3Y1slkVZ2>uwtE`w`plU#J^fE9miYDlVbFiBKDCkXV(!fPe&ks`TyxDe!MKVp zK3kiv_H}e%jQ{7Jd#=x>eCR_T3Uq)`%b9VneY0A0D_F0_*(H4MSg^ zuZEtGF`;uu4uH-H8zJO@^b^^}q#tgv@*azlN@VCqZphsj?&?NG9#$oyk=DkVnzLaa! z8GJPJd1#)#2ALng)e*|@L^7W;QIuh_i`W6tuw}E`W)dq$nuauCY2fc z>0)U;+x?mT7Wf{RdHF|XnZYEN{xb9aMoKeRgnl({)kDw49`5J)pa1;ljRZ^9vM^S_ z12E_D>jIejqGLj?mDKi-XE8oA?@bz;lIS7ZHTo6!4CG^(_rPl~#*#PhgBaU9WbKUm z$SEJ(%oa01am58_beqd)eqa-*w)VX*&#mfjvNGpg(7QcQeWSJO;F7 z#tP_H9r;9x@#^=(R!}~KA zL*7Q9e(5JzqnK?>u2h||HiAAN?g#W&zxq|L@gKWC{COgq%SYFR{?0lF+I1XX#((Sw z;sIG7e1Eg~{PGyUvqQTYU(i@;_^3XZCTsHVZ{#C%Puo5F?6bpSVh=!D#*RJS_TuNz zFYP#ft&nr(`pjQ5$2I>f`bWOa8han0d*&VRDcN-M{@zL);RBGVd3ghCkm#R$o;gw; z^c2Vj3#IRT_b~?|#=pb-A1ny)K=?jF57Ml(4-P;1$xl4}-%~2_>vMZR{~lZ3+bjV) z5$50M0DNGbEp*7dD&GG5Ir6DecmUdOwtj_mEqpWX{}+89atHdPq`A1yZ>5jO1CR;A zXTxJ76Ukf)$($%3O&9Zfl7AIsvdBTO;e;1h)&~Q_h-j(%$N%MW?jOp?7%F*F~)H*>!faAH2d9treC#rh`?fo-r8+8(s4u`m1o z(r+x`J=t`t4F+4U;;`2h@;!V4dU|923(YbYMIIHWb7WxI`1tsJ@pEK)aa|(zbMzyu zY4?6S8PD)X4nOVJ`H)#;fkdyLM|TNdUPweP4(%3lFZVE?#s+}szw0r97kK>f$AhsV zbG;w^B)n0aj!QXDvMXe6g#9n`-DZ^Y^=hzsfwsbY61vqmT4P_r=k;O1mr%0?#uVBx zGP<~q4NSc4ndeK9-zT-xIK5|{d;kB?4f6;1i}TMvKWxwRLCCn^bD4W!lR%x+Qe+IK z9M}TU2Qn_l%ZZG>FMR;`X7XF$#drO>=Voo6ndN4z?r9r;)MH;m;0J(tU9$()Q_-*D zd)}`TV%)>d5*q=2BQl?lxBXn_=*{uv0}p^*&*`V19{Q+77jfNn*9CNit$|i#Qv8=* ze?{Xn8sXJ`q4A*tlwhvd|j=)=?||!~-;zBiyAg zksEKkvG>QC^a}bV{O-c@VrPvFte3?z|H5W~y~gp;<82DLk3AWX&9bi`e5;qMGLL3I zan`V?Tw#OC{BKgsw=GBI_0v1(qwYeEkz~&VEyK?;`alk6+~ z@9+!oObSXE;iay+>Z-E&I%$MveDTE>dwi}gV*{cec^&ML12=O(_RcuYTDMqRT(9T1 zX?&l?gEW3z<2;SbtDo2ShDLw?T0MVJ<0Bex*2vxrtj~aVZq&e>;8LAKZa}_?JxR9l z-P1cqcmeziAscCFX?aQCak9pJH8QV7_7Gr7|BHN(J~wWY*lg_-9uoO;4=!)jV>!bt zeVH`6Kft6m!`2M&n=U*N~C) zl=Cn>Rx+HeFAwHv*uK$kmEn_zypDux$2QUP8`3h(d793(OPQzo7h@ks&S3R zpuNB+!Y|gEoD1J(F&_3;n&18$!{&k){pQU#-`wwA06sCXfsENfQJ2j3uo+|iXTcE2 zg}_k#5&O-*X$<-SWW(5edf$uv*~qaQkF{;=e1>c6?-1AqY9{1kANyFiezjUSq5ojN zPHaG9@;vH}zVSGX{n?z&~uY|I{uw)wL9zxaCck=F zzsXvNS`P3{*kb=hV}K9yC)N$o7qD*LuX)df2{I1++v5M`_~VZccnbf%p5q2RLi5<^ zY^KqIAy7>O7-!z8@oJ5)Y4m+TuwDc|CiDmQ-+zDL!?BP)0Y7itf99EI#^?CF7oK*y z##-X{H9I*2{Q&C_eyTCVhI9Peu^t@1H>~qK;e-={F&*CyT+di<6Mq(c?PVGd*VwAj zf+0`~2+-E|)Hp-qVvX>l@pc>RjlkMx{JA5iVoammbB-Sae92}$qiN$+~y7}{Luc~Y7EHTE84e4nXtf=1?G77T%UKwz+DavzPz3~2Mn>3j7Fdd;_W zfBd&zyGP@x8rwBCYQQ(tmKxup5u6U_Iqs@QbaXbhD+m@E3IW#SF%GtCe49qvJo<&* zG-7*D?_<0c!Ga-R2p9r}fFWQA7y^cXAz%m?0)~JgU_I=qd-Y#STgxDyD)ue)cK;N&(yj5Cp1P$3!Ka-YLy1}l~or$hu+bf*4FeB2(ZGX#wO>#V{m zoL!^;3IkkYuQ&Ryu?p$u>=^y`a|KlS^G5$w2E4{UXY^m=71Y=4jd5KL8DLLXKRYD> z^yx8ff*a+A`J^2i9f2|K4fle|0DHo!&dm0(*iBcNu!ROeV5yto#z$odYI|{ahrco? zr4jW>J2pB36XVin&f~ByWWDFT-Us8M35difaWi1Pp=f2#lAu;LqPN(rp&}`LrDk0Yjj1 z5rF#J_0N|O>L0Fu77PJHpz#ra_T%&q?Hm0!e!W`>4FM1^`mgzR@a$uC=II?1=e+ zn;@={_P=FKuT&2^Jt25r7U0S#2%7G^Xn;>Sx1iO!f(VVqKZEw;^q-=BqkmuX6JK>R z{W(XGP13$7x{{)&Nw4}Vq$g_+-~(xY&M_%HN8Bs^9$74NWuGN6iuAvfc$`?`bM{zM zAYk-g-fG^Wysxvj83GkWpf~*wRrz1Yepd9`_HskO5a?$F*6I4oSdgJId!5Wk<5`EO z$4v`{fFaNX2&{8+;-!=!GJCz~3^f*psD*HIh0QBn+}H|uKDAq=zi00^1PVi7eMWyU zSf*$82ZN8C`+Q+9tC71-o!AxSr0u4zn2c`xvTj6lVShKl%|0rgcJ8RMrrS0>k@h|1 zzb4B9SrFyV&J2MXLSTbi=>Fl-^-(c;X;p7c(_Lq%pQ@ppr?S$Po}-((|JbI$s#K zY5ugBy$QdSPn{Kg$<7P`L!jmmI5#~}~V^7(RAt2-GYBe@v4WAV1hBZFfi64TO6#{$KtI z>8-~^PR&T4D{fu*d#5Z>8NgtlwqrdYFr*$b8RfA}Wq4_!cExpR*=&MDubP-NMOMk$ zkU!Ycbq^f-#G&^*Bc!i|GBBpe?8!hj-@g! z4X^Fo^4j%ZyxMio(H7+^O}Cm>>72>uJRY@nT&s1Z)>xrNLP>MBwC)~eQzTsjQ9}9^ zw=^0LpnX{o(YL0~2^S0LSC?;NZ6yR>PufKHFIlZSR`dj|ognzMbKAQm{p(L-ntL(Q z*QIvTj{5h3>P>&3?y+tm4S|?Oj_Vc2r#_JVpzxvd!XuX~re^X%atqj`fDXhR(;Nt=p~f`EF7FS{<&_Co6wksocprwK3@l zOLT2dhug;|oBpUP`P<_lh%6E}DY|mam4nmv8@lq!=r_*#ntqDbaZ2<~nJ+t@*-Hoz z{cdMYBWIenm?$PiejGFv0eXp+4ZRz=yI|A`XSiUWt zoSj~t0dDz0OCp7b2ck}oWv73g-MbAgT+cSwP1f1{xH?70vYm;*i_*R+zGI8*wWK93 z9@u4<^e1XQyAy?;QLL92!|`9`>*Hi(WDi_@1ni53 zK=UF{ZTfF}C>`rP)037m1Pp;HB2aDmuVT^cQ-**ckOu*y|2zQM&xSzbArN}VpCb#U zD`wv|&<$3L{m#ZKv6aXWFa(_1=FV~L5fFi~r@S1=;|9opC2oi420JqZ41tD4pzSRU zTQn<}Az%m?0)~JgUL>k-(>qQ%@4V@=X6{!aRQj>N_jrqIX=@qeQzgEvZXTPWyXS@bb5Gqe zwL&k-{UF^J0&-|9Y#j%I4STzq0t&+S?1E|2yT*(0>p4hewQ;!_swz$7mTHufO=Y(SIC` z#y`(T|ImJ%{zV2j{(MMp`ScC`m(1WPm+k{cJ|> zh)Tz_O?Y9{ykQgl?+>JY4yk%e-EVt|3&n;0HapfT0%LSXv^OJtkM4#N1fSiOF{9rY z{zB_KpI+6O{`(lWSZjNn|Jjc9j=)kk!;Oy$5!CkL?hb!tP)Z}}lRBPv$r@Q1(*Hhg zq#J7c>4x~!{K-o9nCg4leLl6mm&k0{$ERB+#-;zMxm}m6?exEobVIDRJs1sm+%RtS z$dyADs_jE^SlT&be*0v%Q}VBNwcZfeG;00OHwM0tHQTG^FP%JN-1yD1-e5m86arK7 zmBx$#@*z}Lzfa4TTdVJU2-)w3fFWQA7y^bsDG2QMfV>&XQ?Njv?qpCYFS2J00Yjk1 z5oqfzU)v;ag!C`NWiuJFq{a(j*)s$TfpQ=)UfO~`|E`g`zJ@;whJYc^_y|D#?fU0S z3hfWqKMRI{A<*~;K>Km}hxU#B8^7MIgoXeJ82#6LJ9zdnSsn6c&Ry(OMibgYrE>rJ z>*=LPOF!UNzTO@EKGw-ak$o-K2e{6oqu=eUnGk^f$LpUj^KdsT`15HyCLqv0b5rc< zq(y4P7^M$!6I^F5demFf=<~^G(j9yLjuzQT=KA-oQ)}0y()OlL2llZe<_B(qxJKIl zmNmUnJ?!*^;CWeqE2AK2y7QuF`HWl8YF&ZHrWuaVew_YO)IY2lie69|C%)=t#%~na zB<+i$D=B)K^s2u?db0KaK9Kh39Fx*>#J%G0k;O7s_E{36NdHTT$B89AXOA@n0!IJk zt>!Jt`#O7@Ay82Sdei?7vv)7BxP-9_8)A?K2JXZROoW@qb^Zwl`@)3KfAy6~|>ofX;!7@Fw zKNx)E+~cp<-^KCbE#bk8jmvtke3;VkXZuU{}v~x$5HQl!9iL~!2|20__ z$bu+;c4i3F5CR+ALiZ2#;L{U=#v7xTRyRtygU(PtodKfg$yf$taI)D#J?)wJWYm%VrZKdey|FDY8n| zhWx>nu5+Lq7wIQ}1dT)-nxfL<-O{7}6n}ofSo);+`_37Vx!&mD_|Hv!{_6p6bS#x= zX?ShtmS^=}-0xtmcHMKdMfrT_R?}(wr}*o3tPKR#YF(){R;ZCs(wr@=yN8(*N!LJ> zkbcE2jm86LUlv64t*LXu#X|bk<=gaGmfNNp<#0Vxsvy03y|8?>e$pnof5~e1g3qVc zP7r+Bx$WJO{`IFZ&Ak}u>r%UENB#Ri^`<}2nY+KJzJjxuMvipF@o5jf8vbkGRRn9a zEy~1j{28RHO-IGnON9{B%gMY<{c)#kyHAUCgWVzfbIJei-2UZFZ@4k|qJKa3*Rnj5 z-}b-O|8b7Ipd8J*5YO3ex$fX4kwB#WiTsQD_f#~oQn{0L zYGcw9mgw4^4!4g_HvLgo^0&u95LqN{Qgr2-D+j0TH+1Ed(QlmfHT@K=oukiUiG>0v4T7)bZlhyBZJH=>+xFFjxO zWa$XBkB|Es()LnD{~)F3s-!2=so!~TlW9Cci(4ECI7|9U?LIU|X`erz!<7835A@%r zvWBIRJXQD89VP>o``6pC&JlP~{eP{E@6WJTAT9{k2ko*8>F<5d7qjlt`=#eUD4qWE z<+*sEumg0gK_Am1(3kK~Kvl=>&s-}?Oav|4KU z<><6@<0r09FXGjWB>{{TF2(;h@BF{)^uJ1Vdodm4fwHG@h*wV-&kix|EV6p*J_{V|Ncci$4!&h zEMFS4ui$d4+bQF&+DV?bF=Ic%ad)SU{bRrY9|B2vijTqyOsCe^o2jK4u8y zLBQzW=s!;rv7Z|lfhMK@DZWbQ$U^Cg*|!aJgVkcc)BkKoL!hA%Xgd0zr{2!mqbd!I zJ>}&{9ydS+EO9$TH`tjW(7*^Z9sOt2W!qaaE?CkKXd(ofo&KAss;#JoKraN0{(C`U z7Yu=>LBQz0X{y=^Y6$c~!05jhBzD0NXc`1k^gl6_f2aI^Zq_vAw}Ki1O@}~xqybwk z!FCHF{prtAnQCNYS5rK{Jb8-7cC6|FGyGHhR5|z}>3;{#H5HJJ`0YktL zFa!(%L%XxY$dOq#kAl*%C za+I{QMn|Ac_fuKn3pxJ4jX{I(X{YaUn*?B4oC!05kz z>#h-k_ORGZSDCPd20>t{o8iVsWeI9~ad(Hm zGRRgZc_^CWI595$*ZsIPbjjLK{y)+UvE7p!xFu{Dw|eBtAq&0PMFW@AidF~$tL87A zJY(E=b5KzTSPgJb=%~K|%3?(*0|I9JQ3eq06^4KzUc=OvFDbN-PiPBVcROn)1fc)%`sd3$Ty3rSvycOU z_L-YvzfM}DHi}XD5VctE%teoSYZ`q%IZgNEh@QWrMRt<8KD|!X$Wm#0)29Rb*b(zX zm;I#uZyXcTE7ikJPY9lu1-LQ_f~Gq!8sJmTEoil_AVQ<@&!GJ{{imqk=-=1;#8=%+ zf6h^4le902uB7N`(yRUo>B-sy_(0m9b4*Il5%-F}M;6Om*=I?NBKc%9?K5^aQUD`&Z?^ zCd&d@5arL#41pR#V1rxe{^8Q~QL*;hs@|HWyUtKQRYN&XWu+}WM>loHz0V+@UncI< z=$-|$Cb+|*q?meA9_rk6>3x0@QhQ#xe$K~d7q41wB0AcIrgC=)liDNz=6zA$dn z{An?J6Mie7IxG5;of!g#K+Pd=ZhDS}oBy_SKrCel7y^xkK$?wE`=U+ZeBQZ9ug2&1 zExMakt9$g37y#F|cfS_9|LPXm+vfioJj2Gt%dZTNnP`DN9raFxaQkbcE2 zjm86LUlv64t*LXu#X|bk<=a?W3BlKsHqre{R_l%xJwaQplKU_BaS4i^NTeu3U5F;I#dQuDmk(jkCU{pQ3e~61`LA%Z_LE5&}fO z+gWb}rVUyc>!11k#M(h{VFrINSVnbCUOm5eDav?dp3;0zcu_VkSA9|_j~HS4cn^&o z>j8lioCJS8c|G9-*rn-b_^kgq;GUid>0h-cgeCs>J)B%8*X`kxdhFlX?uyQZGB0gC z4f3~;JY)GR@idU`tq=Q`*=|HR-(F&-?6E!~@ND|?7pEs1IhCURkiJK|)SoZ%LYbB( zoT;Ia-)AxHI7expKYuYLf9nJNx2ddQY0SUVI2k4bmiyP+vCa{|_a8pMd@Ux-Cj%wxxh(_w93e; z7AoJzX2frp`liUl&>r^EO(_P?-yx&b17=$NJ@QB+r?LHymk-;2Q5#^7N7En0g|a@d z5IG|w{fsQUzdeLWu{?XrFs1fN2iKOpQrYQ0R#r|oOzH|84M3uk%JqRhs@?mskrU@STF<( zf$Aerar)0Fl3GTj{(7#D%Fv%=+-^xjz!0b)0@bGf9_kOr|9aP-RIqC7)rLUD5$F&4 zpAy?Y4*H+#WN5_;Y9BBJ41rzMn?9)y)tMQ3;{!+VhB{5 z{@Wf(FUp?jNlO_5hCme&s5bpqv1s-wL%ifFaPZ2(-PWVT)!3 zGXx9)L%gKU2x_e%@KljuvQ!DhM z+z-;-v?fPMJ8N_V+H^ma6~2(;58N0u2%mPkfBS4(uN}cr?`Lb>DmN`EM2h;?P4`zi zl=@qJ!j6rPzzU`Qwy2o2hmEo#bkNYL-cxP0c7#u(ovrOi{WV&-tURS5u%a{dSK8C| zRJ|c!^j~i^R@wX-{Z|&>T6=pT^na(^8T#)b|L}pX1MWDA%fao+}+`?3`%K4eNxBsE?FZhL;ByxjdVk8 zKiv?Ynm<|T9#ef!yU(ZA_Y#>c`}lOr#JKc7HMi@MwVnR=k#309wg;mDj~m9V9=USJ zLbZKJ4of>{%x|CUc1r%$uGSj@n?|i4`o_Q)vSxeL{H2p;j2pjM)*I}HhC*OUzS5X6 zKt6=(>i22+a%=US4e?7ev zY3T>t%GbN2-^V(+D6+5R`T*B?bo9HOH4_5R|9JiLWghN^1%Ezm#{>l0XKspJowP`e z7^CzdZi4H~MUQ%G8ht)FO}b;x-_asF$z1=wb!zRpRNCJ3>A*gA#QeZb5Z6fi-?FAx zs)wDP5IipnaAgz(O?O@tEuV1=j^ejK)~p~yw$u# zd0%I5GXyG%KyUgVs`9^({jBJ>?d67mA<)kVtkdZb_Bxr7#6w9(;O2(0F6?(&|PjchDK?r~G|2b8Jh`$xYpH?=#5fmx((yx@W1NG}CG@nf>1ER1?WRWWlnxnwP?WSYL!h=0$lz2r%EV1>N|Z&NFO1tXe_G7m zgx|`i&WgTdXNG_wP;&^Jo1UZL8UEYSf3=h$UuEq4FaEwZ=G|6};rsUc9a2>dZkT7djuqqN-}Wj7@s8UHVTh4j{A zBBy4g&lR^W{Jm5Cp?1h%pSEK?ATXpJG8yHuO=Wm#p?1Y}Y1wRoM6a5dG(}d)+K@lk z(sd4$<0Ab8kf4!>LsL|GyjyzIpW@Fi7)zfNf8RL+GS?d&9RIng&woAOjgF-H^o9|R7Hrc1EeH<_C&$v5d{S&>@b0PfN zvIjNY{_0b^qV;-i{7r$ukFS?ysqQ-jXs|nEe=hmoo!h^>=?ymqU-a+C{#ur2^4tE` z`ajN*7nGw}7veeFE!Q2qBoc_!e-YD4u%YuYY3q*myJ}JYS{<%4?VgGzRw{S0PHjwj z!V+EE)8Y2<$)-Q*O8)jZ2qKHbO^U8ubLHT){f4f*GWw0PzNVj|b(|8tQ|8N#XVxz} z92F!wOS8DzD6gmao082sNA}g<)U-hh!~P`o*%NCA!G#(8!C)EHHF@>?2%ZX@%~P81 z2`|dV<*H8#kCR-CG~_FSFfbCmY^^A}U{ zw?5E+o5~uNM)FkMPj{FMSngkM$2vy<-+%Z3^Tkl#e?jo+lt-bse4@dAPZcLDeYq@5>+{>w0BiZ>=(Kd>C$3K~;?=byze>+opHnIRzliyN z0{wTW48Uh(;V}yZ8Gui>xW&7}GWaydiw9=vQ?`x&T-Eck)Bh^f?ZtGE3j%G5jI3&* z@_lSZ{D!G-icAdcVK3d3V(|POGFm-grq$mgk2G=`+y8j^u>BXc0rq$_+OY)s4`l^l zA#z4W`Wabxe|rd%VtMwKVM^_l4(_wPQtWMgKP@}`XJl#9AK|#W)5iWWW76~5Xz94^KPAQXzhMV|Enm^}pI-fSpQ<$dyDqi%n;5~gIIuoi zpT-ZN1F>KT^alb)|EV6pW8Y`_zkgAdqgqC!{(62Nm7zb$@|ayV1S*1n(SLR6zlZw6 z@xR{nClzUw_R^{$VDw*o`kxZpKMwkz>ttxvD%?I~2*eOD`mZkicd0*$_3~mk{;Pa_ zoQ#a@fn!x;r-neq5it6%F8#MXlwPHKrY9|B2vijTqyOsCe^o2jK4u8yLBQzW=s!;r zv7Z|lfhMK@&_n(lStwmay)E85b;R2s9A_%})PKRMl2gL!cJ|M*qDau?vPk(;#5< z-!xTi1vLbEAz<|13lh6v2s8}>Df*un%D+?o|2Auy@>@X-fu=*CJ<@=!mSDSukp6Xj zdaHV@wDl-W*C?#O&4|E8`MJ3LqLRzNlg%T21c#j&0)~JgUM$6$OGi>1mMbJwnm!kdS1PWbbkR-?`R* zt}$oUd(Ac1>p%Br>^aALjK^<`-+%nae>~Qi&bdDuo$I_GD~!nXEOA_)BIh&2-6(hT z+0~`%xpIGWiE|&kcK!P&K6c{`6W3jL?G19T=|dtRPWd-7K0?emI}8Cszz{G53;{#H z5HJJ`0YktLFa!(%L%Iav_g!(%it~Y+lIuIfEf#0T@FFlvvi>P?Pm2rd$F%q#61PR%rQ&9b zv!k~Wpp4!qZil#M#Kq+~Ha6zZIO7a|?z!js7rfvF9)Ici6LNm8I6H<9f%%f}Nmgw{6=NyN`eTCT~o%$2)-3}qc}T;2Z6UpR(+vR@SptTC+pUgPkriB{+;i9r~lPo{Z)U; zDW_*Pxo85ZmnCEp)B~wM?Mniei8=#g}FVMLrYXA5cp>a z)c@}9{%%wCg}U?N7r)q}U-G@U|F08?voIS%V3E8vP2cT3?|Dz8$E{z#zNPY%a45@n zHWaJ9)b9wOxBJshJFV&V;)WY;@Cz0!@E`v0heJK!W&I9FpQiuwl;cZZ`qGH=uYdjP zJuYnD8@Yb>yWg#{99_bW!AIZ=lEtWR-$?xk-&wbAoxk9M3!*xPZHaKiJvjJ~^;`}a z%RhnMueKX_O#eSDuSTx7Y}w-H%$cKQX?=j6E9%S>^luWP-?(vO({<&NOD^$me)F57 z@309hT)0r{%_}ocr3F|EfiF>pl!tPBVq(HCTC^zk>-|Q~=bwMRmg8${MQYFb9)WYI zH;e^0QZLf)Fn)a1RaeE|{^oD~rq-Ju^gS~DlY!GDsV5ktr9Y-+ggqa90J*I98a=<| zEpO3s{KNi%!BW}+0$0m7iFTvWI>LL57ySP3|9)J*KJ%H+L~RaXid)e_dhC*mM}TRez&r>^X!f?$PFxV_)*6bZ*y#KoE zuB%uM^y85MmCC5zy*Iw`jar6(FY&xz+$G{{ouGiw#|Ps%lq2PNk2rtfg%`F|Kd|+^ z@|CZw`0Xb?@d-bB_H3;`&y#rU$R2?W5@bC7T8YnBTyaG-KdaKSdiR(+MEEIpQ2ZYi z$2u`P`Ue5Z>(k;`=b0$ex4!kQ6**k5e*D_6{aVdA1D9TUsm60#|G?BYDP=wH6O`o# zKJbC6<#+A1*ZQ;0I;-mQT4hW>j(MG|_gK1gX{0BjYY?Y6)_a%?5D@y{psujyJDS^# zzE62#8={SF#Xd~EdHKsL3EN`+n}+xskrEWl8(Z+8xG;t8uv2 zed^wO-}~O0-(Gz2#adTLgB{r+@PA5B9{s*n88J3P8{X_XAPw^suXsh&PH3Isj0|AB zLE@hgcUE>;vXDA}z;h+c6Vw69EiA|7%a=#%J+-WxJu|kwYnD^BGQ`HiJ`iEO;W}6+ye*q7StuPs;4>1gKl|*nPbx!f z_pf=)Yg(!o)E9II?0nVgj^3yLNE*al*`YiRTd@BDJTJfe^19el2I%k~`p}0OdRNO# z&)5%`)1Br&Z3$@*$6f6=lOc%8NNWaGx@y{a>?Yjc2VGc4XE7 z!R@eKRJvx3S*5u@mEJ{1Wqm;@jjxll9jznqMfudf_O-9ATSi(I^z&A&S`~2`d+l%h z#&3AWW*GNneIRv#Hl8}d8Lp!n@|^m?JOJv*l~-OF)e+`>ZQi{3r1-Hlpl6_$u$LJ1 zi?oVkFUbjUr;4+qeFWI&;bY={DK5fT`)J7RO8fWFRxm%5@=@8&+z{4>W8-1H7yCdi zUc5Nc>lqhd{}}pt?D4}K66z3pNg*rV{`R*={X(r{_0D`(N-N_=cC>~7bA6wMrOEbfKk>}Vc= zbLEpe#nI=AY_RYW8(Xb%XyqAe#+Xxf%{ABf-}#;2iDWu+`WOphUMPD6G7iKzBKv@) z*8rsBM5hSGjo5GAj^+`#PCkkE_ef)mA03|ZLT;mEm ziu*lr%q>1o-1((5{hQ~B`<39#wffV~@SiS@HWB@zlH*yW=gfsWr2)sBN5a)`^js@0 z-XUFK+SAuzUbt4j;sGYCqyM0||0+)9Xk^a|*WG1RR?UP%U1Ps6bVd4w=!v!J8g_?L z9`2U29nB#?U42$u#rZzSYxZHF&jx7*HepAkA zhu()SMPDpyU9FjNtn@BAB)TTHA?5^Envb5E55%~n9gQJC8-*<`>Z67D)7N9)C3LHo zz3gQkdwux47q5{6Z+OESA{zlZ3iTStoC2KsJAFnDHR?K@77ahdfV8QOjU9%5cY5FU zUMpYL5n?C6j)dI+xe%riTNL$J++*TwK7fEw=fK8`%_QQ5uH{wUgEM1s?DZ4k+)vl& zDcCcpE7&a5VVh#ED)ZKuXQuZD<%~Us_LBbe``-7yME)qxM#p!0v8?Iyv917pl>Gr! z4j}sj{qOryuWdb9bpbFckG=!<;Q)Os>I8jy#&O_%xZh7N)`M2l#MmSA%NUdDwSG*i zd8+gs<8A28$bdo2Cw9T#`mNuJ`Yq@K$a(sw!P>xYOZ}#v+i@}i*m|RJTgK$*S1~4o zJk>ouE7hq^-D8XZJ~HQ+u><5gvIs|?ls3B3xL~vCVcj9}ts8l4HmzB_4@cNX4|$$N zc*qp`3Ss#${)U~NHT$&1t?UWcPTJ51=<}hku2``mvP}nj%zowMsz3C1u7dlFp@SD^ z`hu$ew^J6izNMVdui^LWUiZ4FkIvX3V~B8)aUaI^>Blkl#5*|JFzPV+Eb|R1>sIC* zQ#Xkx{M`a{9`-Py-Jt%^cAy)8i`qL#3-b<0JM9emv)-$f-(Gn}e-0UsENvxC=+(#% z?1MPQ^TYC??O?qgYyOA_45?474aIImc`S~ z$yj-W5oJW5sn$B*R_YA48pi*rbNprioT6OdFLj0S9L9IxWTSmi>JGmzNZq4;Q70Rf z1Ehz#i|()a!Oo4$q27{KY{;aAF>mHDAS;-c#5xq@6m^*VAS?7!hBQ z>;bGLyRb}wp}k^EIogk%xfSpSoVacWb2!6X4mbnXsb|;}Si{NKJaqt0WhwiqUxbYu zK>w^YW<{M~{#m;HBMjOq&Xw}d8dlwd27Le>fHnp_g!lDcrEh!j9>b*;yCT`kr+VAxH)Fav+>RS34 ze#X9#A=LM^Yu83?JaZk8UDSW%78o(doVFhRU~|%YwdSN$im%)E&}Yb3HT%c=-~axo zou&_#SKg2>=nBYJ=8&Q%AeSivbO&^nYBIVSo|F@Eh54x1wMsJJTXG)OgS8wHuC$xf zd)jEYik^$ER|!kTgsBs>?brsXJLrSdf9^51l%@VwDGl}RQGbwUq%Cd3Kqk=!)M~?M zZx~mm&XX_t7r*`6za87LSl>h*v3((LNRQsDm$qJfPTLE|LQauCFr+Tf&*^r$kv+&N z<^?mBNnNGg!WM)*7`a@njlniayF_{NE%iOH2fk|v1pvR{aR~ln7onVKFOjvBGdLqJ z;R*5*n;Y#V>$<^>_LcS#PW1AaAbKTwBxR0`4}Bkhr8O(vuHW5`pE9O>1sn7p`fS)X zE9p?(PAl=lb?guH52&~3uJpqwYuYmMgRG~#u`i&bQm@Gu^__Z8{TFvw+!^AA=|Jy^ zWFlDLkdLgh(z2zUr%j_xhd1cB*xDMk&1pE@KBMo`H=!P2`@w#NeINTCva4E~(d~5S z5kEQ*`X22&dNpl5_8)W}>^FI&As_EfOI&rk&q#ap@ z&Ws+1t{kqtCGY6{Yh5wAJEBBFy$o)L~?esONuh`bGH`1q|EU1(8J<#W{vElG@pSnw3 zYPQU&hm7lKI}m=4o}+6|8GlT326=#fNt*~}$PIOjA&~BBj=DxZ+Hs6CRtul|j6G_4 zIFn8sTn}X#IIH|aZZS8m5+8XFc@SdM$Te-R+D@^bl6T4u`GS3j{yKGx`{0O7Lbm5= z8zvw0X~>_}eXikyxSxp|^fdxEl2^(@^Us-n5_PnZa&Gn=bWG-pFb;seLH`V013uFi zgO~7{`WosZVfwhHKZo5Oy#@K*=-L$OIPE+7M6+oh#`kFh*vALmqS5>#U+H^Ome_^T zWv71H8R{AwWGs$y;XCZ|e9L$rHh=n=SanR-}~0h{GKsK;|8W6#nD zrd6XnR9@b+vTT z<`HkT=iR!G9fkCFs-Cx#FLWT}biFXprzj&hj1E_+yc)TO9?tjJpwVe)pJ*SDL)=65 zV{4{gQES|$UV3@XSO;;0>7qR;%?o0$6Kvyycrfod>Ia8;=Nfr{eXJ4uc!#=+4oN*> zYz%oy9iVMSkARyx#;&%Obe!$_X+O{{tBqTePILrhJNAP{^VX^NXjjpJ&^OV4(2uIw`HRhgesZVM&}g{mOkhhpTPU!#*GU~i>pN8k(rFF-^c$#umG*z2-l9t)hpN@#Jnn-bZ4NdJ@csJNzwZC_U;ot~KYl!R z`}gnn-~H})BdpPh=?gVFkGWHMpuJ_hw^I0UpSnO_p0+tGlTKd~pV~3utm+EXU-Ue5 zOK`@9oVI6S*W`WVRyv*3Ut~*32E1s%WI!~}FWv76F+c~Rj?#wHE>RD`L+4pg7qHu) zD|D(&57X1=HT)*sX`3}V3ip}IQ>hN;aSwf!wu-#`@P|K)`5tmVeLXoj8O^gpzGB1A zBb}AvpdCOb#C{(>hx^n6xZg|l09}E;B{G2ei;hL#llFnJAN=T9v>Dav7T=cmziWW_ z&)#s;U_n2tTHf_O`T=$r>@<1Hc1=UGXJi273*WS^vYrUNME!Z6X}{4|(Oa>pH_HFV zAAdX=7opA~d%L-wh%r3mm+FLYAO6rb^b+^0)fdu;KA`ovUYe=z@T4RI77vIFV18pH z6RY7g2={4|Xfv6Qi%pGd`Yw6%p7`JieN6iD=*f(8l0NLCPd)WiMLpo~;luuq{^*aQ zu{`>;*x&xe6W@uTMBch`i1+g)@gK}692Cp5dJ?vesrF1tGM*T zcPayr_w-Y`#Tj%D+Wce3j`@clemH6~u>&x_0AByepZtk`_St7+Iq=ti{nyc$nA%@R z`(ORlUq#RV?9cuz(FW(8C+1CK^C8{nGWFWfeyAt4(iqGO{=oq7pRtUnA3x~jre#Wd ziw&b&^H-TaPhaFmKl)M3?^|xUC9>_qU33HF1NN4`{L8M&3#QT{O`5?zLs~^XLy300N#i@v(II~`^X1<$*ghe5I~8L+d5@=~e|+N`-$?Km8SumtPefyp)z;9X7ocN@dV|W4 zLx&DUIz^squi!qqFm3;^=#I((_)_9O`{4KM@q9^mqzh}lhV6{Kiup)+u2Du${-=NX zr<3>(m%sPD?^T_TKp%j5bgS-cNjB*Dqs0S=VjGUf_?obNW;O z{66WgG}kF>ta>>-+Em)GX6A@B!jWq4P;aW)`04L6zFO&9<_N*NH1E~VT13VLDt(*f zJ<@>v6y0f9xles&zDtS!oBEXhKOy~;WiRtHkF+tb1KV{Y>jm276620P``OP<;_+jT zJr?Qr;hZ$J`4K0)!*219|M-t5eTxiWZZNj^cGFkydu)x^P8yjjl1JHfD-L*5;{RiP z$p2-eojH-+!mrojWxW7nyVd&N^?1?fbLOP}!$15(g2UMVn5(Y(J6vaOGHHTm^!*7R z9fdyk(@#I0_#XbVKA_XK4Ei9P9*C zYMk_iui@lB)YX!bqUcc8oE$$k14tnb6lz`Q%g-55Ki-C~Ui{L^*8-KbN{moM>u zTc7bi+T(*cyxqX9m*b>9(YI@7KcQOOVt*3&t@C@Z=fiL8>GaX0$j^;4HuZ`x<~2@au0me={{ayLE5n4AY1dS zTkxmE|EJ{KFTWf164Hzu%@eDBiVGb8*&g=A+u=|8Tjb03?c4p4BS)e-OM4Etn9Kjm zU;Z-Yb=bx;|5x>Y_7!IQ^2sNk^gsXk&!f3Fotn#lU6J+{ojZ>*q`$+OXZm8uJLP+= zGi#9Ok29}}^}uUh( zQodjH$r|j^wF=#t8Snd zVUtqsbH4IRU-9q$-b0bQ`lHvy&#wIHUli|u`~LVI@3S8@>CVeefoz0N;tq%#6W7bf za!Fid!_BKq`#BzL%;=%$+MVDSHZ|(|gAYE~6#votS^E!PI$^7X`{>en+K1o~yk%~5 z$p1(NeDmIj>u^4LCUQV!0=hx^eb#&DIj@De5b#ReCB003N!u4lCpt?n@e&@O&+?m* zjAL`%E9pZ9psQ1-Iw1pCLr?u-4REEoz1rq;{>3kT;lKFBFGg#%Y2P~0H_>*V^+$P@ z0dp{rvuQ5lze;p}@3QH?>-Rs`@{G2 znz4ht`T*E);FUOjE2vld0O%%B9m|6=VH~vYa9H_{?#geku-9U>ww`Yocf$URtUxZP zy{pqH_2apZu37r~~Wc(0Ju_xr+?rS+Qen&o) zzbBo=#`kXN>#H7c)gOPp`n#eJAWQSiORz`y>xE~%czjtBm8Y`H<=ZwKrfqy2{S@HgZw_vr(`aeglkeVV>3^K5iKVfxrO{Mfb`2L@N&GXu_t zx<7Ro89~3I8@d4cC3O~A0FNmD(b3Upz6^6_ddblqQU}DW;X3d<&MSr7LWPqm0 z&L_{Qs)q^lE}3^l`BstvweIO0f;{Ifg!u`^0S*e^dSyL8H0K9l3{XPr+i&hw1^Cf{I>=@$1j zzwnHKxd9kUL@jK+|RC z{CEs^S&Ks%F}8=z0f(&w+XL$yYxPN3*9VtaUqd+PmyG#Pw%Cs-XShyS*f!8FYw7&7 zL%hR01Lj+4S{QerU!2uEXLwfP|L5e~3&;PIL}Zq~G>3FtU-?EK99@9?&*}3~ zN2nWI>-|Q~=!?hz(xGXip3o+!-_H5+9-d*#gIn+z?!s5fh-=1Al()3owdUzC{{h<% z^@H}EaIvc*8z_6^NG;wYJNOPc!u&1XrF}wgq^_c~!&&UN#6jBHsV~SR_$Ka2aUb%yk#jeXf0D>MQV%I-?Cr=_aKYvV-@q-+QTkidJ!COs{>t-4 z&u|QxjBbGLS4|&aoFVK7blMh(?n#|T$HfMs^$Hybxkg%P3mdIBS-nU72Ww;yx|wo7 z_spSwA_uavm7({519d<#xN+iY|M0i3irgLlYk7UnO`j>>=N)|?eVj0|n6pd$gZmox z+kWe_e!QxvH z?JIR&@6qNz&%Y9c0u$|^=En3eq}8^m$V?O zu!Uq9cdfAKd(bzg?XP7!N2XFP=vuYHYvh^s!yiw37x80fsfJZM_nEtk3?K~E1*iuh|B>gE1$C_&r(3y?Y)1y5SA_9# zO@Bn~0JMGR0PWr1sWIuSg}qFt+$mh?T^ENt%8(i7fChP9*T5;x_C zUIxx#*m}KdN0IOM{OMI52lqMa^Y9F3;SF*=ov%jy$Xn`L7_RakzK8d@R=ps7A3s>O zD!VCz692F2694}b@gol##jct6C~wvMv8SSYhg_u2QxD~I_FufsMU?*H9?yeiTI!gs=Ja7DgXimQ=($RhNvFihotwf6s# zSAR9}zJ5!))~Y^&J+s9BZ+C(Jr%0T~;YZeX9?AeaKXpgT13jBMq&%c=i4NK+?yGE| z|BY=cvOld;Dr0;*30^ACyJhwZfqPg-)b# zvzK@^r|-Zw`UNU3f zTKTW`jQUX;%Q_)koRhN*c%8%-@uFJ#+PRONC$zQG_op3z|LFVV8{3ldw9*;bO8v!zf5$geW z-g##%8yH(cPU-iZI-?VXy8q2L-|VY7%_MiSg_tNjFA6eAu z+8sCt&S|@c@;|NrWA~%)k33lY#xIwV~7!&SwF zHkNt-2hk@fzcf$PPZ`l3!qIw;aO=qd>@#X-Ab#XQsGAU1I6qL++|HRgLEWIve*W{H zk2udB8tmDl^?1XE4Ke@8C%RZWaW?WTIwy1IH4W?&RL$u-?eL8>A?MNeG`#d#`~RH7 z>-2l-rwrSf3qX6VWuswM<3G=_2f}~O`o5kS(`?6nMn9&+fB2Z^@oy4YWCv-a-ZcA8 zn*X%fv?H|rw8ho*ez=U>sK@*CbJ|*!AC=CuU#bJp{)IL_`oiHFfoAhs>3#b0=-OIO zIMe=D<3IUsryZ%3hD!I4h2Ws=-`#iL?W;LWJL4(s@QrjZ)?R6jP1^p?7=YUUd8TR5 zGjbYPP8(4ve~sLuFG79LFgYv#Y3sRGN1M+WApGZE_zf7M=V7~PB;HEz&?kn6;^1Rm za{+c-beojC;GVPf|WQ*{h-f~=60igI9w@Q?t!z~0Vw~lZ-V}> z;}n|ScF*tyJ}b|tH}v^KJ0RD8{KtPB)hXtj>E_T8WTPY9dtmxz5T`f)Ar~KesJGS;Pe&%zc zOQ;Nh+n@b-(WTR&_m%%}9~sFwLKtSN*OUt~Ky5TvT=9Xp&DXYH&*`+`r#)dDlCh*# z)1cpxFZd|#{dtgI;`kQv!2?Z0C(dwG*V)n^ppAw9Y3}3aSv5{Kb02#^rSQ3j9UGn@ zYeIdWdd_%7))=5`6CXHZ-%HmcbOY)S`Ye5gtmCg09=ZnYj@JKl+Q1R6Wj!WI-wVBn zvEX!^)%@6Ju#=IVcJ+`D6XrI6J@YO?J*`?gxR2~Y9>V>s=4)UVhmYcRi|h2kzGKJG zh2cbqN7mQrel=|@I$$M!w$kUXCKu>OREmRfC}ciyp(l`E zq7Ks+LJx-vSa9q<*P4gewKzjjC;XUzCuC?;m%ro>p_$a(&9FTGO zP97^H@F>5{q_LHE;d{s}Fr&Vwxk(+U#^qM-qf4aYz)yWg|3~lD{LuH$+V-zuhiBS% z>LU6t@_@Qut6qlRcJdlrkc+hQq>H-Em{6;8S{qF(GLUfv`WK7^(=O;74ax{Tk^U3B zuGTMtuZ#sCAF$~#ClHwdSLxf67PUE}hmp@}>7y*jJN(8W55NOCf-EF`^j}m?Rtu-q z`{Wf~io3Xz=ulvyi0ul5amPRD_tc84<7zCLn? zc1GtYHj^)VH@p1;kOlOEunS?EfXDUpeEKiw5Xe=vC#2i$(2qQT&+rU=p;r0voN!4C zI)0XIeYJSebKs>o*5h{a_?`rwTiZkbQrla~l`>0nGcW#AXF|D0zY=|~TAA4W>^Pn! zmW&^vH(#%Q8>MK6okaA38|>*%I~%`Wp4*YnFMVomPDey$-`n?Z;0I0A1RM1hOz;!(GSg&1GVzR zGtvS#gr76wIyDzKx4wUvU-W-$JlNQg+w{Xi{&Sth*q`+RPt1K}z?ENqZ^id`pSB>Yam}pi3)~Ry<6bGQ6URp-(1Z@0 zRod%?Lz%&GxKBRQ{O|PmALG5)fJi6f5{z%vOS3)iEjd8jAFf?c-s}0@e_m|+ul&>Z z7UjV14ORJ18^QQzFO_j%1K8LJ@=KWCf&bmIJJZ*Od&uzg93W(S7CHd7gtTnIk8PR$ zKXQmM3HltpRQ8sZ`m%;L8~eNRJ#iLYKh*oRA8_R#Rn`Hp1<((!Hnz}>^1wC$KgHcA zF6)Ee8aTpUn%&AXoWo|rI4XA7G*6Mqa8vd8X3y2u05JasyQ6Zy6LWpKQD25FPV`3V zP=fcO%j3WD_F|il4v_Hwb(@gc*%vR*E~%d)VD6 zOWNZ~GN6^YK-I?l8Ox#H&lnE+g>k=rJ6o$Rz(1YKi9AK0V-5wn9cRiGIgGs*d0s25 zW}h+EkDaU%m*e(c`ux}dX!lhgxa#`%RQ!fHS-mz6!1y>k7574MSsv>ou*~OkHd}`J zKII56bxvF;2Y7~Ut`S{;dRNWf#~c=H#q=Agmm0U7o09>|;RZkCGIn>);LbhD3BDtL zIU^I01K3xv`Qp&|nD2}1Xa{@7ZkfZ8=6}d_<$QR?9+3WqG%>HIo%D3#TkIR~RNU`o zNq!0I7Q(GH2dWeKsTC&kfv^QIM=RvNUh^&O2yG1IMf**?qJB}=s_~xhY4@4K#aJ;m zfm-Qk^clJXTp&%lk1+L~YuZH8({3GaG%cNek9>f!%3!$8ys2jM2PT9=xgk3!J6#jf zY#!+QW5Z>=M#$%P|NFAtkG5akm0t+#fT{gN=o7ZHZok>IH~Kzp6+9JpySOZm|3d=H zY|TQW`Kk9Fc0lwnc#4j$+;8WM9fdGyFVH3GrJ>q$>Z!JGa9{flnl|(cO*8Sp4P=e# z#mI?f`BE({dESSw+HULI%`kp&Ko;;Gash{oR($|@Sc&6&2c8;sD{`56%5Ws+f6DF` z-!It(u8=VRbb-(gs5arAa{ID~zk@fhT?@~hc1 z<%#};PC+=e;;i?=+b2^zLi!P^gW%-so(@ql>I#MVHxmGy<*3IOdI)Q9w9!JD=;lH-` zY3}2v?;kxc*#OWZsb`dHGx6s6ZfPD6GJyGdSsd6BqPh2Z=A+rT=-Z*+VFO_eN_w7P zHF?0;KJ5a$L#L}Zzq?Yp;63$QbyVs*b*u5_RG`Ht@iSM_N6&FBP`;_vo7 z<)iXi(*t*r3Fz(Qi!o~E(PN9N=O`28Fzq~TK4H)w(6G5iK7dPjU!PYyqj#|$2mKCt zREhVM?on2Z1JznfSS`*x?=vm|U&WoD1^Fe6w-O%Zk!LX-C4 zYlYhj&%g@4id!Qti{n!gNXPd2>=b77sVOh&AAABAWEkr(kp+w$w90wX3H0FhsZDAJqX(bHWePo|< zA70|8?MKcw%k5^~M|L9v!u}A~$i+&sxRLy!r(r+C{)>**PTSB(e4TnHmDUZku}cB{jly;ibMHNKLlA3a=qF$^_TbS@t^qM8+teWj!N-&@*eFi{L}spGJv$8 zS2xWsgoG=YS`Gr;j6e`X25^;6N>6&ujV(;bfNRZ(Xh@{ zi(mN^h`-(xRYoqVe->SsZO84jwp>t!4$JVJ1otyp< z;~=`9Po?km9(Eh_AaoAR4>o%GAnG5+Gkr<3X+ z*eBY}Z@+#IU*WB|OU1QwM8EM(UG7&bvPd^=Y_qndJo!%^X_q_Q4}fD~9K^}EWi?)+ z%fnsdLpZ)!i}#hDF~^&*&G-fW(htx!U&~AF6Is-OehP!}E+IYc-R+QHzTHGP z*rod^|5>M%I#1oFzljb@|Eiutf2AJ}ewF(9)$Z{=-|>CDICxIm%9sIdVAwWijRD~i ze#kM}e)RP;AMw+thqKJ>tHkMM?_t*h7vu?ZDARHBJnZ))o0vP*2^@MoO!`vPBXNw~ zw{zSkA4h9!dOgpbOdo9vb&q<-oC0*yMjUg^z^@wDE8XXPzH20W-eIf?y&Cu$?jWCRxxrN9hO&?J{xVIxO=bm^&8My;|4cSBdA|vT{_tDX5kLz){)#r@=BLlE0hH*3hK=l*O*hG4v4&|MOU=F}H z+abSv`;XMetbVH~Zyd6Sk9GvTlKKYM;2Amq`gWtP8rLh`Z!}EaLmr`%Adlbz<<1yI z7Wqzxfm{joX=Eb(p^$&*AmGzzyAMaPU4k)s3uB7ps}Z??j!XY1j2|5()PLx&45JMo zSO>_u-FA-uE*}r|+#em^g8#H_)YVG7?bbcoIP`7wVcKKre44vi_-XISyM{$P;XFUa z^U#sdk81Iku>8KVadKfZC=L+#&I6nisO*l^}ou@(d!&mBRiNA7c`G~?9RGb>g z7i|^wR{5VNuN#dE-ogL8+kfni%71uRZET-;!PpL}@x0c3^aOPHYBGR+4|yjYlxMgu z3f)NU0gaUPpu7X#!g$=#mYHAtaOn9@eL@EG1OL&zY4bW!kKu3Fw!%yL;P4JToVmQ! zI0}BXI9~4=cD-tL0oHfY{~;ZeRX7h49YyCVbpn%K4zt96_}k7g^!%s)NZ+>~_|I6N z>daZ!O}G_?jXjXDeRzlNj}5XC|G}>wzZ-du?5Pw!HcjQ5aVqf>InfBe>%D_s zQ%wh;{nzz?nrG~Qp}f-j!*VY1pWg#)=lGd?-0|OY@AWc4f2Dtmg9DBB%kz2<+duj@ zwk~b|I)N!1ReKtGHCQk&3jS5wi@hGNn|Y3IUrENGe=z11+WzS?gkyo7z@t~g%+UV# z-{3#k_G(`9NEdA%bE@H3BYSz|#e3pG|7GtQ#@)mDubsdJ9XxDPk&D>Zh@Y{CG&gzH z48QBWOMK}#@MHf&E@@w1brtn@0*hV_lR5!!#r;rRJI8;Pk0ZNoFXuOpw4qls-;TZ} zeF4Uu8+FadgGR&TJ=$H;gIq!v@3gHO-6C8ApgI6)VNcvNH{oPGPB-%$`Ie3YKeCU$ zfz|_L6tY77!*&kl|9!U|^2@h(!GGqx4I75({8IOIJc;>`*xGA3#^kVxfnPKH=Y788 z`&!}i4Eq~%?Xbm%x^Ft|S^D7>_D~HIot$;L%mqwyGwb%hk_`}D7kSK>7inS+Amucy zZ9fXKA{x@;$9MSqe)>deF5A#{;7W!p99P(QKi+wHRW4&~>`y5`;W`}bDnTNw1 zoHYO8WHar1y>~11|JlDC{eyf}N?W`6?Z@wt#X@@AC)y#ueEXY(!y3HhS6uz_HoW`0sP@lafP;fy^L-io`R9rDY!?Eg8^?dLe(kkOu7wV3VrU{&tH0 z#EYIxyV`9WusNU$p$E_hU>-oFwSRE8o^D_3IrA^5e`(o5zZAIx7CKM3+n97ae(a#| zRd|b=Bd*=!ze)g+Evnmj>$Ui?3DOQzcfCBq{w4>;@@U54|wN($`tTxXyPw^us zYjM2VGum|UOoz!_N5&n|!H^G?WN@$5mpsxF*#6rc`%}5`S@_R9k37=fk8w~3X@8ib z#yBB$k$LH?O<{bl-fw^C_yF_Od7tn29xP~E;XVEBYPv?ZF(Zxe7kvO7K=&j`a~2r^ zM%B2UzOOn?IvjKiY<}n*VAAdU_fq`mMDSJIA1Z;{JV$!~)2HaAeD-2mv@L?G^hepd znl_65sdBv5nfG`ftkId3D~!2ya$a8+SR(VtGr9?V@=Ei8;4$x!H{@lS@9Ky5$doki z@xy)OBQ|dOLRqAJkirOl|Fhk_e^qSo``?GfMRUyt39C-!1+LN-!Uy(Z!#1wJ>xCXw z$?=@`m`6*Wf_AMR>o?;f^g+=B&|xZZSnq)oc|>mF^jm$NJ~OY4`px`^aK2Bc%CX?Bq72$7kw>tjq@MRFc#b(d=JpBRep{>XP z^e5VU?e7mFPFdthbuM@+?vf0-Pf#P`ek{%-hqFkpg)uyG1iJ=%1<}8u@6K9X<}X*8 zFN99S{8i?#(1*Ylg5T;(?fQbv6@H5Qf5nZ8%ldem1Q_l0U}aIeEDWE@_@EC^PvJK0 zIpfsK-A6w_HZb-9=h;6TT^3njSXRa|jSG0fPjMT>b>bM4Kpz(uk4>f1VE)F+%5W;< z#N1)@0M0hYtc*)DbtGu}|4sOrrQI(rM}8arGe$*uHIqhrx3#i0+zhu`bpSgm`_jWp zaaX1Zo3WpM1-~f}>2S5uWzVeMS{>+o9l)*vAH_W)4m(|^k7W`8`-<7V05U%>i#m?& z7JaH2hb_I9qwmcA0BOhkQ1%H=JMO37#ZNf3;^A2{>E>Mv^>3cBmD2qJxVi>e<%wuD13w4k@cfdU> zVg65XKTn4u6a-2)$6~P zd+G4geodREGkw-H>wD3BQVBOxEUAl>6Xi9??GkgKgSz+)!Lb|19Emhq8_521q^TGA z+e(_4lgECa>?qX+gcU&BJgZnk&J}-{)pj;AVMZKgP>BqEE4|?$%`cJ`F-w!3vv&D7mSS%4gDlU>o z{Va1yJM;3XGvp_5v|0bVxOa*>Tb%J(K`0kZ^cD(xX`(J)y9kdME?*w66p;bb0@Zmdx_5~-5@SXCw*te zn7WN0@q-~_CF~Uy?2SY^4~qMuxC_NGx519VM}T_za&g#iDF3LwQZJ|<)RSJBhlnnn zvO#@P(%b9x^n6KU^t(Y|OFPT>Ox|(;{R8>K-o?Q<31xju+@Fj4b#csTwqtk^p!{Da zZil$z;v!iQ6WHSjLxGC#C6;Gh;gFFa2^Lqt=VtFD}XlYd9IJ?WAm_tmr4P zKLlgO8h7dZU*x<-TrcWQk}NyAi~#lG?cx|Gjq475ZsrP!=Lwy z>-9EYl34LzY=ZPZB`(SrdKT-u=yPQ;UO*W1=h)y@;&|6PjzIdZnc5NH$uY-`tu z`=Pj)kJK;f8TBoTaRllvJb(|We(5hI-fP7%#y$uKde&-jyTmb9F@Y(%4sABJc-AtZ zccBC6d{mvYs&npGKT5x%0^bRjISC&U$5@aZhCsUroFxGu3;s@ATyME%?K1U{`p9?! z^^>}buAp_7ddxlQHT9c%PJORXcc&#x`0*R!;LD&M=vnl=ZxQ!D#3e9o!OuA8t>P{h z*9+sJk_IX zXUEYe`?@%6^&b;QKkWi>l!qOLK(`Qpv(!WCBlVItje5#_M6J8jW9l=%rAz&Ohq#xD zqu$$L2p9r}fFWQA7y^cXAz%m?0)~JgU3a?rC>e6S3}eA06s z)|9<=WeBu~K*8Pa4rmZ7UUec>-xj#7Zc-oHxzh+7aR=RoawJ9eADz%U>xKQNsK?HR zK)VPW5cU_!fl%L0_yaNFC*5uC1?9(fl`{fI)))VBj@F_ZQUnf67XOAc`#qO!TFA8{Ksnxg%`Tpp*oc`^bRjdJQMb~XfBN8mBB z|F0~M0Ho{Ngp4;Wa&uY_-@a@PffdQu{hpci&n|stQSz?&41vxdaNHl6Jh=Ulc!G(} z-|0+j7HT~No}RO7Y;C=l?71P35ds^Ru2^6bOfmw~f-wXP0YktL7-R%4`j$Ty*k?!F zg!8ioIm4DWLttnTD3onwV1T`B+8>Jf@8|mke$L*Zg~u{(2n-VfD|{jP(>yP73!*>u z+u0B>1O^v@BKybV_P@yf`GcEb%b6iy2*e1C`{JM8nD2^z>bJ8YU=NIFWVnK z;Gc>0Gv~HAvC+iqn~SO6zdxCan*PtZsr@tM_s87i<>mQ#+#hj!&M!Z=t3gJ<_}@tB zt~(aIHGb1f$d|j_9_O3kp9GcQ=MOgcnSkG48}aA(+cdtT{;+?<7vlJ%`Hc&=#}jDr z>${vA@=v)b{U!=;^455ol=$~t9*6GSt)nIi6stizukgh`&D%VgpUvN}9)*Y8x-jpa zvC*5#ci_v6AAd;i;NL3}Ko9(S&AAc(Ht&9}-}pWLHotst%tyVS+4+T;g=7H@00PGU z_6o7pt^V4J)xI_avO&Q3pAC){iXqUC2%OCSxiSI}cR%|vOO|Fszz}E@furTM+I-^Y z`mIO(#zY@>j_ja2;Kz6}e}Mrk`Jhw2)T-I@Iu(zFYY5avV9IR^U+{DM#FQ5cRdCEM zOgtWV2;_snl-(d2YGe>3rwx)$QhTRWg7lz1tMtVfDlK?(&^aZys}D zW;SmwKjSKE|D%3;3#ol2Mw^5a=EP8hN%)OT)pBIpkl+%M$U#{B(>_uc)q z{CAAcJQaj@%ZR_w`u|D%%x4I62Z6#=dcTCbX2c#6AJ{`4_3j$Ifq!zzQ|gW8|1DV( zdn@{-M|D)+kk9vu4PdtVOXrjo0p<32Xi-)3VW?T z=#R*s-ku!uZ%4+cKlAa4L#vO5;cpr9vYUDs-{m7RM>&Y!xij6K;oUE4t=l2LJ{#{p zza8yw%=44by>e`DyW|&{<6}hjUmxE2{V@!Ea&eHk81E}5)^GKvge!8g9*@|3OzLuA z(-G=#IkOzStQ8DLM|yqaH%NG=E~nh|xZe=VL%+x`_Y<-tq*sn-+_O&B_-JxYmET_3 z>h`JE&JhBIH+30^@nrm`CB5eBSp;_ZwfD-5nQfXtk(XEax0Usyk!WtrS^7Bl^EUrj zq(N%Le#Fm~9sZW8e<07vJq}mh>!r`G$EIldPb?3oC;WCPv)J$4#TV!5fA&tIi{oeh zrQerc->-?cbFU-tpm#^ycsZ4mqkhe_-w~HDPfp3vK5)XjWA3=jF)1B>&2srgoTcg= z{BvNdBePDlhj{!rQokW7;K`U!z! z()$jqgCY+WPEQ<~OlBd|0x!4+%KJM!?q~M-vnNM2zJ)zLS69<%XG35R5!fO7?YR@8 zBb1KW^6PZVWyk%jjuo<7%#nRFmuYxwTrZg-HC1+Q0)g=r(?1RL$5FR;)L)qVe%Z1C z@{bhuTomNjx$~XhlFVPy2AO=;;i$5TLrDMC>E7rI~cJ)zX=MC;LvDlXPb6Vg> z_qz3seQR&^JOYPh?C+4jJ+2_w3S@my{DY==@42U(je+pXj`W%%@*~G}>SwRQ$=Af{ ztZ1{@wVfJAN6&CS;J+8{^Vjk*?V=rg_4}pnR9U~@cjJ4>Uiv!er3c3>m!1w2JRF3} zf{f7w$7!+(JNDicUqu&}CcOjaE9^{W>5Qbe|9u(re}%ALq-AX1WA~1QZgCIewC?z- zC(FsZOZL+Ae$%wx!N;NgOCxLi7h|hj(K=tY%JsYdrYN@$$N819W3m4G?V5i(4;TU& z^Ixp{howicOHsN^_U@4xNd2<_Z(LJeJn!~yRd{E1Vl{5~WH7E}Waj!XC;sdYXKD-% z!?x=lKp<=WuT192FPdKe%;c`PJ>ls{|MdrwnKGXtFenIQ&HofG4xh^BMw%KsONRF#pq8I^De>Aud3@{YgZ~X5<$y=0$ zfGdsu#pD0Q#yQbl&lV+#U~TaGaq$1PaC|A-*eo0}d%7^QBR2Snd5+(EPtAHB;QU+G41pm=;DGGMv(cB=d&uY9 z4T07XC?r3%o!*jon9mR}1PlQ~zz{G53;{#H5HJJ`0YktLFa!(%L%Cs3B9vk*xx@tj-#ozbI&4hK-eFw>7>4$@CRbTPs(nqFX-9C4}QXq ztS|lzeoifiy@0@h$>Luxq{Y(UoblgkL7oU0|MNt9A2K`^ z`^91T*Sa?T*Fq=%XASZ{?!m$R9sXM}-@PAoqwe%L;mMkhY=|F47v>)bJj9w_JmmKh z_*<&};<@@y^cw$@NOi@x#2wdo;s4auBrBJ=%k;5xkBR+eW!X#H&Y^ew$X++$maEs! zgN}eN{i1p)pT*ixt`Cdde6b&=_`k#*Qdr>s!O8Ob++X7M=wmx~8UgJ8kGPE*O}hL} zmEXdil`cPhSZ;g& z#QI~pzD>w@(;_z~^^tiyhQNws$bQev`e&CuvnYADx^Lt5@ip!o8zQWZ&~QDOntN#M zv2Z}SPWF%cBa;WWKe9$&lSwpobP<84=j<9=+r{`T>fu6QbfPac;%5KNR!d&-V-boV~rB2TPqH zU^gN%Ujzmd{icPw~o{HB?ZFL%rAV&4q^B&Y;Gf3U&N1pNNmh(E{Qrtuw> z`LK`pLL7fIzj5LAcmgzjeV21X{wX)5-$da}-WpGn691mdetdWN&ZjqzCH9-yyt(|0tE~Nx`tg}?#KEuk^Djw8X+A@sdkAbuMm^)N_wJ7J zJ-<-et9@S&+Ef{ac83KlYA<*9l#QO_I*;x6FP~W-X ziJ(7JaKDgW8}s)kvwMFn{~hBqPX*!KGU6|^{(llb^BDr&L7*^|-Y?;<8L@}N2lkLh zy}L$l;GbOblzOB2e@o)s%!~5@(|+ku9n}xy^SxpNn63WOIc3Et?}wjyFDvi4Sp_sC z;RNl&m|JMSZ#pE!o_#h)QbDfu2mKNG)7z6{{_V&Z^=CdlacK3?F#IiJUUpLtt@3ZkPNbb9{`*{_Dd#zdwedPc9BJ z7vp{9#L8~yr-UnVvL27vd`#+cVAB!mZ#lCZy{r{X4)vtvRq$%H%YuwB2lbKHS_q|D z=y~6ta?|5}Lo5&dBEQ^E$dZs=Ii7LPI$7hR$vIVidu6NJr(Qcp2o&DbWgy0r@t>CT z4wCP%{Jx7{8P`YJBI;zn9=rV7dqtjY)AWnHyu!b&tRIa;vh6H=ocnp3e=O1H2o*8f726wyOde%ckbegbM-%aC(*_6Gyl@>ORw+O z#P|QXSRaqZ`K2wAIyo={#t(XT#EqBBX>!!BnNIzl=_xtd2Tt&NTE}IMN$K!wmdh{V zELHE|p95PRnH5`IpiPTCZtwAl!Yj)#+lAo5`0^u(b_&42NnX@M8q1Lgf49``f*{MnPE z8sEZx?rUi#?A*5q4EugFJ4E`q6QU!Oj@k0-bjxMO{j81^vRllNeKVJ7cx&YM6Yc10 z1jbiP|1{Q@o%|a1-cf&HGDpjn^@|_=J6BF`vF$JWB_AV&Jr@P}b?$uUwQ2gfXzo(>Z{9E8h)jL`(gX|f7C_TCj=MHiPQy?y5^ysOv) z(1edgyG@t(n=b6sC&lv_NpJuAGUopZVZTVr*uKZ^9Shy!9>!_i@l{WjlXsWwrRn{q zX}yDwL;aUV*7z^RR=J{ezHF82cmGXMZXb^GD`UrE{rB57|ATfevVT0zZ*jNzGXDoH zzbEAE&zJwl{Ruzi%k#(lV!u?@DE9FLsvIk; zMU^=M#{YczU$`$>gcl^e<}(CxMZoxAfN8UTg7Yq@2&C}^wb#t zd#i+v|GiZngPt1We-D)K5_eov0sp7AmVZy4`%Bzq`q<8fK>G+RafdVz_@>m0qp;exQ!Z3(f(g9k5lEhuxHtdBZbG#?IQ4)*#B3SM*!0GZ9>MI7P&d?#$w+$ ziolBGi+<0{`e&CuvnYAje1<@05IF9SOdj0+NIbzr=kIhTHVd^L0#DD`HMX|iOZMCl z$OwUrOIIwg2__kVX~7r*hJYbp2n;d;7k$eg3+88!xC!TH4RVGpZ-&6oB2XyX%D@17 z*|a|t^WV?+3;dkDLko{(+z=Qh1XlP$^rv}VBK)*`3;R`_$Qwx`1ykk@|zsx z@V++U&+)gZ|ENFgAMu4a{%C&V!tL>v{rL4=&JFpe+?0M3g*SO?JWWdcdoGVdckb3v z69tOZAf8wF;-BVip3KkYZ&;7QLvCG|chA`9P31fAWyX&`q<8S|6$zjRe!b@0h<}@R zKi6;k9)Fu(zIRmpnV(;nd59KZA0uG=Z?EuP(cb6wwIR?|1dRV(h2J7K1iFgA$^4(| z=F6UWb{GPNzz`sCw7gcE-1@nG>ruZk(TANQJLnGhF`mp{U;s-#=#($DYWBQN#bepd}W*6n0xc; z(C^&pj`|DJ4<_Ol`MbvN^gHWh$sQX5`5-VQ`)jOlG2f%g8t`SFTkdu`ACDj9lc*+Q zS(SYEN$)m=cUb)}zPo(q)0@X!n3>I+%g?yV+W)8@p9x1C{CYqCl4O+TGX%Paz=mYh zGyZz-?kL~$3#Gl<_w}HC<(d7$Lm45vG6V(~fo~`Cw#cnc-Y}mbUj7Z{VL?@|1d``F~5~w?XybdD<^Ms-yAqNxxTY0JGI!I;X4{r>7og?maj2 z5C=QJpnVvV-*Zn{h5zX99n%e41dd*m)+FE z_%0ujIm$u&&YkJ@4DWtXYuyg{_1Sp;`R!Rc8(Azys67Tj3?tiE$KC1&myqPuf11h z%xu#HioCqSzpbnvjYM-}&eF%ZpSQ{CQ8Y-6*pK+xvcum}^$+AZxyRwkd%g78_1F|m z|B3OKp77hH%woTD7hjyK|Jgfo1mwsP*eZMB&&b^MnkGwnL#>=Uk9QA9a{h!C> z%ac=bv=5x{?wC6+b4*HyU$b0(5of7-2mc({>d36v>H=+A<~#2_K2dmO8HSs0E?;xi zY5&8%FjKZ3N_w{Yvp$OnTpebx`EN!s&@algZFCE%1VSpuE4s<9=qJKYMai<6GF{b9FVHb~Xee z1iWycztZ9T@@0$NV)e@S%Ccp#M=reVWqn|0S?CM*uR9}nVc%6KjeQBhd%Hf5?lNM`70gU-CnWA#o5!_jh{ET$HZb=-p^@)AKmNLH~zi7 z+0zId_D7{f+Ywg~bXggHi-kJgd+sS`VIciM```Echx-BlBk|S_|EHa36rxjwMWN*YEzDqTD_l=U2v##rp5JYyOA+T!H^PYuNt|Y)RVxMx))0%k>F7_neU)U&-0(B5D{?`G>9vcD!jKBf+bYW&kJU>T_^ZefX&H>J`Wz7&U1WE`T@Q<;h zTfE*wrhiY$jxT+;V8;s75HJK#EAe_;=Vr;TU$4=}b~XgsMWEnr zFaMt7idUV8DqK0x1#YYSB8we4A#lVUbQ{WX71@6@{(;@~!v6mGah!5K?5al*I3Vl~ z)^t+ePWS^c;U{Ic)fe<=(uW~gN7fhrh9R$($L=F=V6ynv{ghY=K*0EK^&n3KjQ@F} zT|cf?_`f6gy>((hrwd~;|B&Ia*e?#tzt*+!zZN?AKWmWxaSsme@9^J>`R@Iw8+E70 z2~XC1WJCNYx-kDh;33xZ;vv76z~55!7ti4@$|j!}|C4xh#ka&A*LdOo)Yc>`m$=LH zv2%}!{bps^OWV$&cl^j+H{q76*Up2EfG_=`dMTg9+EA_!i`{&&AE)@g#2r#t;QztN z^84Ih;`ZocJ9ioZ?EjCrjT%k5{7#kM!k(2bKYdxw$Ngddw0|VF-!5`<2mBZ4gUs8d za}UVq(RdjMzgKo=T|Rwk`A+Jp*nY(NW4gXg$avEtHz)Oxc{_%{ie$)s&&>K~mp-#7 zdAGW6rrWqu8Rb{GPNz~Ccb{O|cP{_z9;nOHw_Zi^EeO*}uY znEL(ulewtr|D2oJKU02x%uQaNoG&@z_MBgSZdZehfbqYP(p`5fcx(KonUF7c%j{y` z4F4pk1V4YU!OsNz{@RE?$KR&$9hLd8kN83ye>A^w;r4g}G=6=Tb3^_qH>KZ1;Z5Ee zPm>b=p3CFVox63^M1f*8i02i)_@{ZBC-bxU8`h)nkXsk#-7_|NQ~3^jnepQf=^gxg zgY^aY^%>_z{M)?yxqjpK_}l#Qy)hs4erD$vW)_kKGyn(~|Jy6XSG4!JeQgMI6#?Ua zSK+tF4S}vAa5DeriuFJ4es(o&7P%o{2y_^Mqvf^Q#N_Attw;UFL?3pJ?4Uc~$9OV- zfdMS}pi{oos@d~86_15$2-HSk%54i@@N@jclotzCaLg`DJRW%EGIzctqp#x|-~F8+ z{i~h~ZE~BY@s(|MWA4qXL%(yYJL)e?KbVMLp4l%vlo7HkLtuar_;xaHi`?qu z4f7cShJYc^-w4F}3r5*k`HfKDx#5YRKU8qPkY5|~_b0P^e=YwV<1?{33IFjL81$!#lq}hM`X`4l)T+Py5$bO_vmCvw z6-*BGq~%rcYPHLPj4%iFk=I%XrCR8D-=A{R<9Es{DpFayR9dUwQ)m&<8#)UTON z{hsM5IobzK@OxUvWsXVd@N1UKFXAj!@8F*UTOFAdTV0?{i#=}d@rlAK%P`!0bNQO9 zPWvDBg_*MTP|~yApZ%deR)>@5MZUl`=G+OtKiY9h-}4J57uDTw>K+%%3%CCBzwh}E_XGY%;;kM2PrF2aYN=RL=l9?EURhzIDzB4XdT`8g9qgY4@pSF7AY(Ma zahj~cj=lHBSJA~=livRGEi3i_G~pvsTWj|JQv_*@|3QaaBkGlN#{6F)>@Rp1+xOVL zV`0!g>XmGl)3NUOswd07yJRm-?>9~B9ef<>zcjMOe=)Yo6|M7St6abPZ;EpJaGYNm zI~MD|->&%|v~!XD(g!FVMfTU&Z@vQmch<1~9oUkz{}p#PF4rfb-E&5Id?m-opYfD&O?mOW+q+fao!N=ixZ#t*xE`Nz*M~XrXMZ?TV{jOD zFI>04|JYVQ`cJm^m2C;d=ZE}bmzQ%You%_bK({2PFO^@+>ndyhuS_P~FPdKe%;c_E zzVdXW|M~;TOqp+B5NMkJ1C#SoMzZFA3Lnv|j)WtQYux0BUpTWT^_qDNfvzB6{Lk3_ zSEm#YG1rp6(lM{!$4@jPcJ50AjQ{!aKjMDC|Hka6eOa|E(S3k`@jrk5uMK`b4*uU3 zjxY5=ov|bi4g$vieEEOepYT(@Jb%nD_Df}rVz;fJgHxd`Cru$>{Lh#Fh5M4lb3xK; zK0_c^1dRXr@;}$Ywa5&CItUp5jsJD3h&>))1dRXg>B7v8csx!_^ZefX&H*lR%bFn& zBQWgzKOp<@$c}FDdJmcYJt;fB*oJ&@4Y7+sMqt?aAMOQ0IS=qoZ-ZQoEpII%F!cOy z5jXqH5EujmjQ@jBik1gMpfv=H|E=L>Ul{^}fPnFT5K7VVUO-XG6dc7&rt@_`mn=qH?zU*n4e9 z`Z64LWe6AohJYbp2p9r}fFWQA7y^cXAz%m?0)~JgUvbdtw4Mf>Vz!e37`+aDC@Bh93bI-Z&xjm<6n90oZeE047v=j97 z^g17n(Hk5bHu?wn+W`JWf6svHc=&gZ`9JsQx6S|lZ47_wkI~Z$*v7%Rd$t7!asqdR z?FbKc@^f~pCmp<82;?Wr3i&od7_r@#;v#SFRdZ$xD7w5a?!o6UA3Dc zM8vWY-)Gmr{Z?VFX${{F?>K*c1O@L&+fU_&5n)^dy(I>2I7yrs8x((-aO39M8CmGy z{gTj+&W>al@tJtmUCR$6Hk6)MBD#V(r>@v>Yxq8aJA56)BoPz+F$U-s>6?eF)``N1 zy+rIu9p8@IRiF~0_xswv5;W3{`=Vnp9qx(JSD)oI{KMSA*alY`NpfRCKhS9cxL$d} zqhSBU#KvPZ;M+uM-|s+D{7_|Lt|LhiA7ZLvC_c$OVk%nb4(sfF&D~N1a@eF8Z;AN3 zezAFe4Ik$YeE0hd?-7AN2CYNCPo8zi#0}`(p%A801WAkDGZ9@?u_m zB)N86X4!ZK+CP_07uUjfPkuQQL?i9Ei%r^8TG8)ZwL;aS139D#_j;3rjvpfR4@LLb z9ccs7NAw@9TXp>3xvy0Al3G~5L!q=o`igQ~xERr8pZn1q=*0DA*@9ds;?N)LMn|%S zZ>vrH0N-S2TH}>qf2u&vvXZYUVTx~mk@pRcM}}ZIwGyL0oqS*^6t$P5lRT<4N?;5S~W#)B(h)A-O6G_ z=l%^0_3zwjzET?MgXkU$k)57>zxN7FKoP)^@_Ni_Wd$<6Gju`$xCyvSdU^qhHGnTGVG zIOu6k5ja=*CN=~8{)5OO4*ZIkt?%TCzT2~Fi4fTd-62ki)+IQVD)iiE{rdGFU&7UO z+j;c-@#VYipg->QfY6s97w+ZpCetath+A^6W)S`U^VXEPwQxO|J$?f4NhrU!hvHn4 zze5=i0Qw+k56s6PJAQL%(^jP4nazqu@M|Jt|Lauvp4%{j3Ptju)0b}afaf-^2L}-y zyFKUE!}J!4E(P44?yoPoH;C$M&evyl1hpA>VEjWd+2@Tyyr-7 zN@dYAr2l-g!+J=r%TG@>MsgNTIeh}rxkfSG8u@!#s0r*J#=UZ-_XY*}@BeF>7}@Vc z`@Ayb&sqB)!nKA7dD!cRzSE2Qu^H*t_*nep8a~7`vA=;%R80(?tG|c#7qq^9hWJms zZf*|y@v#7D00qyzH^hIcg{#7QzcccK+?U^bLC##$zsAj`fS-)Vi!F2_l6d3075qAk zt6o%g812WLK6;57#ifu9A?W`45<|UO_`XZi7lrmqkOj$c*k?zk;xy9#FxU6rfG?4$ z6&>=99k)_w3ULAA{HwUxh+hjmZ0LSgiTw}g-d1R;MD~`^m|}z0d$MW$el1)l^A(^E zHL+G{oQ?LUp9u0r`jUpGf&9a`gFUv1qrh*vVxm7F`_!8~4A!FwqzX(~1N+nER#u>Y z!ht?v0`SkJJ73QP`SMr)Fvb_+HldIyY!Q9t#O~4P~yY6%v z$`|YI%45;~AuB?aNdHDzA+3ncRw3OGT^lcSu#jAKjY^ZC@7Kx32cdf}mZvAW4;}wx zO|1wg9t{i*p!+SGR;Oy=x{a$`kLXacPJ5&0{;W-4&2EHEobVXZuffv9StQ{=o>ra! zyQ|?Z5?i8p%gwlBUWDR>%2v3sRusm~Z!L;Jdi(Zd!e>O!h+JhIx)v1&ycp6;pm++! zr*q$2*9Si)iKNuw*C0M|pM>{zL)_wi{qP*}n;UoFStG<-oGXvggZKh=@2xdN*FEds zF9ErdA#!;LS~qK>uRqYu_htERK>M`_ZE_<;rshr7*p`xhru;f=>3VmGF-u*i0{K3`%#^8;7RE` z6z9CpHpU=20}_qFUfj4c3sXK=H&-c?{)FgUAh-W0RL~ofl`p!Nr z%?I?)l_p9}&~t%_nZAy%;xp*2Li9VY$+r{f8R(i#yTP1oGhKX$uz8$S|VUTa=I1o3It- z74V}2jZH{TEUDhGqggny)1!W44LomBjJMJP->KhxZPa`mPs~R7$BnQbsh3ZJ-OQ;T z_E87>a0heC&^lX!eEeV^0t?CmyT=KU^56_)FKNC~r5fZL;vd?Iu2v;!O2pUf#|Gnp zersPUY?yrd82WwBiH+;g{@&hKpl-srQ|NKA$j)Mm>#mTH2dXyo zIiM@vXe>o`(jD5RL|4DTfhh1leq!k9nQmM)KfpH&L-HDRyA$*c`wmY9{}2)Ri~FJ= zP7!v$UCBYurw4kAk^Q#g5e;ac#igH1NWPA*RK|rOds)A+$1e=%H!?N_Z^60#89&m8 z{8}lOqkYeQOdCXYX)3fDqJQouuShkl`*_2{^&mIy68D*UZV(5Sn8zMO@wlt6^jBnm zrVd%X5D!E<(xjO>pey@`TwViLpX%=;5M49ZKiGxrL2#mfM-A}1(B)h&>_lITKANXuOHI;2XR538h+RG+nK}ugxTopvw>f3ecf3G`c9*v+rSm4 znaAy&*}Nmj5AG4QG}t_JixFwobJo*Nc=%|m(lwI)SF z!Tq|_pcP1N#l4|75dWzsPGv&$#C0Q7W_78z0MR>s z?s$l=ZiJECY80aPAT!St><#Q!nS$suNg9tK{$-X5Z;bYV8q+rTV^&0+XGOFU*u zHOhmFzf8s9y%SjI4wR=dluMVQytyJ2gFFfQ)NQRpde9pO@dNm;FUiQ&!aCnV{Xzd~ zB6g{s1+phan$p%3Cy4+bUl!tDPydI5h<~p;AG(m=Z+Tj<5cJAjShkme?qzvrhWy4d zf8>v9@H@G5rW8Hjkz+kvCjv@}S3=x_4z9-i0rH3DD`KD*?7f&KL;M9!N%2JXI?p;! zhxnW%T)IsJ{3o4O4*65 zIosF$=m>Giuj}(f^qo(8=y&wo%zG!Ye_Yz$KJ?yjmd|7}hzOylE6*Z&dwj$F5x-15 zx&info=e@OqwfNZ|H*^i_QN2ai-<1wzDsV<4|w~84%NLGBj#BnIZp_br>mjAwvb9q zP(4T!3X@U3l*)aF;#*ZQHRuTWuB_~rb0}U6YA%N%`D|@ao1%R@<3b|Q`hzuCH{=Ig zsfwd%;d$k^6@EDQQ<_YPq4(ae;MPFi;6|I#=$`2!uJ^{4A^xHtj|*^wJXuxOQwny* zb%-{lrNDk0x7-9f!?~C=?K0xe|D9kc|9hLr3XooTRo5YIl0>W}6+RT-TH1xk&TQ!B zL-B6Gs2cDah~Fm4Hx%qYQ?Ozik~_W3tO)7T!S3`E#K+g)s8D2gQrm-&2StRE*_lJk|*Ndq20*OSL5K39XiWNruwLUoL<>PS1H=eLi%0py21r-Fv` z+_J9=^Z@qv`sE%C`!+UELnL~VktQo9< zI81ns8GRr8o0x375}xCPT~~9u8SuM0^wfT|f92{7&j>Njwd_Ce8PSnlc}yjBEsZdHu! zAfeB*zvM1Ne7N@BkRm$FPs>3*7*|hZZ$1=vmwK;2el0FaRH8U%I70m^q9Y(6U_Y`u zW4-ACLw2C_Ifdx*t`mhKyHp58Ft5P5{EN1HMBn$stZOJ9tqd3~MEr%?8MdSM_wM{< zDv}TXFGo{!|EkW)4Dr25^+6x}g{!1jhobz$$Sr`nKt$+`wTVUV^}hTbi1bxxe{d+j zN#le|wU7_47As+2jQbYDlv=coWcpCPdR@BtPb9CW4<2z3!(5;&kDiP8)XlZIf#}RT zoScXFSIUHP$Wz=6Ia$Y0zj}MI_BaY=%;V~sbmbMLL52PmMY-&J`J(L4Uf zW0uH1`@5>#kiYgwc0#;_`N9!>n1_I0JPFBF^K1F~7TKm?m!i-hY-qkQI#!?ub(TQx zA1CyH?-O!bv`Bvg`$LKxfG*!{maZOR5pke?J@|)+J6Kk!kNn@;d&_3TPi2Q`3i2Bb zW&IoUGyJk=QBCL?f4FNg>Tl{_T>Fani`XxpkKV8HZvcP5xjh9v87Mv+(3Q4E!g`@%?uan)j*zMO)DFk7b)pN8PdncE4y!?c$k+dln2@@W@_?&oe|IbDYnEJJqOXNGV%4!DUkmz|98^`9!SnJRn^qw@qa>ZXk)LJ@ z6SGm=*sho|7ST8RH=hw6VwhLa=@UJ~kQX0s-tQrX`fP-mBa+Ad4;irzqzN9Hp#(YO z#BYnUj6K8@|M_8=$`Rxq`c(4O0F&fF6X{Au2Yo-$Qm(UNVnA@QF!^zF!2o!JT zS@%OdiW9tUb0iH(}pHc@#Qv0z|KX)S5?sznm|MptNb&N zol2)DhwS8<`uQg0A5)`zR1v`MREL(K_!Fz&3%iI2=QN8MDUgvO{aXi+opqH=Nd>#% zAI{57_7Lm%mrl)bj1-#*dS3l4bR@O>?11iUWQW9{Gat!GsR@AklqA$XKj%VRQRy?-@J8*UrDO!2c(DRy>W49pUI}PZX)@{JnVge^;+u}B^V$1Z_5q~_X4L#0R$ zU+P6*mn1RJBQ!;Ps2ol8QqmpCa~n5@dWt>7KSuU=A-V!QJsZ*Q$;}$bqt*P5W6Gg; z9<{zV7Wof{_yb3BzM;HQg4Vk-ZJ-^!*RGUnXh7$k2TMUOHT(;B;|-{O$V{ce7s=H{ zUIBGM4gc}y7hOoM0isyB1I(f6R=F^DaS+=IEfk2((@InD4>jLbS9}rWvEJS=pi;}f zR@{C}%XcIdnZ7~~cuy{6wj((#J25^M_9seOI)v!{_Z5G!kUh=oL-Q~dky{-nMBjym zDh-bUe?r5j(;{p-RLkCDF2jTSyf`;Zxj13={pqt?_ai&pp^(`iecTX6#f*aI z7gePlX#d^GJCl*TXC7R06#1QAt44|J?Q}-mHneW6tdI`t5)KLRrsHHZ(!0(vX`IYc z?7eX_%qx$kXJ;!dE`Vh5yr1X~z+V-*qPPnnpSHRYH!q~q1REQkze02pVVxaFU-5M% z`X1n)rsVKLjwJZUdNaeL@ZOfXr<)zgPLWOQ2vd-+U|QMPA|&T?3HQE6iXF+c%%!J1 z;d#5cNfNTJ({Jx~dct}=-+hPp@ZMJdbZYs(Dt{QVn~Qzk35b4wJx_ncS6iz*0qHyM z?{<+6GPSq&x)n%1Egec}3aqnn%iv=qr%vJudKPrp z*{cvewR-P<@PzA+dOsRCl1Ig07b!=@?qsV}C_?`5DBI!k1+a(uErU^>C_eT_+aWt& zP|({mq$j-$T*vni3uk)o06D=3sq1+J==;QTp%COj=D8MpL2~Ik(b|FbJ9a2@6XIJ+ zd!a+uqPv$N?$891$G!xTZ=N=J5Xp1=56j1(eW>Ez`!!%US>8V8h_98FfzVfhUn+f5 zK`z`3tHdF@^pPK|MRvx^HMc?QKJn(gMDz|)_PsPx$6s20e|aRx!Lh7=?@{2Zc#F*b zDA02?JDn2=-!)FL$p2C1L28Bi5RV*5x`FBBhw$E~o~KVyT#PlxMxym6%9W8w&QA+^ zM2Oy;GxevC9Fp{^!QN>kcUNCg4amcCg3l>*-@0_h7et3}P}AuFaejn>y*-k*;bd=b zPgpN@HGPXG=tFr!?=*_fE!T?S9IS@#K%c6I?!WGCEJpe}cpCFWd~XVxqD1!JZs&Lw z^v<7?8v=5y;eR{x%`zoj%fIq#UMadSiTc`(?2r5P*x$%r4?J{i_as$B-;1wGB<~T! zOb-G70=Yw)AEHxF9xg=ka6H%+iugB88EIC_j}))l5hz3Vde_HT0iEQ%-Fm~&`mX2N zuO9{a!!z>gke?S7wr8ZExY70MlaB8R@g>y46Xf2IlnVB$8nM7n=@bAAe9AD-qu;gDsc zq6opyXMrv)|8&l#48&*0qI39B_&(E0j_BhL#L5q${Zt#GuF?dvNpr;ps1H0zp;9l+ z1AcF8-`{}b5!R}Nb=1VUoyrDBpld+Gi$-$x(KBa(J(3xT%vi+#*GIlv(SEMZWm`RA zUA-gY9gv+A*Y_qzLfocnK7GFc`C3UY+lclxHrB5@3VI#X@xVW60@t;}E+P3`w#?m$ z^fa433GvPEpx*_%)A5DHzqBK{^u%-*A-eR+in-{y_l7L66N*q-6@vZM5T?1TjS%1Y zuO1iFAw4YmBWEMBce%2-4&{$uJL+Gd^Ohv%I64V-@9mpph}LTr+SQ@=^vn7{&lKTY zFBsy>CTGVN#J^CvzY*m*GL8|4?v*C4Dhj?&RS1XX1o8mUNYv-*>#x{=*1OyO*Bd0K zDc+t;q`&WY-dRL%SHO8oblsbAwbcXO!$b9k^iw4Z{fgx1|MmP0MCX}{^=Y%={`2PO zc9QTQgRm15&^qbqX;Tb5jS6V!4)wLPwcOcJY=W(DI#P*8-C6q1 zCSzHfzA@9l_O0IrwcO>Mx!%0-Wo=6hY&U+@)3?D4Y`q6_^(tI$PO5v|nsIxnrR^5g zh$*(%J9F>BV)Ip)rLDK7Jyz~(2=5hTSYw9My<-M4Zo@i$gGzH7bYC}Ew1&0Psm?K1 zwT6}8@=9AKrNezpdt74cnw3teC!e~SU`_7UpeP>ShKF}&xaRZ&ay~|vKSu<@*?cy%dIWiZj^l&_e zuVUFHC*>%7o0i5VY9_V?7%`tq&#)ET;Y{zkGnZ^Q*)Dryx|JcULoT-V4!58^7Ow)8ZTNv|&T9g)OJqrHry07Kpj= zj7^Uz>Adr{y6g#7Nq8{kmrLV!5Jt=`B^l=Ey-%%-8OEHZrPMI}?$sP)Y^&e??rc`; z8t*N+rj^9)rJpG}y_E=N{o%KDb0rakSz5p9U4vU=KCx0Ej$2{gWhN3ge5S2mbVK4S zJl@5>_3vG+0jsb-W9;ig@mnsrpBm1qxJ@p8Y;LbHN8t1~yVpX+wZgLHQjaA3-O`B; z7K|KLmP_O*i9|)lV_ipb>*&1Y%t7t0#!lGp-1vFls<=6<+u0fZCY+T_`3?UJ1vh~; zzTnko5jlN&aoV)SYj{hi-&M8st>FY>k-VC3?ZgU<%A+zXxmj5Ho@m09H_UlYM^{NZ zA;aF3ZF*qGt6;hItj@3##AB4=uvrqmg8BOU{e~p`5X-A?-6LDvfH~0Dy*`m^$+YXB z@`8zMR`i=pLO}$wqrSX9*3mH>g1ot=8@PHxjuE5~!c?!;GNZ${+4!Qr~; z(QL93_}sAm(T+Eqz2@7u zuDjf3)(>%|b_)Cu=Dzw)V;k4PHZHC)pv70z!c-s-#DHrsksoRE8)RX6WoC5c!1##h$ninuG8Z7Z&~n(_=7YU!v&Hem0LvlgCJ z;&NXLeN*NLUOXmTs5?N1tD|ey+&1nC?2f?TkV;?(cAwL@mTX}ilgxV zINr|9a$-e&um}NrXtPT+N&@{iMy=o6DYnGQwGTcbxto?(#je3`Ir&Giij=TV2VJ?( zj+ekv2MwzV6Id}f_O#5l@b4xnxqPRy$~&=%cp!5L;Ik58T|>GwUnE$?Df8F9 z5DA)C!wqX{%~F>!brCVG)#3`)#=41RAQxuJ?3H*W$Y=el$!|vR4q<-Lyp0`jUkY~J z4)QsvWyH1}F=WtF2AnWb8>GxKJB>n@Pv{hdC4gIzMSt!@d-B*vVcFJBu;GMlwm zCmmrX3E}MLJ^LO;W@DnZw1m8c2BV^62E#)02 zI>mv^ReSe3RFN`PTI2rR?cAA+&>MZP4`CyC2CL=L2-$L`;+wMeL%}LeM9kk0+QIJg z)0T`Bk;51puUX$sp_+W_p73_6;ohNS-l(w}lRaC!|FEP1p9Oz^`Q#>8#r6xA9?^=e z*zUn|=cUMoIiEY;*X}N{V#_Mj&sC(M7tK|L)c_yYW3Fv>kTm;RFE-V4mxSQiSK4Z- z#lf7-Qy0##ml%2xk9k=mQw! zB4oC_y?vK%JZcr2*p*sm99WO_R##T4CYyZ~@=sUXWpaG~mzG+s*oYmcP8ZIC@07|B0Xi`s z`|`2yy`5l&bL-i&Qyvt|WDeD z7!Qe@<92KDRFPmf``G%IX~>>Gc1#P*r8$Q*m1VJ2+KYJ!?#fDyUJKNjsQ`@-1 z*8S!=OL7Nq`DK!6xGV=(mbqkgir=xGA8(X8!1IeK+r}NQ#duGTyEK_emNA(ja}w30 zHM@RxxR%Z{V&^&zH*t^{v0IM}ggVGp<26;%#I>?kqU$u_!b^K^bpbC#m0r|VDBKA2*ILwwA z-}7my<*bVjnoWNf=sQOQ96jG%AYh*G`_|wtdB^(bdq}lbqVHUtJ=MxAHHR}pG<=s@ zP|iB?HR`g5BoY7Pj{|$O;wILPwP~O1Q?1#SNq;q%rQX5MJW2a#mMX`)x4zifU2q5Q zKRrzaal-4??;%rm$}HI-%{#aTUb$50u@q^ttclgwsWdwR*PQv;#%_Y!ELXcs6A`(J zv%q@pHFwz^?%w*-zvNSQxR=Z7*Hw!x*&jKD^F3r7rcOr1o29PetZV4?3}lYeSi}hp z%u=(k`yWmffq!}pe7CQP1pmC2?i$}Ip255xP^+VXJTTJu&hd(14%baqrY89qCahhS zBoJV32k*{skj(U|YFWXqBBj{1&aQpc;w)?r9#N7)37DLgzaKbCWZ14!IA;KV>*z|> zx(Px!pLZN|P6qqlcy!%i`&2s5bMKe!T@>(bQq^{^AeeLhPL6D}CYa+~T()nm#FBmC zLfoJ3G7jTf&lPH|#LDaMzR^N=m>O~>ukQ_=cQqf#Cv zQ-*fa`;&- zs{)Cf49jlG1!i z-?=37NMZ^Vf{SmlYEk~*dmzEhLuSp^J-qK{5Bbvn#}xfM%FydYSx@cp3M+O*=-Wlf zvLH^bzu#Apz{)GbMv8S)A)Kf4>w5C3?aV!!Uw(C$1hL?t%LL^|b*_4=Es#mTY= zSvjVwT^^lB8F_VldA>P9ww?KD@aYp3Y00j)EvZ$Khd5y`e;=U}3z%Cs_DmjKn83jw zeVvF44A~D#uHV^Rv7LEtLQZZt_?=m1*R0)|AkLXZYM*54t<#KTt9Q;C8%|5bpP^U)q! zne5^I!Z;Hl$;MuuH8K12B!`obSKKqEFodI+Htn5*Bpy>wIi5JCupEo}`nAd-brrib z3IB!*vM>v}LS)45E>POoOtF_3FosE$(;XoG9#UN_UMoq!6&^cdMDSkDrLlU|7=bo9l>FBd_m+r+2tP9az(}>pKPYf^FF^mZJ-^u;Y0p8X@F~?&pmV*H^N& zD}xIkP}`=LoIBcw{Pse8g?hx{VGO~@#VU8%3`WEC=+500%b1j9PVVT!W=uV^XJ4|+ zf4Z=^>(+;Y73`l%2S14f%b9-(7f!+ISCUF zj9_UJIW2eIY9G}$v+~+@+;i4!XMXMY_W52x4!h&-4k@Y|ZSxW*=0V(Ze42L7Ub2!c zw|m1$k;P*Q#hZ4NM}pPqmuko+7Vg&82l?O2s8R71u58|!{`X4KfL&l37uqS-XWZbc zGEtnqwr<-1`hA{kXMD131^eae^&1>gjo2**_Dyw=l(XhpbB{eJSjGq~DOpxUzGHp( z^YPZZCxTiwCV`}ec#9uyd{qhii#x(h1ty`s#&j#ngd#-6UBQe<~HcC${!9j^%CctnK% zSwqG%i*M70R&0{t1#X(M&%Asp=* zqE1Z)bGE#)@4z8HF&i_~_E7&gCD-Sv#FkzIFX==+1#!CSP-m#19J{u6{bR&uPXD3> z_aX1p@Bi98669Pqwl(*NY&hFLpyQWMwcD9}1y4VjLA+cO@n=u6Y^K-CxdSowkk{_$ zb8Vaot-S68re2#@FUKG5Z+QiE683ItU2UYy$g6zV#8XI*y)BN)F@*~Jo}Fr+ntbar z!kR`;26>2{nTOIO6)csrRFF?4;*FE3$}xq(9DGH@rhM5=Clh@o-9a*g>Evo9SCbjj zr;j)u>-t6cOc+ls9+eQG2^#!c@Tdai~C{lULR7Y4C=>YpCZg}CkG`H;0vvz?*8>2!xyyqs}; z{=R$%s0&ABzx?VUTfrQcx*zP&n*Cw)IO+n_={qY9?R1nS;U!Hgzj#$tum+p09Nxq8 zPhubD1ImrZ`S8op_YkiLd^2vA8jp46g_}RFy@P-F_Vk6BM4!>noLgK4c6|R@raYHg z!M^4v$uLVz;Iv`;7kbJXoqC@i?{k3oSJJh-vxYQeTS(v7(j*BSRp-ma6d8yWvd{l2 z0(q28T5|m!Wpj3?St=j<6clo5 zwx%3w|L&AICfAmC^z@)dge-t@Q)FwG403G{jD&usnRV>ksKjL1Dz;Q~(W@5fl2hk< z*M`BqCT6y_k+ODfP0bP0T5$_2m*YR<%adhH$L!pWv4sL`S-{7S9w4_g>BHwNjf^R{ z@>AQVS{Wwo?-+0@eB+`lIp+#_nb|e++JyTMH;Xg9p5Lb`SXueJQ^}A=Qwug7j|4uz zPnmR{p;x#1XC+ONgIg}@2}F8!?TuOz26ec%-TY6<)M}1=Y+=q3$SZFGjG~VAFWtM8mT&=(wBV>H$q&`J5;*%zDa2Qp%9b4U+4|VQ#Mp9ex zGv~r`R!ng2rEZGPtar`sONMx~FY<#tkf{|bruHc0sdLkn4CFu0*DuBsIV{bRvEz3fhWxz9 z#BRnt>K3MQxMsUf(}o|@bghekekX)@Gv2GBnbr4s!zVT9z4C5!UyWG74cC>g@POZ$ zndjBWo+4SkVKk@=KpPQ9!> z1KkD9tm)G){pOI`=#+7tF@HDI2cKJv{v4UgxAkw38#@;^v#^^EClOut3G^A=5Jw(8 zAN_KdMuycDRK>VU1k4*nCx3NF4dx7sbu)*!hRsb#E*S;&;5hpklTsj#sC?Poqaoi_ zl*iY4$P5^+CVV-nn|V=TKJN=wv$^m4b8x{jhONlP*%R{gFx$h)o#JJT>*n!oG?*)N zxx2@T1V(Hh@Kc(kiPfjAs?v&ExlOj|>PYCL^D1`_ekg4Qd*Av*C!Wa=Hk3@fS8&kw z{KrQ`vg{W2`bx*gWZ6<%{lYIfG)beaQq!o2l*QT3i5;bNhrTpXSz~@5;^z3p7Iz~g zGZ>m#j)w0G?r`tCq&BL_5YEK-KaZ&(?<9ssKh%N!c-QS+J4R#Wm6Q6*4xKn2`^-8u z{>hU>&cL4N*J}mYtggB9HIdXim*R_4(rBq69M6yiX~!U5EM82NdjQ{^(O0g_h4}C~ zy>q2I^i8S`m-746OfMq5Na-NCh25w;D+PzSoKEq|YU3WO-&zQpp0 zTylBLxqQe|C!69=z6br6x>DZx)H@cR9>s%s2P20z$~Ho#?_8WeE2~Cq!#Q>J7KQA9 z(q1}DgF4_0JFq5(D*|A48WI5*OXf@vh{MPOL@v$kg5WHe;{kml6E4EkuRg3D> zuTgyimurJLh0!mL=R*FD+OJ8cL7jix&V4!fJ9|CPUwN1u&W>ix`O#T()AsqmS=R>z z%b7t5U0)($p40W~@KxOf)@<9Y&wV0cez0afwFTMNH?!eVd*}~8aF;~y(yU;zd-IF( zAui-y_^cTNc7FJ^>YF{pDW$Cm)WZp!3C8?ZHO!5EvL8h{03Q>LKmHmnvtqyK+`k$8 z(98E_wt1dR&Y5SFSPXp^b7o?|&19%sb-vfaYDlSVlC&t71U(%nPnc}So54)`{_N*q zj#+A)<%&a*vS7~QoTh>~8U_9!rf#DS>KV=6y}K_!+?&v%{C*$got{;rJzl$-Q~xb< z!6?|bC3Ff66AWXTOGp1UR@3C0zB#%Qo_k$u_Dy>TbyJy>%T@=NLj>xr4r%8uXJ%C8 z0{@kG-JdaryDQqb1@jLydBFR+t?xhNQLS9gFth7p3T4>IC*mJjnk+0l1k26?`lnZl zG%7NPZ86g5ekb&uoh-NIqo9w>ozhhS{RPv#Q!&d4`ncj+{B;WCgWvkRN@$XDY+Ucl z(=^Bv`}eslbc6ZQg^%AK=TpXPYMv~XP{6$Rla; zuRgWVpF58-`GmqILek|oxIkm4+_dzXCrT^k$>#! z)3)S8ec3+Z)t19h2me`cO&tMs$FAff4q*bQ2adgnzCg*ag!p^-J?P7R?XRcMeBe{0 z-kH&b5V!ayw!12_v1h%Ry8BSqhzGRwX#Q|YefJ`wi$Aiq1DndEw(HgI6CESh73LXdu5? zu}_!Ys6zGZ*`KY|_Nf9U>N~oj@B0P%4o_JicD_qf_^^P!=a1X(qP0-JCX4VJPMYNy zj0v@7vSrwWs$+}Cx8!hQV!}I7{dajxUK$PJ3GOk!tj`8v65i?A`T4 z?xuUPvHgHkJxcv$tMbqdp#EcTmwmiF_|eg_oSB_yK514Oy}Kfa9n#a)4Ecf`rTZQo zA&F;ZkIFfb41T@)ZV}Yo*6cT5ntD)uUs#4W=7F8&>2{793H6By{YEIV3yTjv$_G&2 zc>C?{aFhfw?aw{!sR2I5QmzYefsvQ=dCc?(uouzFh`IUHOh!rS(LQIO=UY>=9L*zV z8jOr1;k^({MQi5{W88?0x|UBZoqX^n-N6?6TLWnu#j04zIe-4{QnYSu2`kMV=42(R z)&Npq$;RC7{;r1lC;ncnz8kNCB}~fwfa2JW+YSjHV4sQr_uOPzBImu|;1*O*r2p*r zJMhQQ>XGIpH$8a79peK_b3RCwLhyId7YOu2|^~{oDD2?a*E|vx=(5PrMKF%Uyxm zjqbq5!ILji%u-i!7N_TCtH@yXzHzf(y33&7h>hL{{UY}COIzhpY9&W3*|-(zM`m); zLB~j$44Y@&2KhgP6FNt;)E?qW!-gjVs4fi|)D$?>VI3ITSr$}RUE_{D||o1;A7ea^OR9VF<7{ot=4#Q#Dp&%p!r$sR2+FgImU zlZy}7LtnA~&C@N;no91AXHP%Bhj@AShzIo9&Dh)hu1VG6Cf}I+j}mTeMF760tJjVv z!?+2<|4e~6_;lB{V3fD|zg-;%^{&@^(s<5Vh{vkW1@{mg!38qwDyY+c^TB?1NL|jX z;siv6k?&kg`X9j@)|%bp{pmR^wV72|7t)CGWk&kn5?y*WmYTpA^ zm*Q4w4dmO9{sZGi!+x9`JZ1>3>l5Z_+HXYPkOj3Q?+ zD(?y%%~H!*ZaSVX>JxXw2Oo8iRN@i3`7z+{?0r8^`5poF{@#d>AB8aQrVkeUF;s^b zn%x~!2BU<&GpMI5E*cr#gZLXBN{SJGU(yC2QxK1$w?3QV0r9YHUQREX5Au_bgcUw9 zVDt}qh<1`UoQ`!=I=VwWIE%sm%O3J>ZSJY_;b0e=J$GM1`NX2{eZYf)H%{w$euIt> zZ%Vd5ALbyDVy(H&o%vJ(e#PDOXawZ9oWqv(H83B0*zvgq#oxxBr)SjAU-BIoSCL<3 z#L+e&`89T#yd7P53w!dq@oF{H+qHQUc*)RLxGr%0azwUd`frXu+&u#G*t6+|b|^l) z>gevG^R_c@-)wBx!uso$4IAGnUd6HWnB|m08Si<0?GN@nsuDl(T(5Gr<`&i)5U}KT zUOe_Zx!@uV=EQuw#Kl1Z{&ukPZi*}$YfpBK%*IkE7IWLu^-oqXzwC)_y8wCNH{>BHH%g@4Z43I6^1VI*^uWHo*^9sWBqvcmVp8HSRCIxBsl`EMRzHzmFe z-8HbUUflchvt^0+0$v(d4e{^2z!)>-m1F)#XWYnx{8s<1p}kt%f^R)^5Bh#Nr)T1? z>qStPnNB$_^nkvE*ZtKE#gp+#_rC3-vRNyeb92$0@KbnLN;v#}{ks#)qfB_m$Eeo2 z36^6SQU!Hb9D+aXyX2!wuV76qyLw4Y!udw*_2(4Cg}}CXFgI;tQF(a_DRDFQuBbgc zANtemylaIzu%~6Tv=;adYnmbIpx~TL_ESr_gG7PHA5!ZOKff(LgB>Qbee?So!@@`f zxBKUp9}knkcx&EATU1X@nqwkF^Zo9-LR)Qm636|{@=mQ-fiulqY3TbqwKrKx&_m|k z>3sXtmF$*swqFxGIjyvrnGAC7>f2JI70dBZ{@*>FWGlBSc5UQgpsU5oW+EkC%~@Sm zH3IedKOaaEnZeuv5L9$@ZnXMNYZv$#MqRZlsew8vM08B$AZz2E9;}Gar6=G|UE7X= z{x~~V-FBqOR&qY~UCTx1POpEQB-e_wS<4p-A>IXZewG^~qPq0g80WW8Oi1KsY%6$#YxUzqizBV0rpvC|?}2dKzqOzC_vARP5KSF8tYdGA=x77Q0SpJHE+G^tVv5;^Tb zyWu{E-TrloUORs{Taz*T_9&3gP5=8in&T(izA8lX)7f$T3S4jtld5MCXg-y9Xnq2k zLk*m`JRlq_XSq$fr00&qf`z~X`=T$C}_g= zIdyylJ~`v9X|K&7Pgb_%!Z}?6rzWfR5lwO%ztn%&FC6mEhP^X~&i{;!(}(=5D9>zK zD9lxDFOA@nWEQsjYe|PT-O#HeA)!B|AO~N+{$zN#M2?3)KL_U$t=#mVe0HOGbmCmo zw`w?t?AE{9kHh!$_j#YpB)3?a6W1o`(kD` zA^YOjGnvU?_r+Zw#c4I*xBabAKB%sokdxJh&g-VwlvB*!^Rt0vsM$uCJpdbqa{J~i>iHs?{c15YgFW_UsYdLAGhe>r|KJa5X<)|)e{dA~`P8EaaMX_g z{=ipF8VunN%%J`o!XH5VAD(NhhxBhSUr}kF+REKLb?)?VDv;d^_ycq<4ETez*_vg{ zbAUg15BVA44-nr7e*kh|^K8;u-6h7ny~XF-E`ubisakwmd56e)y!lR ze6jxZ!GGZo1k74msiFq#2JiQ`8YiM)Vooe7GZdSUmO1y%ds6S$fy@c}*{(#r$ z)S7md@d4^<_8*FTsH+kFpupi@`~jT9GkZVC>ePVCIXdr`1rg8>0RG_KeW3d!cBlsG zJKKNo2Ldc}6v7`YL->QKRpfSThB&%moMss#Z!o>jLzcjHt7c3^eHGvjjyXb{?gRWm z3N?g3cnx#$A^ZWF>(AA-EA1t1_>6VMQEMU2c1`VNPD!`$-zH~axQ4@UR{oV?AtVyCc0 zbxhfnxtf3Q2Y>&PUyuX%gV*b-0KajGlRF>I5B|j;l(XJ9D7*iIKX3qlyHBKlbx1X2 z1OA{RQfBZU`~d^u51`&+yT8i~xl|kUFaBT|1Mmkolc4{=0Ds{A5B|XKE-7Uq{DB+v z!Fz7F!8|_;L-+&KPXhknTn(IKbmS##p?(DXfr0_&kAOdTa2WK?z2Ehv^B&9@zL|*H zW@}cm&)?0F-75%UJ6~L|Z>=nlec{0kz#s6L*FImNsz@uh2U7p@$FgzMo&LoiF#dn| z1LuG62b>}N!N`B`2hO7l4cWO_dkEBbA^gF})OW0ok_FT9L9Tw9RBHwACbsL6UHyHq z?@IsPZ#XZWd9?a5-x=n`fIsM}0o*`a-h^5A3yg;F2loq1_H->;@HflVNxDG@NG|dadz#5mN)_ zVMF)>LE{kq02f#Q{=oNVG{1Tpb?Om1*IkfD%>#SGIDkLU0*;ADZ}3F;kA2iNz#q(D zYz6#5r+B&7tBFQ5bpDL+2Oml~%+mgzdtMa?e_*xuD4biLN`3nCd-(3@w9>8lRL&6o z0G*QyBWl}G9q`j0J7E`P>6McU_=7^gA5aK?U^s+7$p06Afbbpj0DmANgV;Kk552nd z)p)K@PkSHw&8fd$E1s3Q0`LcSG+OaYFX{-wA7uTDKM?$jKY)G~@CT@#`y8zs!XF6# z8~y<0IfOr`63=AEKDY+Bq^y7O2Z8JiX%mL<2Oe;KHs|1x#7H>TTu8F2lc@ww%z;tMB4oq80Dn;LAX(`Z z@?%OI>VFabV0Xnzc0|D2MG>+fwi@sUD>-U`Y)AGKG$%Yc|LGu{2mFISkR2MrAB02P zzx+jg(jDH9nRf5L@CRsK*ira=vm^BJ+W~)2h42S1WwljM*M_}x&qH&cfsI!t--kI# z$1(YQRB!)1*xG))Vmo$j!j=ERAG`;D{K-Y``QPvdY=l3EkO?rma^2epV5bOwpiBP` z{ven$SyFmHE4E-D{DE`fogw@|-IEaZ5dJ{cigN&e@Ss43y*_Jucz4Aee0XwEPdA)L z+5-N-T_R(tr#vFNVeT2#jqnF65&obQ@CQtUKgcg&GL#){ckLzbToC>MuB=1nFBZE) z-ni-!xYsQ87S>X7oDOpYMik%=w7~b5t}WwHz5BiX^imAgNpAYsNs&z$!XLaZ$mSgF zuP+Q0EMqnR{$LEmA??b&g-5A_L->Pxa88Br2c2Z26T%-P%gR{|0UH4)bkNoW3KoQu zK=^|cS*$I>AB4-83}<_#8sYMmDt|tuhW^~heoAWu=mX&o;Jldq5B?wl;SWsaXcE{h zcg_R;z|aff59Vk%41_;`b1}9mu`**M;3g3M0Oz#(79IJ#q$ae zIIllfmZ?MM!hk;*FFO@_D)##KdJk*K(7yiJ|E2nPJf^Mh}5&mEn$hmB6XYLWon(fc+82|Xm zKllR&h@X{RUk39igCYFETBy4Le;^;D$zt9KK36p1Y0eP-z!}aR-%YE7b0Whb`~jRV z58)5q1KtSX547T8j1f6U?BHC2Q}xUyR4YD&iDt(NAbxRfOs3l4ynr3LB7EI_n5!cE zK^6uT$?5=wn#}wMf1nBY7k|JX!XI>s|HU7`etCUcA9q4MXIpY-XfFJ}ID7kmnDX~; z{4^eROqjG-eD-04)WlRQi`^x8SjM7aMAm9cHq)v!thN)4nPF?1l1w%k8Pk-RGf#<> zNoDPNSZsL;pQvnh$)uS?qSXAZ>+re1-|z3f|G97fyq$C257+y8eqZO+nTZpfPvo)% zYo8=U76UFT@P`dg$HYTi{=xQl51!}W;t%EpMgabx9nLWi0e>Ku{mmct>B;$1s$_sa zh=aPPG%#`Q(*0ZD`f#Zp@DXqE2M+Mdt5@F(8FnfX@CW-^hvbZ>@dsFcA5eB1>b`IB z2Y9}&cRy$<0lR*SKS-tV2kuqy9`ez6z#n+7Fe(!Pf8diG(ecr7^!JEASPF64{y3IX zcl#~=U^3L})bMx1t;+r@uDv;~t1(4Io>_Mo@SER9{ss7h_`-AV@COUm_5l9C7w}^S z>Y0fL@8MgY-kObgW5gdod|X>W;}8C(@dueu_e1;v)`54XAN%uJ<@fNO!W=ESuwmB1 z$29&R5by_C?WKS7FGF3)|CIMuz#lB#3;77(54`t6{8!WX19<;;gnel*)HO6|dBJwb zpAmn6xCiga-ZTG$KQKd`))*Nu?$&MDTl_)QKOyrXL^ly1_7;DjrSS))HGk9i1GiI* zS%5$I%(qa=cbb*Iw42@o{DG*xxby-a@dx;RDW`1B{;YTS1Ag*9A&5V~^Yz5ZjyQM^ z5Lw-q7yvk{Fo6N_2hov6z#o`R8`lEvDy|b5vkrSXk`x!aZvzng8C8d|UR_=~;1w6IOgUY2h;Tvxq-vgZKKcMosk&GM)1V z{K1=ch-+iVo$%uwKIe`2154qCS(ZJ6pAH86Kzz7ya7XEYC4fITa1HXK$v^YP?Z5C< zuW58MC``t13NpO(RSrt!1Zj=iS2V-bHa8P0cz zKM1MX%^xi(+y?ryB9;0B@CV~)`~l*S@@f1*3ynX(y4xpvY5V~n@dtSRMf`z#75LR( z5Pz^C5Z+jh=xmH$i}-_Bs1LP$da7y*jJY z7#uTe+u?majX!|6j`)LGsK@+R*6Vp4@F9pl@CE$bZNML3T{S+)^}pc{{t2n#eZ4xX zaC~51Uj9WP;KmVu0JzPyO96i{6>y#`st51~=V<&vZRziUpDQ94o8I9Mu0fsUE&ia{ zAMgiIzj<%PTl~QQ#2>(WqVxRdlcSGZgL+gB;14>Wu2cD$NdW!=@drEi0{)-~@CS%T z$j|=tZ(h!($oz(dZ6?6G>)SMGe=5F;{Fj$3bW-wf{_}F(N)Qh@(RbqTj!HOpzCLs) z4&u;-FY2#D9>+)gK|H)q`(`!Sr>t49_SDYqY^>8HhV=pd!1I6L4;1h42kuoG8h?Pe zu!ZmKeS~-jz#m-1_v$`7>+i-x9q|vqAEe91(f9+zFLa0kcW${be-`2oj)PrVj`mie zpU!C1zsC1Zh(Eym{eVsib^i13@CO@d{6R6`m7DB4zkzzwim=;{XVt=csM3L#tbjwE zh4_QzkT1rrKJ#^dsEFnkrBO?gqXIYm%>GvfoDYvQhJ83BXXD!Z@T?nIP>(|VK?&rY z$77sLen8iq?E=wt@OvKs{s7MdD#f3zb+>h*^$mZ`sZkAF@hE%@i!Wo$Lj1w|kY^|X zf8Y!82=ND)zg7>M`E6Wb^x7zgc?WNVpkMoa+^-qS3I{CVWkvvQ?JfSG9pvAU zq&8sv5%C94p&pid2Ji>Uep3*C&`~;J1>z5`LSBye13X9M1CSi+vjg7Z4k`i3>l+r0iUVc{l&mu zoV$dzLw$}z{TlHHh;y*Il$|z0p8SZ$AMEBQ<*|rje2)$IgDHRm-sbpncO2;7{_3)# zxWe&EJWZ7;!%i(+8(!85_=BG#S8sf?Dh})k@dwv{zX@0FR~TiUYyWyw=*0I1pZd;| z6ocP-yfcao^~;zc=19OFNCQ3ES5x?&S+G7_>U(&>tjX1P%G#70X#9Z?-dCvWTl@V7 ze{j6;E&hP66aRBT2l%oT1I2_D{FuvMx2d4MaPe{h7xXVREwkMY@1-_f^!yp$pB@cdricSv z%d_=Wy@)^f5b+1VKg1uz!g(#O$*lzFq45Wqg;MYPH2z>W;t%BL2VS*K16-T;*6*TB zq5r`j42E;tY!&5+?N5h_8}aYnzC`>%>Fz+pA8h$w_=9=x@CRD{XMjJzdbn_^buQ+! z>L1UPW1SfA2Y}y>dWSz4xZ)1r4@yku{0*;{E_hZ+;}7aqT!6d`@CVmQ`@h2de)scTkBo|efIl$s;5@Qo=aR=ek)Qg}))aitlJr;Ix?;dZ@6UY+_=EAY5PxtT z>TG~N$S^`3o-BS?8FJyPn3%ko}9UAcmP)}ab()!xn57s-IR^5o_8N?qT&L8jxQ(Bc#H2whoUI+09R1NHJ z>5|&dpzg>={K4RyKVlZ|{{H$q{J{>)&wu*xU-z@X?ht=)1>*NCz#qf`&TS5jKiJJr zZTTT*S>fLyKko+o0sK9}Tl_&J;tz74!g~O~A6O7iM@ftCUWLDBajzH$dGW=V;Cls2 zp24}I;@;24EZ_%)5elUP@&UjfDF1}IG2#ypXZm{Du^v0rA*6fFKa|$I#UIqDj(X>{ z{$W8s^2Myb=Ayh1f3O$w#^h@q@LpRMI1ca!$4zhX2V3C0S@EbI-k<))Zz+286zkQN z8s2`yh3o!`g@Z-u|%i5t%a7Q(v65o3@7!ZG;*#9sdaAg0t_=Cu}*vN#atx=o5i;el|{|b2UuJiu^ zf54WRNrJ(gXD9s>LN0d?TVWO%tj+h}YdEAZ+}z5R)sXV+3qCbcKbel=Q+WS(qnzgp z_jO*jK!>yjzE|I$gKfyZfdcrBhiHbO!W+lU7{qc9g8s{7_}#@C(jo6}#{0#4T$p&x zO*jJIb*do~gwxluVO)t%^%1trBzjLw!1D|dN1j*UC(97|eDDc;-z7to5NNBy`M)L} zYH)q;gV7YyCHD~O{9vBTUSqgh;Cmw7%eTXKb85(+ccw-;;CC1N8OPMVurIsjiIENW zH~bg^-*2yh?_-(ZyBIa(+7<~H$A{ehhKKzY*$h=k=cc^o5e})HBwY3_tRekm5{kk1 zRXE5wZ)%-(lC3EEE)9N9phnbU=*x6SO(Hd)!*P?fNsUx1h2sga%OJL*R>ZxcpnzU! z-b5~Zm!yVl%=6-6o3Jl`6ShUW%&EA)TkYItHtbhE^MMDp>eRx!cA($S-S3qj?7Qmc zy#}C1`XLeEhJ3wJ;)n&h6ww>>x*&x^+TXaN)K9^dg@=a(8gDukZ71%W*za z@>wrjC;YMz=%^*#&v|ID|A1`S#(1Se8n$M~Or&SQx!ejr=yxyj2Ls51WDOmc0rD07 zYX)Oh4e6^$%fo%Mljk_Un8TJ?q{d9IRFuCPBangf269_?uz%^<_p;rP51uYX8XTwB z=qVT{+jcYf7V6y+P)l6j#XBd;F7;D*>o@6<&R6v_QgPpheYg)yB8QY(wHWy+B?f)i z4_oO~m%(h{`z*7^7fA0On*-!h1K%xX!S{k{$QwsJT96KZ&0L@#=yC}Nz*h3&m=K?v zTF1iIW9-s~uDxeap3ZjXRQx{A&FZUr@%hnTbXM%IJUV7y2COe!E!>TK&x)MxkNfbT z!i$1sc4=2{59pagy0urd6#4P|3#Sw1tuyefY?x=y#3{6X9)%C*BHeyO|1a3dYy}mz zY2;h}J%59CryElgjrz4qpC?B-Q(T{;@yZO*`feTY%S3+OaJU-vO}DWKs(tdscAbp2)MA5;KIM0OFPfjCW2;!0p zUY~w`!58J(uwaA;`8db<{0;8YWyY`9u;1u`*`CO+B7PBt`=qqd6t0sSELep4$?_%bkuM9z zq~)P}uDOk$k92wKCZqhME>qoHa2!{!7vg;I6E=jetXBG5Uh$BgkFlEeKgRvkS=`6f zt--&ey(rr9OsLOe#pB)3e~1GE&Y~Z18AbHRexHsQwjK8|I{4stY*!r&g75LMWx;ME z2I2a7tdq1JX6I2}Zop3)x2FQ-v2^=uWPE^JWgp-&&Op>)WL*hNLVUPc0gdjr$t*djotw%_8E;jWu?u zn@kTMcJ>9n1!kasgkRP{)qgbdeg6F5Af$i6S08N0^)wGK9^%gb-8&H_6vfaGID?(Zp99X{ zpLjm@)28-6NcWexEOWqj@0sGv5~Tb0_ZNKN3+w7;@LT+=#}Roq3tf4FAP@N z$=<#;-!0Jg{b_7Ne}ASb48H5cmU*W)6yZ2gN6SW(+fnMRo$V2<4#xY-D|yo}zBK*K z1--8!x1WBkLqC0#`__MlubBY9L*ORkQj{F^fZ~n>KVCzQAEnH~c8ptjH2UMGUs`G~ zzP}(o9*q3sRWNKQugM!V43Mvaq8MP0;Q!RKKu#u+^~Pm6z3*#o1oq<_jx|Nz}qPau4^D&$ettyVRs>F5$jT^-b#n|DXt490uR#W6PGd z`oQ<)z>iJ}gzx=1q$?9rj^VgNe&IK;9VafL<-ELeGklMXExViY_$TDwV6_6)x08p) zGJJ6S)8E1-Vg6$+xhzCGmJqQLjK79)x~(uA2+| z-j9QPKBtzBhc~-k48#3#ZTySq->MV+rlP%{YjTz2JVbza7mmNhv5&TIufzS@ z_f0pU-ZI7tDSRGIWpjXUBF(t;D|jttG9k`^KO5zd3-n5NNm|n}-!nXXT#e&d9@E0W z&PAHlx+-7jFRVMB6QnSc+{`Zy<2qZSEGamCF=4i%{JQK;f7(6@`9)yG3ePcAn6DyJ zlvPqs;rOwXR*mb%2L$#t5E`q52!WAJ&V zJeSU^#_s74`!SQ@(Jb+~mzd&meYAq-$OcI%Ien1asTpzZ|5m` zO#O=e9u^gNp?+)#&xL)~knb7dPN4iYt&Gy6UTH_BQ8@3yqN8A!;Lr0OUqOC&&h?&v z{RsUVoQ;V%xz$xgNnRMY-VDDT$#LceVX zswd-k&Z-en^g4nZh#wHIK3$TB^rf?wOhh{Jq8HQpz#5Udp}!Cu zC|im1EEX(YfbFW$!L%M79690u&U49Y!(fzGZ_1^zIG`)Bw`vE{|Kd9-_>me?lv^yv zd?Y$oNbA{v20cf*_uo$$rH9Fgwh9(6bQfJ?oje5$&0988{sMR&MJQum?M7dH%U4 z+R4F{g`j7(q&1D@LGuLy1^8ofT(kWU72XGzo9jzn+<>-w4#E$FQ?k@Wo+n;nk@cd8m4?bnY=eBvr<*3K`c_9_Zm!YX; zA0b~nsBt0aw>sh;^`aeWN9wgWeo}$!DWsnjlnQ>vPU<8S?ABKiHF6WsX(!**BtOFO zcJp7|IA4B-%8L8>o~r?WoFN+3-%EktWAu9HeHryyuN87>KX4!_&=2GqP@D_=gM4_l>kY1#*adWXl|21odVzbz5uQbRt@6JEr5dX++`qnJeSoj%Y;h3hVg3YM^8zR3$e0Q+Xk zKD)W?E1d84S^uxm-;ANWDeM=pOEU`OC>_%8!@0m0Qu`>{4D*ovQwK9qzEL{&VwBhV z(|tFweRJvw$eAsZjMz95`Ob>=rsMKr`6w~w53E!f=z9(M;@P1eFt7N0u;0HhpI}I2 zV&s=DEKGy#K+^=-?hYpB+PF!FjNT+_=YW5$5j}uUV8IoWGCl84Z3H;+}^< zi*Y%&dG95pQ_Ir(&~2vhQZ39+m$H(t5L|wd^CL!+m(R4E2lBP_58UO57y+;kCR(u0eVh2qvrn z{g*8oCdgrf+;6v3cca|&YwAX${-#-~Y}m>TY2crIm8?`b9? zH`^O7_ToN-`EuZAjr4aH!VUfDQQ;^IY8@gCHxDNPIm(n%GmkzdlmS4YpobHP4u ziuTurg2`Xu^ECM$Z`7OXzhAGzeNLjbTHzY%Npqln;H%V#IkcR;bjdp0k5%Di!*P0< zPKb1jITo=U_wTXBJs6*BbT;^1J%_X~YRy4>{_gxw`s}mP_U56dXnLj&{2ld?94j}^ z1A0k&qw5*WGuA8=2#}tw(}v%|{jUiMUgL}PjM_JFUWfYF@GT8!pW?Ftx(tk8TmhVS zGDKeD0l8=w8gX;3ALyB(z#Hm|4$-9&4Q($T%pYm{YfjpB2>Y?T7;8{(xYlNvY@P`i4&G`(zr|Xa|Z}{>f^p}@As95ArZq+6m_Djm0JR0^b`mK115cfY~ zta_9$=-mXmyPEoU_aq8zw zf!+_|FIk;Zjrs7f{=Y(fwU#We$^!Y>$!1x#5&7;pU;Z(UH|!NApxzbACsTOt?{hjJ zFLy}izWB3=wgXK!_z%#WxxZh-dCuAGbUik}&yB!!SB==9c1YW#SI-iIaew+WF8WJZ zG+!;p`s+iR&4%%T(xrcn@ll(p)8Kye1N5{$Y^1bu@TVd_Vn#07n@$q#iF`}dn6ojC zY>i#_3hz@Md0f079Wt?>9rQ0eDsL0|j}GV4UffUMf#`EUkCX@)Js0h4{;EJh4bYRS z$)j*T*BiR-BflALzxpBFfvaWU_spUXhk6@uUe@^npa=ZSz9_Iyw(O$?pIpcG{RQiW zBOmA9a&f_N{h7l!$PdEBZN44&JtAi`eI9com;@X3pZk=SyOvrnN4W!Dm_DBcE|{N> zd@xi^=|MWq5&?8QXoI~6~4D!L(W^* z4d-X@C)7bN-0#h2^Vi|J^_1oT?n}Qz$HV8`R81JtvE|eIaE^9JU+#GCDz3j;9U-Cj z`w0R1*hVso`*HAG*W>Jo^M&~~F5Zxhek4~QL4PkjD-Lk;g*NZ2yjfVMwNYFRuIs;U z!8Y7~G$Voo&&eor9-KEbMDW8i;D=4{JH))mAfV^gy2?82=Qi!bc3eLoYY<&mUz9pP zg8o&bgL(qY9{}>jct|kSw4LVtk^=F^A~IaUb7l?6*k=>sz6LVXxoEeVRcqFuyyV)j zjdr-d>EKB4d$r&nH?*K%SSSpm`BhPY?*rgGwmWwrZAW{GCg8mCSuBWCc4?*aHxACP z@mR7K`TzTw9xl$M^*L_B#Xjqx0M+tdX*1`&MlnNc*W* zAAW)HNvPYnkqvzGGQB~+NAed&9YDVRNu}Fx9Q7R~MEY_N{L6R9_3LGonL@->QcE2k#1d@8h&@BMpRL5x8n7#h%umVkUxFr zELP*E>QQ1aW|sf+pXprU+7;|M3tjn z?jF4+2lEiEKCfsw>@UB82X+0o=YQnK=uJYfTZ_m?aT)4}V255-$iE=36pSX2KVoLM z8s;N&vNRxkWnul>s7ePVX0l6^0c0>Erq@}Y_9u8;x z*UN!#v(5Kj<9T~S*g=phoO4v;C%rxY@B9Gy;H3%Z#{Dilc$BVhjy*kN9LjZ1*m0u>?G3lbD6`5eAnjc_1JGy2=zJcpZAD6l;$VI<)QGN$HulqbU4}Rc^?Fw8#2|>vt(N>VxG(qN5o2*)0skP#*&+S+mfBDMbN**T z9ILBC>%mcCz9@h#2FzJyn~J`zAJwg1mCGXp|qp0zX22S9FlV z{g0(w_MjfLSPf>3%R;TU9_RaMdT1l=<70B%FS!2LTn3zD;T(6as08WSm@|Al^6Bxg zH1PWnhh@Jn!uE{jy$8sT+Vwlv;`LH*52)ipePW?Zfa?@hw1Qp(zd3Sv4`LD(Q?o`P zpF`u6O>Bs3`3G)vqrcKpi=a+WL(1fv#~{DYnRT?@N~H3?K~8Y~FM+xx{I1Knsnd~; z@iQiaUjh5pr-k7-p<1s&d%PO|)QbCGB^bXN^S^R=^mM!)5j9{p_8YL!Gy5wh@6XRpIN%(7pn zOf7}qUAMhCc3nF#L_JH(o??2mc#73r^vuW=`RdD`tSD_uukL95;JEgWh(%u%zl>AC z@Ac~DW=(-{b%(n@W5rA<9Vn;9be{h{V&@Ni|72-B`Lnzv`e(+g(x%e=Eh|cSiY(W- z2fB!2ZO(|o-S3yOVxHgIX>vEEM#v+qvx~KVN@cZ`azQSileMC%m zjd=ykfBS%Mda)`sj7f}hcQ*}H9Lml-CaUxijj=inaVoCJ|KjoY<5Uwv#!~5;>slX? z{)&w|)x;GwQM0_RYkA6i^_12XrRikj#HEirfDa!|sXbN<`&LA&sd&{u{y2sv;FD>fw_+{)lI^F{<`iq~>Cfg2G*1D(Rl4i#*R@ZjKXc0chjHJ#T0jQoa&DVsr-+M>!1&{V}1U)u7%%Glv8$`&D=w4^0YwL~O$RprAx z(lYNW3gnaQ(l`dnr%RVp{mfW6_e^Hu_KHJ`}^8HM1Ebn1nP4WT9uWlVf8WmiVmYLV(7pQE6UZnqTcnH@zLTl zrwzWSR~<>_E}oJr3UArpv>f)uWi6sm@5UPb@;wgv3vf%2Z{b|qr1=(i(?Gt@F)5XJ zJDr?R=RD$W@)1>k-&;_u`hh&ty}q_sn@;k~J-yepe+VB-yr?+1Kk^yVJhc>lAH1pJ zz;flI#RS1TitBZ7mbDdYS)wtQZ6_0N|Figov#$^BD0W}wK;+BQ3)52LR8JygI(F)H z&_C|HQ$=V$x5rNZ;^q0a^z||8Lr`zo1J(1~P5&&OHGa~bV&Ic}Pi{*V?1T59Dl1E+ zkg|t*9g9;LrHe9LSCpRj4Ds#jvZ5Z8e~?$?3vJ72-}mtMt4bbjZou)^Ezj>xG3g@q zTBy7!z~}n%v}-n>a9_ zWNYQ0>CVxopQ0Vi(f?rsd5G@LI0t%blm@M7oRp=CA!~z;<6oZtXR+Z%p9AOr&a3|N z)KXp8{H7z1i?x>`xMM6RN0EI`FA4h~2cDXpgL*N&m4E3vw8nK$K;P3za@MJh&x`~4 z_YU}^VgEBj6*BNcQjN*x(*gRaIQr|gVwm4+@CP%ppgrp~IjvY5L&mij!4A2iCHc-D zm&1MD6Son_cUj`Yf1#dmr{=zges6cfqaGgk1(jJXE(W^WD^{7XpDxup=H&T_Aypqc zdRCMU;8Q%Fu*2vhdR<;IyTb^-!{{2`ab0T@>RxTPqMq!rS`}L>+tNc*#vIyO3BL=q zZtK6}RQiZGUaQTXJ}`t3fBvuAm4ig<$s3Ia8$ClhZj_Z~!Sj#p#rk1jkNTg>9uHMK zS}d2lPeD0F+6t1cYg5B+^x1TADwgOYUI9=RN-f_3pe> zQ68^)B9wSBe?_^yxWLy zQMsZ&DX1E-czR6?{O;qL&=ugvcL?swd7yVj@%4h`N^Xp{S>}p#MR0FvFkXD}8wd1B z7opRcJyAbQCkN_KZU%kiJlv=2^MrZm2Z#DRwsaUhLm>Q?;C(M*)EV$^WMcH2Y~(|g z`lZLz(kH@vF2y|#d?1dBkG?#A6@Eu@$9>eR=Z;svM^;Rq%@DK<SII811#TDvCQ9sFHb>>k&t;1^BzCU>D+9j``OrY@__j{H5L5%sp4whkj-bez(p-UdI=^vj(USr8v4 zdC8<(E8%xT9WTaX{G|TbyJrf-<1;;{AzHSjcUJVCME~vlSp)RNsWvqvTtz*tzWbo>iU01$sP$8( zP5*yMKM2XYQ7%wPc@OdrF+nV^T&5K0bX1LYh(Z95*`<>e0x_J=ONS`d$qBjLI9L(N zqQt(X(2pP}y1!a1Uyj#Yu6TK6ta3X+aOwL5hk84n#qx#moh%03zmDLleJeLBI|;Q1 zyL7PPTQx4OIk<}R6E_!#XlwkLLJFcBvqh8->^DL~~R8dAm{W z)q7`oWvvm&NWpo-qmA8W?Pk@goYb@oXoLHYm}@p}R)y9Jb|c@*e3(o-thajgs+Bk& z<)MkjeGoG)J;QxRZPEdM&Du*ZA6Xs74DHE~7M|VsweognD-Y&Z$t`6=@VSAyu-S34 zMr&(MZ*UmR+QL<HO3sdJhj%Csq~^+?RdQaeg~6XnyHeU z?&@_IV^zeWxb42Bu`0Q(uamw{Isfeo&spZCb=daPw8H(pDz2@m0p?K=oKjz$|4gbG zo?Ep2{@fwJaYUcvmv?O)jwAN=-EqLa&nB(Px5GL^Or`7aIit$aNk6~ixQEwX=8D5K zy=TWZXR3Tl!*tA4To20Mdfe9{p;xL|>sKnIxHMnhII6mWVE=DY0G9}KcfT<=VZX5< zLEDxp9mY)?S9#*K8)w&Cc4>{4BiHelpLZA^uWPEaYaJ&yZluQJb0Q%4oLS2*-NYZ{ zh5RgV>=ZcQdC|T+3ZFAaxrEU3Fd1+8TEv}z{Pl3BJZo^C+@dV{diBUn*iu=gx<8E$ zqdsSiz+p5QomYvoI6f?B?)%sl%H|Tjpika&1qb4Ro;~*(wk@x$(cb;s*%<`wnG|P( z|jD_EmBsg6=W8wD= zIdZNO?=vWh9+yf{U3*oN6<%EK_PsD(t+6*@KZ4M7XR3Hgb&FnLhWnZ3PI`W?^oriS zs%1)DYHldbBcb~4;JQS&8OAMB5^f{bS+vWPlD=)wf0>f&vx6STWmO!(eyKWz6X%oY z4L}EsljQwUYh0!v z!SvJp1nVk2s+|IC#YX2HTxVAKDR^$y3YPZYj&x9~R%viOhQ#X>-NL~Jt7FEA~y3Mu5y-I=uI0vu~FMIuCoJSxW!^P`2eOwE3er7_GT!M35Y$&p;mjwYUB$XX2*>Kv*Vyq3`1+ukM}5-HXQoRbxUBe|3yln1St z609348@yHw?}LUYu7nV}(9kyNl`w)5Y=brgTEP$*eZ2$vuO%FU(4mT_VN6N@^FD=s zq0S*N9{Qy$Q$7uYd7Z6cLuKf9hsyA|z=ZP?HbEcVIt4xSJO+Ui=!=BsPWnEzt}0f! zG?LjM=!{c74Ix@XsZ7;XSbu%rQYFw~6F6!5D1l)Jy{@2dGPW?!G9|sflkNxeEmh(; zyiczW?GT`^)43exC#X9;vC2#l>|@({5q(3&2)7SgYGDT*Lt^wweO@?)TtmY{vM-nD{4sEv} zn?;=lFG&K_1@8z9tSwN-C7;;n`2~qgPB75f>9oNg4tP}GD@&jzQIa%<0X-sD~m&jJuit5@ZPJ+T5vz`)*O8}oE zaLVcNowXF~r=1i$oXkvsb?;C*aW>Gg&S~*^u5wEjI4JH-CA6X4YNT&_55XnI=SL(r9~ugQaYrs%wi_CfS!b=bi;eqr7_#s3`w>U_PM=@TT8Zvy>6U6+d&SA zB$LW#SVWs+q%LZnAM8)y%9RR!fTGuccypW4qon81~)W`jZjp5fSZM+ffg^x(*lOyz8847NCRtm?{_JJf&Qo zI04!#v4kdD2@?M`z9C!LPL}a9d|+LfFnXN?`RVfHMAa6!&N!O?ZyZOA%7u@Y)RMZt z8%=n9%)`YeMByhp%Jt31d7ikIHTlUB6srpJbO{Q7nK*pi>MhXE+8Xx;$8n20>u~;Z z(~Xi7%3xW;JrnWWwvmM75fvM7{yJMpUgnH=&@f;W9ft!s$fN#^Ij&(+11r$D9Bwlh|A!>eOcex z#7DSqHNiQA*Z=f(ro%Wz8^MW~R_HHt@@|KMUWuru50bK#u`yKNC?Ay9tbQ>;INzCw zx!qg9@1An)SK=p|6iK#Q&!GK{*rs8j9#I8P#vnbGy`@g1!&-H3c8PLtj7Gz`@j|;r zvGd3X_DxlSV%&nD53`kv`7OfmaMag+gO0kOT#K&}Ui*RGJC7|tqx_S?8Ju94S9I|>B+Nlvtd7-cba}Y z0rRKaX)H(iT?}l#g>BxvnmUv#F-b$~4~H5 z>e>OPnJrg&}A0xkm z!yb>tcJ#4bc_`-}n}^b_NJZzHkG-ck){b{BbSWS=qW5>HXZ3$w&Zy!Q}KG zcmnKT?Sj8r&~B?r$ALd^lO+nN%SeYgmy?O|XHm8$l#gdsSsh+S2wSLIDmU4V9X{zN zKrjEv*X6r`K8>Juk3c%ksV{$m_n$bMZXo@G-ec=tXcH7KyDGkXp)FDVSynY2?c1Dq z|30qQnZxS$LhCORblI+;{sj_FIgT?YT4x~L#cf41FdoR^Q$-2j|DEYwbiD1{&M}~W z_4S`rhWos~Jz+|=(k${c=ncaQH^+EZ^-v`sS7vs8D(!bFR)pX_2lS6GNWi>F!io2n z(ejUQgSKD4UEohQ$GBR0*5kZEjfb5WUl}rHWG%GJNz`}kq*-*9HCKxAe%D*p{WpT+HM0KY@F6wASD){-pZ z<9w8Vp+KI8*J|qIHI&Dd#%*O7AAX+bJssuOS6}`Gwv_M3AEO^wwSb61zIYwo)rxj2 zPrS1Y;)in2aKM4V^Sqo?wLe^|3#l~RPjUXsBy3HdpG-hK$}ArQaoIs8Mx>Frepr!w z6zYq*`gPk0RjtT(&M61lJ-52B`-L`G_8=)C0r{-!4u3$;BTR<);+9A7l{EKfb73in5dALOE4GQ5bpkmrdWn9X44UV-5Lg&^HPZ?3^$AqPjk za>930MTRilW0oUR)EXxL?Off`7?(GSiC zOkH`38^i%ZZ0>T9upSJk1^;&T$FMaP(cnl%_quhp(Ku(TFpD? zaTIkA^_5B4LmfaTp);(*Yc7vmkNFU{x(@meUdv=T{=t0Cr@CH%_th4``e50l7{bTW z>jwEHF+JlS9H&;>NQ}R7=Z+ozz~2JHXLqo_M|pH#g5pXTi>m$wuO)I}B>+@@7eBFs^SE?7;i1s_HKEv$}6@ zL%doV$+FmXVm?e%pg&$3Q(nEpS&RD$YWD&(IiH|(UG37%F_zt@*M$I|Cs@K7vz1F@ z9u!-;&=0uCodU>bMO+KKz<}rCi`92C;|EI#hG^m6bHxk)^X_Pci?_4Jag``_lFpI3_C&I{|}ctJPdiLk9A2s6wh=oY^npF$DC zkPbL-&~ykW$Y1fAU|gJO0UV(~K#vyyJ+Pj$>+O84oo`!vr3i`pia=xRE0rSg4oKRqd>@-Z!z=2lCcOFB$`{1?fQ`BPk>yMCa zU2-@_2>HEE3Cmw54_UWE0(osKR0ZYfm`~RU#0)n$zZwLo^n8?n3+p_dIr~l(5i4TO zBAksnyr$X=96%9;^a*%O)O(A7>xJ}KfPc`2^au#GJS0<8#1sgiAJC~rdPBMd^{gq1 z$1|OGsvz%^hin6R03G~o!2e*yMgP!V2DC-~>w&L+z^6OVLbmMR3w)*J3;etW>+eYU z7UF7am|pMgk^p||7sEpy<~Mop^R2hQZ@uo@y94uGL%M4U`ZY>oEXDY?Hf>Zn_!Cix zwHW*#{GEcB+6{hLBvq+@$x@++K2Z_^mi@5k-Mcy1AWtD*X#p4V|bxZo#c z0?X|W@p+Sl2l3M_xwAF)fnTz};$YsUO?W-Qf>B*N<{`VH&*Y97XK9Z^o7 zzm8PSgm_p>-u~8=y9MmG{(&6MCuWgT6A4)m#8-{)5TsjA!Fd(`xL?tP*I~T94~7?RQT$ZXEl0lYV)+qxKWVCY5auK6llcye`{4nY|1ApC8(jNg zd^v2;pcLMalnPi)U zbXDjNi!okjk0|Po=d-fU4y!S5{Ac?fp})*Qo=>;CV%&ClYGz^nsPW#Ehx*o7nOTK- ziM6j)7orGO%sh+_V}#x&?8aJ<(!8KDNj0jKlfUK4-{L zf6Uf<@ff#1mq?D_ID5sQv;@%i{A;--NcYDd-9L=2?{hum%Lxh=hnS1}ebPHsgM11O z+aO0e!;24rKdB{SZ6`m1c|o-t_z&k@4~h7N%3sk` zZ4;wC3Rlj6zYBPwh1W$zm@lVSo-$7>Y$qFE*;-LfVof5PzbqnkI#rM70gYT5{X%OI z7W4zIRD9@p(ua3y}l0TTkUvSd=uDqCuPQ;ChT=aUj|uSN_{(>|fj3C%}Hq zi8c$;_586D&Z+I>9zt^YCR`UZ+aMpzR#MRi1ZbZC1HF#t#Q{YTQ2%k0xtw!9g?1Ju zT(}A4+bD8PMf*&rT0GG&e!FAGP@J!~Kq^MLHbrwFo;k=TZEX5nULD$a8RLVssnv+j zm8KoAzFBl{hol(i-!6z>jegCQ{W&e?mu}Wp?3Z`y_G;9di3Er3fXG2{^*Px8?pEqE z@LRIPOOesY$Mp>jzoI|sv{bcYe8^x}!Ozx``zM^G>uKCZo8bhU`zV{Y573+Z`4CqI z%5(BgM?IXUrKy)APruND9`4Stya2m(HgmnF6(%SxA0I)!D*Vbk<1sFrYfI@we*XKR zeaZ_hJGtmwcprWK;|B)#LmhCCt2dnMHpg5($K8eXSBbX=)MF;Cl~AceZo<525|ak> zTv4tm&&PRrV@|}O|F*5I^uc{RNc?6i<^{z!>pgFQ|BkcjApQr-e!2VL9BtoxR`D(9 z@3U>pDdhXTW;K0|N+N6pXeU%+1mpqjWVl2^WGgHrd^eVb{Y;b;_UR`hBhK}Jx0Ol; zeaysl4hrui()kF>cYQY8my210kgj#kClAq&>We+|LKH!Y=g+Us!Fhdm_Ch>??*=?f zOhEbiTAsxDLwrhGrOZS7^{ABmh4K+*ee@0Lhv`rW>=*pLrvdzmo9uqdT8NXiqL1hJ zkS9P6?N2+_!g{hh&X)xGzdBRrUGzg$Eh|AUYRQKMr?LJdD&h{AfOfA5=jcuXAFG^i zAYRmp?uQ*I#s1@1X;_bui8aS2;ClJxHaMr&lDQoXoj6X%F`Pnr-yb{~>P7ytyZv5F zDgioftoH%C%Zzcxh^vAXU&jz>6y!Z{-lv9kzJU1(eOaf`{yQ#Ox?X6r6)pDG!Jrq4 zJKOr&k$#IMhR9Yj1Nn*3;R)#fUHXClhq}c$E)(^{+h8?YL^oBd;l2C|?Fr@L_ad67 zfk&u#xU(1LQ+8>a>QVmR?s-7(-!D2~29B$4dvXQ+LNDtV4(i3JBH|q0_i;2?im+PKC0VIJPTF<>PpRAY~70zrPssa~ECpMqsOgy}4Pf`+XaDIFW4vnZ?9refGL&w=zaQ3p^>qDqhzrRNOp;8L z*R@&IcaZ+OC2fV+Rx@7r;QaS&cSBHK%J}jz7%z_TBxXObCuaNVM2vTF$8yy0K0`Fw zlsOKcn|BsW+7I*$7*JYP0`>m#ygs@P#!ANeH++YL$b@_e>Z^}C!H?FGGiI)WeaFT~ z)IX>bU|dkQrvv@R@f)NF?IQ2&ye{;MUe@yWk&i5bvl`_-(b5V(5t*$Nc2Ev%d*8DH zADMv<&ISZv9T3Ftf^w+jdb?&TZ>g9?yM!pOKd5qVY(tte&*1(3{U>a|@wSv6HQra- z+WK%^&21}P-(*O>!FrQIU98d!2MKgr>si=;O#>V19|?+e$-jV~!g{M*f!A$^eBU5H z?<94AJ;Qn6fQE4k_-GdFn3n*2-1*Hd`h39CJE3JKv&;-SPV<;U-B4e6byelr%3;aP zY6;Xoz>aR;;-I}-OhdW*m0J|XE>9!Ijh>y&5|~%vJB>9P>oQ(d2bjnw4}Pi`*QfO3 zq}XgNoKEQ6F?wR5-r>STksKSP0CopWFa-A9#N-DCcAJGXwP-4j>(^AtCj+ z?hI}{T(^^}*E?T=9~ZHHrofNZlHbHfvQY0TTVtS}yE$gI(`3NBo>Ds@s#-)n#oas6 z|E}knNjw+KQ#t2geP;kW5A@J2dDCAh5C>~T)mIo$m#!5Nnpc4!Uy<+Q6o@Y1ceW4Z zBL6%-E~tK?1;1Hg1^?wI8`aH?hwEfHgNi}>b$-^>JIw@~_-g0&6Dm6XgZ!C+gS=h( zp4JgpkrxA?s8x1_u1*Bdq;pN09K z;arm){g3qM(Ze8jg{vW^4E0ZX%+Q7N-___maGbnLLt>n=aUBhK{miy37N7fU+jbW0 zRv8t}1@GlAYbH9^E{oZs5D0KpsOK=!7@_lefFU$Bm=+gx`F>e!!}zfi0;1;>1j@C z>Pvui;t!00_i?qPkLN%*cf#N4rKUo@=O#On;`G7xcGDctOSfckKP~7T#D^B480~pw zmD7fDEz4v_J4LQHS`3$hieQnv*27D1HY~gd96j%SDTcMaqz~rZA}<| zzrE+n3sGdmq?yf77px_J+H8wNz2#Q+9?8b{(()(rVE8+#OY?r@LA(@6ZQl+>{pC&W z{Ri{4D6dIX$j=CZNF81X=USaE@umvuQ3SV_=DV?C0@{5*KzKCffd-#`F1U_KOwf7a z^SSXsm?!r3QuH~Zp|W=GG*g1YU~nu!c@MgoAC7+BZ#VmNg5rg$aCOvxTM*wKsz0PK zF7#WJ-G=MuQ$Cf@$}Y?AehYpznY*fQDBiDFVrxf#!ti8<;B#9m_XDKgcMwyI{`^-Z zVMP1vZI*jrzIB^PXt2KNb%05rzMnDdQ11u-V2iG@q1{nE>@KXo<_NqOp?urYK7jnz zO*YPYs_CXGMDgQzbqucCT|LJ39M;Kwd8-WlyPP`Fa|`aXkMwk5-csK60_v>63Pxst z9R0?9%36i{U6&I=&%3>xQ~Uz@4M|t`-9*195hvmJ+ScQ}Xs6tJeQKmz&3nA&rfRRq z!eW8dKs}q`x_KJNp{uWY8q#GvoB{gnmi%p%0ra9)w4=M_TeSNRUP+;z78~;-ZeB6w zkJE-&MqpewBqUaVe*pY|M2~tXZF}sAb<6@=VKwU0khX59_d$I4!g?OpsajM$1ofbA znjGX0e|PjCJ_+P6GDv;{|6vxDXHACl3fMo?f0==9vQ;ZruEhS9Axc~fDl5Vuno(W@|Xg^)$6|c}=^p&6b z67}J6N#dti$1dWkIp`01Sk^YQ$46BD+2MsDii+}xZFp{p=(s7}_Tcd&a zM{1Hq&G z_01WhA5ppfzk zpc;k$XxB__PR-|Y%0k-=Jg2cu@BWB%^w?=4T^ZJy15S~jWs=39N4$T!Ku`aB^WUO@@^%&u_^ zb+i9B2SfYud(oEGnXp^1Py6oNZIlBS#@Rdhy|>!pBiQe!r|*u^uF4NCgWcwjctPb5 z;Xj(`xxqM{5QJ+k87x)kk9a}pf5B&{|i)M&&>!S~{UHq=&`ml!dwcm2BU-U!# z3iM*UA^7LGMYrRQ=--BInGi(&wVumP+Jo=CS>-qT(!mvKSC-582=X{bD1`ng4BBVPA^`k(nf zFE7HovF|73-~A19&Hpov`0#3dDUo^G;F)_v2#1zCePL|c}*sLgmp+gIZ3BKVLqt-a!k7BL`h7{ zD$!28S{nM^qoatU+640>$Tg{MD2{j>YU+MMIxyzjWB9#{bm1qCQ)-O8$yPq`mj$F7 z+ppD#clBu(9l@7~j=Vcw9Z zs(>dKG~XZGF07~AGc4I|0-e}3XVRso36I%_J%Pk`-u-ikE7>*2Z={7%j)Zl8at8fo zyPrD=I-QtZ8x&GQf5{vqSMqyq9C60}z0MrDQ3v_Ibn)WFv;)1idqJ<5)VkU)0?8NQ z^3HV`tUKn4RZtEoUXUH+dm9SdT8ZE0jKc$@>)raG*LeRMz2geu8Wte2OsPagkmF|9 zeUS!BZO6ximylp9pg#Du!wvfi{);uqLVF~!-#d9qrCn1xB5bGLX;A+91pBk(f2(5q zr1+n(t5>KSdd%hw*vD8W5==a8Dw~OTZ=bejsSfd0WV9--)|%8AMWuU{h_fJmKG!FN ze!S|-H>)Whug$eW4*F;B?;Oh^9k{>Eh5bIu@Q-Kn5Kq8Ludl9%a&PI<015d!ch|Wy zl)FaMt0&%d&Y=X_^TEe&gz)=+x%ZtISIy}(+4%gT0j}>!SDSk_0e_K2y(epe>+rrJ zC?DgsYxLil$5Wq%`OWYnT!#0z+6c#l9BUoY+4V8$#@16^e~IvuIl)JW2j0f|0Py*jH-`|P@i#0@&sxjz z%xtnEKH_f(o&7?<9<;FyWAMun|1B<*vrX3bLVr3m2c6}EwA-uqzVaIBqjFJaA?;+N z=c?*^^xv}c-filM4?~Q?9>8s3!O3Rg_1rW$_&?HcCSQ0^hv%9+V||n_FWQ%bUt!M| zdP9Ff&rQe57~pSHe@_|l<4H;)JhHduYBC6yRW}OcCvg4BOH%RqI7@)oj+;2w#5ihJ z^@X#1-{Sex5#n{YHrz&iy2WnQ(azpCWB3yF_aHWCP%jIc^w!DXtCUT*VQ;i+LkhhX z*P%UrG9r)sx~)yAz`Tl1zCKz@xu3Q4!i;uksHvrUQ@I|bz+trUz2^dbd`bU0V|V!_ z@O^4d>S_9aQZFCK_o$-G%>An<$L@aI3A@>@895R$gLd7zJJ;bqv}^5eKi^8elMA1V ze%qq}scTe(_hjv+#FJnv$_5*i$P~JZ&=| z?{;dsRCenu(00t@~TY5bTRDx1Mxi6mm8aPPONA zZ&2P^Jqbg^@0n%mfDPoeBnrsOC`0)2^xu*m%C+AXkUnm^s~yaD{OV){^x~e9r~b3v z2L4K}_s{E~UF=GzLOcuW1x7Q$|KRh=ngHslmkwHW^pnh?&K$x)X;pkl`swM(ImYLt zUG5`W-GvAy;yDR8eS2wJH6046M>tJ@O=PJX4 zcpVb_%nq(&Tx{t>eja|aR0{hD{U;d5^Su#U59E1NQGMx?0@7F3$D{qs+rC*)+r@RV zLV-M#_l3qCpijir^BVvIp498n{Ak%VeZq_kM@9`WUhqk zbzFb#EBqYSZC)qlHBJweBOZdhn#31=Xr$rjqf<@9?>1%qRpNiiM%o3D2I=(B{URK? zUJNJvEw$ka^0#j58_55F4&3G|gkSIe-Sjgwub6c=GJsE6WtF^Nd+DI6EE)1Oqv+VN z4~L1j*hd37(U|uyn_b-*MJrM|1_T}B$`x9(G75NCEm;Ek2wvjj=i_q%-%GgOW4(lR zLmqdpsCPo;Uhw1n4M{o$;WrNoso&@+VE*rcCXk{E$m)&D2+Q zDEP`ar+k6+ZMNN~s=G*^GS~HTwqI^KCFW%(1m3=fY==f^ZkgXi?w{;%%sZG?DttVT(XKpV?idiGYto^E9cel<;!x5Rl($*Jhrv_EFF0{qt@M=cRJJ4*ie3ov#w|7X2-}0lUSn zk>=EO(iRGk%c0K0d)JAwV^ zelqT~Yl=(WUljwsj4HA|=kqG%nn}^wM|$YK?!j29cas18{A|dtL>g9Q$EFfa4ZW_3#N*@6eG{mM`o8IIrT^iwB%6sB-@Y{!mvG&s z9R|Ej>bK4LOX1(Cdm7q0N#9LhHy@r|*{;cYn6Q@iQAAkGzbW^tR@EuVcl%ez4x^uJ z)16BF8wC@KQ zrD7bR!zDjMzE4H`Khm&tiBFynFU&&7|a%anqw>%`ANhN z3w%h&b8L;*DQ7AK?HKi7;)ZJYg^1tg2m!pG^Vw6;K8hK+O#9ie)*?O5_WH3#*B-Q| z=G|IF{2guROge%2jb#r$iGm&%bc@esAif!3X?;MxwJi6X;Bz^5hRlq2*)mCmS?m6N&Kh0K3`7! zds5GK5x(tr?sap1moC4IxC-W_42HkfuRY$M;iTO!t;pR*d3QRs_ShxZ;o4hqn>dbf zg=Q=4a`oZa0dr>quh)jG$g9jU_~Z$4!trT(XGb06o8Y!yi^BRE^EI=A_||6Y>d&9Re;d6vXpV-Rq_l0!!Z&k#Xn^AQ5 z#_c@Hfyef7c^O5Q4zB-A)rXf3q8_gx#F6h$J4SN}&tA))F@KZVY)wWSr(av4Y;7UG zs)7$0DNkbB&wfEUQyX~Omv&~1AeGQw^fRYg>7RXC8{%d^jeV`>nRhwzVaO6b@5$)7 z5512QDoYS2h$?#NMP~r{eX>HAL_4Z5EU=XNDZy+-94n(}?0(xu%7bgZB{&` z{e5!#8Q8_eC7-Xl@;sl*z1pm#Jj?0peTn)k?Ju=1ah-!zzl6V^8>?@oUKCsz8Kk>A zA%~D(K;HBGa?u}`>r9Q`V;)ZvJ!6EMM<%^NIT0i4%9;W@U`fVh>Tj+7->%nrzr5cE z>%+1CL`c???-iz6Bj9Z4xe%U3{N6BSt%iKk+&n5(?7{eJA_Sorc%V{EdiKJO3LjqQ-mt)*DK6Rf z*dpeevK|Ve9kA_I))~U7E!8!``8Eu%gdWGb^&lne1;EcQ5bFi7QZb@Vv}=RScE(>z zVn0}q^*|X#*PHE0qz{Ee2Rju0#9MO24T?+RR$7yZ@2rorgK4icwmk?So?>F!#v@-< zl9=21KG#P`C9YKR-S;z-?L?OGr2V{b!F8~WNer_)~%EWWTG zahS&1ipN9bb~R!WOnVPgPq&HSa70bc4eH(xM~XC%lyNgUc%|ACl%`r0H=YiT$fnX z@u$RGuIqdFl8*?tK}W=VUQ1gC%(Odt^h-adoU_jnAh#zO=5<)-5nmGf^=$He=%(t@ zPY{={`uwPZ^RO6ef217PEm4G!9u&{|fX@-B7&l`I@K`jj@h<%i=$$SV_43VA{ch%W zMlHK)D7PJsKvA#D&Sim45myW?tim`;{`_Tg8R_8EP|`}y!`IhV&9*oD9Q4O5gJ)9? z}^}Z|v`1NPERTd`*!7d^Jwpg8X5-cKosU ze9HHcz)Dal_AM#OME@$gxpS0q&*JO*1@(?an;p-2clD|_QxBeXl*jXVh51FqIbi3? zI}gz>9V}H}AfMaRI}t~WGAzxP?;t#mDD_k67v|n*K}Uf< zY3m!a5dj_V+pT3@`TNc9iT;cyqgxJnS`-pSM&jurof{0>Xofv;9>+Dt3l|&b%7KHZFUb2|Y*!P6>e0tdn8m#MS?R3K)LYz@( z4P*apV}Ul{zbN9(a8VvPH{bG`Qn|9oB|X3K(!rhTtHDy(Zy80NqSpqPcT5tNEn&Pt z*;lrZ@i$?HT}S-RJ)I}MpEx*UjN`eJKNtGxlcKra7qcj@7VbA^0e;y}TeIL-ARpC` zhIK0G>hKx%xwKn%ZM6?kuP>b8>ZM;AEHBtjew`3Jp@sY!F3-OE6Y%T^z5N66d*7Mc zLHtN3evY_NyXFg{A(6-4S#yT` z?DI|Dm+U8ax1|;G)DSRIDayN&CW(afnYeYdpK{iDW2mnV<5R?%&Bf6BrkDLf#_|+W8PI>;;<>_F|MlQJ42WjH@kj)CLld zM!hAI>yL%v_f>qqbIYo3_NVvTsV6=bK5?7*d$ifOjDD)IybbY(Nd}wo?W4rIao0)g z^Eh#^EXZ<%aloOrW-$(bMKILz~$ z^))6EuKk$;{K0lj;e{;Z-TSoNUzTHiGVK4<#fvk*U)z1AO~<@T0(!ld@x32sgia@( zGqZDH2S*nf%&7;cr<9Ak;g=$xT2UK;=Zc0b0{YJ=@se1eSH3@J+^!FxJPKEudWi?U zwI@y-&y>=29Pf37+)lYM;x=vMJd~G5&-1#~b?;TuiL!Qvncw#~UC$F-3wEN{!#*ut#^m>0oM$wcI%PQKB(gGvmhIY;M za^Y+GfAXp}=N`aw&c22+($8Fv?g8U1N~^V;c*}PXu8;?H*RMieYs}`2WhOQ3(-_M; zyuYw726TbAp=Gm}f3;d41ODxrc;6NW`S-MN8F}J#_1jsUSGk|3r)~s(H{#+udqw!0M;VLWnUEBtKWUucEBhV{#4thXb4Yz0`ih4m;06VXTPNk46rwF-VYEXU(2%nSL6iN3dJpS;%UOy{`PucZ8oCB@ z0Q=u0*6Y+yf^aX4@H;4xVm(WQay;>4i%tUG znsVoaw`XG?N8(tQ=%>ATFgTNZms~s~;@36$7ct&+%`qc_bP!nC3Oa+nZOi?f`vQvh zPL+@j$`9^7N4vZzUjaJ`^d^=1^kE%Jab@WN{(e&U*?+Be*u3`uAAcBGrzQNVss$ta zQ|#+~2)Gz}%x&PGi&{TfKIAs?Hd5n0!dGXOzsGoh{v%)5IsS&Pw{Jo`X_leknAMm1 zBk|!pF@CKG?tpy=x#+5ZU5xdWaSHeW{@Hr{+S9b_1|I*qkKgn5tAn+U^(V$O%tOro zLjSwt#RcZ_D|j1j*{ zUl$fI?sm4bx1}AKQI$%m033?S;_{QRUqzc=Btcxxq4AQ+@V(qt8cI3P zu+WTrR#efJzy&XmPycST<#WDco*Utmo66%oYTm!l5#P!EJsDG9tRj7^yx$6ZwQC=^ zWqS6z*>Oz#ev6|P{wmgs*kyCr=1ZC$b6w(SaBT(PYDoNJVlMeMS=HMc3IAQ_P*R>7 zIul=R<9F0K`2zEa3+4=52HvwDeq+odeKh^B-N<$JE?fH@>TRC`F;y71LH$-z59vb) zhpQs{rggQ5RvSLh^3QqsZBmJ#F?;AdGp zUy=Xi!IhPyw+AW@^eFrSd6k^wzEPjKnQ)kAO-<*%B;i~R_7kV8JN9>Bf3jUObNH+8 zBO(8~I}>m5Jul-tab1qwdM25C?kH@AU*K=}WBBL;#urzWmQEQTA8FVTQ&~!S&`a{w zq`P6k2l^R&e^2M6UeMLpT%^mVs$9Sw@wp?lZjR3_q>$gWLz!Jm2*(Nb8p{;W*F#kn z_ER`C)5EHfH^zQ$j}7}KkPm9y*UINMb-a0#bX@85NOphqJ?xi(s_HK0tKN8NVjb^a%T&Bb zx-T@wU8H=>INSmKCKL5P<>U74)1_kGL6Q(h`7D#P)RX_4j;~9@ceACEv2~Q|2TDt2 z%qO_|hdcc-|8uhAGU7R0_uX7vb~#(m29W!I{3t(KMz!#cxysJ?q6 z#Dj8Tn2qBx=6N7j5YIW{TS-23oX<@pJqRj`7V$>Sh0r)N^;3?Jm(To6sPu(u;=?@T zzD#`Wy!;vBD1BN*UXB~r+0Xm>PNaObDSz8XKW5v&H)G5zKg{ZPouQR1=~)zKKUUeqoDu!&=Vg*n}L|Y5v&-eSYwxqYTXjW1BfHOP+2w`F>iVS0U+Q*PX|rePJK# zgg)uh)(LSx6m55Zn*ul*f~8vMY2*V0*;Lxm53^Fu#AC4ErNfj9=L#nd&`ueR^*9*! z!SavO#CwgG7v`6yjvdO*%z!)yi)ev;h4(HdgHFLO0n;rUZ_lHzdN^M}>B0VH>B9tf~qORf2Hi%CI_wyR5jRl*RFj>DO*CiZ;rb? zOgPO6Kj#8I3|}brZ}ZQNEDBK-SAm-}=2o`{c9cfeJ>nvqzR+*H3M$d`yQ-hEpOtHL z?~<-uazUgkp|CHP{I3*pW1_H*R%bRMt_#2VN%uDTkMqI{vCpzkJ6q{Hfp8jdSB&wy zS1Y;^H;goF?{S?aJ)|Zie9CzTOqJdyystfULe82r)`eE+dAsJLIoCux&viW*eqOp} zVz}VL??^tVcn}SG)mgt((%(=Ns-XA!v}eXot7pIAzGlj$?6$x$ao+ZxZx8YQ>_4q< z;QZoW4eup?zU9-VCVq8OZ{&hcG-lUoJuk_tc~G*WJzf00QpLo(>zg(|9}7<9ULF;w>{57 zyjoOGV%>45V}t{BP8>98Lfcp`2j91hZ05XuALMK%9;-f7VIQ(X^US;Nz7~o5vj^L( z+{Zd6d=2s{Y3hGjbr$l!`9%r*taSBZWucR8mC0_Sz9^nD!^Yc=r5SI z=f3!gSN4kaY3>C2bw=TP8`6{Rv7cP+SV}bT5%BON>Pnl^&HehyXI1y2(7!Ic z@etwEG}N2O`5yatKjPx;T5aFkhe_8jmQ})D@Hc#Bk^eyb@$iQWg`}&yXLQw(cwW%! z5Z^<*zTipc_t4v8p#ygi$J9EmE{tKE;q*TjD*5|5&xB_gXFpjge@eSMZ{y%d2HM3- z|I`zSxcW2GlHvb4G-pyfVlSd@$MmgZVm*)0d3MCju%Fl0_a66ufc?+%`vG6xzRh|C zsR@0#>FPA~uMNN3&AJYYa_@qVK{sZh(vCV_{iCCgOt_Z$1RKfM@;ypX4n&Q-_6hO& zeU@t$;T&%_VgEbkbL|+`GpDI*d}cl+oIOVSPSROLZGatBWQt;v1r^|t?ffF*GWjGn ze8PS9qucLU&9>p%Nz_kh*KR2>Ir{Ovth1I>*1MYB^&aGjJFYx9Nx=A$C01*hUArRX zY2T>nBIqFWC(#h3` zpWb5rZsX*HPRiTM6Bq0j?+4ZLBf<(JsLMmwtcWMgIRZP_mNW6BbpAeEr%70eTVM zrNP+WjJi_ilO>cFWhJpe#P6K$C)@gQuD~xWmV89}G}SxpSYL%aWoXT1`c0)V1wMqI z)4cG>B=Dh9NHvmfTDu+C7h~6CNS#{|MNKi9d$M+EK2|&37X;Gj-~S%rM|{U@?i>PL zB$wsu@;Q%!!Ww12)};Bvuu;MH*Tu}4Pdadv%`>vKW_1Jq4$aGvoedbbW|OXI9@iDQ z8lK`l_FtSZ+Sq@R^4FH=qOg?Jr?zctM?I2=Rab1RPjakc*$mJ@O775S$$hAQ0(j}z_JzUN8nGN>+jg*_eQXh<=9tk>>jP=oxhWNdg&l8S)SBE5o z-}NhY#6j$uIYC7-RHxEdS6zn{oUSg}n_QpL4MlioB0gq`E*I+ zRMJUY+^Pb1a=W%NFlK$a8s`xD>tV;OP;YZ5J-&#xIzrn*x$@@%8R8Ij&Fg=Rf3OR9 z4pCL&JCMucd{FlSetuR~65(ub_Is85ZcR2?+wmMK-B0Cw`<;H+?+$r)TXv0flq2XX zsNc64bpr9G+n*WER`004#(wHlZpaPP2|VNzKzv`e+oh)QKgr15e9EuM-7aj$AvRVYJpN9W_jq3{AwMUlO0{Q(=A@wW5v0dnN zn=np!$lg`xM?JhXAr)FPTJl_gt{wcasJHVw?BYw=<>QTUjF0aUu3zW4OuEl9+fkp- zve@GG&kj>NmM64u91s3-a*W>(Neh}XDI5OGj9w?_m2WIWlzoL-=Ng5z<2ed@E><;p~&ZjLdRC(y<*`>QC|3tTNXflZ`@!lh{F7=X4fj%)hHKYEC77eP01Gc z7isF9HrAbVXg1{EKSTO`F`)qOVO>kucHD=)^^$t`Yq6eC@%#kpvl?r)o6q|`eSfw% zKBcdW`g5dBkP~jTy+firITf(;Q@$VgU;ohfXP=LoG0snvTXOTw3#4C(RXu@qbF$i$ zSYNe5?aYp7CH|Yrl=61eUv$VFS*RLwv5(`J>#Sft<)!H1T?MfiacsLdNu8J*)w}L5dTMo z&YS%Gjj+&i^4nAQDD19u^;)x3#Lt~E5A3{2s9O>cLi{u?C~)xoKcQd+=Vdi7PbR;# zg)60>;Qg3UmuNRwR42P9;raCFR1e`;Q*aOZ2Kxq4mzjKTNj5d|y{AbJJ(L%lKD+td zB+%ErUfV>I2KzPzWeoM2|H@f!a37vZ>(UeN!|IN?vtal2u3MxdKMD;uBWO=s6{~vq zz3L$+^fT(S<-RvYygzI?8%#RcX_+yB^pIz}($9Ws_cerKJQ`!~a_FZODHi1}f$&vG z-x$Y!4+q;zIbZqUQZXK6Pmg|`__rpzyRo`H#aFs~8m}e6M_laBc%d56I>?ui2chJX z!810oE^5F$Wh%T|P5oPgJNxt6HG{`*pXKkJSFXNK{(XGtWqI(DcAVF<3W>Z{t#U+*N}{ldIdLHt$0rFYXP!{lRQY*t2&|n&6K042WHaTu?euM9GdfA zyy{Q;Y439D2EMOrG!8|B&T{+OUZ&laTctWpdGcgneHZa|EmLdfz2QP&G}lR5nt^ecjuwV->%OJ!MS6KseD*Z^y<(J3A${*qpPR^W8Y7%@Q6DWO{K{h; z=OLM$2mJ*8Je{NDvuQapm8oFA|@An8vBEQL%_6NLg4===iOvHUs1<~$y z`U*Le=hpA@oBQ)X51!sI@^jgG=@gE$E3K`E`1{63FY1L;H+vxG+O@u_t%H>3dRJB- z=?Z1uZ)3HURucOGtX}Mg(T|(Y=l09DL%z3bJt~`=-&g!t1-W}Z&f+IU{i_w~w90Ya z7ctIh_BD3)XRGl=9i?{ds)pw`QNHVR&zOmq*aV}A<8h9g2D^Dyk$!o;k>4$EYEp1r zmGfbD7Uh@O^^@rD$FwOs`Q1Z5UEAD``q`Ua35Gpm(rmtP{sH;)3F=}J4%=$~a-8i! zL2slV>oLxEIyApaky~^s-Zy5=pH94Y4a(a&j>^whU#7f0{y4A={G*9aOo25PmLi*T zatq;j^7M&L*qh1wpU$68`Q^SVz@LEL^V%uG&yrVWnPr${m_O-nlgL+-KYjNN%FUXP zamxswp$H}ReUksjAxbtrULN7t!Esnq<)9P9frO-d(se_PF@V>?b$u}9Wz9*)PCj?- zXlfGu28q;1g4On#|C+a^0CsV_IqR(Wd}W0T(K675qE=$U`%~WEj5>a4>boZ;i^vyp z<*_XGBayr@hw$#*ISBg!@}R8p09%w z{BRBN5b9$?yw2aSK5pt1>ObLdEB39}HC;))anv71iT`5qtG4r2F!5}kdJLfG7;h_7?4yCswZNgHC;PcrmrkI3();^bmUC z3n$>MemdZE5>687?&F9Ps9l-anf#7WxEy*TO+Eav>mKLr{8Arq5qMJ%cK^Yl`B;;$ z4-O~XN~fvzv}<>%`@jBr7WujME!#2r7t(+iCR2_)l)o~{c~6@1(r<{jE#{;|-k-5? z5O7(c?hNdOya8U$7Q%mkzCHP%dJ^!x?VaC55iWb*(JOhsCSQ7n^0>8b9^w|hDXBeH zYseD2rm}JeRyU`q>*vgXoJm(JySfpF06ib6NhSTc8h*Ulp9lL_+cgS&C;Lcb;Hwqb zWOol(K)uj{ieHHIySMInwx6zf;Y;2xjxT$W?e)a0??9b#vu>??mwvh+c*)Z>rj+sj z_|-p5m?utETS|J?U#Qtky1!;0hJS~AxP9JuO@@VBZ`b_p0I3qX=rxP2d{Bm*-uZv>~ zAiqp%xw{N{%A~Qk&KYLwY1-@HJT~1aZ{T=wWQ!N@tPY+TP{%gk@1>cv5A+uw6mot) z35Q-FUklx`o2*+o_#h&baGMhr-oW1lTLbhunq?g=nt%cMJ zUmST)K{^jOyW74?^E>P#w$EkUAW4u*+1BW-_}z`|ph3a#zBLjr#VYoH=oKUWJ2YmW z_aR5p)oo`xZ><1+m8JU;kC>UF=w0?9+wZ*e0(OmPmrVRTL!1rdR z6oiG!IS*ao%r_`c%FJa!M(^G{EK+7*$RIT>xj#aCR5KRr6wgK+p{D|V4ckI@umV_ zDd(5b_tefdqCN!ICXHjtoKV6c=EidPWoeqDbr)Xe?}DwSj_t1MPCez>xb@F~I>*O5 z?Qz|t6Z`&X$YI3c7Q4U=?l@~zfsy!Il>19>!lCcg)&l;%xkHdrp9n{+3c~NfKbQXx z>b}McdZCQ;qpV5ZOTR_1#6e%KP~WiaTtK;J`Nc2ilmE}(DjX!7vPQc^zjIliYa!+E z!wfUxo3O9ik}q){|Cv>l%IC)JavzaNy{5&{H3@v7T{|r5`EP6wLJ7x_yLM|NX5uw{ z!9FODdYWQVbkLI(9bfLZ{fxI_q>Tl&c$>*;YDiHs`I_xb0 zp`?So`>lKV-R58y@RFwPcxtI8-0GUXfc&;=y|aHcaTb2hUcTr8`E*R{nMis@p_e4C}Z&zUDU-_QxauV1Tj8oM~oSIn8qiT5Jq!r7dc>f^>{j59^n zZ8j2)`{OM8iEp`F_cPv;mM4pLUx)>GPE)tERGRpHP|L1ga2&UbEH^w+8tA)79PGg!}fa9t-31%ik09tmAsb*9!WanUXJ?2misYS*i$r zmhkHMNDsWj9-kB9A>J*c)qRr;6AV_R6?D+2HI9~iNB%aKp$;wHOIh2e0v)NXJM#qU z?b8#-7QwF49^G5^0lz zv}@M__OsXh)hpBkHT!oI(0{2co8kkxsfj6-MG?<&RaOP*V1q&;l|lap1_r|ZPFIK2 z-@((c=i3HHNvH9HI{_!;?Rq6*9M4sS6VHJkTY>&C>E1jw7<}BHSEUzr@Hv-ndO7Jq z_nO8@dI^ur3MQXsHkw8GFC3N0NDs@dI?cpe-^&)vAMvEoLYxocjtdb``5EIfmRWTs zv}H*GvXJh$^E+jA{Jq8~)e-O8+7I`nK|fUO-J4H4U`>s=jBu0)w>A@QMb_{Ejw5+! zdH5v2A*wri3-xQ0Cu1htfQXY=x6rShQ=K!6-!<>YCqS#)H5a}eyw(p%Iq5gw`p?39 z=Vo@LN5g))6081*@JP2?<2XDH{~&`qf&=T-?`|gujhELcjL;5B0FmvF}eZ1@S_x`?RP*7s#u3d)6`D9)z7V z9Oqb<74(y?_H7L{l76<{3>W!rZ0K6PsQ*#_qdyOIh(>C6@wqPNDD1#AwNXDYko@1g zKq=Zm>%Y6Wj=wjZx;sj^D}R}cI34O-zcu!V*U1}}V>N}pq2y27VVB~$j?5nRb0#mx zML1dhcv93W>cqx3NEgQL?oWvSnr`=2(#sERhkqblWDRxPrvEzV9)_RbjtdH*AL@=% z71kW3evH}n{S{Gvyt)heWQDrh+Vor63sxaDj(F&8+5*4Hq;@W99p$yf7!5nir0H21 zok@K$uQn8U&VH?K@%awg)$jiGnfEWE{dDrUb@Vqrh!eoy{TlC|{xL?ozuTDzc{DRc zrm`O6ctZ*VIpN;woIK2VwwCP|=j9A;$mZ|zp<@H2m%+h7x#*A04mC4>*jRNFaa*hh z8>xbyV%O~Q`92Q%!r93M^o)vu4;&G8EId5E9F-^h@O@wC^U zw|`9fI2|01IC!KXXhWq)2a{)hahl`N>8*%sv}0a_?Fa9! zgm+-qyqEC=`~ba=1Q6dVxc6_^BWdbpoGZ!iEg1jYT#=sisV+Xh@TIiX%nQU+P7y@E z&symN_-k~}NHBlkUsnowfpZ{^TlMHKMSnhNF8eh;>AJ>wScTeP_TRBFe>~~orjLFf ze?ROvJCWC^U4hT=cWu^cQ6AMlU_Kf4;qWNpkXTnB^|y|XcW7cu4p0@Y z*Ln?p+waw%dc%Ivm^7279WCYa*X=TV5B8)Z9Cn#WbG&SFA?Ylq{B7h3`nB(R{qgUV z6Tuc|Ci!%KfMCIM>ZXK^hy&m}D{1&_#{Y$y4N|_pCe0|~xzJC5zBZ|ot--iXQ$I=U zhWs{Z;{IKeOFb{GsS)L6=$wULMB#o%L~kzfq^nu}8sYin@H8Lj?Yx$-md&JxSaZh{ z;zw7qriXBpn0Nm_UboiYf?l(05`^QhLjd0`n|JWKTR4+UI9pFm5&hgmj(AwCFXzkf0)#jV-+>Lzj^8B%qS7a0lLO9QG zV`n-0v+iuZLi)-0VB%Zsr?s)0^r5~{R(^_Y>&zK$;wxCC1Ky!Oe|`IYKkel^ZS(rI zwOSJnWF#H0Sw1zEbaYM?VB~wFwRUkl%3{lY>~c#P9^G|}fLq&F3n9<3F6fs=@Q+Dj7G}h9zD9SKXg79s zltG&#-tdm^*Szm78=FSB-FMMnLH?zwEP`~^=F%>rT#nfgP)Ym?)4L%rLCrSkTS>r23nu$TtVS34g?ESL5pz>TzMR z40;>!8B1{)@uS*biu^9t)AeM*e?s0wX-6CZc8v4GT)t;~+Faug`_Xbwn8jK9&KxeE$-qEC)GJVR9{yfxoy0dRB@TDHHNec=8%jFN@FTjrO7GQT^UygNpIOF2q zrn%2^oXtW;H~aOM1iZrMHf>B_$?rAKsNBcF zg>4jZcElBAa`2-`Q$E@nN<5a91(g!6&TVU8cOaj1t@bzWxOUANqt!~do|;)L+QC8H zNB+jRhVf^SANJgwseP_-`)LsFZ`R|tC#3>;77zwx9fdi4QqlbYk?oE zGaEN<5~63>W}Rg*f4AteV9z67b2bQZ9FyjrpJ4Gf?9#Y`g;d;EJ9dV{PBm$ku9tvL zR;aIfx~&{X$Hf-dT_#OI*iQK4Sii9x=hcBvI{$p_B7fHzCYTt{+Wl2wKC?3R zZOZYn$9Lcdu2A3GvY&Q)vPGF%)(`pU_B{BuKhHN_ur7zcgS^AU`m3~S57*ZEL;-Ko z=-o1|mmFVNAld`U;9BrcMv)L}37~vv%<3Lv{QqbDk4XQV-Y(ijH(MGYhj1QV=EnP^ zw+WX1YU0E7{lfXYmiq;qA-|i2m=DQUv+~!9aQJR|{2=j}zqHT>IH?_BEk9Cz%9JIk zgwv&>H>UG}Yy2qld$!Z;JK=X>{igoSFgxmk z3zacG*hQ}?QN}?}As&{qdCnxvz-zd{@JV|GD$V=Ly1BP-=GvBvp-hs~XDXa2ty&_TL7_Ff(QM&zx6FLk3%hg#XNdNudEU!E^Ci|b!+whqD$ zPBzPwu)F&5l>a55-*olfK&utinKZ@&aqv&hN6V`OIpwELXc=byvcT9TL<8TQBMsq< z2M2zh6wA0*PIeaLd+?GhEzf^IoTbKALAnqW*3?tFFt!v{2z&lMnQ;;VS&##`4Vq947`hFbZsi-GvNrj#4&}@1%0eM%uCT;Ax zSl2Q!rZ=DRW!(?WS2=%6UxTe?~Ls*&{50y|_ZX z%l7MY?B{QfwxNE#J8t6OLK*E2xzu@$cGqT`X*S|~>H~9L8TtfrbIYTvF4D{Tgmvdg zUxnWYKkGfovoVyrMtP-i68P?E&n?)4%a#aM&o$sNrO{U&!8qY_u0DbGM)uX(_^6_b zuruq*SpVf``Hy&{V6`H@tDek@DCIXdpR()t-#Pq0f9(xwX_-!H2@P%FT#PjtM;`3zCbof`WV>+*OM56WV8-tv8Xa+JBexHEud<&c%yw)98 zi+GniTk8oIoiiDB2;zy>;lpe_P)LlQe>&-zf41j2)P1(zQPR7zZeJkjZJsbs#dXHH z18oDu({IuwjOS=pw^jhY$VcZ$C8YbP+6Rz(&=-pRrKE?(!_|=A*#8y&Ut8$kxtgXW zQ(tQT>|>$-Ra-E4%ij=b7`I&_<_Vlx-N2he^Tz6?iG1(*(z@VC_-n)6-GjZDrXD(Ve;@Vx{IR$S;{BMda4FY`9I5bCQeK=wueW-zH%Qu@(%VooB@<>ZpPMnhvSJ^9|$`I@q&Z| z#1jDbfELWxuKCbWAj0QC^FjyVxxqN$Bic#6uM0II9i`@q@!hMPX^1l*zd5hDp6{(3 zTz8%G{)_*A82O#qy`xvjj{~>DAa{@-Oysr_=6F9D*mR9f!&Y5bq=1@+TCOT#l zE=87zmm*RBV{@VvelFH){&E81H|{uP{)?i$6qFmthenI z_n|DRzKU}gql*6Y;tu4kQ731Q9{F+^?Eg(k5`_e3-J1?3_6(2pePucrqwM3^^atz1 zg-)Io-60WhcDm`HOD7*>do@5nyX&A$7mBmCSCmxYkQd&!=(-Zw%B3|pv)yr2rW02G zp~Z1jkc_Qf+0u6`R3C#gwg1qftPGZjXVOb@5+mwOM_-aPZ`p`5&W*wV&W5i&nr#l? zS>;CurNYLDmiD7^ou`p!pZn&I4Wd1!SA2Get)wS$9sB80jba>W2V-uW)Ab$uT}fxC zT*Cfrg<5>S@0jlTR`hE>y2*9cw&Gy#v6ZTrQl5!knO{)Wi?h;0jL*jIItZIyKNcgl zLSIZeYj{~)Xpy6o3TTl#!J*6UxOCJi6(r+Rer{)`q#xvUI%+0Kr1qcR!*lkdD-gRM zi&^o%zqcQSPlRiX6Kz|J32W}zA8Z8iefv?oFTVG`?LnM5Ey3C9&`}bdE2jM@#wWC0 zMr8dczjxs%?&A;L_A174|mjrpwg=XxF5UvD@$* zYN~H6xj0sUqa9cr0H!N8#79?D&r5UhDET{x%kgSQ>A-*rjWnAMrpyVFG zb`40JXMphgvP+OvF?OqU57s=7IL($~@OVr|u|M{<#xuF;Z5 z1*rhp#qVIG�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�WFnzymIPt{i~g&o!q^UQdG^7)zQf3HLKL zb^iz-TdSE-7ES)W=7=q&g2h%HE?3qN{>LoRI;!t$B-_`dBQ7vW@DAq1F+V>Wd~jZg zI<>L63%uaSSE3;G1vgub#!l#`PTMr-ikPpmC=b*1jF!aUI--wwwzd}MVR~h9BIf?= z_2q75HeRn$ijPqo_&JA6YZa{3=q#nr3F6eTmH57)<_m9- zy-mwphPZ+CVbYkibo)GZy@}?2M)-FvP5Aw&M=jBl^gBv~m?+W>JhoZ~>PB9he-C|v zVqOT38b=ReF7o+p+vXKr=CZSOdhk2$_rL%BZ>I9V%H=C*{>LrYl+*_ji9{82yrnN3 zJ&F2%9d1KR>wCV@g>9(j);hG>12os&dPkw&1m17%H;*T-{XVCtJy1eCV60BAq&(qm zoE-n6WN%i_R}bEppJ`MJN7?z6Le z?L>rhpl{obUPuQI_@t-c4qYGPcPGH>ws7seiKqj&a0za|K=|%`E$?ac)0jl$n5qk0 zY$N8!USL*#V-kHXxTGZ>{=@g3ZB-IJ8EGvNst2xLJ%n|aa(kI*RSsQ8MSYu%aG9@c zFAMRhoJG&UzY%ZB%kg|C*7ZD(dHhONC6OhfFU*>-5j^~k9XmHL5A_G@8Cx>t@AKBmi(^u1=KR7(D@x<2TlIrQpkMIEgxz9yEAlO454m0+8l ze7JMhRQwl7q>60VMOxD0Cy1Zq8}nV1pXO)(0`}u)`9Z6M=0c@o-Ia(h2U|UU@L4}^ z_Q{l_FXa|IA&`!+ZP#0xc||fi_5aA%&xXuB=N}i9xi~5S#B|&b3a^HPxudR zA4NYnDXX+ptBnGla<4x^ec;*G`_gA%p9fXHX=+`7SgQUI{l6T4+JK(o!MN5xRR>tv zn{Pa`VQ$8S{a6`iMEq}%#au@G@BTDF9BaR6$Pb6D(1D8fr7W z-$wI5_3k?r6bCTx*M})rK7P%nD&WtW?Si2bcq_%JQsQ@CJpXDM9&?ACViDo}kDb27 zImLC6a~Js~7YGb{==J%EW*@z7DYGT1aXpjw?U?GbTXwBS-5lco z_`3J`PQ?Aq!sbNEw-S-~L>$gzvlwjDKX#gRxy0k;8(#k_*_8+>SqwnIiyr@&dPlux3 z$icc_&XuZsM~ygOXmx!`{v7_VBiBX_+8J9ioRsxF{@k6;<#Ldc6Q z>%|J_{9F^a4SMdplKh^u_{*pdR_St)Cw%;!{lSiG;3(m46k|@D&6u7LLw)FQ4&L55?U}?L#{#o?qV4GN1hVrd168_OTeT7IH8>nCNN4tl>fARMFL+dEH5`G;%p+HfAsYc{vy z^FDU_!L~;T-y!p^uYeyX6<_P6_~fgRNagmGRmbymh)dI3 zGi}Oxss~*9Y|MX8&yYm5UCAnWJ4=0LVl37^Xb`8WhE3S#Lr9zW_$A=`k^7i~?BVW% z$=?ER&hWj%HNrp7-7HI(F30O>&2x7l&-2%O^&fR&Uhg+uOYRY`7yaV+Dyjo~zW-T5 ze!trg9yx{if!rUzfnQdz8_Mx<;om@Qpg%jW`kFXW@3?@~V|pdz@uu z^f-A&bblv{t4dM2NvC5M&JS5B-@B6USEIh?CTqt7WCv!wUQKp0O*L0UI7Qw0LXSLx zO&eR1$iGbii-nFK|F|3Y7PD-_fED<%^&fq-pUx{y5GtTga*pwz@Ol?pC>v^{?_Xos zm`m#fj9T4P-Z1Gqv~h?t=Wgz{A{ua+*pNYHmu~O~ZBzXJ-)7BvMrBW{I`E3?DZ>93 zPu-7oz%qN)@#;A$*nv^+&<_6}{-p0p`}$L<&!fl*gZCZRjPpYNqrh2*&72g_2M2I| ziVwE(3UAcfM>TDV+^(5xqcqv$CuX^I`?3~!64YC`m<`kC+m5}2y#K`5kmqCM{Z(%k ztS8=o&Uv7f;sCZmz8#A?(Bho)N*eGVxpuu=Zujyn#++)>c?`B7^neQf#OrtOAbztp zZu`y(2K_%f7kL};pxKj3^>w=t){o#Q79UFqp+DE2Y1ONN`~K0MuOuX- z$g}aeqo#KKN*t97w{0eWjFw2IQC+_vIr=O;&a_DEz>|}CB-692;;M2`xn)<;e&3dQ z%=w`X^tHZ(_>?LQPVo$JCF#9ge6-Wj>jQ5!SZ#Y~J<8#l++3_XVGdIec7py-3|~R` z&rSY!cY4)+_En&r@aN6aX)4l#TGq82Ft@~YWEB}`{kKAm3Se(qm1~nxAlyHC?tp{r z;FYKIjA^I`=G>pV4tW5&*S%|$|FQp!b}Fu0L^9gnNh0+1M1kkOHF+lVH0pdsOr(J7 zzrp-I%z^IJM8Zz!gO~#?)lR;X^I29*%g)@CqT^Yu4wcu={h(WvDp8@ISMsRzql#?! z*M}EBUrzb|#w+24@PGE0>PPHLahZRs_Sj711126Vg^vFMn>*Ks*TK^_PK<_-2U?rw zVz=^QrnDEDNdFN8*I~l{r)@FG)K4uH#1ir!nQsC7D#Yi<%sWDH*5)AI970_jH8aGk zt8#QzQC>uX&CY4h$?aV%A+;TkYz8c2Lma+fJSEgM5Pk#PUv24Ch!@WBe0uCjQMJV3 zJk<|b=TKg_5S8knIsE7IxFGC{$Ne&=zW?yJ3;d>&ZyK1`N%_TEcAqW2;W!F z#bDo$VpgFz+nkNhjg^kJzYe=Pp-^~MR;leOr^QKeB+%P;hTcE)$M|IW-ptH>nO)F# z7Z*B2WY5ZpyV0+M_X*-5BlUmo*!nTQ(8X{6B;!G<|BcwsF4hkHU*%QJwx`)Q)vU=Q z+?VgYf8~o<{~;Lb-Lx-=km2{KsxEV-BjK?9|L~_ePS)6lbsY&Ai(UWtH&X zVm7fvRYm%a=frjBwVnLcoV7RTI^sUq>*+j+u_;#4RYgLtUT&}84kRafI>Ebj)ovx> z5O7Eq5`LHGj!u%!FMhNhy1&PuEp10$xL{DIX#bvKZX`)14xvBacQx4}NBw{1t}jCI zV$Q0lS#fU1FJvBi!d5K85c=fBWP1Ch$u>4E>er{G_KiA5dDMhoZtbH3?m59+#P!iT=;*BCOlN`fHh8H0PH~Y5#|R z%lvyg#er#Aai{}id*ejunKJuj&NONowR5x3@7F^A`(AVDQu}Y7LB8+qZb995T$3BL z+MWi^CiB(m*ES?P9*&qz?`&XMe;oFv5MQWERYDIyo#zPTDT|)Q{&9=cmwC5t%&U}O z?yqEbiXaf~-s9to(U)T%EZ+8|8u%M@uO7vIU;OKL-#9^hkhLee!K>L{hHl@+`?+4h zCyb!Zm&`LJcOoDB){^(24}KqQiPi#NCfqE?{PqjX;}#i0$opK z`H9!)x^Lf6cb;%-tn(os``G08U}*@QkKbeCSUDa$ghP39`(@S^+yiStKcFIt`B=LB z-|@S_FIh^XSRaRY)E+1uBRg#Q<@_ETx!a`y6UCu~l<5g^u*24Y;1<+%Y_XajraGXe zRk%p!`$I{`KG+i{Tob{6J9(#Opyx3>-@UeCA3aV=of}WTSL=G{{%q)@Ek8Se^9oKR zNlOI(=fgJ@{%m}2Kp1JD?}>Kpi#!Ir7f$*QQJty91PY#KB{B!}p%tu1UpOoy|DP{Y zq+#8EcIH!$lK-!KYQ`gE2lw6g)S|Rn+1|ds$q4blmD!n?|Nmhr;eR9Nc-ERw7*gP$DEl?)6n;4@iQ~A|ITIpi!MoHI{J2p8W$w1tJ3X~x6Agz z9-wQK)ex?R+Modbz*+PAp}%$VDsS)x)dB5}QyRnxo)R1a9)byGUfks}fp`K{C7 z0nh4WUa1p0Fy^#k`AaL}N+-WqHg_@MiTjIfN8V=1hYk%);VDthkF)#vFY~VgcOP$& zmf>}*FIYI#hwtG(`N?}2{ePbKY4hpzt;OZ|o=Vmz6^r8RxYAdXXOMk@mKl-v_~MPv z|254%WURyd--}fD>E2h8-_MAjf&Cw_|BLIs_g<_WX1jJx_7VPw7g1FI5&vI~zTQ|* z_k6;4XTAyb9r``qwnoAqW8T>`=cQ=qSx-DMx>skW{XeW0r|kP2g{ICX*;A-Hrs;W8 z;c*+V|Knd4E4=K2L9K|90tfcnR!u(P`ndC)0sB01t@jVu2!HE~)E~Bq?!f;r@3A^3hy1_chBPe}c*jO5p?0R%;JOZCUZ_*}s0*Kagn#h@Scy?A7Rw0# zljbZj{l6ly8+PaA6D+s;Xn)5BucB@x{y%+_v6%4f@Hcp9o~-e3VHD|l`MI0V16SS@ zu4q_^&*$#+Na%e1ZPTabQ-`2GL+53Y+uwP8{OG}l$^|REUtJ*?#QAt*vwR`=5W85| z-kxShK2tTa@w9<6pY?f(*mxO$@Kv#j;Ihp_HvgJ%2JMYF%p`s{Zhol{JGz@A9dFO#zN zX4OrTh_-OgH~>|U7G1)AQSg>|;Yn9mE{rRy5`MhbgEJrEfzg}t*$|NI-Yp1>78 zy7(pd@7V5a=s~!jwAZs|1bO^uaxLP3k9orJ^f}HVn0qkSXB>ZH9JpTGE}bS)Vs4BV zOZiyTn+NAqziPnydAA_|{Lz0eXmk+HgObzz*}!RRqQgt|_n68HzxVO`PS~P}w?!!} zr(ehOxIGH$#;xphj7UoT+`g;32fGepegD}zJL&n)nOI84>FNcuU$2$hyA2n>OG3PO z+H&}FB|l3t4!iM#XPnUzU(i{pKh(v?|1MUA`k*RrjZTTY!E5 zCU`Tt4$fxn=Q_5$2wtO5s4<5nv!|3x-b<@mVP7^w97p(XUX@!z_`lV+E1&ZJsOie> zljQ%}($Zt->#W(7<$N2s7nlN=_uQa~^j&k14jeY=gYV!dZ|Uy&0`tC;rtJK;H!RC& z{Mu~Vmo+OZTbtK&5xD2xXi>*j1&v;}R781v%qnVOP@f-0W=vPfg|9}hX>wUOh-1+a$ou~dyLt8}*`b5m}U=(;> zF}Gmx9UbaU)Z6+g{(mpPLl^4eAK$%j9od`3HE5)|G9l?t(-4nY+hDknaF13ro~C$E zHhuzeq>_E({1`Z{;G!Ml+R(u`d(;b0dht37PHM4kAN9R^ra^TyrELr9{CeD24fyh zfyq|rMQm{P9xLU8A=oMTKep|@8N&Orw&ur&Fh3AHljsW_mN#i)Us**fqA z>1_>-r2=qeZL2@Bk)1$)4NM(R%}pWy9DD1R0gBtyCUz2V>}{M%VN;-NgGWaU@~hJDNywhU7~2>0$9Bit2@<=E!}{37GpEj;EI zHn!}Gt4c*a*LSQak?n>4Yb4x_n&ZG7b>C*Uk&cl4un#2q!lw2zvIBE?+bq0Tt^Ks$kI7tA)bZGimvgZ~hgA-Ivdn-( zFx2Pl-`^iqf&Ii5DOApE;JYOpO>kw{iPLiKg@Tx^lN%v ztW3f_Cztszv+FB;=>K7zdLik+&cMh&NDpK``rbkPez302B@q5mGcBmwDp+r2QC}=@ zIM&z$zkPvgJIxE7;B|@h?;RogOArqog`YF5V{j4f-#MFmh&TJaw-D#g8^t2)hm;>& zjt`HJznaFw+z8HlT5Fh^(~;PIJUkVjy|Ojr=T`@}1c*0U+=2_&pKG_g)jNv!H+jBo zCO-Dfy>01uANn#4_&)4QQa5Xo{CL8Uh`DXdl~p*>>3o()k`~6{sDJC)o4}p7nzHqk zIPd1Sj}(UZt?Y^MMm^#H_oz*5?ZV@rc%Zw9l0V5N9?%gy9pWz<7Ru$(YT_Ni zQp6wd!(>T3o!4DoZy_8#5YpAasd~X0#A&(x?@#}tit6+Vr_fJzLQYQM^SJ&jO})5H zjq9I0?(6NudJLQEa5DM(_6s^4txq(%j@U?N)ZJD}($TMx{@AcYZb!dX;vgQwyKK@$ zgx{`0rO*X@Mav#dH^Kjh_)rhxJNvFH#+#1cbMvC0qy0YwWjXlCRwngq#eU#hS(}3P zm7GWGyNxvev1I-t@c&`VA)NW$|Ka^F+^6uD*^wr&E=X0CBB6cY{;|}A1>kR^uP616 z@q}}4fA=Kmyn$dt-Eyq^8yohdo`AmB;FsE#W1TQzZUm1iG}~)i-hiOL;vsFp-HZYWmgG; zR!j9kti(J(c*eW-89Sl>w2r7-zzg}dJ13$#*%oGMYO@f|E49C`iG|KH8r&VG_gNzCtT?iQ2X_@#kTIqW9CFaWE={)Vy` zE1u^^Bbw1J%&QJMg_;nKNAtg96z@%j(0t;Jce=f?qX&_<60z@#9s3%8{N2y=JRdL& zpgYV3JB*pKy|Xd@p_|8EiYV|~Z5EZoN)cyGuM%VkM#qyOuTitj2W2!_cC8< z&kBnBC*H7rN`9|MJZ#*C_?#0Ck5hjba|UWT{PU06WC`*JH>q=BF3`*Fm&}0P_X1a{ zy)(5x;9nb0{7}qQ@L6u1lKkqNHFXYHR*9+idLik=)sa18#M}0tKUPhCqu+2v1htL3 zXf6#9FY`?{R#03OHdu|czoQ}OgMZEA$_I?#0rQw3Q+d?5&Xc-P@WvI;b@{0{Y@N2p zN_wEo>u!4;=a+=3pWDf=n=AsG!@QCBrjhJo{)D%McuCp3x3EuaG4n4_tC1I2luWol z`C#Mkl5z=WtD|@OD9(?*>=F8X;_`&}F8tmk|2XCdMBdJ%}Mp2ZRvC6 zD)>L>e<{U#tG{2MK0xE-DD)mfh8XwxcpCkn(|)Pv>9yZynX-FxmlHn>k6(LwtrhFN z1A*mhZ~RUhaEN12H>kSDB3rn>=_D%mhh9wih^;btT5W2H%iD(izlvDsry&FJ{nIP% z%1t;v{P*xwUa$@Pm_zlUNTh3(BM;Q=ld9#EFT&ZQBf!Oye0MkHduMQ`m*#}x9rZ_i zc9H<0e6zeuVK##L+_k$P8N>U_B@VI$g)8am%4-x0R zS8cXY9`hUV89T4zF$E9$3C!m- ztV{UecmLY|XNvC&xdlnUId|voi{Jr|V{EdX)&)D1Y1L#O4Uy4)$_ovCi}gm%-DdQ{%o7-`|P}iId3of-+pZPXI@5TZ~dd`?^khbcV=>`TT?g;qX*^ zcRD3qSeKE#>iz>{51yThMp~y6wFCRh;aF{9-GqmE=p5dMenE{wN_JtfSWqukqAqFi z5)XVpaKA_SL-Bjs*AH_EL32Osjm0PU6zSN%`)DN8Oy`RhnXXgZNuJ#L2JC<(g%dEh zh<$`=-V?z0vF_Q2{YUn5eqqP~eTUb%1Y;$R?#)61ejm3`XjW34ckpyP9Rc+6sN^7B9&TcPL-ISwj>9F zEiW=^Z5jo`2}!)J2ty?lw|nbL!S0H1tp$d+D+VFVjY2`l^ZByf=lT2bpJNW^<#&GH z_xBfmxjh8-KYMfK?dyN}C;S)vs_SW#&8Z!b1H3bk@e9-g^vw&H3$t$3_FMsR->6kB zDk*;d@8$zjA*Me<{##bvxP3YAaLTMRzE_6$Kl)e&Rj~icX18M?|E0x3oD+N#d0iK5 zD=;mXyPgmXe=q8KS2X&HkncG$c%k+c(7TH7sH5v{%uS;o80h}~)YmT1#Zh~sh2p;T z_35zt3fbE~mAD9=O&=Zwd@Gp7W_*$0^42$~BP@q~X4pGAj(wgjxMU)DE?N7_8}wT= znlnrLAF66mA`Z*@rbktPFYh_ux`Oh+2VXAx8NqLVZ_a{WUp?wZ{2BS3#p*TSBgNt& z!(R9ki!Wr1#p60o92{hXKbaRLqmOSn?Av*t19Zt|G>O2IG`6C)t&QY#l=HoDg3mL? z$Q1gdB424EJK^;Xd-zck)ViVca#?}dtZD(=5BD_9R+8T* zCZa(@N88%pzCe6H8YC=HK|j3tsYCex`o9f7ZF8tp6wm41u7SVzRH|)w-`p<50c@6# zhMn}g{n#Yc{b@)1Udk8T*JJB%+&>MgUw)td|-|AA~Eso#5Uchz4GuZtH;1Mf58OyYnLO+)qcjE)U*g z9l1V2eA~IkH{eXLayI>@}ub0!>=xEqJioP%#$e*7V_Rgrz>}pXX|A!rtn9&Cw@_+3X z(7(Jqq#@%6ME}a&hd&|vz;UH-`?deK|0DeU%HK~?e^>ZFOflf|7OC9z4eCK`ZO1yu zKQME@2=$<+Qbm{K3w@xnOT*qW;u_RGyKu~|lZ{hRHNpk4xby5`ua0v1-i zyJmOHD}d{>=lyrdE_hg~j7&s)SR|Gr{{J#3>ZiVME>`r86nggtE|ASvqCT9(zPEk5 zl_4&$%i-v*!oFmLET#N@Z^fkrIGaXhw1uD>v3cY()M0Y+i??op@3V;~6hqXH{V{3E z&NL42uN7*@4pR!%b?}pMQ%b=}@He+9z1UxNdcQA{=v{W#W~03B8&i{zr&jii=q%#v zR;GTcv>+XHHl0s$3w?;#4%7#ukMP4|umjQWQ$1HmaOfP#-A~U6=bL^{`o7C|&D)!f>oN*VuorsxkN@;gFYf0>r%FPJ|Bs|}U6CKYr&}Xe$*-aRpH^$r5#479 zKU_q|_w9Kpgb&$|J_c-P=FS~&YrFT){;%)u2!>tngTEJf03j{8hy(tcA|V`kp5XH) z@A7EKgJ`vAq3>%+No3?7Qvb5{O7^yGl=s&=ITsgJ{xa7hiHrJz-*Y@9?i_`03)#Lk{>R1*BB?|z@!jqiT-`W_?s;d_OO z5b~p}d5T>oyK8#IWAN|6-YV7Px}o2R;s>2n*L^?u*)}!!|7An}jGG~EG(Mr8{OLnW zxrMy>mks;Y7osjO^9Q>!(Wi|aXozk75BTRZ0z)H2rw)Y( zt|L4idLD1rrhxzG*+JcH+>f~;n0q!+Rl$7Olf7bO;a*W z@?Kuy@_vK*@7OTeLOr0jC0rVfIWQ#$S0_I7>VK~7Yf+%Dup_%c!pp|^Z?mJsx$rwG zcGw&z8(yLO&UR%5;lUqGV^V_uJDR7SEIK~M=gzM~lueB;fDnzq1EFMs#|>-ZtR|f4C!BrLe-@^*^I=TY(2B%NPS5uyta! z-O4sF@q^cUXrFkoc{|aMNo0W^5B?M_*N%N=tZ4}P68b2?zh=e0E?S%ZWg+6bwV978 zdVo*Dx$t1p-)Ahtd3X-j;&G`!XUv$AoNyiQ*}l)a@Vo{|6#UA_|Ekex>HUgidnfgg z=+qW<(R-%U_WOG1c}AgLM1C5J*@b*f)CoF18YcYI+;~Pz{{KUgPVh5C07MB#li&}S z4&-LidgGTy3&a;34{q)!d^t8&aG(0W8*MIA1ombAzfg&V`*t0A;t8@_U%t|roLIiN z813H|`2ObiKY-u&8v3H49|Zcp$Jy5cUW?1m%X;Pr4}Ntd{SW^Kf6DVOvjxD{Po~%~ zzmVnuT13N!^{+;!_~x_3e@Kl<71h^@-i3Tv85Xl9_SN+%%II(z&#g-lNvC>INAhZp zC?l(!8_5fKq-OFc=DSxkA4455^7|?T#Zk2sXtRx zOFH;H?DD?HpSD5{iq43qA^`u{Q5D4x*<*IK2lhB`7e;h+{`O6ZC+k_=iSy4BTr+b`T#&O;=J@*azBBDi)Px=3+-rtK62J@miK><`QY$9H7^ znD^0f?o-TrN#ojACWjct(z9|3{K5s}~nnxLg&a=N&es0N$$dl+?q>|3E(YmW-tvam@H1A)o33q!ycn;6DWV zC%ZnoWJDnQE_c!+BYW`e10A<14lJ+Cf#3fRsg^Uwt>hQt4Egq7z)ysE0KKUN|C_qf z<7to<{8x6TUA6q>;>OLMZ{5Z-r#{zt11nqpMe;seeGGxX#H?%ss0%n$f? z2{ElbfZOE5A7+rB7yj+Xs2{_V(Umrm(}CO^*a@)zf?inU2Ol==ZK_v6A22m$!ViOf zjCnR2(S5Zh8+jw($FH6TpN2hWN55X^6C*>0A;O0)=fw%k;bYE$nPI?-CCZs}fi5kk zWrPf^nti_NhAv{7*liRA^{pRIp=A`$QwzxeX?Gw}b+G(Udg z4C;Rbsp~M!|9nS1wCJzR&FriD-vvk?$j%vtzX$fbExiujTz&lDec3w)o6 zPtSOW&Tm+ZZF&ayhD5CQ;QIBq8c(CI8+X;)I!AC2q}GEA`hig)pQwVL`9SF;^U zB=`pCusybd?1R`a2lO&M+tPQwjOhBnX#R``@4qCCfxU)2Kuqc+x@{jSTEK&C^WUN0 zOa&Xu?MO(%eUleua>65txJ^m=T#CpPMe`gi=?`v_oKPra@N-3!E90`a_VCyCilmBu zdahw;=4;Xi{_RIrE8>ovP1!Ixf&F>8tLtiF`6$hQ2&eq7N7iRPns|KUVfUzel;EBz z$}6VhXNpHI5&z$U#4vfebcaqmq}}!EA2u7{$RoH;9h`PhoWDZuoY}QQylQ1@hi# z{p?)BN;UR*aIoVx!F4Rt3%-j!Dt7{a=VIgm7*m{kH?WM1t&qPP*wr)Nz+OmW$(PPt z2mH7rGo6NR(8ZX~ZX$SI-Z9foaoT6UA46Y`LUv&;FiG>@q!wW(+1H#-4nJ!ei}^MB zy5Rb@UYC>XhD@F5DABF@EkF7-yP3&yZa=~OspvxR11nptGy_Vo{PJHaov6~YR=E@N z_|Vs}W)1k6o|$dh2PCH-7kwfj`%t`0Swr}eJzx$_M?UvtORSfk*EwH%2K<1n?3>Re zdoTCwlM8l2S&9MAP2+N>{3qcb=3H|0DZ}$z**fh6o?qB-d5rwV_flU@1^jg+{|U}b zpRmv9c&H*IiR8d*@25o3?{x)v6J-B+5-VI|OuC~841nld2pEtD^vBf1aG8que0qA3 z?-$?$_V|`w^aV*Z6lLcw1Ad&}d+V!RJC3BTlVtq$y&W;Bo|cQ!m(cgCCHlfhYVm(& zSE$t|sSapmM$OeT$Ccdu?>vo|0(rdFR3A-qfVEF5?SE~S6rcI~)%!FTGPLdAdDI2) z7Hc~49LB0_=|`eL_nddM?LNt6%>>2861dpdj#;8>>4^k8=#;xUJ5u7tac*W5`aIjTcx1MrhKq%Sy^+G4<7Fw7$CU2mG#i`diXQfJE6B= z?r{HwnI1eRD6Ziwty=@bp75D5PYCgoi>^y}ub!0zJO$VB_p$S_D#GWkt`}2O<%*!9Kl){q>~?P}ZweV{sMZ5Q%C#tqlath|Wq^$BagfH}Y zQnz}Hrwh>M9qoXKA7p*k97b`VzKnSr$%TO#rSu^jJs0wu%h2ytCfT|@wfOVwz5nLj z>0%9=eS%c7#HB3$`lD}GSE3I5`Qh1BiPfnbJ8Sw2{T9wuog}^g$+Hsz!T;^EV%P`W zyatjl9Qs~#^m}UXe}3Y6IPwDAe5xb+2FdY=ql*?1T{g=Vq4)M84yLo31@=d~o zaQGoq+?kv6ZGayey7Mwe{6>(fTAPP=!Jg;3E zMf6{?KdY7Ka`_M9fDaYynN<(o0Un`_IA^;DaEwfMbP!&xj5}EvQLg7Z-RGRRo)5FA zju3xns;ynf`!wCqWB z0{(Kj*^GH_)q<`ro9zCE)b$H_K*>u(exQQi7yVvsYV-%e_+78O{AEmy9MOg#&Mz{0 zTFC#?Ipl31yT75-2YeWQI@TkYD8}~|3qvP5;5;hQSrCciPe$&X3m}bIv2jO=@(C>`-x_U)z&7dvIp_p(??{He$Z>_o$+z<^F*cC zQam48S?>1z(CX6ISmJWT)gULrg$szX@wz69)(SX>YXR^oeo#{!&H-Wl4@ZSx~Gdd{tbz9PVzTWObq|LBoltaymv(Apa1AUIimn+>}4 z@LTN0WpwNsDmX{}`D49?f5kj+Hs9jMoI*X@);5GbUK^SJDi|-|-`dc!asuzI468az z@gH&emR~2~y3&~$#EGrEWVSDh_(X5-o(jx`=4Ud!@C#V^iF2Wt_lkId$!DN`u>nqY z7x=>h==YEWJPqjbhxUL^xhf>k{}-2Iu;3TO@2{sP8<7W6y>wIXXNm9&bNBu>fn#~d zp+)EqC&m0{?GWYnVIGkNbe<9&QhA@~KJ2?b)B_JcJ*}A#(VU-_II)P%Ykeao>JK^S zQKK#l{lI+L>6i1+|5bhQBK3W5Q8l<|&hHwZPWUVIJHzt90qO(rUV}_RN6pI5e^2-z zifm{&dMdoUT_~DJJdQkWsR8`Jg*ls{BJ%$=xF#mT;Wr2mpPVE+?z%0~7>4i6&MujD zL;r`G4)~zIceZcL3iykILPVGn0XsfV=>AX-zwt$H5A?bFHrqixvsT$GO%B47{7*5c z4$nK}35Ol6=kpIvWnBlqIq&trzT60Vxyzq~^97%AnA@#qcbc4Cbf56dv2dd6!6K95 zjpLYYZkihYS+B zi0?Yzw5b|X%mWtLUf|DMaW4A0R`NFmW9Jg!fyb?uZvq}z{CzhGU(zj6A{FFTbWKwO z_y`*s_rhhidzd$0F$q4Qt(onMhs{e3wE$aW|<-b@T*C0Qw`onQC}1!GKxfcUf3(aze;{1kH3C#y$bV>P2BcMelg}~ z*y{Cw<06sBr037@iYH7W53c)S(m(6LKJMU_PwoN#6lMJ=m+mj>3+?sriI|h=M88}; zKX&BdJ}ZBecL+ZbDsf#<@}{&(&~<1B>~K6!6d9vPgB~a}G%87d@UJ=7q5}VrNCZ3K zl|+uXE#MfX_IyqI{-CWWUC*3*wb%~&yX*6mP8GBA*;-Al2hXqhaX9k;|609sbAIrf ztfGlqR>bj~g5dE$P95#IkPmpG?&sp62*5cjBPXBm>SRMN^3n8sYgdXE_{Va%&qhfu zbo!f`=)GaBZA!u?g>7Px_=A5RwK`2t_1kH*NiU(gcocy3EjXWGp{Vrsd+#7V`#M|i0Jc4r6tl7~%yv$P{iZ3om(t)6%6mB?h$1v(bTC@V2(B$fVvEH>{GfMgHS{4B3l|3FJ&>Od z2|La3^YMRFDs2SEucu@#+D~DHd$AsHK6FR{`vG|1a6F;M`9B|y$fLinyxSK}_8+p> zKeeKNgCblWPx{1#&g8GHz>~GwO>4=1XsmVE>H6^K#4*5=edl|4jDE+~+LR=J{`SX+ z_2iFene>=wUemDHUGK+p8akvR(m#hs$6?o}A&#W-xLHzlW0O6IInLmnFmP;A7}J zS3vm&=ws3n-MR#S_uuk*CaS2vi6-@F$Pv~2OlcC~V_!f*{U$2D=f+Kp?(Y zQg#mV5&pLsnnSmd-IdMGlKhEY85^MbLv6lKNA+j0Kh>0fsEU^b5&Uh*NBd!q9=9m$ z@He3kNpxczo&UYH@d(+UBl-FH$X`OA8*pK6`@|1-XIf!LxL2(?4LI{Hk3Nw_{)Oh|XYQyt zE5Cbplf@0VI{$Lyw4N<4j!zmR55sdJrEO0VexDfJU!-Rd)jRXck_o@wdClo#HO7uEFnGgiKE(` zW6(?EqGMvn|FC}PFSG8UUpdNFwSX&gxl~P7&||VDBd7;-eZpo0ywdoRX-6pW|J?r- z8ws9{&H1E{v6D7Q1M%-uWhODf+irH|cZ2`m|F~5Ry5*fC(gfm@85x7iiSFhD+1rS} zmkn8dd>!!C=5B>tKpl|nx{cs!xMc?)DrAq;AC{7TDP&2=Ecp{h6N4@S-kf`xG8}$~ zVq;>pv>SCq`3}L<4Yiz=zn{ zMf-y3Jj#Yt)K@ld``Fh6ztP+*TMzIp@=sq)rDI<;Io^yU91TT=LE7h!5AcwCdKRf% zhU?OpsIBB*0as3;Y(id$l~>q&dnc;E?`vWcQ6J12>bLDdyo%jU2ydmn;f7n&$Wufe z!|cFq!mHbwcCjDNv(-l`Nq@a)oQNbmF?95Ao5243Zl5cT?BtrIHPOU3qQ4m(#e10e z(pU>!SKyyQU-gZgIk6r7jt$5^T9*R;uLJ#8QoKnLWtNcN;l)pkHrSu##uTxb>KJfy(+A)kd4om-08vh{!i9m z1i{I4$ZR1vHP3#JuT0}6N(u~!5*bWZ7w9vAdwk7YANBvc4M!2h8*IV}JSUBP6S#Ai z@MZU%{zMV@^Y_1_#e9M^?&RpyK0l7}u@d;F zt!(d(>@n~g&S`d?kEmYEbg(2be?n)L(EQOc{u6XKCO$8%Xo5dVRUVbRdnX9qMO`1I`~sSFR}*Y@9tr)HSd9s zAEo@v1wLZQ``r%_H{`O+F=2o!r|dud7W{ymR&T;?so1>NK1}w8v#w!*FT9W(^!Ek+ zkhHe68~TAiEbk88H`+2Q!+vu)f)w&;0~g{QaR7eY=!g$q2l#U?fALHb_C2Gw?+&f2 z^+3x)-cre}%1+w%%i@FE$ML@9tEFO+vzdK9x}VA4YseygjV?>;=sI(BqLJ|bhmOz% zJ5q77ZwtX`NS9nd-|19IFC~?4%x-_>nxBL8znHu8;7FW2voib>w|28L$nx_2$rkSl52 zz7{w5Q398ml9EdOCuA~;Kgi1^X6X-9=tGgYE3=;X|FUHnqz@dvbbrGz`jfFAt$$Na z@?SfYrz1K*oV3o0?=Mp*D`5TbTatgB?!kN3FWHg|c%ag<8uD4r1og_+9>`gt=mDo^ z`YMG(hd7vu1^js>9(@0;&6mx7{BHFs6DF!a5A#bm0#8^0>P=~%T3ij#4=ebYaUqoC zbJ4PP8%G-@_|^hdB^jZ~suh4LWSQEr36|d9SMFG~_4f^XSMp@S!C} z=<0!9H5Uj!4gG?<6bO7weC7Io{`j|~?-jk8)kpkX>i*<%556PadFh;X{3nc4zF^~6947ZcKC%{{JIgrPINsvb@R3wd|pVFADBsvz1TWaw=fPW|K%Sy1UW{kUJfa%%wbYc`Ucc9-~ZX(T^i;CrHc_?MgPhl>iMfCnV?kRdzm9- zu}F_~hKhdk2IMQxl?E!HPx4Zs9r;35{?)JL63BmkYH%L=t!Ig=-n9|D+nW2eIC76R z^2EQGZCZeyt>R86O5LyEyX1lS0Puld5ie>cdb72eO6k7l8+P!WQA1b}`UrsT|NO@I zJkh=FwD`3IV2Ljnk%pAMg{MfvaS9*rZWb(g*)=eN#wy zc?COA&^1i^eaLkz5K&&iX@@NW(YIXgY@zcPd?~~9{wLad-%0{rL`BJ5;dt&ue#;6E z-mmrLQyvgEYS`-|xpDvGv&eH#;{t-ERS&(s#-Tx8R3Sg?ZEuHt$5x%l$|e3k(itQJ zU9gDNq9l?RIf4MXR}r5TNKXjde1NzczCY#i3d%n*xoivirYrc}d$gaoo-n>ncA`#O zP>@)DRR{H7PH@)MH=ZIt^kdO6=r?$c84n5QJ2}QWgG9;Hhq`^#eg57*^N-w+-*Z`S z7%Bnp1EZI-^?*n4XTLj>z&7$iqoSb@=L>5rDLo&m*vq}%F)M%1;&M15{Z(={^KuvL zMfhP7<1sfnjmawZXO2TJa69L=ga7kK%9@?ANo8}<=1BIO3F32hG;E-L?K=Ep?apqe;_?}Li ztP}FW4pTpod#EqT$jm_8NnZZy=E_gfkcZ@QTF{T+9-HoO(?R}o!Ow-SA^J~V@c+Or z5(T|P^bWU`l_QHwK4Q*^zaqm94N*7CeD6EfPRMh>qK1}_yt!c1WMtj z;U*>?OpqOKOEENc1OF~oc-8Txn?#i&gEcaW_1%hYc_afM1;3uzp)kMefCzFSW{|Y-UjS$~Iu}8iX_`pV8@yk(H z%=&U?Fi(T(LwWe){CqmflMuJ!OPjO%{pep`G@%d(FXk>L3UA=KlezEcuud*=;*gQv z6aBbSM*hB0w+{YE)H{vFsgO5OWidAbPYco4j{H7W#+a}4Y2f2z?AZ@#e}vof3J>;6 zmzNCx59&{%RnA1vdt+>LG}(W;)ECGPUB3QN&k@+vLX+1wDF+zSkJ$J?4iVapm^XKCtNjK}@OLP@gL4}a)07k~FG>4Ue&N{4Vi?`;xGNq@tfRnVoI?{pkM zJ`3`Z6Qrx@Ii`HyJvZKub_tFNtTW(zKAz?ZW`wNMlm6ihtb_i5IV8vUII;ut>ubz( z-^9$!B-w+Pubu{<=;0eOd|{Nge${y~K=dRC-=U8I{8q!S_R{y+OnS&s8TVpWvUZ#s z)y2Ci4iWt260?lp{=%=#pCnc03SX=@$B>6ME8M%8pvPv`$QAs{Tumq)zl5b)12=r zdjVIz-f_H^Lqg)BdJuG9^@ks12i==-<<^?jl2PRuA5nuJof1 zn4Uc!@6029p)InBQ14hAH{sLn`fnE{<3zv#mQ(Bsf=$P){F5_t@_Z(QwxePR?V zk#}ol`@hP!PT+nCA1;dk9@wHUUnYK1vS*!z=*)9$2K)(lKI6R!u3^aUOh)~Ytk~r; zn-?DrE3e;mDx2VM30j{@$M+9cuV0W0HCt;)4-hR{^5g%jKe4h=QDf+44n^-Bnlj3i1ZvTLO1ox%xc8 zU6c~KocO|qpnnq)Tndb$2i@48%4EZ+8*mSZh0*)7YFx1iu)lUTY(^hr z;DbNb-;L`M{k>1qK2?sEe&@$;d5{=!YAb(m|335!N#hb9{$5IW<7ocj$KmBxmcdLd z)K5k}b|3@SbA0r%Vf>x?$9It)DN#A^Q@{FM4XH~LILtxVxm%l#=jR$m+UfaFb40{f z#J+iSnXxjIwS;1w=v(AaK>uX0KOdsG^K493Y(caDOF`=nR7n}Nz`a?Z`#SrRnn$uuUduC^6E5Y}jDv#F# zcx}qs3jSTmE0xN+H2496mV`W{2Rx4MFgxf4b05qLewX?kA0MH zxdpk-=A-3Sbvg1Fr!yw~IIraS65{WXGWA>%=r>VWJL3l)HDgXN*2`SM!RCl^=y5~! zdkLO$NyU)A3iHP^Ft)kWu(vD5|oG!q8@3_xSyWuYHF|({VQy?6X95INp``ubm$#& ziKK<(fXJ8*y0@~2L?X=LvZ6q!{yg!u{Jh;eld7*85PsHu!CfK$uPhAy{a-F!noE8@ zK~P4?%dc`L&&*t-xui?CzK~4wryJ_c(qE=FFU%i@7GXm8Hz*DM9r1-lQqUj2<&;by zI43J3Q;08!>~7cvh~K9qhtN?h35r6V7@yJiIO6wdXgJ(4=!P6f{P*b-MDMw;ZYd=A z?|J6g^CbTU_iN@cPnCV-t~3z+ijImF{Qja)LFUHuN_>uo=$OJStu18t3}SJ6Ob_U0 zMwzIh^AbgV2jPu4zO}U*cv<@OJm?bkLP_R81^9HHMJf)5e)UmrAdjv$ikAg}-uOKo zC1oTJo{C=GPW$Xwop{*~JW+-k4I~E+{f`}a;1w))#)17!L%*{`xxi}tpXGB@g4bxjXB-Upnf2Xb=Yhvk$VE0sk5Me`5N^gy2# zbsX!6K>i7|iBh8TpWpP_{ovz@Q@_b4JwG~HX(E2!<<=VM`q~Mb5%QnkRvJLur;w?f z9wo^+k#TT2+3{bv?dhR8gsO}*D zi6t>a_eIJ1?~d{4IBRic)BNG1+M_RBqxvJ%pQoXIQ6`TI2b}p=B1VSv_-;X|c9UMO zY!H;BXLn|0r0cOirZ0bBrlVA{6kNf||MAPUpIPxcNE##}df8uL?Q_E~Ex>_9=SBJd z@tj(UOhcg3+npWu?QGhr6Z!dIyS&92%x4!;3=?9UIM4xcuzE5wIh`Mof znJvu}2Qf`YlONh-iOysHM;kCU~3hNC@V+@L8Cn z=Er@m3I*iF;^GveQ8W&J>Rqu|l#X@%d8`F+-oOb4w-orwT6^cU3vu9@e8GE7CP^Oo#Ek+NHSu;hmaVQul<6f_^(Wc=@NXSU~$}opZA5`4fQl{ zFkA$^(Hcd@tXoeA1}LJ}&vSA{2HL zD?Kn4PUr7#Z)tYp`O0s<74ZGMVR{C642a{0KJqBR&l|6sCV6l|6qZQmMT;VS0D9zC zwU`>|IJ9l{AkOoe7e$LG@27~H8YFt%Sy}Nl$#=}(gnnGe&U>PpNDogL-_=C>lqXsf zPxN?bB$6KoJYv1eK+k~l%7>Ly@6@zQI7IoUDsSl|U4PH_Y8LnoXEJ?=_aV>pXTj*B zvXPxo%GV?TE-I-zm-N2SD=&`{+)HxA!5%!<*j?QTd4l*yX)eB(o9_s_j;pzNQ9Ag4 z8q3*s9&-*cr^*lyea^}b^7T)UUYvKi3HBZOn@G-Irsp@OXjFFtr* zkH0SxMMl%n9U8fa{O;4Y4cUnEq7RM}`5YU-ha4k-2Om{3vx?;Oh$9O0WaS_44c`K~ z;5B{cyaZRdM5M)a$m3VH;K{8l#6eTQORSla`BW%rFO@YOpXNzjkf%G+nYTbWe8m`l7x{lpe? zMtcS12=dmWSI*FW2dPfv626o;RtM-klPw9)bXO%5&v&${-H1nJx7WQn4m?n)+_SXK zwzk+Xk_+Dt{m4OcqH?oKX3_yyk7u%n`kdqs?o`oxW)6B1DPO^~r6vP^<#m?bP4L?> z;rN4j`fl-!PzxO+_dI!m=;mNv*cn>ShHeBLm%Q-kJ095Kb615NJ@!Ym zMDYRn33sWLx=Q?huYMckxQeOmt*^R4kIqDI1Ht=|-|Whu?-NYz5)b6>6M^Tt&fwV5 z5tt>pa8^0mM&Dr{s_jrQHy@Y1GujIO&F;@_a)M{Z2N4#+gHYwM8)4;oevi%jbvJ(B zZIq7Se)h$hivhas6Or)=viE=3w;K8u^1^tb6#5hDmCE+w{amng{;VI@72R!E<;JmQ zv8a>QdGoQ{Z`{D!hUelRCisu%Z0qtv?-EOIfj(7SxZB$ctB_3>rD(_>^;)JyMBgL% ziOE9O4QZy^+kTS-vzB*vV`dW%Remnn&`i&>yDJ> z&?ywFMc3FDoVe^zD8YY3SEBJ$sd)40mPvv?`icM!iy6j(+6fMEZp8tDzfoj6h4Z}B zWN^4~UMxRWQNf(L6G%)XdUuXXE#O;x?nm2q(DzHF9^|=U{h24$5Ip)l@_+Dk(V<1% z(06+~TzctW43yF{AlAQ1Bm{-&9&9{E$@c^%CG9!LTa9?IjHe+3! zNh*Nvpg*n5WpM+49(%M7>srj2Y>t?2;Kk$5m!f|e`nnG&lz7QD3H($mJ!Zt7ok#gyk}SNBRK(hA%Meyaif7qY!)dQAk6_W1axiU23x zF;PTt+&1kHNDo(PGIgZ4mAYkjK+pW1)Z~41pGb(kPx~@3rF0U#JKugtO?-Zad9sM^ zw;vK4{jdwgi4R%H{+s4iuJq%Gv#Y%YxS#W+P6NJHwsd1;C_OiJvM-C|Mt=4O-w}NU zPx#j);QckxpTMstD?t0uVDgVXJ?FAluSMOG5 z3gL%M+X%R%al@vQpIdnuUxIwn^lIcoZ14Sy_UEK_2=>`xj+vFAh}-iY!J-;ptgrXgMUQ#JK3(r?k2Qbhbh>1%cqejBCPsQ&?f*gv>{7tU{Twj==_ zeou6O>eh~Bl_|+?2>so;a5vUt>^~Dqe%ZOg0O$|>CeDiOB(Ea9v*#D!vU&h^H1z|p zjL~xr&c~_|cP{1-_bp_CPJ#hycn%AZ#lbEoy6ch=e~b7R(aDLb3U>W`=~NGoNLZM6 zIphLAX1Y)IA8&L{f&aMqMd=xtguh)zbspV6x_Z74@8gt}E$}~}?}5MIQbaZ4)BY^* z4J*rfYj%Yna4x;59Et!xF#qdJ@Fw^nGPY5@J^ET$UewZaytk9JlutV1n2V?NWSHuW z6d(5aN?_+NE?B^UBz@0%|aS9|2hovCVWW| ziApMXD$U6yNJ_)Fo3sNPX26)sdA?Fg%{oVYJbtWs;oF#tXY6t-S%z zkkk2{&*zZ+W++_^_`*{(b6eL9`$MKwAU~*(joz=0d;@qh*L1au?rR^*Lj9>5b*%Rh z-HIr$7VMDwz!M+*(|EpXO@GHY_It(gcHec}f7a=ux+CPB_`o-j|I=x*s0ywz9TL2V z|1Sv&77@KiL~MDK>_1bG3BN~KSLd~#(Rq!gQR4*s^GgnH)5G5rCowBM(68>fEazhy{=$eE`wqE$YZiTL;NLqq>4ltS z%bmhIu)P(o(afe=do9xi7O@i6V&|yhjXT-YsMw96v@qwDN!9j$VC5p%eep8rO z7N35+n;*RMQce<{Um{#Ah26tsk-{L&mzcB1t|;FF3T-+Na{9Z z;GG8-{kT5#(x&!s@Y9YRDah+h;}mWIc!vJ%%1G3M!0+3y$&NVg=8t4$!9QVTdH0>r zzpZSaYVLK=E0-9WP)BjNo%xOMvmsCK?SP-~@bN*d&<%V!F-d)f5;&vIds+=Tojg4S zJrDNf^U^3c`1#gGtw8o_V5$k;A}hb$r#X-gxpcCrVITB;HW!~*L&xdy8yY;H&lN~9|M^^lOicEYY>kp#{3Vy zSc5*;ReJOR&HRr1D-TJHpbz+OJ+VB&yU8OG6CATFcxjS)8VLR^ zr<;NaPkayl4gRb`wj2|X{D-{dMG2mbr#PxjF7mbdfsfZt3fm_~qSyh~wGdH|o^mbtfxPR~EH(M5Fj zRG z1Jq~ewZ`mhKb{jCEkpeg`j9l&A>ZrX-m6({h|3^O7aa#Y=w@4r?yNT;Cp2&aXA#D!!% zDkS3Q!VK$4pB@f;ltdlSqAuXxw$3t`2J$KeB~h(2E(Cgf+s8kRDXBPi(Gv=6Je*H?Bl=3MI4y}&z7PV2CpsNH<+p~W^ z!9QBPGMfDVxfz>s>DZgGWhudXanv$ogIr_54~0EM`rwic>(&zghx-43=>BM#5%LW4 zS!SM^@qn(!60-(8fT!uWQ31Y#`1PEI_`mSdR$V3Np+1}PEL6NKTJTlE{%f-c=gE$G zuE`|$0cUkfs}1*~FP70n@?NK@t0(^+x@UcX>)H6IFiiVZKP-vTL(VJfOl8DBPC2yM z#84KHLz50XLLQD- zIGzrAY%>gW(C^XLc3_uf#ij~DY{j~oT{k0%9w$#n%@TdT5r3jL3Hb~i$G?Mpj`?Dl zC4e64dVag}2*KyIrln29uX{gkqQ2&Q?Lb=@Jy$A~%IP?y9V()E6&Ve37uo&miaWK0 z?;p9NFmIOP{jna{mFRz_8zsIVoh`gf?-jl)l=#6Hqic+j*cX`CH z=)?2G( zo0|i;S@|2W;w`{$47Q_wPa8P7xJ=ZI>n>#`xro2+=*!wp&vl*7-C-raiZry&4SZG% zO_WvuFYD{eU>~GmKGD%tq<6NcY}@Gh6@iSmyMaHmb#CNAZD11Rs#Sjc&ez>>&@o6N zm8qe3ONBP*)h_hua{ig-^fV@VnZz2i%>#6Cz69K%? z7!0L!f0vWyKv~(J0|N&m$}5<=#Rpq zix={R2Att$HRxy5UUnLE&rJ!4Q-I&d!^p3vXVdc?$KSyH-~4{%Dw1Em`5Rlp@q1TE zTRr{Vv!=hFo^QEu&_QwO-oekHk0NfOJT0R6HT}~$u*){E)Z{Mo6@dRotyUA=|7zdz zU)|#q%j4o20 zp?yc6PZ99}jbMtR^SM_ityYTfINk9Ck7#dVB*Aa0)-8pga?ZBx;!^uiTu}FAJF$?j2f|S#PLM^kP?s=C@`I84*MYQ-F!nk_51U)Xa1DrBYxoj^qLs(#5`U{SrXPgI;xZcFS%S@|1;s$ zF82FBD?$mb11F}h6CMcKc^B!IC3E*5(07>G#XP`+x3wA8CE>Tx<~&5l)_CLrm!luW zh0`&3J{!;L)PZks?Lx4G{{AN|&6k1Q$7u#gf3bZq_W|*VVW)p=NqINF@>t77H}VW> zc3R@V@7bu+%t#&xGs0su_t?}V6Nq0N>m8^g{XcJbSWErqmq*7gC-`q!pPL~^zE^Ux z2>Kt-)h0^I7V-dM;*`V(wAZuK^EWqhRQF5#{(#0FiD)Swm7?2CdJdcAGC=2i!fR=E*;y4E2$$e%snk>!$Jn?9i9;D_T!|510rOcST2#Gi&Aa;dFY|D$zU9qE5D zTQOG?`>^i2nE>(s_5?5Vt~7RLmFIvTaP=$NkSA)z&YsFGggt>d7vc(L$umvoIMUI- zvclQqBzRAq%Yj@jWR}o)+ge|I^Wc86E@aFNOc=!xOwiH;@E^TW&2f%j^_YbZwpev-zC zyER`R zq<-h4jIu|7;BkrRvj}(PQ5+86l z1oR^u<2*jQ{Tkv!zin9B$YcJ@H1b8i#J&d(_5&_@{>zMCi+ZXy@*jl{4uIbHlIEEq z9690s{Z8-$UQ`zGtrhaUeK>viH~b!Uetg`pAP>Fj0KrjS_6+*`fREnYx0LolsTT6x zsAp-4wuAok?0#U%PxSqu`QD(1Rq#y?a}(qSTQqbdo&N4V_WIRs=t&bsleruG*(*K! zeFgf9WoFhI;qBvvrmfi5G;X=~SRKJ-`nC=FPa#|VCie9A($LMb{)@VGFY?WFxzh@u|Dc~0aww}SYqM#Ehs z{XAAVeLD%)*>3cru4e=C&(1=>gU7+566iaFnKqBswb$Sa5dUDVFwy(q*L(Nn_(1>A zQ|u*4RR1BCA-|}bA05B4KdIW!2X(Gpn?!xggC$a0&p^Ke>l)|nk!^Lf{^**mO|*{U zvyu?<2Rbds9pDqZ=HNW~S61-C%!F1ATL0KJaEjPpZJXy*#N2AG@6p@;Oq6<2H^*{^7S7bu@Rib>G|OMh5WX! zXMalk>5g0eJ<;pn)Ri79^sl0KewN?{9xRVHQJoLJUzGuVjyvCy{*2@S-0}&43tzTk zO`9KdYP9d#NpQdC*mVN<#O@xo8A)$co}D>Me10Y`Fh_7S`aJu`@poyUMZk5O_~ch* zR^&HIMMmiL@E={;c`gZZX2(=-4Z&Yf|Cr<{KX{=%f#9c9sH7C1tNyOFmFUX?2R}qT>vWNp=G$;eSunjn`u21)&hy4jia*M^ zTVmOCI@aByb~-6Pc++|f_`e%d$>ht^f%l0qt5Kf@eAAXdf9l~~N24OsLFbu*2>L$q zPcBZm!C$I6oF2U2!xvfPBI5rC<|8BNeXk#U=S6w>;vKR0NA!>84iO9Re__jdg7=WN za|n6>CwcbaMHCM_yZH>_!KD9dG-s+Rc;AV$!gcWbVZr^ap7m5!|F)^o4}Q=PSRO@i zKN9X$kQ^|X<3O*AK_{|k;`>UwAfxpMJa;(Qhs8H%;SKPlqxCQ~QyAM#h2 z>*hD|@s*992-=^PxrM$Wx7wGtk{*|0+YI{<^(4w7=m}{oK_nk@L+>_T$(NEoSl2E_ z9wzeL_IOqR-pH3~Z3Deq8Nv@+SCE(BhY>^i=}-p=wGH9 zoO}cLu}q)SOZrQ>a{M;!Z*p>eo~(fUKic$WiGmVvr>!{CKh*b&U*qk0}a*X5g^w&M8;adHvyh5a{r;g?I}oHl_au-n8h2K7#Lt3)kMec4t)l}Sv26RY>+)g^z9S<~w$5zE*|9Nj+5gP|y^tP@skiW-VaYsS& zBkCVC5B~(eep3F|@UuZb z5~h*Ifjr<;mxNXiE7vAsHP~G}#3up^^!{Xr8FXIo`&T)sL@ik#cK(Uv$fO1^!pmMTSoBqiJU6{T}4tD_1{;!_4-6S=g;pZox$~7 zfo8}M1Nr1FI>v*~Fqxta{3C&D^xhMoAF^=CCGd@I&Z3t>e+;S6rlgudM=Vd>3B9X~ zKYyvv0KUt`sXO#Rrcte3XrI&nf3RS|52*h0qq^TbLGXX%g~eEdOsRF1OO%igTD3Xl z|ARl`YdtB6sOvst4tzAwb7%w0+TN)}oh4H!njZ@R9X8+oZYTNuZLQHV7vSU`Io(ch z*Vjg4pL2NMpqk!`UsAa?SB7cgzulZQN5nWk?mt)J|uLSI4ng$MSe6F&64 zo!Z%r`~R5tKu+?+!fX~N;!$$J48I5b=Dz&GGF&%5K(vVP<%9N;NUF~qn)Sj@vXB#t z?eHTYj-wk<6WvWJ#da0;GpX9CBDxM(*8}K=nCfi?InZ7_(jjU-@ZqBf!Sh% zzX|(VNBI~YKHg9bJ+6zTyDhf}&K5aV!>i9x~ z`BRub3j03@6`!_xtw8ku!ZG7q{=Wjvo}!@-E_rd&)@aP3pY}fNzx<#1^C1;EEPc;E z#-Mle&Y(7h3u}MjbPCyf79?7^K#!;H>I4_~_0o@oe9=R#DZo$#Q!)%XeRe{sOd2|8vv%bZ`HMMs*2dBI&Kpm8SblTrR%fc`3}MF zspICGJ)o~obnZ~1`z@$P3KVz4QwbU=dqNoD?n0T=u0RFGCHj(rp@%JfjfrH@M{K_rpcZj2nG>-s3*aExi zS5)unbE&S=-({*kzy*AEB7Y_i{I)$TEFJXDg`;5;^gk}NbwC?pD&_Vo!T~oAlN*QV zeb^UfX>20kHfEfrx?Uz;RzD6aQj?NLb^zr|=|x1h<0q72)ahvljz)-xKdfB0D4pQ2 zBzYhBwi9!uh0V~zc(;?ACA)6tMSje~t7?tr#Fq%~)M}a^>_D1Je;>*30|v`s!W)q+ zHi+~`U7!*4j6ObQE$G?Fp0eH^{W;`IxpETw)XhgcYz(05!w+^;!(YXFCxhf6xG(&t zkJ>_r-^A2C3wr0L&+6bMoZvN<`b_XW-qtt(Ik19@i;F{F56mCKKk)gzigx9%3I2w_ zjm5OiUzF(gn&}Sj^Ucn}|I6h~sQ~ z37x-jecJ%>aZ#XnaTl)tx}#G<@MKvRbbvQ6dftNgLjt$eb#lB5_XoJrVE-mCQ{3B6 z!_MSX$ID@V!@vK*DEL4rJ60-=kej-Ye{?WH1-Uuu_EyK3uJ8(@YMk_a^+*s;^1!K# z2Aw)s+w^dOhacmg_4oHDeb6i%?j^aP7ph^ud3gVQLFb7tM@9+)y`SGt#2q2H{|k3P z55AkTuT>om0i5p^41;f!vR|JoITHeWZ;Ky#2j|%{D*rL~Z&_8_Mf@%%Xt@pLd`litrPp|Iglr-x>De zU17YL_E~ImIDd})ik)>Grr${`v<>4t`%YHu1|MWu>Wr}<6XJg>gHZPwZF+IhZ{H=j zhbq>;poBblJ2_K`Mm^BO!Lwen4>u;RTX&iJ7W3y)Qc#!6FZ#*Gb>REVY>qfg`rz1& zReK2jk2gfVjIKVsQ6xS_dbuccp9tT}N68(VUC=us%-@~>zOa-tx9^i*aJ0F3t}o6B z!MKO`z@CXxc{e?Gx^IH)h6VGNPIQBRsC0dxw;a~03Xi7vm@eh!R?_>j9+(B9|Jz?v z|9Hd=46DV!ul%L5rFxRnLg?G@*RRMw9s3OQa=EEeld+%pwhazSlE;b?3)OpahO|NC zd3Q0H#8w9X1M4uG<8hvsg`Ps*eku20*sLY{uclH_k%)a9(b(Hbe?M2g>O1TwH>*(( zJ27|aoU9mnBl7Q}vt!A=+mk+~0UzPp*G4%AA9V$5pnt+n>7O9^z?-wrpQrbaxF*Iv zb@8sCr`8a?SDp5%30@J0AA^2@IF3TmME$3Bq--+NcU9ILS)Kqsuh9r4kfZ!bXC(Y4 zN+yn&m?nH18Rh-x{wCS?&+WPqF^|qepVV%wE#-G_&wV)>`kxi@GTOAl^xcGbA@zO9 z%-j+|cyPeBXe0VUaqeN;TpR%WBH%L3AICpDmoJe~=?mr?(? z|Ct=`v$;}(eEl4D*3MqE9_kL|~m&&|qiJdlUTFW&F9C!~nA6(~`2U(EjInOPeUZ+fyh=37-brrE~eIqg&I4fiJ9m zXvjfzgX;&*+#ozql`I7PBR(R?!(lhFEL~hiwCN(|&j-xaeLwk9LkYoukq9fapJNc<)6)~d zWY0B!rTLolKchLdiv0L?zI7D%&bNxC63S~Cs0@Q0ox?AUsD-_>f^EGy+z$M~ci*0~ z_ne8l3RTE^*g3fp@)2@=#*1Bghl{XTTDpKA-ogkQ(S2T|oF_VRHfl9Qe?bjOKU~KI zo2@_vKdQG~3B3V%{j)coc@6vV4VUOn#J(FJG@M_NKh7I6OW<#IA%D~?C3uT$ErU+v zM@_$9E$ZSg?vP1D3c#BeOD;e@!%i`tL)^;AH-G@?_sr?d=5FA}u=MmG>0Mv%?sWXT z7W2r%99Dwwz7xhk&?grl5=B!y#&c;B-;219L?n9`-#gH|XBd2x**+bZB)1h|9Z_lIr%vU-2^CEi; z{vLMw!Dtug>(1%rDbUMNPf~3pdU(_-wuXSep5gtTqWp#Vl~(L~0{X1qtD=2;XlF|a z4|@9Zt`fe~@Pe80Z=P`)S^)>9@1J!Oyhl>Mk(0ikX_KzT%5~P9Sx$IZ8pKNp-ox&^ zjxM}Ula&8oh`ukZTbBj7f;mE^xPFX(_^cdy zW&+o~#)o;54i*)vZYFu0=5BzWznfo`|L9gZ_^RGEyq@rVY4`zuf=|0m_ZY!FFs%7B zeFwTrVl88XmR9%$;iooFr-Yb1{Nk_u{YehI)Us4c_~6sE^d(~7#`pB0ZXfgR>^t9} zV@2CG_#<6>hejnSgWiGg%o6N7i;#*NNUvAhwrr=o$uWgEk@m&7^rZS-=mUZ*pb2=y z2N|Ri;s?*p&enIsUXI6L>EOK=HHdnJ5#7I#xOdS8;`>IuKH9XQVpH<=?WFg6r@c0U z|8l7W{Qvv;^WDIw=-}9u_#WPzcKA`~ftc4<9oU6+{9;C18TR*_;`_duLk3+M$@#SQ zqI0CrBcIhkc>Td=S1!`NEY-c1P2X(`S9X#-c<apLmO z5bQ(u=r$jHU#HFhe1LvgIHhypcubEcu6>x6-yzA1}*9Kl-{N z-aRu2`I*4=_Z56V`G0nwq=CNQd^0&5*#Ud)>w1bNa%VRoN!TzKm&BK%PMFl-ptmpF)emA0_%WKg_sCa>3wEg#M1XWM6eF z%V00PEYU~O`DI3+81O6C*{^|rCqKNcSVa34DKnU9zlZe~E|4C|8&xAT2mO3!N8Dvr zmjBG!HET%D`-_$6`xtGi7v8SfP?1o9xF7oeMVsb{=ZO*s{tsJhms$9Fxc`x71^>!} z=ALfsH`3FJY2O?Mi=PX}-n&LE>3zS64@q?KE0|rSaFRV}ln(dHP0$Os{^+?F@O@X) zz%<=g+*Mf#c%V;B^J#)tTdP*y|C2SdpbY2dz4-5q^!F3DBOd9-zW>zu zFp>5@P^!5P`OnR!rL=Y-Zr0Fh)_0-bn=MTU9mn}ILUZ*v_Wv=J*G=>4b#9x^iTgf_ zfBC2f_+Z}OmQ4O3w6BLBIDxxo7GXXZ;)0^k+C<=izH{VsV#TO-iP8l-z{&c(DJf%Q z@ArH=$|o+EgF&WiiV-y$dw$O1eRd=C17=fDplgki%bdD;#a?Br~2cKUiJ z%#SIC!FBhE4U%o z$~L-xz!?bmm9lM*PVF8?Uz8P9%}1SlqDia}(0>hn(_>4KXMp~&1=B;|FPt|(2L98< z`(1_K74U43DChXb@|v?sf}ixch+;Y)QMK4b*PHbj9mKb?&M@gloKNcgWG&GzXVa4% z!FjLOf!{k>?#su;L=Q7eKzz~38b&=!A)lCwcNfTUe}9I%jObS>-P?TYd=3{<5mql~sLr>iXRLez_@P_wg+I`9XnB2|jqs1hJQ?r@ZlqHS|L?*q zl}b6+|8YskrcERVMC&Za2oL7zr6SS;y{&?i{67;D6E{zC^?-j~UN_>oflwO} zXXTltp#-08?K0Iko+~j$7>WK{b!I)T;~tX-gPvD#1t|l_H-Vg(`meWPr*nP6td8)& zAXb2nj%wZ2wd!EZGl}xiX3L)kCEPhIR%oacd<&nx(ULstwOvA za)$cbB0tB;R?axi5qxcRH#W!MyIiMLDw41JWJciG&np7e7U=)58_j(?2(R|&@{U5@ zb5V}g7SgYzipD_FGlLKJ3IuPfig!_6l;p`&V-AnJm>QeVjrV;iCqBNh0{%ZZfTQ8( zPg*iRl;D5hi5E%;{(6yUJ{>i<}St|Y2hujCSt|6f(} zD$C*DPEC0mRgC=JWybIQK7s1EZ1lkjv=rNO0C%ZhdoatD)=JwF4APzOL6|fmm%;EAn#y7pt&1;p9_SyTfkrPBcl}9 z*A-lgvl;pV@=x>&hqol9bn>ST@4mp@?()Ad{nG%2dVlw?b^r8;G z1G$o_$sj%_5=T|hb0TW`1=4>rGgA_&{>b#pUi3#qJ((ftMdq|QM9AM}5K{}=F|7ZUO! z!8`25y$1;X#(9sF&~e?mmmLH{R_p`jrMcHrhSG&coyps$^G z@j3kOzD|1uaX$X5iMB$5d(x)8KLlL(|I%6mTsTIZvID=5evGnF%G;|y(qN(c))nrU zD#QMVs>Rg=e75baUdh?XW^C;f7r_=T&4M;EUMjac(Xr3xwh zP<&myW_t+e(K9X>2`<(lOH?-xiLhz$e%#NAM`h9UoalpR%><`E8Vs-pT`Ya~Z8_nc z_;8r46mYAos#MJBw}KgVij&2wY{dkxfQSei@e$jwHy!!QrcA9lj^MRv(-wiA7b?nt zUI4r9Ud}Fj2Mez;K#!DHd{lo#nB%hwXD^+XiJlKFgZ`|w30mMA-;gORrR&s!QA_&b z$@d-sy-e^Km8p0x_QjT3a}{_xH5x^FFzWu+qyHD`zoMcf(eUp?ek4V_C))Ji$Lfv| z+yjp74I@1GjbPZTG+kjgPE_@M`ycq1L4HUiyp;TU2dnE|hF-zDdc1BI^a6FuMJ@;H zh>%&60KV|%{-hTP?ith0xw`2lKI)GfAVI}qwSe>dIjIf%x`DZ3+g9WId2eG+D*3yT zlC+=Wc`T!PZ4vG3h^Fm6`S%wZkJbZT?3>9xC-nhPyPrKmeUz=%`Xgny&bV&h%jo{k zE0R+M=>5#9Hk(O*{MYr6b)Y9^v=!HQNPj5UafbGLTkbjw;X^>kZ@(PJ{o>Iy$cY?= zF=7hp+v^=}1wA?W`|W>x2L2(|zP{K(@Y~gsmWSssmB&f_;aEYw3i4t_zSv!W^RMQg zO&W{Bb)4AW4{$&|DEem*-%cCUksZd~NNESYpnqOvOAG1oNvVkzI*!nXE(d)1_sSP` z5Z;O6;`|7X5uwG}3H;vFT2(aNf9X!|NCNOIF*Ui8@+A)*kp>c8EH9BphE%wC?S*ev z5WQF1gyk-LPw4cc!DK%lZFdc!|C=GML+{Qr$+FP)w~69E-K7sM5!^Fg6c>>{!2HN> zFn<($Kor?P_CfE>R`0j#?l8EE!^$D|B3-WNZovJqrsv+le(_)EPC8t`t8eW#MFQSm zu68v!amp zfjy%aQ~yV<{m~hs$M&1oeFVpV3IBQ7fOn-ewXzKK)OA+oAUs&JwJM76QF4#=HA-Q}dnV-X7%l+4C2-Fj|H;n? zPlgM}W=laQN2&x1!NVw0XOMjFDRg#p;aJdSYKZ=?znHv^>|^6_qmAB6^ecb+T)(f8 zF(1KO|2*QXzz3y$*Vko$_r6L2&vCMuLY*7@pSK3^pHse%YH(_X^3c3r%fuv~Qu`93vi0J<}b;lZj52gTXl9krw`+mQP z@;^fLRXLa0OY4y@rY8Mf_Oj@F8O0l<#xU$3^0}EKf#oti3Mqgee4D^P-Q}3yCs;g_9xh9|j~zkoTU$yhFD4sa|RK?#WW3i$Fn` zo5PCn?6c4l*n4MSpkaUVrgdKOGdj{W!vwb_P2WrsUsH%0qRSya?$}2xr9AqGrc_nB zaJ^yJ8=sBq&Of)X$iWBYOPvDbKl(AQjxYm{QHSwUO9Cu{5?sF3Mx?AC)eih zBzhIqr9=>&4##(1r1)PG0s!Ra3tMTm6aDMg{|0eC=>LlY9;J0-MHsN4H2>LEwg_|w zn}a{>JSGff`Eszj4SsLXYEE5OM16tgJ$;~qjys?Hb_>M^X3q5{;P?2pzVRmV5AErE z8TtRHgO2(bew_sN{w|{gbsoI;55MuF@3C5qAHyDIGFL3{-Noiuk-wFL)q#Cr7+_)C? zs}{KldGorIStp(w>}oqo^y6;QVP1PT|4QEW7jYf*)TQDBh&$}IhBs3m=%gw42*vwO z7K;YK2iVkyoik*2RK_}&l3YGopoO0k^y)KW_&N~eT7S#SGzF35eV)9e5%`2=qmv^@y{H@2l2aBi41ULP3dmrZ)da9 zJ}vMyn>T3OVxsr-q@)6pmsL$4OF~RJe0}(7dVA1~JLTk^GT?_z z(Ez^zGG*4sXO$x^<|r*KjW&()twNNI=>0d+jmQJJ!wf>hoL#UfY4Iki5Btuy_VOK8 z@;b;rE(iOVY1IE_1NLuk$qRdd5B%19lSvx@*Sxqzz2FXr-wnGH*kxV3-e^PD3xhIB zz*{CDpFBX31A2S+vS>c}kxL(Z4g5)9`Vc+*V0d5YIga!~tJP9O^l|FMi7Ma=GxxQ$ z(i&QFVlm*$wT;#`l3&#o6@4NG`}6g^8TdI9xRc_g%Ya9$(9mKgx$v&&@m1OAr!1^0 zuytWyeBRstApY?t3m+nVLa}G3gZ7DuOSa{}Z&#IE1HVKL8&z|y*k{HX+^AQ=lfF`A zlAe)NZ#_czW-uJT4S2Ek!2fH$alWG?eg)>Ru=Jz{59#?-p11D~AX_Q?9QP1Fu+- zq+^(#A1YEj9s~TEE~NSB&{tLlN$I<750j^GJ$t6)yg3H*0rX~?AAvm3ZzYh=Wh~PY z@py=71$*UUN8G}!XcPJKh|XVsvl@QC%WOzHq@lXsfH#Xo8-NdQ203;H!#?=ki~C>r zzy9w8FNx%UHV1sS&kAJbW%+@8(Lou`j0;XTPEE zUffu7&V~K@aJJ3eh5P+ka|8C1laHQAr+sdg*=LB3b+w`3d+^(&r0B?gSB>~m%b=er zno@@dFGRhSzoz?#?z*NzaNmP7O5Ou}JZw?92%l`ibQYcqj=y3boo}f*GY8k?A1_>p zV@`1A&dK>RYl9G%S8{n@h!pgDN+tZ;cwS3wY{?1^^#rh!$X?SpUpYnc&AGOjevdO3 z^c8?FauKZ#@Hxyk;RWz5_~{IBBH|l*zn|35d#F3mHw6;{lT!}@FX4}}i={*t!s2Ct zz<;haHd;h-h||H&$3FQAo5KnIh6|l>v>z~|badWhlws~l4%=*QL7W8ke@4Me(KLS^ z|26>rHfg+w^u8w+H~gOXK=P)fB?SMZ4;0g6A6|XIZxQu>cmGHJpYIU;BnNtcBw(?K z$h0e@HwM|$_6G2OI!M8NkW9(=de zgTJp(xSA(${}+d6FA@HK8*0#$!A?j_tVaKbGQN2E(KHv%|NVB|!Cb)q)2f;z^oL;w zD!=dR;2WOqDFeKMr8gfZIVx63#NY?)?Ys&2z0l8TG-dwmr zBuc<_kyZujO3}Z^ehz#a{;&b>5W!0oa9j#F^3z6P89m22T{TN^j??RJIgubUipyCA_v}m{y|bY>MPm(2iZKq|BLuebvNWrsRR9g zq4$Nn5RyQAATT0+ZvM-s6#=cs_r4YK-`ni0VxqK8I&FD|-Mx*4gBnlw-{>_9FfPyOA>hJ4yd_#?t%h z_5V(Gf}U?WWiq12=QDfet~Utb65?m!!L}O6Z|-Kh0`!f(U!57S!_bFFwmgdH*iTCN z#HjC_o&cSeVXpBm=)K5?OsYRAH|4O&b21tIT_SA(pSjA%>!k*=Ym$>1ikz?qGBZ;@ zB0EzNnVQ^iSQZgq`_<(qI(Zrz~e#mZZ z0)6r`qKQqeLzr`P&o9V%9nOw-=~^X zu{1BiH`8_#_|1(S70e-c?##co?L?n%CRi&UweNV9B_(Z9!~e^UH%`vLFT`nY9rV)m zr6V09bN7u12ExY+Z#}^I1h)FN!;kR!;gqilSc&4r@$|h#fZI=2?uK{1jIB|W07~d@F?`WO-xDjbLmz`bqD>+3 z0MARz>P>|2kBOd~Px`$0aYZH3#k98wbzbot9Kf~G5gr-1eFrqHym5Hj`+Uc zi}UC5zutWFB|kd9u1;ek`ahU)&^AW%|L~9OJEhX$xC*(|KBDrtz}ICWQ8o69FB8&k z=3rm+fhV_+{U>NsFwY=|S(O(@%Yg?ESLj7ofhSsf5a>FG53hXhEl5SaD6wQd?a#{9 z+!Xplv+05dt)Of6D^~~lJY3}iMbgMJz+HdUZ2_G#^@GB+9PFQT{_ib0*#ESb=LNvd zWRtR}`W(b{$`otHi5|;tJY%8#xa8ZD>O{Y{(Gk0h&R32J@rXOH)-rh{(P4F<0`va}xIZ#u|tR~hzf zE%XScu%)EJ|II(#oPLhzRoq%|(}VYl4z0m+y7|VdZs-v?EUoYC4)9^-?-y(V-LQup zaiBZQF+AQ7K)>^e<2>RJutR;WtcgSDZ`U4+dUWUoofqSkil&%_`_&pqnr8!*fL;~1`rUPbf5Bam=R{(-J% zSJeR@xj&}0v=Ln{45*joU=COy>qc>(U%_V#~!y4QUGyh zvFxuCu+v6HUEuHV^9spnWIvr0iu}iMy?S8QPRC`g1;{5x-Aym`332l7H(yC5xS_tc z33&_bgd)O>?_;v*@9EMmlSh(X8rWHoRL*Eli*^gaBW2g-W}?fLN1{R@$N7`19)1`D zxTU0|?WXS#CEA<>$Ji)K7W90+!+DzYBR*K6+(-ODT2-~5&Uea!0M|0!w|46QJuj+e zwTSY6g`xf%F66!Z;+GT92ND0%k3#=J|EF%PE*kU4G5-yDf9UTYDe)utzbO0${{JA8 zD0bgR6!%>oN%^7Z{LCx(Ad>=9BdP!27dxafnFR00+QMbVa{kVa0YM}pz7S=$oh`@t zjJE|F>4#`5_NJ0|@j`~J#)ae6p{?*A=J0W7G?xR7Tdgsmk3$0bmY#t>6nUjPZ-2ZW zaGv$~yaeyH_Ngfk@F)EAV?6{fmD(gi!O z-Jx_6JhvE%hn@Icfpd4#`;Hvphlr1(-7)kl*ulX<9pxu#w;L_wUtALM;=f1^8DuA( z^FV*8SuUpdKJpCiO%gmCGrStI>l8sgFZqM~3=Q!6AU?Zi1o~qR7i*D{oZ^cWP9?3) zu}eps%)_tG+%hNM(=7UE^3QgvwWnzw-{BtJkBE+K%Z-Q|=di6vSR=c3^itLk@$cr9 zP0!GCjmBSC==$^SiZhE$&>y_BVFXW!r)M+y%X)fpOEDLk_a?WC2_Gi@y|943&uH*+ z#Gghv8S+11AFK=y+X#Qg?i}58F8r0S|B?4G#w0R1)*#dV#5gnQgY(vj5>WroG{YHc zIv-RjEhqV(k)a-jbw~5xBg;YGxftMa74Xdbz+fnczMpK?1Mbj=M1g(;=Z(pJpXXp- z{o{}ihW*^$?8~3y3zN`Uc1mCiYu^ze5-HTMK#WJY>T>^lqHLYc}hsUy0K>Y^VD(K0SSP0>4w_qPp)K zetOTY&E3F*no29|>J7}MF~DBT;j=!^LVi~cyVYPH0=(Gd(H_hdf`2%#GveLaa=s86 zG2yw}P%=XC`D{l?DZz(-3waILmtd(fY7x%gID8mz&t{?sGwL>}E*6bu@OQcV4Eh4O z_??BLa+?1>^SgAK|IXhK^c}d5k8%bFVNM)x^z@>CCiE|ZS>!^zRgFRE=#t1=BZZIX zeU*;$?Wq4?YVF7{$$v?ZOh)hxgB?R_aeQ1H`G1T?V&}lXdI-|IL5( zM{)@L^ZEQkmSBBP|NZ{~Gtt)n{67%?7cUZtvNU;bzP~+td(M05?`_Y2@2%XNxA2$$ zAOFPvxzyCc8ZcB-~C|9Doh@U*p?yTm_8JbfvNW9Ivpa%*4Vv?neOmt#Hu@BCkF!1e!IcBl2V+ALNpDw6)G*2Jc+ z{J|%$HF58#exEvi=p9z^*)RW_)<&~mqz8JYO~^~^Mt-^uemL+W{&y1{kV8sdEdRcL zjq5~Yx$BULMFqFElDyfh8}27Qgs)yne5ilw`X2Cg(i(gU>ep zt=5@ubk<8x)yC{S(-T#Gs@BQHMEBQRs~u-$)?cr>R;MyG^HaV1YlBS-0s)ZcwZE2Uy^jfh z{Zrj#%J7Ds&)m&i_FC-~^kKiFjYfU$u_s&Hn~R^!Ood zs@YhIwPCwLN^mXbch8W1>5db1bmJI#7wBK(;^%!N-;B5quWE}>;P2dbzx?G>1otM# z_wgIGxc;cT0{(_?%OW6uIMMNft0W&}Sx`$%8;);r`Tztq~sDewa)|G&fG19W}CP%HSDl5=MnjU<0! zo5GqG;qRXQcKDa5ze&n^$RDO`)9oR6Z|?O;i0_nM>FGf|2q$hG5Msb*3W`sAiNEQ* zG>2{jyDfY0faFhDZ1{sY{*y5>*MAJ=r)mTEjWSAxe8V7HKHG4f{y{$WUiwBv}(py-O;$RW=={nEcDGq`%e5_qtFv^0!(Kdoa7F;u?;vBX?FL zg8qm0uZbSxRVIZba(NclSeKL}m6n5Uz21O;4YhX;g(}pd{k50*Pri=%aW3flzd!o# z75nQ<@Egor`m#>J-KriQ%LD!YYR1zRT@l6269UoC$HeY+ul}=ZiYghr9#W%}Gl`2_Hrb z)YlU6&3U3Y#C_3^EFSvttE~Th0rgI$ywUEP?E-wQD^|o2K7|H38z8S>&sU&NDf$CF z-wC?LJgG2V(GC5wM5~2-Li}K$Z!SMKQZBQ{;JV%dGy2t*u~4aSwEGa|5)SUlBYSdt zMlt+srTm8vPp+o>c6<8}heF&|C@@nTD%%%%n9keVoOh3UsIk2ErDIp9CW%3W7x?tuQu{`_05EC15SGbjID7qT~JAoiI9 z71>###g@kxTVqUZTejY70{>5{irsvzF4)vMxV86RCts_0W0y?U2)Ie5C#;R2=fuR3 zQ)7H?ty!NcG(sL2e_wcy@ItU$o6W_#VfeTKasc%WAB_T8EXWhhnlb?ED10NW6 z?l^v+E{pShb#{#Czu-sN*w;acuCHjT{IC9z%@6b5x&LEe^;{<<> zw{$jVBOk$*ks%QL-yd1Jg5aX*-Hd#q941zzYDnK*H*cqy;2<{0 z=7~_k2VEQL^nnkSG5#sClPi~0h7sQg`(|{M-b4EQ*qr|&%QICl0lmr*-VVP9zCYvn zc<_JnKb2CQv1}4voxLH?8*CvO2@8HqHOKzNt*}M6A^Vgdkj{NpDcJ2}DIOZR%+_t~=mHfnsfwacD zlgxU;4E;8*!rCCxM_2GHFit8Zys&?l+P)v_-ZK~8-VgcyozB~E_v97U_wlY?$a_xg zO?f7`;+1^EXQ2@U|9|%9rESD|bF#@peBh_8o9=_}^VUqYvj z=bkroETn$(VNi^^4!z2HCR;||LEc(@Qhic4_Dk0}vJcq|d{w<-jvuuA;jNc(o)sx? zKSJ{0-0r4NNv{cI895O)iA1251| zm5nA&0MDZlunYlQ8>{Y7e2S&nnw257-E5}qK|7t_wMR22H;#`^?eyUNqQWJm9^he~ zHgydA2K|#9XE67QX<!2g(G-M8!NrLf$uIeRzJvn*%dE|qc zQqNqpL!(TO-b@?VtF^D(9koZvN<0G@n_J8wD%d?wyvln~sO zMQ;YY5gC=C{u1#=)!C)+XM1>y$N+g!#($bDD9Hc(QB7th@s%yfib(Q5_t~Ukv{yj=QBV-UUm|(3p6pKlMShEj4{Tg7t|0iY z|6RsgW9UDdu;{l5caR0U|e^q z!oww_AI5u*E=+y;o$pV;gKx$xs{N4f-nN^muDTeLxmMZ}$0PipYoobOKGy>`+E%tqxAia$-f zkvs@3iZQ3X2=HgN3-Ka?r=NJJH-zetY??yAg|AAjw$XDXQQjt^FOkR+PS=@r?qT2$ z>-($$^p3ue1p{`1mzV`a)A6!mqmAG=P*sb#Hs&Ln^&K1F@zPXkwK!&e3XzQPs#+Hv zP57Mh#-6SaeD{dpfOg>Iw^u66h(EC)RV(znQsmtUSe5yuK_c**QYI5>-4vhl?kLbD zLVirU#$0rM$kwSto&@vu&YTB6@&2P%9&{tmUfmJ*#Tfbz!T%IQ@kPI)`E&l}z>p`A zXA*5vJXusia{qbv4qXuZPZDLxb7Sm6>M-O#=zp*8v8lJG&TTa9+4RQii>(P+2Eq7o zWmBT*jikMM-Zj0N74fuK{{ySaG_xzV;Z$w3DQ7BJvA=el4-PJqlKq#LH{g2>_#nL2 znwtwguu-?Gx?Pxsx`&A_h>{cYv6k=D)9 zM(BCSGYhg~Uvd~9EM6Q^;eg*ou#o;dId$Wv3UFK5KG;Wp?^^id>%?ay_tbNF?@h{c zX9y2$B0c&HAn&MHu8c9|u#(uLXA#d}qE!`D1i!1dJv&H_%*L((K9sWGq45ggP1Cvd z7UEMVtR}o*OFQBelvgnO$CC3gz#Bp6P?NuCcFKi3bQh0!V;ad9#j>N$5abt#6we40 z?{e{4*dIAe75n$W7~sipe8O$wU%CeIoct-_%RPjD)>$*~T*k%0k4W{$2tUsGoq|eA z_fh|uy^kL|K>Q%7YSSiI1*m6Ou0Z_(Q_3&@jN;D#zxzAT{nMZSsCn@pL-r0H+T}Tb zen%B%Gdtbn;c|0#cb}?|?{(khxNEgt+@;aMf&&#QlfS;>hd*$_myy-IrMFV7Xs?1FcwYG8_HC4^8}`6&@S;hrhmA zh&rBk;O7lhO9i$^dtvZ@>Lj_!*NorPj>R zeS?3T6Z)dUH1`oWWt%le}^Ge{_fNtN-!h|API; zY2wyrMVl^ZC7^$@^Mo1!{pap5_e&bgpSi=t>kbAW?~vENTyl-((wrH$p8@*gL@@wETID&IS4nZD5r{jYHFxt%*5-Q4b(x43sJoSfzO%={lX@+u}j z{iOMB-8dhx;O4-WfOCJpRGL%U#k*uR_2spS{F?|E=0VRpV=OyJ`o6+Zin(nLHdZ!! zh4^#L>Xw!RbzOXK;p{Z=`3UDw=Q!wTI6|u?ya;4`U;^ZAax&$OBfh%k*=oX%!j#l@ z>Q8bz?PCSW{nd6qJ<+?+sO~JodZcdA6O`|vO|=fuy1t~?K=dtZEzCLqx~q(}?xg&) z5%(10dtLk+dJ*D3UF^Z7Ju@EIe5=R)v%d$a{DiP-O^DA`C1;FUVD=PS@5pQNx{ z!uxQs)x(zVLrzXHiuqN5-}S#pml7Y)dK8MexT&uXaRlVmt)%+YZ1g=HPlFxLDP+o? zGKM}Ul~Tz2Y^K-xst8|}j;Jk!7ZYD6Z6kj}^6T0;Ij?_aWxX7D(2}yJ5cJPkEOrUq z*XHbl+$!hW_1Lraz;Jm|FH^2=;xQ&w64WV z@VB?Qd_@0ofdAiWLq5EauT|2zCw;o*8t~!4udN#vTf6cH#@)~Vz}m&_@Q&Qt1o_|I zKh!?X@+wknkzs$TyTXFx{deE3jjl~e@xqR~!pc`fS%Xc{wN=LDiriWUUpsR9pRWP# zizB}(hyi}&{_kqU@g4k=FHw9CodB{YHv`^m)fYz8pT5J64srLu|2d<-IEv)rCzDS2 z-5mU^r_cvJ&X3p|qPzK0>H`;F20A{;o_$F4pK*2Pa|CDYr}b^1SKfTIuXE1+8@!d) z1^BEp6uyHz2hI>E0zGH*e~)gtMb8hlAn;^+hoBywFCk z8}!Nr2dY~VVb?ufIpQR^#~+M>KI7n>1NYWAzz-XY4TuMUgPysIx&fMV858OOT%E4z zHq!4Ea%%>`S1g(*B0ARx+CQOv%@~+`y%hS}-iA!%p`$*0vj_Q#=u04thW>`S*XRE^ zKya#@wAIjkBT1>|E%MymdQ21N=v%NSH+*1N5;R(xHC~_`{chgPA6OGB1|skF=2cu_=Ek5h@&{}BUu@bB{eR{`)~#s3 ze_zVPUxH01`9I&DmJ{DT)-ruK25{fqyR8p=o^xgxEaQOhr+4tot1@uMqIW0`tnN4_8B_O|{lx4u4h8*DhC#qKw{acFo zsc!6d-|i6|<+J@E?OhAxFfTfX!X{j=936rFjJo}`XPzUtD}~Yb6S1FpdwO=^I+miy zT|n~vIn{CG0W8c{=b2HL`f9#%EU60c=FEP6V(L%!?=S!IG~u0jXkeQ1mZedN8dMkZ z)&_k%(d~F?crnpu^R&B#`~+j+o@A;UQy51P&%}3~@m&mooL9M2y@Ut)o7WBSOL2N} zbO1dU?4%5NikLH)l0x)u3;XF0XXN?2S$7 z89)Z_-q%xldau>KnkBlLQt%yCySaVx6!7B4n8o{E6YBeuX8v`zPF0bdwkhcc;P{1W zpX<#9|Cg@6^Z@=G&bgyCA`kw&nTNN}slXR5{&{>b27JJEW(a*d9Q=k~*pZixI^Ke_ zUb6Rupr%>q^XyvNnpT3lMblbe3OEbGe(1ra{B5^z(}VNYH@DA@1J1U0iwv|@{qB~O z;MQ9yUqJjLW@5pp2YRB+rGkG1`3An8Uv=ZUKsIoP=w9w8hQ5wDX}fl&ctF=H&*2+Xgw|aZpO_B#FYY8N~A>*l+iWcS4x#O^MJ{Dg*jZTR}{>krBQ_dl0zy`1Fm zaK7N^`FM!5QwjV3jq*L4E1P2W`YL*wDx1bxdf}zEYqcs<(yrmVWdCjJF<$&v z-4%ZH+kwB4pSOQSDEjMO;eVNVEaVEWswkcx)Mo2aHIMdd`HU=*AKkpYR?$Na#JZ=Gccf_=Lbjvmx12-enA0$Ru`W& zKBOYQ&N)dq@C@~sW()k0sHaWkZsI*TxlGbT_$1ON0*}hre357Yy+{1TC(h9GT+5$@ z{tEinW@yKm0Q+Cg2ZP`LcFAMu-_v{L5rX^i{rRub+`+g-iypgz`NQgngQ>q=f0Cp6 zGm`ryUAr_>=bZWN${AlT;Jvc<>uux@zJ7C0JK2NnKaIXj@!vy3|LZwb@mhtwd~?!k z6(&wEY8v|!;sT!c(vz<6uT|7nEh!!|3~8(j{BG^-Vudxd=P)6~Zf_JhbfN%onwK0}T7 z;4Cc##pIvUS{1fD*a_x=w0`(uIca+=-5yE*mgCMWrSH@EwaKmI>4rp;R?abCXn!dth*k3bJ> z@U40la-Idtte(4di0pRi1`*fKUnZ8RzN|BGBSYc4Ne>iGzof{kjp7W()*8|SrRUd2 zfWG;Ir_O#71ALe~GkA&U-dx=JdKdV9*+(6r(D(RX#E7mF{7)a%>OJra+z-EUjqF^bBYO{9-AC+6Z|bA8TS8GZcU4@lJ>jp*^FG` z6X9zHPbZRJC~wH3!ugWpnjy#$Hu16Q36kSstlvz}x!N=uPkc$`v~gs&Pn}!ozBDT?=nT2q&ieW?oCuBG287|J~iykN@J)fpr&MD9L;^uj1tdOGldvzhJ4 zk3wF~tOIyj#+S=xz)w(ruFe?7{0Yo4lku0(pTk-sAm8jVo0oWOZvNc*`2D}9`SXer z1?CQ4<^_X(X+tVMNB>E%@6WtZcyck~z|Fr+7`z64@Q10ihXPJY1M#ZNn-=hYe#rJ#C&hh!ar)jr$?h-jd>#J$ zcUZ9DgLy>n4n^G+>c78vaAO*sKNS@Xe0UXk#_czQ0q0L_d)9OvGO?IhC;VQethw@% zkK(}}_NMQm`oqYjS~K;pn;!G|h+q5{|8Xzj(U2x?6m-c(%amgzCpEtJ$MSHU&hiQL z^e*_}ixEx291-8LlSI$W%?qS+_Q%^o5&8F@JDi?G{MW44`MSZ^4TeALDFYmrJn0vv z!u12u%Jvw*M=5pg!u?#}#aG}j!h58Nfj6&mhVj~A!YAMP!gPZ3Y+LYET+Ky>Cc=J0 zKJC^&c<1DYE=opl&bf5-@j3eEFJR6=4*KBz`4TcIC$Ly!u=08Z69 zoz9d}5eP!)Onr>4ing6oUo~0+hzcpTJs3n$N=w-SF&jIO9ndNuwbaHa7inj-)}1Bow}aN=_F3S5Q@yT_5$@NqVYuLT zrd3St_{h-%CsNr{PhJf6%by^1)taVv>z%$Kb<2T zc7ZioiU)xUVve{8dey|LVZz$8Ao04LMJ_ckBFna zr1!H7O`td8!Mb^i)-tw3LbXa!%&umK4S|L>@B{jfVCNC{9hnckYi75#EDEJOZc;*) z6Z6a1aLaAZUyHeTlW7t3JH%Q^1AZS zRT}#tCbe zoJahSv~c450px=muIrs7*OSlLlWdUpR}4|WYfPMc;|CiE*Yl`;9dc^o*ucqg@Bvp> zDNw;qVBWmgtL>1Pi~HuKnf60z?6Sl4z+KbWZwfV(2V(c@ZJX@ikHY;)z2qM&j#Wy? zPLvjVD<~ek{#YFR%6V+%qVN#6ye4jLgP)zv?bDs;qWdb7`7F{W z)uw?yI*KGmEoA2%4b-24d_-dZC8{?ljHs(4J>+yAMxPP->n{|eE(|!(WIuivDjPOq zH!80Gw%5m(?2xm!QDT9e_=9c=e&0N{C8Zzp=VVMuCONNLhBT74)pI!$%>!xQF}&WT)FjNNqa`J?^NrfAGI^z*{$_&nWXu- zJ%4_8@MR6h zR(&dAB1=RWg4uyV2Z-+9pM2dPxInRJZ!P8fKP|KYA1-3+3cuatS5ky|X3v#_&g|uV zrxBN#I7Xk0`GMP6-EYrkl7FZ?qBPJ^J+~TjZ_SKGVrd3Wh(0f|km&o#l!GHS!f%(u4$)x8SSqt$0Vm4>5Fo^J=Ad7oH4mZhF-X{Fv zuvc>`{w_un9jKzdr05`dLI>nMTU2GCsJLo6Tk^h3-nZ%E>{;65FI6BJ#L)sOeA-k&U4*;;?pviY{LMu z2}&QKKv<0XOcnYBk=*t6w)$ES2S!at-7N0g(VPUl2p&SnwVALBoKZEBF2k|kF3)bq zbyH;WSo+>pRd6zWesJkzn)`eA*OA}GS}w0vKLdZB%ZVE$K54e`z_n4=B@w8|-y8aM z?{`$6d~I_){9V+k@&yX_K8_v*_<1e*!6%Ll9|C?b*z2JBzZR=Pd6o7LFANDHeP21} zk-dcTI|r?l|6xRWHA;%^vFoS&L56scF&NUbX#Z%|KuSt{n=C)>$&dglvmAK#sVm|2{!Rd;1+W4ORCl6GtD=AmSfDdX|yS?_ne&l&-1l@)M zC+=}s7N+L_=)JOH?KZ0WbBGH0ov8m&SN))oLjD&8jvkW&9~vmTsv~>-*zVz=b?^s2 zExoawmvjyy@4goMXk55Y1Uq>0Rx-(deZ9n!=p$b~TTFUbq)G21eiE2G z_q+9S^Yu)~f40TYNBAFO^q)UL^s**czk|QW2*Z>Eq{nLvg}%kG6M>4*aN5Ux*<^BG z*KbHkApJkwvINI9+z1~k!Mtq7?z{niep-eI@y1P zdIWWks8{68z)LXS!b*7oGh11AqY2;7o~dwJJD_j$Mw7cAqEBgs{};|GRcc7}+KiJ6 zLm>a+61^eRK=K|?B8wrsFWPgtfN+3qx*cWodVKtwzG$oDIb zj*e2^cQl75vxC zkK2~E8#s}c^)noW++rT3>FRaz&yy8Zzzfn?ks(1T1D{0tkLJ)ZHt{0#a}ismEa1rx zw5Ph_P0-r{fk=D+`gtN^bkvOeu9>&jm_fI!tjliPU;I&(j{NYWjVoTD=bo~A1NM1P z|7>pr(QV6QDC#-qF-eJmzC`BBpgFUNNmn~54>zVC93%gH z=4nq4;=8WUs68alnf3RU(Z1V>trruckWX;%b$RgDSf4vf}jUji@dp*_{QOEa`#o|kIgOY!1q~K@CKS& z9~wGWf~;P0xWCw!=q-p5co3au%#yk3uKS{_g!IBHMZAn&PdE|3AvNRQR&_nRaf9$b z(}k4OGSGdZK6v8ml7RfloYla60`j|SHs}A&WM@Yw##2s_A1JCk^A+m;{QVi`6mb8z z&H-!fiB%c8_||Ny?=H;xRI(0qY+e4U;j5YVJyZ(Mrb|;)AN{utIE|#eVpJ>HfeM zdzbli+Tafc25weTT#<`HI@(iOcd}uU>^|>Pt+d1c3%c-Xqd%Tw)7BSiiO%Nc#t@3z zBPFWxC_GO?R~P(jGv^hTw2}BCdBkkAL%vMDTpP*1)H|}A@_=uod{9b$zaz&5dk&nS zSHV-?+mZ8I)xaGYL2~m+`kvcTvx@kjeu*duIOU;0b(o&|adR>!&@Tl4z#0$SaR+;$ z&?G0nZz4Rhl=6X&$!|7C!GGxFh2g{aySoR@$q*O9&hf#!E9~4Y)5rDT4^9?%PaY+uB4EdjkkRHb=-~mtMPX78!@Wo`&J{#fz_C)X1rjZ&gdo;JcAN3&In@7DdRv)kXUZfb(w zK;Ku61aay#R%C3@Md7-*&WKT>+q>P*Un6_tKba1^2lEcc)HJ7pdt>8m#2JWFyZfEc zFO0RXYqt#VZ(6n#dJK8puE`wOb0)~wg8pOV50@2s5}zoYzYV1Orz#t)#8-u3nn0=x z>Pbw#OnpqV&mIOovX;5w2viS){#&=MK>a`Le=zdC(DPCg0s-w+%uR`Q$AKS2uW;*u zw5RS)BR+7L4XV_Pf(_r?aBRPiz7Jc`+{d4-^xw01V$V6s2VTx?8_ERz_jO*Ipni~1 zlf4}{p!S&f_89U$+A-(;BFg(YiudQuhIYACs17IZ?82xSgxv8Wuih^oRT^BmDx&|9eo|g!~}t%C~=eo%Tm2 zWb4iJ95~B^Jfi!`p7H^T14Nn)@V~EerxySA64i-y$H!(Of5=Sh69Qj=`~~~^+98M8 z1?8ysy0`msT!S0$^NH}`fCsTL&uhSUY0NvkZiMuzyq?z(9ewiT^SuO#bUD)-p*+S-=I>X84E~E~dC&GV6vS0Q?c9(w#pD`w=g9 zA@7UtooZN2dEcW8eX)-eaf>QhN%rayULdqX|D;^a%q02;OM^oQ|4)*y-$KW0>*X`Z z-}CmK_pBIsU!i!47x{%Y+u&eo2JIisdIp=+5rjZ61Vlm%R6fRpm19$JRDi5ygMHt33@Q|DS#~Z9egVdB8eA zao`!~aae^zN48<#5U#s**^2&E_&<{FY>FS7`_nS%d2UY3$%%p;9nE`nTNJK4+na#? zelzzDGw6%gIbq=q*jGFE`t~j)2AH6QyOM46|Hl+a{ zNaN95_vm0hn@kv{MfiNs_F`V@WXyAbEgE5&Yq?**m>^hcj!bI=_+ z>VoEqcCb;X^yFQgA>UkU2qoxBfyZ)K?(@hti(vy_nkX{q5-Uop*>axHWHj_8fH zPuK+>^Xkn8I*(R;q9D7lJ9>RI>Hp0)y5gU$yo@SXo`1K^&K#aUo<#M&e4o-K(-vo_ zJikvSJ~*c7R|EgeAIZ3L^c48TUu@>x`9HZ|K{`B(--?t@(*N%?bAuEYlwT^)kw2hW z6nci@fy}(xJJ18nP}Aqo-!+`~kwV_Y`DtHN2T>frP5GusNpgFvZ!zk%)-XDqX2Qg^ zWA33r=azSg5Bx>oIF`P6+822>=Gs-dF1sU8M4uD;E?!Le!sr&(J4#!`7FA0zFK!JZ zYHXmmjI~Mz-`xdyy0t@|X+PA?NS?;LZ^+FjpFpqUbNck;VaPw*R5yAH^kw3+v-4<< zd8;uP_%ZgXo^8wxz_ZsbW_}1Y8{Ce=4V<0yVaR$38x^)HW|J?fr-TA+_G9RUVqhZxy zRfPK>+cBHy9-#8@Abp@y1!mP?{#;^i?B=NagC&nwR+Y*t1NLNIoqS07K-Y7#ew=}P zP^YW^7SUg{zo#5Wc10@VJcT;Y{=J_XPgO`u%(pe7^{6x)AMegU{Lcz?=_0cGlbWNS zQhZ<#yx_SLdEzfl?OKESFz%!AAA#r9u(m+Ob5fkgm=eDt`d_w~E(L(DC%2*=>=1C- z#sr?^{jJmYk zd4JIR*l~$B>;&6gG29`;{dmPB_5k7cjgE8^-NV@Pi0@Hv){y-IeVQKDjT8=-JJCK<i0rqzg0a0`^p2NSZ_)@Sa=eAN%!; z%7FZ_>WeF$t(4|Jm-KIW&=KFtEd1&$^aE-&j~vj|o>*5>9T|%{;H#Ww_G=q{ zXR6_T7|F{!1$}Vt?onep@`c}*YVS!)QrQI^=>_EfH!g0-mO>A<9vruloSGu5ylkIlG?d*Lvr{AYFHsQK`+y=f)bgZL{N8`kL}hX$dm% zg7;6_p>JO*mB->f%s^ry>=5F9%gu=QA)lr7KF5iE3Y{pF>@^!fcpvnF!9SAxawz7+ z036ddb?WIlO9c1;IAVe>u#x;VUs0qL`iRRO82gO)IA`6hWHTF}eSB6W>{c;beY_>H z9e%`v8;gU8{t=a%5udhmK7Zb;ru!dGI`1Cr*U8<2nFi7gY?>z>Ka zO3ZXoAIJnJeEd<=1-+I#k%W3Lt?TyIXQ&P^!D$`%5_;gm7_vE!x9_W_J7v5$OTPAw-+bG;eB(hGJD6Z3QOqN#6W2DmhMmwIlzmI>INPKWcA^<(x z*yp^b1?2)XSovrx4za?gAeTHeY%t3VO&R`i13W7et(o zgzUmM9oc-i*LujkQB$G&neGoUzp{wqox{rEL#T_~x^pM; zzo}kgU#}~W_vCG_ec-P9pS5u&$$d4ikwEU@4+x9IS}w!TH_*pn|LEp;S$f0yA@;!o zQ$(ieV9DBlmw!sQ@5%aF2m1drmPOcCbe{Lmx4Y(lM0J39P3IUH^o$6LN8iW(za3k3 zhIHhGMPZNY4*=IwU_wSZ`GfBt{~h&(-5c!xnEb)c2fc0YgAZQXG~t3CV2)WDHBpG~ ze(VIWOY(Eea)xO4thUsJke%Qc zpU3lTraDq{e<3v*Ei5}46bU19l#zJZ&iVv3ujb{ROk!n zn?jmHVP=DbRu>&hL{wjKl`TJKXjqT9duVHR6U`;=99?E6d7ZyR7c$tE}H&x>6OV>x-fIWeHs#=hLJcRr&=NrEs@m`(OXbix8Ml>n90PuxS z=sRDABleigCOMCfju!;r_+dv-AnJX&SY818YTrP-cXBtF!}#9 zuX49`&%+QIE-iI;Wu zB;7wD)4qLU^!6@vmRWiI$ot@fw6BNv!_Kn}54(*N|GmQgyqJ?*o1t^nr<$go?2L50OECp2-e;SfF?n_(Fg-eh&3zwR6IK9!n_SVD1qY zKz>@L)Yrj{66B0?4;ab^2d1ICj#HY&08Sy-i7zER%Q$7i$tr#?FZb|^G9X) zz2kTL`bh3OFLCHkiC`8ilt#MsN{ga^_)r)YhWR7#_mAE{{fF(4O{3v=L;iikI!9&r zJyY%_T(jZ#y%Aeo7=ZgnN2etFrVg8LHH@+Obp8UP!fm_tS*W{YZQ;h96z8Ru)Wwm0 zXzsV>0Ow^2RBR#X{jP6wfalK3NIWbJv;zlvL?jG?-&CSbI8sK>SCo7EF6hWk@saqT zZkUyb_&U-Pj`(rt7d!VT;$nKvj;=BzJx5-t3?+T6Y)O5C_#`c}Zay9Fs)cyn%=!q0 z;2%4;qGFef_>WIlx%W`}`AaKhxNf+wC71lk=fwL#SNMlIs~*3TiPuVCf6#AQSF9rX zHa0e<3A>tv_&8=Ox<|=bJSsWtujRd{JOukQ3|JU@Cpx3DHGq&RM zd1U`rRr5UQg_qWU9`}#^^L`}E_7}|U4U<~*m z@zkfxyKTi8dAW{W!UHu=h8XUn?{ji;IK2#YqVgo$RrCX~x6Up*k9;B57P;kgI_&wa zp|i;UppI{L=y#wull-~PKysh+m$TJ~|4NcIFADwOcXyksj!`~WBKpNS`g=Op4MIAR zZ#DmI8PPv4J=;}#0`)bwXl^9xfO!YycMk=JRV5JrTiE?>y`XM1WzzFE$MdNAMBJY3 z1U|Bc@$?ZPF2i1c>FYCO_UZIW`FFgFQcw{ueiTYkSD z`=QU7&%$2>;y0zBllZ`@H0bDf;bOm<{DRQX8OyN$m7T0`fbMB*zoO5V=;&Q!yqMC3&*V&Mz==;b>RyZde$onL{+W9u} zLwhnEb8L(LV$N_muA?8!KflK*+j9Lp;)AaEPRb9Y^>ihUoG@h!O>H_$xNz-IvYP5V z-m*LIr^7F}pzmms!VY|BFhKsVaw`4I*GT?t-Q^s`f9JDbEhYP&tg*WH*PncC+>P^` z#pD$71Lk-MZsLeN7#jcE2Nc`4x~iHmR}SO3z&% z8WutNwrNs&W3yW_WbAboSw1;nux+oqToNdV8g*w4G-um!gz+>;+W54)5 z@`Q8v{@XGamyD#FuHB1&S6iM;ESTc8N6yniI<8$CU#K(rC z?&sK9<~3DXg38;#&xhe(u%R{Oiv!UAXfT?A9&3^Xfm7&-+Bi#?X$jG_zOCS6qDz67S1lcv$0p-CGbaugib#IT9lVb6kpuk`Ipi0r3#_R` zCl$NbgXs491s{Qe6MO7(}_?;Q(K8pCTolAXJuqXiE zqlkV)*ADM#X-XgTND+%M^8&(sd9_hV^fwER6b0b=-4B-{{zv^U=E(bD|G@kRQv})l znI0bQ{_nuT6;o*MpkTSc#~l|=@tQ{T&${PyYT^GO{EL6vB-MUaH}T*!^1Z(bZRHQb z4>YKvb!DgnANuY=D$N5*xaIWeJm1dT%vt9;2)fTJT7Ba*>^}d|$N=b_-#mN-xIi1L zSuxTm!*lriqUR>b?+fSE$)xYM8GT^~u5t^TD$hhh-ubzE7fB&UTP9l0())JfRm|Pd zvIe_(57hh(6_7lB`hnDY49-Wu@D4&Xz|BHD}W>HoBb_)91b9w)koo~ZSr>z@pa zUbG&6v2#r9PjP<8Z)O<+@V$bl6VDH$?yu`y0C!6RWt%zPh_-_RM zq5hv(WlF4j{^)vd^nXWaADJ!#?kCO<5JW*wTxHUS(}%VHv48e-$rVnhf9k2u^SiX; z8`4(J_$Vz^WDNgJDfGJC^$TZO>a&%+sG2_wePC`l`o~8x7wD!u<{gszk1_us9e7}K z%^=k+4yIOc#(-1*0Me1*7x+nV_H$HWJ_#@{|kuOI(nK{f0J zcPe!pco6C=c{B9$c9t_M&9DdDCyMKjn<4iCl`!5P^yBXq3aNkGV2G`@;TRmeZza)7 zQmMpwlvY`ujJjDnw>z`)BH=rF1@&*5Fb8@di}nvc@9F7DdRu20izUBLm0u3Iv~!+f zF>7X}CBF>j5qIujeSR%$A-VgaFAI6S2BrDVj3TQq9ry$z3HL<2ZsH8B`T^pz z$AYWjH|}7b2o(tFes3!;>;peBd^hIPa8V^6%&Jm9gV#$pnY<83u))5bK}7%fhQ<;Z zUaw121(1Cgh(baTN3fIjU<>63M|g8R$-B^CGCzak!b!_v`o4IlKox-NGQlWW)) zqF=U)N&e@H(f1jtT|51;U33hX{$)ob_7O>f<~>hyX(xMJxs)IF4wY!GFn5-d{C|V% zeh?2mcocr#p7za6?_>VQ29?^c)U-=mckSBD)4&4?P3N1a4*=^^-X*&Giv&uNbDd#_ zkdFEm%RcY{m;J_>nIxCt9)_0*=Q#A%zmGz`Io_ILfZxx~6G^-&Z_`_qWu^QNv%3C1 zKF{UuZX6(fxIH@3Nco-*5=0Wh_w1LKc_QzR{Ka1A!K<9rZl<{a%&dhA1Qg#_8dlBr z*KP;CZNOf6;3eg}p59ky^%!K3n+VhQwYE3x?k2Ci8}{ClLGfmW2b)M}9GDsSorw zo6|k)AUW2heRiQ8{2Ka|8o#%VTPl8fDc!g1j8g$RvZ0tq3A%CKB1s*|cXq-zx@Yj5 z`Ym?oE6lH6=<1=pHlJKKl@mW08@t?oo^`O$gFZjzD|Fwt?Rw}G;!j1NN(ujy%Xwqx z$_VYCaz6TbKyU5F4YMC1xt}7Cyia<-yK2=Gn)~EPcibR5usk{%{U24oNBEcZC*b!+ z<9ElX|KGK+$whH~r{z{6#rNTYD7It{!o_+;I$Db7Efv2y7;@NZR&D0>~` z^|YZFeP3y8ZP%nXo-1c4d+j*+W6w4#6Nw*wtG|%E4*dX4Gx~YDUfs{v0~hAb7fnF_ z+qmSg4L6YQXS+Ij;;xb(Sn7B{c>krKPSnSnxUoqmp1TQio{EuCDTO_XxmbY=2mEIz;fbhFu!*0}{q3+T?8BW)QasT#LJ7-h^ zPZI~=@2<&SC4_!p1;Xma0PwL;-Ef5Re)UqZ1@eMDZpt*eu60?Rk?Q`>mv5N53;dEO zr+gmv6gi84KVY7XOCzLt6`jqbUy8Xw;LlVS>>9a+eLUC;i^fjkgQQU+Q`{Y7!uKkOy&*#)`n>CDAEgI8FC`KQrcpx13oqg!RMimnmXt{2`Yh(j{37Ae9p{yibV14+zu{XCh#PE!|N4P zPr@y*nnK$TO=DXwv??RDJm@bCC;xA+j}UX8)@oM?r<{5PdSE)#2I0VA8VURW*#8d4 z!y4u8%hs%{k|IBNv{&!c!tTm+m6b%l>@FU0`c*SzB&A@->L>t8=mmpImxW z{!Tmmf|+wufcwyV2}29<2gZEe4Z!bE7&g8^^+QA9LK)}`e6uEc7yJR<*9&$7n_!UG zpzCJ6)#T$u>>e;d{^$Dm zAW!h~{`nan)v)5bR-XF*j!gDRf5N=S>%B=aJjqiD$YvJ5gJdBAA@O}<)Z4uYACeZAUIbZ$yKN_hI=!NRfs>r`P zc5eG(_yw4QCPP29jT7j0ShoZBtrBg7-rde+Sw(>q_p3VQdQe_RmENCD=WJn60O7*o zzR4y!SB9c40QO)oU4eMrgh~e%cB6=0k(-FTehp`}gzHf^z%);0S)m75Rk0X&U>p0c zN};0XUe}q$liYtGe@bIVo=>1tfDUPlzVTDwFm`VA=wp4Px1Bvd_7Hy0-|C*EJdJL+ z`#N23b~U5Ed@b8C-1i;v0e{w~8mMiF+{Yss(H^({J(UOiLN+1eRyg_w81?x|^wj{iIam;;G?Kzhp;n8(Lt zJ00i){h9tdP8=-R6O$NA^}jbW>kp-;~4KUJ|kqlP^{N^&orYD4~} z273(3(Z^w8op(36>v$v~s|R+%kNLLh+YRL3rHsV$hBID&DMt--rI6O5}M!C!Mv5>OKtK`U7^dFF50QBGRxrea&=(~%ziil5q)#K$vf1yeIH>$_DZc72)RKzV4_qI+OjBIF*p zuYc)ms{2otX*49)yIXWt=mYlY7LCLPz0h~yH*r+AV;Odgf{x5}oe=%g=+6*@ejYA8#c7=^hCZwSiu- zRmn=?FM&t}+}_0LkpCn7@OBpZh~Re!wpN9vBY!x^**g(Gu(#U%KPCQ3yJ$=-hCC=G zg;nI2gb(Io9ys>p1&70~A^)#P*&)tP%lQ5LCCL9vwFZ4?0m;9>Ll8~+Um%#0M)D6c zp>q2HqPc})?H%l&r~D5h!lxRAkAAc^m7i-doHq3U7}8SMLGr$4Z=A&~Gj4 z{rn>Mg=>+2PH!RopL&)RMQO1I_UKXgZDw|(O*KsV#`(<&_;Z*CyYjfO9lzs&XV4?$ z4-9Ub5F&nMHNw6D(hs>QEk?qlEIz*KDCh$jvx4jh)2HXlNDo|(y~V=Lv+2o}`Q%T^ zuMO=dKQ()CgMeXUbw?B0+y|4cUFffdvB0 z{}pS~RYC#zf5XEa4=Mfw{nOW09Mo=pYtO?m#DC*A;#^ecwPo@rZ7KNR=KZ>BwI@)| zvG3naUqbE^6Gw)ru18+^FzW#1|L-4vSW5DL^>XJ;qN~+h{2tl&v3h~K&cpoI&kj)i z=W)p|CnDhoT%Izek*=F3YPv{tKbhP5=~Xw5Qx5yk#%{=RI@Uptj8^(f{o$XzyT@Bi za@~MAJS3k_h^A?X-dXWKVUK(o`&s?TJ4Nhv=76^Z^S2ORvEeh}Wu^8tR6ws{FOh5S zXaJtKs8D^Do@;bQhoBwt;f3t3KC%muHVfpZm=i5mImv$SW}0o_BZkAAWGU$G46dA@ zc*B*@2Yj%I6@GRRbzMc=@xr@S(i0fB$~Q>P4U!eew`keyR^u7ibMEY8n!i*1&R=*N{6Ez9w6+zX z&x`$H$kl+kzpTA!5Oq5y_RQLGf8v)XzjOls)3Tb~4Epu91K&$%B>Ujyk~9G%T$j zBROvH3x-_cz^!9rbIHJDc)gfXW`VsNPs~+DsoI>C5xgiJeyc*WIv)MuR(AO&{h~m3I!O0@(-`Kcu zV+3=OK>{CY*rhH+*BJ=OX;Wvu4oayW-o9cS=T!T`gm+)|~FCA6b zCjk3^xa7Qv{D$ZVJ@6yUWq;lP`+#`SkhO{QL#S_aWfbZ)dnTKmWKSA`vNwaS$UiA} z(EUTo)xjjsU!f3h7~gLYnXl37BEbtP@(Y(-+cJ~oWx2%N!$WnL(9e*)n z<9mev#mtP6-T4ptpT^vFBkQyGYv8$luU)ebetRn0uM^~wpD$b5m~1;F$xweWM<|8-@=FGd;0ty~iD3cGnfL{TU(oA@ zXHQxwA2`x`u8iWw2(GDu^u>+4I?Qp|&J5SOK5U14?>Fn+{=P=zv=d3>-hBVa(!->lmRY z%^Nkd1u4-fB)7S}*#mUk6yNMmbYAe7p@GgNrjs^04^-*q5`EuNPg9v$y5G#04*ZTm z`&rR2e&5(KX*khUa!frJ^&G4$A-0r0|K1nb41LbuM-)alz{?lg=h6H(r9mRG;O`7{ z%;SmsWruBD7Dg6+DWpK!x5wdEp_p z^G1{N&hnqVs$KDS z8|v;Bhfhmghx5#au0mYL-kmJ}7;z!$g|BCj-uDWzBA#2rI%G|}9mk1!KQr_@EAoCM zkmT#|gr5+7bzC7|`2qASr-;42q>Xz8^*K(+FZ{M(-$FWWKKfp38m`k+2B|5ZBhWQP zLocw?5(O9AAh(Odr5}?0_N@v*d;)!a!=aWz-zJ#~eTw+AsXGDggFAKk8ZN{(v z^wE?4h$nQ}rgIj~#0F{jBjo>0y6U8KpVB-t=!<>w%5BLcuiv~ByWE2B;|H?(+UfhB zd#ak`d*X$wZ^%Gj>()H%^)Pc0#NbMEY_bHzU()Y@$eBgj$ ztyiVoy?=h+dkHZ_f5WBDq$}*pt7!h?eavq%c%MH{{{HB-%@42FnKOFNO(M_R#_&SH z6Eyd8_lR*n%>!RAm&?;3*NzTvGx>j}+^!tV`DJ<4>dR!$xr6|J^7jH+X&K=?X9k*f zM&kA7{`BY1r0DYs5lI52C6Ww*Q1plt{-B^s7fSwK_CvGL%&lg|vx`wbS;Pic%H@dv zIEkiuG0pqPlzYvzLvEUjE({QT)dS8IX*ha_*G@qnCKFcI)lKvro*mjp&n0=XtdaHz zYs&ai;-6@hz@r`Uz?j5rY)2hA7b;nX=gXddhYyUx^S2C*puYlhe8)!RBzKDhr*9Ko zq)+Ch%D@i;Dc1AFi2HoX<{Ysf!W?1$v|hqzmoyKh0Y_q01G(@kidivNZ6SJn+V3f| zgO2GAodf!r;Y0h#1}#j*n9dO{xJMP)Rm9W9I|3|U)ZIONiTfB zdjp@qyc=OM@YZ5Zu4L6wxc|qyr;geWA@5+jVYWd353Fdp6oLF-ePys0(ch~wSW5D* z%2ytvb<$yg?U&Jn+ z?YWoqch`bh!1vZ*A9s9`8P8MTpG4Bbj0hJuNVRF~#q2JpnVrWCLGOsi*nRa#vp;;%m+z5PV2+_E>J~9mqXXy3njAdvG*M348|q zU%p{=v`_SJNsa`<2{nP5Z2^Q24wV0f^mwy%vYG7oS^J$Biu?Q}LrU-qbD(Q1hv?rm zHXrt*m`$ikLH!86H`phf?t6Y*h52uob2f&3p2W||{ip*qbKh>cnNIRwe`%EL)e3KIenIXTyhzSZ9Lmo}I(I(QXCe#kH(`#fokFQNLm;EW-ZTdrTv5ME%z^ zt=jwL{s`&=^WlHa3u0ipyFcvyvSFJ44DpYSME&3EKR)b&eCM;fIy=vx4?uoB#5JSR zl;JcP3=7+2+CM|?$uEp^#qYr!c-GGJo+J973^xW+-_N&_&1vbN_ojQBF0TW3#czFcVM2LaO33-A~WX3G5TMfk ze#pd5>OJQY9p(CS>7=(G@J;iIupiSJnsu%id~mz~D<1IozaW(Ai&@lzJ^c4}8J-j8 zd2x%sb|=>=Ge>i==rJDt#?UpG#@tY#r2F+ z)1Mp#z8bZdB8Yy;t6%=41J@;Go_$DkF!=-pk-RS`RSux~kmJLuW|AFhQKYvLJ#(ab z{5{aq7LA;68cktQ3#vppO*Qg9;LrFQi{O{Gb9;SSvPcgE3W8>nzlS;9ngHz%Ry5yB z6ova69>&HIPJGy&Is!ezsC2X^5qapp78Y6XyJGhS%%=K})t6dO{~M{Ld_MVoij1xI z2=DJ#_9s)_ulV#{HSxpKzE9cxkRSTD>7T!d{p0TW&y@dN;z`hu*-lWQp-d@VEW2g>^k1xt^i|7TBCjYx!ekZoGF_zdKjIa(kv)WVLK4>s_> zz}TkS53ghY&s8?>w!MbtLG|Q@DZj)#fQJTK+LxFQ!)z_vL3ZM@;t4hJL(aoE#7!oa zyBvpos+a@8O(sS`U)1|HZjs{eb(RN0?_sY`eEfFO<5H_Df#x#>ebmucj6Qr-=S^`j zZ~|4Jx{CB@(gfrOa#?nKi5+&E4HOzyw1a*JzNxAtebR8`>=JtJ*Prm720e_7#qVBA z{8XJHqxX}BCW`3(5>>PS=WNTdB|TB#LxqY5{*O9N%gG)K?7fEpdyw&a&1Ij20#{-h z4=+HSmziY}I1BNkYkWZn^ai83+xi9R4{QZ>?;X6O@pb=wak_K$r2nH+vR;nV4q}g} zFZ{i=6&iy?Pk!I$7d)n*9;l?cnolOakiYxelt|FO<3*1dB>#O-03`npV;tMRhQ9AL z)M$Rh9Iz25W6U&B-%rMJ_Z*}Td;9)u*r>3qKK)QMG$#JCFaf|jjn^j5AbS%o^Tz^o47M^;1Ax2Mn#GXCQuX2JzD zQhzV<|AHbzZWDbvtzF0iq3?t5Izad>JKx8r9nb&Xu&x07&R7Q*BEFx;-0^Ru{v5V9 zFYW^I!IQuD)k*PrS8P%``G4g+e}v>$zx!%8@y!{%P#A#c7{?g&B&U0Qu?Y<4qnpl( ziT)vK>{Z2apf4Nv5bAj!+{`BWa6R#TG>4c#SG`tlf?7{D0iixsVkcFaF5 z!UB*;_1 z3roj_h@QNtI5!-9_NArU;_3N+m-$X6=+8ZFZ}+fsX&e&sol#ogICVRL3!*=i@5G$u zwaC+Az8mf%6qy%-{%l~&kFgf;L2OmH5BY^J+`Z<$Ud@LT!B1cp`WcW9*ulnho-H7K zp4XZ(V8Qoa@QsuZoeB%za?$-89qDd4-8}iztak8Wb?FrED12_T4)wcu-$%3v{(J`) zEo1|UZv=uxQ7Txi@e$Df`ERv zy}vw7*?~D9d3858(;l#_!>bM*z+A9x>2tSYJ{0>ccjfH<6CCs0hhh1rPAHl7#{I{r zKUnr|ch42nUpJpkNC2M4>avaA2aOAL3AC8t%n}QTz=I57qh7Q(5Y4! zcdudP@{I~iiDJ|`%Xk{}ucYd|YLef5&em)@p3i9B_;LFD#jTU$MAyZ4p`*ZOKaOTz z1AWoAT@?g6aLP-*z>OkUsV&Pu{I#ew;wk!^r=+aPANF|aPj(^Md$s1wPs4|Huunz| z_4|YF&r0@kr0<6v>oDIJb0>U^@Xyki$ur^5Cz#uzQ#>U7utad&Zx}da(=Drt=%4K@ z#C!tOZ;vi)CO&GNEUSe+;ryBnPKqCrPCp?h{wPS!26DBQi5tBG?_n)t5=*5-@1bn# z2>BIhl^2KbI=g+<>Q#~2%SwM=u|LT@e{A_vbbKT%%!7{WS1u0(ALO?-UK*zO&tfbZ z5~YgsY5wz!%GDWLZ&joI-<+|jNptyw>+7{~Z^g;iYuhqZ=H52i2kbBO^`SiAC0o4g z0Q^AdNr!>@y1l$~x}E6%Jo21Qv9n@TMqz}pKeA*M6Po*Riv@Haxn6mO_(6R}(^m_+ zD}E!<|4`$TQTxfo5%`1LJj17i6LFgTGSoQ?9n#761-IdOjTWS!KQ5Njl=blBk(8Mx#5mJ#2?r@9Dgpm1Ha=bjj5l$FCKwC_>{pQGvuv+PfmR1 zwqIE;OKJz+A37UCbHLcv)-P5>p#Hz8NGzs0Pt(xZW~%=&wCLS+ARej}LZZ7uk&HP3 z_t^fH)kfO?h_c~mpVaqDVq;@ssLw~f&~=OI{wF3AU%e09r{`h8e#`^bF8MfhCC&eJ zB%09w@23^`?eo*J2PzU0P#BN7F3GcdT^1a7=S{q^ANu}>Ull)3bf200?NZ_g_O$x@ z{S~Vi{>-7{^!m|*!`K&dg?sn3+MDLTCFU7I$^Q!u{-YFn9nZsGO!aQ7LlbUL{5Ipj zlqqBn&UwxA#vYf9^iZ9E=>JFYe&9pcyWG<$CHl?~O#c$}Wm5WtR`3I-QCA_JHnXa| zJ|1>_K9Y}3q~p29y`DsO$Mv7!_otz5XsU$dtoC}-Fv)Y@>Xiv3XS^t)ZaU~wT6*nu zqL&$>)f6a00pytESi9sAiMM*!P6}V!sRvBza%7_~ApMn=F2F z4$)B{Gc?ip!f^dI+>dK9pD{vDApa@#Duz9;K2lmvud6w6 zND-fRyBb>~2Ne6!|M}MD8!@Gr|6=ja87Car+rqf0&*SFpx$e3kqjTB$ zN6{znaC{VV|E{u~W5Mc9V$655j@$b2T-l9khfRg>J zHNPhZbrhl{J8SvHH&Pn+Bm_Ib$8Ap8{ z*RJg>!d__38QYA!AvoiVcNXjlx~mNg>50K?(cT^4KT%4uf^en0ohbwB5QmtGd{Fms z1#|f%*!KrNzbjft`TzOr$?uz%pWS%BpI$HW@kT$02>YKEeXBEOestml&;Nuv-&@%e z=>JDwm(*8DeZEtjX9nn)bJ+hX?R{Vdz0@@KN%@7VmyT2aD1u%|WmKwonFaG7o5C}} z9~}Fefn?PWC$4atX7=`A4iHy4Y69L1KX2g|pOXJ4K)v4qY;xrJj^TB<&rZuh1#n>O z+4L4reyGwkFib~({|Vq+MX0aRsmRYyOmH~O@OSkBL3-OE;CvgbcZ)%Pp@)|MbmuO6 zE^Ic#{#qg>*aHN7NT4jfW*EQdf(swY&G?D=j3U85m#{McPF`q z5AEl!W54Hg{4R@GDyG*puCeRGhoC<+*l&;Dk#3!I(%-B4qWU25L4@hT`6$HyDN5x4 z~)j#lX>Av`n zM}W^1q%-Mk<-s}lmtN-a&%e(9XFZP~!BYXkDbWHI=<|}7$4fy1g>WwZRR;(Ja=3{> zf|cb{eBKYB_vJHZ%BKrnR?oq|IQ?&-=bM5(zYU2O&YAN|fgm<^bNoN^#}&bYN5}8% z`0RTyqdar%%71?UU+4d`_~~Et_@7OW>CnehXkf;F$Zybej|2a!es;_M>nrB!A^+>w zZ7W1I-~X@A|Ig<4f6?bZ(`UNC z=K#DjK`Y5`X6Aa>^Z(?ZO&3J}r*rrRv(@1i{QoJx z|NK7bpa1oJ*L1H}@9j%$c@Ofxb?fTO@4(-9api$2Rb=n4mCkhC^mv)Rt01{Q40CT{ zZVJ}6hU`|)of9Mw#KpyK{=dKff&a(v|L5!ft8=%$Df|c?_kWMS^Iv`6zuy1P{Q9rw z@!$JAAkUDll?OgU(6@f$#)SXs`MTo+gb4rOe~2A~-*b+_ZT~m^U+n))!NrftSC&3{ z2l9KZbbj!gf_43KR;lOAd5ZjnILPnCkFpBipM4kKjC#3J*E^5=wngBrs#+!d+P%yW6Y~hzpCDSZm2t2@ACvJFN|( zOkgk)wFvDD1v@f8H3YOGr8={{wPQ7ei(HCyI<;DFAgC2D)9%@7-BvAv7sPt0tt~}h zo$o@PXEDzCoSyyecg}nEp8DxO-~OJpvRLc+-7n9@`~30h)c@+=i~ar+z3eAi!moq& zKGlahwMDMC$hG4BMOI)g=zrzU-{%)ef9ur#D$!mb_Z!drVf<4~@+L3-*9$LLXFmA6 zH<_>225h@6n`YHA6DFa;g2{0ktX};>@bm21bLO09!2HGQll9Q|BSwJ#&x_84(&d^F zu3LtG3CyzG}2gPqKnX&%iaA3%v9ZN-io#{3d49J7ov!~$w7?S0LfB!~)-M3$U0x@@A z-wRCZ+Tick2G=+l2I;iQn@s-uqW$qPB+uMAv;W5ac<_Dvh<}s^L;dRq_rp!*TK~0; zFExq!<>keTV;`jdXYnHDqkkP=_UJ{CKNDV^`g1XUS2Sa-sto&i{rdGIbZU{`dEkGT2U5Hi{J&Oo8N`2L{C0{?wEyGAxe*cc_xI;SekpqPh>r_&@aqU+3fOPt zY|A2RnXulgj;g(OzHr%pRuocgd~mlLz!W1OW6qo`+%drdUnQNBzCW znf&JjXHe8_@|QB@B419m-A%PjDQXSxknq8TOyFJQpF zpRt+B$34s|*-y?^JblE*Ft5HAKTBY~UmN(5DsVc!kC;yvm^63Tr;4YZ0ZvX#ob{jm zK0mJi$Cd}sKfgbSkUF;f06&TGtfhcFDjffVLVP#Fk}j&s%WAZQzB=Qw^~+m*0>M>wM*ZHWpMEMF4}tu6 z$dT@>U`tAh{RQUvuT=f-4L@jv{Sg+X7j$W>w{*UqyV7b=Uf3{lWUXkA6cMre3M6?&yLtZ%C;hA2;v-#lfNe!{kKlx3xtxHqrgIhsl_H;pEI+qp_b;?Dlv; z?Grjq9{S}J@OxfvZmPi4tmEOof0@BBrTDi>_v{B#W_o;dkPjO-jv6H#|MNI;@75%R z+e$yFT`j2d$L`S%o53YPU!Q(TyGT9bLT_%M`@TGwJ!R4OEgjK^O-*vvq3rAH>o~4! z-TK)_IdS3e$G^LEC`eeUR>?GNGQHUY{x3T~`1M+zj;|XN8HRYC_;bwlycO0|rt1qr zuS*JzIG@trO|T(eZq|i8x3^2(HO!Vf~A13WVdw2JY_{ zZ&9D??mY+oFaG1F!^bVMx|BPwJZG(Cp1bq(#N`LVMEfG))fuqwRe{`X*@+_93t^?j zkPl$~X8mQ+UzsyEK~QUM%SJh5esNmHpQ~YoDK2@Z>mg5ge1XzpwKlI}uKfF?lc7&n zatwohR!D^(_y7Nsz59>$eg^b8R5sD)(0{*s*#E2G(cmwbr(r@Z1O5+ig~wO2V1LgP z=DEW~GvA>6Eimr!PaI4+GUg>MGb_s(`{4UKuAchxr=$LPbu08!{42kGSs34}>nt=o zrog{WP4B7|)a-59!&;`*sa2Mi1_spXW`4LJw`iWo|D#5Ze3k zVcxR!eXo!Z{Ogi`_F)d&EX<2nJ)WBhAN~6CS9buU@Y#{Y4N{1L6pkYr&> zg8~`hI1>5zS7nXW{I5T+eJ3~zy z_wVW?I6SGzSiNggFTscH9%Ux}5q!kyW(x`man|R3{tKU}RiC?Xp8miG23VtvERX6gMWsA11!$|{k6 z#MoV#ut$tP_%W-JT8!zn0RaNDz!ZK)F=>$r`OB2QtzUTXd;N(2vE*aIfIq!L zUE@D#3*KP~r~S{+1;fHEx1wUpC z$JLIXUgR^~HDwQk+Rl>ZIRL;SAYwP(qCL%hzceJ?YM;0iv}^v%`2HsPkf__)lG z>~h|}=ALx_3_0J{em#$@FSKek-52^(2=2_buoz!M`F~rIE4H`y!f8U+XLzg+kDR=h z;8o6YYyQoyI)ck_zsL2%-ypcc=7Ig4+D!1r&MD>t2Ust`3KOHQuBxgM79|dU8P8i} zbE)idxmUP(-~I?oDl^uN2@DhY8Tki?HD7Pa>h4{)2=*4@g?i%u2#a9P;J?E>_@8BE zPBN^5EGnl%FRsUq5?cxnaSzui77^GE*v$KvlF`re37Z(7j0{t>x)z`q@hZp zJFwO1W2!7JE++fws;$VB+*@6J-#_&lD#8BjY`h0l}ErI1^Yw_OCMqbR1C8% z!=fqk3sL&HU z!^!etVH0)`4Eu%0SbG=23(Q*0{trK-_Ckft#a%4-Qh6~2{J*2l(@xfpc6yADJh|bs zIq@H~95<=0I&q zrOm|#1dO5jRnD{OoSg12$@(MCGL64~O}#M1S}`w8=6}VmRjzyZY1D$>fB)q+8Ot}X z?jm?vlFn2UUo!75ZdEhl_*+ly&Dqq6l%ed6s zo_=yag~`R9{k;Drg5^mJUl~?U`D>c9z$zP4NAF7%Y}k4F0>RT} zR#-J7!h8w7!ZVs>{k@d`q5qA1YA&@$KF-#e^YWZjUg>xj8?&Qwrx@Sw+f;h~GWb6^ z@xlc`r^%EX9P&fFh2iRh==+UWQe6-dT=xh9wYyD<+ zaPF@CXOJ)V>Sz9Ws{(E`^5t`dDeymiEsP<c--dSeczf1C` zLwSqlrQUA3|HI?kS$|H_r>mepn^SYCye>0m^^)+=7 zAL~o<2$jy7uVRAqR$@25030n4kYepYJwIgFhhFJHr3_{iWi*;8kM0CLv+MfPIMiK~d`e*bnzV zuOCpXpBb9}5Y*?}WRI!jN0EPPC@wA~`w8|qYcPO+2jhQ7vUR59Gu>@M!h*4VYlGHq z<1Nah>^tv+|KaU3Ooh#&>Hk&NQF8rCr-Q|L0F?h1Ww^N6$;py_gnb>V{@r6_{S~X% zTwYU5?R6dB#(B(>w-b7-sfkeO`%O?m9kN0pjH4RY&k6v=X7#2B(zMqICp{BGPDgSWTR>n5emzD}lmK^zj z?<1U||A73YI{MYXD2ps2Ny~&)-na+(fcS#ja2@`I7~lONeey%}`}LYX`B9$yACjlX znbv>3cXio;WB8s42`|Uqm+wb>M4lDpyP$*r?mG_kEXW`G*Hx4Kv@J<)8mLbj)X!Xg zJLa{gI)w8Uxp?gnn};heETQ^sbbhR9;linh$n{g>T^yzX(fz4+V!tOROZTg`Jx=mFZ1uMB;2mV+cprG+=ZYC4a*LS0Y(0^XUo0l)Yc8p-X^FZ{kGTJ)`K5S#y zKl~|FL2xC{u$_*!Hi8e@4n*JS6Da>VdF1c5H(nt0N~fDovi;@X2|j4Euwx&8;zNSd zv*kR}@rwz5-QK3%2?b0rmwmwa-FI`Sym9k7)4C0tXgvVWw`tNno-VS!EV_)XL&@g;34+a2UjkITf*$wb7n?Ae~)-t$bB&VY!5*#98LcYlb#AEZB6 zpIys*mp%DZ=+j3;`{I|susyi`9~}?y4eV@DCCb&k?!~_U_#szQYI}7N2UGapLE%5q#NR zX8iu^b~p(4{j=%NuVWft`z668*=5G&uItwbMtsm57k7`!1JR%N53Kuw(5EH2jJ^HH zw-4U0+;%a#U+DXqU^(n@bNG`_5?qxbH{H4Om%k9a5b=U14F+oeuRy-9{oF~a@3*Bh znu!e<@IQF}nrt0^?(MTSf~O^U*`6E+eP22ggOFYUPv|Uue?rtGs{bqOZP97Hd-oFh zRA-y9BUSQmkYC{@;lMpYuP-trt?tUB@2}PAWfK$a^zYU2IvtKtttIQLGun;iW!I?u zDYSaoZ$7MVJkNmnGj)b4aH`1vV*iKX>iAwC_kM^P9N@htG6r9(!yn<^Ok!ij&g2uG9S;utdkFB-&|x%g5PGTv%inwTCa++Kk~< z6&2)udep}xC#Tp6)|r}E-yp;n2l>Sb|J3!(cUZHO}!G*k*-Me@58G;XI z7qC5dvgqHZu(pM)*}0v@C)%UiR3Gl$OXWqSo#8sZw#}l~=M_ZXxg+tb4*3sD+OnmD zt}|(MyiDgMSkE`HJ1WD>1S^rR95Kn;P4Mf^CSzI-*m>~zk?+(51(p;N9I?c2=Dn;e z%Fkgl%h{a#-%$I(2l2yz;+N~l`WLemahX|PUnjU}dR)ZayLVrO{vW&Tj$za@PA%r0 zY#1r%Uf#b&=j%T`e!gMoBZF99DE2=K%#7cu zzE>$YR2oj0Pziqn%#V33{@{Peqm&2t|35kw<8dOt4)8x3N>Se}pJY*tvkv%YnT`kF zPXg0-Y+gDuZ6JU9@VeQx%=0%d<*nWqIv}6dB_f^!{N|OK!T63a$p1yQRPO7Jd!Li< zJq`Lnh5~qkYcuAv!cwWewa7f5ebijrK?lcUe@vCsB&id)vpN=B;H)NF0=-#_` z9l>&|oBe)XNtzlM#sBVY15CcesG@5 zA||!^vTdxsBLwf;Znp^u=i)x~Tf$VMUN`U8bz=mzQy0wySJz}{l_|FDRAH5<|1)81 zvh4d`U8MPWeNr=vZO;T}$Zm5f6Cs$`yLQgQ*M` z5wmY6p@(HaKW{!q?b*wA7q>J&KZnqZ_*gc*XN*6=K9CQmK08VC@nSr%8w^e8p=MV| zPu3mEpUtsts+b9q|GvnnRVNOKSIY5xH@mD>a(y-G1u;H=`U8IPF814RcT!x4_mSFu z?0K@jf_EAFZbZIA@GvK9dabXYKED|MpEGA2wU^|uSHi>4A2=va4q7k9Ha1H6hSrSB z_Hm&HJ@w$joY z1TS)?@;{BLt402RaUuU>NYe7(eEaRTJdpV?d}=^{{co7h^q_wH?}owOYZrk32W)aN z9=FFYO|;jC#kIt)-X68>{yZ7EVVX&-_nHlTe=cs)$+)Mcn^Dh=fmFiykpul~D^Nd= zfN+giFDl-v3iiCS?(45feCJrUoBQRm^kd}vPIc-{ksKFDa9V~J_PG?F`!a7n7g%>U zfzYQV6~rn7{i(g~zaTumI`?yGzed0xm^(KmnXGq0Unbfm`&ZP*3{y@0U$@o8H8ez0 zeue(kRaaNhcz?Og!A_jmNbQdWu%D~Hl=6Y$No8E3Ek2w4UIFiBi)$L_dL19D*@S{Y zLa#(T5bB)rzrxfO^7XxY)E-#?|9)uwlV5zB3jKAn$@Om72e+Tghfyn^TN?Cz&@7iS6CRV(EK4_eE(_UpGOzcI*%tlO}T{+SvAh^@Q%@WZ3;%V`2zCns?BabLPSq z1Ru$sV(shSMc>~_=RvdEWoZ)LVw6qHaBYVzm0i91dd6|)&96exzkCGoBb1B5zd!z2 z;?FOt#Q5IEQBwZ#uvu>EZ0pKB&SY-?xX18MVtyI^#FICfD}S2QrU>`Je*T3$@gL3} zK)mGlmu9TUCK{Yu)oXeYB=(J1B(i+rLPP0d$(qH z#70I|HWK>kY>zSVjKm-9vGdhuyKj34{fezEWNi3Es*kNp4%xgtjeUe(jo-^zChb2t zuhq1*wNd-OEZV~+EL=RFtS?9XOL4HIuctfPv_e-mwLc227A~s65JlEsfjzwZ*BLy) z71jc-rRCTOf}zjZ+VD!cf8kuKyZieu5PA{#DKYWR9fG}VTS!*pSel=!ur;Y38#OA1 z(BY3LG0*2W1gFA(uBnft{N&`@RPB6vI-wu5Tet(o*Qh^z*`{Ufxl($BiF~mzBp~}r}0GbdA~iENafd}B%Z%^v5d-Fkv~gn^6ruAE3FDPH(mO@ z_ULw1WTeFZFCu?AapJ^LWc@1W|Jv}d4+(xIztX;=QOYMCM!y9F1f}*T;XqF0dN=4eeoJ!uU^a2TpXzuVR1t`UcVccggG%aV8h_m`fij^M^iK z$Tz7BQ4L5Zi}rxrAl94xBi0|?xA!+4bcyyp0;+;K+Z!#9Sycu3nPN|G7nX|l`Yh%k z?-KjdIkA58OV{uwk?ueP?s3L1JMyz-*IFTeUwP$~-wF(h?~STJC;WZ1f75vIbky(d z8}j|DGAeD$(j&dMCZzh)Vmih=!v?!(K~h6y|9{;x{Xn@dU> zN(ntYsocD5`TLZgME#YC4N2%;ql=xt_!l%j=Cyjb(i5eWUz?*DHhIozYX9Qz4~eR8 zpz^5*`qDGmK=1Fv%OZsDwo-nLGaq2L?b=2Ci85o8a&LF{74rL#kD5DovS@!m8OLZI zyCBu~`NYe(35F;df9S9}m~X%R<{Y`c67l|evA=%M9uGAas8+8|O(Xb{-4Rk)BI)O9 zr`I?qMXGn;ZCcG2jZsvdU9!(NwJcw@j9l+EHvN_T0e?lkyXWj$La&6r&%Pt&15qz& zln2z(@3X?j8@oHNFDC037|S&MZM_1)AEz_yh#lzX9DM%Pyb9atlTT22uFUsIGCW>Q zzekl*VQOlE{#Uz(cQy?l7Ycs0@agE+zCUkds>tsv4jmHqAn=5Lm9RHtio!Z_e=&Yk z-PwcsZ;|S!)tj_z?(N&Th#z=`{Lh~k<5hEJpZ0ywzy0rKzyD{S7x_NK`!*GcZ)G6= zzRGuzsP9+s9#zNnz8xeVS7BmUE^17zI8O@wUlzkmMa1);l+@kSUCip*GWmcw|7G!v z88ZduqqV`UYgG#Idx-O22EXq%^wUI!g+&rPI)92&xGvR`znJZ`Hg_GR_U|*q-%j+$Ic?|;R{nEU(BVME`~2UeK9#vOP2z9%@&T)?Gg zTt7yzi)&I|`tnO^ABQJtHSyU)`+pdXdGcc;$$DLq7XHRW>Te&)uxJt!GpYT8cms=g zRtZ_JFuRzv+zhJ!k>3nKJ3=6#qu!V8?L9~Bk5I(#D~HtQHrrAoFfX8stUnOl#QKdy z{FnTFR*QP=+V{p0T;+^2^_=aY{>wqsPd7C===bx5z0q28kJk8@d=kATI7F6Ru<+B{ZSQ& z|MU^)=UgH6LxKOfzP=u6pTNITWhUb zN#WwBV2IRr)IsaLH`tP>>JoEiy-(7vF>>2$139r7~iutk*nL!mOzTYu5^_R)} z55@aqM(^KBa1}2%jfpyyK<05&nh&U+TeRqk!1({};o{#V`rv*D7mXn6S2*2VkZ%y> zS7rW!q)%?$swebq*`d}uZS8dbrsZk5%+#)CLT~1@@TY5Z1czDU*x+C(-;Djux$B~+ zyg3wK!B5z6>J(W&HOZnmcLwvX2KQT~&BLaoNb$Nd#J4b?i`t8B`1iT(%`UROBihA{ ziLAdxuwT56-!x0=_eDPo8!(2Wf6qbGx81mPi}Ggy>rpMs&)G$;_leiCEgh&|8hoB| zj#0n;_O9Io`#3GAcaZw^E@v|lIM-TE=zj62|1BwXaR|gQU+c|L}wP*167$2ED`B8HHwxm!K@@I1i zK4@lG^gG%KzJz>3hI4T-!DEY3jjz7;N*=-6vMX&L$t8PXnlseOJpGt2p&xcmak@Nd z^!cVFxmg^~=0N{&;iNN#lf?ZwE75Kj^-UoD^CI3qFmG(|eXcNjS>5}o8RUK)Mn-+6 zvtfsj(4_d(<-?S77G~bt`eB%tW)XE?1$R-~-7kD5FzvX%B3ussPf4`>kE;)Wz5n}5 zt#3Ud+WRo~F@H{-6FbtvT#DAJWL(X4K^Jpz_TEan*dKfMyOUI3i1z6}|FQ2pd7o8~ zuR>!@nXr_Ri}mn9PcPbx#aqdTAAa-QL~y!|NUXRl4m^G#jL2%U$2)RFWBec!Rb zI~%3?A1CsmKivZRdGLD{A^z~>#KuN~Q9q!uC+E}eb2QJz-MHRC^E>FD;1E8c@_%|Z z;z2e!5 z2meR1WPL|8qpt0e>^qUZ`e-_Re%}Z!^u5#{s$<)f)pvd(zbVc$%X;4cd5n37bAC|& z5BLkO$n~NO5$)&yzw7^1m?w#Tn~DK{U(D|vS71D~icx^SYqxiu5msgT1>tXLF=~}( z`J+e4`xNaZMqW(!^8$QtMI`joq5=MI)0h?Cs+Et>by`spsv1#&>w%JEPBhMA7X>vx3>@3W7&yMJQPT< z+|;CceDD5q1Xn;GX|7o+z}t zjGxYu>V=wZI#WbLCAE)ryq8_Kc{9xyRzUxkmYz64?kCnGM2Y$5!RIf8enk5#%|CdJ zTJ>Vo*burK_P{4A?*5YC!+8wr9~4RXd!f_Ae)-Z%)Zbr#eB^`)`*zUv&H_!!ka~MB z^f%@WQvNIB=W{Ms6SY5N;ODMmt@L?i=BAK_J$vZ)y8phaDSyk6AMo|P+edy6+!@l_ zdvYzoQ^C&{`}$rZ*lm@W8vLdFiXZBui;vB0CUkw$<>-h8gO1>$3`P7aciyMpzcM?O z%gGh(f7I`sDf~Vb{G2jxUOegdJjA+`U+uZqO|FOZRL>UUS%dFe?dW?tYxacyFwfKN#ED!$v-TEew;lgVQ}85{Bf|v0DseZ!hw1gv7Yc{uXz4b z3G&_Z{twg>49K4$=fA8H=R3b94;SNe`1Aj!URw0`i@-wU?-8c>%wjNqve2kycJKZZ zecxA%s3(~iL*;t~=#@L}((fCG{ka_t8Dc!Z!}u}H#xf@B&ilJpG0&e~@a)PL+Xw86 z(X5D(PE2%oGW z*I%?^{#L!DznhUi%N>%R7xzEIeD@o&9`Sm%%h{Yta1s0oj1N3S@FDciU%M#v2gq#& zY}&GAzb5n+Bcn+_cC3V8H|J5^yVlxDurJ~vllDpcRF!SPyn-B>Z`2`Q5In?xslxk7 zZ7!tu^V-}j7m0xP;QRAhWn5cl7ybPM_@38urTQN)?l&p2fqs7;`TCO^cS`ckl7CfY=dCT=_@9#N)3e)c@E@o>vcSp4$HYvc z@~#r|TXK53cL__+DBi&4Jz_5lc`Y!Ym&B)5@{BqK1_8NW$r73Wsq;0oo&^2IV*gMg zp_j8VMp2+(2=+s~r@aB&^Q~UR4BTI5DhTOm>k$N|LY!avWh$#>5FUMgu%0k5Ukq~G>VI#00QUTb1vAzS^A7OaDafPt&}fU| z%Sb$5iaje6^5FOS);~P#9-t?Ts1@;tkun{2FXRK}(P`GoMvjw-^C>CTrqrxgH7ctW@kD^&G^4ILS2?>91{y(o?p0j1tQvX);%bq&_Z)L)iCV403W3Ean zP&q6|R*UnaF+cNRIiBa#-O35V%^7l0_dB6qIPO}{K)*b&9`>?(bv>2u9f;Q&>ZN@0 zVd($4b4P}d_Y+jgGJSQc3MlQCaspq_t~EtXXTE}uu}59M3f&%Urzd9f%% z7oV4#L;24)o{0|*nlO>9ugxnk?&|6Jg5XMfne|uwy>vgup+9!Rh7Hu7D?oe)=GSSm zJ{0rp8ftb=Cb-ZH{>;s#{V#6R!^JIrjoPONjBO#?q8jM_Mm$XuSh8&!xxNMY@ztwQ z&osE-5l>_d!GR?N7h=9g=gnKx{;t40ot+b<{FYA=!`|+d_N!R`UO&Wsal>8>^q1n9 ziU>y2m40kBxj*7t>M?ciP?L+r+lBSsLqKHh@*K1p6I06nZeBaYCg@~D@% z-WWviN~?}dxm!==MOp^K!n|lC^wG{(_QMUkJOod5dac2w*XaJR7I|&Kk<$CGL_JD= zem?zshAnX$M@*9J%j0iU@T+UP(#ie85ifjXJ#@Mz?dzWudi|HxPu z^W=n}8bOVCqvOrPUg&d_Yo#C`?qGbzF-M(oY*{(ts|h%ag?VUnNs(5w=96PHbj?Hoy~XEWXH)(yDe^JP{Qc?ok%M)%NcQc*46m)dth}7iec&IDu9oCYXDY*4Kc|k9 z*X+E`vv|#ccD?*4+p`IcW&RN_Cr&&i(OqamEKPmp=H1O z?u%}6Ke7IO{YTO~3!nIc=!Wfv6@;!weSb-FD$U<_L^r7>Pu@uF5%9m-V36tq5RYQ3 z-jwu_-h_C_#F3-O{X~D?H{6HT8~7mK;%-l+{sgP`s3N+gd;#PIJEsc%=iv7%gM7$J zne!RJ<d_{LdqyLU1wfP&u=Z0T_HfC)=dFc5nAs^dClSy|;6oSRZ&A1K^3ioPqoj{I3*2 z-8B4(P32d{6lob}R^lA7o=*Ro+c0Qv|K06>(H;r>ey!@=HxSRP44XinKP{f;0|TSB z3F@qXj~#DbUW|I&*%){s)XFW4uZj`>zMQW8F=YL8bAhrF10o6bHF;EZQQ?mh9EQI? zD@&4>+>-h1(E1)_zLU>vllpI4V%t^Q`+Lri>lYV!k*_K)CRqHwtB32KAb6US<#KyQ z)BV6Xz0tR_GO4|ee4|mQm-_dQApY0a*G0b{;`8jSi3VzKBi_wU*t&)8_X^O5&cAL# zK9S?3d@<_dS$kp@-7jMO9{15Z?~vyc`MVeT(fEhR|EcX#yuCg8qAKBfNr|*x%kKDS z@-2c3asShozNGJ42m57r$vqUqlH|L_oG3*fQ;bqnI2`%5Pu-f z?ov&fv~M4whoN2q@!mFqsKNl*|kin?}oj_ z?rVJieL}y6`p>N~l7DqA`!a7m+C_iwnWO`%vqR>kccQ-;k58Z9ny-r(`n^L_tzr6 zG2+zRU&0>IG7qZ)YMgB$?^f5<2n>d;yA)5P#cN@|%*qm`_-2anJ};iHuCA`ft#OsQTJ{ih)X#liG@EkysV;d(9A2c33rbX#uT zugU#HevgWh^e7t|3{>7#p#Bg3l8dZIeW_;OKB?d368hgE z-e|mC%=gdQw5f-zPlf(Hefsn{f;)@_?5R_?3JFd{{(sY!{d)-(?fsBPLP7}EnRpGx z9gHTp9r?iAo}5I2k)Ko77nex!2;>Wnrq|N-)0~W^ufCM>8*6rOb;I{=BG(r}UO|83 z6W{muKhBVw`DXWOg2jGinL>98%$!6!=E<*1 zeJri)kX6fsm7bU}o|*o>{<;6}?Emx37Ux$Rd&@-o{)JZ-<9wDsuKlMOzY7@_FV^4e zSWoJ8UqrkjD>W5=59;$VUrXfwf&yv2)*|d*r1PkL*q-gw2t6IGU7x{VO1j(MggnszUOew!m zZgen>8$YA`deEjf9qr;ca(!4lqlP&_<<%wR&m5VVbpLf2UB+Mh{wH>_UhKb5!N5I& zotURxckR|1g1ttEX6*1_s=q~le$$x?`w3lcY9Bp1Lgyw}ln=9J&6e(u^$73N>wRHg z2tp&}myTFQ9o2w&oP+!mhkCr-Jw5pZ7eT*|LW3N^ak*7V^XAQ?-%B4?5nEf^+e_#R zA@8eqN&bBY@-KB!k!6G)9^a;2y{3W2M>-IHEw0%o5W1KTyq9a?37%?o8^deg?jiWH zt({G|Ak~)~u{TGL8nK@4r@HJzw(kD#?vnK{I?Ii1;{3qDzi$!vwfTmWfAn9H%46I( zm5)a<+PU7}EPa)%uQ18khN#9<1g|s~YIi-gJB8rI)&k|3bBJ%NT@QW91V16>3)9on zvj*l-;XEVwbA$cEVmx5@;W)(06B7|1QLlLJOGW6oXTWb6&eW{Gx4wVy{;>Z;X2Slz z4Eb|nMc4$?-&{;SzGJyKKO5t7*NOExO^64I{W}&*GtR5xG8K(&!D2k4v~&g47Yo=X z)rb)z>He*?6{yziKSTLYh8(h_sY`#NN6W_Q#SUL$x0`Zc`Xa=Jf;K_7LWdz#Ld ztI5#wVUHvI% z2t5q;oI?amCFmLy}*P2=gd-3{jM;@vNtXtYa#1>(7!VI$;mMUPk}%2!QKxx6D;}{7lzF9 zo`Um-Ms1Ypc~@rmBt4{`97WdqV%~Uf@0XMx4jEml#3A*P2W>jOprD4 zc~r=GLb|)ZrSb>;y(;j7g4}-^&Lil#cgI0+p|zd6(=W^=xY{l^?U*3t`>xuZR^?NB zza{i%pzk|7rTU#quz!jR3#q;NqBG81T-*{z)>lG5uRbcp|KzMz$?R@~fVyx09Oksj z{?GPP`^SS5Byl{W4E(Q{bSC(U$R9W^EfMj)Oa}9$7bi8Tu5}ca<9xJ;*YACtq&G3l zxy=thDcS=lpn0Ma@&Vf;=RyAbdt)&lDc0MH{q{KC8TvjK{{9>1SE4@Qq}V@u-e)@F zqe4G~mpvL886l{L|J%cF>{wn7`LOfD4eQAJ@NxyJWpigI6I^R|tM1(UB7xvazChL1 z)zwF^SZ}wcp+1b@R?L4=6iV^g3fP-Vl21~7;ur5xA)ij|!`CxbSUp`*{b>O5|7iE2 z{NMZnuS>VvXgr{Tk2QMUlkDv<#MjPkmi!CIJGSie)2GSv9JaC=)ZY{nd=UO-U5!)^ zUk-a|_k|1R2wjhPcmb=ea|jN@yfA;6RG%2;ME&>>{zpBey4bgJ5?Sx&T&hiH&QW>b zHMWPWa=Duc-D|XH0#Hyyup9P0pYz^(1dHeI6c*!roWcF+W}8Arj~+LUV9fL6<~y%9 z5bR_2pdMiB+XPRGXEYd>FqYthRtMA5BmI32@bknGJKrJnMb0>@@}XdAk6l82K>so+ z-dARnt2dd=X0qNVsXZF=S}6aow7H`Lwn+09tR;! zXV#u&KTB{6@>S?Rruso)vM}p+L{WLZkS|jmT`uK|3-JBc@0d_2s2vYgwH3cA+Q-Rf zHiZbQ6nX~}pslpVafRh=4ngeEvmbur{okQ?6TptyK`oBT?>!{D-S!9nO@ zP6zm3vOn+phlsBX{{1@m_p=g{za+RE(?nWI>-Q1768akUI*m^vd!o6}FU33L5zQgR zb#--QeJXcR6)|={mCq|}IxcErJ>~xjdn~RuN09Z!Z@8_IfzrI)v85LO97mI!&oxRzp>dq|J^Fk) z)PKIay^1~`&NE~)&=5*~uh8gGy?=H${rWLJvp3B=UKQ z1dHFVxZsaEVNW7XdS;lbsDIiTgZHUs9uoTvKZZVSb2zd@`&s_jg888lTKEJ0#poY= z-|+K0`Z7$D5YIn-kU#(4SnS_jFk|zjfqGuC-Z8;#YV*l0Kz>ANX>KVHbcVI^Ki&?W z3V!tu3PSwS@#gupfiI(f6#mPnw^2W3VSE^z)#Op3{EogKIg5Iof06vf6?~I&Syxx5 z^uDyr#I1XK35GqOnbaul-%8ZKo)hhlLH<~Pc^GH!VE)G-?#y<>ACu;DF0d9xHylg< z4WY+@|IeMld0m6+kuTTmX>6qOAgD;k9=mb(GeQ^hUnQ<43&E9`=YQ>*>9@e#8CMd`OfH~3Hwvx30=9s!<_Dw|DWBxm%i^qkk79y{yEjhzNi<87+XpGf6P-+tz3Edb@F?iHksxZNz(V0 zo7^Fdjbnoe-N)?ajti&G6I>CiWp7?RM6XvC$*pWzF_o_?5buD#I!V@J|7tdFmGT2C z#du}PXLLUl@;dfzWev3-t853@p7N4(a(%JQqr`FLRNj}vUh7*ENabaf&C9u)+y5fQ z&u%lppAJjXBL9W?i)vr%k(ROJMEn1`Gu5GHrvHBE{QAE)zyHwv1!m+oGNc4`5ey^q z4}v{2{Nlr<2a>jd-*-%q;;Yl0Ue4b2Cf#4t(BI#GLDGj)!2jzvOs4XGo3)us5&GAX z_lNpcRf;{A%7bmdF)>@eAoPzBZ#vU0=}U3Go+s}YNrXP5r~v(TQhvQEwMl#P<`t?x zrJI<>ayuM{d8-sR%T!NARQQy6Bt%KkS$e(i; zQm8#D_LCsqPM>d)RcmZMR(FG}FE?Hc>FBugCxUT)ks9MFssG`G|6f^&e)hrN*V|mG zsD>SP2^Q-CkNy1TGYKvj=Te2OU!Op5@w%qbg~gZY`h|$srghfR?=9x%A{%b~nykNK zm#fdb_deC{mC;Qhr_bG9Md+}Hc$q@#*FnBBWP7;O?LOTy z_?vG;1dR{HAwKf^-@m<{&=39 z3QJvzwdfZd?R2TKdQiW?@Hij&{=A^fS&9GY`Jn^z#%65Bw7mQM1elMRb?=wK-{|0P zBqY2x>xcOJhv)(mjd;NNWavxq`-t_z_+ypx?)ch=#cOeX^`noH_fcglFh2g&CaRyW zAU`u_wxqA!MhCNOSw7WgRkkvfZcOA3a(y%3%q{Ql?I(B&>^~e|LEqYaZX zL9idp07$FlTm#op0DK}B;~gYZ5|cE4f$mKLUEoz%|~w${37Oi=b)gM zVCWOgy!|nB|M_7)WmaacozUfMn`$lo5sdhr8pm18A{h0_A=u9+2yQ{Y=^yt=^TEXW zo{EYJ8ZV92IFv`br1_aT$YWpM6)VW~=vPoR^A>8a7lgQ&h}ueOA1tt0Lc&mhMAlbW z=Nqdllv;uhBA>CaK8ot&O4Q%nX>+s@`lVPIJ9hJy&j}XijRp)K)@S&s(%WtZK@z{Odwgm!pWQOHq?$KIAX~~jIZ!g|G*I6q{Qx}`#CJm z%SAT!(Ea(kjZtqiR1WqBF4_4k)%ygg8V3-cWH z`5%2Z{9(Q0u-T$KbtBMqoM}@S^4j%s@PA_R?1baY^M5gPgnl|859TJrz7XS;ZrZQ& zL;PQh{=Q_Kr^Vxb2nQqxwP`*|pG=%DFJm9All0Gv`1`tZbE!Ra#X4Wpc~p|OKB$-2 z9|LthC_j&2KIdmW?bIHw;Ca)$Ooxlmix6+l>2$jZM!r=Qu%_Sey)QolmkZ%`h&bj^IM{4<}#fr}`-j`hUD`%ufm35B2HYz2Dv>xX60I7+F$FaXH>6 z-d`@E7r=i?%9HY$=;u)<4LL7Utp7Q2LW+mAVEvj>DPPa1T?}KHMg3t^Lok~*N%ev{ z@cXvcC3{$b^Gb8mJE?q*;~WgKWmH}r!u!Ge04fhEa310A+gY@JbE;FvS61$zbYJvm zb#^+^$lp_G*Kv(e{?`d!hc<6&~sXRt} zDWtwe%CE{z^VyMmtINstVm)+tRTcHuuf*!u`KdN)FE*RJ#shtQbiZtKGXC>0z=&M$ z_fj)ge|;C#?{!7x*04Y~m5;rFg&!_=n%^#{8yL zj34TIwZr0_Y{vBrYl2wc3x8kqx5fU#2l;VOzr&**oTtM?uMZeGj_JyPKCVannQ;Yt ziG0C}NfssQUF;9~4+2w^7Td5k*e?$LMM-3sz&vL%Y*HM52KqZ;j{OJa0p!JBpQoks zaftc;708E_1WNYpLYzNk%~-yiybsjNs*59sz7LtYzgwC&b1kDOL=iOM6j{HzsL+h@ z+*DuSJWFF&PGrsM6j1vC^#WXBVOJ`-ekw2H>|Z2# z2tEvXuw|4~A9oP(=rq2$nb1RddBi8e&z>gO2lXwf4ynEr`C|6xpJ2YkpuB7c|2v&| zL+pbl)vtf0WC;DfKCj$IaGT1-%uaJ85-jR-Pg%{|1S=pv^17sc733?}9EXGYA01kY zGPCEkrDT1D4gNr6P!Pqihn=ypz66Ik%c7&A&<`^ByvYA4(>i-<368TJFt%KOc_G10 z8|E3-O8!q6>U(2i_R;4T-{0e74fOt%h_7Pa&^dCwLU&OcHb$EF1N%&^sNGI~uQ}GE z9KJ^Cw>TKfXfiYZLjV32*qf7LBzc4LOL#@y-1X%CwMAj^VWq3P2rkaoYBCd3<`P^4 z`{c8`JLunA1%Iffu9n&l3ox&_xBu2_WPO}D)z~Q9Due$YaKtryoER@}vN$pyLpYJyl`8=_I{wf$KBS-;iF5`Y-1BW<$C=y2=TEVQ$I~&L_D)k8f?D=>KCJ zAC>o&*#Bs!r1}H#6K?V*nDc}CAuPUtO+PC2_rSknz7Tro`t%}3BM+4Pfx0(Z_|Gm# z@&NNTG%1AIrsq<2Q)`|X|)QrXSe%tJx=;k^}{wzQ|VQlyl zl)pv&n4Ug!CV3t`>JL#*Nc~BCKXseKLgk4A_P)QrRPX9yp-L zUxmh{dT0H}4+w@lU@)KWEWrx+Utb8fsD3Mi{ztu2I-%=Lo)9k%D5MzIr{qfZN2L|> zzwnRHMSnPGYQiaki*R0AxFLq}pVQXNy<5BgG@m|4y^6H5vKA`;NMZE)~l;$aTjb*ahb}!u@UaVg+q&^h+Tg*$5=B@h7 z)R`9EI8ERGkqpKZ87QanRGF_A&u5-Pe%}p$qoS&p`gaFyP0;s4>Zedo;OOqB_GGhB z=ilBYop0J6?bd|u+1Dp97Wn&xlf?Kr{QbY-SJ?B9t-<^eoE@}fwjowj3q6@ zqYMfxY({;6&%5^L4V5+vv#Td%?kXnpFJin9^h;tx>R0X zhP{(=p`XfU(LZ;sdY9T?VM+7Zl6uL0507tBq5YQnPr8T#RZQP&uafsYBj2H6EaGR>f0U!YG-TIp${!;Czx2|t=>CSjS7F?ZKe?Yy=VfDG!21~F zAJMCB+=0rHOh`uo0mvR;mOJK7<(6C8^3E?{rc{XYfr|AWT57D7J+ z{nva{n#X~DR^!O%D)$puf*}1^zYa6 z7Inm!Kq?Pfta5X2KIUf*zEAj1A)RTR^m~NzTF%vc-~ge^(a)Tfnx02+IpY5r*QI<~ zA>s?}x*Ga^3X!kr$(81VVxB?Bm@$F$d3{V;|KUM(9&$g?{=<2%H3T2zWv1W;$)2im zx^ezvB=tYHWuQW*EoT{7FZvG;8EWnltdBr^`oiY@1S>F~%H1Z#Q^FB%xO;a8-LD5Z z>@SNHuPH?SC8D-=2hP74md%%fb0O?fQJV0rD2|U@qqK55xm1DscTkJOJk>i1k3z5Krw* z>4(C2=NHM zUGl$+>^k&|ET{Xw2=l&g-TLGNSugVElTS+WH5KwJZ^*pi7SxxZJ-m{v=Zy!H7iw2i z{!@gMsRd!#JVGxtHmTqaQ-4~#e<}{(CiHUfzrQ$-neea4tIEZI9D@Dgn?f*uq?F(| z-or!%OYh4U^Sg7eN%lp#RnFe&N~iK#{2s-{H!hRw+eLXBI);8vr;Ul&yuX3UQ-#%K z>gqasfvk7)ZK_-ZkO}rNBR&@;{rywGuiY5vN$3Zowd%4mY2EJTsOq_2y$j@Ui|0y@i*^1v++;u(V zngV)aZkKpo59$NHr18UndZ3XinOGm?sl9%UypMyZM~kSF;sbtsn|4`Gdl#X1#LCo> zckz3J`wjUx=1gu6rTd`1wY*NC{?j45R^vZO%2!pPzoX3+K)+8|QX6~gRyVaLe9RY( zF+$%Ya{mSB$31aF>POIKQLxnMn1s=>LDW^Fzvi;17+b8ued;f4|tjg7_WfFOfe-44HQ< z;>4WWx5;`D51mgb>i6{Y94cQSuhqU`GAa+txu%f)`}c2_u5Z$||M|_~6l1?~L;5xJ zCWq?6xeGmneg*ZuL!U3yjCzhC&*z&IXO4<2r~Z)w`HW@Dq=_PyHDk;(u*TO*Ee`<^%5C zi@!&nzXJYPOR5ylZ4YrMV+7$_LN8<;Apu^gKe`a_7vivn(B14s73y`Uzv<+fl?_oP z)L)Ua4(7=xqv-Qgum`kXUEl@RiJRX8!pDUy4YNYRh`r-%-+#z&* z63(k=mFkDfF>g9zcjHb%SD4yV+rr0Ed&CKSk-ALESBUepCr53i`=#70Gu35C{r^rY zW6E{3zfA6@Gg;X8-t*A?E9MIt8tRV``aynvNCP$~#Wo%0|D9+hSk#YQoznX#!u6#? z{ObXIF-Gc7Y&Kq0rna|x$@QoYX6iA3OTq=JbcYmg7yJJv4LQH38T{Rym`LrjW=_sl zCP3c|%AYvMYs6RS`-l7t`S+e4`aY`gyeIIFtk)qwj&Z{42|k2;hTD6c{(WIdI&*%$ zbRIz%{KG}Rni)sd^QfPz^VX<T4eotdi;rimVs8KH)lzN3w_~rzA`H;Q{{lm(F*C zep2`MCR6?F#5{yG4SiI9G#f2UcfXKAeoxLh7@<+hXE;Ic$&uJtC*bi{ud%Y%|b!Z9;S8c#;PY)Ef|m&^~aVn3)e(vA-@Ov`!#Vqz<@lE`1gnK zKt7@P9dsuQ`1d0Q@N@9S2Ia~9e!umbMfnr91K;fEB@&6fxN=J z#7@-HjpxMp1m=4(%#Aw9e@o@ODvZ0M`(F-unrV^zP1r}uk~Izc$op`!&8TnLPwl-H z786A5iFCeZ5$xUiFloP~0w>Ov=C2(@{q+PfKRLLci}?9$|E8Yn1dIK%QInTax~T7Q z{6{mPJJc>!ZA}gRK4N@OCQ~Q~9r1Uahw)uaFzCjkT_02WO7thh#r^sgp%=p5t>-vO zzhd|DHPPnXgbx27@jR(tsS5cmsDJvtknarnLf98W*6Si%+KAeI`hG-tmg8{O5jysx z+U5P7mSD`I)>wx$qA0kOOf)oPz3HvsRn@_bhc1x4UgOZnFN!$D#818r$)3e{g;j zaEIdb8}=*3_=!(G%i3WtP!t^5&gl!a{~bE=#51hD5c$9=27mAkgMlybcN)&+EqXpm z%BFJv2YW@7>|*b`y{vyG?4gybv-tU-?MFOK`M`=?CCK+1&*d#0^sily*dy|RpAo&g z_<6}td6Dr|Cizke{de~IkF@N2^!~cK+TUB`?k^HwPbr&EWTRhH zd&$mKu4?@=5h|xHz1x@nD?a=m_XqYRSszk<&-{B2l9=z`tjr140e_RTi*pZ>Q{Vm2 zIwS|@fi!Buqr{cC?3Y9#pI5rzQUUwr!~JuF{i`EYvZnB*&ag`BulZ{zz*{R5Meu9va?F|HE3`sUjlA9M;Yvh5LnLLJ5bW&P#YpB~ZVZ<_)8UFoP%)vVnI^Iec? z@^_{Fa-+&k@+Z!>;=v>F0Ln0XH(U>Ke(+-02#?{S|#i9R<&|7|~ef%CU%@Yg{9V&9D}Jnwmd7#hQYqLAKABW-&6)44U`XUjY?X= zUyFVa(So#^rh!^b|G6IAH=6X zyw79sT%3oh@gR?kpFPEFAN60*cLp(&oV$?oIQ?gW9`e4kIJFSWN%j@ zo)zY)3f6uN`LaGZb>jwuDgMLVhkwu94ftR2i2jfB@A-l|$FBn9!$tXNexAHBpUYwz zKmUl&u(0Gui zhwIld9#PX$QK}+VwS=8W4aFZgw|?K&tHQ7Pg$=Czu;f3itIAoCm1ll{aZ}- zbnu&@c{%H+v-cl$Z?dht*~aMy^)ES`-`vF7%`mak>{!8pUq8`K6 za8tfGry#8&!dTD8SLEI#ynXUEmrs_1zboNli%Cg$3)uVX@%|g7 z1$_Ugz0JmPEI&_L@W=7Pw_RZU3sL{mxh(V&gNqQav}1@(C4)!K$aj5jsp!dI#538Z zMn-V{OZoN9t2fuMcKAmKQ`}AUG7#@TP;P|Fqm;fS$C~OHfu7iou`yrupgKzJb^Yx_m^xkhT^6}95T0N4#4fC~oaKD}V-%xa!BoH4vd59n*B;-S?(O|5Yp3-0QT+?}uE=W*jbQB= zupdlxsVHY~Cgywnk@H9M@l71>a+|fAyLsDnbl>7|CiZvV#P)X94t^v{>bR83VAP+N zIUCB-7+foOktMy=hZ#)yJFbuU`7XlzN;A4ROyytD>v^m{#doBuD`oWp3lvfXy`Q~B z?fXlNee!)c68!$7`1>FIJoO(6!To?e_%+PqsP6$m!3_ESfZr1~neMaTe<6!)EnCIr z3vCqi&m?cdzXbQkHe8<5VLgyRD6GYk*{V+M+e7u^dubA}g#8{pqrT|I?=pVouz3<*hM?ezT1?;yY9!}mJ|rrVCJeDHw5 z_29qDuifYTHy!moC%RPf^X&tE*4cT7%L`PV%zDJ%zzp^}&nYH*prcMNZ#BmA@0qLZ z6`s{8Z?f-YVErfeF5vboN`E?1dKy`KJ>;u>ZRMPw84$nM9iI@(+RGF5vJ_gM{o#D1 zphpsksXis2Hrq`KapY<>!g|Tb4K$7+AX$_HHvXBE@~a z?5mo=<00=5eN9xze>K1VEqX!gdps(Sh(qE3>y%?n$1g=dAe-M&S9^ua_(h!lQF=am z_P6{zX9m^^V-EN5_7-oNA0U#$*c~$ak@yy=ot)TIK1gHluZ29eanl8Uo(o+IZ0kj_ zowe6v|I16q>|pSVxIZ+unc~Nw&ni^TyZCu1LcK9;7N-XVkms$ZUg~Af7kC%AQh#g) z>tMgwabzp^X97M5H;!j<{sn&yh07S|OAqlu34fK4kf%Htyd3^8moA&~XE=gCLZ5w- zwYPXfAARJ0I~w0BPYdPrgT}*Xji!2h_W1tk0H0d+{82oQd|mweh2D8WQnJbaJOlV+ z?R+GZ^~e6fKf_waV0*;#@9n7J^s*WKk#2y$k3OHAiuv6z?R5|6-%FslPqwkKSP5^oHUu^5b)QMCGHp zCKEl?;yw=KlJ6(IUm6|S$;V6mB{YrId^{Pj7lh^!JsZDPkw=1^32>a=udP*@L!J#T zTh+$kB7ZLu+*>w^!ImCTvc^Td`xy*=E+86&pC5w&e=1@wVeL52f^=8)Wtu;e)&pxE ziTm`>|9t)axA6P-wp0JV$B%Ewr1uXF<8gog@P0t5kPd}pAh7uwZM}pghjw!LS&j4B z)4pQ>YX|+6iOF}m861WCh&*j9KTidqzm-!i@%^FnH#|V(@~;8*){61D+t~9IA079d z|_sLua@9@{#cHg^a zl810!=e4dn!P=?3^L5sv`eUB##_{q>)=urmrN^%nFu29NR-T;P$=8?C*z;P*JL0w@&Kw5)i;ZpF!r*$?H+!mVG8inS{AHm@ zzSLp7@0E@zW9@W3%JWSAyM@?aLzyXmN}(&E@+RL8Y7Zx>;ZpXz4*LGobIF|yE>sc0 zr7?}ugBb59;Z6!P%7^qKMiniHX>nN$Hp=sahMIH>29NZQl4%;dj31LEgHv*W6Fu+9 z=R5oN;@^LDf8-<2q4oi+R=>Sa1AhVJ|817bWd)soH_M}iA=M^* zE5=nJq=^SP{>=5)qy9%8KM(N#u`QGB3TDsK{p-vt({LF0et*DWCQToEOVyO!*oLRJZ{u8P4@jHNH2-Y5QLGK81={-Q8vKZ}i6d zCqB919Ba=2eJLw*E@g0mE48N&AI{)9yw9KJe|&gej=^3zeE2p_AL0@fc5}kR(^xy@ z&tB+!eEyQ5QL@VyCl6%p(AVrrORYJ5lY;-hU2fv{nb1Ge>ZF{0yyPaAeSa>Zi9J6; zB^OS#mKQU)ek9dzHN5|dQ20kb;=kb^rx(ukwi*!cho9x>YqQb?V{2O){QnG2GmOeU z{D%YJe>Fb;qu&>Of&4=SiIZARmZIP#Tc7QIdZGP+$=}Km_QG@RH#z;UhkP)#%oM-p zqmT;`xu$sdW;r33dyjDbkuMM-G^B*{hkWP*8;bY7&%Q_H(~^Q;+8Ipu)A}KFGPf|XRyJwmSkiXa(pSo{bzfp$-YPJZ=TO? zJtjEpUv$0SylKkMmJygQ%PKeJ!$3TsYtMVxD_MWo$4OLF z4IfXYDq7aomci*|3F0jmHE!eb`j0U19sk-Sk7-@u@069z)c1Qfudl3@!;&x)&Cv!yweJ58Mbe z2*KeY=NVk&t&oeOT=w^_tXAW!k0o)q9t&zbB*DGXHxk;WriFk-Kv4Zo*qlmX@yZzuE>u(SL#E{vq&t)+9 z8{%tB@t|ttM?Je?6sK1Xx;pp6?zb1Q{w?l$Vg35O4;ZWkKJ7kYk|&D5uj`DsUV3=` zpiKzj0r6f8Mzvi*TUe@M@HCxTb$FCB|K41k(KWf%RNn&ey{fK`ghv3{lNHJV^>)-*gsC8sJM~HzrQ^6iri+(VNQSOebW*+z_b48f?ifuI+8zc z06oxX%n57Hx0RFINYKjQ<=%SRtgON=2K%_zf`0aL`==%7XXE8oDQjP;9pMW1jR_3C znbK?9;v8qH#ociRahgWAWa{pcUX zuYdLDU;mXqkw~PctrmaW@}-s#g!3Za5cF`Ju=&dbgB|B~>GVQsyzy%Wo8f+Pm*+E2G8p%h z!gwc>zc1x~@CV}OZyM@*ofDHnSbuuIc>j@j6>2XoE3v2sZ^<}6){SeNCKV3+?i5^6Qzi;2Qi{}TS_PFGE z^SC@<4tzU&cnn{U6>;`MFISgweoo^-Ixb$~_-+9GLO3V4&sgI5yJtE@_Wc36d?6&% zWbZ|OZpHYjoAX)w4K3{Z4@~;uO5`W~blt(ttexufv+r%^_*!qPmsL+OeLjM-4 zY*>F^*C?T?vaE^0G44_F5oP-t8EobnZF{J@`x1j4Les~M8FP3H{N3@hl<&|=-JhA- z+J^i2yjSksRNjasWdEUah}Y3OxBvLFQwh#@O={$Sd;I=K>5nhD_V@7x#6ht6G-t?3 zom|%FfcW-4`yo;0EYYbVLqa0iehkW#x_5LWaeTK#{eXxAQqIqP+#z3doBY3)c^gMs zkE!PP>gygQ1fN}k1K2OW709DV&y5OxJ~C{z$=;?J9Z`S@ej zZlTT-PWAS3{IEp+@^hjozmtK=%jE~2V*QUN*4Sp3cXIydi+orik(c>-^MO8_+!@O0 zpVA|WoD)s?4yb&6>B9Lu_C9;ir%k742Qyd={#{m9P|9FRAAWGMf^;FcX0Zy!}nA--{$L2@qhEd&-wl}z@90V zPpM|lJNX-I+dDhXp*|HZ(q!YQd@a>qPs05gnO}a>iNbjupf~npoCNW$8GtLZt;SG& z+>6NM%iN=dh%#vn@LRrmr|5Ld8~LZpA~fF<{q(cqHf6CpA$7?b&A z;7_mx_CqQkIW%gp-lz9o=v}9@^e4m^f%~?h(g2B*Fmp$&oD`HIKDaL)QW)kCm_9PE z$r#7>ziym2DcQgO2!ls}o|8LWu?$AO3)|~9L-_gc;}6LzWvrd@`_M&i69!Xxer}cp zzprrwzCV|5%GZC*8}jFmKY~E?(-X?yGFCfOF&O%+u(h|Bzn|JWwoq~7VSh*1_rJx{ z3^w3CA%xa{W$mc1+*O`c!Qf@ccNzJ}{T-!G`zkBfuy!+ye^6BwzrUdVmM*JCd9wCq zZ-d}+*z|pKz=yoBoVQDYdKm6$GXy3@<)7kyt;Y+Nk!f2ICnf0&dP@NmDS z$Gr1wua{J=X3rP8OKHBITn1YN<=I}oREGO>z1h7QYcJFzLcS6s`IyXHt4x)4n!EMI zw}gLBTtD1=e4DaKj`NmF{cZIA_r-O}df@ws#Tw18^JV?B@!@~I*N;zB>Z0+CD?uMF z;;+mpeSm!aXRBN`EK$dy?aO zuvb)4e*x!@Xn#^;wK#VP)z4%HbNTt1>z;3W;GoIAcg-F8>YXmGA6lr5a$N8Kz|I@a zqj3M8>Hcpn^p%O#`}VT-k-DR<9ppm1N7_seD*xO&(Vk- z+@7oR)(Wp(uFPibN4*dqd^wUok9>l{w`HY#eWeO}L7!JsFZ${S%gMkf+#eCdLmUvv zC-Fh>H_OESX7^zKo+D)XQ#YKN$HAUk-X08CPPVMyJOJ{e)EfDss6QGJhFE7_4Q#9) zYmpm#nd>ob&tTI=SL=)lPCHcC_39>nHd>jG!dxGOwF5g;!zmF0lgIWFt zzeN4W^POo+`}IjdYP2Q7k7e7reS`X!r@~>9;Y+@)7yQjjx&3J_;!Q7I-sZ{LSNcZ_ z-(SAO?Ey;AgU%)0d_Gh^n9)9j^nY*MicG{u_d2<3W&RmQT?>SEF}0b!5A;MxPOC5J zpKsGZb80_d)WhxXI?!9Or*c)_{A-p^LA)mFH&;i}d=^A1NJfX@zRzVM2v+}jc||8x zX++a~>ni9w=UPqi+snMY5HDAf$NFFMPPe_8UHO2)_2bfI2U^cBq50^P(~)lm`-yZj z!S*ET@BHC?!OtJ{NBc9`a%VH-$U)0 zO!>necfZUA*D^z1GSv^~J@uOB^f-k~-;^8-`C!}voR3UD*t628yiim2%gvv0{^YRd zhT*&{{pl!%L~tI5gP=hE2v|`4NuU1WJgSlEmvTRck5Sf?xaNk z-ky_)d`mRmdW;474<&Lhhb~X}my?p#_h|pUjnIGSc_*>`@Bh;mAGi2IUU8}D>rdwC zZh97bL!T{Aq0f(#jNNcK##N33UO{1V{bM&h==X?T{pvvy^*0eBF!j&6NaHWaV^M?O z`?w_&@9VS2`r`Y_kCk)rd`Zaxw3nX_{-&!%N58iZ_N}2_PXK?$1K+@YefI+2{2;&R`_>Qk-|CNB=y-`sRlogr%M;&a`b7&( z*E=<^pA)kAzNc{|N0BW`=vjgym(Agf31Jhd^G=m?f=>1{crbvFVNfHnIq(1t^dHj@ozDw zGodyDM`GzS?&-fyhyomxcy*C2iGJ+)k98;IfZus%*1zEyGUB)1+;5ALw&xDlXx5QG z)hUw=*GAYu287dHGf6vxdXbas!80{sp#hS^^xe`;|8su+US^SEea2a>rOVm(`v^6PoWi9_nnP6cT3EoZ&$B!FcV}%L2oEZ{ zU*kuRFP_S9eL@=zc>esQ@5#u^1x5aOE%arS9=KaM>dGqbYpD%j(NzbB2jqBeh}DM! zufm2j969-xk(W>VzH;n!=-}k>C!Tmpv^i}yS#P$}%uRus%%RYM&9!7e08ydF_r7g+ zjkk$kYo^w6Zd{n0XwF?Y^BHVNlWfeMGo$Az`1O~!VtmT1Pnxgc{Tj`pX}8I&$Ht{6 zG#!Hs{7$sDx0FmbAV-^H|7_{M|9(CGUxSfTdhJoajpifz@o%_D!lc(iRy7O=A|y2A z{Q14m;aXn#K)D0!M{~^HCNtZ19)7tnLnSBeKO8&SLw?!Txnll#t03&hYe9cS3`+A9 z^^kdQhQSBt)r7pzp{{>eb7Uku2nZ>5a55Lof5{p0AZqYj;IqT{!v{qxoh9TXl9iY* zT;7iwj6a^1B55rd5LBSpS3dZV*j+mzs-8rlM%U098q@h$fF&IB$-J;A*_ks&Bk09* z9>$Cduz`@+l+ccDa$~J)ux|2+IGo2f-w6_3C8;%*p;0ddrOW(kziyjL_j{?K--e-y z{5Lti|GV#hKmE(${JIVFJMz8U$Cj(*;J+|(lYqff^k$QG0KecUytEKLqDRY7bL&suZrwFQy5GQl2L%Qsh<60(JVGRvLB1Y$^!n-I z{I~7bW50I9+1qK3xKxU7eF*K6ke74{yI-_OV$l;NBXUAkphiV$>6M?vUJw%eriT^y z-&=jNSnMS@4}@F|RoG4UpV20EhY<4`^bbo{Bm@Qqice44C$opW5jBtRZy)O{l6tc# zAR#MtgaqOePb`!?K_mj%5muzSiKLM~gFZRI&3mo~A#?sOjPdwK&!<4~*>CI9bWQ0G zIqd~AzF9%#h39LeU-_U$QM`6WoVdUeIkE(tms(+z3O=tSX|T9^ z%>*A0g>3fhg{i=YVM_1+x||;W=zjj*=U)WAoBORVqBgC@;mD~=P~+I~wB$?R%a}1P z$bqUio18PK(Q<~I#M2o0Z4xiIVZcV8ud3d#VYt{cP@>2B>yUFBDJ@&Dzg9|fEAmE| z)GezmC0XqC7R-UdFNWz|U0j@Yu>CAV4Y~sxJI-_1Ki@SdvF#RvmmvpD>XPnX82q}f z$@_4Z=?=WezsB|cy}_=#aP(m(4hNek}O)4Iy-#oZE& zmXPal1&Y;czDCaE5VJ|A%_d!gO(HoVFyWOx{+}?Z4(kDV@}1wip8x9oseA{WUEa3{ z_!s5($~60yX?^@&6Ci4z6v%*a*gt#43$CA5i}Q(D2{7!dIHRzn!KguTabh4_L?<=7`!T_#&yak)!c_kri)T_9O=2i+8g|9R64CGM#4NuuUftOWP{ja z?n4>Oi#QLTf3|iL&f_p8Scv~ZsoQZM#59o_>2=VH>AEOc+>8^}BH0l1yxMHs8)`Xu zCuQEeKKT$LBAqk+S@f?T=a-2ThWya4zi9sH@^}6>u8Em2RcFoFObBw_afv9QF(c8}9 z&^*=6n`8L-S%LGi^_l0#vi7(ob&4oj!>{jr$n-DbdoGo$QFD!$jq@6^M~D5_YLgGN z5I71S@4nGT26_FJlMTfIBx`fxFWqtjrSCoSr-@R(GcPnx)eoTZ`Qo?h z#r*^N_~A62H)?u$PNDpi{!k-UA$ET|fvuO-LR5WhGx=Cq==7=^tA|vw_89*>#g`>!rvZRlSYD#oBc6_XP_W?CT$`IDg?Xk8z?oL=PQ$D~7ehCmK0S=g()b1!5Au zyS(jL1|LODx1%k$Og>CFKRvfAvRJ#(Td%OG&gI`P(W%wr9ZWT_vf|QJvi-iE78iqGeD2jJaPC0+hRVW1@sE15?wUaj7LaEHLe5`6O};C_2T^90GbCi9lhck} z1e@>wSvS3t!S?^t_kUC$J=$vCuYYTxuYHkYbNtT027fuRaB!$0qEiLra!d$8m zAN#X>uyh>pb@P%c3ON|H3@*g}S5-~r^upewMm}+y z$>%=mh|m!i2VEt%>^Uz}eytRC%2 z=bv$cJ;A7)VNgbc-qM^}|Iz#3udlD={9Z)P@%2`@Lt3bPt7?3e_?(46>`qJTR9?ia zv|_usok(lQ;HFIEq%w9Q2OP2Ms{!B({cg+{_QSl&%9FtiKX9M-xLsIDM)MjZ&19m|_roFrGnU zZr)`5Uz{Pgdn*|}J`d%MW1;D;`_`PvVX#viq5yY)_Kw)?B{7Fe*%0TWj+z%6D@r?=Yw3IpsPE6Vc^o0twHuU*W0o_s4!4>pAN1%78gDjRgh~EsxWvZNH zDCDxy`_~%9N(xycCw!Bj_IvvQ`U__6cgza?5?2gb=42Idv0AYW;c zl5P_B$DBV*)n5;klSQw1UwMJSFB#_g_*Q3T~Z?dtP!M-{#)z4QAqjz{2|ofy8Y%mS-~kUgnPam8cQr?(V#DB0&x5 zKcNOBr4R7=`K|rO{(fkC{6pW8L^(mtqg?SBFYEJOLq>n4gT9umjTO&=(ZB%LfE*>g z_n&!|*-IVVyk#fU{wJ6`|Mhy*OlvMLg}nx&C*-R{g<=KG8QM#!7aW6hb*?C>!{wtd zLlr8g3mts@wg*P5=G^+8pO2%!x7&WNaQRB@;br%oc#`wm4DA1y7hE`8=zqobPRJH6 zZ%I8QcKL;OQ`!30xhm}bnqtZkUx%E40U;e+U&}`hmA6xxxxAqT|E?J0!ui3mIJw=~ zty>PU_hld^1nipA3u;fix3D{$weJ8wsx7_2&)-~sx!tG>z1LX#@kDR6(~i|#pZGGd zNtozls>!BWSR*@s9x?7cM5ZUQabNk{Bj)9C#qRE9b#9|Zrle2@$K+_zbq22vl-;for8|N5y-Pd;t143f*d)W=VX zv&^HYJg-B{>mc(7Lo#^Dk*Y|p&-gQCh5>6k@ z)!qt)WXB5jzGH!M1#oWIpHX>zHHr+g{^qE$x;pDPk0C<^FrnkRsfL{q z`yCnhnH%d*_y5!o%K;3QqK4Fj2@@tV81jeR2G7giG1y++By)B?u$95D>qdBNykp9_ ziJFu$$6eMu2VgO3est`=5XSl|aeh<|4m%i(oJNYhQ>RX4@F>)jExW!Tjlr@dHG<1n z7jA!Ap^LV=*m3Ey*iEcQ*I2(*8kjF6EQn1L-2%)lcc(v@sg9C8`|Ohw#m72UEIe`G zs|K}PwtnxmFQ2A4qu*QWBnQsT*1Y!GZ{h!G#B9Sp8>57NF@8AkU#5E9XL6oRChQNZ z*ElLgH;Euo#GBEafUb(HZ~E{V{QM>L726X(wsL#OSBVD2AX+o>A-!|L{i&skDMwLT zikaHqKb6nd*S}UVU~pw6>yH>{yQe0!aC|))7_G?9|KbJK4t-DN=)7w?gHc0P1@oFe zgYywHq&Jvq1{b(`E8NCTdWy9}zOozO`Zv3J$?lxLG~uDV_;{fzO7`Hw`Sqf`^{28Ic5A791?GWDkvtz> zw{^kjBJkr=Gv9nqN??DC{`cQ6tc89T1AiEM_`eL#g?=}_gFM;fI?XaDO6E9W>o(D4 zN)5>-(ipXu?2UJ?fgVA6l|X<+jQgVKo1FeyU_7J79Omae205Q5cek{#`K-{@yS9p5 zT)vaKX%%Gu5>7u#kYhJ3;7khZ-wHA))i7qae8C;%OBSVgjCI(yL zJnQO@A7`+S{}uU;WfmrSgzw$y=;HK4i5$F3F81zc{mneos+y)yejaut=Bok&O)<2^ z&_6#o^){y;F{mlKufoQf@>jVR>Eplm?(NwjzAOxs6KT`2K%?y|>UsVmc`;~8Sb?LN zGa-pVq2aJ6*FEYp_gnZcC01+4`K|4D(fU$@%diXvwx$li#z-8r+ah=)Hg+$xJt=b-J2j(05I6axJy*W9_By)2 zsDAG1TFUx2yGGer?@!|N-U9Q}>2zw=el$>FH-*{{9o_F zV?2sFwYr(XzW(X1%|9P_o597n|J!d z^cTl4oQFB#0X>ih!u{4~Qh&HU{kZ$Qe(+cNfib8Vva5R`$1n4sJXy#iIZ(1Pn}mWa zlRf2FT)yILXKM#rkE8D1iW#S7zRut}m1L-1I*QA`#n8X57O&v^!wmM=tdURsjrFJZ zzd1j5a{ZwNHCE1_KF`meFKU2XycinF`cr%#J9fk~43^mGWpVyHcQROwnv})mwWkEad! z*J1yk@fhLE;Nq}!Rr@aoIevTzduT?}`8ljT)4xWRJ8{Qt&|4`rxLgH3EJ^F>fxJKX zdX4mXAMktRkW9gTZ+`rX9LaX*|C;uyDIzhmymQ{niRyFB$q{p&e^^5#8u|duL9`Jy zyD5K=P<{^>^8Sk`+iA8k&Wqf=0sj{=^T(Ft4;;XqNp3K*>~14+TGQJM^Xb@cin)$a zOYGK6ycEI4ix_NS{JsYwgTIXPQZ&_NaeCK`^_%lZ4qEuz+1>2w;`@*LFgvF)CVP!N zYH}JoTDiT*9y#lXv2zo9zmD2_TVX$cNFRKmk6k!(hU@>pcj1YrAAg#)Q~WO*Ie9dL z4XP`)^Jm=R{G<@_V|_jJ>4)!&f&FkDehfYuSf`qopyl!ay)W$AhWnd`{k6y;zN+A0 z4}&3Z4t;$6-VF>M;SsG^e|AYPgX{f~gBC&b3^u50g^dR`ZDp_)Ih56xo`ydfvCJdV zDAY6(zFq@+Bm5<%oF4KrR)eI3hT4m2Nffrc=4xQA?UbIh)N(TYjB@7MmMi}o{a^Wz zKTvu>`2)@mayYkVPQ(2yY9=;{))(4<>c3azUCEXae6LwQz(wlIKlBx4g29BTqv>Uqt*GU zq}JQ~JkgwmpM0`$Dr-NA8VyEcR1|}ikeB|N_|8uZu5+ysww$l~fWxX@2vGUYf^ zLmvESg3A!r-VFV8?Dhxze5j#McYid&iM8vno)a9`aD1>=c#-%0wFSU`AfS}k+aU*j zhQvnfwq5QJGGJgm&P!Iss1f22z4ZAS%cyCf*Gb8}yGqGBp6>B?l}2pv{Dj&6#{L8U z>Asi>y8jgaZ;`8M-&lPZPW>4p=R4$zN3q|aI7g9luJ-1N(eR&ID~Ac+pvV_4c6M<2 zCIdOUFNI$^!p84|91@nJ|KP!4%;(hh$^5(&fZjS(UFG%!J7d+m7+mk}CEIl21+EV^;Qp`7x#>;T4*gh>mX;dAV49Qm zpyyt`pT3}HCue9+vvx{P8XK<|8BFE<`MOSizRY3o@9YRU%i0TJZ!7bkd6mIfKVklR zVemhLvGu%Rcp>ytD;PLI4+cITRp3PJH+2aSbD;nIF8SY;od3vFEQ8*IUU<7 zS9AL1i~Hq}@Q75_UVt1fE>14|d3wGkjjiPUN5CGr|Ih;s>rZPC?BCzHn88xu_vjW= z&NICrCxdN<^xvNXA@O}#eDZ6N;w(bEkh3TQITQ*D+hTJmy~ldzQ~v(%u=oC!J}45a z4{iNEDw2Y~IDjFD-G9>$k}V&E@5x}F?_}dOLrxoAq-H6D^D*ABy<2xMSdII^0Xs}N zESoW3oATTIeE6uFgzUD>=WmM-A>>2M%!g2|4HOyh|%Vp!?4kt?_r@ z(57cN>|P_xNqvjk8>syzspGTHS-TGO!N)s$6@#^^JmIrld$~P{$_o>>ZhnfjmjpHm zb6;8d8iQ$0FNqcO@rU1cp!(h;HT$HHHwRT*ZDs8S%r`@q!tF)&uum8d45{bspr5y; zUOEO-|Mp6mDMz2VN3;-Zys4q`ALzeQikw7&5&PCse=_*XPVxc1$E8IENJ%vOgZlje zsQ&g_`}>e!(ZtU@<{D{CKGZQ)XmR9v}ecIQpW8RokjFnuN#gX-fc@Ox*~#%+0e-SIVhNYGnqj{c4>#Ac z_h})I*zUj1+g}7dy>}6xd-%Nq*!zBtTRNA)RGwd|Y3KYEIh7T2wp~bK?S-iM`Q(Nw zPEV*j=>Pit4XmBoBU73WbADik`G>TUysd~zn5k8b)Wo) zn%`Ib2nj(B;oocT`K|qzz#b%rh(N#EZx67zBSD&t6`{!SG-iCGxP!Lj3|6Ih*@j-| zPNUvq$Vu_qh9Rr_=Od77sEN?k-OKrz4{}JQwDxj)AADSdRlOI3+4}im{o02Q zuVV1)uy;oyDK~@RZ!Pp3uHf{w2>N0|Xz~))PUWY8l@&J`tN?y*pM0>C!45i!BHNJB z$Y3AXuahIf`TH}#-#U9IaC*{!8V%=e$MW%7>hf&|RXteDo~JozQx8q%^zQY*Jlp3E z?JDh?Kg^>(IPbH+J$nK2JA&6gA8d~KYc5x#>KGw<$@JyRywl~0T`7yin}o>L5`|-E zy6t$65!s@Qm`4q3ePPfNxh(aYThP}+;7N1bvyM3 zF@NNHcgcIO4{TCe2ldBKLLbFt<(-h&L@(ikKksW4?+bFjGwZ#t&Pulx@4%jd`MOOX zX7GhPm0&D8g7p|TP_=xJIp~LGX+%dqJrT%J_jCc`KbOyokrTP=gVB6HjzPb1IN1(@ z(og@jSkIlerpGdPu0QP2y{(+SNzj%w|EoMI%iHi-~g{Ho0K%CQMt`c%}?ZXE6ow?3wf&! z{M(#NA4cJS*8cu_zke?be~~_YLj6#F>>^il!f~HKb9C82!N7d89G0#9H}pIwjSw5jfZ=XcML?nJv7Sp zZr#@p#2?Ts2*lo%$c)LUBG!BNf;X*6i?^KA-sq|Bqn|HJ4ObzLT^1L2i&v?FNJ|-oSatd8N@D9xis9onG+f zfk7wm{l?csz1XFf*4q1hhn#h?+oID*k>o9F(3_2`ljBfCAbtTk#@b_Na^>!oU|fG$9H6=!Ey6C&lM@qzr#a;&)95S*D=R&wFaQNT1F(-ZxBNKU{(z%e*)vUBwLfLB-! z?s%G062`2358E?nPBHSwd-Q$w^t>+8Xg(z~K5PN3gnjHuyzeK{+?TU<8Sr&vW8Zjo z&quos{CB_D_x`{B05~7xUjqC^eDL4xr{s|uzSv^?(2hU!|NbN7wcq_dtlRH?=68A^cs7P%tk@sE&kM!4UtA3?|)h;duA=t!P8 zOg;9rUPfLDOP7VkUf=mS*5h(g_SZ+zUvqBzu`A>)wPTR|WEw*l5gHiSM>G8X<7EEd zIuCs~ZMBYWXWvgGrCYBISb!L3Z`mt;-rl0*<(Hl`cO~GN4Yyk6e-8+9pbK z8XGkYqU(Ub>*f=PWl*#%T32Tf|123t%wZ$-Pgl)&GdfD_QPyV;^j>ZmByn52_3T9W zL|MU!OA4E{`&;*luCo}ItD&{7JN7+$5HiIuC8X%>ffBc?i{DBUi;2_=vpR}9zQn?e zY|+6WMP5WNzlF6zN^k2_LCG>XE9&;HanwnXVBA@5xI+8{184I~`Z)ZVIHtCp={Fzy8GqNcy>%U}a+UgCrMS`Mov zs*Ri6xQ$^N@BwMjZ^Gx!fDD)BmO6!E;>3wgqBk4>y#}41tE;t*j}HhDr!DunPNcg> zxJAnnqN2|G^qmLzi1*Xmf8zsc)7Rcl&*+}F<9vr7|InA(d=R6VD}HGCRG!p2YJ^*@ zaQx&C55zH+)CM4#i@b?eR#wpQU=Y9`#7HUjUYy78SdXfs6diXuxlO15KAEq-b?Y*l zUm^DY^;lgZgO!lMn+tDnovj`|U#o5{n8(`bolt#OSBlu{B~^M!%^MJNW%I!rD>1@r z@X;D8S&S}P77}v)x_H*h9Q8H5lo;>D2Uic>Ca)Nk9}If;DCH-?RhE7H1ZEOCSNhwH z`NsI=#88`gV=m@vb8@2DNWJxnFgZDQzxSfpA$NOo?F6r{;%a5VKdf;Q(}wmvrwlU) zgKRROv-i(`8OECw-^%AN@rY8q`DQ{68@~bb{VaP1pRWNiNkOlfV$!6r5q|x_eQwjr zK>zr}8yW0*o!Uz=|MsyjF@H*4%%|ZFt#IsTDWEUYmz(>oeO;$jB&;3gCU&l~FIg*> zsTTSu!e_txuIM#VGINr%%Qe*59dFSv-l!o;qRY-Q=h>Z?Q@&YNeh*rVv9-|$&f+-S?jdF%s;66d@A z&BP!!UaC{u3cEjJ@KN|^B*Li8VBGoIP4F;ghF0 z)n|5m z_{Tr_vF}-iMJp;RzvSbi^xC6WW!?r)N@r+O_6#1X7 z;&&)I_(aEEH{F>lU<3UsDW2Q>>Y>BW_q+Yb`PZt_v&wIn-j}Yr>jC+I+w{B@=Dmv@ z89d;v0#)w#ia7YlTh@q~j(s6%*^E2$$18y^DfQ_o7 zt-XFvDI`DsvCVb9%V)d~(bW|#@ULB6}?;-8;@uoI@qfz%} z|NiTV7BMG#D^De{@6-L-*1L+^K#j1eKX+vf&-;$L^mZ;zRmH47>dq;?of8qsV557J z(6i5p?_V?ETY2$({v+TsXOXpS8|z=^T5H?AWy@9uvdc;+koj-d{9rtVjZo~%E;gSadn*w0|Zj0l%6?{MgwpBHQ%I1j;d&Ov^}Y?Wqf z6}Y3w+u8jsVDE8CJ2%9P$izsz?;@C=Uaqi!4JY=0TrfYRQXNh2WP|r5BgPCr z=~Abwvj~~$()F51a_Sv1e)y1*kVtAH1+#l(e}6sS-+~_pf0&Ht<@gNH12%?dws>ZJ zc-|5=ou!)b?XV$uk*F@^8*y@3Pgh9)dfnKhbbt@Vn#x5KhG4o`smA(`t)@O%__b{9 z+mBTxvHkFCALC932emKnD%N}7W~CFJ7qi>ZUSnM|Y@5ucUTYk;%B#FXD`ZQG)r&mE6{)P)OzDl;2cJX=w$l9ZV3auT7WYP7JOrPr|yHy`OXGwbLcFZ#-8Ki+GdI6Y3Rth zTETO;XSvw9RggWVlw}6$6}P&>Q<=;~znApqSi}Vp5N$%#;K6zICqXl&3+8bBE)Ins z+W4v-@x4__+M&olU3Ra#rshlJIhuS-2#;F9VT_As|9VKxjqcudckX_cDs~1a z%-1T8n+IL7Z9W4#KC%AV?PA81dRIBgd^-$rZLT>3vp)3I0*6)?SLKSotefrqN#d60(TB8z}(fe>@lerwv(%gtW#P9CqE#nA{HP<{q zePr_PXMfYnynNIOqUp>d{js8k$=>}qI_DR1{NsR6?@3~CrW(hOcflE29kAQnzX7=u zx>I`>i(Oi|CH5c_IO8(q+3!WNu)txh*=WSdk%;irB+>HO9EoF-q$SXcj5%D1HC5UT zi&0Cd?^aEdx~{j!3w948r*c7I!HqWX0kd&r(2w|@EGfAo4RWg>{UzrpUDMKv)w$v) zm92%L?c8V(y4Mn*cdRus^X9-zR3168rN3$0mdXd!ZHaZ(CnhMM*L!UO22S z70FVA12L=70S*q)YDEi+0Ai8jZ-hTfMh5)5K(h15PIn5|7?UG~zIUjC^2}Z=kz(E6 ztS{@Le|K@+dxO34$6KLL*!$Z1(!blg7yrWV%E&A7gSQUeGV2G4D4Gew@&60u7!Up* zTSLMh{y&zQNQdICU%N12?%d}h7tT#wh`;*%zdmY#BZGeg3Djjd)-0i6)};++m$(#N zdsS0mHO8fA`z%c*A=hTb?<8e&G-vjFIZM;3Cx6*9QmZM`>)+XVY*tUXlzh5pRLU4@ zqW^5qjEvAcLLNJ8ouNr?s=2kfHTktNdnx{#aM^jJd~i{w_ob1D8!lRzpxHGt@8)Kw zSQKR-JF~aXIyYK2a%R!7S2f}R{oC~W!L7tUdwOiCRZqwJ>jr4g*+>mJdsd>qb@|w$ z>DsiCka%l z?c(5Sn^B3ye$`HnhrU@iD&t07M)r*CtOTsYycyZ^v^`ax*6*)#(iR8z+TgpfW<9~- zyAL^Or8c89!y0ZF&~NEq4!yt5kYT3!%ORJY9!^z57M&c{@O^3io3l^e-Sc4Y2W^iX z9=s(gD$3}xrD9#_P3sr_vh(D+i0V-`KOH)`t~cVW)3Ap3*F|3L+`aJR*rIl4Yw56t zx3WKaexz*S$xnC2Wt(L`*YI{x@|`CAPlqb9gT=Cu@jF`$$#>-OyZ7LJ)0w0WF0#}j zKfUIyb(Y$^$aXqT?foj3Np$>?=Z;(-g?ZXbt-rXn?szhhW@%<*Tzj=-WLX;KAB)1n zi{8q9NqfZErG0gFX@ayVWN}u)u~|zxYCtTUGRncdK|&&WPD>-q!Dxf99D z{4_n4&Q997-W5kyI-e|D=4aE6yOBdNe%2_gaQD#mSwvQ;DV{a#Fs-t&d(W{~iC%2K zxw$l%lmx$acTZ`u!5FC-dU$$*b!B7qum-1C`GIr6caJ*7Lb}lWbO`K1z-BUbE-mB5<+`P=MBe+pBFJYNq1Pa+^#b)|#*w#yCWG|a#EiP)$Ubk%4 z{>vK8af7cn>R6$l(!Bt8cTQTRd)_%s>akaq?)rk(rPpTpdbg7DrAqgPS#wH!?=OZ#=8noZHY+&zl01GVUEeY?wrIOwyA2`34wojp)+QxE4fA3(!KocX)>{TP z_LN(Hx+g24%Eh@Xey4RtEL>eruA8paRGLXoB;fnVol>#+*sH zr_jh&gY@N;7;nwF-lq4ni~ZmeK77cQYqMVK97*0T%1Y4mb~L@e?%1qIqh24dGkaa- z5zhgi?%B2O(i&@g@7B7;b6Yq5WzYQV4F`6QJF~~7C_MSj{+yj}Wse=ZbN$^zE=3O2 zl{P=`iQk!!*i?3R&o}E9I}90e2G2#@Dakvt=jDvx&ZfK%i%JuMOF$Q@Ko?TVN@niF zxPv2-T#Du;SXxUzk=b+hPEdrvC<3B|PDjGU3fd_bUxJ8S1rfAT z5TzgzK@^BoEu~(dHFB{b1kr9%t#Yv_-qEqgh_!Ml-Vh6Eoymx3@}B=n2%Yb@=R4>8 zzP-;rXTCGbHsQ&7FY8_JdYwH7R=Z4g#JV)tt#)auR6fQu%ABIo)1zYRGwOo=CAXt`dTm`O5BX5tYS+MGaA|#4 zwY>bBUpt?krowo#&SQ=@KZ+*}YR&QH$rzudTlT6zPjl4UwJI~56>XY*D%ZjZuvw1znfi&h>%TNK~&eQN@H(MBHoqTVz6XeX8 zWG}|GTt238!F!4r9L!k@{&Sh`nD?Y~a)Qg5Cf&5=PK;{_GfkC}N>~juQnNJG1#_2n ztr$Nq8D^sjd^N9v+1sqhAyS28j|%*wq{r_FRUoe+uh7z6YKV>P#JDHNG)#baazY*W zdJ6bKs$?0;5AuSs?nm*n-QC)BOj_mr`J5)bz9`5=M#u5mjG|DThPm`8K1obmnY^Kb z3oA#T<}pqgohug~m+Omj9>sH_?zUrG$IoN(w{_J_PT&EvG*&{+05~W!)`%6ctRRHS zXQq{R6Y`T`nmBf?kng$%vY@jlNY}*SJZo8+nqMH5?rokDwR()hnMcP(HT<#AFIi14 zoZ~H!_s0}+TsJ;{P)Z=j;+gy*Qk?Nt`P6k7@`KU4xuwYh#K=R#wa&c|%EXTSj#mi20Fa@_>(nmb=b-{UJ2u)_ zo|2KzDjm?Sggmt8n`ZDilH2T6O^(9+N`Fa4UFhOP>VELi%Gg+);|cUX1ID#eD^nZ6 zchAmG|5(%Jz}Wn>&FtEz>gf?pS8MD#H(tH^^y9Ad=BXu3`F5QlwQ<>-^KUkf zR6fx+6zo$C*VGSGOP@7sD;ImsZL(`kS4Fqn5T~okWTT3~IM3F`az@`^`O|q`F4OKp zp0&6Z#tuoP)2r6YbWP5Jcfrrk4N@}wzrc-Onl zfNMQ!&9tg{^A;{rYdkxvM&)Ckoo-d~<9F_+*FI~;mM5vNslQP9W*>0a)VZ&g>HhMT zXX`$dZp1ff{J3dn=5N_``c zUUOr46E+RntGZtPxk8X0QU&?Kb?$Ymy0EhUY=2BwWq>zN8O!W0FA9=xW0*48(V#`x zb2J^tJ6dxrx-d@Yoc`YCq9Ecz^&0p}Az|LFT@yqUu}o2@n~Lg3TF=um%)7upRNwGq zb)k__j5Qr^h8RZvC_cb@p04rp;({cx3&UL9+ngk(`G*3;Cz|Q`H(LV5a-3Hbw6T`b zcCF|)(0iNbBna5mxNco2Xx%(jQV_nMyQz+bIztY)sUEMS$3b6&lG6MbyH?O(JR?11 z(vv*gXSr-gP(NZ1mZk=X@5JV_^^_lUQ#mEzOQzk~c_`;-(5kUhWN8BQI2O6BhyLW? zJh!TK37R)u=k8No$&e^HWMA!-j95AO@e9?`C}!xad>`dciudUEh(hsr<@8!P^1e)~ zwa%Ri1Uju2Nvr#%WVg&IL5Jts4g9eob&$+c6=&#%bEU&IMWGDa#8Xu!1u?M(>9WEk zF(YBQuK3pISh5~?Lz2ZxgJn|^Y^K(|RDlm;&~qC782Gd#LqFY`hws$xFS%r- za#>;4&|rT^)zKiywjs{Ej|0Srv>xRZ1_U+5j!H)=1H>4-^eV+ph0=zqU-aE$S3^U+r#LdtCzuxbI_D-Rf%BI{jnjr4;YAv3xbl zBc7eZHNy>~qsN=4*Q(J^H(TCS<(zW1{3G;d_h7NJUF)>ktVT;qo2s8$IHG!tXGP9{ z^lWar%DRwIx@vAVm&r6P-0p9HSQk5UyiT;Nm-(fP-tH=26Xf80DrTN4K#X)nI<#Nh z_Leo|)b4UiPDes|ZGGyUJ5j6MYAn)`*ERc63?!EN^)}#y|9(LZh2{8ROqglFw15-k)z^Vo6=-EN_9PNl{;b z-?2#zuN-=jWi-@Y(XnI2uCn2pUn+fl-7s!*(tnh{4;;bdjY-SE=gdhS>>KAO#*N2I zuf;es6hm)-uULcp8x_Vg!%8m$XJj#1(N79czq;KG<35OCU0mj>9)&Uul;0n()F`Fo zc+1lq26oe?&XNor=#RT>YU|vlAA=px{Obe1Zrs%MO|v3<^o>oeZnY8lF&~FiSr>M< zIg=3ZSxnTB)D1ZB#rEw5!!^9ZkOWq3w71$_xNcQRVhQr8Q9Gy}`pC7gKGo&3Q)j{7 z8S?6ZjOEaK5x3hcmlqa^bD`hp@#-cXCmpyd@e6_A1jT#K0`jamL=}bcnqKuy^Xms! zR#W_?uD9kuPOC}=q|oDgRgLupQqQhGHOJJroU!Z5>U^F*RN&cZof#vSqF+){H92wp zJn%Jn85=j;P!z-ge#z{5v{KrjXPPzjIi);nwOaFs=Ao4Iht$J2q|$U%V|m#7 zg?`hjoKgqI43cTUc`hz(63Q3S!DtEW1Mdut2HL1|r6orh3p~71s%X;9t944KZg5kt zFAwnMG-NS#EB6}Z^j_$FB44dJLGho9Mq<|rJ%@pEAwIm5+BtTuP6>pZWWu|kCj?{A zd*Bb}cINo`BAmx&?P)0r?H|i$(tu0SW0}>DSJs8TNSZ5n3_E1>X_-rXPJp)?(pI|2 zfYaD4y-RtL(SAvTeky?crv1g!;k)KS=se)RdEg78)HAT(Qi-ymY%jI5s$(T}p&O0u zO~W{rk4czED`&g+8(JHSf@XVb>!O_NVdnsrxvi)2mf0ws5;ohrPs5DT`nFG$))fD^ zw#G_~FQ*&=)QB8JaeQ;zY9|BY%KE9s<|Btd>u!1-=@R3+C0n zoyt5{lHHX~36g%P^isivpgKzz4)qVLKGWmoR#g|;4g9rU)(M>TIhWdBIwm*HDK)@* zaGS*IO6nT1gwb&JQaei1UVW^*2zuq!=t%jaP~BW>Yi+r8rkYDQ)2o)t^;2C!BiE&Sf&#)z8DbrmNsinfkG2qslKewLcv8e0V~K)Uz|Bs{5%@ zVAlzc%A9G%&>yM-=ikkPo~Vk+h?L)Ko?g|hvr#US-E4Motb=@YEzE0B$~WSBQQzIY zb{OyN-f!u?X`WU!Bpp(}rmoM}mOqAel_qG6`uKWzZrs`a5Y^;_{Amwl!LzgMLs#B3Xsu7|!Z$&fcUH3bM?L`Fsd^!J3t@C8=wIl--QA62CuXR+F;AqoF!R&l!SVY&)qX%GA!# zz8L0JeQI6kSZtoO1bFJPj^VtkIvNCib6f^}(IeRgJ+ns4W0{!xQs`H`o>>N*$zfT| zvT~=?Y#pnqFK|jt92@*HC1-L%jFQojXlQu$r>X>eq<+(*ceN4WT zw&yh3Q=bETnZ@*4V;sL?n7*Q*rKwh2IcEymkzQJmw=}9KXiMzpHf1vCC(T=KU4@VX zu)80v4Dfbs(CH>etqB@(eT$iruqNnSoV;fr_@~5@^jQ|H^{7k%UuiAy{{B| zo4YE1#VgePbiO_#!k~1y3A>ld2aitJU-*VS7|P_Al5nFOVGGv7e`PaQ{bg6>l!V1i zXL2xp3*!cKns1<&4t^Ih@S3`-{9Z^D{A(e#8rQacFdU z+_nvcb?E=@#n)-S>6)m%jB>Q$@u=L293^C=yfcW|HmEp;euKUy#R&o4eeF$(>y>N7 zb6dM(ZbEJoPJ@0Q{8oq>-VeTQmd6d$s5Rd-$7Hn*tU$lS#Einfp)J2`)6_=oH!qtq z=sj(vKprKr>+C9541VtQthqSjd3!bN!I8?jJ-T-2&sycFdu=z*hp2egZhfAeKbTMn==pW%KPAqag5Rrw4=KNa zhQ6eJnRMFEN|s4$n3ABtbG;8fm%xx70e|GPPYL8d^bAY7%E5QAcW<`16y^>nOGj!- zGBop?`$uY~guTp;IXO~Z7Zk&2@SY#7)G_TOycPBd9MxzK_@@DONs{=fB<2(^ceZyw z!@z%q>(|*>F9kwIRv2bqEj@0GP`n&tDBv8ET?m5TOu8Qaxz&)b%Yc(&hcGWBqx#bT zy>TH_gLdtudW(tu(Y4Siwf%XwbQ$Dm^-~6Tqcc>hj6JlG+DVMgZE36P*fGSe^LqJIS@j#wTIN+5UY(7j`r%FgK5yc!FSqNAQz(_0UGJ)j-CE&C3sE%;rr&+mvb<1w`m^7kfSvK-R7>F4 z^|Cvs6biT6<%KayXXxV%GLh@NV<+Ixb50*s?rSb9M}E%3`HmrLAg74X;bhWFMkm;L_t$28Aihe+Xgg1<`^cXo)Kj&X%_&1VwWVqqc$}IR(uH;-;*}(P@ zRppc@R=0-(J2ll+A1f~_bWY$tXSQ93{YuU-`SYM(kQbV%r~ax__axJR-@qo!A6p)+ z53CQp6Zo`jzo3GVK&nDz< ztleF?Hb1AotI}1Lc233{spQG#o_Gd(p|LzRC!xc&aGFXAj=0$}t!lM{>*<>cD0f?z+Y9Bm)ai}0((41E4essrVQ~QAj zyU3%{DS=2|q937u3K}%ens-+c%wMDfpE;*w=VN{j{wCJtnX1T}!93S%t*)8yJ1XG^ zm+2J8Bq6nB2h=V3fjd9Q#TqIyya zT!zU8;61z6lnjAf4jhSb4FjJ(ABXc&^DgWasWd;9+BrO>1SmNvRL<%freWSMThi@3 zTm$(E!#G&BCMXvETk4N!hl4XEHOZS-q2_f}Iw$CvVcEQ@q99jR(r3hREF+=wRMOQN z-Ia{V37p{uo^>wtTZM?TTvngMO14Js20vo2^lIv#Tm7>4R0{lJx^_2fiq9C>y_a(= zyr|%d8aTZ0t}8is9L)FCr&8Q**V!n8+ZTTD zcU416&vx8wNvlkk%bkz6T+WHzroem<{0;yrBfvvCwVMO=-{fs5hF{Gop?9EwESIIE zM)kFU|1geNWdY^|GIVuLZm<$ED!g1U|5Kkq{c&C8moxs9J8%|!7Zv@ZTwz_9lB(-j zJQw35_j^kY#|?g}|C`ASAK=5_4UXlLvg=5rs#&uW##)UEufg^5r=uhoTedDe3`wltQP z-FI-^*y7O@Ij2s3q6Ko|a8#;iS7VJe&rP4%l~y@$s_o?Qmf?m)iv;tVH9wtShl!b+ zm@kbmC@$@5-Yx6vZg)KaJ{U;w>j!_y)Qjg@(RrwD$j5!ne^7nlb7~{CBbDF7N*GK$1JH?_JE{Rlmvn+87)^fUa!Yl2)9Ir)D8 zJ|Hes-cmnRJ2P86&uBNy7Qfp}<;}abfnov&)4XQz1N1nx%jK~QX16Y^+zPvzj=gdsb3nu*JEuA#yOYs1oryT(A!viRu?+k zJ38IqJUL;KcdSlHQd0fJS&gN}d48|aI&C746=)iYTe0{?CEg(QFkl5@0vbyW1ft^XUp*!_*){_)d4df*>D@V}`CY{_^G zZfF2Ww0Pe#Zpv&Swy+3)Uo3FqFXBtc+5pgl<8(GWW;;?IWF>Rlf zmDLoIPWBQxk$1YIeKTgvm_nvt(*`ZkuO2}#7N4~iJyLjQf);isqg5Yqc!PuN&O->xIdNf*I?l8Bh} z|Nh1QM(uohKY;}y@mya_e9Q>jA}(+gEV|&1b*A z=grDnWtJDd1!xt6eM0EjLNbF?8)=ET{%>CNKk!G~jaod!*K|#PV#GIe+3^2l8OKk? z_M1)-&@c4!Kg!>K4*(zIAuv9veQPoQZC}+KtTV=%w<2Vzo~;pGaHacDF!_mT;u&{V zzc~D_H+CUcOqPGgW%}$EWC26X}?kdQ(mKyFmU_#ZLZ#GgY)H*y2{3hc-%;z!~r|K;g)Gb}c}R=wbT#@*5$doTU!U+yN4^|07r zi@lO2=OfH}I#zYH(|_FcYlM$FI?j@FlKzcgyN054H=OZpPkkE5&m9)cfZbDoy$vV64cqqaqZ{&-(e10{3#FUZ45Czgfm!gzM{s^}ix0PbK7 z?Bn3?`B=|05Hj-Gn7!t`eO4a6#K!SgpRGNx^rX+7<&TFT4|Ela7Glp5*vrAQ*3kP) zB3>Eq?@K~z&;|`Q=6D&bzoEzVO0ZXa!X@Ric)jE3@<;7YQ?U<=qk2E?Wh%c|Z|UK- z_~i3;uNzDIQrO;-RhITKo1#zAe&P@j*COW|@WG#OuDSQC_d|G1VMM5m*zs_H0 z5rF5Z&_qO*%=qrj7keIR2gAhN*whZoN9++co?WQFoEXPN>Mck1hVftDrNkmX8b50B z*kaV~h20pk!T5lVlUSupDzU7xxPboJye%gLx95`(^2PN`+P|6^=cb&pEF?0L|58~d$8a<(J+}@cpP4ZwqwmP93j!0?yEyyp8(h~e&(*H=CZsHdW-k&E~ zM65_4eZJUePEfl*q%y#AckupR7C?9OurU)k2&KfH) zEP;Huz0hhZIkNCktDa?#t9`i@Gu#@qfh_i)WZS(Sf6b3Jui>`w4Ard@dH>`oS&`><9X0%Fr7mu@KU`-;}jL9T&_ zC^qB94EA?y6U!C|<4I&(2XBlKk8J+MePaNC2Ta?6yPC5F2xi@-mR9} zq0M8FYkkDr*ALyzTkO_!Z8pW_=A7FD9Q0!g!ks0kc()lTj`uS_++bi1_Ja zwe+82)#Ko0X$b=kFK4|9&@+Y$eqyd8%3+dE-m`sg)*!}rWOVda@|^$D*C1t&WA96^ zqOK>{7m!R9Obw~U`#>u5TmNc5IOX1?V-^%Ytjo-ZB;ovp@mBjSk|2-N57&ip_<&lT z{341+B$_No{;v0fJ16h9aKihkyPRpp2Lg&`5I#%gxvhZnnrXg5^ZvivUO0m7KI70Q zbe}O}E@S%qC~FgiK49qH?i)jp&X_$DI`8-x4@Cc_Wu&SjYTNEUvRnEt2JYzK zr{qN5yT!gR9$%E!F|)qG#m#gy%Rb7zA12@1{FwD_=k)orJ(n}%x!IACE`o2%Ax&5| z>vkMpS9W#u`ed#BG4Er^BFLktjBONuX2d^zdShK0#;ay7Dct>o=rhg?5{&mfoDeDb zzmD?-#Do8(tyn-P$H$RDwv*MP2Nl5yTU|J^A!Yb|l$v zJD1h;%FXYCyUIbR*7W_enO>GT$s;y)+bc8el2$ED7;48}#HO;#PiU*_z8Cl5n2COX z+#IhfOO@vONgfIIALXnQf*V&;d9lk6`?Mt#@?No{#`im%nIN=sRBKj^YM~8ScD7Fk zf4{u6*nIwgyMuiklAW1pY_nv$duU#(iPjn(VsG@9&+@QulBpzPUoC8fY)@nf?_Rga6U^_P=XJCM03wF5glgZ1zpXug$CU*Ie!!><7*o8~O1>|&cBfVy!*l|p+wUC zs}Ak+CO!0}ZH0yHi}37MR(maO3CA+pC|>HW@Uqz$xBAPH_1(*8f81++?}KH;ku!gG zSVa-uUi23ITfX`u;22hWIW3M`H`ki;mhCdjH~9;9?t;95d+|95h@Z3BKVl)(cMrv1 zTyf_lSR|=JPj7EKd5C@AjoF)AG0?@0&dr?pPEF>oUz^wW= z^Ob@pgiHoEVdsW`405Mj9k_IWcEr&WnQg&owx=Y zy@pxY4S3L}%{>|>O*G zik>lyZ~AYi-wu(5wbGULB`uv{JN=l5Rn;bn>U(4hQyQ?(m8m#_X7?Zhcf5QaT;nct zWb@5*vfnxP$T<10%rxpn9io$cX4=swLAZKrKjJD(CD5-xD6B@lyzw&0=bY%{tplYK zv`4k@H$R?}nd#X+)k4MKX$*Z+=@2CdLn7})nrRDLu`rm!E4?vx)kzh*^Ih}8%(2@)&2l^S-Y$2$)(&+cevbM^1~zdc&@DXWZ4gEwGksrzdBItSIzHR zmH1Qm#f%@Y*%Uh`oRHSJ(|IOsi}AdB^0J>^W*$VgD9TX1e^OrzW}Y`>@0zr1zIV}~ zt;N7kxzT^ohE}d=E6VFmy_y!!eOR<1H@Wo7#uxADe?18Ott~m1Fxl5KgCzR~6@i~` z@2~6n73Nc5@l{j*X@ms!goV&zA@Sx@`NPD?;@j99z_kA81{Agg3oe#af(888bdvjS_%<8{u-v1ko_uE;aEK~~o#mJT= zk5~cy{)+w1z+Bjg@r7`_3UhyU`VEMrac8AwdROoRQF^P+OtMx!g(9m#>4d&L5B*C< zawgYVeS`9{UE`Zi?6xx%ynY@izy10r^BhIBkZl zui7v|&bwRKXGTbJO~=a&vptS*2kzI@vF#>~a>PD@FXwvgx4f0My>Aiw-W(q>nQ<@u z>@n=DH^!c4#oPzIfMc~iXVs9*l(6%pq95Beqf4CWx0++;V^rcz41Br^vcZMx-oO`eQf^ulAYus=6c)M zxR|duIXu9psHkKDkNxu4;$T-QBxR z-BG=2VT=9l&kX(4&AM;WVG_f)pT8D(BRw)YqIzbX5Q=$#Ml{>5DH;IUoUk&Y)AF9pI5cd4Jez)W*I@C(-W znrZs}o3w9E=#K(njYv+GFnF6riRcJ}0xL`s;y!9su6Kb2n%-9_9vJPspB}F~Ofh!G*$@eyLf!Vmu`XvEH%QeEjEOH6ooPdaId! z;4ZYy8MzZEOiK z9+Iqce=^f4t1LZgUc%lTFCy$q+oo4zA52qG_!ZdSNX(rW?>jMGgElH+%X>?lxXQO8oeT7YaM$G9Kc>euGIZQLoRo3jK%27 zV4wR9@HzfDDoYP>Jr$77kPldw%5lGF>f)hHF;~rz6Uf6=H|kctcyIT1Zb$ZKUiTpV zXtCbv@5hA2$=k;*s;il`!T;o~*=DT7$CU81?&r_HmuIH^fTxb1`pXtGeGdHZ)cIuA z1RZrmm=}8+`=%O8+5NEXLS~qA7RY90w1oxP0X(ima+HygfB=$^_(}=9yqNGo+ZR4pE^q82=;O?$5tm9Ej7`=zdjE3F%>f=#^2gtKYKC?U zVSo2&KKn1`<7Xq+t6IUG&}Wl;?u**em2z|aIpjVjL>_6TYe373mB#ka-^BI>qJv5o zrMX^g??><34!{Fjw&YFN4;Ho@`>Dk<8FRfqvt8J$|g$Twf3QGRvSh z)oa=I$6*gdGf$5M_TBxkd+C$R)^o_cl-?(>3TKBKJY%mb-pKe!1+KQEc*m_ z4(>qR2&-g&z;6v{3HJ5+lhfgQ@%hm6jW3m%+quO#G2Nkt^X$uZcK0#Z$BuKK<~LpN zd6cZb-0+J{9W(FkcYJDUj{*L^@7=Q#kjrYk)FRjKPZ^nCndzij@H124W~SHfjt|yl zhMWZ5rcLd;Y87r_&nZn8lv&OQEBdat>QR_4iSD+R3M(#mwZ8F7Li1=;aN?NZ+;x;W z$rRW>XOXK0)`_vu*+S#|X9%60q9va1i%-4wHv179KSJl5IoLp9Wnh$DS5A&%jmVc6 zVx;o|7Qk;o4sk3YE|g4cx|!hxIQKtgJ{9-*Ke_OYVw4a%A7}-ASIEX)6QIwQKwj81 zQvG+2O+3W6qwz0t?8AVe)RmHKyCVVT$)yH0tv`hkwgmgMtJOA^=J!9_@4ox~iaMjb z*RqwbW+PW%e|F@Ayg0)Bex`*>@&x@o{|Hx;6h+MEI|z?(u}cQ_JT;Yb!1H5;HWg<2 zzITF9qwk;aUp#RCp6@`0CiE-yKVl^Z+G{Gc*ms{uH%zn-v~B4*Y&A4q9>TuaqSHYi znaX6yJ58VKR5QH<_~-CHjZN_TF6is*ZL24)Un6p`B@Kp&`a%)Mwz#jm+kE~}$kTx* z*l}mP6ha=!Q+~FmCV70%cd@atFxMyh6bf5yo=Tvfjr{EB;b-rAr%&=_ncgTP&SPJ8 zU1@>*)%XckuxConRU41RPhCkqHKO5G$5wW$Hd1@_E*rG!2>)EhK*Ps+D0D6R;UnZ0 zoNLKR&>p(6G|0lj<8qiUcNFVqB;mqO3Px_TkHg?U(%D!j4H2)-7+5xK_Yppe1R9g|F7>#>=a3}$MU0ITVhw0owNTj za%o0ylebach?sq)aeX5E4NN}PYSXgBju>Y=!Mgv06510|#b3goJ;e`uY?eLDDxQ@& zi4P{H{?H#2S2HV&(gZ89F&eNj*nbw@$K3=JU-+7u@3G(<+JoDVx5kq`AFcl%KA-9Q z|HYpk1l}&%K1VNlrv(8E985Gh4bpZ+vV#NdKkWLY=O_9VT{^QRocd2RjT~5x7##S2 zd18$yRzq_^qf}{UJn$A^_Ie$8w2z*Nrikz2h!X#x+_fZr_}bwA@z@cY)mhH-nJWoxkSyvFfo zewqV*vZ&~u(BDhI-&n>yAup2sIJW(LKjVGuThHa4p}C7qrBIY$zqc)7o0;Ar^s`r; z9{a#dQ-04LdOB>T9qhm69@-W;Xr}$`k8oWY8r5c+WBWbd*LV3ueXZzgZsG9H@`1`T zUb|+ZeTk44ss*YE{Y&RNetcR#alJ{954|UCINB}R)ASxz9!L0pJWjYzSVJG1r#-mS zk7Hk-n)=7~*PZ6CxcCDb2>U-r@)k$@)wlF;_Sg;wdq&s381h8lbZ7m`phS*+jl*w{ zXTwycf7%&rEj?3Vrq^YD?cV$sWa?mz@eu5ZDoYQ{FC#C*!Gkkw-y{N(#>ca$M`ZyofTA zIgl!(@f5M8Zt&N05)S6R*ialSAWTK+{T-q$lk6}>D#G>sbH@4GcD`e^k>51P1NHx@ zQ2wPS`qB>~OuVv|&KEGuTu|;sr&~WT&L1$YX9+*6xKgsfe+v4OvF?}nZ(i&58kuEx z0ps5d>({dbXW{WJ$meK3eSSOI87MTKJ^DXS9+__XKlPb;AYlulS-{^+VLihyyfPBO z5PmIaHS}rKC#+$`mzj5KF~9I@QZd&w9(q{U#B=V^5&5g(?lP81K5}lP!Qf)Hrysh@ zxw>{ZBcXi`AJ~w!!~JWnq2({o|0S=k7z}J3@HnfEp3s-zUw(e^(}j=+)$VzaJ@qe0y?&M#evn^ogXAr9J;ydd$l_G|vkKMhYj@7G)ld%x#XK2XPe7=>3gRP_2 zTkU3gE$rXtl6?Ph+Q1Dz@9u?-MU3JZKVAx+_xVlElqO}9VbM@Pjdrg8+k6EKH{Hkkp>IcxXGDyu#9t^50V%Rwwg%$KmAFx@&cq zKM7l5S-&YethmL>5cNKt-(zAk>Makx_EGAZ1x~nsSk%{fm>l!?Jm-;?;#urxWl6{^ zTjuj&b`I)8gn&gp@(2ag5w(Gg*h9m zU-yZjn^kMKx6$~4*81&~-3?WDe)-%5{u(!ByAE^{;(r=DMMC>qIwnu{<+rb1VAkfm zsDpdwY0ZRvTmXJ*P)2*18ysv%u&bwwh0rjkx`ka|g6cXFvZP{B*v{vr`ykL2G zkIY<8^`}Pd*kGpT_|ynxIWaM83;m@}PvuLDcAm4TeuP~Dd~kVSz^FUvZx5i)qaq`J zmOny{y(Bhwf~NX7cBo^5pZ%ck`>@Z3>3S?n<$+}#6J@3y;IEYa;%nL;#=^0cb7w0Z zCXP=)d$4_$jkzBFE&HIvoS)kRTHV-CYOa6ieopj8K~E2`d>IRrk_0?ckdxDX{q9%a zQK5D**wX%Rk1NINTJ}22D#9Fnwm#37+^FBW;@X+MYmPqKxuY=(`BvLGp_P}0+i0H7 znC`X?`fgavM>vHK%q)>%zPO$3V|)7QQW7oghlY;b!+dhfjlew~rHDsx7>kNw|LL@= z=i3$^snUE&c0Qw-KOCyRz#7;muqPbr&h<+UopEmB#1Kdivb#SrdJ-M%V&6-XDgQB; zuXh(;q=P1j@@K1zd&;S5h3xc5pOuH0OmF?#URlvjB0YB^G|E$avD;Kiz^RS71z0U@FUq*5dm4*yRvs}*)m%g&poM_*RW;5+KA${ z$;>RFn44`FWIa3?CV~lEP|f${>KnKN{#lz3Fv$)UNytH=c57k|l$`z`0y2Q&6{R$X zNy!QX)<^0h@Vhhc00Lge%Ov4c^Pm3*f18_2tr_Ys5egv~Ae+UYUz@=&iH&Q1@E9e! zCy1AQhydV#SEl}7;`254;ZOnfM;VJ1{A;p~l&u&|7*W@+*MUFt8Aq$T?499AwdJ`$ zKQUR{-mSsGz1m%SniJ6fQpEx2D{5U(JS(&pKhc}*Tf9FZ_%S>zZ*g;CGgG1*NAF_7W{9!jI>sp zb6?cK$Nup-=-<9;>`nuXpOO|d72O-T+H2^&arB{k0iRPq__@2be0R-o>S}J!HN-b*m-uk({khK7G+tzcJ-}X0JJcI~v1Qu|X{@pBQL;bgWjF>v8$C9tSvjK)#F-8~$VkBTgB>5%SFX+BKPTtizhdbE+*aZ8lO}nma(+4N1ei=x=cSfm9Uh-9gfxQ$q zz<{HDjl4dc<%vvu`JIeoJ?ohUb4VdNWY3s0M%Mm!r41El- zMf{Y9!7p`Cy9x>t-8_*6O3uf6G`2)^|JU*FaVH5zs1Kg>c|13`T z7{?pq{)K?e)LsYs@@NCxk8ZhKLh%lxo(J>Z_RO`Os(K@>Jd5@_^z>XdOv3wuHHG8t zNb5pYi})GP*;0Bwju@^nU5^O9-5zvwhPHkX@?M>{+6S85IN!Ji_`mwAH%BoZqqQ^o z9HuN25O!;HY;m4u`Rz|TS!?*Cy2SA>=8r*JXmc+Y3I#;nq;z}ddGyP*hHu#i?qcpy zCnszA{%9%oi?`43`@7My? zCDE5S%48<~!1)i5Pv>`Ta~tWk+VtyRUP7L^du<2+8ZUu9wZazFTOu?4II2*z`qrP7 zm|Zi_a!RJe70H;0+z5zn>eeZlPMyrIWx4E8AEwSUuQIFh%n4?dDM!xHe* zpQXGOP5Yqn`J9lW>avYexdXjNOQ262X?(+FLVnTt%oxl!8Y_+ULffZ_i5cv8gwqnA z@A<)3)tYW9EKscA&(vr}C+73%{Eoi&_lu2{_((z?!^MkXNbG?=Kp6~kuwA;1(R>MS zGM&#%hJLnDHyyjz+j_{$|Jv{a=zESr!qYY`aaf};(Drc+5)DVPz$5D(Zs`b`?i1wc zP=AkJ?;_?CkoSPB*g0%$&>(Vv(f!uGGS;%J%PROgSRQ9&q)*ngi0xa3@yO;!N=Leu zEU+clT4?mFGM^zWrSR*LLu+@41XO$vE}2C+Ab_Fv3(~Y$+Zq)lvzg8>fWM#>X(`S& z#n&@ZN(r)(BOZIxdN1r)M@Y8gYzwZRk)I@#{_DagVaa@dcsX#rEbBUk@3HGZ(iXx` z1iw8;z7S8(lI#N|LeCw*8>z5q{kTJ_&_p5Hnh&tqbvSre|Ws`Nf>OP z1nnHQmA(&I+IDX2Q@0PFw;MiW`|!RIKa?50NzZxc`Md{jXg1`HTJ189$6++=aP(j* zkoUTYjbYzmKis(EYyOs)$Y}NIz3)ihtaTYc|KDFXys@)M)AyWxr0ql=$`4-r>z~91 z#*nH%WKclfcj}&FY-`zVV;f#r*l*`@JbrZ&KXhpWwQH$9v2H*PG`8>6?#Gp5OYnSz z{Bs*^D=j^# z6Y~eRng)I}(=`5&TiTIerq8>J?GL!m`YV3v=l^!co)U9Cjh|jUU^(s&=zB%&Bj5#- zI6{1Dt;o+_JIzX0B^kMQ2$*Iq)?Z1U>o4E^{BTYC&@lUX@Dctux7*v5=xbUi9{bCI z-^?>nEW317fheP!a+0Yy0-rQwM@F0JN#NJEw<;>k^yj#KM&!@+@YixVP5C|M`iHP* z+^`_QOj~(yZ0~J%oS@NuTSk)IVXjZKx3E`qOUwX(Erk3l%X|95T#x6o<*jaNGSf63 zMBerp#-pj!;Q3wKB@_PPhwcgXu6a6od>@OqcpmlA+;qIJYduPgclb~2d1^iW8tzek zFw(okXBO6ztj6J$#a^~8DRjLX{KI4HNOm&pcZXV$z{v0;Bgsps&UB>njo7EGMM*t+ z4jwdK5(_Zc4~!mkPls9H-+nwkDpBlg>F9!d7+()ce-+RQQ#Jj|TpR!~P=xru(~Dtj z=^ua?^!(%s#@;_k2u&QGO#P#X@8RsKUvq#6w-h@Q3M<+|l)n z{*3a#f#H!l zXnNU1L(C`p_}NEA-`Ho-t~h)@i@t z=x|T=F-vOyHq}3iq<0%)n=2oOVZEERs~+SHc7s0eXt0#alV?N!zCuLBAnervem|?H z^~SOm{Qdp+bH|@oh4oZN{X#x*mRuL39l?)j;VTM z(cTyJ^Xpxy{jMEus(8kQhw;#m+`N zm($6f)lbI{n2o&5%+oSDzu4E>ld9i^_^c+@X&0@gU6hRWJ2iHh4%cf>J%D_Pjy8nR zdRmfwh`mPBz8HTZ8bO;%@kI2ituZWn<`wf|#aKN2mrRsz9I8i}b~YtLdUBC}(%7F* z*_|(i_6b~`OG7KY7A=@x;oEH3u+c=ZtUvrW{hY?}*0U^~5Blw`xQTd=bHESQdfkNl zfWO1Gpn;hfKa`)P1rzq+jR)@j_N7FYGcwYm?7h}-&B4A##Bf|v4D1*vb_Ug0PYIoSE_{J}ui-z%9$=|QDmz!g}0lkmJ_43Qo>v|{tUVahKZ{00pfs#hL3 z4S0Zh2jOtT;;A%#m=>p1{d^hVnz;l*Uz~KT{}(fTy-G^gyG-c2TdfJg;oR&Aym%iu zL}MA<@6E^W3_HtjlnsrV=}FKxvXY_aM*2qIRhm=OT9$bH#v-3E=I%Xq3GDe;iDvPr z;id7A3z5R4Dw@6?AKaGo%zGSrbq#H-dYPG#- z>Pb@SPbSiq{Sw0(RskOx#h;(oHBtYYv0Mx;6qO(z*i27Cdk!D|XIC?Q9{j&!$mJ7w z^LA7D1pGTQKb6;TaQ{=|EmOdsdFQk`qria-qw{?Z1%5`n@RTjmZ|9y9)zw9Wv|UCR^=)VUD6ffC17A&b-?Hovnu@p?HmOo`1^lJ2kza4l$BI&j=%bLRt_h{0E-10s?5f!FKSgLaJfx zZ2beM1}^}e;WB9f!B81>r>i^F|LM_~d@GAsKlz%b*-U!M=sSL2A z_s90`v4p?rt3zuo5RU^I6N=&cdwRU=fCc<8zX-r_Z=~S^T*6(n3ZF1YMn=3~p=>^2 zjMstsyw+lCYrErg55+G^#>=JQpo7PEJ*sRve>cR)w-ujUbH@OUSDHC6lc}hi;FsXU zLQzI^_xNzfdakfSk=koxzPZ15WfI$kPE8GF`kmbzAFr;=34XlqzFk60dN{n227 z{oOs|1Hs`Hx0JvL@i0T;u>!rt_V-<#`;6XS)QAH+<^6US9@ z?&!UH6uKD81Mq(~7K~2N-u>MAhI146at`$2;nhz}&)3Grbb4FZ_wDJ-r>g<=teO=i z4$O?Yzh@#oAuaKNyCy+DPWM6{O!9b~_Y1@ex9WpxBK?*?Kb#%yjs6);|po|LGf+ZU5@DP{UsRZVvTt*@%1^^nE{g7dY2i z`S=orqI*A%&t+9K{ulEpncHA*U_k?Z3`TrLxZZ?Ujt$Lxbe`%9;=It*8kU{6$-*8N zzhR=a>_>HzJS5lvVtMU*@859HI$cYQ=Sn0o(N-V*ntzC+5d~oUo{WgP-)2>p$M3|Z zX`s_(UYd_scfD-*Th?DFASI4mP8p}?(}ho__)ez&zab~A-~T0PTXdhVDp>cbNWzPu_ua)PRJ&qUIhluZP z@_Np8t<#Qd^MpJ=PU}$;ku%%2S5@&tGE>pd!Z68fwr~G;58VYsBaym(uD1JFTM$)baWN9TQ2rcLf}dZr z$lh!#r2NfDh>rHMgmLgc2l)r>Z?ykkBMU63e87Gik+h!J&JQrv>q;o)k+dSPgC4K5 zE`V-#NKRg0NAUpCJY+-FX9WTgP`sgmJXrC0_7V<+2Hn479umf0YXIWldu*n7g~T#V zB}<4f?5nR@C_Sc|P8`B2e&|s}E{NwsqNFI{z#3^y3G}a^>#3_DKUi$PP{#tXg~btP z>yjaJf8X~`6k>Ev;92Y4mOkpY-cpi#n|6Yno2?Q3u4hiSQ}*RqY;`nuo&{%_eF*yofZ42D9{S?=kXKGV(TKZ^RE zf9uv?%;ZQd@anx^&a-BJxN-Z&?Y`R&#MONHp+kr00V8awwj^wpus$<4iOpx*f6$%; z{x9GU7L=C784B1_`WxMs;H|KkYIz*+c;v&u!(T2-yFXK@_&9p$RY~sk_YCi60(XInmcV~3vbMG;Yx&*iJCF8g==#ByFF*N=&V|vE z47~ipLom^Q$(XON`nc`o9*=xM4F7lY3voT{e-J@N{q&a*k?V)?<7H!Az+Trzt>;W4 zlwWy6F%s`;i#V*;<5PX{&lMVG{f;uvuANVZA9?$7??sPtUSWQo-mw_=T$D~t<9%KF zetB4&x=KKXG9qIf#zL{0&*?=S?q}ubWyWjT;mjSvchlk%KhrOJYN<62JI`Ed-Apgc^&I%yR43)JxHUH5&DYM5CJi+ zubT_|Z8>TC<&)gndb-{)I=!HV@(u@kwoSr#v_WE?OYA;KfLknmUY^a=skE<9w1v>; zF?pZgqC^jy^KGH<2)~-=e4ElJMt_ISP%sfMX@&Wgjxvoz`@@X`h)G~;MMt=8_vGkr zy>_HjyHd<8M0|3cW8WM4){AW7>4(5aIYZWt7UH)#y}?%->(D+;et*HvD@15P7Fp2t zV*CG?;ZHY#=Ln(xZaMJwLc3ilemnW@0YU+hG|bfy5;&`5ZGdMIm1nHN0QMyj3swXg z`8ifD1sB4D4u0pN-pudx$MG^B+S*hK_#fjBTT1YS{uDuguTww?{5A(HxKHJQ#(Ibd z!Z&9xf!7Xg(zy1=hVLqdy@IF)^rtKGWE?CB1pen2-JdBSWEOi z_wC$L-fv!KuW2ve=sf&|zfeG=)n9znccHJYudqEn5%}i#kKcr)Xrm0v#@;-CIjoxB zdGWuo_bzZv9c$m{NTq%+Db($)u;iY z2E=OJ!CO%g4MHsHw_DU|D^_bQwu-i*)+)HQUchSX#T!D__kUJ03*393^PTrQ?|aUB z&TkJt0#D|$W}bQOGizpReK}tV_2?|Q0)8pj=4-9_r0%cIH(VLllQi6#bq^obq}LUE zye28aQZ5ROPn@=>+vxWL9b*c{VZE1|J@>b7jOWGwY|(w{V?=? zRhybKgyv~eGhDCm-gUBt0=-?t3wxOVN0W-1sXiOGmGrN!dcX*Q+v{92D$w6fWfx&@ z+$vey?+r;2&P!W*W9rnmSI$|za`L87l2>c?)=nFIKELMB!h5gUL;bnnWwnN8=T|u4 z=tKT3gPOZsSy`pHsyy`6hrL!j27B_!)olOraaWb`tI$br{j%Ff^(XFSE`>V`eu$5s zH2KW1Y3o&IxJ?u<|8`kqF@>}g+@Dp%KUgWBoE@=<+ba1{rpAeGN~KbWs;{}P zh<;NG0->SFzjPrU8SvEX`M>?f@bfdpXC7Y8uhQ``$G5-LY{7bPir(7qq3P3-zDJ&# z*4H2YLuKTtT0X6*c|rkG5+>(F4w^*k+r)1dcj%zsUw9tes3QA4o_h@Z z-j&mh_8%$e=c#$Lfe0rG=nw-aLoHvOd$QWkix>DAUt+lb`h5e`awj#X!9M}uWlH{d zUO3_dp863{_%0a>2$ZLa8`aOf-Z-EX(&3FayGexo74_S)bd%obCqWSn<>xKX`2l)O z)7*^sC+v{=0eLge-N5pXvF{kzj|0lEUw;4g$T9XKu$R=z(!|};gxwawNE{9RtFH?4 zbkVa7?mqoaajLec`ab`)lR5&jm=glR|i+bu4+5PagkwDmqPqz*Lb=2@1*UufJ zJ=pVR#>fReKgdo=PII+HKZAz^2B!RvUSGWZ=2RMl@6Z~yqx)|jt` ze%LcS1oCK0_f-jtq6dHU)ZrMt1OEK{t%Fa;$3H*HyK67*NQ9cZhXaUdK9IuQ9v-Lp zgpcJ@T{rmhI5k%lC@*CASRC{Bp8A{x0)5hVUk&|IU3OMq^b_17jIVx(m00et5qr2fbUx|S4S{!D&+`4>FO(jNMt}91 z5B$=k)1Obby?FNeP{cF%{Xx4%7Jc%{wcyW>fP#|O^z&}(#rJacI{YWvKRYukb(-0! zuQJce&9I|=pU%sgo?79($@^63wG7C!GpmOLEogc5t7o%bRr^jLT@zW`Z zSszH6pZZ~WY#Z4xNvQ>$tDWIZT&_1xPlHW7pg6u{F>iD=R%8DV<~P%~;D#yt<~ZA0 zKLhrw={wtIPPc}}70EW6WYCwLcI6yeoBl?5TqGAa9sO|(e^A|xo7;WVobKV&UP4Z1 z;k>axZq*Q?H|$wUL_EE~eU;jlX$;#B$R{nyE;s^CrxKV|YlQ%^h^8FjDZ)5>5nxxWqDc^iC zr9Roe>(_sUjQ$GrmQwA*u?3kx#IJJIUe5{j@j>>%nJO*(S9h8Y`&K=0eiDPdlgmPf zAD$=L3kO5(F^dYz%3i|T=?hXDzoq@?@sk^x%lJLt#?11@czmfT)wQTSL{=<%2-7#*hzClapZS?u*+*H1?3EudJywNyKS+}gJZFV;<~V=g+^ah*6j`eRN_`seff zvIKI#$Im@A^lvPEP&y>Q86WQ&mg}7@Rbu{%M!r8K>I40&X5~yNo$s;iSjOvRPpwd9 zDPrYsV0=+g8Sm%na!0M~Zj%!H;K92S|GXq-aNlm6Bm@Te&$H+NPB@!LM8AnpK=oZT8_jnbdN-&kKvbS=1E_bwmq zx(0dFe4}a^cUTtd0}F)aC-O}Eqy>_j-ltPyzq2AgW#*wObuBjyl)#Zc99XYQD71b% zC+{WI3o<-USz0^0yJcLL{2>1{vX^&HzFaj#HK`jnNGj<2^Kpt*_#2+VVAVvoeK6S^i23iry(!ZMl0TzM(0?l+>p%0uUlj?HgWC-J z-Z^|n7cfX7w4Muj5C(bB4ZN<3GmIsBB}G@AkN0y%MM@+p(*FYr6YJlcB#j8)W52aE zNrP}eb*IH`M;aT9N!`3B;(gDha}&0E1>-oo zf;{!#vpY=mUo(=u1{Le-4-47H`1MN=pWZ!SADyq$=-SX~TCClqtf*)<68*REt;fEJ z*%EqgL*$+I_BQ)py*p|G=p6ySx@I}n1GnwyyjmVPq+69AZwkfv+HenNf|4}c%x;5d ze7fSu-TgQ(IRPbHB^IsM{y)AF8A4WRj{{Pow@<*_o2L__*GPZg!rn3qN1N4tIy z(cAg+Yfik|puWM~=e`aGe^l8ctM|UYBt0|zBb+dI!~7>CKo0z8rOiF{Xv*R1Ztt=h;d0QI8~P7?5`IvZ-Y79^%v>brrILh$ z1JPq2iCHrW@irMoK>O(@#wR3b)k4ySrpNGNT7bSZ~ zgL;7++)ezY^I9iO{TKb8=I$NY$LJoHt_o0f!4rv|V{!3naA}~qySba0?6vMaHm-Kc zcm=O;%C^Ej0sAL~yHqVWU-`o(*i-Vr;`-&1M)?<^F&Sf^FD57E+(lj%J>d%Eab&Sh zH=cW48rg5q$my7GoKDe(t|1Y{OasSol7K;w-h-&inC~^E+-ON$V8Fnb}w3-xud;2=V(YNhIb+g5LW}=zUonfBlXyczOih_tOY}|BFA= zf;l1d$B$mvl}S`8o2Jzl{0sh;b2iH_H%%L4%4GDv)5XM_nI@bD$#GkOzl(UETFP;+ zsm|!zNS@+6xB)g7EbKb|^Kz~1sp@S9^fl$ivs;h1{h8K}&hA>OckWwe3%Z_%^PCYs zY8XQPcMbp2j;`J~4=c}+lk(k1v%i^sHVyuCuJZa9ky~#rUhrwpyOI{#kMvu~kU&1C zE_-9{tACRZfA5mKuO~m+JBjscQI?@p_;t!M*iscRfI-aayBWMqyZ$xdZW?c zKkw$@v?5vEkeW%q1Yf~^z@)6RGYS8JTy_tlU&K>JV(pYHCSiZXYxRD`pamjFB!6K( ztlhN1+CPs>UJQRP`R!K^b2e@Z_X(Fv{z7$RWU(V@&XV|5!In|Tw~iPwSpQbUv2LG* zyh--|u|-$jKrD#+!mGK6>;qjwQl7hQFZ@x{zY_ZRZzgW6s%JuHNY+pS zdkX$=x2&#NePjEDllT1OldBsVr4BEhe2`#&WzAeTn`dca)*Aa8F`7#yiXr`7 z6^VP?ar#-BRNamA|CGry=d*JE{8uEjf6n@U@Eg@0!8pbf%Ohg(>F0j7n(!&SU#Q}? zOmj6IzmX04tsjtom3M8gi5&d3!&s)Oyg0df+4WgBn-)5Iz6pD7nEYT)O}7v|S07$D z&;)sMCi2R49y?0jKB72Yg!gY+7fg9MToI=lqlr8W{yBB#PTQ&==Q{K@hOff>Yf>?ONJqctGXhtIk$fUgM?e zF6|-}w0qRy_VyYE;KpThljd@o*JBVEYF z*)>X>M>k2`7Ve7Wv-r0y8m`(dmp>ibN$;yy>bHQuPn|k-dd`&Bi<0+FIfD4Ua>8Q8 zv#Rsl1Iazv6+80nfq}vO#)U3P`Rl^0C?($bDjQeu$&=G2_uVHy1AbOcJ0=h2qHvWe z{7<5X>U!NIS?miD5&gITf_v<3x)Taxmn=4Te_FT;oR`{5EyXvKf9muZ-IG|q>JB;> zjpyOM9(R02K%iPlTK?bZzZL_1g}HI_=VWY%`;R`&&C70UjP}$0N9+IpzPy@^(BrRp zo|aKQAX_-|6KFH8i%K8T+@1y%o@ z{*8fuW8nWP2GpF|T@>;}gP2QZ>MQyuIqqZF$A@1G*Qc`-pD}#0C=I8Ebbh_@+pzedQrKum})tu?A zx-z+5p^~B^cWr3D5c>P?^luFOe-{HXPuFpSipmfF&w@6i|4FCevU%Zus4h<0h2xB= zQ&XqCWtj2K`%~XYoB2+*r>gkpAM>Y9dn+q_nwTwwL8)CdD^W?j@Mr-N3P_Y{Zcdju zc#1py7r`H*^-*eP9v@u1K7MfwrwZ`QFn?&pd8^}o&k9(w-F)1GP?bhxf9 z;brbaHT?fAd^Gs2Sl+o4TmNe=QCi@x>y~rNH4|1WH~Rn|lh2o&Cuz_bZ#F0`sGkro z?tk-#5X8$-V*mbEuz;T~99m%sHM&>})ep$J;5Y^34Xx+$J09?A7uQPiz}%@SP{J*c z@KTp7v`EA0Qes)W7^^AXMpRqDS|fV9FzSNXpMMRnKgP;ae@eabglE%t#LwkjEB6Zd zUZqnN28>TPyO#U2yM_Vw(&3K&EXj*83kSP%J@Ykc&ZF}2-|+uy_z>uS)b1HMK@X}=Sdoj=j#fJG|HT; zi)Lz1%6W4#Ezw8&{m+8D>vG{c%BMun3;3#HpBus_=;h6ZV<$xz^r!Y$=Q7wI|6G_g zb{2!(?fsMf7wOlN|1m2sjQ6uSST%7cdcOY}|23RmwTP<>-}7}g;(^tHw0{rj0l&0r zjB!WB(M|60I{8}L*Quq2j&fX%v7EkBTC|}jtel#ccfQHJt)0JR%b80G+&6l7+4GQ}a_%v1_)|)on&gv-HAncl@Pcuz0EctY z`EfK~_0ZoDlH;;AxggJ`TTs>Ja4}4@Vkgm;y%>DZuuWS z3Flq1i|2Ka*FyhB^*+Yw zxdY%(J@F%!kL%^oJtJ_3V>IA|N$vO(_-p$hQJ)C(_Lm_K*!U}9g=?0%6iV?9k^fuEz4mPu*4*WLWUA5&V00=ZZLuH!c@tr}|#+x)FQS-Mv8 zvFxaV>L(^g6}lNZGK z66WXRR&oAme6AzepR)2azOI&3ar^@Qo*sL?V}4l1%Koums5h>d@qN*_BE`^cpHkswc5?vsUpXTewXPRb#3^+(If&Yu4U@(w`NzBFbx(19}(-B@-um-($C+bg7swJAig#QKedjH5^H@>4q zdLcL?WN|vn7oL~>=|Tome%F9C1s!2L&kcuj-@E-Y9poEqH}L0C8_CZEaeVIQLH~G? z1c>n(J-rKqY5Zm{O#p-G`Mpo}7U@NxpE)m_{D`|Q+o|BVaggsR6W`ad^3=Y;YWRS` zg#RHYhlu?05AbHUJ*SI2QM|L7yBFCOY}6fcI zIMK>U2%=KmOD-FndOUO|P11(2Dw)#v@fJJ*BCRzcN?lTk5XOVf2sk zE5U0Pr!RgqKR}!x>i_N!cYnz8{}C4E>*0taEMI^h^bz|*_I~}s`XP5he)BplJtqkP z{~KPL7$@;mp!^qfjkCG?;e2>w_&}v)lPWS$KHA0a<)eAYL%=;bU++Mi7~h8dNR3KM zKxkZm)oOw*@DeN_(#JS%Mw}0NzN^oJKCC~~-%sCM_ofGy(DSC=ne!^kC;a$o0=z^R z<#)|F`5uD_KYKUzEa(V#^$KK1MxWM<3M@m&O7i5 z?M7}O^s_(@lRB2SWQ1(*2ro_#+Vp=0_ba<9=F@mzxtBeJ%B!^}U8MhYx;qkj-{-iL zSGeU*Vis$U%GdnbAksheKRO8V=MMKbDns&@_9NCo-_!kDO3TPlYZ^*V( z)Srhh&lCDDv;g`(Ijc9Ua8LS|@P$Y6cc0vSk`*U;*ipXG`1N76e~A#X0oqsr&bq( zRlxl?&&Qy&M0JsjmT?;CJs-Z0V2@2PYK6vy57@*0>JSFg_PwmbBZeA>k5N^+A%2#(rEe&wR=dy7x#2nzY?>Ak=Gc4gMlEfq!g(r|OGBr){uYoU zy#I#tw=9Q-+6yYoHCzqXDCNBa4Mg9u0=>m@_q^QtWJ>#dL7r(tLNHn(X$b$&hkn@G ze2@FP*9o#G0t5Y?^no=n6*83gB|g60o~7}Qoz;#H8gK8DyG4CS@UMO9-WOWOhZ{cj zQ%CsSK99oL9bl5TN^9vO@;3X0cK?FtVN6V)Mp?`>Rrp4BJ(VmRXX3BF$5?q3jp03E$MmM2AHJCw`@SgkM5a`&GL0-Tm>8 zwiIxGK!QEw9xg_-H8L-$M!!t*2ag~>3Nl5)E-4>{)s=8 zxc~UqKPIr}k$fuk%2hL%;wcjznvURYf|J~roFFd|55W6dlpfaA+7yQJkctc+t~Xc5 zReI!UY_eD%_$>RA<3sLRURw+3m*Q8|JMs!~-o!$hQHpOxA1(An`$D34-jhJkzm#V` zaQDYT(&KgCKq@Smx7X# zp9%J7IA`Wo2>K2C2Oq-xuR#Db#NzK?cBx`9aObT1ZUX#cp&tA_;&ANt#z>}2dT)yG zdYZp#j2G~EM|WotUI&nz@V&24L*n3#k|59H1pXg;u1eIOM8888)V<5{sXvI{g#H7} z>W3dx<9YQuznzdz_h8Qz6seUyGMQQ+4}7E-O*ibk9vw}vgTKi)m6RJFTweBe%{_OC zv6j4uHKpbNH^lQnCa>fH{6#Z^(~Hs!p`DlQ7rX_~ONb>uM*mHE>)t*$pFjkm6Z}DL zd`z^S^^f{L@6)z!_*2F`$ijK8h%lP4XEzZxjC%+e&(l;GV#bH#f$Bc)+Ad z39LTx>k(7CzfbVk?DqJHpue>$Usj&@qoMcMA4C}V+7h~VQ%AU8CE!1}HVq%(3IV-F z$~(*1_$q8chI2=6{PY;?cixBp3(d}?MEwD=KH4AK`(bb5eflX!KG;974&{e&z8vP$ z;^QNgsvfSu4E%xh<6KqXvQ2J#tN%u>JX?*XG)bK~AE8M(^fR_|iu#o3`(*Evv)J?g z6@0EY;wc(XV=1@dYN{IXK)+(wLg@cz_}&FYQObiCHF|nG=$zNxPNd&;2{Z8&PeS|a z=iQvu3fdTShkJndkIJDgt`^3>mY=V*q=x!-T9+z^?a&z!3|fJb(hF>!ME!xf$%bf| zS2m(PI0*9IZm%`8IZLiDedywEY>{anu0GSBPr_*iIKs`>{&Q5$+`VBGUvfRhZyaAW zO{q^$z233C-NsKvdyeK)8e;Zh`9$w9ce*l|p8rq$4~Ag*^T9W!#ZW=lT^RH|UbHN{gK2LXd$6JWclDtcp9Gf2dqDC3+ zEk}OUt8c&E$ldh1TXUBu`ijlUm$~~dY0X`y%wg3T_3-S3kdc@=t{HihXgmX)5nIABF69S@QLn3*KY-n`5RM ziXDXx9F~c%&huC2X|;}Be~x7N#Q&YcNjNl z55slnVm#I#IWzu1e3<;dFS&b!AJ|`!3qA1=pY>kaPs4cyG(!)dMO+I}<;9ZSuMox?Y3x$Lb^RBDxCy zaH@P(YQ(0d=Je-Ko4ASmrAd<$M1H6FjoA{jg+@T>A7PSr#~M2LyWkI%q|}$waC+$I zQZ@L~Y&L70-gWcvycy#_?^)N)7k}?`!uNnu?K=+gU|L27`VZ^t4{qiW&@Zktsm^N7 z+eUXJ1tH!ypb|#}FWE}o}D>jPdAFE2OtaK9_o3;jmx<^XQw!}!l3pU|Sd)ZLzfBYz(!i1o86 zx+m|s&RyPaUTzLT>(cPEYkaulUbwY;A<2XIoC9V&K9>8PyCbZ})kj7)qAz{8=dwZ& z&reK=_sEA1@s$(YJylyQ^gH+;3XPskDH{6z_NRk|^Qj09Hp!Cje*SPj{ZAvd7^T|M z>pA!5@~^s_E}pj=zwGH9>=5~Vx-r=FT1I)gP#zy&XUmd92KI#gu;Z6!&yC%)2?E#i|ht-h2}RrdpSf-TQvCp zqQ2}l;oD2|_!Z)3o$k!wEIyxK-bV5n{`9hEMf;=HHrg=YJs2LIMB}kufB$W)M(?1n zykP0-sWGHS;!my6)`-wWgFfVea#7z0o33Rl!Az{ZC7|Do#VdvJ*Zi^ZfVZg(@$Q}_ zbsR6yc(4WM`y^KQX7MUM6P3Qs#`!e%4@$jTmI+NPhJFwBHJkb8>J$%NYUTSGqLk4& zHw61cfjRsd`Tw{ckSg3#>+Z8Z{`7gj&#!LCP5-^eUU-JVlt1H-n|-+7xL-l>7m40M z?tZ}La#--o37>ks=77ov1vT8xtEn0;4e<$X4cErCO5TP1Q{D-**Sp$o@d>yDLt(h2 z4p#^ETxd?w_;O03cSAveRN6_9R@jQCg}idxC$Hg|mxcXr^^ty_cmd|;^3c$N!ZKm- zkf}+A^9?PR`w(Q zoqyLLD;xj6)c<7v|6T@Z)WT;JO;h=WVm-@~X#Kv-v895+)}kPzW8I~L434&3j{7+0 zTs&Zbf4z;eiPnJfXa2%%_4#Ouvyt>g%0j2_9j{L%*aP(o$=B?uQ_47VY~0U>FexK#@KL;hQYGr@@yYpUok#F_^|&NRmJj2zb|jTVHB_f3m+t;t#Po&F4npY&}XLMVGK1DHZ3B;!%sbAiU;D3e!2uOG`7j*1!@3vB$tZO_}-l<168>QA)>pEwP3G2=Hh_ zbVVk~Hz(}<-eZw(cFdi*BNl>a1$Q+a^OcxV5Bu0H&%9!;&5KXWBzh4jW;*)kl{t?3 z*GOMki!R4y=e)I#{;z9WaFY0+<3dVkHDCf(>`A_3eTdQf|4=|5w8tM{>!NQp4G{|% zy$kTem)-shwwmC+C6ot0-`gaNr~vV}NDRnLy~KYCgT@Q8bZ5#*Cww|uUnySoqE*68 z+%}7Um%o;e=k0i+^IAXo#^tzQj^g`MuWYa2yi@}fPd+BZzh;k~!}Nc-Cw>u_HU9f? ztvEG@?Bn_G&ny+rkHr|5Kp))NH_2m9x}bkH$2~Vhh>sHrfDdm&m!*YT0l-IX$=9h4 zN3Sr*>y3o5Xz0f*GaK#GoD%#7d`x2BkUa$Uw=mf4e_%bc6w>IvT8Lj1M@EYBe7yZ? zcEi;6^L5jU#u%)Mybk;yXvnQ7EoA+%qQCJAZj~^2Np@LG&ANR73?_b#RVTUU8(#|h zAAnz$KgJklU!yKlGnnYHxJOVZgQ>os_B9cv`f~ZC(UgA+cN&Z<3JApk?O(9+=|wVV zurfNIL`ap;cR0SdlEH}Y7%SH85%nqXY5b&lgNx+{nVM`#9?H(Y!cRS% z{bkSJCeBTt!M)H|ZPF`g{cm#P&H0)yPkj324-Z=MihpJ7Vi1No; zbT#|k=d*sFko8>bgcCdNF3Q&6d{o;op)fw8Eolayip?Uuq+3QC^9tJU7u*W`HLr{+ zXXS%TKPP29G3)@rP6&Y@Q))z+V^0uh%S7K@nxtAX({DVqWT*C zy94}$1y_A<|K(y3_+P(&^#%<;dO1ndAn<4BI$usG%r`E2!Ot-_R^VcPX`pygWMn%C(w(_K zTMQAQ(sp{UM0_Nr<>JA!p855*MWC8ruJH)A!}{@V)Cq=#Pe3IMGDiqSy|`V^Ok_VDw2|t7+jv67c*#IErkY!MEomp zgqAV5*0#u4&|LdFgF}iA8~>us>&sv(_%nI-nH&bEBR)P`*W)yU%d-`RA&IAK45s$S zlwVs`$-BNq#Q1YaBc*Re7U=P~D~n&mr-2@D!0Z#Oe68(pcBSE`Mh1r<{>FJdE6S_s zhKeFbT&mv9Z4Iy=WXX<8p7Z-%yz}eq{20B9>bpyPjq!f++gXo4@QX{Pl_l%qlc4|V zTy-cSORlsfR)o0ey%DF7p}n0g8EJeCwQ!s#de*D|<}k|?Tqx#@_6NP6kdk;Gu@se-5F$(YWF7Uk5hgdC}0fijp1PfME*#(q#46juIa_< zL%tc(E3jVSN#sB9XYSNBeh*^>N^K16k#l7`_ptmRLtleeiOW1eH9YoD>c_agT8=ZF zpFD|p={_RNR@i6#!}^Q;ZMGdYw&dLYgw?mg-Z{JO^IZ%Mw#W@{k6H0PgQG2@4U)XA z^}OesXiHf{4kwk~;&_e^Bz=SP2xGI-Du(c@Aur0u`nB^a} zr5PPJmY!iS(Q{a8ST2LhVGsN~ZKj^VBwuACI!7>=@Db)4)&p=2d2CI#AY-S@bYFfr zzx>mMUL5ROmF7Tg0t6pLY+{A=GWa)2#3Pf#??K)o-WA^GME<6$laCk=OD`I2NKMyUS^j8akm>cf_VWY}8$&Efp`mM8KH=xW-7R>&I}!YH z?-w5PfZ$J=)~arv%kr(z|J4Bjkm2q`_`2V`pn<_hZ5HE=8<$0TqWl57He7=#foLoF zqj@p0@BFlo{-<;~&c~9cMSKY3g*p)A!K_K~@9k|^FUWgYa(=`YyF;w5#x|$3(do1p zWZH#o#=5#2BVs4OzTb@d5|yVWO_p!LY#`Bm3@+B`E=V89FZ+}}@f9L%Su+jeVt-Lz z=kBXqR?JUTWTc2sl24WL!UA`1$Q}qXMMY`k45s`FNBJ8JuC>{XKizQ%^xc3S2Z5g} z)Cy4^E&;zdD$8Rq79mv>T~2yq$H3=U{UyLhW!Pf3zNzA0MEy{8SZSiWd>e1HTMP?Y zTOsM(DG2<3;NCD9_)+qP|@eMtCCDNl@U&qq8U zG(eQkO(kOtEqasqyl6|1Vbs3BJjhNfAK;WKOw&}Q_ucm?kw#XSwAyX^`U~^n0)DZ=Wt?Yl$mSHOk!Ss!HBvnOq1CvJv{zEi?JiJz>7i6#34@?i(bzxv%~^Fzu+vK9p! zD;);~d=m`&-WX)vJP711BrEXIk~lZk4Us>X8q?6$auCvHgwyHd4`=)PA58r?MCB4p z27WB!Id&L0tUT3UQXVjh!GvF*`k)T>M4&;RZY*W_!KT`hfY1sBgUP-%jbPFi&qw@t z)N8NNd<%*6O)H;3=VuBjKTcut@qvEzB&vUayFFQiEz=G6itQsr82R3iQA@p zIa%7=3k_d z!d_JMWaW+aQHJYtlTsLLLH)!ETRDRj(D&GCC1o(-^MEE>w9kS}(~S~|M8iMdu%z+Z z#_tvx%X-G%zki>`M#)$;GTHe>R0*$4P0&#xGUhD6`{d1mF z0sR?jREYKI`6=n`@uPIWN#+7A@1fXWj4$2HnJ?om3?eA9rz-ch`&%?$x;qgytbMXC zhEBb{O%Q{Cv-R@5OwLhJ{@EwkO|-_+Iw5P6I{z3-ZfYac;w6UuCcm`V-2Gw<}Ebn+mGIio#Em?ND5LPEN{k*XOt)%XQ0QygYPB;DCLI z-3v5u81{a`={46q4{#oZ{oXV#H5Hdr)WF1tewS*e&NUut`00G>1=mg2;cRW_)Gvm- z%sNE#bME&WpEEekAWQBpQN6-ok~cfPcjKSjyvB4`na6*#LI1+-O*Lp z%O;t2-HU1~s~-$|_TtQMZVT9U^83EdJ~*`HmN z)WYCeo40A&rVk7Z-U@mix2RaO$I@Znht~EBVfl9WW0~nmuQQnBb!rEChQ2j<$)g0_ zOl624qSIq9vhu_)1&abl18$^v(&eZ(EyaO>m38MDtMN?+^nd7vhU+O#>^3?fo1n-!PvY#~(uKUsRK<9V0c!6N>zUgQh@6duW!4IOKF0|5b6pX6C$?jj9? zsee9>OwpdlcxQ!&KQGQN;jgl=Qsi%nZ-kUqq_g@I|EQ=a6Z-8CHd*?7`;dywg;+s1bRy6E>CjAwFU{8U1BBMedn^ijQih{#{f zCDRQH`Rpk|bp*tgRvn7F-C~?~6Z-uI{~6ZzHud!N?bMkXqXd5;u5+`uNPh~`bi>Nm z*8xB7MD+OfT&K|gHnie!=WE}YN;X3#1Q#?+VJnT%pD31s~zV!DITQ33tcEK zB+{1!*f7HK3I2K5v;7!M@yE59>%!dL7WFqjX7cAd#S4I&Y-K}zI&~85pJtofSo)4y zE!4*plYj2_+UPV-xRv+PdYdwyTDyvkkNCM^>e4dyJY$u@aU22kuZ_uyNtD-!Y)n5Yr8xj^u_!AP*d%sHD@90Sa z|Bq_-*T{b0WL~0uP5iG-m6Y)t-4%jOQ4yJ?<_Z@y8l^Gdx41mtFgHZ{;Bu0WuS1l_ zwUECfMqCm3XB_ye;AW`TM(%OEAvRoiKOvFU2M=&&PLKDU;9uhPM;*~tD}Nd52bV}d z2>j^c%Mm|DJe@0ChUc|9NAZ&MqAz=v?o0%KJ5e6{9mt<_L@q_!v?VDqYt9OrJ`LK^CmX?;~V7MSrXB*;OFTMEE{szGG!)Mw9 zv<>H7V*iPMA|fvMffuRC^2A>wI`$zOviSOO`#QfrYS^k_MMktPtTD)K9pNsd&8}YM|uQ^(BD9&*| zu7zURbmtJwzf=-Y#>$gCEG&Fov3zUMS8*4=snG&E0!fp-xKBG{VT~)Y74cyg`3H{&M$&vb;WPM#Sa3r~UTQXA z>|Sz>2(y0`6;>D%^C#{pur(Gw4A_6r;k-p!4-Bw$js6XLD=+dVc;EGx)SDzeKB;`R z)4;1*uFl)kb6M50YTnD`!|xz_LZeKYdo=F>RtsEs240*QHHT4pMQQma^Lfb3`V?LAT8t0}m znCwkSReQXe_@Vl_<)Z!|{?yNHP2q_P!2RhQ#*x+KDvkl-&Qzc9*+Yox&;9g6cQ zHfvyyH@YbNsgH@dIm z1it!eOU@1(E;No0vL`DGd=v~O`uim(v4p`Sf9C&s=mdkweyER}HJiafre<4d&rGIY zps(S-A26BR`8V-At@hdNPl)nj3;5rjSwQ?GB-)So>zqGY0U%;Y@twSwf%A?vQ|3*+ z(RDe=+a%%I_g_pm!r#>Sjk+Zm^w^&{D=+3w^@y8bEl4+KE6Jx5x7VB%8pj98Z;6VF z0ua`{8BF?cnWmolBP7yqoqgNy`-?6z+7s7JN@V%O|M`0y zeLBJ)fAWfF_ISj{6oysBi+^VMH2-CrzpY|0>?MO#y^fhHkPnCAHk>`{mZ!i6?DOQE zv08z?Zt^!>mxo%5Vp7Tfbtm!<=9)^@F_`e{NR1cmA+pa39ClIO61@+3VgxIW_2O@e zDmR5?urGXygABdApA+Q?tyvb|8BFW{^PYP1%@qkax{zZi|jT7;!qFIGOrzR0j+$A~Wk7|)1-ed*X$a-QWA{#u8Hi}ZnL*ZQ!q zuwEqoC=vZGU-EJd{ZGkQHQliE+mSQfkXt_uJJAQAyFBdI%J%sD6^b9s-#@H9|L3Hk z&wVBO2V|f2U$gE{KrcynX9eO@=M(3?C-kR5kgrE#SI+Le~1adMG=6vk#-mrbKZ`!dMzjctY5yuO{2Ui0a6|&a6K1e@Tdg=v$C*q;EF#XrG@jtdF$Z zHxaUhMEc8nPTP?V@T_~S;lFl(4ZgnJrS0%&Lt<%#XkRL3X|H(v!?a%& z+ig8bmaH)8^RxvFCi|`|J(0<0@b7fP{NU;YmQVcZWmOWrV7{GL-&2@EgM9Jkfji0J zk4C<{V?V<;*~`bbH_v7HzINHf`uTh$rr&i7U&4M$ay*z)O0|i_$RF8do0Q^d4)nJ< z&e!p+q$9j@=iiT0ad2#%Vpd=6o?qU2i}L9iWDiVEpF6|Z+9-%%@{c$A`Qdr))LgQ~ z=wBMJnZXoqDh{RiDXl-a^)p}nS$|JH*4wVzO2bM+0pB8?Cw}DQ|s)CKTzHSliO`_K@ zTiha6p5phna^{HiWV8nwt{5w2ET7`R_IjO1+L68(W%!`=C&G`A#vp#SE;AnV+Xx5b zCSQyAwqnSTV3tqoFO`-4z_&XQeXLp27SG`E;Qv+^tUphR0gPrQFc|T7*@WTrVjE}VVaV- zaG{Wi4~oC}X`)9nnAY1`9~<#RJB;{-k5b(ZPdAk2X~pqEUro$UwJ&A)WG^`qGl5SK zClcXv^{(LOSU&MN(RZdaDd~dn#Vzprp*VZ}5Ru-R zOZp}kS_^$xd77^$q8H9)a1-`>WHgl_->q@Ue%|q>M&qn=HqZckiu2VYBPkmv((Iw8RaT|1ySEi8>okK!I`e;IY|D_(K#q4=> zo?QM*n==`V-HRn09?jf2+A9&0y4QHn4nxs{@yLGuT?x-|nAR#tF#8mxS-6b9?r%e6nxl5>CTl%3nTr zW+B03KN0;F=H?6IbI zIJq*>8}CBDEpbW}+{ zMw?2@(yG`(7XSI2CwXbgnJ3aS`RlfTbq>})+K(ca%SHJ`@-a-c<`H~1ZF{wVm8bQ- zay{|8I|=qZ|GPL|qPLeel$5c2!slyT`}#lSU)o$#R1a&L)&m9oTfh1X(cYuyooi_p=8M`e+GWYXKHGdd zwm1E5)fSpd3gA=xuBrb(QD2b0_G!MarhH15lY&gD(g2aZD~f(j+A*}>_#g{E?>{Sw79z0e)B=UI*E3m!1z4Nq4A4{Rn&&^C1kQ65RI~Bm^s#T6cmY-gvFkNVj=pZi;f4Uv!x&I7J zjv82UuDMZo;630cH3sW%n^=E{-@0?*?em9tkIKtS!}8(ZC9lczyw8L$eTyx|Z*Ptt z&+uc7c@=UU4-HGH$_d=T}4+tM~TU;VNkUX(Dwj_Df75Hd|IF znc+)Wa*Vx#OBeMm?e`cad)7lwjo|-7F{|(U?nco%{D5BbBFy>&X5t4jI^kc3O8Zy2 z_ARj|+K!5vcTUYEA z=@aYUrruw@JVbCOpwKYhxM9VsUSjPL{%2<9oMSNgZ}vsp76HCR^5{@pO5WULmQV5U zvR++9{)IKaUY=2@X8D=0-_w&wp19LC@OwdJaU_Fb4;j{k_7m5WD89Nry=OYhhd*lQ zS!Nc;hyCEj)Nji~`tn(CH$J%|SnifqG#*1t=T4lGCf^DECph>yZ&9Ap`uf^cp~b9y zqQA;KhbSLF&!&ZE=KsL*$$qS;$gG&rGUGvwtEQFwo$Xtspa*v%c~q!vw;!o}MWI9N zAL09a?>Tc&+J%_l)LL6#tx{^%Ouak&!H=zf@cr#FZTDUMH+f@!??9XZL+1pgEh-k!Lp`g`nyrV^ar5E(guU&t?W7S>G-!})gCFWyi8jX&#J zjvIn$Kc+vH{Oc%hU*9DA+_A1KfaO=f{xWAeMEi^UQ=6k_Cd(K6`7nuCp7dQ= zSw(7lKG_ozvOk5?Y=g&yn<71+YboeIC8hC{2S)#}9w?0w>l6Jg^T};u`QxjCO~=9) ziTpzHx-h2jTjUE2PwRo)rMYFS{Yu!&{T!ct#Og!d#3?W8KNi>lUoNeJ|2&{AkmXZ6 zE^fxeH}><#KrIbeufH_(?vUUeF!qj<1gW*<$9$i40Q`1bATjWBMA%@Z8N2s?;`tgu z9_&9`*nz$fAC-)v__>gX|8sJ1mAfYq|8|y-@X%Kq$eqz5bsKB$!e& z?0+sqE%Gn-e!VqIuCYCXIhxm)!aH$;o;XSFjB+#&_wEcYr zZ|v{Q^D{4E`J_*;><+xbU}M$Q?CqvG+abF7M%NBklg$vbd*EPE{t-R)h)opfo8)hS zRPA}5+rIk1Q80kD2l_NtL@TWeA2tdzO8nj<%__>bt%&y*I_%R~dE);y{jB16h<|di zLB?5y1%L+L*<6DC>wU`dukeL!^*8VHq_0DFRQ9joQY1Ti@ZMo|jWTg^j%fc2@BfdT zGfU)uoPU7hLDU*vUC-gf@=ECc&XLuA{EiX#7Mfq;YH@#;`Pq_`7IX;FPvCouq3PqL zmF@ileQKh*Fqri9s#QACexvoiyo?NyAHlr_E;A&Tm8bDpN>}y+ZwNm`ujyI$>2X4; zwFQ~%_ICM7<=3rSlLT9{jpA$H6y?R7J^%UVp5ln_^-a!fo+sKDeUp9HSLoM>{Yl6A zQM~^oQQlI#&@VL5$5UUhZ~e}+CJ+pB8T5ELsbToQtDbzU_d&iFwA*)NKU94YD(-(E zd2>4Hjk7le?+*KmYa3H6s16o~Xp{U5&noXD@x zK1G$G@<2~@VgJOcGP|2!(S9)AH@kh?B#fWR(EhyNzpR^p&XM>6J`N>q+_Aeqs}FlM z**CKg_;mM({Grn|RZCeu>AO`u*4Y?L_S@@R=~@QUd^cQg-~UPWU+air!&rWkZHzHw zi?7Jvz{jk~DepbU@{LtTZ7Rtu6@zI#u<-H6FEE(={|cP$!r*4aUoKU)$J1$jQrf+J zf64eNZ`0~0i$(cM^8EgL`kWf9y-*uvL*KN}`5&yk{>b0&%HGRhqSvJKRkmC376>DR z|L1mWeS+m<{3T&wDp5Yt`p0;$G6l;g`ztJDWfFr4A0uU12N*2G$0{?&3GjY?1I0u7 zJDM{$en0G_B#(=w^BrD%y10IZv@=dD^Tz%jyk9kWGJF4+(_rWMn-^xi$@t$N7Qw?l zN3s$B)8jV_t94v)aXsD-GO0GIj(X8~-Y_3#o)$*q65?G2(vI)@DrTMkZrDB6KjKeE z=u;wn3HF|^xc>wCSz%bgY1FLzczdwvvmHL9e}zQ)I7^?U?+Ax%tMq5EwP?Iu8j`7X z!%ot7hCfd4TE+6mga3>%&7yq)*qFQU3z1%FKBM2yIM2$HKE2)QDiL51IPp`fY|;dx zb^hiru(wlV>bURku<}dLzYW{k{1{C9-f81KF8X2>_T{*zmEOL_TtDZLV0|IL0>Oza5XcS z{H4TouV*lr@MSM7+tvJhp~%!hv+wl>H!pQP`k$-bR@Mfo0v^}qWK8%6#gesoBcTUmXQ zr^RKtb_SC^G^;)K1ioy7e_s&dBkFG>^v}S@JBj@_BL2TZ^YKnrztE0&dF1K>23z4@ zT-Sw5{#t;n_JF}M)^!`7OC)eI(jsl3=~_P{Q(|JIePanlRFG*(%Q zrnJtQ#LClrlwOY!?G=J6JNt)NmI$wdibj7JcGA1bK+_H3du8izqskf?;Yk7 z@|x$v-92iB{SjoketDiCE9pCmUnn&p41a)YZLtOVFn4(xpS9?wB=P znD&=e<9<;Fk4O0nk^MR|1T{k+?g~;`g?xNTe`~7|Y|HoGopZPrJIcJ{pd@j%D_82P+62F?i zW2wHuu&F_Ko<9CI_V*!Mx-LQAxC432^SdU`&Cw=BA%IW3c#grqW>ggl(e3S9+AGc!UXwG5qHGTCiyAcgr9>K zM*7W)tXY!1hV+&6=yXZGZ!v5d5Lef9mVV^82GPs*)am8eHVu>LnoH|W=YP=AcUbiay{a*3bN{;^W+E3KC#*#A0` zs-^d-2kfe_=0iCr=nV+Zu;Ir}39d55fbkS+#}alwT5o4GnnnhnD!i7Ge=wz-!IjWw zhU6yMF_`XGmYI2g!8c*Q4BJ;O?T=_Zz80Dq%J8SK9#a#e7c-dFqoKxE;zy)^x{s{# zVtDYo7zdS$iov8G$>bC-jrnfis|ns#Jd(5GPnFvFWYc`m3o$3{aL)Ox_K!)Q{5E@v zeX=Yd7xLPdZ$^DlXN1)_3Gsdr;nwqmJV5xXZ~%}*|Ii11nR~7t)Q`vj(LYG_wxaqTjd3E%W(R$Ul#t?BX;?S|4Oj zY3TN^Vb4$Y(tPiE()vq8JV#V?R6N7$Yc?dr>rQGIOzW+)>ZUY*dhE{zEBqW8p7?ov z*>943?0&+sHajb6Ue`+&VEy|cOb1d@&J;Dz*&zb?#DD%lH8LS7slTu#52-WT!*cZapD zixqx9WfXph@w+l2kqb-QmEx73OX!^P*%1N22l31I)mHly{WUoqj@0|qbEll-rg_!B*Eaba<>giknCP@Wws(HF_P>|s`UAnZ?!wbJ~O z{m(9cS6Tn{X0qSAmyD3;MPGw@Caze2O}q&+iv6CGUXzMqj(iWI(QiT@-2Jp{I`KcT zAzn-W%m>emWH7Ba_abXN-l;;zRjO=x62lWe%{`PU!5xKH!+ZyON%}d-`$3WEvsr)e zhjiS(g~9ZDl5TZeW-#f83Xd#FKP37$cqt4FPwThmz&%NSqxn-gfggmSc*RKmk8@E^ z_w2s-5b95Bg!JD`nY@_y=i~Ti`N*omD=JN+<}TFu0HR+yUwfGQ>Ig>y(8%8pcfVGI z@JaPQ&b6$ug;S2e{>R~Z6^H0R#Q$qFnS@7$5WwqEk;YE88o4g@xW9VGxFE-B1=c^d z-zL6+^rQCLq_uzJ+fl#!Ch~uS5D(1q3&r)zWd<){zgLfXLdLDpA6Q}RcZ?S$c}(UkLosGX=mfj$NgL9T-kx_2qJLA z=Ut61nK3CAcz6)MhCGiCF+MX~TEDbESsdGvM%SOgd^)C=PtNYB--K9Ox`g(NVN(ol z()uO)l6KAHo>kR0^qE`mC?o;vPw@ir9ajEBzc0|se=oz+eze5+n32K6A4>{{4rMUu0~e0O zO=2*~+xCW?K@1k+6^iyq{3RFmuPV3u0=yVX|M zJ3TGrxQ4+rf0#1u%H!I_;jIN)*J&r zlh1%T$nr`~`O}(XnOrY^TNjL8H<1tRD)X@CPg(>UN)q-fmy*?L8R*wuV=vT`h;$zs zVF&qb4;rTW5|Jo4Nc1c=m3)LuNos)5NdFi7e>U*Oc$_~#^)dsv$wIv{_vTJ|U<&XE z2LCMH=2ap}Gu}-6Ozj=!Yy14_?5bf__*7w1p06a2>G!S}R#C+8J~{sk3t4aX2!qL= z6&IbH%wUrLwN-x7__Sa2et%as!;?Heb;24CvkCs@yK66e$MA_{Pinx#^*8LF(qDY@ z_?rx-@zsVJR|eDgRSNei23O&IZXfPGhQYM{dcq?l{!RNEwTAP7=mq(czOX(dYGv27 z{{DT$FMhjMwwPT{>*Lk|-DL(7eWrxpO@}QL^m+&MiTM5t)_Nbr-&$I3Nb^JYk3cr)*A6J)KIU?J3Z^Am&N@!zNGuzeMnn2u?hC1=!!xHQ~qIv zF<;t`9w+@@J5Tz(^n3hjCv3I$U#ibuktFaRx+mR#eb4Qq43GVP!o$Al(t3BId<7?k zNN@PX6i;yc$v^yw^%tJUV9aAM-fpSdCrQCz;^@dmwwd z@ZU|yLwh%w)Su|@&@(=l*!2^kuP23;e8%7o&{O%E^%6gz{f9b#BmMq6COoyGXm{9_ z4~9);{b@fw`{JQv3?}~escFA7pA`SFHhrDI->J`h!_}}rKR3sIdO^KD#3v-aDm@Rp zd#lE&Whyovt>>aXsk{QN_fEes=Jrp7$8@3I;(WuNibRoL!ed4Ind6Rs`o$T#z844A zToCm2@K_XE>~E0w)4!c=W+2h~rrb@x-~SW6lm0K9-@C}YbM&Lb?EVV+SN#AqAp5~hv)lMhrmn5Onr;@ZpUma27x--tek8r0A5I^c!F~_X`;vU$ zc?_oc``MT;tQY#u7xlf$JcGsc02ui=p#C(8U(kB;_pg@r540X~asCY(?yaORFS}(WgK2%vym0;kgDHOJ zRVo|v!D;H$F-OHnoYOq8!=xY z+|qTA{-&?=v|jrBq~A;Q|HzoAi-e~}>tS^?%kQCjEl#VNdZ#yu*#3OT%0lt?Ai{*` zeae}k((@DCccvHnS9(6tKjoU|KapKe{IR9=ix(vr`mj-#RL=NIRY7~0zjyvth9~}M zG^XV`P{fPkJf<^2cJr1eq~z^-|4ez7<`lo~`rGFkKGWwR`>S!ctno1@g{UND59^&A`u;lT1DqXb zo%RC}+Vj$RceC*Qs@C5m{a>>G;d?ZJ{+r)fG&=M>&1CLL`|acpc)GRIdHtz3r^DjTHk>5!H;VDZn6HfKTk^A-Ob>t zQm>d#KYiAh!NiXuBO|5#CGi8Ju|%?O5r0v;)y`o3X}rubo%H^wevC`5PLdCm1+x>f z%PWgnf8y6`+Tvn~{_*b)#tWM;VY?uoX^j)VF&-{D!uo?=7FHG;#r+0kKH2A7yc90< zx~V_O|5k%x3hNL1ZhB=~w=^HL{w$&CDGU#KU1k5p=hAvi%mMxGXl*6xrbhI6O>vaj z+lBnVU+*TTi&!4}hune)V^lbUiT>9-Y+S=&ivQDKVKA8Jd868}jlpCuSdo~NB+@h0 z7o+{hAZLcB>m413O23Erx6#E%A+R5GBKzkv`vZKe{ZEy;WXkLW*u(umy>>!jw5w90u>_`qDs1=a-p!qbOw$3Nn zL+d+ltrsRAc;ZjGq@TO+C+?2tTNNKNbzPsI$S-Mp#8+o2^4NIvJ~o{CgjwXxlt12a zS#z4Q(F@(b^l`2n+W%hE zHyuRwyv9tO=98pOb>phc;Eqz2{MOZf+}!V57Lx3h{0FLklyK_pw}(2s#1$(e>Z!i< znnlt6M9h?@P5Xb;N0;pXl<&9d%$ntdrzX+AN&vYmXaS>}N4qz?d?@BQ-#2l-P?qxY z$v5UXD7>79$u-VS2}|U2|NPvG`<%}zRC1-0FY*JIELpZX6SNHKNu^w8Oh&Gjp)DA(eX(Ezz5m)y|-|KZn+f;6O+IS6FJ;#S(@m`oEHWIuo@2HNV$l+Jkyc$;r{(Wc}+wC&Xg~Dg65R z2Vk<#dW~(lz^=!BUVr08ViJRCd{>1*;?Me;oPU^~Za%-k*RA)POz zaPt!EL*^cH5ByWBrq-5>;RoRaIQu3Cq9;uqDU3=!eM(2akKceUG`->&|C;Q*I)^jZ)wcF6Y6086X z)<2y0v1s)ZcAs%|+$$W_2h9kLiP1SF!@Z!BU60quSG@Vs%hZ<|jX$@w^&J(D^B^8_ z53BIJ_&E~4r}_Ey#jl=X^Fj2VnhJHfzxm?)f>+*u_&tNEKE3;+$hYn9LGn}^>9twp z?&ALP7vKCttUuA`-SkZ8vtsKgy%z6UMft#Dqx{h8-<9oQFs(0{qDsqPiZ|cZVa<0W zd|~1H5`EKp9yv-miuEUYEYfE9Gnm#xYFBqOqc74Y;vShahT*-_g?w9#hhhWx66-f+ zQl0fYKpH=EP-6h=Px@m~-L=gEj0-4!{&HH_H%$HzzxueU#*Ou-c$3QI%cI$R(SHB2 z#~yR1@H(0^x?g?WW|4mI1?d|_d5*r~e3Je@E=FN=@nz#7-f^MP=sk(SfWkvBYw10pIMohXggnRGxtj4kv?_d zJJVR;Nj>g^z8iBX*;;>I;CGy64&_2oqxc4G8PSt?<%> zuH@uY2}b;8o>!eDFR0#=MyEL`Y$te}=Yo1?A9o*0WBrMr+E<}|Qh&>ZJxm*6wa@Ae z6)Au2y?c`3X+1|guYE~)KW4r`kar+y?tA5FWZWDl`3-Wx`@;Nv-*&=f$GDJ8yMW?2 zyEvR~5NGGI1Lt|f#-a+e*p?8zJ&b$>hc3wB;(Lnp{W){yoZvp?Qn?~7fX>J9GSq1u z`uhv^f5dOI>q*{w!Ms2{^ENdMQu;FN_&Udn}oNq8qX z$X#66^`uWMAGsz))g5XHn9 z9M(9Ezw&~K#uFOB8|tS>>zVZbe>S#A>z$t0z3MS{QQHvdwZc&Ci}fsURHD!Dmo(7- z$(k$b2NT_PiTe@2A>c1Z>$lGoZiWx??{YFvVZLchTL1pu2c+jG`L0I2FV>&-2i_Gz zzJl--!4=-#5`B_=&#>ZsU))}3biI4YHZ{AR>~C4wE+qe{nc=@pSlkj7D}rS2@Jn}*ufO>z>rec;dw9bP2Gjhdr`zODz&~uCnJMyH zbeeB?E}=U*R^kuD&o6YET-f!{{}STjcMJ9!`c7{+Rrn|@?F{b&eqb2pF3>Z5C(lt7 zJR1C{k+|c|-!d9E)s6GTc?uu6{OEk&v0c&V3woV8Z2LBIH^1NH#LI7%W+_$sR>n6t z^Kz5Z7FkCr$0h2d^G^kRUA^Z@2{)d54g~q8in|uCD$`}6qF)*EcXULrRIh@xe@C3L zCeUxBRsa7z{S*BP=YPG444_@yWRmwt|7j-XV#h5Ju7|n#Bf1~k& zbK<>~bpCV?hx`K#@&~Q;Jtx4vq4x1*@6$r(gH^axit>o&59bHIg@Q6{{^&dpVvh`- zxZ`+q&&0N)3?}(;TjRQd!DN3K+u$$CW2hCh-|9_UC+YjwPWANuP_Zj=OLJtdRR%cf8%6X=Btz@*nHmvIU7X;5 zsIrM?CH$-1X?K7!kxqU*%sahgWMx@Dxe4++?`D@k&vZT6Q)+4(X42o(ToA8!VCDfE zec3zxPWr1iKyQ|>`378nJZxBC-Egrt@rPx4S`PHElt4kxJ86 zlj6Ved%V-*%$E~ce?8zCtqdZ1eJ&ww9qL8*H_{LK`ec&+Ncd(z^&SD+(hJU4GYO*sKOy_g0*3OyrMnD|SEAr=7ARf01_Da`Ar%d}~ zE*a;$%7<>-?rt7rdIaay#UYxW-0zJiah7(c{e3$Z&{K5S!i9|9Jn%=`ulr4)brt^9 z(D`AA*JspQ5?|5znwUOG{*(TnVsri<(J#(R%RyHP_u+QI zUbyHl+6zkIbZPD%H`c1S*5+n+L=vGV)enO`(WZVF=?}A1@+{7x;#GawMWp{by17a8 zBhRTeiSvotYUtcSb6FLz+z21BI2J27!=yUr3X}i`yH;6W=5&iF9JNKMG@A#Yadw1yf4v_ZGazlm?y+%gHt^pem8l9IN zwt7)>KPbfioH@5fivJ_|U;o!v&M`dkgCQsP9-KTmhnxHdPT23aE_}zzKS1vqhq5x( zpPr``2P`jf4ppeObDeYaD(>bfKiD&9b@67ZACQ>nqHgNrW4lf5JD&@Z=d5_F0{ii9 zBrx^xRfzAdw#g5p{krz$WwXTD;CZTN5>q@KY!NW zCudJca)46O=e-kLc7!D5OZ;ShO+`vZdiOfkpYlnxzwNp$!O#yZ-}FfS3Znl}L#K|h z;-QhLa6DjtBYRQT*{*{Orv8^^lATCcMv^B~?pN3!Pit8ytRI>oTF=o@i4r~0 z`qjF3PGQ%RKH9QYj2EQuNS`jMy0VMmCu0598IH_kFywc_{Is;M#M27Q@SiPBFx6Sd zo1K7o^GY@2kLF{z|F)tciGFDOq{@h8Y`l)r4GDg?;&sSpY(0AGsKpEU0W%(M4`g`a zf5{!kZS<+7dI!!y^e5nzk^gksA9^$J)6FJJo1lMqhkQr&Vfy*PeB3qJ*0+E>b(Bf; zPWs)hg9kI1^vkk+ zWh1OGb^uz<96GOfE7#Od|52fgewHBqc6#86)?LF^329x|%%5#;RcGheqzJxcPbF^P1(JRsCw;B5vu>N$tM^3J^|0lS; zAPHKB*r@;LwShk{Sll1f|CPaF{2~GZHd_Uzrm(Bf&yTUF1V!evPa;vM~dTlGc zVwW8bA-7JcxVsr=c_I=YoQHlzHc?Ito+ezLQ((x10|0+gI}d%mrle%K#Ba%-vLAK% z2}%7EAy3n~zPU^=wcwoNVZIX|k>)=a^>6;NZL}n>PZt_e%oEHKJ<)kCGMP|cUAUg) zLu=%xQaljBJ-0Tk6_EHK{d2f`9RsnRP9sCwYM&r_jSCN#^kuRi{G1Ug=_{1KALU+7 z^dzizvR5r$9M{4Ir1ex;St0SK+?<>Q7o&GR!&5wYVro|ugJG{*=uxgxF_`w7%Qf+A z{`k|dmz+#GCfTDAuN&jo6e!U*_|w9wa-%eVxxhu1~T6p_E4?67!UY0lxEtWkJwgeG=GPJ(>=rwoD!4{R{l@^ zyQNDzUS{(RX{wJtc;hUCPa&Vge`~EopeM1OhRrOQDd4fBPNQCv+Ha!tevuuXQ?eoz z^l7#n0-p3$PVOgX<57Ha`;GWyEBsu7$>#hmea))|)hr(mh9`UC`2!#R#9-PF9=w=Z z%3!K5ak38!zkh)X^*D;3>SlPlf3^K41%q9n|L5_WB>6@9L|Uq}ABKM#^)!7eU0HwV zR|ZYK-AD!}qQ2lJm+UIhT0rqLi2teU+{y5;=auftbjV^bjsKa>*kOgQMjOj-U@@AA z;llnlK4$ZrWvg2(XHft0HpLSKJq)zkABi3&8spQ%>rEE=JzF-bi608|L-Id8`Gs`C zi}?xJA%h27&r_%S8;wa4Kce`fj5%sSP+(!C~kTOO0OHGPO z?1O#vGyF&P7kgK0J!bO%or}B>$;K1(n`K7QKZK??Y|z`)%53!i=Mr=}YkVg0_l50o z$NRfmC?6p6;NsYR82n*r!X!U8N&Y~8g)DV|rmukXw&zYlACFxe8F_Z_W&HbPF!}rE zp@Gn7$Bx!M0{i_;S{}b;^kyKwH%!GHpZ!Pfbpfm5f(v8{;m`gL4|xy-KKq8~J8*8= zhYTk995{E~D-0I!uk1$+y#5CKnz^f__#eUx_P>5K(X)^b_@7|tE2Al2sJ{_EIJdub zKWP63H{T!+#TmaEu@We-yv)3h3MJnTffa>}yOQEs*&&e@S_V zM`T1_Q_s%pZ|LO6AK>IZrGl{>HZziMj^oQE`lR>w%)xgn+59Ksd^fL0TqS)U>tUf< z?JeQwgWlaMMoaoKjb~Jr;Q4q9J)sf$f${PcX}#%d4Ep4-jA%A~$d2a{CN$JZ{6$}* zH@vsBVhQ2t`Jq3qsxJ(WVEsuR9PDZv&R~j9Z1(RxDpCXO{}V#iwH)jwhCq$vpPRxO z?@#M>bL5Fu5i9P`!=iiFF?d1_JbvoO9%Jx)!^V`}hnBkxo`CblYfim&jbOTmdI#e5 z>-tOtQ$xO4`sbsZXF-1!=#%IvDJe8wcw4xF;A>UYSvYT?jW?MsRPXcsDlcEEFF;Bd zo!3<~Wx5xxZo?Od$GI9EAD^$`kI79(wo-qfh|8 zzq?=$^!M|df#+%9xAJf34GuwEW1~jaPamIS{~Z-kS9EXiuY>Oo?tCN<^)zP9o;{n< z5Bc-7quo34u9}(~$`FsDk#m`w6pkFJ{HWR)1p6K0OIUx(*I)N*pyUrE`u%?Y&Cv`` z^t)8OWTY*eNcp|2p?}NVR!N@!PxLR?3rD9&;}g9HE_fjACrE#gt6Y>^6&Ha=Q~onr z#W@;^n!88$WmqzLCaDb`zVXM71$}`DBbxWgnH{7kqw}Bl;)vEm{L&EG?}tV&>IKpq z*F*J-4j3ydgvExhV6W5v_G=S7Jz^t&o$B#-A-@^CEFoXvBH6#HAa9?o7wRz!1VHjJ z+vr65JG47Dkw30)+?{I@S2NF7q?{_0yEqa*5E|__;ufv_mBFVApGz3~?9rJFCi+*- zqVryb0fqfR%Xblr8J_ItV`jFId=eV%j}P_Tqw_|DM)d?Pb{(81LIVAVx%oe$W^hL- z&O6|n>KRPezx?;zGpw*Y=MA42k-qRH=}*RGEAqsrA^quU*p~6(5*HIY{ZoG7xS;7X`|Drh zrZoQ9&{)Jj(sY{ydjC+dDX@QhA$~70Dk{}%G4(c~hu5lvkihNMdS$PI-=#0Y(H#Aa z;)yCsMoRM`_y?F$aAi}>;Qm`|&AL6a04&0ogW z42eJQNkDyB-wFw){eit5C+S1PzcmN|XnG(pH0)omM!)apFX>xCeVJ4XtyZCRfWKc2 zygAVlhlm|l-j(d%Ab$-bw=B^2swYQ}?sR?V`s*WS2m4UGUc#(-3{Ui$Q|!6qZ0+5d zji!A!ss2{h=Q3Zy%fJJOKZLJ7AI|y{z3RuzZLx(@M(aOdut={~JdM9=*c9rYlHk1<6z!JZd1)X`xB$m>cOFV62B*T5uvr#lhxN;4QuJOw6Ojp zKW2;>IYWYBe@a$f9zgFOgvVYKrL!2n5;&z=dY*~k9}Nw)UhI0>Ul^zLZDp_=`ipj? zRR2t%e}gfb(TBhE$Hww=$q=9-9AJ5 z8*wy7}fzo^uzf>LGEa_)N?{Ggz`WV?K5XU+Tw-#))qz^2PijG6_tc@eopxn<719^Z9YTKLXF}ho_yhJ1ezsnAeL;0FUMlVEPmjcgyxNop# zGj}Z@(g*hcd7G#75uW1nh`zsXJ|C`pX`jPorz`fTza?*e^UXKe^(1e{{FLzvgGt^h zbF-!WKhdLid=7OJ8o?Xm_vHOI*xS?E|M&F&dwKuw=-s2(8b2i9i}OZu=eYpx0C%DS zmW3(JUS4k;d46xZTgNwFM{6-Xnc_Uw$5P9&put~7*qLfNhwgZ z{NS(S<{QTOW}i$%EC@*d10~Q>CVQ!xsJ${yS9|6y!SCtyUmbi)g=(To|jR2PT^W)fe${ z&DzYy2S3qQtcW*CF!o=5uJ-;ma7=31#VESA6)c6$OKLy*;UR{HeRH9U??fAYy`#{@ zB`c5NDL&C?T$u~z^c8(a`crJ^J!mKRF=zP0c0sS?RCs$iwPAb{Oz0to^01oC3TZz` z{>hSjYdv(zpC9kOp@faE2S0Gb2FwaCP0%$qHo}z9W?m=OW0_RHX1j=|{7mQc=&;aB ztUt~hOL2A^>|z?zG>BivH>nM~l);IanM8lih~PVg_#MRG$Lit&nv`3)#XbcZ`58yU z30wM{SDd%Eaoj$;0DMXQ`d?nM+V6=zYtD^nWBmypeW^yWzl-?Mml&St7w)*x{}paM z*UIoB{eLKpNBqU1%psHE{~i7B8aYt@3veLJ9mBXkjKhBA#EBD~_5rIocaHlcOL-!N zbFo7}rQw77{ub=_k3U^~dqp;Kwak6|S0<9bJYRig^f!H`_xZqXp8`*hp`6PEHotn< z2X@~p|CYh@{uHu2X?=8*K9}&wU?JXw#0%-a(W4xuJ=0{ijN%LWZqF+?HbSl|_aN%KfEK733xACMF1?G$ zL1-PN$D`9$#!LF9zUD;A->3JD?MEYD6Y|`Uqi+f586`R^3rDD7u&mU<_sON#Rup5m#rh|gefF#Mxk6<;4@Fxjg%*GTp% zGsV9r-iV4O{B6)D(U)ql+iv_V{9@uCt3yNKA0g=v`49RZ?7FFjjYs;#igIf`M3Vph zt`n_zz?Uy}-GC}VEaeW)BT7&tmD{oFiNAUoto9Y+cZ(NCv@pCt|GIo5E=Oznne_eM z-lN}H&cJ|c=|lab_>hJ7OmT>>Xd;&&@_LJzJ>?yr*EjIc->P!4;ilou#2kI%_ zVxraGK=Py!@%DtLhVz7D{)Y3r-mwS|z#B`Si@AUBy?gjEDA`K&R^>ULPV%ZclMy(> zG3nGM$d?hBbAQU-#=Ywh=(Kf0UZIMMd3#+dcZU0h({bEJ$VbO1Mexs2hQ8>(M|zIr zuj4os{OduIJf}aty`S=hEj{`c=SluQ!nb{~ZU7!9giH8-`W@i!vyJ~1>EG6$r{bQ? zKs~_zM%S0$d{43;2;+794jx&46WxY#Z*jB9UZK6_Sf}dXK7mG{TLFKE-<0O&$lgES z#PXRr(J(H^@L+H19ooO`tXzln9a5d&{)Q1H?)vyIAs;8`y?!&heMQm&HEn3W?(8*f zv+y$T0sfm~?7b|}|LMZ<36mNEC-viTzKuL5H$Fc8$VlFYkL1_PE0B34CMVzJo*3QX z-}+G;$$#y5hm%CGU-CB^Apf87EX%(lTyN=ZO9X#;dFV#OCy0&mIWC5sTgTveus22P z?%roGPAASOEZip04}C}VBggDG+s5$v8kZt@u3Xq(B8J{V>oXb!I2fMj$52%+=^sRI zPyXpw$^Z4L;kkr9FKavu$)Co59FpWc`B%0Mdm#BAD8Gc0S^XzIIpY?pPwN8M{q(3; zcIGyJfx$$t7oR$~k6>EiWIyZjHr!+Uj_`{w(s`1i1`mBZ#<0O!|DN&#Ui|#cHEcY} zk1|Z3QqN$r@8v0~@&xG&10v}U{KHu5@5gv{&r4mVhq5EIXu3U-ba0d=P z#2mF3MPNtCr7bd!c(|a6!3mexoM7yh-y> zz|AXo9|@CD&fd4<&c&~8VUjVwVbw3YO7f1@fAtSno7nrQNXaiv!U1B!;x+LC{V&rl zo5L6La{P0ihkVE~6co+bJ*1fn40x05FYixT-E1)*;LlIv3qb$AnTjQsYx+zbzqM{` zgS-#%QBAb52hK^jc(&^-yPpx~`NBkui{BP=(OdEN>F(rMaa6W|~ zpk^b!Dq@?j^!z?K8xp2zbdR&^X@9-kyF$_ri2oQ1!vTXHFn>hfMnl47)}Qo|@_TRG zWH9k3$A`{?8I1At#)>?f_yx-Mi`Zt1udImfpPxH|{&2W=r^Fvz%u=lwD8q zfB3`U%@PdzKx(&5em?R671nxos3&G0lbLy0qJMeL2K`|Bz?tBVcQQJTK)fQg~_+Fif?EBjFS#BTOk8^18Kk}Im^v}1o8kLqymMO6g zyx|{lfvFIr4?9(e`fg~PP9-FJF!-u~eZ>airKhoHVvf1`S*p?A-Q zGFaHJ^t8k=nC$Hf15l5mzs-mKn6fY>hQWG6UI9*CvyKP(gL09Qe53vNM1`NU-=Y2V z()>Nr`la#er`KV1i*3HaCtb}Q`YVGsK|i{D>$NlnQ+&nHZH3+pCjGx+i263c#8gOM zmgjDE5g3e#zeDF8#v2_rg8s>a1%IJukpHdtOzKZM675&g`rhm#m>Tg9Z>I{0zKMT$ zS8OBt7X&KNbMmO6(}d3WAb*?DS2&ME_=@o1y?0v}o__E9E0);W4`Q}WRX@Y*#%9YSn`T;S``-9<@IBdMCkuB~<=NQtmnKZFtDC~c zBl=ETypGL3=pFgL$Jeg4;w$uU&&^^m=skw>qh{7b;ukvO^`Hy=)FtQAIKkhirS!?s zg}xh#vZ9m1JDg9f8&nAPFVi{vA??-)_&JokK71<;K#d-qCN;b>J5Y? zmkIPQJl|>f3+~+;^8sB;4e^474*0DiMEWM;+0)Z|8H{?e`oQVa>ljS-!$}(lO=U2} zgTM2c*`L8=pR7=Y6fv0mqjwYHlNjuT{m8a=QU8L{`|w0_VGsG?;>hU?PxSiX2KZO$ zX|W$6c|R<0+ZaHQ{*?c-ETJ|fc`xfv@jaG#Z&PpK4G{fKn`X_2B7a?ZdAW3d%EzX1 zlBmn{Dtl|son_e`w85mSu%!)Js>77(1Wl*M93+W6^z34y%w&Q^ct(D(M_ zKG%d7<`pbaI!Ei8`q#gZAJ54{2hh)uoIHtsNS=H8KsAusK$z(3%(oUB{r~?TEZk2n zQ?c9qp8g51anRW67o2*DS`0?QMZ_OGg1v*;^#tc3U=aVKwot4$g7U#C0S+R5P^8s* zHEz{yKf^;b_y1v2Eb_V<Amuz%K# z_5T<4A8OBR4}qTWWiagD?;Z?LGCa{g&5Z;@pYOUDD&3Fx$%hjLOY)E6ck3ppkD4q^ z7IOn1gZ)s5`tV}xS9!j^rw{z_cyw;A-)C$*ivQu;drb^pABOW}+~;V)4zWT-`~CG{ z?!y?KG>V@P9Eem8s;?z=4Xke_>hA;kKX9#P$r_$^ ziy}W>HY=z@0KgE)pYK1|v0wfpKN$QpI`(XOKc4c@!dlMvlYbLm0)I|)UjFH8O3P~V zYdiA4nQvG>*=zH4)7FOv`}+8#PC3@&Q!d=d20KgB-+LQA$t&EYjN;Uw=Z_t)Dqj5~ z(ZBZXC9evrA0I>?8>>caWUvSR067ncuTNQ3bCcnT9{;znewEc9AdLTDfux`R9;WB< z_Q;jwyGZ{w@%^CxOm!xSETPf$8T%36-`@zf)dz&}j{r~33$$Y@`2QC*Yq=Dmo*=hA z1m}r0L*MD(x|7XHBb`s~<*4v-fGtF!lyRsYptqCBxNX0+_pNWJhyBdcx52l@wWY^& zWd7d=eN6RCIPRnU(|Q{D6WgbJFNZ(TTlN{#muraLpB>i}M$)dyGVN#b?;AZos?XpW zxShzkAbmd;1>#gDnC>ecVJa3BCIxFCD6XGX#yGq|>{e25^ z62|3us2EJ~2ihk`YfV20-y$%K2X9> zkmu|PJNyCvyl^!rS`K^O@a?UA0*nu;A71g8xA!`StYCF*LzmkbdC6HgWz)AIEts zcDZr^iw}yAaXw;m9yIii<>8lpXjFfwT910W{ZF_F{ve(|mm!rHTxq~y72FVNZt4c;%zU0++Qg;1Kp5y7^8GrWuV|>q@*UDyH z3NEnoP;iA_{NR{^7qj?33i{;UQqA*fqAWY^8tP?!y~rA`N8j4+?}=Wf|8V~{twn16 z@N^OS-@}5uc5#9IL2Mp!(BDhp?*9oUdSCOq`rq{+XsJK`X!v3vz5nlE6}RBw;Q{^# z?O{A1tQC`tZPf8;wCGCZOYbKDo@tcJZyAoy274}Bk+6BwTC4}V%`GBKF;JDx6z zat33+y3kmr3t}+E{}`rRb7L^YPv!>&XDQr{b-)9pEU3yY&jx zFG;yv`Sux}M+oqB)Yr(K>~AMm;BioKOU&QVMMb9){~?kkeZbMtP0-)(@P`He0axZN z=vQ>lD%2+`8fo4CR~9^%aJ{&46nh@TZ>1cmYWJ~xeVAN)O`8mJk*_tQ!9+K-ScH0f zUFo3z3}`f9P$yEdC$HRlv>f3ZztI>zISC=({w;ew8_zlC$FOmGt@|N4{2$&0->APA z*$K(}=$OluES`gRijn8c(QXjx^TMHLafJP3R-Qxn=cD@`xii&CMe=4|NOI&ozT=De z|M&+_^)JIKvZEHf=$~WS#owH72=ZNe{^Ao*)16*T^f2HKD#~4if<~{FS9sdm;lH-xf@HD>d{{Q!| z$iIa80Kz3AznYFHKd}*hNbnG=KEbKDS&zE$(!AannC%oOOa>C_>r-6d`ykGdn?2HFb}(mgwFSDOYD5+g7=ejZgb$ zMPO~MFxc1p2PbiUY5V2mc))@~ANa7EhCb-~VgxSbD^74#Nc2zjgOZ%Q!EePz{O6F` z>0{Kx^rr}3mmRZ1;*)N4^z8iHq~rJDee6|aBpwAvjq{^hzj&NR`Wl1I0Hxq*#G^x|>wqm#C9wM~33 z;j6tvB>Mo-+b`)m2guXnJga?Ppue18iT?iI;NR>2HugX04-H>rfb#kq=?jmp8O1;v zKi+#_{13tp2Rv`!g0?fZ{XG#c_(|=0%RkJ?X7ayhDP(Rcg~M>WkGXF_|MF$8Y@gD- z{j2p0l>U&tPY95ea&;Uk@W=AYX>J-i+J!S!JX@5!Lkynf(MTW*!^4r`cRQzI;XEEuByY@_wO zVVozEkeS<_#Max5^*@F=*Ts@H-?y`ST_45gmF+93XL!W()W{ud>;tf8gwH8@nc=Bk zn%4NhRpp92ogv5UEUis)Z#be@r6>(+jImU#a9iOoO# z1o2AkBk1!X>w4Q_9lKus83qyFIo-q8>o1M=Kf~Eh1V{F|dc#ati$HziethNHB)xDo zK8Qc~ZQ7KN&PUDsPD_%ZeDb7P{|S`uNdr(k&&<5EH0qBU-G@&jfAL_0Ujx6BzTsOt z;IByxP^tIvyDTl>Ays()3Waq(=sdLhH!e&2s|k?rFZblH6_gJQK=n1BheKT$4IlD? z>nV1bLp&MmgZk2c9sm4D1|xna;WJ}-u>j-ws&N1A;=M_{3nF;qc=NwfJ|B^o*62cO z15LDFce)QPyVJ;T_`>Pk`G#3?-@MPZqqk$vRLc+TDx4_n=)FwrF2?ho4LyIK>*jvp zE_3aa&*SA@R3t-2-~#MP!4FVR&w3s=$;UIR=B^U2=g7Ox-QKh2Nn7}XxoML9pXe{x zW4Yw-BmPjI68H=2|9e=l_Xk?X7hog+vi@|x`oI2e|L>py*ApAr|9@RKQ}RF3{dXmL zhe&vWZ&XMyjkkP9lH|Xj{$wwN^*=+ork~z6bZRMAIJqfwKI!9LQxv!3Ag_r?$vzM( z`bSiB58}U(QyksDywmp1xPNlLwyUZNwMCg4%d2etcH+wSbp@c_9(pc1Je#V>|EyYt3+&bf(saz0HN=`}kO24|!&bQWt zFMHxSk0;iLwT~$8>C>LF9OUaQ2Q0IZuM`>@8ugA4pESLP_A7%pZaiCGH%qU_>ny^|#Qg?cgZxm7Ni{o7yYt2iIz zwaRvk50{+@`~9oYG2ajW5c05z?m6EuJ19k`?qK!+s;~7+$^&Nq66%F`yIcKrzZ*}fb+ z$U=80=~Umyb(3D7Zh|eKkLx^H$Z?a@{!<7~1$4>&k)HYvLb>_`|040PqN*y&@1UOp zed%VQ%L=99M?@d5u{c@OhD@ca%Ovm*u@OJLnAjoO^ReLz&eeh>S*BVWW=m}s=t>&5vJYb$c|eb{*Lm%zSoRkW_)BJkHzuaaf`{KCBT`r53? z>SroncThSB@|@Pgcjv!rwr@`6%gklw`Oxp{5zo~8b6WfxoY_uaW9Q);6CULttTtL< z?J9-B&JNoF6+u*w3+}+q2JW`YCs>tv&`+auZ)*uhjpzgE_VFUb3G@$l+?e0N5_u>Z+D2HO7~9vx^8 zI9LCR#6QUXkgsycVxtp(nQMA}2ZQPPhUC3h#bA0K(^q}ZF_^4-SC)uX}1!U65~b693j8-b6<v=grK50E>1@V@jd5eiJrus+GNk!;y zHvRq3+Z8@JG7p~us((!@w2gPtyX+`QjEEM;`yT#x?1#-;upJkh57xiapg~-}{DytO zE-1dT`X0l(>{y!crsen*Q9lvm`KC^7ILh#pUwExyu-VjWLPk`>`O3nCAWKVWY;3Q| z+^}OV;$1y_Cv3HR2>LYl;i3~MB(%KZ!sq~$qrGy8O~+1f%KU#dL0@`UpLxSgTJ96 z2o3on37OfGa$X;%Mtq(lN0f7;cd>L{Hm#rL)uY|Z_z&M&n8|4W?4KrGMM-BI{4|lH(C9>C;5- z=T_C+VfcRw@89t?!_)P}c_qQN@c58D5op7U;j~Jzr~o)DKu?jVGYrGxeM`zDVdF>@EF%n!iPn?o%M2bsRi+OOy$|I!#$w zlzqD0Rwv#q5B9#dqEj_&eCi(>dVUduY5b9`pX_HajUT@;aX>sX+?T(DZ&cuCVI)!i zb00frw4rUbMQ16?5bCSDx#eBL@|j`z4Du_%*N$_r6V?+x@=N78t)|gy`eE2J*N0r) z7krZ6$Vc&W76iba?Ax6d78&{6lqbf$G(`2|tR?a;%Z28p$VDLfR~zhny*6vCUBfrx<2CZ+-oWif%jJcW$>o$f*rFd0NWL(d_-v`*m+MtyXbZ4S1IcSXsP#{e=f@5BYgL}pncL9DSkaRB0`cES-2n8-)OEdpnIRCjCV0CQPz1Ws$8M3(iPWX?@z}G zGq%rnK73gmgGoM?xxmrY-voMja)HGbZV7CX^if)$Rly|_t$4)u*PkIDlF$gB7hjxb z3xi$Qz<97(s6o)*h`*exx9a->{5$>szuEtYer)jqzxaNjzaZuQ8xlXD{?C6u^(MPN z!E-MKN-$l&cIUu&;X4^)zF__7`F<(oC4NZ#-#T-D2i~?y?9)C1=Yv=BZUuvfI&vJc z3+sHPY-I5~f>oidtJ!!oU%_Cg45sIK|NXSl{|OfQ7mPcJyQy%`S#*BVlzh+HqUys1Jv{$(?@thtYQvtV)%Bao8NHD`FFd@Te()vbBhAMFvHk%~bvUtR z3*|S4?<>#$!@HX=Rv#)(!+9{uqG?orr-fg`*SD$-$7ji1y{h*X6>L27x_PU5$ML6Q zBSJKnN;X;EZc9vaO30q9;{NgwLqoorkpHVTR1_yJBmEyIfFs|2cbMNG!%ToBD6xQg z#GXL`p+bE?zs!gjsUbmtU+p9ncnF$?OP*{T20Fefb5J znS30-)u-U|5toBB1N6%q1;LZboY)LH>BrNb8sQCT+Eps5Kkd$&Tj5cm{mJ#U`A&Op zE$ANA1Doz|!La8}^LCy9-60M(fUUuZ*9q1%HgXl*OEk>&cLOc{Wjt zVqyp@kMLi~UL8?CQ>t=$+QjLC^Wm2!r8+;9`#(@DGTM*58>`HE9g? zz#mQL8s20u@e8GcT^WPPpEt+GpC|AW2bqT9{~fGy$jW4R8vn&_55L1;qNg)!TIMpC zp2wEHe-G31_tys&FF}9Q0~}Jw7tr`icPzij?oZ?I$s3qY zv=IdhZ1JDQd(UGqy&p&SDV+??QmRmTP}j_0!moPg!UYD?_y@fc2aNZgcf17C^XSF+ zW#JOqAB@2Z=VswTJ7^uy|8=S|-P@PvEc=&Z_jrm=8q=_L<*56aJw5!^-}Huf-(g2Q zXE6Gwe8ry1j7j}4@(Hd+ue^~IhT6`hymlGv8#?7_WA~zstG3VHQ~kWbDPh-$C&FB3 z^xnn>8U#)GeVJkNR28Yq!(BW~L+m{CH9nl3qWIuRC1)~1HI4X6eh4QWT@_Q+M{`lutlO}Lp!aq9S z9SL`fH5O&ZOFfexlKge_+U&)ThJ=ugYqfW4u(ge|bhU`tuvz_0h@u)9@d8bD6 zKV3U|Q8yfP_xWGIA3c4m?LB;*?@iyg;Fo9bt-$!hhmINs+K=NG@IpPD%tnnyxELQ< zxc)Cg-B&-#g*xuD%aqA+zLfSI>v=OOkf2fq`KIH_;PLsz1@^RNswY%)W>m)We_x1t z9Z%m#3txOk{U!208stA=cgw8%SApJa&fg^Z82j5{>3R`v_?C_T zdzhYQ?xoRshX0>n;dyg{rRSybzCB|8g&@PvhCrKdpaT`#)VjbM4Hx+4VI3 z@AHF(cj{_8GeM~WQt}fXn zu5dd4ObLgs{NV%sChT3X=P0Mz+ri#t=3h3qnh-Dcv?_l>KL5JO-!jHLT3^$yN{mlR zWcL&5L4BuPl-}69y}O~a>2#?^KIXG*``ER19}Ry#DTdB-8symfRiC}lekk`S=uaaL zO^eie77j_l6X87OS@J3Fo!ZmARd-}RD=%JO+dkqmqod$&>*Kp&k3Sw(TQg%bcI;BdYc$}qVx#M0&kOacoyh=4_3gED`uEF9PDS=L zj?2_=XSutR-^~jpeFDc_T>7Hv@pt4ujYE7$kjL(SPIWxuRLs4K8hXgL=MvLmW0k?& z-;~(ZJE7kGi7D3vdX?dpbicFb!(SHfj$ea6rl-`uX$z00_!t$J$!R#v1Uyf(dvmir zr{FTUMVub<1$+Kt)}Q8QNwLQeTR48t$UIwkZ=&}AcuD0zIDU`sA=aPhbM2+6lDsDR zJhy7}ZHA}&p&o~HKkC2#OxkjWr~O8{`99H)(1iZE1N+4eB%MnJNJX-=h*eMK7xF5X{v=Lj^Dn;7KV9Z&wqq*|ItW@;6CO8sLvPR=M+yU z2lZ2<`sv{|=Mm8O!Qy!a0-oUCkDmmCh=8OI%$H@yT(8~C5_%(On7U%Vjk zcY@vPogK3v-+t`tM7~VBD$mX%3-atJ#P1vPasK?})2jF{M}6hC#WBl?Tk^Z`Nplu@Mo&zc@y*UDrZ{8SjOxey{X2|cR_so>*H|1ix>VQ{*#s-6~#>! z`491rDMj10@x==_PG0JPeCs93O};gKEf(YL${*hP^!fB-Py73e^-$>qf*jPl@^UX$ zo72rnrkB4CM*fd)e3CBh55(N?FAL9eHvGO;(--iY>Pt-@#Y4E^0H;5K{&!%7)B28x zh?eNz15?&E^UXjue*~`!Oc?;LLj8$kUT491Fx{ufpN9kC#v&TCmWvboyWuc6SX}#@ zs3Mjc`|0?V2}{^`G(Stvi~F}gl#~|hST{~_WjIeue$CNPWCz(#jqLLgHuZJs_v}mw z9H758d5W?g5PMV zmtY$2_w|SJiU;OX;6znVc0KjaE14+SKl`r_W_W==DXsC71S7v$WsA>|l{s)KPJ{o1 z^-|_&>R=Z!T`f^ZdNJY}H@suAHoM_bk(+LY1?Z$eIJO%4T0PsP- z|IyYv=Vle{o1qzwvz-b!?#So2wZk369@nFF@^thc$Blz@9wO5JVyk>1etx_6+dw6G zE76F4vz*-*E!_A7xPkHs@B_K)^iJ)Ux2wK?W&fNkr}Qjk{-K>0Y6Ac6o;Eb`547Lc zrKV-b#M=q-Z|S0U-cjDrb#O`C`&EdS_VT-Cc!isb5bhj-zsAJIEbQ+tJpZ}2(GtI; z_cM3RlLPeS5xbx*b#j-Xv2h)ROQ5ebYu$us3{Kc@Q~q1K-#M31d4Oyl=5N-L*A}ty z1^&2HyC~cFO^1&;js0A`ic5HV;X4dZ^Y_E_wqNkS0Q#;R{BXPbqDZ`pq<0@vPVt6( z-;gz{ZwRy-YNX#>jEY#zk^Z)X`&GRI@t@k}DFTvH{9o~TL=VevKRH0YZcH4wAN970 zccb;RG4YVJ{{KB}UH^o~_EVw<;%9*idL()!81;2miOzW4s0`##Uz^9=Qu zwa-51?6db;Yp=ET+VB>h!eWu3p700vd<-pdj|H{{d;MR&U1p!E?!Nv>L;7Bf+3!_& z>sR0TeqjH<^*H@;%--+GvM0&)Z2yPscy`C*^;bW$nq1G;4>e?v`Y}-P(XXEK)`$7E zz(+zZchkQ;_#OJ1&;^9fzTX z>(6`adUyR2(ww{h9&eAGZ$HX=de$%Z{CvZb6(IsP-XLn3!#N$!M_b`Tb0E8;P#ECn z5k7Dew*H@A|L*xI+$T#Y>ID$)Pf~MP{q}+*&w-yOohP8;Ena@X;-7bW^TV*uyLb&` zP@XE}d$9Zf?#0d@|EDcXVr=#GgJ0@7knd>vhC&XYeCK;Sxt5HVC9J+~G#VwI z`u=WtiIDPjcuaR;`$xcuC;X9pzu#BZmd6M30^p2GA6CD6b>+&usp=FvKf%2u102Hs z#$KNn0qcV~yzjv;?)Oc8+vpuYQ2(KhzXMLUGs6Gf>j^MVGK;BYph2#oxWIo?QQ|X3l04G-Gb>+q8HM?OPNA&Gqzp$;{&!EjQ(!b|Nim6 zd*Hut5B!AHCqO;h<$Xq0;(CU+_qYH4*Z-S65D$+n0Eq&MFO-sLlZR*_`@OOIg5_e5 ze{a=}m16gJC}!(Z)Gw$ycYP+=ga7t#)Dl)+!T9+eP_mayto&nN6yTbOHXmv$M3bS< z)Kb8T6usxlHB`H&{=d=QW9{=GzuZG=)Q|%4D$&vA5bC}^Snyj+QrM4DFlh7C{M+Y$ z?0WW}JnC&W8i!#ltVrNZ`}6YgUiSHz(;u2q_fXsW(DMxO4gsTGC@Ky6+unl72C^V0UNQ? z$YiqZO`{f5t1{U8dC$B3&V9gVK(p;xX2JXInuZpmee2o&E@+4Lp%haOW8L?U4x`k6 zWSd8&P~XvbjJgcQ9Vjhf2{b><%%mNkZ~tf!NK1FTP`H)BETEX>50BZSzH|To2LFy8 z5U|&#GAq|y-_x9P)l0Px~Uy_ z(-Mum?!chnNq22HUq}-ws@fM8eGTr%{-ozt7y=s`8`y{S)pN#e?(wug>=bXr2fc)@ zWk>~Fc}j+n>X5klASR6y4*v4yfS{@7P?x4dSj0pJqZd8DEJzgabtQU*50z?Se_pd+`2uE`lMv+1bMA*0eBpU&OpQj*aEJUoaT0!EQ{y7o9Ru71p$!QV{?@H`S$_&| z4@}U3wQxP`wxX7B`oZ1cZqKrG!Le@l(;)OQ%$KsSMlEo*HAHMezV0{`RiMguQ3Bvs zqaPqPQpI96Rp9>I97l&JPz&jgfhd}vruYfm8RGw#ec3c$04(^-GoE-!gg(tFp)GLU z)Hbj$UV|v?z+4GenUd;a`#<5g2%UHLe+!3t2rmm|r5Mhqv)gQL0Dq>5Y`pW2Y9gkO zS=~RKe^Jjhop|Xdn7%76hE~URb;8rHVSk$|HgZ#v8_%)hsf~l=o$Pp$ZTIjG_NL#F z50u2SLSg^A#AkFNvbyW1oo6dT@uPZKe(~7;K+o_0?!QYYO2W<+iKm61!hbs0Zyn&l z|IhtBDrNaK1NlVU<;$iic>)P~1e28G1J|lV0TmEt#=`+s8aUze{mpFXs=EZ{N<|ML z0RZb(awBaUS2FoiKV*ogkfTpTIxt--($lF)uRn+Jpr)42V2pQxykj4|Q-g7sIGvs~>jx@~XJ}0cj*M(_{S0kqRFQ1B3)9a>3HaeD z+t~T(F4ILcANFhS!q{I#hcL2X9M6jeVx;_}hUM>LdVQ3UTmJGp*D<~nS0^;T^SNiT z!jFd&GU(H;n61jG9gv+-dhYWjC2!&SvC?$m0c!_oKUOT^zkX_Fg_oW|>lDce7z4ix zNo{B%^M9?pn657!A@BDw%y;;|TD_JSI)Z z;qfkh36<8e^2i){Y}c^oiWyld+M+C|K(9i!P$;1y6(zaUxOW8yY!OS8y>-Apbj7*cM0qva_wgMy(P@AW-l;TC|5LfLd)XC74s=nz~Z?u{uP5D zMF!dUQG|SepPM+wF)QejES3Pj%+_=DlJn;PVWY&<&!)90?U&0e<9+ul8pU->;|w`^zYEJ&2_!(^|& zj_dDI`|#hjdhfSNF5%zkNmF8aUsOByQYJZ04CEWlZ;p;;MY+3#!u)0i%fm5VBlqEt zL@aE>c(dF#yE}h!C&n9L{`Wh(FJSDij^R66TY@o;)6Nlh6)H*nIns1`puY#?v8RNI zD86at2?fS`q(*9HBr3$%SFEBIG$PW!)1)?D52_;VuM+k0)J2Sp)Q9J92@dwdnEjp; z{W=}StK`K(PGA#hZ?&2eJ}^Iw^ye`2Z(4yjpLeG)hOghZ^9_8xFYwRNrD23$T!>2- z1_*=Am_A3`&g-*fjAN`8Q~VDj?xkT2{6Q4CJdKRUp`1>kBDua7)63Ky!eGn52*!Tv zdWB!~Fm=|c7FUCOu`o;p9 zkD58$T^PqEwsUu!2yeuAu3Qkp?9h6c2mHtZd9-j#=UGLyr$#!w-*rn{AQqT`xgA1F zYqrwEtq|I3;E1*b_zt@t_ZrgemB;WSDu#8>Qb82ULC`HI@Phr5+n+@gybJ^k#<&G>cDT5Y!gy_>k&Wx@t)uvp*L#$>euvP;U9h+@9^*QpksIl7 z5q)a6G@V~q#_kXBlu&U8PdQx9!T4ev#h(=s6p3*c$TwYSKj9alFh0$b-tinJvdtDW z-Sx-y4bo74cgsR@{jr?BsIX;nG9Tj%9m4I~x0CTOS1jO{2R0IY<08oKmeWt4#q9+e z>VyolV;;uy<*Ytq*^9Al_Fc6xqml5_J{Ujks&O(O)9Fp>r{=Ne+j)usvKyAxr7;-C z0slVX7%9W}u7={PI)@3p1HMmPzR`;5uwDysn7rqWuhP^CVSS79U!gjwB!qj+HkF?O zp*oN^f@3R8lV{;Q@_|2SS+07ny1^P&dP_e*R!GYM3Hu&Gi39x@RIa%?4tQ&wE z42)mnFh41aWfpnfAgx;LXg;>rPxc|FkT> z0M}=~C-r3t!C{FuUZhM{jOn}OJ|X#xgY1Xzpxe1-nS$uQrEz*{zC(wrqdu6g^USSe zzH-8iTz$5Jtbf<5B_YFA5oG=pizOjRiW@g@1BYS!E3JiBF#Z^DUSY5W<5J+C&CWr> zpILqss*ou$on61^hZ$0TJu7dKY(J(Si%X}OFz@-yz#r&s1>2`aALKWc8->vkWkI<9 zu*N6kL3Tk3#)sqDXYZ>TxrMP`B29O5%85J-g!z!Iu}@(7K7)k6G&=fejJIh!rT1@o z-*?f$4xtS8OJI5^Jip~ZUn9Yy7``&iPVy1aq7JUJ$t!QT>-55a1||7EwQFNwKDH5l z<|DPuW`gdM{#tynSIAge$#}}tf zNJ4K;vXb#C6|3kX<;WiVe7!;e^naTGW60hG^HE2}XD5vJ`xPTam=5xk?=174AKayp zN|_?(QcS<3f%8ISJBhq~OIyngGuM#udQaU>AMcTo?@?KKSh})EN7`4zd<~p&t{bjoTa67{ntBo5=jR z1p0oSHkHWZP_c?{R4@g&zE8Mq!Tz8U!Y|Zudiv=_F7mvwQVBox8S#6#{*cDVt&%-_ z0^?X%kDAK7=at36dP??wBbg5y5{=x}iiPBPR>*Cj+hY26CHyAUf4FJpQH-}W!}nsCXpGlZ_C>M&E{u;9`ie)YM#y;5Q*hpL z1LQ-WDiV+nMLLR%KRx21(p9Ex4 ze8yQt)}I$(J7{Y)+!1tx z*k5T_Pc}s!CD;3B8EK1bg2?mLFrTi=3d#Ilmu2Gx26)Hknyhr%w%N;%RziOiC?_uA z`@!iUyvB*-c#Mx|RQ%S6R-(Ubl=lk%I_A~)KC7;!mmY*b^Qp2D^z(V;DsulukUtf^ zWv^oT`KV%Uw9G>K`*s5ReHU^t{o@3ikSSOch%wj$LvkJ-Am0=09b651NY)pj@kM zd*8R{YxIdGozKW~{wKa4Yd=3ZW;um1$Rpafbr!jQUsRl-QT6joOy8xJ@NL=K$@r@< zQ2dqvZ~pg-nhu(I*jR__hb2-WsO{!;j9LBHIC(>caR%tS{l8a|{=ZV$E1cEp)z8-$ ze8N+E1SPosUZ5*wsRbB+4C|A-qiuvAXBy^;nYM^#Oka}=`mGFh!%mffgbaR~Z1f9^ z=OuzaVx;RD##>D`Ze)v2iSe#D6?J^36GLQz`z?(0+UM>T(jl(;%M7kCkl+*2{n8I= zF#bf7AYWIFqQBJ|8+2*8093Uv-(r=c9#O<3vul{KZ~;!GKx8vKvn z4mIKW3u`)PP;ZxGdzt7<0*`fs40G-Cbk}9 zwG`%mSVREEErxpOy|OAYep!9LWJp2er5~&x6-p(sm#l*4kG3+m@bw$jwZhaS$03Y& zgMH1($ZlZF0s8@DpTpzhv&PEKD2r)X+iO1dAlUN?`I*5#kw19cGtvFvydU5P9)BmB zkDs!Ew-WMoGOR-3LJyHfTP*zg%GNyG{%Z&OUW^RBnvU^Nb+7P~9p3fgTJZOUe`v8_ z`eD!)CyyGYVH^(j%-|O9`aihFCYMfd@Sj(|qZ`8bFsxT^ywEy;@d-n$NSU2M z?tc&T?W0Tgll%S5G&fPL&LHFQZqDsES-XbJ_Z6%>eEm4FCw!R=R8>iLV$ z3tVwtRv*nj$@Dn8SAjL2hyR7JPG z{QgN?zdkNh6dm#XWf2xjkmgQU^u0IFtSKw|T@^Ayl z_b>-}9CRM&wg*bm|GwbA4>LQ*@%5%So1pxfiS%zD4fJ#>Tx5{fUs0(|=cgvi$n_C5?YtmAGkKmk;4fb!bsxgluK;=4 zHR2-uwF~BH5f z(yMDn$}z47`^$%Cz4n$;m=8B56-rEx75M~kO3E}CuTq2ki)pUK_?3gjLZ&&Q1>*%; zK}ehHi|ZJ_dN4z(L@nfbF6DFx$It1~(bS0B2>NRNPA1JYbpai&qDqW)5lYzo)dDub zi@*cCR>26E+O^nO6 z8RA3xmo{U(CRf7WU#%eHtrX@T9QT-p>GyJ|kY?l8N{m-)(`n6i@Avc#xAB^rC;Bmc zFX&sPP1_4G_62=pG)a+#G5BBjLNiFbsZvoHLkA5pMHm~_8F?1uZLbgPFSm9~;_g9j zHM8=vCK}hjm%|BXWVvMizNM{;3wpwPUS~P*pVZXr&*Saxx=&%e8s_gMXZLc9 zVgBLbVWWW0T4px~^N4+(!; zkXRd~mAzAq>#u?JnZx4skK9R#p@*M%hpZ3$4HESYPtPPM@vA zI1Kz*V-GSjFrEYJ-{@c=SS=R81CjT0*45Kb6p+t@8q_qOv9jl*dkX8%P%0pvWUBPS_mrvJ`N`bBG=l#5 z;m8Q#*PB3}XVMg}SR0z=EC^;=&9;$ z)HMom4p9NQ5-K?!`~j1c6XF{l4nTatfGOHjUxxzj7PfQumG`9}*DK#beupK~z=!HH+xK1}@0-pya^G71KH-ORL^f{gvu6o^mydML< zzvuL>ztq5wT7sS@`rd9$1|6jOG7aDFFvJhE|7-)%=QhH6ReaXF-aZ2HD05Gnm*M)u zhl`^kn!WnX7Q=e+^)c^x+-txe^Lnc5IIgdj`h+~!lS1alM(z2iAx;2;>4RYZMi274 zF+L{u745iIOytu#?OajVL}?kOAFf|7T^7Ck5XSX?sEf)}rI7bqqt#QF-NqbGjZKv? z{5~*X;QFPkJb%o7Jzv|-t#5KTF#VE75Ta6f{aL%fo@kElCf8>IzgrkyLcY&hxt^Ok z`C%Wvenw4)u-9?(ON`aLa>ix%*vO_ zyw)uke^uSiGld}~#w&q8a`j$+SCtn0U!YK9`WoPu5y9m4Xq~o}pPHmpU^;8BuX);` z!+0a`x0%N%C&u@31R+Vvc*5Vpm?UiV<)^xLVB9P0;8pB15&88N=pSh#5oA8UWhj-VF$yBDhT(g#@yq!7 zjcVU;i_>dw9xP<#Q%WkPhXFsEc{=(k#`Colzejd12;;pl|Cmwn3m9{oWB7Tx0wQ0J zXl%1IGYk@i{tZ$Y1q{WR{f)g<6MoZk@r$#30xA){OT z5~hCw_H(7OARFUYsUGtCtR>^gN7T-XXdWT@=V7o{zaJ4x#&^FE`eVK~o@j9v#Q#P{ z68U+xa-~?eLDPxbzmUKY{iXNnYK+x!J|T?d10RfI3qc>0Wl$J{zny>Pv>$12j!44K z-+ccXriX&QC2YMV$M{aBl)f^!a}?vf5HEH4s)Ic5CJje4@vndV3#NZn-7DPJY9aI^ z8jwGewoFWC?Kjcgi11gCzub?OE{|4|!ei$7_?4c{c zKHp>C-h=CJ0Qq)m)I{b7_+NM_O7D3zb+GdFFe{ZeR8=4v!zd{<-R1w|{#{WrmS z{Qeh7X+XaQR-eExpz41zya_ErQydpcMv-Tg~X!DyGZ#8sAr76AZ|3R29`;Ja6)}T@Y z2nVkJ z3dnESx7SH~YH18VBYTYSoA4|NKho))Z$oM$mpe3}#Mci&e-t_^NPm}>rqcl}^U3=4 z=tt7u%(A4B`>!airLTYS1>vXjpnn;LJ>SGrVx@FQVNN4i&$H{l0mpta9{tw&geY~# z1i1cch%fFnBQk$JGjziDY9st^j~LbyJ>K{*6X<7Qrw7RUvi8;d39tRXPT0=tezs@- zRQoL)SP$iEbEn4h7~;w^XyzA*#9nnL4)~+2dl`P7g}KG_(i#~V{}As-C*|7@VfsZ4 z$VZFiD~vx;8~MrkH&0_+5B)Fg@}8g4TuqB~=%&KXu~Jsb?jxa&YW$`L272c<8^#AY z5+mP!mf}GE9*Ad_#=w5@56yxH!ts9rU@t#bz$o$aYyf-kq-B(h z7nuJcPN$vl?*^Fv<`2F6H9*@d982yd^#{Rz;TZTj9=GQQ{$Dw=&%~IGx3Mrodoc!o zim;~H=SB8BzC*v!osTora?8tmy2q#D6SKfx#}p}SxPF%=6ygs?$$VUp)fd&UB_{>b z567wKuZrYE-%^9UhSTse8Q=98dP+C*r5;j0Mb91G{}lfIXdcM>pl>Jjn9k}$8E;w+ zVZ53h4;HU~5c~o3$Yt+*St+lh&nqo8xcn@D;f|Ki3$9>5IVs#+>u&1IG=%qK+-PMod@#k7?rh zkzr(fl-Bo`oLHcjV!Cgkk(Ubj{xRNC$r1HG#(%>SJB8bG)D(cia1~p*Gk*1^W$lX4@zPw(ov8Z?QvI!gVzVll6xQ z;y*WB$YVU$+b1t3_<+7AT84gjh(hHE!Fqy{DIOSYsNKz!&r2m#KtSJ4d_VAC@s6GI z>Khv%URqgHGYiuLv((}n-z+5j@QH&yp*l?$;ZI!PuV%YC0oVT+)?YK1eL&W`WtIJL zlbz?K;QOBe8Qt2BXecT_UuE-7Zvp#HA20qV|J4uHfk_pDcqOVP%r64pf1VuTC3P0k z-xpv#V^wxPjpC@Nu3+>9CT8!EFIcr~O2gc#RU&74Z z{@M%no1cuybeJv#e*)}}t-|<9b-$1qX)49|ex*QE5`^Q6?C4ck4{0BIpXV^}`*;Tu z;`(#suphxPdJ^NVW}k3I*H7lpVc@q#>()syeT7udvpl{0LyT9-sSqmCaSP)B82=}@ zX7c`lS^dJ))GqRVMKjVvZYJ$>jN$w)aj8Q__){6Yf0Eg|AEwe^klt#Z zMaF-HA%@mdsQ}i5pk}`cs76 zLe>iq&%vd7dK8#GPp;=3H=E5EACs2|gOQFrz!f!3FZj}{FC9133QzVqEx10nQboVe zYmbzATgQ<4pc3cOqEh4Px+CwWV zLw;ISok77~(kG5$nj zqmhGbSA%%@SaC!DA8IhYv9eDnV-#e3u<`c!Ukw^DUBHjaeaEP$(938#>=z2y0{VVt zhJxgm`6p*PTTKG_J;5Vb$EEZVeZS`h_E6Ao4lWNL^1!C?)!+~6?*4EY`e0}LGuc+B z5aJ;%%yqVJ+y&$n_}{yuF}?!pEsG+b(1Q-D#i>fKJ~bWuV~_R+0Vnkouy61kGn>hH z@n4_L7Z^SErw`D2wDq8kXLpVed-fIB|5Ms@Uk7_*-8ytX$nzamN9h~Cx4*--*dL|h zUQaFPn`#~f!Tx~evR*PD-)O!q9r0r~FnS8Be+TSKn)G5Sp|d#Fvx! zKx{aF5BTBCeO~_;_`sg! z9~jNFRuF!!>fB)|>-k{E)3uNxOUEkgY{<=w4L!M_3?C+>E7hK2nHza0=YI;V`V|+J<4sXAdqr({D*SX^b$wa?^ zcvEiM4<|Gr{kMSy;F@2ein~3#tqsq63A>*M67*vH&;Hk;dpnZnPf_*YAM6@|^};vQ zx75$6C0BAF|7Em=VV}ndYw(re&&kieg1ZCl2LH=4i)BB4-bPIc{n0aC|G^fyRD4?1 z-h%0S)am>bo!5W9nbnu`c8=lkeIDem)*jJ9p9$a{%7c6mQVA6%8;9}XayL;9`=g55 z#I3l!#!8SNEr-c?S_$?s`OVP*Ouq;IoU4w}so4YP1HgK$E%>e()58)w=$Wky(ZBq_ zzPdB@+)pw6wHolB6>cZ?m!ml)!i*y81g2k68>EX{z4A1xx&OtZrf3VMAFGa`T?P4M zeB1?p-STDL{<#SDfn%Jn?TpgtmIveZUjcs%czSvVb38eWJErIz#k0p(z2x2;dG=SwvAHIZ==ok+HiZr2lVuOvx$tC zCeXhRe4(78zlTgPo=?p@-Hq$7%`yrVHV)A3=!Z>#Pl7#@iYzyPf3aW^aw0C-0Q-mk zc-s5@H*4GJZ?-FX@by;?&=5}|XU9Vv@jNeYu_m)OF_U^-e>00`uU!}9_)i$tSwj4=N(M?rrVI+=0wBk5gnb^xa@zW2ip862)1p4yi zACt)VSOoFIO!v~q)>nN(NB6}bTz`)m;^&rLKZ0?Cp@XhKqh$O^#n~72dgHs89uE9s zZ0rj%9#@K2!~W$)GM?|LW5xH1nn?QxdhH~{uT9bGz#A>>bp z6&F7ECJp~^C#O#sJ#SKp@s;Y2;}l1l!Wh~-(;H8|L0v~LZ__F;JuH#tZ|T1+o`s_xrb_>dnT8Y?%^1%(SVE_!TC5mn<<`=c?)(QB&z08-9YNmxj6DI-C&2XPE&`Cpke*0}=NUUc;doNGAuCNSuk4k# z&u%H#s}VYfra7sJvPwO-WH^sJUw?vxf2!LY2JI`+HtVdl+EBi_t4pzaDj_nP&twq% zySV-Tu>U@yIw^=1gm_0P4D9j1YhEs&MyVj)l4_Uiv>C1RMCt9nVmdy5 zM;<;OGfdvgtElku_brAF-qqk{$2Mvob%_{Fq}#PV`Hjk$G5DjoH!>@Q*9zp46BIPhyvIUFb8zw`W2GJjU7&kG-Pd-uBrX2sBfZO*Cj z^O;?kTPK`M^{$7j%*Dd{Pw*U zo*??nF11>G;tCs2a?5?mQHZbJ7a6?+Utg*1i!!x^*JCWq?G!dIok#pnU6sW`nPQyf zukOO;8**Nik@aaEy+Xcu^Ikos@2%{Av0cw0{Ldfay;NgFeMPrnzh6UuK!EE9!TU2U zS2{6%%LMvzfj6G(uBJBXiEwZH%RJyWnVAP(!u3~!|J3@|Ka=?!Ca>cgjb~CZ{cc<* z{lI3^V64%qLTr8K12Dd*X{RG2n_U?1hVgNnyN>XmID?IAHV=^Rd4i2kX0~3%^>2f} zM^;|y)&0!uQ< z{#yY|NSOcs`}~ZePEqZY%=&V4 zsG+ihC)?Il{vS5a|H+2_;CzUdxMH0vAfBM1q-x*P^IiKGRQ0>qhMFddWg5V z_FCK}mOh5clQ`kEJTz36=c(^P7r?%&g!Ax&FMpt!bRNk8p8m&_|7&_1? zG&@dzgztByx<1a&uZY-ln_EG`bvX!s3fHRW93>oXGF5_V^z`MC1~NXm;J>ga6{*N& zt+ndWdMS)A3R$go7;sLw#vk_oIUNmi@bz4!Ifrqa))W_1r%b^(AlEPPgs#g6 z`j2Gn|mZs&OA@BRZ+$X&1 z@ajV+o9l(lby!bK75}WxsIbwL?HHTXb;6|%&-#-64bC@@%I*YtGgbPf5U)|Rlhj`Y z_J3JXI*|wWTRVi&d0u(B0Q9w7#%n(=702*{E4GsHbQR)Hg7?)o;PyYQTra+JLi}rt z*BuNMnH@>5V+`^yBxAdE!anKw#UL%_e`uSWhpZs&w;^;1=4T$Wwe(4tM;vM=C4u#` zl974hZU4!+#a8-x5>8;1p1ZLAO?ZF%_|-pMUUEbY89!crP6_vGL*ctAh_B(6 zl;;zB-;{pwzw?*>gM0tosv!GW`5(jTE0$SqePsd_qiM0Vydf}vR6mgk?SdQV87T0KQ@+! zJ0r&x&bBdwxR~cZ?`Rtg@1MaM6de|a^7pDj&rZ0#EeNfG^~D{?7+9+JiV#%S*u#wP*qff@fJf2zcD!4f^pEn&V#Z0A|E3wo9WBPI z4PY;fw36q!Ti7X_#p66u>Ar?M@_d;Fh`*;woOYMp{lVD%n~fixZo&1#5<^Ar zeAVI$*lOpuC4Vff<&~dEbUrI-_Lq@kAf^ zyL0XD{P;*XihhSw!0=U6VN*ne52b*JK0j)O6zqG7>hmS8-i=6qbvFRetO{WwlQ zFS?22I}o}Z6~ll3xgWlS>zg1y+WUuw`!L=M@%2N46J&k`fvzNRGZW{@{g)hU7rGkj z$b7h4DB$0`*<;1mU#UDF$5{81{)jO7iWpa?gr$$bBy3P;2+`;$xxZ9&RGu>X-S?~` zV88;7eF5~H?mkdCoZH@ROGh{1Yer?REhF;ta2)uzcfR=zzTcat?MWlWi z=!21w(KVR<>(97ZOiwo9hv#Ac07V7<3ezidVZUh42;t|#T<}M?oo&bTpIFsm=CS=F zCfMIN(zXx!zh%^V0p0gomq8!hFK6q!GZIdZog)fpb0w`rAA zPX{}2VqyL#B|-e{-<0^LKW{O@d2Opfp65Y+0aknHc@fzcW=SBwY4U??$hFG~E!dw% zwL1GtHbbnBeX2c0Je*JUpIz9q-+0uLj@b|o01AL;!V?b+`@^{AV*={h_`Y%LUr_WM zQ8Dk#xdT)BoeA{g41T14Q1Ie$tCQ{1y;>F5zXkFYTtjW}zx%7Rq{7Q~?R|<8{Oi90 z6X+BQC?+g?!P_Wq{F$!P~Z-u;*$I)!FUquqgPm+zAD)qea zysMYlw%Jk+^9Qv(H_L+%`psvsKgTtQ*SD@MT3^wb`_qqN`UP0u>92LafN=%Ho4kd* z`*$KQvhim+8>X*>^=UWq=9BRUe{Gue83Cq$RcR2vxp3xN7;n~g2<@rKWc|8E3-ecV zo~$Q>YC^^Rzf7Bwx7QvGX{L$GMoiDc^>$W*WE###-F0~OyRsd)UfL5s?;0bga{0p-2Ghz>h{Fnz~{!<%Kv)-cc^5 zKWLpMRwx}yG2RUJ4-4|HZ!hIY=z*%>R!qNNsg}ZWhsYB@u;=y3yzu~YL7$(mS@{O8 zZvgv$Tb0g=ai*a?s;Q!HIR z^?eZ7qu_nNy?+wZLq!)gf%A^MhYrDLhH@0*5p@L*#vh@>XzBgcPu2-zxSXIT6#HPV z+I^DZ`yrktH97kSp6R;QCybTf43n#Zx!(?9rJG@YPH;rPPu=amfnMG%@0cw(R^aB3 z?D>)#<)Pu(Nxyg$K>1Ss)O0EX))$b^_W$0#|Bv{Gp4I-r_6PR4S@6IJB|53eC6EtP zKv69NCcB?CPw80RKS!L-Ef|0P6y^G!>wV-z^&o%!!dl#Y!@kpk+`6caqP)xdLYA}p z5n3T19F?M!FCO}few-bo7eIfrIs-zV=+(s{EBNoh-|>!eh>KcW*MPilBlxTCH#Mm~ zXV)Ii5a$uFKl(&?_5BJ1#2X%cd1|~OQ~~jT`t6FH_6+n(Yv($LNowRB`r!|shPK@0 zQ-}{5OD-VuQ3L+NrL)Y;u-OMwtEX zvKYoAm=B@9J>_36mP(7#uR1W6!v5R$m)zKiab{(2RND3jz)C&kYU_oBJUgcZav3G7cQJURvUbG-dMiKm0u!fUk=Xt8drVdiNt=&Vlv(GN%sTuTr~0q&GX*s_t?b_}fd` zr=u}GX)cwfg2N4CHvexp7xHmVl~2Il{Sor+pV?%X!|ul>_KFMYQ2x&RF+Hw74fr!` zM5z{v z>b^|*BZPiCG41o|y&E+WiVClJ$NjyoA>{zrtH;J&IzGkq{o}Qh=sd`;q+gX3Qv=kv z@7TG?fb{}uRVocJuv^9o5M^$yxZvR zqVur7qq|iQxqHJ-8UdT#rH-MLilM-9H0eC_%K!@E1Gp7Vt6N^#(H~Jj4dgGZs>0*> zt&19nXKsm<;Qj{x7T@(nt^zH$FBiYeO}&$#=jLcOeL_u8qu`GmJq+VDEE=!(oal2i z*vkh3Xng%^=k&Brskm{A?OcRTY=rgTqv5mJm~N7H(7p>@X&7f3RQ$-rNAdHZCXnwr ziixS&2=)h9PrJ??A^LMTtS2;QpPhxTZ>sK#8gagtiZLrs6*sHh@5}z?5B^BYoiWnh zjGA7E|9EV_9pp1dCuWlI7+3@AowgPtU*EVGD@JYOqH!LeBTE>wduU0KaWo};yS=EPJ3uQu1mjV!ee zB8%rd^MFbzzgxRgiQ7M_G189K$SQ=|CfY9AOE1HIjPaW{S$@ar!$<@6yWYe!=uekh ze;qWemqs?52JrO}rWMlbOC4J=9%+?I3sm0q(lN*%_j&3L9j1f-Godx23`2;&jp3hs z_Xg>YEbSaoIF|~*6~5?+m8vq-WWGau9>09V(gwDa+32U}wcr{b{>1i-O!oN@%&#oq z7r|zW1z-P$NkvQhhK8rGeLBR0rpd6 zs*;T-ui2K5T3LCf=RIQguYFVw^7^egknfC&RLVeqazMVFNwjuXC>`nW-fy+GE^6}I zQQ3kRUqSS0r*01T6R*og-2$PXbO)S$ZD)IQZEm}8@wgP8Qn2?T zU^T4onSoM{Ct4cpSrE?=9o?-!TRkI^-A}wZzmS2d?s6FNTimp^3Ak7NANhT~)dA~q z%K!BKmpDrl?0G%hIf)RDkfg}(fVF@g{=e{`grXxLK5nXnL;l7aHzk~Nb7V3DvWn07SlD32(muTYVH%xpMRRn@1TQo zApgyFvi{u!@jYAW>&X2YAV2lx^2RIB-Zj_G8*eN+6)H-5Q(c7M>;4Wjz$0=AH?YDR z9}!+vOZVjzGWXG{opCeZyqq`*b!=c0%@mgW{O>h1^d+SDZe!f@#D24c-o)Z<< z(Au{k1NEyLRd%{j1t~DR@64G5$o;A2qY_-b0Lm zUkC#ycD#)7E_t7@hPkK47~W4?b zPMU|~XRJMbt8*!CCzXn8A>Or=vZ3v$wGBN3 z{%5LTW?(z3`}@E|RKt4W^~cUnW+&%=N>NdQ5Nen!pe|{k{io2z3B;ZdFkdU>%j;U$ z*LRn$YJ-%~ohSOQihoQ_=`&NN$gVx$Pj5SuPvqZPSWg!8dHqAH64UwSpN21J{|R;3atGucBg@ZrdLXK&ZCs~wB6?hb;ejns5J731pHS<8i{->iEe7`dIeu~tBag0M@KVHr0c?&T1llMk# zR}SpQI4qG9z9FV)CdRwLe;jt&yC1N{d|S#eVWdBvya@Y8*!+iQP$PTIVX2XOBY(IP zUmvzEoo_28<|H)pRE)G__+Im&svsYKiYs^v;@>8dK;Cx7fAz4KB9xW`t%@u zRPa9z$hU^7ssMBfeZaOk59}5E-!e+v{#8S$s31cv`yursstumNC!Dnhp5Uku<@ALW z^RY@Jch@e*X|}$7+`8c|#Dff#KSkd*>c@x7-fHwCH?3uE(u9p_(yqO04EdqQV{u+axtA9hca}veNsd(z!-EdXlNKJJ9 z&zDH{-$1Am&ZD;8I;%f|eC`Dbwo#wJ`k~_J7peB)m)!OR(61@3f|WPp(0U)bT3t;0 zx|FwY|0Tit_jI(`vF)2T27_W|2tKbCP%kX-3zK3|~0Shw_W!Pp+1y^L8>x)7adH?4a8W>-&Iv6J-3Y$?c`DcaKEk z`(K3ff8?DNdW<(he(w46z5CxZkk54s2lB^tHM+LAS3-SvfWouB1?`jHhQ6LvizTT~_#@`_5iC=4~TZTgDzRLb_g!L?)e-M4`Ku-vfVrO&wm{PBb5VQx9``+>bIo$q)0 zH0e)>_YhVzJ*dX@{d0Sxex%r*fpHn+t7y+oCj6EY-c)&Pfxr_G0+md#zwf|v-uwi6 zARp+=B;^?Dv26xdbKqZ?@RY{@V7qEpK|zp^?v9W5wAi9{$t~a~AS>P`1YY z1`nMbA0^_VZ-V?VUX#TMH+8$R{Nqz0dj<+>GC=+;$ghi9957+Yy14x*V6V&HU(O`4Lk%KYCZ6iGs&s4A5YE4 z!>qmV@5KxK8}s!)@n^^eLg|m^u=dKDS&!WE>K^)+=G$?~F>o*c5bX0H(LRCmA2wap z?5CW1^f~(Z@VqPm6&4WSnLkDM-?$9+Y!20#0k(^2^Wb6q)&oDwR$zX%q!0KTG(YQe znEfv3G%olHJ~%sO!~OS)>AX~CY0X*8do5n4x)%S$!E|oY7dPH!DtotswZ{}zb1iBcvP!hE_15vfwwDtRrhD$tvcVhzMQ1vGp( z3}(rbqTE; z(yJi6>l7!RQmq31-MCfZv4jhlctCwmsC1ve(?LtP@_}FDLvUFjr4!=Z=#3y#eHDbCox~hmAiRbu9CY zFS!1Q8uo7%~^OOEg zJfNHUU+9kn?Efn2VHU`X8b}Bd(+p{ZVAMsQ>YF{{Xc=sJkknzLYPIN^h;f~)JJBIK7nK?t+?C0HY)>PdQm95;_ zjp_FcD$$k)Mh;+h{d%no;@4Xmw--Ep`6RNT-;N&zd)PqlBiEqZb47=6x14+A$VT_e z!cQ|yp;Fz*<*sX#)dwtQ+X8Y`+<3VTdAvasrHAj#`h&*7cA*EGaI*=%S6MU5j@=~_ z?0wqzmp=`6XHPKIO@nyWw8^ZW-G$ZTC~yG3ESe5(y>}1SZ!kz`+ukZNpH?Q;37tFp zNW9M`@Yk(%7J2Lgz$NX~u)brgL_hkG1l$Q2uB51ffnnKjPN9ecFP#bUA@~7UfLMQF z9h^s!1wYO-Gwj)gFTp>rg8rVAnu_KBce^QFFu4jWhmdYRc=Mu%R(4EQRnFzdaTavO z3x*omXL#}iho3f4*9Q!GGHBw}v60G9{{6%AUS??s_1HhH3`@K)q`LIuxzqIuqg9m& z@rF#z`!`@WqtnkBI3l2`Oh#i}f~WpU*E(&fF#M-2ME?sn^(EZeuHNFIgMMhB#dog6 zx`z+2^iO6S?2GE^f8_2DwoYRuM;tix=5<`(KezA2@$p$pJ<*S#pH|)md7+1dZ`L2% zebH%d71+xaDPxDS?|sSYklPMs&=yA|IS=k_lT>Ol_l)880<;;T9$8X5#{RkW;&v^E!Oqn1!5+cOtir+*K=%}bQORsiQ^lJmotQ0^0SDaoH$YozPLQHUqv zJDUSO#P?eV_L}5`Wb*uhxdt(Ve*gX%WNmDF^0hy0()RN1jiR zk67{)rTT>)t$4*C;jT45^DfKa-a~$)kq0^SgENh%*^BK+`{}DD$d8vGw!Z3f37_A0jys*d zMzNU&wJ<#5y=3cCo*Bml`J&2x>%Nnfs8CpY-162|XTZ8?e!i6N?|x@4=6uG!)}PqGZ+3$yK}t)8pFUIgcZ{qnDW-iMYUCCD`7yG~8(wlz{0K^Gi_ zO6yD#?up>w$f^1*pFsZid2j730^Ej_jzcB`&8Hf5Md)_JZNWp{ak2rsKza_M1#$^}{N2BNK6O8^@7aX= zU1M(E$o>|5szfZybLao6x;F2c2(p{a=5!Xw_qB^{@qcdrYCiZI-hKZ^wQOB>kJh3Z zj%a4I*ZvRoLD4%;&yTQjP!O77^$jw?dQBq9hU}4VLw?xLA^$ElYu2o2>m=I(LW{uO z|I^@IZ^A!ESdZa0Q>+v*4qrDB{y@Hc(WqsF+Gew}R}66taQw$-U!ex-B3rku? z&A2}6pLxE7GkpqM*FpP17Kn#-m;c4yyTCPhr2FHOKp+9p(AG=3w>QyniA69i+QwEx z@Cw^gRt!*r5C|#)R;t)iE)s4kAl2P9Vq2Aff?P!G?jqJIAl|i>?w&?mZ$T`|MU2N5 zK_Gem&m_!C_jY#A|LmT#`#TIDl6>Ep_kHJ`cb>~U&oeWtM+#tlwV}`F6_fH+V9%^I zH@;4ALb*6txh8!C=y^SDylsrrM7h0!YeC-A zsyB1}xSFuA7Siu=ygbbJ`NzlSkQ@G^zI{OZ*CQ=Ll>u70Tuxu6cNm|qtFPML!LPi$ zVd&q$7x@Xt$in)3#Cy;`{;P?PGdY>d58Ij zcWiIrpYi{&JU?lnR(Vt|cLDprS2yks zLb~-GHXD6{u>Y_uB{Ycj%o;&3@^F_N!E!bnn|@eD*Vdj@Y%ET;>2s8*93hkmXtAB56Ng2mE3t zef1?6|MV&3g=$z589!qs>}TQPRpWtwLu$Re(O=;oU!|w(8h^wQBjFCvAF~zr`)AyM z)mf6z)2q86+)8H<`x{m3jT7mpZRw}^1MkIO9t3S3Yt(qXeo5(pGTY@q9@b_nvWNN& zN21q$eG;fE<|5EMcU4d;Qt!c2k|{;?OUyioL_Pq|HkQP`Bn|D zGX(1wC3Gg;bY=Uw<*z0LR+sqfgN2h2e=G36uYy(@YAi$GeK*rx+wBzP&fX%Z&TZ4Q zrus_|AJcep^Q|Mb94P(+1hcIX_o*@j+5#u)z-tIig$1E(^G_{Jy=fxZuF!hi{xJWp zlGC%Xl+VphjdJZlrEK|9c3o{WS3Fr4sg(qo_z=I6599x}MV*}>AEY#lssbP$ND^vj zcvxb=Bc_>Gw&!?WE+|<4fHr;J8e*B!ozO8&Bx?lw2Ts4IlghtYzE?L8WrgsOP@5wg zxqe5<0sou>ct|-g{)Op>vXt*vj)XK~R_Azsm`AU*V^9iN3? z$U=DDq>R7A_+>VFhJNnK$LkgLb0(J@did6NB_kDdey&*N(pKSE467X)5iLsqeQbHq z^2;N2BlNpwq~D@Zb34j-laJCi6}SrgUtTPyZAReEKZZHRK8*;4!~T&J?E~Xe!{6OKV;)q5bvC>kY1E0g>4-@;8~=QZrW6vMf&6Y?ROPk$RnHJs@hnUa-gLH zr~{toOR!INyr$`C8W=KKO7M|28atUT;&6eR(?+MR33Sv_u*dvcpg$we@c7gh+oSpT zZR3>1m&4};ntvbUgU_SA(Od|>9~}>w>HpXjE4y*)x*4e7i0d#j9d;z${wM?mvH12N zGqx@V{hDy}J(hPb>U+!ux{t>%SN8F6ZL= z-v`e$PHgCZnw3z$Qt;02M5r?y>nBj3`8YnME3@^>RkD=LA)8?J`z|bRaDR2-7?ikf z=N<~>*@}*7lE=T+(MwD>XAbfP3UXWcbdCEQS^(ay3G|O(z8GrB+}zw?DgNz>-!8iN zc-KN<_Qxfs@cCvssX7SO6Np$5k?|IS(Vb%f+ddfPvnQ!jt(E(Sd}PC$;KP&tW0M|e zAIH}$OQw(~E58=+E#?dnP2i7#_=6Q8fA72h`u9N={>aY8nP6|q$uIA7_>C9ebhU^6 z0_-ny?^Ku3>&+!}6UfVi9K~5~w)1m-lmhh8XKO&dzv?+zJbz{?1Z)Tp2Ygi2Nn@7UX@70ufU@~mQOdh^Dz`)k#(3?z7X zcHsG)F!QXdOK*GMEOZ9@YakxRii40I=5Jcp((wnGN+ zU+gbvj)@Gj3JW+%ZPSmHF!%kp=+mtC7h--e+4lflvmY&FtG`^FK!HCjsxK1ZGp@1@ z0zE@KG<@0;=0~BRU=#e8zU4-s_zDVL0e#AaQ@@f0zBW5391G(Ykk6sLHD<6-e?=!X z#t7-21Eo&iIS;;Lq3ax>zt1^#c$*3K*KA_j^S6P1pAsI~#(D7Zm*yQ7Qvi$?+>oAY zhic}0S2x1rCHtY!k(wA>4_sqw`#=RT{zs%}cz!YF$4sK&7inGQ;D7svycJ;s5V{BS zQ?zO9eiR)0v+Eh)!vBrrGx`X){l10g(>L)qA_{biTnc4b*OS7%wV%f8cX&``RaZtk zYy!l8&hcmj>ar!r#M7Q9XL)9=WrIb=fp&~fmvf_%l9KoqzC{ik4dO*mQRgWZ{R#*} zBW3%4Y@M``fDWI{}*6^9NJ`gu#rM$ zoi9Ug+qWwM2IjhZ8}SVxb#K%t2JVa;v@>&WPRMc5ZUcI7bel89mW%BfWs2;W)LR65 zX#Aud2fFKvhm9ln*!kt9vwuBjvUPlXgm$x>U4EMHfwoluHCx+gOF}b0PAfSvIsvh6 zg6`mQo_hn9hpH0fgyNySQ5WpDurJj5M!3-5VR|Oh-*Eq1f#XqjV%rdm-+=x>+$ygO zH9w-H{x>z?Z}TYaud4&v|CrxbyvRC!xKRe9y?)lPLzNh?!PmWFWCz`3{Ag~hy$bAY zl{b`MfxLP5;##yM*N^Y%=@kWU&-qJ^6ghzYnW9emZ`haoxL6MSaSA$YJCYCW3ju!0 z-U&X_`M&7+o-|l5`}5jO=+~le*${6oL=QKr<73p&dM|-j0oGriE_CwWP`$)nYXm1Q z?vLCAubKMk=q1DfiL)N$RTNBc_~ZNiy)a9F`R_{5GX8I+ey}&=zuxq0;ayLgeXz;= z*3*aE?WFX{E(5%Z?LhAiS8HMa06HoPezWtSiF!bPOiEp;^cjZz4f;`MCD5abZ+$&z zhnYbCFrQE)gW;mlXTFbVHY^je6y){Q+FIx6&w#>V{?VjkGZY58riBI4!_LZQrwiFE z7W+Fo$c&aB3@-=$z{|^E)&EDpN944x)YHkauQt=C#K$%tfcrCk6 zH`#{3{=Kn0;Aa~iVr{hq*C#)rW41P6D8*h2wih*Frqi1@juikI3=A-!}!U3eo`oYdq!&NQQWpQxKnK z@$8WM9%8rVs=3v3%~9(bc-G#qGZ&!|&@Y`vD{$ZMA0r<}FON#0PCxqt`A>5G-~K)d z^M6qV%sl|k$E)pEF4*~F9|y!QUkvoO_gZK@?O4J}M|V6rL7z-X32Vl!k)#@+4?+J@ zD*LA6cX{ZACcm$?%ehKr>PZ95??=tg$5d{FH3+;WH)2|$!6W5e9;0w?gaADk7(s%J z|J9c*vjf&=M#1?>xbGuYp%a?50zGP(w2|84pOdMoUwHqCifxQtGq zISm|pbY+*Et5&ClHgSDfuZ9wUe<_Q7%fWtkv#ELg6A!Te&D3AtZ>=tTEKO(2CN-q?(jU?G zt#)sri|{<{8&V@Ltktu>2_2;Ghs(LcAKYULhmg8z6;0a*BIIp2L1F%J7G?l#gLrP( zFUEVN)ON+5UD=+YFL}KBGK>Bpa16S7mmfDuo{hWr&-tCjITg#HK00v+LWPKSFxssG zeyR*pCxx63YUO{fKz0g0?umSk>QUcy`XWRmUCb?=_PKXmf1wbxp#rSGX!&7bmf;Z? zuRTm&AN1447|)qiE0u@2^p7v^Sdo#xi{hqlrcHb{euD^=fj$`UPtf1rMxf|e)42d1 zA@IU?G%D{nm58H}^V807k=O371?ad}r-NRiuvZ|tfCC#So zM;tmFOnWJmlA88`A{H##13#myp#N4+uJ`)J*8L%#)KB_$*!+|9EE6XRba-s+5bvN2S@9I4 z^*40V!-?nR+?ukPwU`!gaxO2M9d`XzWGl>V!tuEjH-0;W?}>dZNv5Fr`~uA1YKzv1 z>qb7Zum-*yi39zl+FQ)%6R}5ejnF$+^|dk;+3r{}wp7MdRmLxYFzmKWVV9Oo&U@^RshIku?4G2U1< zH-i5O=%3VQ{QIAz1&)G82xt+nr!aZ4&7S)VEIM%s&9jG6QlGr!?gHyC(d)K={+PTX zvPfVO;!sBhw5rgV8O?!Dwid>SM!I#=C;wIu6Y&hxHB)MEfbgo^9%wH^k#;vZfs`xRg2(^j&q;Gtj?* z{zG|eh^6+qb?cK#E?|Q}i|Mzn)p4rtp&T`O{3P{#ntPMQM^g4`;5SL(E0tOpOMFZJ zoW2V2jV@U^RWSZskL_`na{YvHsVNX|Gz=DWkf#tokL?YcDO!owOUp~wb3=^P*>6HD z>U#LIgXfKagubJO^$=fy?bE)#Rg8Zg>=ywIli%Xg6ymMJhl z|7k8*2_WD~Ydi=3S7nA@H|msIn(4A?h=<|U*Rra9fCR{4>WR7;@V+9#-l_c5MD6wY znOo9<*C@m*dTko8IY@RW%NqY4`k&aBu?0|WEj|A6f4Bv1`mDuI9{}=@Q>#>(=sCmS zL3~i1OL5yRFusS>)F$AcPf&9`s1qSr)`8Azg7t`}X%D6xw?Y`8uT3uXZ0%#>ze{|M zX+3Mb8ltSF>7QBN+z0E4arLJ#?K^{fU84bdh5@p9&86GIQ^;pVFvWdR_P_w+ciLQZ z*4a)$akpu;idIiA(BP;1$HD*#^@L1$U4D1~Tn?rgVoeseNTg4-8-9i6nCFCmJ&b#3 zl4BjvA4Hs2dEohAnQ`s$AU(fDzd}}j3#Z=3-(Yp4;Wg=M*q?e*zpeh@e{alI6c)mM zCM9qMST&_`yPDe~PqJ((hVhd5Js2OzxlLa6{g_wa^iHjPU}j2&(uTm_2JuqU?nK3a z?6;;j%d=*O(Whvvy_7&aYZu6GWoShBSWlzy&oPr4P$M3Xt4**v1dN5T{*lkCS3W=0 zac%zGplRRuQOM+=y)7NuF!~TzV zyPna*N4|Be278cs_(_@t=pT=%Yqqz%X0G#kkXf=TYXNcPsG>(f7tN5oktaR7R&+aaQt&-e2*S(a|VrQ>A-z*7;o2mA^B~* z#yss7tL7?`y0jLUyN78S)*qCedTkO}-^}<@d9+NK>EwZHQ%ke2O^5Ng6RNbXUvkT| zH3Rm)^7MMcw*E*TombJNVR>r1+HT|Ww0Uh1-mjs$jJ+RXfgNcl^N&_T{ZqqJD9pT0Pgc`3Fwcx3~r5O$d$$z&dP28SM{rZcvJyhwTQ~ z=5)~a6JY$gvDyi8OIbr#X$m>zA+0|C6$Bn(*m3az&}UR?eAiA&y9s18c+1YbmU}Qr zO&^JSFZ9_EeKx#<>KRyo_I+WS(&DlY*2g;4Mmy|$E6)^SqMUUU_u9x%&`%d`O&DoS z9m!jMm%6oiXjV1oF)&*KRY3i;N@d8mw0s0sJnHX;I~=G5`97M~B%e`q8P90IzUV+N zh52>6gs|R`(RtXIbV2|e=cQU$PxVwt1lq~cL&QU~J3PN`D)<|mY&%*lNtdJ4E(r?Z zhiruL!a9lW^fYCgW$<~B_bha6ogK7CUn{lCZmY)Aj{C#KiQ;Gp80 zV=Oy&P-1LA3es}zXM!m{WO5_JyZ@DKAL zo?H(585Y6#f5hrBo29!S?!xoxB=p8h*2s<}!+z`DpdY+Jb8Bl^0wV(~ zaw|-!?4YS<5YwW@((6DiiHG}Sc!Dl3pxmX&Y}y7RgjU+pyUSkz%UeZx?pfh*8PSgR z%PI6ye2^N~6sLP0qyD32uy6Rleo;V=lZ(mq@O?ec<<+@q0(hT4 zXVLcV+bQmfP*p0ncA!rY^-?(n<9!{Y@B5rzodC-LGxHm2_GZtn9EkvXpDh*cI;$AY z=2h7PFXDg=1EI68VS9(Qa@G%uD<1U1F#Q>a!4A`=cht^0KR~o z-wWS80RIN;-!<0IiYzRjO30VgHImy=F`RLzK|B_s+pVo_!9PJDPCt&_A&?gLa*-rAe@c6lE3+M~i zBE!ornJ5d|6XakI{E3~Hx0ZG~$;R=p3aD>f_@A+NeTx1AOlJj#v58~lrrI&=u_Y* z+J(SxxJ}mUfq|st2W!&gu%BZRjt4Yt*^F3SIa+ScnLZ`g$~zIl{0-D4hQ5)FtnL4D z`%-__KIrEoe-3uM`P4oDp_|V*z9T;{!|ahG!ET@rs5gWU4H)myG4$=G``|yl_aA1O z1M!>QFwVg5Gc+>dug4?&IuKCGk}P`|Hk?J5RqI1G@} zSTlhCM1+K<{z-L*kQrhY3+}66zlCyfc`MbF{np((mT#6L?pMcOuYSe>{hvoWe-jS& z1GQTHC+Fh>6uMsy`a4XQ@j`m}&fza$y)lr#x_$Zr$3=Gk{@k;l(TfwJ`R^Obiy&&^ z60WmDGKG}pYAqh)Pk{0GS)e~|U^gMR_>a>|;(-FWUtuyx;Ah|fjGzrMyEp8WYHr-u%U z66Ac2`}$OC{PSvRdH1epem2`|fcZD*&VAhMWo%9Eve>fh&J61VCoWocV3jbt{;qhPNE%0Yrpc%C7*+{(w!bH1lZ=IV1&9x%9NC}=8~Q6Nh}}K0sYgi>Vvlfv)$SuUhi@) z$n%p^#GHXl+T{^XYS+6<&AswY>dN~xwFilicm~LG310u3z0_I>HyHnJ_}=)Lso4;+ zfIqYO3fTYZ#bT`gTUviLe<#R)kB+y%XbZH1H?RfWKL_$|vahdrehglJ)y^8< zHWC^dI?`H7qg4oz?GJ9ut~+H?=_)YtVqdgZ#Xg&o_5?E>`tM-@LoVMxTXaOuq&1vEUD8B?QE#u2JpI!r|}l}-)jLmo?kGGwl3ZX`e{@pNU2|Sl))L2#@)% z)=aLZ?2s5Galqau$mpC*oBA^cer)1$6XksUy#&~RQcue*e$UQt1bLa&*N=rctkC(E zTY9wTth&~B<|7V&c1W__wls^!dW)f06*~g25<=CgSJF{FBc!dC9IEz!Jj6` zRVtN}|1`sP1Zx|Rf9@~kj~^dzfxoE*IA}7OrgxbE`t7P!i~R0#eeCdg! zIq-ZT*N@lSWW$W$ObhQ|xSG2`(Ubb0-045fq98Yam5!lre7BGb_6(L|`W^huAL7Vq zUgV;<{L25ve7-Db=e+Of8|1p<-{URtUul7FfBY2we*5Ft;4H32qlx~noclMG^|6)0 z^JB1n+*^Cm10Pug@>|=iRJO7{Lhm9U#8QBNFh>tMkhyq_$rM$f_{3NYVE#-5VGKjrx2?~^FF3?8}sqmrB}|K6qX z_-5RG_`L=h|M~x93-GT2HOr9?_LqUZ(E!%QB-hR$SWisO4b}O&9P$K@WGhTch8>sF z+A~9ghHq88oa8VY$47ddC>b!aXMjB)?3ti{eBb|o$I}MoJB+@L^>d|?b@iXM5wWzN z>^sJ3G*k9wHwL!=0^{>$ad_-9z7SI_#tyV7Q!`ikd!TvY#n4T{dH(_(2<&A7E z!t8gG5Pmg6m8Yk^W& zjr$tnp9A@?6N|zAUsU0sh=KiOqxt&AwhSCyh|^gZ-{bgQYPNi;nl&4V11PR}+1oHb zf+XtC#_N*Y@$c~#7;k}p{uY>yoeONf1NIL_s@`1$HHQ7$cAD3K{0IA(Cm-##?vaSs z2U}@@`8LSzbSunPD6EJ1#D)`<2-fGAh~7eh}QBq%yFq#?nMRCGh5|z z@qGU9p#yy@I?NVqBoHaI{GT{g&!XR|v!8`buVedQOG;$N*n?vW;J80+tUbnmjkmx* zQwvz>Q%-7x?cWd&2l_lo6#n6grXnT86w7hE-_>$oof%^NhgvP~EZV5{Y^bff2;KtP zT=E-lmZEtYl5qppBHIoe55T?<9tP{BjHmGy7;k}px)#9kZ!3 zfxiF69%8(@f9Dpk2D8I8VFzpeWcW^oAsznBg5MFNzoDxjyv$8POw`)U%w~{zNRsI(8o3ycW`x+)}QxsVikI%QS^I6pkb39Q>2_dteLCX%9Zk~quXn<21KCupe>5k=Eq&(%~A{HIS zYxu+dlN8v$%d$7#?8T5?9jr$E`PzcMt5!(g*RVvJylX#OJKeYz-!C?hLZM;pm9Ek) zpWzY}Dmh#~yW0Pi&p*NVkVX&K+4(uwIX*{Tp%V^3e4$bq5h;}JzT8EZ)7cQ8r_^P2 zE@IWPXn2Aa1>{GD4dUi|&h?Wjgyi_ApY@~iG7Z}>;`+N_{I;v{BR#w{z}EFSSbiV< z9%j#j27u($BK7SyxPK|WU%AVU>8>?P=y0R*8wCZ=w>%2kpYM0#0-bGbAA^`L%B{jJ zvdACul_Kgn)(5_LJ#nrIU%y!^tjMz(4DRFhuXl2~l8>K`rtyBuD|@z0m?LVe#@G$N zkEj~-ygxyh7ipu-@NRo`oRT_diP%{{mz~3)vcN#iB=#{2{Ut?nmL%o=4kXfY}^E_{qW; z(gxd??Nf+Ogjv@!P?GgS*kjqtQQJ<-f6!I9zZ%56#ySXU7$Zc1--*E!LDvq zxP3yx*huJ8{CjARE8uTYDr-GNtdHDqd;Z+oKO?W>3%~umsrH_&JyM`1o#>hUpd4zZ z)J-;g{sC@H*nb{huWn}H^GJnE9Ans8+&`5O5&Y#ozyB3|j9!Ydfs=dTr*`|Fzt_LnttkEeu? z^>X3HoIJ@>Q?IFl`Psmi2x@1?>@p|-RnvE8%!%vxC7 z+m^P*M$71#HCA4Nk37(ndNzfJNuc%2Cg4|>_nj6h4Z68kV15ox=hKm(-^scrwiRD4 zY8OPN_Au=M>t#wd)lQmV&4iD$0Yd59Y{cwWFk?C5g@%mOk^C|lFD5bxJE#oL*tPr`0zx9Ri26l!b`1GXhzVyv_=Y@~P;nuB)b%pN*W zhgpd=RRpNH+?QV3OYjzzNLrr_uVXZIZTvLdnyH2B!QPjn>n-AUlTS zuZH#x>#ude*x1u_?LZ*xFMZ@%2+3a*sGgS8jJW$pd%zkM;wn!Js-H)-Ti|(2cLmVj zR>*kuqi_7aM$hd}8np5ve2rDXYJtSN-U${%wx;s|?bE_0emQYZuMJm(tND}X%$;cK z-5^hLC`riIU*9Tx&z2vTEJvZohlZZnAYBoxc4ehpiyVbC$6xQdS&_~3*IrGtU}mpr z6W(k22o(BqBTL}4wUc>2-vnp{VQbgTBIiT@bZt7-r%oTX9T5lXxrRuc7s-e9W+j$8kO+YynJLQ^L-bdmh& z{%Tj#=1As!yJ%PvvB3cE*J#H6;KM-Q)X7^+dH29(fm%KoMb38vdR(CCSVu6(N1k>^ z!)WV5e^#+1MW^P;NPgT#t+bp|G%77H|M*6+_Q4L%(R^c^YQ}7j<0E+eIY<_IjjB|r z9&F3BLios$%3ZrHJ^lncC2M)7yx*8k@S?6fXK0Xpa z^4$XO^1Wv{G4&}A(4shXyB_D8EUh>4)8($7UiG22@+R};^k($xOKuIL=bN;WHHn_Q zEM&tLS_JLW@$vPJ(R_L_w13U~Ib-)vY>~Rj+wYKkwP0c3T5Wa}!CmP{!t-im=I>S& zWl3MEj?X0d+mcTUQ$m$0f;EA9Vc*((=KY;h$zA(3dWz&%CEpbq>dV>*UJCr_`l}u_ z1XlpPHB6YeieTUml8z&D7=E!-l_l}zR2SR01x?2#%DHzMk5@u@6X;(hU5TZ_@FyKH z^kF)}T=J~70{UZklHxQ?>nuLz*^jWia2D71!^#FDQ-+=&76*f5aQF2k_v=)ho*h;F z>K=j}VzZ>2@(15w-iXtpz-I1Ek)+UuX!?Ri>pGm2#L#noqE@&;lR%sHn5_ENInZZT zM(Ic22lT(-c7dH8?~?8OTeN#3#rrTN9O%)Owgqb>r-Y(jg4YB`3mxp8nEg-=X*#4| zeRYEAUx$KorJ1eF`=_@CmkL#zzhme>N2Yd7Yf|kb*X!9BBkix{Y$rH27P+$XuZ}() z@Z-dGA(tceCixDD(ZZyzo+N_Zg6$+OE;)=mTd2^3{dJIezv4hW4;`pt-v6RtIZqpt zdYqh}7og{Qaa^1Tb_@0s#0@sb6TBo@A#j$=Wcq(xdb^WqKZy(Xl*M4>VCc!^V zXNjUx)3*_vI_(@>yIYm`| zMlJmgtf$*b(;mR@lVUEz2A*o#-%?G_kiz^Gj<1;9uRC2RG1U9&2!1o~u0$jnX4c*uvERAhegnd#u0ZYy2cfo`l1GIkvPYkaFGs!@VC8yf`>oT6yFutM!JBzq$pg z1z&sAOl<8ojo>opp+8>RTU|#RzXXm>(`$l-t}(|NaC?737t$uIPnQKHG4ysWSSuCJ z7cu?uLNFz|owk9Ichw16*P#Q#EPQR8Rt5VBr}CWHHbm1)(oaiMQc{@bd7$c${i@b? zACsTx?_b~g&TR(APWP9UNumgLSkW$&hh2N0!HH-dFTaqX|3j)WPvssyLr)LW+652d zQYQXObr?o{2NlZ><&j{T*(h4o)5qO&4y=#}#YRgq? zzIdJ-aVCjgT{hfm!3W@aX!0ziw`Rh}IaRZ8?_fd(!FkYLJnHvEyn;TBL{~B+BT4>w zm0D2M(rzSpb+F#EaBTx4Ke@@L1;&+;%_P4dktNb3Y|0=FMDex-?u?7a{L7jS1vd+> z9Nx@a|B~jk@bl_2=6zgAQ%I_vtoq3J-860KIM7X2~RYNvzt{a{a3z`a}9fdMdqrA&f5-L18Ip z>ED4z$wE8A`{uZ0G5o1}<8+C(rF&F9T0lS13Y_Y7cgXe1gjdqpXb;0r&Z~s3w0Mj^ zo`dHfdK|<&@1j8TqPJUJ3pxLcLMym+0`|TdO%=hVg2_K>DkS*4s@c_&8IMWVntIaF zi{Sn;5bP1#Aw^|P^#pfo6w>ofcXOzx@Tl z->9;r_twS_61+GtT2fQi5=d~Ih7z6ju4m-=(nKVJ9>%=CEh_Y4WAXlMQvLwQv$XIe zir_^mtzedy?`Z!uZV~xYs8V0e^k=oJ7~V+rT2da~KhMk8OH1(5M7^sPjg_z2C>QKnR?&4~F`-c4xU(D{h#k8NA_l;tgM>UgwUeV4iEZbX5&QC}bNsSAPp#=9PuqB0M#!Unl zq$6HWc0nJ=ClfxVMV$(96-Z#{+c?eemm^P!2O|&HBsL4nH)Z5lGvNd1Q|R<=0wcff zfjrW7gawg&dw;b6_4NIS;3XG zex-%s4~szGfBcJKMqfAx&$qyv!|00+5q<(=SP;`+;*=xWFRizYd9>LR>@QJ~5si31JTnx^gYi=i&xPSXGxI3X)}o?Xa{hUh zTx7noqk`avxzSQX=$cV|5!-Xxg*(dY8U8*~BNrTK**rkXd#Ln+!Qv7F!3zT^Nw~A~ zIV(2Oru&)$vhYkf507Pr>4)@U|90W!!diGfTUr<>=fz~RWdwKSW=RcUgFOUS!T1pd zWK8?7Nz}|M<2Ew%X}?168WeFYYO@cIa-?`L*fy6nmBZTM5nq`BIsavy$MrbUXjxGevms zLL)x5B(_-)6Kc_u{P@I9#U|ZPb`!itqZdS~QyBf_fRtiQY75;<@++W!cBHM3Cs-5M zjEX~C|E1l;o{2~&2Kudp(a2LZ`>$>U#&Mb_ZWd6$WcnjH75T8@b8jde?lMh(%2Sh zeL^(DKdOMAZRn_BZABxIC&g*a1<8JrD1+IL zV8>WWv_3GN(dREG3q>0hiWtZ@VGg$x#&f-C!J0?(YIFQElVusyw_iv0!-oCigYBg* zMThFh^~zOxN%4_wSf1ehEKH!Ea0}v+9>Yj&@5|G%2cgAN=>dCO-Tt6*VX`#%F3>mL zKi2p8#WPOETUz6-_QBA0w1EnK`Q=w$h7+u*7qo|G^fkI5UCk};&SIY5JvLj~tJ!FK zA8=ETge+licfJd$&zi&z#pK!@jDB}9AxnCwaUR2u@{>!YeU^xgq`W2&iT0W2j$u)0;dV_(!y^ z%KllelIwXUc6iRIOk(607v$sM9Ya0I7lS@*+`S9;A8X1Bj+X4^RvSklTu<&wEeK`m z$4x}8l|59J)!}N5Zf2E<5kp~r;Y$1!4!_cc+ zY@Te};GL+^%hBfp)UFrRag2Ok6|3eI+T|CKe3yN9Wf=|b3_aECqeR1*p|?oB2F8nG z@86XXToEXgwDhiDMzC9KyQf}X$Xu@{T`O&gi(>lg%nZAE%a$c%lJW%N7=osoKQq->=btD&M@%=HE+_@5>7}?dlt% z55f81zst{|x4>$YV1EUBYrS|9o7}Gp^y~J)_7sBc;e9#Ha&IKKT*VUU@0l2Rc^~Yn z^5#^g|4fh7x@P5xXOi+@AC~U@@vMmiJLVw~9r+dTFI&n3dwarfUu53Ln#9w>OV!6e zCi$Mp6z@=7H6l0<`12Fh(eIt_C;3Tzu@}i-mWSr;Zu)@HhaZ4G78a4g$n*0m_PinV zI-}op2C8{oUFsxqzP&%i^Af+oyx&EjuW?aoFv<51)WZHAjSM~L1?_0sHPDYn(~=0a zsMgz)k>3wg(b9PNzpW|NpVYG=#}t?68VP}t#rnW4vX8svGxP{WjOQMC(BrewZP$}bAk z3m$DU*fALVGxvHGq&-ZSzQw%kD>&amzh_wj^6K&65k}u$6{zJoyX+_+=YzeIo6_c; zP4ImM*zZ|24E=e4J?>6h(-|xChC-_Y4@jcLyRMV+i-NOd#$&Rt=|{%ARw) zkt?EbKGwO;DmtclyOdgOk@y(ypONdAHR0bl`GY(uEc~F~hOmDd#s_x!xT{;!;v%_Z zPgoSAU&IDC3qKsV?N9QzCd;K8e^Jkrzm(e|%ZPgWD#_0YE|tD_?okQBOLS^UzhUl} zex9h69@~5LD9O(Odbqq{c?ZFmUU>cwbmTTlvFXbH{QXc%*5?7MR>ocfd z-4M2KIe>j~{*o7_SdTMiCq%j0>)ciC;zVq2LaMTjdHl!9D51z?T;onde!& z{ft!E*2av7?`uv=kN*zu%wbCvfmxFHlsFB+vCyB^WQLXzoCo%>Zxb{@1b0LKuIJ@2 z{QbV-wBS})vJ1&y6(|(3?RZT4d8BtpO?|f)ko@xr)Qh(pk1+E6e1e~B%CQvY{#~jV zNlJ<@Lyv*Ua)}rH-aF*{o{f}9TUZMU@IFf;wBlkIztzp%eVpXy1;$9SwY4n-Zvp=f zH=E5P*glXFxj4EVBlvu9mSpCyzKSBa6Yk&MtYzqHE6B$}?}j8oWD2_u{s*(HW2Ip?YZgL>kR*|2L0^=dWhi{Fg}y~5aq`^ohxRCi|^=1B($&IS5SHBDmrORh@qS>G@hdXgXNXl0AG0D%evHE&~1Y zymU%@JqwJF3(}({J&o=R|K>)BB%3XjC&~SI&|V2?;Y|Bh!1(V*nUjR%R~K~zeBMy+ zOR#&Q*1xiH;vs?^lPSro-UE#MT&rsr&IAV)$)CC3Ke&y4dYEAMJhiOeeI`Zl%-t=r zzDJL&@&+H{W%xYE;ue6Qpd#e#-A%=2)f0RH#=C*lG}FHiY2>a| zbrzruTXKW`lw)aYBRJuyP~vrJOdl`bE0leg9>?%sak5;Nb)YE0R=yAWGX-Ts1q?rt zRIujto9&qPh|egM8rlY!=UbN1qPQWEF#LA=F|{-@GW9aKf1E}uD54Ea{qC!>Wdj9T z=6wQxmR3uC_#G+l5$h-U{P;C9!H1I%$lRZ1GW_5I*oW(bRZRa7r|SjR?s$8V@{3@+ zpK*rE^yi7`9m2e#)(=Vk7r8l7asMXf`ip|~f~5&wbJyLU_Y?+kMn4Hc#kyJBXz-6m z4gBzFCD@yegFqnXR|LzY*RSvW9{s2#72CLD&yzsOLW>VG{=j9rGKIl>g?avq$s+&t zSJpB55+_#6jjC~G_(w%@J7suOz{sof>6Em)&k2$HJ7lzbM%@|nf37XE3pCb!!t~z> z6{WKH4PhaqJnXk9k_{QH^oNfvNfgfWs3{wL-(Wu|I<2U_5y|v7kWZewtM%37{B9UO z)Yg_SAh=7VkkmI-6aHXamaLY{YI!G5#ql#oyaV*dN-0)4;|G(vTs z2Y%t~BDT@5$#nBlY%_P(#Ogcb`dgDrWfcWX^FaKJURHO=h3SvgMU=!6xjdYdUkd(> z_wP9661+NCDDZZ6t|C}3TojnkVrLWVp3yGo+SETp@Y;Qp8G5PG`2{L^s~bsv zce-5Ap0xQs!85@B+PI*}U<7a2k=}imrh`8I`pLFpTmGY)U_av`Z5i`CtAh2ePR@5m z^MRifHA^Hj=UVwu-B8pw4x1=cJ-BNZxqer!UYgXwZX$SDQEXr+{WOJOZr&MLxoJKl zp9^#%|LzYutk>Ue5e%OP{j8~OuC0CHr2FYw$s02(nDIhy?dgC!5ur+uZ*{F6uc4(n zgbJ=iDPw6L3x5slZ3_F7aKzX?i!H33^ua_mx4AuwkzWD3PX{l%)L^YYE;A$6Y5&0j z3DZAwz#jO;d#O0fn!3QgSt#~m+Ium)->MGz29gij+|=uV)`69jL23O7b5y#sn*)nizflN-gW< z<2^l0{XcA!OLp{ZZX@O0Ga&wGWfaq2w}Afeiq62i@2#p9VYs4$(cji4pcm=lvR{+) z^NYa$P%mcWXJCa^F??gpf1M@c#FpzStn$J{x0<(sede*gx&f?HiN>i-ocqvT?MXpl z_DqQ7_o;sDH~%~;LqA9F=79Ygwzo9Y4Si{`fJRUrtmeM(I>qq2)v@e(&K|GdVCttk z5Rm^Ubbw%wBki8_q`A6LoL~?7;K|032pb-zD>UugrpAekzH~kTNuT=lG5qWT=%f0= z&#d%<&p8{=Ecm*3LD}f_=+&TabB7`e2^ElKx1A61i0FlrWBgnk z8!bfMQ@gAu!^ipLtiXmjiBSYs6@8iqaXWkTOHm&I6gQoJTjEc_j&i-gFpKkpxfAYZn@nW?zCY=t`xb2E%oN^WpqREn?cw&R@-I5Fgi*{0j*p*HlxT6TuD; z|Ja(^(L(SgO}n7BYNj*6+Y%2d9zTBEKya0&MOOGZ9eQ{g+5gy13MOo%2A9NX`U!1) zeit6opO!%Ul)`D^EOP!bUGwaYA=sULG$GN0^6DvjM(fvfOe52BKb$m+k;mY_6m8nn z&Gfg*TqMoD(Ztl>dH*-dYs|ed}f9eh`K{Baml-aVP^ zzdz>^BQM+%wczh|=FLYX8r(o);5VTmdp&laHCx~3D&XISPn?`GQR^OjkLi%VBBa{s zUAotL%Z0&m2w$tD$o-e5v;EUP_!aZM%0WLYo-$!G$>&703kT*;EGM{I*Dl!SK9|9g zyp{k}>Sad3>&n$i<~G$elJY&U{6s$$sjmCw@h~6-m+B$ZvlJOtf<>11b0EavhhbHQ9SokGWhP(U7feR2nx}`_YT+bci zJyrMET9Tg+!Ih{(v>2G|D%X&jC=xq;jcR3#L(YCh!?Zdsu}un zk5x;m5$_5)f3-?4&5ry)N$}d>GeQUtWX8YUnlrMAPa_$6T$(&x(H5m`C*>b#vV;St zycmAJHBk@to+L&eb&L>-Qo?^dDnHjTmxIBf__~bwj!Mwb(fPTZsdV;s8nmt?N${6~F{SGCkP8|VF#+J4w>m?r_ z|H?@4MX;|_XQ!kR+?lR-O|72E@EeZ`y`bMT$UF~kw^lOHHWn`j{vgll><%X19{TUs zZ42g->orRq^CXgZ=%2yHV3kWPu# zc1xJ&Ij{1QHgGOpB>9IlYzf`h$LMowp#NutJeH7rZXU$nxU6T|XKfKR@Avy|{Ep-w z0)7?}7POz>3lP7P*PqI~k8R0X*-sW!b(8!Guy?%f5r3NCDqU&tt4~x6ze?QAia0T8 zEWW8Kwp42Qx`FAh9-Gcc^r)_$oZqdpi-?Q6%IF^jdC~r#PcGg}@&k7J$9jJi$@EW; zJip+AthEgPs7h~9%w1q+?%%1BOKUA-@vN7i{b2toXL7ysIyDcro2aFYbR(T^dTmRr zoLi8cnl=@25E}?u4%CnRf8VEV@|)}!XT^B^+GzBPEYLrp_~abRF{^}e&qJ=_+CHZJ z;*!f`9HVMfpU3+1S;gzSNB6IXN>zY8vur5rYf?X_B0tHoIZV8sr6QT-@BQ>W7m~j; zj~(&8IjfZ52f10ntc__e5!|&8`EPzt&F~lAek3v&luUoTkgS$1DBeAXl#k16mYy8k zJ)hvhBmRK~Wt2_XhlAqeW?@f*{LHE0Jv!uOTR z-w@oZD^-}^X!0VsB3&(`Q^xf5Wy#U9jWO{|e}wonq5JKXACmH25HEcwRO$7D(*4)* zF_`~@lx^4BJ2OU;_vYXpCht`FV>wAoU_d))@e3+#>nlSBuUn$`KCT7H^8|7q!J~X-9*I>K9nHIzKZ|UGp*7S&Wk2s)+ zaPHkY>C27|m6&{5%M$o<91T0wDnos{4zohUHvUN_U`xaV-el;~hm1bZF6d4#;PDW--vy8-haZ2$@TZ>g zc2DPC6?47w>1xT*-kwxaelh6BPe-Uz1RsES?t<$S!(Txkm!7NTRg-+T2qc>6txPAl z9Q=oF^Shb%QIzK=O~~$Lt`GB<1oPh*izoHWgLvVB??!tE*k@q8JZUiE1i9XYcEEX{&@hf^K{88s|{1Jy!&h!Vz2)U%IYk+Bwfc*;t-Nj@5 zWm{fzaEBe&${X=L9W#W1#k4|0?!PUM3LMEyJ4O7LRl01&hO}X(KWHTpU}fxDxc3p^sP? zFIUeOGx}Ghu36glxQFRKm(umJ`B53n`&tr9Nsfue?87ksPAHkWFPz-JGCfOB*~PFU zT9TI~>8~I2UsfbjqKUI^W|Hz+S$?`+yL&gmixab?Uv59h=tGMkeu?AW!1TvM>GuNW z7mvlWuhC`6^moSW-FXmys@_(~$V+&BVXb!qb3cd&5>j(B8GSe(0$zV*PVGn@|}zH(o{=?7HK#n0|BCWPCz3w{;#+kV#PByykRM5A_Xd*Rtr3c@ z7QM>QcOZ;UF7*bqkbLM*JZ~=-hTp^eWbf^8jt6n~S!;Bw5Zhzb5s{}jIZdtPFKxp{XLG3zW$e>$%bNx0g0n?A6EE?J!m^EI*+@y2P7<}GAzv`5G- zo#zB}@N??-N%W`mpX@UmG zOOZAmq>XKjK|#oLhKd175F!cQz?LeGlxqk?xk&B5YqaZliD1sRe^}&b(HyL zi#QfZgmM?s?JO0MCyVIXTA9GEq z61i6mu1CjTR|ZC{v6NsOt7S@+a#udhn0y17)bVn-HQ4$(C88i&d?bO*=JOXg*~Q|2 z#{cyEPp=UEa#Pqg*gmjn?BM?)_#Rs4`?OnaTF7_OpSy?0`yPnLa|><8^F?)Tt)QX) zE|$NISzv!DaK%40H4_0k-P90zzIvEH&*i(;t9u~-o^4qF2Sg82sfCMunmZ9*0`v3e z_qwot7usSG^-?6}_v__i;qVPbG3pkFFOa zWh}Rt`X5`Tij^NRt?qkZe$gk<@x}Ah5Lom_VZ7D)xctdpDlu8mVA|SAv=Xgu)LseX zKcprmmFTGB)c*_l5a2%|3^Sl#F0^{3iKk`jvAv-l{FkRw`>_1URAvZM%wWJX!*0xB zl+Raj1koVQNse}7aET8<117{g;0WtwE;m7hPT4`3{2 z#xUPyn&4@mp3k~Jo&A}>{m=e6|Ks&Rt^ITAbLu2D1ol?GFMN*$4eBe<2b0UXu>Lg) z^H;df$Bq;WetLs2i6DF~4ofW-i~b!vk=zm!zGeyN9ep|THT|BmR|Ts^O9mn+_@pWHx^-!j~c6QT~(vX9dFE2QJ6Gt1Q6xQ!eKQ%(CaO{*(#+ME$*i;}l7~ z4mAiU1rgHBX0sE=fS+j@;-0Mr|LP#VKd{dWGwWe}?g<`GXdA&loL=JhO#Ho=95ama zZs129lovwRzo}FTH+8NsIC1CGOt}K~PYPLwHi`Sk!g_bh({-5NHrFUf?>1R0I^UKWy$HNlW`wn{{%>YK(}A!j=o1^F z&UYaU_C_)#$1;iVjY8&f!-E&w5N7Sni0Xp?P=smwdT%(BiEs?q69y9B!{g1Tj=4Ox zWIg8Bw~uTPK5TWx`|Zv)#D&&)IrS+xiF-NJQ?g_AALv^Y z=5NLKzY*3)Kkbw0(D^e}AfLi~aQ_i_pCtoxWefB>EQn9po-(>xA+S*2T&N(ee zBQU-b=e|?Vp{7gJ-sp$*!Iivjxxu>;-~Wh`5T3i}NM&)H65_RmIiI=wrOI4FC|Fu$ zMArj-NK_x;f%S=N<)*-}ec(^FLir#o)br7aiHZDK;Af`<+FP)gOiS5%3utEg91U3- z-fd|u*S_GN+*C*egf_C-==?0Lg}i@=i{)E&vsyH8H?<$pRbYS4zi7nw6Oyfw*oqe9 zi<#;?A+h*Zx13f`1NNq-ozW@@2l#zQqU^AHHS=A@5@L`8b=@F-?9UkfFaLn&`w#4& z*t@B|M|@bH@O2L!p!=zlyOC#3p4^Er=xc$g`Dq=X4;Z21MvFuQ!5`bV13%fR`_w_3 zQm7`Dy7}Vq?E&`S@Q4@F(edm$mDJc!?Ts)Y3@Oqyy8H)0T0+==uhtjQmC|R(s;xq8n3aYqyCw>U7`Y; z43BU3Y=Z2Q4PpP6N~LFU^)COzZn|E~U@4X#l^}1*puiqFANccy5p9pn2#>;iWYj23 z2*Y}oSADZG3t^ZaIo&cjwr|t{fBn9<&7uF$ed@rT@yW-pHKYESaDOIo;V8nMsa(?c zp)Vf)A>i*ylDpznW6QhZO5aYz`o&8JN`>Y=suUexU!xWov=eyz#X`LGqVLEz_EA-W zk==mxcecUw`b4YtHpPMLycWn;ErOtt_yXf+P%nlyio`%XZsxEDmXDP+JQA!aCFpuL zbsG}i&-5Ng*d*~NBJ>|0Lb$t-OD64OV|lx!C`#I>VdMEWSZm@KJ3jph>VHEgCZ~#~ z@%Q%17W1mvE%}JPL`sl1$Fdj*Un_t3&WWbs0)$^`i;~VX+ftz(8Hb9ataN^1PD%=Q zo~otVU`%^}KfcU^FESrI=Kpj5u>Z403;Q{(OH8V?5pxOiQ&eLL@XMmMRx8zPZ-F^0 zIzdge9E^Mmoxid0glK2miWaKb>S>LktS4#vMRID@Q|c?ou(wm?AddwC0hSNf_b!f_ z&zo?5e~>Bl*^5O<_Wn7lbzpCG#A`$}+>dl?l5Ab&j;*i21g{jreBCXRG5=hb|H~YY zeg(1N@&I;Lo5=f_&=jo51U@RSA%9HaCR%-Mc1G^qrK>T z8ISO!&*W`~GdUZK7g7AQ)r|0sLY}a5Mes6&pE+Pdu6T(m9WSbJ!bBi?ES*0t z3&-=pmRxaQn|UG*(MheEOl)%5_x5V_67!+cA0zsv>z=-ZzhIu|2^d+Ik>t-m!#rZ&MwgSNAR3nl z*45$htWD69`Cq9Qqw`5?6vD}w_8%hL1okqj%|{RRJ-ZdA)FAji7xy2={2y1uj4HcT zvJLh3g!SEv-eGvWu#O%V6;f0mqQ~gOobBv=B?xC0u9UPXnSl-rH+cPIslYf$+lQt= z!?jU*BI|IYUJuv%XtWrl$gqSUxCah|EF9kxtl|c6-F&vZ_l5OSDtIF38u07gsm9-LBg7M{6dc-F@5=G5nht5=OyJ^u>7mfRtRUB0#744my?-WsdL$TM{+X~7Cjlm{QSspIWA<* zm46EHg#i|Yg+#|k7wUP@4h zPdjBLsQ=cQ%L(zlO;}%#)pE(?f^As-3LySxoLM%G`m?lZ4)LA(e`DobvZA>IGy!L; zlPg5*mY;r$v9eTH+i?xvU+2^1pOZObN-^=;0oU_G{Rz@CR&oUOznMz}rj*>cg)qp^ zfLl|p^=EMQB|%%;QT#qKp(9$ag&Y)0Vu8zj;J$DpwPUNv-6wyp0<#R#Qi}>=ncPq$>g$ zGQKu@|3^@5elbX}k7@4yBm4f};(vee0B#_5AgRztO@IYw9_lSvSWkv(o6!AvNH6h9 z{BAqsF)i=AA)f)ZY@v98Y{u?YYoU#ul4IOu7~$e_}oyU{&fC$e@2-%!dZob zk|$xl%?OXk)e`;YWitqu%1xZS^R9ZG(b<ICt3FN7U3AleeK=*{Jm$yBBekOYdY8ShzWkEJZrQrY*&FI>W^;uZUsCvfPQZGY zKjXI#(QgzgB!1-CUWCE=kz2=XY(#hyP)ZBsaZn>NAbK?Zo zU%jE8L|$@gKB8A-E4)LQuKHCzb(_|)$rJc|-|Miv(%(OuhWfipO9dj~H`@?i0^?)r z*6MJCujYb28WD-@0~IxjfM%=xEkv&Xd;jgS20Y(yK4OZ?e7kKP(SvKm!Wo%465(>W zSm?%pWQB!xO&KW6OX|bx}^?^)&bGM|7^xEg__CV+Fz!hLytJHkW_z z?V~19V!s{BGgi?Ck$L*22Kv($wL5&s7dmika)H0?R7I}IpRBqyj?TAH-X$^JYQ+2- z4e>Iv2g~t%(g5~HbK3kxNB@hRYp=lgCYGkIL&t9}FB949otR%McLWM$N#9`obx5+3 ztV+6%?FV%U8lmr-&trLXt8j@lY25&pH>2{!!luEFM0EZ!Wtk|&;;MJnK<8sD_3>Gt zKkx>9y5Zc{=zD;^e_0UH&~wB3nmO)b;mzCQJHTIod6oGp^QX);!1VBZe-he5|M0h3{Jg@Zu;KR)Uvjd18o!}-6nz2nGTrMR7*x_w2BK&q;m$0{Y2bPCh zz`krrPx=2=d|%#5v0JrB}hyDx)c2%?cqudyIrxe?^g!6BD_DWpbC7Uoam z_vuwb1h$sk{}`XI+$37jMqzm~0`ZT_B4_aPhW3etDfzc1QUA?_CPBMzbtl4Gbb3zG zIy=5!s1Fk0ee3#ZL^ndczq0n;g9umah!^^(%UC})K>pRafnNN+ytE3T@sm)j4^`3r zt+abb(ecV`v2gvl5Ii4*XnBFYMwfiQ2Js`B7FWG`R?%W9o7Iv=nVo02aU@EVXYa%O z#R7ZAgT8(&kGglj{PfJ(aCE)O!iOmX9ZO;SSudn!WM2pWGeLj=Z{=DS*_-Klps<-q z+FZy4m0{cR)Jn0y-tFDi+kuYnhJ4eI$pUO2G)OEwsMqotq6fkA+Y7$I`ilqDYb2OP zGolZ#T`UZG;kP(`#S^}_kB=)r)|2k9&dZ`M!e$S4NB&Vj+WAlcI^L&HA@m79`_U=e z*7aAIBmf=rztBFgpMap1amXJO6a28oPUrcjVSfcNPvhgVPlc!kMa>pAX2|PurRZ*J z!x!lON3s~?+tW01>tjFV_BA5HZ8~yoM9tc|W!tm-+EZ?)vh!bL? zBocJKI_^CkF4 z16~Yw3q^FF1DR2Ney;jYdm!F%I@yZ-4IrOMLvLR->hGa7iKcVDHY1z`@~xoYeN11S zy^>6Ox?>j6*ML2rN^`}BjX?bLd2>k>qI=XF7bQ`y`6%`XLzvz>1Ca*KwrT%yi0|-e zLb!g1hNlh*Lig7L@zI>f$O{=x`VH#pUcLXU-iW`qN0D06S0ar=$A=WDMYgF@Cc>+v zOwRp50^iRSiC&^haqK8M-)gYeg)W^xfUuWJ&+FIMb|M@D_6I6$(g)$M z))IkCUNfFA>f}t0UfqJ{!`R#|!C?N@t*HN1m_P1kG+}uJ{%z7naQIV1Uo91L#&Xzr zyp5IfgiPOvZHPY6tQO``2XzS7?bQo2e_`rEcmn(b-czo4gXRvCXy^J#%ztZ&^Z`jp zU1g|0WNT`PT4N)^!78zETBF135teGDXtuvE3DHNCrJ_^D`w<9xWe4&!`5heyFIJWE z!hNhW2>WP{OME?!yn}FqqrQ5`S%hOC-q6F3#K&*aaixCs8}awLaYUT(&pS%+_q|fC z5bo8^q@m-l?Prj4A}SqYh?h&uxg~&FA&xl;>Ln2hkdMc~+eWwRpx+vr&tv|+0`b$_ zlxZwaJYfC7Hf5mGQLCYz)AW}6c>I1PvHbAHxW$AzWFI*ZCzE~n5aI4DBH&bV4jbWE z?PW>3K$?Z{^+G0DJaHL+PcPM=plt;iis&rxXH$>&WBEKRVUl5wlkj+m&0b7q6pZ8X zw|j4`VEs-HkCvdG;GUr7ML`f>m>!>;hUWFZ_TN|PUYX$CW9$I^o{&XE^x8n&*r{9g zF}a4<=sT@dv|5L=QPCUY?ic+Am@? z==X!%P&^*pYb`R_qc&&+f8Z}4~me+@^sD6$jv z-=gD@X~{-BUPd5awd_~-CJ`O{{UY+mKRJl-^}T$Nfo#C@vp~C8ls-R;-^c2NQbE{T z2k`f;$W;pSjeb~vugKPjWbchVM(2xxdSxT6&JV)R5Bx!P zsV%99eod#2@->8oA?$rLLsfauRj>PJeT=BPcE|o2jxL_64Z`iphv}&QWu-!RlUb55`=7_+vi$$`9Cj7OmUk8%5F z;plv~Am1mweHoSq-l>-)#~Nnm5WQSlD!SNGGK26{sMq(C;K)dXN0m#YZPTL(2#;lT ziHhe6asMrlU*_H3j>u3S=AoeBZX)>eoUO5ZaBXj|8P6{kfmfliwA6^`UhsXg>`FY} z@6KhAZZ|1Bz63fh8OU$zL;Yj3zluxz9)Yj#k*emk1=Up}`iT65AmlK_(m)%4e_kpk z`DusCo_;N%OE4MvTtDhx1^YcT+D;^bb)*v_epB}DxA@oF3)Av{5Dn}9)r{k>{Pld_ z@5%xO_mAy^xnFYIh+h-xK=5zy7wKehy^GXl>Q^z~|7%E2PN8qF{ZNzfD%9^`8q@pzHQO{_ru zz;lRB*CSm|EyeaJ0mSdk)EM=ME`WTN^(!x7``4)>QPQExrV>QoRCq!%bJtb>-93RT z3^kT)Ky_Ne<=uKJKw+QB&4AAbD=^{;~bwWO5uSl(B{ z{XQs}<|8@})@M~jI3Dj>h#!oYbNL5PLcM_Or}wbDj)webK|xj)I^OGmTIkcfz6jyy zZd1Ul-Haf$3E~yT)UNm2C9?2-l$eS;@IgPZ%oVcG0a;h|uzzAi8^T+(#|0lM^RPVK zT-f!BwJar6V#0@wgn5u@UQkt8}57Cq+d&cfR@MfZg#( zmGYIm#$X-TBH)hU|0UJdcUVU#Q1ng#2J)M8hC zc7BVA(|ge6uW9N4{H<#~8HM=R+9}N}x}HYD<-C0GC==nE<=nuOxuyFM-dGOyz^2FO zbHRSTLs^-bhx`$JSSjXoEL(>470CaJIz)Qm_2NX~s)YBIzqo|@KU@p(p^M%OL|6dz z0B)vd;Ohmqh`swdu)UbN0{Q*2UGwv({~maLe)}Be_cEx@loR;LtBAfOw^Sm%y7D)>A>U@nbCFnn zt%m(aJ5uv*qW-&K|BVRaEj-@cVSmQ`WHlSn>!6;zhCG;xu+gwM%6H%U2vSggpWOeF zM>zUnH`Eu=)OOw!vs2B&GRlY8ezYOg)it0 z<0XB-u9=#4j_>25tTO~tci_%s$M1mw=>3EI2a@{m_4gzwg=y)RH0XRoP`~^&a*m!K z9c>RSe>z?M)HRSVo8;lz|CL>oDZTuMk}0~g)jnXmE>{bdT|9rxaWMQ5q@EP!5VIc* zK=G`0`!uzCIc(SA<>h0|r?Sii#St5U-;;BSlOmp7!ubAwHRkU-g5W!x@BG!t{U4YB z^)Bkw*(iIBeYx%DT8QUl=EU!?L$uTh*p^5I^@Wytqj-Ni)CX8BDtJ$`9!bD8sz6ae z*G+hvQ&4X2lzml#g2xZ@u0hWioI5BmGFy8Q)+S^KgVbBi2zx{P%-t28=?KT^l)~km zGTi@W;b7csnz;ngw?KX;#dn=A&QKP1?^mzW>N$^)Yo zKYIvYZ|^{0!cbB=o_~QKh|`DnV|lj<>TR&^8B5Xiyy_UFpSRBugk!;eG0`=64`FXz znY_2{Y&^nag_k7O)LA_KvyKeKg|!vINOm?a$lo))cFaYG`7WXl*4IO&ER_w7HH3L8%@G^@b}sT{_4iY zMm(R7LOsIP-h)^^dcypjR%{wX=ZE#JAfnlb<;8W#=ge1rKr8nb&HV#V4|M}alXtiN z!G)=Odk1aL7ZZopD!9LyPa0b``m@p1P`~4iMY@C5sgCo_huUK=iy%L8tYLG}vsny2 z%r=&Uwl&YUKn-5lA5}2Z_uLA{!SE+odR)-C zqT_RftFj*QCKUO_2w#n31il}t^G0|N>{#@`20I%rv`z9-&h_o`#A)iLBPDTCY4M*L zu2v(uw{E2{thXJHub;vAm`|fewzDmfn*8s*5wZ;7-LQW}-mELW2fjx@M94+FUZCR* zw}yseeU}C6yK$rDE;{~t;X~2(9GAbLPRUr_^6AS9vkUk?%H5VFdaJlIB~ zdA@!Ag6$S%fc0wsW0!yM8t5-6R1kjtHQ+qD&R+-bOD7`e_diV?qE1~e)Dy7Z zhGTxGEo5;20rN*Y7x>*yc}6ivV?%~hpR<{-*JWA!n+AWE2**POXR6avwKAYqBguet z8GFV2t@+_NF2UwUyy>`JkF|X@#0#@q*NESnsIs52PTYx(;t~h=zLg!%6*68Yo+BOt zzb6C3|6M%%Kj`!c`2SAWHS_MvA)kQ{>3<8YE@4nm(b|^|Ph+NarZHud@YdT}kEH&_ zDVnLY%ekzb?V%*@FTeD|@IF1^^j=WZgtAM}_uxe*zrZ=J%7+B4b0rdIe>?2&!W5dg zpDEGto8=EVOruM`41)0%EIR!Z(M_DI2@G!%&kv3AC}FKx){N+0Qb}ND9PHO)hPMy* zI~*s5YS5FKr;cn3h_{Op6A=+*8r1))a)U6#>Z*U23Hs&>r=jA^LKA42QA;1soJDve z)blx6!N&6=Z9lw~k@6VPR{{TM&c~-C+^w_-?x!Z=@eK7Tg!Tt~A4I3?Yg>En4qz-- zlVx*x>%jVw4|4{U3HO_rI9TgkUD5fm#{(=+Yi)*lqg`1gm=5)k zIj-GpAwMU98Tef5Ei8|=!232C(fmnSJynAezt&e4*a_-C!+Q3V@el8!>&1Zoue7nb z9bql(PqaNR9^1pTuwSrkCd-8Au%EK2F3{O9XnKVNH0HKH4!o?o)j_%Z6>t!0pH--(Z0 z1^FNsBYhiD|7(R&ff}~4#EGXVtpwt&8`MsEE0q=m`8FEO))K1cPRjQ`ApT`B@TVzV zCc6%MMQv??QBDsrCK8FKOKVCkDGHZ3)Ch>5u$fXp#IK}56Tc$ z3XgLl58TD_V2LzC_^>H97SXw6OoGwauo-YiXGH66F>zHQ;}Q&tI!C?oH1acAelk6D z_f%Eu&HL$;3gW7`EZ=es5Y#&#CqGu}9S6y}#^CwGnNmy{X zr5BwKjwgM$l%yf-CgqY77nb4qwjx_E2@1mHlPIV+Bsx`Rn?e1r!G5!|dHMMI7S_sy zcV&r~KcM( zgs9}!&0VNJ&HrxN9+0%o2Kn{8oLMIJ-$a8vUHa1JZy|aN#LFgLoW=SEt7xUL^{k%_ z(O2i1gj7mi3Br)yKuWLGV0q0~EhfVv+TUEDPY=WXQ$9<-boPfub2qHlxgkc-KRV|H zUoid!;z0=gj}P{u^TmSw|HI9B{g7W%0u?zrXnxDj%E&oMb%B`*>T$sP)B1Y1I=zSV zcvl2$vy~`+5dV?HdW)jI0{@yUDSeRE1?c9r(;`?88{|R%WAuD80{I#x#f4*xvUldc z1o{{mu7p zYbQ5?y}wCQ+dc&(I?B=0V~e#d;azolbY&w@ru+b8<-i)^08Py-G4OPiLGBM6V$N(2a$&8 z9x5)Z4_)=D+#sHVdH8d@KGA}IsWZ~I0`;$h{M3@UR?Oe4!9I{)Fj0l*i&d~6wheMe z7TQUu&rH7cycfbFu>aY&ckfvcUIq0tOnmIWvbsaOL8AucFMbL2;2k3%j2>HbTMX}i zDL?#NwUrZZ2dh4>xaALrM$XR4M!cvTPf>isGm*UiUav< zhx)M~lVHCWHJgv;+dU93_D@glKaZ}r8{#Qm_^~VBuMz6=_TOXGAo_?>?XNa|jpvVO z73`NZ{QeX@pVEC+XLF@#XOF&0g+hg$A5k6rR0UCx60abBN6ZLkr>KS$s3)>$Q4=~} zT;W%ef&9@*goCnc{pYA=JYHiUK0(G`I*90!z}*SPMgbFHsJHI@Tk{lw@M_Rso| zMp&E84IG=>Zbo>OR1x6amZnD7D_cUA1czh(0ec6pH??8NnnfLfj9TkerI=Mva-aKZ z)au;4v$r34f;^J-9@I0Tns?myt?ehkzX$n$rrH<2UH`b#MvXu`plrh>H+qz~5!KIS zaA>*=qJsaP#=xE4c`BasHxMg9zlc&O)b#UNFMheP{)E!RvRI70An&YqtS$Fri%kBu zp2tb_eDerCLXcEV3_WbUh@S5X%;%M{Sv?4^0e!HyZ3TjmAFk(wwy5y<-vjpF108=b zp$=XUf0mk@kM;f0I41eouU+xP6>-s(hnOW|eFu97952!lO@ zM%YWMCz(v6H^N$oSFCg+zK5_Xn;Sr=Pc1_j_Nybu`r7v)+$}F9J!&eCQ)a3Y z<|OJXsIMlLNaGS2!KcZYdxzbDW29p9nC9MPZ*iCSYN#c^f^<;21Gw8zr=z1ZJ`K5 z{UeTStMN62FGGIF$_==_S8H84z22zW3UvENBhURy1Jr9$8$mz5NJY~7X?Q|?@8skh z`2O>h?QrM!N1+}Pld)qR=n_A-y)?(JaKt<9fOP`h{(906UHy;#_tzS)jL0CkG*#_b zB($Hax{!M$V~a`z_1BVCXU@Y~fzA(%2K&W>gv1rjEbiN?fpzxMoIOfmQP>)hs=F0c;fPBAn zS^Zn6|0wJ?(Ko5V{IfAvBRcYWry0?MR1YQZz4#Q*2N0hZWtdlHA^I59pZwO~Q;jg> zBa6=J^_LO$hW*7KA6qen@Mg$&ot*CfEy9p5Alh`W9pBGx*e~RR&zk%YJy`WnczX8G zq4$ozssDNW&*PUYCx|cZCerpMm~rhCZND>4jN|dAg!r|4^B-aU+InPVpw*^}MCXfx z_YGyJXA!Pat`Z(SJRn2ZL(28;Km1lJ!d|HaIrr&n;Rp}Kt>maL=7}8`RtV!DpS?*7 zJ$@Yx^c(VeMLZb5ooxxM{~59GP9Qu6`O~*5XYhC!gZ-ceQYB^7Thum~UZ@iiF>7;y z`Hf*1Yjhiws96!Z2{wFvM;lL2iRRb+vbf1MV5h8Bu%|0n%bub4!Z-jrAAsbIof8}uf<}j zGQe7-hyH<|ZSv3Gdd|YVT5qTEDwyAUJskC>Y}-ELdN^;WvI_J|o{a*2J}!I%>Uz43-8NLl?l1t zOMQIX=zFKz;wT<@=6fM%y=T255fdzs&@^qD+I1h|O$jFH%D1KCxjyfE;6^Q{ieN^h zHi5j~t`MtlI7T;AZO8@teEX3;=i8#F*legja{8PRRS@(9drgGV6)#W^`MqUXNyYT_ z=$3UyLRxRn$eak?cOB#hDL?E*I7TZL78fb?2u~dulE{)=`Qm$EzYlg(CFbw1z#rIP z$}pn-Av?LkEk(8ngh#->r%X}c`82deOcu#^;Q73~P%Qji!TMa({{-0ELOOeIAUwL? z#L>s66(J1s3-9!oz3oRAX5=L>|F7dR2}Fnb_q-b)&E+Eu_DIh8zPnidj2#h^kp|cE zdx5^IH#zl#+f(h6kk8`J)+8mN4^)d<87>vg$`C(>+O+!NelAZMzORRLXDcPu-@+43mWC znVDqL?kf;zlo9r~^{Z)abQ}(U1gas)+HbN-5Z1ze&vwl|ERVgj7Yo5(f#vO}+#~AS zeph^1MRta;Xq&4(te}t=m~N}-M(2a|O8~>j#pAQ0rcA<|bHyudfq1-$2v>f3qk#~n zZFc3GL%dHwQ=i-LB6_qmPiP_!?98Fz?^~^)bg#JRqvpjd=1%DGv)XBg_i;eEahIrkm({yJPDe|tvR9NfJf?#P&{CK^MVYQWa@yZ9I+ztAII7M zy)$k10R5niJHLu_*dG>&Z4yTY!5WaahPH6F^KHWXTUahCSntZ0x+I8|lLr#FBKlRR zXWYnR4kA1V^<7&=8$uBF+DnLTls+6nxH~~F@CkP%2#?Acf&KUOdPJ|xb&m?0>&5du z)aT+PCjLhQqJzDVGc`WBFn&Pa$Q_ECD%-Fizrf#F&WK9N+dGQ-H$lBQ|_l6UVL%+I^Qp4^sb!Q(kPmn+PQlHmKL{f~kR?f7}i z69ihlQIGT|Do9%@nwe>0q4Re`JyO~FT73VpT9y362 zwNLT;aE}^MW^Ug9LqxACB%;W<`~rkC;d^`2L9KhE85cGT(@DocZ_C1{7#-z zAEEbXttc!LS_;~GQGcj+92m&$ScGt8QAR>uPR@Lf-3$q}5VD?Y@n7nv9qt)B_JtJ~ z9_nKdhrd{X^{uU7kH~ul<_9y}KeVe#4VhQCx$8W2$R@wna(;=DON5`*9i(YiDrMe7 zrT4GZ82ijjb0FlAuipdmBQ;IFeH83pbOoR#QD9$SYUujMCDbu0bx9P&=ZPiT>D@zm zf$q7E_NO;HUpezR9B zY?`gFKSXVU=Y#raaD5Mc=U@rZ%CIu6Zr|GrXIqy7R?cUMXd>dj$Grc5qx^qQ+Yaf* z3lPKK!fS3H(Ef$RpbtFXE0f)^SKAKQPf~n%UqO+s`Y2nnFAGxQb7~zA4}UzQ8ji;J z6p#04s3-i~-CZ!!oQ?KJhx6O;_}v8YrY(FA%x`Q}EzhDz>qGtPpk7HWqp1yHuT;0F z6PmKO5xxrkH+IL}vj`jHLn3yExd`Fi5Wl=VXCNKnZuy|-xufepM40w(F~jF}AUq^8 zaaa{zc>V|fcYsgF0g&X*HU|4&`_-RqNBAVhzKW+-j0Ge7 z_8E8S@0yCz5q{=cdEAOtmp^f14L8s#lO3jck#3uy-uM@edQ}TJA&|_wiKlBS80@ju z2Z5KIRElC3m5O@%od2nq%$O^ECQO3d{$*^}>Iy(Qjl0nv5O7Q#=1Mv|r zi|*5`xbs^AP*qk>Tqh>($x?k28TBdr0Qqyk|H+mR8{HY&RcRToRCD%B$+PBc`V3(e$gyZUns2>Ia|K>Tld`UZTzLRba$G+f9D%tq}k1nL=rDztNR{U zzn=q#5k;Np5pN|JY>)>ZLA~A0^Nh#D3BtcB%ju8*=<1IYo?L>6hV{c2Nwj^yn*1TG zkG&zD_8chqx2M0qiXbxFgW-JpfFbhi{!fX=#J6i`{a}%<*ZF+bDP!&+Z`ODokGC;- z23d6H8T|e$bKRC#QhBYQ|8ro6?ZnNnK1fMPNhXNZ#9pF>D2Mr0CQF2?{;l?(+yDR8 z(SJ(!f7O2vafXOj13|{`m2H@;1MY@+2+()KcSSRXh%ChY9k+*=~Zp`nO*$ye{}@EH@~sH?pRzB)&Dnx|=$6Ba2I{ zTbG(l=PuD2+E)@2%iD)dqT{`@1BE&Hj|X7<^w9En6!xD)DMfM3^Yx9)P z1uA81J^v{W{l4SF5A~%7GMsNcO;vRheNvh5M&ZPfUx_)zLm@EGY;wMNS zO&eanUyfVJ^NWapcvTqe&@v(3>gC|NHV1$|ExJ`vOCA>?NNv?Jm*1Yi>_t`N``rKl349sM6OLD-0*WpWAWz zW6Yl?BqmOf6r0aFXABIRbS5?_^;OJn9m*8 z?}qw%i$i$==iR`32>YFV{oB?Vgs+vueyFp?YJ>$v7QtD*!yf4PX(QzS>_7a2!-zft z`$2!32ljTWbsFS7HI`-KLA}h+kbVL7b?>moi8LpbqK3eJnQCGC+TV6Y0F5N*iF`@5 z9;|pvl5Y=CLBLru+3+*))d_dv4fuN;JnjpQG>!igEzIZec^47>KgjtAg68+Y`d22q z+c3AD9&HdW3-H5YD@0Z_fY@+V;RIYzz z`OAh}E-{vtltf!c@4-K}C_NO+bIjjXcw^R0@(khKI!b7zeq-wg4$R)mAgRdq67)Vk zKFZ?^S)raK%mxEfw{2c3lYfpPT?e1q(RQa?;-$-Mczi;>CwY3P065jzZfF+^E5m4c z4IFmUoHVKg`|zoOG&8e@v5yEQ#tUhGXQJ$7(6heV?o%so5M26~E23mIL0Es4!ld73 zIXpj|f61pw|5?)z@vuI68KU=CV(3TDUr*tmr%KPNZEE&jxIOAxih}HD;twP2ox4~_ z#m~~B%F#xY4@IJ^IH$4-zpMg#Ci#M>YJp5OYKi3&wNJ5r0rq9-ErZMde^ps4`N?aI zO4NTG;+wvmpO{3Lg!(AyNi$eKs@GX~I=%;OuY}PG@(%Xrwx^~#N8Jb`8J~L;=OMT_oa`e+aB02xSjvlv7Vsk_%#q;KvCNl zzMq|H$lV|;v2<;5_J;KW*c*G(>G)`8BVqp}>usC!c=%~E#P@%*y~y!>=^lGveMN=| z@bkoh{V?g?y>-r`!Tt&L!y|%yLJ{6tQY08dsu z@awvCSyB$Ae&C-$h_@EOd^ZE*$=*7$@8jYePvA%W{&k5UO@2-tgZMwd849uH`?LO& zw!NOwe8HJ`QwRoK7Tj(bGXdXcRJt;TdC@&TU5vWBbnhkbU(4#Ln2=vHOyDe z7NaT?X3w$>J?=g3#f(tG9sJ)Z$$vXv;2*>!=cf6!D6{Aq13sMwW9&yUXxB*_HtEZV-LJo5%fAt-OGRy=jgzji7X_7{ll zsurN3+e-?Nyx*8m%j=w(!RLp1CSXt7wGOPiozQ)s0rCan6@-G8?reeyoXLg%{|CG+ zI18K$zRp5Z_@7+)`+Bj&K&#J3H%j7VA^NstnQA!wr}p8^9kO zzU0$GsQ+fIf;@jdY#YL3Fv9d0=2$+b?=hf2i-o8Q3mghVV-r3I=7n zbQ$5zDvN(Z?TN;L-wgCq1JHVU%xZr;;)*|-#MQN z@>}*mHXos2Dm=w3{{{YV$QRmP_%ql~+{hx2Q-?d<-Y4D@!v0!BPU^bELxIH065_u5 zSC08p_D`A zNrImBNRgpI^q|y1-ox=Hn7$GAn_AX6iS;#)s7t(jz5mCke=x)Y*I3i;BRnD>lEi;# zokDm|F4U{79LM_4b@2Zz>oDT;-RaOuBXqsJsQ;-m9&*WbHpnMutEkZnhIg);hxGxy z-{y3+&cy4b>~8_<1B5J}BaG$in`_4IoHw7h{>mB)`%!QXAGW8l2(N^-_EIVa=EKCK z2>xTx?dWDaq7L}?e{EtJ|9P6-XnkRAd`fEHM|0Mz)xv;KEtl9< zx*j%kvv<~!T!t6SAdIdeS(cldV~GCW;`cw}hr7h*L@iOf7~~a%+b!y`o9Tif{c$xJ zy!!fYQ|YH`v3qutMPgRRQXR-I^VFSo>KMe21(2s6XVLWjU+szBlfWfHPOk-h$^58t zLN#|%$OQ$w4a;*0X^`p;ByXns(+%pU^9~x-ArGA5#7fskiV!eOG>zVKtseY;riP|4 z^M^fKj;<+Qc16M^EE9?A9v>Y!_Afnp$ZzKoMcYN}ex1x4uiao8_r+e`$SUmi68zrOfXkDoub11iE! z6v?dr>^$Ncj4v?8)jAcVCa}k-Sq3)5_bs#xQM4+hR)D|PYN+o+R+m(^1J(p7l#R@F(~qlKVRX&MY|> zcfGieOC0Ax0mhZL3l6Qh{km0@3Hnk-Qd;tA;V(1q_0LgShrV;EOlNb7@My@7G_AZqeuGEdi&{O|oQh=?Ev6YS4Gh%+X*ZK~@~UlsJV znciNe<9Rx%R933qf5ppRCxdc4edwQG6_PqJtK}TT-@*@%s5;xNy_a|;;_5cXev)9o zicvmfg;q|EQs7I;n=c-w&KXK_VYqFWbshNFvy_1!t=p;Uv*q~&YqTr=$Q!;(j>_$KzJj_ zujcfHd;<6p_9qX#e7O|sM@`uq6J8Pyq_=i=f(-!d#ZV7p-F{a+^K0^2(Ka=+1D!8c zRVzxbt!-Xo0~aPV?;`z#~fVoEYr|w(j zdG0DYe?tQDkq`RGmsIZLEBE}n8L)ovGU#;se%Co{1(oue669O|Q9R#Wf&FrS#B6fP z7pNdrU$}uc%{K0Y@#ScnVL#gVF6_?I*CP7hx3r5FUP%A{xf_4UwM`RNOL0N27peTqSiz14)RdmD+ z5fH-G7O4wD+)x2Ab=pX)LM$pENM5HFqmbnN&w0rU>P)|JzL|c@|LgT9C+BYO-g}mN z?jF3J4X=muA$eWe;^KTio@YMya6>yAz5wLA=ljiAG{zat|DG38>LmML!#(!&4QiyHF%=_rQFn%RSr<79{X@P3!lVx8&fc|yF zOsktiLmWmX@DIM70&+@{ao(e0^uN+TY(x;&Fr!5L`@6T7^uhEDf(zdP7fd+8-=^Lb z`v@L<-HsqQeIyBY39FZ!vIx0{@>qfPKhD=RXfA`jc_Hl#pQ){e8VG~^0FJhGy3r5? z<0YP5{u$k6%@Aqmii^CH#e?r{4~6>#CZ&q+!0CYO=Is?FLjv+l)+8ji7!Vv{JQ49m zK9m8(1x8W-#ZG&h!g8F?hc7$rbsY~gh+_yRz+imgv&zEqr55~;%O~J?I;?9=)!=*t ztT)*ZyRB1e)8#os;}FO9w&1WGEh>Tc_lR?;rA+&xUr(&i&aj6nKcuRBx=T5^#VtC8 zc(P}Y&fZq^*82yWaPs<3Gwlz5?t#4@xxT&ox~h#);R|6u89c!7NtY|Vg6icqqp~mz)AR(` zkMmQ1!nbUOD6eUaYrsALV;$V&=n$T)deA;t2>PJq$=QQ!{K6<}yQveDy845>ho+eP z@U5ti4d1%t()5_LN-J&u1nY19PGgmogXbl};eAz7wA7D{9|7mH>}?)Yvv`O9CCBQD z?Tq%^t{&TY739&@)JDdJ7yDlnYSskzvv_yZeMg&N#~1~4HLNT^XQk8B`AUae)kf$dz~WEV8x4YnGXG#j3gDRCe8Y8%k)st z55G09mcjf=E`6;B)}vEQx&xL~A}^y`80*Y$-!QOA*M5}oid5c@4<80fAIa4c^`Rbd zG;i#e6Y50Q{pd~9=yyOV4P9ho^KV_*e6_axl=y;Ji@kV=U5XQ|=@Y*phC<5appU#^ zLiW`%8H|;t@p@+PwMC~J*b5_aWI57(mBqWhm3&(gn`aT=bMqUH^T?{CdzD`>=f7q9 zyt}B|avU?5W~2~>g{G-PzBH~Ha^dYxn;D=lWHPI|Epi*$k(PHr{~-_XICr}(Cvio| zq06qo&oF$2@jI@-z0dTMVASTjf9e`ODx4{~>pOg04GHCRVf5k%S;ppp}= zJz;ynWR<@U+|LEE{|IY*RHi=!LVs_tneZ@LGsKCdDDt!7VEo93AkSRxIugGJtK;-S zZ!S^Hwbu5B;FlGBbow*N9sT&FbmLLGTWk8y!1`HarNLriYZMpmd+`>HvNm9|{@)1& zFBbcXW?dO~0qkM|eP;MRNw!{};RC`zdk;*=HVJ09OQaQ>kx&{f5n1`_~jZ6%KE(fS3flEA@?$|hu|5)e2JA6%uim~ zx!X3C*xUu?#+=@S9*O&1WhxEBZxcRcz##~+}NwQ4DFd$T6-Az6J2SHf%TgH!Sj=0 zOV0BzF3R@Bvt77`Q7|3OTh27s+qWy>{F2!$?RjKOf7vT|Y=1xy!|#Z%a6CG8sow*= zKEVAy!|kU`#V~ZRUyE;THem>ZfC-Qp8cfvr`29fBV4<9*3$gETr$MuLQQ%dyd$1h5mZvE6}9~)i>`$0vK#iwAu zFU-!u_{4v?Nl;r}JKnib zEM5om6LbB^XjmV`)+<~N{qZkxf9hb-ewkmN5T6kr!_p_@o|7Zf$JcRl=YInL+gOk^)G z{g(cC%iXb4?JKqR4@E^pu68o9wJi?d6CV(Ez;Uafy-9D;-`am%AmnqdLA{ptpgqb9 zVLoVzvGD^q|L)i%53PG|1?)%h$bNuP4gU|z|8q^n{Ji(U8nnZRRM>yy4EDi9-peum z|Hh}Ork%E!PgrnZNd}HXk#eK6Mx=p(#rRN|PXvY7k*t(F1sl}_?n7{MHZZ7~7(u)X z|6GN@kEFLZXwzWgbK)p;Du7$%s2BlggZIl_r*F&8J9$_&_u1hAy7!VhmpF0rrha~H z0qjq*FO}%W_cm}oN!BWPo0Xq*A>5~!2gh-AZs?-rydSeCvGxglJhYvkmKNE>hEIh1 z#Ihe$Ph#6Jt3UvC%7Zo_zPDYb@U&2_be^f`q-?< zBZ4YTOf9uiSX)qQ#JuLfsVO}@Q=nwFc zU-D1ktqNGbCWr_knsa+-_c*Y($iQu1zYgD>_4u@|)Znb>WR|o$lPRsi~>iv?lPu(0d#ZFX^AS2<#P+_PjBE!z5s{UES0Pdi!H#;W^80vHeW;`}K!H zmWV|0TUopV<^xQK1pRva0=VBNrTgJ|SSw)m_Y}hV=2`ySsfz2@Ypx$?yIlbH8Qtv) zxRZN*HtDu5vE?|7PwPdZ91irwv*91i=4S6GNcIEx0NwV-|+41baUX`m+E7P|V<#L;v<)v7e9m*sp*7zi9!P{20BiM{xXV z6hWLHF;m9L8P)%uYQ0(v_AqHRv(S*36TIsIrCA94Mv87ueFEcubcJ9)8yMP}?#emb zBL{pa^q<~OCY#_~7rJe+JnP;m<*dCd-5R@m>~=+@ZhcS#_pP5BD>g0R!~A_kgzZ;QlFsybUK+W{Q<6(X*Fsc7r zZE0Y?{EE9K+u#7c_Um|_%y7D_beGN^!2OP}pP+Q=yKK1e{1i)@((cJDeosiedA-m^ zZ_j6DTQ6$ZUXldmV@!lncpu{Xyi*c3{=(=Bf)Bd$@+gf07O-1EzvT0ePn>MuTLCw8 zjiC&2{Wdvr=~B?3FFNNJ$LISUAO3Oon-&IFU_CEsIn4iZi4>;C*QI*nc3|i(qLx!Q zpCGDzyq6jMYTLiqp1)%MQVUq5!S1YM#EDdpH#k0)6QSDJv}xvt1kU%{ba;EiO*su< zF*r5Ac-CB|-CvGp5Hc2hZ$WTcm!W*fGbGb4hr<0PfEC7jm@ z=+8;3YEnH^YKR^F@fg_e3k$^m60v_x{A&xm+yW;+{+AP6*qy_iANhco6FTPd8&~jr zqPHj~hsYz&bIu#x2m$#cVgEME|B=zD)`}?;Z%U`rxhT)?!G5%59(PmO{=a);`t-JP zzHU8VL65Urxngf&VIHK{z?)SO*7LbolBG<3)H7*TcAkZJc#|8oo@(;#GQ0=pi;1VU zIkIlK_KO){U&p;YXL6111NE>zU!I@O(S|~Q5BrtN%S*EW*T8Hc(gDw}>{K-%=Ud2=7KcoDqUWLyC^`RE+p|5DR_PQMl!p&NT!!(-=7(@0(KmG5M+L-F8!v*8EhRzUJW?Foe`H2PY%npkmW25> z*t?|3xZRn4&SLoR!Snt@qQ7^{Thm<*x8W_LuTz>Zc)uYaDCpfKsvGn(U&^Q$?D3i9 zz5#fjjLS3P*tXSNf;h>Z=h4W;v75(!BN-8=mFH{f@$>Hz#l?9!tb8w6>CcKXmEF3yyW~wj|Q?!2%dYn|5u4^_8U_e%D92FyfB;&0hVz-Tx`v@M}5# zFSG#HaGJ5co=#waPD5K%>jvm1&A4ZT^1Te$t28giiZ0t_y~~hZ9Z?AP%lr6j>g6ln zZ~x;bc9+0jUl-vFc)N+O>J)Ou*0zw^n(!gTeJE8EPMjvU&a}|=>#wd zUbjPs_hq2I;HDD-#skhvIKhucZCf_*tYQ8O`ghY_*bma$a43Dpx&uN-*q z?do(qU|=Ysfm?L`^oD-%aJX&fBkBbG&=&NK-?D%^9N6I% zWRYx9B*gq+Jp~+x7|DG5*FV3uz<+lOSU~S&F_QZZoIk>KQoY>k%e9;RmBPqdrW=cE zH&IRF>=~uplz+(98|~ZWahQWOaohNTGx9s>sgM0%^kdppq5yB0JO}-4julD1OT0#) zatnv?j1TUw42y4OszP8$5af%m;dc0$@U)(<(fJn317YTn-0%ang6~Keh*6%>G}; zo+}Gx7URofcEjg^|5_+-anG|=Id51H3t;?F4Hba<{;54}o@(6RaG@sy<{LGzqp_Eo zumr|1`Q^h=w$IY=YVeImtH6O-K4|@9#y40@- zbjlEhpoE2i5pL8cY~BBIkR-*g3{BksQt;2j|C#)`;E(y36gfDV6w@SG>_Z8KGZw(b{OUvYNflVUNXcM*{B;^|Dfe6f!WTP5?<=tg7cZIV4bb87JgEx9zj@z^@xSrD)mp@J zd~-HHKAv2Y6q73soo+>|a*Gg-73UUVI;l$f7@)K7-Y{Q;&7^02r}GvELomSg-`AFd z!2CwndwL^WNdA^BkIdGu(>YG$+k*a{-mY&!J`QuFkLLk&erw?k#GT)|;`tVaTe*6U zW+D1py?k z#6OnLK=?J?I&Z5(p~HT0L=gjvB8$*}qgpgG2bhoK8AW-ao98tPl z#8rn32W7G{5#!OnS2FcgKUU7*yW6_oaIw7P=GE!!ZC$*@&1neCZLMwH`Cdm|#4d=7t+PHw$g|EWL>OkB#qV^sbvN-Ltq>C7q$~Q%tn)u{ za|e1`TW2C3XkGjXLQxSn3+06nhl3o9=ckA_wW3^v6>}Il^vyCMK#l}?Cw)ou_Z6Ek zau_D&K)uHEQ;P!QP|mm|esM1F<^7;<(4Qj?Ye9cP>=UQW-!~r9lVWQKcM%YF3Pz4W zK21Omm~h23=m%$$%u*%zJDb>vTN!Ar!?P;n3~pNrPyp{{R%qjME138e+#W52*T|W8 z+WeMVa2R~Kg$a{C;9?D*4j% z#k+~*_(Kegk3Xb9JU(6>y}P#*ne%#8G~(YO5kW@Xt20(VXSqPH~rKudlmfiTWC#hSsbZA}>5 z+LogL|3Fi_EX3nYGPwu?hvYNy0VlE#^p>XPhKDIi%MZl;c-oMOS7Duk@Vhft^AX0~ z&CfJDk$s@GQ}L*`3gUfvW^!*SRT^BBTO_Sfl#(qs4x687KIShGJ4&k*rE&hQkWN}< zBIMds)|eHjss0K@>Gc|0!ep}o{8PBR@at7(Wl8dLZ}e9EP}*40Qz5PDJr>wQow^SB z<&d7?y~n63ZbWXx4^_RTIn)3}fq$4R$sO~D`T7iL)%7aZk)Df@lm9%RAmhcF~ zU0gj~9|7KaJE;}@OTyn|{6~7aGVvk0E5@_brOTa=8;=VDr(G5JpR)k-d9{SE2|r*89+$Y4Jdj`6*c=K_ir(~rl@L- z+ZT^8Ze?pA#((1vfBh}Og){EVFuSCv+8efFzoM%5A~)59tEd`r$pn62_@x->AIvWG zmJTRt9z(p+n!3kKzD2na6Ci%+C*xf|wLbxLa0v5lK4yMGoW56#{E&MzG8gc&{1sVy z&Cm88tB}|A^p={J*}3JS|B1-rlH6%!y`^wbGUP{CDUc#YH9NW!Fk~_Pmo1 z@K}{*dp=d#Tl!6w=Opv8N2PUN>^#;B^go=`QVaEic5n+bKVeq3+~{?8voiA&ZZ46Q zh`(-`pN{wfi=tff6I073EuP+FcEY@Dr{LNQ^D?tCmpjgjG2Gxy&k~HkYK)~j!cXli z-?Kl_TY9gjd$Bpd?y52GVYxn?P_h8=SrubGSJWtuP1-r2L0H;eI>+pU#fhw<+!DbF zvlH^V0SfWr+=%@aC(KU(d&MFoN4j`U0sl^Uk`i%X%aI5@y)9iae!>380!*(2x*=|z z8?l9f(9jHwDEc!4!%M<-;YAiFdaE?zd|RXg5uWL)sOdeHS))=Q%%|!YSX|RnhkQ_! zm&!$d>k7$0Z)tB$ZmNkL;>Q#UAZ>4H)`36!V!Q&|%vAKxoJ0*U`G$=I{sR2$0OnU} z9iHo}g-1WaaO+Pu{4@gefo;!9QE%z$-Gr@JhI)c9t=Wf=lgT4dU%XSQAsK(`J*`Z< ztjTh1{HnMSWspwXBS-z?vp)p%72w<|B?CFd;S5Z*?Z9x-w}{KYoCrp55u7Yp^zPNi z+|#WyqYp&y9=kt$|LWb*2VBIF9&t9N@s0DSkrsK}AvziJ(kd4^-c;D;rw-Cn1139$NEh}MV0ucF>4!^;MV?HZf##%6mbNeCviuY2t)r^(+EmeCt{*q>~v)tCb9tf2W15@^}VMQpcZT;?rLC{YH}%=Ra0|_(9l)Ai;E4O#9C{o&AvlOW@4>2bUmj(?67f*&ckG{`eq3 z5esYR&kt6@6#8xYgFWCMH$XS1A-t`H5N|}fHvUu$Z_&o%`1CXR1a+MvpV%U-1~oJLcNVuE;r8On~vkegGtZy!bVgd|tK^A5tKcP39psC>;+MUAGm2>R7eyGBuqV|FEFui0JQ9ikjH)NnL4 zDT>sres`v_ce`4xR$=|O^S>NxUu$2@)L#$H%AbC^>QfAtOxePJ(y=&ZW$$V(qz?`B zr|*uYhKKgYt2j417DXvVv%;%t!vuCe1`}1a-60uO>*&806gp2QhS@!Va%H}jZZwKg zq%|8E4H-pcCr;<_1ok^dQC{KUA)~1dw?&OR8b!(yKl2eIOf&s*m4!K0RyR8oX;nwb zq$iNS*&H(PiDGfo9Ez;|roP0_Bv0gr`I=0QTjo_=5~Au45`CQB5c2v=8TmwE8fyQb z&<5d`6~)9Tsxf4#gp*<#HHz{fho#6mGX02zR{8aUh7ba_E1FeT6j*2Qcvw&CC*P?V zSTpZC`)b9B72mFyhWOkqV{2s{C4Q!@L4glLtSHhag*%EWayBjI7yxuwQ)fbq2H3so zY2NPYh7f-^=D54oG&Dy>f`5bGj{clRY_Eo-mVuUz%g)xCgsy7HZ%C)CZ+CU3-}%{1 zkpuLO#9bK~8OTq;;Qm@rYauLqoF?ZaRTb4NHYLooeJ#; z=TyT-QJ>c0j*(H+jRJybrbkg!89~fV9z}IHfA!T}P9J=)rH(`NLA#waO>w#k^(ZMR zE~d^Z3QqSb6y~*Ng*6JrBlB{_i3M+dA_l%Ms8Kw6gm8O>+0Ieaj>KSK=uuD)nTjB0 zQ7Z%^%uR?VPexH|MOH~aDWDv_WX>qcih}JgPF7051Idx2sLu0HW=#Lo}Hb7j061rI}x%%fpT>Ex*Q z-#0fOMKz~cy^ZB4O(S5>*^`duw5c7imaJ$_Q^yl|PdZGa&O{O*CyQbzSAtj+19VRz zNi3&T+`^=XkyezIu}zMmtkKWFfWyK1&E8ot8|D1dtLyihSDTe@UQkd1^e;QrvEt9@ z&p$bYKMLw2n=6aMd}K*hG00CO8J5wUh%nbm+upyO(NCLI+lj;6fj%ejt%LzuaY8Kq z-t!jiRqbt8T#V&^-|3u_*^cO_jq%Cf+&H)+28Q`oO#OH5g8HXBoO3#NZg*{ipNei8 z{rSO%A@f?(0+5f*M+`H>?~w7_oz6 zID7<_`|CI|5TR$_otdL3vuYM;nY?+Jc-)ar4M;O@pz6|q$sRN)x@Ae^ZRX>*F4TAV>Kh(EdEDiuzR+KO9 z>!(Llmt=}d7=Cai7b4#}okPCua6V=D?oT&6!UU3@!@ooR&&b%7(GZfLv;z76{9vKO z7}G2<^1pX`>5&pYpriNFrOfwZe}4mEePByHe%Hfejnhq#>;1+<4v(TT98|$~>xKb+ zR;`Nk!+5c8$<{LIngg$lqNeU4K);%mi~Zn07YFMra9MN;;TxAuEt-n_piork=pT;l zFuP(Z@PBifp@G2&Z1=d)GEe05X@mi^yQ0i!sJZbdDmwIDOmb(m|L$tn1>vhuPnwwz z?L@u+Ic4Y;2ZDn65a*nnfUbVS25xCSKiJ?GTKjvrva&2O8yX-%*%0EC;^efXdPj_p zD4^xz0#koU^1Pf;RC%JYx$y`_zTjjeQyaVqtZ%p(hp?}u3KW@{nON^f;Lv}_ zkg1@z%MwQz8{264FHZEN<64?wMBMNvpjVDxzFaw~v)ixrhHu|zI|?1_xWRLj9g{+p z_xHlrFFV`4uGv07SCnOJ*;0z%hw7(5y+f4iZ*4<9r>l>+sX+dr{@K{SqW-&4Q16^- z`th8<0xWI+3Hq^Ze!~nohFI@-*Ou5mMR5phP}_#>PY|zxo&kM#&bb-+Pb{u0VEm_9 zS~C8x4K>DcyOKWzKI!O9TNAu07W#Rtzo9wQ&!r&J(!SijcK`nUE0})J#pQ;s{>c9Y zJ3&9(?1+l7u_;W#_J-y*r~wU&`&%*p*IqO7c+ycGMbVGf-|Sc%I_FI8>Cseq%*3JQ zm7xC)62LeZANTX=(mQuBEo z!}`ziZg!L>eiV7S7#rz zv&*W){=c_(pbG2z&6+ia2#t-63H%;>_I*bGlY1FGD~kIj4(kimb!EyIFggzP6G43T z@hm9+xpO<~&2}{SeH>f_{bggwXl3$Au;&spw(iOh0iA(A&Mm3k5fdAfyw5DX!OzZq z67X$fh;+mVXwOVPQ2$d$QR`|6CueW0PiB5=Dbz2Gusx@RYX`r}~zRW7IR57r;-KrjUDYnuzAAAM1*I8g!X zg5D!#9h0I0=}VWDU|-7Qk3*j*w)rK~#55(?Wqrv>8^1*RC@2~Dw8Srk+^`m*HJN;W zG{C!L@--}vgiLr3VLzRC7461kdGd-Upts4W%GUHhD5@a@hLzmMElX| zH_{Y#6fc%H&YULlt0iXx3=3ljD z^us3$EJ3~#(}AIDpd3ZYMMT0R}*Q&~9!0 zo>@6{!ady<^k{h^R2%vk;3LR~#ex6RY-Z+wJ-9gLD>+RhC{3f_Rs$jz=yo%xpQwTH zSqtFUueJ4+5>YWVG>n@_#Po-W*5jqGE>X}Z;j8-)bb-!j{ns6o~sXAj+fzc`99D8gM9I#T>P9r)awQLc{!do z+`c^8pMU`n+IKZIv%%hn`5)AKEQEQG8=Y1`-DQ|?#fGyhkN z_jma=I@G=#_J@08|7Pd%3H0}DzyC7CabV3Otlz)#J*5AurvDj!f0!fohhPupu2}J@mcB3W{|os&7yhz8i?ii? z8UCH{e+M7^$8um^4&lK6XyOg=<4eFLR-IorCcw|&CB>; zd4GmaUYUP>CjATiYhJ$Cp9A%;C@3%3AHHA$+B?T#x>qL$y82s!K7e_VyD1-I{a+>D ze@DEZ;s00Qn-}==<@hYl@Egz{^dIW~iUOCAY4(jM1b_1}g6Z9RS=K6^oaK>t^x{>S>CcKHqHf2yGLD%b&_ z?>2=olHKzhU%bHI{{jEJ5bx#mOuA@)`)Wr21AqT! z!qn4xeUJVBbNV00g+*h>7Cp!RFMs#ng+Eu$SK_3RVDxdr-@yyGkLOBL0C1~Gf+2+56e4d$^HD$2h8|?qCc0H>3Z{UBH zub$(J7x;XD^%PAkpIVZ00LC1&N>u#Ob3oV>@pJVz`x z)#|^$`+fF1Y&l-WS$`J(TsZ3w5cz+QA70e&uj>DE@fI`rUoV&xns+pBGmiI^qE=@7 zi?^4cJs_~Ov_xnruzUmC$77?%Ic@!)m&=#)XK@y4zw`6;V8dBBn6B8H@cyOw4f{Xw zQMCU)I4Gg#5r3+Mao_3LspSRs)kG0VGUGtU6lOda*d{LiPn73{{^nKF|IPjbr|0$` z=zg>N4?YNsk6jlQ79F zTpO&PjvmhcAibWynD+SF^o+hSAZGA%`RTrAplwf#?_p8xam6FoNC-EWIyrGC5*s*m z0V-*EA<}2lJ+3Ivfg5As=_? zCpVp_FcA5b$`8@f;n|R0>2zVY^31ImMXoCQkdfnC662ZDa#~JHDSCKLhI?1HV$WGs zVfiyDoz%0#Rj3I&zcg{^OoNTYM54(3zB3Fb^ydfeIY545lBo~2iIK2fK!}NpZ2K_$ zCMNa#!PK{AQ2t&{|8o47aU3`qBs>LT7-ZqF?SJ}jOi#2kD<?#Nwf=}Wz)#Xbs~3hI6R0+|een!#i@^9jd%}c&J->b#{U%+BH^gQG{{(G1 zbAFoP?HF1uRl9A3^7(T$gop8Tho%xa@DK1~Uk0|PF<}byH*_>|(pO*0&la!28ot&c ze;7_P&0}$-ua)QC2o`7f!IF$*abriISGZUo{=k{z2=wk|!y!MTpyEux`zns<6Y=UTBtsy zZ_B&s$aEt|8{^V#nhkOh9YBLTj~99^OQds`+@t?OpHyc9PEMIpzK@b9fE;vop^NZ= zs;e8$>my3{>(X*q-z1(`~vNBC=le`Y|a|L>Sc#a8&Kam?khTXT>dT8;?w?oZT7-{2&a8^^Vfs?E&)deqW#v_f+WH&+(oco||Lw2M&h9lE@-G zeUTqhZBqvIGsL&cx65MVgM2$zbPv)S>G>bw_nnO#OU?|+H}X#r9Kd@nKZlA^v0h)i zOnl>OAYmU<8K)+{wBG~VT z_pxAmcX(YqT8sC~F|Hf%lgIBRNpu%JyKU_7+aoS3^1aRc#jJ^{2G;7=Uz_I(zMegu>Gu#RX*S{VnwDtt4w--$+lWgx~m-8UpEuI0{Nnbqa?B3U2l4>@b)j@O*aOdgg!jzk+8mlt9}S21o#hm~MlE+l zdm&|0!m>HUSmI5>fIDQ#J+K$F_XWH`7;~|`K;OG^!4$QaWl(=L$`JGoX+>CCyfJS2 z{#K|T;x_wlZ?>4-v;m7`xgpwTYF3?d|VD3@Q35l z?Q_n`k4ulEJ?FOZzQgTS;1?B2yGDxow@uX((gblU&*U!2F1+ustA~AWPDArI76?-N zgY@*q{Nwledoh{ApOvBMe6jV#15(3_@ z(aL|0XB=Hp6L;rhIvVV0o4!8%`x;F<7W$`3+SvQEK;JI#Z>6I`LEhz9-$sxh5AoD5 z;ixyLZ0SIU*8{<^Cnw}|8zq7}h^w;;jU1DMg1pLOu53_AX_~3O@5OF)rJ7bBkjF=C zyePPupS<)Lw0#NP0Qrp_9@x>!>O%6|Henlbo%4F zwh8_h$8fSsx6_B@2jn3CT~U_=>z2s-#hPm>)KKMxmjq-|b|01<9rjP8jAHWx{We~> zJt46{&V?sl5k25;PFeb(Ru0CaUJ_(&iGjeF}0>5=Z&r?BjHyZA-p$EwZ?0R{^LaoXnW3Tlu17yXPTKy-Rmd5eme+**4C6(b-WocNm$^&65AL(2^XW$97YDj}CUbv1 z=CKmS7dzUXWI^m2v>#A^4fw^t{n;7{f|DZvFRdwGPuEna9NRq0aXb_d+Zl#w@r}vo zL*s?n5Bf;>mIO-RKRnx2*tn=K2<=S{@%nUz-UD?M(Blmc&M{+@M{if(936dM$MGQy z4+rf77(aqJ_|Mb-B*Ba+(7tg>fs;Jqnv(kF7!ArB8MJz4Nr_+mnLD>Zf8Td#9c|xu z8no)c5}IcE15wMQZm{LB5r=2nIMz-bEy4PsL;HPWaQsPa>!Lo$a35Cc-_17o@SJr1 zQk6KLKA;sDl%FNmr8e;sp*_GEI=+@e@b`*6G!GG_i=*cAKWysK+6R=T7DfjSn@c~8 zgN^4J>@ten3k0pNP5q44$klQ+yie{P$}g*1zf^&@A2-q^D?)a6kQ2x8s-=Q)``ZD(9`Q98E zzD5Y2WAwqWhlw=30j?VZH~G*#!P47xKiq-ecjzWXBiIk*KKpVELzk6)QPB5gK#-hM zX&~Px?J084clfJ{mT*s6oHSGp8yK?WzH?5Fl~p1qnW#0a*w+;U@(Ay_PBYAzoxAbC ztgRq___VEcn3=!tc`GXX&)O*qkVTS)%*dgU{RmQB=aOpd^z^7p?oEE z5$==M)T-f)^@;wM1e#>gkF-LY1-8FTR_KoWhZLFUKaQtZuSsTzDZ%<-d%A6)G?glq zS`GY=#(&SA8Lu!Y7DD{v$9wKE2>iJkW6d*fNeP$u)t2aV*{y zBji;Lna3KOhqs2N33{bZ?$MPfKT2wX{{@S>cQq4sYsWXBUyli>w`Yl@Y8)V!GW4*K z1odq+c+=vLFMSZ`5B6vLd@Z#N82X2kp8EN#=jrcZ#Z6opdd~su3!VKi&XA=y%<&k0 zdZ_Pg7W6-G9_ag)fVDX=D5u=PehDNaR7%kHwVDH(CTPF!8y-|M^^w!%D?`JCn>RZ} zBaRZV5Zaq7muZ_9{JLT?gzLsh<}>XJ_|+&^;Xdyr8{j9#k**Ad_>;tb@Ee?p!t{!v z51_8-ZbSbG`qOON2nK~rhUeUKsNP%L$lz+Kr+Y`3`=7cMK}>Go8M!e`IBA?sD-$lq z{o->H-@*`t=AbxlEg zU9e~yH@qU?mh=kN5$3}jWo_#&YK~}<J^WA4E#AkFUGoFCq zW})Y`PrjShpMT9Z)<{^`Iy8Ov&pw3!oWhL5Yit=)2Izb^(N&638je_}-kK8wC=x0)u9OM`5v1JkE zvv5AA4H^OMrNka_Bg+t%y$bjrJ*l`u%jqN7zutEk$FrNt72THKPS(`Xnhw8o{yZ{p z(rI|B4!RVur(A{g52onkr4;6iY=m=jL~%5w9Z;hErXG z@+<+z))bh~Xm6E7L>beE=^)xPfI$6!F)EN`Cv?#s^zrr9j}H*{%<(MTKR|p<PW;)~aMC7~0cSxNg;* zAXPKAuN>?bu#ZW4mWCd{=~Sq{i{pcB?Wgd*{x-m2d?R#@Pl|+}j4O^B$4jPZdj^37 zMeZy3t4G}XRvQZMf%%UcoBF6>HQ-{}pQbb1FO12otgacY^6$U?P}P9-t*-WB#&<{5 zzv&MTEfBo_V3{_#8vCDP{OT$9*0cH9#&US(#rD0lLK;9P(`BfS$)JEhv|f?FR)T#e z+~l*Y_EEwkKe3{u5bT9Dewt?>X-Hza(0v?lUV?|QS-5!KN0?jeq8{Gta_&uYVw9}LHCqKcPd z+oKB?=3BIVxac@26AJ#dT?T$g**<0FX|*=S%Fy;PRI=t>ZEUpH=kxrNqS{%!!zTWLM=N{VG^ihJw#vST*b<}<(zjbuJM)edG6V(U3jDNd>fW2` zNoS;G5Gjq{;g=WeH-YyxPa_80$dNPlT*}j%8t6MEP13Vqukx0jIr~(i35;v=)lgwV z4sX6Ve+yF@DeZ^)1V&35%C~a=hLW{o?>npvH8xEl{D}a3dWyjDnh(B5&DHh*I6i>& z&oftOiveQyy6K>=h1sBRj(j(_e%Lc#_2fffKedcL$>dK<`maWf7q)j~>;)Y9;Oo%e zfW6l@w?#St{0*KfVZ0!?l?AhTWO!|I%9khi6{cwC8*Ei2G z@ce-Tps^^NPlfZKXM;Y@iF+o8@fg@Qbo`;w*w;5e{9=f|G-yLVro|jl-f|{3_c5p% z`6J)r8`Z>l3=V%HHiCT0a`v$}>UY)25Fv{fL;o8{UexnrD>B*ZOFfjeo(IYObVlUP=c`D<|6bVW#+o*AkU$E8GR;C+Z#}tt)PGDpy;cO znvKfJ@7fM51pUyOBU{VH9}4uaHTzP;;Bs0T=6=D#q-p0Y#PR+1l|bJXS>Iz?D(!D< zy|ZZu%)kBgZIB3cGnROPa2&5C@?!TvS_I;Iq$vZ&XYz!^DVQ2{IP~BALro1bCOZ6C z8EVAKy4d}M#U}tgYUTy$={+oV9B(u{5W$9<0RHxa2jwh|{Xyn{Oh3QX;T6iapK-9| z$g?v{6lVN)FJu`TAML$A)XggYuJ<IR)FlwT$rZGOjw%0(RHTkxpo z-V=VyhHlkt1^(|XnGpn0rRvJwGPjL@dn`+$>06p67~|r2 zJ7*>tSh+vBUw#YLOJeUkc<+__8o+8rr9qh?w7)ay9Uvn1!Fb+-cmVN*BrLGrVx(&y zBhRbLKSO!K=?6yt=b!3E(PK#36_w%sXRZ_4s~WnAhW^4vcrn>G3d6xmj_tvb+{}`; zLhsz#i}d4jpK20jw4P>S;QUZr+|=zfE!P|wpunCA<%?94cf$NWDik^s?EU?reGfpl zXzFgs>${?CgzfdDD5+C==6Z?b*=4Uwg0=zgr<$|WnO2oVUF7bZT|!DCqn&)-E7eYf_@1H{k~J)7e?zw$T+@xRz9}%c&#QE z=Ol3biQJIL!Nx)vn4FWwmsnLRb=fCpr3?ApPvw2;Yj@g8=qpr_`!PYw(+Asc8;ghf z8?+gBdfj*Mga^zCfrqGGbByWFNpi|u5Bl|ia?s)1pueup*_(&!=Ri*wUm7{4<(Yh- z%OB*w!|TAeM+$4@R4djw0`$X2iqtP~ZUyQ~H^O>LYjxqbW$KMpWitOJ)R%JA*$Qp? z0nne91X;!FS^`m{0JyfG=h5}p?6uA#GwKYNZM7M&sH7XC*M>c+!1hv!J z_j7AQK9i$*0OthnynOG85o#u%0jl zCU00b5ys;_9zQ;%PD-kAj_wNBuWEI354~^yl|pqt4dX-p3F6ujHhv-0Pwq|X^-(&1 zf@AQ~F&=Dq5!8o_dThtwQd)}b(YsCZ6mj*zf$Jzg^TOhAJrkV<%R92x?k*((j5^#s z0qmux_77^+v{+r1n`BT7@z2Y^o|>1$$32HQKA0~eNslHXANqhbLqH$Q1cUIF#!^Z2 zC#cYVo^6%W2h`C>qz7uTKcF_RcEyjO!}KO+Mg=hl{DIkh3$ATl8;e8#;P^CiTbQnV zpg+QdA3CM@YN>(aQzOSdbrV>YjFWW56!0}0lj9JRsKzQ`{@cOlww0AO7{9lIKQQ36 zl*J>{3;5Z+&-CrL#odK(YAXLh7cPhSJHe9D0WajjmY`}W~)&~Fsh@2*I+lj=PP`K#0!?jbPW+0&kEF^7;s z4^jyIy;^<8$`Xh>&S2S+4nN#a!>$*>oH48qz+C76@tH*nHvd z0)L6Uw*E}~75t(TCw^kXQ9eFf_TVJW6`%`){GAl>@oqel~#WC)TIL-p%{L;N#%lFw)8=ktc=)C+u;N@i-mhW$Yy zzfbl=MOSF~D+Ba*@o3S_JDb!JR53=5<9L$`D&J}7f5s$n6}DTLYFb`n)5Go(UM1NA z{UJD56GMFw81wldL?1zkjfH9a;;-9AQzE$+ouFBU`uvB#u5ElsC)}C1@AbkApm%6c zQgSmb(P-*4bv7XXrAI!AIUEl?u(WZR5v0F1sd~F1SpO5z{&T-CI3q6A&;Q{1AzZ^5 zxkN^ML3k3=E(wf;8RS?i91vmo3t>D(f4GU!KUk)fzz_D)2O|89ZUKzH?w&Xq4?mgl zjSKXr8GY)QLTGEG0n)oCjaFh@bPK_L+NZA7x8HHlmwx@LwEZE3Pk{C+X_z#Li41>i zVE$})_7RX5t=j_pv25AC!Ej-{z}xGB%V2m0zkEbw09{8XqTg{?5BP>oOJNrCDOxho zzkuHm*}n;?)k~AW5A}kWz__^6O)$O!dtw2|m)c4e$L5FYO_AO}2%TH#pTVE?AWUEX z4EHepJ2&N**>L2?O^szHFLO?R z$W=)}dgQ(3q5S*Ka8FW-U_*exyM*`+q<3ybX(y1LaXA0)>oYk2%j_pQBUU^&9(0E7 zOWg^YPk?Bj0KEzXvu4HV=?CK*-Zw$MdU?nB(ZG3=hD%N-Tfhs6M+&_R$Eqv8d|E+! z(s{rES3qAR)0CdRxZZYTbhQoE4;>%o7lXv9`C5$WUj(i163luF`gOs2o_zgN4I7T_ zuj~t&2g0J6JYcLw`}t|YfFp$0!av|QBVOIJ?Wt`1ZRv#qyIIO4RzFOH^_{j8N;UTF z=y1N`IQ4#@7DM}E952uIR4j{kdd=tk`7Erz>D&azP`=65%4$6h>xCnJAAW?zjU6vq z*j}r9n-L!H#QN5fM`p6&9bSck^^a=WDmhXQDa<0^c&0DjyJf${#GD!{`DhpD4@!Hg z^PVN2?KgPrp}y>u#(!KbsqAl}VRVG!-!y)UPd`D=ymR0U>)Ku>zEZqVEvI2d)T;vd zhg^8)t7b#mP;M{_)`tuDLkTmkj{F4f2dQ{&{|Bz;4({iGySQOK5chLDpgU;~4e=M4 ze-f0OuavOmUkK$5NDS2LgRP+t1as%=j4g;X!9PQAs>mMc!8jNnI4)fhwTZ>ALVrAS z=3ISyzX$ubny8QT?dt~k_U*lTdwEs*1jnG{vpC!bhT zX9M)U-tggWhNv)^1gP}WYKeS_UnD23h_23uQf1O_3-{XMm``rG| z=pX4XVEhc@$@;!(ZE>)?HjZ~bYuDEo$A5{^Bt1R>+JA24k%Yncu0r8TQ3Q(*S(48G zLG>h##g9Y#li8c~?VpuCjt8thj}1rqd_yNN_FWsjAI3}df(wlU`tNo4)eC+Q4bG>B zhfZ*eTirZ`O@AELAL;#%ujA@%BX}Y|%r;q<%Z7J&4RIW8a8$n^2=(_IVWfV&264OE z!d^B$+GD+NgB6P#gZ+Kyu;l+S_wI2`T-&4gB)kHM1eH_lIrbzdNKkpSrBrU z`fD%XN`C{#*OP(o&8m(l-5dQXWv64IK85_<3I_;i=majSHsL(j=aQ7=;mm>$lvmNR zYWSsHbHRLG6D;^*VAS`^N?y$O(lAT_F!h%Cy^$Lhct}Vcp`JnfSLy$+ojWsu{Vr<# z0=O7|Wr<_j&Eqvfer`U$__)YoA8Ilx1(*9`vYGF}`Eop(6=8$1?M4dNFKJRnA6*Yw zcU#b>zb1;zFx&)xP2i+ET?NvtrLw703m|_W?=#*#`bE{aa-N|X*H3^y3-mI91emD% z;?;tjebffJKlq39;cvWuX(1LHDUd&y{rKZpi@v%aS?NE}-d;;r>nT0bBTM>cGQ#M8 zqkL98%}J!|;h)KTo7rVGf2+XX3;iDEb3DP{xR){`l&+7b#{4!bgx@fGniIl9RKMPK z#cFq!?PSte5_t{f(jT(&4qT{xmH&QNlAs++!`jFs! z_?Z!XGX(wR2}JRi>nJg0McE&)x*tjUU#B|yEc65UE~qJ}VPJawXN~t?Vas|z+W)Il zS@^?3?~x9`9?U{}Fl1+*BEB!ai}?clebpa{7Q)3k_`Ki@$B))$L)2Ny1M($f2d>{T zf36nv(PaM7UF}~`pBug}rFPT!{Q0HlKE#)zm>eCm9${y42!ESl#tf61wpWIW89M}zJibZ8 zA1SbcUx)Oupbclf=p72lX_w`2DKrM&-3LURn^y}J>D+MAC zk9kacv^QH$?u}vKZdeZ_j%C(+V*Y17f8Mn68%#aY=eQ=u%%5=ceq^5D_Mdmro?OgQRE{-_HL-XWM5zkZOZ$9Uz2@qmRt zkbjSF+h&Ou(O#{H{Iis4Z~I=ge|Eh5Bm?gR{p9h*gLbVI^$$vm>l0x8*M`h&SE89^ zvZoxY!TGzHi^3eX7>oQjh{eJ`b2lH-`Z?x}qU;vo*NME)eS;4?)AE4f1|7m}3 z)OvKRS9L=?FjD^7Kk5%yPHfR1xSn+0sP&}Cf6AHyrH~R*eiSi;`Krc%Qj0#ue6V|g zo#p%2B!>pieRX{GKC=rMhxx>o&a@oFkJi)${zho9-HdM|en;5@j z9cB6$qGw`vHLedC*5@$!N4xhf_^TX^iO>~YkRSeN`63D+*5)y2K>xo>!eo%(b z;$X=ltY5x6D-K{Ib?yru9Hfi!msKCsTYZkrd6v~O9y!fm6FeSvM-cS-XxKktSIUj_ zd);q5tv{pxw$>OhX!oc6#fepdyFrmjYau^*=s1x@2(5!bKiA0ENS}_@Ez3P1{x+U= z0R5z+JT{_z4&etl6Kum)$Y4#o%tHS-KIgqeoJ+@o^ig-!HHl2H2N1As6>K~E$8@g( z)NYga5kx5P+X}9?3j3c`MGz?wr2j@`Q?tf>4p;!}ANJQ+2lH^Q4hxQVZAqzRy+6Xs z{Uf$0WXpN~}x*&aFd&$c%@iGkl|43_m|6QPeg$1VNTldUjf6#uUy1vLC zApe28Y0)RhU!(QQ{~cz}?goVewj&Gih5paEGdzMypzOE8D6E4o_}fgfA>2n6Fd@Iu z?MFt4_y%<~`u#z`j~1BT{{Z@q3%rMgf%zjaJ^m(mKM&px|Cz_DR=JONVZnc}pWJG; zBiGUVF#L=1H2LlMmJN^ieqevg`w%}-2dd1@)5!Z^`K_hioPT{`&&}_Dfj?TFM~xrf zANaPy@_wW@$iJn({~Jupx7+CFt%70y=l}lu2Z8^O=MNglTZ#dn2nt{#Z zf1`&O2lFoj#1&P$J0G^2_hG#q;bpfb0{e@8pJ|WZbJypD3j-s5)>Qu2@;x;E#$C45 z|NpH$jqe|p-e<;7@ZI_FGdGs~2xfe|peK(1jgZ&>kMySN^Ip``-<$WwOZa$wkx{RM(|%iq}F5pbl97$OeZ3{0(e=kE$=KfZ#v!O}ab zox-H<{DjDe2#1_#Pk zMp}zLSTMDP;C)X{=Jy$Ag8pF@I!uWstL_={{lkME@{aA)fA``-$?F|t1r3_P@N>Tb69 zk2sz;{vyvmEO4>pC-8m1509As{>m?GKA&H*Cp7&F0|f#L1E*%N_yMA8*X^DTtkA6}>R(e|>0g?Y_6y}; zT7drCCnpd2Z;q6qJ@O&Xr4kaeR9&BU&tm-n6%oV;sGO-ddxQX+&6o# z9itLWI!a%kddxHU)Ks6C*hZY~AFfYpZ<*Ep^pccgOgsZi4vp!pdHx>POeWs~4Lgoa zN!Qe`7CeXLch@=m*bN^X?s_Dj&zJOd_!2Og!y+7s1rnftJTw0XT)}r2@FF-b@&8(% z0}OvN^_E|cNBp`hoUrT#ep&pB^CTA-80q^%>RWpl80qu;-_P);J?Vn@Tl>l1E%B!r z->DXUL;ctcKRm{?$LIYoetnFAv3+&fi}u*x0k^P9rXKmJ8yJv*vHtaL&&3Rk-)mj5 z-HU;-|9R2}mU^>(9zy>8nK(l51PVT1i-`wGfWPRdxWLUqX|RSRS`*2d zSXk&>^_Xg>F2ell1^*qNIqZ+6{W)Z`Q*6bGg*;Fg4Ic8LPz* z-<01s>2K-uRO@ZGi{e~P-E5DpN-FU;t=C1}f07RQ+kdt4P%)8XeUo#M{hi#MAGdA8 z3eZ0+p$#FtFblqC#i~hWd|YBpmB9DHasUQDG(LOPO{3w_@{RaAIkXf!cXL5_>046V ze?%9+H~*lui-n(%9-n4>@+nh~?T`IlK8JzL`k?nEeIOY{f8?*~A71tc#(h7>VjtK7 z-o&P7Cv{AJ*#CpmKUw(if8wv6W0JO!_S+Xk(BJP0Fb{qy@rRMSxx-N^d$ExWljlrfqu@;iAzNPe%jz>>JxFN+ng}OpdIqODb#8~nNVN#|4*9%tQ`cS&0kO#R}7iL^V0P{zy$soeJ zF-L5!1^sgD+^kOb8GZVZ8(yuQ+{Eb3gO8~uJ*J>)$YcRkH}`g8!G!Di0DHHt=Jz-1 z#Eo^-Qo~7YXBg-!`zxurX68!3_DHi$0`ee_qE&AfG@{jeeuJT;R^pEnI?-gv#z*z4R zGA@XLX?b=Jeo3C+`u3Tnf2|c=_ z^1s^kOqhjL)RUai)_0ir(32Aqgi ztB9w~0be>XAOPlk;9@U~~NiOFhbO z>RUggGWAIRwbg4a@c%^biR)fn#kBvQ>hE3{cbTcj_szXIXDI{Yc*Zrq@DH(m{BwVU ztclx4oFy)YXR!IT0i`=`Tc5W3IGft`NZ#qTd28=OIBDU`ih-NAzZ0|giLa=elz=~t zQ9Jw{*@F3tzAk_xD8;of3CxGgJWkWk*bQ^8v#R8^Ho4~cy<*BxBR|W{{^L>wrNFSkRxgNkmry5f@|x68{=9?xPfxQ9scCoxQ?}Kd?bcn~MT!iO~brBi{U{Om*8p zwd(6AW0;gZvAafIrB_jTl%uJ*QV#j6_MBD?Wq5qg)SdrOZWEjh^C`tdBJnCuG4&yQ ze{9?@v_G|xa0h<4Hw&^Fmi#c{^JdR$7WyJw{`>eI2LDJe?)H3(|A+KgSUJ%m-+z^V z^tax){LVAxdF&7L|8oW&k^k)s>@FZCHtBlbVPLEe%5w=vc#1G)u$Bb-Cb|axPata( zp}o|FaD3v4e^ckE9j1J0VQb4QCu3tDm3W-azg?7v@Y99{lds*CnGCCe-`W!#iw#GqT>Y}| z4yQ)W7rYv9^(d{dpwUIJM~)}@L^pC?4bQ2dLhdJZ^3L7gKQx$gtw1mLn361bkfc)W z1@Dm!8i62^A>Mfw)zQ!38|kHc=k81LSN{8JFZpkuo5oxGx4*VG#|K^({`f2X(H{W! z!{W~YKk-9ctlgOR)AAkSk~bQjQsz?4yOnjh;?fa)7wGrCo(A*kSq0>aTWw}%rf=)i zjbI%8>%NBCEHl19Umgn!cD9>$O>eF@Osx;*tL`WdBWMB}KNZ$noTK(7jI`JPN9@ku zmYq>-hE0YG@+tg#g=;gI{_GV8xnl?ayoRuDFs{xvBbO6(HBCKcKt0eyZ7lW-hWc}_ zjnfRoL5m^k9CvDls>~smc|NWE{H(Ul&|++f{l)y)-@9Q;dIZ^g>DpKXNw z5a>jN)gSVgUcO6R8G==PPz0@HP3)y$>P=HXG>uTiC>_#EP%=pf-c3qW&;`a?*BoSMsv} zMxb{dgZ+e!YIn^gY;6Glz&g0kd&6<{M%bSTB@Fm4Cc*w1i1r|Aocl>u6B7+J=dcb- z%CRItA9<+V3{g}v$gA@g8><=EroNNA_f|7QUTTf>tXo~sh8Vj3^sw%t_#l_Pao=8K zjzQ{Yh$lk$ou_7=g7vM#fDio+GKr)5Mjkf3fka5=k zNBJMo{}z7^pZ_&=+*;_5hkecmee~aeYnuq(!3mYxPMuc<_;Xw8KwmdeqG6N%qIf=E zmaZwF@c||n;YD_{4I?lW2K$4$1|9AJY3OoMtmGE_*_n&<>+Qes+b5te>r?7JV%pp1 zt>QSs?5xdGbCz5oxy0#8j^`#HCmZITgtVFdZ@Ve^= zL~Q3=ig-lpH?2u?!Up_@#Q0>kaex}^rg*~$7oEAf2%e>jT?UVEZhx^pDNi0mD4i?^ zl4!hI-T94M&yCoRL27OL|Ip6dB!@tolJ^6nhW%|BlI@I@u`^4h{?)8T0nLz z{yRC^ThUAUp?BP3i$6&FZ+R8g=Jxmq_S!#))n5P+n22!$uY~jsAZcQFAX#t27SE!(c~J?jkk2h->6R_bGZe?N-3c3mhLpA3xoA1hrPUEz8QAU zZ>Hq7vk9XA_Lwm`@P`d1>h^aKU)A-@KzdMAvB|(uD7i5r(-Y9mhU@y%6sIJFzoN6* zr&~+uhl~S{@6B3MPgGaKek@wzb*lCx;xa5nsf)Q0ixI)v)_br%B@W~fmK;&kwKWnY zH*m1|TUeWT8*l7{_|LZQ@1^)Rmat=YgMNvx*gtaok3QuX=^vwyxdS#3eE2q9DDbO* zR|D?@aqSq=g-JWz`ZoQ`yTgWZfboLl+Q(@EqGeK$jEQlcGJ#YXGyben0u_XV(ONJOa_yjnueSV~U zLwgn48VXkU?gV{iaTzYE-fW3x6%JfK{%OQtOi2xTA=u`0TR}o((6jXFjr@_K>Kzucy z?_q40Qp0+zZqD!cZPvCWeIgwty^K2N6NtY(`f$GktVV|hUad+_^5x_GI&2LkOuC)J z)0W4;_VqebQx6s1F(pD5A8%f83H!Tt$UC`1l1-cG#_-Q4(v=(^t7dx!ZuyKyqy^%9 zrKTn)FlQ)QIe1U9U1GM9()gWMgbTp4>e9MO7y z){NV*zpTc<(h)g?S8FhTK%5ZG9w8ttk3BE^KcwGe|Ci&l*ZglckES=cck0pbXnzm; zTYpSq$={&9NCgE@Rk1-P>-kxS`E7ki&n{{VoX^=pU8(^%NB^l($qs77F9-+v#y2V- z5?(e7v?n0GhW)WE_t~c^sbZYor#KngIn!zlxE@W98uk(R%LAsqm>#f&X!X4^R&nIp z5)XHd4{ub1eQlUg1WmI|MIiUz$q#WfT1SBq_Uh=Hi}*XG^ZDv; z2IGdaWtAtc7wHa*&vKRDc6|=|$fy}KXgy#&?`^Sq8@g)!?!>SW<+b0Fr~z0*`A|BK zVEBz+U%5Bc{g@ii88<)t>>PK7<}eP8!bM7JJFJ2)_qK}=#^T+&ifHZs1z|&L2dpov zS8`4n4FPEX)l?DnTY3_~W9M7=<+pSmke(Nx_Q3_@+aDl`>%NRQzpoE z0K8NBE3eS|bVCloj>&&qe5nialW`k0^!z`bKgHs=Kzztz*nyz)MTTC>d>88TAkM)G zhTg~@<5q=uGB9oLn_tiy>%TscoQL&+L>9~3KgYUJ{}k3EUZB2*{FY|BaSLr7;S=l? zEwe6{n%9S^DV?^KyvsG1n9m&odw-6rVw;2-s<@B3tcUe`=eR$0%4vDI))RUS@~7OG zNo+NA1=t07iahI$k$mz?`NQTR zgM}Yx`Y#O0gWz&29O}%n4Tk)jO{<7F5;8bMEi*#AKCx8meQL)v^N=uJUQZ0%#_t1m z)S|z(JH6|x%k=05fSx=!jH)b(;Rlo_-d(xJFkKJ!4dCIryT2fq4Rb7OK)(mpGkac@L759DCKBCTu?d3gZ#*+qGwR4I>VcPEaz1->K^8 z#`-IO@!yiB@ORxA*aWDj`NuBfELT&wm61;a+S?Nuo4g=mF!c`g3_pT=e%$8xQyhXH znPpr6`ULRzh5b6Bee)?wi~0Uaf05`UiTz_)Ek7g}KWFSyINkt83+s{U4{{Q>ZCIoS zY!na18h?ZDlWquHiFK4wcljdlN8|RhwPyY?QYmDT;M4D&=#v&*=>!ww* z`{1N~Vg~U~{{3`5A09WaN9KyNSp370z#F^F{J|zdaQ-xYvzcO zoMP%z)+x5c> z55xH#Ubmyg%y1$t;7M0gZDW&EY#RCxcF3BhU1`#iy4EbDLs zelV8>;QyP7RUl7LfFsvPg9x=%Qw~N0C~pGAb}qHyHjKwy4ssvKVF&~?h0+H4Cy0RK zIua>8eFNe@KJl+#Yx_)wQ-!DyACcf+7$}hjeE&gX%?2EA@X&8OY;AxSBnx;%yl4r~ z8%rjc2C)kJW-~W*x1GfF9MjNW+LdHFM zZAlGy+bgbXkF1I4OC`pIN72N z5kN03-s2?miGZpX@%MvtKB0OqCZ1`J`Tt8IJTDiiX@dM7i})(Q29)oh0aX&dFS^|hhTg$Q}zMBg8V^)|A4-)^VWDQ5O2l~z+XrEvEfI$@ngz(FNjCP#I*cI!|=~T zGKgc>5B{S8o5Ow{;4how*J%Dn__y>`oS$EPHXr}S!YNtBVQa3%>xbZbbg;g9*7>9e zPy1Kfa$G|iRMeIE0!~I5p{gLdrw->UJ&6B+yjG2EiqYBWmwK~A#16>EW}S&iWaJy^ z?K$p+yrX}=wz_QXvI&sic}g6a3iRI6-st*(`hnU(g%5fL1Ajl;5U$A|YJlOGa)JKQ z%K~>_J`vU5c55?@AEH1e?4fqjB7*peJyP}+j-Be!4etc`6ty&D&@lXSSiGHEKdUs6 zfo(IC+)a0+?b-VcQ{8Bg76}nt{e4@ACfc+{OWKY^3i4STog~s%OSUBGHAe9X3u)xw! zB1U9zH$%wJ@IF{T07}Cp0~4NP;+_-I&NckMTt8 zjl>)V<`IyO2owq#crEDn^{sst`lGxrD$*kB(Io`rJNw#ti#@OdeHl=fxe>6X#|FrM zoYP+=_U4(yFv7yAFhBO=s%d>*MC>LT{xsU2N7rAooIi>5hUveT=7&RkP9zikT%3c4 zc)~VV$Rmi$!)Lp5^<{JRh+N$EJn(jqz8%|27gkVk?|eYW5M?EeKWQp zEg5)x_aAp?ga8Fv{6QW?0qy2misBJ`?k@LycaTLB-a~%KT;JhzQv1Sw_yvf;UYd)N z%Om`=HgzyCA3UHbGTBP(AA^pa$C8Ml>>P{#w?@jE*z#>mpSit}3IYB9Ra~OQzJ=8v zEN zM@lE`@1wa1{3GxeP86BX!zniDhw%BB_74_~wAUj(;u90UhVzN45_unIc|@=#vibkr zP4FFwjqIT@_md!B7?p@7jV|c#z4gZLmi#`werV1Qhnafhj{^|zt8N}s9AR^h;(Eve z`3C_fjoriGuUtum^In2IZ7um38{mhg#~*(Plmw6!qPdpIg4j{crzO56h9w_ar9q^A+kXTa}$FJ5%(BpPgx zVT6S%vcaD+m%Bkc!Zn~T8*)Pwl}Kr9GYF#Bry2Q+8Zg|`U4ZuDgrENbpq^SfP>j!u z8Ycj3gb@ycJr5*inBJU@?ePmd!C!Rz=o-ym!*Ht_yyq`+x-9#V4gtR5Jo$KRjs^X% z8G1NRL}&hfeBbmh#ZS!iYlC=^AoO+TSD0H<6dxokmxXJ6%?0D9d62+0Lk1I%2;sAd zG&iOm^@R-(M?a`H>IQU!EwlK<{O+J2>|eW+Dzu%=<9tO~IZim(P~5FE89al7_^%Lt zV`Kl`?QtBIA0qwU?c5N`&=>W8NZ!Kz42=1|Zp(Tj84u$@7|t^8%?iymlg+B0dvty;6ea`ze)A%bzIjk0aVsx|?V)u%6GlN_F%a zX^CzD`0WWXbBB9Pu@DnLdxEGRYh$7s^c`SP>GmFVABc<^`VG{5X#cl%Q4ActLEk|9 zMy{CaYpw-;^A93g=ik8HT;Or*P1t{F>D)(@5k zc}14z5%c*(MU4$CALvE~!snfL#BVu`12h?ia6IpcvKJzVg-`G}`QdpkG;o`O>yAMkJ$ z6%Q94Q!V0hJcIfCGk#}6^;bUjP>g$pfPCJ>hOVsWo%cjUq>$)qf%R;}kK?K~gDhb| zdmnMXVT7M4J?i)Nn2xXMO5l-u@V#GIHQX}8C_e;Ic3&RMz`$L^Kl=lJM_3EzH=%yZ zj{jiiNIm3#2tw;|Z$#hr((;+nTy-A!&j9C>>aqWrV{fAWpI|&aNWV`M;Q2E(G;G2? zE&zRYtllJ&J_de*Q6T6#)gKay2g4mx(Bt~b^0KZ|h1WwN-;@jm8df<%)Dsce$clnA9!7yJ^ zNNI}b{%t%w06+B|Y~iaHkUvEgr$-jz=9h^>P8w!%ws28Ow~af z_ru8#nCDS{jQ4CiBpf0J+yl+|qa`hRq_;`?V^=<|R&i)O!~s|b`BV<*Cs@`gZ$a-Z zFG!V#_BrjLl85^DH~1g%Bf8D(KQ$PT{*->MA9a|b#J&7rY;R+H;tj#99{M!nt5g(Y zo_#mdJ1a`mh;@wou&wPmbhZ!f;rjz%0v%*?h`L5~AbnyM7FJ<XWG;J^Ts=O8Q9W3 zn2_N;8+d=Ct4P#xoI}8#3KiN9hvG>pw{%*N2-<%yn7FSL&db#x zMbV$U_2#LZQy>69vjo)lkv0KyrxQ6-TqCxkJ(?&!ShRbP>NDmKUxNIAk{k8$%2;?7 z&Wksg5WkImPTLuJ0lR~W-EGmIl}OLR))Zhz)S$FJ(Vxw3fNaXJ&ZL3ofxn8AS79Z# zLH!W5rQPCR%m@ElPYtxw)$k9S5AjVj*z5Qg{-p<;_m@W%n$RGkz7i(Ac)k<#cZ$Fz z#YX6GwD>4hiS*j{rhmY&YyY+(4#h$G{`Ss=w*H*JCL)llw3)q~JHb)w%Z1#&9cb_i z{w#h+;)gHI=PSmaSePG(*eAf7u${fb{O1S^>%R)}L5j>}G9ctMEFfiIl;3cdyoC&G zq36gAtiO<)ca*88`9X5QGJlBen_+>8p zME!EzsDBRWEU+)zIU5f4Yy)J|1^w}eKVHNGwEV_lzTT_>TA;ssz@OQ@r{UJ$Y5H_V z#w*NELw-F7^>soKtk0ke@=K5?+n%YX`-2H(fSagSV9nqx9!F%;0`qgHVRRbnuO!7;Jj=;5wzvKEciV5Z^WKguI7MoK$SR=oq|MCIUqX&#ZQer1pd zM2y-IxIVlT_6vZENEf6B%*Td}fWX}tzrR?2h4iRv?(JskkzTL;^kTgZ*2DG?%X}Z| z_f&}Ynf8D6|MB@_qv#F%4(o-K1x$Oo{`bc{0Mk51^G9#_@5fu%d~CA-=-&zX>Y;Gi zs|eeI|Il{!#r*V0{`nQ6k9W53;?1+3!N0nN&at-GyVZ~{i;0R!Wax|jFiXa^$TRA5 ziG=Z&DXP;A{QF&{jK4vJ!1GI%yr@Ti3(z$#ex$?SfFE*CwOiyl7uqk{5^aeOCkOM1 z2H*ew3Dlws=6m9#JtZ+DOADstfro=9knRdYqqvm;m{CVfVbb z+ChT}`33nuXsrqS0|Qk&)!-Hy%;O~2DbM>7J|OY%LvN6u?}+^HrTlzP`LiX6ODstL z{b3m|&ENfQ@IPs;B?zQ9h^K|#*#7ly|3yr_nf}#f42<=#{F|l~-a~lzsu%eov=_>} zm-iDap0;>4^E|Ex)y?bfVqoMKcVHL>#{NECSc%8s3Gp84v*WIt;wL@uynOGJ>U-u;4#SG;0eS-wEY%|Hjf#p*I@n*+%;E(;n!cx@rNij zcBG#6x828u=e>a4CldJ%yJ;9)K=iNMH`4w++&ZB_f1@W}#Kn38HEcqAAdOq`5#1jC z;e*Dt50qK>(VY+Phky_o(*^mpf(YbMx*M?Mm)eB%ciq3+1~Lb<452?A?I;pWhmXQ? zKZwPL{n+hhh#*j&ZeCbAXEglynDoI*@bSfOF)+^WdW~AIkNUnBHuNy{i2qMMePpr! zNFT3!@BGpAmwiHB9}S1pj^YOad*a)7>tE0t#`nDEn9Jz)i#*%LF)*zUUi))A1DnSe z#9?5pU+l8rH9`gP#vLLn4B}H8n?~1!fsN>1QMp{t;J>BVa3~KdQ9rDw=jV%kE}y-s z$!qG~d=lm>JKg@CJ0=TcOMB5L>s`d}E3QW-<9QdiP3*G>$34H~W2Oeummn6;e39=Z zVSc7@(}R;xuLBisM1Nc~^DTqhL*K!~{_BU)9xE1oPZ&X881l!Uy&_UY_OyHOPrgoju&Fw)cW)B6V*80GoEU*fsSrnOU;dTbB-=O!{R(jV4; zTHcTKi+nB|W$FH{laZ@+uE3W(Xi$aF7wzENF*an|*3!FSpYXkdZ786qd z=JTVX#NsS(p&EWynT}V_`WrEB3nre52m|50L}ZvEhH5PmL12h`-r`9>aqh2jPkZR*usD7(ZNjU1vGJ z0P9EF2Y0~74F9#}Hz()$F|ZZXA4u7|oPn`^N?C}Cf${l}@huw}81+rQR}cdd z_`ZCv0#%kN%ZUF6axTpBgks=Oc6zps_D5S=AN|fefZzK=h|4Ah#^=HPsbOGz|F|F* z%li?ZAbu9uT<^J?X-~g@6h5&2@6~R#Og+M@M)ptOJ(Pdgj%mTyU+c~OoO+OXe&qet zqhZ*u`O%9|0{uPi%K!cR0U72bt+?oS#*O@qN}CNbnYHSplqAWjb*owDrrQJzK<@RGUrPDLs{KtmQOlhVB=C2Mc&d8Jkn}p&0 zx331n{lyu%`Mh04WTqticU`}l%#_ydI{dmpUL>71^NUjkvVrtFq1kJY7fNNSu4|xv z$oi;t*=kZPwIZxp2vqL)lq)#T-@$dqcF9Ehdx z9tUraslBAIBvo9!V^4Xnd`7=Un^n?FPJ8>%o9oLP$iSl~RK5z|a%JT%@7?7+a>MKD zg!Se1JtHl$M&@ulQ&KDTp4QNae2`ml$Ualrl_aPY(EPwXI`AXXfAH}*uS{}D zMsu@4jr=h_#4o6boSsqeZNqF0xme;|r#+7Rz*8y$TIHD%T{C3_SV9yY%U44?4#z#V zNZLl0Mu+6LkwqlSefL)66K`d~yGTD957#QZ@_58Dyjp|y!Yd#6!C9du<2reSUr7Vm zt*+@Z$XiK@(?165BVBE+@yeaTnSRpj!DE+9zpM3r;x5QTwB02tTo7_*4dO+ok;~@fLRlX$}sh9iU={-Q#+cWzT zeH9IIMZ}Q1FMP**Uf$#^;D-ud>f@~PR9{Lqty`4};E-k635Q`T|rwC?1sU-!HH_uyH zr2*b!Bhn%=rMtx0gEC@?WI;r~%9{gHEACOrh)hXWQfU!~K)An)Ak<`6Qe%4;!1-dr z`(~P&tQE@$b_3!Sc2_<@x&LNdKge^TG{QTaS12tax#6N^Z2+$=^I;c}MbZWSAzYxZ z^u8Nmi*#OSeKio<$wF-=sNufLSzGA8jm%9Ki%{O3oRH3H^3<0+!WZZqv!WC5rzTHs zvZn7lInAW$PuR_Q(^=6*rtRO7j(o#*P*ob_4N_;vfj-nfgq8PKIL?*n5$pDpWJ;av z1@=hatKVHcDg^oy@B{WpzmN$c5z@bK`b?mUuj0uV?KRX7o0ZCff0z3y&i;5=kL{vd zV8b2IS+sola-@HHRjv0P&@bL9>3`vAX@#jz-Ag)`@V#UeehPz}4?j@hTw;(ud#qF; zzj|~O!1#Mffy{cGGw6x!dy=ky0QB~~EY|kQg(blpkIJPb3aMAEqbjRhAyug!ZW~*2 zMV>DYuRl_$AQM(h)vhm>lMOSD3@inH$PzalN4}6v|Fg=cL_vCYdfOr&?9g=d)ya=k zip35N;nGlnlar$}%0FuiVU;Njm)f|xdZ7GTb2n_xloXQtKUJ0)Ku(no4l|td)a28L zE8`46zh33HwaB;2wkIDj$d^cBRVrns)HC9n0U4nNy+G7@Xfvg1(mmyX2<4icOB~dZ zg=Dy@ARXz=;jLmdfOg+go4VIPs!4tCS2QeugSd61f>a+ZsDbuUfzK{4r0>c%=FC96 z`7acS{6OwzpKd<2OwlXm!`c?kpfp^#8wUu3aPzdd!V{a!eh3l;ZBk)Zd36pPu zh48)tZ&j@hzDKlm*tBQmKUure_vYgiqn_C52UiJ2I$=*XOFR0p5q$RO+Sc}V{`zkK^K(z)jw zr?H5Sx4)kA9rmNFIeJ}9`Y9@8HB%68AuLhQ2S6tet(HRkz5C`;5nf#b3ftRA-;%P_ zY6pbde~uBgk$zy0laxq5z4XbTTB?>;d*gf8k$!g{8_@m)=FSgqg>igd`FRHRe?8>t za-wssq_U)d_R5BHQC$oTB1cY8^$LgiIf3*%GNRGanxYFUtY8H}^I zPqfil(Le^zKJ>H~^p?Y$ouJPeNabYqH$HIP@v+!_X~`mq_f&`6-R0Ayr%xUn+zs~N z8@FYB$Pb#Kg;TPC&Yqg=Y@d=vQcc4V+3s?u5>@v*hmrnwj_j=ZH~0gQk)rm6(y0+n zb7vV*{}WdK%*iL0NTP(%Q79j;7MMiBl1xcwM1LR3;VRX%ElBq!Wr4SjY$HEt`Pl~P z-I?yKLV5lzDm1W2nj!I$=M$*+Ifeb%Mdad)6E)3U;O%9&JJ|Z*eRf9pK%u0FOg&JW ziqG52?Pfdy{M`O%`H$GI%X^{Ykq>5kbJ+>?vpCnG0Pz>ZvVr%(l`R83sgn}2Gf$9j z&Jgx?a9>KWn~xxT=XrA`%~x*{zhi$(LVjL=e&(Ii*(Q|#TJPF29B=n3*-ABxFZj$k z8N5dV>dPMAH`w(s%|KpC@^o-uBVApr`FS*b!*YSXFyc!!H-zAy8Q+(z_X9c2pS%mK zIOwI;P{5Z#&gS*5MLsxM-!==M3tbk%^;ImE3|t=oKTt%ot$>ehoD znBUx;zOKlc9&R3JLVH~+m|>6pTHttFN2F(S^LIQU==)_Z1m6$f z^eaBiLa;wQCzN;Tah3|-Mfo;1&TZIJvQ%N*G#rS20fY&z_`JizESUl1-IhhTp=j`qJom=_A(X^DeO^{TUCvGhpSjW2zHzU}%S)(hc!Njw(h zT0!3V%;$B~177tt>F>gQt29%OPl;T*F1QKe1_k+~zHr)Z@CT&pRCYb`^(94Ik?w+l(Ubdy>B2MH@bvioF--0`GB1U`58z;d6M=Ui+D)q)YKCbk$+yR^aeg? zlY1#^l2AU+TaRA?(j=MYz;@>%on7Oj)W8qowJD+}a)W$AdsI7KVHr%j5b9@m*s!w7 zo%6mCZyu!SaoHZ|(?&K8ZUz29J-~9J^+(GDhzHctnbWCuq<5Gg&kp&g)Uzrc;Xt5& zFN{lnduu1!|6p05xQ(1H@y-qITq5Zul_k|6$If{>?6*5WJVyHZdOOhmT~^f3v|l)$ z#YH=?da=Vlz?zqMdY7#Qeo$1Udb(tl`{s4mHI2pgK{g?Q*q-a}7X;V5F=wuvK|dkm z;{k{R4RTI$-1uJ7ucRVLS&ieG5He9<0DLZVjb4cQKK$|-TJIa=_VxIkpEu9`FUA4? zoE`DeSj996L7e$~X-S5p*w;G(FiY)L8jQ(uB%8R6ZPEH=^_jfnl zI)?PlSE&!8aMI4$ zPt!4Ij1$~fSaYU)Dg?crx|-{O@L1ba)Dw9=el%Uk99KC-5cfFE$}UQPhYKV`Q; zPSfAv(2e!w8D!RvJQ?VL@+xK0r~j_JLW;#>r-e&v#6W(U{$bW`D>2_=x;+!(wdom$ zdTV=%qzb9?`FGeT&vX9Od>rxTx4#$iG(}RGT9s`8zo2GwSB4t!OR$0+klqu5CY(in31yk%bQZxOklwD#LMLE<1R)DFU)hdG_Z^}ZkXwkC z!(Chu&wkEsEUv;wk?)?LkN0EwAz|1qDrQ0~_5*e+0N*dULlBt%k*UPdXb*%bkwHLj zsf~+GK23M-m*&kf~Nc)3a+1fqwql+Q?ee z`_?j!HNJOQhzrosAop@Ttio~Gx=fD=+;>*s-*vko-^Ar3T~&>f>a`dUAbvn71mp81#Hu zVaW}Q4>rUWeBL9MlZTpecVDf{0Qx&PPz5%smXbsGr+#;q<($`h4#)L=2@QrpNm8jApQY52?K%tnUbj<0S<_luX_&_ z2?0-nQaG3muDSIp+P_k&su16bE6^b^Ne5yN&Eldbo1sN+HO9Gxe>&Id4z-%Cwpa)HlMbENggA zo}uUt8}+t>=R8}aKX+LOEypf-);OJ$^xW+Ae^I%`idOG zAu#__S{s&*bYHOe!h1L#Hp}fzu41g>*|3g6x>u`efUny~d9g~2^|^r}$R|R+EA5V# zFX;bo{ZlVv9GE`jKZO3^^5=zvKA;cM-~qG?75!ynTjfhjyelJ=7za4(SB*vacO3Gu zT@U`?4zJh^?*e>6wzKN@dMAamvvk`oq(At3X%O!T1H%GQ{{LAxa|Xu!H3w2xo9%z} zUZi`idbSnPn_p1FLpz>dp!yW$X_58SU~CtbngV_z#0h1)93Xy>{G#=t{dOB$ly~r_ zns{&pKXDJ78x2<*S8H1SLtW!(dJ%4U6S;t&9Wf%X-$I27vi-est|FzZrq&<)EGlD& zj`vyuApQqBT=2i*jC4G)9~MEtb0XI$5neZnqTu;V>51!iWc2&K_%#y!jd|Q9;Agdw zf15ZF{A)F7MUQKj_|Ba>D>2TocK|sBJMCxX=ZsfaZ+RZ|wrCj({WpbG{%@)1e|k;- z@k@OF>gd!xc;DG`>_nu0SyDRa1EIp273ha>ZLP{1>sPM6GmLcqIXnIWewQ^zRe^Dz zcWpJ;%U*IEtq+~^HWGnC%nwY-3z?7ObV*AKLqDKs(6q!E=pVX&`M$AW2gZzb3r2rm z`^~LyofQh{?aQC$k5w#^H03+8K>np~+rMpx{{OkJYbt#q9@vsoP_P@~y`x$Gy}i5q ziu~S=CnqpZ6f1Ou{7T_APpkH0oO1M>F=qRP-Ou7eE%YLFM1b*>ze6z zRXVbstatayMS1>eOYc}6T-&3TguCtd<`FdIKx@tugK5k z@DLB*&;JneF{tm;@0hRbM`$Ou+=z@pn9cUtgjX5tm&E>fUMs<`1h{*uH}X+MQ&SZ7 zPY{li=s3`7%sZ%0Sg^Q(z6KIk>3Z3Q2dL*$%W6}Pfc;s1AZ0nmD|3u}J?Lky6{YOe zL7WHTPt%`eFK`6EAL4e086SYYU#FTi4fVc=HEP`hq|HcrZsh zR`I^nsqwSmuzq^D8M-JY5nk8^as2OY-~^;v?Ju&l#D?s**E%C4kLNp#xEip3E3rPU}87Lg57_sjVqs5iG0W&2T%7IN*l zMbch*Y5~SmYSQ@m3_4%)Q0bG8@sn50;B};zosWAies4JY3&__%ep5?PD4#Y_QKeW9 z<{soTQ_7y<;fsFGM@ppw_9q?dNc$1MASbZh7k|j0U6ItWqeBIGB#A7)lLz`p;-lWT z0`(Fh>sn)g{3BppDjoI zpLMlqH_%fm_b#YGJutOE1y^Uql>NS~9{`;@l}&s7zz)p$dd^JD_to-kCxTy~V6)la z7Z~K;0uR~`-1ClXj33bDXiZuo{e8sRdo*1iX4*UFEs@GHOmCxo`{P@0FXy~W=?*Yl zC`Y$`56t4`6_UqK4{6bF5mlGDBOWViE{QSz7syq7$XkR&2-7s2%@eJ=5=btL}8;A;-d_(xDSC2 z(xd0c0(~3E>96ziqNqjG2W!niiAt1ePQoY~vk+XQ?-x~M9Fk03v_ zF0Y^v@?U;_Zf^s<^YR_NUq^XgwSbTz{fkm(rDD8!v$MO&S+PX=_K6D211eINrEEv} z^*V0j4XvcaM~|mNo>cNs^e5X{F+HP}O~}xWw`e>X@P5;+(<%duZ$iaxAzr!m6V}jt zD!$%?`Sj%Xp6PHrt^eY9^}_fM+@I#&MmlCBJduI@gPFEEcO_)o<p%?ss!>i?BR|_EzC)kJde0GNf`>dAcKa|mYey8YW zGt&R+!qq`|4gJIx{D38r!$l5&AILBtNnM0_;p&JQurm;Oexl4p`WDovzQgx~xP(sN zfSSc2EUtE^E|}MZ`Hz)H2k?1r^y=U?QYR1b;)C30O1%xL*PP*f zuuE^YFXRnViX6NZ5SRQ-m0Il!dY)Z3b06yarW(~-m#6i`qL454} zBw!iR)i!U^8sv|%h3VC(7uK#$FGIbu@uS1jxFrqJKCMQH?L%2X1m=5I)J=o;8RVXO zmoLZnY%-`G;&ZC%wbiI6$~w1h$SQHnP)!YJ^i^;bpQY|ujsD4-(XQjsUb{rOOhCHZ zdye@W{r8juJ0K6yMkYOd?KJvtH3w3v@p|BZ0P@CdQi4+fd59wE!?YU6>%k1_3^wQy z;KUgX9oSF&nLtnEhqCI_8uWMKe~CYic&K(jI{CqO2fQ!c=XNF87v=?r7k?axa&Ppg>dJzA&(ZF# zLiG3Qj^2j&@QOUW^T;}!_i?hz=_m&MFBZf45199%Hd*zDOD~CqKMJ3t-j{2ywd4Aa zyvcU|Hpc^YdR*6$nzDBf&h!5-_TD}$s&efcUJRha_n=T(S_6u}@M&t9eFz4Mq&XOa zNSa8()*%!f4P%v#3>wS~h;DQXso^9zvyg!-6Cs7v(n=94%Pf^_XNn4F+pTHJ^ZSik zcis2%9?$#7eZ0qWyvL^F@akG?&6+jW@BBW`>s-S6#g%sG1p)hCOB{HPNg@97z(CXm zK+g|Lk0H*^ftDB^?@+3S5YGa)TEu@mS;Pjuhg|+3#9=?=eDcAolqZ3WHy!-G!~B?q z{PEzz2Njh2PD%cn7vN7y)fJWG-+ubfR+M5CeB)xdP9C;xbzE1g%`Hp~{4%GV8%Z4F z;n))Y>C;ozbGNCC(O}V&%S(_{2!)z{8=CP=Y=lH1mINI6)5-SebSa{ zU!~m|C|AjOzXSfXWAQuPW$Dn*I-zP;J?}qz-b4-ay2^Ap@IGn~J1vp|*C%QGS9T*G zrME~m(eU4;&hrlXtFg_iq#Ss*kJq#Oos;^z`jY+|Uhn>%`1k1-kNTE2LuS8tNO|Tf zEf2A7Mfd2zp8l^`F@gA2um$bCK7%Y&tjg%r54G>aYEFgLZ!Y z+MgWuzE|I?XCVd5(y9W9{$J13>T1gWHN%qzQvau&JbA*FaWNx(eFf?~KF?TmIHnKd z0V_;$!87|+UHn6G3*&>Q|8eF`#s}VdCNGNhAKzS5I+w!#w}pj{p?t7pBub^gC1FdT z!)~nb@DjN0is7eXDHkHG9R;KZt7Jnwz;1Z>nbi>-OZPvwo9pn-7jIo7U%wU{v@NQ< zRBW*?z1f5NEx3f&*Fug+pR0`geDn8`gN{l%-C_45{;b!-o?tv+;CJ*JZR=~SjO$j} zKg978@pArGw;0d#+mh^0`iVn-cIJ5=dFY`^>TBzli_UT^SnU={I?%TkwNegT@qA86 zzVChc_m8@ew^K=TwB3L8d85TH-H66@bw@mgkUj>?coA{EZi95^8)+EfVLW zp856+#}fl?+#=q7af*2IyRS=9Ch1_?Z}E<}uvgMI zNUgL3Z7o41wEwGyXFgB=fAyZ7%gOgQPktXpI&g}r4MUtybX#5Z7X3dx1)#5dV@Qr8}}Xy1k~k4JF88@8wtq5pG4_u}Snh}*k{ zd8e7DYb{cuItrP@+Tt~&mtk`62*}@Zo8%6^0dbzxubjBP@jncX&Q+2 zt@mH{Ul-}nYoe12<Mw|hhWSJv3(sLeUz6TvDY<$aJQD;4tFB`!HRV6uBN&zd%WFI4rp+a9x_Y@^&NjibM}PhPs}1*}x4>bq47VE;vWEgwO; zur}iE-dn#?;i$Q14f_O;ttw)6-)TFJ=^_DObF;hjo5Oy~l2*9Uu=_ zDF^=9(Ek<0zohS(!Q}VA+9$WMPDdD^-AX?}FSBl7obdMVKkh7n9LUP538lYQATzz= zSpQ~wbE#M&_CNgGVdDGp>u(>WyqGs$nr#Q44zWM+w^EZ`*9=3Q{5d=>ZX|IEEYi1^ z0{_kDL&iAn)70F|(SEVH8F-3%WyMp}C$i8@n}~PU%ikg&Tq@>ovQ>ee#DWmZ2Fh!x zv8KRvJc)&T2Ig2`iz4Q&U}e@B~3Ihru+#R_J|+n4}96*5&wC9)$He4 z4-wL|af2Gi=gm)jg=3}^`ZMLlh^~eX#BEK{H;$G--Wx1!lD!+hQxH1FajcSdRuk_@ zN%52Hcn(E$jDrq~RD(+GVu@i*+^{u1s6%PF(AzQvcAnX2q z?E$&+EsyZ*QiDFkI+y%Es8k+Ed;&}n(EBBZkS#RP__>YTf6)>iyl)C{Pg@)*c$>Eywx*ZG5dY{9 z+gZx{+}xSh%2@Z~m3ET#qH1+V1LA)MwUd*-%^=rB_dGkKQyZM<)~AmPabMm4;{eiu z&)AW<9+03w0rfW14+dxN7zN3;TPwFn+ zs%8BD(V9-<5!7#L)7D#efS;Gl{Ay>})*}Y-x8HxK-d{Ap4!iKONvTX;#CT8IX;;L3 zwj0v&^;O#qt2es3RtGg89^m71W;ywP#1GfMwHXpbC)8U{6#I_*puGHd;x3%d@tI8f zF3sLXdT>z$>M8%X{CNFQjz7Jb_a^N@`J;UwWu1q9NA^eL|FFoW`LqkM+OCdx^4ip7 z^7oGkKVE0PwYEC=v`-=G?=%|ZRX_*V_IRw>Xcw=oGYZ6Wmqh^X(dI*ea@66riB#Vs8*%IBq%hmX(?8rf?V!Ik zS((H=AnV7qw5M$w=4COC(9v|UlKKCbap_k20ijOL&dkFU6)^vXxZeIyf6Di&Md>q$ zXTJj@M)0`BjJIeaZ*%bXrvtQh@fJLf-7xFtU=Jn64N_}0B=+6+oO%E;s1SqaF+N2 z^h0EXhkQQxe`Umt{P|lW&Aoe8zeWC6sAJSNlOz_Iiw=Ee>YI21^?-~6?Y55znhZZq zW&5Ub67bx-;|n|Se)G_oWe&R_&PH0uETkq zUU(#T&Bh#^X6Cg~WvHi|7UlsttuQ0)A^fYZUcu291&3?OA#YpLn^=#TD=RopdMNmC z7V_t%qI2&wP432UUFyrdy*bna11r|To-kMg`Z@Gah{B5aGvbh^^5B&rK7KOq7p||h zAVEd`(#uL*c|I~U_%z4%^BJdj{-j0zf{*!PhFh^mtpfQ~ccal^&uf+}o=SYn{FW-~L)8CuwWt5_0`M>U&0T(V zt5wPEIycf-dm-@_y%HCyMBU5rhuY87V2rsr3H} zo)1_No(;d>OK>9J_kI5MTadO>YpYveKZj>m*Hr0QKY?bASXIOcHK&jt%@L1lb_Tyk z+%-ERSuedOG-?p@X$ri-k9>`OEg$fBtIVr&4eXi5o3k|J%aD_KryTsU__-kJ%YP)r z^(Rh+R%KPGSZ4|}78R9Z{>a%P_!TAMZx3IDG%pp`OcDg$ z3-G#rMyZ+i)vS$mqQ057^v-6=EBmis4C{hia&~h={15rp`rhM=C+Mq+VP|)nrmdVX zN&%c7ouZiFQ||MIeeW0xabL4Gs{(esA+VrBOZy?PD8i8kXg{&_+(Og=Ne#6%{9K&x z0mL7p%H2bm4@BJj=ipP__+1Z7cXFb?`%CWrrEPfbsvTMt?f%eDH+M4M8}zW|O~x5N zch~c3f4&{%^ORu<6)f{J^4Kn>0Ea4_7WegTgP?uXa;@*QuI|G5%e9+eSCxpj^P(K_;g+HumB7_dRVYbyhzr_Ms$mz}4TlvU z|DE?sD2$7>1NQ_MIplASZd2_T!~u?q`#TtyMBZi5r5^exn*-&_ZpbY+H@67-|Ibu! z+DY6s$u2>R+uBqXSsV1bBx&dJ_<%{PmJt8wN&QFMrHcu^90$jZokF{C&DMd)2X>pB zlQhYsqoRz?ml=QB^6ipseEv<3*j2PYg`R>|(s$g0SElhfk-m)KsCE+iG5=R-OVl#% zw1X=znewEuE@nEwe1b#``g)c+56_H?$HCL~Io>8Xqgc-v0e{g3?eFzN$8&(K(rTXb90M3$5B=K zDz3NwW!8~GFPxYwS}9zMVe z_L6=>V>i}Avh90?^S&O`qmbvRGfy_42v|ZwpIY9P#_K zIVu&e4@yo}aMWYhC--N#g{kb!=Nl!|hvNwX{Wb6tyA3BhEVYjKQ;Jl~IP^)a1#xMg z!Wo7bJU{IJ3_0V)hB@gmF-I|K=Z#My?tAmM?&au|?o>#9B?xjwo5?KhI2Z6e@oz5e zszuz-(Aeqz8U4X$!%zu|yua7k0)h2@4dE-B8UL+S*}7>D%v)*+Y`}i6D6d&BO~F2) zyu2_E(uKO+-Ms5aR)ow)4C-?bFyNo;t1x&kjyh zd0cahLWt^b#Z$v|LG$Ki+KIEJ=(rvE)0)0AXUb=N(fTg>fi(mB&8Gc-Ewy<~3F`hY z+8amk_?9?}igu1sRg3ksi%rgqTzGW@|Lc;mV%>+dPyI=iUVYA2G8RN+;S$FS!mEAkL+1|b85 zJis?oqlO)hxN^KEmZNXM#{rBJ*c($f6K|wP=F(0W`uZDrv=7IQ9Xo}e(`d#gvo1j) z2wrXQbWrCM5Bt<$v1ngwBhIoa>_W;Apj`&$XomC$8{Qf9Z*QP~NuC zu;MiA+k?SQyWYn`)y(}pA1Y<=;}M_H2j)5KZ2znZ%G1v0-HnV38jEY?r8pn4JJ3ix zu0J1GL_cVEwVOn|Ti>4uISl`1MLO)_9G&^(%XdnF<4yZ~;0->B?YrvYW_BP`I8@FxR z&FczWq@lcj#q{+-jLXcLYqaut!y3xcp^tP+?`%W;A>yrGszBl@)!2|{0^a`qLulVs zO!TjC$ocWw2QU`Sx;(Oz{OyeWhx8A9C-`>p^Tr}Q>bQ^>t2&MRCgfRuzKr@zB}mip zy@r!2PskzoJCBd??#A=PC;Oou0rudGjJaKSuHTwee<6M9Z}g&G;Z^a;=DagLsP}l_ zmzmePOm5lEDtRd5ecsu%@30>HVOucleY4@#XYX`3Ant!LKWgP_*!#tG%c#N?U&F%w*4OYucDl zTQTkZfT9Rj+W&cv=6TW1H-0W%AilSwo(U^Kd?za_D+SfKqI9t<1aeF_`(uucW;Cn1M{XWW;u#CNS^3Heu^Z#}QWb16#<_2QlI zw)Z&iH$0B}OT=4U&yFH4OSKaAnTBVpGcx8fuK#{*Z7uoxRC3ZJj>-&wUyd40ypDFB zz9=k6jrp9pX)hA*W4}~Ppxt0InkEbBBH8IL$_HyRyzsr2@f9N#q-U+Bgk z$p3S`S+<#Wz!!!EdrRN}Zn0?bd$Av}ryc87B5KeHmiV9f{(Mmh_@^f1lzScYZ&8fQ z{fOat-NQbCqjO>R-IT^Ir~ThA>$9O8JO64gfgCXPvzOQze`1R=6q zrR%}IPwofSOq+Uw^4}{W;u-A!5r+%TPQ-sM>Y;3D=VFIA5AmY?7qCL^OgmtRdB|Ye z`?kbt6>K-|}ESXB1wdyEhF1{@vH202h&y>1=x zw>MvUn(L9|>O0G)Fh{JFRIpRp3|ZC()*}uGeOcp=IuOxvF=%%eNA{_{I5?=OHna29%(G4HEx@4&vl?S`8-U0rJt2Xc4U zXl#agQ;X~Bu>W_Wh?W4rzj$hlelF_3K8?luR)Rd?_w|l_BpCj@=&njj0sgwsAeEJT z*oFQm#382t94FdQ(j2Lha&WOeMxY;cGBkBSP-dd7*TS7@-GG)Fh1FAxS2ZO2I=ON#uXuPEzc+JwCiGQ~n3|hV zR08|7XWZv{>Vp8M0_?J~i-DcLpq>dh$NP=sSRO4_Nr#6G3f6sdyvoib4 zyRmX=EY z{?_X5Q4Jd*7gBHCK>dfIKH|pJ7s|`FN>Zq0yP;vLS}jTSTfNIIDz(}T`}^tld3?@PwVeq3tsDYVJmRou7b;@W2g> zDlO?EEkQcWQL}RR*BqyPJfN?`FG$Tl&*zvK=X;Ry!SeBh!+g&5<5$h0ogmGYY8VgP z5*VUtgI;}pbm>#f`!`0_WkrGii`|v(xv={s)w8!(gRei&yx7V7fAdevr%eGJNW}_? zb$*s8pKVvRXB%EhbM4DKfRE3~G;hQStEc;yyJrs#MFW63+I-ANX|EGk< zu7&}B&GnWrNc}YbE)>u#HQI1m7DYsx{uAhgmIEA@uvMPY*f+`7hSFUOhoM(D{ohnDzepa9cI~L1k4~ z4fFr*?zgW^hJ5Jhf&5R48y*o6++t<^FNkp<8=?bqrC`MWf@;>ULp@kpXk@63`2eMi z@xSnFkAM|#k^he!&5oj6PAF8s--14NQXu{U{n>ZbVDf*&Q;$a~@cY*#v7R6MqFqPE z11GUS@9a#RPUg>in7Cz@PY3_l4fD^i`dC_HL7VrU!j zJMz!m#})^9Q6kPg--$Y_@N8@4hmN`&w}279ux`h?A>B&cedCTD;PY3eX$NwCX!xhF z3vt{xuCUt_RTv|S_N1S%#a3fe;P|h)3H{X=v&Xx`kKjHgO`IC^vbVeC4ClJ!_;^0w z(n}i_&`ud0+dh=P$N0~8^7uTe^cy!PK@YAAs@BjBEFKt-_>)lqK;@lNBNDI2}b85@rDGXJykXm}~=e@t&qvj&h3%6EB~=m)M0Zcw5Qw7mD0 zSKs=r+2|jkR(~2>G_|TKJ>3fbA9h_R`Cp4dfbof)+Das}SP$alR2;xKaFNFf55|9o zyyvlk{(KI{2onO37{i0u*dfpDZ zUT~)W13&l0ANmt#_kMZ>*RvqldmrbO$r%E!6RY0VRzb??kS5qkIq=JeEdjLu#sPq}=l>b)RfAwDV+i3SMIP>J=^#A;(%4N*wrKXone--xH zoRGDN^!FpeZ%W(`P$PvDQJyLk51>vQb!-D42fsk?y{>W8!5oW!Tti$|oG_<%LBHA7 zt;+;|i_yRC_auFEoHbW*o%(q6@uoiPe!a_u>%SHIu^j7u*m+VE;%*H)Lif_|o_g{9 zFooI6Tr1s;uL({okvHfzxV|{ahxZBJ9o|4YdtJ(U=wrL#JovU3Mcs2ftIPP((*>^e)_aZ5`}0>^m%-xDRoh!b4v+zRS4YQ&C>b3!4W%?Hz)# zaPDJ2?cj4gKH~6jj{O4;L*93pMtu4EubeOOQ)9nho8gE2jsnuHN|lv`Jb<}MqP}Z0 zd{dU2N;x?F=Up?oZbjY?LB63L^rIX3#6LOJSHpVG-d}aSb-=&oh%4g3uZlMJkcwBa z-u||fO4|F+Rb04<{XnKr=YngX1F`GWuQ$uk4@MPiYa#wh<@aYu54Zk#wd#_oe;wn$ zlm~S`%jE9apF9~>R|mVVI4w9VD9EAzW2{>D>`$Kz-qq5=dSI1Gh5k;E`$cbF=O|Y` z@0Fv=F<32$A*Fauxl5p*gKmogm+^PZ-|%Ar*BN1Xz@@PPlw|^t^(KdiPcAYNk=0RW@w0GPfywRtj8B* z9xD1-pJM+S?{~$oJ&x--;qgL8d?(lU;6l>d!B-tl9G>6J`IgMu-}g7oRe|a zf9w}fSj0HBBDxIww(O$!kccqiTOeJ)exa!Hsxs?r%I}u%&MfEg=(A5{bNp$psEx+h z)^y~YgFbyH#1rS5VYX^M@9gSO)B~aqL|gOcw0~rc+LQcUa3d5BJ7>+j8v32e0dnv; z{C<6_n)6~wE_dLsmCNk#^BWs)pszug?w{gUvxxuXnO>RP_jk3T1%3effZW~xZ*qR) zpYZ$5;&QDt6nP-AN?sQ>q7L<+`GR|*xO(HdYQ}w$_lvxC?aKD;hWdI}R~zEIqptmY z9PyvTsIjB=d}a#Oo$YG-vNPk8C$HZTgw71agEy%{X#cqfKZZDdc=iYVSM<#hU(J>q zl?CLVe$6I_J#anwcgO?yPhM_r-Jpv+$%=YtyJ7K&rzR7(hVK@vWB$b%l`r(8A;+~` zj~BvSVtC!(b6fSacij8dbm8*`d7umI$~MEGk@3{i24#W;aYfX}xwyEJ&SVf2ZH69$ zDx?5%gZrv{2hzUu)o31YeD0Vp^fvT=AEAQlZL6tR#rR@u>!bq?|Fx?N@x~lo{^7Yd zSob`7^ymQ!)NMBReo;?+bJ{$HGmkqUB{PL%=(_3aiSw5JBOLtxUVo>#RC>qhpT5j!Kx&-P5yu>VJzT3^MupsTASH6R`YbvrdKbi!0E z>hdRI-%k({zgEQat?Bx*5YZ$1cDz60{v|W`!^59OIpPIBj&l`=Z_1Sh13F!{JlP19q zfc-e50QLv=@u{{%aXvLBeKzgB!%zR}$vA~YwPhIfR%1Vvfc4fTDy{G*i>3~EXtZM= zP~(slsH;#H{wfIjxgW?+vBHHoHympCAQy6Ak9`dCmpR6)9UP7D` z_k9{An~ldSVwB@K>Ye8djmGB>cU@RY`4DPD-6-Tix+Tq_moAU>zRurcmUYZ80sR^) zr>jT@zyCA(2IaohW9wtUU(6cabe;NOqq|8-xxe}6$nGfE`-OKd{+0ILgRi+j&L1|7 zbxnJec3^%~x~U9)Vadwc-m;C6W{)s0H`e`Jy?Htx^`GUmw}KNtjXkb8K0~GZ^huux zuZRTTD;qH1*9QFj)p1V2iSF5&>DmIu|23NGVdN9{u&6zgab3wjPA?@rC|4ZJD;1wd z9M&!o?*jv!`Wyv*ng`?K$afZPCH#BT!}X2pPq`SdazZBcKf+&*zF!+#-ycVOe4ZY$ zpZZ#pSv;P&9URavmZQ=oSwnh=cKZDo<@^COXeZqa7#`!W@6wOOAYbfLILOHhb97j_ z){cIAcEjUdf{bHB%)S`j$3NqRRlLvQj;kXmM_l3yVja)x>)XnF)+2roJVHLUN~xQG zTcNz*scUM?!@s}2-rF2)9)^BJJbu>T(K-cm@LN|4_F?-J8oh_0o&o#Rkr&~9fBm5z zXB;;T#f(p4zy5vu)06`jE_ldtbYEnwYYG!eO|RE0Ui z6#Rfv(SH?ip`9D}+2>r}s+bzu{{~mL2P=sG$^-r@DF@cY$H$WoYOD=k62C8zAfp@@ zAsl|1IPH7uw`S^Z?`OtFbz^-p{nV(ZY%_$~YMUvCpBT}R^Bw{QHebHoAkE2|m*7i1cZ1Aq0g$12Pn3T(=zY3`(i!%`*~)65w+qi*5hUk46>C%Z#CO)KTbLNSVYuHuJ1toV&=ER zs@4Ydf638_8QHh#&uXe4lDe_qPf*z|Ge01N@BNMVwhFIorktplSEJ>aiGo=A!`+Xb zp2a$_ZC=6ttQW$o+0P%c3XKW<#Up7Pt6h0e}Jh<{Um9L5+`*wHFi z5_i?Snt2M)!^`j7YT@;xx;(<8;9ty_o!`uQ0!3LN_WO4kV$a9u>EEeN{GcTrbcuP- zlmAN_U$nHr9`uy2T}u30_HM9o-`{D|V{+Pim7Volj&0p;+E{nmu@5NoRB&I?fvn(q zUL@@OQ5VCM%mYqau2wHV|DVPV*S^GGGb38KsQu*0J=MF$VjozBzvkO@Hp3Sg`t)%H zjQ>cVdAaAn|Np}4sX9mfSAWs5|3@a*=W$*jAJVrJbyQ<&pNc94zsJP+!k?X_%k8xX zQjV>h=mNf(Z0J?#x$j3jzU!kK(9hx-t=^sZoO;?bn)qCcGNaBKeWR|Uem(omAD0 zxpRs8kk3DkrC(^h**gpVhC#VK(yJTsi-*R)-@Nr~GMM=1oc`$wMsdkeGwxq1 z*4RU!pGpm%O*9H^7~S)@U-a{gCmw$sb&BZIl$>;&I36*1DLGGGa<}hSj7w~E?pC$A z`}*?h#J?Jfns^q-ROlDQz8{Jhj!rJZB;vaieNY)Uz_wyf>W8IgLjtLuqce6IKB z?g!nM7)%}k^m7ewu3y?21)O(&9}pQ`enh-8`ZLsnUN$^)VZlS}`+M#3`|R%}8q->r zG45Bt-@X<5KbqC37pgT~ z?P^9GXuHA3HEk;KkGYX_oPPf`FZ%z8>nFJgl0izFzchUj<$t<5HJSDQ>!)YVq22ex zqU)!yFIYGB=-3hD|3Q2Iah7(V$t7SkahHeo-P49V)%>PQo!#Ke$cXw#aHCm5Jkb&7 zemP|^`E^l(gt%?EE-)-;GxfewXuUx?&3>(41NTImPAaZH2y*e=uc{ZAeB zf&ACTpx-Ovi1yJ<-IV_Wgogy~CrzI#S=jII#t*i$lmmg08HHt_gVSHFb@YABKDeWa zeV#T{Yb~pRyUkY9NIT%Eg~K*F?7oZLpOnErtf@CR{QlJ3&!5G4lV1Prw0h`)B^p5) zoR~1xCB`L&{y*A4BM)3^NEarx+F%dI#Kh=n7n)3NUX1^(2v~`IA>psI9`@W%ydx&` z+0OBa^Ra=XgJp+?lyF`+d_s5$Mw!Z1?U3W14+I_x$aX}1`K0< z^kjx!Mmh-y+I5Hi5yAo+SSOn@>ooeo!hZ~nM0^qcf+n+?pK}ZP+v^-Pe*R8f;CpFv zqm}%BK%syhup8p1CB^f5-75YvtOWLiR;9I(E++Ub^yTOjKWRAV>*|$0PyE+K{oZ1X)D~BP@pf(pv)$1wGKVIQnKs;S4c7M!xmAiCv z8T-6eNwvk~`_|kyJ|vw4I<0VCh25!fggJj1z&O(U-j^J9>49a>B~mUG7`%$3 z&6iC#pREtU?lH4bX+!-;ongbp&hx-q48B~ynfCszmR-@g8;=+kd>}=x!F-9M|LtLE`Hc6K)`^aJl|Fx&LocLS1JA`K zOs&y|Y+&9u!sg{>0}e356+VTs#9cn< z&3|wleEo-ItoP4vb%qwl{$LNKtQ2u|qj88s-d$ht;a9|ed|Vv-_EIr4?3X(7z2GSv zCO$26&*116w-|MuZHB(Fe)06<{82A# z7jsNSfgyp+3$C>oCE6Et!8P01_h+IfB;O|*))I@XN=jP2Li@2|I^Fwj3 zpx)20?9*w`^Jc@T;QFcsi1##Hyjem!&(>V|u`j~P7EQJ_0{a40&b#e2fOIRZ67Y5Zgcc$-8syhTL+`?BFQk z?_KzH_-Nv3#w+Hamyz%JVen$&7CGjKxeWYkZtCi!e23T^NqjX)u6?OD9(6KxU@X^c zP^Z3!`Jwhhix@vDP$#H?Z=v_4J8wke{#UmzJ4${32KsN%9;iY9j&fg6Zh+r|xTh?! znfCvfhpWB0&#O{p4ao)G*$*H6XD;%5p)~=SwD&E`_F9?ued@yt7nt|?Yh72pm-*P% zKrQ=1A2BsAxd8cb*>rTo!Wmwu`>Kny2#EV4|2tK^8unm!@7?&1KN{u{f7F4x*+iQu ztD<5cyeWewUbDsn`GKe>f+Jb~W62no2>#E${`HJp(gVtq5jXeD9{r{1Inu#<;vwwM z2-msUFV+$Fem4>#(Y;K3d(*j!E{wK%Q=J2M->>3`w>~CeDCfQh2DMWTL<`Q&v=g-B zr5eir8TkdUv#}rENwt=BH5uvW(H{}{c{dNlMSKbkWkM|VJj~El#4)jGtwfyD!&J7r z$F(YsHEU`fV!YSh+}uUI;N%dQC94dVT4tkT>h^l`l2yBz&Jy9^&*c-DOi#?bw1v9I)q zL3^#cC*?4w-0 zt9Bp#p_7esVK<__PgQ%n3-Y6~LW4T9Zi6%WmeOv>nA-t)pJTYw*m>3tx_iVDsdU77 zE4MoKhm;wu7UCX*h#~8UoSl!};oMW<&75x8=U51t> z$q@&dwKR7w>`+k{6Yj)#&aHQPkJF#~Cbg%7bm6?h33mOOjgmB1q8zw0X!#k+hgJpZ zUhsRy@A7|(pIiRYuFGApeynNQ6w=ql1bI5;%+AdlAkU-CTen!CWza9-?6?hTeE*mQ z`LH*D|8F6e=obj-?TGv0JplRv0`N9yPus1O2Muj*>l+{kX6}|AfZi7k^3qQc=P_J} zv=lMUyD~&bB%Smb-Tq_0x`^^~`Ad5nwk9I}=WZna@VA^keX`h)oSK}>eE_Ld+z)O` zD=3JGnWXzPwmN8MHR}LP6Z`GwIQiM&L8OOA-%vR0zAqkK=+OU1PF)&D`#=`ZN6-9V zB3{@;fB*7lCA&(o-o7rwH+YBqz?WixS6T9uVdU#!3 zQbs%FOZ>>BPx!s(@E!)rC1c^%D9#JUxp>rrF5fn8==Le>%=xBYkkuc$OoBHeUI0r8ZbJT}a zu7w`XfxLBfvSYnhIrWJHe~SJosRGwORyI)CjrE__Y_-!4Skk@eHQLiHbrBJ3$|vcT z=4Q0gzA83(6mwtx($0?aygqbypKZjo;(=ka?T`m^G#ca;+n^2*KgN9@v#XF-z&?JX z%5mMur2ek>J_F)7i^=cWEty%+69%NOr8fAVH#&OK)#XJ~ojp9SbioK{A$(a z<2@kWh1I*yf@{$iEJo=>`R*=R21J2VgOb;0lFwaY1sC%DndMJ75qDo~Y<&;!iOR$} za2^vIlg#UUW1QM~p9j)~1dcDH3#q*Sq}*)Ojk12+-JN*vcq90#8vfz>S<*1joyp@% zzm=2&7mhYavxeoL`zt{DVgA z+(rBUVDIX&%ma7_HvjC12V|{JqWph!uZN=_$kC=>{>D0*W_!~`jz4uZcfu|gXU2bv z_lL1>*cz-i;(AlZ#nVrV^IbBHI4a^NIO6gBN4^+O{DbgE91gWyf0O)QS#2ApfF3~q z?$eCpzkR9kSL%`RF0VN1Je<83e0T(Uz9POR%Ym~@F`Rl|npZiGbn(i{iFu@(R4IQN z`~Pgo3@EenIh-b;?(-u2+>Q=)xh)Oq&%-XN&FlRAVTb34L);>qsmCR&buMwYs4R%* zAr36W3Y6bYD+L+kM7c(jh4%pA4#lB1?qgQOVIMQ-LFgC_`Jpa!j&|<3JC2A3?uD)U zF1@UPy{FHZD{?>S)r(EJIfeHpbHeBNnmc-E{Ph^w|4 zrk(lRoqQWu7y>(wbux|T`MCprNsfBr%9?pv`hzh}cm033v2$>}7+D)?B<>IS`{TWE zr3OVnJoXX69*9vaX5M14@sOuSc5XSgy@MZtkKBX&URZrl?#btvE7eG}^YA9=9JTqq zjc7}oNqk$b47vl{&B`4fC7c(?Wjc;ZebHBU;|JA2yibSakVB7ERAiu!Y?mQ-Y31H7 zLv*=ING0%AnEM@k2YCkMlLrm12ECUT+X9Bs-uxL1KD(M@Mkt(BbYjm5P4iCz@$a zq=R+->mo2tJ$;^i)`Js+H(QXe@XR*W?}Q$QXHYt9P#f@;g`Pv*1or2)wx8v`nfgHV z@5cVBZfpMQ)TdW{R_{@p!*w_F&o`3a?ALA=P@gaQ%dLIHJ0fD|F0P|yy3JCCK7I1Y z$JehxJmjC@auw}^S*L$LLHvD>^+!ArdAzNb5bh6AuCXFNi9SMjA9ojUw(V%r67M+0 za5>}2iT&hGlt;5`#$*3TovFxJ6qw8RzhNKLnvGM;*MGPH`xAJ#JfY-xY()DEho6_6 ztl@n0xEsqbmM3iRTNed6pO}$8kLS@wVTnWj$7}rG!(8+2Ovrto!W)+wf1>{yp^{P{ z-wPkVbxERM89zAAkGMbISB5^GK83e;b>G3;Cdz}*ppbTNNQ4LMPjsj?&ibCXo955j z#j!SW&j<7i*BNK8r9bd~Rl8*Y@&M^yn-fRWT{gWr>lgnk=<|46d^y=+4|r79FyFT_ zcp3HsA2!VVrM)W`_1{4&&*a_3U*k{ympOa5*-Z0vCPY$b|HY)oIP$~(ef<&ln~+!+ z>LJ7KzKm)K{kx#|AHDixf7XLzJ4h9F`Jn#&7W#p78CKRvRd(=wYKAn1{@Smh#bG|^ zubGc{xR1Fw?eg_IRciF{FkM{K>Qm^MeZFNK@^d+&(yJQ%U$8$Ta-kF%@)BR`wkHPapH{cl^HNIB&yr#D`H|H%SRP z>@u918Gj3LV8h(G;Tvf8PfVNj1oJiwt?}WF{rp;J}7bV4)_VRraB*) zk#PWZ{^e@={a16ZU1J@XYeYj8^*_`<@JHWQl?;6#Y=*eFIMjd8{)@prpig3VJsajj z+-=3~?$q~B4E~^l_!qkk2f!hTDc@Q5982tnvcbqMRrUD1x;buJc<}2)jK;1bKVVuFISrZzCTkX=Xa|fABx%y+=KtqDf+W z*uG=gG};3y$>@j|@7=3{-vWE_MrtZQx5Pg-mU1h!+9r{1VwT3p`MVpfBR2!r!cC8> z@O?h!s9~e~arANXyvngrv2aZ^#_Q7&V z+!wcZvj0=Ic=39Z9QhyJtv!d7%SGSKH8s9U9o8ou^T6R2NE5 zNzFWebU*sFpx?*&+9o^ggM@Ot-wAY-l9@^T3ky6C>49(IAmyP$v;#d43x|pKhB>3> za9lfa73zw>2j|tW6A@Rko?pb{cJIk!IDY@4+f?`?=5LSYb$58L*>bj?n$JOmudk&kfYkm@0Gs7ytW`|B)ArNl$lw<&)U@W}6r7yRf?3xA8tI-8R*QYs}xO&fht8 z6zloXoomM_2Lxq7a2t+~-IRBIA@sE%_2T_Ect6Z(zW>H9_DHuemWr+W@m?mZJKEFd zmjKVTS@X71?_ZqVjeT`?QQsA|m;0Y=lJpSIOKWXbyNLJpSuEJ6hyJqhameG8h+nR1 zegtnHG^p1kCD9-FSn*w}BmOI?KIi!jO@Y=n=#Ax_dJD$v->Rh|%7v7$Dkb*&8>(cX z*k9rCioSMvry9@e=H_Js-)?ksb9eCV3g;Ln&_Q{@2*e{G_YXan5l;TrEE!eK`5V2j z{f@c$;2BSczpv3asfqss1B3b?9$=37DkPn8fnPhF;pd`ms4^{$_zQ=leMldf@za?X zFz+4^2)zP3vFd$1hfiVq`36spTa<@8cz*V}`Nmv~=f055L!Gx_!P!a`$Ns-OdOXs+ z-SmYlV+{9yZ`c+Yi2mLNXYc%%CgZ?ER zj0ZNpRMo)u!phu3RrLFsF4~()A&1YRFCgWw$r!km`?u|O#9uLXVxKqn4{RAWJ!>-b za|-s267NdKdl2p7ucyC0L%x3L`iI=cp4(v9_{Q!Y-zOc1wAHE0ge>2J5X&1yC!27_UKLRC7BIzM|M5{CA<;h9@ z4m-d_<}#7j?{D6}2mEd-&4;~&_}|;_d;`0}+x$T0^ib-3+Zg@3l;@rqRinM}-nWOt zB3tPXymVpg>IK;EAuWpZVjnok`0`Bh{f!e*cJ_mkI8gp1^+#TwWTbuPGF9!!18l{6 z3ibZn|TCmCHCmKS@{S@c;OJ z-;UYmtBAisP{v>^AK0pJA)Yp;bQjWr)*=~+yUWPrX_N)dK$>*>!(*f#6AHP6D#9*+&yN)B-$C8>FZKB;xP~pM!Wzoj-ehF ziB&%4Ddk4}nh@e1Tx83l+~1yIJV*Q+P>{g-Z@WsBT?X8n=G($^A@951PZg(N->FA)}=OO=7kyYV` z_d#Z~rz9t{{wqDVNTB`aTvE50V``PVZIbBmioLmWBmMn-)xql>alZ{I=o_6Q4$MqJ zpAgS%kG`dzjQ^p`(_wcH49!ZR-kn>N1w8pax!?%$>-6>09eMp=*S?N=Uw^sFblQV= z-zQgR7#+T$m$-H{zVXUYj7$8J9QmWcLC%gm@Svm#jyNyMIpZidW-j>}^*?rlfBaw# z>8NpYFZPkvi7$0sy4jBS(emEzJ9W?_@eg2MHRcM>=LGsAZXRxKWze(IY-0n~zq~JA z;1vnJ^j|j}cHR{8sUI$ts>{pDlVcL)?}1MKdHVdQ?^}QW>P3g0fAII)od5l|1`2{Eb7K6T)+g@N;s4D4KrjDUkN?Z*Veqj((d*(Pr~Wkm z|3Ck)x%YdO5Q2e)kJbKh-mkb{@gL_~jc+I7)olJkoUkZ!j38uwQXO>fyg+$*80+aP zj6*sidVi=OTpKlN)V=e(zvL_3m#6=k|7Sh^C+Q&ogw`9^`{5tyt!QFl#vkW}#_ebC z&8g1^9XXkGZw{YKt`M9;?#=%n^!<_la{kZq?Z3;5Kg$>2y#IRrp#O5e`~2~*@BbhB zJ_moU$=iJI`^bOb@4|a?o?m=q@!-4je!@P)nl*Wv)O+W-zE?J0{y|uQYJwOcdi;w@ z!GGPl;{xs@6PyJ@fKt#*OG(B#HU5j91Np$b<=yix!n>h7KP~zG{b0mtd^K_ZncrOj z_JaS4v}F2FZF>HhVcC&efK*?vAY z@=x~1|G@Y8JDv~QIi4E`{?qK_TqcZgQH~41d`b1!cb{J-oE?DY(=16IE{pM31Pjlt z1ph9-aAfD*^Fx+ic!lRRtI-o!cuh96ofIt z(L)Q?@%)SsSG-gEAt6%u>t3G!H~$kKDYAIZ-TU)jtVlmWb#IRIa$!r6@!tHdJUn&b z-kkWIny`4ny}9G_zWV#uJLKIX-)*{gp5GT(*m&Q*x%+)G%OB43`-<+(|MPvoP#xL;PYCE-UWMdA{W!S`q&1kMl?WFejc9zI)_8zjK~_a`62*?kBjok-G2x z@%$5i;P?OCe8RCm#|I|-@Na(S_0XsH@6UPMsgd{R4*pN}Px|Bcjr>1+pYFfN6Qrr` z@;7e6`%gG_Pk-N=e{i4v$q!FJzASc91`7s35%-um z3ZV+Be_!8!d^!&LebluFE>G!A?Y-06>s9h@X!;p1%k|#hd-)f+?x#?jOjz1|qxbgr zADlSU&t4GFq<`tQ<0Srh%)T#FHtV=FHo67f)a&=_KFk=H{FL)Mql9{)Md;S7{ZJ{) zN}cih-QOQ1#Eqf6(9F2E9^}^n#dBBvao$J?Z83u?G>G|JNU`AFLa6|9YNZeEHwhK6tPvmpr~LGL{s|MDiL*1tOEie z6vHBP#%kN!$Wkew30qx|(rZg~=9Xzt8L23tRj}8p0j;A}DN>C%6$oOj)}^UJCQP>T zf8Q?zm%IFb&pqj5d2`PBzO%gh`<@f|Q}X?3et`7&`1tUBVjtHxCD5Ov{-fGg;)7EW z*8ykRvc=#Z7#NrY5;(sMzSVij8#!d|9C5^k{GVnsMA6`Y8*=7wyT5nPXBGiHsmnl~ z$~N@e-?;eQnoJ^hcu_Gq-(a%kAJx?YtY;dv(<8T=?os+~&2{m{4a1rO#}JZmXJv7z zB&bCDEA2EDF6k|^R~GM<2(5yxcBKDKs=HUt@zHaYirxcLDv6C)8;c3iSX#_(`VV>g zx)`q8k4%Z*8`x5LKknTcnJ_JFYn!XyMzbUlP3uxpHOLM0q?3tg$`qz3lagJtmkTOf z{2!&<<8xQwuwJUF*8)ormZ`}kpjP-x(nmgJbv_~?r=#l>i z+e>x@u6DO~!4lv99-duxIvVy@E{}qGpVD5(iE5aiT)tI{?PHdNRG|GMmp8nKFd<9f z>NvLj6ZTZ@k=#gEJxU)!4p|<-?D(f`9pw)k0`g;J(qeaetlzcuFQfE<_z(Cs%5R>A z8MwUP7w#K&_s925>d2kshS~Q`UAMyxV|`>%X+jO!$#=0qq??y zUd_L)YX}|4P|+z=9rCtD*rQ0>MAbBkF0h*p6l;WPwc4AQ#n!9Nw1H~S{|!Oh9%=&a z0H1^=t+=c}LHDRtw2rEDv2Qa(7wUQ6r9U{S=iBXKxl@~A{DBU`JM9ro3j-$1@siRw zs_!-CQQszxk4kxk-LG+^bLOlnoUcMPx_}(61C>as7`Y_Dlmgoy=0eUm%AQe z5Z_P0{|$eb=x#qk52G;qzO1S1-1WGg6JxrLJPt>Kg7gGj^w@X+zq^if<+~*zqrY(!VTu43@zD^_+3sbN5GjeCwA-`ZSIo_{)>)vGkEYYvlRm?*2HR zm(-?F{6ILe_|hoMj<@{Ldj4PNAL50h^pE{xdgnd~_dMxO*zlOXfc%Z4<(G_%SG*eN zeM4_Ai~odKblbavL3|Bkib@2P(I$%YVBSbIL_-D|K?d% z!br(Y%BtiSwyequNb8mtfG+OTcT#$GRdj$sUTH`>FisMrGTw`QyG~dwSBR*xIx&iL z7X9)TA#GIAznA9GaC)XAp(#tUv0m^CJ{!oDcGceq=Bd)>^V+5lo8X&>raGZP$|XSi zkd{tqZmY-gjQw}WUSoxEGrTXc_2*c#IUi!Vc4`5GBD5+?vT46uMaw9kuA-5Zd38d+ zl=Frl>R~UiTAi6Brxnh>C({hzI67)PbRsJtMVZ{~TAu>N-;uuL^8S(a0&L-s32$yi z`6DKm!X3{n$Mvf5CefP>vxb&B_XGbj=)0@0n)J4bw$#q*n&^PWSq23RU1#eYy#FrH zb67!H=&xJs7y9hg=H}WP!Kv9npO_-HHS*1&zjf8Ni&O4PpVL`7?X!M*?aqbbz<^!k zau|)Je0!{Q{4d#=q(60c=3#C;w<4AitVxuS#I(}mbj!6&P)!6XNgUL_89hc^t9_?SF!CKAmsL@5jrK++kg?~o86sl3^Qm!@F9;>sg<_eIDCLG;r`WQaI-HexnL=yJ z*59bnbRzUi!MC@wGl_2Ciu5~L0VqwW2=s5R83?U|1?l~AUWmrrB6m>Jc^#J?uPoLI zH6iI-3QZ)o7{`U;c5yceQg6CC>4%Q*E5_X~Zx=^Br`gz<(G+KZXaxE;(~nOC1`z6SbWpxkWd7Y?Leq7zu*J|# zX)jIfoUcx3T^K-g`5UN!zDo7B=XbP%y^wiO9`7WU8P{9REo`^62*eXF>wzhGz5(*|h)#1`N5kkm%G;b+BD}k4I=%_K%;zZdQ3P><{5-mR{!6aKbH zAiZYRP~wQOp}iF0dAe!S*B%n~FAH{~yf;wKyw#K<+_AjtsX{Emz2q~Bv$Vtj) z`A>gF2m!nzWGZJ*Fz~Akc|rI%5+RX@S%2)g7TbJ@iV-tB0jDe&?Ax?=iuq(ZkrBq@ zCxDZcudi_9C*t>++{jUT2j#^K;TDh*u7dTZ2a`wX1z~+x)cTQX*1q`M_N0FNU*Sje zjrjYIH`a{OBf@=u%N>>P2>-b6{~aEYZ@3sEg_UOf0C+-^xz|d;xg{TmfOTUz)#!0JXQYC{cgRFa!;V zt!I?0pxX0oIQ21*Uh~>FDw9P|MTl?z3_YdVv-B10!TQfmD&a;<2U!T94CC)!aFHg` zqvH=JJl>dBBGl4#N~HJMiwN1AC=-TJxg6=sVtS}PP<~xZTIBgkxRL6pomc?hh3VSR z7A@EZl|{Lqe;#KA8%+BB$qNb8a`e;a!m_JQ#VrW{4W-5&A3W z?~(wABaFm7&<(*WTfiO=cI(M-*!gOk$)UGY*JK5BJBiUEa&E*i+$(PvZd7Zz;#(vf zuB##!iWw4QP$+4&v)ZaQXGAo$i`$~tuO+r{8c((0uaFU`z}pl1Vo7MtlBiOW71hK$ zJY-J;Tc4rm_G*$%BB5f_u?yug(yd6=IE+Uf6Y9Ley(?rygCrfuSGOnA77gI`3#<_Oi0^0TZ5fr< z*#6Id`fH54o`wI`Hwt5a&|fFH>#_fvNB73M;S#|24ZR!YyWl|LiYLj3U9c}{A5Tbr zn&PUvJ@${?@kn3!@8PLmxZ5NBfq$>d4P*OnLY9LT>nb?jgN2Xk|64e>ceFpwM?L9D z|7Y>!r{Ks(_9C{=0|qeiI+lJPr+=h(za@|2llXpEuNNL~|G0l1>;FeMdiK~w@WM2? zbS?QdV*ebz1oT0$-=BRqqY9~6!`P6%)8&RB#(qmr1uFo%2nj7yC2wS@osjxy$cF?4 z*!_N7uf1C*WRaC5kx69g5ua*W9P?Lk$C1S7rHj!1PgWfN8rV(VT`E7F&?F;rGgog8 zbHq{lOijcQu#Jj!ioSWwfcg>K6v|zjM>!C>zcnls|yyfxH+Te4w}B z$7uZ}yHxVmzkYVqM!PAvAB{gpM@!(i^R~)ZzJu!wr;XrW_%6uYVu=HffDZkWCxe&# z)Hwel9Rz;m)PLM63D|o5#&1abPT+CXFdhRa2=_ssr#kh%uKL9h+|2&VU~iagoetLC zSYS{j>*v}#;~gGaq~C&4@15aJ?x__F+o{o^42-;kzk51&pHJk=C7f9jmT-*}g67Ve%LlKykFOqiOz z;%Ds378bvHM(LNuw}V}`wBu=|%C!H>g|rMmztpzI;`+*aTx@)iL=UtMslFjv=t zJd@7ofuPWbRA;1JwUPkCZv*qcIht?h#BU-x@b7468dA2M_c!ZvY_BCWwF$#^rriB? zRb}6cb-(gLtLlXPvvy`|@H_Io@MNQ;685i<`&6n6{LR%w~hMi@Ok_a%?x)v3zu#Qbi=sNw&R0& z?7-pgI+$N=OxgzDkp?*50(o~n2dVwXZ)YNYC~88pFL&ytejJ8OTMjrftVrXIw8!U} zY%7LFK$bssP5Q?K77iz3hK=CMG_vBnps7=$pDzb`-&*wEJN%IjT*#EjOHvyMTFo@dO98ae>+}NP&wVZT3#w;Z0R2V>bSsnt zro;L>=wC=^+8vydzUm*)9QzxaYj;aLxow$3E{7z|{o0BC#$-9=en}bH08MnY*Twk& zgYB~kIyjm^pI>!~C$gwzRCubIjX#|7_E!=ce=zFsJ-D+YQy3I^xUECr0g2`^JNh?O z3h*b0Y%GYcG4K7&h0n89c7`BDlN7Q~LvN|6F`-pPT0Bo`uhIRKSR;qJ z>Lmdpi=lh@1kYqtywmrINSm6$S0E zn!})hq5L@R9Pp--V27yQ-c_F^NzOSxeee%mI=R~G*T&*Z;nw;2|14Nu`&lS;(n^$FR&sRdd*GoSvE9RUjp7-nd`}fKv0m`yx{uAeyN^Hwx^MKwIDQ!46 zEJ&Y{UFBIW$OS_<_|5+fmi%Sv6YZbwKlADOEnD5>$*w2(Tf5xPAE9?QIkEOsWJm5O zJtBQZt{5DR|1Jc$Hs;!UBmJ3^pdZQe6@Ek`ZYULuBjvzpfZQ{RN8~vJ65SITy9clu z_`~J2?u?QC?EELxjoLe^G6@6x6Mm7CA+vd7`J4f0zcZ-(vdSYKP-+f*JurS8U+5y| z!?LA+O2qap%i=Fs=QWX_cs}5hXJ*RIta=k!XS2h#l|x-g>f~R zDZl|}r+th@Aoi{RM%#8-0lvmCuqo(bn19GycS5MgDK%r~f9VVOtU4vw`DGmGD zR@XA*oM7|Y!>{706H=Ba)(9%Q5QwQ z20|KELA_QB@_j1VtRM-<&*kZw7VOUjndpk&C^EtJ3&-yeKkEH}Kf~Lmka!OA1G!?; zdf%9Zu}m5{g8OyrZbuNNU(X>C?EZ2&BG_MXeLEdAVg8l8g>!01@5sOv{kZjwc?&b0 z^20QT<~zuZd&QtXo4lmzo9b}NrOJdRjqrpby`OqJGAza+)?V6_Sr#gz#LhL(zlipd z*=*>6XR)PJlF<5TN~VTbW@=4b`a(`?rcerBbPiLe6JjD=^}B;VQ=Rn8umS^ta;U^Ls75Yd*r`PJ4g4iKp#GDTTr=$f?d>$hc*@PW7uP$fvQypQ zyfv$;BtWDa@_mJv#pO>Mt%VIEsn~6eBQu^1Ldx#yPm%*cE?nfQNWj( zK%f5V+BNQa3H)))l6%ArCBpb)2KhI3YMefuaAU!L z=tVXFeQcBBJ5c{6o8(i`Rhg(EW>TeL{SQWI z#o7!aKo17lp9lIZ$h(rH+3{1tbA7WV45^#gm!n!t&dTfqfDf6%w3Z;3z3ynV_%@YD zLg0s}5m(S-_)+cZ0Qhwu2f|pqt1w~?zW4d#)67>4(L3uV%=95N4bV+i3+{Mf>eZnA5PRbBFd0$0| zduGA>m8y1uP0%dY{@V4*#rhIq&c8g@@A>uD@&2$5nZn%GlprpN1!e#oVDq*-8QNb5 z5hCQ9V<9X)%3UBS9E^hB0e>$;LZLw+ckcyK3cOe;BPrmp zVMI$eUNeTD2?c*kzLVQRnu&_XbOu!d{Td94OhMplJ-A|BAW!AGvQw4?RD>E0$YWu) z>Q2}&XkXvLBIE!+gJ!pRDk0-VCJ~GWcV9_P!o{*1t?G|Y+z74&f^=nb4a4(p1b3%o zo-&=G^dOyOy9nCDZ>3(-u|1YO5*>WQ_3Y8laEdHrwAG+Ll?ozZJwf||7)@9G9!b#7 zWbiN1^Y&GisE=FsNLqL0tkn-eg!hm=e^!-7*w>Siqo-3-U9fqNXc@qH`3$M@}<*8VUTQjF?parxeD@w{|4@L*FR0~Ps2!$maaAba<|9# zz4<}iQ8$e3*L1z~iyKCIU$BA}xnb--xA#%LAlAP-biWwVNmv(Q35>6n*f+q}GSWr9 zMtuRo{q(5FwP zq|0UY2;;(P@y3w1GllhniklcKFgwj>&t(S&eDOuyzq@vu(#7*)65(gPw>}Q<{>!dp zH@qhCMsRv+AH)-(^w94v*0hPeP8Ag$ICGwg9jCxv$RxBeh*NbJ8#LLP!QL=5YMRiR z$fw2hBIv7yrDg@^%L!sVu|9te^dCQEiuyjMkDNVprz59zwJ<*6KT65r)5cB#pF}jl z`gy4oPGc$$mkad;aL~w)?PnK{p#Gx;dOm*vHF`p}1HBT1T)vd-O97ruHp6-4;do0ugQRh!nwa=h9akfRc!H|UtQ#;kn^Y9- z!?LYk9DE4R7CX4$k1Y{$JO$FQ>rooD*vPqGzFPR)C(3wSJqPGy7!Os+N=v-OlSmke zf?+77habYJ|$voYhXy{sw6q6Y%Pe z++~HJydAR{EU|f~f(?1M|6N^ZJVeLQG3y|o>p*D{Dl6{ux$6wkGIbQ2c;WE{xWI9q?l0{ zC3*=n`aXQ#OK~=!cUQspL-=BX8^-!u#gTC&L?-x>lR|C;g9W}ZF*Te66=EVo|Izz$ zgWp%2slWjV5rMs#n4xeBAjtLteWIr;GM=jT}Ci!AU@bM2Z$v#!+TF8~<$f58kWfn{BAd_e?=|o`#ch@3BUaY zuAkyRLWxgBFU--H1Mim~4AaZ#%Q!`dr?iVJHqUs`WHIhGO`$@xH=;gIS0O(`n#paQ zI3ewHa~Yip(|pzd(64>F0Tw;(8r#6bqPiE{w>Taq#*9&$e|WQYtR z{sjd|{f^Krx*~m3=jK%$AF1U%P%o@>fQ{F*iH{w9a1QdNMAVB;TnGA-dQ)*h^JGe(LgiK@+Q%E~;3W*57<3AGs0O4bcoa4Eo17BH==3`0x67&kzmaG>eC25FFY-y@UJ^#=^>T)tmi75LEa9>>i>_*(~jC^&&syeZ1_1aEi2W~t}%=kRuA2l8iMHjua zlj1$!0QvCTrK?4}bd*&ZCtGU)c5I`v|pSM47aISWFEYL%-b?}^P$KX6FATX$Fqx^uS|1ITPUG?xU z(DM-pu)1Mr4(E%Wr1!{zT@p$_~#+N$WFw(bd#-sSa)36N4tTyH!o5VbZ zNZ^8v0g#u=UQf-_n0^cz2MYUQkPlmb>z2<%f8keqoYR&A|CQHxwd-u#KtbzlSOxOS z8bSiib`v$)0G~9%mVstQ?`)tAI=Tt+?E+4Oc7QyMvu^+4OoDesySTsAnD+ez+^|l2 zLet_1xwIZJ*pCdLZ!xRw z!>lC?nGXnc#HWEXD_}5G!c(6RDX=P5%u#Zy(`AQI9HJiO%qeFdu zAXs;>440NIxvdSWzG(FH>NJA@b(nqvf0<<0)sz%>ePZiJwKCbtl^#~1#mhWq5cQQv z|4*;Db`RzK^yLKwnOXW~eK?-5!1&~3V@3w{w}HO}^{dSOf$J{nhg1a6kCNejCCqNN z*bPIN@9gOvS}U}Vu}vC|vLL?wJaqUN(4wB2^p3Yc|6X)%YY?oCj1$fi4~T*P*L=$Q z7nz>^=;s9^55Cry-HWDHySDUdv65)>8FOT$sqC%t`_sx z;kbBr9HYkbVI0&qf!q_Q(GDXTg7xAAJRH2<-kg@Gj;wxBw>hy+`2IVc;H}U*F4!!? zNdI*Q>sx?7#+meb`!kCe5_@*W%i>qzwhD06p+Dxd+0YI0h=g+_{4&@t6 zP$yl(ae*p^1cKls-~Em<7B7<}i`me+}g z6%M1pzSsI)j8Ys6{5tH}0p+&h#_fw^7RB5K`?{FQ^IzUwbiSZ+yF3K!rJWx4Q=a+6MomVSbR^TH)jk9!QV)Ns?M*6Il(owWH$OA zdLlPW9B1X)bHE4S+muOag1mWvzfmAx7K8i}YGf*9UDN~79f?|7A1x!xRwwkdQH$DH zXuKQ?`uoAVwh0*f2YEOV{Yze4jSQUuUs8(^>_s2ZyH#6hoY6z)oAffs8{wM@<-3s0 zXP6Q%OA2|rMCfghr|Iu^(9NA?U|$9X6zrGFzd3qm!fBBW`JTa;g2~m+hJ_WjA99c|PFJ|4^H&hjc^wp|h*B;+~$XSJjY= zrM|9i#`7D~BmH-J5HI2l{s7#6xc));nsvE3^`%9W%f@C|^4;P66umXnj6{Sf?_ZD2nj+`hG{ zD(qUES^ZFV@tNhB!e>79pTsu|*bSTfyAJqq*rhismKj!ae^@D3q>Ia+7 zJQ)vs8t#kff?Z;!qo7y=~OpTlD=Fz*i|xDEO6jFRxlO zg|`_;dSh4}Id?IK>%;imn9Y!Tg%$@VVdc0$88?sz^N}w6-0~t3Bou&JaynW@lDhZH z$)c}+y*dy5O){ZDl$9|ry6B+O@0xu{d#?J=|#le+%TZMR}LnAwT%l@W{Cr zitB`2ZL++>nPh)?c)!{64Q_zl+-(%vJ!}HoSSQAt%q>9z`n*mYr3wRoyNqO|V19m5 z-qE|gPSpS9`jC)&Ts5q}7wg|n#q)g{&=v<7&^KWIHPRs9XPxwUDo<>M_h~ZYb#0Rd zMb@I;*tauD&!*2B8_L@Zu{>mxT)LFrs(C3j9%bo~UJEZ94XTgp6+gSymSTz2za;_i`&Ztkc$19IG3LgBeGWg5&idmht+rLymP(*LsJgkjt-4vK z#rP)Dr^qfci>`~b-c=0o0Q%Gc?&Iek=-27#N+oF97Ld6Q(OwCXN^87eZ@kDDHXecW zU&oZd7O7Z!pljvI*NvC&?X@1T=%xX^eROf)R7QL{vjWaAp}z20k5Z`y-ts-MakhYb zN=ZpcP+U~B$svUDb1di^lRy7FEIuqgN){FVN5HopDCo>nJJhx{iF<-OPM_H~3wTEc zjE~i4LEziQSiLR3GlwC(XwmZJ8t^`dAts41{2ZvXQ)7&$k{|i_{_!9ERuGmJ_l^3t ze&}>`o$#ljW4o;BmJGA*vS|&RZ_CPV-)iF8g%)p1)HLAd3E2zJ!{s!bc&>(z^s;jO z`qezp2V5eU8{jEK^>(Cy{C+(C$LjY_&ofJVVBUt_A$6zsH<5N6eK#hlPtIkUwXU zOSyqE-W-rH2wPUCj#!{XjQmSsRzyNuCSH-mMSWIo;HVaEK3k9~UR)+v4)St$dQ^jb zQR@D{rE{u5e-68!s!&3P(xQDyM^GLrMzGe|7U@vlR7Z zVNu^p(?Qx}ss5<;=V%Ga`|-Nj0tns50X`R}8w_auK>eNjB>puC#Em~E{{b0U3xP^?%>_#T6xzvxL&FpRlLMkJbR&(v>?Bf8yr%t=!o#9*Acyp z{E|IBWLh#jj(sBfGv)bdm$fU%X(9u!AMnp}L15pTd5$WbC1XGEPlLI*=m_0SPtw5+ z=)d#cdTu=q9!p~DfP@G4o8#XQK(l{*Vvj_rOio?`msqW$-$8x55Xj%VGrSY@VIBFn zm8Gu~^>CA=!+Om85zs$eujbdOHENJYK>SR26%+(fyqC<2@nUek?RAvbexPvwj!Xvr3~pxNZ$SRactGjd@Im}q`TFax59tP~yQ-VjKS#@i zr`p@ky-Hq%6OPY=KbnAku2foi7M__u1o1b9B;@AiCTS8?iLylu(&yyQjm9`8P84E~UF=kEUkzJVd~ z11X7tdMP+48$2q#io7?>`Wf_-O>e*b>)ip%mxGBH{Z7Zq$X|AJy zekbslc2!-df59=?JPqvO&S$2L(|Bk^>&Ga<@O^8SE&Bs(CQJUS;op2V{{p^&!qR)z zqw}Mz{1*#h7OsNyzM-qN!wq9SpQD}e1iX-JbHhl)k&pTN7XnlsqDI^KTEra7I+9N^uImXYB3X(@t-{D=GpOZpkWi0J6(Sj1;67s{tZ$PG1q zF34p+IDY)(CGh_8LC4Dj>y>1xQj`N8X5KwcwG8u<*C(d-4C)?E9?rn?j3vVR?_}?8 zrkAY6mRFp!s`YyE-*o`BC5Vh^{Wy$I?=J-8OXGHP`3NDs{y zFQ%Y=i}LLmkRM>)CJ_d@LQXUKS9Zb$Wk_3Uk|C~#`KP9&^tiBr5X6UxY2ANx+_|y8 z&Gf@Co@cVY-*J2lw5K9d6w1$}l%M9XvC5zdTC<^y_B~2M3#vk@csbejpNyz?{0Gm7gc&3qJRrsF z`YV;`=?$6|)nVD5@SmXngpV&>GjU8@BljQ=`E7iBLSgfEO>w-ES&sbK)3fRz(SSCk zC8}(g56(9!ZSMBS{WDr_1P_Qi>svo<6Kcn3o>fIHhW4u$E<2~R>>uhLe0Ul6bHTh< zW~=+pH{VY9PKfe!{H}*t%Jhs4x?E>M4EPT&*soq3A%7Sz+z)jiu-v_$NY7iee3=_& z`N7g@OWiQif9|BBKf7TD=mDafF{X#J>q6YG70PX~pZ{h{` z6{}~r-hJx``0m=k-bZ{oaPW)9u%C3SNMDm!C;oK7f(5iwR~*(b#bnJ#%5Lkp7g(a(SSAHh92?=$MKUVM|bGs5(3jBGhAC^~z+0H+d1_ z!G$jUGATWkB#=eol|+>V>sQm_=NARJG2Xx-Fg~JG0)KFooOXDJVuND(<+d}-k9l_I zixo2R{IyhT#tEGjOn8WAX9aAY_MAR(iD|g=yzv71r(wFv!mF$Y{G009=m4ppbFEt2 z(+uft^!LDjdal{-f_9<w_sgeMUt@eX=%uKGV$gOW0_O0pk`VzE z(BJu!mUzdbzJ~fi%fmwtwFA3P;{6K~Cr+$&X$Lxj`aXzv(06z&Mx2E4E3YKy-zC?` zkMKeJG64!n>J&&zh+$g z*!XOyfAxh=cgobvH$Dd6lT!izszT>SHSdd$CF8m5cqCg-uPO9?W!8k({5PP#@ZWy_ zCyOPbe@62kWQEgUzXw;JI|x77>H`;Av-FHESWenj{?M*UGEO6QO-L({dH^V?kaUTDS@F#dZS_oiF5X%Y!)N>*=&pImJVK zuUsQ6(kQ`R@MrE^y9f6Jfqt6|w~3r;alAGp`YI0m<2CdB{b+B-92go&0DW^95{PJ@ zBXl^!qTjsgy7<|+jSdI>0qD1xBEAxir)SEcC04G5=h)F&0`aSQbXA(@30J;uk|M2_ z3XDv(D(Z(A(C2beu3jF|-#d9BWfI|}asP(dC06iAH`fLRv`B-3L{_2W8+b@4$otlP zuPksz(J@re$>!g*Wvb$Fz97GI2Vi~VwSJ)vYS)?LhC`A5Jlhv$i-3D#hP+EYDHILl z2)RsdaM6FXDlu^-D^Hq9TSYX`Kgbi1mEnF@FVHstzmu-CHk7QeZ|@w|+ZF>p{h~b@ za@VTA20U*C`dd?#JxT}gE*lWB=Nnl8r#hxTX75A?T+F&24DoZdo<3iW@$=lA;mZ~p zPx-b@*7A7^$(~T@t1z3H6u2DbSA)B+U#8=1CSP&BMSZ$zpHxa5c$<=782ob(4-1lx z0p5iEIhTH(oc!&{x;?Ub<`DYpUI~~#5p8=ld>SF1g8Bih4_i=Pm9Gxmb%&+?Yvf$^ z2>rVofP9MS&FeDrhWLa0hXTxh0`+~pyBhodW!4w5K-)mw;ovW4>Hi&fJ>qxg`kwpY ze1<3H5)R;Jz_{@<82Fkk*njmF-)UUf4Gw&R_2Nm%N zMJvHup9nm9$q(od&qHXvJAjr4@80^{o4b$^k z>wXjD5A6wlXZ`D7Ik2TV2J-v;{rxwLvA*gwfxtw4Z0~`FMp|Xe)BmJ4?+Gpuwq2cc z44ww3f2f}8bD%gr{-clZeh@Rf6Z3f@L7&e6>(rZh^)~t!K|Z&V>CDo@eWr?vc@P@8 z$s2E91qR;~ljL4`op7KqA>Os0xT=dJ9AK>v`PV%zinL?U-d(?X?J{DH2mUok;e4J@ z3i<*&TAeX{@w@?X!%6W1o_>+a+w9(kNIRSb<|4=xL z*kxL28jJSHZx>Tiz1C;Me=Utxqx}11*Z%6o=6IXfK_I^k{7<1lQXy#bsLxlx`hoe9 zECziHJ~FhgC)U)|pluKbJ~R=;w}Jd!^dG)Hc0~W%3ew-F&-WPU|1yyOp#PI-xzFtI z{A>xx3$G)g4+Itu+s50bEvqug`rMN|TsfcF%bCB&LOtBa7|sjlF5;Z=uK@dOV_HTQ z)?;DmW8c0m7lhOa=f^HZz5gWa_zyRX?ce+TdtW>Scer7sFVoP9L6{p@XN09!V9%^P zM2!UHrK7iJ1N@1{pSS}+uYX*z;*Y59aNr@xU(%B~I z=j~Y->2>XnsiXEe!oaR@8Y2blc}=;-4Pm{{mPh(O!mB^1yX~&W`R#iwe;*$-*|zD7 zyB^zz{M(cF6HI%|zku~qmd-j0tT3EJka3IP0fw66OL)VKvmGR4rm(9%Gu~xCnl`*T zry24qxAdp5_GD^bsV)c3+egjX+_B1ecByhv(G<+5d!|T{-mx_Iz!btP#{Jh-W|)WR z8Tu7ORJ-c6!e_lk;!h6aer!rIMeHSuYO`VaGKu7->@KLiVSvgjq!J#%!zUni-1!Vc;)&Of_(Rf+KDe-{56 z@WXpY_U=2qM^Xaz$hC=>-}Ws%N`d|(R==K>yvR`?U###5NB{D(PSX{PPws&=M}PFQ zUfy+diOyf`9|!T<61eYBsq{KCFJ8M?vmM_*^|Sxn$}#gEkTza3p1%fvR}J2ujPzd) z;{pFgGLZp(gcI$l*`U7|41Qsn>U$9LNQnV|$>n3k%7a`%e7xOGk)ITN>PddAZ~})l z#%^BKE>8GuIOW39YM&EH5lN`u+)1%IPKKxi-Nk2mE20T$Uwf*oWhObpQ+@A`_SvbR zorUoS`0+Y&dB!xbU#hxOvhj4@N&!hidq9!$R*P%BIq*NokK=2(?)8W15X)@i0yoUc zPq-k%4I{q8@ta+47~4;}-*f+QIP$^g58N=e-?10uE4~T}%FB)nbA}6Y@t^yb9^GGp z&wDV*aq02;2b16LdmQeXSTxBEh8GfaXgSOJWBrvPxzoQ z)?JVMAhPSp^E;#Q1RvLUbBEp?A`J9k>KcBAo^V|UXG{JKkd}7Phf5_!PYxHPV>RSs znm%r=h}N##v+e0I3GgDi&Oc+ zD2;dX3j;F$?C-stA(w>TOq&uLo0un*ifEp z7x=Zn%eK>4Q_LIl+QnYyvsC(2N;8~M=9t9mVx@iPGO>;C)9Yvk#0P}}L3M{4;dYM) z`Iw*94XarfURaug_9f&q9XbUM1PY)(N7sp~bYWrDj!iIwBzilW>r-D}f7+;_SK&62 zi&@bABZe72W!|JIsYLimlpB&|w?{O7snCjXiB=7oFwg!u$X$yZv* z@FC=_^F}VN5`QD3!HeO+|#o@)yIdK z!wc7QWf1X)wel?CC-~v)5AcqW5=a6`DdUEaK7PrI z8TEg|<4Di_OHOw^!k;-N*&c^Kf8zcyLLP^aejmR-%=P?zx$gd0|F685A~%fmI%CUGy+|NqVYM}GIXe!%jNjz{-_;P@}aT$(%by!g{Deaa8y`?n?Z*tN5( z7|4I+G;H}PruR0``@1!}C6imuy?>2D20n1?=Gyl0BG0muz`Gr4s|oDkih0}mB1;`T zh|QhW>e~8wEg>NjurU^lA2p;WH_<-zZ2fmG``IZE(bDCnh8M&ATVmhV3T@h?NXIdH zmEJXd2lO91@KN}T6;nb2V=biH6XSnL3tGNyx-%th5f|$c53Dm~mNlY>7{s3`O@#I?n# zGQ`I5c;2(GPnk(4QV)lR1@w8DaCvTS5o^Br5+2&048w@I4~Qk)afzb*^=~=Y2;8ni zKU8fw9S!ybsuSp?2 zSYRT?Xr#yCf|MU$czOJcrOy}5KzjJ#?|+EZZdPqxxVgSCIsnc)9t@(bMoQC z;ql?&;W^9P~c_vpwF zd;b5vF*SRoK(r(!UL%Nx{d;Tggw+JLkq`8Y`u4>9%c|%`P6>g6`&qqHE?>l3wfImh zsmJGM%_s{h4e>oPWua91b8Mz?VNkaTZZRTD;MetjnhpFosc<9q#e(#=%r_7up{pRh zO)UIoRQ|GXZtGi5)c;r5v}@|cWrp`{-6Jq{-5Fx zIG-KCJEpPYT`A5(dkwCa4P|UM)l?9tsfP1&LD}(2(cJhwQ->>P1=xqlHT5%H+Aiip zPK`)2)J}%YX3KJjkk!<9)&VfzIM3haT?O~&$yAXZgEG6d+7X85&m%)iW9j!Fa=Cav z1?aBFgPCWm1`|O*Avg+aI z?{Yh5gZ(G0%1%g;^)dy*>?ApPg$CT>(+fat&`s}S3laLTew7t|{7ZiT-H`F%4Xf7Jn}Qyj+4 zpl^eI+varyE=<@w-T#~M82`&RZ_0#hQR8sh0a2NRA=*6dEEaPsE+jlb>{6CRL zEQdf|1A7F0JWF!!{9Bo08YUb_s~WFF`)2P4+dqJ4q7^^W6AAd=1cKhF2T2MkAU8nY zE=b8ppTU_yFb0P567p%1N{g8-Or&^vHoR|Z{pr)`npAg*smt}-cH3a1OTM79X{j{^*bVXC>as#l-C8T1oR>5+6} z3>8_U7Ixx!8MS(C)oG5#gS02 ztATZ}_Zz&I@;>TkBSj(h~43?NB+?JnEsE?dz}8U{&5)TeOl$b?e6~g z{C~z>>X7}Acs8a&XAP8>;&sj_|v}@Y{n}&u~g>mk<`?0h3jtg z<~VC7T^Mkod|h-_Dor=?GE`#>%YhTZk0-wrztPJ71L4VH!M{_LqwH=!IOu>Y7piqN zl7O{!H&nnst{qR|C@{WLI()~>XCe;|)5f!?ugi~=eeIw{_H7`u@0DK{heS$s?8_-N zRu6`wh^!y7uR!_K+MwA6(JrBmM2Qh!HJKy+sYG!S;*Z=gT|i>t z@U6Miiu$%W=%pE($QbcVPSN>t!1wgzB;irn*RrzanQH6SA z-fH0|uZo0V-7VDzddP77H2tdXan{{**lskJ&&wqBUj)9az5w!_PhSK1PMUI3)@>p- zo~6-|-X<<+a_s(D*#^>&RZ4 zS~S?%kmkSY!k=J0KD8_MjbHCAF8-Rb_Y3u1p3xr`eqyuZ<#rxE|BAN%eS1S=6ZqrB z$Un9F&4!Kw(D%7K_Wl=bkzBEa%VGpU{U;L~hAxN@q>l-3;lCS3dRp_BHE%oxA9lkd z@)K6cRj_^D!Q3b4xkSq^b=PD2H$QlEKRRq5xpX9#wLh>8!f<@&3HhEo@sWJT`Ul06 zNBs?~KF?(Wu`d>OJm8>nK`waO(ns`;^>ui^2gCYfR^t8}{A&DOI3E+W`MVib%wHYa zP`0R49+XI#(ozit71A;K4*@lVeYRK!o8dK>&h zXEtUi7;8P1JIiiiJh`St>Sq%CLF`e-xdZ1hg^*5Sh~HxHOCiGM#jxLSozN9-f~tJW zSsi=-pXIn$v+I4GkZ&C=34?$V3m?~W;Ql4jKU|fxf! zXO;&BL`8ktEgCycGM~wS`$>UcU+GDK+XRP*jiK1T1}^ZV$0*Op(Sd(kMgmh)Q(pVt z?rqi$g1?TCrmQkg$Ev}5mTfe|6Xe&OF9ucQ{C2y8&i)4J*9`Y#fVWXd1YT1?dFwz7;p8%t1?lro_utqz z0dal_D^+=mOn-7wM@q{H&vu#4Uw$>0AA?7KC~*Zn-y56(Czcer7c*F`Tp z@-N`?KUlY8rn?^DS>O3ial<&DqP|D`0qem%{{;PKm1mW^+vD?NdKXV5<)na_sj5VL zme$`7o(sty$lt4LRtLcS{0nfepqSvk?C7moDrM)Xs63H>gm7A7CfW7R4fzqec(rCy zvj^g1RmY0=1H~K(;W6xeUVomr;vf8iaM72Eh3tMjv*N*TTAg)_WA65LUIh4KvmZ|4 z4IeT&KcqZf$ZBDW_dxv8JKZY^VUy6GHRfcBTwr6I;ZE@hj2}-A5>$i{(QfWZlC%Zh zmvp7Z{7SsnA#S9Mhw`WA@U4yK=^WbLIV8mSkY~4M1HRIp3u4xvbw~6K+#inR2H+p) z{~;nzz-4GbA>a2@u<5Owl)=dsZ}<-K0|g+`Se`?~oZ8A7*_XDiZ z&(U!I$3agn8{dN%H}Wr_mn1kF^P$<>qPv}~5r#T8I^Z-&l)1Bw&2Lb(m3b1Wn9t!( z;Gn&bpD$0cVsX#_;H_B$kduzsMy-+`2v0~XW_kJUO>@AAv8VCC96cWwG zi<7P-CqORUCCorvlgPmN=b#E6*bY)Knvi~`1%)9$WDDMR7F6Miw;K4fvvHowiKQd z3D$8H`j0IWisw%cu1>CCH_f%5ZM0i!i-DLeYiz{bm(fKr0^NlV}QU7^X7JMFj zcOmVx3g%Mm^G~u0B<#C-+^4eroQ@UEEZ7gtu6UwPh6U1(q!`|O!>I~L(eSfZ{dII-oK^?2L3j=7wc2Y#=1x#AGf67qyDq~iZ9#& zwRYuXCD6ZFtXQ@#KGEP*4v%nhUVmxj{G_+jCDT}-R-q?+o11*|?eL(<>56>mP~xYq zzBe+`(^q1^`E2c1$X@^jn=!pNYX3BKUKH_ z)(%Qn=w&ZreV%=J!y-0>NmUYyzC~nY+bbHij`9NP+bF=mJxx9c8Uawca@d`)* zpOyssdW{d;t`qVj=l?MR!k@XMO&U3!n=ummC#=u^s~cw#*q>J}nJgU~aG)}69~|;? z&vI_$57@18#flvG(&+cDD_N|l%yyCgbVoWDWYXpYg%ST-c<}zqKG270llC~1KLpsr z`%8n|wG4O4gpXHnVMxAbIA^0`%R+41>DYf6)2gOxupV(9@sJx&KiQM`XXi#a(c_m| zSFQwAVC4$7mFSZ$DNT;+cZoi-tEwF1{rvOy^RT;{KC0^D?bC`c+~q}*A8BZ64|Q;^n5Sa|Gd@)ed+JVNRyCdh6gRkYeoc13zGXG z{heXTruYV>ia`q=RD^cz>+|hC)a(LzZjGy-#kc}@Ix8~IMpR8rtb6JlPW;BWdzG+% zs#%*?eem<+^Utks$f(Z`_d1mD&6nNW@$Tw9U%pQ9++<-_r{Q^rdP-urYy+kul(-!q-U(+G0tBpW`&al>$uk;Ni`e`vjQs{cZj-slEeD_tD!%q-vs|mBZAj{`O{GtD-QHL$NiA6#$Y(V z+%-lv7c7+YG3+Oo!GAiHvRLK;LjvG;Xy*$vB&6{)TJ0R60sqJQ=K`W3<>J4&HZux?ubehr_7uj(BNgRM=VrW2Wr-)H@~g8lF@}~z{r7$8*xpjMn{Bb) z$IP9dkYs7yVlOlo4oMG(wYzr9HuCp9r>Cuo7wQxJg+IdkkS$sHb$)-_1MHC+p#x9z zyLeqf0`%`6;nPL<1APt4`2s%RT2h_yyqa_U#@XRmz+v ze>HZ|_ywuKBgVkyyyGFy68S|@Y9oraM$=10Q_At`JGYDcz;W}pCr-u| ze~IVFJ~^+O(}A|}$z{oD)y4{G)x7@VaEx)umW8kfznQM*pl+PbNsG^Y8ViX41@8FS(`16-m&RwXLsrg5i0x@UJUDOEx+KCqYT;y?OwH$jpr8?wa zUa)AjIwB}WrDrQ!JsEq~Yp4h1ORV{^3#|H0D2x6paz`t*LxEjXLJIuEbrbpjZh{BT z^|Ze?d-!dB9NHyRpYHESUrtOIkNV6e?-=f|vAv{wYPkmTg~BsulG&IbdFPCUgkOnY z_?O^*-8{LQbrHW^X*gM30&N`9r%yxqc<}SM4}`XG4E)W(-hIs2gE-w1i~D`%n=9UjkCBK~)J zvZ2?2xgh<>`hY*Y!oHDqDcdWXp2L3A`Of7=cG&5ZvQg1U>vOxu(4i$$r%w6VuUr?A zJn`3=FN*ii{Cw|@+R@$DW9m=tQ!a;oLHnastG04;s}7HEbh|SF`cDU6dZ`y@^;>;4 zr|Et&tnl{E9Lsi&{V-KCWNN?#VLhCUkPQBH;+HDO_aL9(*Lo-)aW=RjPL4} z$jpU*QbandJye?l7<>GyFTS|Wsrx{)czaz}_~(4Gr~v^Ix?#_s>a_s=In~{gAfosD z{UqdV$?ThFgRU{&zXjoI3^d3**~ddk{#dPBkZP3n9Bj&o$szg>|IO)Q)-8)Ea8m1* zW4uDHtm};9ip_VpiG6fGFsi~O^dvYq(}tY*Z}SoVOcwDM@Grf61?k2Wptr8aZn?RC zSu%lW7R8V9i$613#7$)6bq8rasStJRu(>{h{VWHqVn?l*iPQ{Y`UH z>)7M#RA>qEL*RQ}wJvT%W|5zIHPL_gt4Kcxf2a{d{Uj4c?!ozR?aOhSf1uvv!fti$hV#^d4cC^>jmLmTi661 zU3q^Yj~UxAmX<{CmqGb<@CrMa+H3N;afB(ga)Yd#KYz`5dF6beJmvSjJT(%}kHLO< ziZqZx?zCowX+kX=v=P29Zrl<78f489l%@N!wXnZ|edcGZlC7tFzdd{Q$Sn;9-Nt*w zBEX4?o6Fz0)Bd@qs&EYK)q+hjBlJVy|MAPUF;iiE_CV4udja`y_mUy`%>5h4;Jayj z_l)ujG34m0GHOpWko>;5SU%p;JY#B%=inPnHTpsNFnx)>mgHwI%{rZXx^z~4iD*Dq@T zcT<9&@25fh@A62|nQ^XZZ9ecHsOLoO`B*q8o#;L;kr}r;f@#-@B z_d76i?>&3C5&_@;X&B?1dCunlm&QL_O_B&pqURM9B0h?WxOJ6;_G9nOFV7Y7HNdZT z-;JH|FuJ|A0}|ON=yPp2G4?>8l5;*Uuai|GoKUT+OSs;e>hIr1eI>x(yHPtcR{i|=<@(JG~o8Es{sBeYuJHXctiu@4K zkLPZ@UmpVMLy4z9Yi#tD#zQ(9y?TW8_H9rJoo``ZTRe_g@m&M_BzzZ>Mxh)nQNg(G zXP_wu37_x2u-XmAHfn86A9ok{SgkvlhwA`B56I{8G;1pKW@z^z-GF~z`0Ipxs?!-3 zF|{1*0ikcQ`~jG7*5lM$Y@Uww;paE1sKvAv3c5cx6aPPcd?<_;<=_^)d4Cm%hZUArn`>av*|pevxmkuFVx`U%O&Fy(RXR6J7Bezq(Gi45gjp3U|3I7&kgZlHS4E*I#UDt-%+^%QzMpYl-@3u2; zAzPH6h(ij^cUSdi}aw)!(%F0j`?FTt-tUr^50Nn)_%=sdU}eb#DAZ?HP?<@ywx zeju&)tCLoMl2{YqmdLD$0?6Z2p)DRc!MkVKU}~>^_`_PEJmCk}u`DV_iSTn}>}rcX zAod7?<8D+B?QZC+GGoR!V}0he-dg;75VOsg^D|gaa~Cd5Y9HU>)9Kwc=nncnZQ3-< zx+T%`yw}^}p%cFO68=~dJ$G5*k|GD#--hqhzfA8YZy}$D>uoUAPqiyg*BP-6yakBeA}8oyHJVzU6>R`sH>mGk+G0i3zM4_vz>L!d3;iS(`m9ozKtttm0Eyv#g8u z<4Kc3M&$-YXk{@{-X8dk;X{Xp!QN;*^Wphxx{q+Tju*{8==JJHYn$r|j(jrbIrv+9 z_}dSbo07NWq?ZI$MP-&MF4os?9$|IxLh^+FT(W-G;X&zxwBD+p6TJzaHD`vkIY#s4 zpE^vGUlAOTD#mx=^Gk;nr3mFE_+|Rs*y#eS!I!2;pEe2Evmp_^9_2ywS-~<{%&_NX z1D_WxoJDz-1pjtY3MDHfjY6$aYi7ru3D+yxWb$8h^3qFpC5S&tZ4G}qELr|EL&pZL`y z4brmL;U!sBu#oou5Zy=5uyeyypULPx>Wp9PDu%h+A`NmHMg5Om<)AN^ny(S21Fwnw z4+tx~4Xx)D??tW?U|R16Uv;o16x6$GJ5P5a)J|Um#M_$WvIREd3z#nfPgU?~| zSI`Xp5ckc_4XUsp!Uy=TXi0!F)mYUae*9t25!>VXrE9ganzxruUA|(4&_2y?{)19~ z0jB5S`oTwlDgWZJf?tX-*3ZP~4zWK}{-7N`6DCJy)`H^Z zGaCe$#&3A9^?p{xf@r-iUH|>~Ox&Nt8>DZ97w&@-W1%lsZJg)l4t+H(lWp3d<>jJA?+3QbmrgGHxk6kNmfb5En)N~)_nk(_;I3t-OVt+>q`*%ERWYC6zgp1De z@#%VY8?nNu{8H@45{WNX4?e2mb84K}Kn`9~nN{Ywka_vf20tNpW70d)&f}Kyuvb<} zCF${bTpRK4T1B%FXM9L62C-Th;RD=T#AE)6L+IQBdV~9s5O6E5(F#`&7sv;eEq^gf zb%=e!jKqe4J0WTj)UrOCMH%{^??4W?8E2l5`A8PyQVI@Pc{a;(43iFK@I&E3`-bt-o~A7?fb45F{Tc#`7hZKMC>( zzv4uHHH`0XspsB8p+5CTV`r~N?cHq4aKI;A_c_pu^`T2Q+Sf0I!Hxh^|D2ew(jFcS z1`YDP{~zJDmwp?KG+`T-SRzPFo_S720x#NU5v1&H!<4N4KF@z_z2;w$9S^R9QC zXcu6f-n?(KhoJl)vJnrV6v9XejqjNN?PA8Bz;{ZmSQBvn+iZdGG<@;-TI~PaaKIn5 zT@OVuRm`=u#gM)vM2(Nc`h3uj42=EE|Fe3QS{=Ibm z<`emotf=sFu06w@pc6Ul?G*3dg3T!O4Qg-)0e)Q}bs7J2q5N#zmt9F-S0})SckPL& zbRAnww1pDb9xzb}5>=s?f2O9W{+PJ$h;oGe2f&(w9r?o3d3=Gv9y`ZA44Pt4mgD{XHrI!PT0;76@lOEr%qVZk6?61gi)QH+wjTD_W6hx zFw7o4Hex2Iq&0z_Jc)qB0!Z!I(>HpbmVNgztCqyWk?*b(V7@=@`Se&m(fFGBpCtMn zqh6OEk*2}?Z!iAsT-cU&-}sY;=<`@LNpO zaB>8;Uk>+|s=`7npf9)dau1GRh!-T`!9k&%udxD8aHVYeFqEC4VT!S0# zug&XLog%Oj5kNk~{v?&T`9XZwpWI$@`@?K4t2p`I4&3J+e^M(&Akle;5-Q+d0m;a% zg}qaGz$wF4KWKC0&~Jg*|JQwYj4|!J>s%rf>ao6?$KFb+XsPm(vfycSKHQH3(AJ3z zX|;~U?`a$L2>;LW{ zcwIi-nPB1f$Fh%EoiyDQ{*D!~^H$Cg>eKo;*yQ~pmI2%zLpBhA@QU07~}Pz}+6MM8L}*gEhxSU{tIL{pmY# zE?WSpf0^DZKK^H6E$Hvfr8fC1zdwimP}3xDmQ=7`0=v%yjC>6K=E7M70x(W&1*ID6 zw{Hsw@nA>{-3k8}#m$(JAWM)vC3{MGCjj;-+1X-$h+l7NbL98o4jiQ{iQeAL>6ccFmsYA~tWnd`0{G4ZEra!M4r0qSmV8O{1paw{ z`ph9J%WdJLL{G=<@8;y{xTZn&AivtAL z*U$!iH|?pqO|0`gG_Kw20)GvDx3;#~^nh;x{=zk0G3kqM$+{XqU;1G`n4$<#mK{1~ zeE{hje(a?92bABf6JxoqiMK1!QbQNcxv3oTm~O>GD_*f2BPG^f6=B3w(Q2p8BhC zx5eKjxO9NOeE17^lgd&e{GPaU=RON$f%ied|L?)H-fJ>;{YJ>A_5)M=XW7I4zWKg5 z3u&>#|Bm>5+y{jW1Gh)_=fwZSLO%uG$vi;7b;C0`_Rss9`bE|PR6cZlXtYqCV9{S7zVrF747Z)qYuW z`L)-lIMfg81q?TnLG_oE^nQrHlSbV7mXa7=Ki8EN6?4X8wU@cgC4}$n;vedJp4lIy z=+ssbJ*&I6x3H_Bzn1Wi{4bWqUFnB{nsWn-K27+_)+OC^?f%HzjW)|QkgvPu8A|%Y z>eg;KQgS9BMX~!bcM(}!Rqw~Yr~SV|uZF)LOUhM5Xo@NVczQy=w0h<$wq$0mg}xqr zoCyEp6XvZ#bc&L$ahdkhNbOV8n|%q9I?ckL4Bi4zDi;cV7|Yvt_T!i1%Eyg z%Tqq$fBEC=GZXmD@85o#Usx@ar}w#k_3CaM^`-RN2t063)@mJ8b@(DFXfNX<&M1_p z{=COMvq6CQ_Mw1m4;L=8%?IWGD=_t^$tN;HY!A<`iFDLw&WHXlpFfz5B7DPkau&Xt zW!;jCD&i+I)-Oyr9DVD_j{08HbnN#RQpy8QMfcX8;O3d~p`SCWb+linvH|iq1mO8)EULq~?}qtA zt9&W;&v1@g)SZm&oTo>*irEh;^17XSl!&1PeVJ1V__NEn1^by!U=Z;G@RvUx?ymad zalRn?$S=PCz7L{5KCQ@%@RIvY3A2KbF96@V)ZYm(#V1F!p#lLu3k^a2{)FWMO!9=?BK;q?VbvR#R6k zSB3iyMOEP@{V4K|(#QJgd=Lf$4$~z#FQ_qgL;py%W82{!s9(eRafruUsuqq z`jzdxP3tc-lg@{HqV^)qFN^#rwO6#lW}nBe?;U}&ti}2L%k=Q3xPQ@lhJMR6A^*Pv zw;i$7r|0bmu;CZ=?}y+W`sta?H2r$)$x^JpI4D%lc_}KE14)CkX z9Lv9ISD&8WF?_uPy@L4WN8#)N`Svi;OSmT#@t@j4`s?js-Q&$Z+#uQsPDg09 z(8cK`3%GJTzq-2ErQ}pjXL5IP5ADD2J^QREkCS?}s&>5l?q8^VN_1W*_P&wD`QUcF zmg7bQYuIm0oa-s)!^LNjPv?_Y?z=LQ{ZpDgIl}9QihBw8n#65fIPt@5gQ|V~YB1RC z(AMa|dEKB0^PX~oFN_FB7t#!hzP`T8Jd8j7^HaTih8vRwnD*~Ams{eix{drJT$VTm zPx@Jv!S}M|qWIrcM6DKmQ-V1Mf>UGi~xUdeFvmIX=SUs6F@B zDqHR09k0G5z7OG3W4#PTDSu~;EuYT&HBFfg{Dpt& zZeWQeS=#^a!!*7bT@ODI+N1Hezh9yLcxSXMePnJ226TX*h5t^8M3ZB9ykpR3uFsPE z>Ai`2|I+Zt*xlXG879LH;rZ0oZ%#g*<(B?z%vR#JwNV!?Jet}QXR531Do4*xw{APu zZ-V>F&D?6j|Ki^H^FnVaI-I+Qn8|84OEXiSsmIh!{`7i!u36*T(E0hcO1Isul}85rfxWDkOy9l8 z0$+#y;c1mV-MHG=pWF=fyo|^?ia?472wGEvoljG z_AdZ$V&*n+3X&)4M{i4w=>J)_5%H8LKG@hV(#ic8pYEsKR$WJ% z{8A$L`wz2W58wv-fPO9y`?(M9BiSd7>FX`!d%(Z!nS8KNB5`e3K2o$h!~Z|U`x_bx z?<&JMCzF)d?_C=okKim-owe0FmyQ19-(P94#M z@z1<5cPi|uaHp9={v{IAwFUO;$ye>97GPRWxLz0edBPW5Upbt&TO!vT70T24-r?^q z;yZnLC9G+ZkWcHiNbRH*U@E`tRY(7N%U(@!Pm%F)0qU58UhRWo;eE-+kYZf%(ex4}MlF8wU0|72BQ_ORK z{Zq^o|Fx^@=p<~-6p*rp{ELUVrY8#c@!0GLulTrTNFObU_yI*@o6~x(7TA*0K=k7K zdz-dca{IX%phwLxxLZp<34L3+wag7}PO{9rU5|Oem6Zuu*~- z8WOi+=tCfNG;|rej2$Mz|3WB48gT!aj*ps+E9UcEFNb~Fu)B5~{Ku{gzi^?qs?*eA zx?C0b>r<+V5#^_yaV1wN&ksBs0sXu9#TSM3M(fL6<8Gr5Sbv$3HvXUAKkZ;D?`h}% zgW$kfHh&<5f0sft#Pv`0BQrvqgy-@3UuPT%C}vba$U$D z*dM}A_#YPQQ~7?MhaWhU&$7J_wf|%IPwgWdsK^gcdHef4dfxra`gKD4e+#Z(cQDf- z9|6P!nA%$wVAD?^e9rWayeZ^Ud)Ee(fBjB@<-AXYkbmPjU*aLM|k~q z=Z?t@xVsJua*H8(T})!)tifmkGV(UlEE8|d@c zw9~4w0RaKD_mAuI#{Qh5aB>H%8U;m(tsoJmq!{@<2sm%!(TF4{SDZ#<9hS=eMWxrt>K$gGA5tGcX>A z$J#8kzirp~w15%5QrFJp&dR<~h|gqDgocjtGf6lp-dT?OOFlkeyXleX5o{926-&KH zA36r{^Clpk5YY>YCzP3^s|_&@MSh;9{_JMwJkxyc2>N$hwKaRkw)psoSx=TH$KF=CQJ;a}hr@rU71|pJ z53En?{b*#%L81IVf(O>;VHf}k^#{WAKJNZDdmw`C@t?{!;XY8PPx&cu=xq<%%k!y! zIG?W&@~yCVUMGG)Wp`ep_M&zay&;t6;oXk(4*D4m^vg8mx{dyTK4kesI?zw@clLST z=l!3UKQT|Ad?sLcSN5l_2G>)r4_(`7|9k7LArl+eNto~KXKLJV$mNecrq3~y7!IEL zgyeG z6FunzMIm{?4+qB{bp5^Zt}{HKpGW=|ZyyaoZA2~SYR@mcr8=DUA7;{$mEIF`bd#M7`lSsJ#2z?w^ z+)Cr^qTwWFX}_$G>{H|zUxiuOuk3T}rTchie}Dh8xSx#2H3sSP{$!lLf3hrjM6Jdo zjmm_7QVPH=QKK1mNjc_!{vutRFu$}u!A_S7Fs*Osk2~=HrNa&d2>G<$VeU{Mz*OHc z-xo_JPc7ur`pNMAVT}M&`x)Mj`YV40?|Q?b{PA5);(m(dgZnh-V_MTdnEK<33jiVC za(=hr6ZJn(Z#%D0{UaG2BK@NL0!S2v`jlU@vL#G_dHi2-^Em;g{G2E1#q$ZDA9-`H zkZ&p9vhVM~R6qLCksKi(&u5C6Hu($nM~z@`C(QVmK53xy@0z5<}FN)Yy9}uXScd<>@9fLZR~@c`bt3E& zQU1wm@$n^{C7mBW=$=FTLCbslBhbE_b27__zG1mP7Gu;m9IkKNw;9iOa|w8w%K`$N z>kz|!!;Inj3oNyH}=2} z0)CSGf6N#cH;5Xz9CJx8#XmItW$zJCA9yV0WKr<1oENbw0lISVex`*#MlFN8pkgRL zO*Bbs*GCk4fQJEdCh>uTLH-QC7@Npvjmbr6ZNul|bV6Bn9V$H@A ztL*2S^at%>?1v)-nBPC`ZNJYsp+3RUX4`!@wdZXo zU!(j_&OOW)%2WRE_Kp(*O!?X8Hag&|uUe@U^6B|4_cLz@FsXphQcEji$OiygibzCyoIE0iAyQ~f!Yj&MRg!Pmp)i0x5%WrRB4K0nht z%crtr#v(SENl72=)PuXLvHs}ON%FpG7{rh*GJR-2znqj6`;hhzka#s#;UrQe<@9p93&10`C=jw$068mGS@OT&29=k$;J)t4J40&(d?Wu< z2#=_IDg0J zftyVj{w zzHGjhO`JG!h+FXEn&Im4D$V?35`zF!`G%(KP^@KmxM!_|Z$N(!i?ytb_fL@7(wBL=p|SRM)$J<6 zKk(nVLVaqlNRLS;YbY!JdwA>`%5(4F;0eFLj;7m}mqJ`**^N$d@aMw{U0ezW1-XJP zQSk9;FGHS%+r2PzbbmCRA87wqnQ`0Q!@BAIQ10wEswAr+JSQkwh0|!nu*Z~A3%33V z=K){Mr{A`A5dPQVvkB!XzAbcp%yfcS5}d2LeY>iUYVha~vRXCW`Y zvT%?(P#c$CZXkJiY~r;`IJ@JxpWJ&PtsMC87uvng+(vCyW=D9v@*gwr;dBmpM86O5 z*NBh(QagXX={A!8@!ZnexWOd-GRElqBaz|@&i+IJBJe&Q9^M|7_vPAFWZnsYI@3I- zUKkX2`SwJ?zW~DH`3;)v8mJtt^c;o$Wg;DhH8g!lADQl3oM25Y$A|3gMj^R9a%*xs z<#w3#@rEEkIK}=>#wT(<^xI(X+-UW;LiFl19Igb-)kbJTv`;}B(QD@Z9Xlor(fne_ zJm+Z<8Z~>lKlF%YzNLJ;ce(8z_@?Rc~WeTr$;yJ^l8w) zAH#pX`~dmip#l25|0JQG_;(ZK_FW9j{A)g=b z<@o{Z6gy1oi}2e61`HN^Svl_y;AT>6OH=Xh)*9jraj9Ogfi5jA73$OYhO4Bbf&1jp zh>uqoI%-W9&hL=kadI_wU^>5x<& z(LMMO{v&+&Y~oFbLX`)AK7m1@wMiesZ?eb#xaU~+mCmm_ApciQe&%OS5bo2bTto7S zdGmToddBtsyl)IWuQhe~a&x8my!jg454Nhz*R~nVi;hQDPd^h$`Z&`vo_`*)fiP|y z=S}D&MI?-a3-A%H+$*`pAD*{T@HR?ke+C@J!0TL@ZXE zNnN&97%!Ti^ZCCY2kMH^WvVieT92$nH@IeVUEA+k!ik@M^yfb(NP3*Q_5!=PbU1ABf2@rwWf; zI-knxb7HE3YSj%SAH4vFE9%kEc%29o|q@mp+S z>>7QXJ~AdUD3sz!+`fI^fqw7EFPKhsEeYohXgZta{qjENUflOHmcGiK|5wAFQj%|3 zk9rfs`J3*a$Qv1n5b6AonYnsVcXW4H*Z7X{gx_^_b<@C`z~dgfO)%JP4~lL{@h!^$ z6aII8)rlB^9dzHQ`p=IJlmhqF4MDZqa^%OvG&c4>!!1O3(i78hzmS@`OpX1&3KF)Q zk&{m=TGM8Kwkm7lnNY;!@yCAoim~M~_j72&Oy)-UrJh!-NL#T&dEI&AJhxNue+&!a zKSrmBZ;LPH>r+}-9gJf$nU3;d4tiITZW&SHS3Gt#$BoAel|Unb@cpfp_r8?%ICaWz zD;mjP!Kl!oZzGzJl>7@V1-v#sF+D4UD*@M%N;4GaI@2>T(6zJ;upKGNPlg5D5)Rt725 z3Y9awGva>Xa_jGNwC^b1$Q z9#u#fkDI%`cG&-4>2t*A>t-8F_}SLt=>t7fw)2A;<5|=6&&&U4zH02` z1N6S$8Sn2CUdICai1L7NWOOwx{Xxj5@d`kI!~ZoH?^EBKJzQv?`g?t9R=WUG|Gbvo z&s2rNS?CPrOZ0vl=)I7y#*HGRJ|aD4n6LaD{u*Amr07=AezI;=hB#mJKEBg^2d5j; z8)6!?urFZjU~zG=&>qcK){{4y+I=3m-EuoMXp=_GriF)xV{au6#USt}7JnR%^XCQf z$3gcAHQZ)1?dK0~_us9JG3iaeCS=Oe+E88?o2?3U(0`$e39cH^jpfG zZiWGmbGl5=49p1Qyqa|5-4ym=nZrx`4i zjb=mBFn~Be%&^Zd803Dw{`j?o&ge&BTNaio{+zKgMcF>AGpZ}Aht69=J~pW^c@hhuD{GjcyI7GzSoJra09+edL@XK!g`1v5dIUjrw^Sk=)7Og>e+SnKuYrm{%I9>FQUH> zx1fI^uy6GXmPf#!dT@jW_H|I0iiy?E4~hu#CH+m!(@*z@%OYeGWI-~}2h~r1I%J60 zAFQ{z9&g;IO| zI|@C8_6D~9w_tmFBYOUtrH=gbzbgNW93YQ8mvN+D=nu8u6ncN108{^u6LRVYueyJn_mCzMgz zogX>V`WYBM{vQ0VgO{WY<{-%ji#KoHEYzp@+!(p0?XW8t(m^rGJPZij{mvrXqz|n( zZ2unQOO);(h(1uftkwOxzUjTbJ*59rHC`?^wi?@O+iN?WU@zeA?mjhyo5)S$=7B#z zeB#G{@)PX+Rlxts{VRL}O!f6IqwQ0yQP%Y0a@fzppNlEoRBHUxbfWxexO&*;saG-! ziBmIQFaA6(bQt|zItluumheAPDb7c8X1x4@kWKU1kujyWG0cE~h1wh!_%D6wr8#rX z{HZb7Fh1Rl=wJEiSuZ_8EOM)8|94i7;qn-Zk)zuQ+eXQhy&yKy~~LU zy5+cxlypjwKi?k#oq_$2Km+bPHjVrxhlDt5oHcTdTt)ca@%}ACm`C`}Xz%v_4v< z4AA%g2XMCC{XUInUV+8-i26+JWy7HneW&zyU~xaD{=X0ZahB2yqXT;`e_MPtdjIx~ z!_OgurvWt5-a|cfLV0RGGxEp{0p{OtX2IZx&W+B85paj_d*#ZPUb4^MJ9B3~`;b+# zt!&*$3M#d5$v(OS?7ecN{QB2>>Ax_ z@TOg~N6kpZ{sj3YUc31_Xl-!KA50|zKiP!SnRChW^;ivQuPe} zd{dntxwgA?`gFdKpk+gUI_W5{9pJ_(?>wz$`tvg#&XaJbmdZaIe`x$xfT0s$;txY% za3R3-KB0&Qqs~#i4v#?R10jDudv^1l7-hP`pg63c{eRXh@HcTdKN#RIBtp&hgm^Sl zec}E^m$e{MXpf$^eqv;kyY&a+@6&JeXR-O1YK#T(-{k*XD9^87Z}@YoVS9)|iTTu@`b+&jLOymsvab;7C7+*R(^sJJZQ6J3 zMWMWFlCD4OZ{;#@YM_L@$I+U z?jPxWXYNgXRqq_*td*-}sT%BO;V-`|l;`uw9yA{3iBJ|o=jF|J@7@*i>G=o;UoTL! zaID8aHYa}|=ew0m+p#4U-UnO6DfP5V8{AgM6iQ3<}#!>!u(S2O0eg1b#kBIM2?d=>hK;Bij z+=2f;Ub<|UP@l>>!02CO(@l|nP=2AWtv>|+)&5`e#!{g@>iDNQ=WYrxmETdgbBsL< zdxHZ4Oy$p+qs99Q9^Tj>?#F}=pkEI7nB&uBol8_k{s&*%>?f!`>;Xl5CHRvrM;P0a zL%uy6j`ERL*X+W7e4_gH=NF=f?g~*H!Sfea!%X7&LBnu=(r<%l{$^wz6wfyVugI)F z3U}wkAxg>L$L1L?ykLDCYV+}#QUq9w`N(?X#vcTj^07V~=<%Clc9t>&-Hx;LO|u|x zBmBqx8>JQ5o5B-Y5|iqn&3c9_=itFKJT@VI2k;*r`#2@hi*->^%kSXs{UP)b2zGTT zbRpg62As*KM?k)xkPst0pT?u3zNP-}!8AVMqetfn`2-`X3$VtL`2Npy^sfQ+*kyM4 z3a1D~nE=yx-5+4z^4dLHeaeSBXK}qsFoJ^z+s@bYJUniP*dFE2^$16}4Qra_wN3cS z{||o=z6Q0>VZzs}Bat0K5t`qX&vrp>Ye|$JZRfw1o>%zhZp?xuQUB_jZXOU|8V_}# zRA?R$^hM{!&alrPP5!2>iN+i5D@FV!_}uyd=V$LGk^iFSy?fBnpAPiR3WWBke}|U& z7dpWEZT>!}{Ub0?6!Q7~u;qq0-&Fsv?0?V=nbiN0eLn019|$n@XII9}8Ud#I`I?ymJIM2+O_>K@6!K|2{uP*S@4(ER zLj8d-_3xFN=cWkx)c*UCj{L+Y#v^S)z8Vw?>dfH+%;Q6WLYxn3FZ!nAc_n93v;+Oj z_svfc>Qnh6FZYGo!_mXR?~uHGH|u*5_}koEE!xw~NR4>9<=H`lKH@$_gpM9=Q@HQo z4Ow@8V%`7cXK=n>27O-AMY;_dFe6oq`^d3lE%;x{W?*I^UyHYYUdxp0B0kXg%-a8I zseQib;d|!{ei zuXLu(KL^nV*r$m57r_X}DV~r1A&eEBt1i+D&!hMIWoWiIzSQ4$_g(c8@-6S{NFSSy zzI#x}=f`WxdXb;B%#XK--+vqaar_@h4`8n+(gSL*KGWVFAM&>P>}Txz(=j2ZSAeb0 z7wH2(9|Q6ENSnBy()+$P#%AA4ia9=|o zkMLjW;`S36Mf;;qNO`!1rKc`#g+Fq9`Z;CxV$Ug4PSle25bgg1@t@YWc7T8OVX2kk zd4T$p(KSKbKWKe>50GC&ys}jo4{FaTLY*tXRDX)e(f$qY8%6p-@Be0CibyZ0d~bHB zxPB==1rERL+k?9ZF`vrY!&KgVZ>pz#`QMN~C~Knf_At%Iz9!rGi}G_mJ<%YP=i7U& z^|(EJ9PyL>9!%{){p~v;-wNLsU|JtXVPGS`ln?b9aXbiK+O$uE>G{x~^B2nhEqHg7 z?R-N0A0CPLH`YYum*GGl!1Vmg8yN%SfBqtVQ~81T?*OCzuJyY_e5Ue&Z`ka0ss9J< z?13o%nE(07!t)8rV zgE#$4#rppM=IcjZ6~~MC!D!=DalKG`!wPNu48i_|%f$Jh{%2o0A&v))uXkjNxSw0< z?+CE(58}l;!1*Z!DMCKgZ^{Jzck=qn-G2-JJz}+5vbUkMDLhRR9nVYz)eu zRsFD!A@VRT4e*0@(BAzce}Z*24(_zrirN+F-dv;MuMO9Yt8M|afPod+R*n`qk~XnX=v9p~$>;HbS0=goj9 zn?DI^Kl_7+4*XRi;t2`Qr}j&i+1jU!%*Y!)LO#L9lQ#JomA5~?(0tU}jgK|IQmAi% z{T<*#Fz^@hi5|j~G*^JB{O(zAiu(tR*ZcMB#qmNu!eiOa3sgP|4u0(G+r!lV59)34 zfr&mxW_ml|6a4iQ+Sku7a2y{8{KoSJ!jSKa^H2Tx%>DH=u{}I56At@CnBLFMe=oh? z&NW`5Jb~)JIzWHNp5LebXSUh=iBNr*OB}UtztH{lI|5Aaf9}$WO9D*o+uLXIFa+F= z`8Ik=^P^H2M%(AV3WvfDaEi^J6SW6&D+&y#f1r2bd5&+-N$=3!jxmnqBP(Y}4K zn-1`czP~NP{?C72u&^~G^+CM6w#r)hxy>vTGb zFWigIrtoQA5=ji>Ap4^;0JRq%?Y&;GU4r zw|C!mUgPPp-Tq3?H+B4`Ln!~R!qk5KwWY&^`tqy)rPf6Gv+VhY z0r}J)$NRj$37@oYuXIV#ZULtHHFo!bm@fo=c9IJD1mgl+BEZxi;CJ8vSj2Z~FVcJc z4I!W3p9b6qfp%k%C1BUm$YK>1@1@WtOd(wp;kFg+jk23bOXXg+XW|4|s`!eT!4 z@19+I1fO?zd>-hVLw|Na!AE!=J-@}Sz5lo2a6BmUhu_@bcmH{e$JdaMW*y;sUu`({ zZ}{g3tXxg{^pvJ)pf6w_uZ+fLV#WwEjcw*5g2!%135BDVFEq z%o|-Y>l<*KOyz;^nRsvdPH&cI{f0sQ){yOD9GE;*?Y(ji%W$S-pev0GoB7}$W zBgT6Wb`l^l`~R3PT2-cSA`YQN()mSF;QacgZ0imB=K_}8Z|%mQoqud0l1WWn@29nas=Gf1CL zr>o_&E7qa?0D>!cSb_)>c|05!&;Ri6KLbV}XgfdtIttK&^zp|Z=ST44E437?d}WUX z)?46rvX)rjG7CII((1{Vx1EnH8UOzKzcT`FkcK{*UTkST1pk#1>|%lcXMa_HFmQKy ze6Dt3s`=LlgYyZWPZ$j0(=GU17LcO4VKD5kCS*oB8&#@usqL~_%OajnM7URXPFymd z$({VT4F3P~JamgJa2xP5!Yfv{i08-Yx3Yk6FS%~fykX2F<4~#&^dMw!OlVkM-+{i+J{KB*7zyzVzrpKqPF}``r5$da_n;8QG2OwS0cP z$N$ep`v0@qT39d9`p~i%Rqt0g-w=IncH6Z78+uu!vr%OM(*0EjEfD!jUAnMh*)oFw zM=tzz{#(kxKmop=Aq&>u>Pd$4Gd`V-%GKQK&f$K>EF(_-QIZx{7%Xon$AA3#w4>*i zoPYoQ*G53qtF0!3H(HNwQ?@sAzqO2i8SGt8$unakC6*bX{cfYa0{iPa{tR43`(uxi zU(bi0x9*Q|_psiX>WOVPP5l1ZJT$Ni@-IqD7p4Xr*ecy>X`Npmnssa8PsW<+Jj()H zl!Co2$Ax>9dj3T2Ve`l5l^XDvlM4t^4y}l)I`n5jLTZt!hTd1gsQ*(C;DzIUfJPqc zqI`v~izu%r`k=c!cQeNsw?hMeD&<%7ton@OUg3FUf{QpE_c`jBrPJH6tLbVFUB-@-4%#{J9h^M88<(!Rw?NTKtMhexv+_(~q~eKmOj8jVKuKc6~5>h&Mr zJ@{Cd55iada@}$PmIbG3I{U+ZBEV$<(DST;i(FU<;IvT}>qgzkuOr7M^Fp z_rVAOU`f>fFBUvMmvEmx6hVjVS(#bI#lRk(p1eYEXII_U$75pyyAIi1kQ@ z?T>Skq@C~of-9kx=%VyK@IP3ENE_C4XF{1KYxtCNoYC@wEVxW_-uQ48#olniJ5YU! zPfp8OwdX6YoIk%DEMv`Zd5qNRiTlgE{T0Id`v#P%icBYaXA?E8h5z#aHA`Hq45a!J z3?tD$oQJ5rAdJX14Z-Mt58oQmuUyrw?zJ1OAm&uGDyN!XhV_B{tdn4lJ5V_yAbfDat{X-k6h&ZQ1)tAxx-5c~71Cy^&vEZwTe?u= zh4nbY0#p31a4$byvb8+%3#}6hA+`6($M;6C3Z;hv`Q z(C0Zt{)an2KWV%q5}DP1h~+#n`SsZmI6e4qE+$DGo?r19`~&uG(AK_K!nXWw@D!{%I>O6O?zM{*aw{hkkCwdY;Z`E?>KW@V(o!$@o!*-#3 zuGeYL1xX9|cti9X;roWrpU8xKg3r`7`Uo)X2Zqrufp~v8(L7pT`t+m)GV4NIjQt1k zUx^n}v9GH^uRw2_<=s%wSk?`QZ|NOT{fEQGvHr*G_u*{#eE!eaFw_6=hapixS z`Ta*%EhB%J{h0JJ(1-F;e*bu%PVJ>C?5{dMoyk10;{Xa!ens^kM0$`0dXVSWnIr6f zL=SZFdOu~Hxv0K z;!pnahd+!H%F}+QP)ek=IQ!FYbpeanPV7!X{#O&E!FzWe*eSr&zCLEhD9*=Cqd<6d_A8tQ;LGVl0HKZ{2i65>NI(I5n7>sX@D$v z$pSBtJ}jAB7BH%Q#J9*da>?8WccW4@IXS;+pp!dWnPDdS5~o8j_R^JRGYUoE{X(`H z9tiE7_PjGe3VDv3e^`FJJ9hew=-a2wnIb>gI=W8P z``yPTZya`wJ2@u_<})@vCY`$T@AUuk2&h;S;TPonA#C>>VbPdBqW`N$FvW11Or-lO zQXt=>^SLF_e!N;9E3UUZ!14vVtnwyP0?rQsdk~>X=$tdDT7=|~p% z{rj|j>a6?@{dP7g+$(w29Y3FKwdDlwj!02lF+!Ecnn)h`!rgP{OqV|}0%qS4}QDtPPqYU(ZLA85VnpL%kXke`SA{T0haeCN-%E_sWAeYI4E!);pf8q0^1}e7 zB_1+?f8Hm)zW!r@yzo@ww14@um9!;YyOI6^9(2MGWD4kU=} zwNAJjwZCF-prtk9UzSYP>9&jeU72Ru!mhrM@!i|V@fNB0a6hvATH zPbCh?RT&Hs#!??GwWY@}5{(J@CqZR1gvShmjS9A%7>NWQ3?tTeOU}t?YENVsLxiCO z`=I7=bQF(ilHUA}>2bu9L3WChO-{0P|){vynbMHO>K3skr<~M8Y*V^m% zek^^^YsGqwfe7$&7w~;Wjr}JyE6Ai+PzN0j^W|1R;Va|Gz61EEI4b-YHv6AZet&#| z|8V$xA*Zp+u;cE1E_VMUKl_dxbgI}Nlbfv2XX*9<*jI^PFE2NaKit}XjolADZ>qj@ z#y3XQEexBH-}l>49LMJi!2f|bf!}`=?0xyoX?KK=4_v_fy5#^QIXMvg#)HuRlOU#V zlmZENDvwZ($kl3#q93S_6#hiPf^b9qaC~!xBlJjKio*l%-J^DQfuFPh9+hW(K>985 z|Ktowih`}9MwnSh+CY9@!K3VKH~;>>m;v*mpdggt4LAx=WY-tKOC{J*K=ZG*^4ZBo zX~3thcdJjc3H+8w&>N>Px8RL#Poq$8}(5GzcS_xU!e0Vjo{%Q9T@{R z89gm9T!kC<{}KiF5v=!fXJdtqAN1k*gcb{s=ZY-;H0C5dui(v?!^Uf8L*mZO=jW@_ zH5pd!eT?Cc++;w#<~48{lW+o%<4HsWh= z--Kv!OSxIlYHM-7=R(3KONMVjy=UtaGz=iHHzU5KX1f0Kwj4z+bp`R8V+oA>k{~IShY6v0no|8U6u(lIW|MK8US{>E9X^{#~C* zL>6wgKV=3)^oY4);Drx7O3*4p5#&Aixed$ zPK7)Qg6@YtxQOhzTD*U2#?&7107SN+JgIM%S=Z_Gl@PCa<*G2=V@!f6O7BPC9Yr{l z@I%{EASHzFm)1kreiZ&?%pXxW;#Ag?lp;KB-uEKTN%X^&t`}&*QX~Id(*3#7Dnu~+ zg$Xg)95xygqs(gvIXdKV59jvbc$2s0+^h1B=)Yl6v1@7-XlqY(dSXUbP)EmrQ*e>Hn7XX9=X&o zD1U?7x02y+!(>cn1IYg-`MjLJl6~Idb@Z_QCQ+NXvN@K=N07aQZiMyIc>TaYn0^F8 z=VuucGo=mOzISnFpEx5WCB3657>{E@7k4HbPp@zJ5j*};WmlRF@lj{P1SZ*N0a4` zBzgq<&rN@UIwg_M2B0DA!DK&mpnf*=RlO|8LQAC}Y*@H(O#FT1kWYPU5PF5{VyF!3 z-Ib9-YpKH*orvmxV1FKa-abu~GMw`1F zoyJj1PRz!qdbgm4I498``;?iFh*yOTPCVa3Z^eMTsZHfqc?#~c-;}>ZOAiV?$`vr7 z4~G|ca+*i{%_}2e?#<{C>JNk)=?4Y*yE(qhhy49gOKmOdcS3(Z^W@n=1^!`?ne68y zLF6Zdn<5`PrZ>s~-vnDX_;2D;U6!TuO2V*eVVIhkq&svaqMyHB?=+^QXJz;n!Nz`0 zmOrRB>dq9jw2-fO1m|2>q@68XmoKvOuUe1zAD?7n@J{R>pcB3y#y(kIRS$~jP~$&%Fxel{q*l@omA1U-n8DKH z4O0j5|G<7|v2*&740_k!+IfbJCx3$H);^<8fIOm?vuHgOuC93VQ}5~~z$Gf;|7Oew z1be0ZLH-*aLw|A`#YXx|^SiBt3vl$)pV&l=xm&nS{8HMG-bn|&`ffNrMoMxD!mI-% zGtl{^!w#>wL}f_XiDund@3o#iYt=X2hup5wGbN9$4a2=jOLc7>=dLoC?AzXahocp- zP1O2kWhXuw=aRlKD@do8m+Jb`z#uBVfLhj#LdSTzA6st08Pv<{72M;^q%UVABm^iP zS+BV@FPZMglK->MJ>-X|E;cA!C;pSs8Obm1GxQq1yJDwmynsYR_6Q-n0rU&aw=Qbx zY=^E*FIWj2g-%|deazr=Y?QFK;70n5^dI}MY?(QHmO$tS^MqX=1@BP+d_*CBTB>sG zU@++yQhbY#!JB|D9&h0}1|Puwp&6ZrdH*kVzLw*@QLNY-Y0R?Bg+;Bg%^Y7zA8^dD za(wGGH0NmHpr^6a`ewl&xqi^ELiE0{g5n?_QnSecPgj z*2;En&n0`OKd&lAvA<$}#5%y=Y;JC*_jL#UsrrQGbFc5_5C5|>Al^pNHN8dIz^Bc- zG{@=fo`Zf*_8hYC098T>gJMYK-v?BC0LJt}KFmE^1x}~5U^eW*m9ZINesgv&tv{*X zSpiwFgCZWl4-N90EgyT|nZF+vW0Jqc+x*(N@bS!lFkTLGXo??L;ShwYKU7D|OFTa- zMOq=)8iM12lamoItUF!se^-nX9tMdx8Iz6s%`aZ^I6QeHVkkF2_@B~@{kdd$Uxy}I ziD%Qg3oX8hiiBaN@pd)-UT4k;(7Z!Iyt|FyN&&6WazYO+En}fUW(^4L4_DOnsAa>@ScY**zw837)o z-xcI8R_P6{K=}T`EALduKD_@%*)}h69^@4ViFis&!sqYRFV!J8KmP}2;9h=>Cc`cK zd*n-l{No441JvUE$DX%HNA@zB-6Rq*w@gsSl0C?b^}%`tz>5G2E_SY^x?FsHCq#Qu zsQUTz0g^}eTqb>@3-rf*S(Q&iSYbw7PtdnzZKOJ{9 zK);m1yYMZO>BWQkkp~DS`QfR4QybZ;NM{ANhrHnK5wENM5ru!B-=0cisqLS&RA-U8 zQ&fofZ=L9GkHk+?JZ(!wA=lsQv$ct(wBKmBQM{JaoiUfe^_d!DXOw)Cqq5@=Rl*wJ*iWu1EC4CFR@Y zVPGs8`3vmM+k7N%HEQIC50qp6Bx@h8f73pu5Gma6i;W zWzv_t%}+}56vP+9r$Y3jE4ys9a{SGYn=|nHW`Kx<(o}uuD}Dwy8OrcgmODe(@>tP# zOw(PUAJkriyjv$8c(FyN!bA_6#I#>7=J6^`h9+_T|22OB>!)}~+nhM=KP=DGB(|ES zPiOrke>>bQD;Rtsrw8oIUCQ9{Z0t8~?cTuPKAb=9OC<*w+zffY))mjse ziVHavUcFxJ-`KpoXkMb%YYO9Q2>#Kfi5q)>U*Sgn{!zJxU!US7U%CA9v#ei>d;&|2 zSKDzXxbdxWE%{H*t*h>l!Jgw}VT9x(?T&UXFOdJnsoTuyCFyeoJ>D>XhrI#wo3QrO zVs?HH@>jeopKsx?G1(ZES5*RCjoRE~qeFKTE@-Cx6FopUDD)3%v%VaTA1+j33PB-> z5`xanc5?>)zzo=t9h8e5hiUz8Yi}ogUup9a>kL`9eg8azDIV`V+V4EvrWKhJM}0n3 zKfc;AUcV+u(8B>r@ix!Fg3wZaOw1efhvf^3$8QqzEfIX*Qq&84-|k@NBm3OYP_;rY z=r>QI)6mk=My7nFfxjjutR()cwiAfok)KO>gTaW$HF{q>z~ytw_nW6FSwnmMOcH|8 z6n|*36x*K;sTw)OAHsZ!^M&9s6+`uzW;jsO`SB-mn$l{#5&ebqkB-6WqwIQQUqSr^ z_k(e=Vv(IXd!8=8RI3OOG$zVaC)RGT_ObCKKTTM&U;>9lr$IEPCNnr0^dWxpiGc{* z1q}azPmZd^4lq$d8Qzb*0Q(P^;hp(J7)gdc;7>@2fvBLga^$m3@09#kRSyg7smH`p zM6d_~GO9A=Ao;hY;y%5)6#ff;h=Sat59)mU{eFtFun#-l4U)YeJ|>2*f3iO>S@T{A zTaOffH)F6bAP`K5w3R=t`^zx08P%p58wBYw%c{?Z`pSNxqX zH1PO`WawWh6X#6A>^9<_1AR$#y1u@ls$?LutteEBzh;bNyw)fiO$svum;1QZU5EUEGmsh6aPRwMD^#-b@P|M=o$D>aD%5P@dO!AaMkr0f`2YK z(bQ7cq`)?L9KC;&slSGDf2bV?Tukv0!)C+>eWJvxY~F?a#>0)`Y5bk(+ZepFzAvq> z+RE*J^I;#nXbo_DI6kXReB?OW{hO8pkuVjUvgn;OOM@7lIOqH|Lt`jJ!| zkvC%37!y71Y<_6{pU9DB`a$uLXG*tn z`a$-Txt-mNo#XsnhR!*oqnepfnK79!D*FHFJ$LKa`6=JsvtmU9gPlf?;o_5PTWgVf z{m4A|I@uEo@`muH%2FFoNgZt5fcLpzdw8S8ne?&4(_8^Vq9{dYb1pWMC(c^k(^>Hp1u5l`%CBlJ-k=?5-I-wgI3zStta zcQ1oUpQ;)-$@`nY|82e*5&Z%FMcb+OP_J2SG+#ac_M3lZuoLpce^fs44&Y007by1I zRg6FLi>yFG&am z=zO3TwGNCSjP8gkBUs9>#@?$+BmD#JPdtC8jQuFoeEO(P3E3ZdIsRvZBpP~#woiP$^3$8Z&3+p?R}eKAJNhCP5#j%V7u+-PRcXkt zGsMU4Xahgc%TbNOmLjuIZM9m=uq|AHg&`U1CwSi$MBcmfjH4G+vzA@-`G5Gu}L~KW!|DD04FVviy7RBHbIZk7| zc1Y0$=r>Q=>*d~x0?uC_Lw>JZjQoZSR3Gpm9s%(ZMYaL`y>QxUmYlnj_}i#-NdF>` zkBAQ9e+~WtFX!h}??9F<+@1k@so0*L#;;HEg}?i~YjkaDR8L{w^e=IGK>1Xa{SkTv zd!jCMc+OqwXw^US-ZO(G4BmwNMrqHXx0pOb`v0|0Mx#!J2h<6DX)C;ud=0X1UVd5l zHani~$9McyKHo&YyxUzX)a-ENW6ahhR(QQDsMaL>BZ|GqQgBq!2dFGxR}#?+t> zS31Stoxl%&E$jaO@O{n;+iSz}(jJSNUm{^vscG>45889#7it1Vk|d9i<2C&XeoE4S zqMt>>f<*6w>IYE&?`}!-Ge)tNq+B+Xa6R*rU=P+O_EyOBdlIZ8w?THCxYvE!G+AC?lot&73L@4x7i{bS*wnu@S= z4*UbZZ0Uc8*lk@NT>|U`9A%CoYc~Mtd)s+q| zPf&bvZO06b@0Tj?&6NCJ-mky`FYn({eoTCF;}F*`NFMUX_8pexfW8r06kUJQIsd3~ zy%*)lb-J!J=}}c58j;N*lJ}9t*3RyS@0;` z-}=4pe}5lhN0@)O`lvcbQK(l#@gT6blX#;(=GK-<&-F1)lYB=$|Il0yEM2<)}-QZe3j4yEX-u-$DI|zBHj@U_gZ_|KLQ<_Yc{^ z{j&Tiwwt2Ahv$W0zs?+}jq)S*U1JD>r82{Z-^RNSih* z|3M#-zGPzAstQY#F4C5^pnXhNyzb-K7)#si0%{haU1#UHW$RqM+$k?vW`VDdlo*hqhs9{@e}oybW-aW#uKSoDPfp~13->OVLJ zsatJiuLQxZW$=ldQXR}=d_Z~TQk{F)&tOm=@H;Q;XR*+>=ZfLhwVHbfapo`*3#S#C7+{FcntVoSWr-`xh{N&?5u+xoDce00z01S zv%NKr*JqQ-9_KYB4>t-y#1P5Rq>rP#kezHh@b?T*wWiixoB8@8d4KT=Z!6ZO((1&h z*(PsmtB@OmHyqRuJxIt$Jr?+P2IQ02U*4$u&CpM|JeUo6?9#Ef#>ztj2=ea7vIFv0Ypa**v&o>3Ta4qM zBj1k7&$Y9Y8h|f$!L2!{IZ6B({Bk0jzgr$`5(hfxa(NT<#PH;rJifmB42UnA5ub#$ z`v{#6@}I7x);C?Pm#WsE^tU>3KWc+KYN)aI^e~v@g9-cIPqN|NBA-4;{M%~nbE>an zjv^00eJj9Pd7GY)?k~lK^*%+e9qD)*_93IL;>}-f8;%}`J74=##a_6&VrrZl+=b}h zGVuGy3s3vm^|llp6)&Cdb2FIm*WclRys0*-H?%he_4vX~YeGE{?~9y%k$itGbcoXj zs@L1xKD~pDr~IGWrp@L2r>*`{rBtWOW&OloLoLG_8BG2???oHGKFQ~Li_{{Q|3x;4r$+eyY^q-07_R3!mGHN?*b3VW{X0nX#FY@if8yWL=56!T zqY+OCdU~*M{X1A6@vvaT90&g{t5{b9`rH71qsS8pE=I3L6nVr_T)d7QZ^ZfT-dp$p zo~M?6L;XlySr_ufvP2?W3lD480GSKkRPUyMQxF{# zs%N>@zxJC9j+&iqylq^3eAK?^edG4&_dQmGdiDMY{-a)$F;1)RX5-5L4`nd; zKB_-zv2yvMKHHesKG4qXOR%RKwQ+|)uhmBJ_c19hPLC*F1l7K2#VU>T;|p0YUkPqd zVangLgj&eAtC6j8@Lt&eEF1eBc6z>$X4r%0-L)%b^HZw03H@WKuC~B(HhenN*GK$+ zLL{F54dmlr9ID~?O!(t|#n;KklYP%yR=}T^{7e2TA46VM8{yxmD@8{HKZ`-yD=M`Jw3I zM3SfDQ}&!zk4L;e`6r~p6)H^hylXu!K8^L-z4usJR}T-!)P_JpS{1oICvx%Gd8U3pOF&3$)j>#dcUQD zxy9=hKGyWa9c;AE&jvp4nb^qYkKWHf?GVRT!oSwEwAlE4wfkrc;%Rgf@41Ie>q^7> zGivlKepQz5L;PxI`q?VhPx#UDrD=W!Q$2I7R?qc+(tjQLL5>gQv*#JJPIvp*_?`9h zGo`B09Sq)5gnH?+p095uet)~f*Z0o)=d&ftZ>No$te<@AKovo2z82ka>iT7_uJQ84Syq{f<=G(flS~afV zN5tcd?@DsM&ia-5ZS$y-c^uYzoRWW(pP!fiW7^%pjOwX~KZ?yz<%wZ=<^igo2>G}Z z(pfa_IE(rVs!PebG86I`B(g5i>`>_!$$Z5B>*mel@4pZH##&sk5%gy=WVS5gx8|IR z{?FmlD5447f>@tT`h+X|enN8Ws^ha(Uv3{@_m9~%4xT3YUu{vDPUGNnfejNwwFC0= z@?4V7HvjCO%T(zn1bWbAkV z;?-rJyTAX_Kg<*k&dhRmko@IOh{Si3J-EOsRk8W-z&>5L;kDgz*g{$c{b$dlNIeIF zE6$$g`W@-BX|b_$*zqT_ooOF@``Z^8O!0SXC(XK-!5*VF@!orn-^O4k@MllT4kv?& zpL#o{ALej2Jnj>=CNr4Uryu*HFqrCB#&`193x2b(C^lBBXZ_^=)|z&!>j4_e&U%kA z9rZ}#X2wD$h!QRUe_-DSeIa`i=sVtDlG_;zX>kfLMzI%kmG-wB1aEm7C#C*vyWTLd z^EZjf#@q=pF$}JU{oS9jnafLbzaH-l{yeDPrR%RL=JEjQcL|#P7kk+8mnzTCI<%|z z4F(f`FLgxtb4&dTIqR!yF0g*e4@ldtPf2_`sN@_i%Pd8`9c_s9FM~c0^+ziQ)L-DA z11t@RAx-Ge)!^ev(2(2>yKh%|gTX{6{2}R*e}gOsLgW5Ljh}INRRaQ|sgR#d3%R9P z4__H9F3r7|kG%Z+_vlxRaQIQOj!P0%cA=)hf7M?8#5v({`i1m&${*4|dsbVYfwbpQ z#hN!0^^s8B$kWux?tc^cM>fJv;;1_!iF9Yra`{lSU%*P6Q>6rU!0*#}2`2W4xg#l5SKh*z>U$a1cLh`R{ zh`KhEd+QxBzBzKxV>Ig)yi?=-HDn!HL}ieF_cxQjL~XE#8zz?2;{B_Q^dalxPb`J0 z?CRvJQ?4fC`CAU|p1}I=gMYcbGi4fsiQiTC6!7&;{7{w$=CXdw|9E+{gxl{ZU&UgP zZm@pfOB(#kgA6A9(c9e2ND3Kemppt26P#dgmYioj(R(4Bm3`@>jGI}5b`AD%OB?Uiv#7y#HwKA`8-&knD2Hr zp5j-xBH@|Az(?Ko9fe$edA-~W{&bAn^V(p~@9s|7#l}}celIQ<9AGffvln)6X?z`?W~FWBtJ7Vq zr14b!%bF>FgRnu$D^Pz@eh+{AnxqHN8>~l&56b*qkVR>p&S>;L|Ga>B98FNOSFzvJ zR4gtGANBkH;5uX51M<_Tb_e++M-Z|G==@i&j0a5l&29)tfaUQ^)O>U|3?^}6mhb}< zy&`hhoFyFf?^fnL#kV72fui#QQ2u!3wnYG?Ei@-3I}1`0loDn zP;EOhB6X!D2^y`y`6=q{q^0aFKhNcNqECAN*=lw^;Hx-ubnXCyY5i+sr!_K|^5?4;-=D%@ zC-w_Sx%&q{U@-ZcV)c-x!%aK8&oCpd<1mAX9%)VS=NVj|4STSoh0oVA*ej$Or=Io0 zULjUV7Eb?OuPoI`u7qaRZ%n*Y8LQFq=ebllHS^fV)f-uV%!740M&$XGA_C3hhdklYH1`D0U@rC$(g+pJ#?vLzCDTNMx{4&Is`Aw)$ z6>cQ&$G8ePev*7Jb=S04SbsC|Sj(D7 zKX1V9;NTaraDb@i8>IS6kk|zcJ3q-Uk~Cqn9JT<|Lq9B;5qV!EkK7Wgce3%occaPV z=J#`f>U1?L;3}Ai@JTe4UbJbyVn#SM+6B}zmo97F6R|^g2>#1**as}O z>R}bzuH#RVa(^oPcmAo=2Ug?bC>kw*`GY-Hm$t*>rn#g>{-(fS@armscyi!ZM*3kN zgVRyd$THycm>s+NlzZ44&;xy9Sh=?}#pq`JMGte@yX zp(C|`!9)+dt^$t#gzr+o%jFU9W8;;MNIWay%iV?wHa>I#_P32SPn=}uL%rQu>9JA# z{waRVA*2qn{#B@t39s)!1jhZlTR491tiLyV#`K8&eP{hKv0zMnAkwF>ec5)3hHAY2 z$n|>o3rZ1hBsN9&K8&l7J;0SPjq3xnAJ4vtbGZCM^@6I=*!9U@UU%CsZeuXbkD=7z zP!$dqL$7d%x`ehq~r-GmKU<2wf9PERAUH_92 zmtN?&F5eOIQG9;h;vBt-`NUmSdi8fg31)_$m$IdUO%S3;9!p4x_;31PUr);^Gbr;H zkSCFS$E4p;7PQ@?SRH755)$0VTRVdPwEj*dq`Va#KNT| z^8Ctvr8I{8b;Z>_CJzTFU!!pS5zvRm(b!(i$8`Sb`mc5>=fel-n=^a^Y(4z&G1yzQ z2smZ^b>i}(X1qMWs0Jwqk|7@s+59Ckj5}`Fm&kwLF(aPyqbTghQ&McRvEwPeHQgUs zZ_D7H!-k3(k$(7(7aO)RSg{Wr9%lHC`j})-C=g&x4$+uj)bCGw_gELBe@eb=K_t8t z{$#|nUS|6xQaszT%&J-^r)R+TOiA+T+4T?)H_LB>JRNS2EioE%ZMDj^@Kb>5ZypVn zSSC-3o%Dr}Fxq8E5`O){g#k7m&!gM5Eu622AD1RBFUrj=C}92I|AhZso*;gme0xFy z>nGTla67lpAU@dO(5LuWKgG*WuU>We&hQT;pADq7viA#lFlzSh-@L-X)7$^9CNIy8SK8pK&i2FkX|I6}5E!L}Kso9~@gVw)p zvT(|No2t)e#G1ns=rIbqbkEgb7({$Zo%r+T#;N*hK(=9jC!^#ac+jVz-78i5MK16i zo)x7cIN2xsET1hXKXM-WfA6buZ7}L#SievYU z?8)*t%9l?PEjz}eSCdje;*yfR|)@> zR*HH!0`v=pAS6$Y%Bj5nL{6_k>QLx~GG5uw>eWbmE%EP8sH@@ADe)$Ht=`4pI+72( zCW*lZ>Omh+aDu@f?2MY7TfDxT!LMQcM`idqe$o0jN%^I$|Co4R5yHv%^8kO+rc1U_ z)=&J|q1czyUzB)SXw$L}{{j?G*(4U*j|z~+KNcFIggQvK%?Ylszi2rT zp5o`He-PPk6!H9~tQnuSH$GBX^d1{e^4jP(Oil)qe`0qmw0Q*I? zmH3a^x=>H`tZSfz!N-9A_f$!I{)zu@e|b+Q>rX~}<~Y+ft`C;#l40N3&EvPJ{y^@E zy(?i)_yKH$ivKOoURL(kMhLEhI}hU@)wf*v=*pG+`GM~rjN7H+JAPbVq%pcu-Y7&o zlg0)g3hl>iw|+i9NesDRpB+{5UE0&)oWNea923w`ywi&68r$dQ$Bk&rPgMW0D zUH?K(vavfOW~I#@cl~Aj*qmJ|y!UhY2=MhPMetI-xzw2+@oxiP+|8|=ACmlt_#zWK zAK?S)&G7X?{woOx`l`OhYe;NFR$$4AL zJ_W-8#^7Jb*#PhAK zt?YOj|I9NHd&7C`A5xGPiJv6<%X51&IDYh{^&0Xb=|Rx%y9{T~`uTiO{!vAcjhyCNF(2;@g{rRXKnO=+f&Pww_ z{#&Z+!Fmk$%e}zoK>vsT!Qdvq{;K?Q90tG7uPUly@G(QFuBGB6_b*ev#>(fPZD9R< z&_}l&I>pxm!Pe^jElM;5VgyNFSW#+Lte~G;+dAr&b{s4 z#j;N(kF<2Is;$rlRDUa^WdYk=eJ-C7 zwX&7^7bGuiPSY^{=A(GL+#6Tc(fmI=U!&AhaJX+M`WjKJOwVZjvPA4)eR@DDkFzEx-8%PpP zeNtPi_6J?hLmz8v-4#B62>crM4sS#r>Vto>*m^FYjwg8n{Bhhg?td*e&P%*9|Lj`U zpPlG|#2n4zk>D*IFWeRwlnxR%2gpda*{L*!(*(f#c9 z>K#~*N+Wx#N$-}t;eOihz#=<1eI)sPFys)z-HQAJdn$)1zd#y{$ZrZP_4EER;KRU$ zLo?a&gzudnJSUEY&pvsk9JpbV8_B<2CqD2Kq(<$eMi6?=x6_J6$6pNRdt zN_8Dme-eIQ!?52p8Q%LX7R!j2NY6^WZq#daG1nT2|j$Y92 zG=%?zZ}~4gwt~Hng+(WF66Q|g{O=9Ks}4xFad`srOQPFd%GVRw`>H{Y*zrW~_okUv zGB_LdX?Ux%7`#b|XX)hp=t7P|r2WQNKgk2Rxe|wev9mrqA!ETqte@`J2!b;f-o2RH z1EK#mrB(E|xG|lrn8RDiz7aQLsG7!K!9VyLM5jKE)2mg`|KrkKVSJTk#20}cxE<{L zO(JEyMCgHjjaw8pUD^=t4{Bkrd2ek=NiC}T>9PMFt%ltEV)HCz#z~`V5?A1J&nxsc z)M{3=|7J)&|LV`gweVk?g{>m{ANr$k)DA%{+$g>|BV&3TgQ>o! zBqgLXSg{w5oaXQ2edrrKafb$2|9y~;MlIHD45s|RYiBob{Q&$c@qTC0{j8tx(WH;q zPiViF7xk}Jv3?``uTp*l|NCaqirpU3cTK$pcYiIv{#1%bS^VJ%c0A>yta&#*nZdot zC;7&V8$B_Qtq`FfP4CZAT)0k|yuYLmV7}Ut1T*_O=CApO49GSXu&-n6|7Q6-h(e=< z{3o61FxT9vS^zE}zF|G|Eq2If{&IRFgzSj_97TQw?f0?Y-GK=04&mzp`C|(Tatl9m z+~+`&RtF%nkiT&IjU6g(!SaN>+;JfWIYmnT88+xc|GExAFJ@ysCOLBR zWO?l#p|e}A$Ni%A3f7~#Mh}!Do<<&j@GbS5FXIThiN}CvUd&d$bom>s*#@~`I^*l0S0?29OmC&{)@+~Ab+ zQ(mt$3%1%R`1Q|gAId$!gAco-DZV?Z^;_Uys(wN7#e9FknZkv;!Tk`Ql%YA*q+od3 z8u*9W^|aoUb|L4D%9cVe=T~o3mS;MKfqm41!HLU@gz@g-kOE&t#3I?NYo$&uPY^$M zxGRXhD2?=2|8rR#41TTth3q_QmAZa>a*&Q6uBoU#D{GKFsyRvV|BhxqUtc88O8JFY z|7xT2^`GCy{bO4Y-(JDfWf3s?#oEw`K(I4j6HiBQu3p?L3*sov^;Qb`8TWij5i$G%Z`-WvhuHbQyQDWMp*x@Rv-Wyz+ID9|KT!02y{VavUk?6%w6v7_ z8)J3hO`rb&s)e@Eo@%c+xyZabC38@k*RX89=08T!R6Vi3*!ZAkQ$Ns4=e{+Z_1Y|$l1A@tpCw=jsU#MtM z;1W58@)^9XwBO@8?2j(EiC;SP(_E@FpzJqih`TKd{zIy#UMm=J{}2aqKf9D~!hhZj zi2EAw{)gq3@+2W9eiN7f9s|A)ZQOW-y)W{QqkcB$_Y_|Tc392&f3zO^*X>E?`YFlN zv5snPzuH;foKr3PB{p91KfMsCPw>VQ3yUO671pEL$R41*J&*Hq!e6C6Q9$jlT#x;W zQIG1@@OZMP#oRJ&LKsx!`-FL)mhXGIHf>}5h!+$Kt0{j}ZLzD6FW4Emzg5ULbU3GT zdh$jk_RlEJJ0JKZ(Id`(r=q^8YfQZ( z!vDaA->UKo4x)G?yH0pr^4|hbqnX(T@h3jZpCL%@%uG)AF>npe>5x7nGZF&Xq1hMku zQ0?^~{etpS`Kq+5;ft846C(IWsJ}VCAlE0rx|1bO=>q;Qt_HllUXRY6=;qpG!i_+_NY-H~j_Vn5B%!%jtMEHIb4rA6&@q3P*AwHhy zg@2$ujrG4#xx7gFvdHn3_^sa5%Fh?O>X_JnEfPON{>tec?Hqqr)nCZ5f7HkIWs>ht zzgNKdGvwE_sHmt<>G#{Pz}fZbertw?IR3nk_&CJNbNWN^uyMcq&IUG~@Ha>5=J*a+ z*l*ggBhv5DeR0Rv_A^+Ec+AVc{OB5ko06JzSB^yXFDLr(V|}WT^~3v#dI%XYv9N#D zBHy0;z4`gY1+=rgAdFvX}tv+MEFO|9TK2e;M#AA;JF%J0HbQ zm=Ms(;AEqAb~6$_Yd=BGcrWQw8DseQGVuR_adWsmnEbyjEh|>A z^O65I$*Je>lkAT+Yjqjx*Mc5QSX{=}53D^p@21$4SkwZ8eecyi&^J8ZwT}@!HWM*J zWIb$Z-Sxd##5)F~) z7oz;E;nUq*zx)y6^{^bZ?0om3zJ;f(jQfiTA8(wqa(dDK1o*ApuE_Uv%ofDox2H$s zdm6tfDmIp#Zz1yiN=uJA7)irD5dv z3`3uwc>UIG5&t0B4+@I%_R2NjpFY^vP=BvE;n9_?!!u85%yakB^|Cr6_C3PqfBM%U zPQS>$Qn>D(;za+o$va!O_F{2kdM01$ocxX6Gk+Vp?mYUXf{y(O6cN-Ou`C z*8u;=$fu+a>)n^~Q_M3GKCVByK(ad0?H`^=nBl^Vp=X1dRIeobg^rM*S zZHF7#qhpVZaC{~FAFg=M#`;MfNKM}S9E0n`F2iQIrV)S+)hZK@(*Xh4him780-Reie;;*UZB{`iW=-rnjl*ss}4J{n|k zqXq)i@7}g0!_Tfy`1Z^>)HB#i8pgO$(lw55eOc z>iOvyrC!jINPQThH|1Nu$k$I7@;xv3ZZLX+?5`xD?MFYVR-kdUU70;S!<^s6uIiel-~J$nFIFZSEl40?=L5d2Us0IP zd=u!)_Sps$>?O`mGfk{|CtpR*Us;^N!+;}!l;5DG=}Ps@VxuUXSNI~ff8uTMybiDoeI=aez^tCtsL zr>#I^{m6F~=XQ3w7)Er~5`~KY!kG?2j`xUWxqoNgkcZ zjwgIDIV1LhWca5}7Uy&Mo%S2ZXumwHjDJ;LK={()edQcGp6IFj*}a_JQN6WmH_mk{ z)ukrMwNs6#pJiX&4{iG-vNdu^e@$_P{g(lGGR1GCnzmO=##$%rrheMLfNEm_oJs3H z0S*PdFxlL?=N(NGe$CJGE;d`9($IM7*C60ap$ve3Upt9(G3q1zp^p4V*n1HbeNQU) z0|Nkio;uV=DlDwXCU{>cZh+|5@X!q|uYteo^n-)H4UaE8KMVOM{tLBX81d_U2A^Mc zFqrDi&DbF2GnndyC2zi+KN41~QJgD(WS23HYsT8F5hj@@(Z2s>>ym^!9A3Y4F_v7zu<@BEH zcfJ{|msvmA(}b45Km>Lg-Cmi?3zsTAz+gi`$*E3`?|^k0WKOd46aL4ixcKuBd?;o5LG*W!PMV!or+choMN$6~^FPs# zzNpx%j$~{h(}~i^9+3Rho<|u>_Py`EI3-UGsK1bW@V`a|d_r!NeVkn{D)>Bc!l^*i zWF-QF)^{!hTxA76^YldOM}4Au#kEz@%4$b{3ikh!s2GmY5I;Z|@aH$mFjKCNd@5z!Lwv&{ZlVv*+X^v<(;SwHAs(m(j*+A!=R{U>?r=7j`57t-|7dKBC*y2AaK-(!aRzx3?tJI@Cv z$TqcJ+wO{sZpwe{MF}sWx8CXL(?AWzp~hc;>;Z+PZ)zitpG033+7|ik<~QNXY!09g z;_H{g_9I$fRNy7A3KYXF^pgHJf`YjrY~M-^*lDae(l%WIWckSQC5XrOwT1cpqj}_S z@9!B3WfBS!Z?Km4x_~-U+ zu3F$+p1hcsIU!w0&?pKO=yf`PkgqPxZ#^}3d z_7>@H?z?v)pOomsTs?dP^R9+u`54ZpQjgYG>6(NeQpa?YfDN&Nu+k{7#et*k>qy8` z;8Cv|)$%?$@>3Px2K?__WcRDcN72sI4)@h2%ayoc8h^(RZ{zt2Bp>|<1(kh09X*X7 z)_$Cg^)qFPZx1`3`dfy&sa|^=Fcv{(O=5z{bT{OEJQ=kn(Tq=eKU4!a*=QDe9Gm$2 zNH#W!x$Ay0*@kDM)|<3c_tjUg9#J9OKh|5+_tIjhdT$o=t1{eQJ~n`$Gv91~#taM! zXQ5!_9tS;6X+}KOk%g!j^>XO>Y{&~Xzy47T)*;?%OQj&#q*3L>RE6u7CA8jT2i~Um z6s))uKO*^k4%%yrS~lLFc6Yayy)WX28&embV=%qHsRzD&mce9ioTlG)FM}zbJpc5_ zF$NR=DEZamH4G+v8FoGp!0(a;k$Qkj!4V%{xaJ|&PjKEZA~5OyM`9*&`lZGFS9QPpA8dRV@cq%XKc5+f zgUR4$wTZiS045t^faqC^qtu$Q_YklhvNwp)^ELUuLtZ!6*zcsLz!S9?gqi=<8*A5? z@pe$Z?=GD#1be(*=NB^&F+l|@(ieBTRuj^uZZ!B_3Nc8IV-M8Pvu1EHdiW}>`ZTlo^aLV;t zM`*tPA^I+^&k_BYC+d;Sf`1xWL+@uXn(}_ed{FIg0ees-gMMrqvmXep7sKLJIqZ5g zzspX}yN|)t|J9U3XM?eL`_Xbt8II3gy}D-1sc(M${WCCA<&c8ezHdd5DGxa3{oc_Xozg`lOI6hE)?jEU@^N*MZlZ_W`HqI|eAFh@o_CD0x z7rQ&gs_^*-rd1N1tzb7q&T_5!YjW$oDJ}~mDM2l@>59{wsC44M-(hGUo_OyHl zt+#?aLv{oFVgCht67jEHmmy4so67$M&sbQSTF3E;`Zc2Ve%7zx!`llI09$2!Kttz? z^VQl&TYWr)98P%by)U#l6!EDs7@dv&u4}cnVE>(NVfa54e(mdSp8CTmS(!tUXRX>) z4@45t_YUemVZ3DN0X_YfcVtVZP>1`Mwz;h@KdMF)6YQ?G!2ch@LySf{|-0H2UVB^VtfA;K2PG2b=IlsVu)EB~t z5xI))H!eyamhbOCJ^fPMw}=1jmB}GxdpMcq`-K-H^@nJ_ZvWN-KjA+9MbZ7&6c()h z+>-o|v=JqnO+fi?Wj#^M57}F;{-)(+c7I*K_i2q)wPWGK6Rf`l_Uj>G#1nm5VJ-#N zy4n8V4A>!o*#$GrSAxssXY)z*G4(TeehB#0mZCy0;}8AFr^z;^w)HFThmLu@@+j(4 z1~}YOP8Ov|J%vAoVX=K4IY`AzIYwQa%iI-QO8D!_52YmNbMEsfLuM?8& z%KXw8+W$r9vvK=w)NH5m9}+fnvEwQJ@7l~qCIP!9Lm%%TeZ6+$Q2!m2&tO-i6Kp@= z3cvqf%Osx{?JN9{9Z&Fg4!`_;aDPNI$k9X}7U!0h{lT?~PQ>~>3LB|=J}`N7Ai>Yh z*JN<&zP95hCI-{~Q)`cG$inVIx8h$s$qzXXs`)~H>>7X9{df;!TS&e%hhU-yNqv&E z?hC9J`vc%BVowL?ebXB+C^ZY}{7(jcG#T#L7UN{^BOCgjBvC$~+K7LkTt#?1`G1Uw zdonh^$@dXe75#uI!>dXmTcKswizO!I7I{S=S9YuEW+24_Qm=_!ru7ew`t+r94LNI&vr zw*Dl*VdF8w)}wvnf;H+dk1a_y4y1H){3d&8N<+Q%))tm; z6QcSm2~tYI*nZcT`XG@1je_GSKR?M2-tGMy|GJVq#*`mW{r?zPTe6gRBW(YgH zhlL+^Vf}2*$Vg!4C;eJ6`z+c{>bHScK^;9c%1Z2^ppbkdl%h9_?HCzet^rDTgp-2 zt255d-nZ7cyr{qxsi%7%;_u+j=Ie*zch{Ai8)D<@!LMa6w(|-%lJ7$+oEo`S{b5Uy zL#!@4zT@F=|1rp$e#=^pZx>MixOH1Rk2fHBprv)?H{~!!Q@s&vSGhiXxWdo1*-0Ef z34ilWm+<=|{(dTB(Hu6u3H&SHAL&={-~ZfZ-jCxKJ!z?8Fww)6Zfz2Sv44Z1xA$@W ze0`~X;>gukRp9`G$UYKyx0|nL67ca?Tw;)W=kiSveDOu?7PFeUD zlJ5m=TzEc4V2=kq>NvvbE7g-T%{V+53u%OGm}S5>s(Zj*Y2F5DjcpU{6ES0W;laeYr4&L7=8PQ*xZWtBzfI?*YnTFqk+eL0%c(% z-%KAH8wf=|j)*xo*{=kPKS4b{BrEd%L#G~M=Og?U@4AEIBhiDB1U0{v#&{s_oE_Mc z!N%8TYK%kcYYG{>EK`$cIg~z~!DYZ#H`X(Q5r1i{!Tx#-rh0?zyP^aJQ+%zI@V~i# zg!W&|>Sn!Ezr8Chzxbna_6y1HYaIfoe^gIiM#0A5VRZzutBjK19jHlABW}r`W9fJ3 zMOTf6EEJRv;KvV_W%1q^Auy5bYbI<+k|nfJ`9o#*+~R_#{(OA?qs#Hn91yFHr}y^| z=JU@y`tNTzdHYZ=koHHy_Ms`vAV%>Tfx+RUvQLhAM2t2QeRbI1>we&XI-*HDKFe9* zb+YmLHC<_)88O?~dZYd5hi}x5u_rAodiL47cs?)H|C_UR?OHbe{lygDACXs4KUCKY zgl7F((}|p%iby?0!~=-c)hl{fKkaXlkP(R&q_>Y_I3PSl>n9;2;lxj2E1U_PgWye?2t88p{mkMs z?E2&nkS4uV#Nc|+Gneb|#|$Qa#^3R6p59v$i zXN`}F$P)yg#C@~zRIeBH3iy1}ek^Yt$nvrNsM$N~5n0FSvC{|&qmPUydLRY*IX$EL zU=El_*zrodzgzIC)PdslkWbX?-FpW`K;DsjF4b`eRpl($-b}@;?}idel1CnEhl)pVw+4^|vS<7~9vg;lPijqAzO- z=YX$)eiXx?fak$(b8;;4`|VvhW#S_<;~T~uj3Rtq@X#XYHA+Ko5`8gad#gLdcgHL8 zPgPZO{6YEffp{?r)B?!xY##VCS=>XgcSHVo>P$rbKz_bIqq&3K5Xm!ViY!%Av*tbe`G&$q7M#z zJD*>wSK3oEyb=0*Gjr+Pjp)Hp#kvv(lf5agpn$JO*pm|bD?BFF zPxh-fj_u$GP53-<;+{RMpW=_)!`4~`FQa@!!I{nAV}^OUFFwC{B7@0ZA8k%`D%PeL zS@Hi@+jF%)7WUY!13o35^wA&O?tc1;s^26V&AL(V<=Ar_vX9O|`7qz93H4A2nI7>Z zL_ZK6RY1Nr%8({{AZU}7_>{&qdLbM0)6r~3M?_HBLa{s|vn`r`LihT#(0pJiyH;!p9(wV_9-UV#2cWlr0j*iphR z9Q=3aV_$OlIX%cG{l9Sc0(L%<4+>R#fbtXN>zTZoG}cf02r7nHB5<$4>lO|(nDhrb z9+|;ZAGEOAUJ$$^c!v-3L-sz>-!SB6`x}}8(Q22?vM}&GB&f3o)U4$coxZx?02U4>!Pz$*?7|5XGxKI;l#h6IJ0yI>nHsnt}~V6-&Be} z4ODf5zs17_86^HzSb9|Gk==n@pJpG~ms+>&I-uwdGX*=*1Jp-P_yqI;5yFoor|B(KoJ?uX>w!at!3vI$9kQS#1G#^&fL-`w8`ZphiW`Kyk z(mYtvME|d2_2$aW;5ULc5>HR~Yr5O(Wb1?W=Nor6qTdt0(d)g1te@of_V$Q$`9XF6tRa9exI(2*xw1CGZsEh@)7}{!?D!uNlq^RQhedBj7B5tC;K}ro-afnh&>oD#w-5!!PEJ9g8Rs& zIgpTuKDh4LVd*2Z9pZ(H;A zThb_220P{|{@B8r>cU**UC_r;T{J%Js2AFcb3$Lxz0vz2w>L}${w{r@C`>OOxPbh9 zoi8gZft?@opLY6OgdQJDi<p(# zb3mV9FvS-;{j!b09{9^oeFncLy`3?}-j4mKb-D)V%l8DL5*2>h_V8kB6spxgLvfRQ zo&bXZJ0IES&08CmGPn!;{hJfhxjd4b<`IAT#JX2lKk@(X8p^-RV66%HzlRTV`Gf3V zErk_janJ^0pc5nmzma}w!L`FEiR(E)AEV`QgYh%h5kA=M*}LHTGGo4!d^*J6CaBs5 z=@%Zc|IPpXb9OzVM~l|hR)t~3UirY=XO{R7>!ZiY@!{@$qIBkVWDxVk9JqZfciv`V7sMIlHtDZ@7_7*V%P3&+wJalo8!j`zkAL-_ug}U z|IhFI&V^~``4}AoF-^nz{$n~{+SoqzbG0#ksDD_69ULZp0QZcSb}xLMU8`oVze@lA zGf4dR`3mPv>?y51{P#}HJ{(ulu6?Z-=gF7C-;Wu;T-_e5{KAL#Kh&}SvDN1XRj zrTW)hJMxtrpCKF%AbhXS4B`Lu7YtD!$wNFI`U&kH`GvY4&+Bj)IKHa#+w(19uM|K( z#%VTd?O;D%aBw&1+_tK~0!1m2@)4=VjQzE-2hpJM%t@YTp> z2z!_G>(XVh%R~6q32oK1y6i%U5>^HL)#yDB2OLpPzfbuI^kMq%LC|Y3*b&b0nfw#a zII#aDRLCC5SX(dXpZt$Ihm`ZYp6r|h3-&`pfqnAZ^iT9$w0!S_ z{ClfmZ(RS_vUfO4{I@>lYg$7#W_E*|| zj&})P8#H}j$~S(A_74KL<|^Si>iRqJhddlO@W&3A^IG;E^j$T-4v!BG!sAv8|9OO? zQ~0xKJ*}|FEBFEBvn=vrJ@R>FZs55&>Un<0@Irs=`}~LBPxcP-b7=GrduLRm&>EZ3 zri@=f?Dh;q`n?Y% z-3$vYJdpXfsy%T!59`5K%heJ1BSrvbzgn#LowlD{yZ9&gl`gYJ>Xzum9^yaaiu32R zzUlGQZc&~e4f{Lju&)&$KWUchzdwGb@j=q;iz(%)DfSq2EAUSi?w70Bs391c7D@ax zBh5Ji?f+YJ2iZ?r{Zyh71y2qIQSBL=nP8(({5W5L3tFa3@W5cOaC9S!g> z>K8vqMKhB_zoYTh%fE93B=+)qU^H}I5!+WXH3^}%KiSF}u))Rv>mriPX9b`Sh{wOw z^=c<592^1JMrJ-{xB>hx7OVdcr}OxIDYhE5Sgs_LM^Uj-`&0Wxt(E?wK2V6GSot5J ze^oeCPnWKit0V9y8v!RgX24+PvC?~{EX>_1ONLdYt@GxvTJQ_GeIUnnym~LEjn7C@LzmgM z*`VxFl!wrtzj7!d)*wYiH;xX?guazK_PmFBpT#;2%`~3!>Jozguwtd#Qhvw1bs>Dn zqk8LsGm1KYatAy*lI?9+IVb4-#{jZbhsfqpzo*kPXtwoEDL&s!`re?&TVXbgZgyRd z*l)aV6%?XR;f3^w|4HSycy?^8r0${w26^-Fod)Jz_C0leWqA^#NLP;DK=^}f^nC>X z$x8{HcT5=M&I9g^8LxCJXEs2-C)p!uE~&(49Z0?M&Ry4OkfNMS4O>`}SCV(maL#(} zeXM`seakQ0=1#kecpa2&$xhbcHPwvnQD#-K8yVs6Q2D2xxBO|JhcP3HVNR-)T23m% z@NdxmXV7115dE*nax$b#@-Vh-yR!R9!xPDUoh}h^X zC0C`#obqGp=f9jbrO|6wINNvDlG)Jj7PE1QBm#qWXrL zx96Ygcyh5}yae=YWy2Tk;VsG)^?~+AP=ykB@dg@oy5C*o zl2zjeFzG+S-dUM;C4_H~7uExWF{*wEc4A%ik=o^wMNSqpwoa8%1j*4SjVJJYFY{Jz zuOF?r0sYbUxIHd;S=5qPfa-q8mk*QtFc63W{NaCU7yiV*R_4SL%*ong+`wK#sVtXn zUwMU9YwznoK8G@O1^ff0Fu6y{WeFXI@s6#^IYoIQxzXf*%Z>dY6b+C3AF0i@$fWbR znJV{J6x02{ACD1B*9P{YLD@2xZUsGL9MhggAF9%e!OSjfngmA0T2Swf#n>3wZiDiq zHvf=ER3t;1X2G(>jZ+h9(wwg>yJlN2 z460q4kl&;$vc{_dTYN^-{Z6P4lR3)nEoweq;*aheSvJ!5pwQQg{E#N1 zqfX>>Xr>P|=;w(QCKp(`{12SN*@}sZkN80zD_LIC?wpc>hRdIup!U1N?630ithL2j z!fuHo>{rWsw-;FEmIa>=fy7^I>)-ATQNN-*T=!mlhdt>oUjJ}iv(1GK+8VVYf1y-s zYp~mS{4?;QN1A>7<1dEVpNq9LN)x~Qf)A?^C1GRhReq7V_s*T;p$6~8_ehN|s`;7s zD5gD{cIeK_m=pT=9*hIjH_)G209ok|CoGy1WHHua{^g`_BiW89KPLW`u|KqabTrfW zL(sR!;YB8tb77pmYmkpg%rAQUFPUHUbAHbsvt%{^A2A$D=+0z$u}tHG-2gkRmzhUS zk+%^&+8d@iX)uD|sr%xS%`D=`BLq_kTNrDpS@3+73gCzMe`Mr~-5ie33R~zsb9eA7 z#O?Pe%7IiX%g%FKC$L&ibN2N`{nD18s=BMUQ2&43u_$5pD_RdHFrVKA3Gx17%_rg= z-u)upkMPCQH0}Io%ZYfW+aux;>HQ`$MLr|oSB)*sajS@b$Nd)Od3x_TgOBZjjX?CD zmZsVVCG1z$pvsRLl6I4AK?VJpqg@jaIk^Dy`HK3Sj8{dL)Ex)z1gjfx2&lGwpB~$`Lg_e6RNFSw?`RN3FRjAO4;f? z%<#3SZ#T)(#<#(LQ@5_j%7Y^0J-;XgA1>BfVl5_vA&l44c-1e!YC@>wTiT1)dUSq$Ahp1p?@f0+E20cK*&HW$j zd(xHnzp%zk$UlrBoKw3 zp!!oVPk&xT0`1cqlROIRmF10IvkKFA(c*6Xl2Sril^*&cVGvI4Jiqc3lbSI;osPTU zKc;e?__ak|-6rB|z%S!7@|{8+1D`Ar37taUJej&L{+<_oLLSuG!WMo82XJfs0e@_@ zmS+l|75sl+JSb*{LExJTW8g)5;7fX1nsYjwx7@izz>rUAa#HAhNuNB9xVYQ&`%%AA zUUU6?#Ii<85iijfhZ_uwUjla`{ifDtWjkV#4{=nP8U;-Hgxp{cSB*!&r2iyB;`8S{ zksNF88=T1HaM;2gsVV<00juNH>R`REqs6XN1L0eQcPQ7l>|ACP#yc7K*4Ib*b)w8n zsv9PIL@x6qvGG3DpGo|rE5R7?ca;CSQ`6U1S8Qk27RHuEDZ6NXy5vgtlu{jJi?4^j z1NwT0U)6WEU125mg-!%Cb@;XSn^NVi&F2*ccd$-#YydU!VqJmo6 zo&S(Iq{G0M?DZ8KrupA){8kKy!xnl>M-L_nesVI^XkL?>nZfI6JTi-l1WfcVefD}W zUj>l&7k+lJlD9{^wsifa^5dh+46G32d#!O2wW z*+Uop-Tz>Ht%q4P{ty-&THnVB1C8Ln`=8Ah@`UDh?X>)SgdY#&F+ndUZ2i)};A9b> zU(zQVw?>Qk-xptFx%1AjJ9$09cd#!?9H#q+hVlnF9Ba85GcF1KL-hVd%dOAx`dSOl z54!F;ZNIWlfj)V{R#W5H`{L!T(1@=nL_e+VZ4M=%Nt{!VPo=#{iNp)2fc|Z@eG}?_ zKZ5%u`@)8BJzr0at=e?7J^v!rQ8|!mGne^}{7_lrGX!QS=MJP+n`GB!j2wIHF-Wl| zus$LC6XTqxGy!`uHIH>0^?YEa`}1wbh5}+dNI$nH9Vyj%Cwt6j=_}9aP*mwo^1H{h zrORi$#c)a0(V@Sa_NWUikYRVj-3zuT8n%|r2W~9 z%uMoE&dv+_#9MqCAhE@CS*35#o7qg#LWBJit0 zJd@;&1NF)%q&W(~tXr0M0z1+XpGxCZi3`ZmYE*ouRDX+H;a2%aGz-L0`%m-tc@&i+ zNW@>-!t4#|WMEGm(f{gJPrU|f`r&6JT|Xp!|8T+&d44*zy{9ou^*+!&#IF>!4i*V{MD`(OK;$DJ`|-iA==!GmKC4z|>i73cHA6$w_>bE(zjHw% zukVqbNM8Nf2*FPvZ=`|AFJbnzLil$E2MTeR@Uv`ghlY%}7@yT>!ui8O{$3L6?;iG62)N+A+mBX#Ti65ZvT&Yr zZno&3#;;HI`+56Vi%0tL{4WdoKY{#Xa;_ryKk>7mYtqfzyHyXMdxr{lDmTKPWHPeB zq8ph?Qclv&v3;TeST0EyCOS3wmw22>8{axQwK0?EQ})dt-569}+zbnV;Lg5m`CJ8= zWa+J>kwJe@bu(r@(`y8NgM6&0WY@z#s!2d&IKltn#pXOh{08gU)c^W}Qy*#eWY-Kh zL*G(G66q^r5zM;{wkDNz(9gz@u4SZGgYT<5kXqH$DZd_~z`^unOQoB&+C%=jZOKlC z^iCf7ngFOm9S_%_ht<&6Y&H--w8Mc1N>rpl&HgmDx5)Q0GhRD zzE?~A(k;t%eHQXaa=uYn#@qMAxJ}P3>kW-I@i#Z+KP7q(&5ywxmh?Pt4tsp*qU-L` z^l`dK{J?4Q^z#NEz&`qL{{@c!qoglkzh#KNw<$9n@WpXPi0 z`r?PD!%atYd+Xu4!sOAfuG_%t$$lC7XyfA?4#WA}IPPo#hYJ@qn&gqbasflW$ItE- z^GEg^Ya0EgsE7Y&V*Vb%AM2qnMATOmi~3}@iSkQunCRF2v}4-*L4r-&-|aS?h==?V z^~5hG9xWC36Thvqy;{3!< zbNp-OWiaC_C-D-^fOC_Ktu=3JZ&VBY4J<(5AC3QhW61wBdQPVKo$@&{t^t2pZrGNQ zjQ&@jmSY)#}E4)2REOqN^2#dwi_&OI5-;q~O7F?pw^!SaFT&O-(KGZlm&~qFg|GD=V*Yv`bfvJy;J| zLvTw&#t`*>8E4qPhy3o!!O!2t-)9^2*<4r7W)71*V6w?VUXVOzGq2I>E&bq7OnhIg zrP?&>OSc^5_Y?hJIdoZDtieU9H};GOe9bcrNK1Ca3Vp(Ds+Q)MpVIsp0p&(3?4$i{ znV(f&t*oAjGn0uQHa*=E&if~P@OMS-bde!^s(kPe{{Ykd{nGUFjN`K&uG_x9LFi9J|7Z6fRjpm> zgXp_@L_g29zPQ=;^0R>e@86F1%Nv}k;xN6x93K&q7Q}yREU9jqU#*b;wf>>~aU6aR z^4Xob$d=Ri|J{P-iJpyRT_OF6_{WYZo&WUX{HT}f z|B6_FT4sWt5KW2s8qIRB-DSit978#+>QOB4$Gvu(01Ry$`t*NrQROrBiSzF9`;lVZND#?yW01b@5d1$3)+sKwYx>m$-0Y`PVQ+T^ z1%AxLe3cDG2!2QUv}?!A^d@~GX4D4cyZoWKKM(;(FD;y^i|V>$Qps~pa-el;}sxS0*{4@$+7!F0r1C9tVd%;@M;l#TpLLDnkQ57FDNYAsyUBXxSb zA$&L!BzaWXr=Q=U%Bu)RJhi4m`nkL9e5nQj2E7{t>ke`l^Do8u0v9<<{SOV63wkB| z@OV1?yxwT>n7&`+5%QxS8qE6T)i?6`M(I?l)2-`wVT&3iT0iCW#J>mo2jAduwd7_C zn;i=UY;v1s+;)$cZ^B1-K?FUMJ}Y+(M0vsm$p=_^19euh+{k@;qIiJ_tfp4$Pn;PEJxQ8iU(c;nW&giSr zqHDb12OiU<$=7D{_B3DP$_>$giG8a6ki$cHdAz<~DzH40`APqITG-6%c|FpHf#C zzdxi$n|2fRx&-}|9@b#eXCeRx6-*7PjoTi>$D8nfEEmE*R7f6ARQ*cO2l4Y##Ow3=CqV!H z;G~dG;z<$9Ifz*mS?gBv*i3LnX`seY7-(^S>9V|G8yl16uV#@K2BA zdodxAw_rl!<--hZvF z!2CDYyes_gB;PZV^O8k9=-ps5M++GB$Y&zxsOt--i+fB}rKW9C=<=BGbBWm^{MC)*pL_7}uWQfO?1z0% zwV64r>Jz7T34TNT#Ow76e6~ZL_+?qh7vcv>sVw*n=@-NPpnDI~Y%3@brV>xWa3e;j*Uq7Jv*tgWYl-JW~zqZ)z5ghKv{8W?=+XSrX>(2{V za+?17=9({XxW2d_`g**P!zV%C^5gdwaJaoV)*SxfKR(6i!GB7KIrz-;i3=rA?5^UsjJ zR}y|h+()bLZaaO6^f#=(s`g9DEs7ey2I`8SdIRgO*Y?x7)aFv`g-e9rSdZ{%^=A=J zL9nY&ZGby8j`}b4pD|_S1cU!FAKY}H-LKL+MG)uXys+Nf%FTSd$zJR2EraJkD{dqA_osV^o=szL#D>a=_*ly~R?8j^lm9HjVHEZi(OblMR~4@( zdhW~A*AMW%QeCUAkC1(W`P!`2F7E0?UH4AC_|XB>c~tbJg|SDkVbG z7vzTMFs{(MK|j;<1r6>0%YLOFA$kuQh^nvD5fYv$wjy33!6=n}q<&I;v6GE?nfp*k z<05~HT<$LC-}@fMr?TRh;QyqbIUX+({*)82pJw>wT;9GA_}@21^7o<|AVc`XUJS}d zLmDvQ-`bbzVILuyKb!o=;13yYD?>giL(kbO#83SGuDf}E^!)R(Ymmc)kI&v3F7T23 zdH=6)-zcvqdhWSK=LdwJQRYTnUj*zh>-10dANbpS{C=YUOovy?R?fXgm@%{85{z<0Fsl=Isgp-JcJRbGRS* zO#6-*KHxC7`!RwBL) z{HU{4#h)wiFOxps5qDD=Z%_EJ>BE{P4%7Q~%HgUb zxAOMXe@|wa*1u0lbW5@3w)&>n`7BIw4vnk+?HpGkRP9>FebbZ_j)k{PQ56PNvFmSn&&m$tU7V@-_>4f`8eZTZN5X zp+fT`pV!;d_^eTmi+%>3dURqr`Zg#QBCh z6Q{i&zIW=%REXOv3VmPM<)%7*zbbD_BLV`(_l$LV6Q;uj_nbb);Q`F=bG~KN9?|v6 zT;k^}&RaCA#4wgd^bUBheTSfL!iS9q6GHH4CVg+wYH#R$Uk3jI_IpE%Dd?2+sfbEh z-ZL*Zy~@4EeJi9{c_0vQ<1B>%xQ%H2pd(&C|BT`}e}MpQ{(dxm9&by7fTaQH*$2Hs zU%>7Zi5+7v^Lo&~wDVG>i^IgfW0&7FThT_m6XPRwL;^VHn|VXckJSA@?(%~P%gxq< zW-J&UqN}(}TDb(v?x|YqmlDdYBu#K(?Km7p>-hl)Zf}Da=()(Ric{ ztuv8-pmq6T#M37ee>k3~@&^ZNnWgF1*~QqOG3nf-@snu#2CDc6peOj3Ol1T5 zdLF@VtkL~nMDImQ_X+!4)%S~*3YhS}F12pDyf@u5^PX$?`>OO&-zwVE{Y&?yJSFN? zd!cz3hp9fLXz3gdtNnZR{?+$M*{9!+=cm@`{j2nZ1sQ(-Quqs8*l!N_Y+$Q0Mf`Qm znjE2T6kz-=w{8;nNAl?KmtR}Y@9&Y0$3&$@aDE*i`i-rR3)wTJmkHmEj+Q*$p2jz* z#ESkQ9pR5F;rG*e=fAL@FudY>Xg-sG54=C}$JU2;30SqSZacM0Ijgn!t^RzZ&)n2~ zU?;f4-*p7+=+&DaEYh_Y=G%_A^!v(|T6@jvYe3hdj67_!{BQ=>dKo z?0s$pZ%^MJtnw$S$}BFDzgZre;nwSWq~7hI5h|mBdDI^H)<}Q$ZMuHn>&>uVeVw`C zzA*aAS2Kxz;Vy6kKrw3Zx?(j!yZWiWDcV)yr(u|nPi^xrFV;bN5INgXWosH~Uq<{% z_D4>_O4-)iYj!r{edKo5z)H4H(h`#Cei&m3My3g*7cwF@5=W}9qR;#Bh{3!f3*Jtw z9}Tyqn;3@F$Mw6%9wMnZde;Tbvc-8r$YZ^Ut68@`YK+2QOD3wC~sr3BR z(tW(1V6P=b_=kERe|zI}dsuD1G+Okp(l;FT{C>j6+u*S0FzNG6`}gnXFxB^@+$Q(~ z^=DpMG@af}*W8gJ`~%c~W6CG#zpW_6{E7ai@ta!EZ@Vv2&@bUnM5TYaK58nF_4Tg; z=ucypvIRdR`hI2jF-`t@pV$oS57{=Vejyl}_epBHd^ zt@+ZCcA1)8lgXhzreW+1BF7=yJL;M z9;sfWpO^ZUp}= zEc^O{99HSg5jCB@B=0o)o#;nD6U4wfMWz)0+P0t?7`p z_xTa@fmjRNk^jKokNgSn=Ppou+7(qEDrK*x2NmDw%ml19TUn>KBzrW5cDrl);rf8@ zSRMP(;!?)cc$B1|T!$HN9cB{cf2hTV7p=F0Q`iq_DXFX)Q39HTrT&uw3NjDObmvM6 zsKsE(s|i=-^JT@O>id(Y20rBPNAJ5f58>pYLi+(7bB-S5Fv)*wT)xl;2%lHER*Ctc z=lSi4k-RC_kYEEJysRI@34dTL%k!pcyyKnUyJ=R7~i}cS#yA^Bk*VQ zV`bw`*jI#4_Sw<|unXltvXk~h3VA{CWu-YEi}C7-sfB;MWtG|(KQzA4(Q5>Jra0D; zFfCqYT^9D&PtzZ%{i-F=q4_6$pxV^wDiQNX^zp{r-ZI|5(VS=6G8sGFUoX{}Qysis zqtD;c&p)v~Y-Rb|V+H+F{+i*A*}|R~OwYH-@}o}PpTY7q)5YH40EZjFKOC`+&jSBd zp9noKWna<0NiEz)A^W6pqAbAM6MumFLD*g zbaze=J(Xl$eECh~N~O<8`S+|Wejvk@fZ0lege88k${uc1T^6IGF{n317~SQgVTtOZ z3eo#omHt#bR3p%$G5#8LLjKVG)8r4wRw;49&hMiT?@#Mb)c>wOzQ^G4Wj%$dl>fPb@)dU~RZ5kqw$_|;%z5lB-};kj z(QIXZ4B}7bf3v|bv8FM-g;@t;tSsrmUx)DLvZ`MijS+kKc#^%*>N_atpY)xFBJ}kO zf|2e+@N2^7)%I|*k9K{9-G}kU3)kdi_@Y&vMcw}oeI4uj+J{*2y@-!-k=&*N@&1Q> zzk2t7@{dGcK^lmYy@c3$!$GF*Zw`+f4~=I{koJG~d9a-*@V+i)v~( zO!U0a@%sN2F!YamW`0utfc-4&OY#@a?O3Mi@3@J+@BNX(^ZETZV>}+I{PwL%E@`(~ z{Ufl}($lpTs(ubM0Tillls*DH2eOAeN~G_*WLN}J+A#41g8dyYx3;{a-B076;q|(A zg))K@9KPCHq-Gnrm-fGc{|^o1KpEF|gQ9Qwp_TGDT)s7m^Kv2fA2)~3wg-^^1UY|h zBzn!vDDOkFKd#W}1HWQprkzfPhtGaO`&wGx1qJVDTM~i;wYElStc{rn_ALop)oin}qeBzp z1h=*t)$jK@5N~Kw=ARjr(q}plFLpX&j27@`;i)}%k6gqn=x_69MDP5i|2_Y%L?bI3 zqkIy6#M@m_tmn&4QNAw3Ysw$X53>{@5rT zJ94Cw*VFwC_Rn}YOwUVxOyzGr1zIfepKXbY`t&c>@RXCz)68R6(l(<@GI7UMlQAZV1!j{*fy(>q( zpZfTBmvQX>oMe~dY9Y^);D7t7vOt-u%#D1dKc*HY;m= zcgGI?{p8;Z-@5-nWGmdIIC7w$Tahm%FS{-}Ce&rWbSu8=YWb}Z7*N?PqxJVBza}3~ zD6GZ42>QTa?U**c@FWAjKKq^Rs(neh4^+2gVsxFEgyj#BxgL501283ycMr9%7y+tXw=P8@;C^4dH%`1ju_qj{k!a)f92KwQmQ0)0LQi^(|9H& z!IHU!h??>p$qH;UM$vxA7XW|1tQd{f$*5|C^dMg?=+Qp`deriJ(t41b)90bKR8;(B zMj_Ao3EvHR{5J8A8PoJ{qs7U#w+r-F`HSPl$S|!p^7m4Hkfw-fur1u%M)sOoNPktP z%7uME`u-cg32fo@*)E#KnpNxwg}>y9P?tM+`Ic%DjM zQK`bdCwn8>yFcZV?OR1X`7gTfoG0v$ClG%oE8W5#@D^kI9OTcV=PyZFF8U`prAS}Tr1*sH#OITE`y%*feD7q5 z@mQDDFV$E0{U*@P=H<#c@(-?dO}#)wU`Eadq91yn-zJKUQ;iDqGbv)9lO@`rpUo%x zB&a03aGZfZM3QeTlI<~LweJ;pp}`-JQI0x+^(jY_eL<^t6AJWw%k1#Bc;r?!`k+xb z*{s^IuyC|TDPa?-coWcjQVGtIML8GoO25<*?ry^Adr~T8q8zCUwjDaP3x=xd4j*nZw{82 z4bg-!h`wg*>)9>>+||J=>i%uHp>1$dyIqNVCDt;#3;Q`>Pt$xupO;GVybI`NgA`%C zC6nmcwQ3T)j7}&X`hMN*ciolPy@1V@Om8j;uBdh5{Nyjq@?b3!#TNnk$MT_D`J4~C z@eJ%<@Ef@AWa{r~`O}TS%L?nF-c6AIkzE@8?3-dWanKjz+>s%Dc?$VyPQ+JqQTj~_1!aG2K1WWOTniT;|F$8mJh{JYGX;oGX+LG|5b zZ@BsWq~F#rAKA#^edsSY=)Zx(w0Y;cICHH5CCSmPPpwniXlfC=lYeUKz?Fkf5^+wgfU-9RWev%U9 z5c)sG+pkP{hWo>?e&9BBY;y^FhxDCYt*1r26QXezrg<209e)-7b(RqQXXIqR!+NY& z-o!)59*9nC68Z=6n`~D=d;3u(cOd4N>6&w2ELN0vl*P#L6t}b5ln`!D`f8f5L^-HH zKWH?q$|w=@d*b$!^&ZdKwIK(~9(#Vo3mM+bXywLE16U2D!@VzHxg&(=>4)g~&_|%< zqY_CxRQ{yyPg&^#$JnFBw*-wt@MgrOMo}whOx`y98*8u`v~2NmM)FnuK|}LLl*f<@ zkZxa>WyNXL#2*f-=gq}~KlDXv@_Ja2wjO{z?khKj@a@bL#SeeipYsyO*Jj{PiA&Dm zFyW(P;T-}Wdmvw*>+Rjf>xo`HAL{hm1N?SIK4awdw0|%uIw^_6lwY=d?WDjr!dJZR zDqc_e@>3~#JmFV1H)8+yj%r^2F!JSWsp#wD@bHFvEX7~F^dg6~`^_yJCVH$=mOanm zIMrU#<4IOvJ+z~!T<{}04#^{G~lxbosvz@O59+*LL< zUA#Z?&pHETg29o0<#WIBJ%jk1NZ9Yl?`CDi#l>gXFQo~}KvkFze^OH70)GGL?>FB* zQMF0vYbWAo)uv}g>{o8s^T8+@6>aE|O7`ybY<*k1BR&i98riNYem~8xGPe7*5WHs} z;z@q#8!J6GE0XLblw^w?`<1SiKg9@`&5(A)e`jdDgP}4j{izK-{q?&Fq@Gq{EqVCk z&=t0rV2YnW`Wx_%10T_1D#=T?QNveUQRAN%47H=4ieI%4Ha+xgj|OY&4{}xF-5ef- z{%TK*7xoIB=Yjn5kMnxUCzQ1!tbxO-KVzD`LHRbTt80b+PW112>7~Et?I|A@!Uy+p zxDfF@NQWut8DD35=F9Q3c>N0GyPR=m-2)u<7JE#&Rc8df7LxyD8b5+QApP1m*U#Gz zZ!nl^+D67XymXPrbm-_mUgR*fU!9#1qE?L`xHDzJkBEL1@6-*v{fdHEiyXXJ=ri=a z4fA)s&g-GSnJ540jlbeB@dwv>J%14KJ9ElYy8ndyBXwpm-iX#QU3lZIL;U_FnLVc2 z!QgHV+w*ZE=CKXp`%a}AED`oLp$`qF7g(JCrv;*1DIypUJ1 z8@nsC`JnS35I2U^LpY3k+bd4RJHQ(u3F2>|NA>X=kSVxWXl)!?GySo zb_*>EN8$Bk|BrR)^1jv*ws7mZR41=rSMXkZ_>Vp)<1oqFjLdW)uW3CqINW6A_3H}y zr33ab!B2)a^h>UaV|~0H^kEt}zC+L##rywqT)BzY52hoZR+3%3-+gr_QxkU#i|?cO z%+tmB<-ENK=Ocabz-SeRX?@3Fd^V56jgbG3KJ)0`aCiXlk*GSZ5BbO+@y5k6wf}DA zG0eBqRI^~`hrB)6U(W@~#P=>))F|c3dVUb>m$sCC^U4RI_5oTS4kkGKhz`0@Id{w8 zx`H0l$of7%;lgs{lO_C;GQ2a~$q1vnE-xvWW8z=uSt*=EbLk23$;w;%5FZtG=I6dF2-Ry_&0V>3VbDfv`oGz?k^zuq{!L3LKZ6N z_Z2m5<5A{Om4EcZ0+gBQusLc(g-JhHgA5!Kp$CxsU2vCXZ_uUT4e!PO_1SrXo=x{Z zysK#4gHQAKi7$A#&hk)$%;DX;pzp5E$mj49$kTW3e|U_;M2~m;pj*J?pMP_;t`DCn zcCw3q8!6=Zih_L0riyYwPp8tGZ4J@-`;q_O_p@K$!24I@qy2}EarhMS-FF7k(>RRy zOY?Q-9<1c>nc^O4dVW3HPY`Cehw*x%7uQAI9w2^Ym|+zBkLce%{MKIHz8&h)&w z`^mo{+dBn5(D#=H9EqX&D$4i2DdWvjW&Szi9H475^uy#5 zCfm#rtbH0iG}|20@?8*rXdODgh0izn1N(9(b2WH$iuBQ($tkx6Lw|?=EDIZu1dMuF z&gJB-G{U}o^gF+Kkk^xc%h8$=H63o8w%?BAdE1WJc3%JRuBD5v{qk?layT6Ma92Om zaw~`L+jSuI8y9m+Is63dgY(%LMI5I0JOAce!d|^^Gw>GzmDf{#-OQ&NV>x^>HQ7?0 zp~siveyMNbsF&9-$Nu!h9YHaE$S+~buL$L%KqFc|Ny{Gpf4n_C|F~~K7l*ec=P}o$ zPS5184+h6C@Om2m1U-F)s^74_3>U9Qehka}juBzMe+9`z+S;1idHwK)MpJo4b`FPC z|KJ@>!XKlqf2Qd11yp~{LPs2LKLGussTW&wLWT5!gx-LGrh^w|6D+`K5fdL;hSLA5X!)la(<+@1!sIT^9ts+mSD4c=o}={Qkn^ zWJ{Gv$9FaVLyp(O>*;x0#`N_Cibv4n*EM_Knj7`=E-1hL5BD4m@cU_f^SXmB;SVJG z*<8MUnAa1(dZfg8jKjkldQ9Ovb_#kqv*G>tnx^59HAME!iFiY-wUxK0{Fz}lFUFdq zR;WMEV8t|f*dyK0&>+T(?1!&B?keT&X+0xp%J?+;Xa~%}?>*2b6wgn9${RPg4s5WJ!r3-#X^zMyH74c7`A1cc1Y2)7` zZOPqo6Yrn+ryM+7ufs-5^t|E*4xhmHTv2lQcn65Tc@%u#ZY5^9Y&%Kz+~p!=0rFW| zw?P42vWVhu>AXbk1Pe9(NAWV-swuTgif@p85uWYk+7pTjPQ=S29g*8Y4~)->H3yYe zmSa$k;?kBSi*SBTlkphkqYlJGsr&Qf%Dz9x{W}TwSMs}+IjEtY^O_# zEn9-9^d-cP{Zn$y#)EW%&@E9EpO(;0>-}#jEZx9<2$y&3^MTTY9KwFD z9+LwKe`tCTZ)7why}S;C-XC2+{6s56|Hf8*KON<(H+p@3UQhX-znwXn#NnbW*vGEi zJ`R(7d3T!s`&4?Z<=qkgm|lM(zI?-ofWsD^O7*1fyPnq%Zz!-KzI8o^O*l`dCidkH z4#%VawQF^Hrt?W!^YR3JoUj=zt(S(1cze}fZ`Ad3qR+4&mCC#x`F^B#l`9%@<08q| z=3NVK7WI&)u{&FZens-NV&k?1UQhP+f_k1$Ls2N+`N(${2>ZWqQNJ{M_wIANeSLAQ z?fXA}u7Sg(|2v1WL)4@wHITP+J32dgJ=t%sy!QR;I86SyJwMD4?@#`UO-J?pbStt9 z=8mG0Y~G%}R}Rh)`V#fm6=@ve3JJ*rqmk#M*3OS*-l<}KKh=A@o#J~4ZuM>gPtpqI zhhMNj2LB4AewE#48{XhFxrbaXoZW*`=cV}8%`y~D6tf|Q9Vh+4UkZKUFT5Bu1B>V% z`>JC?897gGP_z*fTVk?!?u|HqE=I$yX2N>ug9Ol7GA^!YjK%6h!AOxQ>;( z%oAr-oIZi`JjSl*`YzdzvGsbq9_@F5e~#OO6#r+FR~HF+Qfs@%e9gid6QA!Ti(<_S z&gXJaxv#EY@+Ro#&p^J#BfYDTwt?dV)PEMs7Ej-?VV3L>XU9#X0 zg^OY>Nn_%8S;|)=eO#WCOKqu8ykcg?(@$%VX5aL!uMqS^_H-K(Uh;Z^GpFS*aht$l z-D#_NJ;}?U5l6&@3eiic(e35%d&qC)AM^^i8T;SI&W;}A^*2NRSQRZV;V`X-p1}re z4wHZ1sEqkJO!~+1_5Qo2!(-p~e5Awu()x>n{i*vw?!@_|yq^5^5mRJ8YK7hx>|4N; zkGQ_bE$Efv$7Fen&_^)-X4yX?=66LF&a3K5jNtuwaUN1{yS|^~RI0bQ-sp)B)%(DY zZGDrIx!qtWzIihl$sY$YZYZ4{Gl>N>Lq567KV?5W>i)uum92ED=b7NRwRr1+(>tSC z>zP^`_LqBIthfDoNh%o$HbeiLa5byU3y;IDdqOQ(Kb%!&e>?>NN7{MHvhe{@ZX_JlvduJb~EoUp}Oez5nEFpdw< zA7hxYm2^9`sQZgoB@29mc9DLIhjdo06fSbI_8%5iaTxK2QtaiUT^uHP+4rrHpK_S| z-}36rd=9JoMLk7D947s>qM{;?!)ks&;|#H$PVz1zCr8-dMDI(@&#vU{sXl3UJNKWf z`>9yXM6Reu|L#d$A0quhcI)&*&#xKn7~$>7zxd&6-xS|Z=QA{Ud)M%K@LTC8Cq~LR zOwTVZ-SW%naOArc;(cwlMpNh15$=zNedCe({#MT?O8sqrW7$rAe?R#7z(!LEheO{h z_#NphiDd_czB1E+cnyC?NO~yBY3L*EiP+o|DrYvFN_Cao!}-y!M|Kq^D_c5gyhmTW z-hlWMr>WJuSysZP9E+DR&^z+$431s(|NUFcteRc+D2-}9plJJ!-@w#TE_Fm1!mvM{ zwGLtaM?wGEc|mLl{JR^2jY9gr8}a+674{>7VFN7O?;-lCS_K0kjr0Y_RIVQ?{$-Zt z-F`APj}6%Yq6{MdEFSux^083}kOS~v1>|y}?-D+<$ro=>Z@>@X=cMAda+u`xh;k6m z(+cI!Z9;-K9j>)`y^cZ-Q+zNB*Xf_=^XNx9O!UNfzDnFg{_XMLS~tI+@byrCneaEN z`iW8ZFOt0f?B2Z&-agjsmJWUNkMDCB`T3+@R|dv8O#CG6=?pK2>G}H+-^O9m_Z8(_Gs|en0KUR1$Q3n#R-T@M;r+M#Ntu7x-n~ zp4JoO)}-?sruv=utZ%sS<%Q#HWANX!!ZwU6$c(g>m{fGunsrE?t z=N{4c)gGmj^s(;5vv~gK3sKCzaa*#JMbrS_MwYqROQ(Z$z7D(SAL~R!BF>aLJDBQZ z*{)o22%yw!`K)9a{y-{(qE`L~{d=|iXN^Fb`k<8*Vd+%&_ff>^5d1ez=B=q7=J-MS z`g|oP$YHYI;)eVJ->}|gIo}?U%j?M=^+Eu0*uKzc_GI8Vl29Rhv??wShe`j;uWu0T zsow9(c$e23!Cy>mdi)^K^GoeN5bbII0gF6b%-c6gkhiid><6M(q@!uz^_0H@8N@{Y zpx2nF?c2BWdU}58mdC{XJ)n;_F6!}ll)rO4P}a@cQ~liji-Mj=zW%DWY6Y(+dRy~; z&Rh;_^teFq6WZ_UpJW17`5TK6@2j?#RbO9}RuVz>Y2%hrNsObWIo$d>s|X&ef~&)?0Es<{G+r zyNlLmy5T;UQYiaVN;2sSo^Hxtdmj6XmK`U1BB6fv-F*HDAKeKTh5k$Setk=ekcR}X z_3HlqSPRad{0AK$$R22&mQRG_af6&G@R?vwiyogq^zM50CB1*(TW@d0MgBe-zM7}` z?~#wswP`l5SNX${_l12<_`K@PjY8j5>G!9b-r(&?pTT+ZE)G-w-V|F3hpGLFULD`v zCTP%U&j|lkKk5-r!`<1?zv0iHJrI(owBF!^LN*oP{Zafa6W%nchd-rDw;yP{S+PHw zx2O0ChwLBc@QN(N6OE0H2^jX-rkyh)IlN7cS6rRJVbWJxe=EOHlv&>&iSIRCV|EIA zi1e$f@qm~ge66XhD^~N5Z1$=7a627C;Fr6$Uk{8T{F(11|H1Y$$;>3mXA5_eJi7CG zmYmY^`mB5NOvuj@B<=JT#ZUQ*ZzvbV6kGl}B^t*3RhC;B;DxF|l$?Uh%WBgZ9g5!JEW5o z^le1@w>B4*^7fF`F>4*MVm*@RE48dlm$z8Iu8Pyw)B0nq?AB4wC4N8APx*+hFH=4O zWnSVzUQhD+U|rk*hY8=y0#!oZYVz14{9T0qTelzVUkn^S2!|9 zs#E&pu2@S~Vh!ZIR!$e6O5OAgEf+dnq4m0)A@^jc8vb?o=Uesj2?~?*ExChz0$<60 zRpIf(@%CiDUpn@Y;0Khy)foSoXLvoy*Va$w|CZ`f^n8INkM5ayo7g`vfcT<7Z>s2@ z>Sx}e=kFo;_P-XMcJTgH{oC&n^i1?7%UK(EJ)Jk}e)rN54%7QAcj)b%vU7C$#{0)?M*;=jp7@7nf17wef;0E(FgS@9=*-WA>kdUqCVVTA<=s*>!H5E212Imd z0HnPiZe$D~d!#*ncGu`K?Tx^%Och?Y`xdjJ@@V=V`NbEe4D3}E2OlDRb<$fSnU|X# z24y$#kJ6l+8grC67McPxLSKF8133fnMtBzAAL##!(+8yMnK|uk=M?!{O63m3OKCpz zQ2r0(MBJ)M@D*7&FKB^D%>T($?*=jYevcpTuGNLdfr- z$#*zxgZ}%2NLj@16aVmC9y!MANj^`MPwDzT=&5w1R>)7HKWOg*yuBvBi&{8L_XnzO zQgMy0srK)*eC0&{Fn{ZKdy0<>Dwn^;VLC5V`Ea+;2PmFEUNcm~>s5LF$h8g*(|VoX zKksiiT$o&7@#6p|4%2$2tmyU^=;$UR0CynkBn za0K<|ol32>9h}iD=Hm(YlhU#$wePL;87Q86LFDP2J;+W@h43qb@*Dl7m&ddLAbmaw z@*t?J9AZxEK@;emC2H%z!~o80xOJh&F(Bj-#m7WUx`ch}#rmH+SC=1zuYJ?(U$Qr4 z1km#NBYEU))#F3lre>QRn6PsuWsjiq)z$WitfcHMg6Vt$f4jaOUbqPP!rW^$`w{iz z&(7X_QM@1d4{^NKH2D{6ab;c+_(A&xW&fQ*|0R1QH~Wg4o<)V?4PLtDl)!iLN9@?& z)~3~J`7>I)Z9?7=J#2AZI>YOUp4PZz!*uxlq$HJ>(zW(tqd6%l`gvYY`|;{eIrBJ7 z`0Mxk@8&S#mu!Cj*sB~S`e$xWr4GYha74&D3=IhjI2=BJ_&oQa^P)bkxY_3KTJR*V zZ}~KXG`q-0)~KO8!{cFTaMrTPdSa zE$Gd7ILB4Re5~#j55^btm!Z5SP5wyjlI)qT_93BPk$o4DpDFkw@vFUI9uI$hA?UxW zBR)cfM*}y|eoAIU{_fF0%p9OJ`2*Z=5J0U^3&s2VLHUY9_y3Rm$Eo>8$bU!iK10f? z1N5!l0?e0>==mK*N`}P+{lF7iFWdGOla^q;uutT3FC8L$X!W$-IemVR52v2POEF(t z&lVSRnB?adDt;yGfiG+Zy^qb_$?M5JXq#Ls>;b}e_eGzW|60rYw|gVvg}hefr`#a? zb>xp&_o04%DA^a2IT4Y(f6$wyWw&lWBY(MhXsA!nAKAO6KJq_lh3x$cNch8Hl3xz5 zp0BVF`FVEijuZTW{2it8q>$(I{i|~-gujsbpJ5MF@&3->yvg;MiqJ3U{e$feL66X{ zl5^6N1^+sb-eaotdW5}B>$eKy=dn<}5ZDsa5|R-8xQlfAg7p341HIz=W;&oxxuyiY z?E^oBI1uXrJuw5)8}~um4i)OJDN~QnBKzg*;p|!^pfxy^ns3>inAm;&N-akSou}*S zb9=9Z?k9e2Pr5r+qGZJkE(ykJ~6+zd_yo z2Y)Lm7FWxkb_7aTwWdGG(Mhs&{<6}3gfYth(Pea~`1Jbg%p8yS47lf1xc98tTn~A! zOirlqN25JA9!N#Lu6a9DeRxW-tvZnG#9QdE+$gmub zr}dl->vMi@`%8L-IqB_5f24fHfvK>?1n(nxaGvOQ>!fw)r&#~8vdQ)i5sygzAt(o^ z*NPGGY_ELiJ<4IoH`8Ce`{cCv8)!tv{YAW<@UwEbGl9e84;&w;7yd1xH@U*p#Oski zUBY%jL9g}D?>ATG3VY*&nq;#d_{rPPs_ij-J32|&w-Lyv_f|%E1+Rbab}PG+3s+n` z34fY9sIS+7x=gE)5QMj<{V^T0J9cxJ;(M~Q_48EwVb4$f=58CWC;v)!cc;+TC_d-l zft@pW{ks_7i&v(`IZXB(_TTzBO!PRRy8~4ER4VcW|0R9LT~n3I+f#fn@1^!O~&@6P88u^*>uE!Gtn z%|#BJJc1JxaGpT~*^~0>@;t(wtL4vn1l)869@#H)gFWnW2q9>_EPs2PuwRKj-`iNu z>2ouzVbhwjFejS(j)oc4{pJbn74qnn8(t!B6h((vzto`ei?|rrEQ(w?Hnfm*wd}8)ASF>^QCj1;`P)%^7-*94%2uHl$C|N=M*2{HeEX0 zBJ_QV&);8P6U*C^zp(yHmC%pq{<0>W-_iQXPDh-e*B)sgCN)L!@cZupzdZct(FGh< zkhK%E=kd36C-VA}*w5)I&t9#=u@-xKrLcG6ir?h4aT3*KT-vM*`Anh;ZHuFR{Z^+F`phd+0qm#81aIYeYj(`#(#j+ zrds==3*%=?s$LmYX}SpeA6ysTy1?;0@|B1e>dYx39?(>ae1+M1{LMX(&qE&_g{-1- z?vUzF-&7mY4=$^QN`$kBKkbx0c^e z^jkj|yM@CKA%F9@OJ83-N$ZDlW;w5?d|tiJjSK!^v^1K&Ir`;pUQhdj{dS(ek@i<2 ze!5)WPk93S6(=Sug#JMGmB*KxtKZ*fS`ZlXbC~j59MSY`zpJUeV#2) z<9)JM=kNeujucn_y*dJa(h-oRtWzH70LyWnN0@y?&=08Fa%D^|4dL(9w)7LW+l*fp z_#E-VtXg<7qN)DL;OsZ!YH@x-_9_uSNAf?+ShFs$L_5K!=l`?!E>KOK>$>nHf(`K& z5UtW%WGTjYfhAa7wpFxX)wX*7oiS6z?Vg<`|V%o zpWgU~^+dqf1co*Cw(f<$TGcw+okxGVp40E|5O2`;Pv!16%<*okZW)>Tv5X z-e=E@+pnNUA62f2u44=0Jbb=Yz5J-VzWKw$beqi>^H*t`0%a4o((`T0?$Zm)BVFQq zdpfYb5KX=74tGg^!IKN#U!}s9>_^*t0Wn8@HnnG_E zAKDtXb)1Z8PFPJ(a5A#U=KI3^0kY>I%)BFlKRitJb%PjjUw}pX0RdyPJ7ciE$Z&bd z*JA(COfNVn8b!Aax3|#!5*fW;=#Gxy%{$rSD%lBn(hC;oRsuy7h$3}k8A)F>>A|?| zKSJ8KLjeDP^@K{)yTbT{@(Iod_lbyc|5yKHJ<^->&FoIhAH#-uO^|o_c0Y7a{2RM| zi+CcvfXuDWCqMjbQfpIN%s~F|q<0@4xc?-f{Xp8TPjT0Og69om>3g_+KiKmt&u8@a z7tZT)2c8GUdOnptKo?+^Vz{-P%1olbXudY_&W5&pW{9Buvp&@=eyTe4BWrCKIRi?RD@5IyB_|@^~|sp z?mY0xZd|T%E#C9*4OEEwW#t_jo3Q%AeNlRU8$(x1X}{Ms)z#KS=O^aQ3|E7g90GHi zT}Ar`2{fFbywb{F;D5=wCFBY`eChpxu=l!%$Orp_|5sc0e}XuzyA9oVqCX!w?s1$k zGm$bTDQ_{oCgB zd+z!zQoJ8vX#e~RdnWztUoXJ^|M)#}AwD=X@O%^I_tBPt`4|4g`iJv^PiQ@Vpnl+c zq!Z|V^|Ix4f54As(1fL{-S{NGGJmYAwxmB^fc@QjXZZ?u{U=fxWB>SH-|srPgrDoK z|CFBJR1~l3hmZA&_?Lm_t#N*l-8S&Nw&3sT>$BbcL64bU;NZ@GM)pp-vF`N(>|I)S zr%>mvr+C1JyHo3M=jAUSo^*2v#|!zAoP|v7PWHZH!@&K6 z>tn7-{h}NT-1><7Z{MGs66emterbMBcjsaMFE|eO-yvC%(6+ zSJ+0o>#;tk>rBF017&C*?&I`#*9-n4t^6T(9`?05IjMjB0DmKqG%t78!#_k`xRc?| zqrS37Wp?LrAC$iR!2U6>Yrp4?C)yB?)8{7o_;b1&-z!MexbwaHndAQO{6t^;)4!kj z1NC=Jb)WC`KHpR*uC1o4#~i&cWY;3RpQVbG4^<)nH^%lGzy>{$-Zg`j<%j>W8 zuLlr6EF3@ZzAwZF!JiDQ58zMOKfGTQ_P%trmxmi3{7W2bUh2-{eBh&T*Zbcu1b_AD zyFd2FH%I1QlhW(q`~4mE_utttuztb*qgo+Z*p}XK88GpDmdeEb7=EMod_;(660_#I z$Lo!6?zsI0y-$YjZ%(MCmsKtHDS6J-c7x6%>S8+RJ{Dd-^)j=a=IdJeo&;U-qCB1H z)BPS1tv71C=8s1n_MYMzIPXI|uT;_Zl^^^baNxhp0U5nV8vEf){%>Ua*Xf(plA>qF z9rCFD-L22K&$qlu(m(%Rlb-w#U$}MG!@f36*2&#@_=hxoYQMdmp!)jM-!{4H;SUaN z^Rc+|@E1OR^Xb3;1AmZKzq8z3kME_X9T<3?S48U#`y8MC{XN0nqL=lb$H9Nh?-+PL zEAIQ$A1Hs!4G;0T<+rPPcOLl5r*;$y`L^1J>2!f%o7)k;v~45{#CbcDnL_@d^-KUw zvRGf&Dl#aqN4PEInO4mIuxCcF=X+$Y)f0PXWD#+9b-YS7zwdk>`)y{5A2cGg#*gj; z^#1YS{|6lSKgI!RdGAorXT-BTuJd#wK<1ikUCFq*z5CC*VE-yraeurn`A9_h6$AT! z^n6X|+Rw%g*wZJ{UtWCjyMa9I2V5U`Ujh6>ef`^w?t0Ac&jx?u*`KHR{bWpU*Uf6knWOhx_!UL z<*Gl8{dU!xQ}2lAEX`GY6-}>WS#NWxf zf%kg{CJN8vjdJ(L^8z@pcIOWZ`+>^-^*PSpf3(?3-Sv1M+F-Fc-`kM}YE zm;ZiqhPxi$XITI2O_l3m;hEmHt>a`&dP$ILoR0^6VJF4|(N2tN=?cpB{bY{xLfXd!$?~Mqn0`A(Y%q)6qYVs>Yh%?n0oKC zX&LTz zES51D8OxRAo9;24OqYDCjG4jn{2%*IP_PDfenDJ-Ps8ZYDHPwIbxmy(Nj|zZ=D2SD z%d&3f5J<;;`(ZQaeVihfXg=k!-xinh;JggHdGPyh%mG+y3M0dOJ&Ha6+-pxguC0^A z*(!5#p4+~J`@bF@9t~vu4IVDfFAHV#KAbqR^8fJrgt^B2%G?o?y^{IQ_+$StX5jli zu>VO4i6VpfEAu!rjENvfbe}ld?N4L`RyfaU?mh8B{e(JUzwJIfKiMwcn!se+X7u|P zarT5b)hLzfuk>u(P)5uw0K91BgYV`22k|_}KH$K=a6sB?Us(Ube!5-V^o7*>G(RVf z{q&Z{umSsh<s<|Uj)D3#Iw@Ale35kUvjiFkmYF@vZ1K7)4j_B^!px|HtQrvFd) zJScd;fd?G;PjleDE7gI zvmlZ7D;ezPpY{j7GN!R{^Mmv8e-+1r+yf5$mpIVjx<@yJtfy}1L_S-y3cG3kr+-Br zNn~%4OVj_S=U27#ZUOq;2|4!%a5ALu1mqhjd%g%%PuyVNuNk`G&sWQZXgM^PBEbIFlS1m+z4wLfQ$~RJ1 ztxJ4!!=IG-2DkZ!c$fMFXPl}>HwF?aCHfE#ALYfi4o|j`(t~7 zuK0oe0^N6ve7kiABhJ+DO5W2aB22@l@?Uv+dK)Pp&Uk#J;n(wLnX|#jdrD6;nrb6o z|8&|pq~|);H&8iewTM;=`LyS*p#P)c!BT?9H(FR9W>BE`#lQ1o+=#j?YcyG}V?JY#?8)tAI}k z6EX~9hLw&vQlP%f3< zHX$|^{eK(fN$Gn2_K1+40}-Y0~{k3AHdgnkjy zKMQ)~>FIL~<9Q786!gh6%;N;;b+Gb7DEB!YO6^8+Vt=y(<9D?-*HAe(Zi?i*P#=7b z>>`A_rpwer9@iUB3ve}6!QlqtOOms)kjCQ_TM8CpeE(RRAYY}|;I+VOu#eAL=vP{U z&rBnK24`MrslBO6$q9K?UtZIf-bv*n4RkO@<&@G=iNaNjfDf{T`qr3iv^@OK{b)MP!!g@`_aRzD3i(x`a_YodNp1u>>n~(Ak z()u)pY28TWn)p#deU!AXJ=2%=wo_@8enNe{>RF_l>WB5#*Y~#5)Xx;)$NPCnQD2|h z*IyIa+b&HtPw0PfR4*Qy<3c&c7s?}s_NMU~rI4oy3aRF7Z#zxA7juU2)TrVGJUaUD zkEc|i+svwSO=q@7vko@WaAxbdQDF?yc~PlhsMlvjtU^AEpQ)o9Ml<`Ep?ojN0lz;2#DZt%S*9(8sJP#qCHbW}(^Q65cw43o_Z#xa) zhx{n1RKQcWcvTYezxnl!Q%B!KLcTttcbxfscu=tDg>ntO6anc)DdPb=EQwLE$j^&- zOz0P}%!2{lJgZ;mEUg ze?_UOn93FUJ~%!?8o@-OpWSMudcc?VmaCM#<9K;R^}*|__aZ)&nkXVs_O>6@+fL;f z4mbt={=MTyF1A9v0yE z_(_p2%^EL9ib%iIlw!y)Fv|efmZ#96e3aBrdepktaQ3&7`=zFRrXBf#hX9vx$P<9a zcvdX*@5L+9;N?YdtosaAQ}o1_Gh3~m<2?aqJR|dMC%8#1mAlUq9`Vs{jRQW*{_*uy zq*2DWb4MwS^NAAVE|tn;rKa0fv4=w3PJF2S)OGZ>HP=)Z``kv1ud}RlqTgHJ)NMn3 z*PK0PZx@xC_QyP&bh{|U@a^2Y9|`pSymKAOZ*1IdMSj(*;p5SNN7jxAlwVE$+YXd> z&8S$1^zEDHrAXtKY}gJy_&NN?5PVZp*=vuWJS{cqn|AA7(^F^Kf(1PEi_}8-rpuqV zQ~joSF&yA_R8Lg-a-=8rqM~mo|L$|D2(*icA1$O0kKF#)Iik;hkv$OQQ$`&ckMyy` zWrM+|e5q6_(0zONrzqdPEI$!x)P=GNjLT+i7x4c3O!GotuGFMl;8=kAzh%CXCBXS8 z^~rNYae*W$KiAZ5RYk+DZd=D%3&#Ex^s85`0=_?pb`X7~rZEu-5~Sk!nO^8G{#-sE zbRL@(^fdBgGgp3rbVhEb57Gsx0YiYNK5+03$Z4<0vJ&O$_qQKDPie%qTrKcSjEagv z`Le8sNq#iW=7qD6j`NgdjpW*`E5+Um{V3g^I(8e<-(v43hSK~+c2_x)=xVaC2}u2& zTI)z#sY$>0hb+L2SQW9#kNeeH*>iUG?V?{zch7W=2&H&BZPSHcLur1x&~te%^)tww z4*l&SYG1I_igrJ@9HUi7sj1+*`vqvfDb276d|JdN+tJRc9Orx6+HU>pN9&I(solkn z^54*|BbNWiuheew{-iwMJ)vMF&F?7&dy#z#@M8z(s*wh&MgB;$6s$MsAU{-$_LK5_ z7|?gM`iFfK{g}=M-wHuH2RlYQk~?Y&5cxC6=O&0LjWo#j1^c33!U9HHY8uI@{8Z7% zdrK1YN7^C{zGH%==czm*$u5-7(*`a?{l3LoX$bgNF)JVZ={a200{p*={b;#>M;Ig0 z5P$YvIe0ac+K+#4ni}aHf9p>g@T2aD1|@&q>iS1L#a)Dd@svRHTeR1M@VsrECrbS@ z@buK{wZL=R_~C-w^3z{`0_Ds+MhkemE?Opyw51qw1`qW_`GO%bfxb+wRY(Ka@iOpZ zW#B#q>ZM^~(!WrSU1?v0{1~ZpI>sH%>E2+X!&LG?Ng?2_WLbgE%F4eHc_;*Y7O_`@2& z3!Wa#pgete`f9-O^c*}K<;?M+gHS%%GCmi4Cp93xquz5H>W?2kF3{!cyDt;%mdm-x zklV_rs9Zm8BG^)9w8Jb68S`byH%9`q7>uJc51Uh=XQH?0phl!d#6uF`JqdkHiY{7 zB?c}+{h_2Jr=b6pd!eTW`53Vdvnyxk%`kmy`Wuc_sy9W;)8cO&Ni%>;{ za=#W8S#%E2Z&WA60*}GO0JMK3SKbUb>K*EQfiCGdqKoJqKYlz?W}%2dzgSh=et|A} zzyf@Um=QI@*XGA9Vi&Lk-};cAH3P3%GN~Ex_A6{G+C3Z}72#(i{zN6^g^*vB?v)Dm zvE6gbK8(Lz#Ol%BzQefyaj5UUeFexLN{X`~RV3;6BVEASNe@W>$5fKv3+0kC)htqn zGtUY7T{W&+&`VzfE5~>)3MD)S5B8+_hsISWjYq$8 zng}KGNsa=+A9#vAN#EM7OD#JxeQkamo4n5sIQByYl=^XbdONiXwaLYd6yw*n_7MNc zpE&a6XjfogWCuL5$*4^rLuWB)5qCC&pjN77*V@axq>?A3sW?W<*6G?>A~~1mNu6v3m#b zNKR5GA!YZgjYysOb|d6K>=F2*4m1jU_IIlHqrWmDeiYI6s&5tW^ zZWHpJ#f96^KO#z*8ba{Dku~LjehSAJjIS;Tr1`Ghx_YtWFUU_SNM>Q@(zR0J5AnHx zrFok0R3_)Q7m>W3R>HGa{-B>-%?kWh`97xvUQ4{99s=Ib55khm3=l0u`6$nMYC;Qt#hoe!fh8zmm0>Z1^YJD!yg8J1?yTIRZh58=^`IDno(mcdYYyJuOD6L40 z_S=3Q!U}%EW;6nisKN0u7;o{uWWke zym>P#lPa_c|IBe@_K!5Mzjkb73L?%p9GA&k47b=UOs*i$j^&JT578B zf83#@e6%v}YxH+U+XVlXO#U71l<}j;zJ>W+pik|LjzzoutMY_)&Sd+7Uiy3W!*51} zpMakYejRq2C+PoSpTmdX|GK(r?+W^GNKuG*&y%ipgmULhihT|}#*5JS`5=w)QF(yB zV@Ho2jl9%jo&eXDw<5J8_I=nUwa16YbSk(9-XX$+&+*> zY2S|G{Hj((D#3YcvMnF&jjQ&nK)=6l{+Lv%PwM&UX(bEodusr+s0 zzSb%^;0*WNHWYYkw}>eo2;n@WLr0F}G@Q)5jpPma4;}gzmDju8?f|}C^E|^aPONc1 z>0`Tf+?GFWMfr2e2%)@SrIY4=iU-I63izv&;!yAB;S-Kj>J{k+ywzvdEJ)=;x%fps zgORR!B*GKvC$vAIq;Z3T$-XJS#M3VSWl2>w%411Qr{?tCHuP@rqh)t#X zP%h0+O8c83Tv}VjA^3l}R2DqarsN&Tu?6Vg@S*ruz>D|ul%l=z^M{EKrKTO8+ja=~ zkHUr`eK^v`i`v=F-kLQCcnl@~kNwctB()xJx_?DCY&RX4-DMsVo$JE|Af)4o5YLSC;@Y53RHt4iqX zk>IHGbUuYVOL?Snlv7Fr3iVplzpy?Y^=NPI&8rl2*BR}pKf&9m>J-xTs>(ii#i*zD zg+jZHfG>l3q9(Di`u}jwPfRw-1kgynv6T4?c|}&|v{RbbjQfi&BAZddva) zz#BjNH4Xd1w@%y)SJ6JVUvjuV_K$rRdM66|->q%M$cx8>>#{m9coJ}XX){t=_;q>kX8u9}h=Li_t!I}8QF_@*$5Q#8DG!6J%N z>AWHQbK!jA>wVAB`Gk`Hr_Tqsk%mPw!g)8JAKSDTHP{s;Pb*inh^(YH?9eV0eyo66_$dO7RFc~aVQY0shkO>g0Rj(?0CDeycy7kJY7#dBey zJjtV2?@xL|=Qkc-ll+$O0dm=&(C%qw&@G|d$0Hp1kfF?J!1H`I#RKDqdxnQAxia1o zkZ8dLXMN~=jB7|L`kCshMT*rd;Dk#Dg$a0_>zE6<)_1)4n36a0+Y0t4 z83~WKn@=`k{MOSl8OED?i=p$YCH$E=!vh}v2l-{qHywFYZmB8m0v-i^%D6)(>j6p^H7Q>^2WIR!nxAvK|@cYUsY9AD_y5N> zvX_R}yECX?^?aFJWTgK0Gq)`Qo^*T=tfBOCTGk@(IdUZB0pFAOn#!N1gIAO@Qt9)M ze?!LE2ArFSWlTrk`H8A8B|j|82SPo$85Pzu^i+h(D5Q+)tBt8V9dldy+Q*}u+@C6y z=4)zS*OwBUNP|e+du~K-%MCxiuiwR9xc%jtzH*_RR@OVNoPqxr;KuyN_I-Ge9SQY) zc=p31I07AR`rL2?_%bomFo=-FumApm_7C?D`u=v`KUnwX%D+udTamKn)fLlcJSltS z&6Ky%^*{gXyMKUpFbek%w6srsH=```+Bg4>^Wm;YDO|J69!qXHg` zobW=u;%KX(n(!0t108jYXl*O;bp^xp)78g^++I4xrM+o@n=fuY5WfX{BZ-d$r=RZw za?uF&ZaKL5tK@=zVwh-^TW-PsK~BBpp9pe^QJp5ZOE~6u47Cs8n9~_1Qv{8En*71}Ei2Lf%&)wghQhX!8Bm3`7`{4K13;p}y z5A+`xm)dm<;NdP8@TE8?6nFL^}vt#wW%)^=pjD-3w>1oFLd_i1%3&1 zy7|)IPT)&wKc9Q+mHqkNb^@O)T1J3F@ma4t+<3(F%I|-{$A6Vud~drX#AzYiKjXh8 z`ZRn3Yu}>beQm`klSYje9b?%fGur_;EY5lO$Ldwa#p73gN!7rZAqL zD7hfR-lsuFyLF@Ei*BSHu9FnUjO0!ni2qK*uQde8y}dAwy7Chd;cJU;$ZiHb-sYuJ z%x{j)yK_KCUCVki=%2Jlp9wl|vCmRGmTM3>I0fKdcAcD$`rj3kd&o`=<@vfoC3oI* zB%qz*HNtB{j}!Gz)>O^`-lsNqWC(Q4mQTfb!zG6DLMvuZWc zaZdaG3&fYg(qqlQ>###~5b@eC-^|^Oe8=%-E#eYwK|lfUJ=!#P1>k1N^a*@5ou6pU zBt<2+cV4~-@=Q{B*np3t;<6rjeV~^G`6gEXB*wp*QA2boIm6%fQXJH7l^?7WXX?$$R58^iI&tZ9(fpJu)L zF68Gg)13f*n;V_ogqOjaHB-FA^R~Q0!o8sJSqJtbze<&bdpO+iL9+ckwO?H@8F<+K z>D{~;`goye&rR9~$YJF7Otc@8<1J~t1-ufjhkQ8FR=ichjGvNAHL=NRI$uz7tCVVr zOXwWXtYCrP0dvnW;L$Mm%OKGA?pxoM7^z$o*?pl5eOPaEWLz3dpp)n(P%umoCvg>b7kFYOWRUmj?OojDx(1i&9uQ%39gQqzeI zw{)au2GJJnb?A#o=IMie>DOmb+)ww5wge<%ybX!0Ah+*J<{ktctT}8I;II;&;5dYeFZXonLKbCGdQ@rl2H*G}x(-Wud;M@OB2- zoAuH8N7xWsu8Ccprw!q94YGiQ(ZI9kr>Z}}?(SrCOiSfQ*Dsk3d{B<#@^V@amztV)9hE7$ zTvOFGGmV>K5Q~Q_MXH}vK>VbA&LPRgkiUO)H2INsYj*9=3drTk#rS&SpDDkIC4HcM zm&&~)k9KQTOR>O*$-k7$#CS))V&6i)%ex;Xea$sl%uTc|pmkF5E{a#%tvMB!E`V=A zyDCcn*L=0^D$<=njv~bM4*d?I^R~5FolVhvscG`mX%fKG6~HXoK^I+xX#Y-eH(?!Va9BrV0Sry)RG;yX(yR@C?eY-aTpOsXZ&oK7#Rk9IKB4 zulxX+0&?2u+O-9AB*<_-E`&??=gamnfCFCf3ts-90q@C-<(@Q@lsA1tw?y% z-(ttQB)z6(C0+f8~B>M17w0d$(t_|AJ!V!Y?j*PnktSn9@IWZ=gpA)l$tD# z_RAQL8}8wPA508<;{fu-VS^Op=M05TMh5-rx!InAacdNe;J2!WOx+K@$1T*yk)Crm zE>1XwcD0pLE`o2an;lP;Ri86mSfi(Xg-Fw%ycsR@xM)pRmoP3Pdk_9?(~1pWLXRv4 zdjQ}?hb=7wzE?XZPk`L%WnEKK;UDH>-;C_lLr42Fq%W$ZEa3Tu`8e?-m+Wa+AmL}w zg)yWbrKXlimPX*KWn?okp59qzM!N*Z6rG0VuSt@Hy>ew*V^BX^;blbsgTzE-*yl3YUw=Wp1*3ga(t4v-0U zQ~gK{^r!fugu(dv%Lj>}f7;daXdO%b=BMwwA>U(hIvMgkrIU+6UqQ5#^1e39w8}Lo zpWT#JiE{HQtrg=H7Ia!re&`&q#hJUhNG%ZRq~^Y4rQ?9$SoYXbt&uUt6c>s!(1zxz2@tlKe8kKDRRIEx8{D?S@*$02E7(S8sKz`l4ll(%E!EC6QN$@#G zphJ*GKybw4VmM$g5E}(lLdV|@#$~muY+@#%D^DdsWK~6 zfID;Na4qg}CmS=3q%Ve0xrO|GyY+Zg{Oj-&0U{^q%}Cqp8;vu7@B7!^5#sg2o?TO6 zH*ACY67Ve?@_rWjwcOlHdV_P^R|)D`lSuUA8YBOKDW7Ha@C#!~G=*rlA^ih7|D=2L z5=XLu^hEztoB&6k{l4JOn=|e^K%adpM? z+6L!*yn?SjZ>lgSP&vi*nF0C0S8>WtdKSXTM(HOD<0x+f5vV{VKT{Ip`L_#g1EqT#ca^WJC@f1w(!u))tb{xV6gr1QGAL0_KCHPDWu)1eOG=$^EW84UVFtK$5Z&^SxX zB03K$HBBv^C5N25Km1MuKFz!nLwZW_Zj-YIdDqt)DV``bWXhWgmT)8J9<`US?(2A_ z7W!m-GMcT+vT8+KRgwWbsFOD4!EPM~?BrAJf$7b|Da?;Wa z`|%cwJ&Bv#J~os5BGpGO)u6vfmr{|+A2kHXC)+XZ)xe!;=*KA1dH9`s>9s7;!>Pp* zn#W1sz8&vNc9>%uE<@aF(l8chTQ)(CRe8Z~8O34;;tO$}+yObXY}~jTcEn_5D=^+N zb&BM?sWm(L;xp!vwp|;iisax7 zRPD45%{5rO<-uj-A33&?_)KxuTM94Yo9*J{pI3nX){4%9NV~3mpN)E_CT;`hQd_*~ zo=hsQw>s=))#t5CJ9T8AG=H)+`S8mhCq#CjpLx)xNzj8QEt9XquFA5%VAdLzaEp@j zwxhg>brvH|t9kaTgKMipZ7WX4T){XKvSTT3$TfW1`Q?+)llPqC^k~0N*F^II=~JdU zldqRDPeS%DK7D;qH0;lzrMfM@VtxdVD*q+>6Xci$-d z0(b^x=%&N2rcGqTu$$!>8yo2~lRN$N5W$Wm;w<80aE_Qs5$4f7EgbnVUt7EOAf5B% znyU4BjBD%szIQ%fEtQ60{#yR7lI8(l+on^M3XI1U26aLX_m(+6h5VeKXVE+zoKtvn znusSmzHy*!80N3I6NPfft#Gf*4EdTHWv`(9(*+I@_{mmmCV!r5kT?_+e+L^98?_AJ zA5`Q?@(7Rm_?Vs0$K5lt=cLj-2V3%6iNNDa{cZ{TMd-{zR`B=BYH#qwkC-&OC)&O0 z%%?aggnRkrF9~0|=d0i9gj}ynUXBL;NGkYDir_zv{Oe59E8NApQ}_Xik%Mcd6Kq(@xwwAx<)SK+Yu zLq2x4;$tDtkg&sBuifqXt1{Uo(Fz8ZNz$tO7#qam+t^2QI9RBx2;iv@k1?3`+( zMTMQh{=?CV)&js4v+PR(-!@dze%N`dIPP`Y|4!wd_CUe@YX5%o;1bYRpo;}xb==5sXdrA4H7UDnG{q_^f1wA{! znz5eAFy8LO_($_CnOF}5TCEd+XG?QA@h{hOIA7}^`I}mAc3A*__TGCI$aUxN-hI-H zhKDPGk79QDF36$v^&L%U=P2o@LEOq-Es#L}+SBXGK<`V5FGWEf`63DFg)f)hS|aG* zD1NShfBSr$5a%STVrgCZ*PKj6Q99u69glT9&97s}PNne-duswRk^eSbAu8akLkxz@uj@LR1{5dSHznC>m$ zQ^9UDBi@`-^2SEstJjsh4}Mn;-zG;p3nQodaP3yFX>W{&omzf2HwtoTQwVz2xUQMx zL=>|>&}@kHdV&mabh=KmyLRh6Ht0vxe^j87K+lTGS}2}5Z!K!*c9qh6XEgfLc}S2! z_GzSS3Gw^H+&t3nT!US%&w_sJkoji;^GA%HEQIG^?_iYpHy)qCs2(39mW-su6pQxz84&pU4&%_c&A z+~vzl+L6{yji!Agiof4|w;gm$Kbj`Uza^tx;CJ)OJx>Yru4@w3<%RD#GE-?hk(Txc zX?@vl@eq|&Pv^c!*zj)`NN+E?x<3b;_d6z26h(0wyO7p*O&NtQ2aVZMD0sWPc!1-fPZZD7U=C>Q9^pzZe8`Ax8VOa6&tT9qz#Aia^LpnJv|o;|cb zdOA_6NqeRq`Mm}EN#DqxMOT{<_ZQoKJ`8=jo^n~Rw|ee*^6#|n4?8LYoV#BdFGKzk z)_c5)=DFJ^tB<1mO{eRau+P(S`yl3HqiU57cI9&i=V2Ea7v@$$pII9# z$GpVdDLw``v&Mu8<7U6pd>i_F@8vgyeb<$N1+mbdYs=sN9rLZiQbKWiyY;ihn`u9r z*4rJgxHSAoTYB>3|fBMcqi z>d^lB55>?!-aAl1^A^o3cN(4o{71aKnW)b-+(lQ$x4>_fPE-JRr*3>f_ULQ-E+B~H zPWzzS59Z;#V^C1bPUOvwK#gF(2eH3$)MP2sim2Qc^m^sXfcr+Xh2l5Dud=-y{4guG zTVY2pp8CB3{fyJJW|WUpsnxzVCBNOaP2kg?R=u@FL-EW`E`!!LAzVwtR2Sxh&TI~O zTX0dvUC^;#ZyAI7?{-!R`&v^bWRV}CIFX;GNBOAH6ZSywlcw`!9J}$C=Cz-va*ruLSFNRcS?e#(y$Ja>XzFikNDp#qyl-K>o~cWD4}R}? zt~;iT9XGayK=@%dgyCozbLUI{&=yO`KOr#6xHuPkdm)1i!|JhYf*#F^Zel$qr3D%HOMeZK0e>k(>v< z$}Q)~PTQ@+E4gOacc!>di08FGb&_4tdkUm`Jwb0%TRHaWxE+cFG3Ke7$=9oZ*R)Qx z;0K>dTyY2TH_cf^(3ISvIExqJqvIY!!yq5=UWWWR#c|Qk2VGD$L^^54>n!kYw0tbY>ApR7g4b@%sH{-%WcOUck-M<>I@QH*(XO?Ydq&Xb zG2uyoTP$;uzgWjxBp-><@5y6NR~RWR`S=?P+IzF?0nj&T5<~v^wpFq~Lh~Y>=l%W) z?JtLLog=aj0MCz$r{a8*%V<3&oPT7TI!k=SeHgVc-(*;_$zRZV{s(qA;CYPB_ecIl z{>&`&zq5ATtFQ~Hc(6C*ety=(-f`w`YKD9g%pwNuYwy~RgKxVEwF<~ra;kZ-P;M*_ z!MxG=#OyYdcTD-B%1Gx_6``yQc-^_#Fc<9}-#TZ4kNLDenePvK?MmHD^GCb&PZRaz_sK7;){>vdH5AlWD)Ol1 zg|Z*_fNx9fQ?sCdlS}O6$LSt~jU#_`+bT;K69#(Tn^{hJL_3-p-KU6OrsMBO2Ce&? zB9ItW1jdu($IS;F@i7?@uxEZ&iw*iPMt`VTm6-TTH=*b0+LQ6%^FyNMLdemqt)Y32o=5dqM|9G@tF4&mru!<9Pfmb-XfmE4 z`)#)d>SxV@{C>IrRTuc*{OuDrFmB+nS6Ij|La7Re9BR+}a0c`Sc=*2rIHE-bqoJqA zZuVS(y>uPaAB9|BZ+WU~?dM9adhMte|4#MaT)gAL{3r6q`fEB@vf_ENE7D8-lY)HH z?!0pk{r@rIXfyctw7Rkdczto=h;SYpmpOJU=xS_qt^u7>MTgQ*|8pVk6;NC=pQUw> zuT3=MN;&XfEh(h?=)ndL86$$;r+2v$(SBFg)M1cs%-U|c*GTjIIt%%`cI#`K>+WFQ z2-C_WpxZf3tiX8UL91xrG1nw!_L9G#_3Vz;5}xpuSsG|Q3*p2^pWXzx#g$inLp%CF zE6uy~yzRkBq^Gp+n?0=@dc~Ykya&AH^8+5od@(lKO!p?}e9;kR(NN_5#;U!skh?_T zEkRsv3N>Gcyy>{g8|zc?N=Fv(x_5AjurGA8>k^B2^lt5(%c&GUo-`+%2mJ92OYw!T z?dI_A-GJZ11nk9g14(vv0r33k*hUt7f2p!?EYeAA!8VLPVd_#I@{e(?TY+|6PrXm) zF(V9qW<~~hYYumQk8-VS&@RY-R9uvBF5l6Z)&M@7dnT6FaWt>|qoE^;?EksCdKdWm z@2#$CoL?E_j2!ZxV%{snA9bz?^?>6&N#~&Wmz!_<@jc*E7`WpH$R%G~e#@=uX7JB5xnQM{@`clO3H!bcotMg?Kdr97R>=QAL4^tMBG`0Ktapfy zVMtA5awuLqZ+*AeYa-}QetSGcFYVT2?^SoTTgl(pBs4FbH_e(XA?qc11~61g`%%}= zcVhgY3#~0ghe2PQCGh2ZRmnl@OSw+pABy_4e@-L+PV=3;=ncdJpU!-E81&%atx6W_ zaOO{qPSnr(>G`+8*PV0QTfqmh&4Wd|w4O@x4?%{1>Lj!e-fq3w{V~N;6#pCJFGD|{ zJT4OW9kZuR(EsVJ3x)W$V3Pk!Ji$G<$&~>7r?tH%Hd0<${BsHTSZ;EpNUW&*z@v@Z|+l;{Q* zfxd^oDJ8unJ&b?SkVkst>iV4G_gq7|9_NThP48O}$EWg>0J=G36ah2`?7!J*r2Y~-Q@+X^-rc_L; z1pFj6pY*iUBx(wL9r`-PoAp5bsh+FrFkbWJs;ThPjL4=2zM=bl6mT09e#hj>rJ#4=nzs%M`BR7KxkDvqTO=E8tS&W_6<`0J^f4z>d*W-1yWMV408fXg zqzvbHXGN3g{siq0RCHc|oD9jTL}9*f>O2p>U046Eu%BwoUil2{u7>9)!hTb(j=qTe zjjb0cZfLj0rJN#v6=YyJmguGZjNNPm{G}*NJ`Va;e4scJa*%qj`2wkVv33UVt@)tl zF7P?2sG)r%sYxvJ79sDPwzUB5-uEb0}@O%87Rlf^#UtmdJXlKE3fT}gx;)(xkLQ$wY5DZtS4-59%Ro`KU?0M zo(sV5w!`rR{6metayiC-y5#4}AvBImBpDC9uUHBTK|lL+%OTW1vgU{j^Y|*`V!E$G zdTlQDLOC5MGz)aAR?QD|fN&@H^3A>?-i}lIOQP0*k1g6C|2Fiap}b)j##h-?tC9b(uFge#v2D7!={evn>P*i=s&7lb8A{`Q z?pQlU$rWeoQp^(UbJsUFy~9&|YTfdwJipiEEIc_xkPG9y1UuX_LPXFs{0=r>kjKWL zl2)GNQQ`>r8TvJBY~;6Se^Sj}B|1zkqI4tj%}ra5BmMYdRU^*PkBOMS82J%~tey7j zA%sWrxCMgTcfV5JJJ0N#*nn{qzA*feXdfItX4<&dD^Y_mv>OR5N=-H0n!8V zBNCSra_)GuM+tnoKN4N(NcM{-E2l)d*yctHM&`TtR-n-O7KPbrtR8;z;5A zXNN?hM7!&S|3>#YC|(VmLi0J*@Ajm9%*dQCI*(q3f4^LK{4Dh8k*8{^pg*$aAws-! zb#gO}OY@Mojuq^Jt?k_p@N@|Hvu%?=C-+--^lFT6Nqvv(vfa9Q`9>Ya(b9V$0Jl85 zFdKCE9nBh!{RM}3z8>%-K_61QQfgY5dH68oab$-2Q=a6mZhNt033oQzVoo@If%dO& ztJ-Q&e_UNsi*c4G%j1x*aw}Ng^ zJ+13|{m!wPH<~f-)qi_i*oSF;|1joh>$t#abY9bL9eg#3_HA!lt2(*K7fAjcD>l=4 zr;@W5WYP1kv`>Gu^9}G-Z59dmcQ|SUzR7=2zX3e0o$tBeZ|v+=;aq3jSiF}Y*D%K7 zOa8so|Z2+BxL*Hx#UfJ1P zI{03~R?g%pZr=Y)_D^eRpQV0T+A_SCq=`M5j`(P9=h>TrpHj&L`=0EYN%6HWH**fw z*CTD6g(IRNuZKOHiQr3sv-dv8v=g0A0{%}eCmYY3jJ#NFp>wXbzw2bo7|5+GuTTMe z-u~i#8~82#;Gcg&`|m65f_^$JmdbV2ezuISToiA&TWtpxo}hZe?AbpF@+v=k^KIyR z#TYI5@gRfVA$b(`Cv`gaf!_MB4owC8TiI>-fS;ZtXMk_~_01));}%O}J^bILds;K< zwO=>Ve&#+C!>suN_Wn+HY(3zV^pxIN`*|w&uxb>=<-Zo4zJKQL@DuSa*D}cOx^9gS zhnvmgXdd*nHJgi{0{E(kOv#L-xKoPs4Jv%++4%1 z=JI;rb9w4+;wSmz^4BTuC5c?Jehj?Sji6|L<_)_dJ_CBr>ci%PKUb=Ic+hDz7g8z3HTjN`Jc57wnO8|( z#aqqI!v+Cf`n3!e>592?rfNuj6BQyqoP%V%e}&4iPA($*i8K|KX$5*2b-*OhJyV+7;ctdG2=s4l3c^UW~6|a6C{%-WLF3S7bj#lQ=I)ma9OLq_W_UfLA zYeI=X!#i(?ULkH}eD?atpbtHK)pgjxDofKM^uHQ-yB+B- zuU>8dUU};FpOIew`05HH&41lzs|w*4nzahL*BxnCaa~l1dtNOzeS!ctFn<{E&e)Xk zE!yolTtc%k-Oni7M(tnRs3^Xlf_p*Y;i6&Kue_=_lZbkeIE(h-_Zk8miq)l!ABQxzMkKyQ9V zP$l@)vaawL=qXwI`(NOHv|{sMT5rtfiob|xUZ;G; z>@T7Bizl&yp4t!I-WWpV?9p4Ee07RplCz8CookwPTupK02t!33(SCtD}Yad9#?FlcD~4vm#ND)0vZHh`aKZ8W-&)f4nHpM*2ejv!?nN)Q3o> ztN~vZR(z32_o58Vrt1R!^6?f6>K&UqXuc)>pJDScUS$Q=!*oBhs%k&duA^(xK+jzB zp=ju(CszaLVhaP(5`Q$_bx8s97_`y!|sbRGIN3qbpM`J#iE~n zgF?7Ru*P{^3%`-IBQpv5bLxuy1o)w4Uj7j60)uuJBA*%7yc}^wVbI4nF;6yISlY)4 z&SA|Jd%@3b0nt&w&&esYxMyUuIhDY}d$@?bz_;s!8bG~tcycEGvzADA;>UdxW{D!`KRS2HzTp%u4U#E{v!;Q z_AT_@#1PJ~Do?=UrKL-Wk903|<7SF;a!tOuSxNAphql-mq9dn0d)O23cb8WukUtz@ zI3beId`j>CvFjy#b!)a>zpIcZeY3MIQ~Bz>rX}l|FM{8U^gUW9<{DauaXkFl<+LRm zK}X`niee4bvxQT4@}Ns}iX*w2vqYHDu=za&c4p5We_u}g z$vL|?oBZU69Cm7R6XdxxS!DxVAO0-*9{T9NUqyZ|*f8~}DRi$g#o(w=TT!2x{bNrE zx7Hw?y_Duvx|fu5iMSbQxTn8TL}`w^Wthn1Q(&zyS`<@1{y0pJIBb*l=0scxP zp2dV;j;vVNXD}>T)4EYZ?drp7Hv{kgLE5{=HFafw!-sfjh!?E4THBe%R4^PBXl<*t zUZ_)N)Y{)rOHi;Nro}?)T~SM_#6)dK;sp%|F=C7)oScxLwjc%tN2l{s+oEGd98{n< ztq4kdunK~ZcYTlT{GRvuJb%3}|7`Yc?RDSl?3}ZlaU9C!rH{fhi&lj7-eGY5Tki^O?)Va?JB?xUdEJ?ZzD$wH56usX-3D z-nXYwoK2Qn7TOEDFrME6e`bAqZnOdX3H$mWeTU$soA1U7^>(x|3&nFEeX8*B3|2IC@{RT^qHRrEXlHS}buOG)q|P0gKo z{`JIH`QYcse~}ztTt#vWG0&3(k-lbI({jc=06ekSNzVMAhm9RW zc24g-@k-*;8gsebK=A_c^Q1R`_QTkKZU^W) z-*UD{i0hIKq))`>v&jNJRi3-Y(a*?k^Z~qims8nEgwKU04ZXLJZQXG1ZWZ9ZZeQC% zImPu%$f$cKXtVPw4%SD3dXysvdk= zpjIb-MEr|hW>1FPrLW)r4*ZntvY$kITxfC(;Oa#&?@yxAWs3}tB!A$@U+RKiu<)Tf zCV4(I-~XG`i@&&;n2d8dTp?rI}5K%0g4<1hOg0`M<;HQFP1A9$I{Ch+s4 ztOebwNAIQjjUr9s7{s57xJ3!re>4eZ;s^bn)wzY@u+?Vm z3zJ;n)7g2-v5;$mJvSWkhIMp3a`*!d(`I5wt zZKgciT>nL|x4+!o*>sHhor|1g_iN1J@~l?Cx5>+db)=~*=|{FX!n-E|>&3ey;X=Q8 z-rZy57vpm*l(*xUwa48~c29ObzU)sV2XpDa{#Q5r=7QKAhw1x2b$@z~7V|_RIlVVe z@6Q#Kh)|dCjuDXC=c~S>s4d%^HP^UDfODN|hTPl{Jx0hUuF(KJGRvAiq4&BXO}pGZ ztHAH6FP9&IANan#c`fAGhwG&Cv>5Y0&LU3{WgfknSPQ-u%KPXM4@cN7&>Qost8S+~ zr1s>D_MPY-^J<6$>nIE_Btfp`ygdsrZnFF6Ovrt%izoS!U(bvz!1s(1juRp1w|CZ2 z-Z8(9`D3{Z^(h-#Dgp1mL+zc=TUYL#>7eUa%e;})Z}Ly{*V8Gskv=z+g1(CIZ?0ip z1bfm8x@*4tUWn1?y$<-}8e zlvzH8p8&XZBa5oB{`k)GCjs|6yKxT6po>N!?w0R%l@p!jZ@=oA4t(F6n>Gu2L+RB< zK(7Xr_8fx#_2g~`{^pv5Vixcgo;YFvUh#R0D88ZR@Wz)KsNIzNPFFGHd`fX6 z#Z82$2aE${>QoA#d(TBn=lvh^T-AW^3o8a&q5z*v?RzCy zx4oxrFz~O)`WAL$a`u0aN&GV_FC~96)N#qU#p}hsW;%D`+9dFm-{8LqyUpvBNiqNF zml=-r)PL=I!y)*Qe;HQOdo)p|t*_QG;O{Q~0LsQheBW3a>NjSa#|AlaXq@h?h>~LJ z*RjJ-sJuq{-C;qz0`2VfFUem;n(j@#bs^TXQ&(Akl%7vWZ+aucC=Y>Y!sVcQ&?9@; z3?aUp&1Z9<=bUG9Bk0S^`4o0-s%}eO3%#s;bqc*79c6yI?rAUd*s`_oJ-};|T+eu& z_~OXVA}>zQ7v*mW@rv7TAudLlFBLy-Bl>k~8^wC?@9+o9(!dXqctU^dE3T}33-n-Y zTwE^jHdQ|=!MuX6tGvT#Je$&z0DN-7cpiKZsk(&y$u|9T(;I}>?wkB-X-oeq36 z7w-}1wO_B_e$2XDJ;Y|04yFE;`y(aj=X=Nff_d+RR`ft$J}zlf0*`vXcsHeR_bhVTps>8ekbtRLb7xKngWx%gxK^b9x zNjG!G$X&>n{oKw6k>JxZ)zW-?FYIi7oA&QE-NmkvNkY400qxgh=lZ0pz(=C0X0Yzv z!ijdwf4I4e^f=12NAnHo!2_ctZ-PLdJd}F}av#M-a)O+l`Prb)z&UTB&RLX)A=lSp zm=UO}vUt*3%8R}9Fy-f_@9;*>nL~7#GSxQ=Fwf4`ZJ|>cbDxk&51|Jmea&+4*|y1j z5BO z?*z&Rvh&HH>)z7?{|opy!Yq`}y;|3D<>pzmSA=_~(mWI28j=Tir@qOc-^ruQCuda7 z0H6O*c7HVO!)Di`aa+hf%^P||@E@jWMRy`GZb?@=_642y_LWBP@dclMH{fy$*c+fv zRI!?VzwsMycFDb1-(oN%0B&d3;x4qa?o*RdP8l$c){ilT4Zc_jzgxSzirx!ZuKtEM z@R*miERF2VZ`7C%NT7H2e}8lQA$@1m<}JDiyw-i-euDn1D`ykuJ|sSC)R%t5x;f6i^gU3u z?#%ZYgu;hngkubGpIZ4dk!L9ty6}J{4u!-&ytD!@`~FJdP3ew^L`E z7bA{JT3tbY+i#R}muY{CGUa6!(fM4IIWn@W1b(UW%gMs?)i{1U{jTfRHD9iz_i_D3 zQ*uTC&xI9FJmi(5OmCSMCjq{Vx6?S{f74I*A-CF$;ttU1i1hjJ9T_9@-2k57P3)+F zUwhG#%LBhQabzjtM0fk=Cjg%vB+~$o3n$yUG2ZX^;pb!473w;P(FQ!z4a%P|E@Dp| z{IbqxEuTXBx2g7a`-NeS3F?~~MzC|2#K41&i8Cv7kY93AoS?UX<2P#Ihq_w_t%85E zt9wa*=d1M#TghLnQ1?#M;5kGW$%qga>o{I|6L@wvyd%WHVG2bk5`0GF3 z=_otB#(eFw%_Hf2K=*2}rxJEm5>_VI<(8*jVITaW?xF~AmZp@_dk9gce;w(qMg5jy zoE+=*9-L`_-*5cbU;dEnsNwe{5^1z2hX$Cr%X*13u6r> zkKZ-eAT9YFzj1oEi1rb`?q;R770*d;RT?jVjxXB`#K&smg_&K;V9$J&o{wMWeiTmVE_rp2j8$2EXfi?$Z4Z+2-}JD;{BfsGaA5|L~D6 z8_E$W_E6YKe%2==KcXEdv**q?n769T|LojOT(k-Gdn)B2$l>#sCiP9yauNW)E_rzYL?Z$y0_lAg2U_$l-ih?-l@8GzS@TUVw+566q%d3h-9H#=Le(|h6} zrj#5P`5C`%dW2N)Bi(6BG&sM@OpK@J$Ts7nDozf*loeDkh)d%4r_=dEq$w$G$?+Hd zLHDiHr0<4Z7FXS%-^HR#tu-C={JT?K&wA*&Da5oeseUTjd+u&&M0u^vD58DCT3fq> zqvr{uw`)JeujEJle`VIAOl-yTqPf)FN^zrC$KSiA0e%{HO*#6LYAZFMKj#mM^O)CA zo2fO^`bmLa!aKy|X=sxG-$l!R(F~*c0Z)hUywR&jr~QY{EqfG1SCqM7%@R9})72#% zA-nb)SE*PL=6Ql!$lgLs9oPIAXWIX54Lz6Bxk}GMYNt3^kk!csDH&h`&~D4{F1> zSmyosO7bwAmPri+ru?ISzc(CIR_o3NJJGM5@Gjl6n_DV6BQ8V#zTT#4 zRZVHI%NnR2{rzxScgjB|(fwP2faTgfX(Qdw+3{`Tzy%FhU;6YX3*h}p{1S~@RK0D| zzzq%5zl)JLs5~utLvkAZqH$4iglDy8&u}lntNyRQ2bBZ@o!{=!FfD|4bscxIm@jq0aEL}H?^ z%K|+usSk<=Vj4n1dMCr_Vs5kK`iX zOLV+lJtq29qHoGSf(8Y(fFDuJkTR-Qzs!h<{*KdQ&bK$9jAF*90IzX!GSL}dJ!rtt zzpSzPbp>t3K6)xO&VE60Cfgci9>pjq9*!|TI`LyE?4(uxQZ4+Ljen7Y{}M^}aA?ou zcL{NpST#q2x{~L8g5T~liR?epoZIrS7vuY7r!7K%otWd{7dOYIzW{i_&RmKsLQF?` zdEvQ9Bgrz*`lg3FhfO|CrE7G4D9yL#shpITB+@ilBgX!u6B+JQ;kk9Y!;z1D)ZS#^ zQ0L|BGL(Dlyik|%v$EhHW!^F`+7F8idao|Z9G|dPfjY-qSd^@VC%XK`VQa6>L7lFL z;i&I_VUyD$I!sb80EH6$#HZzn!)y~clnahinOyeow zzhAfUuARO^xul!k#Rf2LyCpXTr7CQ!5chlUULbEo{#mtEcNm=?5iIJv`%Iq?dgc!~%g}hM!=Zp2bq&4cy(A~o;haqh z^!NES`oB@`er{S{@P}8*^(fOC|SaL<44Z33KN z)*ynuWhI2?t|7cEzQI1svkc$EL;sZVj&#)HEHPXgm8yg+TQ=$6*#%q_`0(_($o~fa zpMUd1A?{*F$_=0=Sj-CNHqspi;r!)>*C4$@@(DccS|H%%ieI#v*7NOZfnAz;2TO3h zI%8gous+K!qx%K@#+dzoo&!6kw9n6>J~Vrb82UeA`4A=Qvp*>v<<-TQv)MF3kJ?-| z!HHLGvh56Gv=~fPN+QP>#z~8jj zclQc(f66NXmzON}W8DJ_y!3lAosUeYZvbARAE!5BoXg;}KyP1+PZRuBN$80H#_2V3 z(wivLxwXS4LvPaK-19J=t`V?-&Smu780IMw_`^{q`kiF>>=;4+l|1bm^tzvEYUq*?W@csze{$^BuB@X>3Kx{%0%#~YFA4J;AYwM0-Ybmr3n7t!rExRA%Ew7 z`VIOe^d17~gKq5EZ|HYzudb`YJ&t~j)^!)eeHmq1uCt4S`(PcfrCtO47ev^)pofy; zsvgWw>F3~&5gpw7IUj<5Tvu-c68ikPT+P2p7V76djWbL^y{8GSVHGe z^jvqky%Xj1jZX7SV=7H}=*4W6dR;X=mz7YKR z&fU|2@wp>ad4j%hO-X>~+99ERoz9zY`@Tg#@3eF1Z|e{lAZIzBN4rP0ZsOJ>KKQ4x zvli_^zkzL2VZK3|7l-*7VHF1GrN8qF@~dR`y*_ktp+W^>dW>Kv?(GoyTiv zyrp&O5n;VTksW#;ub8(C_I2{E<||>`jK#e%G;cM%zYRX7H}DssS6Y6KV848VjrMiF z?(QPL;1>*{k#WG+>E!77&TkaYdV}^0zwVRh@wCs8pKzUJj}v}1iie$8m*WlL$Eg1F zQtuML!q<1TkbAjFiaWNbF_SD%r*!%2>GK2mOuh$6X# zv%YSEXVnzyNx%KZd97~<^wH-McHsXsb0fV^?$xbH_xrHkM0Ps8RqZ$axMax^;K3Qf zG$>uHT!xad7doTp-qWoNBL!djOFx{2`L{y5TIj8KQ^&bMLL66Up2uUnL8BUl{!`ce z?SSud*#!R~;Z%gr2Wq{9bqaiT$($IUl_r&9egDU`6hHWNcf7-1L%nD7lAC~&)%`?( zZ!pOFWBt{OUU(aDGh3P-g0FEZ!jI(2%y9~Q%5e=MIrxp+%NCLz`E~F3irxgAd44th zPT|$Hx~d|uF3YL%vCa=KG&G?6WADO5w5RdJMLZyv{KApaNR6UZ*e z&$-)*P;RofEXTTEZR{?Ip?PD1vc(ubLNb)SLi3*l+DlLmve;Ry6B?Rq$Go54tlAIx z?{FHVfScvg%TT6pQZ3*$RVg-OT)_WP1M19bi;ytSQzf|@Lw-P>$B8kY<(l2VZ=3YJ;qVud?KuLUGa~Lz1-*Y;a5+7j^iXT4 z%oXf$W$aeq-JhfQjQnmXuQkB0Q2uE_{wkYY*neHAd@9ye+u*!500+L!UHz?Xd2NP3DU zJIqamUoy@t!pYw*y{oxxRS?$Ja(#A zoCZExRhT8pP8XJ|#a$OEDp;Zh2W?tk%nVXOOg3hJHOc?5_FlSDbX@y!^-dYd5r1lm8j*Ndq*+e}$ znVol|o#X9B*g0)fHsJaB#mXDtx6LQ%AvmV2{Crgi*-b@A@sFs-yHrZhcfwYAG|IeF zO&13Wa*MX_%@Oofx+}WcxO|;$bVL~a%4lT9oVy3O<0^fmS6LC`x% zo1N^2{K(F@8nScUUjvn!1^QJy_+ZLc@$7sx?XMPz;MY?u`o7?!l=nXdoxLYYF3_oz z?)#(6YRt>c<&`K$Iy70>U*mWchw)`vSC=rasAUG|m{FU#gtB_*0wV(Cul&Z^*p)S~ zqr9XP%I2~{J(b>@1wW!0jj*n3K>|I;%~u~Rlrv}-i~IEV8ly~3)#$@mFM-#~fydK5 z&%F-%y%x?U@N;YocsxyiMDdkJ_u%ZRe}jJ-jqEVS zX|Jhju+Q9R3m*@BT6g$%#}FMEv-mo!$LI4bN-Z;7i+QR1h^3$}l@k4cZjZ}Ozc+bw zwutTwwEH^$kb}}DbL0X)5yO!l&sU2WO>`u+mnT$}PE5GD;vxZo%7d8WV1ve1ZRq~fX+FV>o|!~&{vUoss-z^ z;*5ee#7&Hb&abI{^e#o$;4h;Az7xM~;fM}XIG3W%R@lYK>V%{MJLF-Ydo=;i6&fxA97%tr3iCz5 zokYiS^{Gw1614Xh@0DR3_bL1_`Mq(h9R5Sobh-redPai zyJ#ND@T}2-ep5hr1oFw;+;ItXWpM9Me6?JCToX<3Ji38nqwZilCk_+*zaxvM_-ci& z-p5KXKDUh}_$2SoW0-1PmeNW0bfC^oeYQW$qx+|0Xq>FL9{Y%CnL$e9Jvt_8;z0PZ z_`~0R20l6^P9F25e+;AfwC>HQGmx8>YoX^U(hFv|unsqcFIq!(IDlVx7kIH*W62() z$gfBQdRpR|3V@e8C!_%O*xcgLf^Y6H-VHo#M;@)Bb#zksE|mZ`==mWSKY%TH;y1pp zwsYb>z(b$9N05Vr@>2j$#qAGW0$wj41ztMwmOG?RE7W>l)qbp>mS&)S@<+`~aJnVnj+<4m8cIv+G8!`>! zi`e2il-s@Cw*j9XL-J8__Pntu4`*ove=g`>?P0^hrT( zlZFZU8s<_N2##ryO3On|Q&;`sg?(+^?~22E;vE(#^e*rGC;?stS6quaU7wH({;>m3 zcVm46quDu$JZxgZN9n*TODmUvAK@0Q!b^Vha=+)JL&&cc)LH(Cc0R?H0{^SGF-piU z_G;hvuwLIIdo_@A*A?|+;2pl(NphaA*64Ety%`_gDA@HF36mx8BRrMf1M}*BIao;c zG++Ho-KrfZ-7bp|FDvzO;kl+HGDL_|{N-#W@a2zo-N*d?^43_4-zJh#{5M}cO2Wy3 zUrPAgG^}s%H60>+jC{wjR^Vlu>Z9**y}BQ&>N^DfA7+JpKrUTvgC3Rh>`u&6SFjq? z?|-|?0KQI~${Vnr?73i)E7{G!zEK$GtJ_o#f1|SG3H)YEjNq?kxWX!dkIFfgN9o9F z7UZy%*J=T$(!9?OxZYhYD^O<`LpJD=RX(P8dZ&7D-CeT#b+PuK0#FoIqrH~>4Ls9JWugE`OnYld~^z=WWppOz_H!3xgp2dH@`pE zW=vDEY^b0QYwxzvJ~Ur_V!uJqqdh^-;{8HK8y-pTGqvun%UiB)+n210$Np>N4NatN zJB{kz<_4@I+fZj)O>y%nzBC~4CF;j0@VSzgWq?1wwlo)^oND;*qZmJydFJs*X zzd7;OqdPO~(%-NL7!vy|V`@9BwQ4H@JB=pM-aYe9y*_1p|;!Vc<6Cxjv@toiX88M=Ksei=VAQJ&CEc|XZb=g;^h7sMjk@) z+Pq3i{~@lTvO{tCT!(sQeUe&6@%dKO;$75uHZ)bj}T zkjsn6&vBth|Sbz7qYaz5wUG6nH_wnck(GQd8Hzd&SGc?|- zS|scz%4Lhjpsq1h?xlW{Vdbd=7uRkKo|hw=QZJdD$5*<3H_mv|7P2OhWVyXjnQ zzB+l6B@6XqZw~$(<$)_`)YWdyW<9xFqUd zC*|@4xU5?{9s8(8qoMJflF{0(gOBC!CBzGK%;lLh`eh-3m+_!OY)I3CUYj_0mOvN1 z7+kGOQ`$Ap{NZSAa1r>#=Q`Ogur3wt>5B=M+= zFwd8{6j@~PyxpJ>;7bS|;O3?fJmRYpb<&qTO0rMAl8;jaqhF*-V~&v?9ePw^gWvnS zW1)tisH)0Z1v^k&%c1@c)IB$s7$`bd_1=8e?pakMU2S$Lw=8L)xGYP__oQn;51XS% z#yV{Oexf$4pGWvpT@#n00iTkIZ=i1;?<9I?B|WbQp>>zl2L=mtB_`T&{<=doR~VNc zmq6nNo9$Xgpo=jmxZn6L65#R*G33Lfpk1$&#e>hOL>J^j>$?;d0S^iF6Wxqmp+Y|! z_l&>#;NRML1;Hb^u)oa{N`fm*VXF5nUneoh2YPkY`-W1!uSl!MSk;mE5Pn+B?|&=A z>NT!z^ZC)QSdgQj@oMHPDe;-=i{chVTC4Z1somTVS?DzeZUySA_u>09%{$m1S-xb& zVCydWPTRdAipIGv!q(-UTcW8MwtJaxsOK9gjjzthJ4?L!TR?7Yx9OszvB6t;e)M1tg6vY)ev)t zHAP|0!MZ17m&x1qMOnFQr>boqjZ0C4?xpsJfvO&1eJ%4Y<~d!KLd-L##5>R~9UjA= zoqj%ahFED`=|GXN&UVH0d5>5cVm)RK(o4m_dqn!b z649Q_h{c%yZS3q=%v)y27vc!9Et>d1eBs@>SU2X+IV1&t9qXCwNHh> zce=(E~a#3CMH&M-$Imm!xWBu}bG4`6^_zm%kuNU|q}5;{YU_Kw8w+75VipZRz@ z#?P_sq4TBceT!1!V+6R0P>Q2Tt_sB>z*qEpH$H^q$7`hY)vCvs(Ri;K^H=ixJm^h- z#z69+6QjYNjTpZuE;-15fSyxY7RUkrvW5u)K4V@VI*>{_p9vzlRqu;S8JdDJK4rsF zlp|B(RuG*!hULpVMv_k&&w_4^w#0@058n&@Gvt`ar7w)6a$ESYM`+hDeM7-Vi%T^c z_2~ZFr5MK-LeEG(y1s*i_UP!L0)H71@qzpwBcUwY)%)rb3?HI@geZvgkND4MlAe`X zrvS%LG&hp^K4S>=uUG8fj52Xy&1{TM`LAEo!8c0x#rO~GRBtJ6>#E*2U;SJk zdVV(!rejSF!KvOCs(4WYKE*p&7V=2o=TaO}y>F~Su>kG%1SiqyG4>7~xES)eeDquu z>fTV7Fb`SG|wa@LTBBu6G(KeyZM=qZk?sc;S4}9|6Z^Q1NL0kODl6pS|!~ z1>il1cr*azi2;qhpl1Wmk-Vz+y?OQ1zTks0bm?7`bJ;|U*A?($-Y=||xPGTD#LAD( zAw8$~W*$R&Nc^G1fLNCwmBACe>lns%6a13!Wu#XtbT;0%8GMOuWF*wCbGalu))TF{ z`3U9E*`sn$#(f(9rcf@uoCChzNKYDp`uFPwlAM>T6AT7nUVPlU&!atd>D*|P{rf>L zm+QP7qrtcwgG?AVWbP0_PcLok5#+luWn4POr;N-=27OtdU3-M`&kLb%D|9NAh6DZs z1~F2|UBY>90iKQTCHtp%nXlRpI`iC4IrypgyJ9%{d6$INt=_j`f1kPZ>*oR9!fU~Q z10`yP+|Dvj8_+NMbk@5lW2e7%2K=wByw^!})TtB!7b|K>~P2o?i(02iX`$^}b!|GhapA!@kDJqE(2C#H@?tThGg)_?X}1ASE`a3bvAQai8mQk=kxf?c5V^`DM*qyLOVMt)Y{ zL(X90m)Bw=Kf6L*WbO4*KiPpCep|=e?vnmg?|VKaUlC&6snY~!kX)+wy}UOi6L=|7 z%<-V>pE-#N=#4Yj;R4+w{-PKz?8^swIv|(poe?(xZ))9?QCEsX%w3oGtHAr?Z5ys3 z&L8s6TMxX%Z~d5@e!}`TNigtl?9Tb2R8aj)@qFG^kSsZPM5g!RXS zc{)y8y>F-b-xWdU+xAUZH%-z=cD++w&is1{`pX$P#f5aPpPJAQ^Aa`k6~sqle9*F3 z#O;0m5Jd47@m2BXc=T^LqoH^wRcVP~s7zBj!%?P^+*Q(Dx=1UV$EgtahP&N9;Ae2> z8z_!SV8n$sn!ip~o?wR@@ABgf*w0#2c50{b`&oiBU){WcqkU`z{l37HUanAo&J+GL zj~N_k7ve09g`VGu{@f81|M+#L?|Y+$klaqT@IqY1FiwiovJ$vd;H6hOoaE2Ik6?)q zCv8{cq+O!*y07wl8_8Xt8$|#gl(>u!T-3W&bg4M=AFuO1b^K{n`k`N$>zf}kl)X{x?e}# zXlR@bd>X4K3i5QlY9sndUthIp&@W907w}y@c)&eDj!XAuVZ48nM$r2liS#h=8S~1? zZ=jF$SDI?UpL{;=0M@A}>k~oGFZ(!sIGyLvxkPXoy_LUQEobK@0Zt!r)Jl4O>rSm)=L&m-zFbQq(z( zZ3Fyo;9&^GXNW80g1<7@KAkN*Z)q(S&?( z`IAHmd1o_k<8$!*R@tVOqFmBeX+Zg;(yzgIgNr9Q?bLM#HcAFlnfOII*&Urr`<7`@ zzS(k%;@A1>xbBq<;2C5Rh5x{E^%rF)IrRU_eUj{Txw==ekoMc<>qH_KL;IwzjIX13 z-K#5V%ccF4&K>v+Nj3eh%aEghoF3lprE%5!uB}KE#v3vUsib|v$qVfZ_s>RY?7ie> z*3h{0#Dz}*hv!cUa1-tpi10kKnDx>)(hok9@R_f!ROA>yNBZ4Fy`T1fm+Ho^d-@P_@D~X588U*(^_pk@Q%J^q{M-NZGYunpo?+ImTKehhOJ0BT zEvo;2`V;*&D~Jgi64W<{(J;)h$8UIQ6#-e)FLWP-;uR)_%5+5^y4RfUXHejN^Yua* z^UHebKl7jV-|xNupZ?#+(|ry!@55h^Zyn_`cpv@-?|9p@ale()-%2gzk7;8Vg<{Qb z{cGB2U7C;k;S~!RW^Y@m0QcBqDi<>LV~+txtU2CBH*_l)F_l99Z*_wEpZf3Z|9{K> z+d7)#0v?Q<@IpU(yv=-?@DuF?9W{(-O&jrb3H`qGJ6*r=A^TZfeC$~%`<*Yp^MUv+ z;2T4HBsl-!`~S!#UFiQ^4!`r4zB~DaVd50O%O#338~yKceAfOqK`sjw-xJ)AJj|Je zG@kOhe_yMZMDrE2e?lLa0PeM?ekv)C>o1D`+-qO+Y&^BocgDXcSiGl4rA$oo2#zrC z_xZn<>*-?`z>EF8{C|xT=Klx2Fz)|Ym*8~#4<5hcBj77%m^Xm84EcnpB!dy~s8uXE z_BhrfZcx-5Cw*czDip`t-taK*ETl5MlzAR?Dzi~ifBJ9zG(MZ;CZ#&~sUW)%;A($^ zdkgpxotu9v1-hQ`>3`_^Kl$*iF7QjB^LM`dXPm&7_5b1Xvwq66^1tz2LGPOqK6VY= z6G7~iMb1U-Xc?XPZLVNhc zT>9>e?xU5y5{!Eqg0{U#c1n4z8Op0hZ!qhnTFQq`_oPR3b(GS$O}3Mihcni5D@(tv ztaN?pjjEw*(LQj%dF!>)>7^Iu=elrz{X8x|4&%6043|S+xcnS(<}Bd(n(F1q-&I?3 ziLByE;lHiC3VHW-ow)Ic@bm1@giDZzt!=Wud6MVH!qrUQzu`NVJ=31utEJ}7_n@CX z0}H~v=4THk-bknK9V6wz{jiS8_czLCImwaOU~BXnym3Pv z;kylb(D{J>Qw)`65$9n6w=QsT7W$vC<9o?Y%_s7-Zpw3GEt0i=FW|MQ*MhHF6WD0aml}|*Tt?Qgtw#Qm_n&<5*0p8al`d+&9bgE5}igJw0nSYJ=;y!k&1$Z6d zLNio*5=iNLJ z4-&=}OBo^L8cvD4JLLy5hC}X+vINRsMqga^w5>$mnKO-95s=rbQ}*wN{6=sUmCQb+D#x{oQ=GdAmBKI$ooED`V>+((*^yrHR<@xa6JuizHSm)u@> ztr+(N#F!X)CuKv4F$GH+>3ig3*5sy1U*kJ$2{U$rkiRHRH5d6&Q({$i$X!K0paZ{y zwqClgX_%v7=7mVmw|~Rck{oK+$gTZ>hwCjbME=}E-*ww?zlB)yaXHp;pRT$DJ1VDyK%i$= zL$4olXev8J-$9n0)`%A^E@E4CVJ0kF%En9YizPqIRT&IW9y#e$k=Gu-Ez;~`8%Y`zF?!N@w z@{GZPp1&M8VZr!w!QKbLyvqx0X8Nv^t4k8{BDd^3F2(o#6+hW%-PM#IZNMUwhHwk< zv%PTCfHeA!oAR2+#gQLrFEm!&74A8>S(|u^_-D;)=1CupS>?VxBoE5l(CQWVF#cZ2 zWUP1m2Yv(Q)$Mwj^flXRw>5VlUwOd0GZFV>n#!-!J#wV!JAEaPx2?XW9_3e&Tybn^ zSt)1OL3BPWY*877yshIWMo0ipYpEx@r~3+0H!PsivG~NL!QjXJH(#OrpH~WZ-^pAE zzDoB6w?eN);@&%;M=II$73QC0)N>Fpt`W6WG(h#vGec-*8%?S)UOI?1ol_uX%gIGKZW)XJX) z{Y%EZh3$m{{_Z9JG}O^~*GK+>Tj73<#mrJF*f9p&0WKznrO8378 zc5Ve7$x^)md{6$@;?aO_XsoNkxO~eZ3HWaBOj4u$^E>Z^Lw`Fm47=e+RE97o?jMNo zRc%3D%j*jRlwX3Lr+IHJ#veIY_W*R9zdw`mAW;5GnP@E9CHl(EDBo6Hps0rQt0FT1 zxym%-{t0@Si@sHYAN|xe7wc)+bisdGSAH`U^%Jc!L66_Rm?prh8F7RB$(6zquCxg7 zx_^|5bEsVMbUf)RegD&2>xwT33eKqOH*EP^* zB_9lYt-&&>U{CUvI^x3`GjHT+T(-6R3=@R!v9-Yr*$?GE;@a!5o@a1S0De#zw(=nA z-snCo@&UVD%(J|__uE%v-Aa}b{MP9K6Z66MggJ%;(sR$9+EBV5iuA-c0rwj?0{1)K zDLeg7>$T4flut0m`YP@Rpz+0@cXtcxGSUO^Z=08Fx&S@0o1HSii;G@V27K$g#)m@g zJ%N&m$QQ@WdUhX=JxELWa7bSi%d&vyO4}LY2i;pE9u!9SnYGaj=?CT64YxM}UkxLj zjP(qzG8@Jva}%`bw10(5<~)-t)3y-(Q`vqwm|sAAg+A)d+mXM(YD=Yi*Ggl|1s((O z@k(JwNlOgId&8t6lsvoXJj%MKUJ3G^jW*|}VEv>PnN+Zw(=Sy*f4sH0ACK-AP3lAM zT~NH2`mQ3K@HhQ(y9e?;m7tZP-g|7k7xY=;hERQ|!ye)L9PLw^H~P?SOVJc!J-4OH zj`l}q%a%~yE~`qUVPH4uaVtkd4|umrgFJ!?Maoj>W8OvEEUf>{K*M$5$I5mWp_J%5 zFQZK8(h7PQzI8VbILE3^lOB*hPq@}nN^G>#4Bu43sW63}HbsZ;gZwP%aDQ7g8zuCY>V*Kvl zh@*%Pju?&CfyV=TMm^|drZiMyezwCA1imOQw(!V@TpHlXf6@JS1~17!+bU(Eg}Ax? zc+*?3%d)KuJlSC6=v z{6KHn7TBrAR!;jP;yi}(D8`uC!Q+Sz?@4S=0pZr^f8@SFcWNZ*H$zf)zYaBoTG zxIQgpPuA5oDaEgJzus7lpto_hXZhyOp8J*jHGR+R3!^-8BsUwY0Nlx64W_&kIi+bi z`W(_1^BZzI`Th37GZl#|;3s4v7wHW$cjUYYd=E8!Da7mU-klR+H++NYI`DN5IFyBX zt@pQ(-cY`(#?J`q8mmb3^XECGM=6UPZ-8GIRg&(;xJ?;HCBP$B!lfY(p5d1S0gl0V zNbu(^nNR6^1j>K;=XfXLvTgDXM#S@ zfS=*s4maTb4u78u7m;r?$Hh^;wI6hhmgx`rL@5cQKcaO$vWfaY?=lBt4~gx7kJc*XbU@%W8@zj+yoHW()E$Yc&0Y=e~~VRnTY0L-n;UFuu9F#RB@) zX(evJyRVwn1G}s0_jwoSr{iXd6KJ1k^{YsZR;^cc65|I?3GYO^c*e^@e6XqA-GzDa z-L2ChzwcCyP1sNPtVG&RiLcS22TG|-J3U}9^yy9sPxIRg2T4RT$o|c?JPYDy&!&Q; zM>*84-CNWK`7s)sVDBXZT&?I2tlfSB>W^8J zj`T0wot*|BHp6-hwgjH9N-5YqU|mXWic(~xjhc@ zU*>r>jo|NH#_xpZz-wFndJuY%pPQNw{gU>Hkb|BgMLRu*Qhxhjc@*U*vU+$Q@tNYR z4QxN+o1=F8-6f#Et-PxMW%p0Fjp%o!BfsYQQk6ZJ@{O%8H577A+>^VgOH1}i`zK$S z2futWIi>^iY<)I|Ll54vk8grql^M^|dqf|3=BDLtM|(5x@*+;F{L>EwbPv5_$@dFy zVx3T9JjD&!=Br&7-h!SSaE&ow{9bJ{?F*z&dX>JE?hoJxOmt(NbNZ{J;9tVVYix+u zey#4lgm(UhN5EVAbM6St4_p}R1>Y{RqCY~9Yvn&+UE8t0t=7wg{UIXRHxT@` zZ;^aCSpJA+rh<=-B5nzj(R#zq4#Da^6exqQ}I~21noIS zo7D%p6%n}HjFZ_po6HiMuIbr=SfyaM~1?DlRz+1uzlgmsqnbCO;^ zEX-pxdfYSI$S>(2xqIGRvT-)_plnfs9^;M1M+)en+0J*PUVOq|g|cI}SGX^0QH!qu z>m5;+Q@$uViQ6ORpf@=q9Uq~+=Z^b4@QlpVQhv=Vg%RTzG3+KOQ{9Mr-oBqUK(J$p zIE(lgWfC*1g?)5)tB3s9P)EC_pfC7++F-!Cj;`C!_Vd&sL!z;NCGC%;ePF0#^LIWL z>v`OfU64cP$J{x{&-HB&iSM{0w7ZH>}TFn*ea z6M>(6`4;l$^!+`@Qv6Ndr!;C9z%O8PCAoyht;B_2K_7QbHqKb@A-zf4padQl47()o z7jH~<^Mb$sxav+R@nhjeXJ3r_%#}xR&?L{|#TN))x-Z3`b3v|6lErc0pFDX8;gepv zeRSv!*w69aJA)DDw6&_W;Da~wSJDrF@3&O5MBrKO5-q3Qg!EtVFzPN zm$}%Hkk>YOm?27=SzMu3ghL+G>~V}lj&8!qqxXhR7#~x>!dg~ z%F1dDD%g9;iF0=1zo+MmSCRxhJBa%)hj}t{9#Woay5A(vuE+C07-g^no~h=1AE zBY7H*h+FA(H3{^u zJ)?&54yTtcP%iio^2ifOX#P-7Mq7!Xeo_SBDOpp52WS+35`{d#$k3w0R-KfHN!`T8Jc$956c7Sg&L$phwx5mx} z%A4WQi3av((5?w`3jC(e5{ds5SGGc4e`95ECn2`wu|hpy|9yy{2Be270~)oI@SsDT<;v zjh{o$cjTYCdXAyKQtTdzc!!a3L`SyOGFB`Goo6O@Z-(85%jAOn9lbx3Fv< zdEO`gPS5?($EASt_(Dz{f_%cJG0r>3x+?WA+I{Z&-vEN)s4D7@{e=S2548C|bH;}yPKE#RR zImG?p4xduEcY6BA3vnO0BK^%8jBon47kXG49LCbVMf=L5hIat}rQl%ZL%KJuHjsH0 z_)XD@Xdj|`J@1?+dmQSxA&Vrr(>dt&f?WLG(I>L?E7WaVSh`@p1vp`xGP)i6u%LEzIMu&66>+WZZCu#eem6r2F%Nez`doho-qoQYN#W&bh~4lz^7MJ zHf&9&c;+ilCOzLw^0YQgq`Z<89~nKUJEDp+AA^p3gMAeGZ+ulDoNG-8%_2WSabjtN z0qrA3hVF*^hx&fvu%2Gxcn@$--UyEr@Z;FOf{uQc1qY#zlIDacoc|Az4t*Z;L>t2z z*OPyyYo+G^?$LW)@S}x#{dS`FVWD`b>oD?H@^5;j==VL{+=G3Hu_v5M$bPx+lnV zlMbRGuSZiOr<2}T#m!ez!H09If7QTFZK82y;4|kw68?H=nTJ>3YgzN{de0wXzg4WE z8+_N*&a8!e8`5t*q8kO+47kAjm-J_0mvW7herLeV!Eq_`o>2dV6H-A9_)AHS zPK=LE1$B+4ew5n;_;bW@3FGDcl<&Xg4YJ>8PD4LfM;l7F(cV(dDKYPX@ArqyVN9Rw zd7!4<#W+K89@JEPY(i!;`H8}Jq`3aHf4B8GpI45bHHrGE`DX@j<6m*w_Uw5~dQVkn zF`q{|_pIutX1yxGQOkO*hbJ6oeCK`kJel+RLj7&ozH@fPFQgsleomjyH2kf_68XO6 zI4;NOJ=T_O50<1o9A9|20RQ~BSoJ>f5%E6n_XRj#9$C^HV%bAJkNbx4{bos4 z;|clwN7lIukR$KI=;ZY#%VB}Pz<&PUQg_k|>XYA}`MddJa@hXK@=rhEyy&6q98A4m z8awz!KNHvash?!KLSX&s-(06Cx6NLINd2nL`8&(cZO2A(-DrM2D!}JYhn}gl;XPIP zC`nDeo;<9Z$a=4tCJeG6z2__4iRVsjmHr&`R9HIbtpNFoSLMxr6LR~nR@=&Y5l15t zcl30g&}UUqujb7b?EiqfUe(P@f8B7-kMD%9EK9LV1@zaE?Tt@Z-x6FYzxS!!M*6;3x3ir5hDu9%^1bdS@%;Hp?|EhxRw5)CE6| zdk)SL_^Zdo>g2xB9MA4C$6!Y|(adT+2YD5qTC3{t>34`gW@;z1KUIFxB#vXw%&f_r z&t+p9oV16&<|4nm4ro`!Q-4>*mN?i)NzG5K7r=LkH)VgX<)ovH*V5kwzcwkF{ZIMQ zXr!L@UjH#k_H&*4Zle6mv#+&N4$o@!W7)5Jlcx{)x?H>G7mlm=a)q0I#q9KzbKLG7 zx8yiE^iMes(#x8%cq!&X$dC9$8FX!9qmcgS;lAJZi_n}4Cc9Tv+0=Ks^Ute)xV z#|!>O|Dx{J06TrfnKbjq)iykjbPtjJo?)w^Fa4I|RxOagK)|>j=Vm4zw9WKo98E+wd|hf!a4`s%%D#(PMo_seHb_HtHh2TMEh8k^*ZJ+zPD)}JC^!;@cf@Gl)wL{ z*YC35uoDX;>Thzq_C4yM`uxAobG)HJ{o_bSF}0u%?exUMw(FcPEql#JsE5T3<64$~ z7hiI6dGBdoq5Q`yPujWuDf)B&HKL?+-UpahuuJo3S--2FtbNAz{~L0|Px-y0t7sr! zXHFlG-v=jU_v^=T-MZz;;5dgVidM7yzL)n35Z6qSun+3#RCKw1jQppmyiX~=Ahn>N z-DkG6r?CF!mSNqf-XHxFXpE}_0nu-^#uX)0CBif)=aonB=rO5u29a3@bi0=#t z+u$#-pWW10S_Zz=jykMoBTJM=-dRVwzKZMrWjz7$KHDYl4TqiL+}9FujCLiQ7k86y z&7{!RxL)*2u;bnY-Y>eN9mJ1blDaLCdRK|TD#qo7qwMw6H(pl-bAPIw@6I7#&-M*;p zh_?vbrwVUBrr}b-M)p5w*sL<@e`foq^H{HC+&;XI39pvRXPxAehRU>Gq|DpD6a{N)# zKIjJN1cw{lh<{5aIq&QyKi;rS|E6B@MW1;D_)aWvO(m}A(u#?7?7!jBNk8S+F}+~E z4S8?GX8GK=uKDUQ+D~J9SR?hnr=Yx$^d?K09l76u9HW^Q_RU4ScBy!$uhU?TJN@&4 zh+dkCPwYO~LcCPBQ-yWuQsLMkD$HK!GgQD+oJZAPYG(hDmm3=}4y)OhBg^GdW$9j? zOSS*-yerFB{~Q7Tj`hxUa0%mqZ^yjajdrm2QH8{PxbVg;56j2?`oZUv>z5Oo8Yu^* zQ^o}>T-{ay{}5^Y*{H%fcvJDi*5in$5dYie)Y3jjpHj&3p1Cz3+y98hDRO*U5Z*to z40QLdYfmQs5rIWY8}jkK`=yj8U;x~-BbUPiK30_k8@|{e%U>rYQXY20Y1xn5s;<^C ze^C9|O16{2#0TUzr8)31-V)iw*GdG{r z%>cgxh6oe5PM!Ry`3C9sJ-D%u@>bzop7yCSc0S1Qz53%}*e&dE&S+~M?5MrvJH+qX zt(j)NM?6xv&%ro;Wm$Nr!NvZ3*Qzm3VGl32$m@Xl_x++gufJ_+rJSxBm7lSnhDELC zs6R(cwugoDY;YU#6a{>Dm-| zy}#FdiGI80#!mS>)n=JLp7XA%tZXpn@3NbHt}uVsbOmt(aG#c)gTDgqTZx46;yh!E zG@1TV5v>_OyY%hxWm6A5f-}xAwZ|CVBEMCKs(vFsXT>UB)tf#q} zZT}C((|`U#oBW>ZeXW*qiuf%M0lN6?nZ-vrJCw-=wzC*2*R90!sCUYD%kxSpqQ+oUd@F@Td6FBJUSqT)#9woyqZ!+4&RfZiLfapH|QH zKK_@@v9z0cGoL^nJ)MDZ@_xd(bgy&??VNcN+Afp7$8PuQ^bb|$in;9noznZYBhimq zp&CfOuRFY6j$eAGp@`*w$T-l>bv)G;gZnzLYrC%#pW}ssUmmwM^Leao4&RV5=zREMPXltX*NWBGpDy*6zE{n*r9b3Uhi)E%qq#{RWV zZ5s1m*VME_F3xoi*ZqZjE1EO&n3@Bb4@aTD@7&A#0(Z}1TxM7Coco6V-r6#hudJCn ztPJ?e9`D&MvR(wwRnEh$Aqr4iRklgWm-V5KENv`&yKEq$9o;hjQXrh; zEctJ1LK^#btlW)x*;KrKuHMLg4Egx|1Lz*Jcr6@9uOm6#dA{IQPBN38D)MW@E5K*Y z-oKyvIPjM4+cM}~7noK$qvV3cVNX7F8RyrJwSj7upVF09v!A)?nk42cJrdUIrs8WK z1XWS4!QnfutV9~y-<-&}?@8+iS+w&MU#ODjdV5YHOhBsovcOOK+A{Na1o5v!f}MEl zI^5dDJjuv)r8X|dTz2vh?9J>Yf7y>B@1h+0`WJrZMAZ^M`@Q);pUdYk{(bwoP8Sad zi@^JursB>w7vkLZadBmH$&ky?|I>Nv@xC*@#8r@!KzWEqnwLaFg~xUat+7n?Ygv)d|50dnUs5}R57Luar3V6mS30SoaM$xt3Qgz{JJNd&18Hu zvH8M7*-vTJ^86msJ_hl1&yq0{xW9hI+3XETpuS!W@}y8Mp`I7_K_X5!k0$+J8_wP$ zPP|g*zfN5SDHZmRYj$6YV&0Y%yM}u8401V%)0g8+)zLolL_uV^rXmmTwPJJ6X1YU&)8mNO z;yeGizniqHBlW8{dhs52yLGew*bVYiJ8TQ&34eTSG2&im&fTZ-&d4z-EfI>vS{a$IeHT1bNysn z8OK>{_u?tyntXR@9_T+F{|WSk?~(eubs)XWU$P{o^CnIhwhHv+z6 z@*h247gFIeA7aGoScQy`$-+5fTti#J4w_8V*q>3KgV~Mp&H!XhL-+PUDV$X!|Xufh}84+Kb8>QyTHp{)_#Zn*F5 z{C9K3=d`17lWriROd~6e*L!q?p@)1+(3`}&Dd9!R&x4|Dz zO>)9s;6JNQK4AGs)zA#eWlH&(6x@rl`U~r2{xt&~4wk#uKgD_r`Jb2a*#Qi@0F!it0`rcc(rM_$eS9nEg7&3+`DdF`V}Yv$AuE zZ`ohcCbFG*jVRwE$nezTJAK&aZ^~Xs`#E>rb(-=p2(!Oty|BnFUgoo-{c{;tcq5NL zqJ8|Aq%G+{dV^OT4N-ZOGKJ@MotxVu8STo(8$N|baf%NoCO+jBgW zhh+FZgZ|ciHu+P^V^G_KmX&xOa=Wt&^sWBnPm@?K47~|GL|Q|-2i02PpXQc61dc4e z?;%VA{~^E|-e6$=*3>+ikGNU0AV=H_)vrgKv%RqA_MC+WsWa;)g{atNZ zGW=nP^|V5T^)%MH#bs8NodnLVd9$|+_U4irhLxS%RygBR{}swx=&>97#O>C`ZY5>( zXSJ(m=sAv*E9E}oCQII7UzTxPigP8Nqp~gnExL2IV)Z3I?gj7C!tV{Y4jVTV_bQiJ-Q~JsmS+R6 zk+|JGqGn-T#l585tB@w}3(eOLBF)t_3@}o@wgk76_*c&ITV9OE+M;^c$a)^7;}H2< zzyHMFiAVR9Eu)6dg|Ld@d_G#)jyUXa;rcNXAM*L6d+t!mqvXJSBlQ}*Ndvq-yeCn0 z^nt%zwNE~0u+GS6)UQIl8`0J4$+tc9cnIxo$%O}-srN~Ldn{27nho=Bk#3jM$D5hA zt9`&V-EK`uGb91`sIUhPR+#S-ZpQm};3)(<@>oAYnT7YhvDQ=O4@C+@^$t`yMgs0?&%D8o?F@J2=FsTmdy90fL>(oy=VD>m5{5Z`%Ca& z&||-)&|`=-Mim5m-Cme(8!Y2vDwQtm8@3lV>K8|nzS}a)P5BHdX#oyRNMVpdkxjaB zsX3wS_sizyw#z5an~pV$?8ka9)2w8w@)uPw-gxgH?whY*dDWc#p-12E5t6Q2S( zEw`3C%!n5t&*Q#!J}(N-#%T%e$36MQ41RO3`s@<>SrLdj&U8&%-cye6jQ{ijIj&1K z!=51L)5$VF)g||juv`ee)`9e5E~bei!O!>g2HacNUijIA`_-g7!ttpe^{f$Ly#l10 z|J!x0o4M!H##K>{mEj%-`LH~_iE-in!}_KO&eK19zX10`fRh(eeUoyE)9dHVfc&Bt zI+Ce(^@xY(C{NWE$8kPS7?d1Ky4i}@4@M&O*dxP<)9Bna-$TDpAPw3yvShmT;os~& z;^6f2baL|h=^^7v5qF=r2wjXhydQrtP$%P@>@XPj^FHuGon4majGVYt97lH4czGVZ zd2|BKzXO(oh55Lb3cOpxtpSWbwF$F;(>0^S0Q_NX+k|h9+0XxJq2G*)-&BO(|LHs99xdA`6dK%{$Njmzr3&U%lCwAU zma+64qBh`VOtoy2>0EW+qu#s`trqGh;g*?pWDTuffO}n$xm&!gODXTDKbG&OANZl^ z-lx=O2lq3ar^Qqrb7XwbVoir^#={Yg0@{uBjitBKiL02L*|eE*AMi$q ziv4KKui|@JMt`t`GZ5n>*Qxez1q#Z`;kuMI(oXO0d^mo`-{Tg zf%+2T(n3i$BENnE=hGjawW3|5^&L}V8rtO=E|%v}U+wFYu&!e~O(nUcdr+F-dIfx! zOM8>Zf0$(bjQiWXf=fv__sdm==->weCU0qp+`nSfJB1~nq?ew=b&9Ur=y*_b9m1;>oV_0 z2Q0JKD0Wky^9}JEXgAqEStnB8iMshhPvFKrTGMvXiu#c!Z|~zc{^z18NIN`V(@+C4FFbR^_C#y4Ka=cE3uhTL(Eu*x%fRT<)<%(@3F z!Cy_w&D(tLc^ux2Y160OVfVN8lF3T-S746_ee>VgX42!{v)4T?Wov}dbtgm zF9FvzV40#Bi+BU?DMXWva+?(!hj<0`Lp`Z%uTr^|QvQ0k|33A8rLN+cJWgA|)EVf{ ze7isVVx-l7@HlWkL#)C0n?-$<)t(>LEzKF$QsyF5=Z8M!SGszY3EAb`x%qaX@Y_6yL=1B4p2;9nKe@TkTn@BHVeyWyo%5B=AV!utTjorzA zWzKh;H@U9PkFp>?Ys!V>1wEY?EbDzfu4}p99=kk}a+N*{T<5&aZwpnk{qwb%&Xp*? ze6?;L{m9SeWw=K(Ja@wzwF2e4CD3IB+f}bF8-%a$SO*3;x1 z{YCj`IbQJw3LwSd)(Z!J1FrK0)2FwT*_2<=w1vdwwkVWCJ9Ay}X&isY+;f_IVg0^^7>>Sf3kiQ_}Y4>y^eGZ+9B|++YA5L zYh^6);O)2PHRHXvHPCol#=i^w#_S;7d(u@oz6(zG=td%cr@DuXqnVeQlP=G{bMN3@ zG4^4V`SQHA71^dzE&*W-;!EHk_BR$&E@`7qR&)Mq*T2?=`ni>r-3D{~`p5@z-1dEQ z+z`fTi66g+%Lc{e_hWg>n6uR!*ROk5#ZmulZ4oc=dG*1$eANI+u&c>Zrq^CR@%!)rHv`%<7UA5)<`^mTJq8ul;`$L zYPrgKSC%S{5QqAd?okAvC+yb0GYaGKm+z}(yWO>=V`vweTa%8GKY!d&aDTxxaYCOh zShcJ#ZGIFvyB+Y;&Kv@^fb#)@#i3?72UYAC>OBz<%!+4|A~n z<8{rj2W=G_G;Mbzt?u|jX= zbvY8OwyAjX{3773;&*s`rcA*&a&8Ec1-k&kHyeJbt&e;DUZYrGH0 zpW)Sf**;&ZtVTQ(Ykg(Pkj=2x6sOeQh^ z7}H0gXOcOt2hVr^hx|Ch3Oww`f9d*ZKCcY-MWJ1;)DV(SdMCcg#C!7L*5ebZCQ#0Q zI{L6b)?usX$>8;vpVrS>74#pu?;N-r$$Imeo478Re77&%q8wkX2y7)?cbs^Q<5N^F z!~2_n<$ANm$Nme<<|NX6);znJ&qeQvF-*sH8I1m8bHloyt)kz(vb7rbLKf-2k<7q@ z!TG?#bQy=#VBM{v-8ugK&5_6Wol&Fjz*+LOVwzXRIsd6#tvOsc#H3P$DH$h-mhy-6 zD+iXEudv?-m32nOlHcmL>!CvrYKEltwDQPdkY2^s1Kj zxqsK?n?3ObGq*H$3C~MZ^}0&ZQyEL}opp>)J6WWiB=m92qI{bh|1p)nCRBJmw14Xx zRd)}to;Lrz6!I^McGM173_W_h%G4X?8qWSN9#l~eCPDK&xu^4j<(Ah7JG)@HXROfi zcXvttxxfWKZ+U%!GK&3d`at&^>Hn!_9{rrDdrKYs73Ra<%UGv?H~Osl++%#FJs*JN ztbgM4z1fVT<}Ry*-ws$b?u%I8hUeyI9l-hAaBF1b(Ng-QXKTmE`>Vmy5WLp~e(A+3 z+{X=Aa+5RLxi75zy#!u)c<$)j*>g$XE;&#S@~{8pA@z18^WamC(-~P&!QYVyp%vH2 z=ec1|Yv|Yd734|euTdFU#yHX2lyQvo#crVn^6~xg#unBOIRANvcwMzBi=BL~GN=5) zdJ)@d>6c9vh2>+he&=4f-Sqt{&Y}A220@=*cYGaKM97VF!F8MqHIMG6YW#lX2zUnid}b>rkXI83V@L_O84=}?Nd zdMN@f9&p@dXZ0i2yK}$(bF2fof$ql;kKz2PeP1QVv1a|+^GI{oRXqMH<*`#CV4cEu zS5>NSVK?~xUCJho%RYLv99OLNM*fxfKYdQszd^ey9@GcF{~De5L+ zfz1zVMtFrx?o;BJU;boWADj=E-Uu$K;=C#iJ1Wo1_0N6sI{10*Sq16LPc6eegyFeA?{B-p z{0;434f|``Gto@Hf9s<_`D4tZr#BpFd_G1Yj^Mn}2on6wa;xF>Z*jkDyY1LeEG+JWJL*Rd6t>dqq zc$3d{>HYgMoiFxDWxH$uC(zIMEalgkkLWcdnDURBx}WiR?!JS0zj0ri?D6c9`!T%H zneR2kAS3(^a5-BPSVsb;>s7`E?vqAWS-$5u);5|U$Mcr&CpItSe3lZNG7hdv(&dr= zr%^ZeQtwqCm=UL*HyXJcC^kOnKL~-XBYx?UnJ1pODTVhlKC*;kzb% zJa(qNQyqgiPj=-j=raQ=P*&^RZ`f|>(ZGvyJLQ2j%s$gO0_BpNkcWAV@2x(#+`xHc@V3kGK(I00!}&gOT>D(&vG#c9)SqR3 z@SRZFOK?TDZ2z+s{kop=kb;lxBkrTSeQ6`@!O^PCqMYlN&zpmGrrNpt8`yrSPL%y# zNwEJu>mSQAv%YobcQri6$hEZ2e8}fw`$I43x>J%0*?!E)-%oNq&-|@Q9*@)f0w=l1 zA%$|ho&aK9WkU!|WDLjTZ#<(;$%eVL{iT|8g0s5TVQFIm*xXZdNj zsfj6+x2ZrQV7evzBfp#yiUX8B=nI<;- zvN$gUsTbZ_U4Y+S)=0W8rK6I3O9=(Cyak`MlQN+Stxy@qha{>uYDt(vi=54NK>tohkEP;0f%Yu-N=!p6>OX^^?Ba z-Q3FOcDuNVxTV*hR|Egm2OM(uGTO@!Z`%#hbLQolflFa}tMuNd%x}wgXM{4OjX*vE}??trNwGolOm zFKxK+l<6(U&Y7fVHs%gvIz)7Xui?4Tk2(;y-69Ps&0@X1lBpE<%GN1)c?(G|R=W#$ zQivxK-FDWy7sza;90IN1uVVesPzBBta30_?!;hcGcQxHxSWg)>tD5%s)OQa1lYqtO zXprODg)WCcda1U2&>NncG`!1Ed0wq4Lp+c3o616*C;Lp#4!EDNz2t6Q$NGt`92fa9 z<6a$BW6R{;bEc605kjjRU)dd-?6iXh%^$E109VB~jLgY3*!lxsw)SVa(oW48Rwd@xzXw%Ss#5|J7jwhg#Z4T z;|h89;dQn*CoRPM#(q4{o5JxKlJfGIcZs*d}$zbx(Ua{0WZRrKPVVYv0B#N@wmo`gwh1IbtO@CR3z7sX^bj+JzRlH-ZHIOpGZ zUTn-xHgMdAfq&{vzEqkp;O6-B=7tU3iR&WFn$;1v(JZU)JMcS{3#ReSVmsc#vDQ`FN=HGp_q@a$DZD1Lo~;YjpH`z!wh5Z8=v5 zT=wC)@x7N#L%qWEl$6fEdC0x6x_u(`eQf8%NZ^v2B)3Y|e}mi6Kst48e)U+Rg1?Jf2QDIbBI8#lFm$^TLlNZK7QkCVDmu;5gOBm}cfZv+{AS zf&EOcUhlGgr%)&80H>{`dpqqtHF<}V<0xI1(ZqJH28(R>Iw>4}4S1o?_W`dEc*jC? zG|MCKLY8hlE>MJbR1@(cZUrdH+kM+g>JqW5Dw2dp%Aes<%vjcc+Yh{cBd$M2tt@=&{p}6!w`UIb^y? zz?DL~Ew?;-NZ%`o$(#oj5nbORzXpYCA^lXkt5{^-?$Ed%7h|9BL^z*_7iI?C9z9_r4igy0^Vb*g#kCB`@xje47j(XL#-EN?~ zHw3&s%Io~=t*^-XXucN7cCBAFalN*j{GhsVxOK7q>P6x_ZnthqPnGHH&`HTGcfS4` z@FjrbCyH|F(iO;l)12*|!t%iPS8)Cju=u5uo5|Pxgi`Du0;Z;$Ex@^goIjRQ$cLm_ z6ky)mSpv>7&PRscl>NBypVwu3a#uLa6?_^K*gY zYOeGSCVp7U-h{QppLHmMJFp*L!A%3(#YNbgDR^qUi{B?zL-LbUaeyU}>6?o8&{on=DOM6P&W%-_Ldd778kwq`;rnfSGIq25< z8qRQ>_{_B^r}@cK_#JC!cyKWBi3F7@obt;{O2&MJ|97f|WoY-%d`I@jDv3B;kYn~B zxqWaSH%?Ko&#f}c^S^mf+dh6bd;fAf)*0e%NGK1Oj2$W&XB6i3(dDzgkSe`+=v(`3-xZ z9s;ovxxLjO?GbGvOj zWd0`1Zi@x~%W!|2bXV0$XJxra{U9f_OR}rkuRGW+kISs+BkT9;`bz9F5HCdc8qIoP zy>$!Ouh(;0mX|?rIVn$1W5t8Rp!@tn%RKs%0fXOivR*{!7Thwyx>w)|BR>UNmx}!+ z_LrFE?%I}QWB%ihQ|7aNVO4!3`!Oa-{h1%=i}aFyRaK6ka-aY0k~DcdOQRZCzQvo_ zPW&suZchdZEA$->zHmO0Zw;fq!iTiry(Z2Pihj0{ZhvXMi|cB)r|nOf_lTjeZ=Y%T zsz3$l4HL)X6ajukrBVef%dKv6nBhe|O{k2+dbCK!L$zwPsgz5{(B*jl4zpMM+z(#R0X=_p#R!&55BtSGfh#d>>P*aZ9Kqqjl;i)>6fcpN;yx0H@vL81pdsYTn zFa!P^xI|@lpf`NqzN5jwdMcM2_e4uf3+_IgP5V*Q*AXu@x1ZCM&h{nMs{5QrntZpC z^F`+gm*eb?p?@FE@_DuO68Tc;+`3qddur^m8v0kGxhjv(@i`hw!`j)6_zd&7Oai_$ z=;8a*vK{E`j%3pHq`kl+)!K5l$^7T_bCX|h^*^iGk5clqa~*U`ZlD1MEIatG4KH)w%> z!+ug2*@N{fhD71q1O9%XQubSGnvH(;U!1f9^l+cA^fy@_oArxN^0`qL2E0F<>vy)x z{;_^;mWAWpZ$1z)95{xm+&wuQm&%KSaoAl=Z|$n(;CF9}SET-39>-qlHLClU$xJl{ zCE}of<)yMj>}TL-j+VAvLc4kK>#m@m+|(R3&ar?`*L)VM8*r4(x(TH7C!qt^-(0Jt z@Q|OpT{3Rt3Q_q2Us@ODvXL)!dBaWOfvO#ToT32VQhHLx`uLq6@jP=qk}IF`!$M`J z{xZ&1T_Yd%3RNrXcFW0n{bibF^az{21&- zZH^``!>#^d@Nfxn9}aTNlTw)WaT;BDU8|(Z`{d)q#V&7i{JsLWU6$v-Tc{7b z=rn_p>BmWB7%%WiHw{zbZMfreS{N7C! zTVs*WoFvtfA88lm0vwT<@5*t`$@Y|^te=Jt{ZQ^D9)}d0J56toXvyUBif8>`Plz)@ zoq24h5X$pd&+Dky^}zG;q*5QgZfaV#G2P4l3+e^NH@OM;J|6K{qu&!`{8Yt$dw-5k zA>OTS#5jKIEMS~?4-`5{5eYhD4-_=9AGgYa(?R$-WfaaWfOnL8aVF+}%BP4|KGeVUFrk+H1;hb%+J$4Ugmnb>A5vU8_7j?- zGE&ZVsqy}B$f1iN_&)8Tx}x$b?exmNN(1$@*6>a=>8S51JCd$a=*W3&4Nu^?HTOOn?AKza)k0TrXVZJCkdw{ExD|tH0atZZFGx>Z?tuTq{ z-DJCi{M4HZ<#`?4*+b2ihINZrLoJV2)ewi=xr%JEHIT8G8 z!~yOj3bXqQ2K?#^xRL$6te+C&fxPkk;N)Zj$06q@s0jGm`G3v$o5F@3dKJa!A-b^5!g=RJC14n)Mu&(r{%HnOIv~QQn zC9$3QwJ@}YpT0i)6#Fr{{fN6@7eX(&ANN@40CCW}NHgz)Uapuk5c1rtA0lPuupfVd zV=q&$Eo5&j+TZh+7)dWkDRfxI`Q&}F6!<aeU#c zyv^}Byd~MP9vu1b8zrWL`8Mp{H=CA9(qPtm*f_L;`d6kI4D9coJ71K?;pigUk-L17 zF_v;U@RmqAQs+%EEH@-a%i|nmcI!fNb5c&HD8T0;{helaDANfI_7w76aq#4Ej$b+Q z6}f(xM`vdL!D5v}dNJxy2UDxTt)TrVH|>_^YyO$OGQCRo!7F?Y_=G&v%hgRcC}DT- zbM<%l-g3^o8LJo{7OsK3dF@u)hPYquQxvzm=u!*iV(1 zu0QXJc?^6z$5#gWv!uA6KO^7aTMsNH9YeNFwi_}0kSxCeDna&B?(nn`A@G;K?kV&iqinQ4xx`_CtMN zm0QE->SulBR8Sk__)QWvYDmu+W0vFde^%dQJZ+VV#3FgUh{|lHe9I+qGx^S_6b z;dfi$cQ)%I`}6z@^QUt#^on`YwQCQSH`lHyr@zq^V84uV;fw%(wOBvF6IMljbgqFC zQ)kXSSq~c|qmg!I-mnS#qgbm0KOB_X36|}&)6lBl5KnK`cdxxK+p)OBjA-aXeYE(7 z>@P)o`yKl6ad9`{f8du?5k;tP+H$kXLtIHgdV#mS;*(m8J10d5Q^5|fj_86$07DJTFP8c`5vr! z63Mh=+>pjbw0n2{sCS7^i60zikpOFY7PFXS5ytk-$f0n7{JB?<8)&QDr*m(p(2>}~Uo6fe?0 zFKcfhe`aYt{M#aZH@o>D_2Y3{v)S*DtDeH&L(Xmcej`1l!VdeyJue}&$ddxxx~YkB zJa%g2_urEK99@OTdPO^wkT2r@{!<{g5A_P&Re7EW&lT85V@3aC*AArP8fzZQd{)<> zM_9#l5$bI?#|NIOy%u%@{oq6Pq#H4$LAF=-fRvbkWwUAGQUQJ&@1><;r4R9XX8`np z@2_I}>r~|5VJ+Cf_6yUM@Oy~2%z1B-PQ&1& z9mJViaJ=zX@~_(WEBp@HOS>S~64T68BK@FMwCBVyRfQ{+0m}#aE-Ix+yXYW}k>i8{ z&6qaQGpmKpOm`fNf?nbG#tuKlw1dzQazXjIGR*(Y`nG~DD%KNEIPpGqk$y<9;vV(( z=+RxAe__6f4NC*j{ zJxbY`b4gdB^fB(XdI}1*li&8U&9LLN6t_xB;yf_AUC`Ty`U7#lc!{HKFr*Jy4%~SA z!yCU5Z&a18!Fvdt=k_aE6`7lkauIgAyihPIa!A)D_6eswB}$X>*`L8E?gZZ^bti)y z58ZHVJxThv)>Pz>&%==`PE14;4$W-Xoq6 zhX0xUHm`m5oc(n1+{J2`ogx@j18J|V-`!Zp@(9I2`CLcR{e)Y3xL=u&2YG!6y!Qx? zET3pWUboWSF?RALIu{D$N078a2;^%=D&wxoXPz!s&p;w!@#kj#=c!C`7kv{c~_9CCV6Kq1`S7|9pHm8u25@{VM zX>fk~p}u|9Dv|SH_wR{^I6i5i8|9FT!wh^y*n=toe4?McRAVFQ7lS_dw{J^996-9- zVDhEVr3=ffIVt;|lnSx%*BMV{+Ij9N0dJD~I_+f}&iNMUTd&VEXQ$xzYB`ejF#2!J zs)@=$UQ`umkH8M4)>WA2IVqw|n@qmN&YcwUxbkiCcnk@tu+N;7WIM-~of0Ftgmko* zvL*Ok#QU`__!Eq4&J9O4#<4@xXme6Rtl0{coH~qxmg6v*weJ7Z1J=Ww6oaHy%KfDB zd3s7{BIOLZ>YyKtQ}AekZ;Difa;_f^t&Zas6JE$SJNW;eOIn$pN_?SLgPf+P2>g^3geuxxOi-onge2PX_J*%e8UD--R80sfL`9pA{bmoL6f{ zgVG@9-`4_vc?-_x3=wQEO5TUW1GW1-i;4RgG(8V^-iSYZ6-mVH6jVVHag#Mkj#r82 zUejxOZ@d>PT&cYRI|wQK=t^(kL*qSdwqOn^JYp3(8ZnOd$a<4kPQP)au<@(M?X6E( zKd(5wZ{t^JpP>Di zEI-*6Xd&M<)3jMk<5$-J9~bu%l^wI0P6`!NFZ!PxPre-a=3j|NsQ|leQWGO7XQ`lodT{tYha7yC)N3tASbvIr8_t)`e-)QHJxl@l${~})POTv~8d_KJUN$BbPSF`eBgNRov`Qv~K`=NfB zLFmAK288xhBE|VkkPraEu=f3z^GIj%^!S$p#L3P}w8{1$V!sGFjy}Y#rynm3C*Aq) z_4|U)=a^^KFfGp6UDE^lbDN8McZZ(SrS$%&XF1bltD5zNj;UymG^k@|rcka!96s|~=J2YSXLj8LByMV(Dda(&I9=$R) zPR)LGE>Aw|X&h6cWIXyg`IBcezp2O8<YX{SM~K-rh)EV!AFVZt4GFm=7&_BSVH{wB(uX#`D>@g*U+xcjk`6JdbeqN#xsrA z#!VewTw-}rSKUs!u1jgaXNF#L(j@ll)Y+3FLGNkEBf0!DT(lrxy=PfFGCkGp-P6?5 zRQDVi58WswO6;$9{GT9?^OhFe=>rqd zΞ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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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

A1 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

(*9p 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⋘MtBkk7O=RgkG6%` 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 O; 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%0Ah1>*DtuAAN_bTI^;sZFigX((HG4&%IV> zeZf}~zC86H+kN)a^)BcxcfDNN4lNaTb1#N;OHbdeZ+Us$>s|d&*Y-*5^`qbW6(1hD z?8#t9%W>vOkKWsXda(4gU)}a(`z?QaY&Ni+ah`hL9DLmK#TOe7L;c9+Rq57?FCJdL zYPFwcZ8k9MIb(Deiyfc#U9s z_N#YHdDv}FR=>}``0YT?-tHuwqrQ5&eGSjj?aT6Ov%Fe5?M8h;^}(qJ*`73gu}^(> z`f3rsQ_tPQoo(~IqS-F3U)irNzP{JvYHvPfmjBDp=2rZZ7E@o;nSJt?rPYUufr{Ub zo2A{V$!hO2$**>kT4W(^;EIKo^#`L_FL z{$e=!kXO50bZD}Tb=t};XU}}G$%@aWzJxWJ%@t*H@8ly_$43FgeDc$i`m*wD?@{*o zN7)yL%uLL&(=5u5!kB-wS+<)!GsUbf_w)JZOCAoi`_>bNmAwjIo^}ha^ZDO;;;W@T zz%Rzesek(nkhLplx#H>N$XC4V+w(B!Pp7`sv-*_$7R~>v|EJH|HtXu$+F|b*jk3pw z4^6f<8&}MH$jiZ(ydEvi-DUFKjq|>`=kPmwe(ta|Cp?TB^JQ|MKJJd#PWRJp^Ty|= zkL!=Gmv+BXZ|lD}Vw|;;uM97yJoYr&&Z6}Dc_&A>sws@qrh`NkqkuU33DtE-xK zZ>l>#cYJY+nLN8_G3kp-$B)ju=!%E7r>E>Gd+{8b-GOrMeDScsdM|F6@7bg~ZQaFp zPTl&fUG}i)U;EdNk-@INY??)WTX#8=l@m?IujeKzW_g<{+HAv%c8Xoj?$4!XY&q50 zKEc&#hUKiLD_U(m6XN0Pn^^TYD$n-2Uh{R_*AFjVb160&#`@u2t?83pQ8wjAi=FMM zX9FWlPCdkvhjC*j3(py|JG(t%>QlYyE)RTa4rhEbN?%`ICoKmmF8uAa8MjAjd6~SA zVV74)+YkDPnbtt=i^ znBaEs-LpCUyTcTpEt>4?K{Kh3dRNU?^@(FH+1#4ltH{NcV`HeHc`atV-mL9Y`K|Bt zNmt*NrfW7=c(zaM*MIq9Md84oczPC+cVB9@dSr&|GQG*uL*hh*{inow>Wqn=e~2)ge!J{Mn<$iLY-b-JYEMRgaRv zCw~~Kl{`FWyzaMVaZ4}9?yi2gvp-|4tXj`ryy-h^%}iewyINp;E{4ZysSkQ@Z<|lM z+pOs9D!bj;o&mHO*UNIFkKtw`?mb&r^am}TT#Fli_bUu!$@9OWi#s2*`s&rzj;}9b z;$dlqi@BWHqvH20u+0xO3$fC>v$=QHpPvJFG01v@OKUH#iR$ES;~yyvF)FsPqxeK{#zI|n3%B45W?jEhCdZ~_LTv4^@tJ?KsPdi1{Gc8{IZyvC2 zzwqI|MVr?Zrl-huCTi9bc**757-T&s`og~csO_ppTaVuCX;B~;ddXn$<=d8J&u6;cf$MYp$xo^?l0o!-@ z-FF6LVxVD7AGYd~yLr*|%;TM&{c=_VT{+}&iivjLT9S*^$@hw~U;p^>@Cnz}P%OIT z&wDzwm{GDY)(dz1&Qsn_@r$vvT*;~hy^^i|W%xcn&}N3#Cv(P=;mgr8R;}3-qkZ4~ zWm^qswQdjDoc$?B{YB4M#SLHg&9gE*+hqAIU7gyi1>Vzc?^iq@F>dvTe2;#5rqtQ< z9IbEFaFs9Ho=cQWyq%Tj-MQ7*~;7VPoMwGP;p^b z>(*B7`c_?GXfNuW`pItP=O@mdVv(6cGl`O2#Z)hOxx)@aC(LrFLrk@ue(q=g>i5Ra zF57H29=@}_Cl90AI__+4`6frtSbI+|UbEYI;?H|_C_gdGaO*37vMc@SZtcMOWq;Cq z$<3_iAHMpf2KV*Nubo1ZE#;$U?c7yQJXyU# z*XLgBbhmmWPQBUZ6kqPeLbki+UXGJ)&iHb;ocmiJs(?VI8b@{W*esBlNT#L zKAmJs)1jL;>?h4WJmOB!_2_F(T~G*dNHYqGFs2TO;noL6-7oP2%SKG*N& zQl6!7xwBonu!~FH^Isj&jdk+s+1_pc$j=(O*PCjR3%~X3E{7$*y{`;^>x2_!BOkh$ z;==+%IKrlH@|Pzp$;v}dR?qP1oi&sz4YFWxw{PJDVvle&#c+T@*og1qy(eER`&^N4FWfsT#}^}?#e-i=_jtBq)R&$Yb(bef*E~`3&2{cN z9UBLRb}~Hl<-xCiIz8jhj%?34@9xEM&wl3^A8pR^?2NYec=qPi^UuCq)l`j3AJc^c zmA|}b*rHnx{$A`W45xiPdyL8z&iL(>UUb;WLvLqxr|7C7Ed11(tvg-#vwJSBrmMa# z4*c2cYCHQL_Il7Aw(^nXpWW6M-?P*{^?jgc5v?bW(Rz<=ZQ)xAYZ%2XA6$HsxwAWK zBhM#{>~>!6VYh~BHRzYzo4?-H zOB4n+_-f0}Jz0D-Oq*Mbju_(htl<0HC+jn1Wo-8B^R{u;W_uX!rQuyX#o}B2V%Gos z)mEM2Y~JPR*zqq;eo;2!E}r=OvyJ8hBj4orXgj{N8J)83^zo?pP`iHJv?4SAW@c+R^HXmg}TpBVWw!eB0IHgr$2ld-v)v>u}+_FYe{(i@*IKTYvX( z-=h3sp$}vC@WEF8a*OS#JDN?prQwX`b88Oi%iVLun47zaTTdZu;qvssT zQ7z>u*7_8e?bhjD|H;EER`C}@JRN(!#e0mF=ZbFbFc$l?Klgw4cK0Oy%ATzF)uBf{ z|7V@qY%KomBOdO0Rv(*zJAKD)HLq;i*R$TI-WOXu-{un5C|!M6dl;KP8o%|`pY%(s zwY+FCvtzS8t;X=2xQo?H-HR11FG?1Lwf)Z))$e-VT%zevxi)V$;_l}m59f8rOH@;ls?1eKQdU->x;AXnEQ-Ic{MuM^rBSm5*Me zOCSEL_+;XRJA5el*0nmg_a3hYboGK9-wAtI!nU*;whp{l^{sr}>o;naoz=(ZyA|(b?MN*n53ocdNH2&hx(UwuX}zzZ~Ut4`(`l)tFCpsL?Z`?)44- z+81}Jxw`Lsm*-!7FqR{md%oF3+o_YLkBX`0=GU&sPlqn1dPi4mvG6E4++yapv(Vcs zIh*>2+WX>fZ1MM*aCgFx51Z`HSjDRc`O3T4)CyO)}8*8hnKu~YJsVqZ@ut5 zcGTza%eHvuJWm^TWH5vc?}$a;X$;Cnti#J-qWq!VmNWYym~W`xB8n)Si|JbFMV`n?b}J$Haus|_u`l1jC8kZH%06txh*pPtd9u$Y-Qqdi#bdkAe!Xd?XYMeN!(RPoo$g{?o%Px0U(V*jX0fD;(m!^xE#?)LQbdY;K)) zC%SgwLd7bF8k<2q+5T@%aX0447rUA_XSQTLqdgbw%d>SB550Qk$nz7w`_l4W`L7SY zzIlG}`Sd(T$;**XG~Z&EzuuztTa5fpnjf2bQqE-cd1W!ZI?#137tn3zF+^BffQ0yqVy07%*%{IDu%R#oW)&?&}f8WSQ zynOKa(8r_MN6-Cn&nLS6o2Ok^TK(%&u8o(B-S%ST;a$4BvVA`N>OpeP9$u_sq}%xT z?mfpRT|c(lL%Ow3x6d~l@|E!s3$|joSEIf<>MG{OSQ&r5^#I=~-YqKsp0$&|SvTj+ zfq%*=W|*#MHqnjCUX53WF57(3GY1*FQ-1HheJuxCo_bR3Y&K@HXf?2jFXqOp1~SjS z?>p@HWFL<56|Z|VIojT=>=qwpdEl@M{RZj#S2k$*PFj5O?D5IbE1zUNzwU52;^eov zi>Xfb`6kb&*eF?j*=Koem(P~WIen7n!@g%9&t6Yq%a`r;S&d}ndW@E9>(ZBYfx0)V%*2`0ljVs2U^3;RwQMUOPFMl@i*+i2s?Onx2ti@R$PFlR^ zsTY57xHtFuTprXe^z6HDT>KfYK18dh`*Y~dO)mL+7xmeHFJ8H$VGr~AX7d>3!)AMw zPyKK&b~5*?oyj-*lhPL>+bCN+y3haebo$s~PmkISa`D@f>LNRz`|kX!<%(_%Vcl7l zD|%HQ9W3?_&*rpqU)|bNZ#3KDR%dj5>ucDfTUY$npNxI+mKNuvY?5v6u!mVM>Id0# zPkAz>UStJ;@tO)Gzl=f@^o)fpD=qx!nBu4X|m-r{Aqe#@Vj&-=y` zQ$NBI&Bq;f_sy~V`XoR7*_^WCCg;1){qotgPvSMl6MuK{dfvCMkMY$IJ#)iQ&CRu1 z_^@y8VWiK8K0AE!rJJ)HV)gu>#m+CEaBq$)9sTxV{qik#7~I85U)ql{) zN;i*O-OD4+_C>ya_a~2@SkL;OaxcbdOYXqs;N^uSJg__KyYq;rU)zK#z*Y))D{w3^WHqVj|V zrO%!|ogT97Y`lAXF^Z$!YA)vDd@g_1+p zvJ=zBktf@pxAue|9a^1giNB)N`W#(8^`bh$6Nbehelu7~z8F_`#V^h)eY=kqyS~)7 zXuYIg40y8j+qqDrtz+6(skyp?BraK$%+E4ucpUpbzxJqaINefGnmXQ$seht;3DTldF&dN!is@PVfu zq4hhgYTDTDeBSZ4|Nj3T9^UbxAN61CJKy&9x8dLZU;NMiKm7at7ylj~_O`eE=CA$o zul~+&{l;&;^S@o5|Nnol|8{xT+urrIPksA){?|p8w>|vd^M8Brzqtc%fBV}X&hsAc z@g5J(JKph*x6XUM=X*XlE6eWmcYobAUF9hMV*204`MT?VQ}NDzZZF^Kz258Lz2E!2 zAKv$U-}mAD-tYY$-v9mI|J@E59UHd#pFg^>Ulsqx+8lDe3eJs-e^s3SfAX*P`dKgE z=Y8Ji#k@c813&QLgFpC#A3pR$KlI_lKm5ZVKH?)j;^8Ad@*`gyJQ*FE5BPu&c;UnM z+=cSIY;W>^9XQowZ{GyYm#y!A!*6@q+&#6~XzckZ4G&$8!RpUn1?Klzg%KJ`;S_2JV${nH;l<1;?v;WIz;Gv7My z4jmhN!H2IHV)d-~d}t5OU06)ZH**;Iis89koc2ymxo_cw8O~4pv`>3sqoymI?W=qz z#y35eHyM9>im$JF>K*7=-MM?N>!sd(_Gf?g!{>a?=RADw=YHL zfnLbxJ+pgy_VIPk>2=0Cv3B=-HGB2hcXKhHPx+KjdGU@kch9(a>Y4t1$(MY|!3_q86&?LFQu*a!1Qd&jf!+1}j6*twf| zd-)j7@L%Cnvz^xip9^{-H*9uP&w4l2%cZY#cCQ+1aV-yjpP}mVzSmdJyq=nqXScaG zbF=>PFaPp~ulS0ucK~4iwzth*J?iqV^E`QnHh2Bhm+hrF zf7Mrg)m!JQzxt~m_N)%Sdto2y^VNG{zms|PI_GzfeIH%zW&5~T79)Oj+1uYRs@)#w zg`U7-|LmjpRNo1D7o2_W{;G3)8UJ!GZSL^y?A7Dh(__!PcW86hi{`CoU-LCz^WuE% z*M9B8Q%?5L?R$ICJHlSb+vl?0?;+n=n^oA%viX+lDfqI*>}6I_Ig|&zu_Cc;o)V@ zH-6(cKAh*9zUiADzWJNK`NjE`Z~2yoZ~fM9efYL-`?eS7+rRzWADq3%vvc_5FP3xW z=>A;H)!Q!Ug`V^-@Y(a&4xIgem03R5cXMAo_rB}-y!Xy;_TF)xdGFDkyWZ8um!Zw3 zS?#RN_&dMzJ0HI5yT0q;d%ov;9=`W`zxUz$zVG`UzW@8b|KSII;0NA1?hYNh@BZ%Z ze&O>S-|-!9?aA&$IioLwn@@YQd*EH*GpHTVL!Y1hF7Ub9_kr`X;JI^ruKms(-|SJC z{C4d(p6`0z`JVe`Z_m}bGp~m)LwDvocXKm$b2M|a{lOpn!G|CI;U9kSZ(e@%M}PF; z$A0X`-tCb6$dCNU!w>z?553q6yTI4(h}HXI_n`df%izzaUC5p z?*sM!oXd0P`V{*!N4ahW z`{O_U;}1XeQ$O|a(?9*w4?puWKlAXjKl`&U4xa2MfAS|^*!Z_jcEvkF40~h;#FnR= z?ZPc=Pmx!jp6HFw20P$g>GRTeCcV^Cz18FXMs({vU{+6=>2qv${>@%~XTR(F-s-ok z-?i}DW%Z~_ed^Vh?cvMteO~X}clJH=o@vjz*_yL|v-1nT@Cz^go!c+|;x9fp{yopn z|NPHC{M^s|+zT7FeE8a*J{Niy$kY2E94}MD`sj(?*n@Ki^iogt)^F+kU3hB;o-^CW zY@afF?b>vt%J)~_^PBz7y?#BF>hrzR-1~0d=Y8*db2LwL z{k32FwHNdL?ce_Ghu`^~-+8w~W*@G0z;}oJJ|LI8?LqjSTGKr{`_mrS0iOkWsi%7D zZ|8mw?B9s*?Z9L6y{CU{_TE8#-}i3vH%y=Te#^oQzuBuv&GiYbe`gQZX6-!N{Wi-t znP=YTz3*%0WL`h<6F>3dv)udqmwxG&Ud+{e{oC8$`@P?L`2FAi{f9sJgFkq2@MLuD z03W`-Gl+Gy11FwvEj{bm`>opbO3(DJ9njN02mBuJ--`M!*!-_P2ky=HG2LVH_w4%{ zrT?DdJF0h-&wRgQe1Cm zj(=;8lC=YD`S@Pwv%_bInEU(CzAc{T)bf~Zd(jT)rN5czt-lNSZzy`+cfos~1CP!4 zp8lMFpZWH!XTR@!{;uyG<@2i9_sm!C_UpO4IbWT==bL@s_06mI{;&MXue|uY_kK6u zKm5Z#d@=KPzVn?AfAmLx^y1*{0UaB*J}b`e0^R}V4lJhU)^yKa&pbDFz;`FVo&5d8 z-vzv9y>Gn(ynnC03-0Xh&G??K7`Hz6&u8CnJ@0*Z;f7xw)w#WTE^p3P$2-5-U%mIu z@wa~Kw;q1?cYpWAGj7i2{wII(CvTlU{^LJ>A*1si@ZDfP2fRnTPxf=5zY~S&x%J$$ zuV1?Zei!KNz6196;JyR;9B6*`W_+c~551aw&%XV$hduk=Q9iGHe`)sBS)X2p?%!&c z_g&8%eCGGw@B6-Yckg}k^clai|Fb{)vlstcDb%qK_QGcYTb~s^2fRPd&w>6vBsUCU zdYRg8`P8>|Ku^6_y25!~*OLNE1HZ@pi=1N_eN-`4tj zz^!+{&hwo0)qU-6J^TIlJ-pz_npr>y!XD(c<*_0Hut~&>%V@tV+Xtk*s%3kaqGLF&lowqb9xV)_|Dq8S3~z` z@}=qYQcwLZ&|`mB@jbxb1N=LI{wC-haP>K`GrgL5vbDpX^B3Qq`R@?^y`sPM*+cv2 z+4qj}zH0W(NpJR4V?A5lEnc7Z_^zk_{e9Q>{hs~(&gZ?~?9JDl&Hi2Qde_6>{LSCI zIC!#lU_S@?F4#L@KL_N7V{x7M>p}ENe@n-all#-va&~;CFZ517GzWu=79P z{eA9x_I>a3cYgm}!XDa3d+8nJePwT}b9?hDygqlDyJyh5xc{!R-}!p>d++b;&HZox z_HW-h?z;nh4tNK6f1JMuy=V5n7s3;^r_}fyTk-T#PxaRO)pso41N@sIzXkgq=yUn8 z?*TpQSM;3!`MGbu`dhE>eg4gwJ+zPZ(mSeMhPygnmFjQDGiUD2zW4tAjo0@!@9v&` z^Ym=@%$vRcZT{c=-QT@9crts?d%)*F-vxb#@OyCI0p2(LCWOfeqk7wkWLNiOkM(DF zz&pVA0KWx%=l1#QbJ)MP@oe@E=x>5O$IbR??quns`|j`Aw_o1o_)Xd;j*K! zs;i#74%)n%xo6&Iu>S9R-+au;JKJ|Y^XoI;T$}sf|NY-T{KG%|!&}GQF0==1eNXT? z(04)a6aS5<--Esjwgd1u;XG?5KV{Fkwt`E)Ybx_Z`)7Jne-jNGwNBr_dT%ZzkP_F@BV%EeedtT^LyX;{b~Pv7THJ7ezWfx z-JUeRTlBQ;jyihW(evK>=Fqe6Z?=8s_qpG*Z=TKG%>Uy*{^PClpa1!vZ}D`y1Ku0n z0sR)(zX{qoyXXA_k9}+p!d|V_5Z(SHi{`VG&fPwE4|oUo9?)liXK}v+cvklvaO*Rm zxksBnd+&|rFP^{o_*=90jDL^jd%ykjSp*MU)zzF{7u8!wpPilkKKnkSJ^Q}5_w4&T z_e?i;^X~sX-$~mAJJ9FAz60#m{&xZ21I`@?>+7n0U3b1^Osoz21uu;L2Zie zX}58^18qzI6TohQZ5)Cex1VE2#1Ok;XhPg{Ou21h?3lzI;@A$#Bxw>&Mocsr6{DHa zFiJ*KPcKiy)3X%S`~S{8|68k0o%?-m-E*t9UVER9qPC1&zim}Ti@hOY4Wiy7?y-Hl*e8c$d+ceuKl$X7OVF?V+OMr5_7{D?Ie@tU z^GUA>vK|0$d;ocn%W~3oqj&V_ZjT3J@+|q5ahDim9!L$qIsohP)Bqj>+SP!Ld$+{D z?H|W}{3(w8%=@VO@jq&Q?J`U`@Uy_80@0 z2T}vD7R=flxtf@bHGpeC*MZakwmYqJf6@N<5&p#3PktfKu;xeIkN*+-cYJ_jIfpR=A0h`Zo_IYd z<^X&Ed63I~U|)zBlq}qy(w9~d`yu?p zc_8KhYRTey5Nl2NAmf02zq}PPK9>lnV4|*KHFD+};7}&4y1M&bdN9-{VAQw{uvL?$t zLFDRw>|f0NwmIN2Otmm$TD`)$2gQ7*XlR##|MZtVvf3t983+!eB5&Y=IP|@Vh!LL zuowGNOERbQH9y7~#v5us@(=YW@?d-PF$Pt(U<;9JZF^$h_w_mVQ=<{5#4EWU`(a0H zYP&!4%ri@^2*0o&kO%M`#t(ccjsac|B=;Z(zKCp)Wvx0CNs} z&^~~SmbdEGnBfD&8Zk%gk%P&_ti^i{i2Y*?IH>&@PkhanxgX;VYtZ-~V-fOTdvpea zstKFh?qH4W9s8d1JNFZ(#4B;@*e9-|&7XVjxur6~CpgYN;2eN2#WBG10O}5M0P^6I zma}U8-5>bk2lxOnN8L>hW)8rZ%yXjL3&2<%?av&bcpa#${`dIrbzjC0@(AMzbp>;O z>I~+7j600|_#v^6T*zq*s%*g)ine#%*Dm)Hzl`bF6WiM6zxkWLxl~5{ZXdvB$N~5e zzT|m8Tni%ikb|59EN7Lv$G*T9A0XBk1IWG9fQ-eg$5ESc9f*4~8M7I?so^@=Kd$|F z?jLJ^?9a33)R)ZtsW%vR$UpcY{)k-2Y7DAuv3t?>G4~UXp7#-_)NIu6&i%wRwzbWl zfByNUGU9jp06v57cnt7bP@D&ndl&l%4b~pWKlma3h+N2O4616$t-^07JoY>HJNB_HHvg^P`mL5^pTGz38R`Ih2w#dd0ClG4fzAQQg?yH` zD*absYd7=wyHD39c<*f7X6j_owb4|KNv? zePoTc0*|W5cE|R_oa;X9z}Qb-_SjF&M%-dQ$G&a<)vtav`}=qNO|c^J8~%Z>Fb?25 zi~-C8JP%}T$!mf59zKXKBA0!jDxHh{fUSLim?QTx22+c14<>Un?#tvJ4Qe*Wf3Nl@ zUodWX?uY$-&lmFy?9aGE{=pB4eQb}sU{E!It?drh*xs>E?)SWpc|URL+|L+}9kG41 z`wK6;uvA9)L-YaX0CEX6Ai2fYgYY@l9GH6`4?bx*tJdHBfvxaylX(r8 zxmuO>r=Db9$#sA1&wPV%hx~&dl9P}NIgLSVBQ|k=H`|;GVfScz>OSyiJ*GJJ6R*TA z@k=fzrm-zHKYsl93i|Ee{_Q2m`#A<5z-RCse2839TnF$N;2eMtAfM&6ZvO7p`)R$e ztc}Nc8+`ErVvU?j?#2Gp;zj#Yv-vtuFY)i|zK(y!5!;{j1=fC1v`T6;l#P9e3zCsRQ48Z=(16>1n z9f*1p-=qF;4zR3l+v!vGTiiz<@jKh#iw}@%$+<=Qd;ITY|7iPO?N9zE{;@yf4}OR| z$c2p7-S(=gCB_{0*dE-8KWg!Ky_edLc*SnmA3H|dfBoxUUxH4YII)V@j}LG>KEoKm z7{XXmTn8fOcnm-ue84hRrE{?#u*C<+wbTIAUXFi{|J48Ff9C9E{Ki2<@ z|Ewnz^FQ+r#vl9;d2D-RHV$CY4f%N6x79heJ8|#W#}2;U>otGk6}w?S>_|-8=HK|n zHyMsBom)LjiXKqIA=XpPN z!+yjvIiGmP#xK3}(kl9$-}#+vAAZ1b9FNbCJIEof0gKmyJO&_-eKN}4Nym1_;y1p!gj z7USRdfO!3{UH)ec*z3P>{zv|2{?Gjd%>SAHG5(YP$wT-fa@qFa5b=oH{*8T+jGgxt zzq8+V2XkVN*mv%a>psLQb|d#=M`9Yg+V(HM{PI#6;TQM-zCsSb{;mNSQ&?B> ze6Q#OmcQH9dbYxaetZo05@SXCGynJce=qjO7b1E5=lWl?KlQ)u&-yRxKSlc^7yfEH zVXLYLdz-L3ps+e*bVz(N5?+){r0!NozZu`^PQzg?8hhY5qyO@fU(0h zK&%0|7Q{Rd-@^xy$MRLBbFm-$0b>GVt>d5ikM%#U|8f17^?%R*z5Yu*R~G-#_H4)a z=lUP(ztn%&pY>m^|8f0?^`?LPT^&j8= z!S&y`{!bkH`XBbi&aVGD*`Mow(f+>v&-Fjnf0_T0|80NdLvG_>TUF6wZxeZ!*aLs! zFy8NB`@8nT{^WGWJ~sdEcfXs_E3dq=1o55y&H)@>w7=&7t^rvG!1s^?d62CNJ&S!9 zOK>gPpZcHmU*_Y~WvtI}|3{Vf=RN|~f4Tn0{l(n>#r1!#|3v%am&k{_=wMq_(SmOz z+aCO}1M%o}-)Mj07dv84$3C{k=D+uQzt<%D06D;80AmKVgXaLz{;mPxj}O`h><3lo z9Qy!Ia3u#Y#yb94|6v?vJZ4;`K4YG4`}_WnvirZh?h|u>?*U+};F*gM zt(oh;tp79rBmS{J@*yX(M|%yr4Ze}U9PEjGazFMbF5}$KwLkV}OeeO9aohg)fB*NF zO!xx(odY=D_NRtq4v77I9RT~o7yixxmTyp9%gzT+e1IHa`!oMz{AC;_A9G(0*XOan z@BhI5t?vKq#{S&@#r+>#|KOxw?HP~pZ*iZuu{+q?_T+xo z{$BUTZrBeylG7dg*#6|nlS_Z_2Y=8czT*%00LO7WV+OT@*8u3(*MZ=N&%qyAAj?*! zi+}5K)}-cS?Fsvn|B;Ivg})-RaluYybW0wB=-~h(e|&t`sz|7e1P8>2gm`80k%JL$#@+o<^X)aa$9D9AGCFU;0dna3(mwF z_9y?6hgth!?T`FSp7#CUUi&HD`%BDsvVU9y;9e4H3g#Bnn5+R&b29hf`XB2*>bIrCl;Oii}rWzkNvSLF;3k7;UE6t()Yjr{U-69{rn#7Pwl`Q(rZA> z0U2Z9=Nw>}s?f2l4{!ueaEp#){_$;~@7q?(lB>zThEQ`m)?_h4S?Xd@T zAwGTGkNi*klgF_qcE!H7{||ofgC!Hb;2hA6{TXZUIs1U+iE?_|`uRJp_gUxoo40)o zID)6`Po5>-h&$rXb3d-l#JPW*`%%*s*M7RSKQ)BsfYg@M7;z3r4a!;+>wnZI$cfKd zH)GRp3&yPo8(@2KGPN1Ce>?j-_hWBt|HB{tFeAQ`1Nfa9fEp0LiSf^zg8tzHKls`Q zET45O`i1xR+S*sZlQDq!C(ejB;*R(u4*l#eYxHGvKkOfCy|VG&`Jdx_9l$kU*&NVw z0PX>$7GeEA+8^1`*Vx1s+=|$h{qEz+TJR?hu?4Zu*dOihwO_|ScE!Hf*|z`DkA9Td zpYQkpxq%u0`!feD+MhW9eBfsvupgAk=zUQ)w!Obo%dy~S`!l{0XT%$|KWi}k*8Yxv z*Zr}La{$JWI0xYRlIH-7J&ZxrBFsscpE~}L+q#0$AcURE2pdHEV;|y_daY=G^1AI$ z+++Jc{^LK+TE4fkKWm7-21NgiG2{UIfaMxQzp``f8@4~V5?@?rWzDTv`^DOyIlF5= z#&PF<=KyTq$^PU3*MN*AaSq`3zcKbO25}E4H3{o~)T#I@v5yYuW{f(u*s|;x+qu|c z0|C){!a05SZ1W#}UU*>+y zxmfq-Iy`f+R<&QT_V3pI9EbgjHK6AJtTQs^a2>!kAT`%Y+AAP`n(2dO2leXRQJ`UW# z&+*Ut5BP#JHFq5Qe!IxzPd@XWc_0U491O{h_suOK(_ zTVLC%%9dD!uswER?C)fM;=E}8vi$G*pZw2SGBpML;{)(BZ|8$5^};{E&GrXRa0Op# zZEEhg?$c`QZ?*2{@ju$UxX(Vo7~p$Br~$aQ#Or{*7l;~wF^L+G8in-=>K5A{Y$9G& zZDR-99=mXz4m)8lU;B;YKlNWX<9}TLiT0;|=K%9|Ta`}sC)*z!!4q7;mvx`Au|MAP z<8_~QxgUGm{{oTI8dDMY%4Dhu;uLJmgaIOWBo0y-nPC*Tb{P;0`jb6sA$`*Ur_Sh%d zKVJXg`X6gMW$VA#-fKVj3D=8?_NS(#f9C-6E&9Wtx`7jTfgAXNBV#|ff-g98?Tu@& ztixaj*8Hi>yyjn=_t~DdZ>QGB+y2Ed!1F-n9o%;kuLY2!SO;RvV*SVVCr-e|wgSg0 z$+pK%to;+W*gszXC%4CI0N9;;fbH4O91%Z>*Z=(-0R7@y@G(^-zkLaOz{&X^{J;@B z!8NY=f_t3z`F@XX=6#*)&+qs^w7=Ja;}}5hDUJc;DCQ}QSyV(tWf zG55!NePiy&9?t#6>%F~>{q~LG7=TZC9zd>%V}REJxfhKbkob}kgo@^RzdD!{=yhQO^f~UcXT#}Rkhg1 zagXgy_5u9B#J))0Uu-$X{n!W0r_3Mv(FGp@5A09=2Pg0%_cM>;dJp${aGyWVU9slI zJ$UgsuTJ-QW5-UmF8kf~CkNR69s^isj`sxdT!^0wV11f<6v$nS0nB5l0niUU(b+hH z>mXF@$M!0=wjVY!=JrLtv!7#%=aqf$M&6=aW#`z3Y=7#1#(r`?_j_=!m*@Q4=gIXJ zaPG9;Uq$Z6AEN!~lib2QpzL};yeEJ;D)$7iUd`GCxr{Ll{i6N5#keonUh9o>e&6R=tow-5 zSo8H8`_2cPA8P>WOL9Q*zR-A0xL60WhCxmPZ}c?A#-)hupx^1&wuhHHOWyJpEA^Z;+$d#`9$f5B*AJ*IBdM`OR)_rl!&#_yD z{p|xD1DpfA7T{-txCfc%kmEH$>KE1m$Z25D7y!P+B^Ve(V>@U|f5tZ29&E8SYbw+v zj7t!{NxXTj5+A`9(Ff>rP`Qfd)2HJf`{R4X*!Q&_ulI4ym1}*)*!OxL_9^E6D(&wv z!1o0?2l&|l?h7hg59Hbv^$c-Gjw2rNd)psusJ90t`my~TSNI#YC)TmM@1tZ*4Wh~o(7b8qJ0UhAhWA4crQ z8lWr(a9@aXfS(WcT#)P2%vqSb#2kPvh%fX;f3O7?=3Ijk{Wz{7uCAxCGc_r733WBZ z7|UE0AH_HD0phUer}oi7W#oMN!uQF6j(zGQczfJ$75jd^2i&Q{h8g>@2C)6hazOFE z06!nXJxbiG#Jz#U8Sw|Uieg#IW;x)DH!|x zRla|j_+zZXPd!%Qr`QZ0=GUo3&az{ooa88CpL|2?lY?UH^K2in@AV$vabzP ze$oE+fmjDP2gG~Biq{0Z9z=XG&XViU8~vmGZCkI&bkiDV$CY!c>#4YohV8L8YiV3Z zWj&qxiupA@;cJ&(x5h4vRrXWczh5~yFUm=cfsd8Ne!R}dxX-g)#J->HD#m`X<{xz2 zx9#21pY31F0lq)j_Xhd?V4h2lIly&*a{&4iuf#2}0zS4mYq6ck$3|SS6*j|mvd#*$&m8OMF@?{m!`$9=E$y5{$FzG02~w!2%~ z-#*|RKo0W#q4D03SO@T&uXvS)-heePV%ek+o9t6Zg{logUm<;p;rq z{Jzcu-i-U?e(W&p*vAKo_Ako;j7!V~7^mXB0mK^bs^B^XH8(NIT!1*KjD)>^xbeQf2pt+KTbcs4_tff+0AtU#&kOzvn ziuHmrIf>_9xyUxu~;=YM~m}4=GT6nyHo71wf)II9tVoo2Dvwh@r(H&<1KYpF~2hYF{)q2T^HEDNcVp}(@8fIu9JLIxzcK7!^ zS|#_)GG8T6;S1bPL2e*NF#h@)R#s2zkggpVxVd*Y}Ax zkNd^3f7tPlPeuFN2b=@qIDqfQT96oYE$HWknD;PGV?1;%NImB|033^$#@MHJU~Dd% zLo>hUeks>dj7QiWT#*f8Y{d_lUprSZ{xS|TALvC+%WB(qD<|UzXb71JxA?9Tod1{O<;TO zm1ZtQUgbIpK0qEISCJ#A1;`o1BsnC`2Rv`7T25*cYEjSik%{Z8Ugz~Xf0_Nu#(-h_ z0R2abIRII`E(pF}6QHJ{w!zo&d190`H^x}sD^{Fq#kFB-t~iHcUhXlUbzAZYb(_~u z81uPb!ub?AuswFCo`S#_8L6?!4de*d0^|;AGS6FlUo$mJzj9KWAgAY~zNZU0eSN2x z_g(X0dlT_rryLONUtAOLH~_ZzIKCd&g;?{9bFFxNfH?s*A$F(k_B@}l!gD=8AK`WL zIF?fHQR9*0u|4)?t%|rZv5(*I55`^W;97tj5^I4$<@8)Hj`?2a@pD}GBfjapA7g(M z_II2Y=M&!Vw&(-Yf^i%m|B{E9gAtq50@UYzhY;%^T>mDY+Xom680WD&wH`U&wvYEp z#CIW6!?W(-=i*-=-E|ZP0a=ufN#`7-yMtL9FdEPay7D zmt)NE{5j6^xev|HM#VW)v^~BF*$0R>Y68Yx*4mjTFkZ!4fS6^Sh8SkOp1Ec>ax%~7 znu6D%nFD!^&tra+6Wb#fdA}_7?FU64D6>&9W_$f!)@Q4IMf-akKo{`Ew_Rts7Krn0 z*IDFqYHhHkCZxuLsPh@`v3xxQqS|rd(4H%gXBZc^(?1lwC%ysHji!5{;hHV_UhH{9N)^$t@cOzBO9{X z2RsfqzNm9u3$QN08XYlAJ;>S+*b-mVTKEBL`o4dUxHqQE(Yda}+6lEjn9?`)!}i29 zc6B}_rXm%6fN>z!0>yO!uMe}fNZrb{jCOMRogJ~>V}8llLR}ii{8;aIihcV*(Fa=D z#`}Bm1E14PYunRWCfh%b1K5N3a-D^}y!K9=MJ$si@d0uy@dLgPb3CrekV|7cdEJb@ zs4>ak_!+#&`NWfB%D5JNfZwSFJPs6V0qS$)gqQ0<>Q;Q9Oiug)AEjPkPQn<+SjU*h z*vCARI)i$HxX1SJ#~&>tvC}H{TiM@s=w!2Q{GixJS>N90*0y*2+Xv9e_&UDOKd!lu z7l~zJn)qUE01T-KAnHT>0^G@^981o3E-jloW7lZwvh89Hz!unq{K0rdj4~&{zhd2r zocJnZ0dbBWK$a69CGJ^A@|=XYr!U97>pifsti(&F+;98SM$Byow?!Mq@9lgb9>=!( zYt=>>qU~*e^Z{q`u;*K`)+WA)4f_Ds;23AQN0WLBB9<5*z&)<-FpjWx<2t`Amf&Bs zz3uMSIe?sD8Oa083+=1USL6Za1mr8^#MVVQ@lkB=xaVBP6OT>sDAuI5JFT}n<$$6O zfPc|WMSJqSTl*LLDt>Qgd;6*D05EnfU~C;<_yF-mOj8eV4F(LE8&G!>OVnSne)il6 z8T-W(IL z_p!A8-N$eoMBi~t@VJG)lC!We*Y3DxfIlE3`GMb9>#&^oJH7=U$e46$i@J34y=bSR zAMm|XKb9-vVvLKev1-*uOdVft!It=S%}uP3XNeou@wk3Ryt7RnWd!Z^dKFz8i;j`9J;Ect?K1A>ojGAo;+0^nrIu zDLzX}@7(;4)IPkqvHcEh@6We4Y5TOviHQe3^5Od*zW2i)y8B`6=;!kjjgzJ(^zR*~ zPfVzPecG-;)aiGR-LWmo$5_tqw8h{0{Z3!h1!w=%*(-?pgZ*coNv+VRN6tO>A8U^J zkF)RmhAjQ^jpv!a`=^D!`LfNv^Rjyu8@~<8AH9tyy3==&%#*qh^4e;YqaRxs^Q^|p zc^7%W3u0}jNR|WndbI((!4(}M&mymX^{-|$FK!TPWstvHHu|8iXv=on@J4528F{*I z^J>-lIAehAy2SwgU>5m;Eq%7)SGM2wKo9!E7u)IW-;fi`B7f^sCd2#R-*~p5Czu0nak-u#K|1w`J-`nQrmi^|7?AE>L^F_VmcjUA$AVbj}=3UmB zH|Ic{k8h(B*hW6>wmB9#IFIv-SQuZo*0so!^XaD--bJ}09@fh(e7)VTu0?)j-;p1i zfQ54<{Jd?9k+tmnD)u1*c$$!>3Vgej%|3ySCiX$y_K}WTIp6PP{o(GrKKSt6AAIyK zVc6eC>kq7J#Px@N_=kU(=#e(*_%lSby`DSh@$bs{!W-iIpnZ0GKD;)I&duo@wP)x1 z&Qc5UJ5;4yr{{f6sl8Bispzu2_4XC|4zZtOI2Jhvl>>~oh%OLaExJK;bAfi~`)<)~ zIq`kF2zii$J|_AgaO8aRrT^PS2StZOcjmg!PjKnBY$Lu{{hi<5&d?u0lHr-vhMU@-p^_K1<|u2^Rccz#``TJ z{LzDWxIjdHm}o(#Y5&=x3q|nfJotY}1n=i_`fB*jHuS8>vP2n-0XD#<*q|K-=#QN6 zC!WCmSrI(UpSJ8fGMX$y^nvU3JG$Z9tz>{d$Ltn?9r8!rqYTAuV`15l;ZvdqM2AGq z1(pF{i?#rN^9L{Ve?rtP|DrtD4H@WzI7Ehjg@dwW68}+yUU#R`$YmVnU`gvT04CYCTJhqMHfH%h(ck~8F z`a-Aeq6^nCvsZK|CwL>%ts=03AG%&4LUzuB zukWc@EqEd5xtw1Hc6-^i5gpX|Rj@jt0Jn^wNr zrgmGQWk0`9u6aHhUhJa}y3UG@h~`BnMW;%L{ro;BIxLzIO^fJbxc*sf{9`|KKBN|X zPN`i~yQH@9>D+Ssf{q81BWl49{}|3VRSmwjHFn0n4V|@7!Dm5zfMo-RYUEw^sljo@ zPY=sJ4ZS$SKjY{)#(gml%t?>N z8cMmIt>S~ZO&#j{LH&=quzfwS8kdIecOPdB8S8mG`Ku8FtMzPUd+-9c_41(}YNhM) zJZKpo@EYg5->}=VKa$5=*MqCESue5Qz@bsYG-{#Mx|Z1=e<|kCasJ-Ofy@5AvhG=l z31bCw{Ppx<9A^$!omLYy@8Uyh#!7UTQ9q5n!fm;HVvF4%wE>%NA)mj7>G2d=~g z`#1C-Uwaq*75!VU3$3>QMvi~tV!h>m;-BlyEAzg;ue3kMZ1nMeqt}1b|Bbn?HrBzX z*ZRM)?zKMhKl7NzI+)|V*shL$azG;oj=%Byng65ztZ-|@z%jf2|1@G?yzNij z(|HZJ7!S+e8{^>k+Ml)ie%F7A0mgxGUjN5_=+T~*?cF$T<#+DuX{>{emmi#z+>Le5 zmHI5t`@FwBK8>|dfjCAV@^~d_^fQ1 zN7rqA2;CW{+Shq2^;p)s7pxk`5?^4@7#FoM!cYHpx8ALAX}_O*z#M6m;{v&kc<;~d z*3UTf-olr0cCD`!;&+U3z2*5<`Wc7*Tdpl|ogH6{`8fNk{Z+N-OLl#az(E@oeHwZ9 z|KNHI*pR>d9pC1-!ScE7+HG5%%~#gdGyyLD(A!Bx@J2RN7IBe3B(`e;EJf6L)E%IbZRG4lo63heOtB(d0S^>fPYSz4Dc$(LC(LKyEvY6&6oZnvEMQngA)9kxx?quFMbenB6wgM zFoLJC@^{X){Fcl8Ap?9ZgZ)MOigA?LkNzWna6lIGH*ahOiC>nB^Y4s9S#F~r2sw;@ zlp)BG$!2VjKl)(gpZ87fYVtN7$fw_%7)Acryof<2Dr=Q*x3I6yDdSLGugJ2*`vT@ILwSMNGW0K)-{^kuXIxh2Fbc9#r z%{KUOe8k=|AP@Vkn|aelJiN_*>yAE(=}azuZ^yq(_aH+y{vbo-6SvLN@^LIaL%-mI zAGG4n{-WK@+btO6eR=$B)*-fwWiW61EHc5vei-p*AIBB#ZoY2q2gu-lrT+?lgMZ}B zaqz~s#HYz0AAm11ag1~?9e2&!Ei!-;{%9rRU-$i#8SxgX@K_5zgUUH>=l8z;aAhA05BVu$y8iIfKmF4a(9Jj9l###3ePzE@;S1(< z%dTA$yOkFqZJ*BmnS#0~e^3m3Z zAAIma(KOrnG3V&_GdR!kbSne=a-QaU27S`E2p-e$g6HFpJ$5amv8GA-eg(uaoCh}8 zhCVyV5O_EBg|GRkrEhp$|MZhj-YD8h%Xbkil*z(z$OumGMTSoNImTE=zUY(poAG`A z*=Kf&_U8O|>NogjSiqO#A^6h|eqmd-lYwKfTh1GNSLtisMgHb#zBy)@Pk@2+%52ce z4%Qu;AvgWxdPS_!yGap$9iQpVxdmDw1Ns+bu-%1IQ+MNy4A|IM7h3jbq~mV149K2S zQzrXjEB@@yv7hGL$Un%?r{*v)5>{j5P`Frd$=MBHi z5AlVZyf4}aT;M;Ae#W}!hq>>W|FuDvCjWvA8Sih-zql6)GQ{u3WJN5N$$<`iZ0{L?)E*~+%~Z9bDa5`r`w!&h70^7fkouaHv7OIJh3Z0 z%3_awjC;nv1Apfh_&2xVg}#9@`H&gkqi=Y$;%^_Y|K&1db|~nNKd+!%j|hSFsuWvJ@4WLI(RugXci%L`ch2$o{kGN_et`#k&U@&gho+%l_=R7%1p4GB zKY7LDk3W6|E#HwxGMw2jZ{#oVg&%w_fzL1h@-JT}x;k&!&c5rntNEQ{kvZC_TUj_K z=bd3%DCTy_^;v}^+)=Yn4a7pe1=NW@ey7N17 z7WL2d&hU=dd)v4mg9%x{4;*{t&+!reBIf4%)KgF02JL+v@Jvi@t~dz__u!xBlq~mwLEzt%0iJ<~hZ~VQ_;vX5ZF&+$!gJr-c@XJV+i?*OUvELE&wg6VVHt>L$27bO zlwr*|{qSmcPHvAZFC&WxTviar461KC8S?y;)xm5>8ivm8WJ$j*&&kFai{95CuGo<4 z4?B16oVfMYTTd@iv^~e{+O_L6j!`?ocPJjy&o+H1);I0jx9`jkeBi*@CcbO`>9^m0 z`z9#={ak+MJHJ(t7uxYRU(0sr(1+lEZZ6va0G_ra$^M5BP{K&FShRpFOgIayh2J0)Oa*KfJ*;=ZRkD zn_q#?4f=k=m%sdFXlG8@J{^BGvVjeKb!!8#0yFWP;{2S@t&Ep>7F&+Z_#->`?YgzQ zF&TS(!=?Bw%~YTfpbOC9`^05&1C|cjLh5kaV|Ec&u+(HY=o^i&oa0_>y@`r z7RQR?Evv&gA6>w;TmIOBPGP@V?_jiuPUSJ_v zY!BNa)_$&$R@I&Mb@b_lw`FjfDRv@ms?yi|iY@)aE7G90E%NB}-8#~8tlLiK)%Ew@ z*B>rV>kq*Pqh0X@;vkQQ%r~m=5Beb9h;zq0E#HYp@`7`N_NVm+joIX6V?^GHcIsw3 zVotuz^L`wE;uy4I9As?PSeVt^*aLs)hNq8#H}(u;OdS4>%VRSBzOuj77G|vBW9!73Mm{ z`Hk%mc&=J|;J5s^$bZG0#QbAUX3U0n34hP2Jilw&zLgx7)A=VK3z~CK_>knPj0r&zwB414F4~j{020p30kk%jM6FC;m zwFmMg`yhM<-=Wq?>krl6A3&aD+=!Ic9|psD)%wH4=@Uy!@7(+jtupM-w>N3~w8@Ey zhd=n>hwgg#ihp)hzbg>=`yVFWG4YP6cbu+Xp!7)yBm@!y34w$_LLecK5J(6l1QG%X zfrLOpAR&+tNC+eZ5&{W;4ixCDt*Qo0oijh+H{4yuDSY}nG+{YoYFnvhn)vnADw>L=m>1yym?Y_e^_z9AX^3 zvH4Z;zid|Wz>TtZPV=4bd}m>5YARh1T#az8_Heys z>BmcpqNR*VY}sm;)_WK8YX!}FKx(C25~g1N!*rTER)>fo;UpSU;m;SSAHR0EtEij=9- zN)Y%bm;96I6=Q#&@t+$!s|6|%EOoNx2)t+eduCqy;cMBL@7N!7{Kqd2e(>Pznk$nS zc7?zbpMK)Vim`vN_|MY7G2KIO)z#Oeb%CxdFoJ`Y=l9v`47Y6AGI`fscO91T`kLow zMsM@q{MYAC>9vWI*{c)boj#*QVA=lDT&HPcy1stO$Buc`d&$2PipdcF*i=lW#J?Xc5TcU$#Ho&Go(#bGF?TYscd+9-mX} zPp9}Ft;tt#U$*}t*?%Zwe$hcx1Ds6pzrqTm`_i!gq-=j`*zr#;IPrBoPq1g-jB~)~ z6i=Kp1WuorKK4T6Uaw);zZm=et{Pw_%K^y@qeY-m_mTU#Ps`@1tc~M8+x{pI98nHP z*MryE`uTWI?CHAR?3-`CS^1pLHZE=R>&o-T=jYd2^~7&A0$a}8GWqhiUtaaxMyc&8 z-ue1pync#n%gtI4-%Ow3B9OWzvukeu0>kKw^3A!;XKo(4-W!pD zp6Qy_a~&g@=M_x8;w#UOZA5$2L1)!pL%-I$!q{DW(e>wf#wpPRls11b}T(VI=GE{?LliVGugd78L(!eRu#Tc<8=|vU_@nd(&t#dTXxv^WzKY+3^7k z-Q%fTcVX?N&hejn%!1Y^W_na_V;`Ph-<$v1eB)V;)}dF6U!0fxjl(Vgr`TV9hPyqsBmdR$ua#1w* zV-)|Hk17WoN!JHE8lBj0%8Ak@xah)*rsLQ@Zt;&Rr@tq$(gDKz_vdOJ>RpMq zVy>6&2VZIIKEC$F;9IntF8YJ3nmOLLgReL%4;Ps#uG#@O4{E|=a9QmvbY_#evq zJ#4qNYKi}p1D1V6e}7}$#J+tOZ<(2TXx-& z?)|DFeaT;_r`DUFSReVyf_!DB3aG}S57|?(-@K>SIINfU=rc#>H9y704nE1>5*>CpY`Fd#@t?ihS@+NA-Oe94 z^zzFu)$TdaUj4<#=kZU?NDf%n?-{@5>TCF$%-M5%%;na@e2%{vPTc!C=7x*UVJo~c z{`E}lW{5n%+R@s+`^`aHVH@I~9B^vt{Hb-719%@oPSY=a^QG~(m;QX9{&Fa^aLevn z4sSVUOS<1@*g2p@|N483(?^dUJtAB1$?Q^LXpW>S{`Q#GJSTnKqlM~YvpbLdNyYxU zsM&C|qrZ8Sd%366{oM6%!&`3A-*i5zzv(Y9JK$1cTC=bkx`h9@y;<}gL{C9}Q-LM~c{|83;e%4+vZ*%OT{-)Rwz0YC! z?|!BDucd3|?*m^C;|+g*bf%UdcPSLlYVtSL+r(Ave)77c+t>f>^;0wda%QaU(51Q7 z$6P_K$Fn^SFKT_+QZd=CiE9q|cZkurmI$`v!O>Q+_yBb*SU! z?Z3~S7aXhi2dVC#^>Yr^V_aH|`K-N8ub1`LF^k^8#Vv_{#!uydZR2?VAn`9+WG!YJZc{Ji+``{#yK5*{^;Q}<`309M%p>|YUpoB%*8og&E2yp z-pjx9^2u?y*W`QO{ocuD?DGzvnt$@xU%chkTQ>Bwm?I6$HD<0HkUdLG9vG*OazL%$ zAEswt4zGy)@rZw7e`bwcFtHmk0{XiW*}LV(DgKdl=g;h{Reuh)yP)~VXl53M%Q>hJ(wG5CAAXNT5 zYV(=2y4M5v?~5m1oY#A1C!kS(h<~n=drf5=@Kfy13%7J#f7H%X2mZP?pd7I6*bB$T zYW&)N$Ns~0h<{xt=b7Hx{(Y`uKH|QFI=Eeup(Y3{UoV_g4ygUTLatxc@~-)j)~`JC zuCf2*xWxZcpMNUFeoexzHexKtKlchvo|xC)ps4XD#6R5~v>hk(Y^$(LSwX)Nwr*ITp}dG@)TK2`q~;(t=lDcAm){yg@%r!RY_ zLe+Sr{?-=)%&DoXl>=(;+mtDK?*VIBYxZ&c^L%oR%cQ1g8u!=L`&8D<&>NId9{V zosxQMc0Fr;UVopvrk}U|?yaSqPVL-PpZg@bbiZ_1%@E)oA+2xM_I=e}5B$*GA3Ch} zAr2kA+1c5dnudNRRv!Bciv2ZR7a2P6iNVMah&iBczVG-~yU6=chYga({#vh%D*hK0 z`{{oEVLQSG>t8v5zgbha@X!91sB^$KzxB_p zK<(DN9s6$IbKQHYt$Ua6kC=YrKfKYrPulnVj5;A&Z`xyDOZWC|_#ho30NMR+U#s{1 zZm)jt)sx)El)Wp2*d6qf$Nx<`ZrWDo_*d+o%Jd#o-*kRLpbY}b0nNWfT*vu99{-KM zO;KU|=lNtZ|2yo>I-n-w|Kjf3$K(A-ZCoq;GzJJz1MJziCtC}sQ~c|1Q=HN}>UmG# z^4MQ}{7?A$zwJa~qd;T_s$bTnwxox3G6}$Oc zg?e@p9Gdk&oR70z`GWpc)lOG3j9+aBSm16T6+l}k5swCmv_j$_qqr7Q1&-z zMhQb>ePB{~fOo1btY!SqAD?f``SUf3Y>qNYEE4Yxg}}eK{$J=_w{PoRw;>0Vhihef z9rwPkb>x7@*wfr&&)z+}_rIA3DpTAYEsy<+v+tdq$=4;;q}~}h)=7ZcA+T@notAGMv>=~oCkV|fBmhVMGgJc-rw9x@U%Z6&GVw#yjwnu>8t&DdA^^;y~e_^p5Iqt)?0Sovdwv*EbjAtBY)p<6}qNA);9vX_w43< z_|5l)tn%mO@qS&*@2j+?^1$?cci%TB%$n;1#!r9m;1KU-N}q&4tr5^GruiEa%^F~N zy?a6T#x&RUHbQ;hkDV@>x@bbQA`htUKc(28O!40jqUoG9MSxe#Z<<(sMo{;{<#E4a zT`$FdXRBO(+2z}wdh)3wYG*nhosLKd)CU2@{Glg4{lt-r&cA5r@8qhF)=83tKtdoP zkPt`+Bm@!y34w$_LLecK5J(6l1QG%XfrLOpAR&+tNC+eZ5&{WR%F>)oD7zrhgLXz{R8-^ty(ch4L=FEy(c%6^`n@-uo(HNE64YV&6_tv)BEq( zKl|c|7qgfT>aW;*^FO?~Ao(Xpu0s+aAut34W@cul-(GxsafQ=|FK_(C8>f!XAD`Rv zzCE*>&fGLDn%J~yB@IEWYP-zie|FF8Hqjx){QTQLetSW*1an9cSGsT zyWd#FmCQeO#yks*-JhmXCG zUE}KH{yqK9y7{XoUp@Kp!yi9<&6U?|6HTtVo={ki>x6^j-fbTH)4TWVo;`N_*xWE; zJ}OQ8A3HvOvXh+Y_k_S$Akc`vNyTb*tt2R;^*(hr$GzK1#`*;v&sY0 z&0Nq1vWqUcD2sWmHJsFZW05(B<#xMP@vj;n-3!phR_UiPK|t}Z+Mh9Wt>fQ$fR5diHRzY z$=BCsn$j-E|4fy9`t2*k|I+@S-G8_#XWB>zj3ELa|K!J4-0#i(J#|J=JXROVfDUWorC z<?MU zpZ6Eyd13Q8?bp;!eB%W7>u2{K(vJGWy6K)hdyb^|-*6!`)(Y!c)@;px1C5a68_j9bvCKQWed{jcLaHCxWvGO2UAeqL|b`WZ*p z02{)Al9FSMfXDti#lOb>?(V5=Tn9KZ?6~f>|B$~HR^7WW)?Si?Ul$03^>ePFy=Ue< zwYZM%_;UR0*}La3bLfsA>7eTXwb8-y(|+jg4|SZsbkr{$kPxU6fkOQM;xGMTt=9bW z`00NB+|Xw-e}>;_WMplOe{C=F%wv`2OMN8-20|dj|BjtIW=9q%Ef^C&$6q`?-}U-= zu1_8A1wf@j{4eR*=Tov*x+iELr%fkUhQM zJu_FUxlGV)Q8}RUcc1iO{&dO)M}XMhbK9OZd%icsKv$c*o2y?FsRpRcI$%-R6DLlb zOyhsQmQCkXg@EF}`EHLIj{l2#?btx;Wv_nsRo>-U%a3Y+^q#P)969yV7Xq&PeV0l*U3kJi(d?hxL&@v`OM8L z-}TWaI>oN;(u_0qS5KyL`xkMef+ z&fTns)$+a=VpD%pcX7wfJ1Uv?7lAtV{IR;;50IrL<$#lV24m&lKqxAaz9$5FL11P4 zPrvq~*J^iG5n+|_0OGfzGIZe@EO#OYJ}i$mb8+=j-P0zVAqF_Kfd9igtN&_BX*4`*n_g*=bQJV4!;+yM!Yh zwvG`{?APjjc*XqV_+QKSW5pVvC{LZf>zd`M>;Cz5=~>}*9G}BfZRTIjY||LG=&`?! z<6R#A&39xE6OY(8?-H#e9>x5l^@@`z2Sm%I?QRet_FwAj``7;DhHe*2Misb#9*uuh_TPuTJz7MYTLD4;|lT5HMaZizi%$q zNOi`G9H93HrW`QV5js5KZ@PI0@9odVePX}v@qgD{cdh;3#w*1CMHgm&Gpsg$FQ8NY z;r)Rr2MjNU#{Qp?^Rd-DwE4` z;`W(mpBeaXc=U*1Wrtt$&#z%Epm}c~I*r>$!^Xm%J^On8`>B>U3ZtwM9JWmi0_|Kf=k zSFAAy^Ni)DVt?rG@LfwAt+-cF*J57%!&k>@J+_k*z4c$;T2KyHAGO+=I>unk6#Lor ze8$U2o$OWT-@o(S@2tPSWgEtSa=|s%U9(L#7`t`ZR(_-SUsC+1b-}^Hx#zPL|FhXO z{dE%miv3el=TB{jTC!<77S+b=6UQ31wdSb0IDadamzUXGdjtP+>MshK~6wxPxl z<$%NExE@%D|3$4kZ-|^dx+rhW`MT?_tNq`jTNUty9mKz$Ki!b`n=1#@@^7dYOtj)_ zjQ{;VyFc9{+&Vnhq!UiI=FtW^R+fn$6sdOJ9~KP#~Nm#G5PGWAA=-fXx5M}S48;A8I;$N{p+q^E`S;Y-= zM3w`_ApSM>r{{rc;+1*)H*0{pyU&W)=h}GstU%zN58rdB&NkH8f6_ijn?9pOAlPD3 z2@B3m#3t#=X3m3iPu(mlMy)U?aE@O!fe)!G$JC_$F3w2rFL&I#&0~LNypSBBw62S4 zSO?6`>fOufQ)>kDH}OVV132#UmT^D%OfMc2;-59rgC87yO~-nKhW@dA4^uDjkNdIu zn|KqWtN}v&FYcJxkz&6BhsvK97|Gvb_xw%kOxb$?D^MqOF=PbfFZ>Nzs zx8iT~X#QG@zt1;hsMoA}*>u~nW5-Ue^|b-w|F+w1JK}ZbHB&e-8VCXT%9{K=%3}O0 z_UBc%PN(bp1DUVY$&<>fb8DFc6#okz`^f?6Gad*qUZ@5b`g7(n{`oJN9X-{BSL)8m=GsV}5?8Xwlu{43*sf^`=D zK9X%bsC|7t*O1cKzm|p_4lmYjkDaY7>>!>_gWJFr;B(1u_4%`BTkz3H13s0p07v-(5YW(i` z*&v-Wr*oTs1A0Jl(&@uSps+r0&(Gg;==HyRy{mNrp4HOWpRVN$+eT~He~5o-fZ1-> z{r>Hr{M%Fe{>}1xUfj_WpdEr=m{N)6fsy{lPmw@ZirL{NRHh zxj#Rxx8^4%Cg^vHfA7%E_v(MlKfG<^U82+0P!D*WExJr}gXlWZvCOuD71up1=E|-YV^=Z*7qCXe?r3e`w5!n}z!PF~%^Wr@8*eU|s z3q`v{VE$DRy#JHvKjz!ds=ZZomFP0jrJ@T&^l_#L`SDTuFMim^G2nHD=w{LFqJyG) zMGuJZw_g?gzUWUy|4sC_qW>X6h98JNF9L^;i8yD!Xs76U5%SPKa>NgI-7Mnw8PVOM zPl)VWU(z-{MvKjPHz>OQchUdM+c(rA({m#H62eCw%?WwXZeA|BvW@=iA8hpGE&p^d}Fc6@FT%$EMFekb4*&mG^#6&F0UY?izbb++ zI@$h@X&aou9RB#h7SUy*8$|dAwzxxttsW5}GuT0&7QG|_4{Q!^cz#RtCDDVTgMsj8 z^rA2DrQfSXw!dY-9+!%)6m1t>mlH90s|d_2(-*Z3Pi&2Sh>gcY90L)<&>_(Q5&G^F z(f70n-thH@ZT3xxkb@SV02Ac6R&-UqjVxgBiU_~LHhkY9;v8ZLOrbMGrcz`9ofREuP4~Wq1JQ4hc^+68w1JBQk@WrP^_{#+%ev7*5Yvmv81i#OS z@Hyh;X3@0BKBdi}eX!{c5jIDETJy)|!~S63?V?9T_}=G4$TKAxrtQ^zHg@BK+ePFB z@&NpIiohTKr-`cm&?mS%N8G4w^e67g1;qUIq6`e;^_Uyde5j(Sssn+b_C8#JT-^(Ajwk zJ+_O$oc@V7_%ptILj-Sf5CkS46Co!ugByMI`hhpHL+6NY5YZpl9uQ%B&L3UzlvuIj` z9MC1A%S8C(RU&jHHjtHLIcF?B@64s_di~+9N5H(l&rdW?)9OQ9e^}DqL|tO=lRx>B zOYLYFSeoBY{nSq_L3#&&OX7EquSyR3GSAB|zkCV0;f5Plk$Q=)MKAV6ezu$iE{;KQe&3?OoKHzRmN-?pSb z>({BZtvI(E{@9{w{VjKu{JX`#*tfHP#2@|nPJS4+|F_CP=1&X~E3IOn9R|_vMco~L z@Gt7_SnT8jjzjY)YdHqqk-vGP8$4R^HwLyr5d-?PUln1~C^$9GsJ_r(5@6dU~fL{T8;y&q70V9V}p4X`4;u=2KS;o zWir?v^lw?b?bfz-uI-1nWx*CjIqaM8DH6Y#FW3#jyG#b#guZQ?$j>~@w^f|E?_OGC zVLaebM*LnS-!d7aEbueWVe>7MWzc=A)*rTOF!KEm`SvC;Y2N>E@WVfMm%jJ)(fY$X zbmDgX%kF>J-d8W(p2XUbiS;xSYeT5S+8FIwdCNAx#dEx^-DC5y%;;;KXd!e)U)B^^ z*JO>3mhY^av2M&Uv_99kxnKRXW*>NV`{7v2YCX{v9nsG%YlP>CrbN?u%eL1KBPMJc z7kq>Mdyz?pugS-9AtO3tQwTlL%lb}f`+U*GqRR?&LH^s6S_o`7Z>z}ofvaOgo3;4Z z?)a;H8=a9KJx!Nt+d5yVZPt#tPH}xsY+oVbIM$bGA#j-z;Wyv_2KF;;j`V}>5H=xZ zu{pXUGqR(nb-hm8UN7IS?H!^S(aoY8^Oj?vt3<{JOuz%b!54@J=LUF>@?+hx39*X5 zA}4l62Xxskx*;cTqw_A&t)iWx+w$-1=h*Fe%Qk%&gDGv}17HmXr-`cm^wKBCVte#Q zE^L3X2%94}@?&Fc4WTP~@6Tzk+5@74qJ1Lcz;T?%`SgP?fa!%Id;qNB3xBApPp^HQ zF2>m5ED`ZJCBpBCWn@MNeA{+LN9dsF9?|`xheh{_?i3vmfyr(WzQFn0Mf8RK7l_Ui z5ohp)f3Gs@luADEg+Fp^79rD=2>avv+jGK}*wuQY>!YH_bNZB82pqr!-v9&qfb*mE z$H&eQohcfI52)L&KGy#X9gY6jWmcCZx`(lq5mNf`l0)0MO>@pKBMRIZSJS| znCPGgtn35$KmJ6Y7mB7tV2%Ex@&W4({>XtntUq=_)@w!EMLR{<6#dZ;J)akSTlD)P z?&IM;0`6UULiB(L3~m+OEZQ!jFLL0;BJ7X;#N4R$hd*+l|5nijBJ{sXgw2p2{qGRn zD|$@y6%o3>CVE4}J!9N2_JW9edL9;`|Lvk1Mfj2Je~GrIMO#FhMfe^zI4vhNYx02~ ze9sh-i;-tagniH-dAEzuf0yWh=s^)N|5eeCM1L*%&!PpQdO(ElbI-<~inw>~e~A9O z2rTdcasfUOkMD05+4dJ}8~fn<=#Jdzj~x$+J|^NGA@nEi(f@ym{#HZ`{D~;qf1keNJ3B@6 zWgl=1oTq)n05YIIa`mEU*cLN4rsoHvMW79sy$(Y>Nih@KR2 zzZN-<*vJ3TAN!*}_9sU?C^{hG{2e0u0R4k8zJ|{c180hmrC0mwl%DY7cs}S4-ta~L zDbe{NWV%9xpQ1l9-zvh6_%nWu{@)Tg{_z29k3XRQZ;L)HdQ^0$=r$2P1eWxRZ-D{6 zHYFnVu)p=!=FmUrkNwdf{ISCYBI45a->z+90Q=n`dO(Ez8Q;+H2cmy3BJW}QKNo#p z^t|XZqDMrBMEDN+Z_nvkwO|adY0*{@`eP5|T8oeM2lI18_J8Y-jfjCABJ7VH9~NPM z;{OE^V;vZvH^eqI0rlV$BCxtgv|oh&H;A^2=o|gyQ1dKe8ZK zwEvCzj_e0Swm} zK45M8Bf|xvOGVZnS!YDZj{n~;dQ|jT5jx@n-z&OT^q}Z*k@d&k_yMi;C-%{w;}3{# z6YUUTe~7-X5M3z3=g@z%Xs!Jpyz#qpMCXZ+2mP@TwnumTf2U}_=x)&iqECq!1HLLk zXY|MZ)P&d{|M-OH9??M&`qKyfZ5Po$7=t(ZgFX76E;>!L79aRT{ok$cQGaB;S+q-p z{OC_CKP*Cja=`N<+nyL;OtAkG``~p@Wc}%n7{CY6{}K`UpC`fx&k(J}2i@_tO`8k$r%E=x;^@2IK(vp+CHl0h_Fq{_p~C=o}F;5c}wleel&=MYoIY5TW}6BK+F^ zkNxp~#yxQ0JI{#W8`vNH$pHsM`$hCezuQIEi_m{sbe`yJ(ODvFa++wEANogs;%&1C z?2!Y1wEp-$^6nKK6rl$;#s1EJ*q@wd!v6R_^`VaiH++YFqW)LuJG?Fw+5Xr9xz<8| z{0x3zj{VUeS&#|a+$h>DLheH%^nXBvUexx^fA~H6I}hUj=#Tw5{(e!^pMGx;T`Pju z#Uk`ahRq`EKkN^>gR}MDqHSbA|0_k&|B3wrBJB8}=o2FJC;stwbO!^7?;igi(>A_> z{@@qwkN#kMNlsvo4<{m)B5Z+8&>tChiI5xnp~J^S*p>W7{p0vY zcX9yVsR{56^yhfnf4{cv|1;Xg{+Eg1IVH0FvCCR~&>yV90R5*#=Zh{Cp+7S26x}8| zC_;biY5zxm;+?z)p+7P3oQVAQxQJt|KR6OkH;S-5bfxG5(Yr-wi_Q`u*IIn=0kFpB z;EfFUKlZ;`Wc#E40nt4o^vC|>Kl~qCqr3f|SRnrs|Bs1mf3Vvpvi{q(4G*w}=aguR z2w9K`*@pdr2Ux?CyfY=j{@4WH+%CfY$b5$g{T~n!|4)dpKeoo^&H>avwm-+A|GlDv zqT5BgL^p`&|0)ssWB>C+=ZNsdwTOSQ$uLHL_`!Rt=wcBuiEPNZLxkMuf4As<(Z@vO zKYScr(HRUN;vZc29sR+J^Ee;>NBkCEJ|151I(^`Dc zf0GFR2m2|JZGXMC(SMf+|KBgVM}$7spBTsf-JE(OwbqV@LG)goyZmQiT5KZvV&r zzbe8%I2Ho4J4IkfEP*LL0H3HoHaJU!OvBcl{vqstjtKlO7F{JmE^LqPw~6+N(Enc1 zgCcB;{^m_(!T#XF?;jIkd;I2*=zs|Q>6iYm7ok6xgZ);~W)U)3K5Y)|gP-AV z*8f6nUn#mybb|;xA?qC?>;JH}u`T(HJcquFeXN5r{u2x6Pkn^`5SUqi@U;HhwGFQ; zL>Gv#0kYr&$OZ<(`q2NoMDPN4_+tlTxk@x6x>1C@w*URwMj!M?KgM}%kM6IE@B!k0 z`u8)UM@8WEfas9ufQbHfiSYmHMewrz)7mBuV-C>Qp?uK)4AI#la6exZ^+z^jL{?>u^NOy7yi?V_C`^uI%d9{4oA z&lr#Izb5*3f&N6>_yE3vpFASM{||_Wqg^8O-!8gJ1TQd0e|!*GunF>EqhaZ9`-Ati z2>p=*ThQX0*dKXs6CDseD8l~aeS92S|EcJ&MGK<;BmxWK;j1EI%Q;n`u)DY_v^aX zF2cY0%Z$ zI;6-P*W!i$=s*0|6_LGpn3K7oQ;SY7I=AT3B3RP>8;kgPe15#>^F?_7LJ>XqP?4Cx z2BHIWXJe6j;eSI>&p+88Q*=}j{>{%k*Wxwie|-%CfBeH9@9@Vzyx@j^_PnKt?ZcPu z> ze_j#(uPf?(-|h7`d#CekpB$q9a-mlgsRi&4|L{+)?*Eu6;a}{5fAoK6@NW#dW=vzl z0RNd+_=o>RMVpH7lDs$m58fZ1k~nx<5&!VQBJ#Sn=*prCiq0;=f5xD1i2l*TBa03# zV*6`}zkB1KuCckJi(&(j!G`+m^dfUUzv%L!UIsVS-(np-;0y5mo}$+k@e#>^@P0|r zc}0&aV*hLa-^L<$`uDIR`gm~B&fx#R8i%ZpD2fiyLvu1WI%=M07M)c@2k?O(z5u>- z{54a$d3xmco>_EN5xr+S7Z%~2JlzxiY(Koi9uDw;3;x&g1%G422Iw96A6rBQk18_9 z4Mk@Znftj#8;dS3!WS9PZ*mY5*#%-FWJFfZ-B@&X5#CQLI;qJ0A6ayKQFw>LAw_U8 zzqNiD!&t^VyomhQ6&+Iq7x=*H)FN{ygENaFhs)~yHAVQnwrFz^AL)2O(fLK^6yg1> zBD@ECydPI&taU|rKeXteBDm}f{%e?-4v@iNMUjEI(Ss9;PAWR3$ozX5Tv&f!T0{;RDi9?+RZ z=M}*iZ7jNAdc-@t&nSxAT#I+|#QU*D#=-kTipc-KqMgZq4cGYJzm5kLk%76y7S`8$ z^CScFMtDFc7eyEFN*+0$R`2n?VMLFvzwzGde0Vo@csHM&`7)ld$=^H%M!+D{{y&d_+I+ z7s_{zdF8qs^Z5=v9x%Pz?Dr2hY~%L_E`GwLPg?B^9<=ZG2UaV(6{^z^vCj$)PXdYO37 z|B_35?@H!Wm+h1nzRk-V@tpV(KJkV}gy-Xn9yz5)O^?Uc(RVq|(e+{y*^vdg8e{*W z-Q{I2_@}SoeOX@7Zzb6#Qw;o+|QqhJYd6(~;=looAOub)ML^sHR42*|% z^$YJPxSQj;B3R-BPk7xhrSN=m{e4E!sYRz3oi?QnbwuPMPjw^{G9(B3z#hneyp6pp zU*Y|bngso%=Xj^v=8oS}ri9nf%Tqb&l>qhUB@vh>XeLprWe(x9WN*7)_&==1KdQ*Q9#v$Xrxd~c?4pZ{E-%94(~9t| z*ZPVgYtAvwNYOz>=CU(i#-n?5@bIGbML%8y zA9FpU2=5z<@c#57y;gkU**cVU2)%d5*B9x%lg;L$D~mQ3xd$B}gAGM&Ap9HC*mS^L z%xP!6j79FSe`wJ$MR*YUeQ%W__3Y@bG+mCjv{L=)}5?Hkpmf!58Gf1^x)j0 z#}#dulKUIy_@bkW_<`ua&a?qz9a3~e(XmC3C_16&v?BAx`=v$rXXE&?zG8jtJw?`^ z?=Jdm(Z3gcyvXO)pCW@z^?qZ~*+uSqauNPx17xtSC^`U-ogsr@Pwo#bI=ToZbd&C% zQ*=?$l|@f0vhIp6{KNS}MR3Ramy5nIeNP|WT69y9Yc?0rf%A(_FLLh%!0qc!OF6p_=* ziui)-ipVlLa8kXe1CJ^)Cfz%#=!hcvxDx#Fv6`1L_#9)dFEWpxF2etrMHduZQ52uY z=HZGr*yA7O2>)N1zNZs+7Lk=}*nxY{f%A%@1Nf%{WPtypi^4zrmn(ztpJQFRte5+f zwK0ti{|!avcW%)oMOPJVDS~74o_;;v%?(|2NkA+lui2 z;UapDS2DmqIUq8i8@CtHm0OC$4fndVh;E%cC1ajY#P1$cBo2)Eua9>4i@fm9-x!np z;b2~PKd*@HZ!UUn5gy)F^!_Q~5C81F*8%+d%=hF(UUWdrd|J^(MduWW1MuHaWZXv< z!9V)n<6j@|@E87#fq(di|5NHc-?y;{|K`koUsHq^nBq&^6YoAhrQnZupNWHP;5|j; z_OhZaMefPA@PASf{P}?6iq;k3|L~%oe{C}A$e1S-!GC=b{tqcSu*e)k^>&B8 z+}9YP9=i(xB-BJYqR~5;3@kIAy`{COW{$zlE z`oRa>QRKQ?ik@Gzsc2(S_~!%Q|KmmLiw-Y>32e-9t?+k$a>oCT6r|8FXS|FuQo|Kxff z{EcrOM;49wua9>4i+u49|Mf-37d@)z)S`2WE-JdJ=$S?I-~9Qq4;Ha){Lyn*hj)B? z@AJ%DoH&*O^DD}s~x!i*1mQ_&qo z^crvYMB$qbc<(cEiT~p_UQzU{qRWc#4}Y@WP;_F^Ba7f*KIUbPYw_B@ZtC9j3;wU6kSwwRnc>b_(1&g|M)|2o=3i;BmBkNie6fTe>z3C z@PBF%ntmdBB1U@PBU6g+*5u(SaL_m}O zp$O(=Kp*_=bL1TSzp?0rMb{MZDQ6dvG5tTOi0&U(WFGLRpKJLdTSU%mfZW#=nMe3P zqu!Ij1x0k=X+><{mZIB>)c^E5egMC0f*ic}IX~&#*A;Ck+EfJp@XywaNAC2G9@5E! zip+1VUhZuS{Kp2?)qDJ(P;_ch_>Ui?19*5<(OZk~#SX;55WeA04#EDN_5Kw_^yiWy zag~3iV<#0E?-51oi;gHVCv#it7acRsAw~3VU6Faf#JuR|nMLOo!T+kFXBEAu=ru*} zDY~HbMY z@Q>}EU+*s}GJpJETlD;*R~6wAzjQy?i;w&xpa0UL=M-I2bZJrWKfB(Oud&zw{*Ng# z5BkV1%@3{R3;xC@0_w=GuiZ&F{JL8+j;YINQYso*}$r=CaieM1@Ppx#? z!ZV)-fA7hI-SZLbp1o`;y12+a+>dU#|4BuUE(-ta>-~{MhZLFLTE5)>;37U}UD5GH z=0P7f6h#Nj)toOVy1a<~`u z@}f5v6rEdyclX8rPZymqCF7e1{2x+8H_a36=$Ctv?;%Cxy{_oEBKYGUom6Cw;oscZ zFn-{Qe|*9m_KEu+toL+)4|skN{I4jwpvXPw(`iLyY#d`E{2yKPup*e4AB@(DfA_{e zd9N=zw#Yo_BHB=dfAc-7$owxWx~3@lk5Bx{bw6A5??rUs_M+Dn$@|D^Q_)35=M=#o z{~L}ywBK*In2>7U3Ts_x=mQ@K5(q;(#28jGkBInrn(KC}K}| z-%v!(2>w0)>_7gm-mb}uZXH~7c+t9|c z`#XxrhyIfln}Pp@MQ0b0>xLpS#y`0~q6q#EErS2f;NKX=GN!R%5dO{Ulp_4o*^7#< zDvJHzRPWzjgjf154j?+gFObiTMb9ZBH`j*$)9StZ-|whv3>lnos5w&jZOZC7n#%gBDzTiKRqRUoKbXo5nUhy zygF_yA`kEJerA#L@a(z`Meg&cBD^14^w1)_KctBK4=mc5{MT@e|NZMo@5sPh$bcRk zU36R#9e^8uaB>kW@q|bEKo*V&=Ds7R4Mlj)@zM33OphsAS9C;CcsF)^0OeSRGZ#+8>;rWq8^vLz=i`*CQ#(_K8hxdBBOJ2PU z%!eGJ3rE)bbwx){34Z9fBD|3Yy~eM<$;)@J_8z%5e3LD{jd5U6c;DH$*ULBLUp?V*e2%|e z>ptPvefN}ChF>JIxh9fIk~PMk3U~2_1EW~8Dl($eD1tlH*dUS>iezHylZm5th;!0^ezoq7|)YvN>Z(HYQKITCVWWAiRPp$K|hx&Pq=aBKu z3Hd(fZ_k+XuFZWj2Qui#pEve7bT&GmngKT>Ov6cV4c)s^0G?a!>b;47N4?cwBvx)lh#QbLhwKvG+N0 zPOe$acuQU5o{_f+2ym8+;osZ-Aeg4jMH`F;Ozdrd z(S27+{q;+Y4+~^o#wVlk_$wVZ=V$yIruTI3@PA~z5C6s|gN#oGuF3Ve-|h98_l{em zobTu!Hx%K?{f%S1Ba6KHIYb5--*v9f{kAq{zRS5ey589CnemNnTrWoy8JGh(t!54x z(=oEUq27o0$UbA%SMB9*b20~W+?E{NKm0oOYhJm;WXlX3B!`_|iSzH+YX%o{}(=hXZ0c*gWO>iKn@d*pugcGa(g>Pq*? zeB3vb&obVs2ichy9xxqj)_>U0^&h71F+J-)JpMPXm|n8-!-IC}?;oywe!K1pJ-PK$ zI-!oCle+i$EJxpk@>!1kb-U_)=CXbc5BTk0ImcWdS9H;oHq{a3@BX;d=lwam`aNC{ zzQ%;-yvQni<7-RN&3ggi1NAsY9(c|x{P(=~@pYx|lMnRy!TA07p5yOV^S67tZhY@x z8J#7Ao`1SQSIH{(aGwv3D4%6+`97cL@1>6RIpw?leLi#adA`fB|9<>_Ip>%kzQ_Te z`0e$dJjllP&b2=&^B5n;-uB=-BR!8jpg3qpAC%lf6Mgy)$%`Y%$0mLkC&BR?|k&Vhx73j{FlrBO2_HP zT+8<>otN`_{a+6MYdQa&eedue|A&7uKQH>frHFnwXF2h2XY;VV<82LpvIzgq+uQk1 z4p^+V*ZqnXD7RB{KG?NrFz%f|DMkON_k%i{>xqeUd;Am_2mfS~ z7(fQgk;9%KC%pH$@0fqSFg0*=!1=u{^n7|BqG#KBpYI*#>E&`iTk87$^L{Mvd)~Ly z-*6uDPyflF*MVLR(S@xY$K(1t=jZ*rbC&un*KW@-7~*Bils~-VwU7Vak7eL|*Nv&a zXWr+SbN!w7<73VpAN%vh*Z1c-FMp5U_xJD~4+jJo+1-JhSxAb^m%z_R&5W?9+jLId%6xBu>S#*@A|Iq znjC!a!IMJ|J#=!|_Z~L+zVG|K$@hQ%_fLM{2Yz63)KNz@`k^2Cq1k(%`~L954{zsx z&-Z-K`I(=Y{Or&E?Bt}APMVRwk?)=7eAl_&eb%pE-}J`)jYGHg-*5kY zIZi6@@ea6I+YQzwso>|-ZqpMCb^SAOMJCL1?yoIL)53nou| z;u9yATyn|e(n~MB5Bc2p&U3!&T<<>aM?c)3UKr1~^x=?04w*dgfe+lL3u~+kWX$&X zX!dyg@y9n?WydfI&T#zg-~R2%rcIkBzyJHcKly_{_=CwG{^1`^{^*bXX!0k2@+Xr& z{nJ04yx;{dm|TDT^^-sQvp;J`e?RYe&uia1?^(}!R=dvi?&E&$OE-*j#u;Z!{>RDx zqv-=5VLs+%espth^|iZ#uYs#MCdE49GHhIT8-?50^{`R*| zknf%M`q#g{UH8|2{nzb2H{5VT(+~GIj`4o$w|=Yn2|j{?z``veE##FpV1e-@P+o?=b!!TXD9#qum3vv z)Tchxu5-QnxS#vdBRXX~;1#y2MS+;h+5Ti?2O^6mfr_T)R?{?6n(-}%nu|NYTHDc{bbqy!+=;&= zC-UM;fA@EPck;|?XYIQKc7>pxd{o!sx=|NY;a9?1pgk?)=N**f2K$ratteWMe` zqYv>9<|DT-Kl9{Q+{66oFMjwjwJ1LEi*NkvJCetawy)jwerja8ua_dm>$%9~#Z9?2 zyOCp&Kf9aZUU6LfC3CWe!I!`M!C;_vOfZqMBMgrS6tXku{mdx00tg%lDEeJCBX0W^)a!qkHO(aDED@{uNc{`i9*{9q$vpnv?wf1FW1b9A2bVfK*^f27@qel)!(ouF67W8cQ7 z3t#%u7u&qd&pa3DLFp}jz_+;no8SEA#;#MXr{doep74Zb z^J-|m@jdT(PqR7xl`oASZMc79z@4p$&FqitgE?FM*j*oMIKk|Lg&jGo+wZvJj#jIw z*NlPQecQVyx7~Kz?s`3;B@K-jo}v>mL5_hfjX$#Gjg+dFGj|Zzj&O zdA7jT*b%>;_|AUB_W1b3bH1JZ@y#Fm*j z9-(UyJ)twvi|B)S8=r45A92F`;uB_iQ2qe+@n`sQ4nJcKH{N(-%X{<<$VWUNuf3s8 z6w`kAhkv-$J#rrYPJjKcZu+YxS3Z@TT5sk%-)Xi_5BO1^-v{@y9lpKUdC3nJFlSG2 zf)}h|DW1IXjc;r{JGJ?1U;Eld>~0?Adq?NLp?Zm~cOUn2-{`|w7tBYzVcYbRua6#x zSNOqK%M*)F{NfM)^prfvg?z|Kok(taMmr+*?uvU@KmU;*`H>de#5n6Hav$=A85zml zo6VOz_)34Px8mnx@8m?jY>ut-%P=52_8q+SXM;72)!}k3eS9+3t3xk;`O7CSf7z70 zpOWvK=X}?>-hE~|F=rjYJj_Qv5j`+pvBCVsEBOk(@Ylx#c`P}&-Wb-P$SFAi`H|zU zz;AoTWcT_U^1NUF^iGG0rqSnHZT6OdGWpZ`LZXtlPmeg*4ZIH&40Ql z*}*AT!%;6zjNxBj{Nfi+{^BqGqSfNohLC!!r}lZ(>dt#no$or=yN~-tC!!C=HNJV6 z&ux`In4fvl)7~H82VaQ^@dfxN2l5~n_arCvfnEstk>j3J8_?Haj|Xe0dLaC$dYEmC zh2QxOo4>c^NwIl8Q$D3;i=QVKawT7KX6yViI}he;mJP!T*2aUazPucZ-?8@l+~+>G z(I5ZuA5Z3ybH;1L?&E%RBKlxFx@LUyz+3b{t`I$-xAFsZ3BK@$fBdQw@UK>sBl?_t z+{<|6wx{U;J>|3LZLojQi(b?)BL}fCwejHZzdnoeY(x&lcdENSQ1P8#9OKT``DOMP z%#AVNy?`}*pYxpOwEEKezaHE(p7D&9Yw5%EBp*e-cb?izUFv%GiB8Zb;~0-F7~ee1 z$Gqqz{fs{_e|)Gz@x`b0zTg|*_k{8xPp&=gd@}#cXTyYjvu$|64u;a zY17p+%4fcJp7UMz)TcgmrWfuTeGsRO%TCS1eE0-<(E9`V2|n)p$2(h0h%ZQ9fPZr6 za{_X59=_?Bxrm|UO1|o;?TO86ajpL4!>eYIx5{0}lWgcQ?A39>UhMO?oGAWY-u%(B zk(td`{U)yyU*qT589DRsY>;1Pm)Wnwe&K!XwbwQb2YffRy&BeF z$2$7lImr1)E`}F6;lA$wjAuN(>6meiZyx4jUgj5nVBY4B5A`TtpypFE;y3Yt9LR%Q z(kuAtR|g#+KXNpOU;DLRYw{)M-Kk%!ucv3BzE``DnH)wOqQq`G(q|<$b0=*-{)M{>D9YW zbRzl?T`)eMU_SJKKQd4AHE%xa4R3ryvjcp|3*0OW$Uo4NsqkMO4_wpP(i-mkca(jMK+>FihrF^P5%Ld3dKAzn9X1JBR*s^u$ zpC~7$<8)e%uh+52?u*xkef3buz?#1P4L?cO*-UI+&Q166?%uI?{y4VIK6}jJCSJmq zy&!w^?9D>thpfHieU3RN`-WZXKD|Eh55_ZYVubn71M^EQGlQ+5V%6w1^vTh~UXD8w>edpukbn1F}t=vX@TV(geGu~T% zU$ODlx8B-vJUoh@z0J$N^-;&LEzj=cwW{yH`ebNh$j2TR!6k4aYY=bx!(Kaf0i9_4%I zy3X~{3Hspv(FNo7dT{w~5B|VB&DXryD?a2c_`)AP@r&qZ~ zltZ`=dDBJmhr`a^Lt@{Gy$fV5Ced^8O-`eyDdrD*<-Xo(Jn>$xlXy?>XV_2oTI;dN zq4`aDwfv6FZ{9rY!8eXQ*JIrIY<3#VgLiPYFI#SE&&^N%*9Ea-tOw?0eQc}Bqe@j*s(0Dp8GztI7HKz%45ARlrfFLEP4YX;=WrpTA? zByabW<0cO5tTkhMHQ7k-_vN%~T&xnaA`lxz9Uq|jE{?Y$nPbSx2|AK~n za(jL>Hh*!|Wqd84$N$FO;m%jXhP}e7#~Y@4(RyckC-m0y8SBWdy_I-4qVgY%OiyD}m$V-oi{K%0!$yH8B z&hlmPmVUtj9&p(nyI#(3 zo4m*@t^uW9l6U^7V%|G!#&tO3ZUNG|mEgz^J0lAP5 zIq8Lwn_jWJIk^D&k~4XeyW9c}@PG?^mV@JJ&hcyp+2C0{B(|w*)Oz;4@=yE}e@_x|3P^6=Qacuf8S?#E7XfA|b@*ahoD3ukkGaFO0Q@@$^E zsXzLh{UCKU`rfHdxLO^aSWsY#%Okn9iD`y>4t@ttVe?e!u*dHC*dn{F(el zuB#_bXT>h_mJ=n{kikO#St4>^&SHKXJLi38-T79?*y74nAzJjg6QU`Ic1$R^~*bWBd8A4^Zw zs&W!}pM7oeQ~pYw&+qe5iTV2A@;$wA*Z*n7E;+3HIJq7f1$(k1LpdJ*D&ES$;_Kyd z{QM6b`2)>gvsE}f_`zl0Q_SHFWBOaabhhl+xZnQ!wd?fQ>5lukulpNEd=MLqZysWg zd6{40fq5qN^!WfCz`q_5dB}y* zA0n?l4v;6gj&ni27Y<_|0I${Lc=N6iBa`#PDRG}qqKD1)t2aS6lk+9tSEs0NtzX3N zC+6#q>5y_{(|z?T3vpj8lB3X3{R`iZ^L>6_FI>&S@5_VaL;AV;NNOfB;={yyvg_@h zZ24Ppp6$iv`R4;FcCW<0&(rheeil72E}vr_<|8jKzePGQ^#l097yj_6KcH?)K1d#N z0e+mE*s~fr^#FO2YkC6YE#K<%0Jy*hPI3{rEtjXI&J+LmTD1-TELPD={i&AoR}2!5 z=GlI7zCPdQ=lDB*FEL+kEavNDsPW{_awze>xBD^n^qbA^xBssB9VWiU_yvs?s`ISfQNJ811EUF&HR?ab~&G` zo8-3Qv3bf-^eNS!bWe|84v@H?9&c=)4$Em=mwaFTV4W;}zmNHRml{tFm0VA}@3A*0 zGN+q6D#kBI7W2+AzI?-c%!?oBb)e4)@r6IKWTW^_93T&JQ4iTeLSE!H_5t_J1E&3e z>c7Cnb#O8U-00=Jc`x@fyugCK@=@v&HT}@*8~VKI@yq$eeeuekP5Kzym)j)glkdmx z^MiU9$@S^2Jd^(-JJ^e>@%c0CtA^WI{(f7sG7q|_W{eKVzs%eG@qr(F;V*TeTtM6t z|J1&z2a*epeL(U6a?aWS9MW%Rd+b z-mgBdo^_w|_qI=`>2{y<@l(CuH;$Mu*Py#{PPM)Ij$O^LFIg`&=Uw?Z9q9dloRAO3 z2Y&E{Ke>SQi^KtP;0vwkk`FoYm17@}o)9_9`Se-z1KGO?kU+#0!6EA8#QogG9`(nPHin*(;Z z-~~7OW)3UGUv4XYCGV$?e2adQ-??bMzw7m;-;=!0{+87H);p8)i*u>>=zeee$@lq7 zelvbweXrJ9%i4aW^4Z$?=4rmk1@M6%dky%2)C0){$9X_{LgbcyfVFb#gXAm5CJ*dm zfIghK;5>NAE#XK1Rw_HW3>@St_A#hQ#GmB-W8ZJ@65pd=k-SeY%l^mod9Ck=bKA0g z_4C;8tM9ia^WE(`^A)f9IDoG{7hK8*kQ4hQH}WG#d34_ovS&0gKo9VqdluFO;Y4rX z20u8ibZ>}x(^WZ&J`G#eXSBA<{$~z7d5qqcXC~6;Rre<6AIE)b zYHUT`YCTr3b$e^rU8Ni5ZT_hV@db-<9*~|;`T^vWo?vQ0u{1s5#DH-f02lc15AcE; z{Nw=eT!|bK`{n2I5&qnM1ogi49(&RF0`Z{F`^5%0>M7Zu)M9_dm_GKW-|IJ5|1!{n6Kb_OB)Ot3h11Q1fqYy-xE< zpP%nfpO=qHpHIHZX2j1u)%GK6^KW^;s20F4yG$)Ojsfaz^^&^DnhANz0mzq}=hcDP z7XTMJLjLgMW9Z#-WtrTMUh>)Exc;;HGHbowi~aVix7c5M6_$$q>GjL|t$qDk?TbpE zpAQ`S{$~5roPSTX{ot=2>}vsb*yn-x&$yQ20u8;JIo{V+S*a=Do>@8`qE;#c*DoQU+!nU$GENTuV?k;oMKaYe)7-Med+fm z_VaaWU->T|IQISWK66-Y-)d{T?TYX4fggIE7$DZ+8~=SBAU8@5KyG~w(60yR_4|#4 z^a0?}dclQHazHTye(3|5!*cl7w~(XCN!TvE({oDSC+mHE?x!wQcdAqAi+WKFs*aI| zszv&`FR@>L!dUcEzhQ6k{gHKI0J*Ab@Y&Y^SrZuN0OUq~1x&g3}G0ofb5lRq5%{v=%BBM(RoK!4QQ zVk}&jnq&XDIkHQ>i2qS{%JbZNZth>XsC+|zLXM(eA!kX?S1x0n8(-=9c@|UdN6*BX z)P3d{{QEiW$?xSbtrt*wjlVtzz&|;Vhh8YGtcjA>=BF&q0sB7C)@lItIlSNoKRCj3 zxiugkB`#T;gdtq%nAmUMan}3oOSTrQ7U}zb$^BickD;F-55<=p2cL4D%8{X?oGL0^uV6;J_m$9zLNu{50n~!y^tIEsR7h-i}3Giz&sxSfBm0+Em-V; zBRt`n`E2c&-j94O>ppV*q4qEQr+fYS+TUI;YgBTMx%f|Ozu=$szInO--ca|C<=b+= zS{GK^*e6Ked;G~GeZZw^zMv><~V^{rekk@Pi{f;kq38$WNETpMDv~{XLVAXFT*ImcoD2rmNfjhNbY= z4-Nk0v)9^x^)r{kKRtlpKlA{uZuNJ1K)wBwH~jCr7i94NJ^t2#SAzdM`(Nb$r})!5 z_>b+Mp7!<+fBM|xKlcCd*Bjv<_gecO;otXwdjC&O!9VezJm=YekN?>J$Nskm|JBC- z9)Ed^{Y~Q9y!fB|ZxQ|ntZvWRa{Sw_IH&CgsJO=d=k)-4{QLO72>x zw>|NHod4@L&CCD${I6g8nVbJr{;v+%8vgdl>}|c@=$ibWUsK-%|Kxvi^ZVdm@*490 z;n|>-;+v-{=a1XU;7Khf9n9&0#pC{&CYTCum6*^zwG;ffBHXt{jU!0>;Jv7 z{_EBNQvbtW{R@BcQ2(p{)c@+gas8kEk2OI3A97d!!y(TFj_ZFIthD~ugV!&z_aps3 zxn7<(3;ucx)}XWYzpefszVx^7xm5p8k7{r0|M3C(Kb|9BQU7nW1|0m!8UFnHooTCH+5hq?h{t`p?!JhP^+-|FRkS|HJ=7P5<9~#EJC(`@J7a_5bCh*1-F< z|KM*8dZl|7<7;=fBR=p$ukqLS|2^MG4%S239)Pa@Zx0Ch>%sYLEco|(KowkF`AnSkD;`;SJ@?UEG&svQBi=1mE>wn@eo6P#Zb>z1GZ|`U27Fqw7W6(3} z&`YiV+xxeo_5bRP?QK0^YZ+wP_x}3YFSWmQ+J)M0Q`<91e%S*!UjMfrcf9@&pLy&5 zax8OLj{VQu|6{+2eI-K=aQL6MYK*r3XZjm2OYQ%etpQAHKsnB~_WvXY*wxQ>E=OKl zJ15VB%!~bf?nfT2_g`xy>HYP!pPs7TpP0V2{Xg)B6THUzf8e^cx#m0mN`KOtggjL3 zA;0&m!LSBU`~T*w0rWLszXoU@TWSDn5puwO4}jly=yQO!2V^ga0rCBQg9=|_zr7#$ z#rM3}pS53Wr`GRQy8llu2(Q)d|Dy+41A>pK7|F7R381Mg= z16p&+?>zN;0GHbTKkftA2f{|052!uB-*wO)zW=}GzoYm6%eA()|6fkHz5D;onU9i_ z$lqssK)W1p{M^5ov(!4!`1v0-Nn!vWs6Nd;K(WUfsXXw(51u~vSw1PcxhEVG_xUik zuc!9#tvvrDCi1m>|IqiDuKU+MioWlk*q>)W$K|RneSib^MCy_;P8P{p8w0<@8#xx!C%jU9~PgiA7vd#Y_Z3*`Gk zKzvgA0P~*z9b&-J&;P~;SPx!TzpW@Se|7Oo`|J+ANHT!_sbpQN+_JQjIwC4b(&;R!^ zpw$88!{o8*fX$nS-~FNMbe?~rqj?rUO{7-sp9Rv7;z#vRclYN(dcUvU=l99KfBs*; zzpwdw+wY(I@$4u0WzWah_mlf<-+$Sp{@nQafB3;McD<4#yAwOa>E-9mjQm{Xl&$NdK5F&+`FU6QrwI6Ue^c!~y+s^GGhxYQo(pA8dACwqjmv zE&0BA_kJH=srT_K=WlVpY%6iUxBaa5S?~3GYUJvfzwvYb`7Iv)7d~);7u?7#_Pm;7 zVgO%dKLT5BzyJ5;{QlqYEU+F#o&}emq!*Cq0mnWdYl3o=>K58r6L-rgzl>ihlVDZlQY`R@07`u_`S z@%w*xHB!q5xAFVm*4Fsn_=2^p z5$Rd8`5I4bRGX5sTu;1@&+oCv2Yz60|6A60mmC2gzCKN}dgn(5Ok4<|9`%Kf20q#qRNeA2C1J;}_rY`{bd|&+l75BQNzn`H>@e z>hP5J~2TnWs|9`LndQP|S5g#BYW%sjv0i}<8K>qKWy+Qtujpvo* zCw&~?uk;1&aU1$V+xP!}tQo}87nNE-KR_SK^G9+OISU=> z^4sJBY99Jz-9sI0&k0%Khuw&q#*{y+4e=-+N(O)KHyv5s^Qcxvadb8J-MuS-(ye5@rhr2!#**eT;NGgepA7- zb#iI-o^?HPWoP89=SuG6FNa9n$Mag&LF|LQ!9mYjy-8=~d*YIuL~g=QwK!0Ii~rIS zFyHJG7vtnPde!p5{(rxwE|d?@0qX>MW$NbS1Z<40k-K=?>w)#d_ym~A%cBqU7l{?{ zZ5Y>kf5X~$Waxa?1#^Dhefb;uX1v7t*nIrGd75vZ>&5Pay}DKahio$FLHc=>q)Nu3QbK13kR? za=Cjx^S$$&?>g7JkNX98tH{XY4UGkKFc`NM%e!eu2muJ&BH5joL&wkd~)BcCNd9pb<;|NnRKP6i8p z;QH1Bl^4s8@ka+dKgy4?De)k_AbKF*ia&r&d;(p7Tl4{zVkBJIbI9L5M?K!IC)>n# z{>C`Q3+{YUZ}aBKCe7RY<+gM>KA+tOd;7H5f_zW^Ha%WBRr3Ab?~^Y%lQ$W$3-f`8 zymhshuJk%FlT68K9hXVdN$t4fDR-dkQ2#^>6O^F zx%Le3pT_BZf%rPs193qd?sb8^+H1>a!4koB9{J9B@Q$4abN7{lj&Y}td{b}p=4*|^ z{PBSweD!u8?8zZ(d*nk-vzTA~mE`;6O1|Vw-sB)G_p!cj4*VP()}r3qmVT(65(oHY{K8NE%6GL~plX#q zAN0%^9`!V$1AL$Omz*%Qp&k=G=QsH}w#243ZMu4h3FTvZJrH;4BDq8t_^0TD+#Htl zg8xSF&3ngSO@CbH`X2KZ-z&H4aTlB5ZeFqZ_IY@j0Imr`xeV`NZ5ilK-IPQDo+~Cd5-EZ*mo7y<~IO0+49q#6p zIIpHP@A!MM9$%^N@r&=|dcEEE*pnYQk|*BCS8ht)VSMi?@aA9)&ah3$OkH?oz?|@v&3iCH}pE?LHr^ci7(JE z6gTOS+?jmInVs>o;w(AAqUl1#Np`7jpbzSA`8ZzL!!*J#I`8oWmYpom5dHD~2$S?N3KtG)C?DZi2KwJ=K>kgyaNgSY^fWrack!)q7^niTD1w8|@k{k00(FOLXzse`Wi+_e449S(Bpc`ZIx$oJXy3KX2 zcOUn2-(YS$}c|-ggep@2GAbA1XAftJD zAg>}jw&#B0uHKsX$v4mkJ_mNaUXb;eU>s^#mo2*9ecaD|-5=($b>qj+)6d{;zUIy5 z@gdj67n@HG>~rUvH=QFF@*yYkBDX!!?#DVHUsbc{59nQ}tL0n#D_ar|#C`H%6T|+N zds<$=S4Iz<6Mw+3T1OBs#ReEe7uaKBM06rP0*=VIJ>_#pwhe37xE|i>QTL^5!Q8mU zHxKn(?A<(ryIzOqMe-ph^4gvA{4vJc`dhuKXJIW??^0h) zjFV%rL-qty@iB2RF@gNp4?U1Ou`BUZKIOS%*Qu-Y55x!h)bychNPZb+utUayC4y_n z-||b}=Umt6(FSk7bJ^lL%&T{8T;rRE+Qq!g&paFM<=grC)b#jL@4C(y_{P8dm^{dZ ze8_2ctnasl@wUF#uR6T;Rq0!+4fKilJ9V`@i=TxXdBS_B5r*{xa*Q6Ro5WK##*WC| zHSCWa@~KT9${*W*3Ny9_Klcxb*P(nycaZa4r%%VPyPx~|e_O^0=CO6_o91QSvG;!d zJ{J>=gHQb88~@}$9^|5@yF2yuwuR|xz8BN1Tk&(&U-Vu10=bV|O^hXbYXs_Jbz)+s zdQqJ4IUDPBq4|ZnMvRC)uuruYn}eSkoWF)=NPXtL&wcMa=hG$E%irD4ea$Ixoesv< zzwwQ)w|SYLTqbc0?ykWPzVL@nHji)X_vAnxdqb>Wu0H4&Ter4%f$qzB^kVg6)Mb35 z*vtONPacrCAdY&^CyaGL{D^OW6Z>>M9|1qa4&jOP-}Bzlcg}IX>s%ka`F8iGb99dm zezSDZJj{ol!rgn{yWafqfggO~51;tOx4lN>uq)Samy6ksKF^*7`-teieK+EtnwL-1 zgHwx>8@u6;;Y0o~|MqvjGuT+!TCWT0srUx<7ObKZ^a6(B#@5Jr{JB`|diR;(UH3PB zFi%bgd!O?M=4rm>ZT|Sc55Dk+PyFIrZMQ4-ZtQ3*x95Ix9dS>;NZrFHTC=tO()6JE zGW@38Tt3D}vrCx3L2O`mi4pWcUoHMYJdR$_4L+GanH}rBd$qsoJHA{W!gaw~{{F3R zm2WTKGt<2q*BGufAM-Ll^E6-fZvObd55Dkc&!N4Vdt;8bJ$j(tk^9Pn$VqNMZtTW7 zjJT;^qc-PLv4!AD;NcVGJ_cxBZ(zwRgyD=a0(&tvk%jwO# z$6bE6md|GFO?n|FvJ=l_hzs`Q+Q(^s7MrqGFNd~Hr-s%;;dAxR^={PH>UMcIznt|K z7$&DnUgwxx(f8uF^VI~dS0m~RxUc&gNBw48n45>3&b-XeJjHkOHh=nje~Hi{^~BT*_8i;8X^)D%+yJ;~M5Bh-`AGnTvQ zQ@f8G&VAkAIQh*2L`14)l_WI z-mN@qY43;LDnFsWZfzHiY}eXu$lpHi_mHU7tif83$up_&HjeSwx$$9cKIX;N_xAqH z?x0VejM%1F3^%%==aRjB@`;{&?tAAs-*v8+x4WPFy1#LZ z=NUnDi+PyO?jV=@;~d0R{S&@{KFA%}V{}4KUH-sU`RJdi=R9DE_zsl6eeXQy=eaTW z;nUsM{f(n`+ZWS!-{0i+yACjtL&B?nrdz)amMG5!d+&2T9LCOfoto5r-0%KqTl;w4 zeRX^vHv6#Irvv+RV4n``(}8_Duulh8s{;qrgs)$ykztQ{WDWD+5UhKg;d{gpN3?#DH9+)( zKlpuFQ*IjpQTVuA*!J5?h=l|N|7k}{=n{LpHu};KCm{;OO?-%eAzV`Wp zAwLZ6j=jB;)!5d>;vRGM7`tY}7hZT_TO+nN0FL(l*&|~Az-wOfn%Yk=>_4zK@Yc80 zvqgXZ_icZueFq_*=Q&3E73@E_`Q}$n{_s$u_8;b_0m`|fz( z^B=9^Zng-f3EE1=KMAsVA5mcz0c?Zq8A8W?OC*`um9vH zKhgXIU(v?{y5u~R=M0U5+%G<&AIJS&$2XYYJKph*$?Ixg@{KpXq~)9TN6JxpJ)q;s z6~#I>ur)nc3tzl5?zt59eLkP~&d-aXzxkWL*?g%O%fE`fp67uRS&uOf-ocq}IM=@8 zyXrY%@f>+p{KFslaN9p_4|z}joWFfve@2VR%NZpM�RP(h5 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

@un+&@JPb5`x%O|E<>RoP@qYJrfA`IMyvKWN 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 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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 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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

Us<_c=H79>p~&Cj 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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 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

wjK^`mnCeatS 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 G1pWsQQ@aHK 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 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 Y!xQ-`BGx&~1bk?o)Kbkle&z1}05jtsNB{r; literal 0 HcmV?d00001 diff --git a/liberty/assets/dc_xboxd.png b/liberty/assets/dc_xboxd.png new file mode 100644 index 0000000000000000000000000000000000000000..99b88447894a24717251e13861d3f89f201f16dc GIT binary patch literal 89065 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 U4?}=OB?7**)b-SgRWJ|#2V8@KJOBUy 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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 literal 0 HcmV?d00001 diff --git a/liberty/assets/ps4_f.png b/liberty/assets/ps4_f.png new file mode 100644 index 0000000000000000000000000000000000000000..a1f8ac49bbb73e58ee6dae69246241e1381d5a15 GIT binary patch literal 78895 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�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 aydoO#fw@xe5 literal 0 HcmV?d00001 diff --git a/liberty/assets/xbox_d.png b/liberty/assets/xbox_d.png new file mode 100644 index 0000000000000000000000000000000000000000..b09842973bad3dd8dae556c666904a6acad7e729 GIT binary patch literal 6437 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 literal 0 HcmV?d00001 diff --git a/liberty/assets/xbox_f.png b/liberty/assets/xbox_f.png new file mode 100644 index 0000000000000000000000000000000000000000..6afb0d7bf127f863ced86e2f9b58bd25eda7ec7a GIT binary patch literal 80524 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 D!2yjT 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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 Xa7>L)F~!mQ{*oNxgmCQ1Sit@tZfsI* 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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ⅆ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

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

@oEb4AvsdOSoFa&@emYgLE2n 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 z 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 I4SK%S4ah9Ho!O%(th>$1RD1L|&`U-d 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≧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%0D75|n>`$O{ai~-Dj#rD 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

RN-nAod!oV(Q0ltv(;hU7t<* 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π0xu-~pVM)~!RJ@xw7Ly-*tspv#3F}S`X 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 Ftq`8;%?NW;I09ed|nZsBEW*qvI1x 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 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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 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 From a7d85c2f7de9e6c1a1f0ff4fe0e229c537b5c25f Mon Sep 17 00:00:00 2001 From: Falco Girgis Date: Sun, 16 Mar 2025 12:17:09 -0500 Subject: [PATCH 3/8] Settings file saving/loading finalized. --- src/liberty/extras/ini.h | 56 ++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 37 deletions(-) diff --git a/src/liberty/extras/ini.h b/src/liberty/extras/ini.h index f14489e6..9a6ebc7c 100644 --- a/src/liberty/extras/ini.h +++ b/src/liberty/extras/ini.h @@ -357,12 +357,11 @@ namespace mINI } 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; + // If we failed to parse, we assume it's raw with no VMS header. start_offt = 0; } else { start_offt = reinterpret_cast(vmu_pkg.data) @@ -370,7 +369,6 @@ namespace mINI } } #endif - std::cout << "POST READ!" << std::endl; for (std::size_t i = start_offt; i < fileSize; ++i) { char& c = fileContents[i]; @@ -392,7 +390,6 @@ namespace mINI 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) { @@ -453,27 +450,23 @@ namespace mINI public: bool prettyPrint = false; - INIGenerator(std::string const& filename) - : fileWriteStream(filename, std::ios::out | std::ios::binary) + INIGenerator(std::string const& filename, bool prettyPrint_=false) + : fileWriteStream(filename, std::ios::out | std::ios::binary), + prettyPrint(prettyPrint_) { 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; } + std::string str = memStream.str(); + const char *buf = str.c_str(); + int buf_size = memStream.tellp(); + #ifdef RW_DC uint8_t *data; uint8_t icon_buf[512 * 1]; @@ -488,34 +481,25 @@ namespace mINI .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; + if(vmu_pkg_build(&vmu_pkg, &data, &buf_size) < 0) { 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); + fileWriteStream.write(buf, buf_size); lastError_ = fileWriteStream.rdstate(); #ifdef RW_DC + // Must free the internal buffer allocated by vmu_pkg_build(). 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) @@ -575,10 +559,11 @@ namespace mINI operator bool() const { - return fileWriteStream.rdstate() & fileWriteStream.goodbit; + return fileWriteStream.rdstate() == fileWriteStream.goodbit; } static std::ios_base::iostate lastError() { return lastError_; } + static bool wasGood() { return lastError_ == std::ios_base::goodbit; } }; class INIWriter @@ -761,11 +746,10 @@ namespace mINI if (!fileExists) { - if(INIGenerator generator(filename); generator) { - generator.prettyPrint = prettyPrint; + if(INIGenerator generator(filename, prettyPrint); generator) { generator << data; } - return INIGenerator::lastError() & std::ios_base::goodbit; + return INIGenerator::wasGood(); } INIStructure originalData; @@ -784,9 +768,8 @@ namespace mINI } T_LineData output = getLazyOutput(lineData, data, originalData); { - if (INIGenerator generator(filename); generator && output.size()) + if (INIGenerator generator(filename, prettyPrint); generator && output.size()) { - generator.prettyPrint = prettyPrint; auto line = output.begin(); for (;;) { @@ -798,7 +781,7 @@ namespace mINI generator << INIStringUtil::endl; } } - return INIGenerator::lastError() & std::ios_base::goodbit; + return INIGenerator::wasGood(); } return false; } @@ -835,11 +818,10 @@ namespace mINI { return false; } - if(INIGenerator generator(filename); generator) { - generator.prettyPrint = pretty; + if(INIGenerator generator(filename, pretty); generator) { generator << data; } - return INIGenerator::lastError() & std::ios_base::goodbit; + return INIGenerator::wasGood(); } bool write(INIStructure& data, bool pretty = false) const { From b739b89fd6030a6b8ac88e1d25d1b538ac6d5106 Mon Sep 17 00:00:00 2001 From: Falco Girgis Date: Sun, 16 Mar 2025 12:57:52 -0500 Subject: [PATCH 4/8] Fixing issues with previous merge. --- miami/Makefile | 4 ---- src/liberty/skel/dc/dc.h | 2 +- src/miami/skel/dc/dc.cpp | 13 ------------- src/miami/skel/dc/dc.h | 2 +- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/miami/Makefile b/miami/Makefile index 0e4a9c5d..3ba3910f 100644 --- a/miami/Makefile +++ b/miami/Makefile @@ -136,11 +136,7 @@ CXXFLAGS += $(if $(WITH_32MB),-O3,-Os) \ $(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 \ -<<<<<<< HEAD - -fno-exceptions -fno-rtti -fipa-pta -Wno-write-strings \ -======= -fno-exceptions -fno-rtti -fipa-pta -fno-PIC -Wno-write-strings \ ->>>>>>> origin/main -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 \ diff --git a/src/liberty/skel/dc/dc.h b/src/liberty/skel/dc/dc.h index bf935fc8..08c3b1fa 100644 --- a/src/liberty/skel/dc/dc.h +++ b/src/liberty/skel/dc/dc.h @@ -1,3 +1,3 @@ #pragma once -extern "C" const char* getExecutableTag(); +extern "C" const char* getExecutableTag(); \ No newline at end of file diff --git a/src/miami/skel/dc/dc.cpp b/src/miami/skel/dc/dc.cpp index d2a72a44..7c997e69 100644 --- a/src/miami/skel/dc/dc.cpp +++ b/src/miami/skel/dc/dc.cpp @@ -2015,11 +2015,7 @@ __attribute__((noinline)) void stacktrace() { : "+r" (sp), "+r" (pr) : : ); -<<<<<<< HEAD - dbglog(DBG_CRITICAL, "DCA3: %s\n", getExecutableTag()); -======= dbglog(DBG_CRITICAL, "%s\n", getExecutableTag()); ->>>>>>> origin/main dbglog(DBG_CRITICAL, "Stack trace: %p ", (void*)pr); int found = 0; if(!(sp & 3) && sp > 0x8c000000 && sp < _arch_mem_top) { @@ -2068,24 +2064,15 @@ __attribute__((noinline)) void stacktrace() { #endif -<<<<<<< HEAD -const char* getExecutableTag() { - return GIT_VERSION ":" CI_JOB_ID; -======= // extern "C" cus also used by KOS extern "C" const char* getExecutableTag() { return "dca-miami " GIT_VERSION ":" CI_JOB_ID; ->>>>>>> origin/main } int main(int argc, char *argv[]) { -<<<<<<< HEAD - dbglog(DBG_CRITICAL, "DCA3: %s\n", getExecutableTag()); -======= dbglog(DBG_CRITICAL, "%s\n", getExecutableTag()); ->>>>>>> origin/main #if !defined(DC_SIM) std::set_terminate([]() { fflush(stdout); diff --git a/src/miami/skel/dc/dc.h b/src/miami/skel/dc/dc.h index bf935fc8..08c3b1fa 100644 --- a/src/miami/skel/dc/dc.h +++ b/src/miami/skel/dc/dc.h @@ -1,3 +1,3 @@ #pragma once -extern "C" const char* getExecutableTag(); +extern "C" const char* getExecutableTag(); \ No newline at end of file From 6bd5a124cdfd2d98ad0793406a3060ddc97e70ec Mon Sep 17 00:00:00 2001 From: Falco Girgis Date: Sun, 16 Mar 2025 12:59:53 -0500 Subject: [PATCH 5/8] Forgot to add actual icon. --- liberty/settings.ico | Bin 0 -> 766 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 liberty/settings.ico diff --git a/liberty/settings.ico b/liberty/settings.ico new file mode 100644 index 0000000000000000000000000000000000000000..5230be86eb1657d75fc63aab39c391fe0e821995 GIT binary patch literal 766 zcmc(dy-EW?6os!Lf)I;{D1t?nsbp+~Ez_h32x=v!4}vy=q}xJ$g7{NgI^|TG9}>vH z_p$Z`;yJrfl!&co_73+ubMM_@S+f5bwr*?GvXm|Yw>%G3)d_G?q<727dDDNCenR6jJAGk-yMTNjeE;)Gb znTpx-ouiNPbzbyqSPYz7OjflLVJWiLbV=_j%1Vr!^#e7zyvm}WXi!d Date: Sun, 16 Mar 2025 13:02:19 -0500 Subject: [PATCH 6/8] Adjusted Makefile to only care about settings.ico. --- liberty/Makefile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/liberty/Makefile b/liberty/Makefile index 3fca2732..dfb3f486 100644 --- a/liberty/Makefile +++ b/liberty/Makefile @@ -202,20 +202,20 @@ 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)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico +$(PROJECT_NAME).iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico 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)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico +$(PROJECT_NAME)-no-repack.iso: IP.BIN 1ST_READ.BIN $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico 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)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico +$(PROJECT_NAME).ds.iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico rm -f $(PROJECT_NAME).ds.iso rm -f $(REPACK_GTA_DIR)/1ST_READ.BIN cp $(TARGET).bin $(REPACK_GTA_DIR)/1ST_READ.BIN @@ -226,7 +226,7 @@ $(PROJECT_NAME).ds.iso: IP.BIN 1ST_READ.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_ $(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)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico $(REPACK_GTA_DIR)/save.ico +$(PROJECT_NAME)-prebuilt.iso: IP.BIN 1ST_READ_PREBUILT.BIN $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico 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) @@ -245,15 +245,15 @@ $(PROJECT_NAME)-prebuilt.cdi: $(PROJECT_NAME)-prebuilt.iso rm 1ST_READ_PREBUILT.BIN @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 +$(PROJECT_NAME).cdi: $(TARGET) $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico 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 +$(PROJECT_NAME)-no-repack.cdi: $(TARGET) $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico 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 +$(PROJECT_NAME)-prebuilt.cdi: $(REPACK_DIR)/repacked $(REPACK_GTA_DIR)/GTA3SF8.b $(REPACK_GTA_DIR)/settings.ico 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 From 1a4b84869a768c4adb0de0660a152aebd2552d77 Mon Sep 17 00:00:00 2001 From: Falco Girgis Date: Sun, 16 Mar 2025 13:31:21 -0500 Subject: [PATCH 7/8] Fixed simulator builds by using the regular path. --- src/liberty/extras/ini.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/liberty/extras/ini.h b/src/liberty/extras/ini.h index 9a6ebc7c..5520cca0 100644 --- a/src/liberty/extras/ini.h +++ b/src/liberty/extras/ini.h @@ -94,7 +94,7 @@ #include #include -#ifdef RW_DC +#ifdef DC_SH4 #include #endif @@ -357,7 +357,7 @@ namespace mINI } std::string buffer; buffer.reserve(50); -#ifdef RW_DC +#ifdef DC_SH4 { vmu_pkg_t vmu_pkg; if(vmu_pkg_parse(reinterpret_cast(const_cast(fileContents.c_str())), &vmu_pkg) != 0) { @@ -467,7 +467,7 @@ namespace mINI const char *buf = str.c_str(); int buf_size = memStream.tellp(); -#ifdef RW_DC +#ifdef DC_SH4 uint8_t *data; uint8_t icon_buf[512 * 1]; vmu_pkg_t vmu_pkg = { @@ -496,7 +496,7 @@ namespace mINI fileWriteStream.write(buf, buf_size); lastError_ = fileWriteStream.rdstate(); -#ifdef RW_DC +#ifdef DC_SH4 // Must free the internal buffer allocated by vmu_pkg_build(). free(data); #endif From 1e069ec895a30230fdabdc78f358572757844eea Mon Sep 17 00:00:00 2001 From: Falco Girgis Date: Sun, 16 Mar 2025 16:56:06 -0500 Subject: [PATCH 8/8] Updated desc + app ID fields for settings file. --- src/liberty/extras/ini.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/liberty/extras/ini.h b/src/liberty/extras/ini.h index 5520cca0..84b73006 100644 --- a/src/liberty/extras/ini.h +++ b/src/liberty/extras/ini.h @@ -471,9 +471,9 @@ namespace mINI 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", + .desc_short = "DCA LC Settings", + .desc_long = "DCA Liberty City Settings File", + .app_id = "The Gang", .icon_cnt = 1, .icon_anim_speed = 0, .data_len = buf_size,