diff --git a/CREDITS.md b/CREDITS.md index a764868957..eaf927a89b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -162,6 +162,7 @@ This page lists all the individual contributions to the project by their author. - Exploding unit passenger killing customization - Railgun particle target coordinate fix - Building target coordinate offset fix + - Additional sync logging - **Morton (MortonPL)**: - `XDrawOffset` - Shield passthrough & absorption diff --git a/Phobos.vcxproj b/Phobos.vcxproj index 0a4fbd6a8b..5a82a3e2b1 100644 --- a/Phobos.vcxproj +++ b/Phobos.vcxproj @@ -123,7 +123,8 @@ - + + @@ -149,6 +150,7 @@ + diff --git a/YRpp b/YRpp index 2218016f71..3324354710 160000 --- a/YRpp +++ b/YRpp @@ -1 +1 @@ -Subproject commit 2218016f7182b0f18e5724f024286b1e46214ae1 +Subproject commit 3324354710ad2c11a68b70b8c6903ad2e5e5b2cc diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index bc567048b8..683123e619 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -75,7 +75,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho ![Waving trees](_static/images/tree-shake.gif) *Animated trees used in [Ion Shock](https://www.moddb.com/mods/tiberian-war-ionshock)* -- `IsAnimated`, `AnimationRate` and `AnimationProbability` now work on TerrainTypes without `SpawnsTiberium` set to true. +- `IsAnimated`, `AnimationRate` and `AnimationProbability` now work on TerrainTypes without `SpawnsTiberium` set to true. Note that this might impact performance. - Fixed transports recursively put into each other not having a correct killer set after second transport when being killed by something. ![image](_static/images/translucency-fix.png) @@ -94,7 +94,7 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - `Weapon` can be set to a WeaponType, to create a projectile and immediately detonate it instead of simply dealing `Damage` by `Warhead`. This allows weapon effects to be applied. - `Damage.Delay` determines delay between two applications of `Damage`. Requires `Damage` to be set to 1.0 or above. Value of 0 disables the delay. Keep in mind that this is measured in animation frames, not game frames. Depending on `Rate`, animation may or may not advance animation frames on every game frame. -- `Damage.DealtByInvoker`, if set to true, makes any `Damage` dealt to be considered as coming from the animation's invoker (f.ex, firer of the weapon if it is Warhead `AnimList/SplashList` animation, the destroyed vehicle if it is `DestroyAnim` animation or the object the animation is attached to). Does not affect which house the `Damage` dealt by `Warhead` is dealt by. +- `Damage.DealtByInvoker`, if set to true, makes any `Damage` dealt to be considered as coming from the animation's invoker (f.ex, firer of the weapon if it is Warhead `AnimList/SplashList` animation, the destroyed vehicle if it is `DestroyAnim` animation or the object the animation is attached to). If invoker has died or does not exist, the house the invoker belonged to is still used to deal damage and apply Phobos-introduced Warhead effects. Does not affect which house the `Damage` dealt by `Warhead` is dealt by. - `Damage.ApplyOncePerLoop`, if set to true, makes `Damage` be dealt only once per animation loop (on single loop animations, only once, period) instead of on every frame or intervals defined by `Damage.Delay`. The frame on which it is dealt is determined by `Damage.Delay`, defaulting to after the first animation frame. In `artmd.ini`: diff --git a/docs/Miscellanous.md b/docs/Miscellanous.md index d0afbb1349..fb8ece6887 100644 --- a/docs/Miscellanous.md +++ b/docs/Miscellanous.md @@ -2,6 +2,8 @@ This page describes every change in Phobos that wasn't categorized into a proper category yet. +- Phobos writes additional information to the `SYNC#.txt` log files when a desynchronization occurs such as calls to random number generator functions, facing / target / destination changes etc. + ## Developer tools ### Dump Object Info diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index b68c05c2e6..3d1eb0abf3 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -227,9 +227,9 @@ Shield.InheritStateOnReplace=false ; boolean - `CreateUnit.RemapAnim`, if set to true, will cause the animation to be drawn in unit palette and remappable to owner's team color. - `CreateUnit.Mission` determines the initial mission of the created VehicleType. - `CreateUnit.Facing` determines the initial facing of created VehicleType. - - `CreateUnit.RandomFacing`, if set to true makes it so that a random facing is picked instead. - - `CreateUnit.InheritFacings` and `CreateUnit.InheritTurretFacings` inherit facings for vehicle body and turret respectively from the destroyed vehicle if the animation is a vehicle destroy animation. - - `CreateUnit.ConsiderPathfinding`, if set to true, will consider whether or not the cell where the animation is located is occupied by other objects or impassable to the vehicle being created and will attempt to find a nearby cell that is not. Otherwise the vehicle will be created at the animation's location despite these obstacles. + - `CreateUnit.RandomFacing`, if set to true, makes it so that a random facing is picked instead. + - `CreateUnit.InheritFacings` and `CreateUnit.InheritTurretFacings` inherit facings for vehicle body and turret respectively from the destroyed vehicle if the animation is a vehicle destroy animation. `InheritTurretFacings` does not work with jumpjet vehicles due to technical constraints. + - `CreateUnit.ConsiderPathfinding`, if set to true, will consider whether or not the cell where the animation is located is occupied by other objects or impassable to the vehicle being created and will attempt to find a nearby cell that is not. Otherwise the vehicle will be created at the animation's location despite these obstacles if possible. In `artmd.ini`: ```ini @@ -618,10 +618,9 @@ Both `InitialStrength` and `InitialStrength.Cloning` never surpass the type's `S If this option is not set, the self-destruction logic will not be enabled. ```{note} Please notice that if the object is a unit which carries passengers, they will not be released even with the `kill` option. This might change in the future if necessary. - -If the object enters transport, the countdown will continue, but it will not self-destruct inside the transport. ``` +This logic also supports buildings delivered by [LimboDelivery](#LimboDelivery) In `rulesmd.ini`: ```ini diff --git a/docs/Whats-New.md b/docs/Whats-New.md index f2ed78e194..bcb49e08c8 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -237,6 +237,33 @@ You can use the migration utility (can be found on [Phobos supplementaries repo] ## Changelog +### 0.3.0.1 + +New: +- Additional sync logging in case of desync errors occuring (by Starkku) + +Phobos fixes: +- `AutoDeath` support for objects in limbo (by Trsdy) +- Buildings sold by `AutoDeath` no longer play a click sound effect (by Trsdy) +- Fixed shield animation being hidden while underground or in tunnels fix not working correctly (by Starkku) +- Restore the `MindClearedSound` when deploying a mind-controlled unit into a building loses the mind-control (by Trsdy) +- Fixed `RadSiteWarhead.Detonate` not detonating precisely on the affected object (thus requiring `CellSpread`) (by Starkku) +- Fixed script action 10103 'Load Into Transports' unintentionally skipping next action (by FS-21) +- Changed mission retry dialog button order to better match old order people are used to (by Trsdy) +- Allow PowerPlant Enhancer to be affected by EMP (by Trsdy) +- Animation `Weapon` with `Damage.DealtByInvoker=true` now uses the invoker's house to deal damage and apply Phobos warhead effects even if invoker is dead when weapon is fired (by Starkku) +- Fixed a crash when trying to create radiation outside map bounds (by Otamaa) +- Fixed new AI attack scripts not allowing zero damage weapons to pick targets (by Starkku) +- Fixed floating point value parsing precision to match the game (by Starkku) +- Power output / drain should now correctly be applied for buildings created via `LimboDelivery` in campaigns (by Starkku) +- Fixed shield health bar showing empty bar when shield is still on very low health instead of depleted (by Starkku) +- Fixed `CanTarget` not considering objects on bridges when checking if cell is empty (by Starkku) +- Fixed vehicle deploy weapons not working if the unit is cloaked and weapon has `DecloakToFire=true` (by NetsuNegi & Starkku) +- Fixed `IsAnimated` terrain not updating correctly in all circumstances (by Starkku) +- Fixed `CreateUnit` interaction with bridges (spawning under when shouldn't etc) (by Starkku) +- `CanTarget` now considers bridges as land like game's normal weapon selection does (by Starkku) +- `AreaFire.Target` now takes cells with bridges into consideration depending on firer's elevation (by Starkku) + ### 0.3
diff --git a/src/Commands/ObjectInfo.cpp b/src/Commands/ObjectInfo.cpp index 4a966b77e1..79ef8e8c36 100644 --- a/src/Commands/ObjectInfo.cpp +++ b/src/Commands/ObjectInfo.cpp @@ -50,81 +50,6 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const strcat_s(buffer, Phobos::readBuffer); }; - auto getMissionName = [](int mID) - { - switch (mID) - { - case -1: - return "None"; - case 0: - return "Sleep"; - case 1: - return "Attack"; - case 2: - return "Move"; - case 3: - return "QMove"; - case 4: - return "Retreat"; - case 5: - return "Guard"; - case 6: - return "Sticky"; - case 7: - return "Enter"; - case 8: - return "Capture"; - case 9: - return "Eaten"; - case 10: - return "Harvest"; - case 11: - return "Area_Guard"; - case 12: - return "Return"; - case 13: - return "Stop"; - case 14: - return "Ambush"; - case 15: - return "Hunt"; - case 16: - return "Unload"; - case 17: - return "Sabotage"; - case 18: - return "Construction"; - case 19: - return "Selling"; - case 20: - return "Repair"; - case 21: - return "Rescue"; - case 22: - return "Missile"; - case 23: - return "Harmless"; - case 24: - return "Open"; - case 25: - return "Patrol"; - case 26: - return "ParadropApproach"; - case 27: - return "ParadropOverfly"; - case 28: - return "Wait"; - case 29: - return "AttackMove"; - case 30: - return "SpyplaneApproach"; - case 31: - return "SpyplaneOverfly"; - default: - return "INVALID_MISSION"; - } - }; - auto display = [&buffer]() { memset(Phobos::wideBuffer, 0, sizeof Phobos::wideBuffer); @@ -134,14 +59,14 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const buffer[0] = 0; }; - auto printFoots = [&append, &display, &getMissionName](FootClass* pFoot) + auto printFoots = [&append, &display](FootClass* pFoot) { append("[Phobos] Dump ObjectInfo runs.\n"); auto pType = pFoot->GetTechnoType(); append("ID = %s, ", pType->ID); append("Owner = %s (%s), ", pFoot->Owner->get_ID(), pFoot->Owner->PlainName); append("Location = (%d, %d), ", pFoot->GetMapCoords().X, pFoot->GetMapCoords().Y); - append("Current Mission = %d (%s)\n", pFoot->CurrentMission, getMissionName((int)pFoot->CurrentMission)); + append("Current Mission = %d (%s)\n", pFoot->CurrentMission, MissionControlClass::FindName(pFoot->CurrentMission)); if (pFoot->BelongsToATeam()) { @@ -177,12 +102,10 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const if (pFoot->Passengers.NumPassengers > 0) { - append("Passengers: %s", pFoot->Passengers.FirstPassenger->GetTechnoType()->ID); - for (NextObject j(pFoot->Passengers.FirstPassenger->NextObject); j && abstract_cast(*j); ++j) - { - auto passenger = static_cast(*j); - append(", %s", passenger->GetTechnoType()->ID); - } + FootClass* pCurrent = pFoot->Passengers.FirstPassenger; + append("%d Passengers: %s", pFoot->Passengers.NumPassengers, pCurrent->GetTechnoType()->ID); + for (pCurrent = abstract_cast(pCurrent->NextObject); pCurrent; pCurrent = abstract_cast(pCurrent->NextObject)) + append(", %s", pCurrent->GetTechnoType()->ID); append("\n"); } @@ -210,7 +133,7 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const auto printBuilding = [&append, &display](BuildingClass* pBuilding) { append("[Phobos] Dump ObjectInfo runs.\n"); - auto pType = pBuilding->GetTechnoType(); + auto pType = pBuilding->Type; append("ID = %s, ", pType->ID); append("Owner = %s (%s), ", pBuilding->Owner->get_ID(), pBuilding->Owner->PlainName); append("Location = (%d, %d)\n", pBuilding->GetMapCoords().X, pBuilding->GetMapCoords().Y); @@ -263,10 +186,10 @@ void ObjectInfoCommandClass::Execute(WWKey eInput) const case AbstractType::Infantry: case AbstractType::Unit: case AbstractType::Aircraft: - printFoots(abstract_cast(pObject)); + printFoots(static_cast(pObject)); break; case AbstractType::Building: - printBuilding(abstract_cast(pObject)); + printBuilding(static_cast(pObject)); break; default: append("INVALID ITEM!"); diff --git a/src/Commands/QuickSave.cpp b/src/Commands/QuickSave.cpp index 8919b79fa6..e62ca04072 100644 --- a/src/Commands/QuickSave.cpp +++ b/src/Commands/QuickSave.cpp @@ -1,5 +1,5 @@ #include "QuickSave.h" - +#include #include #include #include @@ -47,7 +47,7 @@ void QuickSaveCommandClass::Execute(WWKey eInput) const _snprintf_s(fName, 0x7F, "Map.%04u%02u%02u-%02u%02u%02u-%05u.sav", time.wYear, time.wMonth, time.wDay, time.wHour, time.wMinute, time.wSecond, time.wMilliseconds); - PrintMessage(StringTable::LoadString("TXT_SAVING_GAME")); + PrintMessage(StringTable::LoadString(GameStrings::TXT_SAVING_GAME)); wchar_t fDescription[0x80] = { 0 }; if (SessionClass::Instance->GameMode == GameMode::Campaign) @@ -58,9 +58,9 @@ void QuickSaveCommandClass::Execute(WWKey eInput) const wcscat_s(fDescription, GeneralUtils::LoadStringUnlessMissing("TXT_QUICKSAVE_SUFFIX", L"Quicksaved")); if (ScenarioClass::SaveGame(fName, fDescription)) - PrintMessage(StringTable::LoadString("TXT_GAME_WAS_SAVED")); + PrintMessage(StringTable::LoadString(GameStrings::TXT_GAME_WAS_SAVED)); else - PrintMessage(StringTable::LoadString("TXT_ERROR_SAVING_GAME")); + PrintMessage(StringTable::LoadString(GameStrings::TXT_ERROR_SAVING_GAME)); } else { diff --git a/src/Ext/Aircraft/Hooks.cpp b/src/Ext/Aircraft/Hooks.cpp index c95d738c6b..2e4db1a81b 100644 --- a/src/Ext/Aircraft/Hooks.cpp +++ b/src/Ext/Aircraft/Hooks.cpp @@ -95,7 +95,7 @@ DEFINE_HOOK(0x414F47, AircraftClass_AI_TrailerInheritOwner, 0x6) if (auto const pAnimExt = AnimExt::ExtMap.Find(pAnim)) { pAnim->Owner = pThis->Owner; - pAnimExt->Invoker = pThis; + pAnimExt->SetInvoker(pThis); } } diff --git a/src/Ext/Anim/Body.cpp b/src/Ext/Anim/Body.cpp index 2074e7a86e..02cb0f223a 100644 --- a/src/Ext/Anim/Body.cpp +++ b/src/Ext/Anim/Body.cpp @@ -2,10 +2,17 @@ #include #include +#include template<> const DWORD Extension::Canary = 0xAAAAAAAA; AnimExt::ExtContainer AnimExt::ExtMap; +void AnimExt::ExtData::SetInvoker(TechnoClass* pInvoker) +{ + this->Invoker = pInvoker; + this->InvokerHouse = pInvoker ? pInvoker->Owner : nullptr; +} + //Modified from Ares const bool AnimExt::SetAnimOwnerHouseKind(AnimClass* pAnim, HouseClass* pInvoker, HouseClass* pVictim, bool defaultToVictimOwner) { @@ -35,6 +42,7 @@ void AnimExt::ExtData::Serialize(T& Stm) .Process(this->DeathUnitTurretFacing) .Process(this->DeathUnitHasTurret) .Process(this->Invoker) + .Process(this->InvokerHouse) ; } @@ -59,12 +67,39 @@ AnimExt::ExtContainer::~ExtContainer() = default; // ============================= // container hooks +namespace CTORTemp +{ + CoordStruct coords; + unsigned int callerAddress; +} + +DEFINE_HOOK(0x421EA0, AnimClass_CTOR_SetContext, 0x6) +{ + GET_STACK(CoordStruct*, coords, 0x8); + GET_STACK(unsigned int, callerAddress, 0x0); + + CTORTemp::coords = *coords; + CTORTemp::callerAddress = callerAddress; + + return 0; +} + DEFINE_HOOK_AGAIN(0x422126, AnimClass_CTOR, 0x5) DEFINE_HOOK_AGAIN(0x422707, AnimClass_CTOR, 0x5) DEFINE_HOOK(0x4228D2, AnimClass_CTOR, 0x5) { GET(AnimClass*, pItem, ESI); + // Do this here instead of using a duplicate hook in SyncLogger.cpp + if (!SyncLogger::HooksDisabled && pItem->UniqueID != -2) + SyncLogger::AddAnimCreationSyncLogEvent(CTORTemp::coords, CTORTemp::callerAddress); + + if (pItem && !pItem->Type) + { + Debug::Log("Attempting to create animation with null Type (Caller: %08x)!", CTORTemp::callerAddress); + return 0; + } + AnimExt::ExtMap.FindOrAllocate(pItem); return 0; diff --git a/src/Ext/Anim/Body.h b/src/Ext/Anim/Body.h index e21c73f6fc..fe319ca7b4 100644 --- a/src/Ext/Anim/Body.h +++ b/src/Ext/Anim/Body.h @@ -19,6 +19,7 @@ class AnimExt bool FromDeathUnit; bool DeathUnitHasTurret; TechnoClass* Invoker; + HouseClass* InvokerHouse; ExtData(AnimClass* OwnerObject) : Extension(OwnerObject) , DeathUnitFacing { 0 } @@ -26,13 +27,17 @@ class AnimExt , FromDeathUnit { false } , DeathUnitHasTurret { false } , Invoker {} + , InvokerHouse {} { } + void SetInvoker(TechnoClass* pInvoker); + virtual ~ExtData() = default; virtual void InvalidatePointer(void* ptr, bool bRemoved) override { AnnounceInvalidPointer(Invoker, ptr); + AnnounceInvalidPointer(InvokerHouse, ptr); } virtual void LoadFromStream(PhobosStreamReader& Stm) override; @@ -58,6 +63,7 @@ class AnimExt case AbstractType::Building: case AbstractType::Infantry: case AbstractType::Unit: + case AbstractType::House: return false; default: return true; diff --git a/src/Ext/Anim/Hooks.AnimCreateUnit.cpp b/src/Ext/Anim/Hooks.AnimCreateUnit.cpp index deaebe3aac..c412c7fcf7 100644 --- a/src/Ext/Anim/Hooks.AnimCreateUnit.cpp +++ b/src/Ext/Anim/Hooks.AnimCreateUnit.cpp @@ -1,10 +1,11 @@ // Anim-to--Unit -// Author: Otamaa +// Author: Otamaa, revisions by Starkku #include "Body.h" #include #include +#include #include #include @@ -74,74 +75,69 @@ DEFINE_HOOK(0x424932, AnimClass_AI_CreateUnit_ActualAffects, 0x6) auto pCell = pThis->GetCell(); CoordStruct location = pThis->GetCoords(); - if (pCell) - location = pCell->GetCoordsWithBridge(); - else - location.Z = MapClass::Instance->GetCellFloorHeight(location); - pThis->UnmarkAllOccupationBits(location); - if (pTypeExt->CreateUnit_ConsiderPathfinding) - { - bool allowBridges = unit->SpeedType != SpeedType::Float; + bool allowBridges = GroundType::Array[static_cast(LandType::Clear)].Cost[static_cast(unit->SpeedType)] > 0.0; + bool isBridge = allowBridges && pCell->ContainsBridge(); + if (pTypeExt->CreateUnit_ConsiderPathfinding && (!pCell || !pCell->IsClearToMove(unit->SpeedType, false, false, -1, unit->MovementZone, -1, isBridge))) + { auto nCell = MapClass::Instance->NearByLocation(CellClass::Coord2Cell(location), - unit->SpeedType, -1, unit->MovementZone, false, 1, 1, true, - false, false, allowBridges, CellStruct::Empty, false, false); + unit->SpeedType, -1, unit->MovementZone, isBridge, 1, 1, true, + false, false, isBridge, CellStruct::Empty, false, false); pCell = MapClass::Instance->TryGetCellAt(nCell); - location = pThis->GetCoords(); - - if (pCell) - location = pCell->GetCoordsWithBridge(); - else - location.Z = MapClass::Instance->GetCellFloorHeight(location); + location = pCell->GetCoords(); } - if (auto pTechno = static_cast(unit->CreateObject(decidedOwner))) + if (pCell) { - bool success = false; - auto const pExt = AnimExt::ExtMap.Find(pThis); - - auto aFacing = pTypeExt->CreateUnit_RandomFacing.Get() - ? static_cast(ScenarioClass::Instance->Random.RandomRanged(0, 255)) : pTypeExt->CreateUnit_Facing.Get(); - - short resultingFacing = (pTypeExt->CreateUnit_InheritDeathFacings.Get() && pExt->FromDeathUnit) - ? pExt->DeathUnitFacing : aFacing; - - if (pCell) - pTechno->OnBridge = pCell->ContainsBridge(); + isBridge = allowBridges && pCell->ContainsBridge(); + location.Z = MapClass::Instance->GetCellFloorHeight(location) + isBridge ? CellClass::BridgeHeight : 0; - BuildingClass* pBuilding = pCell ? pCell->GetBuilding() : MapClass::Instance->TryGetCellAt(location)->GetBuilding(); - - if (!pBuilding) - { - ++Unsorted::IKnowWhatImDoing; - success = pTechno->Unlimbo(location, static_cast(resultingFacing)); - --Unsorted::IKnowWhatImDoing; - } - else - { - success = pTechno->Unlimbo(location, static_cast(resultingFacing)); - } - - if (success) - { - if (pTechno->HasTurret() && pExt->FromDeathUnit && pExt->DeathUnitHasTurret && pTypeExt->CreateUnit_InheritTurretFacings.Get()) - pTechno->SecondaryFacing.SetCurrent(pExt->DeathUnitTurretFacing); - - Debug::Log("[" __FUNCTION__ "] Stored Turret Facing %d \n", pExt->DeathUnitTurretFacing.GetFacing<256>()); - - if (!pTechno->InLimbo) - pTechno->QueueMission(pTypeExt->CreateUnit_Mission.Get(), false); - - if (!decidedOwner->Type->MultiplayPassive) - decidedOwner->RecheckTechTree = true; - } - else + if (auto pTechno = static_cast(unit->CreateObject(decidedOwner))) { - if (pTechno) - pTechno->UnInit(); + bool success = false; + auto const pExt = AnimExt::ExtMap.Find(pThis); + + auto aFacing = pTypeExt->CreateUnit_RandomFacing.Get() + ? static_cast(ScenarioClass::Instance->Random.RandomRanged(0, 255)) : pTypeExt->CreateUnit_Facing.Get(); + + short resultingFacing = (pTypeExt->CreateUnit_InheritDeathFacings.Get() && pExt->FromDeathUnit) + ? pExt->DeathUnitFacing : aFacing; + + pTechno->OnBridge = isBridge; + + if (!pCell->GetBuilding()) + { + ++Unsorted::IKnowWhatImDoing; + success = pTechno->Unlimbo(location, static_cast(resultingFacing)); + --Unsorted::IKnowWhatImDoing; + } + else + { + success = pTechno->Unlimbo(location, static_cast(resultingFacing)); + } + + if (success) + { + if (pTechno->HasTurret() && pExt->FromDeathUnit && pExt->DeathUnitHasTurret && pTypeExt->CreateUnit_InheritTurretFacings.Get()) + { + pTechno->SecondaryFacing.SetCurrent(pExt->DeathUnitTurretFacing); + Debug::Log("CreateUnit: Stored Turret Facing %d \n", pExt->DeathUnitTurretFacing.GetFacing<256>()); + } + + if (!pTechno->InLimbo) + pTechno->QueueMission(pTypeExt->CreateUnit_Mission.Get(), false); + + if (!decidedOwner->Type->MultiplayPassive) + decidedOwner->RecheckTechTree = true; + } + else + { + if (pTechno) + pTechno->UnInit(); + } } } } @@ -179,7 +175,7 @@ DEFINE_HOOK(0x469C98, BulletClass_DetonateAt_DamageAnimSelected, 0x0) if (pThis->Owner) { auto pExt = AnimExt::ExtMap.Find(pAnim); - pExt->Invoker = pThis->Owner; + pExt->SetInvoker(pThis->Owner); } } else if (pThis->WH == RulesClass::Instance->NukeWarhead) diff --git a/src/Ext/Anim/Hooks.cpp b/src/Ext/Anim/Hooks.cpp index 426d5ed014..e36669188d 100644 --- a/src/Ext/Anim/Hooks.cpp +++ b/src/Ext/Anim/Hooks.cpp @@ -81,6 +81,7 @@ DEFINE_HOOK(0x424513, AnimClass_AI_Damage, 0x6) pThis->Accum = 0.0; TechnoClass* pInvoker = nullptr; + HouseClass* pInvokerHouse = nullptr; if (pTypeExt->Damage_DealtByInvoker) { @@ -88,12 +89,15 @@ DEFINE_HOOK(0x424513, AnimClass_AI_Damage, 0x6) pInvoker = pExt->Invoker; if (!pInvoker) + { pInvoker = pThis->OwnerObject ? abstract_cast(pThis->OwnerObject) : nullptr; + pInvokerHouse = !pInvoker ? pExt->InvokerHouse : nullptr; + } } if (pTypeExt->Weapon.isset()) { - WeaponTypeExt::DetonateAt(pTypeExt->Weapon.Get(), pThis->GetCoords(), pInvoker, appliedDamage); + WeaponTypeExt::DetonateAt(pTypeExt->Weapon.Get(), pThis->GetCoords(), pInvoker, appliedDamage, pInvokerHouse); } else { @@ -105,7 +109,12 @@ DEFINE_HOOK(0x424513, AnimClass_AI_Damage, 0x6) auto pOwner = pInvoker ? pInvoker->Owner : nullptr; if (!pOwner) - pOwner = pThis->OwnerObject ? pThis->OwnerObject->GetOwningHouse() : nullptr; + { + if (pThis->Owner) + pOwner = pThis->Owner; + else if (pThis->OwnerObject) + pOwner = pThis->OwnerObject->GetOwningHouse(); + } MapClass::DamageArea(pThis->GetCoords(), appliedDamage, pInvoker, pWarhead, true, pOwner); } @@ -126,6 +135,7 @@ DEFINE_HOOK(0x424322, AnimClass_AI_TrailerInheritOwner, 0x6) auto pExt = AnimExt::ExtMap.Find(pThis); pTrailerAnim->Owner = pThis->Owner; pTrailerAnimExt->Invoker = pExt->Invoker; + pTrailerAnimExt->InvokerHouse = pExt->InvokerHouse; } } diff --git a/src/Ext/AnimType/Body.cpp b/src/Ext/AnimType/Body.cpp index 55773092d8..d7452b5af0 100644 --- a/src/Ext/AnimType/Body.cpp +++ b/src/Ext/AnimType/Body.cpp @@ -85,7 +85,7 @@ const void AnimTypeExt::ProcessDestroyAnims(UnitClass* pThis, TechnoClass* pKill AnimExt::SetAnimOwnerHouseKind(pAnim, pInvoker, pThis->Owner); - pAnimExt->Invoker = pThis; + pAnimExt->SetInvoker(pThis); pAnimExt->FromDeathUnit = true; if (pAnimTypeExt->CreateUnit_InheritDeathFacings.Get()) diff --git a/src/Ext/BuildingType/Body.cpp b/src/Ext/BuildingType/Body.cpp index fed57b1f87..2c6a615063 100644 --- a/src/Ext/BuildingType/Body.cpp +++ b/src/Ext/BuildingType/Body.cpp @@ -134,8 +134,8 @@ void BuildingTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) if (pThis->MaxNumberOccupants > 10) { char tempBuffer[32]; - this->OccupierMuzzleFlashes.Clear(); - this->OccupierMuzzleFlashes.Reserve(pThis->MaxNumberOccupants); + this->OccupierMuzzleFlashes.clear(); + this->OccupierMuzzleFlashes.resize(pThis->MaxNumberOccupants); for (int i = 0; i < pThis->MaxNumberOccupants; ++i) { diff --git a/src/Ext/BuildingType/Body.h b/src/Ext/BuildingType/Body.h index b650cc1e91..d3818ab004 100644 --- a/src/Ext/BuildingType/Body.h +++ b/src/Ext/BuildingType/Body.h @@ -22,7 +22,7 @@ class BuildingTypeExt Nullable PowerPlantEnhancer_Amount; Nullable PowerPlantEnhancer_Factor; - DynamicVectorClass OccupierMuzzleFlashes; + std::vector OccupierMuzzleFlashes; Valueable Powered_KillSpawns; Valueable Refinery_UseStorage; Valueable> InitialStrength_Cloning; diff --git a/src/Ext/Bullet/Hooks.cpp b/src/Ext/Bullet/Hooks.cpp index dde822229f..d73065b9b9 100644 --- a/src/Ext/Bullet/Hooks.cpp +++ b/src/Ext/Bullet/Hooks.cpp @@ -110,7 +110,7 @@ DEFINE_HOOK(0x4668BD, BulletClass_AI_TrailerInheritOwner, 0x6) if (auto const pAnimExt = AnimExt::ExtMap.Find(pAnim)) { pAnim->Owner = pThis->Owner ? pThis->Owner->Owner : pExt->FirerHouse; - pAnimExt->Invoker = pThis->Owner; + pAnimExt->SetInvoker(pThis->Owner); } } diff --git a/src/Ext/BulletType/Body.cpp b/src/Ext/BulletType/Body.cpp index 888495b69f..b2850bdfa5 100644 --- a/src/Ext/BulletType/Body.cpp +++ b/src/Ext/BulletType/Body.cpp @@ -33,7 +33,6 @@ void BulletTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) INI_EX exINI(pINI); - this->Strength.Read(exINI, pSection, "Strength"); this->Armor.Read(exINI, pSection, "Armor"); this->Interceptable.Read(exINI, pSection, "Interceptable"); this->Interceptable_DeleteOnIntercept.Read(exINI, pSection, "Interceptable.DeleteOnIntercept"); @@ -58,7 +57,6 @@ template void BulletTypeExt::ExtData::Serialize(T& Stm) { Stm - .Process(this->Strength) .Process(this->Armor) .Process(this->Interceptable) .Process(this->Interceptable_DeleteOnIntercept) diff --git a/src/Ext/BulletType/Body.h b/src/Ext/BulletType/Body.h index 7621314dc3..04dc55c332 100644 --- a/src/Ext/BulletType/Body.h +++ b/src/Ext/BulletType/Body.h @@ -17,7 +17,7 @@ class BulletTypeExt class ExtData final : public Extension { public: - Valueable Strength; + // Valueable Strength; //Use OwnerObject()->ObjectTypeClass::Strength Nullable Armor; Valueable Interceptable; Valueable Interceptable_DeleteOnIntercept; @@ -32,7 +32,6 @@ class BulletTypeExt Valueable Shrapnel_AffectsBuildings; ExtData(BulletTypeClass* OwnerObject) : Extension(OwnerObject) - , Strength { 0 } , Armor {} , Interceptable { false } , Interceptable_DeleteOnIntercept { false } diff --git a/src/Ext/House/Body.cpp b/src/Ext/House/Body.cpp index cb3d91af14..b0dbd2eecc 100644 --- a/src/Ext/House/Body.cpp +++ b/src/Ext/House/Body.cpp @@ -10,12 +10,12 @@ template<> const DWORD Extension::Canary = 0x11111111; HouseExt::ExtContainer HouseExt::ExtMap; -bool HouseExt::ExtData::OwnsLimboDeliveredBuilding(BuildingClass const* pBuilding) +bool HouseExt::ExtData::OwnsLimboDeliveredBuilding(BuildingClass* pBuilding) { if (!pBuilding) return false; - return this->OwnedLimboDeliveredBuildings.count(pBuilding->UniqueID); + return this->OwnedLimboDeliveredBuildings.count(pBuilding); } int HouseExt::ActiveHarvesterCount(HouseClass* pThis) @@ -73,6 +73,26 @@ HouseClass* HouseExt::GetHouseKind(OwnerHouseKind const kind, bool const allowRa return pDefault; } } +void HouseExt::ExtData::UpdateAutoDeathObjectsInLimbo() +{ + for (auto pExt : this->OwnedTimedAutoDeathObjects) + { + auto pItem = pExt->OwnerObject(); + + if (!pItem->IsInLogic && pItem->IsAlive && pExt->TypeExtData->AutoDeath_Behavior.isset() && pExt->AutoDeathTimer.Completed()) + { + auto const pBuilding = abstract_cast(pItem); + + if (OwnsLimboDeliveredBuilding(pBuilding)) + this->OwnedLimboDeliveredBuildings.erase(pBuilding); + + pItem->RegisterDestruction(nullptr); + // I doubt those in LimboDelete being really necessary, they're gonna be updated either next frame or after uninit anyway + pItem->UnInit(); + } + } +} + // ============================= // load / save @@ -82,6 +102,7 @@ void HouseExt::ExtData::Serialize(T& Stm) Stm .Process(this->BuildingCounter) .Process(this->OwnedLimboDeliveredBuildings) + .Process(this->OwnedTimedAutoDeathObjects) .Process(this->Factory_BuildingType) .Process(this->Factory_InfantryType) .Process(this->Factory_VehicleType) diff --git a/src/Ext/House/Body.h b/src/Ext/House/Body.h index c4bee11395..914c7b2a36 100644 --- a/src/Ext/House/Body.h +++ b/src/Ext/House/Body.h @@ -17,7 +17,8 @@ class HouseExt { public: std::map BuildingCounter; - std::map OwnedLimboDeliveredBuildings; + std::map OwnedLimboDeliveredBuildings; + std::vector OwnedTimedAutoDeathObjects; BuildingClass* Factory_BuildingType; BuildingClass* Factory_InfantryType; @@ -28,6 +29,7 @@ class HouseExt ExtData(HouseClass* OwnerObject) : Extension(OwnerObject) , BuildingCounter {} , OwnedLimboDeliveredBuildings {} + , OwnedTimedAutoDeathObjects {} , Factory_BuildingType { nullptr } , Factory_InfantryType { nullptr } , Factory_VehicleType { nullptr } @@ -35,7 +37,8 @@ class HouseExt , Factory_AircraftType { nullptr } { } - bool OwnsLimboDeliveredBuilding(BuildingClass const* pBuilding); + bool OwnsLimboDeliveredBuilding(BuildingClass* pBuilding); + void UpdateAutoDeathObjectsInLimbo(); virtual ~ExtData() = default; diff --git a/src/Ext/House/Hooks.cpp b/src/Ext/House/Hooks.cpp index 35aa192aa4..80db89cd6d 100644 --- a/src/Ext/House/Hooks.cpp +++ b/src/Ext/House/Hooks.cpp @@ -4,6 +4,17 @@ #include "../Building/Body.h" #include +DEFINE_HOOK(0x4F8440, HouseClass_Update_Beginning, 0x5) +{ + GET(HouseClass* const, pThis, ECX); + + auto pExt = HouseExt::ExtMap.Find(pThis); + + pExt->UpdateAutoDeathObjectsInLimbo(); + + return 0; +} + DEFINE_HOOK(0x508C30, HouseClass_UpdatePower_UpdateCounter, 0x5) { GET(HouseClass*, pThis, ECX); @@ -15,7 +26,7 @@ DEFINE_HOOK(0x508C30, HouseClass_UpdatePower_UpdateCounter, 0x5) // as M should be much less than N, this will be a great improvement. - secsome for (auto& pBld : pThis->Buildings) { - if (pBld && !pBld->InLimbo && pBld->IsOnMap) + if (TechnoExt::IsActive(pBld) && pBld->IsOnMap && pBld->HasPower) { const auto pExt = BuildingTypeExt::ExtMap.Find(pBld->Type); if (pExt->PowerPlantEnhancer_Buildings.size() && diff --git a/src/Ext/RadSite/Body.cpp b/src/Ext/RadSite/Body.cpp index 2501413d45..6864cea96b 100644 --- a/src/Ext/RadSite/Body.cpp +++ b/src/Ext/RadSite/Body.cpp @@ -25,9 +25,7 @@ bool RadSiteExt::ExtData::ApplyRadiationDamage(TechnoClass* pTarget, int& damage } else { - auto coords = CoordStruct::Empty; - coords = *pTarget->GetCoords(&coords); - WarheadTypeExt::DetonateAt(pWarhead, coords, this->RadInvoker, damage); + WarheadTypeExt::DetonateAt(pWarhead, pTarget, this->RadInvoker, damage); if (!pTarget->IsAlive) return false; diff --git a/src/Ext/RadSite/Hooks.cpp b/src/Ext/RadSite/Hooks.cpp index ae3b945c27..fb3d2c9741 100644 --- a/src/Ext/RadSite/Hooks.cpp +++ b/src/Ext/RadSite/Hooks.cpp @@ -31,7 +31,7 @@ DEFINE_HOOK(0x469150, BulletClass_Detonate_ApplyRadiation, 0x5) auto const pWeapon = pThis->GetWeaponType(); - if (pWeapon && pWeapon->RadLevel > 0) + if (pWeapon && pWeapon->RadLevel > 0 && MapClass::Instance->IsWithinUsableArea((*pCoords))) { auto const pExt = BulletExt::ExtMap.Find(pThis); auto const pWH = pThis->WH; diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index e9ef223361..e23529a4ba 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -12,8 +12,6 @@ inline void LimboCreate(BuildingTypeClass* pType, HouseClass* pOwner, int ID) { - auto pOwnerExt = HouseExt::ExtMap.Find(pOwner); - // BuildLimit check goes before creation if (pType->BuildLimit > 0) { @@ -33,6 +31,16 @@ inline void LimboCreate(BuildingTypeClass* pType, HouseClass* pOwner, int ID) pBuilding->InLimbo = false; pBuilding->IsAlive = true; pBuilding->IsOnMap = true; + + // For reasons beyond my comprehension, the discovery logic is checked for certain logics like power drain/output in campaign only. + // Normally on unlimbo the buildings are revealed to current player if unshrouded or if game is a campaign and to non-player houses always. + // Because of the unique nature of LimboDelivered buildings, this has been adjusted to always reveal to the current player in singleplayer + // and to the owner of the building regardless, removing the shroud check from the equation since they don't physically exist - Starkku + if (SessionClass::Instance->GameMode == GameMode::Campaign) + pBuilding->DiscoveredBy(HouseClass::CurrentPlayer); + + pBuilding->DiscoveredBy(pOwner); + pOwner->RegisterGain(pBuilding, false); pOwner->UpdatePower(); pOwner->RecheckTechTree = true; @@ -68,9 +76,19 @@ inline void LimboCreate(BuildingTypeClass* pType, HouseClass* pOwner, int ID) // LimboKill ID pBuildingExt->LimboID = ID; - // Add building to list of owned limbo buildings - if (pOwnerExt) - pOwnerExt->OwnedLimboDeliveredBuildings.insert({ pBuilding->UniqueID, pBuildingExt }); + if (auto pOwnerExt = HouseExt::ExtMap.Find(pOwner)) + { + // Add building to list of owned limbo buildings + pOwnerExt->OwnedLimboDeliveredBuildings.insert({ pBuilding, pBuildingExt }); + + auto pTechExt = TechnoExt::ExtMap.Find(pBuilding); + + if (pTechExt->TypeExtData->AutoDeath_Behavior.isset() && pTechExt->TypeExtData->AutoDeath_AfterDelay > 0) + { + pTechExt->AutoDeathTimer.Start(pTechExt->TypeExtData->AutoDeath_AfterDelay); + pOwnerExt->OwnedTimedAutoDeathObjects.push_back(pTechExt); + } + } } } } @@ -83,7 +101,7 @@ inline void LimboDelete(BuildingClass* pBuilding, HouseClass* pTargetHouse) // Remove building from list of owned limbo buildings if (pOwnerExt) - pOwnerExt->OwnedLimboDeliveredBuildings.erase(pBuilding->UniqueID); + pOwnerExt->OwnedLimboDeliveredBuildings.erase(pBuilding); // Mandatory pBuilding->InLimbo = true; @@ -194,7 +212,7 @@ void SWTypeExt::ExtData::ApplyLimboKill(HouseClass* pHouse) { if (auto const pHouseExt = HouseExt::ExtMap.Find(pTargetHouse)) { - for (const auto& [buildingUniqueID, pBuildingExt] : pHouseExt->OwnedLimboDeliveredBuildings) + for (const auto& [pBuilding, pBuildingExt] : pHouseExt->OwnedLimboDeliveredBuildings) { if (pBuildingExt->LimboID == limboKillID) LimboDelete(pBuildingExt->OwnerObject(), pTargetHouse); diff --git a/src/Ext/Script/Body.cpp b/src/Ext/Script/Body.cpp index ea3411b966..14d9b8adf5 100644 --- a/src/Ext/Script/Body.cpp +++ b/src/Ext/Script/Body.cpp @@ -260,6 +260,13 @@ void ScriptExt::LoadIntoTransports(TeamClass* pTeam) transports.AddItem(pUnit); } + if (transports.Count == 0) + { + // This action finished + pTeam->StepCompleted = true; + return; + } + // Now load units into transports for (auto pTransport : transports) { @@ -309,9 +316,6 @@ void ScriptExt::LoadIntoTransports(TeamClass* pTeam) } // This action finished - if (pTeam->CurrentScript->HasNextMission()) - ++pTeam->CurrentScript->CurrentMission; - pTeam->StepCompleted = true; } @@ -1092,6 +1096,7 @@ TechnoClass* ScriptExt::GreatestThreat(TechnoClass *pTechno, int method, int cal if ((weaponType && weaponType->Projectile->AG) || agentMode) unitWeaponsHaveAG = true; + /* int weaponDamage = 0; if (weaponType) @@ -1105,9 +1110,13 @@ TechnoClass* ScriptExt::GreatestThreat(TechnoClass *pTechno, int method, int cal // If the target can't be damaged then isn't a valid target if (weaponType && weaponDamage <= 0 && !agentMode) continue; + */ if (!agentMode) { + if (weaponType && GeneralUtils::GetWarheadVersusArmor(weaponType->Warhead, objectType->Armor) == 0.0) + continue; + if (object->IsInAir() && !unitWeaponsHaveAA) continue; diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index cdd35f491f..537b4c6606 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -14,12 +14,25 @@ #include #include #include +#include #include #include template<> const DWORD Extension::Canary = 0x55555555; TechnoExt::ExtContainer TechnoExt::ExtMap; +TechnoExt::ExtData::~ExtData() +{ + if (this->TypeExtData->AutoDeath_Behavior.isset()) + { + auto pThis = this->OwnerObject(); + auto hExt = HouseExt::ExtMap.Find(pThis->Owner); + auto it = std::find(hExt->OwnedTimedAutoDeathObjects.begin(), hExt->OwnedTimedAutoDeathObjects.end(), this); + if (it != hExt->OwnedTimedAutoDeathObjects.end()) + hExt->OwnedTimedAutoDeathObjects.erase(it); + } +} + void TechnoExt::ExtData::ApplyInterceptor() { auto const pThis = this->OwnerObject(); @@ -100,8 +113,9 @@ bool TechnoExt::ExtData::CheckDeathConditions() if (!this->AutoDeathTimer.HasStarted()) { this->AutoDeathTimer.Start(pTypeExt->AutoDeath_AfterDelay); + HouseExt::ExtMap.Find(pThis->Owner)->OwnedTimedAutoDeathObjects.push_back(this); } - else if (!pThis->Transporter && this->AutoDeathTimer.Completed()) + else if (this->AutoDeathTimer.Completed()) { TechnoExt::KillSelf(pThis, howToDie); return true; @@ -292,11 +306,19 @@ void TechnoExt::ExtData::UpdateTypeData(TechnoTypeClass* currentType) // Reset Shield // This part should have been done by UpdateShield + // But that doesn't work correctly either, FIX THAT // Reset AutoDeath Timer if (this->AutoDeathTimer.HasStarted()) + { this->AutoDeathTimer.Stop(); + auto hExt = HouseExt::ExtMap.Find(pThis->Owner); + auto it = std::find(hExt->OwnedTimedAutoDeathObjects.begin(), hExt->OwnedTimedAutoDeathObjects.end(), this); + if (it != hExt->OwnedTimedAutoDeathObjects.end()) + hExt->OwnedTimedAutoDeathObjects.erase(it); + } + // Reset PassengerDeletion Timer - TODO : unchecked if (this->PassengerDeletionTimer.IsTicking() && this->TypeExtData->PassengerDeletion_Rate <= 0) { @@ -581,9 +603,14 @@ void TechnoExt::KillSelf(TechnoClass* pThis, AutoDeathBehavior deathOption) { if (auto pBld = abstract_cast(pThis)) { - if (pBld->Type->LoadBuildup()) + if (pBld->HasBuildUp) { - pBld->Sell(true); + // Sorry FirestormWall + if (pBld->GetCurrentMission() != Mission::Selling) + { + pBld->QueueMission(Mission::Selling, false); + pBld->NextMission(); + } return; } @@ -942,6 +969,7 @@ CoordStruct TechnoExt::PassengerKickOutLocation(TechnoClass* pThis, FootClass* p return finalLocation; } + WeaponTypeClass* TechnoExt::GetDeployFireWeapon(TechnoClass* pThis, int& weaponIndex) { weaponIndex = pThis->GetTechnoType()->DeployFireWeapon; diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 26aa442e87..a080adf2c4 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -67,7 +67,7 @@ class TechnoExt void UpdateTypeData(TechnoTypeClass* currentType); void InitializeLaserTrails(); - virtual ~ExtData() = default; + virtual ~ExtData() override; virtual void InvalidatePointer(void* ptr, bool bRemoved) override { } diff --git a/src/Ext/Techno/Hooks.VehicleToBuildingMCFix.cpp b/src/Ext/Techno/Hooks.DeploysInto.cpp similarity index 58% rename from src/Ext/Techno/Hooks.VehicleToBuildingMCFix.cpp rename to src/Ext/Techno/Hooks.DeploysInto.cpp index 6d35d21cb0..97ba7f2e58 100644 --- a/src/Ext/Techno/Hooks.VehicleToBuildingMCFix.cpp +++ b/src/Ext/Techno/Hooks.DeploysInto.cpp @@ -1,13 +1,5 @@ #include "Body.h" -#include - -#include -#include -#include -#include -#include - #include namespace MindControlFixTemp @@ -24,12 +16,22 @@ void TechnoExt::TransferMindControlOnDeploy(TechnoClass* pTechnoFrom, TechnoClas MindControlFixTemp::isMindControlBeingTransferred = true; CaptureManager::FreeUnit(Manager, pTechnoFrom, true); - CaptureManager::CaptureUnit(Manager, pTechnoTo, true); - if (pTechnoTo->WhatAmI() == AbstractType::Building) + if (CaptureManager::CaptureUnit(Manager, pTechnoTo, false)) { - pTechnoTo->QueueMission(Mission::Construction, 0); - pTechnoTo->Mission_Construction(); + if (auto pBld = abstract_cast(pTechnoTo)) + { + pBld->BeginMode(BStateType::Construction); + pBld->QueueMission(Mission::Construction, false); + } + } + else + { + int nSound = pTechnoTo->GetTechnoType()->MindClearedSound; + if (nSound == -1) + nSound = RulesClass::Instance->MindClearedSound; + if (nSound != -1) + VocClass::PlayIndexAtPos(nSound, pTechnoTo->Location); } MindControlFixTemp::isMindControlBeingTransferred = false; @@ -38,40 +40,44 @@ void TechnoExt::TransferMindControlOnDeploy(TechnoClass* pTechnoFrom, TechnoClas else if (auto MCHouse = pTechnoFrom->MindControlledByHouse) { pTechnoTo->MindControlledByHouse = MCHouse; - pTechnoFrom->MindControlledByHouse = NULL; + pTechnoFrom->MindControlledByHouse = nullptr; } - if (auto Anim = pTechnoFrom->MindControlRingAnim) + if (auto fromAnim = pTechnoFrom->MindControlRingAnim) { - auto ToAnim = &pTechnoTo->MindControlRingAnim; + auto& toAnim = pTechnoTo->MindControlRingAnim; - if (*ToAnim) - (*ToAnim)->TimeToDie = 1; + if (toAnim) + toAnim->TimeToDie = 1; - *ToAnim = Anim; - Anim->SetOwnerObject(pTechnoTo); + toAnim = fromAnim; + fromAnim->SetOwnerObject(pTechnoTo); } } -DEFINE_HOOK(0x739956, UnitClass_Deploy_TransferMindControl, 0x6) +DEFINE_HOOK(0x739956, UnitClass_Deploy_Transfer, 0x6) { GET(UnitClass*, pUnit, EBP); GET(BuildingClass*, pStructure, EBX); TechnoExt::TransferMindControlOnDeploy(pUnit, pStructure); + ShieldClass::SyncShieldToAnother(pUnit, pStructure); + TechnoExt::SyncIronCurtainStatus(pUnit, pStructure); return 0; } -DEFINE_HOOK(0x44A03C, BuildingClass_Mi_Selling_TransferMindControl, 0x6) +DEFINE_HOOK(0x44A03C, BuildingClass_Mi_Selling_Transfer, 0x6) { GET(BuildingClass*, pStructure, EBP); GET(UnitClass*, pUnit, EBX); TechnoExt::TransferMindControlOnDeploy(pStructure, pUnit); + ShieldClass::SyncShieldToAnother(pStructure, pUnit); + TechnoExt::SyncIronCurtainStatus(pStructure, pUnit); pUnit->QueueMission(Mission::Hunt, true); - + //Why? return 0; } diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 5d6072c810..54b6add80f 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -45,7 +45,7 @@ DEFINE_HOOK(0x6F33CD, TechnoClass_WhatWeaponShouldIUse_ForceFire, 0x6) { if (const auto pPrimaryExt = WeaponTypeExt::ExtMap.Find(pThis->GetWeapon(0)->WeaponType)) { - if (pThis->GetWeapon(1)->WeaponType && !EnumFunctions::IsCellEligible(pCell, pPrimaryExt->CanTarget, true)) + if (pThis->GetWeapon(1)->WeaponType && !EnumFunctions::IsCellEligible(pCell, pPrimaryExt->CanTarget, true, true)) return Secondary; } } @@ -116,7 +116,7 @@ DEFINE_HOOK(0x6F36DB, TechnoClass_WhatWeaponShouldIUse, 0x8) { if (const auto pSecondaryExt = WeaponTypeExt::ExtMap.Find(pSecondary->WeaponType)) { - if ((pTargetCell && !EnumFunctions::IsCellEligible(pTargetCell, pSecondaryExt->CanTarget, true)) || + if ((pTargetCell && !EnumFunctions::IsCellEligible(pTargetCell, pSecondaryExt->CanTarget, true, true)) || (pTargetTechno && (!EnumFunctions::IsTechnoEligible(pTargetTechno, pSecondaryExt->CanTarget) || !EnumFunctions::CanTargetHouse(pSecondaryExt->CanTargetHouses, pThis->Owner, pTargetTechno->Owner)))) { @@ -128,7 +128,7 @@ DEFINE_HOOK(0x6F36DB, TechnoClass_WhatWeaponShouldIUse, 0x8) if (pTypeExt->NoSecondaryWeaponFallback && !TechnoExt::CanFireNoAmmoWeapon(pThis, 1)) return Primary; - if ((pTargetCell && !EnumFunctions::IsCellEligible(pTargetCell, pPrimaryExt->CanTarget, true)) || + if ((pTargetCell && !EnumFunctions::IsCellEligible(pTargetCell, pPrimaryExt->CanTarget, true, true)) || (pTargetTechno && (!EnumFunctions::IsTechnoEligible(pTargetTechno, pPrimaryExt->CanTarget) || !EnumFunctions::CanTargetHouse(pPrimaryExt->CanTargetHouses, pThis->Owner, pTargetTechno->Owner)))) { @@ -208,7 +208,7 @@ DEFINE_HOOK(0x6FC339, TechnoClass_CanFire, 0x6) if (targetCell) { - if (!EnumFunctions::IsCellEligible(targetCell, pWeaponExt->CanTarget, true)) + if (!EnumFunctions::IsCellEligible(targetCell, pWeaponExt->CanTarget, true, true)) return CannotFire; } @@ -296,8 +296,9 @@ DEFINE_HOOK(0x6FE19A, TechnoClass_FireAt_AreaFire, 0x6) unsigned int cellIndex = (i + rand) % size; CellStruct tgtPos = pCell->MapCoords + adjacentCells[cellIndex]; CellClass* tgtCell = MapClass::Instance->GetCellAt(tgtPos); + bool allowBridges = tgtCell && tgtCell->ContainsBridge() && (pThis->OnBridge || tgtCell->Level + CellClass::BridgeLevels == pThis->GetCell()->Level); - if (EnumFunctions::AreCellAndObjectsEligible(tgtCell, pExt->CanTarget, pExt->CanTargetHouses, pThis->Owner, true)) + if (EnumFunctions::AreCellAndObjectsEligible(tgtCell, pExt->CanTarget, pExt->CanTargetHouses, pThis->Owner, true, false, allowBridges)) { R->EAX(tgtCell); return 0; @@ -308,14 +309,16 @@ DEFINE_HOOK(0x6FE19A, TechnoClass_FireAt_AreaFire, 0x6) } else if (pExt->AreaFire_Target == AreaFireTarget::Self) { - if (!EnumFunctions::AreCellAndObjectsEligible(pThis->GetCell(), pExt->CanTarget, pExt->CanTargetHouses, nullptr, false)) + if (!EnumFunctions::AreCellAndObjectsEligible(pThis->GetCell(), pExt->CanTarget, pExt->CanTargetHouses, nullptr, false, false, pThis->OnBridge)) return DoNotFire; R->EAX(pThis); return SkipSetTarget; } - if (!EnumFunctions::AreCellAndObjectsEligible(pCell, pExt->CanTarget, pExt->CanTargetHouses, nullptr, false)) + bool allowBridges = pCell && pCell->ContainsBridge() && (pThis->OnBridge || pCell->Level + CellClass::BridgeLevels == pThis->GetCell()->Level); + + if (!EnumFunctions::AreCellAndObjectsEligible(pCell, pExt->CanTarget, pExt->CanTargetHouses, nullptr, false, false, allowBridges)) return DoNotFire; } diff --git a/src/Ext/Techno/Hooks.Shield.cpp b/src/Ext/Techno/Hooks.Shield.cpp index 05ba207ec1..141bf68e5e 100644 --- a/src/Ext/Techno/Hooks.Shield.cpp +++ b/src/Ext/Techno/Hooks.Shield.cpp @@ -125,18 +125,6 @@ DEFINE_HOOK(0x6F6AC4, TechnoClass_Remove_Shield, 0x5) return 0; } -DEFINE_HOOK_AGAIN(0x44A03C, DeploysInto_UndeploysInto_SyncShieldStatus, 0x6) //BuildingClass_Mi_Selling_SyncShieldStatus -DEFINE_HOOK(0x739956, DeploysInto_UndeploysInto_SyncShieldStatus, 0x6) //UnitClass_Deploy_SyncShieldStatus -{ - GET(TechnoClass*, pFrom, EBP); - GET(TechnoClass*, pTo, EBX); - - ShieldClass::SyncShieldToAnother(pFrom, pTo); - TechnoExt::SyncIronCurtainStatus(pFrom, pTo); - - return 0; -} - DEFINE_HOOK(0x6F65D1, TechnoClass_DrawHealthBar_DrawBuildingShieldBar, 0x6) { GET(TechnoClass*, pThis, ESI); diff --git a/src/Ext/TechnoType/Hooks.cpp b/src/Ext/TechnoType/Hooks.cpp index bcc00a8210..c84b9d0143 100644 --- a/src/Ext/TechnoType/Hooks.cpp +++ b/src/Ext/TechnoType/Hooks.cpp @@ -188,12 +188,17 @@ DEFINE_HOOK(0x6F421C, TechnoClass_DefaultDisguise, 0x6) // TechnoClass_DefaultDi DEFINE_HOOK(0x73CF46, UnitClass_Draw_It_KeepUnitVisible, 0x6) { + enum { KeepUnitVisible = 0x73CF62 }; + GET(UnitClass*, pThis, ESI); - auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); + if (pThis->Deploying || pThis->Undeploying) + { + auto pTypeExt = TechnoTypeExt::ExtMap.Find(pThis->GetTechnoType()); - if (pTypeExt->DeployingAnim_KeepUnitVisible && (pThis->Deploying || pThis->Undeploying)) - return 0x73CF62; + if (pTypeExt->DeployingAnim_KeepUnitVisible) + return KeepUnitVisible; + } return 0; } diff --git a/src/Ext/TerrainType/Hooks.cpp b/src/Ext/TerrainType/Hooks.cpp index d4107d7d77..78456cb001 100644 --- a/src/Ext/TerrainType/Hooks.cpp +++ b/src/Ext/TerrainType/Hooks.cpp @@ -51,6 +51,27 @@ DEFINE_HOOK(0x71C84D, TerrainClass_AI_Animated, 0x6) return SkipGameCode; } +// Overrides Ares hook at 0x5F4FF9, required for animated terrain cause game & Ares check SpawnsTiberium instead of IsAnimated +DEFINE_HOOK(0x5F4FEF, ObjectClass_Unlimbo_UpdateTerrain, 0x6) +{ + enum { SkipUpdate = 0x5F5045, ContinueChecks = 0x5F501B }; + + GET(ObjectTypeClass*, pType, EBX); + + if (!pType->IsLogic) + return SkipUpdate; + + if (pType->WhatAmI() != AbstractType::TerrainType) + return ContinueChecks; + + auto const pTerrainType = static_cast(pType); + + if (pTerrainType->IsFlammable || pTerrainType->IsAnimated) + return ContinueChecks; + + return SkipUpdate; +} + DEFINE_HOOK(0x483811, CellClass_SpreadTiberium_TiberiumType, 0x8) { if (TerrainTypeTemp::pCurrentExt) diff --git a/src/Ext/Unit/Hooks.DeployFire.cpp b/src/Ext/Unit/Hooks.DeployFire.cpp index e5aa6735be..c15516775f 100644 --- a/src/Ext/Unit/Hooks.DeployFire.cpp +++ b/src/Ext/Unit/Hooks.DeployFire.cpp @@ -61,10 +61,10 @@ DEFINE_HOOK(0x73DCEF, UnitClass_Mission_Unload_DeployFire, 0x6) } auto pCell = MapClass::Instance->GetCellAt(pThis->GetMapCoords()); + pThis->SetTarget(pCell); if (pThis->GetFireError(pCell, weaponIndex, true) == FireError::OK) { - pThis->SetTarget(pCell); pThis->Fire(pThis->Target, weaponIndex); if (pWeapon->FireOnce) diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index 8243400395..cb68319634 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -30,7 +30,7 @@ bool WarheadTypeExt::ExtData::CanTargetHouse(HouseClass* pHouse, TechnoClass* pT return true; } -void WarheadTypeExt::DetonateAt(WarheadTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, int damage) +void WarheadTypeExt::DetonateAt(WarheadTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse) { BulletTypeClass* pType = BulletTypeExt::GetDefaultBulletType(); @@ -39,6 +39,12 @@ void WarheadTypeExt::DetonateAt(WarheadTypeClass* pThis, ObjectClass* pTarget, T { const CoordStruct& coords = pTarget->GetCoords(); + if (pFiringHouse) + { + auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); + pBulletExt->FirerHouse = pFiringHouse; + } + pBullet->Limbo(); pBullet->SetLocation(coords); pBullet->Explode(true); @@ -46,13 +52,19 @@ void WarheadTypeExt::DetonateAt(WarheadTypeClass* pThis, ObjectClass* pTarget, T } } -void WarheadTypeExt::DetonateAt(WarheadTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage) +void WarheadTypeExt::DetonateAt(WarheadTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse) { BulletTypeClass* pType = BulletTypeExt::GetDefaultBulletType(); if (BulletClass* pBullet = pType->CreateBullet(nullptr, pOwner, damage, pThis, 0, false)) { + if (pFiringHouse) + { + auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); + pBulletExt->FirerHouse = pFiringHouse; + } + pBullet->Limbo(); pBullet->SetLocation(coords); pBullet->Explode(true); diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 7eea009e90..47dad94d5b 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -213,6 +213,6 @@ class WarheadTypeExt static bool LoadGlobals(PhobosStreamReader& Stm); static bool SaveGlobals(PhobosStreamWriter& Stm); - static void DetonateAt(WarheadTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, int damage); - static void DetonateAt(WarheadTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage); + static void DetonateAt(WarheadTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse = nullptr); + static void DetonateAt(WarheadTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse = nullptr); }; diff --git a/src/Ext/WarheadType/Hooks.cpp b/src/Ext/WarheadType/Hooks.cpp index a31d7568b2..7fcec3fe01 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -17,12 +17,12 @@ DEFINE_HOOK(0x46920B, BulletClass_Detonate, 0x6) { GET(BulletClass* const, pBullet, ESI); - auto const pBulletExt = pBullet ? BulletExt::ExtMap.Find(pBullet) : nullptr; auto const pWH = pBullet ? pBullet->WH : nullptr; if (auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH)) { GET_BASE(const CoordStruct*, pCoords, 0x8); + auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); auto const pOwner = pBullet->Owner; auto const pHouse = pOwner ? pOwner->Owner : nullptr; auto const pDecidedHouse = pHouse ? pHouse : pBulletExt->FirerHouse; diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 0a9b41ae99..665b0d1132 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -1,5 +1,6 @@ #include "Body.h" #include +#include template<> const DWORD Extension::Canary = 0x22222222; WeaponTypeExt::ExtContainer WeaponTypeExt::ExtMap; @@ -87,18 +88,24 @@ bool WeaponTypeExt::SaveGlobals(PhobosStreamWriter& Stm) .Success(); } -void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner) +void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, HouseClass* pFiringHouse) { - WeaponTypeExt::DetonateAt(pThis, pTarget, pOwner, pThis->Damage); + WeaponTypeExt::DetonateAt(pThis, pTarget, pOwner, pThis->Damage, pFiringHouse); } -void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, int damage) +void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse) { if (BulletClass* pBullet = pThis->Projectile->CreateBullet(pTarget, pOwner, damage, pThis->Warhead, 0, pThis->Bright)) { const CoordStruct& coords = pTarget->GetCoords(); + if (pFiringHouse) + { + auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); + pBulletExt->FirerHouse = pFiringHouse; + } + pBullet->SetWeaponType(pThis); pBullet->Limbo(); pBullet->SetLocation(coords); @@ -107,16 +114,22 @@ void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, Tec } } -void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner) +void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, HouseClass* pFiringHouse) { - WeaponTypeExt::DetonateAt(pThis, coords, pOwner, pThis->Damage); + WeaponTypeExt::DetonateAt(pThis, coords, pOwner, pThis->Damage, pFiringHouse); } -void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage) +void WeaponTypeExt::DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse) { if (BulletClass* pBullet = pThis->Projectile->CreateBullet(nullptr, pOwner, damage, pThis->Warhead, 0, pThis->Bright)) { + if (pFiringHouse) + { + auto const pBulletExt = BulletExt::ExtMap.Find(pBullet); + pBulletExt->FirerHouse = pFiringHouse; + } + pBullet->SetWeaponType(pThis); pBullet->Limbo(); pBullet->SetLocation(coords); diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 70dd92d4a7..28c10e8514 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -77,8 +77,8 @@ class WeaponTypeExt static double OldRadius; - static void DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner); - static void DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, int damage); - static void DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner); - static void DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage); + static void DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, HouseClass* pFiringHouse = nullptr); + static void DetonateAt(WeaponTypeClass* pThis, ObjectClass* pTarget, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse = nullptr); + static void DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, HouseClass* pFiringHouse = nullptr); + static void DetonateAt(WeaponTypeClass* pThis, const CoordStruct& coords, TechnoClass* pOwner, int damage, HouseClass* pFiringHouse = nullptr); }; diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index de6efbe0f8..8de185b52f 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -578,3 +578,22 @@ DEFINE_HOOK(0x70BCE6, TechnoClass_GetTargetCoords_BuildingFix, 0x6) return 0; } + +// Fixes a literal edge-case in passability checks to cover cells with bridges that are not accessible when moving on the bridge and +// normally not even attempted to enter but things like MapClass::NearByLocation() can still end up trying to pick. +DEFINE_HOOK(0x4834E5, CellClass_IsClearToMove_BridgeEdges, 0x5) +{ + enum { IsNotClear = 0x48351E }; + + GET(CellClass*, pThis, ESI); + GET(int, level, EAX); + GET(bool, isBridge, EBX); + + if (isBridge && pThis->ContainsBridge() && (level == -1 || level == pThis->Level + CellClass::BridgeLevels) + && !(pThis->Flags & CellFlags::Unknown_200)) + { + return IsNotClear; + } + + return 0; +} diff --git a/src/Misc/Hooks.PCX.cpp b/src/Misc/Hooks.PCX.cpp index f59b853d55..88e6c2dd93 100644 --- a/src/Misc/Hooks.PCX.cpp +++ b/src/Misc/Hooks.PCX.cpp @@ -56,11 +56,8 @@ DEFINE_HOOK(0x5535D0, LoadProgressMgr_Draw_PCXLoadingScreen, 0x6) return 0; } -DEFINE_HOOK(0x552F81, LoadProgressMgr_Draw_PCXLoadingScreen_Campaign, 0x5) +DEFINE_HOOK(0x552FCB, LoadProgressMgr_Draw_PCXLoadingScreen_Campaign, 0x6) { - GET(LoadProgressManager*, pThis, EBP); - - DSurface* pSurface = static_cast(pThis->ProgressSurface); char filename[0x40]; strcpy_s(filename, ScenarioClass::Instance->LS800BkgdName); _strlwr_s(filename); @@ -71,6 +68,8 @@ DEFINE_HOOK(0x552F81, LoadProgressMgr_Draw_PCXLoadingScreen_Campaign, 0x5) if (auto const pPCX = PCX::Instance->GetSurface(filename)) { + GET_BASE(DSurface*, pSurface, 0x60); + RectangleStruct pSurfBounds = { 0, 0, pSurface->Width, pSurface->Height }; RectangleStruct pcxBounds = { 0, 0, pPCX->Width, pPCX->Height }; RectangleStruct destClip = { (pSurface->Width - pPCX->Width) / 2, (pSurface->Height - pPCX->Height) / 2, pPCX->Width, pPCX->Height }; @@ -78,8 +77,7 @@ DEFINE_HOOK(0x552F81, LoadProgressMgr_Draw_PCXLoadingScreen_Campaign, 0x5) pSurface->CopyFrom(&pSurfBounds, &destClip, pPCX, &pcxBounds, &pcxBounds, true, true); } - R->EBX(R->EDI()); - return 0x552FC6; + return 0x552FFF; } return 0; diff --git a/src/Misc/RetryDialog.cpp b/src/Misc/RetryDialog.cpp index d023140bf3..c06fae3d7d 100644 --- a/src/Misc/RetryDialog.cpp +++ b/src/Misc/RetryDialog.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -23,20 +24,21 @@ DEFINE_HOOK(0x686092, DoLose_RetryDialogForCampaigns, 0x7) // Button3 // Button2 // I prefer to put the loadgame to the center of them - secsome - switch (WWMessageBox::Instance().Process( - StringTable::LoadString("TXT_TO_REPLAY"), - StringTable::LoadString("TXT_OK"), - StringTable::LoadString("GUI:LOADGAME"), - StringTable::LoadString("TXT_CANCEL"))) + // Did you??? NO, YOU DIDN'T. Bruhhhh + switch (WWMessageBox::Instance->Process( + GameStrings::TXT_TO_REPLAY, + "GUI:LOADGAME", + GameStrings::TXT_CANCEL, + GameStrings::TXT_OK)) { - case WWMessageBox::Result::Button1: + case WWMessageBox::Result::Button3: return OK; default: - case WWMessageBox::Result::Button3: + case WWMessageBox::Result::Button2: return Cancel; - case WWMessageBox::Result::Button2: + case WWMessageBox::Result::Button1: auto pDialog = GameCreate(); RetryDialogFlag::IsCalledFromRetryDialog = true; const bool bIsAboutToLoad = pDialog->LoadDialog(); diff --git a/src/Misc/SyncLogging.cpp b/src/Misc/SyncLogging.cpp new file mode 100644 index 0000000000..575a3dadf0 --- /dev/null +++ b/src/Misc/SyncLogging.cpp @@ -0,0 +1,506 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +bool SyncLogger::HooksDisabled = false; +int SyncLogger::AnimCreations_HighestX = 0; +int SyncLogger::AnimCreations_HighestY = 0; +int SyncLogger::AnimCreations_HighestZ = 0; + +SyncLogEventBuffer SyncLogger::RNGCalls; +SyncLogEventBuffer SyncLogger::FacingChanges; +SyncLogEventBuffer SyncLogger::TargetChanges; +SyncLogEventBuffer SyncLogger::DestinationChanges; +SyncLogEventBuffer SyncLogger::MissionOverrides; +SyncLogEventBuffer SyncLogger::AnimCreations; + +void SyncLogger::AddRNGCallSyncLogEvent(Randomizer* pRandomizer, int type, unsigned int callerAddress, int min, int max) +{ + // Don't log non-critical RNG calls. + if (pRandomizer == &ScenarioClass::Instance->Random) + SyncLogger::RNGCalls.Add(RNGCallSyncLogEvent(type, true, pRandomizer->Next1, pRandomizer->Next2, callerAddress, Unsorted::CurrentFrame, min, max)); +} + +void SyncLogger::AddFacingChangeSyncLogEvent(unsigned short facing, unsigned int callerAddress) +{ + SyncLogger::FacingChanges.Add(FacingChangeSyncLogEvent(facing, callerAddress, Unsorted::CurrentFrame)); +} + +void SyncLogger::AddTargetChangeSyncLogEvent(AbstractClass* pObject, AbstractClass* pTarget, unsigned int callerAddress) +{ + if (!pObject) + return; + + auto targetRTTI = AbstractType::None; + unsigned int targetID = 0; + + if (pTarget) + { + targetRTTI = pTarget->WhatAmI(); + targetID = pTarget->UniqueID; + } + + SyncLogger::TargetChanges.Add(TargetChangeSyncLogEvent(pObject->WhatAmI(), pObject->UniqueID, targetRTTI, targetID, callerAddress, Unsorted::CurrentFrame)); +} + +void SyncLogger::AddDestinationChangeSyncLogEvent(AbstractClass* pObject, AbstractClass* pTarget, unsigned int callerAddress) +{ + if (!pObject) + return; + + auto targetRTTI = AbstractType::None; + unsigned int targetID = 0; + + if (pTarget) + { + targetRTTI = pTarget->WhatAmI(); + targetID = pTarget->UniqueID; + } + + SyncLogger::DestinationChanges.Add(TargetChangeSyncLogEvent(pObject->WhatAmI(), pObject->UniqueID, targetRTTI, targetID, callerAddress, Unsorted::CurrentFrame)); +} + +void SyncLogger::AddMissionOverrideSyncLogEvent(AbstractClass* pObject, int mission, unsigned int callerAddress) +{ + if (!pObject) + return; + + SyncLogger::MissionOverrides.Add(MissionOverrideSyncLogEvent(pObject->WhatAmI(), pObject->UniqueID, mission, callerAddress, Unsorted::CurrentFrame)); +} + +void SyncLogger::AddAnimCreationSyncLogEvent(const CoordStruct& coords, unsigned int callerAddress) +{ + if (coords.X > SyncLogger::AnimCreations_HighestX) + SyncLogger::AnimCreations_HighestX = coords.X; + + if (coords.Y > SyncLogger::AnimCreations_HighestY) + SyncLogger::AnimCreations_HighestY = coords.Y; + + if (coords.Z > SyncLogger::AnimCreations_HighestZ) + SyncLogger::AnimCreations_HighestZ = coords.Z; + + if (SyncLogger::AnimCreations.Add(AnimCreationSyncLogEvent(coords, callerAddress, Unsorted::CurrentFrame))) + { + SyncLogger::AnimCreations_HighestX = 0; + SyncLogger::AnimCreations_HighestY = 0; + SyncLogger::AnimCreations_HighestZ = 0; + } +} + +void SyncLogger::WriteSyncLog(const char* logFilename) +{ + auto const pLogFile = fopen(logFilename, "at"); + + if (pLogFile) + { + Debug::Log("Writing to sync log file '%s'.\n", logFilename); + + fprintf(pLogFile, "\nPhobos synchronization log:\n\n"); + + int frameDigits = GeneralUtils::CountDigitsInNumber(Unsorted::CurrentFrame); + + WriteRNGCalls(pLogFile, frameDigits); + WriteFacingChanges(pLogFile, frameDigits); + WriteTargetChanges(pLogFile, frameDigits); + WriteDestinationChanges(pLogFile, frameDigits); + WriteAnimCreations(pLogFile, frameDigits); + + fclose(pLogFile); + } + else + { + Debug::Log("Failed to open sync log file '%s'.\n", logFilename); + } +} + +void SyncLogger::WriteRNGCalls(FILE* const pLogFile, int frameDigits) +{ + fprintf(pLogFile, "RNG Calls:\n"); + + for (size_t i = 0; i < SyncLogger::RNGCalls.Size(); i++) + { + auto const& rngCall = SyncLogger::RNGCalls.Get(); + + if (!rngCall.Initialized) + continue; + + if (rngCall.Type == 1) + { + fprintf(pLogFile, "#%05d: Single | Caller: %08x | Frame: %*d | Index1: %3d | Index2: %3d\n", + i, rngCall.Caller, frameDigits, rngCall.Frame, rngCall.Index1, rngCall.Index2); + } + else if (rngCall.Type == 2) + { + fprintf(pLogFile, "#%05d: Ranged | Caller: %08x | Frame: %*d | Index1: %3d | Index2: %3d | Min: %d | Max: %d\n", + i, rngCall.Caller, frameDigits, rngCall.Frame, rngCall.Index1, rngCall.Index2, rngCall.Min, rngCall.Max); + } + } + + fprintf(pLogFile, "\n"); +} + +void SyncLogger::WriteFacingChanges(FILE* const pLogFile, int frameDigits) +{ + fprintf(pLogFile, "Facing changes:\n"); + + for (size_t i = 0; i < SyncLogger::FacingChanges.Size(); i++) + { + auto const& facingChange = SyncLogger::FacingChanges.Get(); + + if (!facingChange.Initialized) + continue; + + fprintf(pLogFile, "#%05d: Facing: %5d | Caller: %08x | Frame: %*d\n", + i, facingChange.Facing, facingChange.Caller, frameDigits, facingChange.Frame); + } + + fprintf(pLogFile, "\n"); +} + +void SyncLogger::WriteTargetChanges(FILE* const pLogFile, int frameDigits) +{ + fprintf(pLogFile, "Target changes:\n"); + + for (size_t i = 0; i < SyncLogger::TargetChanges.Size(); i++) + { + auto const& targetChange = SyncLogger::TargetChanges.Get(); + + if (!targetChange.Initialized) + continue; + + fprintf(pLogFile, "#%05d: RTTI: %02d | ID: %08d | TargetRTTI: %02d | TargetID: %08d | Caller: %08x | Frame: %*d\n", + i, targetChange.Type, targetChange.ID, targetChange.TargetType, targetChange.TargetID, targetChange.Caller, frameDigits, targetChange.Frame); + } + + fprintf(pLogFile, "\n"); +} + +void SyncLogger::WriteDestinationChanges(FILE* const pLogFile, int frameDigits) +{ + fprintf(pLogFile, "Destination changes:\n"); + + for (size_t i = 0; i < SyncLogger::DestinationChanges.Size(); i++) + { + auto const& destChange = SyncLogger::DestinationChanges.Get(); + + if (!destChange.Initialized) + continue; + + fprintf(pLogFile, "#%05d: RTTI: %02d | ID: %08d | TargetRTTI: %02d | TargetID: %08d | Caller: %08x | Frame: %*d\n", + i, destChange.Type, destChange.ID, destChange.TargetType, destChange.TargetID, destChange.Caller, frameDigits, destChange.Frame); + } + + fprintf(pLogFile, "\n"); +} + +void SyncLogger::WriteMissionOverrides(FILE* const pLogFile, int frameDigits) +{ + fprintf(pLogFile, "Mission overrides:\n"); + + for (size_t i = 0; i < SyncLogger::MissionOverrides.Size(); i++) + { + auto const& missionOverride = SyncLogger::MissionOverrides.Get(); + + if (!missionOverride.Initialized) + continue; + + fprintf(pLogFile, "#%05d: RTTI: %02d | ID: %08d | Mission: %02d | Caller: %08x | Frame: %*d\n", + i, missionOverride.Type, missionOverride.ID, missionOverride.Mission, missionOverride.Caller, frameDigits, missionOverride.Frame); + } + + fprintf(pLogFile, "\n"); +} + + +void SyncLogger::WriteAnimCreations(FILE* const pLogFile, int frameDigits) +{ + int xDigits = GeneralUtils::CountDigitsInNumber(SyncLogger::AnimCreations_HighestX); + int yDigits = GeneralUtils::CountDigitsInNumber(SyncLogger::AnimCreations_HighestY); + int zDigits = GeneralUtils::CountDigitsInNumber(SyncLogger::AnimCreations_HighestZ); + + fprintf(pLogFile, "Animation creations:\n"); + + for (size_t i = 0; i < SyncLogger::AnimCreations.Size(); i++) + { + auto const& animCreation = SyncLogger::AnimCreations.Get(); + + if (!animCreation.Initialized) + continue; + + fprintf(pLogFile, "#%05d: X: %*d | Y: %*d | Z: %*d | Caller: %08x | Frame: %*d\n", + i, xDigits, animCreation.Coords.X, yDigits, animCreation.Coords.Y, zDigits, animCreation.Coords.Z, animCreation.Caller, frameDigits, animCreation.Frame); + } + + fprintf(pLogFile, "\n"); +} + +// Hooks. Anim contructor logging is in Ext/Anim/Body.cpp to reduce duplicate hooks + +// Sync file writing + +DEFINE_HOOK(0x64736D, Queue_AI_WriteDesyncLog, 0x5) +{ + GET(int, frame, ECX); + + char logFilename[0x40]; + + if (Game::EnableMPSyncDebug) + _snprintf_s(logFilename, _TRUNCATE, "SYNC%01d_%03d.TXT", HouseClass::CurrentPlayer->ArrayIndex, frame % 256); + else + _snprintf_s(logFilename, _TRUNCATE, "SYNC%01d.TXT", HouseClass::CurrentPlayer->ArrayIndex); + + SyncLogger::WriteSyncLog(logFilename); + + // Replace overridden instructions. + JMP_STD(0x6BEC60); + + return 0x647374; +} + +DEFINE_HOOK(0x64CD11, ExecuteDoList_WriteDesyncLog, 0x8) +{ + char logFilename[0x40]; + + if (Game::EnableMPSyncDebug) + { + for (int i = 0; i < 256; i++) + { + _snprintf_s(logFilename, _TRUNCATE, "SYNC%01d_%03d.TXT", HouseClass::CurrentPlayer->ArrayIndex, i); + SyncLogger::WriteSyncLog(logFilename); + } + } + else + { + _snprintf_s(logFilename, _TRUNCATE, "SYNC%01d.TXT", HouseClass::CurrentPlayer->ArrayIndex); + SyncLogger::WriteSyncLog(logFilename); + } + + return 0; +} + +// RNG call logging + +DEFINE_HOOK(0x65C7D0, Random2Class_Random_SyncLog, 0x6) +{ + GET(Randomizer*, pThis, ECX); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddRNGCallSyncLogEvent(pThis, 1, callerAddress); + + return 0; +} + +DEFINE_HOOK(0x65C88A, Random2Class_RandomRanged_SyncLog, 0x6) +{ + GET(Randomizer*, pThis, EDX); + GET_STACK(unsigned int, callerAddress, 0x0); + GET_STACK(int, min, 0x4); + GET_STACK(int, max, 0x8); + + SyncLogger::AddRNGCallSyncLogEvent(pThis, 2, callerAddress, min, max); + + return 0; +} + +// Facing change logging + +DEFINE_HOOK(0x4C9300, FacingClass_Set_SyncLog, 0x5) +{ + GET_STACK(DirStruct*, facing, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddFacingChangeSyncLogEvent(facing->Raw, callerAddress); + + return 0; +} + +// Target change logging + +DEFINE_HOOK(0x51B1F0, InfantryClass_AssignTarget_SyncLog, 0x5) +{ + GET(InfantryClass*, pThis, ECX); + GET_STACK(AbstractClass*, pTarget, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddTargetChangeSyncLogEvent(pThis, pTarget, callerAddress); + + return 0; +} + +DEFINE_HOOK(0x443B90, BuildingClass_AssignTarget_SyncLog, 0xB) +{ + GET(BuildingClass*, pThis, ECX); + GET_STACK(AbstractClass*, pTarget, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddTargetChangeSyncLogEvent(pThis, pTarget, callerAddress); + + return 0; +} + +DEFINE_HOOK(0x6FCDB0, TechnoClass_AssignTarget_SyncLog, 0x5) +{ + GET(TechnoClass*, pThis, ECX); + GET_STACK(AbstractClass*, pTarget, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + auto const RTTI = pThis->WhatAmI(); + + if (RTTI != AbstractType::Building && RTTI != AbstractType::Infantry) + SyncLogger::AddTargetChangeSyncLogEvent(pThis, pTarget, callerAddress); + + return 0; +} + +// Destination change logging + +DEFINE_HOOK(0x41AA80, AircraftClass_AssignDestination_SyncLog, 0x7) +{ + GET(AircraftClass*, pThis, ECX); + GET_STACK(AbstractClass*, pDest, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddDestinationChangeSyncLogEvent(pThis, pDest, callerAddress); + + return 0; +} + +DEFINE_HOOK(0x455D50, BuildingClass_AssignDestination_SyncLog, 0xA) +{ + GET(BuildingClass*, pThis, ECX); + GET_STACK(AbstractClass*, pDest, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddDestinationChangeSyncLogEvent(pThis, pDest, callerAddress); + + return 0; +} + +DEFINE_HOOK(0x51AA40, InfantryClass_AssignDestination_SyncLog, 0x5) +{ + GET(InfantryClass*, pThis, ECX); + GET_STACK(AbstractClass*, pDest, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddDestinationChangeSyncLogEvent(pThis, pDest, callerAddress); + + return 0; +} + +DEFINE_HOOK(0x741970, UnitClass_AssignDestination_SyncLog, 0x6) +{ + GET(UnitClass*, pThis, ECX); + GET_STACK(AbstractClass*, pDest, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddDestinationChangeSyncLogEvent(pThis, pDest, callerAddress); + + return 0; +} + +// Mission override logging + +DEFINE_HOOK(0x41BB30, AircraftClass_OverrideMission_SyncLog, 0x6) +{ + GET(AircraftClass*, pThis, ECX); + GET_STACK(int, mission, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddMissionOverrideSyncLogEvent(pThis, mission, callerAddress); + + return 0; +} + +DEFINE_HOOK(0x4D8F40, FootClass_OverrideMission_SyncLog, 0x5) +{ + GET(FootClass*, pThis, ECX); + GET_STACK(int, mission, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + SyncLogger::AddMissionOverrideSyncLogEvent(pThis, mission, callerAddress); + + return 0; +} + +DEFINE_HOOK(0x7013A0, TechnoClass_OverrideMission_SyncLog, 0x5) +{ + GET(TechnoClass*, pThis, ECX); + GET_STACK(int, mission, 0x4); + GET_STACK(unsigned int, callerAddress, 0x0); + + if (pThis->WhatAmI() == AbstractType::Building) + SyncLogger::AddMissionOverrideSyncLogEvent(pThis, mission, callerAddress); + + return 0; +} + +// Disable sync logging hooks in non-MP games +DEFINE_HOOK(0x683AB0, ScenarioClass_Start_DisableSyncLog, 0x6) +{ + if (!SessionClass::Instance->IsSingleplayer() || SyncLogger::HooksDisabled) + return 0; + + SyncLogger::HooksDisabled = true; + + Patch::Apply_RAW(0x65C7D0, // Disable Random2Class_Random_SyncLog + { 0xC3, 0x90, 0x90, 0x90, 0x90, 0x90 } + ); + + Patch::Apply_RAW(0x65C88A, // Disable Random2Class_RandomRanged_SyncLog + { 0xC2, 0x08, 0x00, 0x90, 0x90, 0x90 } + ); + + Patch::Apply_RAW(0x4C9300, // Disable FacingClass_Set_SyncLog + { 0x83, 0xEC, 0x10, 0x53, 0x56 } + ); + + Patch::Apply_RAW(0x51B1F0, // Disable InfantryClass_AssignTarget_SyncLog + { 0x53, 0x56, 0x8B, 0xF1, 0x57 } + ); + + Patch::Apply_RAW(0x443B90, // Disable BuildingClass_AssignTarget_SyncLog + { 0x56, 0x8B, 0xF1, 0x57, 0x83, 0xBE, 0xAC, 0x0, 0x0, 0x0, 0x13 } + ); + + Patch::Apply_RAW(0x6FCDB0, // Disable TechnoClass_AssignTarget_SyncLog + { 0x83, 0xEC, 0x0C, 0x53, 0x56 } + ); + + Patch::Apply_RAW(0x41AA80, // Disable AircraftClass_AssignDestination_SyncLog + { 0x53, 0x56, 0x57, 0x8B, 0x7C, 0x24, 0x10 } + ); + + Patch::Apply_RAW(0x455D50, // Disable BuildingClass_AssignDestination_SyncLog + { 0x56, 0x8B, 0xF1, 0x83, 0xBE, 0xAC, 0x0, 0x0, 0x0, 0x13 } + ); + + Patch::Apply_RAW(0x51AA40, // Disable InfantryClass_AssignDestination_SyncLog + { 0x83, 0xEC, 0x2C, 0x53, 0x55 } + ); + + Patch::Apply_RAW(0x741970, // Disable UnitClass_AssignDestination_SyncLog + { 0x81, 0xEC, 0x80, 0x0, 0x0, 0x0 } + ); + + Patch::Apply_RAW(0x41BB30, // Disable AircraftClass_OverrideMission_SyncLog + { 0x8B, 0x81, 0xAC, 0x0, 0x0, 0x0 } + ); + + Patch::Apply_RAW(0x4D8F40, // Disable FootClass_OverrideMission_SyncLog + { 0x8B, 0x54, 0x24, 0x4, 0x56 } + ); + + Patch::Apply_RAW(0x7013A0, // Disable TechnoClass_OverrideMission_SyncLog + { 0x8B, 0x54, 0x24, 0x4, 0x56 } + ); + + return 0; +} diff --git a/src/Misc/SyncLogging.h b/src/Misc/SyncLogging.h new file mode 100644 index 0000000000..5d990ee2e4 --- /dev/null +++ b/src/Misc/SyncLogging.h @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include +#include + +// These determine how many of each type of sync log event are stored in the buffers. +// Any events added beyond this count overwrite old ones. +static constexpr unsigned int RNGCalls_Size = 4096; +static constexpr unsigned int FacingChanges_Size = 1024; +static constexpr unsigned int TargetChanges_Size = 1024; +static constexpr unsigned int DestinationChanges_Size = 1024; +static constexpr unsigned int MissionOverrides_Size = 256; +static constexpr unsigned int AnimCreations_Size = 512; + +template +class SyncLogEventBuffer +{ +private: + std::vector Data; + int LastWritePosition; + int LastReadPosition; + bool HasBeenFilled = true; +public: + SyncLogEventBuffer() : Data(size), LastWritePosition(0), LastReadPosition(-1), HasBeenFilled(false) { }; + + bool Add(T item) + { + Data[LastWritePosition] = item; + LastWritePosition++; + + if (static_cast(LastWritePosition) >= Data.size()) + { + HasBeenFilled = true; + LastWritePosition = 0; + return true; + } + + return false; + } + + T Get() + { + if (!LastWritePosition && LastReadPosition == -1 && !HasBeenFilled) + return T(); + + if (LastReadPosition == -1 && HasBeenFilled) + LastReadPosition = LastWritePosition ; + else if (LastReadPosition == -1 || static_cast(LastReadPosition) >= Data.size()) + LastReadPosition = 0; + + return Data[LastReadPosition++]; + } + + size_t Size() { return Data.size(); } +}; + +struct SyncLogEvent +{ + bool Initialized; + unsigned int Caller; + unsigned int Frame; + + SyncLogEvent() : Initialized(false), Caller(0), Frame(0) { } + + SyncLogEvent(unsigned int Caller, unsigned int Frame) + : Caller(Caller), Frame(Frame) + { + Initialized = true; + } +}; + +struct RNGCallSyncLogEvent : SyncLogEvent +{ + int Type; // 0 = Invalid, 1 = Unranged, 2 = Ranged + bool IsCritical; + unsigned int Index1; + unsigned int Index2; + int Min; + int Max; + + RNGCallSyncLogEvent() : SyncLogEvent() { } + + RNGCallSyncLogEvent(int Type, bool IsCritical, unsigned int Index1, unsigned int Index2, unsigned int Caller, unsigned int Frame, int Min, int Max) + : Type(Type), IsCritical(IsCritical), Index1(Index1), Index2(Index2), Min(Min), Max(Max), SyncLogEvent(Caller, Frame) + { + } +}; + +struct FacingChangeSyncLogEvent : SyncLogEvent +{ + unsigned short Facing; + + FacingChangeSyncLogEvent() : SyncLogEvent() { } + + FacingChangeSyncLogEvent(unsigned short Facing, unsigned int Caller, unsigned int Frame) + : Facing(Facing), SyncLogEvent(Caller, Frame) + { + } +}; + +struct TargetChangeSyncLogEvent : SyncLogEvent +{ + AbstractType Type; + DWORD ID; + AbstractType TargetType; + DWORD TargetID; + + TargetChangeSyncLogEvent() = default; + + TargetChangeSyncLogEvent(const AbstractType& Type, const DWORD& ID, const AbstractType& TargetType, const DWORD& TargetID, unsigned int Caller, unsigned int Frame) + : Type(Type), ID(ID), TargetType(TargetType), TargetID(TargetID), SyncLogEvent(Caller, Frame) + { + } +}; + +struct MissionOverrideSyncLogEvent : SyncLogEvent +{ + AbstractType Type; + DWORD ID; + int Mission; + + MissionOverrideSyncLogEvent() : SyncLogEvent() { } + + MissionOverrideSyncLogEvent(const AbstractType& Type, const DWORD& ID, int Mission, unsigned int Caller, unsigned int Frame) + : Type(Type), ID(ID), Mission(Mission), SyncLogEvent(Caller, Frame) + { + } +}; + +struct AnimCreationSyncLogEvent : SyncLogEvent +{ + CoordStruct Coords; + + AnimCreationSyncLogEvent() : SyncLogEvent() { } + + AnimCreationSyncLogEvent(const CoordStruct& Coords, unsigned int Caller, unsigned int Frame) + : Coords(Coords), SyncLogEvent(Caller, Frame) + { + } +}; + +class SyncLogger +{ +private: + static SyncLogEventBuffer RNGCalls; + static SyncLogEventBuffer FacingChanges; + static SyncLogEventBuffer TargetChanges; + static SyncLogEventBuffer DestinationChanges; + static SyncLogEventBuffer MissionOverrides; + static SyncLogEventBuffer AnimCreations; + + static void WriteRNGCalls(FILE* const pLogFile, int frameDigits); + static void WriteFacingChanges(FILE* const pLogFile, int frameDigits); + static void WriteTargetChanges(FILE* const pLogFile, int frameDigits); + static void WriteDestinationChanges(FILE* const pLogFile, int frameDigits); + static void WriteMissionOverrides(FILE* const pLogFile, int frameDigits); + static void WriteAnimCreations(FILE* const pLogFile, int frameDigits); +public: + static bool HooksDisabled; + static int AnimCreations_HighestX; + static int AnimCreations_HighestY; + static int AnimCreations_HighestZ; + + static void AddRNGCallSyncLogEvent(Randomizer* pRandomizer, int type, unsigned int callerAddress, int min = 0, int max = 0); + static void AddFacingChangeSyncLogEvent(unsigned short facing, unsigned int callerAddress); + static void AddTargetChangeSyncLogEvent(AbstractClass* pObject, AbstractClass* pTarget, unsigned int callerAddress); + static void AddDestinationChangeSyncLogEvent(AbstractClass* pObject, AbstractClass* pTarget, unsigned int callerAddress); + static void AddMissionOverrideSyncLogEvent(AbstractClass* pObject, int mission, unsigned int callerAddress); + static void AddAnimCreationSyncLogEvent(const CoordStruct& coords, unsigned int callerAddress); + static void WriteSyncLog(const char* logFilename); +}; diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index fa75714342..5d2bf8b2c9 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -852,7 +852,7 @@ int ShieldClass::DrawShieldBar_Pip(const bool isBuilding) int ShieldClass::DrawShieldBar_PipAmount(int iLength) { return this->IsActive() - ? Math::clamp((int)round(this->GetHealthRatio() * iLength), 0, iLength) + ? Math::clamp((int)round(this->GetHealthRatio() * iLength), 1, iLength) : 0; } @@ -906,5 +906,5 @@ void ShieldClass::SetAnimationVisibility(bool visible) if (!this->AreAnimsHidden && !visible) this->KillAnim(); - this->AreAnimsHidden = visible; + this->AreAnimsHidden = !visible; } diff --git a/src/Phobos.version.h b/src/Phobos.version.h index eed18d88a6..ed80cecb4b 100644 --- a/src/Phobos.version.h +++ b/src/Phobos.version.h @@ -18,7 +18,7 @@ #define VERSION_REVISION 0 // Indicates Phobos-related bugfixes only -#define VERSION_PATCH 0 +#define VERSION_PATCH 1 #pragma endregion diff --git a/src/Utilities/EnumFunctions.cpp b/src/Utilities/EnumFunctions.cpp index f28c1cda9c..7c0fd75a43 100644 --- a/src/Utilities/EnumFunctions.cpp +++ b/src/Utilities/EnumFunctions.cpp @@ -9,11 +9,11 @@ bool EnumFunctions::CanTargetHouse(AffectedHouse flags, HouseClass* ownerHouse, return (flags & AffectedHouse::Enemies) != AffectedHouse::None; } -bool EnumFunctions::IsCellEligible(CellClass* const pCell, AffectedTarget allowed, bool explicitEmptyCells) +bool EnumFunctions::IsCellEligible(CellClass* const pCell, AffectedTarget allowed, bool explicitEmptyCells, bool considerBridgesLand) { if (explicitEmptyCells) { - auto pTechno = pCell->FirstObject ? abstract_cast(pCell->FirstObject) : nullptr; + auto pTechno = pCell->GetContent() ? abstract_cast(pCell->GetContent()) : nullptr; if (!pTechno && !(allowed & AffectedTarget::NoContent)) return false; @@ -21,7 +21,7 @@ bool EnumFunctions::IsCellEligible(CellClass* const pCell, AffectedTarget allowe if (allowed & AffectedTarget::AllCells) { - if (pCell->LandType == LandType::Water) // check whether it supports water + if (pCell->LandType == LandType::Water && (!considerBridgesLand || !pCell->ContainsBridge())) // check whether it supports water return (allowed & AffectedTarget::Water) != AffectedTarget::None; else // check whether it supports non-water return (allowed & AffectedTarget::Land) != AffectedTarget::None; @@ -61,13 +61,13 @@ bool EnumFunctions::IsTechnoEligible(TechnoClass* const pTechno, AffectedTarget return allowed != AffectedTarget::None ? true : false; } -bool EnumFunctions::AreCellAndObjectsEligible(CellClass* const pCell, AffectedTarget allowed, AffectedHouse allowedHouses, HouseClass* owner, bool explicitEmptyCells, bool considerAircraftSeparately) +bool EnumFunctions::AreCellAndObjectsEligible(CellClass* const pCell, AffectedTarget allowed, AffectedHouse allowedHouses, HouseClass* owner, bool explicitEmptyCells, bool considerAircraftSeparately, bool allowBridges) { if (!pCell) return false; auto object = pCell->FirstObject; - bool eligible = EnumFunctions::IsCellEligible(pCell, allowed, explicitEmptyCells); + bool eligible = EnumFunctions::IsCellEligible(pCell, allowed, explicitEmptyCells, allowBridges); while (true) { diff --git a/src/Utilities/EnumFunctions.h b/src/Utilities/EnumFunctions.h index d61f8cc437..3a9a53269f 100644 --- a/src/Utilities/EnumFunctions.h +++ b/src/Utilities/EnumFunctions.h @@ -9,7 +9,7 @@ class EnumFunctions { public: static bool CanTargetHouse(AffectedHouse flags, HouseClass* ownerHouse, HouseClass* targetHouse); - static bool IsCellEligible(CellClass* const pCell, AffectedTarget allowed, bool explicitEmptyCells = false); + static bool IsCellEligible(CellClass* const pCell, AffectedTarget allowed, bool explicitEmptyCells = false, bool considerBridgesLand = false); static bool IsTechnoEligible(TechnoClass* const pTechno, AffectedTarget allowed, bool considerAircraftSeparately = false); - static bool AreCellAndObjectsEligible(CellClass* const pCell, AffectedTarget allowed, AffectedHouse allowedHouses, HouseClass* owner, bool explicitEmptyCells = false, bool considerAircraftSeparately = false); + static bool AreCellAndObjectsEligible(CellClass* const pCell, AffectedTarget allowed, AffectedHouse allowedHouses, HouseClass* owner, bool explicitEmptyCells = false, bool considerAircraftSeparately = false, bool allowBridges = false); }; diff --git a/src/Utilities/GeneralUtils.cpp b/src/Utilities/GeneralUtils.cpp index 5cd8750118..831e573869 100644 --- a/src/Utilities/GeneralUtils.cpp +++ b/src/Utilities/GeneralUtils.cpp @@ -134,3 +134,17 @@ bool GeneralUtils::ApplyTheaterSuffixToString(char* str) return false; } + + +int GeneralUtils::CountDigitsInNumber(int number) +{ + int digits = 0; + + while (number) + { + number /= 10; + digits++; + } + + return digits; +} \ No newline at end of file diff --git a/src/Utilities/GeneralUtils.h b/src/Utilities/GeneralUtils.h index 29de49dee5..bb588258ae 100644 --- a/src/Utilities/GeneralUtils.h +++ b/src/Utilities/GeneralUtils.h @@ -28,4 +28,5 @@ class GeneralUtils static double FastPow(double x, double n); static bool HasHealthRatioThresholdChanged(double oldRatio, double newRatio); static bool ApplyTheaterSuffixToString(char* str); + static int CountDigitsInNumber(int number); }; diff --git a/src/Utilities/Parser.h b/src/Utilities/Parser.h index 4475ed4c5e..a9a357934f 100644 --- a/src/Utilities/Parser.h +++ b/src/Utilities/Parser.h @@ -32,6 +32,8 @@ #pragma once +#include + //! Parses strings into one or more elements of another type. /*! \tparam T The type to convert to. @@ -228,10 +230,14 @@ static bool Parser::TryParse(const char* pValue, OutType* outValue) { template<> static bool Parser::TryParse(const char* pValue, OutType* outValue) { - double buffer = 0.0; - if (sscanf_s(pValue, "%lf", &buffer) == 1) { + + // Game doesn't use double precision when parsing, using double here would create inconsistency. + float buffer = 0.0; + + // Use game's sscanf function, the C library one has different precision/rounding. + if (CRT::sscanf(pValue, "%f", &buffer) == 1) { if (strchr(pValue, '%')) { - buffer *= 0.01; + buffer *= 0.01f; } if (outValue) { *outValue = buffer; diff --git a/src/Utilities/Patch.cpp b/src/Utilities/Patch.cpp index e55d65706a..ccad32f712 100644 --- a/src/Utilities/Patch.cpp +++ b/src/Utilities/Patch.cpp @@ -1,4 +1,5 @@ #include "Patch.h" +#include "Macro.h" #include int GetSection(const char* sectionName, void** pVirtualAddress) @@ -47,3 +48,37 @@ void Patch::Apply() memcpy(pAddress, this->pData, this->size); VirtualProtect(pAddress, this->size, protect_flag, NULL); } + +void Patch::Apply_RAW(DWORD offset, std::initializer_list data) +{ + Patch patch = { offset, data.size(), const_cast(data.begin()) }; + patch.Apply(); +} + +void Patch::Apply_LJMP(DWORD offset, DWORD pointer) +{ + const _LJMP data(offset, pointer); + Patch patch = { offset, sizeof(data), (byte*)&data }; + patch.Apply(); +} + +void Patch::Apply_CALL(DWORD offset, DWORD pointer) +{ + const _CALL data(offset, pointer); + Patch patch = { offset, sizeof(data), (byte*)&data }; + patch.Apply(); +} + +void Patch::Apply_CALL6(DWORD offset, DWORD pointer) +{ + const _CALL6 data(offset, pointer); + Patch patch = { offset, sizeof(data), (byte*)&data }; + patch.Apply(); +} + +void Patch::Apply_VTABLE(DWORD offset, DWORD pointer) +{ + const _VTABLE data(offset, pointer); + Patch patch = { offset, sizeof(data), (byte*)&data }; + patch.Apply(); +} diff --git a/src/Utilities/Patch.h b/src/Utilities/Patch.h index 5b75506698..3672307526 100644 --- a/src/Utilities/Patch.h +++ b/src/Utilities/Patch.h @@ -1,5 +1,6 @@ #pragma once #include +#include // no more than 8 characters #define PATCH_SECTION_NAME ".patch" @@ -7,16 +8,43 @@ #pragma pack(push, 1) #pragma warning(push) -#pragma warning( disable : 4324) -struct __declspec(novtable) -Patch +#pragma warning(disable : 4324) +struct __declspec(novtable) Patch { - unsigned int offset; - unsigned int size; + DWORD offset; + DWORD size; byte* pData; - static void ApplyStatic(); void Apply(); + + // static + static void ApplyStatic(); + + static void Apply_RAW(DWORD offset, std::initializer_list data); + + static void Apply_LJMP(DWORD offset, DWORD pointer); + static inline void Apply_LJMP(DWORD offset, void* pointer) + { + Apply_LJMP(offset, reinterpret_cast(pointer)); + }; + + static void Apply_CALL(DWORD offset, DWORD pointer); + static inline void Apply_CALL(DWORD offset, void* pointer) + { + Apply_CALL(offset, reinterpret_cast(pointer)); + }; + + static void Apply_CALL6(DWORD offset, DWORD pointer); + static inline void Apply_CALL6(DWORD offset, void* pointer) + { + Apply_CALL6(offset, reinterpret_cast(pointer)); + }; + + static void Apply_VTABLE(DWORD offset, DWORD pointer); + static inline void Apply_VTABLE(DWORD offset, void* pointer) + { + Apply_VTABLE(offset, reinterpret_cast(pointer)); + }; }; #pragma warning(pop) #pragma pack(pop)