Skip to content

Commit

Permalink
Implements feature for animations to spawn additional animations. (#753)
Browse files Browse the repository at this point in the history
Co-authored-by: ZivDero <[email protected]>
  • Loading branch information
CCHyper and ZivDero authored Nov 28, 2024
1 parent c1ffe71 commit e5193d0
Show file tree
Hide file tree
Showing 10 changed files with 546 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ This page lists all the individual contributions to the project by their author.
- Implement various controls to customise target lasers line.
- Implement various controls to show and customise NavCom queue lines.
- Implement customizable mouse cursors and actions.
- Implement a feature for animations to spawn additional animations.
- **Kerbiter (Metadorius)**:
- Initial documentation setup.
- **MarkJFox**:
Expand Down Expand Up @@ -206,4 +207,5 @@ This page lists all the individual contributions to the project by their author.
- Implement required and forbidden houses.
- Allow turning off "sticky" technologies.
- Allow disabling the ActLike check on construction yards to allow for faction-specific MCVs.
- Finalize the feature for animations to spawn additional animations.

38 changes: 38 additions & 0 deletions docs/New-Features-and-Enhancements.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,44 @@ ReloadRate= ; float, the rate that this aircraft will reload its ammo when d

## Animations

### Additional Animation Spawning

- Vinifera implements a new system for AnimTypes, allowing them to spawn additional animations at the start, middle and end of their sequence stages. All animations spawned will be from the center coordinate of the animation spawning these additional animations.

```{note}
The `<stage>` keyword used below can be replaced with: `Start`, `Middle`, `End`.
```

```{note}
The `Start` and `End` animations are spawned once per an animation's lifetime, not on each loop iteration.
```

In `RULES.INI`:
```ini
[AnimType] ; AnimType
<stage>Anims= ; list of AnimTypes, list of animations to spawn at the designated stage of the animation sequence.
<stage>AnimsMinimum= ; list of integers, the minimum number of animations that can spawn when choosing the random amount for each of the respective entries on the animations list. This list must have the same number of entries as the animations list. Defaults to 1 for each entry.
<stage>AnimsMaximum= ; list of integers, the maximum number of animations that can spawn when choosing the random amount for each of the respective entries on the animations list. This list must have the same number of entries as the animations list. Defaults to 1 for each entry.
<stage>AnimsCount= ; list of integers, the number of animations to spawn for each of the respective entries on the animations list. This list must have the same number of entries as the animations list. Defaults to 1 for each entry, and takes priority over the Minimum and Maximum entries.
<stage>AnimsDelay= ; list of integers, the number of frames before the spawned animation appears for each of the respective entries on the animations list. This list must have the same number of entries as the animations list. Defaults to 0 for each entry.
```

```{note}
If the animation moves, delayed animations that it spawns will appear where it was when they were spawned, not when their delay expired.
```

- In addition to this new system, a new key for setting the logical middle frame (the frame in which the craters etc, are spawned) can now be set.

In `RULES.INI`:
```ini
[AnimType] ; AnimType
MiddleFrame= ; integer, the frame number in which the animation system will perform various logics (e.g. spawn craters, scorch marks, fires). Defaults to auto-detect based on the largest frame of the shape file. A special value of -1 can be used to tell the animation system to use the exact middle frame of the shape file (if shape file has 30 frames, frame 15 will be used).
```

```{note}
`MiddleFrame=0` is reserved and will not cause `MiddleAnims` to be spawned on every loop, but rather once at the start of the animation (like with `StartAnims`). To repeatedly spawn animations at the start of the loop, use `MiddleFrame` values of `1` or higher.
```

### Various Keys Ported from Red Alert 2

- Vinifera implements various AnimType keys from Red Alert 2.
Expand Down
1 change: 1 addition & 0 deletions docs/Whats-New.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ New:
- Implement required and forbidden houses (by ZivDero)
- Allow turning off "sticky" technologies (by ZivDero)
- Allow disabling the ActLike check on construction yards to allow for faction-specific MCVs (by ZivDero)
- Implement a feature for animations to spawn additional animations (by CCHyper/tomsons26, ZivDero)


Vanilla fixes:
Expand Down
125 changes: 125 additions & 0 deletions src/extensions/anim/animext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
******************************************************************************/
#include "animext.h"
#include "anim.h"
#include "animtype.h"
#include "animtypeext.h"
#include "tibsun_inline.h"
#include "wwcrc.h"
#include "extension.h"
#include "asserthandler.h"
Expand Down Expand Up @@ -164,3 +167,125 @@ void AnimClassExtension::Compute_CRC(WWCRCEngine &crc) const
{
//EXT_DEBUG_TRACE("AnimClassExtension::Compute_CRC - Name: %s (0x%08X)\n", Name(), (uintptr_t)(This()));
}


/**
* Processes any start events.
*
* @author: CCHyper
*/
bool AnimClassExtension::Start()
{
AnimTypeClassExtension *animtypeext = Extension::Fetch<AnimTypeClassExtension>(This()->Class);

/**
* #issue-752
*
* Spawns the start animations.
*/
Spawn_Animations(This()->Center_Coord(), animtypeext->StartAnims, animtypeext->StartAnimsCount, animtypeext->StartAnimsMinimum, animtypeext->StartAnimsMaximum, animtypeext->StartAnimsDelay);

return true;
}


/**
* Processes any middle events.
*
* @author: CCHyper
*/
bool AnimClassExtension::Middle()
{
AnimTypeClassExtension *animtypeext = Extension::Fetch<AnimTypeClassExtension>(This()->Class);

/**
* #issue-752
*
* Spawns the middle animations.
*/
Spawn_Animations(This()->Center_Coord(), animtypeext->MiddleAnims, animtypeext->MiddleAnimsCount, animtypeext->MiddleAnimsMinimum, animtypeext->MiddleAnimsMaximum, animtypeext->MiddleAnimsDelay);

return true;
}


/**
* Processes any end events.
*
* @author: CCHyper
*/
bool AnimClassExtension::End()
{
AnimTypeClassExtension *animtypeext = Extension::Fetch<AnimTypeClassExtension>(This()->Class);

/**
* #issue-752
*
* Spawns the end animations.
*/
Spawn_Animations(This()->Center_Coord(), animtypeext->EndAnims, animtypeext->EndAnimsCount, animtypeext->EndAnimsMinimum, animtypeext->EndAnimsMaximum, animtypeext->EndAnimsDelay);

return true;
}


/**
* #issue-752
*
* Spawns the requested animation from the parsed type lists.
*
* @author: CCHyper
*/
bool AnimClassExtension::Spawn_Animations(const Coordinate &coord, const TypeList<AnimTypeClass *> &animlist, const TypeList<int> &countlist, const TypeList<int> &minlist, const TypeList<int> &maxlist, const TypeList<int>& delaylist)
{
if (!animlist.Count()) {
return false;
}

/**
* Some checks to make sure values are within expected ranges.
*/
if (!countlist.Count()) {
ASSERT(animlist.Count() == minlist.Count());
ASSERT(animlist.Count() == maxlist.Count());
}

/**
* Iterate over all animations set and spawn them.
*/
for (int index = 0; index < animlist.Count(); ++index) {

const AnimTypeClass *animtype = animlist[index];

int count = 1;

/**
* Pick a random count based on the minimum and maximum values
* defined and spawn the animations.
*/
if (animlist.Count() == countlist.Count()) {
count = countlist[index];

} else if (minlist.Count() && maxlist.Count()) {

int min = minlist[index];
int max = maxlist[index];

if (min != max) {
count = Random_Pick(std::min(min, max), std::max(min, max));
} else {
count = std::min(min, max);
}
}

/**
* Based on the count decided above, spawn the animation type.
*/
for (int i = 0; i < count; ++i) {
AnimClass *anim = new AnimClass(animtype, (Coordinate &)coord, delaylist[index]);
ASSERT(anim != nullptr);
}
}

return true;
}
10 changes: 10 additions & 0 deletions src/extensions/anim/animext.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@

#include "objectext.h"
#include "anim.h"
#include "ttimer.h"
#include "ftimer.h"
#include "typelist.h"


class AnimClass;
Expand Down Expand Up @@ -63,5 +66,12 @@ AnimClassExtension final : public ObjectClassExtension
virtual const AnimClass *This_Const() const override { return reinterpret_cast<const AnimClass *>(ObjectClassExtension::This_Const()); }
virtual RTTIType What_Am_I() const override { return RTTI_ANIM; }

bool Start();
bool Middle();
bool End();

private:
bool Spawn_Animations(const Coordinate &coord, const TypeList<AnimTypeClass *> &animlist, const TypeList<int> &countlist, const TypeList<int> &minlist, const TypeList<int> &maxlist, const TypeList<int>& delaylist);

public:
};
99 changes: 99 additions & 0 deletions src/extensions/anim/animext_hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "tibsun_globals.h"
#include "tibsun_inline.h"
#include "anim.h"
#include "animext.h"
#include "animext_init.h"
#include "animtype.h"
#include "animtypeext.h"
Expand Down Expand Up @@ -136,6 +137,82 @@ static void Anim_Spawn_Particles(AnimClass *this_ptr)
}


/**
* Calls the AnimClass extension start event processor.
*
* @author: CCHyper
*/
DECLARE_PATCH(_AnimClass_Start_Ext_Patch)
{
GET_REGISTER_STATIC(AnimClass *, this_ptr, esi);
static AnimClassExtension *animext;

animext = Extension::Fetch<AnimClassExtension>(this_ptr);

animext->Start();

original_code:
_asm { pop esi }
_asm { pop ebx }
_asm { add esp, 0x24 }
_asm { retn }
}


/**
* Calls the AnimClass extension middle event processor.
*
* @author: CCHyper
*/
DECLARE_PATCH(_AnimClass_Middle_Ext_Patch)
{
GET_REGISTER_STATIC(AnimClass *, this_ptr, esi);
static AnimClassExtension *animext;

animext = Extension::Fetch<AnimClassExtension>(this_ptr);

animext->Middle();

original_code:
_asm { pop edi }
_asm { pop esi }
_asm { pop ebp }
_asm { add esp, 0x38 }
_asm { retn }
}


/**
* Calls the AnimClass extension end event processor.
*
* @author: CCHyper
*/
DECLARE_PATCH(_AnimClass_AI_End_Ext_Patch)
{
GET_REGISTER_STATIC(AnimClass *, this_ptr, esi);
static AnimClassExtension *animext;

animext = Extension::Fetch<AnimClassExtension>(this_ptr);

animext->End();

original_code:
/**
* Restore expected register states.
*/
_asm { mov esi, this_ptr }
_asm { xor ebp, ebp}

/**
* Stolen bytes/code.
*/
_asm { mov edx, [esi+0x64] } // this->Class
_asm { mov ecx, [edx+0x154] } // Class->ChainTo

JMP_REG(edx, 0x00415B03);
}


/**
* #issue-568
*
Expand Down Expand Up @@ -384,6 +461,28 @@ void AnimClassExtension_Hooks()
*/
AnimClassExtension_Init();

Patch_Jump(0x00415F38, &_AnimClass_Start_Ext_Patch);

/**
* This patch removes duplicate return in AnimClass::Middle, so we only
* need to hook one place.
*/
Patch_Jump(0x004162BD, 0x0041637C);

/**
* Unfortunately, this manual patch is required because the code is optimised
* and reuses "this" (ESI), which we need for the ext patch.
*/
Patch_Byte(0x0041636D, 0x8B); // mov esi, [esi+0x68] -> mov ebp, [esi+0x68]
Patch_Byte(0x0041636D+1, 0x6E); // ^
Patch_Byte(0x0041636D+2, 0x68); // ^
Patch_Byte(0x00416370, 0x85); // test esi, esi -> test ebp, ebp
Patch_Byte(0x00416370+1, 0xED); // ^
Patch_Byte(0x00416374, 0x55); // push esi -> push ebp

Patch_Jump(0x0041637C, &_AnimClass_Middle_Ext_Patch);

Patch_Jump(0x00415AFA, &_AnimClass_AI_End_Ext_Patch);
Patch_Jump(0x00415ADA, &_AnimClass_AI_RandomLoop_Randomiser_BugFix_Patch);
//Patch_Jump(0x00413C79, &_AnimClass_Constructor_Init_Class_Values_Patch); // Moved to AnimClassExtension due to patching conflict.
Patch_Jump(0x00414E8F, &_AnimClass_AI_Beginning_Patch);
Expand Down
Loading

0 comments on commit e5193d0

Please sign in to comment.