diff --git a/.github/actions/build-phobos/action.yml b/.github/actions/build-phobos/action.yml index 87bf5079cc..d818a510a3 100644 --- a/.github/actions/build-phobos/action.yml +++ b/.github/actions/build-phobos/action.yml @@ -26,7 +26,7 @@ runs: - name: Upload Artifact if: ${{success()}} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: compiled-dll-${{github.sha}} path: | diff --git a/CREDITS.md b/CREDITS.md index 2825b42f12..e076045ae9 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -353,6 +353,8 @@ This page lists all the individual contributions to the project by their author. - Re-enable the Veinhole Monster and Weeds from TS - Recreate the weed-charging of SWs like the TS Chemical Missile - Allow to change the speed of gas particles +- **CrimRecya** + - Fix `LimboKill` not working reliably - **Ollerus** - Build limit group enhancement - Customizable rocker amplitude diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 13392a53c7..f5f3cabe27 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -165,6 +165,8 @@ This page describes all ingame logics that are fixed or improved in Phobos witho - Building upgrades now consistently use building's `PowerUpN` animation settings corresponding to the upgrade's `PowersUpToLevel` where possible. - Subterranean units are no longer allowed to perform deploy functions like firing weapons or `IsSimpleDeployer` while burrowed or burrowing, they will instead emerge first like they do for transport unloading. - The otherwise unused setting `[AI]` -> `PowerSurplus` (defaults to 50) which determines how much surplus power AI players will strive to have can be restored by setting `[AI]` -> `EnablePowerSurplus` to true. +- Planning paths are now shown for all units under player control or when `[GlobalControls]->DebugPlanningPaths=yes` in singleplayer game modes. +- Fixed `Temporal=true` Warheads potentially crashing game if used to attack `Slaved=true` infantry. ## Fixes / interactions with other extensions @@ -1253,7 +1255,7 @@ DecloakDamagedTargets=true ; boolean - You can now make Warheads behave in nonprovocative fashion. Warheads with `Nonprovocative=true` exhibit following behaviours: - They will not generate any EVA announcements upon hitting targets, be it for attacking ore miners, base buildings or ally base buildings. - - They will not spring 'attacked' / 'attacked by' events. Note that if the Warhead deals actual damage, events that check for that can still be sprung. + - They will not spring 'attacked' / 'attacked by' events. Note that if the Warhead deals actual damage, events that check for that can still be sprung. - They will not evoke defense response from AI players when used to attack base buildings, `ToProtect=true` TechnoTypes or members of TeamTypes with `Whiner=true`. - They will not evoke retaliation from TechnoTypes hit by the Warhead. @@ -1333,6 +1335,16 @@ In `rulesmd.ini` KickOutPassengers=true ; boolean ``` +### Disable FireOnce resetting infantry sequence + +- It is now possible to disable `FireOnce=true` weapon resetting infantry sequences after firing via `FireOnce.ResetSequence`. Target will be forgotten like before, the firing sequence will simply continue playing after firing if there are any frames left. + +In `rulesmd.ini` +```ini +[SOMEWEAPON] +FireOnce.ResetSequence=true ; boolean +``` + ### Single-color lasers ![image](_static/images/issinglecolor.gif) diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index cc2ecbac48..ad028a0dc8 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -78,7 +78,7 @@ This page describes all the engine features that are either new and introduced b - `AttachEffect.(Required|Disallowed)MinCounts & (Required|Disallowed)MaxCounts` can be used to set the minimum and maximum number of instances required / disallowed to be on the Techno for `Cumulative=true` types (ignored for other types) respectively. - `AttachEffect.IgnoreFromSameSource` can be set to true to ignore effects that have been attached by the firer of the weapon and its Warhead. - `AttachEffect.CheckOnFirer` is set to true makes it so that the required / disallowed attached effects are checked from the firer of the weapon instead of the target. - + In `rulesmd.ini`: ```ini [AttachEffectTypes] @@ -348,6 +348,8 @@ Shield.AbsorbPercent= ; floating point value Shield.PassPercent= ; floating point value Shield.ReceivedDamage.Minimum= ; integer Shield.ReceivedDamage.Maximum= ; integer +Shield.ReceivedDamage.MinMultiplier=1.0 ; floating point value +Shield.ReceivedDamage.MaxMultiplier=1.0 ; floating point value Shield.Respawn.Duration=0 ; integer, game frames Shield.Respawn.Amount=0.0 ; floating point value, percents or absolute Shield.Respawn.Rate=-1.0 ; floating point value, ingame minutes @@ -423,6 +425,7 @@ Shield.InheritStateOnReplace=false ; boolean - `Shield.AbsorbPercent` overrides the `AbsorbPercent` value set in the ShieldType that is being damaged. - `Shield.PassPercent` overrides the `PassPercent` value set in the ShieldType that is being damaged. - `Shield.ReceivedDamage.Minimum` & `Shield.ReceivedDamage.Maximum` override the values set in in the ShieldType that is being damaged. + - `Shield.ReceivedDamage.MinMultiplier` and `Shield.ReceivedDamage.MinMultiplier` are multipliers to the effective `Shield.ReceivedDamage.Minimum` and `Shield.ReceivedDamage.Maximum` respectively that are applied when the Warhead deals damage to a shield. - `Shield.Respawn.Rate` & `Shield.Respawn.Amount` override ShieldType `Respawn.Rate` and `Respawn.Amount` for duration of `Shield.Respawn.Duration` amount of frames. Negative rate & zero or lower amount default to ShieldType values. If `Shield.Respawn.RestartTimer` is set, currently running shield respawn timer is reset, otherwise the timer's duration is adjusted in proportion to the new `Shield.Respawn.Rate` (e.g timer will be same percentage through before and after) without restarting the timer. If the effect expires while respawn timer is running, remaining time is adjusted to proportionally match ShieldType `Respawn.Rate`. Re-applying the effect resets the duration to `Shield.Respawn.Duration` - `Shield.SelfHealing.Rate` & `Shield.SelfHealing.Amount` override ShieldType `SelfHealing.Rate` and `SelfHealing.Amount` for duration of `Shield.SelfHealing.Duration` amount of frames. Negative rate & zero or lower amount default to ShieldType values. If `Shield.SelfHealing.RestartTimer` is set, currently running self-healing timer is restarted, otherwise timer's duration is adjusted in proportion to the new `Shield.SelfHealing.Rate` (e.g timer will be same percentage through before and after) without restarting the timer. If the effect expires while self-healing timer is running, remaining time is adjusted to proportionally match ShieldType `SelfHealing.Rate`. Re-applying the effect resets the duration to `Shield.SelfHealing.Duration`. - Additionally `Shield.SelfHealing.RestartInCombat` & `Shield.SelfHealing.RestartInCombatDelay` can be used to override ShieldType settings. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 9ed71e3024..86c16799d7 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -429,7 +429,7 @@ New: - Promotion animation (by Fryone) - Allow different technos to share build limit in a group (by ststl & Ollerus) - Map events `604-605` for checking if a specific Techno enters in a cell (by FS-21) -- Waypoint path is drawn for all units, even those not under player control if `DebugKeysEnabled=yes` (by Trsdy) +- Waypoint path is drawn for all units under player control or if `[GlobalControls]->DebugPlanningPaths=yes` (by Trsdy) - `RemoveDisguise` now works on vehicle disguises (by Trsdy) - Allow anchoring extended tooltips to the left side of the sidebar (by Trsdy) - Toggle to allow spawned aircraft to attack immediately after being spawned (by Starkku) @@ -450,6 +450,7 @@ New: - Forbidding parallel AI queues for specific TechnoTypes (by Starkku) - Nonprovocative Warheads (by Starkku) - Option to restore `PowerSurplus` setting for AI (by Starkku) +- `FireOnce` infantry sequence reset toggle (by Starkku) - Minimum & maximum values for received damage and speed (by Ollerus) Vanilla fixes: @@ -522,7 +523,7 @@ Vanilla fixes: - Fixed disguised units not using the correct palette if target has custom palette (by NetsuNegi) - Building upgrades now consistently use building's `PowerUpN` animation settings corresponding to the upgrade's `PowersUpToLevel` where possible (by Starkku) - Subterranean units are no longer allowed to perform deploy functions like firing weapons or `IsSimpleDeployer` while burrowed or burrowing, they will instead emerge first like they do for transport unloading (by Starkku) -- Subterranean units no longer draw an incorrectly positioned shadow when burrowing etc. (by Starkku) +- Fixed `Temporal=true` Warheads potentially crashing game if used to attack `Slaved=true` infantry (by Starkku) Phobos fixes: - Fixed a few errors of calling for superweapon launch by `LaunchSW` or building infiltration (by Trsdy) @@ -564,6 +565,7 @@ Phobos fixes: - Fixed frame by frame hotkey description to read `TXT_FRAME_BY_FRAME_DESC` instead of `TXT_DISPLAY_DAMAGE_DESC` (by DeathFishAtEase) - Buildings considered vehicles (`ConsideredVehicle=true` or not set in conjunction with `UndeploysInto` & 1x1 foundation) are now considered units by affected target enum checks (by Starkku) - Fixed Phobos Warhead effects not reliably being applied on damage area as opposed to full weapon-based Warhead detonation (by Starkku) +- Fix `LimboKill` not working reliably (by CrimRecya) Fixes / interactions with other extensions: - `IsSimpleDeployer` units with Hover locomotor and `DeployToLand` no longer get stuck after deploying or play their move sound indefinitely (by Starkku) diff --git a/src/Ext/Anim/Body.cpp b/src/Ext/Anim/Body.cpp index 68eb7bf94e..9fb253a058 100644 --- a/src/Ext/Anim/Body.cpp +++ b/src/Ext/Anim/Body.cpp @@ -281,10 +281,10 @@ DEFINE_HOOK_AGAIN(0x422126, AnimClass_CTOR, 0x5) DEFINE_HOOK_AGAIN(0x422707, AnimClass_CTOR, 0x5) DEFINE_HOOK(0x4228D2, AnimClass_CTOR, 0x5) { + GET(AnimClass*, pItem, ESI); + if (!Phobos::IsLoadingSaveGame) { - GET(AnimClass*, pItem, ESI); - auto const callerAddress = CTORTemp::callerAddress; // Do this here instead of using a duplicate hook in SyncLogger.cpp @@ -296,10 +296,10 @@ DEFINE_HOOK(0x4228D2, AnimClass_CTOR, 0x5) Debug::Log("Attempting to create animation with null Type (Caller: %08x)!\n", callerAddress); return 0; } - - AnimExt::ExtMap.Allocate(pItem); } + AnimExt::ExtMap.Allocate(pItem); + return 0; } diff --git a/src/Ext/SWType/FireSuperWeapon.cpp b/src/Ext/SWType/FireSuperWeapon.cpp index 519f39d4a8..2241a2e409 100644 --- a/src/Ext/SWType/FireSuperWeapon.cpp +++ b/src/Ext/SWType/FireSuperWeapon.cpp @@ -120,20 +120,6 @@ inline void LimboCreate(BuildingTypeClass* pType, HouseClass* pOwner, int ID) } } -inline void LimboDelete(BuildingClass* pBuilding, HouseClass* pTargetHouse) -{ - auto pOwnerExt = HouseExt::ExtMap.Find(pTargetHouse); - - // Remove building from list of owned limbo buildings - auto& vec = pOwnerExt->OwnedLimboDeliveredBuildings; - vec.erase(std::remove(vec.begin(), vec.end(), pBuilding), vec.end()); - - pBuilding->Stun(); - pBuilding->Limbo(); - pBuilding->RegisterDestruction(nullptr); - pBuilding->UnInit(); -} - void SWTypeExt::ExtData::ApplyLimboDelivery(HouseClass* pHouse) { // random mode @@ -174,13 +160,25 @@ void SWTypeExt::ExtData::ApplyLimboKill(HouseClass* pHouse) if (EnumFunctions::CanTargetHouse(this->LimboKill_Affected, pHouse, pTargetHouse)) { auto const pHouseExt = HouseExt::ExtMap.Find(pTargetHouse); + auto& vec = pHouseExt->OwnedLimboDeliveredBuildings; - for (const auto& pBuilding : pHouseExt->OwnedLimboDeliveredBuildings) + for (auto it = vec.begin(); it != vec.end(); ) { + BuildingClass* const pBuilding = *it; auto const pBuildingExt = BuildingExt::ExtMap.Find(pBuilding); if (pBuildingExt->LimboID == limboKillID) - LimboDelete(pBuilding, pTargetHouse); + { + it = vec.erase(it); + pBuilding->Stun(); + pBuilding->Limbo(); + pBuilding->RegisterDestruction(nullptr); + pBuilding->UnInit(); + } + else + { + ++it; + } } } } diff --git a/src/Ext/Techno/Body.Update.cpp b/src/Ext/Techno/Body.Update.cpp index 3cf902f5bd..4f6cd764fb 100644 --- a/src/Ext/Techno/Body.Update.cpp +++ b/src/Ext/Techno/Body.Update.cpp @@ -192,7 +192,7 @@ bool TechnoExt::ExtData::CheckDeathConditions(bool isInLimbo) // death if listed technos exist if (!pTypeExt->AutoDeath_TechnosExist.empty()) { - if (existTechnoTypes(pTypeExt->AutoDeath_TechnosExist, pTypeExt->AutoDeath_TechnosExist_Houses, pTypeExt->AutoDeath_TechnosExist_Any, pTypeExt->AutoDeath_TechnosDontExist_AllowLimboed)) + if (existTechnoTypes(pTypeExt->AutoDeath_TechnosExist, pTypeExt->AutoDeath_TechnosExist_Houses, pTypeExt->AutoDeath_TechnosExist_Any, pTypeExt->AutoDeath_TechnosExist_AllowLimboed)) { TechnoExt::KillSelf(pThis, howToDie, pVanishAnim, isInLimbo); diff --git a/src/Ext/Techno/Body.cpp b/src/Ext/Techno/Body.cpp index 82368ad6e2..2a0d4bf97d 100644 --- a/src/Ext/Techno/Body.cpp +++ b/src/Ext/Techno/Body.cpp @@ -489,6 +489,7 @@ void TechnoExt::ExtData::Serialize(T& Stm) .Process(this->IsBurrowed) .Process(this->HasBeenPlacedOnMap) .Process(this->DeployFireTimer) + .Process(this->SkipTargetChangeResetSequence) .Process(this->ForceFullRearmDelay) .Process(this->CanCloakDuringRearm) .Process(this->WHAnimRemainingCreationInterval) diff --git a/src/Ext/Techno/Body.h b/src/Ext/Techno/Body.h index 39e777208f..0a90b4f688 100644 --- a/src/Ext/Techno/Body.h +++ b/src/Ext/Techno/Body.h @@ -42,6 +42,7 @@ class TechnoExt bool IsBurrowed; bool HasBeenPlacedOnMap; // Set to true on first Unlimbo() call. CDTimerClass DeployFireTimer; + bool SkipTargetChangeResetSequence; bool ForceFullRearmDelay; bool CanCloakDuringRearm; // Current rearm timer was started by DecloakToFire=no weapon. int WHAnimRemainingCreationInterval; @@ -75,6 +76,7 @@ class TechnoExt , IsBurrowed { false } , HasBeenPlacedOnMap { false } , DeployFireTimer {} + , SkipTargetChangeResetSequence { false } , ForceFullRearmDelay { false } , CanCloakDuringRearm { false } , WHAnimRemainingCreationInterval { 0 } diff --git a/src/Ext/Techno/Hooks.AttachEffect.cpp b/src/Ext/Techno/Hooks.AttachEffect.cpp index 47e35d35ce..f42f36ddb3 100644 --- a/src/Ext/Techno/Hooks.AttachEffect.cpp +++ b/src/Ext/Techno/Hooks.AttachEffect.cpp @@ -30,7 +30,7 @@ DEFINE_HOOK(0x701966, TechnoClass_ArmorMultiplier, 0x6) // TechnoClass_Rec damage = static_cast(damage / pExt->AE.ArmorMultiplier) - pTypeExt->ArmorBonus - pExt->AE.ArmorBonus; if (pShieldData && pShieldData->IsActive()) - damage -= pShieldData->ArmorBonus; + damage -= pShieldData->GetType()->ArmorBonus; R->EAX(damage); diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 3b7840ac4a..eb5db6ecb2 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -509,6 +509,22 @@ DEFINE_HOOK(0x6FF43F, TechnoClass_FireAt_FeedbackWeapon, 0x6) return 0; } +DEFINE_HOOK(0x6FF905, TechnoClass_FireAt_FireOnce, 0x6) +{ + GET(TechnoClass*, pThis, ESI); + + if (auto const pInf = abstract_cast(pThis)) + { + GET(WeaponTypeClass*, pWeapon, EBX); + auto const pWeaponExt = WeaponTypeExt::ExtMap.Find(pWeapon); + + if (!pWeaponExt->FireOnce_ResetSequence) + TechnoExt::ExtMap.Find(pInf)->SkipTargetChangeResetSequence = true; + } + + return 0; +} + DEFINE_HOOK(0x6FF660, TechnoClass_FireAt_Interceptor, 0x6) { GET(TechnoClass* const, pSource, ESI); diff --git a/src/Ext/Techno/Hooks.cpp b/src/Ext/Techno/Hooks.cpp index e2c8c22264..5ecf15359d 100644 --- a/src/Ext/Techno/Hooks.cpp +++ b/src/Ext/Techno/Hooks.cpp @@ -596,6 +596,21 @@ DEFINE_HOOK(0x6F9FA9, TechnoClass_AI_PromoteAnim, 0x6) return aresProcess(); } -// TunnelLocomotionClass_IsToHaveShadow, skip shadow on all but idle. -// TODO: Investigate if it is possible to fix the shadows not tilting on the burrowing etc. states. -DEFINE_JUMP(LJMP, 0x72A070, 0x72A07F); +DEFINE_HOOK(0x51B20E, InfantryClass_AssignTarget_FireOnce, 0x6) +{ + enum { SkipGameCode = 0x51B255 }; + + GET(InfantryClass*, pThis, ESI); + GET(AbstractClass*, pTarget, EBX); + + auto const pExt = TechnoExt::ExtMap.Find(pThis); + + if (!pTarget && pExt->SkipTargetChangeResetSequence) + { + pThis->IsFiring = false; + pExt->SkipTargetChangeResetSequence = false; + return SkipGameCode; + } + + return 0; +} diff --git a/src/Ext/TechnoType/Hooks.cpp b/src/Ext/TechnoType/Hooks.cpp index e177510592..cfd9a1b36a 100644 --- a/src/Ext/TechnoType/Hooks.cpp +++ b/src/Ext/TechnoType/Hooks.cpp @@ -21,6 +21,7 @@ #include #include #include +#include DEFINE_HOOK(0x6F64A9, TechnoClass_DrawHealthBar_Hide, 0x5) { @@ -301,6 +302,60 @@ constexpr double Pade2_2(double in) * (12. - 6 * s + s * s) / (12. + 6 * s + s * s); } +// We need to handle Ares turrets/barrels/waterimage/nospawnalt +struct DummyExtHere // TODO: move it +{ + char _[0xA4]; + std::vector ChargerTurrets; + std::vector ChargerBarrels; + char __[0x120]; + UnitTypeClass* WaterImage; + VoxelStruct NoSpawnAltVXL; +}; + +Matrix3D* __stdcall TunnelLocomotionClass_ShadowMatrix(ILocomotion* iloco, Matrix3D* ret,VoxelIndexKey* key) +{ + __assume(iloco != nullptr); + auto tLoco = static_cast(iloco); + *ret = tLoco->LocomotionClass::Shadow_Matrix(key); + if (tLoco->State != TunnelLocomotionClass::State::Idle) + { + double theta = 0.; + switch (tLoco->State) + { + case TunnelLocomotionClass::State::DiggingIn: + if (key)key->Invalidate(); + theta = Math::HalfPi; + if (auto total = tLoco->DigTimer.Rate) + theta *= 1.0 - double(tLoco->DigTimer.GetTimeLeft()) / double(total); + break; + case TunnelLocomotionClass::State::DugIn: + theta = Math::HalfPi; + break; + case TunnelLocomotionClass::State::PreDigOut: + theta = -Math::HalfPi; + break; + case TunnelLocomotionClass::State::DiggingOut: + if (key)key->Invalidate(); + theta = -Math::HalfPi; + if (auto total = tLoco->DigTimer.Rate) + theta *= double(tLoco->DigTimer.GetTimeLeft()) / double(total); + break; + case TunnelLocomotionClass::State::DugOut: + if (key)key->Invalidate(); + theta = Math::HalfPi; + if (auto total = tLoco->DigTimer.Rate) + theta *= double(tLoco->DigTimer.GetTimeLeft()) / double(total); + break; + default:break; + } + ret->ScaleX((float)Math::cos(theta));// I know it's ugly + } + return ret; +} + +DEFINE_JUMP(VTABLE, 0x7F5A4C, GET_OFFSET(TunnelLocomotionClass_ShadowMatrix)); + DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5) { GET(UnitClass*, pThis, EBP); @@ -355,17 +410,6 @@ DEFINE_HOOK(0x73C47A, UnitClass_DrawAsVXL_Shadow, 0x5) shadow_matrix.Scale((float)Pade2_2(baseScale_log)); } - // We need to handle Ares turrets/barrels - struct DummyExtHere - { - char _[0xA4]; - std::vector ChargerTurrets; - std::vector ChargerBarrels; - char __[0x120]; - UnitTypeClass* WaterImage; - VoxelStruct NoSpawnAltVXL; - }; - auto GetMainVoxel = [&]() { if (pType->NoSpawnAlt && pThis->SpawnManager && pThis->SpawnManager->CountDockedSpawns() == 0) @@ -636,7 +680,7 @@ DEFINE_JUMP(CALL6, 0x4148AB, 0x5F4300); DEFINE_JUMP(CALL6, 0x4147F3, 0x5F4300); */ -DEFINE_HOOK(0x7072A1, suka707280_ChooseTheGoddamnMatrix, 0x7) +DEFINE_HOOK(0x7072A1, suka707280_ChooseTheGoddamnMatrix, 0x6) { GET(FootClass*, pThis, EBX);//Maybe Techno later GET(VoxelStruct*, pVXL, EBP); @@ -660,9 +704,12 @@ DEFINE_HOOK(0x7072A1, suka707280_ChooseTheGoddamnMatrix, 0x7) if (who_are_you[0] == UnitTypeClass::AbsVTable) pType = reinterpret_cast(who_are_you);//you are someone else else - return pThis->TurretAnimFrame % hva->FrameCount; - // you might also be SpawnAlt voxel, but I can't know - // otherwise what would you expect me to do, shift back to ares typeext base and check if ownerobject is technotype? + { + // guess what, someone actually has a multisection nospawnalt + if (!(AresHelper::CanUseAres && pVXL == &reinterpret_cast(pType->align_2FC)->NoSpawnAltVXL)) + return pThis->TurretAnimFrame % hva->FrameCount; + } + // you might also be WaterImage or sth else, but I don't want to care anymore, go fuck yourself } // Main body sections diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index f94a1ba724..281a42af65 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -204,6 +204,8 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Shield_PassPercent.Read(exINI, pSection, "Shield.PassPercent"); this->Shield_ReceivedDamage_Minimum.Read(exINI, pSection, "Shield.ReceivedDamage.Minimum"); this->Shield_ReceivedDamage_Maximum.Read(exINI, pSection, "Shield.ReceivedDamage.Maximum"); + this->Shield_ReceivedDamage_MinMultiplier.Read(exINI, pSection, "Shield.ReceivedDamage.MinMultiplier"); + this->Shield_ReceivedDamage_MaxMultiplier.Read(exINI, pSection, "Shield.ReceivedDamage.MaxMultiplier"); this->Shield_Respawn_Duration.Read(exINI, pSection, "Shield.Respawn.Duration"); this->Shield_Respawn_Amount.Read(exINI, pSection, "Shield.Respawn.Amount"); this->Shield_Respawn_Rate_InMinutes.Read(exINI, pSection, "Shield.Respawn.Rate"); @@ -415,6 +417,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Shield_PassPercent) .Process(this->Shield_ReceivedDamage_Minimum) .Process(this->Shield_ReceivedDamage_Maximum) + .Process(this->Shield_ReceivedDamage_MinMultiplier) + .Process(this->Shield_ReceivedDamage_MaxMultiplier) .Process(this->Shield_Respawn_Duration) .Process(this->Shield_Respawn_Amount) .Process(this->Shield_Respawn_Rate) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 39810cc886..90b9968f7d 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -79,6 +79,8 @@ class WarheadTypeExt Nullable Shield_PassPercent; Nullable Shield_ReceivedDamage_Minimum; Nullable Shield_ReceivedDamage_Maximum; + Valueable Shield_ReceivedDamage_MinMultiplier; + Valueable Shield_ReceivedDamage_MaxMultiplier; Valueable Shield_Respawn_Duration; Nullable Shield_Respawn_Amount; @@ -227,6 +229,8 @@ class WarheadTypeExt , Shield_PassPercent {} , Shield_ReceivedDamage_Minimum {} , Shield_ReceivedDamage_Maximum {} + , Shield_ReceivedDamage_MinMultiplier { 1.0 } + , Shield_ReceivedDamage_MaxMultiplier { 1.0 } , Shield_Respawn_Duration { 0 } , Shield_Respawn_Amount { 0.0 } diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index f3c45295bb..ad123810c6 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -95,6 +95,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Laser_IsSingleColor.Read(exINI, pSection, "IsSingleColor"); this->ROF_RandomDelay.Read(exINI, pSection, "ROF.RandomDelay"); this->OmniFire_TurnToTarget.Read(exINI, pSection, "OmniFire.TurnToTarget"); + this->FireOnce_ResetSequence.Read(exINI, pSection, "FireOnce.ResetSequence"); this->ExtraWarheads.Read(exINI, pSection, "ExtraWarheads"); this->ExtraWarheads_DamageOverrides.Read(exINI, pSection, "ExtraWarheads.DamageOverrides"); this->ExtraWarheads_DetonationChances.Read(exINI, pSection, "ExtraWarheads.DetonationChances"); @@ -137,6 +138,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->Laser_IsSingleColor) .Process(this->ROF_RandomDelay) .Process(this->OmniFire_TurnToTarget) + .Process(this->FireOnce_ResetSequence) .Process(this->ExtraWarheads) .Process(this->ExtraWarheads_DamageOverrides) .Process(this->ExtraWarheads_DetonationChances) diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 7f70011e83..79da4f0b8f 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -40,6 +40,7 @@ class WeaponTypeExt Valueable Laser_IsSingleColor; Nullable> ROF_RandomDelay; Valueable OmniFire_TurnToTarget; + Valueable FireOnce_ResetSequence; ValueableVector ExtraWarheads; ValueableVector ExtraWarheads_DamageOverrides; ValueableVector ExtraWarheads_DetonationChances; @@ -78,6 +79,7 @@ class WeaponTypeExt , Laser_IsSingleColor { false } , ROF_RandomDelay {} , OmniFire_TurnToTarget { false } + , FireOnce_ResetSequence { true } , ExtraWarheads {} , ExtraWarheads_DamageOverrides {} , ExtraWarheads_DetonationChances {} diff --git a/src/Misc/Hooks.BugFixes.cpp b/src/Misc/Hooks.BugFixes.cpp index 86892cb36a..8cd3086aa3 100644 --- a/src/Misc/Hooks.BugFixes.cpp +++ b/src/Misc/Hooks.BugFixes.cpp @@ -934,3 +934,16 @@ DEFINE_HOOK(0x705D74, TechnoClass_GetRemapColour_DisguisePalette, 0x8) return SkipGameCode; } + +// Fixes an edge case crash caused by temporal targeting enslaved infantry. +DEFINE_HOOK(0x71ADE4, TemporalClass_Release_SlaveTargetFix, 0x5) +{ + enum { ReturnFromFunction = 0x71AE47 }; + + GET(TemporalClass* const, pThis, ESI); + + if (!pThis->Target) + return ReturnFromFunction; + + return 0; +} diff --git a/src/Misc/Hooks.UI.cpp b/src/Misc/Hooks.UI.cpp index 95d64b576c..f482ac8dc2 100644 --- a/src/Misc/Hooks.UI.cpp +++ b/src/Misc/Hooks.UI.cpp @@ -377,7 +377,7 @@ DEFINE_HOOK(0x604985, GetDialogUIStatusLabels_ShowBriefing, 0x5) bool __fastcall Fake_HouseIsAlliedWith(HouseClass* pThis, void*, HouseClass* CurrentPlayer) { - return Phobos::Config::DevelopmentCommands + return (Phobos::Config::ShowPlanningPath && SessionClass::IsSingleplayer()) || pThis->IsControlledByCurrentPlayer() || pThis->IsAlliedWith(CurrentPlayer); } diff --git a/src/New/Entity/ShieldClass.cpp b/src/New/Entity/ShieldClass.cpp index 241a432dcb..4d105f7809 100644 --- a/src/New/Entity/ShieldClass.cpp +++ b/src/New/Entity/ShieldClass.cpp @@ -189,9 +189,11 @@ int ShieldClass::ReceiveDamage(args_ReceiveDamage* args) } int originalShieldDamage = shieldDamage; - - shieldDamage = Math::clamp(shieldDamage, pWHExt->Shield_ReceivedDamage_Minimum.Get(this->Type->ReceivedDamage_Minimum), - pWHExt->Shield_ReceivedDamage_Maximum.Get(this->Type->ReceivedDamage_Maximum)); + int min = pWHExt->Shield_ReceivedDamage_Minimum.Get(this->Type->ReceivedDamage_Minimum); + int max = pWHExt->Shield_ReceivedDamage_Maximum.Get(this->Type->ReceivedDamage_Maximum); + int minDmg = static_cast(min * pWHExt->Shield_ReceivedDamage_MinMultiplier); + int maxDmg = static_cast(max * pWHExt->Shield_ReceivedDamage_MaxMultiplier); + shieldDamage = Math::clamp(shieldDamage, minDmg, maxDmg); if (Phobos::DisplayDamageNumbers && shieldDamage != 0) GeneralUtils::DisplayDamageNumberString(shieldDamage, DamageDisplayType::Shield, this->Techno->GetRenderCoords(), TechnoExt::ExtMap.Find(this->Techno)->DamageNumberOffset); diff --git a/src/Phobos.INI.cpp b/src/Phobos.INI.cpp index fdf0992d42..daeda05cca 100644 --- a/src/Phobos.INI.cpp +++ b/src/Phobos.INI.cpp @@ -38,6 +38,7 @@ bool Phobos::Config::ToolTipDescriptions = true; bool Phobos::Config::ToolTipBlur = false; bool Phobos::Config::PrioritySelectionFiltering = true; bool Phobos::Config::DevelopmentCommands = true; +bool Phobos::Config::ShowPlanningPath = false; bool Phobos::Config::ArtImageSwap = false; bool Phobos::Config::ShowPlacementPreview = false; bool Phobos::Config::DigitalDisplay_Enable = false; @@ -211,6 +212,8 @@ DEFINE_HOOK(0x52D21F, InitRules_ThingsThatShouldntBeSerailized, 0x6) #ifndef DEBUG Phobos::Config::DevelopmentCommands = pINI_RULESMD->ReadBool("GlobalControls", "DebugKeysEnabled", Phobos::Config::DevelopmentCommands); #endif + Phobos::Config::ShowPlanningPath = pINI_RULESMD->ReadBool("GlobalControls", "DebugPlanningPaths", Phobos::Config::ShowPlanningPath); + return 0; } diff --git a/src/Phobos.h b/src/Phobos.h index 49d25200c6..26e0493112 100644 --- a/src/Phobos.h +++ b/src/Phobos.h @@ -90,6 +90,7 @@ class Phobos static bool ShowPowerDelta; static bool ShowHarvesterCounter; static bool ShowWeedsCounter; + static bool ShowPlanningPath; }; class Misc diff --git a/src/Utilities/Container.h b/src/Utilities/Container.h index 6ca0002fd6..6a1a343285 100644 --- a/src/Utilities/Container.h +++ b/src/Utilities/Container.h @@ -286,6 +286,7 @@ class Container map_type Items; base_type* SavingObject; + extension_type_ptr SavingExtPointer; IStream* SavingStream; const char* Name; @@ -361,10 +362,6 @@ class Container extension_type_ptr TryAllocate(base_type_ptr key, bool bCond, const std::string_view& nMessage) { - // Do not allow allocation when loading save games. - if (Phobos::IsLoadingSaveGame) - return nullptr; - if (!key || (!bCond && !nMessage.empty())) { Debug::Log("%s \n", nMessage.data()); @@ -376,10 +373,6 @@ class Container extension_type_ptr TryAllocate(base_type_ptr key) { - // Do not allow allocation when loading save games. - if (Phobos::IsLoadingSaveGame) - return nullptr; - if (!key) { Debug::Log("Attempted to allocate %s from nullptr!\n", typeid(extension_type).name()); @@ -400,6 +393,22 @@ class Container return this->Items.find(key); } + // Only used on loading, does not check if key is nullptr. + extension_type_ptr FindOrAllocate(base_type_ptr key) + { + extension_type_ptr value = nullptr; + + if constexpr (HasOffset) + value = GetExtensionPointer(key); + else + value = this->Items.find(key); + + if (!value) + value = Allocate(key); + + return value; + } + void Remove(base_type_ptr key) { if (auto Item = Find(key)) @@ -442,6 +451,10 @@ class Container this->SavingObject = key; this->SavingStream = pStm; + + // Loading the base type data might override the ext pointer stored on it so it needs to be saved. + if constexpr (HasOffset) + this->SavingExtPointer = GetExtensionPointer(key); } void SaveStatic() @@ -466,6 +479,10 @@ class Container { if (this->SavingObject && this->SavingStream) { + // Restore stored ext pointer data. + if constexpr (HasOffset) + SetExtensionPointer(this->SavingObject, this->SavingExtPointer); + //Debug::Log("[LoadStatic] Loading object %p as '%s'\n", this->SavingObject, this->Name); if (!this->Load(this->SavingObject, this->SavingStream)) Debug::FatalErrorAndExit("LoadStatic - Loading object %p as '%s' failed!\n", this->SavingObject, this->Name); @@ -550,8 +567,8 @@ class Container return nullptr; } - extension_type_ptr buffer = this->Allocate(key); - + // get or allocate the value data + extension_type_ptr buffer = this->FindOrAllocate(key); if (!buffer) { Debug::Log("LoadKey - Could not find or allocate value.\n");