Enhanced Damage Dealt Counter gadget

Enhanced Damage Dealt Counter gadget

Discuss Lua based Spring scripts (LuaUI widgets, mission scripts, gaia scripts, mod-rules scripts, scripted keybindings, etc...)

Moderator: Moderators

Post Reply
[Fx]Doo
Posts: 64
Joined: 30 Aug 2013, 16:39

Enhanced Damage Dealt Counter gadget

Post by [Fx]Doo » 19 Feb 2017, 18:42

Hi!

Before going into the subject, i'd like to explain what motivates me to develop such a gadget.

I am a BA player and i often like to pay attention to endgame stats and check who dealt most damages and such.
After large amount of games i finally came to realise that sometimes the amount of damage dealt feels wrong, and is understated.
I looked into this and tried to figure why, and found that it was mostly due to the fact the damage dealt indirectly to enemy units is not counted in the damage dealt statistic. As an exemple, blowing a unit with a huge blow in an enemy's base wont give credit for the whole base destruction to the unit that was actuallly responsible for this. In a similar way, causing a chain reaction (i.e. wind generators or metal makers in BA) will only give you credits for the very firsts destroyed units (the ones you actually dealt direct damages to).

The notion of indirect damages doesn't seem to exist in spring, and i wanted to introduce it via a lua gadget.

For this matter, I figured the easiest way to do this was by triggering the gadget each time a unit is damaged (on the predamaged frame) and run a script that would do this:
-What is damaging unitID? -- Weapon? SelfDestruction? DeathExplosion? Debris? This is the WeaponDefID.
- Is it an allied or an enemy unit that is blowing? If it is a enemy unit, the damages will be counted properly and there is no need for a fix.
- If it is debris or deathExplosion, then it means the attacker was killed by something. -- What unit was it ? (--This is Spring.GetLastAttacker(attackerID))
- Then, we can say that the reason unitID is dying is basically the same reason attackerID is dying. It is realattacker = Spring.GetLastAttacker(attackerID)
- Nullify damages from attackerID to unitID, and add new damages from realattacker to unitID, with the same damage amount and same weaponDefID.

Because of this script, realattacker takes credits for both the unitID and attackerID kills, and not only attackerID's. Also, the next run of that script for the n+2th unit in the chain reaction and, because the lastattacker has been properly set in the last run, it will still keep the same "realattacker" for the whole chain reaction.

Here is the actual code that gets through these steps:

Code: Select all

function gadget:GetInfo()
   return {
      name         = "DamageDealt Handler",
      desc         = "Handles DamageDealt Values for chainreactions",
      author       = "Doo",
      date         = "26/04/2016",
      license      = "GPL 2.0 or later", -- should be compatible with Spring
      layer        = 0,
      enabled      = true
   }
end


if (gadgetHandler:IsSyncedCode()) then  --Sync

function gadget:UnitPreDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponDefID, projectileID, attackerID, attackerDefID, attackerTeam)
Spring.Echo(weaponDefID)
hp = Spring.GetUnitHealth(unitID)
WeaponName = WeaponDefs[weaponDefID]["name"]
Spring.Echo(attackerDefID)
ExplodeAs = UnitDefs[attackerDefID].deathExplosion
Spring.Echo(WeaponName)	
Spring.Echo(ExplodeAs) 
		if WeaponName == ExplodeAs then -- Checks if the unitID died in attackerID's death explosion or debris, otherwise skips)
			Spring.Echo(unitID.." has been caught in "..attackerID.."'s blow")
			if Spring.AreTeamsAllied(unitTeam, attackerTeam) == true then -- the gadget should not erase the damages dealt by attackerID's explosion to its enemies
				Spring.Echo(unitID.." and "..attackerID.." are allied")
				if Spring.GetUnitLastAttacker(attackerID) == nil then realattacker = attackerID else
				realattacker = Spring.GetUnitLastAttacker(attackerID)
				end
				Spring.Echo("Last "..attackerID.."'s attacker was "..realattacker)
				Spring.AddUnitDamage(unitID, damage, 0, realattacker, weaponDefID)
				Spring.Echo("Added "..damage.." damages to "..unitID.." from "..realattacker)
				return 0
				
			end
	end
end
function gadget:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam)
Spring.Echo(attackerID.." of team "..attackerTeam.." has killed "..unitID.." of team "..unitTeam..".")
end
end
Now, here are the issues i'm dealing with:
-Debris damages have no "owner". They are not attached to any attackerID nor attackerDefID and ExplodeAs = UnitDefs[attackerDefID].deathExplosion will cause an error
-Prolly related to this issue, unitIDs of dead units only stay valid for a short amount of time that is most likely less than a chain reaction duration and ExplodeAs = UnitDefs[attackerDefID].deathExplosion will cause an error, as well as Spring.AddUnitDamage(unitID, damage, 0, realattacker, weaponDefID) because both realattacker and attackerID wont be defined.
-Some units have the same selfdestructas and explodeas weaponDefIDs which would cause some mistakes in the process of giving credits to the right unit. (If such a unit is attacked before it self destructs, the unit that attacked it will take credit for the damages of the blow when it shouldn't).

I've been thinking of how to get over this.
-For now i've decided to discard debris damages until i'm able to properly run this for deathExplosions.
-For this script to run, it needs unitIDs to be defined for a longer amount of time after unit's death, is there a way to do so? or is it hardcoded in the engine and is unlikely to be changed ? -- On a side note, that also means that if the damages dealt by a unit occurs after that unit's death, it wont have any "owner" and wont be counted (i.e starbust launchers getting killed between launch and blow).
-How does SelfDestruct work. Does it deal infinite damages to a unitID with a defined weaponDefID = -5 and a defined attackerID = unitID? Is there a way to discern SelfDestruction and Death?

I would gladly welcome any advices and tips on how to get over those 3 main issues that i'm facing, or any tips regarding the code's design.
Attachments
DamageHandler.lua
(3.46 KiB) Downloaded 1 time
1 x

Kloot
Spring Developer
Posts: 1845
Joined: 08 Oct 2006, 16:58

Re: Enhanced Damage Dealt Counter gadget

Post by Kloot » 20 Feb 2017, 16:42

it needs unitIDs to be defined for a longer amount of time after unit's death, is there a way to do so?
You would have to listen for all UnitDestroyed events, and store any information that your delayed damage tracker might need in the future in a table indexed by ID's of dead units.
or is it hardcoded in the engine and is unlikely to be changed?
Pretty much.
Is there a way to discern SelfDestruction and Death?
At present, only if the self-destruct and death-explosion use different weapondefs.
1 x

[Fx]Doo
Posts: 64
Joined: 30 Aug 2013, 16:39

Re: Enhanced Damage Dealt Counter gadget

Post by [Fx]Doo » 23 Feb 2017, 09:40

Kloot wrote:
it needs unitIDs to be defined for a longer amount of time after unit's death, is there a way to do so?
You would have to listen for all UnitDestroyed events, and store any information that your delayed damage tracker might need in the future in a table indexed by ID's of dead units.
I need to get informations on "what killed what" and "how" and storing data over any dead unit isn't enough. The link between just dead unit and a dead attacker isn't always accessible either on predamage or unitDestroyed events.
Best exemple of that is armwin (Wind Gens) chain reactions in BA. The delay between one's explosion and second's death is high enough for the unit ID of the first to be invalid at second's death.

Once i can get this, storing data would still be a pain cause i would still need to Spring.AddUnitDamage from a unit, though i can spawn a fake unit that would take credit for the right team.

After thinking a bit i don't think there is a way out of it, i need to have unitIDs to be valid for a longer time. Be it the whole time of the chain reaction it's involved in or just 10-15 seconds after death and store data -- spawn uid[unitID]=Spring.CreateUnit with fake one that would only get credits for the damages when attackers are already dead...

Though this is a minor issue gameplay wise, i think it only makes sense that a unitID's should be valid at least all the way to its very last weapon anim/ projectile hit / last debris hitting ground. (after testing even a pw's id loses its validity before its last emg shot even hits its target)

Edit: What would it take to change that? What would be the costs on engine performances and what would it change to the existing gadgets/widgets if that was the case? Can it even be considered as an acceptable change to the engine for forthcoming updates?
I guess the fact that it hasn't been done yet must mean that it would be either unstable or ressources hungry, yet i think this would be an interesting feature to the engine.
1 x

User avatar
Silentwings
Moderator
Posts: 3450
Joined: 25 Oct 2008, 00:23

Re: Enhanced Damage Dealt Counter gadget

Post by Silentwings » 23 Feb 2017, 09:58

"what killed what" and "how"
As a widget, you can't get all the information on this. For example, if you are hit by a projectile shot from outside of your own LOS, you shouldn't know who/what fired it.

Also, because of chain explosions and the possibility of multiple shots hitting the same unit, the true explanation for "how" could be very complicated.
1 x

[Fx]Doo
Posts: 64
Joined: 30 Aug 2013, 16:39

Re: Enhanced Damage Dealt Counter gadget

Post by [Fx]Doo » 23 Feb 2017, 13:31

This is a synced gadget. Indeed the player shouldn't know where did the shot come from, and that is not the point of this gadget.
As for answering what killed what and how, it is indeed hard -- impossible -- right now, but would be possible if dead units kept their ids valid.

Anyway, however you put it, having a longer timelap in which the id of a dead unit would stay valid seems all beneficial to me. Would allow for a whole lot of game statistics, and should make some lua gadget's code a lot more easier -- not just this one's.

Also it is pretty much unlikely that a unit would die of 2 different damage sources occuring on the same frame.
1 x

Kloot
Spring Developer
Posts: 1845
Joined: 08 Oct 2006, 16:58

Re: Enhanced Damage Dealt Counter gadget

Post by Kloot » 26 Feb 2017, 21:32

Best exemple of that is armwin (Wind Gens) chain reactions in BA. The delay between one's explosion and second's death is high enough for the unit ID of the first to be invalid at second's death
1) on UnitDestroyed (or in UnitDamaged if the amount of damage is fatal), find all units within the victim's death/selfd explosion radius and tag them by its unitID plus other attributes you care about
2) on UnitDamaged, if attackerID and weaponDefID are both nil then check if the unit has been tagged and accredit damage dealt accordingly

Workarounds are also possible for orphaned weapon and piece projectiles (debris) via the ProjectileCreated event, but that comes with a performance penalty.
i think it only makes sense that a unitID's should be valid at least all the way to its very last weapon anim/ projectile hit / last debris hitting ground.
The amount of time this takes 1) is unpredictable, 2) could theoretically be infinite, and 3) would require costly dependency tracking, so it's unlikely to be integrated into the engine.
1 x

[Fx]Doo
Posts: 64
Joined: 30 Aug 2013, 16:39

Re: Enhanced Damage Dealt Counter gadget

Post by [Fx]Doo » 27 Feb 2017, 05:13

Kloot wrote:
Best exemple of that is armwin (Wind Gens) chain reactions in BA. The delay between one's explosion and second's death is high enough for the unit ID of the first to be invalid at second's death
1) on UnitDestroyed (or in UnitDamaged if the amount of damage is fatal), find all units within the victim's death/selfd explosion radius and tag them by its unitID plus other attributes you care about
2) on UnitDamaged, if attackerID and weaponDefID are both nil then check if the unit has been tagged and accredit damage dealt accordingly

Workarounds are also possible for orphaned weapon and piece projectiles (debris) via the ProjectileCreated event, but that comes with a performance penalty.
i think it only makes sense that a unitID's should be valid at least all the way to its very last weapon anim/ projectile hit / last debris hitting ground.
The amount of time this takes 1) is unpredictable, 2) could theoretically be infinite, and 3) would require costly dependency tracking, so it's unlikely to be integrated into the engine.

That is indeed helpful !

I think i get a approximate picture of what has to be done by now.
I'll try working on that asap and i'll give you some feedback if i'm getting through with it !

Code: Select all

function gadget:GetInfo()
	return {
		name = "Enhanced Damage Handler",
		desc = "Appropriatly accounts units and teams for the damages dealt",
		author = "[Fx]Doo",
		date = "25th of October 2016",
		license = "Free",
		layer = 0,
		enabled = true
	}
end

if (gadgetHandler:IsSyncedCode()) then
ProjectileOwner = {}
UnitsCaughtInBlowOf = {}
LastAttacker = {}
Attacker = {}
DamageHandler = {}
spAreAllied = Spring.AreTeamsAllied
spLastAttacker = Spring.GetUnitLastAttacker
spUnitTeam = Spring.GetUnitTeam
spGetDefID = Spring.GetUnitDefID
ProjectilesOf = {}

function gadget:GameStart() -- spawn damage handlers for each active team
for i = 1,#team do -- not completed yet !!
DamageHandler[i] = {}
DamageHandler[i].id = Spring.CreateUnit("damagehandler",0,0,0,"north",i)
DamageHandler[i].team = i
end
end

function gadget:ProjectileCreated(proID,proOwner) -- if projectiles are created, register their owner, i'd like to find a way to check for projectiles owned by unitID on UnitDestroyed event instead.
ProjectileOwner[proID] = {}
ProjectileOwner[proID].id = proOwner
ProjectileOwner[proID].team = spUnitTeam(proOwner)
end

function gadget:UnitDestroyed(unitID, unitDefID, unitTeam, attackerID, attackerDefID, attackerTeam) --If a unit dies, register the units that it's susceptible to affect and register unit as these units' attacker
x, y, z = Spring.GetUnitPosition(unitID)
xplrange = Spring.GetUnitWeaponDamages(unitID, "explode", "damageAreaOfEffect")
UnitsCaughtInBlowOf[unitID] = Spring.GetUnitsInSphere(x,y,z, xplrange +200)
for i = 1,#UnitsCaughtInBlowOf[unitID] do
	Attacker[UnitsCaughtInBlowOf[unitID][i]] = {}
	Attacker[UnitsCaughtInBlowOf[unitID][i]].id = unitID
	Attacker[UnitsCaughtInBlowOf[unitID][i]].team = spUnitTeam(unitID)
	Attacker[UnitsCaughtInBlowOf[unitID][i]].defid = spGetDefID(unitID)
end
end

function gadget:UnitPreDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponDefID, projectileID, attackerID, attackerDefID, attackerTeam)
if not (weaponDefID == -1 or weaponDefID == -2 or weaponDefID == -3 or weaponDefID == -4 or weaponDefID == -5 or weaponDefID == -6) then -- discard non weapon damages (debris, Spring.DestroyUnit, collision, land...)
	if not (attackerID == nil) then -- attacker is not dead (or dead less than 2 frames earlier)
		if not spLastAttacker(attackerID) == nil then -- attacker's last attacker is not dead (or dead less than 2 frames earlier)
			if spAreAllied(spUnitTeam(unitID),spUnitTeam(attackerID)) == true then -- attacker and unit are allied
				if WeaponDefs[weaponDefID]["name"] == UnitDefs[attackerDefID].deathExplosion then -- unit was caught in attacker's blow
					Spring.AddUnitDamage(unitID, damage, 0, spLastAttacker(attackerID), weaponDefID) -- Erase engine damage, add damages via lua from attacker's lastattacker to unit 
					LastAttacker[unitID] = {}
					LastAttacker[unitID].id = spLastAttacker(attackerID) -- Register unit 's last attacker in LastAttacker table for further use
					LastAttacker[unitID].team = spUnitTeam(spLastAttacker(attackerID))
					return 0
				end
			else -- teams are not allied
				LastAttacker[unitID] = {}
				LastAttacker[unitID].id = spLastAttacker(attackerID) -- Register Last Attacker in LastAttacker table for further use
				LastAttacker[unitID].team = spUnitTeam(spLastAttacker(attackerID))
			end
		end
		if spLastAttacker(attackerID) == nil then  -- Last attacker is dead more than 2 frames earlier
		LastAttacker[attackerID] = {}
			if not LastAttacker[attackerID].id == nil then -- is there a LastAttacker table reference
				if spAreAllied(spUnitTeam(unitID),spUnitTeam(attackerID)) == true then -- attacker and unit are allied
					if WeaponDefs[weaponDefID]["name"] == UnitDefs[attackerDefID].deathExplosion then -- unit was caught in attacker's blow
						Spring.AddUnitDamage(unitID, damage, 0, LastAttacker[attackerID].id, weaponDefID) -- Erase engine damage, add damages via lua from attacker's lastattacker to unit 
						LastAttacker[unitID] = {} -- register unit's last attacker for further use
						LastAttacker[unitID].id = LastAttacker[attackerID].id
						LastAttacker[unitID].team = LastAttacker[attackerID].team
					end
				end
			end
		end
	end
	if attackerID == nil then -- attacker died more than 2 frames earlier, can't use spLastAttacker either
		if (not projectileID == nil) then -- Projectile weapon was used, allowing to trace back to attackerID
			Spring.AddUnitDamage(unitID,damage, weaponID, DamageHandler[ProjectileOwner[ProjectileID].team].id, weaponDefID) -- regardless of either the attacker was on the same time, erase damages an readd them from damagehandler of attacker's team
			LastAttacker[unitID] = {} -- register unitID's last attacker for further use
			LastAttacker[unitID].id = DamageHandler[ProjectileOwner[ProjectileID].team].id
			LastAttacker[unitID].team = ProjectileOwner[ProjectileID].team
			return 0
		end
		if projectileID == nil then -- No projectile weapon, trace back if unit was likely to be caught in another unit's blow to find attacker
			if not Attacker[unitID].id == nil then -- attacker found
				if spAreAllied(Attacker[unitID].team, spUnitTeam(unitID)) == false then -- attacker and unit weren't allied
					Spring.AddUnitDamage(unitID, damage, 0, DamageHandler[Attacker[unitID].team].id, weapondDefID) -- erase damage, readd them from damagehandler of attackerteam
					LastAttacker[unitID] = {} -- register unit's last attacker for further use
					LastAttacker[unitID].id = DamageHandler[Attacker[unitID].team].id
					LastAttacker[unitID].team = DamageHandler[Attacker[unitID].team].team
					return 0
				else -- attacker and unit were allied
					Spring.AddUnitDamage(unitID, damage, 0, DamageHandler[LastAttacker[Attacker[unitID].id].team].id, weapondDefID) -- erase damage, readd them from damage handler of attacker's last attacker team
					LastAttacker[unitID] = {} -- register unit's last attacker for further use
					LastAttacker[unitID].id = DamageHandler[LastAttacker[Attacker[unitID].id].team].id
					LastAttacker[unitID].team = DamageHandler[LastAttacker[Attacker[unitID].id].team].team
					return 0
				end
			end
		end
	end
end
end

end
This is what i made so far, haven't tested it yet because i'm lazy and got exams coming on.
I also still need to put up the DamageHandlers fake units.
1 x

[Fx]Doo
Posts: 64
Joined: 30 Aug 2013, 16:39

Re: Enhanced Damage Dealt Counter gadget

Post by [Fx]Doo » 07 Nov 2018, 13:41

Hello again !
So i went and rewrote a gadget with a similar purpose.

Instead of aiming for more accurate endgame stats i just focused on atleast rerouting the damages when they could result in a unit gaining experience.

Here's the code i'm using:

Code: Select all

function gadget:GetInfo()
  return {
    name      = "Damage Handler",
    desc      = "Reroutes chain damages as damages made by initial attacker",
    author    = "Doo",
    date      = "05/11/18",
    license   = "GPL",
    layer     = 0,
    enabled   = true
  }
end

if gadgetHandler:IsSyncedCode() then

local lastAttacker = {} -- lastAttacker[unitID] = {id = attackerID, team = attackerTeam}
local valid = Spring.ValidUnitID
local getTeam = Spring.GetUnitTeam
local alliedt = Spring.AreTeamsAllied
local ec = Spring.Echo
local isdead = Spring.GetUnitIsDead
local getDefID = Spring.GetUnitDefID
local uSphere = Spring.GetUnitsInSphere
local addDamages = Spring.AddUnitDamage

local function live(unitID)
	return (isdead(unitID) == false)
end

local function alliedu(unitID1, unitID2)
	local teamID1 = getTeam(unitID1)
	local teamID2 = getTeam(unitID1)
	return alliedt(teamID1, teamID2)
end

function gadget:GameFrame(f)
	for unitID, tab in pairs(lastAttacker) do
		if unitID%300 == f%300 then
			if (not valid(unitID)) and (not valid(tab.id)) then
				lastAttacker[unitID] = nil
			end
		end
	end
end

function gadget:UnitPreDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponDefID, projectileID, attackerID, attackerDefID, attackerTeam, forceReroute)
	if not unitID then -- unlikely to happen
		return damage
	end
	
	---- Any damage that can't be traced back to a proper source (waterDamages, explosion damages after attackerID is rendered invalid, debris damages....) are considered as explosion damages.
	local defs = attackerID and UnitDefs[getDefID(attackerID)]
	local wdefs = WeaponDefs[weaponDefID]
	local explosion = (defs and wdefs and (defs.deathExplosion == wdefs.name)) or true
	-----
	
	if attackerID and valid(attackerID) and live(attackerID) then -- live, valid attackerID: there is nothing to do besides registering lastAttacker[unitID]
		lastAttacker[unitID] = {id = attackerID, team = attackerTeam}
	elseif attackerID and valid(attackerID) and (not live(attackerID)) then  -- dead, valid attackerID
		if not explosion then -- from a "conventional" weapon ( == known source, not a death explosion), nothing to do beside registering lastAttacker
			lastAttacker[unitID] = {id = attackerID, team = attackerTeam} 
		else -- Explosion damages
			if alliedu(unitID, attackerID) then -- from an allied unit (== chain damages?)
				if lastAttacker[attackerID] then -- lastAttacker[attackerID] is the unit that killed attackerID, therefor causing the chain damages to unitID
					lastAttacker[unitID] = lastAttacker[attackerID]
				else -- there is no lastAttacker[attackerID] known (rare cases: it is possible attackerID simply self-ded)
					lastAttacker[unitID] = {id = attackerID, team = attackerTeam}
				end
			else -- enemy units: nothing to do as attackerID is dead anyway (no experience to give), simply register it as lastAttacker[unitID]
				lastAttacker[unitID] = {id = attackerID, team = attackerTeam}
			end
		end
	elseif attackerID and (not valid(attackerID)) then -- dead, invalid attackerID: supposedly a rare case but here we sometimes call gadget:UnitPreDamaged again with different IDs and attackerID can be invalid so it still has to be handled
		if not explosion then -- from a "conventional" weapon ( == known source, not a death explosion), nothing to do beside registering lastAttacker
			lastAttacker[unitID] = {id = attackerID, team = attackerTeam} -- nothing to do
		else -- Explosion damages
			if alliedt(unitTeam, attackerTeam) then -- from an allied unit (== chain damages?)
				if lastAttacker[attackerID] then -- lastAttacker[attackerID] is the unit that killed attackerID, therefor causing the chain damages to unitID
					lastAttacker[unitID] = lastAttacker[attackerID]
				else -- there is no lastAttacker[attackerID] known (rare cases: it is possible attackerID simply self-ded)
					lastAttacker[unitID] = {id = attackerID, team = attackerTeam}
				end
			else -- enemy units: nothing to do as attackerID is dead anyway (no experience to give), simply register it as lastAttacker[unitID]
				lastAttacker[unitID] = {id = attackerID, team = attackerTeam}
			end
		end
	elseif not attackerID then -- dead, invalid, no known attackerID: This happens very often in the case of death explosions (can also happen with some conventional weapons)
		if lastAttacker[unitID] then -- If we have a known lastAttacker we will consider it as the source of the damages here. This is an approximation but is rather effective.
			attackerID = lastAttacker[unitID].id
			attackerTeam = lastAttacker[unitID].team
			return gadget:UnitPreDamaged(unitID, unitDefID, unitTeam, damage, paralyzer, weaponDefID, nil, attackerID, attackerDefID, attackerTeam, true) -- we rerun UnitPreDamaged with the new attackerID and attackerTeam, while forcing reroute at the end of this new call.
		else -- we really have no idea what damaged unitID and do nothing
		end
	end
	if forceReroute or (lastAttacker[unitID] and attackerID and lastAttacker[unitID].id ~= attackerID) then -- if the source of the last damages (= the ones we're processing now) isn't attackerID, or if we forced a reroute, we return RerouteDamages()
		attackerID = lastAttacker[unitID].id
		return RerouteDamages(unitID, attackerID, damage, paralyzer, weaponDefID)
	else -- if source == attackerID and forceReroute == nil then we just return the damages
		return damage
	end
end

function gadget:Explosion(weaponDefID, px, py, pz, attackerID, projectileID)
	if not attackerID then -- unlikely to happen
		return
	else
		local defs = UnitDefs[getDefID(attackerID)]
		local wdefs = WeaponDefs[weaponDefID]
		if defs.deathExplosion == wdefs.name then -- It is a death explosion
			for i, unitID in pairs(uSphere(px, py, pz, wdefs.damageAreaOfEffect)) do -- All the units that can be affected
				if not (unitID == attackerID) then
					lastAttacker[unitID] = {id = attackerID, team = getTeam(attackerID)} -- mark them as last attacked by attackerID for the upcoming death explosion damages
				end
			end
		end
	end
end

function RerouteDamages(unitID, attackerID, damage, paralyzer, weaponDefID)
	if valid(attackerID) then -- our attackerID is valid and we can reroute the damages, we return 0 and AddUnitDamage from the right source (keep same damages, paralyze and weaponDefID)
		if paralyzer == true then
			paralyzer = damage
			damage = 0
		else
			paralyzer = 0
			damage = damage
		end
		addDamages(unitID, damage, paralyzer, attackerID, weaponDefID)
		return 0
	else -- attackerID is invalid, we cannot reroute the damages
		return damage
	end
end
end
=> https://pastebin.com/TUrqcATi

It works as expected, as seen here: https://youtu.be/TL6E2-CYDxE

It still gives me some rare errors from times to times, and the infolog isn't very specific about those:
[f=0004928] [Game::ClientReadNet][LOGMSG] sender="UnnamedPlayer" string="[Internal Lua error: Call failure] [string "LuaRules/gadgets.lua"]:1408: C stack overflow
stack traceback:
[string "LuaRules/gadgets.lua"]:1408: in function <[string "LuaRules/gadgets.lua"]:1396>
(tail call): ?
[C]: in function 'AddUnitDamage'
[string "LuaRules/Gadgets/unit_damageshandler.lua"]:119: in function <[string "LuaRules/Gadgets/unit_damageshandler.lua"]:110>
(tail call): ?
(tail call): ?
[string "LuaRules/gadgets.lua"]:1382: in function <[string "LuaRules/gadgets.lua"]:1366>
(tail call): ?
[C]: in function 'AddUnitDamage'
[string "LuaRules/Gadgets/unit_damageshandler.lua"]:119: in function <[string "LuaRules/Gadgets/unit_damageshandler.lua"]:110>
(tail call): ?
...
[string "LuaRules/gadgets.lua"]:1382: in function <[string "LuaRules/gadgets.lua"]:1366>
(tail call): ?
[C]: in function 'AddUnitDamage'
[string "LuaRules/Gadgets/unit_damageshandler.lua"]:119: in function <[string "LuaRules/Gadgets/unit_damageshandler.lua"]:110>
(tail call): ?
(tail call): ?
(tail call): ?
[string "LuaRules/Gadgets/dbg_gadget_profiler.lua"]:113: in function 'UnitPreDamaged'
[string "LuaRules/gadgets.lua"]:1382: in function <[string "LuaRules/gadgets.lua"]:1366>
(tail call): ?"
I tried commenting the gadget: it is not well optimized yet, but i believe with the comments it can be understood with relative ease.

Before optimization i would like to know the possible causes of this error and fix it.
Line "119" is the AddUnitDamage call, for all i know, there have been nil checks on unitID, attackerID at this point, and the weaponDef shouldn't be nil ever (might be <0 for debris though)
I actually believe it might be debris related, but i still don't understand why it would be happening.
1 x

Kloot
Spring Developer
Posts: 1845
Joined: 08 Oct 2006, 16:58

Re: Enhanced Damage Dealt Counter gadget

Post by Kloot » 07 Nov 2018, 16:20

Spring.AddUnitDamage will trigger Unit{Pre}Damaged.

Since you are calling it from *within* UnitPreDamaged (via RerouteDamages --> addDamages), this can cause infinite recursion which ends in a stack overflow.
1 x

[Fx]Doo
Posts: 64
Joined: 30 Aug 2013, 16:39

Re: Enhanced Damage Dealt Counter gadget

Post by [Fx]Doo » 07 Nov 2018, 17:33

A quick Spring.Echo(unitID, attackerID) in RerouteDamages doesn't show any sign of it being repeated for a single unitID.
Is it possible that it's not actually recursive, but that the amount of UnitPreDamaged and AddUnitDamage calls in a very small time still trigger that issue ?
They are all called with different unitIDs, but the same attackerID (since there is only one unit causing this chain reaction).
Also, i'm testing with obviously insane chain reactions (+400 units dying in a couple frames). To be honest the gadget as it is is stable in most cases. Just this amount of chain reaction with a very brief delay triggers that issue...
1 x

User avatar
Anarchid
Posts: 1372
Joined: 30 Nov 2008, 04:31

Re: Enhanced Damage Dealt Counter gadget

Post by Anarchid » 07 Nov 2018, 22:33

I had similar thoughts when implementing Attrition Counter for ZK. Except ZK also has indirect kills by gravity weapons (immediate cause: collision with ground ir other units), and terraforming (that can be used to bury units in lava on some maps), so even this recursive kill attribution wouldn't work.

What does work though, is zero-sum game theory. All losses taken by your enemy (that you see anyway; it's a widget, fog of war applies) in a two-side game are assumed to be your doing. All losses you take are assumed to be enemy action.
1 x

Post Reply

Return to “Lua Scripts”