diff --git a/CREDITS.md b/CREDITS.md index d486713e9d..2fe5311051 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -781,6 +781,7 @@ This page lists all the individual contributions to the project by their author. - Customize whether or not passenger can fire out when the transport is moving - Show game time - Fix a game crash when parsing string list with null entry + - More veteran and elite abilities - **NaotoYuuki** - Vertical & meteor trajectory projectile prototypes - **handama**: - AI script action to `16005 Jump Back To Previous Script` diff --git a/docs/New-or-Enhanced-Logics.md b/docs/New-or-Enhanced-Logics.md index 099e328373..36d337c1d9 100644 --- a/docs/New-or-Enhanced-Logics.md +++ b/docs/New-or-Enhanced-Logics.md @@ -2336,6 +2336,25 @@ CombatAlert.EVA=EVA_UnitsInCombat ; EVA entry CombatAlert.Suppress= ; boolean ``` +### Weapon range adjustment on promotion + +- Now you can add `RANGE` to `VeteranAbilities` and `EliteAbilities` to adjust the weapon range of this TechnoType. + - `ProjectileRange` of the weapon's projectile will also be affected if `Projectile.ApplyModifiers` set to true. + +```{hint} +- Abilities from `VeteranAbilities` keep working at elite level, and `EliteAbilities` are added on top, matching how vanilla abilities accumulate. +- `VeteranRange` will be applied to the range calculation. Values greater than `1.0` lengthen the range, values smaller than `1.0` shorten it, and `1.0` (the default) leaves it unchanged. +``` + +In `rulesmd.ini`: +```ini +[General] +VeteranRange=1.0 ; floating point value, multiplier + +[SOMETECHNO] ; TechnoType, with VeteranAbilities and/or EliteAbilities containing RANGE. +VeteranRange= ; floating point value, multiplier, default to [General] -> VeteranRange +``` + ### Recount burst index - You can now make technos recount their current burst index when they have changed the firing weapon or have maintained for a period of time without any targets (take the larger value of last firing weapon's `ROF` and 30 frames). @@ -2785,10 +2804,19 @@ AffectsGround=true ; boolean - `Crit.AnimOnAffectedTargets`, if set, makes the animation(s) from `Crit.AnimList` play on each affected target *in addition* to animation from Warhead's `AnimList` playing as normal instead of replacing `AnimList` animation. Note that because these animations are independent from `AnimList`, `Crit.AnimList.PickRandom` and `Crit.AnimList.CreateAll` will not default to their `AnimList` counterparts here and need to be explicitly set if needed. - `Crit.ActiveChanceAnims` can be used to set animation to be always displayed at the Warhead's detonation coordinates if the current Warhead has a chance to critically hit. If more than one animation is listed, a random one is selected. - `Crit.SuppressWhenIntercepted`, if set, prevents critical hits from occuring at all if the warhead was detonated from a [projectile that was intercepted](#projectile-interception-logic). - - `ImmuneToCrit` can be set on TechnoTypes and ShieldTypes to make them immune to critical hits. + - `ImmuneToCrit` can be set on TechnoTypes and ShieldTypes to make them immune to critical hits. You can also add `CRITIMMUNE` to the TechnoTypes' `VeteranAbilities` and `EliteAbilities` to grant them immunity when promoting. + - You can add `CRITCHANCE` to `VeteranAbilities` and `EliteAbilities` to adjust the critical hit chance of this TechnoType. + +```{hint} +- Abilities from `VeteranAbilities` keep working at elite level, and `EliteAbilities` are added on top, matching how vanilla abilities accumulate. +- `VeteranCritChance` will be applied to the range calculation. Values greater than `1.0` increase the chance, values smaller than `1.0` decrease it, and `1.0` (the default) leaves it unchanged. +``` In `rulesmd.ini`: ```ini +[General] +VeteranCritChance=1.0 ; floating point value + [CombatDamage] Crit.ApplyChancePerTarget=false ; boolean Crit.ExtraDamage.ApplyFirepowerMult=false ; boolean @@ -2815,6 +2843,9 @@ Crit.SuppressWhenIntercepted= ; boolean, default to [CombatDamage] [SOMETECHNO] ; TechnoType ImmuneToCrit=false ; boolean + +[SOMETECHNO] ; TechnoType, with VeteranAbilities and/or EliteAbilities containing CRITCHANCE. +VeteranCritChance= ; floating point value, multiplier, default to [General] -> VeteranCritChance ``` ```{warning} diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 8225c1eaa0..9d73612f15 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -421,6 +421,7 @@ HideShakeEffects=false ; boolean #### New: - [Customized transport plane for teams](AI-Scripting-and-Mapping.md#customized-transport-plane-for-teams) (by FlyStar) - [Modify ammo on impact](New-or-Enhanced-Logics.md#modify-ammo-on-impact) (by FS-21) +- More veteran and elite abilities (by Ollerus) #### Vanilla fixes: - Fixed the bug where a building with `Factory=BuildingType` owned by the AI did not play `ProductionAnim` when placing a produced building (by Noble_Fish) diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index d54228d3b8..782ffb12fd 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -359,6 +359,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->VeteranReload.Read(exINI, GameStrings::General, "VeteranReload"); this->VeteranEmptyReload.Read(exINI, GameStrings::General, "VeteranEmptyReload"); + this->VeteranRange.Read(exINI, GameStrings::General, "VeteranRange"); + this->VeteranCritChance.Read(exINI, GameStrings::General, "VeteranCritChance"); this->NoTurret_TrackTarget.Read(exINI, GameStrings::General, "NoTurret.TrackTarget"); @@ -923,6 +925,8 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->NoReload_Temporal) .Process(this->VeteranReload) .Process(this->VeteranEmptyReload) + .Process(this->VeteranRange) + .Process(this->VeteranCritChance) .Process(this->NoTurret_TrackTarget) .Process(this->GatherWhenMCVDeploy) .Process(this->AIFireSale) diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 2de689e591..3da156353b 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -291,6 +291,8 @@ class RulesExt Valueable NoReload_Temporal; Valueable VeteranReload; Nullable VeteranEmptyReload; + Valueable VeteranRange; + Valueable VeteranCritChance; Valueable NoTurret_TrackTarget; Valueable GatherWhenMCVDeploy; @@ -788,6 +790,8 @@ class RulesExt , NoReload_Temporal { false } , VeteranReload { 1.0 } , VeteranEmptyReload {} + , VeteranRange { 1.0 } + , VeteranCritChance { 1.0 } , NoTurret_TrackTarget { false } , GatherWhenMCVDeploy { true } , AIFireSale { true } diff --git a/src/Ext/Techno/Hooks.Firing.cpp b/src/Ext/Techno/Hooks.Firing.cpp index 244b1a5cb9..1083b7b288 100644 --- a/src/Ext/Techno/Hooks.Firing.cpp +++ b/src/Ext/Techno/Hooks.Firing.cpp @@ -1027,7 +1027,7 @@ DEFINE_HOOK(0x6F3AEB, TechnoClass_GetFLH, 0x6) GET(TechnoTypeClass*, pType, EAX); GET(const int, weaponIndex, ESI); GET_STACK(CoordStruct*, pCoords, STACK_OFFSET(0xD8, 0x4)); - REF_STACK(CoordStruct, offset, STACK_OFFSET(0xD8, 0xC)); + REF_STACK(const CoordStruct, offset, STACK_OFFSET(0xD8, 0xC)); bool allowOnTurret = true; CoordStruct flh = CoordStruct::Empty; @@ -1172,11 +1172,10 @@ static inline int ScaleReloadDurationForVeterancy(TechnoClass* pThis, int durati return duration; const auto pTypeExt = TechnoExt::Fetch(pThis)->TypeExtData; - const auto pRulesExt = RulesExt::Global(); const double multiplier = ability == AdditionalAbility::EmptyReload - ? pTypeExt->VeteranEmptyReload.Get(pRulesExt->VeteranEmptyReload.Get(RulesExt::Global()->VeteranReload)) - : pTypeExt->VeteranReload.Get(pRulesExt->VeteranReload); + ? pTypeExt->VeteranEmptyReload.Get(RulesExt::Global()->VeteranEmptyReload.Get(RulesExt::Global()->VeteranReload)) + : pTypeExt->VeteranReload.Get(RulesExt::Global()->VeteranReload); return Math::max(1, GeneralUtils::SafeMultiply(duration, multiplier)); } diff --git a/src/Ext/TechnoType/Body.cpp b/src/Ext/TechnoType/Body.cpp index 45dc1e6245..033d39a926 100644 --- a/src/Ext/TechnoType/Body.cpp +++ b/src/Ext/TechnoType/Body.cpp @@ -17,6 +17,9 @@ namespace constexpr std::pair AbilityTokens[] = { { "RELOAD", AdditionalAbility::Reload }, { "EMPTY_RELOAD", AdditionalAbility::EmptyReload }, + { "RANGE", AdditionalAbility::Range }, + { "CRITIMMUNE", AdditionalAbility::CritImmune }, + { "CRITCHANCE", AdditionalAbility::CritChance } }; void ReadAdditionalAbilities( @@ -1050,6 +1053,8 @@ void TechnoTypeExt::LoadFromINIFile(CCINIClass* const pINI) this->VeteranReload.Read(exINI, pSection, "VeteranReload"); this->VeteranEmptyReload.Read(exINI, pSection, "VeteranEmptyReload"); + this->VeteranRange.Read(exINI, pSection, "VeteranRange"); + this->VeteranCritChance.Read(exINI, pSection, "VeteranCritChance"); this->Wake.Read(exINI, pSection, "Wake"); this->Wake_Grapple.Read(exINI, pSection, "Wake.Grapple"); @@ -1678,6 +1683,8 @@ void TechnoTypeExt::Serialize(T& Stm) .Process(this->AdditionalEliteAbilities) .Process(this->VeteranReload) .Process(this->VeteranEmptyReload) + .Process(this->VeteranRange) + .Process(this->VeteranCritChance) .Process(this->Wake) .Process(this->Wake_Grapple) diff --git a/src/Ext/TechnoType/Body.h b/src/Ext/TechnoType/Body.h index 83d33cafd5..9a596b8689 100644 --- a/src/Ext/TechnoType/Body.h +++ b/src/Ext/TechnoType/Body.h @@ -289,6 +289,8 @@ class TechnoTypeExt : public ObjectTypeExt std::bitset AdditionalEliteAbilities; Nullable VeteranReload; Nullable VeteranEmptyReload; + Nullable VeteranRange; + Nullable VeteranCritChance; Nullable Wake; Nullable Wake_Grapple; @@ -702,6 +704,8 @@ class TechnoTypeExt : public ObjectTypeExt , AdditionalEliteAbilities {} , VeteranReload {} , VeteranEmptyReload {} + , VeteranRange {} + , VeteranCritChance {} , Wake { } , Wake_Grapple { } diff --git a/src/Ext/WarheadType/Detonate.cpp b/src/Ext/WarheadType/Detonate.cpp index d8bb3d432f..64cd96367a 100644 --- a/src/Ext/WarheadType/Detonate.cpp +++ b/src/Ext/WarheadType/Detonate.cpp @@ -548,7 +548,7 @@ void WarheadTypeExt::ApplyCrit(HouseClass* pHouse, TechnoClass* pTarget, TechnoC auto const pTargetExt = TechnoExt::Fetch(pTarget); - if (pTargetExt->TypeExtData->ImmuneToCrit) + if (pTargetExt->TypeExtData->ImmuneToCrit || TechnoExt::HasAdditionalAbility(pTarget, AdditionalAbility::CritImmune)) return; auto const pSld = pTargetExt->Shield.get(); @@ -747,6 +747,9 @@ double WarheadTypeExt::GetCritChance(TechnoClass* pFirer) const auto const pExt = TechnoExt::Fetch(pFirer); + if (TechnoExt::HasAdditionalAbility(pFirer, AdditionalAbility::CritChance)) + critChance = critChance * Math::max(pExt->TypeExtData->VeteranCritChance.Get(RulesExt::Global()->VeteranCritChance), 0); + if (!pExt->AE.HasCritModifiers) return critChance; diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index edf00e99fe..5cf9aba3ca 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -409,6 +409,9 @@ int WeaponTypeExt::GetRangeWithModifiers(WeaponTypeClass* pThis, TechnoClass* pF auto const pTechnoExt = TechnoExt::Fetch(pTechno); + if (TechnoExt::HasAdditionalAbility(pTechno, AdditionalAbility::Range)) + range = GeneralUtils::SafeMultiply(range, Math::max(pTechnoExt->TypeExtData->VeteranRange.Get(RulesExt::Global()->VeteranRange), 0.0)); + if (!pTechnoExt->AE.HasRangeModifier) return range; diff --git a/src/Utilities/Enum.h b/src/Utilities/Enum.h index 98430c02e5..3fcd8d1288 100644 --- a/src/Utilities/Enum.h +++ b/src/Utilities/Enum.h @@ -449,6 +449,9 @@ enum class AdditionalAbility : unsigned char { Reload = 0, EmptyReload = 1, + Range = 2, + CritImmune = 3, + CritChance = 4, Count };