Converting incoming damage with a DamageProjector:base hook?

All development conversation and discussion takes place here

Moderator: Moderator

Post Reply
Message
Author
nsrr
Sher'Tul
Posts: 1012
Joined: Mon Sep 21, 2015 8:45 pm
Location: Middle of Nowhere

Converting incoming damage with a DamageProjector:base hook?

#1 Post by nsrr »

I want all damage to a target to be split into a percentage of its original damage as its original type, with the remaining damage converted to a specific damage type. The hook I'm using is kind of working, but the instance of damage as the original type is getting processed twice, resulting in two hits for the reduced amount, and I can't figure out why. The converted damage is behaving properly, hitting the target for the correct value and only hitting once. Full code below:

Code: Select all

--for reference, this is designed to convert a percentage of all incoming damage into Physical, if the target has the Blocking effect and has gained a specific attribute from a new talent I am working on.

class:bindHook("DamageProjector:base", function(self, data)
	if data.state.no_sw_recurse then return data end
	local target = game.level.map(data.x, data.y, Map.ACTOR)
	if not target then return data end
	if target.dead then return data end
	if not target:attr("steel_wall") then return data end
	local eff = target:hasEffect(target.EFF_BLOCKING)
	if not eff then return data end
	local source_talent = data.src.__projecting_for and data.src.__projecting_for.project_type and (data.src.__projecting_for.project_type.talent_id or data.src.__projecting_for.project_type.talent) and data.src.getTalentFromId and data.src:getTalentFromId(data.src.__projecting_for.project_type.talent or data.src.__projecting_for.project_type.talent_id)

	
	if data.src and data.src.__CLASSNAME ~= "mod.class.Grid" and eff and data.type ~= DamageType.PHYSICAL then
		data.state.no_sw_recurse = true
		local convertedDam = data.dam * target.steel_wall
		if convertedDam < 1 then return data end
		local baseDam = data.dam - convertedDam
		local old_sw = target.steel_wall
		target.steel_wall = nil
		data.dam = 0
		data.dam = data.dam + DamageType:get(data.type).projector(data.src, data.x, data.y, data.type, baseDam, data.state) 
		data.dam = data.dam + DamageType:get(DamageType.PHYSICAL).projector(data.src, data.x, data.y, DamageType.PHYSICAL, convertedDam, data.state)
		target.steel_wall = old_sw
	end
	
	return data
end)
Here's the log of my test character facing the Forest Troll Hedge Wizard, so you can get an idea of the output I'm getting.

Code: Select all

[LOG]	Forest Troll Hedge-Wizard casts Flame.
[SPELL CRIT %]	1.6
Adding entity	8388	after	8380
[LOG]	
[LOG]	Player uses Block.
[PROJECTOR] starting dam	FIRE	17.53178929211
[PROJECTOR] starting dam	FIRE	12.066451874129
[PROJECTOR] after difficulty dam	12.066451874129
[PROJECTOR] after DamageType increase dam	12.790438986576
[PROJECTOR] res	0	1	 on dam	12.790438986576
[PROJECTOR] after resists dam	12.790438986576
[PROJECTOR] after flat damage armor	12.790438986576
[PROJECTOR] final dam after static checks	12.790438986576
[PROJECTOR] final dam after hooks and callbacks	12.790438986576
[ENGINE] Setting requested FPS to 30 (33 ms)
[ENGINE] Setting requested FPS to 30 (33 ms)
[PROJECTOR] starting dam	PHYSICAL	5.4653374179816
[PROJECTOR] after difficulty dam	5.4653374179816
[PROJECTOR] res	17.810413038841	0.82189586961159	 on dam	0
[PROJECTOR] after resists dam	0
[PROJECTOR] final dam after static checks	0
[PROJECTOR] final dam after hooks and callbacks	0
[ENGINE] Setting requested FPS to 30 (33 ms)
[ENGINE] Setting requested FPS to 30 (33 ms)
[PROJECTOR] after difficulty dam	12.790438986576
[PROJECTOR] after DamageType increase dam	13.557865325771
[PROJECTOR] res	0	1	 on dam	13.557865325771
[PROJECTOR] after resists dam	13.557865325771
[PROJECTOR] after flat damage armor	13.557865325771
[PROJECTOR] final dam after static checks	13.557865325771
[PROJECTOR] final dam after hooks and callbacks	13.557865325771
[ENGINE] Setting requested FPS to 30 (33 ms)
[ENGINE] Setting requested FPS to 30 (33 ms)
[ENGINE] Setting requested FPS to 30 (33 ms)
[ENGINE] Setting requested FPS to 30 (33 ms)
[LOG]	Player is on fire!
Cloned shader 46
[SHADER] setting reset param	tick_start	581065
[LOG]	Forest Troll Hedge-Wizard's Flame hits #fbd578#player#LAST# for #LIGHT_RED#13 fire#LAST#, #aaaaaa#(5 blocked)#LAST#, #aaaaaa#0 physical#LAST#, #LIGHT_RED#14 fire#LAST# (26 total damage).
[PROJECTOR] starting dam	FIRE	5.8439297640368
[PROJECTOR] starting dam	FIRE	4.0221506247096
[PROJECTOR] after difficulty dam	4.0221506247096
[PROJECTOR] after DamageType increase dam	4.2634796621922
[PROJECTOR] res	0	1	 on dam	4.2634796621922
[PROJECTOR] after resists dam	4.2634796621922
[PROJECTOR] after flat damage armor	4.2634796621922
[PROJECTOR] final dam after static checks	4.2634796621922
[PROJECTOR] final dam after hooks and callbacks	4.2634796621922
[ENGINE] Setting requested FPS to 30 (33 ms)
[ENGINE] Setting requested FPS to 30 (33 ms)
[PROJECTOR] starting dam	PHYSICAL	1.8217791393272
[PROJECTOR] after difficulty dam	1.8217791393272
[PROJECTOR] res	17.810413038841	0.82189586961159	 on dam	0
[PROJECTOR] after resists dam	0
[PROJECTOR] final dam after static checks	0
[PROJECTOR] final dam after hooks and callbacks	0
[ENGINE] Setting requested FPS to 30 (33 ms)
[ENGINE] Setting requested FPS to 30 (33 ms)
[PROJECTOR] after difficulty dam	4.2634796621922
[PROJECTOR] after DamageType increase dam	4.5192884419237
[PROJECTOR] res	0	1	 on dam	4.5192884419237
[PROJECTOR] after resists dam	4.5192884419237
[PROJECTOR] after flat damage armor	4.5192884419237
[PROJECTOR] final dam after static checks	4.5192884419237
[PROJECTOR] final dam after hooks and callbacks	4.5192884419237
[ENGINE] Setting requested FPS to 30 (33 ms)
[ENGINE] Setting requested FPS to 30 (33 ms)
[LOG]	
[LOG]	Burning from Forest Troll Hedge-Wizard hits #fbd578#player#LAST# for #LIGHT_RED#4 fire#LAST#, #aaaaaa#(2 blocked)#LAST#, #aaaaaa#0 physical#LAST#, #LIGHT_RED#5 fire#LAST# (9 total damage).

You can see how the damage from Flame and Burning both have the Fire portion returned twice (and slightly increased on the second instance?), but not the portion that get split off into Physical.

I'm very much an amateur at this stuff, and I've hardly touched hooks before this, aside from copying a couple from other folks' work. As far as I can understand it though, it works more or less like a callback. Aside from just being stumped as to how the base damage is getting processed twice, I'm also a little unclear on where exactly the value I return is going to be returned to. That is, there are a few things that fire off in the damage projector before the hook is called; when my hook returns its data, does that start back at the beginning of the damage projector, or is it spit out back into the original loop right after the place where the hook is called? As far as I can tell, it basically gets sent into a new loop, starting from the beginning, because I was getting some recursion before I put a flag into the state to stop it. Unfortunately, this does not shed any light on what's going on for me.

Any help anyone can offer would be greatly appreciated. I spent the entire day yesterday working on this thing to get it to the point where it is now. It's so close to working properly, and yet so not, it's making me crazy.

ghostbuster
Uruivellas
Posts: 617
Joined: Mon Oct 09, 2006 7:47 pm

Re: Converting incoming damage with a DamageProjector:base h

#2 Post by ghostbuster »

I did something similar for a talent, that if sustained convert x% of damage to a specific type.
I used a callback and it seems to work. And callbacks are easier to maintain if they can be used IMHO.

Here is the code

Code: Select all

	callbackOnTakeDamageBeforeResists = function(self, t, src, x, y, dtype, dam, tmp)
	    local ff = self:isTalentActive(t.id)
	    local ret, mdam
	    ret = {dam = dam}
	    if not ff or dtype == DamageType.MIND then
	       return ret end
	    mdam = ret.dam * t.getMindConvert(self,t)
	    ret.dam = ret.dam - mdam
	    DamageType:get(DamageType.MIND).projector(src, x, y, DamageType.MIND, {dam=mdam})
	    game:delayedLogMessage(self, self, "Mind conversion", "#Source# diverts some damage to its mind!")
	    return ret
	end,
The callback is called in the defaultprojector and dam is always a number, but the callback expects a table returned, that explains part of the code.
Otherwise it should be straightforward.

nsrr
Sher'Tul
Posts: 1012
Joined: Mon Sep 21, 2015 8:45 pm
Location: Middle of Nowhere

Re: Converting incoming damage with a DamageProjector:base h

#3 Post by nsrr »

Thanks for the reply!

I changed your callaback to this:

Code: Select all

	callbackOnTakeDamageBeforeResists = function(self, t, src, x, y, dtype, dam, tmp)
		local eff = self:hasEffect(self.EFF_BLOCKING)
		local ret, pdam
		ret = {dam = dam}	   
		if eff and self:attr("steel_wall") and dtype ~= DamageType.PHYSICAL then
		   pdam = ret.dam * self.steel_wall
		   ret.dam = ret.dam - pdam
		   DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, {dam=pdam})
		   game:delayedLogMessage(self, self, "Physical conversion", "#Source# diverts some damage with its shield!")
		   return ret
		else
			return ret
		end
   end,
...but it did not work. The damage was not being split up and returned. It seemed like I was just getting the starting value back.

However, based on your code, I tried changing my hook to:

Code: Select all

class:bindHook("DamageProjector:base", function(self, data)
	if data.state.no_sw_recurse then return data end
	local target = game.level.map(data.x, data.y, Map.ACTOR)
	if not target then return data end
	if target.dead then return data end
	if not target:attr("steel_wall") then return data end
	local eff = target:hasEffect(target.EFF_BLOCKING)
	if not eff then return data end
	local source_talent = data.src.__projecting_for and data.src.__projecting_for.project_type and (data.src.__projecting_for.project_type.talent_id or data.src.__projecting_for.project_type.talent) and data.src.getTalentFromId and data.src:getTalentFromId(data.src.__projecting_for.project_type.talent or data.src.__projecting_for.project_type.talent_id)

	
	if data.src and data.src.__CLASSNAME ~= "mod.class.Grid" and eff and data.type ~= DamageType.PHYSICAL and not data.state.no_sw_recurse then
		data.state.no_sw_recurse = true
		local convertedDam = data.dam * target.steel_wall
		if convertedDam < 1 then return data end
		local baseDam = data.dam - convertedDam
		local old_sw = target.steel_wall
		target.steel_wall = nil
		data.dam = 0
		data.dam = baseDam
		DamageType:get(DamageType.PHYSICAL).projector(data.src, data.x, data.y, DamageType.PHYSICAL, convertedDam, data.state)
		target.steel_wall = old_sw
	end
	
	return data
end)

... which does work! I was using the dam = dam + DamaeType:get... (etc) method based on some code used for handling elemental and light/dark split, but it seems that was not necessary and was the source of the redundant damage. Just calling DamageType:get(etc) seems to fire off the damage, while the original damage is changed to the reduced amount and passed on normally.

I'm sure the callback could work and I'm just missing something, but I'm happy enough that this method is working. Thanks again!

ghostbuster
Uruivellas
Posts: 617
Joined: Mon Oct 09, 2006 7:47 pm

Re: Converting incoming damage with a DamageProjector:base h

#4 Post by ghostbuster »

It seems that your callback is not triggered. Maybe you can check with a print() right at the beginning

Do you properly associate the callback to either the talent or the effect that will call it?
As I understand callbacks are automatically called if they are a field of a talent that is either passive and known or sustained.
If the callback is part of an activated talent, better use an effect callback by adding a field
callbackOnTakeDamageBeforeResists = function(self, eff, src, x, y, dtype, dam, tmp)
to the effect.
It will be called whenever the effect is on.

it should even (maybe) be possible to add the callback directly to the BLOCKING effect.
I think tt can be done by adding to the talent that allows the conversion the following code
on_learn = function(s,t)
if not Talents.T_BLOCKING.callbackOnTakeDamageBeforeResists then
Talents.T_BLOCKING.callbackOnTakeDamageBeforeResists =function(self, eff, src, x, y, dtype, dam, tmp)
...
end
(untested and ungaranteed)

The only potential problem is if another addon tries to do the same thing for the same talent... Not sure it would be considered as a good practice.

nsrr
Sher'Tul
Posts: 1012
Joined: Mon Sep 21, 2015 8:45 pm
Location: Middle of Nowhere

Re: Converting incoming damage with a DamageProjector:base h

#5 Post by nsrr »

I'm still not sure why the callback was not firing before, but I got it working now and have switched to using that instead of the hook. I also prefer to use callbacks whenever possible, and had only resorted to a hook because I wasn't sure how to fire off the converted damage and thought I would need to hook into the damage projector to handle it. As is usually the case when I'm not sure how to go about something, it was much simpler than I had anticipated, and your code showed me how to work it out. Thanks again for the help!

Post Reply