Commit 083f6152fc1fa03045f9de1da06dc6adb5552bae

Authored by DarkGod
2 parents 76af56f1 3f24a812

Merge branch 'RootOfAllThings/t-engine4-onTakeHit_refactor'

Showing 29 changed files with 866 additions and 384 deletions
... ... @@ -2808,150 +2808,36 @@ function _M:onTakeHit(value, src, death_note)
2808 2808 end
2809 2809 end
2810 2810
2811   - -- Apply Solipsism hit
2812   - if damage_to_psi > 0 then
2813   - local t = self:getTalentFromId(self.T_SOLIPSISM)
2814   - local psi_damage_resist = 1 - t.getPsiDamageResist(self, t)/100
2815   - -- print("Psi Damage Resist", psi_damage_resist, "Damage", damage_to_psi, "Final", damage_to_psi*psi_damage_resist)
2816   - if self:getPsi() > damage_to_psi*psi_damage_resist then
2817   - self:incPsi(-damage_to_psi*psi_damage_resist)
2818   - else
2819   - damage_to_psi = self:getPsi()
2820   - self:incPsi(-damage_to_psi)
2821   - end
2822   - local mindcolor = DamageType:get(DamageType.MIND).text_color or "#aaaaaa#"
2823   - game:delayedLogMessage(self, nil, "Solipsism hit", ("%s#Source# converts some damage to Psi!"):tformat(mindcolor))
2824   - game:delayedLogDamage(src, self, damage_to_psi*psi_damage_resist, ("%s%d %s#LAST#"):tformat(mindcolor, damage_to_psi*psi_damage_resist, _t"to psi"), false)
2825   -
2826   - value = value - damage_to_psi
2827   - end
  2811 + if self.on_takehit then value = self:check("on_takehit", value, src, death_note) end
2828 2812
2829   - if value <= 0 then return 0 end
2830   - -- VITALITY?
2831   - if self:knowTalent(self.T_VITALITY) and self.life > self.max_life /2 and self.life - value <= self.max_life/2 then
2832   - local t = self:getTalentFromId(self.T_VITALITY)
2833   - t.do_vitality_recovery(self, t)
  2813 + local cb = {value=value}
  2814 + if self:fireTalentCheck("callbackOnHit", cb, src, death_note) then value = cb.value end
  2815 + if self.summoner then
  2816 + cb = {value=value}
  2817 + if self.summoner:fireTalentCheck("callbackOnSummonHit", cb, self, src, death_note) then value = cb.value end
2834 2818 end
2835 2819
2836   - -- Shield of Light
2837   - tal = self:isTalentActive(self.T_SHIELD_OF_LIGHT)
2838   - if tal then
2839   - if value <= 2 then
2840   - drain = value
2841   - else
2842   - drain = 2
2843   - end
2844   - if self:getPositive() >= drain then
2845   - self:incPositive(- drain)
2846   -
2847   - -- Only calculate crit once per turn to avoid log spam
2848   - if not self.turn_procs.shield_of_light_heal then
2849   - local t = self:getTalentFromId(self.T_SHIELD_OF_LIGHT)
2850   - self.turn_procs.shield_of_light_heal = true
2851   - self.shield_of_light_heal = self:spellCrit(t.getHeal(self, t))
2852   - end
  2820 + local hd = {"Actor:takeHit", value=value, src=src, death_note=death_note}
  2821 + if self:triggerHook(hd) then value = hd.value end
  2822 +
  2823 + if value > 0 and src and (src.hate_per_powerful_hit or 0) > 0 and src.knowTalent and src:knowTalent(src.T_HATE_POOL) then
  2824 + local hateGain = 0
  2825 + local hateMessage
2853 2826
2854   - self:heal(self.shield_of_light_heal, tal)
  2827 + if value / src.max_life > 0.33 then
  2828 + -- you deliver a big hit
  2829 + hateGain = hateGain + src.hate_per_powerful_hit
  2830 + hatemessage = _t"#F53CBE#Your powerful attack feeds your madness!"
2855 2831 end
2856   - end
2857 2832
2858   - if value >= self.life then
2859   - local tal = self:isTalentActive(self.T_SECOND_LIFE)
2860   - if tal then
2861   - local sl = self:callTalent(self.T_SECOND_LIFE,"getLife")
2862   - value = 0
2863   - self.life = 1
2864   - self:forceUseTalent(self.T_SECOND_LIFE, {ignore_energy=true})
2865   - local value = self:heal(sl, self)
2866   - game.logSeen(self, "#YELLOW#%s has been healed by a blast of positive energy!#LAST#", self:getName():capitalize())
2867   - if value > 0 then
2868   - if self.player then
2869   - self:setEmote(require("engine.Emote").new("The Sun Protects!", 45))
2870   - world:gainAchievement("AVOID_DEATH", self)
2871   - end
  2833 + if hateGain >= 0.1 then
  2834 + src.hate = math.min(src.max_hate, src.hate + hateGain)
  2835 + if hateMessage then
  2836 + game.logPlayer(src, ("%s (+%d hate)"):tformat(hateMessage), hateGain)
2872 2837 end
2873 2838 end
2874   -
2875   - local tal = self:isTalentActive(self.T_HEARTSTART)
2876   - if tal then
2877   - self:forceUseTalent(self.T_HEARTSTART, {ignore_energy=true})
2878   - end
2879   - end
2880   -
2881   - -- Shade's reform
2882   - if value >= self.life and self.ai_state and self.ai_state.can_reform then
2883   - local t = self:getTalentFromId(self.T_SHADOW_REFORM)
2884   - if rng.percent(t.getChance(self, t)) then
2885   - value = 0
2886   - self.life = self.max_life
2887   - game.logSeen(self, "%s fades for a moment and then reforms whole again!", self:getName():capitalize())
2888   - game.level.map:particleEmitter(self.x, self.y, 1, "teleport_out")
2889   - game:playSoundNear(self, "talents/heal")
2890   - game.level.map:particleEmitter(self.x, self.y, 1, "teleport_in")
2891   - end
2892   - end
2893   -
2894   - -- Shadow decoy
2895   - if value >= self.life and self:isTalentActive(self.T_SHADOW_DECOY) then
2896   - local t = self:getTalentFromId(self.T_SHADOW_DECOY)
2897   - if t.onDie(self, t, value, src) then
2898   - value = 0
2899   - end
2900   - end
2901   -
2902   - if value <= 0 then return 0 end
2903   - -- Vim leech
2904   - if self:knowTalent(self.T_LEECH) and src.hasEffect and src:hasEffect(src.EFF_VIMSENSE) then
2905   - local vt = self:getTalentFromId(self.T_LEECH)
2906   - self:incVim(vt.getVim(self, vt))
2907   - self:heal(vt.getHeal(self, vt), src)
2908   - --if self.player then src:logCombat(src, "#AQUAMARINE#You leech a part of #Target#'s vim.") end
2909   - end
2910   -
2911   - -- Invisible on hit
2912   - if value >= self.max_life * 0.10 and self:attr("invis_on_hit") and rng.percent(self:attr("invis_on_hit")) then
2913   - self:setEffect(self.EFF_INVISIBILITY, 5, {power=self:attr("invis_on_hit_power")})
2914   - for tid, _ in pairs(self.invis_on_hit_disable) do self:forceUseTalent(tid, {ignore_energy=true}) end
2915   - end
2916   -
2917   - -- Bloodspring
2918   - if value >= self.max_life * 0.15 and self:knowTalent(self.T_BLOODSPRING) then
2919   - self:triggerTalent(self.T_BLOODSPRING)
2920   - end
2921   -
2922   - if self:knowTalent(self.T_DUCK_AND_DODGE) then
2923   - local t = self:getTalentFromId(self.T_DUCK_AND_DODGE)
2924   - if value >= self.max_life * t.getThreshold(self, t) then
2925   - self:setEffect(self.EFF_EVASION, t.getDuration(self, t), {chance=t.getEvasionChance(self, t), defense = t.getDefense(self)})
2926   - end
2927   - end
2928   -
2929   - -- Damage shield on hit
2930   - if self:attr("contingency") and value >= self.max_life * self:attr("contingency") / 100 and not self:hasEffect(self.EFF_DAMAGE_SHIELD) then
2931   - self:setEffect(self.EFF_DAMAGE_SHIELD, 3, {power=value * self:attr("contingency_shield") / 100})
2932   - for tid, _ in pairs(self.contingency_disable) do self:forceUseTalent(tid, {ignore_energy=true}) end
2933   - end
2934   -
2935   - -- Spell cooldowns on hit
2936   - if self:attr("reduce_spell_cooldown_on_hit") and value >= self.max_life * self:attr("reduce_spell_cooldown_on_hit") / 100 then
2937   - local alt = {}
2938   - for tid, cd in pairs(self.talents_cd) do
2939   - if rng.percent(self:attr("reduce_spell_cooldown_on_hit_chance")) then alt[tid] = true end
2940   - end
2941   - for tid, cd in pairs(alt) do
2942   - self:alterTalentCoolingdown(tid, -1)
2943   - end
2944   - end
2945   -
2946   - -- Life leech
2947   - if value > 0 and src and not src.dead and src.attr and src:attr("life_leech_chance") and rng.percent(src.life_leech_chance) then
2948   - local leech = math.min(value, self.life) * src.life_leech_value / 100
2949   - if leech > 0 then
2950   - src:heal(leech, self)
2951   - game:delayedLogMessage(src, self, "life_leech"..self.uid, "#CRIMSON##Source# leeches life from #Target#!")
2952   - end
2953   - end
2954   -
  2839 + end
  2840 +
2955 2841 -- Life steal from weapon
2956 2842 if value > 0 and src and not src.dead and src.attr and src:attr("lifesteal") then
2957 2843 local leech = math.min(value, self.life) * src.lifesteal / 100
... ... @@ -2960,71 +2846,14 @@ function _M:onTakeHit(value, src, death_note)
2960 2846 game:delayedLogMessage(src, self, "lifesteal"..self.uid, "#CRIMSON##Source# steals life from #Target#!")
2961 2847 end
2962 2848 end
2963   -
2964   - if self.on_takehit then value = self:check("on_takehit", value, src, death_note) end
2965   -
2966   - local eff = self:hasEffect(self.EFF_ELDRITCH_STONE)
2967   - if eff then
2968   - local abs = math.min(value, eff.power)
2969   - self:incEquilibrium(abs * 2)
2970   - if eff.power > abs then
2971   - eff.power = eff.power - abs
2972   - value = 0
2973   - else
2974   - value = value - abs
2975   - self:removeEffect(self.EFF_ELDRITCH_STONE)
2976   - end
2977   - game:delayedLogDamage(src, self, 0, ("#SLATE#(%d to stone)#LAST#"):tformat(abs), false)
2978   - end
2979   -
2980   - if self:knowTalent(self.T_STONESHIELD) and not self.turn_procs.stoneshield then
2981   - local t = self:getTalentFromId(self.T_STONESHIELD)
2982   - local m, mm, e, em = t.getValues(self, t)
2983   - self:incMana(math.min(mm, value * m))
2984   - self:incEquilibrium(-math.min(em, value * e))
2985   - self.turn_procs.stoneshield = true
2986   - end
2987   -
2988   - local eff = self:hasEffect(self.EFF_STONE_LINK)
2989   - if eff then
2990   - if eff.src:attr("dead") then
2991   - self:removeEffect(self.EFF_STONE_LINK)
2992   - else
2993   - game:delayedLogMessage(eff.src, self, "stone_link"..(self.uid or ""), "#OLIVE_DRAB##Source# redirects damage from #Target# to %s!#LAST#", string.his_her_self(eff.src))
2994   - game:delayedLogDamage(src, self, 0, ("#OLIVE_DRAB#(%d redirected)#LAST#"):tformat(value), false)
2995   - eff.src:takeHit(value, src)
2996   - game:delayedLogDamage(src, eff.src, value, ("#OLIVE_DRAB#%d redirected#LAST#"):tformat(value), false)
2997   - value = 0
2998   - end
2999   - end
3000   -
3001   - local cb = {value=value}
3002   - if self:fireTalentCheck("callbackOnHit", cb, src, death_note) then value = cb.value end
3003   - if self.summoner then
3004   - cb = {value=value}
3005   - if self.summoner:fireTalentCheck("callbackOnSummonHit", cb, self, src, death_note) then value = cb.value end
3006   - end
3007   -
3008   - local hd = {"Actor:takeHit", value=value, src=src, death_note=death_note}
3009   - if self:triggerHook(hd) then value = hd.value end
3010   -
3011   - -- Resource leech
3012   - if value > 0 and src and src.attr and src:attr("resource_leech_chance") and rng.percent(src.resource_leech_chance) then
3013   - local leech = src.resource_leech_value
3014   - src:incMana(leech)
3015   - src:incVim(leech * 0.5)
3016   - src:incPositive(leech * 0.25)
3017   - src:incNegative(leech * 0.25)
3018   - src:incEquilibrium(-leech * 0.35)
3019   - src:incStamina(leech * 0.65)
3020   - src:incHate(leech * 0.2)
3021   - src:incPsi(leech * 0.2)
3022   - game:delayedLogMessage(src, self, "resource_leech", "#CRIMSON##Source# leeches energies from #Target#!")
3023   - end
3024   -
3025   - if self:knowTalent(self.T_DRACONIC_BODY) then
3026   - local t = self:getTalentFromId(self.T_DRACONIC_BODY)
3027   - t.trigger(self, t, value)
  2849 +
  2850 + -- Achievements
  2851 + if not self.no_take_hit_achievements and src and src.resolveSource and src:resolveSource().player and value >= 600 then
  2852 + local rsrc = src:resolveSource()
  2853 + world:gainAchievement("SIZE_MATTERS", rsrc)
  2854 + if value >= 1500 then world:gainAchievement("DAMAGE_1500", rsrc) end
  2855 + if value >= 3000 then world:gainAchievement("DAMAGE_3000", rsrc) end
  2856 + if value >= 6000 then world:gainAchievement("DAMAGE_6000", rsrc) end
3028 2857 end
3029 2858
3030 2859 -- Needs to be done last, will break if any damage is taken between doing this and updating the actor's life
... ...
... ... @@ -308,9 +308,23 @@ function _M:display()
308 308 self:mouseTooltip(self.TOOLTIP_LIFE, self:makeTextureBar(_t"#c00000#Life :", nil, player.life, player.max_life, player.life_regen * util.bound((player.healing_factor or 1), 0, 2.5), x, h, 255, 255, 255, colors.DARK_RED, colors.VERY_DARK_RED)) h = h + self.font_h
309 309
310 310 local shield, max_shield = 0, 0
311   - if player:attr("time_shield") then shield = shield + player.time_shield_absorb max_shield = max_shield + player.time_shield_absorb_max end
312   - if player:attr("damage_shield") then shield = shield + player.damage_shield_absorb max_shield = max_shield + player.damage_shield_absorb_max end
313   - if player:attr("displacement_shield") then shield = shield + player.displacement_shield max_shield = max_shield + player.displacement_shield_max end
  311 + for eff_id, p in pairs(player.tmp) do
  312 + if player.tempeffect_def[eff_id].shield_bar then
  313 + local eshield, emax_shield = player.tempeffect_def[eff_id].shield_bar(player, p)
  314 + shield = shield + eshield
  315 + max_shield = max_shield + emax_shield
  316 + end
  317 + end
  318 +
  319 + for tid, act in pairs(player.sustain_talents) do
  320 + local t = player:getTalentFromId(tid)
  321 + if t and t.shield_bar then
  322 + local eshield, emax_shield = t.shield_bar(player, t, act)
  323 + shield = shield + eshield
  324 + max_shield = max_shield + emax_shield
  325 + end
  326 + end
  327 +
314 328 if max_shield > 0 then
315 329 self:mouseTooltip(self.TOOLTIP_DAMAGE_SHIELD, self:makeTextureBar(_t"#WHITE#Shield:", nil, shield, max_shield, nil, x, h, 255, 255, 255, {r=colors.GREY.r / 3, g=colors.GREY.g / 3, b=colors.GREY.b / 3}, {r=colors.GREY.r / 6, g=colors.GREY.g / 6, b=colors.GREY.b / 6})) h = h + self.font_h
316 330 end
... ...
... ... @@ -790,12 +790,22 @@ function _M:displayResources(scale, bx, by, a)
790 790 end
791 791
792 792 local shield, max_shield = 0, 0
793   - if player:attr("time_shield") then shield = shield + (player.time_shield_absorb or 0) max_shield = max_shield + (player.time_shield_absorb_max or 0) end
794   - if player:attr("damage_shield") then shield = shield + (player.damage_shield_absorb or 0) max_shield = max_shield + (player.damage_shield_absorb_max or 0) end
795   - if player:attr("displacement_shield") then shield = shield + (player.displacement_shield or 0) max_shield = max_shield + (player.displacement_shield_max or 0) end
796   - if player:attr("disruption_shield_power") then shield = shield + (player.disruption_shield_power or 0) max_shield = max_shield + (player:callTalent(player.T_DISRUPTION_SHIELD, "getMaxAbsorb") or 0) end
797   - local necroshield = player:isTalentActive(player.T_HIEMAL_SHIELD)
798   - if necroshield then shield = shield + (necroshield.shield or 0) max_shield = max_shield + (necroshield.original_shield or 0) end
  793 + for eff_id, p in pairs(player.tmp) do
  794 + if player.tempeffect_def[eff_id].shield_bar then
  795 + local eshield, emax_shield = player.tempeffect_def[eff_id].shield_bar(player, p)
  796 + shield = shield + eshield
  797 + max_shield = max_shield + emax_shield
  798 + end
  799 + end
  800 +
  801 + for tid, act in pairs(player.sustain_talents) do
  802 + local t = player:getTalentFromId(tid)
  803 + if t and t.shield_bar then
  804 + local eshield, emax_shield = t.shield_bar(player, t, act)
  805 + shield = shield + eshield
  806 + max_shield = max_shield + emax_shield
  807 + end
  808 + end
799 809
800 810 local front = fshat_life_dark
801 811 if max_shield > 0 then
... ...
... ... @@ -2718,8 +2718,7 @@ newDamageType{
2718 2718 -- Shields can't usually merge, so change the parameters manually
2719 2719 local shield = target:hasEffect(target.EFF_DAMAGE_SHIELD)
2720 2720 shield.power = shield.power + shield_power
2721   - target.damage_shield_absorb = target.damage_shield_absorb + shield_power
2722   - target.damage_shield_absorb_max = target.damage_shield_absorb_max + shield_power
  2721 + shield.power_max = shield.power_max + shield_power
2723 2722 shield.dur = math.max(2, shield.dur)
2724 2723
2725 2724 -- Limit the number of times a shield can be extended
... ...
... ... @@ -92,8 +92,7 @@ newTalent{
92 92 local shield_power = t.getShieldFlat(self, t)
93 93
94 94 shield.power = shield.power + shield_power
95   - self.damage_shield_absorb = self.damage_shield_absorb + shield_power
96   - self.damage_shield_absorb_max = self.damage_shield_absorb_max + shield_power
  95 + shield.power_max = shield.power_max + shield_power
97 96 shield.dur = math.max(2, shield.dur)
98 97 end
99 98 if hitted and self:isTalentActive(self.T_GRAVITIC_EFFULGENCE) then
... ... @@ -233,6 +232,25 @@ newTalent{
233 232 cooldown = 30,
234 233 tactical = { DEFEND = 2 },
235 234 getLife = function(self, t) return self.max_life * self:combatTalentLimit(t, 1.5, 0.2, 0.5) end, -- Limit < 150% max life (to survive a large string of hits between turns)
  235 + callbackPriorities = {callbackOnHit = 350},
  236 + callbackOnHit = function(self, t, cb, src, death_note)
  237 + if cb.value >= self.life then
  238 + local sl = t.getLife(self, t)
  239 + cb.value = 0
  240 + self.life = 1
  241 + self:forceUseTalent(self.T_SECOND_LIFE, {ignore_energy=true})
  242 + local value = self:heal(sl, self)
  243 + game.logSeen(self, "#YELLOW#%s has been healed by a blast of positive energy!#LAST#", self:getName():capitalize())
  244 + if value > 0 then
  245 + if self.player then
  246 + self:setEmote(require("engine.Emote").new("The Sun Protects!", 45))
  247 + world:gainAchievement("AVOID_DEATH", self)
  248 + end
  249 + end
  250 + end
  251 +
  252 + return cb
  253 + end,
236 254 activate = function(self, t)
237 255 game:playSoundNear(self, "talents/heal")
238 256 local ret = {}
... ... @@ -251,4 +269,4 @@ newTalent{
251 269 return ([[Any attack that would drop you below 1 hit point instead triggers Second Life, deactivating the talent, setting your hit points to 1, then healing you for %d.]]):
252 270 tformat(t.getLife(self, t))
253 271 end,
254   -}
  272 +}
\ No newline at end of file
... ...
... ... @@ -170,8 +170,7 @@ newTalent{
170 170 local shield_power = self:spellCrit(t.getShield(self, t))
171 171
172 172 shield.power = shield.power + shield_power
173   - self.damage_shield_absorb = self.damage_shield_absorb + shield_power
174   - self.damage_shield_absorb_max = self.damage_shield_absorb_max + shield_power
  173 + shield.power_max = shield.power_max + shield_power
175 174 shield.dur = math.max(eff.dur, shield.dur)
176 175 else
177 176 self:setEffect(self.EFF_DAMAGE_SHIELD, eff.dur, {color={0xff/255, 0x3b/255, 0x3f/255}, power=self:spellCrit(t.getShield(self, t))})
... ...
... ... @@ -43,6 +43,29 @@ newTalent{
43 43 deactivate = function(self, t, p)
44 44 return true
45 45 end,
  46 + callbackPriorities = {callbackOnHit = -500},
  47 + callbackOnHit = function(self, t, cb, src, dt)
  48 + tal = self:isTalentActive(t.id)
  49 + if tal and self.life < self.max_life then
  50 + if cb.value <= 2 then
  51 + drain = cb.value
  52 + else
  53 + drain = 2
  54 + end
  55 + if self:getPositive() >= drain then
  56 + self:incPositive(- drain)
  57 +
  58 + -- Only calculate crit once per turn to avoid log spam
  59 + if not self.turn_procs.shield_of_light_heal then
  60 + local t = self:getTalentFromId(self.T_SHIELD_OF_LIGHT)
  61 + self.turn_procs.shield_of_light_heal = true
  62 + self.shield_of_light_heal = self:spellCrit(t.getHeal(self, t))
  63 + end
  64 +
  65 + self:heal(self.shield_of_light_heal, tal)
  66 + end
  67 + end
  68 + end,
46 69 callbackOnMeleeAttack = function(self, t, target, hitted, crit, weapon, damtype, mult, dam)
47 70 local shield = self:hasShield()
48 71 if hitted and not target.dead and shield and not self.turn_procs.shield_of_light then
... ... @@ -129,12 +152,49 @@ newTalent{
129 152 tactical = { DEFEND = 2 },
130 153 getDamage = function(self, t) return self:combatTalentSpellDamage(t, 40, 400) end,
131 154 iconOverlay = function(self, t, p)
132   - local val = self.retribution_absorb or 0
  155 + local val = p.power or 0
133 156 if val <= 0 then return "" end
134 157 local fnt = "buff_font_small"
135 158 if val >= 1000 then fnt = "buff_font_smaller" end
136 159 return "#RED#"..tostring(math.ceil(val)).."#LAST#", fnt
137 160 end,
  161 + shield_bar = function(self, t, p)
  162 + local power = p.power or 0
  163 + local power_max = p.power_max or 0
  164 + return power, power_max
  165 + end,
  166 + callbackPriorities = {callbackOnHit = -290},
  167 + callbackOnHit = function(self, t, cb, src, dt)
  168 + local p = self:isTalentActive(t.id)
  169 + if not p then return end
  170 +
  171 + local value = cb.value
  172 + -- Absorb damage into the retribution
  173 + local absorb = math.min(value/2, p.power)
  174 + game:delayedLogDamage(src, self, 0, ("#SLATE#(%d absorbed)#LAST#"):tformat(absorb), false)
  175 + if absorb < p.power then
  176 + p.power = p.power - absorb
  177 + value = value - absorb
  178 + else
  179 + value = value - p.power
  180 + p.power = 0
  181 +
  182 + local dam = p.dam
  183 +
  184 + -- Deactivate without loosing energy
  185 + self:forceUseTalent(self.T_RETRIBUTION, {ignore_energy=true, ignore_cd=true})
  186 + self:startTalentCooldown(self.T_RETRIBUTION)
  187 +
  188 + -- Explode!
  189 + game.logSeen(self, "%s unleashes the stored damage in retribution!", self:getName():capitalize())
  190 + local tg = {type="ball", range=0, radius=self:getTalentRange(self:getTalentFromId(self.T_RETRIBUTION)), selffire=false, talent=t}
  191 + local grids = self:project(tg, self.x, self.y, DamageType.LIGHT, dam)
  192 + game.level.map:particleEmitter(self.x, self.y, tg.radius, "sunburst", {radius=tg.radius, grids=grids, tx=self.x, ty=self.y})
  193 + end
  194 +
  195 + cb.value = value
  196 + return cb
  197 + end,
138 198 activate = function(self, t)
139 199 local shield = self:hasShield()
140 200 if not shield then
... ... @@ -142,17 +202,14 @@ newTalent{
142 202 return nil
143 203 end
144 204 local power = t.getDamage(self, t)
145   - self.retribution_absorb = power
146   - self.retribution_strike = power
147 205 game:playSoundNear(self, "talents/generic")
148 206 return {
149   - shield = self:addTemporaryValue("retribution", power),
  207 + power = power,
  208 + power_max = power,
  209 + dam = power
150 210 }
151 211 end,
152 212 deactivate = function(self, t, p)
153   - self:removeTemporaryValue("retribution", p.shield)
154   - self.retribution_absorb = nil
155   - self.retribution_strike = nil
156 213 return true
157 214 end,
158 215 callbackOnRest = function(self, t) -- Make sure we've actually started resting/running before disabling the sustain
... ...
... ... @@ -147,6 +147,14 @@ newTalent{
147 147 -- called by _M:onTakeHit function in mod\class\Actor.lua
148 148 getVim = function(self, t) return self:combatTalentScale(t, 1.7, 6.5, 0.75) end,
149 149 getHeal = function(self, t) return self:combatTalentScale(t, 4, 15, 0.75) end,
  150 + callbackPriorities = {callbackOnHit = -500},
  151 + callbackOnHit = function(self, t, cb, src, death_note)
  152 + if src.hasEffect and src:hasEffect(src.EFF_VIMSENSE) then
  153 + self:incVim(t.getVim(self, t))
  154 + self:heal(t.getHeal(self, t), src)
  155 + --if self.player then src:logCombat(src, "#AQUAMARINE#You leech a part of #Target#'s vim.") end
  156 + end
  157 + end,
150 158 info = function(self, t)
151 159 return ([[Each time a creature affected by vimsense hurts you, you regain %0.2f vim and %0.2f health.]]):
152 160 tformat(t.getVim(self,t),t.getHeal(self,t))
... ...
... ... @@ -102,6 +102,14 @@ newTalent{
102 102 getIncDamageChange = function(self, t, increase)
103 103 return self:combatTalentLimit(t, 60, 5, 10) * increase --I5 Limit < 60%
104 104 end,
  105 + callbackPriorities = {callbackOnHit = -500},
  106 + callbackOnHit = function(self, t, cb, src, death_note)
  107 + if not self:hasEffect(self.EFF_CURSED_FORM) then
  108 + self:setEffect(self.EFF_CURSED_FORM, 1, { increase=0 })
  109 + end
  110 + local eff = self:hasEffect(self.EFF_CURSED_FORM)
  111 + self.tempeffect_def[self.EFF_CURSED_FORM].do_onTakeHit(self, eff, value)
  112 + end,
105 113 info = function(self, t)
106 114 local incDamageChangeMax = t.getIncDamageChange(self, t, 5)
107 115 return ([[You have learned to hold onto your hate and use your suffering to fuel your body's rage. Every turn you take damage, the damage you inflict increases, until it reaches a maximum of +%d%% after 5 turns. Any turn in which you do not take damage will reduce the bonus.]]):tformat(incDamageChangeMax)
... ... @@ -116,6 +124,16 @@ newTalent{
116 124 points = 5,
117 125 getStatChange = function(self, t, increase) return math.floor(self:combatTalentScale(t, 1, 2.24) * increase) end,
118 126 getNeutralizeChance = function(self, t) return self:combatTalentLimit(t, 60, 10, 23.4) end, -- Limit < 60%
  127 + callbackPriorities = {callbackOnHit = -500},
  128 + callbackOnHit = function(self, t, cb, src, death_note)
  129 + if self:knowTalent(self.EFF_CURSED_FORM) then return end
  130 +
  131 + if not self:hasEffect(self.EFF_CURSED_FORM) then
  132 + self:setEffect(self.EFF_CURSED_FORM, 1, { increase=0 })
  133 + end
  134 + local eff = self:hasEffect(self.EFF_CURSED_FORM)
  135 + self.tempeffect_def[self.EFF_CURSED_FORM].do_onTakeHit(self, eff, value)
  136 + end,
119 137 info = function(self, t)
120 138 local statChangeMax = t.getStatChange(self, t, 5)
121 139 local neutralizeChance = t.getNeutralizeChance(self, t)
... ...
... ... @@ -168,8 +168,13 @@ newTalent{
168 168 if val >= 1000 then fnt = "buff_font_smaller" end
169 169 return tostring(math.ceil(val)), fnt
170 170 end,
  171 + shield_bar = function(self, t)
  172 + local p = self:isTalentActive(t.id)
  173 + return math.ceil(p and p.value or 0), math.ceil(t.getMaxDamage(self, t))
  174 + end,
171 175 critpower = function(self, t) return self:combatTalentScale(t, 4, 15) end,
172 176 getRechargeRate = function(self, t) return self:combatTalentLimit(t, 5, 33, 10) end,
  177 + callbackPriorities = {callbackOnHit = -290},
173 178 activate = function(self, t)
174 179 game:playSoundNear(self, "talents/spell_generic2")
175 180 local ret = {
... ... @@ -200,20 +205,25 @@ newTalent{
200 205 t.updateParticles(self, t, p)
201 206 end
202 207 end,
203   - do_onTakeHit = function(self, t, p, damage)
  208 + callbackOnHit = function(self, t, cb, src, dt)
  209 + local value = cb.value
  210 + if cb.value <= 0 then return end
  211 + local p = self:isTalentActive(t.id)
  212 +
204 213 if p.value > 0 then
205 214 -- absorb 50% damage
206   - local deflectDamage = math.floor(math.min(damage * 0.5, p.value))
  215 + local deflectDamage = math.floor(math.min(cb.value * 0.5, p.value))
207 216 if deflectDamage > 0 then
208   - damage = damage - deflectDamage
  217 + cb.value = cb.value - deflectDamage
209 218 p.value = math.max(0, p.value - deflectDamage)
210 219 p.__update_display = true
211 220 t.updateParticles(self, t, p)
212 221
213 222 game.logPlayer(self, "You have deflected %d incoming damage!", deflectDamage)
  223 + game:delayedLogDamage(src, self, 0, ("#SLATE#(%d deflected)#LAST#"):tformat(deflectDamage), false)
  224 + return cb
214 225 end
215 226 end
216   - return damage
217 227 end,
218 228 updateParticles = function(self, t, p)
219 229 local power = 1 + math.floor(p.value / t.getMaxDamage(self, t) * 9)
... ...
... ... @@ -138,40 +138,44 @@ newTalent{
138 138 points = 5,
139 139 cooldown = 50,
140 140 getPower = function(self, t) return 10 + self:combatTalentMindDamage(t, 0, 700) end, --be generous
141   - onDie = function(self, t, value, src)
142   - local shadows = {}
143   - if game.party and game.party:hasMember(self) then
144   - for act, def in pairs(game.party.members) do
145   - if act.summoner and act.summoner == self and act.is_doomed_shadow and not act.dead then
146   - shadows[#shadows+1] = act
  141 + callbackPriorities = {callbackOnHit = 350},
  142 + callbackOnHit = function(self, t, cb, src, death_note)
  143 + if cb.value >= self.life then
  144 + local shadows = {}
  145 + if game.party and game.party:hasMember(self) then
  146 + for act, def in pairs(game.party.members) do
  147 + if act.summoner and act.summoner == self and act.is_doomed_shadow and not act.dead then
  148 + shadows[#shadows+1] = act
  149 + end
147 150 end
148   - end
149   - else
150   - for uid, act in pairs(game.level.entities) do
151   - if act.summoner and act.summoner == self and act.is_doomed_shadow and not act.dead then
152   - shadows[#shadows+1] = act
  151 + else
  152 + for uid, act in pairs(game.level.entities) do
  153 + if act.summoner and act.summoner == self and act.is_doomed_shadow and not act.dead then
  154 + shadows[#shadows+1] = act
  155 + end
153 156 end
154 157 end
155   - end
156   - local shadow = #shadows > 0 and rng.table(shadows)
157   - -- Nope! -- DG
158   - -- if not shadow and self:knowTalent(self.T_CALL_SHADOWS) then
159   - -- local t = self:getTalentFromId(self.T_CALL_SHADOWS)
160   - -- t.summonShadow(self, t) --summon a shadow if you have none
161   - -- end
162   - if not shadow then return false end
  158 + local shadow = #shadows > 0 and rng.table(shadows)
  159 + -- Nope! -- DG
  160 + -- if not shadow and self:knowTalent(self.T_CALL_SHADOWS) then
  161 + -- local t = self:getTalentFromId(self.T_CALL_SHADOWS)
  162 + -- t.summonShadow(self, t) --summon a shadow if you have none
  163 + -- end
  164 + if not shadow then return end
163 165
164   - game:delayedLogDamage(src, self, 0, ("#GOLD#(%d decoy)#LAST#"):tformat(value), false)
165   - game:delayedLogDamage(src, shadow, value, ("#GOLD#%d decoy#LAST#"):tformat(value), false)
166   - shadow:takeHit(value, src)
167   - self:setEffect(self.EFF_SHADOW_DECOY, 4, {power=t.getPower(self, t)})
168   - self:forceUseTalent(t.id, {ignore_energy=true})
  166 + game:delayedLogDamage(src, self, 0, ("#GOLD#(%d to decoy)#LAST#"):tformat(cb.value), false)
  167 + game:delayedLogDamage(src, shadow, cb.value, ("#GOLD#%d to decoy#LAST#"):tformat(cb.value), false)
  168 + shadow:takeHit(cb.value, src)
  169 + self:setEffect(self.EFF_SHADOW_DECOY, 4, {power=t.getPower(self, t)})
  170 + self:forceUseTalent(t.id, {ignore_energy=true})
169 171
170   - if self.player then
171   - self:setEmote(Emote.new("Fools, you never killed me; that was only my shadow!", 45))
172   - world:gainAchievement("AVOID_DEATH", self)
  172 + if self.player then
  173 + self:setEmote(Emote.new("Fools, you never killed me; that was only my shadow!", 45))
  174 + world:gainAchievement("AVOID_DEATH", self)
  175 + end
  176 + cb.value = 0
  177 + return cb
173 178 end
174   - return true
175 179 end,
176 180 activate = function(self, t)
177 181 return {}
... ...
... ... @@ -74,12 +74,15 @@ newTalent{
74 74
75 75 return true
76 76 end,
77   - onTakeHit = function(t, self, fractionDamage)
78   - if fractionDamage < 0.08 then return false end
79   - if self:hasEffect(self.EFF_RAMPAGE) then return false end
80   - if rng.percent(50) then
81   - t.action(self, t, 0)
82   - return true
  77 + callbackPriorities = {callbackOnHit = -100},
  78 + callbackOnHit = function(self, t, cb, src, death_note)
  79 + if cb.value > 0 then
  80 + local fractiondamage = cb.value / self.max_life
  81 + if fractionDamage < 0.08 then return end
  82 + if self:hasEffect(self.EFF_RAMPAGE) then return end
  83 + if rng.percent(50) then
  84 + t.action(self, t, 0)
  85 + end
83 86 end
84 87 end,
85 88 info = function(self, t)
... ...
... ... @@ -173,6 +173,22 @@ newTalent{
173 173 getChance = function(self, t)
174 174 return 50 --10 + self:getMag() * 0.25 + self:getTalentLevel(t) * 2
175 175 end,
  176 + callbackPriorities = {callbackOnHit = 350},
  177 + callbackOnHit = function(self, t, cb, src, death_note)
  178 + local value = cb.value
  179 + if value >= self.life and self.ai_state and self.ai_state.can_reform then
  180 + if rng.percent(t.getChance(self, t)) then
  181 + value = 0
  182 + self.life = self.max_life
  183 + game.logSeen(self, "%s fades for a moment and then reforms whole again!", self:getName():capitalize())
  184 + game.level.map:particleEmitter(self.x, self.y, 1, "teleport_out")
  185 + game:playSoundNear(self, "talents/heal")
  186 + game.level.map:particleEmitter(self.x, self.y, 1, "teleport_in")
  187 + end
  188 + end
  189 + cb.value = value
  190 + return cb
  191 + end,
176 192 info = function(self, t)
177 193 local chance = t.getChance(self, t)
178 194 return ([[When a shadow is hit and killed, there is a %d%% chance it will reform unhurt.]]):tformat(chance)
... ...
... ... @@ -30,6 +30,14 @@ newTalent{
30 30 on_unlearn = function(self, t)
31 31 self:attr("show_shield_combat", -1)
32 32 end,
  33 + callbackPriorities = {callbackOnHit = -500},
  34 + callbackOnHit = function(self, t, cb, src, death_note)
  35 + local value = cb.value
  36 + local m, mm, e, em = t.getValues(self, t)
  37 + self:incMana(math.min(mm, value * m))
  38 + self:incEquilibrium(-math.min(em, value * e))
  39 + self.turn_procs.stoneshield = true
  40 + end,
33 41 getValues = function(self, t)
34 42 return
35 43 self:combatTalentLimit(t, 1, 0.08, 0.165),
... ...
... ... @@ -99,6 +99,37 @@ newTalent{
99 99
100 100 return true
101 101 end,
  102 + callbackPriorities = {callbackOnHit = -40},
  103 + callbackOnHit = function(self, t, cb, src, death_note)
  104 + local value = cb.value
  105 + if value <= 0 then return end
  106 + local chance = t.getChance(self, t)
  107 + local perc = math.min(1, 3 * value / math.max(self.life, 1))
  108 + if rng.percent(chance * perc) then
  109 + t.spawn(self, t, value * 2)
  110 + end
  111 +
  112 + local acts = {}
  113 + if game.party:hasMember(self) then
  114 + for act, def in pairs(game.party.members) do
  115 + if act.summoner and act.summoner == self and act.wild_gift_summon and act.bloated_ooze then acts[#acts+1] = act end
  116 + end
  117 + else
  118 + for _, act in pairs(game.level.entities) do
  119 + if act.summoner and act.summoner == self and act.wild_gift_summon and act.bloated_ooze then acts[#acts+1] = act end
  120 + end
  121 + end
  122 + if #acts > 0 then
  123 + game:delayedLogMessage(self, nil, "mitosis_damage", "#DARK_GREEN##Source# shares damage with %s oozes!", string.his_her(self))
  124 + value = value / (#acts+1)
  125 + for _, act in ipairs(acts) do
  126 + act:takeHit(value, src)
  127 + end
  128 + end
  129 +
  130 + cb.value = value
  131 + return cb
  132 + end,
102 133 activate = function(self, t)
103 134 return {equil_regen = self:knowTalent(self.T_REABSORB) and self:addTemporaryValue("equilibrium_regen", -self:callTalent(self.T_REABSORB, "equiRegen"))}
104 135 end,
... ...
... ... @@ -114,6 +114,27 @@ newTalent{
114 114 local mult = 1 + (not raw and self:callTalent(self.T_AMPLIFICATION, "getFeedbackGain") or 0)
115 115 return ratio*mult
116 116 end,
  117 + callbackPriorities = {callbackOnHit = -100},
  118 + callbackOnHit = function(self, t, cb, src, death_note)
  119 + local value = cb.value + (self.turn_procs.resonance_field_absorb or 0)
  120 + self.turn_procs.resonance_field_absorb = nil
  121 + if value <= 0 then return end
  122 + local ratio = t.getFeedbackRatio(self, t)
  123 + local feedback_gain = value * ratio
  124 + self:incFeedback(feedback_gain)
  125 + -- Give feedback to summoner
  126 + if self.summoner and self.summoner:getTalentLevel(self.summoner.T_OVER_MIND) >=1 and self.summoner:getMaxFeedback() > 0 then
  127 + self.summoner:incFeedback(feedback_gain)
  128 + end
  129 + -- Trigger backlash retribution damage
  130 + if src and src.turn_procs and self:knowTalent(self.T_BACKLASH) and not src.no_backlash_loops and not src.turn_procs.backlash then
  131 + if src.y and src.x and not src.dead then
  132 + local t = self:getTalentFromId(self.T_BACKLASH)
  133 + t.doBacklash(self, src, feedback_gain, t)
  134 + src.turn_procs.backlash = true
  135 + end
  136 + end
  137 + end,
117 138 no_unlearn_last = true,
118 139 on_learn = function(self, t)
119 140 if self:getMaxFeedback() <= 0 then
... ... @@ -232,6 +253,33 @@ newTalent{
232 253 game.logPlayer(self, hateMessage.." (+%d hate)", hateGain - self.hate_per_kill)
233 254 end
234 255 end,
  256 + callbackPriorities = {callbackOnHit = -100},
  257 + callbackOnHit = function(self, t, cb, src, death_note)
  258 + local value = cb.value
  259 + if value <= 0 then return end
  260 + local hateGain = 0
  261 + local hateMessage
  262 +
  263 + if value / self.max_life >= 0.15 then
  264 + -- you take a big hit..adds 2 + 2 for each 5% over 15%
  265 + hateGain = hateGain + 2 + (((value / self.max_life) - 0.15) * 100 * 0.5)
  266 + hatemessage = _t"#F53CBE#You fight through the pain!"
  267 + end
  268 +
  269 + if value / self.max_life >= 0.05 and (self.life - value) / self.max_life < 0.25 then
  270 + -- you take a hit with low health
  271 + hateGain = hateGain + 4
  272 + hatemessage = _t"#F53CBE#Your hatred grows even as your life fades!"
  273 + end
  274 +
  275 + if hateGain >= 1 then
  276 + self:incHate(hateGain)
  277 + if hateMessage then
  278 + game.logPlayer(self, ("%s (+%d hate)"):tformat(hateMessage), hateGain)
  279 + end
  280 + end
  281 +
  282 + end,
235 283 }
236 284
237 285 newTalent{
... ...
... ... @@ -2224,6 +2224,12 @@ newTalent{
2224 2224 points = 1,
2225 2225 mode = "sustained",
2226 2226 cooldown = 10,
  2227 + callbackPriorities = {callbackOnHit = -1000},
  2228 + callbackOnHit = function(self, eff, cb, src, death_note)
  2229 + if cb.value <= 0 then return cb end
  2230 + self:forceUseTalent(self.T_SUSPENDED, {ignore_energy=true})
  2231 + return cb
  2232 + end,
2227 2233 activate = function(self, t)
2228 2234 local ret = {}
2229 2235 self:talentTemporaryValue(ret, "invulnerable", 1)
... ... @@ -3692,6 +3698,22 @@ newTalent{
3692 3698 getDur = function(self, t) return math.floor(self:combatTalentScale(t, 3, 10)) end,
3693 3699 getPower = function(self, t) return 5 + self:combatTalentMindDamage(t, 0, 300) / 8 end,
3694 3700 on_pre_use = function(self, t) return self:callTalent(self.T_CALL_SHADOWS, "nbShadowsUp") > 0 end,
  3701 + callbackPriorities = {callbackOnHit = -800},
  3702 + callbackOnHit = function(self, t, cb, src, death_note)
  3703 + local value = cb.value
  3704 + if value <= 0 then return end
  3705 + local shadow = t.getRandomShadow(self, t)
  3706 + if shadow then
  3707 + game:delayedLogMessage(self, src, "displacement_shield"..(shadow.uid or ""), "#CRIMSON##Source# shares some damage with a shadow!")
  3708 + local displaced = math.min(value * self.shadow_empathy / 100, shadow.life)
  3709 + shadow:takeHit(displaced, src)
  3710 + game:delayedLogDamage(src, self, 0, ("#PINK#(%d linked)#LAST#"):tformat(displaced), false)
  3711 + game:delayedLogDamage(src, shadow, displaced, ("#PINK#%d linked#LAST#"):tformat(displaced), false)
  3712 + value = value - displaced
  3713 + cb.value = value
  3714 + return cb
  3715 + end
  3716 + end,
3695 3717 action = function(self, t)
3696 3718 self:setEffect(self.EFF_SHADOW_EMPATHY, t.getDur(self, t), {power=t.getPower(self, t)})
3697 3719 return true
... ...
... ... @@ -228,6 +228,14 @@ newTalent{
228 228 self:removeTemporaryValue("invis_on_hit_disable", p.talent)
229 229 return true
230 230 end,
  231 + callbackPriorities = {callbackOnHit = 100},
  232 + callbackOnHit = function(self, t, cb, src, death_note)
  233 + local value = cb.value
  234 + if value >= self.max_life * 0.10 and rng.percent(t.getChance(self, t)) then
  235 + self:setEffect(self.EFF_INVISIBILITY, 5, {power=t.getInvis(self, t)})
  236 + for tid, _ in pairs(self.invis_on_hit_disable) do self:forceUseTalent(tid, {ignore_energy=true}) end
  237 + end
  238 + end,
231 239 info = function(self, t)
232 240 return ([[As the only immortal race of Eyal, Shaloren have learnt over the long years to use their innate inner magic to protect themselves.
233 241 %d%% chance to become invisible (power %d) for 5 turns when hit by a blow doing at least 10%% of your total life.]]):
... ... @@ -609,6 +617,13 @@ newTalent{
609 617 local oldevasion = self:hasEffect(self.EFF_EVASION)
610 618 return self:getStat("lck")/200*(self:combatDefenseBase() - (oldevasion and oldevasion.defense or 0)) -- Prevent stacking
611 619 end,
  620 + callbackPriorities = {callbackOnHit = 100},
  621 + callbackOnHit = function(self, t, cb, src, death_note)
  622 + local value = cb.value
  623 + if value >= self.max_life * t.getThreshold(self, t) then
  624 + self:setEffect(self.EFF_EVASION, t.getDuration(self, t), {chance=t.getEvasionChance(self, t), defense = t.getDefense(self)})
  625 + end
  626 + end,
612 627 info = function(self, t)
613 628 local threshold = t.getThreshold(self, t)
614 629 local evasion = t.getEvasionChance(self, t)
... ...
... ... @@ -138,6 +138,13 @@ newTalent{
138 138 return self:combatTalentMindDamage(t, 0, 300) + self.max_life * self:combatTalentLimit(t, 1, .015, .055)
139 139 end,
140 140 getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5)) end,
  141 + callbackPriorities = {callbackOnHit = 350},
  142 + callbackOnHit = function(self, t, cb, src, death_note)
  143 + local value = cb.value
  144 + if value > self.life then
  145 + self:forceUseTalent(t.id, {ignore_energy=true})
  146 + end
  147 + end,
141 148 activate = function(self, t)
142 149 return {}
143 150 end,
... ...
... ... @@ -34,7 +34,33 @@ newTalent{
34 34 local talentmod = self:combatTalentLimit(t, 50, 3, 11) -- Limit < 50%
35 35 return 100 - (100 - talentmod)/lifemod, 1-1/lifemod, talentmod
36 36 end,
  37 + callbackPriorities = {callbackOnHit = -30},
  38 + callbackOnHit = function(self, t, cb, src, death_note)
  39 + local value = cb.value
  40 + local damage_to_psi = 0
  41 + if value > 0 and self:getPsi() > 0 then
  42 + damage_to_psi = value * t.getConversionRatio(self, t)
  43 + end
  44 +
  45 + if damage_to_psi > 0 then
  46 + local psi_damage_resist = 1 - t.getPsiDamageResist(self, t)/100
  47 + -- print("Psi Damage Resist", psi_damage_resist, "Damage", damage_to_psi, "Final", damage_to_psi*psi_damage_resist)
  48 + if self:getPsi() > damage_to_psi*psi_damage_resist then
  49 + self:incPsi(-damage_to_psi*psi_damage_resist)
  50 + else
  51 + damage_to_psi = self:getPsi()
  52 + self:incPsi(-damage_to_psi)
  53 + end
  54 + local mindcolor = DamageType:get(DamageType.MIND).text_color or "#aaaaaa#"
  55 + game:delayedLogMessage(self, nil, "Solipsism hit", ("%s#Source# converts some damage to Psi!"):tformat(mindcolor))
  56 + game:delayedLogDamage(src, self, damage_to_psi*psi_damage_resist, ("%s%d %s#LAST#"):tformat(mindcolor, damage_to_psi*psi_damage_resist, _t"to psi"), false)
37 57
  58 + value = value - damage_to_psi
  59 + end
  60 +
  61 + cb.value = value
  62 + return cb
  63 + end,
38 64 on_levelup_close = function(self, t, lvl, old_lvl, lvl_raw, old_lvl_raw)
39 65 if old_lvl_raw == 0 and lvl_raw >= 1 then
40 66 self.inc_resource_multi.psi = (self.inc_resource_multi.psi or 0) + 0.5
... ... @@ -175,17 +201,17 @@ newTalent{
175 201 self.solipsism_threshold = self.solipsism_threshold - 0.1
176 202 end
177 203 end,
178   - -- called by _M:onTakeHit in mod.class.Actor.lua
179   - doDismissalOnHit = function(self, value, src, t)
180   - local saving_throw = self:combatMentalResist() * t.getSavePercentage(self, t)
181   - print("[Dismissal] ", self:getName():capitalize(), " attempting to ignore ", value, "damage from ", src.name:capitalize(), "using", saving_throw, "mental save.")
182   - if self:checkHit(saving_throw, value) then
183   - local dismissed = value * (1 - (1 / self:mindCrit(2))) -- Diminishing returns on high crits
184   - game:delayedLogMessage(self, nil, "Dismissal", "#TAN##Source# mentally dismisses some damage!")
185   - game:delayedLogDamage(src, self, 0, ("#TAN#(%d dismissed)#LAST#"):tformat(dismissed))
186   - return value - dismissed
187   - else
188   - return value
  204 + callbackPriorities = {callbackOnHit = -700},
  205 + callbackOnHit = function(self, t, cb, src, death_note)
  206 + if cb.value > 0 then
  207 + local saving_throw = self:combatMentalResist() * t.getSavePercentage(self, t)
  208 + print("[Dismissal] ", self:getName():capitalize(), " attempting to ignore ", cb.value, "damage from ", src.name:capitalize(), "using", saving_throw, "mental save.")
  209 + if self:checkHit(saving_throw, cb.value) then
  210 + local dismissed = cb.value * (1 - (1 / self:mindCrit(2))) -- Diminishing returns on high crits
  211 + game:delayedLogMessage(self, nil, "Dismissal", "#TAN##Source# mentally dismisses some damage!")
  212 + game:delayedLogDamage(src, self, 0, ("#TAN#(%d dismissed)#LAST#"):tformat(dismissed))
  213 + cb.value = cb.value - dismissed
  214 + end
189 215 end
190 216 end,
191 217 info = function(self, t)
... ...
... ... @@ -39,6 +39,11 @@ newTalent{
39 39 if val >= 1000 then fnt = "buff_font_smaller" end
40 40 return tostring(math.ceil(val)), fnt
41 41 end,
  42 + shield_bar = function(self, t, p)
  43 + local power = p.shield or 0
  44 + local power_max = t.getMaxAbsorb(self, t)
  45 + return power, power_max
  46 + end,
42 47 resetShield = function(self, t, ret)
43 48 ret.been_used = nil
44 49 ret.shield = t:_getMaxAbsorb(self)
... ... @@ -49,6 +54,7 @@ newTalent{
49 54 ret.waste_counter = 0
50 55 end
51 56 end,
  57 + callbackPriorities = {callbackOnHit = -290},
52 58 callbackOnAct = checkLifeThreshold(1, function(self, t)
53 59 local p = self:isTalentActive(t.id)
54 60 if not p then return end
... ...
... ... @@ -239,7 +239,14 @@ newTalent {
239 239 end
240 240 return damage
241 241 end,
242   -
  242 + callbackPriorities = {callbackOnHit = -50},
  243 + callbackOnHit = function(self, t, cb, src, death_note)
  244 + local value = cb.value
  245 + value = t.onHit(self, t, value)
  246 + print("[onTakeHit] After Trained Reactions life% trigger ", value)
  247 + cb.value = value
  248 + return cb
  249 + end,
243 250 info = function(self, t)
244 251 local trigger = t.getLifeTrigger(self, t)
245 252 local reduce = t.getReduction(self, t)
... ...
... ... @@ -29,11 +29,15 @@ newTalent{
29 29 end,
30 30 getWoundReduction = function(self, t) return self:combatTalentLimit(t, 0.6, 0.225, 0.5) end, -- Limit <60%%
31 31 getDuration = function(self, t) return 8 end,
32   - do_vitality_recovery = function(self, t)
33   - if self:isTalentCoolingDown(t) then return end
34   - local baseheal = t.getHealValues(self, t)
35   - self:setEffect(self.EFF_RECOVERY, t.getDuration(self, t), {regen = baseheal})
36   - self:startTalentCooldown(t)
  32 + callbackPriorities = {callbackOnHit = 300},
  33 + callbackOnHit = function(self, t, cb, src, death_note)
  34 + local value = cb.value
  35 + if self.life > self.max_life /2 and self.life - value <= self.max_life/2 then
  36 + if self:isTalentCoolingDown(t) then return end
  37 + local baseheal = t.getHealValues(self, t)
  38 + self:setEffect(self.EFF_RECOVERY, t.getDuration(self, t), {regen = baseheal})
  39 + self:startTalentCooldown(t)
  40 + end
37 41 end,
38 42 info = function(self, t)
39 43 local wounds = t.getWoundReduction(self, t) * 100
... ...
... ... @@ -22,12 +22,15 @@ uberTalent{
22 22 mode = "passive",
23 23 cooldown = 15,
24 24 require = { special={desc=_t"Be close to the draconic world", fct=function(self) return game.state.birth.ignore_prodigies_special_reqs or (self:attr("drake_touched") and self:attr("drake_touched") >= 2) end} },
25   - trigger = function(self, t, value)
26   - if self.life - value < self.max_life * 0.3 and not self:isTalentCoolingDown(t) then
  25 + callbackPriorities = {callbackOnHit = 300},
  26 + callbackOnHit = function(self, t, cb, src, death_note)
  27 + if cb.value <= 0 then return end
  28 + if self.life - cb.value < self.max_life * 0.3 and not self:isTalentCoolingDown(t) then
27 29 self:heal(self.max_life * 0.4, t)
28 30 self:startTalentCooldown(t)
29 31 game.logSeen(self,"%s's draconic body hardens and heals!",self:getName())
30 32 end
  33 + return cb
31 34 end,
32 35 info = function(self, t)
33 36 return ([[Your body hardens and recovers quickly. When pushed below 30%% life, you instantly restore 40%% of your total life.]])
... ... @@ -40,22 +43,30 @@ uberTalent{
40 43 mode = "passive",
41 44 cooldown = 12,
42 45 require = { special={desc=_t"Have let Melinda be sacrificed", fct=function(self) return game.state.birth.ignore_prodigies_special_reqs or (self:hasQuest("kryl-feijan-escape") and self:hasQuest("kryl-feijan-escape"):isStatus(engine.Quest.FAILED)) end} },
  46 + callbackPriorities = {callbackOnHit = 100},
  47 + callbackOnHit = function(self, t, cb, src, death_note)
  48 + if cb.value <= 0 or self:isTalentCoolingDown(t) then return end
  49 + if cb.value >= self.max_life * 0.15 then
  50 + -- Add a lasting map effect
  51 + game.level.map:addEffect(self,
  52 + self.x, self.y, 4,
  53 + DamageType.BLOODSPRING, {dam={dam=100 + self:getCon() * 3, healfactor=0.5}, x=self.x, y=self.y, st=DamageType.DRAINLIFE, power=50 + self:getCon() * 2},
  54 + 1,
  55 + 5, nil,
  56 + MapEffect.new{color_br=255, color_bg=20, color_bb=20, effect_shader="shader_images/darkness_effect.png"},
  57 + function(e, update_shape_only)
  58 + if not update_shape_only then e.radius = e.radius + 0.5 end
  59 + return true
  60 + end,
  61 + false
  62 + )
  63 + game:playSoundNear(self, "talents/tidalwave")
  64 + self:startTalentCooldown(t)
  65 + game.logSeen(self,"%s's blood gushes out in a torrent!",self:getName())
  66 + end
  67 + end,
43 68 trigger = function(self, t)
44   - -- Add a lasting map effect
45   - game.level.map:addEffect(self,
46   - self.x, self.y, 4,
47   - DamageType.BLOODSPRING, {dam={dam=100 + self:getCon() * 3, healfactor=0.5}, x=self.x, y=self.y, st=DamageType.DRAINLIFE, power=50 + self:getCon() * 2},
48   - 1,
49   - 5, nil,
50   - MapEffect.new{color_br=255, color_bg=20, color_bb=20, effect_shader="shader_images/darkness_effect.png"},
51   - function(e, update_shape_only)
52   - if not update_shape_only then e.radius = e.radius + 0.5 end
53   - return true
54   - end,
55   - false
56   - )
57   - game:playSoundNear(self, "talents/tidalwave")
58   - self:startTalentCooldown(t)
  69 +
59 70 end,
60 71 info = function(self, t)
61 72 return ([[When a single blow deals more than 15%% of your total life, a torrent of blood gushes from your body, creating a bloody tidal wave for 4 turns that deals %0.2f blight damage, heals you for 50%% of the damage done, and knocks foes back.
... ...
... ... @@ -206,12 +206,26 @@ uberTalent{
206 206 on_learn = function(self, t)
207 207 self.inc_stats[self.STAT_LCK] = (self.inc_stats[self.STAT_LCK] or 0) + 40
208 208 self:onStatChange(self.STAT_LCK, 40)
209   - self:attr("phase_shift", 0.1)
210 209 end,
211 210 on_unlearn = function(self, t)
212 211 self.inc_stats[self.STAT_LCK] = (self.inc_stats[self.STAT_LCK] or 0) - 40
213 212 self:onStatChange(self.STAT_LCK, -40)
214   - self:attr("phase_shift", -0.1)
  213 + end,
  214 + callbackPriorities = {callbackOnHit = -1100},
  215 + callbackOnHit = function(self, t, cb, src, death_note)
  216 + if cb.value <= 0 then return cb end
  217 + if rng.percent(10) and not self.turn_procs.phase_shift then
  218 + self.turn_procs.phase_shift = true
  219 + local nx, ny = util.findFreeGrid(self.x, self.y, 1, true, {[Map.ACTOR]=true})
  220 + if nx then
  221 + local ox, oy = self.x, self.y
  222 + self:move(nx, ny, true)
  223 + game.level.map:particleEmitter(ox, oy, math.max(math.abs(nx-ox), math.abs(ny-oy)), "lightning", {tx=nx-ox, ty=ny-oy})
  224 + game:delayedLogDamage(src or {}, self, 0, ("#STEEL_BLUE#(%d shifted)#LAST#"):tformat(cb.value), nil)
  225 + cb.value = 0
  226 + end
  227 + end
  228 + return cb
215 229 end,
216 230 info = function(self, t)
217 231 return ([[Every day is your lucky day! You gain a permanent +40 luck bonus and 10%% to move out of the way of every attack.]])
... ...
... ... @@ -135,6 +135,16 @@ newEffect{
135 135 parameters = {},
136 136 on_gain = function(self, err) return _t"#Target# turns to #GREY#STONE#LAST#!", _t"+Stoned" end,
137 137 on_lose = function(self, err) return _t"#Target# is no longer a #GREY#statue#LAST#.", _t"-Stoned" end,
  138 + callbackPriorities = {callbackOnHit = 200},
  139 + callbackOnHit = function(self, eff, cb, src, death_note)
  140 + if cb.value <= 0 then return cb end
  141 + if cb.value >= self.max_life * 0.3 then
  142 + -- Make the damage high enough to kill it
  143 + cb.value = self.max_life + 1
  144 + game.logSeen(self, "%s shatters into pieces!", self:getName():capitalize())
  145 + end
  146 + return cb
  147 + end,
138 148 activate = function(self, eff)
139 149 eff.tmpid = self:addTemporaryValue("stoned", 1)
140 150 eff.poison = self:addTemporaryValue("poison_immune", 1)
... ... @@ -708,15 +718,18 @@ newEffect{
708 718 newEffect{
709 719 name = "DISPLACEMENT_SHIELD", image = "talents/displacement_shield.png",
710 720 desc = _t"Displacement Shield",
711   - long_desc = function(self, eff) return ("The target is surrounded by a space distortion that randomly sends (%d%% chance) incoming damage to another target (%s). Absorbs %d/%d damage before it crumbles."):tformat(eff.chance, eff.target and eff.target:getName() or "unknown", self.displacement_shield, eff.power) end,
  721 + long_desc = function(self, eff) return ("The target is surrounded by a space distortion that randomly sends (%d%% chance) incoming damage to another target (%s). Absorbs %d/%d damage before it crumbles."):tformat(eff.chance, eff.target and eff.target:getName() or "unknown", eff.power, eff.power_max) end,
712 722 type = "magical",
713 723 subtype = { teleport=true, shield=true },
714 724 status = "beneficial",
715   - parameters = { power=10, target=nil, chance=25 },
  725 + parameters = { power=10, power_max=10, target=nil, chance=25 },
  726 + charges = function(self, eff) return math.ceil(eff.power) end,
  727 + shield_bar = function(self, eff) return eff.power, eff.power_max end,
716 728 on_gain = function(self, err) return _t"The very fabric of space alters around #target#.", _t"+Displacement Shield" end,
717 729 on_lose = function(self, err) return _t"The fabric of space around #target# stabilizes to normal.", _t"-Displacement Shield" end,
718 730 on_aegis = function(self, eff, aegis)
719   - self.displacement_shield = self.displacement_shield + eff.power * aegis / 100
  731 + eff.power = eff.power * (1 + aegis / 100)
  732 + eff.power_max = eff.power_max * (1 + aegis / 100)
720 733 if core.shader.active(4) then
721 734 self:removeParticles(eff.particle)
722 735 eff.particle = self:addParticles(Particles.new("shader_shield", 1, {size_factor=1.3, img="runicshield"}, {type="runicshield", shieldIntensity=0.14, ellipsoidalFactor=1.2, time_factor=4000, bubbleColor={0.5, 1, 0.2, 1.0}, auraColor={0.4, 1, 0.2, 1}}))
... ... @@ -730,14 +743,31 @@ newEffect{
730 743 eff.particle._shader:setUniform("impact_tick", core.game.getTime())
731 744 end
732 745 end,
  746 + callbackPriorities={callbackOnHit = -270}, --Displacement Shield, Time Shield, then Damage Shield.
  747 + callbackOnHit = function(self, eff, cb, src, death_note)
  748 + local value = cb.value
  749 + if value <= 0 or not eff.target then return cb end
  750 + if rng.percent(eff.chance) then
  751 + game:delayedLogMessage(self, src, "displacement_shield"..(eff.target.uid or ""), "#CRIMSON##Source# teleports some damage to #Target#!")
  752 + local displaced = math.min(value, eff.power)
  753