diff --git a/.gitignore b/.gitignore index 173f6ba586..208f81c38e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *~ .*.swp /CMakeLists.txt.user +/cmake-build-debug # ELF files *.o diff --git a/.gitmodules b/.gitmodules index 20ef394e63..beb0a4c74b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,16 @@ [submodule "libs/vorbis"] path = libs/vorbis - url = https://github.com/xiph/vorbis + url = https://github.com/xiph/vorbis.git [submodule "libs/ogg"] path = libs/ogg - url = https://github.com/xiph/ogg + url = https://github.com/xiph/ogg.git [submodule "libs/imgui"] path = libs/imgui - url = ../../ocornut/imgui.git + url = https://github.com/ocornut/imgui.git + branch = docking +[submodule "libs/spdlog"] + path = libs/spdlog + url = https://github.com/gabime/spdlog.git +[submodule "libs/tracy"] + path = libs/tracy + url = https://github.com/wolfpld/tracy.git \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 96da447ec8..0ae19cdded 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,8 @@ cmake_minimum_required(VERSION 3.20 FATAL_ERROR) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + if (MSVC) set(CMAKE_GENERATOR_PLATFORM Win32) endif() diff --git a/README.md b/README.md index 0c1ccee68e..93c2f7e240 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,19 @@ Building this project will result in a DLL file that can be injected into GTA:SA ### Progress The progress of reversed classes can be tracked [here](docs/ReversedClasses.MD). (needs to be updated) +We currently estimate that about 50-60% of the code is done. +Since this project is done as a hobby, and worked on at irregular intervals, there's no real time estimate. ### Coding Guidelines Before you start writing code, please make sure to read the [coding guidelines](docs/CodingGuidelines.MD) for this project. ### Requirements - * [Visual Studio 2022](https://visualstudio.microsoft.com/en/downloads/) (Community Edition is enough) * [CMake](https://cmake.org) (for those who want to use CMake instead of premake5) -#### Game - -* GTA SA **Compact** exe -* [ASI Loader](https://gtaforums.com/topic/523982-relopensrc-silents-asi-loader/) -* [III.VC.SA.WindowedMode.asi](https://github.com/ThirteenAG/III.VC.SA.WindowedMode) -* [Improved Fast Loader](https://www.gtagarage.com/mods/show.php?id=25665) (Optional) -* Mouse Fix (**dinput8.dll**) - -You can download them in a single [archive](https://github.com/codenulls/gta-reversed/files/6949371/gta_sa.zip). -Using other plugins is strongly discouraged as the compact version doesn't like them. - ### Build Instructions -You can either build with **Premake5** or **CMake**; that's up to you, but a C++20 capable compiler with support is required. +You can either build with **Premake5** or **CMake**; that's up to you, but a C++20 capable compiler [with ``, `` support] is required. First clone the project, including the submodules: ```shell @@ -39,10 +29,11 @@ git clone --recurse-submodules https://github.com/gta-reversed/gta-reversed-mode
Premake5 -1) Execute `premake5.bat` for 2022, or `premake5.bat vs20xx` for other VS versions. (Note: Premake works fine with vs2019, CMake requires vs2022) (e.g: `premake5.bat vs2019`) +1) Execute `premake5.bat` 2) You'll find gta_reversed.sln shortcut in the same folder as premake5. +3) Open it, and once the project has loaded, just hit `CTRL + SHIFT + B`
@@ -80,13 +71,30 @@ git clone --recurse-submodules https://github.com/gta-reversed/gta-reversed-mode
+### Game +To install all the necessary files, run `install.py` with **__administrator privileges__** [Necessary to create symlinks on Windows] in the root directory. +Alternatively, you can install them by yourself: + +* GTA SA **Compact** exe +* [ASI Loader](https://gtaforums.com/topic/523982-relopensrc-silents-asi-loader/) +* [III.VC.SA.WindowedMode.asi](https://github.com/ThirteenAG/III.VC.SA.WindowedMode) +* Mouse Fix (**dinput8.dll**) [Can be found in the zip in `./contrib`] + +You can download them in a single [archive](https://github.com/gta-reversed/gta-reversed-modern/blob/master/contrib/plugins.zip). +Using other plugins is strongly discouraged and we provide __**no support**__. ### Preparing Environment (Optional) +[If you have ran `install.py` in the previous step then this step is already done] -You can create symbolic links for artifacts to not copy them every time you compiled the project. +
+Instructions + +You can create symbolic links [symlinks] for artifacts [the `.asi`] to need not copy them every time you compile the project. -Open a console with administrator privileges in the git repo's directory and run `contrib\link_asi.bat` or right click `link_asi.bat` file and click `Run as administrator`, then +Open a console with **__administrator privileges__** in the git repo's directory and run `contrib\link_asi.bat` or right click `link_asi.bat` file and click `Run as administrator`, then follow instructions at the command window. + +
### What to work on? Check [this](https://github.com/gta-reversed/gta-reversed-modern/discussions/402) out for some inspiration ;) diff --git a/contrib/build.bat b/contrib/build.bat index ecc6434cb1..d68d9c5cb4 100644 --- a/contrib/build.bat +++ b/contrib/build.bat @@ -8,6 +8,7 @@ if %errorlevel% neq 0 ( echo CMake is required to use this tool. Exiting. exit ) - +REM rm -rf build/ +cmake --build ./build --target clean cmake -Bbuild -H. -A Win32 -cmake --build build \ No newline at end of file +cmake --build build diff --git a/contrib/clean-srs-calls.py b/contrib/clean-srs-calls.py new file mode 100644 index 0000000000..8eb8d47aa1 --- /dev/null +++ b/contrib/clean-srs-calls.py @@ -0,0 +1,124 @@ +""" +Python script to format garbage RwRenderStateSet calls to be nice +Make sure to copy the garbage looking code to the clipboard, then run the program +And the output will be set on the clipboard. +When pasting to VS make sure to press CTRL Z once, because it automatically gets formatted incorrectly +""" + +import pyperclip as pyc # If the program fails here, please run `pip install pyperclip` +from typing import Type +import re +from enum import Enum + +def cint(val : str): # C-style int + return int(val.rstrip('u')) + +def cenum(enum_type : Type[Enum]): # enum cast + return lambda val: enum_type(cint(val)).name + +def cbool(val : str): # c-style capitalized bool [TRUE, FALSE] + return str(bool(val)).upper() + +# +# RW renderstate enums +# + +class RwTextureAddressMode(Enum): + rwTEXTUREADDRESSNATEXTUREADDRESS = 0 + rwTEXTUREADDRESSWRAP = 1 + rwTEXTUREADDRESSMIRROR = 2 + rwTEXTUREADDRESSCLAMP = 3 + rwTEXTUREADDRESSBORDER = 4 + +class RwBlendFunction(Enum): + rwBLENDNABLEND = 0 + rwBLENDZERO = 1 + rwBLENDONE = 2 + rwBLENDSRCCOLOR = 3 + rwBLENDINVSRCCOLOR = 4 + rwBLENDSRCALPHA = 5 + rwBLENDINVSRCALPHA = 6 + rwBLENDDESTALPHA = 7 + rwBLENDINVDESTALPHA = 8 + rwBLENDDESTCOLOR = 9 + rwBLENDINVDESTCOLOR = 10 + rwBLENDSRCALPHASAT = 11 + +class RwAlphaTestFunction(Enum): + rwALPHATESTFUNCTIONNAALPHATESTFUNCTION = 0 + rwALPHATESTFUNCTIONNEVER = 1 + rwALPHATESTFUNCTIONLESS = 2 + rwALPHATESTFUNCTIONEQUAL = 3 + rwALPHATESTFUNCTIONLESSEQUAL = 4 + rwALPHATESTFUNCTIONGREATER = 5 + rwALPHATESTFUNCTIONNOTEQUAL = 6 + rwALPHATESTFUNCTIONGREATEREQUAL = 7 + rwALPHATESTFUNCTIONALWAYS = 8 + + +# Render state to type mapping +RwRenderState_To_Type = { + # None - Not yet implemented + # Any other type means that the value has to be of that type + + 'rwRENDERSTATENARENDERSTATE': cint, + 'rwRENDERSTATETEXTURERASTER': str, # pointer in reality + 'rwRENDERSTATETEXTUREADDRESS': cenum(RwTextureAddressMode), + 'rwRENDERSTATETEXTUREADDRESSU': cenum(RwTextureAddressMode), + 'rwRENDERSTATETEXTUREADDRESSV': cenum(RwTextureAddressMode), + 'rwRENDERSTATETEXTUREPERSPECTIVE': None, + 'rwRENDERSTATEZTESTENABLE': cbool, + 'rwRENDERSTATESHADEMODE': None, + 'rwRENDERSTATEZWRITEENABLE': cbool, + 'rwRENDERSTATETEXTUREFILTER': None, + 'rwRENDERSTATESRCBLEND': cenum(RwBlendFunction), + 'rwRENDERSTATEDESTBLEND': cenum(RwBlendFunction), + 'rwRENDERSTATEVERTEXALPHAENABLE': cbool, + 'rwRENDERSTATEBORDERCOLOR': cint, + 'rwRENDERSTATEFOGENABLE': cbool, + 'rwRENDERSTATEFOGCOLOR': cbool, + 'rwRENDERSTATEFOGTYPE': None, + 'rwRENDERSTATEFOGDENSITY': int, + 'rwRENDERSTATECULLMODE': None, + 'rwRENDERSTATESTENCILENABLE': cbool, + 'rwRENDERSTATESTENCILFAIL': None, + 'rwRENDERSTATESTENCILZFAIL': None, + 'rwRENDERSTATESTENCILPASS': None, + 'rwRENDERSTATESTENCILFUNCTION': None, + 'rwRENDERSTATESTENCILFUNCTIONREF': str, + 'rwRENDERSTATESTENCILFUNCTIONMASK': None, + 'rwRENDERSTATESTENCILFUNCTIONWRITEMASK': None, + 'rwRENDERSTATEALPHATESTFUNCTION': cenum(RwAlphaTestFunction), + 'rwRENDERSTATEALPHATESTFUNCTIONREF': None, +} + +input("Make sure to have copied the code to the clipboard!") + +# Example user input +# RwEngineInstance->dOpenDevice.fpRenderStateSet(rwRENDERSTATEZWRITEENABLE, 0); +# RwEngineInstance->dOpenDevice.fpRenderStateSet(rwRENDERSTATEZTESTENABLE, 1u); +# RwEngineInstance->dOpenDevice.fpRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, 1u); +# RwEngineInstance->dOpenDevice.fpRenderStateSet(rwRENDERSTATEFOGENABLE, 0); +# RwEngineInstance->dOpenDevice.fpRenderStateSet(rwRENDERSTATESRCBLEND, 5u); +# RwEngineInstance->dOpenDevice.fpRenderStateSet(rwRENDERSTATEDESTBLEND, 6u); +# RwEngineInstance->dOpenDevice.fpRenderStateSet(rwRENDERSTATETEXTURERASTER, (unsigned int)gpCloudMaskTex->raster); + +# Tuples of (state_name, state_value); [Ex.: ("rwRENDERSTATEZWRITEENABLE", "0")] +input_values : list[tuple[str, str]] = [ + re.search(r'\((\w+), (.+)\)', line).groups() + for line in pyc.paste().splitlines() +] + +# Find longest name +longest = max(len(n) for (n, v) in input_values) + +# Format it +output = [] +for n, v in input_values: + value_type = RwRenderState_To_Type[n] + if value_type is None: + raise NotImplementedError(f'Value type for state {n} is not yet implemented!') + output.append( + f'RwRenderStateSet({n}, {" " * (longest - len(n))}RWRSTATE({value_type(v)}));' + ) +pyc.copy('\n'.join(output)) diff --git a/contrib/install.py b/contrib/install.py new file mode 100644 index 0000000000..f03af96af6 --- /dev/null +++ b/contrib/install.py @@ -0,0 +1,36 @@ +import tkinter as tk +import os +import subprocess +from tkinter import filedialog as tkFileDialog +from pathlib import Path +import zipfile + +def main(): + git_repo_root = Path.cwd() + + gta_root_dir = Path(tkFileDialog.askdirectory( + title='Select GTA:SA install directory' + )) + gta_scripts_dir = gta_root_dir / 'scripts' + + # Unpack zip into gta dir + print("Unpacking `plugins.zip` into GTA:SA root directory...") + with zipfile.ZipFile(git_repo_root / 'contrib' / 'plugins.zip') as plugins_zip: + plugins_zip.extractall(gta_root_dir) + + # Create symlinks + config_name = input("Choose configuration to link (debug/release, default: debug): ") or 'debug' + print(f"Creating the symlinks for `{config_name}` configuration...") + config_bin_dir = git_repo_root / 'bin' / Path(config_name) + print(f'{config_bin_dir=}') + for filename in ('gta_reversed.pdb', 'gta_reversed.asi',): + dst = gta_scripts_dir / filename + dst.unlink(missing_ok=True) # Delete symlink if it already exists + # This fails [WinError 1314] if the script isn't run with admin rights [softlinks require it] + # To run as admin just open an admin cmd, and type `python ./install.py` in the repo root dir + os.symlink(config_bin_dir / filename, dst) + + print('Done!') + +if __name__ == "__main__": + main() diff --git a/contrib/plugins.zip b/contrib/plugins.zip new file mode 100644 index 0000000000..e96082358c Binary files /dev/null and b/contrib/plugins.zip differ diff --git a/docs/CodingGuidelines.MD b/docs/CodingGuidelines.MD index 0612e6182a..a401c24f2f 100644 --- a/docs/CodingGuidelines.MD +++ b/docs/CodingGuidelines.MD @@ -4,10 +4,13 @@ * Please check and try to eliminate warnings from your code. ### Code style -* 4 space indentation, LF line endings. -* If some rule about something is not specified here, refer to how it's done in the code. -* Some classes may have *helper* functions to make code more readable. (usually denoted by *NOTSA* or *Helpers*) Try looking for and use them. +* 4 space indentation, LF line endings +* No hungarian notation [It's useless] +* If some rule about something is not specified here, refer to how it's done in the code +* Some classes may have *helper* functions to make code more readable. (Denoted by *NOTSA*) - Try adding new ones, or looking for and using them. +* Prefer `get`-ters/`set`-ters over raw member access * Use range-based for loops as much as possible. +* We encourage you to write modern C++, but if that's not your style, please keep the following in mind: ```cpp for (auto& element : array); // <-- GOOD @@ -17,23 +20,55 @@ for (int i = 0; i < std::size(array); i++); // <-- BAD ```cpp for (auto&& [i, e] : notsa::enumerate(array)); ``` +* If there's a dynamic `count` variable associated with a fixed size array, use `std::span` or `rng::views::take`. E.g.: +```cpp +// Bad +for (auto i = 0u; i < m_numThings; i++); + +// Good +for (auto& thing : std::span{ m_things, m_numThings }); +// Also good +for (auto& thing : m_things | rng::views::take(m_numThings)); -* Use `f` in float literals, omitting it makes them double. (e.g. `1.0f`) -* Use `std` library for generic functions like `min`, `max`, `lerp` etc. -* Function prototypes must look like it's from Android symbols. Except for output parameters like `T*` can be changed to `T&` to make the code prettier. -* Use lambdas for repetitive procedures in functions. -* Use `constexpr` variables instead of macros. +// ^ If these funcs are called more than once, make a helper function in the header. Like below: +auto GetActiveThings() { + return std::span{ m_things, m_numThings } +} +* Use `f` in float literals [As omitting it would make them a `double`] (e.g. `1.0f`) +* Use `std` library for generic functions like `min`, `max`, `lerp`, etc... +* `CVector` is interchangible with 3 floats [As is `CVector2D` with 2 floats] for function args +* Use lambdas for repetitive procedures in functions +* Use `constexpr` variables instead of macros +* Use `static inline` instead of `extern` and `static` in headers: +```cpp +class Foo { + static uint32& m_FooCount; // Bad + + static inline auto& m_FooCount = StaticRef(); // Good +} +``` #### Types * Use `auto` in function bodies if the variables' type is guessable. -* Guess for enum values, at least leave a TODO comment for it. -* Take care of const correctness. (e.g. `const char*` over `char*`) +* Guess for enum values [Or at least leave a `TODO` comment] +* Take care of const correctness [Especially of class methods] (e.g. `const char*` over `char*`) * Try to use SA types over RW as much as possible, **except** `RwMatrix`. (e.g. `CVector` for `RwV3d`, `CRGBA` for `RwRGBA`) * Use fixed width integer types (e.g. `uint8`, `int32`). -* Use `std::array` for arrays most of the time. Do not use it if the variable is just a pair of values or something basic like that. -* Do not use Win32 types. (e.g. `DWORD` -> `uint32`) +* Do not use Win32 integer types. [Except for Win32 exclusive code] (e.g. `DWORD` -> `uint32`) +* For array sizes, etc.. prefer using `unsigned` types over `signed` ones +* Whenever possible use `std::array` over `C-Style` array [as the former has bounds checking in debug mode, and can help us discover many bugs] + +#### Fixing bugs +Whenever you find a bug, we encourage you to fix it [and/or at least] leave a comment explaining what the bug is. +Bug fixes should only be active if `notsa::IsFixBugs()` returns `true`. +If that's not possible [due to code complexity], then wrap into an `#ifdef`: +```c +#ifdef FIX_BUGS +// Bug fixing code here +#endif +``` ### Contributing -Please make sure to test your contribution before opening a PR. Guess what places/missions are affected by your code and test them. Use debug menu (F7) for quick access to stuff. +Please make sure to test your code before opening a PR. Guess what places/missions are affected by your code and test them. Use debug menu (F7) for quick access to stuff. If you don't know how to test the code or think you have not tested enough specify it in the PR message. diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 9dbf67cb70..3f5a1243f7 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -10,6 +10,10 @@ set(OGG_FOUND 1) add_subdirectory(vorbis EXCLUDE_FROM_ALL) +option(SPDLOG_ENABLE_PCH "" ON) +option(SPDLOG_USE_STD_FORMAT "" ON) +add_subdirectory(spdlog EXCLUDE_FROM_ALL) + ############################################################################################# ############# create the imgui project ############################################################################################# @@ -39,3 +43,10 @@ target_include_directories(imgui PUBLIC ) target_link_libraries(imgui PRIVATE d3d9 ${CMAKE_DL_LIBS}) target_compile_definitions(imgui PRIVATE _CRT_SECURE_NO_WARNINGS) + +# set options before add_subdirectory +# available options: TRACY_ENABLE , TRACY_ON_DEMAND , TRACY_NO_BROADCAST , TRACY_NO_CODE_TRANSFER , ... +option(TRACY_ENABLE "" ON) +option(TRACY_ON_DEMAND "" ON) +option(TRACY_CALLSTACK "" ON) +add_subdirectory(tracy) # target: TracyClient or alias Tracy :: TracyClient diff --git a/libs/imgui b/libs/imgui index 17a7084b57..8566fec661 160000 --- a/libs/imgui +++ b/libs/imgui @@ -1 +1 @@ -Subproject commit 17a7084b57f9713ec6538881079d6c5a1b6b3598 +Subproject commit 8566fec661801a026e56f06cd53f5dac25c2595b diff --git a/libs/premake5.lua b/libs/premake5.lua index 1a01b2f4b0..cef9f79e34 100644 --- a/libs/premake5.lua +++ b/libs/premake5.lua @@ -122,3 +122,108 @@ project "imgui" "imgui/backends", "imgui/misc/cpp" } + +project "spdlog" + cppdialect "C++20" + kind "StaticLib" + targetname "spdlog" + warnings "Off" + + vpaths { + ["Headers/*"] = {"spdlog/include/spdlog/**.*",}, + ["Sources/*"] = {"spdlog/src/**.c*",}, + ["*"] = {"premake5.lua", "CMakeLists.txt"} + } + + files { + "spdlog/src/spdlog.cpp", + "spdlog/src/stdout_sinks.cpp", + "spdlog/src/color_sinks.cpp", + "spdlog/src/file_sinks.cpp", + "spdlog/src/async.cpp", + "spdlog/src/cfg.cpp", + "spdlog/include/**" + } + + includedirs { + "spdlog/include" + } + + defines { + "SPDLOG_COMPILED_LIB" + } + +project "tracy" + language "C++" + kind "StaticLib" + targetname "tracy" + warnings "Off" + + vpaths { + ["Headers/*"] = {"tracy/**.h*",}, + ["Sources/*"] = {"tracy/**.cpp",}, + ["*"] = {"premake5.lua", "CMakeLists.txt"} + } + + includedirs { + "tracy/public/", + } + + files { + "tracy/public/TracyClient.cpp", + + -- tracy_includes + "tracy/public/tracy/TracyC.h", + "tracy/public/tracy/Tracy.hpp", + "tracy/public/tracy/TracyD3D11.hpp", + "tracy/public/tracy/TracyD3D12.hpp", + "tracy/public/tracy/TracyLua.hpp", + "tracy/public/tracy/TracyOpenCL.hpp", + "tracy/public/tracy/TracyOpenGL.hpp", + "tracy/public/tracy/TracyVulkan.hpp", + + -- client_includes + "tracy/public/client/tracy_concurrentqueue.h", + "tracy/public/client/tracy_rpmalloc.hpp", + "tracy/public/client/tracy_SPSCQueue.h", + "tracy/public/client/TracyArmCpuTable.hpp", + "tracy/public/client/TracyCallstack.h", + "tracy/public/client/TracyCallstack.hpp", + "tracy/public/client/TracyCpuid.hpp", + "tracy/public/client/TracyDebug.hpp", + "tracy/public/client/TracyDxt1.hpp", + "tracy/public/client/TracyFastVector.hpp", + "tracy/public/client/TracyLock.hpp", + "tracy/public/client/TracyProfiler.hpp", + "tracy/public/client/TracyRingBuffer.hpp", + "tracy/public/client/TracyScoped.hpp", + "tracy/public/client/TracyStringHelpers.hpp", + "tracy/public/client/TracySysPower.hpp", + "tracy/public/client/TracySysTime.hpp", + "tracy/public/client/TracySysTrace.hpp", + "tracy/public/client/TracyThread.hpp", + + -- common_includes + "tracy/public/common/tracy_lz4.hpp", + "tracy/public/common/tracy_lz4hc.hpp", + "tracy/public/common/TracyAlign.hpp", + "tracy/public/common/TracyAlloc.hpp", + "tracy/public/common/TracyApi.h", + "tracy/public/common/TracyColor.hpp", + "tracy/public/common/TracyForceInline.hpp", + "tracy/public/common/TracyMutex.hpp", + "tracy/public/common/TracyProtocol.hpp", + "tracy/public/common/TracyQueue.hpp", + "tracy/public/common/TracySocket.hpp", + "tracy/public/common/TracyStackFrames.hpp", + "tracy/public/common/TracySystem.hpp", + "tracy/public/common/TracyUwp.hpp", + "tracy/public/common/TracyYield.hpp", + } + + defines { + "TRACY_ENABLE", + "TRACY_CALLSTACK", + "TRACY_ON_DEMAND", + --"TRACY_NO_CODE_TRANSFER" -- Uncomment if you want callstacks to be working + } diff --git a/libs/spdlog b/libs/spdlog new file mode 160000 index 0000000000..706ad70591 --- /dev/null +++ b/libs/spdlog @@ -0,0 +1 @@ +Subproject commit 706ad7059125f32158dad4441938c08fa910f143 diff --git a/libs/tracy b/libs/tracy new file mode 160000 index 0000000000..897aec5b06 --- /dev/null +++ b/libs/tracy @@ -0,0 +1 @@ +Subproject commit 897aec5b062664d2485f4f9a213715d2e527e0ca diff --git a/premake5.lua b/premake5.lua index 5501ede402..0da05e60f1 100644 --- a/premake5.lua +++ b/premake5.lua @@ -53,7 +53,7 @@ solution "gta_reversed" flags { "MultiProcessorCompile" } linkoptions { "/ignore:4099,4251,4275" } buildoptions { "/EHsc", "/Zc:preprocessor", "/bigobj" } - disablewarnings { 26812, 26495, 4099, 4251, 4275 } + disablewarnings { 26812, 26495, 4275, 4251, 4200, 4099 } filter "files:libs/**" warnings "Off" diff --git a/source/Base.h b/source/Base.h index a3b99e5f7e..53eef70ed1 100644 --- a/source/Base.h +++ b/source/Base.h @@ -6,6 +6,8 @@ */ #pragma once +#include "app/app_debug.h" + #define PLUGIN_API #define VALIDATE_SIZE(struc, size) static_assert(sizeof(struc) == size, "Invalid structure size of " #struc) @@ -63,7 +65,10 @@ template [[noreturn]] static void unreachable(std::string_view method, std::string_view file, unsigned line, std::string_view fmt = "", Ts&&... fmtArgs) { const auto userDetails = std::vformat(fmt, std::make_format_args(std::forward(fmtArgs)...)); const auto mbMsg = std::format("File: {}\nIn: {}:{}\n\nDetails:\n{}", fs::relative(file, SOURCE_PATH).string(), method, line, userDetails.empty() ? "" : userDetails.c_str()); - + + spdlog::error(mbMsg); + spdlog::dump_backtrace(); + const auto result = MessageBox( NULL, mbMsg.c_str(), @@ -130,9 +135,23 @@ template * @tparam T The type of the variable * @param Addr The address of it */ +template +T& StaticRef(uintptr addr) { + return *reinterpret_cast(addr); +} + +// TODO: Replace this with the one above template T& StaticRef() { - return *reinterpret_cast(Addr); + return StaticRef(Addr); +} + +template +void SAFE_RELEASE(T*& ptr) { // DirectX stuff `Release()` + if (ptr) { + ptr->Release(); + ptr = nullptr; + } } #define _IGNORED_ diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 805fa910b0..b937ad16ce 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -30,7 +30,7 @@ function(supress_deps_warnings targets) ) endforeach() endfunction() -supress_deps_warnings(ogg imgui vorbisfile) +supress_deps_warnings(ogg imgui vorbisfile spdlog) set_target_properties(${RE_PROJECT_LIB_NAME} PROPERTIES @@ -63,7 +63,26 @@ target_link_libraries(${RE_PROJECT_LIB_NAME} PRIVATE ogg vorbisfile imgui + spdlog + Tracy::TracyClient + ddraw + Winmm + dxguid + strmiids + dsound + d3d9 ) + +if(WIN32) + if(MSVC) # MSVC knows how to find dbghelp on it's own + target_link_libraries(${RE_PROJECT_LIB_NAME} PRIVATE + dbghelp + ) + else() + message(FATAL_ERROR "Need to link dbghelp lib for win32") + endif() +endif() + target_include_directories(${RE_PROJECT_LIB_NAME} PRIVATE / toolsmenu/ diff --git a/source/InjectHooksMain.cpp b/source/InjectHooksMain.cpp index 59df7a5fae..ec108a872b 100644 --- a/source/InjectHooksMain.cpp +++ b/source/InjectHooksMain.cpp @@ -1,37 +1,75 @@ #include "StdInc.h" // Audio -#include "AEAudioChannel.h" +// -- General #include "AEAudioEnvironment.h" -#include "AEAudioHardware.h" #include "AEAudioUtility.h" -#include "AEDataStream.h" -#include "AEMFDecoder.h" #include "AESmoothFadeThread.h" -#include "AESoundManager.h" -#include "AEStaticChannel.h" -#include "AEVorbisDecoder.h" -#include "AEWaveDecoder.h" -#include "AEWMADecoder.h" -#include "AEStreamingDecoder.h" -#include "AEUserRadioTrackManager.h" -#include "AEWaterCannonAudioEntity.h" -#include "AEDoorAudioEntity.h" +#include "AESound.h" #include "AEStreamThread.h" -#include "AEFrontendAudioEntity.h" -#include "AEScriptAudioEntity.h" -#include "AERadioTrackManager.h" -#include "AEAmbienceTrackManager.h" -#include "AECutsceneTrackManager.h" +#include "AudioEngine.h" +#include "AudioZones.h" +// -- Entities +#include "AEAudioEntity.h" #include "AECollisionAudioEntity.h" +#include "AEDoorAudioEntity.h" +#include "AEExplosionAudioEntity.h" +#include "AEFireAudioEntity.h" +#include "AEFrontendAudioEntity.h" #include "AEGlobalWeaponAudioEntity.h" #include "AEPedAudioEntity.h" #include "AEPedlessSpeechAudioEntity.h" +#include "AEPedSpeechAudioEntity.h" +#include "AEPedWeaponAudioEntity.h" #include "AEPoliceScannerAudioEntity.h" -#include "AudioEngine.h" +#include "AEScriptAudioEntity.h" +#include "AETwinLoopSoundEntity.h" +#include "AEVehicleAudioEntity.h" +#include "AEWaterCannonAudioEntity.h" +#include "AEWeaponAudioEntity.h" +#include "AEWeatherAudioEntity.h" +// -- Hardware +#include "AEAudioChannel.h" +#include "AEAudioHardware.h" +#include "AEStaticChannel.h" +#include "AEStreamingChannel.h" +// -- Loaders +#include "AEBankLoader.h" +#include "AEDataStream.h" +#include "AEMFDecoder.h" +#include "AEMP3BankLoader.h" +#include "AEMP3TrackLoader.h" +#include "AEStreamingDecoder.h" +#include "AEStreamTransformer.h" +#include "AETrackLoader.h" +#include "AEVorbisDecoder.h" +#include "AEWaveDecoder.h" +#include "AEWMADecoder.h" +// -- Managers +#include "AEAmbienceTrackManager.h" +#include "AECutsceneTrackManager.h" +#include "AERadioTrackManager.h" +#include "AESoundManager.h" +#include "AEUserRadioTrackManager.h" + + #include "Garage.h" #include "Garages.h" +// FX +#include "FxSystemBP.h" +#include "FxSystem.h" +#include "FxSphere.h" +#include "FxPrimBP.h" +#include "FxPrim.h" +#include "FxMemoryPool.h" +#include "FxManager.h" +#include "FxFrustumInfo.h" +#include "FxEmitterPrt.h" +#include "FxEmitterBP.h" +#include "FxEmitter.h" +#include "Fx.h" + #include "UIRenderer.h" #include "CarGenerator.h" @@ -122,6 +160,7 @@ #include "Pickup.h" #include "Pickups.h" #include "PedIK.h" +#include "HandShaker.h" // Plant #include "PlantMgr.h" @@ -384,6 +423,7 @@ #include "TaskComplexSmartFleePoint.h" #include "Interior/TaskInteriorBeInHouse.h" #include "Tasks/TaskTypes/TaskComplexKillPedOnFootArmed.h" +#include "Tasks/TaskTypes/TaskSimpleWaitUntilLeaderAreaCodesMatch.h" #include "EventSeenPanickedPed.h" #include "EventCarUpsideDown.h" @@ -398,8 +438,7 @@ #include "platform/win/VideoPlayer/VideoPlayer.h" #include "platform/win/VideoMode.h" -#include "platform/win/win.h" -#include "platform/platform.h" +#include "platform/win/Platform.h" #include "app/app.h" #include @@ -410,19 +449,25 @@ #include "PedToPlayerConversations.h" #include "extensions/utility.hpp" +#include "extensions/CommandLine.h" #include +#include "ReversibleHooks/RootHookCategory.h" + void InjectHooksMain() { HookInstall(0x53E230, &Render2dStuff); // [ImGui] This one shouldn't be reversible, it contains imgui debug menu logic, and makes game unplayable without HookInstall(0x541DD0, CPad::UpdatePads); // [ImGui] Changes logic of the function and shouldn't be toggled on/off HookInstall(0x459F70, CVehicleRecording::Render); // [ImGui] Debug stuff rendering + CHandShaker::InjectHooks(); CCutsceneMgr::InjectHooks(); CFileMgr::InjectHooks(); CPedGroupPlacer::InjectHooks(); CLoadedCarGroup::InjectHooks(); RenderBuffer::InjectHooks(); CStaticShadow::InjectHooks(); + CPedGroup::InjectHooks(); + CPedGroupMembership::InjectHooks(); CRealTimeShadowManager::InjectHooks(); CRealTimeShadow::InjectHooks(); CPopCycle::InjectHooks(); @@ -443,7 +488,6 @@ void InjectHooksMain() { CPedAttractorPedPlacer::InjectHooks(); BoneNode_c::InjectHooks(); BoneNodeManager_c::InjectHooks(); - CAnimBlendClumpData::InjectHooks(); IKChainManager_c::InjectHooks(); IKChain_c::InjectHooks(); CCheckpoint::InjectHooks(); @@ -484,7 +528,6 @@ void InjectHooksMain() { CFireManager::InjectHooks(); CGroupEventHandler::InjectHooks(); CVehicleRecording::InjectHooks(); - Fx_c::InjectHooks(); CBrightLights::InjectHooks(); CShinyTexts::InjectHooks(); CPedTaskPair::InjectHooks(); @@ -507,7 +550,6 @@ void InjectHooksMain() { CPtrListDoubleLink::InjectHooks(); CPtrNodeSingleLink::InjectHooks(); CPtrListSingleLink::InjectHooks(); - List_c::InjectHooks(); CReferences::InjectHooks(); CPopulation::InjectHooks(); CModelInfo::InjectHooks(); @@ -700,41 +742,60 @@ void InjectHooksMain() { Pools(); const auto Audio = []() { - CAEVehicleAudioEntity::InjectHooks(); - CAESoundManager::InjectHooks(); - CAESound::InjectHooks(); - CAEAudioHardware::InjectHooks(); + // General CAEAudioEnvironment::InjectHooks(); CAEAudioUtility::InjectHooks(); + CAESmoothFadeThread::InjectHooks(); + CAESound::InjectHooks(); + CAEStreamThread::InjectHooks(); + CAudioEngine::InjectHooks(); + CAudioZones::InjectHooks(); + + // Entities + // CAEAudioEntity::InjectHooks(); -- inlined + CAECollisionAudioEntity::InjectHooks(); + CAEDoorAudioEntity::InjectHooks(); + CAEExplosionAudioEntity::InjectHooks(); + CAEFireAudioEntity::InjectHooks(); + CAEFrontendAudioEntity::InjectHooks(); + CAEGlobalWeaponAudioEntity::InjectHooks(); + CAEPedAudioEntity::InjectHooks(); + CAEPedlessSpeechAudioEntity::InjectHooks(); + CAEPedSpeechAudioEntity::InjectHooks(); + CAEPedWeaponAudioEntity::InjectHooks(); + CAEPoliceScannerAudioEntity::InjectHooks(); + CAEScriptAudioEntity::InjectHooks(); + CAETwinLoopSoundEntity::InjectHooks(); + CAEVehicleAudioEntity::InjectHooks(); + CAEWaterCannonAudioEntity::InjectHooks(); + // CAEWeaponAudioEntity::InjectHooks(); + CAEWeatherAudioEntity::InjectHooks(); + + // Hardware CAEAudioChannel::InjectHooks(); + CAEAudioHardware::InjectHooks(); CAEStaticChannel::InjectHooks(); - CAESmoothFadeThread::InjectHooks(); + CAEStreamingChannel::InjectHooks(); + + // Loaders + CAEBankLoader::InjectHooks(); CAEDataStream::InjectHooks(); - CAEStreamingDecoder::InjectHooks(); CAEMFDecoder::InjectHooks(); - CAEUserRadioTrackManager::InjectHooks(); + CAEMP3BankLoader::InjectHooks(); + CAEMP3TrackLoader::InjectHooks(); + CAEStreamingDecoder::InjectHooks(); + // CAEStreamTransformer::InjectHooks(); -- injected by AEDataStream::InjectHooks. + CAETrackLoader::InjectHooks(); CAEVorbisDecoder::InjectHooks(); CAEWaveDecoder::InjectHooks(); CAEWMADecoder::InjectHooks(); - CAEWaterCannonAudioEntity::InjectHooks(); - CAETwinLoopSoundEntity::InjectHooks(); - CAEDoorAudioEntity::InjectHooks(); - CAEWeatherAudioEntity::InjectHooks(); - CAEStreamThread::InjectHooks(); - CAEFrontendAudioEntity::InjectHooks(); - CAEWeaponAudioEntity::InjectHooks(); - CAEScriptAudioEntity::InjectHooks(); - CAERadioTrackManager::InjectHooks(); + + // Managers CAEAmbienceTrackManager::InjectHooks(); CAECutsceneTrackManager::InjectHooks(); - CAECollisionAudioEntity::InjectHooks(); - CAEGlobalWeaponAudioEntity::InjectHooks(); - CAEPedAudioEntity::InjectHooks(); - CAEPedlessSpeechAudioEntity::InjectHooks(); - CAEPoliceScannerAudioEntity::InjectHooks(); - CAudioEngine::InjectHooks(); - CAEFireAudioEntity::InjectHooks(); - CAEExplosionAudioEntity::InjectHooks(); + CAERadioTrackManager::InjectHooks(); + CAESoundManager::InjectHooks(); + CAEUserRadioTrackManager::InjectHooks(); }; const auto Plant = []() { @@ -818,6 +879,7 @@ void InjectHooksMain() { CTaskComplexKillPedOnFootMelee::InjectHooks(); CTaskComplexKillPedOnFootStealth::InjectHooks(); + CTaskSimpleWaitUntilLeaderAreaCodesMatch::InjectHooks(); CTaskComplexLeaveCarAndDie::InjectHooks(); CTaskComplexLeaveBoat::InjectHooks(); CTaskComplexLeaveCarAndFlee::InjectHooks(); @@ -1167,10 +1229,18 @@ void InjectHooksMain() { }; const auto Fx = []() { - FxManager_c::InjectHooks(); FxSystemBP_c::InjectHooks(); - // FxSystem_c::InjectHooks(); + FxSystem_c::InjectHooks(); + FxSphere_c::InjectHooks(); FxPrimBP_c::InjectHooks(); + FxMemoryPool_c::InjectHooks(); + FxInfoManager_c::InjectHooks(); + FxManager_c::InjectHooks(); + // ReversibleHooks::Install("FxFrustumInfo_c", "IsCollision", 0x4AA030, &FxFrustumInfo_c::IsCollision); + FxEmitterPrt_c::InjectHooks(); + FxEmitterBP_c::InjectHooks(); + FxEmitter_c::InjectHooks(); + Fx_c::InjectHooks(); }; const auto Vehicle = []() { @@ -1232,14 +1302,22 @@ void InjectHooksMain() { CPedToPlayerConversations::InjectHooks(); }; + Animation(); App(); Audio(); Tasks(); Events(); Fx(); Vehicle(); + Interior(); Scripts(); Conversations(); + + if (CommandLine::unhookAll) + ReversibleHooks::GetRootCategory().SetAllItemsEnabled(false); + + if (!CommandLine::unhookSome.empty() || !CommandLine::unhookExcept.empty()) + NOTSA_LOG_WARN("Command line arguments --unhook and --unhook-except are unimplemented!"); } void InjectHooksMain(HMODULE hThisDLL) { diff --git a/source/StdInc.h b/source/StdInc.h index 35bdfc2e7c..628d2a7b78 100644 --- a/source/StdInc.h +++ b/source/StdInc.h @@ -29,12 +29,17 @@ namespace rng = std::ranges; namespace rngv = std::views; +#include +namespace fs = std::filesystem; + #include "Base.h" #include "config.h" #include "HookSystem.h" #include "reversiblehooks\ReversibleHooks.h" +#include + // DirectX #include #define DIRECTINPUT_VERSION 0x0800 @@ -62,6 +67,8 @@ namespace rngv = std::views; #include "game_sa\RenderWare\rw\rwtexdict.h" #include "game_sa\RenderWare\rw\skeleton.h" #include "game_sa\RenderWare\RenderWare.h" +#include +#include // oswrapper #include "oswrapper/oswrapper.h" @@ -409,19 +416,9 @@ namespace rngv = std::views; #include "game_sa\Audio\config\eAudioSlot.h" #include "game_sa\Audio\config\eSFX.h" -#include "game_sa\Fx\CarFXRenderer.h" -#include "game_sa\Fx\FxBox.h" -#include "game_sa\Fx\FxEmitterBP.h" -#include "game_sa\Fx\FxFrustumInfo.h" -#include "game_sa\Fx\FxInfoManager.h" +#include "game_sa\Fx\eFxInfoType.h" #include "game_sa\Fx\FxManager.h" -#include "game_sa\Fx\FxMemoryPool.h" -#include "game_sa\Fx\FxPlane.h" -#include "game_sa\Fx\FxPrimBP.h" #include "game_sa\Fx\FxPrtMult.h" -#include "game_sa\Fx\FxSphere.h" -#include "game_sa\Fx\FxSystemBP.h" -#include "game_sa\Fx\FxSystem.h" #include "game_sa\Fx\Fx.h" #include "game_sa\Models\AtomicModelInfo.h" diff --git a/source/app/app.cpp b/source/app/app.cpp index 4fa6c8f797..6a68185a00 100644 --- a/source/app/app.cpp +++ b/source/app/app.cpp @@ -6,11 +6,12 @@ #include "platform.h" #include "Clouds.h" +#include "CarFXRenderer.h" #include "Plugins/BreakablePlugin/BreakablePlugin.h" #include "Pipelines/CustomBuilding/CustomBuildingRenderer.h" -#include "win/win.h" +#include "platform/win/Platform.h" void AppInjectHooks() { RH_ScopedCategory("App"); @@ -54,6 +55,8 @@ bool DoRWStuffStartOfFrame(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 Bo // 0x53D7A0 bool DoRWStuffStartOfFrame_Horizon(int16 TopRed, int16 TopGreen, int16 TopBlue, int16 BottomRed, int16 BottomGreen, int16 BottomBlue, int16 Alpha) { + ZoneScoped; + CDraw::CalculateAspectRatio(); CameraSize(Scene.m_pRwCamera, nullptr, SCREEN_VIEW_WINDOW, SCREEN_ASPECT_RATIO); CVisibilityPlugins::SetRenderWareCamera(Scene.m_pRwCamera); diff --git a/source/app/app.h b/source/app/app.h index 6f53e4db23..308fada285 100644 --- a/source/app/app.h +++ b/source/app/app.h @@ -130,3 +130,5 @@ float GetDayNightBalance(); // 0x746870 void MessageLoop(); + +char* getDvdGamePath(); diff --git a/source/app/app_camera.cpp b/source/app/app_camera.cpp index 192d083de5..f0aed69a30 100644 --- a/source/app/app_camera.cpp +++ b/source/app/app_camera.cpp @@ -74,8 +74,7 @@ void CameraSize(RwCamera* camera, RwRect* rect, RwReal viewWindow, RwReal aspect rect->x = rect->y = 0; } - RwVideoMode videoMode; - RwEngineGetVideoModeInfo(&videoMode, RwEngineGetCurrentVideoMode()); + const auto videoMode = RwEngineGetVideoModeInfo(RwEngineGetCurrentVideoMode()); if (videoMode.flags & rwVIDEOMODEEXCLUSIVE) { rect->x = rect->y = 0; diff --git a/source/app/app_debug.cpp b/source/app/app_debug.cpp index 6e3bdf5930..aef7ad9be5 100644 --- a/source/app/app_debug.cpp +++ b/source/app/app_debug.cpp @@ -1,6 +1,13 @@ #include "StdInc.h" #include "app_debug.h" +#include +#include +#include +#include +#include +#include +#include #define FINAL 0 @@ -51,3 +58,174 @@ void FlushObrsPrintfs() { RtCharsetBufferFlush(); #endif } + +// This probably should be in winps :D +LONG WINAPI WindowsExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { + // If this function itself crashes it's invoked again + // So let's prevent the recusion with this simple hack + static bool s_HasHandled = false; + if (s_HasHandled) { + return EXCEPTION_CONTINUE_EXECUTION; + } + s_HasHandled = true; + + spdlog::apply_all([](auto&& logger) { + logger->dump_backtrace(); + }); + + const auto Section = [](const char* name) { + SPDLOG_INFO("*********{}**********", name); + }; + + Section("UNHANDLED EXCEPTION"); + + SPDLOG_INFO("Exception Code: {:#010x}", pExceptionInfo->ExceptionRecord->ExceptionCode); + SPDLOG_INFO("Exception Flags: {:#010x}", pExceptionInfo->ExceptionRecord->ExceptionFlags); + SPDLOG_INFO("Exception Address: {:#010x}", (uintptr_t)pExceptionInfo->ExceptionRecord->ExceptionAddress); + + // Dump exception parameters + Section("PARAMETERS"); + { + SPDLOG_INFO("Parameters[{}]:", pExceptionInfo->ExceptionRecord->NumberParameters); + for (DWORD i = 0; i < pExceptionInfo->ExceptionRecord->NumberParameters; i++) { + SPDLOG_INFO("{:>8}: {:#010x}", i, pExceptionInfo->ExceptionRecord->ExceptionInformation[i]); + } + } + + CONTEXT& context = *pExceptionInfo->ContextRecord; + + Section("REGISTERS"); + { + const auto DumpRegister = [](auto name, auto value) { + SPDLOG_INFO("\t{}: {:#010x}", name, value); + }; + DumpRegister("EAX", context.Eax); + DumpRegister("EBX", context.Ebx); + DumpRegister("ECX", context.Ecx); + DumpRegister("EDX", context.Edx); + DumpRegister("ESI", context.Esi); + DumpRegister("EDI", context.Edi); + DumpRegister("EBP", context.Ebp); + DumpRegister("ESP", context.Esp); + DumpRegister("EIP", context.Eip); + DumpRegister("EFLAGS", context.EFlags); + } + +#if 0 + Section("LOADED MODULES"); + { + HANDLE hProcess = GetCurrentProcess(); + + HMODULE hModules[1024]; + DWORD cbNeeded; + + if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded)) { + const DWORD numModules = cbNeeded / sizeof(HMODULE); + for (DWORD i = 0; i < numModules; i++) { + MODULEINFO moduleInfo; + if (GetModuleInformation(hProcess, hModules[i], &moduleInfo, sizeof(moduleInfo))) { + char moduleName[MAX_PATH]; + GetModuleBaseName (hProcess, hModules[i], moduleName, sizeof(moduleName)); + + SPDLOG_INFO("\t{:#010x}: {}", LOG_PTR(moduleInfo.lpBaseOfDll), moduleName); + } + } + } + } +#endif + + Section("CALL STACK"); + { + HANDLE hProcess = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + + // Initialize symbol handler + SymInitialize(hProcess, NULL, TRUE); + + STACKFRAME stackFrame = {}; + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrStack.Mode = AddrModeFlat; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrPC.Offset = context.Eip; + stackFrame.AddrStack.Offset = context.Esp; + stackFrame.AddrFrame.Offset = context.Ebp; + + DWORD prevFrameOffset = 0; + while (StackWalk( + IMAGE_FILE_MACHINE_I386, hProcess, hThread, &stackFrame, &context, NULL, + SymFunctionTableAccess, SymGetModuleBase, NULL)) + { + DWORD pcOffset = stackFrame.AddrPC.Offset; + DWORD moduleBase = SymGetModuleBase(hProcess, pcOffset); + if (moduleBase == NULL) { + break; + } + + DWORD displacement = 0; + IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) }; + BOOL hasLineInfo = SymGetLineFromAddr(hProcess, pcOffset, &displacement, &lineInfo); + + IMAGEHLP_MODULE moduleInfo{ sizeof(IMAGEHLP_MODULE) }; + BOOL hasModuleInfo = SymGetModuleInfo(hProcess, moduleBase, &moduleInfo); + + char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = { 0 }; + PSYMBOL_INFO sym = (PSYMBOL_INFO)symbolBuffer; + sym->SizeOfStruct = sizeof(SYMBOL_INFO); + sym->MaxNameLen = MAX_SYM_NAME; + BOOL hasSym = SymFromAddr(hProcess, pcOffset, NULL, sym); + + SPDLOG_INFO( + "\t{:#010x}: {}!{}:{}", + pcOffset, + hasModuleInfo ? moduleInfo.ModuleName : "", + hasSym ? sym->Name : "", + hasLineInfo ? lineInfo.LineNumber : 0 + ); + } + + // Cleanup symbol handler + SymCleanup(hProcess); + } + + Section("END OF UNHANDLED EXCEPTION"); + + spdlog::apply_all([](auto&& logger) { + logger->flush(); + }); + + return EXCEPTION_EXECUTE_HANDLER; +} + +notsa::Logging::Logging() { +#if 0 + while (!IsDebuggerPresent()) { + Sleep(1); + } +#endif + spdlog::init_thread_pool(1 << 16, 4); + + // See https://github.com/gabime/spdlog/wiki/3.-Custom-formatting#pattern-flags + spdlog::set_pattern("%^[%l][%H:%M:%S.%e][%s:%#]: %v%$"); + spdlog::enable_backtrace(128); + spdlog::set_level(spdlog::level::debug); + + m_sinks.emplace_back(std::make_shared("logs/log.log")); + m_sinks.emplace_back(std::make_shared()); + + spdlog::set_default_logger(Create("default")); + + AddVectoredExceptionHandler(1, WindowsExceptionHandler); +} + +notsa::Logging::~Logging() { + //spdlog::shutdown(); +} + +auto notsa::Logging::Create(std::string name, std::optional level) -> notsa::log_ptr { + auto logger = std::make_shared(name, m_sinks.begin(), m_sinks.end()); + spdlog::initialize_logger(logger); + if (level.has_value()) { + logger->set_level(*level); + } + return logger; +} diff --git a/source/app/app_debug.h b/source/app/app_debug.h index 9784bc4c74..554b08661a 100644 --- a/source/app/app_debug.h +++ b/source/app/app_debug.h @@ -1,5 +1,12 @@ #pragma once -//#include "common.h" + +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE +#include +#include +#include + +#define PUSH_RENDERGROUP(str) 0 +#define POP_RENDERGROUP() 0 namespace notsa { namespace detail { @@ -8,35 +15,39 @@ static void VerifyMacroImpl(bool result) { } }; }; - #define VERIFY notsa::detail::VerifyMacroImpl #define VERIFY_TODO_FIX(_expr) (_expr) // Macro used to mark shit that uses `VERIFY and sometimes fails -#ifdef _DEBUG namespace notsa { +using log_ptr = std::shared_ptr; +struct Logging : public Singleton { + Logging(); + ~Logging(); -template -static void DevPrint(int lineno, std::string_view file, std::string_view fmt, Ts&&... fmtArgs) { - const auto userFormat = std::vformat(fmt, std::make_format_args(std::forward(fmtArgs)...)); - std::cout << std::format("[{} @ {}] {}", fs::relative(file, SOURCE_PATH).string(), lineno, userFormat); + auto Create(std::string name, std::optional level = std::nullopt) -> log_ptr; - if (fmt.back() != '\n') { - std::cout << std::endl; - } -} +private: + std::vector m_sinks; }; -// WARNING: Use std::format specifiers! Use LOG_PTR macro for pointer arguments. -#define DEV_LOG(...) notsa::DevPrint(__LINE__, __FILE__, ##__VA_ARGS__) -#define LOG_PTR(x) ((const void*)x) -#else -#define DEV_LOG(...) (void)0 -#define LOG_PTR(x) -#endif +}; +//! Make sure to *NOT* call this function to initialize static variables, it will fuck up spdlog. +//! Instead, make the static variable with type `notsa::log_ptr` and initialize it in `InjectHooks`! +#define NOTSA_MAKE_LOGGER notsa::Logging::GetSingleton().Create -#define PUSH_RENDERGROUP(str) 0 -#define POP_RENDERGROUP() 0 +#define NOTSA_LOG_DEBUG SPDLOG_DEBUG +#define DEV_LOG NOTSA_LOG_DEBUG // Prefer using `NOTSA_LOG_DEBUG` [This macro will be eventually replaced by the latter] + +#define NOTSA_LOG_CRIT SPDLOG_CRITICAL +#define NOTSA_LOG_ERR SPDLOG_ERROR +#define NOTSA_LOG_WARN SPDLOG_WARN +#define NOTSA_LOG_TRACE SPDLOG_TRACE + +//! Use this to pass pointers to logging functions [both std::format, logs, and printf-style stuff] +// We're casting to uintptr_t, because formatting just works better that way +#define LOG_PTR(x) ((uintptr_t)(x)) +static_assert(sizeof(void*) == sizeof(uintptr_t)); void CreateDebugFont(); void DestroyDebugFont(); -void ObrsPrintfString(const char* str, int16 x, int16 y); +//void ObrsPrintfString(const char* str, int16 x, int16 y); void FlushObrsPrintfs(); diff --git a/source/app/app_game.cpp b/source/app/app_game.cpp index c57f3c19cc..e8c6ddbca8 100644 --- a/source/app/app_game.cpp +++ b/source/app/app_game.cpp @@ -1,6 +1,8 @@ #include "StdInc.h" -#include "app_game.h" +#include + +#include "app_game.h" #include "LoadingScreen.h" #include "PlantMgr.h" #include "Shadows.h" @@ -14,13 +16,16 @@ #include "Garages.h" #include "UIRenderer.h" #include "Gamma.h" -#include -#include -#include -#include -#include -#include -#include +#include "Birds.h" +#include "Skidmarks.h" +#include "Ropes.h" +#include "Glass.h" +#include "WaterCannons.h" +#include "VehicleRecording.h" +#include "PostEffects.h" +#include "CarFXRenderer.h" + +#include "extensions/Configs/FastLoader.hpp" void AppGameInjectHooks() { RH_ScopedCategory("App"); @@ -36,11 +41,11 @@ void AppGameInjectHooks() { RH_ScopedGlobalInstall(RenderEffects, 0x53E170); RH_ScopedGlobalInstall(RenderScene, 0x53DF40); RH_ScopedGlobalInstall(RenderMenus, 0x53E530); - RH_ScopedGlobalInstall(Render2dStuff, 0x53E230, {.locked = true}); + RH_ScopedGlobalInstall(Render2dStuff, 0x53E230, { .locked = true }); // Must be hooked at all times otherwise game locks! RH_ScopedGlobalInstall(RenderDebugShit, 0x53E160); - RH_ScopedGlobalInstall(Idle, 0x53E920); - RH_ScopedGlobalInstall(FrontendIdle, 0x53E770); + RH_ScopedGlobalInstall(Idle, 0x53E920, { .locked = true }); // Must be hooked at all times otherwise game locks! + RH_ScopedGlobalInstall(FrontendIdle, 0x53E770, { .locked = true }); // Must be hooked at all times otherwise imgui stops working! } // 0x5BF3B0 @@ -51,6 +56,8 @@ void GameInit() { // 0x53E580 void InitialiseGame() { + ZoneScoped; + static int16& version_number = *(int16*)(0xB72C68); version_number = 78; @@ -85,6 +92,8 @@ void RwTerminate() { // 0x53E170 void RenderEffects() { + ZoneScoped; + CBirds::Render(); CSkidmarks::Render(); CRopes::Render(); @@ -118,6 +127,8 @@ void RenderEffects() { // 0x53DF40 void RenderScene() { + ZoneScoped; + const auto underWater = CWeather::UnderWaterness <= 0.0f; RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(NULL)); @@ -202,6 +213,8 @@ void RenderScene() { // 0x53E530 void RenderMenus() { + ZoneScoped; + if (FrontEndMenuManager.m_bMenuActive) { FrontEndMenuManager.DrawFrontEnd(); } @@ -209,13 +222,15 @@ void RenderMenus() { // 0x53E230 void Render2dStuff() { + ZoneScoped; + RenderDebugShit(); // NOTSA, temp const auto DrawOuterZoomBox = []() { CPed* player = FindPlayerPed(); eWeaponType weaponType = WEAPON_UNARMED; if (player) - weaponType = player->GetActiveWeapon().m_nType; + weaponType = player->GetActiveWeapon().m_Type; eCamMode camMode = CCamera::GetActiveCamera().m_nMode; bool firstPersonWeapon = false; if (camMode == MODE_SNIPER || camMode == MODE_SNIPER_RUNABOUT || camMode == MODE_ROCKETLAUNCHER || camMode == MODE_ROCKETLAUNCHER_RUNABOUT || camMode == MODE_CAMERA || @@ -275,6 +290,8 @@ void RenderDebugShit() { // 0x53E920 void Idle(void* param) { + ZoneScoped; + /* FPS lock. Limits to 26 frames per second. CTimer::GetCurrentTimeInCycles(); CTimer::GetCyclesPerMillisecond(); @@ -308,8 +325,10 @@ void Idle(void* param) { } if (!FrontEndMenuManager.m_bMenuActive && TheCamera.GetScreenFadeStatus() != eNameState::NAME_FADE_IN) { - CVector2D mousePos{SCREEN_WIDTH / 2.0f, SCREEN_HEIGHT / 2.0f}; - RsMouseSetPos(&mousePos); + if (!notsa::ui::UIRenderer::GetSingleton().GetImIO()->NavActive) { // If imgui nav is active don't center the cursor + FrontEndMenuManager.CentreMousePointer(); + } + CRenderer::ConstructRenderList(); CRenderer::PreRender(); CWorld::ProcessPedsAfterPreRender(); @@ -364,6 +383,8 @@ void Idle(void* param) { // 0x53E770 void FrontendIdle() { + ZoneScoped; + CDraw::CalculateAspectRatio(); CTimer::Update(); CSprite2d::SetRecipNearClip(); @@ -384,7 +405,7 @@ void FrontendIdle() { CameraSize(Scene.m_pRwCamera, nullptr, SCREEN_VIEW_WINDOW, SCREEN_ASPECT_RATIO); CVisibilityPlugins::SetRenderWareCamera(Scene.m_pRwCamera); - if (FastLoadSettings.ShouldLoadSaveGame()) { + if (g_FastLoaderConfig.ShouldLoadSaveGame()) { return; // Don't render anything } diff --git a/source/app/app_input.cpp b/source/app/app_input.cpp index f97bba891a..4d1c45ca1b 100644 --- a/source/app/app_input.cpp +++ b/source/app/app_input.cpp @@ -2,6 +2,7 @@ #include "app_input.h" #include "platform.h" +#include "ControllerConfigManager.h" void AppInputInjectHooks() { RH_ScopedCategory("App"); @@ -9,12 +10,15 @@ void AppInputInjectHooks() { RH_ScopedGlobalInstall(AttachInputDevices, 0x744A20); - RH_ScopedGlobalInstall(HandleKeyDown, 0x743DF0, {.reversed = false}); - RH_ScopedGlobalInstall(HandleKeyUp, 0x7443C0, {.reversed = false}); - RH_ScopedGlobalInstall(KeyboardHandler, 0x744880, {.reversed = false}); - RH_ScopedGlobalInstall(HandlePadButtonDown, 0x7448B0, {.reversed = false}); - RH_ScopedGlobalInstall(HandlePadButtonUp, 0x744930, {.reversed = false}); - RH_ScopedGlobalInstall(PadHandler, 0x7449F0, {.reversed = false}); + RH_ScopedGlobalInstall(HandleKeyDown, 0x743DF0); + RH_ScopedGlobalInstall(HandleKeyUp, 0x7443C0); + RH_ScopedGlobalInstall(KeyboardHandler, 0x744880); + + // Can't hook these, because argument is passed in eax (And I don't feel like dealing with that) + //RH_ScopedGlobalInstall(HandlePadButtonDown, 0x7448B0); + //RH_ScopedGlobalInstall(HandlePadButtonUp, 0x744930); + + RH_ScopedGlobalInstall(PadHandler, 0x7449F0); } // 0x744A20 @@ -24,22 +28,141 @@ bool AttachInputDevices() { return true; } +// Combined code from `0x743DF0` and `0x7443C0` +RsEventStatus HandleKeyEvent(bool isDown, RsKeyStatus* ks) { + // First handle keyboard keys + const auto pKBKeyState = [ks]() -> int16* { + auto& tks = CPad::TempKeyState; + switch (ks->keyScanCode) { + case rsNULL: return nullptr; + case rsF1: + case rsF2: + case rsF3: + case rsF4: + case rsF5: + case rsF6: + case rsF7: + case rsF8: + case rsF9: + case rsF10: + case rsF11: + case rsF12: return &tks.FKeys[ks->keyScanCode - rsF1]; + case rsESC: return &tks.esc; + case rsINS: return &tks.insert; + case rsDEL: return &tks.del; + case rsHOME: return &tks.home; + case rsEND: return &tks.end; + case rsPGUP: return &tks.pgup; + case rsPGDN: return &tks.pgdn; + case rsUP: return &tks.up; + case rsDOWN: return &tks.down; + case rsLEFT: return &tks.left; + case rsRIGHT: return &tks.right; + case rsDIVIDE: return &tks.div; + case rsTIMES: return &tks.mul; + case rsPLUS: return &tks.add; + case rsMINUS: return &tks.sub; + case rsPADDEL: return &tks.decimal; + case rsPADEND: return &tks.num1; + case rsPADDOWN: return &tks.num2; + case rsPADPGDN: return &tks.num3; + case rsPADLEFT: return &tks.num4; + case rsPAD5: return &tks.num5; + case rsNUMLOCK: return &tks.numlock; + case rsPADRIGHT: return &tks.num6; + case rsPADHOME: return &tks.num7; + case rsPADUP: return &tks.num8; + case rsPADPGUP: return &tks.num9; + case rsPADINS: return &tks.num0; + case rsPADENTER: return &tks.enter; + case rsSCROLL: return &tks.scroll; + case rsPAUSE: return &tks.pause; + case rsBACKSP: return &tks.back; + case rsTAB: return &tks.tab; + case rsCAPSLK: return &tks.capslock; + case rsENTER: return &tks.extenter; + case rsLSHIFT: return &tks.lshift; + case rsRSHIFT: return &tks.rshift; + case rsSHIFT: return &tks.shift; + case rsLCTRL: return &tks.lctrl; + case rsRCTRL: return &tks.rctrl; + case rsLALT: return &tks.lmenu; + case rsRALT: return &tks.rmenu; + case rsLWIN: return &tks.lwin; + case rsRWIN: return &tks.rwin; + case rsAPPS: return &tks.apps; + default: return (ks->keyScanCode < 255) ? &tks.standardKeys[ks->keyScanCode] : nullptr; + } + }(); + if (pKBKeyState) { + *pKBKeyState = isDown ? 255 : 0; + } + + // Then if a pad is plugged in, possibly handle that as well + if (CPad::padNumber) { + const auto [pPadBtnState, downValue] = [&]() -> std::pair { + auto& pctks = CPad::GetPad(1)->PCTempKeyState; // Why pad 1? + switch (ks->keyScanCode) { // TODO: Enums + case 68: return { &pctks.LeftStickX, 128 }; + case 65: return { &pctks.LeftStickX, -128 }; + + case 87: return { &pctks.LeftStickY, 128 }; + case 83: return { &pctks.LeftStickY, -128 }; + + case 74: return { &pctks.RightStickX, 128 }; + case 71: return { &pctks.RightStickX, -128 }; + + case 89: return { &pctks.RightStickY, 128 }; + case 72: return { &pctks.RightStickY, -128 }; + + case 90: return { &pctks.LeftShoulder1, 255 }; + case 88: return { &pctks.LeftShoulder2, 255 }; + + case 67: return { &pctks.RightShoulder1, 255 }; + case 86: return { &pctks.RightShoulder2, 255 }; + + case 79: return { &pctks.DPadUp, 255 }; + case 76: return { &pctks.DPadDown, 255 }; + case 75: return { &pctks.DPadLeft, 255 }; + case 59: return { &pctks.DPadRight, 255 }; + + case 66: return { &pctks.Start, 255 }; + case 78: return { &pctks.Select, 255 }; + + case 77: return { &pctks.ButtonSquare, 255 }; + case 44: return { &pctks.ButtonTriangle, 255 }; + case 46: return { &pctks.ButtonCross, 255 }; + case 47: return { &pctks.ButtonCircle, 255 }; + + case 1047: return { &pctks.ShockButtonL, 255 }; + case 1050: return { &pctks.ShockButtonR, 255 }; + } + return { NULL, NULL }; + }(); + if (pPadBtnState) { + *pPadBtnState = isDown ? downValue : 0; + } + } + + return rsEVENTPROCESSED; +} + // 0x743DF0 -RsEventStatus HandleKeyDown(RsKeyStatus* param) { - return plugin::CallAndReturn(param); +RsEventStatus HandleKeyDown(RsKeyStatus* ks) { + return HandleKeyEvent(true, ks); } // 0x7443C0 -RsEventStatus HandleKeyUp(RsKeyStatus* param) { - return plugin::CallAndReturn(param); +RsEventStatus HandleKeyUp(RsKeyStatus* ks) { + return HandleKeyEvent(false, ks); } // 0x744880 RsEventStatus KeyboardHandler(RsEvent event, void* param) { switch (event) { - case rsPADBUTTONDOWN: + case rsKEYDOWN: return HandleKeyDown((RsKeyStatus*)param); - case rsPADBUTTONUP: + case rsKEYUP: return HandleKeyUp((RsKeyStatus*)param); default: return rsEVENTNOTPROCESSED; @@ -48,12 +171,14 @@ RsEventStatus KeyboardHandler(RsEvent event, void* param) { // 0x7448B0 RsEventStatus HandlePadButtonDown(RsKeyStatus* param) { - return plugin::CallAndReturn(param); + ControlsManager.HandleJoyButtonUpDown(CPad::padNumber ? 1 : 0, true); + return rsEVENTPROCESSED; } // 0x744930 RsEventStatus HandlePadButtonUp(RsKeyStatus* param) { - return plugin::CallAndReturn(param); + ControlsManager.HandleJoyButtonUpDown(CPad::padNumber ? 1 : 0, false); + return rsEVENTPROCESSED; } // 0x7449F0 diff --git a/source/app/app_light.cpp b/source/app/app_light.cpp index d0e9370943..20c3680856 100644 --- a/source/app/app_light.cpp +++ b/source/app/app_light.cpp @@ -126,6 +126,8 @@ void LightsEnable(int32 enable) { // 0x7354E0 void SetLightsWithTimeOfDayColour(RpWorld* world) { + ZoneScoped; + assert(world); if (pAmbient) { diff --git a/source/app/platform/platform.cpp b/source/app/platform/platform.cpp index 65c7e0ff2c..156becf5cc 100644 --- a/source/app/platform/platform.cpp +++ b/source/app/platform/platform.cpp @@ -67,6 +67,8 @@ bool RsCameraBeginUpdate(RwCamera* camera) { // 0x619440 RwCamera* RsCameraShowRaster(RwCamera* camera) { + ZoneScoped; + return psCameraShowRaster(camera); } @@ -131,7 +133,7 @@ bool rsCommandLine(void* param) { // 0x619530 bool rsPreInitCommandLine(RwChar* arg) { if (strcmp(arg, RWSTRING("-vms")) == 0) { - DefaultVideoMode = FALSE; + DefaultVM = FALSE; return true; } return false; @@ -245,7 +247,7 @@ RwMemoryFunctions* psGetMemoryFunctions() { } // 0x619C90 -bool RsRwInitialize(void* param) { +bool RsRwInitialize(void* param) { // Win32: Param is HWND if (!RwEngineInit(psGetMemoryFunctions(), 0, rsRESOURCESDEFAULTARENASIZE)) return false; @@ -304,7 +306,7 @@ RsEventStatus RsEventHandler(RsEvent event, void* param) { case rsREGISTERIMAGELOADER: return rsEVENTPROCESSED; - case rsRWINITIALIZE: + case rsRWINITIALIZE: // Win32: Param is HWND return RSEVENT_SUCCEED(RsRwInitialize(param)); case rsRWTERMINATE: @@ -331,10 +333,12 @@ RsEventStatus RsEventHandler(RsEvent event, void* param) { return rsEVENTNOTPROCESSED; } -float IsWideScreenRatio(float ratio) { +// Returns true if ratio is 5:3, 16:9 or 16:10. +bool IsWideScreenRatio(float ratio) { return ratio == 0.6f || ratio == 10.0f / 16.0f || ratio == 9.0f / 16.0f; } -float IsFullScreenRatio(float ratio) { - return ratio == 3.0f / 4.0f || ratio == 0.8f; +// Returns true if ratio is 4:3 or 5:4. +bool IsFullScreenRatio(float ratio) { + return ratio == 3.0f / 4.0f || ratio == 4.0f / 5.0f; } diff --git a/source/app/platform/platform.h b/source/app/platform/platform.h index 61606cb4b7..7465bbdca3 100644 --- a/source/app/platform/platform.h +++ b/source/app/platform/platform.h @@ -1,7 +1,7 @@ #pragma once -float IsWideScreenRatio(float ratio); -float IsFullScreenRatio(float ratio); +bool IsWideScreenRatio(float ratio); +bool IsFullScreenRatio(float ratio); #define IS_WIDESCREEN_RATIO(ratio) IsWideScreenRatio(ratio) #define IS_FULLSCREEN_RATIO(ratio) IsFullScreenRatio(ratio) @@ -56,7 +56,7 @@ RwCamera* RsCameraShowRaster(RwCamera* camera); /** * Platform Specific */ -bool psInitialize(); +RwBool psInitialize(); void psTerminate(); void psWindowSetText(const char* str); diff --git a/source/app/platform/win/Input.cpp b/source/app/platform/win/Input.cpp index 3b221a8814..0aaa37f3b5 100644 --- a/source/app/platform/win/Input.cpp +++ b/source/app/platform/win/Input.cpp @@ -2,22 +2,12 @@ #include "Input.h" #include "ControllerConfigManager.h" -#include "win.h" +#include "Platform.h" #include "VideoMode.h" #pragma comment(lib, "dinput8.lib") #pragma comment(lib, "dxguid.lib") namespace WinInput { -void InjectHooks() { - RH_ScopedCategory("Win"); - RH_ScopedNamespaceName("Input"); - - //RH_ScopedGlobalInstall(Initialise, 0x7487CF, { .reversed = false }); - //RH_ScopedGlobalInstall(InitialiseMouse, 0x7469A0, { .reversed = false }); // Can't be hooked because it fails with ACCESS DENIED and crashes - //RH_ScopedGlobalInstall(InitialiseJoys, 0x7485C0, {.reversed = false}); - RH_ScopedGlobalInstall(EnumDevicesCallback, 0x747020); -} - // 0x746990 HRESULT CreateInput() { if (PSGLOBAL(diInterface)) { @@ -40,8 +30,8 @@ bool Initialise() { return false; } - InitialiseMouse(false); - InitialiseJoys(); + diMouseInit(false); + diPadInit(); return true; } @@ -57,8 +47,88 @@ HRESULT Shutdown() { return ReleaseInput(); } +// 0x746D80 +HRESULT diPadSetRanges(LPDIRECTINPUTDEVICE8 dev, DWORD padNum) { + if (dev == NULL) { + return S_OK; // Weird but okay + } + + enum { + NO_PROPERTY, // Device doesn't have this property + PROP_READ_ONLY, // Deivce does have this property, but it's read-only + SUCCESS // Device has property and we've set it successfully + }; + + const auto SetPropery = [&](DWORD prop) { + // Set ranges + DIDEVICEOBJECTINSTANCEA objinfo{ + sizeof(DIDEVICEOBJECTINSTANCEA) + }; + DIPROPRANGE range{ + .diph = DIPROPHEADER{ + .dwSize = sizeof(DIPROPRANGE), + .dwHeaderSize = sizeof(DIPROPHEADER), + .dwObj = prop, + .dwHow = DIPH_BYOFFSET, + }, + .lMin = -2000, + .lMax = 2000, + }; + if (FAILED(dev->GetObjectInfo(&objinfo, prop, DIPH_BYOFFSET))) { + return NO_PROPERTY; + } + if (FAILED(dev->SetProperty(DIPROP_RANGE, &range.diph))) { + return PROP_READ_ONLY; + } + return SUCCESS; + }; + + if (SetPropery(DIJOFS_X) == PROP_READ_ONLY) { + return S_FALSE; + } + + if (SetPropery(DIJOFS_Y) == PROP_READ_ONLY) { + return S_FALSE; + } + + const auto SetProperyAndSetFlag = [&](DWORD prop, bool& flag) { + const auto res = SetPropery(DIJOFS_Z); + if (res != NO_PROPERTY) { + flag = true; + } + return res; + }; + + if (SetProperyAndSetFlag(DIJOFS_Z, PadConfigs[padNum].zAxisPresent) == PROP_READ_ONLY) { + return S_FALSE; + } + + if (SetProperyAndSetFlag(DIJOFS_RZ, PadConfigs[padNum].rzAxisPresent) == PROP_READ_ONLY) { + return S_FALSE; + } + + return S_OK; +} + +//! [NOTSA - From 0x7485C0] - Set device config product/vendor id +void diPadSetPIDVID(LPDIRECTINPUTDEVICE8 dev, DWORD padNum) { + DIPROPDWORD vidpid{ + .diph = DIPROPHEADER{ + .dwSize = sizeof(DIPROPDWORD), + .dwHeaderSize = sizeof(DIPROPHEADER), + .dwObj = NULL, + .dwHow = DIPH_DEVICE, + } + }; + WIN_FCHECK(dev->GetProperty(DIPROP_VIDPID, &vidpid.diph)); + auto& cfg = PadConfigs[padNum]; + cfg.vendorId = LOWORD(vidpid.dwData); + cfg.productId = HIWORD(vidpid.dwData); + cfg.present = true; +} + // 0x7469A0 -void InitialiseMouse(bool exclusive) { +void diMouseInit(bool exclusive) { WIN_FCHECK(PSGLOBAL(diInterface)->CreateDevice(GUID_SysMouse, &PSGLOBAL(diMouse), 0)); WIN_FCHECK(PSGLOBAL(diMouse)->SetDataFormat(&c_dfDIMouse2)); WIN_FCHECK(PSGLOBAL(diMouse)->SetCooperativeLevel(PSGLOBAL(window), DISCL_FOREGROUND | (exclusive ? DISCL_EXCLUSIVE : DISCL_NONEXCLUSIVE))); @@ -66,8 +136,24 @@ void InitialiseMouse(bool exclusive) { } // 0x7485C0 -void InitialiseJoys() { - plugin::Call<0x7485C0>(); +void diPadInit() { + rng::fill(PadConfigs, CPadConfig{}); + + // Initialize devices (+ Set PSGLOBAL(diDeviceX) vars) + WIN_FCHECK(PSGLOBAL(diInterface)->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumDevicesCallback, NULL, DIEDFL_ALLDEVICES)); + + // Pirulax: Original code queried the capabilities [for pad 0] too, but did nothing with it, so I'll skip that. + + const auto InitializePad = [](LPDIRECTINPUTDEVICE8 dev, DWORD padNum) { + if (dev == NULL) { + return; + } + WIN_FCHECK(diPadSetRanges(dev, padNum)); + diPadSetPIDVID(dev, padNum); + PadConfigs[padNum].present = true; + }; + InitializePad(PSGLOBAL(diDevice1), 0); + InitializePad(PSGLOBAL(diDevice2), 1); } // 0x747020 @@ -105,39 +191,48 @@ BOOL CALLBACK EnumDevicesCallback(LPCDIDEVICEINSTANCEA inst, LPVOID) { } // 0x53F2D0 +// NOTSA(Grinch_): Directly returning CMouseControllerState CMouseControllerState GetMouseState() { - CMouseControllerState state; - - if (!PSGLOBAL(diMouse)) { - InitialiseMouse(!FrontEndMenuManager.m_bMenuActive && IsVideoModeExclusive()); + DIMOUSESTATE2 mouseState; + CMouseControllerState state; + + if (PSGLOBAL(diMouse)) { + if (SUCCEEDED(PSGLOBAL(diMouse)->GetDeviceState(sizeof(DIMOUSESTATE2), &mouseState))) { + state.X = static_cast(mouseState.lX); + state.Y = static_cast(mouseState.lY); + state.Z = static_cast(mouseState.lZ); + state.wheelUp = (mouseState.lZ > 0); + state.wheelDown = (mouseState.lZ < 0); + state.lmb = mouseState.rgbButtons[0] & 0x80; + state.rmb = mouseState.rgbButtons[1] & 0x80; + state.mmb = mouseState.rgbButtons[2] & 0x80; + state.bmx1 = mouseState.rgbButtons[3] & 0x80; + state.bmx2 = mouseState.rgbButtons[4] & 0x80; + } + } else { + diMouseInit(!FrontEndMenuManager.m_bMenuActive && IsVideoModeExclusive()); } - if (PSGLOBAL(diMouse)) { - DIDEVCAPS devCaps{ .dwSize = sizeof(DIDEVCAPS) }; - - PSGLOBAL(diMouse)->GetCapabilities(&devCaps); - switch (devCaps.dwButtons) { - case 3: - case 4: - case 5: - case 6: - case 7: - case 8: - state.mmb = true; - NOTSA_SWCFALLTHRU; - case 2: - state.rmb = true; - NOTSA_SWCFALLTHRU; - case 1: - state.lmb = true; - } - - if (devCaps.dwAxes == 3) { - state.wheelUp = state.wheelDown = true; - } - } - return state; } +void InjectHooks() { + RH_ScopedCategory("Win"); + RH_ScopedNamespaceName("Input"); + + //RH_ScopedGlobalInstall(Initialise, 0x7487CF, { .reversed = false }); + RH_ScopedGlobalInstall(diMouseInit, 0x7469A0); // Can't be hooked because it fails with ACCESS DENIED and crashes + //RH_ScopedGlobalInstall(InitialiseJoys, 0x7485C0, {.reversed = false}); + RH_ScopedGlobalInstall(EnumDevicesCallback, 0x747020); + RH_ScopedGlobalInstall(diPadInit, 0x7485C0); + RH_ScopedGlobalInstall(diPadSetRanges, 0x746D80); +} + +bool IsKeyPressed(unsigned int keyCode) { + return (GetKeyState(keyCode) & 0x8000) != 0; +} + +bool IsKeyDown(unsigned int keyCode) { + return GetAsyncKeyState(keyCode) >> 0x8; +} } // namespace WinInput diff --git a/source/app/platform/win/Input.h b/source/app/platform/win/Input.h index 594441e223..75c032160a 100644 --- a/source/app/platform/win/Input.h +++ b/source/app/platform/win/Input.h @@ -5,13 +5,18 @@ namespace WinInput { +void diMouseInit(bool exclusive); +void diPadInit(); + +BOOL CALLBACK EnumDevicesCallback(LPCDIDEVICEINSTANCEA pInst, LPVOID); +CMouseControllerState GetMouseState(); + void InjectHooks(); bool Initialise(); -HRESULT Shutdown(); -void InitialiseMouse(bool exclusive); -void InitialiseJoys(); -BOOL CALLBACK EnumDevicesCallback(LPCDIDEVICEINSTANCEA pInst, LPVOID); -CMouseControllerState GetMouseState(); +bool IsKeyDown(unsigned int keyCode); +bool IsKeyPressed(unsigned int keyCode); + +HRESULT Shutdown(); }; // namespace WinInput diff --git a/source/app/platform/win/Platform.cpp b/source/app/platform/win/Platform.cpp new file mode 100644 index 0000000000..6615e6f8a9 --- /dev/null +++ b/source/app/platform/win/Platform.cpp @@ -0,0 +1,33 @@ +/* +* This file contains ps* function implementations for Windows +*/ + +#include "StdInc.h" + +#include "WndProc.h" +#include "WinMain.h" +#include "Input.h" + +#include "Gamma.h" + +// @notsa +// @brief Resets the screen gamma if ever changed. +void ResetGammaWhenExiting() { + if (gbGammaChanged) { + if (auto d3dDevice = (IDirect3DDevice9*)RwD3D9GetCurrentD3DDevice()) { + d3dDevice->SetGammaRamp(0u, D3DSGR_CALIBRATE, &savedGamma); + } + gbGammaChanged = false; + } +} + +void WinPsInjectHooks(); +void Win32InjectHooks() { + RH_ScopedCategory("Win"); + RH_ScopedNamespaceName("Win"); + + InjectHooksWndProcStuff(); + InjectWinMainStuff(); + WinPsInjectHooks(); + WinInput::InjectHooks(); +} diff --git a/source/app/platform/win/Platform.h b/source/app/platform/win/Platform.h new file mode 100644 index 0000000000..96ac8ca357 --- /dev/null +++ b/source/app/platform/win/Platform.h @@ -0,0 +1,48 @@ +#pragma once + +#define APP_CLASS "Grand theft auto San Andreas" + +//! Win32 fail check (returns from function automagically) +#define WIN_FCHECK(x) do { \ + if (HRESULT hr = (x); FAILED(hr)) { \ + DEV_LOG(TEXT("FAILED(hr=0x{:x}) in ") TEXT(#x) TEXT("\n"), (size_t)(hr)); \ + return; \ + } \ + } while (0) \ + +// Custom enum, generated by ChatGPT ~~thank you~~ - ChatGPT was wrong. +// +enum class WinVer { + WIN_95, //< Windows 95 + WIN_98, //< Windows 98 + WIN_NT, //< Windows NT + WIN_2000_XP_2003, //< Windows 2000/XP/Server 2003 + WIN_VISTA_OR_LATER, //< Windows Vista/7/8/10 + + UNKNOWN +}; +static auto& s_WinVer = StaticRef(); + +struct OSStatus { + WinVer OSVer; + DWORD DxVer; // Always 0x900 + struct { // TODO: Just use MEMORYSTATUS stuct here + SIZE_T TotalPhys; + SIZE_T AvailPhys; + SIZE_T TotalVirtual; + SIZE_T AvailVirtual; + } RAM; + struct { + SIZE_T Total; + SIZE_T Avail; + } VRAM; +}; +inline auto& s_OSStatus = StaticRef(); + +inline bool& anisotropySupportedByGFX = *(bool*)0xC87FFC; +inline bool& isForeground = *(bool*)0xC920EC; +inline bool& Windowed = *(bool*)0xC920CC; + +void Win32InjectHooks(); + +#define IDI_MAIN_ICON 1042 diff --git a/source/app/platform/win/VideoMode.cpp b/source/app/platform/win/VideoMode.cpp index 3acd3472f5..ee16cdfa30 100644 --- a/source/app/platform/win/VideoMode.cpp +++ b/source/app/platform/win/VideoMode.cpp @@ -27,20 +27,30 @@ char** GetVideoModeList() { } gCurrentGpu = RwEngineGetCurrentSubSystem(); - uint32 numVidModes = RwEngineGetNumVideoModes(); + const auto numVidModes = RwEngineGetNumVideoModes(); assert(numVidModes != -1 && "Failed to get Video Modes"); gVideoModes = (char**)CMemoryMgr::Calloc(numVidModes, sizeof(char*)); - RwVideoMode videoMode{}; - for (auto modeId = 0u; modeId < numVidModes; modeId++) { - VERIFY(RwEngineGetVideoModeInfo(&videoMode, modeId)); + for (auto modeId = 0; modeId < numVidModes; modeId++) { + const auto videoMode = RwEngineGetVideoModeInfo(modeId); gVideoModes[modeId] = nullptr; + if ((videoMode.flags & rwVIDEOMODEEXCLUSIVE) == 0) { + if (notsa::IsFixBugs()) { + gVideoModes[modeId] = (char*)CMemoryMgr::Calloc(100, sizeof(char)); + sprintf_s(gVideoModes[modeId], 100 * sizeof(char), "WINDOWED"); + +#ifdef VIDEO_MODE_LOGS + DEV_LOG("Available video mode id={:02d}: {}", modeId, gVideoModes[modeId]); +#endif + } else { + // Ignore windowed mode. #ifdef VIDEO_MODE_LOGS - DEV_LOG("Unavailable video mode id={:02d}: {} X {} X {} [reason: video mode not exclusive]", modeId, videoMode.width, videoMode.height, videoMode.depth); + DEV_LOG("Unavailable video mode id={:02d}: {} X {} X {} [reason: video mode not exclusive]", modeId, videoMode.width, videoMode.height, videoMode.depth); #endif + } continue; } @@ -51,22 +61,27 @@ char** GetVideoModeList() { continue; } - float fRatio = float(videoMode.height) / float(videoMode.width); - if (!IS_FULLSCREEN_RATIO(fRatio) && !IS_WIDESCREEN_RATIO(fRatio)) { + const auto aspectRatio = float(videoMode.height) / float(videoMode.width); + if (!IsFullScreenRatio(aspectRatio) && !IsWideScreenRatio(aspectRatio)) { #ifdef VIDEO_MODE_LOGS - DEV_LOG("Unavailable video mode id={:02d}: {} X {} X {} [reason: ratio {:0.2f}]", modeId, videoMode.width, videoMode.height, videoMode.depth, fRatio); + const auto gcd = std::gcd(videoMode.width, videoMode.height); + const auto ratioW = videoMode.width / gcd, ratioH = videoMode.height / gcd; + DEV_LOG("Unavailable video mode id={:02d}: {} X {} X {} [reason: ratio {}:{}]", modeId, videoMode.width, videoMode.height, videoMode.depth, ratioW, ratioH); #endif continue; } if (videoMode.width != APP_MINIMAL_WIDTH || videoMode.height != APP_MINIMAL_HEIGHT) { - if (gnMemTotalVideo - videoMode.height * videoMode.width * videoMode.depth / 8 <= GAME_FREE_VIDEO_MEM_REQUIRED) { + if (s_OSStatus.VRAM.Avail - videoMode.height * videoMode.width * videoMode.depth / 8 <= GAME_FREE_VIDEO_MEM_REQUIRED) { +#ifdef VIDEO_MODE_LOGS + DEV_LOG("Unavailable video mode id={:02d}: {} X {} X {} [reason: out of vram]", modeId, videoMode.width, videoMode.height, videoMode.depth); +#endif continue; } } - gVideoModes[modeId] = (char*)CMemoryMgr::Calloc(100, sizeof(char)); // 100 chars - sprintf_s(gVideoModes[modeId], 100 * sizeof(char), "%lu X %lu X %lu", videoMode.width, videoMode.height, videoMode.depth); // rwsprintf + gVideoModes[modeId] = (char*)CMemoryMgr::Calloc(100, sizeof(char)); + sprintf_s(gVideoModes[modeId], 100 * sizeof(char), "%lu X %lu X %lu", videoMode.width, videoMode.height, videoMode.depth); #ifdef VIDEO_MODE_LOGS DEV_LOG("Available video mode id={:02d}: {}", modeId, gVideoModes[modeId]); @@ -105,7 +120,5 @@ void SetVideoMode(int32 mode) { // 0x745CA0 bool IsVideoModeExclusive() { // AKA isCurrentModeFullscreen - RwVideoMode videoMode{}; - VERIFY(RwEngineGetVideoModeInfo(&videoMode, gCurrentVideoMode)); - return videoMode.flags & rwVIDEOMODEEXCLUSIVE; + return RwEngineGetVideoModeInfo(gCurrentVideoMode).flags & rwVIDEOMODEEXCLUSIVE; } diff --git a/source/app/platform/win/VideoMode.h b/source/app/platform/win/VideoMode.h index ecf437decb..745fc986fa 100644 --- a/source/app/platform/win/VideoMode.h +++ b/source/app/platform/win/VideoMode.h @@ -1,15 +1,50 @@ #pragma once -static inline bool DefaultVideoMode = *(bool*)0x8D2E34; -static inline bool MultipleVideoModes = *(bool*)0xC92118; -static inline bool VideoModeNotSelected = *(bool*)0x8D6218; +#include "Platform.h" +#include +#include + +// +// Sub Systems +// + +//! Maximum number of subsystems we can deal with +//! 1 SubSystem / Display (And maybe / GPU ?) +constexpr auto MAX_SUBSYSTEMS = 16; + +//! Subsystem infos (Populated using `RwEngineGetSubSystemInfo`) +static inline auto& GsubSysInfo = StaticRef, 0xC8CFC0>(); + +//! Number of subsystems +static inline auto& GnumSubSystems = StaticRef(); + +//! Currently selected subsystem +static inline auto& GcurSelSS = StaticRef(); + +//! Whenever there are multiple subsystems available +static inline auto& MultipleSubSystems = StaticRef(); + +// +// Video Mode +// + +//! Currently selected videomode +static inline auto& GcurSelVM = StaticRef(); // VM = Video Mode + +//! Whenever to use the default videomode (Instead of the user selecting it) +static inline auto& UseDefaultVM = StaticRef(); + +//! Unused shit +static inline auto& DefaultVM = StaticRef(); + +//! Whenever FrontEndMemnuManager videomode stuff was **NOT** yet set (See WinMain) +static inline auto& IsVMNotSelected = StaticRef(); /* * Dynamic array of video modes with format "width x height x depth" */ static inline char**& gVideoModes = *(char***)0xC920D0; static inline uint32& gCurrentGpu = *(uint32*)0x8D6248; -static inline uint32 gnMemTotalVideo = *(uint32*)(0xC8CF68 + 0x18); // todo: fix static inline int32& gCurrentVideoMode = *(int32*)0x8D6220; void VideoModeInjectHooks(); diff --git a/source/app/platform/win/VideoModeSelectDialog.cpp b/source/app/platform/win/VideoModeSelectDialog.cpp new file mode 100644 index 0000000000..0c30060ee7 --- /dev/null +++ b/source/app/platform/win/VideoModeSelectDialog.cpp @@ -0,0 +1,185 @@ +#include "StdInc.h" + +#include "VideoModeSelectDialog.h" +#include "VideoMode.h" +#include "platform/platform.h" + +// +// TODO: +// For the final exe we have to add win.rc (A resource file that contains the definition of this dialogbox) +// It will also generate a resource.h that will have these defines in it +// + +//! Resource ID of the videomode select dialog +#define IDD_DIALOG1 104 + +//! Device select combobox item id +#define IDC_DEVICESEL 1000 + +//! Video mode select combobox item id +#define IDC_VIDMODE 1001 + +//! Exit button item id +#define IDEXIT 1002 + +// +// Helper functions +// + +RwInt32 GetCBCurSel(HWND hComboBox) { + return SendMessage(hComboBox, CB_GETCURSEL, NULL, NULL); +} + +void SetCBCurSel(HWND hComboBox, RwInt32 entryIdx) { + SendMessage(hComboBox, CB_SETCURSEL, entryIdx, NULL); +} + +RwInt32 GetCBItemData(HWND hComboBox, RwInt32 entryIdx) { + return SendMessage(hComboBox, CB_GETITEMDATA, entryIdx, NULL); +} + +RwInt32 GetCBCurSelData(HWND hComboBox) { + return GetCBItemData(hComboBox, GetCBCurSel(hComboBox)); +} + +void SetSelectedVM(HWND hDlg, RwInt32 vm) { + GcurSelVM = vm; + SetCBCurSel(GetDlgItem(hDlg, IDC_VIDMODE), vm); +} + +// 0x745920 - Fill in the available VMs to the dialog box vm select item +void FillAvailableVMs(HWND hVMSel) { + const auto numVM = RwEngineGetNumVideoModes(); + for (auto i = 0; i < numVM; i++) { + const auto vmi = RwEngineGetVideoModeInfo(i); + static char vmName[1024]; + + if (notsa::IsFixBugs() && (vmi.flags & rwVIDEOMODEEXCLUSIVE) == 0) { + *std::format_to(vmName, "WINDOWED") = 0; + const auto idx = SendMessage(hVMSel, CB_ADDSTRING, NULL, (LPARAM)vmName); // Add entry, and get it's index + SendMessage(hVMSel, CB_SETITEMDATA, idx, i); // Set index of that entry to correspond to `i` + continue; + } + + if ((vmi.flags & rwVIDEOMODEEXCLUSIVE) == 0 || vmi.width < APP_MINIMAL_WIDTH || vmi.height < APP_MINIMAL_HEIGHT) { + continue; + } + + const auto aspectr = (float)vmi.height / (float)vmi.width; + // printf("%f\n", aspectr) // annoying printf that spams the console was here + + const auto gcd = std::gcd(vmi.width, vmi.height); + const auto ratioW = vmi.width / gcd, ratioH = vmi.height / gcd; + + // NOTSA: Provide aspect ratio info (W:H) instead of 'FULLSCREEN' and 'WIDESCREEN'. + if (IsFullScreenRatio(aspectr) || IsWideScreenRatio(aspectr)) { + *std::format_to(vmName, "{} x {} x {} ({}:{})", vmi.width, vmi.height, vmi.depth, ratioW, ratioH) = 0; + const auto idx = SendMessage(hVMSel, CB_ADDSTRING, NULL, (LPARAM)vmName); // Add entry, and get it's index + SendMessage(hVMSel, CB_SETITEMDATA, idx, i); // Set index of that entry to correspond to `i` + } else { + DEV_LOG("Not listing video mode ({}) to device select! [{} x {} ({}:{})]", i, vmi.width, vmi.height, ratioW, ratioH); + } + } +} + +// Fill in the available devices (subystems) +void FillAvailableDevices(HWND hDevSel) { + // Fill in the available subystems + for (auto ss = GnumSubSystems; ss-- > 0;) { + SendMessage(hDevSel, CB_ADDSTRING, NULL, (LPARAM)(&GsubSysInfo[ss])); + SendMessage(hDevSel, CB_SETITEMDATA, (WPARAM)ss, (LPARAM)ss); + } + + // Set current SS as the selected one + SendMessage(hDevSel, CB_SETCURSEL, GcurSelSS, NULL); +} + +void ComboBoxClear(HWND hComboBox) { + SendMessage(hComboBox, CB_RESETCONTENT, NULL, NULL); +} + +// 0x745CC0 - Initialize the dialogbox +INT_PTR InitDialog(HWND hDlg) { + const auto hDevSel = GetDlgItem(hDlg, IDC_DEVICESEL), + hVMSel = GetDlgItem(hDlg, IDC_VIDMODE); + + FillAvailableDevices(hDevSel); + FillAvailableVMs(hVMSel); + + // Figure out currently selected videomode + const auto numVM = RwEngineGetNumVideoModes(); + const auto lastUsedVM = FrontEndMenuManager.m_nDisplayVideoMode; + if (GcurSelVM == -1 && lastUsedVM < numVM && GetVideoModeList()[lastUsedVM]) { + for (auto i = 0; i < numVM; i++) { + if (GetCBItemData(hVMSel, i) == FrontEndMenuManager.m_nDisplayVideoMode) { + SetSelectedVM(hDlg, i); + return NULL; + } + } + + // Failed + SetSelectedVM(hDlg, -1); + } else { + SetSelectedVM(hDlg, RwEngineGetCurrentVideoMode()); + } + + return NULL; +} + +// 0x745E50 +INT_PTR CALLBACK DialogFunc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam) { + switch (Msg) { + case WM_INITDIALOG: + return InitDialog(hDlg); + + case WM_COMMAND: { + const auto hDevSel = GetDlgItem(hDlg, IDC_DEVICESEL), + hVMSel = GetDlgItem(hDlg, IDC_VIDMODE); + + switch (LOWORD(wParam)) { + case IDEXIT: { + if (HIWORD(wParam) == 0) { + EndDialog(hDlg, NULL); + } + return TRUE; + } + case IDC_VIDMODE: { + if (HIWORD(wParam) == 1) { + GcurSelVM = GetCBCurSelData(hVMSel); + } + return TRUE; + } + case IDOK: { + if (HIWORD(wParam) == 0) { + GcurSelVM = GetCBCurSelData(hVMSel); + EndDialog(hDlg, TRUE); + } + return TRUE; + } + case IDC_DEVICESEL: { // Function from 0x745DC0 + const auto hDevSel = GetDlgItem(hDlg, IDC_DEVICESEL), + hVMSel = GetDlgItem(hDlg, IDC_VIDMODE); + + const auto currEntry = GetCBCurSel(hDevSel); + if (currEntry != GcurSelSS) { + GcurSelSS = GetCBItemData(hDevSel, currEntry); + + VERIFY(RwEngineSetSubSystem(GcurSelSS)); + + // Deal with VM select ComboBox + ComboBoxClear(hVMSel); + FillAvailableVMs(hVMSel); + SetSelectedVM(hDlg, RwEngineGetCurrentVideoMode()); + } + return TRUE; + } + break; + } + } + } + return FALSE; +} + +LRESULT CreateVidModeSelectDialog(HINSTANCE hInst, HWND hWnd) { + return DialogBoxParam(hInst, MAKEINTRESOURCEA(IDD_DIALOG1), hWnd, DialogFunc, 0); +} diff --git a/source/app/platform/win/VideoModeSelectDialog.h b/source/app/platform/win/VideoModeSelectDialog.h new file mode 100644 index 0000000000..13fd57af57 --- /dev/null +++ b/source/app/platform/win/VideoModeSelectDialog.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +LRESULT CreateVidModeSelectDialog(HINSTANCE hInst, HWND hWnd); diff --git a/source/app/platform/win/WinMain.cpp b/source/app/platform/win/WinMain.cpp new file mode 100644 index 0000000000..a189be10fb --- /dev/null +++ b/source/app/platform/win/WinMain.cpp @@ -0,0 +1,441 @@ +/* +* This file contains WinMain and related functions +*/ + +#include "StdInc.h" + +#include "LoadingScreen.h" +#include "ControllerConfigManager.h" +#include "Gamma.h" + +#include "VideoPlayer.h" +#include "VideoMode.h" +#include "Input.h" +#include "Platform.h" +#include "WndProc.h" + +#include "extensions/Configs/FastLoader.hpp" +#include "extensions/Configs/WindowedMode.hpp" + +constexpr auto NO_FOREGROUND_PAUSE = true; + +// 0x747300 +char* getDvdGamePath() { + return plugin::CallAndReturn(); +} + +// 0x746870 +void MessageLoop() { + MSG msg; + while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE | PM_NOYIELD)) { + if (msg.message == WM_QUIT) { + RsGlobal.quit = true; + } else { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + } +} + +// 0x7486A0 +bool InitApplication(HINSTANCE hInstance) { + WNDCLASS windowClass = { 0 }; + windowClass.style = CS_BYTEALIGNWINDOW; + windowClass.lpfnWndProc = __MainWndProc; + windowClass.hInstance = hInstance; + windowClass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MAIN_ICON)); + windowClass.hCursor = LoadCursor(nullptr, IDC_ARROW); + windowClass.lpszClassName = APP_CLASS; + return RegisterClass(&windowClass); +} + +// 0x745560 +HWND InitInstance(HINSTANCE hInstance) { + // NOTSA/HACK: With this hack we can set a specific value for + // windowed mode. Also this is so stupid it needs to be changed + // after 'window-stretch-freeze' bug fixed. + RsGlobal.maximumWidth = g_WindowedModeConfig.WindowWidth; + RsGlobal.maximumHeight = g_WindowedModeConfig.WindowHeight; + + RECT winRt; + GetClientRect(GetDesktopWindow(), &winRt); + + RECT rect = { 0, 0, RsGlobal.maximumWidth, RsGlobal.maximumHeight }; + AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false); + const auto width = rect.right - rect.left, height = rect.bottom - rect.top; + + return CreateWindowEx( + 0, + APP_CLASS, + RsGlobal.appName, + WS_OVERLAPPEDWINDOW, + (g_WindowedModeConfig.Centered) ? (winRt.right - winRt.left - width) / 2 : CW_USEDEFAULT, + (g_WindowedModeConfig.Centered) ? (winRt.bottom - winRt.top - height) / 2 : CW_USEDEFAULT, + width, + height, + nullptr, + nullptr, + hInstance, + nullptr + ); +} + +// 0x7468E0 +bool IsAlreadyRunning() { + CreateEvent(nullptr, false, true, APP_CLASS); + if (GetLastError() != ERROR_ALREADY_EXISTS) { + return false; + } + + const auto fgwnd = FindWindow(APP_CLASS, RsGlobal.appName); + SetForegroundWindow(fgwnd != INVALID_HANDLE_VALUE ? fgwnd : PSGLOBAL(window)); + + return true; +}; + +// 0x746060 +bool IsForegroundApp() { + return ForegroundApp; +} + +// 0x746480 +char** CommandLineToArgv(char* cmdLine, int* argCount) { + return plugin::CallAndReturn(cmdLine, argCount); +} + +// Code from winmain, 0x748DCF +bool ProcessGameLogic(INT nCmdShow, MSG& Msg) { + if (RsGlobal.quit || FrontEndMenuManager.m_bStartGameLoading) { + return false; + } + + // Process Window messages + if (PeekMessage(&Msg, NULL, NULL, NULL, PM_REMOVE | PM_NOYIELD)) { + if (Msg.message == WM_QUIT) { + return false; + } + TranslateMessage(&Msg); + DispatchMessage(&Msg); + return true; + } + + // Game is in background + if (!NO_FOREGROUND_PAUSE && !ForegroundApp) { + if (isForeground) { + isForeground = false; + } + Sleep(100); + return true; + } + + FrameMark; + + // TODO: Move this out from here (It's not platform specific at all) + switch (gGameState) { + case GAME_STATE_INITIAL: { + const auto ProcessSplash = [](bool isNVidia) { + CLoadingScreen::LoadSplashes(true, isNVidia); + CLoadingScreen::Init(true, true); + CLoadingScreen::DoPCTitleFadeOut(); + CLoadingScreen::DoPCTitleFadeIn(); + CLoadingScreen::Shutdown(); + }; + if (!g_FastLoaderConfig.NoEAX) { + ProcessSplash(false); + } + if (!g_FastLoaderConfig.NoNVidia) { + ProcessSplash(true); + } + ChangeGameStateTo(GAME_STATE_LOGO); + break; + } + case GAME_STATE_LOGO: { + if (!g_FastLoaderConfig.NoLogo) { + if (!Windowed) { + VideoPlayer::Play(nCmdShow, "movies\\Logo.mpg"); + } + } + ChangeGameStateTo(g_FastLoaderConfig.NoLogo ? GAME_STATE_TITLE : GAME_STATE_PLAYING_LOGO); + break; + } + case GAME_STATE_PLAYING_LOGO: + case GAME_STATE_PLAYING_INTRO: { // 0x748B17 + CPad::UpdatePads(); + auto* pad = CPad::GetPad(); + if ( Windowed + || ControlsManager.GetJoyButtonJustDown() + || pad->NewState.CheckForInput() + || CPad::IsMouseLButtonPressed() + || CPad::IsEnterJustPressed() + || pad->IsStandardKeyJustPressed(VK_SPACE) + || CPad::IsMenuKeyJustPressed() + || CPad::IsTabJustPressed() + ) { + ChangeGameStateTo([] { + switch (gGameState) { + case GAME_STATE_PLAYING_LOGO: return GAME_STATE_TITLE; + case GAME_STATE_PLAYING_INTRO: return GAME_STATE_FRONTEND_LOADING; + default: NOTSA_UNREACHABLE(); + } + }()); + } + break; + } + case GAME_STATE_TITLE: { + if (!g_FastLoaderConfig.NoTitleOrIntro) { + VideoPlayer::Shutdown(); + VideoPlayer::Play(nCmdShow, FrontEndMenuManager.GetMovieFileName()); + } + ChangeGameStateTo(g_FastLoaderConfig.NoTitleOrIntro ? GAME_STATE_FRONTEND_LOADING : GAME_STATE_PLAYING_INTRO); + break; + } + case GAME_STATE_FRONTEND_LOADING: { + VideoPlayer::Shutdown(); + CLoadingScreen::Init(true, false); + if (!g_FastLoaderConfig.NoCopyright) { + CLoadingScreen::DoPCTitleFadeOut(); + } + if (!CGame::InitialiseEssentialsAfterRW()) { + RsGlobal.quit = true; + } + CGame::InitialiseCoreDataAfterRW(); + ChangeGameStateTo(GAME_STATE_FRONTEND_LOADED); + anisotropySupportedByGFX = (RwD3D9GetCaps()->RasterCaps & D3DPRASTERCAPS_ANISOTROPY) != 0; // todo: func + break; + } + case GAME_STATE_FRONTEND_LOADED: { + FrontEndMenuManager.m_bActivateMenuNextFrame = true; + FrontEndMenuManager.m_bMainMenuSwitch = true; + if (IsVMNotSelected) { + FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode = gCurrentVideoMode; + IsVMNotSelected = false; + } + ChangeGameStateTo(GAME_STATE_FRONTEND_IDLE); + if (g_FastLoaderConfig.NoCopyright) { + CLoadingScreen::SkipCopyrightSplash(); + } else { + CLoadingScreen::DoPCTitleFadeIn(); + } + break; + } + case GAME_STATE_FRONTEND_IDLE: { // 0x748CB2 + WINDOWPLACEMENT wndpl{ .length = sizeof(WINDOWPLACEMENT) }; + VERIFY(GetWindowPlacement(PSGLOBAL(window), &wndpl)); + if (g_FastLoaderConfig.ShouldLoadSaveGame()) { + RsEventHandler(rsFRONTENDIDLE, nullptr); // We need to still run the frontend processing once because it has some important stuff + if ((GetAsyncKeyState(g_FastLoaderConfig.SkipSaveGameLoadKey) & 0xF000) == 0) { + g_FastLoaderConfig.StartGame(g_FastLoaderConfig.SaveGameToLoad); // Load game + } + g_FastLoaderConfig.TriedLoadingSaveGame = true; + } else if (g_FastLoaderConfig.RenderAtAllTimes || wndpl.showCmd != SW_SHOWMINIMIZED) { + RsEventHandler(rsFRONTENDIDLE, nullptr); + } + if (FrontEndMenuManager.m_bMenuActive && !FrontEndMenuManager.m_bLoadingData) { + break; + } + ChangeGameStateTo(GAME_STATE_LOADING_STARTED); + if (!FrontEndMenuManager.m_bLoadingData) { + break; + } + NOTSA_SWCFALLTHRU; // Fall down and start loading + } + case GAME_STATE_LOADING_STARTED: { + if (!g_FastLoaderConfig.NoLoadingTune) { + AudioEngine.StartLoadingTune(); + } + + InitialiseGame(); + ChangeGameStateTo(GAME_STATE_IDLE); + FrontEndMenuManager.m_bMainMenuSwitch = false; + + AudioEngine.InitialisePostLoading(); + break; + } + case GAME_STATE_IDLE: { + if (!RwInitialized) + break; + + auto v9_1 = 1000.0f / (float)RsGlobal.frameLimit; + auto v9_2 = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); + if (!FrontEndMenuManager.m_bPrefsFrameLimiter && CReplay::Mode != eReplayMode::MODE_PLAYBACK && !AudioEngine.IsBeatInfoPresent() || v9_1 < v9_2) { + RsEventHandler(rsIDLE, (void*)true); + } + break; + } + } + + if (!isForeground) { + isForeground = true; + } + + return true; +} + +// Code from winmain, 0x7489FB +void MainLoop(INT nCmdShow, MSG& Msg) { + bool bNewGameFirstTime = false; + while (true) { + RwInitialized = true; + + // Useless, and annoying + //RwV2d pos{ SCREEN_WIDTH / 2.0f, SCREEN_HEIGHT / 2.0f }; + //RsMouseSetPos(&pos); + + gamma.Init(); + + // Game logic main loop + while (ProcessGameLogic(nCmdShow, Msg)); + + // 0x748DDA + RwInitialized = false; + FrontEndMenuManager.UnloadTextures(); + if (!FrontEndMenuManager.m_bStartGameLoading) { + break; + } + + // load game + CCheat::ResetCheats(); + CTimer::Stop(); + + if (FrontEndMenuManager.m_bLoadingData) { + CGame::ShutDownForRestart(); + CGame::InitialiseWhenRestarting(); + FrontEndMenuManager.m_bLoadingData = false; + } else if (bNewGameFirstTime) { + CTimer::Stop(); + ChangeGameStateTo( + FrontEndMenuManager.m_nGameState != 1 + ? GAME_STATE_LOADING_STARTED + : GAME_STATE_FRONTEND_LOADED + ); + } else { + CCheat::ResetCheats(); + CGame::ShutDownForRestart(); + CTimer::Stop(); + CGame::InitialiseWhenRestarting(); + } + + bNewGameFirstTime = false; + FrontEndMenuManager.m_nGameState = 0; + FrontEndMenuManager.m_bStartGameLoading = false; + } + +} + +// 0x748710 +INT WINAPI NOTSA_WinMain(HINSTANCE instance, HINSTANCE hPrevInstance, LPSTR cmdLine, INT nCmdShow) { + SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0u, nullptr, 2); + if (IsAlreadyRunning()) { + return false; + } + + auto initializeEvent = RsEventHandler(rsINITIALIZE, nullptr); + if (rsEVENTERROR == initializeEvent) { + return false; + } + + if (!InitApplication(instance)) { + return false; + } + + cmdLine = GetCommandLine(); + int argc; + char** argv = CommandLineToArgv(cmdLine, &argc); + for (int i = 0; i < argc; i++) { + RsEventHandler(rsPREINITCOMMANDLINE, argv[i]); + } + + PSGLOBAL(window) = InitInstance(instance); + if (!PSGLOBAL(window)) { + return false; + } + PSGLOBAL(instance) = instance; + + // 0x7487CF + VERIFY(WinInput::Initialise()); + + ControlsManager.InitDefaultControlConfigMouse(WinInput::GetMouseState(), !FrontEndMenuManager.m_nController); + + // 0x748847 + if (RsEventHandler(rsRWINITIALIZE, PSGLOBAL(window)) == rsEVENTERROR) { + DestroyWindow(PSGLOBAL(window)); + RsEventHandler(rsTERMINATE, nullptr); + return false; + } + + // 0x7488EE + for (auto i = 0; i < argc; i++) { + RsEventHandler(rsCOMMANDLINE, argv[i]); + } + + if (MultipleSubSystems || PSGLOBAL(fullScreen)) { + SetWindowLongPtr(PSGLOBAL(window), GWL_STYLE, (LONG_PTR)WS_POPUP); + SetWindowPos(PSGLOBAL(window), nullptr, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); + } + + RwRect rect{ 0, 0, RsGlobal.maximumWidth, RsGlobal.maximumHeight }; + RsEventHandler(rsCAMERASIZE, &rect); + + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 0u, nullptr, 2u); + SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 0u, nullptr, 2u); + SystemParametersInfo(SPI_SETLOWPOWERACTIVE, 0u, nullptr, 2u); + STICKYKEYS pvParam { .cbSize = sizeof(STICKYKEYS) }; + SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS), &pvParam, 2u); + STICKYKEYS pvParam1 = { .cbSize = sizeof(STICKYKEYS), .dwFlags = SKF_TWOKEYSOFF }; + SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &pvParam1, 2u); + + ShowWindow(PSGLOBAL(window), nCmdShow); + UpdateWindow(PSGLOBAL(window)); + + // 0x748995 + CFileMgr::SetDirMyDocuments(); + if (auto* file = CFileMgr::OpenFile("gta_sa.set", "rb")) { + if (!ControlsManager.LoadSettings(file)) { + ControlsManager.ReinitControls(); + } + CFileMgr::CloseFile(file); + } + CFileMgr::SetDir(""); + + SetErrorMode(SEM_FAILCRITICALERRORS); + + // 0x7489FB + MSG Msg; + MainLoop(nCmdShow, Msg); + + // if game is loaded, shut it down + if (gGameState == GAME_STATE_IDLE) { + CGame::Shutdown(); + } + + // now quit 0x748E75 + AudioEngine.Shutdown(); + FreeVideoModeList(); + RsEventHandler(rsRWTERMINATE, nullptr); + DestroyWindow(PSGLOBAL(window)); + RsEventHandler(rsTERMINATE, nullptr); + free(argv); + ShowCursor(true); + + SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &pvParam, 2u); + SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 1u, nullptr, 2u); // TODO: GUID_VIDEO_POWERDOWN_TIMEOUT + SystemParametersInfo(SPI_SETLOWPOWERACTIVE, 1u, nullptr, 2u); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 1u, nullptr, 2u); + // nullsub_0x72F3C0() + SetErrorMode(0); + + return Msg.wParam; +} + +void InjectWinMainStuff() { + RH_ScopedCategory("Win"); + RH_ScopedNamespaceName("Win"); + + RH_ScopedGlobalInstall(IsForegroundApp, 0x746060); + RH_ScopedGlobalInstall(IsAlreadyRunning, 0x7468E0); + RH_ScopedGlobalInstall(CommandLineToArgv, 0x746480, { .reversed = false }); + + // Unhooking these 2 after the game has started will do nothing + RH_ScopedGlobalInstall(NOTSA_WinMain, 0x748710, { .locked = true }); + RH_ScopedGlobalInstall(InitInstance, 0x745560); +} diff --git a/source/app/platform/win/WinMain.h b/source/app/platform/win/WinMain.h new file mode 100644 index 0000000000..99cc414e24 --- /dev/null +++ b/source/app/platform/win/WinMain.h @@ -0,0 +1,3 @@ +#pragma once + +void InjectWinMainStuff(); diff --git a/source/app/platform/win/WinPs.cpp b/source/app/platform/win/WinPs.cpp new file mode 100644 index 0000000000..066818732d --- /dev/null +++ b/source/app/platform/win/WinPs.cpp @@ -0,0 +1,594 @@ +#include "StdInc.h" + +#include +#include +#include +#include +#include "VideoMode.h" +#include "VideoModeSelectDialog.h" +#include "LoadingScreen.h" +#include "C_PcSave.h" + +// NOTE: This macro doesn't do a whole lot. Leaving it here for completeness sake +#define USE_D3D9 + +static auto& PsGlobal = StaticRef(); + +//! Disable "This function was depracated" +#pragma warning (disable : 28159 4996) + +// 0x7455E0 - Get available videomem +HRESULT GetVideoMemInfo(LPDWORD total, LPDWORD available) { + LPDIRECTDRAW7 dd; + + if (HRESULT hr = DirectDrawCreateEx(NULL, (LPVOID*)&dd, IID_IDirectDraw7, NULL); FAILED(hr)) { + return hr; + } + + DDSCAPS2 caps; + ZeroMemory(&caps, sizeof(DDSCAPS2)); + caps.dwCaps = DDSCAPS_VIDEOMEMORY; + HRESULT hr = dd->GetAvailableVidMem(&caps, total, available); + dd->Release(); + + return hr; +} + +//! Check if D3D9 can be loaded (Originally this checked for versions 7 => 9, but GTA can only run with 9, so... :D +BOOL CheckDirectX() { + const auto hD3D9DLL = LoadLibrary("D3D9.DLL"); + if (hD3D9DLL == NULL) { + return FALSE; + } + FreeLibrary(hD3D9DLL); + return TRUE; +} + +//! 0x745840 - Check if DirectSound can be loaded +BOOL CheckDirectSound() { + LPDIRECTSOUND ds; + + if (FAILED(DirectSoundCreate(NULL, &ds, NULL))) { + return FALSE; + } + + DSCAPS caps{ sizeof(DSCAPS) }; + HRESULT hr = ds->GetCaps(&caps); + + ds->Release(); + + return SUCCEEDED(hr); +} + +// 0x7465B0 +void InitialiseLanguage() { +//#pragma warning (disable : 4302) // "Type truncation from HKL to + + // TODO: Use `GetLocaleInfoEx` + const auto sysDefaultLCID = PRIMARYLANGID(GetSystemDefaultLCID()); + const auto usrDefaultLCID = PRIMARYLANGID(GetUserDefaultLCID()); + const auto kbLayoutLCID = PRIMARYLANGID(LOWORD(GetKeyboardLayout(0))); + + FrontEndMenuManager.m_nTitleLanguage = sysDefaultLCID; + + FrontEndMenuManager.m_nTextLanguage = (int32)[&] { + switch (kbLayoutLCID) { + case LANG_GERMAN: return eLanguage::GERMAN; + case LANG_SPANISH: return eLanguage::SPANISH; + case LANG_FRENCH: return eLanguage::FRENCH; + case LANG_ITALIAN: return eLanguage::ITALIAN; + default: return eLanguage::AMERICAN; + } + }(); + + FrontEndMenuManager.m_nPrefsLanguage = [&] { + switch (usrDefaultLCID) { + case LANG_SPANISH: return eLanguage::SPANISH; + default: return eLanguage::AMERICAN; + } + }(); + + // Reload text + TheText.Unload(false); + TheText.Load(false); +} + +// 0x747420 +RwBool psInitialize() { + auto ps = &PsGlobal; + + RsGlobal.ps = ps; + + ps->lastMousePos.y = 0.f; + ps->lastMousePos.x = 0.f; + ps->fullScreen = FALSE; + ps->diInterface = NULL; + ps->diMouse = NULL; + ps->diDevice1 = NULL; + ps->diDevice2 = NULL; + + CFileMgr::Initialise(); + const auto usrdir = InitUserDirectories(); + s_PcSaveHelper.SetSaveDirectory(usrdir); + + gGameState = GAME_STATE_INITIAL; + + // TODO: Load vendor from CPUID + + // Figure out Windows version (TODO: Use `IsWindowsVersion*` from VersionHelpers.h instead) + s_OSStatus.OSVer = [&] { + OSVERSIONINFO verInfo{ sizeof(OSVERSIONINFO) }; + GetVersionEx(&verInfo); + + if (verInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) { + switch (const auto mv = verInfo.dwMajorVersion) { + case 4: return WinVer::WIN_NT; + case 5: return WinVer::WIN_2000_XP_2003; + case 6: return WinVer::WIN_VISTA_OR_LATER; + default: return mv < 4 ? WinVer::WIN_NT : WinVer::WIN_VISTA_OR_LATER; + } + } else if (verInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + return verInfo.dwMajorVersion > 4 || verInfo.dwMajorVersion == 4 && verInfo.dwMinorVersion != 0 + ? WinVer::WIN_98 + : WinVer::WIN_95; + } + NOTSA_UNREACHABLE(); // If this is reached the game wouldn't run anyways, so might as well just crash it. + }(); + + if (s_OSStatus.OSVer == WinVer::WIN_95) { + MessageBoxW( + NULL, + (LPCWSTR)TheText.Get("WIN_95"), // Grand Theft Auto San Andreas cannot run on Windows 95 + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto San Andreas + MB_OK + ); + + return FALSE; + } + + // Originally figured out available dx version and only allowed dx9, but we've simplified it. + if (!CheckDirectX()) { + MessageBoxW( + NULL, + (LPCWSTR)TheText.Get("WIN_DX"), // Grand Theft Auto San Andreas requires at least DirectX version 8.1 + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto San Andreas + MB_OK + ); + return FALSE; + } + + // CheckDirectX() Checks for Dx9 only, so use that + s_OSStatus.DxVer = 0x900; + + if (!CheckDirectSound()) { + MessageBoxW( + NULL, + (LPCWSTR)TheText.Get("WIN_NSC"), // Grand Theft Auto San Andreas requires a sound card (I guess?) + (LPCWSTR)TheText.Get("WIN_TTL"), // Grand Theft Auto San Andreas + MB_ICONEXCLAMATION + ); + return FALSE; + } + + MEMORYSTATUS memstat{ sizeof(MEMORYSTATUS) }; + GlobalMemoryStatus(&memstat); // TODO: `GlobalMemoryStatusEx` + s_OSStatus.RAM.TotalPhys = memstat.dwTotalPhys; + s_OSStatus.RAM.AvailPhys = memstat.dwAvailPhys; + s_OSStatus.RAM.TotalVirtual = memstat.dwTotalVirtual; + s_OSStatus.RAM.AvailVirtual = memstat.dwAvailVirtual; + + VERIFY(SUCCEEDED(GetVideoMemInfo(&s_OSStatus.VRAM.Total, &s_OSStatus.VRAM.Avail))); + VERIFY(SUCCEEDED(CoInitialize(NULL))); + + // Load setting only after everything was checked - TODO: Move this out from here, it's not platform specific + FrontEndMenuManager.LoadSettings(); + + return TRUE; +} + +// 0x7458A0 +void psTerminate() { + // NOP +} + +// 0x7451B0 +void psWindowSetText(const char* str) { + SetWindowTextA(PSGLOBAL(window), str); +} + +// 0x7451D0 +void psErrorMessage(const char* str) { + MessageBoxA(nullptr, str, RsGlobal.appName, MB_ICONERROR | MB_TASKMODAL | MB_TOPMOST); +} + +// 0x7451F0 +void psWarningMessage(const char* str) { + MessageBoxA(nullptr, str, RsGlobal.appName, MB_ICONWARNING | MB_TASKMODAL | MB_TOPMOST); +} + +// 0x745210 +bool psCameraBeginUpdate(RwCamera* camera) { + if (RwCameraBeginUpdate(Scene.m_pRwCamera)) { + return true; + } + RsEventHandler(rsACTIVATE, nullptr); + return false; +} + +// 0x745240 +RwCamera* psCameraShowRaster(RwCamera* camera) { + auto flags = FrontEndMenuManager.m_bPrefsFrameLimiter || CLoadingScreen::m_bActive ? rwRASTERFLIPWAITVSYNC : rwRASTERFLIPDONTWAIT; + return RwCameraShowRaster(camera, PSGLOBAL(window), flags); +} + +// 0x745270 +uint32 psTimer() { + return OS_TimeMS(); +} + +// 0x7452B0 +RwImage* psGrabScreen(RwCamera* camera) { + auto* device = static_cast(RwD3D9GetCurrentD3DDevice()); + assert(device); + + D3DDISPLAYMODE displayMode{}; + VERIFY(SUCCEEDED(device->GetDisplayMode(0, &displayMode))); + + IDirect3DSurface9* surface = nullptr; + VERIFY(SUCCEEDED(device->CreateOffscreenPlainSurface(displayMode.Width, displayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nullptr))); + VERIFY(SUCCEEDED(device->GetFrontBufferData(0, surface))); + + D3DLOCKED_RECT lockedRect{}; +#ifndef FIX_BUGS + // It's not needed as ClientToScreen func works with fullscreen mode. + if (PSGLOBAL(fullScreen)) { // todo: Doesn't work properly with III.VC.SA.WindowedMode.asi + VERIFY(SUCCEEDED(surface->LockRect(&lockedRect, nullptr, D3DLOCK_READONLY))); + } else { +#endif + RECT rect; +#ifdef FIX_BUGS + // SA code gets the whole window of the game, which includes the titlebar etc. + // + // BUG: There should be bugs for older versions of Windows IIRC. + // One example would be Vista version of the func doesn't count Aero effects of windows. + // + // TODO: Test with dual monitors etc. + + GetClientRect(PSGLOBAL(window), &rect); + + // GetClientRect returns relative positions unlike GetWindowRect. + // i.e. will return { 0, 0, width, height }. + ClientToScreen(PSGLOBAL(window), (POINT*)(&rect)); // kinda hacky but should work. + rect.right += rect.left; + rect.bottom += rect.top; +#else + GetWindowRect(PSGLOBAL(window), &rect); +#endif + displayMode.Height = rect.bottom - rect.top; + displayMode.Width = rect.right - rect.left; + VERIFY(SUCCEEDED(surface->LockRect(&lockedRect, &rect, D3DLOCK_READONLY))); +#ifndef FIX_BUGS + } +#endif + + RwImage* image = RwImageCreate(int32(displayMode.Width), int32(displayMode.Height), 32); + if (image) { + RwImageAllocatePixels(image); + + auto* pixels = (RwRGBA*)RwImageGetPixels(image); + auto* imagePixels = (uint8*)lockedRect.pBits; + assert(pixels && imagePixels); + + for (auto h = 0u; h < displayMode.Height; h++) { + for (auto w = 0u; w < displayMode.Width; w++) { + pixels->red = imagePixels[sizeof(RwRGBA) * w + 2]; + pixels->green = imagePixels[sizeof(RwRGBA) * w + 1]; + pixels->blue = imagePixels[sizeof(RwRGBA) * w + 0]; + pixels->alpha = 255; + pixels++; + } + imagePixels += lockedRect.Pitch; + } + } + + { // FIX_BUGS + surface->UnlockRect(); + surface->Release(); + delete surface; + } + + return image; +} + +// 0x7453E0 +void psMouseSetVisibility(bool visible) { + ::ShowCursor(visible); +} + +// 0x7453F0 +void psMouseSetPos(RwV2d* pos) { + POINT point = { .x = LONG(pos->x), .y = LONG(pos->y) }; + ::ClientToScreen(PSGLOBAL(window), &point); + ::SetCursorPos(point.x, point.y); + PSGLOBAL(lastMousePos) = *pos; +} + +// 0x745470 +char* psPathnameCreate(const char* buffer) { + const auto pathSize = std::strlen(buffer) + 1u; + auto path = (char*)CMemoryMgr::Malloc(pathSize); + if (path) { + strcpy_s(path, pathSize, buffer); + + while (auto ch = std::strchr(path, '/')) { + *ch = psPathGetSeparator(); + } + } + + return path; +} + +// 0x7454E0 +void psPathnameDestroy(char* buffer) { + if (buffer) { + RwFree(buffer); + } +} + +// 0x745500 +char psPathGetSeparator() { + return '\\'; +} + +// 0x745520 +bool psInstallFileSystem() { + return true; +} + +// 0x745530 +bool psNativeTextureSupport() { + return RwD3D9DeviceSupportsDXTTexture(); +} + +// 0x745540 +RsEventStatus psDebugMessageHandler(RsEvent event, void* param) { + if (rsINITDEBUG == event) { + RwDebugSetHandler(psDebugMessageHandler); +#if defined(DEBUG) && defined(RWTRACE) + RwDebugSetTraceState(true); +#else + RwDebugSetTraceState(false); +#endif + } + + return rsEVENTNOTPROCESSED; +} + +// 0x7458B0 +bool psAlwaysOnTop(bool alwaysOnTop) { + const auto hwnd = PSGLOBAL(window); + + RECT winRect; + VERIFY(GetWindowRect(hwnd, &winRect)); + + return SetWindowPos( + hwnd, + alwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST, + winRect.left, winRect.top, + winRect.right - winRect.left, winRect.bottom - winRect.top, + NULL + ); +} + +// NOTSA +auto GetNativeResolutionOfCurrentSubsystem() { +#ifdef USE_D3D9 + const auto d3d = Direct3DCreate9(D3D_SDK_VERSION); +#else + const auto d3d = Direct3DCreate8(D3D_SDK_VERSION); +#endif + const notsa::ScopeGuard released3d{[d3d] { d3d->Release(); }}; + assert(d3d != NULL); + + D3DDISPLAYMODE nativeRes; + d3d->GetAdapterDisplayMode(RwEngineGetCurrentSubSystem(), &nativeRes); + + DEV_LOG("Got native resolution from RW subsystem ({}): {} x {}", RwEngineGetCurrentSubSystem(), nativeRes.Width, nativeRes.Height); + + return std::make_pair(nativeRes.Width, nativeRes.Height); +} + +BOOL CheckDefaultVideoModeSupported() { + // IMPROVEMENT/FIX_BUGS: The game will now default to native adapter + // resolution instead of 800x600. + + const auto SearchVideoMode = [](int32 width, int32 height) { + for (auto i = 0; i < RwEngineGetNumVideoModes(); i++) { + const auto vm = RwEngineGetVideoModeInfo(i); + if (vm.width == width && vm.height == height && vm.depth == 32 && (vm.flags & rwVIDEOMODEEXCLUSIVE)) { + return i; + } + } + + return -1; + }; + + if (notsa::IsFixBugs()) { + const auto&& [w, h] = GetNativeResolutionOfCurrentSubsystem(); + if (const auto vm = SearchVideoMode(w, h); vm != -1) { + GcurSelVM = vm; + return TRUE; + } + /* fallthrough if native res is not supported! */ + } + + if (const auto vm = SearchVideoMode(800, 600); vm != -1) { + GcurSelVM = vm; + return TRUE; + } + + MessageBox(NULL, "Cannot find 800x600x32 video mode", "GTA: San Andreas", IDOK); + return FALSE; +} + +// 0x7460A0 +RwUInt32 GetBestRefreshRate(RwUInt32 width, RwUInt32 height, RwUInt32 depth) { +#ifdef USE_D3D9 + const auto d3d = Direct3DCreate9(D3D_SDK_VERSION); +#else + const auto d3d = Direct3DCreate8(D3D_SDK_VERSION); +#endif + const notsa::ScopeGuard released3d{ [d3d] { d3d->Release(); } }; + + assert(d3d != NULL); + + RwUInt32 refreshRate = INT_MAX; + + const auto format = depth == 32 + ? D3DFMT_X8R8G8B8 + : depth == 24 + ? D3DFMT_R8G8B8 + : D3DFMT_R5G6B5; + +#ifdef USE_D3D9 + for (auto i = d3d->GetAdapterModeCount(GcurSelSS, format); i-- > 0;) { +#else + for (auto i = d3d->GetAdapterModeCount(GcurSel); i-- > 0;) { +#endif + D3DDISPLAYMODE mode; + +#ifdef USE_D3D9 + d3d->EnumAdapterModes(GcurSelSS, format, i, &mode); +#else + d3d->EnumAdapterModes(GcurSel, i, &mode); +#endif + + if (mode.Width != width || mode.Height != height || mode.Format != format) { + continue; + } + + if (mode.RefreshRate == 0) { + return 0; + } + + if (mode.RefreshRate < refreshRate && mode.RefreshRate >= 60) { + refreshRate = mode.RefreshRate; + } + } + + return refreshRate; +} + +// 0x746190 +bool psSelectDevice() { + const auto wnd = PSGLOBAL(window); + const auto inst = PSGLOBAL(instance); + + if (!UseDefaultVM) { + GnumSubSystems = RwEngineGetNumSubSystems(); + if (!GnumSubSystems) { + DEV_LOG("No SubSystems to select from!"); + return FALSE; + } + + /* Just to be sure ... */ + GnumSubSystems = std::min(MAX_SUBSYSTEMS, GnumSubSystems); + + /* Get the names of all the sub systems */ + for (auto i = 0; i < GnumSubSystems; i++) { + RwEngineGetSubSystemInfo(&GsubSysInfo[i], i); + } + + /* Get the default selection */ + GcurSelSS = RwEngineGetCurrentSubSystem(); + } + + MultipleSubSystems = GnumSubSystems > 1; + + // Select video mode to use + if (MultipleSubSystems && !UseDefaultVM) { + if (!CreateVidModeSelectDialog(inst, wnd)) { + return FALSE; // User failed to select video mode + } + } + + // Set selected subsystem + if (!RwEngineSetSubSystem(GcurSelSS)) { + DEV_LOG("Failed: RwEngineSetSubSystem({})", GcurSelSS); + return FALSE; + } + + DEV_LOG("GcurSelSS={}", GcurSelSS); + + if (!UseDefaultVM && !MultipleSubSystems) { + const auto vmDisplay = FrontEndMenuManager.m_nDisplayVideoMode; + + // IMPROVEMENT/NOTSA: Originally `vmDisplay == 0`. + // Originally 0 (which is windowed vm) is a sentinel value for resolution settings. + // If that sentinel value is selected, the game searches for 'real default video mode'. (SA: 800x600) + // + // Zero as a sentinel value prevents us to save the game with windowed mode, so it's converted to -1. + // Look at: `SetToDefaultSettings` lambda at CMenuManager::LoadSettings. + if (vmDisplay == -1 || !GetVideoModeList()[vmDisplay]) { + if (IsVMNotSelected && !CheckDefaultVideoModeSupported()) { + return FALSE; + } + } else { + GcurSelVM = FrontEndMenuManager.m_nPrefsVideoMode = vmDisplay; + } + } + + FrontEndMenuManager.m_nCurrentScreenItem = 0; + + // Set selected videomode + if (!RwEngineSetVideoMode(GcurSelVM)) { + DEV_LOG("Failed: RwEngineSetVideoMode({})", GcurSelVM); + return FALSE; + } + + DEV_LOG("GcurSelVM={}", GcurSelVM); + + if (const auto vmi = RwEngineGetVideoModeInfo(GcurSelVM); vmi.flags & rwVIDEOMODEEXCLUSIVE) { + if (const auto rr = GetBestRefreshRate(vmi.width, vmi.height, vmi.depth); rr != -1) { + DEV_LOG("Refresh Rate: {} Hz", rr); + RwD3D9EngineSetRefreshRate(rr); + } + + RsGlobal.maximumHeight = vmi.height; + RsGlobal.maximumWidth = vmi.width; + PSGLOBAL(fullScreen) = true; + } + + RwD3D9EngineSetMultiSamplingLevels(FrontEndMenuManager.m_nPrefsAntialiasing); + + return TRUE; +} + +void WinPsInjectHooks() { + RH_ScopedCategory("Win"); + RH_ScopedNamespaceName("Ps"); + + RH_ScopedGlobalInstall(psInitialize, 0x747420); + RH_ScopedGlobalInstall(psTerminate, 0x7458A0); + RH_ScopedGlobalInstall(psWindowSetText, 0x7451B0); + RH_ScopedGlobalInstall(psErrorMessage, 0x7451D0); + RH_ScopedGlobalInstall(psWarningMessage, 0x7451F0); + RH_ScopedGlobalInstall(psCameraBeginUpdate, 0x745210); + RH_ScopedGlobalInstall(psCameraShowRaster, 0x745240); + RH_ScopedGlobalInstall(psTimer, 0x745270); + RH_ScopedGlobalInstall(psGrabScreen, 0x7452B0); + RH_ScopedGlobalInstall(psMouseSetVisibility, 0x7453E0); + RH_ScopedGlobalInstall(psMouseSetPos, 0x7453F0); + RH_ScopedGlobalInstall(psPathnameCreate, 0x745470); + RH_ScopedGlobalInstall(psPathnameDestroy, 0x7454E0); + RH_ScopedGlobalInstall(psPathGetSeparator, 0x745500); + RH_ScopedGlobalInstall(psInstallFileSystem, 0x745520); + RH_ScopedGlobalInstall(psNativeTextureSupport, 0x745530); + RH_ScopedGlobalInstall(psDebugMessageHandler, 0x745540); + RH_ScopedGlobalInstall(psAlwaysOnTop, 0x7458B0); + RH_ScopedGlobalInstall(psSelectDevice, 0x746190); + + RH_ScopedGlobalInstall(InitialiseLanguage, 0x7465B0); + RH_ScopedGlobalInstall(CheckDirectSound, 0x745840); + RH_ScopedGlobalInstall(GetVideoMemInfo, 0x7455E0); +} diff --git a/source/app/platform/win/WndProc.cpp b/source/app/platform/win/WndProc.cpp new file mode 100644 index 0000000000..6a6299b457 --- /dev/null +++ b/source/app/platform/win/WndProc.cpp @@ -0,0 +1,408 @@ +/* +* This file contains WndProc and related functions +*/ + +#include "StdInc.h" + +#include "imgui_impl_win32.h" + +#include +#include +#include +#include + +#include +#include "PostEffects.h" +#include "AEAudioHardware.h" +#include "VideoMode.h" +#include "VideoPlayer.h" +#include "Input.h" +#include "Gamma.h" + +// Dear ImGui said I have to copy this here +extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + +// 0x747820 +BOOL GTATranslateKey(RsKeyCodes* ck, LPARAM lParam, UINT vk) { + *ck = [&] { + // Handle extended keys + const auto Ext = [kf = HIWORD(lParam)](RsKeyCodes extended, RsKeyCodes unextended) { + return (kf & KF_EXTENDED) ? extended : unextended; + }; + + switch (vk) { + case VK_RETURN: return Ext(rsPADENTER, rsENTER); + case VK_CONTROL: return Ext(rsRCTRL, rsLCTRL); + case VK_MENU: return Ext(rsRALT, rsLALT); + case VK_PRIOR: return Ext(rsPGUP, rsPADPGUP); + case VK_NEXT: return Ext(rsPGDN, rsPADPGDN); + case VK_END: return Ext(rsEND, rsPADEND); + case VK_HOME: return Ext(rsHOME, rsPADHOME); + case VK_LEFT: return Ext(rsLEFT, rsPADLEFT); + case VK_UP: return Ext(rsUP, rsPADUP); + case VK_RIGHT: return Ext(rsRIGHT, rsPADRIGHT); + case VK_DOWN: return Ext(rsDOWN, rsPADDOWN); + case VK_INSERT: return Ext(rsINS, rsPADINS); + case VK_DELETE: return Ext(rsDEL, rsPADDEL); + case VK_BACK: return rsBACKSP; + case VK_TAB: return rsTAB; + case VK_PAUSE: return rsPAUSE; + case VK_CAPITAL: return rsCAPSLK; + case VK_ESCAPE: return rsESC; + case VK_LWIN: return rsLWIN; + case VK_RWIN: return rsRWIN; + case VK_APPS: return rsAPPS; + case VK_NUMPAD0: return rsPADINS; + case VK_NUMPAD1: return rsPADEND; + case VK_NUMPAD2: return rsPADDOWN; + case VK_NUMPAD3: return rsPADPGDN; + case VK_NUMPAD4: return rsPADLEFT; + case VK_NUMPAD5: return rsPAD5; + case VK_NUMPAD6: return rsPADRIGHT; + case VK_NUMPAD7: return rsPADHOME; + case VK_NUMPAD8: return rsPADUP; + case VK_NUMPAD9: return rsPADPGUP; + case VK_MULTIPLY: return rsTIMES; + case VK_ADD: return rsPLUS; + case VK_SUBTRACT: return rsMINUS; + case VK_DECIMAL: return rsPADDEL; + case VK_DIVIDE: return rsDIVIDE; + case VK_F1: return rsF1; + case VK_F2: return rsF2; + case VK_F3: return rsF3; + case VK_F4: return rsF4; + case VK_F5: return rsF5; + case VK_F6: return rsF6; + case VK_F7: return rsF7; + case VK_F8: return rsF8; + case VK_F9: return rsF9; + case VK_F10: return rsF10; + case VK_F11: return rsF11; + case VK_F12: return rsF12; + case VK_NUMLOCK: return rsNUMLOCK; + case VK_SCROLL: return rsSCROLL; + case VK_SHIFT: { + return s_OSStatus.OSVer == WinVer::WIN_98 // Will be handled later + ? rsSHIFT + : rsNULL; + } + default: { // Try mapping to regular ASCII char + const auto chr = MapVirtualKey(vk, MAPVK_VK_TO_CHAR); + if (chr <= 0xFF) { + return (RsKeyCodes)(chr); + } + break; + } + } + return rsNULL; + }(); + return *ck != rsNULL; +} + +/*! +* Process shift keys. +* Unless Win98, in which case `GTATranslateKey` should handle it. +* @addr 0x747CD0 +*/ +BOOL GTATranslateShiftKey(RsKeyCodes*) { // The in keycode is ignored, so we won't bother + if (s_OSStatus.OSVer == WinVer::WIN_98) { + return false; // Already handled by `GTATranslateKey` + } + + constexpr struct { RsKeyCodes ck; INT vk; } Keys[]{ + {rsLSHIFT, VK_LSHIFT}, + {rsRSHIFT, VK_RSHIFT}, + }; + + for (auto shouldBeDown : { false, true }) { + for (auto [ck, vk] : Keys) { + // GetKeyState reads from the message queue, + // so we must call it like the og code + const auto isDown = (HIWORD(GetKeyState(vk)) & 0x80) == 1; // Check is key pressed + if (isDown == shouldBeDown) { + RsEventHandler( + isDown ? rsKEYDOWN : rsKEYUP, + &ck + ); + } + } + } + + return true; +} + + +// 0x747EB0 +LRESULT CALLBACK __MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + const auto imio = ImGui::GetCurrentContext() ? &ImGui::GetIO() : nullptr; + if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) { + return true; + } + + switch (uMsg) { + case WM_SETCURSOR: { + ShowCursor(false); + SetCursor(NULL); + break; + } + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + case WM_KEYUP: + case WM_SYSKEYUP: { //< 0x74823B - wParam is a `VK_` (virtual key), lParam are the flags (See https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keyup)] + if (imio && imio->WantCaptureKeyboard) { + return 0; + } + + if (RsKeyCodes ck; GTATranslateKey(&ck, lParam, wParam)) { + RsKeyboardEventHandler( + (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) + ? rsKEYDOWN + : rsKEYUP, + &ck + ); + } + if (wParam == VK_SHIFT) { + RsKeyCodes ck; + GTATranslateShiftKey(&ck); // Original code uses this variable for storage, so we can't pass in nullptr - TODO: Remove parameter + } + return 0; + } + case WM_MOUSEMOVE: { //< 0x748323 + if (imio && imio->WantCaptureMouse) { + return 0; + } + FrontEndMenuManager.m_nMousePosWinX = GET_X_LPARAM(lParam); + FrontEndMenuManager.m_nMousePosWinY = GET_Y_LPARAM(lParam); + break; + } + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_MBUTTONDOWN: { + if (imio && imio->WantCaptureMouse) { + return 0; + } + SetCapture(hWnd); + return 0; + } + case WM_LBUTTONUP: + case WM_RBUTTONUP: + case WM_MBUTTONUP: { + if (imio && imio->WantCaptureMouse) { + return 0; + } + ReleaseCapture(); + return 0; + } + case WM_MOUSEWHEEL: + return 0; + case WM_SIZING: { // 0x74829E + if (RwInitialized) { + if (gGameState == GAME_STATE_IDLE) { + RsEventHandler(rsIDLE, (void*)TRUE); + } + } + + const auto wndrect = reinterpret_cast(lParam); + VERIFY(SetWindowPos( + hWnd, + HWND_TOP, + 0, 0, + wndrect->right - wndrect->left, wndrect->bottom - wndrect->top, + SWP_NOSIZE | SWP_NOMOVE + )); + + CPostEffects::SetupBackBufferVertex(); + + return 0; + } + case WM_ACTIVATEAPP: { // 0x748087 + const auto wndBeingActivated = !!wParam; + + //> 0x748087 - Set gamma (If changed) + if (gbGammaChanged) { + if (const auto dev = RwD3D9GetCurrentD3DDevice()) { + dev->SetGammaRamp(9, 0, wndBeingActivated ? &gammaTable : &savedGamma); + } + } + + // + switch (gGameState) { + case GAME_STATE_PLAYING_LOGO: + case GAME_STATE_PLAYING_INTRO: { + const auto mc = VideoPlayer::GetMediaControl(); + + OAFilterState state; + VERIFY(SUCCEEDED(mc->GetState(10, &state))); + if (wndBeingActivated) { + VideoPlayer::UpdateWindow(); + if (state == State_Running) { + break; + } + VERIFY(SUCCEEDED(mc->Run())); + VideoPlayer::WaitState(State_Running); + SetFocus(PSGLOBAL(window)); + } else if (state == State_Running) { // Window is being deactivatd, pause media + VERIFY(SUCCEEDED(mc->Pause())); + } + + break; + } + case GAME_STATE_INITIAL: { + if (!wndBeingActivated) { // 0x748171 + if (PSGLOBAL(fullScreen)) { + Windowed = true; // ??? + } + } + break; + } + } + + //> 0x748183 Clear pads + for (auto& pad : CPad::Pads) { + pad.Clear(false, true); + } + + return 0; + } + case WM_DESTROY: + case WM_CLOSE: { // 0x747EF3 + VERIFY(ClipCursor(nullptr)); + VERIFY(SUCCEEDED(WinInput::Shutdown())); + PostQuitMessage(0); + return 0; + } + case WM_SIZE: { // 0x747F04 + // Figure out new size + const auto width = LOWORD(lParam), height = HIWORD(lParam); + RwRect wndsz{ + .x = 0, .y = 0, + .w = width, .h = height, + }; + + // Nothing to do? + if (!RwInitialized || !wndsz.h || !wndsz.w) { + return 0; + } + + // Try resizing the camera + RsEventHandler(rsCAMERASIZE, &wndsz); + + // Check if it has failed + if (wndsz.w != width && wndsz.h != height) { // TODO/BUG: Shouldnt `&&` instead be `||`? + VERIFY(ReleaseCapture()); + + WINDOWPLACEMENT wndpl; + VERIFY(GetWindowPlacement(PSGLOBAL(window), &wndpl)); + + if (wndpl.showCmd == SW_SHOWMAXIMIZED) { + SendMessage(PSGLOBAL(window), WM_WINDOWPOSCHANGED, 0, 0); + } + } + + return 0; + } + case WM_ACTIVATE: { // 0x747FA3 + const auto wndBeingActivated = !!wParam; + + if (wndBeingActivated) { + CAudioEngine::ResumeAllSounds(); + ShowCursor(FALSE); + } else { + CAudioEngine::PauseAllSounds(); + VERIFY(SUCCEEDED(SetTimer(PSGLOBAL(window), 1, (UINT)(1000.f / RsGlobal.frameLimit), nullptr))); + } + + ForegroundApp = wndBeingActivated; + RsEventHandler(rsACTIVATE, (void*)wndBeingActivated); + + //> 0x74800C Clear pads + for (auto& pad : CPad::Pads) { + pad.Clear(false, true); + } + +#ifdef FIX_BUGS + break; +#else + return 0; +#endif + } + case WM_SETFOCUS: { // 0x748063 + ForegroundApp = true; + if (!FrontEndMenuManager.m_bMainMenuSwitch && !FrontEndMenuManager.m_bMenuActive) { // OnSetFocus + FrontEndMenuManager.m_bActivateMenuNextFrame = true; + } +#ifdef FIX_BUGS + break; +#else + return 0; +#endif + } + case WM_KILLFOCUS: { // 0x748054 + ForegroundApp = false; +#ifdef FIX_BUGS + break; +#else + return 0; +#endif + } + case WM_GRAPHNOTIFY: { //< 0x74842A - Dispatched from VideoPlayer::Play + switch (gGameState) { + case GAME_STATE_PLAYING_INTRO: + case GAME_STATE_PLAYING_LOGO: { + VideoPlayer::OnGraphNotify(); + break; + } + } +#ifdef FIX_BUGS + break; +#else + return 0; +#endif + } + case WM_DEVICECHANGE: { //> 0x748282 - Handle AudioHardware DVD removal + if (wParam != DBT_DEVICEREMOVECOMPLETE) { + break; + } + const auto eparams = reinterpret_cast(lParam); + if (eparams->dbch_devicetype != DBT_DEVTYP_VOLUME) { + break; + } + const auto idev = reinterpret_cast(lParam); + if ((idev->dbcv_flags & DBTF_MEDIA) == 0) { // Not a media drive? + break; + } + if (!AEAudioHardware.m_bInitialised || !AEAudioHardware.IsStreamingFromDVD()) { + break; + } + const auto dvletter = AEAudioHardware.GetDVDDriveLetter(); + if (dvletter < 'A' || (idev->dbcv_unitmask & (1 << dvletter)) == 0) { + break; + } + DEV_LOG("About to check CD drive"); + CTimer::SetCodePause(true); + if (CCutsceneMgr::IsRunning()) { + CCutsceneMgr::SkipCutscene(); + } + while (!AEAudioHardware.CheckDVD()) { + FrontEndMenuManager.NoDiskInDriveMessage(); + if (FrontEndMenuManager.m_bQuitGameNoDVD) { + DEV_LOG("Exiting game as Audio CD was not inserted"); + break; + } + } + DEV_LOG("GTA Audio DVD has been inserted"); + CTimer::SetCodePause(false); + break; + } + } + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +void InjectHooksWndProcStuff() { + RH_ScopedCategory("Win"); + RH_ScopedNamespaceName("Win"); + + RH_ScopedGlobalInstall(GTATranslateShiftKey, 0x747CD0); + RH_ScopedGlobalInstall(GTATranslateKey, 0x747820); + RH_ScopedGlobalInstall(__MainWndProc, 0x747EB0, {.locked = true}); // Locked because of ImGui +} diff --git a/source/app/platform/win/WndProc.h b/source/app/platform/win/WndProc.h new file mode 100644 index 0000000000..957734b29e --- /dev/null +++ b/source/app/platform/win/WndProc.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +LRESULT CALLBACK __MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); +void InjectHooksWndProcStuff(); diff --git a/source/app/platform/win/win.cpp b/source/app/platform/win/win.cpp deleted file mode 100644 index ae9972ba49..0000000000 --- a/source/app/platform/win/win.cpp +++ /dev/null @@ -1,831 +0,0 @@ -#include "StdInc.h" - -#include -#include - -#include "win.h" -#include "dshow.h" - -#include "VideoPlayer.h" -#include "Gamma.h" - -// #include "InputEvents.h" -#include "platform.h" - -#include "PostEffects.h" -#include "Clouds.h" -#include "Skidmarks.h" -#include "LoadingScreen.h" -#include "VideoMode.h" -#include "ControllerConfigManager.h" -#include "Input.h" -#include "MenuManager.h" -#include - -extern void WinPsInjectHooks(); - -static LPSTR AppClassName = LPSTR(APP_CLASS); - -constexpr auto NO_FOREGROUND_PAUSE = true; - -// Custom enum, generated by ChatGPT, thank you -enum class WinVer { - WIN_95, //< Windows 95 - WIN_98, //< Windows 98 - WIN_ME, //< Windows ME - WIN_NT_4, //< Windows NT 4.0 - WIN_2000_XP_2003, //< Windows 2000/XP/Server 2003 - WIN_VISTA_OR_LATER, //< Windows Vista/7/8/10 - - UNKNOWN -}; - -static auto& s_WinVer = StaticRef(); - -// forward declarations -bool IsAlreadyRunning(); -char** CommandLineToArgv(char* cmdLine, int* argCount); -LRESULT CALLBACK __MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); -INT WINAPI __WinMain(HINSTANCE instance, HINSTANCE hPrevInstance, LPSTR cmdLine, INT nCmdShow); - -// @notsa -// @brief Resets the screen gamma if ever changed. -void ResetGammaWhenExiting() { - if (gbGammaChanged) { - if (auto d3dDevice = (IDirect3DDevice9*)RwD3D9GetCurrentD3DDevice()) { - d3dDevice->SetGammaRamp(0u, D3DSGR_CALIBRATE, &savedGamma); - } - gbGammaChanged = false; - } -} - -// 0x746870 -void MessageLoop() { - tagMSG msg; - while (PeekMessageA(&msg, nullptr, 0, 0, PM_REMOVE | PM_NOYIELD)) { - if (msg.message == WM_QUIT) { - RsGlobal.quit = true; - } else { - TranslateMessage(&msg); - DispatchMessageA(&msg); - } - } -} - -// 0x7486A0 -bool InitApplication(HINSTANCE hInstance) { - WNDCLASS windowClass = { 0 }; - windowClass.style = CS_BYTEALIGNWINDOW; - windowClass.lpfnWndProc = __MainWndProc; - windowClass.hInstance = hInstance; - windowClass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MAIN_ICON)); - windowClass.hCursor = LoadCursor(nullptr, IDC_ARROW); - windowClass.lpszClassName = AppClassName; - return RegisterClass(&windowClass); -} - -// 0x745560 -HWND InitInstance(HINSTANCE hInstance) { - RECT rect = { 0, 0, RsGlobal.maximumWidth, RsGlobal.maximumHeight }; - AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, false); - - return CreateWindowEx( - 0, - APP_CLASS, - RsGlobal.appName, - WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, - CW_USEDEFAULT, - rect.right - rect.left, - rect.bottom - rect.top, - nullptr, - nullptr, - hInstance, - nullptr - ); -} - -// 0x7468E0 -bool IsAlreadyRunning() { - CreateEventA(nullptr, false, true, AppClassName); - if (GetLastError() != ERROR_ALREADY_EXISTS) { - return false; - } - - HWND window = FindWindowA(AppClassName, RsGlobal.appName); - if (window) - SetForegroundWindow(window); - else - SetForegroundWindow(PSGLOBAL(window)); - - return true; -}; - -// 0x746060 -bool IsForegroundApp() { - return ForegroundApp; -} - -// 0x746480 -char** CommandLineToArgv(char* cmdLine, int* argCount) { - return plugin::CallAndReturn(cmdLine, argCount); -} - -// 0x747820 -BOOL GTATranslateKey(RsKeyCodes* ck, LPARAM lParam, UINT vk) { - *ck = [&] { - // Handle extended keys - const auto Ext = [kf = HIWORD(lParam)](RsKeyCodes extended, RsKeyCodes unextended) { - return (kf & KF_EXTENDED) ? extended : unextended; - }; - - switch (vk) { - case VK_RETURN: return Ext(rsPADENTER, rsENTER); - case VK_CONTROL: return Ext(rsRCTRL, rsLCTRL); - case VK_MENU: return Ext(rsRALT, rsLALT); - case VK_PRIOR: return Ext(rsPGUP, rsPADPGUP); - case VK_NEXT: return Ext(rsPGDN, rsPADPGDN); - case VK_END: return Ext(rsEND, rsPADEND); - case VK_HOME: return Ext(rsHOME, rsPADHOME); - case VK_LEFT: return Ext(rsLEFT, rsPADLEFT); - case VK_UP: return Ext(rsUP, rsPADUP); - case VK_RIGHT: return Ext(rsRIGHT, rsPADRIGHT); - case VK_DOWN: return Ext(rsDOWN, rsPADDOWN); - case VK_INSERT: return Ext(rsINS, rsPADINS); - case VK_DELETE: return Ext(rsDEL, rsPADDEL); - case VK_BACK: return rsBACKSP; - case VK_TAB: return rsTAB; - case VK_PAUSE: return rsPAUSE; - case VK_CAPITAL: return rsCAPSLK; - case VK_ESCAPE: return rsESC; - case VK_LWIN: return rsLWIN; - case VK_RWIN: return rsRWIN; - case VK_APPS: return rsAPPS; - case VK_NUMPAD0: return rsPADINS; - case VK_NUMPAD1: return rsPADEND; - case VK_NUMPAD2: return rsPADDOWN; - case VK_NUMPAD3: return rsPADPGDN; - case VK_NUMPAD4: return rsPADLEFT; - case VK_NUMPAD5: return rsPAD5; - case VK_NUMPAD6: return rsPADRIGHT; - case VK_NUMPAD7: return rsPADHOME; - case VK_NUMPAD8: return rsPADUP; - case VK_NUMPAD9: return rsPADPGUP; - case VK_MULTIPLY: return rsTIMES; - case VK_ADD: return rsPLUS; - case VK_SUBTRACT: return rsMINUS; - case VK_DECIMAL: return rsPADDEL; - case VK_DIVIDE: return rsDIVIDE; - case VK_F1: return rsF1; - case VK_F2: return rsF2; - case VK_F3: return rsF3; - case VK_F4: return rsF4; - case VK_F5: return rsF5; - case VK_F6: return rsF6; - case VK_F7: return rsF7; - case VK_F8: return rsF8; - case VK_F9: return rsF9; - case VK_F10: return rsF10; - case VK_F11: return rsF11; - case VK_F12: return rsF12; - case VK_NUMLOCK: return rsNUMLOCK; - case VK_SCROLL: return rsSCROLL; - case VK_SHIFT: { - return s_WinVer == WinVer::WIN_98 // Will be handled later - ? rsSHIFT - : rsNULL; - } - default: { // Try mapping to regular ASCII char - const auto chr = MapVirtualKey(vk, MAPVK_VK_TO_CHAR); - if (chr <= 0xFF) { - return (RsKeyCodes)(chr); - } - break; - } - } - return rsNULL; - }(); - return *ck != rsNULL; -} - -/*! -* Process shift keys. -* Unless Win98, in which case `GTATranslateKey` should handle it. -* @addr 0x747CD0 -*/ -BOOL GTATranslateShiftKey(RsKeyCodes*) { // The in keycode is ignored, so we won't bother - if (s_WinVer == WinVer::WIN_98) { - return false; // Already handled by `GTATranslateKey` - } - - constexpr struct { RsKeyCodes ck; INT vk; } Keys[]{ - {rsLSHIFT, VK_LSHIFT}, - {rsRSHIFT, VK_RSHIFT}, - }; - - for (auto shouldBeDown : { false, true }) { - for (auto [ck, vk] : Keys) { - // GetKeyState reads from the message queue, - // so we must call it like the og code - const auto isDown = (HIWORD(GetKeyState(vk)) & 0x80) == 1; // Check is key pressed - if (isDown == shouldBeDown) { - RsEventHandler( - isDown ? rsKEYDOWN : rsKEYUP, - &ck - ); - } - } - } - - return true; -} - -// 0x747EB0 -LRESULT CALLBACK __MainWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { - switch (uMsg) { - case WM_SETCURSOR: { - ShowCursor(false); - SetCursor(NULL); - break; - } - case WM_KEYDOWN: - case WM_SYSKEYDOWN: - case WM_KEYUP: - case WM_SYSKEYUP: { //< 0x74823B - wParam is a `VK_` (virtual key), lParam are the flags (See https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keyup) - if (RsKeyCodes ck; GTATranslateKey(&ck, lParam, wParam)) { - RsKeyboardEventHandler( - (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) - ? rsKEYDOWN - : rsKEYUP, - &ck - ); - } - if (wParam == VK_SHIFT) { - RsKeyCodes ck; - GTATranslateShiftKey(&ck); // Original code uses this variable for storage, so we can't pass in nullptr - TODO: Remove parameter - } - return 0; - } - case WM_ACTIVATEAPP: { // 0x748087 - const auto wndBeingActivated = !!wParam; - - //> 0x748087 - Set gamma (If changed) - if (gbGammaChanged) { - if (const auto dev = RwD3D9GetCurrentD3DDevice()) { - dev->SetGammaRamp(9, 0, wndBeingActivated ? &gammaTable : &savedGamma); - } - } - - // - switch (gGameState) { - case GAME_STATE_PLAYING_LOGO: - case GAME_STATE_PLAYING_INTRO: { - const auto mc = VideoPlayer::GetMediaControl(); - - OAFilterState state; - VERIFY(SUCCEEDED(mc->GetState(10, &state))); - if (wndBeingActivated) { - VideoPlayer::UpdateWindow(); - if (state == State_Running) { - break; - } - VERIFY(SUCCEEDED(mc->Run())); - VideoPlayer::WaitState(State_Running); - SetFocus(PSGLOBAL(window)); - } else if (state == State_Running) { // Window is being deactivatd, pause media - VERIFY(SUCCEEDED(mc->Pause())); - } - - break; - } - case GAME_STATE_INITIAL: { - if (!wndBeingActivated) { // 0x748171 - if (PSGLOBAL(fullScreen)) { - Windowed = true; // ??? - } - } - break; - } - } - - //> 0x748183 Clear pads - for (auto& pad : CPad::Pads) { - pad.Clear(false, true); - } - - return 0; - } - case WA_CLICKACTIVE: - case WM_CLOSE: { // 0x747EF3 - VERIFY(ClipCursor(nullptr)); - VERIFY(SUCCEEDED(WinInput::Shutdown())); - PostQuitMessage(0); - return 0; - } - case WM_SIZE: { // 0x747F04 - // Figure out new size - const auto width = LOWORD(lParam), height = HIWORD(lParam); - RwRect wndsz{ - .x = 0, .y = 0, - .w = width, .h = height, - }; - - // Nothing to do? - if (!RwInitialized || !wndsz.h || !wndsz.w) { - return 0; - } - - // Try resizing the camera - RsEventHandler(rsCAMERASIZE, &wndsz); - - // Check if it has failed - if (wndsz.w != width && wndsz.h != height) { // TODO/BUG: Shouldnt `&&` instead be `||`? - VERIFY(ReleaseCapture()); - - WINDOWPLACEMENT wndpl; - VERIFY(GetWindowPlacement(PSGLOBAL(window), &wndpl)); - - if (wndpl.showCmd == SW_SHOWMAXIMIZED) { - SendMessage(PSGLOBAL(window), WM_WINDOWPOSCHANGED, 0, 0); - } - } - - return 0; - } - case WM_ACTIVATE: { // 0x747FA3 - const auto wndBeingActivated = !!wParam; - - if (wndBeingActivated) { - CAudioEngine::ResumeAllSounds(); - ShowCursor(FALSE); - } else { - CAudioEngine::PauseAllSounds(); - VERIFY(SUCCEEDED(SetTimer(PSGLOBAL(window), 1, (UINT)(1000.f / RsGlobal.frameLimit), nullptr))); - } - - ForegroundApp = wndBeingActivated; - RsEventHandler(rsACTIVATE, (void*)wndBeingActivated); - - //> 0x74800C Clear pads - for (auto& pad : CPad::Pads) { - pad.Clear(false, true); - } - -#ifdef FIX_BUGS - break; -#else - return 0; -#endif - } - case WM_SETFOCUS: { // 0x748063 - ForegroundApp = true; - if (!FrontEndMenuManager.m_bMainMenuSwitch && !FrontEndMenuManager.m_bMenuActive) { // OnSetFocus - FrontEndMenuManager.m_bActivateMenuNextFrame = true; - } -#ifdef FIX_BUGS - break; -#else - return 0; -#endif - } - case WM_KILLFOCUS: { // 0x748054 - ForegroundApp = false; -#ifdef FIX_BUGS - break; -#else - return 0; -#endif - } - case WM_GRAPHNOTIFY: { //< 0x74842A - Dispatched from VideoPlayer::Play - switch (gGameState) { - case GAME_STATE_PLAYING_INTRO: - case GAME_STATE_PLAYING_LOGO: { - VideoPlayer::OnGraphNotify(); - break; - } - } -#ifdef FIX_BUGS - break; -#else - return 0; -#endif - } - case WM_DEVICECHANGE: { //> 0x748282 - Handle AudioHardware DVD removal - if (wParam != DBT_DEVICEREMOVECOMPLETE) { - break; - } - const auto eparams = reinterpret_cast(lParam); - if (eparams->dbch_devicetype != DBT_DEVTYP_VOLUME) { - break; - } - const auto idev = reinterpret_cast(lParam); - if ((idev->dbcv_flags & DBTF_MEDIA) == 0) { // Not a media drive? - break; - } - if (!AEAudioHardware.m_bInitialised || !AEAudioHardware.IsStreamingFromDVD()) { - break; - } - const auto dvletter = AEAudioHardware.GetDVDDriveLetter(); - if (dvletter < 'A' || (idev->dbcv_unitmask & (1 << dvletter)) == 0) { - break; - } - DEV_LOG("About to check CD drive"); - CTimer::SetCodePause(true); - if (CCutsceneMgr::IsRunning()) { - CCutsceneMgr::SkipCutscene(); - } - while (!AEAudioHardware.CheckDVD()) { - FrontEndMenuManager.NoDiskInDriveMessage(); - if (FrontEndMenuManager.m_bQuitGameNoDVD) { - DEV_LOG("Exiting game as Audio CD was not inserted"); - break; - } - } - DEV_LOG("GTA Audio DVD has been inserted"); - CTimer::SetCodePause(false); - break; - } - case WM_MOUSEFIRST: { //< 0x748323 - FrontEndMenuManager.m_nMousePosWinX = GET_X_LPARAM(lParam); - FrontEndMenuManager.m_nMousePosWinY = GET_Y_LPARAM(lParam); - return 0; - } - case WM_LBUTTONDOWN: - case WM_RBUTTONDOWN: - case WM_MBUTTONDOWN: { - SetCapture(hWnd); - return 0; - } - case WM_LBUTTONUP: - case WM_RBUTTONUP: - case WM_MBUTTONUP: { - ReleaseCapture(); - return 0; - } - case WM_MOUSEWHEEL: { - return 0; - } - case WM_SIZING: { // 0x74829E - if (RwInitialized) { - if (gGameState == GAME_STATE_IDLE) { - RsEventHandler(rsIDLE, (void*)TRUE); - } - } - - const auto wndrect = reinterpret_cast(lParam); - VERIFY(SetWindowPos( - hWnd, - HWND_TOP, - 0, 0, - wndrect->right - wndrect->left, wndrect->bottom - wndrect->top, - SWP_NOSIZE | SWP_NOMOVE - )); - - CPostEffects::SetupBackBufferVertex(); - - return 0; - } - } - return DefWindowProc(hWnd, uMsg, wParam, lParam); -} - -// Code from winmain, 0x748DCF -bool ProcessGameLogic(INT nCmdShow, MSG& Msg) { - if (RsGlobal.quit || FrontEndMenuManager.m_bStartGameLoading) { - return false; - } - - // Process Window messages - if (PeekMessage(&Msg, nullptr, 0u, 0u, PM_REMOVE | PM_NOYIELD)) { - if (Msg.message == WM_QUIT) { - return false; - } - TranslateMessage(&Msg); - DispatchMessage(&Msg); - return true; - } - - // Game is in background - if (!NO_FOREGROUND_PAUSE && !ForegroundApp) { - if (isForeground) { - isForeground = false; - } - Sleep(100); - return true; - } - - // TODO: Move this out from here (It's not platform specific at all) - switch (gGameState) { - case GAME_STATE_INITIAL: { - const auto ProcessSplash = [](bool isNVidia) { - CLoadingScreen::LoadSplashes(true, isNVidia); - CLoadingScreen::Init(true, true); - CLoadingScreen::DoPCTitleFadeOut(); - CLoadingScreen::DoPCTitleFadeIn(); - CLoadingScreen::Shutdown(); - }; - if (!FastLoadSettings.NoEAX) { - ProcessSplash(false); - } - if (!FastLoadSettings.NoNVidia) { - ProcessSplash(true); - } - ChangeGameStateTo(GAME_STATE_LOGO); - break; - } - case GAME_STATE_LOGO: { - if (!FastLoadSettings.NoLogo) { - if (!Windowed) { - VideoPlayer::Play(nCmdShow, "movies\\Logo.mpg"); - } - } - ChangeGameStateTo(FastLoadSettings.NoLogo ? GAME_STATE_TITLE : GAME_STATE_PLAYING_LOGO); - break; - } - case GAME_STATE_PLAYING_LOGO: - case GAME_STATE_PLAYING_INTRO: { // 0x748B17 - CPad::UpdatePads(); - auto* pad = CPad::GetPad(); - if ( Windowed - || ControlsManager.GetJoyButtonJustDown() - || pad->NewState.CheckForInput() - || CPad::IsMouseLButtonPressed() - || CPad::IsEnterJustPressed() - || pad->IsStandardKeyJustPressed(VK_SPACE) - || CPad::IsMenuKeyJustPressed() - || CPad::IsTabJustPressed() - ) { - ChangeGameStateTo([] { - switch (gGameState) { - case GAME_STATE_PLAYING_LOGO: return GAME_STATE_TITLE; - case GAME_STATE_PLAYING_INTRO: return GAME_STATE_FRONTEND_LOADING; - default: NOTSA_UNREACHABLE(); - } - }()); - } - break; - } - case GAME_STATE_TITLE: { - if (!FastLoadSettings.NoTitleOrIntro) { - VideoPlayer::Shutdown(); - VideoPlayer::Play(nCmdShow, FrontEndMenuManager.GetMovieFileName()); - } - ChangeGameStateTo(FastLoadSettings.NoTitleOrIntro ? GAME_STATE_FRONTEND_LOADING : GAME_STATE_PLAYING_INTRO); - break; - } - case GAME_STATE_FRONTEND_LOADING: { - VideoPlayer::Shutdown(); - CLoadingScreen::Init(true, false); - if (!FastLoadSettings.NoCopyright) { - CLoadingScreen::DoPCTitleFadeOut(); - } - if (!CGame::InitialiseEssentialsAfterRW()) { - RsGlobal.quit = true; - } - CGame::InitialiseCoreDataAfterRW(); - ChangeGameStateTo(GAME_STATE_FRONTEND_LOADED); - anisotropySupportedByGFX = (RwD3D9GetCaps()->RasterCaps & D3DPRASTERCAPS_ANISOTROPY) != 0; // todo: func - break; - } - case GAME_STATE_FRONTEND_LOADED: { - FrontEndMenuManager.m_bActivateMenuNextFrame = true; - FrontEndMenuManager.m_bMainMenuSwitch = true; - if (VideoModeNotSelected) { - FrontEndMenuManager.m_nPrefsVideoMode = FrontEndMenuManager.m_nDisplayVideoMode = gCurrentVideoMode; - VideoModeNotSelected = false; - } - ChangeGameStateTo(GAME_STATE_FRONTEND_IDLE); - if (FastLoadSettings.NoCopyright) { - CLoadingScreen::SkipCopyrightSplash(); - } else { - CLoadingScreen::DoPCTitleFadeIn(); - } - break; - } - case GAME_STATE_FRONTEND_IDLE: { // 0x748CB2 - WINDOWPLACEMENT wndpl{ .length = sizeof(WINDOWPLACEMENT) }; - VERIFY(GetWindowPlacement(PSGLOBAL(window), &wndpl)); - if (FastLoadSettings.ShouldLoadSaveGame()) { - RsEventHandler(rsFRONTENDIDLE, nullptr); // We need to still run the frontend processing once because it has some important stuff - if ((GetAsyncKeyState(FastLoadSettings.SkipSaveGameLoadKey) & 0xF000) == 0) { - FastLoadSettings.StartGame(FastLoadSettings.SaveGameToLoad); // Load game - } - FastLoadSettings.TriedLoadingSaveGame = true; - } else if (FastLoadSettings.RenderAtAllTimes || wndpl.showCmd != SW_SHOWMINIMIZED) { - RsEventHandler(rsFRONTENDIDLE, nullptr); - } - if (FrontEndMenuManager.m_bMenuActive && !FrontEndMenuManager.m_bLoadingData) { - break; - } - ChangeGameStateTo(GAME_STATE_LOADING_STARTED); - if (!FrontEndMenuManager.m_bLoadingData) { - break; - } - NOTSA_SWCFALLTHRU; // Fall down and start loading - } - case GAME_STATE_LOADING_STARTED: { - if (!FastLoadSettings.NoLoadingTune) { - AudioEngine.StartLoadingTune(); - } - - InitialiseGame(); - ChangeGameStateTo(GAME_STATE_IDLE); - FrontEndMenuManager.m_bMainMenuSwitch = false; - - AudioEngine.InitialisePostLoading(); - break; - } - case GAME_STATE_IDLE: { - if (!RwInitialized) - break; - - auto v9_1 = 1000.0f / (float)RsGlobal.frameLimit; - auto v9_2 = (float)CTimer::GetCurrentTimeInCycles() / (float)CTimer::GetCyclesPerMillisecond(); - if (!FrontEndMenuManager.m_bPrefsFrameLimiter && CReplay::Mode != eReplayMode::MODE_PLAYBACK && !AudioEngine.IsBeatInfoPresent() || v9_1 < v9_2) { - RsEventHandler(rsIDLE, (void*)true); - } - break; - } - } - - if (!isForeground) { - isForeground = true; - } - - return true; -} - -// Code from winmain, 0x7489FB -void MainLoop(INT nCmdShow, MSG& Msg) { - bool bNewGameFirstTime = false; - while (true) { - RwInitialized = true; - - RwV2d pos{ SCREEN_WIDTH / 2.0f, SCREEN_HEIGHT / 2.0f }; - RsMouseSetPos(&pos); - - gamma.Init(); - - // Game logic main loop - while (ProcessGameLogic(nCmdShow, Msg)); - - // 0x748DDA - RwInitialized = false; - FrontEndMenuManager.UnloadTextures(); - if (!FrontEndMenuManager.m_bStartGameLoading) { - break; - } - - // load game - CCheat::ResetCheats(); - CTimer::Stop(); - - if (FrontEndMenuManager.m_bLoadingData) { - CGame::ShutDownForRestart(); - CGame::InitialiseWhenRestarting(); - FrontEndMenuManager.m_bLoadingData = false; - } else if (bNewGameFirstTime) { - CTimer::Stop(); - ChangeGameStateTo( - FrontEndMenuManager.m_nGameState != 1 - ? GAME_STATE_LOADING_STARTED - : GAME_STATE_FRONTEND_LOADED - ); - } else { - CCheat::ResetCheats(); - CGame::ShutDownForRestart(); - CTimer::Stop(); - CGame::InitialiseWhenRestarting(); - } - - bNewGameFirstTime = false; - FrontEndMenuManager.m_nGameState = 0; - FrontEndMenuManager.m_bStartGameLoading = false; - } - -} - -// 0x748710 -INT WINAPI __WinMain(HINSTANCE instance, HINSTANCE hPrevInstance, LPSTR cmdLine, INT nCmdShow) { - SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0u, nullptr, 2); - if (IsAlreadyRunning()) { - return false; - } - - auto initializeEvent = RsEventHandler(rsINITIALIZE, nullptr); - if (rsEVENTERROR == initializeEvent) { - return false; - } - - if (!InitApplication(instance)) { - return false; - } - - cmdLine = GetCommandLine(); - int argc; - char** argv = CommandLineToArgv(cmdLine, &argc); - for (int i = 0; i < argc; i++) { - RsEventHandler(rsPREINITCOMMANDLINE, argv[i]); - } - - PSGLOBAL(window) = InitInstance(instance); - if (!PSGLOBAL(window)) { - return false; - } - PSGLOBAL(instance) = instance; - - // 0x7487CF - VERIFY(WinInput::Initialise()); - - ControlsManager.InitDefaultControlConfigMouse(WinInput::GetMouseState(), !FrontEndMenuManager.m_nController); - - // 0x748847 - if (RsEventHandler(rsRWINITIALIZE, PSGLOBAL(window)) == rsEVENTERROR) { - DestroyWindow(PSGLOBAL(window)); - RsEventHandler(rsTERMINATE, nullptr); - return false; - } - - // 0x7488EE - for (auto i = 0; i < argc; i++) { - RsEventHandler(rsCOMMANDLINE, argv[i]); - } - - if (MultipleVideoModes || PSGLOBAL(fullScreen)) { - SetWindowLongPtr(PSGLOBAL(window), GWL_STYLE, (LONG_PTR)WS_POPUP); - SetWindowPos(PSGLOBAL(window), nullptr, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED); - } - - RwRect rect{ 0, 0, RsGlobal.maximumWidth, RsGlobal.maximumHeight }; - RsEventHandler(rsCAMERASIZE, &rect); - - SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 0u, nullptr, 2u); - SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 0u, nullptr, 2u); - SystemParametersInfo(SPI_SETLOWPOWERACTIVE, 0u, nullptr, 2u); - STICKYKEYS pvParam { .cbSize = sizeof(STICKYKEYS) }; - SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS), &pvParam, 2u); - STICKYKEYS pvParam1 = { .cbSize = sizeof(STICKYKEYS), .dwFlags = SKF_TWOKEYSOFF }; - SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &pvParam1, 2u); - - ShowWindow(PSGLOBAL(window), nCmdShow); - UpdateWindow(PSGLOBAL(window)); - - // 0x748995 - CFileMgr::SetDirMyDocuments(); - if (auto* file = CFileMgr::OpenFile("gta_sa.set", "rb")) { - if (!ControlsManager.LoadSettings(file)) { - ControlsManager.ReinitControls(); - } - CFileMgr::CloseFile(file); - } - CFileMgr::SetDir(""); - - SetErrorMode(SEM_FAILCRITICALERRORS); - - // 0x7489FB - MSG Msg; - MainLoop(nCmdShow, Msg); - - // if game is loaded, shut it down - if (gGameState == GAME_STATE_IDLE) { - CGame::Shutdown(); - } - - // now quit 0x748E75 - AudioEngine.Shutdown(); - FreeVideoModeList(); - RsEventHandler(rsRWTERMINATE, nullptr); - DestroyWindow(PSGLOBAL(window)); - RsEventHandler(rsTERMINATE, nullptr); - free(argv); - ShowCursor(true); - - SystemParametersInfo(SPI_SETSTICKYKEYS, sizeof(STICKYKEYS), &pvParam, 2u); - SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 1u, nullptr, 2u); // TODO: GUID_VIDEO_POWERDOWN_TIMEOUT - SystemParametersInfo(SPI_SETLOWPOWERACTIVE, 1u, nullptr, 2u); - SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 1u, nullptr, 2u); - // nullsub_0x72F3C0() - SetErrorMode(0); - - return Msg.wParam; -} - -void Win32InjectHooks() { - RH_ScopedCategory("Win"); - RH_ScopedNamespaceName("Win"); - - RH_ScopedGlobalInstall(IsForegroundApp, 0x746060); - RH_ScopedGlobalInstall(IsAlreadyRunning, 0x7468E0); - RH_ScopedGlobalInstall(CommandLineToArgv, 0x746480, {.reversed = false}); - - RH_ScopedGlobalInstall(GTATranslateShiftKey, 0x747CD0); - RH_ScopedGlobalInstall(GTATranslateKey, 0x747820); - RH_ScopedGlobalInstall(__MainWndProc, 0x747EB0); - - // Unhooking these 2 after the game has started will do nothing - RH_ScopedGlobalInstall(__WinMain, 0x748710); - RH_ScopedGlobalInstall(InitInstance, 0x745560); - - WinPsInjectHooks(); - WinInput::InjectHooks(); -} diff --git a/source/app/platform/win/win.h b/source/app/platform/win/win.h deleted file mode 100644 index 8c54f816e3..0000000000 --- a/source/app/platform/win/win.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define APP_CLASS "Grand theft auto San Andreas" - -//! Win32 fail check (returns from function automagically) -#define WIN_FCHECK(x) do { \ - if (HRESULT hr = (x); FAILED(hr)) { \ - DEV_LOG(TEXT("FAILED(hr=0x{:x}) in ") TEXT(#x) TEXT("\n"), (size_t)(hr)); \ - return; \ - } \ - } while (0) \ - -static bool& anisotropySupportedByGFX = *(bool*)0xC87FFC; -static bool& isForeground = *(bool*)0xC920EC; -static bool& Windowed = *(bool*)0xC920CC; - -void Win32InjectHooks(); - -#define IDD_DIALOG1 104 -#define IDC_DEVICESEL 1000 -#define IDC_VIDMODE 1001 -#define IDEXIT 1002 -#define IDC_SELECTDEVICE 1005 - -#define IDI_MAIN_ICON 1042 diff --git a/source/app/platform/win/win_impl.cpp b/source/app/platform/win/win_impl.cpp deleted file mode 100644 index 6194b48978..0000000000 --- a/source/app/platform/win/win_impl.cpp +++ /dev/null @@ -1,233 +0,0 @@ -#include "StdInc.h" - -#include "win.h" - -#include "platform.h" -#include "LoadingScreen.h" - -void WinPsInjectHooks() { - RH_ScopedCategory("Win"); - RH_ScopedNamespaceName("Ps"); - - RH_ScopedGlobalInstall(psInitialize, 0x747420, {.reversed = false}); - RH_ScopedGlobalInstall(psTerminate, 0x7458A0); - RH_ScopedGlobalInstall(psWindowSetText, 0x7451B0); - RH_ScopedGlobalInstall(psErrorMessage, 0x7451D0); - RH_ScopedGlobalInstall(psWarningMessage, 0x7451F0); - RH_ScopedGlobalInstall(psCameraBeginUpdate, 0x745210); - RH_ScopedGlobalInstall(psCameraShowRaster, 0x745240); - RH_ScopedGlobalInstall(psTimer, 0x745270); - RH_ScopedGlobalInstall(psGrabScreen, 0x7452B0); - RH_ScopedGlobalInstall(psMouseSetVisibility, 0x7453E0); - RH_ScopedGlobalInstall(psMouseSetPos, 0x7453F0); - RH_ScopedGlobalInstall(psPathnameCreate, 0x745470); - RH_ScopedGlobalInstall(psPathnameDestroy, 0x7454E0); - RH_ScopedGlobalInstall(psPathGetSeparator, 0x745500); - RH_ScopedGlobalInstall(psInstallFileSystem, 0x745520); - RH_ScopedGlobalInstall(psNativeTextureSupport, 0x745530); - RH_ScopedGlobalInstall(psDebugMessageHandler, 0x745540); - RH_ScopedGlobalInstall(psAlwaysOnTop, 0x7458B0); - RH_ScopedGlobalInstall(psSelectDevice, 0x746190, {.reversed = false}); -} - -// 0x747420 -bool psInitialize() { - return plugin::CallAndReturn(); -} - -// 0x7458A0 -void psTerminate() { - // NOP -} - -// 0x7451B0 -void psWindowSetText(const char* str) { - SetWindowTextA(PSGLOBAL(window), str); -} - -// 0x7451D0 -void psErrorMessage(const char* str) { - MessageBoxA(nullptr, str, RsGlobal.appName, MB_ICONERROR | MB_TASKMODAL | MB_TOPMOST); -} - -// 0x7451F0 -void psWarningMessage(const char* str) { - MessageBoxA(nullptr, str, RsGlobal.appName, MB_ICONWARNING | MB_TASKMODAL | MB_TOPMOST); -} - -// 0x745210 -bool psCameraBeginUpdate(RwCamera* camera) { - if (RwCameraBeginUpdate(Scene.m_pRwCamera)) { - return true; - } - RsEventHandler(rsACTIVATE, nullptr); - return false; -} - -// 0x745240 -RwCamera* psCameraShowRaster(RwCamera* camera) { - auto flags = FrontEndMenuManager.m_bPrefsFrameLimiter || CLoadingScreen::m_bActive ? rwRASTERFLIPWAITVSYNC : rwRASTERFLIPDONTWAIT; - return RwCameraShowRaster(camera, PSGLOBAL(window), flags); -} - -// 0x745270 -uint32 psTimer() { - return OS_TimeMS(); -} - -// 0x7452B0 -RwImage* psGrabScreen(RwCamera* camera) { - auto* device = static_cast(RwD3D9GetCurrentD3DDevice()); - assert(device); - - D3DDISPLAYMODE displayMode{}; - VERIFY(SUCCEEDED(device->GetDisplayMode(0, &displayMode))); - - IDirect3DSurface9* surface = nullptr; - VERIFY(SUCCEEDED(device->CreateOffscreenPlainSurface(displayMode.Width, displayMode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nullptr))); - VERIFY(SUCCEEDED(device->GetFrontBufferData(0, surface))); - - D3DLOCKED_RECT lockedRect{}; -#ifndef FIX_BUGS - // It's not needed as ClientToScreen func works with fullscreen mode. - if (PSGLOBAL(fullScreen)) { // todo: Doesn't work properly with III.VC.SA.WindowedMode.asi - VERIFY(SUCCEEDED(surface->LockRect(&lockedRect, nullptr, D3DLOCK_READONLY))); - } else { -#endif - RECT rect; -#ifdef FIX_BUGS - // SA code gets the whole window of the game, which includes the titlebar etc. - // - // BUG: There should be bugs for older versions of Windows IIRC. - // One example would be Vista version of the func doesn't count Aero effects of windows. - // - // TODO: Test with dual monitors etc. - - GetClientRect(PSGLOBAL(window), &rect); - - // GetClientRect returns relative positions unlike GetWindowRect. - // i.e. will return { 0, 0, width, height }. - ClientToScreen(PSGLOBAL(window), (POINT*)(&rect)); // kinda hacky but should work. - rect.right += rect.left; - rect.bottom += rect.top; -#else - GetWindowRect(PSGLOBAL(window), &rect); -#endif - displayMode.Height = rect.bottom - rect.top; - displayMode.Width = rect.right - rect.left; - VERIFY(SUCCEEDED(surface->LockRect(&lockedRect, &rect, D3DLOCK_READONLY))); -#ifndef FIX_BUGS - } -#endif - - RwImage* image = RwImageCreate(int32(displayMode.Width), int32(displayMode.Height), 32); - if (image) { - RwImageAllocatePixels(image); - - auto* pixels = (RwRGBA*)RwImageGetPixels(image); - auto* imagePixels = (uint8*)lockedRect.pBits; - assert(pixels && imagePixels); - - for (auto h = 0u; h < displayMode.Height; h++) { - for (auto w = 0u; w < displayMode.Width; w++) { - pixels->red = imagePixels[sizeof(RwRGBA) * w + 2]; - pixels->green = imagePixels[sizeof(RwRGBA) * w + 1]; - pixels->blue = imagePixels[sizeof(RwRGBA) * w + 0]; - pixels->alpha = 255; - pixels++; - } - imagePixels += lockedRect.Pitch; - } - } - - { // FIX_BUGS - surface->UnlockRect(); - surface->Release(); - delete surface; - } - - return image; -} - -// 0x7453E0 -void psMouseSetVisibility(bool visible) { - ::ShowCursor(visible); -} - -// 0x7453F0 -void psMouseSetPos(RwV2d* pos) { - POINT point = { .x = LONG(pos->x), .y = LONG(pos->y) }; - ::ClientToScreen(PSGLOBAL(window), &point); - ::SetCursorPos(point.x, point.y); - PSGLOBAL(lastMousePos) = *pos; -} - -// 0x745470 -char* psPathnameCreate(const char* buffer) { - const auto pathSize = std::strlen(buffer) + 1u; - auto path = (char*)CMemoryMgr::Malloc(pathSize); - if (path) { - strcpy_s(path, pathSize, buffer); - - while (auto ch = std::strchr(path, '/')) { - *ch = psPathGetSeparator(); - } - } - - return path; -} - -// 0x7454E0 -void psPathnameDestroy(char* buffer) { - if (buffer) { - RwFree(buffer); - } -} - -// 0x745500 -char psPathGetSeparator() { - return '\\'; -} - -// 0x745520 -bool psInstallFileSystem() { - return true; -} - -// 0x745530 -bool psNativeTextureSupport() { - return RwD3D9DeviceSupportsDXTTexture(); -} - -// 0x745540 -RsEventStatus psDebugMessageHandler(RsEvent event, void* param) { - if (rsINITDEBUG == event) { - RwDebugSetHandler(psDebugMessageHandler); -#if defined(DEBUG) && defined(RWTRACE) - RwDebugSetTraceState(true); -#else - RwDebugSetTraceState(false); -#endif - } - - return rsEVENTNOTPROCESSED; -} - -// 0x7458B0 -bool psAlwaysOnTop(bool alwaysOnTop) { - RECT winRect; - - HWND hwnd = PSGLOBAL(window); - GetWindowRect(hwnd, &winRect); - - if (alwaysOnTop) { - return (bool)SetWindowPos(hwnd, HWND_TOPMOST, winRect.left, winRect.top, winRect.right - winRect.left, winRect.bottom - winRect.top, 0); - } else { - return (bool)SetWindowPos(hwnd, HWND_NOTOPMOST, winRect.left, winRect.top, winRect.right - winRect.left, winRect.bottom - winRect.top, 0); - } -} - -// 0x746190 -bool psSelectDevice() { - return plugin::CallAndReturn(); -} diff --git a/source/config.h b/source/config.h index 386e2e7102..b3241a8890 100644 --- a/source/config.h +++ b/source/config.h @@ -1,14 +1,30 @@ #pragma once -#define MORE_LANGUAGES -#define USE_ORIGINAL_CODE FALSE -#define USE_BUILD_INFORMATION +// Disables many extensions and non-vanilla features +//#define BAREBONES + #define BUILD_PC +#define USE_BUILD_INFORMATION + +#undef TRACY_ENABLE // TODO + +#ifndef BAREBONES + +#define MORE_LANGUAGES +#define USE_ORIGINAL_CODE #define USE_ADDITIONAL_CHEATS +// Enables opcodes from III/VC that are not implemented in SA +#define IMPLEMENT_UNSUPPORTED_OPCODES + +// Extensions +#define EXT_FAST_LOADER + +// do not uncomment -- doesn't work rn +// #define USE_OPENAL + +#endif // !BAREBONES + #ifdef BUILD_PC //#define USE_EU_STUFF #endif - -// Enables opcodes from III/VC that are not implemented in SA -#define IMPLEMENT_UNSUPPORTED_OPCODES diff --git a/source/dllmain.cpp b/source/dllmain.cpp index 4fccc1e9d4..ac89676997 100644 --- a/source/dllmain.cpp +++ b/source/dllmain.cpp @@ -3,9 +3,11 @@ // PVS-Studio Static Code Analyzer for C, C++, C#, and Java: https://pvs-studio.com #include "StdInc.h" - #include "config.h" +#include "extensions/CommandLine.h" +#include "extensions/Configuration.hpp" + void InjectHooksMain(HMODULE hThisDLL); void DisplayConsole() @@ -25,33 +27,20 @@ void WaitForDebugger() { } } -namespace CommandLineArguments { +static constexpr auto DEFAULT_INI_FILENAME = "gta-reversed.ini"; -std::vector Get() { - std::vector out; - int numArgs{0}; - LPWSTR* szArgs = CommandLineToArgvW(GetCommandLineW(), &numArgs); - out.reserve(numArgs); - if (szArgs) { - for (int i = 0; i < numArgs; i++) { - out.emplace_back(szArgs[i]); - } - } - LocalFree(szArgs); - return out; -} +#include "extensions/Configs/FastLoader.hpp" +#include "extensions/Configs/WindowedMode.hpp" -void Process() { - using namespace std::literals; - const auto args = Get(); - for (const auto& arg : args) { - if (arg == L"--debug") { - WaitForDebugger(); - } - } -} +void LoadConfigurations() { + // Firstly load the INI into the memory. + g_ConfigurationMgr.Load(DEFAULT_INI_FILENAME); -} // namespace CommandLineArguments + // Then load all specific configurations. + g_FastLoaderConfig.Load(); + g_WindowedModeConfig.Load(); + // ... +} BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { @@ -67,7 +56,13 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReser } DisplayConsole(); - CommandLineArguments::Process(); + CommandLine::Load(__argc, __argv); + + if (CommandLine::waitForDebugger) + WaitForDebugger(); + + LoadConfigurations(); + InjectHooksMain(hModule); break; } diff --git a/source/extensions/CommandLine.cpp b/source/extensions/CommandLine.cpp new file mode 100644 index 0000000000..7b7d40dcd8 --- /dev/null +++ b/source/extensions/CommandLine.cpp @@ -0,0 +1,76 @@ +#include "StdInc.h" +#include "CommandLine.h" + +#include +#include +#include + +namespace CommandLine { + bool unhookAll{false}; + std::vector unhookSome{}; + std::vector unhookExcept{}; + + bool waitForDebugger{false}; + + void ProcessArgument(const char* arg) { + const auto str = std::string_view{arg, std::strlen(arg)}; + + if (str == "--debug") { + waitForDebugger = true; + return; + } + + if (str == "--unhook-all") { + if (!unhookExcept.empty()) { + NOTSA_LOG_WARN("--unhook-except has been called previously, unhook-all will be effective."); + unhookExcept.clear(); // we aren't gonna use it. + } + if (!unhookSome.empty()) { + NOTSA_LOG_WARN("--unhook-some has been called previously, unhook-all will be effective."); + unhookSome.clear(); // we aren't gonna use it. + } + unhookAll = true; + return; + } + + if (str.starts_with("--unhook-except=")) { + if (unhookAll) { + NOTSA_LOG_WARN("--unhook-all has been called previously, unhook-all will be effective."); + } else { + if (!unhookSome.empty()) { + NOTSA_LOG_WARN("--unhook has been called previously, unhook-except will be effective."); + unhookSome.clear(); // we aren't gonna use it. + } + + for (auto hook : SplitStringView(str.substr(str.find('=') + 1), ",")) { + unhookExcept.emplace_back(std::move(hook)); + } + } + return; + } + + if (str.starts_with("--unhook=")) { + if (unhookAll) { + NOTSA_LOG_WARN("--unhook-all has been called previously, unhook-all will be effective."); + } else if (!unhookExcept.empty()) { + NOTSA_LOG_WARN("--unhook-except has been called previously, unhook-except will be effective."); + } else { + for (auto hook : SplitStringView(str.substr(str.find('=') + 1), ",")) { + unhookSome.emplace_back(std::move(hook)); + } + } + return; + } + + NOTSA_LOG_WARN("Unknown argument '{}'", str); + } + + void CommandLine::Load(int argc, char** argv) { + if (--argc <= 0) + return; // No need to process, there is no argument supplied. + ++argv; // Skip first 'argument' which is the exec path. + + for (auto& arg : std::span{argv, (size_t)argc}) + ProcessArgument(arg); + } +} // namespace CommandLine diff --git a/source/extensions/CommandLine.h b/source/extensions/CommandLine.h new file mode 100644 index 0000000000..dd1d7019b9 --- /dev/null +++ b/source/extensions/CommandLine.h @@ -0,0 +1,13 @@ +#pragma once + +namespace CommandLine { + // Hook features + extern bool unhookAll; + extern std::vector unhookSome; + extern std::vector unhookExcept; + + // Debug features + extern bool waitForDebugger; + + void Load(int argc, char** argv); +} // namespace CommandLine; diff --git a/source/extensions/Configs/FastLoader.hpp b/source/extensions/Configs/FastLoader.hpp new file mode 100644 index 0000000000..cea41667e1 --- /dev/null +++ b/source/extensions/Configs/FastLoader.hpp @@ -0,0 +1,101 @@ +#pragma once +#include +#include "app_debug.h" +#include "AudioEngine.h" +#include "Common.h" +#include "FileMgr.h" +#include "GenericGameStorage.h" +#include "MenuManager.h" + +#include "extensions/Configuration.hpp" + +inline struct FastLoaderConfig { + bool TriedLoadingSaveGame = false; //< [Runtime Var] Whenever `SaveGameToLoad` was tried to be loaded + + INI_CONFIG_SECTION("FastLoader"); + + int32 SaveGameToLoad = -2; //< -2 - Don't load, -1 = Load first available, 0 = unused, 1 <= - load from save slot + uint32 SkipSaveGameLoadKey = VK_CONTROL; //< Skip auto-loading save game (If enabled) + uint32 SoundDelay = 50; + + bool NoEAX = false; //< Skip EAX splash + bool NoNVidia = false; //< Skip nVidia splash + bool NoLogo = false; //< Skip Logo.mpg + bool NoTitleOrIntro = false; //< Skip GTAtitles.mpg + bool NoCopyright = false; //< Skip Copyright screen + bool NoFading = false; //< Skip fading (takes quite a bit of time) + bool NoLoadScreen = false; //< Skip load pre-game screen + bool NoLoadBar = false; //< Skip load bar in pre-game load screen + bool NoLoadingTune = false; //< Skip GTA theme music + bool NoDbgLogScreens = true; //< Don't display [notsa] loading screen debug messages to console + bool RenderAtAllTimes = true; //< Render even when minimized + float ScreenChangeTime = 5.f; //< Time to change splash screens in the loading screen (seconds?) + + //! If there's a game to load, but not loaded yet... + bool ShouldLoadSaveGame() const { return SaveGameToLoad >= -1 && !TriedLoadingSaveGame; } + + //! Start the game from a slot + //! `slot` can have the same values as `SaveGameToLoad` + bool StartGame(int32 slot) { + const auto CheckIfSaveFileExists = [](int32 slot) { + CFileMgr::SetDirMyDocuments(); + const bool exists = std::filesystem::exists(std::format("GTASAsf{}.b", slot + 1)); + CFileMgr::ChangeDir(""); + return exists; + }; + + if (slot == -1) { // Find first valid slot and load that + for (auto i = 0u; i < MAX_SAVEGAME_SLOTS; i++) { + if (CheckIfSaveFileExists(i)) { + return StartGame(i); // Load this slot + } + } + DEV_LOG("Couldn't find any savegame to load!"); + return false; + } + + // Load game from slot + assert(slot > 0); + if (!CheckIfSaveFileExists(slot - 1)) { + DEV_LOG("Save slot {} is empty!", slot); + return false; + } + DEV_LOG("Loading game from slot ({})", slot); + + if (!NoLoadingTune) { + for (size_t i = 0; i < SoundDelay; i++) { + AudioEngine.ServiceLoadingTune(1.f); + } + } + + // Actually load the game (By simulating the user going in the menu and doing it) + FrontEndMenuManager.SimulateGameLoad(false, slot - 1); + + return true; + } + + void Load() { +#ifdef EXT_FAST_LOADER + if (!GET_INI_CONFIG_VALUE("Enable", true)) { + return; + } + + STORE_INI_CONFIG_VALUE(SaveGameToLoad, -1); + STORE_INI_CONFIG_VALUE(SkipSaveGameLoadKey, VK_CONTROL); + STORE_INI_CONFIG_VALUE(SoundDelay, 50); + + STORE_INI_CONFIG_VALUE(NoEAX, true); + STORE_INI_CONFIG_VALUE(NoNVidia, true); + STORE_INI_CONFIG_VALUE(NoLogo, true); + STORE_INI_CONFIG_VALUE(NoTitleOrIntro, true); + STORE_INI_CONFIG_VALUE(NoCopyright, true); + STORE_INI_CONFIG_VALUE(NoFading, true); + STORE_INI_CONFIG_VALUE(NoLoadScreen, true); + STORE_INI_CONFIG_VALUE(NoLoadBar, false); + STORE_INI_CONFIG_VALUE(NoLoadingTune, true); + STORE_INI_CONFIG_VALUE(NoDbgLogScreens, true); + STORE_INI_CONFIG_VALUE(RenderAtAllTimes, true); + STORE_INI_CONFIG_VALUE(ScreenChangeTime, 5.f); +#endif + } +} g_FastLoaderConfig{}; diff --git a/source/extensions/Configs/WindowedMode.hpp b/source/extensions/Configs/WindowedMode.hpp new file mode 100644 index 0000000000..125d9a81c9 --- /dev/null +++ b/source/extensions/Configs/WindowedMode.hpp @@ -0,0 +1,17 @@ +#pragma once +#include +#include "extensions/Configuration.hpp" + +inline struct WindowedModeConfig { + INI_CONFIG_SECTION("WindowedMode"); + + uint32 WindowWidth{800}; + uint32 WindowHeight{600}; + bool Centered{true}; + + void Load() { + STORE_INI_CONFIG_VALUE(WindowWidth, 800); + STORE_INI_CONFIG_VALUE(WindowHeight, 600); + STORE_INI_CONFIG_VALUE(Centered, true); + } +} g_WindowedModeConfig{}; diff --git a/source/extensions/Configuration.hpp b/source/extensions/Configuration.hpp new file mode 100644 index 0000000000..070ae518be --- /dev/null +++ b/source/extensions/Configuration.hpp @@ -0,0 +1,120 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "app_debug.h" + +static inline class { + using IniContent = std::unordered_map>; + IniContent m_content{}; + + struct NewConfigFile { + std::ofstream out; + std::string lastSection; + }; + std::optional m_createdIniFile{}; + +public: + template + Ret GetIniValue(const std::string& section, const std::string& key, Ret&& _default) { + if (m_content.empty() || !m_content.contains(section) || !m_content[section].contains(key)) { + if (m_createdIniFile.has_value()) { + if (std::exchange(m_createdIniFile->lastSection, section) != section) + m_createdIniFile->out << "\n[" << section << "]\n"; + + if constexpr (std::is_same_v) + m_createdIniFile->out << key << " = " << std::boolalpha << _default << "\n"; + else + m_createdIniFile->out << key << " = " << _default << "\n"; + } + return _default; + } + + const auto& str = m_content[section][key]; + if constexpr (std::is_same_v) { + return str; + } else if constexpr (std::is_same_v) { + return str == "true" || str == "TRUE" || str == "1"; + } else if constexpr (notsa::is_standard_integer || std::is_floating_point_v) { + Ret rt{}; + + auto [_, ec] = std::from_chars(str.data(), str.data() + str.size(), rt); + if (ec != std::errc{}) + return _default; + + return rt; + } else { + return _default; + } + } + + void Load(const std::filesystem::path& fileName) { + if (!std::filesystem::exists(fileName)) { + DEV_LOG("Configuration file {} doesn't exist! A new one will be generated.", fileName.string()); + DEV_LOG("Using the provided config file with comments is recommended."); + + auto out = std::ofstream{fileName}; + out << "; GTA-Reversed configuration file generated at " << std::chrono::system_clock::now(); + m_createdIniFile = {std::move(out), ""}; + return; + } + + std::ifstream in(fileName); + assert(!in.bad()); + + ParseIniFile(in, m_content); + } + +private: + static void ParseIniFile(std::ifstream& file, IniContent& ini) { + std::string sectionName{""}; + std::string line; + while (std::getline(file, line)) { + const auto StripText = [](std::string& s) { + s.erase(0, s.find_first_not_of(" \t\n\r\f\v")); + s.erase(s.find_last_not_of(" \t\n\r\f\v") + 1); + }; + + if (const auto cf = line.find(';'); cf != std::string::npos) { + line.erase(cf); + } + + StripText(line); + + if (line.empty()) + continue; + + if (line.front() == '[' && line.back() == ']') { + sectionName = line.substr(1, line.size() - 2); + continue; + } + + if (const auto ef = line.find('='); ef != std::string::npos) { + auto key = line.substr(0, ef); + auto value = line.substr(ef + 1); + + StripText(key); + StripText(value); + + DEV_LOG("key: {} value: {}", key, value); + ini[sectionName][std::move(key)] = std::move(value); + continue; + } + + DEV_LOG("err: something wrong!"); + break; + } + } +} g_ConfigurationMgr; + +#define INI_CONFIG_SECTION(name) \ + static constexpr auto IniSectionName = name + +#define GET_INI_CONFIG_VALUE(key, _default) \ + g_ConfigurationMgr.GetIniValue(IniSectionName, key, _default) + +#define STORE_INI_CONFIG_VALUE(key_var, _default) \ + key_var = g_ConfigurationMgr.GetIniValue(IniSectionName, #key_var, _default) diff --git a/source/extensions/FixedFloat.hpp b/source/extensions/FixedFloat.hpp index 53dea32018..4b771574d5 100644 --- a/source/extensions/FixedFloat.hpp +++ b/source/extensions/FixedFloat.hpp @@ -1,17 +1,15 @@ #pragma once //! Fixed point number (With implicit conversion to float) -template - requires std::is_integral_v +template class FixedFloat { T value{}; public: constexpr FixedFloat() = default; - constexpr FixedFloat(float X) : value(static_cast(X * CompressValue)) {} - explicit constexpr FixedFloat(T X) : value(X) {} + constexpr FixedFloat(float v) : value(static_cast(v * CompressValue)) {} + template + constexpr FixedFloat(Y x) : value(x) {} - constexpr operator float() const { - return static_cast(value) / CompressValue; - } + constexpr operator float() const { return static_cast(value) / CompressValue; } }; diff --git a/source/extensions/FixedVector.hpp b/source/extensions/FixedVector.hpp index f9db269e57..1f1a5bda82 100644 --- a/source/extensions/FixedVector.hpp +++ b/source/extensions/FixedVector.hpp @@ -5,9 +5,8 @@ template struct FixedVector { constexpr FixedVector() = default; - constexpr FixedVector(float X, float Y, float Z) : x(X), y(Y), z(Z) {} constexpr FixedVector(CVector v3d) : x(v3d.x), y(v3d.y), z(v3d.z) {} - explicit constexpr FixedVector(T X, T Y, T Z) : x(X), y(Y), z(Z) {} + constexpr FixedVector(T X, T Y, T Z) : x(X), y(Y), z(Z) {} constexpr operator CVector() const { return CVector{ x, y, z }; } @@ -20,7 +19,7 @@ struct FixedVector2D { constexpr FixedVector2D() = default; constexpr FixedVector2D(float X, float Y) : x(X), y(Y) {} constexpr FixedVector2D(CVector2D v2d) : FixedVector2D{v2d.x, v2d.y} {} - explicit constexpr FixedVector2D(T X, T Y) : FixedVector2D{ x, y } {} + constexpr FixedVector2D(T X, T Y) : FixedVector2D{ x, y } {} constexpr operator CVector2D() const { return { x, y }; } diff --git a/source/extensions/HeapPtrArray.hpp b/source/extensions/HeapPtrArray.hpp new file mode 100644 index 0000000000..3f75f2274d --- /dev/null +++ b/source/extensions/HeapPtrArray.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include +#include + +// Dynamic array that frees all pointers on destruction. +// +// TODO: Make sure all instances of this to use new/delete +// allocators and remove the `UseCFree` flag. +template +class HeapPtrArray : public std::vector { + HeapPtrArray() = default; + ~HeapPtrArray() { + for (auto& ptr : *this) { + if constexpr (!UseCFree) { + delete ptr; + } else { + free(ptr); + } + } + } + + constexpr void push_back(const T*& value) { + assert(_CrtIsValidHeapPointer(value)); + std::vector::push_back(value); + } + + constexpr void push_back(T*&& value) { + assert(_CrtIsValidHeapPointer(value)); + std::vector::push_back(value); + } +}; diff --git a/source/extensions/Singleton.hpp b/source/extensions/Singleton.hpp new file mode 100644 index 0000000000..714b2c7cf0 --- /dev/null +++ b/source/extensions/Singleton.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +namespace notsa { +//! Simple (not thread safe) singleton class. Instance created on first call to `GetSingleton()` or manually by `CreateInstance()` +template +class Singleton { + static inline std::unique_ptr s_instance{}; +public: + Singleton() = default; + Singleton(const Singleton&) = delete; + Singleton& operator=(const Singleton&) = delete; + + //! Get current singleton instance (Create it if none) + static T& GetSingleton() { + if (!s_instance) { + CreateInstance(); + } + return *s_instance; + } + + //! Destroy current instance and create new + static void ResetSingleton() { + DestroySingleton(); + CreateInstance(); + } + + //! Create the singleton instance now + static void CreateInstance() { + assert(!s_instance); + s_instance = std::make_unique(); + } + + static void DestroySingleton() { + s_instance.reset(); + } + + static bool HasInstance() { + return s_instance != nullptr; + } +}; +}; // namespace notsa diff --git a/source/extensions/utility.hpp b/source/extensions/utility.hpp index 84680d0ca7..ed70297624 100644 --- a/source/extensions/utility.hpp +++ b/source/extensions/utility.hpp @@ -1,12 +1,10 @@ #pragma once -#include #include #include - +#include #include "Base.h" - namespace notsa { //template //struct basic_static_string { @@ -20,6 +18,12 @@ namespace notsa { //}; namespace rng = std::ranges; +//! [mostly] Works like C#'s `??` (null coalescing operator) or GCC's `?:` +template +T coalesce(T a, T b) { + return a ? a : b; +} + /*! * Much like std::stoi [and variants] but takes an `std::string_view` + in debug does error checking [unlike the C stuff] * @param str The string to convert @@ -121,7 +125,7 @@ struct NotIsNull { } }; -// Find first non-null value in range. If found it's returned, `null` otherwise. +//! Find first non-null value in range. If found it's returned, `null` otherwise. template> requires(std::is_pointer_v) T_Ret FirstNonNull(R&& range) { @@ -269,40 +273,12 @@ static constexpr void IterateFunction(auto&& functor) { } } -//! Simple (not thread safe) singleton class. Instance created on first call to `GetSingleton()`. -template -class Singleton { - static inline std::unique_ptr s_instance{}; -public: - Singleton() = default; - Singleton(const Singleton&) = delete; - Singleton& operator=(const Singleton&) = delete; - -public: - //! Get current singleton instance (Create it if none) - static T& GetSingleton() { - if (!s_instance) { - CreateSingleton(); - } - return *s_instance; - } - - //! Destroy current instance and create new - static void ResetSingleton() { - DestroySingleton(); - CreateSingleton(); - } - -private: - static void CreateSingleton() { - assert(!s_instance); - s_instance = std::make_unique(); - } +template +concept is_any_of_type_v = (std::same_as || ...); - static void DestroySingleton() { - s_instance.reset(); - } -}; +//! Check if the type is an integer type excluding bool and character types. +template +inline constexpr bool is_standard_integer = std::is_integral_v && !is_any_of_type_v; // Find value in a range then return a pointer of it. template diff --git a/source/game_sa/3dMarkers.cpp b/source/game_sa/3dMarkers.cpp index 47ca9df0fa..d46dff2ef4 100644 --- a/source/game_sa/3dMarkers.cpp +++ b/source/game_sa/3dMarkers.cpp @@ -95,6 +95,8 @@ void C3dMarkers::Shutdown() { // 0x725040 void C3dMarkers::Render() { + ZoneScoped; + static RwRGBAReal& ambient = *(RwRGBAReal*)0xC80444; // STATICREF static RwRGBAReal& directional = *(RwRGBAReal*)0xC80434; // STATICREF diff --git a/source/game_sa/Animation/AnimAssocDefinitions.cpp b/source/game_sa/Animation/AnimAssocDefinitions.cpp index 33a89aa3c5..fd3c0bc14a 100644 --- a/source/game_sa/Animation/AnimAssocDefinitions.cpp +++ b/source/game_sa/Animation/AnimAssocDefinitions.cpp @@ -6,7 +6,7 @@ // animsCount, anims #define awc(a) std::size(a), a -std::array CAnimManager::ms_aAnimAssocDefinitionsX = std::to_array({ // 0x8AA5A8 +std::array CAnimManager::ms_aAnimAssocDefinitionsX{{ // 0x8AA5A8 { "default", "ped", MODEL_MALE01, awc(aStdAnimations), aStdAnimDescs }, { "door", "ped", MODEL_INVALID, awc(aDoorAnimations), aDoorDescs }, { "bikes", "bikes", MODEL_MALE01, awc(aBikesAnimations), aBikesDescs }, @@ -125,5 +125,5 @@ std::array CAnimManager::ms_aAnimAss { "harrplaneanims", "rustler", MODEL_MALE01, awc(aHarrPlaneAnimations), aCarAnimDescs1 }, { "stdcarupright", "ped", MODEL_MALE01, awc(aStdCarAnimations), aCarAnimDescs2 }, { "nvadaplaneanims", "nevada", MODEL_MALE01, awc(aNevadaPlaneAnimations), aCarAnimDescs1 } -}); +}}; #undef awc diff --git a/source/game_sa/Animation/AnimBlendAssociation.cpp b/source/game_sa/Animation/AnimBlendAssociation.cpp index e605eeed62..fdbb45ddf0 100644 --- a/source/game_sa/Animation/AnimBlendAssociation.cpp +++ b/source/game_sa/Animation/AnimBlendAssociation.cpp @@ -316,3 +316,7 @@ void CAnimBlendAssociation::ReferenceAnimBlock() { CAnimBlendNode* CAnimBlendAssociation::GetNode(int32 nodeIndex) { return &m_pNodeArray[nodeIndex]; } + +float SClumpAnimAssoc::GetTimeProgress() const { + return m_fCurrentTime / m_pHierarchy->m_fTotalTime; +} diff --git a/source/game_sa/Animation/AnimBlendAssociation.h b/source/game_sa/Animation/AnimBlendAssociation.h index 0b95978371..88fa48b5bd 100644 --- a/source/game_sa/Animation/AnimBlendAssociation.h +++ b/source/game_sa/Animation/AnimBlendAssociation.h @@ -87,7 +87,8 @@ struct SClumpAnimAssoc { int16 m_nAnimId; uint16 m_nFlags; // TODO: use bitfield - float GetBlendAmount(float weight) { return IsPartial() ? m_fBlendAmount : m_fBlendAmount * weight; } + float GetTimeProgress() const; + float GetBlendAmount(float weight) const { return IsPartial() ? m_fBlendAmount : m_fBlendAmount * weight; } [[nodiscard]] bool IsRunning() const { return (m_nFlags & ANIMATION_STARTED) != 0; } [[nodiscard]] bool IsRepeating() const { return (m_nFlags & ANIMATION_LOOPED) != 0; } [[nodiscard]] bool IsPartial() const { return (m_nFlags & ANIMATION_PARTIAL) != 0; } @@ -124,7 +125,7 @@ class NOTSA_EXPORT_VTABLE CAnimBlendAssociation : public SClumpAnimAssoc { void SetCurrentTime(float currentTime); void SetDeleteCallback(void(*callback)(CAnimBlendAssociation*, void*), void* data = nullptr); void SetFinishCallback(void(*callback)(CAnimBlendAssociation*, void*), void* data = nullptr); - void Start(float currentTime); + void Start(float currentTime = 0.f); void SyncAnimation(CAnimBlendAssociation* syncWith); bool UpdateBlend(float mult); bool UpdateTime(float a1, float a2); diff --git a/source/game_sa/Animation/AnimBlendHierarchy.cpp b/source/game_sa/Animation/AnimBlendHierarchy.cpp index 0ecd035b35..6e084f35c4 100644 --- a/source/game_sa/Animation/AnimBlendHierarchy.cpp +++ b/source/game_sa/Animation/AnimBlendHierarchy.cpp @@ -28,7 +28,7 @@ void CAnimBlendHierarchy::InjectHooks() { CAnimBlendHierarchy::CAnimBlendHierarchy() { m_pSequences = nullptr; m_nSeqCount = 0; - m_bRunningCompressed = false; + m_bIsCompressed = false; m_bKeepCompressed = false; m_nAnimBlockId = -1; m_fTotalTime = 0.0f; @@ -44,7 +44,7 @@ CAnimBlendHierarchy::~CAnimBlendHierarchy() { // 0x4CF980 void CAnimBlendHierarchy::Shutdown() { RemoveAnimSequences(); - m_bRunningCompressed = false; + m_bIsCompressed = false; } // 0x4CF8E0 @@ -156,7 +156,7 @@ void CAnimBlendHierarchy::Uncompress() { CMemoryMgr::Free(oldFrameData); } - m_bRunningCompressed = false; + m_bIsCompressed = false; if (m_fTotalTime == 0.0f) { RemoveQuaternionFlips(); CalcTotalTime(); @@ -194,7 +194,7 @@ void CAnimBlendHierarchy::RemoveUncompressedData() { CMemoryMgr::Free(oldFrameData); } - m_bRunningCompressed = true; + m_bIsCompressed = true; } // 0x4CF800 diff --git a/source/game_sa/Animation/AnimBlendHierarchy.h b/source/game_sa/Animation/AnimBlendHierarchy.h index 7d3700218a..60af27cf02 100644 --- a/source/game_sa/Animation/AnimBlendHierarchy.h +++ b/source/game_sa/Animation/AnimBlendHierarchy.h @@ -13,7 +13,7 @@ class CAnimBlendHierarchy { uint32 m_hashKey; CAnimBlendSequence* m_pSequences; uint16 m_nSeqCount; - bool m_bRunningCompressed; + bool m_bIsCompressed; bool m_bKeepCompressed; int32 m_nAnimBlockId; float m_fTotalTime; diff --git a/source/game_sa/Animation/AnimManager.cpp b/source/game_sa/Animation/AnimManager.cpp index d61651970c..3aec64318f 100644 --- a/source/game_sa/Animation/AnimManager.cpp +++ b/source/game_sa/Animation/AnimManager.cpp @@ -7,6 +7,8 @@ #include "StdInc.h" +#include + #include "AnimManager.h" #include "AnimAssocDescriptions.h" @@ -14,37 +16,37 @@ void CAnimManager::InjectHooks() { RH_ScopedClass(CAnimManager); RH_ScopedCategory("Animation"); - RH_ScopedInstall(Initialise, 0x5BF6B0, { .reversed = false }); - RH_ScopedInstall(ReadAnimAssociationDefinitions, 0x5BC910, { .reversed = false }); + RH_ScopedInstall(Initialise, 0x5BF6B0); + RH_ScopedInstall(ReadAnimAssociationDefinitions, 0x5BC910); RH_ScopedInstall(Shutdown, 0x4D4130); RH_ScopedOverloadedInstall(GetAnimationBlock, "", 0x4D3940, CAnimBlock*(*)(const char*)); - RH_ScopedOverloadedInstall(GetAnimationBlockIndex, "by-name", 0x4D3990, int32(*)(const char*), { .reversed = false }); - RH_ScopedInstall(GetFirstAssocGroup, 0x4D39B0, { .reversed = false }); + RH_ScopedOverloadedInstall(GetAnimationBlockIndex, "by-name", 0x4D3990, int32(*)(const char*)); + RH_ScopedInstall(GetFirstAssocGroup, 0x4D39B0); RH_ScopedOverloadedInstall(GetAnimation, "0", 0x4D39F0, CAnimBlendHierarchy*(*)(uint32, const CAnimBlock*)); RH_ScopedOverloadedInstall(GetAnimation, "1", 0x4D42F0, CAnimBlendHierarchy*(*)(const char*, const CAnimBlock*)); RH_ScopedInstall(GetAnimGroupName, 0x4D3A20); RH_ScopedInstall(GetAnimBlockName, 0x4D3A30); RH_ScopedInstall(CreateAnimAssociation, 0x4D3A40); - RH_ScopedOverloadedInstall(GetAnimAssociation, "", 0x4D3A60, CAnimBlendStaticAssociation*(*)(AssocGroupId, AnimationId)); - RH_ScopedOverloadedInstall(GetAnimAssociation, "", 0x4D3A80, CAnimBlendStaticAssociation*(*)(AssocGroupId, const char*)); - RH_ScopedOverloadedInstall(AddAnimation, "id", 0x4D3AA0, CAnimBlendAssociation*(*)(RpClump*, AssocGroupId, AnimationId), { .reversed = false }); - RH_ScopedOverloadedInstall(AddAnimation, "hier", 0x4D4330, CAnimBlendAssociation*(*)(RpClump*, CAnimBlendHierarchy*, int32), { .reversed = false }); - RH_ScopedInstall(AddAnimationAndSync, 0x4D3B30, { .reversed = false }); - RH_ScopedInstall(AddAnimAssocDefinition, 0x4D3BA0, { .reversed = false }); - RH_ScopedInstall(AddAnimToAssocDefinition, 0x4D3C80, { .reversed = false }); - RH_ScopedInstall(CreateAnimAssocGroups, 0x4D3CC0, { .reversed = false }); - RH_ScopedInstall(RegisterAnimBlock, 0x4D3E50, { .reversed = false }); - RH_ScopedInstall(RemoveLastAnimFile, 0x4D3ED0, { .reversed = false }); - RH_ScopedInstall(RemoveAnimBlock, 0x4D3F40, { .reversed = false }); - RH_ScopedInstall(AddAnimBlockRef, 0x4D3FB0, { .reversed = false }); - RH_ScopedInstall(RemoveAnimBlockRefWithoutDelete, 0x4D3FF0, { .reversed = false }); - RH_ScopedInstall(GetNumRefsToAnimBlock, 0x4D4010, { .reversed = false }); - RH_ScopedInstall(UncompressAnimation, 0x4D41C0, { .reversed = false }); - RH_ScopedInstall(RemoveFromUncompressedCache, 0x4D42A0, { .reversed = false }); + RH_ScopedOverloadedInstall(GetAnimAssociation, "by-id", 0x4D3A60, CAnimBlendStaticAssociation*(*)(AssocGroupId, AnimationId)); + RH_ScopedOverloadedInstall(GetAnimAssociation, "by-name", 0x4D3A80, CAnimBlendStaticAssociation*(*)(AssocGroupId, const char*)); + RH_ScopedOverloadedInstall(AddAnimation, "id", 0x4D3AA0, CAnimBlendAssociation*(*)(RpClump*, AssocGroupId, AnimationId)); + RH_ScopedOverloadedInstall(AddAnimation, "hier", 0x4D4330, CAnimBlendAssociation*(*)(RpClump*, CAnimBlendHierarchy*, int32)); + RH_ScopedInstall(AddAnimationAndSync, 0x4D3B30); + RH_ScopedInstall(AddAnimAssocDefinition, 0x4D3BA0); + RH_ScopedInstall(AddAnimToAssocDefinition, 0x4D3C80); + RH_ScopedInstall(CreateAnimAssocGroups, 0x4D3CC0); + RH_ScopedInstall(RegisterAnimBlock, 0x4D3E50); + RH_ScopedInstall(RemoveLastAnimFile, 0x4D3ED0); + RH_ScopedInstall(RemoveAnimBlock, 0x4D3F40); + RH_ScopedInstall(AddAnimBlockRef, 0x4D3FB0); + RH_ScopedInstall(RemoveAnimBlockRefWithoutDelete, 0x4D3FF0); + RH_ScopedInstall(GetNumRefsToAnimBlock, 0x4D4010); + RH_ScopedInstall(UncompressAnimation, 0x4D41C0); + RH_ScopedInstall(RemoveFromUncompressedCache, 0x4D42A0); RH_ScopedOverloadedInstall(BlendAnimation, "id", 0x4D4610, CAnimBlendAssociation*(*)(RpClump*, AssocGroupId, AnimationId, float), { .reversed = false }); RH_ScopedOverloadedInstall(BlendAnimation, "hier", 0x4D4410, CAnimBlendAssociation*(*)(RpClump*, CAnimBlendHierarchy*, int32, float), { .reversed = false }); RH_ScopedInstall(LoadAnimFiles, 0x4D5620); - RH_ScopedInstall(LoadAnimFile, 0x4D47F0, { .reversed = false }); + RH_ScopedInstall(LoadAnimFile, 0x4D47F0, {.reversed = false}); } struct IfpHeader { @@ -54,7 +56,7 @@ struct IfpHeader { // 0x5BF6B0 void CAnimManager::Initialise() { - return plugin::Call<0x5BF6B0>(); + ZoneScoped; ms_numAnimations = 0; ms_numAnimBlocks = 0; @@ -66,36 +68,36 @@ void CAnimManager::Initialise() { // 0x5BC910 void CAnimManager::ReadAnimAssociationDefinitions() { - // return plugin::Call<0x5BC910>(); - char name[32], block[32], type[32]; - bool isAnimSection = false; + bool isAnimSection = false; AnimAssocDefinition* animStyle; - int animCount; + uint32 animCount; CFileMgr::SetDir(""); - auto* file = CFileMgr::OpenFile("DATA\\ANIMGRP.DAT", "rb"); - for (auto line = CFileLoader::LoadLine(file); line; line = CFileLoader::LoadLine(file)) { - if (!*line || *line == '#') + const auto f = CFileMgr::OpenFile("DATA\\ANIMGRP.DAT", "rb"); + for (;;) { + const auto l = CFileLoader::LoadLine(f); + if (!l) { + break; + } + if (!*l || *l == '#') { continue; - + } if (isAnimSection) { - if (sscanf_s(line, "%s", SCANF_S_STR(name)) == 1) { + if (sscanf_s(l, "%s", SCANF_S_STR(name)) == 1) { if (!memcmp(name, "end", 4)) { isAnimSection = false; } else { AddAnimToAssocDefinition(animStyle, name); } } - } - else - { - VERIFY(sscanf_s(line, "%s %s %s %d", SCANF_S_STR(name), SCANF_S_STR(block), SCANF_S_STR(type), &animCount) == 4); + } else { + VERIFY(sscanf_s(l, "%s %s %s %d", SCANF_S_STR(name), SCANF_S_STR(block), SCANF_S_STR(type), &animCount) == 4); animStyle = AddAnimAssocDefinition(name, block, MODEL_MALE01, animCount, aStdAnimDescs); isAnimSection = true; } } - CFileMgr::CloseFile(file); + CFileMgr::CloseFile(f); } // 0x4D4130 @@ -118,9 +120,10 @@ CAnimBlock* CAnimManager::GetAnimationBlock(AssocGroupId animGroup) { // 0x4D3940 CAnimBlock* CAnimManager::GetAnimationBlock(const char* name) { - for (auto& block : std::span{ ms_aAnimBlocks.data(), (size_t)ms_numAnimBlocks }) { - if (_stricmp(block.szName, name) == 0) { - return █ + const auto namesv = notsa::ci_string_view{ name }; + for (auto& ab : GetAnimBlocks()) { + if (namesv == ab.szName) { + return &ab; } } return nullptr; @@ -136,32 +139,30 @@ int32 CAnimManager::GetAnimationBlockIndex(CAnimBlock* animBlock) { // 0x4D3990 int32 CAnimManager::GetAnimationBlockIndex(const char* name) { - return plugin::CallAndReturn(name); + const auto b = GetAnimationBlock(name); + return b + ? (int32)(std::distance(ms_aAnimBlocks.data(), b)) + : -1; } // 0x4D39B0 AssocGroupId CAnimManager::GetFirstAssocGroup(const char* name) { - return plugin::CallAndReturn(name); - - // ANIM_TOTAL_GROUPS + const auto namesv = notsa::ci_string_view{ name }; for (auto i = 0; i < ANIM_GROUP_MAN; i++) { - if (!_stricmp(ms_aAnimAssocDefinitions[i].blockName, name)) { + if (ms_aAnimAssocDefinitions[i].blockName == namesv) { return static_cast(i); } } - return ANIM_GROUP_MAN; } // 0x4D39F0 CAnimBlendHierarchy* CAnimManager::GetAnimation(uint32 hash, const CAnimBlock* animBlock) { - CAnimBlendHierarchy* hier = &ms_aAnimations[animBlock->startAnimation]; - - for (auto i = 0; i < animBlock->animationCount; i++) { - if (hier->m_hashKey == hash) { - return hier; + auto h = &ms_aAnimations[animBlock->startAnimation]; + for (auto i = animBlock->animationCount; i-- > 0; h++) { + if (h->m_hashKey == hash) { + return h; } - hier++; } return nullptr; } @@ -182,9 +183,9 @@ const char* CAnimManager::GetAnimBlockName(AssocGroupId groupId) { } // NOTSA -AssocGroupId CAnimManager::GetAnimationGroupId(const char* name) { - for (auto i = 0; i < ms_numAnimAssocDefinitions; i++) { - if (std::string_view{ name } == GetAnimGroupName((AssocGroupId)i)) { +AssocGroupId CAnimManager::GetAnimationGroupIdByName(notsa::ci_string_view name) { + for (const auto& [i, gd] : notsa::enumerate(GetAssocGroupDefs())) { + if (gd.groupName == name) { return (AssocGroupId)i; } } @@ -206,126 +207,103 @@ CAnimBlendStaticAssociation* CAnimManager::GetAnimAssociation(AssocGroupId group return ms_aAnimAssocGroups[groupId].GetAnimation(animName); } -// 0x4D3AA0 -CAnimBlendAssociation* CAnimManager::AddAnimation(RpClump* clump, AssocGroupId groupId, AnimationId animId) { - return plugin::CallAndReturn(clump, groupId, animId); - - CAnimBlendAssociation* anim = CreateAnimAssociation(groupId, animId); - CAnimBlendClumpData* clumpData = RpClumpGetAnimBlendClumpData(clump); - if (anim->IsMoving()) { - CAnimBlendAssociation* syncAnim; - CAnimBlendLink* link; - for (link = clumpData->m_Associations.next; link; link = link->next) { - syncAnim = CAnimBlendAssociation::FromLink(link); - if (syncAnim->IsMoving()) - break; +CAnimBlendAssociation* CAnimManager::AddAnimationToClump(RpClump* clump, CAnimBlendAssociation* anim) { + const auto clumpAnims = &RpClumpGetAnimBlendClumpData(clump)->m_Associations; + const auto syncWith = [&]() -> CAnimBlendAssociation* { + if (anim->IsMoving()) { + for (auto l = clumpAnims->next; l; l = l->next) { + const auto a = CAnimBlendAssociation::FromLink(l); + if (a->IsMoving()) { + return a; + } + } } - if (link) { - anim->SyncAnimation(syncAnim); - anim->m_nFlags |= ANIMATION_STARTED; - } else - anim->Start(0.0f); - } else + return nullptr; + }(); + if (syncWith) { + anim->SyncAnimation(syncWith); + anim->m_nFlags |= ANIMATION_STARTED; + } else { anim->Start(0.0f); - - clumpData->m_Associations.Prepend(&anim->m_Link); + } + clumpAnims->Prepend(&anim->m_Link); return anim; } +// 0x4D3AA0 +CAnimBlendAssociation* CAnimManager::AddAnimation(RpClump* clump, AssocGroupId groupId, AnimationId animId) { + return AddAnimationToClump(clump, CreateAnimAssociation(groupId, animId)); +} + // 0x4D4330 CAnimBlendAssociation* CAnimManager::AddAnimation(RpClump* clump, CAnimBlendHierarchy* hier, int32 clumpAssocFlag) { - return plugin::CallAndReturn(clump, hier, clumpAssocFlag); - - CAnimBlendAssociation* anim = new CAnimBlendAssociation(clump, hier); + const auto anim = new CAnimBlendAssociation(clump, hier); anim->m_nFlags |= clumpAssocFlag; anim->ReferenceAnimBlock(); UncompressAnimation(hier); - CAnimBlendClumpData* clumpData = RpClumpGetAnimBlendClumpData(clump); - if (anim->IsMoving()) { - CAnimBlendAssociation* syncAnim; - CAnimBlendLink* link; - for (link = clumpData->m_Associations.next; link; link = link->next) { - syncAnim = CAnimBlendAssociation::FromLink(link); - if (syncAnim->IsMoving()) - break; - } - if (link) { - anim->SyncAnimation(syncAnim); - anim->m_nFlags |= ANIMATION_STARTED; - } else - anim->Start(0.0f); - } else - anim->Start(0.0f); - - clumpData->m_Associations.Prepend(&anim->m_Link); - return anim; + return AddAnimationToClump(clump, anim); } // 0x4D3B30 CAnimBlendAssociation* CAnimManager::AddAnimationAndSync(RpClump* clump, CAnimBlendAssociation* animBlendAssoc, AssocGroupId groupId, AnimationId animId) { - return plugin::CallAndReturn(clump, animBlendAssoc, groupId, animId); - - CAnimBlendAssociation* anim = CreateAnimAssociation(groupId, animId); - CAnimBlendClumpData* clumpData = RpClumpGetAnimBlendClumpData(clump); - if (anim->IsMoving() && animBlendAssoc) { - anim->SyncAnimation(animBlendAssoc); - anim->m_nFlags |= ANIMATION_STARTED; - } else - anim->Start(0.0f); - - clumpData->m_Associations.Prepend(&anim->m_Link); - return anim; + const auto a = CreateAnimAssociation(groupId, animId); + const auto clumpAnims = RpClumpGetAnimBlendClumpData(clump); + if (a->IsMoving() && animBlendAssoc) { + a->SyncAnimation(animBlendAssoc); + a->m_nFlags |= ANIMATION_STARTED; + } else { + a->Start(0.0f); + } + clumpAnims->m_Associations.Prepend(&a->m_Link); + return a; } // 0x4D3BA0 AnimAssocDefinition* CAnimManager::AddAnimAssocDefinition(const char* groupName, const char* blockName, uint32 modelIndex, uint32 animsCount, AnimDescriptor* descriptor) { - return plugin::CallAndReturn(groupName, blockName, modelIndex, animsCount, descriptor); + const auto d = &ms_aAnimAssocDefinitions[ms_numAnimAssocDefinitions++]; - /* - auto* def = &ms_aAnimAssocDefinitions[ms_numAnimAssocDefinitions++]; - strcpy_s(def->groupName, groupName); - strcpy_s(def->blockName, blockName); - def->modelIndex = modelIndex; - def->animsCount = animsCount; - def->animDesc = descriptor; - def->animNames = new char*[animsCount]; - for (auto i = 0; i < animsCount; i++) { - def->animNames[i] = new char[24]; - *def->animNames[i] = '\0'; + strcpy_s(d->groupName, groupName); + strcpy_s(d->blockName, blockName); + + d->modelIndex = modelIndex; + d->animsCount = animsCount; + d->animDesc = descriptor; + + d->animNames = new const char*[animsCount]; + const auto bufsz = AnimAssocDefinition::ANIM_NAME_BUF_SZ * animsCount; + const auto buf = new char[bufsz]; + memset(buf, 0, bufsz); + for (auto i = animsCount; i-->0;) { + d->animNames[i] = buf + i * 24; } - return def; - */ + + return d; } // 0x4D3C80 -void CAnimManager::AddAnimToAssocDefinition(AnimAssocDefinition* definition, const char* animName) { - return plugin::Call<0x4D3C80, AnimAssocDefinition*, const char*>(definition, animName); - - /* - int i = 0; - while (*definition->animNames[i]) { - i++; +void CAnimManager::AddAnimToAssocDefinition(AnimAssocDefinition* def, const char* animName) { + int32 i = 0; + for (; *def->animNames[i]; i++) { + assert(i < def->animsCount); } - strcpy_s(definition->animNames[i], animName); - */ + // `const_cast` is fine here, because it's heap allocated [presumeably] + strcpy_s(const_cast(def->animNames[i]), AnimAssocDefinition::ANIM_NAME_BUF_SZ, animName); } // 0x4C4DC0 bool IsClumpSkinned(RpClump *clump) { - auto* atomic = GetFirstAtomic(clump); - return atomic && RpSkinGeometryGetSkin(RpAtomicGetGeometry(atomic)); + const auto a = GetFirstAtomic(clump); + return a && RpSkinGeometryGetSkin(RpAtomicGetGeometry(a)); } // 0x4D3CC0 void CAnimManager::CreateAnimAssocGroups() { - return plugin::Call<0x4D3CC0>(); - - for (auto i = 0; i < ms_numAnimAssocDefinitions; i++) { - CAnimBlendAssocGroup* group = &ms_aAnimAssocGroups[i]; - AnimAssocDefinition* def = &ms_aAnimAssocDefinitions[i]; - CAnimBlock* block = GetAnimationBlock(def->blockName); - if (block == nullptr || !block->bLoaded || group->m_pAssociations) + for (auto&& [i, group] : notsa::enumerate(GetAssocGroups())) { + const auto def = &ms_aAnimAssocDefinitions[i]; + const auto block = GetAnimationBlock(def->blockName); + if (block == nullptr || !block->bLoaded || group.m_pAssociations) { continue; + } RpClump* clump = nullptr; if (def->modelIndex != MODEL_INVALID) { @@ -333,11 +311,11 @@ void CAnimManager::CreateAnimAssocGroups() { RpAnimBlendClumpInit(clump); } - group->m_nGroupID = i; - group->m_nIdOffset = def->animDesc->animId; - group->CreateAssociations(def->blockName, clump, const_cast(def->animNames), def->animsCount); // todo: remove const_cast - for (auto j = 0u; j < group->m_nNumAnimations; j++) { - group->GetAnimation(def->animDesc[j].animId)->m_nFlags |= def->animDesc[j].flags; + group.m_nGroupID = i; + group.m_nIdOffset = def->animDesc->animId; + group.CreateAssociations(def->blockName, clump, const_cast(def->animNames), def->animsCount); // todo: remove const_cast + for (auto j = 0u; j < group.m_nNumAnimations; j++) { + group.GetAnimation(def->animDesc[j].animId)->m_nFlags |= def->animDesc[j].flags; } if (clump) { @@ -351,59 +329,54 @@ void CAnimManager::CreateAnimAssocGroups() { // 0x4D3E50 int32 CAnimManager::RegisterAnimBlock(const char* name) { - return plugin::CallAndReturn(name); - - CAnimBlock* animBlock = GetAnimationBlock(name); - if (animBlock == nullptr) { - animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; - strncpy_s(animBlock->szName, name, MAX_ANIM_BLOCK_NAME); - animBlock->animationCount = 0; - animBlock->animationStyle = GetFirstAssocGroup(name); - assert(animBlock->usRefs == 0); + CAnimBlock* ab = GetAnimationBlock(name); + if (ab == nullptr) { // Initialize a new anim block + ab = &ms_aAnimBlocks[ms_numAnimBlocks++]; + strncpy_s(ab->szName, name, MAX_ANIM_BLOCK_NAME); + ab->animationCount = 0; + ab->animationStyle = GetFirstAssocGroup(name); + assert(ab->usRefs == 0); } - return GetAnimationBlockIndex(animBlock); + + return GetAnimationBlockIndex(ab); } // 0x4D3ED0 void CAnimManager::RemoveLastAnimFile() { - return plugin::Call<0x4D3ED0>(); - - ms_numAnimBlocks--; - ms_numAnimations = ms_aAnimBlocks[ms_numAnimBlocks].startAnimation; - for (auto i = 0; i < ms_aAnimBlocks[ms_numAnimBlocks].animationCount; i++) { - ms_aAnimations[ms_aAnimBlocks[ms_numAnimBlocks].startAnimation + i].Shutdown(); + const auto ab = &GetAnimBlocks()[--ms_numAnimBlocks]; + ms_numAnimations = ab->startAnimation; + for (auto i = 0; i < ab->animationCount; i++) { // Remove related animations too + ms_aAnimations[ab->startAnimation + i].Shutdown(); } - - ms_aAnimBlocks[ms_numAnimBlocks].bLoaded = false; + ab->bLoaded = false; } // 0x4D3F40 void CAnimManager::RemoveAnimBlock(int32 index) { - return plugin::Call<0x4D3F40, int32>(index); + const auto ab = &GetAnimBlocks()[index]; - CAnimBlock* block = &ms_aAnimBlocks[index]; - for (auto i = 0; i < ms_numAnimAssocDefinitions; i++) { - if (ms_aAnimAssocGroups[i].m_pAnimBlock == block) { - ms_aAnimAssocGroups[i].DestroyAssociations(); + for (auto& g : GetAssocGroups()) { + if (g.m_pAnimBlock == ab) { + g.DestroyAssociations(); } } - - for (auto i = 0; i < block->animationCount; i++) { - ms_aAnimations[block->startAnimation + i].Shutdown(); + + for (auto i = 0; i < ab->animationCount; i++) { // Remove related animations too + ms_aAnimations[ab->startAnimation + i].Shutdown(); } - block->bLoaded = false; - block->usRefs = 0; + ab->bLoaded = false; + ab->usRefs = 0; } // 0x4D3FB0 void CAnimManager::AddAnimBlockRef(int32 index) { - ms_aAnimBlocks[index].usRefs++; + GetAnimBlocks()[index].usRefs++; } // 0x4D3FD0 void CAnimManager::RemoveAnimBlockRef(int32 index) { - ms_aAnimBlocks[index].usRefs--; + GetAnimBlocks()[index].usRefs--; /* see RemoveAnimBlockRefWithoutDelete, logically here should be called RemoveModel or something if (--ms_aAnimBlocks[index].usRefs == 0) { CStreaming::RemoveModel(IFPToModelId(index)); @@ -422,13 +395,35 @@ int32 CAnimManager::GetNumRefsToAnimBlock(int32 index) { } // 0x4D41C0 -void CAnimManager::UncompressAnimation(CAnimBlendHierarchy* hier) { - return plugin::Call<0x4D41C0, CAnimBlendHierarchy*>(hier); +void CAnimManager::UncompressAnimation(CAnimBlendHierarchy* h) { + if (h->m_bKeepCompressed) { + if (h->m_fTotalTime == 0.f) { + h->CalcTotalTimeCompressed(); + } + } else if (h->m_bIsCompressed) { + auto l = ms_animCache.Insert(h); + if (!l) { // Not more free links? + // Remove least recently added item + const auto llr = ms_animCache.GetTail(); + llr->data->RemoveUncompressedData(); + RemoveFromUncompressedCache(llr->data); + + // Now try again, this time it should succeed + VERIFY(l = ms_animCache.Insert(h)); + } + h->m_Link = l; + h->Uncompress(); + } else if (h->m_Link) { // Already uncompressed, add to cache + h->m_Link->Insert(ms_animCache.GetHead()); + } } // 0x4D42A0 -void CAnimManager::RemoveFromUncompressedCache(CAnimBlendHierarchy* hier) { - return plugin::Call<0x4D42A0, CAnimBlendHierarchy*>(hier); +void CAnimManager::RemoveFromUncompressedCache(CAnimBlendHierarchy* h) { + if (const auto l = h->m_Link) { + l->data->m_Link = nullptr; + ms_animCache.Remove(l); + } } // 0x4D4410 @@ -555,8 +550,6 @@ void CAnimManager::LoadAnimFiles() { // 0x4D47F0 void CAnimManager::LoadAnimFile(RwStream* stream, bool loadCompressed, char const(*uncompressedAnimations)[32]) { - return plugin::Call<0x4D47F0, RwStream*, bool, char const(*)[32]>(stream, loadCompressed, uncompressedAnimations); - RwStreamRead(stream, &header, sizeof(IfpHeader)); if (header.ident == '3PNA' || header.ident == '2PNA') { LoadAnimFile_ANP23(stream, loadCompressed, header.ident == '3PNA'); @@ -570,7 +563,6 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con if ((x)&3) \ (x) += 4 - ((x)&3) - /* IfpHeader info, name, dgan, cpan, anim; char buf[256]; float* fbuf = (float*)buf; @@ -593,7 +585,7 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con } } else { animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; - strncpy_s(animBlock->szName, buf + 4, MAX_ANIMBLOCK_NAME); + strncpy_s(animBlock->szName, buf + 4, MAX_ANIM_BLOCK_NAME); animBlock->animationCount = *(int*)buf; animBlock->startAnimation = ms_numAnimations; animBlock->animationStyle = GetFirstAssocGroup(animBlock->szName); @@ -604,7 +596,7 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con int animIndex = animBlock->startAnimation; for (auto j = 0; j < animBlock->animationCount; j++) { - assert(animIndex < ARRAY_SIZE(ms_aAnimations)); + assert(animIndex < (int32)std::size(ms_aAnimations)); CAnimBlendHierarchy* hier = &ms_aAnimations[animIndex++]; // animation name @@ -615,7 +607,7 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con hier->SetName(buf); //#ifdef ANIM_COMPRESSION - bool compressHier = compress; + bool isCompressed = compress; //#else // bool compressHier = false; //#endif @@ -624,32 +616,36 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con //if (!stricmp(uncompressedAnims[i], buf)) if (CKeyGen::GetUppercaseKey(uncompressedAnims[i]) == hier->m_hashKey)// { //debug("Loading %s uncompressed\n", hier->name); - compressHier = false; + isCompressed = false; //} } } - hier->m_bRunningCompressed = compressHier; + hier->m_bIsCompressed = isCompressed; hier->m_bKeepCompressed = false; // DG info has number of nodes/sequences RwStreamRead(stream, (char*)&dgan, sizeof(IfpHeader)); ROUND_SIZE(dgan.size); + RwStreamRead(stream, (char*)&info, sizeof(IfpHeader)); ROUND_SIZE(info.size); + RwStreamRead(stream, buf, info.size); int nSeq = *(int*)buf; - hier->numSequences = nSeq; - //hier->sequences = (CAnimBlendSequence*)CMemoryMgr::Malloc(nSeq * sizeof(CAnimBlendSequence)); - hier->sequences = new CAnimBlendSequence[nSeq]; + hier->m_nSeqCount = nSeq; + hier->m_pSequences = new CAnimBlendSequence[nSeq]; //= (CAnimBlendSequence*)CMemoryMgr::Malloc(nSeq * sizeof(CAnimBlendSequence)); + + for (auto s = 0; s < nSeq; s++) { // or seq++ ? + CAnimBlendSequence* seq = &hier->m_pSequences[s]; - for (auto k = 0; k < nSeq; k++) { // or seq++ ? - CAnimBlendSequence* seq = &hier->sequences[k]; // Each node has a name and key frames RwStreamRead(stream, &cpan, sizeof(IfpHeader)); ROUND_SIZE(cpan.size); + RwStreamRead(stream, &anim, sizeof(IfpHeader)); ROUND_SIZE(anim.size); + RwStreamRead(stream, buf, anim.size); int numFrames = *(int*)(buf + 28); seq->SetName(buf); @@ -664,15 +660,17 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con RwStreamRead(stream, &info, sizeof(info)); //if (numFrames == 0) // continue; - if (strncmp(info.ident, "KRTS", 4) == 0) { + + if (memcmp(&info.ident, "KRTS", 4) == 0) { hasScale = true; - seq->SetNumFrames(numFrames, true, compressHier, NULL); - } else if (strncmp(info.ident, "KRT0", 4) == 0) { + seq->SetNumFrames(numFrames, true, isCompressed, NULL); + } else if (memcmp(&info.ident, "KRT0", 4) == 0) { hasTranslation = true; - seq->SetNumFrames(numFrames, true, compressHier, NULL); - } else if (strncmp(info.ident, "KR00", 4) == 0) { - seq->SetNumFrames(numFrames, false, compressHier, NULL); + seq->SetNumFrames(numFrames, true, isCompressed, NULL); + } else if (memcmp(&info.ident, "KR00", 4) == 0) { + seq->SetNumFrames(numFrames, false, isCompressed, NULL); } + //if (seq->numFrames) // if(strstr(seq->name, "L Toe")) // debug("anim %s has toe keyframes\n", hier->name); // BUG: seq->name @@ -684,14 +682,14 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con rot.Conjugate(); CVector trans(fbuf[4], fbuf[5], fbuf[6]); - if (compressHier) { - KeyFrameTransCompressed* kf = (KeyFrameTransCompressed*)seq->GetKeyFrameCompressed(l); + if (isCompressed) { + KeyFrameTransCompressed* kf = (KeyFrameTransCompressed*)seq->GetCompressedFrame(l); kf->SetRotation(rot); kf->SetTranslation(trans); // scaling ignored kf->SetTime(fbuf[10]); // absolute time here } else { - KeyFrameTrans* kf = (KeyFrameTrans*)seq->GetKeyFrame(l); + KeyFrameTrans* kf = (KeyFrameTrans*)seq->GetUncompressedFrame(l); kf->rotation = rot; kf->translation = trans; // scaling ignored @@ -703,13 +701,13 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con rot.Conjugate(); CVector trans(fbuf[4], fbuf[5], fbuf[6]); - if (compressHier) { - KeyFrameTransCompressed* kf = (KeyFrameTransCompressed*)seq->GetKeyFrameCompressed(l); + if (isCompressed) { + KeyFrameTransCompressed* kf = (KeyFrameTransCompressed*)seq->GetCompressedFrame(l); kf->SetRotation(rot); kf->SetTranslation(trans); kf->SetTime(fbuf[7]); // absolute time here } else { - KeyFrameTrans* kf = (KeyFrameTrans*)seq->GetKeyFrame(l); + KeyFrameTrans* kf = (KeyFrameTrans*)seq->GetUncompressedFrame(l); kf->rotation = rot; kf->translation = trans; kf->deltaTime = fbuf[7]; // absolute time here @@ -719,12 +717,12 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con CQuaternion rot(fbuf[0], fbuf[1], fbuf[2], fbuf[3]); rot.Conjugate(); - if (compressHier) { - KeyFrameCompressed* kf = (KeyFrameCompressed*)seq->GetKeyFrameCompressed(l); + if (isCompressed) { + KeyFrameCompressed* kf = (KeyFrameCompressed*)seq->GetCompressedFrame(l); kf->SetRotation(rot); kf->SetTime(fbuf[4]); // absolute time here } else { - KeyFrame* kf = (KeyFrame*)seq->GetKeyFrame(l); + KeyFrame* kf = (KeyFrame*)seq->GetUncompressedFrame(l); kf->rotation = rot; kf->deltaTime = fbuf[4]; // absolute time here } @@ -732,7 +730,7 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con } } - if (!compressHier) { + if (!isCompressed) { hier->RemoveQuaternionFlips(); hier->CalcTotalTime(); } @@ -741,26 +739,26 @@ inline void CAnimManager::LoadAnimFile_ANPK(RwStream* stream, bool compress, con if (animIndex > ms_numAnimations) { ms_numAnimations = animIndex; } - */ + #undef ROUND_SIZE } // NOTSA inline void CAnimManager::LoadAnimFile_ANP23(RwStream* stream, bool compress, bool isANP3) { char buf[256]; - // char name[24]; + char blockName[24]; int nAnims, nSeq; - RwStreamRead(stream, &buf, 24); // animation name - RwStreamRead(stream, &nAnims, 4); + RwStreamRead(stream, &blockName, sizeof(blockName)); + RwStreamRead(stream, &nAnims, sizeof(nAnims)); - CAnimBlock* animBlock = GetAnimationBlock(buf); + CAnimBlock* animBlock = GetAnimationBlock(blockName); if (animBlock) { if (animBlock->animationCount == 0) { animBlock->animationCount = nAnims; animBlock->startAnimation = ms_numAnimations; } - } else { + } else { // Register a new block animBlock = &ms_aAnimBlocks[ms_numAnimBlocks++]; strncpy_s(animBlock->szName, buf, MAX_ANIM_BLOCK_NAME); animBlock->animationCount = nAnims; @@ -834,7 +832,7 @@ inline void CAnimManager::LoadAnimFile_ANP23(RwStream* stream, bool compress, bo } if (!bInvalidType) { if (k == 0) { - hier->m_bRunningCompressed = bIsCompressed; + hier->m_bIsCompressed = bIsCompressed; } seq->SetNumFrames(sdata.frames_count, bIsRoot, bIsCompressed, st); @@ -847,7 +845,7 @@ inline void CAnimManager::LoadAnimFile_ANP23(RwStream* stream, bool compress, bo } } } - if (!hier->m_bRunningCompressed) { + if (!hier->m_bIsCompressed) { hier->RemoveQuaternionFlips(); hier->CalcTotalTime(); } diff --git a/source/game_sa/Animation/AnimManager.h b/source/game_sa/Animation/AnimManager.h index 9bf8e2c85d..e522bb72b4 100644 --- a/source/game_sa/Animation/AnimManager.h +++ b/source/game_sa/Animation/AnimManager.h @@ -11,6 +11,8 @@ #include "AnimBlendAssociation.h" #include "AnimBlock.h" +#include + constexpr auto MAX_ANIM_BLOCK_NAME = 16; constexpr auto NUM_ANIM_ASSOC_GROUPS = 118; constexpr auto NUM_ANIM_BLOCKS = 180; @@ -19,10 +21,10 @@ class CAnimManager { public: static std::array ms_aAnimAssocDefinitionsX; // replacement static inline AnimAssocDefinition (&ms_aAnimAssocDefinitions)[NUM_ANIM_ASSOC_GROUPS] = *(AnimAssocDefinition(*)[NUM_ANIM_ASSOC_GROUPS])0x8AA5A8; // std::array - see SurfaceInfos_c - static inline int32& ms_numAnimAssocDefinitions = *(int32*)0xB4EA28; + static inline uint32& ms_numAnimAssocDefinitions = *(uint32*)0xB4EA28; static inline CAnimBlendAssocGroup*& ms_aAnimAssocGroups = *(CAnimBlendAssocGroup**)0xB4EA34; - static inline int32& ms_numAnimBlocks = *(int32*)0xB4EA30; + static inline uint32& ms_numAnimBlocks = *(uint32*)0xB4EA30; static inline std::array& ms_aAnimations = *(std::array*)0xB4EA40; static inline int32& ms_numAnimations = *(int32*)0xB4EA2C; @@ -52,9 +54,10 @@ class CAnimManager { static CAnimBlendHierarchy* GetAnimation(const char* animName, const CAnimBlock* animBlock); static const char* GetAnimGroupName(AssocGroupId groupId); static const char* GetAnimBlockName(AssocGroupId groupId); - static AssocGroupId GetAnimationGroupId(const char* name); + static AssocGroupId GetAnimationGroupIdByName(notsa::ci_string_view name); static CAnimBlendStaticAssociation* GetAnimAssociation(AssocGroupId groupId, AnimationId animId); static CAnimBlendStaticAssociation* GetAnimAssociation(AssocGroupId groupId, const char* animName); + static CAnimBlendAssociation* AddAnimationToClump(RpClump* clump, CAnimBlendAssociation* anim); // NOTSA - Internal static int32 GetNumRefsToAnimBlock(int32 index); static CAnimBlendAssociation* CreateAnimAssociation(AssocGroupId groupId, AnimationId animId); @@ -79,7 +82,11 @@ class CAnimManager { /// NOTSA. Get random gangtalk anim static AnimationId GetRandomGangTalkAnim(); + + static auto GetAnimBlocks() { return ms_aAnimBlocks | rng::views::take(ms_numAnimBlocks); } + static auto GetAssocGroups() { return std::span{ms_aAnimAssocGroups, ms_numAnimAssocDefinitions}; } + static auto GetAssocGroupDefs() { return std::span{ms_aAnimAssocDefinitions, ms_numAnimAssocDefinitions}; } private: - static inline void LoadAnimFile_ANPK(RwStream* stream, bool compress, const char (*uncompressedAnims)[32]); - static inline void LoadAnimFile_ANP23(RwStream* stream, bool compress, bool isANP3); + static void LoadAnimFile_ANPK(RwStream* stream, bool compress, const char (*uncompressedAnims)[32]); + static void LoadAnimFile_ANP23(RwStream* stream, bool compress, bool isANP3); }; diff --git a/source/game_sa/Animation/AnimationStyleDescriptor.h b/source/game_sa/Animation/AnimationStyleDescriptor.h index b2d770e78d..3af164dd50 100644 --- a/source/game_sa/Animation/AnimationStyleDescriptor.h +++ b/source/game_sa/Animation/AnimationStyleDescriptor.h @@ -6,12 +6,14 @@ struct AnimDescriptor { }; struct AnimAssocDefinition { + constexpr static size_t ANIM_NAME_BUF_SZ = 24; + char groupName[16]{}; char blockName[16]{}; int32 modelIndex{}; int32 animsCount{}; - const char** animNames{}; + const char** animNames{}; //< Pointers to heap allocated char arrays (size 24 each) - The array of pointers itself is heap allocated as well - Size == animsCount AnimDescriptor* animDesc{}; }; VALIDATE_SIZE(AnimAssocDefinition, 0x30); diff --git a/source/game_sa/Audio/AEAudioUtility.cpp b/source/game_sa/Audio/AEAudioUtility.cpp index da3f52213f..7b1763ec85 100644 --- a/source/game_sa/Audio/AEAudioUtility.cpp +++ b/source/game_sa/Audio/AEAudioUtility.cpp @@ -7,6 +7,9 @@ uint64& CAEAudioUtility::startTimeMs = *reinterpret_cast(0xb610f8); float (&CAEAudioUtility::m_sfLogLookup)[50][2] = *reinterpret_cast(0xb61100); +// NOTE: Not sure about the size. +static inline auto& gScriptBanksLookup = *reinterpret_cast*>(0x8ABC70); + void CAEAudioUtility::InjectHooks() { RH_ScopedClass(CAEAudioUtility); RH_ScopedCategory("Audio"); @@ -18,10 +21,10 @@ void CAEAudioUtility::InjectHooks() { RH_ScopedInstall(AudioLog10, 0x4d9e50); RH_ScopedInstall(ConvertFromBytesToMS, 0x4d9ef0); RH_ScopedInstall(ConvertFromMSToBytes, 0x4d9f40); - RH_ScopedInstall(GetBankAndSoundFromScriptSlotAudioEvent, 0x4D9CC0, { .reversed = false }); + RH_ScopedInstall(GetBankAndSoundFromScriptSlotAudioEvent, 0x4D9CC0); RH_ScopedInstall(FindVehicleOfPlayer, 0x4D9E10); - RH_ScopedInstall(GetCurrentTimeInMilliseconds, 0x4d9e80); + RH_ScopedInstall(GetCurrentTimeInMS, 0x4d9e80); RH_ScopedInstall(StaticInitialise, 0x5b97f0); } @@ -39,7 +42,7 @@ float CAEAudioUtility::GetRandomNumberInRange(float a, float b) { // 0x4d9c80 bool CAEAudioUtility::ResolveProbability(float p) { - return p >= 1.0f || ((float)CGeneral::GetRandomNumber() * RAND_MAX_FLOAT_RECIPROCAL) < p; + return p >= 1.0f || CGeneral::RandomBool(100.0f * p); } // 0x4d9d90 @@ -67,7 +70,7 @@ float CAEAudioUtility::AudioLog10(float p) { // REFACTORED // 0x4d9e80 -uint64 CAEAudioUtility::GetCurrentTimeInMilliseconds() { +uint64 CAEAudioUtility::GetCurrentTimeInMS() { using namespace std::chrono; auto nowMs = time_point_cast(high_resolution_clock::now()); auto value = duration_cast(nowMs.time_since_epoch()); @@ -75,8 +78,8 @@ uint64 CAEAudioUtility::GetCurrentTimeInMilliseconds() { } // 0x4d9ef0 -uint32 CAEAudioUtility::ConvertFromBytesToMS(uint32 a, uint32 frequency, uint16 frequencyMult) { - return static_cast(std::floorf(a / (float(frequency * frequencyMult) / 500.0f))); +uint32 CAEAudioUtility::ConvertFromBytesToMS(uint32 lengthInBytes, uint32 frequency, uint16 frequencyMult) { + return static_cast(std::floorf(lengthInBytes / (float(frequency * frequencyMult) / 500.0f))); } // 0x4d9f40 @@ -100,12 +103,33 @@ void CAEAudioUtility::StaticInitialise() { m_sfLogLookup[1][1] = log10f(v); } - startTimeMs = GetCurrentTimeInMilliseconds(); + startTimeMs = GetCurrentTimeInMS(); } // 0x4D9CC0 -bool CAEAudioUtility::GetBankAndSoundFromScriptSlotAudioEvent(int32* a1, int32* a2, int32* a3, int32 a4) { - return plugin::CallAndReturn(a1, a2, a3, a4); +bool CAEAudioUtility::GetBankAndSoundFromScriptSlotAudioEvent(int32& slot, int32& outBank, int32& outSound, int32 a4) { + if (slot < 1800) + return false; + + if (slot < 2000) { + outBank = gScriptBanksLookup[slot - 1800]; + return true; + } + + if (slot == 0xFFFF) { // (uint16)-1 + outBank = 291; + + if (a4 > 3) + outSound = 0; + else + outSound = 2 * (a4 % 2); + + return true; + } + + outBank = static_cast(std::floor(float(slot - 2000) / 200.0f)) + 147; + outSound = (slot - 2000) % 200; + return true; } // 0x4D9E10 diff --git a/source/game_sa/Audio/AEAudioUtility.h b/source/game_sa/Audio/AEAudioUtility.h index 3b35475d77..7b4cc80377 100644 --- a/source/game_sa/Audio/AEAudioUtility.h +++ b/source/game_sa/Audio/AEAudioUtility.h @@ -1,4 +1,5 @@ #pragma once +#include "eRadioID.h" class CVehicle; @@ -13,12 +14,17 @@ class CAEAudioUtility { static bool ResolveProbability(float prob); static float AudioLog10(float p); - static uint32 ConvertFromBytesToMS(uint32 a, uint32 frequency, uint16 frequencyMult); + static uint32 ConvertFromBytesToMS(uint32 lengthInBytes, uint32 frequency, uint16 frequencyMult); static uint32 ConvertFromMSToBytes(uint32 a, uint32 frequency, uint16 frequencyMult); - static bool GetBankAndSoundFromScriptSlotAudioEvent(int32* a1, int32* a2, int32* a3, int32 a4); + static bool GetBankAndSoundFromScriptSlotAudioEvent(int32& slot, int32& outBank, int32& outSound, int32 a4); static float GetPiecewiseLinear(float x, int16 dataCount, float (*data)[2]); - static uint64 GetCurrentTimeInMilliseconds(); + static uint64 GetCurrentTimeInMS(); + + // NOTSA + static eRadioID GetRandomRadioStation() { + return static_cast(GetRandomNumberInRange(1, RADIO_COUNT - 1)); + } private: static uint64& startTimeMs; diff --git a/source/game_sa/Audio/AESmoothFadeThread.cpp b/source/game_sa/Audio/AESmoothFadeThread.cpp index 5ec1ff66f1..202fa30426 100644 --- a/source/game_sa/Audio/AESmoothFadeThread.cpp +++ b/source/game_sa/Audio/AESmoothFadeThread.cpp @@ -36,9 +36,9 @@ CAESmoothFadeThread::CAESmoothFadeThread() { void CAESmoothFadeThread::Initialise() { InitialiseRequestSlots(); m_threadHandle = CreateThread(nullptr, 0, &CAESmoothFadeThread::SmoothFadeProc, this, CREATE_SUSPENDED, &m_dwThreadId); - if (m_threadHandle == INVALID_HANDLE_VALUE || !m_threadHandle) // NOTSA: nullptr check + if (m_threadHandle == INVALID_HANDLE_VALUE || !m_threadHandle) { // NOTSA: nullptr check m_bThreadCreated = false; - else { + } else { SetThreadPriority(m_threadHandle, 0); m_bThreadCreated = true; m_bThreadInvalid = false; @@ -66,7 +66,7 @@ void CAESmoothFadeThread::WaitForExit() { } void CAESmoothFadeThread::Service() { - m_nLastServiceTime = (uint32)CAEAudioUtility::GetCurrentTimeInMilliseconds(); + m_nLastServiceTime = (uint32)CAEAudioUtility::GetCurrentTimeInMS(); for (auto& entry : m_aEntries) { if (entry.m_nStatus == eSmoothFadeEntryStatus::STATE_INACTIVE) continue; @@ -229,8 +229,14 @@ void CAESmoothFadeThread::SetBufferVolume(IDirectSoundBuffer* buffer, float volu } DWORD WINAPI CAESmoothFadeThread::SmoothFadeProc(void* smoothFade) { +#ifdef TRACY_ENABLE + tracy::SetThreadName("AESmoothFadeThread"); +#endif + auto* fade = static_cast(smoothFade); while (fade->m_bActive) { + ZoneScoped; + fade->Service(); Sleep(1); } diff --git a/source/game_sa/Audio/AESound.cpp b/source/game_sa/Audio/AESound.cpp index 74d65848e3..0134881377 100644 --- a/source/game_sa/Audio/AESound.cpp +++ b/source/game_sa/Audio/AESound.cpp @@ -22,7 +22,7 @@ void CAESound::InjectHooks() { RH_ScopedInstall(StopSound, 0x4EF1C0); RH_ScopedInstall(SetIndividualEnvironment, 0x4EF2B0); RH_ScopedInstall(UpdatePlayTime, 0x4EF2E0); - RH_ScopedInstall(GetRelativePosition, 0x4EF350); + RH_ScopedOverloadedInstall(GetRelativePosition, "OG", 0x4EF350, void(CAESound::*)(CVector&) const); RH_ScopedInstall(CalculateFrequency, 0x4EF390); RH_ScopedInstall(UpdateFrequency, 0x4EF3E0); RH_ScopedInstall(GetRelativePlaybackFrequencyWithDoppler, 0x4EF400); @@ -106,6 +106,7 @@ CAESound::~CAESound() { UnregisterWithPhysicalEntity(); } +// 0x4EF680 CAESound& CAESound::operator=(const CAESound& sound) { if (this == &sound) return *this; @@ -144,15 +145,18 @@ CAESound& CAESound::operator=(const CAESound& sound) { return *this; } +// 0x4EF1A0 void CAESound::UnregisterWithPhysicalEntity() { CEntity::ClearReference(m_pPhysicalEntity); } +// 0x4EF1C0 void CAESound::StopSound() { m_nPlayingState = eSoundState::SOUND_STOPPED; UnregisterWithPhysicalEntity(); } +// 0x4EF2B0 void CAESound::SetIndividualEnvironment(uint16 envFlag, uint16 bEnabled) { if (bEnabled) m_nEnvironmentFlags |= envFlag; @@ -180,43 +184,47 @@ void CAESound::UpdatePlayTime(int16 soundLength, int16 loopStartTime, int16 play return; } + assert(soundLength > 0); m_nCurrentPlayPosition = loopStartTime + (m_nCurrentPlayPosition % soundLength); } -void CAESound::GetRelativePosition(CVector* outPos) { - if (!GetFrontEnd()) - return CAEAudioEnvironment::GetPositionRelativeToCamera(outPos, &m_vecCurrPosn); - - *outPos = m_vecCurrPosn; +// 0x4EF350 +void CAESound::GetRelativePosition(CVector& out) const { + if (!GetFrontEnd()) { + CAEAudioEnvironment::GetPositionRelativeToCamera(&out, &m_vecCurrPosn); + } + out = m_vecCurrPosn; } +// 0x4EF390 void CAESound::CalculateFrequency() { - if (m_fSpeedVariability <= 0.0F || m_fSpeedVariability >= m_fSpeed) - m_fFrequency = m_fSpeed; - else - m_fFrequency = m_fSpeed + CAEAudioUtility::GetRandomNumberInRange(-m_fSpeedVariability, m_fSpeedVariability); + m_fFrequency = m_fSpeedVariability <= 0.0F || m_fSpeedVariability >= m_fSpeed + ? m_fSpeed + : m_fSpeed + CAEAudioUtility::GetRandomNumberInRange(-m_fSpeedVariability, m_fSpeedVariability); } +// 0x4EF3E0 void CAESound::UpdateFrequency() { - if (m_fSpeedVariability == 0.0F) + if (m_fSpeedVariability == 0.0F) { m_fFrequency = m_fSpeed; + } } +// 0x4EF400 float CAESound::GetRelativePlaybackFrequencyWithDoppler() { - if (GetFrontEnd()) - return m_fFrequency; - - return m_fFrequency * CAEAudioEnvironment::GetDopplerRelativeFrequency(m_fPrevCamDist, m_fCurrCamDist, m_nPrevTimeUpdate, m_nCurrTimeUpdate, m_fTimeScale); + return GetFrontEnd() + ? m_fFrequency + : m_fFrequency * CAEAudioEnvironment::GetDopplerRelativeFrequency(m_fPrevCamDist, m_fCurrCamDist, m_nPrevTimeUpdate, m_nCurrTimeUpdate, m_fTimeScale); } +// 0x4EF440 float CAESound::GetSlowMoFrequencyScalingFactor() { - if (GetUnpausable() || !CTimer::GetIsSlowMotionActive() || CCamera::GetActiveCamera().m_nMode == eCamMode::MODE_CAMERA) { - return 1.0F; - } - - return fSlowMoFrequencyScalingFactor; + return GetUnpausable() || !CTimer::GetIsSlowMotionActive() || CCamera::GetActiveCamera().m_nMode == eCamMode::MODE_CAMERA + ? 1.f + : fSlowMoFrequencyScalingFactor; } +// 0x4EF7A0 void CAESound::NewVPSLentry() { m_nHasStarted = 0; m_nPlayingState = eSoundState::SOUND_ACTIVE; @@ -226,6 +234,7 @@ void CAESound::NewVPSLentry() { CalculateFrequency(); } +// 0x4EF820 void CAESound::RegisterWithPhysicalEntity(CEntity* entity) { CAESound::UnregisterWithPhysicalEntity(); if (entity) { @@ -234,12 +243,14 @@ void CAESound::RegisterWithPhysicalEntity(CEntity* entity) { } } +// 0x4EF850 void CAESound::StopSoundAndForget() { m_bRequestUpdates = false; m_pBaseAudio = nullptr; StopSound(); } +// 0x4EF880 void CAESound::SetPosition(CVector vecPos) { if (!m_nLastFrameUpdate) { m_vecPrevPosn = vecPos; @@ -268,6 +279,7 @@ void CAESound::SetPosition(CVector vecPos) { } } +// 0x4EFA10 void CAESound::CalculateVolume() { if (GetFrontEnd()) m_fFinalVolume = m_fVolume - m_fSoundHeadRoom; @@ -280,6 +292,7 @@ void CAESound::CalculateVolume() { } } +// 0x4EFE50 void CAESound::Initialise(int16 bankSlotId, int16 sfxId, CAEAudioEntity* baseAudio, CVector posn, float volume, float maxDistance, float speed, float timeScale, uint8 ignoredServiceCycles, eSoundEnvironment environmentFlags, float speedVariability, int16 currPlayPosn) { @@ -312,6 +325,7 @@ void CAESound::Initialise(int16 bankSlotId, int16 sfxId, CAEAudioEntity* baseAud m_fFrequency = 1.0F; } +// 0x4EFF50 void CAESound::UpdateParameters(int16 curPlayPos) { if (GetLifespanTiedToPhysicalEntity()) { if (!m_pPhysicalEntity) @@ -327,6 +341,7 @@ void CAESound::UpdateParameters(int16 curPlayPos) { } } +// 0x4EFFD0 void CAESound::SoundHasFinished() { UpdateParameters(-1); UnregisterWithPhysicalEntity(); diff --git a/source/game_sa/Audio/AESound.h b/source/game_sa/Audio/AESound.h index 9743538389..89695d9b98 100644 --- a/source/game_sa/Audio/AESound.h +++ b/source/game_sa/Audio/AESound.h @@ -124,7 +124,8 @@ class CAESound { bool GetForcedFront() const { return m_bForcedFront; } void SetIndividualEnvironment(uint16 envFlag, uint16 bEnabled); // pass eSoundEnvironment as envFlag void UpdatePlayTime(int16 soundLength, int16 loopStartTime, int16 playProgress); - void GetRelativePosition(CVector* outPos); + void GetRelativePosition(CVector& out) const; + CVector GetRelativePosition() const { CVector out; GetRelativePosition(out); return out; } // NOTSA void CalculateFrequency(); void UpdateFrequency(); float GetRelativePlaybackFrequencyWithDoppler(); diff --git a/source/game_sa/Audio/AEStreamThread.cpp b/source/game_sa/Audio/AEStreamThread.cpp index 8ea15d4d39..dec0fb79d9 100644 --- a/source/game_sa/Audio/AEStreamThread.cpp +++ b/source/game_sa/Audio/AEStreamThread.cpp @@ -2,13 +2,17 @@ #include "AEStreamThread.h" #include "AEAudioUtility.h" +#include "AEMP3TrackLoader.h" +#include "AEUserRadioTrackManager.h" +#include "AEVorbisDecoder.h" void CAEStreamThread::InjectHooks() { RH_ScopedClass(CAEStreamThread); RH_ScopedCategory("Audio"); - RH_ScopedInstall(Initialise, 0x4F1680, { .reversed = false }); + RH_ScopedInstall(Initialise, 0x4F1680); RH_ScopedInstall(Start, 0x4F11F0); + RH_ScopedInstall(Stop, 0x4F1590); RH_ScopedInstall(Pause, 0x4F1200); RH_ScopedInstall(Resume, 0x4F1210); RH_ScopedInstall(WaitForExit, 0x4F1220); @@ -19,6 +23,7 @@ void CAEStreamThread::InjectHooks() { RH_ScopedInstall(GetPlayingTrackID, 0x4F1570); RH_ScopedInstall(StopTrack, 0x4F1580); RH_ScopedInstall(MainLoop, 0x4F15C0); + RH_ScopedInstall(Service, 0x4F1290); } // 0x4F11E0 @@ -27,12 +32,10 @@ CAEStreamThread::~CAEStreamThread() { } // 0x4F1680 -void CAEStreamThread::Initialise(CAEStreamingChannel* streamingChannel) { - return plugin::Call<0x4F1680, CAEStreamingChannel*>(streamingChannel); - +bool CAEStreamThread::Initialise(CAEStreamingChannel* streamingChannel) { m_bActive = true; m_bThreadActive = false; - field_12 = 0; + m_bNeedsService = 0; m_bPreparingStream = 0; m_bStopRequest = false; m_nPlayingTrackId = streamingChannel->GetPlayingTrackID(); @@ -40,14 +43,18 @@ void CAEStreamThread::Initialise(CAEStreamingChannel* streamingChannel) { m_nActiveTrackId = streamingChannel->GetActiveTrackID(); m_nTrackLengthMs = streamingChannel->GetLength(); - m_nHandle = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)(&CAEStreamThread::MainLoop), this, CREATE_SUSPENDED, (LPDWORD)(&m_lpThreadId)); + m_nHandle = CreateThread(nullptr, 0, &CAEStreamThread::MainLoop, this, CREATE_SUSPENDED, (LPDWORD)(&m_lpThreadId)); + assert(m_nHandle); SetThreadPriority(m_nHandle, 0); + InitializeCriticalSection(&m_criticalSection); OS_MutexObtain(&m_criticalSection); m_pStreamingChannel = streamingChannel; - // m_pMp3TrackLoader = new CAEMP3TrackLoader(); - // m_pMp3TrackLoader->Initialise(); + m_pMp3TrackLoader = new CAEMP3TrackLoader(); + m_pMp3TrackLoader->Initialise(); + + return m_bActive; } // 0x4F11F0 @@ -56,6 +63,14 @@ void CAEStreamThread::Start() { ResumeThread(m_nHandle); } +// 0x4F1590 +void CAEStreamThread::Stop() { + m_bThreadActive = false; + if (std::exchange(m_bActive, false)) { + delete m_pMp3TrackLoader; + } +} + // 0x4F1200 void CAEStreamThread::Pause() const { SuspendThread(m_nHandle); @@ -105,10 +120,10 @@ void CAEStreamThread::PlayTrack(uint32 iTrackId, int32 iNextTrackId, uint32 a3, m_iTrackId = iTrackId; m_iNextTrackId = iNextTrackId; field_1C = a3; - field_24 = a4; + m_TrackFlags = a4; m_bIsUserTrack = bIsUserTrack; m_bNextIsUserTrack = bNextIsUserTrack; - field_12 = 1; + m_bNeedsService = 1; OS_MutexRelease(&m_criticalSection); } @@ -119,29 +134,35 @@ void CAEStreamThread::StopTrack() { } // 0x4F15C0 -uint32 CAEStreamThread::MainLoop(void* param) { - auto* stream = reinterpret_cast(param); +DWORD WINAPI CAEStreamThread::MainLoop(LPVOID data) { +#ifdef TRACY_ENABLE + tracy::SetThreadName("AEStreamThread"); +#endif + + const auto self = reinterpret_cast(data); bool wasForeground = true; bool play = false; - while (stream->m_bThreadActive) { + while (self->m_bThreadActive) { + ZoneScoped; + bool isForeground = IsForegroundApp(); if (isForeground) { if (!wasForeground && play){ - stream->m_pStreamingChannel->Play(0, 0, 1.0f); + self->m_pStreamingChannel->Play(0, 0, 1.0f); } - auto start = CAEAudioUtility::GetCurrentTimeInMilliseconds(); - stream->Service(); - stream->m_pStreamingChannel->Service(); - auto end = CAEAudioUtility::GetCurrentTimeInMilliseconds(); + auto start = CAEAudioUtility::GetCurrentTimeInMS(); + self->Service(); + self->m_pStreamingChannel->Service(); + auto end = CAEAudioUtility::GetCurrentTimeInMS(); if (end - start < 5) { OS_ThreadSleep(uint32(start - end + 5)); } } else if (wasForeground) { - if (stream->m_pStreamingChannel->IsBufferPlaying()) { - stream->m_pStreamingChannel->Pause(); + if (self->m_pStreamingChannel->IsBufferPlaying()) { + self->m_pStreamingChannel->Pause(); play = true; } else { play = false; @@ -156,5 +177,79 @@ uint32 CAEStreamThread::MainLoop(void* param) { // 0x4F1290 void CAEStreamThread::Service() { - plugin::CallMethod<0x4F1290, CAEStreamThread*>(this); + m_pStreamingChannel->UpdatePlayTime(); + if (m_bStopRequest) { + m_pStreamingChannel->Stop(); + m_bStopRequest = false; + } + + const auto SaveStreamingState = [&] { + if (m_bPreparingStream) + return; + + m_nPlayingTrackId = m_pStreamingChannel->GetPlayingTrackID(); + m_nPlayTime = m_pStreamingChannel->GetPlayTime(); + m_nActiveTrackId = m_pStreamingChannel->GetActiveTrackID(); + m_nTrackLengthMs = m_pStreamingChannel->GetLength(); + }; + + if (!m_bNeedsService) { + SaveStreamingState(); + return; + } + + EnterCriticalSection(&m_criticalSection); + m_bNeedsService = false; + + const auto LoadDecoder = [&](bool userTrackCheck, uint32 trackId) { + CAEStreamingDecoder* ptr{}; + if (userTrackCheck) { + ptr = AEUserRadioTrackManager.LoadUserTrack(trackId); + } else { + ptr = new CAEVorbisDecoder(m_pMp3TrackLoader->GetDataStream(trackId), 0); + } + + if (ptr && !ptr->Initialise()) { + delete std::exchange(ptr, nullptr); + } + return ptr; + }; + + CAEStreamingDecoder* nextDecoder{}; + if (m_iNextTrackId != -1) { // RADIO_INVALID + nextDecoder = LoadDecoder(m_bNextIsUserTrack, m_iNextTrackId); + } else { + m_pStreamingChannel->SetNextStream(nullptr); + } + + if (m_iNextTrackId == -1 || m_pStreamingChannel->GetPlayingTrackID() != m_iTrackId) { + auto* currDecoder = LoadDecoder(m_bIsUserTrack, m_iTrackId); + if (currDecoder) { + currDecoder->SetCursor(currDecoder->GetStreamLengthMs()); + + m_pStreamingChannel->SetNextStream(nextDecoder); + m_pStreamingChannel->PrepareStream(currDecoder, m_TrackFlags, 1u); + m_bPreparingStream = false; + } else { + m_pStreamingChannel->SetNextStream(nullptr); + if (nextDecoder) { + m_pStreamingChannel->PrepareStream(nextDecoder, m_TrackFlags, 1u); + m_bPreparingStream = false; + } else { + m_bPreparingStream = true; + } + } + } else { + if (nextDecoder) + m_pStreamingChannel->SetNextStream(nextDecoder); + m_pStreamingChannel->SetReady(); + } + + if (m_bPreparingStream) { + m_nPlayingTrackId = m_iNextTrackId == -1 ? m_iTrackId : m_iNextTrackId; + m_nActiveTrackId = m_iNextTrackId == -1 ? m_iTrackId : m_iNextTrackId; + } + SaveStreamingState(); + + LeaveCriticalSection(&m_criticalSection); } diff --git a/source/game_sa/Audio/AEStreamThread.h b/source/game_sa/Audio/AEStreamThread.h index f47ce12529..4b37236f6e 100644 --- a/source/game_sa/Audio/AEStreamThread.h +++ b/source/game_sa/Audio/AEStreamThread.h @@ -12,7 +12,7 @@ class CAEStreamThread { CAEMP3TrackLoader* m_pMp3TrackLoader; bool m_bActive; bool m_bThreadActive; - char field_12; + char m_bNeedsService; bool m_bIsUserTrack; bool m_bNextIsUserTrack; char m_bPreparingStream; @@ -20,8 +20,8 @@ class CAEStreamThread { char field_17; uint32 m_iTrackId; uint32 field_1C; - uint32 m_iNextTrackId; - int8 field_24; + int32 m_iNextTrackId; + int8 m_TrackFlags; char field_25; char field_26; char field_27; @@ -37,9 +37,10 @@ class CAEStreamThread { CAEStreamThread() = default; // 0x4F11D0 ~CAEStreamThread(); - void Initialise(CAEStreamingChannel* streamingChannel); + bool Initialise(CAEStreamingChannel* streamingChannel); void Start(); + void Stop(); void Pause() const; void Resume() const; void WaitForExit() const; @@ -52,7 +53,7 @@ class CAEStreamThread { int32 GetActiveTrackID() const; int32 GetPlayingTrackID() const; - static uint32 MainLoop(void* param); + static DWORD WINAPI MainLoop(LPVOID); void Service(); }; diff --git a/source/game_sa/Audio/AudioEngine.cpp b/source/game_sa/Audio/AudioEngine.cpp index 0df5e6d476..c804e77ea0 100644 --- a/source/game_sa/Audio/AudioEngine.cpp +++ b/source/game_sa/Audio/AudioEngine.cpp @@ -16,8 +16,9 @@ void CAudioEngine::InjectHooks() { RH_ScopedClass(CAudioEngine); RH_ScopedCategory("Audio"); - //RH_ScopedInstall(CAudioEngine, 0x507670, { .reversed = false }); // default - // Install("CAudioEngine", "~CAudioEngine", 0x506CD0, &CAudioEngine::~CAudioEngine); default + RH_ScopedInstall(Constructor, 0x507670); + RH_ScopedInstall(Destructor, 0x506CD0); + RH_ScopedInstall(Initialise, 0x5B9C60); RH_ScopedInstall(Shutdown, 0x507CB0); RH_ScopedInstall(Restart, 0x506DB0); @@ -31,7 +32,7 @@ void CAudioEngine::InjectHooks() { RH_ScopedInstall(PauseBeatTrack, 0x507200); RH_ScopedInstall(RetuneRadio, 0x507E10); RH_ScopedOverloadedInstall(StartRadio, "", 0x507DF0, void (CAudioEngine::*)(tVehicleAudioSettings*)); - RH_ScopedOverloadedInstall(StartRadio, "1", 0x507DC0, void (CAudioEngine::*)(RadioStationId, int8)); + RH_ScopedOverloadedInstall(StartRadio, "1", 0x507DC0, void (CAudioEngine::*)(eRadioID, int8)); RH_ScopedInstall(ServiceLoadingTune, 0x5078A0); RH_ScopedInstall(ResumeAllSounds, 0x507440); RH_ScopedInstall(PauseAllSounds, 0x507430); @@ -105,29 +106,29 @@ bool CAudioEngine::Initialise() { CLoadingScreen::Pause(); if (!AEAudioHardware.Initialise()) { - DEV_LOG("[AudioEngine] Failed to initialise Audio Hardware"); + NOTSA_LOG_ERR("Failed to initialise Audio Hardware"); return false; } m_nBackgroundAudioChannel = AEAudioHardware.AllocateChannels(1); if (!AERadioTrackManager.Initialise(m_nBackgroundAudioChannel)) { - DEV_LOG("[AudioEngine] Failed to initialise Radio Track Manager"); + NOTSA_LOG_ERR("Failed to initialise Radio Track Manager"); return false; } if (!AECutsceneTrackManager.Initialise(m_nBackgroundAudioChannel)) { - DEV_LOG("[AudioEngine] Failed to initialise Cutscene Track Manager"); + NOTSA_LOG_ERR("Failed to initialise Cutscene Track Manager"); return false; } if (!AEAmbienceTrackManager.Initialise(m_nBackgroundAudioChannel)) { - DEV_LOG("[AudioEngine] Failed to initialise Ambience Track Manager"); + NOTSA_LOG_ERR("Failed to initialise Ambience Track Manager"); return false; } if (!AESoundManager.Initialise()) { - DEV_LOG("[AudioEngine] Failed to initialise Sound Manager"); + NOTSA_LOG_ERR("Failed to initialise Sound Manager"); return false; } @@ -151,9 +152,9 @@ bool CAudioEngine::Initialise() { m_PedlessSpeechAE.Initialise(); m_CollisionAE.Initialise(); - m_nCurrentRadioStationId = -1; - field_0 = false; - field_1 = false; + m_nCurrentRadioStationId = RADIO_INVALID; + m_bPlayingMissionCompleteTrack = false; + m_bStoppingMissionCompleteTrack = false; CLoadingScreen::Continue(); @@ -247,7 +248,7 @@ bool CAudioEngine::IsCutsceneTrackActive() { } // 0x507E10 -void CAudioEngine::RetuneRadio(int8 radioId) { +void CAudioEngine::RetuneRadio(eRadioID radioId) { if (!GetBeatTrackStatus()) AERadioTrackManager.RetuneRadio(radioId); } @@ -350,7 +351,7 @@ void CAudioEngine::ReportFrontendAudioEvent(eAudioEvents eventId, float volumeCh } // 0x506EC0 -void CAudioEngine::ReportBulletHit(CEntity* entity, eSurfaceType surface, CVector& posn, float angleWithColPointNorm) { +void CAudioEngine::ReportBulletHit(CEntity* entity, eSurfaceType surface, const CVector& posn, float angleWithColPointNorm) { m_CollisionAE.ReportBulletHit(entity, surface, posn, angleWithColPointNorm); } @@ -439,11 +440,11 @@ void CAudioEngine::StopCutsceneTrack(bool a2) { AECutsceneTrackManager.StopCutsceneTrack(); if (!a2) { - if (!field_0) + if (!m_bPlayingMissionCompleteTrack) return; - field_1 = true; - field_0 = false; + m_bStoppingMissionCompleteTrack = true; + m_bPlayingMissionCompleteTrack = false; return; } @@ -455,22 +456,22 @@ void CAudioEngine::StopCutsceneTrack(bool a2) { if (AERadioTrackManager.IsVehicleRadioActive()) { tVehicleAudioSettings* settings = CAEVehicleAudioEntity::StaticGetPlayerVehicleAudioSettingsForRadio(); AERadioTrackManager.StartRadio(settings); - field_0 = false; + m_bPlayingMissionCompleteTrack = false; return; } - field_0 = false; + m_bPlayingMissionCompleteTrack = false; return; } if (AERadioTrackManager.IsVehicleRadioActive()) AERadioTrackManager.StartRadio(m_nCurrentRadioStationId, 0, 0, 0); - m_nCurrentRadioStationId = -1; - field_0 = false; + m_nCurrentRadioStationId = RADIO_INVALID; + m_bPlayingMissionCompleteTrack = false; } // 0x507180 void CAudioEngine::PlayPreloadedBeatTrack(bool a2) { AECutsceneTrackManager.PlayPreloadedCutsceneTrack(); - field_0 = a2; + m_bPlayingMissionCompleteTrack = a2; } // 0x5071A0 @@ -488,7 +489,7 @@ tBeatInfo* CAudioEngine::GetBeatInfo() { // 0x5071D0 bool CAudioEngine::IsBeatInfoPresent() { auto beatInfo = GetBeatInfo(); - return beatInfo && beatInfo->m_beatInfoPresent; + return beatInfo && beatInfo->IsBeatInfoPresent; } // 0x507210 @@ -509,7 +510,7 @@ void CAudioEngine::StopAmbienceTrack(bool a1) { // 0x507270 bool CAudioEngine::DoesAmbienceTrackOverrideRadio() { - return AEAmbienceTrackManager.m_bStop; + return AEAmbienceTrackManager.m_OverrideRadio; } // 0x507280 @@ -602,17 +603,19 @@ void CAudioEngine::ResumeAllSounds() { // 0x507750 void CAudioEngine::Service() { + ZoneScoped; + m_FrontendAE.AddAudioEvent(AE_FRONTEND_WAKEUP_AMPLIFIER); if (!CTimer::GetIsPaused()) m_FrontendAE.AddAudioEvent(AE_FRONTEND_RADIO_RETUNE_STOP_PAUSED); int32 trackPlayTime = AEAudioHardware.GetTrackPlayTime(); - AEAudioHardware.GetChannelPlayTimes(m_nBackgroundAudioChannel, nullptr); - if (field_0 && trackPlayTime == -4) { - field_1 = true; - } else if (field_1) { + //AEAudioHardware.GetChannelPlayTimes(m_nBackgroundAudioChannel, nullptr); // Does nothing + if (m_bPlayingMissionCompleteTrack && trackPlayTime == -4) { + m_bStoppingMissionCompleteTrack = true; + } else if (m_bStoppingMissionCompleteTrack) { if (trackPlayTime == -6) { - field_1 = false; + m_bStoppingMissionCompleteTrack = false; if (CAERadioTrackManager::IsVehicleRadioActive()) { AERadioTrackManager.StartRadio(CAEVehicleAudioEntity::StaticGetPlayerVehicleAudioSettingsForRadio()); } @@ -708,9 +711,9 @@ void CAudioEngine::Reset() { AESoundManager.Reset(); AESoundManager.Service(); AESoundManager.PauseManually(true); - m_nCurrentRadioStationId = -1; - field_0 = false; - field_1 = false; + m_nCurrentRadioStationId = RADIO_INVALID; + m_bPlayingMissionCompleteTrack = false; + m_bStoppingMissionCompleteTrack = false; } // 0x507C30 @@ -739,7 +742,7 @@ int8 CAudioEngine::GetBeatTrackStatus() { } // 0x507DC0 -void CAudioEngine::StartRadio(RadioStationId id, int8 bassValue) { +void CAudioEngine::StartRadio(eRadioID id, int8 bassValue) { if (!GetBeatTrackStatus()) AERadioTrackManager.StartRadio(id, bassValue, 0, 0); } @@ -766,7 +769,7 @@ void CAudioEngine::SetRadioBassSetting(int8 nBassSet) { } // 0x506FC0 -void CAudioEngine::InitialiseRadioStationID(RadioStationId id) { +void CAudioEngine::InitialiseRadioStationID(eRadioID id) { AERadioTrackManager.InitialiseRadioStationID(id); } @@ -786,12 +789,12 @@ bool CAudioEngine::IsRadioRetuneInProgress() { } // 0x507000 -const char* CAudioEngine::GetRadioStationName(RadioStationId id) { +const char* CAudioEngine::GetRadioStationName(eRadioID id) { return AERadioTrackManager.GetRadioStationName(id); } // 0x507010 -void CAudioEngine::GetRadioStationNameKey(RadioStationId id, char* outStr) { +void CAudioEngine::GetRadioStationNameKey(eRadioID id, char* outStr) { AERadioTrackManager.GetRadioStationNameKey(id, outStr); } @@ -801,7 +804,7 @@ int32* CAudioEngine::GetRadioStationListenTimes() { } // 0x507040 -RadioStationId CAudioEngine::GetCurrentRadioStationID() { +eRadioID CAudioEngine::GetCurrentRadioStationID() { return AERadioTrackManager.GetCurrentRadioStationID(); } diff --git a/source/game_sa/Audio/AudioEngine.h b/source/game_sa/Audio/AudioEngine.h index fd948d4e73..ed7cc5115c 100644 --- a/source/game_sa/Audio/AudioEngine.h +++ b/source/game_sa/Audio/AudioEngine.h @@ -6,6 +6,7 @@ #include "AECollisionAudioEntity.h" #include "AEGlobalWeaponAudioEntity.h" #include "AEPedlessSpeechAudioEntity.h" +#include "AETrackLoader.h" class CEntity; class CColPoint; @@ -13,19 +14,19 @@ class CVector; class tBeatInfo { public: - char f0[12]; - char fC[148]; - int32 m_beatInfoPresent; - char fA4[8]; + tTrackInfo::tBeat BeatWindow[20]; + int32 IsBeatInfoPresent; + int32 BeatTypeThisFrame; + int32 BeatNumber; }; VALIDATE_SIZE(tBeatInfo, 0xAC); class CAudioEngine { public: - bool field_0; - bool field_1; - RadioStationId m_nCurrentRadioStationId; - RadioStationId m_nSavedRadioStationId; + bool m_bPlayingMissionCompleteTrack; + bool m_bStoppingMissionCompleteTrack; + eRadioID m_nCurrentRadioStationId; + eRadioID m_nSavedRadioStationId; int32 m_nBackgroundAudioChannel; tBeatInfo m_BeatInfo; CAEFrontendAudioEntity m_FrontendAE; @@ -73,7 +74,7 @@ class CAudioEngine { static void DisableEffectsLoading(); void ReportCollision(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, CVector& point, CVector* normal, float fCollisionImpact1, float fCollisionImpact2, bool playOnlyOneShotCollisionSound, bool unknown); - void ReportBulletHit(CEntity* entity, eSurfaceType surface, CVector& posn, float angleWithColPointNorm); + void ReportBulletHit(CEntity* entity, eSurfaceType surface, const CVector& posn, float angleWithColPointNorm); void ReportObjectDestruction(CEntity* entity); void ReportGlassCollisionEvent(eAudioEvents glassSoundType, Const CVector& posn); void ReportWaterSplash(CVector posn, float volume); @@ -87,21 +88,21 @@ class CAudioEngine { void ReportMissionAudioEvent(uint16 eventId, CPhysical* physical, float a3, float a4); void ReportFrontendAudioEvent(eAudioEvents eventId, float volumeChange = 0.0f, float speed = 1.0f); - void InitialiseRadioStationID(RadioStationId id); + void InitialiseRadioStationID(eRadioID id); void StartRadio(tVehicleAudioSettings* settings); - void StartRadio(int8, int8); + void StartRadio(eRadioID id, int8 bassValue); void StopRadio(tVehicleAudioSettings* settings, bool bDuringPause); void SetRadioAutoRetuneOnOff(bool); void SetBassEnhanceOnOff(bool enable); void SetRadioBassSetting(int8); bool HasRadioRetuneJustStarted(); - const char* GetRadioStationName(RadioStationId id); - void GetRadioStationNameKey(RadioStationId id, char* outStr); + const char* GetRadioStationName(eRadioID id); + void GetRadioStationNameKey(eRadioID id, char* outStr); int32* GetRadioStationListenTimes(); void DisplayRadioStationName(); - RadioStationId GetCurrentRadioStationID(); + eRadioID GetCurrentRadioStationID(); void PlayRadioAnnouncement(uint32); - void RetuneRadio(int8); + void RetuneRadio(eRadioID id); void PreloadCutsceneTrack(int16 trackId, bool wait); static void PlayPreloadedCutsceneTrack(); @@ -146,6 +147,18 @@ class CAudioEngine { void Save(); void Load(); #endif +private: // Wrappers for hooks + + // 0x507670 + CAudioEngine* Constructor() { + this->CAudioEngine::CAudioEngine(); + return this; + } + // 0x506CD0 + CAudioEngine* Destructor() { + this->CAudioEngine::~CAudioEngine(); + return this; + } }; VALIDATE_SIZE(CAudioEngine, 0x1FD8); diff --git a/source/game_sa/Audio/AudioZones.cpp b/source/game_sa/Audio/AudioZones.cpp index 95a6ede034..369b8550a4 100644 --- a/source/game_sa/Audio/AudioZones.cpp +++ b/source/game_sa/Audio/AudioZones.cpp @@ -2,30 +2,21 @@ #include "AudioZones.h" -int32 (&CAudioZones::m_aActiveBoxes)[10] = *(int32(*)[10])0xB6DC6C; -int32 (&CAudioZones::m_aActiveSpheres)[10] = *(int32(*)[10])0xB6DC94; - -uint32& CAudioZones::m_NumActiveBoxes = *(uint32*)0xB6DCBC; -uint32& CAudioZones::m_NumActiveSpheres = *(uint32*)0xB6DCC0; -uint32& CAudioZones::m_NumBoxes = *(uint32*)0xB6DCC4; -uint32& CAudioZones::m_NumSpheres = *(uint32*)0xB6DCC8; - -tAudioZoneBox (&CAudioZones::m_aBoxes)[NUM_AUDIO_BOXES] = *(tAudioZoneBox(*)[NUM_AUDIO_BOXES])0xB6DCD0; -tAudioZoneSphere (&CAudioZones::m_aSpheres)[NUM_AUDIO_SPHERES] = *(tAudioZoneSphere(*)[NUM_AUDIO_SPHERES])0xB6EBA8; - void CAudioZones::InjectHooks() { RH_ScopedClass(CAudioZones); RH_ScopedCategory("Audio"); - RH_ScopedInstall(Init, 0x5081A0, { .reversed = false }); - RH_ScopedInstall(RegisterAudioSphere, 0x5081C0, { .reversed = false }); - RH_ScopedInstall(RegisterAudioBox, 0x508240, { .reversed = false }); - RH_ScopedInstall(SwitchAudioZone, 0x508320, { .reversed = false }); - RH_ScopedInstall(Update, 0x5083C0, { .reversed = false }); + RH_ScopedInstall(Init, 0x5081A0); + RH_ScopedInstall(RegisterAudioSphere, 0x5081C0); + RH_ScopedInstall(RegisterAudioBox, 0x508240); + RH_ScopedInstall(SwitchAudioZone, 0x508320); + RH_ScopedInstall(Update, 0x5083C0); } // 0x5081A0 void CAudioZones::Init() { + ZoneScoped; + m_NumSpheres = 0; m_NumBoxes = 0; m_NumActiveSpheres = 0; @@ -33,21 +24,77 @@ void CAudioZones::Init() { } // 0x508240 -int32 CAudioZones::RegisterAudioBox(char* name, int32 id, bool b, float x1, float y1, float z1, float x2, float y2, float z2) { - return plugin::CallAndReturn(name, id, b, x1, y1, z1, x2, y2, z2); +void CAudioZones::RegisterAudioBox(char name[8], int32 id, bool isActive, CVector min, CVector max) { + + tAudioZoneBox audioZoneBox; + strcpy_s(audioZoneBox.m_szName, name); + audioZoneBox.m_bIsActive = isActive; // TODO: m_nFlags field has only 1 flag - Active or inactive and takes only 1 bit. Although gta uses 2 bytes for this, but how is the idea to define this single flag so as not to be confused in the future + audioZoneBox.m_nAudioZone = id; + audioZoneBox.m_Box = CompressedBox{ + .m_vecMin = CompressLargeVector(min), + .m_vecMax = CompressLargeVector(max), + }; + m_aBoxes[m_NumBoxes++] = audioZoneBox; } // 0x5081C0 -int32 CAudioZones::RegisterAudioSphere(char* name, int32 id, bool b, float x1, float y1, float z1, float radius) { - return plugin::CallAndReturn(name, id, b, x1, y1, z1, radius); +void CAudioZones::RegisterAudioSphere(char name[8], int32 id, bool isActive, CVector position, float radius) { + tAudioZoneSphere audioZoneSphere; + strcpy_s(audioZoneSphere.m_szName, name); + audioZoneSphere.m_nAudioZone = id; + audioZoneSphere.m_bIsActive = isActive; // TODO: m_nFlags field has only 1 flag - Active or inactive and takes only 1 bit. Although gta uses 2 bytes for this, but how is the idea to define this single flag so as not to be confused in the future + audioZoneSphere.m_Sphere = {position, radius}; + + m_aSpheres[m_NumSpheres++] = audioZoneSphere; } // 0x508320 -void CAudioZones::SwitchAudioZone(char* zoneName, bool enable) { - plugin::Call<0x508320, const char*, bool>(zoneName, enable); +void CAudioZones::SwitchAudioZone(const char* zoneName, bool enable) { + for (auto& sphere : GetAvailableSpheres()) { + if (!_stricmp(zoneName, sphere.m_szName)) + sphere.m_bIsActive = enable; + } + + for (auto& box : GetAvailableBoxes()) { + if (!_stricmp(zoneName, box.m_szName)) + box.m_bIsActive = enable; + } } // 0x5083C0 -void CAudioZones::Update(bool a1, CVector posn) { - plugin::Call<0x5083C0, bool, CVector>(a1, posn); +void CAudioZones::Update(bool forceUpdate, CVector posn) { + ZoneScoped; + + static CVector LastUpdateCoors{}; + + // TODO: FPS-independent counter + if (!forceUpdate && (CTimer::m_FrameCounter % 16) != 6 && DistanceBetweenPoints(posn, LastUpdateCoors) < 20.0f) + return; + + LastUpdateCoors = posn; + m_NumActiveSpheres = m_NumActiveBoxes = 0; + + for (auto&& [idx, sphere] : notsa::enumerate(GetAvailableSpheres())) { + // Do not add more to list if it's filled. + if (m_NumActiveSpheres >= std::size(m_aActiveSpheres)) + break; + + if (!sphere.m_bIsActive) + continue; + + if (sphere.m_Sphere.IsPointWithin(posn)) + m_aActiveSpheres[m_NumActiveSpheres++] = idx; + } + + for (auto&& [idx, box] : notsa::enumerate(GetAvailableBoxes())) { + // Do not add more to list if it's filled. + if (m_NumActiveBoxes >= std::size(m_aActiveBoxes)) + break; + + if (!box.m_bIsActive) + continue; + + if (CBox{box.m_Box}.IsPointInside(posn)) + m_aActiveBoxes[m_NumActiveBoxes++] = idx; + } } diff --git a/source/game_sa/Audio/AudioZones.h b/source/game_sa/Audio/AudioZones.h index 72a420ef99..fdcb452e34 100644 --- a/source/game_sa/Audio/AudioZones.h +++ b/source/game_sa/Audio/AudioZones.h @@ -1,48 +1,66 @@ #pragma once #include "CompressedBox.h" +#include -struct tAudioZoneSphere { - char m_szName[8]; - int16 m_nAudioZone; - uint16 m_nFlags; - CVector m_vPosn; - float m_fRadius; +struct tAudioZoneData { + char m_szName[8]; + int16 m_nAudioZone; + bool m_bIsActive : 1; +}; +VALIDATE_SIZE(tAudioZoneData, 0xC); + +struct tAudioZoneSphere : tAudioZoneData { + CSphere m_Sphere; }; VALIDATE_SIZE(tAudioZoneSphere, 0x1C); -struct tAudioZoneBox { - char m_Name[8]; - int16 m_nAudioZone; - uint16 m_Flags; +struct tAudioZoneBox : tAudioZoneData { CompressedBox m_Box; + + void DrawWireFrame(CRGBA color, const CMatrix& transform) const { + m_Box.DrawWireFrame(color, transform); + } }; VALIDATE_SIZE(tAudioZoneBox, 0x18); class CAudioZones { public: - static int32 (&m_aActiveBoxes)[10]; - static int32 (&m_aActiveSpheres)[10]; + static inline auto& m_aActiveBoxes = *reinterpret_cast*>(0xB6DC6C); + static inline auto& m_aActiveSpheres = *reinterpret_cast*>(0xB6DC94); - static uint32& m_NumActiveBoxes; - static uint32& m_NumActiveSpheres; - static uint32& m_NumBoxes; - static uint32& m_NumSpheres; + static inline auto& m_NumActiveBoxes = *reinterpret_cast(0xB6DCBC); + static inline auto& m_NumActiveSpheres = *reinterpret_cast(0xB6DCC0); + static inline auto& m_NumBoxes = *reinterpret_cast(0xB6DCC4); + static inline auto& m_NumSpheres = *reinterpret_cast(0xB6DCC8); static constexpr int32 NUM_AUDIO_BOXES = 158; - static tAudioZoneBox (&m_aBoxes)[NUM_AUDIO_BOXES]; + static inline auto& m_aBoxes = *reinterpret_cast*>(0xB6DCD0); static constexpr int32 NUM_AUDIO_SPHERES = 3; - static tAudioZoneSphere (&m_aSpheres)[NUM_AUDIO_SPHERES]; + static inline auto& m_aSpheres = *reinterpret_cast*>(0xB6EBA8); public: static void InjectHooks(); static void Init(); - static int32 RegisterAudioBox(char name[16], int32 id, bool b, float x1, float y1, float z1, float x2, float y2, float z2); - static int32 RegisterAudioSphere(char name[16], int32 id, bool b, float x1, float y1, float z1, float radius); + static void RegisterAudioBox(char name[8], int32 id, bool isActive, CVector min, CVector max); + static void RegisterAudioSphere(char name[8], int32 id, bool isActive, CVector position, float radius); + + static void SwitchAudioZone(const char* zoneName, bool enable); + static void Update(bool forceUpdate, CVector posn); + + static auto GetActiveAuZoBoxes() { // TODO/NOTE: This isn't how it works! See `m_aActiveBoxes` + return m_aBoxes | rng::views::take((size_t)m_NumBoxes); + } + + // TODO: idc about func at top rn. + static auto GetAvailableBoxes() { + return m_aBoxes | rng::views::take((size_t)m_NumBoxes); + } - static void SwitchAudioZone(char* zoneName, bool enable); - static void Update(bool a1, CVector posn); + static auto GetAvailableSpheres() { + return m_aSpheres | rng::views::take((size_t)m_NumSpheres); + } }; diff --git a/source/game_sa/Audio/OpenAL/OALBase.cpp b/source/game_sa/Audio/OpenAL/OALBase.cpp new file mode 100644 index 0000000000..f0b51e0e22 --- /dev/null +++ b/source/game_sa/Audio/OpenAL/OALBase.cpp @@ -0,0 +1,52 @@ +#include "StdInc.h" +#include "OALBase.h" + +#ifdef USE_OPENAL +OALBase::OALBase() { + m_refCount = 1; + ++livingCount; +} + +OALBase::~OALBase() { + --livingCount; +} + +void OALBase::Release() { + if (--m_refCount) + return; + + // OS_MutexObtain(trashMutex); + trashCan.push_back(this); + // OS_MutexRelease(trashMutex); +} + +void OALBase::AddRef() { + ++m_refCount; +} + +bool OALCheckErrors(std::string_view file, int32 line) { + const auto error = alGetError(); + if (error == AL_NO_ERROR) + return true; + + const auto message = [error] { + switch (error) { + case AL_INVALID_NAME: + return "Invalid name(ID) passed to an AL call."; + case AL_INVALID_ENUM: + return "Invalid enumeration passed to AL call."; + case AL_INVALID_VALUE: + return "Invalid value passed to AL call."; + case AL_INVALID_OPERATION: + return "Illegal AL call."; + case AL_OUT_OF_MEMORY: + return "Not enough memory to execute the AL call."; + default: + return "Unknown?"; + } + }(); + + NOTSA_LOG_ERR("OpenAL error at '{}' line {}: {}", file, line, message); + return false; +} +#endif diff --git a/source/game_sa/Audio/OpenAL/OALBase.h b/source/game_sa/Audio/OpenAL/OALBase.h new file mode 100644 index 0000000000..b28bb2e306 --- /dev/null +++ b/source/game_sa/Audio/OpenAL/OALBase.h @@ -0,0 +1,29 @@ +#pragma once +#ifdef USE_OPENAL +#include "extensions/HeapPtrArray.hpp" + +// This class has to be heap allocated! (with new`) +class OALBase { +public: + // Number of living `OALBase` object instances. + static inline uint32 livingCount{}; + + // To be garbage collected OpenAL objects. + static inline HeapPtrArray trashCan{}; + + uint32 m_refCount; + +public: + // NOTSA: possibly inlined in SA. + OALBase(); + + virtual ~OALBase(); + virtual void Release(); + + // NOTSA + virtual void AddRef(); +}; + +bool OALCheckErrors(std::string_view file, int32 line); +#define OAL_CHECKED(c) (c, OALCheckErrors(__FILE__, __LINE__)) +#endif diff --git a/source/game_sa/Audio/OpenAL/OALBuffer.cpp b/source/game_sa/Audio/OpenAL/OALBuffer.cpp new file mode 100644 index 0000000000..f0485cea14 --- /dev/null +++ b/source/game_sa/Audio/OpenAL/OALBuffer.cpp @@ -0,0 +1,46 @@ +#include "StdInc.h" +#include "OALBuffer.h" + +#ifdef USE_OPENAL +OALBuffer::OALBuffer(void* data, uint32 size, uint32 frequency, uint32 channels) + : OALBase() +{ + alGenBuffers(1, &m_bufferId); + alBufferData(m_bufferId, channels == 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16, data, size, frequency); + m_bufferSize = size; +} + +OALBuffer::OALBuffer(void* preloopData, uint32 preloopSize, void* loopData, uint32 loopSize, uint32 frequency, uint32 channels) + : OALBase() +{ + ALuint buffers[2]{}; + alGenBuffers(2, buffers); + m_bufferId = buffers[0]; + m_preloopBufferId = buffers[1]; + + const auto format = channels == 2 ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + + alBufferData(m_preloopBufferId, format, preloopData, preloopSize, frequency); + + if (loopSize > 0x18F) { + alBufferData(m_bufferId, format, loopData, loopSize, frequency); + } else { + auto* data = new uint8[4 * loopSize]; + for (auto i = 0; i < 4; i++) { + memcpy(&data[i * loopSize], loopData, loopSize); + } + alBufferData(m_bufferId, format, data, 4 * loopSize, frequency); + delete[] data; + } + + m_bufferSize = loopSize + preloopSize; +} + +OALBuffer::~OALBuffer() { + alDeleteBuffers(1, &m_bufferId); + + if (m_preloopBufferId) { + alDeleteBuffers(1, &m_preloopBufferId); + } +} +#endif diff --git a/source/game_sa/Audio/OpenAL/OALBuffer.h b/source/game_sa/Audio/OpenAL/OALBuffer.h new file mode 100644 index 0000000000..ec50268251 --- /dev/null +++ b/source/game_sa/Audio/OpenAL/OALBuffer.h @@ -0,0 +1,19 @@ +#pragma once +#include "OALBase.h" + +#ifdef USE_OPENAL +class OALBuffer final : public OALBase { +public: + ALuint m_bufferId{}; + ALuint m_preloopBufferId{0}; + uint32 m_bufferSize{}; + +public: + OALBuffer(void* data, uint32 size, uint32 frequency, uint32 channels); + OALBuffer(void* preloopData, uint32 preloopSize, void* loopData, uint32 loopSize, uint32 frequency, uint32 channels); + virtual ~OALBuffer() override; + + // NOTSA + OALBuffer(const OALBuffer&) = delete; +}; +#endif diff --git a/source/game_sa/Audio/OpenAL/OALSource.cpp b/source/game_sa/Audio/OpenAL/OALSource.cpp new file mode 100644 index 0000000000..1325189eda --- /dev/null +++ b/source/game_sa/Audio/OpenAL/OALSource.cpp @@ -0,0 +1,157 @@ +#include "StdInc.h" +#include "OALSource.h" +#include "OALBuffer.h" + +#ifdef USE_OPENAL +OALSource::~OALSource() { + if (m_oalBuffer && m_type != OALSourceType::OST_Preloop) { + if (m_sourceId) + alSourcei(m_sourceId, AL_BUFFER, 0); + + m_oalBuffer->Release(); + m_oalBuffer = nullptr; + } + + if (m_sourceId) { + switch (m_type) { + case OALSourceType::OST_Preloop: + case OALSourceType::OST_Streaming: + UnqueueBuffers(); + break; + } + + std::erase(activeSources, this); + alDeleteSources(1, &m_sourceId); + m_sourceId = 0; + } +} + +// Ensures that OpenAL source is created. +void OALSource::ObtainSource() { + if (m_sourceId) + return; + + alGenSources(1, &m_sourceId); + activeSources.push_back(this); + + if (m_oalBuffer && m_type != OALSourceType::OST_Preloop) { + alSourcei(m_sourceId, AL_BUFFER, m_oalBuffer->m_bufferId); + } +} + +void OALSource::SetZero(uint32 size, uint32 frequency) { + auto* zeros = new uint8[size]; + std::memset(zeros, 0, size); + SetData(zeros, size, frequency, 1); + delete[] zeros; +} + +void OALSource::SetStream() { + m_type = OALSourceType::OST_Streaming; +} + +void OALSource::SetData(void* data, uint32 size, uint32 frequency, uint32 channels) { + const auto buffer = new OALBuffer(data, size, frequency, channels); + SetBuffer(buffer); + buffer->Release(); // ??? +} + +void OALSource::SetBuffer(OALBuffer* buffer) { + assert(buffer); + + if (m_oalBuffer) + m_oalBuffer->Release(); + + m_type = buffer->m_preloopBufferId ? OALSourceType::OST_Preloop : OALSourceType::OST_Static; + + if (m_sourceId) + alSourcei(m_sourceId, AL_BUFFER, buffer->m_bufferId); + + m_oalBuffer = buffer; + buffer->AddRef(); +} + +// !!!ONLY PUT PTRS THAT ARE ALLOCATED WITH `NEW`!!! +void OALSource::QueueBuffer(OALBuffer* buffer) { + ObtainSource(); + UnqueueBuffers(); + + alSourceQueueBuffers(m_sourceId, 1, &buffer->m_bufferId); + m_queuedBuffers.push_back(buffer); + buffer->AddRef(); +} + +void OALSource::UnqueueBuffers() { + ALint processed{}; + alGetSourcei(m_sourceId, AL_BUFFERS_PROCESSED, &processed); + + for (auto i = 0; i < processed; i++) { + ALuint buffer{}; + alSourceUnqueueBuffers(m_sourceId, 1, &buffer); + + const auto queuedBufIt = rng::find_if(m_queuedBuffers, [&buffer](OALBuffer* buf) { + return buf->m_bufferId == buffer || buf->m_preloopBufferId == buffer; + }); + + if (queuedBufIt == m_queuedBuffers.end()) + continue; + auto queuedBuf = *queuedBufIt; + + m_posOffset += queuedBuf->m_bufferSize; + std::erase(m_queuedBuffers, queuedBuf); + queuedBuf->Release(); + } +} + +void OALSource::Play() { + ObtainSource(); + + if (m_type == OALSourceType::OST_Preloop) { + UnqueueBuffers(); + + alSourceQueueBuffers(m_sourceId, 1, &m_oalBuffer->m_preloopBufferId); + for (auto i = 0; i < 11; i++) { // todo: why 11? + alSourceQueueBuffers(m_sourceId, 1, &m_oalBuffer->m_bufferId); + } + } + + alSourcePlay(m_sourceId); + m_currentState = AL_PLAYING; + m_wasStopped = false; +} + +void OALSource::Pause() { + ObtainSource(); + alSourcePause(m_sourceId); +} + +float OALSource::GetVolume() { + return std::log(1.0f / m_currentVolume) * -8.6562f; // todo: constant +} + +void OALSource::SetVolume(float volume) +{ + ObtainSource(); + + if (const auto vol = std::min(1.0f / std::exp2f(volume / -6.0f), 1.0f); m_currentVolume != vol) { + m_currentVolume = vol; + alSourcef(m_sourceId, AL_GAIN, vol); + } +} + +void OALSource::Update() { + m_currentState = 0; + + if (m_type != OALSourceType::OST_Preloop) + return; + + ALint processed{}; + alGetSourcei(m_sourceId, AL_BUFFERS_PROCESSED, &processed); + + for (auto i = 0; i < processed; i++) { + ALuint buffer{}; + alSourceUnqueueBuffers(m_sourceId, 1, &buffer); + alSourceQueueBuffers(m_sourceId, 1, &m_oalBuffer->m_bufferId); + } +} +#endif diff --git a/source/game_sa/Audio/OpenAL/OALSource.h b/source/game_sa/Audio/OpenAL/OALSource.h new file mode 100644 index 0000000000..e4675ef075 --- /dev/null +++ b/source/game_sa/Audio/OpenAL/OALSource.h @@ -0,0 +1,49 @@ +#pragma once +#include "OALBase.h" + +#ifdef USE_OPENAL +class OALBuffer; + +enum class OALSourceType { + OST_Uninitialized, + OST_Static, + OST_Preloop, + OST_Streaming +}; + +class OALSource final : public OALBase { +public: + static inline HeapPtrArray activeSources{}; + + ALuint m_sourceId{0}; + OALSourceType m_type{ OALSourceType::OST_Uninitialized }; + uint32 field_14; + OALBuffer* m_oalBuffer{nullptr}; + HeapPtrArray m_queuedBuffers{}; + uint32 m_posOffset{0}; + float m_currentVolume{1.0f}; + ALuint m_currentState{}; + bool m_wasStopped{false}; + +public: + OALSource() : OALBase() {} + virtual ~OALSource() override; + + void ObtainSource(); + void SetZero(uint32 size, uint32 frequency); + void SetStream(); + void SetData(void* data, uint32 size, uint32 frequency, uint32 channels); + + void SetBuffer(OALBuffer* buffer); + void QueueBuffer(OALBuffer* buffer); + void UnqueueBuffers(); + + void Play(); + void Pause(); + + float GetVolume(); + void SetVolume(float volume); + + void Update(); +}; +#endif diff --git a/source/game_sa/Audio/entities/AEAudioEntity.cpp b/source/game_sa/Audio/entities/AEAudioEntity.cpp index 030e7f7be9..2901183cfb 100644 --- a/source/game_sa/Audio/entities/AEAudioEntity.cpp +++ b/source/game_sa/Audio/entities/AEAudioEntity.cpp @@ -17,12 +17,12 @@ bool CAEAudioEntity::StaticInitialise() { m_pAudioEventVolumes = new int8[45401]; auto file = CFileMgr::OpenFile("AUDIO\\CONFIG\\EVENTVOL.DAT", "r"); if (!file) { - DEV_LOG("[AudioEngine] Failed to open EVENTVOL.DAT"); + NOTSA_LOG_WARN("[AudioEngine] Failed to open EVENTVOL.DAT"); CFileMgr::CloseFile(file); return false; } if (CFileMgr::Read(file, m_pAudioEventVolumes, 45401) != 45401) { - DEV_LOG("[AudioEngine] Failed to read EVENTVOL.DAT"); + NOTSA_LOG_WARN("[AudioEngine] Failed to read EVENTVOL.DAT"); CFileMgr::CloseFile(file); return false; } diff --git a/source/game_sa/Audio/entities/AECollisionAudioEntity.cpp b/source/game_sa/Audio/entities/AECollisionAudioEntity.cpp index f4b4d415c6..610f9c562c 100644 --- a/source/game_sa/Audio/entities/AECollisionAudioEntity.cpp +++ b/source/game_sa/Audio/entities/AECollisionAudioEntity.cpp @@ -9,27 +9,28 @@ void CAECollisionAudioEntity::InjectHooks() { RH_ScopedClass(CAECollisionAudioEntity); RH_ScopedCategory("Audio/Entities"); - RH_ScopedInstall(Initialise, 0x5B9BD0, { .reversed = false }); + RH_ScopedInstall(Initialise, 0x5B9BD0); RH_ScopedInstall(InitialisePostLoading, 0x4DA050); - RH_ScopedInstall(AddCollisionSoundToList, 0x4DAAC0, { .reversed = false }); - RH_ScopedInstall(Reset, 0x4DA320, { .reversed = false }); - RH_ScopedInstall(ReportGlassCollisionEvent, 0x4DA070, { .reversed = false }); + RH_ScopedInstall(AddCollisionSoundToList, 0x4DAAC0); + RH_ScopedInstall(Reset, 0x4DA320); + RH_ScopedInstall(ReportGlassCollisionEvent, 0x4DA070); RH_ScopedInstall(UpdateLoopingCollisionSound, 0x4DA540, { .reversed = false }); - RH_ScopedInstall(GetCollisionSoundStatus, 0x4DA830, { .reversed = false }); + RH_ScopedInstall(GetCollisionSoundStatus, 0x4DA830, { .reversed = true }); RH_ScopedInstall(ReportObjectDestruction, 0x4DAB60, { .reversed = false }); RH_ScopedInstall(PlayOneShotCollisionSound, 0x4DB150, { .reversed = false }); RH_ScopedInstall(PlayLoopingCollisionSound, 0x4DB450, { .reversed = false }); RH_ScopedInstall(PlayBulletHitCollisionSound, 0x4DB7C0, { .reversed = false }); RH_ScopedInstall(ReportCollision, 0x4DBA10, { .reversed = false }); RH_ScopedInstall(ReportBulletHit, 0x4DBDF0); + RH_ScopedInstall(Service, 0x4DA2C0); - RH_ScopedOverloadedInstall(ReportWaterSplash, "0", 0x4DA190, void(CAECollisionAudioEntity::*)(CVector, float), { .reversed = false }); - RH_ScopedOverloadedInstall(ReportWaterSplash, "1", 0x4DAE40, void(CAECollisionAudioEntity::*)(CPhysical*, float, bool), { .reversed = false }); + RH_ScopedOverloadedInstall(ReportWaterSplash, "at-position", 0x4DA190, void(CAECollisionAudioEntity::*)(CVector, float), { .reversed = true }); + RH_ScopedOverloadedInstall(ReportWaterSplash, "for-physical", 0x4DAE40, void(CAECollisionAudioEntity::*)(CPhysical*, float, bool), { .reversed = false }); } // 0x5B9BD0 void CAECollisionAudioEntity::Initialise() { - plugin::CallMethod<0x5B9BD0, CAECollisionAudioEntity*>(this); + *this = {}; } // 0x4DA050 @@ -40,17 +41,51 @@ void CAECollisionAudioEntity::InitialisePostLoading() { // 0x4DA320 void CAECollisionAudioEntity::Reset() { - plugin::CallMethod<0x4DA320, CAECollisionAudioEntity*>(this); + for (auto& entry : m_Entries) { + if (entry.m_nStatus != COL_AUDIO_ENTRY_STATUS_2) + continue; + + if (entry.m_Sound) + entry.m_Sound->StopSoundAndForget(); + + entry = {}; + } } // 0x4DAAC0 -void CAECollisionAudioEntity::AddCollisionSoundToList(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, CAESound* sound, int32 status) { - plugin::CallMethod<0x4DAAC0, CAECollisionAudioEntity*, CEntity*, CEntity*, uint8, uint8, CAESound*, int32>(this, entity1, entity2, surf1, surf2, sound, status); +void CAECollisionAudioEntity::AddCollisionSoundToList(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, CAESound* sound, + eCollisionAudioEntryStatus status) +{ + // Find an entry with no sound. + const auto newEntry = rng::find_if(m_Entries, [](tCollisionAudioEntry& entry) { + return !entry.m_Sound; + }); + + if (newEntry == m_Entries.end()) { + // Game tries to access m_Entries[300] in this case. + NOTSA_UNREACHABLE(); + } + + // ? check + newEntry->m_Entity1 = entity1; + newEntry->m_Entity2 = entity2; + newEntry->m_nSurface1 = surf1; + newEntry->m_nSurface2 = surf2; + newEntry->m_Sound = sound; + newEntry->m_nStatus = status; + newEntry->m_nTime = status == COL_AUDIO_ENTRY_STATUS_2 ? CTimer::GetTimeInMS() + 100 : 0; + ++m_nActiveCollisionSounds; } // 0x4DA830 -int8 CAECollisionAudioEntity::GetCollisionSoundStatus(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, int32* a5) { - return plugin::CallMethodAndReturn(this, entity1, entity2, surf1, surf2, a5); +void CAECollisionAudioEntity::GetCollisionSoundStatus(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, int32& outIndex) { + const auto foundEntry = rng::find_if(m_Entries, [entity1, entity2](tCollisionAudioEntry& entry) { + return entry.m_Entity1 == entity1 && entry.m_Entity2 == entity2 + || entry.m_Entity1 == entity2 && entry.m_Entity2 == entity1; + }); + + // 300 is possibly unintended. + outIndex = foundEntry != m_Entries.end() ? std::distance(m_Entries.begin(), foundEntry) : 300; } // 0x4DB150 @@ -69,7 +104,7 @@ void CAECollisionAudioEntity::UpdateLoopingCollisionSound() { } // 0x4DB7C0 -void CAECollisionAudioEntity::PlayBulletHitCollisionSound(eSurfaceType surface, CVector& posn, float angleWithColPointNorm) { +void CAECollisionAudioEntity::PlayBulletHitCollisionSound(eSurfaceType surface, const CVector& posn, float angleWithColPointNorm) { if (surface >= NUM_FUCKING_SURFACES) return; @@ -80,13 +115,13 @@ void CAECollisionAudioEntity::PlayBulletHitCollisionSound(eSurfaceType surface, { do iRand = CAEAudioUtility::GetRandomNumberInRange(7, 9); - while (iRand == m_nRandom); + while (iRand == m_nLastBulletHitSoundID); } else if (g_surfaceInfos.IsAudioWater(surface)) { do iRand = CAEAudioUtility::GetRandomNumberInRange(16, 18); - while (iRand == m_nRandom); + while (iRand == m_nLastBulletHitSoundID); maxDistance = 2.0f; volume = volume + 6.0f; } @@ -94,7 +129,7 @@ void CAECollisionAudioEntity::PlayBulletHitCollisionSound(eSurfaceType surface, { do iRand = CAEAudioUtility::GetRandomNumberInRange(19, 21); - while (iRand == m_nRandom); + while (iRand == m_nLastBulletHitSoundID); } else if (g_surfaceInfos.IsAudioMetal(surface)) { @@ -103,43 +138,118 @@ void CAECollisionAudioEntity::PlayBulletHitCollisionSound(eSurfaceType surface, { do iRand = CAEAudioUtility::GetRandomNumberInRange(10, 12); - while (iRand == m_nRandom); + while (iRand == m_nLastBulletHitSoundID); } else { do iRand = CAEAudioUtility::GetRandomNumberInRange(4, 6); - while (iRand == m_nRandom); + while (iRand == m_nLastBulletHitSoundID); } } else if (g_surfaceInfos.IsAudioGravelConcreteOrTile(surface)) { do iRand = CAEAudioUtility::GetRandomNumberInRange(13, 15); - while (iRand == m_nRandom); + while (iRand == m_nLastBulletHitSoundID); } else { do iRand = CAEAudioUtility::GetRandomNumberInRange(1, 3); - while (iRand == m_nRandom); + while (iRand == m_nLastBulletHitSoundID); } if (iRand >= 0) { CAESound sound; sound.Initialise(3, iRand, this, posn, volume, maxDistance, 1.0f, 1.0f, 0, SOUND_DEFAULT, 0.02f, 0); AESoundManager.RequestNewSound(&sound); - m_nRandom = iRand; + m_nLastBulletHitSoundID = iRand; } } // 0x4DA070 -void CAECollisionAudioEntity::ReportGlassCollisionEvent(int32 glassSoundType, Const CVector& posn, uint32 time) { - plugin::CallMethod<0x4DA070, CAECollisionAudioEntity*, int32, const CVector&, uint32>(this, glassSoundType, posn, time); +void CAECollisionAudioEntity::ReportGlassCollisionEvent(eAudioEvents glassSoundType, Const CVector& posn, uint32 time) { + auto speed = 1.0f; + const auto sfxId = [glassSoundType, &speed] { + switch (glassSoundType) { + case AE_GLASS_HIT: + return 51; + case AE_GLASS_CRACK: + return 68; + case AE_GLASS_BREAK_SLOW: + speed = 0.75f; + return 56; + case AE_GLASS_BREAK_FAST: + return 56; + case AE_GLASS_HIT_GROUND: + return CAEAudioUtility::GetRandomNumberInRange(15, 18); + case AE_GLASS_HIT_GROUND_SLOW: + speed = 0.56f; + return CAEAudioUtility::GetRandomNumberInRange(15, 18); + default: + return -1; // Invalid audio event + } + }(); + + if (sfxId == -1) + return; + + m_tempSound.Initialise( + 2, + sfxId, + this, + posn, + GetDefaultVolume(glassSoundType), + 1.5f, + speed + ); + + if (time) { + auto& snd = m_tempSound; + snd.m_fMaxVolume = (float)(time + CTimer::GetTimeInMS()); + snd.m_nEvent = glassSoundType; + snd.m_bRequestUpdates = true; + } } // 0x4DA190 void CAECollisionAudioEntity::ReportWaterSplash(CVector posn, float volume) { - return plugin::CallMethod<0x4DA190, CAECollisionAudioEntity*, CVector, float>(this, posn, volume); + if (!AEAudioHardware.IsSoundBankLoaded(39, 2)) { + if (!AudioEngine.IsLoadingTuneActive()) + AEAudioHardware.LoadSoundBank(39, 2); + + return; + } + + m_tempSound.Initialise( + 2, + 67, + this, + posn, + GetDefaultVolume(AE_WATER_SPLASH) + volume, + 2.5f, + 1.26f, + 1.0f, + 0u, + SOUND_REQUEST_UPDATES + ); + m_tempSound.m_nEvent = AE_FRONTEND_SELECT; + AESoundManager.RequestNewSound(&m_tempSound); + + m_tempSound.Initialise( + 2, + 66, + this, + posn, + GetDefaultVolume(AE_WATER_SPLASH) + volume, + 2.5f, + 0.0f, + 1.0f, + 0u, + SOUND_REQUEST_UPDATES + ); + m_tempSound.m_nEvent = AE_FRONTEND_BACK; + m_tempSound.m_fMaxVolume = static_cast(CTimer::GetTimeInMS() + 166); } // 0x4DAE40 @@ -158,17 +268,25 @@ void CAECollisionAudioEntity::ReportCollision(CEntity* entity1, CEntity* entity2 } // 0x4DBDF0 -void CAECollisionAudioEntity::ReportBulletHit(CEntity* entity, eSurfaceType surface, CVector& posn, float angleWithColPointNorm) { +void CAECollisionAudioEntity::ReportBulletHit(CEntity* entity, eSurfaceType surface, const CVector& posn, float angleWithColPointNorm) { if (AEAudioHardware.IsSoundBankLoaded(27, 3)) { if (entity && entity->IsVehicle()) { - surface = entity->AsVehicle()->IsSubBMX() ? eSurfaceType(188) : SURFACE_CAR; // todo: C* Surface + surface = entity->AsVehicle()->IsSubBMX() + ? eSurfaceType(188) // todo: C* Surface + : SURFACE_CAR; } - PlayBulletHitCollisionSound(surface, posn, angleWithColPointNorm); } } // 0x4DA2C0 void CAECollisionAudioEntity::Service() { - plugin::CallMethod<0x4DA2C0, CAECollisionAudioEntity*>(this); + const auto time = CTimer::GetTimeInMS(); + for (auto& entry : m_Entries) { + if (entry.m_nStatus != COL_AUDIO_ENTRY_STATUS_2 || time < entry.m_nTime) + continue; + + entry = {}; + --m_nActiveCollisionSounds; + } } diff --git a/source/game_sa/Audio/entities/AECollisionAudioEntity.h b/source/game_sa/Audio/entities/AECollisionAudioEntity.h index 1ebcf8486d..6a15e865e5 100644 --- a/source/game_sa/Audio/entities/AECollisionAudioEntity.h +++ b/source/game_sa/Audio/entities/AECollisionAudioEntity.h @@ -2,24 +2,33 @@ #include "AEAudioEntity.h" +enum eCollisionAudioEntryStatus : uint8 { + COL_AUDIO_ENTRY_STATUS_0, + COL_AUDIO_ENTRY_STATUS_2 = 2, +}; + struct tCollisionAudioEntry { - CEntity* m_Entity1; - CEntity* m_Entity2; - CAESound* m_Sound; - int32 m_nTime; - uint8 m_nStatus; - eSurfaceType m_nSurface1; - eSurfaceType m_nSurface2; + CEntity* m_Entity1{nullptr}; + CEntity* m_Entity2{nullptr}; + CAESound* m_Sound{nullptr}; + uint32 m_nTime{0}; + eCollisionAudioEntryStatus m_nStatus{COL_AUDIO_ENTRY_STATUS_0}; + eSurfaceType m_nSurface1{NUM_FUCKING_SURFACES + 1}; // ? + eSurfaceType m_nSurface2{NUM_FUCKING_SURFACES + 1}; // ? + + tCollisionAudioEntry() = default; }; VALIDATE_SIZE(tCollisionAudioEntry, 0x14); class NOTSA_EXPORT_VTABLE CAECollisionAudioEntity : public CAEAudioEntity { public: - int16 m_aHistory[194]; // NUM_FUCKING_SURFACES - int16 field_200; - int16 m_nRandom; - int32 m_nCurrentId; - tCollisionAudioEntry m_Entries[300]; + static constexpr auto NUM_ENTRIES = 300u; + + int16 m_aHistory[NUM_FUCKING_SURFACES]{255}; + int16 m_nLastBulletHitSoundID{-1}; + int16 m_nRandom{-1}; + int32 m_nActiveCollisionSounds{0}; + std::array m_Entries{}; public: static void InjectHooks(); @@ -28,22 +37,22 @@ class NOTSA_EXPORT_VTABLE CAECollisionAudioEntity : public CAEAudioEntity { static void InitialisePostLoading(); void Reset(); - void AddCollisionSoundToList(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, CAESound* sound, int32 status); + void AddCollisionSoundToList(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, CAESound* sound, eCollisionAudioEntryStatus status); - int8 GetCollisionSoundStatus(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, int32* a5); + void GetCollisionSoundStatus(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, int32& outIndex); void PlayLoopingCollisionSound(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, float a5, CVector& posn, uint8 a7); void UpdateLoopingCollisionSound(); void PlayOneShotCollisionSound(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, float a5, CVector& posn); - void PlayBulletHitCollisionSound(eSurfaceType surface, CVector& posn, float angleWithColPointNorm); + void PlayBulletHitCollisionSound(eSurfaceType surface, const CVector& posn, float angleWithColPointNorm); - void ReportGlassCollisionEvent(int32 glassSoundType, Const CVector& posn, uint32 time); + void ReportGlassCollisionEvent(eAudioEvents glassSoundType, Const CVector& posn, uint32 time); void ReportWaterSplash(CVector posn, float volume); void ReportWaterSplash(CPhysical* physical, float height, bool splashMoreThanOnce); void ReportObjectDestruction(CEntity* entity); void ReportCollision(CEntity* entity1, CEntity* entity2, eSurfaceType surf1, eSurfaceType surf2, CVector& colPoint, CVector* normal, float collisionImpact1, float collisionImpact2, bool bOnlyPlayOneShotCollisionSound, bool unknown); - void ReportBulletHit(CEntity* entity, eSurfaceType surface, CVector& posn, float angleWithColPointNorm); + void ReportBulletHit(CEntity* entity, eSurfaceType surface, const CVector& posn, float angleWithColPointNorm); void Service(); }; diff --git a/source/game_sa/Audio/entities/AEPedSpeechAudioEntity.cpp b/source/game_sa/Audio/entities/AEPedSpeechAudioEntity.cpp index efc0f03956..b73928dfe0 100644 --- a/source/game_sa/Audio/entities/AEPedSpeechAudioEntity.cpp +++ b/source/game_sa/Audio/entities/AEPedSpeechAudioEntity.cpp @@ -1,9 +1,9 @@ #include "StdInc.h" -#include "StdInc.h" - #include "AEPedSpeechAudioEntity.h" #include "AEAudioUtility.h" +#include "PedClothesDesc.h" +#include int16& CAEPedSpeechAudioEntity::s_nCJWellDressed = *(int16*)0xB613D0; int16& CAEPedSpeechAudioEntity::s_nCJFat = *(int16*)0xB613D4; @@ -23,9 +23,8 @@ int16& CAEPedSpeechAudioEntity::s_pConversationPedSlot1 = *(int16*)0xB61408; CPed*& CAEPedSpeechAudioEntity::s_pConversationPed2 = *(CPed**)0xB6140C; CPed*& CAEPedSpeechAudioEntity::s_pConversationPed1 = *(CPed**)0xB61410; int16& CAEPedSpeechAudioEntity::s_NextSpeechSlot = *(int16*)0xB61414; -int16& CAEPedSpeechAudioEntity::s_PhraseMemory = *(int16*)0xB61418; -// CAEPedSpeechAudioEntity::Slot (&CAEPedSpeechAudioEntity::s_PedSpeechSlots)[6] = *(CAEPedSpeechAudioEntity::Slot(*)[6])0xB61C38; -uint32 (&gGlobalSpeechContextNextPlayTime)[360] = *(uint32(*)[360])0xB61670; +static inline auto& gGlobalSpeechContextNextPlayTime = *reinterpret_cast*>(0xB61670); +static inline auto& gSpeechContextLookup = *reinterpret_cast*>(0x8C6A68); // 0x4E4F10 CAEPedSpeechAudioEntity::CAEPedSpeechAudioEntity() : CAEAudioEntity() { @@ -43,28 +42,59 @@ CAEPedSpeechAudioEntity::CAEPedSpeechAudioEntity() : CAEAudioEntity() { } // 0x4E4600 -int8 CAEPedSpeechAudioEntity::IsGlobalContextImportantForInterupting(int16 a1) { - return plugin::CallAndReturn(a1); +bool __stdcall CAEPedSpeechAudioEntity::IsGlobalContextImportantForInterupting(int16 globalCtx) { + // return plugin::CallAndReturn(a1); + switch (globalCtx) { + case 13: + case 15: + case 125: + case 126: + case 127: + return true; + default: + return false; + } } -// 0x4E46F0 -int8 CAEPedSpeechAudioEntity::IsGlobalContextUberImportant(int16 a1) { - return plugin::CallAndReturn(a1); +// 0x4E46F0 - unused +bool CAEPedSpeechAudioEntity::IsGlobalContextUberImportant(int16 globalCtx) { + return false; } // 0x4E4700 -int16 CAEPedSpeechAudioEntity::GetNextMoodToUse(int16 a1) { - return plugin::CallAndReturn(a1); +int16 __stdcall CAEPedSpeechAudioEntity::GetNextMoodToUse(int16 lastMood) { + switch (lastMood) { + case 0: + case 7: + return 1; + case 6: + return 0; + case 8: + return 4; + default: + return 5; + } } // 0x4E4760 -int32 CAEPedSpeechAudioEntity::GetVoiceForMood(int16 a1) { - return plugin::CallAndReturn(a1); +int32 __stdcall CAEPedSpeechAudioEntity::GetVoiceForMood(int16 mood) { + auto rnd = CAEAudioUtility::GetRandomNumberInRange(0, 1); + if (mood < 0 || mood >= 10) + return rnd + 10; + return rnd + 2 * mood; } // 0x4E4950 int16 CAEPedSpeechAudioEntity::CanWePlayScriptedSpeech() { - return plugin::CallAndReturn(); + for (auto i = 0; i < 5; i++) { + const auto slot = (s_NextSpeechSlot + i) % 5; + if (s_PedSpeechSlots[slot].m_nState) + continue; + + s_NextSpeechSlot = slot; + return slot; + } + return -1; } // 0x4E4AE0 @@ -89,7 +119,18 @@ int16 CAEPedSpeechAudioEntity::GetCurrentCJMood() { // 0x5B98C0 void CAEPedSpeechAudioEntity::StaticInitialise() { - plugin::Call<0x5B98C0>(); + rng::for_each(s_PedSpeechSlots, &tSpeechSlot::Reset); + rng::for_each(s_PhraseMemory, &tPhraseMemory::Reset); + s_NextSpeechSlot = 0; + Reset(); + s_pConversationPed1 = s_pConversationPed2 = nullptr; + s_pConversationPedSlot1 = s_pConversationPedSlot2 = 0; + s_pPlayerConversationPed = nullptr; + s_bPedConversationHappening = s_bPlayerConversationHappening = false; + rng::fill(s_Conversation, -1); + s_nCJBasicMood = s_nCJGangBanging = s_nCJFat = s_nCJWellDressed = -1; + s_NextSpeechSlot = s_nCJMoodOverrideTime = 0; + s_bAllSpeechDisabled = s_bAPlayerSpeaking = s_bForceAudible = 0; } // 0x4E4470 @@ -188,8 +229,8 @@ void CAEPedSpeechAudioEntity::DisableAllPedSpeech() { } // 0x4E44F0 -bool CAEPedSpeechAudioEntity::IsGlobalContextPain(int16 a1) { - return plugin::CallAndReturn(this, a1); +bool CAEPedSpeechAudioEntity::IsGlobalContextPain(int16 globalCtx) { + return globalCtx > 339 && globalCtx < 359; } // 0x4E3ED0 @@ -204,7 +245,36 @@ void CAEPedSpeechAudioEntity::EnableAllPedSpeech() { // 0x4E4270 bool CAEPedSpeechAudioEntity::IsCJDressedInForGangSpeech() { - return plugin::CallAndReturn(); + static constexpr struct { + eClothesTexturePart clothesPart; + const char* textureName; + } GANG_SPEECH_CLOTHES[] = { + { CLOTHES_TEXTURE_TORSO, "hoodyagreen" }, + { CLOTHES_TEXTURE_TORSO, "shirtbgang" }, + { CLOTHES_TEXTURE_LEGS, "tracktrgang" }, + { CLOTHES_TEXTURE_LEGS, "denimsgang" }, + { CLOTHES_TEXTURE_HATS, "bandgang" }, + { CLOTHES_TEXTURE_HATS, "hockey" }, + { CLOTHES_TEXTURE_HATS, "capgang" }, + { CLOTHES_TEXTURE_HATS, "capgangback" }, + { CLOTHES_TEXTURE_HATS, "capgangside" }, + { CLOTHES_TEXTURE_HATS, "capgangover" }, + { CLOTHES_TEXTURE_HATS, "capgangup" }, + { CLOTHES_TEXTURE_GLASSES, "bandred3" }, + { CLOTHES_TEXTURE_GLASSES, "bandblue3" }, + { CLOTHES_TEXTURE_GLASSES, "bandgang3" }, + { CLOTHES_TEXTURE_GLASSES, "bandblack3" }, + }; + + if (!FindPlayerPed(PED_TYPE_PLAYER1)) + return false; + + if (CGameLogic::FindCityClosestToPoint(FindPlayerCoors()) != LEVEL_NAME_LOS_SANTOS) + return false; + + return rng::any_of(GANG_SPEECH_CLOTHES, [pcd = FindPlayerPed()->GetClothesDesc()](auto& gc) { + return pcd->m_anTextureKeys[gc.clothesPart] == CKeyGen::GetUppercaseKey(gc.textureName); + }); } // 0x4E4260 @@ -214,8 +284,8 @@ int8 CAEPedSpeechAudioEntity::GetSexForSpecialPed(uint32 a1) { // Methods // 0x4E46B0 -bool CAEPedSpeechAudioEntity::IsGlobalContextImportantForWidescreen(int16 a1) { - return plugin::CallMethodAndReturn(this, a1); +bool CAEPedSpeechAudioEntity::IsGlobalContextImportantForWidescreen(int16 globalCtx) { + return notsa::contains({4, 2}, m_nVoiceType) || notsa::contains({13, 15, 116}, globalCtx); } // 0x4E47E0 @@ -224,23 +294,64 @@ int32 CAEPedSpeechAudioEntity::GetRepeatTime(int16 a1) { } // 0x4E4840 -void CAEPedSpeechAudioEntity::LoadAndPlaySpeech(uint32 a2) { - plugin::CallMethod<0x4E4840>(this, a2); +void CAEPedSpeechAudioEntity::LoadAndPlaySpeech(uint32 offset) { + //plugin::CallMethod<0x4E4840>(this, offset); + auto& currSpeech = GetCurrentPedSpeech(); + if (currSpeech.m_nState != 0 && currSpeech.m_nState != 4) + return; + + AEAudioHardware.LoadSound(m_nBankId, m_nSoundId, m_nPedSpeechSlotIndex + 20); // TODO: Helper + currSpeech.m_nState = 1; + currSpeech.m_nBankId = m_nBankId; + currSpeech.m_nSoundId = m_nSoundId; + currSpeech.m_PedSpeechAE = this; + currSpeech.m_nTime = CTimer::GetTimeInMS() + offset; + currSpeech.m_nVoiceType = m_nVoiceType; + currSpeech.m_nPhraseId = m_nCurrentPhraseId; + // byte_B61C50[28 * m_nPedSpeechSlotIndex] = field_9C; } // 0x4E49B0 -int32 CAEPedSpeechAudioEntity::GetNumSlotsPlayingContext(int16 a2) { - return plugin::CallMethodAndReturn(this, a2); +int32 CAEPedSpeechAudioEntity::GetNumSlotsPlayingContext(int16 context) { + return rng::count_if(s_PedSpeechSlots, [&](tSpeechSlot& speech) { + return speech.m_nState && speech.m_nPhraseId == context; + }); } // 0x4E49E0 -int32 CAEPedSpeechAudioEntity::GetNextPlayTime(int16 a2) { - return plugin::CallMethodAndReturn(this, a2); +uint32 CAEPedSpeechAudioEntity::GetNextPlayTime(int16 globalCtx) { + if (globalCtx > 359) // todo: const + return 0; + + if (IsGlobalContextPain(globalCtx)) + return field_B4[globalCtx - 340]; + + return gGlobalSpeechContextNextPlayTime[globalCtx]; } // 0x4E4A20 -void CAEPedSpeechAudioEntity::SetNextPlayTime(int16 a2) { - plugin::CallMethod<0x4E4A20>(this, a2); +void CAEPedSpeechAudioEntity::SetNextPlayTime(int16 globalCtx) { + // plugin::CallMethod<0x4E4A20>(this, globalCtx); + + if (globalCtx > 359) + return; + + for (auto& lookup : gSpeechContextLookup) { + if (lookup[0] == -1) + break; + + if (lookup[0] != globalCtx) + continue; + + const auto playTime = lookup[6] + CAEAudioUtility::GetRandomNumberInRange(1, 1000); + auto& ctxNextPlayTime = [&]() -> uint32& { + if (IsGlobalContextPain(globalCtx)) + return field_B4[globalCtx - 340]; + else + return gGlobalSpeechContextNextPlayTime[globalCtx]; + }() = CTimer::GetTimeInMS() + playTime; + return; + } } // 0x4E56D0 @@ -423,16 +534,16 @@ void CAEPedSpeechAudioEntity::InjectHooks() { RH_ScopedCategory("Audio/Entities"); RH_ScopedInstall(Constructor, 0x4E4F10); - RH_ScopedInstall(IsGlobalContextImportantForInterupting, 0x4E4600, { .reversed = false }); - RH_ScopedInstall(IsGlobalContextUberImportant, 0x4E46F0, { .reversed = false }); - RH_ScopedInstall(GetNextMoodToUse, 0x4E4700, { .reversed = false }); - RH_ScopedInstall(GetVoiceForMood, 0x4E4760, { .reversed = false }); - RH_ScopedInstall(CanWePlayScriptedSpeech, 0x4E4950, { .reversed = false }); + RH_ScopedInstall(IsGlobalContextImportantForInterupting, 0x4E4600); + RH_ScopedInstall(IsGlobalContextUberImportant, 0x4E46F0); + RH_ScopedInstall(GetNextMoodToUse, 0x4E4700); + RH_ScopedInstall(GetVoiceForMood, 0x4E4760); + RH_ScopedInstall(CanWePlayScriptedSpeech, 0x4E4950); RH_ScopedInstall(GetSpeechContextVolumeOffset, 0x4E4AE0, { .reversed = false }); RH_ScopedInstall(RequestPedConversation, 0x4E50E0, { .reversed = false }); RH_ScopedInstall(ReleasePedConversation, 0x4E52A0, { .reversed = false }); RH_ScopedInstall(GetCurrentCJMood, 0x4E53B0, { .reversed = false }); - RH_ScopedInstall(StaticInitialise, 0x5B98C0, { .reversed = false }); + RH_ScopedInstall(StaticInitialise, 0x5B98C0, { .reversed = true }); RH_ScopedInstall(GetSpecificSpeechContext, 0x4E4470, { .reversed = false }); RH_ScopedInstall(Service, 0x4E3710, { .reversed = false }); RH_ScopedInstall(Reset, 0x4E37B0); @@ -444,17 +555,17 @@ void CAEPedSpeechAudioEntity::InjectHooks() { RH_ScopedInstall(GetAudioPedType, 0x4E3C60); RH_ScopedInstall(GetVoice, 0x4E3CD0, { .reversed = false }); RH_ScopedInstall(DisableAllPedSpeech, 0x4E3EB0); - RH_ScopedInstall(IsGlobalContextPain, 0x4E44F0, { .reversed = false }); + RH_ScopedInstall(IsGlobalContextPain, 0x4E44F0, { .reversed = true }); RH_ScopedInstall(SetCJMood, 0x4E3ED0, { .reversed = false }); RH_ScopedInstall(EnableAllPedSpeech, 0x4E3EC0); - RH_ScopedInstall(IsCJDressedInForGangSpeech, 0x4E4270, { .reversed = false }); + RH_ScopedInstall(IsCJDressedInForGangSpeech, 0x4E4270, { .reversed = true }); RH_ScopedInstall(GetSexForSpecialPed, 0x4E4260); - RH_ScopedInstall(IsGlobalContextImportantForWidescreen, 0x4E46B0, { .reversed = false }); + RH_ScopedInstall(IsGlobalContextImportantForWidescreen, 0x4E46B0, { .reversed = true }); RH_ScopedInstall(GetRepeatTime, 0x4E47E0, { .reversed = false }); RH_ScopedInstall(LoadAndPlaySpeech, 0x4E4840, { .reversed = false }); - RH_ScopedInstall(GetNumSlotsPlayingContext, 0x4E49B0, { .reversed = false }); - RH_ScopedInstall(GetNextPlayTime, 0x4E49E0, { .reversed = false }); - RH_ScopedInstall(SetNextPlayTime, 0x4E4A20, { .reversed = false }); + RH_ScopedInstall(GetNumSlotsPlayingContext, 0x4E49B0, { .reversed = true }); + RH_ScopedInstall(GetNextPlayTime, 0x4E49E0, { .reversed = true }); + RH_ScopedInstall(SetNextPlayTime, 0x4E4A20, { .reversed = true }); RH_ScopedInstall(DisablePedSpeech, 0x4E56D0); RH_ScopedInstall(DisablePedSpeechForScriptSpeech, 0x4E5700); RH_ScopedInstall(CanPedSayGlobalContext, 0x4E5730, { .reversed = false }); diff --git a/source/game_sa/Audio/entities/AEPedSpeechAudioEntity.h b/source/game_sa/Audio/entities/AEPedSpeechAudioEntity.h index cb431dbb3b..b32f728d8f 100644 --- a/source/game_sa/Audio/entities/AEPedSpeechAudioEntity.h +++ b/source/game_sa/Audio/entities/AEPedSpeechAudioEntity.h @@ -18,6 +18,42 @@ enum eAudioPedType : int16 { PED_TYPE_SPC = 5 }; +class CAEPedSpeechAudioEntity; + +struct tSpeechSlot { + uint16 m_nState; + uint16 field_02; + CAEPedSpeechAudioEntity* m_PedSpeechAE; + uint32 field_08; + uint16 m_nSoundId; + uint16 m_nBankId; + uint32 m_nTime; + uint16 m_nPhraseId; + uint16 m_nVoiceType; + uint8 field_18; + uint8 field_19; + uint8 field_1A; + uint8 field_1B; + + void Reset() { + m_PedSpeechAE = nullptr; + m_nState = field_08 = m_nTime = 0; + m_nSoundId = m_nBankId = m_nPhraseId = m_nVoiceType = -1; + field_19 = field_1A = field_18 = 0; + } +}; +VALIDATE_SIZE(tSpeechSlot, 0x1C); + +struct tPhraseMemory { + int16 m_nSoundId{-1}; + int16 m_nBankId{-1}; + + void Reset() { + m_nSoundId = m_nBankId = -1; + } +}; +VALIDATE_SIZE(tPhraseMemory, 0x04); + class NOTSA_EXPORT_VTABLE CAEPedSpeechAudioEntity : public CAEAudioEntity { public: char field_7C[20]; @@ -42,7 +78,7 @@ class NOTSA_EXPORT_VTABLE CAEPedSpeechAudioEntity : public CAEAudioEntity { float m_fVoiceVolume; int16 m_nCurrentPhraseId; int16 field_B2; - int32 field_B4[19]; + uint32 field_B4[19]; public: static int16& s_nCJWellDressed; @@ -66,8 +102,12 @@ class NOTSA_EXPORT_VTABLE CAEPedSpeechAudioEntity : public CAEAudioEntity { static int16& s_pConversationPedSlot2; static int16& s_NextSpeechSlot; - static int16& s_PhraseMemory; - // static CAEPedSpeechAudioEntity::Slot (&s_PedSpeechSlots)[6]; + + static constexpr auto PHRASE_MEMORY_COUNT = 150; + static inline auto& s_PhraseMemory = *reinterpret_cast*>(0xB61418); + + static constexpr auto NUM_PED_SPEECH_SLOTS = 6; + static inline auto& s_PedSpeechSlots = *reinterpret_cast*>(0xB61C38); public: static void InjectHooks(); @@ -75,10 +115,10 @@ class NOTSA_EXPORT_VTABLE CAEPedSpeechAudioEntity : public CAEAudioEntity { CAEPedSpeechAudioEntity(); ~CAEPedSpeechAudioEntity() = default; - static int8 IsGlobalContextImportantForInterupting(int16 a1); // typo: Interrupting - static int8 IsGlobalContextUberImportant(int16 a1); - static int16 GetNextMoodToUse(int16 a1); - static int32 GetVoiceForMood(int16 a1); + static bool __stdcall IsGlobalContextImportantForInterupting(int16 globalCtx); // typo: Interrupting + static bool IsGlobalContextUberImportant(int16 globalCtx); + static int16 __stdcall GetNextMoodToUse(int16 lastMood); + static int32 __stdcall GetVoiceForMood(int16 mood); static int16 CanWePlayScriptedSpeech(); static float GetSpeechContextVolumeOffset(int16 a1); static int8 RequestPedConversation(CPed* ped1, CPed* ped2); @@ -96,18 +136,18 @@ class NOTSA_EXPORT_VTABLE CAEPedSpeechAudioEntity : public CAEAudioEntity { static int16 GetAudioPedType(Const char* name); static int32 GetVoice(char* name, int16 type); static void DisableAllPedSpeech(); - bool IsGlobalContextPain(int16 a1); + bool IsGlobalContextPain(int16 globalCtx); static void SetCJMood(int16, uint32, int16, int16, int16); static void EnableAllPedSpeech(); static bool IsCJDressedInForGangSpeech(); int8 GetSexForSpecialPed(uint32 a1); - bool IsGlobalContextImportantForWidescreen(int16 a1); + bool IsGlobalContextImportantForWidescreen(int16 globalCtx); int32 GetRepeatTime(int16 a1); - void LoadAndPlaySpeech(uint32 a2); - int32 GetNumSlotsPlayingContext(int16 a2); - int32 GetNextPlayTime(int16 a2); - void SetNextPlayTime(int16 a2); + void LoadAndPlaySpeech(uint32 offset); + int32 GetNumSlotsPlayingContext(int16 context); + uint32 GetNextPlayTime(int16 globalCtx); + void SetNextPlayTime(int16 globalCtx); void DisablePedSpeech(int16 a1); void DisablePedSpeechForScriptSpeech(int16 a1); int8 CanPedSayGlobalContext(int16 a2); @@ -146,6 +186,12 @@ class NOTSA_EXPORT_VTABLE CAEPedSpeechAudioEntity : public CAEAudioEntity { bool WillPedChatAboutTopic_Reversed(int16 topic); int16 GetPedType_Reversed(); bool IsPedFemaleForAudio_Reversed(); + + // NOTSA + tSpeechSlot& GetCurrentPedSpeech() { + assert(m_nPedSpeechSlotIndex >= 0 && (size_t)(m_nPedSpeechSlotIndex) < std::size(s_PedSpeechSlots)); + return s_PedSpeechSlots[m_nPedSpeechSlotIndex]; + } }; VALIDATE_SIZE(CAEPedSpeechAudioEntity, 0x100); diff --git a/source/game_sa/Audio/entities/AEPedWeaponAudioEntity.cpp b/source/game_sa/Audio/entities/AEPedWeaponAudioEntity.cpp index df1732e00e..958dc6e4cc 100644 --- a/source/game_sa/Audio/entities/AEPedWeaponAudioEntity.cpp +++ b/source/game_sa/Audio/entities/AEPedWeaponAudioEntity.cpp @@ -28,18 +28,18 @@ void CAEPedWeaponAudioEntity::AddAudioEvent(eAudioEvents event) { return; const auto& weapon = m_Ped->GetActiveWeapon(); - if (weapon.m_nType == WEAPON_UNARMED) + if (weapon.m_Type == WEAPON_UNARMED) return; switch (event) { case AE_WEAPON_FIRE: case AE_WEAPON_FIRE_MINIGUN_AMMO: case AE_WEAPON_FIRE_MINIGUN_NO_AMMO: - WeaponFire(weapon.m_nType, m_Ped, event); + WeaponFire(weapon.m_Type, m_Ped, event); break; case AE_WEAPON_RELOAD_A: case AE_WEAPON_RELOAD_B: - WeaponReload(weapon.m_nType, m_Ped, event); + WeaponReload(weapon.m_Type, m_Ped, event); break; case AE_WEAPON_CHAINSAW_IDLE: case AE_WEAPON_CHAINSAW_ACTIVE: @@ -47,7 +47,7 @@ void CAEPedWeaponAudioEntity::AddAudioEvent(eAudioEvents event) { ReportChainsawEvent(m_Ped, event); break; case AE_WEAPON_STEALTH_KILL: - ReportStealthKill(weapon.m_nType, m_Ped, event); + ReportStealthKill(weapon.m_Type, m_Ped, event); break; default: return; @@ -59,7 +59,7 @@ void CAEPedWeaponAudioEntity::Service() { if ( !m_Ped || m_Ped->bCollidedWithMyVehicle || m_Ped->GetIntelligence()->GetTaskSwim() - || m_Ped->GetActiveWeapon().m_nType != WEAPON_FLAMETHROWER + || m_Ped->GetActiveWeapon().m_Type != WEAPON_FLAMETHROWER ) { StopFlameThrowerIdleGasLoop(); } else { diff --git a/source/game_sa/Audio/entities/AEVehicleAudioEntity.h b/source/game_sa/Audio/entities/AEVehicleAudioEntity.h index 224a4f29b3..e67cce0ff2 100644 --- a/source/game_sa/Audio/entities/AEVehicleAudioEntity.h +++ b/source/game_sa/Audio/entities/AEVehicleAudioEntity.h @@ -94,12 +94,12 @@ struct tVehicleAudioSettings { int16 m_nEngineOnSoundBankId; int16 m_nEngineOffSoundBankId; int8 m_nBassSetting; // m_nStereo - float m_fBassEq; + float m_fBassEq; // m_fBassFactor float field_C; int8 m_nHornToneSoundInBank; // sfx id float m_fHornHigh; char m_nDoorSound; - char field_19; + char m_EngineUpgrade; eRadioID m_nRadioID; eRadioType m_nRadioType; int8 m_nVehTypeForAudio; diff --git a/source/game_sa/Audio/hardware/AEAudioChannel.cpp b/source/game_sa/Audio/hardware/AEAudioChannel.cpp index 75a9622115..69f24a5ec9 100644 --- a/source/game_sa/Audio/hardware/AEAudioChannel.cpp +++ b/source/game_sa/Audio/hardware/AEAudioChannel.cpp @@ -25,25 +25,25 @@ void CAEAudioChannel::InjectHooks() { // 0x4D7890 CAEAudioChannel::CAEAudioChannel(IDirectSound* directSound, uint16 channelId, uint32 samplesPerSec, uint16 bitsPerSample) { - m_wBitsPerSample = bitsPerSample; m_pDirectSound = directSound; m_nChannelId = channelId; - m_nBytesPerSec = samplesPerSec * (bitsPerSample / 8); m_nFlags = 0; m_nBufferStatus = 0; m_nFrequency = samplesPerSec; - field_57 = 0; - field_53 = 2; - m_wFrequencyMult = 1; - m_nBufferFrequency = samplesPerSec; m_nOriginalFrequency = samplesPerSec; - field_47 = 1; m_fVolume = -100.0f; m_pDirectSoundBuffer = nullptr; m_pDirectSound3DBuffer= nullptr; m_bNoScalingFactor = false; m_bLooped = false; field_45 = 0; + m_WaveFormat.wFormatTag = WAVE_FORMAT_PCM; + m_WaveFormat.nChannels = 1; + m_WaveFormat.nSamplesPerSec = samplesPerSec; + m_WaveFormat.nAvgBytesPerSec = samplesPerSec * (bitsPerSample / 8); + m_WaveFormat.nBlockAlign = 2; + m_WaveFormat.wBitsPerSample = bitsPerSample; + m_WaveFormat.cbSize = 0; } // 0x4D7910 @@ -100,7 +100,7 @@ void CAEAudioChannel::SetFrequencyScalingFactor_Reversed(float factor) { } // 0x4D7950 -void CAEAudioChannel::SetPosition(CVector* vecPos) const { +void CAEAudioChannel::SetPosition(const CVector& vecPos) const { if (!m_pDirectSoundBuffer) return; @@ -108,7 +108,7 @@ void CAEAudioChannel::SetPosition(CVector* vecPos) const { return; #ifdef USE_DSOUND - m_pDirectSound3DBuffer->SetPosition(vecPos->x, vecPos->y, vecPos->z, DS3D_DEFERRED); + m_pDirectSound3DBuffer->SetPosition(vecPos.x, vecPos.y, vecPos.z, DS3D_DEFERRED); #endif } @@ -145,12 +145,12 @@ uint32 CAEAudioChannel::GetCurrentPlaybackPosition() const { // 0x4D79D0 uint32 CAEAudioChannel::ConvertFromBytesToMS(uint32 bytes) const { - return CAEAudioUtility::ConvertFromBytesToMS(bytes, m_nBufferFrequency, m_wFrequencyMult); + return CAEAudioUtility::ConvertFromBytesToMS(bytes, m_WaveFormat.nSamplesPerSec, m_WaveFormat.nChannels); } // 0x4D79F0 uint32 CAEAudioChannel::ConvertFromMsToBytes(uint32 ms) const { - return CAEAudioUtility::ConvertFromMSToBytes(ms, m_nBufferFrequency, m_wFrequencyMult); + return CAEAudioUtility::ConvertFromMSToBytes(ms, m_WaveFormat.nSamplesPerSec, m_WaveFormat.nChannels); } // 0x4D7A50 @@ -193,3 +193,7 @@ bool CAEAudioChannel::Lost() const { return true; } + +bool CAEAudioChannel::SetReverbAndDepth(uint32 reverb, uint32 depth) { + return plugin::CallMethodAndReturn(this, reverb, depth); +} diff --git a/source/game_sa/Audio/hardware/AEAudioChannel.h b/source/game_sa/Audio/hardware/AEAudioChannel.h index adda5035ec..5805593f55 100644 --- a/source/game_sa/Audio/hardware/AEAudioChannel.h +++ b/source/game_sa/Audio/hardware/AEAudioChannel.h @@ -11,7 +11,10 @@ class CAEAudioChannel { public: #ifdef USE_DSOUND IDirectSound* m_pDirectSound; - IDirectSoundBuffer* m_pDirectSoundBuffer; + union { + IDirectSoundBuffer* m_pDirectSoundBuffer; + IDirectSoundBuffer8* m_pDirectSoundBuffer8; + }; IDirectSound3DBuffer* m_pDirectSound3DBuffer; #endif @@ -28,17 +31,16 @@ class CAEAudioChannel { bool m_bLooped; uint8 field_45; uint8 field_46; // unused - uint16 field_47; - uint16 m_wFrequencyMult; - uint32 m_nBufferFrequency; - uint32 m_nBytesPerSec; - uint16 field_53; - uint16 m_wBitsPerSample; - uint16 field_57; + WAVEFORMATEX m_WaveFormat; uint16 field_59; #ifdef USE_DSOUND char _pad; - uint32 m_nBufferStatus; + union { + struct { + bool Bit0x1 : 1; + } bufferStatus; + uint32 m_nBufferStatus; + }; #endif public: @@ -55,7 +57,7 @@ class CAEAudioChannel { virtual void Stop() = 0; virtual void SetFrequencyScalingFactor(float factor); - void SetPosition(CVector* vecPos) const; + void SetPosition(const CVector& vecPos) const; float GetVolume() const { return m_fVolume; }; void SetVolume(float volume); bool IsBufferPlaying() const { return m_nBufferStatus & DSBSTATUS_PLAYING; }; @@ -83,7 +85,7 @@ class CAEAudioChannel { VALIDATE_SIZE(CAEAudioChannel, 0x60); VALIDATE_OFFSET(CAEAudioChannel, m_pDirectSound, 0x4); VALIDATE_OFFSET(CAEAudioChannel, m_nChannelId, 0x3A); -VALIDATE_OFFSET(CAEAudioChannel, m_nBufferFrequency, 0x4B); -VALIDATE_OFFSET(CAEAudioChannel, m_wFrequencyMult, 0x49); +//VALIDATE_OFFSET(CAEAudioChannel, m_nBufferFrequency, 0x4B); +//VALIDATE_OFFSET(CAEAudioChannel, m_wFrequencyMult, 0x49); extern uint32& g_numSoundChannelsUsed; diff --git a/source/game_sa/Audio/hardware/AEAudioHardware.cpp b/source/game_sa/Audio/hardware/AEAudioHardware.cpp index 508c3bd14a..bdb19fb494 100644 --- a/source/game_sa/Audio/hardware/AEAudioHardware.cpp +++ b/source/game_sa/Audio/hardware/AEAudioHardware.cpp @@ -1,6 +1,11 @@ #include "StdInc.h" #include "AEAudioHardware.h" +#include "AEMP3BankLoader.h" +#include "AEMP3TrackLoader.h" +#include "AEAudioEnvironment.h" +#include "AEStaticChannel.h" +#include "AEUserRadioTrackManager.h" CAEAudioHardware& AEAudioHardware = *reinterpret_cast(0xB5F8B8); @@ -8,42 +13,42 @@ void CAEAudioHardware::InjectHooks() { RH_ScopedClass(CAEAudioHardware); RH_ScopedCategory("Audio/Hardware"); - // RH_ScopedInstall(Constructor, 0x4D83E0, { .reversed = false }); - // RH_ScopedInstall(Destructor, 0x4D83A0, { .reversed = false }); + RH_ScopedInstall(Constructor, 0x4D83E0); + RH_ScopedInstall(Destructor, 0x4D83A0); RH_ScopedInstall(AllocateChannels, 0x5B9340); RH_ScopedInstall(RequestVirtualChannelSoundInfo, 0x4D8E60); RH_ScopedInstall(Query3DSoundEffects, 0x4D8490, { .reversed = false }); RH_ScopedInstall(GetNumAvailableChannels, 0x4D8810); - RH_ScopedInstall(GetChannelPlayTimes, 0x4D8820, { .reversed = false }); + RH_ScopedInstall(GetChannelPlayTimes, 0x4D8820); RH_ScopedInstall(SetChannelVolume, 0x4D8870); RH_ScopedInstall(LoadSoundBank, 0x4D88A0); - RH_ScopedInstall(IsSoundBankLoaded, 0x4D88C0, { .reversed = false }); - RH_ScopedInstall(GetSoundBankLoadingStatus, 0x4D88D0, { .reversed = false }); + RH_ScopedInstall(IsSoundBankLoaded, 0x4D88C0); + RH_ScopedInstall(GetSoundBankLoadingStatus, 0x4D88D0); RH_ScopedInstall(LoadSound, 0x4D8ED0); - RH_ScopedInstall(IsSoundLoaded, 0x4D8EF0, { .reversed = false }); - RH_ScopedInstall(GetSoundLoadingStatus, 0x4D8F00, { .reversed = false }); - RH_ScopedInstall(StopSound, 0x4D88E0, { .reversed = false }); - RH_ScopedInstall(SetChannelPosition, 0x4D8920, { .reversed = false }); - RH_ScopedInstall(SetChannelFrequencyScalingFactor, 0x4D8960, { .reversed = false }); + RH_ScopedInstall(IsSoundLoaded, 0x4D8EF0); + RH_ScopedInstall(GetSoundLoadingStatus, 0x4D8F00); + RH_ScopedInstall(StopSound, 0x4D88E0); + RH_ScopedInstall(SetChannelPosition, 0x4D8920); + RH_ScopedInstall(SetChannelFrequencyScalingFactor, 0x4D8960); RH_ScopedInstall(RescaleChannelVolumes, 0x4D8990, { .reversed = false }); - RH_ScopedInstall(UpdateReverbEnvironment, 0x4D8DA0, { .reversed = false }); - RH_ScopedInstall(GetSoundHeadroom, 0x4D8E30, { .reversed = false }); - RH_ScopedInstall(EnableEffectsLoading, 0x4D8E40, { .reversed = false }); - RH_ScopedInstall(DisableEffectsLoading, 0x4D8E50, { .reversed = false }); + RH_ScopedInstall(UpdateReverbEnvironment, 0x4D8DA0); + RH_ScopedInstall(GetSoundHeadroom, 0x4D8E30); + RH_ScopedInstall(EnableEffectsLoading, 0x4D8E40); + RH_ScopedInstall(DisableEffectsLoading, 0x4D8E50); RH_ScopedInstall(GetVirtualChannelSoundLengths, 0x4D8E90); RH_ScopedInstall(GetVirtualChannelSoundLoopStartTimes, 0x4D8EB0); - RH_ScopedInstall(PlayTrack, 0x4D8F10, { .reversed = false }); + RH_ScopedInstall(PlayTrack, 0x4D8F10); RH_ScopedInstall(StartTrackPlayback, 0x4D8F30); - RH_ScopedInstall(StopTrack, 0x4D8F50, { .reversed = false }); - RH_ScopedInstall(GetTrackPlayTime, 0x4D8F60, { .reversed = false }); - RH_ScopedInstall(GetTrackLengthMs, 0x4D8F70, { .reversed = false }); - RH_ScopedInstall(GetActiveTrackID, 0x4D8F80, { .reversed = false }); - RH_ScopedInstall(GetPlayingTrackID, 0x4D8F90, { .reversed = false }); - RH_ScopedInstall(GetBeatInfo, 0x4D8FA0, { .reversed = false }); - RH_ScopedInstall(SetBassSetting, 0x4D94A0, { .reversed = false }); - RH_ScopedInstall(DisableBassEq, 0x4D94D0, { .reversed = false }); - RH_ScopedInstall(EnableBassEq, 0x4D94E0, { .reversed = false }); + RH_ScopedInstall(StopTrack, 0x4D8F50); + RH_ScopedInstall(GetTrackPlayTime, 0x4D8F60); + RH_ScopedInstall(GetTrackLengthMs, 0x4D8F70); + RH_ScopedInstall(GetActiveTrackID, 0x4D8F80); + RH_ScopedInstall(GetPlayingTrackID, 0x4D8F90); + RH_ScopedInstall(GetBeatInfo, 0x4D8FA0); + RH_ScopedInstall(SetBassSetting, 0x4D94A0); + RH_ScopedInstall(DisableBassEq, 0x4D94D0); + RH_ScopedInstall(EnableBassEq, 0x4D94E0); RH_ScopedInstall(SetChannelFlags, 0x4D9500); RH_ScopedInstall(SetMusicMasterScalingFactor, 0x4D9530); RH_ScopedInstall(SetEffectsMasterScalingFactor, 0x4D9540); @@ -54,40 +59,184 @@ void CAEAudioHardware::InjectHooks() { RH_ScopedInstall(GetEffectsFaderScalingFactor, 0x4D9590); RH_ScopedInstall(SetNonStreamFaderScalingFactor, 0x4D95A0); RH_ScopedInstall(SetStreamFaderScalingFactor, 0x4D95B0); - RH_ScopedInstall(IsStreamingFromDVD, 0x4D95C0, { .reversed = false }); - RH_ScopedInstall(GetDVDDriveLetter, 0x4D95D0, { .reversed = false }); - RH_ScopedInstall(CheckDVD, 0x4D95E0, { .reversed = false }); - RH_ScopedInstall(PauseAllSounds, 0x4D95F0, { .reversed = false }); - RH_ScopedInstall(ResumeAllSounds, 0x4D9630, { .reversed = false }); - RH_ScopedInstall(InitDirectSoundListener, 0x4D9640, { .reversed = false }); - RH_ScopedInstall(Terminate, 0x4D97A0, { .reversed = false }); - RH_ScopedInstall(Service, 0x4D9870, { .reversed = false }); + RH_ScopedInstall(IsStreamingFromDVD, 0x4D95C0); + RH_ScopedInstall(GetDVDDriveLetter, 0x4D95D0); + RH_ScopedInstall(CheckDVD, 0x4D95E0); + RH_ScopedInstall(PauseAllSounds, 0x4D95F0); + RH_ScopedInstall(ResumeAllSounds, 0x4D9630); + RH_ScopedInstall(InitDirectSoundListener, 0x4D9640); + RH_ScopedInstall(Terminate, 0x4D97A0); + RH_ScopedInstall(Service, 0x4D9870); RH_ScopedInstall(Initialise, 0x4D9930, { .reversed = false }); } // 0x4D83E0 CAEAudioHardware::CAEAudioHardware() { - plugin::CallMethod<0x4D83E0, CAEAudioHardware*>(this); -} - -// 0x4D83A0 -CAEAudioHardware::~CAEAudioHardware() { - plugin::CallMethod<0x4D83A0, CAEAudioHardware*>(this); + rng::fill(m_afChannelVolumes, -1000.f); + // Rest done using member initializers } // 0x4D9930 bool CAEAudioHardware::Initialise() { - return plugin::CallMethodAndReturn(this); + m_pMP3BankLoader = new CAEMP3BankLoader; + m_pMP3TrackLoader = new CAEMP3TrackLoader; + if (!m_pMP3BankLoader->Initialise() || !m_pMP3TrackLoader->Initialise()) { + NOTSA_LOG_ERR("Failed initializing MP3 module!"); + return false; + } + + VERIFY(SUCCEEDED(CoInitialize(nullptr))); + + // TODO: We need EAX headers here. + // + // Though, EAX through DirectSound IS NOT SUPPORTED after Windows Vista since + // Windows doesn't emulate EAX extensions. + // + // Wine or 3rd party DSOUND libraries may support it so we should have them + // here. + + // if (FAILED(EAXDirectSoundCreate(&DSDEVID_DefaultPlayback, &m_pDSDevice, 0))) + // return false; + + if (FAILED(DirectSoundCreate8(&DSDEVID_DefaultPlayback, &m_pDSDevice, 0))) + return false; + + m_dsCaps.dwSize = 96; + m_pDSDevice->GetCaps(&m_dsCaps); + + if (FAILED(m_pDSDevice->SetCooperativeLevel(PSGLOBAL(window), DSSCL_PRIORITY)) + || !InitDirectSoundListener(2, 48'000, 16)) + return false; + + m_pDSDevice->GetSpeakerConfig((LPDWORD)&m_nSpeakerConfig); + + m_pStreamingChannel = new CAEStreamingChannel(m_pDSDevice, 0); + m_pStreamingChannel->Initialise(); + + const uint32 freeHw3DAllBuffers = m_dsCaps.dwFreeHw3DAllBuffers; + if (freeHw3DAllBuffers < 24) { + m_nNumChannels = 48; + AESmoothFadeThread.m_nNumAvailableBuffers = 48; + field_4 = 0; + } else { + m_nNumChannels = std::min(freeHw3DAllBuffers, 64u) - 7; + field_4 = 1; + AESmoothFadeThread.m_nNumAvailableBuffers = 7; + } + + m_aChannels[0] = m_pStreamingChannel; + for (auto i = 1u; i < m_nNumChannels; i++) { + m_aChannels[i] = new CAEStaticChannel(m_pDSDevice, i, field_4, 44'100, 16); + } + + m_pStreamingChannel->SetVolume(-100.0f); + m_awChannelFlags[0] = 55; // todo: flag + + if (FrontEndMenuManager.m_bTracksAutoScan) { + AEUserRadioTrackManager.ScanUserTracks(); + + // Wait other threads until scanning is done. + auto& state = AEUserRadioTrackManager.m_nUserTracksScanState; + while (state != USER_TRACK_SCAN_IN_PROGRESS) { + switch (state) { + case USER_TRACK_SCAN_COMPLETE: + case USER_TRACK_SCAN_ERROR: + // Set off after ended up complete or failed. + state = USER_TRACK_SCAN_OFF; + break; + default: + break; + } + + Sleep(100); + } + } + + AEUserRadioTrackManager.Initialise(); + AESmoothFadeThread.Initialise(); + AESmoothFadeThread.Start(); + m_pStreamThread.Initialise(m_pStreamingChannel); + m_pStreamThread.Start(); + + m_nNumAvailableChannels = m_nNumChannels; + m_bInitialised = true; + + return true; } // 0x4D9640 bool CAEAudioHardware::InitDirectSoundListener(uint32 numChannels, uint32 samplesPerSec, uint32 bitsPerSample) { - return plugin::CallMethodAndReturn(this, numChannels, samplesPerSec, bitsPerSample); + if (!m_pDSDevice) + return false; + + DSBUFFERDESC dsBuffDsc{ + sizeof(DSBUFFERDESC), + DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER, + 0, + 0, + 0, + }; + LPDIRECTSOUNDBUFFER soundbuf; + if (FAILED(m_pDSDevice->CreateSoundBuffer(&dsBuffDsc, &soundbuf, NULL))) { + return false; + } + + const auto nBlockAlign = numChannels * bitsPerSample / 8; + WAVEFORMATEX wavFmt{ + .wFormatTag = 1, + .nChannels = (WORD)numChannels, + .nSamplesPerSec = samplesPerSec, + .nAvgBytesPerSec = samplesPerSec * nBlockAlign, + .nBlockAlign = (WORD)nBlockAlign, + .wBitsPerSample = (WORD)bitsPerSample, + .cbSize = 0 + }; + if (FAILED(soundbuf->SetFormat(&wavFmt))) { +#ifdef FIX_BUGS + soundbuf->Release(); +#endif + return false; + } + + auto& listener = m_pDirectSound3dListener; + if (FAILED(soundbuf->QueryInterface( + IID_IDirectSound3DListener, + (LPVOID*)&listener + ))) { + soundbuf->Release(); + return false; + } + + listener->SetPosition(0.f, 0.f, 0.f, TRUE); + listener->SetOrientation( + 0.f, 1.f, 0.f, // In Front + 0.f, 0.f, -1.f, // Bottom + TRUE + ); + listener->SetRolloffFactor(0.f, TRUE); + listener->SetDopplerFactor(0.f, TRUE); + listener->CommitDeferredSettings(); + + soundbuf->Release(); + + Query3DSoundEffects(); + + return true; } // 0x4D97A0 void CAEAudioHardware::Terminate() { - plugin::CallMethod<0x4D97A0, CAEAudioHardware*>(this); + m_bInitialised = false; + m_pStreamThread.Stop(); + AESmoothFadeThread.Stop(); + m_pStreamThread.WaitForExit(); + for (const auto ch : GetChannels()) { + delete ch; + } + delete std::exchange(m_pMP3BankLoader, nullptr); + delete std::exchange(m_pMP3TrackLoader, nullptr); + SAFE_RELEASE(m_pDirectSound3dListener); + SAFE_RELEASE(m_pDSDevice); } void CAEAudioHardware::PlaySound(int16 channel, uint16 channelSlot, uint16 soundIdInSlot, uint16 bankSlot, int16 playPosition, int16 flags, float speed) { @@ -113,12 +262,9 @@ int16 CAEAudioHardware::AllocateChannels(uint16 numChannels) { // 0x4D94A0 void CAEAudioHardware::SetBassSetting(int8 nBassSet, float fBassEqGain) { - plugin::CallMethod<0x4D94A0, CAEAudioHardware*, int8, float>(this, nBassSet, fBassEqGain); - /* - m_nBassSet = nBassSet; + m_nBassSet = nBassSet; m_fBassEqGain = fBassEqGain; - ??? m_pStreamingChannel->SetBassEQ(reinterpret_cast(nBassSet), fBassEqGain); - */ + m_pStreamingChannel->SetBassEQ(nBassSet, fBassEqGain); } // 0x4D8810 @@ -128,7 +274,12 @@ uint16 CAEAudioHardware::GetNumAvailableChannels() const { // 0x4D8820 void CAEAudioHardware::GetChannelPlayTimes(int16 channel, int16* playTimes) { - plugin::CallMethodAndReturn(this, channel, playTimes); + if (!playTimes) + return; + + for (auto i = m_anNumChannelsInSlot[channel]; i-- > 0;) { + playTimes[i] = m_aChannels[channel + i]->GetPlayTime(); + } } // 0x4D8870 @@ -138,71 +289,64 @@ void CAEAudioHardware::SetChannelVolume(int16 channel, uint16 channelId, float v } // 0x4D88A0 -bool CAEAudioHardware::LoadSoundBank(uint16 bankId, int16 bankSlotId) { +void CAEAudioHardware::LoadSoundBank(uint16 bankId, int16 bankSlotId) { if (!m_bDisableEffectsLoading) { - return plugin::CallMethodAndReturn(m_pMP3BankLoader, bankId, bankSlotId); - // todo: return m_pMP3BankLoader->LoadSoundBank(bankId, bankSlotId); + m_pMP3BankLoader->LoadSoundBank(bankId, bankSlotId); } - - return m_bDisableEffectsLoading; } // 0x4D88C0 bool CAEAudioHardware::IsSoundBankLoaded(uint16 bankId, int16 bankSlotId) { - return plugin::CallMethodAndReturn(this, bankId, bankSlotId); - // todo: return m_pMP3BankLoader->IsSoundBankLoaded(bankId, bankSlotId); + return m_pMP3BankLoader->IsSoundBankLoaded(bankId, bankSlotId); } // 0x4D88D0 int8 CAEAudioHardware::GetSoundBankLoadingStatus(uint16 bankId, int16 bankSlotId) { - return plugin::CallMethodAndReturn(this, bankId, bankSlotId); - // todo: return m_pMP3BankLoader->GetSoundBankLoadingStatus(bankId, bankSlotId); + return m_pMP3BankLoader->GetSoundBankLoadingStatus(bankId, bankSlotId); } // 0x4D8ED0 -bool CAEAudioHardware::LoadSound(uint16 bank, uint16 sound, int16 slot) { +void CAEAudioHardware::LoadSound(uint16 bank, uint16 sound, int16 slot) { if (!m_bDisableEffectsLoading) { - return plugin::CallMethodAndReturn(m_pMP3BankLoader, bank, sound, slot); - // todo: return m_pMP3BankLoader->LoadSound(bank, sound, slot); + m_pMP3BankLoader->LoadSound(bank, sound, slot); } - - return m_bDisableEffectsLoading; } // 0x4D8EF0 bool CAEAudioHardware::IsSoundLoaded(uint16 bankId, uint16 sfxId, int16 bankSlot) { - return plugin::CallMethodAndReturn(this, bankId, sfxId, bankSlot); - // return m_pMP3BankLoader->IsSoundLoaded(bankId, sfxId, bankSlotId); + return m_pMP3BankLoader->IsSoundLoaded(bankId, sfxId, bankSlot); } // 0x4D8F00 bool CAEAudioHardware::GetSoundLoadingStatus(uint16 bankId, uint16 sfxId, int16 bankSlot) { - return plugin::CallMethodAndReturn(this, bankId, sfxId, bankSlot); - // return m_pMP3BankLoader->GetSoundLoadingStatus(bankId, sfxId, bankSlot); + return m_pMP3BankLoader->GetSoundLoadingStatus(bankId, sfxId, bankSlot); } // 0x4D88E0 void CAEAudioHardware::StopSound(int16 channel, uint16 channelId) { if (channel >= 0 && channelId < m_anNumChannelsInSlot[channel]) { - auto channel_ = m_aChannels[channel + channelId]; - if (channel_) - channel_->Stop(); + const auto ch = m_aChannels[channel + channelId]; + if (ch) { + ch->Stop(); + } } } // 0x4D8920 -void CAEAudioHardware::SetChannelPosition(int16 channel, uint16 channelId, CVector* posn, uint8 unused) { - if (channel >= 0 && channelId < m_anNumChannelsInSlot[channel]) { - auto channel_ = m_aChannels[channel + channelId]; - if (channel_) - channel_->SetPosition(posn); +void CAEAudioHardware::SetChannelPosition(int16 slotId, uint16 channelId, const CVector& posn, uint8 unused) { + if (slotId >= 0 && channelId < m_anNumChannelsInSlot[slotId]) { + const auto ch = m_aChannels[slotId + channelId]; + if (ch) { + ch->SetPosition(posn); + } } } // 0x4D8960 void CAEAudioHardware::SetChannelFrequencyScalingFactor(int16 channel, uint16 channelId, float factor) { - if (channel >= 0 && channelId < m_anNumChannelsInSlot[channel]) + if (channel >= 0 && channelId < m_anNumChannelsInSlot[channel]) { m_afChannelsFrqScalingFactor[channel + channelId] = factor; + } } // 0x4D8990 @@ -212,13 +356,24 @@ void CAEAudioHardware::RescaleChannelVolumes() { // 0x4D8DA0 void CAEAudioHardware::UpdateReverbEnvironment() { - plugin::CallMethod<0x4D8DA0, CAEAudioHardware*>(this); + int8 reverb; + int32 depth; + CAEAudioEnvironment::GetReverbEnvironmentAndDepth(&reverb, &depth); + if (reverb == m_nReverbEnvironment && depth == m_nReverbDepth) { // Nothing is changing? + return; + } + if (!rng::none_of(GetChannels(), [=](CAEAudioChannel* ch) { // Also covers the case of `m_nNumChannels == 0` + return ch && ch->SetReverbAndDepth(reverb, depth); } + )) { + return; + } + m_nReverbEnvironment = reverb; + m_nReverbDepth = depth; } // 0x4D8E30 float CAEAudioHardware::GetSoundHeadroom(uint16 sfxId, int16 bankSlotId) { - return plugin::CallMethodAndReturn(this, sfxId, bankSlotId); - // return m_pMP3BankLoader->GetSoundHeadroom(sfxId, bankSlotId); + return m_pMP3BankLoader->GetSoundHeadroom(sfxId, bankSlotId); } // 0x4D8E40 @@ -232,32 +387,31 @@ void CAEAudioHardware::DisableEffectsLoading() { } // 0x4D8E60 -void CAEAudioHardware::RequestVirtualChannelSoundInfo(uint16 channel, uint16 sfxId, uint16 bankSlotId) { - if (channel < MAX_NUM_SOUNDS) { - m_aBankSlotIds[channel] = bankSlotId; - m_aSoundIdsInSlots[channel] = sfxId; - } +void CAEAudioHardware::RequestVirtualChannelSoundInfo(uint16 idx, uint16 sfxId, uint16 bankSlotId) { + assert(idx < MAX_NUM_SOUNDS); + m_aBankSlotIds[idx] = bankSlotId; + m_aSoundIdsInSlots[idx] = sfxId; } // 0x4D8E90 void CAEAudioHardware::GetVirtualChannelSoundLengths(int16* soundLengths) { - memcpy(soundLengths, m_anVirtualChannelSoundLengths, sizeof(m_anVirtualChannelSoundLengths)); + memcpy(soundLengths, m_VirtualChannelSoundLengths, sizeof(m_VirtualChannelSoundLengths)); } // 0x4D8EB0 void CAEAudioHardware::GetVirtualChannelSoundLoopStartTimes(int16* soundLoopStartTimes) { - memcpy(soundLoopStartTimes, m_anVirtualChannelSoundLoopStartTimes, sizeof(m_anVirtualChannelSoundLoopStartTimes)); // size is 600, replaced by loop? + memcpy(soundLoopStartTimes, m_VirtualChannelLoopTimes, sizeof(m_VirtualChannelLoopTimes)); // size is 600, replaced by loop? } // 0x4D8F10 -void CAEAudioHardware::PlayTrack(uint32 trackId, int32 nextTrackId, uint32 a3, char a4, bool isUserTrack, bool nextIsUserTrack) { - field_1010 = a4; - m_pStreamThread.PlayTrack(trackId, nextTrackId, a3, a4, isUserTrack, nextIsUserTrack); +void CAEAudioHardware::PlayTrack(uint32 trackID, int nextTrackID, uint32 startOffsetMs, uint8 trackFlags, bool bUserTrack, bool bUserNextTrack) { + m_PlayingTrackFlags = trackFlags; + m_pStreamThread.PlayTrack(trackID, nextTrackID, startOffsetMs, trackFlags, bUserTrack, bUserNextTrack); } // 0x4D8F30 void CAEAudioHardware::StartTrackPlayback() const { - m_pStreamingChannel->Play(0, field_1010, 1.0f); + m_pStreamingChannel->Play(0, m_PlayingTrackFlags, 1.0f); } // 0x4D8F50 @@ -287,19 +441,71 @@ int32 CAEAudioHardware::GetPlayingTrackID() const { // 0x4D8FA0 void CAEAudioHardware::GetBeatInfo(tBeatInfo* beatInfo) { - plugin::CallMethod<0x4D8FA0, CAEAudioHardware*, tBeatInfo*>(this, beatInfo); + const auto bi = &gBeatInfo; + + if (m_pStreamThread.GetActiveTrackID() == -1) { + bi->IsBeatInfoPresent = false; + rng::fill(bi->BeatWindow, tTrackInfo::tBeat{}); + return; + } + + const auto tId = m_pStreamThread.GetActiveTrackID(); + const auto tInfo = std::unique_ptr{m_pMP3TrackLoader->GetTrackInfo(tId)}; + const auto tPlayTime = m_pStreamThread.GetTrackPlayTime(); + + size_t i = 0; + + if (tPlayTime < 0 || tInfo->m_aBeats[0].m_nTime < 0) { + if (tPlayTime != -7) { + bi->IsBeatInfoPresent = false; + rng::fill(bi->BeatWindow, tTrackInfo::tBeat{}); + } + } else { + const auto beatTimeWindowBegin = bi->BeatNumber + ? std::max(0u, (uint32)tPlayTime - 50u) + : (uint32)tPlayTime; + + bi->IsBeatInfoPresent = true; + bi->BeatTypeThisFrame = 0; + bi->BeatNumber = -1; + rng::fill(bi->BeatWindow, tTrackInfo::tBeat{}); + + //> 0x4D904C - Find first beat in the given time window + for (; tInfo->m_aBeats[i].m_nKey && tInfo->m_aBeats[i].m_nTime < beatTimeWindowBegin; i++); + } + + // 0x4D9346 and 0x4D9088 combined - Copy beats at the beginning of the time window + for (auto k = 0u; k < std::min(10u, i); k++) { + auto& beat = bi->BeatWindow[10 - i]; + beat = tInfo->m_aBeats[i - k - 1]; + beat.m_nTime -= tPlayTime; + } + + if (tInfo->m_aBeats[i].m_nKey != 0) { + // 0x4D9078 + if (i != bi->BeatNumber && i > 0) { + bi->BeatTypeThisFrame = tInfo->m_aBeats[i - 1].m_nKey; + } + + // 0x4D91A9 + for (auto k = 0u; k < std::min(std::size(tInfo->m_aBeats) - i, 10u); k++) { // Copy beats towards the end + auto& beat = bi->BeatWindow[10 + i]; + if (beat.m_nKey) { + beat = tInfo->m_aBeats[i + k]; + beat.m_nTime -= tPlayTime; + } + } + } } -// unused? // 0x4D94E0 void CAEAudioHardware::EnableBassEq() { - plugin::CallMethod<0x4D94E0, CAEAudioHardware*>(this); - // m_pStreamingChannel->SetBassEQ(m_nBassSet, m_fBassEqGain); + m_pStreamingChannel->SetBassEQ(m_nBassSet, m_fBassEqGain); } // 0x4D94D0 void CAEAudioHardware::DisableBassEq() { - m_pStreamingChannel->SetBassEQ(nullptr, 0); + m_pStreamingChannel->SetBassEQ(0, 0.f); } // 0x4D9500 @@ -355,38 +561,85 @@ void CAEAudioHardware::SetStreamFaderScalingFactor(float factor) { // 0x4D95C0 bool CAEAudioHardware::IsStreamingFromDVD() { - return plugin::CallMethodAndReturn(this); - // todo: return m_pMP3TrackLoader->m_bStreamingFromDVD; + return m_pMP3TrackLoader->m_bStreamingFromDVD; } // 0x4D95D0 char CAEAudioHardware::GetDVDDriveLetter() { - return plugin::CallMethodAndReturn(this); - // todo: return m_pMP3TrackLoader->m_szDvdDrivePath[0]; + return m_pMP3TrackLoader->m_pszDvdDrivePath[0]; } // 0x4D95E0 bool CAEAudioHardware::CheckDVD() { - return plugin::CallMethodAndReturn(this); - // todo: return m_pMP3TrackLoader->IsCurrentAudioStreamAvailable(); + return m_pMP3TrackLoader->IsCurrentAudioStreamAvailable(); } // 0x4D95F0 void CAEAudioHardware::PauseAllSounds() { - plugin::CallMethod<0x4D95F0, CAEAudioHardware*>(this); + if (!m_bInitialised) { + return; + } + for (const auto ch : GetChannels()) { + ch->SetFrequencyScalingFactor(0.f); + } } // 0x4D9630 void CAEAudioHardware::ResumeAllSounds() { - plugin::CallMethod<0x4D9630, CAEAudioHardware*>(this); + if (!m_bInitialised) { + return; + } + RescaleChannelVolumes(); } // 0x4D8490 void CAEAudioHardware::Query3DSoundEffects() { - return plugin::CallMethod<0x4D8490, CAEAudioHardware*>(this); + DSBUFFERDESC bufferDesc; + bufferDesc.guid3DAlgorithm = GUID_NULL; + bufferDesc.lpwfxFormat = nullptr; + bufferDesc.dwFlags = DSBCAPS_CTRL3D; + bufferDesc.dwReserved = 0; + bufferDesc.dwSize = sizeof(DSBUFFERDESC); + bufferDesc.dwBufferBytes = 1024; + + IDirectSoundBuffer* ppDSBuffer{}; + if (FAILED(m_pDSDevice->CreateSoundBuffer(&bufferDesc, &ppDSBuffer, 0)) || !ppDSBuffer) + return; + + IDirectSound3DBuffer* ppDS3DBuffer{}; + if (FAILED(ppDSBuffer->QueryInterface(IID_IDirectSound3DBuffer, (LPVOID*)&ppDS3DBuffer)) || !ppDS3DBuffer) + ppDSBuffer->Release(); + + IKsPropertySet* ppIKsPropertySet{}; + if (ppDS3DBuffer->QueryInterface(IID_IKsPropertySet, (LPVOID*)&ppIKsPropertySet) == S_OK) { + // TODO: EAX (0x4D8593) + m_n3dEffectsQueryResult = 1; + SAFE_RELEASE(ppIKsPropertySet); + } + if (!m_n3dEffectsQueryResult) + m_n3dEffectsQueryResult = 2; + + SAFE_RELEASE(ppDS3DBuffer); + SAFE_RELEASE(ppDSBuffer); } // 0x4D9870 void CAEAudioHardware::Service() { - plugin::CallMethod<0x4D9870, CAEAudioHardware*>(this); + VERIFY(SUCCEEDED(m_pDirectSound3dListener->CommitDeferredSettings())); + RescaleChannelVolumes(); + if (m_n3dEffectsQueryResult) { + UpdateReverbEnvironment(); + } + for (const auto ch : GetChannels() | rng::views::drop(1)) { + ch->SynchPlayback(); + } + for (const auto ch : GetChannels() | rng::views::drop(1)) { + ch->Service(); + } + m_pMP3BankLoader->UpdateVirtualChannels( + &m_VirtualChannelSettings, + m_VirtualChannelSoundLengths, + m_VirtualChannelLoopTimes + ); + m_pMP3BankLoader->Service(); } diff --git a/source/game_sa/Audio/hardware/AEAudioHardware.h b/source/game_sa/Audio/hardware/AEAudioHardware.h index 0b615c9add..33f8ad99da 100644 --- a/source/game_sa/Audio/hardware/AEAudioHardware.h +++ b/source/game_sa/Audio/hardware/AEAudioHardware.h @@ -4,15 +4,18 @@ #include "Vector.h" +#include "AETrackLoader.h" #include "AESoundManager.h" #include "AEStreamThread.h" +#include "AudioEngine.h" +#include "AEMP3BankLoader.h" #ifdef PlaySound #undef PlaySound #endif union CAEAudioHardwarePlayFlags { - uint16 m_nFlags; + uint16 m_nFlags{}; struct { uint16 m_bIsFrontend : 1; uint16 m_bIsUncompressable : 1; @@ -26,6 +29,18 @@ union CAEAudioHardwarePlayFlags { uint16 m_bIsForcedFront : 1; uint16 m_bUnpausable : 1; }; + + void CopyFromAESound(const CAESound& sound) { + m_bIsFrontend = sound.GetFrontEnd(); + m_bIsUncompressable = sound.GetUncompressable(); + m_bIsUnduckable = sound.GetUnduckable(); + m_bIsStartPercentage = sound.GetStartPercentage(); + m_bIsMusicMastered = sound.GetMusicMastered(); + m_bIsRolledOff = sound.GetRolledOff(); + m_bIsSmoothDucking = sound.GetSmoothDucking(); + m_bIsForcedFront = sound.GetForcedFront(); + m_bUnpausable = m_bIsFrontend ? sound.GetUnpausable() : false; + } }; class CAEStreamingChannel; @@ -38,51 +53,57 @@ class tBeatInfo; class CAEAudioHardware { public: - bool m_bInitialised; // maybe - bool m_bDisableEffectsLoading; - uint8 m_prev; - uint8 field_3; - uint8 field_4; - uint8 m_nReverbEnvironment; - int16 m_awChannelFlags[MAX_NUM_AUDIO_CHANNELS]; - uint16 field_86; - int32 m_nReverbDepth; - uint16 m_nNumAvailableChannels; - int16 m_nNumChannels; - uint16 m_anNumChannelsInSlot[MAX_NUM_AUDIO_CHANNELS]; - float m_afChannelVolumes[MAX_NUM_AUDIO_CHANNELS]; - float m_afUnkn[MAX_NUM_AUDIO_CHANNELS]; - float m_afChannelsFrqScalingFactor[MAX_NUM_AUDIO_CHANNELS]; - float m_fMusicMasterScalingFactor; - float m_fEffectMasterScalingFactor; - float m_fMusicFaderScalingFactor; - float m_fEffectsFaderScalingFactor; - float m_fNonStreamFaderScalingFactor; - float m_fStreamFaderScalingFactor; - float field_428; - float field_42C; - int16 m_aBankSlotIds[MAX_NUM_SOUNDS]; - int16 m_aSoundIdsInSlots[MAX_NUM_SOUNDS]; - int16 m_anVirtualChannelSoundLoopStartTimes[MAX_NUM_SOUNDS]; - int16 m_anVirtualChannelSoundLengths[MAX_NUM_SOUNDS]; - uint32 m_nBassSet; - float m_fBassEqGain; - CAEMP3BankLoader* m_pMP3BankLoader; - CAEMP3TrackLoader* m_pMP3TrackLoader; - IDirectSound8* m_pDevice; - void* m_dwSpeakerConfig; - void* m_n3dEffectsQueryResult; - DSCAPS m_dsCaps; - IDirectSound3DListener* m_pDirectSound3dListener; - CAEStreamingChannel* m_pStreamingChannel; - CAEStreamThread m_pStreamThread; - CAEAudioChannel* m_aChannels[MAX_NUM_AUDIO_CHANNELS]; - uint32 m_aBeatInfo[40]; - int32 field_1004; - int32 field_1008; - int32 field_100C; - uint8 field_1010; - int32 field_1014; + bool m_bInitialised{}; + bool m_bDisableEffectsLoading{}; + uint8 m_prev{}; + uint8 field_3{}; + uint8 field_4{}; + uint8 m_nReverbEnvironment{ (uint8)-1}; + int16 m_awChannelFlags[MAX_NUM_AUDIO_CHANNELS]{}; + uint16 field_86{}; + int32 m_nReverbDepth{ -10000 }; + uint16 m_nNumAvailableChannels{}; + uint16 m_nNumChannels{}; + uint16 m_anNumChannelsInSlot[MAX_NUM_AUDIO_CHANNELS]{}; + float m_afChannelVolumes[MAX_NUM_AUDIO_CHANNELS]{}; // -1000.f + float m_afUnkn[MAX_NUM_AUDIO_CHANNELS]{}; + float m_afChannelsFrqScalingFactor[MAX_NUM_AUDIO_CHANNELS]{}; + + float m_fMusicMasterScalingFactor{ 1.f }; + float m_fEffectMasterScalingFactor{ 1.f }; + + float m_fMusicFaderScalingFactor{ 1.f }; + float m_fEffectsFaderScalingFactor{ 1.f }; + + float m_fNonStreamFaderScalingFactor{ 1.f }; + float m_fStreamFaderScalingFactor{ 1.f }; + + float field_428{}; + float field_42C{}; + union { // TODO: Get rid of the union, and use `m_VirtualChannelSettings` directly + tVirtualChannelSettings m_VirtualChannelSettings{}; + struct { + int16 m_aBankSlotIds[MAX_NUM_SOUNDS]; + int16 m_aSoundIdsInSlots[MAX_NUM_SOUNDS]; + }; + }; + int16 m_VirtualChannelLoopTimes[MAX_NUM_SOUNDS]{}; + int16 m_VirtualChannelSoundLengths[MAX_NUM_SOUNDS]{}; + + uint8 m_nBassSet{}; + float m_fBassEqGain{}; + CAEMP3BankLoader* m_pMP3BankLoader{}; + CAEMP3TrackLoader* m_pMP3TrackLoader{}; + IDirectSound8* m_pDSDevice{}; + uint32 m_nSpeakerConfig{}; + int32 m_n3dEffectsQueryResult{}; + DSCAPS m_dsCaps{}; + IDirectSound3DListener* m_pDirectSound3dListener{}; + CAEStreamingChannel* m_pStreamingChannel{}; + CAEStreamThread m_pStreamThread{}; + CAEAudioChannel* m_aChannels[MAX_NUM_AUDIO_CHANNELS]{}; + tBeatInfo gBeatInfo{}; + uint8 m_PlayingTrackFlags{}; public: static void InjectHooks(); @@ -90,7 +111,7 @@ class CAEAudioHardware { // Return types aren't real, I've just copied the signatures for now CAEAudioHardware(); - ~CAEAudioHardware(); + ~CAEAudioHardware() = default; bool Initialise(); void InitOpenALListener(); @@ -104,16 +125,16 @@ class CAEAudioHardware { void GetChannelPlayTimes(int16 channel, int16* playTimes); void SetChannelVolume(int16 channel, uint16 channelId, float volume, uint8 unused); - bool LoadSoundBank(uint16 bankId, int16 bankSlotId); + void LoadSoundBank(uint16 bankId, int16 bankSlotId); bool IsSoundBankLoaded(uint16 bankId, int16 bankSlotId); int8 GetSoundBankLoadingStatus(uint16 bankId, int16 bankSlotId); - bool LoadSound(uint16 bank, uint16 sound, int16 slot); + void LoadSound(uint16 bank, uint16 sound, int16 slot); bool IsSoundLoaded(uint16, uint16, int16); bool GetSoundLoadingStatus(uint16 bankId, uint16 sfxId, int16 bankSlot); void StopSound(int16 channel, uint16 channelSlot); - void SetChannelPosition(int16 channel, uint16 channelSlot, CVector* vecPos, uint8 unused); + void SetChannelPosition(int16 slotId, uint16 channelSlot, const CVector& vecPos, uint8 unused); void SetChannelFrequencyScalingFactor(int16 channel, uint16 channelSlot, float freqFactor); void RescaleChannelVolumes(); void UpdateReverbEnvironment(); @@ -126,7 +147,7 @@ class CAEAudioHardware { void GetVirtualChannelSoundLengths(int16* outArr); void GetVirtualChannelSoundLoopStartTimes(int16* outArr); - void PlayTrack(uint32 trackId, int32 nextTrackId, uint32 a3, char a4, bool isUserTrack, bool nextIsUserTrack); + void PlayTrack(uint32 trackID, int nextTrackID, uint32 startOffsetMs, uint8 trackFlags, bool bUserTrack, bool bUserNextTrack); void StartTrackPlayback() const; void StopTrack(); @@ -167,8 +188,24 @@ class CAEAudioHardware { void Query3DSoundEffects(); void Service(); -}; -VALIDATE_SIZE(CAEAudioHardware, 0x1018); +private: + auto GetChannels() const { return std::span{m_aChannels, m_nNumChannels}; } + +private: + // 0x4D83E0 + CAEAudioHardware* Constructor() { + this->CAEAudioHardware::CAEAudioHardware(); + return this; + } + + // 0x4D83A0 + CAEAudioHardware* Destructor() { + this->CAEAudioHardware::~CAEAudioHardware(); + return this; + } + +}; +VALIDATE_SIZE(CAEAudioHardware, 0x1014); // Size might be bigger, but nothing is accessed beyond `0x1014` extern CAEAudioHardware& AEAudioHardware; diff --git a/source/game_sa/Audio/hardware/AEStaticChannel.cpp b/source/game_sa/Audio/hardware/AEStaticChannel.cpp index 4f8d3d86f2..914ef2c620 100644 --- a/source/game_sa/Audio/hardware/AEStaticChannel.cpp +++ b/source/game_sa/Audio/hardware/AEStaticChannel.cpp @@ -8,21 +8,70 @@ void CAEStaticChannel::InjectHooks() { RH_ScopedClass(CAEStaticChannel); RH_ScopedCategory("Audio/Hardware"); + RH_ScopedVirtualInstall(Service, 0x4F10D0); RH_ScopedVirtualInstall(IsSoundPlaying, 0x4F0F40); RH_ScopedVirtualInstall(GetPlayTime, 0x4F0F70); RH_ScopedVirtualInstall(GetLength, 0x4F0FA0); + RH_ScopedVirtualInstall(Play, 0x4F0BD0); RH_ScopedVirtualInstall(SynchPlayback, 0x4F1040); RH_ScopedVirtualInstall(Stop, 0x4F0FB0); + + RH_ScopedInstall(SetAudioBuffer, 0x4F0C40, {.reversed = false}); } CAEStaticChannel::CAEStaticChannel(IDirectSound* pDirectSound, uint16 channelId, bool arg3, uint32 samplesPerSec, uint16 bitsPerSample) : CAEAudioChannel(pDirectSound, channelId, samplesPerSec, bitsPerSample) { - m_bUnkn1 = false; + m_bNeedData = false; m_bNeedsSynch = false; field_8A = arg3; } +// 0x4F10D0 +void CAEStaticChannel::Service() { + if (!m_pDirectSoundBuffer) { + m_nBufferStatus = 0; + return; + } + + if (m_bNeedData && (int32)(CTimer::GetTimeInMS() - m_nSyncTime) > field_74) { + uint8* ppvAudioPtr1{}; + DWORD pdwAudioBytes{}; + + VERIFY(SUCCEEDED(m_pDirectSoundBuffer->Lock( + m_dwLockOffset, + m_nNumLockBytes, + reinterpret_cast(&ppvAudioPtr1), + &pdwAudioBytes, + nullptr, + 0, + 0 + ))); + + for (auto i = 0u; i < m_nNumLoops; i++) { + memcpy( + &ppvAudioPtr1[i * m_nNumLockBytes], + (uint8*)m_pBuffer + m_nCurrentBufferOffset, + m_nNumLockBytes + ); + } + VERIFY(SUCCEEDED(m_pDirectSoundBuffer->Unlock(ppvAudioPtr1, pdwAudioBytes, nullptr, 0))); + m_bNeedData = false; + } + + UpdateStatus(); + + if (!m_bNoScalingFactor && !bufferStatus.Bit0x1) { + if (const auto buf = std::exchange(m_pDirectSoundBuffer, nullptr)) { + --g_numSoundChannelsUsed; + buf->Release(); + } + } +} +void CAEStaticChannel::Service_Reversed() { + CAEStaticChannel::Service(); +} + // 0x4F0F40 bool CAEStaticChannel::IsSoundPlaying() { if (!m_pDirectSoundBuffer) @@ -57,7 +106,21 @@ uint16 CAEStaticChannel::GetLength_Reversed() { return CAEStaticChannel::GetLength(); } -// 0x4F1040 +// 0x4F0BD0 +void CAEStaticChannel::Play(int16 timeInMs, int8 unused, float scalingFactor) { + if (m_bLooped && m_nCurrentBufferOffset != 0 || !timeInMs) { + m_bUnkn2 = false; + } else { + m_pDirectSoundBuffer->SetCurrentPosition(ConvertFromMsToBytes(timeInMs)); + m_bUnkn2 = true; + } + m_bNeedsSynch = true; + m_bNoScalingFactor = scalingFactor == 0.0f; +} +void CAEStaticChannel::Play_Reversed(int16 a, int8 b, float c) { + CAEStaticChannel::Play(a, b, c); +} + // 0x4F1040 void CAEStaticChannel::SynchPlayback() { if (!m_pDirectSoundBuffer || !m_bNeedsSynch || m_bNoScalingFactor) return; @@ -103,3 +166,7 @@ void CAEStaticChannel::Stop() { void CAEStaticChannel::Stop_Reversed() { CAEStaticChannel::Stop(); } + +bool CAEStaticChannel::SetAudioBuffer(IDirectSound3DBuffer* buffer, uint16 size, int16 f88, int16 f8c, int16 loopOffset, uint16 frequency) { + return false; +} diff --git a/source/game_sa/Audio/hardware/AEStaticChannel.h b/source/game_sa/Audio/hardware/AEStaticChannel.h index 17e8e82625..603bb80765 100644 --- a/source/game_sa/Audio/hardware/AEStaticChannel.h +++ b/source/game_sa/Audio/hardware/AEStaticChannel.h @@ -3,7 +3,7 @@ class NOTSA_EXPORT_VTABLE CAEStaticChannel : public CAEAudioChannel { public: - bool m_bUnkn1; + bool m_bNeedData; bool m_bUnkn2; bool m_bNeedsSynch; bool m_bUnkn4; @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CAEStaticChannel : public CAEAudioChannel { int32 field_74; int32 m_dwLockOffset; int32 m_nNumLockBytes; - uint16 field_80; + uint16 m_nNumLoops; uint16 field_82; IDirectSound3DBuffer* m_pBuffer; uint16 field_88; @@ -25,17 +25,15 @@ class NOTSA_EXPORT_VTABLE CAEStaticChannel : public CAEAudioChannel { public: CAEStaticChannel(IDirectSound* pDirectSound, uint16 channelId, bool arg3, uint32 samplesPerSec, uint16 bitsPerSample); - // VIRTUAL - void Service() override { assert(false); /* Needs reversing */ }; + void Service() override; bool IsSoundPlaying() override; int16 GetPlayTime() override; uint16 GetLength() override; - void Play(int16, int8, float) override { assert(false); /* Needs reversing */ }; + void Play(int16 timeInMs, int8 unused, float scalingFactor) override; void SynchPlayback() override; void Stop() override; - // CLASS - bool SetAudioBuffer(IDirectSound3DBuffer* buffer, uint16 soundIdInSlot, int16, int16, uint16 loopOffset); + bool SetAudioBuffer(IDirectSound3DBuffer* buffer, uint16 size, int16 f88, int16 f8c, int16 loopOffset, uint16 frequency); private: friend void InjectHooksMain(); @@ -45,7 +43,7 @@ class NOTSA_EXPORT_VTABLE CAEStaticChannel : public CAEAudioChannel { bool IsSoundPlaying_Reversed(); int16 GetPlayTime_Reversed(); uint16 GetLength_Reversed(); - void Play_Reversed(int16, int8, float); + void Play_Reversed(int16 a, int8 b, float c); void SynchPlayback_Reversed(); void Stop_Reversed(); }; diff --git a/source/game_sa/Audio/hardware/AEStreamingChannel.cpp b/source/game_sa/Audio/hardware/AEStreamingChannel.cpp index 6d48f874d9..dbe134708d 100644 --- a/source/game_sa/Audio/hardware/AEStreamingChannel.cpp +++ b/source/game_sa/Audio/hardware/AEStreamingChannel.cpp @@ -1,40 +1,166 @@ #include "StdInc.h" +#include + #include "AEStreamingChannel.h" +#include "AEAudioUtility.h" -// 0x4F1800 -CAEStreamingChannel::CAEStreamingChannel(IDirectSound* directSound, uint16 channelId) : CAEAudioChannel(directSound, channelId, 48000, 16) { - plugin::CallMethod<0x4F1800, CAEStreamingChannel*, IDirectSound*, uint16>(this, directSound, channelId); -} +//! Stereo channel FX Param EQ presets [0x8CBA70] +static inline DSFXParamEq s_FXParamEqPresets[3][2]{ + {{ 0.0f, 0.0f, 0.0f }, { 0.f, 0.0f, 0.0f }}, // Preset 1 + {{ 80.f, 30.f, 4.0f }, { 180.f, 30.f, 1.5f }}, // Preset 2 + {{ 80.f, 36.f, -15.f }, { 80.f, 36.f, -15.f }}, // Preset 3 +}; // 0x4F2200 CAEStreamingChannel::~CAEStreamingChannel() { - plugin::CallMethod<0x4F2200, CAEStreamingChannel*>(this); + DirectSoundBufferFadeToSilence(); + + m_nState = StreamingChannelState::UNK_MINUS_5; + field_60088 = 0; + + if (m_pDirectSoundBuffer8) + m_pDirectSoundBuffer8->SetFX(0, nullptr, nullptr); + + if (m_pSilenceBuffer) + m_pSilenceBuffer->Release(); + + if (m_pDirectSoundBuffer) + m_pDirectSoundBuffer->Release(); + + if (m_pStreamingDecoder) + delete m_pStreamingDecoder; + + if (m_pNextStreamingDecoder) + delete m_pNextStreamingDecoder; } // 0x4F22F0 void CAEStreamingChannel::Initialise() { - plugin::CallMethod<0x4F22F0, CAEStreamingChannel*>(this); + VERIFY(SUCCEEDED(CoInitialize(nullptr))); + + DSBUFFERDESC bufferDesc{}; + bufferDesc.guid3DAlgorithm = GUID_NULL; + bufferDesc.lpwfxFormat = &m_WaveFormat; + bufferDesc.dwSize = 36; + bufferDesc.dwBufferBytes = 0xC0000; + bufferDesc.dwReserved = 0; + bufferDesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLFX | DSBCAPS_CTRLVOLUME | DSBCAPS_CTRLFREQUENCY | DSBCAPS_LOCSOFTWARE; + + m_WaveFormat.wFormatTag = WAVE_FORMAT_PCM; + m_WaveFormat.cbSize = 0; + m_WaveFormat.nChannels = 2; + m_WaveFormat.wBitsPerSample = 16; + m_WaveFormat.nSamplesPerSec = 48000; + m_WaveFormat.nAvgBytesPerSec = 192000; + m_WaveFormat.nBlockAlign = 4; + + if (SUCCEEDED(m_pDirectSound->CreateSoundBuffer( + &bufferDesc, + &m_pDirectSoundBuffer, + 0 + ))) { + m_bInitialized = true; + SetOriginalFrequency(m_WaveFormat.nSamplesPerSec); + m_pBuffer = m_aBuffer; + m_bLooped = true; + } + InitialiseSilence(); } // 0x4F1C70 void CAEStreamingChannel::InitialiseSilence() { - return plugin::CallMethod<0x4F1C70, CAEStreamingChannel*>(this); + DSBUFFERDESC bufferDesc{}; + bufferDesc.dwSize = 36; + bufferDesc.lpwfxFormat = &m_WaveFormat; + bufferDesc.dwBufferBytes = 0x8000; + bufferDesc.dwFlags = DSBCAPS_GLOBALFOCUS | DSBCAPS_LOCSOFTWARE; + + if (SUCCEEDED(m_pDirectSound->CreateSoundBuffer(&bufferDesc, &m_pSilenceBuffer, 0))) { + void *audioPtr; + DWORD audioPtrBytes; + + m_pSilenceBuffer->Lock(0, 0, &audioPtr, &audioPtrBytes, nullptr, 0, DSBLOCK_ENTIREBUFFER); + memset(audioPtr, 0, audioPtrBytes); + m_pSilenceBuffer->Unlock(audioPtr, audioPtrBytes, nullptr, 0); + m_pSilenceBuffer->Play(0, 0, DSBPLAY_LOOPING); + + } else { + NOTSA_LOG_WARN("Creating silence buffer failed."); + } } // 0x4F1FF0 void CAEStreamingChannel::SetReady() { - plugin::CallMethod<0x4F1FF0, CAEStreamingChannel*>(this); + switch (m_nState) { + case StreamingChannelState::UNK_MINUS_5: + AESmoothFadeThread.CancelFade(m_pDirectSoundBuffer); + m_pDirectSoundBuffer->Stop(); + m_nState = StreamingChannelState::UNK_MINUS_6; + + break; + case StreamingChannelState::UNK_MINUS_6: + if (m_pStreamingDecoder) + m_nState = StreamingChannelState::UNK_MINUS_2; + break; + } } // 0x4F1F30 -void CAEStreamingChannel::SetBassEQ(IDirectSoundFXParamEq* paramEq, float gain) { - plugin::CallMethod<0x4F1F30, CAEStreamingChannel*, IDirectSoundFXParamEq*, float>(this, paramEq, gain); +void CAEStreamingChannel::SetBassEQ(uint8 mode, float gain) { + if (mode == 0) { + if (m_bFxEnabled) + RemoveFX(); + return; + } + + if (!m_bFxEnabled && !AddFX()) { + return; + } + + for (auto i = 0; i < 2; i++) { + IDirectSoundFXParamEq* fxEqParam; + if (FAILED(m_pDirectSoundBuffer8->GetObjectInPath( + GUID_All_Objects, + i, + IID_IDirectSoundFXParamEq, + reinterpret_cast(&fxEqParam) + ))) { + continue; + } + + DSFXParamEq savedFxExParam{ s_FXParamEqPresets[mode][i] }; + savedFxExParam.fGain *= gain; + VERIFY(SUCCEEDED(fxEqParam->SetAllParameters(&savedFxExParam))); + fxEqParam->Release(); + } } // 0x4F2060 -void CAEStreamingChannel::SetFrequencyScalingFactor(float a2) { - plugin::CallMethod<0x4F2060, CAEStreamingChannel*, float>(this, a2); +void CAEStreamingChannel::SetFrequencyScalingFactor(float factor) { + if (factor == 0.0f) { + if (!m_pDirectSoundBuffer || m_nState == StreamingChannelState::UNK_MINUS_7 || !IsBufferPlaying()) + return; + + DirectSoundBufferFadeToSilence(); + m_nState = StreamingChannelState::UNK_MINUS_7; + } else { + SetFrequency(static_cast((float)m_nOriginalFrequency * factor)); + + if (m_nState != StreamingChannelState::UNK_MINUS_7) + return; + + if (!m_pDirectSoundBuffer) + return; + + m_pDirectSoundBuffer->SetVolume(-10'000); + m_pDirectSoundBuffer->Play(0, 0, m_bLooped ? DSBPLAY_LOOPING : 0); + + if (!AESmoothFadeThread.RequestFade(m_pDirectSoundBuffer, m_fVolume, 35, true)) + m_pDirectSoundBuffer->SetVolume(static_cast(m_fVolume * 100.0f)); + + m_nState = StreamingChannelState::UNK_MINUS_1; + } } // 0x4F1870 @@ -44,27 +170,134 @@ void CAEStreamingChannel::SynchPlayback() { // 0x4F1E20 uint32 CAEStreamingChannel::FillBuffer(void* buffer, uint32 size) { - return plugin::CallMethodAndReturn(this, buffer, size); -} - -// 0x4F23D0 -void CAEStreamingChannel::PrepareStream(CAEStreamingDecoder* stream, int8 arg2, bool bStopCurrent) { - plugin::CallMethod<0x4F23D0, CAEStreamingChannel*, CAEStreamingDecoder*, int8, bool>(this, stream, arg2, bStopCurrent); + auto filled = m_pStreamingDecoder->FillBuffer(buffer, size); + if (filled < size) { + if (field_61) { + while (filled < size) { + m_pStreamingDecoder->SetCursor(1); + filled += m_pStreamingDecoder->FillBuffer((uint8*)buffer + filled, size - filled); + } + } else { + if (m_pNextStreamingDecoder) { + if (m_pNextStreamingDecoder->GetSampleRate() == m_pStreamingDecoder->GetSampleRate()) { + if (m_pStreamingDecoder) + delete m_pStreamingDecoder; + + m_pStreamingDecoder = m_pNextStreamingDecoder; + m_pNextStreamingDecoder = nullptr; + + filled += m_pStreamingDecoder->FillBuffer((uint8*)buffer + filled, size - filled); + } else { + memset((uint8*)buffer + filled, 0, size - filled); + filled = size; + m_bWrongSampleRate = true; + } + } else { + m_nState = StreamingChannelState::UNK_MINUS_4; + } + } + } + + m_nStreamPlayTimeMs = m_pStreamingDecoder->GetStreamLengthMs(); + return filled; +} + +// 0x4F23D0, broken af +void CAEStreamingChannel::PrepareStream(CAEStreamingDecoder* newDecoder, int8 arg2, uint32 audioBytes) { + if (!newDecoder || !m_pDirectSoundBuffer) + return; + + if (audioBytes != 0) { + switch (m_nState) { + case StreamingChannelState::UNK_MINUS_3: + case StreamingChannelState::UNK_MINUS_4: + case StreamingChannelState::UNK_MINUS_1: + DirectSoundBufferFadeToSilence(); + break; + default: + break; + } + + m_nState = StreamingChannelState::UNK_MINUS_6; + } + + if (m_pStreamingDecoder) + delete m_pStreamingDecoder; + + field_61 = arg2; + m_pStreamingDecoder = newDecoder; + if (SUCCEEDED(m_pDirectSoundBuffer->Lock( + 0, + 0, + (LPVOID*)(&newDecoder), + (DWORD*)&audioBytes, + nullptr, + 0, + DSBLOCK_ENTIREBUFFER + ))) { + const auto written = FillBuffer(newDecoder, audioBytes); + if (written == audioBytes) { + field_64 = 0; + } else { + memset(reinterpret_cast(newDecoder) + written, 0, audioBytes - written); + field_64 = 1; + } + + // ? + if (audioBytes & 0xFFFFFFFC) { + for (auto i = 0u; i < audioBytes >> 2; i++) { + // *((uint32*)&stream->_vftable + i) |= 0x10001u; + } + } + + m_pDirectSoundBuffer->Unlock(newDecoder, audioBytes, nullptr, 0); + } + + switch (m_nState) { + case StreamingChannelState::UNK_MINUS_3: + case StreamingChannelState::UNK_MINUS_1: + case StreamingChannelState::UNK_MINUS_7: + m_nState = StreamingChannelState::UNK_MINUS_2; + break; + default: + break; + } + + m_nCurrentlyLoadedChunk = 0; + SetOriginalFrequency(newDecoder->GetSampleRate()); + m_bWrongSampleRate = false; + m_bPrepareNewStream = false; + field_45 = 0; + m_pDirectSoundBuffer->SetCurrentPosition(0); } // 0x4F1DE0 void CAEStreamingChannel::SetNextStream(CAEStreamingDecoder* decoder) { - return plugin::CallMethod<0x4F1DE0, CAEStreamingChannel*, CAEStreamingDecoder*>(this, decoder); + if (m_pNextStreamingDecoder) { + delete m_pNextStreamingDecoder; + } + m_pNextStreamingDecoder = decoder; } // 0x4F1A60 int32 CAEStreamingChannel::GetPlayingTrackID() { - return plugin::CallMethodAndReturn(this); + switch (m_nState) { + case StreamingChannelState::UNK_MINUS_3: + case StreamingChannelState::UNK_MINUS_4: + case StreamingChannelState::UNK_MINUS_1: + case StreamingChannelState::UNK_MINUS_5: + if (m_pStreamingDecoder) + return m_pStreamingDecoder->GetStreamID(); + + [[fallthrough]]; + default: + return -1; + } } // 0x4F1A40 int32 CAEStreamingChannel::GetActiveTrackID() { - return plugin::CallMethodAndReturn(this); + return m_pStreamingDecoder ? m_pStreamingDecoder->GetStreamID() : -1; } // 0x4F18A0 @@ -74,42 +307,141 @@ int32 CAEStreamingChannel::UpdatePlayTime() { // 0x4F1AE0 bool CAEStreamingChannel::AddFX() { - return plugin::CallMethodAndReturn(this); + if (m_bFxEnabled) + return true; + + DSEFFECTDESC effectDesc[2]{}; + for (auto& desc : effectDesc) { + desc.guidDSFXClass = GUID_DSFX_STANDARD_PARAMEQ; + desc.dwFlags = 0; + desc.dwReserved1 = desc.dwReserved2 = 0; + desc.dwSize = 32; + } + + bool wasStopped = false; + if (bufferStatus.Bit0x1) { + m_pDirectSoundBuffer->Stop(); + wasStopped = true; + } + + if (FAILED(m_pDirectSoundBuffer8->SetFX( + std::size(effectDesc), + effectDesc, + nullptr + ))) + return false; + + for (auto i = 0; i < 2; i++) { // Set both channels + IDirectSoundFXParamEq* fxParamEq{}; + m_pDirectSoundBuffer8->GetObjectInPath( + GUID_All_Objects, + i, + IID_IDirectSoundFXParamEq, + reinterpret_cast(&fxParamEq) + ); + fxParamEq->SetAllParameters(&s_FXParamEqPresets[0][i]); + fxParamEq->Release(); + } + + if (wasStopped) + m_pDirectSoundBuffer->Play(0, 0, DSBPLAY_LOOPING); + + m_bFxEnabled = true; + return true; } // 0x4F1C20 void CAEStreamingChannel::RemoveFX() { - plugin::CallMethod<0x4F1C20, CAEStreamingChannel*>(this); + bool stopped{false}; + if (bufferStatus.Bit0x1) { + m_pDirectSoundBuffer->Stop(); + stopped = true; + } + + m_pDirectSoundBuffer8->SetFX(0, nullptr, nullptr); + + if (stopped) + m_pDirectSoundBuffer->Play(0, 0, DSBPLAY_LOOPING); + + m_bFxEnabled = false; } // 0x4F2040 bool CAEStreamingChannel::IsSoundPlaying() { - return plugin::CallMethodAndReturn(this); + switch (m_nState) { + case StreamingChannelState::UNK_MINUS_3: + case StreamingChannelState::UNK_MINUS_4: + case StreamingChannelState::UNK_MINUS_5: + case StreamingChannelState::UNK_MINUS_1: + return true; + default: + return false; + } } // 0x4F19E0 int16 CAEStreamingChannel::GetPlayTime() { - return plugin::CallMethodAndReturn(this); + switch (m_nState) { + case StreamingChannelState::UNK_MINUS_1: + case StreamingChannelState::UNK_MINUS_3: + if (CAEAudioUtility::GetCurrentTimeInMS() - m_nLastUpdateTime >= 1000u) + return UpdatePlayTime(); + else + return static_cast(m_nPlayTime - static_cast(m_nLastUpdateTime) + CAEAudioUtility::GetCurrentTimeInMS()); + break; + default: + break; + } + + return static_cast(m_nState); // possibly not intended. } // 0x4F1880 uint16 CAEStreamingChannel::GetLength() { - return plugin::CallMethodAndReturn(this); + return static_cast(m_pStreamingDecoder ? m_pStreamingDecoder->GetStreamLengthMs() : -1); } // 0x4F1D40 -void CAEStreamingChannel::Play(int16 a2, int8 a3, float a4) { - plugin::CallMethod<0x4F1D40, CAEStreamingChannel*, int16, int8, float>(this, a2, a3, a4); +void CAEStreamingChannel::Play(int16, int8 a3, float) { + if (!m_pStreamingDecoder || !m_pDirectSoundBuffer) + return; + + SetOriginalFrequency(m_pStreamingDecoder->GetSampleRate()); + + if (m_nState != StreamingChannelState::UNK_MINUS_7) + field_61 = a3; + + if (m_nState == StreamingChannelState::UNK_MINUS_5) { + AESmoothFadeThread.CancelFade(m_pDirectSoundBuffer); + m_pDirectSoundBuffer->Stop(); + + m_nState = StreamingChannelState::UNK_MINUS_6; + } + + m_nState = StreamingChannelState::UNK_MINUS_1; + m_pDirectSoundBuffer->SetVolume(static_cast(m_fVolume * 100.0f)); + m_pDirectSoundBuffer->Play(0, 0, DSBPLAY_LOOPING); } // 0x4F2170 void CAEStreamingChannel::Pause() { - plugin::CallMethod<0x4F2170, CAEStreamingChannel*>(this); + if (!m_pDirectSoundBuffer) + return; + + DirectSoundBufferFadeToSilence(); + + m_nState = StreamingChannelState::UNK_MINUS_7; } // 0x4F1A90 void CAEStreamingChannel::Stop() { - plugin::CallMethod<0x4F1A90, CAEStreamingChannel*>(this); + DirectSoundBufferFadeToSilence(); + + // SA: dead code + if (false) { + m_nState = StreamingChannelState::UNK_MINUS_5; + field_60088 = 0; + } } // 0x4F2550 @@ -121,75 +453,28 @@ void CAEStreamingChannel::InjectHooks() { RH_ScopedClass(CAEStreamingChannel); RH_ScopedCategory("Audio/Hardware"); - RH_ScopedInstall(Constructor, 0x4F1800, { .reversed = false }); - // Install("CAEStreamingChannel", "~CAEStreamingChannel", 0x4F2200, static_cast(&CAEStreamingChannel::Destructor)); - RH_ScopedInstall(SynchPlayback, 0x4F1870, { .reversed = false }); + RH_ScopedInstall(Constructor, 0x4F1800, { .reversed = false }); // makes game not load radio + RH_ScopedInstall(Destructor, 0x4F2200); + RH_ScopedInstall(SynchPlayback, 0x4F1870); RH_ScopedInstall(PrepareStream, 0x4F23D0, { .reversed = false }); - RH_ScopedInstall(Initialise, 0x4F22F0, { .reversed = false }); - RH_ScopedInstall(Pause, 0x4F2170, { .reversed = false }); - RH_ScopedInstall(SetReady, 0x4F1FF0, { .reversed = false }); - RH_ScopedInstall(SetBassEQ, 0x4F1F30, { .reversed = false }); - RH_ScopedInstall(FillBuffer, 0x4F1E20, { .reversed = false }); - RH_ScopedInstall(InitialiseSilence, 0x4F1C70, { .reversed = false }); - RH_ScopedInstall(SetNextStream, 0x4F1DE0, { .reversed = false }); - RH_ScopedInstall(AddFX, 0x4F1AE0, { .reversed = false }); - // RH_ScopedOverloadedInstall(Stop, "", 0x4F1A90, int8(CAEStreamingChannel::*)(bool)); - RH_ScopedInstall(GetPlayingTrackID, 0x4F1A60, { .reversed = false }); - RH_ScopedInstall(GetActiveTrackID, 0x4F1A40, { .reversed = false }); + RH_ScopedInstall(Initialise, 0x4F22F0); + RH_ScopedInstall(Pause, 0x4F2170); + RH_ScopedInstall(SetReady, 0x4F1FF0); + RH_ScopedInstall(SetBassEQ, 0x4F1F30); + RH_ScopedInstall(FillBuffer, 0x4F1E20); + RH_ScopedInstall(InitialiseSilence, 0x4F1C70); + RH_ScopedInstall(SetNextStream, 0x4F1DE0); + RH_ScopedInstall(AddFX, 0x4F1AE0); + RH_ScopedInstall(Stop, 0x4F1A90); + RH_ScopedInstall(GetPlayingTrackID, 0x4F1A60); + RH_ScopedInstall(GetActiveTrackID, 0x4F1A40); RH_ScopedInstall(UpdatePlayTime, 0x4F18A0, { .reversed = false }); - RH_ScopedInstall(RemoveFX, 0x4F1C20, { .reversed = false }); + RH_ScopedInstall(RemoveFX, 0x4F1C20); RH_ScopedVirtualInstall(Service, 0x4F2550, { .reversed = false }); - RH_ScopedVirtualInstall(IsSoundPlaying, 0x4F2040, { .reversed = false }); - RH_ScopedVirtualInstall(GetPlayTime, 0x4F19E0, { .reversed = false }); - RH_ScopedVirtualInstall(GetLength, 0x4F1880, { .reversed = false }); - RH_ScopedVirtualInstall(Play, 0x4F1D40, { .reversed = false }); - // RH_ScopedVirtualOverloadedInstall(Stop, "", 0x4F21C0, int8(CAEStreamingChannel::*)()); - RH_ScopedVirtualInstall(SetFrequencyScalingFactor, 0x4F2060, { .reversed = false }); -} - -// 0x4F1800 -CAEStreamingChannel* CAEStreamingChannel::Constructor(IDirectSound* directSound, uint16 channelId) { - this->CAEStreamingChannel::CAEStreamingChannel(directSound, channelId); - return this; -} - -// -CAEStreamingChannel* CAEStreamingChannel::Destructor() { - this->CAEStreamingChannel::~CAEStreamingChannel(); - return this; -} - -// 0x4F2550 -void CAEStreamingChannel::Service_Reversed() { - CAEStreamingChannel::Service(); -} - -// 0x4F2040 -bool CAEStreamingChannel::IsSoundPlaying_Reversed() { - return CAEStreamingChannel::IsSoundPlaying(); -} - -// 0x4F19E0 -int32 CAEStreamingChannel::GetPlayTime_Reversed() { - return CAEStreamingChannel::GetPlayTime(); -} - -// 0x4F1880 -int32 CAEStreamingChannel::GetLength_Reversed() { - return CAEStreamingChannel::GetLength(); -} - -// 0x4F1D40 -void CAEStreamingChannel::Play_Reversed(int16 a2, char a3, float a4) { - CAEStreamingChannel::Play(a2, a3, a4); -} - -// 0x4F21C0 -void CAEStreamingChannel::Stop_Reversed() { - CAEStreamingChannel::Stop(); -} - -// 0x4F2060 -void CAEStreamingChannel::SetFrequencyScalingFactor_Reversed(float a2) { - CAEStreamingChannel::SetFrequencyScalingFactor(a2); + RH_ScopedVirtualInstall(IsSoundPlaying, 0x4F2040); + RH_ScopedVirtualInstall(GetPlayTime, 0x4F19E0); + RH_ScopedVirtualInstall(GetLength, 0x4F1880); + RH_ScopedVirtualInstall(Play, 0x4F1D40); + // RH_ScopedVirtualOverloadedInstall(Stop, "", 0x4F21C0, int8(CAEStreamingChannel::*)()); <-- unused, maybe inlined? + RH_ScopedVirtualInstall(SetFrequencyScalingFactor, 0x4F2060); } diff --git a/source/game_sa/Audio/hardware/AEStreamingChannel.h b/source/game_sa/Audio/hardware/AEStreamingChannel.h index e6d868e518..a181f97e7f 100644 --- a/source/game_sa/Audio/hardware/AEStreamingChannel.h +++ b/source/game_sa/Audio/hardware/AEStreamingChannel.h @@ -1,34 +1,46 @@ #pragma once #include "AEAudioChannel.h" +#include "AEStreamingDecoder.h" +#include "AESmoothFadeThread.h" -class CAEStreamingDecoder; +enum class StreamingChannelState : int32 { + UNK_MINUS_7 = -7, + UNK_MINUS_6 = -6, + UNK_MINUS_5 = -5, + UNK_MINUS_4 = -4, + UNK_MINUS_3 = -3, + UNK_MINUS_2 = -2, + UNK_MINUS_1 = -1, +}; class NOTSA_EXPORT_VTABLE CAEStreamingChannel : public CAEAudioChannel { public: - uint8 field_60; - uint8 field_61; - bool m_bPrepareNewStream; - uint8 field_63; - uint8 field_64; - uint8 m_bFxEnabled; - uint8 m_nCurrentlyLoadedChunk; - uint8 field_67; - void* m_pBuffer; - uint8 m_aBuffer[0x60000]; - CAEStreamingDecoder* m_pStreamingDecoder; - CAEStreamingDecoder* m_pNextStreamingDecoder; - uint32 m_nState; - uint32 m_nStreamPlayTimeMs; - uint32 m_nPlayTime; - uint32 field_60084; - int32 field_60088; - uint64 m_nLastUpdateTime; - IDirectSoundBuffer* m_pSilenceBuffer; - float m_fSomething; + bool m_bInitialized{false}; + uint8 field_61{0u}; + bool m_bPrepareNewStream{false}; + uint8 m_bWrongSampleRate{false}; + uint8 field_64{0u}; + uint8 m_bFxEnabled{false}; + uint8 m_nCurrentlyLoadedChunk{0u}; + uint8 field_67{0u}; + void* m_pBuffer{nullptr}; + uint8 m_aBuffer[0x60000]; + CAEStreamingDecoder* m_pStreamingDecoder{nullptr}; + CAEStreamingDecoder* m_pNextStreamingDecoder{nullptr}; + StreamingChannelState m_nState{StreamingChannelState::UNK_MINUS_6}; + uint32 m_nStreamPlayTimeMs{0u}; + uint32 m_nPlayTime; + uint32 field_60084; + int32 field_60088{0u}; + uint64 m_nLastUpdateTime; + IDirectSoundBuffer* m_pSilenceBuffer; + float m_fSomething{1.0f}; public: - CAEStreamingChannel(IDirectSound* directSound, uint16 channelId); + CAEStreamingChannel(IDirectSound* directSound, uint16 channelId) + : CAEAudioChannel(directSound, channelId, 48000, 16) + {} // 0x4F1800 ~CAEStreamingChannel() override; void Service() override; @@ -52,25 +64,57 @@ class NOTSA_EXPORT_VTABLE CAEStreamingChannel : public CAEAudioChannel { int32 GetPlayingTrackID(); void SetNextStream(CAEStreamingDecoder* decoder); uint32 FillBuffer(void* buffer, uint32 size); - void SetBassEQ(IDirectSoundFXParamEq* paramEq, float gain); + void SetBassEQ(uint8 mode, float gain); void SetReady(); - void PrepareStream(CAEStreamingDecoder* stream, int8 arg2, bool bStopCurrent); + void PrepareStream(CAEStreamingDecoder* stream, int8 arg2, uint32 audioBytes); void Pause(); private: friend void InjectHooksMain(); static void InjectHooks(); - CAEStreamingChannel* Constructor(IDirectSound* directSound, uint16 channelId); - CAEStreamingChannel* Destructor(); + CAEStreamingChannel* Constructor(IDirectSound* directSound, uint16 channelId) { + this->CAEStreamingChannel::CAEStreamingChannel(directSound, channelId); + return this; + } + + void Destructor() { + CAEStreamingChannel::~CAEStreamingChannel(); + } + + void Service_Reversed() { + CAEStreamingChannel::Service(); + } + + bool IsSoundPlaying_Reversed() { + return CAEStreamingChannel::IsSoundPlaying(); + } + + int32 GetPlayTime_Reversed() { + return CAEStreamingChannel::GetPlayTime(); + } + + int32 GetLength_Reversed() { + return CAEStreamingChannel::GetLength(); + } + + void Play_Reversed(int16 a2, char a3, float a4) { + CAEStreamingChannel::Play(a2, a3, a4); + } + + void Stop_Reversed() { + CAEStreamingChannel::Stop(); + } + + void SetFrequencyScalingFactor_Reversed(float a2) { + CAEStreamingChannel::SetFrequencyScalingFactor(a2); + } - void Service_Reversed(); - bool IsSoundPlaying_Reversed(); - int32 GetPlayTime_Reversed(); - int32 GetLength_Reversed(); - void Play_Reversed(int16 a2, char a3, float a4); - void Stop_Reversed(); - void SetFrequencyScalingFactor_Reversed(float a2); + // NOTSA + void DirectSoundBufferFadeToSilence() { + if (!AESmoothFadeThread.RequestFade(m_pDirectSoundBuffer, -100.0, 35, true)) + m_pDirectSoundBuffer->Stop(); + } }; VALIDATE_SIZE(CAEStreamingChannel, 0x60098); diff --git a/source/game_sa/Audio/loaders/AEBankLoader.cpp b/source/game_sa/Audio/loaders/AEBankLoader.cpp new file mode 100644 index 0000000000..eaf3d58905 --- /dev/null +++ b/source/game_sa/Audio/loaders/AEBankLoader.cpp @@ -0,0 +1,123 @@ +#include "StdInc.h" +#include "AEBankLoader.h" + +void CAEBankLoader::InjectHooks() { + RH_ScopedClass(CAEBankLoader); + RH_ScopedCategory("Audio/Loaders"); + + RH_ScopedInstall(Deconstructor, 0x4DFB20); + RH_ScopedInstall(GetBankLookup, 0x4E01B0); + RH_ScopedInstall(LoadBankLookupFile, 0x4DFBD0); + RH_ScopedInstall(LoadBankSlotFile, 0x4E0590); + RH_ScopedInstall(LoadSFXPakLookupFile, 0x4DFC70, {.reversed = true}); + RH_ScopedInstall(CalculateBankSlotsInfosOffsets, 0x4DFBA0); +} + +// 0x4DFB20 +CAEBankLoader::~CAEBankLoader() { + if (m_bInitialised) { + CMemoryMgr::Free(m_pBuffer); + + delete m_paBankSlots; + delete m_paBankLookups; + delete m_paStreamHandles; + } +} + +// 0x4E01B0 +CAEBankLookupItem* CAEBankLoader::GetBankLookup(uint16 bankId) { + if (m_bInitialised && bankId < m_nBankLookupCount) + return &m_paBankLookups[bankId]; + + return nullptr; +} + +// 0x4DFBD0 +bool CAEBankLoader::LoadBankLookupFile() { + const auto file = CFileMgr::OpenFile("AUDIO\\CONFIG\\BANKLKUP.DAT", "rb"); + if (!file) + return false; + + if (const auto size = CFileMgr::GetTotalSize(file)) { + m_nBankLookupCount = size / sizeof(CAEBankLookupItem); + m_paBankLookups = new CAEBankLookupItem[m_nBankLookupCount]; + + if (CFileMgr::Read(file, m_paBankLookups, size) != size) { + delete m_paBankLookups; + m_paBankLookups = nullptr; + } + + CFileMgr::CloseFile(file); + return m_paBankLookups != nullptr; + } else { + CFileMgr::CloseFile(file); + } + + return false; +} + +// 0x4E0590 +bool CAEBankLoader::LoadBankSlotFile() { + const auto file = CFileMgr::OpenFile("AUDIO\\CONFIG\\BANKSLOT.DAT", "rb"); + if (!file) + return false; + + m_pBuffer = nullptr; // NOTSA: for return check + + if (auto size = CFileMgr::GetTotalSize(file); size > 2) { + CFileMgr::Read(file, &m_nBankSlotCount, 2u); + m_paBankSlots = new CAEBankSlot[m_nBankSlotCount]; + + if (CFileMgr::Read(file, m_paBankSlots, size - 2) == size - 2) { + CalculateBankSlotsInfosOffsets(); + assert(m_nBankSlotCount > 0); + const auto& lastSlot = m_paBankSlots[m_nBankSlotCount - 1]; + m_nBufferSize = lastSlot.m_nOffset + lastSlot.m_nSize; + m_pBuffer = (uint8*)CMemoryMgr::Malloc(m_nBufferSize); + } else { + delete m_paBankSlots; + m_paBankSlots = nullptr; + } + } + + CFileMgr::CloseFile(file); + return m_pBuffer != nullptr; +} + +// 0x4DFC70 +bool CAEBankLoader::LoadSFXPakLookupFile() { + const auto file = CFileMgr::OpenFile("AUDIO\\CONFIG\\PAKFILES.DAT", "rb"); + bool failed = true; + if (!file) + return failed; + + if (auto totalSize = CFileMgr::GetTotalSize(file)) { + m_nPakLookupCount = totalSize / sizeof(tPakLookup); + m_paPakLookups = new tPakLookup[m_nPakLookupCount]; + + if (CFileMgr::Read(file, m_paPakLookups, totalSize) == totalSize) { + m_paStreamHandles = new int32[m_nPakLookupCount]; + + for (auto i = 0; i < m_nPakLookupCount; i++) { + // NOTSA: Originally a 128 char array allocated in the stack. + m_paStreamHandles[i] = CdStreamOpen(std::format("AUDIO\\SFX\\{}", m_paPakLookups[i].m_szName).c_str()); + } + failed = false; + } + + delete m_paPakLookups; + m_paPakLookups = nullptr; + } + + CFileMgr::CloseFile(file); + return !failed; +} + +// 0x4DFBA0 +void CAEBankLoader::CalculateBankSlotsInfosOffsets() { + auto offset = 0u; + for (auto& slot : std::span{m_paBankSlots, m_nBankSlotCount}) { + slot.m_nOffset = offset; + offset += slot.m_nSize; + } +} diff --git a/source/game_sa/Audio/loaders/AEBankLoader.h b/source/game_sa/Audio/loaders/AEBankLoader.h new file mode 100644 index 0000000000..60b22c0ce3 --- /dev/null +++ b/source/game_sa/Audio/loaders/AEBankLoader.h @@ -0,0 +1,144 @@ +#pragma once + +enum class eSoundRequestStatus : uint32 { + UNK_0, + JUST_LOADED, + ALREADY_LOADED, + UNK_3 +}; + +struct CAEBankLookupItem { + uint8 m_nPakFileNumber; + uint32 m_nOffset; + uint32 m_nSize; +}; +VALIDATE_SIZE(CAEBankLookupItem, 0xC); + +struct CAEBankSlotItem { + uint32 m_nOffset; + uint32 m_nLoopOffset; + uint16 m_usSampleRate; + int16 m_usSoundHeadroom; +}; +VALIDATE_SIZE(CAEBankSlotItem, 0xC); + +constexpr auto NUM_BANK_SLOT_ITEMS = 400u; + +struct CAEBankSlot { + uint32 m_nOffset; + uint32 m_nSize; + uint32 field_8; + uint32 field_C; + int16 m_nBankId; + int16 m_nSoundCount; // -1: Single sound. + CAEBankSlotItem m_aSlotItems[NUM_BANK_SLOT_ITEMS]; + + bool IsSingleSound() const { return m_nSoundCount == -1; } + + // Calculate size of the slot item by - + size_t CalculateSizeOfSlotItem(size_t index) const { + assert(m_nSoundCount == -1 || index < (size_t)m_nSoundCount); + const auto nextOffset = [&] { + if (index == m_nSoundCount - 1) { + return m_nSize; + } else { + return m_aSlotItems[index + 1].m_nOffset; + } + }(); + + return nextOffset - m_aSlotItems[index].m_nOffset; + } +}; +VALIDATE_SIZE(CAEBankSlot, 0x12D4); + +struct tPakLookup { + char m_szName[12]; + uint32 field_0C[10]; +}; +VALIDATE_SIZE(tPakLookup, 0x34); + +// NOTSA +#pragma warning(push) +#pragma warning(disable : 4200) // nonstandard extension used: zero-sized array in struct/union +struct CdAudioStream { + int16 m_nSoundCount; + int16 __pad; + CAEBankSlotItem m_aSlotItems[400]; + uint8 m_aBankData[]; // uint16 samples? +}; +#pragma warning(pop) +VALIDATE_SIZE(CdAudioStream, 0x12C4 /* + samples*/); + +class CAESoundRequest { +public: + CAEBankSlot* m_pBankSlotInfo{}; + uint32 m_nBankOffset{}; + uint32 m_nBankSize{}; + CdAudioStream* m_pStreamOffset{}; + CdAudioStream* m_pStreamBuffer{}; + eSoundRequestStatus m_nStatus{eSoundRequestStatus::UNK_0}; + int16 m_nBankId{-1}; + int16 m_nBankSlotId{-1}; + int16 m_nNumSounds{-1}; + uint8 m_nPakFileNumber{}; + +public: + CAESoundRequest() = default; + + CAESoundRequest(int16 bankId, int16 bankSlot, int16 numSounds, CAEBankSlot& slot, + CAEBankLookupItem* lookup, eSoundRequestStatus status) + : m_nBankId(bankId), m_nBankSlotId(bankSlot), m_nNumSounds(numSounds), m_pBankSlotInfo(&slot) + , m_nPakFileNumber(lookup->m_nPakFileNumber), m_nBankOffset(lookup->m_nOffset), m_nBankSize(lookup->m_nSize) + , m_nStatus(status) + {} + + void Reset() { + m_nBankId = m_nBankSlotId = m_nNumSounds = -1; + m_pBankSlotInfo = nullptr; + m_nStatus = eSoundRequestStatus::UNK_0; + } + + bool IsSingleSound() const { + return m_nNumSounds == -1; + } +}; +VALIDATE_SIZE(CAESoundRequest, 0x20); + +class CAEBankLoader { +public: + CAEBankSlot* m_paBankSlots; + CAEBankLookupItem* m_paBankLookups; + tPakLookup* m_paPakLookups; + uint16 m_nBankSlotCount; + uint16 m_nBankLookupCount; + int16 m_nPakLookupCount; + uint16 __pad; + bool m_bInitialised; + uint32 m_nBufferSize; + uint8* m_pBuffer; + int32* m_paStreamHandles; + CAESoundRequest m_aRequests[50]; + uint16 field_664; + uint16 m_iRequestCount; + uint16 m_iNextRequest; + uint16 m_iStreamingChannel; + uint16 m_aBankSlotSound[60]; + +public: + static void InjectHooks(); + + CAEBankLoader() = default; + ~CAEBankLoader(); + + CAEBankLookupItem* GetBankLookup(uint16 bankId); + bool LoadBankLookupFile(); + bool LoadBankSlotFile(); + bool LoadSFXPakLookupFile(); + + void CalculateBankSlotsInfosOffsets(); + +private: + // NOTSA + void Deconstructor() { this->~CAEBankLoader(); } +}; +VALIDATE_SIZE(CAEBankLoader, 0x6E4); diff --git a/source/game_sa/Audio/loaders/AEDataStream.cpp b/source/game_sa/Audio/loaders/AEDataStream.cpp index f925771564..dbc701f3ce 100644 --- a/source/game_sa/Audio/loaders/AEDataStream.cpp +++ b/source/game_sa/Audio/loaders/AEDataStream.cpp @@ -76,11 +76,15 @@ HRESULT CAEDataStream::Write(const void* src, ULONG size, ULONG* written) { HRESULT CAEDataStream::Seek(LARGE_INTEGER offset, DWORD whence, ULARGE_INTEGER* newOffset) { // C-style cast is not a good idea here // but I can't figure out which cast is best to preserve - // the sign-bit of LARGE_INTEGER - if (m_bIsOpen == false) + // the sign-bit of LARGE_INTEGER. + // + // TODO: Implement 64-bit variants? + + if (!m_bIsOpen) return E_INVALIDARG; - unsigned long pos = Seek((long)offset.QuadPart, static_cast(whence)); + assert(std::in_range(offset.QuadPart)); + unsigned long pos = Seek(static_cast(offset.QuadPart), static_cast(whence)); if (newOffset) newOffset->QuadPart = pos; @@ -119,7 +123,7 @@ HRESULT CAEDataStream::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, // 0x4dc3a0 HRESULT CAEDataStream::Stat(STATSTG* statout, DWORD flags) { - if (m_bIsOpen == false || statout == nullptr || flags != STATFLAG_NONAME) + if (!m_bIsOpen || statout == nullptr || flags != STATFLAG_NONAME) return E_INVALIDARG; auto fileSize = CFileMgr::GetTotalSize(m_pFileHandle); @@ -136,7 +140,7 @@ HRESULT CAEDataStream::Clone(IStream** target) { // 0x4dc1c0 size_t CAEDataStream::FillBuffer(void* dest, size_t size) { - if (m_bIsOpen == false) + if (!m_bIsOpen) return 0; size_t sizeToRead = static_cast((m_nCurrentPosition - m_nLength) - m_nStartPosition); @@ -160,7 +164,7 @@ uint32 CAEDataStream::GetCurrentPosition() { // 0x4dc250 uint32 CAEDataStream::Seek(long offset, int32 whence) { - if (m_bIsOpen == false) + if (!m_bIsOpen) return uint32(-1); if (whence == SEEK_SET) { @@ -190,7 +194,7 @@ bool CAEDataStream::Initialise() { if (m_bIsOpen) return true; - // MikuAuahDark: Rewrite it to use CFileMgr + // NOTSA(MikuAuahDark): Rewrite it to use CFileMgr CFileMgr::SetDir(""); m_pFileHandle = CFileMgr::OpenFile(m_pszFilename, "rb"); @@ -209,15 +213,20 @@ void CAEDataStream::InjectHooks() { RH_ScopedClass(CAEDataStream); RH_ScopedCategory("Audio/Loaders"); + // NOTSA: This class used Win32 file functions to deal with files, we converted + // them to C-compatible ones. So all of them have to be locked. + RH_ScopedInstall(Constructor, 0x4dc620); RH_ScopedInstall(Destructor, 0x4dc490); - RH_ScopedInstall(Initialise, 0x4dc2b0); - RH_ScopedInstall(FillBuffer, 0x4dc1c0); - RH_ScopedInstall(GetCurrentPosition, 0x4dc230); - RH_ScopedOverloadedInstall(Seek, "uint32", 0x4dc250, uint32(CAEDataStream::*)(long offset, int32 whence)); - RH_ScopedOverloadedInstall(Seek, "", 0x4dc340, HRESULT(__stdcall CAEDataStream::*)(LARGE_INTEGER, DWORD, ULARGE_INTEGER*)); + RH_ScopedInstall(Initialise, 0x4dc2b0, {.locked = true}); + RH_ScopedInstall(FillBuffer, 0x4dc1c0, {.locked = true}); + RH_ScopedInstall(GetCurrentPosition, 0x4dc230, {.locked = true}); + RH_ScopedOverloadedInstall(Seek, "OG", 0x4dc250, uint32(CAEDataStream::*)(long offset, int32 whence), {.locked = true}); + + // IStream implementation. These depend on functions above so most of them don't have to be locked. RH_ScopedInstall(Read, 0x4dc320); - RH_ScopedInstall(Stat, 0x4dc3a0); + RH_ScopedOverloadedInstall(Seek, "istream", 0x4dc340, HRESULT(__stdcall CAEDataStream::*)(LARGE_INTEGER, DWORD, ULARGE_INTEGER*)); + RH_ScopedInstall(Stat, 0x4dc3a0, {.locked = false}); // Uses CFileMgr::GetTotalSize RH_ScopedInstall(QueryInterface, 0x4dc410); RH_ScopedInstall(AddRef, 0x4dc460); RH_ScopedInstall(Write, 0x4dc4d0); diff --git a/source/game_sa/Audio/loaders/AEMP3BankLoader.cpp b/source/game_sa/Audio/loaders/AEMP3BankLoader.cpp new file mode 100644 index 0000000000..16b9f512d3 --- /dev/null +++ b/source/game_sa/Audio/loaders/AEMP3BankLoader.cpp @@ -0,0 +1,341 @@ +#include "StdInc.h" +#include "AEMP3BankLoader.h" +#include "AEAudioUtility.h" + +// Naming scheme: +// * BankLookup == BankId +// + +void CAEMP3BankLoader::InjectHooks() { + RH_ScopedClass(CAEMP3BankLoader); + RH_ScopedCategory("Audio/Loaders"); + + RH_ScopedInstall(Constructor, 0x4DFB10); + RH_ScopedInstall(Initialise, 0x4E08F0); + RH_ScopedInstall(GetBankSlot, 0x4DFDE0); + RH_ScopedInstall(GetSoundHeadroom, 0x4E01E0); + RH_ScopedInstall(IsSoundBankLoaded, 0x4E0220); + RH_ScopedInstall(GetSoundBankLoadingStatus, 0x4E0250); + RH_ScopedInstall(GetSoundBuffer, 0x4E0280); + RH_ScopedInstall(IsSoundLoaded, 0x4E03B0); + RH_ScopedInstall(GetSoundLoadingStatus, 0x4E0400); + RH_ScopedInstall(UpdateVirtualChannels, 0x4E0450); + RH_ScopedInstall(LoadSoundBank, 0x4E0670); + RH_ScopedInstall(LoadSound, 0x4E07A0); + RH_ScopedInstall(Service, 0x4DFE30,{.reversed=false}); // TODO: broken +} + +// 0x4DFB10 +CAEMP3BankLoader::CAEMP3BankLoader() { + m_bInitialised = false; + m_iStreamingChannel = 4; +} + +// 0x4E08F0 +bool CAEMP3BankLoader::Initialise() { + rng::for_each(m_aRequests, &CAESoundRequest::Reset); + field_664 = 0; + m_iRequestCount = 0; + m_iNextRequest = 0; + + if (!LoadBankSlotFile() || !LoadBankLookupFile() || !LoadSFXPakLookupFile()) + return false; + + m_bInitialised = true; + return true; +} + +// 0x4DFDE0, inlined +uint8* CAEMP3BankLoader::GetBankSlot(uint16 bankSlot, uint32& outLength) { + if (!m_bInitialised || bankSlot >= m_nBankSlotCount) + return nullptr; + + outLength = m_paBankSlots[bankSlot].m_nSize; + return &m_pBuffer[m_paBankSlots[bankSlot].m_nOffset]; +} + +// 0x4E01E0 +float CAEMP3BankLoader::GetSoundHeadroom(uint16 soundId, int16 bankSlot) { + return m_bInitialised + ? (float)m_paBankSlots[bankSlot].m_aSlotItems[soundId].m_usSoundHeadroom / 100.0f + : 0.f; +} + +// 0x4E0220 +bool CAEMP3BankLoader::IsSoundBankLoaded(uint16 bankId, int16 bankSlot) { + assert(bankSlot < m_nBankSlotCount); + return m_bInitialised && m_paBankSlots[bankSlot].m_nBankId == bankId; +} + +// 0x4E0250 +bool CAEMP3BankLoader::GetSoundBankLoadingStatus(uint16 bankId, int16 bankSlot) { + return IsSoundBankLoaded(bankId, bankSlot); +} + +// 0x4E0280 +uint8* CAEMP3BankLoader::GetSoundBuffer(uint16 soundId, int16 bankSlot, uint32& outSize, uint16& outSampleRate) { + if (!m_bInitialised || m_paBankSlots[bankSlot].m_nBankId == (uint16)-1) + return nullptr; + + auto& bank = m_paBankSlots[bankSlot]; + + if (!bank.IsSingleSound() && soundId >= bank.m_nSoundCount) + return nullptr; + + if (bank.IsSingleSound() && m_aBankSlotSound[bankSlot] != soundId) + return nullptr; + + uint32 bankBufferSize{}; + auto* bankInBuffer = GetBankSlot(bankSlot, bankBufferSize); + + if (soundId < NUM_BANK_SLOT_ITEMS && bank.IsSingleSound() || soundId < bank.m_nSoundCount - 1) { + assert(soundId < NUM_BANK_SLOT_ITEMS); + outSize = bank.m_aSlotItems[(soundId + 1) % NUM_BANK_SLOT_ITEMS].m_nOffset - bank.m_aSlotItems[soundId].m_nOffset; + } else { + assert(soundId < NUM_BANK_SLOT_ITEMS); + outSize = m_paBankLookups[bank.m_nBankId].m_nSize - bank.m_aSlotItems[soundId].m_nOffset; + } + assert(soundId < NUM_BANK_SLOT_ITEMS); + outSampleRate = bank.m_aSlotItems[soundId].m_usSampleRate; + + return &bankInBuffer[bank.m_aSlotItems[soundId].m_nOffset]; +} + +// 0x4E0380 +int32 CAEMP3BankLoader::GetLoopOffset(uint16 soundId, int16 bankSlot) { + if (!m_bInitialised) + return -1; + + assert(bankSlot < m_nBankSlotCount); + auto& bank = m_paBankSlots[bankSlot]; + + assert(bank.IsSingleSound() && m_aBankSlotSound[bankSlot] == soundId || soundId < bank.m_nSoundCount); + return bank.m_aSlotItems[soundId].m_nLoopOffset; +} + +// 0x4E03B0 +bool CAEMP3BankLoader::IsSoundLoaded(uint16 bankId, uint16 soundId, int16 bankSlot) { + return m_bInitialised && m_paBankSlots[bankSlot].m_nBankId == bankId && m_aBankSlotSound[bankSlot] == soundId; +} + +// 0x4E0400 +bool CAEMP3BankLoader::GetSoundLoadingStatus(uint16 bankId, uint16 soundId, int16 bankSlot) { + return IsSoundLoaded(bankId, soundId, bankSlot); +} + +// 0x4E0450 +void CAEMP3BankLoader::UpdateVirtualChannels(tVirtualChannelSettings* settings, int16* lengths, int16* loopStartTimes) { + for (auto i = 0u; i < std::size(settings->BankSlotIDs); i++) { + const auto slotId = settings->BankSlotIDs[i]; + const auto soundId = settings->SoundIDs[i]; + const auto& bankSlot = m_paBankSlots[slotId]; + + if (slotId < 0 || soundId < 0 || bankSlot.m_nBankId == -1 || (soundId >= bankSlot.m_nSoundCount && bankSlot.m_nSoundCount >= 0)) { + // perhaps invalid? + lengths[i] = -1; + loopStartTimes[i] = -1; + } else { + lengths[i] = CAEAudioUtility::ConvertFromBytesToMS( + bankSlot.CalculateSizeOfSlotItem(soundId), + bankSlot.m_aSlotItems[soundId].m_usSoundHeadroom, + 1u + ); + + loopStartTimes[i] = [&]() -> int32 { + if (const auto loopOffset = bankSlot.m_aSlotItems[soundId].m_nLoopOffset; loopOffset != -1) { + return CAEAudioUtility::ConvertFromBytesToMS( + 2 * loopOffset, // ?? + bankSlot.m_aSlotItems[soundId].m_usSampleRate, + 1u + ); + } else { + return -1; + } + }(); + } + } +} + +// 0x4E0670 +void CAEMP3BankLoader::LoadSoundBank(uint16 bankId, int16 bankSlot) { + if (!m_bInitialised) + return; + + if (bankId > m_nBankLookupCount || bankSlot < 0 || bankSlot > m_nBankSlotCount) + return; + + if (IsSoundBankLoaded(bankId, bankSlot)) + return; + + for (auto& req : m_aRequests) { + if (req.m_nBankId == bankId && req.m_nBankSlotId == bankSlot) + return; + } + + m_aRequests[m_iNextRequest] = CAESoundRequest( + bankId, + bankSlot, + -1, + m_paBankSlots[bankSlot], + GetBankLookup(bankId), + eSoundRequestStatus::JUST_LOADED + ); + + m_iRequestCount++; + m_iNextRequest = (m_iNextRequest + 1) % std::size(m_aRequests); +} + +// 0x4E07A0 +void CAEMP3BankLoader::LoadSound(uint16 bankId, uint16 soundId, int16 bankSlot) { + if (!m_bInitialised) + return; + + if (soundId >= NUM_BANK_SLOT_ITEMS || bankId > m_nBankLookupCount || bankSlot < 0 || bankSlot > m_nBankSlotCount) + return; + + if (IsSoundLoaded(bankId, soundId, bankSlot)) + return; + + for (auto& req : m_aRequests) { + if (req.m_nBankId == bankId && req.m_nBankSlotId == bankSlot && req.m_nNumSounds == soundId) + return; + } + + const auto bankLookup = GetBankLookup(bankId); + auto& nextRequest = m_aRequests[m_iNextRequest]; + + nextRequest.m_nBankId = bankId; + nextRequest.m_nBankSlotId = bankSlot; + nextRequest.m_nNumSounds = soundId; + nextRequest.m_pBankSlotInfo = &m_paBankSlots[bankSlot]; + nextRequest.m_nPakFileNumber = bankLookup->m_nPakFileNumber; + nextRequest.m_nBankOffset = bankLookup->m_nOffset; + nextRequest.m_nBankSize = bankLookup->m_nSize; + nextRequest.m_nStatus = eSoundRequestStatus::JUST_LOADED; + + m_iRequestCount++; + m_iNextRequest = (m_iNextRequest + 1) % 50; +} + +// 0x4DFE30, broken +void CAEMP3BankLoader::Service() { + // TODO: Refactor the shit out of this + for (auto&& [i, req] : notsa::enumerate(m_aRequests)) { + switch (req.m_nStatus) { + case eSoundRequestStatus::JUST_LOADED: { + if (CdStreamGetStatus(m_iStreamingChannel) != eCdStreamStatus::READING_SUCCESS) + continue; + + const auto numSectors = req.IsSingleSound() ? (req.m_nBankSize + sizeof(CdAudioStream)) / STREAMING_SECTOR_SIZE + 2 : 4; + const auto bankOffsetInSectors = req.m_nBankOffset / STREAMING_SECTOR_SIZE; + + req.m_pStreamBuffer = (CdAudioStream*)CMemoryMgr::Malloc(numSectors * STREAMING_SECTOR_SIZE); + req.m_pStreamOffset = req.m_pStreamBuffer; + //&req.m_pStreamBuffer[req.m_nBankOffset - bankOffsetInSectors * STREAMING_SECTOR_SIZE]; // == &buffer[0]? + CdStreamRead( + m_iStreamingChannel, + req.m_pStreamBuffer, + bankOffsetInSectors & 0xFFFFFF | m_paStreamHandles[req.m_nPakFileNumber], // TODO: Encoding helper. + numSectors + ); + + req.m_nStatus = eSoundRequestStatus::ALREADY_LOADED; + break; + } + case eSoundRequestStatus::ALREADY_LOADED: { + if (CdStreamGetStatus(m_iStreamingChannel) != eCdStreamStatus::READING_SUCCESS) + continue; + + if (req.IsSingleSound()) { + memcpy( + &m_pBuffer[req.m_pBankSlotInfo->m_nOffset], + &req.m_pStreamOffset->m_aBankData, + req.m_nBankSize + ); + memcpy( + &m_paBankSlots[req.m_nBankSlotId].m_aSlotItems, + &req.m_pStreamOffset->m_aSlotItems, + sizeof(m_paBankSlots[req.m_nBankSlotId].m_aSlotItems) + ); + m_paBankSlots[req.m_nBankSlotId].m_nBankId = req.m_nBankId; + m_paBankSlots[req.m_nBankSlotId].m_nSoundCount = req.m_pStreamOffset->m_nSoundCount; + m_aBankSlotSound[req.m_nBankSlotId] = -1; + + CMemoryMgr::Free(req.m_pStreamBuffer); + req.m_pStreamBuffer = nullptr; + req.m_nBankId = req.m_nBankSlotId = req.m_nNumSounds = -1; + req.m_nStatus = eSoundRequestStatus::UNK_0; + + m_iRequestCount--; + } else { + memcpy( + m_paBankSlots[req.m_nBankSlotId].m_aSlotItems, + &req.m_pStreamOffset->m_aSlotItems, + sizeof(m_paBankSlots[req.m_nBankSlotId].m_aSlotItems) + ); + m_paBankSlots[req.m_nBankSlotId].m_nBankId = -1; + m_paBankSlots[req.m_nBankSlotId].m_nSoundCount = -1; // ? + m_aBankSlotSound[req.m_nBankSlotId] = -1; + + const auto nextItemOffset = [&] { + if (req.m_nNumSounds >= req.m_pStreamOffset->m_nSoundCount - 1) { + // No next item? + return m_paBankLookups[req.m_nBankId].m_nSize; + } else { + return req.m_pStreamOffset->m_aSlotItems[req.m_nNumSounds + 1].m_nOffset; + } + }(); + req.m_nBankSize = nextItemOffset - req.m_pStreamOffset->m_aSlotItems[req.m_nNumSounds].m_nOffset; + req.m_nBankOffset += req.m_pStreamOffset->m_aSlotItems[req.m_nNumSounds].m_nOffset + sizeof(CdAudioStream); + + const auto bankOffsetInSectors = req.m_nBankOffset / STREAMING_SECTOR_SIZE; + + CMemoryMgr::Free(req.m_pStreamBuffer); + + req.m_pStreamBuffer = (CdAudioStream*)CMemoryMgr::Malloc((bankOffsetInSectors + 2) * STREAMING_SECTOR_SIZE); + req.m_pStreamOffset = req.m_pStreamBuffer; //&req.m_pStreamBuffer[req.m_nBankOffset - bankOffsetInSectors * STREAMING_SECTOR_SIZE]; // == &buffer[0]? + + CdStreamRead( + m_iStreamingChannel, + req.m_pStreamBuffer, + bankOffsetInSectors & 0xFFFFFF | m_paStreamHandles[req.m_nPakFileNumber], + bankOffsetInSectors + 2 + ); + + req.m_nStatus = eSoundRequestStatus::UNK_3; + } + break; + } + case eSoundRequestStatus::UNK_3: { + if (CdStreamGetStatus(m_iStreamingChannel) != eCdStreamStatus::READING_SUCCESS || req.IsSingleSound()) + continue; + + memcpy( + &m_pBuffer[req.m_pBankSlotInfo[0].m_nOffset], + req.m_pStreamOffset, + req.m_nBankSize + ); + + auto& slot = m_paBankSlots[req.m_nBankSlotId]; + slot.m_nBankId = req.m_nBankId; + slot.m_aSlotItems[req.m_nNumSounds].m_nOffset = 0; + slot.m_aSlotItems[(req.m_nNumSounds + 1) % std::size(slot.m_aSlotItems)].m_nOffset = req.m_nBankSize; + m_aBankSlotSound[req.m_nBankSlotId] = req.m_nNumSounds; + + CMemoryMgr::Free(req.m_pStreamBuffer); + req.m_pStreamBuffer = nullptr; + req.m_nBankId = req.m_nBankSlotId = req.m_nNumSounds = -1; + req.m_nStatus = eSoundRequestStatus::UNK_0; + + m_iRequestCount--; + + if (i == field_664) { + field_664 = (field_664 + 1) % std::size(m_aRequests); + } + break; + } + default: + break; + } + } +} diff --git a/source/game_sa/Audio/loaders/AEMP3BankLoader.h b/source/game_sa/Audio/loaders/AEMP3BankLoader.h new file mode 100644 index 0000000000..1f2579a58c --- /dev/null +++ b/source/game_sa/Audio/loaders/AEMP3BankLoader.h @@ -0,0 +1,53 @@ +#pragma once +#include "AEBankLoader.h" + +struct tVirtualChannelSettings { + int16 BankSlotIDs[300]{}; + int16 SoundIDs[300]{}; + + tVirtualChannelSettings() { + rng::fill(BankSlotIDs, -1); + rng::fill(SoundIDs, -1); + } +}; +VALIDATE_SIZE(tVirtualChannelSettings, 0x4B0); + +class CAEMP3BankLoader : public CAEBankLoader { +public: + static void InjectHooks(); + + CAEMP3BankLoader(); + + bool Initialise(); + uint8* GetBankSlot(uint16 bankSlot, uint32& outLength); + float GetSoundHeadroom(uint16 soundId, int16 bankSlot); + bool IsSoundBankLoaded(uint16 bankId, int16 bankSlot); + bool GetSoundBankLoadingStatus(uint16 bankId, int16 bankSlot); + uint8* GetSoundBuffer(uint16 soundId, int16 bankSlot, uint32& outIndex, uint16& outSampleRate); + int32 GetLoopOffset(uint16 soundId, int16 bankSlot); + bool IsSoundLoaded(uint16 bankId, uint16 soundId, int16 bankSlot); + bool GetSoundLoadingStatus(uint16 bankId, uint16 soundId, int16 bankSlot); + void UpdateVirtualChannels(tVirtualChannelSettings* settings, int16* lengths, int16* loopStartTimes); + void LoadSoundBank(uint16 bankId, int16 bankSlot); + void LoadSound(uint16 bankId, uint16 soundId, int16 bankSlot); + void Service(); + + // NOTSA + bool DoesRequestExist(uint16 bankId, int16 bankSlot, std::optional numSounds = {}) { + for (auto& request : m_aRequests) { + if (request.m_nBankId == bankId && request.m_nBankSlotId == bankSlot) { + if (!numSounds.has_value() || request.m_nNumSounds == *numSounds) + return true; + } + } + return false; + } + +private: + // NOTSA + CAEMP3BankLoader* Constructor() { + this->CAEMP3BankLoader::CAEMP3BankLoader(); + return this; + } +}; +VALIDATE_SIZE(CAEMP3BankLoader, 0x6E4); diff --git a/source/game_sa/Audio/loaders/AEMP3TrackLoader.cpp b/source/game_sa/Audio/loaders/AEMP3TrackLoader.cpp new file mode 100644 index 0000000000..8f37cd46e9 --- /dev/null +++ b/source/game_sa/Audio/loaders/AEMP3TrackLoader.cpp @@ -0,0 +1,178 @@ +#include "StdInc.h" +#include "AEMP3TrackLoader.h" +#include "app.h" +#include "FileMgr.h" +#include "MemoryMgr.h" + +void CAEMP3TrackLoader::InjectHooks() { + RH_ScopedClass(CAEMP3TrackLoader); + RH_ScopedCategory("Audio/Loaders"); + + RH_ScopedInstall(Constructor, 0x4E0930); + RH_ScopedInstall(Deconstructor, 0x4E0940); + RH_ScopedInstall(Initialise, 0x4E0C50); + RH_ScopedInstall(LoadStreamPackTable, 0x4E0970); + RH_ScopedInstall(LoadTrackLookupTable, 0x4E09F0); + RH_ScopedInstall(GetTrackInfo, 0x4E0A70); + RH_ScopedInstall(IsCurrentAudioStreamAvailable, 0x4E0BD0); +} + +// 0x4E0930 +CAEMP3TrackLoader::CAEMP3TrackLoader() { + m_bInitialised = false; + m_pszDvdDrivePath = nullptr; +} + +// 0x4E0940 +CAEMP3TrackLoader::~CAEMP3TrackLoader() { + if (m_bInitialised) { + CMemoryMgr::Free(m_paTrackLookups); + CMemoryMgr::Free(m_paStreamPacks); + } + + if (m_pszDvdDrivePath) { + delete m_pszDvdDrivePath; + } +} + +// 0x4E0C50 +bool CAEMP3TrackLoader::Initialise() { + if (!LoadStreamPackTable() || !LoadTrackLookupTable()) + return false; + + m_bStreamingFromDVD = false; + + // NOTSA: Originally used a heap allocated string and strcpy/strcat to make paths. + + // Firstly try loading locally. + const auto localPath = std::format("AUDIO\\STREAMS\\{}", m_paStreamPacks[0].m_szName); + if (auto file = CFileMgr::OpenFile(localPath.c_str(), "r")) { + m_bInitialised = true; + m_bStreamingFromDVD = false; + + CFileMgr::CloseFile(file); + return true; + } + + // Try loading from a DVD. + return IsCurrentAudioStreamAvailable(); +} + +// 0x4E0970 +bool CAEMP3TrackLoader::LoadStreamPackTable(void) { + // NOTSA: Originally Win32 file API was used. + auto* fp = fopen("AUDIO\\CONFIG\\STRMPAKS.DAT", "r"); + if (!fp) { + // Win32 API creates a file if it doesn't exists, and reads 0 bytes. + fp = fopen("AUDIO\\CONFIG\\STRMPAKS.DAT", "w"); + fclose(fp); + + // NOTSA: Originally (StreamPack*)CMemoryMgr::Malloc(0), return value is implementation-dependent. + m_paStreamPacks = nullptr; + m_nStreamPackCount = 0; + return false; + } + + // Get file size. + fseek(fp, 0, SEEK_END); + const auto size = ftell(fp); + const auto numPacks = size / sizeof(StreamPack); + rewind(fp); + + m_paStreamPacks = (StreamPack*)CMemoryMgr::Malloc(size); + m_nStreamPackCount = numPacks; + + // NOTE: Win32 API outputs number of bytes read, most likely checking is unnecessary. + fread(m_paStreamPacks, sizeof(StreamPack), numPacks, fp); + + fclose(fp); + return true; +} + +// 0x4E09F0 +bool CAEMP3TrackLoader::LoadTrackLookupTable(void) { + // NOTSA: Originally Win32 file API was used. + auto* fp = fopen("AUDIO\\CONFIG\\TRAKLKUP.DAT", "r"); + if (!fp) { + // Win32 API creates a file if it doesn't exists, and reads 0 bytes. + fp = fopen("AUDIO\\CONFIG\\TRAKLKUP.DAT", "w"); + fclose(fp); + + // NOTSA: Originally (tTrackLookup*)CMemoryMgr::Malloc(0), return value is implementation-dependent. + m_paTrackLookups = nullptr; + m_nTrackCount = 0; + return false; + } + + // Get file size. + fseek(fp, 0, SEEK_END); + const auto size = ftell(fp); + const auto numPacks = size / sizeof(tTrackLookup); + rewind(fp); + + m_paTrackLookups = (tTrackLookup*)CMemoryMgr::Malloc(size); + m_nTrackCount = numPacks; + + // NOTE: Win32 API outputs number of bytes read, most likely checking is unnecessary. + fread(m_paTrackLookups, sizeof(tTrackLookup), numPacks, fp); + + fclose(fp); + return true; +} + +// 0x4E0A70 +tTrackInfo* CAEMP3TrackLoader::GetTrackInfo(uint32 trackId) { + if (trackId >= m_nTrackCount) + return nullptr; + + const auto packId = m_paTrackLookups[trackId].m_nbPackId; + if (packId > m_nStreamPackCount) + return nullptr; + + const auto length = [&] { + if (m_bStreamingFromDVD) { + return strlen(m_pszDvdDrivePath) + strlen(m_paStreamPacks[packId].m_szName) + 21; + } + return strlen(m_paStreamPacks[packId].m_szName) + 21; + }(); + + char* path = new char[length]; // owned by to be ctor'd CAEDataStream. + strcpy_s(path, length, + std::format("{}AUDIO\\STREAMS\\{}", + !m_bStreamingFromDVD ? "" : std::string{m_pszDvdDrivePath}, + m_paStreamPacks[packId].m_szName + ).c_str() + ); + + auto* stream = new CAEDataStream( + trackId, + path, + m_paTrackLookups[trackId].m_nOffset, + m_paTrackLookups[trackId].m_nSize, + true + ); + auto* trackInfo = new tTrackInfo; + + stream->Initialise(); + stream->FillBuffer(trackInfo, sizeof(tTrackInfo)); + delete stream; + + return trackInfo; +} + +// 0x4E0BD0 +bool CAEMP3TrackLoader::IsCurrentAudioStreamAvailable() { + if (m_pszDvdDrivePath = getDvdGamePath()) { + const auto dvdPath = std::format("{}\\AUDIO\\STREAMS\\{}", getDvdGamePath(), m_paStreamPacks[0].m_szName); + if (auto file = CFileMgr::OpenFile(dvdPath.c_str(), "r")) { + m_bInitialised = true; + m_bStreamingFromDVD = true; + + CFileMgr::CloseFile(file); + return true; + } + } + + m_bStreamingFromDVD = false; + return false; +} diff --git a/source/game_sa/Audio/loaders/AEMP3TrackLoader.h b/source/game_sa/Audio/loaders/AEMP3TrackLoader.h new file mode 100644 index 0000000000..b264ad07cc --- /dev/null +++ b/source/game_sa/Audio/loaders/AEMP3TrackLoader.h @@ -0,0 +1,27 @@ +#pragma once +#include "AETrackLoader.h" + +class CAEMP3TrackLoader : public CAETrackLoader { +public: + static void InjectHooks(); + + CAEMP3TrackLoader(); + ~CAEMP3TrackLoader(); + + bool Initialise(); + bool LoadStreamPackTable(void); + bool LoadTrackLookupTable(void); + tTrackInfo* GetTrackInfo(uint32 trackId); + + // Checks for DVD stream. + bool IsCurrentAudioStreamAvailable(); + +private: + // NOTSA + CAEMP3TrackLoader* Constructor() { + this->CAEMP3TrackLoader::CAEMP3TrackLoader(); + return this; + } + + void Deconstructor() { this->~CAEMP3TrackLoader(); } +}; diff --git a/source/game_sa/Audio/loaders/AETrackLoader.cpp b/source/game_sa/Audio/loaders/AETrackLoader.cpp new file mode 100644 index 0000000000..f7b48e7277 --- /dev/null +++ b/source/game_sa/Audio/loaders/AETrackLoader.cpp @@ -0,0 +1,50 @@ +#include "StdInc.h" +#include "AETrackLoader.h" +#include "FileMgr.h" + +void CAETrackLoader::InjectHooks() { + RH_ScopedClass(CAETrackLoader); + RH_ScopedCategory("Audio/Loaders"); + + RH_ScopedInstall(GetDataStream, 0x4E0D20); +} + +CAEDataStream* CAETrackLoader::GetDataStream(uint32 trackId) { + if (trackId >= m_nTrackCount) + return nullptr; + + const auto packId = m_paTrackLookups[trackId].m_nbPackId; + if (packId > m_nStreamPackCount) + return nullptr; + + const auto length = [&] { + if (m_bStreamingFromDVD) { + return strlen(m_pszDvdDrivePath) + strlen(m_paStreamPacks[packId].m_szName) + 21; + } + return strlen(m_paStreamPacks[packId].m_szName) + 21; + }(); + + char* path = new char[length]; // owned by to be ctor'd CAEDataStream. + strcpy_s(path, length, + std::format("{}\\{}", + !m_bStreamingFromDVD ? "AUDIO\\STREAMS\\" : std::string{m_pszDvdDrivePath} + "AUDIO\\STREAMS\\", + m_paStreamPacks[packId].m_szName + ).c_str() + ); + + auto* stream = new CAEDataStream( + trackId, + path, + m_paTrackLookups[trackId].m_nOffset + sizeof(tTrackInfo), + m_paTrackLookups[trackId].m_nSize, + true + ); + + CFileMgr::SetDir(""); + if (!stream->Initialise()) { + delete stream; + stream = nullptr; + } + + return stream; +} diff --git a/source/game_sa/Audio/loaders/AETrackLoader.h b/source/game_sa/Audio/loaders/AETrackLoader.h new file mode 100644 index 0000000000..a7f77eecb6 --- /dev/null +++ b/source/game_sa/Audio/loaders/AETrackLoader.h @@ -0,0 +1,43 @@ +#pragma once +#include "AEDataStream.h" + +struct tTrackLookup { + uint8 m_nbPackId; + uint32 m_nOffset; + uint32 m_nSize; +}; +VALIDATE_SIZE(tTrackLookup, 0xC); + +struct StreamPack { + char m_szName[16]; +}; +VALIDATE_SIZE(StreamPack, 0x10); + +struct tTrackInfo { + struct tBeat { + uint32 m_nTime{}; + uint32 m_nKey{}; + } m_aBeats[1000]; + + uint32 field_1F40[16]; + uint16 field_1F80; +}; +VALIDATE_SIZE(tTrackInfo, 0x1F84); +VALIDATE_SIZE(tTrackInfo::tBeat, 0x8); + +class CAETrackLoader { +public: + bool m_bInitialised; + uint32 m_nTrackCount; + uint32 m_nStreamPackCount; + tTrackLookup* m_paTrackLookups; + StreamPack* m_paStreamPacks; + bool m_bStreamingFromDVD; + char* m_pszDvdDrivePath; + +public: + static void InjectHooks(); + + CAEDataStream* GetDataStream(uint32 trackId); +}; +VALIDATE_SIZE(CAETrackLoader, 0x1C); diff --git a/source/game_sa/Audio/managers/AEAmbienceTrackManager.cpp b/source/game_sa/Audio/managers/AEAmbienceTrackManager.cpp index 9260e7cd0a..13ec8e2705 100644 --- a/source/game_sa/Audio/managers/AEAmbienceTrackManager.cpp +++ b/source/game_sa/Audio/managers/AEAmbienceTrackManager.cpp @@ -1,5 +1,6 @@ #include "StdInc.h" +#include "AEAudioEnvironment.h" #include "AEAmbienceTrackManager.h" #include "AEAudioHardware.h" #include "AEAudioUtility.h" @@ -15,134 +16,395 @@ void CAEAmbienceTrackManager::InjectHooks() { RH_ScopedInstall(IsAmbienceRadioActive, 0x4D6D40); RH_ScopedInstall(PlaySpecialMissionAmbienceTrack, 0x4D6D50); RH_ScopedInstall(StopSpecialMissionAmbienceTrack, 0x4D6D60); - RH_ScopedInstall(UpdateAmbienceTrackAndVolume, 0x4D6E60, { .reversed = false }); + RH_ScopedInstall(UpdateAmbienceTrackAndVolume, 0x4D6E60, { .reversed = false }); // bad RH_ScopedInstall(Service, 0x4D76C0); } // 0x5B9660 -bool CAEAmbienceTrackManager::Initialise(int32 channelId) { - m_nChannel = channelId; - m_nTrackId = -1; - dword24 = -1; - m_nState = STATE_8; - m_nVolume = -100.0f; - m_fFreqFactor = 1.0f; - byte28 = 3; +bool CAEAmbienceTrackManager::Initialise(int32 hwClientHandle) { + m_HwClientHandle = hwClientHandle; + m_RequestedSettings = { + .TrackID = -1, + .PlayingTrackID = -1, + .TrackFlags = 3 + }; + m_AmbienceStatus = AmbienceStatus::STOPPED; + m_Volume = -100.0f; + m_FreqFactor = 1.0f; return true; } // 0x4D6CA0 void CAEAmbienceTrackManager::Reset() { - m_nSpecialMissionAmbienceTrack = -1; - m_bStop = false; - m_bStopPrev = false; - m_nTrackPlayTime = -1; - m_nTimeInMs = 0; - m_b3 = false; - m_IsAmbienceRadioActive = -1; + m_SpecialMissionAmbienceTrackID = -1; + m_OverrideRadio = false; + m_LastAmbienceOverrodeRadio = false; + m_PrevAmbiencePlayTimeMs = -1; + m_PrevAmbienceStopTimeMs = 0; + m_StartAmbienceAtBeginning = false; + m_AmbienceRadioStation = RADIO_INVALID; } // 0x4D6CF0 bool CAEAmbienceTrackManager::IsAmbienceTrackActive() const { - return m_nState != STATE_8; + return m_AmbienceStatus != AmbienceStatus::STOPPED; } // 0x4D6D40 bool CAEAmbienceTrackManager::IsAmbienceRadioActive() const { - return m_IsAmbienceRadioActive >= 0; + return m_AmbienceRadioStation != RADIO_INVALID; } // 0x4D6D50 void CAEAmbienceTrackManager::PlaySpecialMissionAmbienceTrack(eAudioEvents event) { - m_nSpecialMissionAmbienceTrack = event; + m_SpecialMissionAmbienceTrackID = event; } -// 0x4D6D60 -void CAEAmbienceTrackManager::StopSpecialMissionAmbienceTrack() { - switch (m_nState) { - case STATE_0: - case PLAY_TRACK: - case START_TRACK_PLAYBACK: - case SET_CHANNEL_VOLUME: - m_nState = STOP_AMBIENCE_TRACK; - break; +// 0x4D6E60 +void CAEAmbienceTrackManager::UpdateAmbienceTrackAndVolume() { + const auto prevAmbienceRadioStation = m_AmbienceRadioStation; + auto specialTrackId = -1; + auto volume = -6.f; + auto isSandstorm = false; + + m_OverrideRadio = false; + m_StartAmbienceAtBeginning = 0; + m_AmbienceRadioStation = RADIO_INVALID; + + if (m_SpecialMissionAmbienceTrackID < 0) { // 0x4D6E97 + if (CWeather::Sandstorm > 0.f) { + if (CGame::CanSeeOutSideFromCurrArea() && !CCullZones::PlayerNoRain() && !CCullZones::CamNoRain()) { + specialTrackId = 167; + volume = std::log10f(2) * std::log2f(CWeather::Sandstorm); + } + isSandstorm = true; + } + } else { // 0x4D6E97 + specialTrackId = m_SpecialMissionAmbienceTrackID; + m_StartAmbienceAtBeginning = true; + m_OverrideRadio = true; } - Reset(); -} + if ((CWeather::Sandstorm <= 0.f && m_SpecialMissionAmbienceTrackID < 0) || (m_SpecialMissionAmbienceTrackID >= 0 && specialTrackId == -1)) { + auto activeAuZoIdx = -1; + CVector activeAuZoPos{}; -// 0x4D6E60 -void CAEAmbienceTrackManager::UpdateAmbienceTrackAndVolume() { - return plugin::CallMethod<0x4D6E60, CAEAmbienceTrackManager*>(this); + if (CAudioZones::m_NumActiveSpheres) { // 0x4D6F0D + const auto& sp = CAudioZones::m_aSpheres[CAudioZones::m_aActiveSpheres[0]]; + activeAuZoIdx = sp.m_nAudioZone; + activeAuZoPos = sp.m_Sphere.m_vecCenter; + } else if (CAudioZones::m_NumActiveBoxes) { + const auto& box = CAudioZones::m_aBoxes[CAudioZones::m_aActiveBoxes[0]]; + activeAuZoIdx = box.m_nAudioZone; + } + + const auto GetVolumeByAuZoDist = [&] { + CVector relPos; + CAEAudioEnvironment::GetPositionRelativeToCamera(&relPos, &activeAuZoPos); + return CAEAudioEnvironment::GetDistanceAttenuation(relPos.Magnitude() * 0.2f) - 6.f; + }; + + switch (activeAuZoIdx) { + case -1: + break; + case 4: { + specialTrackId = 143; + const auto camZ = TheCamera.GetPosition().z; + volume = camZ <= 1372.f // TODO: Magic number + ? -6.f - 9.f * (1372.f - camZ) + : -6.f; + break; + } + case 5: { + specialTrackId = 140; + volume = GetVolumeByAuZoDist(); + break; + } + case 8: { + specialTrackId = 165; + break; + } + case 10: { + specialTrackId = 139; + volume = GetVolumeByAuZoDist(); + break; + } + case 12: + specialTrackId = 168; + break; + case 13: + specialTrackId = 157; + m_OverrideRadio = true; + break; + case 15: + specialTrackId = 164; + break; + case 17: + specialTrackId = 146; + break; + case 19: + specialTrackId = 138; + break; + case 20: + specialTrackId = 136; + break; + case 21: + specialTrackId = 135; + break; + case 23: + specialTrackId = 148; + break; + case 24: + specialTrackId = 159; + break; + case 25: + specialTrackId = 158; + break; + case 26: + specialTrackId = 154; + break; + case 28: + case 29: + specialTrackId = 147; + break; + case 30: + m_AmbienceRadioStation = CStats::FindMostFavoriteRadioStation(); + break; + case 34: + specialTrackId = 162; + break; + case 36: + specialTrackId = 155; + break; + case 37: + specialTrackId = 144; + m_StartAmbienceAtBeginning = 1; + break; + case 39: + specialTrackId = 163; + break; + case 41: + specialTrackId = 169; + m_OverrideRadio = 1; + break; + case 44: + specialTrackId = 152; + break; + case 48: + specialTrackId = 137; + break; + case 50: + specialTrackId = 173; + break; + case 51: + specialTrackId = 156; + break; + case 52: + m_AmbienceRadioStation = RADIO_CLASSIC_HIP_HOP; + break; + case 53: + m_AmbienceRadioStation = RADIO_COUNTRY; + break; + case 54: + m_AmbienceRadioStation = RADIO_CLASSIC_ROCK; + break; + case 55: + m_AmbienceRadioStation = RADIO_DISCO_FUNK; + break; + case 56: + m_AmbienceRadioStation = RADIO_HOUSE_CLASSICS; + break; + case 57: + m_AmbienceRadioStation = RADIO_MODERN_HIP_HOP; + break; + case 58: + m_AmbienceRadioStation = RADIO_MODERN_ROCK; + break; + case 59: + m_AmbienceRadioStation = RADIO_NEW_JACK_SWING; + break; + case 60: + m_AmbienceRadioStation = RADIO_REGGAE; + break; + case 61: + m_AmbienceRadioStation = RADIO_RARE_GROOVE; + break; + case 62: + m_AmbienceRadioStation = RADIO_TALK; + break; + case 64: + specialTrackId = 151; + break; + case 66: + specialTrackId = 170; + break; + case 67: + specialTrackId = 171; + break; + default: + break; + } + } + const auto FadeOutAndStop = [this] { + if (m_Volume > -40.f) { + m_Volume -= 0.4f; + } else { + StopAmbienceTrack(); + } + }; + if (m_AmbienceRadioStation != RADIO_INVALID) { // 0x4D718C + if (AudioEngine.IsRadioOn()) { // Inverted + if (AudioEngine.GetCurrentRadioStationID() != m_AmbienceRadioStation) { + AudioEngine.StopRadio(nullptr, false); + } + } else { + AudioEngine.StartRadio(m_AmbienceRadioStation, 2); + } + } else if (prevAmbienceRadioStation != RADIO_INVALID) { //>0x4D71E5 - Radio was running previously, but isn't anymore, so stop it + if (AudioEngine.IsRadioOn()) { + AudioEngine.StopRadio(0, 0); + } + } else if (AudioEngine.IsRadioOn()) { // 0x4D720A + if (m_OverrideRadio) { + AudioEngine.StopRadio(nullptr, false); + AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_RETUNE_STOP); + } + m_LastAmbienceOverrodeRadio = m_OverrideRadio; + } else if (specialTrackId != -1) { + volume = CAEAudioEnvironment::GetDistanceAttenuation(std::sqrt(sq(TheCamera.m_fDistanceToWater) + sq(TheCamera.GetPosition().z - TheCamera.m_fHeightOfNearestWater)) / SQRT_3) - 12.f; + if (CWeather::UnderWaterness < 0.5f) { + if (volume <= -34.f) { // 0x4D74A7 + if (CGameLogic::LaRiotsActiveHere() || !CGame::CanSeeOutSideFromCurrArea()) { + if (m_AmbienceStatus != 8) { // 0x4D7529 + if (m_RequestedSettings.TrackID == 174) { + if (volume < -40.f) { + StopAmbienceTrack(); + } + m_Volume = -100.f; + m_FreqFactor = 1.f; + } else if (isSandstorm && m_Volume > -40.f) { // 0x4D756C + m_FreqFactor = 1.f; + m_Volume -= 0.4f; + } else { + StopAmbienceTrack(); + if (m_LastAmbienceOverrodeRadio || AudioEngine.IsVehicleRadioActive()) { + AudioEngine.StartRadio(AudioEngine.GetCurrentRadioStationID(), 0); + } + m_FreqFactor = 1.f; + } + } + } else if (m_AmbienceStatus == 8) { // 0x4D74A9 + m_AmbienceStatus = AmbienceStatus::STARTING; + m_RequestedSettings.TrackID = 166; + m_Volume = -34.f; + m_FreqFactor = 1.f; + } else if (m_RequestedSettings.TrackID == 166) { // 0x4D7500 + m_Volume = std::min(-12.f, m_Volume + 1.f); + } else { + FadeOutAndStop(); + } + return; + } + } else { + volume = 14.f; + } + + if (m_AmbienceStatus == 8) { // 0x4D7370 + m_RequestedSettings.TrackID = 174; + m_AmbienceStatus = AmbienceStatus::STARTING; + m_Volume = -34.f; + m_FreqFactor = CWeather::UnderWaterness < 0.5 + ? 1.f + : 0.0625f; + } else if (m_RequestedSettings.TrackID != 174) { // 0x4D73AE + FadeOutAndStop(); + } else { // 0x4D73E7 + m_Volume = stepto(m_Volume, volume, 1.f); + } + m_FreqFactor = stepto(m_FreqFactor, CWeather::UnderWaterness < 0.5f ? 1.f : 0.0625f, 0.25f); + } else if (specialTrackId != m_SpecialMissionAmbienceTrackID) { // 0x4D7250 + if (m_AmbienceStatus == 8) { + m_RequestedSettings.TrackID = specialTrackId; + m_AmbienceStatus = AmbienceStatus::STARTING; + m_Volume = -34.f; + m_FreqFactor = 1.f; + } else if (m_RequestedSettings.TrackID == specialTrackId) { + m_Volume = std::min(m_Volume + 1.f, m_Volume); + } else { + FadeOutAndStop(); + } + } else if (m_AmbienceStatus == 8) { // 0x4D725A + m_RequestedSettings.TrackID = specialTrackId; + m_AmbienceStatus = AmbienceStatus::STARTING; + m_Volume = -2.f; + m_FreqFactor = 1.f; + } else if (m_RequestedSettings.TrackID == specialTrackId) { // 0x4D7278 + StopAmbienceTrack(); + } } // 0x4D76C0 void CAEAmbienceTrackManager::Service(int32 trackPlayTime) { - if ( - !AudioEngine.IsCutsceneTrackActive() + if ( !AudioEngine.IsCutsceneTrackActive() && !CTimer::GetIsPaused() && !FrontEndMenuManager.m_bMainMenuSwitch ) { UpdateAmbienceTrackAndVolume(); } - switch (m_nState) { - case STATE_0: - int32 random; - - if (m_b3) { - random = 0; - } else { - if (m_nTrackPlayTime < 0) { - random = CAEAudioUtility::GetRandomNumberInRange(0, 600000); - } else { - random = m_nTrackPlayTime; - if (m_nTimeInMs) - random = CTimer::GetTimeInMS() - m_nTimeInMs + m_nTrackPlayTime; - - random = std::max(random, m_nTrackPlayTime + 7000); - } - } - - AEAudioHardware.PlayTrack(m_nTrackId, -1, random, byte28, false, false); - m_nState = PLAY_TRACK; + switch (m_AmbienceStatus) { + case AmbienceStatus::STARTING: // 0x4D770B + AEAudioHardware.PlayTrack( + m_RequestedSettings.TrackID, + -1, + m_StartAmbienceAtBeginning // Pirulax: I'm so sorry I couldn't resist writing this! + ? 0 + : m_PrevAmbiencePlayTimeMs < 0 + ? CAEAudioUtility::GetRandomNumberInRange(0, 600'000) + : m_PrevAmbiencePlayTimeMs + std::max( + m_PrevAmbienceStopTimeMs ? (int32)CTimer::GetTimeInMS() - m_PrevAmbienceStopTimeMs : 0, + 7000 + ), + m_RequestedSettings.TrackFlags, + false, + false + ); + m_AmbienceStatus = AmbienceStatus::WAITING_TO_PLAY; break; - case PLAY_TRACK: + case AmbienceStatus::WAITING_TO_PLAY: // 0x4D7779 if (trackPlayTime == -2) { - m_nState = START_TRACK_PLAYBACK; + m_AmbienceStatus = READY_TO_PLAY; } break; - case START_TRACK_PLAYBACK: + case AmbienceStatus::READY_TO_PLAY: // 0x4D778D StartTrackPlayback(); - m_nState = SET_CHANNEL_VOLUME; + m_AmbienceStatus = PLAYING; break; - case SET_CHANNEL_VOLUME: + case AmbienceStatus::PLAYING: // 0x4D77A0 CheckForPause(); - AEAudioHardware.SetChannelVolume(m_nChannel, 0, m_nVolume, 0); + AEAudioHardware.SetChannelVolume(m_HwClientHandle, 0, m_Volume, 0); break; - case STOP_AMBIENCE_TRACK: - if (m_nTrackId == m_nSpecialMissionAmbienceTrack) { - m_nTrackPlayTime = -1; - m_nTimeInMs = 0; + case AmbienceStatus::STOPPING: // 0x4D77C3 + if (m_RequestedSettings.TrackID == m_SpecialMissionAmbienceTrackID) { + m_PrevAmbiencePlayTimeMs = -1; + m_PrevAmbienceStopTimeMs = 0; } else { - m_nTrackPlayTime = trackPlayTime; - m_nTimeInMs = CTimer::GetTimeInMS(); + m_PrevAmbiencePlayTimeMs = trackPlayTime; + m_PrevAmbienceStopTimeMs = CTimer::GetTimeInMS(); } - m_nVolume = -100.0f; + m_Volume = -100.0f; + [[fallthrough]]; + case AmbienceStatus::STOPPING_CHANNELS_STOPPED: // Moved here to allow fallthrough AEAudioHardware.StopTrack(); - m_nState = STOP_TRACK; + m_AmbienceStatus = WAITING_TO_STOP; break; - case STATE_5: - AEAudioHardware.StopSound(m_nChannel, 0); - m_nState = STOP_SOUND; + case AmbienceStatus::STOPPING_SILENCED: + AEAudioHardware.StopSound(m_HwClientHandle, 0); + m_AmbienceStatus = STOPPING_CHANNELS_STOPPED; break; - case STOP_SOUND: - AEAudioHardware.StopTrack(); - m_nState = STOP_TRACK; - break; - case STOP_TRACK: + case AmbienceStatus::WAITING_TO_STOP: if (trackPlayTime == -6) { - m_nState = STATE_8; + m_AmbienceStatus = STOPPED; } else if (trackPlayTime == -7 || trackPlayTime == -2) { AEAudioHardware.StopTrack(); } @@ -152,33 +414,46 @@ void CAEAmbienceTrackManager::Service(int32 trackPlayTime) { } } +// 0x4D6D60 +void CAEAmbienceTrackManager::StopSpecialMissionAmbienceTrack() { + ChangeStatusToStopped(); + Reset(); +} + // 0x4D6CC0 void CAEAmbienceTrackManager::StopAmbienceTrack() { - switch (m_nState) { - case STATE_0: - case PLAY_TRACK: - case START_TRACK_PLAYBACK: - case SET_CHANNEL_VOLUME: - m_nState = STOP_AMBIENCE_TRACK; - break; - } - - m_bStop = false; - m_bStopPrev = false; - m_b3 = false; + ChangeStatusToStopped(); + m_OverrideRadio = false; + m_LastAmbienceOverrodeRadio = false; + m_StartAmbienceAtBeginning = false; } // 0x4D6DA0 void CAEAmbienceTrackManager::StartTrackPlayback() const { AEAudioHardware.DisableBassEq(); - AEAudioHardware.SetChannelVolume(m_nChannel, 0, m_nVolume, 0); - AEAudioHardware.SetChannelFlags(m_nChannel, 0, 33); + AEAudioHardware.SetChannelVolume(m_HwClientHandle, 0, m_Volume, 0); + AEAudioHardware.SetChannelFlags(m_HwClientHandle, 0, 33); AEAudioHardware.StartTrackPlayback(); } // 0x4D6DF0 void CAEAmbienceTrackManager::CheckForPause() const { - float freqFactor = CTimer::GetIsPaused() || FrontEndMenuManager.m_bMainMenuSwitch ? 0.0f : m_fFreqFactor; - AEAudioHardware.SetChannelFrequencyScalingFactor(m_nChannel, 0, freqFactor); - AEAudioHardware.SetChannelFrequencyScalingFactor(m_nChannel, 0, freqFactor); + float freqFactor = CTimer::GetIsPaused() || FrontEndMenuManager.m_bMainMenuSwitch ? 0.0f : m_FreqFactor; + AEAudioHardware.SetChannelFrequencyScalingFactor(m_HwClientHandle, 0, freqFactor); + AEAudioHardware.SetChannelFrequencyScalingFactor(m_HwClientHandle, 0, freqFactor); +} + +// NOTSA +void CAEAmbienceTrackManager::ChangeStatusToStopped() { + m_AmbienceStatus = [this] { + using enum AmbienceStatus; + switch (m_AmbienceStatus) { + case STARTING: + case WAITING_TO_PLAY: + case READY_TO_PLAY: + case PLAYING: + return STOPPING; + } + return m_AmbienceStatus; + }(); } diff --git a/source/game_sa/Audio/managers/AEAmbienceTrackManager.h b/source/game_sa/Audio/managers/AEAmbienceTrackManager.h index 4dc6735b8a..e43a67916c 100644 --- a/source/game_sa/Audio/managers/AEAmbienceTrackManager.h +++ b/source/game_sa/Audio/managers/AEAmbienceTrackManager.h @@ -1,33 +1,38 @@ #pragma once class CAEAmbienceTrackManager { + struct tAmbienceSettings { + int32 TrackID; + int32 PlayingTrackID; + uint8 TrackFlags; + }; public: - enum State : int32 { - STATE_0 = 0, - PLAY_TRACK = 1, - START_TRACK_PLAYBACK = 2, - SET_CHANNEL_VOLUME = 3, - STOP_AMBIENCE_TRACK = 4, - STATE_5 = 5, - STOP_SOUND = 6, - STOP_TRACK = 7, - STATE_8 = 8, + enum AmbienceStatus : int32 { + STARTING, + WAITING_TO_PLAY, + READY_TO_PLAY, + PLAYING, + STOPPING, + STOPPING_SILENCED, + STOPPING_CHANNELS_STOPPED, + WAITING_TO_STOP, + STOPPED, }; - bool m_bStop; - bool m_bStopPrev; - bool m_b3; - int8 m_IsAmbienceRadioActive; - int32 m_nChannel; - State m_nState; - int32 m_nSpecialMissionAmbienceTrack; - int32 m_nTrackPlayTime; - uint32 m_nTimeInMs; - float m_nVolume; - float m_fFreqFactor; - int32 m_nTrackId; - int32 dword24; - int8 byte28; + bool m_OverrideRadio; + bool m_LastAmbienceOverrodeRadio; + bool m_StartAmbienceAtBeginning; + eRadioID m_AmbienceRadioStation; + int32 m_HwClientHandle; // AKA channel + AmbienceStatus m_AmbienceStatus; + int32 m_SpecialMissionAmbienceTrackID; + int32 m_PrevAmbiencePlayTimeMs; + int32 m_PrevAmbienceStopTimeMs; + float m_Volume; + float m_FreqFactor; + tAmbienceSettings m_RequestedSettings; + + /*tAmbienceSettings m_ActiveSettings;*/ public: static void InjectHooks(); @@ -48,6 +53,9 @@ class CAEAmbienceTrackManager { void Service(int32 trackPlayTime); void StartTrackPlayback() const; +private: + void ChangeStatusToStopped(); + }; VALIDATE_SIZE(CAEAmbienceTrackManager, 0x2C); diff --git a/source/game_sa/Audio/managers/AERadioTrackManager.cpp b/source/game_sa/Audio/managers/AERadioTrackManager.cpp index e87993d217..c1ee01f643 100644 --- a/source/game_sa/Audio/managers/AERadioTrackManager.cpp +++ b/source/game_sa/Audio/managers/AERadioTrackManager.cpp @@ -10,11 +10,6 @@ CAERadioTrackManager& AERadioTrackManager = *(CAERadioTrackManager*)0x8CB6F8; -int32 (&CAERadioTrackManager::m_nDJBanterIndexHistory)[DJBANTER_INDEX_HISTORY_COUNT][RADIO_COUNT] = *(int32(*)[DJBANTER_INDEX_HISTORY_COUNT][RADIO_COUNT])0xB61D78; -int32 (&CAERadioTrackManager::m_nAdvertIndexHistory)[ADVERT_INDEX_HISTORY_COUNT][RADIO_COUNT] = *(int32(*)[ADVERT_INDEX_HISTORY_COUNT][RADIO_COUNT])0xB620C0; -int32 (&CAERadioTrackManager::m_nIdentIndexHistory)[IDENT_INDEX_HISTORY_COUNT][RADIO_COUNT] = *(int32(*)[IDENT_INDEX_HISTORY_COUNT][RADIO_COUNT])0xB62980; -int8 (&CAERadioTrackManager::m_nMusicTrackIndexHistory)[220] = *(int8(*)[220])0xB62B40; - uint8& CAERadioTrackManager::m_nStatsLastHitTimeOutHours = *(uint8*)0xB62C58; uint8& CAERadioTrackManager::m_nStatsLastHitGameClockHours = *(uint8*)0xB62C59; uint8& CAERadioTrackManager::m_nStatsLastHitGameClockDays = *(uint8*)0xB62C5A; @@ -50,21 +45,21 @@ void CAERadioTrackManager::InjectHooks() { RH_ScopedInstall(Load, 0x5D40E0, { .reversed = false }); RH_ScopedInstall(Save, 0x5D3EE0, { .reversed = false }); - RH_ScopedInstall(Initialise, 0x5B9390, { .reversed = false }); + RH_ScopedInstall(Initialise, 0x5B9390); RH_ScopedInstall(Service, 0x4EB9A0, { .reversed = false }); RH_ScopedInstall(DisplayRadioStationName, 0x4E9E50); RH_ScopedInstall(CheckForStationRetune, 0x4EB660, { .reversed = false }); RH_ScopedInstall(CheckForPause, 0x4EA590); RH_ScopedInstall(IsVehicleRadioActive, 0x4E9800, { .reversed = false }); - RH_ScopedInstall(AddDJBanterIndexToHistory, 0x4E97B0, { .reversed = false }); - RH_ScopedInstall(AddAdvertIndexToHistory, 0x4E9760, { .reversed = false }); - RH_ScopedInstall(AddIdentIndexToHistory, 0x4E9720, { .reversed = false }); - // RH_ScopedOverloadedInstall(StartRadio, "", 0x4EB3C0, void (CAERadioTrackManager::*)(int8, int8, float, uint8)); - // RH_ScopedOverloadedInstall(StartRadio, "", 0x4EB550, void (CAERadioTrackManager::*)(tVehicleAudioSettings*)); - RH_ScopedInstall(CheckForStationRetuneDuringPause, 0x4EB890, { .reversed = false }); + RH_ScopedInstall(AddDJBanterIndexToHistory, 0x4E97B0); + RH_ScopedInstall(AddAdvertIndexToHistory, 0x4E9760); + RH_ScopedInstall(AddIdentIndexToHistory, 0x4E9720); + RH_ScopedInstall(AddMusicTrackIndexToHistory, 0x4E96C0); + RH_ScopedOverloadedInstall(StartRadio, "manual", 0x4EB3C0, void (CAERadioTrackManager::*)(eRadioID, int8, float, uint8)); + RH_ScopedOverloadedInstall(StartRadio, "with-settings", 0x4EB550, void (CAERadioTrackManager::*)(tVehicleAudioSettings*)); + RH_ScopedInstall(CheckForStationRetuneDuringPause, 0x4EB890); RH_ScopedInstall(TrackRadioStation, 0x4EAC30, { .reversed = false }); RH_ScopedInstall(ChooseTracksForStation, 0x4EB180); - RH_ScopedInstall(AddMusicTrackIndexToHistory, 0x4E96C0, { .reversed = false }); RH_ScopedInstall(CheckForTrackConcatenation, 0x4EA930, { .reversed = false }); RH_ScopedInstall(QueueUpTracksForStation, 0x4EA670, { .reversed = false }); RH_ScopedInstall(ChooseDJBanterIndex, 0x4EA2D0, { .reversed = false }); @@ -76,73 +71,116 @@ void CAERadioTrackManager::InjectHooks() { RH_ScopedInstall(CheckForMissionStatsChanges, 0x4E8410); RH_ScopedInstall(StartTrackPlayback, 0x4EA640); RH_ScopedInstall(UpdateRadioVolumes, 0x4EA010, { .reversed = false }); - RH_ScopedInstall(PlayRadioAnnouncement, 0x4E8400, { .reversed = false }); + RH_ScopedInstall(PlayRadioAnnouncement, 0x4E8400); RH_ScopedInstall(GetCurrentRadioStationID, 0x4E83F0); RH_ScopedInstall(GetRadioStationListenTimes, 0x4E83E0); RH_ScopedInstall(GetRadioStationName, 0x4E9E10); RH_ScopedInstall(GetRadioStationNameKey, 0x4E8380); RH_ScopedInstall(HasRadioRetuneJustStarted, 0x4E8370); RH_ScopedInstall(StopRadio, 0x4E9820, { .reversed = false }); - RH_ScopedInstall(IsRadioOn, 0x4E8350, { .reversed = false }); + RH_ScopedInstall(IsRadioOn, 0x4E8350, { .reversed = true }); RH_ScopedInstall(InitialiseRadioStationID, 0x4E8330); RH_ScopedInstall(SetBassEnhanceOnOff, 0x4E9DB0); RH_ScopedInstall(SetBassSetting, 0x4E82F0); RH_ScopedInstall(SetRadioAutoRetuneOnOff, 0x4E82E0); - RH_ScopedInstall(RetuneRadio, 0x4E8290, { .reversed = false }); + RH_ScopedInstall(RetuneRadio, 0x4E8290, { .reversed = true }); RH_ScopedInstall(ResetStatistics, 0x4E8200); - RH_ScopedInstall(Reset, 0x4E7F80, { .reversed = false }); + RH_ScopedInstall(Reset, 0x4E7F80, { .reversed = true }); +} + +// Code from 0x5B9390 +CAERadioTrackManager::CAERadioTrackManager(int32 hwClientHandle) : + m_HwClientHandle{ hwClientHandle }, + m_nUserTrackPlayMode{ AEUserRadioTrackManager.GetUserTrackPlayMode() } +{ + // All constant value inits are done using member init lists + + // NOTSA: SA gets the list via CStats::GetFullFavoriteRadioStationList() but this way is much more clear. + rng::copy(CStats::FavoriteRadioStationList, m_aListenTimes); + + for (auto i = 0u; i < RADIO_COUNT; i++) { + m_nMusicTrackIndexHistory[i].Reset(); + m_nDJBanterIndexHistory[i].Reset(); + m_nAdvertIndexHistory[i].Reset(); + m_nIdentIndexHistory[i].Reset(); + } + + // [1st radio, off] + m_RequestedSettings = m_ActiveSettings = tRadioSettings{CAEAudioUtility::GetRandomRadioStation()}; } // 0x5B9390 bool CAERadioTrackManager::Initialise(int32 channelId) { - return plugin::CallMethodAndReturn(this, channelId); + *this = CAERadioTrackManager{}; + return true; } // 0x4E8330 -void CAERadioTrackManager::InitialiseRadioStationID(RadioStationId id) { - settings1.m_nCurrentRadioStation = id; - settings2.m_nCurrentRadioStation = id; +void CAERadioTrackManager::InitialiseRadioStationID(eRadioID id) { + m_RequestedSettings.m_nCurrentRadioStation = m_ActiveSettings.m_nCurrentRadioStation = id; } // 0x4E7F80 void CAERadioTrackManager::Reset() { - plugin::CallMethod<0x4E7F80, CAERadioTrackManager*>(this); + m_bInitialised = false; + m_bDisplayStationName = false; + rng::copy(CStats::FavoriteRadioStationList, m_aListenTimes); + + rng::for_each(m_nDJBanterIndexHistory, &DJBanterIndexHistory::Reset); + rng::for_each(m_nAdvertIndexHistory, &AdvertIndexHistory::Reset); + rng::for_each(m_nIdentIndexHistory, &IdentIndexHistory::Reset); + rng::for_each(m_nMusicTrackIndexHistory, &MusicTrackHistory::Reset); + rng::for_each(m_aRadioState, [](auto& s) { s.Reset(); }); + + m_RequestedSettings = m_ActiveSettings = tRadioSettings{CAEAudioUtility::GetRandomRadioStation()}; + m_nStationsListed = m_nStationsListDown = 0; + m_nTimeRadioStationRetuned = m_nTimeToDisplayRadioName = 0; + m_prev = field_60 = 0; + m_nRetuneStartedTime = 0; + m_bEnabledInPauseMode = false; + m_nSavedGameClockDays = m_nSavedGameClockHours = -1; + m_bRadioAutoSelect = m_bBassEnhance = true; + m_nSavedRadioStationId = m_iRadioStationMenuRequest = m_iRadioStationScriptRequest = RADIO_INVALID; + m_nSpecialDJBanterPending = 3; // todo: enum + m_nSpecialDJBanterIndex = -1; + m_bPauseMode = m_bRetuneJustStarted = false; + m_f80 = m_f84 = 0.0f; + ResetStatistics(); } // 0x4E8200 void CAERadioTrackManager::ResetStatistics() { - m_nStatsCitiesPassed = 0; - m_nStatsLastHitGameClockDays = -1; + m_nStatsCitiesPassed = 0; + m_nStatsLastHitGameClockDays = -1; m_nStatsLastHitGameClockHours = -1; - m_nStatsLastHitTimeOutHours = -1; - m_nStatsPassedCasino3 = false; - m_nStatsPassedCasino6 = false; - m_nStatsPassedCasino10 = false; - m_nStatsPassedCat1 = false; - m_nStatsPassedDesert1 = false; - m_nStatsPassedDesert3 = false; - m_nStatsPassedDesert5 = false; - m_nStatsPassedDesert8 = false; - m_nStatsPassedDesert10 = false; - m_nStatsPassedFarlie3 = false; - m_nStatsPassedLAFin2 = false; - m_nStatsPassedMansion2 = false; - m_nStatsPassedRyder2 = false; - m_nStatsPassedRiot1 = false; - m_nStatsPassedSCrash1 = false; - m_nStatsPassedStrap4 = false; - m_nStatsPassedSweet2 = false; - m_nStatsPassedTruth2 = false; - m_nStatsPassedVCrash2 = false; - m_nStatsStartedBadlands = false; - m_nStatsStartedCat2 = false; - m_nStatsStartedCrash1 = false; + m_nStatsLastHitTimeOutHours = -1; + m_nStatsPassedCasino3 = false; + m_nStatsPassedCasino6 = false; + m_nStatsPassedCasino10 = false; + m_nStatsPassedCat1 = false; + m_nStatsPassedDesert1 = false; + m_nStatsPassedDesert3 = false; + m_nStatsPassedDesert5 = false; + m_nStatsPassedDesert8 = false; + m_nStatsPassedDesert10 = false; + m_nStatsPassedFarlie3 = false; + m_nStatsPassedLAFin2 = false; + m_nStatsPassedMansion2 = false; + m_nStatsPassedRyder2 = false; + m_nStatsPassedRiot1 = false; + m_nStatsPassedSCrash1 = false; + m_nStatsPassedStrap4 = false; + m_nStatsPassedSweet2 = false; + m_nStatsPassedTruth2 = false; + m_nStatsPassedVCrash2 = false; + m_nStatsStartedBadlands = false; + m_nStatsStartedCat2 = false; + m_nStatsStartedCrash1 = false; } - // 0x4E8350 bool CAERadioTrackManager::IsRadioOn() const { - return m_nMode != 7 || m_bInitialised || m_nStationsListed || m_nStationsListDown; + return m_nMode != eRadioTrackMode::UNK_7 || m_bInitialised || m_nStationsListed || m_nStationsListDown; } // 0x4E8370 @@ -156,10 +194,8 @@ int32* CAERadioTrackManager::GetRadioStationListenTimes() { } // 0x4E83F0 -int8 CAERadioTrackManager::GetCurrentRadioStationID() const { - return settings1.m_nCurrentRadioStation == -1 - ? RADIO_OFF - : settings1.m_nCurrentRadioStation; +eRadioID CAERadioTrackManager::GetCurrentRadioStationID() const { + return m_RequestedSettings.m_nCurrentRadioStation == RADIO_INVALID ? RADIO_OFF : m_RequestedSettings.m_nCurrentRadioStation; } // 0x4E82E0 @@ -169,28 +205,41 @@ void CAERadioTrackManager::SetRadioAutoRetuneOnOff(bool enable) { // 0x4E82F0 void CAERadioTrackManager::SetBassSetting(int8 nBassSet, float fBassGrain) { - settings1.m_fBassGain = settings2.m_fBassGain = fBassGrain; - settings1.m_nBassSet = settings2.m_nBassSet = nBassSet; + m_RequestedSettings.m_fBassGain = m_ActiveSettings.m_fBassGain = fBassGrain; + m_RequestedSettings.m_nBassSet = m_ActiveSettings.m_nBassSet = nBassSet; AEAudioHardware.SetBassSetting(m_bBassEnhance ? nBassSet : 0, fBassGrain); } // 0x4E9DB0 void CAERadioTrackManager::SetBassEnhanceOnOff(bool enable) { m_bBassEnhance = enable; - if (m_nMode == 2) { - settings1.m_nBassSet = settings2.m_nBassSet; - settings1.m_fBassGain = settings2.m_fBassGain; + if (m_nMode == eRadioTrackMode::UNK_2) { + m_RequestedSettings.m_nBassSet = m_ActiveSettings.m_nBassSet; + m_RequestedSettings.m_fBassGain = m_ActiveSettings.m_fBassGain; if (enable) { - AEAudioHardware.SetBassSetting(settings2.m_nBassSet, settings2.m_fBassGain); + AEAudioHardware.SetBassSetting(m_ActiveSettings.m_nBassSet, m_ActiveSettings.m_fBassGain); } else { - AEAudioHardware.SetBassSetting(0, settings2.m_fBassGain); + AEAudioHardware.SetBassSetting(0, m_ActiveSettings.m_fBassGain); } } } // 0x4E8290 -void CAERadioTrackManager::RetuneRadio(RadioStationId id) { - plugin::CallMethod<0x4E8290, CAERadioTrackManager*, int8>(this, id); +void CAERadioTrackManager::RetuneRadio(eRadioID id) { + const auto retunedStation = [id] { + if (id == RADIO_USER_TRACKS && !AEUserRadioTrackManager.m_nUserTracksCount) { + return RADIO_OFF; + } else { + return id; + } + }(); + + if (CTimer::GetIsPaused()) { + m_iRadioStationMenuRequest = retunedStation; + m_nRetuneStartedTime = CTimer::GetTimeInMSPauseMode(); + } else { + m_iRadioStationScriptRequest = retunedStation; + } } // 0x4E9E50 @@ -213,13 +262,12 @@ void CAERadioTrackManager::DisplayRadioStationName() { } if (CTimer::GetTimeInMS() < m_nTimeToDisplayRadioName) { - auto station = m_nStationsListed + settings1.m_nCurrentRadioStation; + int station = m_nStationsListed + m_RequestedSettings.m_nCurrentRadioStation; if (station) { - if (station > 0) { - if (station >= RADIO_COUNT) - station = station - 13; - } else { - station = station + 13; + if (station >= RADIO_COUNT) { + station -= RADIO_COUNT - 1; + } else if (station <= 0) { + station += RADIO_COUNT - 1; } CFont::SetFontStyle(eFontStyle::FONT_MENU); @@ -239,9 +287,11 @@ void CAERadioTrackManager::DisplayRadioStationName() { } // 0x4E9E10 -const char* CAERadioTrackManager::GetRadioStationName(RadioStationId id) { - if (id <= 0) +const char* CAERadioTrackManager::GetRadioStationName(eRadioID id) { + if (id <= 0) { + NOTSA_UNREACHABLE(); return nullptr; + } char str[8]; GetRadioStationNameKey(id, str); @@ -249,19 +299,34 @@ const char* CAERadioTrackManager::GetRadioStationName(RadioStationId id) { } // 0x4E8380 -void CAERadioTrackManager::GetRadioStationNameKey(RadioStationId id, char* outStr) { - if (id == RADIO_OFF) { - strcpy_s(outStr, 8u, "FEA_NON"); - } else if (id == RADIO_USER_TRACKS) { - strcpy_s(outStr, 8u, "FEA_MP3"); - } else { - sprintf_s(outStr, 8u, "FEA_R%d", id - 1); +void CAERadioTrackManager::GetRadioStationNameKey(eRadioID id, char* outStr) { + switch (id) { + case RADIO_OFF: + *std::format_to_n(outStr, 7u, "FEA_NON").out = '\0'; + break; + case RADIO_USER_TRACKS: + *std::format_to_n(outStr, 7u, "FEA_MP3").out = '\0'; + break; + default: + assert(0 <= id && id < RADIO_USER_TRACKS); + *std::format_to_n(outStr, 7u, "FEA_R{:d}", (int32)id - 1).out = '\0'; + break; } } // 0x4E9800 bool CAERadioTrackManager::IsVehicleRadioActive() { - return plugin::CallAndReturn(); + if (const auto opts = CAEVehicleAudioEntity::StaticGetPlayerVehicleAudioSettingsForRadio()) { + switch (opts->m_nRadioType) { + case RADIO_CIVILIAN: + case RADIO_EMERGENCY: + case RADIO_UNKNOWN: + return true; + default: + break; + } + } + return false; } // 0x4E8410 @@ -280,59 +345,95 @@ void CAERadioTrackManager::CheckForMissionStatsChanges() { } } - float statsCitiesPassed = CStats::GetStatValue(STAT_CITY_UNLOCKED); - if ((float)m_nStatsCitiesPassed < statsCitiesPassed) { - m_nStatsCitiesPassed = (uint8)statsCitiesPassed; + const auto statsCitiesPassed = CStats::GetStatValue(STAT_CITY_UNLOCKED); + if (m_nStatsCitiesPassed < statsCitiesPassed) { + m_nStatsCitiesPassed = statsCitiesPassed; if (statsCitiesPassed == 1 || statsCitiesPassed == 2) { - m_nStatsLastHitGameClockDays = CClock::GetGameClockDays(); + m_nStatsLastHitGameClockDays = CClock::GetGameClockDays(); m_nStatsLastHitGameClockHours = CClock::GetGameClockHours(); - m_nStatsLastHitTimeOutHours = 24; - m_nSpecialDJBanterPending = 1; - m_nSpecialDJBanterIndex = m_nStatsCitiesPassed - 1; + m_nStatsLastHitTimeOutHours = 24; + m_nSpecialDJBanterPending = 1; + m_nSpecialDJBanterIndex = m_nStatsCitiesPassed - 1; } } const auto Update = [](uint8& inputStat, const eStats stat, const auto specialDJBanterIndex) { - float statValue = CStats::GetStatValue(stat); - if ((float)inputStat < statValue) { - inputStat = (uint8)statValue; + const auto statValue = CStats::GetStatValue(stat); + if (inputStat < statValue) { + inputStat = statValue; if (inputStat == 1) { - m_nStatsLastHitGameClockDays = CClock::GetGameClockDays(); + m_nStatsLastHitGameClockDays = CClock::GetGameClockDays(); m_nStatsLastHitGameClockHours = CClock::GetGameClockHours(); - m_nStatsLastHitTimeOutHours = 24 * 7; - m_nSpecialDJBanterPending = 2; - m_nSpecialDJBanterIndex = specialDJBanterIndex; + m_nStatsLastHitTimeOutHours = 24 * 7; + m_nSpecialDJBanterPending = 2; + m_nSpecialDJBanterIndex = specialDJBanterIndex; } } }; - Update(m_nStatsPassedCasino3, STAT_LEAST_FAVORITE_RADIO_STATION, 0); - Update(m_nStatsPassedCasino6, STAT_CURRENT_WEAPON_SKILL, 1); - Update(m_nStatsPassedCasino10, STAT_WEAPON_SKILL_LEVELS, 2); - Update(m_nStatsPassedCat1, STAT_LOCAL_LIQUOR_STORE_MISSION_ACCOMPLISHED, 3); - Update(m_nStatsPassedDesert1, STAT_PLAYING_TIME, 4); - Update(m_nStatsPassedDesert3, STAT_PILOT_RANKING, 5); - Update(m_nStatsPassedDesert5, STAT_STRONGEST_GANG, 6); - Update(m_nStatsPassedDesert8, STAT_2ND_STRONGEST_GANG, 7); - Update(m_nStatsPassedDesert10, STAT_3RD_STRONGEST_GANG, 8); - Update(m_nStatsPassedFarlie3, STAT_MIKE_TORENO_MISSION_ACCOMPLISHED, 9); - Update(m_nStatsPassedLAFin2, STAT_LEAST_FAVORITE_GANG, 10); - Update(m_nStatsPassedMansion2, STAT_A_HOME_IN_THE_HILLS_MISSION_ACCOMPLISHED, 11); - Update(m_nStatsPassedRyder2, STAT_RYDERS_MISSION_ROBBING_UNCLE_SAM_ACCOMPLISHED, 12); - Update(m_nStatsPassedRiot1, STAT_RIOT_MISSION_ACCOMPLISHED, 13); - Update(m_nStatsPassedSCrash1, STAT_GANG_STRENGTH, 14); - Update(m_nStatsPassedStrap4, STAT_TERRITORY_UNDER_CONTROL, 15); - Update(m_nStatsPassedSweet2, STAT_DRIVE_THRU_MISSION_ACCOMPLISHED, 16); - Update(m_nStatsPassedTruth2, STAT_ARE_YOU_GOING_TO_SAN_FIERRO_MISSION_ACCOMPLISHED, 17); - Update(m_nStatsPassedVCrash2, STAT_HIGH_NOON_MISSION_ACCOMPLISHED, 18); - Update(m_nStatsStartedBadlands, STAT_THE_GREEN_SABRE_MISSION_ACCOMPLISHED, 19); - Update(m_nStatsStartedCat2, STAT_MAYBE_CATALINA_MEETING, 20); - Update(m_nStatsStartedCrash1, STAT_MAYBE_WU_ZI_MEETING, 21); + Update(m_nStatsPassedCasino3, STAT_LEAST_FAVORITE_RADIO_STATION, 0); + Update(m_nStatsPassedCasino6, STAT_CURRENT_WEAPON_SKILL, 1); + Update(m_nStatsPassedCasino10, STAT_WEAPON_SKILL_LEVELS, 2); + Update(m_nStatsPassedCat1, STAT_LOCAL_LIQUOR_STORE_MISSION_ACCOMPLISHED, 3); + Update(m_nStatsPassedDesert1, STAT_PLAYING_TIME, 4); + Update(m_nStatsPassedDesert3, STAT_PILOT_RANKING, 5); + Update(m_nStatsPassedDesert5, STAT_STRONGEST_GANG, 6); + Update(m_nStatsPassedDesert8, STAT_2ND_STRONGEST_GANG, 7); + Update(m_nStatsPassedDesert10, STAT_3RD_STRONGEST_GANG, 8); + Update(m_nStatsPassedFarlie3, STAT_MIKE_TORENO_MISSION_ACCOMPLISHED, 9); + Update(m_nStatsPassedLAFin2, STAT_LEAST_FAVORITE_GANG, 10); + Update(m_nStatsPassedMansion2, STAT_A_HOME_IN_THE_HILLS_MISSION_ACCOMPLISHED, 11); + Update(m_nStatsPassedRyder2, STAT_RYDERS_MISSION_ROBBING_UNCLE_SAM_ACCOMPLISHED, 12); + Update(m_nStatsPassedRiot1, STAT_RIOT_MISSION_ACCOMPLISHED, 13); + Update(m_nStatsPassedSCrash1, STAT_GANG_STRENGTH, 14); + Update(m_nStatsPassedStrap4, STAT_TERRITORY_UNDER_CONTROL, 15); + Update(m_nStatsPassedSweet2, STAT_DRIVE_THRU_MISSION_ACCOMPLISHED, 16); + Update(m_nStatsPassedTruth2, STAT_ARE_YOU_GOING_TO_SAN_FIERRO_MISSION_ACCOMPLISHED, 17); + Update(m_nStatsPassedVCrash2, STAT_HIGH_NOON_MISSION_ACCOMPLISHED, 18); + Update(m_nStatsStartedBadlands, STAT_THE_GREEN_SABRE_MISSION_ACCOMPLISHED, 19); + Update(m_nStatsStartedCat2, STAT_MAYBE_CATALINA_MEETING, 20); + Update(m_nStatsStartedCrash1, STAT_MAYBE_WU_ZI_MEETING, 21); } // 0x4EA930 void CAERadioTrackManager::CheckForTrackConcatenation() { plugin::CallMethod<0x4EA930, CAERadioTrackManager*>(this); + /* + const auto utPlayMode = AEUserRadioTrackManager.GetUserTrackPlayMode(); + if (m_ActiveSettings.m_nCurrentRadioStation == RADIO_USER_TRACKS && utPlayMode != 0) { + if (utPlayMode == 2 && m_ActiveSettings.m_iTrackPlayTime != -4) { // ??? + AEUserRadioTrackManager.SetUserTrackIndex(m_ActiveSettings.m_aTrackQueue.front()); + + m_ActiveSettings.m_aTrackQueue[1] = AEUserRadioTrackManager.SelectUserTrackIndex(); + m_ActiveSettings.m_aTrackTypes[1] = TYPE_USER_TRACK; + m_ActiveSettings.m_aTrackIndexes[1] = m_ActiveSettings.m_aTrackQueue[1]; + + AEAudioHardware.PlayTrack( + m_ActiveSettings.m_aTrackQueue[0], + m_ActiveSettings.m_aTrackQueue[1], + 0u, + m_ActiveSettings.m_nTrackFlags, + m_ActiveSettings.m_aTrackTypes[0] == TYPE_USER_TRACK, + m_ActiveSettings.m_aTrackTypes[1] == TYPE_USER_TRACK // always true? + ); + } + m_nUserTrackPlayMode = AEUserRadioTrackManager.GetUserTrackPlayMode(); + } + + const auto nextTrack = m_ActiveSettings.m_aTrackQueue[1]; + if (AEAudioHardware.GetActiveTrackID() == nextTrack && nextTrack >= 0) { + m_ActiveSettings.SwitchToNextTrack(); + + if (m_ActiveSettings.m_aTrackQueue[1] == -1) { + const auto radioId = m_ActiveSettings.m_nCurrentRadioStation; + if (radioId == RADIO_USER_TRACKS) { + if (!FrontEndMenuManager.m_nRadioMode && CAEAudioUtility::ResolveProbability(0.17f)) { + m_ActiveSettings.m_aTrackQueue + } + } + } + } + */ } // 0x4EB660 @@ -342,13 +443,33 @@ void CAERadioTrackManager::CheckForStationRetune() { // 0x4EB890 void CAERadioTrackManager::CheckForStationRetuneDuringPause() { - plugin::CallMethod<0x4EB890, CAERadioTrackManager*>(this); -} + if (m_ActiveSettings.m_nCurrentRadioStation == RADIO_EMERGENCY_AA && IsRadioOn() || m_iRadioStationMenuRequest <= RADIO_INVALID) + return; + if (m_iRadioStationMenuRequest != RADIO_OFF) { + if (m_ActiveSettings.m_nCurrentRadioStation == RADIO_OFF) { + AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_CLICK_ON); + m_ActiveSettings.m_nCurrentRadioStation = RADIO_INVALID; + } else { + AudioEngine.StopRadio(nullptr, true); + } + + AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_RETUNE_START); + if (CTimer::GetTimeInMSPauseMode() > m_nRetuneStartedTime + 700u) { + StartRadio((eRadioID)m_iRadioStationMenuRequest, m_ActiveSettings.m_nBassSet, m_ActiveSettings.m_fBassGain, 0); + m_iRadioStationMenuRequest = RADIO_INVALID; + } + } else { + AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_CLICK_OFF); + AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_RETUNE_STOP); + StartRadio(RADIO_OFF, m_ActiveSettings.m_nBassSet, m_ActiveSettings.m_fBassGain, 0); + m_iRadioStationMenuRequest = RADIO_INVALID; + } +} // 0x4EA640 void CAERadioTrackManager::StartTrackPlayback() { - AEAudioHardware.SetChannelFlags(m_nChannel, 0, 55); + AEAudioHardware.SetChannelFlags(m_HwClientHandle, 0, 55); AEAudioHardware.StartTrackPlayback(); UpdateRadioVolumes(); } @@ -412,8 +533,8 @@ void CAERadioTrackManager::UpdateRadioVolumes() { volume = volume - 20.0f; } - if (m_bBassEnhance && settings2.m_nBassSet) { - switch (settings2.m_nBassSet) { + if (m_bBassEnhance && m_ActiveSettings.m_nBassSet) { + switch (m_ActiveSettings.m_nBassSet) { case 1: volume -= 2.0f; break; @@ -434,123 +555,207 @@ void CAERadioTrackManager::PlayRadioAnnouncement(uint32) { // 0x4EB550 void CAERadioTrackManager::StartRadio(tVehicleAudioSettings* settings) { - plugin::CallMethod<0x4EB550, CAERadioTrackManager*, tVehicleAudioSettings*>(this, settings); + // plugin::CallMethod<0x4EB550, CAERadioTrackManager*, tVehicleAudioSettings*>(this, settings); + + if (CReplay::Mode == MODE_PLAYBACK) + return; + + if (settings->m_nRadioType == RADIO_EMERGENCY) { + StartRadio(RADIO_EMERGENCY_AA, settings->m_nBassSetting, settings->m_fBassEq, 0); + return; + } + + if (settings->m_nRadioType != RADIO_CIVILIAN) + return; + + const bool needsRetune = [&] { + if (!m_bRadioAutoSelect) + return false; + + const auto savedId = m_nSavedRadioStationId; + if (savedId < 0 || savedId == settings->m_nRadioID || savedId == RADIO_OFF || savedId == RADIO_EMERGENCY_AA) + return false; + + if (CTimer::GetTimeInMS() > m_nSavedTimeMs + 60'000) + return false; + + const auto savedHours = m_nSavedGameClockHours; + auto savedDays = m_nSavedGameClockDays; + if (savedHours < 0 || savedDays < 0) + return false; + + if (savedDays > CClock::GetGameClockDays()) { + const auto month = CClock::GetGameClockMonth(); + savedDays += CClock::daysInMonth[month == 0 ? 11 : month - 1]; // prev month + } + + if (CClock::GetGameClockHours() + 24 * savedDays - savedHours > 5) + return false; + + return true; + }(); + + if (needsRetune) { + AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_RETUNE_START); + StartRadio((eRadioID)m_nSavedRadioStationId, settings->m_nBassSetting, settings->m_fBassEq, 0); + } else { + StartRadio(settings->m_nRadioID, settings->m_nBassSetting, settings->m_fBassEq, 0); + } } // 0x4EB3C0 -void CAERadioTrackManager::StartRadio(RadioStationId id, int8 bassValue, float bassGain, uint8 a5) { - plugin::CallMethod<0x4EB3C0, CAERadioTrackManager*, int8, int8, float, uint8>(this, id, bassValue, bassGain, a5); +void CAERadioTrackManager::StartRadio(eRadioID id, int8 bassValue, float bassGain, uint8 a5) { + // plugin::CallMethod<0x4EB3C0, CAERadioTrackManager*, int8, int8, float, uint8>(this, id, bassValue, bassGain, a5); + id = std::min(id, RADIO_OFF); + + if (CTimer::GetIsPaused()) { + m_bEnabledInPauseMode = true; + + if (IsRadioOn() && id == m_ActiveSettings.m_nCurrentRadioStation) { + m_aRadioState[id].m_iTimeInPauseModeInMs = CTimer::GetTimeInMSPauseMode(); + return; + } + } + + if (id != RADIO_OFF && CAudioEngine::IsAmbienceTrackActive()) { + if (!CTimer::GetIsPaused() && CAudioEngine::DoesAmbienceTrackOverrideRadio()) { + AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_RETUNE_STOP); + return; + } + AudioEngine.StopAmbienceTrack(false); + } + + m_RequestedSettings.m_nCurrentRadioStation = id; + m_RequestedSettings.m_nBassSet = bassValue; + m_RequestedSettings.m_fBassGain = bassGain; + + if (id == RADIO_OFF) { + m_RequestedSettings.Reset(); + } else if (m_aRadioState[id].m_iTimeInMs < 0 || !TrackRadioStation(id, a5)) { + ChooseTracksForStation(m_RequestedSettings.m_nCurrentRadioStation); + m_RequestedSettings.m_iTrackPlayTime = CAEAudioUtility::GetRandomNumberInRange(0, 300'000); + } + + m_bInitialised = true; + switch (m_nMode) { + case eRadioTrackMode::UNK_0: + case eRadioTrackMode::UNK_1: + case eRadioTrackMode::UNK_2: + m_nMode = eRadioTrackMode::GAME_PAUSED; + break; + default: + break; + } + m_aRadioState[m_RequestedSettings.m_nCurrentRadioStation].m_iTimeInPauseModeInMs = -1; } // 0x4EAC30 -bool CAERadioTrackManager::TrackRadioStation(RadioStationId id, uint8 a2) { +bool CAERadioTrackManager::TrackRadioStation(eRadioID id, uint8 a2) { return plugin::CallMethodAndReturn(this, id, a2); } // 0x4EA670 -bool CAERadioTrackManager::QueueUpTracksForStation(RadioStationId id, int8* iTrackCount, int8 radioState, tRadioSettings* settings) { - return plugin::CallMethodAndReturn(this, id, iTrackCount, radioState, settings); +bool CAERadioTrackManager::QueueUpTracksForStation(eRadioID id, int8* iTrackCount, int8 radioState, tRadioSettings& settings) { + return plugin::CallMethodAndReturn(this, id, iTrackCount, radioState, settings); } // 0x4E9820 -void CAERadioTrackManager::StopRadio(tVehicleAudioSettings* settings, bool bDuringPause) { - plugin::CallMethod<0x4E9820, CAERadioTrackManager*, tVehicleAudioSettings*, bool>(this, settings, bDuringPause); +void CAERadioTrackManager::StopRadio(tVehicleAudioSettings* settings, bool duringPause) { + return plugin::CallMethod<0x4E9820, CAERadioTrackManager*, tVehicleAudioSettings*, bool>(this, settings, duringPause); } // 0x4E94C0 -int32 CAERadioTrackManager::ChooseIdentIndex(RadioStationId id) { +int32 CAERadioTrackManager::ChooseIdentIndex(eRadioID id) { return plugin::CallAndReturn(this, id); } // 0x4E9570 -int32 CAERadioTrackManager::ChooseAdvertIndex(RadioStationId id) { +int32 CAERadioTrackManager::ChooseAdvertIndex(eRadioID id) { return plugin::CallAndReturn(this, id); } // 0x4EA270 -int8 CAERadioTrackManager::ChooseMusicTrackIndex(RadioStationId id) { +int8 CAERadioTrackManager::ChooseMusicTrackIndex(eRadioID id) { return plugin::CallAndReturn(this, id); } // 0x4EA2D0 -int32 CAERadioTrackManager::ChooseDJBanterIndex(RadioStationId id) { +int32 CAERadioTrackManager::ChooseDJBanterIndex(eRadioID id) { return plugin::CallAndReturn(this, id); } // 0x4E95E0 -int32 CAERadioTrackManager::ChooseDJBanterIndexFromList(RadioStationId id, int32** list) { - return plugin::CallMethodAndReturn(this, id, list); +int32 CAERadioTrackManager::ChooseDJBanterIndexFromList(eRadioID id, int32** list) { + return plugin::CallMethodAndReturn(this, id, list); } // 0x4EB180 -void CAERadioTrackManager::ChooseTracksForStation(RadioStationId id) { - int8 trackCount = 0; - tRadioSettings* settings = &settings1; - - settings->Reset(); +void CAERadioTrackManager::ChooseTracksForStation(eRadioID id) { + int8 trackCount = 0; + m_RequestedSettings.Reset(); if (!CAEAudioUtility::ResolveProbability(0.95f)) { if (id) { if (CAEAudioUtility::ResolveProbability(0.5f)) - QueueUpTracksForStation(id, &trackCount, TYPE_INDENT, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_INDENT, m_RequestedSettings); - if (!QueueUpTracksForStation(id, &trackCount, TYPE_DJ_BANTER, settings)) - QueueUpTracksForStation(id, &trackCount, TYPE_ADVERT, settings); + if (!QueueUpTracksForStation(id, &trackCount, TYPE_DJ_BANTER, m_RequestedSettings)) + QueueUpTracksForStation(id, &trackCount, TYPE_ADVERT, m_RequestedSettings); if (id == RADIO_USER_TRACKS) { - QueueUpTracksForStation(RADIO_USER_TRACKS, &trackCount, TYPE_TRACK, settings); + QueueUpTracksForStation(RADIO_USER_TRACKS, &trackCount, TYPE_TRACK, m_RequestedSettings); return; } } else { - QueueUpTracksForStation(0, &trackCount, TYPE_DJ_BANTER, settings); + QueueUpTracksForStation(RADIO_EMERGENCY_AA, &trackCount, TYPE_DJ_BANTER, m_RequestedSettings); } - QueueUpTracksForStation(id, &trackCount, TYPE_INTRO, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_INTRO, m_RequestedSettings); return; } if (id == RADIO_USER_TRACKS) { - QueueUpTracksForStation(RADIO_USER_TRACKS, &trackCount, TYPE_TRACK, settings); - QueueUpTracksForStation(RADIO_USER_TRACKS, &trackCount, TYPE_TRACK, settings); + QueueUpTracksForStation(RADIO_USER_TRACKS, &trackCount, TYPE_TRACK, m_RequestedSettings); + QueueUpTracksForStation(RADIO_USER_TRACKS, &trackCount, TYPE_TRACK, m_RequestedSettings); if (!FrontEndMenuManager.m_nRadioMode && CAEAudioUtility::ResolveProbability(0.17f)) { - QueueUpTracksForStation(RADIO_USER_TRACKS, &trackCount, TYPE_ADVERT, settings); + QueueUpTracksForStation(RADIO_USER_TRACKS, &trackCount, TYPE_ADVERT, m_RequestedSettings); } return; } if (CAEAudioUtility::ResolveProbability(0.9f)) { - QueueUpTracksForStation(id, &trackCount, TYPE_TRACK, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_TRACK, m_RequestedSettings); return; } if (CAEAudioUtility::ResolveProbability(0.5f)) { if (CAEAudioUtility::ResolveProbability(0.5f)) { - QueueUpTracksForStation(id, &trackCount, TYPE_INDENT, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_INDENT, m_RequestedSettings); } - QueueUpTracksForStation(id, &trackCount, TYPE_INTRO, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_INTRO, m_RequestedSettings); return; } - QueueUpTracksForStation(id, &trackCount, TYPE_OUTRO, settings); - AddMusicTrackIndexToHistory(id, *(&settings1.m_iPrevTrackType + trackCount)); + QueueUpTracksForStation(id, &trackCount, TYPE_OUTRO, m_RequestedSettings); + AddMusicTrackIndexToHistory(id, m_RequestedSettings.m_aTrackIndexes[trackCount - 1]); if (id == RADIO_EMERGENCY_AA) { - QueueUpTracksForStation(0, &trackCount, TYPE_DJ_BANTER, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_DJ_BANTER, m_RequestedSettings); return; } if (CAEAudioUtility::ResolveProbability(0.5f)) { if (CAEAudioUtility::ResolveProbability(0.5f)) { - QueueUpTracksForStation(id, &trackCount, TYPE_INDENT, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_INDENT, m_RequestedSettings); } - QueueUpTracksForStation(id, &trackCount, TYPE_INTRO, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_INTRO, m_RequestedSettings); return; } if (CAEAudioUtility::ResolveProbability(0.5f)) - QueueUpTracksForStation(id, &trackCount, TYPE_INDENT, settings); + QueueUpTracksForStation(id, &trackCount, TYPE_INDENT, m_RequestedSettings); - if (!QueueUpTracksForStation(id, &trackCount, TYPE_DJ_BANTER, settings)) - QueueUpTracksForStation(id, &trackCount, TYPE_ADVERT, settings); + if (!QueueUpTracksForStation(id, &trackCount, TYPE_DJ_BANTER, m_RequestedSettings)) + QueueUpTracksForStation(id, &trackCount, TYPE_ADVERT, m_RequestedSettings); } // 0x4E8E40 @@ -559,46 +764,65 @@ int8 CAERadioTrackManager::ChooseTalkRadioShow() { } // 0x4E96C0 -void CAERadioTrackManager::AddMusicTrackIndexToHistory(RadioStationId id, int8 trackIndex) { - plugin::CallMethod<0x4E96C0, CAERadioTrackManager*, int8, int8>(this, id, trackIndex); +void CAERadioTrackManager::AddMusicTrackIndexToHistory(eRadioID id, int8 trackIndex) { + if (trackIndex >= 0 && m_nMusicTrackIndexHistory[id].indices[0] != trackIndex) { + m_nMusicTrackIndexHistory[id].PutAtFirst(trackIndex); + m_nTracksInARow[id]++; + } } // 0x4E9720 -void CAERadioTrackManager::AddIdentIndexToHistory(RadioStationId id, int8 trackIndex) { - plugin::CallMethod<0x4E9720, CAERadioTrackManager*, RadioStationId, int8>(this, id, trackIndex); +void CAERadioTrackManager::AddIdentIndexToHistory(eRadioID id, int8 trackIndex) { + if (m_nIdentIndexHistory[id].indices[0] != trackIndex) + m_nIdentIndexHistory[id].PutAtFirst(trackIndex); } // 0x4E9760 -void CAERadioTrackManager::AddAdvertIndexToHistory(RadioStationId id, int8 trackIndex) { - plugin::CallMethod<0x4E9760, CAERadioTrackManager*, RadioStationId, int8>(this, id, trackIndex); +void CAERadioTrackManager::AddAdvertIndexToHistory(eRadioID id, int8 trackIndex) { + if (m_nAdvertIndexHistory[id].indices[0] != trackIndex) { + m_nAdvertIndexHistory[id].PutAtFirst(trackIndex); + m_nTracksInARow[id] = 0; + } } // 0x4E97B0 -void CAERadioTrackManager::AddDJBanterIndexToHistory(RadioStationId id, int8 trackIndex) { - plugin::CallMethod<0x4E97B0, CAERadioTrackManager*, RadioStationId, int8>(this, id, trackIndex); +void CAERadioTrackManager::AddDJBanterIndexToHistory(eRadioID id, int8 trackIndex) { + if (m_nDJBanterIndexHistory[id].indices[0] != trackIndex) { + m_nDJBanterIndexHistory[id].PutAtFirst(trackIndex); + m_nTracksInARow[id] = 0; + } } // 0x4EA590 void CAERadioTrackManager::CheckForPause() { if (CTimer::GetIsPaused()) { - if (m_bEnabledInPauseMode) { - AEAudioHardware.SetChannelFrequencyScalingFactor(m_nChannel, 0, 1.0f); - } else { - AEAudioHardware.SetChannelFrequencyScalingFactor(m_nChannel, 0, 0.0f); - } m_bPauseMode = true; - } else { - // todo: See CAEVehicleAudioEntity::Terminate:437 m_nRadioType. - tVehicleAudioSettings* settings = CAEVehicleAudioEntity::StaticGetPlayerVehicleAudioSettingsForRadio(); - // todo: Comparison of different enumeration types - if (settings && (settings->m_nRadioType == RADIO_EMERGENCY_AA || settings->m_nRadioType == RADIO_CLASSIC_ROCK || settings->m_nRadioType == RADIO_COUNTRY) || AudioEngine.IsAmbienceRadioActive()) { - m_bPauseMode = false; - AEAudioHardware.SetChannelFrequencyScalingFactor(m_nChannel, 0, 1.0f); - } else { - StopRadio(nullptr, false); - AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_RETUNE_STOP); - m_bPauseMode = false; + AEAudioHardware.SetChannelFrequencyScalingFactor(m_HwClientHandle, 0, m_bEnabledInPauseMode ? 1.0f : 0.0f); + + return; + } + + // todo: See CAEVehicleAudioEntity::Terminate:437 m_nRadioType. + tVehicleAudioSettings* settings = CAEVehicleAudioEntity::StaticGetPlayerVehicleAudioSettingsForRadio(); + + const bool isRadioTypeOrdinary = settings && [radioType = settings->m_nRadioType]{ + switch (radioType) { + case RADIO_CIVILIAN: + case RADIO_EMERGENCY: + case RADIO_UNKNOWN: + return true; + default: + return false; } + }(); + + if (isRadioTypeOrdinary || CAudioEngine::IsAmbienceRadioActive()) { + m_bPauseMode = false; + AEAudioHardware.SetChannelFrequencyScalingFactor(m_HwClientHandle, 0, 1.0f); + } else { + StopRadio(nullptr, false); + AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_RADIO_RETUNE_STOP); + m_bPauseMode = false; } } @@ -615,15 +839,15 @@ void CAERadioTrackManager::Load() { // todo: m_nMusicTrackIndexHistory; for (auto i = 0; i < IDENT_INDEX_HISTORY_COUNT; i++) { - CGenericGameStorage::LoadDataFromWorkBuffer(&m_nIdentIndexHistory[r][i], sizeof(m_nIdentIndexHistory[r][i])); + CGenericGameStorage::LoadDataFromWorkBuffer(&m_nIdentIndexHistory[r].indices[i], sizeof(m_nIdentIndexHistory[r].indices[i])); } for (auto i = 0; i < ADVERT_INDEX_HISTORY_COUNT; i++) { - CGenericGameStorage::LoadDataFromWorkBuffer(&m_nAdvertIndexHistory[r][i], sizeof(m_nAdvertIndexHistory[r][i])); + CGenericGameStorage::LoadDataFromWorkBuffer(&m_nAdvertIndexHistory[r].indices[i], sizeof(m_nAdvertIndexHistory[r].indices[i])); } for (auto i = 0; i < DJBANTER_INDEX_HISTORY_COUNT; i++) { - CGenericGameStorage::LoadDataFromWorkBuffer(&m_nDJBanterIndexHistory[r][i], sizeof(m_nDJBanterIndexHistory[r][i])); + CGenericGameStorage::LoadDataFromWorkBuffer(&m_nDJBanterIndexHistory[r].indices[i], sizeof(m_nDJBanterIndexHistory[r].indices[i])); } } @@ -665,15 +889,15 @@ void CAERadioTrackManager::Save() { // todo: m_nMusicTrackIndexHistory; for (auto i = 0; i < IDENT_INDEX_HISTORY_COUNT; i++) { - CGenericGameStorage::SaveDataToWorkBuffer(&m_nIdentIndexHistory[r][i], sizeof(m_nIdentIndexHistory[r][i])); + CGenericGameStorage::SaveDataToWorkBuffer(&m_nIdentIndexHistory[r].indices[i], sizeof(m_nIdentIndexHistory[r].indices[i])); } for (auto i = 0; i < ADVERT_INDEX_HISTORY_COUNT; i++) { - CGenericGameStorage::SaveDataToWorkBuffer(&m_nAdvertIndexHistory[r][i], sizeof(m_nAdvertIndexHistory[r][i])); + CGenericGameStorage::SaveDataToWorkBuffer(&m_nAdvertIndexHistory[r].indices[i], sizeof(m_nAdvertIndexHistory[r].indices[i])); } for (auto i = 0; i < DJBANTER_INDEX_HISTORY_COUNT; i++) { - CGenericGameStorage::SaveDataToWorkBuffer(&m_nDJBanterIndexHistory[r][i], sizeof(m_nDJBanterIndexHistory[r][i])); + CGenericGameStorage::SaveDataToWorkBuffer(&m_nDJBanterIndexHistory[r].indices[i], sizeof(m_nDJBanterIndexHistory[r].indices[i])); } } diff --git a/source/game_sa/Audio/managers/AERadioTrackManager.h b/source/game_sa/Audio/managers/AERadioTrackManager.h index dab1f49231..050d71fa6e 100644 --- a/source/game_sa/Audio/managers/AERadioTrackManager.h +++ b/source/game_sa/Audio/managers/AERadioTrackManager.h @@ -18,21 +18,27 @@ enum { }; struct tRadioSettings { - int32 m_aTrackQueue[5]; - int32 m_iCurrentTrackID; - int32 m_iPrevTrackID; - int32 m_iTrackPlayTime; - int32 m_iTrackLengthMs; - int8 m_f24; - int8 m_nCurrentRadioStation; - int8 m_nBassSet; - float m_fBassGain; - int8 m_aTrackTypes[5]; - int8 m_iCurrentTrackType; - int8 m_iPrevTrackType; - int8 m_aTrackIndexes[5]; - int8 m_iCurrentTrackIndex; - int8 m_iPrevTrackIndex; + static constexpr size_t NUM_TRACKS = 5u; + + std::array m_aTrackQueue{-1}; + int32 m_iCurrentTrackID{-1}; + int32 m_iPrevTrackID{-1}; + int32 m_iTrackPlayTime{0}; + int32 m_iTrackLengthMs{0}; + int8 m_nTrackFlags{2}; // TODO: enum + eRadioID m_nCurrentRadioStation{RADIO_OFF}; // NOTSA init value. + int8 m_nBassSet{0}; + float m_fBassGain{}; // unk. init + std::array m_aTrackTypes{TYPE_NONE}; + int8 m_iCurrentTrackType{TYPE_NONE}; + int8 m_iPrevTrackType{TYPE_NONE}; + std::array m_aTrackIndexes{-1}; + int8 m_iCurrentTrackIndex{-1}; + int8 m_iPrevTrackIndex{-1}; + + tRadioSettings(eRadioID currentStation = RADIO_OFF) + : m_nCurrentRadioStation(currentStation) + {} void Reset() { for (auto i = 0u; i < std::size(m_aTrackQueue); i++) { @@ -41,132 +47,191 @@ struct tRadioSettings { m_aTrackIndexes[i] = -1; } } -}; + void SwitchToNextTrack() { + m_iPrevTrackID = m_aTrackQueue.front(); + m_iPrevTrackType = m_aTrackTypes.front(); + m_iPrevTrackIndex = m_aTrackIndexes.front(); + + const auto Rotate = [](auto& arr, auto invalidValue) { + std::copy(arr.begin() + 1, arr.end(), arr.begin()); + arr.back() = invalidValue; + }; + + Rotate(m_aTrackQueue, -1); + Rotate(m_aTrackTypes, TYPE_NONE); + Rotate(m_aTrackIndexes, -1); + } +}; VALIDATE_SIZE(tRadioSettings, 0x3C); struct tRadioState { - int32 m_aElapsed[3]; - int32 m_iTimeInPauseModeInMs; - int32 m_iTimeInMs; - int32 m_iTrackPlayTime; - int32 m_aTrackQueue[3]; - int8 m_aTrackTypes[3]; - uint8 m_nGameClockDays; - uint8 m_nGameClockHours; -}; + int32 m_aElapsed[3]{0}; + int32 m_iTimeInPauseModeInMs{-1}; + int32 m_iTimeInMs{-1}; + int32 m_iTrackPlayTime{-1}; + int32 m_aTrackQueue[3]{-1}; + int8 m_aTrackTypes[3]{TYPE_NONE}; + int8 m_nGameClockDays{-1}; + int8 m_nGameClockHours{-1}; + + void Reset(bool paused = false) { + rng::fill(m_aElapsed, 0); + rng::fill(m_aTrackQueue, -1); + rng::fill(m_aTrackTypes, TYPE_NONE); + m_iTimeInMs = -1; + if (!paused) + m_iTimeInPauseModeInMs = -1; + m_iTrackPlayTime = -1; + m_nGameClockDays = -1; + m_nGameClockHours = -1; + } +}; VALIDATE_SIZE(tRadioState, 0x2C); -struct tMusicTrackHistory { - int8 historyIndices[20]; +//typedef int8 RadioStationId; => eRadioID + +// NOTSA +template +struct tRadioIndexHistory { + std::array indices{-1}; + + void Reset() { + rng::fill(indices, -1); + } + + void PutAtFirst(int32 index) { + if constexpr (Count > 1) { + // rotate all elements to right. + std::rotate(indices.rbegin(), indices.rbegin() + 1, indices.rend()); + } + indices[0] = index; + } }; +static_assert(sizeof(tRadioIndexHistory) == sizeof(int32)); // No VALIDATE_SIZE because the preprocessor is dumb -typedef int8 RadioStationId; +enum class eRadioTrackMode { + UNK_0, + UNK_1, + UNK_2, + GAME_PAUSED, // ? + UNK_4, + UNK_5, + UNK_6, + UNK_7 +}; class CAERadioTrackManager { public: - bool m_bInitialised; - bool m_bDisplayStationName; - char m_prev; - bool m_bEnabledInPauseMode; - bool m_bBassEnhance; - bool m_bPauseMode; - bool m_bRetuneJustStarted; - bool m_bRadioAutoSelect; - uint8 m_nTracksInARow[RADIO_COUNT]; - uint8 m_nSavedGameClockDays; - uint8 m_nSavedGameClockHours; - int32 m_aListenTimes[RADIO_COUNT]; - uint32 m_nTimeRadioStationRetuned; - uint32 m_nTimeToDisplayRadioName; - uint32 m_nSavedTimeMs; - uint32 m_nRetuneStartedTime; - uint32 field_60; - int32 m_nChannel; - int32 m_nMode; - int32 m_nStationsListed; - int32 m_nStationsListDown; - int32 m_nSavedRadioStationId; - int8 m_iRadioStationMenuRequest; - int8 m_iRadioStationRequest; - int32 field_7C; - float m_f80; // 80 and 84 volume related fields. See ::UpdateRadioVolumes - float m_f84; - tRadioSettings settings1; // TODO: Maybe just make an array out of this?? - tRadioSettings settings2; - tRadioState m_aRadioState[RADIO_COUNT]; - uint32 field_368; - int8 m_nUserTrackPlayMode; + bool m_bInitialised{false}; + bool m_bDisplayStationName{false}; + char m_prev{0}; // TODO: make sense of this. + bool m_bEnabledInPauseMode{false}; + bool m_bBassEnhance{true}; + bool m_bPauseMode{false}; + bool m_bRetuneJustStarted{false}; + bool m_bRadioAutoSelect{true}; + uint8 m_nTracksInARow[RADIO_COUNT]{0}; + uint8 m_nSavedGameClockDays{0xff}; + uint8 m_nSavedGameClockHours{0xff}; + int32 m_aListenTimes[RADIO_COUNT]{}; // Filled from `CStats::FavoriteRadioStationList` + uint32 m_nTimeRadioStationRetuned{0}; + uint32 m_nTimeToDisplayRadioName{0}; + uint32 m_nSavedTimeMs{0}; + uint32 m_nRetuneStartedTime; + uint32 field_60{0}; + int32 m_HwClientHandle; + eRadioTrackMode m_nMode{eRadioTrackMode::UNK_7}; + int32 m_nStationsListed{0}; + int32 m_nStationsListDown{0}; + int32 m_nSavedRadioStationId{-1}; // TODO: convert to eRadioID after finished reversing + int32 m_iRadioStationMenuRequest{ -1 }; // <- + int32 m_iRadioStationScriptRequest{ -1 }; // <- + float m_f80{0.0f}; // 80 and 84 volume related fields. See ::UpdateRadioVolumes + float m_f84{0.0f}; + tRadioSettings m_RequestedSettings{}; // settings1 + tRadioSettings m_ActiveSettings{}; // settings2 + tRadioState m_aRadioState[RADIO_COUNT]{}; + uint32 field_368{0}; + uint8 m_nUserTrackPlayMode{}; public: static constexpr auto DJBANTER_INDEX_HISTORY_COUNT = 15; static constexpr auto ADVERT_INDEX_HISTORY_COUNT = 40; static constexpr auto IDENT_INDEX_HISTORY_COUNT = 8; + static constexpr auto MUSIC_TRACK_HISTORY_COUNT = 20; + using DJBanterIndexHistory = tRadioIndexHistory; + using AdvertIndexHistory = tRadioIndexHistory; + using IdentIndexHistory = tRadioIndexHistory; + using MusicTrackHistory = tRadioIndexHistory; + + static inline DJBanterIndexHistory (&m_nDJBanterIndexHistory)[RADIO_COUNT] = *(DJBanterIndexHistory(*)[RADIO_COUNT])0xB61D78; // 210 + static inline AdvertIndexHistory (&m_nAdvertIndexHistory)[RADIO_COUNT] = *(AdvertIndexHistory(*)[RADIO_COUNT])0xB620C0; // 560 + static inline IdentIndexHistory (&m_nIdentIndexHistory)[RADIO_COUNT] = *(IdentIndexHistory(*)[RADIO_COUNT])0xB62980; // 112 + static inline MusicTrackHistory (&m_nMusicTrackIndexHistory)[RADIO_COUNT] = *(MusicTrackHistory(*)[RADIO_COUNT])0xB62B40; // 280 - static int32 (&m_nDJBanterIndexHistory) [DJBANTER_INDEX_HISTORY_COUNT][RADIO_COUNT]; // 210 - static int32 (&m_nAdvertIndexHistory) [ADVERT_INDEX_HISTORY_COUNT][RADIO_COUNT]; // 560 - static int32 (&m_nIdentIndexHistory) [IDENT_INDEX_HISTORY_COUNT][RADIO_COUNT]; // 112 - static int8 (&m_nMusicTrackIndexHistory)[220]; // 220; - - static uint8& m_nStatsLastHitTimeOutHours; - static uint8& m_nStatsLastHitGameClockHours; - static uint8& m_nStatsLastHitGameClockDays; - static uint8& m_nStatsStartedCrash1; - static uint8& m_nStatsStartedCat2; - static uint8& m_nStatsStartedBadlands; - static uint8& m_nStatsPassedVCrash2; - static uint8& m_nStatsPassedTruth2; - static uint8& m_nStatsPassedSweet2; - static uint8& m_nStatsPassedStrap4; - static uint8& m_nStatsPassedSCrash1; - static uint8& m_nStatsPassedRiot1; - static uint8& m_nStatsPassedRyder2; - static uint8& m_nStatsPassedMansion2; - static uint8& m_nStatsPassedLAFin2; - static uint8& m_nStatsPassedFarlie3; - static uint8& m_nStatsPassedDesert10; - static uint8& m_nStatsPassedDesert8; - static uint8& m_nStatsPassedDesert5; - static uint8& m_nStatsPassedDesert3; - static uint8& m_nStatsPassedDesert1; - static uint8& m_nStatsPassedCat1; - static uint8& m_nStatsPassedCasino10; - static uint8& m_nStatsPassedCasino6; - static uint8& m_nStatsPassedCasino3; - static uint8& m_nStatsCitiesPassed; - static uint8& m_nSpecialDJBanterIndex; - static uint8& m_nSpecialDJBanterPending; + static uint8& m_nStatsLastHitTimeOutHours; // = -1; + static uint8& m_nStatsLastHitGameClockHours; // = -1; + static uint8& m_nStatsLastHitGameClockDays; // = -1; + static uint8& m_nStatsStartedCrash1; // = 0; + static uint8& m_nStatsStartedCat2; // = 0; + static uint8& m_nStatsStartedBadlands; // = 0; + static uint8& m_nStatsPassedVCrash2; // = 0; + static uint8& m_nStatsPassedTruth2; // = 0; + static uint8& m_nStatsPassedSweet2; // = 0; + static uint8& m_nStatsPassedStrap4; // = 0; + static uint8& m_nStatsPassedSCrash1; // = 0; + static uint8& m_nStatsPassedRiot1; // = 0; + static uint8& m_nStatsPassedRyder2; // = 0; + static uint8& m_nStatsPassedMansion2; // = 0; + static uint8& m_nStatsPassedLAFin2; // = 0; + static uint8& m_nStatsPassedFarlie3; // = 0; + static uint8& m_nStatsPassedDesert10; // = 0; + static uint8& m_nStatsPassedDesert8; // = 0; + static uint8& m_nStatsPassedDesert5; // = 0; + static uint8& m_nStatsPassedDesert3; // = 0; + static uint8& m_nStatsPassedDesert1; // = 0; + static uint8& m_nStatsPassedCat1; // = 0; + static uint8& m_nStatsPassedCasino10; // = 0; + static uint8& m_nStatsPassedCasino6; // = 0; + static uint8& m_nStatsPassedCasino3; // = 0; + static uint8& m_nStatsCitiesPassed; // = 0; + static uint8& m_nSpecialDJBanterIndex; // = -1; + static uint8& m_nSpecialDJBanterPending; // = 3; // ? public: static void InjectHooks(); + CAERadioTrackManager(int32 hwClientHandle); + + CAERadioTrackManager() = default; // NOTSA ~CAERadioTrackManager() = default; bool Initialise(int32 channelId); - void InitialiseRadioStationID(RadioStationId id); + void InitialiseRadioStationID(eRadioID id); void Reset(); static void ResetStatistics(); bool IsRadioOn() const; bool HasRadioRetuneJustStarted() const; - int8 GetCurrentRadioStationID() const; + eRadioID GetCurrentRadioStationID() const; int32* GetRadioStationListenTimes(); void SetRadioAutoRetuneOnOff(bool enable); void SetBassEnhanceOnOff(bool enable); void SetBassSetting(int8 nBassSet, float fBassGrain); - void RetuneRadio(int8 radioId); + void RetuneRadio(eRadioID radioId); void DisplayRadioStationName(); - const char* GetRadioStationName(RadioStationId id); - void GetRadioStationNameKey(RadioStationId id, char* outStr); + const char* GetRadioStationName(eRadioID id); + void GetRadioStationNameKey(eRadioID id, char* outStr); static bool IsVehicleRadioActive(); void StartTrackPlayback(); void UpdateRadioVolumes(); void PlayRadioAnnouncement(uint32); - void StartRadio(RadioStationId id, int8 bassValue, float bassGain, uint8 a5); + void StartRadio(eRadioID id, int8 bassValue, float bassGain, uint8 a5); void StartRadio(tVehicleAudioSettings* settings); void StopRadio(tVehicleAudioSettings* settings, bool bDuringPause); @@ -176,17 +241,17 @@ class CAERadioTrackManager { static void Save(); protected: - void AddMusicTrackIndexToHistory(RadioStationId id, int8 trackIndex); - void AddIdentIndexToHistory(RadioStationId id, int8 trackIndex); - void AddAdvertIndexToHistory(RadioStationId id, int8 trackIndex); - void AddDJBanterIndexToHistory(RadioStationId id, int8 trackIndex); - - void ChooseTracksForStation(RadioStationId id); - int32 ChooseIdentIndex(RadioStationId id); - int32 ChooseAdvertIndex(RadioStationId id); - int32 ChooseDJBanterIndex(RadioStationId id); - int32 ChooseDJBanterIndexFromList(RadioStationId id, int32** list); - int8 ChooseMusicTrackIndex(RadioStationId id); + void AddMusicTrackIndexToHistory(eRadioID id, int8 trackIndex); + void AddIdentIndexToHistory(eRadioID id, int8 trackIndex); + void AddAdvertIndexToHistory(eRadioID id, int8 trackIndex); + void AddDJBanterIndexToHistory(eRadioID id, int8 trackIndex); + + void ChooseTracksForStation(eRadioID id); + int32 ChooseIdentIndex(eRadioID id); + int32 ChooseAdvertIndex(eRadioID id); + int32 ChooseDJBanterIndex(eRadioID id); + int32 ChooseDJBanterIndexFromList(eRadioID id, int32** list); + int8 ChooseMusicTrackIndex(eRadioID id); static int8 ChooseTalkRadioShow(); void CheckForTrackConcatenation(); @@ -195,10 +260,9 @@ class CAERadioTrackManager { void CheckForStationRetuneDuringPause(); void CheckForPause(); - bool QueueUpTracksForStation(RadioStationId id, int8* iTrackCount, int8 radioState, tRadioSettings* settings); - bool TrackRadioStation(RadioStationId id, uint8 a2); + bool QueueUpTracksForStation(eRadioID id, int8* iTrackCount, int8 radioState, tRadioSettings& settings); + bool TrackRadioStation(eRadioID id, uint8 a2); }; - VALIDATE_SIZE(CAERadioTrackManager, 0x370); extern CAERadioTrackManager& AERadioTrackManager; diff --git a/source/game_sa/Audio/managers/AESoundManager.cpp b/source/game_sa/Audio/managers/AESoundManager.cpp index 052bafd30d..859979b1eb 100644 --- a/source/game_sa/Audio/managers/AESoundManager.cpp +++ b/source/game_sa/Audio/managers/AESoundManager.cpp @@ -22,10 +22,12 @@ void CAESoundManager::InjectHooks() { RH_ScopedInstall(AreSoundsOfThisEventPlayingForThisEntityAndPhysical, 0x4EF5D0); RH_ScopedInstall(GetVirtualChannelForPhysicalChannel, 0x4EF630); RH_ScopedInstall(CancelSoundsInBankSlot, 0x4EFC60); + RH_ScopedInstall(CancelSoundsOwnedByAudioEntity, 0x4EFCD0); RH_ScopedInstall(CancelSoundsOfThisEventPlayingForThisEntity, 0x4EFB90); RH_ScopedInstall(CancelSoundsOfThisEventPlayingForThisEntityAndPhysical, 0x4EFBF0); } +// 0x5B9690 bool CAESoundManager::Initialise() { const auto availChannels = AEAudioHardware.GetNumAvailableChannels(); if (availChannels <= 10) @@ -57,6 +59,7 @@ bool CAESoundManager::Initialise() { return true; } +// 0x4EFAA0 void CAESoundManager::Terminate() { delete[] m_aChannelSoundTable; delete[] m_aChannelSoundPlayTimes; @@ -67,6 +70,7 @@ void CAESoundManager::Terminate() { m_aChannelSoundUncancellable = nullptr; } +// 0x4EF4D0 void CAESoundManager::Reset() { for (CAESound& sound : m_aSounds) { if (!sound.IsUsed()) @@ -76,10 +80,12 @@ void CAESoundManager::Reset() { } } +// 0x4EF510 void CAESoundManager::PauseManually(uint8 bPause) { m_bManuallyPaused = bPause; } +// 0x4F0000 void CAESoundManager::Service() { // Clear sounds uncancellable status for this frame std::fill_n(m_aChannelSoundUncancellable, m_nNumAvailableChannels, -1); @@ -97,10 +103,7 @@ void CAESoundManager::Service() { m_nUpdateTime = CTimer::GetTimeInMSPauseMode(); m_bPauseTimeInUse = true; } else { - if (m_bPauseTimeInUse) - timeSinceLastUpdate = CTimer::GetTimeInMS() - m_nPauseUpdateTime; - else - timeSinceLastUpdate = CTimer::GetTimeInMS() - m_nUpdateTime; + timeSinceLastUpdate = CTimer::GetTimeInMS() - (m_bPauseTimeInUse ? m_nPauseUpdateTime : m_nUpdateTime); m_nUpdateTime = CTimer::GetTimeInMS(); m_bPauseTimeInUse = false; @@ -112,8 +115,7 @@ void CAESoundManager::Service() { AEAudioHardware.GetVirtualChannelSoundLoopStartTimes(m_aSoundLoopStartTimes); // Initialize sounds that are using percentage specified start positions 0x4F011C - for (auto i = 0; i < MAX_NUM_SOUNDS; ++i) { - auto& sound = m_aSounds[i]; + for (auto&& [i, sound] : notsa::enumerate(m_aSounds)) { if (!sound.IsUsed() || !sound.WasServiced() || !sound.GetStartPercentage()) continue; @@ -121,7 +123,8 @@ void CAESoundManager::Service() { if (sound.m_nHasStarted) continue; - sound.m_nCurrentPlayPosition *= uint16(static_cast(m_aSoundLengths[i]) / 100.0F); + //sound.m_nCurrentPlayPosition *= uint16(static_cast(m_aSoundLengths[i]) / 100.0F); + sound.m_nCurrentPlayPosition = static_cast((float)(sound.m_nCurrentPlayPosition * m_aSoundLengths[i]) / 100.0f); } // Stop sounds that turned inactive @@ -137,8 +140,7 @@ void CAESoundManager::Service() { } // Update sounds playtime - for (auto i = 0; i < MAX_NUM_SOUNDS; ++i) { - auto& sound = m_aSounds[i]; + for (auto&& [i, sound] : notsa::enumerate(m_aSounds)) { if (!sound.IsUsed() || !sound.WasServiced() || sound.m_nIgnoredServiceCycles) continue; @@ -179,8 +181,7 @@ void CAESoundManager::Service() { } // Mark some more songs as uncancellable under specific conditions - for (auto i = 0; i < MAX_NUM_SOUNDS; ++i) { - auto& sound = m_aSounds[i]; + for (auto&& [i, sound] : notsa::enumerate(m_aSounds)) { if (!sound.IsUsed() || (sound.m_nHasStarted && sound.GetUncancellable()) || sound.m_nIgnoredServiceCycles) continue; @@ -224,8 +225,7 @@ void CAESoundManager::Service() { } // Play sounds that require that - auto curSound = 0; - for (auto i = 0; i < m_nNumAvailableChannels; ++i) { + for (auto i = 0, curSound = 0; i < m_nNumAvailableChannels; ++i, ++curSound) { const auto uncancell = m_aChannelSoundUncancellable[i]; if (uncancell == -1) continue; @@ -244,26 +244,13 @@ void CAESoundManager::Service() { auto slomoFactor = sound.GetSlowMoFrequencyScalingFactor(); CAEAudioHardwarePlayFlags flags{}; - flags.m_nFlags = 0; - - flags.m_bIsFrontend = sound.GetFrontEnd(); - flags.m_bIsUncompressable = sound.GetUncompressable(); - flags.m_bIsUnduckable = sound.GetUnduckable(); - flags.m_bIsStartPercentage = sound.GetStartPercentage(); - flags.m_bIsMusicMastered = sound.GetMusicMastered(); - flags.m_bIsRolledOff = sound.GetRolledOff(); - flags.m_bIsSmoothDucking = sound.GetSmoothDucking(); - flags.m_bIsForcedFront = sound.GetForcedFront(); - flags.m_bUnpausable = flags.m_bIsFrontend ? sound.GetUnpausable() : false; + flags.CopyFromAESound(sound); AEAudioHardware.PlaySound(m_nChannel, curSound, sound.m_nSoundIdInSlot, sound.m_nBankSlotId, sound.m_nCurrentPlayPosition, flags.m_nFlags, sound.m_fSpeed); AEAudioHardware.SetChannelVolume(m_nChannel, curSound, sound.m_fFinalVolume, 0); - CVector vecPos; - sound.GetRelativePosition(&vecPos); - AEAudioHardware.SetChannelPosition(m_nChannel, curSound, &vecPos, 0); + AEAudioHardware.SetChannelPosition(m_nChannel, curSound, sound.GetRelativePosition(), 0); AEAudioHardware.SetChannelFrequencyScalingFactor(m_nChannel, curSound, freq * slomoFactor); - ++curSound; } for (auto i = 0; i < m_nNumAvailableChannels; ++i) { @@ -284,133 +271,148 @@ void CAESoundManager::Service() { AEAudioHardware.SetChannelVolume(m_nChannel, i, -100.0F, 0); AEAudioHardware.SetChannelFrequencyScalingFactor(m_nChannel, i, 0.0F); } - - CVector vecPos; - sound.GetRelativePosition(&vecPos); - AEAudioHardware.SetChannelPosition(m_nChannel, i, &vecPos, 0); + AEAudioHardware.SetChannelPosition(m_nChannel, i, sound.GetRelativePosition(), 0); } AEAudioHardware.Service(); + for (CAESound& sound : m_aSounds) { - if (!sound.IsUsed()) + if (!sound.IsUsed()) { continue; + } - sound.m_bWasServiced = 1; + sound.m_bWasServiced = true; if (sound.m_nIgnoredServiceCycles > 0 && !CAESoundManager::IsSoundPaused(sound)) --sound.m_nIgnoredServiceCycles; } } +// 0x4EFB10 CAESound* CAESoundManager::RequestNewSound(CAESound* pSound) { - int16 firstFree; - for (firstFree = 0; firstFree < MAX_NUM_SOUNDS; ++firstFree) { - if (!m_aSounds[firstFree].IsUsed()) - break; + size_t sidx; + auto s = GetFreeSound(&sidx); + if (s) { + *s = *pSound; + pSound->UnregisterWithPhysicalEntity(); + s->NewVPSLentry(); + AEAudioHardware.RequestVirtualChannelSoundInfo((uint16)sidx, s->m_nSoundIdInSlot, s->m_nBankSlotId); } - - if (firstFree >= MAX_NUM_SOUNDS) - return nullptr; - - auto& newSound = m_aSounds[firstFree]; - newSound = *pSound; - pSound->UnregisterWithPhysicalEntity(); - newSound.NewVPSLentry(); - AEAudioHardware.RequestVirtualChannelSoundInfo(firstFree, newSound.m_nSoundIdInSlot, newSound.m_nBankSlotId); - - return &newSound; + return s; } +// 0x4EF520 int16 CAESoundManager::AreSoundsPlayingInBankSlot(int16 bankSlot) { auto nPlaying = eSoundPlayingStatus::SOUND_NOT_PLAYING; for (CAESound& sound : m_aSounds) { - if (!sound.IsUsed() || sound.m_nBankSlotId != bankSlot) - continue; - - if (sound.m_nHasStarted) + if (!sound.IsUsed() || sound.m_nBankSlotId != bankSlot) { + continue; + } + if (sound.m_nHasStarted) { return eSoundPlayingStatus::SOUND_HAS_STARTED; - + } nPlaying = eSoundPlayingStatus::SOUND_PLAYING; } - return nPlaying; } +// 0x4EF570 int16 CAESoundManager::AreSoundsOfThisEventPlayingForThisEntity(int16 eventId, CAEAudioEntity* audioEntity) { auto nPlaying = eSoundPlayingStatus::SOUND_NOT_PLAYING; for (CAESound& sound : m_aSounds) { - if (!sound.IsUsed() || sound.m_nEvent != eventId || sound.m_pBaseAudio != audioEntity) + if (!sound.IsUsed() || sound.m_nEvent != eventId || sound.m_pBaseAudio != audioEntity) { continue; - - if (sound.m_nHasStarted) + } + if (sound.m_nHasStarted) { return eSoundPlayingStatus::SOUND_HAS_STARTED; - + } nPlaying = eSoundPlayingStatus::SOUND_PLAYING; } return nPlaying; } +// 0x4EF5D0 int16 CAESoundManager::AreSoundsOfThisEventPlayingForThisEntityAndPhysical(int16 eventId, CAEAudioEntity* audioEntity, CPhysical* physical) { bool nPlaying = eSoundPlayingStatus::SOUND_NOT_PLAYING; for (CAESound& sound : m_aSounds) { - if (!sound.IsUsed() || sound.m_nEvent != eventId || sound.m_pBaseAudio != audioEntity || sound.m_pPhysicalEntity != physical) + if (!sound.IsUsed() || sound.m_nEvent != eventId || sound.m_pBaseAudio != audioEntity || sound.m_pPhysicalEntity != physical) { continue; - - if (sound.m_nHasStarted) + } + if (sound.m_nHasStarted) { return eSoundPlayingStatus::SOUND_HAS_STARTED; - + } nPlaying = eSoundPlayingStatus::SOUND_PLAYING; } return nPlaying; } +// 0x4EFB90 void CAESoundManager::CancelSoundsOfThisEventPlayingForThisEntity(int16 eventId, CAEAudioEntity* audioEntity) { for (CAESound& sound : m_aSounds) { - if (!sound.IsUsed() || sound.m_nEvent != eventId || sound.m_pBaseAudio != audioEntity) + if (!sound.IsUsed() || sound.m_nEvent != eventId || sound.m_pBaseAudio != audioEntity) { continue; - + } sound.StopSoundAndForget(); } } +// 0x4EFBF0 void CAESoundManager::CancelSoundsOfThisEventPlayingForThisEntityAndPhysical(int16 eventId, CAEAudioEntity* audioEntity, CPhysical* physical) { for (CAESound& sound : m_aSounds) { - if (!sound.IsUsed() || sound.m_nEvent != eventId || sound.m_pBaseAudio != audioEntity || sound.m_pPhysicalEntity != physical) + if (!sound.IsUsed() || sound.m_nEvent != eventId || sound.m_pBaseAudio != audioEntity || sound.m_pPhysicalEntity != physical) { continue; - + } sound.StopSoundAndForget(); } } +// 0x4EFC60 void CAESoundManager::CancelSoundsInBankSlot(int16 bankSlot, bool bFullStop) { for (CAESound& sound : m_aSounds) { - if (!sound.IsUsed() || sound.m_nBankSlotId != bankSlot) + if (!sound.IsUsed() || sound.m_nBankSlotId != bankSlot) { continue; - - if (bFullStop) + } + if (bFullStop) { sound.StopSoundAndForget(); - else + } else { sound.StopSound(); + } } } +// 0x4EFCD0 void CAESoundManager::CancelSoundsOwnedByAudioEntity(CAEAudioEntity* audioEntity, bool bFullStop) { for (CAESound& sound : m_aSounds) { - if (!sound.IsUsed() || sound.m_pBaseAudio != audioEntity) + if (!sound.IsUsed() || sound.m_pBaseAudio != audioEntity) { continue; - - if (bFullStop) + } + if (bFullStop) { sound.StopSoundAndForget(); - else + } else { sound.StopSound(); + } } } -int16 CAESoundManager::GetVirtualChannelForPhysicalChannel(int16 physicalChannel) { - const auto channel = physicalChannel - m_nChannel; - if (channel < 0 || channel >= m_nNumAvailableChannels) +// 0x4EF630, unused +int16 CAESoundManager::GetVirtualChannelForPhysicalChannel(int16 physicalChannel) const { + const auto chIdx = physicalChannel - m_nChannel; + if (chIdx < 0 || chIdx >= m_nNumAvailableChannels) { return -1; + } + return m_aChannelSoundTable[chIdx]; +} - return m_aChannelSoundTable[channel]; +// NOTSA +CAESound* CAESoundManager::GetFreeSound(size_t* outIdx) { + for (auto&& [i, s] : notsa::enumerate(m_aSounds)) { + if (!s.IsUsed()) { + if (outIdx) { + *outIdx = (size_t)i; + } + return &s; + } + } + return nullptr; } diff --git a/source/game_sa/Audio/managers/AESoundManager.h b/source/game_sa/Audio/managers/AESoundManager.h index 7b7da7bd5f..b5c0dd8da4 100644 --- a/source/game_sa/Audio/managers/AESoundManager.h +++ b/source/game_sa/Audio/managers/AESoundManager.h @@ -47,7 +47,10 @@ class CAESoundManager { void CancelSoundsOfThisEventPlayingForThisEntityAndPhysical(int16 eventId, CAEAudioEntity* audioEntity, CPhysical* physical); void CancelSoundsInBankSlot(int16 bankSlot, bool bFullStop); void CancelSoundsOwnedByAudioEntity(CAEAudioEntity* audioEntity, bool bFullStop); - int16 GetVirtualChannelForPhysicalChannel(int16 physicalChannel); + int16 GetVirtualChannelForPhysicalChannel(int16 physicalChannel) const; + +private: + CAESound* GetFreeSound(size_t* outIdx); public: bool IsPaused() const { return CTimer::GetIsPaused() || m_bManuallyPaused; } diff --git a/source/game_sa/Audio/managers/AEUserRadioTrackManager.cpp b/source/game_sa/Audio/managers/AEUserRadioTrackManager.cpp index 81bc0e1906..6cf1bb7559 100644 --- a/source/game_sa/Audio/managers/AEUserRadioTrackManager.cpp +++ b/source/game_sa/Audio/managers/AEUserRadioTrackManager.cpp @@ -21,9 +21,7 @@ CAEUserRadioTrackManager& AEUserRadioTrackManager = *(CAEUserRadioTrackManager*)0xB6B970; -#if 0 -// This is the default list -static tAudioExtensionType defaultAudioExtensionTypes[] = { +static auto audioExtensionTypes = std::to_array({ {".ogg", AUDIO_FILE_TYPE_VORBIS}, {".mp3", AUDIO_FILE_TYPE_QUICKTIME}, // replaced to AUDIO_FILE_TYPE_WMA if QuickTime is not installed {".wav", AUDIO_FILE_TYPE_WAV}, @@ -31,24 +29,33 @@ static tAudioExtensionType defaultAudioExtensionTypes[] = { {".wmv", AUDIO_FILE_TYPE_WMA}, {".aac", AUDIO_FILE_TYPE_QUICKTIME}, {".m4a", AUDIO_FILE_TYPE_QUICKTIME}, -}; +#ifdef USERTRACK_FLAC_SUPPORT + {".flac", AUDIO_FILE_TYPE_FLAC}, #endif -tAudioExtensionType (&CAEUserRadioTrackManager::audioExtensionTypes)[7] = *reinterpret_cast(0x8cbb28); +}); // 0x4f35b0 bool CAEUserRadioTrackManager::Initialise() { m_bUserTracksLoaded = ReadUserTracks(); m_bUserTracksLoadedCopy = m_bUserTracksLoaded; - memset(m_baDecodersSupported, 0, sizeof(m_baDecodersSupported)); + rng::fill(m_baDecodersSupported, 0); + m_baDecodersSupported[AUDIO_FILE_TYPE_VORBIS] = true; m_baDecodersSupported[AUDIO_FILE_TYPE_WAV] = true; m_baDecodersSupported[AUDIO_FILE_TYPE_WMA] = CAEWMADecoder::InitLibrary(); m_baDecodersSupported[AUDIO_FILE_TYPE_QUICKTIME] = CAEMFDecoder::InitLibrary(); - if (m_baDecodersSupported[AUDIO_FILE_TYPE_QUICKTIME] == false) + if (!m_baDecodersSupported[AUDIO_FILE_TYPE_QUICKTIME]) { // change MP3 decoder from QuickTime to WMA - audioExtensionTypes[1].type = AUDIO_FILE_TYPE_WMA; + const auto typeMP3 = rng::find_if(audioExtensionTypes, [](const auto& type) { + return std::string_view{type.extension} == ".mp3"; + }); + if (typeMP3 != audioExtensionTypes.end()) { + typeMP3->type = AUDIO_FILE_TYPE_WMA; + NOTSA_LOG_DEBUG("Assigned MP3 decoder to be WMA because MediaFoundation decoder failed to load."); + } + } return true; } @@ -89,14 +96,14 @@ char* CAEUserRadioTrackManager::GetTrackPath(int32 trackID) const { // 0x4f35f0 CAEStreamingDecoder* CAEUserRadioTrackManager::LoadUserTrack(int32 trackID) { - if (m_bUserTracksLoaded == false) + if (!m_bUserTracksLoaded) return nullptr; if (trackID < 0 || trackID >= m_nUserTracksCount) return nullptr; tUserTracksInfo& targetOffset = m_pUserTracksInfo[trackID]; - if (m_baDecodersSupported[targetOffset.fileType] == false) + if (!m_baDecodersSupported[targetOffset.fileType]) return nullptr; char* filename = GetTrackPath(trackID); @@ -116,29 +123,25 @@ CAEStreamingDecoder* CAEUserRadioTrackManager::LoadUserTrack(int32 trackID) { bool dataStreamInitialized = dataStream->Initialise(); CFileMgr::SetDir(""); - if (dataStreamInitialized == false) { + if (!dataStreamInitialized) { delete dataStream; return nullptr; } switch (targetOffset.fileType) { case AUDIO_FILE_TYPE_UNKNOWN: - default: { - delete dataStream; - return nullptr; - } - case AUDIO_FILE_TYPE_VORBIS: { + case AUDIO_FILE_TYPE_VORBIS: decoder = new CAEVorbisDecoder(dataStream, true); break; - } - case AUDIO_FILE_TYPE_WAV: { + case AUDIO_FILE_TYPE_WAV: decoder = new CAEWaveDecoder(dataStream); break; - } - case AUDIO_FILE_TYPE_WMA: { + case AUDIO_FILE_TYPE_WMA: decoder = new CAEWMADecoder(dataStream); break; - } + default: + delete dataStream; + return nullptr; } } @@ -151,20 +154,21 @@ bool CAEUserRadioTrackManager::ReadUserTracks() { auto file = CFileMgr::OpenFile("sa-utrax.dat", "rb"); CFileMgr::SetDir(""); - if (file == nullptr) + if (!file) return false; - auto size = CFileMgr::GetTotalSize(file); + const auto size = CFileMgr::GetTotalSize(file); if (size == 0) { CFileMgr::CloseFile(file); return false; } - m_nUserTracksCount = size / sizeof(tUserTracksInfo); - + // NOTSA: Don't leak memory in case of multiple calls. if (m_pUserTracksInfo) { CMemoryMgr::Free(m_pUserTracksInfo); } + + m_nUserTracksCount = size / sizeof(tUserTracksInfo); m_pUserTracksInfo = (tUserTracksInfo*)CMemoryMgr::Malloc(size); CFileMgr::Read(file, m_pUserTracksInfo, size); @@ -195,8 +199,8 @@ bool CAEUserRadioTrackManager::ScanUserTracks() { void CAEUserRadioTrackManager::DeleteUserTracksInfo() { TerminateThread(m_hwndUserTracksScanThreadHandle, 0); CFileMgr::SetDirMyDocuments(); - remove("sa-ufiles.dat"); // todo: cross-platform - remove("sa-utrax.dat"); + fs::remove("sa-ufiles.dat"); + fs::remove("sa-utrax.dat"); CFileMgr::SetDir(""); m_nUserTracksScanState = USER_TRACK_SCAN_OFF; @@ -237,8 +241,7 @@ int32 CAEUserRadioTrackManager::SelectUserTrackIndex() const { // 0x4f31f0 eAudioFileType CAEUserRadioTrackManager::GetAudioFileType(const char* filename) { - constexpr size_t AUDIO_EXTENSIONS = sizeof(CAEUserRadioTrackManager::audioExtensionTypes) / sizeof(tAudioExtensionType); - const char* dotPosition = strrchr(filename, '.'); + const char* dotPosition = strrchr(filename, '.'); if (dotPosition == nullptr) return AUDIO_FILE_TYPE_UNKNOWN; @@ -268,41 +271,36 @@ DWORD __stdcall CAEUserRadioTrackManager::WriteUserTracksThread(CAEUserRadioTrac // Open sa-ufiles.dat CFileMgr::SetDirMyDocuments(); - auto file = CFileMgr::OpenFile("sa-ufiles.dat", "wb"); - if (file == nullptr) { - self->m_nUserTracksScanState = USER_TRACK_SCAN_ERROR; - } else { - // Create path to "User Tracks" - size_t documentsDirLen = strlen(CFileMgr::ms_dirName), dummy = 0; - size_t userTracksDirSize = documentsDirLen + 15; - char* userTracksDir = new char[userTracksDirSize]; + if (const auto ufiles = CFileMgr::OpenFile("sa-ufiles.dat", "wb")) { + size_t dummy{}; std::vector offsets; - // Concat - strcpy_s(userTracksDir, userTracksDirSize, CFileMgr::ms_dirName); - strcat_s(userTracksDir, userTracksDirSize, "\\User Tracks\\"); - // Retrieve all user tracks info - int32 amountOfTracks = self->WriteUserTracksFile(userTracksDir, dummy, file, offsets, 0); - CFileMgr::CloseFile(file); + int32 amountOfTracks = self->WriteUserTracksFile(fs::path(CFileMgr::ms_dirName) / "User Tracks", dummy, ufiles, offsets, 0); + CFileMgr::CloseFile(ufiles); // Open sa-utrax.dat containing the offsets - file = CFileMgr::OpenFile("sa-utrax.dat", "wb"); - - // todo: FIX_BUGS - // MikuAuahDark: GTASA doesn't check if sa-utrax.dat fails to open - if (file == nullptr) { - self->m_nUserTracksScanState = USER_TRACK_SCAN_ERROR; - } else { + if (const auto utrax = CFileMgr::OpenFile("sa-utrax.dat", "wb")) { if (amountOfTracks > 0) - CFileMgr::Write(file, offsets.data(), amountOfTracks * sizeof(tUserTracksInfo)); + CFileMgr::Write(utrax, offsets.data(), amountOfTracks * sizeof(tUserTracksInfo)); - CFileMgr::CloseFile(file); + // SA: Game duplicates the entry if there is only one? + if (amountOfTracks == 1) + CFileMgr::Write(utrax, offsets.data(), sizeof(tUserTracksInfo)); + + CFileMgr::CloseFile(utrax); FrontEndMenuManager.m_nUserTrackIndex = 0; self->m_nUserTracksScanState = USER_TRACK_SCAN_COMPLETE; + + CoUninitialize(); + } else { + // NOTSA(MikuAuahDark): GTASA doesn't check if sa-utrax.dat fails to open + self->m_nUserTracksScanState = USER_TRACK_SCAN_ERROR; } + } else { + self->m_nUserTracksScanState = USER_TRACK_SCAN_ERROR; } // Read user tracks back @@ -322,62 +320,46 @@ int32 CAEUserRadioTrackManager::WriteUserTracksFile(const char* dir, size_t& cur int32 CAEUserRadioTrackManager::WriteUserTracksFile(const std::wstring& dir, size_t& currentLength, auto file, std::vector& offsets, int32 depth) { // Limit folder scan to 16 folders deep - if (depth >= 15) + if (depth >= 15 || !fs::exists(dir)) return 0; - int32 amountOfTracks = 0; - - // Copy search directory - std::wstring dirSearch = dir + L"\\*.*"; - - WIN32_FIND_DATAW findData; - HANDLE findHandle = FindFirstFileW(dirSearch.c_str(), &findData); - - if (findHandle != INVALID_HANDLE_VALUE) { - do { - if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if (wcscmp(findData.cFileName, L".") && wcscmp(findData.cFileName, L"..")) - // Nested scan - amountOfTracks += WriteUserTracksFile(dir + findData.cFileName + L"\\", currentLength, file, offsets, depth + 1); - } else { - size_t filenameLength = wcslen(findData.cFileName); - std::wstring path = dir + L"\\" + findData.cFileName; - - // If the file is a shortcut, then resolve the target - const wchar_t* extension = wcsrchr(findData.cFileName, L'.'); - if (path.rfind(L".lnk") == path.length() - 4) - path = ResolveShortcut(path); - - // "path" now points to target file (if it's a shortcut) - // or the actual file (if it's regular file) - DWORD fileAttr; - if ((fileAttr = GetFileAttributesW(path.c_str())) != INVALID_FILE_ATTRIBUTES) { - // If path pointed by the shortcut is a directory - // then perform 15 deep scan on those folders too. - // MikuAuahDark: Note that GTASA code actually do this. My minor - // improvement is only to ensure it uses UTF-8 chars by operating - // on wide char. - if (fileAttr & FILE_ATTRIBUTE_DIRECTORY) - amountOfTracks += WriteUserTracksFile(path + L"\\", currentLength, file, offsets, 1); - else { - std::string pathChar = UnicodeToUTF8(path); - eAudioFileType fileType = GetAudioFileType(pathChar.c_str()); - - if (fileType != AUDIO_FILE_TYPE_UNKNOWN) { - size_t pathLen = pathChar.length(); - CFileMgr::Write(file, pathChar.c_str(), pathLen); - offsets.push_back({currentLength, pathLen, fileType}); - currentLength += pathLen; - amountOfTracks++; - } - } - } - } - } while (FindNextFileW(findHandle, &findData)); + int32 numTracks{}; + for (auto& entry : fs::directory_iterator(dir)) { + if (fs::is_directory(entry)) { + // NOTE: STD can also do recursive search, maybe use that? + numTracks += WriteUserTracksFile(entry.path(), currentLength, file, offsets, depth + 1); + continue; + } + + auto path = entry.path(); +#ifdef WIN32 + if (path.extension() == ".lnk") + path = ResolveShortcut(path.wstring()); +#endif + + // TODO: symlink + if (!fs::is_regular_file(path)) + continue; + + // if shortcut was a directory. + if (fs::is_directory(path)) { + numTracks += WriteUserTracksFile(path, currentLength, file, offsets, depth + 1); + continue; + } + + // NOTSA: Use UTF-8 generic path (POSIX like) instead of Windows paths. + const auto pathStr = UnicodeToUTF8(path.generic_wstring()); + const auto fileType = GetAudioFileType(pathStr.c_str()); + if (fileType != AUDIO_FILE_TYPE_UNKNOWN) { + size_t pathLen = pathStr.length(); + CFileMgr::Write(file, pathStr.c_str(), pathLen); + offsets.push_back({currentLength, pathLen, fileType}); + currentLength += pathLen; + numTracks++; + } } - FindClose(findHandle); - return amountOfTracks; + return numTracks; } // todo: FIX_BUGS @@ -391,21 +373,22 @@ char* CAEUserRadioTrackManager::ResolveShortcut(const char* path) { } std::wstring CAEUserRadioTrackManager::ResolveShortcut(const std::wstring& path) { - IShellLinkW* shellLink = nullptr; - IPersistFile* persistFile = nullptr; + IShellLinkW* shellLink{}; + IPersistFile* persistFile{}; if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (void**)&shellLink))) { - assert(true && "CoCreateInstance failed"); + NOTSA_UNREACHABLE("CoCreateInstance(CLSID_ShellLink) failed"); } if (FAILED(shellLink->QueryInterface(IID_IPersistFile, (void**)&persistFile))) { + NOTSA_LOG_ERR("QueryInterface(IID_IPersistFile) failed."); shellLink->Release(); - return std::wstring(); + return {}; } - wchar_t* target = new wchar_t[MAX_PATH]; + std::wstring target(MAX_PATH, L'\0'); WIN32_FIND_DATAW findData{}; - if (FAILED(persistFile->Load(path.c_str(), STGM_READ)) || FAILED(shellLink->GetPath(target, MAX_PATH, &findData, 0))) { + if (FAILED(persistFile->Load(path.c_str(), STGM_READ)) || FAILED(shellLink->GetPath(target.data(), MAX_PATH, &findData, 0))) { persistFile->Release(); shellLink->Release(); NOTSA_UNREACHABLE("Load or GetPath failed"); @@ -414,9 +397,7 @@ std::wstring CAEUserRadioTrackManager::ResolveShortcut(const std::wstring& path) persistFile->Release(); shellLink->Release(); - std::wstring out = target; - delete[] target; - return out; + return target; } void CAEUserRadioTrackManager::InjectHooks() { @@ -435,7 +416,7 @@ void CAEUserRadioTrackManager::InjectHooks() { RH_ScopedInstall(LoadUserTrack, 0x4f35f0); RH_ScopedInstall(GetUserTrackPlayMode, 0x4f3330); RH_ScopedInstall(SetUserTrackIndex, 0x4f3340); - RH_ScopedOverloadedInstall(WriteUserTracksFile, "", 0x4f4690, int32(CAEUserRadioTrackManager::*)(const char*, size_t&, FILE*, std::vector&, int32)); - RH_ScopedInstall(WriteUserTracksThread, 0x4f4a20); + RH_ScopedOverloadedInstall(WriteUserTracksFile, "", 0x4f4690, int32(CAEUserRadioTrackManager::*)(const char*, size_t&, FILE*, std::vector&, int32), {.locked=true}); + RH_ScopedInstall(WriteUserTracksThread, 0x4f4a20, {.locked=true}); RH_ScopedInstall(ScanUserTracks, 0x4f4ba0); } diff --git a/source/game_sa/Audio/managers/AEUserRadioTrackManager.h b/source/game_sa/Audio/managers/AEUserRadioTrackManager.h index 0ac9f5290c..c6e5dd3e50 100644 --- a/source/game_sa/Audio/managers/AEUserRadioTrackManager.h +++ b/source/game_sa/Audio/managers/AEUserRadioTrackManager.h @@ -49,10 +49,8 @@ class CAEUserRadioTrackManager { uint8 GetUserTrackPlayMode(); private: - static tAudioExtensionType (&audioExtensionTypes)[7]; - static DWORD __stdcall WriteUserTracksThread(CAEUserRadioTrackManager* self); - int32 WriteUserTracksFile(const char* dir, size_t& currentLength, auto file, std::vector& offsets, int32 depth); + int32 WriteUserTracksFile(const char* dir, size_t& currentLength, auto file, std::vector& offsets, int32 depth); char* ResolveShortcut(const char* path); // Private functions which aren't part of GTASA itself @@ -67,4 +65,4 @@ class CAEUserRadioTrackManager { VALIDATE_SIZE(CAEUserRadioTrackManager, 0x1c); -extern CAEUserRadioTrackManager& AEUserRadioTrackManager; \ No newline at end of file +extern CAEUserRadioTrackManager& AEUserRadioTrackManager; diff --git a/source/game_sa/Birds.cpp b/source/game_sa/Birds.cpp index d3661aea76..87ddbf3d55 100644 --- a/source/game_sa/Birds.cpp +++ b/source/game_sa/Birds.cpp @@ -38,6 +38,8 @@ void CBirds::InjectHooks() { // 0x711EC0 void CBirds::Init() { + ZoneScoped; + for (auto& bird : aBirds) { bird.m_bCreated = false; } @@ -142,6 +144,8 @@ void CBirds::Shutdown() { // 0x712330 void CBirds::Update() { + ZoneScoped; + const auto& vecCamPos = TheCamera.GetPosition(); if (!CGame::currArea @@ -249,6 +253,8 @@ void CBirds::Update() { // 0x712810 void CBirds::Render() { + ZoneScoped; + if (uiNumberOfBirds == 0) return; diff --git a/source/game_sa/BoneNode_c.h b/source/game_sa/BoneNode_c.h index eb649864b8..a7f059f33d 100644 --- a/source/game_sa/BoneNode_c.h +++ b/source/game_sa/BoneNode_c.h @@ -8,7 +8,7 @@ #include #include -class BoneNode_c : public ListItem_c { +class BoneNode_c : public ListItem_c { public: ePedBones m_BoneTag; RpHAnimBlendInterpFrame* m_InterpFrame; diff --git a/source/game_sa/BreakManager_c.cpp b/source/game_sa/BreakManager_c.cpp index 8d8f70d7a1..78bd47cbcf 100644 --- a/source/game_sa/BreakManager_c.cpp +++ b/source/game_sa/BreakManager_c.cpp @@ -53,6 +53,8 @@ BreakObject_c* BreakManager_c::GetFirstFreeSlot() { // 0x59E670 void BreakManager_c::Update(float timeStep) { + ZoneScoped; + for (BreakObject_c& object : m_aObjects) { if (object.m_bActive) { object.Update(timeStep); diff --git a/source/game_sa/Bridge.cpp b/source/game_sa/Bridge.cpp index d8537e7916..734201688d 100644 --- a/source/game_sa/Bridge.cpp +++ b/source/game_sa/Bridge.cpp @@ -19,6 +19,8 @@ void CBridge::Init() { // 0x41BC80 void CBridge::Update() { + ZoneScoped; + // NOP } diff --git a/source/game_sa/BrightLights.cpp b/source/game_sa/BrightLights.cpp index 7d23f90b86..06e6a364bb 100644 --- a/source/game_sa/BrightLights.cpp +++ b/source/game_sa/BrightLights.cpp @@ -36,6 +36,8 @@ void CBrightLights::RenderOutGeometryBuffer() { // 0x7241C0 void CBrightLights::Render() { + ZoneScoped; + if (NumBrightLights == 0) return; diff --git a/source/game_sa/BulletTraces.cpp b/source/game_sa/BulletTraces.cpp index dc76ddc898..4c6358d7a0 100644 --- a/source/game_sa/BulletTraces.cpp +++ b/source/game_sa/BulletTraces.cpp @@ -11,8 +11,8 @@ void CBulletTraces::InjectHooks() RH_ScopedCategoryGlobal(); RH_ScopedInstall(Init, 0x721D50); - RH_ScopedOverloadedInstall(AddTrace, "", 0x723750, void(*)(CVector*, CVector*, float, uint32, uint8)); - RH_ScopedOverloadedInstall(AddTrace, "Wrapper", 0x726AF0, void(*)(CVector*, CVector*, eWeaponType, CEntity*)); + RH_ScopedOverloadedInstall(AddTrace, "", 0x723750, void(*)(const CVector&, const CVector&, float, uint32, uint8)); + RH_ScopedOverloadedInstall(AddTrace, "Wrapper", 0x726AF0, void(*)(const CVector&, const CVector&, eWeaponType, CEntity*)); RH_ScopedInstall(Render, 0x723C10); RH_ScopedInstall(Update, 0x723FB0); } @@ -99,11 +99,11 @@ void PlayFrontEndSoundForTrace(CVector fromWorldSpace, CVector toWorldSpace) { } // 0x723750 -void CBulletTraces::AddTrace(CVector* from, CVector* to, float radius, uint32 disappearTime, uint8 alpha) +void CBulletTraces::AddTrace(const CVector& from, const CVector& to, float radius, uint32 disappearTime, uint8 alpha) { if (CBulletTrace* pTrace = GetFree()) { - pTrace->m_vecStart = *from; - pTrace->m_vecEnd = *to; + pTrace->m_vecStart = from; + pTrace->m_vecEnd = to; pTrace->m_nCreationTime = CTimer::GetTimeInMS(); pTrace->m_nTransparency = alpha; pTrace->m_bExists = true; @@ -118,11 +118,11 @@ void CBulletTraces::AddTrace(CVector* from, CVector* to, float radius, uint32 di pTrace->m_nLifeTime = (uint32)(disappearTime / 4.0f); } } - PlayFrontEndSoundForTrace(*from, *to); + PlayFrontEndSoundForTrace(from, to); } // 0x726AF0 -void CBulletTraces::AddTrace(CVector* posMuzzle, CVector* posBulletHit, eWeaponType weaponType, CEntity* fromEntity) +void CBulletTraces::AddTrace(const CVector& posMuzzle, const CVector& posBulletHit, eWeaponType weaponType, CEntity* fromEntity) { if (FindPlayerPed() == fromEntity || FindPlayerVehicle() && FindPlayerVehicle() == fromEntity) { switch (CCamera::GetActiveCamera().m_nMode) { @@ -143,14 +143,14 @@ void CBulletTraces::AddTrace(CVector* posMuzzle, CVector* posBulletHit, eWeaponT } } - CVector dir = *posBulletHit - *posMuzzle; + CVector dir = posBulletHit - posMuzzle; const float traceLengthOriginal = dir.Magnitude(); dir.Normalise(); const float traceLengthNew = CGeneral::GetRandomNumberInRange(0.0f, traceLengthOriginal); const float fRadius = std::min(CGeneral::GetRandomNumberInRange(2.0f, 5.0f), traceLengthOriginal - traceLengthNew); - CVector from = *posMuzzle + dir * traceLengthNew; + CVector from = posMuzzle + dir * traceLengthNew; CVector to = from + dir * fRadius; AddTrace( @@ -163,8 +163,9 @@ void CBulletTraces::AddTrace(CVector* posMuzzle, CVector* posBulletHit, eWeaponT } // 0x723C10 -void CBulletTraces::Render() -{ +void CBulletTraces::Render() { + ZoneScoped; + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(RwBlendFunction::rwBLENDSRCALPHA)); RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(RwBlendFunction::rwBLENDINVSRCALPHA)); diff --git a/source/game_sa/BulletTraces.h b/source/game_sa/BulletTraces.h index 1afdc3d00b..ba34b9af3f 100644 --- a/source/game_sa/BulletTraces.h +++ b/source/game_sa/BulletTraces.h @@ -15,8 +15,8 @@ class CBulletTraces { static void Init(); static void Update(); - static void AddTrace(CVector* from, CVector* to, float radius, uint32 disappearTime, uint8 alpha); - static void AddTrace(CVector* from, CVector* to, eWeaponType weaponType, CEntity* fromEntity); + static void AddTrace(const CVector& from, const CVector& to, float radius, uint32 disappearTime, uint8 alpha); + static void AddTrace(const CVector& from, const CVector& to, eWeaponType weaponType, CEntity* fromEntity); static void Render(); static CBulletTrace* GetFree(); // Inlined function (Kinda NOTSA) diff --git a/source/game_sa/C_PcSave.cpp b/source/game_sa/C_PcSave.cpp index 17de022382..0dcef36350 100644 --- a/source/game_sa/C_PcSave.cpp +++ b/source/game_sa/C_PcSave.cpp @@ -99,14 +99,5 @@ bool C_PcSave::DeleteSlot(int32 slot) { char path[MAX_PATH]{}; s_PcSaveHelper.error = eErrorCode::NONE; GenerateGameFilename(slot, path); -#ifdef DEFAULT_FUNCTIONS - DeleteFile(path); - if (auto f = CFileMgr::OpenFile(path, "rb")) { - CFileMgr::CloseFile(f); - return true; - } - return false; -#else return std::filesystem::remove(path); -#endif } diff --git a/source/game_sa/Cam.cpp b/source/game_sa/Cam.cpp index 34b2733d25..fff43bd7f5 100644 --- a/source/game_sa/Cam.cpp +++ b/source/game_sa/Cam.cpp @@ -8,8 +8,8 @@ void CCam::InjectHooks() { RH_ScopedClass(CCam); RH_ScopedCategory("Camera"); - RH_ScopedInstall(Constructor, 0x517730, { .reversed = false }); - RH_ScopedInstall(Init, 0x50E490, { .reversed = false }); + RH_ScopedInstall(Constructor, 0x517730); + RH_ScopedInstall(Init, 0x50E490); RH_ScopedInstall(CacheLastSettingsDWCineyCam, 0x50D7A0, { .reversed = false }); RH_ScopedInstall(DoCamBump, 0x50CB30); RH_ScopedInstall(Finalise_DW_CineyCams, 0x50DD70, { .reversed = false }); @@ -65,7 +65,70 @@ CCam* CCam::Constructor() { // 0x50E490 void CCam::Init() { - plugin::CallMethod<0x50E490, CCam*>(this); + m_vecFront = CVector(0, 0, -1); + m_vecUp = CVector(0, 0, 1); + m_nMode = eCamMode::MODE_FOLLOWPED; + m_bRotating = false; + m_nDoCollisionChecksOnFrameNum = 1; + m_nDoCollisionCheckEveryNumOfFrames = 9; + m_nFrameNumWereAt = 0; + m_bCollisionChecksOn = true; + m_fRealGroundDist = 0.0f; + m_fBetaSpeed = 0.0f; + m_fAlphaSpeed = 0.0f; + m_fCameraHeightMultiplier = 0.75; + m_fMaxRoleAngle = DegreesToRadians(20.0f); + m_fDistance = 30.0f; + m_fDistanceSpeed = 0.0f; + m_pLastCarEntered = nullptr; + m_pLastPedLookedAt = nullptr; + m_bResetStatics = true; + m_fHorizontalAngle = 0.0f; + m_fTilt = 0.0f; + m_fTiltSpeed = 0.0f; + m_bFixingBeta = false; + m_fCaMinDistance = 0.0f; + m_fCaMaxDistance = 0.0f; + m_bLookingBehind = false; + m_bLookingLeft = false; + m_bLookingRight = false; + m_fPlayerInFrontSyphonAngleOffSet = DegreesToRadians(20.0f); + m_fSyphonModeTargetZOffSet = 0.5f; + m_fRadiusForDead = 1.5f; + m_nDirectionWasLooking = 3; // TODO: enum + m_bLookBehindCamWasInFront = 0; + m_fRoll = 0.0f; + m_fRollSpeed = 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.0; + m_fBeta_Targeting = 0.0f; + m_fX_Targetting = 0.0f; + m_fY_Targetting = 0.0f; + m_pCarWeAreFocussingOn = nullptr; + m_pCarWeAreFocussingOnI = nullptr; + m_fCamBumpedHorz = 1.0f; + m_fCamBumpedVert = 0.0f; + m_nCamBumpedTime = 0; + for (int i = 0; i < 4; ++i) { + m_anTargetHistoryTime[i] = 0; + m_avecTargetHistoryPos[i] = CVector{}; + } + m_nCurrentHistoryPoints = 0; + gPlayerPedVisible = true; + gbCineyCamMessageDisplayed = 2; // TODO: enum + gCameraDirection = 3; // TODO: enum + gCameraMode = (eCamMode)-1; + gLastTime2PlayerCameraWasOK = 0; + gLastTime2PlayerCameraCollided = 0; + TheCamera.m_bCinemaCamera = false; } // 0x50D7A0 diff --git a/source/game_sa/Cam.h b/source/game_sa/Cam.h index 8b4578574b..8adac50837 100644 --- a/source/game_sa/Cam.h +++ b/source/game_sa/Cam.h @@ -102,7 +102,7 @@ class CCam { CVehicle* m_pCarWeAreFocussingOnI; float m_fCamBumpedHorz; float m_fCamBumpedVert; - uint32 m_nCamBumpedTime; + uint32 m_nCamBumpedTime; // TODO: Probably float CVector m_vecSourceSpeedOverOneFrame; CVector m_vecTargetSpeedOverOneFrame; CVector m_vecUpOverOneFrame; diff --git a/source/game_sa/Camera.cpp b/source/game_sa/Camera.cpp index 214e9d5f5b..28af148a0e 100644 --- a/source/game_sa/Camera.cpp +++ b/source/game_sa/Camera.cpp @@ -17,6 +17,10 @@ bool& CCamera::bDidWeProcessAnyCinemaCam = *reinterpret_cast(0xB6EC2D); CCamera& TheCamera = *reinterpret_cast(0xB6F028); bool& gbModelViewer = *reinterpret_cast(0xBA6728); int8& gbCineyCamMessageDisplayed = *(int8*)0x8CC381; // 2 +int32& gCameraDirection = *(int32*)0x8CC384; // 3 +eCamMode& gCameraMode = *(eCamMode*)0x8CC388; // -1 +uint32& gLastTime2PlayerCameraWasOK = *(uint32*)0xB6EC24; // 0 +uint32& gLastTime2PlayerCameraCollided = *(uint32*)0xB6EC28; // 0 bool& gPlayerPedVisible = *(bool*)0x8CC380; // true uint8& gCurCamColVars = *(uint8*)0x8CCB80; float& gCurDistForCam = *(float*)0x8CCB84; @@ -551,6 +555,8 @@ void CCamera::DealWithMirrorBeforeConstructRenderList(bool bActiveMirror, CVecto /// III/VC leftover // 0x50B8F0 void CCamera::RenderMotionBlur() { + ZoneScoped; + if (m_nBlurType) { // CMBlur::MotionBlurRender(); // todo: Add CMBlur::MotionBlurRender is NOP, 0x71D700 } @@ -1293,11 +1299,11 @@ void CCamera::AddShake(float duration, float a2, float a3, float a4, float a5) { } // 0x50D240 -void CCamera::AddShakeSimple(float duration, int32 type, float intensity) { +void CCamera::AddShakeSimple(float durationMs, int32 type, float intensity) { m_fShakeIntensity = intensity; m_nShakeType = type; m_fStartShakeTime = static_cast(CTimer::GetTimeInMS()); - m_fEndShakeTime = m_fStartShakeTime + duration; + m_fEndShakeTime = m_fStartShakeTime + durationMs; } // 0x50D280 @@ -1312,6 +1318,8 @@ void CCamera::LerpFOV(float zoomInFactor, float zoomOutFactor, float timeLimit, // 0x50B5D0 void CCamera::ProcessFade() { + ZoneScoped; + if (!m_bFading) { return; } @@ -1494,6 +1502,8 @@ void CCamera::ProcessScriptedCommands() { // 0x52B730 void CCamera::Process() { + ZoneScoped; + plugin::CallMethod<0x52B730, CCamera*>(this); } diff --git a/source/game_sa/Camera.h b/source/game_sa/Camera.h index 93591f14d8..3debcccb22 100644 --- a/source/game_sa/Camera.h +++ b/source/game_sa/Camera.h @@ -34,6 +34,12 @@ enum class eSwitchType : uint16 { JUMPCUT }; +/* todo: + LOOKING_BEHIND = 0x0, + LOOKING_LEFT = 0x1, + LOOKING_RIGHT = 0x2, + LOOKING_FORWARD = 0x3, +*/ enum eLookingDirection { LOOKING_DIRECTION_UNKNOWN_1 = 0, LOOKING_DIRECTION_BEHIND = 1, @@ -285,7 +291,7 @@ class CCamera : public CPlaceable { CVector m_vecTrackLinear{}; bool m_bVecTrackLinearProcessed{}; float m_fShakeIntensity{}; - float m_fStartShakeTime{}; + float m_fStartShakeTime{}; ///< In MS [Obtained from `CTimer::GetTimeInMS()`] float m_fEndShakeTime{}; int32 field_C9C{}; int32 m_nShakeType{}; @@ -516,6 +522,10 @@ extern bool& gbModelViewer; extern int8& gbCineyCamMessageDisplayed; extern bool& gPlayerPedVisible; extern uint8& gCurCamColVars; +extern int32& gCameraDirection; +extern eCamMode& gCameraMode; +extern uint32& gLastTime2PlayerCameraWasOK; +extern uint32& gLastTime2PlayerCameraCollided; extern float*& gpCamColVars; extern float (&gCamColVars)[28][6]; static inline auto& gpMadeInvisibleEntities = StaticRef, 0x9655A0>(); diff --git a/source/game_sa/CarCtrl.cpp b/source/game_sa/CarCtrl.cpp index a4a707f72a..9b5ebd624d 100644 --- a/source/game_sa/CarCtrl.cpp +++ b/source/game_sa/CarCtrl.cpp @@ -64,6 +64,8 @@ void CCarCtrl::InjectHooks() // 0x4212E0 void CCarCtrl::Init() { + ZoneScoped; + CarDensityMultiplier = 1.0f; NumRandomCars = 0; NumLawEnforcerCars = 0; @@ -377,6 +379,8 @@ void CCarCtrl::GenerateOneRandomCar() { // 0x4341C0 void CCarCtrl::GenerateRandomCars() { + ZoneScoped; + plugin::Call<0x4341C0>(); } @@ -604,6 +608,8 @@ void CCarCtrl::PossiblyRemoveVehicle(CVehicle* vehicle) { // 0x423F10 void CCarCtrl::PruneVehiclesOfInterest() { + ZoneScoped; + if ((CTimer::GetFrameCounter() % 64) == 19 && FindPlayerCoors(-1).z < 950.0f) { for (size_t i = 0; i < std::size(apCarsToKeep); i++) { if (apCarsToKeep[i]) { @@ -627,6 +633,8 @@ void CCarCtrl::RegisterVehicleOfInterest(CVehicle* vehicle) { // 0x4322B0 void CCarCtrl::RemoveCarsIfThePoolGetsFull() { + ZoneScoped; + if (CTimer::GetFrameCounter() % 8 != 3) return; @@ -663,6 +671,8 @@ void CCarCtrl::RemoveCarsIfThePoolGetsFull() { // 0x42CD10 void CCarCtrl::RemoveDistantCars() { + ZoneScoped; + for (auto i = 0; i < GetVehiclePool()->GetSize(); i++) { if (auto vehicle = GetVehiclePool()->GetAt(i)) { PossiblyRemoveVehicle(vehicle); diff --git a/source/game_sa/CdStreamInfo.cpp b/source/game_sa/CdStreamInfo.cpp index 1ba26a633e..96adb5dbad 100644 --- a/source/game_sa/CdStreamInfo.cpp +++ b/source/game_sa/CdStreamInfo.cpp @@ -46,7 +46,7 @@ class CLockGuard { static CSync cdStreamThreadSync; #endif - +#include "AEBankLoader.h" void InjectCdStreamHooks() { RH_ScopedNamespaceName("CdStream"); RH_ScopedCategoryGlobal(); @@ -64,6 +64,7 @@ void InjectCdStreamHooks() { // 0x4067B0 int32 CdStreamOpen(const char* lpFileName) { + NOTSA_LOG_DEBUG("CdStreamOpen: {}", lpFileName); int32 freeHandleIndex = 0; for (; freeHandleIndex < MAX_CD_STREAM_HANDLES; freeHandleIndex++) { if (!gStreamFileHandles[freeHandleIndex]) @@ -184,8 +185,15 @@ bool CdStreamRead(int32 streamId, void* lpBuffer, uint32 offsetAndHandle, int32 // 0x406560 [[noreturn]] void WINAPI CdStreamThread(LPVOID lpParam) { +#ifdef TRACY_ENABLE + tracy::SetThreadName("CdStreamThread"); +#endif + while (true) { WaitForSingleObject(gStreamSemaphore, INFINITE); + + ZoneScoped; + const int32 streamId = GetFirstInQueue(&gStreamQueue); CdStream& stream = gCdStreams[streamId]; stream.bInUse = true; @@ -214,6 +222,7 @@ bool CdStreamRead(int32 streamId, void* lpBuffer, uint32 offsetAndHandle, int32 stream.status = eCdStreamStatus::READING_FAILURE; } } + RemoveFirstInQueue(&gStreamQueue); #ifdef APPLY_CD_STREAM_DEADLOCK_FIX CLockGuard lockGuard(cdStreamThreadSync); diff --git a/source/game_sa/Cheat.cpp b/source/game_sa/Cheat.cpp index d1ff1192b3..3376031cdf 100644 --- a/source/game_sa/Cheat.cpp +++ b/source/game_sa/Cheat.cpp @@ -223,6 +223,8 @@ void CCheat::ApplyCheat(eCheats cheat) { // 0x438450 void CCheat::ResetCheats() { + ZoneScoped; + memset(&m_aCheatsActive, 0, sizeof(m_aCheatsActive)); CWeather::ReleaseWeather(); CTimer::ResetTimeScale(); @@ -232,6 +234,8 @@ void CCheat::ResetCheats() { // 0x439AF0 void CCheat::DoCheats() { + ZoneScoped; + for (auto key = 0; key < 256; ++key) { if (CPad::GetPad(0)->IsStandardKeyJustPressed(key)) { AddToCheatString(key); @@ -352,7 +356,7 @@ void CCheat::DrivebyCheat() { Toggle(CHEAT_WEAPON_AIMING_WHILE_DRIVING); CPlayerPed *player = FindPlayerPed(); - if (IsActive(CHEAT_WEAPON_AIMING_WHILE_DRIVING) && player->m_aWeapons[WEAPON_KNIFE].m_nType == WEAPON_UNARMED) { + if (IsActive(CHEAT_WEAPON_AIMING_WHILE_DRIVING) && player->m_aWeapons[WEAPON_KNIFE].m_Type == WEAPON_UNARMED) { player->GiveDelayedWeapon(WEAPON_MICRO_UZI, 150); player->SetCurrentWeapon(WEAPON_MICRO_UZI); } diff --git a/source/game_sa/Checkpoints.cpp b/source/game_sa/Checkpoints.cpp index 3cb291dc5c..721a5d2b95 100644 --- a/source/game_sa/Checkpoints.cpp +++ b/source/game_sa/Checkpoints.cpp @@ -91,6 +91,8 @@ void CCheckpoints::DeleteCP(uint32 id, uint16 type) { // 0x726060 void CCheckpoints::Render() { + ZoneScoped; + for (auto& checkpoint : m_aCheckPtArray) { if (checkpoint.m_bIsUsed) { checkpoint.Render(); diff --git a/source/game_sa/Clock.cpp b/source/game_sa/Clock.cpp index 6c2f5923d4..27199bc2e4 100644 --- a/source/game_sa/Clock.cpp +++ b/source/game_sa/Clock.cpp @@ -58,6 +58,8 @@ void CClock::Initialise(uint32 millisecondsPerGameMinute) { * @addr 0x52CF10 */ void CClock::Update() { + ZoneScoped; + if (gbFreezeTime) { // NOTSA ms_nLastClockTick = CTimer::GetTimeInMS(); } diff --git a/source/game_sa/ClothesBuilder.cpp b/source/game_sa/ClothesBuilder.cpp index 2ffd53d038..bf5b9c1206 100644 --- a/source/game_sa/ClothesBuilder.cpp +++ b/source/game_sa/ClothesBuilder.cpp @@ -1,49 +1,50 @@ #include "StdInc.h" +#include + #include "ClothesBuilder.h" +#include "PedClothesDesc.h" CDirectory& playerImg = *(CDirectory*)0xBC12C0; CDirectory::DirectoryInfo& playerImgEntries = *(CDirectory::DirectoryInfo*)0xBBCDC8; +auto& gBoneIndices = StaticRef, 0xBBC8C8>(); + +auto& ms_ratiosHaveChanged = StaticRef(); +auto& ms_geometryHasChanged = StaticRef(); +auto& ms_textureHasChanged = StaticRef(); + void CClothesBuilder::InjectHooks() { RH_ScopedClass(CClothesBuilder); RH_ScopedCategoryGlobal(); - RH_ScopedInstall(LoadCdDirectory, 0x5A4190, { .reversed = false }); - RH_ScopedInstall(RequestGeometry, 0x5A41C0, { .reversed = false }); - RH_ScopedInstall(RequestTexture, 0x5A4220, { .reversed = false }); + RH_ScopedInstall(LoadCdDirectory, 0x5A4190); + RH_ScopedInstall(RequestGeometry, 0x5A41C0); + RH_ScopedInstall(RequestTexture, 0x5A4220); //RH_ScopedInstall(nullptr, 0x5A42B0, { .reversed = false }); //RH_ScopedInstall(nullptr, 0x5A4380, { .reversed = false }); AtomicInstanceCB //RH_ScopedInstall(nullptr, 0x5A43A0, { .reversed = false }); //RH_ScopedInstall(nullptr, 0x5A44A0, { .reversed = false }); DestroyTextureCB RH_ScopedInstall(PreprocessClothesDesc, 0x5A44C0, { .reversed = false }); RH_ScopedInstall(ReleaseGeometry, 0x5A47B0, { .reversed = false }); - RH_ScopedGlobalInstall(FindAtomicFromNameCB, 0x5A47E0, { .reversed = false }); - RH_ScopedGlobalInstall(GetAtomicWithName, 0x5A4810, { .reversed = false }); - RH_ScopedInstall(sub_5A4840, 0x5A4840, { .reversed = false }); - RH_ScopedInstall(StoreBoneArray, 0x5A48B0, { .reversed = false }); - // RH_ScopedOverloadedInstall(BlendGeometry, "", 0x5A4940, RpGeometry* (*)(RpClump*, const char*, const char*, const char*, float, float, float)); - // RH_ScopedOverloadedInstall(BlendGeometry, "", 0x5A4F10, RpGeometry* (*)(RpClump*, const char*, const char*, float, float)); + RH_ScopedGlobalInstall(GetAtomicWithName, 0x5A4810); + RH_ScopedInstall(AddWeightToBoneVertex, 0x5A4840); + RH_ScopedInstall(StoreBoneArray, 0x5A48B0); + RH_ScopedOverloadedInstall(BlendGeometry, "3", 0x5A4940, RpGeometry * (*)(RpClump*, const char*, const char*, const char*, float, float, float), { .reversed = false }); + RH_ScopedOverloadedInstall(BlendGeometry, "2", 0x5A4F10, RpGeometry* (*)(RpClump*, const char*, const char*, float, float), {.reversed = false}); RH_ScopedInstall(CopyGeometry, 0x5A5340, { .reversed = false }); - RH_ScopedInstall(ConstructGeometryArray, 0x5A55A0, { .reversed = false }); - RH_ScopedInstall(DestroySkinArrays, 0x5A56C0, { .reversed = false }); - RH_ScopedInstall(BuildBoneIndexConversionTable, 0x5A56E0, { .reversed = false }); - RH_ScopedInstall(CopyTexture, 0x5A5730, { .reversed = false }); - RH_ScopedInstall(PlaceTextureOnTopOfTexture, 0x5A57B0, { .reversed = false }); - // RH_ScopedOverloadedInstall(BlendTextures, "", 0x5A5820, void (*)(RwTexture*, RwTexture*, float, float, int32)); - // RH_ScopedOverloadedInstall(BlendTextures, "", 0x5A59C0, void (*)(RwTexture*, RwTexture*, RwTexture*, float, float, float, int32)); - // RH_ScopedOverloadedInstall(BlendTextures, "", 0x5A5BC0, void (*)(RwTexture*, RwTexture*, RwTexture*, float, float, float, int32, RwTexture*)); - RH_ScopedInstall(InitPaletteOctTree, 0x5A5EB0, { .reversed = false }); - RH_ScopedInstall(ShutdownPaletteOctTree, 0x5A5EE0, { .reversed = false }); - RH_ScopedInstall(ReducePaletteOctTree, 0x5A5EF0, { .reversed = false }); - RH_ScopedInstall(AddColour, 0x5A5F00, { .reversed = false }); - RH_ScopedInstall(FillPalette, 0x5A5F30, { .reversed = false }); - RH_ScopedInstall(FindNearestColour, 0x5A5F40, { .reversed = false }); - RH_ScopedGlobalInstall(GetTextureFromTxdAndLoadNextTxd, 0x5A5F70, { .reversed = false }); + RH_ScopedInstall(ConstructGeometryArray, 0x5A55A0, { .reversed = false }); // Makes the game crash - Probably a register is changed or smth + RH_ScopedInstall(DestroySkinArrays, 0x5A56C0); + RH_ScopedInstall(BuildBoneIndexConversionTable, 0x5A56E0); + RH_ScopedInstall(CopyTexture, 0x5A5730); + RH_ScopedInstall(PlaceTextureOnTopOfTexture, 0x5A57B0); + RH_ScopedOverloadedInstall(BlendTextures, "Dst-Src", 0x5A5820, void (*)(RwTexture*, RwTexture*, float, float, int32)); + RH_ScopedOverloadedInstall(BlendTextures, "Dst-Src1-Src2", 0x5A59C0, void (*)(RwTexture*, RwTexture*, RwTexture*, float, float, float, int32)); + RH_ScopedOverloadedInstall(BlendTextures, "Dst-Src1-Src2-Tat", 0x5A5BC0, void (*)(RwTexture*, RwTexture*, RwTexture*, float, float, float, int32, RwTexture*)); + RH_ScopedGlobalInstall(GetTextureFromTxdAndLoadNextTxd, 0x5A5F70); RH_ScopedInstall(ConstructTextures, 0x5A6040, { .reversed = false }); RH_ScopedInstall(ConstructGeometryAndSkinArrays, 0x5A6530, { .reversed = false }); - RH_ScopedInstall(ReducePaletteSize, 0x5A6870, { .reversed = false }); - RH_ScopedInstall(CreateSkinnedClump, 0x5A69D0, { .reversed = false }); + RH_ScopedInstall(CreateSkinnedClump, 0x5A69D0); } // inlined @@ -54,13 +55,28 @@ void CClothesBuilder::LoadCdDirectory() { } // 0x5A41C0 -void CClothesBuilder::RequestGeometry(int32 modelId, uint32 crc) { - plugin::Call<0x5A41C0, int32, uint32>(modelId, crc); +void CClothesBuilder::RequestGeometry(int32 modelId, uint32 modelNameKey) { + CModelInfo::GetModelInfo(modelId)->bHasComplexHierarchy = true; // TODO/NOTE: Not sure + uint32 offset, size; + VERIFY(playerImg.FindItem(CKeyGen::AppendStringToKey(modelNameKey, ".DFF"), offset, size)); + CStreaming::RequestFile(modelId, offset, size, CClothes::ms_clothesImageId, STREAMING_PRIORITY_REQUEST | STREAMING_GAME_REQUIRED); } // 0x5A4220 -int32 CClothesBuilder::RequestTexture(uint32 crc) { - return plugin::CallAndReturn(crc); +int32 CClothesBuilder::RequestTexture(uint32 txdNameKey) { + if (txdNameKey == 0) { + return -1; + } + + auto& defaultTxdIdx = StaticRef(); + const auto defaultTxd = CTxdStore::defaultTxds[defaultTxdIdx]; + defaultTxdIdx = (defaultTxdIdx + 1) % 4; + + uint32 offset, size; + VERIFY(playerImg.FindItem(CKeyGen::AppendStringToKey(txdNameKey, ".TXD"), offset, size)); + CStreaming::RequestFile(TXDToModelId(defaultTxd), offset, size, CClothes::ms_clothesImageId, STREAMING_PRIORITY_REQUEST | STREAMING_GAME_REQUIRED); + + return defaultTxd; } // 0x5A44C0 @@ -75,34 +91,167 @@ void CClothesBuilder::ReleaseGeometry(int32 numToRelease) { --i; } -// 0x5A47E0 -void FindAtomicFromNameCB(RpAtomic* atomic, void* data) { - plugin::Call<0x5A47E0, RpAtomic*, void*>(atomic, data); -} - // 0x5A4810 -void GetAtomicWithName(RpClump* clump, const char* name) { - plugin::Call<0x5A4810, RpClump*, const char*>(clump, name); +RpAtomic* GetAtomicWithName(RpClump* clump, const char* name) { + struct Context { + notsa::ci_string_view name{}; + RpAtomic* atomic{}; + } c{name}; + RpClumpForAllAtomics(clump, [](RpAtomic* a, void* data) { // 0x5A47E0 + auto& ctx = *static_cast(data); + if (ctx.name == GetFrameNodeName(RpAtomicGetFrame(a))) { + ctx.atomic = a; + } + return a; + }, &c); + return c.atomic; } // 0x5A4840 -void CClothesBuilder::sub_5A4840() { - +void CClothesBuilder::AddWeightToBoneVertex(float (&weights)[8], uint8(&boneVertexIdxs)[8], float weightToAdd, RwUInt32 targetVertexIdx) { // Unknown OG name + if (weightToAdd == 0.f) { + return; + } + for (auto i = 0; i < 8; i++) { + if (weights[i] == 0.f) { // Weight not yet used? + boneVertexIdxs[i] = targetVertexIdx; // Add to list + weights[i] = weightToAdd; + weights[i + 1] = 0.f; // Mark next as unused [Though this step in our case is not necessary as the whole weights array is already zero-inited] + return; + } + if (boneVertexIdxs[i] == targetVertexIdx) { // Already in the list, use that + weights[i] += weightToAdd; + return; + } + } + NOTSA_UNREACHABLE(); // OG code had UB in this case } // 0x5A48B0 -void CClothesBuilder::StoreBoneArray(RpClump* clump, int32 a2) { - plugin::Call<0x5A48B0, RpClump*, int32>(clump, a2); +void CClothesBuilder::StoreBoneArray(RpClump* clump, int32 idx) { + const auto a = GetAtomicWithName(clump, "normal"); + assert(a); + + const auto h = RpSkinAtomicGetHAnimHierarchy(a); + assert(h); + + rng::fill(gBoneIndices[idx], -1); + for (auto i = h->numNodes; i-- > 0;) { + gBoneIndices[idx][i] = static_cast(h->pNodeInfo[i].nodeID); + } +} + +/* +* @notsa +* +* Based on 0x5A4940 +* Blend any number of geometries together +* The result is stored in the 0th frame's geometry. +* +* @arg clump The clump to which the frames belong to +* @arg frames A list of frames whose geometry should be blended together +*/ +template +RpGeometry* BlendGeometry(RpClump* clump, std::pair (&&frameNamesRatios)[N]) { +#ifdef NOTSA_DEBUG + { + float a{}; + for (auto&& v : frameNamesRatios) { + a += v.second; + } + assert(a >= 0.f && a <= 1.f); + } +#endif + + // Process data needed for blending + struct GeoBlendData { + RpAtomic* a; + RpGeometry* g; + const RwUInt8* boneIdxs; + RwMatrixWeights* boneWeights; + RwTexCoords* uvs; + CVector* verts; + CVector* nrmls; + float r; // Blend ratio + } fds[N]; + auto& out = fds[0]; + for (auto&& [i, v] : notsa::enumerate(frameNamesRatios)) { + const auto a = GetAtomicWithName(clump, v.first); + const auto g = RpAtomicGetGeometry(a); + const auto s = RpSkinGeometryGetSkin(g); + const auto mt = RpGeometryGetMorphTarget(g, 0); + fds[i] = { + a, + g, + (const RwUInt8*)RpSkinGetVertexBoneIndices(s), // NOTE: Not sure why it's casted to UInt8, but that really is how the data is stored / TODO: Okay, so actually it seems like something is fucked + RpSkinGetVertexBoneWeights(s), + RpGeometryGetVertexTexCoords(g, 1), + (CVector*)RpMorphTargetGetVertices(mt), + (CVector*)RpMorphTargetGetVertexNormals(mt), + v.second + }; + } + + RpGeometryLock(out.g, rpGEOMETRYLOCKALL); + + for (auto i = 0; i < RpGeometryGetNumVertices(out.g); i++) { + const auto Blend = [&](auto&& Get) { + return multiply_weighted(fds | rng::views::transform([&](auto&& fd) { return WeightedValue{ std::invoke(Get, &fd)[i], fd.r }; })); + }; + out.verts[i] = Blend(&GeoBlendData::verts); + out.nrmls[i] = Blend(&GeoBlendData::nrmls); + out.uvs[i] = Blend(&GeoBlendData::uvs); + + // Helper function to get the weight at a given weight index + const auto RwMatrixWeightsGetWeight = [](RwMatrixWeights& mw, int32 wi) -> float& { + switch (wi) { + case 0: return mw.w0; + case 1: return mw.w1; + case 2: return mw.w2; + case 3: return mw.w3; + default: NOTSA_UNREACHABLE(); + } + }; + + // Calculate bone weights + float weights[8]{}; + uint8 boneVertexIdxs[8]{}; + for (auto& fd : fds) { + for (auto wi = 0; wi < 4; wi++) { + const auto vtxIdx = i + wi; + CClothesBuilder::AddWeightToBoneVertex( + weights, + boneVertexIdxs, + RwMatrixWeightsGetWeight(fd.boneWeights[vtxIdx], wi), + fd.boneIdxs[vtxIdx] + ); + } + } + for (auto b = 0; b < 4; b++) { + const_cast(out.boneIdxs)[i + b] = boneVertexIdxs[b]; + } + const auto t = weights[4] != 0.f + ? 1.f / std::accumulate(weights, weights + 4, 0.f) + : 1.f; + for (auto wi = 0; wi < 4; wi++) { + RwMatrixWeightsGetWeight(out.boneWeights[i + wi], wi) = weights[wi] * t; + } + } + + RpGeometryUnlock(out.g); + out.g->refCount++; // TODO: Function missing + + return out.g; } // 0x5A4940 -RpGeometry* CClothesBuilder::BlendGeometry(RpClump* clump, const char* a2, const char* a3, const char* a4, float a5, float a6, float a7) { - return plugin::CallAndReturn(clump, a2, a3, a4, a5, a6, a7); +RpGeometry* CClothesBuilder::BlendGeometry(RpClump* clump, const char* frameName0, const char* frameName1, const char* frameName2, float r0, float r1, float r2) { + return ::BlendGeometry(clump, { {frameName0, r0}, {frameName1, r1}, {frameName2, r2} }); } // 0x5A4F10 -RpGeometry* CClothesBuilder::BlendGeometry(RpClump* clump, const char* a2, const char* a3, float a4, float a5) { - return plugin::CallAndReturn(clump, a2, a3, a4, a5); +RpGeometry* CClothesBuilder::BlendGeometry(RpClump* clump, const char* frameName0, const char* frameName1, float r0, float r1) { + return ::BlendGeometry(clump, { {frameName0, r0}, {frameName1, r1} }); } // 0x5A5340 @@ -111,97 +260,228 @@ RpGeometry* CClothesBuilder::CopyGeometry(RpClump* clump, const char* a2, const } // 0x5A55A0 -void CClothesBuilder::ConstructGeometryArray(RpGeometry** geometry, uint32* a2, float a3, float a4, float a5) { - plugin::Call<0x5A55A0, RpGeometry**, uint32*, float, float, float>(geometry, a2, a3, a4, a5); +void CClothesBuilder::ConstructGeometryArray(RpGeometry** out, uint32* modelNameKeys, float normal, float fatness, float strength) { + for (auto i = 0; i < 10; i++) { + if (modelNameKeys[i] == 0) { + *out = nullptr; + continue; + } + const auto modelIdx = (eModelID)((int)MODEL_CLOTHES01_ID384 + i); + const auto mi = CModelInfo::GetModelInfo(modelIdx); + + CModelInfo::GetModelInfo(modelIdx)->bHasComplexHierarchy = true; + RequestGeometry(modelIdx, modelNameKeys[i]); + CStreaming::LoadAllRequestedModels(true); + + if (i + 1 < 10 && modelNameKeys[i + 1]) { // Request next model to be loaded in advance + RequestGeometry((eModelID)((int)MODEL_CLOTHES01_ID384 + i + 1), modelNameKeys[i + 1]); + CStreaming::LoadRequestedModels(); + } + + *out = BlendGeometry(mi->m_pRwClump, "normal", "fat", "ripped", normal, fatness, strength); + StoreBoneArray(mi->m_pRwClump, i); + CStreaming::RemoveModel(modelIdx); + } } // inlined, see 0x5A6CE1 // 0x5A56C0 -void CClothesBuilder::DestroySkinArrays(RwMatrixWeights* weights, uint32* a2) { - operator delete(weights); - operator delete(a2); +void CClothesBuilder::DestroySkinArrays(RwMatrixWeights* weights, RwUInt32* bones) { + // TODO: Should this be `delete[]` or `delete`? + delete weights; + delete bones; } // 0x5A56E0 -void CClothesBuilder::BuildBoneIndexConversionTable(uint8* a1, RpHAnimHierarchy* a2, int32 a3) { - plugin::Call<0x5A56E0, uint8*, RpHAnimHierarchy*, int32>(a1, a2, a3); +void CClothesBuilder::BuildBoneIndexConversionTable(uint8* pTable, RpHAnimHierarchy* hier, int32 index) { + for (const auto [tableIdx, boneId] : notsa::enumerate(gBoneIndices[index])) { + if (boneId == -1) { + break; + } + const auto idx = RpHAnimIDGetIndex(hier, boneId); + pTable[tableIdx] = idx == 0xFF ? 0 : idx; + } +} + +void AssertTextureLayouts(std::initializer_list textures) { + assert(textures.size() >= 2); + for (auto i = 0u; i < textures.size() - 1; i++) { + const auto r1 = RwTextureGetRaster(textures.begin()[i]), r2 = RwTextureGetRaster(textures.begin()[i + 1]); + + assert(RwRasterGetWidth(r1) == RwRasterGetWidth(r2)); + assert(RwRasterGetHeight(r1) == RwRasterGetHeight(r2)); + assert(RwRasterGetDepth(r1) == RwRasterGetDepth(r2)); + assert(RwRasterGetDepth(r1) == 32); + } } // 0x5A5730 -RwTexture* CClothesBuilder::CopyTexture(RwTexture* texture) { - return plugin::CallAndReturn(texture); +RwTexture* CClothesBuilder::CopyTexture(RwTexture* srcTex) { + const auto srcRaster = RwTextureGetRaster(srcTex); + + // Create a new raster to which we're going to copy to + const auto dstRaster = RwRasterCreate( + RwRasterGetWidth(srcRaster), + RwRasterGetHeight(srcRaster), + RwRasterGetDepth(srcRaster), + (RwRasterGetFormat(srcRaster) & rwRASTERFORMATPIXELFORMATMASK) | 4 // TODO + ); + + // Copy data from the src raster to this one + const auto srcLck = RwRasterLock(srcRaster, 0, rwRASTERLOCKREAD); + memcpy( + RwRasterLock(dstRaster, 0, rwRASTERLOCKWRITE), + srcLck, + RwRasterGetHeight(srcRaster) * RwRasterGetStride(srcRaster) + ); + RwRasterUnlock(srcRaster); + RwRasterUnlock(dstRaster); + + // Create a texture from the copied raster + const auto dstTex = RwTextureCreate(dstRaster); + RwTextureSetFilterMode(dstTex, rwFILTERLINEAR); + + AssertTextureLayouts({ dstTex, srcTex }); + + return dstTex; } // 0x5A57B0 -void CClothesBuilder::PlaceTextureOnTopOfTexture(RwTexture* texture1, RwTexture* texture2) { - plugin::Call<0x5A57B0, RwTexture*, RwTexture*>(texture1, texture2); +void CClothesBuilder::PlaceTextureOnTopOfTexture(RwTexture* dstTex, RwTexture* srcTex) { + ZoneScoped; + + AssertTextureLayouts({ dstTex, srcTex }); + + const auto dstRaster = RwTextureGetRaster(dstTex); + const auto srcRaster = RwTextureGetRaster(srcTex); + + auto dstIt = (RwUInt32*)RwRasterLock(dstRaster, 0, rwRASTERLOCKREADWRITE); + auto srcIt = (RwUInt32*)RwRasterLock(srcRaster, 0, rwRASTERLOCKREADWRITE); + + // NOTE: They don't skip the stride, but it's fine [This way vectorization should be easier for the compiler] + for (auto i = RwRasterGetHeight(dstRaster) * RwRasterGetWidth(dstRaster); i-- > 0; dstIt++, srcIt++) { + if (*srcIt & 0xFF000000) { // Check alpha != 0 + *dstIt = *srcIt; + } + } + + RwRasterUnlock(dstRaster); + RwRasterUnlock(srcRaster); } // 0x5A5820 -void CClothesBuilder::BlendTextures(RwTexture* t1, RwTexture* t2, float a3, float a4, int32 a5) { - plugin::Call<0x5A5820, RwTexture*, RwTexture*, float, float, int32>(t1, t2, a3, a4, a5); +void CClothesBuilder::BlendTextures(RwTexture* dst, RwTexture* src, float r1, float r2, int32 numColors) { + ZoneScoped; + + AssertTextureLayouts({ dst, src }); + + const auto dstRaster = RwTextureGetRaster(dst); + const auto srcRaster = RwTextureGetRaster(src); + + CTimer::Suspend(); + + auto srcIt = RwRasterLock(srcRaster, 0, rwRASTERLOCKREAD); + auto dstIt = RwRasterLock(dstRaster, 0, rwRASTERLOCKREADWRITE); + + for (auto i = RwRasterGetHeight(dstRaster) * RwRasterGetWidth(dstRaster); i-- > 0; dstIt++, srcIt++) { + for (auto c = 3; i-- > 0; dstIt++, srcIt++) { // Copy RGB, alpha stays the same + *dstIt = multiply_weighted({ { *dstIt, r1 }, { *srcIt, r2 } }); + } + } + + RwRasterUnlock(dstRaster); + RwRasterUnlock(srcRaster); + + CTimer::Resume(); } // 0x5A59C0 -void CClothesBuilder::BlendTextures(RwTexture* t1, RwTexture* t2, RwTexture* t3, float factorA, float factorB, float factorC, int32 a7) { - plugin::Call<0x5A59C0, RwTexture*, RwTexture*, RwTexture*, float, float, float>(t1, t2, t3, factorA, factorB, factorC, a7); +void CClothesBuilder::BlendTextures(RwTexture* dst, RwTexture* src1, RwTexture* src2, float r1, float r2, float r3, int32) { + ZoneScoped; + + AssertTextureLayouts({ dst, src1, src2 }); + + const auto dstRaster = RwTextureGetRaster(dst); + const auto src1Raster = RwTextureGetRaster(src1); + const auto src2Raster = RwTextureGetRaster(src2); + + CTimer::Suspend(); + + auto src1It = RwRasterLock(src1Raster, 0, rwRASTERLOCKREAD); + auto src2It = RwRasterLock(src2Raster, 0, rwRASTERLOCKREAD); + auto dstIt = RwRasterLock(dstRaster, 0, rwRASTERLOCKREADWRITE); + + for (auto i = RwRasterGetHeight(dstRaster) * RwRasterGetWidth(dstRaster); i-- > 0; dstIt++, src1It++, src2It++) { + for (auto c = 3; i-- > 0; dstIt++, src1It++, src2It++) { // Copy RGB, alpha doesn't change + *dstIt = multiply_weighted({ { *dstIt, r1 }, { *src1It, r2 }, { *src2It, r3 } }); + } + } + + RwRasterUnlock(dstRaster); + RwRasterUnlock(src1Raster); + RwRasterUnlock(src2Raster); + + CTimer::Resume(); } // 0x5A5BC0 -void CClothesBuilder::BlendTextures(RwTexture* t1, RwTexture* t2, RwTexture* t3, float factorA, float factorB, float factorC, int32 a7, RwTexture* t4) { - plugin::Call<0x5A5BC0, RwTexture*, RwTexture*, RwTexture*, float, float, float, int32, RwTexture*>(t1, t2, t3, factorA, factorB, factorC, a7, t4); -} +void CClothesBuilder::BlendTextures(RwTexture* dst, RwTexture* src1, RwTexture* src2, float r1, float r2, float r3, int32 numColors, RwTexture* tattoos) { + ZoneScoped; -// unused or inlined -// 0x5A5EB0 -void CClothesBuilder::InitPaletteOctTree(int32 numColors) { - /* - COctTree::InitPool(&PC_Scratch[1024], 15360u); - COctTreeBase::Init(&gOctTreeBase, numColors); - */ -} + AssertTextureLayouts({ dst, src1, src2, tattoos }); -// 0x5A5EE0 -void CClothesBuilder::ShutdownPaletteOctTree() { - // COctTree::ShutdownPool(); -} + const auto dstRaster = RwTextureGetRaster(dst); + const auto src1Raster = RwTextureGetRaster(src1); + const auto src2Raster = RwTextureGetRaster(src2); + const auto tatRaster = RwTextureGetRaster(tattoos); -// 0x5A5EF0 -void CClothesBuilder::ReducePaletteOctTree(int32 numColorsToReduce) { - // gOctTreeBase.ReduceBranches(newBranchesCount); -} + CTimer::Suspend(); -// 0x5A5F00 -bool CClothesBuilder::AddColour(RwRGBA* color) { - return plugin::CallAndReturn(color); - /* - if (!color->alpha) - gOctTreeBase.m_bHasTransparentPixels = 1; + auto src1It = RwRasterLock(src1Raster, 0, rwRASTERLOCKREAD); + auto src2It = RwRasterLock(src2Raster, 0, rwRASTERLOCKREAD); + auto tatIt = RwRasterLock(tatRaster, 0, rwRASTERLOCKREAD); + auto dstIt = RwRasterLock(dstRaster, 0, rwRASTERLOCKREADWRITE); - return gOctTreeBase.Insert(color); - */ -} + for (auto i = RwRasterGetHeight(dstRaster) * RwRasterGetWidth(dstRaster); i-- > 0; dstIt++, src1It++, src2It++, tatIt++) { + const auto tatAlphaT = (float)tatIt[3] / 255.f; + for (auto c = 3; i-- > 0; dstIt++, src1It++, src2It++, tatIt++) { // Copy RGB, alpha doesn't change + *dstIt = (RwUInt8)lerp(multiply_weighted({ { *dstIt, r1 }, { *src1It, r2 }, { *src2It, r3 } }), *tatIt, tatAlphaT); + } + } -// 0x5A5F30 -void CClothesBuilder::FillPalette(RwRGBA* color) { - // gOctTreeBase.FillPalette(color); -} + RwRasterUnlock(dstRaster); + RwRasterUnlock(src1Raster); + RwRasterUnlock(src2Raster); + RwRasterUnlock(tatRaster); -// unused -// 0x5A5F40 -int32 CClothesBuilder::FindNearestColour(RwRGBA* color) { - return plugin::CallAndReturn(color); - /* - if (color->alpha) - return gOctTreeBase.FindNearestColour(color); - else - return 0; - */ + CTimer::Resume(); } // 0x5A5F70 -RwTexture* GetTextureFromTxdAndLoadNextTxd(RwTexture* destTexture, int32 txdId_withTexture, int32 CRC_nextTxd, int32* nextTxdId) { - return plugin::CallAndReturn(destTexture, txdId_withTexture, CRC_nextTxd, nextTxdId); +RwTexture* GetTextureFromTxdAndLoadNextTxd(RwTexture* dstTex, int32 txdId_withTexture, int32 CRC_nextTxd, int32* nextTxdId) { + if (txdId_withTexture == -1) { + if (CRC_nextTxd) { + *nextTxdId = CClothesBuilder::RequestTexture(CRC_nextTxd); + CStreaming::LoadRequestedModels(); + } else { + *nextTxdId = -1; + } + return dstTex; + } + + CStreaming::LoadAllRequestedModels(true); + if (CRC_nextTxd) { + *nextTxdId = CClothesBuilder::RequestTexture(CRC_nextTxd); + CStreaming::LoadRequestedModels(); + } else { + *nextTxdId = -1; + } + const auto tex = GetFirstTexture(CTxdStore::GetTxd(txdId_withTexture)); + const auto res = dstTex + ? CClothesBuilder::PlaceTextureOnTopOfTexture(dstTex, tex), dstTex + : CClothesBuilder::CopyTexture(tex); + CStreaming::RemoveModel(TXDToModelId(txdId_withTexture)); + return res; } // 0x5A6040 @@ -210,17 +490,172 @@ void CClothesBuilder::ConstructTextures(RwTexDictionary* dict, uint32* hashes, f } // 0x5A6530 -void CClothesBuilder::ConstructGeometryAndSkinArrays(RpHAnimHierarchy* animHierarchy, RpGeometry** geometry1, RwMatrixWeights** weights, uint32** a4, uint32 a5, RpGeometry** geometry2, RpMaterial** material) { - plugin::Call<0x5A6530, RpHAnimHierarchy*, RpGeometry**, RwMatrixWeights**, uint32**, uint32, RpGeometry**, RpMaterial**>(animHierarchy, geometry1, weights, a4, a5, geometry2, material); -} - -// unused -// 0x5A6870 -void CClothesBuilder::ReducePaletteSize(RwTexture* texture, int32 numColorsToReduce) { - plugin::Call<0x5A6870, RwTexture*, int32>(texture, numColorsToReduce); +void CClothesBuilder::ConstructGeometryAndSkinArrays(RpHAnimHierarchy* pBoneHier, RpGeometry** ppGeometry, RwMatrixWeights** ppWeights, uint32** ppIndices, uint32 numModels, RpGeometry** pGeometrys, RpMaterial** pMaterial) { + plugin::Call<0x5A6530>(pBoneHier, ppGeometry, ppWeights, ppIndices, numModels, pGeometrys, pMaterial); } // 0x5A69D0 -RpClump* CClothesBuilder::CreateSkinnedClump(RpClump* clump, RwTexDictionary* dict, CPedClothesDesc& newClothes, const CPedClothesDesc* oldClothes, bool bCutscenePlayer) { - return plugin::CallAndReturn(clump, dict, newClothes, oldClothes, bCutscenePlayer); +RpClump* CClothesBuilder::CreateSkinnedClump(RpClump* bones, RwTexDictionary* dict, CPedClothesDesc& ndscr, const CPedClothesDesc* odscr, bool bCutscenePlayer) { + LoadCdDirectory(); + + const struct { + eClothesModelPart mp; + const char* name; + } parts[]{ + {eClothesModelPart::CLOTHES_MODEL_TORSO, "torso"}, + {eClothesModelPart::CLOTHES_MODEL_HEAD, "head"}, + {eClothesModelPart::CLOTHES_MODEL_HANDS, "hands"}, + {eClothesModelPart::CLOTHES_MODEL_LEGS, "legs"}, + {eClothesModelPart::CLOTHES_MODEL_SHOES, "feet"} + }; + for (const auto [mp, name] : parts) { + if (!ndscr.m_anModelKeys[(int)mp]) { + ndscr.SetModel(name, mp); + } + } + + if (odscr) { + ms_geometryHasChanged = false; + ms_ratiosHaveChanged = false; + if (odscr->m_fFatStat != ndscr.m_fFatStat || odscr->m_fMuscleStat != ndscr.m_fMuscleStat) { + ms_textureHasChanged = true; + ms_geometryHasChanged = true; + } else { + ms_textureHasChanged = true; + } + ms_geometryHasChanged = !rng::equal(ndscr.m_anModelKeys, odscr->m_anModelKeys); + ms_ratiosHaveChanged = !rng::equal(ndscr.m_anTextureKeys, odscr->m_anTextureKeys); + if (!ms_ratiosHaveChanged && !ms_geometryHasChanged && !ms_textureHasChanged) { + return nullptr; + } + } else { + ms_ratiosHaveChanged = ms_geometryHasChanged = ms_textureHasChanged = true; + } + CPedClothesDesc dscr = ndscr; + PreprocessClothesDesc(dscr, bCutscenePlayer); + + //> 0x5A42B0 - Calculate blend ratios + float rNormal, rFatness, rMuscle; + { + rMuscle = std::clamp(CStats::GetStatValue(STAT_MUSCLE) / 1000.f, 0.f, 1.f); + rFatness = std::clamp((dscr.m_fFatStat - 200.f) / 800.f, 0.f, 1.f); + rNormal = 1.f - rMuscle - rFatness; + if (rNormal <= 0.f) { + const auto t = 1.f / (rFatness + rMuscle); + rMuscle *= t; + rFatness *= t; + rNormal = 0.f; + } + } + + if ((ms_textureHasChanged || ms_ratiosHaveChanged) && !bCutscenePlayer) { + RwTexDictionaryForAllTextures(dict, [](RwTexture* t, void* data) { + RwTexDictionaryRemoveTexture(t); + RwTextureDestroy(t); + return t; + }, nullptr); + ConstructTextures(dict, dscr.m_anTextureKeys.data(), rNormal, rFatness, rMuscle); + } + + constexpr auto NO_BODY_PARTS = 10; + + constexpr const char* BODY_PART_TEX_NAMES[NO_BODY_PARTS]{ + "torso", + "head", + "torso", + "legs", + "feet", + "necklace", + "watch", + "glasses", + "hat", + "extra1" + }; + + //> 0x5A6B2C + RpMaterial* ms[NO_BODY_PARTS]; + for (uint32 i{}; const auto name : BODY_PART_TEX_NAMES) { + ms[i++] = [&]() -> RpMaterial* { + if (const auto tex = RwTexDictionaryFindNamedTexture(dict, name)) { + const auto mat = RpMaterialCreate(); + RpMaterialSetTexture(mat, tex); + const auto clr = RwRGBA(0xFF, 0xFF, 0xFF, 0xFF); + RpMaterialSetColor(mat, &clr); + return mat; + } + return nullptr; + }(); + } + + //> 0x5A6C5D + RpGeometry* gs[NO_BODY_PARTS]; + ConstructGeometryArray(gs, dscr.m_anModelKeys.data(), rNormal, rFatness, rMuscle); + + //> 0x5A6C6A + const auto boneAtomic = GetFirstAtomic(bones); + const auto boneSkin = RpSkinGeometryGetSkin(RpAtomicGetGeometry(boneAtomic)); + const auto boneAnimHr = RpSkinAtomicGetHAnimHierarchy(boneAtomic); + + RwMatrixWeights* boneWeights; + RwUInt32* boneIdxs; + RpGeometry* tmpGeo; + ConstructGeometryAndSkinArrays( + boneAnimHr, + &tmpGeo, + &boneWeights, + &boneIdxs, + NO_BODY_PARTS, + gs, + ms + ); + RpSkinGeometrySetSkin( + tmpGeo, + RpSkinCreate( + RpGeometryGetNumVertices(tmpGeo), + RpSkinGetNumBones(boneSkin), + boneWeights, + boneIdxs, + const_cast(RpSkinGetSkinToBoneMatrices(boneSkin)) // TODO + ) + ); + DestroySkinArrays(boneWeights, boneIdxs); + + const auto hier = RpHAnimHierarchyCreateFromHierarchy( + boneAnimHr, + (RpHAnimHierarchyFlag)boneAnimHr->flags, // TODO: Use function to access + boneAnimHr->currentAnim->maxInterpKeyFrameSize // TODO: Use function to access + ); + + const auto childFrame = RwFrameCreate(); + RpHAnimFrameSetHierarchy(childFrame, hier); + + const auto atomic = RpAtomicCreate(); + RpAtomicSetGeometry(atomic, tmpGeo, NULL); + RpSkinAtomicSetHAnimHierarchy(atomic, hier); + RpAtomicSetFrame(atomic, childFrame); + RpSkinAtomicSetType(atomic, rpSKINTYPEGENERIC); + + const auto rootFrame = RwFrameCreate(); + RwFrameAddChild(rootFrame, childFrame); + + const auto clump = RpClumpCreate(); + RpClumpSetFrame(clump, rootFrame); + RpClumpAddAtomic(clump, atomic); + + // Free memory + { + RpGeometryDestroy(tmpGeo); + + RwTexDictionarySetCurrent(dict); + for (auto i = 0; i < NO_BODY_PARTS; i++) { + if (gs[i]) { + RpGeometryDestroy(gs[i]); + } + if (ms[i]) { + RpMaterialDestroy(ms[i]); + } + } + } + + return clump; } diff --git a/source/game_sa/ClothesBuilder.h b/source/game_sa/ClothesBuilder.h index 26d04702a5..56cf37de4b 100644 --- a/source/game_sa/ClothesBuilder.h +++ b/source/game_sa/ClothesBuilder.h @@ -20,31 +20,93 @@ class CClothesBuilder { static int32 RequestTexture(uint32 crc); static void PreprocessClothesDesc(CPedClothesDesc& desc, bool a2); static void ReleaseGeometry(int32 numToRelease); - static void sub_5A4840(); + static void AddWeightToBoneVertex(float(&out_weights)[8], uint8(&bone_vertex_indices)[8], float weight_to_add, RwUInt32 target_vertex); static void StoreBoneArray(RpClump* clump, int32 a2); - static RpGeometry* BlendGeometry(RpClump* clump, const char* a2, const char* a3, const char* a4, float a5, float a6, float a7); - static RpGeometry* BlendGeometry(RpClump* clump, const char* a2, const char* a3, float a4, float a5); + + /*! + * Blend 3 geometries together and store the result in the first one + * + * @addr 0x5A4940 + * + * @arg clump The clump of the named frames [which in turn contain the geometries] + * @arg frameName0 The 0th frame - This is where the result is stored to + * @arg frameName1 The 1st frame + * @arg frameName2 The 2nd frame + * @arg r0 Blend ratio of the corresponding frame + * @arg r1 Blend ratio of the corresponding frame + * @arg r2 Blend ratio of the corresponding frame + */ + static RpGeometry* BlendGeometry(RpClump* pClump, const char* frameName0, const char* frameName1, const char* frameName2, float r0, float r1, float r2); + + /*! + * Blend 2 geometries together and store the result in the first one + * + * @addr 0x5A4F10 + * + * @arg clump The clump of the named frames [which in turn contain the geometries] + * @arg frameName0 The 0th frame - This is where the result is stored to + * @arg frameName1 The 1st frame + * @arg r0 Blend ratio of the corresponding frame + * @arg r1 Blend ratio of the corresponding frame + */ + static RpGeometry* BlendGeometry(RpClump* clump, const char* frameName0, const char* frameName1, float r0, float r1); + static RpGeometry* CopyGeometry(RpClump* clump, const char* a2, const char* a3); - static void ConstructGeometryArray(RpGeometry** geometry, uint32* a2, float a3, float a4, float a5); - static void DestroySkinArrays(RwMatrixWeights* weights, uint32* a2); + static void ConstructGeometryArray(RpGeometry** ppGeometry, uint32* pModelKeys, float normal, float fatness, float strength); + static void DestroySkinArrays(RwMatrixWeights* weights, RwUInt32* bones); static void BuildBoneIndexConversionTable(uint8* a1, RpHAnimHierarchy* a2, int32 a3); + + /*! + * Create a copy of a texture + * + * @addr 0x5A5730 + */ static RwTexture* CopyTexture(RwTexture* texture); static void PlaceTextureOnTopOfTexture(RwTexture* texture1, RwTexture* texture2); - static void BlendTextures(RwTexture* t1, RwTexture* t2, float a3, float a4, int32 a5); - static void BlendTextures(RwTexture* t1, RwTexture* t2, RwTexture* t3, float factorA, float factorB, float factorC, int32 a7); - static void BlendTextures(RwTexture* t1, RwTexture* t2, RwTexture* t3, float factorA, float factorB, float factorC, int32 a7, RwTexture* t4); - static void InitPaletteOctTree(int32 numColors); - static void ShutdownPaletteOctTree(); - static void ReducePaletteOctTree(int32 numColorsToReduce); - static bool AddColour(RwRGBA* color); - static void FillPalette(RwRGBA* color); - static int32 FindNearestColour(RwRGBA* color); + + /*! + * Blend textures with a given blend ratio + * + * @addr 0x5A5820 + * + * @arg dst The texture to which the blended data is written to, and read from + * @arg src The texture that should be blended with the other one + * @arg r1 Ratio of the `dst` texture + * @arg r2 Ratio of the `src` texture + */ + static void BlendTextures(RwTexture* dst, RwTexture* src, float r1, float r2, int32); + + /*! + * Blend textures with a given blend ratio + * + * @addr 0x5A59C0 + * + * @arg dst The texture to which the blended data is written to, and read from + * @arg src1 The texture that should be blended with the other one + * @arg src2 The texture that should be blended with the other one + * @arg r1 Ratio of the `dst` texture + * @arg r2 Ratio of the `src1` texture + * @arg r3 Ratio of the `src2` texture + */ + static void BlendTextures(RwTexture* dst, RwTexture* src1, RwTexture* src2, float r1, float r2, float r3, int32); + + /*! + * Blend textures with a given blend ratio + * + * @addr 0x5A5BC0 + * + * @arg dst The texture to which the blended data is written to, and read from + * @arg src1 The texture that should be blended with the other one + * @arg src2 The texture that should be blended with the other one + * @arg r1 Ratio of the `dst` texture + * @arg r2 Ratio of the `src1` texture + * @arg r3 Ratio of the `src2` texture + */ + static void BlendTextures(RwTexture* dst, RwTexture* src1, RwTexture* src2, float r1, float r2, float r3, int32 numColors, RwTexture* tattoos); static void ConstructTextures(RwTexDictionary* dict, uint32* hashes, float factorA, float factorB, float factorC); static void ConstructGeometryAndSkinArrays(RpHAnimHierarchy* animHierarchy, RpGeometry** geometry1, RwMatrixWeights** weights, uint32** a4, uint32 a5, RpGeometry** geometry2, RpMaterial** material); - static void ReducePaletteSize(RwTexture* texture, int32 numColorsToReduce); static RpClump* CreateSkinnedClump(RpClump* clump, RwTexDictionary* dict, CPedClothesDesc& newClothes, const CPedClothesDesc* oldClothes, bool CutscenePlayer); }; -void FindAtomicFromNameCB(RpAtomic* atomic, void* data); -void GetAtomicWithName(RpClump* clump, const char* name); +RpAtomic* GetAtomicWithName(RpClump* clump, const char* name); RwTexture* GetTextureFromTxdAndLoadNextTxd(RwTexture* destTexture, int32 txdId_withTexture, int32 CRC_nextTxd, int32* nextTxdId); diff --git a/source/game_sa/Clouds.cpp b/source/game_sa/Clouds.cpp index 0b99efe48f..39279bf3e5 100644 --- a/source/game_sa/Clouds.cpp +++ b/source/game_sa/Clouds.cpp @@ -3,9 +3,6 @@ #include "Clouds.h" #include "PostEffects.h" -// float& CClouds::m_fVolumetricCloudDensity; // unused -// bool& CClouds::m_bVolumetricCloudHeightSwitch; // unused -// float& CClouds::m_fVolumetricCloudWindMoveFactor; // unused float& CClouds::m_fVolumetricCloudMaxDistance = *reinterpret_cast(0xC6AA58); uint32& CClouds::m_VolumetricCloudsUsedNum = *reinterpret_cast(0xC6AA5C); @@ -27,19 +24,20 @@ RwTexture*& gpMoonMask = *reinterpret_cast(0xC6AA74); RwTexture*& gpCloudTex = *reinterpret_cast(0xC6AA78); RwTexture*& gpCloudMaskTex = *reinterpret_cast(0xC6AA78 + 0x4); -float& flt_C6E954 = *reinterpret_cast(0xC6E954); // see CClouds::RenderBottomFromHeight, CClouds::MovingFogRender -float& flt_C6E970 = *reinterpret_cast(0xC6E970); // see CClouds::VolumetricCloudsRender - -int32& dword_C6E974 = *reinterpret_cast(0xC6E974); // see CClouds::VolumetricCloudsRender - void CClouds::InjectHooks() { RH_ScopedClass(CClouds); RH_ScopedCategoryGlobal(); + // Clouds RH_ScopedInstall(Init, 0x7138D0); RH_ScopedInstall(Update, 0x712FF0); RH_ScopedInstall(Shutdown, 0x712FA0); RH_ScopedInstall(SetUpOneSkyPoly, 0x713060); + RH_ScopedInstall(Render, 0x713950); + RH_ScopedInstall(RenderSkyPolys, 0x714650); + RH_ScopedInstall(RenderBottomFromHeight, 0x7154B0, { .reversed = false }); + + // Moving fog RH_ScopedInstall(MovingFogInit, 0x713660); RH_ScopedInstall(MovingFog_Create, 0x713760); RH_ScopedInstall(MovingFog_Delete, 0x713730); @@ -48,15 +46,14 @@ void CClouds::InjectHooks() { RH_ScopedInstall(MovingFog_GetWind, 0x7136E0); RH_ScopedInstall(MovingFog_GetFirstFreeSlot, 0x713710); RH_ScopedInstall(MovingFogRender, 0x716C90); - RH_ScopedInstall(Render, 0x713950, { .reversed = false }); - RH_ScopedInstall(RenderSkyPolys, 0x714650); - RH_ScopedInstall(RenderBottomFromHeight, 0x7154B0, { .reversed = false }); + + // Volumetric clouds RH_ScopedInstall(VolumetricCloudsInit, 0x7131C0); - RH_ScopedInstall(VolumetricClouds_Create, 0x715F40, { .reversed = false }); + RH_ScopedInstall(VolumetricClouds_Create, 0x715F40); RH_ScopedInstall(VolumetricClouds_Delete, 0x7135F0); RH_ScopedInstall(VolumetricClouds_GetFirstFreeSlot, 0x7135C0); RH_ScopedInstall(VolumetricCloudsGetMaxDistance, 0x713630); - RH_ScopedInstall(VolumetricCloudsRender, 0x716380, { .reversed = false }); + RH_ScopedInstall(VolumetricCloudsRender, 0x716380); } // 0x7138D0 @@ -66,7 +63,7 @@ void CClouds::Init() { gpCloudTex = RwTextureRead("cloud1", nullptr); gpCloudMaskTex = RwTextureRead("cloudmasked", nullptr); gpMoonMask = RwTextureRead("lunar", "lunarm"); - ms_vc.m_pTex = RwTextureRead("cloudhigh", "cloudhighm"); + ms_vc.texture = RwTextureRead("cloudhigh", "cloudhighm"); CTxdStore::PopCurrentTxd(); CloudRotation = 0.0f; VolumetricCloudsInit(); @@ -75,6 +72,8 @@ void CClouds::Init() { // 0x712FF0 void CClouds::Update() { + ZoneScoped; + CloudRotation = std::sin(TheCamera.m_fOrientation - 0.85f) * CWeather::Wind * 0.001f + CloudRotation; IndividualRotation += (int32)((CTimer::GetTimeStep() * CWeather::Wind * 0.5f + 0.3f) * 60.0f); } @@ -87,8 +86,8 @@ void CClouds::Shutdown() { RwTextureDestroy(gpCloudMaskTex); gpCloudMaskTex = nullptr; - RwTextureDestroy(ms_vc.m_pTex); - ms_vc.m_pTex = nullptr; + RwTextureDestroy(ms_vc.texture); + ms_vc.texture = nullptr; } // 0x713060 @@ -214,6 +213,8 @@ int32 CClouds::MovingFog_GetFirstFreeSlot() { // 0x716C90 void CClouds::MovingFogRender() { + ZoneScoped; + if (MovingFog_GetFXIntensity() == 0.f || !CGame::CanSeeOutSideFromCurrArea() && FindPlayerPed()->m_nAreaCode != AREA_CODE_NORMAL_WORLD) return; @@ -221,10 +222,9 @@ void CClouds::MovingFogRender() { { const float step = CTimer::GetTimeStep() / 300.f; if (CCullZones::CamNoRain() && CCullZones::PlayerNoRain()) - CurrentFogIntensity = std::max(CurrentFogIntensity - step, 0.f); + CurrentFogIntensity = std::max(CurrentFogIntensity - step, 0.f); // Decreasing [towards 0] else - CurrentFogIntensity = std::min(CurrentFogIntensity + step, 1.f); - + CurrentFogIntensity = std::min(CurrentFogIntensity + step, 1.f); // Increasing [towards 1] if (CWeather::UnderWaterness >= CPostEffects::m_fWaterFXStartUnderWaterness) { CurrentFogIntensity = 0.f; @@ -301,9 +301,414 @@ void CClouds::MovingFogRender() { MovingFog_Update(); } +uint8 CalculateColorWithBalance(uint8 blue, float colorBalance) { + return lerp(blue, 0u, colorBalance); +} + +// From `CClouds::Render` [0x7139B2 - 0x713D2A] +void CClouds::Render_MaybeRenderMoon(float colorBalance) { + // 3D position offset of the moon relative to the camera + constexpr auto CAMERA_TO_CLOUD_OFFSET = CVector{ 0.f, -100.f, 15.f }; + + // How big the [moon] mask texture is relative to the actual moon + constexpr auto MOON_TO_MASK_SIZE_MULT = 1.7f; + + // Time range the moon is visible in + // The default value [220] means the moon is visible starting at + // 220 minutes before noon, and until 220 minutes after it + // So, that's 20:20 - 03:40 + constexpr auto MOON_VISIBILITY_RANGE_MINS = 3u * 60u + 40u; + + // Unused + //const auto clckHrs = CClock::ms_nGameClockHours; + //const auto clckMins = CClock::ms_nGameClockMinutes; + + if (!s_DebugSettings.Moon.Enabled) { + return; + } + + const auto moonVisibilityTimeMins = (size_t)std::abs(CClock::GetMinutesToday() - (float)MOON_VISIBILITY_RANGE_MINS); + if (!s_DebugSettings.Moon.Force) { + if (moonVisibilityTimeMins >= MOON_VISIBILITY_RANGE_MINS) { // Check is the moon not visible at the current time + return; + } + } + + const auto colorB = MOON_VISIBILITY_RANGE_MINS - moonVisibilityTimeMins; + static_assert(MOON_VISIBILITY_RANGE_MINS == 220); // NOTE/TODO: The above will break otherwise, as it's really just a clever trick to avoid a more complex solution + const auto colorRG = CalculateColorWithBalance(colorB, colorBalance); + if (colorRG == 0) { + return; + } + + // + // Calculate moon position on the screen [From the 3D position] + // + CVector moonPosScr; + CVector2D scrSize; + if (!CSprite::CalcScreenCoors(TheCamera.GetPosition() + CAMERA_TO_CLOUD_OFFSET, &moonPosScr, &scrSize.x, &scrSize.y, false, true)) { + return; + } + + const auto z = CDraw::GetFarClipZ(); + const auto rhw = 1.f / z; + + // + // Draw black [textureless] sprite + // + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(NULL)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + + const auto moonSz = scrSize * ((float)CCoronas::MoonSize * 2.f + 4.f); + CSprite::RenderOneXLUSprite( + { moonPosScr.x, moonPosScr.y, z }, + moonSz, + 0, 0, 0, 255, + rhw, + 255, + 0, + 0 + ); + + // + // Draw moon's mask + // + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpMoonMask))); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + + const auto moonMaskSz = moonSz * MOON_TO_MASK_SIZE_MULT; + const auto moonMaskPosScr = moonPosScr + moonMaskSz * CVector2D{ + 0.7f, + 5.4f * (((float)CClock::GetGameClockDays() / 31.f - 0.5f)) // Slowly glide on the X axis according to current game day + }; + CSprite::RenderOneXLUSprite( + { moonMaskPosScr.x, moonMaskPosScr.y, z }, + moonMaskSz, + 0, 0, 0, 0, + rhw, + 255, + 0, + 0 + ); + + // + // Draw the actual moon texture + // + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[2]))); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDDESTALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + + CSprite::RenderOneXLUSprite( + { moonPosScr.x, moonPosScr.y, z }, + moonSz, + colorRG, colorRG, (uint8)((float)colorB * 0.85f), 255, + rhw, + 255, + 0, + 0 + ); + + // + // [Cleanup]: Restore generic render states + // + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); +} + +// From `CClouds::Render` [0x713D2A - 0x714019] +// Draws the R* logo on the sky +void CClouds::Render_MaybeRenderRockstarLogo(float colorBalance) { + constexpr auto LOGO_VISIBLE_FROM_HRS = 22u, + LOGO_VISIBLE_UNTIL_HRS = 5u; + + constexpr auto R_OFFSET_FROM_CAMERA = CVector{ 100.f, 0.f, 10.f }; // Letter `R` offset from camera + constexpr auto STAR_OFFSET_FROM_CAMERA = CVector{ 100.f, 0.f, R_OFFSET_FROM_CAMERA.z - 90.f }; // `*` [As in R*] offset from camera + + constexpr auto STARS_NUM_POSITIONS = 9; + constexpr float STARS_Y_POSITIONS[STARS_NUM_POSITIONS] = { 0.00f, 0.05f, 0.13f, 0.40f, 0.70f, 0.60f, 0.27f, 0.55f, 0.75f }; // 0x8D55EC + constexpr float STARS_Z_POSITIONS[STARS_NUM_POSITIONS] = { 0.00f, 0.45f, 0.90f, 1.00f, 0.85f, 0.52f, 0.48f, 0.35f, 0.20f }; // 0x8D5610 + constexpr float STARS_SIZES[STARS_NUM_POSITIONS] = { 1.00f, 1.40f, 0.90f, 1.00f, 0.60f, 1.50f, 1.30f, 1.00f, 0.80f }; // 0x8D5634 + + if (!s_DebugSettings.Rockstar.Enabled) { + return; + } + + if (!s_DebugSettings.Rockstar.Force) { + if (!CClock::GetIsTimeInRange(LOGO_VISIBLE_FROM_HRS, LOGO_VISIBLE_UNTIL_HRS)) { + return; + } + } + + const auto time = CClock::GetGameClockHours() == LOGO_VISIBLE_FROM_HRS + ? CClock::GetGameClockMinutes() + : 60u - CClock::GetGameClockMinutes(); + + const auto colorB = 255u * time / 60u; + const auto colorRG = CalculateColorWithBalance(colorB, colorBalance); + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[0]))); + + const auto camPos = TheCamera.GetPosition(); + + // + // Draw `R` + // + for (auto i = 0; i < 11; i++) { + CVector offset = R_OFFSET_FROM_CAMERA; + if (i >= 9) { // Clever trick to save memory I guess, re-uses the first 2 star vertices, but with X adjusted to be on the flipside + offset.x = -offset.x; + } + + const auto posIdx = i % STARS_NUM_POSITIONS; + offset.y -= STARS_Y_POSITIONS[posIdx] * 90.f; + offset.z += STARS_Z_POSITIONS[posIdx] * 80.f; + + CVector starPosScr; + CVector2D starSizeScr; + if (!CSprite::CalcScreenCoors(camPos + offset, &starPosScr, &starSizeScr.x, &starSizeScr.y, false, true)) { + continue; + } + + const auto cc = CalculateColorWithBalance(colorB, (float)(rand() % 32) * 0.015f); + + starSizeScr *= STARS_SIZES[posIdx] * 0.8f; + + CSprite::RenderBufferedOneXLUSprite( + starPosScr, + starSizeScr, + cc, cc, cc, 255, + 1.f / starPosScr.z, + 255 + ); + } + + // + // Draw the `*` + // + CVector lastStarPosScr; + CVector2D lastStarSizeScr; + if (CSprite::CalcScreenCoors(camPos + STAR_OFFSET_FROM_CAMERA, &lastStarPosScr, &lastStarSizeScr.x, &lastStarSizeScr.y, false, true)) { + const auto cc = CalculateColorWithBalance(colorB, (float)(rand() % 128) * 0.0015625f + 0.5f); + + lastStarSizeScr *= 5.f; + + CSprite::RenderBufferedOneXLUSprite( + lastStarPosScr, + lastStarSizeScr, + cc, cc, cc, 255, + 1.f / lastStarPosScr.z, + 255 + ); + } + + // + // Finally, draw it + // + CSprite::FlushSpriteBuffer(); +} + +// From `CClouds::Render` [0x714019 - 0x71422A] +void CClouds::Render_RenderLowClouds(float colorBalance) { + constexpr CVector LOW_CLOUDS_COORDS[]{ + {1.0f, 0.0f, 0.0f}, + {0.7f, -0.7f, 1.0f}, + {0.0f, -1.0f, 0.5f}, + {-0.7f, -0.7f, 0.0f}, + {-1.0f, 0.0f, 1.0f}, + {-0.7f, 0.7f, 0.3f}, + {0.0f, 1.0f, 0.9f}, + {0.7f, 0.7f, 0.4f}, + {0.8f, 0.4f, 1.3f}, + {-0.8f, 0.4f, 1.4f}, + {0.4f, -0.8f, 1.2f}, + {0.4f, -0.8f, 1.7f}, + }; + + const auto colorR = CalculateColorWithBalance((uint8)CTimeCycle::m_CurrentColours.m_nLowCloudsRed, colorBalance); + const auto colorG = CalculateColorWithBalance((uint8)CTimeCycle::m_CurrentColours.m_nLowCloudsGreen, colorBalance); + const auto colorB = CalculateColorWithBalance((uint8)CTimeCycle::m_CurrentColours.m_nLowCloudsBlue, colorBalance); + + if (!s_DebugSettings.LowClouds.Enabled) { + return; + } + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCloudTex))); + + // Calculate camera roll + ms_cameraRoll = TheCamera.GetRoll(); + + // Render clouds + const auto camPos = TheCamera.GetPosition(); + for (const auto& offset : LOW_CLOUDS_COORDS) { + CVector cloudPosScr; + CVector2D cloudSizeScr; + if (!CSprite::CalcScreenCoors(camPos + offset * CVector{800.f, 800.f, 60.f} + CVector{0.f, 0.f, 40.f}, &cloudPosScr, &cloudSizeScr.x, &cloudSizeScr.y, false, true)) { + continue; + } + CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension( + cloudPosScr, + cloudSizeScr * CVector2D{ 40.f, 320.f }, + colorR, colorG, colorB, 255, + 1.f / cloudPosScr.z, + ms_cameraRoll, + 255 + ); + } + + CSprite::FlushSpriteBuffer(); +} + +// From `CClouds::Render` [0x71422A - 0x714387] +void CClouds::Render_MaybeRenderRainbows() { + constexpr size_t NUM_RAINBOW_LINES = 6; + const CRGBA RAINBOW_LINES_COLOR[]{ + {30, 0, 0, 255}, + {30, 15, 0, 255}, + {30, 30, 0, 255}, + {10, 30, 10, 255}, + {0, 0, 30, 255}, + {15, 0, 30, 255} + }; + + if (!s_DebugSettings.Rainbow.Enabled) { + return; + } + + if (!s_DebugSettings.Rainbow.Force) { + if (CWeather::Rainbow == 0.f) { + return; + } + } + + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[0]))); + + const auto camPos = TheCamera.GetPosition(); + + for (auto i = 0; i < NUM_RAINBOW_LINES; i++) { + const auto offset = CVector{ + (float)i * 1.5f, + 100.f, + 5.f + }; + + CVector rblinePosScr; + CVector2D rblineSizeScr; + if (!CSprite::CalcScreenCoors(camPos + offset, &rblinePosScr, &rblineSizeScr.x, &rblineSizeScr.y, false, true)) { + continue; + } + rblineSizeScr *= CVector2D{ 2.f, 50.f }; + + const auto& clr = RAINBOW_LINES_COLOR[i]; + CSprite::RenderBufferedOneXLUSprite( + rblinePosScr, + rblineSizeScr, + (uint8)((float)clr.r * CWeather::Rainbow), + (uint8)((float)clr.g * CWeather::Rainbow), + (uint8)((float)clr.b * CWeather::Rainbow), + clr.a, + 1.f / rblinePosScr.z, + 255 + ); + } + + CSprite::FlushSpriteBuffer(); +} + +// From `CClouds::Render` [0x714387 - 0x714640] +void CClouds::Render_MaybeRenderStreaks() { + constexpr auto REPEAT_INTERVAL_MS = 8192u; // Use power-of-2 numbers here if possible + constexpr auto VISIBILE_TIME_MS = 800u; + static_assert(REPEAT_INTERVAL_MS >= VISIBILE_TIME_MS); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDINVSRCALPHA)); + + if (!s_DebugSettings.Streaks.Enabled) { + return; + } + + if (!s_DebugSettings.Streaks.Force) { + if (CClock::GetGameClockHours() >= 5) { // Time is between 0 and 5 AM + return; + } + if (!IsExtraSunny(CWeather::OldWeatherType) || !IsExtraSunny(CWeather::NewWeatherType)) { // Both weathers must be extra sunny + return; + } + } + + const auto timeMS = CTimer::GetTimeInMS(); + + // This must always be checked, otherwise code breaks + const auto repeatDelta = timeMS % REPEAT_INTERVAL_MS; + if (repeatDelta >= VISIBILE_TIME_MS) { + return; + } + + const auto repeat64 = (timeMS / REPEAT_INTERVAL_MS) % 64; + + //> 0x714464 + const auto size = CVector{ + (float)((int32)(repeat64 % 7) - 3) * 0.1f, + (float)((int32)((timeMS & 0xFFFF) / REPEAT_INTERVAL_MS) - 4) * 0.1f, + 1.f + }.Normalized(); + + //> 0x7144C7 + const auto offsetDir = CVector{ + (float)((int32)(repeat64 % 9) - 5), + (float)((int32)(repeat64 % 10) - 5), + 0.1f + }.Normalized(); + + RenderBuffer::ClearRenderBuffer(); + + const auto PushVertex = [ + basePos = offsetDir * 1000.f + TheCamera.GetPosition(), + size + ](float scale, CRGBA color) { + RenderBuffer::PushVertex( + basePos + size * scale, + color + ); + }; + + const auto scale = (float)((VISIBILE_TIME_MS / 2 - (int32)repeatDelta) * 2); + PushVertex(scale, { 255, 255, 255, 225 }); + PushVertex(scale + 50.f, { 255, 255, 255, 0 }); + + RenderBuffer::PushIndices({ 1, 0 }, false); + + RenderBuffer::Render(rwPRIMTYPEPOLYLINE, nullptr, rwIM3D_VERTEXRGBA | rwIM3D_VERTEXXYZ); +} + // 0x713950 void CClouds::Render() { - plugin::Call<0x713950>(); + if (!CGame::CanSeeOutSideFromCurrArea()) { + return; + } + + CCoronas::SunBlockedByClouds = false; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + + CSprite::InitSpriteBuffer(); + + const auto colorBalance = std::max(CWeather::Foggyness, CWeather::CloudCoverage); + + // I've broken up this function into several others to make the code easier to understand + + Render_MaybeRenderMoon(colorBalance); // [0x7139B2 - 0x713D2A] + Render_MaybeRenderRockstarLogo(colorBalance); // 0x713D2A - 0x714019 + Render_RenderLowClouds(std::max(colorBalance, CWeather::ExtraSunnyness)); + Render_MaybeRenderRainbows(); + Render_MaybeRenderStreaks(); } // 0x714650 @@ -461,140 +866,225 @@ void CClouds::RenderSkyPolys() { // 0x7154B0 void CClouds::RenderBottomFromHeight() { - plugin::Call<0x7154B0>(); + /**** + * Code below should be good + * but it isn't complete... + *****\ + + const auto camPos = TheCamera.GetPosition(); + if (camPos.z < -90.f) { // 0x71557D [Moved up here] + return; + } + + const auto& cc = CTimeCycle::m_CurrentColours; // cc = color component + const auto ClampClr = [](float clr) { + return std::min(clr, 255.f); + }; + const auto fcClr = CRGBA{ // fc = fluffy clouds + (uint8)ClampClr(cc.m_nFluffyCloudsBottomRed * 2.f + 20.f), + (uint8)ClampClr(cc.m_nFluffyCloudsBottomGreen * 1.5f), + (uint8)ClampClr(cc.m_nFluffyCloudsBottomBlue * 1.5f), + (uint8)255 + }; + + auto lowZ = 160.f, highZ = 190.f; + + auto& windShift = StaticRef(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDINVSRCALPHA)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCloudMaskTex))); + + // TODO.... + */ } +// +// -- VOLUMETRIC CLOUDS -- +// + // 0x7131C0 void CClouds::VolumetricCloudsInit() { - ms_vc.m_vecCloudsSpace[0] = CVector(0.0f, 1.0f, 0.0f); - ms_vc.m_vecCloudsSpace[1] = CVector(0.0f, 0.0f, 1.0f); - ms_vc.m_vecCloudsSpace[2] = CVector(1.0f, 0.0f, 0.0f); - - ms_vc.m_fCloudXCoords[0] = -0.5f; - ms_vc.m_fCloudYCoords[0] = 0.0f; - ms_vc.m_fCloudZCoords[0] = 0.5f; - ms_vc.m_fCloudUCoords[0] = 0.0f; - ms_vc.m_fCloudVCoords[0] = 0.0f; - - ms_vc.m_fCloudXCoords[1] = 0.5f; - ms_vc.m_fCloudYCoords[1] = 0.0f; - ms_vc.m_fCloudZCoords[1] = 0.5f; - ms_vc.m_fCloudUCoords[1] = 1.0f; - ms_vc.m_fCloudVCoords[1] = 0.0f; - - ms_vc.m_fCloudXCoords[2] = -0.5f; - ms_vc.m_fCloudYCoords[2] = 0.0f; - ms_vc.m_fCloudZCoords[2] = -0.5f; - ms_vc.m_fCloudUCoords[2] = 0.0f; - ms_vc.m_fCloudVCoords[2] = 1.0f; - - ms_vc.m_fCloudXCoords[3] = 0.5f; - ms_vc.m_fCloudYCoords[3] = 0.0f; - ms_vc.m_fCloudZCoords[3] = 0.5f; - ms_vc.m_fCloudUCoords[3] = 1.0f; - ms_vc.m_fCloudVCoords[3] = 0.0f; - - ms_vc.m_fCloudXCoords[4] = 0.5f; - ms_vc.m_fCloudYCoords[4] = 0.0f; - ms_vc.m_fCloudZCoords[4] = -0.5f; - ms_vc.m_fCloudUCoords[4] = 1.0f; - ms_vc.m_fCloudVCoords[4] = 1.0f; - - ms_vc.m_fCloudXCoords[5] = -0.5f; - ms_vc.m_fCloudYCoords[5] = 0.0f; - ms_vc.m_fCloudZCoords[5] = -0.5f; - ms_vc.m_fCloudUCoords[5] = 0.0f; - ms_vc.m_fCloudVCoords[5] = 1.0f; - - ms_vc.m_fCloudXCoords[6] = -0.5f; - ms_vc.m_fCloudYCoords[6] = 0.5f; - ms_vc.m_fCloudZCoords[6] = 0.0f; - ms_vc.m_fCloudUCoords[6] = 0.0f; - ms_vc.m_fCloudVCoords[6] = 0.0f; - - ms_vc.m_fCloudXCoords[7] = 0.5f; - ms_vc.m_fCloudYCoords[7] = 0.5f; - ms_vc.m_fCloudZCoords[7] = 0.0f; - ms_vc.m_fCloudUCoords[7] = 1.0f; - ms_vc.m_fCloudVCoords[7] = 0.0f; - - ms_vc.m_fCloudXCoords[8] = -0.5f; - ms_vc.m_fCloudYCoords[8] = -0.5f; - ms_vc.m_fCloudZCoords[8] = 0.0f; - ms_vc.m_fCloudUCoords[8] = 0.0f; - ms_vc.m_fCloudVCoords[8] = 1.0f; - - ms_vc.m_fCloudXCoords[9] = 0.5f; - ms_vc.m_fCloudYCoords[9] = 0.5f; - ms_vc.m_fCloudZCoords[9] = 0.0f; - ms_vc.m_fCloudUCoords[9] = 1.0f; - ms_vc.m_fCloudVCoords[9] = 0.0f; - - ms_vc.m_fCloudXCoords[10] = 0.5f; - ms_vc.m_fCloudYCoords[10] = -0.5f; - ms_vc.m_fCloudZCoords[10] = 0.0f; - ms_vc.m_fCloudUCoords[10] = 1.0f; - ms_vc.m_fCloudVCoords[10] = 1.0f; - - ms_vc.m_fCloudXCoords[11] = -0.5f; - ms_vc.m_fCloudYCoords[11] = -0.5f; - ms_vc.m_fCloudZCoords[11] = 0.0f; - ms_vc.m_fCloudUCoords[11] = 0.0f; - ms_vc.m_fCloudVCoords[11] = 1.0f; - - ms_vc.m_fCloudXCoords[12] = 0.0f; - ms_vc.m_fCloudYCoords[12] = -0.5f; - ms_vc.m_fCloudZCoords[12] = 0.5f; - ms_vc.m_fCloudUCoords[12] = 0.0f; - ms_vc.m_fCloudVCoords[12] = 0.0f; - - ms_vc.m_fCloudXCoords[13] = 0.0f; - ms_vc.m_fCloudYCoords[13] = 0.5f; - ms_vc.m_fCloudZCoords[13] = 0.5f; - ms_vc.m_fCloudUCoords[13] = 1.0f; - ms_vc.m_fCloudVCoords[13] = 0.0f; - - ms_vc.m_fCloudXCoords[14] = 0.0f; - ms_vc.m_fCloudYCoords[14] = -0.5f; - ms_vc.m_fCloudZCoords[14] = -0.5f; - ms_vc.m_fCloudUCoords[14] = 0.0f; - ms_vc.m_fCloudVCoords[14] = 1.0f; - - ms_vc.m_fCloudXCoords[15] = 0.0f; - ms_vc.m_fCloudYCoords[15] = 0.5f; - ms_vc.m_fCloudZCoords[15] = 0.5f; - ms_vc.m_fCloudUCoords[15] = 1.0f; - ms_vc.m_fCloudVCoords[15] = 0.0f; - - ms_vc.m_fCloudXCoords[16] = 0.0f; - ms_vc.m_fCloudYCoords[16] = 0.5f; - ms_vc.m_fCloudZCoords[16] = -0.5f; - ms_vc.m_fCloudUCoords[16] = 1.0f; - ms_vc.m_fCloudVCoords[16] = 1.0f; - - ms_vc.m_fCloudXCoords[17] = 0.0f; - ms_vc.m_fCloudYCoords[17] = -0.5f; - ms_vc.m_fCloudZCoords[17] = -0.5f; - ms_vc.m_fCloudUCoords[17] = 0.0f; - ms_vc.m_fCloudVCoords[17] = 1.0f; + ms_vc.quadNormal[0] = CVector(0.0f, 1.0f, 0.0f); + ms_vc.quadNormal[1] = CVector(0.0f, 0.0f, 1.0f); + ms_vc.quadNormal[2] = CVector(1.0f, 0.0f, 0.0f); + + ms_vc.modelX[0] = -0.5f; + ms_vc.modelY[0] = 0.0f; + ms_vc.modelZ[0] = 0.5f; + ms_vc.modelU[0] = 0.0f; + ms_vc.modelV[0] = 0.0f; + + ms_vc.modelX[1] = 0.5f; + ms_vc.modelY[1] = 0.0f; + ms_vc.modelZ[1] = 0.5f; + ms_vc.modelU[1] = 1.0f; + ms_vc.modelV[1] = 0.0f; + + ms_vc.modelX[2] = -0.5f; + ms_vc.modelY[2] = 0.0f; + ms_vc.modelZ[2] = -0.5f; + ms_vc.modelU[2] = 0.0f; + ms_vc.modelV[2] = 1.0f; + + ms_vc.modelX[3] = 0.5f; + ms_vc.modelY[3] = 0.0f; + ms_vc.modelZ[3] = 0.5f; + ms_vc.modelU[3] = 1.0f; + ms_vc.modelV[3] = 0.0f; + + ms_vc.modelX[4] = 0.5f; + ms_vc.modelY[4] = 0.0f; + ms_vc.modelZ[4] = -0.5f; + ms_vc.modelU[4] = 1.0f; + ms_vc.modelV[4] = 1.0f; + + ms_vc.modelX[5] = -0.5f; + ms_vc.modelY[5] = 0.0f; + ms_vc.modelZ[5] = -0.5f; + ms_vc.modelU[5] = 0.0f; + ms_vc.modelV[5] = 1.0f; + + ms_vc.modelX[6] = -0.5f; + ms_vc.modelY[6] = 0.5f; + ms_vc.modelZ[6] = 0.0f; + ms_vc.modelU[6] = 0.0f; + ms_vc.modelV[6] = 0.0f; + + ms_vc.modelX[7] = 0.5f; + ms_vc.modelY[7] = 0.5f; + ms_vc.modelZ[7] = 0.0f; + ms_vc.modelU[7] = 1.0f; + ms_vc.modelV[7] = 0.0f; + + ms_vc.modelX[8] = -0.5f; + ms_vc.modelY[8] = -0.5f; + ms_vc.modelZ[8] = 0.0f; + ms_vc.modelU[8] = 0.0f; + ms_vc.modelV[8] = 1.0f; + + ms_vc.modelX[9] = 0.5f; + ms_vc.modelY[9] = 0.5f; + ms_vc.modelZ[9] = 0.0f; + ms_vc.modelU[9] = 1.0f; + ms_vc.modelV[9] = 0.0f; + + ms_vc.modelX[10] = 0.5f; + ms_vc.modelY[10] = -0.5f; + ms_vc.modelZ[10] = 0.0f; + ms_vc.modelU[10] = 1.0f; + ms_vc.modelV[10] = 1.0f; + + ms_vc.modelX[11] = -0.5f; + ms_vc.modelY[11] = -0.5f; + ms_vc.modelZ[11] = 0.0f; + ms_vc.modelU[11] = 0.0f; + ms_vc.modelV[11] = 1.0f; + + ms_vc.modelX[12] = 0.0f; + ms_vc.modelY[12] = -0.5f; + ms_vc.modelZ[12] = 0.5f; + ms_vc.modelU[12] = 0.0f; + ms_vc.modelV[12] = 0.0f; + + ms_vc.modelX[13] = 0.0f; + ms_vc.modelY[13] = 0.5f; + ms_vc.modelZ[13] = 0.5f; + ms_vc.modelU[13] = 1.0f; + ms_vc.modelV[13] = 0.0f; + + ms_vc.modelX[14] = 0.0f; + ms_vc.modelY[14] = -0.5f; + ms_vc.modelZ[14] = -0.5f; + ms_vc.modelU[14] = 0.0f; + ms_vc.modelV[14] = 1.0f; + + ms_vc.modelX[15] = 0.0f; + ms_vc.modelY[15] = 0.5f; + ms_vc.modelZ[15] = 0.5f; + ms_vc.modelU[15] = 1.0f; + ms_vc.modelV[15] = 0.0f; + + ms_vc.modelX[16] = 0.0f; + ms_vc.modelY[16] = 0.5f; + ms_vc.modelZ[16] = -0.5f; + ms_vc.modelU[16] = 1.0f; + ms_vc.modelV[16] = 1.0f; + + ms_vc.modelX[17] = 0.0f; + ms_vc.modelY[17] = -0.5f; + ms_vc.modelZ[17] = -0.5f; + ms_vc.modelU[17] = 0.0f; + ms_vc.modelV[17] = 1.0f; for (auto i = 0u; i < MAX_VOLUMETRIC_CLOUDS; ++i) { - ms_vc.m_bSlots[i] = false; - ms_vc.m_bInsideVisibilityRange[i] = 0; + ms_vc.bUsed[i] = false; + ms_vc.bJustCreated[i] = 0; } } -// see VolumetricClouds_GetFirstFreeSlot // 0x715F40 void CClouds::VolumetricClouds_Create(CVector* posn) { - plugin::Call<0x715F40, CVector*>(posn); + using CGeneral::GetRandomNumberInRange; + + const auto rand1To5 = GetRandomNumberInRange(1.f, 5.f); + + const auto AddVolumetricCloud = [ + randMinSizeXYZ = rand1To5 * 20.f, + randMaxSizeXY = rand1To5 * 100.f, + randMaxSizeZ = rand1To5 * 40.f + ](int32 vcidx, CVector pos) { + ms_vc.bUsed[vcidx] = true; + ms_vc.bJustCreated[vcidx] = true; + ms_vc.alpha[vcidx] = GetRandomNumberInRange(36, 128); + + ms_vc.size[vcidx] = CVector{ + GetRandomNumberInRange(randMinSizeXYZ, randMaxSizeXY), + GetRandomNumberInRange(randMinSizeXYZ, randMaxSizeXY), + GetRandomNumberInRange(randMinSizeXYZ, randMaxSizeZ), + }; + + ms_vc.pos[vcidx] = pos; + }; + + if (posn) { + const auto randMinMaxOffsetXYZ = rand1To5 * 3.f; + for (auto i = 0; i < 5; i++) { + const auto vcidx = VolumetricClouds_GetFirstFreeSlot(); + if (vcidx == -1) { + return; + } + const auto pos = *posn = *posn + CVector::Random(-randMinMaxOffsetXYZ, randMinMaxOffsetXYZ); + AddVolumetricCloud( + vcidx, + pos + ); + } + } else { + const auto camPos = TheCamera.GetPosition(); + const auto maxDist = m_fVolumetricCloudMaxDistance; + for (auto i = 0; i < MAX_VOLUMETRIC_CLOUDS; i++) { + AddVolumetricCloud( + i, + camPos + CVector{ + GetRandomNumberInRange(-maxDist, maxDist), + GetRandomNumberInRange(-maxDist, maxDist), + GetRandomNumberInRange(-maxDist * 0.25f, maxDist * 0.25f), + } + ); + } + } } // 0x7135F0 void CClouds::VolumetricClouds_Delete(int32 vcSlotIndex) { vcSlotIndex = std::clamp(vcSlotIndex, 0, MAX_VOLUMETRIC_CLOUDS - 1); - ms_vc.m_bSlots[vcSlotIndex] = false; - ms_vc.m_bInsideVisibilityRange[vcSlotIndex] = false; + ms_vc.bUsed[vcSlotIndex] = false; + ms_vc.bJustCreated[vcSlotIndex] = false; } // unused @@ -602,20 +1092,207 @@ void CClouds::VolumetricClouds_Delete(int32 vcSlotIndex) { // 0x7135C0 int32 CClouds::VolumetricClouds_GetFirstFreeSlot() { for (auto i = 0u; i < m_VolumetricCloudsUsedNum; i++) { - if (!ms_vc.m_bSlots[i]) - return i; + if (!ms_vc.bUsed[i]) { + return (int32)i; + } } - return -1; } // 0x713630 float CClouds::VolumetricCloudsGetMaxDistance() { - const auto farPlane = RwCameraGetFarClipPlane(Scene.m_pRwCamera); - return farPlane < 600.0f ? farPlane : 600.0f; + return std::min(RwCameraGetFarClipPlane(Scene.m_pRwCamera), 600.f); } // 0x716380 void CClouds::VolumetricCloudsRender() { - plugin::Call<0x716380>(); + ZoneScoped; + + if (!s_DebugSettings.VolumetricClouds.Enabled) { + return; + } + + const auto plyr = FindPlayerPed(); + if (!CGame::CanSeeOutSideFromCurrArea() || !plyr->IsInCurrentArea()) { + if (!s_DebugSettings.VolumetricClouds.Force) { + return; + } + } + + m_fVolumetricCloudDensity = [] { + switch (g_fx.GetFxQuality()) { + case FX_QUALITY_LOW: return 0.5f; + case FX_QUALITY_MEDIUM: return 1.f / 3.f; + default: return 1.f; + } + }(); + + m_VolumetricCloudsUsedNum = lerp(0, MAX_VOLUMETRIC_CLOUDS, m_fVolumetricCloudDensity); + + if (!m_VolumetricCloudsUsedNum) { + return; + } + + m_fVolumetricCloudMaxDistance = CClouds::VolumetricCloudsGetMaxDistance(); + + const auto fadeOutBeginDist = m_fVolumetricCloudMaxDistance - 100.f; + const auto fadeOutDist = m_fVolumetricCloudMaxDistance + 200.f; + + const auto camPos = TheCamera.GetPosition(); + + auto& gfVolumetricCloudFader = StaticRef(); + if (m_bVolumetricCloudHeightSwitch) { + const auto delta = CTimer::GetTimeStep() * 4.f; + if (camPos.z < 220.f) { + gfVolumetricCloudFader += delta; + if (gfVolumetricCloudFader >= 255.f) { + gfVolumetricCloudFader = 255.f; + return; + } + } else { + gfVolumetricCloudFader = std::max(0.f, gfVolumetricCloudFader - delta); + } + } else { + gfVolumetricCloudFader = 0.f; + } + + CPostEffects::ImmediateModeRenderStatesStore(); + CPostEffects::ImmediateModeRenderStatesSet(); + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(ms_vc.texture))); + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, RWRSTATE(rwFILTERLINEAR)); + + FindPlayerPed()->GetMoveSpeed() = CVector{0.f, 0.f, 0.f}; + + const auto plyrPos = FindPlayerCoors(); + const auto plyrVeh = FindPlayerVehicle(); + + + //> 0x71653F + auto& gVecCameraCoors = StaticRef(); + auto& gVecPlayerCoors = StaticRef(); + + const auto bIsCameraOrPlayerPosNotStatic = (camPos != gVecCameraCoors) || (plyrPos != gVecPlayerCoors); + + gVecCameraCoors = camPos; + gVecPlayerCoors = plyrPos; + + if (bIsCameraOrPlayerPosNotStatic) { // If player/it's veh has moved, recreate the clouds + const auto t = plyrVeh ? (CPlaceable*)plyrVeh : (CPlaceable*)&TheCamera; + auto pos = ( + t->GetPosition() + + t->GetForward() * fadeOutDist + + CVector::Random( + CVector{-200.f, -200.f, -50.f}, + CVector{ 200.f, 200.f, 50.f} + ) + ); + VolumetricClouds_Create(&pos); + } + + //> 0x7166DD - Calculate color + const auto& cc = CTimeCycle::m_CurrentColours; + const auto vcClr = (uint8)std::min(( + cc.m_nSkyTopRed + + cc.m_nSkyTopGreen + + cc.m_nSkyTopBlue + + cc.m_nSkyBottomRed + + cc.m_nSkyBottomGreen + + cc.m_nSkyBottomBlue + ) / 6 + 64, 255); + + if (!m_VolumetricCloudsUsedNum) { + CPostEffects::ImmediateModeRenderStatesReStore(); + return; + } + + // + // Actually render the vc's + // + + + // NOTE: They didn't use RenderBuffer functions, but to make our life easier [and the code nicer] we do + RenderBuffer::ClearRenderBuffer(); + + const auto RenderOutBuffer = [] { + RenderBuffer::Render(rwPRIMTYPETRILIST, nullptr, rwIM3D_VERTEXXYZ | rwIM3D_VERTEXUV, false); + }; + + for (auto vcidx = 0u; vcidx < m_VolumetricCloudsUsedNum; vcidx++) { + if (!ms_vc.bUsed[vcidx]) { + continue; + } + + auto& vcPos = ms_vc.pos[vcidx]; + auto& vcSz = ms_vc.size[vcidx]; + + const auto vcDistToCam = (camPos - vcPos).Magnitude(); + + //> 0x71674E - Adjust vc position by wind [TODO: Should use TimeStep/Framedelta] + vcPos += CVector{ m_fVolumetricCloudWindMoveFactor * CVector2D{ CWeather::WindDir } }; + + //> 0x716772 - VC too far, delete it + if (!ms_vc.bJustCreated[vcidx] && vcDistToCam > m_fVolumetricCloudMaxDistance) { + VolumetricClouds_Delete(vcidx); + continue; + } + + if (vcDistToCam <= m_fVolumetricCloudMaxDistance || fadeOutDist <= vcDistToCam) { + ms_vc.bJustCreated[vcidx] = false; + } + + //> 0x7167DC - Alpha calculation [I don't understand it either] + auto vcAlpha = std::max(0, ms_vc.alpha[vcidx] - (int32)gfVolumetricCloudFader); + if (vcDistToCam > fadeOutBeginDist) { + if (!vcAlpha) { + continue; + } + if (vcDistToCam > m_fVolumetricCloudMaxDistance) { + continue; + } + const auto distAlpha = std::max(0, (int32)(((m_fVolumetricCloudMaxDistance - fadeOutBeginDist) - (vcDistToCam - fadeOutBeginDist)) * (float)vcAlpha / (m_fVolumetricCloudMaxDistance - fadeOutBeginDist))); + vcAlpha = std::min(vcAlpha, distAlpha); + if (!vcAlpha) { + continue; + } + } + + // Direction of vc to camera + const auto vcToCamDir = (vcPos - camPos).Normalized(); + + //> 0x7168F0 - Calculate quad colors + CRGBA quadColors[3]; + for (auto i = 0; i < 3; i++) { + quadColors[i] = { + vcClr, + vcClr, + vcClr, + (uint8)(std::abs(vcToCamDir.Dot(ms_vc.quadNormal[i])) * (float)vcAlpha) + }; + } + + //> 0x7169F0 - Each quad has 3 vertices [As each quad consists of 2 triangles, each with 3 vertices] + for (auto k = 0; k < 3 * 6; k++) { + const auto quadIdx = k / 6; + + // If the buffer is full - Render it to make space for the upcoming vertices + // This does differ a little from what they did originally, but it's fine, as the + // end result is the same + if (!RenderBuffer::CanFitVertices(3)) { // Must be rendered in groups of 3, otherwise triangles would be rendered with the wrong vertices + RenderOutBuffer(); + } + + RenderBuffer::PushVertex( + vcPos + CVector{ ms_vc.modelX[k], ms_vc.modelY[k], ms_vc.modelZ[k] } * vcSz, + CVector2D{ms_vc.modelU[k], ms_vc.modelV[k]}, + quadColors[k / 6] + ); + } + } + + // Render whatever remains in the buffer + RenderOutBuffer(); + + CPostEffects::ImmediateModeRenderStatesReStore(); } diff --git a/source/game_sa/Clouds.h b/source/game_sa/Clouds.h index ef2927f431..7c8cee684b 100644 --- a/source/game_sa/Clouds.h +++ b/source/game_sa/Clouds.h @@ -33,25 +33,31 @@ struct tMovingFog { }; struct tVolumetricClouds { - bool m_bSlots[MAX_VOLUMETRIC_CLOUDS]; - bool m_bInsideVisibilityRange[MAX_VOLUMETRIC_CLOUDS]; - CVector field_168[MAX_VOLUMETRIC_CLOUDS]; - CVector field_9D8[MAX_VOLUMETRIC_CLOUDS]; - int32 m_nHeight[MAX_VOLUMETRIC_CLOUDS]; - RwTexture* m_pTex; - CVector m_vecCloudsSpace[3]; - float m_fCloudXCoords[18]; - float m_fCloudYCoords[18]; - float m_fCloudZCoords[18]; - float m_fCloudUCoords[18]; - float m_fCloudVCoords[18]; + bool bUsed[MAX_VOLUMETRIC_CLOUDS]; + bool bJustCreated[MAX_VOLUMETRIC_CLOUDS]; + + CVector pos[MAX_VOLUMETRIC_CLOUDS]; + CVector size[MAX_VOLUMETRIC_CLOUDS]; + + int32 alpha[MAX_VOLUMETRIC_CLOUDS]; + + RwTexture* texture; + + CVector quadNormal[3]; + + float modelX[18]; + float modelY[18]; + float modelZ[18]; + + float modelU[18]; + float modelV[18]; }; class CClouds { public: - static float& m_fVolumetricCloudDensity; // default 1.0f - static bool& m_bVolumetricCloudHeightSwitch; // default true - static float& m_fVolumetricCloudWindMoveFactor; // default 0.1f + static inline auto& m_fVolumetricCloudDensity = StaticRef(); + static inline auto& m_bVolumetricCloudHeightSwitch = StaticRef(); + static inline auto& m_fVolumetricCloudWindMoveFactor = StaticRef(); static float& m_fVolumetricCloudMaxDistance; static uint32& m_VolumetricCloudsUsedNum; static float& ms_cameraRoll; @@ -62,6 +68,12 @@ class CClouds { static CVector& PlayerCoords; static CVector& CameraCoors; + static inline struct DebugSettings { + struct RenderSettingPair { + bool Enabled = true, Force = false; + } Moon, Rockstar, LowClouds, Rainbow, Streaks, VolumetricClouds; + } s_DebugSettings; + public: static void InjectHooks(); @@ -76,6 +88,11 @@ class CClouds { static void MovingFog_Delete(int32 fogSlotIndex); static void MovingFog_Update(); static void MovingFogRender(); + static void Render_MaybeRenderMoon(float colorBalance); + static void Render_MaybeRenderRockstarLogo(float colorBalance); + static void Render_RenderLowClouds(float colorBalance); + static void Render_MaybeRenderRainbows(); + static void Render_MaybeRenderStreaks(); static float MovingFog_GetFXIntensity(); static CVector MovingFog_GetWind(); static int32 MovingFog_GetFirstFreeSlot(); @@ -92,15 +109,6 @@ class CClouds { static void RenderBottomFromHeight(); }; -extern uint8* RAINBOW_LINES_COLOR_RED; // RAINBOW_LINES_COLOR_RED[6] = { 30, 30, 30, 10, 0, 15 } -extern uint8* RAINBOW_LINES_COLOR_GREEN; // RAINBOW_LINES_COLOR_GREEN[6] = { 0, 15, 30, 30, 0, 0 } -extern uint8* RAINBOW_LINES_COLOR_BLUE; // RAINBOW_LINES_COLOR_BLUE[6] = { 0, 0, 0, 10, 30, 30 } -extern float* LOW_CLOUDS_X_COORDS; // LOW_CLOUDS_X_COORDS[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 } -extern float* LOW_CLOUDS_Y_COORDS; // LOW_CLOUDS_Y_COORDS[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 } -extern float* LOW_CLOUDS_Z_COORDS; // LOW_CLOUDS_Z_COORDS[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 } -extern float* STARS_Y_POSITIONS; // STARS_Y_POSITIONS[9] = { 0.0f, 0.05f, 0.13f, 0.4f, 0.7f, 0.6f, 0.27f, 0.55f, 0.75f } -extern float* STARS_Z_POSITIONS; // STARS_Z_POSITIONS[9] = { 0.0f, 0.45f, 0.9f, 1.0f, 0.85f, 0.52f, 0.48f, 0.35f, 0.2f } -extern float* STARS_SIZES; // STARS_SIZES[9] = { 1.0f, 1.4f, 0.9f, 1.0f, 0.6f, 1.5f, 1.3f, 1.0f, 0.8f } extern float& CurrentFogIntensity; // default 1.0f extern RwTexture*& gpMoonMask; extern RwTexture*& gpCloudTex; diff --git a/source/game_sa/Collision/ColAccel.cpp b/source/game_sa/Collision/ColAccel.cpp index 7140f72a9f..8ad2c115b8 100644 --- a/source/game_sa/Collision/ColAccel.cpp +++ b/source/game_sa/Collision/ColAccel.cpp @@ -39,6 +39,8 @@ bool CColAccel::isCacheLoading() { // 0x5B31A0 void CColAccel::startCache() { + ZoneScoped; + m_iCachingColSize = GetColModelPool()->GetSize(); m_iSectionSize = new int32[64]; m_iplDefs = new IplDef[TOTAL_IPL_MODEL_IDS](); @@ -47,6 +49,8 @@ void CColAccel::startCache() { // 0x5B2AD0 void CColAccel::endCache() { + ZoneScoped; + if (m_iCacheState == eColAccelState::COLACCEL_STARTED) { auto* file = CFileMgr::OpenFileForWriting(mp_cCacheName); CFileMgr::Write(file, &m_iNumColItems, sizeof(m_iNumColItems)); diff --git a/source/game_sa/Collision/ColStore.cpp b/source/game_sa/Collision/ColStore.cpp index 992687351b..93e952a3c1 100644 --- a/source/game_sa/Collision/ColStore.cpp +++ b/source/game_sa/Collision/ColStore.cpp @@ -138,8 +138,9 @@ int32 CColStore::FindColSlot() } // 0x410EC0 -void CColStore::BoundingBoxesPostProcess() -{ +void CColStore::BoundingBoxesPostProcess() { + ZoneScoped; + for (auto i = 1; i < ms_pColPool->GetSize(); i++) { auto* def = ms_pColPool->GetAt(i); if (!def) @@ -241,8 +242,9 @@ bool CColStore::HasCollisionLoaded(const CVector& pos, int32 areaCode) } // 0x4113D0 -void CColStore::LoadAllBoundingBoxes() -{ +void CColStore::LoadAllBoundingBoxes() { + ZoneScoped; + if (CColAccel::isCacheLoading()) CColAccel::cacheLoadCol(); else @@ -250,8 +252,7 @@ void CColStore::LoadAllBoundingBoxes() } // 0x410E60 -void CColStore::LoadAllCollision() -{ +void CColStore::LoadAllCollision() { for (auto i = 1; i < ms_pColPool->GetSize(); i++) { auto* def = ms_pColPool->GetAt(i); if (!def) @@ -353,8 +354,9 @@ void CColStore::LoadCollision(CVector pos, bool bIgnorePlayerVeh) } // 0x410E00 -void CColStore::RemoveAllCollision() -{ +void CColStore::RemoveAllCollision() { + ZoneScoped; + for (auto i = 1; i < ms_pColPool->GetSize(); i++) { auto* def = ms_pColPool->GetAt(i); if (!def) diff --git a/source/game_sa/Collision/Collision.cpp b/source/game_sa/Collision/Collision.cpp index 75207296db..58b81d2578 100644 --- a/source/game_sa/Collision/Collision.cpp +++ b/source/game_sa/Collision/Collision.cpp @@ -27,6 +27,8 @@ using Shape = CCollision::DebugSettings::ShapeShapeCollision::Shape; * @address 0x416260 */ void CCollision::Init() { + ZoneScoped; + ms_colModelCache.Init(50); ms_collisionInMemory = 0; CColStore::Initialise(); @@ -57,6 +59,8 @@ void CCollision::SortOutCollisionAfterLoad() { // 0x416330 void CCollision::CalculateTrianglePlanes(CCollisionData* colData) { + ZoneScoped; + if (!colData->m_nNumTriangles) return; @@ -80,6 +84,8 @@ void CCollision::CalculateTrianglePlanes(CCollisionData* colData) { // 0x416400 void CCollision::RemoveTrianglePlanes(CCollisionData* colData) { + ZoneScoped; + if (!colData->m_pTrianglePlanes) return; @@ -90,6 +96,8 @@ void CCollision::RemoveTrianglePlanes(CCollisionData* colData) { // 0x411E70 bool CCollision::TestSphereSphere(CColSphere const& sphere1, CColSphere const& sphere2) { // Yes, it's __stdcall + ZoneScoped; + return (sphere1.m_vecCenter - sphere2.m_vecCenter).SquaredMagnitude() <= sq(sphere1.m_fRadius + sphere2.m_fRadius); } @@ -128,6 +136,8 @@ void CalculateColPointInsideBox(CBox const& box, CVector const& point, CColPoint * @brief Tests if the \a bb is fully inside \a sphere */ bool CCollision::TestSphereBox(CSphere const& sphere, CBox const& box) { + ZoneScoped; + for (auto i = 0u; i < 3u; i++) { if (sphere.m_vecCenter[i] + sphere.m_fRadius < box.m_vecMin[i] || sphere.m_vecCenter[i] - sphere.m_fRadius > box.m_vecMax[i] @@ -142,6 +152,8 @@ bool CCollision::TestSphereBox(CSphere const& sphere, CBox const& box) { * @address 0x412130 */ bool CCollision::ProcessSphereBox(CColSphere const& sph, CColBox const& box, CColPoint & colp, float& minDistSq) { + ZoneScoped; + // GTA's code is too complicated, uses a huge 3x3x3 if statement // we can simplify the structure a lot // Some of the original code, to give you an idea: @@ -335,6 +347,8 @@ bool CCollision::ProcessSphereBox(CColSphere const& sph, CColBox const& box, CCo * @address 0x412700 */ bool __stdcall CCollision::PointInTriangle(CVector const& point, CVector const* triPoints) { + ZoneScoped; + // Make everything relative to 0th vertex of the triangle const auto v1 = triPoints[1] - triPoints[0]; const auto v2 = triPoints[2] - triPoints[0]; @@ -373,6 +387,8 @@ bool __stdcall CCollision::PointInTriangle(CVector const& point, CVector const* * @returns Sq. dist. from `pt` to point closest to `pt` on the line segment (ln0, ln1) */ float CCollision::DistToLineSqr(CVector const& ln0, CVector const& ln1, CVector const& pt) { + ZoneScoped; + // Make line end (l) and pt (pl_ip) relative to ln0 (by this ln0 becomes the space origin) const auto l = ln1 - ln0; const auto p = pt - ln0; @@ -416,6 +432,8 @@ float CCollision::DistToLineSqr(CVector const& ln0, CVector const& ln1, CVector // 0x417610 float CCollision::DistToLine(const CVector& lineStart, const CVector& lineEnd, const CVector& point) { + ZoneScoped; + return std::sqrt(DistToLineSqr(lineStart, lineEnd, point)); } @@ -424,6 +442,8 @@ float CCollision::DistToLine(const CVector& lineStart, const CVector& lineEnd, c * @brief Similar to \r DistToLineSqr it always returns the distance to the projected intersection point. */ float CCollision::DistToMathematicalLine(CVector const* lineStart, CVector const* lineEnd, CVector const* point) { + ZoneScoped; + const auto l = *lineEnd - *lineStart; const auto p = *point - *lineStart; @@ -443,6 +463,8 @@ float CCollision::DistToMathematicalLine(CVector const* lineStart, CVector const * @brief Same as \r DistToMathematicalLine but in 2D */ float CCollision::DistToMathematicalLine2D(float lineStartX, float lineStartY, float lineEndX, float lineEndY, float pointX, float pointY) { + ZoneScoped; + const float px{ pointX - lineStartX }, py{ pointY - lineStartY }; const auto dot = px * lineEndX + py * lineEndY; const auto distSq = px * px + py * py - dot * dot; @@ -454,6 +476,8 @@ float CCollision::DistToMathematicalLine2D(float lineStartX, float lineStartY, f * @brief TODO */ float CCollision::DistAlongLine2D(float lineX, float lineY, float lineDirX, float lineDirY, float pointX, float pointY) { + ZoneScoped; + return (pointX - lineX) * lineDirX + (pointY - lineY) * lineDirY; } @@ -464,6 +488,8 @@ float CCollision::DistAlongLine2D(float lineX, float lineY, float lineDirX, floa * @notsa */ CVector CCollision::GetClosestPtOnLine(const CVector& l0, const CVector& l1, const CVector& point) { + ZoneScoped; + const auto lnMagSq = (l1 - l0).SquaredMagnitude(); const auto dot = (point - l0).Dot(l1 - l0); if (dot <= 0.0f) { @@ -478,6 +504,8 @@ CVector CCollision::GetClosestPtOnLine(const CVector& l0, const CVector& l1, con // 0x417FD0 void CCollision::ClosestPointOnLine(const CVector& l0, const CVector& l1, const CVector& point, CVector& closest) { + ZoneScoped; + closest = GetClosestPtOnLine(l0, l1, point); } @@ -511,6 +539,8 @@ float CCollision::ClosestPtSegmentSegment( float& s, float& t, CVector& c1, CVector& c2 ) { + ZoneScoped; + // Must be non-negative! assert(a >= 0.f); assert(e >= 0.f); @@ -691,6 +721,8 @@ float ClosestSquaredDistanceBetweenFiniteLines( * @notsa */ CVector CCollision::GetBaryCoordsOnTriangle(CVector a, CVector b, CVector c, CVector p) { + ZoneScoped; + const auto vab = b - a, vac = c - a, vap = p - a; const auto bb = vab.Dot(vab); const auto bc = vab.Dot(vac); @@ -711,6 +743,8 @@ CVector CCollision::GetBaryCoordsOnTriangle(CVector a, CVector b, CVector c, CVe * @notsa */ CVector CCollision::GetClampedBaryCoordsIntoTriangle(CVector a, CVector b, CVector c, CVector p) { + ZoneScoped; + // Calculate barycentric coords const auto [u, v, w] = GetBaryCoordsOnTriangle(a, b, c, p); @@ -745,6 +779,8 @@ CVector CCollision::GetClampedBaryCoordsIntoTriangle(CVector a, CVector b, CVect * I'm leaving it here (even though it's unused), as it might actually be faster than the abovementioned function . */ CVector CCollision::GetCoordsClampedIntoTriangle(CVector a, CVector b, CVector c, CVector p) { + ZoneScoped; + const auto [u, v, w] = GetClampedBaryCoordsIntoTriangle(a, b, c, p); return a * u + b * v + c * w; } @@ -891,6 +927,8 @@ NOTSA_FORCEINLINE bool ProcessLineSphere_Internal( * @param[in,out] depth `t` parameter - relative distance on line from it's origin (`line.start`) */ bool CCollision::ProcessLineSphere(CColLine const& line, CColSphere const& sphere, CColPoint& colPoint, float& depth) { + ZoneScoped; + if (!ProcessLineSphere_Internal(line, sphere, &colPoint.m_vecPoint, &depth)) { return false; } @@ -913,12 +951,16 @@ bool CCollision::TestLineSphere( const CColLine& line, const CColSphere& sphere ) { + ZoneScoped; + return ProcessLineSphere_Internal(line, sphere, nullptr, nullptr); } // 0x412C70 // Maybe just adapt the code from: http://www.3dkingdoms.com/weekly/weekly.php?a=3 ? Looks nicer than this one bool CCollision::TestLineBox_DW(CColLine const& line, CBox const& box) { + ZoneScoped; + const auto IsInBox = [bb = CBoundingBox(box)](const CVector& point) { return bb.IsPointWithin(point); }; @@ -1003,6 +1045,8 @@ bool CCollision::TestLineBox_DW(CColLine const& line, CBox const& box) { // 0x413070 bool CCollision::TestLineBox(CColLine const& line, CBox const& box) { + ZoneScoped; + return TestLineBox_DW(line, box); } @@ -1011,6 +1055,8 @@ bool CCollision::TestLineBox(CColLine const& line, CBox const& box) { * @brief Test vertical \a line against \a bb */ bool CCollision::TestVerticalLineBox(CColLine const& line, CBox const& box) { + ZoneScoped; + for (auto i = 0u; i < 2u; i++) { // Deal with x, y axies if ( line.m_vecStart[i] <= box.m_vecMin[i] || line.m_vecStart[i] >= box.m_vecMax[i] @@ -1035,6 +1081,8 @@ bool CCollision::TestVerticalLineBox(CColLine const& line, CBox const& box) { * @returns If there was a collision or not. If there was a collision, but calculated depth is bigger than `maxTouchDistance` it returns false regardless. */ bool CCollision::ProcessLineBox(CColLine const& line, CColBox const& box, CColPoint& colPoint, float& maxTouchDistance) { + ZoneScoped; + float mint, t, x, y, z; CVector normal; CVector p; @@ -1155,6 +1203,8 @@ bool CCollision::ProcessLineBox(CColLine const& line, CColBox const& box, CColPo * @returns If there was an intersection - TODO: What if lines are colinear? */ bool CCollision::Test2DLineAgainst2DLine(float line1StartX, float line1StartY, float line1EndX, float line1EndY, float line2StartX, float line2StartY, float line2EndX, float line2EndY) { + ZoneScoped; + return ((line2StartX - line1StartX + line2EndX) * line1EndY - (line2StartY - line1StartY + line2EndY) * line1EndX) * ((line2StartX - line1StartX) * line1EndY - (line2StartY - line1StartY) * line1EndX) <= 0.0 && @@ -1184,6 +1234,8 @@ bool CCollision::ProcessDiscCollision( float& lineRatio, CColPoint& lineColPoint ) { + ZoneScoped; + const auto cp = matBA * tempTriCol.m_vecPoint; const auto cpNormal = Multiply3x3(matBA, tempTriCol.m_vecNormal); @@ -1393,6 +1445,8 @@ bool NOTSA_FORCEINLINE ProcessLineTriangle_Internal( * @addr 0x413AC0 */ bool CCollision::TestLineTriangle(const CColLine& line, const CompressedVector* verts, const CColTriangle& tri, const CColTrianglePlane& plane) { + ZoneScoped; + return ProcessLineTriangle_Internal(line, tri.GetPoly(verts), plane, nullptr, nullptr, nullptr); } @@ -1441,12 +1495,16 @@ bool CCollision::ProcessVerticalLineTriangle( float& maxTouchDistance, CStoredCollPoly* collPoly ) { + ZoneScoped; + // Not really SA, but the only difference is an early out bounds check that I've implemented into `ProcessLineTriangle_Internal` return ProcessLineTriangle(line, verts, tri, plane, colPoint, maxTouchDistance, collPoly); } // 0x416450 bool CCollision::ProcessSphereSphere(const CColSphere& spA, const CColSphere& spB, CColPoint& colPoint, float& maxTouchDistance) { + ZoneScoped; + const auto spBToA = spA.m_vecCenter - spB.m_vecCenter; const auto distSq = spBToA.SquaredMagnitude(); @@ -1490,6 +1548,8 @@ bool CCollision::TestSphereTriangle( const CColTriangle& tri, const CColTrianglePlane& plane ) { + ZoneScoped; + if (!CCollision::s_DebugSettings.ShapeShapeCollision.IsEnabled(Shape::SSPHERE, Shape::STRI)) { return false; } @@ -1543,6 +1603,8 @@ bool CCollision::ProcessSphereTriangle( CColPoint& colPoint, float& maxTouchDistance ) { + ZoneScoped; + if (!CCollision::s_DebugSettings.ShapeShapeCollision.IsEnabled(Shape::SSPHERE, Shape::STRI)) { return false; } @@ -1595,6 +1657,8 @@ bool CCollision::TestLineOfSight( bool doSeeThroughCheck, bool doShootThroughCheck ) { + ZoneScoped; + const auto cd = cm.GetData(); if (!cd) { return false; @@ -1644,6 +1708,8 @@ bool CCollision::TestLineOfSight( // 0x417950 bool CCollision::ProcessLineOfSight(const CColLine& lnws, const CMatrix& transform, CColModel& colModel, CColPoint& colPoint, float& maxTouchDistance, bool doSeeThroughCheck, bool doShootThroughCheck) { + ZoneScoped; + assert(colModel.m_pColData); const auto colData = colModel.m_pColData; @@ -1709,6 +1775,8 @@ bool CCollision::ProcessVerticalLine( bool doShootThroughCheck, CStoredCollPoly* outColPoly ) { + ZoneScoped; + const auto cd = cm.GetData(); if (!cd) { return false; @@ -1778,6 +1846,8 @@ bool CCollision::ProcessVerticalLine( // 0x417F20 bool CCollision::SphereCastVsSphere(const CColSphere& spA, const CColSphere& spB, const CColSphere& spS) { + ZoneScoped; + if (TestSphereSphere(spA, spS) || TestSphereSphere(spB, spS)) { return true; } @@ -1799,6 +1869,8 @@ void CCollision::ClosestPointOnPoly(CColTriangle* arg0, CVector* arg1, CVector* // 0x418580 void CCollision::CalculateTrianglePlanes(CColModel* colModel) { + ZoneScoped; + plugin::Call<0x418580, CColModel*>(colModel); if (colModel->m_pColData && colModel->m_pColData->m_pTriangles) { assert(colModel->m_pColData->m_pTrianglePlanes); // If model has triangles it should also have triPls by now (otherwise random crashes will occour) @@ -1842,6 +1914,8 @@ int32 CCollision::ProcessColModels(const CMatrix& transformA, CColModel& cmA, float* maxTouchDistances, bool bReturnAllCollisions ) { + ZoneScoped; + return plugin::CallAndReturn*, CColPoint*, float*, bool>( transformA, cmA, transformB, cmB, &sphereCPs, lineCPs, maxTouchDistances, bReturnAllCollisions); /* @@ -2238,6 +2312,8 @@ int32 CCollision::ProcessColModels(const CMatrix& transformA, CColModel& cmA, // 0x414D70 bool CCollision::IsStoredPolyStillValidVerticalLine(const CVector& lineOrigin, float lnMag, CColPoint& colPoint, CStoredCollPoly* collPoly) { + ZoneScoped; + if (!collPoly->valid) { return false; } @@ -2258,6 +2334,8 @@ bool CCollision::IsStoredPolyStillValidVerticalLine(const CVector& lineOrigin, f // 0x415230 CColBox CCollision::GetBoundingBoxFromTwoSpheres(const CColSphere& spA, const CColSphere& spB) { + ZoneScoped; + CVector min, max; for (size_t i = 0; i < 3; i++) { std::tie(min[i], max[i]) = std::minmax(spA.m_vecCenter[i], spB.m_vecCenter[i]); @@ -2270,6 +2348,8 @@ CColBox CCollision::GetBoundingBoxFromTwoSpheres(const CColSphere& spA, const CC // 0x4152C0 bool CCollision::IsThisVehicleSittingOnMe(CVehicle* veh, CVehicle* vehOnMe) { + ZoneScoped; + if (!veh || !vehOnMe) { return false; } @@ -2312,6 +2392,8 @@ bool CCollision::CheckCameraCollisionPeds( const CVector& /*unused*/, float& /*unused*/ ) { + ZoneScoped; + constexpr auto gPedCylinderWidth = 1.f; bool addedAny = false; @@ -2383,6 +2465,8 @@ bool CCollision::RayPolyPOP(CVector* arg0, CVector* arg1, CColTriangle* arg2, CV // 0x4156D0 int32 CCollision::GetPrincipleAxis(const CVector& normal) { + ZoneScoped; + const auto nx = std::abs(normal.x), ny = std::abs(normal.y), nz = std::abs(normal.z); @@ -2412,6 +2496,8 @@ bool CCollision::PointInPoly( const CVector& normal, const CVector* verts // Uncompressed vertices ) { + ZoneScoped; + // Shuffle look-up table constexpr uint8 lut[3][2]{ { 1, 2 }, // X @@ -2436,6 +2522,8 @@ bool CCollision::SphereCastVersusVsPoly( const CColTrianglePlane& triPlane, CompressedVector* verts ) { + ZoneScoped; + const auto plNorm = triPlane.GetNormal(); const auto spARadius = spA.m_fRadius; @@ -2560,6 +2648,8 @@ bool CCollision::SphereCastVsBBox( const CColSphere& spB, const CColBox& box ) { + ZoneScoped; + const auto r = spA.m_fRadius; const CVector vRadius{ r, r, r }; return TestLineBox_DW( @@ -2590,6 +2680,8 @@ bool CCollision::SphereCastVsCaches( int32& numOut, CColCacheEntry* out ) { + ZoneScoped; + const CColSphere spBws{ spAws.m_vecCenter + velocity, spAws.m_fRadius }; assert(!numIn || in[0].ent); @@ -2680,6 +2772,8 @@ bool CCollision::SphereCastVsEntity( const CColSphere& spBws, CEntity* entity ) { + ZoneScoped; + if (!entity->m_bUsesCollision || TheCamera.IsExtraEntityToIgnore(entity)) { return false; } @@ -2798,6 +2892,8 @@ bool CCollision::CheckCameraCollisionBuildings( const CColSphere& spA, const CColSphere& spB ) { + ZoneScoped; + const auto plyrVeh = FindPlayerVehicle(); const auto checkFlyerCollision = plyrVeh && plyrVeh->physicalFlags.bDontCollideWithFlyers; @@ -2833,6 +2929,8 @@ bool CCollision::CheckCameraCollisionVehicles( const CColSphere& spB, const CVector* plyrVehVel ) { + ZoneScoped; + static auto& gFramesSittingOnTimeOut = StaticRef(); static auto& gpLastSittingOnEntity = StaticRef(); @@ -2887,6 +2985,8 @@ bool CCollision::CheckCameraCollisionObjects( const CColSphere& spA, const CColSphere& spB ) { + ZoneScoped; + // Pirulax: At this point I'm certain R* devs were paid by lines written bool anyCollided = false; @@ -2923,6 +3023,8 @@ bool CCollision::CheckPeds( const CVector& normal, /*unused*/ float& nearest /*unused*/ ) { + ZoneScoped; + if (!bCamCollideWithPeds) { return false; } @@ -2940,6 +3042,8 @@ bool CCollision::BuildCacheOfCameraCollision( const CColSphere& spA, const CColSphere& spB ) { + ZoneScoped; + const auto spABBox = GetBoundingBoxFromTwoSpheres(spA, spB); const auto spABBSp = CColSphere{CSphere{ spABBox.GetCenter(), spABBox.GetSize().Magnitude() / 2.f} }; @@ -3005,6 +3109,8 @@ bool CCollision::CameraConeCastVsWorldCollision( float& dst, float minDist ) { + ZoneScoped; + ColCache caches[2]{}; gpColCache = &caches[0]; gpColCache2 = &caches[1]; @@ -3054,6 +3160,8 @@ bool CCollision::CameraConeCastVsWorldCollision( // 0x41A5A0 bool CCollision::SphereVsEntity(CColSphere* sphere, CEntity* entity) { + ZoneScoped; + NOTSA_UNREACHABLE(); /* unused */ } diff --git a/source/game_sa/Collision/CollisionData.cpp b/source/game_sa/Collision/CollisionData.cpp index 79c1c3a601..2245e53ecd 100644 --- a/source/game_sa/Collision/CollisionData.cpp +++ b/source/game_sa/Collision/CollisionData.cpp @@ -229,16 +229,17 @@ CLink* CCollisionData::GetLinkPtr() { auto CCollisionData::GetNumFaceGroups() const -> uint32 { // See `CCollisionData` header for explanation :) - return bHasFaceGroups ? *reinterpret_cast(reinterpret_cast(m_pTriangles) - sizeof(uint32)) : 0u; + assert(!bHasFaceGroups || m_pTriangles); + return bHasFaceGroups + ? *reinterpret_cast(reinterpret_cast(m_pTriangles) - sizeof(uint32)) + : 0u; } auto CCollisionData::GetFaceGroups() const -> std::span { using namespace ColHelpers; - - if (bHasFaceGroups) { - // See `CCollisionData` header for explanation - const auto numfg = GetNumFaceGroups(); - return std::span{ + if (const auto numfg = GetNumFaceGroups()) { + assert(numfg); + return std::span{ // See `CCollisionData` header for explanation reinterpret_cast(reinterpret_cast(m_pTriangles) - sizeof(uint32) - sizeof(TFaceGroup) * numfg), numfg }; diff --git a/source/game_sa/CompressedBox.cpp b/source/game_sa/CompressedBox.cpp new file mode 100644 index 0000000000..e15b8a370b --- /dev/null +++ b/source/game_sa/CompressedBox.cpp @@ -0,0 +1,11 @@ +#include "StdInc.h" +#include "CompressedBox.h" +#include "Lines.h" + +void CompressedBox::DrawWireFrame(CRGBA color, const CMatrix& transform) const +{ + CBox(*this).DrawWireFrame(color, transform); +} +CompressedBox::operator CBox() const { + return CBox{UncompressLargeVector(m_vecMin), UncompressLargeVector(m_vecMax)}; +} diff --git a/source/game_sa/CompressedBox.h b/source/game_sa/CompressedBox.h index f61c1d8c02..d91e0ad69e 100644 --- a/source/game_sa/CompressedBox.h +++ b/source/game_sa/CompressedBox.h @@ -6,5 +6,10 @@ class CompressedBox { public: CompressedVector m_vecMin; CompressedVector m_vecMax; + + void DrawWireFrame(CRGBA color, const CMatrix& transform) const; + + // NOTSA + operator CBox() const; }; VALIDATE_SIZE(CompressedBox, 0xC); diff --git a/source/game_sa/ControllerConfigManager.cpp b/source/game_sa/ControllerConfigManager.cpp index c7f6c8b54c..117c975f0c 100644 --- a/source/game_sa/ControllerConfigManager.cpp +++ b/source/game_sa/ControllerConfigManager.cpp @@ -18,6 +18,9 @@ void CControllerConfigManager::InjectHooks() { RH_ScopedInstall(SetMouseButtonAssociatedWithAction, 0x52F590); RH_ScopedInstall(StoreMouseButtonState, 0x52DA30, { .reversed = false }); RH_ScopedInstall(UpdateJoyInConfigMenus_ButtonDown, 0x52DAB0, { .reversed = false }); + RH_ScopedInstall(UpdateJoy_ButtonDown, 0x530F42); + RH_ScopedInstall(UpdateJoy_ButtonUp, 0x531070); + RH_ScopedInstall(StoreJoyButtonStates, 0x52F510); RH_ScopedInstall(AffectControllerStateOn_ButtonDown_DebugStuff, 0x52DC10, { .reversed = false }); RH_ScopedInstall(UpdateJoyInConfigMenus_ButtonUp, 0x52DC20, { .reversed = false }); RH_ScopedInstall(AffectControllerStateOn_ButtonUp_DebugStuff, 0x52DD80, { .reversed = false }); @@ -50,6 +53,35 @@ CControllerConfigManager* CControllerConfigManager::Constructor() { return this; } +// 0x52F510 +void CControllerConfigManager::StoreJoyButtonStates() { // Name unknown (I made it up) + for (auto&& [idx, bs] : notsa::enumerate(m_ButtonStates)) { + bs = m_NewJoyState.rgbButtons[idx] >> 7; + } +} + +// NOTSA [Code combined from 0x7448B0 and 0x744930] +void CControllerConfigManager::HandleJoyButtonUpDown(int32 joyNo, bool isDown) { + StoreJoyButtonStates(); + const auto forceConfigMenuMode = !isDown && notsa::contains({ MODE_FLYBY, MODE_FIXED }, TheCamera.GetActiveCamera().m_nMode); // Probably leftover debug stuff? + for (auto i = isDown ? 1u : 2u; i < std::size(m_ButtonStates); i++) { // TODO: Why is this starting from 1/2? + const auto padBtn = (ePadButton)((m_ButtonStates[i - 1] == isDown) ? i : 0); // This doesn't make sense + if (forceConfigMenuMode || FrontEndMenuManager.m_bMenuActive || joyNo != 0) { + if (isDown) { + UpdateJoyInConfigMenus_ButtonDown(padBtn, joyNo); + } else { + UpdateJoyInConfigMenus_ButtonUp(padBtn, joyNo); + } + } else { + if (isDown) { + UpdateJoy_ButtonDown(padBtn, 3); + } else { + UpdateJoy_ButtonUp(padBtn, 3); + } + } + } +} + // 0x530530 bool CControllerConfigManager::LoadSettings(FILESTREAM file) { return plugin::CallMethodAndReturn(this, file); @@ -143,6 +175,11 @@ void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonDown(ePadButton butt plugin::CallMethod<0x52DAB0, CControllerConfigManager*, ePadButton, int32>(this, button, padNumber); } +// 0x530F42 +void CControllerConfigManager::UpdateJoy_ButtonDown(ePadButton button, int32 unk) { + plugin::CallMethod<0x530F42>(this, button, unk); +} + // unused // 0x52DC10 void CControllerConfigManager::AffectControllerStateOn_ButtonDown_DebugStuff(int32, eControllerType) { @@ -154,6 +191,11 @@ void CControllerConfigManager::UpdateJoyInConfigMenus_ButtonUp(ePadButton button plugin::CallMethod<0x52DC20, CControllerConfigManager*, ePadButton, int32>(this, button, padNumber); } +// 0x531070 +void CControllerConfigManager::UpdateJoy_ButtonUp(ePadButton button, int32 unk) { + plugin::CallMethod<0x531070>(this, button, unk); +} + // unused // 0x52DD80 void CControllerConfigManager::AffectControllerStateOn_ButtonUp_DebugStuff(int32, eControllerType) { @@ -190,7 +232,7 @@ bool CControllerConfigManager::GetIsKeyboardKeyJustDown(RsKeyCodes key) { bool CControllerConfigManager::GetIsMouseButtonDown(RsKeyCodes key) { return plugin::CallMethodAndReturn(this, key); } - + // 0x52F020 bool CControllerConfigManager::GetIsMouseButtonUp(RsKeyCodes key) { return plugin::CallMethodAndReturn(this, key); diff --git a/source/game_sa/ControllerConfigManager.h b/source/game_sa/ControllerConfigManager.h index 001d997091..d1712b3e37 100644 --- a/source/game_sa/ControllerConfigManager.h +++ b/source/game_sa/ControllerConfigManager.h @@ -101,6 +101,20 @@ struct CControllerAction { CControllerKey Keys[4]; }; +struct CPadConfig { + int32 field_0{}; + bool present{}; // Device exists + bool zAxisPresent{}; // Has property DIJOFS_Z + bool rzAxisPresent{}; // Has property DIJOFS_RZ +private: + char __align{}; +public: + int32 vendorId{}; + int32 productId{}; +}; +VALIDATE_SIZE(CPadConfig, 16); +static inline auto& PadConfigs = StaticRef, 0xC92144>(); + using ControlName = char[40]; class CControllerConfigManager { @@ -110,8 +124,8 @@ class CControllerConfigManager { DIJOYSTATE2 m_OldJoyState; DIJOYSTATE2 m_NewJoyState; - ControlName m_arrControllerActionName[59]; - bool m_ButtonStates[17]; + char m_arrControllerActionName[59][40]; // todo: 182 + bool m_ButtonStates[17]; // True if down, false if up or missing CControllerAction m_Actions[59]; bool m_bStickL_X_Rgh_Lft_MovementBothDown[4]; @@ -127,6 +141,7 @@ class CControllerConfigManager { CControllerConfigManager(); CControllerConfigManager* Constructor(); + bool LoadSettings(FILESTREAM file); void SaveSettings(FILESTREAM file); @@ -139,8 +154,10 @@ class CControllerConfigManager { void StoreMouseButtonState(eMouseButtons button, bool state); void UpdateJoyInConfigMenus_ButtonDown(ePadButton button, int32 padNumber); + void UpdateJoy_ButtonDown(ePadButton button, int32 unk); void AffectControllerStateOn_ButtonDown_DebugStuff(int32, eControllerType); void UpdateJoyInConfigMenus_ButtonUp(ePadButton button, int32 padNumber); + void UpdateJoy_ButtonUp(ePadButton button, int32 unk); void AffectControllerStateOn_ButtonUp_DebugStuff(int32, eControllerType); void ClearSimButtonPressCheckers(); @@ -155,6 +172,8 @@ class CControllerConfigManager { eActionType GetActionType(eControllerAction action); char* GetControllerSettingTextMouse(eControllerAction action); char* GetControllerSettingTextJoystick(eControllerAction action); + void StoreJoyButtonStates(); + void HandleJoyButtonUpDown(int32 joyNo, bool isDown); // NOTSA void ClearSettingsAssociatedWithAction(eControllerAction action, eControllerType type); void MakeControllerActionsBlank(); diff --git a/source/game_sa/Conversations.cpp b/source/game_sa/Conversations.cpp index 2affabcad9..25d3722266 100644 --- a/source/game_sa/Conversations.cpp +++ b/source/game_sa/Conversations.cpp @@ -28,6 +28,8 @@ void CConversations::InjectHooks() { // 0x43A7B0 void CConversations::Clear() { + ZoneScoped; + rng::for_each(m_aConversations, &CConversationForPed::Clear); rng::for_each(m_aNodes, &CConversationNode::Clear); @@ -46,6 +48,8 @@ void CConversations::RemoveConversationForPed(CPed* ped) { // 0x43C590 void CConversations::Update() { + ZoneScoped; + switch (m_AwkwardSayStatus) { case AwkwardSayState::AUDIO_PLAYING: if (AudioEngine.GetMissionAudioLoadingStatus(0)) { diff --git a/source/game_sa/Core/LinkList.h b/source/game_sa/Core/LinkList.h index 263e160049..0f432a13e2 100644 --- a/source/game_sa/Core/LinkList.h +++ b/source/game_sa/Core/LinkList.h @@ -75,6 +75,9 @@ template class CLinkList { link->Remove(); freeListHead.Insert(link); } + + auto GetTail() { return usedListTail.prev; } + auto GetHead() { return usedListHead.next; } }; VALIDATE_SIZE(CLinkList, 0x34); diff --git a/source/game_sa/Core/ListItem_c.h b/source/game_sa/Core/ListItem_c.h index 61ec933ec3..0c61a26ee8 100644 --- a/source/game_sa/Core/ListItem_c.h +++ b/source/game_sa/Core/ListItem_c.h @@ -6,19 +6,23 @@ */ #pragma once +#include "List_c.h" + /** * Double linked list item base class * * You should inherit this class to use it in List_c lists! */ +template // T should be derived from `ListItem_c` class ListItem_c { -protected: - ListItem_c* m_pPrev{}; - ListItem_c* m_pNext{}; - -protected: +public: ListItem_c() = default; // 0x4A8DB0 ~ListItem_c() = default; // 0x49EA70 - friend class List_c; +public: // + T* m_pPrev{}; + T* m_pNext{}; + +protected: + friend class TList_c; }; diff --git a/source/game_sa/Core/List_c.cpp b/source/game_sa/Core/List_c.cpp deleted file mode 100644 index 2862b55acb..0000000000 --- a/source/game_sa/Core/List_c.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#include "StdInc.h" - -#include "List_c.h" - -void List_c::InjectHooks() { - RH_ScopedClass(List_c); - RH_ScopedCategory("Core"); - - RH_ScopedInstall(AddItem, 0x4A8DF0); - RH_ScopedInstall(RemoveItem, 0x4A8E30); - RH_ScopedInstall(RemoveHead, 0x4A8E70); - RH_ScopedInstall(RemoveTail, 0x4A8FD0); - RH_ScopedInstall(RemoveAll, 0x4A8EB0); - RH_ScopedInstall(InsertAfterItem, 0x4A8F10); - RH_ScopedInstall(InsertBeforeItem, 0x4A8F70); - RH_ScopedInstall(GetNext, 0x4A8FF0); - RH_ScopedInstall(GetPrev, 0x4A9000); - RH_ScopedInstall(GetNumItems, 0x4A8EC0); - RH_ScopedInstall(GetItemOffset, 0x4A9010); -} - -// 0x4A8DF0 -void List_c::AddItem(ListItem_c* item) { - assert(item); - auto* pOldHead = m_pHead; - m_pHead = item; - item->m_pPrev = nullptr; - item->m_pNext = pOldHead; - - if (pOldHead) - pOldHead->m_pPrev = item; - else - m_pTail = item; - - ++m_nCount; -} - -// 0x4A8E30 -void List_c::RemoveItem(ListItem_c* item) { - assert(item); - - if (item->m_pNext) - item->m_pNext->m_pPrev = item->m_pPrev; - else - m_pTail = item->m_pPrev; - - if (item->m_pPrev) - item->m_pPrev->m_pNext = item->m_pNext; - else - m_pHead = item->m_pNext; - - --m_nCount; -} - -ListItem_c* List_c::GetHead() { - return m_pHead; -} - -ListItem_c* List_c::GetTail() { - return m_pTail; -} - -// 0x4A8E70 -ListItem_c* List_c::RemoveHead() { - // return plugin::CallMethodAndReturn(this); - if (!m_pHead) - return nullptr; - - --m_nCount; - auto* pOldHead = m_pHead; - if (m_pHead == m_pTail) { - m_pTail = nullptr; - m_pHead = nullptr; - return pOldHead; - } - - if (m_pHead->m_pNext) - m_pHead->m_pNext->m_pPrev = nullptr; - - m_pHead = m_pHead->m_pNext; - return pOldHead; -} - -ListItem_c* List_c::RemoveTail() { - if (!m_pTail) - return nullptr; - - --m_nCount; - auto* pOldTail = m_pTail; - m_pTail->m_pPrev->m_pNext = nullptr; - m_pTail = m_pTail->m_pPrev; - return pOldTail; -} - -void List_c::RemoveAll() { - m_pHead = nullptr; - m_pTail = nullptr; - m_nCount = 0; -} - -uint32 List_c::GetNumItems() const { - return m_nCount; -} - -void List_c::AppendItem(ListItem_c* item) { - auto* pOldTail = m_pTail; - m_pTail = item; - item->m_pPrev = pOldTail; - item->m_pNext = nullptr; - - if (pOldTail) - pOldTail->m_pNext = item; - else - m_pHead = item; - - ++m_nCount; -} - -void List_c::InsertAfterItem(ListItem_c* addedItem, ListItem_c* pExistingItem) { - ++m_nCount; // BUG: We increment count even though the item wasn't added to table, and there's no certainity that it will - if (!m_pHead) - return; - - auto curItem = GetHead(); - while (curItem && curItem != pExistingItem) - curItem = GetNext(curItem); - - if (!curItem) - return; - - addedItem->m_pPrev = curItem; - addedItem->m_pNext = curItem->m_pNext; - auto* pOldNext = curItem->m_pNext; - curItem->m_pNext = addedItem; - if (pOldNext) - pOldNext->m_pPrev = addedItem; - else - m_pTail = addedItem; -} - -void List_c::InsertBeforeItem(ListItem_c* addedItem, ListItem_c* pExistingItem) { - ++m_nCount; // BUG: We increment count even though the item wasn't added to table, and there's no certainity that it will - if (!m_pHead) - return; - - auto curItem = GetHead(); - while (curItem && curItem != pExistingItem) - curItem = GetNext(curItem); - - if (!curItem) - return; - - addedItem->m_pPrev = curItem->m_pPrev; - addedItem->m_pNext = curItem; - auto* oldPrev = curItem->m_pPrev; - curItem->m_pPrev = addedItem; - if (oldPrev) - oldPrev->m_pNext = addedItem; - else - m_pHead = addedItem; -} - -ListItem_c* List_c::GetNext(ListItem_c* item) { - assert(item); - return item->m_pNext; -} - -// 0x4A9000 -ListItem_c* List_c::GetPrev(ListItem_c* item) { - assert(item); - return item->m_pPrev; -} - -// 0x4A9010 -ListItem_c* List_c::GetItemOffset(bool bFromHead, int32 iOffset) { - if (bFromHead) { - auto* result = GetHead(); - if (iOffset > 0 && result) { - int32 iCounter = 0; - while (iCounter < iOffset && result) { - ++iCounter; - result = GetNext(result); - } - } - return result; - } else { - auto* result = GetTail(); - if (iOffset > 0 && result) { - int32 iCounter = 0; - while (iCounter < iOffset && result) { - ++iCounter; - result = GetPrev(result); - } - } - return result; - } -} diff --git a/source/game_sa/Core/List_c.h b/source/game_sa/Core/List_c.h index 6dd37f573f..dee33d51c8 100644 --- a/source/game_sa/Core/List_c.h +++ b/source/game_sa/Core/List_c.h @@ -6,102 +6,217 @@ */ #pragma once -#include "ListItem_c.h" - -/** - * Double linked list base implementation - * - * NOTE: You should not use this class directly, use TList_c template instead. - */ -class List_c { +template +class ListItem_c; + +template +class TList_c { + template + class BaseIterator { + public: + using iterator_category = std::forward_iterator_tag; // Actually it's bidirectional, but there are quirks, so let's pretend like its not + using difference_type = std::ptrdiff_t; + using value_type = Y; + using pointer = Y*; + using reference = Y&; + + BaseIterator() = default; + BaseIterator(pointer ptr) : m_ptr{ ptr } {} + + reference operator*() const { return *m_ptr; } + pointer operator->() { return m_ptr; } + + auto& operator++() { assert(m_ptr); m_ptr = m_ptr->m_pNext; return *this; } + auto operator++(int) { const auto tmp{ *this }; ++(*this); return tmp; } + + // NOTE: Won't work properly in case `list.end() == *this` [Because `m_ptr` will be null] + auto& operator--() { assert(m_ptr); m_ptr = m_ptr->m_pPrev; return *this; } + auto operator--(int) { const auto tmp{ *this }; --(*this); return tmp; } + + friend bool operator==(const BaseIterator& lhs, const BaseIterator& rhs) { return lhs.m_ptr == rhs.m_ptr; } + friend bool operator!=(const BaseIterator& lhs, const BaseIterator& rhs) { return !(lhs == rhs); } + private: + pointer m_ptr; + }; public: - List_c() : m_pHead(nullptr), m_pTail(nullptr), m_nCount(0) {} - ~List_c() = default; + using iterator = BaseIterator; + using const_iterator = BaseIterator; public: - ListItem_c* m_pHead; - ListItem_c* m_pTail; - uint32 m_nCount; + void AddItem(T* item) { + assert(item); -public: - static void InjectHooks(); - - // Add new item to the head - void AddItem(ListItem_c* item); + auto* pOldHead = m_head; + m_head = item; + item->m_pPrev = nullptr; + item->m_pNext = pOldHead; - // Remove given item from the list and decrease counter - void RemoveItem(ListItem_c* item); + if (pOldHead) + pOldHead->m_pPrev = item; + else + m_tail = item; - // Remove heading item and return it's pointer - ListItem_c* RemoveHead(); + ++m_cnt; + } - // Remove tail item and return it's pointer - ListItem_c* RemoveTail(); + void AppendItem(T* item) { + auto* pOldTail = m_tail; + m_tail = item; + item->m_pPrev = pOldTail; + item->m_pNext = nullptr; - // Remove all items - void RemoveAll(); + if (pOldTail) + pOldTail->m_pNext = item; + else + m_head = item; - // Get number of items in the list - uint32 GetNumItems() const; + ++m_cnt; + } - // Append item to the list - void AppendItem(ListItem_c* item); + + void InsertAfterItem(T* addedItem, T* pExistingItem) { + ++m_cnt; // BUG: We increment count even though the item wasn't added to table, and there's no certainity that it will + if (!m_head) + return; + + auto curItem = GetHead(); + while (curItem && curItem != pExistingItem) + curItem = GetNext(curItem); + + if (!curItem) + return; + + addedItem->m_pPrev = curItem; + addedItem->m_pNext = curItem->m_pNext; + auto* pOldNext = curItem->m_pNext; + curItem->m_pNext = addedItem; + if (pOldNext) + pOldNext->m_pPrev = addedItem; + else + m_tail = addedItem; + } - // Append item to the list - void InsertAfterItem(ListItem_c* addedItem, ListItem_c* pExistingItem); + void InsertBeforeItem(T* addedItem, T* pExistingItem) { + ++m_cnt; // BUG: We increment count even though the item wasn't added to table, and there's no certainity that it will - // Append item to the list - void InsertBeforeItem(ListItem_c* addedItem, ListItem_c* pExistingItem); + if (!m_head) + return; - // Get list head - ListItem_c* GetHead(); + auto curItem = GetHead(); + while (curItem && curItem != pExistingItem) + curItem = GetNext(curItem); - // Get list head - ListItem_c* GetTail(); + if (!curItem) + return; - // Get next item in a list - ListItem_c* GetNext(ListItem_c* item); + addedItem->m_pPrev = curItem->m_pPrev; + addedItem->m_pNext = curItem; + auto* oldPrev = curItem->m_pPrev; + curItem->m_pPrev = addedItem; + if (oldPrev) + oldPrev->m_pNext = addedItem; + else + m_head = addedItem; + } + + void RemoveItem(T* item) { + assert(item); - // Get previous item - ListItem_c* GetPrev(ListItem_c* item); + if (item->m_pNext) + item->m_pNext->m_pPrev = item->m_pPrev; + else + m_tail = item->m_pPrev; - // Get N-th item from list head/tail - ListItem_c* GetItemOffset(bool bFromHead, int32 iOffset); -}; + if (item->m_pPrev) + item->m_pPrev->m_pNext = item->m_pNext; + else + m_head = item->m_pNext; -/** - * Double linked list template wrapper - * (not an original game class name) - */ -template class TList_c : public List_c { -public: - ItemType* GetHead() { - return static_cast(List_c::GetHead()); + --m_cnt; } - ItemType* GetTail() { - return static_cast(List_c::GetTail()); + void RemoveAll() { + m_head = nullptr; + m_tail = nullptr; + m_cnt = 0; } - ItemType* RemoveHead() { - return static_cast(List_c::RemoveHead()); - } + T* RemoveHead() { + if (!m_head) + return nullptr; - ItemType* RemoveTail() { - return static_cast(List_c::RemoveTail()); - } + --m_cnt; + auto* pOldHead = m_head; + if (m_head == m_tail) { + m_tail = nullptr; + m_head = nullptr; + return pOldHead; + } - ItemType* GetNext(ItemType* item) { - return static_cast(List_c::GetNext(item)); + if (m_head->m_pNext) + m_head->m_pNext->m_pPrev = nullptr; + + m_head = m_head->m_pNext; + return pOldHead; } - ItemType* GetPrev(ItemType* item) { - return static_cast(List_c::GetPrev(item)); + T* RemoveTail() { + if (!m_tail) { + return nullptr; + } + + --m_cnt; + const auto oldTail = m_tail; + m_tail->m_pPrev->m_pNext = nullptr; + m_tail = m_tail->m_pPrev; + return oldTail; } - ItemType* GetItemOffset(bool bFromHead, int32 iOffset) { - return static_cast(List_c::GetItemOffset(bFromHead, iOffset)); + T* GetItemOffset(bool bFromHead, int32 iOffset) { + if (bFromHead) { + auto* result = GetHead(); + if (iOffset > 0 && result) { + int32 iCounter = 0; + while (iCounter < iOffset && result) { + ++iCounter; + result = GetNext(result); + } + } + return result; + } else { + auto* result = GetTail(); + if (iOffset > 0 && result) { + int32 iCounter = 0; + while (iCounter < iOffset && result) { + ++iCounter; + result = GetPrev(result); + } + } + return result; + } } -}; -VALIDATE_SIZE(List_c, 0xC); + T* GetNext(T* item) const { assert(item); return item->m_pNext; } + T* GetPrev(T* item) const { assert(item); return item->m_pPrev; } + T* GetHead() const { return m_head; } + T* GetTail() const { return m_tail; } + + auto GetNumItems() const { return m_cnt; } + + auto cbegin() const { return const_iterator{ GetHead() }; } + auto begin() const { return cbegin(); } + auto begin() { return iterator{ GetHead() }; } + + // Past the end is always `nullptr` - Not really std comforting, but oh well + auto cend() const { return const_iterator{ nullptr }; } + auto end() const { return cend(); } + auto end() { return iterator{ nullptr }; } + + auto IsEmpty() const { return m_head == nullptr; } + +private: + T* m_head{}; + T* m_tail{}; + size_t m_cnt{}; +}; +using List_c = TList_c; diff --git a/source/game_sa/Core/Pool.h b/source/game_sa/Core/Pool.h index 7e0b9c7254..6e0cbf5e23 100644 --- a/source/game_sa/Core/Pool.h +++ b/source/game_sa/Core/Pool.h @@ -247,11 +247,8 @@ template class CPool { } // 0x5A1CD0 - bool IsObjectValid(A *obj) { - auto slot = GetIndex(obj); - return slot >= 0 && - slot < m_nSize && - !IsFreeSlotAtIndex(slot); + bool IsObjectValid(const A *obj) { + return IsFromObjectArray(obj) && !IsFreeSlotAtIndex(GetIndex(obj)); } // Helper so we don't write memcpy manually diff --git a/source/game_sa/Core/QuadTreeNode.cpp b/source/game_sa/Core/QuadTreeNode.cpp index 995f201551..1978b10539 100644 --- a/source/game_sa/Core/QuadTreeNode.cpp +++ b/source/game_sa/Core/QuadTreeNode.cpp @@ -218,6 +218,8 @@ bool CQuadTreeNode::InSector(const CRect& rect, int32 sector) const // 0x552C00 void CQuadTreeNode::InitPool() { + ZoneScoped; + if (ms_pQuadTreeNodePool) return; diff --git a/source/game_sa/Core/Rect.h b/source/game_sa/Core/Rect.h index 7745d64231..f1f92b039f 100644 --- a/source/game_sa/Core/Rect.h +++ b/source/game_sa/Core/Rect.h @@ -104,6 +104,9 @@ class CRect { * @brief Check if this rectangle is inside another one */ bool Contains(const CRect& o) const; + + bool operator==(const CRect&) const = default; + bool operator!=(const CRect&) const = default; }; VALIDATE_SIZE(CRect, 0x10); diff --git a/source/game_sa/Core/Vector.cpp b/source/game_sa/Core/Vector.cpp index 8a81683f7e..dc537e7223 100644 --- a/source/game_sa/Core/Vector.cpp +++ b/source/game_sa/Core/Vector.cpp @@ -181,14 +181,13 @@ CVector CVector::Average(const CVector* begin, const CVector* end) { } float CVector::Heading(bool limitAngle) const { - const auto heading = std::atan2(-x, y); + const auto radians = std::atan2(-x, y); if (limitAngle) { - return CGeneral::LimitRadianAngle(heading); + return CGeneral::LimitRadianAngle(radians); } - return heading; + return radians; } - CVector* CrossProduct(CVector* out, CVector* a, CVector* b) { *out = a->Cross(b); diff --git a/source/game_sa/Core/Vector.h b/source/game_sa/Core/Vector.h index 2748e41ba2..e97d9bdbe1 100644 --- a/source/game_sa/Core/Vector.h +++ b/source/game_sa/Core/Vector.h @@ -8,8 +8,7 @@ #include #include -#include "PluginBase.h" // !!! -#include "RenderWare.h" +#include #include "Vector2D.h" class CMatrix; @@ -247,6 +246,14 @@ inline CVector Lerp(const CVector& vecOne, const CVector& vecTwo, float fProgres return vecOne * (1.0F - fProgress) + vecTwo * fProgress; } +//! Component-wise clamp of values +inline CVector Clamp(CVector val, CVector min, CVector max) { + for (auto i = 0; i < 3; i++) { + val[i] = std::clamp(val[i], min[i], max[i]); + } + return val; +} + inline CVector Pow(const CVector& vec, float fPow) { return { pow(vec.x, fPow), pow(vec.y, fPow), pow(vec.z, fPow) }; } diff --git a/source/game_sa/Core/Vector2D.cpp b/source/game_sa/Core/Vector2D.cpp index 85d7976c5d..e7201aa885 100644 --- a/source/game_sa/Core/Vector2D.cpp +++ b/source/game_sa/Core/Vector2D.cpp @@ -48,11 +48,3 @@ CVector2D CVector2D::RotatedBy(float rad) const { x * s - y * c, }; } - -CVector2D CVector2D::GetPerpRight() const { - return { y, -x }; // `RotatedBy(-PI / 2)` done manually, rotate by +PI/2 would be `{-y, x}` -} - -CVector2D CVector2D::GetPerpLeft() const { - return { -y, x }; -} diff --git a/source/game_sa/Core/Vector2D.h b/source/game_sa/Core/Vector2D.h index a5cc4a239a..8bb69d3aba 100644 --- a/source/game_sa/Core/Vector2D.h +++ b/source/game_sa/Core/Vector2D.h @@ -7,8 +7,8 @@ #pragma once #include - -#include "RenderWare.h" +#include +#include class CVector; @@ -95,6 +95,11 @@ class CVector2D : public RwV2d { y *= multiplier; } + inline void operator*=(CVector2D multiplier) { + x *= multiplier.x; + y *= multiplier.y; + } + inline void operator/=(float divisor) { x /= divisor; y /= divisor; @@ -144,11 +149,11 @@ class CVector2D : public RwV2d { //! Get vector perpendicular to `*this` on the right side (Same direction `*this` rotated by -90) //! Also see `GetPerpLeft` and `RotatedBy` //! (This sometimes is also called a 2D cross product https://stackoverflow.com/questions/243945 ) - CVector2D GetPerpRight() const; + CVector2D GetPerpRight() const { return { y, -x }; } //! Get vector perpendicular to `*this` on the left side (Same direction `*this` rotated by 90) //! Also see `GetPerpRight` and `RotatedBy` - CVector2D GetPerpLeft() const; + CVector2D GetPerpLeft() const { return { -y, x }; } /*! * @notsa diff --git a/source/game_sa/Coronas.cpp b/source/game_sa/Coronas.cpp index 35e5b19971..2267124fe0 100644 --- a/source/game_sa/Coronas.cpp +++ b/source/game_sa/Coronas.cpp @@ -2,8 +2,6 @@ #include "Coronas.h" -float& CCoronas::SunScreenX = *(float*)0xC3E028; -float& CCoronas::SunScreenY = *(float*)0xC3E02C; //bool& CCoronas::SunBlockedByClouds = *(bool*)0x0; bool& CCoronas::bChangeBrightnessImmediately = *(bool*)0xC3E034; uint32& CCoronas::NumCoronas = *(uint32*)0xC3E038; @@ -14,58 +12,497 @@ CRegisteredCorona(&CCoronas::aCoronas)[MAX_NUM_CORONAS] = *(CRegisteredCorona(*) uint16(&CCoronas::ms_aEntityLightsOffsets)[8] = *(uint16(*)[8])0x8D5028; -char (&coronaTexturesAlphaMasks)[260] = *(char (*)[260])0x8D4A58; +auto& aCoronastar = StaticRef, 0x8D4950>(); +auto& coronaTexturesAlphaMasks = StaticRef, 0x8D4A58>(); + +struct CFlareDefinition +{ + float Position; + float Size; + FixedVector ColorMult; + FixedFloat IntensityMult; + int16 Sprite; // Only used for array-end checking +}; +constexpr CFlareDefinition HeadLightsFlareDef[]{ + { 4.00f, 5.0f, { 60, 60, 60 }, 200, 4 }, + { 3.00f, 7.0f, { 40, 40, 40 }, 200, 4 }, + { 2.00f, 5.0f, { 40, 40, 40 }, 200, 4 }, + { 1.50f, 6.0f, { 90, 90, 90 }, 200, 4 }, + { 1.25f, 5.0f, { 40, 40, 40 }, 200, 4 }, + { 0.80f, 14.f, { 60, 60, 60 }, 200, 4 }, + { 0.60f, 4.0f, { 40, 40, 40 }, 200, 4 }, + { 0.25f, 10.f, { 60, 60, 60 }, 200, 4 }, + { 0.10f, 6.0f, { 30, 30, 30 }, 200, 4 }, + { 0.05f, 14.f, { 50, 50, 50 }, 200, 4 }, + { -0.03f, 3.0f, { 30, 30, 30 }, 200, 4 }, + { -0.10f, 6.0f, { 60, 60, 60 }, 200, 4 }, + { -0.30f, 5.0f, { 30, 30, 30 }, 200, 4 }, + { -0.40f, 60.f, { 30, 30, 30 }, 200, 4 }, + { -0.55f, 4.0f, { 40, 40, 40 }, 200, 4 }, + { -0.75f, 14.f, { 50, 50, 50 }, 200, 4 }, + { -0.90f, 5.2f, { 35, 35, 35 }, 200, 4 }, + { -1.00f, 11.f, { 55, 55, 55 }, 200, 4 }, + { -1.20f, 3.5f, { 35, 35, 35 }, 200, 4 }, + { -1.35f, 9.0f, { 50, 50, 50 }, 200, 4 }, + { -1.70f, 54.f, { 35, 35, 35 }, 200, 4 }, + { -2.00f, 5.0f, { 50, 50, 50 }, 200, 4 }, + { -2.50f, 4.5f, { 35, 35, 35 }, 200, 4 }, + { -3.00f, 14.f, { 50, 50, 50 }, 200, 4 }, + { -6.00f, 24.f, { 70, 70, 70 }, 200, 4 }, + { -9.00f, 14.f, { 70, 50, 70 }, 200, 4 }, + { 0.00f, 0.0f, { 255, 255, 255 }, 255, 0 } +}; + +constexpr CFlareDefinition SunFlareDef[]{ + { 4.00f, 8.00f, { 36, 30, 24 }, 200, 4 }, + { 3.00f, 11.2f, { 24, 18, 15 }, 200, 4 }, + { 2.00f, 8.00f, { 24, 12, 12 }, 200, 4 }, + { 1.50f, 9.60f, { 54, 54, 48 }, 200, 4 }, + { 1.25f, 8.00f, { 24, 24, 18 }, 200, 4 }, + { 0.80f, 22.4f, { 36, 30, 24 }, 200, 4 }, + { 0.60f, 6.40f, { 24, 15, 12 }, 200, 4 }, + { 0.25f, 16.0f, { 36, 30, 30 }, 200, 4 }, + { 0.10f, 9.60f, { 18, 18, 18 }, 200, 4 }, + { 0.05f, 22.4f, { 36, 30, 24 }, 200, 4 }, + { -0.03f, 4.80f, { 18, 18, 18 }, 200, 4 }, + { -0.10f, 9.60f, { 42, 42, 42 }, 200, 4 }, + { -0.30f, 8.00f, { 18, 6, 6 }, 200, 4 }, + { -0.40f, 96.0f, { 18, 12, 9 }, 200, 4 }, + { -0.55f, 6.40f, { 18, 18, 12 }, 200, 4 }, + { -0.75f, 22.4f, { 42, 24, 18 }, 200, 4 }, + { -0.90f, 8.32f, { 21, 12, 18 }, 200, 4 }, + { -1.00f, 17.6f, { 42, 13, 18 }, 200, 4 }, + { -1.20f, 5.60f, { 21, 12, 12 }, 200, 4 }, + { -1.35f, 14.4f, { 42, 42, 24 }, 200, 4 }, + { -1.70f, 86.8f, { 21, 15, 15 }, 200, 4 }, + { -2.00f, 8.00f, { 48, 30, 30 }, 200, 4 }, + { -2.50f, 7.20f, { 21, 15, 12 }, 200, 4 }, + { -3.00f, 22.4f, { 42, 30, 24 }, 200, 4 }, + { -6.00f, 38.4f, { 42, 42, 30 }, 200, 4 }, + { -9.00f, 22.4f, { 42, 30, 36 }, 200, 4 }, + { 0.00f, 0.00f, { 255, 255, 255 }, 255, 0 } +}; void CCoronas::InjectHooks() { RH_ScopedClass(CCoronas); RH_ScopedCategoryGlobal(); - RH_ScopedInstall(Init, 0x6FAA70, { .reversed = false }); - RH_ScopedInstall(Shutdown, 0x6FAB00, { .reversed = false }); + RH_ScopedInstall(Init, 0x6FAA70); + RH_ScopedInstall(Shutdown, 0x6FAB00); RH_ScopedInstall(Update, 0x6FADF0, { .reversed = false }); - RH_ScopedInstall(Render, 0x6FAEC0, { .reversed = false }); - RH_ScopedInstall(RenderReflections, 0x6FB630, { .reversed = false }); - RH_ScopedInstall(RenderSunReflection, 0x6FBAA0, { .reversed = false }); + RH_ScopedInstall(Render, 0x6FAEC0); + RH_ScopedInstall(RenderReflections, 0x6FB630); + RH_ScopedInstall(RenderSunReflection, 0x6FBAA0); RH_ScopedOverloadedInstall(RegisterCorona, "type", 0x6FC180, void(*)(uint32, CEntity*, uint8, uint8, uint8, uint8, const CVector&, float, float, RwTexture*, eCoronaFlareType, bool, bool, int32, float, bool, float, uint8, float, bool, bool reflectionDelay), { .reversed = false }); RH_ScopedOverloadedInstall(RegisterCorona, "texture", 0x6FC580, void(*)(uint32, CEntity*, uint8, uint8, uint8, uint8, const CVector&, float, float, eCoronaType, eCoronaFlareType, bool, bool, int32, float, bool, float, uint8, float, bool, bool reflectionDelay), { .reversed = false }); + RH_ScopedInstall(UpdateCoronaCoors, 0x6FC4D0, { .reversed = false }); - RH_ScopedInstall(DoSunAndMoon, 0x6FC5A0, { .reversed = false }); + RH_ScopedInstall(DoSunAndMoon, 0x6FC5A0); } // Initialises coronas // 0x6FAA70 void CCoronas::Init() { - plugin::Call<0x6FAA70>(); + { + CTxdStore::ScopedTXDSlot txd{"particle"}; + //for (auto&& [tex, name, maskName] : rng::zip_view{ gpCoronaTexture, aCoronastar, coronaTexturesAlphaMasks }) { // TODO: C++23 + // if (!tex) { + // tex = RwTextureRead(name, maskName); + // } + //} + for (auto i = 0; i < CORONA_TEXTURES_COUNT; i++) { + auto& tex = gpCoronaTexture[i]; + if (!tex) { + tex = RwTextureRead(aCoronastar[i], coronaTexturesAlphaMasks[i]); + } + } + } + rng::fill(aCoronas, CRegisteredCorona{}); } // Terminates coronas // 0x6FAB00 void CCoronas::Shutdown() { - plugin::Call<0x6FAB00>(); + rng::for_each(gpCoronaTexture, RwTextureDestroy); } // Updates coronas // 0x6FADF0 void CCoronas::Update() { + ZoneScoped; + plugin::Call<0x6FADF0>(); + + /** + * NOTE: Unfinished and untested + *** + + LightsMult = std::min(CTimer::GetTimeStep() * 0.03f * LightsMult, 1.f); + + struct CamLook { + bool unused : 4{}, left : 1{}, right : 1{}, behind : 1{}, forward : 1{}; // Have to initialize the msb 4 bits too, otherwise it wont compare equal to the original code's value + } &LastCamLook = StaticRef(); // NOTE/TODO: I'm not sure if foward is really forward + + const auto c = TheCamera.GetActiveCam(); + const CamLook currLook{ + .left = c.m_bLookingLeft, + .right = c.m_bLookingRight, + .behind = c.m_bLookingBehind, + .forward = TheCamera.GetLookDirection() != 0, + }; + + if (currLook == LastCamLook) { + + } + */ } -// Renders coronas // 0x6FAEC0 void CCoronas::Render() { - plugin::Call<0x6FAEC0>(); + ZoneScoped; + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + + const auto raster = RwCameraGetRaster(Scene.m_pRwCamera); + const auto rasterSize = CVector2D{ (float)RwRasterGetWidth(raster), (float)RwRasterGetHeight(raster) }; + const auto rasterRect = CRect{ + 0.f, 0.f, + rasterSize.x, rasterSize.y + }; + + bool zTestEnable = true; + for (auto& c : aCoronas) { + if (!c.m_dwId) { + continue; + } + + if (!c.m_FadedIntensity && !c.m_Color.a) { + continue; + } + + const auto covidPos = c.GetPosition(); + + //< 0x6FB009 - Get on-screen position, and check if it's on it + CVector onScrPos; + CVector2D onScrSize; + if (c.m_bOffScreen = !CSprite::CalcScreenCoors(covidPos, &onScrPos, &onScrSize.x, &onScrSize.y, true, true)) { + continue; + } + + c.m_bOffScreen = !rasterRect.IsPointInside(CVector2D{ onScrPos }); // Seems like a useless check + + // Already faded out + if (c.m_FadedIntensity == 0) { + continue; + } + + // If outside farclip range, just ignore + if (onScrPos.z >= c.m_fFarClip) { + continue; + } + + const auto rz = 1.f / onScrPos.z; + + //< 0x6FB0A7 - Start fading out at half the far clip distance + const auto intensity = (int16)((float)c.m_FadedIntensity * c.CalculateIntensity(onScrPos.z, c.m_fFarClip)); + + //< 0x6FB0D9 - Enable/disable Z test if necessary + if (c.m_bCheckObstacles == zTestEnable) { + zTestEnable = !zTestEnable; + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(zTestEnable)); + } + + const auto LerpColorC = [](uint8 cc, float t) { + return (uint8)((float)cc * t); + }; + + //< 0x6FB131 - Render with texture + if (c.m_pTexture) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(c.m_pTexture))); + + //< 0x6FB122 + const auto scale = std::min(40.f, onScrPos.z) * CWeather::Foggyness / 40.f + 1.f; + + // NOP - The coordinates are later overwritten + //if (c.m_dwId == 1) { + // scrPos.z = RwCameraGetFarClipPlane(Scene.m_pRwCamera) * 0.95f; + //} + + //< 0x6FB24E + if (CSprite::CalcScreenCoors( + covidPos - (covidPos - TheCamera.GetPosition()).Normalized() * c.m_fNearClip, + &onScrPos, // NOTE/BUG: Yeah, overwrites the previously calculated value. Not sure if it's intentional. + &onScrSize.x, &onScrSize.y, + true, + true + )) { + CSprite::RenderOneXLUSprite_Rotate_Aspect( + onScrPos, + onScrSize * c.m_fSize * CVector2D{1.f, scale}, + LerpColorC(c.m_Color.r, 1.f / scale), + LerpColorC(c.m_Color.g, 1.f / scale), + LerpColorC(c.m_Color.b, 1.f / scale), + intensity, + rz * 20.f, + 0.f, + 255 + ); + } + } + + //< 0x6FB2F3 - Render flare + if (c.m_nFlareType != FLARETYPE_NONE) { + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[0]))); + + //< 0x6FB35B + const auto colorVariationMult = CGeneral::GetRandomNumberInRange(0.7f, 1.f) * (float)c.m_FadedIntensity; + + //< 0x6FB2FC [Moved here] + auto it = [&] { + switch (c.m_nFlareType) { + case FLARETYPE_SUN: return &SunFlareDef[0]; + case FLARETYPE_HEADLIGHTS: return &HeadLightsFlareDef[0]; + default: NOTSA_UNREACHABLE(); + } + }(); + + //< 0x6FB46C + for (; it->Sprite; it++) { + CEntity* hitEntity; + CColPoint hitCP; + if (!CWorld::ProcessLineOfSight( + covidPos, + TheCamera.GetPosition(), + hitCP, + hitEntity, + false, + true, + true, + false, + false, + false, + false, + true + )) { //< 0x6FB409 + auto color = c.m_Color * colorVariationMult; + color.a = 255; + CSprite::RenderBufferedOneXLUSprite2D( + lerp(rasterSize / 2.f, CVector2D{ onScrPos }, it->Position), + CVector2D{ it->Size, it->Size } * 4.f, + color, + 255, + 255 + ); + } + } + + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + + //< 0x6FB483 + if (c.m_nFlareType == FLARETYPE_HEADLIGHTS && CWeather::HeadLightsSpectrum != 0.f && CGame::CanSeeOutSideFromCurrArea()) { + for (auto it = HeadLightsFlareDef; it->Sprite; it++) { + const auto RenderFlareSprite = [ + &, + spriteIntensity = (int16)((float)intensity * it->IntensityMult) + ](RwRGBA clr, float posOffset) { + CSprite::RenderBufferedOneXLUSprite2D( + lerp(rasterSize / 2.f, CVector2D{ onScrPos }, it->Position + posOffset), + CVector2D{ it->Size, it->Size }, + clr, + spriteIntensity, + 255 + ); + }; + RenderFlareSprite({ LerpColorC(c.m_Color.r, it->ColorMult.x * CWeather::HeadLightsSpectrum), 0, 0, 255 }, +0.05f); // 0x6FB561 + RenderFlareSprite({ 0, 0, LerpColorC(c.m_Color.b, it->ColorMult.z * CWeather::HeadLightsSpectrum), 255 }, -0.05f); // 0x6FB5EA + } + } + } + } + CSprite::FlushSpriteBuffer(); + + /* NOTE/BUG: Renderstates not restored? */ } -// Renders coronas reflections on a wet ground // 0x6FB630 void CCoronas::RenderReflections() { - plugin::Call<0x6FB630>(); + if (s_DebugSettings.DisableWetRoadReflections) { + return; + } + + if (!s_DebugSettings.AlwaysRenderWetRoadReflections) { + // Check if the roads are wet enough + if (CWeather::WetRoads <= 0.f) { + for (auto& c : aCoronas) { + c.m_bHasValidHeightAboveGround = false; + } + return; + } + } + + RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[3]))); + + const auto camPos = TheCamera.GetPosition(); + for (auto&& [i, c] : notsa::enumerate(aCoronas)) { + const auto covidPos = c.GetPosition(); + if (!c.m_bHasValidHeightAboveGround || ((i & 0xFF) + (CTimer::GetFrameCounter() & 0xFF) % 16) == 0) { //< Simplified code + bool bGroundFound; + const auto groundZ = CWorld::FindGroundZFor3DCoord(covidPos, &bGroundFound); + if (bGroundFound) { // NOTE/BUG: Weird.. Why not set it according to `bGroundFound` directly? + c.m_bHasValidHeightAboveGround = true; + c.m_fHeightAboveGround = covidPos.z - groundZ; + } + } + if (!c.m_bHasValidHeightAboveGround) { + continue; + } + if (c.m_bHasValidHeightAboveGround >= 20.f) { + continue; + } + if (covidPos.z - c.m_fHeightAboveGround >= camPos.z) { + continue; + } + CVector onScrPos; + CVector2D onScrSize; + if (!CSprite::CalcScreenCoors(covidPos - CVector{0.f, 0.f, 2 * c.m_fHeightAboveGround }, & onScrPos, & onScrSize.x, & onScrSize.y, true, true)) { + continue; + } + const auto clampedFarClip = std::min(55.f, c.m_fFarClip * 0.75f); + if (onScrPos.z >= clampedFarClip) { + continue; + } + const auto LerpColorC = [ + t = (s_DebugSettings.AlwaysRenderWetRoadReflections ? 1.f : CWeather::WetRoads) + * c.CalculateIntensity(onScrPos.z, clampedFarClip) + * invLerp(20.f, 0.f, c.m_fHeightAboveGround) + * 230.f + ](uint8 cc) { + return (uint8)((uint16)((float)cc * t) >> 8 & 0xFF); // divide by 256 + }; + CSprite::RenderBufferedOneXLUSprite( + { onScrPos.x, onScrPos.y, notsa::IsFixBugs() ? onScrPos.z : RwIm2DGetNearScreenZMacro() }, + onScrSize * CVector2D{0.75f, 2.f} * c.m_fSize, + LerpColorC(c.m_Color.r), + LerpColorC(c.m_Color.g), + LerpColorC(c.m_Color.b), + 128, + 1.f / RwCameraGetNearClipPlane(Scene.m_pRwCamera), + 255 + ); + } + CSprite::FlushSpriteBuffer(); + + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDINVSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); } -// Renders sun reflection on water // 0x6FBAA0 void CCoronas::RenderSunReflection() { - plugin::Call<0x6FBAA0>(); + constexpr auto REFLECTION_SIZE = 60.f; + constexpr auto REFLECTION_PERIOD = 2048; // Code is faster if this is a power-of-2 + + const auto vecToSun3D = CTimeCycle::m_VectorToSun[CTimeCycle::m_CurrentStoredValue]; + + if (vecToSun3D.z <= -0.05f) { + return; + } + + const auto camPos = TheCamera.GetPosition(); + + auto t = + invLerp(0.3f, 0.f, std::abs(vecToSun3D.z - 0.25f)) + * (1.f - CWeather::CloudCoverage) + * (1.f - CWeather::Foggyness) + * (1.f - CWeather::Wind); + if (t <= 0.f) { + return; + } + for (const auto& v : std::initializer_list{ // TODO: Magic numberz + { 611.0f, 875.0f, 0.f }, + { -929.0f, 2364.f, 0.f }, + { -1034.f, 2640.f, 0.f }, + { 2372.f, -1854.f, 0.f }, + { -1633.f, 106.0f, 0.f } + }) { + t *= std::clamp(invLerp(0.f, 100.f, (camPos - v).Magnitude2D() - 250.f), 0.f, 1.f); + } + t *= 0.25f; + + const auto center2D = CVector2D{ vecToSun3D } * 40.f + CVector2D{ camPos }; + const auto vecToSun2D = CVector2D{ vecToSun3D }.Normalized(); + const auto vecToSunCore2D = vecToSun2D * (REFLECTION_SIZE / 2.f); + const auto vecToSunCorona2D = vecToSun2D * REFLECTION_SIZE; + + #define CalcColorC(_color) \ + (uint8)((float)(CTimeCycle::m_CurrentColours.m_nSunCorona##_color + CTimeCycle::m_CurrentColours.m_nSunCore##_color) * t) + const auto PushVertex = [ + &, + color = CRGBA{ CalcColorC(Red), CalcColorC(Green), CalcColorC(Blue), 255 }, + posZ = CWeather::Wind * 0.5f + 0.1f + ](CVector2D offsetToCenter, CVector2D uv) { + RenderBuffer::PushVertex(CVector{ center2D + offsetToCenter, posZ }, uv, color); + }; + #undef CalcColorC + + RenderBuffer::ClearRenderBuffer(); + + // The part closer to the camera + { + RenderBuffer::PushIndices({ 2, 1, 0, 2, 3, 1 }, false); + + PushVertex(vecToSunCore2D.GetPerpRight(), { 0.0f, 1.0f }); + PushVertex(vecToSunCore2D.GetPerpLeft(), { 1.0f, 1.0f }); + PushVertex(vecToSunCorona2D + vecToSunCore2D.GetPerpRight(), { 0.0f, 0.5f }); + PushVertex(vecToSunCorona2D + vecToSunCore2D.GetPerpLeft(), { 1.0f, 0.5f }); + } + + //< 0x6FBF36 - The part that is changing position [To imitate how the water is flowing] + const auto time = CTimer::GetTimeInMS(); + for (auto i = 0u; i < 20u; i++) { + RenderBuffer::PushIndices({ 0, -1, -2, 0, 1, -1 }, true); // Same as the above indices, but with 2 subtracted from each + + const auto forward = vecToSun2D * ( + std::sin((float)((time + 900 * i) % REFLECTION_PERIOD) / (float)REFLECTION_PERIOD * TWO_PI) * 10.f + + (float)(i * 970 / 20 + 30) + ); + const auto offset = vecToSun2D * (float)(i * 1440 / 20 + 60); + + PushVertex(offset + forward.GetPerpRight(), { 0.0f, 0.5f }); + PushVertex(offset + forward.GetPerpLeft(), { 1.0f, 0.5f }); + } + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEFOGTYPE, RWRSTATE(rwFOGTYPELINEAR)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDONE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(gpCoronaTexture[4]))); + + RenderBuffer::RenderStuffInBuffer(); + + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDINVSRCALPHA)); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(FALSE)); } // Creates corona by texture @@ -88,5 +525,48 @@ void CCoronas::UpdateCoronaCoors(uint32 id, const CVector& posn, float farClip, // Draw sun (Moon went to CClouds since SA) // 0x6FC5A0 void CCoronas::DoSunAndMoon() { - plugin::Call<0x6FC5A0>(); + ZoneScoped; + + if (!CGame::CanSeeOutSideFromCurrArea()) { + return; + } + + const auto vecToSun = CTimeCycle::GetVectorToSun(); + + if (vecToSun.z >= -0.1f) { + const auto DoRegisterCorona = [ + coronaPos = vecToSun * (CDraw::GetFarClipZ() * 0.95f) + TheCamera.GetPosition() + ](uint32 id, eCoronaFlareType ftype, float radiusMult) { + const auto& cc = CTimeCycle::m_CurrentColours; + RegisterCorona( + id, + nullptr, + (uint8)cc.m_nSunCoreRed, + (uint8)cc.m_nSunCoreGreen, + (uint8)cc.m_nSunCoreBlue, + 255u, + &coronaPos, + cc.m_fSunSize * radiusMult, + 999999.88f, + gpCoronaTexture[0], + ftype, + false, + false, + 0, + 0.f, + false, + 1.5f, + 0, + 15.f, + false, + false + ); + }; + DoRegisterCorona(1, FLARETYPE_NONE, 2.7335f); + if (vecToSun.z >= 0.f) { // Removed redudant check + DoRegisterCorona(2, FLARETYPE_SUN, 6.f); + } + } + + // Dead code here } diff --git a/source/game_sa/Coronas.h b/source/game_sa/Coronas.h index 2420ca3d4a..da3ab62e74 100644 --- a/source/game_sa/Coronas.h +++ b/source/game_sa/Coronas.h @@ -10,11 +10,8 @@ class CCoronas { public: - // sun 2d position - static float& SunScreenX; - static float& SunScreenY; // are there any obstacles between sun and camera - static bool& SunBlockedByClouds; + static inline auto& SunBlockedByClouds = StaticRef(); // change coronas brightness immediately static bool& bChangeBrightnessImmediately; // num of registered coronas in frame @@ -29,15 +26,36 @@ class CCoronas { static uint16 (&ms_aEntityLightsOffsets)[8]; + inline static struct { // NOTSA + bool DisableWetRoadReflections; + bool AlwaysRenderWetRoadReflections; // Ignored if if `DisableReflections == false` + } s_DebugSettings{}; + public: static void InjectHooks(); static void Init(); static void Shutdown(); static void Update(); + + /*! + * @addr 0x6FAEC0 + * Renders the registered coronas + */ static void Render(); + + /*! + * @addr 0x6FB630 + * Renders registered coronas reflections on a wet roads ground + */ static void RenderReflections(); + + /*! + * @addr 0x6FBAA0 + * Renders sun's reflection on the water [sea] + */ static void RenderSunReflection(); + static void RegisterCorona(uint32 id, CEntity* attachTo, uint8 red, uint8 green, uint8 blue, uint8 alpha, const CVector& posn, float radius, float farClip, RwTexture* texture, eCoronaFlareType flareType, bool enableReflection, bool checkObstacles, int32 _param_not_used, float angle, bool longDistance, float nearClip, uint8 fadeState, float fadeSpeed, bool onlyFromBelow, bool reflectionDelay); diff --git a/source/game_sa/Cover.cpp b/source/game_sa/Cover.cpp index 21195c6a15..f18d042486 100644 --- a/source/game_sa/Cover.cpp +++ b/source/game_sa/Cover.cpp @@ -27,6 +27,8 @@ void CCover::InjectHooks() { // 0x698710 void CCover::Init() { + ZoneScoped; + plugin::Call<0x698710>(); } @@ -51,6 +53,8 @@ bool CCover::ShouldThisBuildingHaveItsCoverPointsCreated(CBuilding* building) { // 0x6997E0 void CCover::Update() { + ZoneScoped; + plugin::Call<0x6997E0>(); } diff --git a/source/game_sa/Cranes.cpp b/source/game_sa/Cranes.cpp index 50a12ae03b..1fc8e22cc3 100644 --- a/source/game_sa/Cranes.cpp +++ b/source/game_sa/Cranes.cpp @@ -20,6 +20,8 @@ void CCranes::InitCranes() { // 0x6F3FE0 void CCranes::UpdateCranes() { + ZoneScoped; + // NOP } diff --git a/source/game_sa/Credits.cpp b/source/game_sa/Credits.cpp index d5c5d10aeb..446d368e9a 100644 --- a/source/game_sa/Credits.cpp +++ b/source/game_sa/Credits.cpp @@ -30,6 +30,8 @@ void CCredits::Stop() { // 0x53D5B0 void CCredits::Render() { + ZoneScoped; + if (bCreditsGoing && !FrontEndMenuManager.m_bMenuActive) { RenderCredits(); } diff --git a/source/game_sa/CreepingFire.cpp b/source/game_sa/CreepingFire.cpp index af81edb3d3..1ff8167b6f 100644 --- a/source/game_sa/CreepingFire.cpp +++ b/source/game_sa/CreepingFire.cpp @@ -25,6 +25,8 @@ void CCreepingFire::SetReadyToBurn() { // 0x539CE0 void CCreepingFire::Update() { + ZoneScoped; + uint8& status = m_aFireStatus[CTimer::GetFrameCounter() % 32][(CTimer::GetFrameCounter() / 32) % 32]; if (status == 4) { status = 0; diff --git a/source/game_sa/Crime.cpp b/source/game_sa/Crime.cpp index df28997c0c..4194023929 100644 --- a/source/game_sa/Crime.cpp +++ b/source/game_sa/Crime.cpp @@ -3,5 +3,6 @@ #include "Crime.h" void CCrime::ReportCrime(eCrimeType crimeType, CEntity* entity, CPed* ped2) { + assert(!ped2 || ped2->IsPed()); plugin::Call<0x532010, eCrimeType, CEntity*, CPed*>(crimeType, entity, ped2); } diff --git a/source/game_sa/CullZones.cpp b/source/game_sa/CullZones.cpp index 0cf9cb0c19..e7fe1d35a6 100644 --- a/source/game_sa/CullZones.cpp +++ b/source/game_sa/CullZones.cpp @@ -26,6 +26,8 @@ void CCullZones::InjectHooks() { // 0x72D6B0 void CCullZones::Init() { + ZoneScoped; + NumAttributeZones = 0; CurrentFlags_Player = 0; CurrentFlags_Camera = 0; @@ -171,6 +173,8 @@ eZoneAttributes CCullZones::FindAttributesForCoors(CVector pos) { // 0x72DEC0 void CCullZones::Update() { + ZoneScoped; + if ((CTimer::GetFrameCounter() & 7) == 2) { CurrentFlags_Camera = FindAttributesForCoors(TheCamera.GetGameCamPosition()); return; diff --git a/source/game_sa/CustomRoadsignMgr.cpp b/source/game_sa/CustomRoadsignMgr.cpp index 30f6c97be8..c0fa45164a 100644 --- a/source/game_sa/CustomRoadsignMgr.cpp +++ b/source/game_sa/CustomRoadsignMgr.cpp @@ -24,6 +24,8 @@ void CCustomRoadsignMgr::InjectHooks() // 0x6FE120 bool CCustomRoadsignMgr::Initialise() { + ZoneScoped; + CTxdStore::PushCurrentTxd(); CTxdStore::SetCurrentTxd(CTxdStore::FindTxdSlot("particle")); diff --git a/source/game_sa/CutsceneMgr.cpp b/source/game_sa/CutsceneMgr.cpp index 0fdb6376b5..96b74024b4 100644 --- a/source/game_sa/CutsceneMgr.cpp +++ b/source/game_sa/CutsceneMgr.cpp @@ -5,9 +5,7 @@ Do not delete this comment block. Respect others' work! */ #include "StdInc.h" - -#include // TODO: Remove (Included because of isForeground) - +#include #include #include "Fx.h" @@ -300,6 +298,8 @@ void CCutsceneMgr::HideRequestedObjects() { // 0x4D5A20 void CCutsceneMgr::Initialise() { + ZoneScoped; + ms_cutsceneLoadStatus = LoadStatus::NOT_LOADED; ms_running = false; ms_animLoaded = false; @@ -406,7 +406,7 @@ void CCutsceneMgr::LoadCutsceneData_loading() { }(); // Finally, create the fx - csfx.m_pFxSystem = g_fxMan.CreateFxSystem(csfx.m_szEffectName, &fxTransform, objMat, true); + csfx.m_pFxSystem = g_fxMan.CreateFxSystem(csfx.m_szEffectName, fxTransform, objMat, true); } // Finally, process attachments @@ -866,7 +866,7 @@ void CCutsceneMgr::SetCutsceneAnim(const char* animName, CObject* object) { return; } - if (theAnim->m_pHierarchy->m_bRunningCompressed) { + if (theAnim->m_pHierarchy->m_bIsCompressed) { theAnim->m_pHierarchy->m_bKeepCompressed = true; } @@ -914,7 +914,7 @@ void CCutsceneMgr::SetupCutsceneToStart() { anim->SetFlag(ANIMATION_STARTED, true); } else { // Get anim translation and offset the object's position by it - const auto animTrans = anim->m_pHierarchy->m_bRunningCompressed + const auto animTrans = anim->m_pHierarchy->m_bIsCompressed ? anim->m_pHierarchy->m_pSequences->GetCompressedFrame(1)->GetTranslation() : anim->m_pHierarchy->m_pSequences->GetUncompressedFrame(1)->translation; SetObjPos(ms_cutsceneOffset + animTrans); @@ -967,6 +967,8 @@ void CCutsceneMgr::StartCutscene() { // 0x4D5D00 void CCutsceneMgr::Update() { + ZoneScoped; + if (ms_cutsceneLoadStatus != LoadStatus::NOT_LOADED) { Update_overlay(); } diff --git a/source/game_sa/Darkel.cpp b/source/game_sa/Darkel.cpp index 75c641a0ef..7cef86cecd 100644 --- a/source/game_sa/Darkel.cpp +++ b/source/game_sa/Darkel.cpp @@ -180,12 +180,12 @@ void CDarkel::StartFrenzy(eWeaponType weaponType, int32 timeLimit, uint16 killsN auto playerPed = FindPlayerPed(); if (weapon < WEAPON_LAST_WEAPON) { - InterruptedWeaponTypeSelected = playerPed->GetActiveWeapon().m_nType; + InterruptedWeaponTypeSelected = playerPed->GetActiveWeapon().m_Type; playerPed->RemoveWeaponAnims(InterruptedWeaponTypeSelected, -1000.0f); const auto frenzyWeaponSlot = CWeaponInfo::GetWeaponInfo(weapon)->m_nSlot; - InterruptedWeaponType = playerPed->GetWeaponInSlot(frenzyWeaponSlot).m_nType; - AmmoInterruptedWeapon = playerPed->GetWeaponInSlot(frenzyWeaponSlot).m_nTotalAmmo; + InterruptedWeaponType = playerPed->GetWeaponInSlot(frenzyWeaponSlot).m_Type; + AmmoInterruptedWeapon = playerPed->GetWeaponInSlot(frenzyWeaponSlot).m_TotalAmmo; if (InterruptedWeaponType != WEAPON_UNARMED) { const auto weaponInfo = CWeaponInfo::GetWeaponInfo(InterruptedWeaponType); @@ -204,10 +204,10 @@ void CDarkel::StartFrenzy(eWeaponType weaponType, int32 timeLimit, uint16 killsN auto chosenWeapon = playerPed->m_pPlayerData->m_nChosenWeapon; playerPed->SetCurrentWeapon(chosenWeapon); - if (auto& activeWeapon = playerPed->GetActiveWeapon(); activeWeapon.m_nTotalAmmo >= activeWeapon.GetWeaponInfo().m_nAmmoClip) { - activeWeapon.m_nAmmoInClip = activeWeapon.GetWeaponInfo().m_nAmmoClip; + if (auto& activeWeapon = playerPed->GetActiveWeapon(); activeWeapon.m_TotalAmmo >= activeWeapon.GetWeaponInfo().m_nAmmoClip) { + activeWeapon.m_AmmoInClip = activeWeapon.GetWeaponInfo().m_nAmmoClip; } else { - activeWeapon.m_nAmmoInClip = activeWeapon.m_nTotalAmmo; + activeWeapon.m_AmmoInClip = activeWeapon.m_TotalAmmo; } playerPed->ClearWeaponTarget(); @@ -249,10 +249,10 @@ void CDarkel::DealWithWeaponChangeAtEndOfFrenzy() { const auto weaponInfo = CWeaponInfo::GetWeaponInfo(weapon); playerPed->RemoveWeaponModel(playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).GetWeaponInfo().m_nModelId1); - playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).m_nType = WEAPON_UNARMED; - playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).m_nTotalAmmo = 0; - playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).m_nAmmoInClip = 0; - playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).m_nState = WEAPONSTATE_READY; + playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).m_Type = WEAPON_UNARMED; + playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).m_TotalAmmo = 0; + playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).m_AmmoInClip = 0; + playerPed->GetWeaponInSlot(weaponInfo->m_nSlot).m_State = WEAPONSTATE_READY; playerPed->RemoveWeaponAnims(weapon, -1000.0f); if (auto mi = CModelInfo::GetModelInfo(weaponInfo->m_nModelId1); mi->m_nRefCount > 0) { @@ -265,7 +265,7 @@ void CDarkel::DealWithWeaponChangeAtEndOfFrenzy() { playerPed->RemoveWeaponModel(playerPed->GetActiveWeapon().GetWeaponInfo().m_nModelId1); auto& chosenWeaponSlot = playerPed->m_pPlayerData->m_nChosenWeapon; - if (playerPed->GetWeaponInSlot(eWeaponSlot::SMG).m_nType != WEAPON_UNARMED) { + if (playerPed->GetWeaponInSlot(eWeaponSlot::SMG).m_Type != WEAPON_UNARMED) { chosenWeaponSlot = (uint8)eWeaponSlot::SMG; } else { chosenWeaponSlot = (uint8)eWeaponSlot::UNARMED; @@ -366,6 +366,8 @@ bool CDarkel::CheckDamagedWeaponType(eWeaponType damageWeaponId, eWeaponType exp // 0x43DAC0 void CDarkel::Update() { + ZoneScoped; + if (!FrenzyOnGoing()) return; diff --git a/source/game_sa/Debug.cpp b/source/game_sa/Debug.cpp index 2836eb1869..67cf38222f 100644 --- a/source/game_sa/Debug.cpp +++ b/source/game_sa/Debug.cpp @@ -12,8 +12,9 @@ void CDebug::InjectHooks() RH_ScopedInstall(DebugDisplayTextBuffer, 0x532260); } -void CDebug::DebugInitTextBuffer() -{ +void CDebug::DebugInitTextBuffer() { + ZoneScoped; + m_debugStrings.reserve(500); } @@ -33,8 +34,9 @@ void CDebug::DebugAddText(const char* str, float x, float y) m_debugStrings.push_back({ str, x, y }); } -void CDebug::DebugDisplayTextBuffer() -{ +void CDebug::DebugDisplayTextBuffer() { + ZoneScoped; + CFont::SetBackground(0, 0); CFont::SetBackgroundColor(CRGBA(0, 0, 0, 128)); CFont::SetOrientation(eFontAlignment::ALIGN_LEFT); diff --git a/source/game_sa/Draw.cpp b/source/game_sa/Draw.cpp index e61efa8ece..84a7121616 100644 --- a/source/game_sa/Draw.cpp +++ b/source/game_sa/Draw.cpp @@ -40,6 +40,8 @@ void CDraw::CalculateAspectRatio() { // 0x53E600 void DoFade() { + ZoneScoped; + if (CTimer::GetIsPaused()) return; diff --git a/source/game_sa/Entity/Entity.h b/source/game_sa/Entity/Entity.h index 636a5ebfcf..e43232343d 100644 --- a/source/game_sa/Entity/Entity.h +++ b/source/game_sa/Entity/Entity.h @@ -289,7 +289,7 @@ class NOTSA_EXPORT_VTABLE CEntity : public CPlaceable { auto AsBuilding() { return reinterpret_cast(this); } auto AsDummy() { return reinterpret_cast(this); } - [[nodiscard]] auto GetType() const noexcept { return m_nType; } + [[nodiscard]] auto GetType() const noexcept { return (eEntityType)m_nType; } void SetType(eEntityType type) { m_nType = type; } [[nodiscard]] auto GetStatus() const noexcept { return m_nStatus; } diff --git a/source/game_sa/Entity/Object/Object.cpp b/source/game_sa/Entity/Object/Object.cpp index 48409525cb..ce7d73558a 100644 --- a/source/game_sa/Entity/Object/Object.cpp +++ b/source/game_sa/Entity/Object/Object.cpp @@ -1014,7 +1014,7 @@ void CObject::ProcessTrainCrossingBehaviour() { } // 0x5A0D90 -void CObject::ObjectDamage(float damage, CVector* fxOrigin, CVector* fxDirection, CEntity* damager, eWeaponType weaponType) { +void CObject::ObjectDamage(float damage, const CVector* fxOrigin, const CVector* fxDirection, CEntity* damager, eWeaponType weaponType) { if (!m_bUsesCollision) return; @@ -1159,7 +1159,7 @@ void CObject::ObjectDamage(float damage, CVector* fxOrigin, CVector* fxDirection RwMatrix particleMat; g_fx.CreateMatFromVec(&particleMat, fxOrigin, fxDirection); - auto* fxSystem = g_fxMan.CreateFxSystem(m_pObjectInfo->m_pFxSystemBP, &particleMat, nullptr, false); + auto* fxSystem = g_fxMan.CreateFxSystem(m_pObjectInfo->m_pFxSystemBP, particleMat, nullptr, false); if (fxSystem) fxSystem->PlayAndKill(); @@ -1168,7 +1168,7 @@ void CObject::ObjectDamage(float damage, CVector* fxOrigin, CVector* fxDirection auto vecPoint = *m_matrix * m_pObjectInfo->m_vFxOffset; vecPoint += GetPosition(); - auto* fxSystem = g_fxMan.CreateFxSystem(m_pObjectInfo->m_pFxSystemBP, &vecPoint, nullptr, false); + auto* fxSystem = g_fxMan.CreateFxSystem(m_pObjectInfo->m_pFxSystemBP, vecPoint, nullptr, false); if (fxSystem) fxSystem->PlayAndKill(); } diff --git a/source/game_sa/Entity/Object/Object.h b/source/game_sa/Entity/Object/Object.h index 5c2d8b5744..ac11207bbb 100644 --- a/source/game_sa/Entity/Object/Object.h +++ b/source/game_sa/Entity/Object/Object.h @@ -140,7 +140,7 @@ class NOTSA_EXPORT_VTABLE CObject : public CPhysical { void GetLightingFromCollisionBelow(); void ProcessSamSiteBehaviour(); void ProcessTrainCrossingBehaviour(); - void ObjectDamage(float damage, CVector* fxOrigin, CVector* fxDirection, CEntity* damager, eWeaponType weaponType); + void ObjectDamage(float damage, const CVector* fxOrigin, const CVector* fxDirection, CEntity* damager, eWeaponType weaponType); void Explode(); void ObjectFireDamage(float damage, CEntity* damager); diff --git a/source/game_sa/Entity/Ped/Ped.cpp b/source/game_sa/Entity/Ped/Ped.cpp index 931e39b5c2..949443a92a 100644 --- a/source/game_sa/Entity/Ped/Ped.cpp +++ b/source/game_sa/Entity/Ped/Ped.cpp @@ -247,11 +247,11 @@ CPed::CPed(ePedType pedType) : CPhysical(), m_pedIK{CPedIK(this)} { m_nActiveWeaponSlot = 0; for (auto& weapon : m_aWeapons ) { - weapon.m_nType = WEAPON_UNARMED; - weapon.m_nState = WEAPONSTATE_READY; - weapon.m_nAmmoInClip = 0; - weapon.m_nTotalAmmo = 0; - weapon.m_nTimeForNextShot = 0; + weapon.m_Type = WEAPON_UNARMED; + weapon.m_State = WEAPONSTATE_READY; + weapon.m_AmmoInClip = 0; + weapon.m_TotalAmmo = 0; + weapon.m_TimeForNextShotMs = 0; } m_nWeaponSkill = eWeaponSkill::STD; @@ -633,13 +633,13 @@ void CPed::CreateDeadPedWeaponPickups() { } for (auto& wep : m_aWeapons) { - switch (wep.m_nType) { + switch (wep.m_Type) { case WEAPON_UNARMED: case WEAPON_DETONATOR: continue; } - if (!wep.m_nTotalAmmo && !wep.IsTypeMelee()) { + if (!wep.m_TotalAmmo && !wep.IsTypeMelee()) { continue; // Has no ammo, but isn't a melee weapon.. so it's a weapon with no ammo :D } @@ -649,18 +649,18 @@ void CPed::CreateDeadPedWeaponPickups() { pickupPos.z += 0.3f; // No. of ammo the pickups will contain - const auto pickupAmmo{ std::min(wep.m_nTotalAmmo, (uint32)AmmoForWeapon_OnStreet[(size_t)wep.m_nType] * 2) }; + const auto pickupAmmo{ std::min(wep.m_TotalAmmo, (uint32)AmmoForWeapon_OnStreet[(size_t)wep.m_Type] * 2) }; if (CPickups::TryToMerge_WeaponType( pickupPos, - wep.m_nType, + wep.m_Type, ePickupType::PICKUP_ONCE_TIMEOUT, pickupAmmo, false )) { CPickups::GenerateNewOne_WeaponType( pickupPos, - wep.m_nType, + wep.m_Type, bDeathPickupsPersist ? ePickupType::PICKUP_ONCE_FOR_MISSION : ePickupType::PICKUP_ONCE_TIMEOUT, pickupAmmo, false, @@ -1079,12 +1079,12 @@ void CPed::GrantAmmo(eWeaponType weaponType, uint32 ammo) { if (wepSlot != -1) { auto& wepInSlot = GetWeaponInSlot(wepSlot); - wepInSlot.m_nTotalAmmo = std::min(ammo, 99'999u); // Clamp upper + wepInSlot.m_TotalAmmo = std::min(wepInSlot.m_TotalAmmo + ammo, 99'999u); // Clamp upper // TODO: Inlined - if (wepInSlot.m_nState == WEAPONSTATE_OUT_OF_AMMO) { - if (wepInSlot.m_nTotalAmmo > 0) { - wepInSlot.m_nState = WEAPONSTATE_READY; + if (wepInSlot.m_State == WEAPONSTATE_OUT_OF_AMMO) { + if (wepInSlot.m_TotalAmmo > 0) { + wepInSlot.m_State = WEAPONSTATE_READY; } } } @@ -1099,13 +1099,13 @@ void CPed::SetAmmo(eWeaponType weaponType, uint32 ammo) { if (wepSlot != -1) { auto& wepInSlot = GetWeaponInSlot(wepSlot); - wepInSlot.m_nTotalAmmo = std::min(ammo, 99'999u); - wepInSlot.m_nAmmoInClip = std::max(wepInSlot.m_nTotalAmmo, wepInSlot.m_nAmmoInClip); + wepInSlot.m_TotalAmmo = std::min(ammo, 99'999u); + wepInSlot.m_AmmoInClip = std::max(wepInSlot.m_TotalAmmo, wepInSlot.m_AmmoInClip); // TODO: Inlined - if (wepInSlot.m_nState == WEAPONSTATE_OUT_OF_AMMO) { - if (wepInSlot.m_nTotalAmmo > 0) { - wepInSlot.m_nState = WEAPONSTATE_READY; + if (wepInSlot.m_State == WEAPONSTATE_OUT_OF_AMMO) { + if (wepInSlot.m_TotalAmmo > 0) { + wepInSlot.m_State = WEAPONSTATE_READY; } } } @@ -1117,7 +1117,7 @@ void CPed::SetAmmo(eWeaponType weaponType, uint32 ammo) { */ bool CPed::DoWeHaveWeaponAvailable(eWeaponType weaponType) { const auto slot = GetWeaponSlot(weaponType); - return slot != -1 && GetWeaponInSlot(slot).m_nType == weaponType; + return slot != -1 && GetWeaponInSlot(slot).m_Type == weaponType; } /*! @@ -1824,9 +1824,9 @@ void CPed::RemoveWeaponModel(int32 modelIndex) { // For players remove any attached FX (Created in `AddWeaponModel` for molotov) if (IsPlayer()) { auto& activeWep = GetActiveWeapon(); - if (activeWep.m_pFxSystem) { - g_fxMan.DestroyFxSystem(activeWep.m_pFxSystem); - activeWep.m_pFxSystem = nullptr; + if (activeWep.m_FxSystem) { + g_fxMan.DestroyFxSystem(activeWep.m_FxSystem); + activeWep.m_FxSystem = nullptr; } } @@ -1881,7 +1881,7 @@ void CPed::PutOnGoggles() { // Game checks if wepInSlot.m_nType != UNARMED here, not sure why? Probably compiler mistake on switch case codegen.. - switch (wepInSlot.m_nType) { + switch (wepInSlot.m_Type) { case WEAPON_INFRARED: case WEAPON_NIGHTVISION: { @@ -1890,7 +1890,7 @@ void CPed::PutOnGoggles() { AddGogglesModel(wepInSlot.GetWeaponInfo().m_nModelId1, state); }; - switch (wepInSlot.m_nType) { + switch (wepInSlot.m_Type) { case WEAPON_INFRARED: DoAddGogglesModel(CPostEffects::m_bInfraredVision); break; @@ -1900,7 +1900,7 @@ void CPed::PutOnGoggles() { } // Make sure weapon model doesn't get loaded (Because we've put the them on) - wepInSlot.m_bNoModel = true; + wepInSlot.m_DontPlaceInHand = true; // If it was the active weapon: unload it's weapon model if (&wepInSlot == &GetActiveWeapon()) { @@ -1917,7 +1917,7 @@ void CPed::PutOnGoggles() { * @returns Weapon skill with current weapon */ eWeaponSkill CPed::GetWeaponSkill() { - return GetWeaponSkill(GetActiveWeapon().m_nType); + return GetWeaponSkill(GetActiveWeapon().m_Type); } /*! @@ -2050,16 +2050,10 @@ void CPed::GetBonePosition(RwV3d& outPosition, ePedBones bone, bool updateSkinBo } } else if (!bCalledPreRender) { // Return static local bone position instead outPosition = MultiplyMatrixWithVector(*m_matrix, GetPedBoneStdPosition(bone)); - } else if (const auto hier = GetAnimHierarchyFromSkinClump(m_pRwClump)) { // Use position of bone matrix from anim hierarchy (if any) - // NOTE: Can't use `GetBoneMatrix` here, because it doesn't check for `hier`'s validity. (It's questionable whenever that's needed at all..) - RwV3dAssign(&outPosition, RwMatrixGetPos(&RpHAnimHierarchyGetMatrixArray(hier)[RpHAnimIDGetIndex(hier, (size_t)bone)])); - } else { // Not sure when can this happen.. GetTransformedBonePosition doesn't check this case. - outPosition = GetPosition(); // Return something close to valid.. - assert(0); // Let's see if this is possible at all. + return; } - - // TODO: Sometimes this shit becomes nan, let's investigate - assert(!std::isnan(outPosition.x)); + RwV3dAssign(&outPosition, RwMatrixGetPos(&GetBoneMatrix(bone))); + assert(!std::isnan(outPosition.x)); // TODO: Sometimes this shit becomes nan, let's investigate } /*! @@ -2097,6 +2091,7 @@ void CPed::SetPedState(ePedState pedState) { ReleaseCoverPoint(); if (bClearRadarBlipOnDeath) { CRadar::ClearBlipForEntity(BLIP_CHAR, GetPedPool()->GetRef(this)); + // TODO: Shouldn't we `bClearRadarBlipOnDeath = false` here? } } } @@ -2271,7 +2266,7 @@ void CPed::PlayFootSteps() { } } - if (m_pedAudio.field_7C) { // Move condition out here, but originally it was at 0x5E5AFA and 0x5E5A68 + if (m_pedAudio.m_iRadioStationScriptRequest) { // Move condition out here, but originally it was at 0x5E5AFA and 0x5E5A68 const auto DoAddSkateAE = [&, this](eAudioEvents audio) { // 0x5E5AB4 m_pedAudio.AddAudioEvent(audio, @@ -2302,7 +2297,7 @@ void CPed::PlayFootSteps() { // 0x5E5E56 and 0x5E5D57.. Seems like inlined? const auto DoFootStepAE = [&, this](bool isLeftFoot) { - if (m_pedAudio.field_7C) { + if (m_pedAudio.m_iRadioStationScriptRequest) { const auto DoAddFootStepAE = [&, this](float volume, float speed) { m_pedAudio.AddAudioEvent(isLeftFoot ? eAudioEvents::AE_PED_FOOTSTEP_RIGHT : eAudioEvents::AE_PED_FOOTSTEP_LEFT, volume, speed); }; @@ -2410,7 +2405,7 @@ void CPed::AddWeaponModel(int32 modelIndex) { // Make sure this weapon is supposed to have a model // (May be set to false even if it does, eg.: in case of infrared or night googles, see `TakeOffGoggles`) auto& activeWep = GetActiveWeapon(); - if (activeWep.m_bNoModel) { + if (activeWep.m_DontPlaceInHand) { return; } @@ -2429,13 +2424,13 @@ void CPed::AddWeaponModel(int32 modelIndex) { // If player and model is molotov create FX for it. if (IsPlayer()) { - if (activeWep.m_nType == WEAPON_MOLOTOV + if ( activeWep.m_Type == WEAPON_MOLOTOV && modelIndex == eModelID::MODEL_MOLOTOV - && !activeWep.m_pFxSystem + && !activeWep.m_FxSystem ) { CVector pos{ 0.f, 0.f, 0.f }; - activeWep.m_pFxSystem = g_fxMan.CreateFxSystem("molotov_flame", &pos, &GetBoneMatrix(ePedBones::BONE_R_HAND), false); - if (const auto fx = activeWep.m_pFxSystem) { + activeWep.m_FxSystem = g_fxMan.CreateFxSystem("molotov_flame", &pos, &GetBoneMatrix(ePedBones::BONE_R_HAND), false); + if (const auto fx = activeWep.m_FxSystem) { fx->SetLocalParticles(true); fx->CopyParentMatrix(); fx->Play(); @@ -2454,13 +2449,13 @@ void CPed::TakeOffGoggles() // Game checks if wepInSlot.m_nType != UNARMED here, not sure why? Probably compiler mistake on switch case codegen.. - switch (wepInSlot.m_nType) { + switch (wepInSlot.m_Type) { case WEAPON_INFRARED: case WEAPON_NIGHTVISION: { // Remove googles model RemoveGogglesModel(); - wepInSlot.m_bNoModel = false; + wepInSlot.m_DontPlaceInHand = false; // Since we've took off the goggles we might have to load it's weapon model if (&wepInSlot == &GetActiveWeapon()) { @@ -2482,15 +2477,15 @@ eWeaponSlot CPed::GiveWeapon(eWeaponType weaponType, uint32 ammo, bool likeUnuse auto& wepInSlot = GetWeaponInSlot(givenWepInfo->m_nSlot); const auto wepSlot = (eWeaponSlot)givenWepInfo->m_nSlot; - if (wepInSlot.m_nType != weaponType) { // Another weapon in the slot, remove it, and set this weapon + if (wepInSlot.m_Type != weaponType) { // Another weapon in the slot, remove it, and set this weapon // Remove previous weapon (and possibly add any ammo it had to `ammo`) - if (wepInSlot.m_nType != WEAPON_UNARMED) { + if (wepInSlot.m_Type != WEAPON_UNARMED) { switch (wepSlot) { case eWeaponSlot::SHOTGUN: case eWeaponSlot::SMG: case eWeaponSlot::RIFLE: { - ammo += wepInSlot.m_nTotalAmmo; + ammo += wepInSlot.m_TotalAmmo; break; } } @@ -2517,19 +2512,19 @@ eWeaponSlot CPed::GiveWeapon(eWeaponType weaponType, uint32 ammo, bool likeUnuse return eWeaponSlot::GIFT; } - wepInSlot.m_nTotalAmmo = std::min(99'999u, wepInSlot.m_nTotalAmmo + ammo); + wepInSlot.m_TotalAmmo = std::min(99'999u, wepInSlot.m_TotalAmmo + ammo); wepInSlot.Reload(this); // TODO: Inlined - if (wepInSlot.m_nState == WEAPONSTATE_OUT_OF_AMMO) { - if (wepInSlot.m_nTotalAmmo > 0) { - wepInSlot.m_nState = WEAPONSTATE_READY; + if (wepInSlot.m_State == WEAPONSTATE_OUT_OF_AMMO) { + if (wepInSlot.m_TotalAmmo > 0) { + wepInSlot.m_State = WEAPONSTATE_READY; } } } - if (wepInSlot.m_nState != WEAPONSTATE_OUT_OF_AMMO) { - wepInSlot.m_nState = WEAPONSTATE_READY; + if (wepInSlot.m_State != WEAPONSTATE_OUT_OF_AMMO) { + wepInSlot.m_State = WEAPONSTATE_READY; } return wepSlot; @@ -2599,7 +2594,7 @@ void CPed::SetCurrentWeapon(int32 slot) { } // Remove current weapon's model (if any) - if (const auto currWepType = GetActiveWeapon().m_nType; currWepType != WEAPON_UNARMED) { + if (const auto currWepType = GetActiveWeapon().m_Type; currWepType != WEAPON_UNARMED) { RemoveWeaponModel(CWeaponInfo::GetWeaponInfo(currWepType)->m_nModelId1); } @@ -2612,7 +2607,7 @@ void CPed::SetCurrentWeapon(int32 slot) { } // Load weapon in this slot (if any) - if (const auto wepInSlotType = m_aWeapons[slot].m_nType; wepInSlotType != WEAPON_UNARMED) { + if (const auto wepInSlotType = m_aWeapons[slot].m_Type; wepInSlotType != WEAPON_UNARMED) { AddWeaponModel(CWeaponInfo::GetWeaponInfo(wepInSlotType)->m_nModelId1); } } @@ -2637,7 +2632,7 @@ void CPed::ClearWeapon(eWeaponType weaponType) } auto& wep = m_aWeapons[wepSlot]; - if (wep.m_nType != weaponType) { + if (wep.m_Type != weaponType) { return; // Slot doesn't contain the given weapon - Might happen as some weapons share slots. } @@ -2735,7 +2730,7 @@ void CPed::ReplaceWeaponWhenExitingVehicle() { */ void CPed::ReplaceWeaponForScriptedCutscene() { - m_nSavedWeapon = GetActiveWeapon().m_nType; + m_nSavedWeapon = GetActiveWeapon().m_Type; SetCurrentWeapon(0); } @@ -2856,8 +2851,8 @@ CEntity* CPed::AttachPedToEntity(CEntity* entity, CVector offset, uint16 turretA } if (m_nSavedWeapon == WEAPON_UNIDENTIFIED) { - m_nSavedWeapon = GetActiveWeapon().m_nType; - m_nTurretAmmo = GetActiveWeapon().m_nTotalAmmo; // todo: unify types + m_nSavedWeapon = GetActiveWeapon().m_Type; + m_nTurretAmmo = GetActiveWeapon().m_TotalAmmo; // todo: unify types } if (!IsPlayer()) { @@ -2925,11 +2920,11 @@ void CPed::DettachPedFromEntity(){ // Restore old weapon if any if (m_nSavedWeapon != WEAPON_UNIDENTIFIED) { - GetActiveWeapon().m_nAmmoInClip = 0; - GetActiveWeapon().m_nTotalAmmo = 0; + GetActiveWeapon().m_AmmoInClip = 0; + GetActiveWeapon().m_TotalAmmo = 0; SetCurrentWeapon(m_nSavedWeapon); - GetActiveWeapon().m_nTotalAmmo = (uint32)m_nTurretAmmo; + GetActiveWeapon().m_TotalAmmo = (uint32)m_nTurretAmmo; m_nSavedWeapon = WEAPON_UNIDENTIFIED; } @@ -3029,7 +3024,7 @@ bool IsPedPointerValid(CPed* ped) */ void CPed::GiveWeaponAtStartOfFight() { - if (m_nCreatedBy != PED_MISSION && GetActiveWeapon().m_nType == WEAPON_UNARMED) + if (m_nCreatedBy != PED_MISSION && GetActiveWeapon().m_Type == WEAPON_UNARMED) { const auto GiveRandomWeaponByType = [this](eWeaponType type, uint16 maxRandom) { @@ -3075,7 +3070,7 @@ void CPed::GiveWeaponAtStartOfFight() */ void CPed::GiveWeaponWhenJoiningGang() { - if (GetActiveWeapon().m_nType == WEAPON_UNARMED && m_nDelayedWeapon == WEAPON_UNIDENTIFIED) { + if (GetActiveWeapon().m_Type == WEAPON_UNARMED && m_nDelayedWeapon == WEAPON_UNIDENTIFIED) { if (CCheat::IsActive(CHEAT_NO_ONE_CAN_STOP_US)) { GiveDelayedWeapon(WEAPON_AK47, 200); SetCurrentWeapon(CWeaponInfo::GetWeaponInfo(WEAPON_AK47, eWeaponSkill::STD)->m_nSlot); @@ -3304,6 +3299,13 @@ bool CPed::IsInVehicleThatHasADriver() { return false; } +/*! +* @notsa +*/ +int32 CPed::GetGroupId() { + return GetGroup() ? GetGroup()->GetId() : -1; +} + /*! * @notsa * @returns If ped is follower of \a group diff --git a/source/game_sa/Entity/Ped/Ped.h b/source/game_sa/Entity/Ped/Ped.h index c859584915..59363e8615 100644 --- a/source/game_sa/Entity/Ped/Ped.h +++ b/source/game_sa/Entity/Ped/Ped.h @@ -27,6 +27,7 @@ #include "ePedState.h" #include "ePedStats.h" #include "ePedType.h" +#include "eMoveState.h" class CPedGroup; class CCivilianPed; @@ -77,17 +78,6 @@ enum ePedCreatedBy : uint8 { PED_GAME_MISSION = 3, // used for the playbacked peds on replay }; -enum eMoveState : uint32 { - PEDMOVE_NONE = 0, - PEDMOVE_STILL, - PEDMOVE_TURN_L, - PEDMOVE_TURN_R, - PEDMOVE_WALK, - PEDMOVE_JOG, - PEDMOVE_RUN, - PEDMOVE_SPRINT -}; - enum eFightingStyle : int8 { STYLE_STANDARD = 4, STYLE_BOXING, @@ -525,8 +515,8 @@ class NOTSA_EXPORT_VTABLE CPed : public CPhysical { bool IsCreatedBy(ePedCreatedBy v) const noexcept { return v == m_nCreatedBy; } bool IsCreatedByMission() const noexcept { return IsCreatedBy(ePedCreatedBy::PED_MISSION); } - int32 GetGroupId() { return m_pPlayerData->m_nPlayerGroup; } CPedGroup* GetGroup() const { return CPedGroups::GetPedsGroup(this); } + int32 GetGroupId(); CPedClothesDesc* GetClothesDesc() { return m_pPlayerData->m_pPedClothesDesc; } CPedIntelligence* GetIntelligence() { return m_pIntelligence; } @@ -541,6 +531,7 @@ class NOTSA_EXPORT_VTABLE CPed : public CPhysical { CWeapon& GetWeaponInSlot(size_t slot) noexcept { return m_aWeapons[slot]; } CWeapon& GetWeaponInSlot(eWeaponSlot slot) noexcept { return m_aWeapons[(size_t)slot]; } CWeapon& GetActiveWeapon() noexcept { return GetWeaponInSlot(m_nActiveWeaponSlot); } + CWeapon& GetWeapon(eWeaponType wt) noexcept { return GetWeaponInSlot(GetWeaponSlot(wt)); } void SetSavedWeapon(eWeaponType weapon) { m_nSavedWeapon = weapon; } bool IsStateDriving() const noexcept { return m_nPedState == PEDSTATE_DRIVING; } @@ -567,7 +558,7 @@ class NOTSA_EXPORT_VTABLE CPed : public CPhysical { bool IsInVehicle(const CVehicle* veh) const { return bInVehicle && m_pVehicle == veh; } CVector GetBonePosition(ePedBones boneId, bool updateSkinBones = false); int32 GetPadNumber() const; - bool IsCurrentlyUnarmed() { return GetActiveWeapon().m_nType == WEAPON_UNARMED; } + bool IsCurrentlyUnarmed() { return GetActiveWeapon().m_Type == WEAPON_UNARMED; } /*! * @notsa @@ -598,7 +589,7 @@ class NOTSA_EXPORT_VTABLE CPed : public CPhysical { * @brief Give weapon according to given CWeapon struct. */ eWeaponSlot GiveWeapon(const CWeapon& weapon, bool likeUnused) { - return GiveWeapon(weapon.m_nType, weapon.m_nTotalAmmo, likeUnused); + return GiveWeapon(weapon.m_Type, weapon.m_TotalAmmo, likeUnused); } auto GetPedModelInfo() const { return reinterpret_cast(GetModelInfo()); } diff --git a/source/game_sa/Entity/Ped/PlayerPed.cpp b/source/game_sa/Entity/Ped/PlayerPed.cpp index 0c1e3503a3..e23d58972b 100644 --- a/source/game_sa/Entity/Ped/PlayerPed.cpp +++ b/source/game_sa/Entity/Ped/PlayerPed.cpp @@ -278,7 +278,7 @@ bool CPlayerPed::DoesPlayerWantNewWeapon(eWeaponType weaponType, bool arg1) { // GetPadFromPlayer(); // Called, but not used auto weaponSlot = GetWeaponSlot(weaponType); - auto weaponInSlotType = GetWeaponInSlot(weaponSlot).m_nType; + auto weaponInSlotType = GetWeaponInSlot(weaponSlot).m_Type; if (weaponInSlotType == weaponType) return true; @@ -322,7 +322,7 @@ void CPlayerPed::PickWeaponAllowedFor2Player() { // 0x609830 void CPlayerPed::UpdateCameraWeaponModes(CPad* pad) { - switch (GetActiveWeapon().m_nType) { + switch (GetActiveWeapon().m_Type) { case eWeaponType::WEAPON_M4: TheCamera.SetNewPlayerWeaponMode(eCamMode::MODE_M16_1STPERSON, 0, 0); break; @@ -372,7 +372,7 @@ float CPlayerPed::GetWeaponRadiusOnScreen() { return 0.0f; const float accuracyProg = 0.5f / wepInfo.m_fAccuracy; - switch (wep.m_nType) { + switch (wep.m_Type) { case eWeaponType::WEAPON_SHOTGUN: case eWeaponType::WEAPON_SPAS12_SHOTGUN: case eWeaponType::WEAPON_SAWNOFF_SHOTGUN: @@ -745,7 +745,7 @@ void CPlayerPed::MakeChangesForNewWeapon(eWeaponType weaponType) { CWeapon& wep = GetActiveWeapon(); CWeaponInfo& wepInfo = wep.GetWeaponInfo(this); - wep.m_nAmmoInClip = std::min(wep.m_nTotalAmmo, (uint32)wepInfo.m_nAmmoClip); + wep.m_AmmoInClip = std::min(wep.m_TotalAmmo, (uint32)wepInfo.m_nAmmoClip); if (!wepInfo.flags.bCanAim) ClearWeaponTarget(); @@ -802,7 +802,7 @@ bool CPlayerPed::DoesTargetHaveToBeBroken(CEntity* entity, CWeapon* weapon) { if (weapon->GetWeaponInfo(this).m_fTargetRange < (entity->GetPosition() - GetPosition()).Magnitude()) return true; - if (weapon->m_nType == eWeaponType::WEAPON_SPRAYCAN) { + if (weapon->m_Type == eWeaponType::WEAPON_SPRAYCAN) { if (entity->IsBuilding()) { if (CTagManager::IsTag(entity)) { if (CTagManager::GetAlpha(entity) == 255) { // they probably used -1 @@ -924,7 +924,7 @@ void CPlayerPed::SetInitialState(bool bGroupCreated) { // 0x60D000 void CPlayerPed::MakeChangesForNewWeapon(uint32 weaponSlot) { if (weaponSlot != -1) - MakeChangesForNewWeapon(GetWeaponInSlot(weaponSlot).m_nType); + MakeChangesForNewWeapon(GetWeaponInSlot(weaponSlot).m_Type); } static auto& PLAYER_MAX_TARGET_VIEW_ANGLE = *(float*)0x8D243C; // 140.0f @@ -1030,7 +1030,7 @@ void CPlayerPed::ProcessControl() { if (!bCanPointGunAtTarget) { m_pPlayerData->m_pWanted->Update(); PruneReferences(); - if (GetActiveWeapon().m_nType == WEAPON_MINIGUN) { + if (GetActiveWeapon().m_Type == WEAPON_MINIGUN) { auto weaponInfo = CWeaponInfo::GetWeaponInfo(WEAPON_MINIGUN, eWeaponSkill::STD); if (m_pIntelligence->GetTaskUseGun()) { auto animAssoc = m_pIntelligence->GetTaskUseGun()->m_pAnim; @@ -1039,7 +1039,7 @@ void CPlayerPed::ProcessControl() { m_pPlayerData->m_fGunSpinSpeed += CTimer::GetTimeStep() * 0.025f; m_pPlayerData->m_fGunSpinSpeed = std::min(m_pPlayerData->m_fGunSpinSpeed, 0.45f); } - if (pad->GetWeapon(this) && GetActiveWeapon().m_nTotalAmmo > 0 && animAssoc->m_fCurrentTime >= weaponInfo->m_fAnimLoopStart) + if (pad->GetWeapon(this) && GetActiveWeapon().m_TotalAmmo > 0 && animAssoc->m_fCurrentTime >= weaponInfo->m_fAnimLoopStart) m_weaponAudio.AddAudioEvent(AE_WEAPON_FIRE_MINIGUN_AMMO); else m_weaponAudio.AddAudioEvent(AE_WEAPON_FIRE_MINIGUN_NO_AMMO); @@ -1051,19 +1051,19 @@ void CPlayerPed::ProcessControl() { } } } - if (GetActiveWeapon().m_nType == WEAPON_CHAINSAW && m_nPedState != PEDSTATE_ATTACK && !bInVehicle) { + if (GetActiveWeapon().m_Type == WEAPON_CHAINSAW && m_nPedState != PEDSTATE_ATTACK && !bInVehicle) { m_pIntelligence->GetTaskSwim(); // hmmm? } if (m_pTargetedObject) { ClearReference(m_p3rdPersonMouseTarget); if (m_pTargetedObject->IsPed()) { CPed* targetPed = m_pTargetedObject->AsPed(); - auto weaponInfo = CWeaponInfo::GetWeaponInfo(GetActiveWeapon().m_nType, GetWeaponSkill()); + auto weaponInfo = CWeaponInfo::GetWeaponInfo(GetActiveWeapon().m_Type, GetWeaponSkill()); float targetHeadRange = weaponInfo->GetTargetHeadRange(); markColor = targetPed->m_fHealth / targetPed->m_fMaxHealth; bool instantFireHit = false; if (targetPed->IsAlive()) { - auto stdWeaponInfo = CWeaponInfo::GetWeaponInfo(GetActiveWeapon().m_nType, eWeaponSkill::STD); + auto stdWeaponInfo = CWeaponInfo::GetWeaponInfo(GetActiveWeapon().m_Type, eWeaponSkill::STD); if (stdWeaponInfo->m_nWeaponFire == WEAPON_FIRE_INSTANT_HIT) { instantFireHit = true; CVector distance = targetPed->GetPosition() - GetPosition(); @@ -1141,8 +1141,8 @@ void CPlayerPed::ProcessControl() { if (pad) { if (pad->WeaponJustDown(this)) { auto& activeWeapon = GetActiveWeapon(); - auto weaponType = activeWeapon.m_nType; - if (!TheCamera.Using1stPersonWeaponMode() || activeWeapon.m_nState == WEAPONSTATE_OUT_OF_AMMO) { + auto weaponType = activeWeapon.m_Type; + if (!TheCamera.Using1stPersonWeaponMode() || activeWeapon.m_State == WEAPONSTATE_OUT_OF_AMMO) { if (!m_pIntelligence->GetTaskSwim()) { if (weaponType == WEAPON_SNIPERRIFLE) { AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_FIRE_FAIL_SNIPERRIFFLE, 0.0f, 1.0f); @@ -1198,7 +1198,7 @@ void CPlayerPed::ProcessControl() { m_pPlayerData->m_bDontAllowWeaponChange = false; } } - if (m_nPedState != PEDSTATE_SNIPER_MODE && GetActiveWeapon().m_nState == WEAPONSTATE_FIRING) + if (m_nPedState != PEDSTATE_SNIPER_MODE && GetActiveWeapon().m_State == WEAPONSTATE_FIRING) m_pPlayerData->m_nLastTimeFiring = CTimer::GetTimeInMS(); ProcessGroupBehaviour(pad); if (bInVehicle) diff --git a/source/game_sa/Entity/Physical.cpp b/source/game_sa/Entity/Physical.cpp index cb7f2d0393..1361b344fa 100644 --- a/source/game_sa/Entity/Physical.cpp +++ b/source/game_sa/Entity/Physical.cpp @@ -249,8 +249,9 @@ void CPhysical::ProcessControl() } // 0x54DFB0 -void CPhysical::ProcessCollision() -{ +void CPhysical::ProcessCollision() { + ZoneScoped; + auto* vehicle = AsVehicle(); auto* automobile = AsAutomobile(); auto* bike = AsBike(); @@ -462,8 +463,9 @@ void CPhysical::ProcessCollision() } // 0x54DB10 -void CPhysical::ProcessShift() -{ +void CPhysical::ProcessShift() { + ZoneScoped; + CRect boundingBox; GetBoundRect(&boundingBox); m_fMovingSpeed = 0.0f; diff --git a/source/game_sa/Entity/Placeable.cpp b/source/game_sa/Entity/Placeable.cpp index fd463d720b..6eff7a953e 100644 --- a/source/game_sa/Entity/Placeable.cpp +++ b/source/game_sa/Entity/Placeable.cpp @@ -17,6 +17,7 @@ void CPlaceable::InjectHooks() { RH_ScopedOverloadedInstall(SetOrientation, "xyz", 0x439A80, void(CPlaceable::*)(float, float, float)); RH_ScopedInstall(SetHeading, 0x43E0C0); RH_ScopedInstall(GetHeading, 0x441DB0); + RH_ScopedInstall(GetRoll, 0x420B30); RH_ScopedOverloadedInstall(IsWithinArea, "xy", 0x54F200, bool(CPlaceable::*)(float, float, float, float) const); RH_ScopedOverloadedInstall(IsWithinArea, "xyz", 0x54F2B0, bool(CPlaceable::*)(float, float, float, float, float, float) const); RH_ScopedInstall(RemoveMatrix, 0x54F3B0); @@ -89,11 +90,23 @@ void CPlaceable::SetHeading(float heading) { } float CPlaceable::GetHeading() { - if (!m_matrix) + if (!m_matrix) { return m_placement.m_fHeading; + } - const auto& vecForward = m_matrix->GetForward(); - return std::atan2(-vecForward.x, vecForward.y); + const auto& fwd = m_matrix->GetForward(); + return std::atan2(-fwd.x, fwd.y); +} + +// 0x420B30 +float CPlaceable::GetRoll() const { + if (!m_matrix) { + return 0.f; + } + + const auto& right = m_matrix->GetRight(); + const auto xymag = CVector2D{ right }.SquaredMagnitude(); // NOTE: We're using sqmag here because it doesn't matter, and we save a sqrt this way. + return std::atan2(right.z, m_matrix->GetUp().z < 0.f ? -xymag : xymag); } bool CPlaceable::IsWithinArea(float x1, float y1, float x2, float y2) const { @@ -196,6 +209,8 @@ void CPlaceable::ShutdownMatrixArray() { } void CPlaceable::InitMatrixArray() { + ZoneScoped; + gMatrixList.Init(CPlaceable::NUM_MATRICES_TO_CREATE); } diff --git a/source/game_sa/Entity/Placeable.h b/source/game_sa/Entity/Placeable.h index bf572d7b7a..59d996293b 100644 --- a/source/game_sa/Entity/Placeable.h +++ b/source/game_sa/Entity/Placeable.h @@ -40,6 +40,7 @@ class CPlaceable { void GetOrientation(float& x, float& y, float& z); void SetHeading(float heading); float GetHeading(); + float GetRoll() const; bool IsWithinArea(float x1, float y1, float x2, float y2) const; bool IsWithinArea(float x1, float y1, float z1, float x2, float y2, float z2) const; void RemoveMatrix(); diff --git a/source/game_sa/Entity/Vehicle/Automobile.cpp b/source/game_sa/Entity/Vehicle/Automobile.cpp index c08df4e414..46d0d52fd3 100644 --- a/source/game_sa/Entity/Vehicle/Automobile.cpp +++ b/source/game_sa/Entity/Vehicle/Automobile.cpp @@ -4927,8 +4927,7 @@ void CAutomobile::ProcessCarOnFireAndExplode(bool bExplodeImmediately) { m_pFireParticle = g_fxMan.CreateFxSystem( typ == 1 ? "fire_car" : "fire_large", &pos, - mat - ); + mat); } if (m_pFireParticle) { m_pFireParticle->Play(); @@ -5111,7 +5110,6 @@ CObject* CAutomobile::SpawnFlyingComponent(eCarNodes nodeIndex, uint32 collision *RwFrameGetMatrix(flyingObjFrame) = *frameLTM; // Set this frame's matrix to be the same as the component's - TODO: This most likely isn't the correct way to do this.. CVisibilityPlugins::SetAtomicRenderCallback(clonedFlyingObjAtomic, nullptr); obj->AttachToRwObject((RwObject*)clonedFlyingObjAtomic, true); - obj->m_bDontStream = true; obj->m_fMass = 10.f; obj->m_fTurnMass = 25.f; diff --git a/source/game_sa/Entity/Vehicle/Heli.cpp b/source/game_sa/Entity/Vehicle/Heli.cpp index b8a71f1508..91f018e990 100644 --- a/source/game_sa/Entity/Vehicle/Heli.cpp +++ b/source/game_sa/Entity/Vehicle/Heli.cpp @@ -124,6 +124,8 @@ void CHeli::PreRenderAlways() { // 0x6C4650 void CHeli::Pre_SearchLightCone() { + ZoneScoped; + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDONE)); @@ -138,6 +140,8 @@ void CHeli::Pre_SearchLightCone() { // 0x6C46E0 void CHeli::Post_SearchLightCone() { + ZoneScoped; + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); @@ -223,11 +227,15 @@ bool CHeli::SendDownSwat() { // 0x6C79A0 void CHeli::UpdateHelis() { + ZoneScoped; + ((void(__cdecl*)())0x6C79A0)(); } // 0x6C7C50 void CHeli::RenderAllHeliSearchLights() { + ZoneScoped; + for (auto& light : HeliSearchLights) { SearchLightCone( light.m_nCoronaIndex, diff --git a/source/game_sa/Entity/Vehicle/Train.cpp b/source/game_sa/Entity/Vehicle/Train.cpp index 472de77a65..4d0a40aa5d 100644 --- a/source/game_sa/Entity/Vehicle/Train.cpp +++ b/source/game_sa/Entity/Vehicle/Train.cpp @@ -155,6 +155,8 @@ void CTrain::SetupModelNodes() { // 0x6F7440 void CTrain::InitTrains() { + ZoneScoped; + return plugin::Call<0x6F7440>(); bDisableRandomTrains = false; @@ -195,6 +197,8 @@ void CTrain::Shutdown() { // 0x6F5900 void CTrain::UpdateTrains() { + ZoneScoped; + // NOP } diff --git a/source/game_sa/Entity/Vehicle/Vehicle.cpp b/source/game_sa/Entity/Vehicle/Vehicle.cpp index 5abab5c7b5..3f947e17b4 100644 --- a/source/game_sa/Entity/Vehicle/Vehicle.cpp +++ b/source/game_sa/Entity/Vehicle/Vehicle.cpp @@ -101,7 +101,6 @@ void CVehicle::InjectHooks() { RH_ScopedInstall(UpdateLightingFromStoredPolys, 0x6D0CC0); RH_ScopedInstall(CalculateLightingFromCollision, 0x6D0CF0); RH_ScopedInstall(ProcessWheel, 0x6D6C00); - RH_ScopedInstall(AddExhaustParticles, 0x6DE240); RH_ScopedInstall(ApplyBoatWaterResistance, 0x6D2740); RH_ScopedInstall(ProcessBoatControl, 0x6DBCE0); RH_ScopedInstall(ChangeLawEnforcerState, 0x6D2330); @@ -138,7 +137,6 @@ void CVehicle::InjectHooks() { RH_ScopedInstall(CarHasRoof, 0x6D25D0); RH_ScopedInstall(HeightAboveCeiling, 0x6D2600); RH_ScopedInstall(SetComponentVisibility, 0x6D2700); - RH_ScopedInstall(ApplyBoatWaterResistance, 0x6D2740); RH_ScopedInstall(SetComponentAtomicAlpha, 0x6D2960); RH_ScopedInstall(UpdateClumpAlpha, 0x6D2980); RH_ScopedInstall(UpdatePassengerList, 0x6D29E0); diff --git a/source/game_sa/Entity/Vehicle/Vehicle.h b/source/game_sa/Entity/Vehicle/Vehicle.h index 55615ff34f..1a09f5ad85 100644 --- a/source/game_sa/Entity/Vehicle/Vehicle.h +++ b/source/game_sa/Entity/Vehicle/Vehicle.h @@ -110,6 +110,7 @@ enum eCarPiece { CAR_PIECE_WHEEL_RR, CAR_PIECE_WINDSCREEN = 19, }; +constexpr inline eCarPiece eCarPiece_WheelPieces[]{ CAR_PIECE_WHEEL_LF, CAR_PIECE_WHEEL_RF, CAR_PIECE_WHEEL_RL, CAR_PIECE_WHEEL_RR }; enum eRotationAxis : int32 { AXIS_X = 0, diff --git a/source/game_sa/EntryExit.cpp b/source/game_sa/EntryExit.cpp index a034628678..c566cbd40a 100644 --- a/source/game_sa/EntryExit.cpp +++ b/source/game_sa/EntryExit.cpp @@ -4,8 +4,12 @@ #include "EntryExitManager.h" #include "TaskComplexGotoDoorAndOpen.h" #include "TaskSimpleUninterruptable.h" +#include "TaskComplexFollowLeaderInFormation.h" #include "TaskComplexFacial.h" #include "Garages.h" +#include "PlayerPed.h" +#include "Object.h" +#include "Interior/InteriorManager_c.h" bool& CEntryExit::ms_bWarping = *(bool*)0x96A7B8; CObject*& CEntryExit::ms_pDoor = *(CObject**)0x96A7BC; @@ -19,13 +23,13 @@ void CEntryExit::InjectHooks() { RH_ScopedInstall(GetEntryExitToDisplayNameOf, 0x43E650); RH_ScopedInstall(FindValidTeleportPoint, 0x43EAF0); RH_ScopedInstall(IsInArea, 0x43E460); - RH_ScopedInstall(GetPositionRelativeToOutsideWorld, 0x43EA00); + RH_ScopedInstall(GetPositionRelativeToOutsideWorld, 0x43EA00, {.locked = true}); RH_ScopedInstall(TransitionStarted, 0x43FFD0); RH_ScopedInstall(TransitionFinished, 0x4404A0, { .reversed = false }); RH_ScopedInstall(RequestObjectsInFrustum, 0x43E690); RH_ScopedInstall(RequestAmbientPeds, 0x43E6D0); - RH_ScopedInstall(WarpGangWithPlayer, 0x43F1F0, { .reversed = false }); - RH_ScopedInstall(ProcessStealableObjects, 0x43E990, { .reversed = false }); + RH_ScopedInstall(WarpGangWithPlayer, 0x43F1F0); + RH_ScopedInstall(ProcessStealableObjects, 0x43E990); } // Code based on 0x43FA00 @@ -78,6 +82,12 @@ CEntryExit::CEntryExit( } } +// 0x43EA90 +bool IsTeleportPointValid(const CVector& origin, const CVector& target) { + return !CWorld::TestSphereAgainstWorld(target, 0.35f, nullptr, true, true, true, true, true, false) + && CWorld::GetIsLineOfSightClear(origin, target, true, true, false, true, true, false, false); +} + // 0x43E8B0 void CEntryExit::GenerateAmbientPeds(const CVector& posn) { CPopulation::bInPoliceStation = false; @@ -96,18 +106,19 @@ void CEntryExit::GenerateAmbientPeds(const CVector& posn) { // 0x43E650 CEntryExit* CEntryExit::GetEntryExitToDisplayNameOf() { - if (ms_spawnPoint->m_nArea != eAreaCodes::AREA_CODE_NORMAL_WORLD && HasNameSet()) { - if (m_nArea != eAreaCodes::AREA_CODE_NORMAL_WORLD) { - // TODO: Probably inlined from `CEntryExitManager` - if ( CEntryExitManager::ms_entryExitStackPosn > 1 - && CEntryExitManager::ms_entryExitStack[CEntryExitManager::ms_entryExitStackPosn - 1] == ms_spawnPoint - ) { - return CEntryExitManager::ms_entryExitStack[CEntryExitManager::ms_entryExitStackPosn]; - } - return this; - } + if (ms_spawnPoint->m_nArea == eAreaCodes::AREA_CODE_NORMAL_WORLD || !HasNameSet()) { + return nullptr; + } + if (m_nArea == eAreaCodes::AREA_CODE_NORMAL_WORLD) { + return nullptr; } - return nullptr; + // TODO: Probably inlined from `CEntryExitManager` + if ( CEntryExitManager::ms_entryExitStackPosn > 1 + && CEntryExitManager::ms_entryExitStack[CEntryExitManager::ms_entryExitStackPosn - 1] == ms_spawnPoint + ) { + return CEntryExitManager::ms_entryExitStack[CEntryExitManager::ms_entryExitStackPosn]; + } + return this; } // 0x43EA00 @@ -116,7 +127,7 @@ CEntryExit* CEntryExit::GetEntryExitToDisplayNameOf() { void CEntryExit::GetPositionRelativeToOutsideWorld(CVector& outPos) { _asm { push edx }; - const auto enex = GetLinkedOrThis(); + const auto enex = GetLinkedOrThis(); // this = GetLinkedOrThis(); if (enex->m_nArea != eAreaCodes::AREA_CODE_NORMAL_WORLD) { outPos += GetPosition() - enex->m_vecExitPos; } @@ -151,29 +162,27 @@ CVector CEntryExit::TransformEntrancePoint(const CVector& point) const { } // 0x43EAF0 -void CEntryExit::FindValidTeleportPoint(CVector* outTeleportPoint) { - const auto spawnPointExitPos = ms_spawnPoint->m_vecExitPos; +void CEntryExit::FindValidTeleportPoint(CVector& outTeleportPoint) { + outTeleportPoint = ms_spawnPoint->m_vecExitPos; - if (!CWorld::TestSphereAgainstWorld(spawnPointExitPos, 0.35f, nullptr, true, true, true, true, true, false)) { + if (!CWorld::TestSphereAgainstWorld(outTeleportPoint, 0.35f, nullptr, true, true, true, true, true, false)) { return; } - - // Test 8 spheres around the spawn point, and return whichever + + // Test 2 * 8 spheres around the spawn point, and return whichever // doesn't collide with the world and the line of sight between it and `outPoint` is clear for (auto r : { 1.25f, 2.f }) { // Test with 2 ranges constexpr auto NumTestPoints{ 8 }; for (auto i = 0; i < NumTestPoints; i++) { const auto rot{ (float)i * TWO_PI / (float)NumTestPoints }; - const auto point = *outTeleportPoint + CVector{ + const auto point = outTeleportPoint + CVector{ std::cos(rot) * r, std::sin(rot) * r, 0.f }; - if (!CWorld::TestSphereAgainstWorld(point, 0.35f, nullptr, true, true, true, true, true, false)) { - if (CWorld::GetIsLineOfSightClear(*outTeleportPoint, point, true, true, false, true, true, false, false)) { - *outTeleportPoint = point; - return; - } + if (IsTeleportPointValid(outTeleportPoint, point)) { + outTeleportPoint = point; + return; } } } @@ -235,10 +244,9 @@ bool CEntryExit::TransitionStarted(CPed* ped) { } break; } - default: { // Nothing else is disallowed - TODO: Allow uncomment this, I want my Rhino in CJ's house + default: // Nothing else is disallowed - TODO: Uncomment this, I want my Rhino in CJ's house return false; } - } } else if (bDisableExit) { return false; } @@ -257,21 +265,24 @@ bool CEntryExit::TransitionStarted(CPed* ped) { } bEnteredWithoutExit = true; - ms_pDoor = nullptr; + ms_pDoor = nullptr; - ped->bCanExitCar = false; + ped->bCanExitCar = false; const auto AddPedScriptCommand = [ped](auto* task) { - CEventScriptCommand scriptCmdEvent{ ePrimaryTasks::TASK_PRIMARY_PRIMARY, task, false }; - ped->GetEventGroup().Add(&scriptCmdEvent); + ped->GetEventGroup().Add(CEventScriptCommand{ + TASK_PRIMARY_PRIMARY, + task, + false + }); }; - if ((bUnknownPairing || bFoodDateFlag) || ped->bInVehicle) { - if (spawnPointExitToUs.Magnitude() > 10.f) { + if (bUnknownPairing || bFoodDateFlag || ped->bInVehicle) { + if (spawnPointExitToUs.SquaredMagnitude() > sq(10.f)) { ms_bWarping = true; } } else { - ms_bWarping = spawnPointExitToUs.Magnitude() > 10.f; + ms_bWarping = spawnPointExitToUs.SquaredMagnitude() > sq(10.f); const auto spawnPointExitToUsDir = Normalized(spawnPointExitToUs); @@ -312,7 +323,7 @@ bool CEntryExit::TransitionStarted(CPed* ped) { // 0x4404A0 bool CEntryExit::TransitionFinished(CPed* ped) { return plugin::CallMethodAndReturn(this, ped); - + /* const auto spawnPos = ms_spawnPoint->m_vecExitPos; if (const auto entity = ped->GetEntityThatThisPedIsHolding()) { @@ -509,6 +520,7 @@ bool CEntryExit::TransitionFinished(CPed* ped) { } return 0; // TODO + */ } // 0x43E6D0 @@ -518,44 +530,34 @@ void CEntryExit::RequestAmbientPeds() { return; } - if (_stricmp("bar1", m_szName) == 0) { - int32 peds[] = { - MODEL_BFYRI, MODEL_OFYST, MODEL_WFYST, MODEL_WMYST, - MODEL_BMYRI, MODEL_BMYST, MODEL_OMOST, MODEL_OMYST, - }; - CStreaming::StreamPedsIntoRandomSlots(peds); - } - - if (_stricmp("strip2", m_szName) == 0) { - int32 peds[] = { - MODEL_SBFYSTR, MODEL_SWFYSTR, MODEL_INVALID, MODEL_INVALID, - MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, - }; - CStreaming::StreamPedsIntoRandomSlots(peds); - } - - if (_stricmp("LAstrip", m_szName) == 0) { - int32 peds[] = { - MODEL_VWFYST1, MODEL_VBFYST2, MODEL_VHFYST3, MODEL_INVALID, - MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, - }; - CStreaming::StreamPedsIntoRandomSlots(peds); - } - - if (_stricmp("MAFCAS", m_szName) == 0) { - int32 peds[] = { - MODEL_BFORI, MODEL_HFYRI, MODEL_OMYRI, MODEL_WMYRI, - MODEL_OFYST, MODEL_VHMYELV, MODEL_WMOST, MODEL_BMORI, - }; - CStreaming::StreamPedsIntoRandomSlots(peds); - } + constexpr struct { const char* name; int32 models[TOTAL_LOADED_PEDS]; } mapping[]{ + { + "bar1", + {MODEL_BFYRI, MODEL_OFYST, MODEL_WFYST, MODEL_WMYST, MODEL_BMYRI, MODEL_BMYST, MODEL_OMOST, MODEL_OMYST} + }, + { + "strip2", + {MODEL_SBFYSTR, MODEL_SWFYSTR, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID} + }, + { + "LAstrip", + {MODEL_VWFYST1, MODEL_VBFYST2, MODEL_VHFYST3, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID, MODEL_INVALID} + }, + { + "MAFCAS", + {MODEL_BFORI, MODEL_HFYRI, MODEL_OMYRI, MODEL_WMYRI, MODEL_OFYST, MODEL_VHMYELV, MODEL_WMOST, MODEL_BMORI} + }, + { + "TRICAS", + {MODEL_BFYRI, MODEL_BMYRI, MODEL_HFORI, MODEL_HMORI, MODEL_WMORI, MODEL_WFYRI, MODEL_OMOST, MODEL_VBMYELV} + }, + }; - if (_stricmp("TRICAS", m_szName) == 0) { - int32 peds[] = { - MODEL_BFYRI, MODEL_BMYRI, MODEL_HFORI, MODEL_HMORI, - MODEL_WMORI, MODEL_WFYRI, MODEL_OMOST, MODEL_VBMYELV, - }; - CStreaming::StreamPedsIntoRandomSlots(peds); + for (auto&& [name, models] : mapping) { + if (_stricmp(name, m_szName)) { + CStreaming::StreamPedsIntoRandomSlots(models); + return; + } } } // 0x43E690 @@ -564,29 +566,75 @@ void CEntryExit::RequestObjectsInFrustum() const { } // 0x43F1F0 -void CEntryExit::WarpGangWithPlayer(CPed* ped) { - auto& grp = ped->AsPlayer()->GetPlayerGroup(); +void CEntryExit::WarpGangWithPlayer(CPlayerPed* player) { + auto& grp = player->GetPlayerGroup(); - if (!CPedGroups::ScriptReferenceIndex[grp.GetId()]) { + if (!grp.IsActive()) { return; } auto& ms = grp.GetMembership(); - if (!ms.IsLeader(ped)) { + if (!ms.IsLeader(player)) { return; } - /* - for (auto i = 0; i < 8 - 1; i++) { - const auto member = membership.GetMember(i); - if (!member || member == ped || ) + const auto& plyrPos = player->GetPosition(); + const auto& offsets = CTaskComplexFollowLeaderInFormation::ms_offsets.offsets; + + size_t offsetIdx = 0; + for (auto & mem : ms.GetMembers()) { + if (&mem == player) { + continue; + } + const auto& memPos = mem.GetPosition(); + + // Find position to teleport member to + // Original code tried only twice, but we'll try all offsets + // (Probably won't make a lot of difference) + CVector memTeleportTo; + do { + if (offsetIdx >= offsets.size()) { + return; // No more offsets + } + memTeleportTo = memPos + CVector{ offsets[offsetIdx++], 0.f }; + } while (!IsTeleportPointValid(plyrPos, memTeleportTo)); - // TODO: Missing CTaskComplexFollowLeaderInFormation::ms_offsets - }*/ + // Calculate member heading (So that they'll face the player) + const auto memHeading = (plyrPos - memPos).Heading(); + + // Teleport them + mem.Teleport(memTeleportTo, false); + + // Make the member be heading towards the player + mem.m_fCurrentRotation = mem.m_fAimingRotation = memHeading; + mem.SetHeading(memHeading); + mem.m_nAreaCode = player->m_nAreaCode; + mem.m_pEnex = player->m_pEnex; + } } // 0x43E990 void CEntryExit::ProcessStealableObjects(CPed* ped) { - plugin::CallMethod<0x43E990, CEntryExit*, CPed*>(this, ped); + const auto helde = ped->GetEntityThatThisPedIsHolding(); + if (!helde || !helde->IsObject() || !helde->AsObject()->objectFlags.bIsLiftable) { + return; + } + const auto heldobj = helde->AsObject(); + switch (heldobj->m_nObjectType) { + case OBJECT_MISSION: + case OBJECT_MISSION2: + break; + default: { + if (ms_spawnPoint->GetArea() != AREA_CODE_NORMAL_WORLD) { + if (g_interiorMan.FindStealableObjectId(heldobj) != -1) { + g_interiorMan.SetStealableObjectStolen(heldobj, false); + } + } else { + g_interiorMan.SetStealableObjectStolen(heldobj, true); + } + break; + } + } + heldobj->m_nAreaCode = ms_spawnPoint->GetArea(); } diff --git a/source/game_sa/EntryExit.h b/source/game_sa/EntryExit.h index 4813ca95e8..2c83ea3578 100644 --- a/source/game_sa/EntryExit.h +++ b/source/game_sa/EntryExit.h @@ -11,6 +11,7 @@ class CObject; class CPed; +class CPlayerPed; class CEntryExit; class CEntryExit { @@ -63,7 +64,7 @@ class CEntryExit { }; uint16 m_nFlags{}; }; - uint8 m_nArea{}; + eAreaCodes m_nArea{}; uint8 m_nSkyColor{}; uint8 m_nTimeOn{}; uint8 m_nTimeOff{}; @@ -76,6 +77,8 @@ class CEntryExit { public: static void InjectHooks(); + + static void WarpGangWithPlayer(CPlayerPed* plyr); CEntryExit( CVector center, @@ -99,9 +102,8 @@ class CEntryExit { void RequestObjectsInFrustum() const; bool TransitionFinished(CPed* ped); bool TransitionStarted(CPed* ped); - static void WarpGangWithPlayer(CPed* ped); void ProcessStealableObjects(CPed* ped); - void FindValidTeleportPoint(CVector* point); + void FindValidTeleportPoint(CVector& outTeleportPoint); bool HasNameSet() const; // NOTSA @@ -121,6 +123,6 @@ class CEntryExit { [[nodiscard]] CVector GetPosition() const; [[nodiscard]] CVector2D GetPosition2D() const; [[nodiscard]] uint8 GetMyOrLinkedArea() const; + [[nodiscard]] auto GetArea() const { return m_nArea; } }; - VALIDATE_SIZE(CEntryExit, 0x3C); diff --git a/source/game_sa/EntryExitManager.cpp b/source/game_sa/EntryExitManager.cpp index c336172590..65b0f4222a 100644 --- a/source/game_sa/EntryExitManager.cpp +++ b/source/game_sa/EntryExitManager.cpp @@ -16,7 +16,7 @@ void CEntryExitManager::InjectHooks() { RH_ScopedInstall(FindNearestDoor, 0x43F630); RH_ScopedInstall(FindNearestEntryExit, 0x43F4B0); RH_ScopedInstall(EnableBurglaryHouses, 0x43F180); - RH_ScopedInstall(GetPositionRelativeToOutsideWorld, 0x43F150); + RH_ScopedInstall(GetPositionRelativeToOutsideWorld, 0x43F150, {.locked = true}); RH_ScopedInstall(PostEntryExitsCreation, 0x43F0A0); RH_ScopedInstall(LinkEntryExit, 0x43F050); RH_ScopedInstall(GetEntryExitIndex, 0x43EFD0); @@ -28,6 +28,8 @@ void CEntryExitManager::InjectHooks() { // 0x43F880 void CEntryExitManager::Init() { + ZoneScoped; + mp_QuadTree = new CQuadTreeNode(WORLD_BOUNDS, 4); ms_exitEnterState = 0; @@ -80,6 +82,8 @@ void CEntryExitManager::ShutdownForRestart() { // 0x440D10 void CEntryExitManager::Update() { + ZoneScoped; + const bool bDontShowMarkers = CCutsceneMgr::ms_cutsceneProcessing || CPad::GetPad()->DisablePlayerControls @@ -156,9 +160,8 @@ void CEntryExitManager::Update() { // 0x43E410 void CEntryExitManager::AddEntryExitToStack(CEntryExit* enex) { - if (enex->m_pLink) - enex = enex->m_pLink; - + enex = enex->GetLinkedOrThis(); + if (ms_entryExitStackPosn > 0 && ms_entryExitStack[ms_entryExitStackPosn - 1] == enex) { ms_entryExitStackPosn--; } else if (enex->m_nArea != AREA_CODE_NORMAL_WORLD) { @@ -224,6 +227,9 @@ int32 CEntryExitManager::AddOne( void CEntryExitManager::DeleteOne(int32 index) { if (const auto enex = mp_poolEntryExits->GetAt(index)) { mp_QuadTree->DeleteItem(enex); +#ifdef FIX_BUGS // Call destructor + std::destroy_at(enex); +#endif mp_poolEntryExits->Delete(enex); } } @@ -236,30 +242,31 @@ CObject* CEntryExitManager::FindNearestDoor(CEntryExit const& exit, float radius int16 numObjsInRange{}; CWorld::FindObjectsInRange(entranceCenter, radius, false, &numObjsInRange, (int16)std::size(objsInRange), objsInRange, false, false, false, true, false); - if (numObjsInRange) { - float closestDistSq{ FLT_MAX }; - CObject* closest{}; - - for (auto&& entity : std::span{ objsInRange, (size_t)numObjsInRange }) { - const auto object = entity->AsObject(); - if (object->physicalFlags.bDisableMoveForce) { - const auto distSq = (object->GetPosition() - entranceCenter).SquaredMagnitude(); // Using `SqMag` instead of `Mag` - if (distSq < closestDistSq) { - closest = object; - closestDistSq = distSq; - } - } + if (!numObjsInRange) { + return nullptr; + } + + float closestDistSq{ FLT_MAX }; + CObject* closest{}; + for (auto&& entity : std::span{ objsInRange, (size_t)numObjsInRange }) { + const auto obj = entity->AsObject(); + if (!obj->physicalFlags.bDisableMoveForce) { + continue; + } + + const auto distSq = (obj->GetPosition() - entranceCenter).SquaredMagnitude(); // Using `SqMag` instead of `Mag` + if (distSq < closestDistSq) { + closest = obj; + closestDistSq = distSq; } - return closest; } - return nullptr; + return closest; } // 0x43F4B0 int32 CEntryExitManager::FindNearestEntryExit(const CVector2D& position, float range, int32 ignoreArea) { - CRect rect(position.x - range, position.y - range, position.x + range, position.y + range); CPtrListSingleLink enexInRange{}; - mp_QuadTree->GetAllMatching(rect, enexInRange); + mp_QuadTree->GetAllMatching(CRect{ position, range }, enexInRange); float closestDist2D{ 2.f * range }; CEntryExit* closest{}; @@ -267,16 +274,20 @@ int32 CEntryExitManager::FindNearestEntryExit(const CVector2D& position, float r next = it->GetNext(); auto* enex = it->ItemAs(); - if (enex->GetMyOrLinkedArea() != ignoreArea) { - const auto dist = (enex->GetPosition2D() - position).Magnitude(); // TODO: Use SqMag - if (dist < closestDist2D) { - closest = enex; - closestDist2D = dist; - } + if (enex->GetLinkedOrThis()->GetArea() == ignoreArea) { + continue; + } + + const auto dist = (enex->GetPosition2D() - position).Magnitude(); // TODO: Use SqMag + if (dist < closestDist2D) { + closest = enex; + closestDist2D = dist; } } - return closest ? mp_poolEntryExits->GetIndex(closest) : -1; + return closest + ? mp_poolEntryExits->GetIndex(closest) + : -1; } // 0x43F180 @@ -323,6 +334,7 @@ void CEntryExitManager::LinkEntryExit(CEntryExit* enex) { if (linkedEnEx->m_pLink) { linkedEnEx->m_pLink = enex; } + // ????? linkedEnEx->m_nTimeOn = 0; linkedEnEx->m_nTimeOff = 24; } @@ -375,6 +387,39 @@ void CEntryExitManager::SetAreaCodeForVisibleObjects() { ms_oldAreaCode = CGame::currArea; } +// NOTSA (Code somewhat based on 0x43FC00) +void CEntryExitManager::AddEnExToWorld(CEntryExit* enex) { + // Calculate rotated corner positions (We ain't gonna use a matrix for this like they did, too complicated) + const auto& r = enex->m_recEntrance; + const auto rc = r.GetCenter(); + CVector2D corners[]{ + { r.right, r.top }, + { r.left, r.bottom } + }; + + // Rotate it around the center + for (auto& corner : corners) { + corner = rc + (corner - rc).RotatedBy(enex->m_fEntranceAngleRad); // NOTE: If doesn't work properly, negate (-angle) the angle + } + + const auto GetMinMaxAxis = [&](size_t axis) { + return rng::minmax( + corners | rng::views::transform([axis](auto&& c) { return c[axis]; }) + ); + }; + + // Calculate min-max coordinates + const auto [minX, maxX] = GetMinMaxAxis(0); + const auto [minY, maxY] = GetMinMaxAxis(1); + + // Add it to the QuadTree using the calculated bounding rect + mp_QuadTree->AddItem(enex, {minX, minY, maxX, maxY}); +} + +bool CEntryExitManager::WeAreInInteriorTransition() { + return ms_exitEnterState != 0; +} + // 0x5D55C0 bool CEntryExitManager::Load() { // Load entry exit stack @@ -404,7 +449,7 @@ bool CEntryExitManager::Load() { enex->m_pLink = nullptr; } } else { - assert(0); // NOTSA - Probably corrupted save file or something. + NOTSA_UNREACHABLE(); // NOTSA - Probably corrupted save file or something. } CGenericGameStorage::LoadDataFromWorkBuffer(&enexIdx, sizeof(enexIdx)); @@ -432,33 +477,3 @@ bool CEntryExitManager::Save() { return true; } - -// NOTSA (Code somewhat based on 0x43FC00) -void CEntryExitManager::AddEnExToWorld(CEntryExit* enex) { - // Calculate corner positions (We ain't gonna use a matrix for this like they did, too complicated) - const auto& r = enex->m_recEntrance; - CVector2D corners[]{ - { r.right, r.top }, - { r.left, r.bottom } - }; - for (auto& c : corners) { - c = c.RotatedBy(enex->m_fEntranceAngleRad); // NOTE: If doesn't work properly, negate (-angle) the angle - } - - const auto GetMinMax = [&](size_t axis) { - return rng::minmax( - corners | rng::views::transform([axis](auto&& c) { return c[axis]; }) - ); - }; - - // Calculate min-max coordinates - const auto [minX, maxX] = GetMinMax(0); - const auto [minY, maxY] = GetMinMax(1); - - // Add it to the QuadTree using the calculated bounding rect - mp_QuadTree->AddItem(enex, {minX, minY, maxX, maxY}); -} - -bool CEntryExitManager::WeAreInInteriorTransition() { - return plugin::CallAndReturn(); -} diff --git a/source/game_sa/Enums/eAudioFileType.h b/source/game_sa/Enums/eAudioFileType.h index 417059aab6..b18e44cc9f 100644 --- a/source/game_sa/Enums/eAudioFileType.h +++ b/source/game_sa/Enums/eAudioFileType.h @@ -6,9 +6,9 @@ enum eAudioFileType AUDIO_FILE_TYPE_VORBIS, AUDIO_FILE_TYPE_WAV, AUDIO_FILE_TYPE_WMA, - AUDIO_FILE_TYPE_QUICKTIME, -#if 0 - AUDIO_FILE_TYPE_FLAC, // SilentPatch-specific + AUDIO_FILE_TYPE_QUICKTIME, // NOTSA: We switched it to MediaFoundation. +#ifdef USERTRACK_FLAC_SUPPORT + AUDIO_FILE_TYPE_FLAC, #endif TOTAL_AUDIO_FILE_TYPE }; diff --git a/source/game_sa/Enums/eMoveState.h b/source/game_sa/Enums/eMoveState.h new file mode 100644 index 0000000000..0b4275d976 --- /dev/null +++ b/source/game_sa/Enums/eMoveState.h @@ -0,0 +1,12 @@ +#pragma once + +enum eMoveState : uint32 { + PEDMOVE_NONE = 0, + PEDMOVE_STILL, + PEDMOVE_TURN_L, + PEDMOVE_TURN_R, + PEDMOVE_WALK, + PEDMOVE_JOG, + PEDMOVE_RUN, + PEDMOVE_SPRINT +}; diff --git a/source/game_sa/Enums/eRadioID.h b/source/game_sa/Enums/eRadioID.h index 8bd08be49f..62802a2916 100644 --- a/source/game_sa/Enums/eRadioID.h +++ b/source/game_sa/Enums/eRadioID.h @@ -7,7 +7,9 @@ #pragma once // Genre-based names -enum eRadioID : uint8 { +enum eRadioID : int8 { + RADIO_INVALID = -1, // Used in CAudioEngine code. + RADIO_EMERGENCY_AA, // AA RADIO_CLASSIC_HIP_HOP, // Playback FM RADIO_COUNTRY, // K-Rose diff --git a/source/game_sa/Enums/eWeatherType.h b/source/game_sa/Enums/eWeatherType.h index bcad834999..28f52d7d03 100644 --- a/source/game_sa/Enums/eWeatherType.h +++ b/source/game_sa/Enums/eWeatherType.h @@ -35,3 +35,16 @@ enum eWeatherType : int16 { NUM_WEATHERS, WEATHER_EXTRA_START = WEATHER_EXTRACOLOURS_1 }; + +inline bool IsExtraSunny(eWeatherType wt) { + switch (wt) { + case WEATHER_EXTRASUNNY_LA: + case WEATHER_EXTRASUNNY_SMOG_LA: + case WEATHER_EXTRASUNNY_COUNTRYSIDE: + case WEATHER_EXTRASUNNY_SF: + case WEATHER_EXTRASUNNY_VEGAS: + case WEATHER_EXTRASUNNY_DESERT: + return true; + } + return false; +} diff --git a/source/game_sa/Events/EventCreatePartnerTask.cpp b/source/game_sa/Events/EventCreatePartnerTask.cpp index 5ceebd02e6..4f97b08de7 100644 --- a/source/game_sa/Events/EventCreatePartnerTask.cpp +++ b/source/game_sa/Events/EventCreatePartnerTask.cpp @@ -3,11 +3,12 @@ #include "EventCreatePartnerTask.h" // 0x5F6190 -CEventCreatePartnerTask::CEventCreatePartnerTask(int32 randomNumber, CPed* partner, bool leadSpeaker, float distanceMultiplier) { - m_randomNumber = randomNumber; - m_leadSpeaker = leadSpeaker; - m_distanceMultiplier = distanceMultiplier; - m_partner = partner; +CEventCreatePartnerTask::CEventCreatePartnerTask(int32 type, CPed* partner, bool isLeadSpeaker, float meetDist) : + m_type{type}, + m_partner{partner}, + m_isLeadSpeaker{isLeadSpeaker}, + m_meetDist{meetDist} +{ CEntity::SafeRegisterRef(m_partner); } diff --git a/source/game_sa/Events/EventCreatePartnerTask.h b/source/game_sa/Events/EventCreatePartnerTask.h index 2f064245cf..063795b70c 100644 --- a/source/game_sa/Events/EventCreatePartnerTask.h +++ b/source/game_sa/Events/EventCreatePartnerTask.h @@ -4,19 +4,19 @@ class NOTSA_EXPORT_VTABLE CEventCreatePartnerTask : public CEvent { public: - int32 m_randomNumber; + int32 m_type; CPed* m_partner; - bool m_leadSpeaker; - float m_distanceMultiplier; + bool m_isLeadSpeaker; + float m_meetDist; public: - CEventCreatePartnerTask(int32 randomNumber, CPed* partner, bool leadSpeaker, float distanceMultiplier); + CEventCreatePartnerTask(int32 type, CPed* partner, bool isLeadSpeaker, float meetDist); ~CEventCreatePartnerTask() override; - eEventType GetEventType() const override { return EVENT_CREATE_PARTNER_TASK; } - int32 GetEventPriority() const override { return 6; } - int32 GetLifeTime() override { return 0; } - CEvent* Clone() override { return new CEventCreatePartnerTask(m_randomNumber, m_partner, m_leadSpeaker, m_distanceMultiplier); } - bool AffectsPed(CPed* ped) override { return true; } + eEventType GetEventType() const override { return EVENT_CREATE_PARTNER_TASK; } + int32 GetEventPriority() const override { return 6; } + int32 GetLifeTime() override { return 0; } + CEvent* Clone() override { return new CEventCreatePartnerTask(m_type, m_partner, m_isLeadSpeaker, m_meetDist); } + bool AffectsPed(CPed* ped) override { return true; } }; VALIDATE_SIZE(CEventCreatePartnerTask, 0x1C); diff --git a/source/game_sa/Events/EventDamage.cpp b/source/game_sa/Events/EventDamage.cpp index 5b717604ab..a96e7d901b 100644 --- a/source/game_sa/Events/EventDamage.cpp +++ b/source/game_sa/Events/EventDamage.cpp @@ -955,6 +955,14 @@ void CEventDamage::ComputeDamageAnim(CPed* ped, bool bMakeActiveTaskAbortable) { } } +void CEventDamage::ComputeAnim(CPed* ped, bool bMakeActiveTaskAbortable) { + if (ped->m_fHealth <= 0.f) { + ComputeDeathAnim(ped, bMakeActiveTaskAbortable); + } else { + ComputeDeathAnim(ped, bMakeActiveTaskAbortable); + } +} + // NOTSA void CEventDamage::ComputeDamageResponseIfAffectsPed(CPed* ped, CPedDamageResponseCalculator calculator, bool bSpeak) { if (AffectsPed(ped)) { diff --git a/source/game_sa/Events/EventDamage.h b/source/game_sa/Events/EventDamage.h index 6b0fd7ba78..6669a4f131 100644 --- a/source/game_sa/Events/EventDamage.h +++ b/source/game_sa/Events/EventDamage.h @@ -58,6 +58,7 @@ class NOTSA_EXPORT_VTABLE CEventDamage : public CEventEditableResponse { void ComputeBodyPartToRemove(int32& boneFrameId); void ComputeDeathAnim(CPed* ped, bool bMakeActiveTaskAbortable); void ComputeDamageAnim(CPed* ped, bool bMakeActiveTaskAbortable); + void ComputeAnim(CPed* ped, bool bMakeActiveTaskAbortable = true); //! Either computes the damage, or sets it as computed (Without computing it) - Very common logic in the code void ComputeDamageResponseIfAffectsPed(CPed* ped, CPedDamageResponseCalculator calculator, bool bSpeak); diff --git a/source/game_sa/Events/EventEditableResponse.cpp b/source/game_sa/Events/EventEditableResponse.cpp index f4aa73d96b..7f03e5dc95 100644 --- a/source/game_sa/Events/EventEditableResponse.cpp +++ b/source/game_sa/Events/EventEditableResponse.cpp @@ -156,67 +156,68 @@ void CEventEditableResponse::TriggerLookAt(CPed* ped) { // 0x4B56C0 void CEventEditableResponse::ComputeResponseTaskType(CPed* ped, bool bDecisionMakerTypeInGroup) { - if (m_taskId == TASK_NONE) { - int32 eventSourceType = CEventSource::ComputeEventSourceType(*this, *ped); - CDecisionMakerTypes::GetInstance()->MakeDecision( - ped, - GetEventType(), - eventSourceType, - ped->bInVehicle, - TASK_SIMPLE_INFORM_RESPECTED_FRIENDS, - TASK_SIMPLE_INFORM_GROUP, - TASK_SIMPLE_LOOK_AT_ENTITY_OR_COORD, - -1, - bDecisionMakerTypeInGroup, - m_taskId, - field_10 - ); + if (m_taskId != TASK_NONE) { + return; } + CDecisionMakerTypes::GetInstance()->MakeDecision( + ped, + GetEventType(), + CEventSource::ComputeEventSourceType(*this, *ped), + ped->bInVehicle, + TASK_SIMPLE_INFORM_RESPECTED_FRIENDS, + TASK_SIMPLE_INFORM_GROUP, + TASK_SIMPLE_LOOK_AT_ENTITY_OR_COORD, + -1, + bDecisionMakerTypeInGroup, + m_taskId, + field_10 + ); } // 0x4B57A0 void CEventEditableResponse::ComputeResponseTaskType(CPedGroup* pedGroup) { - if (m_taskId == TASK_NONE) { - CPed* groupLeader = pedGroup->GetMembership().GetLeader(); - CPed* member = groupLeader; - if (groupLeader && groupLeader->IsPlayer()) - member = nullptr; - if (!member) { - for (size_t memberId = 0; memberId < TOTAL_PED_GROUP_FOLLOWERS; memberId++) { - member = pedGroup->GetMembership().GetMember(memberId); - if (member) - break; - } + if (m_taskId != TASK_NONE) { + return; + } + CPed* groupLeader = pedGroup->GetMembership().GetLeader(); + CPed* member = groupLeader; + if (groupLeader && groupLeader->IsPlayer()) + member = nullptr; + if (!member) { + for (size_t memberId = 0; memberId < TOTAL_PED_GROUP_FOLLOWERS; memberId++) { + member = pedGroup->GetMembership().GetMember(memberId); + if (member) + break; } - if (member) { - int32 eventSourceType = CEventSource::ComputeEventSourceType(*this, *member); + } + if (member) { + int32 eventSourceType = CEventSource::ComputeEventSourceType(*this, *member); + m_taskId = CDecisionMakerTypes::GetInstance()->MakeDecision( + pedGroup, + GetEventType(), + eventSourceType, + member->bInVehicle, + TASK_SIMPLE_INFORM_GROUP, + TASK_SIMPLE_INFORM_RESPECTED_FRIENDS, + TASK_SIMPLE_LOOK_AT_ENTITY_OR_COORD, + -1 + ); + } else { + m_taskId = TASK_NONE; + } + groupLeader = pedGroup->GetMembership().GetLeader(); + if (m_taskId == TASK_NONE && groupLeader) { + if (groupLeader->IsPlayer()) { + int32 eventSourceType = CEventSource::ComputeEventSourceType(*this, *groupLeader); m_taskId = CDecisionMakerTypes::GetInstance()->MakeDecision( pedGroup, GetEventType(), - eventSourceType, - member->bInVehicle, + eventSourceType, groupLeader->bInVehicle, TASK_SIMPLE_INFORM_GROUP, TASK_SIMPLE_INFORM_RESPECTED_FRIENDS, TASK_SIMPLE_LOOK_AT_ENTITY_OR_COORD, -1 ); - } else { - m_taskId = TASK_NONE; - } - groupLeader = pedGroup->GetMembership().GetLeader(); - if (m_taskId == TASK_NONE && groupLeader) { - if (groupLeader->IsPlayer()) { - int32 eventSourceType = CEventSource::ComputeEventSourceType(*this, *groupLeader); - m_taskId = CDecisionMakerTypes::GetInstance()->MakeDecision( - pedGroup, - GetEventType(), - eventSourceType, groupLeader->bInVehicle, - TASK_SIMPLE_INFORM_GROUP, - TASK_SIMPLE_INFORM_RESPECTED_FRIENDS, - TASK_SIMPLE_LOOK_AT_ENTITY_OR_COORD, - -1 - ); - } } } } diff --git a/source/game_sa/Events/EventGroupEvent.h b/source/game_sa/Events/EventGroupEvent.h index 346a25599d..ae75663110 100644 --- a/source/game_sa/Events/EventGroupEvent.h +++ b/source/game_sa/Events/EventGroupEvent.h @@ -25,6 +25,8 @@ class NOTSA_EXPORT_VTABLE CEventGroupEvent : public CEvent { CEvent* Clone_Reversed(); bool BaseEventTakesPriorityOverBaseEvent(const CEventGroupEvent& other); + auto& GetEvent() const { return *m_event; } + private: bool IsPriorityEvent() const; }; diff --git a/source/game_sa/Events/EventGunShot.cpp b/source/game_sa/Events/EventGunShot.cpp index 728263d925..70a0d6625c 100644 --- a/source/game_sa/Events/EventGunShot.cpp +++ b/source/game_sa/Events/EventGunShot.cpp @@ -25,21 +25,21 @@ CEventEditableResponse* CEventGunShot::CloneEditable() { return CEventGunShot::C CEventGunShot::CEventGunShot(CEntity* entity, CVector startPoint, CVector endPoint, bool bHasNoSound) : CEventEditableResponse() { m_startPoint = startPoint; m_endPoint = endPoint; - m_entity = entity; + m_firedBy = entity; m_bHasNoSound = bHasNoSound; - CEntity::SafeRegisterRef(m_entity); + CEntity::SafeRegisterRef(m_firedBy); } CEventGunShot::~CEventGunShot() { - CEntity::SafeCleanUpRef(m_entity); + CEntity::SafeCleanUpRef(m_firedBy); } // 0x4B2CD0 bool CEventGunShot::AffectsPed_Reversed(CPed* ped) { - if (!m_entity) + if (!m_firedBy) return false; - if (m_entity->IsPed() && CPedGroups::AreInSameGroup(ped, m_entity->AsPed())) + if (m_firedBy->IsPed() && CPedGroups::AreInSameGroup(ped, m_firedBy->AsPed())) return false; if (!ped->IsInVehicleThatHasADriver()) { @@ -47,11 +47,11 @@ bool CEventGunShot::AffectsPed_Reversed(CPed* ped) { if (ped->m_nPedType == PED_TYPE_COP && playerWanted->m_nWantedLevel > 0) { CCopPed* cop = static_cast(ped); if (playerWanted->IsInPursuit(cop) || playerWanted->CanCopJoinPursuit(cop)) { - if (m_entity != FindPlayerPed()) + if (m_firedBy != FindPlayerPed()) return false; } } - if (ped->IsAlive() && ped != m_entity && !ped->IsPlayer()) { + if (ped->IsAlive() && ped != m_firedBy && !ped->IsPlayer()) { float fGunShotRange = 45.0f; if (CEventGunShot::ms_fGunShotSenseRangeForRiot2 <= 0.0f) { if (ped->IsCreatedByMission()) { @@ -62,7 +62,7 @@ bool CEventGunShot::AffectsPed_Reversed(CPed* ped) { } const auto& pedPos = ped->GetPosition(); - if (DistanceBetweenPointsSquared(m_entity->GetPosition(), pedPos) <= sq(fGunShotRange)) { + if (DistanceBetweenPointsSquared(m_firedBy->GetPosition(), pedPos) <= sq(fGunShotRange)) { if (!m_bHasNoSound) return true; @@ -78,7 +78,7 @@ bool CEventGunShot::AffectsPed_Reversed(CPed* ped) { // 0x4AC810 bool CEventGunShot::IsCriminalEvent_Reversed() { - return m_entity && m_entity->IsPed() && m_entity->AsPed()->IsPlayer(); + return m_firedBy && m_firedBy->IsPed() && m_firedBy->AsPed()->IsPlayer(); } // 0x4AC780 @@ -87,11 +87,11 @@ bool CEventGunShot::TakesPriorityOver_Reversed(const CEvent& refEvent) { bool bIsPlayer = false; bool otherPedIsPlayer = false; const auto refEventGunShot = static_cast(&refEvent); - if (m_entity && m_entity->AsPed()->IsPed()) { - bIsPlayer = m_entity->AsPed()->IsPlayer(); + if (m_firedBy && m_firedBy->AsPed()->IsPed()) { + bIsPlayer = m_firedBy->AsPed()->IsPlayer(); } - CPed* otherPed = refEventGunShot->m_entity->AsPed(); + CPed* otherPed = refEventGunShot->m_firedBy->AsPed(); if (otherPed && otherPed->IsPed()) otherPedIsPlayer = otherPed->IsPlayer(); @@ -102,5 +102,5 @@ bool CEventGunShot::TakesPriorityOver_Reversed(const CEvent& refEvent) { // 0x4B6B20 CEventEditableResponse* CEventGunShot::CloneEditable_Reversed() { - return new CEventGunShot(m_entity, m_startPoint, m_endPoint, m_bHasNoSound); + return new CEventGunShot(m_firedBy, m_startPoint, m_endPoint, m_bHasNoSound); } diff --git a/source/game_sa/Events/EventGunShot.h b/source/game_sa/Events/EventGunShot.h index e08499d201..0260cfaed7 100644 --- a/source/game_sa/Events/EventGunShot.h +++ b/source/game_sa/Events/EventGunShot.h @@ -5,7 +5,7 @@ class NOTSA_EXPORT_VTABLE CEventGunShot : public CEventEditableResponse { public: - CEntity* m_entity; + CEntity* m_firedBy; CVector m_startPoint; CVector m_endPoint; bool m_bHasNoSound; @@ -22,7 +22,7 @@ class NOTSA_EXPORT_VTABLE CEventGunShot : public CEventEditableResponse { bool AffectsPed(CPed* ped) override; bool IsCriminalEvent() override; void ReportCriminalEvent(CPed* ped) override { } // empty - CEntity* GetSourceEntity() const override { return m_entity; } + CEntity* GetSourceEntity() const override { return m_firedBy; } bool TakesPriorityOver(const CEvent& refEvent) override; float GetLocalSoundLevel() override { return m_bHasNoSound ? 0.0f : 160.0f; } bool CanBeInterruptedBySameEvent() override { return true; } diff --git a/source/game_sa/Events/EventGunShotWhizzedBy.h b/source/game_sa/Events/EventGunShotWhizzedBy.h index ab8d596442..6bad206350 100644 --- a/source/game_sa/Events/EventGunShotWhizzedBy.h +++ b/source/game_sa/Events/EventGunShotWhizzedBy.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CEventGunShotWhizzedBy : public CEventGunShot { int32 GetLifeTime() override { return 0; } bool AffectsPed(CPed* ped) override; bool CanBeInterruptedBySameEvent() override { return true; } - CEventEditableResponse* CloneEditable() override { return new CEventGunShotWhizzedBy(m_entity, m_startPoint, m_endPoint, m_bHasNoSound); } + CEventEditableResponse* CloneEditable() override { return new CEventGunShotWhizzedBy(m_firedBy, m_startPoint, m_endPoint, m_bHasNoSound); } private: CEventGunShotWhizzedBy* Constructor(CEntity* entity, const CVector& startPoint, const CVector& endPoint, bool bHasNoSound); diff --git a/source/game_sa/Events/EventHandler.cpp b/source/game_sa/Events/EventHandler.cpp index adba633386..d4d20d745f 100644 --- a/source/game_sa/Events/EventHandler.cpp +++ b/source/game_sa/Events/EventHandler.cpp @@ -92,7 +92,7 @@ void CEventHandler::InjectHooks() { RH_ScopedInstall(ComputeWaterCannonResponse, 0x4BAE30, { .reversed = false }); // RH_ScopedOverloadedInstall(ComputeEventResponseTask, "0", 0x4C3870, void (CEventHandler::*)(CEvent*, CTask*)); - // RH_ScopedOverloadedInstall(ComputeEventResponseTask, "1", 0x4C4220, CTask* (CEventHandler::*)(CPed*, CEvent*)); + RH_ScopedOverloadedInstall(ComputeEventResponseTask, "Ped", 0x4C4220, CTask*(*)(const CPed&, const CEvent&)); } // 0x4C3E80 @@ -784,24 +784,20 @@ void CEventHandler::ComputeEventResponseTask(CEvent* event, CTask* task) { } } -// should be (const CPed& ped, const CEvent& event); // 0x4C4220 -CTask* CEventHandler::ComputeEventResponseTask(CPed* ped, CEvent* event) { - return plugin::CallAndReturn(ped, event); - /* +CTask* CEventHandler::ComputeEventResponseTask(const CPed& ped, const CEvent& e) { CTask* task = nullptr; - CEventHandler handler(ped); - handler.ComputeEventResponseTask(event, nullptr); - if (handler.m_eventResponseTask) { - task = handler.m_eventResponseTask; - handler.m_eventResponseTask = nullptr; + CEventHandler eh{const_cast(&ped)}; + eh.ComputeEventResponseTask(const_cast(&e), nullptr); + if (eh.m_eventResponseTask) { + task = eh.m_eventResponseTask; + eh.m_eventResponseTask = nullptr; } - handler.Flush(); - delete handler.m_history.m_tempEvent; - delete handler.m_history.m_nonTempEvent; - delete handler.m_history.m_storedActiveEvent; + eh.Flush(); + delete eh.m_history.m_tempEvent; + delete eh.m_history.m_nonTempEvent; + delete eh.m_history.m_storedActiveEvent; return task; - */ } diff --git a/source/game_sa/Events/EventHandler.h b/source/game_sa/Events/EventHandler.h index fabb9e11b9..07e24df2d2 100644 --- a/source/game_sa/Events/EventHandler.h +++ b/source/game_sa/Events/EventHandler.h @@ -106,7 +106,7 @@ class CEventHandler { void ComputeWaterCannonResponse(CEvent* event, CTask* task1, CTask* task2); void ComputeEventResponseTask(CEvent* event, CTask* task); - CTask* ComputeEventResponseTask(CPed* ped, CEvent* event); + static CTask* ComputeEventResponseTask(const CPed& ped, const CEvent& e); auto& GetHistory() { return m_history; } }; diff --git a/source/game_sa/Events/GroupEventHandler.cpp b/source/game_sa/Events/GroupEventHandler.cpp index caa2f2bdf2..1becb65db1 100644 --- a/source/game_sa/Events/GroupEventHandler.cpp +++ b/source/game_sa/Events/GroupEventHandler.cpp @@ -1,262 +1,782 @@ #include "StdInc.h" #include "GroupEventHandler.h" +#include "EventHandler.h" +#include "IKChainManager_c.h" + +#include "Tasks/TaskTypes/TaskComplexStareAtPed.h" +#include "Tasks/TaskTypes/TaskSimpleNone.h" +#include "Tasks/TaskTypes/TaskComplexLeaveAnyCar.h" +#include "Tasks/TaskTypes/TaskComplexGangJoinRespond.h" +#include "Tasks/TaskTypes/TaskSimpleGoToPoint.h" +#include "Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.h" +#include "Tasks/TaskTypes/TaskComplexEnterCarAsPassengerWait.h" +#include "Tasks/TaskTypes/TaskComplexLeaveCar.h" +#include "Tasks/TaskTypes/TaskComplexLeaveCarAsPassengerWait.h" +#include "Tasks/TaskTypes/TaskComplexSmartFleeEntity.h" +#include "Tasks/TaskTypes/TaskSimpleWaitUntilLeaderAreaCodesMatch.h" +#include "Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntity.h" +#include "Tasks/TaskTypes/SeekEntity/PosCalculators/EntitySeekPosCalculatorStandard.h" +#include "Tasks/TaskTypes/TaskGoToVehicleAndLean.h" +#include "Tasks/TaskTypes/TaskComplexKillPedOnFoot.h" +#include "Tasks/TaskTypes/TaskGangHassleVehicle.h" +#include "Tasks/TaskTypes/TaskGangHasslePed.h" +#include "Tasks/TaskTypes/TaskComplexSignalAtPed.h" +#include "Tasks/TaskTypes/TaskComplexPartnerGreet.h" +#include "Tasks/TaskTypes/TaskComplexPartnerDeal.h" + +#include "Tasks/Allocators/TaskAllocator.h" +#include "Tasks/Allocators/TaskAllocatorPlayerCommandAttack.h" +#include "Tasks/Allocators/TaskAllocatorKillThreatsBasic.h" +#include "Tasks/Allocators/TaskAllocatorKillThreatsBasicRandomGroup.h" +#include "Tasks/Allocators/TaskAllocatorKillThreatsDriveby.h" + +#include "Events/EventVehicleDamage.h" +#include "Events/EventGunShot.h" +#include "Events/EventSexyPed.h" +#include "Events/EventAcquaintancePed.h" +#include "Events/GroupEvents.h" +#include "Events/LeaderEvents.h" +#include "Events/EventGunAimedAt.h" +#include "Events/EventDraggedOutCar.h" +#include "Events/EventDanger.h" +#include "Events/EventDamage.h" +#include "Events/EventLeanOnVehicle.h" void CGroupEventHandler::InjectHooks() { RH_ScopedClass(CGroupEventHandler); RH_ScopedCategory("Events"); - RH_ScopedInstall(IsKillTaskAppropriate, 0x5F7A60, { .reversed = false }); - RH_ScopedInstall(ComputeWalkAlongsideResponse, 0x5FA910, { .reversed = false }); - RH_ScopedInstall(ComputeStareResponse, 0x5F9BD0, { .reversed = false }); - RH_ScopedInstall(ComputeResponseVehicleDamage, 0x5FC070, { .reversed = false }); - RH_ScopedInstall(ComputeResponseShotFired, 0x5FBDF0, { .reversed = false }); - RH_ScopedInstall(ComputeResponseSexyPed, 0x5FB390, { .reversed = false }); - RH_ScopedInstall(ComputeResponseSeenCop, 0x5FBCB0, { .reversed = false }); - RH_ScopedInstall(ComputeResponsePlayerCommand, 0x5FB470, { .reversed = false }); - RH_ScopedInstall(ComputeResponsePedThreat, 0x5FBB90, { .reversed = false }); - RH_ScopedInstall(ComputeResponsePedFriend, 0x5FB2D0, { .reversed = false }); - RH_ScopedInstall(ComputeResponseNewGangMember, 0x5F9840, { .reversed = false }); - RH_ScopedInstall(ComputeResponseLeaderExitedCar, 0x5F90A0, { .reversed = false }); + RH_ScopedInstall(IsKillTaskAppropriate, 0x5F7A60); + RH_ScopedInstall(ComputeWalkAlongsideResponse, 0x5FA910); + RH_ScopedInstall(ComputeStareResponse, 0x5F9BD0); + RH_ScopedInstall(ComputeResponseVehicleDamage, 0x5FC070); + RH_ScopedInstall(ComputeResponseShotFired, 0x5FBDF0); + RH_ScopedInstall(ComputeResponseSexyPed, 0x5FB390); + RH_ScopedInstall(ComputeResponseSeenCop, 0x5FBCB0); + RH_ScopedInstall(ComputeResponsePlayerCommand, 0x5FB470); + RH_ScopedInstall(ComputeResponsePedThreat, 0x5FBB90); + RH_ScopedInstall(ComputeResponsePedFriend, 0x5FB2D0); + RH_ScopedInstall(ComputeResponseNewGangMember, 0x5F9840); + RH_ScopedInstall(ComputeResponseLeaderExitedCar, 0x5F90A0); RH_ScopedInstall(ComputeResponsLeaderQuitEnteringCar, 0x5F9530, { .reversed = false }); RH_ScopedInstall(ComputeResponseLeaderEnteredCar, 0x5F8900, { .reversed = false }); - RH_ScopedInstall(ComputeResponseLeaderEnterExit, 0x5F9710, { .reversed = false }); - RH_ScopedInstall(ComputeResponseGunAimedAt, 0x5FBD10, { .reversed = false }); - RH_ScopedInstall(ComputeResponseGather, 0x5F99F0, { .reversed = false }); - RH_ScopedInstall(ComputeResponseDraggedOutCar, 0x5FBE70, { .reversed = false }); - RH_ScopedInstall(ComputeResponseDanger, 0x5FB540, { .reversed = false }); - RH_ScopedInstall(ComputeResponseDamage, 0x5FBF50, { .reversed = false }); - RH_ScopedInstall(ComputeMemberResponses, 0x5FAA50, { .reversed = false }); - RH_ScopedInstall(ComputeLeanOnVehicleResponse, 0x5F9B20, { .reversed = false }); - RH_ScopedInstall(ComputeKillThreatsBasicResponse, 0x5FB590, { .reversed = false }); - RH_ScopedInstall(ComputeKillPlayerBasicResponse, 0x5FB670, { .reversed = false }); - RH_ScopedInstall(ComputeHassleThreatResponse, 0x5F9D50, { .reversed = false }); - RH_ScopedInstall(ComputeHassleSexyPedResponse, 0x5FA020, { .reversed = false }); - RH_ScopedInstall(ComputeHandSignalResponse, 0x5FA820, { .reversed = false }); - RH_ScopedInstall(ComputeGreetResponse, 0x5FA550, { .reversed = false }); - RH_ScopedInstall(ComputeFleePedResponse, 0x5FA130, { .reversed = false }); + RH_ScopedInstall(ComputeResponseLeaderEnterExit, 0x5F9710); + RH_ScopedInstall(ComputeResponseGunAimedAt, 0x5FBD10); + RH_ScopedInstall(ComputeResponseGather, 0x5F99F0); + RH_ScopedInstall(ComputeResponseDraggedOutCar, 0x5FBE70); + RH_ScopedInstall(ComputeResponseDanger, 0x5FB540); + RH_ScopedInstall(ComputeResponseDamage, 0x5FBF50); + RH_ScopedInstall(ComputeMemberResponses, 0x5FAA50); + RH_ScopedInstall(ComputeLeanOnVehicleResponse, 0x5F9B20); + RH_ScopedInstall(ComputeKillThreatsBasicResponse, 0x5FB590); + RH_ScopedInstall(ComputeKillPlayerBasicResponse, 0x5FB670); + RH_ScopedInstall(ComputeHassleThreatResponse, 0x5F9D50); + RH_ScopedInstall(ComputeHassleSexyPedResponse, 0x5FA020); + RH_ScopedInstall(ComputeHandSignalResponse, 0x5FA820); + RH_ScopedInstall(ComputeGreetResponse, 0x5FA550); + RH_ScopedInstall(ComputeFleePedResponse, 0x5FA130); RH_ScopedInstall(ComputeEventResponseTasks, 0x5FC200); - RH_ScopedInstall(ComputeDrivebyResponse, 0x5F7A00, { .reversed = false }); - RH_ScopedInstall(ComputeDoDealResponse, 0x5FA290, { .reversed = false }); + RH_ScopedInstall(ComputeDrivebyResponse, 0x5F7A00); + RH_ScopedInstall(ComputeDoDealResponse, 0x5FA290); +} + +bool IsPedInPlayersGroup(CPedGroup* pg, CPed* p) { + const auto playerGrp = &FindPlayerPed(-1)->GetPlayerGroup(); + if (pg == playerGrp) { + return true; + } + if (playerGrp->GetMembership().IsMember(p)) { + return true; + } + return false; +} + +void MaybeAdjustTaskOfGroupThreatEvent(const CEventEditableResponse& e, CPedGroup* pg, CPed* originator, CPed* srcPed) { + if (pg->m_bIsMissionGroup && srcPed->IsPlayer()) { + if (const auto l = pg->GetMembership().GetLeader()) { + if (!l->GetActiveWeapon().IsTypeMelee() && !l->GetIntelligence()->IsFriendlyWith(*originator)) { + const_cast(&e)->m_taskId = TASK_GROUP_KILL_THREATS_BASIC; // nice R* + } + } + } } // 0x5F7A60 -void CGroupEventHandler::IsKillTaskAppropriate(CPedGroup* group, CPed* ped) { - plugin::Call<0x5F7A60, CPedGroup*, CPed*>(group, ped); +bool CGroupEventHandler::IsKillTaskAppropriate(CPedGroup* g, CPed* threat) { + if (g->m_bIsMissionGroup || threat->GetActiveWeapon().IsTypeMelee()) { + return true; + } + for (auto& m : g->GetMembership().GetMembers()) { + if (!m.GetActiveWeapon().IsTypeMelee()) { + return true; + } + } + return false; } // 0x5FA910 -void CGroupEventHandler::ComputeWalkAlongsideResponse(CPedGroup* group, CPed* ped1, CPed* ped2) { - plugin::Call<0x5FA910, CPedGroup*, CPed*, CPed*>(group, ped1, ped2); +CTaskAllocator* CGroupEventHandler::ComputeWalkAlongsideResponse(CPedGroup* pg, CPed* ped1, CPed* ped2) { + NOTSA_UNREACHABLE(); // Unused } // 0x5F9BD0 -void CGroupEventHandler::ComputeStareResponse(CPedGroup* group, CPed* ped1, CPed* ped2, int32 a4, int32 a5) { - plugin::Call<0x5F9BD0, CPedGroup*, CPed*, CPed*, int32, int32>(group, ped1, ped2, a4, a5); +CTaskAllocator* CGroupEventHandler::ComputeStareResponse(CPedGroup* pg, CPed* stareAt, CPed* originatorPed, int32 timeout, int32 timeoutBias) { + if (!stareAt) { + return nullptr; + } + if (!pg->GetMembership().GetLeader()) { + return nullptr; + } + if ((stareAt->GetPosition() - pg->GetMembership().GetLeader()->GetPosition()).SquaredMagnitude2D() >= sq(8.f)) { + return nullptr; + } + for (auto& m : pg->GetMembership().GetMembers()) { + pg->GetIntelligence().SetEventResponseTask( + &m, + CTaskComplexStareAtPed{ + pg, + stareAt, + timeout + CGeneral::GetRandomNumberInRange(-timeoutBias, timeoutBias) + } + ); + } + return nullptr; } // 0x5FC070 -void CGroupEventHandler::ComputeResponseVehicleDamage(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FC070, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseVehicleDamage(const CEventVehicleDamage& e, CPedGroup* pg, CPed* originator) { + if (!e.m_attacker || !e.m_attacker->IsPed()) { + return nullptr; + } + const auto threat = e.m_attacker->AsPed(); + switch (e.m_taskId) { + case TASK_GROUP_KILL_THREATS_BASIC: return ComputeKillThreatsBasicResponse(pg, threat, originator, true); + case TASK_GROUP_KILL_PLAYER_BASIC: return ComputeKillPlayerBasicResponse(pg, threat, originator, true); + case TASK_GROUP_FLEE_THREAT: return ComputeFleePedResponse(pg, threat, originator, true); + case TASK_GROUP_USE_MEMBER_DECISION: return ComputeMemberResponses(e, pg, originator); + case TASK_GROUP_DRIVEBY: return ComputeDrivebyResponse(pg, threat, originator); + } + return nullptr; } // 0x5FBDF0 -void CGroupEventHandler::ComputeResponseShotFired(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FBDF0, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseShotFired(const CEventGunShot& e, CPedGroup* pg, CPed* originator) { + if (!e.m_firedBy || !e.m_firedBy->IsPed()) { + return nullptr; + } + const auto threat = e.m_firedBy->AsPed(); + switch (e.m_taskId) { + case TASK_GROUP_KILL_THREATS_BASIC: return ComputeKillThreatsBasicResponse(pg, threat, originator, false); + case TASK_GROUP_FLEE_THREAT: return ComputeFleePedResponse(pg, threat, originator, false); + case TASK_GROUP_USE_MEMBER_DECISION: return ComputeMemberResponses(e, pg, originator); + } + return nullptr; } // 0x5FB390 -void CGroupEventHandler::ComputeResponseSexyPed(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FB390, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseSexyPed(const CEventSexyPed& e, CPedGroup* pg, CPed* originator) { + if (!e.m_SexyPed) { + return nullptr; + } + if (pg->GetMembership().IsMember(e.m_SexyPed)) { + return nullptr; + } + if (IsPedInPlayersGroup(pg, e.m_SexyPed)) { + return nullptr; + } + switch (e.m_taskId) { + case TASK_GROUP_STARE_AT_PED: return ComputeStareResponse(pg, e.m_SexyPed, originator, CGeneral::GetRandomNumberInRange(3000, 5000), 1000); + case TASK_GROUP_HASSLE_SEXY_PED: return ComputeHassleSexyPedResponse(pg, e.m_SexyPed, originator); + } + return nullptr; } // 0x5FBCB0 -void CGroupEventHandler::ComputeResponseSeenCop(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FBCB0, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseSeenCop(const CEventSeenCop& e, CPedGroup* pg, CPed* originator) { + switch (e.m_taskId) { + case TASK_GROUP_KILL_THREATS_BASIC: return ComputeKillThreatsBasicResponse(pg, e.m_ped, originator, false); + case TASK_GROUP_FLEE_THREAT: return ComputeFleePedResponse(pg, e.m_ped, originator, false); + case TASK_GROUP_HAND_SIGNAL: return ComputeHandSignalResponse(pg, e.m_ped, originator); + } + return nullptr; } // 0x5FB470 -void CGroupEventHandler::ComputeResponsePlayerCommand(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FB470, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponsePlayerCommand(const CEventPlayerCommandToGroup& e, CPedGroup* pg, CPed* originator) { + switch (e.m_command) { + case ePlayerGroupCommand::PLAYER_GROUP_COMMAND_GATHER: + return ComputeResponseGather(static_cast(e), pg, originator); + case ePlayerGroupCommand::PLAYER_GROUP_COMMAND_ATTACK: + return new CTaskAllocatorPlayerCommandAttack{ + e.m_target, + e.m_target->GetGroupId(), + e.m_target->m_nPedType + }; + } + return nullptr; } // 0x5FBB90 -void CGroupEventHandler::ComputeResponsePedThreat(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FBB90, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponsePedThreat(const CEventAcquaintancePed& e, CPedGroup* pg, CPed* originator) { + if (!e.m_ped) { + return nullptr; + } + if (pg->GetMembership().IsMember(e.m_ped)) { + return nullptr; + } + switch (e.m_taskId) { + case TASK_GROUP_KILL_THREATS_BASIC: return ComputeKillThreatsBasicResponse(pg, e.m_ped, originator, false); + case TASK_GROUP_STARE_AT_PED: return ComputeStareResponse(pg, e.m_ped, originator, 99'999'999, false); // 1.15740739583 days + case TASK_GROUP_FLEE_THREAT: return ComputeFleePedResponse(pg, e.m_ped, originator, false); + case TASK_GROUP_HASSLE_THREAT: return ComputeHassleThreatResponse(pg, e.m_ped, originator, true); + case TASK_GROUP_USE_MEMBER_DECISION: return ComputeMemberResponses(e, pg, originator); + case TASK_GROUP_DRIVEBY: return ComputeDrivebyResponse(pg, e.m_ped, originator); + case TASK_GROUP_HASSLE_THREAT_PASSIVE: return ComputeHassleThreatResponse(pg, e.m_ped, originator, false); + } + return nullptr; } // 0x5FB2D0 -void CGroupEventHandler::ComputeResponsePedFriend(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FB2D0, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponsePedFriend(const CEventAcquaintancePed& e, CPedGroup* pg, CPed* originator) { + if (!e.m_ped) { + return nullptr; + } + if (IsPedInPlayersGroup(pg, e.m_ped)) { + return nullptr; + } + switch (e.m_taskId) { + case TASK_GROUP_PARTNER_DEAL: return ComputeDoDealResponse(pg, e.m_ped, originator); + case TASK_GROUP_PARTNER_GREET: return ComputeGreetResponse(pg, e.m_ped, originator); + } + return nullptr; } // 0x5F9840 -void CGroupEventHandler::ComputeResponseNewGangMember(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5F9840, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseNewGangMember(const CEventNewGangMember& e, CPedGroup* pg, CPed* originator) { + if (!e.m_member || pg->GetMembership().IsMember(e.m_member)) { + return nullptr; + } + CTaskComplexSequence tseq{ + new CTaskComplexLeaveAnyCar{0, false, true}, + new CTaskComplexGangJoinRespond{true} + }; + if (pg->GetMembership().GetLeader() == FindPlayerPed()) { + tseq.AddTask(new CTaskSimpleGoToPoint{ + eMoveState::PEDMOVE_RUN, + FindPlayerPed()->GetPosition(), + 3.f, + true + }); + } + pg->GetIntelligence().SetEventResponseTask(e.m_member, tseq); + return nullptr; } // 0x5F90A0 -void CGroupEventHandler::ComputeResponseLeaderExitedCar(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5F90A0, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseLeaderExitedCar(const CEventEditableResponse&, CPedGroup* pg, CPed* originator) { + const auto leader = pg->GetMembership().GetLeader(); + for (auto&& [i, m] : notsa::enumerate(pg->GetMembership().GetFollowers())) { + if (m.m_pVehicle && m.bInVehicle && m.m_pVehicle == leader->m_pVehicle) { // Already in the leader's vehicle + continue; + } + CVehicle* mveh{}; + if (const auto t = m.GetTaskManager().Find(false); !t || !(mveh = t->GetTarget())) { + if (const auto t = m.GetTaskManager().Find(false); !t || !(mveh = t->GetTarget())) { + continue; + } + } + const auto SetTask = [&](const auto& task) { + pg->GetIntelligence().SetEventResponseTask(&m, task); + }; + const auto isVehOnFire = mveh->m_pFireParticle && mveh->m_pFireParticle->GetPlayStatus() == eFxSystemPlayStatus::FX_PLAYING; + if (notsa::contains({ 15, 16 }, mveh->m_pHandlingData->m_nAnimGroup)) { // TODO: Enums + if (isVehOnFire) { // INVERTED - 0x5F9483 + SetTask(CTaskComplexSequence{ + new CTaskComplexLeaveCarAsPassengerWait{mveh}, + new CTaskComplexSmartFleeEntity{ + mveh, + false, + 15.f, + 1'000'000, + 1000, + fEntityPosChangeThreshold + } + }); + } else { // 0x5F937C + SetTask(CTaskComplexLeaveCarAsPassengerWait{ + mveh + }); + } + } else { + // NOTSA/NOTE: + // To make this behave the same as the OG code we'd have to either: + // - Use `pg->GetMembership() * 500` + // - Use an `for i` loop [to iterate the array] + // But I'm way too fancy to do that, so... + if (isVehOnFire) { // INVERTED + SetTask(CTaskComplexSequence{ // 0x5F9193 + new CTaskComplexLeaveCar{ + mveh, + eTargetDoor::TARGET_DOOR_FRONT_LEFT, + CGeneral::GetRandomNumberInRange(-250, 250) + (i + 1) * 500, // SEE NOTE + false, + false + }, + new CTaskComplexSmartFleeEntity{ + mveh, + false, + 15.f, + 1'000'000, + 1'000, + fEntityPosChangeThreshold + } + }); + } else { // 0x5F92E7 + SetTask(CTaskComplexLeaveCar{ + mveh, + eTargetDoor::TARGET_DOOR_FRONT_LEFT, + CGeneral::GetRandomNumberInRange(-250, 250) + i * 500, // SEE NOTE + false, + false + }); + } + } + } + return nullptr; } // 0x5F8900 -void CGroupEventHandler::ComputeResponseLeaderEnteredCar(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5F8900, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseLeaderEnteredCar(const CEvent& e, CPedGroup* pg, CPed* originator) { + return plugin::CallAndReturn(e, pg, originator); } // 0x5F9710 -void CGroupEventHandler::ComputeResponseLeaderEnterExit(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5F9710, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseLeaderEnterExit(const CEventLeaderEntryExit& e, CPedGroup* pg, CPed* originator) { + const auto pgms = &pg->GetMembership(); + const auto leader = pgms->GetLeader(); + if (!leader) { + return nullptr; + } + for (auto& m : pgms->GetFollowers()) { + if (m.bInVehicle) { + continue; + } + CTaskSimpleWaitUntilLeaderAreaCodesMatch task{leader}; + if (task.ProcessPed(&m)) { + m.m_bUsesCollision = true; + } else { + pg->GetIntelligence().SetEventResponseTask(&m, task); + } + } + return nullptr; } // 0x5FBD10 -void CGroupEventHandler::ComputeResponseGunAimedAt(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FBD10, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseGunAimedAt(const CEventGunAimedAt& e, CPedGroup* pg, CPed* originator) { + const auto src = e.GetSourceEntity(); + if (!src || !src->IsPed()) { + return nullptr; + } + const auto srcPed = src->AsPed(); + MaybeAdjustTaskOfGroupThreatEvent(e, pg, originator, srcPed); + switch (e.m_taskId) { + case TASK_GROUP_KILL_THREATS_BASIC: return ComputeKillThreatsBasicResponse(pg, srcPed, originator, false); + case TASK_GROUP_FLEE_THREAT: return ComputeFleePedResponse(pg, srcPed, originator, false); + case TASK_GROUP_USE_MEMBER_DECISION: return ComputeMemberResponses(e, pg, originator); + } + return nullptr; } // 0x5F99F0 -void CGroupEventHandler::ComputeResponseGather(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5F99F0, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseGather(const CEventPlayerCommandToGroupGather& e, CPedGroup* pg, CPed* originator) { + for (auto& m : pg->GetMembership().GetFollowers()) { + pg->GetIntelligence().SetEventResponseTask( + &m, + CTaskComplexSeekEntity{ + originator, + 50'000, + 1'000, + 7.5f, + 2.f, + 2.f, + true, + true, + CEntitySeekPosCalculatorStandard{} + } + ); + } + return nullptr; } // 0x5FBE70 -void CGroupEventHandler::ComputeResponseDraggedOutCar(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FBE70, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseDraggedOutCar(const CEventDraggedOutCar& e, CPedGroup* pg, CPed* originator) { + if (!e.m_CarJacker) { + return nullptr; + } + assert(!e.m_CarJacker->IsPed()); // Original code just `returns nullptr` in this case, but but since `m_CarJacker` is typed as `CPed*` it *should* be at least a `CPed*` + switch (e.m_taskId) { + case TASK_GROUP_KILL_THREATS_BASIC: + return e.m_CarJacker->IsPlayer() && originator && originator->GetIntelligence()->Respects(e.m_CarJacker) && !pg->m_bIsMissionGroup + ? ComputeFleePedResponse(pg, e.m_CarJacker, originator, false) + : ComputeKillThreatsBasicResponse(pg, e.m_CarJacker, originator, false); + case TASK_GROUP_FLEE_THREAT: return ComputeFleePedResponse(pg, e.m_CarJacker, originator, false); + case TASK_GROUP_USE_MEMBER_DECISION: return ComputeMemberResponses(e, pg, originator); + case TASK_GROUP_EXIT_CAR: return ComputeResponseLeaderExitedCar(static_cast(e), pg, originator); + } + return nullptr; } // 0x5FB540 -void CGroupEventHandler::ComputeResponseDanger(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FB540, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseDanger(const CEventDanger& e, CPedGroup* pg, CPed* originator) { + const auto esrc = e.GetSourceEntity(); + if (!esrc || !esrc->IsPed()) { + return nullptr; + } + switch (e.m_taskId) { + case TASK_GROUP_FLEE_THREAT: return ComputeFleePedResponse(pg, esrc->AsPed(), originator, false); + } + return nullptr; } // 0x5FBF50 -void CGroupEventHandler::ComputeResponseDamage(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FBF50, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponseDamage(const CEventDamage& e, CPedGroup* pg, CPed* originator) { + const auto src = e.GetSourceEntity(); + if (!src || !src->IsPed()) { + return nullptr; + } + const auto srcPed = src->AsPed(); + MaybeAdjustTaskOfGroupThreatEvent(e, pg, originator, srcPed); + switch (e.m_taskId) { + case TASK_GROUP_KILL_THREATS_BASIC: return ComputeKillThreatsBasicResponse(pg, srcPed, originator, true); + case TASK_GROUP_KILL_PLAYER_BASIC: return ComputeKillPlayerBasicResponse(pg, srcPed, originator, true); + case TASK_GROUP_FLEE_THREAT: return ComputeFleePedResponse(pg, srcPed, originator, true); + case TASK_GROUP_USE_MEMBER_DECISION: return ComputeMemberResponses(e, pg, originator); + } + return nullptr; } // 0x5F9530 -void CGroupEventHandler::ComputeResponsLeaderQuitEnteringCar(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5F9530, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeResponsLeaderQuitEnteringCar(const CEvent& e, CPedGroup* pg, CPed* originator) { + return plugin::CallAndReturn(e, pg, originator); } // 0x5FAA50 -void CGroupEventHandler::ComputeMemberResponses(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5FAA50, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeMemberResponses(const CEventEditableResponse& e, CPedGroup* pg, CPed* originator) { + const std::unique_ptr ce{ static_cast(const_cast(e).Clone()) }; + for (auto& m : pg->GetMembership().GetFollowers()) { + if (!m.IsAlive()) { + continue; + } + if (ce->HasEditableResponse()) { + ce->m_taskId = TASK_NONE; + ce->ComputeResponseTaskType(&m, true); + } else if (const auto rt = std::unique_ptr(CEventHandler::ComputeEventResponseTask(m, *ce))) { + pg->GetIntelligence().SetEventResponseTask(&m, *rt); + } + } + return nullptr; } // 0x5F9B20 -void CGroupEventHandler::ComputeLeanOnVehicleResponse(const CEvent& event, CPedGroup* group, CPed* ped) { - plugin::Call<0x5F9B20, const CEvent&, CPedGroup*, CPed*>(event, group, ped); +CTaskAllocator* CGroupEventHandler::ComputeLeanOnVehicleResponse(const CEventLeanOnVehicle& e, CPedGroup* pg, CPed* originator) { + const auto leader = pg->GetMembership().GetLeader(); + if (!leader) { + return nullptr; + } + pg->GetIntelligence().SetEventResponseTask( + leader, + CTaskGoToVehicleAndLean{e.m_vehicle, e.m_leanAnimDurationInMs} + ); + return nullptr; } // 0x5FB590 -void CGroupEventHandler::ComputeKillThreatsBasicResponse(CPedGroup* group, CPed* ped1, CPed* ped2, uint8 a4) { - plugin::Call<0x5FB590, CPedGroup*, CPed*, CPed*, uint8>(group, ped1, ped2, a4); +CTaskAllocator* CGroupEventHandler::ComputeKillThreatsBasicResponse(CPedGroup* pg, CPed* threat, CPed* originator, bool bDamageOriginator) { + if (!IsKillTaskAppropriate(pg, threat)) { + return ComputeFleePedResponse(pg, threat, originator, false); + } + if (pg->m_bIsMissionGroup) { + return new CTaskAllocatorKillThreatsBasic{threat}; + } + return new CTaskAllocatorKillThreatsBasicRandomGroup{threat}; } // 0x5FB670 -void CGroupEventHandler::ComputeKillPlayerBasicResponse(CPedGroup* group, CPed* ped1, CPed* ped2, uint8 a4) { - plugin::Call<0x5FB670, CPedGroup*, CPed*, CPed*, uint8>(group, ped1, ped2, a4); +CTaskAllocator* CGroupEventHandler::ComputeKillPlayerBasicResponse(CPedGroup* pg, CPed* threat, CPed* originator, bool bDamageOriginator) { + if (!threat) { + return nullptr; + } + if (!IsKillTaskAppropriate(pg, threat)) { + return ComputeFleePedResponse(pg, threat, originator, false); + } + for (auto& m : pg->GetMembership().GetMembers()) { + pg->GetIntelligence().SetEventResponseTask( + &m, + CTaskComplexKillPedOnFoot{ threat } + ); + } + return nullptr; } // 0x5F9D50 -void CGroupEventHandler::ComputeHassleThreatResponse(CPedGroup* group, CPed* ped1, CPed* ped2, bool a4) { - plugin::Call<0x5F9D50, CPedGroup*, CPed*, CPed*, bool>(group, ped1, ped2, a4); +CTaskAllocator* CGroupEventHandler::ComputeHassleThreatResponse(CPedGroup* pg, CPed* threat, CPed* originator, bool bBeAggressive) { + if (!threat) { + return nullptr; + } + const auto leader = pg->GetMembership().GetLeader(); + if (!leader) { + return nullptr; + } + if ((leader->GetPosition() - threat->GetPosition()).SquaredMagnitude() >= sq(12.f)) { + return nullptr; + } + for (auto& m : pg->GetMembership().GetMembers()) { + const auto SetTask = [&](const auto& t) { + pg->GetIntelligence().SetEventResponseTask(&m, t); + }; + if (threat->IsInVehicle() && threat->m_pVehicle->IsSubAutomobile()) { + if (!bBeAggressive) { + if (threat->m_pVehicle->GetSpareHasslePosId() != -1) { + SetTask(CTaskGangHassleVehicle{ + threat->m_pVehicle, + -1, + 0, + 0.25f, + 0.65f + }); + } + } + } else { + SetTask(CTaskGangHasslePed{ + threat, + bBeAggressive ? 2 : 1, // TODO: Enum + 12'000, + 20'000 + }); + } + } + return nullptr; } // 0x5FA020 -void CGroupEventHandler::ComputeHassleSexyPedResponse(CPedGroup* group, CPed* ped1, CPed* ped2) { - plugin::Call<0x5FA020, CPedGroup*, CPed*, CPed*>(group, ped1, ped2); +CTaskAllocator* CGroupEventHandler::ComputeHassleSexyPedResponse(CPedGroup* pg, CPed* sexyPed, CPed* originator) { + if (!sexyPed) { + return nullptr; + } + for (auto& m : pg->GetMembership().GetFollowers()) { + if (!CGeneral::RandomBool(25.f)) { + continue; + } + pg->GetIntelligence().SetEventResponseTask( + &m, + CTaskGangHasslePed{sexyPed, 0, 8'000, 12'000} + ); + } + return nullptr; } // 0x5FA820 -void CGroupEventHandler::ComputeHandSignalResponse(CPedGroup* group, CPed* ped1, CPed* ped2) { - plugin::Call<0x5FA820, CPedGroup*, CPed*, CPed*>(group, ped1, ped2); +CTaskAllocator* CGroupEventHandler::ComputeHandSignalResponse(CPedGroup* pg, CPed* signalAt, CPed* originator) { + if (!signalAt) { + return nullptr; + } + for (auto& m : pg->GetMembership().GetMembers()) { + if (m.IsPlayer()) { + continue; + } + pg->GetIntelligence().SetEventResponseTask( + &m, + CTaskComplexSignalAtPed{signalAt} + ); + } + return nullptr; } // 0x5FA550 -void CGroupEventHandler::ComputeGreetResponse(CPedGroup* group, CPed* ped1, CPed* ped2) { - plugin::Call<0x5FA550, CPedGroup*, CPed*, CPed*>(group, ped1, ped2); +CTaskAllocator* CGroupEventHandler::ComputeGreetResponse(CPedGroup* pg, CPed* toGreet, CPed* originator) { + if (!toGreet) { + return nullptr; + } + if (!toGreet->GetTaskManager().Has()) { + return nullptr; + } + const auto leader = pg->GetMembership().GetLeader(); + if (!leader) { + return nullptr; + } + const auto [closestToGreeter, closestToGreeterDistSq] = pg->GetMembership().GetMemberClosestTo(toGreet); + if (!closestToGreeter || closestToGreeterDistSq < sq(4.f) && closestToGreeterDistSq > sq(10.f)) { + return nullptr; + } + const auto partnerType = CGeneral::GetRandomNumberInRange(0, 6); + pg->GetIntelligence().SetEventResponseTask( + closestToGreeter, + CTaskComplexPartnerGreet{ + "CompGreetResp", + toGreet, + true, + 0.f, + partnerType, + {} + } + ); + toGreet->GetEventGroup().Add(CEventCreatePartnerTask{ + partnerType + 1, + closestToGreeter, + false, + 0.f // Seems odd + }); + for (auto& m : pg->GetMembership().GetMembers()) { + if (&m == closestToGreeter) { + continue; + } + g_ikChainMan.LookAt( + "CompGreetResp", + &m, + closestToGreeter, + CGeneral::GetRandomNumberInRange(3000, 6000), + BONE_HEAD, + nullptr, + true, + 0.25f, + 500, + 3, + 0 + ); + } + return nullptr; +} + +// 0x5FA290 +CTaskAllocator* CGroupEventHandler::ComputeDoDealResponse(CPedGroup* pg, CPed* dealWith, CPed* originator) { // Pretty much copy-paste of `ComputeGreetResponse` + if (!dealWith) { + return nullptr; + } + if (!dealWith->GetTaskManager().Has()) { + return nullptr; + } + const auto leader = pg->GetMembership().GetLeader(); + if (!leader) { + return nullptr; + } + const auto [closestToGreeter, closestToGreeterDistSq] = pg->GetMembership().GetMemberClosestTo(dealWith); + if (!closestToGreeter || closestToGreeterDistSq < sq(4.f) && closestToGreeterDistSq > sq(10.f)) { + return nullptr; + } + const auto partnerType = CGeneral::GetRandomNumberInRange(0, 6); + pg->GetIntelligence().SetEventResponseTask( + closestToGreeter, + CTaskComplexPartnerDeal{ + "CompDoDealResp", + dealWith, + true, + 0.f, + {} + } + ); + dealWith->GetEventGroup().Add(CEventCreatePartnerTask{ + partnerType + 1, + closestToGreeter, + false, + 0.f // Seems odd + }); + for (auto& m : pg->GetMembership().GetMembers()) { + if (&m == closestToGreeter) { + continue; + } + g_ikChainMan.LookAt( + "CompGreetResp", + &m, + closestToGreeter, + CGeneral::GetRandomNumberInRange(3000, 6000), + BONE_HEAD, + nullptr, + true, + 0.25f, + 500, + 3, + 0 + ); + } + return nullptr; } // 0x5FA130 -void CGroupEventHandler::ComputeFleePedResponse(CPedGroup* group, CPed* ped1, CPed* ped2, uint8 a4) { - plugin::Call<0x5FA130, CPedGroup*, CPed*, CPed*, uint8>(group, ped1, ped2, a4); +CTaskAllocator* CGroupEventHandler::ComputeFleePedResponse(CPedGroup* pg, CPed* threat, CPed* originator, bool bDamageOriginator) { + if (!threat) { + return nullptr; + } + const auto leader = pg->GetMembership().GetLeader(); + if (!leader) { + return nullptr; + } + /* rand(); */ + for (auto& m : pg->GetMembership().GetMembers()) { + pg->GetIntelligence().SetEventResponseTask( + threat, + CTaskComplexSmartFleeEntity{ + &m, + false, + 60.f, + 10'000, + 1'000, + fEntityPosChangeThreshold + } + ); + } + return nullptr; } -// 0x5FC200 -void CGroupEventHandler::ComputeEventResponseTasks(const CEventGroupEvent& groupEvent, CPedGroup* group) { - const auto& ped = groupEvent.m_ped; - const auto& m_event = groupEvent.m_event; +// 0x5F7A00 +CTaskAllocator* CGroupEventHandler::ComputeDrivebyResponse(CPedGroup* pg, CPed* threat, CPed* originator) { + return new CTaskAllocatorKillThreatsDriveby{threat}; +} - switch (m_event->GetEventType()) { +// 0x5FC200 +CTaskAllocator* CGroupEventHandler::ComputeEventResponseTasks(const CEventGroupEvent& ge, CPedGroup* g) { + const auto& p = ge.m_ped; + const auto& e = ge.m_event; + switch (e->GetEventType()) { case EVENT_DRAGGED_OUT_CAR: - ComputeResponseDraggedOutCar(*m_event, group, ped); - break; + return ComputeResponseDraggedOutCar(static_cast(*e), g, p); case EVENT_DAMAGE: - ComputeResponseDamage(*m_event, group, ped); - break; + return ComputeResponseDamage(static_cast(*e), g, p); case EVENT_SHOT_FIRED: - ComputeResponseShotFired(*m_event, group, ped); - break; + return ComputeResponseShotFired(static_cast(*e), g, p); case EVENT_SEXY_PED: - ComputeResponseSexyPed(*m_event, group, ped); - break; + return ComputeResponseSexyPed(static_cast(*e), g, p); case EVENT_GUN_AIMED_AT: - ComputeResponseGunAimedAt(*m_event, group, ped); - break; + return ComputeResponseGunAimedAt(static_cast(*e), g, p); case EVENT_ACQUAINTANCE_PED_HATE: case EVENT_ACQUAINTANCE_PED_DISLIKE: - ComputeResponsePedThreat(*m_event, group, ped); - break; + return ComputeResponsePedThreat(static_cast(*e), g, p); case EVENT_ACQUAINTANCE_PED_LIKE: case EVENT_ACQUAINTANCE_PED_RESPECT: - ComputeResponsePedFriend(*m_event, group, ped); - break; + return ComputeResponsePedFriend(static_cast(*e), g, p); case EVENT_VEHICLE_DAMAGE_WEAPON: case EVENT_VEHICLE_DAMAGE_COLLISION: - ComputeResponseVehicleDamage(*m_event, group, ped); - break; + return ComputeResponseVehicleDamage(static_cast(*e), g, p); case EVENT_LEADER_ENTERED_CAR_AS_DRIVER: - ComputeResponseLeaderEnteredCar(*m_event, group, ped); - break; + return ComputeResponseLeaderEnteredCar(*e, g, p); case EVENT_LEADER_EXITED_CAR_AS_DRIVER: - ComputeResponseLeaderExitedCar(*m_event, group, ped); - break; + return ComputeResponseLeaderExitedCar(static_cast(*e), g, p); case EVENT_LEADER_QUIT_ENTERING_CAR_AS_DRIVER: - ComputeResponsLeaderQuitEnteringCar(*m_event, group, ped); - break; + return ComputeResponsLeaderQuitEnteringCar(*e, g, p); case EVENT_PLAYER_COMMAND_TO_GROUP: case EVENT_PLAYER_COMMAND_TO_GROUP_GATHER: - ComputeResponsePlayerCommand(*m_event, group, ped); - break; + return ComputeResponsePlayerCommand(static_cast(*e), g, p); case EVENT_SEEN_COP: - ComputeResponseSeenCop(*m_event, group, ped); - break; + return ComputeResponseSeenCop(static_cast(*e), g, p); case EVENT_DANGER: - ComputeResponseDanger(*m_event, group, ped); - break; + return ComputeResponseDanger(static_cast(*e), g, p); case EVENT_LEADER_ENTRY_EXIT: - ComputeResponseLeaderEnterExit(*m_event, group, ped); - break; + return ComputeResponseLeaderEnterExit(static_cast(*e), g, p); case EVENT_NEW_GANG_MEMBER: - ComputeResponseNewGangMember(*m_event, group, ped); - break; + return ComputeResponseNewGangMember(static_cast(*e), g, p); case EVENT_LEAN_ON_VEHICLE: - ComputeLeanOnVehicleResponse(*m_event, group, ped); - break; + return ComputeLeanOnVehicleResponse(static_cast(*e), g, p); default: - return; + return nullptr; } } - -// 0x5F7A00 -void CGroupEventHandler::ComputeDrivebyResponse(CPedGroup* group, CPed* ped1, CPed* ped2) { - plugin::Call<0x5F7A00, CPedGroup*, CPed*, CPed*>(group, ped1, ped2); -} - -// 0x5FA290 -void CGroupEventHandler::ComputeDoDealResponse(CPedGroup* group, CPed* ped1, CPed* ped2) { - plugin::Call<0x5FA290, CPedGroup*, CPed*, CPed*>(group, ped1, ped2); -} diff --git a/source/game_sa/Events/GroupEventHandler.h b/source/game_sa/Events/GroupEventHandler.h index ad73a6ce52..e5159d5f64 100644 --- a/source/game_sa/Events/GroupEventHandler.h +++ b/source/game_sa/Events/GroupEventHandler.h @@ -3,42 +3,65 @@ class CPed; class CEvent; class CEventGroupEvent; +class CTaskAllocator; + +class CEventVehicleDamage; +class CEventGunShot; +class CEventSexyPed; +class CEventSeenCop; +class CEventPlayerCommandToGroup; +class CEventAcquaintancePed; +class CEventNewGangMember; +class CEventLeaderExitedCarAsDriver; +class CEventLeaderEntryExit; +class CEventGunAimedAt; +class CEventPlayerCommandToGroupGather; +class CEventDraggedOutCar; +class CEventDanger; +class CEventDamage; +class CEventEditableResponse; +class CEventLeanOnVehicle; class CGroupEventHandler { public: static void InjectHooks(); - static void IsKillTaskAppropriate(CPedGroup* group, CPed* ped); - static void ComputeWalkAlongsideResponse(CPedGroup* group, CPed* ped1, CPed* ped2); - static void ComputeStareResponse(CPedGroup* group, CPed* ped1, CPed* ped2, int32, int32); - static void ComputeResponseVehicleDamage(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseShotFired(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseSexyPed(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseSeenCop(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponsePlayerCommand(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponsePedThreat(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponsePedFriend(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseNewGangMember(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseLeaderExitedCar(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseLeaderEnteredCar(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseLeaderEnterExit(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseGunAimedAt(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseGather(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseDraggedOutCar(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseDanger(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponseDamage(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeResponsLeaderQuitEnteringCar(const CEvent&, CPedGroup* group, CPed* ped); - static void ComputeMemberResponses(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeLeanOnVehicleResponse(const CEvent& event, CPedGroup* group, CPed* ped); - static void ComputeKillThreatsBasicResponse(CPedGroup* group, CPed* ped1, CPed* ped2, uint8); - static void ComputeKillPlayerBasicResponse(CPedGroup* group, CPed* ped1, CPed* ped2, uint8); - static void ComputeHassleThreatResponse(CPedGroup* group, CPed* ped1, CPed* ped2, bool); - static void ComputeHassleSexyPedResponse(CPedGroup* group, CPed* ped1, CPed* ped2); - static void ComputeHandSignalResponse(CPedGroup* group, CPed* ped1, CPed* ped2); - static void ComputeGreetResponse(CPedGroup* group, CPed* ped1, CPed* ped2); - static void ComputeFleePedResponse(CPedGroup* group, CPed* ped1, CPed* ped2, uint8); - static void ComputeEventResponseTasks(const CEventGroupEvent& groupEvent, CPedGroup* group); - static void ComputeDrivebyResponse(CPedGroup* group, CPed* ped1, CPed* ped2); - static void ComputeDoDealResponse(CPedGroup* group, CPed* ped1, CPed* ped2); + static bool IsKillTaskAppropriate(CPedGroup* pg, CPed* ped); + + static CTaskAllocator* ComputeWalkAlongsideResponse(CPedGroup* pg, CPed* ped1, CPed* ped2); + static CTaskAllocator* ComputeStareResponse(CPedGroup* pg, CPed* stareAt, CPed* originator, int32 timeout, int32 timeoutBias); + + static CTaskAllocator* ComputeResponseVehicleDamage(const CEventVehicleDamage& event, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseShotFired(const CEventGunShot& event, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseSexyPed(const CEventSexyPed& event, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseSeenCop(const CEventSeenCop& event, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponsePlayerCommand(const CEventPlayerCommandToGroup& event, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponsePedThreat(const CEventAcquaintancePed& event, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponsePedFriend(const CEventAcquaintancePed& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseNewGangMember(const CEventNewGangMember& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseLeaderExitedCar(const CEventEditableResponse& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseLeaderEnteredCar(const CEvent& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseLeaderEnterExit(const CEventLeaderEntryExit& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseGunAimedAt(const CEventGunAimedAt& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseGather(const CEventPlayerCommandToGroupGather& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseDraggedOutCar(const CEventDraggedOutCar& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseDanger(const CEventDanger& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponseDamage(const CEventDamage& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeResponsLeaderQuitEnteringCar(const CEvent&, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeMemberResponses(const CEventEditableResponse& e, CPedGroup* pg, CPed* originator); + static CTaskAllocator* ComputeLeanOnVehicleResponse(const CEventLeanOnVehicle& e, CPedGroup* pg, CPed* originator); + + static CTaskAllocator* ComputeKillThreatsBasicResponse(CPedGroup* pg, CPed* threat, CPed* originator, bool bDamageOriginator); + static CTaskAllocator* ComputeKillPlayerBasicResponse(CPedGroup* pg, CPed* threat, CPed* originator, bool bDamageOriginator); + static CTaskAllocator* ComputeHassleThreatResponse(CPedGroup* pg, CPed* threat, CPed* originator, bool bDamageOriginator); + static CTaskAllocator* ComputeHassleSexyPedResponse(CPedGroup* pg, CPed* sexyPed, CPed* originator); + static CTaskAllocator* ComputeHandSignalResponse(CPedGroup* pg, CPed* signalAt, CPed* originator); + static CTaskAllocator* ComputeGreetResponse(CPedGroup* pg, CPed* toGreet, CPed* originator); + static CTaskAllocator* ComputeDoDealResponse(CPedGroup* pg, CPed* dealWith, CPed* originator); + static CTaskAllocator* ComputeFleePedResponse(CPedGroup* pg, CPed* threat, CPed* originator, bool bDamageOriginator); + static CTaskAllocator* ComputeDrivebyResponse(CPedGroup* pg, CPed* threat, CPed* originator); + + static CTaskAllocator* ComputeEventResponseTasks(const CEventGroupEvent& groupEvent, CPedGroup* pg); }; +static inline auto& fEntityPosChangeThreshold = StaticRef(0xC18CF0); diff --git a/source/game_sa/Events/LeaderEvents.cpp b/source/game_sa/Events/LeaderEvents.cpp index 1933674c12..8f6dce741a 100644 --- a/source/game_sa/Events/LeaderEvents.cpp +++ b/source/game_sa/Events/LeaderEvents.cpp @@ -109,20 +109,13 @@ bool CEventLeaderExitedCarAsDriver::AffectsPedGroup(CPedGroup* pedGroup) return CEventLeaderExitedCarAsDriver::AffectsPedGroup_Reversed(pedGroup); } -bool CEventLeaderExitedCarAsDriver::AffectsPedGroup_Reversed(CPedGroup* pedGroup) -{ - for (int32 i = 0; i < TOTAL_PED_GROUP_FOLLOWERS; i++) { - CPedGroupMembership& memberShip = pedGroup->GetMembership(); - CPed* member = memberShip.GetMember(i); - if (!member) - continue; - - if (member->m_pVehicle && member->bInVehicle && member->m_pVehicle == memberShip.GetLeader()->m_pVehicle) +bool CEventLeaderExitedCarAsDriver::AffectsPedGroup_Reversed(CPedGroup* pg) { + const auto leader = pg->GetMembership().GetLeader(); + for (auto& m : pg->GetMembership().GetFollowers()) { + if (m.m_pVehicle && m.bInVehicle && m.m_pVehicle == leader->m_pVehicle) { return true; - - if (member->GetIntelligence()->FindTaskByType(TASK_COMPLEX_ENTER_CAR_AS_PASSENGER) - || member->GetIntelligence()->FindTaskByType(TASK_COMPLEX_ENTER_CAR_AS_PASSENGER_WAIT)) - { + } + if (m.GetTaskManager().HasAnyOf(false)) { return true; } } @@ -141,12 +134,9 @@ bool CEventLeaderQuitEnteringCarAsDriver::AffectsPedGroup(CPedGroup* pedGroup) return CEventLeaderQuitEnteringCarAsDriver::AffectsPedGroup_Reversed(pedGroup); } -bool CEventLeaderQuitEnteringCarAsDriver::AffectsPedGroup_Reversed(CPedGroup* pedGroup) -{ - auto oldEventGroupEvent = pedGroup->GetIntelligence().m_pOldEventGroupEvent; - if (oldEventGroupEvent) - return oldEventGroupEvent->m_event->GetEventType() == EVENT_LEADER_ENTERED_CAR_AS_DRIVER; - return false; +bool CEventLeaderQuitEnteringCarAsDriver::AffectsPedGroup_Reversed(CPedGroup* pedGroup) { + const auto oe = pedGroup->GetIntelligence().GetOldEvent(); + return oe && oe->GetEvent().GetEventType() == EVENT_LEADER_ENTERED_CAR_AS_DRIVER; } CEventAreaCodes::CEventAreaCodes(CPed* ped) diff --git a/source/game_sa/FileLoader.cpp b/source/game_sa/FileLoader.cpp index e8197c7c80..321efb23ce 100644 --- a/source/game_sa/FileLoader.cpp +++ b/source/game_sa/FileLoader.cpp @@ -257,18 +257,18 @@ char* CFileLoader::LoadLine(char*& bufferIt, int32& buffSize) { // IPL -> AUZO // 0x5B4D70 void CFileLoader::LoadAudioZone(const char* line) { - char name[16]; + char name[8]; int32 id; - int32 enabled; + int32 flags; float x1, y1, z1; float x2, y2, z2; float radius; - if (sscanf_s(line, "%s %d %d %f %f %f %f %f %f", SCANF_S_STR(name), &id, &enabled, &x1, &y1, &z1, &x2, &y2, &z2) == 9) { - CAudioZones::RegisterAudioBox(name, id, enabled != 0, x1, y1, z1, x2, y2, z2); + if (sscanf_s(line, "%s %d %d %f %f %f %f %f %f", SCANF_S_STR(name), &id, &flags, &x1, &y1, &z1, &x2, &y2, &z2) == 9) { + CAudioZones::RegisterAudioBox(name, id, flags == 1, {x1, y1, z1}, {x2, y2, z2}); } else { - VERIFY(sscanf_s(line, "%s %d %d %f %f %f %f", SCANF_S_STR(name), &id, &enabled, &x1, &y1, &z1, &radius) == 7); - CAudioZones::RegisterAudioSphere(name, id, enabled != 0, x1, y1, z1, radius); + VERIFY(sscanf_s(line, "%s %d %d %f %f %f %f", SCANF_S_STR(name), &id, &flags, &x1, &y1, &z1, &radius) == 7); + CAudioZones::RegisterAudioSphere(name, id, flags == 1, {x1, y1, z1}, radius); } } @@ -738,7 +738,7 @@ void CFileLoader::LoadCollisionModelVer2(uint8* buffer, uint32 dataSize, CColMod // Return pointer for offset in allocated memory (relative to where it was in the file) const auto GetDataPtr = [&]() { return reinterpret_cast( - p + p + sizeof(CCollisionData) // Must offset by this (See memory layout above) + fileOffset + sizeof(FileHeader::FileInfo::fourcc) // All offsets are relative to this, but since it is already included in the header's size, so we gotta compensate for it. @@ -764,6 +764,8 @@ void CFileLoader::LoadCollisionModelVer2(uint8* buffer, uint32 dataSize, CColMod cd->m_pShadowTriangles = nullptr; cm.m_bIsSingleColDataAlloc = true; + + assert(!cd->bHasFaceGroups || cd->m_pTriangles); // If it has face groups it should also have triangles allocated } // 0x537CE0 @@ -834,6 +836,8 @@ void CFileLoader::LoadCollisionModelVer3(uint8* buffer, uint32 dataSize, CColMod cd->m_pTrianglePlanes = nullptr; cm.m_bIsSingleColDataAlloc = true; + + assert(!cd->bHasFaceGroups || cd->m_pTriangles); // If it has face groups it should also have triangles allocated } // 0x537AE0 @@ -904,6 +908,8 @@ void CFileLoader::LoadCollisionModelVer4(uint8* buffer, uint32 dataSize, CColMod cd->m_pTrianglePlanes = nullptr; cm.m_bIsSingleColDataAlloc = true; + + assert(!cd->bHasFaceGroups || cd->m_pTriangles); // If it has face groups it should also have triangles allocated } // 0x5B3C60 @@ -972,7 +978,7 @@ void CFileLoader::Load2dEffect(const char* line) { break; case EFFECT_ATTRACTOR: break; - case EFFECT_FURNITURE: + case EFFECT_INTERIOR: break; case EFFECT_ENEX: break; @@ -1278,6 +1284,8 @@ void CFileLoader::LoadGarage(const char* line) { // 0x5B9030 void CFileLoader::LoadLevel(const char* levelFileName) { + ZoneScoped; + auto txd = RwTexDictionaryGetCurrent(); if (!txd) { txd = RwTexDictionaryCreate(); @@ -1316,15 +1324,22 @@ void CFileLoader::LoadLevel(const char* levelFileName) { break; // Done if (LineBeginsWith("TEXDICTION")) { + ZoneScopedN("TEXDICTION"); + const auto path = ExtractPathFor("TEXDICTION"); + ZoneText(path, strlen(path)); + LoadingScreenLoadingFile(path); const auto txd = LoadTexDictionary(path); RwTexDictionaryForAllTextures(txd, AddTextureCB, txd); RwTexDictionaryDestroy(txd); } else if (LineBeginsWith("IPL")) { + ZoneScopedN("IPL"); + // Have to call this here, because line buffer's content may change after the `if` below const auto path = ExtractPathFor("IPL"); + ZoneText(path, strlen(path)); if (!hasLoadedAnyIPLs) { MatchAllModelStrings(); @@ -1378,9 +1393,15 @@ void CFileLoader::LoadLevel(const char* levelFileName) { }; for (const auto& v : functions) { if (LineBeginsWith(v.id)) { + ZoneScoped; + ZoneText(v.id.data(), v.id.size()); + const auto path = ExtractPathFor(v.id); + ZoneText(path, strlen(path)); + LoadingScreenLoadingFile(path); v.fn(path); + break; } } @@ -1389,8 +1410,8 @@ void CFileLoader::LoadLevel(const char* levelFileName) { CFileMgr::CloseFile(f); RwTexDictionarySetCurrent(txd); - if (hasLoadedAnyIPLs) - { + + if (hasLoadedAnyIPLs) { CIplStore::LoadAllRemainingIpls(); CColStore::BoundingBoxesPostProcess(); CTrain::InitTrains(); @@ -1461,33 +1482,24 @@ int32 CFileLoader::LoadPedObject(const char* line) { SCANF_S_STR(voiceMax) ) == 14); - const auto FindAnimGroup = [animGroup, nAssocGroups = CAnimManager::ms_numAnimAssocDefinitions] { - for (auto i = 0; i < nAssocGroups; i++) { - if (CAnimManager::GetAnimGroupName((AssocGroupId)i) == std::string_view{animGroup}) { - return (AssocGroupId)i; - } - } - return (AssocGroupId)nAssocGroups; - }; - const auto mi = CModelInfo::AddPedModel(modelId); mi->m_nKey = CKeyGen::GetUppercaseKey(modelName); mi->SetTexDictionary(texName); mi->SetAnimFile(animFile); mi->SetColModel(&colModelPeds, false); - mi->m_nPedType = CPedType::FindPedType(pedType); - mi->m_nStatType = CPedStats::GetPedStatType(statName); - mi->m_nAnimType = FindAnimGroup(); + mi->m_nPedType = CPedType::FindPedType(pedType); + mi->m_nStatType = CPedStats::GetPedStatType(statName); + mi->m_nAnimType = CAnimManager::GetAnimationGroupIdByName(animGroup); mi->m_nCarsCanDriveMask = carsCanDriveMask; - mi->m_nPedFlags = flags; - mi->m_nRadio2 = (eRadioID)(radio2 + 1); - mi->m_nRadio1 = (eRadioID)(radio1 + 1); - mi->m_nRace = CPopulation::FindPedRaceFromName(modelName); - mi->m_nPedAudioType = CAEPedSpeechAudioEntity::GetAudioPedType(pedVoiceType); - mi->m_nVoiceMin = CAEPedSpeechAudioEntity::GetVoice(voiceMin, mi->m_nPedAudioType); - mi->m_nVoiceMax = CAEPedSpeechAudioEntity::GetVoice(voiceMax, mi->m_nPedAudioType); - mi->m_nVoiceId = mi->m_nVoiceMin; + mi->m_nPedFlags = flags; + mi->m_nRadio2 = (eRadioID)(radio2 + 1); + mi->m_nRadio1 = (eRadioID)(radio1 + 1); + mi->m_nRace = CPopulation::FindPedRaceFromName(modelName); + mi->m_nPedAudioType = CAEPedSpeechAudioEntity::GetAudioPedType(pedVoiceType); + mi->m_nVoiceMin = CAEPedSpeechAudioEntity::GetVoice(voiceMin, mi->m_nPedAudioType); + mi->m_nVoiceMax = CAEPedSpeechAudioEntity::GetVoice(voiceMax, mi->m_nPedAudioType); + mi->m_nVoiceId = mi->m_nVoiceMin; return modelId; } @@ -1656,7 +1668,7 @@ void CFileLoader::LoadPickup(const char* line) { } }; - if (const auto model = GetModel(); model != -1) { + if (const auto model = GetModel(); model != MODEL_INVALID) { CPickups::GenerateNewOne(pos, model, PICKUP_ON_STREET, 0, 0, false, nullptr); } } @@ -1950,15 +1962,14 @@ int32 CFileLoader::LoadWeaponObject(const char* line) { // 0x5B4AB0 void CFileLoader::LoadZone(const char* line) { - char name[24]; - int32 type; + char infoLabel[24]; + int32 type; CVector min{}, max{}; - int32 island; - char zoneName[12]; + int32 level; + char textLabel[12]; - auto iNumRead = sscanf_s(line, "%s %d %f %f %f %f %f %f %d %s", SCANF_S_STR(name), &type, &min.x, &min.y, &min.z, &max.x, &max.y, &max.z, &island, SCANF_S_STR(zoneName)); - if (iNumRead == 10) - CTheZones::CreateZone(name, static_cast(type), min.x, min.y, min.z, max.x, max.y, max.z, static_cast(island), zoneName); + VERIFY(sscanf_s(line, "%s %d %f %f %f %f %f %f %d %s", SCANF_S_STR(infoLabel), &type, &min.x, &min.y, &min.z, &max.x, &max.y, &max.z, &level, SCANF_S_STR(textLabel)) == 10); + CTheZones::CreateZone(infoLabel, static_cast(type), min, max, static_cast(level), textLabel); } // 0x5B51E0 @@ -1968,6 +1979,8 @@ void LinkLods(int32 a1) { // 0x5B8700 void CFileLoader::LoadScene(const char* filename) { + ZoneScoped; + gCurrIplInstancesCount = 0; enum class SectionID { diff --git a/source/game_sa/FileMgr.cpp b/source/game_sa/FileMgr.cpp index fe8cbfbb04..d958574864 100644 --- a/source/game_sa/FileMgr.cpp +++ b/source/game_sa/FileMgr.cpp @@ -246,8 +246,9 @@ size_t CFileMgr::LoadFile(const char *path, uint8 *buf, size_t size, const char } // 0x538900 -FILESTREAM CFileMgr::OpenFile(const char *path, const char *mode) -{ +FILESTREAM CFileMgr::OpenFile(const char *path, const char *mode) { + ZoneScoped; + FILESTREAM fs{nullptr}; if (WindowsCharset == CP_UTF8) { fopen_s(&fs, path, mode); diff --git a/source/game_sa/FireManager.cpp b/source/game_sa/FireManager.cpp index 076840507c..57227a6473 100644 --- a/source/game_sa/FireManager.cpp +++ b/source/game_sa/FireManager.cpp @@ -359,6 +359,8 @@ int32 CFireManager::StartScriptFire(const CVector& pos, CEntity* target, float _ // 0x53AF00 void CFireManager::Update() { + ZoneScoped; + if (CReplay::Mode == MODE_PLAYBACK) return; diff --git a/source/game_sa/Font.cpp b/source/game_sa/Font.cpp index 0a386d1c65..ad209a9a92 100644 --- a/source/game_sa/Font.cpp +++ b/source/game_sa/Font.cpp @@ -605,6 +605,8 @@ void CFont::SetOrientation(eFontAlignment alignment) { // Need to call this each frame // 0x719800 void CFont::InitPerFrame() { + ZoneScoped; + m_nFontOutline = 0; m_nFontOutlineOrShadow = 0; m_nFontShadow = 0; @@ -678,6 +680,8 @@ float CFont::GetStringWidth(const char* string, bool full, bool scriptText) { // same as RenderFontBuffer() (0x71A210) void CFont::DrawFonts() { + ZoneScoped; + RenderFontBuffer(); } diff --git a/source/game_sa/Frontend/MenuManager_Input.cpp b/source/game_sa/Frontend/MenuManager_Input.cpp index 9130a1e8a8..5b63134c01 100644 --- a/source/game_sa/Frontend/MenuManager_Input.cpp +++ b/source/game_sa/Frontend/MenuManager_Input.cpp @@ -6,7 +6,8 @@ #include "app/app.h" #include "VideoMode.h" // todo #include "ControllerConfigManager.h" -#include "LoadingScreen.h" + +#include "extensions/Configs/FastLoader.hpp" /*! * @addr 0x57FD70 @@ -230,7 +231,7 @@ void CMenuManager::CheckForMenuClosing() { if ((!field_35 || !m_bActivateMenuNextFrame) && !m_bLoadingData) { AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_START); - if (!FastLoadSettings.ShouldLoadSaveGame()) { // If loading, skip menu audio + if (!g_FastLoaderConfig.ShouldLoadSaveGame()) { // If loading, skip menu audio AudioEngine.Service(); } } @@ -284,7 +285,11 @@ void CMenuManager::CheckForMenuClosing() { if (IsVideoModeExclusive()) { DIReleaseMouse(); +#ifdef FIX_BUGS // Causes the retarded fucktard code to not dispatch mouse input to WndProc => ImGUI mouse not working. Amazing piece of technology. + InitialiseMouse(false); +#else InitialiseMouse(true); +#endif // !FIX_BUGS } m_fStatsScrollSpeed = 150.0f; @@ -310,8 +315,8 @@ void CMenuManager::CheckForMenuClosing() { if (field_F4) { auto player = FindPlayerPed(); - if (player->GetActiveWeapon().m_nType != WEAPON_CAMERA - || CTimer::GetTimeInMS() >= player->GetActiveWeapon().m_nTimeForNextShot) { + if (player->GetActiveWeapon().m_Type != WEAPON_CAMERA + || CTimer::GetTimeInMS() >= player->GetActiveWeapon().m_TimeForNextShotMs) { TheCamera.SetFadeColour(0u, 0u, 0u); TheCamera.Fade(0.0f, eFadeFlag::FADE_IN); TheCamera.ProcessFade(); @@ -333,7 +338,7 @@ void CMenuManager::CheckForMenuClosing() { AudioEngine.ReportFrontendAudioEvent(AE_FRONTEND_START); - if (!FastLoadSettings.ShouldLoadSaveGame()) { // If loading, skip menu audio + if (!g_FastLoaderConfig.ShouldLoadSaveGame()) { // If loading, skip menu audio AudioEngine.Service(); } diff --git a/source/game_sa/Frontend/MenuManager_Process.cpp b/source/game_sa/Frontend/MenuManager_Process.cpp index adfdce480c..cd2e33b820 100644 --- a/source/game_sa/Frontend/MenuManager_Process.cpp +++ b/source/game_sa/Frontend/MenuManager_Process.cpp @@ -17,6 +17,8 @@ // 0x57B440 void CMenuManager::Process() { + ZoneScoped; + if (m_bMenuActive) { ProcessStreaming(m_bAllStreamingStuffLoaded); UserInput(); diff --git a/source/game_sa/Fx/Blueprints/FxInfo.cpp b/source/game_sa/Fx/Blueprints/FxInfo.cpp new file mode 100644 index 0000000000..31254914c1 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfo.cpp @@ -0,0 +1,7 @@ +#include "StdInc.h" + +#include "FxInfo.h" + +FxInfo_c::~FxInfo_c() { + // NOP +} diff --git a/source/game_sa/Fx/Blueprints/FxInfo.h b/source/game_sa/Fx/Blueprints/FxInfo.h new file mode 100644 index 0000000000..472dcc9baa --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfo.h @@ -0,0 +1,24 @@ +#pragma once + +#include "FxTools.h" + +#include "EmissionInfo.h" +#include "MovementInfo.h" +#include "RenderInfo.h" +#include "eFxInfoType.h" + +class FxInfo_c { +protected: + eFxInfoType m_nType; + bool m_bTimeModeParticle; + +public: + FxInfo_c() = default; // 0x4A4B30 + virtual ~FxInfo_c() = 0; // 0x4A4940 + virtual void Load(FILESTREAM file, int32 version) = 0; + virtual void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) = 0; + + friend class FxInfoManager_c; + friend class FxEmitterBP_c; +}; +VALIDATE_SIZE(FxInfo_c, 0x8); diff --git a/source/game_sa/Fx/Blueprints/FxInfoAnimTexture.cpp b/source/game_sa/Fx/Blueprints/FxInfoAnimTexture.cpp new file mode 100644 index 0000000000..07b2a8c505 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoAnimTexture.cpp @@ -0,0 +1,29 @@ +#include "StdInc.h" + +#include "FxInfoAnimTexture.h" +#include "RenderInfo.h" + +// 0x4A6550 +FxInfoAnimTexture_c::FxInfoAnimTexture_c() : FxInfo_c() { + m_nType = FX_INFO_ANIMTEX_DATA; + m_InterpInfo.Allocate(1); +} + +// 0x4A65C0 +void FxInfoAnimTexture_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A65E0 +void FxInfoAnimTexture_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[16]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + render.m_bHasAnimTextures = true; + render.m_nCurrentTexId = static_cast(values[0]); +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoAnimTexture.h b/source/game_sa/Fx/Blueprints/FxInfoAnimTexture.h new file mode 100644 index 0000000000..6edf183e45 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoAnimTexture.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoAnimTexture_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoAnimTexture_c(); + ~FxInfoAnimTexture_c() override = default; // 0x4A78F0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoAttractLine.cpp b/source/game_sa/Fx/Blueprints/FxInfoAttractLine.cpp new file mode 100644 index 0000000000..916e000605 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoAttractLine.cpp @@ -0,0 +1,53 @@ +#include "StdInc.h" + +#include "FxInfoAttractLine.h" + +// 0x4A57C0 +FxInfoAttractLine_c::FxInfoAttractLine_c() : FxInfo_c() { + m_nType = FX_INFO_ATTRACTLINE_DATA; + m_InterpInfo.Allocate(7); +} + +// 0x4A5830 +void FxInfoAttractLine_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A4880 +void FindClosestPtOnLine(CVector& out, CVector& lineA, CVector& lineB, CVector& point) { + return plugin::Call<0x4A4880, CVector&, CVector&, CVector&, CVector&>(out, lineA, lineB, point); + + const auto a = DistanceBetweenPoints(point, lineA); + const auto b = DistanceBetweenPoints(lineB, lineA); + const auto a3m2 = lineB - lineA; + const auto a4m2 = point - lineA; + float v9 = DotProduct(a4m2, a3m2) / a3m2.SquaredMagnitude(); + out = a3m2 * std::clamp(v9, 0.0f, 1.0f) + lineA; +} + +// 0x4A5850 +void FxInfoAttractLine_c::GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) { + float values[7]; + if (m_bTimeModeParticle) { + m_InterpInfo.GetVal(values, mult); + } else { + m_InterpInfo.GetVal(values, currentTime / length); + } + + CVector out; + auto& lineA = *(CVector*)&values[0]; + auto& lineB = *(CVector*)&values[3]; + + auto& movement = *static_cast(info); + FindClosestPtOnLine(out, lineA, lineB, movement.m_Pos); + CVector in = out - movement.m_Pos; + auto dist = in.Magnitude(); + + if (dist > 0.0f) { + in.Normalise(); + } + + if (dist > 0.1f) { + movement.m_Vel += in * values[6] * totalTime; + } +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoAttractLine.h b/source/game_sa/Fx/Blueprints/FxInfoAttractLine.h new file mode 100644 index 0000000000..88d0bd4f73 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoAttractLine.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoAttractLine_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoAttractLine_c(); + ~FxInfoAttractLine_c() override = default; // 0x4A7040 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoAttractPt.cpp b/source/game_sa/Fx/Blueprints/FxInfoAttractPt.cpp new file mode 100644 index 0000000000..34068399e8 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoAttractPt.cpp @@ -0,0 +1,35 @@ +#include "StdInc.h" + +#include "FxInfoAttractPt.h" + +// 0x4A5650 +FxInfoAttractPt_c::FxInfoAttractPt_c() : FxInfo_c() { + m_nType = FX_INFO_ATTRACTPT_DATA; + m_InterpInfo.Allocate(4); +} + +// 0x4A56C0 +void FxInfoAttractPt_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A56E0 +void FxInfoAttractPt_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[4]; + m_InterpInfo.GetVal(values, mult); + + auto& movement = *static_cast(info); + CVector in = *(CVector*)&values[0] - movement.m_Pos; + + auto length = in.Magnitude(); + if (length > 0.0f) + in.Normalise(); + + if (length > 0.1f) { + movement.m_Vel += in * values[0] * totalTime; + } +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoAttractPt.h b/source/game_sa/Fx/Blueprints/FxInfoAttractPt.h new file mode 100644 index 0000000000..8acf42219b --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoAttractPt.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoAttractPt_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoAttractPt_c(); + ~FxInfoAttractPt_c() override = default; // 0x4A6FD0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoColour.cpp b/source/game_sa/Fx/Blueprints/FxInfoColour.cpp new file mode 100644 index 0000000000..0a592e1329 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoColour.cpp @@ -0,0 +1,25 @@ +#include "StdInc.h" + +#include "FxInfoColour.h" + +// 0x4A5EA0 +FxInfoColour_c::FxInfoColour_c() : FxInfo_c() { + m_nType = FX_INFO_COLOUR_DATA; + m_InterpInfo.Allocate(4); +} + +// 0x4A5F10 +void FxInfoColour_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +void FxInfoColour_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[4]; + m_InterpInfo.GetVal(values, mult); + + static_cast(info)->SetColor(values[0], values[1], values[2], values[3]); +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoColour.h b/source/game_sa/Fx/Blueprints/FxInfoColour.h new file mode 100644 index 0000000000..d0a7745860 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoColour.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoU255.h" + +class FxInfoColour_c : public FxInfo_c { +protected: + FxInterpInfoU255_c m_InterpInfo; + +public: + FxInfoColour_c(); + ~FxInfoColour_c() override = default; // 0x4A75E0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoColourBright.cpp b/source/game_sa/Fx/Blueprints/FxInfoColourBright.cpp new file mode 100644 index 0000000000..f9503cad7f --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoColourBright.cpp @@ -0,0 +1,29 @@ +#include "StdInc.h" + +#include "FxInfoColourBright.h" + +// 0x4A67D0 +FxInfoColourBright_c::FxInfoColourBright_c() : FxInfo_c() { + m_nType = FX_INFO_COLOURBRIGHT_DATA; + m_InterpInfo.Allocate(5); +} + +// 0x4A6840 +void FxInfoColourBright_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A6860 +void FxInfoColourBright_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[5]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + render.SetColor(values[0], values[1], values[2], values[3]); + render.m_Color2.alpha = (uint8)values[4]; + render.m_nColorType = ERenderColorType::BRIGHT; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoColourBright.h b/source/game_sa/Fx/Blueprints/FxInfoColourBright.h new file mode 100644 index 0000000000..8d00e50312 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoColourBright.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoU255.h" + +class FxInfoColourBright_c : public FxInfo_c { +protected: + FxInterpInfoU255_c m_InterpInfo; + +public: + FxInfoColourBright_c(); + ~FxInfoColourBright_c() override = default; // 0x4A7A40 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoColourRange.cpp b/source/game_sa/Fx/Blueprints/FxInfoColourRange.cpp new file mode 100644 index 0000000000..a0aa807eb1 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoColourRange.cpp @@ -0,0 +1,29 @@ +#include "StdInc.h" + +#include "FxInfoColourRange.h" + +// 0x4A6630 +FxInfoColourRange_c::FxInfoColourRange_c() : FxInfo_c() { + m_nType = FX_INFO_COLOURRANGE_DATA; + m_InterpInfo.Allocate(7); +} + +// 0x4A66A0 +void FxInfoColourRange_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A66C0 +void FxInfoColourRange_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[7]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + render.SetColor(values[0], values[2], values[4], values[6]); + render.SetRangeColor(values[1], values[3], values[5]); + render.m_nColorType = ERenderColorType::RANGE; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoColourRange.h b/source/game_sa/Fx/Blueprints/FxInfoColourRange.h new file mode 100644 index 0000000000..e31f969422 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoColourRange.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoU255.h" + +class FxInfoColourRange_c : public FxInfo_c { +protected: + FxInterpInfoU255_c m_InterpInfo; + +public: + FxInfoColourRange_c(); + ~FxInfoColourRange_c() override = default; // 0x4A7960 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoDir.cpp b/source/game_sa/Fx/Blueprints/FxInfoDir.cpp new file mode 100644 index 0000000000..48fa7f5cf2 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoDir.cpp @@ -0,0 +1,31 @@ +#include "StdInc.h" + +#include "FxInfoDir.h" + +// 0x4A6440 +FxInfoDir_c::FxInfoDir_c() : FxInfo_c() { + m_nType = FX_INFO_DIR_DATA; + m_InterpInfo.Allocate(3); +} + +// 0x4A64B0 +void FxInfoDir_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A64D0 +void FxInfoDir_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[3]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + render.m_bHasDir = true; + render.m_Direction = *(CVector*)&values[0]; + if (render.m_Direction.Magnitude() < 0.001f) { + render.m_bUseVel = true; + } +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoDir.h b/source/game_sa/Fx/Blueprints/FxInfoDir.h new file mode 100644 index 0000000000..d63b4a1c72 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoDir.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +class FxInfoDir_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoDir_c(); + ~FxInfoDir_c() override = default; // 0x4A7880 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmAngle.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmAngle.cpp new file mode 100644 index 0000000000..8b3ab3df72 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmAngle.cpp @@ -0,0 +1,24 @@ +#include "StdInc.h" + +#include "FxInfoEmAngle.h" + +// 0x4A4E90 +FxInfoEmAngle_c::FxInfoEmAngle_c() : FxInfo_c() { + m_nType = FX_INFO_EMANGLE_DATA; + m_InterpInfo.Allocate(2); +} + +// 0x4A4F00 +void FxInfoEmAngle_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A4F20 +void FxInfoEmAngle_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[2]; + m_InterpInfo.GetVal(values, currentTime); + + auto& emission = *static_cast(info); + emission.m_fAngleMin = values[0]; + emission.m_fAngleMax = values[1]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmAngle.h b/source/game_sa/Fx/Blueprints/FxInfoEmAngle.h new file mode 100644 index 0000000000..ddbb269f98 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmAngle.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +class FxInfoEmAngle_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoEmAngle_c(); + ~FxInfoEmAngle_c() override = default; // 0x4A6C50 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmDir.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmDir.cpp new file mode 100644 index 0000000000..4d1fe5be55 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmDir.cpp @@ -0,0 +1,22 @@ +#include "StdInc.h" + +#include "FxInfoEmDir.h" + +// 0x4A4DC0 +FxInfoEmDir_c::FxInfoEmDir_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_EMDIR_DATA; + m_InterpInfo.Allocate(3); +} + +// 0x4A4E30 +void FxInfoEmDir_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A4E50 +void FxInfoEmDir_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[3]; + m_InterpInfo.GetVal(values, currentTime); + + static_cast(info)->m_Direction = *(CVector*)&values[0]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmDir.h b/source/game_sa/Fx/Blueprints/FxInfoEmDir.h new file mode 100644 index 0000000000..bb9676aff3 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmDir.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoEmDir_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoEmDir_c(); + ~FxInfoEmDir_c() override = default; // 0x4A6BE0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmLife.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmLife.cpp new file mode 100644 index 0000000000..e28950353b --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmLife.cpp @@ -0,0 +1,24 @@ +#include "StdInc.h" + +#include "FxInfoEmLife.h" + +// 0x4A4F50 +FxInfoEmLife_c::FxInfoEmLife_c() : FxInfo_c() { + m_nType = FX_INFO_EMLIFE_DATA; + m_InterpInfo.Allocate(2); +} + +// 0x4A4FC0 +void FxInfoEmLife_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A4FE0 +void FxInfoEmLife_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[2]; + m_InterpInfo.GetVal(values, currentTime); + + auto& emission = *static_cast(info); + emission.m_fLife = values[0]; + emission.m_fLifeBias = values[1]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmLife.h b/source/game_sa/Fx/Blueprints/FxInfoEmLife.h new file mode 100644 index 0000000000..080e64b212 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmLife.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoU255.h" + +class FxInfoEmLife_c : public FxInfo_c { +protected: + FxInterpInfoU255_c m_InterpInfo; + +public: + FxInfoEmLife_c(); + ~FxInfoEmLife_c() override = default; // 0x4A6CC0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmPos.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmPos.cpp new file mode 100644 index 0000000000..7689e29595 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmPos.cpp @@ -0,0 +1,22 @@ +#include "StdInc.h" + +#include "FxInfoEmPos.h" + +// 0x4A5010 +FxInfoEmPos_c::FxInfoEmPos_c() : FxInfo_c() { + m_nType = FX_INFO_EMPOS_DATA; + m_InterpInfo.Allocate(3); +} + +// 0x4A5080 +void FxInfoEmPos_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A50A0 +void FxInfoEmPos_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[3]; + m_InterpInfo.GetVal(values, currentTime); + + static_cast(info)->m_Pos = *(CVector*)&values[0]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmPos.h b/source/game_sa/Fx/Blueprints/FxInfoEmPos.h new file mode 100644 index 0000000000..f57ccd4635 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmPos.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +class FxInfoEmPos_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoEmPos_c(); + ~FxInfoEmPos_c() override = default; // 0x4A6D30 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmRate.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmRate.cpp new file mode 100644 index 0000000000..6d4df35e6b --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmRate.cpp @@ -0,0 +1,32 @@ +#include "StdInc.h" + +#include "FxInfoEmRate.h" + +// 0x4A6A00 +FxInfoEmRate_c::FxInfoEmRate_c() : FxInfo_c() { + m_nType = FX_INFO_EMRATE_DATA; + m_InterpInfo.Allocate(1); +} + +// 0x4A4B40 +void FxInfoEmRate_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A4B60 +void FxInfoEmRate_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[16]; + m_InterpInfo.GetVal(values, currentTime); + + auto& emission = *static_cast(info); + + auto v10 = currentTime - totalTime; + if (useConst) { + emission.m_fCount = values[0] * totalTime; + } else if (v10 >= 0.0f) { + emission.m_fCount = m_InterpInfo.GetVal(0, currentTime, totalTime); + } else { + emission.m_fCount = m_InterpInfo.GetVal(0, len, -v10); + emission.m_fCount = m_InterpInfo.GetVal(0, currentTime, v10 + totalTime) + emission.m_fCount; + } +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmRate.h b/source/game_sa/Fx/Blueprints/FxInfoEmRate.h new file mode 100644 index 0000000000..1724aeb2a3 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmRate.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +class FxInfoEmRate_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoEmRate_c(); + ~FxInfoEmRate_c() override = default; // 0x4A6A90 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmRotation.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmRotation.cpp new file mode 100644 index 0000000000..c582457e7d --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmRotation.cpp @@ -0,0 +1,24 @@ +#include "StdInc.h" + +#include "FxInfoEmRotation.h" + +// 0x4A51B0 +FxInfoEmRotation_c::FxInfoEmRotation_c() : FxInfo_c() { + m_nType = FX_INFO_EMROTATION_DATA; + m_InterpInfo.Allocate(2); +} + +// 0x4A5220 +void FxInfoEmRotation_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A5240 +void FxInfoEmRotation_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[2]; + m_InterpInfo.GetVal(values, currentTime); + + auto& emission = *static_cast(info); + emission.m_fRotationMinAngle = values[0]; + emission.m_fRotationMaxAngle = values[1]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmRotation.h b/source/game_sa/Fx/Blueprints/FxInfoEmRotation.h new file mode 100644 index 0000000000..584898eed7 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmRotation.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +class FxInfoEmRotation_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoEmRotation_c(); + ~FxInfoEmRotation_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmSize.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmSize.cpp new file mode 100644 index 0000000000..cd9a04c3f3 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmSize.cpp @@ -0,0 +1,31 @@ +#include "StdInc.h" + +#include "FxInfoEmSize.h" + +// 0x4A4C20 +FxInfoEmSize_c::FxInfoEmSize_c() : FxInfo_c() { + m_nType = FX_INFO_EMSIZE_DATA; + m_InterpInfo.Allocate(7); +} + +// 0x4A4C90 +void FxInfoEmSize_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A4CB0 +void FxInfoEmSize_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[7]; + m_InterpInfo.GetVal(values, currentTime); + + auto& emission = *static_cast(info); + emission.m_fRadius = values[0]; + + emission.m_SizeMin.x = values[1]; + emission.m_SizeMin.y = values[3]; + emission.m_SizeMin.z = values[5]; + + emission.m_SizeMax.x = values[2]; + emission.m_SizeMax.y = values[4]; + emission.m_SizeMax.z = values[6]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmSize.h b/source/game_sa/Fx/Blueprints/FxInfoEmSize.h new file mode 100644 index 0000000000..b503c51eb8 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmSize.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoEmSize_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoEmSize_c(); + ~FxInfoEmSize_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmSpeed.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmSpeed.cpp new file mode 100644 index 0000000000..0c9e78b6d7 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmSpeed.cpp @@ -0,0 +1,30 @@ +#include "StdInc.h" + +#include "FxInfoEmSpeed.h" +#include "FxManager.h" + +// 0x4A4D00 +FxInfoEmSpeed_c::FxInfoEmSpeed_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_EMSPEED_DATA; + m_InterpInfo.Allocate(2); +} + +// 0x4A4D70 +void FxInfoEmSpeed_c::Load(FILESTREAM file, int32 version) { + // nick7: + // well, this is interesting: this is not a pointer, so there's 2 ways: + // 1) WIN32 version of SA calls __vfptr->Load + // 2) Android version of SA calls FxInterpInfo32_c::Load + // anyway, this is NOT a bug here, but this is not good + m_InterpInfo.Load(file); +} + +// 0x4A4D90 +void FxInfoEmSpeed_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[2]; + m_InterpInfo.GetVal(values, currentTime); + + auto& emission = *static_cast(info); + emission.m_fSpeed = values[0]; + emission.m_fSpeedBias = values[1]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmSpeed.h b/source/game_sa/Fx/Blueprints/FxInfoEmSpeed.h new file mode 100644 index 0000000000..50e4758c23 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmSpeed.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoEmSpeed_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoEmSpeed_c(); + ~FxInfoEmSpeed_c() override = default; // 0x4A6B70 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmWeather.cpp b/source/game_sa/Fx/Blueprints/FxInfoEmWeather.cpp new file mode 100644 index 0000000000..308efe8ca4 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmWeather.cpp @@ -0,0 +1,27 @@ +#include "StdInc.h" + +#include "FxInfoEmWeather.h" +#include "EmissionInfo.h" + +// 0x4A50E0 +FxInfoEmWeather_c::FxInfoEmWeather_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_EMWEATHER_DATA; + m_InterpInfo.Allocate(4); +} + +// 0x4A5150 +void FxInfoEmWeather_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A5170 +void FxInfoEmWeather_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + float values[4]; + m_InterpInfo.GetVal(values, currentTime); + + auto& emission = *static_cast(info); + emission.m_fMinWind = values[0]; + emission.m_fMaxWind = values[1]; + emission.m_fMinRain = values[2]; + emission.m_fMaxRain = values[3]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoEmWeather.h b/source/game_sa/Fx/Blueprints/FxInfoEmWeather.h new file mode 100644 index 0000000000..51209cdfec --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoEmWeather.h @@ -0,0 +1,18 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +struct EmissionInfo_t; + +class FxInfoEmWeather_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoEmWeather_c(); + ~FxInfoEmWeather_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoFlat.cpp b/source/game_sa/Fx/Blueprints/FxInfoFlat.cpp new file mode 100644 index 0000000000..b64a0b5477 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoFlat.cpp @@ -0,0 +1,32 @@ +#include "StdInc.h" + +#include "FxInfoFlat.h" +#include "FxManager.h" + +// 0x4A6320 +FxInfoFlat_c::FxInfoFlat_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_FLAT_DATA; + m_InterpInfo.Allocate(9); +} + +// 0x4A6390 +void FxInfoFlat_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 004A63B0 +void FxInfoFlat_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if ( !m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[9]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + render.m_FlatMatrix.right = *(RwV3d *)&values[0]; + render.m_FlatMatrix.up = *(RwV3d *)&values[3]; + render.m_FlatMatrix.at = *(RwV3d *)&values[6]; + render.m_bIsFlat = true; + RwMatrixUpdate(&render.m_FlatMatrix); +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoFlat.h b/source/game_sa/Fx/Blueprints/FxInfoFlat.h new file mode 100644 index 0000000000..0481de0a6d --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoFlat.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +class FxInfoFlat_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoFlat_c(); + ~FxInfoFlat_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoFloat.cpp b/source/game_sa/Fx/Blueprints/FxInfoFloat.cpp new file mode 100644 index 0000000000..2a68c253c5 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoFloat.cpp @@ -0,0 +1,20 @@ +#include "StdInc.h" + +#include "FxInfoFloat.h" +#include "FxManager.h" + +// 0x4A5DA0 +FxInfoFloat_c::FxInfoFloat_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_FLOAT_DATA; + m_InterpInfo.Allocate(0); +} + +// 0x4A5DF0 +void FxInfoFloat_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A5E10 +void FxInfoFloat_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + static_cast(info)->m_bHasFloatInfo = true; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoFloat.h b/source/game_sa/Fx/Blueprints/FxInfoFloat.h new file mode 100644 index 0000000000..5053efb648 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoFloat.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +class FxInfoFloat_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoFloat_c(); + ~FxInfoFloat_c() override = default; // 0x4A7500 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoForce.cpp b/source/game_sa/Fx/Blueprints/FxInfoForce.cpp new file mode 100644 index 0000000000..d273a63c9e --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoForce.cpp @@ -0,0 +1,27 @@ +#include "StdInc.h" + +#include "FxInfoForce.h" + +// 0x4A5450 +FxInfoForce_c::FxInfoForce_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_FORCE_DATA; + m_InterpInfo.Allocate(3); +} + +// 0x4A54C0 +void FxInfoForce_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A54E0 +void FxInfoForce_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[3]; + m_InterpInfo.GetVal(values, mult); + + auto& movement = *static_cast(info); + movement.m_Vel += CVector(values[0], values[1], values[2]) * totalTime; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoForce.h b/source/game_sa/Fx/Blueprints/FxInfoForce.h new file mode 100644 index 0000000000..70e192fba6 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoForce.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoForce_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoForce_c(); + ~FxInfoForce_c() override = default; // 0x4A6EF0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoFriction.cpp b/source/game_sa/Fx/Blueprints/FxInfoFriction.cpp new file mode 100644 index 0000000000..8f6d90803f --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoFriction.cpp @@ -0,0 +1,27 @@ +#include "StdInc.h" + +#include "FxInfoFriction.h" + +// 0x4A5550 +FxInfoFriction_c::FxInfoFriction_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_FRICTION_DATA; + m_InterpInfo.Allocate(1); +} + +// 0x4A55C0 +void FxInfoFriction_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A55E0 +void FxInfoFriction_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[3]; + m_InterpInfo.GetVal(values, mult); + + auto& movement = *static_cast(info); + movement.m_Vel *= std::pow(values[0], totalTime * 50.0f); +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoFriction.h b/source/game_sa/Fx/Blueprints/FxInfoFriction.h new file mode 100644 index 0000000000..117ce7867a --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoFriction.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoFriction_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoFriction_c(); + ~FxInfoFriction_c() override = default; // 0x4A6F60 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoGroundCollide.cpp b/source/game_sa/Fx/Blueprints/FxInfoGroundCollide.cpp new file mode 100644 index 0000000000..8cd08fe2ce --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoGroundCollide.cpp @@ -0,0 +1,49 @@ +#include "StdInc.h" + +#include "FxInfoGroundCollide.h" + +// 0x4A5980 +FxInfoGroundCollide_c::FxInfoGroundCollide_c() : FxInfo_c() { + m_nType = FX_INFO_GROUNDCOLLIDE_DATA; + m_InterpInfo.Allocate(3); +} + +// 0x4A59F0 +void FxInfoGroundCollide_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A7100 +void FxInfoGroundCollide_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + auto& movement = *static_cast(info); + + CVector origin = movement.m_Pos; + auto diff = movement.m_Pos.z - movement.m_Vel.z; + CColPoint colPoint; + CEntity* colEntity; + if (!CWorld::ProcessVerticalLine(&origin, diff, colPoint, colEntity, true)) + return; + + if (colPoint.m_vecPoint.z > origin.z || movement.m_Pos.z >= colPoint.m_vecPoint.z) + return; + + float values[16]; + m_InterpInfo.GetVal(values, mult); + + CVector in((float)(CGeneral::GetRandomNumber() % 10'000) / 5000.0f - 1.0f); + in.Normalise(); + in *= values[2] * totalTime * 5.0f; + + auto v13 = colPoint.m_vecNormal.SquaredMagnitude() * values[0]; + CVector out; + out = movement.m_Vel - colPoint.m_vecNormal * (v13 + v13); + out += in; + out.Normalise(); + + movement.m_Pos.z = colPoint.m_vecPoint.z; + movement.m_Vel = values[1] * out.Magnitude() * out; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoGroundCollide.h b/source/game_sa/Fx/Blueprints/FxInfoGroundCollide.h new file mode 100644 index 0000000000..d3898f3589 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoGroundCollide.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoGroundCollide_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoGroundCollide_c(); + ~FxInfoGroundCollide_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoHeatHaze.cpp b/source/game_sa/Fx/Blueprints/FxInfoHeatHaze.cpp new file mode 100644 index 0000000000..a0b25f9aa4 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoHeatHaze.cpp @@ -0,0 +1,19 @@ +#include "StdInc.h" + +#include "FxInfoHeatHaze.h" + +// 0x4A61A0 +FxInfoHeatHaze_c::FxInfoHeatHaze_c() : FxInfo_c() { + m_nType = FX_INFO_HEATHAZE_DATA; + m_InterpInfo.Allocate(0); +} + +// 0x4A61F0 +void FxInfoHeatHaze_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A6210 +void FxInfoHeatHaze_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + static_cast(info)->m_bHeatHaze = true; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoHeatHaze.h b/source/game_sa/Fx/Blueprints/FxInfoHeatHaze.h new file mode 100644 index 0000000000..f4379ff2e2 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoHeatHaze.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo255.h" + +class FxInfoHeatHaze_c : public FxInfo_c { +protected: + FxInterpInfo255_c m_InterpInfo; + +public: + FxInfoHeatHaze_c(); + ~FxInfoHeatHaze_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoJitter.cpp b/source/game_sa/Fx/Blueprints/FxInfoJitter.cpp new file mode 100644 index 0000000000..e3f560fa54 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoJitter.cpp @@ -0,0 +1,31 @@ +#include "StdInc.h" + +#include "FxInfoJitter.h" + +// 0x4A5B20 +FxInfoJitter_c::FxInfoJitter_c() : FxInfo_c() { + m_nType = FX_INFO_JITTER_DATA; + m_InterpInfo.Allocate(1); +} + +// 0x4A5B90 +void FxInfoJitter_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A5BB0 +void FxInfoJitter_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float value; + m_InterpInfo.GetVal(&value, mult); + + CVector in((float)(CGeneral::GetRandomNumber() % 10'000) / 5000.0f - 1.0f); + in.Normalise(); + in *= value * totalTime; + + auto& movement = *static_cast(info); + movement.m_Pos += in; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoJitter.h b/source/game_sa/Fx/Blueprints/FxInfoJitter.h new file mode 100644 index 0000000000..a426bb16f9 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoJitter.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoJitter_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoJitter_c(); + ~FxInfoJitter_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoNoise.cpp b/source/game_sa/Fx/Blueprints/FxInfoNoise.cpp new file mode 100644 index 0000000000..64608723e3 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoNoise.cpp @@ -0,0 +1,36 @@ +#include "StdInc.h" + +#include "FxInfoNoise.h" + +FxInfoNoise_c::FxInfoNoise_c() : FxInfo_c() { + m_nType = FX_INFO_NOISE_DATA; + m_InterpInfo.Allocate(1); +} + +void FxInfoNoise_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A5300 +void FxInfoNoise_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + auto& movement = *static_cast(info); + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float value; + m_InterpInfo.GetVal(&value, mult); + + CVector in((float)(CGeneral::GetRandomNumber() % 10'000) / 5000.0f - 1.0f); + in.Normalise(); + in *= value * totalTime; + + auto length = movement.m_Vel.Magnitude(); + movement.m_Vel += in; + + if (movement.m_Vel.Magnitude() > 0.0f) { + movement.m_Vel.Normalise(); + } + + movement.m_Vel *= length; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoNoise.h b/source/game_sa/Fx/Blueprints/FxInfoNoise.h new file mode 100644 index 0000000000..b978fdd1b6 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoNoise.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoNoise_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoNoise_c(); + ~FxInfoNoise_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoRotSpeed.cpp b/source/game_sa/Fx/Blueprints/FxInfoRotSpeed.cpp new file mode 100644 index 0000000000..a2c7e6316c --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoRotSpeed.cpp @@ -0,0 +1,31 @@ +#include "StdInc.h" + +#include "FxInfoRotSpeed.h" +#include "MovementInfo.h" + +// 0x4A5CB0 +FxInfoRotSpeed_c::FxInfoRotSpeed_c() : FxInfo_c() { + m_nType = FX_INFO_ROTSPEED_DATA; + m_InterpInfo.Allocate(4); +} + +// 0x4A5D20 +void FxInfoRotSpeed_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A5D40 +void FxInfoRotSpeed_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[4]; + m_InterpInfo.GetVal(values, mult); + + auto& movement = *static_cast(info); + movement.m_Rot[0] = values[0]; + movement.m_Rot[1] = values[1]; + movement.m_Rot[2] = values[2]; + movement.m_Rot[3] = values[3]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoRotSpeed.h b/source/game_sa/Fx/Blueprints/FxInfoRotSpeed.h new file mode 100644 index 0000000000..791723ca87 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoRotSpeed.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +class FxInfoRotSpeed_c : public FxInfo_c { +protected: + FxInterpInfoFloat_c m_InterpInfo; + +public: + FxInfoRotSpeed_c(); + ~FxInfoRotSpeed_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoRotate.h b/source/game_sa/Fx/Blueprints/FxInfoRotate.h new file mode 100644 index 0000000000..825f691994 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoRotate.h @@ -0,0 +1,10 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +// From Manhunt +class FxInfoRotate_c : public FxInfo_c { +public: + FxInterpInfoFloat_c m_InterpInfo; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoRotateOffset.h b/source/game_sa/Fx/Blueprints/FxInfoRotateOffset.h new file mode 100644 index 0000000000..3e198c1d3f --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoRotateOffset.h @@ -0,0 +1,8 @@ +#pragma once + +#include "FxInfo.h" + +// From Manhunt +class FxInfoRotateOffset_c : public FxInfo_c { + +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoSelfLit.cpp b/source/game_sa/Fx/Blueprints/FxInfoSelfLit.cpp new file mode 100644 index 0000000000..938e0b3f10 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoSelfLit.cpp @@ -0,0 +1,20 @@ +#include "StdInc.h" + +#include "FxInfoSelfLit.h" +#include "RenderInfo.h" + +// 0x4A6760 +FxInfoSelfLit_c::FxInfoSelfLit_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_SELFLIT_DATA; + m_InterpInfo.Allocate(0); +} + +// 0x4A67B0 +void FxInfoSelfLit_c::Load(FILESTREAM file, int32 version) { + // NOP +} + +// 0x4A67C0 +void FxInfoSelfLit_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + static_cast(info)->m_bSelfLit = true; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoSelfLit.h b/source/game_sa/Fx/Blueprints/FxInfoSelfLit.h new file mode 100644 index 0000000000..60f8ebccaa --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoSelfLit.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoU255.h" + +class FxInfoSelfLit_c : public FxInfo_c { +protected: + FxInterpInfoU255_c m_InterpInfo; + +public: + FxInfoSelfLit_c(); + ~FxInfoSelfLit_c() override = default; // 0x4A79D0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoSize.cpp b/source/game_sa/Fx/Blueprints/FxInfoSize.cpp new file mode 100644 index 0000000000..feb02d0fdc --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoSize.cpp @@ -0,0 +1,40 @@ +#include "StdInc.h" + +#include "FxInfoSize.h" +#include "RenderInfo.h" + +// 0x4A5FA0 +FxInfoSize_c::FxInfoSize_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_SIZE_DATA; + m_InterpInfo.Allocate(4); +} + +// 0x4A6010 +void FxInfoSize_c::Load(FILESTREAM file, int32 version) { + if (version < 105) { + m_InterpInfo.m_nCount = 2; + } + m_InterpInfo.Load(file); +} + +// 0x4A6030 +void FxInfoSize_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[4]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + render.m_fSizeX = values[0]; + render.m_fSizeY = values[1]; + + if (m_InterpInfo.m_nCount == 4) { + render.m_fSizeXBias = values[2]; + render.m_fSizeYBias = values[3]; + } else { + render.m_fSizeXBias = 0.0f; + render.m_fSizeYBias = 0.0f; + } +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoSize.h b/source/game_sa/Fx/Blueprints/FxInfoSize.h new file mode 100644 index 0000000000..af041f58d0 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoSize.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoSize_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoSize_c(); + ~FxInfoSize_c() override = default; // 0x4A7650 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float delta, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoSmoke.cpp b/source/game_sa/Fx/Blueprints/FxInfoSmoke.cpp new file mode 100644 index 0000000000..dd2ca755e7 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoSmoke.cpp @@ -0,0 +1,35 @@ +#include "StdInc.h" + +#include "FxInfoSmoke.h" + +// 0x4A68E0 +FxInfoSmoke_c::FxInfoSmoke_c() : FxInfo_c() { + m_nType = FX_INFO_SMOKE_DATA; + m_InterpInfo.Allocate(8); +} + +// 0x4A6950 +void FxInfoSmoke_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A6970 +void FxInfoSmoke_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[8]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + + render.m_SmokeType = static_cast(values[0]); + render.m_SmokeColor.red = values[1]; + render.m_SmokeColor.green = values[2]; + render.m_SmokeColor.blue = values[3]; + render.m_SmokeColor.alpha = values[4]; + render.m_SmokeBrightness = values[5]; + render.m_SmokeSize = values[6]; + render.m_SmokeLife = values[7]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoSmoke.h b/source/game_sa/Fx/Blueprints/FxInfoSmoke.h new file mode 100644 index 0000000000..f9da866639 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoSmoke.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoU255.h" + +class FxInfoSmoke_c : public FxInfo_c { +protected: + FxInterpInfoU255_c m_InterpInfo; + +public: + FxInfoSmoke_c(); + ~FxInfoSmoke_c() override = default; // 0x4A7AB0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoSpriteRect.cpp b/source/game_sa/Fx/Blueprints/FxInfoSpriteRect.cpp new file mode 100644 index 0000000000..5b61b95d79 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoSpriteRect.cpp @@ -0,0 +1,30 @@ +#include "StdInc.h" + +#include "FxInfoSpriteRect.h" + +// 0x4A60B0 +FxInfoSpriteRect_c::FxInfoSpriteRect_c() : FxInfo_c() { + m_nType = FX_INFO_SPRITERECT_DATA; + m_InterpInfo.Allocate(4); +} + +// 0x4A6120 +void FxInfoSpriteRect_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A6140 +void FxInfoSpriteRect_c::GetValue(float currentTime, float mult, float totalTime, float len, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / len; + } + + float values[4]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + render.m_fSpriteTop = values[0]; + render.m_fSpriteBottom = values[1]; + render.m_fSpriteLeft = values[2]; + render.m_fSpriteRight = values[3]; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoSpriteRect.h b/source/game_sa/Fx/Blueprints/FxInfoSpriteRect.h new file mode 100644 index 0000000000..750a75febf --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoSpriteRect.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo255.h" + +class FxInfoSpriteRect_c : public FxInfo_c { +protected: + FxInterpInfo255_c m_InterpInfo; + +public: + FxInfoSpriteRect_c(); + ~FxInfoSpriteRect_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoTexCoords.h b/source/game_sa/Fx/Blueprints/FxInfoTexCoords.h new file mode 100644 index 0000000000..3b35af2e87 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoTexCoords.h @@ -0,0 +1,7 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoFloat.h" + +// From Manhunt +class FxInfoTexCoords_c : public FxInfo_c {}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoTrail.cpp b/source/game_sa/Fx/Blueprints/FxInfoTrail.cpp new file mode 100644 index 0000000000..4fa09ee74e --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoTrail.cpp @@ -0,0 +1,31 @@ +#include "StdInc.h" + +#include "FxInfoTrail.h" + +// 0x4A6220 +FxInfoTrail_c::FxInfoTrail_c() : FxInfo_c(), m_InterpInfo() { + m_nType = FX_INFO_TRAIL_DATA; + m_InterpInfo.Allocate(2); +} + +// 0x4A6290 +void FxInfoTrail_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A62B0 +void FxInfoTrail_c::GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / length; + } + + float values[2]; + m_InterpInfo.GetVal(values, mult); + + auto& render = *static_cast(info); + render.m_fTrailTime = values[0]; + render.m_nTrailScreenMode = 1; + if (values[1] > 0.1f) { + render.m_nTrailScreenMode = 2; + } +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoTrail.h b/source/game_sa/Fx/Blueprints/FxInfoTrail.h new file mode 100644 index 0000000000..92dca94b5f --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoTrail.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoU255.h" + +class FxInfoTrail_c : public FxInfo_c { +protected: + FxInterpInfoU255_c m_InterpInfo; + +public: + FxInfoTrail_c(); + ~FxInfoTrail_c() override = default; // 0x4A77A0 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoUnderwater.cpp b/source/game_sa/Fx/Blueprints/FxInfoUnderwater.cpp new file mode 100644 index 0000000000..183abba0c3 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoUnderwater.cpp @@ -0,0 +1,19 @@ +#include "StdInc.h" + +#include "FxInfoUnderwater.h" +#include "MovementInfo.h" + +// 0x4A5E20 +FxInfoUnderwater_c::FxInfoUnderwater_c() : FxInfo_c() { + m_nType = FX_INFO_UNDERWATER_DATA; + m_InterpInfo.Allocate(0); +} + +void FxInfoUnderwater_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A5E90 +void FxInfoUnderwater_c::GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) { + static_cast(info)->m_bHasUnderwaterInfo = true; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoUnderwater.h b/source/game_sa/Fx/Blueprints/FxInfoUnderwater.h new file mode 100644 index 0000000000..932a12ce16 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoUnderwater.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfoU255.h" + +class FxInfoUnderwater_c : public FxInfo_c { +protected: + FxInterpInfoU255_c m_InterpInfo; + +public: + FxInfoUnderwater_c(); + ~FxInfoUnderwater_c() override = default; // 0x4A7570 + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/FxInfoWind.cpp b/source/game_sa/Fx/Blueprints/FxInfoWind.cpp new file mode 100644 index 0000000000..2be3845ce3 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoWind.cpp @@ -0,0 +1,29 @@ +#include "StdInc.h" + +#include "FxInfoWind.h" +#include "MovementInfo.h" + +// 0x4A5A10 +FxInfoWind_c::FxInfoWind_c() : FxInfo_c() { + m_nType = FX_INFO_WIND_DATA; + m_InterpInfo.Allocate(1); +} + +// 0x4A5A80 +void FxInfoWind_c::Load(FILESTREAM file, int32 version) { + m_InterpInfo.Load(file); +} + +// 0x4A5AA0 +void FxInfoWind_c::GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) { + if (!m_bTimeModeParticle) { + mult = currentTime / length; + } + + float value; + m_InterpInfo.GetVal(&value, mult); + + auto& movement = *static_cast(info); + auto wind = value * *g_fxMan.m_pfWindSpeed * totalTime; + movement.m_Vel += wind * *g_fxMan.m_pWindDir; +} diff --git a/source/game_sa/Fx/Blueprints/FxInfoWind.h b/source/game_sa/Fx/Blueprints/FxInfoWind.h new file mode 100644 index 0000000000..fe5956304a --- /dev/null +++ b/source/game_sa/Fx/Blueprints/FxInfoWind.h @@ -0,0 +1,16 @@ +#pragma once + +#include "FxInfo.h" +#include "FxInterpInfo32.h" + +class FxInfoWind_c : public FxInfo_c { +protected: + FxInterpInfo32_c m_InterpInfo; + +public: + FxInfoWind_c(); + ~FxInfoWind_c() override = default; + + void Load(FILESTREAM file, int32 version) override; + void GetValue(float currentTime, float mult, float totalTime, float length, bool useConst, void* info) override; +}; diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo.cpp b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo.cpp new file mode 100644 index 0000000000..16be70cfbb --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo.cpp @@ -0,0 +1,9 @@ +#include "StdInc.h" + +#include "FxInterpInfo.h" + +// 0x4A8410 +FxInterpInfo_c::FxInterpInfo_c() { + m_bLooped = false; + m_pTimes = nullptr; +} diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo.h b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo.h new file mode 100644 index 0000000000..6b13c04402 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo.h @@ -0,0 +1,17 @@ +#pragma once + +#include "FxTools.h" + +class FxInterpInfo_c { +public: + bool m_bLooped; // (4 bytes in Manhunt) + int8 m_nNumKeys; + int16 m_nCount; + uint16* m_pTimes; + +public: + FxInterpInfo_c(); + ~FxInterpInfo_c() = default; // 0x4A8430 + + virtual void Load(FILESTREAM file) = 0; +}; diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo255.cpp b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo255.cpp new file mode 100644 index 0000000000..5eead2750c --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo255.cpp @@ -0,0 +1,42 @@ +#include "StdInc.h" + +#include "FxInterpInfo255.h" +#include "FxManager.h" + +// 0x4A8B50 +FxInterpInfo255_c::FxInterpInfo255_c() : FxInterpInfo_c() { + m_Keys = nullptr; +} + +// 0x5C1D30 +void FxInterpInfo255_c::Load(FILESTREAM file) { + for (auto i = 0; i < m_nCount; i++) { + ReadField(file); + ReadField(file, "FX_INTERP_DATA:"); + + m_bLooped = ReadField(file); + m_nNumKeys = ReadField(file); + + if (i == 0) { + m_pTimes = g_fxMan.Allocate(m_nNumKeys); + } + + m_Keys[i] = g_fxMan.Allocate(m_nNumKeys); + for (auto j = 0; j < m_nNumKeys; j++) { + ReadField(file, "FX_KEYFLOAT_DATA:"); + m_pTimes[j] = uint16(ReadField(file) * 256.f); + m_Keys[i][j] = uint16(ReadField(file) * 128.f); + } + } +} + +// NOTSA +void FxInterpInfo255_c::Allocate(int32 count) { + m_nCount = count; + m_Keys = g_fxMan.Allocate(count); +} + +// 0x4A8B80 +void FxInterpInfo255_c::GetVal(float* outValues, float delta) { + plugin::CallMethod<0x4A8B80, FxInterpInfo255_c*, float*, float>(this, outValues, delta); +} diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo255.h b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo255.h new file mode 100644 index 0000000000..b256df8c61 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo255.h @@ -0,0 +1,17 @@ +#pragma once + +#include "FxInterpInfo.h" + +class FxInterpInfo255_c : public FxInterpInfo_c { +protected: + int16** m_Keys; + +public: + FxInterpInfo255_c(); + ~FxInterpInfo255_c() = default; // 0x4A8B70 + + void Load(FILESTREAM file) override; + + void Allocate(int32 count); + void GetVal(float* outValues, float delta); +}; diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo32.cpp b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo32.cpp new file mode 100644 index 0000000000..cdb6686fcb --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo32.cpp @@ -0,0 +1,42 @@ +#include "StdInc.h" + +#include "FxInterpInfo32.h" +#include "FxManager.h" + +// 0x4A8990 +FxInterpInfo32_c::FxInterpInfo32_c() : FxInterpInfo_c() { + m_Keys = nullptr; +} + +// 0x5C1B10 +void FxInterpInfo32_c::Load(FILESTREAM file) { + for (auto i = 0; i < m_nCount; i++) { + ReadField(file); + ReadField(file, "FX_INTERP_DATA:"); + + m_bLooped = ReadField(file); + m_nNumKeys = ReadField(file); + + if (i == 0) { + m_pTimes = g_fxMan.Allocate(m_nNumKeys); + } + + m_Keys[i] = g_fxMan.Allocate(m_nNumKeys); + for (auto j = 0; j < m_nNumKeys; j++) { + ReadField(file, "FX_KEYFLOAT_DATA:"); + m_pTimes[j] = uint16(ReadField(file) * 256.f); + m_Keys[i][j] = int16(ReadField(file) * 1000.f); + } + } +} + +// NOTSA +void FxInterpInfo32_c::Allocate(int32 count) { + m_nCount = count; + m_Keys = g_fxMan.Allocate(count); +} + +// 0x4A89C0 +void FxInterpInfo32_c::GetVal(float* outValues, float delta) { + plugin::CallMethod<0x4A89C0, FxInterpInfo32_c*, float*, float>(this, outValues, delta); +} diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo32.h b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo32.h new file mode 100644 index 0000000000..c55bb2e705 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfo32.h @@ -0,0 +1,17 @@ +#pragma once + +#include "FxInterpInfo.h" + +class FxInterpInfo32_c : public FxInterpInfo_c { +protected: + int16** m_Keys; + +public: + FxInterpInfo32_c(); + ~FxInterpInfo32_c() = default; // 0x4A89B0 + + void Load(FILESTREAM file) override; + + void Allocate(int32 count); + void GetVal(float* outValues, float delta); +}; diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoFloat.cpp b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoFloat.cpp new file mode 100644 index 0000000000..b4cb3754f4 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoFloat.cpp @@ -0,0 +1,91 @@ +#include "StdInc.h" + +#include "FxInterpInfoFloat.h" +#include "FxManager.h" + +// 0x4A8440 +FxInterpInfoFloat_c::FxInterpInfoFloat_c() : FxInterpInfo_c() { + m_Keys = nullptr; +} + +// 0x5C16F0 +void FxInterpInfoFloat_c::Load(FILESTREAM file) { + for (auto i = 0; i < m_nCount; i++) { + ReadField(file); + ReadField(file, "FX_INTERP_DATA:"); + + m_bLooped = ReadField(file); + m_nNumKeys = ReadField(file); + + if (i == 0) { + m_pTimes = g_fxMan.Allocate(m_nNumKeys); + } + + m_Keys[i] = g_fxMan.Allocate(m_nNumKeys); + for (auto j = 0; j < m_nNumKeys; j++) { + ReadField(file, "FX_KEYFLOAT_DATA:"); + m_pTimes[j] = uint16(ReadField(file) * 256.f); + m_Keys[i][j] = ReadField(file); + } + } +} + +// NOTSA +void FxInterpInfoFloat_c::Allocate(int32 count) { + m_nCount = count; + m_Keys = g_fxMan.Allocate(count); +} + +// 0x4A85C0 +float FxInterpInfoFloat_c::GetVal(int32 attrib, float time, float deltaTime) { + return plugin::CallMethodAndReturn(this, attrib, time, deltaTime); +} + +// 0x4A8470 +void FxInterpInfoFloat_c::GetVal(float* outValues, float delta) { + return plugin::CallMethod<0x4A8470, FxInterpInfoFloat_c*, float*, float>(this, outValues, delta); + + if (m_nNumKeys == 1) { + for (auto i = 0; i < m_nCount; i++) { + outValues[i] = *m_Keys[i]; + } + return; + } + + if (m_bLooped) { + auto totalTime = (float)m_pTimes[m_nNumKeys - 1] / 256.0f; + delta = delta - (delta / totalTime) * totalTime; + } + + if (m_nNumKeys <= 1) { + for (auto j = 0; j < m_nCount; j++) { + outValues[j] = m_Keys[j][m_nNumKeys - 1]; + } + return; + } + + int v8 = 1; + float v11; + uint16* v10 = m_pTimes + 1; + while (true) { + v11 = (float)*v10 / 256.0f; + if (delta < v11) + break; + ++v8; + ++v10; + if (v8 >= m_nNumKeys) { + for (auto j = 0; j < m_nCount; j++) { + outValues[j] = m_Keys[j][m_nNumKeys - 1]; + } + return; + } + } + + int v13 = 0; + float v14 = (float)m_pTimes[v8 - 1] / 256.0f; + float a3a; + float* v15; + for (a3a = (delta - v14) / (v11 - v14); v13 < m_nCount; outValues[v13 - 1] = (v15[v8] - v15[v8 - 1]) * a3a + v15[v8 - 1]) { + v15 = m_Keys[v13++]; + } +} diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoFloat.h b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoFloat.h new file mode 100644 index 0000000000..8acea0beff --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoFloat.h @@ -0,0 +1,18 @@ +#pragma once + +#include "FxInterpInfo.h" + +class FxInterpInfoFloat_c : public FxInterpInfo_c { +protected: + float** m_Keys; + +public: + FxInterpInfoFloat_c(); + ~FxInterpInfoFloat_c() = default; // 0x4A8460 + + void Load(FILESTREAM file) override; + + void Allocate(int32 count); + float GetVal(int32 attrib, float time, float deltaTime); + void GetVal(float* outValues, float delta); +}; diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoU255.cpp b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoU255.cpp new file mode 100644 index 0000000000..8e87063944 --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoU255.cpp @@ -0,0 +1,42 @@ +#include "StdInc.h" + +#include "FxInterpInfoU255.h" +#include "FxManager.h" + +// 0x4A87D0 +FxInterpInfoU255_c::FxInterpInfoU255_c() : FxInterpInfo_c() { + m_Keys = nullptr; +} + +// 0x5C18F0 +void FxInterpInfoU255_c::Load(FILESTREAM file) { + for (auto i = 0; i < m_nCount; i++) { + ReadField(file); + ReadField(file, "FX_INTERP_DATA:"); + + m_bLooped = ReadField(file); + m_nNumKeys = ReadField(file); + + if (i == 0) { + m_pTimes = g_fxMan.Allocate(m_nNumKeys); + } + + m_Keys[i] = g_fxMan.Allocate(m_nNumKeys); + for (auto j = 0; j < m_nNumKeys; j++) { + ReadField(file, "FX_KEYFLOAT_DATA:"); + m_pTimes[j] = uint16(ReadField(file) * 256.f); + m_Keys[i][j] = uint16(ReadField(file) * 256.f); + } + } +} + +// NOTSA +void FxInterpInfoU255_c::Allocate(int32 count) { + m_nCount = count; + m_Keys = g_fxMan.Allocate(count); +} + +// 0x4A8800 +void FxInterpInfoU255_c::GetVal(float* outValues, float delta) { + plugin::CallMethod<0x4A8800, FxInterpInfoU255_c*, float*, float>(this, outValues, delta); +} diff --git a/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoU255.h b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoU255.h new file mode 100644 index 0000000000..3873491a1a --- /dev/null +++ b/source/game_sa/Fx/Blueprints/Interp/FxInterpInfoU255.h @@ -0,0 +1,17 @@ +#pragma once + +#include "FxInterpInfo.h" + +class FxInterpInfoU255_c : public FxInterpInfo_c { +protected: + uint16** m_Keys; + +public: + FxInterpInfoU255_c(); + ~FxInterpInfoU255_c() = default; // 0x4A87F0 + + void Load(FILESTREAM file) override; + + void Allocate(int32 count); + void GetVal(float* outValues, float delta); +}; diff --git a/source/game_sa/Fx/EmissionInfo.h b/source/game_sa/Fx/EmissionInfo.h new file mode 100644 index 0000000000..e4459027a9 --- /dev/null +++ b/source/game_sa/Fx/EmissionInfo.h @@ -0,0 +1,59 @@ +#pragma once + +#include "Vector.h" + +struct EmissionInfo_t { + float m_fCount; + float m_fRadius; + + CVector m_SizeMin; + CVector m_SizeMax; + + float m_fSpeed; + float m_fSpeedBias; + CVector m_Direction; + + float m_fAngleMin; + float m_fAngleMax; + + float m_fLife; + float m_fLifeBias; + CVector m_Pos; + + float m_fRotationMinAngle; + float m_fRotationMaxAngle; + + float m_fMinWind; + float m_fMaxWind; + float m_fMinRain; + float m_fMaxRain; + + void Process(float mult) { + m_fCount = mult * 10.0f; + m_fLife = 1.0f; + m_fRadius = 0.0f; + + m_fSpeed = 0.0f; + m_fSpeedBias = 0.0f; + + m_fAngleMin = 0.0f; + m_fAngleMax = 0.0f; + + m_fLifeBias = 0.0f; + + m_fRotationMinAngle = 0.0f; + m_fRotationMaxAngle = 0.0f; + + m_fMinWind = 0.0f; + m_fMaxWind = 2.0f; + + m_fMinRain = 0.0f; + m_fMaxRain = 2.0f; + + m_SizeMin.Set(0.0f, 0.0f, 0.0f); + m_SizeMax.Set(0.0f, 0.0f, 0.0f); + m_Direction.Set(0.0f, 0.0f, 1.0f); + m_Pos.Set(0.0f, 0.0f, 0.0f); + } +}; +VALIDATE_SIZE(EmissionInfo_t, 0x68); diff --git a/source/game_sa/Fx/Fx.cpp b/source/game_sa/Fx/Fx.cpp index 941191f83c..a6b0071bbb 100644 --- a/source/game_sa/Fx/Fx.cpp +++ b/source/game_sa/Fx/Fx.cpp @@ -9,8 +9,6 @@ #include "Fx.h" -#include "rwcore.h" // RxObjSpace3dVertex - static RxObjSpace3DVertex (&TempVertexBuffer)[4] = *(RxObjSpace3DVertex (*)[4])0xC4D958; Fx_c& g_fx = *(Fx_c*)0xA9AE00; @@ -19,57 +17,49 @@ void Fx_c::InjectHooks() { RH_ScopedClass(Fx_c); RH_ScopedCategory("Fx"); - RH_ScopedInstall(Constructor, 0x49E620, { .reversed = false }); - RH_ScopedInstall(Destructor, 0x49E630, { .reversed = false }); - RH_ScopedInstall(InitStaticSystems, 0x49E660); - RH_ScopedInstall(ExitStaticSystems, 0x49E850); - RH_ScopedInstall(InitEntitySystems, 0x49EA60); - RH_ScopedInstall(ExitEntitySystems, 0x4A12D0, { .reversed = false }); - RH_ScopedInstall(Init, 0x49EA90); - RH_ScopedInstall(Exit, 0x4A1320); - RH_ScopedInstall(Reset, 0x49EAE0); - RH_ScopedInstall(CreateEntityFx, 0x4A11E0, { .reversed = false }); - RH_ScopedInstall(DestroyEntityFx, 0x4A1280, { .reversed = false }); - RH_ScopedInstall(Update, 0x49E640, { .reversed = false }); - RH_ScopedInstall(Render, 0x49E650); - RH_ScopedInstall(CreateMatFromVec, 0x49E950, { .reversed = false }); - RH_ScopedInstall(SetFxQuality, 0x49EA40); - RH_ScopedInstall(GetFxQuality, 0x49EA50); - RH_ScopedInstall(AddBlood, 0x49EB00, { .reversed = false }); - RH_ScopedInstall(AddWood, 0x49EE10, { .reversed = false }); - RH_ScopedInstall(AddSparks, 0x49F040, { .reversed = false }); - RH_ScopedInstall(AddTyreBurst, 0x49F300, { .reversed = false }); - RH_ScopedInstall(AddBulletImpact, 0x49F3D0, { .reversed = false }); - RH_ScopedInstall(AddPunchImpact, 0x49F670, { .reversed = false }); - RH_ScopedInstall(AddDebris, 0x49F750, { .reversed = false }); - RH_ScopedInstall(AddGlass, 0x49F970, { .reversed = false }); - RH_ScopedInstall(AddWheelSpray, 0x49FB30, { .reversed = false }); - RH_ScopedInstall(AddWheelGrass, 0x49FF20, { .reversed = false }); - RH_ScopedInstall(AddWheelGravel, 0x4A0170, { .reversed = false }); - RH_ScopedInstall(AddWheelMud, 0x4A03C0, { .reversed = false }); - RH_ScopedInstall(AddWheelSand, 0x4A0610, { .reversed = false }); - RH_ScopedInstall(AddWheelDust, 0x4A09C0, { .reversed = false }); - RH_ScopedInstall(TriggerWaterHydrant, 0x4A0D70, { .reversed = false }); - RH_ScopedInstall(TriggerGunshot, 0x4A0DE0, { .reversed = false }); - RH_ScopedInstall(TriggerTankFire, 0x4A0FA0, { .reversed = false }); - RH_ScopedInstall(TriggerWaterSplash, 0x4A1070, { .reversed = false }); - RH_ScopedInstall(TriggerBulletSplash, 0x4A10E0, { .reversed = false }); - RH_ScopedInstall(TriggerFootSplash, 0x4A1150, { .reversed = false }); + // + RH_ScopedInstall(Constructor, 0x49E620); + // + RH_ScopedInstall(Destructor, 0x49E630); + // + RH_ScopedInstall(InitStaticSystems, 0x49E660); + // + RH_ScopedInstall(ExitStaticSystems, 0x49E850); + // + RH_ScopedInstall(InitEntitySystems, 0x49EA60); + // + RH_ScopedInstall(ExitEntitySystems, 0x4A12D0); + // + RH_ScopedInstall(Init, 0x49EA90); + // + RH_ScopedInstall(Exit, 0x4A1320); + // + RH_ScopedInstall(Reset, 0x49EAE0); + // + RH_ScopedInstall(CreateEntityFx, 0x4A11E0); + // + RH_ScopedInstall(DestroyEntityFx, 0x4A1280); + // + RH_ScopedInstall(Update, 0x49E640); + // + RH_ScopedInstall(Render, 0x49E650); + // RH_ScopedInstall(CreateMatFromVec, 0x49E950); + // + RH_ScopedInstall(SetFxQuality, 0x49EA40); + // + RH_ScopedInstall(GetFxQuality, 0x49EA50); + // RH_ScopedInstall(AddBlood, 0x49EB00); + // RH_ScopedInstall(AddWood, 0x49EE10); + // RH_ScopedInstall(AddSparks, 0x49F040); + // RH_ScopedInstall(AddTyreBurst, 0x49F300); + // RH_ScopedInstall(AddBulletImpact, 0x49F3D0); + // RH_ScopedInstall(AddPunchImpact, 0x49F670); + // RH_ScopedInstall(AddDebris, 0x49F750); + // RH_ScopedInstall(AddGlass, 0x49F970); + // RH_ScopedInstall(AddWheelSpray, 0x49FB30); + // RH_ScopedInstall(AddWheelGrass, 0x49FF20); + // RH_ScopedInstall(AddWheelGravel, 0x4A0170); + // RH_ScopedInstall(AddWheelMud, 0x4A03C0); + // RH_ScopedInstall(AddWheelSand, 0x4A0610); + // RH_ScopedInstall(AddWheelDust, 0x4A09C0); + // RH_ScopedInstall(TriggerWaterHydrant, 0x4A0D70); + // RH_ScopedInstall(TriggerGunshot, 0x4A0DE0); + // RH_ScopedInstall(TriggerTankFire, 0x4A0FA0); + // RH_ScopedInstall(TriggerWaterSplash, 0x4A1070); + // RH_ScopedInstall(TriggerBulletSplash, 0x4A10E0); + // RH_ScopedInstall(TriggerFootSplash, 0x4A1150); RH_ScopedGlobalInstall(RenderAddTri_, 0x4A1410); RH_ScopedGlobalInstall(RenderEnd, 0x4A1600); RH_ScopedGlobalInstall(RenderBegin, 0x4A13B0); } - -Fx_c* Fx_c::Constructor() { - this->Fx_c::Fx_c(); - return this; -} - -Fx_c* Fx_c::Destructor() { - this->Fx_c::~Fx_c(); - return this; -} +Fx_c* Fx_c::Constructor() { this->Fx_c::Fx_c(); return this; } +Fx_c* Fx_c::Destructor() { this->Fx_c::~Fx_c(); return this; } // 0x49E660 void Fx_c::InitStaticSystems() { @@ -115,23 +105,29 @@ void Fx_c::ExitStaticSystems() { } // 0x49EA60 -// unused void Fx_c::InitEntitySystems() { // NOP } // 0x4A12D0 void Fx_c::ExitEntitySystems() { - ((void(__thiscall*)(Fx_c*))0x4A12D0)(this); + for (auto it = m_FxEntities.GetHead(); it; it = m_FxEntities.GetNext(it)) { + m_FxEntities.RemoveItem(it); + g_fxMan.DestroyFxSystem(it->m_System); + delete it; + } } // 0x49EA90 void Fx_c::Init() { + ZoneScoped; + g_fxMan.Init(); g_fxMan.LoadFxProject("models\\effects.fxp"); g_fxMan.SetWindData(&CWeather::WindDir, &CWeather::Wind); InitStaticSystems(); - m_nBloodPoolsCount = 0; + InitEntitySystems(); + m_Randomizer = 0; } // 0x4A1320 @@ -145,31 +141,70 @@ void Fx_c::Exit() { void Fx_c::Reset() { g_fxMan.DestroyAllFxSystems(); InitStaticSystems(); + InitEntitySystems(); // NOTSA } // 0x4A11E0 -void Fx_c::CreateEntityFx(CEntity* entity, char* fxName, CVector* posn, RwMatrix* transform) { - ((void(__thiscall*)(Fx_c*, CEntity*, char*, CVector*, RwMatrix*))0x4A11E0)(this, entity, fxName, posn, transform); +void Fx_c::CreateEntityFx(CEntity* entity, const char* fxName, CVector* pos, RwMatrix* transform) { + // ((void(__thiscall*)(Fx_c*, CEntity*, char*, RwV3d*, RwMatrix*))0x4A11E0)(this, entity, fxName, pos transform); + + auto* particle = g_fxMan.CreateFxSystem(fxName, pos, transform, true); + if (particle) { + auto it = new FxEntitySystem(); + it->m_System = particle; + it->m_Entity = entity; + m_FxEntities.AddItem(it); + it->m_System->Play(); + } } // 0x4A1280 void Fx_c::DestroyEntityFx(CEntity* entity) { - ((void(__thiscall*)(Fx_c*, CEntity*))0x4A1280)(this, entity); + // ((void(__thiscall*)(Fx_c*, CEntity*))0x4A1280)(this, entity); + + for (auto it = m_FxEntities.GetHead(); it; it = m_FxEntities.GetNext(it)) { + if (it->m_Entity == entity) { + m_FxEntities.RemoveItem(it); + it->m_System->Kill(); + operator delete(it); + } + } } // 0x49E640 void Fx_c::Update(RwCamera* camera, float timeDelta) { - ((void(__thiscall*)(Fx_c*, RwCamera*, float))0x49E640)(this, camera, timeDelta); + ZoneScoped; + + g_fxMan.Update(camera, timeDelta); } // 0x49E650 void Fx_c::Render(RwCamera* camera, bool heatHaze) { + ZoneScoped; + g_fxMan.Render(camera, heatHaze); } // 0x49E950 -void Fx_c::CreateMatFromVec(RwMatrix* out, CVector* origin, CVector* direction) { - ((void(__thiscall*)(Fx_c*, RwMatrix*, CVector*, CVector*))0x49E950)(this, out, origin, direction); +void Fx_c::CreateMatFromVec(RwMatrix* out, const CVector* origin, const CVector* direction) { + ((void(__thiscall*)(Fx_c*, RwMatrix*, const CVector*, const CVector*))0x49E950)(this, out, origin, direction); + return; + + /* + RwMatrixSetIdentity(out); + RwV3dAssign(RwMatrixGetPos(out), origin); + RwV3dAssign(RwMatrixGetUp(out), direction); + RwV3dNormalize(&out->up, RwMatrixGetUp(out)); + + out->right.x = out->up.z * 0.0f - out->up.y * -1.0f; + out->right.y = out->up.x * -1.0f - out->up.z * 0.0f; + out->right.z = out->up.y * 0.0f - out->up.x * 0.0f; + + out->at.x = out->up.x * -1.0f - out->up.z * 0.0f * out->up.z - out->up.y * 0.0f - out->up.x * 0.0f * out->up.y; + out->at.y = out->up.y * 0.0f - out->up.x * 0.0f * out->up.x - out->right.x * out->up.z; + out->at.z = out->right.x * out->up.y - out->up.x * -1.0f - out->up.z * 0.0f * out->up.x; + + RwMatrixUpdate(out);*/ } // 0x49EA40 @@ -183,103 +218,103 @@ FxQuality_e Fx_c::GetFxQuality() const { } // 0x49EB00 -void Fx_c::AddBlood(Const CVector& origin, Const CVector& direction, int32 amount, float arg3) { - ((void(__thiscall*)(Fx_c*, Const CVector&, Const CVector&, int32, float))0x49EB00)(this, origin, direction, amount, arg3); +void Fx_c::AddBlood(const CVector& pos, const CVector& direction, int32 amount, float lightMult) { + ((void(__thiscall*)(Fx_c*, const CVector&, const CVector&, int32, float))0x49EB00)(this, pos, direction, amount, lightMult); } // 0x49EE10 -void Fx_c::AddWood(CVector& origin, CVector& direction, int32 amount, float arg3) { - ((void(__thiscall*)(Fx_c*, CVector&, CVector&, int32, float))0x49EE10)(this, origin, direction, amount, arg3); +void Fx_c::AddWood(CVector& pos, CVector& direction, int32 amount, float lightMult) { + ((void(__thiscall*)(Fx_c*, CVector&, CVector&, int32, float))0x49EE10)(this, pos, direction, amount, lightMult); } // 0x49F040 -void Fx_c::AddSparks(Const CVector& origin, Const CVector& direction, float force, int32 amount, CVector across, eSparkType sparksType, float spread, float life) { - ((void(__thiscall*)(Fx_c*, Const CVector&, Const CVector&, float, int32, CVector, uint8, float, float))0x49F040)(this, origin, direction, force, amount, across, sparksType, spread, life); +void Fx_c::AddSparks(const CVector& origin, const CVector& direction, float force, int32 amount, CVector across, eSparkType sparksType, float spread, float life) { + ((void(__thiscall*)(Fx_c*, const CVector&, const CVector&, float, int32, CVector, uint8, float, float))0x49F040)(this, origin, direction, force, amount, across, sparksType, spread, life); } // 0x49F300 -void Fx_c::AddTyreBurst(CVector& posn, CVector& velocity) { - ((void(__thiscall*)(Fx_c*, CVector&, CVector&))0x49F300)(this, posn, velocity); +void Fx_c::AddTyreBurst(const CVector& posn, const CVector& velocity) { + ((void(__thiscall*)(Fx_c*, const CVector&, const CVector&))0x49F300)(this, posn, velocity); } // 0x49F3D0 -void Fx_c::AddBulletImpact(CVector& posn, CVector& direction, int32 bulletFxType, int32 amount, float arg4) { - ((void(__thiscall*)(Fx_c*, CVector&, CVector&, int32, int32, float))0x49F3D0)(this, posn, direction, bulletFxType, amount, arg4); +void Fx_c::AddBulletImpact(const CVector& posn, const CVector& direction, int32 bulletFxType, int32 amount, float arg4) { + ((void(__thiscall*)(Fx_c*, const CVector&, const CVector&, int32, int32, float))0x49F3D0)(this, posn, direction, bulletFxType, amount, arg4); } // 0x49F670 -void Fx_c::AddPunchImpact(CVector& posn, CVector& velocity, int32 arg2) { - ((void(__thiscall*)(Fx_c*, CVector&, CVector&, int32))0x49F670)(this, posn, velocity, arg2); +void Fx_c::AddPunchImpact(CVector& pos, CVector& velocity, int32 num) { + ((void(__thiscall*)(Fx_c*, CVector&, CVector&, int32))0x49F670)(this, pos, velocity, num); } // 0x49F750 -void Fx_c::AddDebris(CVector& posn, RwRGBA& color, float scale, int32 amount) { - ((void(__thiscall*)(Fx_c*, CVector&, RwRGBA&, float, int32))0x49F750)(this, posn, color, scale, amount); +void Fx_c::AddDebris(CVector& pos, RwRGBA& color, float scale, int32 amount) { + ((void(__thiscall*)(Fx_c*, CVector&, RwRGBA&, float, int32))0x49F750)(this, pos, color, scale, amount); } // 0x49F970 -void Fx_c::AddGlass(CVector& posn, RwRGBA& color, float scale, int32 amount) { - ((void(__thiscall*)(Fx_c*, CVector&, RwRGBA&, float, int32))0x49F970)(this, posn, color, scale, amount); +void Fx_c::AddGlass(CVector& pos, RwRGBA& color, float scale, int32 amount) { + ((void(__thiscall*)(Fx_c*, CVector&, RwRGBA&, float, int32))0x49F970)(this, pos, color, scale, amount); } // 0x49FB30 -void Fx_c::AddWheelSpray(CVehicle* vehicle, CVector posn, uint8 arg2, uint8 arg3, float arg4) { - ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, uint8, float))0x49FB30)(this, vehicle, posn, arg2, arg3, arg4); +void Fx_c::AddWheelSpray(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, bool bInWater, float lightMult) { + ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, uint8, float))0x49FB30)(this, vehicle, pos, bWheelsSpinning, bInWater, lightMult); } // 0x49FF20 -void Fx_c::AddWheelGrass(CVehicle* vehicle, CVector posn, uint8 arg2, float arg3) { - ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x49FF20)(this, vehicle, posn, arg2, arg3); +void Fx_c::AddWheelGrass(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult) { + ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x49FF20)(this, vehicle, pos, bWheelsSpinning, lightMult); } // 0x4A0170 -void Fx_c::AddWheelGravel(CVehicle* vehicle, CVector posn, uint8 arg2, float arg3) { - ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x4A0170)(this, vehicle, posn, arg2, arg3); +void Fx_c::AddWheelGravel(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult) { + ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x4A0170)(this, vehicle, pos, bWheelsSpinning, lightMult); } // 0x4A03C0 -void Fx_c::AddWheelMud(CVehicle* vehicle, CVector posn, uint8 arg2, float arg3) { - ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x4A03C0)(this, vehicle, posn, arg2, arg3); +void Fx_c::AddWheelMud(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult) { + ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x4A03C0)(this, vehicle, pos, bWheelsSpinning, lightMult); } // 0x4A0610 -void Fx_c::AddWheelSand(CVehicle* vehicle, CVector posn, uint8 arg2, float arg3) { - ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x4A0610)(this, vehicle, posn, arg2, arg3); +void Fx_c::AddWheelSand(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult) { + ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x4A0610)(this, vehicle, pos, bWheelsSpinning, lightMult); } // 0x4A09C0 -void Fx_c::AddWheelDust(CVehicle* vehicle, CVector posn, uint8 arg2, float arg3) { - ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x4A09C0)(this, vehicle, posn, arg2, arg3); +void Fx_c::AddWheelDust(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult) { + ((void(__thiscall*)(Fx_c*, CVehicle*, CVector, uint8, float))0x4A09C0)(this, vehicle, pos, bWheelsSpinning, lightMult); } // 0x4A0D70 -void Fx_c::TriggerWaterHydrant(CVector& posn) { - ((void(__thiscall*)(Fx_c*, CVector&))0x4A0D70)(this, posn); +void Fx_c::TriggerWaterHydrant(CVector& pos) { + ((void(__thiscall*)(Fx_c*, CVector&))0x4A0D70)(this, pos); } // 0x4A0DE0 -void Fx_c::TriggerGunshot(CEntity* entity, const CVector& origin, const CVector& direction, bool doGunflash) { - ((void(__thiscall*)(Fx_c*, CEntity*, const CVector&, const CVector&, bool))0x4A0DE0)(this, entity, origin, direction, doGunflash); +void Fx_c::TriggerGunshot(CEntity* entity, const CVector& origin, const CVector& target, bool doGunflash) { + ((void(__thiscall*)(Fx_c*, CEntity*, const CVector&, const CVector&, bool))0x4A0DE0)(this, entity, origin, target, doGunflash); } // 0x4A0FA0 -void Fx_c::TriggerTankFire(CVector& origin, CVector& target) { - ((void(__thiscall*)(Fx_c*, CVector&, CVector&))0x4A0FA0)(this, origin, target); +void Fx_c::TriggerTankFire(CVector& pos, CVector& dir) { + ((void(__thiscall*)(Fx_c*, CVector&, CVector&))0x4A0FA0)(this, pos, dir); } // 0x4A1070 -void Fx_c::TriggerWaterSplash(CVector& posn) { - ((void(__thiscall*)(Fx_c*, CVector&))0x4A1070)(this, posn); +void Fx_c::TriggerWaterSplash(CVector& pos) { + ((void(__thiscall*)(Fx_c*, CVector&))0x4A1070)(this, pos); } // 0x4A10E0 -void Fx_c::TriggerBulletSplash(CVector& posn) { - ((void(__thiscall*)(Fx_c*, CVector&))0x4A10E0)(this, posn); +void Fx_c::TriggerBulletSplash(CVector& pos) { + ((void(__thiscall*)(Fx_c*, CVector&))0x4A10E0)(this, pos); } // 0x4A1150 -void Fx_c::TriggerFootSplash(CVector& posn) { - ((void(__thiscall*)(Fx_c*, CVector&))0x4A1150)(this, posn); +void Fx_c::TriggerFootSplash(CVector& pos) { + ((void(__thiscall*)(Fx_c*, CVector&))0x4A1150)(this, pos); } // see RwIm3DTransformFlags @@ -377,12 +412,11 @@ void RenderEnd() { } // 0x4A1660 -void RotateVecIntoVec(RwV3d* vectorsOut, RwV3d* vectorsIn, RwV3d* dir) { - ((void(__cdecl*)(RwV3d*, RwV3d*, RwV3d*))0x4A1660)(vectorsOut, vectorsIn, dir); +void RotateVecIntoVec(RwV3d* vecRes, RwV3d* vec, RwV3d* vecAlign) { + ((void(__cdecl*)(RwV3d*, RwV3d*, RwV3d*))0x4A1660)(vecRes, vec, vecAlign); } // 0x4A1780 -void RotateVecAboutVec(RwV3d* out, RwV3d* arg1, RwV3d* arg2, float angle) { - ((void(__cdecl*)(RwV3d*, RwV3d*, RwV3d*, float))0x4A1780)(out, arg1, arg2, angle); +void RotateVecAboutVec(RwV3d* vecRes, RwV3d* vec, RwV3d* axis, float angle) { + ((void(__cdecl*)(RwV3d*, RwV3d*, RwV3d*, float))0x4A1780)(vecRes, vec, axis, angle); } - diff --git a/source/game_sa/Fx/Fx.h b/source/game_sa/Fx/Fx.h index c602474322..cf7ff7fd6d 100644 --- a/source/game_sa/Fx/Fx.h +++ b/source/game_sa/Fx/Fx.h @@ -24,7 +24,7 @@ enum eSparkType : uint8 { }; // original name unknown -struct FxEntitySystem : public ListItem_c { +struct FxEntitySystem : public ListItem_c { FxSystem_c* m_System; CEntity* m_Entity; }; @@ -49,7 +49,8 @@ class Fx_c { FxSystem_c* m_WheelDirt; FxSystem_c* m_Glass; TList_c m_FxEntities; - uint32 m_nBloodPoolsCount; + uint32 m_Randomizer; + FxQuality_e m_FxQuality; uint32 m_nVerticesCount2; uint32 m_nVerticesCount; @@ -76,29 +77,29 @@ class Fx_c { void Exit(); void Reset(); - void CreateEntityFx(CEntity* entity, char* fxName, CVector* posn, RwMatrix* transform); + void CreateEntityFx(CEntity* entity, const char* fxName, CVector* pos, RwMatrix* transform); void DestroyEntityFx(CEntity* entity); void Update(RwCamera* camera, float timeDelta); void Render(RwCamera* camera, bool heatHaze); - void CreateMatFromVec(RwMatrix* out, CVector* origin, CVector* direction); + void CreateMatFromVec(RwMatrix* out, const CVector* origin, const CVector* direction); void SetFxQuality(FxQuality_e quality); [[nodiscard]] FxQuality_e GetFxQuality() const; - void AddBlood(Const CVector& origin, Const CVector& direction, int32 amount, float arg3); + void AddBlood(const CVector& origin, const CVector& direction, int32 amount, float arg3); void AddWood(CVector& origin, CVector& direction, int32 amount, float arg3); - void AddSparks(Const CVector& origin, Const CVector& direction, float force, int32 amount, CVector across, eSparkType sparksType, float spread, float life); - void AddTyreBurst(CVector& posn, CVector& velocity); - void AddBulletImpact(CVector& posn, CVector& direction, int32 bulletFxType, int32 amount, float arg4); + void AddSparks(const CVector& origin, const CVector& direction, float force, int32 amount, CVector across, eSparkType sparksType, float spread, float life); + void AddTyreBurst(const CVector& posn, const CVector& velocity); + void AddBulletImpact(const CVector& posn, const CVector& direction, int32 bulletFxType, int32 amount, float arg4); void AddPunchImpact(CVector& posn, CVector& velocity, int32 arg2); void AddDebris(CVector& posn, RwRGBA& color, float scale, int32 amount); void AddGlass(CVector& posn, RwRGBA& color, float scale, int32 amount); - void AddWheelSpray(CVehicle* vehicle, CVector posn, uint8 arg2, uint8 arg3, float arg4); - void AddWheelGrass(CVehicle* vehicle, CVector posn, uint8 arg2, float brightness); - void AddWheelGravel(CVehicle* vehicle, CVector posn, uint8 arg2, float brightness); - void AddWheelMud(CVehicle* vehicle, CVector posn, uint8 arg2, float brightness); - void AddWheelSand(CVehicle* vehicle, CVector posn, uint8 arg2, float brightness); - void AddWheelDust(CVehicle* vehicle, CVector posn, uint8 arg2, float brightness); + void AddWheelSpray(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, bool bInWater, float lightMult); + void AddWheelGrass(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult); + void AddWheelGravel(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult); + void AddWheelMud(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult); + void AddWheelSand(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult); + void AddWheelDust(CVehicle* vehicle, CVector pos, bool bWheelsSpinning, float lightMult); void TriggerWaterHydrant(CVector& posn); void TriggerGunshot(CEntity* entity, const CVector& origin, const CVector& target, bool doGunflash); void TriggerTankFire(CVector& origin, CVector& target); @@ -106,7 +107,6 @@ class Fx_c { void TriggerBulletSplash(CVector& posn); void TriggerFootSplash(CVector& posn); }; - VALIDATE_SIZE(Fx_c, 0x70); void RenderBegin(RwRaster* raster, RwMatrix* transform, uint32 transformRenderFlags); @@ -128,7 +128,7 @@ void RenderAddTri( const CRGBA& color1, const CRGBA& color2, const CRGBA& color3 ); void RenderEnd(); -void RotateVecIntoVec(RwV3d* vectorsOut, RwV3d* vectorsIn, RwV3d* dir); -void RotateVecAboutVec(RwV3d* out, RwV3d* arg1, RwV3d* arg2, float angle); +void RotateVecIntoVec(RwV3d* vecRes, RwV3d* vec, RwV3d* vecAlign); +void RotateVecAboutVec(RwV3d* vecRes, RwV3d* vec, RwV3d* axis, float angle); extern Fx_c& g_fx; diff --git a/source/game_sa/Fx/FxBox.h b/source/game_sa/Fx/FxBox.h index 2a327605c9..62d6d29d80 100644 --- a/source/game_sa/Fx/FxBox.h +++ b/source/game_sa/Fx/FxBox.h @@ -1,26 +1,36 @@ -/* - Plugin-SDK file - Authors: GTA Community. See more here - https://github.com/DK22Pac/plugin-sdk - Do not delete this comment block. Respect others' work! -*/ #pragma once #include "RenderWare.h" class FxBox_c { public: - float m_fCornerA_x; - float m_fCornerB_x; - float m_fCornerA_y; - float m_fCornerB_y; - float m_fCornerA_z; - float m_fCornerB_z; + float minX; + float maxX; + float minY; + float maxY; + float minZ; + float maxZ; - inline FxBox_c() { - m_fCornerA_x = m_fCornerA_y = m_fCornerA_z = 999999.0f; - m_fCornerB_x = m_fCornerB_y = m_fCornerB_z = -999999.0f; + FxBox_c() { + Reset(); } -}; -VALIDATE_SIZE(FxBox_c, 0x18); \ No newline at end of file + void Reset() { + minX = minY = minZ = 999999.0f; + maxX = maxY = maxZ = -999999.0f; + } + + FxBox_c& operator=(const FxBox_c& right) = default; + + FxBox_c& operator=(const RwMatrix& right) { + minX = right.pos.x; + minY = right.pos.y; + minZ = right.pos.z; + + maxX = right.pos.x; + maxY = right.pos.y; + maxZ = right.pos.z; + return *this; + } +}; +VALIDATE_SIZE(FxBox_c, 0x18); diff --git a/source/game_sa/Fx/FxEmitter.cpp b/source/game_sa/Fx/FxEmitter.cpp new file mode 100644 index 0000000000..3fdcdcc44b --- /dev/null +++ b/source/game_sa/Fx/FxEmitter.cpp @@ -0,0 +1,281 @@ +#include "StdInc.h" + +#include "FxEmitter.h" +#include "EmissionInfo.h" +#include "FxEmitterPrt.h" +#include "FxPrimBP.h" +#include "FxEmitterBP.h" +#include "FxEmitterPrt.h" + +void FxEmitter_c::InjectHooks() { + RH_ScopedClass(FxEmitter_c); + RH_ScopedCategory("Fx"); + + // RH_ScopedInstall(Init_Reversed, 0x4A2550); + // RH_ScopedInstall(Update_Reversed, 0x4A4460); // bad + // RH_ScopedInstall(Reset_Reversed, 0x4A2570); + // RH_ScopedOverloadedInstall(AddParticle_Reversed, "matrix", 0x4A3EA0, void(FxEmitter_c::*)(RwMatrix*, CVector*, float, FxPrtMult_c*, float, float, bool)); + // RH_ScopedOverloadedInstall(AddParticle_Reversed, "", 0x4A4050, void(FxEmitter_c::*)(CVector*, CVector*, float, FxPrtMult_c*, float, float, bool)); + // RH_ScopedInstall(CreateParticles, 0x4A41E0); + // RH_ScopedInstall(CreateParticle, 0x4A2580); +} + +bool FxEmitter_c::Init(FxPrimBP_c* primBP, FxSystem_c* system) { return Init_Reversed(primBP, system); } +void FxEmitter_c::Update(float currentTime, float deltaTime) { Update_Reversed(currentTime, deltaTime); } +void FxEmitter_c::Reset() { Reset_Reversed(); } +void FxEmitter_c::AddParticle(RwMatrix* mat, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal) { AddParticle_Reversed(mat, vel, timeSince, fxMults, rotZ, brightness, createLocal); } +void FxEmitter_c::AddParticle(CVector* pos, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal) { AddParticle_Reversed(pos, vel, timeSince, fxMults, rotZ, brightness, createLocal); } + +// 0x4A2550 +bool FxEmitter_c::Init_Reversed(FxPrimBP_c* primBP, FxSystem_c* system) { + m_PrimBP = primBP; + m_System = system; + m_fEmissionIntensity = 0.0f; + return true; +} + +// 0x4A4460 +void FxEmitter_c::Update_Reversed(float currentTime, float deltaTime) { + if (m_bEnabled && !m_System->m_stopParticleCreation) { + CreateParticles(currentTime, deltaTime); + } +} + +// 0x4A2570 +void FxEmitter_c::Reset_Reversed() { + m_fEmissionIntensity = 0.0f; +} + +// 0x4A3EA0 +void FxEmitter_c::AddParticle_Reversed(CVector* pos, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal) { + return plugin::CallMethod<0x4A3EA0, FxEmitter_c*, CVector*, CVector*, float, FxPrtMult_c*, float, float, bool>(this, pos, vel, timeSince, fxMults, rotZ, brightness, + createLocal); + + // todo: + EmissionInfo_t emission; + m_PrimBP->m_FxInfoManager.ProcessEmissionInfo(0.0f, 0.0f, m_System->m_SystemBP->m_fLength, m_System->m_UseConstTime, &emission); + + auto mat1 = g_fxMan.FxRwMatrixCreate(); + auto mat0 = g_fxMan.FxRwMatrixCreate(); + auto mat = g_fxMan.FxRwMatrixCreate(); + + RwMatrixSetIdentity(mat); + RwV3dAssign(RwMatrixGetPos(mat), pos); + + RwMatrixUpdate(mat); + if (m_System->m_ParentMatrix) { + RwMatrixMultiply(mat0, mat, m_System->m_ParentMatrix); + } + else { + mat0 = mat; + } + + auto mat2 = g_fxMan.FxRwMatrixCreate(); + m_PrimBP->GetRWMatrix(*mat2); + RwMatrixMultiply(mat1, mat2, mat0); + g_fxMan.FxRwMatrixDestroy(mat2); + + if (auto* particle = CreateParticle(&emission, mat1, vel, timeSince, fxMults, brightness, createLocal)) { + if (rotZ >= 0.0f) { + particle->m_RotZ = (uint8)(rotZ / 2.0f); // todo: int8/uint8 conversion + } + } + + g_fxMan.FxRwMatrixDestroy(mat); + g_fxMan.FxRwMatrixDestroy(mat0); + g_fxMan.FxRwMatrixDestroy(mat1); +} + +// 0x4A4050 +void FxEmitter_c::AddParticle_Reversed(RwMatrix* mat, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal) { + return plugin::CallMethod<0x4A4050, FxEmitter_c*, RwMatrix*, CVector*, float, FxPrtMult_c*, float, float, bool>(this, mat, vel, timeSince, fxMults, rotZ, brightness, + createLocal); + + // todo: + EmissionInfo_t emission; + m_PrimBP->m_FxInfoManager.ProcessEmissionInfo(0.0f, 0.0f, m_System->m_SystemBP->m_fLength, m_System->m_UseConstTime, &emission); + auto mat2 = g_fxMan.FxRwMatrixCreate(); + auto mat1 = g_fxMan.FxRwMatrixCreate(); + auto mat0 = g_fxMan.FxRwMatrixCreate(); + RwMatrixSetIdentity(mat0); + mat0 = mat; + + RwMatrixUpdate(mat0); + if (m_System->m_ParentMatrix) { + RwMatrixMultiply(mat1, mat0, m_System->m_ParentMatrix); + } else { + mat1 = mat0; + } + + auto mat3 = g_fxMan.FxRwMatrixCreate(); + m_PrimBP->GetRWMatrix(*mat3); + RwMatrixMultiply(mat2, mat3, mat1); + g_fxMan.FxRwMatrixDestroy(mat3); + + if (auto* particle = CreateParticle(&emission, mat2, vel, timeSince, fxMults, brightness, createLocal)) { + if (rotZ >= 0.0f) + particle->m_RotZ = (uint8)(rotZ * 2.0f); // todo:: see above + } + + g_fxMan.FxRwMatrixDestroy(mat0); + g_fxMan.FxRwMatrixDestroy(mat1); + g_fxMan.FxRwMatrixDestroy(mat2); +} + +// 0x4A41E0 +void FxEmitter_c::CreateParticles(float currentTime, float deltaTime) { + return plugin::CallMethod<0x4A41E0, FxEmitter_c*, float, float>(this, currentTime, deltaTime); + + EmissionInfo_t emission; + m_PrimBP->m_FxInfoManager.ProcessEmissionInfo(currentTime, deltaTime, m_System->m_SystemBP->m_fLength, m_System->m_UseConstTime, &emission); + + auto lodStart = (float)m_PrimBP->m_FxInfoManager.m_nLodStart / 64.0f; + auto lodEnd = (float)m_PrimBP->m_FxInfoManager.m_nLodEnd / 64.0f; + + float visibility; + if (m_System->m_fCameraDistance >= lodStart) { + if (m_System->m_fCameraDistance <= lodEnd) { + visibility = 1.0f - (m_System->m_fCameraDistance - lodStart) / (lodEnd - lodStart); + } else { + visibility = 0.0f; + } + } else { + visibility = 1.0f; + } + + m_fEmissionIntensity += emission.m_fCount * visibility * ((float)m_System->m_nRateMult * 1000.0f); + if ( CWeather::Wind >= emission.m_fMinWind + && CWeather::Wind <= emission.m_fMaxWind + && CWeather::Rain >= emission.m_fMinRain + && CWeather::Rain <= emission.m_fMaxRain + && m_fEmissionIntensity >= 1.0f + ) { + auto mat = g_fxMan.FxRwMatrixCreate(); + auto mat1 = g_fxMan.FxRwMatrixCreate(); + RwMatrixUpdate(&m_System->m_LocalMatrix); + if (m_System->m_ParentMatrix) { + RwMatrixMultiply(mat1, &m_System->m_LocalMatrix, m_System->m_ParentMatrix); + } else { + *mat1 = m_System->m_LocalMatrix; + } + + auto mat2 = g_fxMan.FxRwMatrixCreate(); + m_PrimBP->GetRWMatrix(*mat2); + RwMatrixMultiply(mat, mat2, mat1); + g_fxMan.FxRwMatrixDestroy(mat2); + + auto counter = 0u; + for (auto step = 0u; counter < (uint32)m_fEmissionIntensity; step = counter) { + FxPrtMult_c prtMult; + auto timeSince = (float)step / m_fEmissionIntensity * deltaTime; + CreateParticle(&emission, mat, nullptr, timeSince, &prtMult, 1.2f, m_System->m_createLocal); + counter++; + } + m_fEmissionIntensity -= m_fEmissionIntensity; // OG shit + + g_fxMan.FxRwMatrixDestroy(mat1); + g_fxMan.FxRwMatrixDestroy(mat); + } +} + +// 0x4A2580 +FxEmitterPrt_c* FxEmitter_c::CreateParticle(EmissionInfo_t* emissionInfo, RwMatrix* wldMat, CVector* velOverride, float timeSince, FxPrtMult_c* fxMults, float brightness, bool createLocal) { + //return plugin::CallMethodAndReturn(this, emissionInfo, wldMat, velOverride, timeSince, fxMults, brightness, createLocal); + + // todo: + auto* particle = [&]() -> FxEmitterPrt_c* { + auto* prt = g_fxMan.GetParticle(0); + if (!prt) { + if (!m_System->m_MustCreateParticles) + return nullptr; + + g_fxMan.FreeUpParticle(); + prt = g_fxMan.GetParticle(0); + } + return reinterpret_cast(prt); + }(); + + particle->m_fCurrentLife = 0.0f; + particle->m_fTotalLife = (((float)(CGeneral::GetRandomNumber() % 10'000) / 5'000.0f - 1.0f) * emissionInfo->m_fLifeBias + emissionInfo->m_fLife) * fxMults->m_fLife; + particle->m_System = m_System; + + particle->m_MultColor = CRGBA{fxMults->m_Color}; + + particle->m_MultSize = fxMults->m_fSize; + particle->m_MultRot = fxMults->m_Rot; + + particle->m_bLocalToSystem = createLocal; + + particle->m_RandR = CGeneral::GetRandomNumberInRange(0, 256); + particle->m_RandG = CGeneral::GetRandomNumberInRange(0, 256); + particle->m_RandB = CGeneral::GetRandomNumberInRange(0, 256); + particle->m_Brightness = brightness; + + particle->m_RotZ = -1; + particle->m_CurrentRotation = CGeneral::GetRandomNumberInRange(0.0f, 1.0f) * (emissionInfo->m_fRotationMaxAngle - emissionInfo->m_fRotationMinAngle) + emissionInfo->m_fRotationMinAngle; + + if (createLocal) { + m_PrimBP->GetRWMatrix(*wldMat); + } + + CVector vec; + if (approxEqual(emissionInfo->m_fRadius, 0.0f, 0.001f)) { + vec = CVector{ + CGeneral::GetRandomNumberInRange(0.0f, 1.0f) * (emissionInfo->m_SizeMax.x - emissionInfo->m_SizeMin.x) + emissionInfo->m_SizeMin.x, + CGeneral::GetRandomNumberInRange(0.0f, 1.0f) * (emissionInfo->m_SizeMax.y - emissionInfo->m_SizeMin.y) + emissionInfo->m_SizeMin.y, + CGeneral::GetRandomNumberInRange(0.0f, 1.0f) * (emissionInfo->m_SizeMax.z - emissionInfo->m_SizeMin.z) + emissionInfo->m_SizeMin.z, + }; + } else { + vec = CVector{ + CGeneral::GetRandomNumberInRange(0.0f, 2.0f) - 1.0f, + CGeneral::GetRandomNumberInRange(0.0f, 2.0f) - 1.0f, + CGeneral::GetRandomNumberInRange(0.0f, 2.0f) - 1.0f, + }; + + float radius; + auto invDist = 1.0f / vec.Magnitude(); + if (emissionInfo->m_fRadius < 0.0f) { // todo: check comp. + radius = invDist * emissionInfo->m_fRadius; + } else { + radius = (invDist * CGeneral::GetRandomNumberInRange(0.0f, 1.0f)) * emissionInfo->m_fRadius; + } + vec *= radius; + } + vec += emissionInfo->m_Pos; + + particle->m_Pos = vec.z * wldMat->at + vec.y * wldMat->up + vec.x * wldMat->right + wldMat->pos; + + if (velOverride) { + particle->m_Velocity = *velOverride; + } else { + auto index = CGeneral::GetRandomNumberInRange(0.0f, TWO_PI); + auto minAngle = DegreesToRadians(emissionInfo->m_fAngleMin); + auto maxAngle = DegreesToRadians(emissionInfo->m_fAngleMax); + + auto sinY = (CGeneral::GetRandomNumberInRange(0.0f, 1.0f) * (maxAngle - minAngle) + minAngle) * 40.743664f; + + CVector v37; + v37.x = CMaths::ms_SinTable[(uint8)((index * 40.743664f) + 64.0f) + 1] * CMaths::ms_SinTable[(uint8)sinY + 1]; + v37.y = CMaths::ms_SinTable[(uint8)(sinY + 64.0f) + 1]; + v37.z = CMaths::ms_SinTable[(uint8)(index * 40.743664f) + 1] * CMaths::ms_SinTable[(uint8)sinY + 1]; + + CVector vectorsIn; + CVector vectorsOut; + if (emissionInfo->m_Direction.x <= 10.0f) { + vectorsIn = emissionInfo->m_Direction; + vectorsIn.Normalise(); + } else { + vectorsIn = particle->m_Pos; + } + RwV3dTransformVectors(&vectorsOut, &vectorsIn, 1, wldMat); + CVector v38; + RotateVecIntoVec(&v38, &v37, &vectorsOut); + particle->m_Velocity = v38 * ((CGeneral::GetRandomNumberInRange(0.0f, 2.0f) - 1.0f) * emissionInfo->m_fSpeedBias + emissionInfo->m_fSpeed); + } + + particle->m_Velocity += m_System->m_VelAdd; + static_cast(m_PrimBP)->UpdateParticle(timeSince, particle); + m_PrimBP->m_Particles.AddItem(particle); + + return particle; +} diff --git a/source/game_sa/Fx/FxEmitter.h b/source/game_sa/Fx/FxEmitter.h new file mode 100644 index 0000000000..aed191b45a --- /dev/null +++ b/source/game_sa/Fx/FxEmitter.h @@ -0,0 +1,30 @@ +#pragma once + +#include "FxPrim.h" + +struct EmissionInfo_t; + +class FxEmitter_c : public FxPrim_c { +public: + float m_fEmissionIntensity; + +public: + FxEmitter_c() = default; // 0x4A1920 + ~FxEmitter_c() override = default; // 0x4A2BB0 + + bool Init(FxPrimBP_c* primBP, FxSystem_c* system) override; + void Update(float currentTime, float deltaTime) override; + void Reset() override; + void AddParticle(RwMatrix* mat, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal) override; + void AddParticle(CVector* pos, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal) override; + void CreateParticles(float currentTime, float deltaTime); + FxEmitterPrt_c* CreateParticle(EmissionInfo_t* emissionInfo, RwMatrix* wldMat, CVector* velOverride, float timeSince, FxPrtMult_c* fxMults, float brightness, bool createLocal); + + static void InjectHooks(); + bool Init_Reversed(FxPrimBP_c* primBP, FxSystem_c* system); + void Update_Reversed(float currentTime, float deltaTime); + void Reset_Reversed(); + void AddParticle_Reversed(RwMatrix* mat, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal); + void AddParticle_Reversed(CVector* pos, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal); +}; +VALIDATE_SIZE(FxEmitter_c, 0x14); diff --git a/source/game_sa/Fx/FxEmitterBP.cpp b/source/game_sa/Fx/FxEmitterBP.cpp new file mode 100644 index 0000000000..e2095cf917 --- /dev/null +++ b/source/game_sa/Fx/FxEmitterBP.cpp @@ -0,0 +1,192 @@ +#include "StdInc.h" + +#include "FxEmitterBP.h" +#include "FxEmitter.h" +#include "FxPrimBP.h" +#include "FxEmitterPrt.h" +#include "FxInfo.h" +#include "FxInfoManager.h" + +#include "Particle.h" +#include "FxTools.h" + +void FxEmitterBP_c::InjectHooks() { + RH_ScopedClass(FxEmitterBP_c); + RH_ScopedCategory("Fx"); + + RH_ScopedInstall(Constructor, 0x4A18D0); + RH_ScopedInstall(RenderHeatHaze, 0x4A1940, {.reversed = false}); + RH_ScopedInstall(UpdateParticle, 0x4A21D0, {.reversed = false}); + RH_ScopedInstall(CreateInstance_Reversed, 0x4A2B40, {.reversed = false}); // bad + RH_ScopedInstall(Update_Reversed, 0x4A2BC0, {.reversed = false}); + RH_ScopedInstall(Load_Reversed, 0x5C25F0, {.reversed = false}); + RH_ScopedInstall(LoadTextures_Reversed, 0x5C0A30, {.reversed = true}); + RH_ScopedInstall(Render_Reversed, 0x4A2C40, {.reversed = false}); + RH_ScopedInstall(FreePrtFromPrim_Reversed, 0x4A2510, {.reversed = false}); +} + +FxPrim_c* FxEmitterBP_c::CreateInstance() { return CreateInstance_Reversed(); } +void FxEmitterBP_c::Update(float deltaTime) { Update_Reversed(deltaTime); } +bool FxEmitterBP_c::LoadTextures(FxName32_t* textureNames, int32 version) { return LoadTextures_Reversed(textureNames, version); } +bool FxEmitterBP_c::Load(FILESTREAM file, int32 version, FxName32_t* textureNames) { return Load_Reversed(file, version, textureNames); } +void FxEmitterBP_c::Render(RwCamera* camera, uint32 a2, float dayNightBalance, bool bCanRenderHeatHaze) { Render_Reversed(camera, a2, dayNightBalance, bCanRenderHeatHaze); } +bool FxEmitterBP_c::FreePrtFromPrim(FxSystem_c* system) { return FreePrtFromPrim_Reversed(system); } + +// 0x4A18D0 +FxEmitterBP_c::FxEmitterBP_c() : FxPrimBP_c() { + m_Type = 0; +} + +// 0x4A1940 +void FxEmitterBP_c::RenderHeatHaze(RwCamera* camera, uint32 txdHashKey, float brightness) { + return plugin::CallMethod<0x4A1940, FxEmitterBP_c*, RwCamera*, uint32, float>(this, camera, txdHashKey, brightness); +} + +// 0x4A21D0 +bool FxEmitterBP_c::UpdateParticle(float deltaTime, FxEmitterPrt_c* emitter) { + return plugin::CallMethodAndReturn(this, deltaTime, emitter); +} + +// 0x4A2B40 +FxPrim_c* FxEmitterBP_c::CreateInstance_Reversed() { + return new FxEmitter_c(); +} + +// 0x4A2BC0 +void FxEmitterBP_c::Update_Reversed(float deltaTime) { + for (auto it = m_Particles.GetHead(); it; it = m_Particles.GetNext(it)) { + if (it->m_System->m_nKillStatus == eFxSystemKillStatus::FX_3) { + it->m_System->m_nKillStatus = eFxSystemKillStatus::FX_KILLED; + } + + // wrong casts or smth + if (it->m_System->m_nPlayStatus != eFxSystemPlayStatus::T2 && UpdateParticle(deltaTime, reinterpret_cast(it))) { + m_Particles.RemoveItem(it); + g_fxMan.ReturnParticle(reinterpret_cast(it)); + } + } +} + +// 0x5C25F0 +bool FxEmitterBP_c::Load_Reversed(FILESTREAM file, int32 version, FxName32_t* textureNames) { + FxPrimBP_c::Load(file, version, textureNames); + + m_nLodStart = uint16(ReadField(file, "LODSTART:") * 64.0f); + m_nLodEnd = uint16(ReadField(file, "LODEND:") * 64.0f); + + return true; +} + +// 0x5C0A30 +bool FxEmitterBP_c::LoadTextures_Reversed(FxName32_t* textureNames, int32 version) { + assert(textureNames); + + const auto LoadTexture = [&](auto ind) -> RwTexture* { + char mask[64]; + sprintf(mask, "%sm", textureNames[ind]); + + auto* texture = RwTextureRead(textureNames[ind], mask); + return texture ? texture : RwTextureRead(textureNames[ind], nullptr); + }; + + const auto LoadTextureIfExists = [=](auto ind) -> RwTexture* { + assert(&textureNames[ind]); + return strncmp(textureNames[ind], "NULL", 5u) != 0 ? LoadTexture(ind) : nullptr; + }; + + m_apTextures[0] = LoadTexture(0); + + if (version > 101) { + m_apTextures[1] = LoadTextureIfExists(1); + m_apTextures[2] = LoadTextureIfExists(2); + m_apTextures[3] = LoadTextureIfExists(3); + } + + return true; +} + +// 0x4A2C40 +void FxEmitterBP_c::Render_Reversed(RwCamera* camera, uint32 txdHashKey, float brightness, bool doHeatHaze) { + return plugin::CallMethod<0x4A2C40, FxEmitterBP_c*, RwCamera*, uint32, float, bool>(this, camera, txdHashKey, brightness, doHeatHaze); + + /* + static constexpr RwBlendFunction g_BlendFunctions[] = { + rwBLENDZERO, rwBLENDONE, rwBLENDSRCCOLOR, rwBLENDINVSRCCOLOR, rwBLENDSRCALPHA, rwBLENDINVSRCALPHA, + rwBLENDDESTALPHA, rwBLENDINVDESTALPHA, rwBLENDDESTCOLOR, rwBLENDINVDESTCOLOR, rwBLENDSRCALPHASAT + }; + + if (doHeatHaze) { + if (m_FxInfoManager.m_bHasHeatHazeParticleEmitter) + RenderHeatHaze(camera, txdHashKey, brightness); + return; + } + + if (IsFxInfoPresent(FX_INFO_HEATHAZE_DATA)) { + if (m_Particles.GetNumItems()) + g_fxMan.m_bHeatHazeEnabled = true; + return; + } + + if (!m_Particles.GetNumItems()) + return; + + RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(m_bAlphaOn)); + RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(g_BlendFunctions[m_bAlphaOn ? m_nSrcBlendId : 1])); + RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(g_BlendFunctions[m_bAlphaOn ? m_nDstBlendId : 0])); + + auto* raster = RwTextureGetRaster(m_apTextures[0]); + RenderBegin(raster, nullptr, rwIM3D_VERTEXUV); + + for (auto* prt = (FxEmitterPrt_c*)m_Particles.GetHead(); prt; prt = (FxEmitterPrt_c*)m_Particles.GetNext(prt)) { + const auto pos = [prt] { + if (prt->m_bLocalToSystem) { + // Get updated matrix position. + CVector out{}; + auto* mat = g_fxMan.FxRwMatrixCreate(); + prt->m_System->GetCompositeMatrix(mat); + RwV3dTransformPoint(&out, &prt->m_Pos, mat); // SA: RwV3dTransformPoints(...,...,1,...) + g_fxMan.FxRwMatrixDestroy(mat); + return out; + } else { + return prt->m_Pos; + } + }(); + + RenderInfo_t renderInfo{}; + m_FxInfoManager.ProcessRenderInfo( + prt->m_System->m_fCurrentTime, + prt->m_fCurrentLife / prt->m_fTotalLife, + 0.0f, + prt->m_System->m_SystemBP->m_fLength, + false, + &renderInfo + ); + + if (renderInfo.m_SmokeType > -1) { + // RenderSmoke(); + CGeneral::GetRandomNumberInRange(0.0f, 1.0f) * + } + } + + RenderEnd(); + */ +} + +// 0x4A2510 +bool FxEmitterBP_c::FreePrtFromPrim_Reversed(FxSystem_c* system) { + return plugin::CallMethodAndReturn(this, system); +} + +// todo: eFxInfo +// 0x4A24D0 +bool FxEmitterBP_c::IsFxInfoPresent(eFxInfoType type) const { + if (m_FxInfoManager.m_nNumInfos <= 0) + return false; + + for (auto& info : m_FxInfoManager.GetInfos()) { + if (info->m_nType == type) { + return true; + } + } + return false; +} diff --git a/source/game_sa/Fx/FxEmitterBP.h b/source/game_sa/Fx/FxEmitterBP.h index 29ed82137e..6e8b3142c0 100644 --- a/source/game_sa/Fx/FxEmitterBP.h +++ b/source/game_sa/Fx/FxEmitterBP.h @@ -1,15 +1,42 @@ -/* - Plugin-SDK file - Authors: GTA Community. See more here - https://github.com/DK22Pac/plugin-sdk - Do not delete this comment block. Respect others' work! -*/ #pragma once #include "FxPrimBP.h" +class FxEmitterPrt_c; + class NOTSA_EXPORT_VTABLE FxEmitterBP_c : public FxPrimBP_c { public: -}; + uint16 m_nLodStart; + uint16 m_nLodEnd; + + bool m_bHasInfoFlatData; + bool m_bHasInfoHeatHazeData; + +public: + static void InjectHooks(); -VALIDATE_SIZE(FxEmitterBP_c, 0x40); \ No newline at end of file + FxEmitterBP_c(); + ~FxEmitterBP_c() override = default; // 0x4A18F0 + + void RenderHeatHaze(RwCamera* camera, uint32 txdHashKey, float brightness); + bool UpdateParticle(float deltaTime, FxEmitterPrt_c* emitter); + FxPrim_c* CreateInstance() override; + void Update(float deltaTime) override; + bool LoadTextures(FxName32_t* textureNames, int32 version) override; + bool Load(FILESTREAM file, int32 version, FxName32_t* textureNames) override; + void Render(RwCamera* camera, uint32 a2, float dayNightBalance, bool bCanRenderHeatHaze) override; + bool FreePrtFromPrim(FxSystem_c* system) override; + [[nodiscard]] bool IsFxInfoPresent(eFxInfoType type) const; + +private: + FxEmitterBP_c* Constructor() { this->FxEmitterBP_c::FxEmitterBP_c(); return this; } + + FxPrim_c* CreateInstance_Reversed(); + void Update_Reversed(float deltaTime); + bool LoadTextures_Reversed(FxName32_t* textureNames, int32 version); + bool Load_Reversed(FILESTREAM file, int32 version, FxName32_t* textureNames); + void Render_Reversed(RwCamera* camera, uint32 txdHashKey, float brightness, bool doHeatHaze); + bool FreePrtFromPrim_Reversed(FxSystem_c* system); + +}; +VALIDATE_SIZE(FxEmitterBP_c, 0x48); diff --git a/source/game_sa/Fx/FxEmitterPrt.cpp b/source/game_sa/Fx/FxEmitterPrt.cpp new file mode 100644 index 0000000000..191d161096 --- /dev/null +++ b/source/game_sa/Fx/FxEmitterPrt.cpp @@ -0,0 +1,16 @@ +#include "StdInc.h" + +#include "FxEmitterPrt.h" +#include "FxManager.h" + +// 0x4A18B0 +void* FxEmitterPrt_c::operator new[](size_t size) { + return g_fxMan.GetMemPool().GetMem(size); +} + +void FxEmitterPrt_c::InjectHooks() { + RH_ScopedClass(FxEmitterPrt_c); + RH_ScopedCategory("Fx"); + + RH_ScopedInstall(operator new[], 0x4A18B0); +} diff --git a/source/game_sa/Fx/FxEmitterPrt.h b/source/game_sa/Fx/FxEmitterPrt.h new file mode 100644 index 0000000000..0386e82def --- /dev/null +++ b/source/game_sa/Fx/FxEmitterPrt.h @@ -0,0 +1,32 @@ +#pragma once + +#include "ListItem_c.h" +#include "Vector.h" +#include "Particle.h" +#include "extensions/FixedFloat.hpp" + +struct RwRGBA; +class FxSystem_c; + +class FxEmitterPrt_c : public Particle_c, ListItem_c { +public: + CRGBA m_MultColor; + FixedFloat m_MultSize; + FixedFloat m_MultRot; + uint8 m_RandR; + uint8 m_RandG; + uint8 m_RandB; + FixedFloat m_Brightness; + int8 m_RotZ; + bool m_bLocalToSystem; + float m_CurrentRotation; + +public: + FxEmitterPrt_c() = default; // 0x4A94E0 + ~FxEmitterPrt_c() = default; // 0x4A94F0 + + static void InjectHooks(); + + static void* operator new[](size_t size); +}; +VALIDATE_SIZE(FxEmitterPrt_c, 0x44); diff --git a/source/game_sa/Fx/FxFrustumInfo.cpp b/source/game_sa/Fx/FxFrustumInfo.cpp index a15c7a792d..09efbe3061 100644 --- a/source/game_sa/Fx/FxFrustumInfo.cpp +++ b/source/game_sa/Fx/FxFrustumInfo.cpp @@ -1 +1,22 @@ -#include "StdInc.h" \ No newline at end of file +#include "StdInc.h" + +#include "FxFrustumInfo.h" + +// 0x4AA030 +bool FxFrustumInfo_c::IsCollision(FxSphere_c* sphere) { + return plugin::CallMethodAndReturn(this, sphere); + + // todo: maybe wrong + if (!m_Sphere.IsCollision(sphere)) + return false; + + for (auto i = 0u; i < std::size(m_Planes); i++) { + if (sphere->GetDistToPlane(&m_Planes[i]) > sphere->m_fRadius) { + sphere->m_nNumPlanesPassed = i; + return false; + } + + sphere->m_nNumPlanesPassed = i; + } + return true; +} diff --git a/source/game_sa/Fx/FxFrustumInfo.h b/source/game_sa/Fx/FxFrustumInfo.h index 6ad4b311c4..d863a12a8f 100644 --- a/source/game_sa/Fx/FxFrustumInfo.h +++ b/source/game_sa/Fx/FxFrustumInfo.h @@ -11,10 +11,9 @@ class FxFrustumInfo_c { public: - FxSphere_c m_sphere; - FxPlane_c m_planes[4]; + FxSphere_c m_Sphere; + FxPlane_c m_Planes[4]; bool IsCollision(FxSphere_c* sphere); }; - -VALIDATE_SIZE(FxFrustumInfo_c, 0x54); \ No newline at end of file +VALIDATE_SIZE(FxFrustumInfo_c, 0x54); diff --git a/source/game_sa/Fx/FxInfoManager.cpp b/source/game_sa/Fx/FxInfoManager.cpp new file mode 100644 index 0000000000..17f05c574a --- /dev/null +++ b/source/game_sa/Fx/FxInfoManager.cpp @@ -0,0 +1,228 @@ +#include "StdInc.h" + +#include "FxInfoManager.h" +#include "FxManager.h" + +#include "EmissionInfo.h" +#include "MovementInfo.h" +#include "RenderInfo.h" + +#include "FxInfoEmRate.h" +#include "FxInfoEmSize.h" +#include "FxInfoEmSpeed.h" +#include "FxInfoEmDir.h" +#include "FxInfoEmAngle.h" +#include "FxInfoEmLife.h" +#include "FxInfoEmPos.h" +#include "FxInfoEmWeather.h" +#include "FxInfoEmRotation.h" +#include "FxInfoNoise.h" +#include "FxInfoForce.h" +#include "FxInfoFriction.h" +#include "FxInfoAttractPt.h" +#include "FxInfoAttractLine.h" +#include "FxInfoGroundCollide.h" +#include "FxInfoWind.h" +#include "FxInfoJitter.h" +#include "FxInfoRotSpeed.h" +#include "FxInfoFloat.h" +#include "FxInfoUnderwater.h" +#include "FxInfoColour.h" +#include "FxInfoSize.h" +#include "FxInfoSpriteRect.h" +#include "FxInfoHeatHaze.h" +#include "FxInfoTrail.h" +#include "FxInfoFlat.h" +#include "FxInfoDir.h" +#include "FxInfoAnimTexture.h" +#include "FxInfoColourRange.h" +#include "FxInfoSelfLit.h" +#include "FxInfoColourBright.h" +#include "FxInfoSmoke.h" +#include "FxInfoRotate.h" +#include "FxInfoTexCoords.h" +#include "FxInfoRotateOffset.h" + + +void FxInfoManager_c::InjectHooks() { + RH_ScopedClass(FxInfoManager_c); + RH_ScopedCategory("Fx"); + + RH_ScopedInstall(AddFxInfo, 0x4A7B00); + // RH_ScopedInstall(Load, 0x5C0B70); + RH_ScopedInstall(ProcessEmissionInfo, 0x4A4960); + RH_ScopedInstall(ProcessMovementInfo, 0x4A4A10); + RH_ScopedInstall(ProcessRenderInfo, 0x4A4A80); +} + +// 0x4A7B00 +FxInfo_c* FxInfoManager_c::AddFxInfo(int32 info) { + switch (info) { + case FX_INFO_EMRATE_DATA: return new FxInfoEmRate_c(); + case FX_INFO_EMSIZE_DATA: return new FxInfoEmSize_c(); + case FX_INFO_EMSPEED_DATA: return new FxInfoEmSpeed_c(); + case FX_INFO_EMDIR_DATA: return new FxInfoEmDir_c(); + case FX_INFO_EMANGLE_DATA: return new FxInfoEmAngle_c(); + case FX_INFO_EMLIFE_DATA: return new FxInfoEmLife_c(); + case FX_INFO_EMPOS_DATA: return new FxInfoEmPos_c(); + case FX_INFO_EMWEATHER_DATA: return new FxInfoEmWeather_c(); + case FX_INFO_EMROTATION_DATA: return new FxInfoEmRotation_c(); + case FX_INFO_NOISE_DATA: return new FxInfoNoise_c(); + case FX_INFO_FORCE_DATA: return new FxInfoForce_c(); + case FX_INFO_FRICTION_DATA: return new FxInfoFriction_c(); + case FX_INFO_ATTRACTPT_DATA: return new FxInfoAttractPt_c(); + case FX_INFO_ATTRACTLINE_DATA: return new FxInfoAttractLine_c(); + case FX_INFO_GROUNDCOLLIDE_DATA: return new FxInfoGroundCollide_c(); + case FX_INFO_WIND_DATA: return new FxInfoWind_c(); + case FX_INFO_JITTER_DATA: return new FxInfoJitter_c(); + case FX_INFO_ROTSPEED_DATA: return new FxInfoRotSpeed_c(); + case FX_INFO_FLOAT_DATA: return new FxInfoFloat_c(); + case FX_INFO_UNDERWATER_DATA: return new FxInfoUnderwater_c(); + case FX_INFO_COLOUR_DATA: return new FxInfoColour_c(); + case FX_INFO_SIZE_DATA: return new FxInfoSize_c(); + case FX_INFO_SPRITERECT_DATA: return new FxInfoSpriteRect_c(); + case FX_INFO_HEATHAZE_DATA: return new FxInfoHeatHaze_c(); + case FX_INFO_TRAIL_DATA: return new FxInfoTrail_c(); + case FX_INFO_FLAT_DATA: return new FxInfoFlat_c(); + case FX_INFO_DIR_DATA: return new FxInfoDir_c(); + case FX_INFO_ANIMTEX_DATA: return new FxInfoAnimTexture_c(); + case FX_INFO_COLOURRANGE_DATA: return new FxInfoColourRange_c(); + case FX_INFO_SELFLIT_DATA: return new FxInfoSelfLit_c(); + case FX_INFO_COLOURBRIGHT_DATA: return new FxInfoColourBright_c(); + case FX_INFO_SMOKE_DATA: return new FxInfoSmoke_c(); + // NOTSA case FX_INFO_ROTATE_DATA: return new FxInfoRotate_c(); + // NOTSA case FX_INFO_TEXCOORDS_DATA: return new FxInfoTexCoords_c(); + // NOTSA case FX_INFO_ROTATEOFFSET_DATA: return new FxInfoRotateOffset_c(); + default: return nullptr; + }; +} + +constexpr struct { int32 type; const char* name; } FXINFOMANAGER_C_LOAD_MAPPING[] = { + { FX_INFO_EMRATE_DATA, "FX_INFO_EMRATE_DATA:" }, + { FX_INFO_EMSIZE_DATA, "FX_INFO_EMSIZE_DATA:" }, + { FX_INFO_EMSPEED_DATA, "FX_INFO_EMSPEED_DATA:" }, + { FX_INFO_EMDIR_DATA, "FX_INFO_EMDIR_DATA:" }, + { FX_INFO_EMANGLE_DATA, "FX_INFO_EMANGLE_DATA:" }, + { FX_INFO_EMLIFE_DATA, "FX_INFO_EMLIFE_DATA:" }, + { FX_INFO_EMPOS_DATA, "FX_INFO_EMPOS_DATA:" }, + { FX_INFO_EMWEATHER_DATA, "FX_INFO_EMWEATHER_DATA:" }, + { FX_INFO_EMROTATION_DATA, "FX_INFO_EMROTATION_DATA:" }, + { FX_INFO_NOISE_DATA, "FX_INFO_NOISE_DATA:" }, + { FX_INFO_FORCE_DATA, "FX_INFO_FORCE_DATA:" }, + { FX_INFO_FRICTION_DATA, "FX_INFO_FRICTION_DATA:" }, + { FX_INFO_ATTRACTPT_DATA, "FX_INFO_ATTRACTPT_DATA:" }, + { FX_INFO_ATTRACTLINE_DATA, "FX_INFO_ATTRACTLINE_DATA:" }, + { FX_INFO_GROUNDCOLLIDE_DATA, "FX_INFO_GROUNDCOLLIDE_DATA:" }, + { FX_INFO_WIND_DATA, "FX_INFO_WIND_DATA:" }, + { FX_INFO_JITTER_DATA, "FX_INFO_JITTER_DATA:" }, + { FX_INFO_ROTSPEED_DATA, "FX_INFO_ROTSPEED_DATA:" }, + { FX_INFO_FLOAT_DATA, "FX_INFO_FLOAT_DATA:" }, + { FX_INFO_UNDERWATER_DATA, "FX_INFO_UNDERWATER_DATA:" }, + { FX_INFO_COLOUR_DATA, "FX_INFO_COLOUR_DATA:" }, + { FX_INFO_SIZE_DATA, "FX_INFO_SIZE_DATA:" }, + { FX_INFO_SPRITERECT_DATA, "FX_INFO_SPRITERECT_DATA:" }, + { FX_INFO_HEATHAZE_DATA, "FX_INFO_HEATHAZE_DATA:" }, + { FX_INFO_TRAIL_DATA, "FX_INFO_TRAIL_DATA:" }, + { FX_INFO_FLAT_DATA, "FX_INFO_FLAT_DATA:" }, + { FX_INFO_DIR_DATA, "FX_INFO_DIR_DATA:" }, + { FX_INFO_ANIMTEX_DATA, "FX_INFO_ANIMTEX_DATA:" }, + { FX_INFO_COLOURRANGE_DATA, "FX_INFO_COLOURRANGE_DATA:" }, + { FX_INFO_SELFLIT_DATA, "FX_INFO_SELFLIT_DATA:" }, + { FX_INFO_COLOURBRIGHT_DATA, "FX_INFO_COLOURBRIGHT_DATA:" }, + { FX_INFO_SMOKE_DATA, "FX_INFO_SMOKE_DATA:" }, + // NOTSA { FX_INFO_ROTATE_DATA, "FX_INFO_ROTATE_DATA:" }, + // NOTSA { FX_INFO_TEXCOORDS_DATA, "FX_INFO_TEXCOORDS_DATA:" }, + // NOTSA { FX_INFO_ROTATEOFFSET_DATA, "FX_INFO_ROTATEOFFSET_DATA:" }, +}; + +// 0x5C0B70 +void FxInfoManager_c::Load(FILESTREAM file, int32 version) { + plugin::CallMethod<0x5C0B70, FxInfoManager_c*, FILESTREAM, int32>(this, file, version); + return; + + ReadField(file); + m_nNumInfos = ReadField(file, "NUM_INFOS:"); + m_MovementOffset = -1; + m_RenderOffset = -1; + + m_pInfos = g_fxMan.Allocate(m_nNumInfos); + assert(m_pInfos); + + char line[256], field[128]; + for (auto& info : GetInfos()) { + ReadLine(file, line, sizeof(line)); + VERIFY(sscanf(line, "%s", field) == 1); + + const auto GetType = [=]() -> int32 { + for (auto& [type, name] : FXINFOMANAGER_C_LOAD_MAPPING) { + if (strcmp(field, name) == 0) { + return type; + } + } + NOTSA_UNREACHABLE("Unknown FX Info: %s", field); // NOTSA + }; + auto infoType = GetType(); + + bool bTimeModePrt = true; + if ((infoType & 0xF000) > 0x1000 && version >= 0.7f) { + bTimeModePrt = ReadField(file, "TIMEMODEPRT:"); + } + + info = AddFxInfo(infoType); + assert(info); // printf("Failed to load info: %s\n", field) + info->Load(file, version); + ReadField(file); + + info->m_bTimeModeParticle = bTimeModePrt; + } + + if (m_RenderOffset == -1) + m_RenderOffset = m_nNumInfos; + + if (m_MovementOffset == -1) + m_MovementOffset = m_RenderOffset; +} + +/* todo: + * info->m_nType + * replace raw iteration to *irange* + * ProcessRenderInfo: mult = prt->m_fCurrentLife / prt->m_fTotalLife; + */ + +// 0x4A4960 +void FxInfoManager_c::ProcessEmissionInfo(float currentTime, float mult, float totalTime, bool useConst, EmissionInfo_t* emission) { + emission->Process(mult); + + for (auto i = 0; i < m_MovementOffset; i++) { + auto& info = m_pInfos[i]; + if ((info->m_nType & 0x1000) != 0) { + info->GetValue(currentTime, 0.0f, mult, totalTime, useConst, emission); + } + } +} + +// a6 - always false +// 0x4A4A10 +void FxInfoManager_c::ProcessMovementInfo(float currentTime, float mult, float totalTime, float length, bool useConst, MovementInfo_t* movementInfo) { + movementInfo->Process(); + + for (int i = m_MovementOffset; i < m_RenderOffset; i++) { + auto& info = m_pInfos[i]; + if ((info->m_nType & 0x2000) != 0) { + info->GetValue(currentTime, mult, totalTime, length, useConst, movementInfo); + } + } +} + +// a6 - always false +// 0x4A4A80 +void FxInfoManager_c::ProcessRenderInfo(float currentTime, float mult, float totalTime, float length, bool useConst, RenderInfo_t* renderInfo) { + renderInfo->Process(); + + for (int i = m_RenderOffset; i < m_nNumInfos; i++) { + auto& info = m_pInfos[i]; + if ((info->m_nType & 0xC000) != 0) { // 0xC000 == -0x4000u | m_nType >= 0x4000u + info->GetValue(currentTime, mult, totalTime, length, useConst, renderInfo); + } + } +} diff --git a/source/game_sa/Fx/FxInfoManager.h b/source/game_sa/Fx/FxInfoManager.h index 51ff6032a2..a610052121 100644 --- a/source/game_sa/Fx/FxInfoManager.h +++ b/source/game_sa/Fx/FxInfoManager.h @@ -6,19 +6,45 @@ */ #pragma once +struct EmissionInfo_t; +struct MovementInfo_t; +struct RenderInfo_t; +class FxInfo_c; + class FxInfoManager_c { public: - uint32 m_nNumInfos; - void* m_pInfos; - uint8 m_nFirstMovementInfo; - uint8 m_nFirstRenderInfo; - char _padA[2]; - int16 m_nLodStart; - int16 m_nLodEnd; - bool m_bHasFlatParticleEmitter; - bool m_bHasHeatHazeParticleEmitter; - char field_12; - char field_13; -}; + int32 m_nNumInfos; + FxInfo_c** m_pInfos; + int8 m_MovementOffset; + int8 m_RenderOffset; + char _align[2]; // urgent + + // FxEmitterBP_c + uint16 m_nLodStart; + uint16 m_nLodEnd; + bool m_bHasFlatParticleEmitter; + bool m_bHasHeatHazeParticleEmitter; + char field_12; + char field_13; + +public: + static void InjectHooks(); -VALIDATE_SIZE(FxInfoManager_c, 0x14); \ No newline at end of file + FxInfoManager_c() = default; + ~FxInfoManager_c() = default; // 0x4A4950 + + FxInfo_c* AddFxInfo(int32 info); + void Load(FILESTREAM file, int32 version); + + void ProcessEmissionInfo(float currentTime, float mult, float totalTime, bool useConst, EmissionInfo_t* emissionInfo); + void ProcessMovementInfo(float currentTime, float mult, float totalTime, float length, bool useConst, MovementInfo_t* movementInfo); + void ProcessRenderInfo(float currentTime, float mult, float totalTime, float length, bool useConst, RenderInfo_t* renderInfo); + + auto GetInfos() { return std::span{ m_pInfos, size_t(m_nNumInfos) }; } + auto GetInfos() const { return std::span{ m_pInfos, size_t(m_nNumInfos) }; } + + auto GetEmissionInfos(); + auto GetMovementInfos(); + auto GetRenderInfos(); +}; +VALIDATE_SIZE(FxInfoManager_c, 0x14); diff --git a/source/game_sa/Fx/FxManager.cpp b/source/game_sa/Fx/FxManager.cpp index 34b2999288..b23455e7d7 100644 --- a/source/game_sa/Fx/FxManager.cpp +++ b/source/game_sa/Fx/FxManager.cpp @@ -7,6 +7,13 @@ #include "StdInc.h" #include "FxManager.h" +#include "FxEmitterPrt.h" +#include "CustomBuildingDNPipeline.h" +#include "FxTools.h" +#include "FxSystem.h" +#include "FxSystemBP.h" +#include "FxPrimBP.h" +#include "Particle.h" FxManager_c& g_fxMan = *(FxManager_c*)0xA9AE80; @@ -14,36 +21,36 @@ void FxManager_c::InjectHooks() { RH_ScopedClass(FxManager_c); RH_ScopedCategory("Fx"); - RH_ScopedInstall(Constructor, 0x4A9470, { .reversed = false }); - RH_ScopedInstall(Destructor, 0x4A90A0, { .reversed = false }); - RH_ScopedInstall(Init, 0x4A98E0, { .reversed = false }); - RH_ScopedInstall(Exit, 0x4A9A10, { .reversed = false }); - RH_ScopedInstall(DestroyFxSystem, 0x4A9810, { .reversed = false }); - RH_ScopedInstall(DestroyAllFxSystems, 0x4A98B0, { .reversed = false }); - RH_ScopedInstall(Update, 0x4A9A80, { .reversed = false }); - RH_ScopedInstall(LoadFxProject, 0x5C2420, { .reversed = false }); - RH_ScopedInstall(UnloadFxProject, 0x4A9AE0, { .reversed = false }); - RH_ScopedInstall(LoadFxSystemBP, 0x5C1F50, { .reversed = false }); - RH_ScopedInstall(FindFxSystemBP, 0x4A9360, { .reversed = false }); + RH_ScopedInstall(Constructor, 0x4A9470); + RH_ScopedInstall(Destructor, 0x4A90A0); + RH_ScopedInstall(Init, 0x4A98E0); + RH_ScopedInstall(Exit, 0x4A9A10); + RH_ScopedInstall(DestroyFxSystem, 0x4A9810); + RH_ScopedInstall(DestroyAllFxSystems, 0x4A98B0, {.reversed = false}); // <-- broken for some reason? + RH_ScopedInstall(Update, 0x4A9A80); + RH_ScopedInstall(LoadFxProject, 0x5C2420); + RH_ScopedInstall(UnloadFxProject, 0x4A9AE0); + RH_ScopedInstall(LoadFxSystemBP, 0x5C1F50); + RH_ScopedInstall(FindFxSystemBP, 0x4A9360); RH_ScopedInstall(GetFrustumInfo, 0x4A9130); - RH_ScopedInstall(CalcFrustumInfo, 0x4A9140, { .reversed = false }); - RH_ScopedInstall(Render, 0x4A92A0, { .reversed = false }); - RH_ScopedInstall(ReturnParticle, 0x4A93B0, { .reversed = false }); - RH_ScopedInstall(GetParticle, 0x4A93C0, { .reversed = false }); - RH_ScopedInstall(FreeUpParticle, 0x4A9400, { .reversed = false }); + RH_ScopedInstall(CalcFrustumInfo, 0x4A9140); + RH_ScopedInstall(Render, 0x4A92A0); + RH_ScopedInstall(ReturnParticle, 0x4A93B0); + RH_ScopedInstall(GetParticle, 0x4A93C0); + RH_ScopedInstall(FreeUpParticle, 0x4A9400); RH_ScopedInstall(SetWindData, 0x4A93E0); - RH_ScopedInstall(FxRwMatrixCreate, 0x4A9440, { .reversed = false }); + RH_ScopedInstall(FxRwMatrixCreate, 0x4A9440); RH_ScopedInstall(FxRwMatrixDestroy, 0x4A9460); - RH_ScopedInstall(ShouldCreate, 0x4A9500, { .reversed = false }); - // RH_ScopedOverloadedInstall(CreateFxSystem, "", 0x4A95C0, FxSystem_c* (FxManager_c::*)(FxSystemBP_c*, RwMatrix*, RwMatrix*, bool)); - // RH_ScopedOverloadedInstall(CreateFxSystem, "", 0x4A9BB0, FxSystem_c* (FxManager_c::*)(const char*, RwMatrix*, RwMatrix*, bool)); - // RH_ScopedOverloadedInstall(CreateFxSystem, "", 0x4A96B0, FxSystem_c* (FxManager_c::*)(FxSystemBP_c*, RwV3d*, RwMatrix*, bool)); - // RH_ScopedOverloadedInstall(CreateFxSystem, "", 0x4A9BE0, FxSystem_c* (FxManager_c::*)(const char*, RwV3d*, RwMatrix*, bool)); + RH_ScopedInstall(ShouldCreate, 0x4A9500); + RH_ScopedOverloadedInstall(CreateFxSystem, "2", 0x4A9BB0, FxSystem_c* (FxManager_c::*)(const char*, const RwMatrix&, RwMatrix*, bool)); + RH_ScopedOverloadedInstall(CreateFxSystem, "1", 0x4A95C0, FxSystem_c* (FxManager_c::*)(FxSystemBP_c*, const RwMatrix&, RwMatrix*, bool)); + RH_ScopedOverloadedInstall(CreateFxSystem, "4", 0x4A9BE0, FxSystem_c* (FxManager_c::*)(const char*, const CVector&, RwMatrix*, bool)); + RH_ScopedOverloadedInstall(CreateFxSystem, "3", 0x4A96B0, FxSystem_c* (FxManager_c::*)(FxSystemBP_c*, const CVector&, RwMatrix*, bool)); } // 0x4A9470 FxManager_c::FxManager_c() { - ((void(__thiscall*)(FxManager_c*))0x4A9470)(this); + m_FxEmitters = nullptr; } FxManager_c* FxManager_c::Constructor() { @@ -51,11 +58,6 @@ FxManager_c* FxManager_c::Constructor() { return this; } -// 0x4A90A0 -FxManager_c::~FxManager_c() { - ((void(__thiscall*)(FxManager_c*))0x4A90A0)(this); -} - FxManager_c* FxManager_c::Destructor() { this->FxManager_c::~FxManager_c(); return this; @@ -63,88 +65,230 @@ FxManager_c* FxManager_c::Destructor() { // 0x4A98E0 bool FxManager_c::Init() { - return ((bool(__thiscall*)(FxManager_c*))0x4A98E0)(this); + m_Pool.Init(); + + m_nCurrentMatrix = 0; + m_apMatrices.fill(RwMatrixCreate()); + + m_FxEmitters = new FxEmitterPrt_c[FX_MANAGER_NUM_EMITTERS]; + for (auto i = 0; i < FX_MANAGER_NUM_EMITTERS; i++) { + m_FxEmitterParticles.AddItem(&m_FxEmitters[i]); + } + + return true; } // 0x4A9A10 void FxManager_c::Exit() { - ((void(__thiscall*)(FxManager_c*))0x4A9A10)(this); + DestroyAllFxSystems(); + m_FxSystemBPs.RemoveAll(); + m_FxEmitters = nullptr; + m_FxEmitterParticles.RemoveAll(); + CTxdStore::RemoveTxdSlot(m_nFxTxdIndex); + std::ranges::for_each(m_apMatrices, RwMatrixDestroy); + m_Pool.Exit(); } // 0x4A9810 void FxManager_c::DestroyFxSystem(FxSystem_c* system) { - ((void(__thiscall*)(FxManager_c*, FxSystem_c*))0x4A9810)(this, system); + assert(system); + assert(system->m_SystemBP); + + for (auto i = 0; i < system->m_SystemBP->m_nNumPrims; i++) { + auto& prim = system->m_SystemBP->m_Prims[i]; + auto& particles = prim->m_Particles; + + for (Particle_c* particle = particles.GetHead(); particle; particle = particles.GetNext(particle)) { + if (particle->m_System == system) { + prim->m_Particles.RemoveItem(particle); + m_FxEmitterParticles.AddItem(particle); + } + } + } + + m_FxSystems.RemoveItem(system); + system->Exit(); + delete system; } // 0x4A98B0 void FxManager_c::DestroyAllFxSystems() { - ((void(__thiscall*)(FxManager_c*))0x4A98B0)(this); -} - -// 0x4A9A80 -void FxManager_c::Update(RwCamera* arg0, float timeDelta) { - ((void(__thiscall*)(FxManager_c*, RwCamera*, float))0x4A9A80)(this, arg0, timeDelta); + for (FxSystem_c* it = m_FxSystems.GetHead(); it; it = m_FxSystems.GetNext(it)) { + DestroyFxSystem(it); + } } // 0x5C2420 -bool FxManager_c::LoadFxProject(const char* filename) { - return ((bool(__thiscall*)(FxManager_c*, const char*))0x5C2420)(this, filename); +bool FxManager_c::LoadFxProject(const char* path) { + char txdPath[256]; // to be set to "models\\effectsPC.txd" + strcpy(txdPath, path); + strcpy(&txdPath[strlen(path) - strlen(".fxp")], "PC.txd"); + + // NOTSA: sanity check + if (!fs::exists(txdPath)) { + NOTSA_LOG_ERR("Fx TXD file '{}' doesn't exists. Game will crash after trying to render a fx!", txdPath); + } + + m_nFxTxdIndex = CTxdStore::AddTxdSlot("fx"); + CTxdStore::LoadTxd(m_nFxTxdIndex, txdPath); + CTxdStore::AddRef(m_nFxTxdIndex); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(m_nFxTxdIndex); + + auto* file = CFileMgr::OpenFile(path, "r"); + if (!file) + return false; + + char line[256], buffer[128]; + + ReadField(file, "FX_PROJECT_DATA:"); // NOTSA + + while (true) { + ReadField(file); + ReadLine(file, line, sizeof(line)); + RET_IGNORED(sscanf(line, "%s", buffer)); + if (strncmp(buffer, "FX_SYSTEM_DATA:", 16u)) + break; + + LoadFxSystemBP(path, file); + } + + CFileMgr::CloseFile(file); + CTxdStore::PopCurrentTxd(); + GetMemPool().Optimise(); + return true; } // 0x4A9AE0 void FxManager_c::UnloadFxProject() { - ((void(__thiscall*)(FxManager_c*))0x4A9AE0)(this); + // ((void(__thiscall*)(FxManager_c*))0x4A9AE0)(this); + + DestroyAllFxSystems(); + m_FxSystemBPs.RemoveAll(); + GetMemPool().Reset(); + m_FxEmitterParticles.RemoveAll(); + + m_FxEmitters = new FxEmitterPrt_c[FX_MANAGER_NUM_EMITTERS]; + for (auto i = 0; i < FX_MANAGER_NUM_EMITTERS; i++) { + m_FxEmitterParticles.AddItem(&m_FxEmitters[i]); + } } // 0x5C1F50 -FxSystemBP_c* FxManager_c::LoadFxSystemBP(char* filename, int32 file) { - return ((FxSystemBP_c * (__thiscall*)(FxManager_c*, char*, int32))0x5C1F50)(this, filename, file); +void FxManager_c::LoadFxSystemBP(Const char* filename, FILESTREAM file) { + // return ((FxSystemBP_c * (__thiscall*)(FxManager_c*, char*, FILESTREAM))0x5C1F50)(this, filename, file); + + int32 version; + char line[256]; + ReadLine(file, line, sizeof(line)); + (void)sscanf(line, "%d", &version); + + auto system = new FxSystemBP_c(); + system->Load(filename, file, version); + m_FxSystemBPs.AddItem(system); } // 0x4A9360 FxSystemBP_c* FxManager_c::FindFxSystemBP(const char* name) { - return ((FxSystemBP_c * (__thiscall*)(FxManager_c*, const char*))0x4A9360)(this, name); -} -// 0x4A9130 -FxFrustumInfo_c* FxManager_c::GetFrustumInfo() { - return &m_frustum; + // return ((FxSystemBP_c * (__thiscall*)(FxManager_c*, const char*))0x4A9360)(this, name); + + const auto key = CKeyGen::GetUppercaseKey(name); + for (FxSystemBP_c* it = m_FxSystemBPs.GetHead(); it; it = m_FxSystemBPs.GetNext(it)) { + if (it->m_nNameKey == key) { + return it; + } + } + + NOTSA_LOG_ERR("Couldn't find Fx system bp '{}'", name); + return nullptr; } // 0x4A9140 void FxManager_c::CalcFrustumInfo(RwCamera* camera) { - ((void(__thiscall*)(FxManager_c*, RwCamera*))0x4A9140)(this, camera); + const auto* matrix = RwFrameGetMatrix(RwCameraGetFrame(camera)); + const auto* viewWindow = RwCameraGetViewWindow(camera); + const auto farClip = RwCameraGetFarClipPlane(camera); + + const auto dist = RwV2dLength(viewWindow); + const auto angle = RadiansToDegrees(std::atan2(dist, 1.0f)); + const auto radius = std::sqrt(sq(dist) + 1.0f) * farClip / std::sin(DegreesToRadians(180.0f - 2.0f * angle)) * std::sin(DegreesToRadians(angle)); + + m_Frustum.m_Sphere = {CVector{matrix->pos} + CVector{matrix->at} * radius, radius}; + + m_Frustum.m_Planes[0] = camera->frustumPlanes[2].plane; + m_Frustum.m_Planes[1] = camera->frustumPlanes[3].plane; + m_Frustum.m_Planes[2] = camera->frustumPlanes[4].plane; + m_Frustum.m_Planes[3] = camera->frustumPlanes[5].plane; +} + +// 0x4A9A80 +void FxManager_c::Update(RwCamera* camera, float timeDelta) { + // ((void(__thiscall*)(FxManager_c*, RwCamera*, float))0x4A9A80)(this, camera, timeDelta); + + assert(camera); + CalcFrustumInfo(camera); + + for (FxSystemBP_c* it = m_FxSystemBPs.GetHead(); it; it = m_FxSystemBPs.GetNext(it)) { + it->Update(timeDelta); + } + + for (FxSystem_c* it = m_FxSystems.GetHead(); it; it = m_FxSystems.GetNext(it)) { + if (it->Update(camera, timeDelta)) { + DestroyFxSystem(it); + } + } } // 0x4A92A0 -void FxManager_c::Render(RwCamera* camera, uint8 arg1) { - ((void(__thiscall*)(FxManager_c*, RwCamera*, uint8))0x4A92A0)(this, camera, arg1); +void FxManager_c::Render(RwCamera* camera, bool bHeatHaze) { + // ((void(__thiscall*)(FxManager_c*, RwCamera*, uint8))0x4A92A0)(this, camera, bHeatHaze); + + auto balance = 1.0f - CCustomBuildingDNPipeline::m_fDNBalanceParam; + m_bHeatHazeEnabled = false; + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); + RwRenderStateSet(rwRENDERSTATEALPHATESTFUNCTIONREF, RWRSTATE(0)); + for (FxSystemBP_c* it = m_FxSystemBPs.GetHead(); it; it = m_FxSystemBPs.GetNext(it)) { + it->Render(camera, balance * 0.6f + 0.4f, bHeatHaze); + } + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); } // 0x4A93B0 void FxManager_c::ReturnParticle(FxEmitterPrt_c* emitter) { - ((void(__thiscall*)(FxManager_c*, FxEmitterPrt_c*))0x4A93B0)(this, emitter); - // m_fxEmitterParticlesList.AddItem(&emitter->m_link); + m_FxEmitterParticles.AddItem(emitter); } -// 0x4A93C0 -FxEmitterPrt_c* FxManager_c::GetParticle(int8 arg0) { - return ((FxEmitterPrt_c * (__thiscall*)(FxManager_c*, int8))0x4A93C0)(this, arg0); +// 0x4A93C0 -- has no xref with primType != 0. +Particle_c* FxManager_c::GetParticle(int8 primType) { + assert(!primType); + + return !primType ? m_FxEmitterParticles.RemoveHead() : nullptr; } // 0x4A9400 void FxManager_c::FreeUpParticle() { - ((void(__thiscall*)(FxManager_c*))0x4A9400)(this); + // ((void(__thiscall*)(FxManager_c*))0x4A9400)(this); + + FxSystem_c* system; + do { + do { + auto numItems = m_FxSystems.GetNumItems(); + system = m_FxSystems.GetItemOffset(true, CGeneral::GetRandomNumber() % numItems); + } while (system->m_MustCreateParticles); + } while (!system->m_SystemBP->FreePrtFromSystem(system)); } // 0x4A93E0 -void FxManager_c::SetWindData(RwV3d* dir, float* speed) { - m_pWindDir = dir; +void FxManager_c::SetWindData(CVector* dir, float* speed) { + m_pWindDir = dir; m_pfWindSpeed = speed; } // 0x4A9440 RwMatrix* FxManager_c::FxRwMatrixCreate() { - return ((RwMatrix * (__thiscall*)(FxManager_c*))0x4A9440)(this); + return m_apMatrices[m_nCurrentMatrix++]; } // 0x4A9460 @@ -152,64 +296,79 @@ void FxManager_c::FxRwMatrixDestroy(RwMatrix* matrix) { m_nCurrentMatrix--; } -// 0x4A9500 -bool FxManager_c::ShouldCreate(FxSystemBP_c* bpSystem, RwMatrix* transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { - return ((bool(__thiscall*)(FxManager_c*, FxSystemBP_c*, RwMatrix*, RwMatrix*, bool))0x4A9500)(this, bpSystem, transform, objectMatrix, ignoreBoundingChecks); -} - -// 0x4A95C0 -FxSystem_c* FxManager_c::CreateFxSystem(FxSystemBP_c* bpSystem, RwMatrix* transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { - return plugin::CallMethodAndReturn(this, bpSystem, transform, objectMatrix, ignoreBoundingChecks); - - /* - if (!bpSystem || !FxManager_c::ShouldCreate(bpSystem, transform, objectMatrix, ignoreBoundingChecks)) - return nullptr; - - auto fx = new FxSystem_c(); - fx->Init(bpSystem, transform, objectMatrix); - m_fxSystems.AddItem(&fx->m_link); - - const auto quality = g_fx.GetFxQuality(); - switch (quality) { - case FXQUALITY_LOW: - fx->SetRateMult(0.5f); - break; - case FXQUALITY_MEDIUM: - fx->SetRateMult(0.75f); - break; - case FXQUALITY_HIGH: - case FXQUALITY_VERY_HIGH: - fx->SetRateMult(1.0f); - break; - }; - - return fx; - */ -} - -// char* name -> const char* // 0x4A9BB0 -FxSystem_c* FxManager_c::CreateFxSystem(const char* name, RwMatrix* transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { - return plugin::CallMethodAndReturn(this, name, transform, objectMatrix, ignoreBoundingChecks); +FxSystem_c* FxManager_c::CreateFxSystem(const char* name, const RwMatrix& transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { + FxSystemBP_c* systemBP = FindFxSystemBP(name); + assert(systemBP); + return CreateFxSystem(systemBP, transform, objectMatrix, ignoreBoundingChecks); +} - /* - FxSystemBP_c* bpSystem = FindFxSystemBP(name); - return CreateFxSystem(bpSystem, transform, objectMatrix, ignoreBoundingChecks); - */ +// 0x4A9BE0 +FxSystem_c* FxManager_c::CreateFxSystem(Const char* name, const CVector& point, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { + FxSystemBP_c* systemBP = FindFxSystemBP(name); + assert(systemBP); + return CreateFxSystem(systemBP, point, objectMatrix, ignoreBoundingChecks); } // 0x4A96B0 -FxSystem_c* FxManager_c::CreateFxSystem(FxSystemBP_c* bpSystem, RwV3d* position, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { - return ((FxSystem_c * (__thiscall*)(FxManager_c*, FxSystemBP_c*, RwV3d*, RwMatrix*, bool))0x4A96B0)(this, bpSystem, position, objectMatrix, ignoreBoundingChecks); +FxSystem_c* FxManager_c::CreateFxSystem(FxSystemBP_c* systemBP, const CVector& point, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { + if (systemBP) { + auto* pointMatrix = FxRwMatrixCreate(); + RwMatrixSetIdentity(pointMatrix); + RwV3dAssign(RwMatrixGetPos(pointMatrix), &point); + RwMatrixUpdate(pointMatrix); + + FxSystem_c* system = CreateFxSystem(systemBP, *pointMatrix, objectMatrix, ignoreBoundingChecks); + + FxRwMatrixDestroy(pointMatrix); + return system; + } + return nullptr; } -// char* name -> const char* -// 0x4A9BE0 -FxSystem_c* FxManager_c::CreateFxSystem(const char* name, RwV3d* position, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { - return plugin::CallMethodAndReturn(this, name, position, objectMatrix, ignoreBoundingChecks); +// 0x4A9500 +bool FxManager_c::ShouldCreate(FxSystemBP_c* system, const RwMatrix& transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { + if (ignoreBoundingChecks) + return true; + + if (!system->m_BoundingSphere) + return true; + + auto* curr = FxRwMatrixCreate(); + if (objectMatrix) + RwMatrixMultiply(curr, &transform, objectMatrix); + else + *curr = transform; + + FxSphere_c pointsOut; + RwV3dTransformPoints(&pointsOut.m_vecCenter, reinterpret_cast(system->m_BoundingSphere), 1, curr); + pointsOut.m_fRadius = system->m_BoundingSphere->m_fRadius; + FxRwMatrixDestroy(curr); + return GetFrustumInfo()->IsCollision(&pointsOut) != 0; +} - /* - FxSystemBP_c * bpSystem = FindFxSystemBP(name); - return CreateFxSystem(bpSystem, position, objectMatrix, ignoreBoundingChecks); - */ +// 0x4A95C0 +FxSystem_c* FxManager_c::CreateFxSystem(FxSystemBP_c* systemBP, const RwMatrix& transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks) { + if (systemBP && ShouldCreate(systemBP, transform, objectMatrix, ignoreBoundingChecks)) { + auto* fx = new FxSystem_c(); + fx->Init(systemBP, transform, objectMatrix); + m_FxSystems.AddItem(fx); + + const auto quality = g_fx.GetFxQuality(); + switch (quality) { + case FX_QUALITY_LOW: + fx->SetRateMult(0.5f); + break; + case FX_QUALITY_MEDIUM: + fx->SetRateMult(0.75f); + break; + case FX_QUALITY_HIGH: + case FX_QUALITY_VERY_HIGH: + fx->SetRateMult(1.0f); + break; + }; + + return fx; + } + return nullptr; } diff --git a/source/game_sa/Fx/FxManager.h b/source/game_sa/Fx/FxManager.h index a2fc81ed1e..903c32ab75 100644 --- a/source/game_sa/Fx/FxManager.h +++ b/source/game_sa/Fx/FxManager.h @@ -16,61 +16,71 @@ class FxEmitterPrt_c; +static constexpr auto FX_MANAGER_NUM_EMITTERS = 1000; + class FxManager_c { public: - TList_c m_fxSystemBPs; - TList_c m_fxSystems; - FxEmitterPrt_c* m_pFxEmitters; - TList_c m_fxEmitterParticles; - int32 m_nFxTxdIndex; - RwV3d* m_pWindDir; - float* m_pfWindSpeed; - FxFrustumInfo_c m_frustum; - uint32 m_nCurrentMatrix; - RwMatrix* m_apMatrices[8]; - FxMemoryPool_c m_pool; - bool m_bHeatHazeEnabled; + TList_c m_FxSystemBPs; + TList_c m_FxSystems; + FxEmitterPrt_c* m_FxEmitters; + TList_c m_FxEmitterParticles; + int32 m_nFxTxdIndex; + CVector* m_pWindDir; + float* m_pfWindSpeed; + FxFrustumInfo_c m_Frustum; + uint32 m_nCurrentMatrix; + std::array m_apMatrices; + FxMemoryPool_c m_Pool; + bool m_bHeatHazeEnabled; public: static void InjectHooks(); FxManager_c(); + ~FxManager_c() = default; // 0x4A90A0 FxManager_c* Constructor(); - - ~FxManager_c(); FxManager_c* Destructor(); bool Init(); void Exit(); void DestroyFxSystem(FxSystem_c* system); void DestroyAllFxSystems(); - void Update(RwCamera* arg0, float timeDelta); - bool LoadFxProject(const char* filename); + bool LoadFxProject(const char* path); void UnloadFxProject(); - FxSystemBP_c* LoadFxSystemBP(char* filename, int32 file); + void LoadFxSystemBP(Const char* filename, FILESTREAM file); FxSystemBP_c* FindFxSystemBP(const char* name); - FxFrustumInfo_c* GetFrustumInfo(); + FxFrustumInfo_c* GetFrustumInfo() { return &m_Frustum; } // 0x4A9130 void CalcFrustumInfo(RwCamera* camera); void ReturnParticle(FxEmitterPrt_c* emitter); - FxEmitterPrt_c* GetParticle(int8 arg0); + Particle_c* GetParticle(int8 primType); void FreeUpParticle(); - void Render(RwCamera* camera, uint8 arg1); + void Update(RwCamera* camera, float timeDelta); + void Render(RwCamera* camera, bool bHeatHaze); - void SetWindData(RwV3d* dir, float* speed); + void SetWindData(CVector* dir, float* speed); RwMatrix* FxRwMatrixCreate(); void FxRwMatrixDestroy(RwMatrix* matrix); - bool ShouldCreate(FxSystemBP_c* bpSystem, RwMatrix* transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); - FxSystem_c* CreateFxSystem(FxSystemBP_c* bpSystem, RwMatrix* transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); - FxSystem_c* CreateFxSystem(const char* name, RwMatrix* transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); - FxSystem_c* CreateFxSystem(FxSystemBP_c* bpSystem, RwV3d* position, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); - FxSystem_c* CreateFxSystem(const char* name, RwV3d* position, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); + bool ShouldCreate(FxSystemBP_c* system, const RwMatrix& transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); + FxSystem_c* CreateFxSystem(Const char* name, const RwMatrix& transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); + FxSystem_c* CreateFxSystem(Const char* name, const CVector& point, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); + FxSystem_c* CreateFxSystem(FxSystemBP_c* systemBP, const CVector& point, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); + FxSystem_c* CreateFxSystem(FxSystemBP_c* systemBP, const RwMatrix& transform, RwMatrix* objectMatrix, bool ignoreBoundingChecks = false); + + FxMemoryPool_c& GetMemPool() { return m_Pool; } + + template + Type* Allocate(int32 count) { + const auto size = sizeof(Type) * count; + const auto align = std::min(sizeof(Type), sizeof(int32)); + return (Type*)GetMemPool().GetMem(size, align); + } }; VALIDATE_SIZE(FxManager_c, 0xBC); diff --git a/source/game_sa/Fx/FxMemoryPool.h b/source/game_sa/Fx/FxMemoryPool.h index db4e012cc9..04a2621152 100644 --- a/source/game_sa/Fx/FxMemoryPool.h +++ b/source/game_sa/Fx/FxMemoryPool.h @@ -12,8 +12,8 @@ class FxMemoryPool_c { public: uint8* m_pData; - uint32 m_nSize; - uint32 m_nPosition; + size_t m_nSize; + size_t m_nPosition; // offset, current byte public: static void InjectHooks(); @@ -24,7 +24,7 @@ class FxMemoryPool_c { void Init(); void Exit(); void Reset(); - void* GetMem(int32 size, int32 align); + void* GetMem(int32 size, int32 align = sizeof(void*)); void Optimise(); }; diff --git a/source/game_sa/Fx/FxPlane.h b/source/game_sa/Fx/FxPlane.h index e9b76cc824..411773df52 100644 --- a/source/game_sa/Fx/FxPlane.h +++ b/source/game_sa/Fx/FxPlane.h @@ -1,17 +1,16 @@ -/* - Plugin-SDK file - Authors: GTA Community. See more here - https://github.com/DK22Pac/plugin-sdk - Do not delete this comment block. Respect others' work! -*/ #pragma once #include "RenderWare.h" -class FxPlane_c { +class FxPlane_c : public RwPlane { public: - RwV3d m_vecNormal; - float m_fDistance; -}; + // CVector m_Normal; + // float m_fDistance; -VALIDATE_SIZE(FxPlane_c, 0x10); \ No newline at end of file + FxPlane_c& operator=(const RwPlane& rhs) { + normal = rhs.normal; + distance = rhs.distance; + return *this; + } +}; +VALIDATE_SIZE(FxPlane_c, 0x10); diff --git a/source/game_sa/Fx/FxPrim.cpp b/source/game_sa/Fx/FxPrim.cpp new file mode 100644 index 0000000000..fb555f9c44 --- /dev/null +++ b/source/game_sa/Fx/FxPrim.cpp @@ -0,0 +1,21 @@ +#include "StdInc.h" + +#include "FxPrim.h" + +// 0x4A9F20 +FxPrim_c::FxPrim_c() { + m_PrimBP = nullptr; + m_System = nullptr; + m_bEnabled = true; +} + +// 0x4A9F40 +FxPrim_c::~FxPrim_c() { + m_PrimBP = nullptr; + m_System = nullptr; +} + +// 0x4A9F50 +void FxPrim_c::Enable(bool enabled) { + m_bEnabled = enabled; +} diff --git a/source/game_sa/Fx/FxPrim.h b/source/game_sa/Fx/FxPrim.h new file mode 100644 index 0000000000..87b563123f --- /dev/null +++ b/source/game_sa/Fx/FxPrim.h @@ -0,0 +1,23 @@ +#pragma once + +class FxPrimBP_c; +class FxSystem_c; + +class FxPrim_c { +public: + FxPrimBP_c* m_PrimBP; + FxSystem_c* m_System; + bool m_bEnabled; + +public: + FxPrim_c(); + virtual ~FxPrim_c() = 0; + + virtual bool Init(FxPrimBP_c* prim, FxSystem_c* system) = 0; + virtual void Update(float a2, float a3) = 0; + virtual void Reset() = 0; + virtual void AddParticle(RwMatrix* mat, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal) = 0; + virtual void AddParticle(CVector* pos, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float brightness, bool createLocal) = 0; + + void Enable(bool enabled); +}; diff --git a/source/game_sa/Fx/FxPrimBP.cpp b/source/game_sa/Fx/FxPrimBP.cpp index 1355ee7cf9..0b0171bb1b 100644 --- a/source/game_sa/Fx/FxPrimBP.cpp +++ b/source/game_sa/Fx/FxPrimBP.cpp @@ -1,41 +1,87 @@ #include "StdInc.h" +#include "FxPrimBP.h" +#include "FxTools.h" + void FxPrimBP_c::InjectHooks() { RH_ScopedClass(FxPrimBP_c); RH_ScopedCategory("Fx"); - RH_ScopedInstall(GetRWMatrix, 0x4A9DC0, { .reversed = false }); + RH_ScopedInstall(GetRWMatrix, 0x4A9DC0); RH_ScopedInstall(Load, 0x5C2010, { .reversed = false }); } // 0x4A9CF0 FxPrimBP_c::FxPrimBP_c() { - std::ranges::fill(m_apTextures, nullptr); + m_apTextures.fill(nullptr); } // 0x4A9D20 FxPrimBP_c::~FxPrimBP_c() { + // FIX_BUGS: They forgot destroy third texture (texture 3 destroyed twice) for (auto& texture : m_apTextures) { RwTextureDestroy(texture); + texture = nullptr; } } // 0x4A9DC0 -void FxPrimBP_c::GetRWMatrix(RwMatrix* outMatrix) { - plugin::CallMethod<0x4A9DC0, FxPrimBP_c*, RwMatrix*>(this, outMatrix); - return; - +void FxPrimBP_c::GetRWMatrix(RwMatrix& outMatrix) { if (m_pMatrixBuffered) { - m_pMatrixBuffered->UncompressInto(outMatrix); + m_pMatrixBuffered->CopyToRwMatrix(outMatrix); } else { - outMatrix->right = { 1.0f, 0.0f, 0.0f }; - outMatrix->up = { 0.0f, 1.0f, 0.0f }; - outMatrix->at = { 0.0f, 0.0f, 1.0f }; - outMatrix->pos = { 0.0f, 0.0f, 0.0f }; + RwMatrixSetIdentity(&outMatrix); } } // 0x5C2010 bool FxPrimBP_c::Load(FILESTREAM file, int32 version, FxName32_t* textureNames) { return plugin::CallMethodAndReturn(this, file, version, textureNames); + + char line[256], field[128]; + + ReadFieldImpl(file, field, "FX_PRIM_BASE_DATA:"); + ReadFieldImpl(file, field, "NAME:"); + + CVector mat[4]; + ReadLine(file, line, sizeof(line)); + VERIFY(sscanf( + line, + "%s %f %f %f %f %f %f %f %f %f %f %f %f", + field, + &mat[0].x, &mat[0].y, &mat[0].z, // right + &mat[1].x, &mat[1].y, &mat[1].z, // up + &mat[2].x, &mat[2].y, &mat[2].z, // at + &mat[3].x, &mat[3].y, &mat[3].z // pos + ) == 13); + + // if it's identity matrix, don't allocate it + if (mat[0] == CVector{1.0f, 0.0f, 0.0f} && + mat[1] == CVector{0.0f, 1.0f, 0.0f} && + mat[2] == CVector{0.0f, 0.0f, 1.0f} && + mat[3] == CVector{0.0f, 0.0f, 0.0f} + ) { + m_pMatrixBuffered = nullptr; + } else { + m_pMatrixBuffered = g_fxMan.Allocate(1); + assert(m_pMatrixBuffered); + + m_pMatrixBuffered->CopyFromVectors(mat[0], mat[1], mat[2], mat[3]); + } + + assert(textureNames); + ReadFieldImpl(file, textureNames[0], "TEXTURE:"); + + if (version > 101) { + ReadFieldImpl(file, textureNames[1], "TEXTURE2:"); + ReadFieldImpl(file, textureNames[2], "TEXTURE3:"); + ReadFieldImpl(file, textureNames[3], "TEXTURE4:"); + } + + m_bAlphaOn = ReadField(file, "ALPHAON:"); + m_nSrcBlendId = ReadField(file, "SRCBLENDID:"); + m_nDstBlendId = ReadField(file, "DSTBLENDID:"); + + m_FxInfoManager.Load(file, version); + return true; } diff --git a/source/game_sa/Fx/FxPrimBP.h b/source/game_sa/Fx/FxPrimBP.h index 308f7553f8..e90e572ffe 100644 --- a/source/game_sa/Fx/FxPrimBP.h +++ b/source/game_sa/Fx/FxPrimBP.h @@ -9,73 +9,55 @@ #include "RenderWare.h" #include "List_c.h" #include "FxInfoManager.h" +#include "extensions/FixedVector.hpp" class FxPrim_c; +class FxPrimBP_c; -struct FxName32_t { - char value[32]; -}; +// struct FxName32_t { +// char value[32]; +// }; +using FxName32_t = char[32]; struct FxBufferedMatrix { - int16 rightX; - int16 rightY; - int16 rightZ; - - int16 upX; - int16 upY; - int16 upZ; + static constexpr float FX_RECIPROCAL = std::numeric_limits::max(); - int16 apX; - int16 apY; - int16 apZ; + FixedVector right; + FixedVector up; + FixedVector at; + FixedVector pos; - int16 posX; - int16 posY; - int16 posZ; + void CopyFromRwMatrix(const RwMatrix& mat) { + right = static_cast(mat.right); + up = static_cast(mat.up); + at = static_cast(mat.at); + pos = static_cast(mat.pos); + } - static constexpr float COMPRESS_VALUE = 32767.0f; - inline void CompressInto(RwMatrix*mat) { - mat->right.x = (float)rightX * COMPRESS_VALUE; - mat->right.y = (float)rightY * COMPRESS_VALUE; - mat->right.z = (float)rightZ * COMPRESS_VALUE; - mat->up.x = (float)upX * COMPRESS_VALUE; - mat->up.y = (float)upY * COMPRESS_VALUE; - mat->up.z = (float)upZ * COMPRESS_VALUE; - mat->at.x = (float)apX * COMPRESS_VALUE; - mat->at.y = (float)apY * COMPRESS_VALUE; - mat->at.z = (float)apZ * COMPRESS_VALUE; - mat->pos.x = (float)posX * COMPRESS_VALUE; - mat->pos.y = (float)posY * COMPRESS_VALUE; - mat->pos.z = (float)posZ * COMPRESS_VALUE; + void CopyFromVectors(const CVector& r, const CVector& u, const CVector& a, const CVector& p) { + right = r; up = u; at = a; pos = p; } - inline void UncompressInto(RwMatrix* mat) { // maybe wrong - mat->right.x = (float)rightX / COMPRESS_VALUE; - mat->right.y = (float)rightY / COMPRESS_VALUE; - mat->right.z = (float)rightZ / COMPRESS_VALUE; - mat->up.x = (float)upX / COMPRESS_VALUE; - mat->up.y = (float)upY / COMPRESS_VALUE; - mat->up.z = (float)upZ / COMPRESS_VALUE; - mat->at.x = (float)apX / COMPRESS_VALUE; - mat->at.y = (float)apY / COMPRESS_VALUE; - mat->at.z = (float)apZ / COMPRESS_VALUE; - mat->pos.x = (float)posX / COMPRESS_VALUE; - mat->pos.y = (float)posY / COMPRESS_VALUE; - mat->pos.z = (float)posZ / COMPRESS_VALUE; + void CopyToRwMatrix(RwMatrix& out) { + out.right = right; + out.up = up; + out.at = at; + out.pos = pos; } }; +VALIDATE_SIZE(FxBufferedMatrix, 0x18); class FxPrimBP_c { public: - char field_4; - uint8 m_nSrcBlendId; - uint8 m_nDstBlendId; - uint8 m_nAlphaOn; - FxBufferedMatrix* m_pMatrixBuffered; - RwTexture* m_apTextures[4]; - int32 field_1C; - List_c m_EmitterPtrs; - FxInfoManager_c m_FxInfoManager; + int8 m_Type; + uint8 m_nSrcBlendId; + uint8 m_nDstBlendId; + bool m_bAlphaOn; + FxBufferedMatrix* m_pMatrixBuffered; // int16* m_matVals; + std::array m_apTextures; + int32 m_TxdIndex; + TList_c m_Particles; + FxInfoManager_c m_FxInfoManager; public: static void InjectHooks(); @@ -83,11 +65,13 @@ class FxPrimBP_c { FxPrimBP_c(); virtual ~FxPrimBP_c() = 0; virtual bool Load(FILESTREAM file, int32 version, FxName32_t* textureNames) = 0; - virtual bool LoadTextures(FxName32_t* textureNames, int32 iVersion) = 0; + virtual bool LoadTextures(FxName32_t* textureNames, int32 version) = 0; virtual FxPrim_c* CreateInstance() = 0; virtual void Update(float a1) = 0; + virtual void Render(RwCamera* camera, uint32 a2, float a3, bool bCanRenderHeatHaze) = 0; + virtual bool FreePrtFromPrim(FxSystem_c* system) = 0; - void GetRWMatrix(RwMatrix* outMatrix); + void GetRWMatrix(RwMatrix& outMatrix); }; VALIDATE_SIZE(FxPrimBP_c, 0x40); diff --git a/source/game_sa/Fx/FxPrtMult.cpp b/source/game_sa/Fx/FxPrtMult.cpp index 2a0a516029..f7601137c0 100644 --- a/source/game_sa/Fx/FxPrtMult.cpp +++ b/source/game_sa/Fx/FxPrtMult.cpp @@ -5,20 +5,20 @@ // 0x4AB270 FxPrtMult_c::FxPrtMult_c() { SetColor(1.0f, 1.0f, 1.0f, 1.0f); - m_fSize = 1.0f; - field_14 = 1.0f; - m_fLife = 1.0f; + m_fSize = 1.0f; + m_Rot = 1.0f; + m_fLife = 1.0f; } // 0x4AB290 -FxPrtMult_c::FxPrtMult_c(float red, float green, float blue, float alpha, float size, float arg5, float lastFactor) { - SetUp(red, green, blue, alpha, size, arg5, lastFactor); +FxPrtMult_c::FxPrtMult_c(float red, float green, float blue, float alpha, float size, float rot, float life) { + SetUp(red, green, blue, alpha, size, rot, life); } // 0x4AB2D0 -void FxPrtMult_c::SetUp(float red, float green, float blue, float alpha, float size, float arg5, float lastFactor) { +void FxPrtMult_c::SetUp(float red, float green, float blue, float alpha, float size, float rot, float life) { SetColor(red, green, blue, alpha); - m_fSize = size; - field_14 = arg5; - m_fLife = lastFactor; + m_fSize = size; + m_Rot = rot; + m_fLife = life; } diff --git a/source/game_sa/Fx/FxPrtMult.h b/source/game_sa/Fx/FxPrtMult.h index 6fa4c3d837..b4fcbcf0f5 100644 --- a/source/game_sa/Fx/FxPrtMult.h +++ b/source/game_sa/Fx/FxPrtMult.h @@ -6,14 +6,14 @@ class FxPrtMult_c { public: RwRGBAReal m_Color; float m_fSize; - float field_14; + float m_Rot; float m_fLife; public: FxPrtMult_c(); - FxPrtMult_c(float red, float green, float blue, float alpha, float size, float arg5, float lastFactor); + FxPrtMult_c(float red, float green, float blue, float alpha, float size, float rot, float life); - void SetUp(float red, float green, float blue, float alpha, float size, float arg5, float lastFactor); + void SetUp(float red, float green, float blue, float alpha, float size, float rot, float life); inline void SetColor(float red, float green, float blue, float alpha = 1.0f) { m_Color.red = red; diff --git a/source/game_sa/Fx/FxSphere.cpp b/source/game_sa/Fx/FxSphere.cpp index 45a7bf3592..b52bdb560d 100644 --- a/source/game_sa/Fx/FxSphere.cpp +++ b/source/game_sa/Fx/FxSphere.cpp @@ -1,32 +1,50 @@ -/* - Plugin-SDK file - Authors: GTA Community. See more here - https://github.com/DK22Pac/plugin-sdk - Do not delete this comment block. Respect others' work! -*/ #include "StdInc.h" +#include "FxSphere.h" +#include "FxPlane.h" + +void FxSphere_c::InjectHooks() { + RH_ScopedClass(FxSphere_c); + RH_ScopedCategory("Fx"); + + RH_ScopedInstall(operator new, 0x4A9F80); + RH_ScopedOverloadedInstall(operator delete, "", 0x4A9FB0, void(*)(void*)); + RH_ScopedInstall(IsCollision, 0x4A9FC0); + RH_ScopedInstall(GetDistToPlane, 0x4AA010); +} + // 0x4A9F70 FxSphere_c::FxSphere_c() { - ((void(__thiscall*)(FxSphere_c*))0x4A9F70)(this); + m_nNumPlanesPassed = 0; } // 0x4A9F80 -void* FxSphere_c::operator new(uint32 size, int32 notAligned) { - return ((void* (__cdecl*)(uint32, int32))0x4A9F80)(size, notAligned); +void* FxSphere_c::operator new(uint32 size, bool32 bUseGlobalHeep) { + if (bUseGlobalHeep) + return CMemoryMgr::Malloc(size); + else + return g_fxMan.GetMemPool().GetMem(size); } // 0x4A9FB0 void FxSphere_c::operator delete(void* data) { - ((void(__cdecl*)(void*))0x4A9FB0)(data); + CMemoryMgr::Free(data); +} + +void FxSphere_c::operator delete(void* data, bool32 bUseGlobalHeep) { + if (bUseGlobalHeep) { + CMemoryMgr::Free(data); + } } // 0x4A9FC0 -bool FxSphere_c::IsCollision(FxSphere_c* sphere) { - return ((bool(__thiscall*)(FxSphere_c*, FxSphere_c * sphere))0x4A9FC0)(this, sphere); +bool FxSphere_c::IsCollision(FxSphere_c* sphere) const { + assert(sphere); + return std::powf(m_fRadius + sphere->m_fRadius, 2) > (sphere->m_vecCenter - m_vecCenter).SquaredMagnitude(); } // 0x4AA010 -float FxSphere_c::GetDistToPlane(FxPlane_c* plane) { - return ((float(__thiscall*)(FxSphere_c*, FxPlane_c*))0x4AA010)(this, plane); -} \ No newline at end of file +float FxSphere_c::GetDistToPlane(FxPlane_c* plane) const { + assert(plane); + return DotProduct(plane->normal, m_vecCenter) - plane->distance; +} diff --git a/source/game_sa/Fx/FxSphere.h b/source/game_sa/Fx/FxSphere.h index d36b8491fd..005d5eb210 100644 --- a/source/game_sa/Fx/FxSphere.h +++ b/source/game_sa/Fx/FxSphere.h @@ -1,27 +1,26 @@ -/* - Plugin-SDK file - Authors: GTA Community. See more here - https://github.com/DK22Pac/plugin-sdk - Do not delete this comment block. Respect others' work! -*/ #pragma once #include "FxPlane.h" +#include "Sphere.h" -class FxSphere_c { +class FxSphere_c : public CSphere { public: - RwV3d m_vecCenter; - float m_fRadius; - uint32 m_nNumPlanesPassed; + uint32 m_nNumPlanesPassed; // m_LastPlaneRejected public: - static void* operator new(uint32 size, int32 notAligned); + static void InjectHooks(); + + static void* operator new(uint32 size, bool32 bUseGlobalHeep); static void operator delete(void* data); + static void operator delete(void* data, bool32 bUseGlobalHeep); FxSphere_c(); - bool IsCollision(FxSphere_c* sphere); - float GetDistToPlane(FxPlane_c* plane); + // NOTSA + FxSphere_c(CVector center, float radius) : CSphere(center, radius), m_nNumPlanesPassed(0) {} + + inline bool IsCollision(FxSphere_c* sphere) const; + inline float GetDistToPlane(FxPlane_c* plane) const; }; -VALIDATE_SIZE(FxSphere_c, 0x14); \ No newline at end of file +VALIDATE_SIZE(FxSphere_c, 0x14); diff --git a/source/game_sa/Fx/FxSystem.cpp b/source/game_sa/Fx/FxSystem.cpp index 40aa8bfac8..9435618d32 100644 --- a/source/game_sa/Fx/FxSystem.cpp +++ b/source/game_sa/Fx/FxSystem.cpp @@ -1,189 +1,426 @@ -/* - Plugin-SDK file - Authors: GTA Community. See more here - https://github.com/DK22Pac/plugin-sdk - Do not delete this comment block. Respect others' work! -*/ - #include "StdInc.h" +#include "FxSystem.h" +#include "FxPrim.h" +#include "FxEmitter.h" +#include "FxEmitterBP.h" +#include "FxEmitterPrt.h" +#include "FxBox.h" +#include "Particle.h" + +void FxSystem_c::InjectHooks() { + RH_ScopedClass(FxSystem_c); + RH_ScopedCategory("Fx"); + + RH_ScopedInstall(Constructor, 0x4AAF00); + RH_ScopedInstall(Destructor, 0x4AA260); + RH_ScopedInstall(Init, 0x4AA750); + RH_ScopedInstall(Exit, 0x4AA840); + RH_ScopedInstall(Play, 0x4AA2F0); + RH_ScopedInstall(Pause, 0x4AA370); + RH_ScopedInstall(Stop, 0x4AA390); + RH_ScopedInstall(PlayAndKill, 0x4AA3D0); + RH_ScopedInstall(Kill, 0x4AA3F0); + RH_ScopedInstall(AttachToBone, 0x4AA400); + RH_ScopedOverloadedInstall(AddParticle, "v3d", 0x4AA440, void(FxSystem_c::*)(CVector*,CVector*,float,FxPrtMult_c*,float,float,float,bool), {.reversed = false}); + RH_ScopedOverloadedInstall(AddParticle, "mat", 0x4AA540, void(FxSystem_c::*)(RwMatrix*,CVector*,float,FxPrtMult_c*,float,float,float,bool), {.reversed = false}); + RH_ScopedInstall(EnablePrim, 0x4AA610); + RH_ScopedInstall(SetMatrix, 0x4AA630); + RH_ScopedInstall(SetOffsetPos, 0x4AA660); + RH_ScopedInstall(AddOffsetPos, 0x4AA690); + RH_ScopedInstall(CopyParentMatrix, 0x4AA890); + RH_ScopedInstall(GetCompositeMatrix, 0x4AA8C0); + RH_ScopedInstall(GetPlayStatus, 0x4AA900); + RH_ScopedInstall(ForAllParticles, 0x4AA930); + RH_ScopedInstall(UpdateBoundingBoxCB, 0x4AA9A0, {.reversed=false}); + RH_ScopedInstall(GetBoundingSphereWld, 0x4AAAD0); + RH_ScopedInstall(GetBoundingSphereLcl, 0x4AAB50); + RH_ScopedInstall(SetBoundingSphere, 0x4AAB80); + RH_ScopedInstall(ResetBoundingSphere, 0x4AABF0); + RH_ScopedInstall(SetConstTime, 0x4AA6C0); + RH_ScopedInstall(SetRateMult, 0x4AA6F0); + RH_ScopedInstall(SetTimeMult, 0x4AA710); + RH_ScopedInstall(SetVelAdd, 0x4AA730); + RH_ScopedInstall(SetLocalParticles, 0x4AA910); + RH_ScopedInstall(SetZTestEnable, 0x4AAC50); + RH_ScopedInstall(SetMustCreatePrts, 0x4AAC70); + RH_ScopedInstall(DoFxAudio, 0x4AAC90); + RH_ScopedInstall(IsVisible, 0x4AAF30); + RH_ScopedInstall(Update, 0x4AAF70, {.reversed=false}); +} +FxSystem_c* FxSystem_c::Constructor() { this->FxSystem_c::FxSystem_c(); return this; } +FxSystem_c* FxSystem_c::Destructor() { this->FxSystem_c::~FxSystem_c(); return this; } + +// 0x4AAF00 +FxSystem_c::FxSystem_c() : ListItem_c() { + m_SystemBP = nullptr; + m_BoundingSphere = nullptr; + m_MustCreateParticles = false; +} + // 0x4AA260 FxSystem_c::~FxSystem_c() { - ((void(__thiscall *)(FxSystem_c*))0x4AA260)(this); + m_SystemBP = nullptr; + + if (m_BoundingSphere) // looks pointless + CMemoryMgr::Free(m_BoundingSphere); + + if (m_allocatedParentMat) + RwMatrixDestroy(m_ParentMatrix); +} + +// 0x4AA750 +bool FxSystem_c::Init(FxSystemBP_c* systemBP, const RwMatrix& local, RwMatrix* parent) { + m_SystemBP = systemBP; + m_LocalMatrix = local; + m_ParentMatrix = parent; + m_fCurrentTime = 0; + m_nPlayStatus = eFxSystemPlayStatus::FX_STOPPED; + m_nKillStatus = eFxSystemKillStatus::FX_NOT_KILLED; + m_UseConstTime = false; + m_nConstTime = 0; + m_nRateMult = 1000; + m_nTimeMult = 1000; + m_VelAdd = CVector(); + m_useZTest = true; + + m_BoundingSphere = nullptr; + if (m_SystemBP->m_BoundingSphere) { + m_BoundingSphere = new (true) FxSphere_c(*systemBP->m_BoundingSphere); + } + + m_Prims = new FxPrim_c * [m_SystemBP->m_nNumPrims]; + for (auto i = 0; i < m_SystemBP->m_nNumPrims; i++) { + m_Prims[i] = m_SystemBP->m_Prims[i]->CreateInstance(); + m_Prims[i]->Init(m_SystemBP->m_Prims[i], this); + } + + m_FireAE.Initialise(this); + return true; +} + +// 0x4AA840 +void FxSystem_c::Exit() { + for (auto& prim : GetPrims()) { + delete prim; + } + delete[] m_Prims; + m_FireAE.Terminate(); } // 0x4AA2F0 void FxSystem_c::Play() { - ((void(__thiscall *)(FxSystem_c*))0x4AA2F0)(this); + if (m_nPlayStatus != eFxSystemPlayStatus::T2) { + m_fCurrentTime = 0; + for (auto& prim : GetPrims()) { + prim->Reset(); + } + } + + m_nKillStatus = eFxSystemKillStatus::FX_NOT_KILLED; + m_nPlayStatus = eFxSystemPlayStatus::FX_PLAYING; + + m_stopParticleCreation = false; + m_prevCulled = false; + + m_LoopInterval = (float)(CGeneral::GetRandomNumber() % 10'000) / 10'000.0f * (m_SystemBP->m_fLoopLength - m_SystemBP->m_fLoopIntervalMin) + m_SystemBP->m_fLoopIntervalMin; } // 0x4AA370 void FxSystem_c::Pause() { - ((void(__thiscall *)(FxSystem_c*))0x4AA370)(this); + if (m_nPlayStatus == eFxSystemPlayStatus::FX_STOPPED) + return; + + if (m_nPlayStatus == eFxSystemPlayStatus::T2) { + m_nPlayStatus = eFxSystemPlayStatus::FX_PLAYING; + } else { + m_nPlayStatus = eFxSystemPlayStatus::T2; + } } // 0x4AA390 void FxSystem_c::Stop() { - ((void(__thiscall *)(FxSystem_c*))0x4AA390)(this); + m_nPlayStatus = eFxSystemPlayStatus::FX_STOPPED; + m_fCurrentTime = 0; + + for (auto& prim : GetPrims()) { + prim->Reset(); + } } // 0x4AA3D0 void FxSystem_c::PlayAndKill() { - ((void(__thiscall *)(FxSystem_c*))0x4AA3D0)(this); + Play(); + + if (m_SystemBP->m_nPlayMode != 0) + return; + + m_nKillStatus = FX_PLAY_AND_KILL; } // 0x4AA3F0 void FxSystem_c::Kill() { - ((void(__thiscall *)(FxSystem_c*))0x4AA3F0)(this); + Stop(); + m_nKillStatus = FX_KILLED; } // 0x4AA400 -void FxSystem_c::AttachToBone(CEntity* entity, int32 boneId) { - ((void(__thiscall *)(FxSystem_c*, CEntity*, int32))0x4AA400)(this, entity, boneId); -} - -// 0x4AA440 -void FxSystem_c::AddParticle(RwV3d* position, RwV3d* velocity, float arg2, FxPrtMult_c* prtMult, float arg4, float brightness, float arg6, uint8 arg8) { - ((void(__thiscall *)(FxSystem_c*, RwV3d*, RwV3d*, float, FxPrtMult_c*, float, float, float, uint8))0x4AA440)(this, position, velocity, arg2, prtMult, arg4, brightness, arg6, arg8); -} - +void FxSystem_c::AttachToBone(CEntity* entity, ePedBones boneId) { + auto animHier = GetAnimHierarchyFromSkinClump(entity->m_pRwClump); + auto index = RpHAnimIDGetIndex(animHier, boneId); + m_ParentMatrix = &RpHAnimHierarchyGetMatrixArray(animHier)[index]; +} + +auto CanAddParticle() { + switch (g_fx.GetFxQuality()) { + case FX_QUALITY_LOW: + return CGeneral::RandomBool(50.0f); + case FX_QUALITY_MEDIUM: + return CGeneral::RandomBool(75.0f); + default: + return true; + } +} + +// 0x4AA440 +void FxSystem_c::AddParticle(CVector* pos, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float lightMult, float lightMultLimit, bool createLocal) { + if (CanAddParticle()) { + auto brightness = lightMult < lightMultLimit ? 1.0f - lightMultLimit + lightMult : 1.0f; + for (auto& prim : GetPrims()) { + if (prim->m_bEnabled) { + prim->AddParticle(pos, vel, timeSince, fxMults, rotZ, brightness, createLocal); + } + } + } +} + +// unused // 0x4AA540 -void FxSystem_c::AddParticle(RwMatrix* transform, RwV3d* position, float arg2, FxPrtMult_c* prtMult, float arg4, float arg5, float arg6, uint8 arg7) { - ((void(__thiscall *)(FxSystem_c*, RwMatrix*, RwV3d*, float, FxPrtMult_c*, float, float, float, uint8))0x4AA540)(this, transform, position, arg2, prtMult, arg4, arg5, arg6, arg7); +void FxSystem_c::AddParticle(RwMatrix* mat, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float lightMult, float lightMultLimit, bool createLocal) { + if (CanAddParticle()) { + auto brightness = lightMult < lightMultLimit ? 1.0f - lightMultLimit + lightMult : 1.0f; + for (auto& prim : GetPrims()) { + prim->AddParticle(mat, vel, timeSince, fxMults, rotZ, brightness, createLocal); + } + } } // 0x4AA610 -void FxSystem_c::EnablePrim(int32 primIndex, uint8 enable) { - ((void(__thiscall *)(FxSystem_c*, int32, uint8))0x4AA610)(this, primIndex, enable); +void FxSystem_c::EnablePrim(int32 primIndex, bool enable) { + m_Prims[primIndex]->Enable(enable); } // 0x4AA630 void FxSystem_c::SetMatrix(RwMatrix* matrix) { - ((void(__thiscall *)(FxSystem_c*, RwMatrix*))0x4AA630)(this, matrix); + if (m_allocatedParentMat) + *m_ParentMatrix = *matrix; + else + m_ParentMatrix = matrix; } -// (RwV3d*) // 0x4AA660 void FxSystem_c::SetOffsetPos(const CVector& pos) { - ((void(__thiscall *)(FxSystem_c*, const CVector&))0x4AA660)(this, pos); + m_LocalMatrix.pos = *(RwV3d*)&pos; + RwMatrixUpdate(&m_LocalMatrix); } // 0x4AA690 -void FxSystem_c::AddOffsetPos(RwV3d* pos) { - ((void(__thiscall *)(FxSystem_c*, RwV3d*))0x4AA690)(this, pos); +void FxSystem_c::AddOffsetPos(CVector* pos) { + RwV3dAdd(&m_LocalMatrix.pos, &m_LocalMatrix.pos, pos); + RwMatrixUpdate(&m_LocalMatrix); } // 0x4AA6C0 -void FxSystem_c::SetConstTime(uint8 arg0, float amount) { - ((void(__thiscall *)(FxSystem_c*, uint8, float))0x4AA6C0)(this, arg0, amount); +void FxSystem_c::SetConstTime(bool on, float time) { + m_UseConstTime = on; + m_nConstTime = (uint16)(time * 256.0f); } // 0x4AA6F0 void FxSystem_c::SetRateMult(float mult) { - ((void(__thiscall *)(FxSystem_c*, float))0x4AA6F0)(this, mult); + m_nRateMult = (uint16)(mult * 1000.0f); } // 0x4AA710 void FxSystem_c::SetTimeMult(float mult) { - ((void(__thiscall *)(FxSystem_c*, float))0x4AA710)(this, mult); + m_nTimeMult = (uint16)(mult * 1000.0f); } // 0x4AA730 -void FxSystem_c::SetVelAdd(RwV3d* velAdd) { - ((void(__thiscall *)(FxSystem_c*, RwV3d*))0x4AA730)(this, velAdd); +void FxSystem_c::SetVelAdd(CVector* velocity) { + m_VelAdd = *velocity; } -// 0x4AA750 -bool FxSystem_c::Init(FxSystemBP_c* arg0, RwMatrix* local, RwMatrix* parent) { - return ((bool(__thiscall *)(FxSystem_c*, FxSystemBP_c*, RwMatrix*, RwMatrix*))0x4AA750)(this, arg0, local, parent); +// 0x4AA910 +void FxSystem_c::SetLocalParticles(bool enable) { + m_createLocal = enable; } -// 0x4AA840 -void FxSystem_c::Exit() { - ((void(__thiscall *)(FxSystem_c*))0x4AA840)(this); +// 0x4AAC50 +void FxSystem_c::SetZTestEnable(bool enable) { + m_useZTest = enable; +} + +// 0x4AAC70 +void FxSystem_c::SetMustCreatePrts(bool enable) { + m_MustCreateParticles = enable; } // 0x4AA890 void FxSystem_c::CopyParentMatrix() { - ((void(__thiscall *)(FxSystem_c*))0x4AA890)(this); + RwMatrix* old = m_ParentMatrix; + RwMatrix* allocated = RwMatrixCreate(); + m_ParentMatrix = allocated; + *allocated = *old; + m_allocatedParentMat = true; } // 0x4AA8C0 void FxSystem_c::GetCompositeMatrix(RwMatrix* out) { - ((void(__thiscall *)(FxSystem_c*, RwMatrix*))0x4AA8C0)(this, out); + if (m_ParentMatrix) + RwMatrixMultiply(out, &m_LocalMatrix, m_ParentMatrix); + else + *out = m_LocalMatrix; } // 0x4AA900 -eFxSystemPlayStatus FxSystem_c::GetPlayStatus() { - return ((eFxSystemPlayStatus(__thiscall *)(FxSystem_c*))0x4AA900)(this); -} - -// 0x4AA910 -void FxSystem_c::SetLocalParticles(uint8 enable) { - ((void(__thiscall *)(FxSystem_c*, uint8))0x4AA910)(this, enable); +eFxSystemPlayStatus FxSystem_c::GetPlayStatus() const { + return m_nPlayStatus; } // 0x4AA930 -uint32 FxSystem_c::ForAllParticles(void(*callback)(Particle_c *, int32, void **), void* data) { - return ((uint32(__thiscall *)(FxSystem_c*, void(*)(Particle_c *, int32, void **), void*))0x4AA930)(this, callback, data); +uint32 FxSystem_c::ForAllParticles(void(*callback)(Particle_c*, int32, FxBox_c**), FxBox_c* data) { + auto count = 0; + + for (auto& prim : m_SystemBP->GetPrims()) { + for (auto* particle = prim->m_Particles.GetHead(); particle; particle = prim->m_Particles.GetNext(particle)) { + if (prim->m_Type && this == particle->m_System) { + callback(particle, 0, &data); + count++; + } + } + } + + return count; } // 0x4AA9A0 -void FxSystem_c::UpdateBoundingBoxCB(Particle_c* particle, int32 arg1, void** data) { - ((void(__cdecl *)(Particle_c*, int32, void**))0x4AA9A0)(particle, arg1, data); +void FxSystem_c::UpdateBoundingBoxCB(Particle_c* particle, int32 a2, FxBox_c** data) { + ((void(__cdecl *)(Particle_c*, int32, FxBox_c**))0x4AA9A0)(particle, a2, data); } // 0x4AAA40 void FxSystem_c::GetBoundingBox(FxBox_c* out) { - ((void(__thiscall *)(FxSystem_c*, FxBox_c*))0x4AAA40)(this, out); + out->Reset(); + + if (ForAllParticles(UpdateBoundingBoxCB, out) != 0) { + return; + } + + auto mat = g_fxMan.FxRwMatrixCreate(); + if (m_ParentMatrix) { + RwMatrixMultiply(mat, &m_LocalMatrix, m_ParentMatrix); + } else { + *mat = m_LocalMatrix; + } + + out->minX = out->maxX = mat->pos.x; + out->minY = out->maxY = mat->pos.y; + out->minZ = out->maxZ = mat->pos.z; + g_fxMan.FxRwMatrixDestroy(mat); } // 0x4AAAD0 bool FxSystem_c::GetBoundingSphereWld(FxSphere_c* out) { - return ((bool(__thiscall *)(FxSystem_c*, FxSphere_c*))0x4AAAD0)(this, out); + if (!m_BoundingSphere) + return false; + + auto mat = g_fxMan.FxRwMatrixCreate(); + GetCompositeMatrix(mat); + + RwV3dTransformPoints(reinterpret_cast(out), reinterpret_cast(m_BoundingSphere), 1, mat); + out->m_fRadius = m_BoundingSphere->m_fRadius; + g_fxMan.FxRwMatrixDestroy(mat); + return true; } // 0x4AAB50 bool FxSystem_c::GetBoundingSphereLcl(FxSphere_c* out) { - return ((bool(__thiscall *)(FxSystem_c*, FxSphere_c*))0x4AAB50)(this, out); + if (!m_BoundingSphere) + return false; + + *out = *m_BoundingSphere; + return true; } // 0x4AAB80 -void FxSystem_c::SetBoundingSphere(FxSphere_c* bound) { - ((void(__thiscall *)(FxSystem_c*, FxSphere_c*))0x4AAB80)(this, bound); +void FxSystem_c::SetBoundingSphere(FxSphere_c* sphere) { + if (sphere) { + if (!m_BoundingSphere) { + m_BoundingSphere = new (true) FxSphere_c(); + return; + } + m_BoundingSphere = sphere; + } else if (m_BoundingSphere) { + CMemoryMgr::Free(m_BoundingSphere); + m_BoundingSphere = nullptr; + } } // 0x4AABF0 void FxSystem_c::ResetBoundingSphere() { - ((void(__thiscall *)(FxSystem_c*))0x4AABF0)(this); -} - -// 0x4AAC50 -void FxSystem_c::SetZTestEnable(uint8 enable) { - ((void(__thiscall *)(FxSystem_c*, uint8))0x4AAC50)(this, enable); -} - -// 0x4AAC70 -void FxSystem_c::SetMustCreatePrts(uint8 enable) { - ((void(__thiscall *)(FxSystem_c*, uint8))0x4AAC70)(this, enable); + if (m_SystemBP->m_BoundingSphere) { + if (!m_BoundingSphere) { + m_BoundingSphere = new (true) FxSphere_c(); + } + m_BoundingSphere = m_SystemBP->m_BoundingSphere; + } else if (m_BoundingSphere) { + CMemoryMgr::Free(m_BoundingSphere); + m_BoundingSphere = nullptr; + } } // 0x4AAC90 -void FxSystem_c::DoFxAudio(CVector posn) { - ((void(__thiscall *)(FxSystem_c*, CVector))0x4AAC90)(this, posn); -} - -// 0x4AAF00 -FxSystem_c::FxSystem_c() { - ((void(__thiscall *)(FxSystem_c*))0x4AAF00)(this); +void FxSystem_c::DoFxAudio(CVector pos) { + constexpr struct { const char* hash; eAudioEvents event; } mapping[] = { + { "fire", AE_FIRE }, + { "fire_med", AE_FIRE_MEDIUM }, + { "fire_large", AE_FIRE_LARGE }, + { "fire_car", AE_FIRE_CAR }, + { "fire_bike", AE_FIRE_BIKE }, + { "Flame", AE_FIRE_FLAME }, + { "molotov_flame", AE_FIRE_MOLOTOV_FLAME }, + { "water_hydrant", AE_FIRE_HYDRANT }, + { "water_fountain", AE_FIRE_HYDRANT }, + { "water_fnt_tme", AE_FIRE_HYDRANT }, + { "smoke_flare", AE_SMOKE_FLARE }, + { "teargas", AE_TEARGAS }, + { "heli_dust", AE_HELI_DUST }, + }; + for (auto& [hash, event] : mapping) { + if (m_SystemBP->GetNameKey() == CKeyGen::GetUppercaseKey(hash)) { + m_FireAE.AddAudioEvent(event, pos); + } + } } // 0x4AAF30 bool FxSystem_c::IsVisible() { - return ((bool(__thiscall *)(FxSystem_c*))0x4AAF30)(this); + FxSphere_c sphere; + if (GetBoundingSphereWld(&sphere)) { + FxFrustumInfo_c* info = g_fxMan.GetFrustumInfo(); + if (!info->IsCollision(&sphere)) + return false; + } + return true; } // 0x4AAF70 bool FxSystem_c::Update(RwCamera* camera, float timeDelta) { return ((bool(__thiscall *)(FxSystem_c*, RwCamera*, float))0x4AAF70)(this, camera, timeDelta); } + +// NOTSA +std::span FxSystem_c::GetPrims() { + return std::span{ m_Prims, m_SystemBP->m_nNumPrims }; +} diff --git a/source/game_sa/Fx/FxSystem.h b/source/game_sa/Fx/FxSystem.h index 26d27de192..d9b1dd7d99 100644 --- a/source/game_sa/Fx/FxSystem.h +++ b/source/game_sa/Fx/FxSystem.h @@ -10,93 +10,113 @@ #include "RenderWare.h" #include "Vector.h" #include "AEFireAudioEntity.h" -#include "FxPrtMult.h" -#include "FxSphere.h" -#include "FxBox.h" - -enum eFxSystemKillStatus { - FX_NOT_KILLED = 0, - FX_PLAY_AND_KILL = 1, - FX_KILLED = 2 + +enum eFxSystemKillStatus : uint8 { + FX_NOT_KILLED = 0, + FX_PLAY_AND_KILL = 1, // DESTROY_AFTER_FINISHING + FX_KILLED = 2, + FX_3 = 3, }; -enum class eFxSystemPlayStatus { +enum class eFxSystemPlayStatus : uint8 { FX_PLAYING = 0, - FX_STOPPED = 1 + FX_STOPPED = 1, + T2 = 2, }; class FxSystemBP_c; -class Particle_c; +class FxPrtMult_c; +class FxSphere_c; +class FxPrim_c; +class FxPrimBP_c; +class FxBox_c; +struct Particle_c; -class FxSystem_c : public ListItem_c { +class FxSystem_c : public ListItem_c { public: - // ListItem_c m_link; - void *m_pBlueprint; - RwMatrix *m_pParentMatrix; - RwMatrix m_localMatrix; - uint8 m_nPlayStatus; // see eFxSystemPlayStatus - uint8 m_nKillStatus; // see eFxSystemKillStatus - uint8 m_bConstTimeSet; - char field_53; - int32 field_54; - float m_fCameraDistance; + FxSystemBP_c* m_SystemBP; + RwMatrix* m_ParentMatrix; + RwMatrix m_LocalMatrix; // aka Offset Mat + + eFxSystemPlayStatus m_nPlayStatus; + eFxSystemKillStatus m_nKillStatus; + + bool m_UseConstTime; + float m_fCurrentTime; + float m_fCameraDistance; uint16 m_nConstTime; + uint16 m_nRateMult; uint16 m_nTimeMult; - struct { - uint8 bOwnedParentMatrix: 1; - uint8 bLocalParticles : 1; - uint8 bZTestEnabled : 1; - uint8 bUnknown4 : 1; - uint8 bUnknown5 : 1; - uint8 bMustCreatePtrs : 1; - } m_nFlags; - char field_63; - float fUnkRandom; - CVector m_vecVelAdd; - void *m_pBounding; - void **m_pPrimsPtrList; - char m_fireAudio[0x88]; // CAEFireAudioEntity - - CAEFireAudioEntity *GetFireAudio() { return reinterpret_cast(m_fireAudio); } + + uint8 m_allocatedParentMat : 1; // m_bOwnedParentMatrix + uint8 m_createLocal : 1; + uint8 m_useZTest : 1; + uint8 m_stopParticleCreation : 1; + uint8 m_prevCulled : 1; + uint8 m_MustCreateParticles : 1; + + float m_LoopInterval; + CVector m_VelAdd; + FxSphere_c* m_BoundingSphere; + FxPrim_c** m_Prims; + CAEFireAudioEntity m_FireAE; + +public: + static void InjectHooks(); FxSystem_c(); ~FxSystem_c(); + FxSystem_c* Constructor(); + FxSystem_c* Destructor(); + + bool Init(FxSystemBP_c* systemBP, const RwMatrix& local, RwMatrix* parent); + void Exit(); + void Play(); - void Pause(); - void Stop(); void PlayAndKill(); void Kill(); - void AttachToBone(CEntity* entity, int32 boneId); - void AddParticle(RwV3d* position, RwV3d* velocity, float arg2, FxPrtMult_c* prtMult, float arg4, float brightness, float arg6, uint8 arg7); - void AddParticle(RwMatrix* transform, RwV3d* position, float arg2, FxPrtMult_c* prtMult, float arg4, float arg5, float arg6, uint8 arg7); - void EnablePrim(int32 primIndex, uint8 enable); + void Pause(); + void Stop(); + + void AttachToBone(CEntity* entity, ePedBones boneId); + + void AddParticle(CVector* pos, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float lightMult, float lightMultLimit, bool createLocal); + void AddParticle(RwMatrix* mat, CVector* vel, float timeSince, FxPrtMult_c* fxMults, float rotZ, float lightMult, float lightMultLimit, bool createLocal); + + void EnablePrim(int32 primIndex, bool enable); void SetMatrix(RwMatrix* matrix); void SetOffsetPos(const CVector& pos); - void AddOffsetPos(RwV3d* pos); - void SetConstTime(uint8 arg0, float amount); + void AddOffsetPos(CVector* pos); + void SetConstTime(bool on, float time); void SetRateMult(float mult); void SetTimeMult(float mult); - void SetVelAdd(RwV3d* velAdd); - bool Init(FxSystemBP_c* arg0, RwMatrix* local, RwMatrix* parent); - void Exit(); + void SetVelAdd(CVector* velocity); void CopyParentMatrix(); void GetCompositeMatrix(RwMatrix* out); - eFxSystemPlayStatus GetPlayStatus(); - void SetLocalParticles(uint8 enable); - uint32 ForAllParticles(void(*callback)(Particle_c *, int32, void **), void* data); - static void UpdateBoundingBoxCB(Particle_c* particle, int32 arg1, void** data); + eFxSystemPlayStatus GetPlayStatus() const; + + uint32 ForAllParticles(void(*callback)(Particle_c *, int32, FxBox_c**), FxBox_c* data); + static void UpdateBoundingBoxCB(Particle_c* particle, int32 arg1, FxBox_c** data); + void GetBoundingBox(FxBox_c* out); bool GetBoundingSphereWld(FxSphere_c* out); bool GetBoundingSphereLcl(FxSphere_c* out); - void SetBoundingSphere(FxSphere_c* bound); + void SetBoundingSphere(FxSphere_c* sphere); void ResetBoundingSphere(); - void SetZTestEnable(uint8 enable); - void SetMustCreatePrts(uint8 enable); - void DoFxAudio(CVector posn); + + void SetLocalParticles(bool enable); + void SetZTestEnable(bool enable); + void SetMustCreatePrts(bool enable); + bool IsVisible(); + + void DoFxAudio(CVector pos); bool Update(RwCamera* camera, float timeDelta); +public: + std::span GetPrims(); + template static void KillAndClear(T*& fx) requires std::is_base_of_v { fx->Kill(); diff --git a/source/game_sa/Fx/FxSystemBP.cpp b/source/game_sa/Fx/FxSystemBP.cpp index a5f606db40..f8a44abd24 100644 --- a/source/game_sa/Fx/FxSystemBP.cpp +++ b/source/game_sa/Fx/FxSystemBP.cpp @@ -1,37 +1,41 @@ #include "StdInc.h" #include "FxSystemBP.h" +#include "FxPrimBP.h" +#include "FxEmitterBP.h" +#include "FxInfo.h" + +#include "FxTools.h" void FxSystemBP_c::InjectHooks() { RH_ScopedClass(FxSystemBP_c); RH_ScopedCategory("Fx"); - using namespace ReversibleHooks; - // Install("FxSystemBP_c", "operator new", 0x4AA100, &FxSystemBP_c::operator new); - RH_ScopedInstall(Constructor, 0x4AA0D0, { .reversed = false }); - RH_ScopedInstall(Destructor, 0x4AA0F0, { .reversed = false }); - RH_ScopedInstall(Load, 0x5C05F0, { .reversed = false }); - RH_ScopedInstall(Unload, 0x4AA120, { .reversed = false }); - RH_ScopedInstall(Update, 0x4AA130, { .reversed = false }); - RH_ScopedInstall(Render, 0x4AA160, { .reversed = false }); - RH_ScopedInstall(FreePrtFromSystem, 0x4AA1B0, { .reversed = false }); - RH_ScopedInstall(GetBoundingSphere, 0x4AA1F0, { .reversed = false }); - RH_ScopedInstall(SetBoundingSphere, 0x4AA200, { .reversed = false }); + RH_ScopedInstall(operator new, 0x4AA100); + RH_ScopedInstall(Constructor, 0x4AA0D0); + RH_ScopedInstall(Destructor, 0x4AA0F0); + RH_ScopedInstall(Load, 0x5C05F0); + RH_ScopedInstall(Unload, 0x4AA120); + RH_ScopedInstall(Update, 0x4AA130); + RH_ScopedInstall(Render, 0x4AA160); + RH_ScopedInstall(FreePrtFromSystem, 0x4AA1B0); + RH_ScopedInstall(GetBoundingSphere, 0x4AA1F0); + RH_ScopedInstall(SetBoundingSphere, 0x4AA200); } // 0x4AA100 void* FxSystemBP_c::operator new(unsigned size) { - return g_fxMan.m_pool.GetMem(size, 4);; + return g_fxMan.GetMemPool().GetMem(size); } -// 0x4AA0D0 -FxSystemBP_c::FxSystemBP_c() { - plugin::CallMethod<0x4AA0D0, FxSystemBP_c*>(this); +// NOTSA +void FxSystemBP_c::operator delete(void* data) { + CMemoryMgr::Free(data); } -// 0x4AA0F0 -FxSystemBP_c::~FxSystemBP_c() { - plugin::CallMethod<0x4AA0F0, FxSystemBP_c*>(this); +// 0x4AA0D0 +FxSystemBP_c::FxSystemBP_c() : ListItem_c() { + m_BoundingSphere = nullptr; } FxSystemBP_c* FxSystemBP_c::Constructor() { @@ -44,38 +48,127 @@ FxSystemBP_c* FxSystemBP_c::Destructor() { return this; } -// int32 file -> FILESTREAM? // 0x5C05F0 -void FxSystemBP_c::Load(char* filename, int32 file, int32 version) { - plugin::CallMethod<0x5C05F0, FxSystemBP_c*, char*, int32, int32>(this, filename, file, version); +void FxSystemBP_c::Load(Const char* filename, FILESTREAM file, int32 version) { + assert(file); + + char line[256], field[32]; + + ReadField(file); + + if (version > 100) { + char fname[128]; + ReadFieldImpl(file, fname, "FILENAME:"); + } + + char name[32]; + ReadFieldImpl(file, name, "NAME:"); + m_nNameKey = CKeyGen::GetUppercaseKey(name); + + m_fLength = ReadField(file, "LENGTH:"); + + if (version < 106) { + m_fLoopIntervalMin = 0.0f; + m_fLoopLength = 0.0f; + } else { + m_fLoopIntervalMin = ReadField(file); + m_fLoopLength = ReadField(file); + } + + m_nPlayMode = ReadField(file, "PLAYMODE:"); + m_nCullDist = uint16(ReadField(file, "CULLDIST:") * 256.0f); + + if (version > 103) { + CVector vecCenter; + float fRadius; + ReadLine(file, line, sizeof(line)); + RET_IGNORED(sscanf(line, "%s %f %f %f %f", field, &vecCenter.x, &vecCenter.y, &vecCenter.z, &fRadius)); + m_BoundingSphere = nullptr; + SetBoundingSphere(&vecCenter, fRadius); + } + + m_nNumPrims = ReadField(file, "NUM_PRIMS:"); + m_Prims = g_fxMan.Allocate(m_nNumPrims); + + FxName32_t aTextureNames[8][4]; + assert(m_nNumPrims < std::size(aTextureNames)); + + for (auto i = 0; i < m_nNumPrims; i++) { + ReadLine(file, line, sizeof(line)); + RET_IGNORED(sscanf(line, "%s", field)); + if (strcmp(field, "FX_PRIM_EMITTER_DATA:") != 0) + continue; + + auto* emitter = new FxEmitterBP_c(); + assert(emitter); + + emitter->m_bHasInfoFlatData = false; + emitter->m_bHasInfoHeatHazeData = false; + + ReadField(file); + + emitter->Load(file, version, aTextureNames[i]); + emitter->m_bHasInfoFlatData = emitter->IsFxInfoPresent(FX_INFO_FLAT_DATA); + emitter->m_bHasInfoHeatHazeData = emitter->IsFxInfoPresent(FX_INFO_HEATHAZE_DATA); + m_Prims[i] = emitter; + } + + if (version >= 108) { + ReadField(file, "OMITTEXTURES:"); // unused + } + + if (version >= 109) { + char txd[128]; + ReadFieldImpl(file, txd, "TXDNAME:"); // unused + } + + for (auto i = 0; i < m_nNumPrims; i++) { + m_Prims[i]->LoadTextures(&aTextureNames[i][0], version); + } } // 0x4AA120 void FxSystemBP_c::Unload() { - plugin::CallMethod<0x4AA120, FxSystemBP_c*>(this); + // NOP } // 0x4AA130 void FxSystemBP_c::Update(float arg0) { - plugin::CallMethod<0x4AA130, FxSystemBP_c*, float>(this, arg0); + for (auto& prim : GetPrims()) { + prim->Update(arg0); + } } // 0x4AA160 void FxSystemBP_c::Render(RwCamera* camera, float dayNightBalance, bool bHeatHaze) { - plugin::CallMethod<0x4AA160, FxSystemBP_c*, RwCamera*, float, bool>(this, camera, dayNightBalance, bHeatHaze); + for (auto& prim : GetPrims()) { + prim->Render(camera, 0, dayNightBalance, bHeatHaze); + } } // 0x4AA1B0 bool FxSystemBP_c::FreePrtFromSystem(FxSystem_c* system) { - return plugin::CallMethodAndReturn(this, system); + if (m_nNumPrims <= 0) + return false; + + for (auto& prim : GetPrims()) { + if (prim->FreePrtFromPrim(system)) { + return true; + } + } + return false; } // 0x4AA1F0 -FxSphere_c* FxSystemBP_c::GetBoundingSphere() { - return plugin::CallMethodAndReturn(this); +FxSphere_c* FxSystemBP_c::GetBoundingSphere() const { + return m_BoundingSphere; } // 0x4AA200 -void FxSystemBP_c::SetBoundingSphere(RwV3d* center, float radius) { - plugin::CallMethod<0x4AA200, FxSystemBP_c*, RwV3d*, float>(this, center, radius); +void FxSystemBP_c::SetBoundingSphere(CVector* center, float radius) { + if (!m_BoundingSphere && radius > 0.0f) { + m_BoundingSphere = new (true) FxSphere_c(); + m_BoundingSphere->m_vecCenter = center; + m_BoundingSphere->m_fRadius = radius; + } } diff --git a/source/game_sa/Fx/FxSystemBP.h b/source/game_sa/Fx/FxSystemBP.h index 8344eb49f4..6bf8630ece 100644 --- a/source/game_sa/Fx/FxSystemBP.h +++ b/source/game_sa/Fx/FxSystemBP.h @@ -7,44 +7,48 @@ #pragma once #include "ListItem_c.h" -#include "FxSphere.h" class FxSystem_c; +class FxPrimBP_c; +class FxSphere_c; /* FX System BluePrint */ -class FxSystemBP_c : public ListItem_c { +class FxSystemBP_c : public ListItem_c { public: - uint32 m_nNameKey; - float m_fLength; - float m_fLoopIntervalMin; - float m_fLoopLength; - int16 m_nCullDist; - uint8 m_nPlayMode; - uint8 m_nNumEmitters; - void** m_emittersList; - FxSphere_c* m_pBoundingSphere; + uint32 m_nNameKey; + float m_fLength; + float m_fLoopIntervalMin; + float m_fLoopLength; + uint16 m_nCullDist; + uint8 m_nPlayMode; + uint8 m_nNumPrims; + FxPrimBP_c** m_Prims; + FxSphere_c* m_BoundingSphere; public: static void InjectHooks(); FxSystemBP_c(); + ~FxSystemBP_c() = default; // 0x4AA0F0 FxSystemBP_c* Constructor(); - - ~FxSystemBP_c(); FxSystemBP_c* Destructor(); static void* operator new(unsigned size); - static void operator delete(void* dat); + static void operator delete(void* data); - void Load(char* filename, int32 file, int32 version); + void Load(Const char* filename, FILESTREAM file, int32 version); void Unload(); void Update(float arg0); void Render(RwCamera* camera, float dayNightBalance, bool bHeatHaze); bool FreePrtFromSystem(FxSystem_c* system); - FxSphere_c* GetBoundingSphere(); - void SetBoundingSphere(RwV3d* center, float radius); + FxSphere_c* GetBoundingSphere() const; + void SetBoundingSphere(CVector* center, float radius); + + // NOTSA + [[nodiscard]] auto GetNameKey() const noexcept { return m_nNameKey; } + auto GetPrims() { return std::span{ m_Prims, m_nNumPrims }; } }; -VALIDATE_SIZE(FxSystemBP_c, 0x24); \ No newline at end of file +VALIDATE_SIZE(FxSystemBP_c, 0x24); diff --git a/source/game_sa/Fx/FxTools.cpp b/source/game_sa/Fx/FxTools.cpp new file mode 100644 index 0000000000..e0a81dae3a --- /dev/null +++ b/source/game_sa/Fx/FxTools.cpp @@ -0,0 +1,21 @@ +#include "StdInc.h" + +#include "FxTools.h" + +#ifdef _DEBUG +//# define TRACE_ALL_FX_SHIT +#endif + +#if defined TRACE_ALL_FX_SHIT +static Int32 _gLine; +#endif + +bool ReadLine(FILESTREAM file, char* buffer, int32 bufferSize) { + if (CFileMgr::ReadLine(file, buffer, bufferSize)) { +#if defined TRACE_ALL_FX_SHIT + _DEVLOG("FX ReadLine %4d: %s", ++_gLine, buffer); +#endif + return true; + } + return false; +} diff --git a/source/game_sa/Fx/FxTools.h b/source/game_sa/Fx/FxTools.h new file mode 100644 index 0000000000..c2aa1ddb01 --- /dev/null +++ b/source/game_sa/Fx/FxTools.h @@ -0,0 +1,86 @@ +/** + * nick7 @ 2015/12/19 23:48 + */ + +#pragma once + +#include "Base.h" + +extern bool ReadLine(FILESTREAM file, char* pszBuffer, int32 iBufferSize); + +namespace traits { + +template +struct fx_field_scanf_format; + +template <> +struct fx_field_scanf_format { + static inline const char* value() { + return "%s %d"; + } + + static inline size_t num_parts() { return 2; } +}; + +template <> +struct fx_field_scanf_format { + static inline const char* value() { + return "%s %f"; + } + + static inline size_t num_parts() { return 2; } +}; + +template +struct fx_field_scanf_format { + static inline const char* value() { + return "%s %s"; + } + + static inline size_t num_parts() { return 2; } +}; + +} // namespace traits + +template +inline void ReadFieldImpl(FILESTREAM file, Type& refValue, const char* fieldName = nullptr) { + char line[256]{}, field[128]{0}; + ReadLine(file, line, sizeof(line)); + VERIFY(sscanf(line, traits::fx_field_scanf_format::value(), field, &refValue) == traits::fx_field_scanf_format::num_parts()); + + if (fieldName != nullptr && strcmp(field, fieldName) != 0) { + NOTSA_UNREACHABLE(std::format("Fx Project field mismatch!\nExpected:\t{}\nGot:\t{}", fieldName, field)); + } +} + +template +Type ReadField(FILESTREAM file, const char* fieldName = nullptr); + +template +Type ReadField(FILESTREAM file, const char* fieldName) { + Type value; + ReadFieldImpl(file, value, fieldName); + return value; +} + +template <> +inline void ReadField(FILESTREAM file, const char* fieldName) { + char line[256], field[128]; + ReadLine(file, line, sizeof(line)); + RET_IGNORED(sscanf(line, "%s", field) == 1); +} + +template <> +inline int8 ReadField(FILESTREAM file, const char* fieldName) { + return (int8)ReadField(file, fieldName); +} + +template <> +inline uint8 ReadField(FILESTREAM file, const char* fieldName) { + return (uint8)ReadField(file, fieldName); +} + +template <> +inline bool ReadField(FILESTREAM file, const char* fieldName) { + return ReadField(file, fieldName) != 0; +} diff --git a/source/game_sa/Fx/MovementInfo.h b/source/game_sa/Fx/MovementInfo.h new file mode 100644 index 0000000000..b6a3a8c18b --- /dev/null +++ b/source/game_sa/Fx/MovementInfo.h @@ -0,0 +1,18 @@ +#pragma once + +#include "Vector.h" + +struct MovementInfo_t { + CVector m_Pos; + CVector m_Vel; + float m_Rot[4]; + bool m_bHasFloatInfo; + bool m_bHasUnderwaterInfo; + + void Process() { + for (auto& r : m_Rot) { r = 0.0f; } + m_bHasFloatInfo = false; + m_bHasUnderwaterInfo = false; + } +}; +VALIDATE_SIZE(MovementInfo_t, 0x2C); diff --git a/source/game_sa/Fx/Particle.h b/source/game_sa/Fx/Particle.h new file mode 100644 index 0000000000..d7119298b4 --- /dev/null +++ b/source/game_sa/Fx/Particle.h @@ -0,0 +1,19 @@ +#pragma once + +#include "ListItem_c.h" +#include "Vector.h" + +class FxSystem_c; +class FxEmitterPrt_c; + +struct Particle_c : public ListItem_c { + float m_fTotalLife; + float m_fCurrentLife; + CVector m_Pos; + CVector m_Velocity; + FxSystem_c* m_System; + + // NOTSA + FxEmitterPrt_c* AsFxEmitterPrt() { return reinterpret_cast(this); } +}; +VALIDATE_SIZE(Particle_c, 0x2C); diff --git a/source/game_sa/Fx/RenderInfo.h b/source/game_sa/Fx/RenderInfo.h new file mode 100644 index 0000000000..9d2280a73e --- /dev/null +++ b/source/game_sa/Fx/RenderInfo.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include "Vector.h" + +enum class ERenderColorType : int32 { + DEFAULT = 0, + RANGE = 1, + BRIGHT = 2, +}; + +struct RenderInfo_t { + RwRGBA m_Color1; + RwRGBA m_Color2; + ERenderColorType m_nColorType; + + float m_fSizeX; // todo: CVector2D m_Size, m_SizeBias + float m_fSizeY; + float m_fSizeXBias; + float m_fSizeYBias; + + float m_fSpriteTop; // todo: RwV4d m_SpriteRect + float m_fSpriteBottom; + float m_fSpriteLeft; + float m_fSpriteRight; + + int32 m_nTrailScreenMode; // trail id + float m_fTrailTime; + + bool m_bHasAnimTextures; + int8 m_nCurrentTexId; + + bool m_bHeatHaze; + bool m_bSelfLit; + + RwMatrix m_FlatMatrix; + bool m_bIsFlat; + bool m_bHasDir; + bool m_bUseVel; + + int8 m_SmokeType; + CVector m_Direction; + RwRGBAReal m_SmokeColor; + float m_SmokeBrightness; + float m_SmokeSize; + float m_SmokeLife; + + void SetColor(float red, float green, float blue, float alpha) { + m_Color1.red = (uint8)red; + m_Color1.green = (uint8)green; + m_Color1.blue = (uint8)blue; + m_Color1.alpha = (uint8)alpha; + } + + void SetRangeColor(float red, float green, float blue) { + m_Color2.red = (uint8)red; + m_Color2.green = (uint8)green; + m_Color2.blue = (uint8)blue; + } + + void Process() { + m_Color1.red = 255; + m_Color1.green = 255; + m_Color1.blue = 255; + m_Color1.alpha = 255; + m_fSizeX = 0.1f; + m_fSizeY = 0.1f; + m_nColorType = ERenderColorType::DEFAULT; + m_fSizeXBias = 0.0f; + m_fSizeYBias = 0.0f; + m_fSpriteTop = 0.5f; + m_fSpriteBottom = -0.5f; + m_fSpriteLeft = 0.5f; + m_fSpriteRight = -0.5f; + m_bHeatHaze = false; + m_bSelfLit = false; + m_bIsFlat = false; + m_bHasAnimTextures = false; + m_nTrailScreenMode = 0; + m_bHasDir = false; + m_bUseVel = false; + m_SmokeType = -1; + } +}; +VALIDATE_SIZE(RenderInfo_t, 0xA4); diff --git a/source/game_sa/Fx/eFxInfoType.h b/source/game_sa/Fx/eFxInfoType.h new file mode 100644 index 0000000000..d6dd341b35 --- /dev/null +++ b/source/game_sa/Fx/eFxInfoType.h @@ -0,0 +1,53 @@ +#pragma once + +/* Legend: + * MH - Manhunt (Version 104) + * SA - San Andreas (Version 109) + */ + +enum eFxInfoType : uint16 { + FX_INFO_EMRATE_DATA = 0x1001, // MH SA + // = 0x1002 // + FX_INFO_EMSIZE_DATA = 0x1004, // MH SA + FX_INFO_EMSPEED_DATA = 0x1008, // MH SA + FX_INFO_EMDIR_DATA = 0x1010, // MH SA + FX_INFO_EMANGLE_DATA = 0x1020, // MH SA + FX_INFO_EMLIFE_DATA = 0x1040, // MH SA + FX_INFO_EMPOS_DATA = 0x1080, // MH SA + FX_INFO_EMWEATHER_DATA = 0x1100, // SA + FX_INFO_EMROTATION_DATA = 0x1200, // SA + // = 0x1400, // + // = 0x1800, // + + FX_INFO_NOISE_DATA = 0x2001, // MH SA + FX_INFO_FORCE_DATA = 0x2002, // MH SA + FX_INFO_FRICTION_DATA = 0x2004, // MH SA + FX_INFO_ATTRACTPT_DATA = 0x2008, // MH SA + FX_INFO_ATTRACTLINE_DATA = 0x2010, // MH SA + FX_INFO_GROUNDCOLLIDE_DATA = 0x2020, // MH SA + FX_INFO_WIND_DATA = 0x2040, // MH SA + FX_INFO_JITTER_DATA = 0x2080, // MH SA + FX_INFO_ROTSPEED_DATA = 0x2100, // SA + FX_INFO_FLOAT_DATA = 0x2200, // SA + FX_INFO_UNDERWATER_DATA = 0x2400, // + // = 0x2800, // + + FX_INFO_COLOUR_DATA = 0x4001, // MH SA + FX_INFO_SIZE_DATA = 0x4002, // MH SA + FX_INFO_SPRITERECT_DATA = 0x4004, // MH SA + FX_INFO_HEATHAZE_DATA = 0x4008, // SA + FX_INFO_TRAIL_DATA = 0x4010, // SA + FX_INFO_FLAT_DATA = 0x4020, // MH SA + FX_INFO_DIR_DATA = 0x4040, // MH SA + FX_INFO_ANIMTEX_DATA = 0x4080, // MH SA + FX_INFO_COLOURRANGE_DATA = 0x4100, // MH SA + FX_INFO_SELFLIT_DATA = 0x4200, // SA + FX_INFO_COLOURBRIGHT_DATA = 0x4400, // SA + + FX_INFO_ROTATE_DATA = 0x4008, // MH + FX_INFO_TEXCOORDS_DATA = 0x4010, // MH + FX_INFO_ROTATEOFFSET_DATA = 0x4200, // MH + // = 0x4800, // + + FX_INFO_SMOKE_DATA = 0x8001, // SA +}; diff --git a/source/game_sa/Game.cpp b/source/game_sa/Game.cpp index 3a430c5616..09c376cb75 100644 --- a/source/game_sa/Game.cpp +++ b/source/game_sa/Game.cpp @@ -166,7 +166,7 @@ void CGame::ShutdownRenderWare() { } // 0x53C4A0 -bool CGame::CanSeeOutSideFromCurrArea() { +bool CGame::CanSeeOutSideFromCurrArea() { // pattern: !CGame::currArea return currArea == AREA_CODE_NORMAL_WORLD; } @@ -286,7 +286,6 @@ void CGame::ShutDownForRestart() { CReplay::EmptyReplayBuffer(); CMovingThings::Shutdown(); rng::for_each(CWorld::Players, [](auto& info) { info.Clear(); }); - memset(CTheZones::ZonesVisited, 0, sizeof(CTheZones::ZonesVisited)); CTheScripts::UndoBuildingSwaps(); CTheScripts::UndoEntityInvisibilitySettings(); g_interiorMan.Exit(); @@ -336,6 +335,8 @@ void CGame::GenerateTempPedAtStartOfNetworkGame() { // 0x5BF840 bool CGame::Init1(char const *datFile) { + ZoneScoped; + CMaths::InitMathsTables(); strcpy_s(aDatFile, datFile); CPools::Initialise(); @@ -426,6 +427,8 @@ bool CGame::Init1(char const *datFile) { // 0x5BA1A0 bool CGame::Init2(const char* datFile) { + ZoneScoped; + LoadingScreen("Loading the Game", "Add Particles"); CTheZones::PostZoneCreation(); CEntryExitManager::PostEntryExitsCreation(); @@ -526,6 +529,8 @@ bool CGame::Init2(const char* datFile) { // 0x5BA400 bool CGame::Init3(const char* datFile) { + ZoneScoped; + LoadingScreen("Loading the Game", "Load scene"); CPad::GetPad(PED_TYPE_PLAYER1)->Clear(true, true); CPad::GetPad(PED_TYPE_PLAYER2)->Clear(true, true); @@ -540,6 +545,8 @@ bool CGame::Init3(const char* datFile) { // 0x53BC80 void CGame::Initialise(const char* datFile) { + ZoneScoped; + Init1(datFile); CColAccel::startCache(); CFileLoader::LoadLevel("DATA\\DEFAULT.DAT"); @@ -708,6 +715,8 @@ void CGame::InitialiseWhenRestarting() { // 0x53BEE0 void CGame::Process() { + ZoneScoped; + CPad::UpdatePads(); g_LoadMonitor.BeginFrame(); @@ -750,8 +759,7 @@ void CGame::Process() { if (updateTimeDelta >= 4) { CPopulation::Update(false); - } - else { + } else { const auto timeBeforePopulationUpdate = GetTime(); CPopulation::Update(true); updateTimeDelta = GetTime() - timeBeforePopulationUpdate; @@ -795,8 +803,7 @@ void CGame::Process() { if (CReplay::ShouldStandardCameraBeProcessed()) { TheCamera.Process(); - } - else { + } else { TheCamera.CCamera::ProcessFade(); } diff --git a/source/game_sa/GameLogic.cpp b/source/game_sa/GameLogic.cpp index 71f5e67256..a43c269a03 100644 --- a/source/game_sa/GameLogic.cpp +++ b/source/game_sa/GameLogic.cpp @@ -86,7 +86,7 @@ void CGameLogic::DoWeaponStuffAtStartOf2PlayerGame(bool shareWeapons) { if (shareWeapons) { for (auto& weapon : player1->m_aWeapons) { - if (weapon.m_nType == WEAPON_UNARMED) + if (weapon.m_Type == WEAPON_UNARMED) continue; player2->GiveWeapon(weapon, true); @@ -98,24 +98,28 @@ void CGameLogic::DoWeaponStuffAtStartOf2PlayerGame(bool shareWeapons) { // 0x441B70 // 1 - Los Santos, 2 - San Fierro, 3 - Las Venturas -uint32 CGameLogic::FindCityClosestToPoint(CVector2D point) { - constexpr CVector2D cityCoords[] = { - { 1670.0f, -1137.0f}, // LS - {-1810.0f, 884.0f}, // SF - { 2161.0f, 2140.0f}, // LV +eLevelName CGameLogic::FindCityClosestToPoint(CVector2D point) { + constexpr struct { + eLevelName level; + CVector2D coords; + } CITIES[] = { + {LEVEL_NAME_LOS_SANTOS, { 1670.0f, -1137.0f}}, + {LEVEL_NAME_SAN_FIERRO, {-1810.0f, 884.0f}}, + {LEVEL_NAME_LAS_VENTURAS, { 2161.0f, 2140.0f}}, }; - std::pair closest{FLT_MAX, 3}; // NOTSA - for (auto&& [i, d] : notsa::enumerate(cityCoords)) { - if (const auto d = DistanceBetweenPoints2D(cityCoords[i], point); d < closest.first) { - closest = {d, i}; + + std::pair closest{FLT_MAX, NUM_LEVELS}; // NOTSA + for (const auto& city : CITIES) { + if (const auto d = DistanceBetweenPoints2D(city.coords, point); d < closest.first) { + closest = {d, city.level}; } } - if (closest.second == 3) { + if (closest.second == NUM_LEVELS) { NOTSA_UNREACHABLE(); } - return closest.second + 1; + return closest.second; } // 0x441240 @@ -129,6 +133,8 @@ void CGameLogic::ForceDeathRestart() { // 0x441210 void CGameLogic::InitAtStartOfGame() { + ZoneScoped; + ActivePlayers = true; SkipState = SKIP_NONE; NumAfterDeathStartPoints = 0; @@ -340,7 +346,7 @@ void CGameLogic::RestorePedsWeapons(CPed* ped) { const auto IsModelLoaded = [](int id) { return id == MODEL_INVALID || CStreaming::GetInfo(id).IsLoaded(); }; if (rng::all_of(weapon.GetWeaponInfo().GetModels(), IsModelLoaded)) { // FIX_BUGS: They checked modelId1 twice - ped->GiveWeapon(weapon.m_nType, weapon.m_nTotalAmmo, true); + ped->GiveWeapon(weapon.m_Type, weapon.m_TotalAmmo, true); } } } @@ -560,6 +566,8 @@ void CGameLogic::StopPlayerMovingFromDirection(int32 playerId, CVector direction // 0x442AD0 void CGameLogic::Update() { + ZoneScoped; + CStats::UpdateRespectStat(0); CStats::UpdateSexAppealStat(); SetPlayerWantedLevelForForbiddenTerritories(false); diff --git a/source/game_sa/GameLogic.h b/source/game_sa/GameLogic.h index 23db95cc0b..ed53160461 100644 --- a/source/game_sa/GameLogic.h +++ b/source/game_sa/GameLogic.h @@ -74,7 +74,7 @@ class CGameLogic { static void ClearSkip(bool afterMission); static void DoWeaponStuffAtStartOf2PlayerGame(bool shareWeapons); static void StorePedsWeapons(CPed* ped); - static uint32 FindCityClosestToPoint(CVector2D point); + static eLevelName FindCityClosestToPoint(CVector2D point); static void ForceDeathRestart(); static void InitAtStartOfGame(); static bool IsCoopGameGoingOn(); diff --git a/source/game_sa/GangWars.cpp b/source/game_sa/GangWars.cpp index de82d050c2..32dba1e774 100644 --- a/source/game_sa/GangWars.cpp +++ b/source/game_sa/GangWars.cpp @@ -82,6 +82,8 @@ bool CGangWars::Save() { // 0x443920 void CGangWars::InitAtStartOfGame() { + ZoneScoped; + State = NOT_IN_WAR; State2 = NO_ATTACK; NumSpecificZones = 0; @@ -598,7 +600,7 @@ void CGangWars::StartDefensiveGangWar() { }()); bPlayerIsCloseby = false; - pZoneInfoToFightOver->Flags1 = pZoneInfoToFightOver->Flags1 & 0x9F | 0x40; // todo: flags + pZoneInfoToFightOver->radarMode = 2; pZoneInfoToFightOver->ZoneColor = CRGBA{ 255, 0, 0, 160 }; } else { TimeTillNextAttack = CalculateTimeTillNextAttack(); @@ -655,7 +657,7 @@ void CGangWars::StartOffensiveGangWar() { Gang2 = Gang1; TellGangMembersTo(false); - zoneInfo->Flags1 = zoneInfo->Flags1 & 0x9F | 0x40; // todo: flags + pZoneInfoToFightOver->radarMode = 2; zoneInfo->ZoneColor = CRGBA{ 255, 0, 0, 160 }; pDriveByCar = nullptr; } @@ -731,6 +733,8 @@ void CGangWars::TellStreamingWhichGangsAreNeeded(uint32& gangsBitFlags) { // 0x446610 void CGangWars::Update() { + ZoneScoped; + return plugin::Call<0x446610>(); if (CTheScripts::IsPlayerOnAMission() && !bIsPlayerOnAMission && NumSpecificZones == 0) diff --git a/source/game_sa/GangWars.h b/source/game_sa/GangWars.h index 3210bc6439..b5ac882f22 100644 --- a/source/game_sa/GangWars.h +++ b/source/game_sa/GangWars.h @@ -29,6 +29,24 @@ enum eGangWarState { THIRD_WAVE = 6 }; +constexpr union { + struct { + uint8 r, g, b; + }; + uint8 components[3]; +} gaGangColors[TOTAL_GANGS]{ // based on gaGangColorsR, gaGangColorsG, gaGangColorsB + { 200, 0, 200 }, + { 70, 200, 0 }, + { 255, 200, 0 }, + { 0, 0, 200 }, + { 255, 220, 190 }, + { 200, 200, 200 }, + { 240, 140, 240 }, + { 0, 200, 255 }, + { 255, 255, 255 }, + { 255, 255, 255 }, +}; + class CGangWars { public: static inline int32& ZoneInfoForTraining = *(int32*)0x8A5F40; // -1 diff --git a/source/game_sa/Garages.cpp b/source/game_sa/Garages.cpp index 7eb556718b..4ebf72e3f5 100644 --- a/source/game_sa/Garages.cpp +++ b/source/game_sa/Garages.cpp @@ -38,6 +38,8 @@ void CGarages::InjectHooks() { // 0x447120 void CGarages::Init() { + ZoneScoped; + NumGarages = 0; MessageEndTime = 0; MessageStartTime = 0; @@ -88,6 +90,8 @@ void CGarages::Shutdown() { // 0x44C8C0 void CGarages::Update() { + ZoneScoped; + if (CReplay::Mode == eReplayMode::MODE_PLAYBACK || CGameLogic::IsCoopGameGoingOn()) return; @@ -126,7 +130,7 @@ void CGarages::GivePlayerDetonator() { auto player = FindPlayerPed(); auto slot = CWeaponInfo::GetWeaponInfo(WEAPON_DETONATOR, eWeaponSkill::STD)->m_nSlot; player->GiveWeapon(WEAPON_DETONATOR, 1, true); - player->m_aWeapons[slot].m_nState = WEAPONSTATE_READY; + player->m_aWeapons[slot].m_State = WEAPONSTATE_READY; player->m_pPlayerData->m_nChosenWeapon = slot; if (player->m_nSavedWeapon != WEAPON_UNIDENTIFIED) player->m_nSavedWeapon = WEAPON_DETONATOR; diff --git a/source/game_sa/Glass.cpp b/source/game_sa/Glass.cpp index c390f856bc..780b58b099 100644 --- a/source/game_sa/Glass.cpp +++ b/source/game_sa/Glass.cpp @@ -360,6 +360,8 @@ void CGlass::GeneratePanesForWindow(ePaneType type, CVector point, CVector fwd, // 0x71B0D0 void CGlass::Update() { + ZoneScoped; + for (auto& pane : aGlassPanes) { if (pane.m_bExist) { pane.Update(); @@ -369,6 +371,8 @@ void CGlass::Update() { // 0x71CE20 void CGlass::Render() { + ZoneScoped; + H1iLightPolyVerticesIdx = 0; HiLightPolyIndicesIdx = 0; diff --git a/source/game_sa/HandShaker.cpp b/source/game_sa/HandShaker.cpp index b408c6d1f4..ffc3b7b87f 100644 --- a/source/game_sa/HandShaker.cpp +++ b/source/game_sa/HandShaker.cpp @@ -10,34 +10,55 @@ void CHandShaker::InjectHooks() { RH_ScopedInstall(SetDefaults, 0x517330); RH_ScopedInstall(Reset, 0x50D860); - RH_ScopedInstall(Process, 0x50D930, { .reversed = false }); + RH_ScopedInstall(Process, 0x50D930); } // 0x517330 void CHandShaker::SetDefaults() { - vec1.Set(0.02f, 0.02f, 0.01f); - vec2.Set(0.0002f, 0.0002f, 0.0001f); - vec4.Set(1.3f, 1.3f, 1.4f); + m_lim.Set(0.02f, 0.02f, 0.01f); + m_motion.Set(0.0002f, 0.0002f, 0.0001f); + m_slow.Set(1.3f, 1.3f, 1.4f); - f84 = 0.3f; - f88 = 1.0f; - n8C = 20; - f90 = 0.001f; + m_scaleReactionMin = 0.3f; + m_scaleReactionMax = 1.f; + m_twitchFreq = 20; + m_twitchVel = 0.001f; Reset(); } // 0x50D860 void CHandShaker::Reset() { - vec = CVector(); - vec3 = CVector{ - CGeneral::GetRandomNumberInRange(0.0f, vec2.x), - CGeneral::GetRandomNumberInRange(0.0f, vec2.y), - CGeneral::GetRandomNumberInRange(0.0f, vec2.z), - }; + m_ang = CVector(); + m_vel = CVector::Random({0.f, 0.f, 0.f}, m_motion); } // 0x50D930 -void CHandShaker::Process(float a1) { - plugin::CallMethod<0x50D930, CHandShaker*, float>(this, a1); +void CHandShaker::Process(float degree) { + // Process velocity on each axis + for (auto i = 0; i < 3; i++) { + //> 0x50D951 - Calculate scale [of motion] + auto scale = std::lerp(m_scaleReactionMin, m_scaleReactionMax, std::abs(m_ang[i] / m_lim[i])); + if (std::signbit(m_ang[i]) == std::signbit(m_vel[i])) { + scale *= m_slow[i]; + } + + //> 0x50DA61 - Calculate random motion using it + float motion = CGeneral::GetRandomNumberInRange(0.f, 1.f) * m_motion[i] * scale; + if (m_ang[i] > 0.f) { + motion = -motion; + } + + //> 0x50DB29 + m_vel[i] += motion; + } + + const auto twitchT = (float)(size_t)CTimer::GetTimeStepNonClippedInMS() * 0.03f; + if (CGeneral::GetRandomNumberInRange((size_t)((float)m_twitchFreq * twitchT)) == 1) { //> 0x50DB97 + m_vel += CVector::Random(-m_twitchVel, m_twitchVel); + } + m_ang += twitchT * m_vel; //> 0x50DC74 + m_ang = Clamp(m_ang, -m_lim, m_lim); //> 0x50DCAF + + m_resultMat.SetRotate(m_ang * degree); //> 0x50DD40 } diff --git a/source/game_sa/HandShaker.h b/source/game_sa/HandShaker.h index 96aefd43b6..827a9586a0 100644 --- a/source/game_sa/HandShaker.h +++ b/source/game_sa/HandShaker.h @@ -2,16 +2,16 @@ class CHandShaker { public: - CVector vec; - CVector vec1; - CVector vec2; - CVector vec3; - CVector vec4; - CMatrix matrix; - float f84; - float f88; - int32 n8C; - float f90; + CVector m_ang; + CVector m_lim; //! Angle (`m_ang`) limit [That is, `m_ang` is clamped to [-m_lim, m_lim]) + CVector m_motion; + CVector m_vel; ///! Motion velocity + CVector m_slow; ///! Slow motion velocity + CMatrix m_resultMat; ///! + float m_scaleReactionMin; + float m_scaleReactionMax; + int32 m_twitchFreq; + float m_twitchVel; public: static void InjectHooks(); @@ -21,9 +21,9 @@ class CHandShaker { void SetDefaults(); void Reset(); - void Process(float a1); + void Process(float degree); }; VALIDATE_SIZE(CHandShaker, 0x94); -extern CHandShaker*& gHandShaker; \ No newline at end of file +extern CHandShaker*& gHandShaker; diff --git a/source/game_sa/Hud.cpp b/source/game_sa/Hud.cpp index 73e36a019d..e23bd05370 100644 --- a/source/game_sa/Hud.cpp +++ b/source/game_sa/Hud.cpp @@ -387,6 +387,8 @@ void CHud::Draw() { // 0x58D490 void CHud::DrawAfterFade() { + ZoneScoped; + RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, RWRSTATE(rwFILTERNEAREST)); RwRenderStateSet(rwRENDERSTATETEXTUREADDRESS, RWRSTATE(rwTEXTUREADDRESSCLAMP)); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(FALSE)); @@ -646,10 +648,10 @@ void CHud::DrawCrossHairs() { if (!player->m_pTargetedObject && !player->bIsRestoringLook && (!localTakUseGun || !localTakUseGun->m_bSkipAim)) { if (camMode == MODE_AIMWEAPON || camMode == MODE_AIMWEAPON_FROMCAR || camMode == MODE_AIMWEAPON_ATTACHED) { if (player->m_nPedState != ePedState::PEDSTATE_ENTER_CAR && player->m_nPedState != ePedState::PEDSTATE_CARJACK) { - if ((activeWeapon.m_nType >= eWeaponType::WEAPON_PISTOL && - activeWeapon.m_nType <= eWeaponType::WEAPON_COUNTRYRIFLE + if ((activeWeapon.m_Type >= eWeaponType::WEAPON_PISTOL && + activeWeapon.m_Type <= eWeaponType::WEAPON_COUNTRYRIFLE ) || - activeWeapon.m_nType == eWeaponType::WEAPON_FLAMETHROWER || activeWeapon.m_nType == eWeaponType::WEAPON_MINIGUN + activeWeapon.m_Type == eWeaponType::WEAPON_FLAMETHROWER || activeWeapon.m_Type == eWeaponType::WEAPON_MINIGUN ) { bDrawCircleCrossHair = camMode == MODE_AIMWEAPON || TheCamera.m_bTransitionState; } @@ -738,10 +740,10 @@ void CHud::DrawCrossHairs() { float screenOffsetCenterX = 0.0f; float screenOffsetCenterY = 0.0f; - if (activeWeapon.m_nType == eWeaponType::WEAPON_CAMERA || activeWeapon.m_nType == eWeaponType::WEAPON_SNIPERRIFLE || + if (activeWeapon.m_Type == eWeaponType::WEAPON_CAMERA || activeWeapon.m_Type == eWeaponType::WEAPON_SNIPERRIFLE || CTheScripts::bDrawCrossHair == eCrossHairType::FIXED_DRAW_1STPERSON_WEAPON ) { - if (activeWeapon.m_nType == eWeaponType::WEAPON_CAMERA || CTheScripts::bDrawCrossHair == eCrossHairType::FIXED_DRAW_1STPERSON_WEAPON) { + if (activeWeapon.m_Type == eWeaponType::WEAPON_CAMERA || CTheScripts::bDrawCrossHair == eCrossHairType::FIXED_DRAW_1STPERSON_WEAPON) { screenStretchCrossHairX = SCREEN_STRETCH_X(256.0f); screenStretchCrossHairY = SCREEN_STRETCH_Y(192.0f); } else { @@ -786,10 +788,12 @@ void CHud::DrawCrossHairs() { const auto RenderOneXLUSprite = [=](float x, float y, auto u, auto v) { CSprite::RenderOneXLUSprite( - x, y, - 1.0f, - screenStretchCrossHairX / 2.0f, screenStretchCrossHairY / 2.0f, - 255, 255, 255, 255, 0.01f, 255, u, v + { x, y, 1.0f } , + { screenStretchCrossHairX / 2.0f, screenStretchCrossHairY / 2.0f }, + 255, 255, 255, 255, + 0.01f, + 255, + u, v ); }; @@ -1134,7 +1138,7 @@ void CHud::DrawRadar() { CPlayerPed* player = FindPlayerPed(); // Draws Altimeter on Planes And Helis or when parachuting down if (vehicle && (vehicle->IsSubPlane() || vehicle->IsSubHeli() && vehicle->m_nModelIndex != MODEL_VORTEX) - || player->GetActiveWeapon().m_nType == WEAPON_PARACHUTE + || player->GetActiveWeapon().m_Type == WEAPON_PARACHUTE ) { rect.left = SCREEN_STRETCH_X(40.0f) - SCREEN_STRETCH_X(20.0f); rect.bottom = SCREEN_STRETCH_FROM_BOTTOM(104.0f); @@ -1379,16 +1383,16 @@ void CHud::DrawAmmo(CPed* ped, int32 x, int32 y, float alpha) { const auto MAX_CLIP = 9999; const auto& weapon = ped->GetActiveWeapon(); - const auto& totalAmmo = weapon.m_nTotalAmmo; - const auto& ammoInClip = weapon.m_nAmmoInClip; - const auto& ammoClip = CWeaponInfo::GetWeaponInfo(weapon.m_nType, ped->GetWeaponSkill())->m_nAmmoClip; + const auto& totalAmmo = weapon.m_TotalAmmo; + const auto& ammoInClip = weapon.m_AmmoInClip; + const auto& ammoClip = CWeaponInfo::GetWeaponInfo(weapon.m_Type, ped->GetWeaponSkill())->m_nAmmoClip; if (ammoClip <= 1 || ammoClip >= 1000) { sprintf_s(gString, "%d", totalAmmo); } else { uint32 total, current; - if (weapon.m_nType == WEAPON_FLAMETHROWER ) { + if (weapon.m_Type == WEAPON_FLAMETHROWER ) { uint32 out = MAX_CLIP; if ((totalAmmo - ammoInClip) / 10 <= MAX_CLIP) { out = (totalAmmo - ammoInClip) / 10u; @@ -1418,19 +1422,19 @@ void CHud::DrawAmmo(CPed* ped, int32 x, int32 y, float alpha) { CFont::SetDropColor({ 0, 0, 0, 255 }); CFont::SetFontStyle(eFontStyle::FONT_SUBTITLES); - if ( totalAmmo - weapon.m_nAmmoInClip >= MAX_CLIP + if ( totalAmmo - weapon.m_AmmoInClip >= MAX_CLIP || CDarkel::FrenzyOnGoing() - || weapon.m_nType == WEAPON_UNARMED - || weapon.m_nType == WEAPON_DETONATOR - || weapon.m_nType == WEAPON_DILDO1 - || weapon.m_nType == WEAPON_DILDO2 - || weapon.m_nType == WEAPON_VIBE1 - || weapon.m_nType == WEAPON_VIBE2 - || weapon.m_nType == WEAPON_FLOWERS - || weapon.m_nType == WEAPON_CANE - || weapon.m_nType == WEAPON_PARACHUTE - || CWeaponInfo::GetWeaponInfo(weapon.m_nType)->m_nWeaponFire == WEAPON_FIRE_USE - || CWeaponInfo::GetWeaponInfo(weapon.m_nType)->m_nSlot <= 1 + || weapon.m_Type == WEAPON_UNARMED + || weapon.m_Type == WEAPON_DETONATOR + || weapon.m_Type == WEAPON_DILDO1 + || weapon.m_Type == WEAPON_DILDO2 + || weapon.m_Type == WEAPON_VIBE1 + || weapon.m_Type == WEAPON_VIBE2 + || weapon.m_Type == WEAPON_FLOWERS + || weapon.m_Type == WEAPON_CANE + || weapon.m_Type == WEAPON_PARACHUTE + || CWeaponInfo::GetWeaponInfo(weapon.m_Type)->m_nWeaponFire == WEAPON_FIRE_USE + || CWeaponInfo::GetWeaponInfo(weapon.m_Type)->m_nSlot <= 1 ) { CFont::SetEdge(0); return; @@ -1572,7 +1576,14 @@ void CHud::DrawWeaponIcon(CPed* ped, int32 x, int32 y, float alpha) { RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(NULL)); RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(RwTextureGetRaster(texture))); - CSprite::RenderOneXLUSprite(x0 + halfWidth, y0 + halfHeight, 1.0f, halfWidth, halfHeight, 255u, 255u, 255u, 255, 1.0f, 255, 0, 0); + CSprite::RenderOneXLUSprite( + { x0 + halfWidth, y0 + halfHeight, 1.0f }, + { halfWidth, halfHeight }, + 255u, 255u, 255u, 255, + 1.0f, + 255, + 0, 0 + ); RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); } diff --git a/source/game_sa/IKChain_c.h b/source/game_sa/IKChain_c.h index 86ac386c8d..0f1b4d978f 100644 --- a/source/game_sa/IKChain_c.h +++ b/source/game_sa/IKChain_c.h @@ -11,7 +11,7 @@ class CEntity; class CPed; -class IKChain_c : public ListItem_c { +class IKChain_c : public ListItem_c { public: static void InjectHooks(); diff --git a/source/game_sa/IniFile.cpp b/source/game_sa/IniFile.cpp index 1ce6ac5679..fcc76a53a9 100644 --- a/source/game_sa/IniFile.cpp +++ b/source/game_sa/IniFile.cpp @@ -15,6 +15,8 @@ void CIniFile::InjectHooks() { // 0x56D070 void CIniFile::LoadIniFile() { + ZoneScoped; + CFileMgr::SetDir(""); auto file = CFileMgr::OpenFile("gta3.ini", "r"); if (file) { diff --git a/source/game_sa/InterestingEvents.cpp b/source/game_sa/InterestingEvents.cpp index 6aba7a5910..e6542d2cb1 100644 --- a/source/game_sa/InterestingEvents.cpp +++ b/source/game_sa/InterestingEvents.cpp @@ -40,7 +40,7 @@ CInterestingEvents::CInterestingEvents() { }; SetOptions(INTERESTING_EVENT_0, 5, 2000); - SetOptions(INTERESTING_EVENT_1, 1, 5000); + SetOptions(PEDS_CHATTING, 1, 5000); SetOptions(INTERESTING_EVENT_2, 1, 5000); SetOptions(INTERESTING_EVENT_3, 1, 5000); SetOptions(INTERESTING_EVENT_4, 2, 3000); @@ -149,6 +149,8 @@ void CInterestingEvents::Add(CInterestingEvents::EType type, CEntity* entity) { // 0x605A30 void CInterestingEvents::ScanForNearbyEntities() { + ZoneScoped; + return plugin::CallMethod<0x605A30, CInterestingEvents*>(this); if (!m_b1) diff --git a/source/game_sa/InterestingEvents.h b/source/game_sa/InterestingEvents.h index f69f6ed877..f05d5e7ef9 100644 --- a/source/game_sa/InterestingEvents.h +++ b/source/game_sa/InterestingEvents.h @@ -12,7 +12,7 @@ class CInterestingEvents { public: enum EType { INTERESTING_EVENT_0 = 0, // - INTERESTING_EVENT_1 = 1, // CTaskComplexChat::CreateFirstSubTask + PEDS_CHATTING = 1, // CTaskComplexChat::CreateFirstSubTask INTERESTING_EVENT_2 = 2, // CTaskComplexSunbathe::CreateNextSubTask INTERESTING_EVENT_3 = 3, // CTaskComplexUseAttractor::CreateFirstSubTask CTaskComplexUseClosestFreeScriptedAttractor::CreateFirstSubTask INTERESTING_EVENT_4 = 4, // diff --git a/source/game_sa/Interior/FurnitureEntity_c.h b/source/game_sa/Interior/FurnitureEntity_c.h new file mode 100644 index 0000000000..83e7b490c7 --- /dev/null +++ b/source/game_sa/Interior/FurnitureEntity_c.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class CEntity; + +struct FurnitureEntity_c : ListItem_c { + CEntity* m_entity; + uint16 m_tileX, m_tileY; +}; diff --git a/source/game_sa/Interior/FurnitureGroup_c.h b/source/game_sa/Interior/FurnitureGroup_c.h index 35aaefafc9..9ef9eb11c3 100644 --- a/source/game_sa/Interior/FurnitureGroup_c.h +++ b/source/game_sa/Interior/FurnitureGroup_c.h @@ -8,7 +8,7 @@ class Furniture_c; class FurnitureGroup_c { public: - List_c m_subGroupsList; + TList_c m_subGroupsList; public: static void InjectHooks(); diff --git a/source/game_sa/Interior/FurnitureItem.h b/source/game_sa/Interior/FurnitureItem.h index 3e0fea016a..885f6196c3 100644 --- a/source/game_sa/Interior/FurnitureItem.h +++ b/source/game_sa/Interior/FurnitureItem.h @@ -6,7 +6,7 @@ class CObject; -class FurnitureItem : public ListItem_c { +class FurnitureItem : public ListItem_c { public: FurnitureItem() = default; diff --git a/source/game_sa/Interior/FurnitureManager_c.cpp b/source/game_sa/Interior/FurnitureManager_c.cpp index 991d86770b..56fee9e6f9 100644 --- a/source/game_sa/Interior/FurnitureManager_c.cpp +++ b/source/game_sa/Interior/FurnitureManager_c.cpp @@ -1,6 +1,9 @@ #include "StdInc.h" #include "FurnitureManager_c.h" +auto& g_currSubGroupId = StaticRef(); +auto& g_currFurnitureId = StaticRef(); + void FurnitureManager_c::InjectHooks() { RH_ScopedClass(FurnitureManager_c); RH_ScopedCategory("Interior"); diff --git a/source/game_sa/Interior/FurnitureManager_c.h b/source/game_sa/Interior/FurnitureManager_c.h index 46decb6718..ec70487958 100644 --- a/source/game_sa/Interior/FurnitureManager_c.h +++ b/source/game_sa/Interior/FurnitureManager_c.h @@ -11,7 +11,7 @@ class FurnitureManager_c { public: FurnitureGroup_c m_groups[9]; FurnitureItem m_furnitureItem[512]; - List_c m_furnitureList; + TList_c m_furnitureList; public: static void InjectHooks(); @@ -28,3 +28,5 @@ class FurnitureManager_c { static int32 GetSubGroupId(const char* name); }; VALIDATE_SIZE(FurnitureManager_c, 0x2078); + +static inline auto& g_furnitureMan = StaticRef(); diff --git a/source/game_sa/Interior/FurnitureSubGroup_c.h b/source/game_sa/Interior/FurnitureSubGroup_c.h index dab01fe966..301a1bf609 100644 --- a/source/game_sa/Interior/FurnitureSubGroup_c.h +++ b/source/game_sa/Interior/FurnitureSubGroup_c.h @@ -6,13 +6,13 @@ class Furniture_c; -class FurnitureSubGroup_c : public ListItem_c { +class FurnitureSubGroup_c : public ListItem_c { public: - int32 m_nSubgroupId; - List_c m_Furnitures; - bool m_bCanPlaceInFrontOfWindow; - bool m_bIsTall; - bool m_bCanSteal; + int32 m_nSubgroupId; + TList_c m_Furnitures; + bool m_bCanPlaceInFrontOfWindow; + bool m_bIsTall; + bool m_bCanSteal; public: static void InjectHooks(); diff --git a/source/game_sa/Interior/Furniture_c.h b/source/game_sa/Interior/Furniture_c.h index 606ebb94de..638082d7be 100644 --- a/source/game_sa/Interior/Furniture_c.h +++ b/source/game_sa/Interior/Furniture_c.h @@ -2,7 +2,7 @@ #include "Base.h" -class Furniture_c : public ListItem_c { +class Furniture_c : public ListItem_c { public: int16 m_nModelId; // 0x8 int16 m_nId; // 0xA diff --git a/source/game_sa/Interior/InteriorEffectInfo_t.h b/source/game_sa/Interior/InteriorEffectInfo_t.h index c2b689f213..1430d412ec 100644 --- a/source/game_sa/Interior/InteriorEffectInfo_t.h +++ b/source/game_sa/Interior/InteriorEffectInfo_t.h @@ -4,16 +4,15 @@ #include "Vector.h" -class C2dEffect; -class CEntity; +struct C2dEffectInterior; +class CEntity; struct InteriorEffectInfo_t { - CEntity* m_pEntity; - int32 m_nEffectCount; - C2dEffect* m_pEffects[8]; - int32 m_nEffectIndicesInModelInfo[8]; - int32 m_field_48; - bool m_field_4C; - int8 m_field_4D[3]; + CEntity* entity; + size_t numFx; + C2dEffectInterior* fxs[8]; + int32 fxIds[8]; + float distSq; + bool8 culled; }; VALIDATE_SIZE(InteriorEffectInfo_t, 0x50); diff --git a/source/game_sa/Interior/InteriorGroup_c.cpp b/source/game_sa/Interior/InteriorGroup_c.cpp index 7da774b2f0..df56a8fc9c 100644 --- a/source/game_sa/Interior/InteriorGroup_c.cpp +++ b/source/game_sa/Interior/InteriorGroup_c.cpp @@ -35,8 +35,8 @@ void InteriorGroup_c::InjectHooks() { } // 0x5947E0 -void InteriorGroup_c::Init(CEntity* entity, int32 a3) { - plugin::CallMethod<0x5947E0, InteriorGroup_c*, CEntity*, int32>(this, entity, a3); +void InteriorGroup_c::Init(CEntity* entity, int32 id) { + plugin::CallMethod<0x5947E0, InteriorGroup_c*, CEntity*, int32>(this, entity, id); } // 0x5968E0 diff --git a/source/game_sa/Interior/InteriorGroup_c.h b/source/game_sa/Interior/InteriorGroup_c.h index f653cebf32..26946953b9 100644 --- a/source/game_sa/Interior/InteriorGroup_c.h +++ b/source/game_sa/Interior/InteriorGroup_c.h @@ -9,24 +9,23 @@ class CPed; class Interior_c; struct InteriorInfo_t; -class InteriorGroup_c : public ListItem_c { +class InteriorGroup_c : public ListItem_c { +public: CEntity* m_pEntity; // 0x8 - uint8 m_nId; // 0xC - uint8 m_furnitureId; // 0xD - uint8 m_animType; // 0xE - uint8 m_interiorCount; // 0xF + uint8 m_id; // 0xC + uint8 m_groupId; // 0xD + uint8 m_groupType; // 0xE + uint8 m_numInteriors; // 0xF Interior_c* m_interiors[8]; // 0x10 - int32 field_30; // 0x30 - int8 m_isVisible; // 0x34 - int8 m_removePeds; // 0x35 - int8 m_pedCount; // 0x36 - int8 field_37; // 0x37 + CEntryExit* m_enex; // 0x30 + bool m_isVisible; // 0x34 + bool m_lastIsVisible; // 0x35 + int8 m_numPeds; // 0x36 CPed* m_peds[16]; // 0x38 - int32 field_78[16]; // 0x78 + CPed* m_pedsToRemove[16]; // 0x78 int8 m_pathSetupComplete; // 0xB8 int8 m_updatePeds; // 0xB9 int8 m_animBlockReferenced; // 0xBA - int8 field_BB; // 0xBB public: static void InjectHooks(); @@ -34,7 +33,10 @@ class InteriorGroup_c : public ListItem_c { InteriorGroup_c() = default; // 0x597FE0 ~InteriorGroup_c() = default; // 0x597FF0 - void Init(CEntity* entity, int32 a3); + auto GetInteriors() { return m_interiors | rng::views::take(m_numInteriors); } + auto GetPeds() { return m_peds | rng::views::take(m_numPeds); } + + void Init(CEntity* entity, int32 id); void Update(); int32 AddInterior(Interior_c* interior); void SetupPeds(); @@ -58,5 +60,8 @@ class InteriorGroup_c : public ListItem_c { bool FindInteriorInfo(int32 a2, InteriorInfo_t** a3, Interior_c** a4); int32 GetNumInteriorInfos(int32 a2); int32 GetRandomInterior(); + auto GetId() const { return m_id; } + + auto GetInteriors() const { return m_interiors | std::views::take(m_numInteriors); } }; VALIDATE_SIZE(InteriorGroup_c, 0xBC); diff --git a/source/game_sa/Interior/InteriorManager_c.cpp b/source/game_sa/Interior/InteriorManager_c.cpp index ae89cf1fc4..058c1648de 100644 --- a/source/game_sa/Interior/InteriorManager_c.cpp +++ b/source/game_sa/Interior/InteriorManager_c.cpp @@ -1,47 +1,144 @@ #include "StdInc.h" + #include "InteriorManager_c.h" +#include "FurnitureManager_c.h" + +#include void InteriorManager_c::InjectHooks() { RH_ScopedClass(InteriorManager_c); RH_ScopedCategory("Interior"); RH_ScopedGlobalInstall(AddSameGroupEffectInfos, 0x598430, { .reversed = false }); - RH_ScopedGlobalInstall(AreAnimsLoaded, 0x5980F0, { .reversed = false }); + RH_ScopedGlobalInstall(AreAnimsLoaded, 0x5980F0); RH_ScopedInstall(PruneVisibleEffects, 0x598A60, { .reversed = false }); - RH_ScopedInstall(Exit, 0x598010, { .reversed = false }); + RH_ScopedInstall(Exit, 0x598010); RH_ScopedInstall(GetVisibleEffects, 0x598D80, { .reversed = false }); RH_ScopedInstall(IsInteriorEffectVisible, 0x598690, { .reversed = false }); - RH_ScopedInstall(GetPedsInterior, 0x598620, { .reversed = false }); - RH_ScopedInstall(ReturnInteriorToPool, 0x5984B0, { .reversed = false }); - RH_ScopedInstall(GetInteriorFromPool, 0x5984A0, { .reversed = false }); - RH_ScopedInstall(GetVectorsInterior, 0x5983D0, { .reversed = false }); - RH_ScopedInstall(SetStealableObjectStolen, 0x598390, { .reversed = false }); - // RH_ScopedOverloadedInstall(FindStealableObjectId, "", 0x598360, int32(InteriorManager_c::*)(CEntity *)); - // RH_ScopedOverloadedInstall(FindStealableObjectId, "", 0x5982F0, int32(InteriorManager_c::*)(int32, int32, CVector)); - RH_ScopedInstall(HasInteriorHadStealDataSetup, 0x5982B0, { .reversed = false }); - RH_ScopedInstall(IsGroupActive, 0x598280, { .reversed = false }); - RH_ScopedInstall(GetPedsInteriorGroup, 0x598240, { .reversed = false }); - RH_ScopedInstall(SetEntryExitPtr, 0x598180, { .reversed = false }); - RH_ScopedInstall(GetBoundingBox, 0x598090, { .reversed = false }); - RH_ScopedInstall(ActivatePeds, 0x598080, { .reversed = false }); + RH_ScopedInstall(GetPedsInterior, 0x598620); + RH_ScopedInstall(ReturnInteriorToPool, 0x5984B0); + RH_ScopedInstall(GetInteriorFromPool, 0x5984A0); + RH_ScopedInstall(GetVectorsInterior, 0x5983D0); + RH_ScopedInstall(SetStealableObjectStolen, 0x598390); + RH_ScopedOverloadedInstall(FindStealableObjectId, "1", 0x598360, int32(InteriorManager_c::*)(CEntity *)); + RH_ScopedOverloadedInstall(FindStealableObjectId, "2", 0x5982F0, int32(InteriorManager_c::*)(int32, int32, CVector)); + RH_ScopedInstall(HasInteriorHadStealDataSetup, 0x5982B0); + RH_ScopedInstall(IsGroupActive, 0x598280); + RH_ScopedInstall(GetPedsInteriorGroup, 0x598240); + RH_ScopedInstall(SetEntryExitPtr, 0x598180); + RH_ScopedInstall(GetBoundingBox, 0x598090); + RH_ScopedInstall(ActivatePeds, 0x598080); RH_ScopedInstall(inlined_prune_visible_effects, 0x598070, { .reversed = false }); - RH_ScopedInstall(Update, 0x598F50, { .reversed = false }); - RH_ScopedInstall(Init, 0x5C0500, { .reversed = false }); + RH_ScopedInstall(Update, 0x598F50); + RH_ScopedInstall(Init, 0x5C0500); } // 0x5C0500 void InteriorManager_c::Init() { - plugin::CallMethod<0x5C0500, InteriorManager_c*>(this); + m_freeze = false; + m_pruneVisibleEffects = true; + m_bPedsEnabled = true; + for (auto& i : m_interiors) { + m_interiorPool.AddItem(&i); + } + for (auto&& [i, g] : notsa::enumerate(m_interiorGroups)) { + g.m_id = (uint8)i; + m_interiorGroupPool.AddItem(&g); + } + g_furnitureMan.Init(); + m_enex = nullptr; + m_interiorCount = 0; + m_objectCount = 0; + rng::fill(m_interiorPedsAliveState, true); + m_lastUpdateTimeInMs = UINT32_MAX; } // 0x598F50 -int8 InteriorManager_c::Update() { - return plugin::CallMethodAndReturn(this); +bool InteriorManager_c::Update() { + ZoneScoped; + + if (m_freeze) { + return false; + } + + const auto plyr = FindPlayerPed(); + + InteriorEffectInfo_t visibleFxBuf[32]; + const auto numVisibleFx = plyr->m_nAreaCode != eAreaCodes::AREA_CODE_NORMAL_WORLD && m_pruneVisibleEffects && !plyr->GetTaskManager().GetActiveTaskAs() + ? GetVisibleEffects(visibleFxBuf, std::size(visibleFxBuf)) + : 0; + PruneVisibleEffects(visibleFxBuf, numVisibleFx, 8, 20.f); + const auto visibleFx = visibleFxBuf | rng::views::take(numVisibleFx); + + const auto InteriorGroupHasSameEffect = [](InteriorEffectInfo_t& fx, InteriorGroup_c& g) { + return fx.entity == g.GetEntity() && fx.fxs[0]->m_groupId == g.GetId(); + }; + + for (auto& g : m_interiorGroupList) { + if (rng::none_of(visibleFx, [&](InteriorEffectInfo_t& fx){ + return InteriorGroupHasSameEffect(fx, g) && !fx.culled; + })) { // Remove this group + g.Exit(); + m_interiorGroupPool.AddItem(&g); + m_interiorGroupList.RemoveItem(&g); + } + } + + bool hasAddedAnyInteriors{}; + for (auto& fx : visibleFx) { + //> 0x599071 + if (fx.culled) { + continue; + } + + //> 0x59909F + if (rng::any_of(m_interiorGroupList, [&](InteriorGroup_c& g) { + return InteriorGroupHasSameEffect(fx, g); + })) { + continue; + } + + //> 0x5990B8 + const auto grp = m_interiorGroupPool.RemoveHead(); + assert(grp); + grp->Init(fx.entity, fx.fxs[0]->m_groupId); + grp->m_enex = m_enex; + m_interiorGroupList.AddItem(grp); + + //> 0x59910C + for (auto k = fx.numFx; k-->0;) { + const auto i = m_interiorPool.RemoveHead(); + if (!k) { + break; + } + const auto& fxpos = fx.entity->GetPosition(); + + i->m_box = fx.fxs[k]; + i->m_interiorId = (uint32)(fxpos.x * fxpos.y * fxpos.z) + fx.fxIds[k]; + i->m_areaCode = fx.entity->m_nAreaCode; + i->m_pGroup = grp; + + i->Init(fx.fxs[k]->m_pos); + grp->AddInterior(i); + + hasAddedAnyInteriors = true; + } + + grp->Setup(); + + m_lastUpdateTimeInMs = CTimer::GetTimeInMS(); + } + + for (auto& g : m_interiorGroupList) { + g.Update(); + } + + return hasAddedAnyInteriors; } // 0x598A60 -void InteriorManager_c::PruneVisibleEffects(InteriorEffectInfo_t* effectInfo, int32 a2, int32 a3, float a4) { - plugin::CallMethod<0x598A60, InteriorManager_c*, InteriorEffectInfo_t*, int32, int32, float>(this, effectInfo, a2, a3, a4); +void InteriorManager_c::PruneVisibleEffects(InteriorEffectInfo_t* pInteriorEffectInfos, int32 numInfos, int32 reqdNumInfos, float maxDis) { + plugin::CallMethod<0x598A60, InteriorManager_c*, InteriorEffectInfo_t*, int32, int32, float>(this, pInteriorEffectInfos, numInfos, reqdNumInfos, maxDis); } // 0x598430 @@ -51,12 +148,26 @@ int8 InteriorManager_c::AddSameGroupEffectInfos(InteriorEffectInfo_t* effectInfo // 0x5980F0 bool InteriorManager_c::AreAnimsLoaded(int32 animBlock) { - return plugin::CallAndReturn(animBlock); + animBlock = [&] { + switch (animBlock) { + case 0: return CAnimManager::GetAnimationBlockIndex("int_house"); + case 1: return CAnimManager::GetAnimationBlockIndex("int_shop"); + case 2: return CAnimManager::GetAnimationBlockIndex("int_office"); + default: return animBlock; + } + }(); + return CAnimManager::GetAnimationBlock((AssocGroupId)animBlock)->bLoaded; } // 0x598010 void InteriorManager_c::Exit() { - plugin::CallMethod<0x598010, InteriorManager_c*>(this); + for (auto& g : m_interiorGroupList) { + g.Exit(); + } + m_interiorGroupList.RemoveAll(); + m_interiorPool.RemoveAll(); + m_interiorGroupPool.RemoveAll(); + g_furnitureMan.Exit(); } // 0x598D80 @@ -70,66 +181,112 @@ int32 InteriorManager_c::IsInteriorEffectVisible(C2dEffect* effect, CEntity* ent } // 0x598620 -int32 InteriorManager_c::GetPedsInterior(CPed* ped) { - return plugin::CallMethodAndReturn(this, ped); +Interior_c* InteriorManager_c::GetPedsInterior(CPed* ped) { + return GetVectorsInterior(ped->GetPosition()); } // 0x5984B0 -int32 InteriorManager_c::ReturnInteriorToPool(Interior_c* interior) { - return plugin::CallMethodAndReturn(this, interior); +void InteriorManager_c::ReturnInteriorToPool(Interior_c* interior) { + m_interiorPool.AddItem(interior); } // 0x5984A0 -ListItem_c* InteriorManager_c::GetInteriorFromPool() { - return plugin::CallMethodAndReturn(this); +Interior_c* InteriorManager_c::GetInteriorFromPool() { + return m_interiorPool.RemoveHead(); } // 0x5983D0 -int32 InteriorManager_c::GetVectorsInterior(CVector* pos) { - return plugin::CallMethodAndReturn(this, pos); +Interior_c* InteriorManager_c::GetVectorsInterior(const CVector& pt) { // TODO: Name is shit, should be `GetInteriorOfPoint` or something similar + for (auto& g : m_interiorGroupList) { + for (auto& i : g.GetInteriors()) { + if (i && i->IsPtInside(pt)) { + return i; + } + } + } + return nullptr; } // 0x598390 -int32 InteriorManager_c::SetStealableObjectStolen(CEntity* entity, uint8 a3) { - return plugin::CallMethodAndReturn(this, entity, a3); +void InteriorManager_c::SetStealableObjectStolen(CEntity* entity, uint8 isStolen) { + if (const auto idx = FindStealableObjectId(entity); idx != -1) { + m_objects[idx].wasStolen = isStolen; + } } // 0x598360 int32 InteriorManager_c::FindStealableObjectId(CEntity* entity) { - return plugin::CallMethodAndReturn(this, entity); + for (auto&& [i, v] : notsa::enumerate(GetObjects())) { + if (v.entity == entity) { + return i; + } + } + return -1; } // 0x5982F0 int32 InteriorManager_c::FindStealableObjectId(int32 interiorId, int32 modelId, CVector point) { - return plugin::CallMethodAndReturn(this, interiorId, modelId, point); + for (auto&& [i, v] : notsa::enumerate(GetObjects())) { + if (v.interiorId == interiorId && v.modelId == modelId && v.pos == point) { + return i; + } + } + return -1; } // 0x5982B0 bool InteriorManager_c::HasInteriorHadStealDataSetup(Interior_c* interior) { - return plugin::CallMethodAndReturn(this, interior); + return m_interiorCount && notsa::contains(GetInteriorIds(), interior->m_interiorId); } // 0x598280 -int8 InteriorManager_c::IsGroupActive(int32 group) { - return plugin::CallMethodAndReturn(this, group); +int8 InteriorManager_c::IsGroupActive(int32 groupType) { + return rng::any_of(m_interiorGroupList, [&](InteriorGroup_c& g) { + return g.m_groupType == groupType; + }); } // 0x598240 -ListItem_c* InteriorManager_c::GetPedsInteriorGroup(CPed* ped) { - return plugin::CallMethodAndReturn(this, ped); +InteriorGroup_c* InteriorManager_c::GetPedsInteriorGroup(CPed* ped) { + for (auto& grp : m_interiorGroupList) { + if (notsa::contains(grp.GetPeds(), ped)) { + return &grp; + } + } + return nullptr; } // 0x598180 -int32 InteriorManager_c::SetEntryExitPtr(CEntryExit* exit) { - return plugin::CallMethodAndReturn(this, exit); +void InteriorManager_c::SetEntryExitPtr(CEntryExit* enex) { + if ((enex->m_pLink ? enex->m_pLink : enex)->m_nArea == eAreaCodes::AREA_CODE_NORMAL_WORLD) { + return; + } + if (enex->m_recEntrance == m_enexRect) { + return; + } + + m_objectCount = 0; + m_interiorCount = 0; + + rng::fill(m_interiorPedsAliveState, true); + + m_enex = enex; + m_enexRect = enex->m_recEntrance; } // 0x598090 -int8 InteriorManager_c::GetBoundingBox(CEntity* entity, CVector* pos) { - return plugin::CallMethodAndReturn(this, entity, pos); +bool InteriorManager_c::GetBoundingBox(FurnitureEntity_c* entity, CVector* pos) { + for (auto& grp : m_interiorGroupList) { + for (const auto i : grp.GetInteriors()) { + if (i) { + return i->GetBoundingBox(entity, pos); + } + } + } + return false; } // 0x598080 -int8 InteriorManager_c::ActivatePeds(uint8 enable) { - return plugin::CallMethodAndReturn(this, enable); +void InteriorManager_c::ActivatePeds(bool enable) { + m_bPedsEnabled = enable; } diff --git a/source/game_sa/Interior/InteriorManager_c.h b/source/game_sa/Interior/InteriorManager_c.h index c0fa46b226..06474139b3 100644 --- a/source/game_sa/Interior/InteriorManager_c.h +++ b/source/game_sa/Interior/InteriorManager_c.h @@ -10,23 +10,23 @@ class InteriorManager_c { public: // TODO: Use TList_c instead of List_c - Interior_c m_interiors[8]; // 0x0 - List_c m_interiorList; // 0x3CA0 - InteriorGroup_c m_interiorGroups[8]; // 0x3CAC - List_c m_interiorGroupList; // 0x428C - List_c m_interiorGroupsList; // 0x4298 - int32 m_interiorCount; // 0x42A4 - int32 m_InteriorIds[64]; // 0x42A8 - int32 m_objectCount; // 0x43A8 - InteriorObject m_objects[32]; // 0x43AC - char m_interiorPedsAliveState[16]; // 0x472C - int32 dword473C; // 0x473C - CRect m_Rect; // 0x4740 - bool m_bIsInitialised; // 0x4750 - int8 m_pruneVisibleEffects; // 0x4751 - int8 m_bPedsEnabled; // 0x4752 - int8 field_4753; // 0x4753 - int32 m_lastUpdateTimeInMs; // 0x4754 + Interior_c m_interiors[8]; // 0x0 + TList_c m_interiorPool; // 0x3CA0 + InteriorGroup_c m_interiorGroups[8]; // 0x3CAC + TList_c m_interiorGroupList; // 0x428C + TList_c m_interiorGroupPool; // 0x4298 + size_t m_interiorCount; // 0x42A4 + int32 m_InteriorIds[64]; // 0x42A8 + int32 m_objectCount; // 0x43A8 + InteriorObject m_objects[32]; // 0x43AC + char m_interiorPedsAliveState[16]; // 0x472C + CEntryExit* m_enex; // 0x473C + CRect m_enexRect; // 0x4740 + bool m_freeze; // 0x4750 + int8 m_pruneVisibleEffects; // 0x4751 + int8 m_bPedsEnabled; // 0x4752 + int8 field_4753; // 0x4753 + int32 m_lastUpdateTimeInMs; // 0x4754 public: static void InjectHooks(); @@ -35,28 +35,55 @@ class InteriorManager_c { ~InteriorManager_c() = default; // 0x598580 void Init(); - int8 Update(); + bool Update(); void Exit(); static int8 AddSameGroupEffectInfos(InteriorEffectInfo_t* effectInfo, int32 a2); static bool AreAnimsLoaded(int32 animBlock); - void PruneVisibleEffects(InteriorEffectInfo_t* effectInfo, int32 a2, int32 a3, float a4); + void PruneVisibleEffects(InteriorEffectInfo_t* pInteriorEffectInfos, int32 numInfos, int32 reqdNumInfos, float maxDis); int32 GetVisibleEffects(InteriorEffectInfo_t* effectInfo, int32 totalEffects); int32 IsInteriorEffectVisible(C2dEffect* effect, CEntity* entity); - int32 GetPedsInterior(CPed* ped); - int32 ReturnInteriorToPool(Interior_c* interior); - ListItem_c* GetInteriorFromPool(); - int32 GetVectorsInterior(CVector* pos); - int32 SetStealableObjectStolen(CEntity* entity, uint8 a3); + + /*! + * @addr 0x5984B0 + * + * Add an interior to the free list + */ + void ReturnInteriorToPool(Interior_c* interior); + + /*! + * @addr 0x5984A0 + * @returns An interior from the free list + */ + Interior_c* GetInteriorFromPool(); + + /*! + * @addr 0x5983D0 + * + * @returns The interior the point `pt` lies within or `null` + */ + Interior_c* GetVectorsInterior(const CVector& pt); + + /*! + * @addr 0x598620 + * + * @returns The interior the ped (it's position) is within, or null + */ + Interior_c* GetPedsInterior(CPed* ped); + + void SetStealableObjectStolen(CEntity* entity, uint8 a3); int32 FindStealableObjectId(CEntity* entity); int32 FindStealableObjectId(int32 interiorId, int32 modelId, CVector point); bool HasInteriorHadStealDataSetup(Interior_c* interior); int8 IsGroupActive(int32 group); - ListItem_c* GetPedsInteriorGroup(CPed* ped); - int32 SetEntryExitPtr(CEntryExit* exit); - int8 GetBoundingBox(CEntity* entity, CVector* pos); - int8 ActivatePeds(uint8 enable); + InteriorGroup_c* GetPedsInteriorGroup(CPed* ped); + void SetEntryExitPtr(CEntryExit* exit); + bool GetBoundingBox(FurnitureEntity_c* entity, CVector* pos); + void ActivatePeds(bool enable); + + auto GetInteriorIds() const { return m_InteriorIds | rng::views::take(m_interiorCount); } + auto GetObjects() const { return m_objects | rng::views::take(m_objectCount); } void inlined_prune_visible_effects(bool prune) { m_pruneVisibleEffects = prune; } // 0x598070 }; diff --git a/source/game_sa/Interior/InteriorObject.h b/source/game_sa/Interior/InteriorObject.h index 05ad3769df..b36a333d52 100644 --- a/source/game_sa/Interior/InteriorObject.h +++ b/source/game_sa/Interior/InteriorObject.h @@ -7,10 +7,9 @@ class CEntity; struct InteriorObject { - CEntity* m_pObject; - int32 m_nModelId; - int32 m_nInteriorId; - CVector m_vPosition; - bool m_bIsStolen; - int8 m_field_19[3]; + CEntity* entity; + int32 modelId; + int32 interiorId; + CVector pos; + bool wasStolen; }; diff --git a/source/game_sa/Interior/Interior_c.cpp b/source/game_sa/Interior/Interior_c.cpp index b1446723b8..26fa1648d7 100644 --- a/source/game_sa/Interior/Interior_c.cpp +++ b/source/game_sa/Interior/Interior_c.cpp @@ -62,15 +62,9 @@ void Interior_c::InjectHooks() { RH_ScopedInstall(FurnishShop, 0x59A790, { .reversed = false }); } -// 0x5921D0 -Interior_c::Interior_c() : ListItem_c() { - field_3EC = -1; - m_nodeAddress.m_wAreaId = (uint16)-1; -} - // 0x593BF0 -int32 Interior_c::Init(CVector* a2) { - return plugin::CallMethodAndReturn(this, a2); +int32 Interior_c::Init(const CVector& pos) { + return plugin::CallMethodAndReturn(this, &pos); } // 0x592230 @@ -194,8 +188,8 @@ void Interior_c::Shop_FurnishEdges() { } // 0x593DB0 -int8 Interior_c::GetBoundingBox(FurnitureEntity_c* entity, CVector* a3) { - return plugin::CallMethodAndReturn(this, entity, a3); +bool Interior_c::GetBoundingBox(FurnitureEntity_c* entity, CVector* a3) { + return plugin::CallMethodAndReturn(this, entity, a3); } // 0x593910 @@ -210,13 +204,13 @@ CObject* Interior_c::PlaceObject(uint8 isStealable, Furniture_c* furniture, floa } // 0x5913B0 -ListItem_c* Interior_c::GetFurnitureEntity(CEntity* entity) { - return plugin::CallMethodAndReturn(this, entity); +FurnitureEntity_c* Interior_c::GetFurnitureEntity(CEntity* entity) { + return plugin::CallMethodAndReturn(this, entity); } // 0x5913E0 -bool Interior_c::IsPtInside(CVector* a2, float a3, float a4, float a5) { - return plugin::CallMethodAndReturn(this, a2, a3, a4, a5); +bool Interior_c::IsPtInside(const CVector& pt, CVector bias) { + return plugin::CallMethodAndReturn(this, pt, bias); } // 0x5914D0 diff --git a/source/game_sa/Interior/Interior_c.h b/source/game_sa/Interior/Interior_c.h index 0fd32b9fc3..1ddb704a55 100644 --- a/source/game_sa/Interior/Interior_c.h +++ b/source/game_sa/Interior/Interior_c.h @@ -6,25 +6,25 @@ #include "Vector.h" #include "NodeAddress.h" -#include "tEffectFurniture.h" #include "InteriorInfo_t.h" #include "List_c.h" #include "ListItem_c.h" +#include "./FurnitureEntity_c.h" class CEntity; class CObject; -class FurnitureEntity_c; // TODO class Furniture_c; +class InteriorGroup_c; -class Interior_c : public ListItem_c { +class Interior_c : public ListItem_c { public: int32 m_interiorId; // 0x8 - int32 m_pGroup; // 0xC + InteriorGroup_c* m_pGroup; // 0xC int32 m_areaCode; // 0x10 - tEffectFurniture* m_furnitureEffect; // 0x14 + tEffectInterior* m_box; // 0x14 RwMatrix m_matrix; // 0x18 int32 field_58; // 0x58 - List_c m_list; // 0x5C + TList_c m_list; // 0x5C - TODO: Figure out type char field_68[900]; // 0x68 int16 field_3EC; // 0x3EC int16 field_3EE; // 0x3EE @@ -55,10 +55,10 @@ class Interior_c : public ListItem_c { public: static void InjectHooks(); - Interior_c(); + Interior_c() = default; ~Interior_c() = default; // 0x591360 - int32 Init(CVector* a2); + int32 Init(const CVector& pos); void Exit(); CObject* Bedroom_AddTableItem(int32 a2, int32 a3, int32 a4, int32 a5, int32 a6, int32 a7); @@ -84,11 +84,11 @@ class Interior_c : public ListItem_c { void Shop_FurnishCeiling(); void Shop_AddShelfInfo(int32 a2, int32 a3, int32 a5); void Shop_FurnishEdges(); - int8 GetBoundingBox(FurnitureEntity_c* entity, CVector* a3); + bool GetBoundingBox(FurnitureEntity_c* entity, CVector* a3); void ResetTiles(); CObject* PlaceObject(uint8 isStealable, Furniture_c* furniture, float offsetX, float offsetY, float offsetZ, float rotationZ); - ListItem_c* GetFurnitureEntity(CEntity*); - bool IsPtInside(CVector* a2, float a3, float a4, float a5); + FurnitureEntity_c* GetFurnitureEntity(CEntity*); + bool IsPtInside(const CVector& pt, CVector bias = {}); void CalcMatrix(CVector* translation); void Furnish(); void Unfurnish(); diff --git a/source/game_sa/Interior/tEffectFurniture.h b/source/game_sa/Interior/tEffectFurniture.h deleted file mode 100644 index 5fef2def53..0000000000 --- a/source/game_sa/Interior/tEffectFurniture.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "Base.h" - -struct tEffectFurniture { - int8 m_type; - int8 m_id; - int8 m_x; - int8 m_y; - int8 m_z; - int8 field_5; - int8 field_6; - int8 field_7; - int8 field_8; - int8 field_9; - int8 field_A; - int8 field_B; - int8 field_C; - int8 field_D; - int8 field_E; - int8 field_F; - int8 field_10; - int8 field_11; - int8 field_12; - int8 field_13; - int8 field_14; - int8 field_15; - int8 field_16; - int8 field_17; - int8 field_18; - int8 field_19; - int8 field_1A; - int8 field_1B; - int8 field_1C; - int8 field_1D; - int8 field_1E; - int8 m_wealth; - float field_20; - int32 field_24; - int32 field_28; - int32 field_2C; - int32 field_30; -}; -VALIDATE_SIZE(tEffectFurniture, 0x34); diff --git a/source/game_sa/IplStore.cpp b/source/game_sa/IplStore.cpp index 21e66237d7..10ac0cce2f 100644 --- a/source/game_sa/IplStore.cpp +++ b/source/game_sa/IplStore.cpp @@ -282,6 +282,8 @@ void CIplStore::IncludeEntity(int32 iplSlotIndex, CEntity* entity) { * @brief Load remaining IPL defs (using the streamer) */ void CIplStore::LoadAllRemainingIpls() { + ZoneScoped; + // Can't use `ms_pPool->GetAllValid()` here, because we must ignore the first slot (whenever it's valid or not). for (auto slot = 1 /*skip 1st*/; slot < TOTAL_IPL_MODEL_IDS; slot++) { auto def = ms_pPool->GetAt(slot); diff --git a/source/game_sa/LoadMonitor.h b/source/game_sa/LoadMonitor.h index cdbf62f74c..00c8487d5f 100644 --- a/source/game_sa/LoadMonitor.h +++ b/source/game_sa/LoadMonitor.h @@ -5,25 +5,26 @@ TODO: size of CLoadMonitor is unknown, and it's uncomplete. */ class CLoadMonitor { public: - uint32 m_bInFrame; - uint32 m_bUseLoadMonitor; - uint32 m_bForceProcLevel; - char field_C; - char field_D; - char field_E; - char field_F; - char field_10; - char field_11; - char field_12; - char field_13; - char field_14; - char field_15; - char field_16; - char field_17; - char field_18; - char field_19; - char field_1A; - char field_1B; + enum class ELoadMonitorDisplay { + eNone = 0x0, + eCompact = 0x1, + eCompactPlusGraph = 0x2, + }; + + enum EProcessingLevel { + OK = 0x0, + MED = 0x1, + HIGH = 0x2, + }; + +public: + uint32 m_bInFrame; + uint32 m_bUseLoadMonitor; + uint32 m_bForceProcLevel; + ELoadMonitorDisplay m_DisplayType; + uint32 m_VarConsoleDisplayType; + EProcessingLevel m_eProcLevel; + EProcessingLevel m_eProcLevelToForce; char field_1C; char field_1D; char field_1E; diff --git a/source/game_sa/LoadingScreen.cpp b/source/game_sa/LoadingScreen.cpp index 0e9d857d44..bf2be98d18 100644 --- a/source/game_sa/LoadingScreen.cpp +++ b/source/game_sa/LoadingScreen.cpp @@ -9,6 +9,8 @@ #include "platform.h" #include "LoadingScreen.h" +#include "extensions/Configs/FastLoader.hpp" + void CLoadingScreen::InjectHooks() { RH_ScopedClass(CLoadingScreen); RH_ScopedCategoryGlobal(); @@ -175,9 +177,10 @@ void CLoadingScreen::Continue() { // 0x590370 void CLoadingScreen::RenderLoadingBar() { +#ifdef PRINT_LOADMSG // NOTSA - // TODO: Add some kind of ifdef for dev stuff // TODO: Fix new-line not rendered when using fastload into a savegame + char loadingMsg[1024]; *std::format_to(loadingMsg, "{}\n{}", m_LoadingGxtMsg1, m_LoadingGxtMsg2) = 0; CFont::SetOrientation(eFontAlignment::ALIGN_LEFT); @@ -189,6 +192,7 @@ void CLoadingScreen::RenderLoadingBar() { loadingMsg ); CFont::RenderFontBuffer(); +#endif if (m_TimeBarAppeared == 0.0f) { m_TimeBarAppeared = GetClockTime(); @@ -240,7 +244,7 @@ void CLoadingScreen::DisplayPCScreen() { DefinedState2d(); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); RenderSplash(); - if (!FastLoadSettings.NoLoadBar) { + if (!g_FastLoaderConfig.NoLoadBar) { if (m_currDisplayedSplash > 0 && (!m_bFading || m_currDisplayedSplash != 1)) { RenderLoadingBar(); } @@ -299,7 +303,7 @@ void CLoadingScreen::DoPCScreenChange(uint32 finish) { #endif } - if (!FastLoadSettings.NoFading) { + if (!g_FastLoaderConfig.NoFading) { for (auto i = 20; i > 0; i--) { m_FadeAlpha = 0; DisplayPCScreen(); @@ -344,11 +348,11 @@ void CLoadingScreen::NewChunkLoaded() { } #ifdef FIX_BUGS // Fix copyright screen appearing instead of an actual loading screen splash - if (m_currDisplayedSplash && delta < FastLoadSettings.ScreenChangeTime) { + if (m_currDisplayedSplash && delta < g_FastLoaderConfig.ScreenChangeTime) { #else if ((m_currDisplayedSplash && delta < 5.0f) || (!m_currDisplayedSplash && delta < 5.5f)) { #endif - if (!FastLoadSettings.NoLoadScreen || !FastLoadSettings.NoLoadBar) { + if (!g_FastLoaderConfig.NoLoadScreen || !g_FastLoaderConfig.NoLoadBar) { DisplayPCScreen(); } } else { // New splash screen @@ -377,7 +381,7 @@ void CLoadingScreen::SkipCopyrightSplash() { // 0x53DED0 void LoadingScreen(const char* msg1, const char* msg2, const char* msg3) { if (msg1) { - if (!FastLoadSettings.NoDbgLogScreens) { // Very slow, so skip it + if (!g_FastLoaderConfig.NoDbgLogScreens) { // Very slow, so skip it DEV_LOG("Loadingscreen: {} [{}][{}]", msg1, msg2 ? msg2 : "NULL", msg3 ? msg3 : "NULL"); } CLoadingScreen::SetLoadingBarMsg(msg1, msg2); diff --git a/source/game_sa/LoadingScreen.h b/source/game_sa/LoadingScreen.h index f3c0c02e0b..88e25601ba 100644 --- a/source/game_sa/LoadingScreen.h +++ b/source/game_sa/LoadingScreen.h @@ -7,63 +7,6 @@ #pragma once class CSprite2d; - -// TODO: Move this to an appropriate place -inline struct FastLoadSettings { - int32 SaveGameToLoad = -1; //< -2 - Don't load, -1 = Load first available, 0 <= - load from save slot - bool TriedLoadingSaveGame = false; //< [Runtime Var] Whenever `SaveGameToLoad` was tried to be loaded - uint32 SkipSaveGameLoadKey = VK_CONTROL; //< Skip auto-loading save game (If enabled) - uint32 SoundDelay = 50; - - bool NoEAX = true; //< Skip EAX splash - bool NoNVidia = true; //< Skip nVidia splash - bool NoLogo = true; //< Skip Logo.mpg - bool NoTitleOrIntro = true; //< Skip GTAtitles.mpg - bool NoCopyright = true; //< Skip Copyright screen - bool NoFading = true; //< Skip fading (takes quite a bit of time) - bool NoLoadScreen = true; //< Skip load pre-game screen - bool NoLoadBar = false; //< Skip load bar in pre-game load screen - bool NoLoadingTune = true; //< Skip GTA theme music - bool NoDbgLogScreens = true; //< Don't display [notsa] loading screen debug messages to console - bool RenderAtAllTimes = true; //< Render even when minimized - float ScreenChangeTime = 5.f; //< Time to change splash screens in the loading screen (seconds?) - - //! If there's a game to load, but not loaded yet... - bool ShouldLoadSaveGame() const { return SaveGameToLoad >= -1 && !TriedLoadingSaveGame; } - - //! Start the game from a slot - //! `slot` can have the same values as `SaveGameToLoad` - bool StartGame(int32 slot) { - if (slot == -1) { // Find first valid slot and load that - CFileMgr::SetDirMyDocuments(); - for (auto i = 0u; i < MAX_SAVEGAME_SLOTS; i++) { - if (std::filesystem::exists(std::format("GTASAsf{}.b", i + 1))) { // Save file IDs start from 1, not 0 - return StartGame(i); // Load this slot - } - } - CFileMgr::ChangeDir(""); - DEV_LOG("Didn't find any savegame to load!"); - return false; - } - - // Load game from slot - assert(slot >= 0); - - DEV_LOG("Loading game from slot ({})", slot); - - if (!NoLoadingTune) { - for (size_t i = 0; i < SoundDelay; i++) { - AudioEngine.ServiceLoadingTune(1.f); - } - } - - // Actually load the game (By simulating the user going in the menu and doing it) - FrontEndMenuManager.SimulateGameLoad(false, slot); - - return true; - } -} FastLoadSettings{}; - class CLoadingScreen { public: static constexpr size_t MAX_SPLASHES = 7u; diff --git a/source/game_sa/Maths.cpp b/source/game_sa/Maths.cpp index 58c9536bc0..98bfaef2f1 100644 --- a/source/game_sa/Maths.cpp +++ b/source/game_sa/Maths.cpp @@ -12,8 +12,9 @@ void CMaths::InjectHooks() RH_ScopedInstall(InitMathsTables, 0x59AC90); } -void CMaths::InitMathsTables() -{ +void CMaths::InitMathsTables() { + ZoneScoped; + for (int32 i = 0; i < 256; ++i) ms_SinTable[i] = sin(static_cast(i) * PI / 128.0F); } diff --git a/source/game_sa/MenuManager.cpp b/source/game_sa/MenuManager.cpp index 0b8ecca832..97281f0b8c 100644 --- a/source/game_sa/MenuManager.cpp +++ b/source/game_sa/MenuManager.cpp @@ -67,7 +67,7 @@ void CMenuManager::InjectHooks() { RH_ScopedInstall(CheckFrontEndDownInput, 0x5738B0); RH_ScopedInstall(CheckFrontEndLeftInput, 0x573920); RH_ScopedInstall(CheckFrontEndRightInput, 0x573990); - RH_ScopedInstall(CheckForMenuClosing, 0x576B70); + RH_ScopedInstall(CheckForMenuClosing, 0x576B70, { .locked = true }); // Must be hooked at all times otherwise imgui stops working! [The input at least does] RH_ScopedInstall(CheckHover, 0x57C4F0); RH_ScopedInstall(CheckMissionPackValidMenu, 0x57D720); RH_ScopedInstall(CheckCodesForControls, 0x57DB20, { .reversed = false }); @@ -317,7 +317,7 @@ void CMenuManager::DoSettingsBeforeStartingAGame() { if (m_bMenuActive) AudioEngine.Reset(); - m_nRadioStation = static_cast(CAEAudioUtility::GetRandomNumberInRange(1, RADIO_COUNT - 1)); + m_nRadioStation = CAEAudioUtility::GetRandomRadioStation(); m_bDontDrawFrontEnd = true; m_bStartGameLoading = true; @@ -435,12 +435,12 @@ void CMenuManager::ScrollRadioStations(int8 numStations) { return; } - m_nRadioStation += numStations; - if (m_nRadioStation <= 0) { - m_nRadioStation = RADIO_COUNT - 1; + m_nRadioStation = static_cast(m_nRadioStation + numStations); + if (m_nRadioStation <= RADIO_EMERGENCY_AA) { + m_nRadioStation = RADIO_OFF; } if (m_nRadioStation >= RADIO_COUNT) { - m_nRadioStation = 1; + m_nRadioStation = RADIO_CLASSIC_HIP_HOP; } AudioEngine.RetuneRadio(m_nRadioStation); SaveSettings(); @@ -491,7 +491,7 @@ void CMenuManager::SetDefaultPreferences(eMenuScreen screen) { m_bWidescreenOn = false; m_bMapLegend = false; m_nRadarMode = eRadarMode::MAPS_AND_BLIPS; - m_nDisplayVideoMode = m_nPrefsVideoMode; + m_nDisplayVideoMode = -1; // Originally m_nPrefsVideoMode. Look at: `psSelectDevice`. m_ShowLocationsBlips = true; m_ShowContactsBlips = true; m_ShowMissionBlips = true; @@ -572,6 +572,13 @@ void CMenuManager::JumpToGenericMessageScreen(eMenuScreen screen, const char* ti // 0x57C520 void CMenuManager::CentreMousePointer() { +#ifdef FIX_BUGS + // Not really a vanilla bug, because the vanilla game stops rendering when not in foreground + if (!IsForegroundApp()) { + return; + } +#endif + CVector2D pos{ SCREEN_WIDTH / 2.0f, SCREEN_HEIGHT / 2.0f }; if (pos.x != 0.0f && pos.y != 0.0f) { RsMouseSetPos(&pos); @@ -593,9 +600,9 @@ void CMenuManager::LoadSettings() { SetDefaultPreferences(SCREEN_DISPLAY_SETTINGS); SetDefaultPreferences(SCREEN_DISPLAY_ADVANCED); SetDefaultPreferences(SCREEN_CONTROLLER_SETUP); - m_nPrefsVideoMode = 0; + m_nPrefsVideoMode = -1; // Originally 0. Look at: `psSelectDevice`. m_nPrefsLanguage = eLanguage::AMERICAN; - m_nRadioStation = 1; + m_nRadioStation = RADIO_CLASSIC_HIP_HOP; CFileMgr::CloseFile(file); CFileMgr::SetDir(""); diff --git a/source/game_sa/MenuManager.h b/source/game_sa/MenuManager.h index 5e82792f59..f5e0786e47 100644 --- a/source/game_sa/MenuManager.h +++ b/source/game_sa/MenuManager.h @@ -89,7 +89,7 @@ class CMenuManager { int8 m_nRadioVolume; bool m_bRadioEq; - int8 m_nRadioStation; + eRadioID m_nRadioStation; char field_53; int32 m_nCurrentScreenItem; bool m_bQuitGameNoDVD; // CMenuManager::WaitForUserCD 0x57C5E0 @@ -108,8 +108,8 @@ class CMenuManager { CVector2D m_vMousePos; // Red marker position (world coordinates) bool m_bMapLoaded; - int32 m_nTitleLanguage; - int32 m_nTextLanguage; + int32 m_nTitleLanguage; // Value is PRIMARYLANGID(GetSystemDefaultLCID()) + int32 m_nTextLanguage; // TODO: Change to `eLanguage` eLanguage m_nPrefsLanguage; eLanguage m_nPreviousLanguage; int32 m_nLanguageF0x88; diff --git a/source/game_sa/Messages.cpp b/source/game_sa/Messages.cpp index f5e93617e0..61105c5d84 100644 --- a/source/game_sa/Messages.cpp +++ b/source/game_sa/Messages.cpp @@ -43,6 +43,8 @@ void CMessages::InjectHooks() { // Initialises messages // 0x69EE00 void CMessages::Init() { + ZoneScoped; + ClearMessages(true); rng::fill(PreviousBriefs, tPreviousBrief{}); } @@ -355,6 +357,8 @@ void CMessages::ClearThisPrintBigNow(eMessageStyle style) { // Removes all displayed messages // 0x69EDC0 void CMessages::ClearAllMessagesDisplayedByGame(bool unk) { + ZoneScoped; + ClearMessages(unk); ClearPreviousBriefArray(); CHud::GetRidOfAllHudMessages(unk); @@ -557,6 +561,8 @@ void CMessages::Process() { // Displays messages // 0x69EFC0 void CMessages::Display(bool bNotFading) { + ZoneScoped; + char msgText[MSG_BUF_SZ]; const auto PreProcessMsgText = [&](tMessage msg) { InsertNumberInString( diff --git a/source/game_sa/Mirrors.cpp b/source/game_sa/Mirrors.cpp index 907bd57bce..f1a95b80af 100644 --- a/source/game_sa/Mirrors.cpp +++ b/source/game_sa/Mirrors.cpp @@ -116,6 +116,8 @@ void CMirrors::BuildCamMatrix(CMatrix& mat, CVector pointA, CVector pointB) { // 0x726090 void CMirrors::RenderMirrorBuffer() { + ZoneScoped; + if (TypeOfMirror == MIRROR_TYPE_NONE) return; @@ -351,6 +353,8 @@ void CMirrors::BeforeConstructRenderList() { // 0x727140 void CMirrors::BeforeMainRender() { + ZoneScoped; + if (TypeOfMirror == MIRROR_TYPE_NONE) return; diff --git a/source/game_sa/MissionCleanup.cpp b/source/game_sa/MissionCleanup.cpp index 536a2a366f..8a1ded52e6 100644 --- a/source/game_sa/MissionCleanup.cpp +++ b/source/game_sa/MissionCleanup.cpp @@ -52,6 +52,8 @@ void CMissionCleanup::RemoveEntityFromList(int32 handle, MissionCleanUpEntityTyp * @addr 0x4652D0 */ void CMissionCleanup::CheckIfCollisionHasLoadedForMissionObjects() { + ZoneScoped; + ((void(__thiscall*)(CMissionCleanup*))0x4652D0)(this); } diff --git a/source/game_sa/ModelIndices.cpp b/source/game_sa/ModelIndices.cpp index a9370e56a2..7265f2e2d1 100644 --- a/source/game_sa/ModelIndices.cpp +++ b/source/game_sa/ModelIndices.cpp @@ -181,6 +181,8 @@ void ModelIndices::InjectHooks() { // 0x5BCA10 void InitModelIndices() { + ZoneScoped; + using namespace ModelIndices; MI_TRAFFICLIGHTS = -1; @@ -351,6 +353,8 @@ void InitModelIndices() // 0x5B57C0 void MatchAllModelStrings() { + ZoneScoped; + using namespace ModelIndices; struct {const char* name; ModelIndex& index; } models[] = { diff --git a/source/game_sa/Models/ClumpModelInfo.cpp b/source/game_sa/Models/ClumpModelInfo.cpp index 704791737d..92fa3b8a0f 100644 --- a/source/game_sa/Models/ClumpModelInfo.cpp +++ b/source/game_sa/Models/ClumpModelInfo.cpp @@ -330,7 +330,6 @@ RwFrame* CClumpModelInfo::FindFrameFromNameWithoutIdCB(RwFrame* frame, void* sea searchInfo->m_pFrame = frame; return nullptr; } - RwFrameForAllChildren(frame, FindFrameFromNameWithoutIdCB, searchData); if (searchInfo->m_pFrame) return nullptr; diff --git a/source/game_sa/Models/ModelInfo.cpp b/source/game_sa/Models/ModelInfo.cpp index 1bbe24681f..26a515c06c 100644 --- a/source/game_sa/Models/ModelInfo.cpp +++ b/source/game_sa/Models/ModelInfo.cpp @@ -197,8 +197,9 @@ CPedModelInfo* CModelInfo::AddPedModel(int32 index) } // 0x4C6810 -void CModelInfo::Initialise() -{ +void CModelInfo::Initialise() { + ZoneScoped; + memset(ms_modelInfoPtrs, 0, sizeof(ms_modelInfoPtrs)); ms_damageAtomicModelInfoStore.m_nCount = 0; ms_lodAtomicModelInfoStore.m_nCount = 0; diff --git a/source/game_sa/Models/VehicleModelInfo.cpp b/source/game_sa/Models/VehicleModelInfo.cpp index 6b987134d2..215f288bff 100644 --- a/source/game_sa/Models/VehicleModelInfo.cpp +++ b/source/game_sa/Models/VehicleModelInfo.cpp @@ -1253,8 +1253,9 @@ RpAtomic* CVehicleModelInfo::StoreAtomicUsedMaterialsCB(RpAtomic* atomic, void* return atomic; } -void CVehicleModelInfo::SetupCommonData() -{ +void CVehicleModelInfo::SetupCommonData() { + ZoneScoped; + LoadVehicleColours(); CLoadingScreen::NewChunkLoaded(); LoadVehicleUpgrades(); diff --git a/source/game_sa/MotionBlurStreaks.cpp b/source/game_sa/MotionBlurStreaks.cpp index 909e095dfe..ce27bbfd74 100644 --- a/source/game_sa/MotionBlurStreaks.cpp +++ b/source/game_sa/MotionBlurStreaks.cpp @@ -35,8 +35,9 @@ void CMotionBlurStreaks::Update() } // 0x7240E0 -void CMotionBlurStreaks::Render() -{ +void CMotionBlurStreaks::Render() { + ZoneScoped; + bool bRenderParamsSet = false; for (CRegisteredMotionBlurStreak& streak : CMotionBlurStreaks::aStreaks) { if (streak.m_nId) { diff --git a/source/game_sa/MovingThings.cpp b/source/game_sa/MovingThings.cpp index 49dc14da41..5697f12e27 100644 --- a/source/game_sa/MovingThings.cpp +++ b/source/game_sa/MovingThings.cpp @@ -30,12 +30,16 @@ void CMovingThings::Shutdown() { // 0x7185B0 void CMovingThings::Update() { + ZoneScoped; + CPlaneTrails::Update(); CEscalators::Update(); } // 0x717150 void CMovingThings::Render() { + ZoneScoped; + // NOP } diff --git a/source/game_sa/NodeAddress.h b/source/game_sa/NodeAddress.h index 610b7b826d..c1b1eff17d 100644 --- a/source/game_sa/NodeAddress.h +++ b/source/game_sa/NodeAddress.h @@ -21,7 +21,7 @@ class CNodeAddress { void ResetNodeId() { m_wNodeId = UINT16_MAX; } [[nodiscard]] bool IsAreaValid() const { return m_wAreaId != (uint16)-1; } - [[nodiscard]] bool IsValid() const { return m_wAreaId != (uint16)-1 || m_wNodeId != (uint16)-1; } + [[nodiscard]] bool IsValid() const { return IsAreaValid() && m_wNodeId != UINT16_MAX; } operator bool() const { return IsValid(); } }; diff --git a/source/game_sa/ObjectData.cpp b/source/game_sa/ObjectData.cpp index 6c7624c568..99b0c7fb35 100644 --- a/source/game_sa/ObjectData.cpp +++ b/source/game_sa/ObjectData.cpp @@ -14,8 +14,10 @@ void CObjectData::InjectHooks() } // 0x5B5360 -void CObjectData::Initialise(const char* fileName) -{ +void CObjectData::Initialise(const char* fileName) { + ZoneScoped; + ZoneText(fileName, strlen(fileName)); + auto& default0 = CObjectData::GetDefault(); default0.m_fMass = 99999.0F; default0.m_fTurnMass = 99999.0F; diff --git a/source/game_sa/Occlusion.cpp b/source/game_sa/Occlusion.cpp index 5a18705713..43719fd9b5 100644 --- a/source/game_sa/Occlusion.cpp +++ b/source/game_sa/Occlusion.cpp @@ -39,8 +39,9 @@ void COcclusion::InjectHooks() } // 0x71DCA0 -void COcclusion::Init() -{ +void COcclusion::Init() { + ZoneScoped; + NumOccludersOnMap = 0; NumInteriorOcculdersOnMap = 0; FarAwayList = -1; diff --git a/source/game_sa/Pad.cpp b/source/game_sa/Pad.cpp index 16de590488..910207a87d 100644 --- a/source/game_sa/Pad.cpp +++ b/source/game_sa/Pad.cpp @@ -7,10 +7,11 @@ #include "StdInc.h" #include "Pad.h" - +#include "platform/win/Input.h" #include "UIRenderer.h" #include "ControllerConfigManager.h" #include "app.h" +#include "platform/win/Platform.h" // mouse states CMouseControllerState& CPad::PCTempMouseControllerState = *(CMouseControllerState*)0xB73404; @@ -41,15 +42,15 @@ void CPad::InjectHooks() { RH_ScopedInstall(ClearMouseHistory, 0x541BD0); RH_ScopedInstall(Clear, 0x541A70); RH_ScopedInstall(Update, 0x541C40); - RH_ScopedInstall(UpdateMouse, 0x53F3C0, { .reversed = false }); - RH_ScopedInstall(ProcessPad, 0x746A10, { .reversed = false }); + RH_ScopedInstall(UpdateMouse, 0x53F3C0); + RH_ScopedInstall(ProcessPad, 0x746A10); RH_ScopedInstall(ProcessPCSpecificStuff, 0x53FB40); RH_ScopedInstall(ReconcileTwoControllersInput, 0x53F530); RH_ScopedInstall(SetTouched, 0x53F200); RH_ScopedInstall(GetTouchedTimeDelta, 0x53F210); RH_ScopedInstall(StartShake, 0x53F920); RH_ScopedInstall(StartShake_Distance, 0x53F9A0); - //RH_ScopedInstall(StartShake_Train, 0x53FA70, { .reversed = false }); + RH_ScopedInstall(StartShake_Train, 0x53FA70); RH_ScopedInstall(StopShaking, 0x53FB50); RH_ScopedInstall(GetCarGunLeftRight, 0x53FC50); RH_ScopedInstall(GetCarGunUpDown, 0x53FC10); @@ -212,11 +213,18 @@ void CPad::Update(int32 pad) { // 0x541DD0 void CPad::UpdatePads() { - GetPad(0)->UpdateMouse(); - ProcessPad(false); + ZoneScoped; + + const auto& ImIONavActive = notsa::ui::UIRenderer::GetSingleton().GetImIO()->NavActive; + if (!ImIONavActive) { + GetPad(0)->UpdateMouse(); + } + + ProcessPad(0); ControlsManager.ClearSimButtonPressCheckers(); - if (!notsa::ui::UIRenderer::GetSingleton().Visible()) { // NOTSA: Don't handle updates if the menu is open, so we don't affect gameplay inputting text + + if (!ImIONavActive) { ControlsManager.AffectPadFromKeyBoard(); ControlsManager.AffectPadFromMouse(); GetPad(0)->Update(0); @@ -225,18 +233,94 @@ void CPad::UpdatePads() { OldKeyState = NewKeyState; NewKeyState = TempKeyState; - - notsa::ui::UIRenderer::GetSingleton().UpdateInput(); } // 0x53F3C0 +// NOTSA(Grinch_): Game does this a bit differently, but does the same thing void CPad::UpdateMouse() { - plugin::CallMethod<0x53F3C0, CPad*>(this); + if (ForegroundApp) { + int32_t invertX, invertY; + + invertX = FrontEndMenuManager.bInvertMouseX ? -1 : 1; + invertY = FrontEndMenuManager.bInvertMouseY ? -1 : 1; + + CMouseControllerState state = WinInput::GetMouseState(); + if (state.CheckForInput()) { + CPad::GetPad(0)->LastTimeTouched = CTimer::m_snTimeInMilliseconds; + } + + // Write directly to NewMouseControllerState + CPad::OldMouseControllerState = std::exchange(CPad::NewMouseControllerState, state); + CPad::NewMouseControllerState.X *= invertX; + CPad::NewMouseControllerState.Y *= invertY; + } } // 0x746A10 -void CPad::ProcessPad(bool padNum) { - ((void(__cdecl*)(bool))0x746A10)(padNum); +void CPad::ProcessPad(int padNum) { + LPDIRECTINPUTDEVICE8* pDiDevice = nullptr; + DIJOYSTATE2 joyState; + + if (padNum == 0) { + pDiDevice = &PSGLOBAL(diDevice1); + } else if (padNum == 1) { + pDiDevice = &PSGLOBAL(diDevice2); + } else { + return; + } + + if (!*pDiDevice) { + return; + } + + if (FAILED((*pDiDevice)->Poll())) { + (*pDiDevice)->Acquire(); + return; + } + + + WIN_FCHECK((*pDiDevice)->GetDeviceState(sizeof(joyState), &joyState)); + + if (ControlsManager.m_bJoyJustInitialised) { + ControlsManager.m_OldJoyState = ControlsManager.m_NewJoyState = joyState; + ControlsManager.m_bJoyJustInitialised = false; + } else { + ControlsManager.m_OldJoyState = std::exchange(ControlsManager.m_NewJoyState, joyState); + } + RsPadEventHandler(RsEvent::rsPADBUTTONUP, &padNum); + + if (*pDiDevice) { + float padX1 = joyState.lX / 2000.0f; + float padX2 = joyState.lY / 2000.0f; + float padY1 = 0.0f; + float padY2 = 0.0f; + + if (joyState.rgdwPOV[1] >= 0) { + padX1 = sin(joyState.rgdwPOV[1] / 5730.0f); + padY1 = cos(joyState.rgdwPOV[1] / 5730.0f) * -1.0f; + } + if (PadConfigs[padNum].rzAxisPresent && PadConfigs[padNum].zAxisPresent) { + padX2 = joyState.lZ / 2000.0f; + padY2 = joyState.lRz / 2000.0f; + } + + RsPadEventHandler(RsEvent::rsPADBUTTONUP, &padNum); + RsPadEventHandler(RsEvent::rsPADBUTTONDOWN, &padNum); + CPad* pPad = CPad::GetPad(padNum); + + const auto UpdateJoyStickPosition = [](float pos, int16& outA, int16& outB, bool isInverted, bool isSwapped) { + if (fabs(pos) > 0.3f) { + pos = isInverted ? -pos : pos; + pos /= 128.f; + (isSwapped ? outA : outB) = (int32)pos; + } + }; + + UpdateJoyStickPosition(padX1, pPad->PCTempJoyState.LeftStickY, pPad->PCTempJoyState.LeftStickX, FrontEndMenuManager.m_bInvertPadX1, FrontEndMenuManager.m_bSwapPadAxis1); + UpdateJoyStickPosition(padY1, pPad->PCTempJoyState.LeftStickX, pPad->PCTempJoyState.LeftStickY, FrontEndMenuManager.m_bInvertPadY1, FrontEndMenuManager.m_bSwapPadAxis2); + UpdateJoyStickPosition(padX2, pPad->PCTempJoyState.LeftStickY, pPad->PCTempJoyState.LeftStickX, FrontEndMenuManager.m_bInvertPadX2, FrontEndMenuManager.m_bSwapPadAxis1); + UpdateJoyStickPosition(padY2, pPad->PCTempJoyState.LeftStickX, pPad->PCTempJoyState.LeftStickY, FrontEndMenuManager.m_bInvertPadY2, FrontEndMenuManager.m_bSwapPadAxis2); + } } // 0x53FB40 @@ -354,7 +438,7 @@ void CPad::StartShake(int16 time, uint8 freq, uint32 shakeDelayMs) { ShakeDur = time; ShakeFreq = freq; } - NoShakeBeforeThis = float(shakeDelayMs + CTimer::GetTimeInMS()); + NoShakeBeforeThis = (float)(shakeDelayMs + CTimer::GetTimeInMS()); NoShakeFreq = freq; } } else { @@ -382,22 +466,22 @@ void CPad::StartShake_Distance(int16 time, uint8 freq, CVector pos) { } } -/* todo: // 0x53FA70 void CPad::StartShake_Train(const CVector2D& point) { - if (!FrontEndMenuManager.m_PrefsUseVibration || CCutsceneMgr::ms_running) + if (!FrontEndMenuManager.m_PrefsUseVibration || CCutsceneMgr::ms_running) { return; + } - if (FindPlayerVehicle(-1) && FindPlayerVehicle(-1)->IsTrain()) + if (!FindPlayerVehicle(-1) || !FindPlayerVehicle(-1)->IsTrain()) { return; + } - auto fDistSq = DistanceBetweenPointsSquared(TheCamera.GetPosition(), point); + const auto fDistSq = (TheCamera.GetPosition() - point).SquaredMagnitude(); if (ShakeDur < 100 && fDistSq < 4900.0f) { ShakeDur = 100; ShakeFreq = static_cast(70.0f - sqrt(fDistSq) + 30.0f); } } -*/ // 0x541D70 void CPad::StopPadsShaking() { @@ -1190,10 +1274,10 @@ int GetCurrentKeyPressed(RsKeyCodes& keys) { return plugin::CallAndReturn(keys); } -IDirectInputDevice8* DIReleaseMouse() { +IDirectInputDevice8* DIReleaseMouse() { // todo: wininput return plugin::CallAndReturn(); } void InitialiseMouse(bool exclusive) { - plugin::Call<0x7469A0, bool>(exclusive); + WinInput::diMouseInit(exclusive); } diff --git a/source/game_sa/Pad.h b/source/game_sa/Pad.h index e70e674d1a..df5731e876 100644 --- a/source/game_sa/Pad.h +++ b/source/game_sa/Pad.h @@ -106,7 +106,7 @@ class CPad { void Update(int32 pad); static void UpdatePads(); void UpdateMouse(); - static void ProcessPad(bool numPad); + static void ProcessPad(int numPad); void ProcessPCSpecificStuff(); CControllerState& ReconcileTwoControllersInput(CControllerState& out, const CControllerState& controllerA, const CControllerState& controllerB); diff --git a/source/game_sa/PathFind.cpp b/source/game_sa/PathFind.cpp index e25079eda0..734e6f16ae 100644 --- a/source/game_sa/PathFind.cpp +++ b/source/game_sa/PathFind.cpp @@ -112,6 +112,8 @@ void CPathNode::InjectHooks() { // 0x44D080 void CPathFind::Init() { + ZoneScoped; + static int32 NumTempExternalNodes = 0; // Unused m_nNumForbiddenAreas = 0; m_loadAreaRequestPending = false; @@ -230,7 +232,7 @@ auto CPathFind::FindIntersection(const CNodeAddress& startNodeAddress, const CNo return nullptr; } - const auto& startNode = m_pPathNodes[startNodeAddress.m_wAreaId][startNodeAddress.m_wNodeId]; + const auto& startNode = *GetPathNode(startNodeAddress); const auto& nodeLinks = m_pNodeLinks[startNodeAddress.m_wAreaId]; for (auto i = 0u; i < startNode.m_nNumLinks; i++) { const auto linkedNodeIdx = startNode.m_wBaseLinkId + i; @@ -584,6 +586,7 @@ void CPathFind::SwitchRoadsOffInAreaForOneRegion(float xMin, float xMax, float y // NOTSA CPathNode* CPathFind::GetPathNode(CNodeAddress address) { assert(address.IsValid()); + assert(IsAreaNodesAvailable(address)); return &m_pPathNodes[address.m_wAreaId][address.m_wNodeId]; } @@ -723,6 +726,8 @@ CNodeAddress CPathFind::FindNodeClosestToCoors( // 0x450A60 void CPathFind::UpdateStreaming(bool bForceStreaming) { + ZoneScoped; + // The time thingy I think is some kind of `% 512`, not sure yet, will have to figure it out. if (!s_bLoadPathsNeeded && !bForceStreaming && (CTimer::m_snTimeInMilliseconds ^ CTimer::m_snPreviousTimeInMilliseconds) < 512) { return; @@ -873,6 +878,8 @@ CNodeAddress CPathFind::FindNearestExteriorNodeToInteriorNode(int32 interiorId) // 0x44E000 void CPathFind::AddDynamicLinkBetween2Nodes_For1Node(CNodeAddress first, CNodeAddress second) { + assert(IsAreaNodesAvailable(first)); + auto& firstPathInfo = m_pPathNodes[first.m_wAreaId][first.m_wNodeId]; auto numAddresses = m_anNumAddresses[first.m_wAreaId]; diff --git a/source/game_sa/PedGroup.cpp b/source/game_sa/PedGroup.cpp index 19824db33a..91853c352c 100644 --- a/source/game_sa/PedGroup.cpp +++ b/source/game_sa/PedGroup.cpp @@ -1,45 +1,76 @@ #include "StdInc.h" #include "PedGroup.h" +#include +#include -// 0x5FC150 -CPedGroup::CPedGroup() { - m_groupMembership.m_pPedGroup = this; - m_groupIntelligence.m_pPedGroup = this; - m_bIsMissionGroup = false; - m_pPed = nullptr; - m_bMembersEnterLeadersVehicle = true; -} - -// 0x5FC190 -CPedGroup::~CPedGroup() { - for (auto i = 0u; i < m_groupMembership.m_apMembers.size(); i++) { - m_groupMembership.RemoveMember(i); - } -} - +//! @returns Distance of the furthers member from the leader float CPedGroup::FindDistanceToFurthestMember() { return plugin::CallMethodAndReturn(this); + /* + const auto leader = GetMembership().GetLeader(); + for (const auto& mem : GetMembership().GetMembers(true)) { + + }*/ } +// 0x5FB0A0 float CPedGroup::FindDistanceToNearestMember(CPed** ppOutNearestMember) { - return plugin::CallMethodAndReturn(this, ppOutNearestMember); + const auto [nearest, distSq] = GetMembership().FindClosestFollowerToLeader(); + if (nearest) { + if (ppOutNearestMember) { + *ppOutNearestMember = nearest; + } + return std::sqrt(distSq); + } + return 1.0e10f; } -void CPedGroup::Flush() { - plugin::CallMethod<0x5FB790, CPedGroup*>(this); +// 0x5FACD0 +CPed* CPedGroup::GetClosestGroupPed(CPed* ped, float* pOutDistSq) { + const auto [closest, distSq] = GetMembership().GetMemberClosestTo(ped); + if (closest) { + if (pOutDistSq) { + *pOutDistSq = distSq; + } + } + return closest; } -CPed* CPedGroup::GetClosestGroupPed(CPed* ped, float* pOutDistance) { - return plugin::CallMethodAndReturn(this, ped, pOutDistance); +// 0x5FB790 +void CPedGroup::Flush() { + m_groupMembership.Flush(); + m_groupIntelligence.Flush(); + m_bIsMissionGroup = false; } +// 0x5F7DB0 bool CPedGroup::IsAnyoneUsingCar(const CVehicle* vehicle) { - return plugin::CallMethodAndReturn(this, vehicle); + assert(vehicle); + + for (auto& mem : m_groupMembership.GetMembers()) { + if (mem.GetVehicleIfInOne() == vehicle) { + return true; + } + // I did a slight change here. + // Game originally checked both EnterAsDriver and EnterAsPassenger tasks + // but that makes no sense, as the ped can't have both tasks at the same time (I hope) + // So the function below returns the target vehicle of the first task found + if (mem.GetIntelligence()->GetEnteringVehicle() == vehicle) { + return true; + } + } + return false; } -void CPedGroup::PlayerGaveCommand_Attack(CPed* playerPed, CPed* ped) { - plugin::CallMethod<0x5F7CC0, CPedGroup*, CPed*, CPed*>(this, playerPed, ped); +// 0x5F7CC0 +void CPedGroup::PlayerGaveCommand_Attack(CPed* playerPed, CPed* target) { + if (!m_groupIntelligence.AddEvent(CEventGroupEvent{ playerPed, new CEventPlayerCommandToGroup{PLAYER_GROUP_COMMAND_ATTACK, target} })) { + return; + } + if (target && target->m_nPedType != PED_TYPE_GANG2) { + target->Say(target->IsGangster() ? 147 : 148); + } } void CPedGroup::PlayerGaveCommand_Gather(CPed* ped) { @@ -47,17 +78,72 @@ void CPedGroup::PlayerGaveCommand_Gather(CPed* ped) { } void CPedGroup::Process() { - plugin::CallMethod<0x5FC7E0, CPedGroup*>(this); + m_groupMembership.Process(); + m_groupIntelligence.Process(); } void CPedGroup::RemoveAllFollowers() { - plugin::CallMethod<0x5FB7D0, CPedGroup*>(this); + GetMembership().RemoveAllFollowers(false); } -void CPedGroup::Teleport(const CVector* pos) { - plugin::CallMethod<0x5F7AD0, CPedGroup*, const CVector*>(this, pos); +void CPedGroup::Teleport(const CVector& pos) { + if (const auto leader = GetMembership().GetLeader()) { + leader->Teleport(pos, false); + } + + if (const auto oe = m_groupIntelligence.GetOldEvent()) { + if (oe->GetEventType() == EVENT_LEADER_ENTRY_EXIT) { + return; + } + } + + // Set *followers* out of the vehicle + for (auto& f : GetMembership().GetFollowers()) { + if (!f.IsAlive() || !f.bInVehicle || f.IsCreatedByMission()) { + continue; + } + CTaskSimpleCarSetPedOut{ + f.m_pVehicle, + (eTargetDoor)CCarEnterExit::ComputeTargetDoorToExit(f.m_pVehicle, &f), + false + }.ProcessPed(&f); + } + + // Teleport *followers* + const auto& offsets = CTaskComplexFollowLeaderInFormation::ms_offsets.offsets; + for (auto&& [offsetIdx, f] : notsa::enumerate(GetMembership().GetMembers(false))) { + if (!f.IsAlive()) { + continue; + } + f.Teleport(pos + CVector{ offsets[offsetIdx] }, false); + f.PositionAnyPedOutOfCollision(); + f.GetTaskManager().AbortFirstPrimaryTaskIn({ TASK_PRIMARY_PHYSICAL_RESPONSE, TASK_PRIMARY_EVENT_RESPONSE_TEMP, TASK_PRIMARY_EVENT_RESPONSE_NONTEMP }, &f); + } } int32 CPedGroup::GetId() const { return CPedGroups::GetGroupId(this); } + +bool CPedGroup::IsActive() const { + return CPedGroups::ms_activeGroups[GetId()]; +} + +void CPedGroup::InjectHooks() { + RH_ScopedClass(CPedGroup); + RH_ScopedCategory(); // TODO: Change this to the appropriate category! + + RH_ScopedInstall(Constructor, 0x5FC150); + RH_ScopedInstall(Destructor, 0x5FC190); + + RH_ScopedInstall(Teleport, 0x5F7AD0); + RH_ScopedInstall(PlayerGaveCommand_Gather, 0x5FAB60, {.reversed = false}); + RH_ScopedInstall(PlayerGaveCommand_Attack, 0x5F7CC0); + RH_ScopedInstall(IsAnyoneUsingCar, 0x5F7DB0); + RH_ScopedInstall(GetClosestGroupPed, 0x5FACD0); + RH_ScopedInstall(FindDistanceToFurthestMember, 0x5FB010, {.reversed = false}); + RH_ScopedInstall(FindDistanceToNearestMember, 0x5FB0A0); + RH_ScopedInstall(Flush, 0x5FB790); + RH_ScopedInstall(Process, 0x5FC7E0); + RH_ScopedInstall(RemoveAllFollowers, 0x5FB7D0); +} diff --git a/source/game_sa/PedGroup.h b/source/game_sa/PedGroup.h index bbe5f8db84..dd552e465a 100644 --- a/source/game_sa/PedGroup.h +++ b/source/game_sa/PedGroup.h @@ -15,35 +15,67 @@ class CVehicle; class CPedGroup { public: - CPed* m_pPed; - bool m_bMembersEnterLeadersVehicle; - CPedGroupMembership m_groupMembership; - CPedGroupIntelligence m_groupIntelligence; - bool m_bIsMissionGroup; - char field_2D1[3]; + static void InjectHooks(); -public: - CPedGroup(); - ~CPedGroup(); + CPedGroup() = default; + ~CPedGroup() = default; - float FindDistanceToFurthestMember(); + //! Find follower closest to the leader float FindDistanceToNearestMember(CPed** ppOutNearestMember); - void Flush(); + + //! Clear state + void Flush(); + + //! Find member closest to `ped` CPed* GetClosestGroupPed(CPed* ped, float* pOutDistance); - bool IsAnyoneUsingCar(const CVehicle* vehicle); - void PlayerGaveCommand_Attack(CPed* playerPed, CPed* ped); - void PlayerGaveCommand_Gather(CPed* ped); - void Process(); - void RemoveAllFollowers(); - void Teleport(const CVector* pos); - // NOTSA - inline CPedGroupIntelligence& GetIntelligence() { return m_groupIntelligence; } + //! Find distance of the furthest member to `ped` + float FindDistanceToFurthestMember(); + + //! Is anyone from this group using the given car + bool IsAnyoneUsingCar(const CVehicle* vehicle); + + //! todo + void PlayerGaveCommand_Attack(CPed* playerPed, CPed* ped); + + //! todo + void PlayerGaveCommand_Gather(CPed* ped); + //! Update routine + void Process(); + + //! Remove all followers of the group [That is, all members, excluding the leader] + void RemoveAllFollowers(); + + //! Teleport the whole group [incl. leader] to a position + void Teleport(const CVector& pos); + + //! Get id of this group int32 GetId() const; + bool IsActive() const; - inline auto& GetMembership() const { return m_groupMembership; } - inline auto& GetMembership() { return m_groupMembership; } -}; + auto& GetIntelligence() { return m_groupIntelligence; } + auto& GetMembership() const { return m_groupMembership; } + auto& GetMembership() { return m_groupMembership; } +private: // Wrappers for hooks + // 0x5FC150 + CPedGroup* Constructor() { + this->CPedGroup::CPedGroup(); + return this; + } + + // 0x5FC190 + CPedGroup* Destructor() { + this->CPedGroup::~CPedGroup(); + return this; + } + +public: + CPed* m_pPed{}; + bool m_bMembersEnterLeadersVehicle{true}; + CPedGroupMembership m_groupMembership{*this}; + CPedGroupIntelligence m_groupIntelligence{this}; + bool m_bIsMissionGroup{}; +}; VALIDATE_SIZE(CPedGroup, 0x2D4); diff --git a/source/game_sa/PedGroupIntelligence.cpp b/source/game_sa/PedGroupIntelligence.cpp index ce55730ebe..93c07adce0 100644 --- a/source/game_sa/PedGroupIntelligence.cpp +++ b/source/game_sa/PedGroupIntelligence.cpp @@ -6,30 +6,53 @@ void CPedGroupIntelligence::InjectHooks() { RH_ScopedClass(CPedGroupIntelligence); RH_ScopedCategoryGlobal(); - //RH_ScopedInstall(Constructor, 0x5F7250, { .reversed = false }); - //RH_ScopedInstall(Destructor, 0x5F7350, { .reversed = false }); + RH_ScopedInstall(Constructor, 0x5F7250, { .reversed = false }); - RH_ScopedOverloadedInstall(AddEvent, "", 0x5F7470, bool(CPedGroupIntelligence::*)(CEvent*), { .reversed = false }); - RH_ScopedInstall(SetScriptCommandTask, 0x5F8560, { .reversed = false }); - RH_ScopedInstall(GetTaskMain, 0x5F85A0, { .reversed = false }); + RH_ScopedGlobalInstall(FlushTasks, 0x5F79C0, { .reversed = false }); + //RH_ScopedGlobalInstall(ReportFinishedTask_, 0x5F76C0, { .reversed = false }); + + RH_ScopedInstall(SetTask, 0x5F7540); + RH_ScopedInstall(Flush, 0x5F7350); + RH_ScopedInstall(SetDefaultTaskAllocatorType, 0x5FBB70, { .reversed = false }); + RH_ScopedInstall(SetDefaultTaskAllocator, 0x5FB280, { .reversed = false }); RH_ScopedInstall(ComputeDefaultTasks, 0x5F88D0, { .reversed = false }); + RH_ScopedInstall(ProcessIgnorePlayerGroup, 0x5F87A0, { .reversed = false }); + RH_ScopedInstall(ReportAllBarScriptTasksFinished, 0x5F8780, { .reversed = false }); + //RH_ScopedInstall(ReportFinishedTask, 0x5F86F0, { .reversed = false }); + RH_ScopedInstall(GetTaskDefault, 0x5F86C0, { .reversed = false }); RH_ScopedInstall(GetTaskScriptCommand, 0x5F8690, { .reversed = false }); - RH_ScopedInstall(GetTaskSecondary, 0x5F8620, { .reversed = false }); RH_ScopedInstall(GetTaskSecondarySlot, 0x5F8650, { .reversed = false }); - RH_ScopedInstall(SetGroupDecisionMakerType, 0x5F7340, { .reversed = false }); + RH_ScopedInstall(GetTaskSecondary, 0x5F8620, { .reversed = false }); + RH_ScopedInstall(GetTaskMain, 0x5F85A0); + RH_ScopedInstall(SetScriptCommandTask, 0x5F8560, { .reversed = false }); + RH_ScopedInstall(IsCurrentEventValid, 0x5F77A0, { .reversed = false }); + RH_ScopedInstall(IsGroupResponding, 0x5F7760, { .reversed = false }); + //RH_ScopedInstall(AddEvent, 0x5F7470, { .reversed = false }); + //RH_ScopedInstall(SetEventResponseTask, 0x5F8510); // Register allocation is weird, better not to hook it at all + RH_ScopedInstall(SetEventResponseTaskAllocator, 0x5F7440, { .reversed = false }); RH_ScopedInstall(SetPrimaryTaskAllocator, 0x5F7410, { .reversed = false }); - RH_ScopedInstall(SetDefaultTaskAllocatorType, 0x5FBB70, { .reversed = false }); + RH_ScopedInstall(SetGroupDecisionMakerType, 0x5F7340, { .reversed = false }); + RH_ScopedInstall(ComputeEventResponseTasks, 0x5FC440, { .reversed = false }); + RH_ScopedInstall(Process, 0x5FC4A0, { .reversed = false }); + RH_ScopedInstall(ReportAllTasksFinished, 0x5F7730); RH_ScopedOverloadedInstall(ReportFinishedTask, "", 0x5F86F0, bool(CPedGroupIntelligence::*)(const CPed*, const CTask*), { .reversed = false }); } // 0x5F7250 -CPedGroupIntelligence::CPedGroupIntelligence() { +CPedGroupIntelligence::CPedGroupIntelligence(CPedGroup* owner) : // TODO: Use `CPedGroup&` + m_pPedGroup{owner} +{ plugin::CallMethod<0x5F7250, CPedGroupIntelligence*>(this); } -// 0x5F7350 +// Unknown address (If any) CPedGroupIntelligence::~CPedGroupIntelligence() { + Flush(); // Not sure if it does this at all, but it worked so far, so let's leave it like this for now +} + +// 0x5F7350 +void CPedGroupIntelligence::Flush() { // Pirulax: For some reason this is called `~CPedGroupIntelligence` in *there*... plugin::CallMethod<0x5F7350, CPedGroupIntelligence*>(this); } @@ -43,43 +66,50 @@ void CPedGroupIntelligence::SetScriptCommandTask(CPed* ped, const CTask* task) { plugin::CallMethod<0x5F8560, CPedGroupIntelligence*, CPed*, const CTask*>(this, ped, task); } -CTask* CPedGroupIntelligence::GetTask(CPed* ped, const CPedTaskPair* taskPair) { - const CPedTaskPair* tp; - auto index = 0; - while (true) { - auto taskPed = taskPair[index].m_pPed; - tp = &taskPair[index++]; - if (taskPed == ped) - break; - if (index == 8) - return nullptr; +// notsa +CPedTaskPair* CPedGroupIntelligence::GetPedsTaskPair(CPed* ped, PedTaskPairs& taskPairs) const { + for (auto& tp : taskPairs) { + if (tp.m_Ped == ped) { + return &tp; + } + } + return nullptr; +} + +// notsa (not sure) +CTask* CPedGroupIntelligence::GetTask(CPed* ped, PedTaskPairs& taskPairs) { + if (const auto tp = GetPedsTaskPair(ped, taskPairs)) { + return tp->m_Task; } - return tp->m_pTask; + return nullptr; } // 0x5F85A0 CTask* CPedGroupIntelligence::GetTaskMain(CPed* ped) { - CTask* task = GetTask(ped, &m_groupTasks[16]); - if (!task) { - task = GetTask(ped, &m_groupTasks[0]); - if (!task) - return GetTask(ped, &m_groupTasks[24]); + if (const auto t = GetTaskScriptCommand(ped)) { + return t; + } + if (const auto t = GetTask(ped, m_PedTaskPairs)) { + return t; + } + if (const auto t = GetTaskDefault(ped)) { + return t; } - return task; + return nullptr; } CTask* CPedGroupIntelligence::GetTaskDefault(CPed* ped) { - return GetTask(ped, &m_groupTasks[24]); + return GetTask(ped, m_DefaultPedTaskPairs); } // 0x5F8690 CTask* CPedGroupIntelligence::GetTaskScriptCommand(CPed* ped) { - return GetTask(ped, &m_groupTasks[16]); + return GetTask(ped, m_ScriptCommandPedTaskPairs); } // 0x5F8620 CTask* CPedGroupIntelligence::GetTaskSecondary(CPed* ped) { - return GetTask(ped, &m_groupTasks[8]); + return GetTask(ped, m_SecondaryPedTaskPairs); } // 0x5F8650 @@ -87,14 +117,8 @@ eSecondaryTask CPedGroupIntelligence::GetTaskSecondarySlot(CPed* ped) { return plugin::CallMethodAndReturn(this, ped); } -// 0x5F88D0 -void CPedGroupIntelligence::ComputeDefaultTasks(CPed* ped) { - plugin::CallMethod<0x5F88D0, CPedGroupIntelligence*, CPed*>(this, ped); -} - -// 0x5F7340 -int32 CPedGroupIntelligence::SetGroupDecisionMakerType(int32 a2) { - return plugin::CallMethodAndReturn(this, a2); +void CPedGroupIntelligence::Process() { + plugin::CallMethod<0x5FC4A0>(this); } // 0x5F7410 @@ -107,12 +131,93 @@ void CPedGroupIntelligence::SetDefaultTaskAllocatorType(ePedGroupDefaultTaskAllo plugin::CallMethod<0x5FBB70, CPedGroupIntelligence*, ePedGroupDefaultTaskAllocatorType>(this, nPedGroupTaskAllocator); } +// 0x5F8510 +void CPedGroupIntelligence::SetEventResponseTask(CPed* ped, bool hasMainTask, const CTask& mainTask, bool hasSecondaryTask, const CTask& secondaryTask, int32 slot) { + if (hasMainTask) { + SetTask(ped, mainTask, m_PedTaskPairs); + } + if (hasSecondaryTask) { + SetTask(ped, secondaryTask, m_SecondaryPedTaskPairs, slot, false); + } +} + // 0x5F86F0 bool CPedGroupIntelligence::ReportFinishedTask(const CPed* ped, const CTask* task) { return plugin::CallMethodAndReturn(this, ped, task); } // 0x5F7540 -void CPedGroupIntelligence::SetTask(CPed* ped, const CTask* task, CPedTaskPair* pair, int32 arg5, bool arg6) { - plugin::Call<0x5F7540, CPed*, const CTask*, CPedTaskPair*, int32, bool>(ped, task, pair, arg5, arg6); +void CPedGroupIntelligence::SetTask(CPed* ped, const CTask& task, PedTaskPairs& taskPairs, int32 slot, bool force) { + assert(!GetTaskPool()->IsObjectValid(&task)); // Shouldn't be `new`'d [Keep in mind that there might be false positives] + + const auto tp = GetPedsTaskPair(ped, taskPairs); + if (!tp) { + return; + } + if (tp->m_Task && (force || tp->m_Task->GetTaskType() != const_cast(task).GetTaskType())) { + delete std::exchange(tp->m_Task, const_cast(task).Clone()); + tp->m_Slot = slot; + } else if (!tp->m_Task) { + tp->m_Task = const_cast(task).Clone(); + tp->m_Slot = slot; + } +} + +// 0x5F79C0 +void CPedGroupIntelligence::FlushTasks(PedTaskPairs& taskPairs, CPed* ped) { + return plugin::Call<0x5F79C0, PedTaskPairs&, CPed*>(taskPairs, ped); +} + +// 0x5FB280 +void CPedGroupIntelligence::SetDefaultTaskAllocator(CPedGroupDefaultTaskAllocator const* a2) { + return plugin::CallMethod<0x5FB280, CPedGroupIntelligence*, CPedGroupDefaultTaskAllocator const*>(this, a2); +} + +// 0x5F88D0 +void CPedGroupIntelligence::ComputeDefaultTasks(CPed* ped) { + return plugin::CallMethod<0x5F88D0, CPedGroupIntelligence*, CPed *>(this, ped); +} + +// 0x5F87A0 +void CPedGroupIntelligence::ProcessIgnorePlayerGroup() { + return plugin::CallMethod<0x5F87A0, CPedGroupIntelligence*>(this); +} + +// 0x5F8780 +void CPedGroupIntelligence::ReportAllBarScriptTasksFinished() { + ReportAllTasksFinished(m_PedTaskPairs); + ReportAllTasksFinished(m_SecondaryPedTaskPairs); +} + +// 0x5F7730 +void CPedGroupIntelligence::ReportAllTasksFinished(PedTaskPairs& taskPairs) { + for (auto& tp : taskPairs) { + delete tp.m_Task; + tp.m_Task = nullptr; + } +} + +// 0x5F77A0 +bool CPedGroupIntelligence::IsCurrentEventValid() { + return plugin::CallMethodAndReturn(this); +} + +// 0x5F7760 +bool CPedGroupIntelligence::IsGroupResponding() { + return plugin::CallMethodAndReturn(this); +} + +// 0x5F7440 +void CPedGroupIntelligence::SetEventResponseTaskAllocator(CTaskAllocator* a) { + return plugin::CallMethod<0x5F7440, CPedGroupIntelligence*, CTaskAllocator*>(this, a); +} + +// 0x5F7340 +void CPedGroupIntelligence::SetGroupDecisionMakerType(int32 t) { + return plugin::CallMethod<0x5F7340, CPedGroupIntelligence*, int32>(this, t); +} + +// 0x5FC440 +CTaskAllocator* CPedGroupIntelligence::ComputeEventResponseTasks() { + return plugin::CallMethodAndReturn(this); } diff --git a/source/game_sa/PedGroupIntelligence.h b/source/game_sa/PedGroupIntelligence.h index e30e2a6916..346729b1ce 100644 --- a/source/game_sa/PedGroupIntelligence.h +++ b/source/game_sa/PedGroupIntelligence.h @@ -10,6 +10,8 @@ #include "PedTaskPair.h" +#include "Tasks/TaskTypes/TaskSimpleNone.h" + class CPed; class CTask; class CGroupEventHandler; @@ -29,71 +31,93 @@ enum class ePedGroupDefaultTaskAllocatorType : uint32 { }; class CPedGroupIntelligence { -public: - CPedGroup* m_pPedGroup; - CEventGroupEvent* m_pOldEventGroupEvent; - CEventGroupEvent* m_pEventGroupEvent; - CPedTaskPair m_groupTasks[32]; // todo: split array - /* TODO: Split above to below: - * CPedTaskPair m_pedTaskPairs[8]; - * CPedTaskPair m_secondaryPedTaskPairs[8]; - * CPedTaskPair m_scriptCommandPedTaskPairs[8]; - * CPedTaskPair m_defaultPedTaskPairs[8]; - */ - CPedGroupDefaultTaskAllocator* m_pPedGroupDefaultTaskAllocator; - CTaskAllocator* m_pPrimaryTaskAllocator; - CTaskAllocator* m_pEventResponseTaskAllocator; - int32 m_nDecisionMakerType; - int32 m_nTaskSequenceId; // Used in CTaskSequences::ms_taskSequence - + using PedTaskPairs = std::array; public: static void InjectHooks(); - CPedGroupIntelligence(); + CPedGroupIntelligence(CPedGroup* owner); ~CPedGroupIntelligence(); - bool AddEvent(CEvent* event); - void ComputeDefaultTasks(CPed* ped); - void* ComputeEventResponseTasks(); - void ComputeScriptCommandTasks(); - static void FlushTasks(CPedTaskPair* taskpair, CPed* ped); + void Flush(); - CTask* GetTask(CPed* ped, CPedTaskPair const* taskPair); + bool AddEvent(CEvent* event); + void ComputeDefaultTasks(CPed* ped); + CTaskAllocator* ComputeEventResponseTasks(); + void ComputeScriptCommandTasks(); + static void FlushTasks(PedTaskPairs& taskPairs, CPed* ped); + + //! @notsa + CPedTaskPair* GetPedsTaskPair(CPed* ped, PedTaskPairs& taskPairs) const; + + CTask* GetTask(CPed* ped, PedTaskPairs& taskPairs); CTask* GetTaskMain(CPed* ped); CTask* GetTaskDefault(CPed* ped); CTask* GetTaskScriptCommand(CPed* ped); CTask* GetTaskSecondary(CPed* ped); eSecondaryTask GetTaskSecondarySlot(CPed* ped); - bool IsCurrentEventValid(); - bool IsGroupResponding(); - void Process(); - void ProcessIgnorePlayerGroup(); - void ReportAllBarScriptTasksFinished(); - void ReportAllTasksFinished(CPedTaskPair* taskpair); - void ReportAllTasksFinished(); - bool ReportFinishedTask(const CPed* ped, const CTask* task, CPedTaskPair* taskpair); - bool ReportFinishedTask(const CPed* ped, const CTask* task); - void SetDefaultTask(CPed* ped, const CTask* task); - void SetDefaultTaskAllocator(CPedGroupDefaultTaskAllocator const* PedGroupDefaultTaskAllocator); - //! see ePedGroupDefaultTaskAllocatorType + bool IsCurrentEventValid(); + bool IsGroupResponding(); + void Process(); + void ProcessIgnorePlayerGroup(); + void ReportAllBarScriptTasksFinished(); + void ReportAllTasksFinished(PedTaskPairs& taskPairs); + //void ReportAllTasksFinished(); + bool ReportFinishedTask(const CPed* ped, const CTask* task, CPedTaskPair* taskpair); + bool ReportFinishedTask(const CPed* ped, const CTask* task); + void SetDefaultTask(CPed* ped, const CTask* task); + void SetDefaultTaskAllocator(CPedGroupDefaultTaskAllocator const* PedGroupDefaultTaskAllocator); void SetDefaultTaskAllocatorType(ePedGroupDefaultTaskAllocatorType nPedGroupTaskAllocator); - //! arg3 always true - //! arg5 always false - //! arg7 always -1 - void SetEventResponseTask(CPed* ped, bool arg3, const CTask* task1, bool arg5, const CTask* task2, int32 arg7); - int32 SetEventResponseTaskAllocator(int32 a2); - int32 SetGroupDecisionMakerType(int32 a2); - void SetPrimaryTaskAllocator(CTaskAllocator* taskAllocator); - void SetScriptCommandTask(CPed* ped, const CTask* task); - - /// Helper so events can be directly passed in without having to it into a variable + + /*! + * @addr 0x5F8510 + * @note The tasks passed to this function **shouldn't** be `new`-d, but rather stack allocated! Also, it's often inlined into an unused `CTaskSimpleNone` and `SetTask` call. + */ + void SetEventResponseTask( + CPed* ped, + bool hasMainTask, + const CTask& mainTask, + bool hasSecondaryTask = false, + const CTask& secondaryTask = CTaskSimpleNone{}, + int32 slot = -1 + ); + //! @notsa + void SetEventResponseTask(CPed* ped, const CTask& task) { SetEventResponseTask(ped, true, task); } + void SetEventResponseTaskAllocator(CTaskAllocator* a); + void SetGroupDecisionMakerType(int32 t); + void SetPrimaryTaskAllocator(CTaskAllocator* taskAllocator); + void SetScriptCommandTask(CPed* ped, const CTask* task); + + auto GetOldEvent() { return m_pOldEventGroupEvent; } + auto GetCurrentEvent() { return m_pEventGroupEvent; } + template T> - auto AddEvent(T event) { + auto AddEvent(T event) { // TODO: Remove in final return AddEvent(&event); } - static void SetTask(CPed* ped, const CTask* task, CPedTaskPair* pair, int32 arg5 = -1, bool arg6 = false); -}; + //! `task` shouldn't be `new`-d, but rather stack allocated! + void SetTask(CPed* ped, const CTask& task, PedTaskPairs& taskPairs, int32 slot = -1, bool force = false); +private: // Wrappers for hooks + // 0x5F7250 + CPedGroupIntelligence* Constructor() { + this->CPedGroupIntelligence::CPedGroupIntelligence(nullptr); + return this; + } + +private: + CPedGroup* m_pPedGroup{}; + CEventGroupEvent* m_pOldEventGroupEvent{}; + CEventGroupEvent* m_pEventGroupEvent{}; + PedTaskPairs m_PedTaskPairs{}; + PedTaskPairs m_SecondaryPedTaskPairs{}; + PedTaskPairs m_ScriptCommandPedTaskPairs{}; + PedTaskPairs m_DefaultPedTaskPairs{}; + CPedGroupDefaultTaskAllocator* m_DefaultTaskAllocator{}; + CTaskAllocator* m_PrimaryTaskAllocator{}; + CTaskAllocator* m_EventResponseTaskAllocator{}; + int32 m_DecisionMakerType{-1}; + int32 m_TaskSeqId{-1}; // Used in CTaskSequences::ms_taskSequence +}; VALIDATE_SIZE(CPedGroupIntelligence, 0x2A0); diff --git a/source/game_sa/PedGroupMembership.cpp b/source/game_sa/PedGroupMembership.cpp index 9a28bf8cea..ae9b5d3003 100644 --- a/source/game_sa/PedGroupMembership.cpp +++ b/source/game_sa/PedGroupMembership.cpp @@ -3,124 +3,320 @@ #include #include "PedGroupMembership.h" -// 0x5F6930 -CPedGroupMembership::CPedGroupMembership() { - std::ranges::fill(m_apMembers, nullptr); - m_fSeparationRange = 60.0f; -} - -// 0x5FB140 -CPedGroupMembership::CPedGroupMembership(const CPedGroupMembership& obj) { - From(obj); +CPedGroupMembership::CPedGroupMembership(CPedGroup& group) : + m_group{&group} +{ } CPedGroupMembership::~CPedGroupMembership() { Flush(); } -// 0x5F7FE0 -void CPedGroupMembership::From(const CPedGroupMembership& obj) { - for (auto i = 0; i < TOTAL_PED_GROUP_MEMBERS; i++) { - AddMember(obj.m_apMembers[i], i); +// 0x5F8020 +void CPedGroupMembership::AddFollower(CPed* ped) { + ped->bHasGroupDriveTask = false; + + // Peds in the player's group can't drown + if (const auto leader = GetLeader()) { + if (leader->IsPlayer()) { // same effect as m_pPlayerData, no? + assert(leader->m_pPlayerData); // Test above theory + ped->bDrownsInWater = false; + } } - m_pPedGroup = obj.m_pPedGroup; - m_fSeparationRange = obj.m_fSeparationRange; -} -void CPedGroupMembership::AddFollower(CPed* ped) { - plugin::CallMethod<0x5F8020, CPedGroupMembership*, CPed*>(this, ped); + if (IsFollower(ped) || GetLeader() == ped) { // TODO/BUG/NOTE: Stuff that was set above is now not reverted... I'm not sure if that's intended + return; + } + + const auto memId = FindIdForNewMember(); + assert(memId != -1); + if (memId == -1) { + return; + } + + AddMember(ped, memId); + GivePedRandomObjectToHold(ped); } // 0x5F6AE0 -void CPedGroupMembership::AddMember(CPed* member, int32 memberID) { - plugin::CallMethod<0x5F6AE0, CPedGroupMembership*, CPed*, int32>(this, member, memberID); +void CPedGroupMembership::AddMember(CPed* member, int32 memIdx) { + assert(member); + assert(!m_members[memIdx]); + + m_members[memIdx] = member; + CEntity::RegisterReference(m_members[memIdx]); + /* dead code before checking if the member is in the player's group */ + if (!member->IsPlayer()) { + member->GetIntelligence()->SetPedDecisionMakerType(DM_EVENT_DRAGGED_OUT_CAR); + } } +// 0x5FB240 void CPedGroupMembership::AppointNewLeader() { - plugin::CallMethod<0x5FB240, CPedGroupMembership*>(this); + if (HasLeader()) { + return; + } + + const auto memId = FindNewLeaderToAppoint(); + if (memId == -1) { + return; + } + + const auto leader = m_members[memId]; + RemoveMember(memId); // Must call as it does some cleanup + AddMember(leader, LEADER_MEM_ID); } // 0x5F6A50 size_t CPedGroupMembership::CountMembers() { - return rng::count_if(m_apMembers, notsa::NotIsNull{}); + return rng::count_if(m_members, notsa::NotIsNull{}); } // 0x5F6AA0 int32 CPedGroupMembership::CountMembersExcludingLeader() { - // Last member is the leader - return rng::count_if(m_apMembers | rng::views::drop(1), notsa::NotIsNull{}); + const auto cnt = CountMembers(); + return HasLeader() + ? cnt - 1 + : cnt; } // 0x5FB160 void CPedGroupMembership::Flush() { - for (auto i = 0u; i < m_apMembers.size(); i++) { - RemoveMember(i); + for (auto&& [i, mem] : notsa::enumerate(m_members)) { + if (mem) { + RemoveMember(i); + } } } -CPed* CPedGroupMembership::GetLeader() { - return m_apMembers[7]; +CPed* CPedGroupMembership::GetLeader() const { + return m_members[LEADER_MEM_ID]; } // 0x5F69B0 CPed* CPedGroupMembership::GetMember(int32 memberId) { - return m_apMembers[memberId]; + return m_members[memberId]; +} + +int32 CPedGroupMembership::GetMemberId(const CPed* ped) const { + for (auto&& [id, mem] : notsa::enumerate(m_members)) { + if (mem == ped) { + return id; + } + } + return -1; } // 0x5F69E0 bool CPedGroupMembership::IsFollower(const CPed* ped) const { - return plugin::CallMethodAndReturn(this, ped); + return !IsLeader(ped) && IsMember(ped); } // 0x5F69C0 -bool CPedGroupMembership::IsLeader(const CPed* ped) { +bool CPedGroupMembership::IsLeader(const CPed* ped) const { return ped && GetLeader() == ped; } // 0x5F6A10 -bool CPedGroupMembership::IsMember(const CPed* ped) { - return plugin::CallMethodAndReturn(this, ped); +bool CPedGroupMembership::IsMember(const CPed* ped) const { + return ped && notsa::contains(m_members, ped); } // 0x5FBA60 void CPedGroupMembership::Process() { - plugin::CallMethod<0x5FBA60, CPedGroupMembership*>(this); + // Remove dead members (except player ped) + for (auto&& [i, mem] : notsa::enumerate(m_members)) { + if (!mem || mem->IsAlive()) { + continue; + } + if (IsLeader(mem) && mem->IsPlayer()) { // Player always stays in it's group + continue; + } + RemoveMember(i); + } + + // If no leader, try appointing a new one + if (!HasLeader()) { + AppointNewLeader(); + if (!HasLeader()) { // Couldn't appoint a new leader + return; + } + } + + // Now that we have a leader, check for separation distance and remove members further than it + const auto& leaderPos = GetLeader()->GetPosition(); + for (auto&& [i, mem] : notsa::enumerate(m_members)) { + if (!mem || mem->bNeverLeavesGroup) { + continue; + } + if (IsLeader(mem)) { // Leader's distance to itself always 0, thus ignore + continue; + } + if (sq(m_separationRange) >= (leaderPos - mem->GetPosition()).SquaredMagnitude()) { + continue; + } + RemoveMember(i); // Ped is too far + } } // 0x5FB190 -void CPedGroupMembership::RemoveAllFollowers(bool bCreatedByGameOnly) { - plugin::CallMethod<0x5FB190, CPedGroupMembership*, bool>(this, bCreatedByGameOnly); +void CPedGroupMembership::RemoveAllFollowers(bool bCreatedByMissionOnly) { + for (auto&& [i, mem] : notsa::enumerate(m_members)) { + if (IsLeader(mem)) { // Leader isn't a follower + continue; + } + if (bCreatedByMissionOnly && mem->IsCreatedBy(PED_MISSION)) { + continue; + } + RemoveMember(i); + } } -// 0x5F80D0 -void CPedGroupMembership::RemoveMember(int32 memberID) { - plugin::CallMethod<0x5F80D0, CPedGroupMembership*, int32>(this, memberID); +// 0x5FB1D0 +void CPedGroupMembership::RemoveNFollowers(size_t count) { + if (count == 0) { // Nothing to do + return; + } + for (auto&& [i, mem] : notsa::enumerate(m_members)) { + if (IsLeader(mem)) { // Leader isn't a follower + continue; + } + if (mem->IsCreatedBy(PED_MISSION)) { + continue; + } + RemoveMember(i); + if (!--count) { + break; + } + } } -// 0x5FB210 -void CPedGroupMembership::RemoveMember(CPed& ped) { - plugin::CallMethod<0x5FB210>(this, &ped); +// 0x5F80D0 +void CPedGroupMembership::RemoveMember(int32 memIdx) { + const auto mem = m_members[memIdx]; + assert(mem); + + CEntity::ClearReference(m_members[memIdx]); // Does `m_apMembers[memIdx] = nullptr` + + if (mem->IsPlayer()) { + return; + } + + if (mem->bClearRadarBlipOnDeath) { + CRadar::ClearBlipForEntity(mem); + mem->bClearRadarBlipOnDeath = false; + } + + mem->GetIntelligence()->RestorePedDecisionMakerType(); + + if (const auto leader = GetLeader()) { + if (const auto plyrdat = leader->m_pPlayerData) { + mem->bDrownsInWater = true; + } + } } -// 0x5FB1D0 -char CPedGroupMembership::RemoveNFollowers(int32 count) { - return plugin::CallMethodAndReturn(this, count); +// 0x5FB210 +void CPedGroupMembership::RemoveMember(CPed* ped) { + if (const auto id = GetMemberId(ped); id != -1) { + RemoveMember(id); + } } // 0x5FB9C0 void CPedGroupMembership::SetLeader(CPed* ped) { - plugin::CallMethod<0x5FB9C0, CPedGroupMembership*, CPed*>(this, ped); + assert(ped); + + // Remove member (Just in case he's in the group already) + RemoveMember(ped); + + // Remov the current leader (If any) + if (GetLeader()) { + RemoveMember(GetLeader()); + } + + // Now add member as the leader + AddMember(ped, LEADER_MEM_ID); + GivePedRandomObjectToHold(ped); +} + +// NOTSA +auto CPedGroupMembership::FindClosestFollowerToLeader() -> FindClosestMemberResult { + if (const auto leader = GetLeader()) { + return GetMemberClosestTo(leader); + } + return { nullptr, 0.f }; // We return 0.f here, `GetMemberClosestTo` returns FLT_MAX, but it should be ignored anyways, because the CPed* is nullptr } // 0x5F6950 -int32 CPedGroupMembership::GetObjectForPedToHold() { - return plugin::CallAndReturn(); +eModelID CPedGroupMembership::GetObjectForPedToHold() { + using namespace ModelIndices; + return CGeneral::RandomChoiceFromList({ (eModelID)MI_GANG_SMOKE, MODEL_INVALID, (eModelID)MI_GANG_DRINK }); // Each has 33% chance +} + +// NOTSA +int32 CPedGroupMembership::FindNewLeaderToAppoint() const { + for (auto&& [i, mem] : notsa::enumerate(m_members)) { + if (mem) { + return i; + } + } + return -1; +} + +// NOTSA +int32 CPedGroupMembership::FindIdForNewMember() const { + for (auto&& [i, mem] : notsa::enumerate(m_members)) { + if (!mem) { + return i; + } + } + return -1; } +// Based on code from 0x5F80BE +void CPedGroupMembership::GivePedRandomObjectToHold(CPed* mem, bool onlyIfUnarmed) const { + if (m_group->m_bIsMissionGroup) { + return; + } + if (onlyIfUnarmed && !mem->IsCurrentlyUnarmed()) { + return; + } + if (const auto modelId = GetObjectForPedToHold(); modelId != MODEL_INVALID) { + mem->GiveObjectToPedToHold(modelId, true); + } +} + +// NOTSA bool CPedGroupMembership::CanAddFollower() { - return std::size(m_apMembers) > CountMembers(); + return std::size(m_members) - 1 >= CountMembers(); // - 1 to compensate for leader } +// NOTSA CPed* CPedGroupMembership::GetRandom() { - return CGeneral::RandomChoice(m_apMembers | rng::views::take(CountMembers())); + return CGeneral::RandomChoice(m_members | rng::views::take(CountMembers())); +} + +void CPedGroupMembership::InjectHooks() { + RH_ScopedClass(CPedGroupMembership); + RH_ScopedCategoryGlobal(); + + RH_ScopedInstall(GetObjectForPedToHold, 0x5F6950); + RH_ScopedInstall(From, 0x5F7FE0); + RH_ScopedInstall(CountMembersExcludingLeader, 0x5F6AA0); + RH_ScopedInstall(CountMembers, 0x5F6A50); + RH_ScopedInstall(IsMember, 0x5F6A10); + RH_ScopedInstall(IsFollower, 0x5F69E0); + RH_ScopedInstall(IsLeader, 0x5F69C0); + RH_ScopedInstall(GetMember, 0x5F69B0); + RH_ScopedInstall(AddMember, 0x5F6AE0); + RH_ScopedInstall(AddFollower, 0x5F8020); + RH_ScopedInstall(GetLeader, 0x5F69A0); + RH_ScopedInstall(SetLeader, 0x5FB9C0); + RH_ScopedInstall(AppointNewLeader, 0x5FB240); + RH_ScopedInstall(RemoveNFollowers, 0x5FB1D0); + RH_ScopedInstall(RemoveAllFollowers, 0x5FB190); + RH_ScopedOverloadedInstall(RemoveMember, "ByPed", 0x5FB210, void(CPedGroupMembership::*)(CPed*)); + RH_ScopedOverloadedInstall(RemoveMember, "ByMemIdx", 0x5F80D0, void(CPedGroupMembership::*)(int32)); + RH_ScopedInstall(Process, 0x5FBA60); } diff --git a/source/game_sa/PedGroupMembership.h b/source/game_sa/PedGroupMembership.h index 0445f62544..3743cac629 100644 --- a/source/game_sa/PedGroupMembership.h +++ b/source/game_sa/PedGroupMembership.h @@ -18,51 +18,96 @@ const int32 TOTAL_PED_GROUP_FOLLOWERS = TOTAL_PED_GROUP_MEMBERS - 1; class CPedGroupMembership { public: - CPedGroup* m_pPedGroup; - std::array m_apMembers; // m_apMembers[7] is the leader - float m_fSeparationRange; - static const float& ms_fMaxSeparation; - static const float& ms_fPlayerGroupMaxSeparation; + static constexpr int32 LEADER_MEM_ID = 7; ///< Leader's member ID + //static const float& ms_fMaxSeparation; + //static const float& ms_fPlayerGroupMaxSeparation; public: - CPedGroupMembership(); - CPedGroupMembership(const CPedGroupMembership& from); + static void InjectHooks(); + + CPedGroupMembership(CPedGroup& group); + CPedGroupMembership(const CPedGroupMembership&) = default; // 0x5FB140 ~CPedGroupMembership(); - void AddFollower(CPed* ped); - void AddMember(CPed* member, int32 memberID); - void AppointNewLeader(); + //! R*'s implementation of `operator=` xD [0x5F7FE0] + void From(const CPedGroupMembership& obj) { *this = obj; } + + //! Add a new follower to the group. If there's no more memory, they aren't added + void AddFollower(CPed* ped); + + //! Set a new leader [Should be called only if there's no leader] + void AppointNewLeader(); + + //! Count of members size_t CountMembers(); + + //! Count of followers int32 CountMembersExcludingLeader(); - void Flush(); - void From(const CPedGroupMembership& obj); - CPed* GetLeader(); + + //! Flush the toilet + void Flush(); + + //! Update the states + void Process(); + + //! If there isn't one null is returned. + CPed* GetLeader() const; + + //! Set a new leader + void SetLeader(CPed* ped); + + //! Whenever there's a leader + bool HasLeader() const { return GetLeader() != nullptr; } + + //! Is ped the leader + bool IsLeader(const CPed* ped) const; + + //! Get the member using it's id CPed* GetMember(int32 memberId); - bool IsFollower(const CPed* ped) const; - bool IsLeader(const CPed* ped); - bool IsMember(const CPed* ped); - void Process(); - void RemoveAllFollowers(bool bCreatedByGameOnly); - void RemoveMember(int32 memberID); - void RemoveMember(CPed& ped); - char RemoveNFollowers(int32 count); - void SetLeader(CPed* ped); - - /// Get a random ped from the group. Might return null. + + //! Get a member's id + int32 GetMemberId(const CPed* ped) const; + + //! Is ped a follower (A member, but not the leader) + bool IsFollower(const CPed* ped) const; + + //! Is ped a member (follower or leader) + bool IsMember(const CPed* ped) const; + + //! Remove all followers of this group [That is, all members except the leader] + void RemoveAllFollowers(bool bCreatedByGameOnly); + + //! Remove a specific member by it's ID + void RemoveMember(int32 memberID); + + //! Remove a specific member + void RemoveMember(CPed* ped); + + //! I'll let you guess + void RemoveNFollowers(size_t count); + + //! Get a random ped from the group. Might return null. CPed* GetRandom(); - /// Whenever `AddFollower` can be called to add a new follower + //! Whenever `AddFollower` can be called to add a new follower bool CanAddFollower(); - /// Get all the members (including the leader) - auto GetMembers() { - return - m_apMembers - | rng::views::filter(notsa::NotIsNull{}) - | rng::views::transform([](CPed* mem) -> CPed& { return *mem; }); // Dereference + //! Get all the present members [Returns a view of references] + auto GetMembers(bool bIncludeLeader = true) { + assert(LEADER_MEM_ID == m_members.size() - 1); // the drop below requires this + return m_members + | rng::views::drop(bIncludeLeader ? 0 : 1) // Last member is the leader + | rng::views::filter(notsa::NotIsNull{}) + | rng::views::transform([](CPed* mem) -> CPed& { return *mem; }); // Dereference } + //! Get followers [that is, members excl. the leader] + auto GetFollowers() { return GetMembers(false); } + + // Closest member, closest member distance sq + using FindClosestMemberResult = std::tuple; + /*! * @notsa * @brief Find the member of this group closest to the ped. @@ -73,33 +118,48 @@ class CPedGroupMembership { * @return The closest member (may be null, in which case the distance should be considered invalid), and it's sq. dist from `ped` */ template Pred> - auto GetMemberClosestToIf(CPed* ped, Pred&& pred) -> std::tuple { + auto GetMemberClosestToIf(CPed* ped, Pred&& pred, bool includeLeader = true) -> FindClosestMemberResult { const auto& pedPos = ped->GetPosition(); - float closestDistSq{ std::numeric_limits::max() }; CPed* closest{}; - for (auto& mem : GetMembers()) { + for (auto& mem : GetMembers(includeLeader)) { if (&mem == ped) { continue; } - if (!std::invoke(pred, mem)) { continue; } - if (const auto distSq = (pedPos - mem.GetPosition()).SquaredMagnitude(); closestDistSq > distSq) { closestDistSq = distSq; - closest = &mem; + closest = &mem; } } - return { closest, closestDistSq }; } /// Wrapper around `GetMemberClosestToIf`, using an always-true predicate - auto GetMemberClosestTo(CPed* ped) { return GetMemberClosestToIf(ped, [](CPed&) { return true; }); } + auto GetMemberClosestTo(CPed* ped, bool includeLeader = true) { return GetMemberClosestToIf(ped, [](CPed&) { return true; }, includeLeader); } - static int32 GetObjectForPedToHold(); -}; + //! Find follower closest to the leader + auto FindClosestFollowerToLeader() -> FindClosestMemberResult; + + static eModelID GetObjectForPedToHold(); +private: + //! NOTSA + int32 FindNewLeaderToAppoint() const; + //! NOTSA + int32 FindIdForNewMember() const; + + //! Based on code from 0x5F80BE + void GivePedRandomObjectToHold(CPed* mem, bool onlyIfUnarmed = true) const; + + //! Add a member by placing it at the given index in the `m_members` array + void AddMember(CPed* member, int32 memIdx); + +private: + CPedGroup* m_group{}; + std::array m_members{}; // m_apMembers[LEADER_MEM_ID] is the leader. The array is not contigous and it might have null values in it! + float m_separationRange{60.f}; +}; VALIDATE_SIZE(CPedGroupMembership, 0x28); diff --git a/source/game_sa/PedIntelligence.cpp b/source/game_sa/PedIntelligence.cpp index dc3db9e454..0f5042a6d8 100644 --- a/source/game_sa/PedIntelligence.cpp +++ b/source/game_sa/PedIntelligence.cpp @@ -337,7 +337,7 @@ CTaskSimpleClimb* CPedIntelligence::GetTaskClimb() { // 0x6011B0 bool CPedIntelligence::GetUsingParachute() { CWeapon* activeWeapon = &m_pPed->GetActiveWeapon(); - if (activeWeapon->m_nType != WEAPON_PARACHUTE) { + if (activeWeapon->m_Type != WEAPON_PARACHUTE) { return false; } @@ -605,7 +605,7 @@ void CPedIntelligence::ProcessAfterPreRender() { } CWeapon* activeWeapon = &m_pPed->GetActiveWeapon(); - if (activeWeapon->m_nType == WEAPON_MOLOTOV && activeWeapon->m_pFxSystem) + if (activeWeapon->m_Type == WEAPON_MOLOTOV && activeWeapon->m_FxSystem) { RpHAnimHierarchy* animHierarchy = GetAnimHierarchyFromSkinClump(m_pPed->m_pRwClump); int32 animIDIndex = RpHAnimIDGetIndex(animHierarchy, 24); // 24 = BONE_R_HAND? @@ -619,7 +619,7 @@ void CPedIntelligence::ProcessAfterPreRender() { memcpy(&matrix, m_pPed->GetModellingMatrix(), sizeof(matrix)); matrix.pos = pointOut; RwMatrixUpdate(&matrix); - activeWeapon->m_pFxSystem->SetMatrix(&matrix); + activeWeapon->m_FxSystem->SetMatrix(&matrix); } if (m_pPed->bInVehicle) @@ -664,11 +664,11 @@ bool CPedIntelligence::Respects(CPed* ped) const { // 0x601CC0 bool CPedIntelligence::IsInACarOrEnteringOne() { if (const auto task = m_TaskMgr.Find()) { - return !!task->GetVehicle(); + return !!task->GetTarget(); } if (const auto task = m_TaskMgr.Find()) { - return !!task->GetVehicle(); + return !!task->GetTarget(); } if (const auto task = m_TaskMgr.Find()) { @@ -758,16 +758,15 @@ bool CPedIntelligence::TestForStealthKill(CPed* target, bool bFullTest) { auto hate = target->GetAcquaintance().GetAcquaintances(ACQUAINTANCE_HATE); auto dislike = target->GetAcquaintance().GetAcquaintances(ACQUAINTANCE_DISLIKE); auto pedFlag = CPedType::GetPedFlag(m_pPed->m_nPedType); - bool bAcquaintancesFlagSet = ( (hate && (pedFlag & hate)) || (dislike && (pedFlag & dislike)) ); - CPedGroup* pedGroup = CPedGroups::GetPedsGroup(target); - if (bAcquaintancesFlagSet && pedGroup) { - CEventGroupEvent* eventGroupEvent = pedGroup->GetIntelligence().m_pOldEventGroupEvent; - if (eventGroupEvent && eventGroupEvent->GetSourceEntity() == m_pPed && bAcquaintancesFlagSet) + if (bAcquaintancesFlagSet && target->GetGroup()) { + const auto oe = target->GetGroup()->GetIntelligence().GetOldEvent(); + if (oe && oe->GetSourceEntity() == m_pPed && bAcquaintancesFlagSet) { return false; + } } } return true; @@ -775,8 +774,7 @@ bool CPedIntelligence::TestForStealthKill(CPed* target, bool bFullTest) { // 0x602050 void CPedIntelligence::RecordEventForScript(int32 eventId, int32 eventPriority) { - if (eventId != EVENT_SCRIPT_COMMAND && (!eventId || eventPriority > m_nEventPriority)) - { + if (eventId != EVENT_SCRIPT_COMMAND && (!eventId || eventPriority > m_nEventPriority)) { m_nEventId = eventId; m_nEventPriority = eventPriority; } @@ -1038,6 +1036,16 @@ void CPedIntelligence::operator delete(void* object) { GetPedIntelligencePool()->Delete(static_cast(object)); } +// NOTSA +CVehicle* CPedIntelligence::GetEnteringVehicle() { + for (const auto taskt : { TASK_COMPLEX_ENTER_CAR_AS_DRIVER, TASK_COMPLEX_ENTER_CAR_AS_PASSENGER }) { + if (const auto task = FindTaskByType(taskt)) { + return static_cast(task)->GetTarget(); + } + } + return nullptr; +} + // 0x607140 CPedIntelligence* CPedIntelligence::Constructor(CPed* ped) { this->CPedIntelligence::CPedIntelligence(ped); diff --git a/source/game_sa/PedIntelligence.h b/source/game_sa/PedIntelligence.h index fa9c7eeda0..f776011e0a 100644 --- a/source/game_sa/PedIntelligence.h +++ b/source/game_sa/PedIntelligence.h @@ -148,7 +148,11 @@ class CPedIntelligence { return false; } - CEventHandler& GetEventHandler() { return m_eventHandler; } + //! Get the vehicle the ped is entering now (If any) + CVehicle* GetEnteringVehicle(); + + CEventHandler& GetEventHandler() { return m_eventHandler; } + CEventGroup& GetEventGroup() { return m_eventGroup; } CEventScanner& GetEventScanner() { return m_eventScanner; } CPedScanner& GetPedScanner() { return m_pedScanner; } CVehicleScanner& GetVehicleScanner() { return m_vehicleScanner; } diff --git a/source/game_sa/PedTaskPair.cpp b/source/game_sa/PedTaskPair.cpp index 1d5101e352..1c5778dcd0 100644 --- a/source/game_sa/PedTaskPair.cpp +++ b/source/game_sa/PedTaskPair.cpp @@ -11,8 +11,8 @@ void CPedTaskPair::InjectHooks() { // 0x5E95B0 void CPedTaskPair::Flush() { - m_pPed = nullptr; - delete m_pTask; - m_pTask = nullptr; - m_taskSlot = -1; + m_Ped = nullptr; + delete m_Task; + m_Task = nullptr; + m_Slot = -1; } diff --git a/source/game_sa/PedTaskPair.h b/source/game_sa/PedTaskPair.h index eece4fbc52..30e22636f9 100644 --- a/source/game_sa/PedTaskPair.h +++ b/source/game_sa/PedTaskPair.h @@ -11,11 +11,11 @@ class CTask; class CPedTaskPair { public: - CPed* m_pPed; - CTask* m_pTask; - int32 m_taskSlot; - int32 field_C; - int8 field_10; + CPed* m_Ped{}; + CTask* m_Task{}; + int32 m_Slot{-1}; + int32 m_MatchID{}; + int8 m_bUsedTask{}; public: static void InjectHooks(); diff --git a/source/game_sa/Pickup.cpp b/source/game_sa/Pickup.cpp index 4e31db98ba..d110b22094 100644 --- a/source/game_sa/Pickup.cpp +++ b/source/game_sa/Pickup.cpp @@ -387,7 +387,7 @@ bool CPickup::Update(CPlayerPed* player, CVehicle* vehicle, int32 playerId) { const auto& weaponInfo = CWeaponInfo::GetWeaponInfo(weapon); auto& pedWeapon = player->GetWeaponInSlot(weaponInfo->m_nSlot); - if (pedWeapon.m_nType != weapon) { + if (pedWeapon.m_Type != weapon) { if (!CStreaming::GetInfo(m_pObject->m_nModelIndex).IsLoaded()) { return false; } @@ -403,7 +403,7 @@ bool CPickup::Update(CPlayerPed* player, CVehicle* vehicle, int32 playerId) { } }; - if (pedWeapon.m_nType != WEAPON_UNARMED && (pedWeapon.m_nTotalAmmo != 0 || IsSlotExchangeable(weaponInfo->m_nSlot))) { + if (pedWeapon.m_Type != WEAPON_UNARMED && (pedWeapon.m_TotalAmmo != 0 || IsSlotExchangeable(weaponInfo->m_nSlot))) { CPickups::PlayerOnWeaponPickup = 6; if (IsSlotExchangeable(weaponInfo->m_nSlot)) { diff --git a/source/game_sa/Pickups.cpp b/source/game_sa/Pickups.cpp index 28cc352bde..447639fba7 100644 --- a/source/game_sa/Pickups.cpp +++ b/source/game_sa/Pickups.cpp @@ -64,6 +64,8 @@ void CPickups::InjectHooks() { // 0x454A70 void CPickups::Init() { + ZoneScoped; + NumMessages = 0; for (auto& pickup : aPickUps) { pickup.m_nPickupType = PICKUP_NONE; @@ -621,6 +623,8 @@ bool CPickups::TryToMerge_WeaponType(CVector posn, eWeaponType weaponType, ePick // 0x458DE0 void CPickups::Update() { + ZoneScoped; + if (CReplay::Mode == MODE_PLAYBACK) return; diff --git a/source/game_sa/Pipelines/CustomBuilding/CustomBuildingRenderer.cpp b/source/game_sa/Pipelines/CustomBuilding/CustomBuildingRenderer.cpp index 8133422f5c..67f1a7192d 100644 --- a/source/game_sa/Pipelines/CustomBuilding/CustomBuildingRenderer.cpp +++ b/source/game_sa/Pipelines/CustomBuilding/CustomBuildingRenderer.cpp @@ -82,6 +82,8 @@ void CCustomBuildingRenderer::UpdateDayNightBalanceParam() { // 0x5D8050 void CCustomBuildingRenderer::Update() { + ZoneScoped; + plugin::Call<0x5D8050>(); /* diff --git a/source/game_sa/Plant/ProcObjectMan.cpp b/source/game_sa/Plant/ProcObjectMan.cpp index a92e0f42c6..76822ce868 100644 --- a/source/game_sa/Plant/ProcObjectMan.cpp +++ b/source/game_sa/Plant/ProcObjectMan.cpp @@ -31,6 +31,8 @@ void ProcObjectMan_c::Init() { // 0x5A3110 void ProcObjectMan_c::Update() { + ZoneScoped; + // NOP } @@ -96,7 +98,7 @@ ProcObjectListItem* ProcObjectMan_c::GetEntityFromPool() { } // 0x5A3130 -void ProcObjectMan_c::ReturnEntityToPool(ListItem_c* item) { // todo: EntityItem_c +void ProcObjectMan_c::ReturnEntityToPool(ProcObjectListItem* item) { m_ObjectsList.AddItem(item); } diff --git a/source/game_sa/Plant/ProcObjectMan.h b/source/game_sa/Plant/ProcObjectMan.h index 21b4119abf..4d007626b7 100644 --- a/source/game_sa/Plant/ProcObjectMan.h +++ b/source/game_sa/Plant/ProcObjectMan.h @@ -6,10 +6,9 @@ class ProcSurfaceInfo_c; class CObject; class CEntity; -class ListItem_c; class CPlantLocTri; -class ProcObjectListItem : public ListItem_c { +class ProcObjectListItem : public ListItem_c { public: CEntity* m_Obj; CPlantLocTri* m_LocTri; @@ -37,7 +36,7 @@ class ProcObjectMan_c { void LoadDataFile(); ProcObjectListItem* GetEntityFromPool(); - void ReturnEntityToPool(ListItem_c* item); + void ReturnEntityToPool(ProcObjectListItem* item); int32 ProcessTriangleAdded(CPlantLocTri* plant); void ProcessTriangleRemoved(CPlantLocTri* plant); diff --git a/source/game_sa/PlantMgr.cpp b/source/game_sa/PlantMgr.cpp index bd424db93c..2961e5bc60 100644 --- a/source/game_sa/PlantMgr.cpp +++ b/source/game_sa/PlantMgr.cpp @@ -105,6 +105,8 @@ void CPlantMgr::InjectHooks() { // 0x5DD910 bool CPlantMgr::Initialise() { + ZoneScoped; + if (!ReloadConfig()) return false; @@ -303,6 +305,8 @@ void CPlantMgr::SetPlantFriendlyFlagInAtomicMI(CAtomicModelInfo* ami) { // 0x5DCFA0 void CPlantMgr::Update(const CVector& cameraPosition) { + ZoneScoped; + static int8& cache = *(int8*)0xC09171; static int8& section = *(int8*)0xC09170; diff --git a/source/game_sa/Plugins/TwoDEffectPlugin/2dEffect.h b/source/game_sa/Plugins/TwoDEffectPlugin/2dEffect.h index 7f367d1b9e..cd86dda090 100644 --- a/source/game_sa/Plugins/TwoDEffectPlugin/2dEffect.h +++ b/source/game_sa/Plugins/TwoDEffectPlugin/2dEffect.h @@ -15,7 +15,7 @@ enum e2dEffectType : uint8 { EFFECT_MISSING_OR_UNK= 2, EFFECT_ATTRACTOR = 3, EFFECT_SUN_GLARE = 4, - EFFECT_FURNITURE = 5, + EFFECT_INTERIOR = 5, EFFECT_ENEX = 6, EFFECT_ROADSIGN = 7, EFFECT_TRIGGER_POINT = 8, // todo: EFFECT_SLOTMACHINE_WHEEL? @@ -186,6 +186,37 @@ struct tEffectEscalator { }; VALIDATE_SIZE(tEffectEscalator, 0x28); +struct tEffectInterior { + static inline constexpr e2dEffectType Type = EFFECT_INTERIOR; + + uint8 m_type; + int8 m_groupId; + uint8 m_width; + uint8 m_depth; + uint8 m_height; + int8 m_door; + int8 m_lDoorStart; + int8 m_lDoorEnd; + int8 m_rDoorStart; + int8 m_rDoorEnd; + int8 m_tDoorStart; + int8 m_tDoorEnd; + int8 m_lWindowStart; + int8 m_lWindowEnd; + int8 m_rWindowStart; + int8 m_rWindowEnd; + int8 m_tWindowStart; + int8 m_tWindowEnd; + int8 m_noGoLeft[3]; + int8 m_noGoBottom[3]; + int8 m_noGoWidth[3]; + int8 m_noGoDepth[3]; + uint8 m_seed; + uint8 m_status; + float m_rot; +}; +VALIDATE_SIZE(tEffectInterior, 0x34 - 0x10); + //! NOTASA base class (otherwise SA) struct C2dEffectBase { CVector m_pos; @@ -204,6 +235,7 @@ struct C2dEffectRoadsign : C2dEffectBase, tEffectRoadsign {}; struct C2dEffectSlotMachineWheel : C2dEffectBase, tEffectSlotMachineWheel {}; struct C2dEffectCoverPoint : C2dEffectBase, tEffectCoverPoint {}; struct C2dEffectEscalator : C2dEffectBase, tEffectEscalator {}; +struct C2dEffectInterior : C2dEffectBase, tEffectInterior {}; struct C2dEffect : public C2dEffectBase { union { diff --git a/source/game_sa/PointLights.cpp b/source/game_sa/PointLights.cpp index f6cfa8a4df..b86fc13ae8 100644 --- a/source/game_sa/PointLights.cpp +++ b/source/game_sa/PointLights.cpp @@ -40,5 +40,7 @@ void CPointLights::AddLight(uint8 lightType, CVector point, CVector direction, f // 0x7002D0 void CPointLights::RenderFogEffect() { + ZoneScoped; + plugin::Call<0x7002D0>(); } diff --git a/source/game_sa/Pools.cpp b/source/game_sa/Pools.cpp index f23483487c..4b1f7e8a8e 100644 --- a/source/game_sa/Pools.cpp +++ b/source/game_sa/Pools.cpp @@ -32,6 +32,8 @@ void CPools::InjectHooks() { // 0x550F10 void CPools::Initialise() { + ZoneScoped; + plugin::Call<0x550F10>(); /* CMemoryMgr::PushMemId(MEM_POOLS); diff --git a/source/game_sa/PopCycle.cpp b/source/game_sa/PopCycle.cpp index 9673dbee2f..8aea982fdb 100644 --- a/source/game_sa/PopCycle.cpp +++ b/source/game_sa/PopCycle.cpp @@ -321,6 +321,8 @@ void CPopCycle::PlayerKilledADealer() { // 0x610BF0 void CPopCycle::Update() { + ZoneScoped; + m_nCurrentTimeOfWeek = [] { switch (CClock::GetGameWeekDay()) { case 0: // Not sure (Maybe Sunday) diff --git a/source/game_sa/Population.cpp b/source/game_sa/Population.cpp index e434e12e3c..82ce6d1ad8 100644 --- a/source/game_sa/Population.cpp +++ b/source/game_sa/Population.cpp @@ -27,9 +27,9 @@ #define EXTRA_DEBUG_LOGS #ifdef EXTRA_DEBUG_LOGS -#define POP_DEV_LOG DEV_LOG +#define POP_LOG_DEBUG DEV_LOG #else -#define POP_DEV_LOG(...) +#define POP_LOG_DEBUG(...) #endif float& CPopulation::PedDensityMultiplier = *(float*)0x8D2530; @@ -167,7 +167,7 @@ void LoadGroup(const char* fileName, auto& outModelsInGroup, auto& outNumOfModel const auto file = CFileMgr::OpenFile(fileName, "r"); CFileMgr::ChangeDir("\\"); - POP_DEV_LOG("Loading `{}`...", fileName); + POP_LOG_DEBUG("Loading `{}`...", fileName); size_t currGrpIdx{}, lineno{1}; for (;const auto l = CFileLoader::LoadLine(file); lineno++) { // Also replaces `,` with ` ` (space) (Important to know) @@ -184,7 +184,7 @@ void LoadGroup(const char* fileName, auto& outModelsInGroup, auto& outNumOfModel #ifdef _DEBUG // See bottom of the outer loop for info if (currGrpIdx >= outModelsInGroup.size()) { - POP_DEV_LOG("Data found past-the-end! This would crash the vanilla game! [Line: {}]", lineno); + POP_LOG_DEBUG("Data found past-the-end! This would crash the vanilla game! [Line: {}]", lineno); break; } #endif @@ -194,7 +194,7 @@ void LoadGroup(const char* fileName, auto& outModelsInGroup, auto& outNumOfModel // loop is let to do one more iteration before breaking // to see if there are any more models to be added if (npeds >= outModelsInGroup[currGrpIdx].size()) { - POP_DEV_LOG("There are models to be added to the group, but there's no memory! [Group ID: {}; Line: {}]", currGrpIdx, lineno); + POP_LOG_DEBUG("There are models to be added to the group, but there's no memory! [Group ID: {}; Line: {}]", currGrpIdx, lineno); break; } @@ -212,7 +212,7 @@ void LoadGroup(const char* fileName, auto& outModelsInGroup, auto& outNumOfModel continue; // Blank line } - //POP_DEV_LOG("Loaded ({}) models into the group ({})", outNumOfModelsPerGroup[currGrpIdx], currGrpIdx); + //POP_LOG_DEBUG("Loaded ({}) models into the group ({})", outNumOfModelsPerGroup[currGrpIdx], currGrpIdx); // Only now set this outNumOfModelsPerGroup[currGrpIdx] = npeds; @@ -1613,6 +1613,8 @@ void CPopulation::ConvertToDummyObject(CObject* object) { // 0x614720 bool CPopulation::AddToPopulation(float arg0, float arg1, float arg2, float arg3) { + ZoneScoped; + return ((bool(__cdecl*)(float, float, float, float))0x614720)(arg0, arg1, arg2, arg3); } @@ -1626,6 +1628,8 @@ int32 CPopulation::GeneratePedsAtAttractors( int32 decisionMaker, int32 numPedsToCreate ) { + ZoneScoped; + if (!numPedsToCreate) { return 0; } @@ -1718,6 +1722,8 @@ int32 CPopulation::GeneratePedsAtAttractors( // 0x615C90 void CPopulation::GeneratePedsAtStartOfGame() { + ZoneScoped; + const auto minRadius = 10.f, maxRadius = 50.5f * PedCreationDistMultiplier(); for (int32 i = 100; i --> 0;) { // "down to" operator in use @@ -1770,26 +1776,36 @@ void CPopulation::ManageAllPopulation() { // 0x616190 void CPopulation::ManagePopulation() { + ZoneScoped; + // TODO: Implement original `framecounter % 32` pool splitting logic // It's just a perf optimization, so I didn't bother const auto& center = FindPlayerCentreOfWorld(); - - for (auto& obj : GetObjectPool()->GetAllValid()) { - ManageObject(&obj, center); + { + ZoneScopedN("Manage Objects"); + for (auto& obj : GetObjectPool()->GetAllValid()) { + ManageObject(&obj, center); + } } - - for (auto& dummy : GetDummyPool()->GetAllValid()) { - ManageDummy(&dummy, center); + { + ZoneScopedN("Manage Dummies"); + for (auto& dummy : GetDummyPool()->GetAllValid()) { + ManageDummy(&dummy, center); + } } - - for (auto& ped : GetPedPool()->GetAllValid()) { - ManagePed(&ped, center); + { + ZoneScopedN("Manage Peds"); + for (auto& ped : GetPedPool()->GetAllValid()) { + ManagePed(&ped, center); + } } } // 0x616300 void CPopulation::RemovePedsIfThePoolGetsFull() { + ZoneScoped; + if (CTimer::GetFrameCounter() % 8 != 5) { return; } @@ -1851,14 +1867,16 @@ void CPopulation::PopulateInterior(int32 numPedsToCreate, CVector pos) { ped->GetIntelligence()->SetPedDecisionMakerType(7); - if (ped->m_nAnimGroup == CAnimManager::GetAnimationGroupId("jogger")) { // TODO: Move `GetAnimationGroupId` out the loop? - ped->m_nAnimGroup = CAnimManager::GetAnimationGroupId("man"); + if (ped->m_nAnimGroup == CAnimManager::GetAnimationGroupIdByName("jogger")) { // TODO: Move `GetAnimationGroupId` out the loop? + ped->m_nAnimGroup = CAnimManager::GetAnimationGroupIdByName("man"); } } } // 0x616650 void CPopulation::Update(bool generatePeds) { + ZoneScoped; + generatePeds = true; CurrentWorldZone = [] { switch (CWeather::WeatherRegion) { @@ -1926,6 +1944,8 @@ uint32 CPopulation::CalculateTotalNumGangPeds() { // NOTSA - Moved here for reuseability void CPopulation::UpdatePedCounts() { + ZoneScoped; + ms_nTotalGangPeds = CalculateTotalNumGangPeds(); ms_nTotalCivPeds = ms_nNumCivMale + ms_nNumCivFemale; ms_nTotalPeds = ms_nTotalCivPeds + ms_nTotalGangPeds + ms_nNumCop + ms_nNumEmergency; diff --git a/source/game_sa/PostEffects.cpp b/source/game_sa/PostEffects.cpp index 028b2193d9..b8da40af4b 100644 --- a/source/game_sa/PostEffects.cpp +++ b/source/game_sa/PostEffects.cpp @@ -253,6 +253,8 @@ void CPostEffects::SetupBackBufferVertex() { // 0x7046A0 void CPostEffects::Update() { + ZoneScoped; + m_bRainEnable = CWeather::Rain > 0.0f; if (!pRasterFrontBuffer) { SetupBackBufferVertex(); @@ -753,5 +755,7 @@ void CPostEffects::Radiosity(int32 intensityLimit, int32 filterPasses, int32 ren // 0x7046E0 void CPostEffects::Render() { + ZoneScoped; + plugin::Call<0x7046E0>(); } diff --git a/source/game_sa/ProjectileInfo.cpp b/source/game_sa/ProjectileInfo.cpp index 1e699017d8..2f3f7e46c8 100644 --- a/source/game_sa/ProjectileInfo.cpp +++ b/source/game_sa/ProjectileInfo.cpp @@ -42,8 +42,8 @@ void CProjectileInfo::RemoveNotAdd(CEntity* creator, eWeaponType weaponType, CVe } // 0x737C80 -bool CProjectileInfo::AddProjectile(CEntity* creator, eWeaponType WeponType, CVector src, float force, CVector* dest, CEntity* victim) { - return plugin::CallAndReturn(creator, WeponType, src, force, dest, victim); +bool CProjectileInfo::AddProjectile(CEntity* creator, eWeaponType projectileType, CVector origin, float force, const CVector* dir, CEntity* target) { + return plugin::CallAndReturn(creator, projectileType, origin, force, dir, target); } // 0x738860 diff --git a/source/game_sa/ProjectileInfo.h b/source/game_sa/ProjectileInfo.h index c6eb07f04b..b324034aa2 100644 --- a/source/game_sa/ProjectileInfo.h +++ b/source/game_sa/ProjectileInfo.h @@ -34,7 +34,7 @@ class CProjectileInfo { void RemoveFXSystem(bool bInstantly); static CProjectileInfo* GetProjectileInfo(int32 infoId); static void RemoveNotAdd(CEntity* creator, eWeaponType weaponType, CVector posn); - static bool AddProjectile(CEntity* creator, eWeaponType eWeaponType, CVector posn, float force, CVector* direction, CEntity* victim); + static bool AddProjectile(CEntity* creator, eWeaponType eWeaponType, CVector posn, float force, const CVector* direction, CEntity* victim); static void RemoveDetonatorProjectiles(); static void RemoveProjectile(CProjectileInfo* projectileInfo, CProjectile* projectileObject); static void Update(); diff --git a/source/game_sa/Radar.cpp b/source/game_sa/Radar.cpp index 0d5f1073ae..5810a39adf 100644 --- a/source/game_sa/Radar.cpp +++ b/source/game_sa/Radar.cpp @@ -115,7 +115,7 @@ void CRadar::InjectHooks() { RH_ScopedInstall(ChangeBlipColour, 0x583AB0); RH_ScopedOverloadedInstall(ClearActualBlip, "OG", 0x587C10, void (*)(int32)); RH_ScopedInstall(ClearBlip, 0x587CE0); - RH_ScopedInstall(ClearBlipForEntity, 0x587C60); + RH_ScopedOverloadedInstall(ClearBlipForEntity, "OG", 0x587C60, void(*)(eBlipType, int32)); RH_ScopedInstall(RequestMapSection, 0x584B50); RH_ScopedInstall(RemoveMapSection, 0x584BB0); RH_ScopedInstall(RemoveRadarSections, 0x584BF0); @@ -147,7 +147,7 @@ void CRadar::InjectHooks() { RH_ScopedInstall(ShowRadarTraceWithHeight, 0x584070); RH_ScopedInstall(DrawCoordBlip, 0x586D60); - RH_ScopedInstall(SetupAirstripBlips, 0x587D20, {.reversed = false}); // TEST + RH_ScopedInstall(SetupAirstripBlips, 0x587D20); // TEST RH_ScopedInstall(DrawBlips, 0x588050); // RH_ScopedInstall(ClipRadarPoly, 0x585040); // RH_ScopedInstall(DrawAreaOnRadar, 0x5853D0); @@ -239,10 +239,6 @@ int32 CRadar::GetActualBlipArrayIndex(tBlipHandle blip) { // 0x5828A0 void CRadar::DrawLegend(int32 x, int32 y, eRadarSprite blipType) { - if (blipType == RADAR_SPRITE_NONE) { // None => Player position - blipType = RADAR_SPRITE_MAP_HERE; - } - CFont::PrintString( (float)x + SCREEN_STRETCH_X(20.0f), (float)y + SCREEN_STRETCH_Y(3.0f), @@ -253,13 +249,18 @@ void CRadar::DrawLegend(int32 x, int32 y, eRadarSprite blipType) { RadarBlipSprites[blipType].Draw( { (float)x, (float)y, - (float)x + SCREEN_STRETCH_X(16.0f), (float)y + SCREEN_STRETCH_Y(16.0f) + (float)x + SCREEN_STRETCH_X(16.0f), (float)y + SCREEN_STRETCH_X(16.0f) }, + // NOTE: `y + SCREEN_STRETCH_X(16.0f)` is correct. It is here to make sprites + // square instead of dependent on the aspect ratio. { 255, 255, 255, 255 } ); return; } + static auto& legendTraceHeight = StaticRef(); // = eRadarTraceHeight::RADAR_TRACE_LOW; + static auto& legendTraceTimer = StaticRef(); // = CTimer::GetTimeInMS(); + if (CTimer::GetTimeInMSPauseMode() - legendTraceTimer > 600) { legendTraceTimer = CTimer::GetTimeInMSPauseMode(); @@ -425,13 +426,7 @@ void CRadar::TransformRadarPointToScreenSpace(CVector2D& out, const CVector2D& i * @addr 0x583530 */ void CRadar::TransformRealWorldPointToRadarSpace(CVector2D& out, const CVector2D& in) { - const auto xOffset = (in.x - vec2DRadarOrigin.x) / m_radarRange; - const auto yOffset = (in.y - vec2DRadarOrigin.y) / m_radarRange; - - out = { - cachedSin * yOffset + cachedCos * xOffset, - cachedCos * yOffset - cachedSin * xOffset - }; + out = CachedRotateClockwise((in - vec2DRadarOrigin) / m_radarRange); } /*! @@ -439,10 +434,7 @@ void CRadar::TransformRealWorldPointToRadarSpace(CVector2D& out, const CVector2D * @addr 0x5835A0 */ void CRadar::TransformRadarPointToRealWorldSpace(CVector2D& out, const CVector2D& in) { - out = CVector2D{ - cachedCos * in.x - cachedSin * in.y, - cachedCos * in.y + cachedSin * in.x - } * m_radarRange + vec2DRadarOrigin; + out = CachedRotateCounterclockwise(in) * m_radarRange + vec2DRadarOrigin; } /*! @@ -495,7 +487,7 @@ void CRadar::CalculateCachedSinCos() { * @brief Creates a new coordinate blip (i.e. tracing blip with no sprite) * @param type Type * @param posn Position - * @param color Color + * @param color Color (Unused) * @param blipDisplay Display option * @param scriptName Script name (Unused) (from Android) * @addr 0x583820 @@ -511,7 +503,7 @@ tBlipHandle CRadar::SetCoordBlip(eBlipType type, CVector posn, eBlipColour color t.m_vPosition = posn; t.m_nBlipDisplayFlag = blipDisplay; t.m_nBlipType = type; - t.m_nColour = color; + t.m_nColour = BLIP_COLOUR_DESTINATION; t.m_fSphereRadius = 1.f; t.m_nEntityHandle = 0; t.m_nBlipSize = 1; @@ -590,16 +582,13 @@ void CRadar::ChangeBlipColour(tBlipHandle blip, eBlipColour color) { * @returns True if it's revealed to the player. */ bool CRadar::HasThisBlipBeenRevealed(int32 blipIndex) { - const auto& blipPos = ms_RadarTrace[blipIndex].m_vPosition; - if (!FrontEndMenuManager.m_bDrawingMap || !ms_RadarTrace[blipIndex].m_bShortRange || CTheZones::ZonesRevealed > 80 - || CTheZones::GetCurrentZoneLockedOrUnlocked(blipPos.x, blipPos.y) + || CTheZones::GetCurrentZoneLockedOrUnlocked(ms_RadarTrace[blipIndex].m_vPosition) ) { return true; } - return false; } @@ -851,10 +840,15 @@ void CRadar::ShowRadarTrace(float x, float y, uint32 size, CRGBA color) { * @brief Draws a coordinate blip to the map with height information. * @addr 0x584070 */ -void CRadar::ShowRadarTraceWithHeight(float x, float y, uint32 size, CRGBA color, eRadarTraceHeight height) { +void CRadar::ShowRadarTraceWithHeight(float x, float y, uint32 size, uint32 R, uint32 G, uint32 B, uint32 A, eRadarTraceHeight height) { Limit(x, y); RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(NULL)); + // NOTE: Those RGBA parameters are 4-bytes per channel. + // then we construct 1-byte per channel CRGBA. It's stupid isn't it? + // NOTE: {R,G,B,A} > 255 is possible, so we can't assert for x <= UCHAR_MAX. + const auto r = static_cast(R), g = static_cast(G), b = static_cast(B), a = static_cast(A); + const auto size0 = float(size + 0), size1 = float(size + 1); const auto size2 = float(size + 2), size3 = float(size + 3); @@ -865,23 +859,23 @@ void CRadar::ShowRadarTraceWithHeight(float x, float y, uint32 size, CRGBA color x, y + SCREEN_STRETCH_Y(size3), x + SCREEN_STRETCH_X(size3), y - SCREEN_STRETCH_Y(size2), x - SCREEN_STRETCH_X(size3), y - SCREEN_STRETCH_Y(size2), - { 0, 0, 0, color.a } + { 0, 0, 0, a } ); CSprite2d::Draw2DPolygon( // draw triangle x, y + SCREEN_STRETCH_Y(size1), x, y + SCREEN_STRETCH_Y(size1), x + SCREEN_STRETCH_X(size1), y - SCREEN_STRETCH_Y(size1), x - SCREEN_STRETCH_X(size1), y - SCREEN_STRETCH_Y(size1), - color + { r, g, b, a } ); break; case RADAR_TRACE_NORMAL: CSprite2d::DrawRect( // draw black border - CRect( + { x - SCREEN_STRETCH_X(size1), y - SCREEN_STRETCH_Y(size1), x + SCREEN_STRETCH_X(size1), y + SCREEN_STRETCH_Y(size1) - ), - { 0, 0, 0, color.a } + }, + { 0, 0, 0, a } ); CSprite2d::DrawRect( // draw box @@ -889,7 +883,7 @@ void CRadar::ShowRadarTraceWithHeight(float x, float y, uint32 size, CRGBA color x - SCREEN_STRETCH_X(size0), y - SCREEN_STRETCH_Y(size0), x + SCREEN_STRETCH_X(size0), y + SCREEN_STRETCH_Y(size0) }, - color + { r, g, b, a } ); break; case RADAR_TRACE_LOW: @@ -898,7 +892,7 @@ void CRadar::ShowRadarTraceWithHeight(float x, float y, uint32 size, CRGBA color x - SCREEN_STRETCH_X(size3), y + SCREEN_STRETCH_Y(size2), x, y - SCREEN_STRETCH_Y(size3), x, y - SCREEN_STRETCH_Y(size3), - { 0, 0, 0, color.a } + { 0, 0, 0, a } ); CSprite2d::Draw2DPolygon( // draw triangle @@ -906,7 +900,7 @@ void CRadar::ShowRadarTraceWithHeight(float x, float y, uint32 size, CRGBA color x - SCREEN_STRETCH_X(size1), y + SCREEN_STRETCH_Y(size1), x, y - SCREEN_STRETCH_Y(size1), x, y - SCREEN_STRETCH_Y(size1), - color + { r, g, b, a } ); break; } @@ -998,7 +992,7 @@ void CRadar::DrawYouAreHereSprite(float x, float y) { ); } - MapLegendList[++MapLegendCounter] = RADAR_SPRITE_MAP_HERE; + MapLegendList[MapLegendCounter++] = RADAR_SPRITE_MAP_HERE; } // 0x584A80 @@ -1181,7 +1175,7 @@ void CRadar::InitFrontEndMap() { rng::fill(MapLegendList, RADAR_SPRITE_NONE); vec2DRadarOrigin.Set(0.0f, 0.0f); - m_radarRange = 2990.0f; // todo: world const - 1.0f + m_radarRange = WORLD_BOUND_RANGE - 10.0f; MapLegendCounter = 0; rng::fill(ArrowBlipColour, CRGBA(0, 0, 0, 0)); @@ -1532,7 +1526,7 @@ void CRadar::DrawRadarMap() { const auto vehicle = FindPlayerVehicle(); // Draw green rectangle when in plane - if (vehicle && vehicle->IsSubPlane() && ModelIndices::IsVortex(vehicle->m_nModelIndex)) { + if (vehicle && vehicle->IsSubPlane() && !ModelIndices::IsVortex(vehicle->m_nModelIndex)) { CVector playerPos = FindPlayerCentreOfWorld_NoInteriorShift(0); const auto cSin = cachedSin; @@ -1562,7 +1556,6 @@ void CRadar::DrawRadarMap() { } // 0x586B00 -// TODO: Fix me - Zoom incorrect void CRadar::DrawMap() { const auto player = FindPlayerPed(); const auto mapShouldDrawn = !CGame::currArea && player->m_nAreaCode == 0 && FrontEndMenuManager.m_nRadarMode != 1; @@ -1578,7 +1571,7 @@ void CRadar::DrawMap() { else m_radarRange = RADAR_MIN_RANGE; } else { - if (vehicle && vehicle->IsSubPlane() && ModelIndices::IsVortex(vehicle->m_nModelIndex)) { + if (vehicle && vehicle->IsSubPlane() && !ModelIndices::IsVortex(vehicle->m_nModelIndex)) { const auto speedZ = vehicle->GetPosition().z * 1.0f / 200.0f; if (speedZ < RADAR_MIN_SPEED) @@ -1652,17 +1645,17 @@ void CRadar::DrawCoordBlip(int32 blipIndex, bool isSprite) { } const auto GetHeight = [&] { - const auto zDiff = trace.GetWorldPos().z - FindPlayerCentreOfWorld_NoInteriorShift().z; + const auto zDiff = trace.GetWorldPos().z - FindPlayerCentreOfWorld_NoInteriorShift(PED_TYPE_PLAYER1).z; - if (zDiff < 2.0f) { + if (zDiff > 2.0f) { // trace is higher return RADAR_TRACE_LOW; - } else if (zDiff < -4.0f) { - // player is higher - return RADAR_TRACE_HIGH; - } else { + } else if (zDiff >= -4.0f) { // they are at the around the same elevation. return RADAR_TRACE_NORMAL; + } else { + // player is higher + return RADAR_TRACE_HIGH; } }; @@ -1671,12 +1664,10 @@ void CRadar::DrawCoordBlip(int32 blipIndex, bool isSprite) { screenPos.x, screenPos.y, trace.m_nBlipSize, - { - color.r, - color.g, - color.b, - trace.m_bBlipFade ? color.a : CalculateBlipAlpha(realDist) - }, + color.r, + color.g, + color.b, + trace.m_bBlipFade ? color.a : CalculateBlipAlpha(realDist), GetHeight() ); @@ -1733,6 +1724,10 @@ void CRadar::ClearBlipForEntity(eBlipType blipType, int32 entityHandle) { } } +void CRadar::ClearBlipForEntity(CPed* ped) { + ClearBlipForEntity(BLIP_CHAR, GetPedPool()->GetIndex(ped)); +} + /* * @brief Clear a blip * @addr 0x587CE0 @@ -1750,7 +1745,11 @@ CVector GetAirStripLocation(eAirstripLocation location) { // 0x587D20 void CRadar::SetupAirstripBlips() { if (const auto veh = FindPlayerVehicle(); veh && veh->IsSubPlane() && !ModelIndices::IsVortex(veh->m_nModelIndex)) { - if ((CTimer::GetFrameCounter() & 4) == 0) { + // FIX_BUGS: FPS independent counter. SA: (CTimer::GetFrameCounter() & 4) == 0 + static auto airstripBlipCounter = CTimer::GetTimeInMS() + 200; + if (CTimer::GetTimeInMS() >= airstripBlipCounter) { + airstripBlipCounter = CTimer::GetTimeInMS() + 200; + if (airstrip_blip) return; diff --git a/source/game_sa/Radar.h b/source/game_sa/Radar.h index 159792f47d..50e18519c6 100644 --- a/source/game_sa/Radar.h +++ b/source/game_sa/Radar.h @@ -203,10 +203,6 @@ class CRadar { static inline float& cachedCos = *(float*)0xBA8308; static inline float& cachedSin = *(float*)0xBA830C; - // original name unknown - static inline eRadarTraceHeight& legendTraceHeight = *(eRadarTraceHeight*)0xBAA350; - static inline uint32& legendTraceTimer = *(uint32*)0xBAA354; - static SpriteFileName RadarBlipFileNames[]; static inline float& m_radarRange = *(float*)0xBA8314; // 2990.0 by default @@ -257,7 +253,7 @@ class CRadar { static void SetBlipFriendly(tBlipHandle blip, bool friendly); static void SetBlipEntryExit(tBlipHandle blip, CEntryExit* enex); static void ShowRadarTrace(float x, float y, uint32 size, CRGBA color); - static void ShowRadarTraceWithHeight(float x, float y, uint32 size, CRGBA color, eRadarTraceHeight height); + static void ShowRadarTraceWithHeight(float x, float y, uint32 size, uint32 r, uint32 g, uint32 b, uint32 a, eRadarTraceHeight height); static void ShowRadarMarker(CVector posn, uint32 color, float radius); static uint32 GetRadarTraceColour(eBlipColour color, bool bright, bool friendly); static void DrawRotatingRadarSprite(CSprite2d& sprite, float x, float y, float angle, uint32 width, uint32 height, CRGBA color); @@ -288,6 +284,7 @@ class CRadar { static void ClearActualBlip(int32 blipIndex); static void ClearActualBlip(tRadarTrace& trace); static void ClearBlipForEntity(eBlipType blipType, int32 entityHandle); + static void ClearBlipForEntity(CPed* ped); static void ClearBlip(tBlipHandle blip); static void SetupAirstripBlips(); static void DrawBlips(); @@ -312,7 +309,7 @@ class CRadar { static auto CachedRotateClockwise(const CVector2D& point) { return CVector2D{ - cachedCos * point.x + cachedSin * point.y, + +cachedCos * point.x + cachedSin * point.y, -cachedSin * point.x + cachedCos * point.y }; } diff --git a/source/game_sa/RealTimeShadowManager.cpp b/source/game_sa/RealTimeShadowManager.cpp index 4839657115..97c3b1ea61 100644 --- a/source/game_sa/RealTimeShadowManager.cpp +++ b/source/game_sa/RealTimeShadowManager.cpp @@ -72,6 +72,8 @@ void CRealTimeShadowManager::ReInit() { // 0x706AB0 void CRealTimeShadowManager::Update() { + ZoneScoped; + if (m_bInitialised && m_bNeedsReinit) { ReInit(); m_bNeedsReinit = false; diff --git a/source/game_sa/References.cpp b/source/game_sa/References.cpp index f2dd3d8549..ac4115710d 100644 --- a/source/game_sa/References.cpp +++ b/source/game_sa/References.cpp @@ -17,6 +17,8 @@ void CReferences::InjectHooks() { // 0x5719B0 void CReferences::Init() { + ZoneScoped; + pEmptyList = aRefs; // todo: do better diff --git a/source/game_sa/RegisteredCorona.cpp b/source/game_sa/RegisteredCorona.cpp index a15c7a792d..62ec0eafce 100644 --- a/source/game_sa/RegisteredCorona.cpp +++ b/source/game_sa/RegisteredCorona.cpp @@ -1 +1,18 @@ -#include "StdInc.h" \ No newline at end of file +#include "StdInc.h" + +#include "RegisteredCorona.h" + +//! Calculate the position to use for rendering +auto CRegisteredCorona::GetPosition() const -> CVector { + if (!m_pAttachedTo) { + return m_vPosn; + } + if (m_pAttachedTo->GetType() == ENTITY_TYPE_VEHICLE && m_pAttachedTo->AsVehicle()->IsSubBike()) { + return m_pAttachedTo->AsBike()->m_mLeanMatrix * m_vPosn; + } + return m_pAttachedTo->GetMatrix() * m_vPosn; +} + +auto CRegisteredCorona::CalculateIntensity(float scrZ, float farClip) const -> float { + return std::clamp(invLerp(farClip, farClip / 2.f, scrZ), 0.f, 1.f); +} diff --git a/source/game_sa/RegisteredCorona.h b/source/game_sa/RegisteredCorona.h index a8f9833ed9..c4d9e4ea8b 100644 --- a/source/game_sa/RegisteredCorona.h +++ b/source/game_sa/RegisteredCorona.h @@ -32,7 +32,7 @@ enum eCoronaFlareType : uint8 { FLARETYPE_NONE, FLARETYPE_SUN, FLARETYPE_HEADLIG class CRegisteredCorona { public: CVector m_vPosn; - uint32 m_dwId; // Should be unique for each corona. Address or something + uint32 m_dwId{}; // Should be unique for each corona. Address or something RwTexture* m_pTexture; // Pointer to the actual texture to be rendered float m_fSize; float m_fAngle; // left from III&VC @@ -41,7 +41,7 @@ class CRegisteredCorona { float m_fHeightAboveGround; float m_fFadeSpeed; // The speed the corona fades in and out CRGBA m_Color; - uint8 m_nFadeState; // Intensity that lags behind the given intenisty and fades out if the LOS is blocked + uint8 m_FadedIntensity; // Intensity that lags behind the given intenisty and fades out if the LOS is blocked uint8 m_bRegisteredThisFrame; // Has this corona been registered by game code this frame eCoronaFlareType m_nFlareType; uint8 m_bUsesReflection; @@ -50,12 +50,16 @@ class CRegisteredCorona { uint8 m_bJustCreated; // If this corona has been created this frame we won't delete it (It hasn't had the time to get its OffScreen cleared) uint8 m_bFlashWhileFading : 1; // Does the corona fade out when closer to cam uint8 m_bOnlyFromBelow : 1; // This corona is only visible if the camera is below it - uint8 m_bReflectionDelay : 1; // this corona Has Valid Height Above Ground + uint8 m_bHasValidHeightAboveGround : 1; // this corona Has Valid Height Above Ground uint8 m_bDrawWithWhiteCore : 1; // This corona rendered with a small white core. uint8 m_bAttached : 1; // This corona is attached to an entity. CEntity* m_pAttachedTo; + CRegisteredCorona() = default; + void Update(); + auto GetPosition() const -> CVector; + auto CalculateIntensity(float scrZ, float farClip) const -> float; }; VALIDATE_SIZE(CRegisteredCorona, 0x3C); diff --git a/source/game_sa/RenderBuffer.cpp b/source/game_sa/RenderBuffer.cpp index 73a412d721..0958cd9989 100644 --- a/source/game_sa/RenderBuffer.cpp +++ b/source/game_sa/RenderBuffer.cpp @@ -58,17 +58,27 @@ void StopStoring() { } } -// 0x707800 -void RenderStuffInBuffer() { +// NOTSA +void Render(RwPrimitiveType primType, RwMatrix* ltm, RwUInt32 /*RwIm3DTransformFlags*/ flags, bool isIndexed) { if (uiTempBufferVerticesStored) { - if (RwIm3DTransform(aTempBufferVertices, uiTempBufferVerticesStored, nullptr, rwIM3D_VERTEXUV)) { - RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, aTempBufferIndices, uiTempBufferIndicesStored); + if (RwIm3DTransform(aTempBufferVertices, uiTempBufferVerticesStored, ltm, flags)) { + if (isIndexed) { + assert(aTempBufferIndices); + RwIm3DRenderIndexedPrimitive(primType, aTempBufferIndices, uiTempBufferIndicesStored); + } else { + RwIm3DRenderPrimitive(primType); + } RwIm3DEnd(); } } ClearRenderBuffer(); } +// 0x707800 +void RenderStuffInBuffer() { + Render(rwPRIMTYPETRILIST, nullptr, rwIM3D_VERTEXUV); +} + // 0x707790 void ClearRenderBuffer() { uiTempBufferIndicesStored = 0; @@ -83,24 +93,43 @@ void RenderIfDoesntFit(int32 nIdxNeeded, int32 nVtxNeeded) { } // notsa -void PushVertex(CVector pos, CVector2D uv, CRGBA color) { +RwIm3DVertex* PushVertex(CVector pos, CRGBA color) { const auto vtx = &aTempBufferVertices[uiTempBufferVerticesStored++]; RwIm3DVertexSetPos(vtx, pos.x, pos.y, pos.z); RwIm3DVertexSetRGBA(vtx, color.r, color.g, color.b, color.a); + + return vtx; +} + +// notsa +RwIm3DVertex* PushVertex(CVector pos, CVector2D uv, CRGBA color) { + const auto vtx = PushVertex(pos, color); + RwIm3DVertexSetU(vtx, uv.x); RwIm3DVertexSetV(vtx, uv.y); + + return vtx; } // notsa -void PushIndex(RwImVertexIndex idx, bool useCurrentVtxAsBase) { - aTempBufferIndices[uiTempBufferIndicesStored++] = useCurrentVtxAsBase ? uiTempBufferVerticesStored + idx : idx; +void PushIndex(int32 idx, bool useCurrentVtxAsBase) { + idx = useCurrentVtxAsBase + ? (int32)uiTempBufferVerticesStored + idx + : idx; + assert(idx >= 0); + aTempBufferIndices[uiTempBufferIndicesStored++] = idx; } // notsa -void PushIndices(std::initializer_list idxs, bool useCurrentVtxAsBase) { +void PushIndices(std::initializer_list idxs, bool useCurrentVtxAsBase) { for (auto idx : idxs) { PushIndex(idx, useCurrentVtxAsBase); } } + +bool CanFitVertices(int32 nVtxNeeded) { + return uiTempBufferVerticesStored + nVtxNeeded <= VtxBufferSize; +} + }; // namespace RenderBuffer diff --git a/source/game_sa/RenderBuffer.hpp b/source/game_sa/RenderBuffer.hpp index 6c31a59a47..2ed653ae0c 100644 --- a/source/game_sa/RenderBuffer.hpp +++ b/source/game_sa/RenderBuffer.hpp @@ -38,6 +38,12 @@ void StopStoring(); */ void RenderStuffInBuffer(); +/*! +* @notsa +* @brief Render out the contents of the temporary buffer as specified by the arguments. Frequently inlined! +*/ +void Render(RwPrimitiveType primType, RwMatrix* ltm = nullptr, RwUInt32 /*RwIm3DTransformFlags*/ flags = 0, bool isIndexed = true); + /*! * @addr 0x707790 * @brief Reset the index/vertex buffer stored counters. @@ -53,20 +59,32 @@ void RenderIfDoesntFit(int32 nIdx, int32 nVtx); /* * @addr notsa -* @brief Push a vertex to the buffer. Not to be used with `StartStoring`! +* @brief Push a vertex to the buffer. Not to be used with `StartStoring`! Use if the VERTEXUV flag **IS NOT** used when calling `Render` */ -void PushVertex(CVector pos, CVector2D uv, CRGBA color); +RwIm3DVertex* PushVertex(CVector pos, CRGBA color); + +/* +* @addr notsa +* @brief Push a vertex to the buffer. Not to be used with `StartStoring`! Use if the VERTEXUV flag **IS** used when calling `Render` +*/ +RwIm3DVertex* PushVertex(CVector pos, CVector2D uv, CRGBA color); /*! * @addr notsa * @brief Push an index into the buffer. Not to be used with `StartStoring`! */ -void PushIndex(RwImVertexIndex idx, bool useCurrentVtxAsBase); +void PushIndex(int32 idx, bool useCurrentVtxAsBase); /*! * @addr notsa * @brief Push multiple indices into the buffer. Not to be used with `StartStoring`! */ -void PushIndices(std::initializer_list idxs, bool useCurrentVtxAsBase); +void PushIndices(std::initializer_list idxs, bool useCurrentVtxAsBase); + +/*! +* @addr notsa +* @brief Check if the buffer can fit `nVtxNeeded` vertices +*/ +bool CanFitVertices(int32 nVtxNeeded); }; // namespace RenderBuffer diff --git a/source/game_sa/RenderWare/D3DResourceSystem.cpp b/source/game_sa/RenderWare/D3DResourceSystem.cpp index d0011ff227..7352872a07 100644 --- a/source/game_sa/RenderWare/D3DResourceSystem.cpp +++ b/source/game_sa/RenderWare/D3DResourceSystem.cpp @@ -29,6 +29,8 @@ void D3DResourceSystem::Init() { // 0x730AC0 void D3DResourceSystem::SetUseD3DResourceBuffering(bool bUse) { + ZoneScoped; + plugin::Call<0x730AC0, bool>(bUse); } diff --git a/source/game_sa/RenderWare/rw/rwcore.cpp b/source/game_sa/RenderWare/rw/rwcore.cpp index 45359a3eda..b698a7eff4 100644 --- a/source/game_sa/RenderWare/rw/rwcore.cpp +++ b/source/game_sa/RenderWare/rw/rwcore.cpp @@ -294,6 +294,7 @@ RwRaster* RwRasterShowRaster(RwRaster* raster, void* dev, RwUInt32 flags) { return ((RwRaster*(__cdecl *)(RwRaster*, void*, RwUInt32))0x7FB1A0)(raster, dev, flags); } +//! NOTE: This function is responsible for calculating the `stride` in the `raster` RwUInt8* RwRasterLock(RwRaster* raster, RwUInt8 level, RwInt32 lockMode) { return ((RwUInt8*(__cdecl *)(RwRaster*, RwUInt8, RwInt32))0x7FB2D0)(raster, level, lockMode); } @@ -1056,10 +1057,14 @@ RwBool RwBBoxContainsPoint(const RwBBox* boundBox, const RwV3d* vertex) { } RwCamera* RwCameraBeginUpdate(RwCamera* camera) { + ZoneScoped; + return ((RwCamera*(__cdecl *)(RwCamera*))0x7EE190)(camera); } RwCamera* RwCameraEndUpdate(RwCamera* camera) { + ZoneScoped; + return ((RwCamera*(__cdecl *)(RwCamera*))0x7EE180)(camera); } diff --git a/source/game_sa/RenderWare/rw/rwplcore.cpp b/source/game_sa/RenderWare/rw/rwplcore.cpp index 79a7401199..393c24f8ac 100644 --- a/source/game_sa/RenderWare/rw/rwplcore.cpp +++ b/source/game_sa/RenderWare/rw/rwplcore.cpp @@ -378,8 +378,8 @@ RwInt32 RwEngineGetNumVideoModes() { return ((RwInt32(__cdecl *)(void))0x7F2CC0)(); } -RwVideoMode* RwEngineGetVideoModeInfo(RwVideoMode* modeinfo, RwInt32 modeIndex) { - return ((RwVideoMode*(__cdecl *)(RwVideoMode*, RwInt32))0x7F2CF0)(modeinfo, modeIndex); +RwVideoMode RwEngineGetVideoModeInfo(RwInt32 modeIndex) { + return ((RwVideoMode(__cdecl *)(RwInt32))0x7F2CF0)(modeIndex); } RwInt32 RwEngineGetCurrentVideoMode() { diff --git a/source/game_sa/RenderWare/rw/rwplcore.h b/source/game_sa/RenderWare/rw/rwplcore.h index c8d23f1dfb..506f07c7d5 100644 --- a/source/game_sa/RenderWare/rw/rwplcore.h +++ b/source/game_sa/RenderWare/rw/rwplcore.h @@ -5329,7 +5329,7 @@ RwSubSystemInfo* RwEngineGetSubSystemInfo(RwSubSystemInfo* subSystemInfo, RwInt3 RwInt32 RwEngineGetCurrentSubSystem(); // 0x7F2C60 RwBool RwEngineSetSubSystem(RwInt32 subSystemIndex); // 0x7F2C90 RwInt32 RwEngineGetNumVideoModes(); // 0x7F2CC0 -RwVideoMode* RwEngineGetVideoModeInfo(RwVideoMode* modeinfo, RwInt32 modeIndex); // 0x7F2CF0 +RwVideoMode RwEngineGetVideoModeInfo(RwInt32 modeIndex); // 0x7F2CF0 RwInt32 RwEngineGetCurrentVideoMode(); // 0x7F2D20 RwBool RwEngineSetVideoMode(RwInt32 modeIndex); // 0x7F2D50 RwInt32 RwEngineGetTextureMemorySize(); // 0x7F2D80 diff --git a/source/game_sa/Renderer.cpp b/source/game_sa/Renderer.cpp index 0b35358053..97e6bb4622 100644 --- a/source/game_sa/Renderer.cpp +++ b/source/game_sa/Renderer.cpp @@ -295,6 +295,8 @@ void CRenderer::ProcessLodRenderLists() { // 0x553910 void CRenderer::PreRender() { + ZoneScoped; + assert(ms_nNoOfVisibleLods <= MAX_VISIBLE_LOD_PTRS); std::ranges::for_each(GetVisibleLodPtrs(), [](auto& entity) { entity->PreRender(); }); @@ -418,9 +420,11 @@ void CRenderer::RenderEverythingBarRoads() { // 0x553D00 void CRenderer::RenderFirstPersonVehicle() { + ZoneScoped; + if (m_pFirstPersonVehicle) { bool bRestoreAlphaTest = false; - if (FindPlayerPed(0)->GetActiveWeapon().m_nType == WEAPON_MICRO_UZI) { + if (FindPlayerPed(0)->GetActiveWeapon().m_Type == WEAPON_MICRO_UZI) { bRestoreAlphaTest = true; RwRenderStateSet(rwRENDERSTATEALPHATESTFUNCTIONREF, RWRSTATE(80u)); } @@ -960,6 +964,8 @@ void CRenderer::ScanPtrList_RequestModels(CPtrList& list) { // 0x5556E0 void CRenderer::ConstructRenderList() { + ZoneScoped; + const auto& camPos = TheCamera.GetPosition(); eZoneAttributes zoneAttributes = CCullZones::FindTunnelAttributesForCoors(camPos); diff --git a/source/game_sa/Replay.cpp b/source/game_sa/Replay.cpp index 376e5d6140..ac4391c4ad 100644 --- a/source/game_sa/Replay.cpp +++ b/source/game_sa/Replay.cpp @@ -83,6 +83,8 @@ void CReplay::Init() { // 0x460500 void CReplay::Update() { + ZoneScoped; + if (CCutsceneMgr::IsCutsceneProcessing() || CPad::GetPad()->ArePlayerControlsDisabled() || FrontEndMenuManager.m_bMenuActive || CEntryExitManager::ms_exitEnterState) { Init(); @@ -1524,13 +1526,13 @@ void CReplay::TriggerPlayback(eReplayCamMode mode, CVector fixedCamPos, bool loa bAllowLookAroundCam = true; FramesActiveLookAroundCam = 0; - OldRadioStation = [&]() -> int8 { + OldRadioStation = [&]() -> eRadioID { if (FindPlayerVehicle()) { AudioEngine.StopRadio(nullptr, false); return AERadioTrackManager.GetCurrentRadioStationID(); } - return 0; + return RADIO_EMERGENCY_AA; // Possibly not intended. }(); CurrArea = CGame::currArea; diff --git a/source/game_sa/Replay.h b/source/game_sa/Replay.h index 68aeb01c91..97ffaeabf6 100644 --- a/source/game_sa/Replay.h +++ b/source/game_sa/Replay.h @@ -236,7 +236,7 @@ class CReplay { inline static CVector_Reversed& LoadScene = *reinterpret_cast(0x97FAC0); inline static eReplayCamMode& CameraMode = *reinterpret_cast(0x97FB30); inline static int32& FramesActiveLookAroundCam = *reinterpret_cast(0x97FAD0); - inline static int8& OldRadioStation = *reinterpret_cast(0x97FABC); + inline static eRadioID& OldRadioStation = *reinterpret_cast(0x97FABC); inline static int8& CurrArea = *reinterpret_cast(0x97FAB8); inline static std::array& BufferStatus = *reinterpret_cast*>(0x97FB7C); diff --git a/source/game_sa/Restart.cpp b/source/game_sa/Restart.cpp index 4225d476c8..2dfcfa2233 100644 --- a/source/game_sa/Restart.cpp +++ b/source/game_sa/Restart.cpp @@ -27,6 +27,8 @@ void CRestart::InjectHooks() { // 0x460630 void CRestart::Initialise() { + ZoneScoped; + for (auto i = 0u; i < MAX_RESTART_POINTS; i++) { HospitalRestartHeadings[i] = 0.0f; HospitalRestartPoints[i] = CVector{}; diff --git a/source/game_sa/RoadBlocks.cpp b/source/game_sa/RoadBlocks.cpp index fa7b333ba3..2486fcd9cc 100644 --- a/source/game_sa/RoadBlocks.cpp +++ b/source/game_sa/RoadBlocks.cpp @@ -43,6 +43,8 @@ void CRoadBlocks::GenerateRoadBlockCopsForCar(CVehicle* vehicle, int32 pedsPosit // 0x4629E0 void CRoadBlocks::GenerateRoadBlocks() { + ZoneScoped; + plugin::Call<0x4629E0>(); } diff --git a/source/game_sa/Ropes.cpp b/source/game_sa/Ropes.cpp index fcad28eb08..c81b3f5572 100644 --- a/source/game_sa/Ropes.cpp +++ b/source/game_sa/Ropes.cpp @@ -50,6 +50,8 @@ void CRopes::Shutdown() { // 0x558D70 void CRopes::Update() { + ZoneScoped; + if (CReplay::Mode == MODE_PLAYBACK) return; @@ -61,6 +63,8 @@ void CRopes::Update() { // 0x556AE0 void CRopes::Render() { + ZoneScoped; + for (auto& rope : aRopes) { if (rope.m_nType != eRopeType::NONE) rope.Render(); diff --git a/source/game_sa/Scripts/CommandParser/Parser.hpp b/source/game_sa/Scripts/CommandParser/Parser.hpp index 533ce24249..6088998bba 100644 --- a/source/game_sa/Scripts/CommandParser/Parser.hpp +++ b/source/game_sa/Scripts/CommandParser/Parser.hpp @@ -59,19 +59,18 @@ inline OpcodeResult CollectArgsAndCall(CRunningScript* S, eScriptCommands comman } } -//! Called for unimplemented commands //! That is, ones that aren't used anywhere. //! If this ever gets called, that means that the command is used after all, and shouldn't be hooked as unimplemented. -inline auto NotImplemented(eScriptCommands cmd) { - DEV_LOG("Unimplemented command has been called! [ID: {:04X}; Name: {}]", (unsigned)(cmd), GetScriptCommandName(cmd)); - NOTSA_DEBUGBREAK(); // Something went horribly wrong here, and the game will crash after this, so better stop here. +inline auto NotImplemented(CRunningScript& S, eScriptCommands cmd) { + DEV_LOG("[{}][IP: {:#x} + {:#x}]: Unimplemented command has been called! [ID: {:04X}; Name: {}]", S.m_szName, LOG_PTR(S.m_pBaseIP), LOG_PTR(S.m_IP - S.m_pBaseIP), (unsigned)(cmd), GetScriptCommandName(cmd)); + NOTSA_DEBUGBREAK(); // Something went horribly wrong here, and the game will crash after this [if the function is supposed to take/return any arguments], so better stop here. return OR_INTERRUPT; // Vanilla SA behavior } template inline OpcodeResult CommandParser(CRunningScript* S) { return detail::CollectArgsAndCall(S, Command, CommandFn); -} +} template inline void AddCommandHandler() { diff --git a/source/game_sa/Scripts/CommandParser/ReadArg.hpp b/source/game_sa/Scripts/CommandParser/ReadArg.hpp index f11807706f..1be4080f07 100644 --- a/source/game_sa/Scripts/CommandParser/ReadArg.hpp +++ b/source/game_sa/Scripts/CommandParser/ReadArg.hpp @@ -79,9 +79,6 @@ concept PooledType = }; namespace detail { -//! Check if the type is an integer type excluding bool and character types. -template -inline constexpr bool is_standard_integer = std::is_integral_v && !is_any_of_type_v; //! Safely cast one arithmetic type to another (Checks for under/overflow in debug mode only), then casts to `T` template diff --git a/source/game_sa/Scripts/CommandParser/Utility.hpp b/source/game_sa/Scripts/CommandParser/Utility.hpp index 5a8b3badf6..bf1dddd81c 100644 --- a/source/game_sa/Scripts/CommandParser/Utility.hpp +++ b/source/game_sa/Scripts/CommandParser/Utility.hpp @@ -125,7 +125,4 @@ inline auto GetPedOrItsVehicle(CPed& ped) -> CPhysical& { template using nth_element_t = typename std::tuple_element>::type; -template -concept is_any_of_type_v = (std::same_as || ...); - }; // notsa diff --git a/source/game_sa/Scripts/Commands/Character.cpp b/source/game_sa/Scripts/Commands/Character.cpp index 4d96ec60ad..6bedb76e77 100644 --- a/source/game_sa/Scripts/Commands/Character.cpp +++ b/source/game_sa/Scripts/Commands/Character.cpp @@ -705,7 +705,7 @@ auto IsCurrentCharWeapon(CPed& ped, eWeaponType wep) { if (wep == WEAPON_ANYMELEE && ped.GetActiveWeapon().IsTypeMelee()) { return true; } - return ped.GetActiveWeapon().m_nType == wep; + return ped.GetActiveWeapon().m_Type == wep; } // GET_RANDOM_CHAR_IN_ZONE @@ -814,7 +814,7 @@ auto RemoveCharElegantly(CRunningScript& S, CPed* ped) { ped->SetCharCreatedBy(PED_GAME); if (const auto grp = ped->GetGroup()) { if (grp->GetMembership().IsFollower(ped)) { // TODO: Most likely inlined, this check makes no sense otherwise - grp->GetMembership().RemoveMember(*ped); + grp->GetMembership().RemoveMember(ped); } } CPopulation::ms_nTotalMissionPeds--; @@ -882,8 +882,8 @@ auto SetCharMoney(CPed& ped, int16 money) { // GET_AMMO_IN_CHAR_WEAPON auto GetAmmoInCharWeapon(CPed& ped, eWeaponType wtype) -> uint32 { for (auto& wep : ped.m_aWeapons) { - if (wep.m_nType == wtype) { // Originally they continued looping, but that doesn't make sense (A ped can't have the same weapon _twice_) - return wep.m_nTotalAmmo; + if (wep.m_Type == wtype) { // Originally they continued looping, but that doesn't make sense (A ped can't have the same weapon _twice_) + return wep.m_TotalAmmo; } } return 0; @@ -947,7 +947,7 @@ auto ClearCharLastWeaponDamage(CPed& ped) { // GET_CURRENT_CHAR_WEAPON auto GetCurrentCharWeapon(CPed& ped) { - return ped.GetActiveWeapon().m_nType; + return ped.GetActiveWeapon().m_Type; } // CAN_CHAR_SEE_DEAD_CHAR @@ -976,7 +976,7 @@ auto RemoveAllCharWeapons(CPed& ped) { // HAS_CHAR_GOT_WEAPON auto HasCharGotWeapon(CPed& ped, eWeaponType wtype) { - return notsa::contains(ped.m_aWeapons, wtype, [](CWeapon& w) { return w.m_nType; }); + return notsa::contains(ped.m_aWeapons, wtype, [](CWeapon& w) { return w.m_Type; }); } // GET_DEAD_CHAR_PICKUP_COORDS @@ -1018,7 +1018,7 @@ auto IsCharInWater(CPed* ped) { // GET_CHAR_WEAPON_IN_SLOT auto GetCharWeaponInSlot(CPed& ped, eWeaponSlot slut) { const auto& wep = ped.GetWeaponInSlot(slut); - return notsa::script::return_multiple(wep.m_nType, wep.m_nTotalAmmo, CPickups::ModelForWeapon(wep.m_nType)); + return notsa::script::return_multiple(wep.m_Type, wep.m_TotalAmmo, CPickups::ModelForWeapon(wep.m_Type)); } // GET_OFFSET_FROM_CHAR_IN_WORLD_COORDS @@ -1311,7 +1311,7 @@ auto GetCharModel(CPed& ped) { // SET_CURRENT_CHAR_WEAPON void SetCurrentCharWeapon(CPed& ped, eWeaponType weaponType) { for (auto&& [slot, weapon] : notsa::enumerate(ped.m_aWeapons)) { - if (weapon.m_nType != weaponType) + if (weapon.m_Type != weaponType) continue; if (ped.IsPlayer()) { @@ -1344,7 +1344,7 @@ bool IsCharInSearchlight(uint32 searchLightIdx, CPed& ped) { // REMOVE_CHAR_FROM_GROUP void RemoveCharFromGroup(CPed& ped) { if (auto pedGroup = ped.GetGroup(); pedGroup && !pedGroup->GetMembership().IsLeader(&ped)) { - pedGroup->GetMembership().RemoveMember(ped); + pedGroup->GetMembership().RemoveMember(&ped); pedGroup->Process(); } } diff --git a/source/game_sa/Scripts/Commands/Player.cpp b/source/game_sa/Scripts/Commands/Player.cpp index 54ab8d4fb2..79ab5c887e 100644 --- a/source/game_sa/Scripts/Commands/Player.cpp +++ b/source/game_sa/Scripts/Commands/Player.cpp @@ -196,7 +196,6 @@ void notsa::script::commands::player::RegisterHandlers() { REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_GANG_PLAYER_ATTITUDE); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_IS_PLAYER_IN_REMOTE_MODE); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_ANIM_GROUP_FOR_PLAYER); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_RESET_NUM_OF_MODELS_KILLED_BY_PLAYER); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_GET_NUM_OF_MODELS_KILLED_BY_PLAYER); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_CAR_ONLY_DAMAGED_BY_PLAYER); //REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_NEVER_GETS_TIRED); @@ -234,7 +233,6 @@ void notsa::script::commands::player::RegisterHandlers() { REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_MOOD); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_IS_PLAYER_WEARING); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_CAN_DO_DRIVE_BY); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_DRUNKENNESS); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_GET_PLAYER_DRUNKENNESS); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_DRUG_LEVEL); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_GET_PLAYER_DRUG_LEVEL); @@ -243,19 +241,11 @@ void notsa::script::commands::player::RegisterHandlers() { REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_CHECK_FOR_PED_MODEL_AROUND_PLAYER); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_HAS_MET_DEBBIE_HARRY); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_MAKE_PLAYER_FIRE_PROOF); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_INCREASE_PLAYER_MAX_HEALTH); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_INCREASE_PLAYER_MAX_ARMOUR); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_ENSURE_PLAYER_HAS_DRIVE_BY_WEAPON); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_GET_BUS_FARES_COLLECTED_BY_PLAYER); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_IS_PLAYER_IN_INFO_ZONE); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_GANG_ATTACK_PLAYER_WITH_COPS); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_IS_PLAYER_IN_SHORTCUT_TAXI); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_TASK_PLAYER_ON_FOOT); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_TASK_PLAYER_IN_CAR); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_IS_PLAYER_TARGETTING_ANYTHING); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_GET_CLOSEST_BUYABLE_OBJECT_TO_PLAYER); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_DISABLE_PLAYER_SPRINT); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_DELETE_PLAYER); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_TWO_PLAYER_CAMERA_MODE); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_LIMIT_TWO_PLAYER_DISTANCE); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_RELEASE_TWO_PLAYER_DISTANCE); @@ -271,7 +261,6 @@ void notsa::script::commands::player::RegisterHandlers() { REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_TWO_PLAYER_CAM_MODE_SAME_CAR_SHOOTING); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_TWO_PLAYER_CAM_MODE_SAME_CAR_NO_SHOOTING); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_TWO_PLAYER_CAM_MODE_NOT_BOTH_IN_CAR); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_ENABLE_ENTRY_EXIT_PLAYER_GROUP_WARPING); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_OBJECT_ONLY_DAMAGED_BY_PLAYER); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_FIRE_BUTTON); //REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_IS_PLAYER_IN_POSITION_FOR_CONVERSATION); diff --git a/source/game_sa/Scripts/RunningScript.cpp b/source/game_sa/Scripts/RunningScript.cpp index 92149971dd..6e71e69bfd 100644 --- a/source/game_sa/Scripts/RunningScript.cpp +++ b/source/game_sa/Scripts/RunningScript.cpp @@ -4,6 +4,11 @@ #include "TheScripts.h" #include "CarGenerator.h" #include "Hud.h" +#include "spdlog/sinks/stdout_color_sinks.h" + +static notsa::log_ptr logger; + +//static auto logger = NOTSA_MAKE_LOGGER("script"); //! Define it to dump out all commands that don't have a custom handler (that is, they aren't reversed) //! Makes compilation slow, so don't enable unless necessary! @@ -29,6 +34,8 @@ static inline std::array s_CustomCommandHandlerTable{}; void CRunningScript::InjectHooks() { + logger = NOTSA_MAKE_LOGGER("script"); + InjectCustomCommandHooks(); RH_ScopedClass(CRunningScript); @@ -909,6 +916,8 @@ OpcodeResult CRunningScript::ProcessOneCommand() { }; } op = { CTheScripts::Read2BytesFromScript(m_IP) }; + SPDLOG_LOGGER_TRACE(logger, "[{}][IP: {:#x} + {:#x}]: {} [{:#x}]", m_szName, LOG_PTR(m_pBaseIP), LOG_PTR(m_IP - m_pBaseIP), notsa::script::GetScriptCommandName((eScriptCommands)op.command), (size_t)op.command); + m_bNotFlag = op.notFlag; if (const auto handler = CustomCommandHandlerOf((eScriptCommands)(op.command))) { diff --git a/source/game_sa/Scripts/TheScripts.cpp b/source/game_sa/Scripts/TheScripts.cpp index de45c6b451..376e3fe5c6 100644 --- a/source/game_sa/Scripts/TheScripts.cpp +++ b/source/game_sa/Scripts/TheScripts.cpp @@ -7,6 +7,7 @@ #include "PedGroups.h" #include "Checkpoint.h" #include "Checkpoints.h" +#include "LoadingScreen.h" // #include "Scripted2dEffects.h" #include "Shadows.h" @@ -28,6 +29,7 @@ void CTheScripts::InjectHooks() { RH_ScopedInstall(IsPedStopped, 0x486110); RH_ScopedInstall(HasCarModelBeenSuppressed, 0x46A810); RH_ScopedInstall(HasVehicleModelBeenBlockedByScript, 0x46A890); + RH_ScopedInstall(Process, 0x46A000); } // 0x468D50 @@ -367,19 +369,81 @@ bool CTheScripts::HasVehicleModelBeenBlockedByScript(eModelID carModelId) { // 0x464D40 void CTheScripts::StartTestScript() { + ZoneScoped; + StartNewScript(MainSCMBlock); } // 0x46A000 void CTheScripts::Process() { - plugin::Call<0x46A000>(); + ZoneScoped; + + if (CReplay::Mode == MODE_PLAYBACK) { + return; + } + + CommandsExecuted = 0; + + UpsideDownCars.UpdateTimers(); + StuckCars.Process(); + MissionCleanUp.CheckIfCollisionHasLoadedForMissionObjects(); + CTheScripts::DrawScriptSpheres(); + CTheScripts::ProcessAllSearchLights(); + CTheScripts::ProcessWaitingForScriptBrainArray(); + + if (CTheScripts::FailCurrentMission) { + --CTheScripts::FailCurrentMission; + } + + if (CTheScripts::UseTextCommands) { + rng::fill(IntroTextLines, tScriptText{}); + NumberOfIntroTextLinesThisFrame = 0; + + rng::fill(IntroRectangles, tScriptRectangle{}); + NumberOfIntroRectanglesThisFrame = 0; + + CTheScripts::UseTextCommands = false; + } + + const auto timeStepMS = (int32)CTimer::GetTimeStepInMS(); + LocalVariablesForCurrentMission[32].iParam += timeStepMS; + LocalVariablesForCurrentMission[33].iParam += timeStepMS; + + CLoadingScreen::NewChunkLoaded(); + + for (auto it = pActiveScripts; it;) { + const auto next = it->m_pNext; + + for (auto& t : it->m_anTimers) { + t += timeStepMS; + } + it->Process(); + + it = next; + } + + CLoadingScreen::NewChunkLoaded(); + + for (auto& ped : GetPedPool()->GetAllValid()) { + if (ped.IsCreatedByMission()) { + ped.GetIntelligence()->RecordEventForScript(0, 0); + } + } } // 0x4939F0 void CTheScripts::ProcessAllSearchLights() { + ZoneScoped; + return plugin::Call<0x4939F0>(); } +void CTheScripts::ProcessWaitingForScriptBrainArray() { + ZoneScoped; + + plugin::Call<0x46CF00>(); +} + // 0x4812D0 void CTheScripts::UndoEntityInvisibilitySettings() { plugin::Call<0x4812D0>(); @@ -530,8 +594,9 @@ void CTheScripts::ScriptDebugCircle2D(float x, float y, float width, float heigh } // 0x4810E0 -void CTheScripts::DrawScriptSpheres() -{ +void CTheScripts::DrawScriptSpheres() { + ZoneScoped; + return plugin::Call<0x4810E0>(); for (auto& script : ScriptSphereArray) { if (script.m_bUsed) { @@ -648,5 +713,7 @@ void CTheScripts::RenderTheScriptDebugLines() { // 0x493E30 void CTheScripts::RenderAllSearchLights() { + ZoneScoped; + return plugin::Call<0x493E30>(); } diff --git a/source/game_sa/Scripts/TheScripts.h b/source/game_sa/Scripts/TheScripts.h index 9460ccfc43..627ac19dd2 100644 --- a/source/game_sa/Scripts/TheScripts.h +++ b/source/game_sa/Scripts/TheScripts.h @@ -124,51 +124,28 @@ struct tScriptSequence { VALIDATE_SIZE(tScriptSequence, 0x4); struct tScriptText { - float m_fLetterWidth; - float m_fLetterHeight; - CRGBA m_Color; - bool m_bJustify; - bool m_bCentered; - bool m_bWithBackground; - bool m_bUnk; - float m_fLineHeight; - float m_fLineWidth; - CRGBA m_BackgroundBoxColor; - bool m_bProportional; - CRGBA m_BackgroundColor; - int8 m_nShadowType; - int8 m_nOutlineType; - bool m_bDrawBeforeFade; - bool m_bRightJustify; - int32 m_nFont; - CVector2D m_Pos; - char m_szGxtEntry[8]; - int32 param1; - int32 param2; - - tScriptText() { // 0x4690A8 - m_fLetterWidth = 0.48f; - m_fLetterHeight = 1.12f; - m_Color = CRGBA(225, 225, 225, 255); - m_fLineHeight = SCREEN_WIDTH; - m_fLineWidth = SCREEN_WIDTH; - m_bJustify = false; - m_bRightJustify = false; - m_bCentered = false; - m_bWithBackground = false; - m_bUnk = false; - m_BackgroundBoxColor = CRGBA(128, 128, 128, 128); - m_bProportional = true; - m_BackgroundColor = CRGBA(0, 0, 0, 255); - m_nShadowType = 2; - m_nOutlineType = 0; - m_bDrawBeforeFade = false; - m_nFont = 1; - m_Pos = CVector2D(); - param1 = -1; - param2 = -1; - memset(&m_szGxtEntry, 0, sizeof(m_szGxtEntry)); - } + // values from 0x4690A8 + float m_fLetterWidth{ 0.48f }; + float m_fLetterHeight{ 1.12f }; + CRGBA m_Color{ 225, 225, 225, 255 }; + bool m_bJustify{ false }; + bool m_bCentered{ false }; + bool m_bWithBackground{ false }; + bool m_bUnk{ false }; + float m_fLineHeight{ SCREEN_HEIGHT }; + float m_fLineWidth{ SCREEN_WIDTH }; + CRGBA m_BackgroundBoxColor{ 128, 128, 128, 128 }; + bool m_bProportional{ true }; + CRGBA m_BackgroundColor{ 0, 0, 0, 255 }; + int8 m_nShadowType{ 2 }; + int8 m_nOutlineType{ 0 }; + bool m_bDrawBeforeFade{ false }; + bool m_bRightJustify{ false }; + int32 m_nFont{ 1 }; + CVector2D m_Pos{}; + char m_szGxtEntry[8]{}; + int32 param1{ -1 }; + int32 param2{ -1 }; }; VALIDATE_SIZE(tScriptText, 0x44); diff --git a/source/game_sa/SetPieces.cpp b/source/game_sa/SetPieces.cpp index 64e25bf5f7..bce0ec70ac 100644 --- a/source/game_sa/SetPieces.cpp +++ b/source/game_sa/SetPieces.cpp @@ -19,6 +19,8 @@ void CSetPieces::AddOne(uint8 type, CVector2D cornerA, CVector2D cornerB, CVecto // 0x4994F0 void CSetPieces::Init() { + ZoneScoped; + plugin::Call<0x4994F0>(); } @@ -34,5 +36,7 @@ bool CSetPieces::Save() { // 0x49AA00 void CSetPieces::Update() { + ZoneScoped; + plugin::Call<0x49AA00>(); } diff --git a/source/game_sa/ShadowCamera.cpp b/source/game_sa/ShadowCamera.cpp index 9046a4a381..332003bc82 100644 --- a/source/game_sa/ShadowCamera.cpp +++ b/source/game_sa/ShadowCamera.cpp @@ -211,19 +211,11 @@ RwCamera* CShadowCamera::Create(int32 rasterSizePower) { RpAtomic* atomicQuickRender(RpAtomic* atomic, void* data) { UNUSED(data); - // Save original callback - const auto origcb = RpAtomicGetRenderCallBack(atomic); + const auto original = RpAtomicGetRenderCallBack(atomic); - // Set default callback - RpAtomicSetRenderCallBack(atomic, &AtomicDefaultRenderCallBack); - - // NOTSA: Omitting useless `if` - It's condition always evals to `false` - Perhaps BUG? - - // Render using default - AtomicDefaultRenderCallBack(atomic); - - // Set original (if not null) or use default - RpAtomicSetRenderCallBack(atomic, origcb ? origcb : &AtomicDefaultRenderCallBack); + RpAtomicSetRenderCallBack(atomic, NULL); + RpAtomicRender(atomic); + RpAtomicSetRenderCallBack(atomic, original); return atomic; } diff --git a/source/game_sa/Shadows.cpp b/source/game_sa/Shadows.cpp index c29b0058ed..5a7e2b6fc3 100644 --- a/source/game_sa/Shadows.cpp +++ b/source/game_sa/Shadows.cpp @@ -138,6 +138,8 @@ void CShadows::AddPermanentShadow(uint8 type, RwTexture* texture, CVector* posn, // 0x70C950 void CShadows::UpdatePermanentShadows() { + ZoneScoped; + ((void(__cdecl*)())0x70C950)(); } diff --git a/source/game_sa/ShinyTexts.cpp b/source/game_sa/ShinyTexts.cpp index ace6ffbea5..dbd24585bd 100644 --- a/source/game_sa/ShinyTexts.cpp +++ b/source/game_sa/ShinyTexts.cpp @@ -37,6 +37,8 @@ void CShinyTexts::RenderOutGeometryBuffer() { // 0x724890 void CShinyTexts::Render() { + ZoneScoped; + if (NumShinyTexts == 0) return; diff --git a/source/game_sa/ShotInfo.cpp b/source/game_sa/ShotInfo.cpp index 19fbdab416..7335856a27 100644 --- a/source/game_sa/ShotInfo.cpp +++ b/source/game_sa/ShotInfo.cpp @@ -1,15 +1,38 @@ #include "StdInc.h" - #include "ShotInfo.h" +void CShotInfo::InjectHooks() { + RH_ScopedClass(CShotInfo); + RH_ScopedCategory(); + + RH_ScopedInstall(Initialise, 0x739B60, {.reversed = false}); + RH_ScopedInstall(Shutdown, 0x739C20, {.reversed = false}); + RH_ScopedInstall(AddShot, 0x739C30, {.reversed = false}); + RH_ScopedInstall(GetFlameThrowerShotPosn, 0x739DE0, {.reversed = false}); + RH_ScopedInstall(Update, 0x739E60, {.reversed = false}); +} + +// 0x739B60 void CShotInfo::Initialise() { - plugin::Call<0x739B60>(); + return plugin::CallAndReturn(); } +// 0x739C20 void CShotInfo::Shutdown() { - plugin::Call<0x739C20>(); + return plugin::CallAndReturn(); +} + +// 0x739C30 +bool CShotInfo::AddShot(CEntity* creator, eWeaponType weaponType, CVector origin, CVector target) { + return plugin::CallAndReturn(creator, weaponType, origin, target); +} + +// 0x739DE0 +bool CShotInfo::GetFlameThrowerShotPosn(uint8 shotId, CVector* pPosn) { + return plugin::CallAndReturn(shotId, pPosn); } +// 0x739E60 void CShotInfo::Update() { - plugin::Call<0x739E60>(); + return plugin::CallAndReturn(); } diff --git a/source/game_sa/ShotInfo.h b/source/game_sa/ShotInfo.h index 60aa7fd852..2ae994ceba 100644 --- a/source/game_sa/ShotInfo.h +++ b/source/game_sa/ShotInfo.h @@ -26,8 +26,9 @@ class CShotInfo { static float *ms_afRandTable; // static float ms_afRandTable[20] public: + static void InjectHooks(); + static void Initialise(); - // dummy function static void Shutdown(); static bool AddShot(CEntity* creator, eWeaponType weaponType, CVector origin, CVector target); static bool GetFlameThrowerShotPosn(uint8 shotId, CVector* outPos); diff --git a/source/game_sa/SimpleTransform.cpp b/source/game_sa/SimpleTransform.cpp index e406a99989..dddb64f719 100644 --- a/source/game_sa/SimpleTransform.cpp +++ b/source/game_sa/SimpleTransform.cpp @@ -9,17 +9,36 @@ #include "SimpleTransform.h" +// 0x54EF40 void CSimpleTransform::UpdateRwMatrix(RwMatrix* out) { - ((void(__thiscall*)(CSimpleTransform*, RwMatrix*))0x54EF40)(this, out); + const float sinHeading = std::sin(m_fHeading); + const float cosHeading = std::cos(m_fHeading); + + out->right = { cosHeading, sinHeading, 0.0f }; + out->up = { -sinHeading, cosHeading, 0.0f }; + out->at = { 0.0f, 0.0f, 1.0f }; + out->pos = m_vPosn; + + RwMatrixUpdate(out); + } +// 0x54EF90 void CSimpleTransform::Invert(const CSimpleTransform& base) { - ((void(__thiscall*)(CSimpleTransform*, const CSimpleTransform&))0x54EF90)(this, base); + const float cosHeading = cosf(base.m_fHeading); + const float sinHeading = sinf(base.m_fHeading); + + m_vPosn.x = -(cosHeading * base.m_vPosn.x) - (sinHeading * base.m_vPosn.y); + m_vPosn.y = (sinHeading * base.m_vPosn.x) - (cosHeading * base.m_vPosn.y); + m_vPosn.z = -base.m_vPosn.z; + m_fHeading = -base.m_fHeading; } +// 0x54F1B0 void CSimpleTransform::UpdateMatrix(CMatrix* out) { - ((void(__thiscall*)(CSimpleTransform*, class CMatrix*))0x54F1B0)(this, out); + out->SetTranslate(m_vPosn); + out->SetRotateZOnly(m_fHeading); } diff --git a/source/game_sa/Skidmarks.cpp b/source/game_sa/Skidmarks.cpp index 15d300e428..28abb93b72 100644 --- a/source/game_sa/Skidmarks.cpp +++ b/source/game_sa/Skidmarks.cpp @@ -54,6 +54,8 @@ void CSkidmarks::Clear() { // 0x7205C0 void CSkidmarks::Update() { + ZoneScoped; + for (CSkidmark& mark : m_aSkidmarks) { mark.Update(); } @@ -61,6 +63,8 @@ void CSkidmarks::Update() { // 0x720640 void CSkidmarks::Render() { + ZoneScoped; + RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(FALSE)); RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, RWRSTATE(TRUE)); RwRenderStateSet(rwRENDERSTATESRCBLEND, RWRSTATE(rwBLENDSRCALPHA)); diff --git a/source/game_sa/SpecialFX.cpp b/source/game_sa/SpecialFX.cpp index f9422e1810..ffff0a6185 100644 --- a/source/game_sa/SpecialFX.cpp +++ b/source/game_sa/SpecialFX.cpp @@ -1,5 +1,6 @@ #include "StdInc.h" - +#include "MotionBlurStreaks.h" +#include "Checkpoints.h" #include "SpecialFX.h" extern RwTexture*& gpFinishFlagTex; @@ -19,7 +20,7 @@ void CSpecialFX::InjectHooks() { RH_ScopedInstall(Update, 0x726AA0, { .reversed = false }); RH_ScopedInstall(Shutdown, 0x723390); //RH_ScopedInstall(AddWeaponStreak, 0x0, { .reversed = false }); - RH_ScopedInstall(Render, 0x726AD0, { .reversed = false }); + RH_ScopedInstall(Render, 0x726AD0); RH_ScopedInstall(Render2DFXs, 0x721660, { .reversed = false }); RH_ScopedInstall(ReplayStarted, 0x721D30); } @@ -31,6 +32,8 @@ void CSpecialFX::Init() { // 0x726AA0 void CSpecialFX::Update() { + ZoneScoped; + plugin::Call<0x726AA0>(); } @@ -52,13 +55,14 @@ void CSpecialFX::AddWeaponStreak(eWeaponType weaponType) { // 0x726AD0 void CSpecialFX::Render() { - plugin::Call<0x726AD0>(); -// CMotionBlurStreaks::Render(); -// CBulletTraces::Render(); -// CBrightLights::Render(); -// CShinyTexts::Render(); -// C3dMarkers::Render(); -// CCheckpoints::Render(); + ZoneScoped; + + CMotionBlurStreaks::Render(); + CBulletTraces::Render(); + CBrightLights::Render(); + CShinyTexts::Render(); + C3dMarkers::Render(); + CCheckpoints::Render(); } // 0x721660 diff --git a/source/game_sa/Sprite.cpp b/source/game_sa/Sprite.cpp index f63eee86ed..7d4d8fe8de 100644 --- a/source/game_sa/Sprite.cpp +++ b/source/game_sa/Sprite.cpp @@ -67,13 +67,13 @@ bool CSprite::CalcScreenCoors(const RwV3d& posn, RwV3d* out, float* w, float* h, if (out->z >= CDraw::GetFarClipZ() && checkMaxVisible) return false; - const float recip = 1.0f / out->z; + const float rd = 1.0f / out->z; // reciprocal of depth - out->x = SCREEN_WIDTH * recip * out->x; - out->y = SCREEN_HEIGHT * recip * out->y; + out->x = SCREEN_WIDTH * rd * out->x; + out->y = SCREEN_HEIGHT * rd * out->y; - *w = SCREEN_WIDTH * recip / CDraw::GetFOV() * 70.0f; - *h = SCREEN_HEIGHT * recip / CDraw::GetFOV() * 70.0f; + *w = SCREEN_WIDTH * rd / CDraw::GetFOV() * 70.0f; + *h = SCREEN_HEIGHT * rd / CDraw::GetFOV() * 70.0f; return true; } @@ -110,8 +110,8 @@ void CSprite::Set4Vertices2D(RwD3D9Vertex*, float, float, float, float, float, f /* --- XLU Sprite --- */ // 0x70D000 -void CSprite::RenderOneXLUSprite(float x, float y, float z, float halfWidth, float halfHeight, uint8 r, uint8 g, uint8 b, int16 a, float rhw, uint8 intensity, uint8 udir, uint8 vdir) { - plugin::Call<0x70D000, float, float, float, float, float, uint8, uint8, uint8, int16, float, uint8, uint8, uint8>(x, y, z, halfWidth, halfHeight, r, g, b, a, rhw, intensity, udir, vdir); +void CSprite::RenderOneXLUSprite(CVector pos, CVector2D halfSize, uint8 r, uint8 g, uint8 b, int16 intensity, float rhw, uint8 a, uint8 udir, uint8 vdir) { + plugin::Call<0x70D000>(pos, halfSize, r, g, b, intensity, rhw, a, udir, vdir); } // 0x70D320 @@ -120,8 +120,8 @@ void CSprite::RenderOneXLUSprite_Triangle(float, float, float, float, float, flo } // 0x70D490 -void CSprite::RenderOneXLUSprite_Rotate_Aspect(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8) { - assert(false); +void CSprite::RenderOneXLUSprite_Rotate_Aspect(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float rz, float rotation, uint8 alpha) { + plugin::Call<0x70D490>(pos, size, r, g, b, intensity, rz, rotation, alpha); } // Android @@ -148,8 +148,8 @@ void CSprite::RenderOneXLUSprite2D_Rotate_Dimension(float, float, float, float, /* --- Buffered XLU Sprite --- */ // 0x70E4A0 -void CSprite::RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, uint8 a11) { - plugin::Call<0x70E4A0, float, float, float, float, float, uint8, uint8, uint8, int16, float, uint8>(x, y, z, w, h, r, g, b, intensity, recipNearZ, a11); +void CSprite::RenderBufferedOneXLUSprite(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, uint8 a11) { + plugin::Call<0x70E4A0>(pos, size, r, g, b, intensity, recipNearZ, a11); } // 0x70E780 @@ -157,9 +157,8 @@ void CSprite::RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z plugin::Call<0x70E780, float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8>(x, y, z, w, h, r, g, b, intensity, recipNearZ, angle, a12); } -// 0x70EAB0 -void CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8) { - assert(false); +void CSprite::RenderBufferedOneXLUSprite_Rotate_Dimension(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float rz, float rotation, uint8 a) { + plugin::Call<0x70EAB0>(pos, size, r, g, b, intensity, rz, rotation, a); } // 0x70EDE0 @@ -168,8 +167,8 @@ void CSprite::RenderBufferedOneXLUSprite_Rotate_2Colours(float, float, float, fl } // 0x70F440 -void CSprite::RenderBufferedOneXLUSprite2D(float, float, float, float, const RwRGBA&, int16, uint8) { - assert(false); +void CSprite::RenderBufferedOneXLUSprite2D(CVector2D pos, CVector2D size, const RwRGBA& color, int16 intensity, uint8 alpha) { + plugin::Call<0x70F440>(pos, size, &color, intensity, alpha); } // unused diff --git a/source/game_sa/Sprite.h b/source/game_sa/Sprite.h index fd4710c7fa..585e2dcb7c 100644 --- a/source/game_sa/Sprite.h +++ b/source/game_sa/Sprite.h @@ -31,18 +31,18 @@ class CSprite { static void Set4Vertices2D(RwD3D9Vertex*, const CRect&, const CRGBA&, const CRGBA&, const CRGBA&, const CRGBA&); static void Set4Vertices2D(RwD3D9Vertex*, float, float, float, float, float, float, float, float, const CRGBA&, const CRGBA&, const CRGBA&, const CRGBA&); - static void RenderOneXLUSprite(float x, float y, float z, float halfWidth, float halfHeight, uint8 r, uint8 g, uint8 b, int16 a, float rhw, uint8 intensity, uint8 udir, uint8 vdir); + static void RenderOneXLUSprite(CVector pos, CVector2D halfSize, uint8 r, uint8 g, uint8 b, int16 intensity, float rhw, uint8 a, uint8 udir, uint8 vdir); static void RenderOneXLUSprite_Triangle(float, float, float, float, float, float, float, uint8, uint8, uint8, int16, float, uint8); - static void RenderOneXLUSprite_Rotate_Aspect(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8); + static void RenderOneXLUSprite_Rotate_Aspect(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float rz, float rotation, uint8 alpha); static void RenderOneXLUSprite_Rotate_Dimension(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8); static void RenderOneXLUSprite_Rotate_2Colours(float, float, float, float, float, uint8, uint8, uint8, uint8, uint8, uint8, float, float, float, float, uint8); static void RenderOneXLUSprite2D(float, float, float, float, const RwRGBA&, int16, uint8); static void RenderOneXLUSprite2D_Rotate_Dimension(float, float, float, float, const RwRGBA&, int16, float, uint8); - static void RenderBufferedOneXLUSprite(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, uint8 a11); + static void RenderBufferedOneXLUSprite(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, uint8 a11); static void RenderBufferedOneXLUSprite_Rotate_Aspect(float x, float y, float z, float w, float h, uint8 r, uint8 g, uint8 b, int16 intensity, float recipNearZ, float angle, uint8 a12); - static void RenderBufferedOneXLUSprite_Rotate_Dimension(float, float, float, float, float, uint8, uint8, uint8, int16, float, float, uint8); + static void RenderBufferedOneXLUSprite_Rotate_Dimension(CVector pos, CVector2D size, uint8 r, uint8 g, uint8 b, int16 intensity, float rz, float rotation, uint8 a); static void RenderBufferedOneXLUSprite_Rotate_2Colours(float, float, float, float, float, uint8, uint8, uint8, uint8, uint8, uint8, float, float, float, float, uint8); - static void RenderBufferedOneXLUSprite2D(float, float, float, float, const RwRGBA&, int16, uint8); + static void RenderBufferedOneXLUSprite2D(CVector2D pos, CVector2D size, const RwRGBA& color, int16 intensity, uint8 alpha); static void RenderBufferedOneXLUSprite2D_Rotate_Dimension(float, float, float, float, const RwRGBA&, int16, float, uint8); }; diff --git a/source/game_sa/Sprite2d.cpp b/source/game_sa/Sprite2d.cpp index bb6fc4275d..3bb07d3659 100644 --- a/source/game_sa/Sprite2d.cpp +++ b/source/game_sa/Sprite2d.cpp @@ -169,13 +169,14 @@ void CSprite2d::Draw(float x1, float y1, float x2, float y2, float x3, float y3, } // 0x727260 -void CSprite2d::SetRecipNearClip() -{ +void CSprite2d::SetRecipNearClip() { + ZoneScoped; // NOP } -void CSprite2d::InitPerFrame() -{ +void CSprite2d::InitPerFrame() { + ZoneScoped; + nextBufferVertex = 0; nextBufferIndex = 0; RecipNearClip = 1.0f / RwCameraGetNearClipPlane(Scene.m_pRwCamera); diff --git a/source/game_sa/Stats.cpp b/source/game_sa/Stats.cpp index 8ada5c3ce9..415a52dcf6 100644 --- a/source/game_sa/Stats.cpp +++ b/source/game_sa/Stats.cpp @@ -44,9 +44,9 @@ void CStats::InjectHooks() { RH_ScopedCategoryGlobal(); RH_ScopedInstall(Init, 0x55C0C0); - RH_ScopedInstall(GetStatValue, 0x558E40); + RH_ScopedOverloadedInstall(GetStatValue, "-OG", 0x558E40, float(*)(eStats)); RH_ScopedInstall(SetStatValue, 0x55A070); - RH_ScopedInstall(GetStatType, 0x558E30); + RH_ScopedInstall(IsStatFloat, 0x558E30); RH_ScopedInstall(GetFullFavoriteRadioStationList, 0x558F90); RH_ScopedInstall(FindCriminalRatingNumber, 0x559080); RH_ScopedInstall(GetPercentageProgress, 0x5591E0); @@ -113,7 +113,7 @@ void CStats::Init() { // 0x558E40 float CStats::GetStatValue(eStats stat) { - if (!GetStatType(stat)) { // int32 + if (!IsStatFloat(stat)) { // int32 assert(stat >= FIRST_INT_STAT); return static_cast(StatTypesInt[stat - FIRST_INT_STAT]); @@ -124,7 +124,7 @@ float CStats::GetStatValue(eStats stat) { // 0x55A070 void CStats::SetStatValue(eStats stat, float value) { - if (GetStatType(stat)) { + if (IsStatFloat(stat)) { StatTypesFloat[stat] = value; } else { // int32 assert(stat >= FIRST_INT_STAT); @@ -135,7 +135,7 @@ void CStats::SetStatValue(eStats stat, float value) { } // 0x558E30 -bool CStats::GetStatType(eStats stat) { +bool CStats::IsStatFloat(eStats stat) { return stat < FIRST_UNUSED_STAT; } @@ -150,8 +150,8 @@ int32* CStats::GetFullFavoriteRadioStationList() { } // 0x558FA0 -int32 CStats::FindMostFavoriteRadioStation() { - return plugin::CallAndReturn(); +eRadioID CStats::FindMostFavoriteRadioStation() { + return plugin::CallAndReturn(); } // 0x559010 @@ -508,7 +508,7 @@ void CStats::IncrementStat(eStats stat, float value) if (value <= 0.0f) return; - if (GetStatType(stat)) { // float + if (IsStatFloat(stat)) { // float StatTypesFloat[stat] += value; if (IsStatCapped(stat)) @@ -704,7 +704,7 @@ bool CStats::Load() { // Unused // 0x558DE0 char* CStats::GetStatID(eStats stat) { - if (!GetStatType(stat)) // int32 + if (!IsStatFloat(stat)) // int32 sprintf_s(gString, "stat_i_%d", stat); else sprintf_s(gString, "stat_f_%d", stat); diff --git a/source/game_sa/Stats.h b/source/game_sa/Stats.h index 87a04f32d4..b8acaa8789 100644 --- a/source/game_sa/Stats.h +++ b/source/game_sa/Stats.h @@ -43,7 +43,7 @@ class CStats { static uint32& TotalNumStatMessages; static char (&LastMissionPassedName)[8]; static int32 (&TimesMissionAttempted)[100]; - static int32 (&FavoriteRadioStationList)[14]; + static int32 (&FavoriteRadioStationList)[RADIO_COUNT]; static int32 (&PedsKilledOfThisType)[32]; static float (&StatReactionValue)[59]; static int32 (&StatTypesInt)[223]; @@ -71,14 +71,14 @@ class CStats { static void InjectHooks(); static char* GetStatID(eStats stat); - static bool GetStatType(eStats stat); + static bool IsStatFloat(eStats stat); static float GetStatValue(eStats stat); static int8 GetTimesMissionAttempted(uint8 missionId); static void RegisterMissionAttempted(uint8 missionId); static void RegisterMissionPassed(uint8 missionId); static bool PopulateFavoriteRadioStationList(); static int32* GetFullFavoriteRadioStationList(); - static int32 FindMostFavoriteRadioStation(); + static eRadioID FindMostFavoriteRadioStation(); static int32 FindLeastFavoriteRadioStation(); static int32 FindCriminalRatingNumber(); static float GetPercentageProgress(); @@ -122,4 +122,22 @@ class CStats { static void ModifyStat(eStats stat, float value); static bool Save(); static bool Load(); + + // NOTSA + template requires std::is_arithmetic_v + static T GetStatValue(eStats stat) { + if constexpr (std::is_integral_v) { + // NOTE: First func checks if it's not a float stat, allowing unused ones. + // Second check eliminates them as well. + assert(!IsStatFloat(stat) && stat >= FIRST_INT_STAT); + } + if constexpr (std::is_floating_point_v) { + assert(IsStatFloat(stat)); + } + const float r = CStats::GetStatValue(stat); + + // there shouldn't be any stat value bigger than int64::max. + assert(std::is_floating_point_v || std::in_range(static_cast(r))); + return static_cast(r); + } }; diff --git a/source/game_sa/StencilShadows.cpp b/source/game_sa/StencilShadows.cpp index b29c584c84..e3af4ed10a 100644 --- a/source/game_sa/StencilShadows.cpp +++ b/source/game_sa/StencilShadows.cpp @@ -16,10 +16,14 @@ RH_ScopedInstall(Process, 0x711D90, { .reversed = false }); // 0x70F9E0 void CStencilShadows::Init() { + ZoneScoped; + plugin::Call<0x70F9E0>(); } // 0x711D90 void CStencilShadows::Process(CVector& cameraPos) { + ZoneScoped; + plugin::Call<0x711D90, CVector&>(cameraPos); } diff --git a/source/game_sa/Streaming.cpp b/source/game_sa/Streaming.cpp index ff14c54f64..952b54c21a 100644 --- a/source/game_sa/Streaming.cpp +++ b/source/game_sa/Streaming.cpp @@ -384,6 +384,8 @@ int32 CStreaming::GetDiscInDrive() { // 0x408E20 int32 CStreaming::GetNextFileOnCd(uint32 streamLastPosn, bool bNotPriority) { + ZoneScoped; + uint32 nextRequestModelPos = UINT32_MAX; uint32 firstRequestModelCdPos = UINT32_MAX; int32 firstRequestModelId = MODEL_INVALID; @@ -1089,6 +1091,8 @@ bool CStreaming::Save() { // streaming channel within CStreaming::ms_channel which is 1 (second streaming channel). // 0x40EA10 void CStreaming::LoadAllRequestedModels(bool bOnlyPriorityRequests) { + ZoneScoped; + if (m_bLoadingAllRequestedModels) { return; } @@ -1154,8 +1158,10 @@ void CStreaming::LoadAllRequestedModels(bool bOnlyPriorityRequests) { // Load a directory (aka img file) // This will set the `CdSize, CdPosn, m_nImgId, m_nNextIndexOnCd` member variables of // each model present in the file (according to file name) -void CStreaming::LoadCdDirectory(const char* filename, int32 archiveId) -{ +void CStreaming::LoadCdDirectory(const char* filename, int32 archiveId) { + ZoneScoped; + ZoneText(filename, strlen(filename)); + auto* imgFile = CFileMgr::OpenFile(filename, "rb"); if (!imgFile) return; @@ -1638,6 +1644,8 @@ void CStreaming::FlushChannels() // Removes all unused (if not IsRequiredToBeKept()) IFP/TXDs models as well. // 0x40CBA0 void CStreaming::RequestModelStream(int32 chIdx) { + ZoneScoped; + int32 modelId = GetNextFileOnCd(CdStreamGetLastPosn(), true); if (modelId == MODEL_INVALID) return; @@ -2076,6 +2084,8 @@ void CStreaming::ReInit() { // Loads `stream.ini` settings file // 0x5BCCD0 void CStreaming::ReadIniFile() { + ZoneScoped; + bool bHasDevkitMemory = false; auto* file = CFileMgr::OpenFile("stream.ini", "r"); for (char* line = CFileLoader::LoadLine(file); line; line = CFileLoader::LoadLine(file)) { @@ -2846,6 +2856,8 @@ void CStreaming::Init() { // 0x5B8AD0 void CStreaming::Init2() { + ZoneScoped; + std::ranges::for_each(ms_aInfoForModel, [](CStreamingInfo& si) { si.Init(); }); CStreamingInfo::ms_pArrayBase = &GetInfo(0); @@ -2953,6 +2965,8 @@ void CStreaming::Init2() { // 0x4083C0 void CStreaming::InitImageList() { + ZoneScoped; + std::ranges::fill(ms_files, tStreamingFileDesc()); AddImageToList("MODELS\\GTA3.IMG", true); AddImageToList("MODELS\\GTA_INT.IMG", true); @@ -3374,7 +3388,7 @@ void CStreaming::StreamPedsForInterior(int32 interiorType) { // * -2 - Unload model from slot (If there's any) // * Positive values - Load given model into slot // 0x40BDA0 -void CStreaming::StreamPedsIntoRandomSlots(int32 modelArray[TOTAL_LOADED_PEDS]) { +void CStreaming::StreamPedsIntoRandomSlots(const int32(&modelArray)[TOTAL_LOADED_PEDS]) { for (int32 i = 0; i < TOTAL_LOADED_PEDS; i++) { if (modelArray[i] >= 0) { // Load model into slot @@ -3453,7 +3467,7 @@ void CStreaming::StreamVehiclesAndPeds() { if (CWeather::WeatherRegion == WEATHER_REGION_SF) { pedGroupId = POPCYCLE_PEDGROUP_BUSINESS_LA; } else if (CPopCycle::m_pCurrZoneInfo) { - const auto& currenZoneFlags = CPopCycle::m_pCurrZoneInfo->Flags2; + const auto& currenZoneFlags = CPopCycle::m_pCurrZoneInfo->zonePopulationRace; if (currenZoneFlags & 1) pedGroupId = 0; // POPCYCLE_PEDGROUP_WORKERS_LA ? else if (currenZoneFlags & 2) @@ -3735,6 +3749,8 @@ void CStreaming::StreamZoneModels_Gangs(const CVector& unused) { // 0x40E670 void CStreaming::Update() { + ZoneScoped; + g_LoadMonitor.m_numModelsRequest = ms_numModelsRequested; if (CTimer::GetIsPaused()) return; diff --git a/source/game_sa/Streaming.h b/source/game_sa/Streaming.h index 56e0df7e97..bba025da1b 100644 --- a/source/game_sa/Streaming.h +++ b/source/game_sa/Streaming.h @@ -370,7 +370,7 @@ class CStreaming { static bool StreamFireEngineAndFireman(bool bStreamForFire); static void StreamOneNewCar(); static void StreamPedsForInterior(int32 interiorType); - static void StreamPedsIntoRandomSlots(int32 modelArray[TOTAL_LOADED_PEDS]); + static void StreamPedsIntoRandomSlots(const int32 (&modelArray)[TOTAL_LOADED_PEDS]); static void StreamVehiclesAndPeds(); static void StreamVehiclesAndPeds_Always(const CVector& unused); static void StreamZoneModels(const CVector& unused); diff --git a/source/game_sa/StuckCarCheck.cpp b/source/game_sa/StuckCarCheck.cpp index 7a65d9e9fb..1034463ed0 100644 --- a/source/game_sa/StuckCarCheck.cpp +++ b/source/game_sa/StuckCarCheck.cpp @@ -69,6 +69,8 @@ bool CStuckCarCheck::IsCarInStuckCarArray(int32 carHandle) { // 0x465680 void CStuckCarCheck::Process() { + ZoneScoped; + plugin::CallMethod<0x465680, CStuckCarCheck*>(this); } diff --git a/source/game_sa/StuntJumpManager.cpp b/source/game_sa/StuntJumpManager.cpp index fbfc883bf3..d8293fb14e 100644 --- a/source/game_sa/StuntJumpManager.cpp +++ b/source/game_sa/StuntJumpManager.cpp @@ -20,6 +20,8 @@ void CStuntJumpManager::InjectHooks() { // 0x49CA50 void CStuntJumpManager::Init() { + ZoneScoped; + mp_poolStuntJumps = new CStuntJumpsPool(STUNT_JUMP_COUNT, "Stunt Jumps"); m_bActive = true; } @@ -85,6 +87,8 @@ void CStuntJumpManager::AddOne(const CBoundingBox& start, const CBoundingBox& en // 0x49C490 void CStuntJumpManager::Update() { + ZoneScoped; + if (!mp_poolStuntJumps || CReplay::Mode == MODE_PLAYBACK) return; diff --git a/source/game_sa/TagManager.cpp b/source/game_sa/TagManager.cpp index 18291504ed..40d2908fa1 100644 --- a/source/game_sa/TagManager.cpp +++ b/source/game_sa/TagManager.cpp @@ -35,8 +35,9 @@ void CTagManager::InjectHooks() } // 0x49CC50 -void CTagManager::Init() -{ +void CTagManager::Init() { + ZoneScoped; + ms_numTags = 0; ms_numTagged = 0; } diff --git a/source/game_sa/Tasks/Allocators/TaskAllocatorKillThreatsBasicRandomGroup.h b/source/game_sa/Tasks/Allocators/TaskAllocatorKillThreatsBasicRandomGroup.h index c0089f59c7..9a1274b2ca 100644 --- a/source/game_sa/Tasks/Allocators/TaskAllocatorKillThreatsBasicRandomGroup.h +++ b/source/game_sa/Tasks/Allocators/TaskAllocatorKillThreatsBasicRandomGroup.h @@ -4,6 +4,8 @@ class CTaskAllocatorKillThreatsBasicRandomGroup : public CTaskAllocatorKillThreatsBasic { public: + using CTaskAllocatorKillThreatsBasic::CTaskAllocatorKillThreatsBasic; + eTaskAllocatorType GetType() override { return TASK_ALLOCATOR_KILL_THREATS_BASIC_RANDOM_GROUP; }; // 0x5F68F0 void AllocateTasks(CPedGroupIntelligence* intel) override; }; diff --git a/source/game_sa/Tasks/Task.h b/source/game_sa/Tasks/Task.h index b63f1c662c..8f9894f442 100644 --- a/source/game_sa/Tasks/Task.h +++ b/source/game_sa/Tasks/Task.h @@ -33,12 +33,12 @@ class CTask { CTask() { m_pParentTask = nullptr; } // 0x61A340 virtual ~CTask() = default; // 0x61A660 - virtual CTask* Clone() = 0; + virtual CTask* Clone() const = 0; virtual CTask* GetSubTask() = 0; virtual bool IsSimple() = 0; - virtual eTaskType GetTaskType() = 0; + virtual eTaskType GetTaskType() const = 0; virtual void StopTimer(const CEvent* event); - virtual bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) = 0; + virtual bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) = 0; static bool IsGoToTask(CTask* task); static bool IsTaskPtr(CTask* task); diff --git a/source/game_sa/Tasks/TaskComplex.h b/source/game_sa/Tasks/TaskComplex.h index c0bb1ac23a..63b3c1707a 100644 --- a/source/game_sa/Tasks/TaskComplex.h +++ b/source/game_sa/Tasks/TaskComplex.h @@ -20,11 +20,12 @@ class NOTSA_EXPORT_VTABLE CTaskComplex : public CTask { CTask* GetSubTask() override; bool IsSimple() override; - bool MakeAbortable(class CPed* ped, eAbortPriority priority, const CEvent* event) override; // Seems like priority defaults to `ABORT_PRIORITY_URGENT` and `event = nullptr` + bool MakeAbortable(class CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; virtual void SetSubTask(CTask* subTask); virtual CTask* CreateNextSubTask(CPed* ped) = 0; virtual CTask* CreateFirstSubTask(CPed* ped) = 0; virtual CTask* ControlSubTask(CPed* ped) = 0; + // #vtable: 11 }; VALIDATE_SIZE(CTaskComplex, 0xC); diff --git a/source/game_sa/Tasks/TaskComplexSequence.cpp b/source/game_sa/Tasks/TaskComplexSequence.cpp index 846f34b10f..d8be663a72 100644 --- a/source/game_sa/Tasks/TaskComplexSequence.cpp +++ b/source/game_sa/Tasks/TaskComplexSequence.cpp @@ -3,16 +3,16 @@ #include "TaskComplexSequence.h" void CTaskComplexSequence::InjectHooks() { - RH_ScopedClass(CTaskComplexSequence); + RH_ScopedVirtualClass(CTaskComplexSequence, 0x86e200, 11); RH_ScopedCategory("Tasks"); RH_ScopedInstall(Constructor, 0x632BD0); - RH_ScopedVirtualInstall(Clone, 0x5F6710); - RH_ScopedVirtualInstall(MakeAbortable, 0x632C00); - RH_ScopedVirtualInstall(CreateNextSubTask, 0x638A40); + RH_ScopedVMTInstall(Clone, 0x5F6710); + RH_ScopedVMTInstall(MakeAbortable, 0x632C00); + RH_ScopedVMTInstall(CreateNextSubTask, 0x638A40); + RH_ScopedVMTInstall(CreateFirstSubTask, 0x638A60); + RH_ScopedVMTInstall(ControlSubTask, 0x632D00); RH_ScopedOverloadedInstall(CreateNextSubTask, "ped", 0x632C70, CTask*(CTaskComplexSequence::*)(CPed*, int32&, int32&)); - RH_ScopedVirtualInstall(CreateFirstSubTask, 0x638A60); - RH_ScopedVirtualInstall(ControlSubTask, 0x632D00); RH_ScopedOverloadedInstall(AddTask, "0", 0x632D10, void(CTaskComplexSequence::*)(CTask*)); RH_ScopedOverloadedInstall(AddTask, "1", 0x632D50, void(CTaskComplexSequence::*)(int32, CTask*)); RH_ScopedInstall(Flush, 0x632C10); @@ -31,6 +31,16 @@ CTaskComplexSequence::CTaskComplexSequence() : CTaskComplex() { std::ranges::fill(m_aTasks, nullptr); } +// For 0x5F6710 +CTaskComplexSequence::CTaskComplexSequence(const CTaskComplexSequence& o) : + m_bRepeatSequence{o.m_bRepeatSequence}, + m_nCurrentTaskIndex{o.m_nCurrentTaskIndex} +{ + for (auto&& [i, t] : notsa::enumerate(o.m_aTasks)) { + m_aTasks[i] = t ? t->Clone() : nullptr; + } +} + // 0x6389F0 CTaskComplexSequence::~CTaskComplexSequence() { Flush(); @@ -53,11 +63,6 @@ CTaskComplexSequence* CTaskComplexSequence::Constructor() { return this; } -// 0x5F6710 -CTask* CTaskComplexSequence::Clone() { - return Clone_Reversed(); -} - // 0x632C00 bool CTaskComplexSequence::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) { return MakeAbortable_Reversed(ped, priority, event); @@ -78,17 +83,6 @@ CTask* CTaskComplexSequence::ControlSubTask(CPed* ped) { return ControlSubTask_Reversed(ped); } -CTask* CTaskComplexSequence::Clone_Reversed() { - auto* sequence = new CTaskComplexSequence(); - for (auto taskIndex = 0u; taskIndex < std::size(m_aTasks); taskIndex++) { - CTask* task = m_aTasks[taskIndex]; - sequence->m_aTasks[taskIndex] = task ? task->Clone() : nullptr; - } - sequence->m_bRepeatSequence = m_bRepeatSequence; - sequence->m_nCurrentTaskIndex = m_nCurrentTaskIndex; - return sequence; -} - bool CTaskComplexSequence::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event) { return m_pSubTask->MakeAbortable(ped, priority, event); } @@ -108,9 +102,9 @@ CTask* CTaskComplexSequence::ControlSubTask_Reversed(CPed* ped) { // 0x632D10 void CTaskComplexSequence::AddTask(CTask* task) { - for (auto& m_aTask : m_aTasks) { - if (!m_aTask) { - m_aTask = task; + for (auto& t : m_aTasks) { + if (!t) { + t = task; return; } } diff --git a/source/game_sa/Tasks/TaskComplexSequence.h b/source/game_sa/Tasks/TaskComplexSequence.h index 74a22c0679..82b72f7a15 100644 --- a/source/game_sa/Tasks/TaskComplexSequence.h +++ b/source/game_sa/Tasks/TaskComplexSequence.h @@ -4,24 +4,24 @@ class CTask; -class CTaskComplexSequence : public CTaskComplex { +class NOTSA_EXPORT_VTABLE CTaskComplexSequence : public CTaskComplex { public: - int32 m_nCurrentTaskIndex; // Used in m_aTasks - CTask* m_aTasks[8]; - bool m_bRepeatSequence; // Sequence will loop if set to 1 - int32 m_nSequenceRepeatedCount; // m_nSequenceRepeatedCount simply tells us how many times the sequence has been repeated. - // If m_bRepeatSequence is true, this can be greater than 1, - // otherwise it's set to 1 when the sequence is done executing tasks. - bool m_bFlushTasks; - uint32 m_nReferenceCount; // count of how many CTaskComplexUseSequence instances are using this sequence + int32 m_nCurrentTaskIndex{}; //!< Used in m_aTasks + CTask* m_aTasks[8]{}; //!< The sequenced tasks + bool m_bRepeatSequence{}; //!< Sequence will loop if set to 1 + int32 m_nSequenceRepeatedCount{}; //!< m_nSequenceRepeatedCount simply tells us how many times the sequence has been repeated. + //!< If m_bRepeatSequence is true, this can be greater than 1, + //!< otherwise it's set to 1 when the sequence is done executing tasks. + bool m_bFlushTasks{}; + uint32 m_nReferenceCount{}; //!< count of how many CTaskComplexUseSequence instances are using this sequence public: static constexpr auto Type = TASK_COMPLEX_SEQUENCE; - /*! - * Construct using multiple tasks, same as constructing - * and then calling `AddTask` for every task passed in. - */ + //! Constructor + CTaskComplexSequence(); + + //! Construct using multiple tasks, same as constructing and then calling `AddTask` for every task passed in. template CTaskComplexSequence(T*... tasks) : CTaskComplexSequence{} @@ -29,12 +29,12 @@ class CTaskComplexSequence : public CTaskComplex { (AddTask(tasks), ...); } - CTaskComplexSequence(); + CTaskComplexSequence(const CTaskComplexSequence&); ~CTaskComplexSequence() override; - eTaskType GetTaskType() override { return Type; } // 0x632C60 - CTask* Clone() override; - bool MakeAbortable(class CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x632C60 + CTask* Clone() const override { return new CTaskComplexSequence{*this}; } // 0x5F6710 + bool MakeAbortable(class CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -53,7 +53,6 @@ class CTaskComplexSequence : public CTaskComplex { CTaskComplexSequence* Constructor(); - CTask* Clone_Reversed(); bool MakeAbortable_Reversed(class CPed* ped, eAbortPriority priority, const CEvent* event); CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskComplexUseSequence.cpp b/source/game_sa/Tasks/TaskComplexUseSequence.cpp index 5242680bf4..3abf81ea6e 100644 --- a/source/game_sa/Tasks/TaskComplexUseSequence.cpp +++ b/source/game_sa/Tasks/TaskComplexUseSequence.cpp @@ -3,26 +3,31 @@ #include "TaskComplexUseSequence.h" void CTaskComplexUseSequence::InjectHooks() { - RH_ScopedClass(CTaskComplexUseSequence); + RH_ScopedVirtualClass(CTaskComplexUseSequence, 0x86e518, 11); RH_ScopedCategory("Tasks"); RH_ScopedInstall(Constructor, 0x635450); - RH_ScopedVirtualInstall(Clone, 0x637100); - RH_ScopedVirtualInstall(MakeAbortable, 0x639730); - RH_ScopedVirtualInstall(CreateNextSubTask, 0x6354A0); - RH_ScopedVirtualInstall(CreateFirstSubTask, 0x6354D0); - RH_ScopedVirtualInstall(ControlSubTask, 0x635530); + RH_ScopedVMTInstall(Clone, 0x637100); + RH_ScopedVMTInstall(MakeAbortable, 0x639730); + RH_ScopedVMTInstall(CreateNextSubTask, 0x6354A0); + RH_ScopedVMTInstall(CreateFirstSubTask, 0x6354D0); + RH_ScopedVMTInstall(ControlSubTask, 0x635530); } // 0x635450 -CTaskComplexUseSequence::CTaskComplexUseSequence(int32 sequenceIndex) { - m_nSequenceIndex = sequenceIndex; - m_nCurrentTaskIndex = 0; - m_nSequenceRepeatedCount = 0; - m_nEndTaskIndex = -1; +CTaskComplexUseSequence::CTaskComplexUseSequence(int32 sequenceIndex) : + m_nSequenceIndex{sequenceIndex} +{ CTaskSequences::ms_taskSequence[m_nSequenceIndex].m_nReferenceCount++; } +CTaskComplexUseSequence::CTaskComplexUseSequence(const CTaskComplexUseSequence& o) : + CTaskComplexUseSequence{o.m_nCurrentTaskIndex} +{ + m_nCurrentTaskIndex = o.m_nCurrentTaskIndex; + m_nEndTaskIndex = o.m_nEndTaskIndex; +} + CTaskComplexUseSequence::~CTaskComplexUseSequence() { if (m_nSequenceIndex != -1) { auto complexSequence = &CTaskSequences::ms_taskSequence[m_nSequenceIndex]; @@ -40,11 +45,6 @@ CTaskComplexUseSequence* CTaskComplexUseSequence::Constructor(int32 sequenceInde return this; } -// 0x637100 -CTask* CTaskComplexUseSequence::Clone() { - return CTaskComplexUseSequence::Clone_Reversed(); -} - // 0x639730 bool CTaskComplexUseSequence::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) { return MakeAbortable_Reversed(ped, priority, event); @@ -65,14 +65,6 @@ CTask* CTaskComplexUseSequence::ControlSubTask(CPed* ped) { return ControlSubTask_Reversed(ped); } -CTask* CTaskComplexUseSequence::Clone_Reversed() { - auto* task = new CTaskComplexUseSequence(); - task->m_nCurrentTaskIndex = m_nCurrentTaskIndex; - task->m_nEndTaskIndex = m_nEndTaskIndex; - CTaskSequences::ms_taskSequence[m_nSequenceIndex].m_nReferenceCount++; - return task; -} - bool CTaskComplexUseSequence::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event) { bool bMakeAbortable = m_pSubTask->MakeAbortable(ped, priority, event); if (bMakeAbortable && event && event->GetEventType() == EVENT_DAMAGE) { diff --git a/source/game_sa/Tasks/TaskComplexUseSequence.h b/source/game_sa/Tasks/TaskComplexUseSequence.h index facd1483b0..f4b299efb2 100644 --- a/source/game_sa/Tasks/TaskComplexUseSequence.h +++ b/source/game_sa/Tasks/TaskComplexUseSequence.h @@ -5,24 +5,17 @@ class CPed; class CTaskComplexUseSequence : public CTaskComplex { -public: - int32 m_nSequenceIndex; // Used in CTaskSequences::ms_taskSequence global array - int32 m_nCurrentTaskIndex; // Used in CTaskComplexSequence::m_aTasks array - int32 m_nEndTaskIndex; // Sequence will stop performing tasks when current index is equal to endTaskIndex - int32 m_nSequenceRepeatedCount; // m_nSequenceRepeatedCount simply tells us how many times the sequence has been repeated. - // If CTaskComplexSequence::m_bRepeatSequence is true, this can be greater than 1, - // otherwise it's set to 1 when the sequence is done executing tasks. - public: static constexpr auto Type = TASK_COMPLEX_USE_SEQUENCE; CTaskComplexUseSequence() = default; // blame R* for this explicit CTaskComplexUseSequence(int32 sequenceIndex); + CTaskComplexUseSequence(const CTaskComplexUseSequence&); ~CTaskComplexUseSequence() override; - eTaskType GetTaskType() override { return Type; } // 0x635490 - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x635490 + CTask* Clone() const override { return new CTaskComplexUseSequence{*this}; } // 0x637100 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -33,10 +26,17 @@ class CTaskComplexUseSequence : public CTaskComplex { CTaskComplexUseSequence* Constructor(int32 sequenceIndex); - CTask* Clone_Reversed(); bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* ControlSubTask_Reversed(CPed* ped); + +public: + int32 m_nSequenceIndex{}; /**< Used in CTaskSequences::ms_taskSequence global array */ + int32 m_nCurrentTaskIndex{}; /**< Used in CTaskComplexSequence::m_aTasks array */ + int32 m_nEndTaskIndex{-1}; /**< Sequence will stop performing tasks when current index is equal to endTaskIndex */ + int32 m_nSequenceRepeatedCount{}; /**< This simply tells us how many times the sequence has been repeated. + If `CTaskComplexSequence::m_bRepeatSequence` is true, this can be greater than 1, + otherwise it's set to 1 when the sequence is done executing the sequence tasks. */ }; VALIDATE_SIZE(CTaskComplexUseSequence, 0x1C); diff --git a/source/game_sa/Tasks/TaskManager.cpp b/source/game_sa/Tasks/TaskManager.cpp index 00f3d25876..76d857f89e 100644 --- a/source/game_sa/Tasks/TaskManager.cpp +++ b/source/game_sa/Tasks/TaskManager.cpp @@ -41,7 +41,7 @@ static void DeleteTaskAndNull(CTask*& task) { } /// Find the first task (or subtask) that is of the required type -static CTask* FindFirstTaskOfType(CTask* task, eTaskType type) { +inline CTask* FindFirstTaskOfType(CTask* task, eTaskType type) { for (; task; task = task->GetSubTask()) { if (task->GetTaskType() == type) { return task; @@ -63,7 +63,7 @@ CTaskManager::~CTaskManager() { // 0x681720 CTask* CTaskManager::GetActiveTask() { - for (auto& task : m_aPrimaryTasks) { + for (const auto task : m_aPrimaryTasks) { if (task) { return task; } @@ -73,19 +73,19 @@ CTask* CTaskManager::GetActiveTask() { // 0x681740 CTask* CTaskManager::FindActiveTaskByType(eTaskType taskType) { - // First try current active task, and its sub-tasks + // First try current active task and it's sub-tasks if (const auto task = FindFirstTaskOfType(GetActiveTask(), taskType)) { return task; } - // Now try secondarie's sub tasks + // The same as above, but for all secondary tasks for (const auto sec : m_aSecondaryTasks) { - // NOTE: Original code doesn't break first match, but that's by a bug, not intentional. if (const auto task = FindFirstTaskOfType(sec, taskType)) { return task; } } + // Not found return nullptr; } @@ -184,7 +184,7 @@ void CTaskManager::AddSubTasks(CTask* toTask) { if (const auto sub = ctask->CreateFirstSubTask(m_pPed)) { ctask->SetSubTask(sub); task = sub; // Go on creating the subtask of the created task - } else { // No sub task, so we can stop here + } else { // No sub task created, so we can stop here if (ctask->m_pParentTask) { SetNextSubTask(ctask->m_pParentTask->AsComplex()); // We're a subtask of the parent (if any), thus parent must be complex } @@ -210,6 +210,15 @@ void CTaskManager::ParentsControlChildren(CTask* parent) { } } +void CTaskManager::AbortFirstPrimaryTaskIn(std::initializer_list slots, CPed* ped, eAbortPriority priority, const CEvent* event) { + for (const auto slot : slots) { + if (const auto task = GetTaskPrimary(slot)) { + task->MakeAbortable(ped, priority, event); + break; + } + } +} + // 0x681BD0 void CTaskManager::ClearTaskEventResponse() { // Looking at the code, I think `nontemp` is only present if `temp` is, as it's not deleted otherwise. diff --git a/source/game_sa/Tasks/TaskManager.h b/source/game_sa/Tasks/TaskManager.h index 17e436ff88..97ae05b516 100644 --- a/source/game_sa/Tasks/TaskManager.h +++ b/source/game_sa/Tasks/TaskManager.h @@ -11,7 +11,7 @@ #include "Task.h" #include -enum ePrimaryTasks // array indexes +enum ePrimaryTasks // array indices { TASK_PRIMARY_INVALID = -1, @@ -55,7 +55,7 @@ class CTaskManager { /*! * @0x681720 - * @brief Get the first primary task (that is, the first non-null entry from `m_aPrimaryTasks`) + * @brief Get the first present primary task (that is, the first non-null entry from `m_aPrimaryTasks`) */ CTask* GetActiveTask(); @@ -67,7 +67,7 @@ class CTaskManager { /*! * @addr 0x681810 - * @brief Similar to `FindActiveTaskByType` but only checks the given primary task + * @brief Similar to `FindActiveTaskByType` but only checks the given primary task's subtasks */ CTask* FindTaskByType(ePrimaryTasks taskIndex, eTaskType taskType); @@ -101,9 +101,6 @@ class CTaskManager { */ void Flush(); - - - /// Create the next subtask of `task` /*! * @addr 0x681920 * @brief Set the next sub task of `task` @@ -155,16 +152,26 @@ class CTaskManager { /*! * @addr 0x681B60 - * @brief Set the seconady task + * @brief Set the secondary task * @param task The new, dynamically allocated, task, might be null, in case the specified task will be removed. * @param taskIndex The index of the secondary task to be changed */ void SetTaskSecondary(CTask* task, eSecondaryTask taskIndex) { ChangeTaskInSlot(m_aSecondaryTasks[taskIndex], task); } + /*! + * Abort the first task found out of the primary tasks given + * @notsa + */ + void AbortFirstPrimaryTaskIn(std::initializer_list idxs, CPed* ped, eAbortPriority priority = ABORT_PRIORITY_LEISURE, const CEvent* event = nullptr); + /*! * @brief Clear primary tasks `TASK_PRIMARY_EVENT_RESPONSE_TEMP` and `TASK_PRIMARY_EVENT_RESPONSE_NONTEMP` */ void ClearTaskEventResponse(); + + /*! + * @addr 0x681C10 + */ void ManageTasks(); // Why they doesn't have version for *primary tasks*? :thinking: @@ -172,9 +179,15 @@ class CTaskManager { return m_aPrimaryTasks[taskIndex]; } + /*! + * Get all the primary tasks [there might be null entries] + */ auto& GetPrimaryTasks() const { return m_aPrimaryTasks; } - auto& GetSecondaryTasks() const { return m_aSecondaryTasks; } + /*! + * Get all the primary tasks [there might be null entries] + */ + auto& GetSecondaryTasks() const { return m_aSecondaryTasks; } // NOTSA - Check if any of the given tasks is active bool IsAnyTaskActiveByType(std::initializer_list types) { @@ -183,6 +196,7 @@ class CTaskManager { }); } + // TODO: Replace with `Find<>(true)` CTask* FindActiveTaskFromList(std::initializer_list types) { for (auto type : types) { if (const auto task = FindActiveTaskByType(type)) { @@ -194,7 +208,7 @@ class CTaskManager { /*! * @notsa - * @brief Find active task, check if its of type `T`, and return it, nullptr othetwise (if not found/not of the requrested type + * @brief Find active task, check if its of type `T`, and return it, nullptr otherwise (if not found/not of the requested type */ template T* GetActiveTaskAs() { @@ -208,7 +222,7 @@ class CTaskManager { /*! * @notsa - * @brief Find simplest active task, check if its of type `T`, and return it, nullptr othetwise (if not found/not of the requrested type) + * @brief Find simplest active task, check if its of type `T`, and return it, nullptr otherwise (if not found/not of the requested type) */ template T* GetSimplestActiveTaskAs() { @@ -233,23 +247,38 @@ class CTaskManager { /*! * @notsa - * @brief Find an active task from the give types and return the first one. + * @brief Find task from the give types and return the first one. */ template - auto Find() { // TODO: For now just return `CTask*`, but would be nice to return the first common base class somehow + CTask* Find(bool activeOnly = true) { // TODO: For now just return `CTask*`, but would be nice to return the first common base class somehow + // This won't work if the task has no `Type` member + // If it has a `GetTaskType` function feel free to add it, + // otherwise don't. CTask* ret{}; - (... || (ret = FindActiveTaskByType(Ts))); // Find first active task from given types + if (activeOnly) { + (... || (ret = FindActiveTaskByType(Ts))); + } else { + const auto FindPrimaryTaskByType = [this](eTaskType type) -> CTask* { // Based on `CPedIntelligence::FindTaskByType` + for (const auto idx : { TASK_PRIMARY_DEFAULT, TASK_PRIMARY_PRIMARY, TASK_PRIMARY_EVENT_RESPONSE_TEMP, TASK_PRIMARY_EVENT_RESPONSE_NONTEMP }) { + if (const auto task = FindTaskByType(idx, type)) { + return task; + } + } + return nullptr; + }; + (... || (ret = FindPrimaryTaskByType(Ts))); + } return ret; } /*! * @notsa - * @brief Find an active task from the given types and return the first one. + * @brief Find a task from the given types and return the first one. */ template requires(sizeof...(Ts) > 1) - auto Find() { - return Find(); + CTask* Find(bool activeOnly = true) { + return Find(activeOnly); } /*! @@ -257,25 +286,22 @@ class CTaskManager { * @brief Find task of the given type `T` */ template - T* Find() { - return static_cast(Find()); + T* Find(bool activeOnly = true) { + return static_cast(Find(activeOnly)); } /*! * @notsa - * @brief Check if any of the active tasks is of the given types + * @brief Check if any of the tasks is of the given types */ template - bool HasAnyOf() { - // This won't work if the task has no `Type` member - // If it has a `GetTaskType` function feel free to add it, - // otherwise don't. - return (... || FindActiveTaskByType(Ts::Type)); + bool HasAnyOf(bool activeOnly = true) { + return (... || Find(activeOnly)); } template - bool HasAnyOf() { - return (... || Find()); + bool HasAnyOf(bool activeOnly = true) { + return (... || Find(activeOnly)); } /*! @@ -283,8 +309,8 @@ class CTaskManager { * @brief Check if any active task is of type `T` */ template - bool Has() { - return Find(); + bool Has(bool activeOnly = true) { + return Find(activeOnly); } /*! @@ -292,8 +318,8 @@ class CTaskManager { * @brief Check if any active task is of type `T` */ template - bool Has() { - return Find(); + bool Has(bool activeOnly = true) { + return Find(activeOnly); } /*! diff --git a/source/game_sa/Tasks/TaskTypes/Interior/TaskInteriorBeInHouse.h b/source/game_sa/Tasks/TaskTypes/Interior/TaskInteriorBeInHouse.h index 3825ff3b4e..71814849f0 100644 --- a/source/game_sa/Tasks/TaskTypes/Interior/TaskInteriorBeInHouse.h +++ b/source/game_sa/Tasks/TaskTypes/Interior/TaskInteriorBeInHouse.h @@ -26,8 +26,8 @@ class NOTSA_EXPORT_VTABLE CTaskInteriorBeInHouse : public CTaskComplex { void GetInfoForPedToUse(CPed* ped, int32* outDuration); - CTask* Clone() override { return new CTaskInteriorBeInHouse{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskInteriorBeInHouse{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override { return CreateFirstSubTask(ped); } CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/SeekEntity/PosCalculators/EntitySeekPosCalculatorStandard.cpp b/source/game_sa/Tasks/TaskTypes/SeekEntity/PosCalculators/EntitySeekPosCalculatorStandard.cpp index a80f6f7782..aaa2be44fb 100644 --- a/source/game_sa/Tasks/TaskTypes/SeekEntity/PosCalculators/EntitySeekPosCalculatorStandard.cpp +++ b/source/game_sa/Tasks/TaskTypes/SeekEntity/PosCalculators/EntitySeekPosCalculatorStandard.cpp @@ -6,7 +6,7 @@ void CEntitySeekPosCalculatorStandard::InjectHooks() { RH_ScopedClass(CEntitySeekPosCalculatorStandard); RH_ScopedCategory("Tasks/TaskTypes/SeekPosCalculators"); - RH_ScopedVirtualInstall2(ComputeEntitySeekPos, 0x46af20, { .reversed = false }); + RH_ScopedVirtualInstall2(ComputeEntitySeekPos, 0x46af20); } /* @@ -17,5 +17,5 @@ CEntitySeekPosCalculatorStandard::CEntitySeekPosCalculatorStandard(CEntity* enti */ void CEntitySeekPosCalculatorStandard::ComputeEntitySeekPos(const CPed& seeker, const CEntity& target, CVector& outPos) { - return plugin::CallMethod<0x46af20, CEntitySeekPosCalculatorStandard*, const CPed&, const CEntity&, CVector&>(this, seeker, target, outPos); + outPos = target.GetPosition(); } diff --git a/source/game_sa/Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntity.h b/source/game_sa/Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntity.h index 8ecf370e9e..d1c0f716b3 100644 --- a/source/game_sa/Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntity.h +++ b/source/game_sa/Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntity.h @@ -137,15 +137,15 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSeekEntity : public CTaskComplex { } } - CTask* Clone() override { + CTask* Clone() const override { return new CTaskComplexSeekEntity{ *this }; } - eTaskType GetTaskType() override { + eTaskType GetTaskType() const override { return eTaskType::TASK_COMPLEX_SEEK_ENTITY; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { if (priority == ABORT_PRIORITY_LEISURE) { m_scanInterval = -1; m_scanTimer.SetAsOutOfTime(); @@ -332,7 +332,7 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSeekEntity : public CTaskComplex { Multiply3x3(Invert(commonBoat->GetMatrix()), newPedPos - boatPos), (int16)CGeneral::LimitRadianAngle(CGeneral::GetRadianAngleBetweenPoints(pedToBoatDir, {})), 0.2f, - ped->GetActiveWeapon().m_nType + ped->GetActiveWeapon().m_Type ); return new CTaskSimpleStandStill{ 2000 }; diff --git a/source/game_sa/Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntityAnyMeans.h b/source/game_sa/Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntityAnyMeans.h index 46d1aa0756..6716be5b17 100644 --- a/source/game_sa/Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntityAnyMeans.h +++ b/source/game_sa/Tasks/TaskTypes/SeekEntity/TaskComplexSeekEntityAnyMeans.h @@ -54,11 +54,11 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSeekEntityAnyMeans : public CTaskComplex { CEntity::SafeCleanUpRef(m_entity); } - CTask* Clone() override { + CTask* Clone() const override { return new CTaskComplexSeekEntityAnyMeans{ *this }; } - eTaskType GetTaskType() override { + eTaskType GetTaskType() const override { return Type; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexArrestPed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexArrestPed.h index f459ab998c..5650780391 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexArrestPed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexArrestPed.h @@ -19,9 +19,9 @@ class CTaskComplexArrestPed : public CTaskComplex { explicit CTaskComplexArrestPed(CPed* ped); ~CTaskComplexArrestPed() override; - CTask* Clone() override { return new CTaskComplexArrestPed(m_PedToArrest); } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + CTask* Clone() const override { return new CTaskComplexArrestPed(m_PedToArrest); } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexAttractorPartnerWait.h b/source/game_sa/Tasks/TaskTypes/TaskComplexAttractorPartnerWait.h index 4e4b7732e2..4c1ed84300 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexAttractorPartnerWait.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexAttractorPartnerWait.h @@ -15,8 +15,8 @@ class CTaskComplexAttractorPartnerWait : public CTaskComplex { CTaskComplexAttractorPartnerWait(bool a2, const CScriptedEffectPair* pair); // 0x633250 ~CTaskComplexAttractorPartnerWait() override = default; // 0x633290 - eTaskType GetTaskType() override { return Type; } // 0x633280 - CTask* Clone() override { return new CTaskComplexAttractorPartnerWait(byteC, m_Pair); } // 0x636CF0 + eTaskType GetTaskType() const override { return Type; } // 0x633280 + CTask* Clone() const override { return new CTaskComplexAttractorPartnerWait(byteC, m_Pair); } // 0x636CF0 CTask* CreateNextSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidEntity.h b/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidEntity.h index 86eb86d24e..8e91bc09ca 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidEntity.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidEntity.h @@ -33,9 +33,9 @@ class CTaskComplexAvoidEntity : CTaskComplex { CTaskComplexAvoidEntity(eMoveState, CEntity* entity, const CVector& pos); ~CTaskComplexAvoidEntity() override; - eTaskType GetTaskType() override { return Type; } // 0x66AAD0 - CTask* Clone() override { return new CTaskComplexAvoidEntity(m_moveState, m_Entity, f20); } // 0x66D0C0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x66AAD0 + CTask* Clone() const override { return new CTaskComplexAvoidEntity(m_moveState, m_Entity, f20); } // 0x66D0C0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* ControlSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidOtherPedWhileWandering.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidOtherPedWhileWandering.cpp index 7727174ed7..dad5bf38aa 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidOtherPedWhileWandering.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidOtherPedWhileWandering.cpp @@ -32,8 +32,8 @@ CTaskComplexAvoidOtherPedWhileWandering* CTaskComplexAvoidOtherPedWhileWandering return this; } -CTask* CTaskComplexAvoidOtherPedWhileWandering::Clone() { - return plugin::CallMethodAndReturn(this); +CTask* CTaskComplexAvoidOtherPedWhileWandering::Clone() const { + return plugin::CallMethodAndReturn(this); } CTask* CTaskComplexAvoidOtherPedWhileWandering::ControlSubTask(CPed* ped) { diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidOtherPedWhileWandering.h b/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidOtherPedWhileWandering.h index 452a97bca4..d20242aa78 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidOtherPedWhileWandering.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexAvoidOtherPedWhileWandering.h @@ -30,10 +30,10 @@ class CTaskComplexAvoidOtherPedWhileWandering : public CTaskComplex { CTaskComplexAvoidOtherPedWhileWandering(CPed* ped, const CVector& targetPoint, int32 moveState); ~CTaskComplexAvoidOtherPedWhileWandering() override; - CTask* Clone() override; + CTask* Clone() const override; CTask* ControlSubTask(CPed* ped) override; - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexBeCop.h b/source/game_sa/Tasks/TaskTypes/TaskComplexBeCop.h index 6464a107f1..e90390722c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexBeCop.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexBeCop.h @@ -5,11 +5,11 @@ class NOTSA_EXPORT_VTABLE CTaskComplexBeCop : public CTaskComplexWanderCop { static constexpr auto Type = TASK_COMPLEX_BE_COP; - CTaskComplexBeCop(uint8 moveState, bool dir, CTask* task) : CTaskComplexWanderCop(moveState, dir) { + CTaskComplexBeCop(eMoveState moveState, bool dir, CTask* task) : CTaskComplexWanderCop(moveState, dir) { m_pGoToPointAndStandStillTask = static_cast(task); }; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexBeCop(m_nMoveState, m_nDir, m_pGoToPointAndStandStillTask->Clone()); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexBeCop(m_nMoveState, m_nDir, m_pGoToPointAndStandStillTask->Clone()); } }; VALIDATE_SIZE(CTaskComplexBeCop, 0x50); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexBeInCouple.h b/source/game_sa/Tasks/TaskTypes/TaskComplexBeInCouple.h index 293f1c2302..a5744d6dde 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexBeInCouple.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexBeInCouple.h @@ -28,9 +28,9 @@ class CTaskComplexBeInCouple : public CTaskComplex { ); ~CTaskComplexBeInCouple() override; - eTaskType GetTaskType() override { return Type; } // 0x683770 - CTask* Clone() override { return new CTaskComplexBeInCouple(m_partner, m_isLeader, m_holdHands, m_lookAtEachOther, m_giveUpDist); } // 0x6839C0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x683770 + CTask* Clone() const override { return new CTaskComplexBeInCouple(m_partner, m_isLeader, m_holdHands, m_lookAtEachOther, m_giveUpDist); } // 0x6839C0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexBeInGroup.h b/source/game_sa/Tasks/TaskTypes/TaskComplexBeInGroup.h index 7bc9a86e2e..8cf4209266 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexBeInGroup.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexBeInGroup.h @@ -20,9 +20,9 @@ class CTaskComplexBeInGroup : public CTaskComplex { CTask* MonitorMainGroupTask(CPed* ped); void MonitorSecondaryGroupTask(CPed* ped); - CTask* Clone() override { return new CTaskComplexBeInGroup(m_nGroupId, m_bIsLeader); } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + CTask* Clone() const override { return new CTaskComplexBeInGroup(m_nGroupId, m_bIsLeader); } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexBeStill.h b/source/game_sa/Tasks/TaskTypes/TaskComplexBeStill.h index a903ab8102..0a32fd7993 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexBeStill.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexBeStill.h @@ -9,8 +9,8 @@ class CTaskComplexBeStill : public CTaskComplex { CTaskComplexBeStill() = default; ~CTaskComplexBeStill() override = default; // 0x5F6700 - eTaskType GetTaskType() override { return Type; } // 0x5F66D0 - CTask* Clone() override { return new CTaskComplexBeStill(); } // 0x5F6680 + eTaskType GetTaskType() const override { return Type; } // 0x5F66D0 + CTask* Clone() const override { return new CTaskComplexBeStill(); } // 0x5F6680 CTask* ControlSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexCarDrive.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexCarDrive.cpp index 7000be7a2e..198297b53a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexCarDrive.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexCarDrive.cpp @@ -61,7 +61,7 @@ CTaskComplexCarDrive::~CTaskComplexCarDrive() { } // 0x63DC90 -CTask* CTaskComplexCarDrive::Clone() { +CTask* CTaskComplexCarDrive::Clone() const { auto* task = new CTaskComplexCarDrive(m_pVehicle, m_fSpeed, m_carModelIndexToCreate, static_cast(m_nCarDrivingStyle)); task->m_asDriver = m_asDriver; return task; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexCarDrive.h b/source/game_sa/Tasks/TaskTypes/TaskComplexCarDrive.h index e8efb7a9ed..b389a4423b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexCarDrive.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexCarDrive.h @@ -26,8 +26,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexCarDrive : public CTaskComplex { CTaskComplexCarDrive(CVehicle* vehicle, float speed, int32 carModelIndexToCreate, eCarDrivingStyle carDrivingStyle); ~CTaskComplexCarDrive() override; - CTask* Clone() override; - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override; + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexCarDriveMission.h b/source/game_sa/Tasks/TaskTypes/TaskComplexCarDriveMission.h index 4e03f02f21..8d57b43e41 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexCarDriveMission.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexCarDriveMission.h @@ -17,8 +17,8 @@ class CTaskComplexCarDriveMission : public CTaskComplexCarDrive { CTaskComplexCarDriveMission(CVehicle* vehicle, CEntity* targetVehicle, eCarMission carDriveMission, eCarDrivingStyle carDrivingStyle, float fSpeed); ~CTaskComplexCarDriveMission() override; - CTask* Clone() override { return new CTaskComplexCarDriveMission(m_pVehicle, m_pTargetVehicle, m_nCarMission, (eCarDrivingStyle)m_nCarDrivingStyle, m_fSpeed); } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexCarDriveMission(m_pVehicle, m_pTargetVehicle, m_nCarMission, (eCarDrivingStyle)m_nCarDrivingStyle, m_fSpeed); } + eTaskType GetTaskType() const override { return Type; } void SetUpCar() override; private: @@ -40,8 +40,8 @@ class CTaskComplexCarDriveMissionFleeScene : public CTaskComplexCarDriveMission ~CTaskComplexCarDriveMissionFleeScene() override = default; // 0x4B89F0 static constexpr auto Type = TASK_COMPLEX_CAR_DRIVE_MISSION_FLEE_SCENE; - eTaskType GetTaskType() override { return Type; } // 0x4B89C0 - CTask* Clone() override { return new CTaskComplexCarDriveMissionFleeScene(m_pVehicle); } // 0x4B8950 + eTaskType GetTaskType() const override { return Type; } // 0x4B89C0 + CTask* Clone() const override { return new CTaskComplexCarDriveMissionFleeScene(m_pVehicle); } // 0x4B8950 }; class CTaskComplexCarDriveMissionKillPed : public CTaskComplexCarDriveMission { @@ -50,6 +50,6 @@ class CTaskComplexCarDriveMissionKillPed : public CTaskComplexCarDriveMission { ~CTaskComplexCarDriveMissionKillPed() override = default; // 0x4B8AD0 static constexpr auto Type = TASK_COMPLEX_CAR_DRIVE_MISSION_KILL_PED; - eTaskType GetTaskType() override { return Type; } // 0x4B8AA0 - CTask* Clone() override { return new CTaskComplexCarDriveMissionKillPed(m_pVehicle, m_pTargetVehicle); } // 0x4B8A30 + eTaskType GetTaskType() const override { return Type; } // 0x4B8AA0 + CTask* Clone() const override { return new CTaskComplexCarDriveMissionKillPed(m_pVehicle, m_pTargetVehicle); } // 0x4B8A30 }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexCarSlowBeDraggedOut.h b/source/game_sa/Tasks/TaskTypes/TaskComplexCarSlowBeDraggedOut.h index 85074174aa..02b7705c2b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexCarSlowBeDraggedOut.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexCarSlowBeDraggedOut.h @@ -19,9 +19,9 @@ class CTaskComplexCarSlowBeDraggedOut : public CTaskComplex { CTaskComplexCarSlowBeDraggedOut(CVehicle* vehicle, int32 door, bool a4); ~CTaskComplexCarSlowBeDraggedOut() override; - CTask* Clone() override { return new CTaskComplexCarSlowBeDraggedOut(m_Vehicle, m_Door, field_14); } // 0x64A120 - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + CTask* Clone() const override { return new CTaskComplexCarSlowBeDraggedOut(m_Vehicle, m_Door, field_14); } // 0x64A120 + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexCarSlowBeDraggedOutAndStandUp.h b/source/game_sa/Tasks/TaskTypes/TaskComplexCarSlowBeDraggedOutAndStandUp.h index bdd0844d26..a00b10ae1c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexCarSlowBeDraggedOutAndStandUp.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexCarSlowBeDraggedOutAndStandUp.h @@ -16,9 +16,9 @@ class CTaskComplexCarSlowBeDraggedOutAndStandUp : public CTaskComplex { CTaskComplexCarSlowBeDraggedOutAndStandUp(CVehicle* vehicle, int32 a3); ~CTaskComplexCarSlowBeDraggedOutAndStandUp() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexCarSlowBeDraggedOutAndStandUp(m_Vehicle, dword10); } // 0x64A190; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexCarSlowBeDraggedOutAndStandUp(m_Vehicle, dword10); } // 0x64A190; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexChat.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexChat.cpp index 3055b185b2..e71c6a892b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexChat.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexChat.cpp @@ -1,31 +1,49 @@ #include "StdInc.h" #include "TaskComplexChat.h" + +#include "TaskSimpleChat.h" #include "InterestingEvents.h" #include "TaskSimpleRunAnim.h" #include "TaskSimpleStandStill.h" +void CTaskComplexChat::InjectHooks() { + RH_ScopedVirtualClass(CTaskComplexChat, 0x8706A0, 14); + RH_ScopedCategory("Tasks/TaskTypes"); + + RH_ScopedInstall(Constructor, 0x682CB0); + RH_ScopedInstall(Destructor, 0x682D40); + RH_ScopedVMTInstall(Clone, 0x682DA0); + RH_ScopedVMTInstall(GetTaskType, 0x682D30); + RH_ScopedVMTInstall(CreateNextSubTask, 0x682E10); + RH_ScopedVMTInstall(CreateFirstSubTask, 0x682E20); + RH_ScopedVMTInstall(ControlSubTask, 0x683060); +} + // 0x682CB0 -CTaskComplexChat::CTaskComplexChat(bool a2, CPed* ped, int32 updateDirectionCount, int16 conversationId) : CTaskComplex() { - byteC = a2; - m_UpdateDirectionCount = updateDirectionCount; - m_Ped = ped; - m_ConversationId = conversationId; - CEntity::SafeRegisterRef(m_Ped); - byte1A = 0; - dword1C = 0; +CTaskComplexChat::CTaskComplexChat(bool isChatter, CPed* partner, int32 stage, int16 globalSpeechContext) : + m_IsChatter{ isChatter }, + m_ChatPartner{partner}, + m_Stage{stage}, + m_GlobalSpeechContext{ globalSpeechContext } +{ + CEntity::SafeRegisterRef(m_ChatPartner); } -// 0x682D40 -CTaskComplexChat::~CTaskComplexChat() { - CEntity::SafeCleanUpRef(m_Ped); +CTaskComplexChat::CTaskComplexChat(const CTaskComplexChat& o) : + CTaskComplexChat{ + o.m_IsChatter, + o.m_ChatPartner, + o.m_Stage, + o.m_GlobalSpeechContext +} +{ + m_TaskFinished = o.m_TaskFinished; } -// 0x682DA0 -CTask* CTaskComplexChat::Clone() { - auto* task = new CTaskComplexChat(byteC, m_Ped, m_UpdateDirectionCount, m_ConversationId); - task->byte1A = byte1A; - return task; +// 0x682D40 +CTaskComplexChat::~CTaskComplexChat() { + CEntity::SafeCleanUpRef(m_ChatPartner); } // 0x682E10 @@ -35,38 +53,63 @@ CTask* CTaskComplexChat::CreateNextSubTask(CPed* ped) { // 0x682E20 CTask* CTaskComplexChat::CreateFirstSubTask(CPed* ped) { - return plugin::CallMethodAndReturn(this, ped); - - if (!m_Ped) + if (!m_ChatPartner) { return nullptr; + } - if (!byteC) { - return new CTaskSimpleStandStill(999999); + if (!m_IsChatter) { + return new CTaskSimpleStandStill(999'999); } - if (m_ConversationId > -1) { - ped->Say(m_ConversationId); + if (m_GlobalSpeechContext > -1) { + ped->Say(m_GlobalSpeechContext); } - g_InterestingEvents.Add(CInterestingEvents::INTERESTING_EVENT_1, ped); - - if (m_ConversationId == 164 || m_ConversationId > 166 && m_ConversationId <= 168) { - auto RandomNumberInRange = CGeneral::GetRandomNumberInRange(0, 3); - if (RandomNumberInRange) { - if (RandomNumberInRange == 1) { - return new CTaskSimpleRunAnim(ANIM_GROUP_DEFAULT, ANIM_ID_ENDCHAT_02, 4.0f, 0); - } else if (RandomNumberInRange == 2) { - return new CTaskSimpleRunAnim(ANIM_GROUP_DEFAULT, ANIM_ID_ENDCHAT_03, 4.0f, 0); - } else { - // todo: return new CTaskSimpleChat(4000); - } + g_InterestingEvents.Add(CInterestingEvents::PEDS_CHATTING, ped); + + if (m_GlobalSpeechContext == 164 || m_GlobalSpeechContext > 166 && m_GlobalSpeechContext <= 168) { + switch (CGeneral::GetRandomNumberInRange(0, notsa::IsFixBugs() ? 3 : 4)) { + case 0: return new CTaskSimpleRunAnim(ANIM_GROUP_DEFAULT, ANIM_ID_ENDCHAT_01, 4.0f, 0); + case 1: return new CTaskSimpleRunAnim(ANIM_GROUP_DEFAULT, ANIM_ID_ENDCHAT_02, 4.0f, 0); + case 2: return new CTaskSimpleRunAnim(ANIM_GROUP_DEFAULT, ANIM_ID_ENDCHAT_03, 4.0f, 0); + case 3: return new CTaskSimpleChat{ 4000 }; // NOTE: (FIXED-BUG) Originally unreachable } - return new CTaskSimpleRunAnim(ANIM_GROUP_DEFAULT, ANIM_ID_ENDCHAT_01, 4.0f, 0); } - // todo: return new CTaskSimpleChat(999999); + + return new CTaskSimpleChat{ 999'999 }; } // 0x683060 CTask* CTaskComplexChat::ControlSubTask(CPed* ped) { - return plugin::CallMethodAndReturn(this, ped); + if (!m_ChatPartner) { + return nullptr; + } + + if (m_TaskFinished && m_pSubTask->MakeAbortable(ped, ABORT_PRIORITY_URGENT, nullptr)) { + return nullptr; + } + + const auto partnerChatTask = m_ChatPartner->GetTaskManager().Find(); + if (partnerChatTask) { + if (partnerChatTask->m_Stage < m_Stage || !partnerChatTask->m_TaskFinished && m_IsChatter == partnerChatTask->m_IsChatter) { + m_TaskFinished = true; + } + } + + if (m_IsChatter && !ped->GetPedTalking()) { + if (partnerChatTask) { + if (!partnerChatTask->m_TaskFinished && !partnerChatTask->m_IsChatter) { + partnerChatTask->m_TaskFinished = m_TaskFinished = true; + } + } + m_NoChatTimeout++; + if (m_NoChatTimeout > 50) { + m_TaskFinished = true; + if (partnerChatTask) { + partnerChatTask->m_TaskFinished = true; + } + } + } + + return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexChat.h b/source/game_sa/Tasks/TaskTypes/TaskComplexChat.h index 5bd093a868..b74749ac64 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexChat.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexChat.h @@ -6,24 +6,39 @@ class CEntity; class CTaskComplexChat : public CTaskComplex { public: - bool byteC; - CPed* m_Ped; - int32 m_UpdateDirectionCount; - int16 m_ConversationId; - int8 byte1A; - int32 dword1C; + bool m_IsChatter{}; + CPed* m_ChatPartner{}; + int32 m_Stage{}; + int16 m_GlobalSpeechContext{}; + bool m_TaskFinished{}; + int32 m_NoChatTimeout{}; public: static constexpr auto Type = TASK_COMPLEX_CHAT; - CTaskComplexChat(bool a2, CPed* ped, int32 updateDirectionCount, int16 conversationId); + static void InjectHooks(); + + CTaskComplexChat(bool isChatter, CPed* partner, int32 stage, int16 globalSpeechContext); + CTaskComplexChat(const CTaskComplexChat&); ~CTaskComplexChat() override; - eTaskType GetTaskType() override { return Type; } // 0x682D30 - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } // 0x682D30 + CTask* Clone() const override { return new CTaskComplexChat{*this}; } + CTask* ControlSubTask(CPed* ped) override; + CTask* CreateFirstSubTask(CPed* ped) override; + CTask* CreateNextSubTask(CPed* ped) override; + +private: // Wrappers for hooks + // 0x682CB0 + CTaskComplexChat* Constructor(uint8 a, CPed* b, int32 c, int16 d) { + this->CTaskComplexChat::CTaskComplexChat(a, b, c, d); + return this; + } - CTask* ControlSubTask(CPed* ped) override; - CTask* CreateFirstSubTask(CPed* ped) override; - CTask* CreateNextSubTask(CPed* ped) override; + // 0x682D40 + CTaskComplexChat* Destructor() { + this->CTaskComplexChat::~CTaskComplexChat(); + return this; + } }; VALIDATE_SIZE(CTaskComplexChat, 0x20); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexClimb.h b/source/game_sa/Tasks/TaskTypes/TaskComplexClimb.h index 705ffc3b9c..dca867f2bc 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexClimb.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexClimb.h @@ -9,6 +9,6 @@ class CTaskComplexClimb : public CTaskComplexJump { CTaskComplexClimb() : CTaskComplexJump(COMPLEX_JUMP_TYPE_CLIMB) {}; // 0x46A630 ~CTaskComplexClimb() override = default; // 0x46A6E0 - CTask* Clone() override { return new CTaskComplexClimb(); } // 0x46A6B0 - eTaskType GetTaskType() override { return Type; } // 0x46A6C0 + CTask* Clone() const override { return new CTaskComplexClimb(); } // 0x46A6B0 + eTaskType GetTaskType() const override { return Type; } // 0x46A6C0 }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexCopInCar.h b/source/game_sa/Tasks/TaskTypes/TaskComplexCopInCar.h index 40cc810837..001e8426cc 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexCopInCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexCopInCar.h @@ -28,9 +28,9 @@ class CTaskComplexCopInCar : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType, CPed* copPed); - CTask* Clone() override { return new CTaskComplexCopInCar{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexCopInCar{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexCrossRoadLookAndAchieveHeading.h b/source/game_sa/Tasks/TaskTypes/TaskComplexCrossRoadLookAndAchieveHeading.h index 97313c5389..19e2ad0f9f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexCrossRoadLookAndAchieveHeading.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexCrossRoadLookAndAchieveHeading.h @@ -13,13 +13,13 @@ class CTaskComplexCrossRoadLookAndAchieveHeading : public CTaskComplex { CTaskComplexCrossRoadLookAndAchieveHeading(int32 durationInMs, float fTargetHeading); ~CTaskComplexCrossRoadLookAndAchieveHeading() override; - CTask* Clone() override { return new CTaskComplexCrossRoadLookAndAchieveHeading(m_nDuration, m_fTargetHeading); } // 0x636500 + CTask* Clone() const override { return new CTaskComplexCrossRoadLookAndAchieveHeading(m_nDuration, m_fTargetHeading); } // 0x636500 CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } // 0x631CB0 CTask* CreateFirstSubTask(CPed* ped) override { return CreateSubTask(TASK_SIMPLE_ACHIEVE_HEADING, ped); } // 0x631C90 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateSubTask(eTaskType taskType, CPed* ped); - eTaskType GetTaskType() override { return Type; } // 0x631B10 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x631B10 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; #if ANDROID void Serialize(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCar.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCar.h index e0bd46f253..b1bb13987b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCar.h @@ -19,9 +19,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexDestroyCar : public CTaskComplex { CTaskComplexDestroyCar(CVehicle* vehicleToDestroy, uint32 a3 = 0, uint32 a4 = 0, uint32 a5 = 0); ~CTaskComplexDestroyCar() override; - eTaskType GetTaskType() override { return Type; } // 0x621C70 - CTask* Clone() override { return new CTaskComplexDestroyCar(m_VehicleToDestroy, m_unused, m_unused2, m_unused3); } // 0x623530 - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x621C70 + CTask* Clone() const override { return new CTaskComplexDestroyCar(m_VehicleToDestroy, m_unused, m_unused2, m_unused3); } // 0x623530 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCarArmed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCarArmed.h index debcd96c9d..ebc8a9704b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCarArmed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCarArmed.h @@ -34,9 +34,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexDestroyCarArmed : public CTaskComplex { CTaskComplexDestroyCarArmed(const CTaskComplexDestroyCarArmed&); // NOTSA ~CTaskComplexDestroyCarArmed() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexDestroyCarArmed{ *this }; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexDestroyCarArmed{ *this }; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCarMelee.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCarMelee.h index bdd409773b..f3183c409d 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCarMelee.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDestroyCarMelee.h @@ -29,9 +29,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexDestroyCarMelee : public CTaskComplex { void CalculateSearchPositionAndRanges(CPed* ped); CTask* CreateSubTask(eTaskType taskType, CPed* ped); - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexDestroyCarMelee(m_VehToDestroy); } // 0x6235A0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexDestroyCarMelee(m_VehToDestroy); } // 0x6235A0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDie.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDie.h index 0396c9be10..5ad34df0e9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDie.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDie.h @@ -40,12 +40,12 @@ class NOTSA_EXPORT_VTABLE CTaskComplexDie : public CTaskComplex { ); ~CTaskComplexDie() override = default; // 0x6300C0 0x637910 - eTaskType GetTaskType() override { return Type; } // 0x6300B0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x6300B0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed*) override { return m_pSubTask; } // 0x630580 - CTask* Clone() override { + CTask* Clone() const override { return new CTaskComplexDie(m_nWeaponType, m_nAnimGroup, m_nAnimID, m_fBlendDelta, m_fAnimSpeed, m_bBeingKilledByStealth, m_bFallingToDeath, m_nFallToDeathDir, m_bFallToDeathOverRailing); } // 0x636060 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDieInCar.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDieInCar.h index fe2421286f..7fae2ade47 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDieInCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDieInCar.h @@ -16,13 +16,13 @@ class CTaskComplexDieInCar : public CTaskComplex { explicit CTaskComplexDieInCar(eWeaponType weaponType); ~CTaskComplexDieInCar() override = default; // 0x62FCF0, 0x6375D0 - eTaskType GetTaskType() override { return Type; }; // 0x62FCB0 - CTask* Clone() override { return new CTaskComplexDieInCar(m_nWeaponType); } // 0x635F90 + eTaskType GetTaskType() const override { return Type; }; // 0x62FCB0 + CTask* Clone() const override { return new CTaskComplexDieInCar(m_nWeaponType); } // 0x635F90 CTask* ControlSubTask(CPed* ped) override; CTask* CreateSubTask(eTaskType taskType, CPed* ped); CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; void PreparePedVehicleForPedDeath(CVehicle* vehicle); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDragPedFromCar.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDragPedFromCar.h index 6cc6539f79..a45a14a5a1 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDragPedFromCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDragPedFromCar.h @@ -13,8 +13,8 @@ class CTaskComplexDragPedFromCar : public CTaskComplexEnterCar { CTaskComplexDragPedFromCar(CPed* ped, int32 draggedPedDownTime); ~CTaskComplexDragPedFromCar() override; - eTaskType GetTaskType() override { return Type; } // 0x6404C0 - CTask* Clone() override { return new CTaskComplexDragPedFromCar(m_Ped, m_draggedPedDownTime); } // 0x643950 + eTaskType GetTaskType() const override { return Type; } // 0x6404C0 + CTask* Clone() const override { return new CTaskComplexDragPedFromCar(m_Ped, m_draggedPedDownTime); } // 0x643950 CTask* ControlSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDriveFireTruck.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexDriveFireTruck.cpp index 3bfc303c87..22a7893497 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDriveFireTruck.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDriveFireTruck.cpp @@ -42,7 +42,7 @@ CTaskComplexDriveFireTruck::~CTaskComplexDriveFireTruck() { } // 0x659BC0 -CTask* CTaskComplexDriveFireTruck::Clone() { +CTask* CTaskComplexDriveFireTruck::Clone() const { return Clone_Reversed(); } @@ -61,7 +61,7 @@ CTask* CTaskComplexDriveFireTruck::ControlSubTask(CPed* ped) { return ControlSubTask_Reversed(ped); } -CTask* CTaskComplexDriveFireTruck::Clone_Reversed() { +CTask* CTaskComplexDriveFireTruck::Clone_Reversed() const { return new CTaskComplexDriveFireTruck(m_pVehicle, m_pPartnerFireman, m_bIsDriver); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDriveFireTruck.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDriveFireTruck.h index 458b50e79a..9ff708778e 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDriveFireTruck.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDriveFireTruck.h @@ -15,13 +15,13 @@ class CTaskComplexDriveFireTruck : public CTaskComplex { CTaskComplexDriveFireTruck(CVehicle* vehicle, CPed* partnerFireman, bool bIsDriver); ~CTaskComplexDriveFireTruck() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* ControlSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDrivePointRoute.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDrivePointRoute.h index 8af906ca72..0886fac268 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDrivePointRoute.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDrivePointRoute.h @@ -32,8 +32,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexDrivePointRoute : public CTaskComplex { /// NOTSA: Create `CTaskComplexDriveToPoint` for the current point. CTask* CreateTaskForCurrentPoint() const; - CTask* Clone() override { return new CTaskComplexDrivePointRoute{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexDrivePointRoute{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override { return CreateTaskForCurrentPoint(); } CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDriveToPoint.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDriveToPoint.h index d3543d06c2..f8e7f0fad4 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDriveToPoint.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDriveToPoint.h @@ -23,8 +23,8 @@ class CTaskComplexDriveToPoint : public CTaskComplexCarDrive { CTaskComplexDriveToPoint(CVehicle* vehicle, const CVector& point, float speed, int32 arg4, int32 carModelIndexToCreate, float radius, eCarDrivingStyle drivingStyle); ~CTaskComplexDriveToPoint() override = default; - eTaskType GetTaskType() override { return Type;} - CTask* Clone() override { return new CTaskComplexDriveToPoint(m_pVehicle, m_Point, m_fSpeed, field_30, m_carModelIndexToCreate, m_Radius, m_nCarDrivingStyle); } + eTaskType GetTaskType() const override { return Type;} + CTask* Clone() const override { return new CTaskComplexDriveToPoint(m_pVehicle, m_Point, m_fSpeed, field_30, m_carModelIndexToCreate, m_Radius, m_nCarDrivingStyle); } void SetUpCar() override; CTask* CreateSubTaskCannotGetInCar(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexDriveWander.h b/source/game_sa/Tasks/TaskTypes/TaskComplexDriveWander.h index 5733d0b65c..ae484b4364 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexDriveWander.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexDriveWander.h @@ -11,8 +11,8 @@ class CTaskComplexCarDriveWander : public CTaskComplexCarDrive { CTaskComplexCarDriveWander(CVehicle* vehicle, eCarDrivingStyle carDrivingStyle, float fSpeed); ~CTaskComplexCarDriveWander() override = default; - CTask* Clone() override { return new CTaskComplexCarDrive(m_pVehicle, m_fSpeed, -1, static_cast(m_nCarDrivingStyle)); }; - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexCarDrive(m_pVehicle, m_fSpeed, -1, static_cast(m_nCarDrivingStyle)); }; + eTaskType GetTaskType() const override { return Type; } void SetUpCar() override; CTask* CreateSubTaskCannotGetInCar(CPed* ped) override; }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterAnyCarAsDriver.h b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterAnyCarAsDriver.h index 7e049d3296..27cab59ac6 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterAnyCarAsDriver.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterAnyCarAsDriver.h @@ -9,8 +9,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexEnterAnyCarAsDriver : public CTaskComplex CTaskComplexEnterAnyCarAsDriver() = default; ~CTaskComplexEnterAnyCarAsDriver() = default; - CTask* Clone() override { return new CTaskComplexEnterAnyCarAsDriver{}; } - eTaskType GetTaskType() override { return Type; }; + CTask* Clone() const override { return new CTaskComplexEnterAnyCarAsDriver{}; } + eTaskType GetTaskType() const override { return Type; }; CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCar.h b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCar.h index a5363ba698..6c76dc374b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCar.h @@ -42,7 +42,7 @@ class NOTSA_EXPORT_VTABLE CTaskComplexEnterCar : public CTaskComplex { CTaskComplexEnterCar(CVehicle* targetVehicle, bool bAsDriver, bool bQuitAfterOpeningDoor, bool bQuitAfterDraggingPedOut, bool bCarryOnAfterFallingOff = false); ~CTaskComplexEnterCar() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -51,7 +51,7 @@ class NOTSA_EXPORT_VTABLE CTaskComplexEnterCar : public CTaskComplex { CVector GetTargetPos(); CVehicle* GetCameraAvoidVehicle(); - auto GetVehicle() const { return m_car; } + auto GetTarget() const { return m_car; } private: // 0x63A220 CTaskComplexEnterCar* Constructor(CVehicle* vehicle, bool bAsDriver, bool bQuitAfterOpeningDoor, bool bQuitAfterDraggingPedOut, bool bCarryOnAfterFallingOff) { diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriver.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriver.cpp index 4f8a054cea..cf5c02e2a9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriver.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriver.cpp @@ -14,9 +14,9 @@ CTaskComplexEnterCarAsDriver::CTaskComplexEnterCarAsDriver(CVehicle* targetVehic m_moveState = moveState; } -// 0x643780 -CTask* CTaskComplexEnterCarAsDriver::Clone() { - auto task = new CTaskComplexEnterCarAsDriver(m_car); - task->m_moveState = m_moveState; - return task; +// For 0x643780 +CTaskComplexEnterCarAsDriver::CTaskComplexEnterCarAsDriver(const CTaskComplexEnterCarAsDriver& o) : + CTaskComplexEnterCarAsDriver{ m_car } +{ + m_moveState = o.m_moveState; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriver.h b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriver.h index aef1cb1a96..a06f98ad2e 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriver.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriver.h @@ -15,9 +15,10 @@ class CTaskComplexEnterCarAsDriver : public CTaskComplexEnterCar { explicit CTaskComplexEnterCarAsDriver(CVehicle* targetVehicle); CTaskComplexEnterCarAsDriver(CVehicle* targetVehicle, eMoveState moveState); + CTaskComplexEnterCarAsDriver(const CTaskComplexEnterCarAsDriver&); ~CTaskComplexEnterCarAsDriver() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexEnterCarAsDriver{*this}; } }; VALIDATE_SIZE(CTaskComplexEnterCarAsDriver, 0x50); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriverTimed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriverTimed.h index 658cccaea3..d3c331f58a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriverTimed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsDriverTimed.h @@ -30,9 +30,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexEnterCarAsDriverTimed : public CTaskComple CTaskComplexEnterCarAsDriverTimed(const CTaskComplexEnterCarAsDriverTimed&); ~CTaskComplexEnterCarAsDriverTimed(); - CTask* Clone() override { return new CTaskComplexEnterCarAsDriverTimed{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexEnterCarAsDriverTimed{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.cpp index 1d6ef342cf..aee6a37e5f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.cpp @@ -9,9 +9,13 @@ CTaskComplexEnterCarAsPassenger::CTaskComplexEnterCarAsPassenger(CVehicle* targe m_targetSeat = nTargetSeat; } -// 0x6437F0 -CTask* CTaskComplexEnterCarAsPassenger::Clone() { - auto task = new CTaskComplexEnterCarAsPassenger(m_car, m_targetSeat, m_bCarryOnAfterFallingOff); - task->m_moveState = m_moveState; - return task; +// For 0x6437F0 +CTaskComplexEnterCarAsPassenger::CTaskComplexEnterCarAsPassenger(const CTaskComplexEnterCarAsPassenger& o) : + CTaskComplexEnterCarAsPassenger{ + o.m_car, + o.m_targetSeat, + o.m_bCarryOnAfterFallingOff + } +{ + m_moveState = o.m_moveState; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.h b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.h index 2e1d2bfd45..d3e102e847 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassenger.h @@ -13,10 +13,11 @@ class CTaskComplexEnterCarAsPassenger : public CTaskComplexEnterCar { static constexpr auto Type = TASK_COMPLEX_ENTER_CAR_AS_PASSENGER; CTaskComplexEnterCarAsPassenger(CVehicle* targetVehicle, int32 nTargetSeat, bool bCarryOnAfterFallingOff); + CTaskComplexEnterCarAsPassenger(const CTaskComplexEnterCarAsPassenger&); ~CTaskComplexEnterCarAsPassenger() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexEnterCarAsPassenger{*this}; } // 0x6437F0 }; VALIDATE_SIZE(CTaskComplexEnterCarAsPassenger, 0x50); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassengerWait.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassengerWait.cpp new file mode 100644 index 0000000000..0011f7ef8d --- /dev/null +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassengerWait.cpp @@ -0,0 +1,65 @@ +#include "StdInc.h" +#include "TaskComplexEnterCarAsPassengerWait.h" + +void CTaskComplexEnterCarAsPassengerWait::InjectHooks() { + RH_ScopedVirtualClass(CTaskComplexEnterCarAsPassengerWait, 0x86E778, 11); + RH_ScopedCategory("Tasks/TaskTypes"); + + RH_ScopedInstall(Constructor, 0x63B320); + RH_ScopedInstall(Destructor, 0x63B3C0); + + RH_ScopedInstall(CreateSubTask, 0x6408D0, {.reversed = false}); + RH_ScopedVMTInstall(Clone, 0x63D850); + RH_ScopedVMTInstall(GetTaskType, 0x63B3B0); + RH_ScopedVMTInstall(CreateNextSubTask, 0x643E10, {.reversed = false}); + RH_ScopedVMTInstall(CreateFirstSubTask, 0x643F00, {.reversed = false}); + RH_ScopedVMTInstall(ControlSubTask, 0x640730, {.reversed = false}); +} + +// 0x63B320 +CTaskComplexEnterCarAsPassengerWait::CTaskComplexEnterCarAsPassengerWait(CVehicle* target, CPed* waitFor, bool forceFrontSeat, eMoveState ms) : + m_Target{target}, + m_WaitForPed{waitFor}, + m_bForceFrontSeat{forceFrontSeat}, + m_MoveState{ms} +{ + CEntity::SafeRegisterRef(m_Target); + CEntity::SafeRegisterRef(m_WaitForPed); +} + +// NOTSA (for 0x63D850) +CTaskComplexEnterCarAsPassengerWait::CTaskComplexEnterCarAsPassengerWait(const CTaskComplexEnterCarAsPassengerWait& o) : + CTaskComplexEnterCarAsPassengerWait{ + o.m_Target, + o.m_WaitForPed, + o.m_bForceFrontSeat, + o.m_MoveState + } +{ +} + +// 0x63B3C0 +CTaskComplexEnterCarAsPassengerWait::~CTaskComplexEnterCarAsPassengerWait() { + CEntity::SafeCleanUpRef(m_Target); + CEntity::SafeCleanUpRef(m_WaitForPed); +} + +// 0x6408D0 +CTaskComplexEnterCarAsPassengerTimed* CTaskComplexEnterCarAsPassengerWait::CreateSubTask(int32 taskType, CPed* ped) { + return plugin::CallMethodAndReturn(this, taskType, ped); +} + +// 0x643E10 +CTask* CTaskComplexEnterCarAsPassengerWait::CreateNextSubTask(CPed* ped) { + return plugin::CallMethodAndReturn(this, ped); +} + +// 0x643F00 +CTask* CTaskComplexEnterCarAsPassengerWait::CreateFirstSubTask(CPed* ped) { + return plugin::CallMethodAndReturn(this, ped); +} + +// 0x640730 +CTask* CTaskComplexEnterCarAsPassengerWait::ControlSubTask(CPed* ped) { + return plugin::CallMethodAndReturn(this, ped); +} diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassengerWait.h b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassengerWait.h new file mode 100644 index 0000000000..8bc6725030 --- /dev/null +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEnterCarAsPassengerWait.h @@ -0,0 +1,46 @@ +#pragma once + +class CPed; +class CTaskComplexEnterCarAsPassengerTimed; +class CTaskComplexEnterCarAsPassengerWait; +class CVehicle; + +class CTaskComplexEnterCarAsPassengerWait : public CTaskComplex { +public: + static constexpr auto Type = eTaskType::TASK_COMPLEX_ENTER_CAR_AS_PASSENGER_WAIT; + + static void InjectHooks(); + + CTaskComplexEnterCarAsPassengerWait(CVehicle* target, CPed* waitFor, bool forceFrontSeat, eMoveState ms); + CTaskComplexEnterCarAsPassengerWait(const CTaskComplexEnterCarAsPassengerWait&); + ~CTaskComplexEnterCarAsPassengerWait(); + + CTaskComplexEnterCarAsPassengerTimed* CreateSubTask(int32 taskType, CPed* ped); + + CTask* Clone() const override { return new CTaskComplexEnterCarAsPassengerWait{ *this }; } + eTaskType GetTaskType() const override { return Type; } + CTask* CreateNextSubTask(CPed* ped) override; + CTask* CreateFirstSubTask(CPed* ped) override; + CTask* ControlSubTask(CPed* ped) override; + + auto GetTarget() const { return m_Target; } + +private: // Wrappers for hooks + // 0x63B320 + CTaskComplexEnterCarAsPassengerWait* Constructor(CVehicle* target, CPed* waitFor, bool forceFrontSeat, eMoveState ms) { + this->CTaskComplexEnterCarAsPassengerWait::CTaskComplexEnterCarAsPassengerWait(target, waitFor, forceFrontSeat, ms); + return this; + } + // 0x63B3C0 + CTaskComplexEnterCarAsPassengerWait* Destructor() { + this->CTaskComplexEnterCarAsPassengerWait::~CTaskComplexEnterCarAsPassengerWait(); + return this; + } +private: + CVehicle* m_Target{}; + CPed* m_WaitForPed{}; + uint32 m_EnterCarFails{}; + bool m_bForceFrontSeat{}; + eMoveState m_MoveState{}; + bool m_bPlayedSample{}; +}; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveCower.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveCower.cpp index b7810ca029..767f5bcf8c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveCower.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveCower.cpp @@ -3,9 +3,10 @@ #include "TaskComplexEvasiveCower.h" // 0x655460 -CTaskComplexEvasiveCower::CTaskComplexEvasiveCower(CEntity* entity, CVector& pos) : CTaskComplex() { - m_Pos = pos; - m_Entity = entity; +CTaskComplexEvasiveCower::CTaskComplexEvasiveCower(CEntity* entity, const CVector& pos) : + m_Pos{pos}, + m_Entity{entity} +{ CEntity::SafeRegisterRef(m_Entity); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveCower.h b/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveCower.h index 76c2a56f90..3c95ed31b8 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveCower.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveCower.h @@ -10,11 +10,11 @@ class CTaskComplexEvasiveCower : public CTaskComplex { public: static constexpr auto Type = TASK_COMPLEX_EVASIVE_COWER; - CTaskComplexEvasiveCower(CEntity* entity, CVector& pos); + CTaskComplexEvasiveCower(CEntity* entity, const CVector& pos); ~CTaskComplexEvasiveCower() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexEvasiveCower(m_Entity, m_Pos); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexEvasiveCower(m_Entity, m_Pos); } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveStep.h b/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveStep.h index 382302111b..2a26bf191a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveStep.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexEvasiveStep.h @@ -14,8 +14,8 @@ class CTaskComplexEvasiveStep : public CTaskComplex { CTaskComplexEvasiveStep(CEntity* entity, const CVector& pos); ~CTaskComplexEvasiveStep() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexEvasiveStep(m_Entity, m_Pos); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexEvasiveStep(m_Entity, m_Pos); } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } // 0x653550 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexExtinguishFires.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexExtinguishFires.cpp index 235166a167..7097645ad2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexExtinguishFires.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexExtinguishFires.cpp @@ -55,7 +55,7 @@ CTask* CTaskComplexExtinguishFires::CreateNextSubTask(CPed* ped) { return CreateFirstSubTask(ped); } - if (ped->GetActiveWeapon().m_nType == WEAPON_EXTINGUISHER) { // Inverted + if (ped->GetActiveWeapon().m_Type == WEAPON_EXTINGUISHER) { // Inverted return new CTaskSimpleGunControl{ nullptr, nearestFireNow->GetPosition(), {}, eGunCommand::PISTOLWHIP, 1, -1 }; } else { return new CTaskSimpleStandStill{ 2000 }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexExtinguishFires.h b/source/game_sa/Tasks/TaskTypes/TaskComplexExtinguishFires.h index e170e25371..f2d7922f76 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexExtinguishFires.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexExtinguishFires.h @@ -22,8 +22,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexExtinguishFires : public CTaskComplex { CFire* GetNearestFire(CPed* ped); - CTask* Clone() override { return new CTaskComplexExtinguishFires{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexExtinguishFires{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFacial.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFacial.h index 083b163f6b..b0ca4315b9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFacial.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFacial.h @@ -23,9 +23,9 @@ class CTaskComplexFacial : public CTaskComplex { CTaskComplexFacial(); ~CTaskComplexFacial() override = default; // 0x690D90 - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexFacial(); }; // 0x6928B0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexFacial(); }; // 0x6928B0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFallAndGetUp.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFallAndGetUp.h index 373ee5723d..2a67a99f67 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFallAndGetUp.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFallAndGetUp.h @@ -15,12 +15,12 @@ class CTaskComplexFallAndGetUp : public CTaskComplex { CTaskComplexFallAndGetUp(int32 nDir, int32 nFallDownTime); ~CTaskComplexFallAndGetUp() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexFallAndGetUp(m_nFallAnimId, m_nFallAnimGroup, m_nFallDownTime); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexFallAndGetUp(m_nFallAnimId, m_nFallAnimGroup, m_nFallDownTime); } CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateSubTask(eTaskType taskType); bool IsFalling(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFallToDeath.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFallToDeath.h index 5768748d6e..774e4dfb91 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFallToDeath.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFallToDeath.h @@ -31,9 +31,9 @@ class CTaskComplexFallToDeath : public CTaskComplex { CTaskComplexFallToDeath(int32 direction, const CVector& posn, bool bFallToDeathOverRailing, bool a5); ~CTaskComplexFallToDeath() override = default; // 0x6790B0 - eTaskType GetTaskType() override { return Type; }; // 0x6790A0 - CTask* Clone() override { return new CTaskComplexFallToDeath(static_cast(m_nFallToDeathDir), m_Posn, b0x8, b0x10); } // 0x67C480 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; }; // 0x6790A0 + CTask* Clone() const override { return new CTaskComplexFallToDeath(static_cast(m_nFallToDeathDir), m_Posn, b0x8, b0x10); } // 0x67C480 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* ControlSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFleeAnyMeans.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFleeAnyMeans.h index 3c3adf6208..d31babb1e4 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFleeAnyMeans.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFleeAnyMeans.h @@ -38,8 +38,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexFleeAnyMeans : public CTaskComplexSmartFle CTask* CreateSubTask(eTaskType taskId, CPed* ped); - CTask* Clone() override { return new CTaskComplexFleeAnyMeans{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexFleeAnyMeans{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFleeEntity.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFleeEntity.h index 589cb153aa..433da0c3b5 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFleeEntity.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFleeEntity.h @@ -44,8 +44,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexFleeEntity : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType); - CTask* Clone() override { return new CTaskComplexFleeEntity{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexFleeEntity{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFleePoint.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFleePoint.h index 81da9810d1..220cacf632 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFleePoint.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFleePoint.h @@ -40,9 +40,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexFleePoint : public CTaskComplex { void SetFleePosition(CVector const& fleePos, float safeDistance, bool scream); void ComputeTargetPoint(CPed const* ped); - CTask* Clone() override { return new CTaskComplexFleePoint{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexFleePoint{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed * ped) override; CTask* CreateFirstSubTask(CPed * ped) override; CTask* ControlSubTask(CPed * ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFleeShooting.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFleeShooting.h index 2d5cdbafa6..fb4f351437 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFleeShooting.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFleeShooting.h @@ -24,9 +24,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexFleeShooting : public CTaskComplexSmartFle CTaskComplexFleeShooting(const CTaskComplexFleeShooting&); ~CTaskComplexFleeShooting(); - CTask* Clone() override { return new CTaskComplexFleeShooting{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexFleeShooting{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderAnyMeans.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderAnyMeans.h index 0701e251b3..983a77bda9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderAnyMeans.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderAnyMeans.h @@ -26,8 +26,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexFollowLeaderAnyMeans : public CTaskComplex CTask* CreateSubTask(eTaskType taskType, CPed const* ped); - CTask* Clone() override { return new CTaskComplexFollowLeaderAnyMeans{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexFollowLeaderAnyMeans{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderInFormation.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderInFormation.cpp index 8399a71746..197be71f14 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderInFormation.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderInFormation.cpp @@ -3,17 +3,17 @@ #include "TaskComplexFollowLeaderInFormation.h" void CTaskComplexFollowLeaderInFormation::InjectHooks() { - RH_ScopedClass(CTaskComplexFollowLeaderInFormation); + RH_ScopedVirtualClass(CTaskComplexFollowLeaderInFormation, 0x870c3c, 11); RH_ScopedCategory("Tasks/TaskTypes"); RH_ScopedInstall(Constructor, 0x6949A0, { .reversed = false }); - // RH_ScopedOverloadedInstall(Destructor, "", 0x694A40, CTaskComplexFollowLeaderInFormation * (CTaskComplexFollowLeaderInFormation::*)()); + RH_ScopedInstall(Destructor, 0x694A40); - RH_ScopedVirtualInstall(Clone, 0x695740, { .reversed = false }); - RH_ScopedVirtualInstall(GetTaskType, 0x694A30, { .reversed = false }); - RH_ScopedVirtualInstall(CreateNextSubTask, 0x696820, { .reversed = false }); - RH_ScopedVirtualInstall(CreateFirstSubTask, 0x6968E0, { .reversed = false }); - RH_ScopedVirtualInstall(ControlSubTask, 0x696940, { .reversed = false }); + RH_ScopedVMTInstall(Clone, 0x695740, { .reversed = false }); + RH_ScopedVMTInstall(GetTaskType, 0x694A30, { .reversed = false }); + RH_ScopedVMTInstall(CreateNextSubTask, 0x696820, { .reversed = false }); + RH_ScopedVMTInstall(CreateFirstSubTask, 0x6968E0, { .reversed = false }); + RH_ScopedVMTInstall(ControlSubTask, 0x696940, { .reversed = false }); } // 0x6949A0 @@ -21,9 +21,20 @@ CTaskComplexFollowLeaderInFormation::CTaskComplexFollowLeaderInFormation(CPedGro plugin::CallMethod<0x6949A0, CTaskComplexFollowLeaderInFormation*, CPedGroup*, CPed*, const CVector&, float>(this, pedGroup, ped, posn, a5); } +// For 0x695740 +CTaskComplexFollowLeaderInFormation::CTaskComplexFollowLeaderInFormation(const CTaskComplexFollowLeaderInFormation& o) : + CTaskComplexFollowLeaderInFormation{ + o.m_Group, + o.m_Leader, + o.m_Pos, + o.m_Dist + } +{ +} + // 0x694A40 CTaskComplexFollowLeaderInFormation::~CTaskComplexFollowLeaderInFormation() { - plugin::CallMethod<0x694A40, CTaskComplexFollowLeaderInFormation*>(this); + CEntity::SafeCleanUpRef(m_Leader); } // 0x696820 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderInFormation.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderInFormation.h index 340edb031f..f2d76c4e13 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderInFormation.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowLeaderInFormation.h @@ -6,7 +6,7 @@ class CPedGroup; class CPed; -class CTaskComplexFollowLeaderInFormation : public CTaskComplex { +class NOTSA_EXPORT_VTABLE CTaskComplexFollowLeaderInFormation : public CTaskComplex { CPedGroup* m_Group; CPed* m_Leader; CVector m_Pos; @@ -17,17 +17,18 @@ class CTaskComplexFollowLeaderInFormation : public CTaskComplex { static constexpr auto Type = TASK_COMPLEX_FOLLOW_LEADER_IN_FORMATION; struct Offsets { - CVector2D offsets[8]; - CVector2D movingOffsets[8]; - float scale; + std::array offsets; + std::array movingOffsets; + float scale; }; static inline auto& ms_offsets = StaticRef(); CTaskComplexFollowLeaderInFormation(CPedGroup* pedGroup, CPed* ped, const CVector& posn, float dist = -1.f); + CTaskComplexFollowLeaderInFormation(const CTaskComplexFollowLeaderInFormation&); ~CTaskComplexFollowLeaderInFormation() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return plugin::CallMethodAndReturn(this); } // 0x695740 + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexFollowLeaderInFormation{*this}; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -38,11 +39,5 @@ class CTaskComplexFollowLeaderInFormation : public CTaskComplex { CTaskComplexFollowLeaderInFormation* Constructor(CPedGroup* pedGroup, CPed* ped, CVector const& posn, float a5) { return this; } CTaskComplexFollowLeaderInFormation* Destructor() { this->CTaskComplexFollowLeaderInFormation::~CTaskComplexFollowLeaderInFormation(); return this; } - - CTask* Clone_Reversed() { return CTaskComplexFollowLeaderInFormation::Clone(); } - eTaskType GetTaskType_Reversed() { return CTaskComplexFollowLeaderInFormation::GetTaskType(); } - CTask* CreateNextSubTask_Reversed(CPed* ped) { return CTaskComplexFollowLeaderInFormation::CreateNextSubTask(ped); } - CTask* CreateFirstSubTask_Reversed(CPed* ped) { return CTaskComplexFollowLeaderInFormation::CreateFirstSubTask(ped); } - CTask* ControlSubTask_Reversed(CPed* ped) { return CTaskComplexFollowLeaderInFormation::ControlSubTask(ped); } }; VALIDATE_SIZE(CTaskComplexFollowLeaderInFormation, 0x28); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowNodeRoute.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowNodeRoute.cpp index 9849c2815a..12d4408005 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowNodeRoute.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowNodeRoute.cpp @@ -35,7 +35,7 @@ CTaskComplexFollowNodeRoute::~CTaskComplexFollowNodeRoute() { } // 0x6713E0 -CTask* CTaskComplexFollowNodeRoute::Clone() { +CTask* CTaskComplexFollowNodeRoute::Clone() const { auto* task = new CTaskComplexFollowNodeRoute(m_nMode, m_vecTargetPos, m_fRadius, m_fUnkn1, m_fUnkn2, m_bUnknFlag0x1, m_nTime, m_bUnknFlag0x8); task->m_nodeAddress1 = m_nodeAddress1; return task; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowNodeRoute.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowNodeRoute.h index cfd1ac9625..f6c6d3d496 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowNodeRoute.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowNodeRoute.h @@ -43,10 +43,10 @@ class CTaskComplexFollowNodeRoute : public CTaskComplex { CTaskComplexFollowNodeRoute(int32 mode, const CVector& targetPos, float radius, float fUnkn1, float fUnkn2, bool bUnknFlag, int32 time, bool bUnknFlag2); ~CTaskComplexFollowNodeRoute() override; - eTaskType GetTaskType() override { return Type; } // 0x66EB60 - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } // 0x66EB60 + CTask* Clone() const override; void StopTimer(const CEvent* event) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowPedFootsteps.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowPedFootsteps.h index 31b4b5d38e..8fe5da797c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowPedFootsteps.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowPedFootsteps.h @@ -19,11 +19,11 @@ class CTaskComplexFollowPedFootsteps : public CTaskComplex { explicit CTaskComplexFollowPedFootsteps(CPed* ped); ~CTaskComplexFollowPedFootsteps() override; - CTask* Clone() override { + CTask* Clone() const override { return new CTaskComplexFollowPedFootsteps(m_targetPed); } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowPointRoute.h b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowPointRoute.h index 4da0cea1c9..083fd443bd 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexFollowPointRoute.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexFollowPointRoute.h @@ -58,9 +58,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexFollowPointRoute : public CTaskComplex { eTaskType CalcGoToTaskType(CPed* ped, eTaskType subTaskType); float CalcBlendRatio(CPed* ped, bool slowing); - CTask* Clone() override { return new CTaskComplexFollowPointRoute{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexFollowPointRoute{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGangFollower.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexGangFollower.cpp index c33726791d..005159c467 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGangFollower.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGangFollower.cpp @@ -21,6 +21,7 @@ CTaskComplexGangFollower::CTaskComplexGangFollower(CPedGroup* pedGroup, CPed* pe plugin::CallMethod<0x65EAA0, CTaskComplexGangFollower*, CPedGroup*, CPed*, uint8, CVector, float>(this, pedGroup, ped, a4, pos, a6); } + // 0x65EBB0 CTaskComplexGangFollower::~CTaskComplexGangFollower() { plugin::CallMethod<0x65EBB0, CTaskComplexGangFollower*>(this); @@ -32,8 +33,8 @@ void CTaskComplexGangFollower::CalculateOffsetPosition(CVector& pos) { } // 0x65ECB0 -CTask* CTaskComplexGangFollower::Clone() { - return plugin::CallMethodAndReturn(this); +CTask* CTaskComplexGangFollower::Clone() const { + return plugin::CallMethodAndReturn(this); } // 0x65EC30 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGangFollower.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGangFollower.h index 703f16b3ec..74fe1a88f0 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGangFollower.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGangFollower.h @@ -26,9 +26,9 @@ class CTaskComplexGangFollower : public CTaskComplex { CTaskComplexGangFollower(CPedGroup* pedGroup, CPed* ped, uint8 a4, CVector pos, float a6); ~CTaskComplexGangFollower() override; - eTaskType GetTaskType() override{ return Type; } - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override{ return Type; } + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGangJoinRespond.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexGangJoinRespond.cpp index 49a88d11a2..67c6e71048 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGangJoinRespond.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGangJoinRespond.cpp @@ -19,15 +19,15 @@ void CTaskComplexGangJoinRespond::InjectHooks() { // 0x6616F0 CTaskComplexGangJoinRespond::CTaskComplexGangJoinRespond(uint8 a2) : CTaskComplex() { - byteC = a2; - m_bAnimBlockRefAdded = false; + m_response = a2; + m_animsReferenced = false; } // 0x661720 CTaskComplexGangJoinRespond::~CTaskComplexGangJoinRespond() { - if (m_bAnimBlockRefAdded) { + if (m_animsReferenced) { CAnimManager::RemoveAnimBlockRef(CAnimManager::GetAnimationBlockIndex("gangs")); - m_bAnimBlockRefAdded = false; + m_animsReferenced = false; } } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGangJoinRespond.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGangJoinRespond.h index bc9884e304..856d547d96 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGangJoinRespond.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGangJoinRespond.h @@ -6,9 +6,9 @@ class CPed; class CTaskComplexGangJoinRespond : public CTaskComplex { public: - bool byteC; - bool m_bAnimBlockRefAdded; - int32 b10; + bool m_response; + bool m_animsReferenced; + int32 m_attempts; public: static constexpr auto Type = TASK_COMPLEX_GANG_JOIN_RESPOND; @@ -16,9 +16,9 @@ class CTaskComplexGangJoinRespond : public CTaskComplex { explicit CTaskComplexGangJoinRespond(uint8 a2); ~CTaskComplexGangJoinRespond() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexGangJoinRespond(byteC); } // 0x662290; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return true; } // 0x661790; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexGangJoinRespond(m_response); } // 0x662290; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } // 0x661790; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGangLeader.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexGangLeader.cpp index df5c3b82dd..7c0cd171a7 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGangLeader.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGangLeader.cpp @@ -236,7 +236,7 @@ CTask* CTaskComplexGangLeader::ControlSubTask(CPed* ped) { if (m_exhaleTimer.IsOutOfTime()) { // 0x6624C1 if (ped->m_pRwClump) { if (auto matrix = RwFrameGetMatrix(RpClumpGetFrame(ped->m_pRwClump))) { - RwV3d PoS{ 0.f, 0.1f, 0.f }; + CVector PoS{ 0.f, 0.1f, 0.f }; if (const auto fx = g_fxMan.CreateFxSystem("exhale", &PoS, matrix)) { fx->AttachToBone(ped, ePedBones::BONE_HEAD); fx->PlayAndKill(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGangLeader.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGangLeader.h index f3868bac39..5544acceee 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGangLeader.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGangLeader.h @@ -32,9 +32,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexGangLeader : public CTaskComplex { CPed* TryToPassObject(CPed* ped, CPedGroup* group); void UnrefAnimBlock(); // NOTSA - CTask* Clone() override { return new CTaskComplexGangLeader{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexGangLeader{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGetOnBoatSeat.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGetOnBoatSeat.h index 39654ef330..020ccea669 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGetOnBoatSeat.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGetOnBoatSeat.h @@ -13,9 +13,9 @@ class CTaskComplexGetOnBoatSeat : CTaskComplex { explicit CTaskComplexGetOnBoatSeat(CVehicle* vehicle); ~CTaskComplexGetOnBoatSeat() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexGetOnBoatSeat(m_Vehicle); } // 0x64A3B0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexGetOnBoatSeat(m_Vehicle); } // 0x64A3B0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGetUpAndStandStill.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGetUpAndStandStill.h index bc13ed8299..2521087d60 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGetUpAndStandStill.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGetUpAndStandStill.h @@ -9,8 +9,8 @@ class CTaskComplexGetUpAndStandStill : public CTaskComplex { CTaskComplexGetUpAndStandStill() = default; ~CTaskComplexGetUpAndStandStill() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexGetUpAndStandStill(); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexGetUpAndStandStill(); } CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToAttractor.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToAttractor.h index 691f5814f7..8d7cbe9027 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToAttractor.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToAttractor.h @@ -17,9 +17,9 @@ class CTaskComplexGoToAttractor : public CTaskComplex { CTaskComplexGoToAttractor(CPedAttractor* attractor, CVector const& pos, float heading, float attrTime, int32 queueNumber, int32 a7); ~CTaskComplexGoToAttractor() override = default; // 0x66B6A0 - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexGoToAttractor(m_Attractor, m_vecAttrPosn, m_fAttrHeading, m_fAttrTime, m_nQueueNumber, field_28); } // 0x66D130 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexGoToAttractor(m_Attractor, m_vecAttrPosn, m_fAttrHeading, m_fAttrTime, m_nQueueNumber, field_28); } // 0x66D130 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToBoatSteeringWheel.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToBoatSteeringWheel.h index 88ad6fcbc2..17855b4f21 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToBoatSteeringWheel.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToBoatSteeringWheel.h @@ -19,8 +19,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexGoToBoatSteeringWheel : public CTaskComple CTask* CreateSubTask(eTaskType taskType); void ComputeTargetPos(); - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexGoToBoatSteeringWheel(m_Vehicle); } // 0x64A350 + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexGoToBoatSteeringWheel(m_Vehicle); } // 0x64A350 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToCarDoorAndStandStill.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToCarDoorAndStandStill.h index d86fdfb246..877f6a7ac8 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToCarDoorAndStandStill.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToCarDoorAndStandStill.h @@ -31,9 +31,9 @@ class CTaskComplexGoToCarDoorAndStandStill : public CTaskComplex { CTaskComplexGoToCarDoorAndStandStill(CVehicle* vehicle, int32 moveState, bool a4, int32 a5, float a6, float a7, float a8, int32 a9); ~CTaskComplexGoToCarDoorAndStandStill() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexGoToCarDoorAndStandStill(m_Vehicle, m_nMoveState, f14, f44, m_fRadius, f1C, f20, f24); } // 0x6498B0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexGoToCarDoorAndStandStill(m_Vehicle, m_nMoveState, f14, f44, m_fRadius, f1C, f20, f24); } // 0x6498B0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAiming.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAiming.h index 266a514a3a..daf4ee663c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAiming.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAiming.h @@ -27,8 +27,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexGoToPointAiming : public CTaskComplex { CTaskComplexGoToPointAiming(const CTaskComplexGoToPointAiming&); ~CTaskComplexGoToPointAiming() override; - eTaskType GetTaskType() override { return Type; } // 0x668860 - CTask* Clone() override { return new CTaskComplexGoToPointAiming{ *this }; } // 0x66CD80 + eTaskType GetTaskType() const override { return Type; } // 0x668860 + CTask* Clone() const override { return new CTaskComplexGoToPointAiming{ *this }; } // 0x66CD80 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStill.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStill.cpp index 2a9ea6e0b9..5efdfafeed 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStill.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStill.cpp @@ -57,7 +57,7 @@ CTaskComplexGoToPointAndStandStill::~CTaskComplexGoToPointAndStandStill() { } // 0x66CEA0 -CTask* CTaskComplexGoToPointAndStandStill::Clone() { +CTask* CTaskComplexGoToPointAndStandStill::Clone() const { return CTaskComplexGoToPointAndStandStill::Clone_Reversed(); } @@ -76,7 +76,7 @@ CTask* CTaskComplexGoToPointAndStandStill::ControlSubTask(CPed* ped) { return CTaskComplexGoToPointAndStandStill::ControlSubTask_Reversed(ped); } -CTask* CTaskComplexGoToPointAndStandStill::Clone_Reversed() { +CTask* CTaskComplexGoToPointAndStandStill::Clone_Reversed() const { return new CTaskComplexGoToPointAndStandStill(m_moveState, m_vecTargetPoint, m_fRadius, m_fMoveStateRadius, m_b01, m_bGoToPoint); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStill.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStill.h index fdda6141ea..9a2082d2a4 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStill.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStill.h @@ -28,13 +28,13 @@ class CTaskComplexGoToPointAndStandStill : public CTaskComplex { CTaskComplexGoToPointAndStandStill(eMoveState moveState, const CVector& targetPoint, float fRadius = 1.5f, float fMoveStateRadius = 2.f, bool bUnknown = false, bool bGoToPoint = false); ~CTaskComplexGoToPointAndStandStill() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* ControlSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillAndAchieveHeading.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillAndAchieveHeading.h index 9c278ef423..5b759bf1d6 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillAndAchieveHeading.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillAndAchieveHeading.h @@ -20,8 +20,8 @@ class CTaskComplexGoToPointAndStandStillAndAchieveHeading : public CTaskComplex CTaskComplexGoToPointAndStandStillAndAchieveHeading(eMoveState moveState, const CVector& targetPos, float angle, float radius, float changeRateMult, float maxHeading); ~CTaskComplexGoToPointAndStandStillAndAchieveHeading() override = default; // 0x668D40 - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexGoToPointAndStandStillAndAchieveHeading(m_MoveState, m_TargetPos, m_Angle, m_Radius, m_ChangeRateMult, m_MaxHeading); } // 0x66CFD0 + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexGoToPointAndStandStillAndAchieveHeading(m_MoveState, m_TargetPos, m_Angle, m_Radius, m_ChangeRateMult, m_MaxHeading); } // 0x66CFD0 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillTimed.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillTimed.cpp index c96d9476d0..8bc0d782d4 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillTimed.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillTimed.cpp @@ -25,7 +25,7 @@ CTaskComplexGoToPointAndStandStillTimed::~CTaskComplexGoToPointAndStandStillTime } // 0x66CF30 -CTask* CTaskComplexGoToPointAndStandStillTimed::Clone() { +CTask* CTaskComplexGoToPointAndStandStillTimed::Clone() const { return CTaskComplexGoToPointAndStandStillTimed::Clone_Reversed(); } @@ -49,7 +49,7 @@ CTask* CTaskComplexGoToPointAndStandStillTimed::ControlSubTask(CPed* ped) { return CTaskComplexGoToPointAndStandStillTimed::ControlSubTask_Reversed(ped); } -CTask* CTaskComplexGoToPointAndStandStillTimed::Clone_Reversed() { +CTask* CTaskComplexGoToPointAndStandStillTimed::Clone_Reversed() const { return new CTaskComplexGoToPointAndStandStillTimed(m_moveState, m_vecTargetPoint, m_fRadius, m_fMoveStateRadius, m_nTime); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillTimed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillTimed.h index 5ae93365a4..64e59cf659 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillTimed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAndStandStillTimed.h @@ -12,9 +12,9 @@ class CTaskComplexGoToPointAndStandStillTimed : public CTaskComplexGoToPointAndS CTaskComplexGoToPointAndStandStillTimed(eMoveState moveState, const CVector& targetPoint, float fRadius, float fMoveStateRadius, int32 time); ~CTaskComplexGoToPointAndStandStillTimed() override; - CTask* Clone() override; + CTask* Clone() const override; void StopTimer(const CEvent* event) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -28,7 +28,7 @@ class CTaskComplexGoToPointAndStandStillTimed : public CTaskComplexGoToPointAndS return this; } - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; void StopTimer_Reversed(const CEvent* event); bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); CTask* CreateFirstSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAnyMeans.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAnyMeans.h index eb0342f001..41d9c5f774 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAnyMeans.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointAnyMeans.h @@ -25,8 +25,8 @@ class CTaskComplexGoToPointAnyMeans : public CTaskComplex { CTaskComplexGoToPointAnyMeans(int32 moveState, const CVector& posn, CVehicle* vehicle, float radius, int32 modelId); ~CTaskComplexGoToPointAnyMeans() override; - eTaskType GetTaskType() override { return Type; } // 0x66B780 - CTask* Clone() override { return new CTaskComplexGoToPointAnyMeans(m_MoveState, m_Pos, m_Vehicle, m_fRadius, m_nModelId); } // 0x66D1E0 + eTaskType GetTaskType() const override { return Type; } // 0x66B780 + CTask* Clone() const override { return new CTaskComplexGoToPointAnyMeans(m_MoveState, m_Pos, m_Vehicle, m_fRadius, m_nModelId); } // 0x66D1E0 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointShooting.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointShooting.h index 995e6aae29..cb59c54c91 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointShooting.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGoToPointShooting.h @@ -19,8 +19,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexGoToPointShooting : public CTaskComplexGoT CTaskComplexGoToPointShooting(const CTaskComplexGoToPointShooting&); ~CTaskComplexGoToPointShooting() override = default; - eTaskType GetTaskType() override { return Type; } // 0x668CC0 - CTask* Clone() override { return new CTaskComplexGoToPointAiming{ *this }; } // 0x66CE10 + eTaskType GetTaskType() const override { return Type; } // 0x668CC0 + CTask* Clone() const override { return new CTaskComplexGoToPointAiming{ *this }; } // 0x66CE10 private: friend void InjectHooksMain(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGotoDoorAndOpen.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexGotoDoorAndOpen.cpp index 973b7fdad5..9da81ed51d 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGotoDoorAndOpen.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGotoDoorAndOpen.cpp @@ -47,7 +47,7 @@ CTaskComplexGotoDoorAndOpen::~CTaskComplexGotoDoorAndOpen() { } // 0x66BCA0 -CTask* CTaskComplexGotoDoorAndOpen::Clone() { +CTask* CTaskComplexGotoDoorAndOpen::Clone() const { if ((m_nFlags & 1) != 0) { // todo: flags return new CTaskComplexGotoDoorAndOpen(m_Object); } else { diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexGotoDoorAndOpen.h b/source/game_sa/Tasks/TaskTypes/TaskComplexGotoDoorAndOpen.h index 26c95ac769..4929172bf6 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexGotoDoorAndOpen.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexGotoDoorAndOpen.h @@ -25,9 +25,9 @@ class CTaskComplexGotoDoorAndOpen : public CTaskComplex { CTaskComplexGotoDoorAndOpen(const CVector& start, const CVector& end); ~CTaskComplexGotoDoorAndOpen() override; - eTaskType GetTaskType() override{ return Type; } // 0x66BB90 - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override{ return Type; } // 0x66BB90 + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexHitByGunResponse.h b/source/game_sa/Tasks/TaskTypes/TaskComplexHitByGunResponse.h index 3a6d6c9f30..3f3a87ab50 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexHitByGunResponse.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexHitByGunResponse.h @@ -12,9 +12,9 @@ class CTaskComplexHitByGunResponse : public CTaskComplex { explicit CTaskComplexHitByGunResponse(int32 direction); ~CTaskComplexHitByGunResponse() override = default; // 0x631E30 - eTaskType GetTaskType() override { return Type; } // 0x631DF0 - CTask* Clone() override { return new CTaskComplexHitByGunResponse(m_Direction); } // 0x636640 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x631DF0 + CTask* Clone() const override { return new CTaskComplexHitByGunResponse(m_Direction); } // 0x636640 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } // 0x631E40 CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } // 0x631F70 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexHitResponse.h b/source/game_sa/Tasks/TaskTypes/TaskComplexHitResponse.h index bdfb3057aa..3ccd3ed51c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexHitResponse.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexHitResponse.h @@ -12,8 +12,8 @@ class CTaskComplexHitResponse : public CTaskComplex { explicit CTaskComplexHitResponse(int32 direction); ~CTaskComplexHitResponse() override = default; // 0x631DA0 - eTaskType GetTaskType() override { return Type; } // 0x631D90 - CTask* Clone() override { return new CTaskComplexHitResponse(m_Direction); } // 0x6365E0 + eTaskType GetTaskType() const override { return Type; } // 0x631D90 + CTask* Clone() const override { return new CTaskComplexHitResponse(m_Direction); } // 0x6365E0 CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } // 0x631DB0 CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } // 0x631DC0 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexInAirAndLand.h b/source/game_sa/Tasks/TaskTypes/TaskComplexInAirAndLand.h index 217363b262..f5549eb917 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexInAirAndLand.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexInAirAndLand.h @@ -14,8 +14,8 @@ class CTaskComplexInAirAndLand : public CTaskComplex { CTaskComplexInAirAndLand(bool bUsingJumpGlide, bool bUsingFallGlide); ~CTaskComplexInAirAndLand() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexInAirAndLand(m_bUsingJumpGlide, m_bUsingFallGlide); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexInAirAndLand(m_bUsingJumpGlide, m_bUsingFallGlide); } CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexInWater.h b/source/game_sa/Tasks/TaskTypes/TaskComplexInWater.h index be518f0275..fe917a304e 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexInWater.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexInWater.h @@ -16,8 +16,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexInWater : public CTaskComplex { CTaskComplexInWater(const CTaskComplexInWater&); ~CTaskComplexInWater() = default; - CTask* Clone() override { return new CTaskComplexInWater{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexInWater{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexInvestigateDeadPed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexInvestigateDeadPed.h index aa4d4c68eb..c24a7f6b43 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexInvestigateDeadPed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexInvestigateDeadPed.h @@ -30,8 +30,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexInvestigateDeadPed : public CTaskComplex { void ComputeTargetPos(CPed* ped, CVector& targetPos); CTask* CreateSubTask(eTaskType taskType, CPed* ped); - CTask* Clone() override { return new CTaskComplexInvestigateDeadPed{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexInvestigateDeadPed{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed * ped) override; CTask* CreateFirstSubTask(CPed * ped) override; CTask* ControlSubTask(CPed * ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexInvestigateDisturbance.h b/source/game_sa/Tasks/TaskTypes/TaskComplexInvestigateDisturbance.h index 623be6df62..a373ec40a2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexInvestigateDisturbance.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexInvestigateDisturbance.h @@ -22,9 +22,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexInvestigateDisturbance : public CTaskCompl CTaskComplexInvestigateDisturbance(const CTaskComplexInvestigateDisturbance&); ~CTaskComplexInvestigateDisturbance(); - CTask* Clone() override { return new CTaskComplexInvestigateDisturbance{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return !m_pSubTask || m_pSubTask->MakeAbortable(ped, priority, event); } + CTask* Clone() const override { return new CTaskComplexInvestigateDisturbance{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return !m_pSubTask || m_pSubTask->MakeAbortable(ped, priority, event); } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexJump.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexJump.cpp index edcf91bc90..d2db218d64 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexJump.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexJump.cpp @@ -30,7 +30,7 @@ CTaskComplexJump::CTaskComplexJump(eComplexJumpType type) : CTaskComplex() { } // 0x67C5A0 -CTask* CTaskComplexJump::Clone() { +CTask* CTaskComplexJump::Clone() const { return Clone_Reversed(); } @@ -49,7 +49,7 @@ bool CTaskComplexJump::MakeAbortable(CPed* ped, eAbortPriority priority, const C return MakeAbortable_Reversed(ped, priority, event); } -CTask* CTaskComplexJump::Clone_Reversed() { +CTask* CTaskComplexJump::Clone_Reversed() const { auto newTask = new CTaskComplexJump(m_nType); newTask->m_bHighJump = this->m_bHighJump; return newTask; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexJump.h b/source/game_sa/Tasks/TaskTypes/TaskComplexJump.h index d88e04c8f7..26c40a2017 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexJump.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexJump.h @@ -24,12 +24,12 @@ class CTaskComplexJump : public CTaskComplex { explicit CTaskComplexJump(eComplexJumpType type); ~CTaskComplexJump() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; private: friend void InjectHooksMain(); @@ -37,7 +37,7 @@ class CTaskComplexJump : public CTaskComplex { CTaskComplexJump* Constructor(eComplexJumpType jumpType); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* CreateNextSubTask_Reversed(CPed* ped); bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedGroupOnFoot.h b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedGroupOnFoot.h index 083e96499d..795a3fac94 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedGroupOnFoot.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedGroupOnFoot.h @@ -32,8 +32,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexKillPedGroupOnFoot : public CTaskComplex { CPed* ComputeNearestLivingGroupPed(CPed& ped, bool flag = false); eTaskType ComputeNewTargetAndTaskType(CPed* ped); // NOTSA - CTask* Clone() override { return new CTaskComplexKillPedGroupOnFoot{ *this }; } // 0x6237C0 - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexKillPedGroupOnFoot{ *this }; } // 0x6237C0 + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } // 0x0x622310 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFoot.h b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFoot.h index c60b7d6935..b31ef14f11 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFoot.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFoot.h @@ -36,9 +36,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexKillPedOnFoot : public CTaskComplex { ~CTaskComplexKillPedOnFoot() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexKillPedOnFoot(m_target, m_time, m_pedFlags, m_actionDelay, m_actionChance, m_nCompetence); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexKillPedOnFoot(m_target, m_time, m_pedFlags, m_actionDelay, m_actionChance, m_nCompetence); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootArmed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootArmed.h index e846e8c3bb..48440462de 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootArmed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootArmed.h @@ -63,9 +63,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexKillPedOnFootArmed : public CTaskComplex { bool IsPedInLeaderFiringLine(CPed* ped); CTask* CreateSubTask(eTaskType taskType, CPed* ped); - CTask* Clone() override { return new CTaskComplexKillPedOnFootArmed{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexKillPedOnFootArmed{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootMelee.h b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootMelee.h index 21f9e7cddf..3897f7b65b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootMelee.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootMelee.h @@ -33,8 +33,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexKillPedOnFootMelee : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType, CPed * ped); bool IsTargetInRange(CPed* ped, float range); // NOTSA - CTask* Clone() override { return new CTaskComplexKillPedOnFootMelee{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexKillPedOnFootMelee{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootStealth.h b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootStealth.h index a8feca9371..7085c7e563 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootStealth.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexKillPedOnFootStealth.h @@ -21,8 +21,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexKillPedOnFootStealth : public CTaskComplex CTaskComplexKillPedOnFootStealth(const CTaskComplexKillPedOnFootStealth&); // NOTSA ~CTaskComplexKillPedOnFootStealth() = default; - CTask* Clone() override { return new CTaskComplexKillPedOnFootStealth{ *this }; } // 0x623400 - eTaskType GetTaskType() override { return Type; } // 0x620FE0 + CTask* Clone() const override { return new CTaskComplexKillPedOnFootStealth{ *this }; } // 0x623400 + eTaskType GetTaskType() const override { return Type; } // 0x620FE0 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveAnyCar.h b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveAnyCar.h index 0cf815963b..eeb8968e5e 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveAnyCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveAnyCar.h @@ -14,8 +14,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexLeaveAnyCar : public CTaskComplex { CTaskComplexLeaveAnyCar(int32 delayTime, bool sensibleLeaveCar, bool forceGetOut); ~CTaskComplexLeaveAnyCar() = default; - CTask* Clone() override { return new CTaskComplexLeaveAnyCar(m_nDelayTime, m_sensibleLeaveCar, m_forceGetOut); } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexLeaveAnyCar(m_nDelayTime, m_sensibleLeaveCar, m_forceGetOut); } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveBoat.h b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveBoat.h index 6388e69208..a2a549d061 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveBoat.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveBoat.h @@ -21,8 +21,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexLeaveBoat : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType); - CTask* Clone() override { return new CTaskComplexLeaveBoat{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexLeaveBoat{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCar.h b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCar.h index 9ab0ab9d23..95aed4bc1e 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCar.h @@ -34,9 +34,9 @@ class CTaskComplexLeaveCar : public CTaskComplex { CTaskComplexLeaveCar(const CTaskComplexLeaveCar& o); // NOTSA ~CTaskComplexLeaveCar() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexLeaveCar{ *this }; } // 0x63D9E0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexLeaveCar{ *this }; } // 0x63D9E0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndDie.h b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndDie.h index fbbb29e80f..787acc1d51 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndDie.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndDie.h @@ -13,7 +13,7 @@ class NOTSA_EXPORT_VTABLE CTaskComplexLeaveCarAndDie : public CTaskComplexLeaveC CTaskComplexLeaveCarAndDie(const CTaskComplexLeaveCarAndDie&); - CTask* Clone() override { return new CTaskComplexLeaveCarAndDie{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexLeaveCarAndDie{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateFirstSubTask(CPed * ped) override; }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndFlee.h b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndFlee.h index cf5394f1df..4912302d27 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndFlee.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndFlee.h @@ -27,8 +27,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexLeaveCarAndFlee : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType); - CTask* Clone() override { return new CTaskComplexLeaveCarAndFlee{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexLeaveCarAndFlee{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndWander.h b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndWander.h index f022d54ee9..6550d553b2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndWander.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAndWander.h @@ -27,8 +27,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexLeaveCarAndWander : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType); - CTask* Clone() override { return new CTaskComplexLeaveCarAndWander{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexLeaveCarAndWander{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAsPassengerWait.h b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAsPassengerWait.h index 7dd2913301..09c4eb402d 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAsPassengerWait.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexLeaveCarAsPassengerWait.h @@ -5,7 +5,6 @@ class CPed; class CTask; class CVehicle; -class CTaskComplexLeaveCarAsPassengerWait; class NOTSA_EXPORT_VTABLE CTaskComplexLeaveCarAsPassengerWait : public CTaskComplex { public: @@ -24,8 +23,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexLeaveCarAsPassengerWait : public CTaskComp CPed* GetNextPassengerToLeave(); void SetPedInZerothPassengerSeat(CPed* ped); - CTask* Clone() override { return new CTaskComplexLeaveCarAsPassengerWait{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexLeaveCarAsPassengerWait{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -36,6 +35,7 @@ class NOTSA_EXPORT_VTABLE CTaskComplexLeaveCarAsPassengerWait : public CTaskComp this->CTaskComplexLeaveCarAsPassengerWait::CTaskComplexLeaveCarAsPassengerWait(veh); return this; } + // 0x63B4A0 CTaskComplexLeaveCarAsPassengerWait* Destructor() { this->CTaskComplexLeaveCarAsPassengerWait::~CTaskComplexLeaveCarAsPassengerWait(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexMedicTreatInjuredPed.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexMedicTreatInjuredPed.cpp index f55bac111b..1dd3954ce4 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexMedicTreatInjuredPed.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexMedicTreatInjuredPed.cpp @@ -115,7 +115,7 @@ void CTaskComplexMedicTreatInjuredPed::FindAccidentPosition(CPed* ped, CPed* tar } // 0x659AF0 -CTask* CTaskComplexMedicTreatInjuredPed::Clone() { +CTask* CTaskComplexMedicTreatInjuredPed::Clone() const { return Clone_Reversed(); } @@ -134,7 +134,7 @@ CTask* CTaskComplexMedicTreatInjuredPed::ControlSubTask(CPed* ped) { return ControlSubTask_Reversed(ped); } -CTask* CTaskComplexMedicTreatInjuredPed::Clone_Reversed() { +CTask* CTaskComplexMedicTreatInjuredPed::Clone_Reversed() const { return new CTaskComplexMedicTreatInjuredPed(m_pVehicle, m_pPartnerMedic, m_bIsDriver); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexMedicTreatInjuredPed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexMedicTreatInjuredPed.h index 0f11f5c078..57ea83b323 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexMedicTreatInjuredPed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexMedicTreatInjuredPed.h @@ -19,8 +19,8 @@ class CTaskComplexMedicTreatInjuredPed : public CTaskComplex { CTaskComplexMedicTreatInjuredPed(CVehicle* vehicle, CPed* ped, bool isDriver); ~CTaskComplexMedicTreatInjuredPed() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -36,7 +36,7 @@ class CTaskComplexMedicTreatInjuredPed : public CTaskComplex { CTaskComplexMedicTreatInjuredPed* Constructor(CVehicle* vehicle, CPed* ped, bool isDriver); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* ControlSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexMoveBackAndJump.h b/source/game_sa/Tasks/TaskTypes/TaskComplexMoveBackAndJump.h index edce6fe454..60bdefd982 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexMoveBackAndJump.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexMoveBackAndJump.h @@ -18,8 +18,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexMoveBackAndJump : public CTaskComplex { ~CTaskComplexMoveBackAndJump() = default; static CTask* CreateSubTask(eTaskType taskType); - CTask* Clone() override { return new CTaskComplexMoveBackAndJump{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexMoveBackAndJump{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLights.h b/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLights.h index c0cf94e4fc..0bbf42b1e9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLights.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLights.h @@ -12,9 +12,9 @@ class CTaskComplexObserveTrafficLights : public CTaskComplex { CTaskComplexObserveTrafficLights() = default; // 0x631790 ~CTaskComplexObserveTrafficLights() override = default; // 0x6317C0 - eTaskType GetTaskType() override { return Type; } // 0x6317B0 - CTask* Clone() override { return new CTaskComplexObserveTrafficLights(); } // 0x636410 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return true; } // 0x6317D0 + eTaskType GetTaskType() const override { return Type; } // 0x6317B0 + CTask* Clone() const override { return new CTaskComplexObserveTrafficLights(); } // 0x636410 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } // 0x6317D0 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override { return CreateNextSubTask(ped); } // 0x6318D0 CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLightsAndAchieveHeading.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLightsAndAchieveHeading.cpp index cec9fc4509..eaac3041ad 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLightsAndAchieveHeading.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLightsAndAchieveHeading.cpp @@ -12,7 +12,7 @@ CTaskComplexObserveTrafficLightsAndAchieveHeading::CTaskComplexObserveTrafficLig } // 0x636490 -CTask* CTaskComplexObserveTrafficLightsAndAchieveHeading::Clone() { +CTask* CTaskComplexObserveTrafficLightsAndAchieveHeading::Clone() const { return new CTaskComplexObserveTrafficLightsAndAchieveHeading(m_nDurationInMs, m_fTargetHeading); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLightsAndAchieveHeading.h b/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLightsAndAchieveHeading.h index a2411557d3..5c15d25dba 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLightsAndAchieveHeading.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexObserveTrafficLightsAndAchieveHeading.h @@ -13,12 +13,12 @@ class CTaskComplexObserveTrafficLightsAndAchieveHeading : public CTaskComplex { CTaskComplexObserveTrafficLightsAndAchieveHeading(int32 durationInMs, float targetHeading); ~CTaskComplexObserveTrafficLightsAndAchieveHeading() override = default; // 0x631980 - eTaskType GetTaskType() override { return Type; } // 0x631940 - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } // 0x631940 + CTask* Clone() const override; CTask* ControlSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateSubTask(eTaskType taskType) const; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexOnFire.h b/source/game_sa/Tasks/TaskTypes/TaskComplexOnFire.h index f048ecb8e2..85d2dadbc9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexOnFire.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexOnFire.h @@ -23,8 +23,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexOnFire : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType); - CTask* Clone() override { return new CTaskComplexOnFire{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexOnFire{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed * ped) override; CTask* CreateFirstSubTask(CPed * ped) override; CTask* ControlSubTask(CPed * ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexOpenDriverDoor.h b/source/game_sa/Tasks/TaskTypes/TaskComplexOpenDriverDoor.h index 147d7ee7f5..3bc16b7d3b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexOpenDriverDoor.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexOpenDriverDoor.h @@ -12,8 +12,8 @@ class CTaskComplexOpenDriverDoor : public CTaskComplexEnterCar { } // 0x640390 ~CTaskComplexOpenDriverDoor() override = default; - eTaskType GetTaskType() override { return Type; } // 0x6403C0 - CTask* Clone() override { + eTaskType GetTaskType() const override { return Type; } // 0x6403C0 + CTask* Clone() const override { auto task = new CTaskComplexOpenDriverDoor(m_car); task->m_moveState = m_moveState; return task; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexOpenPassengerDoor.h b/source/game_sa/Tasks/TaskTypes/TaskComplexOpenPassengerDoor.h index 82e342a1d1..998eff994d 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexOpenPassengerDoor.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexOpenPassengerDoor.h @@ -11,6 +11,6 @@ class CTaskComplexOpenPassengerDoor : public CTaskComplexEnterCar { } // 0x6403E0 ~CTaskComplexOpenPassengerDoor() override = default; // 0x640420 - eTaskType GetTaskType() override{ return Type; } // 0x640410 - CTask* Clone() override { return new CTaskComplexOpenPassengerDoor(m_car, m_targetSeat); } // 0x6438E0 + eTaskType GetTaskType() const override{ return Type; } // 0x640410 + CTask* Clone() const override { return new CTaskComplexOpenPassengerDoor(m_car, m_targetSeat); } // 0x6438E0 }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPartner.h b/source/game_sa/Tasks/TaskTypes/TaskComplexPartner.h index 6f8c95e672..7806c8d20f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPartner.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPartner.h @@ -33,7 +33,7 @@ class CTaskComplexPartner : public CTaskComplex { CTaskComplexPartner(const char* commandName, CPed* partner, bool leadSpeaker, float distanceMultiplier, bool makePedAlwaysFacePartner, int8 updateDirectionCount, CVector point); ~CTaskComplexPartner() override; - eTaskType GetTaskType() override { return Type; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerChat.h b/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerChat.h index c5768c1be7..0fb8f8fd98 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerChat.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerChat.h @@ -15,9 +15,9 @@ class CTaskComplexPartnerChat : public CTaskComplexPartner { CTaskComplexPartnerChat(const char* commandName, CPed* partner, bool leadSpeaker, float distanceMultiplier, int8 updateDirectionCount, bool conversationEnabled, bool a8, CVector point); ~CTaskComplexPartnerChat() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexPartnerChat(m_commandName, m_partner, m_leadSpeaker, m_distanceMultiplier, m_updateDirectionCount, m_conversationEnabled, field_75, m_point); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexPartnerChat(m_commandName, m_partner, m_leadSpeaker, m_distanceMultiplier, m_updateDirectionCount, m_conversationEnabled, field_75, m_point); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; virtual CTaskComplexSequence* GetPartnerSequence(); static void InjectHooks(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerDeal.h b/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerDeal.h index 192784af26..c300fc86ac 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerDeal.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerDeal.h @@ -12,8 +12,8 @@ class CTaskComplexPartnerDeal : public CTaskComplexPartner { CTaskComplexPartnerDeal(const char* commandName, CPed* partner, bool leadSpeaker, float distanceMultiplier, CVector point); ~CTaskComplexPartnerDeal() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexPartnerDeal(m_commandName, m_partner, m_leadSpeaker, m_distanceMultiplier, m_point); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexPartnerDeal(m_commandName, m_partner, m_leadSpeaker, m_distanceMultiplier, m_point); } CTask* CreateFirstSubTask(CPed* ped) override; void StreamRequiredAnims() override; virtual CTaskComplexSequence* GetPartnerSequence(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerGreet.h b/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerGreet.h index 118cf9d5b1..d456497a26 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerGreet.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerGreet.h @@ -11,8 +11,8 @@ class CTaskComplexPartnerGreet : public CTaskComplexPartner { CTaskComplexPartnerGreet(const char* commandName, CPed* partner, bool leadSpeaker, float distanceMultiplier, int32 handShakeType, CVector point); ~CTaskComplexPartnerGreet() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexPartnerGreet(m_commandName, m_partner, m_leadSpeaker, m_distanceMultiplier, m_handShakeType, m_point); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexPartnerGreet(m_commandName, m_partner, m_leadSpeaker, m_distanceMultiplier, m_handShakeType, m_point); } CTask* CreateFirstSubTask(CPed* ped) override; void StreamRequiredAnims() override; virtual CTaskComplexSequence* GetPartnerSequence(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerShove.h b/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerShove.h index fb82a5ef0c..0dd7b238d5 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerShove.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPartnerShove.h @@ -12,8 +12,8 @@ class CTaskComplexPartnerShove : public CTaskComplexPartner { CTaskComplexPartnerShove(const char* commandName, CPed* partner, bool leadSpeaker, float distanceMultiplier, int8 updateDirectionCount, CVector point); ~CTaskComplexPartnerShove() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexPartnerShove(m_commandName, m_partner, m_leadSpeaker, m_distanceMultiplier, m_updateDirectionCount, m_point); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexPartnerShove(m_commandName, m_partner, m_leadSpeaker, m_distanceMultiplier, m_updateDirectionCount, m_point); } virtual CTaskComplexSequence* GetPartnerSequence(); static void InjectHooks(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPassObject.h b/source/game_sa/Tasks/TaskTypes/TaskComplexPassObject.h index 7f042d9b9f..8f3fb2b571 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPassObject.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPassObject.h @@ -29,9 +29,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexPassObject : public CTaskComplex { void AbortIK(CPed* ped); - CTask* Clone() override { return new CTaskComplexPassObject{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexPassObject{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPlayHandSignalAnim.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexPlayHandSignalAnim.cpp index 5713f74b44..98e1385668 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPlayHandSignalAnim.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPlayHandSignalAnim.cpp @@ -69,8 +69,8 @@ CTask* CTaskComplexPlayHandSignalAnim::CreateSubTask(eTaskType taskType) { } // 0x61BA00 -CTask* CTaskComplexPlayHandSignalAnim::Clone() { - return plugin::CallMethodAndReturn(this); +CTask* CTaskComplexPlayHandSignalAnim::Clone() const { + return plugin::CallMethodAndReturn(this); } // 0x61B570 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPlayHandSignalAnim.h b/source/game_sa/Tasks/TaskTypes/TaskComplexPlayHandSignalAnim.h index 3659ac5314..149a23739b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPlayHandSignalAnim.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPlayHandSignalAnim.h @@ -36,8 +36,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexPlayHandSignalAnim : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType); - CTask* Clone() override; - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override; + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPolicePursuit.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexPolicePursuit.cpp index a619ff7185..03e893ea22 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPolicePursuit.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPolicePursuit.cpp @@ -7,16 +7,17 @@ CTaskComplexPolicePursuit::CTaskComplexPolicePursuit() : CTaskComplex() { plugin::CallMethod<0x68BA70, CTaskComplexPolicePursuit*>(this); } +// 0x68CDD0 +CTaskComplexPolicePursuit::CTaskComplexPolicePursuit(const CTaskComplexPolicePursuit& o) : + CTaskComplexPolicePursuit{} +{ +} + // 0x68D880 CTaskComplexPolicePursuit::~CTaskComplexPolicePursuit() { plugin::CallMethod<0x68D880, CTaskComplexPolicePursuit*>(this); } -// 0x68CDD0 -CTask* CTaskComplexPolicePursuit::Clone() { - return plugin::CallMethodAndReturn(this); -} - // 0x690920 CTask* CTaskComplexPolicePursuit::ControlSubTask(CPed* ped) { return plugin::CallMethodAndReturn(this, ped); @@ -79,7 +80,7 @@ CTaskComplexPolicePursuit* CTaskComplexPolicePursuit::Constructor() { } // 0x68CDD0 -CTask* CTaskComplexPolicePursuit::Clone_Reversed() { +CTask* CTaskComplexPolicePursuit::Clone_Reversed() const { return CTaskComplexPolicePursuit::Clone(); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexPolicePursuit.h b/source/game_sa/Tasks/TaskTypes/TaskComplexPolicePursuit.h index e2c1104279..04af69a789 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexPolicePursuit.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexPolicePursuit.h @@ -12,11 +12,12 @@ class CTaskComplexPolicePursuit : public CTaskComplex { static constexpr auto Type = TASK_COMPLEX_POLICE_PURSUIT; CTaskComplexPolicePursuit(); + CTaskComplexPolicePursuit(const CTaskComplexPolicePursuit&); ~CTaskComplexPolicePursuit() override; - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return m_pSubTask->MakeAbortable(ped, priority, event); } // 0x68BAB0 - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return m_pSubTask->MakeAbortable(ped, priority, event); } // 0x68BAB0 + CTask* Clone() const override { return new CTaskComplexPolicePursuit{*this}; } // 0x68CDD0 CTask* ControlSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; @@ -33,7 +34,7 @@ class CTaskComplexPolicePursuit : public CTaskComplex { CTaskComplexPolicePursuit* Constructor(); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; eTaskType GetTaskType_Reversed(); bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); CTask* CreateNextSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexProstituteSolicit.h b/source/game_sa/Tasks/TaskTypes/TaskComplexProstituteSolicit.h index 4c057cd414..79f1dd55ca 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexProstituteSolicit.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexProstituteSolicit.h @@ -40,9 +40,9 @@ class CTaskComplexProstituteSolicit : public CTaskComplex { explicit CTaskComplexProstituteSolicit(CPed* client); ~CTaskComplexProstituteSolicit() override; - eTaskType GetTaskType() override { return Type; } // 0x661AE0 - CTask* Clone() override { return new CTaskComplexProstituteSolicit(m_pClient); } // 0x6622F0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x661AE0 + CTask* Clone() const override { return new CTaskComplexProstituteSolicit(m_pClient); } // 0x6622F0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexReactToGunAimedAt.h b/source/game_sa/Tasks/TaskTypes/TaskComplexReactToGunAimedAt.h index 1267893c88..ac2f24506b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexReactToGunAimedAt.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexReactToGunAimedAt.h @@ -23,8 +23,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexReactToGunAimedAt : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType, CPed* ped); - CTask* Clone() override { return new CTaskComplexReactToGunAimedAt{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexReactToGunAimedAt{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexRoadRage.h b/source/game_sa/Tasks/TaskTypes/TaskComplexRoadRage.h index e9c3cf3d29..fd03dbb7fc 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexRoadRage.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexRoadRage.h @@ -22,9 +22,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexRoadRage : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType, CPed* ped); - CTask* Clone() override { return new CTaskComplexRoadRage{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexRoadRage{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexScreamInCarThenLeave.h b/source/game_sa/Tasks/TaskTypes/TaskComplexScreamInCarThenLeave.h index ffa43f1007..da552e0162 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexScreamInCarThenLeave.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexScreamInCarThenLeave.h @@ -32,9 +32,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexScreamInCarThenLeave : public CTaskComplex CTask* CreateSubTask(eTaskType taskType, CPed* ped); - CTask* Clone() override { return new CTaskComplexScreamInCarThenLeave{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexScreamInCarThenLeave{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexSeekCoverUntilTargetDead.h b/source/game_sa/Tasks/TaskTypes/TaskComplexSeekCoverUntilTargetDead.h index 1064cc4453..2037d2f7e0 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexSeekCoverUntilTargetDead.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexSeekCoverUntilTargetDead.h @@ -35,8 +35,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSeekCoverUntilTargetDead : public CTaskCom CPed* GetCoverPed(CPed* ped); CPed* GetTargetPed(); - CTask* Clone() override { return new CTaskComplexSeekCoverUntilTargetDead{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexSeekCoverUntilTargetDead{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexSignalAtPed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexSignalAtPed.h index 83ccf9658d..95994cbdd0 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexSignalAtPed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexSignalAtPed.h @@ -17,12 +17,12 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSignalAtPed : public CTaskComplex { constexpr static auto Type = eTaskType::TASK_COMPLEX_SIGNAL_AT_PED; - CTaskComplexSignalAtPed(CPed* pedToSignalAt, int32 unused1, bool playAnimAtEnd); + CTaskComplexSignalAtPed(CPed* pedToSignalAt, int32 unused1 = -1, bool playAnimAtEnd = false); CTaskComplexSignalAtPed(const CTaskComplexSignalAtPed& o); ~CTaskComplexSignalAtPed(); - CTask* Clone() override { return new CTaskComplexSignalAtPed{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexSignalAtPed{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed * ped) override; CTask* CreateFirstSubTask(CPed * ped) override; CTask* ControlSubTask(CPed * ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexSitDownThenIdleThenStandUp.h b/source/game_sa/Tasks/TaskTypes/TaskComplexSitDownThenIdleThenStandUp.h index 48cae52839..3c94e00451 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexSitDownThenIdleThenStandUp.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexSitDownThenIdleThenStandUp.h @@ -24,9 +24,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSitDownThenIdleThenStandUp : public CTaskC CTask* CreateSubTask(eTaskType taskType); - CTask* Clone() override { return new CTaskComplexSitDownThenIdleThenStandUp{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexSitDownThenIdleThenStandUp{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed * ped) override; CTask* CreateFirstSubTask(CPed * ped) override; CTask* ControlSubTask(CPed * ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexSmartFleeEntity.h b/source/game_sa/Tasks/TaskTypes/TaskComplexSmartFleeEntity.h index 3cf0b79134..9ab4a61416 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexSmartFleeEntity.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexSmartFleeEntity.h @@ -40,8 +40,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSmartFleeEntity : public CTaskComplex { CTaskComplexSmartFleeEntity(const CTaskComplexSmartFleeEntity&); ~CTaskComplexSmartFleeEntity() override; - eTaskType GetTaskType() override { return Type; } // 0x65C4C0 - CTask* Clone() override { return new CTaskComplexSmartFleeEntity{ *this }; } + eTaskType GetTaskType() const override { return Type; } // 0x65C4C0 + CTask* Clone() const override { return new CTaskComplexSmartFleeEntity{ *this }; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexSmartFleePoint.h b/source/game_sa/Tasks/TaskTypes/TaskComplexSmartFleePoint.h index 2a6f77c446..e10b80d834 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexSmartFleePoint.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexSmartFleePoint.h @@ -34,9 +34,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSmartFleePoint : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType, CPed* ped); int8 SetFleePosition(CVector const& a2, float a3, bool a4); - CTask* Clone() override { return new CTaskComplexSmartFleePoint{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexSmartFleePoint{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexStareAtPed.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexStareAtPed.cpp index 4aa9e45e84..2aac0425e6 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexStareAtPed.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexStareAtPed.cpp @@ -34,7 +34,7 @@ CTaskComplexStareAtPed::CTaskComplexStareAtPed(const CTaskComplexStareAtPed& o) o.m_pPedGroup, o.m_pPed, o.m_timer.m_bStarted - ? o.m_timer.m_nStartTime + o.m_timer.m_nInterval - CTimer::GetTimeInMS() // Either time left of timer + ? static_cast(o.m_timer.m_nStartTime) + o.m_timer.m_nInterval - static_cast(CTimer::GetTimeInMS()) // Either time left of timer : o.m_timeout // Or the original timeout } { diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexStareAtPed.h b/source/game_sa/Tasks/TaskTypes/TaskComplexStareAtPed.h index 838ec23643..bdc4af3fe8 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexStareAtPed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexStareAtPed.h @@ -26,8 +26,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexStareAtPed : public CTaskComplex { CTaskComplexStareAtPed(const CTaskComplexStareAtPed&); ~CTaskComplexStareAtPed(); - CTask* Clone() override { return new CTaskComplexStareAtPed{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexStareAtPed{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexStealCar.h b/source/game_sa/Tasks/TaskTypes/TaskComplexStealCar.h index 15b07f02e6..f588a33a32 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexStealCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexStealCar.h @@ -26,8 +26,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexStealCar : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType, CPed* ped); - CTask* Clone() override { return new CTaskComplexStealCar{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexStealCar{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexStuckInAir.h b/source/game_sa/Tasks/TaskTypes/TaskComplexStuckInAir.h index ed3aad51d7..f23d6ea80a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexStuckInAir.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexStuckInAir.h @@ -9,8 +9,8 @@ class CTaskComplexStuckInAir : public CTaskComplex { CTaskComplexStuckInAir() = default; // 0x67BA40 ~CTaskComplexStuckInAir() override = default; // 0x67BA70 - eTaskType GetTaskType() override { return Type; } // 0x67BA60 - CTask* Clone() override { return new CTaskComplexStuckInAir(); } // 0x67C700 + eTaskType GetTaskType() const override { return Type; } // 0x67BA60 + CTask* Clone() const override { return new CTaskComplexStuckInAir(); } // 0x67C700 CTask* ControlSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* CreateNextSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexSunbathe.h b/source/game_sa/Tasks/TaskTypes/TaskComplexSunbathe.h index 2ad1f9bf48..0665d70a83 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexSunbathe.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexSunbathe.h @@ -45,9 +45,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexSunbathe : public CTaskComplex { CTaskComplexSunbathe(const CTaskComplexSunbathe&); ~CTaskComplexSunbathe(); - CTask* Clone() override { return new CTaskComplexSunbathe{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexSunbathe{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexTrackEntity.h b/source/game_sa/Tasks/TaskTypes/TaskComplexTrackEntity.h index 2e8b10dcd0..7a4c832fc2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexTrackEntity.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexTrackEntity.h @@ -36,9 +36,9 @@ class CTaskComplexTrackEntity : public CTaskComplex { void CalcTargetPos(CPed* ped); void CalcMoveRatio(CPed* ped); - CTask* Clone() override { return new CTaskComplexTrackEntity{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexTrackEntity{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexTreatAccident.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexTreatAccident.cpp index 9688b45a25..8f84653646 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexTreatAccident.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexTreatAccident.cpp @@ -52,8 +52,7 @@ CTask* CTaskComplexTreatAccident::ControlSubTask(CPed* ped) } // 0x659A90 -CTask* CTaskComplexTreatAccident::Clone() -{ +CTask* CTaskComplexTreatAccident::Clone() const { return Clone_Reversed(); } @@ -103,8 +102,7 @@ CTask* CTaskComplexTreatAccident::ControlSubTask_Reversed(CPed* ped) return m_pSubTask; } -CTask* CTaskComplexTreatAccident::Clone_Reversed() -{ +CTask* CTaskComplexTreatAccident::Clone_Reversed() const { return new CTaskComplexTreatAccident(m_pAccident); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexTreatAccident.h b/source/game_sa/Tasks/TaskTypes/TaskComplexTreatAccident.h index 95ae0aa6d0..de2adfee98 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexTreatAccident.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexTreatAccident.h @@ -13,8 +13,8 @@ class CTaskComplexTreatAccident : public CTaskComplex { explicit CTaskComplexTreatAccident(CAccident* accident); ~CTaskComplexTreatAccident() override = default; // 0x658AE0 - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -31,6 +31,6 @@ class CTaskComplexTreatAccident : public CTaskComplex { CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* ControlSubTask_Reversed(CPed* ped); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; }; VALIDATE_SIZE(CTaskComplexTreatAccident, 0x10); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexTurnToFaceEntityOrCoord.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexTurnToFaceEntityOrCoord.cpp index dcbeaf0ec7..3abc2ee0d3 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexTurnToFaceEntityOrCoord.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexTurnToFaceEntityOrCoord.cpp @@ -42,7 +42,7 @@ CTaskComplexTurnToFaceEntityOrCoord::~CTaskComplexTurnToFaceEntityOrCoord() { } // 0x66D250 -CTask* CTaskComplexTurnToFaceEntityOrCoord::Clone() { +CTask* CTaskComplexTurnToFaceEntityOrCoord::Clone() const { if (m_bFaceEntity) { return new CTaskComplexTurnToFaceEntityOrCoord(m_EntityToFace, m_fChangeRateMult, m_fMaxHeading); } else { diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexTurnToFaceEntityOrCoord.h b/source/game_sa/Tasks/TaskTypes/TaskComplexTurnToFaceEntityOrCoord.h index ece5f8a68b..29459b7223 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexTurnToFaceEntityOrCoord.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexTurnToFaceEntityOrCoord.h @@ -21,8 +21,8 @@ class CTaskComplexTurnToFaceEntityOrCoord : public CTaskComplex { CTaskComplexTurnToFaceEntityOrCoord(const CVector& coords, float changeRateMult = 0.5f, float maxHeading = 0.2f); ~CTaskComplexTurnToFaceEntityOrCoord() override; - eTaskType GetTaskType() override { return Type; } // 0x66B900 - CTask* Clone() override; // 0x66D250 + eTaskType GetTaskType() const override { return Type; } // 0x66B900 + CTask* Clone() const override; // 0x66D250 CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } // 0x66B9C0 CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexUseAttractor.h b/source/game_sa/Tasks/TaskTypes/TaskComplexUseAttractor.h index 15fd5a9935..8a2f6d82cd 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexUseAttractor.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexUseAttractor.h @@ -20,8 +20,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexUseAttractor : public CTaskComplex { CTaskComplexUseAttractor(const CTaskComplexUseAttractor&); ~CTaskComplexUseAttractor() = default; - CTask* Clone() override { return new CTaskComplexUseAttractor{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexUseAttractor{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexUseGoggles.h b/source/game_sa/Tasks/TaskTypes/TaskComplexUseGoggles.h index e6836eb94f..d067a2bfcf 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexUseGoggles.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexUseGoggles.h @@ -15,8 +15,8 @@ class CTaskComplexUseGoggles : public CTaskComplex { CTaskComplexUseGoggles() = default; // 0x634EF0 ~CTaskComplexUseGoggles() override = default; // 0x634F20 - eTaskType GetTaskType() override { return Type; } // 0x634F10 - CTask* Clone() override { return new CTaskComplexUseGoggles(); } // 0x637060 + eTaskType GetTaskType() const override { return Type; } // 0x634F10 + CTask* Clone() const override { return new CTaskComplexUseGoggles(); } // 0x637060 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexUseScriptedAttractor.h b/source/game_sa/Tasks/TaskTypes/TaskComplexUseScriptedAttractor.h index acc56f7310..943ed06c8b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexUseScriptedAttractor.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexUseScriptedAttractor.h @@ -19,8 +19,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexUseScriptedAttractor : public CTaskComplex CTaskComplexUseScriptedAttractor(const CTaskComplexUseScriptedAttractor&); ~CTaskComplexUseScriptedAttractor() = default; - CTask* Clone() override { return new CTaskComplexUseScriptedAttractor{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexUseScriptedAttractor{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexUseScriptedBrain.h b/source/game_sa/Tasks/TaskTypes/TaskComplexUseScriptedBrain.h index 5aed91bf3d..c43f5d989e 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexUseScriptedBrain.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexUseScriptedBrain.h @@ -23,8 +23,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexUseScriptedBrain : public CTaskComplex { CTaskComplexUseScriptedBrain(const CTaskComplexUseScriptedBrain&); ~CTaskComplexUseScriptedBrain(); - CTask* Clone() override { return new CTaskComplexUseScriptedBrain{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexUseScriptedBrain{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexUseSwatRope.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexUseSwatRope.cpp index 09d2e615e5..dcee4b89e3 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexUseSwatRope.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexUseSwatRope.cpp @@ -51,7 +51,7 @@ CTaskComplexUseSwatRope::~CTaskComplexUseSwatRope() { } // 0x659C30 -CTask* CTaskComplexUseSwatRope::Clone() { +CTask* CTaskComplexUseSwatRope::Clone() const { return Clone_Reversed(); } @@ -75,7 +75,7 @@ CTask* CTaskComplexUseSwatRope::ControlSubTask(CPed* ped) { return ControlSubTask_Reversed(ped); } -CTask* CTaskComplexUseSwatRope::Clone_Reversed() { +CTask* CTaskComplexUseSwatRope::Clone_Reversed() const { if (m_bIsOnHeli) return new CTaskComplexUseSwatRope(m_nRopeId, m_pHeli); else diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexUseSwatRope.h b/source/game_sa/Tasks/TaskTypes/TaskComplexUseSwatRope.h index 67c9f324eb..392bb6ec9a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexUseSwatRope.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexUseSwatRope.h @@ -17,9 +17,9 @@ class CTaskComplexUseSwatRope : public CTaskComplex { explicit CTaskComplexUseSwatRope(uint32 ropeId); ~CTaskComplexUseSwatRope() override; - CTask* Clone() override; - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + CTask* Clone() const override; + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -34,7 +34,7 @@ class CTaskComplexUseSwatRope : public CTaskComplex { CTaskComplexUseSwatRope* Constructor(uint32 ropeId, CHeli* heli); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexUseWaterCannon.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexUseWaterCannon.cpp index a2969b0072..fb0c4c7104 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexUseWaterCannon.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexUseWaterCannon.cpp @@ -35,8 +35,7 @@ CTaskComplexUseWaterCannon::~CTaskComplexUseWaterCannon() } // 0x659D10 -CTask* CTaskComplexUseWaterCannon::Clone() -{ +CTask* CTaskComplexUseWaterCannon::Clone() const { return Clone_Reversed(); } @@ -58,8 +57,7 @@ CTask* CTaskComplexUseWaterCannon::ControlSubTask(CPed* ped) return ControlSubTask_Reversed(ped); } -CTask* CTaskComplexUseWaterCannon::Clone_Reversed() -{ +CTask* CTaskComplexUseWaterCannon::Clone_Reversed() const { return new CTaskComplexUseWaterCannon(m_pFire); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexUseWaterCannon.h b/source/game_sa/Tasks/TaskTypes/TaskComplexUseWaterCannon.h index 70ce74887a..7cb78afdf0 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexUseWaterCannon.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexUseWaterCannon.h @@ -12,15 +12,15 @@ class CTaskComplexUseWaterCannon : public CTaskComplex { explicit CTaskComplexUseWaterCannon(CFire* pFire); ~CTaskComplexUseWaterCannon() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; static void InjectHooks(); CTaskComplexUseWaterCannon* Constructor(CFire* pFire); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* ControlSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWaitAtAttractor.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWaitAtAttractor.h index 7f7f093d9b..a297af001f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWaitAtAttractor.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWaitAtAttractor.h @@ -21,8 +21,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWaitAtAttractor : public CTaskComplex { CTaskComplexWaitAtAttractor(const CTaskComplexWaitAtAttractor&); ~CTaskComplexWaitAtAttractor() = default; - CTask* Clone() override { return new CTaskComplexWaitAtAttractor{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexWaitAtAttractor{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForBackup.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForBackup.h index caaa45bf4b..37113ee56d 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForBackup.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForBackup.h @@ -25,8 +25,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWaitForBackup : public CTaskComplex { CTask* CreateSubTask(eTaskType taskType, CPed* a3); - CTask* Clone() override { return new CTaskComplexWaitForBackup{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexWaitForBackup{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForBus.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForBus.h index c291f1a835..47ac8ece4a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForBus.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForBus.h @@ -18,9 +18,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWaitForBus : public CTaskComplex { CTaskComplexWaitForBus(const CTaskComplexWaitForBus&); ~CTaskComplexWaitForBus() = default; - CTask* Clone() override { return new CTaskComplexWaitForBus{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return true; } + CTask* Clone() const override { return new CTaskComplexWaitForBus{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForDryWeather.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForDryWeather.h index 7eabd4e4f1..55875ef8d4 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForDryWeather.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWaitForDryWeather.h @@ -16,8 +16,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWaitForDryWeather : public CTaskComplex { CTaskComplexWaitForDryWeather(const CTaskComplexWaitForDryWeather&); ~CTaskComplexWaitForDryWeather() = default; - CTask* Clone() override { return new CTaskComplexWaitForDryWeather{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexWaitForDryWeather{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override { return m_pSubTask; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundBuildingAttempt.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundBuildingAttempt.cpp index 3a430ee923..14c72bb928 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundBuildingAttempt.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundBuildingAttempt.cpp @@ -237,6 +237,7 @@ CTask* CTaskComplexWalkRoundBuildingAttempt::CreateNextSubTask(CPed* ped) { case TASK_SIMPLE_GO_TO_POINT: case TASK_SIMPLE_SCRATCH_HEAD: case TASK_SIMPLE_LOOK_ABOUT: + case TASK_SIMPLE_HIT_WALL: return CreateSubTask(m_routeHasPoints ? TASK_SIMPLE_ACHIEVE_HEADING : TASK_SIMPLE_LOOK_ABOUT, ped); case TASK_SIMPLE_STAND_STILL: case TASK_COMPLEX_FOLLOW_POINT_ROUTE: diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundBuildingAttempt.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundBuildingAttempt.h index 8f1e4a8c1a..55c52299c0 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundBuildingAttempt.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundBuildingAttempt.h @@ -44,9 +44,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWalkRoundBuildingAttempt : public CTaskCom void ComputeRoute(CPed const& ped); CTask* CreateSubTask(eTaskType taskType, CPed* ped); - CTask* Clone() override { return new CTaskComplexWalkRoundBuildingAttempt{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskComplexWalkRoundBuildingAttempt{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundFire.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundFire.h index 76752fd551..d177001f5f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundFire.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundFire.h @@ -28,8 +28,8 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWalkRoundFire : public CTaskComplex { bool ComputeDetourTarget(const CPed& ped, CVector& outTarget) const; CVector GetDetourTarget(const CPed& ped) const; - CTask* Clone() override { return new CTaskComplexWalkRoundFire{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskComplexWalkRoundFire{ *this }; } + eTaskType GetTaskType() const override { return Type; } CTask* CreateNextSubTask(CPed* ped) override { return nullptr; } CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundObject.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundObject.h index ecc1e7bc36..ce91e4cf49 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundObject.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWalkRoundObject.h @@ -23,8 +23,8 @@ class CTaskComplexWalkRoundObject : public CTaskComplex { CTaskComplexWalkRoundObject(int32 moveState, const CVector& targetPoint, CEntity* object); ~CTaskComplexWalkRoundObject() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskComplexWalkRoundObject(m_moveState, m_targetPoint, m_object); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskComplexWalkRoundObject(m_moveState, m_targetPoint, m_object); } CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWander.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexWander.cpp index a5f50ad87a..8fb185e410 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWander.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWander.cpp @@ -36,7 +36,7 @@ void CTaskComplexWander::InjectHooks() { RH_ScopedOverloadedInstall(ScanForBlockedNode, "1", 0x66F4C0, bool(CTaskComplexWander::*)(const CVector&, CEntity*)); RH_ScopedInstall(GetWanderTaskByPedType, 0x673D00); } -CTaskComplexWander* CTaskComplexWander::Constructor(int32 moveState, uint8 dir, bool bWanderSensibly, float fTargetRadius) { this->CTaskComplexWander::CTaskComplexWander(moveState, dir, bWanderSensibly, fTargetRadius); return this; } +CTaskComplexWander* CTaskComplexWander::Constructor(eMoveState moveState, uint8 dir, bool bWanderSensibly, float fTargetRadius) { this->CTaskComplexWander::CTaskComplexWander(moveState, dir, bWanderSensibly, fTargetRadius); return this; } CTask* CTaskComplexWander::CreateNextSubTask(CPed* ped) { return CreateNextSubTask_Reversed(ped); } CTask* CTaskComplexWander::CreateFirstSubTask(CPed* ped) { return CreateFirstSubTask_Reversed(ped); } CTask* CTaskComplexWander::ControlSubTask(CPed* ped) { return ControlSubTask_Reversed(ped); } @@ -44,8 +44,8 @@ void CTaskComplexWander::UpdateDir(CPed* ped) { return UpdateDir_Reversed(ped); void CTaskComplexWander::UpdatePathNodes(const CPed* ped, uint8 dir, CNodeAddress& originNode, CNodeAddress& targetNode, uint8& outDir) { return UpdatePathNodes_Reversed(ped, dir, originNode, targetNode, outDir); } // 0x66F450 -CTaskComplexWander::CTaskComplexWander(int32 moveState, uint8 dir, bool bWanderSensibly, float fTargetRadius) : CTaskComplex() { - m_nMoveState = static_cast(moveState); // todo: change signature +CTaskComplexWander::CTaskComplexWander(eMoveState moveState, uint8 dir, bool bWanderSensibly, float fTargetRadius) : CTaskComplex() { + m_nMoveState = moveState; m_nDir = dir; m_fTargetRadius = fTargetRadius; m_bWanderSensibly = bWanderSensibly; @@ -176,7 +176,7 @@ CTask* CTaskComplexWander::ControlSubTask_Reversed(CPed* ped) { // 0x669DA0 void CTaskComplexWander::UpdateDir_Reversed(CPed* ped) { uint8 newDir = m_nDir; - if (m_NextNode.IsValid()) { + if (m_NextNode.IsAreaValid()) { const CPathNode* pathNodes = ThePaths.m_pPathNodes[m_NextNode.m_wAreaId]; if (pathNodes) { const CPathNode* pathNode = &pathNodes[m_NextNode.m_wNodeId]; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWander.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWander.h index 72d435f1b3..2b40aa32bd 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWander.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWander.h @@ -46,10 +46,10 @@ class CTaskComplexWander : public CTaskComplex { public: static constexpr auto Type = TASK_COMPLEX_WANDER; - CTaskComplexWander(int32 moveState, uint8 dir, bool bWanderSensibly = true, float fTargetRadius = 0.5f); + CTaskComplexWander(eMoveState moveState, uint8 dir, bool bWanderSensibly = true, float fTargetRadius = 0.5f); ~CTaskComplexWander() override = default; - eTaskType GetTaskType() override { return Type; } // 0x460CD0 + eTaskType GetTaskType() const override { return Type; } // 0x460CD0 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -73,7 +73,7 @@ class CTaskComplexWander : public CTaskComplex { private: friend void InjectHooksMain(); static void InjectHooks(); - CTaskComplexWander* Constructor(int32 moveState, uint8 dir, bool bWanderSensibly = true, float fTargetRadius = 0.5f); + CTaskComplexWander* Constructor(eMoveState moveState, uint8 dir, bool bWanderSensibly = true, float fTargetRadius = 0.5f); CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* ControlSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCop.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCop.cpp index 5fc2b352c8..2f1aaa52f4 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCop.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCop.cpp @@ -19,14 +19,14 @@ void CTaskComplexWanderCop::InjectHooks() { RH_ScopedInstall(LookForCriminals, 0x66B300); RH_ScopedInstall(ShouldPursuePlayer, 0x66B160); } -CTaskComplexWanderCop* CTaskComplexWanderCop::Constructor(int32 moveState, uint8 dir) { this->CTaskComplexWanderCop::CTaskComplexWanderCop(moveState, dir); return this; } +CTaskComplexWanderCop* CTaskComplexWanderCop::Constructor(eMoveState moveState, uint8 dir) { this->CTaskComplexWanderCop::CTaskComplexWanderCop(moveState, dir); return this; } CTask* CTaskComplexWanderCop::CreateNextSubTask(CPed* ped) { return CreateNextSubTask_Reversed(ped); } CTask* CTaskComplexWanderCop::CreateFirstSubTask(CPed* ped) { return CreateFirstSubTask_Reversed(ped); } CTask* CTaskComplexWanderCop::ControlSubTask(CPed* ped) { return ControlSubTask_Reversed(ped); } void CTaskComplexWanderCop::ScanForStuff(CPed* ped) { return ScanForStuff_Reversed(ped); } // 0x460D80 -CTaskComplexWanderCop::CTaskComplexWanderCop(int32 moveState, uint8 dir) : CTaskComplexWander(moveState, dir, true, 0.5f) { +CTaskComplexWanderCop::CTaskComplexWanderCop(eMoveState moveState, uint8 dir) : CTaskComplexWander(moveState, dir, true, 0.5f) { m_pGoToPointAndStandStillTask = nullptr; m_nTimePassedSinceLastLookedForCriminals = 0; m_nTimePassedSinceLastLookedForCarAlarmsAndStolenCopCars = 0; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCop.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCop.h index 4fe2deced8..5b8fc44219 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCop.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCop.h @@ -3,6 +3,7 @@ #include "TaskComplexWander.h" #include "TaskComplexGoToPointAndStandStill.h" #include "TaskTimer.h" +#include "eMoveState.h" class CPed; @@ -16,11 +17,11 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWanderCop : public CTaskComplexWander { CTaskTimer m_nSubTaskCreatedTimer; public: - CTaskComplexWanderCop(int32 moveState, uint8 dir); + CTaskComplexWanderCop(eMoveState moveState, uint8 dir); ~CTaskComplexWanderCop() override; eWanderType GetWanderType() override { return WANDER_TYPE_COP; } // 0x460D50 - CTask* Clone() override { return new CTaskComplexWanderCop(m_nMoveState, m_nDir); } // 0x460CE0 + CTask* Clone() const override { return new CTaskComplexWanderCop(m_nMoveState, m_nDir); } // 0x460CE0 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; @@ -35,9 +36,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWanderCop : public CTaskComplexWander { friend void InjectHooksMain(); static void InjectHooks(); - CTaskComplexWanderCop* Constructor(int32 moveState, uint8 dir); + CTaskComplexWanderCop* Constructor(eMoveState moveState, uint8 dir); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; CTask* CreateNextSubTask_Reversed(CPed* ped); CTask* CreateFirstSubTask_Reversed(CPed* ped); CTask* ControlSubTask_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCriminal.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCriminal.cpp index 0130735710..e7820f050a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCriminal.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCriminal.cpp @@ -3,7 +3,7 @@ #include "TaskComplexWanderCriminal.h" // 0x48E610 -CTaskComplexWanderCriminal::CTaskComplexWanderCriminal(int32 MoveState, uint8 Dir, bool bWanderSensibly) : CTaskComplexWander(MoveState, Dir, bWanderSensibly) { } +CTaskComplexWanderCriminal::CTaskComplexWanderCriminal(eMoveState MoveState, uint8 Dir, bool bWanderSensibly) : CTaskComplexWander(MoveState, Dir, bWanderSensibly) { } // 0x670350 void CTaskComplexWanderCriminal::ScanForStuff(CPed* ped) { diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCriminal.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCriminal.h index b67311286d..a7015bfe36 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCriminal.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderCriminal.h @@ -1,6 +1,7 @@ #pragma once #include "TaskComplexWander.h" +#include "eMoveState.h" class NOTSA_EXPORT_VTABLE CTaskComplexWanderCriminal : public CTaskComplexWander { public: @@ -8,10 +9,10 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWanderCriminal : public CTaskComplexWander uint32 m_nMinNextScanTime; public: - CTaskComplexWanderCriminal(int32 MoveState, uint8 Dir, bool bWanderSensibly = true); + CTaskComplexWanderCriminal(eMoveState MoveState, uint8 Dir, bool bWanderSensibly = true); ~CTaskComplexWanderCriminal() override = default; // 0x48E720 - CTask* Clone() override { return new CTaskComplexWanderCriminal(m_nMoveState, m_nDir); }// 0x48E650 + CTask* Clone() const override { return new CTaskComplexWanderCriminal(m_nMoveState, m_nDir); }// 0x48E650 eWanderType GetWanderType() override { return WANDER_TYPE_CRIMINAL; } // 0x48E6F0 void LookForCarsToSteal(CPed* ped); // 0x66B4F0 void ScanForStuff(CPed* ped) override; // 0x670350 diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderFlee.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderFlee.cpp index d3a6467cce..16a5aaeee7 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderFlee.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderFlee.cpp @@ -15,7 +15,7 @@ void CTaskComplexWanderFlee::InjectHooks() { } // 0x65B320 -CTaskComplexWanderFlee::CTaskComplexWanderFlee(int32 moveState, uint8 dir) : +CTaskComplexWanderFlee::CTaskComplexWanderFlee(eMoveState moveState, uint8 dir) : CTaskComplexWander{moveState, dir, false} { } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderFlee.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderFlee.h index 38911c2b6f..6190c757b2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderFlee.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderFlee.h @@ -1,6 +1,7 @@ #pragma once #include "TaskComplexWander.h" +#include "eMoveState.h" class CPed; class CTaskComplexWanderFlee; @@ -9,17 +10,17 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWanderFlee : public CTaskComplexWander { public: static void InjectHooks(); - CTaskComplexWanderFlee(int32 moveState, uint8 dir); + CTaskComplexWanderFlee(eMoveState moveState, uint8 dir); CTaskComplexWanderFlee(const CTaskComplexWanderFlee&); ~CTaskComplexWanderFlee() = default; - CTask* Clone() override { return new CTaskComplexWanderFlee{ *this }; } + CTask* Clone() const override { return new CTaskComplexWanderFlee{ *this }; } eWanderType GetWanderType() override { return WANDER_TYPE_FLEE; } void ScanForStuff(CPed* ped) override { /*nop*/ } private: // Wrappers for hooks // 0x65B320 - CTaskComplexWanderFlee* Constructor(int32 moveState, uint8 dir) { + CTaskComplexWanderFlee* Constructor(eMoveState moveState, uint8 dir) { this->CTaskComplexWanderFlee::CTaskComplexWanderFlee(moveState, dir); return this; } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderGang.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderGang.h index bccb74e9be..a45d9f864c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderGang.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderGang.h @@ -13,7 +13,7 @@ class CTaskComplexWanderGang : public CTaskComplexWander { ~CTaskComplexWanderGang() override = default; // 0x66F650 eWanderType GetWanderType() override { return WANDER_TYPE_GANG; } // 0x66F630 - CTask* Clone() override { return new CTaskComplexWanderGang(m_nMoveState, m_nDir, m_NextScanTime, m_bWanderSensibly, 0.5f); } // 0x671470 + CTask* Clone() const override { return new CTaskComplexWanderGang(m_nMoveState, m_nDir, m_NextScanTime, m_bWanderSensibly, 0.5f); } // 0x671470 void ScanForStuff(CPed* ped) override; // 0x66F640 bool CanJoinGang() { return m_TaskTimer.IsOutOfTime(); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderMedic.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderMedic.cpp index c9f8f55b17..aaaadd466b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderMedic.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderMedic.cpp @@ -3,6 +3,6 @@ #include "TaskComplexWanderMedic.h" // 0x658770 -CTaskComplexWanderMedic::CTaskComplexWanderMedic(int32 MoveState, uint8 Dir, bool bWanderSensibly) : CTaskComplexWander(MoveState, Dir, bWanderSensibly) { +CTaskComplexWanderMedic::CTaskComplexWanderMedic(eMoveState MoveState, uint8 Dir, bool bWanderSensibly) : CTaskComplexWander(MoveState, Dir, bWanderSensibly) { // NOP } diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderMedic.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderMedic.h index 35d34925c2..27b54ef46c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderMedic.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderMedic.h @@ -1,12 +1,13 @@ #pragma once #include "TaskComplexWander.h" +#include "eMoveState.h" class CTaskComplexWanderMedic : public CTaskComplexWander { public: - CTaskComplexWanderMedic(int32 MoveState, uint8 Dir, bool bWanderSensibly = true); + CTaskComplexWanderMedic(eMoveState MoveState, uint8 Dir, bool bWanderSensibly = true); eWanderType GetWanderType() override { return WANDER_TYPE_MEDIC; } // 0x658810 - CTask* Clone() override { return new CTaskComplexWanderMedic(m_nMoveState, m_nDir, m_bWanderSensibly); } // 0x6587A0 + CTask* Clone() const override { return new CTaskComplexWanderMedic(m_nMoveState, m_nDir, m_bWanderSensibly); } // 0x6587A0 void ScanForStuff(CPed* ped) override {} // 0x658820 }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderProstitute.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderProstitute.cpp index 3036540b24..487a168425 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderProstitute.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderProstitute.cpp @@ -3,7 +3,7 @@ #include "TaskComplexWanderProstitute.h" // 0x672690 -CTaskComplexWanderProstitute::CTaskComplexWanderProstitute(int32 MoveState, uint8 Dir, bool bWanderSensibly) : +CTaskComplexWanderProstitute::CTaskComplexWanderProstitute(eMoveState MoveState, uint8 Dir, bool bWanderSensibly) : CTaskComplexWanderStandard(MoveState, Dir, bWanderSensibly), m_nStartTimeInMs{ 0 } { diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderProstitute.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderProstitute.h index 4310299723..1033527f13 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderProstitute.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderProstitute.h @@ -7,10 +7,10 @@ class CTaskComplexWanderProstitute : public CTaskComplexWanderStandard { uint32 m_nStartTimeInMs; public: - CTaskComplexWanderProstitute(int32 MoveState, uint8 Dir, bool bWanderSensibly = true); + CTaskComplexWanderProstitute(eMoveState MoveState, uint8 Dir, bool bWanderSensibly = true); eWanderType GetWanderType() override { return WANDER_TYPE_PROSTITUTE; } // 0x6726C0 - CTask* Clone() override { return new CTaskComplexWanderProstitute(m_nMoveState, m_nDir, m_bWanderSensibly); } // 0x673C80 + CTask* Clone() const override { return new CTaskComplexWanderProstitute(m_nMoveState, m_nDir, m_bWanderSensibly); } // 0x673C80 CTask* CreateFirstSubTask(CPed* ped) override { return CTaskComplexWander::CreateFirstSubTask(ped); } // 0x674920 void ScanForStuff(CPed* ped) override; }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderStandard.cpp b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderStandard.cpp index afef3720eb..004e504d95 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderStandard.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderStandard.cpp @@ -3,7 +3,7 @@ #include "TaskComplexWanderStandard.h" // 0x48E4F0 -CTaskComplexWanderStandard::CTaskComplexWanderStandard(int32 MoveState, uint8 Dir, bool bWanderSensibly) : +CTaskComplexWanderStandard::CTaskComplexWanderStandard(eMoveState MoveState, uint8 Dir, bool bWanderSensibly) : CTaskComplexWander(MoveState, Dir, bWanderSensibly), m_nMinNextScanTime{ 0 } { diff --git a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderStandard.h b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderStandard.h index 0e3c85c1ab..781a4e0754 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskComplexWanderStandard.h +++ b/source/game_sa/Tasks/TaskTypes/TaskComplexWanderStandard.h @@ -2,6 +2,7 @@ #include "TaskComplexWander.h" #include "TaskTimer.h" +#include "eMoveState.h" class NOTSA_EXPORT_VTABLE CTaskComplexWanderStandard : public CTaskComplexWander { public: @@ -9,9 +10,9 @@ class NOTSA_EXPORT_VTABLE CTaskComplexWanderStandard : public CTaskComplexWander uint32 m_nMinNextScanTime; public: - CTaskComplexWanderStandard(int32 MoveState, uint8 Dir, bool bWanderSensibly = true); + CTaskComplexWanderStandard(eMoveState MoveState, uint8 Dir, bool bWanderSensibly = true); - CTask* Clone() override { return new CTaskComplexWanderStandard(m_nMoveState, m_nDir); } // 0x48E530 + CTask* Clone() const override { return new CTaskComplexWanderStandard(m_nMoveState, m_nDir); } // 0x48E530 eWanderType GetWanderType() override { return WANDER_TYPE_STANDARD; } // 0x48E5D0 void ScanForStuff(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskGangHasslePed.h b/source/game_sa/Tasks/TaskTypes/TaskGangHasslePed.h index b054d33673..8122aab87c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskGangHasslePed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskGangHasslePed.h @@ -22,8 +22,8 @@ class CTaskGangHasslePed : public CTaskComplex { CTaskGangHasslePed(CPed* ped, int32 a3, int32 a4, int32 a5); ~CTaskGangHasslePed() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskGangHasslePed(m_Ped, dword10, m_RndMin, m_RndMax); } // 0x6620D0 + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskGangHasslePed(m_Ped, dword10, m_RndMin, m_RndMax); } // 0x6620D0 CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskGangHassleVehicle.h b/source/game_sa/Tasks/TaskTypes/TaskGangHassleVehicle.h index 0d0b86c566..3bd21f8617 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskGangHassleVehicle.h +++ b/source/game_sa/Tasks/TaskTypes/TaskGangHassleVehicle.h @@ -31,8 +31,8 @@ class CTaskGangHassleVehicle : public CTaskComplex { CTaskGangHassleVehicle(CVehicle* vehicle, int32 a3, uint8 a4, float a5, float a6); ~CTaskGangHassleVehicle() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskGangHassleVehicle(m_Vehicle, m_nHasslePosId, byte18, dword1C, m_fOffsetX); } // 0x65FC00; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskGangHassleVehicle(m_Vehicle, m_nHasslePosId, byte18, dword1C, m_fOffsetX); } // 0x65FC00; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskGoToVehicleAndLean.h b/source/game_sa/Tasks/TaskTypes/TaskGoToVehicleAndLean.h index d1d789c54b..1b7ab66b16 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskGoToVehicleAndLean.h +++ b/source/game_sa/Tasks/TaskTypes/TaskGoToVehicleAndLean.h @@ -17,9 +17,9 @@ class CTaskGoToVehicleAndLean : public CTaskComplex { CTaskGoToVehicleAndLean(CVehicle* vehicle, int32 leanAnimDurationInMs); ~CTaskGoToVehicleAndLean() override; - eTaskType GetTaskType() override { return Type; } // 0x660ED0 - CTask* Clone() override { return new CTaskGoToVehicleAndLean(m_Vehicle, m_LeanAnimDurationInMs); } // 0x6621B0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x660ED0 + CTask* Clone() const override { return new CTaskGoToVehicleAndLean(m_Vehicle, m_LeanAnimDurationInMs); } // 0x6621B0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; CTask* CreateNextSubTask(CPed* ped) override; CTask* CreateFirstSubTask(CPed* ped) override; CTask* ControlSubTask(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskLeanOnVehicle.h b/source/game_sa/Tasks/TaskTypes/TaskLeanOnVehicle.h index 3fad124bf2..5a13232235 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskLeanOnVehicle.h +++ b/source/game_sa/Tasks/TaskTypes/TaskLeanOnVehicle.h @@ -21,9 +21,9 @@ class CTaskLeanOnVehicle : public CTaskSimple { CTaskLeanOnVehicle(CEntity* vehicle, int32 leanAnimDurationInMs, uint8 a4); ~CTaskLeanOnVehicle() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskLeanOnVehicle(m_Vehicle, m_LeanAnimDurationInMs, field_10); } // 0x6610A0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskLeanOnVehicle(m_Vehicle, m_LeanAnimDurationInMs, field_10); } // 0x6610A0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; static void FinishAnimCB(CAnimBlendAssociation* assoc, void* data); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleAbseil.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleAbseil.h index 154150793f..44fe21206a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleAbseil.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleAbseil.h @@ -7,7 +7,7 @@ class CTaskSimpleAbseil : public CTaskSimpleRunAnim public: CTaskSimpleAbseil() : CTaskSimpleRunAnim(ANIM_GROUP_DEFAULT, ANIM_ID_ABSEIL, 4.0F, TASK_SIMPLE_ABSEIL, "Abseil", false) {} ~CTaskSimpleAbseil() override {} - CTask* Clone() override { return new CTaskSimpleAbseil(); } + CTask* Clone() const override { return new CTaskSimpleAbseil(); } virtual bool IsInterruptable(CPed* ped) { return false; } }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleAchieveHeading.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleAchieveHeading.h index 291c82ba4a..ee0bee8ae1 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleAchieveHeading.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleAchieveHeading.h @@ -20,9 +20,9 @@ class CTaskSimpleAchieveHeading : public CTaskSimple { CTaskSimpleAchieveHeading(float fAngle, float changeRateMult = 0.5f, float maxHeading = 0.2f); ~CTaskSimpleAchieveHeading() override = default; // 0x667E70 - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleAchieveHeading(m_fAngle, m_fChangeRateMult, m_fMaxHeading); } // 0x66CCF0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleAchieveHeading(m_fAngle, m_fChangeRateMult, m_fMaxHeading); } // 0x66CCF0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(class CPed* ped) override; void QuitIK(CPed* ped) const; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleAffectSecondaryBehaviour.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleAffectSecondaryBehaviour.h index b78e1ef554..4fa0616fd1 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleAffectSecondaryBehaviour.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleAffectSecondaryBehaviour.h @@ -26,9 +26,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleAffectSecondaryBehaviour : public CTaskSimp CTaskSimpleAffectSecondaryBehaviour(bool add, eSecondaryTask secondaryTaskType, CTask* task); CTaskSimpleAffectSecondaryBehaviour(const CTaskSimpleAffectSecondaryBehaviour&); // NOTSA - CTask* Clone() override { return new CTaskSimpleAffectSecondaryBehaviour(*this); } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return false; } + CTask* Clone() const override { return new CTaskSimpleAffectSecondaryBehaviour(*this); } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleAnim.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleAnim.h index 715c633cfa..b51c327265 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleAnim.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleAnim.h @@ -14,17 +14,17 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleAnim : public CTaskSimple { CAnimBlendAssociation *m_pAnim; union { struct { - uint8 m_bIsFinished : 1; - uint8 m_bDontInterrupt : 1; - uint8 m_bHoldLastFrame : 1; + bool m_bIsFinished : 1; + bool m_bDontInterrupt : 1; + bool m_bHoldLastFrame : 1; // These flags are used in CTaskSimpleRunAnim only - uint8 m_bDontBlendOut : 1; + bool m_bDontBlendOut : 1; // These flags are used in CTaskSimpleRunNamedAnim only - uint8 m_bRunInSequence : 1; - uint8 m_bOffsetAtEnd : 1; - uint8 m_bOffsetAvailable : 1; + bool m_bRunInSequence : 1; + bool m_bOffsetAtEnd : 1; + bool m_bOffsetAvailable : 1; }; uint8 m_nFlags; }; @@ -33,7 +33,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleAnim : public CTaskSimple { explicit CTaskSimpleAnim(bool bHoldLastFrame); ~CTaskSimpleAnim() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; static void FinishRunAnimCB(CAnimBlendAssociation* blendAssoc, void* data); //data is CTaskSimpleAnim static void InjectHooks(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleArrestPed.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleArrestPed.h index 9cdad9f1f1..4fba2f4fbf 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleArrestPed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleArrestPed.h @@ -14,9 +14,9 @@ class CTaskSimpleArrestPed : public CTaskSimple { CTaskSimpleArrestPed(CPed* ped); ~CTaskSimpleArrestPed() override; - eTaskType GetTaskType() override { return Type; } // 0x68B680 - CTask* Clone() override { return new CTaskSimpleArrestPed(m_Ped); } // 0x68CD10 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x68B680 + CTask* Clone() const override { return new CTaskSimpleArrestPed(m_Ped); } // 0x68CD10 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed*) override; void StartAnim(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleBeHit.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleBeHit.cpp index aaa0dfb3e6..ede1fbf02a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleBeHit.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleBeHit.cpp @@ -49,7 +49,7 @@ CTaskSimpleBeHit::~CTaskSimpleBeHit() { } // 0x623290 -CTask* CTaskSimpleBeHit::Clone() { +CTask* CTaskSimpleBeHit::Clone() const { auto task = new CTaskSimpleBeHit(m_Attacker, m_eHitZone, m_nDirn, m_nHitPower); task->m_bAnimAdded = m_bAnimAdded; task->m_nAnimId = m_nAnimId; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleBeHit.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleBeHit.h index 4dd9545975..afef2f1f7f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleBeHit.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleBeHit.h @@ -25,9 +25,9 @@ class CTaskSimpleBeHit : public CTaskSimple { CTaskSimpleBeHit(CPed* ped, ePedPieceTypes pedPieceType, int32 direction, int32 damageHealth); ~CTaskSimpleBeHit() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void StartAnim(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleBikeJacked.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleBikeJacked.h index 6a5f3ce2a1..5e55c00c98 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleBikeJacked.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleBikeJacked.h @@ -28,9 +28,9 @@ class CTaskSimpleBikeJacked : public CTaskSimple { static void FinishAnimBikeHitCB(CAnimBlendAssociation* anim, void* data); - CTask* Clone() override { return new CTaskSimpleBikeJacked{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleBikeJacked{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarAlign.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarAlign.h index f4f621d421..45b06c57b8 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarAlign.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarAlign.h @@ -31,9 +31,9 @@ class CTaskSimpleCarAlign : public CTaskSimple { void FixHeading(CPed* ped); void StartAnim(CPed* ped); - CTask* Clone() override { return new CTaskSimpleCarAlign{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarAlign{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarCloseDoorFromInside.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarCloseDoorFromInside.h index b3f0edd61e..fefdb8d981 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarCloseDoorFromInside.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarCloseDoorFromInside.h @@ -30,9 +30,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarCloseDoorFromInside : public CTaskSimple void StartAnim(CPed const* ped); void ProcessDoorOpen(CPed const* ped); // NOTSA - CTask* Clone() override { return new CTaskSimpleCarCloseDoorFromInside{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarCloseDoorFromInside{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarCloseDoorFromOutside.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarCloseDoorFromOutside.h index dfb08666d4..8daec52a09 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarCloseDoorFromOutside.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarCloseDoorFromOutside.h @@ -30,9 +30,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarCloseDoorFromOutside : public CTaskSimpl void ProcessDoorOpen(CPed const* ped); // NOTSA - CTask* Clone() override { return new CTaskSimpleCarCloseDoorFromOutside{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarCloseDoorFromOutside{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDrive.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDrive.cpp index c715ab2b9f..47d2beead4 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDrive.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDrive.cpp @@ -152,7 +152,7 @@ void CTaskSimpleCarDrive::ProcessBopping(CPed* ped, bool a3) { } // 0x63DC20 -CTask* CTaskSimpleCarDrive::Clone() { +CTask* CTaskSimpleCarDrive::Clone() const { auto task = new CTaskSimpleCarDrive(m_pVehicle); task->m_bUpdateCurrentVehicle = m_bUpdateCurrentVehicle; return task; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDrive.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDrive.h index ae098e3dac..c3f054de14 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDrive.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDrive.h @@ -43,10 +43,10 @@ class CTaskSimpleCarDrive : public CTaskSimple { explicit CTaskSimpleCarDrive(CVehicle* vehicle, CTaskUtilityLineUpPedWithCar* utilityTask = {}, bool updateCurrentVehicle = {}); ~CTaskSimpleCarDrive() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; bool ProcessPed(class CPed* ped) override; - bool MakeAbortable(class CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(class CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool SetPedPosition(CPed* ped) override; void TriggerIK(CPed* ped) const; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDriveTimed.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDriveTimed.h index eff2bbf1d8..442be864b6 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDriveTimed.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarDriveTimed.h @@ -16,8 +16,8 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarDriveTimed : public CTaskSimpleCarDrive CTaskSimpleCarDriveTimed(CVehicle* vehicle, int32 nTime); ~CTaskSimpleCarDriveTimed() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleCarDriveTimed(m_pVehicle, m_nTime); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleCarDriveTimed(m_pVehicle, m_nTime); } bool ProcessPed(class CPed* ped) override; private: diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarGetIn.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarGetIn.h index 16fc1e8974..ba42bc804b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarGetIn.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarGetIn.h @@ -35,9 +35,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarGetIn : public CTaskSimple { void StartAnim(CPed const* ped); - CTask* Clone() override { return new CTaskSimpleCarGetIn{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarGetIn{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarGetOut.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarGetOut.h index 5100dee581..db95993534 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarGetOut.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarGetOut.h @@ -33,9 +33,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarGetOut : public CTaskSimple { void StartAnim(const CPed* ped); - CTask* Clone() override { return new CTaskSimpleCarGetOut{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarGetOut{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarJumpOut.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarJumpOut.h index bddfa3e521..842ffd7769 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarJumpOut.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarJumpOut.h @@ -34,9 +34,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarJumpOut : public CTaskSimple { auto ComputeAnimID() -> std::pair; // NOTSA: Changed return type void ComputeAnimID_ToHook(AssocGroupId& animGroup, AnimationId& animId) { std::tie(animGroup, animId) = ComputeAnimID(); } // Hooking this instead - CTask* Clone() override { return new CTaskSimpleCarJumpOut{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarJumpOut{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarOpenDoorFromOutside.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarOpenDoorFromOutside.h index 9bdf29553b..f1010f9bfb 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarOpenDoorFromOutside.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarOpenDoorFromOutside.h @@ -37,9 +37,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarOpenDoorFromOutside : public CTaskSimple void StartAnim(CPed* ped); void IfNotAlreadySetPlayerCanExitCarFlag(); // NOTSA? - CTask* Clone() override { return new CTaskSimpleCarOpenDoorFromOutside{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarOpenDoorFromOutside{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarOpenLockedDoorFromOutside.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarOpenLockedDoorFromOutside.h index a6011c29d7..41978e621b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarOpenLockedDoorFromOutside.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarOpenLockedDoorFromOutside.h @@ -29,9 +29,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarOpenLockedDoorFromOutside : public CTask static void FinishAnimCarOpenLockedDoorFromOutsideCB(CAnimBlendAssociation* anim, void* data); void StartAnim(CPed* ped); - CTask* Clone() override { return new CTaskSimpleCarOpenLockedDoorFromOutside{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarOpenLockedDoorFromOutside{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed * ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsDriver.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsDriver.cpp index 88e7a3b093..b297ce0d44 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsDriver.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsDriver.cpp @@ -25,7 +25,7 @@ CTaskSimpleCarSetPedInAsDriver::~CTaskSimpleCarSetPedInAsDriver() { } // 0x649E00 -CTask* CTaskSimpleCarSetPedInAsDriver::Clone() { +CTask* CTaskSimpleCarSetPedInAsDriver::Clone() const { auto task = new CTaskSimpleCarSetPedInAsDriver(m_pTargetVehicle, m_pUtility); task->m_bWarpingInToCar = m_bWarpingInToCar; task->m_nDoorFlagsToClear = m_nDoorFlagsToClear; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsDriver.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsDriver.h index 21a532a6f1..02270585d9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsDriver.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsDriver.h @@ -27,9 +27,9 @@ class CTaskSimpleCarSetPedInAsDriver : public CTaskSimple { CTaskSimpleCarSetPedInAsDriver(CVehicle *targetVehicle, bool warpingInToCar, CTaskUtilityLineUpPedWithCar *utility = nullptr); ~CTaskSimpleCarSetPedInAsDriver() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return false; } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } bool ProcessPed(CPed* ped) override; }; VALIDATE_SIZE(CTaskSimpleCarSetPedInAsDriver, 0x1C); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsPassenger.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsPassenger.cpp index a05ccd7677..388ae44475 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsPassenger.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsPassenger.cpp @@ -13,21 +13,23 @@ CTaskSimpleCarSetPedInAsPassenger::CTaskSimpleCarSetPedInAsPassenger(CVehicle* t CEntity::SafeRegisterRef(m_pTargetVehicle); } +// For 0x649D90 +CTaskSimpleCarSetPedInAsPassenger::CTaskSimpleCarSetPedInAsPassenger(const CTaskSimpleCarSetPedInAsPassenger& o) : + CTaskSimpleCarSetPedInAsPassenger{ + o.m_pTargetVehicle, + o.m_nTargetDoor, + o.m_bWarpingInToCar, + o.m_pUtility + } +{ + m_nNumGettingInToClear = o.m_nNumGettingInToClear; +} // 0x647080 CTaskSimpleCarSetPedInAsPassenger::~CTaskSimpleCarSetPedInAsPassenger() { CEntity::SafeCleanUpRef(m_pTargetVehicle); } -// 0x649D90 -CTask* CTaskSimpleCarSetPedInAsPassenger::Clone() { - auto task = new CTaskSimpleCarSetPedInAsPassenger(m_pTargetVehicle, m_nTargetDoor, m_pUtility); - task->m_bWarpingInToCar = m_bWarpingInToCar; - task->m_nDoorFlagsToClear = m_nDoorFlagsToClear; - task->m_nNumGettingInToClear = m_nNumGettingInToClear; - return task; -} - // 0x64B5D0 bool CTaskSimpleCarSetPedInAsPassenger::ProcessPed(CPed* ped) { return plugin::CallMethodAndReturn(this, ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsPassenger.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsPassenger.h index b35c84c40b..0d23efdd18 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsPassenger.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedInAsPassenger.h @@ -29,11 +29,12 @@ class CTaskSimpleCarSetPedInAsPassenger : public CTaskSimple { static constexpr auto Type = TASK_SIMPLE_CAR_SET_PED_IN_AS_PASSENGER; CTaskSimpleCarSetPedInAsPassenger(CVehicle* targetVehicle, eTargetDoor nTargetDoor, bool warpingInToCar /* notsa arg */ = false, CTaskUtilityLineUpPedWithCar* utility = nullptr); + CTaskSimpleCarSetPedInAsPassenger(const CTaskSimpleCarSetPedInAsPassenger&); ~CTaskSimpleCarSetPedInAsPassenger() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return false; } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleCarSetPedInAsPassenger{*this}; } // 0x649D90 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } bool ProcessPed(CPed* ped) override; }; VALIDATE_SIZE(CTaskSimpleCarSetPedInAsPassenger, 0x20); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedOut.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedOut.cpp index 921dfd77d2..e315423413 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedOut.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedOut.cpp @@ -15,8 +15,8 @@ CTaskSimpleCarSetPedOut::~CTaskSimpleCarSetPedOut() { CEntity::SafeCleanUpRef(m_pTargetVehicle); } -CTask* CTaskSimpleCarSetPedOut::Clone() { - return plugin::CallMethodAndReturn(this); +CTask* CTaskSimpleCarSetPedOut::Clone() const { + return plugin::CallMethodAndReturn(this); } bool CTaskSimpleCarSetPedOut::ProcessPed(CPed* ped) { diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedOut.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedOut.h index 408a3158a0..95e6bb4b4a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedOut.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetPedOut.h @@ -26,9 +26,9 @@ class CTaskSimpleCarSetPedOut : public CTaskSimple { CTaskSimpleCarSetPedOut(CVehicle* targetVehicle, eTargetDoor nTargetDoor, bool bSwitchOffEngine, bool warpingOutOfCar /*notsa arg*/ = false); ~CTaskSimpleCarSetPedOut() override; - eTaskType GetTaskType() override { return Type; }; - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return false; } + eTaskType GetTaskType() const override { return Type; }; + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } bool ProcessPed(CPed* ped) override; }; VALIDATE_SIZE(CTaskSimpleCarSetPedOut, 0x18); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetTempAction.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetTempAction.h index 1d66fbded3..977a1a9a52 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetTempAction.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSetTempAction.h @@ -22,9 +22,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarSetTempAction : public CTaskSimpleCarDri CTaskSimpleCarSetTempAction(const CTaskSimpleCarSetTempAction&); ~CTaskSimpleCarSetTempAction() = default; - CTask* Clone() override { return new CTaskSimpleCarSetTempAction(*this); } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarSetTempAction(*this); } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSlowDragPedOut.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSlowDragPedOut.h index 81536fa03e..323284d02c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSlowDragPedOut.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarSlowDragPedOut.h @@ -29,9 +29,9 @@ class CTaskSimpleCarSlowDragPedOut : public CTaskSimple { void StartAnim(const CPed* ped); CPed* GetJackedPed() const; // NOTSA - CTask* Clone() override { return new CTaskSimpleCarSlowDragPedOut(m_Vehicle, m_TargetDoor, m_LineUpPedWithCarTask, m_bWasPedStatic); } // 0x649FD0 - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarSlowDragPedOut(m_Vehicle, m_TargetDoor, m_LineUpPedWithCarTask, m_bWasPedStatic); } // 0x649FD0 + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarWaitForDoorNotToBeInUse.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarWaitForDoorNotToBeInUse.h index 897f108000..3b65896f1d 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarWaitForDoorNotToBeInUse.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarWaitForDoorNotToBeInUse.h @@ -22,9 +22,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarWaitForDoorNotToBeInUse : public CTaskSi CTaskSimpleCarWaitForDoorNotToBeInUse(const CTaskSimpleCarWaitForDoorNotToBeInUse&); ~CTaskSimpleCarWaitForDoorNotToBeInUse(); - CTask* Clone() override { return new CTaskSimpleCarWaitForDoorNotToBeInUse{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCarWaitForDoorNotToBeInUse{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarWaitToSlowDown.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarWaitToSlowDown.h index fe59cbea59..2a625fb949 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCarWaitToSlowDown.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCarWaitToSlowDown.h @@ -22,9 +22,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCarWaitToSlowDown : public CTaskSimple { CTaskSimpleCarWaitToSlowDown(CVehicle* vehicle, SlowDownType type); ~CTaskSimpleCarWaitToSlowDown() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleCarWaitToSlowDown(m_TargetVehicle, m_SlowType); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleCarWaitToSlowDown(m_TargetVehicle, m_SlowType); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleChat.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleChat.cpp new file mode 100644 index 0000000000..c3e5365058 --- /dev/null +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleChat.cpp @@ -0,0 +1,38 @@ +#include "StdInc.h" +#include "TaskSimpleChat.h" + + +void CTaskSimpleChat::InjectHooks() { + RH_ScopedVirtualClass(CTaskSimpleChat, 0x86C78C, 10); + RH_ScopedCategory("Tasks/TaskTypes"); + + RH_ScopedInstall(Destructor, 0x5F7FD0); + RH_ScopedVMTInstall(Clone, 0x5F65E0); + RH_ScopedVMTInstall(IsInterruptable, 0x5F6650); +} + +// 0x5F65A0 +CTaskSimpleChat::CTaskSimpleChat(uint32 duration) : + CTaskSimpleRunTimedAnim{ + ANIM_GROUP_DEFAULT, + ANIM_ID_IDLE_CHAT, + 4.f, + -4.f, + duration, + TASK_SIMPLE_CHAT, + "Chat", + 0 + } +{ +} + +// 0x5F65E0 +CTaskSimpleChat::CTaskSimpleChat(const CTaskSimpleChat& o) : + CTaskSimpleChat{ o.m_durationMs } +{ +} + +// 0x5F7FD0 +CTaskSimpleChat::~CTaskSimpleChat() { + assert(false && "Destructor not reversed"); // TODO: Reverse destructor} +} diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleChat.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleChat.h new file mode 100644 index 0000000000..3bbc57a909 --- /dev/null +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleChat.h @@ -0,0 +1,26 @@ +#pragma once + +#include "TaskSimpleRunTimedAnim.h" + +class CPed; + +class CTaskSimpleChat : public CTaskSimpleRunTimedAnim { +public: + static constexpr auto Type = TASK_SIMPLE_CHAT; + + static void InjectHooks(); + + CTaskSimpleChat(uint32 duration); + CTaskSimpleChat(const CTaskSimpleChat& o); + ~CTaskSimpleChat(); + + virtual CTask* Clone() const { return new CTaskSimpleChat{ *this }; } + virtual bool IsInterruptable(CPed const* ped) { return false; } + +private: + // 0x5F7FD0 + CTaskSimpleChat* Destructor() { + this->CTaskSimpleChat::~CTaskSimpleChat(); + return this; + } +}; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleClearLookAt.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleClearLookAt.h index 1e6dff837e..175278454d 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleClearLookAt.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleClearLookAt.h @@ -15,9 +15,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleClearLookAt : public CTaskSimple { CTaskSimpleClearLookAt() = default; ~CTaskSimpleClearLookAt() = default; - CTask* Clone() override { return new CTaskSimpleClearLookAt(*this); } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return true; } + CTask* Clone() const override { return new CTaskSimpleClearLookAt(*this); } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleClimb.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleClimb.h index 36cdeaf737..8bd15198ae 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleClimb.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleClimb.h @@ -65,10 +65,10 @@ class CTaskSimpleClimb : public CTaskSimple { CTaskSimpleClimb(CEntity* climbEntity, const CVector& vecTarget, float fHeading, uint8 nSurfaceType, eClimbHeights nHeight, bool bForceClimb); ~CTaskSimpleClimb() override; - CTask* Clone() override { return new CTaskSimpleClimb(m_pClimbEnt, m_vecHandholdPos, m_fHandholdHeading, m_nSurfaceType, (eClimbHeights)m_nHeightForAnim, m_bForceClimb); } + CTask* Clone() const override { return new CTaskSimpleClimb(m_pClimbEnt, m_vecHandholdPos, m_fHandholdHeading, m_nSurfaceType, (eClimbHeights)m_nHeightForAnim, m_bForceClimb); } bool ProcessPed(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; - eTaskType GetTaskType() override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; + eTaskType GetTaskType() const override { return Type; } static CEntity* TestForClimb(CPed* ped, CVector& climbPos, float& fAngle, uint8& nSurfaceType, bool theBool); static void* ScanToGrabSectorList(CPtrList* sectorList, CPed* ped, CVector& climbPos, float& angle, uint8& pSurfaceType, bool flag1, bool bStandUp, bool bVault); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCower.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCower.h index d3c9734776..d301909d0b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCower.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCower.h @@ -15,7 +15,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleCower : public CTaskSimpleRunAnim { CTaskSimpleCower() : CTaskSimpleRunAnim{ ANIM_GROUP_DEFAULT, ANIM_ID_HANDSCOWER, 4.0, Type, "Cower", false } {} ~CTaskSimpleCower() = default; - CTask* Clone() override { return new CTaskSimpleCower{}; } + CTask* Clone() const override { return new CTaskSimpleCower{}; } virtual bool IsInterruptable(CPed const* ped) { return false; } // NOTE: Possible leftover from `CTaskSimpleRunAnim`? private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleCreateCarAndGetIn.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleCreateCarAndGetIn.h index f283ac4338..b30e0cbb45 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleCreateCarAndGetIn.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleCreateCarAndGetIn.h @@ -19,9 +19,9 @@ class CTaskSimpleCreateCarAndGetIn : public CTaskSimple { CTaskSimpleCreateCarAndGetIn(CVector const& pos, int32 model); ~CTaskSimpleCreateCarAndGetIn() override; - CTask* Clone() override { return new CTaskSimpleCreateCarAndGetIn{ *this }; } - eTaskType GetTaskType() override { return Type; }; - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleCreateCarAndGetIn{ *this }; } + eTaskType GetTaskType() const override { return Type; }; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleDead.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleDead.h index 03ff259070..c0defc4db6 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleDead.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleDead.h @@ -13,9 +13,9 @@ class CTaskSimpleDead : public CTaskSimple { CTaskSimpleDead(uint32 deathTime, bool); ~CTaskSimpleDead() override = default; // 0x6305F0 - eTaskType GetTaskType() override { return Type; } // 0x6305D0 - CTask* Clone() override { return new CTaskSimpleDead(m_nDeathTimeMS, m_nFlags); } // 0x636100 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return true; }; // 0x6305E0 + eTaskType GetTaskType() const override { return Type; } // 0x6305D0 + CTask* Clone() const override { return new CTaskSimpleDead(m_nDeathTimeMS, m_nFlags); } // 0x636100 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; }; // 0x6305E0 bool ProcessPed(CPed* ped) override; static void InjectHooks(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleDie.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleDie.cpp index 2e51fe417f..fcfc30f02c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleDie.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleDie.cpp @@ -70,7 +70,7 @@ void CTaskSimpleDie::StartAnim(CPed* ped) { } // 0x635DA0 -CTask* CTaskSimpleDie::Clone() { +CTask* CTaskSimpleDie::Clone() const { if (m_animHierarchy) { return new CTaskSimpleDie(m_animHierarchy, m_animFlags, m_blendDelta, m_animSpeed); } else { diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleDie.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleDie.h index 973bd36ba3..a8a58d6b0b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleDie.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleDie.h @@ -36,9 +36,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleDie : public CTaskSimple { CTaskSimpleDie(CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float blendDelta, float animSpeed); ~CTaskSimpleDie() override; - eTaskType GetTaskType() override { return Type; } // 0x62FA50 - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x62FA50 + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void StartAnim(CPed* ped); @@ -49,7 +49,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleDie : public CTaskSimple { CTaskSimpleDie* Constructor(CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float blendDelta, float animSpeed); CTaskSimpleDie* Destructor(); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); bool ProcessPed_Reversed(CPed* ped); }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleDieInCar.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleDieInCar.h index 1e3c961887..eea3021909 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleDieInCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleDieInCar.h @@ -9,8 +9,8 @@ class CTaskSimpleDieInCar : public CTaskSimpleDie { CTaskSimpleDieInCar(AssocGroupId groupId, AnimationId animId); ~CTaskSimpleDieInCar() override = default; // 0x62FC70, 0x6375B0 - CTask* Clone() override { return new CTaskSimpleDieInCar(m_animGroupId, m_animId); } // 0x635EF0 - eTaskType GetTaskType() override { return Type; } // 0x62FC60 + CTask* Clone() const override { return new CTaskSimpleDieInCar(m_animGroupId, m_animId); } // 0x635EF0 + eTaskType GetTaskType() const override { return Type; } // 0x62FC60 bool ProcessPed(CPed* ped) override; // 0x6398F0 }; VALIDATE_SIZE(CTaskSimpleDieInCar, 0x28); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleDrown.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleDrown.h index 9352edc516..93ce010c62 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleDrown.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleDrown.h @@ -12,8 +12,8 @@ class CTaskSimpleDrown : public CTaskSimpleDie { CTaskSimpleDrown(); ~CTaskSimpleDrown() override = default; // 0x62FF10, 0x6378D0 - eTaskType GetTaskType() override { return Type; } // 0x62FF00 - CTask* Clone() override { return new CTaskSimpleDrown(); } // 0x635E60 + eTaskType GetTaskType() const override { return Type; } // 0x62FF00 + CTask* Clone() const override { return new CTaskSimpleDrown(); } // 0x635E60 bool ProcessPed(CPed* ped) override; private: diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleDrownInCar.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleDrownInCar.h index 633d95e413..0093eac989 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleDrownInCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleDrownInCar.h @@ -9,9 +9,9 @@ class CTaskSimpleDrownInCar : public CTaskSimple { CTaskSimpleDrownInCar() = default; // 0x62FF20 ~CTaskSimpleDrownInCar() override = default; // 0x62FF60 - eTaskType GetTaskType() override { return Type; }; // 0x62FF40 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return false; } // 0x62FF50 - CTask* Clone() override { return new CTaskSimpleDrownInCar(); } // 0x636010 + eTaskType GetTaskType() const override { return Type; }; // 0x62FF40 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } // 0x62FF50 + CTask* Clone() const override { return new CTaskSimpleDrownInCar(); } // 0x636010 bool ProcessPed(CPed* ped) override; private: diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleDuck.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleDuck.h index 0c10bdaefa..1e51928146 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleDuck.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleDuck.h @@ -41,9 +41,9 @@ class CTaskSimpleDuck : public CTaskSimple { CTaskSimpleDuck(eDuckControlTypes DuckControlType, uint16 nLengthOfDuck, int16 nUseShotsWhizzingEvents = -1); ~CTaskSimpleDuck() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleDuck(m_nDuckControlType, m_nLengthOfDuck, m_nShotWhizzingCounter); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleDuck(m_nDuckControlType, m_nLengthOfDuck, m_nShotWhizzingCounter); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; static bool CanPedDuck(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleEvasiveDive.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleEvasiveDive.h index 3717eb17fe..5881f79164 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleEvasiveDive.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleEvasiveDive.h @@ -14,9 +14,9 @@ class CTaskSimpleEvasiveDive : public CTaskSimple { CTaskSimpleEvasiveDive(CVehicle *vehicle); ~CTaskSimpleEvasiveDive() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleEvasiveDive(m_Vehicle); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleEvasiveDive(m_Vehicle); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void StartAnim(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleEvasiveStep.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleEvasiveStep.h index 47261d9da0..c6630a08ad 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleEvasiveStep.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleEvasiveStep.h @@ -15,9 +15,9 @@ class CTaskSimpleEvasiveStep : public CTaskSimple { explicit CTaskSimpleEvasiveStep(CEntity* entity); ~CTaskSimpleEvasiveStep() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleEvasiveStep(m_Entity); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleEvasiveStep(m_Entity); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void StartAnim(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleFacial.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleFacial.h index 6a8353bf9a..55595f95f2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleFacial.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleFacial.h @@ -33,9 +33,9 @@ class CTaskSimpleFacial : public CTaskSimple { CTaskSimpleFacial(eFacialExpression nFacialExpress, int32 nDuration); ~CTaskSimpleFacial() override = default; // 0x690CB0 - eTaskType GetTaskType() override { return Type; } // 0x690CA0 - CTask* Clone() override { return new CTaskSimpleFacial(m_nFacialExpression, m_nDuration); } // 0x692820 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x690CA0 + CTask* Clone() const override { return new CTaskSimpleFacial(m_nFacialExpression, m_nDuration); } // 0x692820 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; static AnimationId GetAnimId(eFacialExpression expression); }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleFall.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleFall.h index 2e68e07860..cca35448d0 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleFall.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleFall.h @@ -20,10 +20,10 @@ class CTaskSimpleFall : public CTaskSimple { CTaskSimpleFall(AnimationId nAnimId, AssocGroupId nAnimGroup, int32 nDownTime); ~CTaskSimpleFall() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleFall(m_nAnimId, m_nAnimGroup, m_nTotalDownTime); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleFall(m_nAnimId, m_nAnimGroup, m_nTotalDownTime); } bool ProcessPed(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed_Reversed(CPed* ped); bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleFight.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleFight.cpp index 9ea25e2374..1c5ed550fa 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleFight.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleFight.cpp @@ -34,6 +34,8 @@ int32 CTaskSimpleFight::GetComboAnimGroupID() { // 0x5BEDC0 void CTaskSimpleFight::LoadMeleeData() { + ZoneScoped; + plugin::Call<0x5BEDC0>(); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleFight.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleFight.h index ef7ccb7f0c..19ffb66209 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleFight.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleFight.h @@ -65,9 +65,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleFight : public CTaskSimple { CTaskSimpleFight(CEntity* entity, int32 nCommand, uint32 nIdlePeriod = 10000); ~CTaskSimpleFight() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleFight(m_pTargetEntity, m_nLastCommand, m_nIdlePeriod); } // 0x622E40 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleFight(m_pTargetEntity, m_nLastCommand, m_nIdlePeriod); } // 0x622E40 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; static void LoadMeleeData(); @@ -107,7 +107,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleFight : public CTaskSimple { CTaskSimpleFight* Constructor(CEntity* entity, int32 nCommand, uint32 nIdlePeriod); CTaskSimpleFight* Destructor(); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); bool ProcessPed_Reversed(CPed* ped); }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleFightingControl.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleFightingControl.h index 32541f2d9b..253b0b23ba 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleFightingControl.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleFightingControl.h @@ -28,9 +28,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleFightingControl : public CTaskSimple { int16 CalcMoveCommand(CPed* ped); - CTask* Clone() override { return new CTaskSimpleFightingControl{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleFightingControl{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGangDriveBy.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleGangDriveBy.cpp index 035ece0741..e517cb8904 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGangDriveBy.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGangDriveBy.cpp @@ -43,9 +43,8 @@ CTaskSimpleGangDriveBy::~CTaskSimpleGangDriveBy() CEntity::SafeCleanUpRef(m_pTargetEntity); } -CTask* CTaskSimpleGangDriveBy::Clone() -{ - return plugin::CallMethodAndReturn(this); +CTask* CTaskSimpleGangDriveBy::Clone() const { + return plugin::CallMethodAndReturn(this); } bool CTaskSimpleGangDriveBy::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGangDriveBy.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleGangDriveBy.h index d96d3af767..3aeabcd475 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGangDriveBy.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGangDriveBy.h @@ -43,9 +43,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleGangDriveBy : public CTaskSimple { CTaskSimpleGangDriveBy(CEntity *target, const CVector *targetPos, float abortRange, int8 frequencyPercentage, int8 drivebyStyle, bool seatRHS); ~CTaskSimpleGangDriveBy() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; }; VALIDATE_SIZE(CTaskSimpleGangDriveBy, 0x44); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGetUp.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleGetUp.h index c0ac0a8889..972c102f57 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGetUp.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGetUp.h @@ -14,10 +14,10 @@ class CTaskSimpleGetUp : public CTaskSimple { CTaskSimpleGetUp(); ~CTaskSimpleGetUp() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleGetUp(); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleGetUp(); } bool ProcessPed(class CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; private: friend void InjectHooksMain(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGiveCPR.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleGiveCPR.cpp index d8c643e1ee..fcd0aab368 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGiveCPR.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGiveCPR.cpp @@ -35,7 +35,7 @@ CTaskSimpleGiveCPR::~CTaskSimpleGiveCPR() { } // 0x659A10 -CTask* CTaskSimpleGiveCPR::Clone() { +CTask* CTaskSimpleGiveCPR::Clone() const { return Clone_Reversed(); } @@ -49,7 +49,7 @@ bool CTaskSimpleGiveCPR::MakeAbortable(CPed* ped, eAbortPriority priority, const return MakeAbortable_Reversed(ped, priority, event); } -CTask* CTaskSimpleGiveCPR::Clone_Reversed() { +CTask* CTaskSimpleGiveCPR::Clone_Reversed() const { return new CTaskSimpleGiveCPR(m_pAccident); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGiveCPR.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleGiveCPR.h index 587b5a3d8a..55a5ed7299 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGiveCPR.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGiveCPR.h @@ -22,10 +22,10 @@ class CTaskSimpleGiveCPR : public CTaskSimple { explicit CTaskSimpleGiveCPR(CAccident* accident); ~CTaskSimpleGiveCPR() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; bool ProcessPed(CPed* ped) override; - bool MakeAbortable(class CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(class CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; void ReviveDeadPed(CPed* ped); static void FinishGiveCPRAnimCB(CAnimBlendAssociation* anim, void* ptask); @@ -36,7 +36,7 @@ class CTaskSimpleGiveCPR : public CTaskSimple { CTaskSimpleGiveCPR* Constructor(CAccident* pAccident); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; bool ProcessPed_Reversed(CPed* ped); bool MakeAbortable_Reversed(class CPed* ped, eAbortPriority priority, const CEvent* event); }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPoint.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPoint.cpp index 00f15cbe6a..4df7ccf3b5 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPoint.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPoint.cpp @@ -85,7 +85,7 @@ bool CTaskSimpleGoToPoint::ProcessPed_Reversed(CPed* ped) { } else { bool bSprinting = false; - CWeaponInfo* pWeaponInfo = CWeaponInfo::GetWeaponInfo(ped->GetActiveWeapon().m_nType, eWeaponSkill::STD); + CWeaponInfo* pWeaponInfo = CWeaponInfo::GetWeaponInfo(ped->GetActiveWeapon().m_Type, eWeaponSkill::STD); if (!pWeaponInfo->flags.bHeavy) { auto* task = static_cast(ped->GetIntelligence()->GetTaskHold(false)); if (!task || !task->m_pAnimBlendAssociation) { diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPoint.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPoint.h index f21134ee2f..88b6969c4f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPoint.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPoint.h @@ -22,9 +22,9 @@ class CTaskSimpleGoToPoint : public CTaskSimpleGoTo { CTaskSimpleGoToPoint(eMoveState moveState, const CVector& targetPoint, float fRadius = 0.5f, bool bMoveTowardsTargetPoint = false, bool a6 = false); ~CTaskSimpleGoToPoint() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleGoToPoint(m_moveState, m_vecTargetPoint, m_fRadius, gotoPointFlags.m_bMoveTowardsTargetPoint, gotoPointFlags.m_b04); } // 0x66CC60 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleGoToPoint(m_moveState, m_vecTargetPoint, m_fRadius, gotoPointFlags.m_bMoveTowardsTargetPoint, gotoPointFlags.m_b04); } // 0x66CC60 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; // bDontCheckRadius is always false diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPointFine.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPointFine.cpp index 9b01521403..aa76c5b4b7 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPointFine.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPointFine.cpp @@ -3,8 +3,7 @@ #include "TaskSimpleGoToPointFine.h" #include "TaskSimpleDuck.h" -void CTaskSimpleGoToPointFine::InjectHooks() -{ +void CTaskSimpleGoToPointFine::InjectHooks() { RH_ScopedClass(CTaskSimpleGoToPointFine); RH_ScopedCategory("Tasks/TaskTypes"); RH_ScopedInstall(Constructor, 0x65EEB0); @@ -18,44 +17,36 @@ void CTaskSimpleGoToPointFine::InjectHooks() } // 0x65EEB0 -CTaskSimpleGoToPointFine::CTaskSimpleGoToPointFine(float moveRatio, CVector targetPoint, float fRadius, CEntity* entity) : - CTaskSimpleGoTo(PEDMOVE_WALK, targetPoint, fRadius) -{ +CTaskSimpleGoToPointFine::CTaskSimpleGoToPointFine(float moveRatio, CVector targetPoint, float fRadius, CEntity* entity) : CTaskSimpleGoTo(PEDMOVE_WALK, targetPoint, fRadius) { SetMoveRatio(moveRatio); } // 0x65EEB0 -CTaskSimpleGoToPointFine* CTaskSimpleGoToPointFine::Constructor(float moveRatio, CVector targetPoint, float fRadius, CEntity* entity) -{ +CTaskSimpleGoToPointFine* CTaskSimpleGoToPointFine::Constructor(float moveRatio, CVector targetPoint, float fRadius, CEntity* entity) { this->CTaskSimpleGoToPointFine::CTaskSimpleGoToPointFine(moveRatio, targetPoint, fRadius, entity); return this; } // 0x662040 -CTask* CTaskSimpleGoToPointFine::Clone() -{ +CTask* CTaskSimpleGoToPointFine::Clone() const { return CTaskSimpleGoToPointFine::Clone_Reversed(); } // 0x663500 -bool CTaskSimpleGoToPointFine::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) -{ +bool CTaskSimpleGoToPointFine::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) { return CTaskSimpleGoToPointFine::MakeAbortable_Reversed(ped, priority, event); } // 0x663540 -bool CTaskSimpleGoToPointFine::ProcessPed(CPed* ped) -{ +bool CTaskSimpleGoToPointFine::ProcessPed(CPed* ped) { return CTaskSimpleGoToPointFine::ProcessPed_Reversed(ped); } -CTask* CTaskSimpleGoToPointFine::Clone_Reversed() -{ +CTask* CTaskSimpleGoToPointFine::Clone_Reversed() const { return new CTaskSimpleGoToPointFine(m_fMoveRatio, m_vecTargetPoint, m_fRadius, nullptr); } -bool CTaskSimpleGoToPointFine::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event) -{ +bool CTaskSimpleGoToPointFine::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event) { QuitIK(ped); Finish(ped); return true; @@ -69,8 +60,7 @@ void CTaskSimpleGoToPointFine::Finish(CPed* ped) { ped->SetMoveAnim(); } -bool CTaskSimpleGoToPointFine::ProcessPed_Reversed(CPed* ped) -{ +bool CTaskSimpleGoToPointFine::ProcessPed_Reversed(CPed* ped) { CVector2D vecDistance = m_vecTargetPoint - ped->GetPosition(); if (m_fRadius * m_fRadius >= vecDistance.SquaredMagnitude() || HasCircledTarget(ped)) { QuitIK(ped); @@ -86,12 +76,11 @@ bool CTaskSimpleGoToPointFine::ProcessPed_Reversed(CPed* ped) // todo: IDA code, BaseRatio can be used // 0x65EF80 -void CTaskSimpleGoToPointFine::SetBlendedMoveAnim(CPed* ped) -{ - auto idleAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_IDLE); - auto walkAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_WALK); - auto runAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_RUN); - auto sprintAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_SPRINT); +void CTaskSimpleGoToPointFine::SetBlendedMoveAnim(CPed* ped) { + auto idleAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_IDLE); + auto walkAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_WALK); + auto runAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_RUN); + auto sprintAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_SPRINT); auto pIdleTiredAnimAssoc = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_IDLE_TIRED); if (ped->bIsDucking && ped->m_pIntelligence->GetTaskDuck(false)) { @@ -153,8 +142,7 @@ void CTaskSimpleGoToPointFine::SetBlendedMoveAnim(CPed* ped) runAnimAssoc->m_nFlags |= ANIMATION_STARTED; runAnimAssoc->m_fBlendDelta = 0.0f; runAnimAssoc->m_fBlendAmount = 3.0f - m_fMoveRatio; - if (!sprintAnimAssoc) - { + if (!sprintAnimAssoc) { sprintAnimAssoc = CAnimManager::AddAnimation(ped->m_pRwClump, ped->m_nAnimGroup, ANIM_ID_SPRINT); sprintAnimAssoc->m_fBlendAmount = 0.0f; sprintAnimAssoc->m_fSpeed = 1.0f; @@ -163,8 +151,7 @@ void CTaskSimpleGoToPointFine::SetBlendedMoveAnim(CPed* ped) sprintAnimAssoc->m_fBlendDelta = 0.0f; moveState = PEDMOVE_SPRINT; sprintAnimAssoc->m_fBlendAmount = m_fMoveRatio - 2.0f; - } - else { + } else { if (!walkAnimAssoc) { walkAnimAssoc = CAnimManager::AddAnimation(ped->m_pRwClump, ped->m_nAnimGroup, ANIM_ID_WALK); walkAnimAssoc->m_fBlendAmount = 0.0f; @@ -173,8 +160,7 @@ void CTaskSimpleGoToPointFine::SetBlendedMoveAnim(CPed* ped) walkAnimAssoc->m_nFlags |= ANIMATION_STARTED; walkAnimAssoc->m_fBlendDelta = 0.0f; walkAnimAssoc->m_fBlendAmount = 2.0f - m_fMoveRatio; - if (!runAnimAssoc) - { + if (!runAnimAssoc) { runAnimAssoc = CAnimManager::AddAnimation(ped->m_pRwClump, ped->m_nAnimGroup, ANIM_ID_RUN); runAnimAssoc->m_fBlendAmount = 0.0f; runAnimAssoc->m_fSpeed = 1.0f; @@ -185,8 +171,7 @@ void CTaskSimpleGoToPointFine::SetBlendedMoveAnim(CPed* ped) delete sprintAnimAssoc; moveState = PEDMOVE_RUN; } - } - else { + } else { if (!walkAnimAssoc) { walkAnimAssoc = CAnimManager::AddAnimation(ped->m_pRwClump, ped->m_nAnimGroup, ANIM_ID_WALK); walkAnimAssoc->m_fBlendAmount = 0.0f; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPointFine.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPointFine.h index f2dfff03fa..330b507cf3 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPointFine.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGoToPointFine.h @@ -12,9 +12,9 @@ class CTaskSimpleGoToPointFine : public CTaskSimpleGoTo { CTaskSimpleGoToPointFine(float moveRatio, CVector targetPoint, float fRadius = 0.5f, CEntity* entity = nullptr); ~CTaskSimpleGoToPointFine() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void Finish(CPed* ped); @@ -30,7 +30,7 @@ class CTaskSimpleGoToPointFine : public CTaskSimpleGoTo { CTaskSimpleGoToPointFine* Constructor(float moveRatio, CVector targetPoint, float fRadius, CEntity* entity); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); bool ProcessPed_Reversed(CPed* ped); }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGunControl.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleGunControl.cpp index 7da8830f8b..b9feab47de 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGunControl.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGunControl.cpp @@ -159,7 +159,7 @@ bool CTaskSimpleGunControl::ProcessPed(CPed* ped) { auto [nextGunCmd, dontCheckLeisureDur] = [&, this]() -> std::pair { using enum eGunCommand; - if (ped->GetActiveWeapon().m_nState == WEAPONSTATE_RELOADING && winfo.flags.bReload) { + if (ped->GetActiveWeapon().m_State == WEAPONSTATE_RELOADING && winfo.flags.bReload) { return { RELOAD, false }; // 0x625517 } diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleGunControl.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleGunControl.h index a2f16648ad..e5fe02e09a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleGunControl.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleGunControl.h @@ -47,9 +47,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleGunControl : public CTaskSimple { CTaskSimpleGunControl(const CTaskSimpleGunControl&); ~CTaskSimpleGunControl(); - CTask* Clone() override { return new CTaskSimpleGunControl{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleGunControl{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHandsUp.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHandsUp.h index 6447a32104..0ea7308ce2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHandsUp.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHandsUp.h @@ -16,7 +16,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHandsUp : public CTaskSimpleRunTimedAnim { CTaskSimpleHandsUp(const CTaskSimpleHandsUp& o) : CTaskSimpleHandsUp{o.m_durationMs} {} ~CTaskSimpleHandsUp() override = default; - CTask* Clone() override { return new CTaskSimpleHandsUp{*this}; } + CTask* Clone() const override { return new CTaskSimpleHandsUp{*this}; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromFront.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromFront.h index 54cf55baf8..f38b22b328 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromFront.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromFront.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitByGunFromFront : public CTaskSimpleRunAn CTaskSimpleHitByGunFromFront(); CTaskSimpleHitByGunFromFront(const CTaskSimpleHitByGunFromFront&); - virtual CTask* Clone() { return new CTaskSimpleHitByGunFromFront{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitByGunFromFront{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromLeft.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromLeft.h index 9699cbe289..8297880fbc 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromLeft.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromLeft.h @@ -15,7 +15,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitByGunFromLeft : public CTaskSimpleRunAni CTaskSimpleHitByGunFromLeft(); CTaskSimpleHitByGunFromLeft(const CTaskSimpleHitByGunFromLeft&); - virtual CTask* Clone() { return new CTaskSimpleHitByGunFromLeft{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitByGunFromLeft{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromRear.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromRear.h index 41fd7cb09b..cfa28c8f60 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromRear.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromRear.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitByGunFromRear : public CTaskSimpleRunAni CTaskSimpleHitByGunFromRear(); CTaskSimpleHitByGunFromRear(const CTaskSimpleHitByGunFromRear&); - virtual CTask* Clone() { return new CTaskSimpleHitByGunFromRear{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitByGunFromRear{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromRight.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromRight.h index ba2dc4284a..045188f16b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromRight.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitByGunFromRight.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitByGunFromRight : public CTaskSimpleRunAn CTaskSimpleHitByGunFromRight(); CTaskSimpleHitByGunFromRight(const CTaskSimpleHitByGunFromRight&); - virtual CTask* Clone() { return new CTaskSimpleHitByGunFromRight{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitByGunFromRight{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromBack.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromBack.h index 60ce1c8faf..71e4475ff9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromBack.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromBack.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitFromBack : public CTaskSimpleRunAnim { CTaskSimpleHitFromBack(); CTaskSimpleHitFromBack(const CTaskSimpleHitFromBack&); - virtual CTask* Clone() { return new CTaskSimpleHitFromBack{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitFromBack{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromBehind.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromBehind.h index 1af23befd8..d8a6377bc0 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromBehind.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromBehind.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitFromBehind : public CTaskSimpleRunAnim { CTaskSimpleHitFromBehind(); CTaskSimpleHitFromBehind(const CTaskSimpleHitFromBehind&); - virtual CTask* Clone() { return new CTaskSimpleHitFromBehind{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitFromBehind{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromFront.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromFront.h index fc870e9edf..c0cc1fb20c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromFront.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromFront.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitFromFront : public CTaskSimpleRunAnim { CTaskSimpleHitFromFront(); CTaskSimpleHitFromFront(const CTaskSimpleHitFromFront&); - virtual CTask* Clone() { return new CTaskSimpleHitFromFront{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitFromFront{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromLeft.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromLeft.h index 2b8985bbf6..ac3b1784f6 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromLeft.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromLeft.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitFromLeft : public CTaskSimpleRunAnim { CTaskSimpleHitFromLeft(); CTaskSimpleHitFromLeft(const CTaskSimpleHitFromLeft&); - virtual CTask* Clone() { return new CTaskSimpleHitFromLeft{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitFromLeft{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromRight.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromRight.h index 999a704c81..27bbfb128f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromRight.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitFromRight.h @@ -14,7 +14,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitFromRight : public CTaskSimpleRunAnim { CTaskSimpleHitFromRight(); CTaskSimpleHitFromRight(const CTaskSimpleHitFromRight&); - virtual CTask* Clone() { return new CTaskSimpleHitFromRight{ *this }; } + virtual CTask* Clone() const { return new CTaskSimpleHitFromRight{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitHead.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitHead.h index c23728acbd..1e6c46b3b9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitHead.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitHead.h @@ -13,9 +13,9 @@ class CTaskSimpleHitHead : public CTaskSimple { CTaskSimpleHitHead(); ~CTaskSimpleHitHead() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleHitHead(); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleHitHead(); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; static void FinishAnimCB(CAnimBlendAssociation* anim, void* data); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitWall.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitWall.h index 3f32e2d429..aecb64cee9 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHitWall.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHitWall.h @@ -13,7 +13,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleHitWall : public CTaskSimpleRunAnim { CTaskSimpleHitWall(); CTaskSimpleHitWall(const CTaskSimpleHitWall& o); - CTask* Clone() override { return new CTaskSimpleHitWall{ *this }; } + CTask* Clone() const override { return new CTaskSimpleHitWall{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks // 0x652F90 diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHoldEntity.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleHoldEntity.cpp index a7e6a0648d..d1308a890b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHoldEntity.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHoldEntity.cpp @@ -26,16 +26,21 @@ void CTaskSimpleHoldEntity::InjectHooks() { CTaskSimpleHoldEntity* CTaskSimpleHoldEntity::Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, bool bDisAllowDroppingOnAnimEnd) { this->CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(entityToHold, posn, boneFrameId, boneFlags, animId, groupId, bDisAllowDroppingOnAnimEnd); return this; } CTaskSimpleHoldEntity* CTaskSimpleHoldEntity::Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags) { this->CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(entityToHold, posn, boneFrameId, boneFlags, animName, animBlockName, animFlags); return this; } CTaskSimpleHoldEntity* CTaskSimpleHoldEntity::Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags) { this->CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(entityToHold, posn, boneFrameId, boneFlags, animBlock, animHierarchy, animFlags); return this; } -CTask* CTaskSimpleHoldEntity::Clone() { return CTaskSimpleHoldEntity::Clone_Reversed(); } +CTask* CTaskSimpleHoldEntity::Clone() const { return CTaskSimpleHoldEntity::Clone_Reversed(); } bool CTaskSimpleHoldEntity::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) { return CTaskSimpleHoldEntity::MakeAbortable_Reversed(ped, priority, event); } bool CTaskSimpleHoldEntity::ProcessPed(CPed* ped) { return ProcessPed_Reversed(ped); } bool CTaskSimpleHoldEntity::SetPedPosition(CPed* ped) { return CTaskSimpleHoldEntity::SetPedPosition_Reversed(ped); } // 0x6913A0 -CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(CEntity* entityToHold, CVector* posn, - uint8 boneFrameId, uint8 boneFlags, - AnimationId animId, AssocGroupId groupId, - bool bDisAllowDroppingOnAnimEnd) +CTaskSimpleHoldEntity::CTaskSimpleHoldEntity( + CEntity* entityToHold, + const CVector* posn, + uint8 boneFrameId, + uint8 boneFlags, + AnimationId animId, + AssocGroupId groupId, + bool bDisAllowDroppingOnAnimEnd +) : CTaskSimple() { m_pEntityToHold = entityToHold; @@ -86,7 +91,7 @@ CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(CEntity* entityToHold, } // 0x691470 -CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags) : CTaskSimple() +CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags) : CTaskSimple() { m_pEntityToHold = entityToHold; m_vecPosition = CVector(0.0f, 0.0f, 0.0f); @@ -111,7 +116,7 @@ CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(CEntity* entityToHold, CVector* pos } // 0x691550 -CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags) : CTaskSimple() +CTaskSimpleHoldEntity::CTaskSimpleHoldEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags) : CTaskSimple() { m_pEntityToHold = entityToHold; m_vecPosition = CVector(0.0f, 0.0f, 0.0f); @@ -153,7 +158,7 @@ CTaskSimpleHoldEntity::~CTaskSimpleHoldEntity() { } // 0x6929B0 -CTask* CTaskSimpleHoldEntity::Clone_Reversed() { +CTask* CTaskSimpleHoldEntity::Clone_Reversed() const { if (m_pAnimBlendHierarchy) return new CTaskSimpleHoldEntity(m_pEntityToHold, &m_vecPosition, m_nBoneFrameId, m_bBoneFlags, m_pAnimBlock, m_pAnimBlendHierarchy, static_cast(m_animFlags)); else diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleHoldEntity.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleHoldEntity.h index 27bac4bf0c..52f1162b39 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleHoldEntity.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleHoldEntity.h @@ -36,7 +36,7 @@ class CTaskSimpleHoldEntity : public CTaskSimple { CTaskSimpleHoldEntity( CEntity* entityToHold, - CVector* posn, + const CVector* posn, uint8 boneFrameId, uint8 boneFlags = HOLD_ENTITY_UPDATE_TRANSLATION_ONLY, AnimationId animId = AnimationId::ANIM_ID_NO_ANIMATION_SET, @@ -52,13 +52,13 @@ class CTaskSimpleHoldEntity : public CTaskSimple { AssocGroupId groupId = AssocGroupId::ANIM_GROUP_DEFAULT, bool bDisAllowDroppingOnAnimEnd = true ); - CTaskSimpleHoldEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags); - CTaskSimpleHoldEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags); + CTaskSimpleHoldEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags); + CTaskSimpleHoldEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags); ~CTaskSimpleHoldEntity() override; - eTaskType GetTaskType() override { return Type; }; // 0x691460 - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; }; // 0x691460 + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; @@ -79,7 +79,7 @@ class CTaskSimpleHoldEntity : public CTaskSimple { CTaskSimpleHoldEntity* Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags); CTaskSimpleHoldEntity* Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); bool ProcessPed_Reversed(CPed* ped); bool SetPedPosition_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKChain.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKChain.cpp index a0990a2f46..edc8fd77e1 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKChain.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKChain.cpp @@ -45,7 +45,7 @@ CTaskSimpleIKChain::~CTaskSimpleIKChain() { } // 0x633B00 -CTask* CTaskSimpleIKChain::Clone() { +CTask* CTaskSimpleIKChain::Clone() const { auto* task = new CTaskSimpleIKChain("", m_nEffectorBoneTag, m_vecEffectorVec, m_nPivotBoneTag, m_pEntity, m_nOffsetBoneTag, m_vecOffsetPos, m_fSpeed, m_nTime, m_nBlendTime); if (m_pIKChain) { task->m_fBlend = m_fBlend; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKChain.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKChain.h index 2bd7de8fd9..1dffdb7e59 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKChain.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKChain.h @@ -37,9 +37,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleIKChain : public CTaskSimple { float speed, int32 time, int32 blendTime); ~CTaskSimpleIKChain() override; - eTaskType GetTaskType() override { return Type; } // 0x62EC30 - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x62EC30 + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; virtual bool CreateIKChain(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKLookAt.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKLookAt.cpp index 0eebc22762..f9b29c095b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKLookAt.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKLookAt.cpp @@ -28,7 +28,7 @@ CTaskSimpleIKLookAt::CTaskSimpleIKLookAt(Const char* name, CEntity* lookAtEntity } // 0x633F00 -CTaskSimpleIKLookAt* CTaskSimpleIKLookAt::Clone() { +CTaskSimpleIKLookAt* CTaskSimpleIKLookAt::Clone() const { auto* task = new CTaskSimpleIKLookAt("", m_pEntity, m_nTime, m_nOffsetBoneTag, m_vecOffsetPos, m_bUseTorso, m_fSpeed, m_nBlendTime, m_nPriority); if (m_pIKChain) { task->m_fBlend = m_fBlend; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKLookAt.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKLookAt.h index 4ba189ddd5..52be98c821 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKLookAt.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKLookAt.h @@ -19,8 +19,8 @@ class CTaskSimpleIKLookAt : public CTaskSimpleIKChain { CTaskSimpleIKLookAt(Const char* name, CEntity* lookAtEntity, int32 time, ePedBones pedBoneID, CVector lookAtOffset, bool useTorso, float speed, int32 blendTime, int8 priority); ~CTaskSimpleIKLookAt() override = default; // 0x633EF0 - eTaskType GetTaskType() override { return Type; } - CTaskSimpleIKLookAt* Clone() override; + eTaskType GetTaskType() const override { return Type; } + CTaskSimpleIKLookAt* Clone() const override; bool CreateIKChain(CPed* ped) override; void UpdateLookAtInfo(const char* strPurpose, CPed* ped, CEntity* targetPed, int32 time, ePedBones pedBoneID, RwV3d lookAtOffset, bool useTorso, float fSpeed, int32 blendTime, int32 unused); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKManager.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKManager.cpp index a60c487e6e..92898ce412 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKManager.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKManager.cpp @@ -47,8 +47,8 @@ void CTaskSimpleIKManager::AddIKChainTask(CTaskSimpleIKChain* task, int32 slot) } // 0x639350 -CTask* CTaskSimpleIKManager::Clone() { - return plugin::CallMethodAndReturn(this); +CTask* CTaskSimpleIKManager::Clone() const { + return plugin::CallMethodAndReturn(this); } // 0x6338A0 diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKManager.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKManager.h index 0d2ff15241..7c9fdafb33 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKManager.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKManager.h @@ -21,9 +21,9 @@ class CTaskSimpleIKManager : public CTaskSimple { CTaskSimpleIKManager(); ~CTaskSimpleIKManager() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void AddIKChainTask(CTaskSimpleIKChain* task, int32 slot); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKPointArm.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKPointArm.cpp index 7f3c1c64ae..ee9d230799 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKPointArm.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKPointArm.cpp @@ -23,7 +23,7 @@ CTaskSimpleIKPointArm::CTaskSimpleIKPointArm(const char* purpose, int32 hand, CE } // 0x634250 -CTask* CTaskSimpleIKPointArm::Clone() { +CTask* CTaskSimpleIKPointArm::Clone() const { auto* task = new CTaskSimpleIKPointArm("", m_Hand, m_pEntity, m_nOffsetBoneTag, m_vecOffsetPos, m_fSpeed, m_nBlendTime); if (m_pIKChain) { task->m_fBlend = m_fBlend; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKPointArm.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKPointArm.h index ccf8001eb9..3454528a88 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleIKPointArm.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleIKPointArm.h @@ -17,8 +17,8 @@ class CTaskSimpleIKPointArm : public CTaskSimpleIKChain { CTaskSimpleIKPointArm(const char* purpose, int32 hand, CEntity* targetEntity, ePedBones bone, CVector offsetPos, float speed, int32 blendTime); ~CTaskSimpleIKPointArm() override = default; // 0x634240 - eTaskType GetTaskType() override { return Type; } // Weird.. L_ARM never used? - CTask* Clone() override; + eTaskType GetTaskType() const override { return Type; } // Weird.. L_ARM never used? + CTask* Clone() const override; bool CreateIKChain(CPed* ped) override; void UpdatePointArmInfo(const char* purpose, CEntity* entity, ePedBones bone, CVector posn, float speed, int32 timeOffset); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleInAir.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleInAir.h index f704152e2a..a9beb2e575 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleInAir.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleInAir.h @@ -41,10 +41,10 @@ class CTaskSimpleInAir : public CTaskSimple { CTaskSimpleInAir(bool bUsingJumpGlide, bool bUsingFallGlide, bool bUsingClimbJump); ~CTaskSimpleInAir() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleInAir(m_bUsingJumpGlide, m_bUsingFallGlide, m_bUsingClimbJump); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleInAir(m_bUsingJumpGlide, m_bUsingFallGlide, m_bUsingClimbJump); } bool ProcessPed(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed_Reversed(CPed* ped); bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleJetPack.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleJetPack.cpp index 760bf76368..786c17c2cd 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleJetPack.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleJetPack.cpp @@ -65,11 +65,11 @@ bool CTaskSimpleJetPack::MakeAbortable_Reversed(CPed* ped, eAbortPriority priori } // 0x67C690 -CTask* CTaskSimpleJetPack::Clone() { +CTask* CTaskSimpleJetPack::Clone() const { return Clone_Reversed(); } -CTask* CTaskSimpleJetPack::Clone_Reversed() { - return plugin::CallMethodAndReturn(this); +CTask* CTaskSimpleJetPack::Clone_Reversed() const { + return plugin::CallMethodAndReturn(this); } // 0x6801F0 diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleJetPack.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleJetPack.h index 1b4b1e1ce6..e903bf0d06 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleJetPack.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleJetPack.h @@ -72,9 +72,9 @@ class CTaskSimpleJetPack : public CTaskSimple { CTaskSimpleJetPack* Constructor(const CVector* pVecTargetPos, float fCruiseHeight, int32 nHoverTime, CEntity* entity); ~CTaskSimpleJetPack() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; - bool MakeAbortable(class CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; + bool MakeAbortable(class CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void RenderJetPack(CPed* ped); @@ -87,7 +87,7 @@ class CTaskSimpleJetPack : public CTaskSimple { static void InjectHooks(); bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; }; VALIDATE_SIZE(CTaskSimpleJetPack, 0x70); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleJump.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleJump.cpp index ce5929e85b..1e15c85978 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleJump.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleJump.cpp @@ -4,8 +4,7 @@ #include "TaskSimpleClimb.h" #include "Shadows.h" -void CTaskSimpleJump::InjectHooks() -{ +void CTaskSimpleJump::InjectHooks() { RH_ScopedClass(CTaskSimpleJump); RH_ScopedCategory("Tasks/TaskTypes"); RH_ScopedInstall(Constructor, 0x679AA0); @@ -13,21 +12,19 @@ void CTaskSimpleJump::InjectHooks() RH_ScopedInstall(Launch, 0x679B80); RH_ScopedInstall(StartLaunchAnim, 0x67D7A0); RH_ScopedInstall(JumpAnimFinishCB, 0x67A020); - //VTABLE + // VTABLE RH_ScopedVirtualInstall(Clone, 0x67C510); RH_ScopedVirtualInstall(MakeAbortable, 0x679B60); RH_ScopedVirtualInstall(ProcessPed, 0x680C60); } -CTaskSimpleJump* CTaskSimpleJump::Constructor(bool bCanClimb) -{ +CTaskSimpleJump* CTaskSimpleJump::Constructor(bool bCanClimb) { this->CTaskSimpleJump::CTaskSimpleJump(bCanClimb); return this; } // 0x679AA0 -CTaskSimpleJump::CTaskSimpleJump(bool bCanClimb) -{ +CTaskSimpleJump::CTaskSimpleJump(bool bCanClimb) { m_bCanClimb = bCanClimb; m_pClimbEntity = nullptr; m_bIsFinished = false; @@ -39,8 +36,7 @@ CTaskSimpleJump::CTaskSimpleJump(bool bCanClimb) } // 0x679AF0 -CTaskSimpleJump::~CTaskSimpleJump() -{ +CTaskSimpleJump::~CTaskSimpleJump() { if (m_pAnim) m_pAnim->SetFinishCallback(CDefaultAnimCallback::DefaultAnimCB, nullptr); @@ -48,34 +44,28 @@ CTaskSimpleJump::~CTaskSimpleJump() } // 0x67C510 -CTask* CTaskSimpleJump::Clone() -{ +CTask* CTaskSimpleJump::Clone() const { return Clone_Reversed(); } // 0x679B60 -bool CTaskSimpleJump::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) -{ +bool CTaskSimpleJump::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) { return MakeAbortable_Reversed(ped, priority, event); } // 0x680C60; -bool CTaskSimpleJump::ProcessPed(CPed* ped) -{ +bool CTaskSimpleJump::ProcessPed(CPed* ped) { return ProcessPed_Reversed(ped); } -CTask* CTaskSimpleJump::Clone_Reversed() -{ +CTask* CTaskSimpleJump::Clone_Reversed() const { auto newTask = new CTaskSimpleJump(m_bCanClimb); newTask->m_bHighJump = this->m_bHighJump; return newTask; } -bool CTaskSimpleJump::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event) -{ - if (m_pAnim) - { +bool CTaskSimpleJump::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event) { + if (m_pAnim) { m_pAnim->m_nFlags |= ANIMATION_FREEZE_LAST_FRAME; m_pAnim->m_fBlendDelta = -4.0F; } @@ -83,14 +73,10 @@ bool CTaskSimpleJump::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, return priority == ABORT_PRIORITY_IMMEDIATE; } -bool CTaskSimpleJump::ProcessPed_Reversed(CPed* ped) -{ - if (!m_bIsFinished) - { - if (!m_pAnim) - { - if (!StartLaunchAnim(ped)) - { +bool CTaskSimpleJump::ProcessPed_Reversed(CPed* ped) { + if (!m_bIsFinished) { + if (!m_pAnim) { + if (!StartLaunchAnim(ped)) { m_bLaunchAnimStarted = false; return true; } @@ -105,12 +91,9 @@ bool CTaskSimpleJump::ProcessPed_Reversed(CPed* ped) if (!m_pClimbEntity) CheckIfJumpBlocked(ped); - if (!m_bIsJumpBlocked) - { + if (!m_bIsJumpBlocked) { Launch(ped); - } - else if (ped->IsPlayer()) - { + } else if (ped->IsPlayer()) { CVector empty{}; CEventSoundQuiet event(ped, 45.0F, -1, empty); GetEventGlobalGroup()->Add(&event, false); @@ -120,8 +103,7 @@ bool CTaskSimpleJump::ProcessPed_Reversed(CPed* ped) } // 0x67D590 -bool CTaskSimpleJump::CheckIfJumpBlocked(CPed* ped) -{ +bool CTaskSimpleJump::CheckIfJumpBlocked(CPed* ped) { if (ped->m_bIsStuck || ped->GetEventHandler().GetCurrentEventType() == EVENT_STUCK_IN_AIR) return false; @@ -149,15 +131,13 @@ bool CTaskSimpleJump::CheckIfJumpBlocked(CPed* ped) } // 0x679B80 -void CTaskSimpleJump::Launch(CPed* ped) -{ +void CTaskSimpleJump::Launch(CPed* ped) { float fHorizontalJumpSpeed = 0.1F; auto pSprintAnim = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_SPRINT); if (pSprintAnim) fHorizontalJumpSpeed = lerp(0.17F, 0.22F, pSprintAnim->m_fBlendAmount); - else - { + else { auto pRunAnim = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_RUN); if (pRunAnim) fHorizontalJumpSpeed = lerp(0.1F, 0.17F, pRunAnim->m_fBlendAmount); @@ -165,8 +145,7 @@ void CTaskSimpleJump::Launch(CPed* ped) float fJumpForce = (ped->IsPlayer() || m_bHighJump) ? 8.5F : 4.5F; - if (ped->m_pPlayerData) - { + if (ped->m_pPlayerData) { float modifier = CStats::GetFatAndMuscleModifier(STAT_MOD_2); fHorizontalJumpSpeed *= modifier; fJumpForce *= modifier; @@ -177,18 +156,14 @@ void CTaskSimpleJump::Launch(CPed* ped) ped->ApplyMoveForce(0.0F, 0.0F, fJumpForce); - if (m_bClimbJump) - { + if (m_bClimbJump) { ped->m_vecMoveSpeed.x = 0.0F; ped->m_vecMoveSpeed.y = 0.0F; - } - else if (!m_pClimbEntity && (ped->m_standingOnEntity || ped->m_vecMoveSpeed.SquaredMagnitude2D() < fHorizontalJumpSpeed)) - { + } else if (!m_pClimbEntity && (ped->m_standingOnEntity || ped->m_vecMoveSpeed.SquaredMagnitude2D() < fHorizontalJumpSpeed)) { ped->m_vecMoveSpeed.x = -fHorizontalJumpSpeed * sin(ped->m_fCurrentRotation); ped->m_vecMoveSpeed.y = fHorizontalJumpSpeed * cos(ped->m_fCurrentRotation); - if (ped->m_standingOnEntity) - { + if (ped->m_standingOnEntity) { ped->m_vecMoveSpeed.x += ped->m_standingOnEntity->AsPhysical()->m_vecMoveSpeed.x; ped->m_vecMoveSpeed.y += ped->m_standingOnEntity->AsPhysical()->m_vecMoveSpeed.y; } @@ -197,54 +172,48 @@ void CTaskSimpleJump::Launch(CPed* ped) ped->bIsStanding = false; ped->bIsInTheAir = true; - if (!m_pClimbEntity) - { - if (m_bClimbJump) - { + if (!m_pClimbEntity) { + if (m_bClimbJump) { auto anim = CAnimManager::BlendAnimation(ped->m_pRwClump, ANIM_GROUP_DEFAULT, ANIM_ID_CLIMB_JUMP, 8.0F); anim->m_nFlags |= ANIMATION_UNLOCK_LAST_FRAME; - } - else + } else CAnimManager::BlendAnimation(ped->m_pRwClump, ANIM_GROUP_DEFAULT, ANIM_ID_JUMP_GLIDE, 8.0F); } - if (ped->bDoBloodyFootprints && CLocalisation::Blood()) - { + if (ped->bDoBloodyFootprints && CLocalisation::Blood()) { auto hier = GetAnimHierarchyFromSkinClump(ped->m_pRwClump); CVector v; RwV3dTransformPoints(&v, &v, 1, &RpHAnimHierarchyGetMatrixArray(hier)[RpHAnimIDGetIndex(hier, ped->m_apBones[PED_NODE_LEFT_FOOT]->m_nNodeId)]); CVector v1 = ped->GetForward() * 0.2F; v += v1 + CVector(0.0F, 0.0F, -0.1F); - CShadows::AddPermanentShadow(SHADOW_DEFAULT, gpBloodPoolTex, &v, v1.x * 0.26F, v1.y * 0.26F, ped->GetForward().x * 0.14F, ped->GetForward().y * 0.14F, 255, 255, 0, 0, 4.0F, 3000, 1.0F); + CShadows::AddPermanentShadow(SHADOW_DEFAULT, gpBloodPoolTex, &v, v1.x * 0.26F, v1.y * 0.26F, ped->GetForward().x * 0.14F, ped->GetForward().y * 0.14F, 255, 255, 0, 0, 4.0F, + 3000, 1.0F); v.Set(0.0F, 0.0F, 0.0F); RwV3dTransformPoints(&v, &v, 1, &RpHAnimHierarchyGetMatrixArray(hier)[RpHAnimIDGetIndex(hier, ped->m_apBones[PED_NODE_RIGHT_FOOT]->m_nNodeId)]); v += v1 + CVector(0.0F, 0.0F, -0.1F); - CShadows::AddPermanentShadow(SHADOW_DEFAULT, gpBloodPoolTex, &v, v1.x * 0.26F, v1.y * 0.26F, ped->GetForward().x * 0.14F, ped->GetForward().y * 0.14F, 255, 255, 0, 0, 4.0F, 3000, 1.0F); + CShadows::AddPermanentShadow(SHADOW_DEFAULT, gpBloodPoolTex, &v, v1.x * 0.26F, v1.y * 0.26F, ped->GetForward().x * 0.14F, ped->GetForward().y * 0.14F, 255, 255, 0, 0, 4.0F, + 3000, 1.0F); - if (ped->m_nDeathTimeMS <= 40) - { + if (ped->m_nDeathTimeMS <= 40) { ped->m_nDeathTimeMS = 0; ped->bDoBloodyFootprints = false; - } - else + } else ped->m_nDeathTimeMS -= 40; } } // 0x67D7A0 -bool CTaskSimpleJump::StartLaunchAnim(CPed* ped) -{ +bool CTaskSimpleJump::StartLaunchAnim(CPed* ped) { m_pAnim = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_JUMP_LAUNCH); if (!m_pAnim) m_pAnim = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_JUMP_LAUNCH_R); if (m_pAnim) return false; - if (g_surfaceInfos.IsSteepSlope(ped->m_nContactSurface) && DotProduct(ped->GetForward(), ped->field_578) < 0.0F - || ped->IsPlayer() && !CGameLogic::IsPlayerAllowedToGoInThisDirection(ped, ped->GetForward(), 5.0F)) - { + if (g_surfaceInfos.IsSteepSlope(ped->m_nContactSurface) && DotProduct(ped->GetForward(), ped->field_578) < 0.0F || + ped->IsPlayer() && !CGameLogic::IsPlayerAllowedToGoInThisDirection(ped, ped->GetForward(), 5.0F)) { ped->m_pedIK.bSlopePitch = true; return false; } @@ -256,8 +225,7 @@ bool CTaskSimpleJump::StartLaunchAnim(CPed* ped) moveAnim = RpAnimBlendClumpGetAssociation(ped->m_pRwClump, ANIM_ID_WALK); float fMoveAnimBlendAmount = 0.0F; - if (moveAnim && moveAnim->m_fBlendAmount > 0.3F) - { + if (moveAnim && moveAnim->m_fBlendAmount > 0.3F) { fMoveAnimBlendAmount = moveAnim->m_fCurrentTime / moveAnim->m_pHierarchy->m_fTotalTime + 0.367F; if (fMoveAnimBlendAmount > 1.0F) fMoveAnimBlendAmount -= 1.0F; @@ -274,8 +242,7 @@ bool CTaskSimpleJump::StartLaunchAnim(CPed* ped) } // 0x67A020 -void CTaskSimpleJump::JumpAnimFinishCB(CAnimBlendAssociation* anim, void* data) -{ +void CTaskSimpleJump::JumpAnimFinishCB(CAnimBlendAssociation* anim, void* data) { auto task = reinterpret_cast(data); task->m_bIsFinished = true; task->m_pAnim = nullptr; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleJump.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleJump.h index 8f3864421d..24e38afa5c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleJump.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleJump.h @@ -22,9 +22,9 @@ class CTaskSimpleJump : public CTaskSimple { explicit CTaskSimpleJump(bool bCanClimb); ~CTaskSimpleJump() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool CheckIfJumpBlocked(CPed* ped); @@ -34,7 +34,7 @@ class CTaskSimpleJump : public CTaskSimple { static void InjectHooks(); CTaskSimpleJump* Constructor(bool bCanClimb); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); bool ProcessPed_Reversed(CPed* ped); }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleLand.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleLand.h index 17d40fce36..84c622c67a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleLand.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleLand.h @@ -21,10 +21,10 @@ class CTaskSimpleLand : public CTaskSimple { explicit CTaskSimpleLand(AnimationId nAnimId); ~CTaskSimpleLand() override; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleLand(m_nAnimId); } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleLand(m_nAnimId); } bool ProcessPed(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool LeftFootLanded(); bool RightFootLanded(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleLookAbout.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleLookAbout.h index 63b0a3dfdd..862f5473a1 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleLookAbout.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleLookAbout.h @@ -15,7 +15,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleLookAbout : public CTaskSimpleRunTimedAnim CTaskSimpleLookAbout(uint32 durationMs); CTaskSimpleLookAbout(const CTaskSimpleLookAbout& o); - CTask* Clone() override { return new CTaskSimpleLookAbout{*this}; } + CTask* Clone() const override { return new CTaskSimpleLookAbout{*this}; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks // 0x48E0A0 diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleNone.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleNone.h index d3448192a9..b89b5b115c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleNone.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleNone.h @@ -9,9 +9,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleNone : public CTaskSimple { CTaskSimpleNone() = default; ~CTaskSimpleNone() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleNone(); } - bool MakeAbortable(class CPed* ped, eAbortPriority priority, const CEvent* event) override { return true; } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleNone(); } + bool MakeAbortable(class CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } bool ProcessPed(CPed* ped) override { return true; } }; VALIDATE_SIZE(CTaskSimpleNone, 0x8); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePause.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimplePause.cpp index c0e9837b57..396c8fddee 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePause.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePause.cpp @@ -2,8 +2,7 @@ #include "TaskSimplePause.h" -void CTaskSimplePause::InjectHooks() -{ +void CTaskSimplePause::InjectHooks() { RH_ScopedClass(CTaskSimplePause); RH_ScopedCategory("Tasks/TaskTypes"); RH_ScopedInstall(Constructor, 0x48E750); @@ -12,24 +11,20 @@ void CTaskSimplePause::InjectHooks() } // 0x48E750 -CTaskSimplePause::CTaskSimplePause(int32 time) -{ +CTaskSimplePause::CTaskSimplePause(int32 time) { m_nTime = time; } -CTaskSimplePause* CTaskSimplePause::Constructor(int32 time) -{ +CTaskSimplePause* CTaskSimplePause::Constructor(int32 time) { this->CTaskSimplePause::CTaskSimplePause(time); return this; } -CTask* CTaskSimplePause::Clone() -{ - return plugin::CallMethodAndReturn(this); +CTask* CTaskSimplePause::Clone() const { + return plugin::CallMethodAndReturn(this); } -bool CTaskSimplePause::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event) -{ +bool CTaskSimplePause::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event) { m_timer.m_nStartTime = CTimer::GetTimeInMS(); m_timer.m_nInterval = -1; m_timer.m_bStarted = true; @@ -37,13 +32,11 @@ bool CTaskSimplePause::MakeAbortable_Reversed(CPed* ped, eAbortPriority priority } // 0x48E810 -bool CTaskSimplePause::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) -{ +bool CTaskSimplePause::MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) { return CTaskSimplePause::MakeAbortable_Reversed(ped, priority, event); } -bool CTaskSimplePause::ProcessPed_Reversed(CPed* ped) -{ +bool CTaskSimplePause::ProcessPed_Reversed(CPed* ped) { if (!m_timer.m_bStarted && !m_timer.Start(m_nTime)) { return false; } @@ -52,7 +45,6 @@ bool CTaskSimplePause::ProcessPed_Reversed(CPed* ped) } // 0x48E830 -bool CTaskSimplePause::ProcessPed(CPed* ped) -{ +bool CTaskSimplePause::ProcessPed(CPed* ped) { return CTaskSimplePause::ProcessPed_Reversed(ped); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePause.h b/source/game_sa/Tasks/TaskTypes/TaskSimplePause.h index 4234250073..93309587e1 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePause.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePause.h @@ -14,11 +14,11 @@ class NOTSA_EXPORT_VTABLE CTaskSimplePause : public CTaskSimple { explicit CTaskSimplePause(int32 time = 0); ~CTaskSimplePause() = default; - CTask* Clone() override; - eTaskType GetTaskType() override { + CTask* Clone() const override; + eTaskType GetTaskType() const override { return TASK_SIMPLE_PAUSE; }; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool MakeAbortable_Reversed(CPed* ped, eAbortPriority priority, const CEvent* event); bool ProcessPed(CPed* ped) override; bool ProcessPed_Reversed(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpBike.h b/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpBike.h index 6fec6935da..21fa9b756d 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpBike.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpBike.h @@ -31,9 +31,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimplePickUpBike : public CTaskSimple { void StartAnim(CPed const* ped); - CTask* Clone() override { return new CTaskSimplePickUpBike{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimplePickUpBike{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed * ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpEntity.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpEntity.cpp index 4b85dd7bd8..351a52ef60 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpEntity.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpEntity.cpp @@ -3,52 +3,49 @@ #include "TaskSimplePickUpEntity.h" void CTaskSimplePickUpEntity::InjectHooks() { - RH_ScopedClass(CTaskSimplePickUpEntity); + RH_ScopedVirtualClass(CTaskSimplePickUpEntity, 0x870b50, 9); RH_ScopedCategory("Tasks/TaskTypes"); + RH_ScopedOverloadedInstall(Constructor, "1", 0x691870, CTaskSimplePickUpEntity*(CTaskSimplePickUpEntity::*)(CEntity*, CVector*, uint8, uint8, CAnimBlock*, CAnimBlendHierarchy*, eAnimationFlags, float)); RH_ScopedOverloadedInstall(Constructor, "2", 0x6917B0, CTaskSimplePickUpEntity * (CTaskSimplePickUpEntity::*)(CEntity*, CVector*, uint8, uint8, AnimationId, AssocGroupId, float)); - RH_ScopedVirtualInstall(Clone, 0x692A90); - RH_ScopedVirtualInstall(GetId, 0x691810); + + RH_ScopedVMTInstall(Clone, 0x692A90); + RH_ScopedVMTInstall(GetTaskType, 0x691810); } -CTaskSimplePickUpEntity::CTaskSimplePickUpEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* pAnimBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fMovePedUntilAnimProgress) +CTaskSimplePickUpEntity::CTaskSimplePickUpEntity( + CEntity* entityToHold, + const CVector* posn, + uint8 boneFrameId, + uint8 boneFlags, + CAnimBlock* pAnimBlock, + CAnimBlendHierarchy* animHierarchy, + eAnimationFlags animFlags, + float fMovePedUntilAnimProgress +) : CTaskSimpleHoldEntity(entityToHold, posn, boneFrameId, boneFlags, pAnimBlock, animHierarchy, animFlags) { m_fMovePedUntilAnimProgress = fMovePedUntilAnimProgress; } -CTaskSimplePickUpEntity::CTaskSimplePickUpEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, float fMovePedUntilAnimProgress) +CTaskSimplePickUpEntity::CTaskSimplePickUpEntity( + CEntity* entityToHold, + const CVector* posn, + uint8 boneFrameId, + uint8 boneFlags, + AnimationId animId, + AssocGroupId groupId, + float fMovePedUntilAnimProgress +) : CTaskSimpleHoldEntity(entityToHold, posn, boneFrameId, boneFlags, animId, groupId, false) { m_fMovePedUntilAnimProgress = fMovePedUntilAnimProgress; } -// 0x691870 -CTaskSimplePickUpEntity* CTaskSimplePickUpEntity::Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* pAnimBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fMovePedUntilAnimProgress) { - this->CTaskSimplePickUpEntity::CTaskSimplePickUpEntity(entityToHold, posn, boneFrameId, boneFlags, pAnimBlock, animHierarchy, animFlags, fMovePedUntilAnimProgress); - return this; -} - -// 0x6917B0 -CTaskSimplePickUpEntity* CTaskSimplePickUpEntity::Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, float fMovePedUntilAnimProgress){ - this->CTaskSimplePickUpEntity::CTaskSimplePickUpEntity(entityToHold, posn, boneFrameId, boneFlags, animId, groupId, fMovePedUntilAnimProgress); - return this; -} - // 0x692A90 -CTask* CTaskSimplePickUpEntity::Clone() { - return CTaskSimplePickUpEntity::Clone_Reversed(); -} - -// 0x691810 -eTaskType CTaskSimplePickUpEntity::GetTaskType() { - return CTaskSimplePickUpEntity::GetId_Reversed(); -} - -// reversed virtual functions -CTask* CTaskSimplePickUpEntity::Clone_Reversed() { - if (m_pAnimBlendHierarchy) { - return new CTaskSimplePickUpEntity( +CTask* CTaskSimplePickUpEntity::Clone() const { + return m_pAnimBlendHierarchy + ? new CTaskSimplePickUpEntity( m_pEntityToHold, &m_vecPosition, m_nBoneFrameId, @@ -57,15 +54,14 @@ CTask* CTaskSimplePickUpEntity::Clone_Reversed() { m_pAnimBlendHierarchy, static_cast(m_animFlags), m_fMovePedUntilAnimProgress + ) + : new CTaskSimplePickUpEntity( + m_pEntityToHold, + &m_vecPosition, + m_nBoneFrameId, + m_bBoneFlags, + m_nAnimId, + m_nAnimGroupId, + m_fMovePedUntilAnimProgress ); - } - return new CTaskSimplePickUpEntity( - m_pEntityToHold, - &m_vecPosition, - m_nBoneFrameId, - m_bBoneFlags, - m_nAnimId, - m_nAnimGroupId, - m_fMovePedUntilAnimProgress - ); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpEntity.h b/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpEntity.h index fbd102b2e2..64f00bfd5e 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpEntity.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePickUpEntity.h @@ -2,31 +2,39 @@ #include "TaskSimpleHoldEntity.h" -class CTaskSimplePickUpEntity : public CTaskSimpleHoldEntity { +class NOTSA_EXPORT_VTABLE CTaskSimplePickUpEntity : public CTaskSimpleHoldEntity { public: - float m_fMovePedUntilAnimProgress; // value can be 0.0 to 1.0. Ped Will stop moving when the current `m_fCurrentTime` - // of `m_pAnimBlendAssociation` is equal or greater than this. + float m_fMovePedUntilAnimProgress; //< value can be 0.0 to 1.0. Ped Will stop moving when the current `m_fCurrentTime` + //< of `m_pAnimBlendAssociation` is equal or greater than this. CVector m_vecPickuposn; public: static constexpr auto Type = TASK_SIMPLE_PICKUP_ENTITY; - CTaskSimplePickUpEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fMovePedUntilAnimProgress); - CTaskSimplePickUpEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, float fMovePedUntilAnimProgress); + CTaskSimplePickUpEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fMovePedUntilAnimProgress); + CTaskSimplePickUpEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, float fMovePedUntilAnimProgress); ~CTaskSimplePickUpEntity() override = default; - CTask* Clone() override; - eTaskType GetTaskType() override; + CTask* Clone() const override; + eTaskType GetTaskType() const override { return TASK_SIMPLE_PICKUP_ENTITY; } private: friend void InjectHooksMain(); static void InjectHooks(); - CTaskSimplePickUpEntity* Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* animBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fMovePedUntilAnimProgress); - CTaskSimplePickUpEntity* Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, float fMovePedUntilAnimProgress); +private: // Wrappers for hooks + // 0x691870 + CTaskSimplePickUpEntity* Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* pAnimBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fMovePedUntilAnimProgress) { + this->CTaskSimplePickUpEntity::CTaskSimplePickUpEntity(entityToHold, posn, boneFrameId, boneFlags, pAnimBlock, animHierarchy, animFlags, fMovePedUntilAnimProgress); + return this; + } + + // 0x6917B0 + CTaskSimplePickUpEntity* Constructor(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, float fMovePedUntilAnimProgress){ + this->CTaskSimplePickUpEntity::CTaskSimplePickUpEntity(entityToHold, posn, boneFrameId, boneFlags, animId, groupId, fMovePedUntilAnimProgress); + return this; + } - CTask* Clone_Reversed(); - eTaskType GetId_Reversed() { return TASK_SIMPLE_PICKUP_ENTITY; }; }; VALIDATE_SIZE(CTaskSimplePickUpEntity, 0x4C); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayHandSignalAnim.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayHandSignalAnim.cpp index c1bb908749..2c4a983850 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayHandSignalAnim.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayHandSignalAnim.cpp @@ -56,7 +56,7 @@ void CTaskSimplePlayHandSignalAnim::StartAnim(CPed* ped) { const AnimationId animId = static_cast(CGeneral::GetRandomNumberInRange((int32)ANIM_ID_GSIGN1, (int32)ANIM_ID_GSIGN5 + 1)); // Pointing / weapon logic - if (ped->GetEntityThatThisPedIsHolding() || g_ikChainMan.IsArmPointing(0, ped) || ped->GetActiveWeapon().m_nType != eWeaponType::WEAPON_UNARMED) { + if (ped->GetEntityThatThisPedIsHolding() || g_ikChainMan.IsArmPointing(0, ped) || ped->GetActiveWeapon().m_Type != eWeaponType::WEAPON_UNARMED) { m_pAnim = CAnimManager::BlendAnimation(ped->m_pRwClump, ANIM_GROUP_HANDSIGNALL, animId, m_fBlendFactor); m_pAnim->SetFinishCallback(CTaskSimpleAnim::FinishRunAnimCB, this); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayHandSignalAnim.h b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayHandSignalAnim.h index fe494f3a29..fd0cb2de2f 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayHandSignalAnim.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayHandSignalAnim.h @@ -17,9 +17,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimplePlayHandSignalAnim : public CTaskSimpleAnim CTaskSimplePlayHandSignalAnim(AnimationId animationId, float fBlendFactor, bool bFatHands, bool bHoldLastFrame); ~CTaskSimplePlayHandSignalAnim() override; - eTaskType GetTaskType() override { return Type; } // 0x61AEA0; - CTask* Clone() override { return new CTaskSimplePlayHandSignalAnim(m_nAnimationBlockIndex, m_fBlendFactor, m_bUseFatHands, m_bHoldLastFrame); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return CTaskSimpleAnim::MakeAbortable(ped, priority, event); } + eTaskType GetTaskType() const override { return Type; } // 0x61AEA0; + CTask* Clone() const override { return new CTaskSimplePlayHandSignalAnim(m_nAnimationBlockIndex, m_fBlendFactor, m_bUseFatHands, m_bHoldLastFrame); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return CTaskSimpleAnim::MakeAbortable(ped, priority, event); } bool ProcessPed(CPed* ped) override; void StartAnim(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFire.h b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFire.h index 9c359b6ec3..ac8a8aa233 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFire.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFire.h @@ -22,9 +22,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimplePlayerOnFire : public CTaskSimple { CTaskSimplePlayerOnFire(const CTaskSimplePlayerOnFire&) : CTaskSimplePlayerOnFire{} {} // NOTSA ~CTaskSimplePlayerOnFire() = default; - CTask* Clone() override { return new CTaskSimplePlayerOnFire{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return false; } + CTask* Clone() const override { return new CTaskSimplePlayerOnFire{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks // 0x633560 diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFoot.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFoot.cpp index b98a814d83..f36da50b98 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFoot.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFoot.cpp @@ -102,7 +102,7 @@ bool CTaskSimplePlayerOnFoot::ProcessPed(CPed* ped) { if (player->GetPadFromPlayer()) { CPedIntelligence* intelligence = player->GetIntelligence(); bool bPedMoving = player->m_nMoveState >= PEDMOVE_WALK; - if (player->GetActiveWeapon().m_nType == WEAPON_CHAINSAW) { + if (player->GetActiveWeapon().m_Type == WEAPON_CHAINSAW) { if (const auto fightingTask = intelligence->GetTaskFighting()) { if (fightingTask->m_nCurrentMove == FIGHT_ATTACK_FIGHTIDLE) { bPedMoving = true; @@ -137,7 +137,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { CTaskManager* taskManager = &player->GetTaskManager(); CPad* pad = player->GetPadFromPlayer(); - eWeaponType weaponType = player->GetActiveWeapon().m_nType; + eWeaponType weaponType = player->GetActiveWeapon().m_Type; eWeaponSkill weaponSkill = player->GetWeaponSkill(); CWeaponInfo* weaponInfo = CWeaponInfo::GetWeaponInfo(weaponType, weaponSkill); @@ -190,7 +190,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { } if (!TheCamera.Using1stPersonWeaponMode()) { - weaponType = player->GetActiveWeapon().m_nType; + weaponType = player->GetActiveWeapon().m_Type; if (weaponType == WEAPON_RLAUNCHER || weaponType == WEAPON_RLAUNCHER_HS || weaponType == WEAPON_SNIPERRIFLE || weaponType == WEAPON_CAMERA) { CTaskSimpleUseGun* simpleTaskUseGun = intelligence->GetTaskUseGun(); if (simpleTaskUseGun) { @@ -271,7 +271,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { auto* pTaskSimpleStealthKill = new CTaskSimpleStealthKill(true, targetEntity, weaponInfo->m_eAnimGroup); taskManager->SetTask(pTaskSimpleStealthKill, TASK_PRIMARY_PRIMARY, false); - eWeaponType activeWeaponType = player->GetActiveWeapon().m_nType; + eWeaponType activeWeaponType = player->GetActiveWeapon().m_Type; CPedDamageResponseCalculator damageCalculator(player, 0.0f, activeWeaponType, PED_PIECE_TORSO, false); CEventDamage eventDamage(player, CTimer::GetTimeInMS(), activeWeaponType, PED_PIECE_TORSO, 0, false, targetEntity->bInVehicle); if (eventDamage.AffectsPed(targetEntity)) { @@ -288,7 +288,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { break; } case 4: { - if (!CWeaponInfo::GetWeaponInfo(player->GetActiveWeapon().m_nType, eWeaponSkill::STD)->flags.bHeavy) { + if (!CWeaponInfo::GetWeaponInfo(player->GetActiveWeapon().m_Type, eWeaponSkill::STD)->flags.bHeavy) { fightCommand = 12; } else { fightCommand = 11; @@ -300,7 +300,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { break; } default: { - if (pad->GetMeleeAttack(false) && player->GetActiveWeapon().m_nType == WEAPON_CHAINSAW && taskManager->GetTaskSecondary(TASK_SECONDARY_ATTACK)) { + if (pad->GetMeleeAttack(false) && player->GetActiveWeapon().m_Type == WEAPON_CHAINSAW && taskManager->GetTaskSecondary(TASK_SECONDARY_ATTACK)) { fightCommand = 11; } else { handleFighting = true; @@ -325,7 +325,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { if (weaponInfo->m_nWeaponFire == WEAPON_FIRE_USE) { if (pad->WeaponJustDown(nullptr)) { uint8 activeWeaponSlot = player->m_nActiveWeaponSlot; - weaponType = player->m_aWeapons[activeWeaponSlot].m_nType; + weaponType = player->m_aWeapons[activeWeaponSlot].m_Type; CWeapon* playerWeapon = &player->m_aWeapons[activeWeaponSlot]; if (weaponType == WEAPON_DETONATOR) { playerWeapon->Fire(player, &player->GetPosition(), &player->GetPosition(), nullptr, nullptr, nullptr); @@ -351,7 +351,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { int32 fightCommand = 2; if (CTaskSimpleUseGun::RequirePistolWhip(player, targetedObject)) { fightCommand = 5; - } else if (player->GetActiveWeapon().m_nState == 2) { + } else if (player->GetActiveWeapon().m_State == 2) { if (!pad->GetTarget() && !targetedObject && !playerData->m_bFreeAiming) { break; } @@ -373,7 +373,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { player->m_pPlayerData->m_fAttackButtonCounter = 0; } if (!pad->GetTarget()) { - if (player->GetActiveWeapon().m_nType == WEAPON_EXTINGUISHER) { + if (player->GetActiveWeapon().m_Type == WEAPON_EXTINGUISHER) { playerData->m_fLookPitch = -CWeapon::ms_fExtinguisherAimAngle; } else { playerData->m_fLookPitch = 0.0f; @@ -384,9 +384,9 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { case WEAPON_FIRE_PROJECTILE: { uint8 activeWeaponSlot = player->m_nActiveWeaponSlot; CWeapon* activeWeapon = &player->m_aWeapons[activeWeaponSlot]; - if (activeWeapon->m_nType == WEAPON_RLAUNCHER || activeWeapon->m_nType == WEAPON_RLAUNCHER_HS) { + if (activeWeapon->m_Type == WEAPON_RLAUNCHER || activeWeapon->m_Type == WEAPON_RLAUNCHER_HS) { int32 fightCommand = 2; - if (activeWeapon->m_nState == WEAPONSTATE_RELOADING) { + if (activeWeapon->m_State == WEAPONSTATE_RELOADING) { fightCommand = 1; } CTask* secondaryTask = taskManager->GetTaskSecondary(TASK_SECONDARY_ATTACK); @@ -422,7 +422,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { case WEAPON_FIRE_CAMERA: { uint8 activeWeaponSlot = player->m_nActiveWeaponSlot; CWeapon* activeWeapon = &player->m_aWeapons[activeWeaponSlot]; - if (CCamera::GetActiveCamera().m_nMode == MODE_CAMERA && CTimer::GetTimeInMS() > activeWeapon->m_nTimeForNextShot) { + if (CCamera::GetActiveCamera().m_nMode == MODE_CAMERA && CTimer::GetTimeInMS() > activeWeapon->m_TimeForNextShotMs) { CVector firingPoint(0.0f, 0.0f, 0.6f); CVector outputFiringPoint = *player->m_matrix * firingPoint; activeWeapon->Fire(player, &outputFiringPoint, nullptr, nullptr, nullptr, nullptr); @@ -451,7 +451,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { CVector firingPoint(0.0f, 0.0f, 0.0f); CVector upVector(0.0f, 0.0f, 0.0f); - if (player->GetActiveWeapon().m_nState == WEAPONSTATE_RELOADING && weaponInfo->flags.bReload) { + if (player->GetActiveWeapon().m_State == WEAPONSTATE_RELOADING && weaponInfo->flags.bReload) { if (!intelligence->GetTaskUseGun()) { AssocGroupId animGroupId = weaponInfo->m_eAnimGroup; AnimationId crouchReloadAnimID = weaponInfo->flags.bReload ? ANIM_ID_RELOAD : ANIM_ID_WALK; @@ -565,7 +565,7 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { !CLocalisation::KickingWhenDown() && ((pedState = targetedEntity->m_nPedState, pedState == PEDSTATE_DIE) || pedState == PEDSTATE_DEAD)) || player->DoesTargetHaveToBeBroken(player->m_pTargetedObject, activeWeapon) || !player->bCanPointGunAtTarget && - (activeWeapon->m_nType, weaponSkill = player->GetWeaponSkill(), !(CWeaponInfo::GetWeaponInfo(activeWeapon->m_nType, weaponSkill)->flags.bCanAim))) { + (activeWeapon->m_Type, weaponSkill = player->GetWeaponSkill(), !(CWeaponInfo::GetWeaponInfo(activeWeapon->m_Type, weaponSkill)->flags.bCanAim))) { player->ClearWeaponTarget(); playerData->m_bFreeAiming = 1; } @@ -579,12 +579,12 @@ void CTaskSimplePlayerOnFoot::ProcessPlayerWeapon(CPlayerPed* player) { } } - if (CWeaponInfo::GetWeaponInfo(activeWeapon->m_nType, eWeaponSkill::STD)->m_nWeaponFire == WEAPON_FIRE_INSTANT_HIT) { + if (CWeaponInfo::GetWeaponInfo(activeWeapon->m_Type, eWeaponSkill::STD)->m_nWeaponFire == WEAPON_FIRE_INSTANT_HIT) { targetedEntity = (CPed*)player->m_pTargetedObject; if (targetedEntity && targetedEntity->IsPed() && intelligence->IsInSeeingRange(player->GetPosition())) { CTask* activePrimaryTask = intelligence->GetActivePrimaryTask(); if (!activePrimaryTask || activePrimaryTask->GetTaskType() != TASK_COMPLEX_REACT_TO_GUN_AIMED_AT) { - if (activeWeapon->m_nType != WEAPON_PISTOL_SILENCED) { + if (activeWeapon->m_Type != WEAPON_PISTOL_SILENCED) { player->Say(176); } CPedGroup* pedGroup = CPedGroups::GetPedsGroup(targetedEntity); @@ -974,7 +974,7 @@ int32 CTaskSimplePlayerOnFoot::PlayerControlZelda(CPlayerPed* player, bool bAvoi player->m_pPlayerData->m_fMoveBlendRatio = 0.0f; } } - if (!(CWeaponInfo::GetWeaponInfo(player->GetActiveWeapon().m_nType, eWeaponSkill::STD)->flags.bHeavy)) { + if (!(CWeaponInfo::GetWeaponInfo(player->GetActiveWeapon().m_Type, eWeaponSkill::STD)->flags.bHeavy)) { if (!player->m_standingOnEntity || !player->m_standingOnEntity->m_bIsStatic || player->m_standingOnEntity->m_bHasContacted) { if (!player->GetIntelligence()->GetTaskHold(false) || !((CTaskSimpleHoldEntity*)player->GetIntelligence()->GetTaskHold(false))->m_pAnimBlendAssociation) { CAnimBlendHierarchy* animHierarchy = nullptr; @@ -1008,7 +1008,7 @@ int32 CTaskSimplePlayerOnFoot::PlayerControlZelda(CPlayerPed* player, bool bAvoi player->GetIntelligence()->SetTaskDuckSecondary(0); } - if (!player->bIsInTheAir && !(CWeaponInfo::GetWeaponInfo(player->GetActiveWeapon().m_nType, eWeaponSkill::STD)->flags.bHeavy) && pad->JumpJustDown() && !pad->GetTarget() && + if (!player->bIsInTheAir && !(CWeaponInfo::GetWeaponInfo(player->GetActiveWeapon().m_Type, eWeaponSkill::STD)->flags.bHeavy) && pad->JumpJustDown() && !pad->GetTarget() && !player->m_pAttachedTo) { // Possibly inlined code? Like CCamera::IsInSniperMode() switch (CCamera::GetActiveCamera().m_nMode) { diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFoot.h b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFoot.h index 81fb3152b2..7887a1a416 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFoot.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePlayerOnFoot.h @@ -30,13 +30,13 @@ class NOTSA_EXPORT_VTABLE CTaskSimplePlayerOnFoot : public CTaskSimple { CTaskSimplePlayerOnFoot(); ~CTaskSimplePlayerOnFoot() override = default; - eTaskType GetTaskType() override { + eTaskType GetTaskType() const override { return Type; } // 0x6857C0 - CTask* Clone() override { + CTask* Clone() const override { return new CTaskSimplePlayerOnFoot(); } // 0x68AFF0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void ProcessPlayerWeapon(CPlayerPed* player); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePutDownEntity.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimplePutDownEntity.cpp index 5bc1929e84..b834f33cdd 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePutDownEntity.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePutDownEntity.cpp @@ -3,54 +3,41 @@ #include "TaskSimplePutDownEntity.h" void CTaskSimplePutDownEntity::InjectHooks() { - RH_ScopedClass(CTaskSimplePutDownEntity); + RH_ScopedVirtualClass(CTaskSimplePutDownEntity, 0x870b74, 9); RH_ScopedCategory("Tasks/TaskTypes"); + RH_ScopedInstall(Constructor, 0x691990); - RH_ScopedVirtualInstall(Clone, 0x692B70); - RH_ScopedVirtualInstall(GetId, 0x691900); + + RH_ScopedVMTInstall(Clone, 0x692B70); + RH_ScopedVMTInstall(GetTaskType, 0x691900); } CTaskSimplePutDownEntity::CTaskSimplePutDownEntity() : CTaskSimpleHoldEntity(nullptr, nullptr, PED_NODE_RIGHT_HAND, HOLD_ENTITY_FLAG_1, ANIM_ID_NO_ANIMATION_SET, ANIM_GROUP_DEFAULT, false) { m_fPutDownHeightZ = 0.6f; } -CTaskSimplePutDownEntity::CTaskSimplePutDownEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, bool bDisAllowDroppingOnAnimEnd, float fPutDownHeightZ) +CTaskSimplePutDownEntity::CTaskSimplePutDownEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, bool bDisAllowDroppingOnAnimEnd, float fPutDownHeightZ) : CTaskSimpleHoldEntity(entityToHold, posn, boneFrameId, boneFlags, animId, groupId, bDisAllowDroppingOnAnimEnd) { m_fPutDownHeightZ = fPutDownHeightZ; } -CTaskSimplePutDownEntity::CTaskSimplePutDownEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags, float fPutDownHeightZ) +CTaskSimplePutDownEntity::CTaskSimplePutDownEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags, float fPutDownHeightZ) : CTaskSimpleHoldEntity(entityToHold, posn, boneFrameId, boneFlags, animName, animBlockName, animFlags) { m_fPutDownHeightZ = fPutDownHeightZ; } -CTaskSimplePutDownEntity::CTaskSimplePutDownEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* pAnimBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fPutDownHeightZ) +CTaskSimplePutDownEntity::CTaskSimplePutDownEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* pAnimBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fPutDownHeightZ) : CTaskSimpleHoldEntity(entityToHold, posn, boneFrameId, boneFlags, pAnimBlock, animHierarchy, animFlags) { m_fPutDownHeightZ = fPutDownHeightZ; } -// 0x691990 -CTaskSimplePutDownEntity* CTaskSimplePutDownEntity::Constructor() { - this->CTaskSimplePutDownEntity::CTaskSimplePutDownEntity(); - return this; -} - // 0x692B70 -CTask* CTaskSimplePutDownEntity::Clone() { - return CTaskSimplePutDownEntity::Clone_Reversed(); -} - -// 0x691900 -eTaskType CTaskSimplePutDownEntity::GetTaskType() { - return CTaskSimplePutDownEntity::GetId_Reversed(); -} - -CTask* CTaskSimplePutDownEntity::Clone_Reversed() { - if (m_pAnimBlendHierarchy) { - return new CTaskSimplePutDownEntity( +CTask* CTaskSimplePutDownEntity::Clone() const { + return m_pAnimBlendHierarchy + ? new CTaskSimplePutDownEntity( m_pEntityToHold, &m_vecPosition, m_nBoneFrameId, @@ -59,16 +46,15 @@ CTask* CTaskSimplePutDownEntity::Clone_Reversed() { m_pAnimBlendHierarchy, static_cast(m_animFlags), m_fPutDownHeightZ + ) + : new CTaskSimplePutDownEntity( + m_pEntityToHold, + &m_vecPosition, + m_nBoneFrameId, + m_bBoneFlags, + m_nAnimId, + m_nAnimGroupId, + false, + m_fPutDownHeightZ ); - } - return new CTaskSimplePutDownEntity( - m_pEntityToHold, - &m_vecPosition, - m_nBoneFrameId, - m_bBoneFlags, - m_nAnimId, - m_nAnimGroupId, - false, - m_fPutDownHeightZ - ); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimplePutDownEntity.h b/source/game_sa/Tasks/TaskTypes/TaskSimplePutDownEntity.h index 1ab43bc01a..eef2a4d256 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimplePutDownEntity.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimplePutDownEntity.h @@ -2,31 +2,33 @@ #include "TaskSimpleHoldEntity.h" -class CTaskSimplePutDownEntity : public CTaskSimpleHoldEntity { +class NOTSA_EXPORT_VTABLE CTaskSimplePutDownEntity : public CTaskSimpleHoldEntity { public: float m_fPutDownHeightZ; public: CTaskSimplePutDownEntity(); - CTaskSimplePutDownEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, bool bDisAllowDroppingOnAnimEnd, float fPutDownHeightZ); - CTaskSimplePutDownEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags, float fPutDownHeightZ); - CTaskSimplePutDownEntity(CEntity* entityToHold, CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* pAnimBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fPutDownHeightZ); + CTaskSimplePutDownEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, AnimationId animId, AssocGroupId groupId, bool bDisAllowDroppingOnAnimEnd, float fPutDownHeightZ); + CTaskSimplePutDownEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, const char* animName, const char* animBlockName, eAnimationFlags animFlags, float fPutDownHeightZ); + CTaskSimplePutDownEntity(CEntity* entityToHold, const CVector* posn, uint8 boneFrameId, uint8 boneFlags, CAnimBlock* pAnimBlock, CAnimBlendHierarchy* animHierarchy, eAnimationFlags animFlags, float fPutDownHeightZ); ~CTaskSimplePutDownEntity() override = default; public: static constexpr auto Type = TASK_SIMPLE_PUTDOWN_ENTITY; - CTask* Clone() override; - eTaskType GetTaskType() override; + CTask* Clone() const override; + eTaskType GetTaskType() const override { return TASK_SIMPLE_PUTDOWN_ENTITY; } private: friend void InjectHooksMain(); static void InjectHooks(); - CTaskSimplePutDownEntity* Constructor(); - - CTask* Clone_Reversed(); - eTaskType GetId_Reversed() { return TASK_SIMPLE_PUTDOWN_ENTITY; }; +private: // Wrappers for hooks + // 0x691990 + CTaskSimplePutDownEntity* Constructor() { + this->CTaskSimplePutDownEntity::CTaskSimplePutDownEntity(); + return this; + } }; -VALIDATE_SIZE(CTaskSimplePutDownEntity, 0x40); \ No newline at end of file +VALIDATE_SIZE(CTaskSimplePutDownEntity, 0x40); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleRunAnim.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleRunAnim.cpp index 73ea963f4f..26d25cdc46 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleRunAnim.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleRunAnim.cpp @@ -45,8 +45,7 @@ CTaskSimpleRunAnim::CTaskSimpleRunAnim(AssocGroupId animGroup, AnimationId animI } // 0x61B6D0 -CTask* CTaskSimpleRunAnim::Clone() -{ +CTask* CTaskSimpleRunAnim::Clone() const { return Clone_Reversed(); } @@ -56,8 +55,7 @@ bool CTaskSimpleRunAnim::ProcessPed(CPed* ped) return ProcessPed_Reversed(ped); } -CTask* CTaskSimpleRunAnim::Clone_Reversed() -{ +CTask* CTaskSimpleRunAnim::Clone_Reversed() const { return new CTaskSimpleRunAnim(m_nAnimGroup, m_nAnimId, m_fBlendDelta, m_bHoldLastFrame); } diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleRunAnim.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleRunAnim.h index e2ac9f4fe9..7d20afc0ca 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleRunAnim.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleRunAnim.h @@ -22,8 +22,8 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleRunAnim : public CTaskSimpleAnim { CTaskSimpleRunAnim(AssocGroupId animGroup, AnimationId animId, float fBlendDelta = 4.f, bool bHoldLastFrame = false); CTaskSimpleRunAnim(AssocGroupId animGroup, AnimationId animId, float fBlendDelta, int32 nTaskType, const char* taskName _IGNORED_, bool bHoldLastFrame); - CTask* Clone() override; - eTaskType GetTaskType() override { return static_cast(m_nTaskType); } + CTask* Clone() const override; + eTaskType GetTaskType() const override { return static_cast(m_nTaskType); } bool ProcessPed(CPed* ped) override; void StartAnim(CPed* ped); @@ -35,7 +35,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleRunAnim : public CTaskSimpleAnim { CTaskSimpleRunAnim* Constructor(AssocGroupId animGroup, AnimationId animId, float fBlendDelta, bool bHoldLastFrame); CTaskSimpleRunAnim* Constructor2(AssocGroupId animGroup, AnimationId animId, float fBlendDelta, int32 nTaskType, const char* taskName _IGNORED_, bool bHoldLastFrame); - CTask* Clone_Reversed(); + CTask* Clone_Reversed() const; bool ProcessPed_Reversed(CPed* ped); }; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleRunNamedAnim.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleRunNamedAnim.h index bd90afaced..33fbd062c5 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleRunNamedAnim.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleRunNamedAnim.h @@ -30,8 +30,8 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleRunNamedAnim : public CTaskSimpleAnim { CTaskSimpleRunNamedAnim(const char* animName, const char* animGroupName, uint32 animFlags, float blendDelta, uint32 endTime, bool bDontInterrupt, bool bRunInSequence, bool bOffsetPed, bool bHoldLastFrame); - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleRunNamedAnim(m_animName, m_animGroupName, m_animFlags, m_fBlendDelta, m_Time, (m_nFlags & 2) >> 1, (m_nFlags & 0x10) >> 4, (m_nFlags & 0x20) >> 5, (m_nFlags & 4) >> 2); // todo: flags diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleRunTimedAnim.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleRunTimedAnim.h index bfadc27e45..f69bf3de81 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleRunTimedAnim.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleRunTimedAnim.h @@ -27,8 +27,8 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleRunTimedAnim : public CTaskSimpleAnim { void StartAnim(CPed* ped); - CTask* Clone() override { return new CTaskSimpleRunTimedAnim{ *this }; } - eTaskType GetTaskType() override { return m_taskId; } + CTask* Clone() const override { return new CTaskSimpleRunTimedAnim{ *this }; } + eTaskType GetTaskType() const override { return m_taskId; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks // 0x61AB70 diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSay.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleSay.h index 7d2226246a..2f17ac09a2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSay.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSay.h @@ -23,9 +23,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleSay : public CTaskSimple { CTaskSimpleSay(const CTaskSimpleSay& o) : CTaskSimpleSay{o.m_sayId, o.m_sayDuration} {} // NOTSA ~CTaskSimpleSay() = default; - CTask* Clone() override { return new CTaskSimpleSay(*this); } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return true; } + CTask* Clone() const override { return new CTaskSimpleSay(*this); } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleScratchHead.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleScratchHead.h index 20eac68407..69913f881c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleScratchHead.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleScratchHead.h @@ -7,6 +7,6 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleScratchHead : public CTaskSimpleRunAnim { CTaskSimpleScratchHead() : CTaskSimpleRunAnim(ANIM_GROUP_DEFAULT, ANIM_ID_XPRESSSCRATCH, 4.0, TASK_SIMPLE_SCRATCH_HEAD, "ScratchHead", false) { } ~CTaskSimpleScratchHead() override = default; - CTask* Clone() override { return new CTaskSimpleScratchHead(); } // 0x48DF60 + CTask* Clone() const override { return new CTaskSimpleScratchHead(); } // 0x48DF60 virtual bool IsInterruptable(const CPed* ped) { return false; } // 0x48DFD0 }; \ No newline at end of file diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSetCharDecisionMaker.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleSetCharDecisionMaker.h index e41b49f35b..66c71875fe 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSetCharDecisionMaker.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSetCharDecisionMaker.h @@ -20,9 +20,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleSetCharDecisionMaker : public CTaskSimple { CTaskSimpleSetCharDecisionMaker(const CTaskSimpleSetCharDecisionMaker&); // NOTSA ~CTaskSimpleSetCharDecisionMaker() = default; - CTask* Clone() override { return new CTaskSimpleSetCharDecisionMaker{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return false; } + CTask* Clone() const override { return new CTaskSimpleSetCharDecisionMaker{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSetPedAsAutoDriver.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleSetPedAsAutoDriver.h index 8d69046e59..7ec64c1197 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSetPedAsAutoDriver.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSetPedAsAutoDriver.h @@ -21,9 +21,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleSetPedAsAutoDriver : public CTaskSimple { CTaskSimpleSetPedAsAutoDriver(const CTaskSimpleSetPedAsAutoDriver&); ~CTaskSimpleSetPedAsAutoDriver(); - CTask* Clone() override { return new CTaskSimpleSetPedAsAutoDriver{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return false; } + CTask* Clone() const override { return new CTaskSimpleSetPedAsAutoDriver{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSetStayInSamePlace.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleSetStayInSamePlace.h index 62a5faf896..b4c15db080 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSetStayInSamePlace.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSetStayInSamePlace.h @@ -12,9 +12,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleSetStayInSamePlace : public CTaskSimple { explicit CTaskSimpleSetStayInSamePlace(bool bStayInSamePlace); ~CTaskSimpleSetStayInSamePlace() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleSetStayInSamePlace(m_bStayInSamePlace); } - bool MakeAbortable(class CPed* ped, eAbortPriority priority, const CEvent* event) override { return true; } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleSetStayInSamePlace(m_bStayInSamePlace); } + bool MakeAbortable(class CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } bool ProcessPed(CPed* ped) override; }; VALIDATE_SIZE(CTaskSimpleSetStayInSamePlace, 0xC); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleShakeFist.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleShakeFist.h index ad0091b094..b5176def7a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleShakeFist.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleShakeFist.h @@ -25,9 +25,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleShakeFist : public CTaskSimple { void StartAnim(CPed* ped); - CTask* Clone() override { return new CTaskSimpleShakeFist{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleShakeFist{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSitDown.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleSitDown.h index 5d5ffa9bb4..e6a690fc4b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSitDown.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSitDown.h @@ -27,9 +27,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleSitDown : public CTaskSimple { void StartAnim(CPed* ped); - CTask* Clone() override { return new CTaskSimpleSitDown{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleSitDown{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSitIdle.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleSitIdle.h index bd598c8128..bb96d7eb66 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSitIdle.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSitIdle.h @@ -28,9 +28,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleSitIdle : public CTaskSimple { void StartAnim(CPed* ped); - CTask* Clone() override { return new CTaskSimpleSitIdle{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleSitIdle{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSlideToCoord.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleSlideToCoord.cpp index 2d8f04f283..f81bb14536 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSlideToCoord.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSlideToCoord.cpp @@ -38,7 +38,7 @@ CTaskSimpleSlideToCoord::CTaskSimpleSlideToCoord(const CVector& slideToPos, floa } // 0x66D300 -CTask* CTaskSimpleSlideToCoord::Clone() { +CTask* CTaskSimpleSlideToCoord::Clone() const { return m_bRunningAnim ? new CTaskSimpleSlideToCoord(m_SlideToPos, m_fAimingRotation, m_fSpeed, m_animName, m_animGroupName, m_animFlags, m_fBlendDelta, !!m_bRunInSequence, m_Time) : new CTaskSimpleSlideToCoord(m_SlideToPos, m_fAimingRotation, m_fSpeed); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSlideToCoord.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleSlideToCoord.h index cc323b1d45..391eaba50a 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSlideToCoord.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSlideToCoord.h @@ -21,9 +21,9 @@ class CTaskSimpleSlideToCoord : public CTaskSimpleRunNamedAnim { CTaskSimpleSlideToCoord(const CVector& slideToPos, float aimingRotation, float speed, const char* animBlockName, const char* animGroupName, uint32 animFlags, float animBlendDelta, bool bRunInSequence, uint32 time); ~CTaskSimpleSlideToCoord() override = default; - CTask* Clone() override; - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + CTask* Clone() const override; + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleStandStill.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleStandStill.h index 3827add058..e6fa8bc409 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleStandStill.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleStandStill.h @@ -24,9 +24,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleStandStill : public CTaskSimple { ~CTaskSimpleStandStill() override = default; public: - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleStandStill(m_nTime, m_bLooped, m_bUseAnimIdleStance, m_fBlendData); } // 0x635CF0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleStandStill(m_nTime, m_bLooped, m_bUseAnimIdleStance, m_fBlendData); } // 0x635CF0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleStandUp.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleStandUp.h index 2e6187816d..e747cf47ef 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleStandUp.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleStandUp.h @@ -28,9 +28,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleStandUp : public CTaskSimple { void StartAnim(CPed* ped); - CTask* Clone() override { return new CTaskSimpleStandUp{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + CTask* Clone() const override { return new CTaskSimpleStandUp{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleStealthKill.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleStealthKill.cpp index 2cbc11d6e2..ab25a76319 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleStealthKill.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleStealthKill.cpp @@ -121,14 +121,14 @@ void CTaskSimpleStealthKill::ManageAnim(CPed* ped) { CPedDamageResponseCalculator damageCalculator{ ped, CPedDamageResponseCalculator::ms_damageFactor, - m_target->GetActiveWeapon().m_nType, + m_target->GetActiveWeapon().m_Type, PED_PIECE_TORSO, false }; CEventDamage eventDamage{ m_target, CTimer::GetTimeInMS(), - m_target->GetActiveWeapon().m_nType, + m_target->GetActiveWeapon().m_Type, PED_PIECE_TORSO, 0, false, diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleStealthKill.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleStealthKill.h index 0c1bd64ee3..050f190c03 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleStealthKill.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleStealthKill.h @@ -37,10 +37,10 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleStealthKill : public CTaskSimple { void ManageAnim(CPed* ped); - CTask* Clone() override { return new CTaskSimpleStealthKill{ *this }; } - eTaskType GetTaskType() override { return TASK_SIMPLE_STEALTH_KILL; } // 0x622670 + CTask* Clone() const override { return new CTaskSimpleStealthKill{ *this }; } + eTaskType GetTaskType() const override { return TASK_SIMPLE_STEALTH_KILL; } // 0x622670 bool ProcessPed(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; private: auto Constructor(bool keepTargetAlive, CPed* target, AssocGroupId groupId) { diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSwim.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleSwim.cpp index a3efece469..ddcf479be5 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSwim.cpp +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSwim.cpp @@ -24,7 +24,9 @@ bool CTaskSimpleSwim::MakeAbortable(class CPed* ped, eAbortPriority priority, co bool CTaskSimpleSwim::ProcessPed(CPed* ped) { return ProcessPed_Reversed(ped); } // 0x688930 -CTaskSimpleSwim::CTaskSimpleSwim(CVector* pos, CPed* ped) : CTaskSimple(), m_vecPos{ CVector() } { +CTaskSimpleSwim::CTaskSimpleSwim(const CVector* pos, CPed* ped) : + m_vecPos{pos ? *pos : CVector{}} +{ m_bFinishedBlending = false; m_bAnimBlockRefAdded = false; m_fAnimSpeed = -1.0f; @@ -40,9 +42,6 @@ CTaskSimpleSwim::CTaskSimpleSwim(CVector* pos, CPed* ped) : CTaskSimple(), m_vec m_nSwimState = SWIM_TREAD; m_AnimID = ANIM_ID_NO_ANIMATION_SET; - if (pos) - m_vecPos = *pos; - CEntity::SafeRegisterRef(m_pPed); m_pFxSystem = nullptr; @@ -241,7 +240,7 @@ void CTaskSimpleSwim::ProcessSwimAnims(CPed* ped) { } } RpAnimBlendClumpSetBlendDeltas(player->m_pRwClump, 0x10, -8.0f); // todo: ANIMATION_PARTIAL ? - FxSystem_c::SafeKillAndClear(player->GetActiveWeapon().m_pFxSystem); // Removes fire or something in water + FxSystem_c::SafeKillAndClear(player->GetActiveWeapon().m_FxSystem); // Removes fire or something in water if (player->IsPlayer() && !m_nSwimState) { float waterLevel = 0.0f; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleSwim.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleSwim.h index f5db088355..58f72fc348 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleSwim.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleSwim.h @@ -53,12 +53,12 @@ class CTaskSimpleSwim : public CTaskSimple { public: static constexpr auto Type = TASK_SIMPLE_SWIM; - CTaskSimpleSwim(CVector* pos, CPed* ped); + CTaskSimpleSwim(const CVector* pos, CPed* ped); ~CTaskSimpleSwim() override; - CTask* Clone() override { return new CTaskSimpleSwim(&m_vecPos, m_pPed); } // 0x68B050 - eTaskType GetTaskType() override { return Type; }; // 0x6889F0 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + CTask* Clone() const override { return new CTaskSimpleSwim{&m_vecPos, m_pPed}; } // 0x68B050 + eTaskType GetTaskType() const override { return Type; }; // 0x6889F0 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void CreateFxSystem(CPed* ped, RwMatrix* pRwMatrix); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleThrowControl.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleThrowControl.h index 601ec00873..b3f7bd6c77 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleThrowControl.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleThrowControl.h @@ -22,9 +22,9 @@ class CTaskSimpleThrowControl : public CTaskSimple { CTaskSimpleThrowControl(const CTaskSimpleThrowControl&); ~CTaskSimpleThrowControl(); - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleThrowControl{ *this }; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleThrowControl{ *this }; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleThrowProjectile.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleThrowProjectile.h index eefeb6d151..60d74188e8 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleThrowProjectile.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleThrowProjectile.h @@ -26,9 +26,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleThrowProjectile : public CTaskSimple { CTaskSimpleThrowProjectile(CEntity* target, CVector posn); ~CTaskSimpleThrowProjectile() override; - eTaskType GetTaskType() override { return Type; } // 0x61F6F0 - CTask* Clone() override { return new CTaskSimpleThrowProjectile(m_pTarget, m_vecPosition); } // 0x623030 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x61F6F0 + CTask* Clone() const override { return new CTaskSimpleThrowProjectile(m_pTarget, m_vecPosition); } // 0x623030 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool ControlThrow(bool bUpdateStartTime, CEntity* entity, CVector* posn); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleTired.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleTired.h index c87b956ba1..a8409c99ac 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleTired.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleTired.h @@ -20,9 +20,9 @@ class CTaskSimpleTired : public CTaskSimple { explicit CTaskSimpleTired(uint32 tiredDurationMs); ~CTaskSimpleTired() override = default; - eTaskType GetTaskType() override { return Type; } // 0x630F50 - CTask* Clone() override { return new CTaskSimpleTired(m_TiredDurationMs); } // 0x636180 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + eTaskType GetTaskType() const override { return Type; } // 0x630F50 + CTask* Clone() const override { return new CTaskSimpleTired(m_TiredDurationMs); } // 0x636180 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; void StartAnim(CPed* ped); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleTogglePedThreatScanner.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleTogglePedThreatScanner.h index c4a83d05a6..9359819a72 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleTogglePedThreatScanner.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleTogglePedThreatScanner.h @@ -12,9 +12,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleTogglePedThreatScanner : public CTaskSimple CTaskSimpleTogglePedThreatScanner(bool bScanAllowedScriptPed, bool bScanAllowedInVehicle, bool bScanAllowedScriptedTask); ~CTaskSimpleTogglePedThreatScanner() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleTogglePedThreatScanner(m_bScanAllowedScriptPed, m_bScanAllowedInVehicle, m_bScanAllowedScriptedTask); } // 0x492D50 - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return false; } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleTogglePedThreatScanner(m_bScanAllowedScriptPed, m_bScanAllowedInVehicle, m_bScanAllowedScriptedTask); } // 0x492D50 + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } bool ProcessPed(CPed* ped) override; }; VALIDATE_SIZE(CTaskSimpleTogglePedThreatScanner, 0xC); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleTurn180.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleTurn180.h index d1e8b79520..bdc6852aab 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleTurn180.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleTurn180.h @@ -23,10 +23,10 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleTurn180 : public CTaskSimple { static void FinishAnimTurn180CB(CAnimBlendAssociation* anim, void* data); - CTask* Clone() override { return new CTaskSimpleTurn180{ *this }; } - eTaskType GetTaskType() override { return Type; } + CTask* Clone() const override { return new CTaskSimpleTurn180{ *this }; } + eTaskType GetTaskType() const override { return Type; } bool ProcessPed(CPed* ped) override; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return false; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return false; } private: // Wrappers for hooks // 0x631CC0 diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleUninterruptable.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleUninterruptable.h index 2dd925795a..f2826e24ea 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleUninterruptable.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleUninterruptable.h @@ -9,9 +9,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleUninterruptable : public CTaskSimple { CTaskSimpleUninterruptable() = default; ~CTaskSimpleUninterruptable() override = default; - eTaskType GetTaskType() override { return Type; } - CTask* Clone() override { return new CTaskSimpleUninterruptable(); } - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override { return priority == ABORT_PRIORITY_IMMEDIATE; } + eTaskType GetTaskType() const override { return Type; } + CTask* Clone() const override { return new CTaskSimpleUninterruptable(); } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return priority == ABORT_PRIORITY_IMMEDIATE; } bool ProcessPed(CPed* ped) override { return false; } static void InjectHooks(); diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleUseAtm.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleUseAtm.h index 0f3eb95a22..67d66d7b6e 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleUseAtm.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleUseAtm.h @@ -15,7 +15,7 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleUseAtm : public CTaskSimpleRunAnim { CTaskSimpleUseAtm(const CTaskSimpleUseAtm&); // NOTSA ~CTaskSimpleUseAtm() = default; - CTask* Clone() override { return new CTaskSimpleUseAtm{ *this }; } + CTask* Clone() const override { return new CTaskSimpleUseAtm{ *this }; } virtual bool IsInterruptable(CPed const* ped) { return false; } private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleUseGun.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleUseGun.h index 79a4af3960..7e759e94c5 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleUseGun.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleUseGun.h @@ -52,9 +52,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleUseGun : public CTaskSimple { CTaskSimpleUseGun(CEntity* targetEntity, CVector vecTarget, uint8 nCommand, uint16 nBurstLength = 1, bool bAimImmediate = false); ~CTaskSimpleUseGun() override; - CTask* Clone() override { return new CTaskSimpleUseGun(m_pTarget, m_vecTarget, m_nLastCommand, m_nBurstLength, m_bAimImmediate); } - eTaskType GetTaskType() override { return Type; }; - bool MakeAbortable(CPed* ped, eAbortPriority priority, const CEvent* event) override; + CTask* Clone() const override { return new CTaskSimpleUseGun(m_pTarget, m_vecTarget, m_nLastCommand, m_nBurstLength, m_bAimImmediate); } + eTaskType GetTaskType() const override { return Type; }; + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override; bool ProcessPed(CPed* ped) override; bool SetPedPosition(CPed* ped) override; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitForBus.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitForBus.h index a86f69b34e..d31230ba3c 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitForBus.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitForBus.h @@ -21,9 +21,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleWaitForBus : public CTaskSimple { CTaskSimpleWaitForBus(const CTaskSimpleWaitForBus&); ~CTaskSimpleWaitForBus() = default; - CTask* Clone() override { return new CTaskSimpleWaitForBus{*this}; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return true; } + CTask* Clone() const override { return new CTaskSimpleWaitForBus{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilAreaCodesMatch.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilAreaCodesMatch.h index 6c18c8b534..3e4e8841dc 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilAreaCodesMatch.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilAreaCodesMatch.h @@ -20,9 +20,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleWaitUntilAreaCodesMatch : public CTaskSimpl CTaskSimpleWaitUntilAreaCodesMatch(const CTaskSimpleWaitUntilAreaCodesMatch& o) : CTaskSimpleWaitUntilAreaCodesMatch{o.m_pedToWaitFor} {} // NOTSA ~CTaskSimpleWaitUntilAreaCodesMatch(); - CTask* Clone() override { return new CTaskSimpleWaitUntilAreaCodesMatch{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return priority == ABORT_PRIORITY_IMMEDIATE; } + CTask* Clone() const override { return new CTaskSimpleWaitUntilAreaCodesMatch{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return priority == ABORT_PRIORITY_IMMEDIATE; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks // 0x635540 diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilLeaderAreaCodesMatch.cpp b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilLeaderAreaCodesMatch.cpp new file mode 100644 index 0000000000..a725ec7444 --- /dev/null +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilLeaderAreaCodesMatch.cpp @@ -0,0 +1,37 @@ +#include "StdInc.h" +#include "TaskSimpleWaitUntilLeaderAreaCodesMatch.h" + +void CTaskSimpleWaitUntilLeaderAreaCodesMatch::InjectHooks() { + RH_ScopedVirtualClass(CTaskSimpleWaitUntilLeaderAreaCodesMatch, 0x86c7e8, 9); + RH_ScopedCategory("Tasks/TaskTypes"); + + RH_ScopedVMTInstall(Clone, 0x5F67F0); + RH_ScopedVMTInstall(GetTaskType, 0x5F6870); + RH_ScopedVMTInstall(MakeAbortable, 0x6356A0); + RH_ScopedVMTInstall(ProcessPed, 0x6356C0); +} + +// 0x6356A0 +bool CTaskSimpleWaitUntilLeaderAreaCodesMatch::MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) { + if (priority != ABORT_PRIORITY_IMMEDIATE) { + return false; + } else { + ped->m_bUsesCollision = true; + return true; + } +} + +// 0x6356C0 +bool CTaskSimpleWaitUntilLeaderAreaCodesMatch::ProcessPed(CPed* ped) { + if (CTaskSimpleWaitUntilAreaCodesMatch::ProcessPed(ped)) { + ped->m_bUsesCollision = true; + return true; + } + if (!m_b) { + if (ped->GetTaskManager().Find()) { + m_b = true; + ped->m_bUsesCollision = false; + } + } + return false; +} diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilLeaderAreaCodesMatch.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilLeaderAreaCodesMatch.h new file mode 100644 index 0000000000..2424a5ca4c --- /dev/null +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilLeaderAreaCodesMatch.h @@ -0,0 +1,27 @@ +#pragma once + +#include "TaskSimpleWaitUntilAreaCodesMatch.h" +#include "Vector.h" + +class CPed; +class CEvent; + +class NOTSA_EXPORT_VTABLE CTaskSimpleWaitUntilLeaderAreaCodesMatch final : public CTaskSimpleWaitUntilAreaCodesMatch { +public: + bool m_bGrabbedStartPos{}; + CVector m_vStartPos{}; + bool m_b{}; + +public: + static constexpr auto Type = eTaskType::TASK_WAIT_FOR_MATCHING_LEADER_AREA_CODES; + + static void InjectHooks(); + + using CTaskSimpleWaitUntilAreaCodesMatch::CTaskSimpleWaitUntilAreaCodesMatch; + CTaskSimpleWaitUntilLeaderAreaCodesMatch(const CTaskSimpleWaitUntilLeaderAreaCodesMatch& o) = default; + + CTask* Clone() const override { return new CTaskSimpleWaitUntilLeaderAreaCodesMatch{*this}; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override; + bool ProcessPed(CPed* ped) override; +}; diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilPedIsInCar.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilPedIsInCar.h index 8c15bb81fc..21dc53a9c2 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilPedIsInCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilPedIsInCar.h @@ -18,9 +18,9 @@ class NOTSA_EXPORT_VTABLE CTaskSimpleWaitUntilPedIsInCar : public CTaskSimple { CTaskSimpleWaitUntilPedIsInCar(CPed* pedToWaitFor); ~CTaskSimpleWaitUntilPedIsInCar(); - CTask* Clone() override { return new CTaskSimpleWaitUntilPedIsInCar{ *this }; } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return true; } + CTask* Clone() const override { return new CTaskSimpleWaitUntilPedIsInCar{ *this }; } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } bool ProcessPed(CPed* ped) override; private: // Wrappers for hooks diff --git a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilPedIsOutCar.h b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilPedIsOutCar.h index 55cd146234..f2b6be832b 100644 --- a/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilPedIsOutCar.h +++ b/source/game_sa/Tasks/TaskTypes/TaskSimpleWaitUntilPedIsOutCar.h @@ -19,9 +19,9 @@ class CTaskSimpleWaitUntilPedIsOutCar : public CTaskSimple { CTaskSimpleWaitUntilPedIsOutCar(CPed * ped, CVector const& pos); ~CTaskSimpleWaitUntilPedIsOutCar() override; - CTask* Clone() override { return new CTaskSimpleWaitUntilPedIsOutCar(m_PedToWaitFor, m_Pos); } - eTaskType GetTaskType() override { return Type; } - bool MakeAbortable(CPed* ped, eAbortPriority priority, CEvent const* event) override { return true; } + CTask* Clone() const override { return new CTaskSimpleWaitUntilPedIsOutCar(m_PedToWaitFor, m_Pos); } + eTaskType GetTaskType() const override { return Type; } + bool MakeAbortable(CPed* ped, eAbortPriority priority = ABORT_PRIORITY_URGENT, const CEvent* event = nullptr) override { return true; } bool ProcessPed(CPed * ped) override; private: diff --git a/source/game_sa/Text/Text.cpp b/source/game_sa/Text/Text.cpp index 450fc10ebe..ece5e2ed50 100644 --- a/source/game_sa/Text/Text.cpp +++ b/source/game_sa/Text/Text.cpp @@ -148,11 +148,9 @@ void CheckFileEncoding(const char* file, uint16 version, uint16 encoding) { return std::format("{}-bit unknown", e); } }; - auto fileEncoding = GetEncodingName(encoding); - - DEV_LOG("Loading '{}' version={:02d} ({})\n", file, version, fileEncoding.c_str()); + DEV_LOG("Loading '{}' version={:02d} ({})", file, version, GetEncodingName(encoding)); if (encoding != GAME_ENCODING) { - NOTSA_UNREACHABLE("File {} was compiled with {} encoding but {} is required.", file, fileEncoding, GetEncodingName(GAME_ENCODING)); + NOTSA_UNREACHABLE("File {} was compiled with {} encoding but {} is required.", file, GetEncodingName(encoding), GetEncodingName(GAME_ENCODING)); } } diff --git a/source/game_sa/TheCarGenerators.cpp b/source/game_sa/TheCarGenerators.cpp index 15c39ed774..d34ad2c59e 100644 --- a/source/game_sa/TheCarGenerators.cpp +++ b/source/game_sa/TheCarGenerators.cpp @@ -45,6 +45,8 @@ int32 CTheCarGenerators::CreateCarGenerator(CVector posn, float angle, int32 mod // 0x6F3270 void CTheCarGenerators::Init() { + ZoneScoped; + NumOfCarGenerators = 0; ProcessCounter = 0; GenerateEvenIfPlayerIsCloseCounter = 0; @@ -79,6 +81,8 @@ void CTheCarGenerators::Load() { // 0x6F3F40 void CTheCarGenerators::Process() { + ZoneScoped; + if (FindPlayerTrain() || CCutsceneMgr::IsCutsceneProcessing() || CReplay::Mode == MODE_PLAYBACK) return; diff --git a/source/game_sa/TheZones.cpp b/source/game_sa/TheZones.cpp index 8b7e5492c1..534a784291 100644 --- a/source/game_sa/TheZones.cpp +++ b/source/game_sa/TheZones.cpp @@ -9,10 +9,11 @@ #include "TheZones.h" +#include + // Variables eLevelName& CTheZones::m_CurrLevel = *(eLevelName*)0xBA6718; -char* CTheZones::ZonesVisited = (char*)0xBA3730; int32& CTheZones::ZonesRevealed = *(int32*)0xBA372C; int16& CTheZones::TotalNumberOfMapZones = *(int16*)0xBA1900; CZone (&CTheZones::NavigationZoneArray)[380] = *(CZone(*)[380])0xBA3798; @@ -25,40 +26,88 @@ int16& CTheZones::TotalNumberOfZoneInfos = *(int16*)0xBA1DE8; void CTheZones::InjectHooks() { RH_ScopedClass(CTheZones); RH_ScopedCategoryGlobal(); - RH_ScopedInstall(ResetZonesRevealed, 0x572110); - RH_ScopedInstall(GetCurrentZoneLockedOrUnlocked, 0x572130); - RH_ScopedInstall(PointLiesWithinZone, 0x572270); - RH_ScopedInstall(GetNavigationZone, 0x572590); - RH_ScopedInstall(GetMapZone, 0x5725A0); - RH_ScopedInstall(Save, 0x5D2E60); - RH_ScopedInstall(Load, 0x5D2F40); - RH_ScopedInstall(PostZoneCreation, 0x572B70); + + RH_ScopedGlobalInstall(ResetZonesRevealed, 0x572110); + RH_ScopedGlobalInstall(PointLiesWithinZone, 0x572270); + RH_ScopedGlobalInstall(GetNavigationZone, 0x572590); + RH_ScopedGlobalInstall(GetMapZone, 0x5725A0); + RH_ScopedGlobalInstall(Save, 0x5D2E60); + RH_ScopedGlobalInstall(Load, 0x5D2F40); + RH_ScopedGlobalInstall(PostZoneCreation, 0x572B70); + RH_ScopedGlobalInstall(InitZonesPopulationSettings, 0x5720D0); + RH_ScopedGlobalInstall(Update, 0x572D10); + RH_ScopedGlobalInstall(SetZoneRadarColours, 0x572CC0); + RH_ScopedGlobalInstall(FindZoneByLabel, 0x572C40); + RH_ScopedGlobalInstall(CreateZone, 0x5728A0); + RH_ScopedGlobalInstall(Init, 0x572670); + RH_ScopedGlobalInstall(AssignZoneInfoForThisZone, 0x572180); + RH_ScopedGlobalOverloadedInstall(FindZone, "", 0x572B80, bool(*)(CVector*, uint64_t, eZoneType)); + RH_ScopedGlobalInstall(Calc2DDistanceBetween2Zones, 0x5725B0); + RH_ScopedGlobalInstall(FillZonesWithGangColours, 0x572440); + RH_ScopedGlobalOverloadedInstall(GetZoneInfo, "", 0x572400, CZoneInfo*(*)(const CVector& point, CZone**)); + RH_ScopedGlobalInstall(FindSmallestZoneForPosition, 0x572360); + RH_ScopedGlobalInstall(GetLevelFromPosition, 0x572300); + RH_ScopedGlobalInstall(ZoneIsEntirelyContainedWithinOtherZone, 0x572220); + } // 0x5720D0 void CTheZones::InitZonesPopulationSettings() { - ((void(__cdecl*)())0x5720D0)(); + rng::fill(ZoneInfoArray, CZoneInfo{}); } // 0x572110 void CTheZones::ResetZonesRevealed() { - memset(ZonesVisited, 0, 100); // TODO: sizeof(CTheZones::ExploredTerritoriesArray) + std::memset(&ZonesVisited, 0, sizeof(ZonesVisited)); ZonesRevealed = 0; } -// 0x572130 -bool CTheZones::GetCurrentZoneLockedOrUnlocked(float posx, float posy) { - return CTheZones::ZonesVisited[10 * (uint8)((posx + 3000.0) * 0.0016666667) - (uint8)((posy + 3000.0) * 0.0016666667) + 9] != 0; // todo: ugly +// NOTSA +bool& CTheZones::GetZoneWasVisited(CVector2D pos) { +#ifdef FIX_BUGS + return ZonesVisited[(size_t)((std::clamp(pos.x, -2999.f, 2999.f) + 3000.f) / 600.f)][9LL - (size_t)((std::clamp(pos.y, -2999.f, 2999.f) + 3000.f) / 600.f)]; +#else + return ZonesVisited[10 * (size_t)((pos.x + 3000.f) / 600.f) - (size_t)((pos.y + 3000.f) / 600.f) + 9]; +#endif +} + +// NOTSA +bool CTheZones::SetZoneWasVisited(CVector2D pos, bool visited) { + bool& wasVisited = GetZoneWasVisited(pos); + if (wasVisited == visited) { + return false; + } + wasVisited = visited; + return true; +} + +bool CTheZones::GetCurrentZoneLockedOrUnlocked(CVector2D pos) { + return GetZoneWasVisited(pos); } // 0x572180 -void CTheZones::AssignZoneInfoForThisZone(int16 index) { - ((void(__cdecl*)(int16))0x572180)(index); +void CTheZones::AssignZoneInfoForThisZone(int16 newZoneIndex) { + auto& zone = NavigationZoneArray[newZoneIndex]; + zone.m_nZoneExtraIndexInfo = [&]{ + for (const auto& [i, zoneInfo] : notsa::enumerate(GetNavigationZones())) { + if (i != newZoneIndex && zoneInfo.GetInfoLabel() == zone.GetInfoLabel()) { + return zoneInfo.m_nZoneExtraIndexInfo; + } + } + return TotalNumberOfZoneInfos++; + }(); } // 0x572220 -bool CTheZones::ZoneIsEntirelyContainedWithinOtherZone(CZone* zone1, CZone* zone2) { - return ((bool(__cdecl*)(CZone*, CZone*))0x572220)(zone1, zone2); +bool CTheZones::ZoneIsEntirelyContainedWithinOtherZone(CZone* a, CZone* b) { + return a->m_fX1 >= b->m_fX1 + && a->m_fX2 <= b->m_fX2 + + && a->m_fY1 >= b->m_fY1 + && a->m_fY2 <= b->m_fY2 + + && a->m_fZ1 >= b->m_fZ1 + && a->m_fZ2 <= b->m_fZ2; } // Returns true if point lies within zone @@ -76,23 +125,72 @@ bool CTheZones::PointLiesWithinZone(const CVector* point, CZone* zone) { // Returns eLevelName from position eLevelName CTheZones::GetLevelFromPosition(const CVector& point) { - return ((eLevelName(__cdecl*)(const CVector&))0x572300)(point); + for (auto& z : GetMapZones()) { + if (z.GetBB().IsPointInside(point)) { + return z.m_nLevel; + } + } + return GetMapZones()[0].m_nLevel; } // Returns pointer to zone by a point // 0x572360 -CZone* CTheZones::FindSmallestZoneForPosition(const CVector& point, bool FindOnlyZonesType0) { - return ((CZone * (__cdecl*)(const CVector&, bool))0x572360)(point, FindOnlyZonesType0); +CZone* CTheZones::FindSmallestZoneForPosition(const CVector& point, bool checkIsNavi) { + const auto GetZoneSize = [](CZone* z) { + return z->m_fX2 - z->m_fX1 + z->m_fY2 - z->m_fY1; + }; + + auto smallestZone = &NavigationZoneArray[0]; // Start with the whole map + auto smallestZoneSize = GetZoneSize(smallestZone); + for (auto& z : GetNavigationZones()) { + if (checkIsNavi && z.m_nType != ZONE_TYPE_NAVI) { + continue; + } + if (!z.GetBB().IsPointInside(point)) { + continue; + } + const auto zsize = GetZoneSize(&z); + if (zsize < smallestZoneSize) { + smallestZone = &z; + smallestZoneSize = zsize; + } + } + return smallestZone; } // 0x572400 CZoneInfo* CTheZones::GetZoneInfo(const CVector& point, CZone** outZone) { - return ((CZoneInfo * (__cdecl*)(const CVector&, CZone**))0x572400)(point, outZone); + const auto z = FindSmallestZoneForPosition(point, false); + if (outZone) { + *outZone = z ? z : &NavigationZoneArray[0]; // Zone 0 is always SAN_AND + } + return &ZoneInfoArray[z ? z->m_nZoneExtraIndexInfo : 0]; } // 0x572440 void CTheZones::FillZonesWithGangColours(bool disableRadarGangColors) { - ((void(__cdecl*)(bool))0x572440)(disableRadarGangColors); + for (auto& z : GetZoneInfos()) { + const auto gdSum = z.GangDensity[GANG_BALLAS] + z.GangDensity[GANG_GROVE] + z.GangDensity[GANG_VAGOS]; + + uint8 color[3]; + for (auto i = 0; i < 3; i++) { + const auto GetVW = [&](eGangID g) { + return WeightedValue{ gaGangColors[g].components[i], z.GangDensity[g] }; + }; + color[i] = (uint8)multiply_weighted({ GetVW(GANG_BALLAS), GetVW(GANG_GROVE), GetVW(GANG_VAGOS) }) / std::max(1, gdSum); + } + + z.radarMode = gdSum && !disableRadarGangColors && CGangWars::CanPlayerStartAGangWarHere(&z) + ? 1 + : 0; + + z.ZoneColor = { + color[0], + color[1], + color[2], + std::clamp(gdSum * 3, 55, 120) + }; + } } // Returns pointer to zone by index @@ -108,14 +206,46 @@ CZone* CTheZones::GetMapZone(uint16 index) { } // 0x5725b0 -long double CTheZones::Calc2DDistanceBetween2Zones(CZone* zone1, CZone* zone2) { - return ((long double(__cdecl*)(CZone*, CZone*))0x5725b0)(zone1, zone2); +float CTheZones::Calc2DDistanceBetween2Zones(CZone* a, CZone* b) { + int16 dx, dy; + + if (a->m_fX1 <= b->m_fX2) { + dx = a->m_fX2 >= b->m_fX1 + ? 0 + : b->m_fX1 - a->m_fX2; + } else { + dx = a->m_fX1 - b->m_fX2; + } + + if (a->m_fY1 <= b->m_fY2) { // Copy paste of the above but for the `y` axis + dy = a->m_fY2 >= b->m_fY1 + ? 0 + : b->m_fY1 - a->m_fY2; + } else { + dy = a->m_fY1 - b->m_fY2; + } + + return (float)std::sqrt(sq(dx) + sq(dy)); } // Initializes CTheZones // 0x572670 void CTheZones::Init() { - ((void(__cdecl*)())0x572670)(); + ZoneScoped; + + rng::fill(NavigationZoneArray, CZone{}); + rng::fill(MapZoneArray, CZone{}); + + InitZonesPopulationSettings(); + ResetZonesRevealed(); + + TotalNumberOfZoneInfos = 0; + TotalNumberOfNavigationZones = 0; + TotalNumberOfMapZones = 0; + m_CurrLevel = LEVEL_NAME_COUNTRY_SIDE; + + CreateZone("SAN_AND", ZONE_TYPE_NAVI, { -3000.f, -3000.f, -2000.f }, { 3000.f, 3000.f, 2000.f }, LEVEL_NAME_COUNTRY_SIDE, "SAN_AND"); + CreateZone("THEMAP", ZONE_TYPE_MAP, { -3000.f, -3000.f, -2000.f }, { 3000.f, 3000.f, 2000.f }, LEVEL_NAME_COUNTRY_SIDE, "THEMAP"); } // Unlock the current zone @@ -126,14 +256,57 @@ void CTheZones::SetCurrentZoneAsUnlocked() { // Creates a zone // 0x5728A0 -void CTheZones::CreateZone(const char* name, eZoneType type, float posX1, float posY1, float posZ1, float posX2, float posY2, float posZ2, eLevelName island, const char* GXT_key) { - ((void(__cdecl*)(const char*, eZoneType, float, float, float, float, float, float, eLevelName island, const char*))0x5728A0)(name, type, posX1, posY1, posZ1, posX2, posY2, posZ2, island, GXT_key); +void CTheZones::CreateZone( + const char* infoLabel, + eZoneType type, + CVector pos1, + CVector pos2, + eLevelName level, + const char* textLabel +) { + const auto z = [type]{ + switch (type) { + case ZONE_TYPE_LOCAL_NAVI: + case ZONE_TYPE_NAVI: return &NavigationZoneArray[TotalNumberOfNavigationZones++]; + case ZONE_TYPE_MAP: return &MapZoneArray[TotalNumberOfMapZones++]; + } + NOTSA_UNREACHABLE(); + }(); + + const auto StrCpyUpper = [](auto& dst, auto& src) { + rng::fill(dst, 0); + strcpy_s(dst, src); + for (auto& ch : dst) { + if (ch == 0) { + break; + } + ch = toupper(ch); + } + }; + StrCpyUpper(z->m_TextLabel, textLabel); + StrCpyUpper(z->m_InfoLabel, infoLabel); + + std::tie(z->m_fX1, z->m_fX2) = std::minmax((int16)pos1.x, (int16)pos2.x); + std::tie(z->m_fY1, z->m_fY2) = std::minmax((int16)pos1.y, (int16)pos2.y); + std::tie(z->m_fZ1, z->m_fZ2) = std::minmax((int16)pos1.z, (int16)pos2.z); + + z->m_nLevel = level; + + switch (type) { + case ZONE_TYPE_LOCAL_NAVI: + case ZONE_TYPE_NAVI: + AssignZoneInfoForThisZone(TotalNumberOfNavigationZones - 1); + break; + } } // Returns 1 if point lies within the specified zonename otherwise return 0 // 0x572B80 -bool CTheZones::FindZone(CVector* point, int32 zonename_part1, int32 zonename_part2, eZoneType type) { - return ((bool(__cdecl*)(CVector*, int32, int32, eZoneType))0x572B80)(point, zonename_part1, zonename_part2, type); +bool CTheZones::FindZone(CVector* point, uint64_t packedZoneName, eZoneType type) { + char zoneName[sizeof(packedZoneName)]; + memcpy(zoneName, &packedZoneName, sizeof(packedZoneName)); + assert(zoneName[sizeof(packedZoneName) - 1] == 0); + return FindZone(*point, { zoneName }, type); } //! Find zone by name `name` and of type `type` and check if `point` lies within it @@ -160,18 +333,44 @@ bool CTheZones::FindZone(const CVector& point, std::string_view name, eZoneType // Returns pointer to zone by index // 0x572C40 int16 CTheZones::FindZoneByLabel(const char* name, eZoneType type) { - return ((int16(__cdecl*)(const char*, eZoneType))0x572C40)(name, type); + assert(type == eZoneType::ZONE_TYPE_INFO); // Originally an `if` returning `-1`, but let's be safe + + for (auto&& [i, v] : notsa::enumerate(GetNavigationZones())) { + if (name == v.GetInfoLabel()) { + return (int16)i; + } + } + return -1; } // 0x572cc0 -void CTheZones::SetZoneRadarColours(int16 index, char flag, uint8 red, uint8 green, uint8 blue) { - ((void(__cdecl*)(int16, char, uint8, uint8, uint8))0x572cc0)(index, flag, red, green, blue); +void CTheZones::SetZoneRadarColours(int16 index, char radarMode, uint8 red, uint8 green, uint8 blue) { + const auto zone = &ZoneInfoArray[NavigationZoneArray[index].m_nZoneExtraIndexInfo]; + zone->radarMode = radarMode; + zone->ZoneColor = { red, green, blue, 255 }; } // Updates CTheZones info // 0x572D10 void CTheZones::Update() { - ((void(__cdecl*)())0x572D10)(); + ZoneScoped; + + const auto DELAY = 5000; + + static auto CountSeconds = CTimer::GetTimeInMS() - DELAY; // NOTE/BUG: This can underflow in the special case that `CTimer::GetTimeMS() < DELAY` + + if (CGame::CanSeeOutSideFromCurrArea()) { // Inverted + if (CTimer::GetTimeInMS() - CountSeconds > DELAY) { + // Code from 0x572800 + const auto pos = FindPlayerCoors(); + m_CurrLevel = CTheZones::GetLevelFromPosition(pos); + if (!CTheScripts::bPlayerIsOffTheMap && CGame::CanSeeOutSideFromCurrArea() && SetZoneWasVisited(pos, true)) { + CTheZones::ZonesRevealed++; + } + } + } else { + CountSeconds = CTimer::GetTimeInMS(); + } } // Save CTheZones info @@ -194,7 +393,7 @@ void CTheZones::Save() { CGenericGameStorage::SaveDataToWorkBuffer(&MapZoneArray[i], 0x20); } - CGenericGameStorage::SaveDataToWorkBuffer(ZonesVisited, 100); + SaveDataToWorkBuffer(ZonesVisited); CGenericGameStorage::SaveDataToWorkBuffer(&ZonesRevealed, 4); } @@ -219,7 +418,7 @@ void CTheZones::Load() { for (int32 i = 0; i < TotalNumberOfMapZones; i++) { CGenericGameStorage::LoadDataFromWorkBuffer(&MapZoneArray[i], 0x20u); } - CGenericGameStorage::LoadDataFromWorkBuffer(ZonesVisited, 100); + LoadDataFromWorkBuffer(ZonesVisited); CGenericGameStorage::LoadDataFromWorkBuffer(&ZonesRevealed, 4u); } diff --git a/source/game_sa/TheZones.h b/source/game_sa/TheZones.h index 08d1740881..130294a88a 100644 --- a/source/game_sa/TheZones.h +++ b/source/game_sa/TheZones.h @@ -13,8 +13,9 @@ class CTheZones { public: + static inline auto& ZonesVisited = StaticRef, 0xBA3730>(); // Explored territories. Count: 100 + static eLevelName& m_CurrLevel; - static char* ZonesVisited; // Explored territories. Count: 100 static int32& ZonesRevealed; // Number of explored territories static int16& TotalNumberOfNavigationZones; // Info zones static CZone (&NavigationZoneArray)[380]; @@ -31,7 +32,9 @@ class CTheZones { static void ResetZonesRevealed(); static void AssignZoneInfoForThisZone(int16 index); static bool ZoneIsEntirelyContainedWithinOtherZone(CZone* zone1, CZone* zone2); - static bool GetCurrentZoneLockedOrUnlocked(float posx, float posy); + static bool& GetZoneWasVisited(CVector2D pos); // NOTSA + static bool SetZoneWasVisited(CVector2D pos, bool locked); // NOTSA + static bool GetCurrentZoneLockedOrUnlocked(CVector2D pos); // Returns true if point lies within zone static bool PointLiesWithinZone(const CVector* point, CZone* zone); // Returns eLevelName from position @@ -44,15 +47,13 @@ class CTheZones { static CZone* GetNavigationZone(uint16 index); // Returns pointer to zone by index static CZone* GetMapZone(uint16 index); - static long double Calc2DDistanceBetween2Zones(CZone* zone1, CZone* zone2); + static float Calc2DDistanceBetween2Zones(CZone* zone1, CZone* zone2); static void Init(); static void SetCurrentZoneAsUnlocked(); - static void CreateZone(const char* name, eZoneType type, float posX1, float posY1, float posZ1, float posX2, float posY2, float posZ2, eLevelName island, const char* GXT_key); - - static bool FindZone(CVector* point, int32 zonename_part1, int32 zonename_part2, eZoneType type); + static void CreateZone(const char* name, eZoneType type, CVector pos1, CVector pos2, eLevelName level, const char* GXT_key); - + static bool FindZone(CVector* point, uint64_t zoneName, eZoneType type); static bool FindZone(const CVector& point, std::string_view name, eZoneType type); static int16 FindZoneByLabel(const char* name, eZoneType type); static void SetZoneRadarColours(int16 index, char flag, uint8 red, uint8 green, uint8 blue); @@ -80,4 +81,12 @@ class CTheZones { static auto GetNavigationZones() { return std::span{NavigationZoneArray, (size_t)TotalNumberOfNavigationZones}; } + + static auto GetMapZones() { + return std::span{MapZoneArray, (size_t)TotalNumberOfMapZones}; + } + + static auto GetZoneInfos() { + return ZoneInfoArray | rng::views::take((size_t)TotalNumberOfZoneInfos); + } }; diff --git a/source/game_sa/TimeCycle.cpp b/source/game_sa/TimeCycle.cpp index 54aae92a83..dd018ad9a0 100644 --- a/source/game_sa/TimeCycle.cpp +++ b/source/game_sa/TimeCycle.cpp @@ -185,6 +185,8 @@ void CTimeCycle::Shutdown() { // 0x561760 void CTimeCycle::Update() { + ZoneScoped; + CalcColoursForPoint(TheCamera.GetPosition(), &m_CurrentColours); } diff --git a/source/game_sa/Timer.cpp b/source/game_sa/Timer.cpp index b3b2694b25..705df2fd9e 100644 --- a/source/game_sa/Timer.cpp +++ b/source/game_sa/Timer.cpp @@ -233,8 +233,9 @@ void CTimer::UpdateVariables(float timeElapsed) } // 0x561B10 -void CTimer::Update() -{ +void CTimer::Update() { + ZoneScoped; + if (!ms_fnTimerFunction) return; diff --git a/source/game_sa/TxdStore.cpp b/source/game_sa/TxdStore.cpp index 5676e59ec0..48b1a9851e 100644 --- a/source/game_sa/TxdStore.cpp +++ b/source/game_sa/TxdStore.cpp @@ -117,6 +117,8 @@ bool CTxdStore::LoadTxd(int32 index, RwStream* stream) { // load txd from file // 0x7320B0 bool CTxdStore::LoadTxd(int32 index, const char* filename) { + ZoneScoped; + char data[260]; RwStream* stream; sprintf_s(data, "%s", filename); diff --git a/source/game_sa/TxdStore.h b/source/game_sa/TxdStore.h index c7fd81db92..5531d1ef41 100644 --- a/source/game_sa/TxdStore.h +++ b/source/game_sa/TxdStore.h @@ -24,6 +24,23 @@ static inline int32& ms_txdPluginOffset = *reinterpret_cast(0xC88018); typedef CPool CTxdPool; class CTxdStore { +public: + struct ScopedTXDSlot { + ScopedTXDSlot(int32 id) { + assert(id >= 0); + CTxdStore::PushCurrentTxd(); + CTxdStore::SetCurrentTxd(static_cast(id)); + } + + ScopedTXDSlot(const char* txd) : + ScopedTXDSlot{ CTxdStore::FindTxdSlot(txd) } + { + } + + ~ScopedTXDSlot() { + CTxdStore::PopCurrentTxd(); + } + }; public: static CTxdPool*& ms_pTxdPool; static RwTexDictionary*& ms_pStoredTxd; diff --git a/source/game_sa/UpsideDownCarCheck.cpp b/source/game_sa/UpsideDownCarCheck.cpp index bcd4c5507c..5eb1214dce 100644 --- a/source/game_sa/UpsideDownCarCheck.cpp +++ b/source/game_sa/UpsideDownCarCheck.cpp @@ -46,6 +46,8 @@ bool CUpsideDownCarCheck::IsCarUpsideDown(const CVehicle* vehicle) { // Process // 0x4655E0 void CUpsideDownCarCheck::UpdateTimers() { + ZoneScoped; + for (auto& car : m_aUpsideDownCars) { if (!car.m_nHandle) continue; diff --git a/source/game_sa/UserDisplay.cpp b/source/game_sa/UserDisplay.cpp index 7e3b53fc23..55573bc06c 100644 --- a/source/game_sa/UserDisplay.cpp +++ b/source/game_sa/UserDisplay.cpp @@ -18,6 +18,8 @@ void CUserDisplay::InjectHooks() { // 0x571EE0 void CUserDisplay::Init() { + ZoneScoped; + PlaceName.Init(); OnscnTimer.Init(); CurrentVehicle.Init(); @@ -25,6 +27,8 @@ void CUserDisplay::Init() { // 0x5720A0 void CUserDisplay::Process() { + ZoneScoped; + PlaceName.Process(); OnscnTimer.Process(); CurrentVehicle.Process(); diff --git a/source/game_sa/VehicleRecording.cpp b/source/game_sa/VehicleRecording.cpp index 16d3f0eea2..ecda3eb6e3 100644 --- a/source/game_sa/VehicleRecording.cpp +++ b/source/game_sa/VehicleRecording.cpp @@ -61,6 +61,8 @@ void CVehicleRecording::InjectHooks() { // 0x459390 void CVehicleRecording::Init() { + ZoneScoped; + rng::fill(bPlaybackGoingOn, false); rng::fill(bPlaybackPaused, false); rng::fill(pPlaybackBuffer, nullptr); @@ -87,6 +89,8 @@ void CVehicleRecording::ShutDown() { // 0x459F70 hook not needed void CVehicleRecording::Render() { + ZoneScoped; + } // 0x45A360 diff --git a/source/game_sa/VisibilityPlugins.cpp b/source/game_sa/VisibilityPlugins.cpp index 0b6af01ded..89934b1525 100644 --- a/source/game_sa/VisibilityPlugins.cpp +++ b/source/game_sa/VisibilityPlugins.cpp @@ -703,6 +703,8 @@ RpAtomic* CVisibilityPlugins::RenderPlayerCB(RpAtomic* atomic) { // 0x733800 void CVisibilityPlugins::RenderReallyDrawLastObjects() { + ZoneScoped; + RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RWRSTATE(NULL)); RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); @@ -711,9 +713,11 @@ void CVisibilityPlugins::RenderReallyDrawLastObjects() { RwRenderStateSet(rwRENDERSTATEDESTBLEND, RWRSTATE(rwBLENDINVSRCALPHA)); RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(TRUE)); RwRenderStateSet(rwRENDERSTATECULLMODE, RWRSTATE(rwCULLMODECULLNONE)); + SetAmbientColours(); DeActivateDirectional(); RenderOrderedList(m_alphaReallyDrawLastList); + RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(FALSE)); } @@ -984,6 +988,8 @@ RpAtomic* CVisibilityPlugins::RenderWeaponCB(RpAtomic* atomic) { // 0x732F30 void CVisibilityPlugins::RenderWeaponPedsForPC() { + ZoneScoped; + RwRenderStateSet(rwRENDERSTATEZTESTENABLE, RWRSTATE(TRUE)); RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, RWRSTATE(TRUE)); RwRenderStateSet(rwRENDERSTATEFOGENABLE, RWRSTATE(TRUE)); @@ -997,7 +1003,7 @@ void CVisibilityPlugins::RenderWeaponPedsForPC() { ped->SetupLighting(); const CWeapon& activeWeapon = ped->GetActiveWeapon(); RpHAnimHierarchy* pRpAnimHierarchy = GetAnimHierarchyFromSkinClump(ped->m_pRwClump); - const int32 boneID = activeWeapon.m_nType != WEAPON_PARACHUTE ? BONE_R_HAND : BONE_SPINE1; + const int32 boneID = activeWeapon.m_Type != WEAPON_PARACHUTE ? BONE_R_HAND : BONE_SPINE1; int32 animIDIndex = RpHAnimIDGetIndex(pRpAnimHierarchy, boneID); RwMatrix* pRightHandMatrix = &RpHAnimHierarchyGetMatrixArray(pRpAnimHierarchy)[animIDIndex]; { // todo: NOTSA @@ -1008,7 +1014,7 @@ void CVisibilityPlugins::RenderWeaponPedsForPC() { RwFrame* weaponFrame = RpClumpGetFrame(ped->m_pWeaponObject); RwMatrix* weaponRwMatrix = RwFrameGetMatrix(weaponFrame); memcpy(weaponRwMatrix, pRightHandMatrix, sizeof(RwMatrixTag)); - if (activeWeapon.m_nType == WEAPON_PARACHUTE) { + if (activeWeapon.m_Type == WEAPON_PARACHUTE) { static RwV3d rightWeaponTranslate = { 0.1f, -0.15f, 0.0f }; RwMatrixTranslate(weaponRwMatrix, &rightWeaponTranslate, rwCOMBINEPRECONCAT); RwMatrixRotate(weaponRwMatrix, &CPedIK::YaxisIK, 90.0f, rwCOMBINEPRECONCAT); @@ -1017,7 +1023,7 @@ void CVisibilityPlugins::RenderWeaponPedsForPC() { RwFrameUpdateObjects(weaponFrame); RpClumpRender(ped->m_pWeaponObject); eWeaponSkill weaponSkill = ped->GetWeaponSkill(); - if (CWeaponInfo::GetWeaponInfo(activeWeapon.m_nType, weaponSkill)->flags.bTwinPistol) { + if (CWeaponInfo::GetWeaponInfo(activeWeapon.m_Type, weaponSkill)->flags.bTwinPistol) { int32 animIDIndex = RpHAnimIDGetIndex(pRpAnimHierarchy, BONE_L_HAND); RwMatrix* pLeftHandMatrix = &RpHAnimHierarchyGetMatrixArray(pRpAnimHierarchy)[animIDIndex]; memcpy(weaponRwMatrix, pLeftHandMatrix, sizeof(RwMatrixTag)); @@ -1152,3 +1158,7 @@ bool CVisibilityPlugins::VehicleVisibilityCB(RpClump* clump) { bool CVisibilityPlugins::VehicleVisibilityCB_BigVehicle(RpClump* clump) { return FrustumSphereCB(clump); } + +void weaponPedsForPc_Insert(CPed* ped) { + plugin::Call<0x5E46D0>(ped); +} diff --git a/source/game_sa/VisibilityPlugins.h b/source/game_sa/VisibilityPlugins.h index c894f56603..6431da7ad0 100644 --- a/source/game_sa/VisibilityPlugins.h +++ b/source/game_sa/VisibilityPlugins.h @@ -58,9 +58,7 @@ struct tFrameVisibilityPlugin { VALIDATE_SIZE(tFrameVisibilityPlugin, 0x4); // TODO: Probably belongs inside `CVisibilityPlugins` -static void weaponPedsForPc_Insert(CPed* ped) { - plugin::Call<0x5E46D0>(ped); -} +void weaponPedsForPc_Insert(CPed* ped); class CVisibilityPlugins { public: diff --git a/source/game_sa/Wanted.cpp b/source/game_sa/Wanted.cpp index 7a8e70683a..e8fe5596a8 100644 --- a/source/game_sa/Wanted.cpp +++ b/source/game_sa/Wanted.cpp @@ -368,6 +368,8 @@ bool CWanted::IsInPursuit(CCopPed* cop) { // 0x562360 void CWanted::UpdateEachFrame() { + ZoneScoped; + auto playerWanted = FindPlayerWanted(); auto wantedLevel = playerWanted->GetWantedLevel(); diff --git a/source/game_sa/WaterCannons.cpp b/source/game_sa/WaterCannons.cpp index 5ac5d0dfb7..9ed07930bf 100644 --- a/source/game_sa/WaterCannons.cpp +++ b/source/game_sa/WaterCannons.cpp @@ -46,6 +46,8 @@ void CWaterCannons::UpdateOne(uint32 vehicle, CVector* start, CVector* end) { // 0x72A3C0 void CWaterCannons::Update() { + ZoneScoped; + int16 index = 0; for (auto& cannon : aCannons) { cannon.m_Audio.Service(); @@ -58,6 +60,8 @@ void CWaterCannons::Update() { // 0x729B30 void CWaterCannons::Render() { + ZoneScoped; + for (auto& cannon : aCannons) { cannon.Render(); } diff --git a/source/game_sa/WaterCreatureManager_c.cpp b/source/game_sa/WaterCreatureManager_c.cpp index 9c46c9bd09..d5150f7a8d 100644 --- a/source/game_sa/WaterCreatureManager_c.cpp +++ b/source/game_sa/WaterCreatureManager_c.cpp @@ -124,8 +124,9 @@ void WaterCreatureManager_c::TryToExitGroup(WaterCreature_c* pCreature) apCreatures[i]->m_bShouldBeDeleted = true; } -void WaterCreatureManager_c::Update(float fTimestep) -{ +void WaterCreatureManager_c::Update(float fTimestep) { + ZoneScoped; + if (FindPlayerPed(0)->m_pPlayerData->m_nWaterCoverPerc > 50) { const auto nCurTime = CTimer::GetTimeInMS(); diff --git a/source/game_sa/WaterCreature_c.h b/source/game_sa/WaterCreature_c.h index d4f0a764da..d893559dee 100644 --- a/source/game_sa/WaterCreature_c.h +++ b/source/game_sa/WaterCreature_c.h @@ -18,7 +18,7 @@ enum eWaterCreatureType : uint8 { DOLPHIN }; -class WaterCreature_c : public ListItem_c { +class WaterCreature_c : public ListItem_c { public: WaterCreature_c() : ListItem_c(), m_pObject(nullptr) {} ~WaterCreature_c() = default; diff --git a/source/game_sa/WaterLevel.cpp b/source/game_sa/WaterLevel.cpp index 2936d0f7fe..e42f6181a0 100644 --- a/source/game_sa/WaterLevel.cpp +++ b/source/game_sa/WaterLevel.cpp @@ -570,6 +570,8 @@ void CWaterLevel::SplitWaterRectangleAlongYLine(int32 splitAtY, int32 minX, int3 // 0x6EB710 void CWaterLevel::PreRenderWater() { + ZoneScoped; + if (CGame::CanSeeWaterFromCurrArea()) { ScanThroughBlocks(); UpdateFlow(); @@ -659,6 +661,8 @@ bool CWaterLevel::GetWaterDepth(const CVector& vecPos, float* pOutWaterDepth, fl // 0x6E7760 void CWaterLevel::RenderWaterFog() { + ZoneScoped; + plugin::Call<0x6E7760>(); } diff --git a/source/game_sa/Weapon.cpp b/source/game_sa/Weapon.cpp index e4f6442cee..4db2bec494 100644 --- a/source/game_sa/Weapon.cpp +++ b/source/game_sa/Weapon.cpp @@ -8,6 +8,7 @@ #include "Weapon.h" +#include "Glass.h" #include "WeaponInfo.h" #include "CreepingFire.h" #include "BulletInfo.h" @@ -15,31 +16,20 @@ #include "Shadows.h" #include "Birds.h" -float& CWeapon::ms_fExtinguisherAimAngle = *(float*)0x8D610C; -bool& CWeapon::bPhotographHasBeenTaken = *(bool*)0xC8A7C0; -bool& CWeapon::ms_bTakePhoto = *(bool*)0xC8A7C1; -CColModel& CWeapon::ms_PelletTestCol = *(CColModel*)0xC8A7DC; -float& fPlayerAimScale = *(float*)0x8D6110; -float& fPlayerAimScaleDist = *(float*)0x8D6114; -float& fPlayerAimRotRate = *(float*)0x8D6118; -float& SHOTGUN_SPREAD_RATE = *(float*)0x8D611C; -uint32& SHOTGUN_NUM_PELLETS = *(uint32*)0x8D6120; -uint32& SPAS_NUM_PELLETS = *(uint32*)0x8D6124; -float& PELLET_COL_SCALE_RATIO_MULT = *(float*)0x8D6128; -float* fReloadAnimSampleFraction = (float*)0x8D612C; +//float& PELLET_COL_SCALE_RATIO_MULT = *(float*)0x8D6128; // 1.3 void CWeapon::InjectHooks() { RH_ScopedClass(CWeapon); RH_ScopedCategoryGlobal(); RH_ScopedInstall(Constructor, 0x73B430); + RH_ScopedInstall(Shutdown, 0x73A380); RH_ScopedInstall(Reload, 0x73AEB0); RH_ScopedInstall(IsTypeMelee, 0x73B1C0); RH_ScopedInstall(IsType2Handed, 0x73B1E0); RH_ScopedInstall(IsTypeProjectile, 0x73B210); RH_ScopedInstall(HasWeaponAmmoToBeUsed, 0x73B2A0); - RH_ScopedOverloadedInstall(CanBeUsedFor2Player, "", 0x73B240, bool (*)(eWeaponType weaponType)); RH_ScopedInstall(InitialiseWeapons, 0x73A300); RH_ScopedInstall(ShutdownWeapons, 0x73A330); RH_ScopedInstall(UpdateWeapons, 0x73A360); @@ -52,51 +42,56 @@ void CWeapon::InjectHooks() { RH_ScopedInstall(FireSniper, 0x73AAC0); RH_ScopedInstall(TakePhotograph, 0x73C1F0); RH_ScopedInstall(DoDoomAiming, 0x73CDC0); - RH_ScopedInstall(FireInstantHitFromCar2, 0x73CBA0, { .reversed = false }); + RH_ScopedInstall(GenerateDamageEvent, 0x73A530); + RH_ScopedInstall(FireInstantHitFromCar2, 0x73CBA0); + RH_ScopedInstall(Update, 0x73DB40); + RH_ScopedInstall(SetUpPelletCol, 0x73C710, { .reversed = false }); + RH_ScopedInstall(FireAreaEffect, 0x73E800); + RH_ScopedInstall(FireInstantHitFromCar, 0x73EC40, { .reversed = false }); + RH_ScopedInstall(FireFromCar, 0x73FA20); + RH_ScopedInstall(FireInstantHit, 0x73FB10, { .reversed = false }); + RH_ScopedInstall(FireProjectile, 0x741360); + RH_ScopedInstall(DoBulletImpact, 0x73B550); + RH_ScopedInstall(LaserScopeDot, 0x73A8D0); + RH_ScopedInstall(FireM16_1stPerson, 0x741C00); + RH_ScopedInstall(Fire, 0x742300); + RH_ScopedGlobalInstall(DoTankDoomAiming, 0x73D1E0, { .reversed = false }); + RH_ScopedGlobalInstall(DoDriveByAutoAiming, 0x73D720, { .reversed = false }); + RH_ScopedGlobalInstall(FindNearestTargetEntityWithScreenCoors, 0x73E240, { .reversed = false }); + RH_ScopedGlobalInstall(EvaluateTargetForHeatSeekingMissile, 0x73E560); + RH_ScopedGlobalInstall(CheckForShootingVehicleOccupant, 0x73F480, { .reversed = false }); + RH_ScopedGlobalInstall(PickTargetForHeatSeekingMissile, 0x73F910); + RH_ScopedOverloadedInstall(CanBeUsedFor2Player, "Static", 0x73B240, bool(*)(eWeaponType)); + RH_ScopedOverloadedInstall(CanBeUsedFor2Player, "Method", 0x73DEF0, bool(CWeapon::*)()); } // 0x73B430 -CWeapon::CWeapon(eWeaponType weaponType, int32 ammo) { - m_nType = weaponType; - m_nState = eWeaponState::WEAPONSTATE_READY; - m_nAmmoInClip = 0; - m_nTotalAmmo = std::min(ammo, 99999); - +CWeapon::CWeapon(eWeaponType weaponType, uint32 ammo) : + m_Type{ weaponType }, + m_TotalAmmo{ std::min(ammo, 99'999) } +{ Reload(); - - m_nTimeForNextShot = 0; - field_14 = 0; - m_pFxSystem = nullptr; - m_bNoModel = false; -} - -CWeapon* CWeapon::Constructor(eWeaponType weaponType, int32 ammo) { - this->CWeapon::CWeapon(weaponType, ammo); - return this; } // 0x73B4A0 void CWeapon::Initialise(eWeaponType weaponType, int32 ammo, CPed* owner) { - m_nType = weaponType; - m_nState = eWeaponState::WEAPONSTATE_READY; - m_nAmmoInClip = 0; - m_nTotalAmmo = std::min(ammo, 99999); + m_Type = weaponType; + m_State = eWeaponState::WEAPONSTATE_READY; + m_AmmoInClip = 0; + m_TotalAmmo = std::min(ammo, 99999); Reload(owner); - m_nTimeForNextShot = 0; + m_TimeForNextShotMs = 0; - int32 model1 = CWeaponInfo::GetWeaponInfo(weaponType, eWeaponSkill::STD)->m_nModelId1; - int32 model2 = CWeaponInfo::GetWeaponInfo(weaponType, eWeaponSkill::STD)->m_nModelId2; - - if (model1 != -1) - CModelInfo::GetModelInfo(model1)->AddRef(); - - if (model2 != -1) - CModelInfo::GetModelInfo(model2)->AddRef(); + for (const auto m : CWeaponInfo::GetWeaponInfo(m_Type)->GetModels()) { + if (m != MODEL_INVALID) { + CModelInfo::GetModelInfo(m)->AddRef(); + } + } - m_pFxSystem = nullptr; - m_bNoModel = false; + m_FxSystem = nullptr; + m_DontPlaceInHand = false; } // 0x73A300 @@ -124,52 +119,154 @@ void CWeapon::ShutdownWeapons() { // 0x73A380 void CWeapon::Shutdown() { - int32 weaponModelID1 = CWeaponInfo::GetWeaponInfo(m_nType, eWeaponSkill::STD)->m_nModelId1; - int32 weaponModelID2 = CWeaponInfo::GetWeaponInfo(m_nType, eWeaponSkill::STD)->m_nModelId2; - - if (weaponModelID1 != -1) - CModelInfo::GetModelInfo(weaponModelID1)->RemoveRef(); - - if (weaponModelID2 != -1) - CModelInfo::GetModelInfo(weaponModelID2)->RemoveRef(); + for (const auto m : CWeaponInfo::GetWeaponInfo(m_Type)->GetModels()) { + if (m != MODEL_INVALID) { + CModelInfo::GetModelInfo(m)->RemoveRef(); + } + } - m_nType = eWeaponType::WEAPON_UNARMED; - m_nState = eWeaponState::WEAPONSTATE_READY; - m_nTotalAmmo = 0; - m_nAmmoInClip = 0; - m_nTimeForNextShot = 0; + m_Type = eWeaponType::WEAPON_UNARMED; + m_State = eWeaponState::WEAPONSTATE_READY; + m_TotalAmmo = 0; + m_AmmoInClip = 0; + m_TimeForNextShotMs = 0; } // 0x73A3E0 void CWeapon::AddGunshell(CEntity* creator, CVector& position, const CVector2D& direction, float size) { - if (!creator || !creator->GetIsOnScreen()) + if (!creator || !creator->GetIsOnScreen()) { return; + } // originally squared - if (DistanceBetweenPoints(creator->GetPosition(), TheCamera.GetPosition()) > 10.0f) + if ((creator->GetPosition() - TheCamera.GetPosition()).SquaredMagnitude() > sq(10.0f)) { return; + } + // Add gunshell fx particle CVector velocity(direction.x, direction.y, CGeneral::GetRandomNumberInRange(0.4f, 1.6f)); - FxPrtMult_c fxprt(0.5f, 0.5f, 0.5f, 1.0f, size, 1.0f, 1.0f); - - switch (m_nType) { + switch (m_Type) { case eWeaponType::WEAPON_SPAS12_SHOTGUN: case eWeaponType::WEAPON_SHOTGUN: fxprt.SetColor(0.6f, 0.1f, 0.1f); } - g_fx.m_GunShell->AddParticle(&position, &velocity, 0.0f, &fxprt, -1.0f, 1.2f, 0.6f, 0); } // 0x73A530 -void CWeapon::GenerateDamageEvent(CPed* victim, CEntity* creator, eWeaponType weaponType, int32 damageFactor, ePedPieceTypes pedPiece, int32 direction) { - plugin::Call<0x73A530, CPed*, CEntity*, eWeaponType, int32, ePedPieceTypes, int32>(victim, creator, weaponType, damageFactor, pedPiece, direction); +bool CWeapon::GenerateDamageEvent(CPed* victim, CEntity* creator, eWeaponType weaponType, int32 damageFactor, ePedPieceTypes pedPiece, uint8 direction) { + CPedDamageResponseCalculator pedDmgRespCalc{ + creator, + (float)damageFactor, + weaponType, + pedPiece, + false + }; + + CEventDamage eventDmg{ + creator, + CTimer::GetTimeInMS(), + weaponType, + pedPiece, + direction, + false, + victim->bInVehicle + }; + + if ( victim->m_fHealth <= 0.f + && CLocalisation::Blood() + && CLocalisation::KickingWhenDown() + && victim->GetTaskManager().GetSimplestActiveTask()->GetTaskType() == TASK_SIMPLE_DEAD + ) { + const auto floorHitAnim = CAnimManager::BlendAnimation( + victim->m_pRwClump, + ANIM_GROUP_DEFAULT, + RpAnimBlendClumpGetFirstAssociation(victim->m_pRwClump, ANIMATION_800) ? ANIM_ID_FLOOR_HIT_F : ANIM_ID_FLOOR_HIT + ); + if (floorHitAnim) { + floorHitAnim->Start(); + } + return true; + } + + if (!victim->IsAlive()) { + return true; + } + + if (!eventDmg.AffectsPed(victim)) { + return false; + } + + if (creator == FindPlayerPed()) { + CCrime::ReportCrime(CRIME_DAMAGED_PED, victim, static_cast(creator)); + } + + pedDmgRespCalc.ComputeDamageResponse( + victim, + eventDmg.m_damageResponse, + true + ); + + bool ret = true; + if ((CWeaponInfo::GetWeaponInfo(weaponType)->m_nWeaponFire == eWeaponFire::WEAPON_FIRE_MELEE || weaponType == WEAPON_FALL && creator && creator->GetType() == ENTITY_TYPE_OBJECT) && !victim->bInVehicle) { + eventDmg.ComputeAnim(victim, true); + switch (eventDmg.m_nAnimID) { + case ANIM_ID_SHOT_PARTIAL: + case ANIM_ID_SHOT_LEFTP: + case ANIM_ID_SHOT_PARTIAL_B: + case ANIM_ID_SHOT_RIGHTP: { //> 0x73A769 - Inverted + auto anim = RpAnimBlendClumpGetAssociation(victim->m_pRwClump, eventDmg.m_nAnimID); + if (!anim) { + anim = CAnimManager::AddAnimation( + victim->m_pRwClump, + (AssocGroupId)eventDmg.m_nAnimGroup, + (AnimationId)eventDmg.m_nAnimID + ); + } + anim->m_fBlendAmount = 0.f; + anim->m_fBlendDelta = eventDmg.m_fAnimBlend; + anim->m_fSpeed = eventDmg.m_fAnimSpeed; + anim->Start(); + break; + } + case ANIM_ID_NO_ANIMATION_SET: + break; + case ANIM_ID_DOOR_LHINGE_O: + ret = false; + break; + default: { //< 0x73A7B5 + const auto a = CAnimManager::BlendAnimation( + victim->m_pRwClump, + (AssocGroupId)eventDmg.m_nAnimGroup, + (AnimationId)eventDmg.m_nAnimID, + eventDmg.m_fAnimBlend + ); + a->m_fSpeed = eventDmg.m_fAnimSpeed; + a->SetFlag(ANIMATION_STARTED); + break; + } + } + } + bool isStealth = false; + if (creator && creator->IsPed()) { + const auto pcreator = creator->AsPed(); + if (pcreator->GetTaskManager().GetActiveTask()->GetTaskType() == TASK_SIMPLE_STEALTH_KILL || weaponType == WEAPON_PISTOL_SILENCED) { + isStealth = true; + } + } + eventDmg.m_b05 = isStealth; + if (!victim->bInVehicle || victim->m_fHealth <= 0.f || !victim->GetTaskManager().GetActiveTask() || victim->GetTaskManager().GetActiveTask()->GetTaskType() != TASK_SIMPLE_GANG_DRIVEBY) { + victim->GetEventGroup().Add(eventDmg); + } + return ret; + } // 0x73A8D0 bool CWeapon::LaserScopeDot(CVector* outCoord, float* outSize) { - return plugin::CallMethodAndReturn(this, outCoord, outSize); + /* UNUSED */ + NOTSA_UNREACHABLE(); } // 0x73AAC0 @@ -203,7 +300,7 @@ bool CWeapon::FireSniper(CPed* shooter, CEntity* victim, CVector* target) { velocity.Normalise(); velocity *= 16.0f; - CBulletInfo::AddBullet(shooter, m_nType, activeCam.m_vecSource, velocity); + CBulletInfo::AddBullet(shooter, m_Type, activeCam.m_vecSource, velocity); // recoil effect for players if (shooter->IsPlayer()) { @@ -221,7 +318,7 @@ bool CWeapon::FireSniper(CPed* shooter, CEntity* victim, CVector* target) { } CVector targetPoint = velocity * 40.0f + activeCam.m_vecSource; - bool hasNoSound = m_nType == eWeaponType::WEAPON_PISTOL_SILENCED || m_nType == eWeaponType::WEAPON_TEARGAS; + bool hasNoSound = m_Type == eWeaponType::WEAPON_PISTOL_SILENCED || m_Type == eWeaponType::WEAPON_TEARGAS; CEventGroup* eventGroup = GetEventGlobalGroup(); CEventGunShot gs(shooter, activeCam.m_vecSource, targetPoint, hasNoSound); @@ -237,22 +334,22 @@ bool CWeapon::FireSniper(CPed* shooter, CEntity* victim, CVector* target) { // 0x73AEB0 void CWeapon::Reload(CPed* owner) { - if (!m_nTotalAmmo) + if (!m_TotalAmmo) { return; + } uint32 ammo = GetWeaponInfo(owner).m_nAmmoClip; - m_nAmmoInClip = std::min(ammo, m_nTotalAmmo); + m_AmmoInClip = std::min(ammo, m_TotalAmmo); } // 0x73B1C0 bool CWeapon::IsTypeMelee() { - auto weaponInfo = CWeaponInfo::GetWeaponInfo(m_nType, eWeaponSkill::STD); - return weaponInfo->m_nWeaponFire == eWeaponFire::WEAPON_FIRE_MELEE; + return GetWeaponInfo().m_nWeaponFire == eWeaponFire::WEAPON_FIRE_MELEE; } // 0x73B1E0 bool CWeapon::IsType2Handed() { - switch (m_nType) { + switch (m_Type) { case eWeaponType::WEAPON_M4: case eWeaponType::WEAPON_AK47: case eWeaponType::WEAPON_SPAS12_SHOTGUN: @@ -262,13 +359,12 @@ bool CWeapon::IsType2Handed() { case eWeaponType::WEAPON_COUNTRYRIFLE: return true; } - return false; } // 0x73B210 bool CWeapon::IsTypeProjectile() { - switch (m_nType) { + switch (m_Type) { case eWeaponType::WEAPON_GRENADE: case eWeaponType::WEAPON_REMOTE_SATCHEL_CHARGE: case eWeaponType::WEAPON_TEARGAS: @@ -276,7 +372,6 @@ bool CWeapon::IsTypeProjectile() { case eWeaponType::WEAPON_FREEFALL_BOMB: return true; } - return false; } @@ -289,13 +384,12 @@ bool CWeapon::CanBeUsedFor2Player(eWeaponType weaponType) { case eWeaponType::WEAPON_PARACHUTE: return false; } - return true; } // 0x73B2A0 bool CWeapon::HasWeaponAmmoToBeUsed() { - switch (m_nType) { + switch (m_Type) { case eWeaponType::WEAPON_UNARMED: case eWeaponType::WEAPON_BRASSKNUCKLE: case eWeaponType::WEAPON_GOLFCLUB: @@ -312,8 +406,7 @@ bool CWeapon::HasWeaponAmmoToBeUsed() { case eWeaponType::WEAPON_PARACHUTE: return true; } - - return m_nTotalAmmo != 0; + return m_TotalAmmo != 0; } // 0x73B300 @@ -321,28 +414,28 @@ bool CWeapon::ProcessLineOfSight(const CVector& startPoint, const CVector& endPo bool buildings, bool vehicles, bool peds, bool objects, bool dummies, bool arg11, bool doIgnoreCameraCheck) { CBirds::HandleGunShot(&startPoint, &endPoint); CShadows::GunShotSetsOilOnFire(startPoint, endPoint); - return CWorld::ProcessLineOfSight(startPoint, endPoint, outColPoint, outEntity, buildings, vehicles, peds, objects, dummies, false, doIgnoreCameraCheck, true); } // 0x73B360 void CWeapon::StopWeaponEffect() { - if (!m_pFxSystem || m_nType == eWeaponType::WEAPON_MOLOTOV) - return; - - m_pFxSystem->Kill(); - m_pFxSystem = nullptr; + if (m_FxSystem && m_Type != WEAPON_MOLOTOV) { + m_FxSystem->Kill(); + m_FxSystem = nullptr; + } } // 0x73B380 float CWeapon::TargetWeaponRangeMultiplier(CEntity* victim, CEntity* weaponOwner) { - if (!victim || !weaponOwner) + if (!victim || !weaponOwner) { return 1.0f; + } switch (victim->m_nType) { case ENTITY_TYPE_VEHICLE: { - if (!victim->AsVehicle()->IsBike()) + if (!victim->AsVehicle()->IsBike()) { return 3.0f; + } break; } case ENTITY_TYPE_PED: { @@ -353,15 +446,18 @@ float CWeapon::TargetWeaponRangeMultiplier(CEntity* victim, CEntity* weaponOwner } if (CEntity* attachedTo = pedVictim->m_pAttachedTo) { - if (attachedTo->IsVehicle() && !attachedTo->AsVehicle()->IsBike()) + if (attachedTo->IsVehicle() && !attachedTo->AsVehicle()->IsBike()) { return 3.0f; + } } + break; } } - if (!weaponOwner->IsPed() || !weaponOwner->AsPed()->IsPlayer()) + if (!weaponOwner->IsPed() || !weaponOwner->AsPed()->IsPlayer()) { return 1.0f; + } switch (CCamera::GetActiveCamera().m_nMode) { case MODE_TWOPLAYER_IN_CAR_AND_SHOOTING: @@ -374,17 +470,276 @@ float CWeapon::TargetWeaponRangeMultiplier(CEntity* victim, CEntity* weaponOwner } // 0x73B550 -void CWeapon::DoBulletImpact(CEntity* owner, CEntity* victim, CVector* startPoint, CVector* endPoint, CColPoint* colPoint, int32 arg5) { - plugin::CallMethod<0x73B550, CWeapon*, CEntity*, CEntity*, CVector*, CVector*, CColPoint*, int32>(this, owner, victim, startPoint, endPoint, colPoint, arg5); +void CWeapon::DoBulletImpact(CEntity* firedBy, CEntity* victim, const CVector& startPoint, const CVector& endPoint, const CColPoint& hitCP, int32 incrementalHit) { + const auto firedByPed = firedBy->IsPed() + ? firedBy->AsPed() + : nullptr; + const auto firedByPlayer = firedByPed && firedByPed->IsPlayer() + ? firedByPed->AsPlayer() + : nullptr; + + const auto wi = &GetWeaponInfo(firedByPed); + + if (firedByPed && firedByPed->IsPlayer()) { + CCrime::ReportCrime(CRIME_FIRE_WEAPON, victim, firedByPed); + } + + if (victim) { // Inverted + CBulletTraces::AddTrace( // 0x73B60C + incrementalHit + ? startPoint + (hitCP.m_vecPoint - startPoint) * 0.4f + : startPoint, + hitCP.m_vecPoint, + GetType(), + firedBy + ); + + const auto DoBulletHitFx = [&] { + if (incrementalHit <= 0) { + if ((endPoint - startPoint).Dot(hitCP.m_vecNormal) < 0.f) { // Normal is opposite to that of the bullet's direction + AudioEngine.ReportBulletHit( + victim, + hitCP.m_nSurfaceTypeB, + hitCP.m_vecPoint, + RWRAD2DEG((float)std::asin(-incrementalHit)) + ); + } + } + }; + +#ifdef FIX_BUGS + if (firedByPlayer && (firedByPlayer != victim || firedBy->GetStatus() == STATUS_PLAYER)) { // 0x73B6D0 +#else + if (firedByPlayer && firedByPlayer != victim || firedBy->GetStatus() == STATUS_PLAYER) { // 0x73B6D0 +#endif + if (notsa::contains({ ENTITY_TYPE_PED, ENTITY_TYPE_VEHICLE, ENTITY_TYPE_OBJECT }, victim->GetType())) { + if (CStats::GetStatValue(STAT_BULLETS_FIRED) >= CStats::GetStatValue(STAT_BULLETS_THAT_HIT)) { + CStats::IncrementStat(STAT_BULLETS_THAT_HIT); + } + } + if (CWeaponInfo::WeaponHasSkillStats(GetType())) { // 0x73B738 + // Redundant `if` check here was removed + if ([&]{ + const auto victimEntity = notsa::coalesce(firedByPlayer->m_pTargetedObject, victim); + + // NOTE: The code is written upside down to make the controlflow easier + + if (victimEntity->IsPed() && CPedGroups::AreInSameGroup(victimEntity->AsPed(), firedByPed)) { + return false; + } + + switch (victimEntity->GetType()) { + case eEntityType::ENTITY_TYPE_PED: { + const auto victimPed = victimEntity->AsPed(); + return !CPedGroups::AreInSameGroup(victimPed, firedByPed) && victimPed->m_fHealth > 0.f; + } + case eEntityType::ENTITY_TYPE_VEHICLE: { + const auto victimVeh = victimEntity->AsVehicle(); + if (notsa::contains(eCarPiece_WheelPieces, (eCarPiece)hitCP.m_nPieceTypeB)) { + if (!victimVeh->BurstTyre(hitCP.m_nPieceTypeB, true)) { + return false; + } + } + if (victimVeh->physicalFlags.bBulletProof || !victimVeh->vehicleFlags.bCanBeDamaged) { + return false; + } + if (victimVeh->m_fHealth <= 0.f || victimVeh->GetStatus() == STATUS_WRECKED) { + return false; + } + return true; + } + case eEntityType::ENTITY_TYPE_OBJECT: { + const auto victimObj = victimEntity->AsObject(); + return victimObj->m_fHealth > 0.f && victimObj->m_nColDamageEffect && victimObj->m_pObjectInfo->m_fColDamageMultiplier >= 0.5f; + } + } + return false; + }()) { + CStats::UpdateStatsWhenWeaponHit(GetType()); + } + + } + } + + if (!victim->IsPed()) { // 0x73B85B + CGlass::WasGlassHitByBullet(victim, hitCP.m_vecPoint); + + const auto DoBulletImpactFx = [&] { + if (TheCamera.IsSphereVisible(hitCP.m_vecPoint, 1.f)) { + g_fx.AddBulletImpact( + &hitCP.m_vecPoint, + &hitCP.m_vecNormal, + hitCP.m_nSurfaceTypeB, + incrementalHit ? 2 : 8, + hitCP.m_nLightingA.GetCurrentLighting() + ); + } + }; + + switch (victim->GetType()) { + case eEntityType::ENTITY_TYPE_BUILDING: { // 0x73C014 + DoBulletImpactFx(); + if (firedByPlayer) { + firedByPlayer->m_pPlayerData->m_nModelIndexOfLastBuildingShot = victim->m_nModelIndex; + } + break; + } + case eEntityType::ENTITY_TYPE_VEHICLE: { // 0x73BD2A + const auto victimVeh = victim->AsVehicle(); + if (!notsa::contains(eCarPiece_WheelPieces, (eCarPiece)hitCP.m_nPieceTypeB)) { + victimVeh->InflictDamage( + firedBy, + GetType(), + (float)(firedByPlayer && TheCamera.GetActiveCam().m_nMode == MODE_TWOPLAYER_IN_CAR_AND_SHOOTING ? 2 * wi->m_nDamage : wi->m_nDamage), + hitCP.m_vecPoint + ); + DoBulletImpactFx(); + if (g_LoadMonitor.m_bForceProcLevel != CLoadMonitor::EProcessingLevel::HIGH) { // 0x73BF6C - NOTE/TODO: Useless, remove + const auto wepForceMult = [this]{ + switch (GetType()) { + case WEAPON_DESERT_EAGLE: + case WEAPON_MINIGUN: + return -20.f; + case WEAPON_SHOTGUN: + case WEAPON_SPAS12_SHOTGUN: + return -4.0f; + default: + return -10.f; + } + }(); + victimVeh->ApplyForce( + hitCP.m_vecNormal * (wepForceMult * std::min(1.f, victimVeh->m_fMass / 1000.f)), + hitCP.m_vecPoint - victimVeh->GetPosition(), + true + ); + } + } else { // 0x73BD3F + victimVeh->BurstTyre(hitCP.m_nPieceTypeB, true); + g_fx.AddTyreBurst(hitCP.m_vecPoint, hitCP.m_vecNormal); + if (firedByPed) { // Add event to occupants + const auto AddEventVehicleDamageWeapon = [&](CPed* ped) { + if (ped) { + ped->GetEventGroup().Add(CEventVehicleDamageWeapon{ + victimVeh, + firedBy, + GetType() + }); + } + }; + AddEventVehicleDamageWeapon(victimVeh->m_pDriver); + rng::for_each(victimVeh->GetPassengers(), AddEventVehicleDamageWeapon); + } + } + break; + } + case eEntityType::ENTITY_TYPE_OBJECT: { // 0x73BB4F + const auto victimObj = victim->AsObject(); + const auto oinfo = victimObj->m_pObjectInfo; + + DoBulletImpactFx(); + if (victimObj->m_nColDamageEffect < 200) { + if (!victimObj->physicalFlags.bDisableCollisionForce && oinfo->m_fColDamageMultiplier < 99.9f) { + if (victimObj->IsStatic() && oinfo->m_fUprootLimit <= 0.f) { + victimObj->SetIsStatic(false); + victimObj->AddToMovingList(); + } + if (!victimObj->IsStatic()) { // 0x73BC6B - Move the object a little + float force = -2.f; + if (victimObj->physicalFlags.bDisableZ || victimObj->physicalFlags.bDisableMoveForce) { + force *= 0.1f; + } + if (incrementalHit) { + force *= 0.2f; + } + victimObj->ApplyForce( + hitCP.m_vecNormal * force, + hitCP.m_vecPoint - victimObj->GetPosition(), + true + ); + } + } + } else { // 0x73BB94 + victimObj->ObjectDamage( + [&]{ + switch (oinfo->m_nGunBreakMode) { + case 1: return 151.f; + case 2: return 151.f * oinfo->m_fSmashMultiplier; + default: return 50.f; + } + }(), + &hitCP.m_vecPoint, + &hitCP.m_vecNormal, + firedBy, + GetType() + ); + } + + break; + } + } + DoBulletHitFx(); + } else if (victim != firedBy) { + const auto victimPed = victim->AsPed(); + if ( !firedByPed + || firedByPed->m_nPedType != victimPed->m_nPedType + || notsa::contains({ PED_TYPE_CIVMALE, PED_TYPE_CIVFEMALE }, victimPed->m_nPedType) + || firedByPlayer + ) { + const auto bAddBloodFx = [&]{ + if (incrementalHit > 0) { // 0x73B8B8 + return false; + } + DoBulletHitFx(); + return GenerateDamageEvent( + victimPed, + firedBy, + GetType(), + [&] { + if (firedByPlayer + && (victim->GetPosition() - startPoint).SquaredMagnitude() <= 1.f + && !victimPed->bNoCriticalHits + && !notsa::contains({ WEAPON_SHOTGUN, WEAPON_SPAS12_SHOTGUN }, GetType()) + ) { + return 150; + } + return incrementalHit < 0 + ? -(incrementalHit * (int32)wi->m_nDamage) + : (int32)wi->m_nDamage; + }(), + (ePedPieceTypes)hitCP.m_nSurfaceTypeB, + victimPed->GetLocalDirection(startPoint - victimPed->GetPosition2D()) + ); + }(); + if (firedByPlayer) { // 0x73BA79 + CCrime::ReportCrime(CRIME_DAMAGE_CAR, victim, firedByPed); + } + if (CLocalisation::Blood() && bAddBloodFx) { // 0x73BA81 + g_fx.AddBlood( + hitCP.m_vecPoint, + hitCP.m_vecNormal, + hitCP.m_nPieceTypeB == ePedPieceTypes::PED_PIECE_HEAD + ? victimPed->m_fHealth <= 0.f ? 32 : 16 + : incrementalHit ? 4 : 8, + victimPed->m_fContactSurfaceBrightness + ); + } + } + } + } else { + CBulletTraces::AddTrace(startPoint, endPoint, GetType(), firedBy); + } + + // 0x73C11B [Moved down here] + if (firedByPed && firedByPed->IsPlayer()) { // 0x73C14B + firedByPed->AsPlayer()->GetPadFromPlayer()->StartShake_Distance( + 240, + 128, + FindPlayerPed()->GetPosition() + ); + } } -/*! -* @addr 0x73C1F0 -* @brief Marks all peds and objects that are in range (125 units) and in frame (on the screen - 0.1 relative border) as photographed. -* -* @param owner Camera owner - unused. -* @param point Pos of the camflash effect -*/ +// 0x73C1F0 bool CWeapon::TakePhotograph(CEntity* owner, CVector* point) { UNUSED(owner); @@ -491,42 +846,30 @@ void CWeapon::SetUpPelletCol(int32 numPellets, CEntity* owner, CEntity* victim, // 0x73CBA0 void CWeapon::FireInstantHitFromCar2(CVector startPoint, CVector endPoint, CVehicle* vehicle, CEntity* owner) { - return plugin::CallMethod<0x73CBA0, CWeapon*, CVector, CVector, CVehicle*, CEntity*>(this, startPoint, endPoint, vehicle, owner); - - const auto player = FindPlayerPed(); - CWeaponInfo::GetWeaponInfo(m_nType, eWeaponSkill::STD); - CCrime::ReportCrime(CRIME_FIRE_WEAPON, player, player); - - CEntity* entity; - if (owner) { - entity = owner; - } else { - entity = vehicle; - } - CEventGunShot event(entity, startPoint, endPoint, m_nType == WEAPON_PISTOL_SILENCED || m_nType == WEAPON_TEARGAS); - GetEventGlobalGroup()->Add(&event); + CCrime::ReportCrime(CRIME_FIRE_WEAPON, FindPlayerPed(), FindPlayerPed()); + + GetEventGlobalGroup()->Add(CEventGunShot{ + notsa::coalesce(owner, vehicle), + startPoint, + endPoint, + notsa::contains({WEAPON_PISTOL_SILENCED, WEAPON_TEARGAS}, GetType()) + }); g_InterestingEvents.Add(CInterestingEvents::EType::INTERESTING_EVENT_22, owner); - CVector direction{}; - CPointLights::AddLight(PLTYPE_POINTLIGHT, startPoint, direction, 3.0f, 0.25f, 0.22f, 0.0f, 0, false, nullptr); + CPointLights::AddLight(PLTYPE_POINTLIGHT, startPoint, {}, 3.0f, 0.25f, 0.22f, 0.0f, 0, false, nullptr); CWorld::bIncludeBikers = true; CWorld::pIgnoreEntity = vehicle; CBirds::HandleGunShot(&startPoint, &endPoint); CShadows::GunShotSetsOilOnFire(startPoint, endPoint); CEntity* victim{}; - CColPoint outColPoint{}; - CWorld::ProcessLineOfSight(&startPoint, &endPoint, outColPoint, victim, true, true, true, true, true, false, false, true); + CColPoint cpImpact{}; + CWorld::ProcessLineOfSight(&startPoint, &endPoint, cpImpact, victim, true, true, true, true, true, false, false, true); CWorld::ResetLineTestOptions(); - DoBulletImpact(owner, victim, &startPoint, &endPoint, &outColPoint, 0); + DoBulletImpact(owner, victim, startPoint, endPoint, cpImpact, 0); } -/*! -* @addr 0x73CDC0 -* @brief Find closest entity in range that is visible to `owner` (Eg.: Is in [-PI/8, PI/8] angle) and modify `end->z` to be pointing at it. idk.. -* -* @param end out Z axis is modified -*/ +// 0x73CDC0 void CWeapon::DoDoomAiming(CEntity* owner, CVector* start, CVector* end) { int16 inRangeCount{}; std::array objInRange{}; @@ -588,11 +931,101 @@ void CWeapon::DoDriveByAutoAiming(CEntity* owner, CVehicle* vehicle, CVector* st // 0x73DB40 void CWeapon::Update(CPed* owner) { - plugin::CallMethod<0x73DB40, CWeapon*, CPed*>(this, owner); + const auto wi = &GetWeaponInfo(owner); + const auto ao = &wi->GetAimingOffset(); + + const auto ProcessReloadAudioIf = [&](auto Pred) { + const auto ProcessOne = [&](uint32 delay, eAudioEvents ae) { + if (Pred(delay, ae)) { + owner->m_weaponAudio.AddAudioEvent(ae); + } + }; + ProcessOne(owner->bIsDucking ? ao->CrouchRLoadA : ao->RLoadA, AE_WEAPON_RELOAD_A); + ProcessOne(owner->bIsDucking ? ao->CrouchRLoadB : ao->RLoadB, AE_WEAPON_RELOAD_B); + }; + + switch (m_State) { + case WEAPONSTATE_FIRING: { + if (owner && notsa::contains({ WEAPON_SPAS12_SHOTGUN, WEAPON_SHOTGUN }, m_Type)) { // 0x73DBA5 + ProcessReloadAudioIf([&](uint32 rload, eAudioEvents ae) { + if (!rload) { + return false; + } + const auto nextShotEnd = m_TimeForNextShotMs + rload; + return CTimer::GetPreviousTimeInMS() < nextShotEnd && CTimer::GetTimeInMS() >= nextShotEnd; + }); + } + if (CTimer::GetTimeInMS() > m_TimeForNextShotMs) { + m_State = wi->m_nWeaponFire == eWeaponFire::WEAPON_FIRE_MELEE || m_TotalAmmo != 0 + ? eWeaponState::WEAPONSTATE_READY + : eWeaponState::WEAPONSTATE_OUT_OF_AMMO; + } + break; + } + case WEAPONSTATE_RELOADING: { + if (owner && m_Type < WEAPON_LAST_WEAPON) { + const auto DoPlayAnimlessReloadAudio = [&] { + ProcessReloadAudioIf([ + &, + shootDelta = m_TimeForNextShotMs - wi->GetWeaponReloadTime() + ](uint32 rload, eAudioEvents ae) { + const auto audioTimeMs = rload + shootDelta; + return CTimer::GetPreviousTimeInMS() < audioTimeMs && CTimer::GetTimeInMS() >= audioTimeMs; + }); + }; + if (wi->flags.bReload && (!owner->IsPlayer() || !FindPlayerInfo().m_bFastReload)) { // 0x73DCCE + auto animRLoad = RpAnimBlendClumpGetAssociation( + owner->m_pRwClump, + ANIM_ID_RELOAD //(wi->m_nFlags & 0x1000) != 0 ? ANIM_ID_RELOAD : ANIM_ID_WALK // Always going to be `ANIM_ID_RELOAD` + ); + if (!animRLoad) { + animRLoad = RpAnimBlendClumpGetAssociation(owner->m_pRwClump, wi->GetCrouchReloadAnimationID()); + } + if (animRLoad) { // 0x73DD30 + ProcessReloadAudioIf([&](uint32 rloadMs, eAudioEvents ae) { + const auto rloadS = (float)rloadMs / 1000.f; + return rloadS <= animRLoad->m_fCurrentTime && animRLoad->m_fCurrentTime - animRLoad->m_fTimeStep < rloadS; + }); + if (CTimer::GetTimeInMS() > m_TimeForNextShotMs) { + if (animRLoad->GetTimeProgress() < 0.9f) { + m_TimeForNextShotMs = CTimer::GetTimeInMS(); + } + } + } else if (owner->GetIntelligence()->GetTaskUseGun()) { // 0x73DDF9 + if (CTimer::GetTimeInMS() > m_TimeForNextShotMs) { + m_TimeForNextShotMs = CTimer::GetTimeInMS(); + } + } else { // 0x73DE16 + DoPlayAnimlessReloadAudio(); + } + } else { + DoPlayAnimlessReloadAudio(); + } + } + //> 0x73DEA4 + if (CTimer::GetTimeInMS() > m_TimeForNextShotMs) { + Reload(owner); + m_State = WEAPONSTATE_READY; + } + StopWeaponEffect(); + break; + } + case WEAPONSTATE_MELEE_MADECONTACT: { + m_State = WEAPONSTATE_READY; + StopWeaponEffect(); + break; + } + default: { + StopWeaponEffect(); + break; + } + } } // 0x73A360 void CWeapon::UpdateWeapons() { + ZoneScoped; + CShotInfo::Update(); CExplosion::Update(); CProjectileInfo::Update(); @@ -601,62 +1034,200 @@ void CWeapon::UpdateWeapons() { // 0x73DEF0 bool CWeapon::CanBeUsedFor2Player() { - return CanBeUsedFor2Player(m_nType); + return CanBeUsedFor2Player(m_Type); } // 0x73E240 -void CWeapon::FindNearestTargetEntityWithScreenCoors(float screenX, float screenY, float range, CVector point, float* outX, float* outY) { - plugin::Call<0x73E240, float, float, float, CVector, float*, float*>(screenX, screenY, range, point, outX, outY); +CEntity* CWeapon::FindNearestTargetEntityWithScreenCoors(float screenX, float screenY, float range, CVector point, float* outScrX, float* outScrY) { + screenX = (screenX + 1.f) * SCREEN_WIDTH / 2.f; + screenY = (screenY + 1.f) * SCREEN_HEIGHT / 2.f; + + float closestScrDistSq = sq(SCREEN_WIDTH / 15.f); + CEntity* closest{}; + const auto ProcessEntity = [&](CEntity* e) { + const auto epos = e->GetPosition(); + + CVector scrPos{}; + CVector scrSz{}; + if (!CSprite::CalcScreenCoors(epos, &scrPos, &scrSz.x, &scrSz.y, true, true)) { + return; + } + const auto scrDistSq = (CVector2D{ scrPos } - CVector2D{screenX, screenY}).SquaredMagnitude(); + if (scrDistSq >= closestScrDistSq) { + return; + } + if (sq(range) <= (point - epos).SquaredMagnitude()) { + return; + } + closestScrDistSq = scrDistSq; + closest = e; + + if (outScrX && outScrY) { + *outScrX = scrPos.x / (SCREEN_WIDTH / 2.f) - 1.f; + *outScrY = scrPos.y / (SCREEN_HEIGHT / 2.f) - 1.f; + } + }; + + for (auto& ped : GetPedPool()->GetAllValid()) { + if (ped.IsStateDead() || ped.bInVehicle) { + continue; + } + if (!CDarkel::ThisPedShouldBeKilledForFrenzy(ped)) { + continue; + } + ProcessEntity(&ped); + } + + for (auto& veh : GetVehiclePool()->GetAllValid()) { + if (&veh == FindPlayerVehicle()) { + continue; + } + if (!CDarkel::ThisVehicleShouldBeKilledForFrenzy(veh)) { + continue; + } + ProcessEntity(&veh); + } + + return closest; +} + +// notsa +auto CWeapon::GetProjectileType() { + switch (GetType()) { + case eWeaponType::WEAPON_RLAUNCHER: + return eWeaponType::WEAPON_ROCKET; + case eWeaponType::WEAPON_RLAUNCHER_HS: + return eWeaponType::WEAPON_ROCKET_HS; + case WEAPON_GRENADE: + case WEAPON_TEARGAS: + case WEAPON_MOLOTOV: + case WEAPON_REMOTE_SATCHEL_CHARGE: + return GetType(); + default: + NOTSA_UNREACHABLE(); + } } // 0x73E560 -float CWeapon::EvaluateTargetForHeatSeekingMissile(CEntity* entity, CVector& posn, CVector& direction, float distanceMultiplier, bool fromVehicle, CEntity* lastEntity) { - return plugin::CallAndReturn(entity, posn, direction, distanceMultiplier, fromVehicle, lastEntity); +float CWeapon::EvaluateTargetForHeatSeekingMissile(CEntity* potentialTarget, const CVector& origin, const CVector& aimingDir, float tolerance, bool arePlanesPriority, CEntity* preferredExistingTarget) { + const auto potentialTargetDist = (origin - potentialTarget->GetPosition()).Magnitude(); + + const auto lineDir = aimingDir * 250.f; + const auto potentialTargetDistToLine = CCollision::DistToLine(origin, origin + aimingDir, potentialTarget->GetPosition()); + + auto ret = std::sqrt(potentialTargetDist) / 10.f + potentialTargetDistToLine / potentialTargetDist; + + if (potentialTargetDistToLine * tolerance >= potentialTargetDist) { + return -1.f; + } + + if (arePlanesPriority) { + if (potentialTarget->IsVehicle() && notsa::contains({ VEHICLE_TYPE_PLANE, VEHICLE_TYPE_HELI }, potentialTarget->AsVehicle()->m_nVehicleSubType)) { + ret *= 0.25f; + } + } + + if (preferredExistingTarget && preferredExistingTarget == potentialTarget) { + ret *= 0.25f; + } + + return ret; } // 0x73E690 -void CWeapon::DoWeaponEffect(CVector origin, CVector target) { - char fxName[32]{}; - switch (m_nType) { - case eWeaponType::WEAPON_FLAMETHROWER: - strcpy_s(fxName, "flamethrower"); - break; - case eWeaponType::WEAPON_EXTINGUISHER: - strcpy_s(fxName, "extinguisher"); - break; - case eWeaponType::WEAPON_SPRAYCAN: - strcpy_s(fxName, "spraycan"); - break; - default: - return StopWeaponEffect(); +void CWeapon::DoWeaponEffect(CVector origin, CVector dir) { + const char* fxName{}; + switch (m_Type) { + case eWeaponType::WEAPON_FLAMETHROWER: fxName = "flamethrower"; break; + case eWeaponType::WEAPON_EXTINGUISHER: fxName = "extinguisher"; break; + case eWeaponType::WEAPON_SPRAYCAN: fxName = "spraycan"; break; + default: StopWeaponEffect(); return; } - RwMatrix* mat = RwMatrixCreate(); - g_fx.CreateMatFromVec(mat, &origin, &target); + const auto mat = RwMatrixCreate(); + g_fx.CreateMatFromVec(mat, &origin, &dir); - if (m_pFxSystem) { - m_pFxSystem->SetMatrix(mat); + if (m_FxSystem) { + m_FxSystem->SetMatrix(mat); } else { CVector posn{}; - m_pFxSystem = g_fxMan.CreateFxSystem(fxName, &posn, mat, false); + m_FxSystem = g_fxMan.CreateFxSystem(fxName, &posn, mat, false); - if (!m_pFxSystem) { + if (!m_FxSystem) { RwMatrixDestroy(mat); return; } - m_pFxSystem->CopyParentMatrix(); - m_pFxSystem->Play(); - m_pFxSystem->SetMustCreatePrts(true); + m_FxSystem->CopyParentMatrix(); + m_FxSystem->Play(); + m_FxSystem->SetMustCreatePrts(true); } - m_pFxSystem->SetConstTime(1, 1.0f); + m_FxSystem->SetConstTime(1, 1.0f); RwMatrixDestroy(mat); } // 0x73E800 -bool CWeapon::FireAreaEffect(CEntity* firingEntity, CVector* origin, CEntity* targetEntity, CVector* target) { - return plugin::CallMethodAndReturn(this, firingEntity, origin, targetEntity, target); +bool CWeapon::FireAreaEffect(CEntity* firingEntity, const CVector& origin, CEntity* targetEntity, CVector* target) { + const auto wi = &GetWeaponInfo(); // TODO/NOTE: Why not `GetWeaponInfo(firingEntity)`? + const auto [shotDir, shotPt] = [&]() -> std::pair { + if (!targetEntity && !target) { + if (firingEntity == FindPlayerPed() && TheCamera.m_aCams[0].Using3rdPersonMouseCam()) { + CVector camPos, camTargetPos; + TheCamera.Find3rdPersonCamTargetVector(wi->m_fWeaponRange, origin, &camPos, &camTargetPos); + return { + (camTargetPos - camPos) / wi->m_fWeaponRange, // Scale to a unit vector + camTargetPos + }; + } else { + // NOTE: Moved here from `0x73E83F` + // NOTE: Original code used degs instead of radians + // and then converted back to radians... So we're gonna stick to radians only ;) + const auto heading = [&] { // 0x73E83F + if (targetEntity) { + return (targetEntity->GetPosition() - origin).Heading(); + } + if (target) { + return (*target - origin).Heading(); + } + return firingEntity->GetHeading(); + }(); + CVector dir{ + -std::sin(heading), + std::cos(heading), + 0.f + }; + if (firingEntity->IsPed()) { + if (const auto pd = firingEntity->AsPed()->m_pPlayerData) { + dir.z = -std::tan(pd->m_fLookPitch); + } + } + return { dir, origin + dir }; + } + } else { + const auto ptTarget = target + ? *target + : targetEntity->IsPed() + ? targetEntity->AsPed()->GetBonePosition(BONE_SPINE1) + : targetEntity->GetPosition(); + return { (ptTarget - origin).Normalized(), ptTarget }; + } + }(); + CShotInfo::AddShot(firingEntity, m_Type, origin, shotPt); + DoWeaponEffect(origin, shotDir); + if (m_Type == WEAPON_FLAMETHROWER && CGeneral::RandomBool(1.f / 3.f * 100.f)) { + if (CCreepingFire::TryToStartFireAtCoors( + shotDir * CVector::Random(3.5f, 6.f) + origin + CVector{0.f, 0.f, 0.5f}, + 0, + true, + false, + 2.3f) + ) { + CStats::IncrementStat(STAT_FIRES_STARTED); + } + } + CCrime::ReportCrime(CRIME_FIRE_WEAPON, nullptr, firingEntity->AsPed()); + return true; } // 0x73EC40 @@ -670,42 +1241,630 @@ bool CWeapon::CheckForShootingVehicleOccupant(CEntity** pCarEntity, CColPoint* c } // 0x73F910 -CEntity* CWeapon::PickTargetForHeatSeekingMissile(CVector origin, CVector direction, float distanceMultiplier, CEntity* ignoreEntity, bool fromVehicle, CEntity* lastEntity) { - return plugin::CallAndReturn(origin, direction, distanceMultiplier, ignoreEntity, fromVehicle, lastEntity); +CEntity* CWeapon::PickTargetForHeatSeekingMissile(CVector origin, CVector direction, float distanceMultiplier, CEntity* ignoreEntity, bool arePlanesPriority, CEntity* preferredExistingTarget) { + float minRating = FLT_MAX; + CEntity* minRated{}; + const auto point = origin + direction * 5.f; + for (auto& veh : GetVehiclePool()->GetAllValid()) { + if (&veh == ignoreEntity) { + continue; + } + if (!veh.vehicleFlags.bVehicleCanBeTargettedByHS) { + continue; + } + if (veh.m_fHealth <= 0.f) { + continue; + } + const auto rating = EvaluateTargetForHeatSeekingMissile(&veh, point, direction, distanceMultiplier, arePlanesPriority, preferredExistingTarget); + if (rating >= 0.f && rating <= minRating) { + minRating = rating; + minRated = &veh; + } + } + return minRated; } // 0x73FA20 -void CWeapon::FireFromCar(CVehicle* vehicle, bool leftSide, bool rightSide) { - plugin::CallMethod<0x73FA20, CWeapon*, CVehicle*, bool, bool>(this, vehicle, leftSide, rightSide); +bool CWeapon::FireFromCar(CVehicle* vehicle, bool leftSide, bool rightSide) { + if (m_State != WEAPONSTATE_READY || m_AmmoInClip <= 0) { + return false; + } + if (!CWeapon::FireInstantHitFromCar(vehicle, leftSide, rightSide)) { + return notsa::IsFixBugs() ? false : true; + } + if (const auto d = vehicle->m_pDriver) { + d->m_weaponAudio.AddAudioEvent(AE_WEAPON_FIRE); + } + if (!CCheat::IsActive(CHEAT_INFINITE_AMMO)) { + if (m_AmmoInClip) { // NOTE: I'm pretty sure this is redundant + m_AmmoInClip--; + } + if ( m_TotalAmmo < 25'000 && m_TotalAmmo > 0 + && (vehicle->GetStatus() != STATUS_PLAYER || CStats::GetPercentageProgress() < 100.f) + ) { + m_TotalAmmo--; + } + } + m_State = WEAPONSTATE_FIRING; + if (m_AmmoInClip) { + m_TimeForNextShotMs = CTimer::GetTimeInMS() + 1000; // NOTE: Shoot delay can be adjusted here + } else if (m_TotalAmmo) { + m_State = WEAPONSTATE_RELOADING; + m_TimeForNextShotMs = CTimer::GetTimeInMS() + GetWeaponInfo().GetWeaponReloadTime(); + } + return true; } // 0x73FB10 bool CWeapon::FireInstantHit(CEntity* firingEntity, CVector* origin, CVector* muzzlePosn, CEntity* targetEntity, CVector* target, CVector* originForDriveBy, bool arg6, bool muzzle) { + constexpr auto PLAYER_AIM_SCALE = 0.75f; + constexpr auto PLAYER_AIM_SCALE_DIST = 5.00f; + constexpr auto PLAYER_ANIM_ROT_RATE = 0.0062832f; + constexpr auto SHOTGUN_SPREAD_RATE = 0.05f; + constexpr auto SHOTGUN_NUM_PELLETS = 15u; + constexpr auto SPAS_NUM_PELLETS = 4u; + return plugin::CallMethodAndReturn(this, firingEntity, origin, muzzlePosn, targetEntity, target, originForDriveBy, arg6, muzzle); } // 0x741360 -bool CWeapon::FireProjectile(CEntity* firingEntity, CVector* origin, CEntity* targetEntity, CVector* target, float force) { - return plugin::CallMethodAndReturn(this, firingEntity, origin, targetEntity, target, force); +bool CWeapon::FireProjectile(CEntity* firedBy, const CVector& origin, CEntity* targetEntity, const CVector* targetPos, float force) { + assert(firedBy); + + const auto firedByPed = firedBy->IsPed() + ? firedBy->AsPed() + : nullptr; + auto projOrigin = origin; + auto losCheckTarget = origin; + auto losCheckOrigin = origin; + auto projType = GetProjectileType(); + if (notsa::contains({ WEAPON_RLAUNCHER, WEAPON_RLAUNCHER_HS }, GetType())) { + if (firedByPed && firedByPed->IsPlayer()) { + switch (TheCamera.GetActiveCam().m_nMode) { + case MODE_M16_1STPERSON: + case MODE_SNIPER: + case MODE_ROCKETLAUNCHER: + case MODE_ROCKETLAUNCHER_HS: + case MODE_M16_1STPERSON_RUNABOUT: + case MODE_SNIPER_RUNABOUT: + case MODE_ROCKETLAUNCHER_RUNABOUT: + case MODE_ROCKETLAUNCHER_RUNABOUT_HS: + break; + default: + return false; + } + projOrigin = origin + TheCamera.GetActiveCam().m_vecFront; + } else { + projOrigin = origin + firedBy->GetForward(); + } + if (firedByPed) { + if (firedByPed->IsPlayer()) { // 0x7416DC + CEntity* hsMissleTarget{}; + if (GetType() == WEAPON_RLAUNCHER_HS && CWeaponEffects::IsLockedOn(WEAPONEFFECTS_LOCK_ON)) { + const auto pd = firedByPed->m_pPlayerData; + if (pd->m_nFireHSMissilePressedTime) { + hsMissleTarget = PickTargetForHeatSeekingMissile( + firedBy->GetPosition(), + firedBy->GetForward(), + 1.2f, + firedBy, + false, + pd->m_LastHSMissileTarget + ); + if (hsMissleTarget == pd->m_LastHSMissileTarget && CTimer::GetTimeInMS() - pd->m_nFireHSMissilePressedTime > 1500) { // 0x74178B + const auto ch = &gCrossHair[0]; + ch->m_color = { 255, 0, 0, 255 }; + ch->m_fRotation = 1.f; + ch->m_nTimeWhenToDeactivate = 0; + } + } + } + if (hsMissleTarget) { // 0x7417BB + targetEntity = hsMissleTarget; + } else { + targetEntity = nullptr; + projType = WEAPON_ROCKET; + } + } else { // 0x7418A3 + if (targetEntity || targetPos) { + CWorld::pIgnoreEntity = firedBy; + const auto losClear = CWorld::GetIsLineOfSightClear( + projOrigin, + projOrigin + ((targetEntity ? targetEntity->GetPosition() : *targetPos) - projOrigin).Normalized() * 8.f, + true, + false, + false, + false + ); + CWorld::pIgnoreEntity = nullptr; + if (!losClear) { + return false; + } + } + } + } + } else { // 0x74139B + if (const auto t = (origin - firedBy->GetPosition()).Dot(firedBy->GetForward()); t < 0.3f) { // 0x7413FC + projOrigin += (0.3f - t) * firedBy->GetForward(); + } + losCheckTarget = projOrigin; + if (projOrigin.z - firedBy->GetPosition().z > 0.f) { + losCheckTarget += firedBy->GetForward() * 0.6f; + } + losCheckOrigin = projOrigin - (projOrigin - firedBy->GetPosition()).ProjectOnToNormal(firedBy->GetForward()); // 0x7415A2 + } + + // 0x7418F5 + CWorld::pIgnoreEntity = firedBy; + if (CWorld::GetIsLineOfSightClear( + losCheckOrigin, + losCheckTarget, + true, + true, + false, + true + )) { + if (projType == WEAPON_ROCKET && targetEntity && targetPos) { + const auto projTargetPos = targetEntity + ? targetEntity->GetPosition() + : *targetPos; + const auto projDir = (projTargetPos - losCheckOrigin).Normalized(); + CProjectileInfo::AddProjectile( // 0x741AF9 + firedBy, + WEAPON_ROCKET, + projOrigin, + force, + &projDir, + targetEntity + ); + } else { + CProjectileInfo::AddProjectile( + firedBy, + projType, + projOrigin, + force, + nullptr, + targetEntity + ); + } + + } else if (notsa::contains({ WEAPON_GRENADE, WEAPON_REMOTE_SATCHEL_CHARGE }, GetType()) && firedBy->IsPed()) { // 0x74193B + const auto thorwableProjOrigin = firedBy->GetPosition() - firedBy->GetForward() - CVector{0.f, 0.f, 0.4f}; + if (CWorld::TestSphereAgainstWorld(thorwableProjOrigin, 0.3f, nullptr, false, false, true, false, false, false)) { // 0x7419CE + CProjectileInfo::AddProjectile( + firedBy, + projType, + thorwableProjOrigin, + force, + nullptr, + targetEntity + ); + } else { + CProjectileInfo::RemoveNotAdd(firedBy, projType, projOrigin); + } + } else { + CProjectileInfo::RemoveNotAdd(firedBy, projType, projOrigin); + } + CWorld::pIgnoreEntity = nullptr; + + if (firedByPed) { // 0x741A74 + CCrime::ReportCrime(CRIME_EXPLOSION, firedByPed, firedByPed); + g_InterestingEvents.Add(CInterestingEvents::INTERESTING_EVENT_22, firedBy); + } else if (firedBy->IsVehicle()) { // 0x741B10 + if (const auto drvr = firedBy->AsVehicle()->m_pDriver) { + CCrime::ReportCrime(CRIME_FIRE_WEAPON, firedBy, drvr); + g_InterestingEvents.Add(CInterestingEvents::INTERESTING_EVENT_22, drvr); + } + } + + GetEventGlobalGroup()->Add( + CEventGunShot{ + firedBy, + projOrigin, + targetEntity + ? targetEntity->GetPosition() + : targetPos + ? *targetPos + : projOrigin, + notsa::contains({WEAPON_PISTOL_SILENCED, WEAPON_TEARGAS}, GetType()) + } + ); + + return true; } // 0x741C00 -bool CWeapon::FireM16_1stPerson(CEntity* owner) { - return plugin::CallMethodAndReturn(this, owner); +bool CWeapon::FireM16_1stPerson(CPed* owner) { + const auto cam = &TheCamera.GetActiveCam(); + + switch (cam->m_nMode) { + case MODE_M16_1STPERSON: + case MODE_SNIPER: + case MODE_CAMERA: + case MODE_ROCKETLAUNCHER: + case MODE_ROCKETLAUNCHER_HS: + case MODE_M16_1STPERSON_RUNABOUT: + case MODE_SNIPER_RUNABOUT: + case MODE_ROCKETLAUNCHER_RUNABOUT: + case MODE_ROCKETLAUNCHER_RUNABOUT_HS: + case MODE_HELICANNON_1STPERSON: + break; + default: + return false; + } + + const auto wi = &GetWeaponInfo(); // NOTE: Why not `GetWeaponInfo(owner)` + + CWorld::bIncludeDeadPeds = true; + CWorld::bIncludeCarTyres = true; + CWorld::bIncludeBikers = true; + + const auto camOriginPos = cam->m_vecSource; + const auto camTargetPos = camOriginPos + cam->m_vecFront * 3.f; + + CBirds::HandleGunShot(&camOriginPos, &camTargetPos); + CShadows::GunShotSetsOilOnFire(camOriginPos, camTargetPos); + + CColPoint shotCP; + CEntity* shotHitEntity; + if (CWorld::ProcessLineOfSight(camOriginPos, camTargetPos, shotCP, shotHitEntity, true, true, true, true, true, false, false, true)) { + CheckForShootingVehicleOccupant(&shotHitEntity, &shotCP, m_Type, camOriginPos, camTargetPos); + } + + CWorld::bIncludeDeadPeds = false; + CWorld::bIncludeCarTyres = false; + CWorld::bIncludeBikers = false; + CWorld::pIgnoreEntity = nullptr; + + //> 0x741DC4 - Check if hit entity is within range + if (shotHitEntity) { + if (TargetWeaponRangeMultiplier(shotHitEntity, owner) * wi->m_fWeaponRange >= (camOriginPos - shotCP.m_vecPoint).SquaredMagnitude2D()) { + shotHitEntity = nullptr; + } + } + + DoBulletImpact(owner, shotHitEntity, &camOriginPos, &camTargetPos, shotCP, false); + + //> 0x741E48 - Visual/physical feedback for the player(s) + if (owner->IsPlayer()) { + auto intensity = [&]{ + switch (m_Type) { + case WEAPON_AK47: + return 0.00015f; + case WEAPON_M4: + return 0.0003f; + default: + return 0.0002f; + } + }(); + if (FindPlayerPed()->bIsDucking || FindPlayerPed()->m_pAttachedTo) { + intensity *= 0.3f; + } + + // Move the camera around a little + cam->m_fHorizontalAngle += (float)CGeneral::GetRandomNumberInRange(-64, 64); + cam->m_fVerticalAngle += (float)CGeneral::GetRandomNumberInRange(-64, 64); + + // Do pad shaking + const auto shakeFreq = (uint8)lerp(130.f, 210.f, std::clamp((20.f - (wi->m_fAnimLoopEnd - wi->m_fAnimLoopStart) * 900.f) / 80.f, 0.f, 1.f)); + CPad::GetPad(owner->GetPadNumber())->StartShake( + (int16)(CTimer::GetTimeStep() * 20'000.f / (float)shakeFreq), + shakeFreq, + 0 + ); + } + + return true; } // 0x742300 -bool CWeapon::Fire(CEntity* firingEntity, CVector* origin, CVector* muzzlePosn, CEntity* targetEntity, CVector* target, CVector* originForDriveBy) { - return plugin::CallMethodAndReturn(this, firingEntity, origin, muzzlePosn, targetEntity, - target, originForDriveBy); +bool CWeapon::Fire(CEntity* firedBy, CVector* startPosn, CVector* barrelPosn, CEntity* targetEnt, CVector* targetPosn, CVector* altPosn) { + const auto firedByPed = firedBy && firedBy->IsPed() + ? firedBy->AsPed() + : nullptr; + const auto wi = &GetWeaponInfo(firedByPed); + + CVector point{ 0.f, 0.f, 0.6f }; + + const auto fxPos = startPosn + ? startPosn + : &point; + const auto shotOrigin = startPosn + ? barrelPosn + : &point; + if (!startPosn) { + point = firedBy->GetMatrix() * point; + startPosn = &point; + } + + if (m_IsFirstPersonWeaponModeSelected) { + const auto r = 0.15f; + + const auto h = firedBy->GetHeading(); + fxPos->x -= std::sin(h) * r; + fxPos->y += std::cos(h) * r; + } + + switch (m_State) { + case WEAPONSTATE_READY: + case WEAPONSTATE_FIRING: + break; + default: + return false; + } + + if (!m_AmmoInClip) { + if (!m_TotalAmmo) { + return false; + } + m_AmmoInClip = std::min(m_TotalAmmo, wi->m_nAmmoClip); + } + + const auto [hasFired, delayNextShot] = [&]() -> std::pair { + switch (m_Type) { + case WEAPON_GRENADE: + case WEAPON_TEARGAS: + case WEAPON_MOLOTOV: + case WEAPON_REMOTE_SATCHEL_CHARGE: { // 0x74268B + if (targetPosn) { + return { + FireProjectile( // 0x742705 + firedBy, + shotOrigin, + targetEnt, + targetPosn, + std::clamp(((firedBy->GetPosition() - *targetPosn).Magnitude() - 10.f) / 10.f, 0.2f, 1.f) + ), + true + }; + } else if (firedBy == FindPlayerPed()) { // 0x74271F + return { + FireProjectile( + firedBy, + shotOrigin, + targetEnt, + nullptr, + firedBy->AsPed()->m_pPlayerData->m_fAttackButtonCounter * 0.0375f + ), + true + }; + } + return { + FireProjectile( // 0x74274E + firedBy, + shotOrigin, + targetEnt, + nullptr, + 0.3f + ), + true + }; + } + case WEAPON_PISTOL: + case WEAPON_PISTOL_SILENCED: + case WEAPON_DESERT_EAGLE: + case WEAPON_MICRO_UZI: + case WEAPON_MP5: + case WEAPON_AK47: + case WEAPON_M4: + case WEAPON_TEC9: + case WEAPON_COUNTRYRIFLE: + case WEAPON_MINIGUN: { // 0x7424FE + if ( firedByPed + && firedByPed->m_nPedType == PED_TYPE_PLAYER1 + && notsa::contains({ MODE_M16_1STPERSON, MODE_HELICANNON_1STPERSON }, TheCamera.m_PlayerWeaponMode.m_nMode) + ) { + return { FireM16_1stPerson(firedByPed), true }; + } + const auto fired = FireInstantHit(firedBy, startPosn, shotOrigin, targetEnt, targetPosn, altPosn, false, true); + if (firedByPed) { // 0x74255B + if (!firedByPed->bInVehicle) { + return { fired, false }; + } + if (const auto t = firedByPed->GetTaskManager().GetActiveTask()) { + return { fired, t->GetTaskType() == TASK_SIMPLE_GANG_DRIVEBY }; + } + } + return { fired, true }; + } + case WEAPON_SHOTGUN: + case WEAPON_SAWNOFF_SHOTGUN: + case WEAPON_SPAS12_SHOTGUN: + return { + FireInstantHit( // 0x742495 + firedBy, + startPosn, + shotOrigin, + targetEnt, + targetPosn, + altPosn, + false, + true + ), + true + }; + case WEAPON_SNIPERRIFLE: { // 0x7424AC + if (firedByPed && firedByPed->m_nPedType == PED_TYPE_PLAYER1 && TheCamera.m_PlayerWeaponMode.m_nMode == MODE_SNIPER) { + return { + FireSniper(firedByPed, targetEnt, targetPosn), + true + }; + } + return { + FireInstantHit( + firedBy, + startPosn, + shotOrigin, + targetEnt, + targetPosn, + nullptr, + false, + true + ), + true + }; + } + case WEAPON_RLAUNCHER: + case WEAPON_RLAUNCHER_HS: { // 0x7425B3 + if (firedByPed) { + const auto CanFire = [&](CVector origin, CVector end) { + return (origin - end).SquaredMagnitude() <= sq(8.f) && !firedBy->IsPed(); + }; + if ( targetEnt && !CanFire(firedBy->GetPosition(), targetEnt->GetPosition()) + || targetPosn && !CanFire(firedBy->GetPosition(), targetPosn) + ) { + return { false, true }; + } + } + return { + FireProjectile( + firedBy, + shotOrigin, + targetEnt, + targetPosn + ), + true + }; + } + case WEAPON_FLAMETHROWER: + case WEAPON_SPRAYCAN: + case WEAPON_EXTINGUISHER: + return { + FireAreaEffect( + firedBy, + shotOrigin, + targetEnt, + targetPosn + ), + true + }; + case WEAPON_DETONATOR: { + assert(firedByPed); + CWorld::UseDetonator(firedByPed); + m_AmmoInClip = m_TotalAmmo = 1; + return { true, true }; + } + case WEAPON_CAMERA: + return { + TakePhotograph(firedBy, shotOrigin), + true + }; + default: + NOTSA_UNREACHABLE(); + } + }(); + + // 0x74279A + if (hasFired) { + // 0x7427B3 + const bool isPlayerFiring = firedByPed && m_Type != WEAPON_CAMERA && firedByPed->IsPlayer(); + if (firedByPed) { + if (m_Type != WEAPON_CAMERA) { + firedByPed->bFiringWeapon = true; + } + firedByPed->m_weaponAudio.AddAudioEvent(AE_WEAPON_FIRE); + if (isPlayerFiring && targetEnt && targetEnt->IsPed() && m_Type != WEAPON_PISTOL_SILENCED) { + firedByPed->Say(182, 200); // 0x74280E + } + } + + // 0x74282C + if (m_Type == WEAPON_REMOTE_SATCHEL_CHARGE) { + firedByPed->GiveWeapon(WEAPON_DETONATOR, true, true); + if (firedByPed->GetWeapon(WEAPON_REMOTE_SATCHEL_CHARGE).m_TotalAmmo <= 1) { + firedByPed->GetWeapon(WEAPON_DETONATOR).m_State = eWeaponState::WEAPONSTATE_READY; + firedByPed->SetCurrentWeapon(WEAPON_DETONATOR); + } + } + + //> 0x74286D - Increase stats + if (isPlayerFiring) { + switch (m_Type) + { + case WEAPON_GRENADE: + case WEAPON_MOLOTOV: + case WEAPON_ROCKET: + case WEAPON_RLAUNCHER: + case WEAPON_RLAUNCHER_HS: + case WEAPON_REMOTE_SATCHEL_CHARGE: + case WEAPON_DETONATOR: + CStats::IncrementStat(STAT_KGS_OF_EXPLOSIVES_USED); + break; + case WEAPON_PISTOL: + case WEAPON_PISTOL_SILENCED: + case WEAPON_DESERT_EAGLE: + case WEAPON_SHOTGUN: + case WEAPON_SAWNOFF_SHOTGUN: + case WEAPON_SPAS12_SHOTGUN: + case WEAPON_MICRO_UZI: + case WEAPON_MP5: + case WEAPON_AK47: + case WEAPON_M4: + case WEAPON_TEC9: + case WEAPON_COUNTRYRIFLE: + case WEAPON_SNIPERRIFLE: + case WEAPON_MINIGUN: + CStats::IncrementStat(STAT_BULLETS_FIRED); + break; + default: + break; + } + } + + // 0x7428A6 + if (!CCheat::IsActive(CHEAT_INFINITE_AMMO)) { + if (m_AmmoInClip) { + m_AmmoInClip--; + } + if (m_TotalAmmo > 0) { + if (isPlayerFiring + ? m_Type == WEAPON_DETONATOR || CStats::GetPercentageProgress() < 100.f + : m_TotalAmmo < 25'000 + ) { + m_TotalAmmo--; + } + } + } + + m_State = WEAPONSTATE_FIRING; + + if (!m_AmmoInClip) { // 0x7428FB + if (m_TotalAmmo) { + m_State = WEAPONSTATE_RELOADING; + m_TimeForNextShotMs = s_DebugSettings.NoShotDelay + ? 0 + : firedBy == FindPlayerPed() && FindPlayerInfo().m_bFastReload + ? wi->GetWeaponReloadTime() / 4 + : wi->GetWeaponReloadTime(); + m_TimeForNextShotMs += CTimer::GetTimeInMS(); + } else if (TheCamera.GetActiveCam().m_nMode == MODE_CAMERA) { + CPad::GetPad()->Clear(false, true); + } + return true; + } + + m_TimeForNextShotMs = s_DebugSettings.NoShotDelay + ? 0 + : delayNextShot + ? m_Type == WEAPON_CAMERA + ? 1100 + : (uint32)((wi->m_fAnimLoopEnd - wi->m_fAnimLoopStart) * 900.f) + : 0; + m_TimeForNextShotMs += CTimer::GetTimeInMS(); + } + // 0x7429F2 + if (m_Type == WEAPON_UNARMED || m_Type == WEAPON_BASEBALLBAT) { + return true; + } + return hasFired; } CWeaponInfo& CWeapon::GetWeaponInfo(CPed* owner) const { - return GetWeaponInfo(owner ? owner->GetWeaponSkill(m_nType) : eWeaponSkill::STD); + return GetWeaponInfo(owner ? owner->GetWeaponSkill(m_Type) : eWeaponSkill::STD); } CWeaponInfo& CWeapon::GetWeaponInfo(eWeaponSkill skill) const { - return *CWeaponInfo::GetWeaponInfo(m_nType, skill); + return *CWeaponInfo::GetWeaponInfo(m_Type, skill); } // 0x73AF00 diff --git a/source/game_sa/Weapon.h b/source/game_sa/Weapon.h index ad4e967d1e..c17c8ff3ec 100644 --- a/source/game_sa/Weapon.h +++ b/source/game_sa/Weapon.h @@ -48,29 +48,18 @@ class CWeaponInfo; class CWeapon { public: - eWeaponType m_nType; - eWeaponState m_nState; - uint32 m_nAmmoInClip; - uint32 m_nTotalAmmo; - uint32 m_nTimeForNextShot; - uint8 field_14; - bool m_bNoModel; // Used in case of goggles (infrared/nightvision) : When they're put on the weapon model isn't and shouldn't be loaded. - uint8 field_16; - uint8 field_17; - FxSystem_c* m_pFxSystem; // flamethrower, spraycan, extinguisher particle - - static float& ms_fExtinguisherAimAngle; // default -0.34907 rad. (-pi/8) - static bool& bPhotographHasBeenTaken; - static bool& ms_bTakePhoto; - static CColModel& ms_PelletTestCol; + static inline auto& ms_fExtinguisherAimAngle = StaticRef(0x8D610C); // default -0.34907 rad. (-pi/8) + static inline auto& bPhotographHasBeenTaken = StaticRef(0xC8A7C0); + static inline auto& ms_bTakePhoto = StaticRef(0xC8A7C1); + static inline auto& ms_PelletTestCol = StaticRef(0xC8A7DC); + + static inline struct DebugSettings { + bool NoShotDelay; + } s_DebugSettings; public: - CWeapon() { // 0x441E00 - field_14 = 0; - m_bNoModel = false; - m_pFxSystem = nullptr; - }; - CWeapon(eWeaponType weaponType, int32 ammo); + CWeapon() = default; + CWeapon(eWeaponType weaponType, uint32 ammo); CWeapon(const CWeapon&) = delete; void Initialise(eWeaponType weaponType, int32 ammo, CPed* owner); @@ -89,7 +78,14 @@ class CWeapon { bool HasWeaponAmmoToBeUsed(); void StopWeaponEffect(); - void DoBulletImpact(CEntity* owner, CEntity* victim, CVector* startPoint, CVector* endPoint, CColPoint* colPoint, int32 arg5); + void DoBulletImpact(CEntity* owner, CEntity* victim, const CVector& startPoint, const CVector& endPoint, const CColPoint& colPoint, int32 arg5); + /*! + * @addr 0x73C1F0 + * @brief Marks all peds and objects that are in range (125 units) and in frame (on the screen - 0.1 relative border) as photographed. + * + * @param owner Camera owner - unused. + * @param point Pos of the camflash effect + */ bool TakePhotograph(CEntity* owner, CVector* point); void SetUpPelletCol(int32 numPellets, CEntity* owner, CEntity* victim, CVector& point, CColPoint& colPoint, CMatrix& outMatrix); bool CanBeUsedFor2Player(); @@ -97,49 +93,70 @@ class CWeapon { // outX and outY will be placed in [-1;1] ranges void DoWeaponEffect(CVector origin, CVector target); - bool FireAreaEffect(CEntity* firingEntity, CVector* origin, CEntity* targetEntity, CVector* target); + bool FireAreaEffect(CEntity* firingEntity, const CVector& origin, CEntity* targetEntity, CVector* target); bool FireInstantHitFromCar(CVehicle* vehicle, bool leftSide, bool rightSide); - void FireFromCar(CVehicle* vehicle, bool leftSide, bool rightSide); + bool FireFromCar(CVehicle* vehicle, bool leftSide, bool rightSide); void FireInstantHitFromCar2(CVector startPoint, CVector endPoint, CVehicle* vehicle, CEntity* owner); bool FireInstantHit(CEntity* firingEntity, CVector* origin, CVector* muzzlePosn, CEntity* targetEntity = nullptr, CVector* target = nullptr, CVector* originForDriveBy = nullptr, bool arg6 = false, bool muzzle = false); - bool FireProjectile(CEntity* firingEntity, CVector* origin, CEntity* targetEntity = nullptr, CVector* target = nullptr, float force = 0.f); - bool FireM16_1stPerson(CEntity* owner); - bool Fire(CEntity* firingEntity, CVector* origin, CVector* muzzlePosn, CEntity* targetEntity, CVector* target, CVector* originForDriveBy); + bool FireProjectile(CEntity* firingEntity, const CVector& origin, CEntity* targetEntity = nullptr, const CVector* targetPos = nullptr, float force = 0.f); + bool FireM16_1stPerson(CPed* owner); + bool Fire(CEntity* firedBy, CVector* startPosn, CVector* barrelPosn, CEntity* targetEnt, CVector* targetPosn, CVector* altPosn); void Update(CPed* owner); static void UpdateWeapons(); - static void GenerateDamageEvent(CPed* victim, CEntity* creator, eWeaponType weaponType, int32 damageFactor, ePedPieceTypes pedPiece, int32 direction); + static bool GenerateDamageEvent(CPed* victim, CEntity* creator, eWeaponType weaponType, int32 damageFactor, ePedPieceTypes pedPiece, uint8 direction); static bool CanBeUsedFor2Player(eWeaponType weaponType); static float TargetWeaponRangeMultiplier(CEntity* victim, CEntity* weaponOwner); + + /*! + * @addr 0x73CDC0 + * @brief Find closest entity in range that is visible to `owner` (Eg.: Is in [-PI/8, PI/8] angle) and modify `end->z` to be pointing at it. idk.. + * + * @param end out Z axis is modified + */ static void DoDoomAiming(CEntity* owner, CVector* start, CVector* end); static void DoTankDoomAiming(CEntity* vehicle, CEntity* owner, CVector* startPoint, CVector* endPoint); static void DoDriveByAutoAiming(CEntity* owner, CVehicle* vehicle, CVector* startPoint, CVector* endPoint, bool canAimVehicles); - static void FindNearestTargetEntityWithScreenCoors(float screenX, float screenY, float range, CVector point, float* outX, float* outY); - static float EvaluateTargetForHeatSeekingMissile(CEntity* entity, CVector& posn, CVector& direction, float distanceMultiplier, bool fromVehicle, CEntity* lastEntity); + static CEntity* FindNearestTargetEntityWithScreenCoors(float screenX, float screenY, float range, CVector point, float* outX, float* outY); + static float EvaluateTargetForHeatSeekingMissile(CEntity* potentialTarget, const CVector& origin, const CVector& aimingDir, float tolerance, bool arePlanesPriority, CEntity* preferredExistingTarget); static bool CheckForShootingVehicleOccupant(CEntity** pCarEntity, CColPoint* colPoint, eWeaponType weaponType, const CVector& origin, const CVector& target); - static CEntity* PickTargetForHeatSeekingMissile(CVector origin, CVector direction, float distanceMultiplier, CEntity* ignoreEntity, bool fromVehicle, CEntity* lastEntity); + static CEntity* PickTargetForHeatSeekingMissile(CVector origin, CVector direction, float distanceMultiplier, CEntity* ignoreEntity, bool arePlanesPriority, CEntity* preferredExistingTarget); static bool ProcessLineOfSight(const CVector& startPoint, const CVector& endPoint, CColPoint& outColPoint, CEntity*& outEntity, eWeaponType weaponType, CEntity* arg5, bool buildings, bool vehicles, bool peds, bool objects, bool dummies, bool arg11, bool doIgnoreCameraCheck); CWeaponInfo& GetWeaponInfo(CPed* owner = nullptr) const; CWeaponInfo& GetWeaponInfo(eWeaponSkill skill) const; + auto GetType() const noexcept { return m_Type; } + auto GetState() const noexcept { return m_State; } + auto GetAmmoInClip() const noexcept { return m_AmmoInClip; } + auto GetTotalAmmo() const noexcept { return m_TotalAmmo; } + private: friend void InjectHooksMain(); static void InjectHooks(); - CWeapon* Constructor(eWeaponType weaponType, int32 ammo); + //! @notsa + //! @brief Get the projectile type of this weapon - Only valid for weapons that fire a projectile [like rlaunchers, etc], or are itself a projectile [ex.: grenades] + auto GetProjectileType(); + + //! @notsa + CWeapon* Constructor(eWeaponType weaponType, uint32 ammo) { + this->CWeapon::CWeapon(weaponType, ammo); + return this; + } + +public: // TODO: Eventually make this private + eWeaponType m_Type{}; //< Weapon's type + eWeaponState m_State{}; //< Current weapon state + uint32 m_AmmoInClip{}; //< Count of ammo in the clip currently + uint32 m_TotalAmmo{}; //< The total amount of ammo (Anything above 25k is considered infinite in case of player peds) + uint32 m_TimeForNextShotMs{}; //< When the next shot is allowed to be fired + bool m_IsFirstPersonWeaponModeSelected{}; //< Fuck knows, unused + bool m_DontPlaceInHand{}; //< Used in case of goggles (infrared/nightvision) : When they're put on the weapon model isn't [and shouldn't be] loaded. + FxSystem_c* m_FxSystem{}; //< Fx system [flamethrower, spraycan, extinguisher] }; VALIDATE_SIZE(CWeapon, 0x1C); -extern float &fPlayerAimScale; // default 0.75 -extern float &fPlayerAimScaleDist; // default 5.0 -extern float &fPlayerAimRotRate; // default 0.0062832 -extern float &SHOTGUN_SPREAD_RATE; // default 0.05 -extern uint32 &SHOTGUN_NUM_PELLETS; // default 15 -extern uint32 &SPAS_NUM_PELLETS; // default 4 -extern float &PELLET_COL_SCALE_RATIO_MULT; // default 1.3 -extern float *fReloadAnimSampleFraction; // default { 0.5, 0.7, 0.75, 0.75, 0.7 } - void FireOneInstantHitRound(CVector* startPoint, CVector* endPoint, int32 intensity); diff --git a/source/game_sa/WeaponEffects.cpp b/source/game_sa/WeaponEffects.cpp index 81e0bafa99..98e19c92f2 100644 --- a/source/game_sa/WeaponEffects.cpp +++ b/source/game_sa/WeaponEffects.cpp @@ -101,5 +101,7 @@ void CWeaponEffects::ClearCrossHairsImmediately() { // 0x742CF0 void CWeaponEffects::Render() { + ZoneScoped; + plugin::Call<0x742CF0>(); } diff --git a/source/game_sa/WeaponInfo.cpp b/source/game_sa/WeaponInfo.cpp index a557641f07..4897d38342 100644 --- a/source/game_sa/WeaponInfo.cpp +++ b/source/game_sa/WeaponInfo.cpp @@ -232,7 +232,7 @@ void CWeaponInfo::LoadWeaponData() { if (!std::string_view{ animGrpName }.starts_with("null")) { - wi.m_eAnimGroup = CAnimManager::GetAnimationGroupId(animGrpName); + wi.m_eAnimGroup = CAnimManager::GetAnimationGroupIdByName(animGrpName); } if (wi.m_eAnimGroup >= ANIM_GROUP_PYTHON && wi.m_eAnimGroup <= ANIM_GROUP_SPRAYCAN) { @@ -255,7 +255,7 @@ void CWeaponInfo::LoadWeaponData() { VERIFY(sscanf_s(line, "%*s %s %f %f %f %f %d %d %d %d", SCANF_S_STR(stealthAnimGrp), &aimX, &aimZ, &duckX, &duckZ, &RLoadA, &RLoadB, &crouchRLoadA, &crouchRLoadB) == 9); - g_GunAimingOffsets[CAnimManager::GetAnimationGroupId(stealthAnimGrp) - ANIM_GROUP_PYTHON] = { + g_GunAimingOffsets[CAnimManager::GetAnimationGroupIdByName(stealthAnimGrp) - ANIM_GROUP_PYTHON] = { .AimX = aimX, .AimZ = aimZ, @@ -310,7 +310,7 @@ void CWeaponInfo::LoadWeaponData() { wi.m_nFlags = flags; if (!std::string_view{stealthAnimGrpName}.starts_with("null")) - wi.m_eAnimGroup = CAnimManager::GetAnimationGroupId(stealthAnimGrpName); + wi.m_eAnimGroup = CAnimManager::GetAnimationGroupIdByName(stealthAnimGrpName); if (modelId1 > 0) CModelInfo::GetModelInfo(modelId1)->AsWeaponModelInfoPtr()->m_weaponInfo = wType; diff --git a/source/game_sa/WeaponInfo.h b/source/game_sa/WeaponInfo.h index 5148692ae5..4887533fc7 100644 --- a/source/game_sa/WeaponInfo.h +++ b/source/game_sa/WeaponInfo.h @@ -122,8 +122,10 @@ class CWeaponInfo { //! NOTSA: GetWeaponInfo for specific ped. static auto GetWeaponInfo(CPed* ped) { - return GetWeaponInfo(ped->GetActiveWeapon().m_nType, ped->GetWeaponSkill()); + return GetWeaponInfo(ped->GetActiveWeapon().m_Type, ped->GetWeaponSkill()); } + + const auto& GetAimingOffset() const { return g_GunAimingOffsets[m_nAimOffsetIndex]; } }; VALIDATE_SIZE(CWeaponInfo, 0x70); diff --git a/source/game_sa/Weather.cpp b/source/game_sa/Weather.cpp index 2efc6bab9c..0db2d3656a 100644 --- a/source/game_sa/Weather.cpp +++ b/source/game_sa/Weather.cpp @@ -76,6 +76,8 @@ void CWeather::InjectHooks() { // 0x72A480 void CWeather::Init() { + ZoneScoped; + NewWeatherType = WEATHER_EXTRASUNNY_LA; OldWeatherType = WEATHER_EXTRASUNNY_LA; WeatherRegion = WEATHER_REGION_DEFAULT; @@ -298,6 +300,8 @@ void CWeather::SetWeatherToAppropriateTypeNow() { // 0x72B850 void CWeather::Update() { + ZoneScoped; + plugin::Call<0x72B850>(); } diff --git a/source/game_sa/World.cpp b/source/game_sa/World.cpp index 5b21e65f12..2515022f4a 100644 --- a/source/game_sa/World.cpp +++ b/source/game_sa/World.cpp @@ -154,6 +154,8 @@ void CWorld::ResetLineTestOptions() { // 0x5631E0 void CWorld::Initialise() { + ZoneScoped; + bDoingCarCollisions = false; bNoMoreCollisionTorque = false; bIncludeDeadPeds = false; @@ -254,6 +256,8 @@ void CWorld::ProcessForAnimViewer() { // 0x563430 void CWorld::ProcessPedsAfterPreRender() { + ZoneScoped; + if (CTimer::bSkipProcessThisFrame) return; @@ -1963,6 +1967,8 @@ void CWorld::TriggerExplosionSectorList(CPtrList& ptrList, const CVector& point, // 0x5684A0 void CWorld::Process() { + ZoneScoped; + const auto IterateMovingList = [&](auto&& fn) { for (CPtrNodeDoubleLink* node = ms_listMovingEntityPtrs.GetNode(), *next{}; node; node = next) { next = node->m_next; @@ -2009,6 +2015,8 @@ void CWorld::Process() { // Process moving entities (And possibly remove them from the world) { + ZoneScopedN("Process moving entities"); + const auto DoProcessMovingEntity = [&](CEntity* entity) { if (entity->m_bRemoveFromWorld) { if (entity->IsPed()) { @@ -2046,6 +2054,8 @@ void CWorld::Process() { g_LoadMonitor.StartTimer(true); if (CReplay::Mode == MODE_PLAYBACK) { + ZoneScopedN("Update entity RW"); + IterateMovingList([&](CEntity* entity) { entity->m_bIsInSafePosition = true; entity->UpdateRW(); @@ -2054,6 +2064,8 @@ void CWorld::Process() { } else { // Process collision { + ZoneScopedN("Process collision"); + const auto ProcessMovingEntityCollision = [](CEntity* entity) { if (!entity->m_bIsInSafePosition) { entity->ProcessCollision(); @@ -2092,6 +2104,8 @@ void CWorld::Process() { // Process "Shift" (Not sure what that is) { + ZoneScopedN("Process shift"); + bSecondShift = false; IterateMovingList([&](CEntity* entity) { diff --git a/source/game_sa/World.h b/source/game_sa/World.h index 318a34096c..c933549f25 100644 --- a/source/game_sa/World.h +++ b/source/game_sa/World.h @@ -30,6 +30,7 @@ constexpr int32 MAX_LOD_PTR_LISTS_X = 30; constexpr int32 MAX_LOD_PTR_LISTS_Y = 30; constexpr int32 MAX_LOD_PTR_LISTS = MAX_LOD_PTR_LISTS_X * MAX_LOD_PTR_LISTS_Y; +constexpr inline float WORLD_BOUND_RANGE = 3000.0f; constexpr inline CRect WORLD_BOUNDS{-3000.0F, -3000.0F, 3000.0F, 3000.0F}; constexpr float MAP_Z_LOW_LIMIT = -100.0f; @@ -153,7 +154,7 @@ class CWorld { static void FindMissionEntitiesIntersectingCube(const CVector& cornerA, const CVector& cornerB, int16* outCount, int16 maxCount, CEntity** outEntities, bool vehicles, bool peds, bool objects); static CEntity* FindNearestObjectOfType(int32 modelId, const CVector& point, float radius, bool b2D, bool buildings, bool vehicles, bool peds, bool objects, bool dummies); static float FindGroundZForCoord(float x, float y); - static float FindGroundZFor3DCoord(CVector coord, bool* outResult, CEntity** outEntity); + static float FindGroundZFor3DCoord(CVector coord, bool* outResult = nullptr, CEntity** outEntity = nullptr); static float FindRoofZFor3DCoord(float x, float y, float z, bool* outResult); static float FindLowestZForCoord(float x, float y); static void RepositionOneObject(CEntity* object); diff --git a/source/game_sa/Zone.h b/source/game_sa/Zone.h index b6880dcaa1..db4adb8cf0 100644 --- a/source/game_sa/Zone.h +++ b/source/game_sa/Zone.h @@ -22,18 +22,20 @@ class CZone { auto GetInfoLabel() const { return std::string_view{ m_InfoLabel }; } auto GetNaviLabel() const { return std::string_view{ m_TextLabel }; } auto GetRect() const { return CRect{ (float)m_fX1, (float)m_fY1, (float)m_fX2, (float)m_fY2 }; } + auto GetBB() const { return CBox{ {(float)m_fX1, (float)m_fY1, (float)m_fZ1}, {(float)m_fX2, (float)m_fY2, (float)m_fZ2} }; } + public: - char m_InfoLabel[8]; // Zone info `TheText.Get` key (Unsure) - char m_TextLabel[8]; // Display name `TheText.Get` key - int16 m_fX1; - int16 m_fY1; - int16 m_fZ1; - int16 m_fX2; - int16 m_fY2; - int16 m_fZ2; - int16 m_nZoneExtraIndexInfo; - eZoneType m_nType; - eLevelName m_nLevel; + char m_InfoLabel[8]{}; // Zone info `TheText.Get` key (Unsure) + char m_TextLabel[8]{}; // Display name `TheText.Get` key + int16 m_fX1{}; + int16 m_fY1{}; + int16 m_fZ1{}; + int16 m_fX2{}; + int16 m_fY2{}; + int16 m_fZ2{}; + int16 m_nZoneExtraIndexInfo{}; + eZoneType m_nType{}; + eLevelName m_nLevel{}; }; diff --git a/source/game_sa/ZoneInfo.h b/source/game_sa/ZoneInfo.h index be47223f84..495917f216 100644 --- a/source/game_sa/ZoneInfo.h +++ b/source/game_sa/ZoneInfo.h @@ -12,19 +12,14 @@ class CZoneInfo { public: - uint8 GangDensity[10]; - uint8 DrugDealerCounter; /// Counter updated in `UpdateDealerStrengths`. Only used durning gang wars. Max value is the size of the array in the beforementioned function (15 currently) - CRGBA ZoneColor; - union { - struct { - uint8 zonePopulationType : 5; - uint8 radarMode : 2; - uint8 noCops : 1; - uint8 zonePopulationRace : 4; // Bitfield for race allowed in the zone. See `ePedRace`. Default value (RACE_DEFAULT) isn't counted. See `IsPedAppropriateForCurrentZone` for usage example. - }; - struct { - uint8 Flags1, Flags2; - }; + uint8 GangDensity[10]{}; + uint8 DrugDealerCounter{}; /// Counter updated in `UpdateDealerStrengths`. Only used durning gang wars. Max value is the size of the array in the beforementioned function (15 currently) + CRGBA ZoneColor{}; + struct { + uint8 zonePopulationType : 5{ 5 }; + uint8 radarMode : 2{}; + uint8 noCops : 1{}; + uint8 zonePopulationRace : 4{0b1111}; // Bitfield for race allowed in the zone. See `ePedRace`. Default value (RACE_DEFAULT) isn't counted. See `IsPedAppropriateForCurrentZone` for usage example. }; auto GetSumOfGangDensity() const { diff --git a/source/game_sa/cTransmission.cpp b/source/game_sa/cTransmission.cpp index b175eeaaf9..ab8b80ec2d 100644 --- a/source/game_sa/cTransmission.cpp +++ b/source/game_sa/cTransmission.cpp @@ -28,11 +28,11 @@ void cTransmission::DisplayGearRatios() static constexpr float magic_0 = 1000.0f / 3600.0f; static constexpr float magic = magic_0 / 50.0f; - for (uint8 i = 0; i <= m_nNumberOfGears; i++) + for (size_t i = 0; i <= m_nNumberOfGears; i++) { tTransmissionGear& gear = m_aGears[i]; DEV_LOG( - "{} => max v = {3.2f}, up at = {3.2f}, down at = {3.2f}", + "{} => max v = {:03.2f}, up at = {:03.2f}, down at = {:03.2f}", i, gear.m_maxVelocity / magic, gear.m_changeUpVelocity / magic, diff --git a/source/game_sa/common.h b/source/game_sa/common.h index 1a437d30ec..47d15efa66 100644 --- a/source/game_sa/common.h +++ b/source/game_sa/common.h @@ -113,7 +113,8 @@ constexpr float TWO_PI = 6.28318f; // τ (TAU) constexpr float COS_45 = SQRT_2; // cos(45deg) -constexpr float sq(float x) { return x * x; } +template +constexpr T sq(T x) { return x * x; } struct SpriteFileName { const char* name; @@ -147,18 +148,61 @@ constexpr float DegreesToRadians(float angleInDegrees) { return angleInDegrees * PI / 180.0F; } +//! @notsa +inline RwTexCoords operator*(RwTexCoords lhs, float rhs) { + return { lhs.u * rhs, lhs.v * rhs }; +} + +//! @notsa +inline RwTexCoords operator+(RwTexCoords lhs, RwTexCoords rhs) { + return { lhs.u + rhs.u, lhs.v + rhs.v }; +} + +template +struct WeightedValue { + using value_type = T; + + T v; + Y w; +}; + +template // Range of WeightedValue`s +auto multiply_weighted(R&& r) { + using T = rng::range_value_t::value_type; + + T a{}; + for (const auto& vw : r) { + a = a + (T)(vw.v * vw.w); + } + return a; +} + +template +auto multiply_weighted(WeightedValue (&&values)[N]) { + return multiply_weighted(values); +} + // Converts radians to degrees // 57.295826 constexpr float RadiansToDegrees(float angleInRadians) { return angleInRadians * 180.0F / PI; } +//! Step towards a certain number +template +T stepto(const T& from, const T& to, float step) { + return to <= from + ? std::min(from + step, to) + : std::max(from - step, to); +} + template -auto lerp(const T& from, const T& to, float t) { - return to * t + from * (1.f - t); +T lerp(const T& from, const T& to, float t) { + // Same as from + (to - from) * t + return static_cast(to * t + from * (1.f - t)); } -inline const float invLerp(float fMin, float fMax, float fVal) { +constexpr float invLerp(float fMin, float fMax, float fVal) { return (fVal - fMin) / (fMax - fMin); } diff --git a/source/premake5.lua b/source/premake5.lua index 6a593319c2..5311fd4b05 100644 --- a/source/premake5.lua +++ b/source/premake5.lua @@ -8,7 +8,7 @@ project "gta_reversed" pchsource "StdInc.cpp" filter {"options:allow-script-cmd-hooks"} - defines { "ENABLE_SCRIPT_COMMAND_HOOKS" } + defines { "ENABLE_SCRIPT_COMMAND_HOOKS" } filter {} -- Clear filter @@ -33,14 +33,23 @@ project "gta_reversed" "../libs/imgui", "../libs/imgui/backends", "../libs/imgui/misc/cpp", - "../libs/dxsdk" + "../libs/dxsdk", + "../libs/spdlog/include", + "../libs/tracy/public" } defines { "NOMINMAX", "USE_GTASA_ALLOCATOR", "EXTRA_DEBUG_FEATURES", - "FIX_BUGS" + "FIX_BUGS", + "_CRT_SECURE_NO_WARNINGS", + + "TRACY_ENABLE", + "TRACY_ON_DEMAND", + "TRACY_CALLSTACK", + + "SPDLOG_USE_STD_FORMAT" } links { @@ -48,15 +57,31 @@ project "gta_reversed" "vorbis", "vorbisenc", "vorbisfile", - "imgui" + "imgui", + "spdlog", + "imgui", + "tracy", + "ddraw.lib", + "Winmm.lib", + "dxguid.lib", + "strmiids.lib", + "dsound.lib", + "d3d9.lib" } + filter "configurations:Debug*" + links { + "dbghelp" + } + filter {} + libdirs { + "../libs", "../%{cfg.targetdir}/ogg.lib", "../%{cfg.targetdir}/vorbis.lib", "../%{cfg.targetdir}/vorbisfile.lib", "../%{cfg.targetdir}/vorbisenc.lib", - "../%{cfg.targetdir}/imgui.lib", + "../%{cfg.targetdir}/imgui.lib", "../libs/dxsdk/d3d9.lib", - "../libs/dxsdk/dinput.lib" + "../libs/dxsdk/dinput.lib", } diff --git a/source/reversiblehooks/ReversibleHook/Simple.h b/source/reversiblehooks/ReversibleHook/Simple.h index 43a7dd88e3..32ca415eca 100644 --- a/source/reversiblehooks/ReversibleHook/Simple.h +++ b/source/reversiblehooks/ReversibleHook/Simple.h @@ -15,7 +15,7 @@ struct SHookContent { VALIDATE_SIZE(SHookContent, 0x34); struct Simple : Base { - // TODO: Refactor this copy-pate and document it + // TODO: Refactor this copy-paste and document it SHookContent m_HookContent{}; uint8 m_OriginalFunctionContent[sizeof(m_HookContent)]{}; diff --git a/source/reversiblehooks/ReversibleHooks.cpp b/source/reversiblehooks/ReversibleHooks.cpp index 909a078fc1..8f34b6353c 100644 --- a/source/reversiblehooks/ReversibleHooks.cpp +++ b/source/reversiblehooks/ReversibleHooks.cpp @@ -116,8 +116,8 @@ void HookInstall(std::string_view category, std::string fnName, uint32 installAd #ifndef NDEBUG // Functions with the same name are asserted in `HookCategory::AddItem()` auto [iter, inserted] = s_HookedAddresses.insert(installAddress); if (!inserted) { - // If this asserts that means the address was hooked once already - Thats bad! - printf("Warn %s %s\n", category.data(), fnName.c_str()); + NOTSA_LOG_ERR("{}/{} is hooked to an address (0x{:X}) that is already hooked! That's bad!", category.data(), fnName.c_str(), LOG_PTR(installAddress)); + assert(false); return; } #endif diff --git a/source/toolsmenu/DebugModules/Audio/AmbienceTrackManagerDebugModule.cpp b/source/toolsmenu/DebugModules/Audio/AmbienceTrackManagerDebugModule.cpp index 5d238bf569..5ae1381d8a 100644 --- a/source/toolsmenu/DebugModules/Audio/AmbienceTrackManagerDebugModule.cpp +++ b/source/toolsmenu/DebugModules/Audio/AmbienceTrackManagerDebugModule.cpp @@ -20,23 +20,23 @@ void AmbienceTrackManagerDebugModule::RenderWindow() { ImGui::Text(vfmt, v); }; - KVRow("Stop", "%s", AEAmbienceTrackManager.m_bStop ? "true" : "false"); - KVRow("Stop Prev", "%s", AEAmbienceTrackManager.m_bStopPrev ? "true" : "false"); - KVRow("b3", "%s", AEAmbienceTrackManager.m_b3 ? "true" : "false"); - KVRow("Is Ambience Radio Active", "%d", AEAmbienceTrackManager.m_IsAmbienceRadioActive); - KVRow("Channel Id", "%d", AEAmbienceTrackManager.m_nChannel); - KVRow("Special Mission Ambience Track", "%d", AEAmbienceTrackManager.m_nSpecialMissionAmbienceTrack); - KVRow("Track Play Time", "%d", AEAmbienceTrackManager.m_nTrackPlayTime); - KVRow("Time In Ms", "%d", AEAmbienceTrackManager.m_nTimeInMs); - KVRow("dword24", "%d", AEAmbienceTrackManager.dword24); - KVRow("byte28", "%d", AEAmbienceTrackManager.byte28); + KVRow("Stop", "%s", AEAmbienceTrackManager.m_OverrideRadio ? "true" : "false"); + KVRow("Stop Prev", "%s", AEAmbienceTrackManager.m_LastAmbienceOverrodeRadio ? "true" : "false"); + KVRow("StartAmbienceAtBeginning", "%s", AEAmbienceTrackManager.m_StartAmbienceAtBeginning ? "true" : "false"); + KVRow("Is Ambience Radio Active", "%d", AEAmbienceTrackManager.m_AmbienceRadioStation); + KVRow("HwClientHandle", "%d", AEAmbienceTrackManager.m_HwClientHandle); + KVRow("Special Mission Ambience Track", "%d", AEAmbienceTrackManager.m_SpecialMissionAmbienceTrackID); + KVRow("PrevAmbiencePlayTimeMs", "%d", AEAmbienceTrackManager.m_PrevAmbiencePlayTimeMs); + KVRow("PrevAmbienceStopTimeMs", "%d", AEAmbienceTrackManager.m_PrevAmbienceStopTimeMs); + KVRow("RequestedSettings.PlayingTrackID", "%d", AEAmbienceTrackManager.m_RequestedSettings.PlayingTrackID); + KVRow("RequestedSettings.TrackFlags", "%d", AEAmbienceTrackManager.m_RequestedSettings.TrackFlags); ImGui::EndTable(); } - ImGui::SliderFloat("Volume %f", &AEAmbienceTrackManager.m_nVolume, -100.f, 100.f, NULL, ImGuiSliderFlags_Logarithmic); - ImGui::SliderFloat("Frequency %f", &AEAmbienceTrackManager.m_fFreqFactor, 0.0f, 100.0f, NULL, ImGuiSliderFlags_Logarithmic); - ImGui::InputInt("Track Id %d", &AEAmbienceTrackManager.m_nTrackId); + ImGui::SliderFloat("Volume %f", &AEAmbienceTrackManager.m_Volume, -100.f, 100.f, NULL, ImGuiSliderFlags_Logarithmic); + ImGui::SliderFloat("Frequency %f", &AEAmbienceTrackManager.m_FreqFactor, 0.0f, 100.0f, NULL, ImGuiSliderFlags_Logarithmic); + ImGui::InputInt("Track Id %d", &AEAmbienceTrackManager.m_RequestedSettings.PlayingTrackID); } void AmbienceTrackManagerDebugModule::RenderMenuEntry() { diff --git a/source/toolsmenu/DebugModules/Audio/UserRadioTrackDebugModule.cpp b/source/toolsmenu/DebugModules/Audio/UserRadioTrackDebugModule.cpp new file mode 100644 index 0000000000..2bb05e25f8 --- /dev/null +++ b/source/toolsmenu/DebugModules/Audio/UserRadioTrackDebugModule.cpp @@ -0,0 +1,56 @@ +#include "StdInc.h" +#include "imgui.h" + +#include "AEUserRadioTrackManager.h" +#include "UserRadioTrackDebugModule.h" + +#ifdef USERTRACK_FLAC_SUPPORT +#include "FLAC/all.h" +#endif + +using namespace ImGui; + +constexpr const char* AudioTypeFileNames[] = {"Unknown", "Vorbis", "WAV", "WMA", "QuickTime (MediaFoundation)", "FLAC"}; + +void UserRadioTrackDebugModule::RenderWindow() { + const notsa::ui::ScopedWindow window{"User Track Audio Manager", {860.f, 290.f}, m_IsOpen}; + if (!m_IsOpen) { + return; + } + + BeginGroup(); + Text("Supported decoders:\n"); + for (auto i = 1u; i < TOTAL_AUDIO_FILE_TYPE; i++) { + Text("\t%s: %s\n", AudioTypeFileNames[i], AEUserRadioTrackManager.m_baDecodersSupported[i] ? "Supported" : "Unsupported"); + } + + Text("\nNum tracks loaded: %u\n", AEUserRadioTrackManager.m_nUserTracksCount); + Text("User tracks:\n"); + const auto& trackSpan = std::span{AEUserRadioTrackManager.m_pUserTracksInfo, (size_t)AEUserRadioTrackManager.m_nUserTracksCount}; + for (auto&& [i, track] : notsa::enumerate(trackSpan)) { + Text( + "Track %u\n\tFormat: %s\n\tPath: '%s'", + i, + AudioTypeFileNames[track.fileType], + fs::path{AEUserRadioTrackManager.GetTrackPath(i)}.filename().string().c_str() + ); + } + +#ifdef USERTRACK_FLAC_SUPPORT + static bool flacDecoderInit = false; + static bool runOnce = true; + if (std::exchange(runOnce, false)) { + auto* decoder = FLAC__stream_decoder_new(); + flacDecoderInit = decoder != nullptr; + } + Text("libFLAC initialize status: %s\n", flacDecoderInit ? "Successful" : "Failed"); +#else + Text("libFLAC is not supported.\n"); +#endif + + EndGroup(); +} + +void UserRadioTrackDebugModule::RenderMenuEntry() { + notsa::ui::DoNestedMenuIL({"Extra", "Audio"}, [&] { ImGui::MenuItem("User Track", nullptr, &m_IsOpen); }); +} diff --git a/source/toolsmenu/DebugModules/Audio/UserRadioTrackDebugModule.h b/source/toolsmenu/DebugModules/Audio/UserRadioTrackDebugModule.h new file mode 100644 index 0000000000..27df48038e --- /dev/null +++ b/source/toolsmenu/DebugModules/Audio/UserRadioTrackDebugModule.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../DebugModule.h" + +class UserRadioTrackDebugModule : public DebugModule { +public: + void RenderWindow() override final; + void RenderMenuEntry() override final; + +private: + bool m_IsOpen{}; +}; diff --git a/source/toolsmenu/DebugModules/AudioZonesDebugModule.cpp b/source/toolsmenu/DebugModules/AudioZonesDebugModule.cpp new file mode 100644 index 0000000000..aa6e40d8a4 --- /dev/null +++ b/source/toolsmenu/DebugModules/AudioZonesDebugModule.cpp @@ -0,0 +1,18 @@ +#include "StdInc.h" + +#include "AudioZonesDebugModule.h" +#include "Lines.h" + +void AudioZonesDebugModule::RenderMenuEntry() { + notsa::ui::DoNestedMenuIL({"Visualization", "Audio zones"}, [&] { ImGui::Checkbox("Show all", &m_ShowAudioZones); }); +} + +void AudioZonesDebugModule::Render3D() { + if (!m_ShowAudioZones) { + return; + } + + for (auto& box : CAudioZones::GetActiveAuZoBoxes()) { + box.DrawWireFrame(CRGBA(255, 0, 0, 255), m_transform); + } +} diff --git a/source/toolsmenu/DebugModules/AudioZonesDebugModule.h b/source/toolsmenu/DebugModules/AudioZonesDebugModule.h new file mode 100644 index 0000000000..131e115381 --- /dev/null +++ b/source/toolsmenu/DebugModules/AudioZonesDebugModule.h @@ -0,0 +1,15 @@ +#pragma once + +#include "DebugModule.h" + +class AudioZonesDebugModule : public DebugModule { +public: + void Render3D() override final; + void RenderMenuEntry() override final; + +private: + bool m_ShowAudioZones{}; + bool m_IsOpen{}; + + CMatrix m_transform = CMatrix::GetIdentity(); +}; diff --git a/source/toolsmenu/DebugModules/CloudsDebugModule.cpp b/source/toolsmenu/DebugModules/CloudsDebugModule.cpp new file mode 100644 index 0000000000..36b3db54c4 --- /dev/null +++ b/source/toolsmenu/DebugModules/CloudsDebugModule.cpp @@ -0,0 +1,68 @@ +#include +#include "CloudsDebugModule.hpp" +#include + +using namespace ImGui; + +namespace notsa { +namespace debugmodules { +void RenderDebugSettingPairs() { + if (!BeginTable("Settings", 6, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_ScrollY)) { + return; + } + + TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed); + TableSetupColumn("Enabled", ImGuiTableColumnFlags_WidthFixed); + TableSetupColumn("Force Render", ImGuiTableColumnFlags_WidthFixed); + + TableHeadersRow(); + + const auto DoRenderSettingPair = [](const char* name, auto& pair) { + const ui::ScopedID scopeID{ name }; + + TableNextRow(); + BeginGroup(); + + TableNextColumn(); + TextUnformatted(name); + + TableNextColumn(); + Checkbox("###Enabled", &pair.Enabled); + + TableNextColumn(); + Checkbox("###Force", &pair.Force); + + EndGroup(); + }; + +#define RenderSettingPair(_name) DoRenderSettingPair(#_name, CClouds::s_DebugSettings._name) + RenderSettingPair(Moon); + RenderSettingPair(Rockstar); + RenderSettingPair(LowClouds); + RenderSettingPair(Rainbow); + RenderSettingPair(Streaks); + RenderSettingPair(VolumetricClouds); +#undef RenderSettingPair + + EndTable(); +} + +void CloudsDebugModule::RenderWindow() { + const ::notsa::ui::ScopedWindow window{ "CloudsDebugModule", {}, m_IsOpen }; + if (!m_IsOpen) { + return; + } + + if (TreeNodeEx("Render Settings")) { + RenderDebugSettingPairs(); + TreePop(); + } +} + +void CloudsDebugModule::RenderMenuEntry() { + notsa::ui::DoNestedMenuIL({ "Extra" }, [&] { + ImGui::MenuItem("Clouds", nullptr, &m_IsOpen); + }); +} +}; // namespace debugmodules +}; // namespace notsa diff --git a/source/toolsmenu/DebugModules/CloudsDebugModule.hpp b/source/toolsmenu/DebugModules/CloudsDebugModule.hpp new file mode 100644 index 0000000000..a1ddb8d47a --- /dev/null +++ b/source/toolsmenu/DebugModules/CloudsDebugModule.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "DebugModule.h" + +namespace notsa { +namespace debugmodules { +class CloudsDebugModule final : public DebugModule { +public: + void RenderWindow() override; + void RenderMenuEntry() override; + +private: + bool m_IsOpen{}; +}; +}; // namespace debugmodules +}; // namespace notsa diff --git a/source/toolsmenu/DebugModules/DebugModule.h b/source/toolsmenu/DebugModules/DebugModule.h index 485b960688..3357e67e1f 100644 --- a/source/toolsmenu/DebugModules/DebugModule.h +++ b/source/toolsmenu/DebugModules/DebugModule.h @@ -1,5 +1,6 @@ #pragma once +// NOTE: Ideally we'd use imconfig.h, but it's too finnicky #define IM_VEC2_CLASS_EXTRA \ operator CVector2D() const { return {x, y}; } \ ImVec2(const CVector2D& v) : x{v.x}, y{v.y} {} \ diff --git a/source/toolsmenu/DebugModules/DebugModules.cpp b/source/toolsmenu/DebugModules/DebugModules.cpp index 6229ba6907..25c568f145 100644 --- a/source/toolsmenu/DebugModules/DebugModules.cpp +++ b/source/toolsmenu/DebugModules/DebugModules.cpp @@ -10,12 +10,14 @@ #include "./Audio/CutsceneTrackManagerDebugModule.h" #include "./Audio/AmbienceTrackManagerDebugModule.h" #include "./Audio/PoliceScannerAudioEntityDebugModule.h" +#include "./Audio/UserRadioTrackDebugModule.h" #include "./CStreamingDebugModule.h" #include "./CPickupsDebugModule.h" #include "./CDarkelDebugModule.h" #include "./HooksDebugModule.h" #include "./CTeleportDebugModule.h" -#include "./FXDebugModule.h" +#include "./ParticleDebugModule.h" +#include "./PostEffectsDebugModule.h" #include "./PoolsDebugModule.h" #include "./TimeCycleDebugModule.h" #include "./CullZonesDebugModule.h" @@ -23,6 +25,9 @@ #include "./Spawner/SpawnerDebugModule.hpp" #include "./ImGuiDebugModule.hpp" #include "./ScriptDebugModule.hpp" +#include "./CloudsDebugModule.hpp" +#include "./AudioZonesDebugModule.h" +#include "./WeaponDebugModule.hpp" DebugModules::DebugModules(ImGuiContext* ctx) : m_ImCtx(ctx) @@ -50,6 +55,8 @@ void DebugModules::Render2D() { } void DebugModules::Render3D() { + ZoneScoped; + for (auto& module : m_Modules) { module->Render3D(); } @@ -64,7 +71,7 @@ void DebugModules::CreateModules() { // "Settings" menu Add(); - Add(); + Add(); // "Visualization" menu Add(); @@ -80,12 +87,17 @@ void DebugModules::CreateModules() { Add(); Add(); Add(); + Add(); Add(); + Add(); + Add(); + Add(); // Stuff that is present in multiple menus Add(); // Visualization + Extra Add(); // Visualization + Extra Add(); // Visualization + Extra + Add(); // Visualization + Extra Add(); // Stats + Extra } diff --git a/source/toolsmenu/DebugModules/ParticleDebugModule.cpp b/source/toolsmenu/DebugModules/ParticleDebugModule.cpp new file mode 100644 index 0000000000..517210b98c --- /dev/null +++ b/source/toolsmenu/DebugModules/ParticleDebugModule.cpp @@ -0,0 +1,50 @@ +#include "StdInc.h" + +#include "ParticleDebugModule.h" +#include "imgui.h" + +using namespace ImGui; + +constexpr static const char* FX_PARTICLES[] = { + "blood_heli", "boat_prop", "camflash", "carwashspray", "cement", "cloudfast", "coke_puff", "coke_trail", "cigarette_smoke", "explosion_barrel", "explosion_crate", + "explosion_door", "exhale", "explosion_fuel_car", "explosion_large", "explosion_medium", "explosion_molotov", "explosion_small", "explosion_tiny", "extinguisher", "flame", + "fire", "fire_med", "fire_large", "flamethrower", "fire_bike", "fire_car", "gunflash", "gunsmoke", "insects", "heli_dust", "jetpack", "jetthrust", "nitro", "molotov_flame", + "overheat_car", "overheat_car_electric", "prt_boatsplash", "prt_cardebris", "prt_collisionsmoke", "prt_glass", "prt_gunshell", "prt_sand", "prt_sand2", "prt_smokeII_3_expand", + "prt_smoke_huge", "prt_spark", "prt_spark_2", "prt_splash", "prt_wake", "prt_watersplash", "prt_wheeldirt", "petrolcan", "puke", "riot_smoke", "spraycan", "smoke30lit", + "smoke30m", "smoke50lit", "shootlight", "smoke_flare", "tank_fire", "teargas", "teargasAD", "tree_hit_fir", "tree_hit_palm", "vent", "vent2", "water_hydrant", "water_ripples", + "water_speed", "water_splash", "water_splash_big", "water_splsh_sml", "water_swim", "waterfall_end", "water_fnt_tme", "water_fountain", "wallbust", "WS_factorysmoke" +}; + +void ParticleDebugModule::RenderWindow() { + const notsa::ui::ScopedWindow window{ "Particles", {860.f, 290.f}, m_IsOpen }; + if (!m_IsOpen) { + return; + } + + BeginGroup(); + + static bool ignoreBC{false}; + Checkbox("Ignore bound checks", &ignoreBC); + + if (Button("Random Fx here")) { + auto* fx = g_fxMan.CreateFxSystem( + CGeneral::RandomChoice(FX_PARTICLES), + FindPlayerCoors(PED_TYPE_PLAYER1), + nullptr, + ignoreBC + ); + + if (fx) + fx->PlayAndKill(); + } + + Separator(); + + EndGroup(); +} + +void ParticleDebugModule::RenderMenuEntry() { + notsa::ui::DoNestedMenuIL({ "Extra" }, [&] { + ImGui::MenuItem("Particles", nullptr, &m_IsOpen); + }); +} diff --git a/source/toolsmenu/DebugModules/ParticleDebugModule.h b/source/toolsmenu/DebugModules/ParticleDebugModule.h new file mode 100644 index 0000000000..ab4f1da31b --- /dev/null +++ b/source/toolsmenu/DebugModules/ParticleDebugModule.h @@ -0,0 +1,12 @@ +#pragma once + +#include "DebugModule.h" + +class ParticleDebugModule : public DebugModule { +public: + void RenderWindow() override final; + void RenderMenuEntry() override final; + +private: + bool m_IsOpen{}; +}; diff --git a/source/toolsmenu/DebugModules/FXDebugModule.cpp b/source/toolsmenu/DebugModules/PostEffectsDebugModule.cpp similarity index 97% rename from source/toolsmenu/DebugModules/FXDebugModule.cpp rename to source/toolsmenu/DebugModules/PostEffectsDebugModule.cpp index 9485079460..00a94e69d7 100644 --- a/source/toolsmenu/DebugModules/FXDebugModule.cpp +++ b/source/toolsmenu/DebugModules/PostEffectsDebugModule.cpp @@ -1,9 +1,9 @@ #include "StdInc.h" -#include "FXDebugModule.h" +#include "PostEffectsDebugModule.h" #include "PostEffects.h" -void FXDebugModule::RenderMenuEntry() { +void PostEffectsDebugModule::RenderMenuEntry() { notsa::ui::DoNestedMenuIL({ "Settings", "Post FX" }, [&] { ImGui::Checkbox("In Cutscene", &CPostEffects::m_bInCutscene ); ImGui::Checkbox("Skip Post Process", &CPostEffects::m_bDisableAllPostEffect); diff --git a/source/toolsmenu/DebugModules/FXDebugModule.h b/source/toolsmenu/DebugModules/PostEffectsDebugModule.h similarity index 64% rename from source/toolsmenu/DebugModules/FXDebugModule.h rename to source/toolsmenu/DebugModules/PostEffectsDebugModule.h index bd1645a790..d6a8a11f02 100644 --- a/source/toolsmenu/DebugModules/FXDebugModule.h +++ b/source/toolsmenu/DebugModules/PostEffectsDebugModule.h @@ -2,7 +2,7 @@ #include "DebugModule.h" -class FXDebugModule : public DebugModule { +class PostEffectsDebugModule : public DebugModule { public: void RenderMenuEntry() override final; }; diff --git a/source/toolsmenu/DebugModules/WeaponDebugModule.cpp b/source/toolsmenu/DebugModules/WeaponDebugModule.cpp new file mode 100644 index 0000000000..e11e3a0f2d --- /dev/null +++ b/source/toolsmenu/DebugModules/WeaponDebugModule.cpp @@ -0,0 +1,24 @@ +#include + +#include "WeaponDebugModule.hpp" + +namespace notsa { +namespace debugmodules { +void WeaponDebugModule::RenderWindow() { + const ::notsa::ui::ScopedWindow window{ "WeaponDebugModule", {}, m_IsOpen }; + if (!m_IsOpen) { + return; + } + auto& ds = CWeapon::s_DebugSettings; + + ImGui::Checkbox("No Shoot Delay", &ds.NoShotDelay); + ImGui::SetItemTooltip("Changes shoot delay for rocketlauncher, etc"); +} + +void WeaponDebugModule::RenderMenuEntry() { + notsa::ui::DoNestedMenuIL({ "Extra" }, [&] { + ImGui::MenuItem("Weapon", nullptr, &m_IsOpen); + }); +} +}; // namespace debugmodules +}; // namespace notsa diff --git a/source/toolsmenu/DebugModules/WeaponDebugModule.hpp b/source/toolsmenu/DebugModules/WeaponDebugModule.hpp new file mode 100644 index 0000000000..10900d5c90 --- /dev/null +++ b/source/toolsmenu/DebugModules/WeaponDebugModule.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "DebugModule.h" + +namespace notsa { +namespace debugmodules { +class WeaponDebugModule final : public DebugModule { +public: + void RenderWindow() override; + void RenderMenuEntry() override; + +private: + bool m_IsOpen{}; +}; +}; // namespace debugmodules +}; // namespace notsa diff --git a/source/toolsmenu/UIRenderer.cpp b/source/toolsmenu/UIRenderer.cpp index da9ed372ab..dece561bd8 100644 --- a/source/toolsmenu/UIRenderer.cpp +++ b/source/toolsmenu/UIRenderer.cpp @@ -25,12 +25,9 @@ UIRenderer::UIRenderer() : { IMGUI_CHECKVERSION(); - m_ImIO->WantCaptureMouse = true; - m_ImIO->WantCaptureKeyboard = true; - m_ImIO->WantSetMousePos = true; - m_ImIO->MouseDrawCursor = false; - m_ImIO->ConfigFlags = ImGuiConfigFlags_NavEnableSetMousePos | ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad; - m_ImIO->DisplaySize = ImVec2(SCREEN_WIDTH, SCREEN_HEIGHT); + m_ImIO->ConfigFlags = ImGuiConfigFlags_NavEnableKeyboard | ImGuiConfigFlags_NavEnableGamepad | ImGuiConfigFlags_DockingEnable | ImGuiConfigFlags_ViewportsEnable; + m_ImIO->DisplaySize = ImVec2(SCREEN_WIDTH, SCREEN_HEIGHT); + m_ImIO->NavActive = false; ImGui_ImplWin32_Init(PSGLOBAL(window)); ImGui_ImplDX9_Init(GetD3DDevice()); @@ -43,77 +40,12 @@ UIRenderer::~UIRenderer() { ImGui_ImplWin32_Shutdown(); ImGui::DestroyContext(m_ImCtx); - DEV_LOG("Good bye!"); -} - -void UIRenderer::UpdateInput() { - if (!Visible()) { - return; - } - - // Update mouse - { - const auto WHEEL_SPEED = 20.0f; - - CPad::GetPad()->DisablePlayerControls = true; - - // Update position - auto& MousePos = m_ImIO->MousePos; - MousePos.x += CPad::NewMouseControllerState.X; - MousePos.y -= CPad::NewMouseControllerState.Y; - - MousePos.x = std::clamp(MousePos.x, 0.0f, SCREEN_WIDTH); - MousePos.y = std::clamp(MousePos.y, 0.0f, SCREEN_HEIGHT); - - if (CPad::NewMouseControllerState.wheelDown) - m_ImIO->MouseWheel -= (WHEEL_SPEED * m_ImIO->DeltaTime); - - if (CPad::NewMouseControllerState.wheelUp) - m_ImIO->MouseWheel += (WHEEL_SPEED * m_ImIO->DeltaTime); - - m_ImIO->MouseDown[ImGuiMouseButton_Left] = CPad::NewMouseControllerState.lmb; - m_ImIO->MouseDown[ImGuiMouseButton_Right] = CPad::NewMouseControllerState.rmb; - m_ImIO->MouseDown[ImGuiMouseButton_Middle] = CPad::NewMouseControllerState.mmb; - - CPad::NewMouseControllerState.X = 0.0f; - CPad::NewMouseControllerState.Y = 0.0f; - } - - // Update keyboard - { - BYTE KeyStates[256]; - - VERIFY(GetKeyboardState(KeyStates)); - - const auto IsKeyDown = [&](auto key) { return (KeyStates[key] & 0x80) != 0; }; - - for (auto key = 0; key < 256; key++) { - // Check if there was a state change - if (IsKeyDown(key) == m_ImIO->KeysDown[key]) { - continue; - } - - // There was! - if (IsKeyDown(key)) { // Key is now down - m_ImIO->KeysDown[key] = true; - - char ResultUTF8[16] = {0}; - if (ToAscii(key, MapVirtualKey(key, 0), KeyStates, (LPWORD)ResultUTF8, 0)) { - m_ImIO->AddInputCharactersUTF8(ResultUTF8); - } - } else { // Key is now released - m_ImIO->KeysDown[key] = false; - } - } - - m_ImIO->KeyCtrl = IsKeyDown(VK_CONTROL); - m_ImIO->KeyShift = IsKeyDown(VK_SHIFT); - m_ImIO->KeyAlt = IsKeyDown(VK_MENU); - m_ImIO->KeySuper = false; - } + //DEV_LOG("Good bye!"); } void UIRenderer::PreRenderUpdate() { + ZoneScoped; + m_ImIO->DeltaTime = CTimer::GetTimeStepInSeconds(); m_ImIO->DisplaySize = ImVec2(SCREEN_WIDTH, SCREEN_HEIGHT); // Update display size, in case of window resize after imgui was already initialized @@ -121,21 +53,34 @@ void UIRenderer::PreRenderUpdate() { DebugCode(); ReversibleHooks::CheckAll(); - if (const auto pad = CPad::GetPad(); pad->DebugMenuJustPressed()) { - m_ShowMenu = !m_ShowMenu; - m_ImIO->MouseDrawCursor = m_ShowMenu; - pad->bPlayerSafe = m_ShowMenu; + // A delay of a frame has to be added, otherwise the release of F7 wont be processed + // and the menu will close + const auto Shortcut = [](ImGuiKeyChord chord) { + return ImGui::Shortcut(chord, ImGuiKeyOwner_Any, ImGuiInputFlags_RouteAlways); + }; + if (Shortcut(ImGuiKey_F7) || Shortcut(ImGuiKey_M | ImGuiMod_Ctrl)) { + m_InputActive = !m_InputActive; + m_ImIO->MouseDrawCursor = m_InputActive; + m_ImIO->NavActive = m_InputActive; + CPad::GetPad()->DisablePlayerControls = m_InputActive; + CPad::GetPad()->Clear(m_InputActive, true); } } +void UIRenderer::PostRenderUpdate() { + m_ImIO->NavActive = m_InputActive; // ImGUI clears `NavActive` every frame, so have to set it here. +} + void UIRenderer::DrawLoop() { + ZoneScoped; + if (m_ReInitRequested) { ResetSingleton(); // This will destruct the current object so we gotta stop here. return; } PreRenderUpdate(); - + ImGui_ImplWin32_NewFrame(); ImGui_ImplDX9_NewFrame(); ImGui::NewFrame(); @@ -144,14 +89,26 @@ void UIRenderer::DrawLoop() { ImGui::EndFrame(); ImGui::Render(); ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData()); - ImGui_ImplDX9_InvalidateDeviceObjects(); + //ImGui_ImplDX9_InvalidateDeviceObjects(); + + PostRenderUpdate(); + + // Update and Render additional Platform Windows + if (m_ImIO->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } } void UIRenderer::Render2D() { + ZoneScoped; + m_DebugModules.Render2D(); } void UIRenderer::Render3D() { + ZoneScoped; + m_DebugModules.Render3D(); } @@ -163,30 +120,30 @@ void UIRenderer::DebugCode() { if (UIRenderer::Visible() || CPad::NewKeyState.lctrl || CPad::NewKeyState.rctrl) return; - if (pad->IsStandardKeyJustPressed('8')) { - - CPointRoute route{}; - - const auto r = 10.f; - const auto totalAngle = PI * 2.f; - for (auto a = 0.f; a < totalAngle; a += totalAngle / 8.f) { - route.AddPoints(player->GetPosition() + CVector{std::cosf(a), std::sinf(a), 0.f} *r); - } - - player->GetTaskManager().SetTask( - new CTaskComplexFollowPointRoute{ - PEDMOVE_SPRINT, - route, - CTaskComplexFollowPointRoute::Mode::ONE_WAY, - 3.f, - 3.f, - false, - true, - true - }, - TASK_PRIMARY_PRIMARY - ); - } + //if (pad->IsStandardKeyJustPressed('8')) { + // + // CPointRoute route{}; + // + // const auto r = 10.f; + // const auto totalAngle = PI * 2.f; + // for (auto a = 0.f; a < totalAngle; a += totalAngle / 8.f) { + // route.AddPoints(player->GetPosition() + CVector{std::cosf(a), std::sinf(a), 0.f} *r); + // } + // + // player->GetTaskManager().SetTask( + // new CTaskComplexFollowPointRoute{ + // PEDMOVE_SPRINT, + // route, + // CTaskComplexFollowPointRoute::Mode::ONE_WAY, + // 3.f, + // 3.f, + // false, + // true, + // true + // }, + // TASK_PRIMARY_PRIMARY + // ); + //} if (pad->IsStandardKeyJustPressed('0')) { if (const auto veh = FindPlayerVehicle()) { @@ -211,8 +168,9 @@ void UIRenderer::DebugCode() { if (pad->IsStandardKeyJustPressed('3')) { CCheat::VehicleCheat(MODEL_INFERNUS); } - if (pad->IsStandardKeyJustPressed('4')) { - CTimer::Suspend(); + if (pad->IsStandardKeyJustDown('8')) { + TheCamera.AddShakeSimple(10000.f, 1, 10.f); + DEV_LOG("Hey"); } if (pad->IsStandardKeyJustPressed('5')) { if (const auto veh = FindPlayerVehicle()) { diff --git a/source/toolsmenu/UIRenderer.h b/source/toolsmenu/UIRenderer.h index 7e1d35cc4d..e748e0bb78 100644 --- a/source/toolsmenu/UIRenderer.h +++ b/source/toolsmenu/UIRenderer.h @@ -1,7 +1,7 @@ #pragma once #include "DebugModules/DebugModules.h" -#include "extensions/utility.hpp" +#include namespace notsa { namespace ui { @@ -10,11 +10,14 @@ class UIRenderer : public notsa::Singleton { UIRenderer(); ~UIRenderer(); - bool Visible() { return m_ShowMenu; } + bool Visible() { return m_InputActive; } //! Request restart of render (done on before frame) void RequestReInit() { m_ReInitRequested = true; } + //! Same as ImGui::GetIO(), but won't crash the code if called before ctx is created + auto GetImIO() const { return m_ImIO; } + private: //! Render 3D stuff in the world (If rendered elsewhere it won't be visible) void Render3D(); @@ -22,26 +25,28 @@ class UIRenderer : public notsa::Singleton { //! Render 2D stuff (Called after a new (ImGui) frame has been began) void Render2D(); - //! Update input (mouse and keyboard) if necessary - void UpdateInput(); - //! Called before a new (ImGui) frame is started (and after the previous one has ended) void PreRenderUpdate(); + //! Called after the frame has ended + void PostRenderUpdate(); + //! The actual draw loop void DrawLoop(); //! Random code you want to run (Called from `PreRenderUpdate`) void DebugCode(); + private: friend void ::RenderEffects(); // For `Render3D()` friend void ::FrontendIdle(); // For `DrawLoop()` VVV friend void ::Idle(void*); // For `DrawLoop()` Yes, called in 2 places, but they are never called in the same frame (As `Idle` is called when not in the menu only) friend void ::CPad::UpdatePads(); + friend void ::Idle(void*); private: bool m_Initialised{}; - bool m_ShowMenu{}; + bool m_InputActive{}; bool m_ReInitRequested{}; ImGuiContext* m_ImCtx{}; ImGuiIO* m_ImIO{};