Character controller, round 8: rewrite attack animation logic (bug #4127)
Fixes #4127 (closed).
This merges the attack animation sections that are supposed to play continuously and does the remaining necessary reorganization of attack animations.
Now, I believe I will have to explain the entirety of the existing OpenMW logic and the proposed logic because updateWeaponState is the worst function ever written. Here goes...
Common Concepts
A section is a part of an attack animation group defined by two text keys. It's a bit of the attack animation that plays during a specific attack state. Each melee weapon type has 3 attack types - except tools and ranged weapons, which either have no attack type or have a special attack type - and each attack type for each weapon type defines its own attack animation with its own sections. A text key might be missing or may be set to use the same time as a different key, in which case the behavior is usually unpredictable (the animation might fail to play, or skip to the wrong point, or get stuck), and we employ a set of workarounds to avoid some of such issues.
Biped hand-to-hand combat conforms to melee rules. Non-biped creature combat adds a special set of 3 random attack animations which don't have an attack type. Non-biped creatures use these animations if they use hand-to-hand combat (think rats and cliff racers) or if their weapon animations are missing (think poorly done third-party content).
The attack type is determined randomly for non-playable actors. For the player character, it depends on the "best attack" setting. If it's disabled, the attack type is based on the player's movement. If it's enabled, the attack type is based on the damage type that gives the best average damage.
Attack states' function in the character controller is to time the attack correctly depending on the player's and combat AI's input, successfully play multiple sections of the attack animation and, most importantly, play the hit text key, as well as play the weapon swish sound and set up the attack strength before that happens.
One important element of a weapon animation is the so-called wind-up, which controls the attack strength. Naturally and usually, the more time is spent on preparing an attack, the more damage it deals. This attack strength is determined by the wind-up section completion. The player prepares an attack by holding the attack button, while the combat AI sets up a random attack strength it expects the actor to use and waits for the animation to play to the point the sufficient swing and thus attack strength is reached.
After wind-up, the weapon animation skips a bit depending on the attack strength (the weapon's swing must be reversed) up to the hit key, and then it actually ends, and what follows is the independent Follow section (think recoil or crossbow reloading).
However, random attack animations are much less advanced than a normal weapon animation group and don't have any "release" or "wind-up", while a weapon animation may actually not properly define a wind-up section - it must define its text keys to be played properly, but these text keys might be set to be handled at the same time (think Bloodmoon rieklings or some modded weapon animations). In this case we try to fall back to random attack strength. Additionally, the player may release the attack button before the wind-up section is reached, which must also be handled delicately. More things to remember is that the action of releasing a weapon breaks the actor's invisbility effect, and that the weapon swish sound's volume and pitch (currently just pitch) depends on the attack strength.
Spellcasting animations work slightly differently and use their own attack state, I'm not going to cover them and I didn't change anything about them in this MR.
One extra element of ranged weapons is that the actor's torso is pitched to the actor's facing direction when a ranged weapon is being prepared. There are artificial smooth transitions done that are based on the pre-wind-up section and follow section completion. For crossbows, the transition is sped up 10 times, though it doesn't work exactly like Morrowind at the moment, which appears to pitch the torso back before playing the reloading animation.
Knockdown and knockout cancel the on-going attack animation. Hit recoil prevents the actor from attacking and plays on the actor's lower body, and it's cancelled when the attack animation ends. All these states prevent weapons from being equipped or unequipped. Abrupt weapon type changes (caused by summon effects or Equip script instruction) cancel the on-going attack. Running out of ammunition prevents the actor from attacking and cancels the attack if it's being prepared.
Differences
Before (0.48.0)
Definitions:
- Pre-wind-up section:
starttomin attack - Pre-wind-up state:
UpperCharState_StartToMinAttack(==UpperBodyState::AttackPreWindUp) - Wind-up section:
min attacktomax attack - Wind-up state:
UpperCharState_MinAttackToMaxAttack(==UpperBodyState::AttackWindUp) - Release section:
max attacktomin hit - Release state:
UpperCharState_MaxAttackToMinHit(==UpperBodyState::AttackRelease) - Hit section:
min hittohitfor melee,min hittoreleasefor ranged. - Hit state:
UpperCharState_MinHitToHit(==UpperBodyState::AttackHit) - Follow section:
follow starttofollow stopfor ranged,small/medium/large follow starttosmall/medium/large follow stopfor melee - Follow state:
UpperCharState_FollowStartToFollowStop(==UpperBodyState::AttackEnd) - Generic animation group:
starttostop
These definitions are not necessarily what Bethesda used to refer to the Morrowind equivalent of however they implemented the attack animations but I find these somewhat intuitive to follow.
Tools
Tools are special. Their pseudo-attack animation is played as a generic animation group in the Follow state. The tools are "released" at the first frame of the pseudo-attack and don't "hit", and their animation is played to its end. Their animation is disabled using the same rules as the end animation for melee weapons, so the animation's end cancels stagger, resets the weapon state to weapon equipped state, etc. If the animation is missing, tools still work because they don't depend on the presence of "hit" key.
Not tools
When the attack state is set by the input action manager or combat AI, the pre-wind-up section starts playing for any other weapon animation - as long as the attack animation is present! If it's not present, the attack won't start and the character won't enter Pre-wind-up state.
Random attack animations
They are played as a generic animation group, never leave the pre-wind-up state and are disabled automatically when they end. They have their attack strength set up and swish sound played in their first frame.
Pre-wind-up state
The character controller keeps track of the input attack state. It might be unset prematurely depending on combat AI's or the player's mood. If this happens, the pre-wind-up state is usually cancelled and the animation skips right to the release section. If this doesn't happen or the current wind-up heuristic detected that there is no wind-up section to play, the pre-wind-up section is cancelled and the wind-up section starts playing and the Wind-up state is set. During this state, ranged weapon torso pitching gradually transitions to 1 depending on the section's completion.
Wind-up state
The character controller still keeps track of the attack state, and handles this state when the attack is released. Now the invisibility is broken, the attack strength is set depending on the wind-up section completion (unless the wind-up section is a stub, in which case it'll be random) and the swish sound is played for melee weapons and werewolf attacks. Then we are entering the Release state and section.
The player might be holding the attack button for longer than the section plays. In this case the final 0.1% of the section will play with the goal of preventing body position desync.
During this state, torso pitching is set to 1.
Release state
A bit of the release section is skipped depending on the wind-up section completion. If the wind-up section is a stub and is still played, I think it considers it 0% complete and skips the entire section. Then the character controller checks if this section is over, disables it and moves on to the hit section.
During this state, torso pitching is set to 1.
Hit state
This section is just played with no complications. When it ends, the attack is processed by gameplay, and the follow section is picked depending on the attack strength
During this state, torso pitching is set to 1.
Follow state
The animation group is played from its start to its stop key with no complications, and when it ends, the attack animation is considered complete.
During this state, torso pitching gradually transitions to 0 depending on the follow animation group's completion.
Notes
updateWeaponState constantly updates the current attack strength depending on the wind-up progression to let the combat AI know that the swing is in progress. This doesn't work properly if there's no wind-up to speak of, though this doesn't lead to obvious ill effects at the moment.
After (MR)
Definitions:
- Wind-up section:
starttomax attack - Wind-up state:
UpperBodyState::AttackWindUp - Release section:
max attacktohitfor melee,max attacktoreleasefor ranged - Release state:
UpperBodyState::AttackRelease - Follow section:
follow starttofollow stopfor ranged,small/medium/large follow starttosmall/medium/large follow stopfor melee - Follow state:
UpperBodyState::AttackEnd - Generic animation group:
starttostop
Tools
Tools are not as special. They're played as a generic animation group and enter wind-up state.
Not tools
When the attack state is set by the input action manager or combat AI, the wind-up section starts playing for any other weapon animation regardless of whether the animation group is available or not.
Random attack animations
Actually no special behavior here either, but they are still played as a generic animation group.
Wind-up state
This state is shared between every weapon attack animation. Animations that have wind-up or are supposed to have wind-up are stuck in this state until the input attack state is disabled.
Once that happens, or for tools and random attack animations, this wind-up state ends immediately and the attack potentially briefly enters the Release state.
Release state
When this state is reached, invisibility is broken and the swish sound is played (not for tools or ranged weapons).
Tools
They work, enter the follow state and we can forget about them now.
Not tools
The attack strength is calculated depending on the wind-up section completion. This time it's calculated depending on the current animation time relative to the min attack and max attack keys. Random attack animations and invalid text key definitions lead to the use a special attack strength of -1, which is immediately replaced by a random attack strength, and is acknowledged by the combat AI, which will now try and disable the attack state regardless of the swing completion. The attack strength is not updated until this point either.
Torso pitching is calculated depending on the current animation's time relative to start and min attack keys.
Random attack animations
They enter the Follow section/state after doing the bit above and end.
Non-random attack animations
Release section is played to the hit key. A bit of the section is skipped depending on the attack strength (as long as the wind-up section is defined). The start point is recalculated depending on the position of the max attack key relative to min hit and hit keys. After the hit key is reached or it's detected that the animation didn't play, the attack enters the follow section/state.
As before, the animation only plays the release section if the wind-up text keys are reached.
Follow state
Completely unchanged.
Notes
The tools/random attack animations state acrobatics are necessary to avoid calling breakInvisibility, playSwishSound and calculating the attack strength multiple times (up to 4 times in the existing logic).
It's now technically possible for the attack animation not to have min attack or min hit keys or even be missing and it should (probably) work vaguely correctly and not get stuck. Though if there's no hit key, there's no hit, yet you'll hear an attack sound.
Unfortunately, while some of the calculations are for supposedly constant factors, they're not actually constant because the source animation might change at any time (e.g. if the actor turns into a werewolf, or the player goes in a different camera mode, etc.)
I tried as carefully as possible to make it possible for a missing wind-up and/or release section to be handled within the frame the attack starts or the previous section ends.
I'll note again that the combat AI should now properly handle unavailable wind-up.
Also note that I removed the body pos desync hack used for the final frame of the wind-up animation because I couldn't see any difference between it being there and it not being there! But I might have missed something glaringly obvious.
I've also patched in some missing things I've patched in 0.48.0 version of stagger fixes but didn't in 0.49.0 version, but they shouldn't normally lead to any noticeable difference.
I've set @akortunov and @Assumeru as reviewers as they're the only other available people that worked the most with/contributed a major part of the existing logic, and would appreciate their input.
And hopefully now all most of it just works.