Is there any way to modify animation paths or "fake" such a change?

Merkava120

Oberleutnant
Joined
Feb 2, 2013
Messages
450
Location
Socially distant
I am getting into SDK modding now and fiddling around with the ranged combat system brought me to this spot:

Code:
if (pPlot->isActiveVisible(false))
    {
        // Range strike entity mission
        CvMissionDefinition kDefiniton;
        kDefiniton.setMissionTime(GC.getMissionInfo(MISSION_RANGE_ATTACK).getTime() * gDLL->getSecsPerTurn());
        kDefiniton.setMissionType(MISSION_RANGE_ATTACK);
        kDefiniton.setPlot(pDefender->plot());
        kDefiniton.setUnit(BATTLE_UNIT_ATTACKER, this);
        kDefiniton.setUnit(BATTLE_UNIT_DEFENDER, pDefender);
        gDLL->getEntityIFace()->AddMission(&kDefiniton);

        //delay death
        pDefender->getGroup()->setMissionTimer(GC.getMissionInfo(MISSION_RANGE_ATTACK).getTime());
    }

This is where the actual animation for the range strike is handled. MISSION_RANGE_ATTACK seems to be hidden within the DLL or EXE somewhere you can't get to it, and it does this:
  1. Move defender entity to face the attacker entity. (Not an animation! If you change the animation for movement ("Run") it will still move the entity, it will just animate them running differently. Movement seems to be hidden in the EXE.)
  2. Animate the attacker shooting at the defender, and the defender taking damage. (This calls KFs which you can change in the KFM using Esemjay's KFM converter, but the actual call itself seems to be hidden in the EXE too.)
  3. Add a graphical effect for the explosion on the tile - this is controlled in EntityEventInfos, calling an effect in EffectInfos, and you can change or remove the effect as you please.
  4. Move the entities back into their positions. (Not an animation, see #1.)
I would like to kill step 1. It doesn't make sense for ranged attacks in a tactical game, especially when I'm modding it to include differences between front armor/side armor/etc. Whenever you shoot at a tank it turns directly toward you - and then away again!

Unfortunately this seems impossible. As far as I can tell by digging around in all of the SDK files, CvMissionDefinition - which is in CvStructs - just defines the variables required for the mission, and it's actually handled within getEntityIFace, which is defined in CvDLLUtilityIFaceBase.h but has no moddable counterpart anywhere else. The actual movement of the entity seems to be attached to ENTITY_EVENT_BOMBARD, which is used for both bombarding and ranged attacks.

I have tried
  • Changing the animations called at different points in the kfm using Esemjay's converter. This does not affect the actual movement of the unit entity, but does make things funny.
  • Setting pDefender to NULL in kDefinition.setUnit() in the snippet above. This works, and the defender does not rotate - but the attacker no longer fires its gun, either!
  • Changing the mission type, the entity used for the mission, and so on - all of that seems to just result in a CTD.
  • Setting the defender entity to be invisible (using setVisible, not the submarine/spy invisibility stuff but the actual entity itself) before starting the mission seemed to do nothing whatsoever - I assume the mission sets the entity to be visible when it is triggered.
  • If you comment out the whole snippet I posted, then there are no animations at all, which sort of solves the problem but not in any satisfying way.
  • There seems to be no way to trigger the animation manually - any way to trigger it has to go through the animation path ANIMATIONPATH_RANGED_STRIKE, which bundles the movement/facing/firing/animation all together itself.


So, anyone know anything I could do to modify the animation path? I have had some ideas:
  • Spawn another defender temporarily in the same direction the attacker is shooting at, but really far away so the player doesn't see it - then use that one for the kDefinition and manually apply the explosion effect to the tile for the actual defender. Really not ideal but I think it would fake it okay.
  • Edit the actual EXE file? Is that even a thing? Edit: No, no it is not. :nono:
  • If there is a way to call specific KFs without going through the normal Civ4 methods that would do it too. But I know of no way.
  • Rewrite the functions for animation paths in the SDK, maybe as overloads of the inaccessible functions, and use trial and error to figure out what they do? That's probably not possible
 
Last edited:

f1rpo

plastics
Joined
May 22, 2014
Messages
1,422
Location
Germany
Changing the mission type [...] seems to just result in a CTD.
That's strange because other mission types are used elsewhere in the code. I've just done a test with the Bombard mission, based on code from CvUnit::bombard:
Spoiler :
Code:
if (getPlot().isActiveVisible(false))
{
   CvMissionDefinition kDefiniton;
   kDefiniton.setMissionTime(GC.getInfo(MISSION_BOMBARD).getTime()
         * gDLL->getSecsPerTurn());
   kDefiniton.setMissionType(MISSION_BOMBARD);
   kDefiniton.setPlot(pDefender->plot());
   kDefiniton.setUnit(BATTLE_UNIT_ATTACKER, this);
   kDefiniton.setUnit(BATTLE_UNIT_DEFENDER, NULL);
   gDLL->getEntityIFace()->AddMission(&kDefiniton);
}
(Replacing the "Range strike entity mission" block)
For the test, I've given the Artillery unit AirRange and AirCombat values in XML, then fired at some Barbarian Archers. Seems to play the same attack animation as for the Range Attack mission, and the defender just stands there as if nothing happened – no turning, no Hurt animation; i.e. this approach seems to kill steps 1 and 2. :undecide:
One could try some other missions, e.g. Air Strike (CvUnit::updateAirStrike, updateAirCombat), Air Bomb (CvUnit::airBomb), but I doubt that they'll play a hurt animation without facing the attacker. That said, since you write that
The actual movement of the entity seems to be attached to ENTITY_EVENT_BOMBARD, which is used for both bombarding and ranged attacks.
perhaps Air Strike is worth a try. The Air Strike mission also gets used for ground-to-air attacks, which brings to mind this thread about a bug in the EXE that prevents the SAM Infantry's anti-air animation from playing. Seems like a related problem, but I don't think the thread will help either; we didn't get anywhere with it.
Spawn another defender temporarily [...].
This sounds promising to me. Will just have to make sure that the dummy unit isn't counted for any statistics that the game keeps track of. Hm, I guess sometimes, when fighting near a non-wrapping edge of the map, there will not be enough room for placing a unit out of sight.
Edit the actual EXE file? Is that even a thing?
Locating the pertinent instructions in disassembled code seems essentially impossible. (I've tried for a little while, to no avail, with the more humble goal of changing the size of the resource bubbles.) Edit: I expect that
CvDLLEntityIFaceBase::AddMission
won't immediately animate the mission, so it would seem difficult to get a proximate entry point from the DLL.
 
Last edited:

Merkava120

Oberleutnant
Joined
Feb 2, 2013
Messages
450
Location
Socially distant
That's strange because other mission types are used elsewhere in the code. I've just done a test with the Bombard mission [...]

Well, this is strange. So, I switched the mission to MISSION_BOMBARD with NULL for the defender, and it did pretty much exactly what I am looking for - shooter shoots, defender doesn't move but effect is displayed. Then I tried switching it back to MISSION_RANGE_ATTACK with a NULL defender, and it...also did exactly what I want?

Last night I used NULL defender and the attacker did nothing at all, with the explosion just appearing out of nowhere - hence I wrote
but the attacker no longer fires its gun, either!

....but now it works fine. :shifty: So, thank you for the help! Whatever was preventing the attacker from doing things, is now no longer...existing.

...and the defender just stands there as if nothing happened...

This is still sort of an issue. In CvUnit::rangestrike(), the actual damage is set near the end with pDefender->setDamage(iDamage, getOwnerINLINE(), false) - the "false" prevents damage from showing up on the unit entity. No idea why they need it "false" in vanilla, but if you change it to true then the damage does get applied to the defender model - just before the attacker shoots, lol. :crazyeye: No amount of setMissionTimer() seems to delay the damage being applied.

On second thought, though, I'm not sure the true/false thing has anything to do with this. There is no damage animation no matter what you set that to, so if you have a unit without damage states like an archer and you do less than, say, 30% damage to it...you might just not see anything at all, ever.

EDIT: Turns out the setMissionTimer() thing only delays the death of the unit, so if you set it to a huge value the damage is applied, the unit disappears, and the flag remains behind. For a really long time.
 
Last edited:

Leoreth

Blue Period
Moderator
Joined
Aug 23, 2009
Messages
36,176
Location
東京藝術大学
Possible yes but impractical. Not to mention that the staff on this forum justifiably have a policy against it.
Moderator Action: Just dropping by to confirm that this is the case. Disassembling the exe is a potential EULA violation and it's best if we stay away from discussing that here.
 

f1rpo

plastics
Joined
May 22, 2014
Messages
1,422
Location
Germany
No idea why they need it "false" in vanilla, but if you change it to true then the damage does get applied to the defender model - just before the attacker shoots [...].
My guess is that the addMission call will take care of updating the damage state after the animations are through – if a BATTLE_UNIT_DEFENDER is set. If we don't set one and just update the damage state right away, then the update gets applied as soon as the DLL returns control to the EXE. (What gets updated, to be clear, is apparently the texture in the case of a mechanized defender, and the number of unit graphics.) It should be possible to make the
NotifyEntity(MISSION_DAMAGE);
call (that normally happens in setDamage if the bNotifyEntity param is set) at the next game update (CvGame::update). Or after a few game updates; I think the updates happen every 250 ms. Just a bit tedious to implement as the unit entities to be notified need to be stored somewhere along with a countdown (remaining delay). Could add something like
std::vector<std:: pair<CvUnit*,int> > m_unitsWithDelayedDamage
to CvGame for this, along with a public function that CvUnit::rangeStrike can call.
There is no damage animation no matter what you set that to [...].
Right, we're not getting a flinch/damage/hurt animation (whatever the proper name may be) out of the defender this way. Maybe passing the defender (instead of NULL) to the Air Strike or Air Bomb mission would accomplish that – and wouldn't rotate the defender (since an air strike normally comes from straight above). Or perhaps you've already tried (I haven't).
 

Merkava120

Oberleutnant
Joined
Feb 2, 2013
Messages
450
Location
Socially distant
Or after a few game updates; I think the updates happen every 250 ms. Just a bit tedious to implement as the unit entities to be notified need to be stored somewhere along with a countdown (remaining delay). Could add something like
std::vector<std:: pair<CvUnit*,int> > m_unitsWithDelayedDamageto CvGame for this, along with a public function that CvUnit::rangeStrike can call.

That's a great idea! I am dealing with a (completely separate) CTD issue but once I have that figured out I will give this a shot.

Maybe passing the defender (instead of NULL) to the Air Strike or Air Bomb mission would accomplish that –

Oh yes, I forgot to mention that I tried Air Strike. When I tried it, neither the attacker nor defender moved at all, but there was an effect displayed IIRC. I suspect it's because the tank KFM has no airstrike animation. Since MISSION_AIRSTRIKE calls ENTITY_EVENT_AIRSTRIKE which calls ANIMATIONPATH_AIRSTRIKE, it's possible that changing the entity event or the animation path could make it work for land units - or maybe adding the ranged attack in as an airstrike animation in the KFM could work. Have to figure out my current CTD before I can try those, but stay tuned...
 

f1rpo

plastics
Joined
May 22, 2014
Messages
1,422
Location
Germany
My thinking was that the anti-air animation will fall back on the range attack animation. But that would mean that attacker and defender need to be switched ...
Doesn't seem to work. I guess if the BATTLE_UNIT_ATTACKER doesn't have an air strike animation, then the anti-air animation of the BATTLE_UNIT_DEFENDER isn't used either. My Artillery unit (actual attacker) does nothing when I try this. The Archer (actual defender) does play the Hurt animation. It also does something undesirable that I can't quite interpret; readying for battle or so (but at least doesn't rotate). Combining this anti-air experiment with the NULL-defender range attack looks actually pretty good:
Spoiler :
Code:
pDefender->setDamage(iUnitDamage, getOwnerINLINE(), false);
if (pPlot->isActiveVisible(false))
{
   CvMissionDefinition kDefiniton;
   kDefiniton.setMissionTime(GC.getMissionInfo(MISSION_RANGE_ATTACK).getTime()
         * gDLL->getSecsPerTurn());
   kDefiniton.setMissionType(MISSION_RANGE_ATTACK);
   kDefiniton.setPlot(pDefender->plot());
   kDefiniton.setUnit(BATTLE_UNIT_ATTACKER, this);
   // The only line I've changed:
   kDefiniton.setUnit(BATTLE_UNIT_DEFENDER, /*pDefender*/NULL);
   gDLL->getEntityIFace()->AddMission(&kDefiniton);
   pDefender->getGroup()->setMissionTimer(GC.getMissionInfo(MISSION_RANGE_ATTACK).getTime());

   // Based on code in CvUnit::updateAirCombat
   CvAirMissionDefinition kAirMission;
   kAirMission.setMissionType(MISSION_AIRSTRIKE);
   kAirMission.setUnit(BATTLE_UNIT_ATTACKER, pDefender); // !!
   kAirMission.setUnit(BATTLE_UNIT_DEFENDER, this); // !!
   kAirMission.setPlot(pDefender->plot());
   kAirMission.setMissionTime(GC.getInfo(MISSION_AIRSTRIKE).getTime()
         * gDLL->getSecsPerTurn());
    gDLL->getEntityIFace()->AddMission(&kAirMission);
}
Btw, the Unofficial Patch comments out the CvSelectionGroup::setMissionTimer call. Comment in the code:
UNOFFICIAL_PATCH (jdog5000) said:
mission timer is not used like this in any other part of code, so it might cause OOS issues ... at worst I think unit dies before animation is complete, so no real harm in commenting it out.
I don't know if a group's mission timer can really affect the synchronized game state. Not seeing any difference if I uncomment that line. But, then, my range strikes have a damage limit, so they can't kill the defender.
Have to figure out my current CTD before I can try those, but stay tuned...
Sure, no pressure.
 

Merkava120

Oberleutnant
Joined
Feb 2, 2013
Messages
450
Location
Socially distant
All right, found the CTD (turns out you need to initialize variables before you call them, who would've guessed) and tried some variations on the mission.
  • Changing the entity event for air strike mission to entity_event_bombard in the MissionInfos.xml -> doesn't do anything.
  • Changing the animation path for the airstrike entity event to the range attack animation path in EntityEventInfos.xml -> doesn't do anything
  • Changing the animation path in AnimationPathInfos so it actually includes all the same stuff as the ranged attack animation path -> doesn't do anything
  • I also tried changing the tank's kfm to include air animations. But it turns out that air units don't have any more animations than tanks, they both use "range attack" when they are supposed to do anything involving combat. So that's a dead end.
Combining this anti-air experiment with the NULL-defender range attack looks actually pretty good:

I have tried a few variations on this - one exactly as you did, one with the AnimationPathInfos changed as described above, one with the defender's plot in the airstrike mission definitions changed to the attacker's plot, and one with both the AnimPath and the plot changed. When you did yours, did the defender move to face north before getting shot at? That's happening the first two times in mine, although if you have other units on the tile they seem to cover up the "driving north" part. Then there is that weird flicker thing you mentioned, that might be related to <bUpdateFormation> in EntityEventInfos, maybe? Also a fade-in animation, which I can definitely remove in the kfm if needed.

When I set the plot to the attacker's plot, the defender no longer moves to face north, but also no longer has a "get hit" animation at all, sadly. At that point it looks exactly like the bombard mission...
 

Merkava120

Oberleutnant
Joined
Feb 2, 2013
Messages
450
Location
Socially distant
Also thanks for the heads up about the mission timer thing. Stuff like that makes me want to merge my modding into Advanced Civ or Unofficial Patch or something, maybe I will move that up the priority list
 

f1rpo

plastics
Joined
May 22, 2014
Messages
1,422
Location
Germany
[...] did the defender move to face north before getting shot at?
My targets were already facing north because that's apparently the default when placing units through WorldBuilder; so I hadn't noticed. That's too bad. Perhaps this happens because air strike paths always(?) start out in a northerly direction. Letting the defender paradrop instead of air-striking also doesn't work (and causes the defender to disappear for a while, or teleport out of sight, and then run back into position). I've also tried setting up a battle, but I think the defender will always turn to face the attacker in a battle ... Oof, I don't think there's much more fun to be had, for me, experimenting with this.
 

Merkava120

Oberleutnant
Joined
Feb 2, 2013
Messages
450
Location
Socially distant
If you are already digging into this could you see if there is anything that can be done here?

I'll see what I can do. I may not be able to fix it but I can at least put together a list of what all the missions, entity events and animation paths actually do, since that seems to be the source of many issues, and ranged combat provides an easy way to test them anyway.

Oof, I don't think there's much more fun to be had, for me, experimenting with this.

No worries, you have already been super helpful!! Figuring out how to get the defender to quit facing the attacker with RANGE ATTACK has gotten me 90% the way there.

and causes the defender to disappear for a while, or teleport out of sight, and then run back into position

As a side note, this seems to be a general glitch with paradropping - I've had paratroopers doing that for years (some after parachuting, some without parachuting).

Edit: for what it's worth I remember that the paradropping and air striking were using the same path or mission or something. I'm definitely going to do some animation testing and see what I can figure out about all this.
 
Top Bottom