Understanding CanSaveUnit & UnitKilledInCombat

Pazyryk

Deity
Joined
Jun 13, 2008
Messages
3,584
I was playing around with these two GameEvents to get a handle on how they work. Documenting here for my own reference. These are definitely a better alternative to Events like EndCombatSim, etc., since those are there are for the purpose of graphics rather than game rule changes.

CanSaveUnit fires twice (assuming you don't return true), once before and once after UnitKilledInCombat. As far as I can tell this only fires if the unit is going to die. It fires for non-combat extinction too, like a setter settling (also twice, but in this case there is no UnitKilledInCombat call).

If you set CanSaveUnit to return true, then it will still be followed by UnitKilledInCombat -- but the "supposed-to-die" unit doesn't die. The game graphics seem to get a little confused in this situation if the "supposed-to-die" unit was the attacker: graphics and the game engine seem to disagree on where the unit is exactly. Graphics seem OK if the saved unit was the defender.

UnitKilledInCombat gives you killerID, ownerID, unitTypeID. The last argument is definitely the killed unit's ID from the Units table, not its instance integer.

So you could use the first call to CanSaveUnit to ... well ... save a unit, getting the unit object from playerID, unitID. I'm not sure what happens if you try to save the unit on the second call rather than the first.

UnitKilledInCombat is useful by itself if you only want playerIDs and the unit Type killed. But using these together, you could get more detailed unit info about the victim (while the unit object still exists) such as level, original owner, etc. This could be held in a file local variable and used later with the UnitKilledInCombat call, since that is always preceded by CanSaveUnit.

Here my Fire Tuner session:

>GameEvents.CanSaveUnit.Add(function(...) print("Listener - CanSaveUnit( player, unitID ): ", unpack(arg)); return false end)
>GameEvents.UnitKilledInCombat.Add(function(...) print("Listener - UnitKilledInCombat( killerID, ownerID, unitID ): ", unpack(arg)) end)

--My damaged warrior attacks barb and dies in combat:

ActionInfoPanel: Listener - CanSaveUnit( player, unitID ): 0 16385
ActionInfoPanel: Listener - UnitKilledInCombat( killerID, ownerID, unitID ): 63 0 83
ActionInfoPanel: Listener - CanSaveUnit( player, unitID ): 0 16385

--83 is the killed UnitTypeID! (UNIT_WARRIOR)
--Changed CanSaveUnit return to true; attacked weak barb unit:

ActionInfoPanel: Listener - CanSaveUnit( player, unitID ): 63 114701
ActionInfoPanel: Listener - UnitKilledInCombat( killerID, ownerID, unitID ): 0 63 85

--Graphics are all screwed up, but barb lived
--Barb attacks weak scout:

ActionInfoPanel: Listener - CanSaveUnit( player, unitID ): 0 32768
ActionInfoPanel: Listener - UnitKilledInCombat( killerID, ownerID, unitID ): 63 0 82

--Scout alive, everyone where they were originally
 
I'm not sure what happens if you try to save the unit on the second call rather than the first.

Probably very bad things. The only way you can get two calls for the same unit is when the first call has the "delayed death" flag set to true - which causes partial clean-up of the object but not actual unit death. If you save the unit on the second call, it will be missing lots of necessary info for its next turn - so I'd expect a CTD sometime soon after.
 
Probably very bad things. The only way you can get two calls for the same unit is when the first call has the "delayed death" flag set to true - which causes partial clean-up of the object but not actual unit death. If you save the unit on the second call, it will be missing lots of necessary info for its next turn - so I'd expect a CTD sometime soon after.

Even with the first call returning true, the dll still calls UnitKilledInCombat. Is there no cleanup in this case? (I haven't played on very long to see if there are CTDs. But as I said in OP, I did see some graphic glitches when the saved unit was the attacker.)
 
From digging deeper into the DLL, both events are very specific to the DLC scenario they were written for.

UnitKilledInCombat came first and is used in the Korean scenario, and would be better called ChangeScoreWhenUnitKilledInSpecifcWay. There are over 90 places where pUnit->kill() is called in the C++ code but only three of those also send the event. A lot of the other calls are when units can't be placed on the map (so not in combat) but some seem to be obscure ways to kill units - where the event should be triggered but isn't.

CanSaveUnit came second and is used in the Civil War scenario, and would be better called CanGeneralMakeHeroicEscape. The event is called at the start of the pUnit->kill() method, but that method doesn't return a value, so even if the unit is saved the main code continues to assume the unit is dead continues normally, awarding XP, yields, even converting it to a civ unit if it was a barb, etc. Which is why saving a unit also triggers the UnitKilledInCombat event. It also explains why the graphics are foobar, as GGs don't die fighting they are just vapourised by the graphics engine. CanSaveUnit probably only works reliably for civilians - possibly useful for a Dragon Pass type scenario for heroic escapes.

Trying to get the two events to reliably work together sensibly or in wider contexts than originally required for those specific scenarios are almost certainly flawed.
 
Back
Top Bottom