Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ This page lists all the individual contributions to the project by their author.
- Restored parabombs
- Delayed fire weapons
- Changes / fixes to `Vertical` projectile logic and customizing projectile initial facing behavior
- Attached animation draw offset customizations
- **Morton (MortonPL)**:
- `XDrawOffset` for animations
- Shield passthrough & absorption
Expand Down
18 changes: 17 additions & 1 deletion docs/Fixed-or-Improved-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ This page describes all ingame logics that are fixed or improved in Phobos witho

- Vehicle to building deployers now keep their target when deploying with `DeployToFire`.
- Effects like lasers are no longer drawn from wrong firing offset on weapons that use Burst.
- Animations can now be offset on the X axis with `XDrawOffset`.
- `IsSimpleDeployer` units now only play `DeploySound` and `UndeploySound` once, when done with (un)deploying instead of repeating it over duration of turning and/or `DeployingAnim`.
- AITrigger can now recognize Building Upgrades as legal condition.
- `EWGates` and `NSGates` now will link walls like `xxGateOne` and `xxGateTwo` do.
Expand Down Expand Up @@ -438,6 +437,23 @@ In `artmd.ini`:
Crater.DestroyTiberium= ; boolean, default to [General] -> AnimCraterDestroyTiberium
```

### Draw offset customization

- `XDrawOffset` can be used to adjust horizontal/X axis position of the animation.
- `YDrawOffset.ApplyBracketHeight` makes Y axis position follow it's owner object's selection bracket height (for buildings, this is based on `Height` and `Foundation`, for others it is influenced by `PixelSelectionBracketDelta`) if it is attached to one.
- By default this will only apply if the bracket position is negative e.g it is moved upwards from the object center. If `YDrawOffset.InvertBracketShift` is set to true, the opposite is true and negative shift is ignored.
- The bracket-based shift can be further adjusted with offset from `YDrawOffset.BracketAdjust`, overridden by `YDrawOffset.BracketAdjust.Buildings` for buildings only.

In `artmd.ini`:
```ini
[SOMEANIM] ; AnimationType
XDrawOffset=0,0 ; X,Y, pixels relative to default
YDrawOffset.ApplyBracketHeight=false ; boolean
YDrawOffset.InvertBracketShift=false ; boolean
YDrawOffset.BracketAdjust=0,0 ; X,Y, pixels relative to default
YDrawOffset.BracketAdjust.Buildings= ; X,Y, pixels relative to default
```

### Fire animations spawned by Scorch & Flamer

- Tiberian Sun allowed `Scorch=true` and `Flamer=true` animations to spawn fire animations from `[AudioVisual] -> SmallFire` & `LargeFire`. This behaviour has been reimplemented and is fully customizable.
Expand Down
4 changes: 4 additions & 0 deletions docs/New-or-Enhanced-Logics.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ This page describes all the engine features that are either new and introduced b
- `Animation.TemporalAction` determines what happens to the animation when the attached object is under effect of `Temporal=true` Warhead.
- `Animation.UseInvokerAsOwner` can be used to set the house and TechnoType that created the effect (e.g firer of the weapon that applied it) as the animation's owner & invoker instead of the object the effect is attached to.
- `Animation.HideIfAttachedWith` contains list of other AttachEffectTypes that if attached to same techno as the current one, will hide this effect's animation.
- `Animation.DrawOffsetN` (where N is integer starting from 0) can be used to define draw offset rules for the animation. These are parsed starting from index 0 until offset with value 0,0 is encountered.
- `Animation.DrawOffsetN.RequiredTypes` contains list other AttachEffectTypes that need to be attached on the same techno as the current one for the draw offset rule to apply.
- `CumulativeAnimations` can be used to declare a list of animations used for `Cumulative=true` types instead of `Animation`. An animation is picked from the list in order matching the number of active instances of the type on the object, with last listed animation used if number is higher than the number of listed animations. This animation is only displayed once and is transferred from the effect to another of same type (specifically one with longest remaining duration), if such exists, upon expiration or removal. Note that because `Cumulative.MaxCount` limits the number of effects of same type that can be applied this can cause animations to 'flicker' here as effects expire before new ones can be applied in some circumstances.
- `CumulativeAnimations.RestartOnChange` determines if the animation playback is restarted when the type of animation changes, if not then playback resumes at frame at same position relative to the animation's length.
- Attached effect can fire off a weapon when expired / removed / object dies by setting `ExpireWeapon`.
Expand Down Expand Up @@ -103,6 +105,8 @@ Animation.OfflineAction=Hides ; AttachedAnimFlag (None, Hid
Animation.TemporalAction=None ; AttachedAnimFlag (None, Hides, Temporal, Paused or PausedTemporal)
Animation.UseInvokerAsOwner=false ; boolean
Animation.HideIfAttachedWith= ; List of AttachEffectTypes
Animation.DrawOffsetN=0,0 ; X,Y, pixels relative to default
Animation.DrawOffsetN.RequiredTypes= ; List of AttachEffectTypes
CumulativeAnimations= ; List of AnimationTypes
CumulativeAnimations.RestartOnChange=true ; boolean
ExpireWeapon= ; WeaponType
Expand Down
2 changes: 2 additions & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,8 @@ New:
- [Ammo-based deploy customizations for vehicles expanded to non-IsSimpleDeployer deploy functions](New-or-Enhanced-Logics.md#automatic-deploy-and-blocking-deploying-based-on-ammo) (by Starkku)
- Randomized anims for several behaviors (by Ollerus)
- Shield respawn animation and weapon (by Ollerus)
- [Attached animation draw offset customizations](Fixed-or-Improved-Logics.md#draw-offset-customization) (by Starkku)
- [Draw offset rules for AttachEffect animations](New-or-Enhanced-Logics.md#attached-effects) by (Starkku)

Vanilla fixes:
- Fixed sidebar not updating queued unit numbers when adding or removing units when the production is on hold (by CrimRecya)
Expand Down
1 change: 1 addition & 0 deletions src/Ext/Anim/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ void AnimExt::ExtData::Serialize(T& Stm)
.Process(this->DelayedFireRemoveOnNoDelay)
.Process(this->IsAttachedEffectAnim)
.Process(this->IsShieldIdleAnim)
.Process(this->AEDrawOffset)
;
}

Expand Down
2 changes: 2 additions & 0 deletions src/Ext/Anim/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class AnimExt
bool DelayedFireRemoveOnNoDelay;
bool IsAttachedEffectAnim;
bool IsShieldIdleAnim;
Point2D AEDrawOffset;

ExtData(AnimClass* OwnerObject) : Extension<AnimClass>(OwnerObject)
, DeathUnitFacing { 0 }
Expand All @@ -45,6 +46,7 @@ class AnimExt
, DelayedFireRemoveOnNoDelay { false }
, IsAttachedEffectAnim { false }
, IsShieldIdleAnim { false }
, AEDrawOffset { Point2D::Empty }
{ }

void SetInvoker(TechnoClass* pInvoker);
Expand Down
51 changes: 47 additions & 4 deletions src/Ext/Anim/Hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,57 @@ DEFINE_HOOK(0x424CF1, AnimClass_Start_DetachedReport, 0x6)
}

// 0x422CD8 is in an alternate code path only used by anims with ID RING1, unused normally but covering it just because
DEFINE_HOOK_AGAIN(0x422CD8, AnimClass_DrawIt_XDrawOffset, 0x6)
DEFINE_HOOK(0x423122, AnimClass_DrawIt_XDrawOffset, 0x6)
DEFINE_HOOK_AGAIN(0x422CD8, AnimClass_DrawIt_DrawOffset, 0x6)
DEFINE_HOOK(0x423122, AnimClass_DrawIt_DrawOffset, 0x6)
{
GET(AnimClass* const, pThis, ESI);
GET_STACK(Point2D*, pLocation, STACK_OFFSET(0x110, 0x4));

if (auto const pTypeExt = AnimTypeExt::ExtMap.TryFind(pThis->Type))
pLocation->X += pTypeExt->XDrawOffset;
auto const pTypeExt = AnimTypeExt::ExtMap.TryFind(pThis->Type);
pLocation->X += pTypeExt->XDrawOffset;

if (pTypeExt->YDrawOffset_ApplyBracketHeight && pThis->OwnerObject && pThis->OwnerObject->AbstractFlags & AbstractFlags::Techno)
{
// Le magic number.
constexpr int SHIELD_HEALTHBAR_OFFSET = -3;

auto const pTechno = static_cast<TechnoClass*>(pThis->OwnerObject);
bool inverse = pTypeExt->YDrawOffset_InvertBracketShift;

if (auto const pBuilding = abstract_cast<BuildingClass*>(pTechno))
{
auto const pType = pBuilding->Type;

if ((pType->Height >= 0 && !inverse) || (pType->Height < 0 && inverse))
{
auto const pos = TechnoExt::GetBuildingSelectBracketPosition(pBuilding, BuildingSelectBracketPosition::Top);
pLocation->Y = pos.Y + pTypeExt->YDrawOffset_BracketAdjust_Buildings.Get(pTypeExt->YDrawOffset_BracketAdjust);
}
}
else
{
auto const pType = pTechno->GetTechnoType();

if ((pType->PixelSelectionBracketDelta <= 0 && !inverse) || (pType->PixelSelectionBracketDelta > 0 && inverse))
{
auto const pos = TechnoExt::GetFootSelectBracketPosition(pTechno, Anchor(HorizontalPosition::Left, VerticalPosition::Top));
pLocation->Y = pos.Y + pType->PixelSelectionBracketDelta + pTypeExt->YDrawOffset_BracketAdjust;
}
}

if (auto const pShield = TechnoExt::ExtMap.Find(pTechno)->Shield.get())
{
auto const pShieldType = pShield->GetType();

if (pShield->IsAvailable() && !pShield->IsBrokenAndNonRespawning() && (pShield->GetHealthRatio() > 0.0 || !pShieldType->Pips_HideIfNoStrength))
{
if ((pShieldType->BracketDelta <= 0 && !inverse) || (pShieldType->BracketDelta > 0 && inverse))
pLocation->Y += pShieldType->BracketDelta + SHIELD_HEALTHBAR_OFFSET;
}
}
}

*pLocation += AnimExt::ExtMap.Find(pThis)->AEDrawOffset;

return 0;
}
Expand Down
8 changes: 8 additions & 0 deletions src/Ext/AnimType/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ void AnimTypeExt::ExtData::LoadFromINIFile(CCINIClass* pINI)

this->Palette.LoadFromINI(pINI, pID, "CustomPalette");
this->XDrawOffset.Read(exINI, pID, "XDrawOffset");
this->YDrawOffset_ApplyBracketHeight.Read(exINI, pID, "YDrawOffset.ApplyBracketHeight");
this->YDrawOffset_InvertBracketShift.Read(exINI, pID, "YDrawOffset.InvertBracketShift");
this->YDrawOffset_BracketAdjust.Read(exINI, pID, "YDrawOffset.BracketAdjust");
this->YDrawOffset_BracketAdjust_Buildings.Read(exINI, pID, "YDrawOffset.BracketAdjust.Buildings");
this->HideIfNoOre_Threshold.Read(exINI, pID, "HideIfNoOre.Threshold");
this->Layer_UseObjectLayer.Read(exINI, pID, "Layer.UseObjectLayer");
this->AttachedAnimPosition.Read(exINI, pID, "AttachedAnimPosition");
Expand Down Expand Up @@ -146,6 +150,10 @@ void AnimTypeExt::ExtData::Serialize(T& Stm)
.Process(this->Palette)
.Process(this->CreateUnitType)
.Process(this->XDrawOffset)
.Process(this->YDrawOffset_ApplyBracketHeight)
.Process(this->YDrawOffset_InvertBracketShift)
.Process(this->YDrawOffset_BracketAdjust)
.Process(this->YDrawOffset_BracketAdjust_Buildings)
.Process(this->HideIfNoOre_Threshold)
.Process(this->Layer_UseObjectLayer)
.Process(this->AttachedAnimPosition)
Expand Down
6 changes: 6 additions & 0 deletions src/Ext/AnimType/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ class AnimTypeExt
CustomPalette Palette;
std::unique_ptr<CreateUnitTypeClass> CreateUnitType;
Valueable<int> XDrawOffset;
Valueable<bool> YDrawOffset_ApplyBracketHeight;
Valueable<bool> YDrawOffset_InvertBracketShift;
Valueable<int> YDrawOffset_BracketAdjust;
Nullable<int> YDrawOffset_BracketAdjust_Buildings;
Valueable<int> HideIfNoOre_Threshold;
Nullable<bool> Layer_UseObjectLayer;
Valueable<AttachedAnimPosition> AttachedAnimPosition;
Expand Down Expand Up @@ -68,6 +72,8 @@ class AnimTypeExt
, Palette { CustomPalette::PaletteMode::Temperate }
, CreateUnitType { nullptr }
, XDrawOffset { 0 }
, YDrawOffset_ApplyBracketHeight { false }
, YDrawOffset_InvertBracketShift { false }
, HideIfNoOre_Threshold { 0 }
, Layer_UseObjectLayer {}
, AttachedAnimPosition { AttachedAnimPosition::Default }
Expand Down
15 changes: 15 additions & 0 deletions src/Ext/Techno/Body.Update.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1840,7 +1840,10 @@ void TechnoExt::ExtData::UpdateAttachEffects()
}

if (altered)
{
this->RecalculateStatMultipliers();
this->UpdateAEAnimLogic();
}

if (markForRedraw)
pThis->MarkForRedraw();
Expand Down Expand Up @@ -1912,7 +1915,10 @@ void TechnoExt::ExtData::UpdateSelfOwnedAttachEffects()
const int count = AttachEffectClass::Attach(pThis, pThis->Owner, pThis, pThis, pTypeExt->AttachEffects);

if (altered && !count)
{
this->RecalculateStatMultipliers();
this->UpdateAEAnimLogic();
}

if (markForRedraw)
pThis->MarkForRedraw();
Expand Down Expand Up @@ -1960,6 +1966,15 @@ void TechnoExt::ExtData::UpdateCumulativeAttachEffects(AttachEffectTypeClass* pA
}
}

// Update AttachEffect animation logic.
void TechnoExt::ExtData::UpdateAEAnimLogic()
{
for (auto const& attachEffect : this->AttachedEffects)
{
attachEffect->UpdateAnimLogic();
}
}

// Recalculates AttachEffect stat multipliers and other bonuses.
void TechnoExt::ExtData::RecalculateStatMultipliers()
{
Expand Down
33 changes: 10 additions & 23 deletions src/Ext/Techno/Body.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,37 +465,31 @@ bool TechnoExt::IsTypeImmune(TechnoClass* pThis, TechnoClass* pSource)
return false;
}

/// <summary>
/// Gets whether or not techno has listed AttachEffect types active on it
/// </summary>
/// <param name="attachEffectTypes">Attacheffect types.</param>
/// <param name="requireAll">Whether or not to require all listed types to be present or if only one will satisfy the check.</param>
/// <param name="ignoreSameSource">Ignore AttachEffects that come from set invoker and source.</param>
/// <param name="pInvoker">Invoker Techno used for same source check.</param>
/// <param name="pSource">Source AbstractClass instance used for same source check.</param>
/// <returns>True if techno has active AttachEffects that satisfy the source, false if not.</returns>
bool TechnoExt::ExtData::HasAttachedEffects(std::vector<AttachEffectTypeClass*> attachEffectTypes, bool requireAll, bool ignoreSameSource,
TechnoClass* pInvoker, AbstractClass* pSource, std::vector<int> const* minCounts, std::vector<int> const* maxCounts) const
// Gets whether or not techno has listed AttachEffect types active on it
bool TechnoExt::ExtData::HasAttachedEffects(std::vector<AttachEffectTypeClass*> const& attachEffectTypes, bool requireAll, bool ignoreSameSource,
TechnoClass* pInvoker, AbstractClass* pSource, std::vector<int> const* minCounts, std::vector<int> const* maxCounts, bool requireAnims) const
{
unsigned int foundCount = 0;
unsigned int typeCounter = 1;
const bool checkSource = ignoreSameSource && pInvoker && pSource;

for (auto const& type : attachEffectTypes)
for (auto const& pType : attachEffectTypes)
{
for (auto const& attachEffect : this->AttachedEffects)
{
if (attachEffect->GetType() == type && attachEffect->IsActive())
auto const pAttachedType = attachEffect->GetType();

if (pAttachedType == pType && attachEffect->IsActive() && (!requireAnims || !pAttachedType->HasAnim() || attachEffect->HasAnim()))
{
if (checkSource && attachEffect->IsFromSource(pInvoker, pSource))
continue;

const unsigned int minSize = minCounts ? minCounts->size() : 0;
const unsigned int maxSize = maxCounts ? maxCounts->size() : 0;

if (type->Cumulative && (minSize > 0 || maxSize > 0))
if (pType->Cumulative && (minSize > 0 || maxSize > 0))
{
const int cumulativeCount = this->GetAttachedEffectCumulativeCount(type, ignoreSameSource, pInvoker, pSource);
const int cumulativeCount = this->GetAttachedEffectCumulativeCount(pType, ignoreSameSource, pInvoker, pSource);

if (minSize > 0)
{
Expand Down Expand Up @@ -531,14 +525,7 @@ bool TechnoExt::ExtData::HasAttachedEffects(std::vector<AttachEffectTypeClass*>
return false;
}

/// <summary>
/// Gets how many counts of same cumulative AttachEffect type instance techno has active on it.
/// </summary>
/// <param name="pAttachEffectType">AttachEffect type.</param>
/// <param name="ignoreSameSource">Ignore AttachEffects that come from set invoker and source.</param>
/// <param name="pInvoker">Invoker Techno used for same source check.</param>
/// <param name="pSource">Source AbstractClass instance used for same source check.</param>
/// <returns>Number of active cumulative AttachEffect type instances on the techno. 0 if the AttachEffect type is not cumulative.</returns>
// Gets how many counts of same cumulative AttachEffect type instance techno has active on it.
int TechnoExt::ExtData::GetAttachedEffectCumulativeCount(AttachEffectTypeClass* pAttachEffectType, bool ignoreSameSource, TechnoClass* pInvoker, AbstractClass* pSource) const
{
if (!pAttachEffectType->Cumulative)
Expand Down
3 changes: 2 additions & 1 deletion src/Ext/Techno/Body.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class TechnoExt
void UpdateKeepTargetOnMove();
void UpdateWarpInDelay();
void UpdateCumulativeAttachEffects(AttachEffectTypeClass* pAttachEffectType, AttachEffectClass* pRemoved = nullptr);
void UpdateAEAnimLogic();
void RecalculateStatMultipliers();
void UpdateTemporal();
void UpdateMindControlAnim();
Expand All @@ -183,7 +184,7 @@ class TechnoExt
void InitializeLaserTrails();
void InitializeAttachEffects();
void UpdateSelfOwnedAttachEffects();
bool HasAttachedEffects(std::vector<AttachEffectTypeClass*> attachEffectTypes, bool requireAll, bool ignoreSameSource, TechnoClass* pInvoker, AbstractClass* pSource, std::vector<int> const* minCounts, std::vector<int> const* maxCounts) const;
bool HasAttachedEffects(std::vector<AttachEffectTypeClass*> const& attachEffectTypes, bool requireAll, bool ignoreSameSource, TechnoClass* pInvoker, AbstractClass* pSource, std::vector<int> const* minCounts, std::vector<int> const* maxCounts, bool requireAnims = false) const;
int GetAttachedEffectCumulativeCount(AttachEffectTypeClass* pAttachEffectType, bool ignoreSameSource = false, TechnoClass* pInvoker = nullptr, AbstractClass* pSource = nullptr) const;
void InitializeDisplayInfo();
void ApplyMindControlRangeLimit();
Expand Down
Loading
Loading