From 4a7fdc086e29702085db7677b93f0b74193b8175 Mon Sep 17 00:00:00 2001 From: yukani Date: Sun, 14 Jul 2024 19:08:53 +0300 Subject: [PATCH] CTheScripts (#654) Co-authored-by: Pirulax --- source/extensions/EntityRef.hpp | 22 + source/extensions/File.hpp | 34 + source/extensions/utility.hpp | 8 +- source/game_sa/Checkpoint.h | 27 +- source/game_sa/Checkpoints.cpp | 6 +- source/game_sa/Checkpoints.h | 2 +- source/game_sa/DecisionMakerTypes.h | 5 +- source/game_sa/Entity/Object/Object.cpp | 13 +- source/game_sa/Entity/Object/Object.h | 5 +- source/game_sa/Entity/Ped/Ped.cpp | 8 +- source/game_sa/Entity/Ped/Ped.h | 2 +- source/game_sa/Entity/Vehicle/Automobile.cpp | 5 + source/game_sa/Entity/Vehicle/Heli.cpp | 24 +- source/game_sa/Entity/Vehicle/Heli.h | 16 +- source/game_sa/Entity/Vehicle/Vehicle.cpp | 4 +- source/game_sa/FireManager.h | 13 +- source/game_sa/Frontend/MenuManager_Draw.cpp | 14 +- source/game_sa/Fx/FxManager.cpp | 5 +- source/game_sa/IKChain_c.h | 2 +- source/game_sa/MenuManager.h | 2 +- source/game_sa/PedGroupMembership.cpp | 2 +- .../game_sa/Scripts/CommandParser/ReadArg.hpp | 8 +- source/game_sa/Scripts/Commands/Player.cpp | 4 - source/game_sa/Scripts/RunningScript.h | 2 +- source/game_sa/Scripts/SCMChunks.hpp | 108 ++ source/game_sa/Scripts/TheScripts.cpp | 1468 ++++++++++++++++- source/game_sa/Scripts/TheScripts.h | 279 ++-- source/game_sa/ScriptsForBrains.cpp | 17 +- source/game_sa/ScriptsForBrains.h | 22 +- .../DebugModules/CheckpointsDebugModule.cpp | 2 +- 30 files changed, 1850 insertions(+), 279 deletions(-) create mode 100644 source/extensions/File.hpp create mode 100644 source/game_sa/Scripts/SCMChunks.hpp diff --git a/source/extensions/EntityRef.hpp b/source/extensions/EntityRef.hpp index 5113559d40..bc8fa97403 100644 --- a/source/extensions/EntityRef.hpp +++ b/source/extensions/EntityRef.hpp @@ -18,6 +18,28 @@ struct EntityRef { } } + // No need for moving/copying the class itself, but just the pointer directly to it's new address + // (This way we avoid extra calls to `CleanUpOld/RegisterReference`...) + EntityRef& operator=(EntityRef&&) = delete; + EntityRef& operator=(const EntityRef&) = delete; + EntityRef(EntityRef&&) = delete; + EntityRef(const EntityRef&) = delete; + + // This is the only magic we need + EntityRef& operator=(T* ptr) { + if (m_Ptr) { + m_Ptr->CleanUpOldReference(reinterpret_cast(&m_Ptr)); + } + m_Ptr = ptr; + if (m_Ptr) { + m_Ptr->RegisterReference(reinterpret_cast(&m_Ptr)); + } + return *this; + } + + T* Get() { return m_Ptr; } + const T* Get() const { return m_Ptr; } + operator T*() const { return m_Ptr; } operator T*() { return m_Ptr; } diff --git a/source/extensions/File.hpp b/source/extensions/File.hpp new file mode 100644 index 0000000000..2158be0a31 --- /dev/null +++ b/source/extensions/File.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include "FileMgr.h" + +namespace notsa { +// RAII-wrapper for CFileMgr files. +class File { +public: + File(const char* path, const char* mode) : + m_file(CFileMgr::OpenFile(path, mode)) + {} + + ~File() + { + if (m_file) + CloseFile(); + } + + operator bool() const { return m_file != nullptr; } + + auto Read(void* buf, size_t size) { return CFileMgr::Read(m_file, buf, size); } + auto Write(const void* buf, size_t size) { return CFileMgr::Write(m_file, buf, size); } + auto Seek(long offset, int32 origin) { return CFileMgr::Seek(m_file, offset, origin); } + auto ReadLine(char* str, int32 num) { return CFileMgr::ReadLine(m_file, str, num); } + auto CloseFile() { return CFileMgr::CloseFile(std::exchange(m_file, nullptr)); } + auto GetTotalSize() const { return CFileMgr::GetTotalSize(m_file); } + auto Tell() const { return CFileMgr::Tell(m_file); } + auto GetErrorReadWrite() const { return CFileMgr::GetErrorReadWrite(m_file); } + void SeekNextLine() { CFileMgr::SeekNextLine(m_file); } + +private: + FILESTREAM m_file{}; +}; +}; diff --git a/source/extensions/utility.hpp b/source/extensions/utility.hpp index 21ca442bad..e7521f5fa7 100644 --- a/source/extensions/utility.hpp +++ b/source/extensions/utility.hpp @@ -358,10 +358,15 @@ inline constexpr bool is_standard_integer = std::is_integral_v && !is_any_of_ //! NOTE: Not a complete replacement for std::format_to, //! e.g. it doesn't use output iterators. i don't care. template -void format_to_sz(char (&out)[N], std::string_view fmt, Args&&... args) { +void format_to_sz(char(&out)[N], std::string_view fmt, Args&&... args) { *std::vformat_to(out, fmt, std::make_format_args(args...)) = '\0'; } +//! Reads a pointer as specified type. +template requires std::is_trivially_constructible_v +T ReadAs(void* ptr) { + return *static_cast(ptr); +} //! Safe C string copying, use this instead of strcpy. inline void string_copy(char* out, const char* from, size_t size) { std::snprintf(out, size, "%s", from); @@ -372,5 +377,4 @@ template void string_copy(char (&out)[N], const char* from) { std::snprintf(out, N, "%s", from); } - }; diff --git a/source/game_sa/Checkpoint.h b/source/game_sa/Checkpoint.h index 43faf92cb7..4756174117 100644 --- a/source/game_sa/Checkpoint.h +++ b/source/game_sa/Checkpoint.h @@ -29,6 +29,7 @@ enum class eCheckpointType : uint32 { NUM = 0x9, ///< Count of different checkpoint types NA = 0x101, ///< Sentinel value (Used for markers not in use) }; +using eCheckpointTypeU16 = notsa::WEnumU16; class CCheckpoint { public: @@ -66,19 +67,19 @@ class CCheckpoint { void SetHeading(float headingDeg); public: - notsa::WEnumU16 m_Type{eCheckpointType::NA}; - bool m_IsUsed{false}; - bool m_RotFlag{true}; - int32 m_ID{0}; - CRGBA m_Colour{255, 255, 255, 255}; - uint16 m_PulsePeriod{1'024}; - int16 m_RotateRate{5}; - CVector m_Pos{}; - CVector m_Fwd{}; ///< Pointing direction - float m_PulseFraction{0.25f}; - float m_Size{1.f}; - float m_DistToCam2D{0.f}; ///< (AKA CameraRange) - Distance to player's camera at the moment it's placed - float m_MultiSize{0.f}; + eCheckpointTypeU16 m_Type{ eCheckpointType::NA }; + bool m_IsUsed{ false }; + bool m_RotFlag{ true }; + int32 m_ID{ 0 }; + CRGBA m_Colour{ 255, 255, 255, 255 }; + uint16 m_PulsePeriod{ 1'024 }; + int16 m_RotateRate{ 5 }; + CVector m_Pos{}; + CVector m_Fwd{}; ///< Pointing direction + float m_PulseFraction{ 0.25f }; + float m_Size{ 1.f }; + float m_DistToCam2D{ 0.f }; ///< (AKA CameraRange) - Distance to player's camera at the moment it's placed + float m_MultiSize{ 0.f }; }; VALIDATE_SIZE(CCheckpoint, 0x38); diff --git a/source/game_sa/Checkpoints.cpp b/source/game_sa/Checkpoints.cpp index 9df44b936a..11f107cd8a 100644 --- a/source/game_sa/Checkpoints.cpp +++ b/source/game_sa/Checkpoints.cpp @@ -53,11 +53,11 @@ void CCheckpoints::Update() { // 0x722C40 CCheckpoint* CCheckpoints::PlaceMarker( uint32 id, - notsa::WEnumU16 type, + eCheckpointTypeU16 type, const CVector& pos, const CVector& direction, float size, - uint8 red, uint8 green, uint8 blue, uint8 alpha, + CRGBA color, uint16 pulsePeriod, float pulseFraction, int16 rotateRate @@ -108,7 +108,7 @@ CCheckpoint* CCheckpoints::PlaceMarker( if (cp) { // 0x722F07 cp->m_DistToCam2D = cpDistToPlayer2D; - cp->m_Colour = {red, green, blue, alpha}; + cp->m_Colour = color; cp->m_Size = size; cp->m_RotateRate = rotateRate; cp->m_Pos = pos; diff --git a/source/game_sa/Checkpoints.h b/source/game_sa/Checkpoints.h index 01608a34f1..ce7e6a8dc8 100644 --- a/source/game_sa/Checkpoints.h +++ b/source/game_sa/Checkpoints.h @@ -18,7 +18,7 @@ class CCheckpoints { static void Shutdown(); static void SetHeading(uint32 id, float angle); static void Update(); - static CCheckpoint* PlaceMarker(uint32 id, notsa::WEnumU16 type, const CVector& posn, const CVector& direction, float size, uint8 red, uint8 green, uint8 blue, uint8 alpha, uint16 pulsePeriod, float pulseFraction, int16 rotateRate); + static CCheckpoint* PlaceMarker(uint32 id, eCheckpointTypeU16 type, const CVector& posn, const CVector& direction, float size, CRGBA color, uint16 pulsePeriod, float pulseFraction, int16 rotateRate); /*! * @brief Set the position of a checkpoint with the given `id` * @param id ID of the checkpoint diff --git a/source/game_sa/DecisionMakerTypes.h b/source/game_sa/DecisionMakerTypes.h index 6616a0e7f1..3bef87aa49 100644 --- a/source/game_sa/DecisionMakerTypes.h +++ b/source/game_sa/DecisionMakerTypes.h @@ -20,7 +20,10 @@ class CDecisionMakerTypesFileLoader { class CDecisionMakerTypes { public: - static inline std::array& ScriptReferenceIndex = *(std::array*)0xC0AFF4; + static constexpr auto NUM_TYPES = 20u; + + static inline auto& ScriptReferenceIndex = *(std::array*)0xC0AFF4; + static inline auto& m_bIsActive = *(std::array*)0xC0B01C; static void InjectHooks(); diff --git a/source/game_sa/Entity/Object/Object.cpp b/source/game_sa/Entity/Object/Object.cpp index 9bcb305fed..d1eafa3f38 100644 --- a/source/game_sa/Entity/Object/Object.cpp +++ b/source/game_sa/Entity/Object/Object.cpp @@ -121,13 +121,12 @@ CObject::CObject(CDummyObject* dummyObj) : CPhysical() { // 0x59F660 CObject::~CObject() { - if (objectFlags.b0x200000 || objectFlags.b0x100000) { - const auto iIndex = SCMToModelId(CTheScripts::ScriptsForBrains.m_aScriptForBrains[m_wScriptTriggerIndex].m_nIMGindex); + if (objectFlags.b0x100000_0x200000) { + const auto iIndex = SCMToModelId(CTheScripts::ScriptsForBrains.m_aScriptForBrains[m_nStreamedScriptBrainToLoad].m_StreamedScriptIndex); CStreaming::SetMissionDoesntRequireModel(iIndex); - objectFlags.b0x100000 = false; - objectFlags.b0x200000 = false; - CTheScripts::RemoveFromWaitingForScriptBrainArray(this, m_wScriptTriggerIndex); - m_wScriptTriggerIndex = -1; + objectFlags.b0x100000_0x200000 = 0; + CTheScripts::RemoveFromWaitingForScriptBrainArray(this, m_nStreamedScriptBrainToLoad); + m_nStreamedScriptBrainToLoad = -1; } if (objectFlags.bHasNoModel) { @@ -865,7 +864,7 @@ void CObject::Init() { m_nColLighting.day = 0x8; m_nColLighting.night = 0x4; - m_wScriptTriggerIndex = -1; + m_nStreamedScriptBrainToLoad = -1; } // 0x59FB50 diff --git a/source/game_sa/Entity/Object/Object.h b/source/game_sa/Entity/Object/Object.h index f7e3917e07..0741450cd1 100644 --- a/source/game_sa/Entity/Object/Object.h +++ b/source/game_sa/Entity/Object/Object.h @@ -52,8 +52,7 @@ class NOTSA_EXPORT_VTABLE CObject : public CPhysical { uint32 bIsScaled : 1; uint32 bCanBeAttachedToMagnet : 1; uint32 bDamaged : 1; - uint32 b0x100000 : 1; - uint32 b0x200000 : 1; + uint32 b0x100000_0x200000 : 2; // something something scripts for brains uint32 bFadingIn : 1; // works only for objects with type 2 (OBJECT_MISSION) uint32 bAffectedByColBrightness : 1; @@ -82,7 +81,7 @@ class NOTSA_EXPORT_VTABLE CObject : public CPhysical { float m_fScale; CObjectData* m_pObjectInfo; CFire* m_pFire; - int16 m_wScriptTriggerIndex; + int16 m_nStreamedScriptBrainToLoad; int16 m_wRemapTxd; // this is used for detached car parts RwTexture* m_pRemapTexture; // this is used for detached car parts CDummyObject* m_pDummyObject; // used for dynamic objects like garage doors, train crossings etc. diff --git a/source/game_sa/Entity/Ped/Ped.cpp b/source/game_sa/Entity/Ped/Ped.cpp index 7fb006fd92..6c4e25b0d6 100644 --- a/source/game_sa/Entity/Ped/Ped.cpp +++ b/source/game_sa/Entity/Ped/Ped.cpp @@ -298,7 +298,7 @@ CPed::CPed(ePedType pedType) : CPhysical(), m_pedIK{CPedIK(this)} { field_758 = 0; m_fRemovalDistMultiplier = 1.0f; - m_nSpecialModelIndex = -1; + m_StreamedScriptBrainToLoad = -1; CPopulation::UpdatePedCount(this, 0); @@ -318,10 +318,10 @@ CPed::~CPed() { // Remove script brain if (bWaitingForScriptBrainToLoad) { - CStreaming::SetMissionDoesntRequireModel(SCMToModelId(CTheScripts::ScriptsForBrains.m_aScriptForBrains[m_nSpecialModelIndex].m_nIMGindex)); + CStreaming::SetMissionDoesntRequireModel(SCMToModelId(CTheScripts::ScriptsForBrains.m_aScriptForBrains[m_StreamedScriptBrainToLoad].m_StreamedScriptIndex)); bWaitingForScriptBrainToLoad = false; - CTheScripts::RemoveFromWaitingForScriptBrainArray(this, m_nSpecialModelIndex); - m_nSpecialModelIndex = -1; + CTheScripts::RemoveFromWaitingForScriptBrainArray(this, m_StreamedScriptBrainToLoad); + m_StreamedScriptBrainToLoad = -1; } CWorld::Remove(this); diff --git a/source/game_sa/Entity/Ped/Ped.h b/source/game_sa/Entity/Ped/Ped.h index fbcb69dc77..190adcc299 100644 --- a/source/game_sa/Entity/Ped/Ped.h +++ b/source/game_sa/Entity/Ped/Ped.h @@ -337,7 +337,7 @@ class NOTSA_EXPORT_VTABLE CPed : public CPhysical { CCoverPoint* m_pCoverPoint; CEntryExit* m_pEnex; // CEnEx * float m_fRemovalDistMultiplier; - int16 m_nSpecialModelIndex; + int16 m_StreamedScriptBrainToLoad; int32 field_798; public: diff --git a/source/game_sa/Entity/Vehicle/Automobile.cpp b/source/game_sa/Entity/Vehicle/Automobile.cpp index 30af0ad7d9..1c7c00bc7d 100644 --- a/source/game_sa/Entity/Vehicle/Automobile.cpp +++ b/source/game_sa/Entity/Vehicle/Automobile.cpp @@ -577,6 +577,11 @@ void CAutomobile::ProcessControl() } for (auto& collisionEntity : m_apWheelCollisionEntity) { + // FIXME(yukani): I need this to test anything outside, do not remove + // until game doesn't crash on me here. + if (collisionEntity == (CPhysical*)0xffffffff) { + collisionEntity = nullptr; + } if (collisionEntity) { vehicleFlags.bRestingOnPhysical = true; if (!CWorld::bForceProcessControl && collisionEntity->m_bIsInSafePosition) { diff --git a/source/game_sa/Entity/Vehicle/Heli.cpp b/source/game_sa/Entity/Vehicle/Heli.cpp index 132aa5037d..28c8e8ae5f 100644 --- a/source/game_sa/Entity/Vehicle/Heli.cpp +++ b/source/game_sa/Entity/Vehicle/Heli.cpp @@ -184,14 +184,22 @@ void CHeli::SwitchPoliceHelis(bool enable) { // 0x6C58E0 void CHeli::SearchLightCone(int32 coronaIndex, - CVector origin, CVector target, + CVector origin, + CVector target, float targetRadius, float power, - uint8 unknownFlag, uint8 drawShadow, - CVector* useless0, CVector* useless1, CVector* useless2, - bool a11, float baseRadius, float a13,float a14,float a15 + uint8 unknownFlag, + uint8 drawShadow, + CVector& useless0, + CVector& useless1, + CVector& useless2, + bool a11, + float baseRadius, + float a13, + float a14, + float a15 ) { - ((void(__cdecl*)(int32, CVector, CVector, float, float, uint8, uint8, CVector*, CVector*, CVector*, bool, float, float, float, float))0x6C58E0)(coronaIndex, origin, target, targetRadius, power, unknownFlag, drawShadow, useless0, useless1, useless2, a11, baseRadius, a13, a14, a15); + ((void(__cdecl*)(int32, CVector, CVector, float, float, uint8, uint8, CVector&, CVector&, CVector&, bool, float, float, float, float))0x6C58E0)(coronaIndex, origin, target, targetRadius, power, unknownFlag, drawShadow, useless0, useless1, useless2, a11, baseRadius, a13, a14, a15); } // 0x6C6520 @@ -245,9 +253,9 @@ void CHeli::RenderAllHeliSearchLights() { light.m_fPower, light.field_24, light.m_bDrawShadow, - light.m_vecUseless, - &light.m_vecUseless[1], - &light.m_vecUseless[2], + light.m_vecUseless[0], + light.m_vecUseless[1], + light.m_vecUseless[2], false, 0.05f, 0.0f, diff --git a/source/game_sa/Entity/Vehicle/Heli.h b/source/game_sa/Entity/Vehicle/Heli.h index 1f6864cb5c..46fd315121 100644 --- a/source/game_sa/Entity/Vehicle/Heli.h +++ b/source/game_sa/Entity/Vehicle/Heli.h @@ -127,12 +127,20 @@ class NOTSA_EXPORT_VTABLE CHeli : public CAutomobile { static void SpecialHeliPreRender(); // dummy function static void SwitchPoliceHelis(bool enable); static void SearchLightCone(int32 coronaIndex, - CVector origin, CVector target, + CVector origin, + CVector target, float targetRadius, float power, - uint8 unknownFlag, uint8 drawShadow, - CVector* useless0, CVector* useless1, CVector* useless2, - bool a11, float baseRadius, float a13, float a14, float a15); + uint8 clipIfColliding, + uint8 drawShadow, + CVector& useless0, + CVector& useless1, + CVector& useless2, + bool a11, + float baseRadius, + float a13, + float a14, + float a15); static CHeli* GenerateHeli(CPed* target, bool newsHeli); static void TestSniperCollision(CVector* origin, CVector* target); static void UpdateHelis(); diff --git a/source/game_sa/Entity/Vehicle/Vehicle.cpp b/source/game_sa/Entity/Vehicle/Vehicle.cpp index 132177b1cf..4636a8c716 100644 --- a/source/game_sa/Entity/Vehicle/Vehicle.cpp +++ b/source/game_sa/Entity/Vehicle/Vehicle.cpp @@ -3080,7 +3080,7 @@ void CVehicle::SetupRender() { vehicleFlags.bDontSetColourWhenRemapping = false; } else { // Make sure it's loaded, if not, request it to be loaded - if (CStreaming::IsModelLoaded(ModelIdToTXD(m_nRemapTxd))) { + if (CStreaming::IsModelLoaded(TXDToModelId(m_nRemapTxd))) { // If there was a remap texture set, remove it if (m_pRemapTexture) { m_pRemapTexture = nullptr; @@ -3105,7 +3105,7 @@ void CVehicle::SetupRender() { m_nPrimaryColor = 1; } } else { - CStreaming::RequestModel(ModelIdToTXD(m_nRemapTxd), STREAMING_KEEP_IN_MEMORY); + CStreaming::RequestModel(TXDToModelId(m_nRemapTxd), STREAMING_KEEP_IN_MEMORY); } } diff --git a/source/game_sa/FireManager.h b/source/game_sa/FireManager.h index 2ee1649245..61b578cefc 100644 --- a/source/game_sa/FireManager.h +++ b/source/game_sa/FireManager.h @@ -14,7 +14,7 @@ class CEntity; class CFireManager { public: - CFire m_aFires[MAX_NUM_FIRES]; + std::array m_aFires; uint32 m_nMaxFireGenerationsAllowed; public: @@ -57,16 +57,19 @@ class CFireManager { uint32 GetNumOfFires(); CFire& GetRandomFire(); + // NOTSA + CFire& Get(size_t idx) + { + assert(m_aFires[idx].IsActive()); + return m_aFires[idx]; + } + auto GetIndexOf(const CFire* fire) const { return std::distance(m_aFires.data(), fire); } private: friend void InjectHooksMain(); static void InjectHooks(); CFireManager* Constructor(); CFireManager* Destructor(); - - // NOTSA - CFire& Get(size_t idx) { return m_aFires[idx]; } - auto GetIndexOf(const CFire* fire) const { return std::distance(std::begin(m_aFires), fire); } }; VALIDATE_SIZE(CFireManager, 0x964); diff --git a/source/game_sa/Frontend/MenuManager_Draw.cpp b/source/game_sa/Frontend/MenuManager_Draw.cpp index fd63a7c293..a74887b337 100644 --- a/source/game_sa/Frontend/MenuManager_Draw.cpp +++ b/source/game_sa/Frontend/MenuManager_Draw.cpp @@ -357,8 +357,8 @@ void CMenuManager::DrawWindow(const CRect& coords, const char* key, uint8 color, } // 0x578F50, untested -void CMenuManager::DrawWindowedText(float x, float y, float wrap, const char* str1, const char* str2, eFontAlignment alignment) { - // return plugin::CallMethod<0x578F50, CMenuManager*, float, float, float, Const char*, Const char*, eFontAlignment>(this, x, y, a4, str, str2, alignment); +void CMenuManager::DrawWindowedText(float x, float y, float wrap, const char* title, const char* message, eFontAlignment alignment) { + // return plugin::CallMethod<0x578F50, CMenuManager*, float, float, float, Const char*, Const char*, eFontAlignment>(this, x, y, a4, str, message, alignment); CFont::SetWrapx(x + wrap - StretchX(10.0f)); CFont::SetRightJustifyWrap(StretchX(10.0f) + wrap); @@ -368,7 +368,7 @@ void CMenuManager::DrawWindowedText(float x, float y, float wrap, const char* st CFont::SetScale(StretchX(0.7f), StretchY(1.0f)); CRect rt; - CFont::GetTextRect(&rt, x, y, TheText.Get(str2)); + CFont::GetTextRect(&rt, x, y, TheText.Get(message)); rt.left -= 4.0f; rt.bottom += StretchY(22.0f); CSprite2d::DrawRect(rt, {0, 0, 0, 255}); @@ -380,11 +380,11 @@ void CMenuManager::DrawWindowedText(float x, float y, float wrap, const char* st CFont::SetScaleForCurrentLanguage(StretchX(1.1f), StretchY(1.4f)); CFont::SetWrapx(rt.right); - if (str1 && *str1) { - CFont::PrintString(rt.left + StretchX(20.0f), rt.top - StretchY(16.0f), TheText.Get(str1)); + if (title && *title) { + CFont::PrintString(rt.left + StretchX(20.0f), rt.top - StretchY(16.0f), TheText.Get(title)); } - if (str2 && *str2) { + if (message && *message) { CFont::SetWrapx(x + wrap - StretchX(10.0f)); CFont::SetRightJustifyWrap(StretchX(10.0f) + wrap); CFont::SetCentreSize(wrap - 2.0f * StretchX(10.0f)); @@ -394,7 +394,7 @@ void CMenuManager::DrawWindowedText(float x, float y, float wrap, const char* st CFont::SetDropShadowPosition(2); CFont::SetDropColor({ 0, 0, 0, 255 }); - CFont::PrintString(x, y + StretchY(15.0f), TheText.Get(str2)); + CFont::PrintString(x, y + StretchY(15.0f), TheText.Get(message)); } } diff --git a/source/game_sa/Fx/FxManager.cpp b/source/game_sa/Fx/FxManager.cpp index 1b5fbf3409..6046332ee2 100644 --- a/source/game_sa/Fx/FxManager.cpp +++ b/source/game_sa/Fx/FxManager.cpp @@ -95,11 +95,10 @@ void FxManager_c::DestroyFxSystem(FxSystem_c* 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& prim = system->m_SystemBP->m_Prims[i]; auto& particles = prim->m_Particles; - for (Particle_c* it = particles.GetHead(); it;) { - const auto prt = it; + auto* const prt = it; it = particles.GetNext(it); // Iterator will be invalidated, so get next here immediately if (prt->m_System == system) { diff --git a/source/game_sa/IKChain_c.h b/source/game_sa/IKChain_c.h index 156322ec60..8a89222cca 100644 --- a/source/game_sa/IKChain_c.h +++ b/source/game_sa/IKChain_c.h @@ -44,7 +44,7 @@ class IKChain_c : public ListItem_c { auto GetBones() { return std::span{ m_Bones, (size_t)m_BonesCount }; } auto GetIKSlot() const { return m_IKSlot; } - auto GetPed() const { return m_Ped; } + CPed* GetPed() const { return m_Ped; } private: CPed::Ref m_Ped{}; diff --git a/source/game_sa/MenuManager.h b/source/game_sa/MenuManager.h index f5e0786e47..316fe45f95 100644 --- a/source/game_sa/MenuManager.h +++ b/source/game_sa/MenuManager.h @@ -281,7 +281,7 @@ class CMenuManager { void DrawBackground(); void DrawStandardMenus(uint8); void DrawWindow(const CRect& coords, const char* key, uint8 color, CRGBA backColor, bool unused, bool background); - void DrawWindowedText(float x, float y, float wrap, const char* str1, const char* str2, eFontAlignment alignment); + void DrawWindowedText(float x, float y, float wrap, const char* title, const char* message, eFontAlignment alignment); void DrawQuitGameScreen(); void DrawControllerScreenExtraText(int32); void DrawControllerBound(uint16, bool); diff --git a/source/game_sa/PedGroupMembership.cpp b/source/game_sa/PedGroupMembership.cpp index ae9b5d3003..a29c6e0d30 100644 --- a/source/game_sa/PedGroupMembership.cpp +++ b/source/game_sa/PedGroupMembership.cpp @@ -162,7 +162,7 @@ void CPedGroupMembership::Process() { // 0x5FB190 void CPedGroupMembership::RemoveAllFollowers(bool bCreatedByMissionOnly) { for (auto&& [i, mem] : notsa::enumerate(m_members)) { - if (IsLeader(mem)) { // Leader isn't a follower + if (!mem || IsLeader(mem)) { // Leader isn't a follower continue; } if (bCreatedByMissionOnly && mem->IsCreatedBy(PED_MISSION)) { diff --git a/source/game_sa/Scripts/CommandParser/ReadArg.hpp b/source/game_sa/Scripts/CommandParser/ReadArg.hpp index 0ad9e3625c..82a994046a 100644 --- a/source/game_sa/Scripts/CommandParser/ReadArg.hpp +++ b/source/game_sa/Scripts/CommandParser/ReadArg.hpp @@ -180,9 +180,9 @@ inline T Read(CRunningScript* S) { if constexpr (std::is_pointer_v) { // This is a special case, as some basic ops need a reference instead of a value switch (ptype) { case SCRIPT_PARAM_GLOBAL_NUMBER_VARIABLE: - return reinterpret_cast(&CTheScripts::ScriptSpace[S->ReadAtIPAs()]); + return reinterpret_cast(&CTheScripts::ScriptSpace[S->ReadAtIPAs()]); case SCRIPT_PARAM_LOCAL_NUMBER_VARIABLE: - return reinterpret_cast(S->GetPointerToLocalVariable(S->ReadAtIPAs())); + return reinterpret_cast(S->GetPointerToLocalVariable(S->ReadAtIPAs())); case SCRIPT_PARAM_GLOBAL_NUMBER_ARRAY: { const auto [offset, idx] = detail::ReadArrayInfo(S); return reinterpret_cast(&CTheScripts::ScriptSpace[offset + sizeof(tScriptParam) * idx]); @@ -197,9 +197,9 @@ inline T Read(CRunningScript* S) { case SCRIPT_PARAM_STATIC_INT_32BITS: return detail::safe_arithmetic_cast(S->ReadAtIPAs()); case SCRIPT_PARAM_GLOBAL_NUMBER_VARIABLE: - return *reinterpret_cast(&CTheScripts::ScriptSpace[S->ReadAtIPAs()]); + return *reinterpret_cast(&CTheScripts::ScriptSpace[S->ReadAtIPAs()]); case SCRIPT_PARAM_LOCAL_NUMBER_VARIABLE: - return *reinterpret_cast(S->GetPointerToLocalVariable(S->ReadAtIPAs())); + return *reinterpret_cast(S->GetPointerToLocalVariable(S->ReadAtIPAs())); case SCRIPT_PARAM_STATIC_INT_8BITS: return detail::safe_arithmetic_cast(S->ReadAtIPAs()); case SCRIPT_PARAM_STATIC_INT_16BITS: diff --git a/source/game_sa/Scripts/Commands/Player.cpp b/source/game_sa/Scripts/Commands/Player.cpp index 79ab5c887e..63df474843 100644 --- a/source/game_sa/Scripts/Commands/Player.cpp +++ b/source/game_sa/Scripts/Commands/Player.cpp @@ -197,7 +197,6 @@ void notsa::script::commands::player::RegisterHandlers() { REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_IS_PLAYER_IN_REMOTE_MODE); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_ANIM_GROUP_FOR_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); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_FAST_RELOAD); //REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_EVERYONE_IGNORE_PLAYER); @@ -261,12 +260,9 @@ 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_SET_OBJECT_ONLY_DAMAGED_BY_PLAYER); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_FIRE_BUTTON); //REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_IS_PLAYER_IN_POSITION_FOR_CONVERSATION); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_PLANE_ATTACK_PLAYER_USING_DOG_FIGHT); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_MAKE_PLAYER_GANG_DISAPPEAR); - REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_MAKE_PLAYER_GANG_REAPPEAR); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_JUMP_BUTTON); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_SET_PLAYER_CAN_BE_DAMAGED); REGISTER_COMMAND_UNIMPLEMENTED(COMMAND_GET_PLAYERS_GANG_IN_CAR_ACTIVE); diff --git a/source/game_sa/Scripts/RunningScript.h b/source/game_sa/Scripts/RunningScript.h index 2737b66fcc..42e2e1a613 100644 --- a/source/game_sa/Scripts/RunningScript.h +++ b/source/game_sa/Scripts/RunningScript.h @@ -84,7 +84,7 @@ union tScriptParam { uint16 u16Param; int16 i16Param; - uint32 uParam; + uint32 uParam{0u}; int32 iParam; float fParam; diff --git a/source/game_sa/Scripts/SCMChunks.hpp b/source/game_sa/Scripts/SCMChunks.hpp new file mode 100644 index 0000000000..8d664ffe43 --- /dev/null +++ b/source/game_sa/Scripts/SCMChunks.hpp @@ -0,0 +1,108 @@ +#pragma once + +#pragma pack(push, 1) +struct tSCMChunkHeader { + uint8 m_InstrGoTo[3]; // 02 00 01 (GOTO ) + uint32 m_NextChunkOffset; + uint8 m_ChunkIndex; + + template requires std::is_base_of_v + ChunkT* As() { + assert(m_ChunkIndex == ChunkT::Index); + return (ChunkT*)this; + } + + template requires std::is_base_of_v + const ChunkT* As() const { + assert(m_ChunkIndex == ChunkT::Index); + return (ChunkT*)this; + } +}; +VALIDATE_SIZE(tSCMChunkHeader, 0x8); + +// m_ChunkIndex = 's' -- for San Andreas +struct tSCMGlobalVarChunk : tSCMChunkHeader { + static constexpr uint8 Index = 's'; + + uint8 m_GlobalVarSpace[]; + + template + requires std::is_integral_v || notsa::is_any_of_type_v + const T& ReadVariable(size_t offset) { + return *reinterpret_cast(m_GlobalVarSpace[offset - 8]); + } +}; +VALIDATE_SIZE(tSCMGlobalVarChunk, 0x8 /* + m_NextChunkOffset - 8 */); + +// m_ChunkIndex = 0 +struct tSCMUsedObjectsChunk : tSCMChunkHeader { + using UsedObjectName = char[24]; + + static constexpr uint8 Index = 0; + + uint32 m_NumberOfUsedObjects; + UsedObjectName m_UsedObjectNames[]; + + auto GetObjectNames() const { + return std::span{ m_UsedObjectNames, m_NumberOfUsedObjects }; + } +}; +VALIDATE_SIZE(tSCMUsedObjectsChunk, 0xC /* + m_NumberOfUsedObjects * 24 */); + +// m_ChunkIndex = 1 +struct tSCMScriptFileInfoChunk : tSCMChunkHeader { + static constexpr uint8 Index = 1; + + uint32 m_MainScriptSize; + uint32 m_LargestMissionScriptSize; + uint16 m_NumberOfMissionScripts; + uint16 m_NumberOfExclusiveMissionScripts; + uint32 m_LargestNumberOfMissionScriptLocalVars; + + uint32 m_MissionScriptOffsets[]; + + auto GetMissionOffsets() const { + return std::span{ m_MissionScriptOffsets, m_NumberOfMissionScripts }; + } +}; +VALIDATE_SIZE(tSCMScriptFileInfoChunk, 0x18 /* + 4 * m_NumberOfMissionScripts */); + +// m_ChunkIndex = 2 +struct tSCMStreamedScriptFileInfoChunk : tSCMChunkHeader { + struct StreamScriptDefinition { + char m_FileName[20]; + uint32 m_FileOffset; + uint32 m_Size; + }; + + static constexpr uint8 Index = 2; + + uint32 m_LargestStreamScriptSize; + uint32 m_NumberOfStreamedScripts; + + StreamScriptDefinition m_StreamedScriptDefs[]; + + auto GetStreamedScripts() const { + return std::span{ m_StreamedScriptDefs, m_NumberOfStreamedScripts }; + } +}; +VALIDATE_SIZE(tSCMStreamedScriptFileInfoChunk, 0x10 /* + 28 * m_NumberOfStreamedScripts */); +VALIDATE_SIZE(tSCMStreamedScriptFileInfoChunk::StreamScriptDefinition, 28); + +// m_ChunkIndex = 3 +struct tSCMUnknownChunk3 : tSCMChunkHeader { + static constexpr uint8 Index = 3; + + uint32 unk; +}; +VALIDATE_SIZE(tSCMUnknownChunk3, 0xC); + +// m_ChunkIndex = 4 +struct tSCMExtraInfoChunk : tSCMChunkHeader { + static constexpr uint8 Index = 4; + + uint32 m_GlobalVarSpaceSize; + uint32 m_BuildNumber; // ? +}; +VALIDATE_SIZE(tSCMExtraInfoChunk, 0x10); +#pragma pack(pop) diff --git a/source/game_sa/Scripts/TheScripts.cpp b/source/game_sa/Scripts/TheScripts.cpp index 376e3fe5c6..f07d26f3aa 100644 --- a/source/game_sa/Scripts/TheScripts.cpp +++ b/source/game_sa/Scripts/TheScripts.cpp @@ -8,8 +8,15 @@ #include "Checkpoint.h" #include "Checkpoints.h" #include "LoadingScreen.h" -// #include "Scripted2dEffects.h" +#include "Scripted2dEffects.h" #include "Shadows.h" +#include "VehicleRecording.h" +#include "TaskComplexLeaveAnyCar.h" +#include "TaskComplexWander.h" + +#include "extensions/File.hpp" + +static inline bool gAllowScriptedFixedCameraCollision = false; void CTheScripts::InjectHooks() { // Has to have these, because there seems to be something going on with the variable init order @@ -20,21 +27,207 @@ void CTheScripts::InjectHooks() { RH_ScopedClass(CTheScripts); RH_ScopedCategory("Scripts"); - RH_ScopedInstall(Init, 0x468D50, { .reversed = false }); - RH_ScopedOverloadedInstall(StartNewScript, "", 0x464C20, CRunningScript* (*)(uint8*)); - // RH_ScopedOverloadedInstall(StartNewScript, "index", 0x464C90, CRunningScript* (*)(uint8*, uint16)); - RH_ScopedInstall(StartTestScript, 0x464D40); + RH_ScopedInstall(Init, 0x468D50); + RH_ScopedInstall(InitialiseAllConnectLodObjects, 0x470960); + RH_ScopedInstall(InitialiseConnectLodObjects, 0x470940); + RH_ScopedInstall(InitialiseSpecialAnimGroupsAttachedToCharModels, 0x474730); + RH_ScopedInstall(InitialiseSpecialAnimGroup, 0x474710); + RH_ScopedInstall(ReadObjectNamesFromScript, 0x486720); + RH_ScopedInstall(UpdateObjectIndices, 0x486780); + RH_ScopedInstall(ReadMultiScriptFileOffsetsFromScript, 0x4867C0); + RH_ScopedInstall(AddScriptCheckpoint, 0x4935A0); + RH_ScopedInstall(AddScriptEffectSystem, 0x492F90); + RH_ScopedInstall(AddScriptSearchLight, 0x493000); + RH_ScopedInstall(AddScriptSphere, 0x483B30); RH_ScopedInstall(AddToBuildingSwapArray, 0x481140); + RH_ScopedInstall(AddToInvisibilitySwapArray, 0x481200); + RH_ScopedInstall(AddToListOfConnectedLodObjects, 0x470980); + RH_ScopedInstall(AddToListOfSpecialAnimGroupsAttachedToCharModels, 0x474750); + RH_ScopedInstall(AddToSwitchJumpTable, 0x470390); + RH_ScopedInstall(AddToVehicleModelsBlockedByScript, 0x46B200); + RH_ScopedInstall(AddToWaitingForScriptBrainArray, 0x46AB60); + RH_ScopedInstall(CleanUpThisObject, 0x4866C0); + RH_ScopedInstall(CleanUpThisPed, 0x486300); + RH_ScopedInstall(CleanUpThisVehicle, 0x486670); + RH_ScopedInstall(ClearAllVehicleModelsBlockedByScript, 0x46A840); + RH_ScopedInstall(ClearAllSuppressedCarModels, 0x46A7C0); + RH_ScopedInstall(ClearSpaceForMissionEntity, 0x486B00); + RH_ScopedInstall(DoScriptSetupAfterPoolsHaveLoaded, 0x5D3390); + RH_ScopedInstall(GetActualScriptThingIndex, 0x4839A0); + RH_ScopedInstall(GetNewUniqueScriptThingIndex, 0x483720); + RH_ScopedInstall(GetScriptIndexFromPointer, 0x464D20); + RH_ScopedInstall(GetUniqueScriptThingIndex, 0x4810C0); + RH_ScopedInstall(ReinitialiseSwitchStatementData, 0x470370); + // RH_ScopedInstall(RemoveFromVehicleModelsBlockedByScript, 0x0); + RH_ScopedInstall(RemoveFromWaitingForScriptBrainArray, 0x46ABC0); + RH_ScopedInstall(RemoveScriptCheckpoint, 0x4936C0); + RH_ScopedInstall(RemoveScriptEffectSystem, 0x492FD0); + RH_ScopedInstall(RemoveScriptSearchLight, 0x493160); + RH_ScopedInstall(RemoveScriptSphere, 0x483BA0); + RH_ScopedInstall(RemoveScriptTextureDictionary, 0x465A40); + RH_ScopedInstall(RemoveThisPed, 0x486240); + RH_ScopedOverloadedInstall(StartNewScript, "last-idle", 0x464C20, CRunningScript* (*)(uint8*)); + RH_ScopedOverloadedInstall(StartNewScript, "indexed", 0x464C90, CRunningScript* (*)(uint8*, uint16)); RH_ScopedInstall(UndoBuildingSwaps, 0x481290); RH_ScopedInstall(IsPedStopped, 0x486110); + RH_ScopedInstall(IsPlayerOnAMission, 0x464D50); + RH_ScopedInstall(IsVehicleStopped, 0x4861F0); + RH_ScopedInstall(Load, 0x5D4FD0); + RH_ScopedInstall(Save, 0x5D4C40); + RH_ScopedInstall(WipeLocalVariableMemoryForMissionScript, 0x464BB0); RH_ScopedInstall(HasCarModelBeenSuppressed, 0x46A810); RH_ScopedInstall(HasVehicleModelBeenBlockedByScript, 0x46A890); + RH_ScopedInstall(StartTestScript, 0x464D40); RH_ScopedInstall(Process, 0x46A000); + RH_ScopedInstall(ProcessAllSearchLights, 0x4939F0); + RH_ScopedInstall(ProcessWaitingForScriptBrainArray, 0x46CF00); + RH_ScopedInstall(UndoEntityInvisibilitySettings, 0x4812D0); + RH_ScopedInstall(PrintListSizes, 0x4646D0); + RH_ScopedOverloadedInstall(DrawDebugSquare, "", 0x486840, void(*)(float,float,float,float)); + RH_ScopedInstall(DrawDebugAngledSquare, 0x486990); + // RH_ScopedInstall(DrawDebugCube, 0x0); + // RH_ScopedInstall(DrawDebugAngledCube, 0x0) + RH_ScopedInstall(DrawScriptSpritesAndRectangles, 0x464980); + RH_ScopedInstall(ScriptDebugCircle2D, 0x485C20); + RH_ScopedInstall(DrawScriptSpheres, 0x4810E0); + RH_ScopedOverloadedInstall(HighlightImportantArea, "", 0x485E00, void(*)(uint32,float,float,float,float,float)); + RH_ScopedInstall(HighlightImportantAngledArea, 0x485EF0); + RH_ScopedInstall(ScriptDebugLine3D, 0x485DE0); + // RH_ScopedInstall(RenderTheScriptDebugLines, 0x0); + RH_ScopedInstall(RenderAllSearchLights, 0x493E30); + RH_ScopedInstall(ScriptConnectLodsFunction, 0x470A20); + RH_ScopedInstall(ScriptAttachAnimGroupToCharModel, 0x474800); + RH_ScopedInstall(UseSwitchJumpTable, 0x4703C0); + RH_ScopedInstall(AttachSearchlightToSearchlightObject, 0x4934F0); + RH_ScopedInstall(CheckStreamedScriptVersion, 0x464FF0); } // 0x468D50 void CTheScripts::Init() { - plugin::Call<0x468D50>(); + rng::fill(ScriptSpace, 0u); + rng::fill(LocalVariablesForCurrentMission, tScriptParam{}); + + CRunningScript* nextScript = nullptr; + for (auto& script : ScriptsArray) { + script.Init(); + script.m_pPrev = nullptr; + script.m_pNext = nextScript; + + if (nextScript) { + nextScript->m_pPrev = &script; + } + nextScript = &script; + } + pActiveScripts = nullptr; + pIdleScripts = nextScript; + + MissionCleanUp.Init(); + UpsideDownCars.Init(); + StuckCars.Init(); + ScriptsForBrains.Init(); + ScriptResourceManager.Initialise(); + rng::for_each(EntitiesWaitingForScriptBrain, &tScriptBrainWaitEntity::Clear); + + if (CGame::bMissionPackGame) { + char scrFile[MAX_PATH]{}; + + while (FrontEndMenuManager.CheckMissionPackValidMenu()) { + CFileMgr::SetDirMyDocuments(); + notsa::format_to_sz(scrFile, "MPACK//MPACK{:d}//SCR.SCM", CGame::bMissionPackGame); + + if (auto f = notsa::File(scrFile, "rb"); f && f.Read(ScriptSpace.data(), MAIN_SCRIPT_SIZE) >= 1) { + break; + } + } + } else { + CFileMgr::SetDir("data\\script"); + + VERIFY(notsa::File("main.scm", "rb").Read(ScriptSpace.data(), MAIN_SCRIPT_SIZE) >= 1); + } + CFileMgr::SetDir(""); + + StoreVehicleIndex = -1; + StoreVehicleWasRandom = true; + OnAMissionFlag = false; + LastMissionPassedTime = -1; + LastRandomPedId = -1; + + rng::fill(UsedObjectArray, tUsedObject{}); + ReadObjectNamesFromScript(); + UpdateObjectIndices(); + rng::fill(MultiScriptArray, 0u); + NumberOfUsedObjects = 0; + bAlreadyRunningAMissionScript = false; + bUsingAMultiScriptFile = true; + MainScriptSize = 0; + LargestMissionScriptSize = 0; + NumberOfMissionScripts = 0; + NumberOfExclusiveMissionScripts = 0; + LargestNumberOfMissionScriptLocalVariables = 0; + + ReadMultiScriptFileOffsetsFromScript(); + if (!CGame::bMissionPackGame) { + StreamedScripts.ReadStreamedScriptData(); + } + + ForceRandomCarModel = -1; + FailCurrentMission = 0; + ScriptPickupCycleIndex = 0; + bMiniGameInProgress = false; + bDisplayNonMiniGameHelpMessages = true; + bPlayerHasMetDebbieHarry = false; + RiotIntensity = 0; + bPlayerIsOffTheMap = false; + RadarZoomValue = 0; + RadarShowBlipOnAllLevels = false; + HideAllFrontEndMapBlips = false; + bDisplayHud = true; + fCameraHeadingWhenPlayerIsAttached = 0.0f; + fCameraHeadingStepWhenPlayerIsAttached = 0.0f; + bEnableCraneRaise = true; + bEnableCraneLower = true; + bEnableCraneRelease = true; + bDrawCrossHair = eCrossHairType::NONE; + gAllowScriptedFixedCameraCollision = false; + bAddNextMessageToPreviousBriefs = true; + bScriptHasFadedOut = false; + bDrawOddJobTitleBeforeFade = true; + bDrawSubtitlesBeforeFade = true; + + rng::fill(ScriptSphereArray, tScriptSphere{}); + rng::fill(IntroTextLines, tScriptText{}); + + NumberOfIntroTextLinesThisFrame = 0; + UseTextCommands = false; + bUseMessageFormatting = false; + MessageCentre = 0; + MessageWidth = 0; + + rng::fill(IntroRectangles, tScriptRectangle{}); + NumberOfIntroRectanglesThisFrame = 0; + + RemoveScriptTextureDictionary(); + + rng::fill(BuildingSwapArray, tBuildingSwap{}); + rng::fill(InvisibilitySettingArray, nullptr); + ClearAllSuppressedCarModels(); + ClearAllVehicleModelsBlockedByScript(); + InitialiseAllConnectLodObjects(); + InitialiseSpecialAnimGroupsAttachedToCharModels(); + rng::fill(ScriptEffectSystemArray, tScriptEffectSystem{}); + rng::for_each(ScriptSearchLightArray, &tScriptSearchlight::Clear); + NumberOfScriptSearchLights = 0; + rng::fill(ScriptCheckpointArray, tScriptCheckpoint{}); + NumberOfScriptCheckpoints = 0; + rng::fill(ScriptSequenceTaskArray, tScriptSequence{}); + + CScripted2dEffects::Init(); + CTaskSequences::Init(); + CPedGroups::Init(); + CInformFriendsEventQueue::Init(); + CInformGroupEventQueue::Init(); + CDecisionMakerTypes::GetInstance(); + } // 0x470960 @@ -63,39 +256,144 @@ void CTheScripts::InitialiseSpecialAnimGroup(uint16 index) { // 0x486720 void CTheScripts::ReadObjectNamesFromScript() { - plugin::Call<0x486720>(); + auto* usedObjs = GetSCMChunk(); + + NOTSA_LOG_TRACE("Number of used objects: {}", usedObjs->m_NumberOfUsedObjects); + NumberOfUsedObjects = usedObjs->m_NumberOfUsedObjects; + assert(NumberOfUsedObjects < std::size(UsedObjectArray)); + + for (auto&& [i, name] : notsa::enumerate(usedObjs->GetObjectNames())) { + UsedObjectArray[i].nModelIndex = 0; // To be updated via UpdateObjectIndices. + std::memcpy(UsedObjectArray[i].szModelName, name, sizeof(name)); + + NOTSA_LOG_TRACE("Script object #{}: \"{}\"", i, usedObjs->m_UsedObjectNames[i]); + } } // 0x486780 void CTheScripts::UpdateObjectIndices() { - plugin::Call<0x486780>(); + // First one is ignored because it's empty. + for (auto& obj : UsedObjectArray | std::views::drop(1)) { + CModelInfo::GetModelInfo(obj.szModelName, &obj.nModelIndex); + } } // 0x4867C0 void CTheScripts::ReadMultiScriptFileOffsetsFromScript() { - plugin::Call<0x4867C0>(); + auto* sfi = GetSCMChunk(); + + NOTSA_LOG_TRACE("Main script size: {}", sfi->m_MainScriptSize); + NOTSA_LOG_TRACE("Largest mission size: {}", sfi->m_LargestMissionScriptSize); + NOTSA_LOG_TRACE("Number of mission scripts: {}", sfi->m_NumberOfMissionScripts); + NOTSA_LOG_TRACE("Number of exclusive mission script: {}", sfi->m_NumberOfExclusiveMissionScripts); + NOTSA_LOG_TRACE("Largest num of mission script local vars: {}", sfi->m_LargestNumberOfMissionScriptLocalVars); + + MainScriptSize = sfi->m_MainScriptSize; + LargestMissionScriptSize = sfi->m_LargestMissionScriptSize; + NumberOfExclusiveMissionScripts = sfi->m_NumberOfExclusiveMissionScripts; + NumberOfMissionScripts = sfi->m_NumberOfMissionScripts; + LargestNumberOfMissionScriptLocalVariables = sfi->m_LargestNumberOfMissionScriptLocalVars; + + for (const auto&& [i, missionOffset] : notsa::enumerate(sfi->GetMissionOffsets())) { + MultiScriptArray[i] = missionOffset; + } } // signature changed (CVector) // 0x4935A0 -uint32 CTheScripts::AddScriptCheckpoint(CVector at, CVector pointTo, float radius, int32 type) { - return plugin::CallAndReturn(at, pointTo, radius, type); +uint32 CTheScripts::AddScriptCheckpoint(CVector at, CVector pointTo, float radius, eCheckpointType type) { + const auto cp = rng::find_if(ScriptCheckpointArray, [](auto& cp) { return !cp.IsActive(); }); + assert(cp != ScriptCheckpointArray.end()); // In vanilla game does OOB access. + + cp->m_bUsed = true; + const auto color = [&type]() -> CRGBA { + switch (type) { + case eCheckpointType::TUBE: + case eCheckpointType::ENDTUBE: + case eCheckpointType::EMPTYTUBE: + return { 255, 0, 0, 32 }; + case eCheckpointType::TORUS: + case eCheckpointType::TORUS_NOFADE: + case eCheckpointType::TORUSROT: + case eCheckpointType::TORUSTHROUGH: + case eCheckpointType::TORUS_UPDOWN: + case eCheckpointType::TORUS_DOWN: + return { 255, 0, 0, 96 }; + default: + NOTSA_UNREACHABLE(); + } + }(); + const auto index = GetNewUniqueScriptThingIndex(std::distance(ScriptCheckpointArray.begin(), cp), SCRIPT_THING_CHECKPOINT); + cp->m_Checkpoint = CCheckpoints::PlaceMarker( + index, + type, + at, + pointTo, + radius, + color, + 1024, + 0.075f, + 0 + ); + + ++NumberOfScriptCheckpoints; + return index; } // 0x492F90 uint32 CTheScripts::AddScriptEffectSystem(FxSystem_c* system) { - return plugin::CallAndReturn(system); + const auto fx = rng::find_if(ScriptEffectSystemArray, [](auto& fx) { + return !fx.IsActive(); + }); + assert(fx != ScriptEffectSystemArray.end()); // In vanilla game does OOB access. + + fx->m_bUsed = true; + fx->m_pFxSystem = system; + return GetNewUniqueScriptThingIndex(std::distance(ScriptEffectSystemArray.begin(), fx), SCRIPT_THING_EFFECT_SYSTEM); } // signature changed (CVector) // 0x493000 uint32 CTheScripts::AddScriptSearchLight(CVector start, CEntity* entity, CVector target, float targetRadius, float baseRadius) { - return plugin::CallAndReturn(start, entity, target, targetRadius, baseRadius); + const auto ssl = rng::find_if(ScriptSearchLightArray, [](auto& ssl) { + return !ssl.IsActive(); + }); + assert(ssl != ScriptSearchLightArray.end()); // In vanilla game does OOB access. + + const auto idx = std::distance(ScriptSearchLightArray.begin(), ssl); + RemoveScriptSearchLight(idx); + + ssl->m_bClipIfColliding = false; + ssl->bIsUsed = true; + ssl->m_bUsed = true; + ssl->m_bEnableShadow = true; + ssl->m_Origin = start; + ssl->m_Target = target; + ssl->m_fBaseRadius = baseRadius; + ssl->m_fTargetRadius = targetRadius; + ssl->m_PathCoord1 = CVector{}; + ssl->m_PathCoord2 = CVector{}; + ssl->m_fPathSpeed = 0.0f; + + ssl->m_AttachedEntity = entity; + + ++NumberOfScriptSearchLights; + return CTheScripts::GetNewUniqueScriptThingIndex(idx, SCRIPT_THING_SEARCH_LIGHT); } // 0x483B30 uint32 CTheScripts::AddScriptSphere(uint32 id, CVector posn, float radius) { - return plugin::CallAndReturn(id, posn, radius); + const auto sphere = rng::find_if(ScriptSphereArray, [](auto& sphere) { + return !sphere.IsActive(); + }); + assert(sphere != ScriptSphereArray.end()); // In vanilla game does OOB access. + + const auto idx = std::distance(ScriptSphereArray.begin(), sphere); + sphere->m_nId = idx + id; + sphere->m_vCoords = posn; + sphere->m_bUsed = true; + sphere->m_fRadius = radius; + return GetNewUniqueScriptThingIndex(idx, SCRIPT_THING_EFFECT_SYSTEM); } // 0x481140 @@ -123,18 +421,66 @@ void CTheScripts::AddToBuildingSwapArray(CBuilding* building, int32 oldModelId, } // 0x481200 -void CTheScripts::AddToInvisibilitySwapArray(CEntity* entity, bool bVisible) { - plugin::Call<0x481200, CEntity*, bool>(entity, bVisible); +void CTheScripts::AddToInvisibilitySwapArray(CEntity* entity, bool visible) { + if (entity->m_nIplIndex) + return; + + const auto is = rng::find(InvisibilitySettingArray, entity); + if (is != InvisibilitySettingArray.end()) { + // Already exists. + if (visible) { + *is = nullptr; + } + return; + } + + const auto free = rng::find(InvisibilitySettingArray, nullptr); + if (free == InvisibilitySettingArray.end()) { + return; + } + + *free = entity; } // 0x470980 void CTheScripts::AddToListOfConnectedLodObjects(CObject* obj1, CObject* obj2) { - plugin::Call<0x470980, CObject*, CObject*>(obj1, obj2); + const auto idx1 = GetObjectPool()->GetIndex(obj1), idx2 = GetObjectPool()->GetIndex(obj2); + + const auto lod = rng::find_if(ScriptConnectLodsObjects, [idx1, idx2](auto& lod) { + return lod.a == idx1 && lod.b == idx2; + }); + if (lod != ScriptConnectLodsObjects.end()) { + // Already exists. + return; + } + + const auto free = rng::find_if(ScriptConnectLodsObjects, [](auto& lod) { return lod.a == -1; }); + assert(free != ScriptConnectLodsObjects.end()); // In vanilla game does OOB access. + + free->a = idx1; + free->b = idx2; } // 0x474750 void CTheScripts::AddToListOfSpecialAnimGroupsAttachedToCharModels(int32 modelId, Const char* ifpName) { - plugin::Call<0x474750, int32, const char*>(modelId, ifpName); + const auto aag = rng::find_if(ScriptAttachedAnimGroups, [modelId, ifpName](auto& aag) { + return aag.m_nModelID == modelId && !std::strcmp(aag.m_IfpName, ifpName); + }); + if (aag != ScriptAttachedAnimGroups.end()) { + // Already exists. + return; + } + + const auto free = rng::find_if(ScriptAttachedAnimGroups, [](auto& aag) { + return aag.m_nModelID == MODEL_INVALID; + }); + if (free == ScriptAttachedAnimGroups.end()) { + return; + } + + free->m_nModelID = modelId; + assert(std::strlen(ifpName) < sizeof(free->m_IfpName)); + std::strcpy(free->m_IfpName, ifpName); } // 0x470390 @@ -145,72 +491,389 @@ void CTheScripts::AddToSwitchJumpTable(int32 switchValue, int32 switchLabelLocal } // 0x46B200, unused | inlined? -void CTheScripts::AddToVehicleModelsBlockedByScript(int32 modelIndex) { +void CTheScripts::AddToVehicleModelsBlockedByScript(eModelID modelIndex) { + if (notsa::contains(VehicleModelsBlockedByScript, modelIndex)) { + return; + } + const auto free = rng::find(VehicleModelsBlockedByScript, MODEL_INVALID); + assert(free != VehicleModelsBlockedByScript.end()); // In vanilla game does OOB access. + + *free = modelIndex; } // 0x46AB60 void CTheScripts::AddToWaitingForScriptBrainArray(CEntity* entity, int16 specialModelIndex) { - return plugin::Call<0x46AB60, CEntity*, int16>(entity, specialModelIndex); + const auto wfsb = rng::find_if(EntitiesWaitingForScriptBrain, [entity](auto& wfsb) { + return wfsb.m_pEntity == entity; + }); + if (wfsb != EntitiesWaitingForScriptBrain.end()) { + // Already exists. + return; + } + + const auto free = rng::find_if(EntitiesWaitingForScriptBrain, [entity](auto& wfsb) { return !wfsb.m_pEntity; }); + if (free == EntitiesWaitingForScriptBrain.end()) { + return; + } + + free->m_pEntity = entity; + free->m_ScriptBrainIndex = specialModelIndex; +} + +// 0x4934F0 +void CTheScripts::AttachSearchlightToSearchlightObject(int32 searchLightId, CObject* tower, CObject* housing, CObject* bulb, CVector offset) { + const auto idx = GetActualScriptThingIndex(searchLightId, SCRIPT_THING_SEARCH_LIGHT); + if (idx < 0) { + return; + } + + auto& sl = ScriptSearchLightArray[idx]; + sl.m_Origin = offset; + sl.m_AttachedEntity = nullptr; + sl.m_Tower = tower; + sl.m_Housing = housing; + sl.m_Bulb = bulb; +} + +// 0x464FF0 +bool CTheScripts::CheckStreamedScriptVersion(RwStream* stream, char* filename) { + return true; } // 0x4866C0 void CTheScripts::CleanUpThisObject(CObject* obj) { - if (!obj) + if (!obj) { return; + } if (obj->IsMissionObject()) { - obj->m_nObjectType = OBJECT_TEMPORARY; - obj->m_nRemovalTime = CTimer::GetTimeInMS() + 200'00'000; - obj->m_nRefModelIndex = -1; + obj->m_nObjectType = OBJECT_TEMPORARY; + obj->m_nRemovalTime = CTimer::GetTimeInMS() + 20'000'000; + obj->m_nRefModelIndex = -1; obj->objectFlags.bChangesVehColor = false; CObject::nNoTempObjects++; } } // 0x486300 +// TODO: test void CTheScripts::CleanUpThisPed(CPed* ped) { - plugin::Call<0x486300, CPed*>( ped); + if (!ped || ped->IsCreatedByMission()) { + return; + } + + ped->SetCharCreatedBy(ePedCreatedBy::PED_GAME); + if (ped->bKeepTasksAfterCleanUp) { + return; + } + + notsa::ScopeGuard _([]() { + --CPopulation::ms_nTotalMissionPeds; + }); + + if (auto* veh = ped->GetVehicleIfInOne(); veh && veh->IsDriver(ped)) { + const auto FixMission = [veh](eCarMission fix) { + auto& mis = veh->m_autoPilot.m_nCarMission; + if (mis != MISSION_CRASH_PLANE_AND_BURN && mis != MISSION_CRASH_HELI_AND_BURN) { + mis = fix; + } + }; + + switch (veh->GetType()) { + case eVehicleType::VEHICLE_TYPE_HELI: { + FixMission(MISSION_HELI_FLYTOCOORS); + + veh->m_autoPilot.m_vecDestinationCoors = CVector{ 10'000.0f, -10'000.0f, 1'000.0f }; + veh->AsHeli()->m_fMinAltitude = 1000.0f; + veh->AsHeli()->m_fMaxAltitude = 1000.0f; + break; + } + case eVehicleType::VEHICLE_TYPE_PLANE: { + FixMission(MISSION_PLANE_FLYTOCOORS); + + veh->m_autoPilot.m_vecDestinationCoors = CVector{ 10'000.0f, 10'000.0f, 1'000.0f }; + veh->AsPlane()->m_minAltitude = 1000.0f; + veh->AsPlane()->m_maxAltitude = 1000.0f; + break; + } + default: + if (veh->IsSubAutomobile() || veh->IsSubBike()) { + CCarCtrl::JoinCarWithRoadSystem(veh); + FixMission(MISSION_CRUISE); + } + } + + // Quick return: The captain goes down with the ship. + ped->bStayInSamePlace = false; // ??? + + if (auto* group = CPedGroups::GetPedsGroup(ped)) { + if (auto& member = group->GetMembership(); member.IsFollower(ped)) { + member.RemoveMember(ped); + } + } + return; + } + ped->bStayInSamePlace = false; // ??? + + if (auto* group = CPedGroups::GetPedsGroup(ped)) { + if (auto& member = group->GetMembership(); member.IsFollower(ped)) { + member.RemoveMember(ped); + } + } + + const auto CheckTaskExists = [ped](eTaskType type) { + if (auto* event = ped->GetEventGroup().GetEventOfType(EVENT_SCRIPT_COMMAND)) { + if (auto* esc = CEvent::DynCast(event); esc && esc->m_task->GetTaskType() == type) { + return true; + } + } + + if (auto* task = ped->GetTaskManager().GetTaskPrimary(TASK_PRIMARY_PRIMARY); task && task->GetTaskType() == type) { + return true; + } + + return false; + }; + + if (ped->IsInVehicle()) { + if (CheckTaskExists(TASK_COMPLEX_SEQUENCE)) { + return; + } + + // Get them out of the car then make them wander. + ped->GetEventGroup().Add(CEventScriptCommand(TASK_PRIMARY_PRIMARY, new CTaskComplexSequence( + new CTaskComplexLeaveAnyCar(0, true, false), + CTaskComplexWander::GetWanderTaskByPedType(ped) + ))); + } else { + if (CheckTaskExists(TASK_COMPLEX_WANDER)) { + return; + } + + // Make them wander. + ped->GetEventGroup().Add(CEventScriptCommand(TASK_PRIMARY_PRIMARY, CTaskComplexWander::GetWanderTaskByPedType(ped))); + } } // 0x486670 void CTheScripts::CleanUpThisVehicle(CVehicle* vehicle) { - plugin::Call<0x486670, CVehicle*>(vehicle); + if (!vehicle || vehicle->IsCreatedBy(eVehicleCreatedBy::MISSION_VEHICLE)) { + return; + } + + vehicle->physicalFlags.bDontApplySpeed = false; + vehicle->physicalFlags.bDisableCollisionForce = false; + vehicle->vehicleFlags.bIsLocked = false; + + CCarCtrl::RemoveFromInterestingVehicleList(vehicle); + CVehicleRecording::StopPlaybackRecordedCar(vehicle); + vehicle->SetVehicleCreatedBy(eVehicleCreatedBy::RANDOM_VEHICLE); } // 0x46A840 void CTheScripts::ClearAllVehicleModelsBlockedByScript() { - memset(&VehicleModelsBlockedByScript, 255, sizeof(VehicleModelsBlockedByScript)); + rng::fill(VehicleModelsBlockedByScript, MODEL_INVALID); } // 0x46A7C0 void CTheScripts::ClearAllSuppressedCarModels() { - memset(&SuppressedVehicleModels, 255, sizeof(SuppressedVehicleModels)); + rng::fill(SuppressedVehicleModels, MODEL_INVALID); } // 0x486B00 -void CTheScripts::ClearSpaceForMissionEntity(const CVector& pos, CEntity* entity) { - plugin::Call<0x486B00, const CVector&, CEntity*>(pos, entity); +void CTheScripts::ClearSpaceForMissionEntity(const CVector& pos, CEntity* ourEntity) { + std::array colEntities{}; + int16 numColliding{}; + + CWorld::FindObjectsKindaColliding( + pos, + ourEntity->GetModelInfo()->GetColModel()->GetBoundRadius(), + false, + &numColliding, + (int16)colEntities.max_size(), + colEntities.data(), + false, + true, + true, + false, + false + ); + + auto* ourColData = ourEntity->GetColData(); + if (!ourColData) { + return; + } + + // Disable suspension lines of vehicles. + const auto cdNumLines = std::exchange(ourColData->m_nNumLines, 0); + + for (auto& entity : std::span{ colEntities.data(), (size_t)numColliding }) { + if (!entity || entity == ourEntity || (entity->IsPed() && entity->AsPed()->IsInVehicle())) { + continue; + } + + std::array colPoints{}; + const auto numCollisions = CCollision::ProcessColModels( + ourEntity->GetMatrix(), + *ourEntity->GetColModel(), + entity->GetMatrix(), + *entity->GetColModel(), + colPoints, + nullptr, + nullptr, + false + ); + + if (numCollisions <= 0) { + continue; + } + + if (entity->IsVehicle()) { + auto* vehicle = entity->AsVehicle(); + if (vehicle->vehicleFlags.bIsLocked || !vehicle->CanBeDeleted()) { + continue; + } + + if (auto& driver = vehicle->m_pDriver) { + CPopulation::RemovePed(driver); + CEntity::SafeCleanUpRef(driver); + } + + for (auto& passenger : vehicle->GetPassengers()) { + if (passenger) { + vehicle->RemovePassenger(passenger); + CPopulation::RemovePed(passenger); + } + } + + CCarCtrl::RemoveFromInterestingVehicleList(vehicle); + CWorld::Remove(vehicle); + delete vehicle; + } + + if (entity->IsPed() && !entity->AsPed()->IsPlayer() && entity->AsPed()->CanBeDeleted()) { + CPopulation::RemovePed(entity->AsPed()); + } + } + ourColData->m_nNumLines = cdNumLines; } // 0x5D3390 void CTheScripts::DoScriptSetupAfterPoolsHaveLoaded() { - plugin::Call<0x5D3390>(); + for (const auto& lod : ScriptConnectLodsObjects) { + if (lod.a != -1 && lod.b != -1) { + ScriptConnectLodsFunction(lod.a, lod.b); + } + } } // 0x4839A0 -int32 CTheScripts::GetActualScriptThingIndex(int32 index, eScriptThingType type) { - return plugin::CallAndReturn(index, type); +int32 CTheScripts::GetActualScriptThingIndex(int32 ref, eScriptThingType type) { + if (ref == -1) { + return -1; + } + + const auto idx = LOWORD(ref), id = HIWORD(ref); + + switch (type) { + case SCRIPT_THING_SPHERE: + if (const auto& s = ScriptSphereArray[idx]; s.IsActive() && s.m_nUniqueId == id) { + return idx; + } + break; + case SCRIPT_THING_EFFECT_SYSTEM: + if (const auto& fx = ScriptEffectSystemArray[idx]; fx.IsActive() && fx.m_nId == id) { + return idx; + } + break; + case SCRIPT_THING_SEARCH_LIGHT: + if (const auto& sl = ScriptSearchLightArray[idx]; sl.IsActive() && sl.m_nId == id) { + return idx; + } + break; + case SCRIPT_THING_CHECKPOINT: + if (const auto& cp = ScriptCheckpointArray[idx]; cp.IsActive() && cp.m_nId == id) { + return idx; + } + break; + case SCRIPT_THING_SEQUENCE_TASK: + if (const auto& sqt = ScriptSequenceTaskArray[idx]; sqt.IsActive() && sqt.m_nId == id) { + return idx; + } + break; + case SCRIPT_THING_FIRE: + if (const auto& f = gFireManager.Get(idx); f.IsScript() && f.m_nScriptReferenceIndex == id) { + return idx; + } + break; + case SCRIPT_THING_2D_EFFECT: + if (CScripted2dEffects::ms_activated[idx] && CScripted2dEffects::ScriptReferenceIndex[idx] == id) { + return idx; + } + break; + case SCRIPT_THING_DECISION_MAKER: + CDecisionMakerTypes::GetInstance(); + if (CDecisionMakerTypes::m_bIsActive[idx] && CDecisionMakerTypes::ScriptReferenceIndex[idx] == id) { + return idx; + } + break; + case SCRIPT_THING_PED_GROUP: + if (CPedGroups::ms_activeGroups[idx] && CPedGroups::ScriptReferenceIndex[idx] == id) { + return idx; + } + break; + default: + break; + } + return -1; } // 0x483720 +// TODO: TEST REFACTOR! int32 CTheScripts::GetNewUniqueScriptThingIndex(int32 index, eScriptThingType type) { - return plugin::CallAndReturn(index, type); + const auto NewUniqueId = [index](auto& id) -> int32 { + if (id == -1 || id == -2) { + id = 1; + } else { + ++id; + } + + return (uint32)id << 16 | index; + }; + + switch (type) { + case eScriptThingType::SCRIPT_THING_SPHERE: + return NewUniqueId(ScriptSphereArray[index].m_nUniqueId); + case eScriptThingType::SCRIPT_THING_EFFECT_SYSTEM: + return NewUniqueId(ScriptEffectSystemArray[index].m_nId); + case eScriptThingType::SCRIPT_THING_SEARCH_LIGHT: + return NewUniqueId(ScriptSearchLightArray[index].m_nId); + case eScriptThingType::SCRIPT_THING_CHECKPOINT: + return NewUniqueId(ScriptCheckpointArray[index].m_nId); + case eScriptThingType::SCRIPT_THING_SEQUENCE_TASK: + ScriptSequenceTaskArray[index].m_bUsed = true; + return NewUniqueId(ScriptSequenceTaskArray[index].m_nId); + case eScriptThingType::SCRIPT_THING_FIRE: + return NewUniqueId(gFireManager.m_aFires[index].m_nScriptReferenceIndex); + case eScriptThingType::SCRIPT_THING_2D_EFFECT: + return NewUniqueId(CScripted2dEffects::ScriptReferenceIndex[index]); + case eScriptThingType::SCRIPT_THING_DECISION_MAKER: + CDecisionMakerTypes::GetInstance(); // ? TODO check if we really need this + return NewUniqueId(CDecisionMakerTypes::ScriptReferenceIndex[index]); + case eScriptThingType::SCRIPT_THING_PED_GROUP: + return NewUniqueId(CPedGroups::ScriptReferenceIndex[index]); + default: + break; + } + + return -1; } // 0x464D20 int32 CTheScripts::GetScriptIndexFromPointer(CRunningScript* thread) { - return (thread - ScriptsArray.data()) / sizeof(CRunningScript); + assert(ScriptsArray.data() <= thread && thread < ScriptsArray.data() + ScriptsArray.size()); + return std::distance(ScriptsArray.data(), thread); } /*! @@ -236,51 +899,122 @@ void CTheScripts::ReinitialiseSwitchStatementData() { NumberOfEntriesStillToReadForSwitch = 0; ValueToCheckInSwitchStatement = 0; SwitchDefaultExists = false; - SwitchDefaultAddress = nullptr; + SwitchDefaultAddress = 0; NumberOfEntriesInSwitchTable = 0; } // unused // 0x? void CTheScripts::RemoveFromVehicleModelsBlockedByScript(int32 modelIndex) { - for (auto& script : VehicleModelsBlockedByScript) { - // ? + for (auto& model : VehicleModelsBlockedByScript) { + if (model == modelIndex) { + model = MODEL_INVALID; + } } } // 0x46ABC0 void CTheScripts::RemoveFromWaitingForScriptBrainArray(CEntity* entity, int16 modelIndex) { - plugin::Call<0x46ABC0, CEntity*, int16>(entity, modelIndex); + for (auto& bwe : EntitiesWaitingForScriptBrain) { + if (bwe.m_pEntity != entity || bwe.m_ScriptBrainIndex != modelIndex) { + continue; + } + + bwe.m_pEntity = nullptr; + bwe.m_ScriptBrainIndex = MODEL_INVALID; + } } // 0x4936C0 void CTheScripts::RemoveScriptCheckpoint(int32 scriptIndex) { - plugin::Call<0x4936C0, int32>(scriptIndex); + const auto i = GetActualScriptThingIndex(scriptIndex, eScriptThingType::SCRIPT_THING_CHECKPOINT); + if (i == -1) { + return; + } + + auto& scp = ScriptCheckpointArray[i]; + if (const auto* cp = scp.m_Checkpoint) { + CCheckpoints::DeleteCP(cp->m_ID, cp->m_Type.get_underlying()); + } + scp.m_bUsed = false; + scp.m_nId = 0; + + --NumberOfScriptCheckpoints; } // 0x492FD0 void CTheScripts::RemoveScriptEffectSystem(int32 scriptIndex) { - plugin::Call<0x492FD0, int32>(scriptIndex); + const auto i = GetActualScriptThingIndex(scriptIndex, eScriptThingType::SCRIPT_THING_EFFECT_SYSTEM); + if (i == -1) { + return; + } + + auto& sef = ScriptEffectSystemArray[i]; + sef.m_bUsed = false; + sef.m_pFxSystem = nullptr; } // 0x493160 void CTheScripts::RemoveScriptSearchLight(int32 scriptIndex) { - plugin::Call<0x493160, int32>(scriptIndex); + const auto i = GetActualScriptThingIndex(scriptIndex, eScriptThingType::SCRIPT_THING_SEARCH_LIGHT); + if (i != -1) { + ScriptSearchLightArray[i].Clear(); + --NumberOfScriptSearchLights; + } } // 0x483BA0 void CTheScripts::RemoveScriptSphere(int32 scriptIndex) { - plugin::Call<0x483BA0>(); + const auto i = GetActualScriptThingIndex(scriptIndex, eScriptThingType::SCRIPT_THING_SPHERE); + if (i == -1) { + return; + } + + auto& ss = ScriptSphereArray[i]; + ss.m_bUsed = false; + ss.m_nId = 0; } // 0x465A40 void CTheScripts::RemoveScriptTextureDictionary() { - plugin::Call<0x465A40>(); + rng::for_each(ScriptSprites, &CSprite2d::Delete); + if (const auto slot = CTxdStore::FindTxdSlot("script"); slot != -1) { + if (const auto* txd = CTxdStore::ms_pTxdPool->GetAt(slot); txd) { + CTxdStore::RemoveTxd(slot); + } + } } // 0x486240 void CTheScripts::RemoveThisPed(CPed* ped) { - plugin::Call<0x486240, CPed*>( ped); + if (!ped) { + return; + } + + if (auto* veh = ped->GetVehicleIfInOne()) { + if (veh->IsDriver(ped)) { + veh->RemoveDriver(false); + + if (veh->m_nDoorLock == eCarLock::CARLOCK_COP_CAR) { + veh->m_nDoorLock = eCarLock::CARLOCK_UNLOCKED; + } + + if (ped->IsCop() && veh->IsLawEnforcementVehicle()) { + veh->ChangeLawEnforcerState(false); + } + } else { + veh->RemovePassenger(ped); + } + } + + const auto isMissionChar = ped->m_nCreatedBy == ePedCreatedBy::PED_MISSION; + + CWorld::RemoveReferencesToDeletedObject(ped); + delete ped; + + if (isMissionChar) { + --CPopulation::ms_nTotalMissionPeds; + } } // 0x464C20 @@ -297,10 +1031,30 @@ CRunningScript* CTheScripts::StartNewScript(uint8* startIP) { } // 0x464C90 -CRunningScript* StartNewScript(uint8* startIP, uint16 index) { - return plugin::CallAndReturn(startIP, index); +CRunningScript* CTheScripts::StartNewScript(uint8* startIP, uint16 index) { + if (!pIdleScripts) { + return nullptr; + } + + auto* script = pIdleScripts; + while (script && script != &ScriptsArray[index]) { + script = script->m_pNext; + } + + if (!script) { + return nullptr; + } + + script->RemoveScriptFromList(&pIdleScripts); + script->Init(); + script->SetCurrentIp(startIP); + script->AddScriptToList(&pActiveScripts); + script->SetActive(true); + + return script; } +// 0x481290 void CTheScripts::UndoBuildingSwaps() { for (auto& swap : BuildingSwapArray) { if (swap.m_pCBuilding) { @@ -334,27 +1088,266 @@ bool CTheScripts::IsPedStopped(CPed* ped) { // 0x464D50 bool CTheScripts::IsPlayerOnAMission() { - return plugin::CallAndReturn(); + return OnAMissionFlag && notsa::ReadAs(&ScriptSpace[OnAMissionFlag]) == 1; } // 0x4861F0 -bool CTheScripts::IsVehicleStopped(CVehicle* vehicle) { - return plugin::CallAndReturn(vehicle); +bool CTheScripts::IsVehicleStopped(CVehicle* veh) { + return std::max(CTimer::GetTimeStep(), CTimer::ms_fOldTimeStep) / 100.0f >= veh->m_fMovingSpeed; } // 0x5D4FD0 -bool CTheScripts::Load() { - return plugin::CallAndReturn(); +void CTheScripts::Load() { + Init(); + + const auto globalVarsLen = LoadDataFromWorkBuffer(); + if (globalVarsLen > MAX_SAVED_GVAR_PART_SIZE) { + const auto numParts = (globalVarsLen - MAX_SAVED_GVAR_PART_SIZE - 1) / MAX_SAVED_GVAR_PART_SIZE + 1; + const auto remainder = globalVarsLen - MAX_SAVED_GVAR_PART_SIZE * numParts; + + for (auto i = 0u; i < numParts; i++) { + CGenericGameStorage::LoadDataFromWorkBuffer(ScriptSpace.data(), MAX_SAVED_GVAR_PART_SIZE); + } + CGenericGameStorage::LoadDataFromWorkBuffer(ScriptSpace.data(), remainder); + } else { + CGenericGameStorage::LoadDataFromWorkBuffer(ScriptSpace.data(), globalVarsLen); + } + + for (auto& sfb : ScriptsForBrains.m_aScriptForBrains) { + LoadDataFromWorkBuffer(sfb); + } + + LoadDataFromWorkBuffer(OnAMissionFlag); + LoadDataFromWorkBuffer(LastMissionPassedTime); + + for (auto& bswap : BuildingSwapArray) { + const auto type = LoadDataFromWorkBuffer(); + const auto poolRef = LoadDataFromWorkBuffer() - 1; + LoadDataFromWorkBuffer(bswap.m_nNewModelIndex); + LoadDataFromWorkBuffer(bswap.m_nOldModelIndex); + + bswap.m_pCBuilding = nullptr; + switch (type) { + case ScriptSavedObjectType::NONE: + case ScriptSavedObjectType::NOP: + break; + case ScriptSavedObjectType::BUILDING: + bswap.m_pCBuilding = GetBuildingPool()->GetAt(poolRef); + break; + default: + NOTSA_UNREACHABLE(); + } + + if (auto* b = bswap.m_pCBuilding) { + CWorld::Remove(b); + b->ReplaceWithNewModel(bswap.m_nNewModelIndex); + CWorld::Add(b); + } + } + + for (auto& is : InvisibilitySettingArray) { + const auto type = LoadDataFromWorkBuffer(); + const auto poolRef = LoadDataFromWorkBuffer() - 1; + + is = nullptr; // clear beforehand + switch (type) { + case ScriptSavedObjectType::NONE: + case ScriptSavedObjectType::NOP: + // set to nullptr, already done + break; + case ScriptSavedObjectType::BUILDING: + if (auto* obj = GetBuildingPool()->GetAt(poolRef)) { + is = obj; + is->m_bUsesCollision = false; + is->m_bIsVisible = false; + } + break; + case ScriptSavedObjectType::OBJECT: + if (auto* obj = GetObjectPool()->GetAt(poolRef)) { + is = obj; + is->m_bUsesCollision = false; + is->m_bIsVisible = false; + } + break; + case ScriptSavedObjectType::DUMMY: + if (auto* obj = GetDummyPool()->GetAt(poolRef)) { + is = obj; + is->m_bUsesCollision = false; + is->m_bIsVisible = false; + } + break; + default: + NOTSA_UNREACHABLE(); + } + } + + for (auto& veh : VehicleModelsBlockedByScript) { + LoadDataFromWorkBuffer(veh); + } + + for (auto& lod : ScriptConnectLodsObjects) { + LoadDataFromWorkBuffer(lod); + } + + for (auto& ag : ScriptAttachedAnimGroups) { + LoadDataFromWorkBuffer(ag); + + if (ag.m_nModelID != MODEL_INVALID) { + ScriptAttachAnimGroupToCharModel(ag.m_nModelID, ag.m_IfpName); + } + } + + LoadDataFromWorkBuffer(bUsingAMultiScriptFile); + LoadDataFromWorkBuffer(bPlayerHasMetDebbieHarry); + + { + // Ignored + LoadDataFromWorkBuffer(); // MainScriptSize + LoadDataFromWorkBuffer(); // LargestMissionScriptSize + LoadDataFromWorkBuffer(); // NumberOfMissionScripts + LoadDataFromWorkBuffer(); // NumberOfExclusiveMissionScripts + LoadDataFromWorkBuffer(); // LargestNumberOfMissionScriptLocalVariables + } + + // Unused + // auto j = 0u; + // for (auto* s = pActiveScripts; s; s->m_pNext) + // j++; + + auto numScripts = LoadDataFromWorkBuffer(); + for (auto i = 0u; i < numScripts; i++) { + auto* script = StartNewScript((uint8*)LoadDataFromWorkBuffer()); + { + const auto prev = script->m_pPrev, next = script->m_pNext; + LoadDataFromWorkBuffer(*script); + script->m_pPrev = prev; + script->m_pNext = next; + } + script->SetCurrentIp(&ScriptSpace[LoadDataFromWorkBuffer()]); + + for (auto& stk : script->m_IPStack) { + if (const auto ip = LoadDataFromWorkBuffer()) { + stk = &ScriptSpace[ip]; + } else { + stk = nullptr; + } + } + } } // 0x5D4C40 -bool CTheScripts::Save() { - return plugin::CallAndReturn(); +void CTheScripts::Save() { + const auto globalVarsLen = GetSCMChunk()->m_NextChunkOffset; + if (globalVarsLen > MAX_SAVED_GVAR_PART_SIZE) { + const auto numParts = (globalVarsLen - MAX_SAVED_GVAR_PART_SIZE - 1) / MAX_SAVED_GVAR_PART_SIZE + 1; + const auto remainder = globalVarsLen - MAX_SAVED_GVAR_PART_SIZE * numParts; + + for (auto i = 0u; i < numParts; i++) { + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSpace.data(), MAX_SAVED_GVAR_PART_SIZE); + } + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSpace.data(), remainder); + } else { + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSpace.data(), globalVarsLen); + } + + for (auto& sfb : ScriptsForBrains.m_aScriptForBrains) { + CGenericGameStorage::SaveDataToWorkBuffer(sfb); + } + + CGenericGameStorage::SaveDataToWorkBuffer(OnAMissionFlag); + CGenericGameStorage::SaveDataToWorkBuffer(LastMissionPassedTime); + + for (auto& bswap : BuildingSwapArray) { + if (auto* b = bswap.m_pCBuilding) { + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSavedObjectType::BUILDING); + + // Add 1 to the index because 0 is considered invalid and index can be 0. + CGenericGameStorage::SaveDataToWorkBuffer(GetBuildingPool()->GetIndex(b) + 1); + } else { + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSavedObjectType::NONE); + CGenericGameStorage::SaveDataToWorkBuffer(0); + } + + CGenericGameStorage::SaveDataToWorkBuffer(bswap.m_nNewModelIndex); + CGenericGameStorage::SaveDataToWorkBuffer(bswap.m_nOldModelIndex); + } + + for (auto& is : InvisibilitySettingArray) { + if (!is) { + continue; + } + + switch (is->GetType()) { + case ENTITY_TYPE_NOTHING: + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSavedObjectType::NONE); + CGenericGameStorage::SaveDataToWorkBuffer(0); + break; + case ENTITY_TYPE_BUILDING: + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSavedObjectType::BUILDING); + CGenericGameStorage::SaveDataToWorkBuffer(GetBuildingPool()->GetIndex(is->AsBuilding()) + 1); + break; + case ENTITY_TYPE_OBJECT: + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSavedObjectType::OBJECT); + CGenericGameStorage::SaveDataToWorkBuffer(GetObjectPool()->GetIndex(is->AsObject()) + 1); + break; + case ENTITY_TYPE_DUMMY: + CGenericGameStorage::SaveDataToWorkBuffer(ScriptSavedObjectType::DUMMY); + CGenericGameStorage::SaveDataToWorkBuffer(GetDummyPool()->GetIndex(is->AsDummy()) + 1); + break; + default: + NOTSA_UNREACHABLE(); + } + } + + for (auto& veh : VehicleModelsBlockedByScript) { + CGenericGameStorage::SaveDataToWorkBuffer(veh); + } + + for (auto& lod : ScriptConnectLodsObjects) { + CGenericGameStorage::SaveDataToWorkBuffer(lod); + } + + for (auto& ag : ScriptAttachedAnimGroups) { + CGenericGameStorage::SaveDataToWorkBuffer(ag); + } + + CGenericGameStorage::SaveDataToWorkBuffer(bUsingAMultiScriptFile); + CGenericGameStorage::SaveDataToWorkBuffer(bPlayerHasMetDebbieHarry); // ? + + CGenericGameStorage::SaveDataToWorkBuffer(MainScriptSize); + CGenericGameStorage::SaveDataToWorkBuffer(LargestMissionScriptSize); + CGenericGameStorage::SaveDataToWorkBuffer(NumberOfMissionScripts); + CGenericGameStorage::SaveDataToWorkBuffer(NumberOfExclusiveMissionScripts); + CGenericGameStorage::SaveDataToWorkBuffer(LargestNumberOfMissionScriptLocalVariables); + + auto numNonExternalScripts = 0u; + auto* lastScript = pActiveScripts; + for (auto* s = pActiveScripts; s; s = s->m_pNext) { + if (!s->m_bIsExternal && s->m_nExternalType == -1) { + numNonExternalScripts++; + lastScript = s; + } + } + CGenericGameStorage::SaveDataToWorkBuffer(numNonExternalScripts); + + for (auto* s = lastScript; s; s = s->m_pPrev) { + if (s->m_bIsExternal || s->m_nExternalType != -1) { + continue; + } + + CGenericGameStorage::SaveDataToWorkBuffer((int16)GetScriptIndexFromPointer(s)); + CGenericGameStorage::SaveDataToWorkBuffer(*s); + CGenericGameStorage::SaveDataToWorkBuffer((uint32)(s->m_IP - ScriptSpace.data())); + + for (auto& stk : s->m_IPStack) { + CGenericGameStorage::SaveDataToWorkBuffer((uint32)(stk ? stk - ScriptSpace.data() : 0)); + } + } } // 0x464BB0 void CTheScripts::WipeLocalVariableMemoryForMissionScript() { - memset(&LocalVariablesForCurrentMission, 0, sizeof(LocalVariablesForCurrentMission)); + rng::fill(LocalVariablesForCurrentMission, tScriptParam{}); } // 0x46A810 @@ -371,7 +1364,7 @@ bool CTheScripts::HasVehicleModelBeenBlockedByScript(eModelID carModelId) { void CTheScripts::StartTestScript() { ZoneScoped; - StartNewScript(MainSCMBlock); + StartNewScript(MainSCMBlock.data()); } // 0x46A000 @@ -387,27 +1380,27 @@ void CTheScripts::Process() { UpsideDownCars.UpdateTimers(); StuckCars.Process(); MissionCleanUp.CheckIfCollisionHasLoadedForMissionObjects(); - CTheScripts::DrawScriptSpheres(); - CTheScripts::ProcessAllSearchLights(); - CTheScripts::ProcessWaitingForScriptBrainArray(); + DrawScriptSpheres(); + ProcessAllSearchLights(); + ProcessWaitingForScriptBrainArray(); - if (CTheScripts::FailCurrentMission) { - --CTheScripts::FailCurrentMission; + if (FailCurrentMission) { + --FailCurrentMission; } - if (CTheScripts::UseTextCommands) { + if (UseTextCommands) { rng::fill(IntroTextLines, tScriptText{}); NumberOfIntroTextLinesThisFrame = 0; rng::fill(IntroRectangles, tScriptRectangle{}); NumberOfIntroRectanglesThisFrame = 0; - CTheScripts::UseTextCommands = false; + UseTextCommands = false; } const auto timeStepMS = (int32)CTimer::GetTimeStepInMS(); - LocalVariablesForCurrentMission[32].iParam += timeStepMS; - LocalVariablesForCurrentMission[33].iParam += timeStepMS; + LocalVariablesForCurrentMission[SCRIPT_VAR_TIMERA].iParam += timeStepMS; + LocalVariablesForCurrentMission[SCRIPT_VAR_TIMERB].iParam += timeStepMS; CLoadingScreen::NewChunkLoaded(); @@ -435,29 +1428,204 @@ void CTheScripts::Process() { void CTheScripts::ProcessAllSearchLights() { ZoneScoped; - return plugin::Call<0x4939F0>(); + for (auto& light : ScriptSearchLightArray) { + if (!light.IsActive() || !light.bIsUsed) { + continue; + } + + switch (light.m_nCurrentState) { + case eScriptSearchLightState::STATE_1: { + const auto d = light.m_PathCoord1 - light.m_Target; + if (d.SquaredMagnitude() > sq(light.m_fPathSpeed)) { + light.m_Target *= d.Normalized(); + break; + } + + light.m_Target = light.m_PathCoord1; + light.m_nCurrentState = eScriptSearchLightState::STATE_2; + break; + } + case eScriptSearchLightState::STATE_2: { + const auto d = light.m_PathCoord2 - light.m_Target; + if (d.SquaredMagnitude() > sq(light.m_fPathSpeed)) { + light.m_Target *= d.Normalized(); + break; + } + + light.m_Target = light.m_PathCoord2; + light.m_nCurrentState = eScriptSearchLightState::STATE_1; + break; + } + case eScriptSearchLightState::STATE_3: { + const auto d = light.m_FollowingEntity->GetPosition() - light.m_Target; + if (d.SquaredMagnitude() > sq(light.m_fPathSpeed)) { + light.m_Target *= d.Normalized(); + break; + } + + light.m_Target = light.m_FollowingEntity->GetPosition(); + /* flag is not altered */ + break; + } + case eScriptSearchLightState::STATE_4: { + auto d = light.m_PathCoord1 - light.m_FollowingEntity->GetPosition(); + if (d.SquaredMagnitude() > sq(light.m_fPathSpeed)) { + light.m_Target *= d.Normalized(); + break; + } + + light.m_Target = light.m_PathCoord1; + light.m_fPathSpeed = 0.0f; + light.m_PathCoord1 = CVector{}; + light.m_nCurrentState = eScriptSearchLightState::STATE_0; + break; + } + default: + break; + } + + CEntity* bulb = light.m_Bulb; + if (!bulb) { + continue; + } + + const auto prevPos = bulb->GetPosition(); + const auto tgtBulbDir = (light.m_Target - bulb->GetPosition()).Normalized(); + + const auto Transform = [&](CEntity* entity) { + const auto rotX = std::atan2(tgtBulbDir.z, tgtBulbDir.Magnitude2D()); + const auto rotZ = tgtBulbDir.Heading(); + + entity->m_matrix->RotateX(rotX); + entity->m_matrix->RotateZ(rotZ); + entity->GetPosition() += prevPos; + entity->UpdateRW(); + entity->UpdateRwFrame(); + }; + + Transform(bulb); + Transform(light.m_Housing); + } } +// 0x46CF00 void CTheScripts::ProcessWaitingForScriptBrainArray() { ZoneScoped; - plugin::Call<0x46CF00>(); + if (!FindPlayerPed()) + return; + + for (auto& e : EntitiesWaitingForScriptBrain) { + if (!e.m_pEntity) { + continue; + } + + switch (const auto t = ScriptsForBrains.m_aScriptForBrains[e.m_ScriptBrainIndex].m_TypeOfBrain) { + case 0: // TODO: enum + case 3: // for peds? + { + auto* ped = e.m_pEntity->AsPed(); + const auto idx = ScriptsForBrains.m_aScriptForBrains[ped->m_StreamedScriptBrainToLoad].m_StreamedScriptIndex; + + if (CStreaming::IsModelLoaded(SCMToModelId(idx))) { + ScriptsForBrains.StartNewStreamedScriptBrain( + static_cast(ped->m_StreamedScriptBrainToLoad), // cast? + ped, + false + ); + } else { + CStreaming::RequestModel(SCMToModelId(idx), STREAMING_MISSION_REQUIRED); + } + break; + } + case 1: + case 4: // for objects? + { + auto* obj = e.m_pEntity->AsObject(); + + switch (obj->objectFlags.b0x100000_0x200000) { + case 1: + if (!ScriptsForBrains.IsObjectWithinBrainActivationRange(obj, FindPlayerCentreOfWorld())) + break; + + [[fallthrough]]; + case 2: + ScriptsForBrains.StartOrRequestNewStreamedScriptBrain( + static_cast(obj->m_nStreamedScriptBrainToLoad), // cast? + obj, + t, + false + ); + break; + default: + break; + } + break; + } + default: + break; + } + } } // 0x4812D0 void CTheScripts::UndoEntityInvisibilitySettings() { - plugin::Call<0x4812D0>(); + for (auto& is : InvisibilitySettingArray) { + if (!is) { + continue; + } + + is->m_bIsVisible = true; + is->m_bUsesCollision = true; + + is = nullptr; // Remove from the array. + } +} + +// 0x4703C0 +void CTheScripts::UseSwitchJumpTable(int32& switchLabelAddress) { + switchLabelAddress = 0x0; + + const auto CheckEntryAndJump = [&](tScriptSwitchCase* swtch) { + if (swtch && ValueToCheckInSwitchStatement != swtch->m_nSwitchValue) { + return false; + } + + switchLabelAddress = swtch ? swtch->m_nSwitchLabelAddress : SwitchDefaultAddress; + ReinitialiseSwitchStatementData(); + return true; + }; + + auto ptr1 = 0u; + auto ptr2 = NumberOfEntriesInSwitchTable - 1u; + while (ptr2 - ptr1 > 1) { + const auto idx = (ptr1 + ptr2) / 2; + if (CheckEntryAndJump(&SwitchJumpTable[idx])) { + return; + } + + if (ValueToCheckInSwitchStatement <= SwitchJumpTable[idx].m_nSwitchValue) { + ptr2 = idx; + } else { + ptr1 = idx; + } + } + + if (CheckEntryAndJump(&SwitchJumpTable[ptr2]) || CheckEntryAndJump(&SwitchJumpTable[ptr1])) { + return; + } + + CheckEntryAndJump(nullptr); // Jump to the default case } // 0x4646D0 void CTheScripts::PrintListSizes() { - int active = 0; - int idle = 0; + auto active{ 0u }, idle{ 0u }; - for (CRunningScript* script = pActiveScripts; script; script = script->m_pNext) active++; - for (CRunningScript* script = pIdleScripts; script; script = script->m_pNext) idle++; + for (const auto* s = pActiveScripts; s; s = s->m_pNext) active++; + for (const auto* s = pIdleScripts; s; s = s->m_pNext) idle++; - DEV_LOG("Scripts Active: {}, Idle: {}", active, idle); + NOTSA_LOG_DEBUG("Scripts Active: {}, Idle: {}", active, idle); } uint32 DbgLineColour = 0x0000FFFF; // r = 0, g = 0, b = 255, a = 255 @@ -553,8 +1721,71 @@ void CTheScripts::DrawDebugAngledCube(const CVector& inf, const CVector& sup, co } // 0x464980 -void CTheScripts::DrawScriptSpritesAndRectangles(bool bDrawBeforeFade) { - return plugin::Call<0x464980, bool>(bDrawBeforeFade); +void CTheScripts::DrawScriptSpritesAndRectangles(bool drawBeforeFade) { + for (const auto& rt : IntroRectangles) { + if (rt.m_bDrawBeforeFade != drawBeforeFade) { + continue; + } + + switch (rt.m_nType) { + case eScriptRectangleType::TYPE_1: + FrontEndMenuManager.DrawWindowedText( + rt.cornerA.x, rt.cornerA.y, + rt.cornerB.x, // ? + rt.gxt1, + rt.gxt2, + rt.m_Alignment + ); + break; + case eScriptRectangleType::TYPE_2: + FrontEndMenuManager.DrawWindow( + CRect{ rt.cornerA, rt.cornerB }, + rt.gxt1, + 0, + CRGBA{ 0, 0, 0, 190 }, + rt.m_nTextboxStyle, // ? + true + ); + break; + case eScriptRectangleType::TYPE_3: + CSprite2d::DrawRect( + CRect{ rt.cornerA, rt.cornerB }, + rt.m_nTransparentColor + ); + break; + case eScriptRectangleType::TYPE_4: + ScriptSprites[rt.m_nTextureId].Draw( + CRect{ rt.cornerA, rt.cornerB }, + rt.m_nTransparentColor + ); + break; + case eScriptRectangleType::TYPE_5: { + // mid: Vector that points to the middle of line A-B from A. + // vAM: A to mid. + const auto mid = (rt.cornerA + rt.cornerB) / 2.0f; + const auto vAM = mid - rt.cornerA; + const auto cos = std::cos(rt.m_nAngle); + const auto sin = std::sin(rt.m_nAngle); + + // This is 2D rotation, couldn't find a better function aside from + // using matricies or quaternions. + ScriptSprites[rt.m_nTextureId].Draw( + -cos * vAM.x + sin * vAM.y + mid.x, + -sin * vAM.x - cos * vAM.y + mid.y, + +sin * vAM.y + cos * vAM.x + mid.x, + +sin * vAM.x - cos * vAM.y + mid.y, + -cos * vAM.x - sin * vAM.y + mid.x, + +cos * vAM.y - sin * vAM.x + mid.y, + +cos * vAM.x - sin * vAM.y + mid.x, + +sin * vAM.x + cos * vAM.y + mid.y, + rt.m_nTransparentColor + ); + break; + } + default: + break; + } + } } // Usage: @@ -597,22 +1828,24 @@ void CTheScripts::ScriptDebugCircle2D(float x, float y, float width, float heigh void CTheScripts::DrawScriptSpheres() { ZoneScoped; - return plugin::Call<0x4810E0>(); - for (auto& script : ScriptSphereArray) { - if (script.m_bUsed) { - /* todo: - C3dMarkers::PlaceMarkerSet( - script.nId, - MARKER3D_CYLINDER, - script.vCoords, - script.fRadius, - SPHERE_MARKER_R, SPHERE_MARKER_G, SPHERE_MARKER_B, SPHERE_MARKER_A, - SPHERE_MARKER_PULSE_PERIOD, - SPHERE_MARKER_PULSE_FRACTION, - 0 - ); - */ + for (auto& ss : ScriptSphereArray) { + if (!ss.m_bUsed) { + continue; } + + C3dMarkers::PlaceMarkerSet( + ss.m_nId, + e3dMarkerType::MARKER3D_CYLINDER, + ss.m_vCoords, + ss.m_fRadius, + 255, + 0, + 0, + 228, + 2048, + 0.1f, + 0 + ); } } @@ -715,5 +1948,58 @@ void CTheScripts::RenderTheScriptDebugLines() { void CTheScripts::RenderAllSearchLights() { ZoneScoped; - return plugin::Call<0x493E30>(); + for (const auto&& [i, light] : notsa::enumerate(ScriptSearchLightArray)) { + if (!light.IsActive()) { + continue; + } + + const auto origin = [&] { + if (auto e = notsa::coalesce(light.m_AttachedEntity.Get(), light.m_Bulb.Get())) { + return e->GetMatrix().TransformVector(light.m_Origin) + e->GetPosition(); + } + + return light.m_Origin; + }(); + + CHeli::SearchLightCone( + i, + origin, + light.m_Target, + light.m_fTargetRadius, + 1.0f, + light.m_bClipIfColliding, + light.m_bEnableShadow, + light.m_TargetSpot, + light.vf64, + light.vf70, + true, + light.m_fBaseRadius, + 0.0f, + 0.0f, + 1.0f + ); + } +} + +// 0x474800 +bool CTheScripts::ScriptAttachAnimGroupToCharModel(int32 modelId, const char* ifpName) { + auto* mi = CModelInfo::GetModelInfo(modelId); + if (mi->GetAnimFileIndex() == CAnimManager::GetAnimationBlockIndex(ifpName)) { + // Already attached? + return false; + } + mi->SetAnimFile(ifpName); + mi->ConvertAnimFileIndex(); + return true; +} + +// 0x470A20 +void CTheScripts::ScriptConnectLodsFunction(int32 lodRef1, int32 lodRef2) { + auto obj1 = GetObjectPool()->GetAtRef(lodRef1), obj2 = GetObjectPool()->GetAtRef(lodRef2); + + obj1->m_pLod = obj2; + ++obj2->m_nNumLodChildren; + CWorld::Remove(obj2); + obj2->SetupBigBuilding(); + CWorld::Add(obj2); } diff --git a/source/game_sa/Scripts/TheScripts.h b/source/game_sa/Scripts/TheScripts.h index 2569b1db06..1a6e6bac52 100644 --- a/source/game_sa/Scripts/TheScripts.h +++ b/source/game_sa/Scripts/TheScripts.h @@ -20,7 +20,10 @@ #include "UpsideDownCarCheck.h" #include "ScriptsForBrains.h" +#include "SCMChunks.hpp" + class CCheckpoint; +enum class eCheckpointType : uint32; enum class eCrossHairType : uint32 { NONE, @@ -150,31 +153,40 @@ struct tScriptText { }; VALIDATE_SIZE(tScriptText, 0x44); +enum class eScriptRectangleType : int32 { + TYPE_0, + TYPE_1, + TYPE_2, + TYPE_3, + TYPE_4, + TYPE_5, +}; + struct tScriptRectangle { - int32 m_nType; - bool m_bDrawBeforeFade; - char field_5; - int16 m_nTextureId; - CVector2D cornerA; - CVector2D cornerB; - int32 m_nAngle; - CRGBA m_nTransparentColor; - char gxt[8]; - int32 field_28; - int32 field_2C; - int32 field_30; - int32 field_34; - uint32 m_nTextboxStyle; + eScriptRectangleType m_nType; + bool m_bDrawBeforeFade; + char field_5; + int16 m_nTextureId; + CVector2D cornerA; + CVector2D cornerB; + float m_nAngle; + CRGBA m_nTransparentColor; + char gxt1[8]; + int16 field_28; + char gxt2[8]; + int16 field_32; + eFontAlignment m_Alignment; + uint32 m_nTextboxStyle; tScriptRectangle() { // 0x4691C8 - m_nType = 0; + m_nType = eScriptRectangleType::TYPE_0; m_bDrawBeforeFade = false; m_nTextureId = -1; cornerA = CVector2D(); cornerB = CVector2D(); m_nAngle = 0; m_nTransparentColor = CRGBA(255, 255, 255, 255); - gxt[0] = 0; + gxt1[0] = '\0'; m_nTextboxStyle = 3; } }; @@ -190,27 +202,61 @@ struct tScriptAttachedAnimGroup { } }; +enum class eScriptSearchLightState : uint8 { + STATE_0, + STATE_1, + STATE_2, + STATE_3, + STATE_4, +}; + struct tScriptSearchlight { - bool m_bUsed{true}; - char m_field_1{}; // unk flag; m_bNotScriptedLight ? - bool m_bEnableShadow{}; - uint8 m_nFlags{}; - int16 m_nId{1}; - CVector m_Origin{}; - CVector m_Target{}; - float m_fTargetRadius{}; - float m_fBaseRadius{}; - CVector m_PathCoord1{}; - CVector m_PathCoord2{}; - float m_fPathSpeed{}; - CEntity* m_AttachedEntity{}; - CEntity* m_FollowingEntity{}; - CEntity* m_Tower{}; - CEntity* m_Housing{}; - CEntity* m_Bulb{}; - CVector m_TargetSpot{}; - CVector vf64{}; - CVector vf70{}; + bool m_bUsed{ true }; + bool m_bClipIfColliding{}; + bool m_bEnableShadow{}; + struct { + eScriptSearchLightState m_nCurrentState : 7; + bool bIsUsed : 1; // ? + } /* m_Flags */; + int16 m_nId{ 1 }; + CVector m_Origin{}; + CVector m_Target{}; + float m_fTargetRadius{}; + float m_fBaseRadius{}; + CVector m_PathCoord1{}; + CVector m_PathCoord2{}; + float m_fPathSpeed{}; + CEntity::Ref m_AttachedEntity{}; + CEntity::Ref m_FollowingEntity{}; + CEntity::Ref m_Tower{}; + CEntity::Ref m_Housing{}; + CEntity::Ref m_Bulb{}; + CVector m_TargetSpot{}; + CVector vf64{}; + CVector vf70{}; + + // NOTSA + void Clear() { + m_bUsed = true; + m_bClipIfColliding = false; + m_bEnableShadow = false; + m_nId = 1; + m_Origin = CVector{}; + m_Target = CVector{}; + m_fTargetRadius = 0.0f; + m_fBaseRadius = 0.0f; + m_PathCoord1 = CVector{}; + m_PathCoord2 = CVector{}; + m_fPathSpeed = 0.0f; + m_AttachedEntity = nullptr; + m_FollowingEntity = nullptr; + m_Tower = nullptr; + m_Housing = nullptr; + m_Bulb = nullptr; + m_TargetSpot = nullptr; + vf64 = CVector{}; + vf70 = CVector{}; + } //! Script thing ID auto GetId() { return m_nId; } @@ -261,13 +307,16 @@ struct tStoredLine { VALIDATE_SIZE(tStoredLine, 0x20); struct tScriptBrainWaitEntity { - CEntity* m_pEntity; - int16 m_nSpecialModelIndex; - int16 field_6; + CEntity::Ref m_pEntity{}; + int16 m_ScriptBrainIndex{ -1 }; + int16 field_6{}; - tScriptBrainWaitEntity() { // 0x468E12 - m_pEntity = nullptr; - m_nSpecialModelIndex = -1; + tScriptBrainWaitEntity() = default; // 0x468E12 + + // NOTSA + void Clear() { + m_pEntity = nullptr; + m_ScriptBrainIndex = -1; } }; VALIDATE_SIZE(tScriptBrainWaitEntity, 0x8); @@ -292,7 +341,7 @@ enum { MAX_NUM_SCRIPT_SEQUENCE_TASKS = 64, MAX_NUM_SCRIPT_CHECKPOINTS = 20, MAX_NUM_SCRIPT_EFFECT_SYSTEMS = 32, - MAX_NUM_SCRIPT_CONNECT_LODS_OBJECTS = 20, + MAX_NUM_SCRIPT_CONNECT_LODS_OBJECTS = 10, MAX_NUM_SCRIPT_ATTACHED_ANIM_GROUPS = 8, MAX_NUM_ENTITIES_WAITING_FOR_SCRIPT_BRAIN = 150, MAX_NUM_VEHICLE_MODELS_BLOCKED_BY_SCRIPT = 20, @@ -308,71 +357,82 @@ enum { MAX_NUM_SUPPRESSED_VEHICLE_MODELS = 40, }; +enum class ScriptSavedObjectType : uint32 { + NONE = 0, + NOP = 1, // ? + BUILDING = 2, + OBJECT = 3, + DUMMY = 4, +}; + +static constexpr uint32 SCRIPT_VAR_TIMERA = 32, SCRIPT_VAR_TIMERB = 33; + static constexpr uint32 MISSION_SCRIPT_SIZE = 69000; class CTheScripts { public: - static constexpr uint32 MAIN_SCRIPT_SIZE = 200000; - static constexpr uint32 SCRIPT_SPACE_SIZE = MAIN_SCRIPT_SIZE + MISSION_SCRIPT_SIZE; + static constexpr uint32 MAIN_SCRIPT_SIZE = 200'000; + static constexpr uint32 SCRIPT_SPACE_SIZE = MAIN_SCRIPT_SIZE + MISSION_SCRIPT_SIZE; + static constexpr uint32 MAX_SAVED_GVAR_PART_SIZE = 51'200; //! Lower `MAIN_SCRIPT_SIZE` is where MAIN.SCM is, remaining `MISSION_SCRIPT_SIZE` is for other loaded scripts. - static inline uint8(&ScriptSpace)[SCRIPT_SPACE_SIZE] = *(uint8(*)[SCRIPT_SPACE_SIZE])0xA49960; + //static inline uint8(&ScriptSpace)[SCRIPT_SPACE_SIZE] = *(uint8(*)[SCRIPT_SPACE_SIZE])0xA49960; + static inline auto& ScriptSpace = *(std::array*)0xA49960; - //! Reference to \r ScriptSpace's lower portion for MAIN.SCM - Prefer this over `&ScriptSpace[0]` - static inline uint8(&MainSCMBlock)[MAIN_SCRIPT_SIZE] = *(uint8(*)[MAIN_SCRIPT_SIZE])(0xA49960 + 0); // Can't use `&ScriptSpace[0]` because init order seems to be messed up... + //! Reference to ScriptSpace's lower portion for MAIN.SCM - Prefer this over `&ScriptSpace[0]` + static inline std::span MainSCMBlock{ ScriptSpace.data() + 0, MAIN_SCRIPT_SIZE }; - //! Reference to \r ScriptSpace's upper portion for other scripts - Prefer this over `&ScriptSpace[MAIN_SCRIPT_SIZE]` - static inline uint8(&MissionBlock)[MISSION_SCRIPT_SIZE] = *(uint8(*)[MISSION_SCRIPT_SIZE])(0xA49960 + MAIN_SCRIPT_SIZE); // Can't use `&ScriptSpace[MAIN_SCRIPT_SIZE]` because init order seems to be messed up... + //! Reference to ScriptSpace's upper portion for other scripts - Prefer this over `&ScriptSpace[MAIN_SCRIPT_SIZE]` + static inline std::span MissionBlock{ ScriptSpace.data() + MainSCMBlock.size(), MISSION_SCRIPT_SIZE }; - static inline std::array& SwitchJumpTable = *(std::array*)0xA43CF8; - static inline uint16& NumberOfEntriesInSwitchTable = *reinterpret_cast(0xA43F50); + static inline auto& SwitchJumpTable = *(std::array*)0xA43CF8; + static inline uint16& NumberOfEntriesInSwitchTable = *reinterpret_cast(0xA43F50); static inline uint16& NumberOfEntriesStillToReadForSwitch = *reinterpret_cast(0xA43F60); + static inline auto& CardStack = *(std::array*)0xA44218; + static inline auto& MultiScriptArray = *(std::array*)0xA444C8; + static inline auto& ScriptConnectLodsObjects = *(std::array*)0xA44800; + static inline auto& ScriptAttachedAnimGroups = *(std::array*)0xA44850; + static inline auto& VehicleModelsBlockedByScript = *(std::array*)0xA448F0; + static inline auto& SuppressedVehicleModels = *(std::array*)0xA44940; + static inline auto& InvisibilitySettingArray = *(std::array*)0xA449E0; - static inline std::array& CardStack = *(std::array*)0xA44218; - static inline std::array& MultiScriptArray = *(std::array*)0xA444C8; - static inline std::array& ScriptConnectLodsObjects = *(std::array*)0xA44800; - static inline std::array& ScriptAttachedAnimGroups = *(std::array*)0xA44850; - static inline std::array& VehicleModelsBlockedByScript = *(std::array*)0xA448F0; - static inline std::array& SuppressedVehicleModels = *(std::array*)0xA44940; - static inline std::array& InvisibilitySettingArray = *(std::array*)0xA449E0; - - static inline std::array& LocalVariablesForCurrentMission =*(std::array*)0xA48960; + static inline auto& LocalVariablesForCurrentMission = *(std::array*)0xA48960; static inline uint32& LargestNumberOfMissionScriptLocalVariables = *reinterpret_cast(0xA444B4); - static inline std::array& BuildingSwapArray = *(std::array*)0xA44A30; + static inline auto& BuildingSwapArray = *(std::array*)0xA44A30; - static inline std::array& UsedObjectArray = *(std::array*)0xA44B70; + static inline auto& UsedObjectArray = *(std::array*)0xA44B70; static inline uint16& NumberOfUsedObjects = *reinterpret_cast(0xA44B6C); - static inline std::array& EntitiesWaitingForScriptBrain = *(std::array*)0xA476B0; - static inline std::array& ScriptsArray = *(std::array*)0xA8B430; - static inline std::array& IntroTextLines = *(std::array*)0xA913E8; + static inline auto& EntitiesWaitingForScriptBrain = *(std::array*)0xA476B0; + static inline auto& ScriptsArray = *(std::array*)0xA8B430; + static inline auto& IntroTextLines = *(std::array*)0xA913E8; static inline uint16& NumberOfIntroTextLinesThisFrame = *reinterpret_cast(0xA44B68); - static inline std::array& IntroRectangles = *(std::array*)0xA92D68; + static inline auto& IntroRectangles = *(std::array*)0xA92D68; static inline uint16& NumberOfIntroRectanglesThisFrame = *reinterpret_cast(0xA44B5C); - static inline std::array& ScriptSprites = *(std::array*)0xA94B68; + static inline auto& ScriptSprites = *(std::array*)0xA94B68; static inline uint16& NumberOfExclusiveMissionScripts = *reinterpret_cast(0xA444B8); - static inline uint16& NumberOfMissionScripts = *reinterpret_cast(0xA444BC); + static inline uint16& NumberOfMissionScripts = *reinterpret_cast(0xA444BC); // // Script things // - static inline std::array& ScriptSphereArray = *(std::array*)0xA91268; - static inline std::array& ScriptEffectSystemArray = *(std::array*)0xA44110; - static inline std::array& ScriptSearchLightArray = *(std::array*)0xA94D68; + static inline auto& ScriptSphereArray = *(std::array*)0xA91268; + static inline auto& ScriptEffectSystemArray = *(std::array*)0xA44110; + static inline auto& ScriptSearchLightArray = *(std::array*)0xA94D68; - static inline std::array& ScriptSequenceTaskArray = *(std::array*)0xA43F68; - static inline uint16& NumberOfScriptSearchLights = *reinterpret_cast(0xA90830); + static inline auto& ScriptSequenceTaskArray = *(std::array*)0xA43F68; + static inline uint16& NumberOfScriptSearchLights = *reinterpret_cast(0xA90830); - static inline std::array& ScriptCheckpointArray = *(std::array*)0xA44070; - static inline uint16& NumberOfScriptCheckpoints = *reinterpret_cast(0xA44068); + static inline auto& ScriptCheckpointArray = *(std::array*)0xA44070; + static inline uint16& NumberOfScriptCheckpoints = *reinterpret_cast(0xA44068); static inline bool& DbgFlag = *reinterpret_cast(0x859CF8); - static inline void*& SwitchDefaultAddress = *reinterpret_cast(0xA43F54); + static inline int32& SwitchDefaultAddress = *reinterpret_cast(0xA43F54); static inline bool& SwitchDefaultExists = *reinterpret_cast(0xA43F58); static inline int32& ValueToCheckInSwitchStatement = *reinterpret_cast(0xA43F5C); static inline int16& CardStackPosition = *reinterpret_cast(0xA44210); @@ -412,7 +472,7 @@ class CTheScripts { static inline bool& UseTextCommands = *reinterpret_cast(0xA44B67); static inline int32& LastRandomPedId = *reinterpret_cast(0xA476A4); static inline uint32& LastMissionPassedTime = *reinterpret_cast(0xA476A8); - static inline int32& OnAMissionFlag = *reinterpret_cast(0xA476AC); + static inline int32& OnAMissionFlag = *reinterpret_cast(0xA476AC); // Refers to the offset of OM flag in script space. static inline CStreamedScripts& StreamedScripts = *reinterpret_cast(0xA47B60); static inline CScriptResourceManager& ScriptResourceManager = *reinterpret_cast(0xA485A8); static inline CUpsideDownCarCheck& UpsideDownCars = *reinterpret_cast(0xA4892C); @@ -434,21 +494,21 @@ class CTheScripts { static void UpdateObjectIndices(); static void ReadMultiScriptFileOffsetsFromScript(); - static uint32 AddScriptCheckpoint(CVector at, CVector pointTo, float radius, int32 type); + static uint32 AddScriptCheckpoint(CVector at, CVector pointTo, float radius, eCheckpointType type); static uint32 AddScriptEffectSystem(FxSystem_c* system); static uint32 AddScriptSearchLight(CVector start, CEntity* entity, CVector target, float targetRadius, float baseRadius); static uint32 AddScriptSphere(uint32 id, CVector posn, float radius); - static void AddToBuildingSwapArray(CBuilding* building, int32 oldModelId, int32 newModelId); - static void AddToInvisibilitySwapArray(CEntity* entity, bool bVisible); - static void AddToListOfConnectedLodObjects(CObject* obj1, CObject* obj2); - static void AddToListOfSpecialAnimGroupsAttachedToCharModels(int32 modelId, Const char* ifpName); - static void AddToSwitchJumpTable(int32 switchValue, int32 switchLabelLocalAddress); - static void AddToVehicleModelsBlockedByScript(int32 modelIndex); - static void AddToWaitingForScriptBrainArray(CEntity* entity, int16 arg2); + static void AddToBuildingSwapArray(CBuilding* building, int32 oldModelId, int32 newModelId); + static void AddToInvisibilitySwapArray(CEntity* entity, bool bVisible); + static void AddToListOfConnectedLodObjects(CObject* obj1, CObject* obj2); + static void AddToListOfSpecialAnimGroupsAttachedToCharModels(int32 modelId, Const char* ifpName); + static void AddToSwitchJumpTable(int32 switchValue, int32 switchLabelLocalAddress); + static void AddToVehicleModelsBlockedByScript(eModelID modelIndex); + static void AddToWaitingForScriptBrainArray(CEntity* entity, int16 arg2); - static void AttachSearchlightToSearchlightObject(int32 searchLightId, CObject* tower, CObject* housing, CObject* bulb, float offsetX, float offsetY, float offsetZ); - static char CheckStreamedScriptVersion(RwStream* stream, char* arg2); + static void AttachSearchlightToSearchlightObject(int32 searchLightId, CObject* tower, CObject* housing, CObject* bulb, CVector offset); + static bool CheckStreamedScriptVersion(RwStream* stream, char* arg2); static void CleanUpThisObject(CObject* obj); static void CleanUpThisPed(CPed* ped); static void CleanUpThisVehicle(CVehicle* vehicle); @@ -457,7 +517,7 @@ class CTheScripts { static void ClearSpaceForMissionEntity(const CVector& pos, CEntity* entity); static void DoScriptSetupAfterPoolsHaveLoaded(); - static int32 GetActualScriptThingIndex(int32 index, eScriptThingType type); + static int32 GetActualScriptThingIndex(int32 ref, eScriptThingType type); static int32 GetNewUniqueScriptThingIndex(int32 index, eScriptThingType type); static int32 GetScriptIndexFromPointer(CRunningScript* thread); static int32 GetUniqueScriptThingIndex(int32 playerGroup, eScriptThingType type); @@ -466,11 +526,11 @@ class CTheScripts { static bool IsEntityWithinSearchLight(uint32 index, CEntity* entity); static bool IsPedStopped(CPed* ped); static bool IsPlayerOnAMission(); - static bool IsPointWithinSearchLight(CVector* pointPosn, int32 index); - static bool IsVehicleStopped(CVehicle* vehicle); + static bool IsPointWithinSearchLight(const CVector& pointPosn, int32 index); + static bool IsVehicleStopped(CVehicle* veh); - static bool Load(); - static bool Save(); + static void Load(); + static void Save(); static void MoveSearchLightBetweenTwoPoints(int32 index, float x1, float y1, float z1, float x2, float y2, float z2, float pathSpeed); static void MoveSearchLightToEntity(int32 index, CEntity* entity, float pathSpeed); @@ -492,7 +552,7 @@ class CTheScripts { static void RemoveThisPed(CPed* ped); static void RenderAllSearchLights(); - static bool ScriptAttachAnimGroupToCharModel(int32 modelId, char* ifpName); // 0x474800 + static bool ScriptAttachAnimGroupToCharModel(int32 modelId, const char* ifpName); static void ScriptConnectLodsFunction(int32 objectHandle1, int32 objectHandle2); static void ScriptDebugCircle2D(float x, float y, float width, float height, CRGBA color); static CRunningScript* StartNewScript(uint8* startIP); @@ -500,7 +560,7 @@ class CTheScripts { static void StartTestScript(); static void UndoBuildingSwaps(); static void UndoEntityInvisibilitySettings(); - static void UseSwitchJumpTable(int32* pSwitchLabelAddress); + static void UseSwitchJumpTable(int32& switchLabelAddress); static void WipeLocalVariableMemoryForMissionScript(); static bool HasCarModelBeenSuppressed(eModelID carModelId); @@ -521,7 +581,34 @@ class CTheScripts { static void DrawDebugAngledSquare(const CVector2D& inf, const CVector2D& sup, const CVector2D& rotSup, const CVector2D& rotInf); static void DrawDebugCube(const CVector& inf, const CVector& sup); static void DrawDebugAngledCube(const CVector& inf, const CVector& sup, const CVector2D& rotSup, const CVector2D& rotInf); - static void DrawScriptSpritesAndRectangles(bool bDrawBeforeFade); + static void DrawScriptSpritesAndRectangles(bool drawBeforeFade); + + // NOTSA + template + requires std::is_base_of_v + static const ChunkT* GetSCMChunk() { + // A SCM file can have any amount of header chunks before the main script, + // under these conditions: + // + // 1. Vanilla EXE expects at least 2 chunks to be available. Having less than that + // will crash the game. + // + // 2. The last chunk must set the main script offset as next chunk offset. So virtual + // machine will just jump through all chunks to the main script. + constexpr auto NUM_VANILLA_MAIN_CHUNKS = 6u; + const auto* header = reinterpret_cast(&ScriptSpace[0]); + for (auto i = 0u; i < NUM_VANILLA_MAIN_CHUNKS; i++) { + static constexpr uint8 GoToInst[] = { 0x02, 0x00, 0x01 }; + + if (header->m_ChunkIndex == ChunkT::Index) + return header->As(); + + assert(!memcmp(header->m_InstrGoTo, GoToInst, sizeof(GoToInst))); + header = reinterpret_cast(&ScriptSpace[header->m_NextChunkOffset]); + } + + NOTSA_UNREACHABLE(); + } static int32* GetPointerToScriptVariable(uint32 offset) { // TODO: find out how this method changed between re3 and GTA:SA diff --git a/source/game_sa/ScriptsForBrains.cpp b/source/game_sa/ScriptsForBrains.cpp index fa08596461..b456dea2c3 100644 --- a/source/game_sa/ScriptsForBrains.cpp +++ b/source/game_sa/ScriptsForBrains.cpp @@ -14,8 +14,9 @@ void CScriptsForBrains::InjectHooks() { RH_ScopedInstall(HasAttractorScriptBrainWithThisNameLoaded, 0x46AB20); //RH_ScopedInstall(StartNewStreamedScriptBrain, 0x46B270, {.reversed = false}); RH_ScopedInstall(StartAttractorScriptBrainWithThisName, 0x46B390); - //RH_ScopedInstall(StartOrRequestNewStreamedScriptBrain, 0x46CD80, {.reversed = false}); + RH_ScopedInstall(StartOrRequestNewStreamedScriptBrain, 0x46CD80, {.reversed = false}); //RH_ScopedInstall(StartOrRequestNewStreamedScriptBrainWithThisName, 0x46CED0, {.reversed = false}); + RH_ScopedInstall(IsObjectWithinBrainActivationRange, 0x46B3D0, {.reversed=false}); } @@ -42,16 +43,24 @@ void CScriptsForBrains::StartNewStreamedScriptBrain(uint8 index, CEntity* entity plugin::CallMethodAndReturn(this, index, entity, bHasAScriptBrain); } +void CScriptsForBrains::StartOrRequestNewStreamedScriptBrain(uint8 index, CEntity* entity, int8 attachType, bool bAddToWaitingArray) { + NOTSA_UNREACHABLE(); +} + bool CScriptsForBrains::HasAttractorScriptBrainWithThisNameLoaded(const char* name) { if (const auto idx = GetIndexOfScriptBrainWithThisName(name, 5); idx >= 0) { - return CStreaming::IsModelLoaded(SCMToModelId(m_aScriptForBrains[idx].m_nIMGindex)); + return CStreaming::IsModelLoaded(SCMToModelId(m_aScriptForBrains[idx].m_StreamedScriptIndex)); } return false; } -int16 CScriptsForBrains::GetIndexOfScriptBrainWithThisName(const char* name, int8 attachType) { +bool CScriptsForBrains::IsObjectWithinBrainActivationRange(CObject* entity, const CVector& point) { + NOTSA_UNREACHABLE(); +} + +int16 CScriptsForBrains::GetIndexOfScriptBrainWithThisName(const char* name, int8 type) { const auto it = rng::find_if(m_aScriptForBrains, [=](tScriptForBrains& script) { - return script.m_nAttachType == attachType && !_stricmp(script.m_scriptName, name); + return script.m_TypeOfBrain == type && !_stricmp(script.m_ScriptName, name); }); return it != m_aScriptForBrains.end() ? rng::distance(m_aScriptForBrains.begin(), it) diff --git a/source/game_sa/ScriptsForBrains.h b/source/game_sa/ScriptsForBrains.h index dc00aecc9b..1af513bc51 100644 --- a/source/game_sa/ScriptsForBrains.h +++ b/source/game_sa/ScriptsForBrains.h @@ -12,18 +12,18 @@ class CEntity; class CObject; struct tScriptForBrains { - int16 m_nIMGindex{-1}; /// SCM ID for `CStreaming` (Translated using SCMToModelId) - int8 m_nAttachType{-1}; - int8 m_nType{-1}; - int8 m_ucStatus{1}; - float m_fRadius{5.f}; + int16 m_StreamedScriptIndex{-1}; /// SCM ID for `CStreaming` (Translated using SCMToModelId) + int8 m_TypeOfBrain{ -1 }; + int8 m_ObjectGroupingId{ -1 }; + bool m_bBrainActive{ true }; + float m_ObjectBrainActivationRadius{ 5.f }; union { struct { - int16 m_pedModelOrPedGeneratorIndex; - uint16 m_percentageChance; - uint32 m_pad; + int16 m_PedModelOrPedGeneratorIndex; + uint16 m_PercentageChance; + uint32 m_Pad; }; - char m_scriptName[8]{}; + char m_ScriptName[8]{}; }; }; @@ -41,10 +41,10 @@ class CScriptsForBrains { void CheckIfNewEntityNeedsScript(CEntity* entity, int8 attachType, void* unused); - int16 GetIndexOfScriptBrainWithThisName(const char* name, int8 attachType); + int16 GetIndexOfScriptBrainWithThisName(const char* name, int8 type); bool HasAttractorScriptBrainWithThisNameLoaded(const char* name); - bool IsObjectWithinBrainActivationRange(CObject* entity, CVector const* point); + bool IsObjectWithinBrainActivationRange(CObject* entity, const CVector& point); void MarkAttractorScriptBrainWithThisNameAsNoLongerNeeded(const char* name); void RequestAttractorScriptBrainWithThisName(const char* name); diff --git a/source/toolsmenu/DebugModules/CheckpointsDebugModule.cpp b/source/toolsmenu/DebugModules/CheckpointsDebugModule.cpp index aec9540663..2db90f0958 100644 --- a/source/toolsmenu/DebugModules/CheckpointsDebugModule.cpp +++ b/source/toolsmenu/DebugModules/CheckpointsDebugModule.cpp @@ -42,7 +42,7 @@ void CheckpointsDebugModule::RenderWindow() { pos, m_Dir, m_Size, - color.r, color.g, color.b, color.a, + color, (uint16)m_PulsePeriod, m_PulseFraction, (int16)m_RotateRate