diff --git a/CREDITS.md b/CREDITS.md index db73b2649c..418e1c229e 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -662,6 +662,7 @@ This page lists all the individual contributions to the project by their author. - Toggle per-target warhead effects apply timing - Extra range for chasing and pre-firing - Fix an issue that rockets do not consider the destination altitude during climbing + - Updateable firing anim - **solar-III (凤九歌)** - Target scanning delay customization (documentation) - Skip target scanning function calling for unarmed technos (documentation) diff --git a/docs/Fixed-or-Improved-Logics.md b/docs/Fixed-or-Improved-Logics.md index 5dc33e1079..9d8edfc69d 100644 --- a/docs/Fixed-or-Improved-Logics.md +++ b/docs/Fixed-or-Improved-Logics.md @@ -2540,3 +2540,17 @@ In `rulesmd.ini`: [SOMEWEAPON] ; WeaponType IsSingleColor=false ; boolean ``` + +### Updateable firing anim + +- In vanilla, firing anims is attached to the firer, but it won't update its type and location to fit the firer's facing. This is now customizable by the following flags. + +In `rulesmd.ini`: +```ini +[AudioVisual] +FiringAnim.Update=false ; boolean + +[SOMEWEAPON] ; WeaponType +Anim.Update= ; boolean, default to [AudioVisual] -> FiringAnim.Update +``` + diff --git a/docs/Whats-New.md b/docs/Whats-New.md index 10c5a6a3ec..8ac9cc3c26 100644 --- a/docs/Whats-New.md +++ b/docs/Whats-New.md @@ -480,6 +480,7 @@ New: - Allow techno type considered as other type when recruiting techno for teams (by NetsuNegi) - Map Action [`511` Undeploy Building to Waypoint](AI-Scripting-and-Mapping.md#undeploy-building-to-waypoint), [`609` Set Radar Mode](AI-Scripting-and-Mapping.md#set-radar-mode), [`610` Set house's `TeamDelays` value](AI-Scripting-and-Mapping.md#set-house-s-teamdelays-value) (by FlyStar) - Fixed an issue that rockets do not consider the destination altitude during climbing (by TaranDahl) +- [Updateable firing anim](Fixed-or-Improved-Logics.md#updateable-firing-anim) (by TaranDahl) 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/Anim/Body.cpp b/src/Ext/Anim/Body.cpp index f7c74fdb28..5d94d8fb96 100644 --- a/src/Ext/Anim/Body.cpp +++ b/src/Ext/Anim/Body.cpp @@ -65,6 +65,64 @@ void AnimExt::ExtData::DeleteAttachedSystem() } } +inline unsigned int TranslateFixedPoint(size_t bitsFrom, size_t bitsTo, unsigned int value, unsigned int offset = 0) +{ + const size_t MaskIn = ((1u << bitsFrom) - 1); + const size_t MaskOut = ((1u << bitsTo) - 1); + + if (bitsFrom > bitsTo) + { + // converting down + return (((((value & MaskIn) >> (bitsFrom - bitsTo - 1)) + 1) >> 1) + offset) & MaskOut; + + } + else if (bitsFrom < bitsTo) + { + // converting up + return (((value - offset) & MaskIn) << (bitsTo - bitsFrom)) & MaskOut; + + } + else + { + return value & MaskOut; + } +} + +void AnimExt::ExtData::UpdateAsFiringAnim() +{ + auto pThis = this->OwnerObject(); + auto pOwner = abstract_cast(pThis->OwnerObject); + + if (this->FromWeapon && pOwner) + { + AnimTypeClass* pNewType = nullptr; + auto pWeapon = this->FromWeapon; + + auto highest = Conversions::Int2Highest(pWeapon->Anim.Count); + + // 2^highest is the frame count, 3 means 8 frames + if (highest >= 3) + { + auto offset = 1u << (highest - 3); + auto index = TranslateFixedPoint(16, highest, static_cast((pOwner)->GetRealFacing().GetValue<16>()), offset); + pNewType = pWeapon->Anim.GetItemOrDefault(index); + } + else + { + pNewType = pWeapon->Anim.GetItemOrDefault(0); + } + + if (pNewType) + pThis->Type = pNewType; + + auto burstIdx = pOwner->CurrentBurstIndex; + pOwner->CurrentBurstIndex = this->FromBurstIdx; + auto flh = pOwner->GetFLH(this->FromWeaponIdx, CoordStruct::Empty); + pOwner->CurrentBurstIndex = burstIdx; + pThis->SetLocation(flh - pOwner->GetCoords()); + } +} + //Modified from Ares bool AnimExt::SetAnimOwnerHouseKind(AnimClass* pAnim, HouseClass* pInvoker, HouseClass* pVictim, bool defaultToVictimOwner, bool defaultToInvokerOwner) { @@ -411,6 +469,9 @@ void AnimExt::ExtData::Serialize(T& Stm) .Process(this->DelayedFireRemoveOnNoDelay) .Process(this->IsAttachedEffectAnim) .Process(this->IsShieldIdleAnim) + .Process(this->FromWeapon) + .Process(this->FromWeaponIdx) + .Process(this->FromBurstIdx) ; } diff --git a/src/Ext/Anim/Body.h b/src/Ext/Anim/Body.h index 72f3726100..9376772905 100644 --- a/src/Ext/Anim/Body.h +++ b/src/Ext/Anim/Body.h @@ -31,6 +31,9 @@ class AnimExt bool DelayedFireRemoveOnNoDelay; bool IsAttachedEffectAnim; bool IsShieldIdleAnim; + WeaponTypeClass* FromWeapon; + int FromWeaponIdx; + int FromBurstIdx; ExtData(AnimClass* OwnerObject) : Extension(OwnerObject) , DeathUnitFacing { 0 } @@ -45,6 +48,9 @@ class AnimExt , DelayedFireRemoveOnNoDelay { false } , IsAttachedEffectAnim { false } , IsShieldIdleAnim { false } + , FromWeapon {} + , FromWeaponIdx {} + , FromBurstIdx {} { } void SetInvoker(TechnoClass* pInvoker); @@ -52,6 +58,8 @@ class AnimExt void CreateAttachedSystem(); void DeleteAttachedSystem(); + void UpdateAsFiringAnim(); + virtual ~ExtData() override; virtual void InvalidatePointer(void* ptr, bool bRemoved) override { } diff --git a/src/Ext/Anim/Hooks.cpp b/src/Ext/Anim/Hooks.cpp index fc8ad2c1a4..52ed9bd59a 100644 --- a/src/Ext/Anim/Hooks.cpp +++ b/src/Ext/Anim/Hooks.cpp @@ -29,6 +29,8 @@ DEFINE_HOOK(0x423B95, AnimClass_AI_HideIfNoOre_Threshold, 0x8) pThis->Invisible = pThis->GetCell()->GetContainedTiberiumValue() <= nThreshold; } + AnimExt::ExtMap.Find(pThis)->UpdateAsFiringAnim(); + return 0x423BBF; } @@ -549,3 +551,27 @@ DEFINE_HOOK(0x4250E1, AnimClass_Middle_CraterDestroyTiberium, 0x6) return AnimTypeExt::ExtMap.Find(pType)->Crater_DestroyTiberium.Get(RulesExt::Global()->AnimCraterDestroyTiberium) ? 0 : SkipDestroyTiberium; } +#pragma region FiringAnimUpdate + +DEFINE_HOOK(0x6FF42B, TechnoClass_Fire_Anim, 0x7) +{ + enum { SkipBuildingCheck = 0x6FF437 }; + + GET(TechnoClass*, pThis, ESI); + GET(AnimClass*, pAnim, EDI); + GET(WeaponTypeClass*, pWeapon, EBX); + GET_BASE(int, wpIdx, 0xC); + + auto pAnimExt = AnimExt::ExtMap.Find(pAnim); + + if (WeaponTypeExt::ExtMap.Find(pWeapon)->Anim_Update.Get(RulesExt::Global()->FiringAnim_Update)) + { + pAnimExt->FromWeapon = pWeapon; + pAnimExt->FromWeaponIdx = wpIdx; + pAnimExt->FromBurstIdx = pThis->CurrentBurstIndex; + } + + return SkipBuildingCheck; +} + +#pragma endregion diff --git a/src/Ext/Rules/Body.cpp b/src/Ext/Rules/Body.cpp index d78d69069c..83247f76ed 100644 --- a/src/Ext/Rules/Body.cpp +++ b/src/Ext/Rules/Body.cpp @@ -349,6 +349,8 @@ void RulesExt::ExtData::LoadBeforeTypeData(RulesClass* pThis, CCINIClass* pINI) this->ExtraRange_Prefiring.Read(exINI, GameStrings::General, "ExtraRange.Prefiring"); this->ExtraRange_Prefiring_IncludeBurst.Read(exINI, GameStrings::General, "ExtraRange.Prefiring.IncludeBurst"); + this->FiringAnim_Update.Read(exINI, GameStrings::AudioVisual, "FiringAnim.Update"); + // Section AITargetTypes int itemsCount = pINI->GetKeyCount("AITargetTypes"); for (int i = 0; i < itemsCount; ++i) @@ -632,6 +634,7 @@ void RulesExt::ExtData::Serialize(T& Stm) .Process(this->ExtraRange_TargetMoving_CloseRangeOnly) .Process(this->ExtraRange_Prefiring) .Process(this->ExtraRange_Prefiring_IncludeBurst) + .Process(this->FiringAnim_Update) ; } diff --git a/src/Ext/Rules/Body.h b/src/Ext/Rules/Body.h index 2144aa9cab..4d89e200e2 100644 --- a/src/Ext/Rules/Body.h +++ b/src/Ext/Rules/Body.h @@ -297,6 +297,8 @@ class RulesExt Valueable ExtraRange_Prefiring_IncludeBurst; Valueable ApplyPerTargetEffectsOnDetonate; + + Valueable FiringAnim_Update; ExtData(RulesClass* OwnerObject) : Extension(OwnerObject) , Storage_TiberiumIndex { -1 } @@ -534,6 +536,8 @@ class RulesExt , ExtraRange_TargetMoving_CloseRangeOnly { false } , ExtraRange_Prefiring { Leptons(0) } , ExtraRange_Prefiring_IncludeBurst { true } + + , FiringAnim_Update { false } { } virtual ~ExtData() = default; diff --git a/src/Ext/WeaponType/Body.cpp b/src/Ext/WeaponType/Body.cpp index 666ad53dc6..c22ed4b49f 100644 --- a/src/Ext/WeaponType/Body.cpp +++ b/src/Ext/WeaponType/Body.cpp @@ -1,4 +1,4 @@ -#include "Body.h" +#include "Body.h" #include #include #include @@ -156,6 +156,7 @@ void WeaponTypeExt::ExtData::LoadFromINIFile(CCINIClass* const pINI) this->ExtraRange_TargetMoving.Read(exINI, GameStrings::General, "ExtraRange.TargetMoving"); this->ExtraRange_Prefiring.Read(exINI, GameStrings::General, "ExtraRange.Prefiring"); this->ExtraRange_Prefiring_IncludeBurst.Read(exINI, GameStrings::General, "ExtraRange.Prefiring.IncludeBurst"); + this->Anim_Update.Read(exINI, pSection, "Anim.Update"); // handle SkipWeaponPicking if (this->CanTarget != AffectedTarget::All || this->CanTargetHouses != AffectedHouse::All @@ -243,6 +244,7 @@ void WeaponTypeExt::ExtData::Serialize(T& Stm) .Process(this->ExtraRange_TargetMoving) .Process(this->ExtraRange_Prefiring) .Process(this->ExtraRange_Prefiring_IncludeBurst) + .Process(this->Anim_Update) ; }; diff --git a/src/Ext/WeaponType/Body.h b/src/Ext/WeaponType/Body.h index 31eb143d4b..f84f39bdb2 100644 --- a/src/Ext/WeaponType/Body.h +++ b/src/Ext/WeaponType/Body.h @@ -91,10 +91,10 @@ class WeaponTypeExt Valueable DelayedFire_OnlyOnInitialBurst; Nullable DelayedFire_AnimOffset; Valueable DelayedFire_AnimOnTurret; - Nullable ExtraRange_TargetMoving; Nullable ExtraRange_Prefiring; Nullable ExtraRange_Prefiring_IncludeBurst; + Nullable Anim_Update; bool SkipWeaponPicking; @@ -168,10 +168,10 @@ class WeaponTypeExt , DelayedFire_OnlyOnInitialBurst { false } , DelayedFire_AnimOffset {} , DelayedFire_AnimOnTurret { true } - , ExtraRange_TargetMoving {} , ExtraRange_Prefiring {} , ExtraRange_Prefiring_IncludeBurst {} + , Anim_Update {} { } int GetBurstDelay(int burstIndex) const;