diff --git a/CREDITS.md b/CREDITS.md index 170bc75fae..f4e54c6a86 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -292,6 +292,7 @@ This page lists all the individual contributions to the project by their author. - Fix vehicles disguised as trees incorrectly displaying veterancy insignia when they shouldn't - GapGen + SpySat desync fix - Frame CRC generation rewrite + - Berzerk duration stacking behaviour customization - **Morton (MortonPL)**: - `XDrawOffset` for animations - Shield passthrough & absorption diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 61cd727f28..78732d0d3d 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -2446,6 +2446,19 @@ AllowBerzerkOnAllies=false ; boolean No per-warhead setting because `AffectsAllies` etc. is respected. ``` +### Berzerk (Psychedelic) duration stacking customization + +- By default `Psychedelic` warheads override the current duration of the berzerk effect regardless of if the new duration is higher or lower than the current one. This can now be customized with `Psychedelic.StackingMode`, with both global setting under `[CombatDamage]` and per-Warhead customization. + +In `rulesmd.ini`: +```ini +[CombatDamage] +Psychedelic.StackingMode=override ; Stacking mode enum (override|setifzero|min|max|add|subtract|multiply|divide) + +[SOMEWARHEAD] ; WarheadType +Psychedelic.StackingMode= ; Stacking mode enum (override|setifzero|min|max|add|subtract|multiply|divide) +``` + ### Combat light customizations - You can now set minimum detail level at which combat light effects are shown by setting `[AudioVisual] -> CombatLightDetailLevel` or `CombatLightDetailLevel` on Warhead. diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 24b3b1c159..307a11ff68 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -576,6 +576,7 @@ New: - Customize `HarvesterLoadRate` (by Noble_Fish) - [Toggle to prevent `ShrapnelWeapon` from targeting buildings multiple times](Fixed-or-Improved-Logics.md#shrapnel-enhancements) (by Starkku) - [Laser drawing Z-adjust customization](Fixed-or-Improved-Logics.md#laser-z-adjust) (by Starkku) +- [Berzerk / `Psychedelic` duration stacking customization](Fixed-or-Improved-Logics.md#berzerk-psychedelic-duration-stacking-customization) (by Starkku) Vanilla fixes: - Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya) diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index 260e0f7843..2f3b036405 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -400,6 +400,7 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->ExtendedPlayerRepair.Read(exINI, GameStrings::General, "ExtendedPlayerRepair"); this->Shrapnel_IgnoreHitBuildings.Read(exINI, GameStrings::CombatDamage, "Shrapnel.IgnoreHitBuildings"); + this->Psychedelic_StackingMode.Read(exINI, GameStrings::CombatDamage, "Psychedelic.StackingMode"); // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); @@ -726,6 +727,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->FiringAnim_Update) .Process(this->ExtendedPlayerRepair) .Process(this->Shrapnel_IgnoreHitBuildings) + .Process(this->Psychedelic_StackingMode) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 17cd44fcdd..55cc630458 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -348,6 +348,8 @@ class RulesExt Valueable ShipLocomotorMakesWake; Valueable Shrapnel_IgnoreHitBuildings; + + Valueable Psychedelic_StackingMode; ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } @@ -636,7 +638,9 @@ class RulesExt , ShipLocomotorMakesWake { true } , FiringAnim_Update { false } , ExtendedPlayerRepair { false } + , Shrapnel_IgnoreHitBuildings { false } + , Psychedelic_StackingMode { StackingMode::Override } { } virtual ~ExtData() = default; diff --git a/src/Ext/WarheadType/Body.cpp b/src/Ext/WarheadType/Body.cpp index c2c000a655..a14b30c827 100644 --- a/src/Ext/WarheadType/Body.cpp +++ b/src/Ext/WarheadType/Body.cpp @@ -410,6 +410,8 @@ void WarheadTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->Taunt.Read(exINI, pSection, "Taunt"); + this->Psychedelic_StackingMode.Read(exINI, pSection, "Psychedelic.StackingMode"); + // Convert.From & Convert.To TypeConvertGroup::Parse(this->Convert_Pairs, exINI, pSection, AffectedHouse::All); @@ -742,6 +744,8 @@ void WarheadTypeExt::ExtData::Serialize(T& Stm) .Process(this->Taunt) + .Process(this->Psychedelic_StackingMode) + // Ares tags .Process(this->AffectsEnemies) .Process(this->AffectsOwner) diff --git a/src/Ext/WarheadType/Body.h b/src/Ext/WarheadType/Body.h index 88be830a6d..f536d50dd6 100644 --- a/src/Ext/WarheadType/Body.h +++ b/src/Ext/WarheadType/Body.h @@ -247,6 +247,8 @@ class WarheadTypeExt Valueable Taunt; + Nullable Psychedelic_StackingMode; + // Ares tags // http://ares-developers.github.io/Ares-docs/new/warheads/general.html Valueable AffectsEnemies; @@ -522,6 +524,8 @@ class WarheadTypeExt , ApplyPerTargetEffectsOnDetonate {} , Taunt { false } + + , Psychedelic_StackingMode {} { } void ApplyConvert(HouseClass* pHouse, TechnoClass* pTarget); diff --git a/src/Ext/WarheadType/Hooks.cpp b/src/Ext/WarheadType/Hooks.cpp index 2b59d709ac..80affa8c3c 100644 --- a/src/Ext/WarheadType/Hooks.cpp +++ b/src/Ext/WarheadType/Hooks.cpp @@ -665,3 +665,17 @@ DEFINE_HOOK(0x48DC90, MapClass_UnselectAll_ClearLimboLaunchers, 0x5) } #pragma endregion + +DEFINE_HOOK(0x701D6B, TechnoClass_ReceiveDamage_Psychedelic, 0x6) +{ + enum { SkipGameCode = 0x701D71 }; + + GET(TechnoClass*, pThis, ESI); + GET(WarheadTypeClass*, pWH, EBP); + GET(int, damage, EAX); + + auto const pWHExt = WarheadTypeExt::ExtMap.Find(pWH); + EnumFunctions::CalcValueWithStackingMode(pThis->BerzerkDurationLeft, damage, pWHExt->Psychedelic_StackingMode); + + return SkipGameCode; +} diff --git a/src/Utilities/Enum.h b/src/Utilities/Enum.h index 2109fdf943..97c1593467 100644 --- a/src/Utilities/Enum.h +++ b/src/Utilities/Enum.h @@ -243,6 +243,18 @@ enum class DamageDisplayType Intercept = 2 }; +enum class StackingMode +{ + Override = 0, + SetIfZero = 1, + Min = 2, + Max = 3, + Add = 4, + Subtract = 5, + Multiply = 6, + Divide = 7 +}; + enum class ChronoSparkleDisplayPosition : unsigned char { None = 0x0, diff --git a/src/Utilities/EnumFunctions.cpp b/src/Utilities/EnumFunctions.cpp index 2fc159e8c7..7288b8d5ae 100644 --- a/src/Utilities/EnumFunctions.cpp +++ b/src/Utilities/EnumFunctions.cpp @@ -1,5 +1,7 @@ #include "EnumFunctions.h" +#include + bool EnumFunctions::CanTargetHouse(AffectedHouse flags, HouseClass* ownerHouse, HouseClass* targetHouse) { if (flags == AffectedHouse::All) @@ -111,3 +113,48 @@ bool EnumFunctions::AreCellAndObjectsEligible(CellClass* const pCell, AffectedTa return true; } + +bool EnumFunctions::CalcValueWithStackingMode(int& oldValue, int newValue, StackingMode stackingMode) +{ + bool valueChanged = true; + int oldValueTemp = oldValue; + + switch (stackingMode) + { + case StackingMode::Override: + oldValue = newValue; + break; + case StackingMode::SetIfZero: + if (oldValue == 0) + oldValue = newValue; + else + valueChanged = false; + break; + case StackingMode::Min: + oldValue = Math::min(oldValue, newValue); + break; + case StackingMode::Max: + oldValue = Math::max(oldValue, newValue); + break; + case StackingMode::Add: + oldValue += newValue; + break; + case StackingMode::Subtract: + oldValue -= newValue; + break; + case StackingMode::Multiply: + oldValue = GeneralUtils::SafeMultiply(oldValue, newValue); + break; + case StackingMode::Divide: + if (newValue != 0) + oldValue /= newValue; + else + valueChanged = false; + break; + default: + valueChanged = false; + break; + } + + return valueChanged && oldValueTemp != oldValue; +} diff --git a/src/Utilities/EnumFunctions.h b/src/Utilities/EnumFunctions.h index a06c5c273a..20abfcc159 100644 --- a/src/Utilities/EnumFunctions.h +++ b/src/Utilities/EnumFunctions.h @@ -13,4 +13,5 @@ class EnumFunctions 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, bool allowBridges = false); + static bool CalcValueWithStackingMode(int& oldValue, int newValue, StackingMode stackingMode); }; diff --git a/src/Utilities/TemplateDef.h b/src/Utilities/TemplateDef.h index 856355209f..dad47fb577 100644 --- a/src/Utilities/TemplateDef.h +++ b/src/Utilities/TemplateDef.h @@ -1078,6 +1078,55 @@ namespace detail return false; } + template <> + inline bool read(StackingMode& value, INI_EX& parser, const char* pSection, const char* pKey) + { + if (parser.ReadString(pSection, pKey)) + { + if (_strcmpi(parser.value(), "override") == 0) + { + value = StackingMode::Override; + } + if (_strcmpi(parser.value(), "setifzero") == 0) + { + value = StackingMode::SetIfZero; + } + else if (_strcmpi(parser.value(), "min") == 0) + { + value = StackingMode::Min; + } + else if (_strcmpi(parser.value(), "max") == 0) + { + value = StackingMode::Max; + } + else if (_strcmpi(parser.value(), "add") == 0) + { + value = StackingMode::Add; + } + else if (_strcmpi(parser.value(), "subtract") == 0) + { + value = StackingMode::Subtract; + } + else if (_strcmpi(parser.value(), "multiply") == 0) + { + value = StackingMode::Multiply; + } + else if (_strcmpi(parser.value(), "divide") == 0) + { + value = StackingMode::Divide; + } + else + { + Debug::INIParseFailed(pSection, pKey, parser.value(), "Expected a stacking mode type"); + return false; + } + + return true; + } + + return false; + } + template <> inline bool read(ChronoSparkleDisplayPosition& value, INI_EX& parser, const char* pSection, const char* pKey) {