From 527840a8d718a6206c11404df49c06af3afedfca Mon Sep 17 00:00:00 2001
From: grayswandir <grayswandir76@gmail.com>
Date: Sat, 20 Dec 2014 16:52:55 -0500
Subject: [PATCH] Adds a 'lists' system for convenience. Modified stealth and
 step up to use.

This lets talents / effects add themselves to lists at appropriate
times. Bascially, just a quicker way than searching for all applied
talents/effects for a specific tag.

Talents can have a sustain_lists and a learn_lists field.
Effects just have a lists field.

Example:
talent A has the following:
learn_lists = {'red', {'blue', 'green'}}
When you learn it, your character now has the following fields:
self.red = {'T_A'}
self.blue.green = {'T_A'}

Modified stealth and step up to use this system. So, to make something
break when step up does, just add it to the 'break_with_step_up' list.
---
 .../default/engine/interface/ActorTalents.lua | 52 +++++++++++++++++++
 .../interface/ActorTemporaryEffects.lua       | 27 ++++++++++
 game/engines/default/engine/utils.lua         | 20 +++++++
 game/modules/tome/class/Actor.lua             | 42 ++++++++-------
 .../tome/data/talents/cunning/stealth.lua     |  5 +-
 .../tome/data/timed_effects/physical.lua      |  9 +++-
 6 files changed, 133 insertions(+), 22 deletions(-)

diff --git a/game/engines/default/engine/interface/ActorTalents.lua b/game/engines/default/engine/interface/ActorTalents.lua
index a055f4dda5..9230becb03 100644
--- a/game/engines/default/engine/interface/ActorTalents.lua
+++ b/game/engines/default/engine/interface/ActorTalents.lua
@@ -197,6 +197,19 @@ function _M:useTalent(id, who, force_level, ignore_cd, force_target, silent, no_
 				if not self:postUseTalent(ab, ret) then return end
 
 				self.sustain_talents[id] = ret
+
+				if ab.sustain_lists then
+					local lists = ab.sustain_lists
+					if 'table' ~= type(lists) then lists = {lists} end
+					for _, list in ipairs(lists) do
+						if 'table' == type(list) then
+							list = table.getTable(self, unpack(list))
+						else
+							list = table.getTable(self, list)
+						end
+						table.insert(list, id)
+					end
+				end
 			else
 				local old_level
 				if force_level then old_level = who.talents[id]; who.talents[id] = force_level end
@@ -214,6 +227,19 @@ function _M:useTalent(id, who, force_level, ignore_cd, force_target, silent, no_
 				-- Everything went ok? then start cooldown if any
 				if not ignore_cd then self:startTalentCooldown(ab) end
 				self.sustain_talents[id] = nil
+
+				if ab.sustain_lists then
+					local lists = ab.sustain_lists
+					if 'table' ~= type(lists) then lists = {lists} end
+					for _, list in ipairs(lists) do
+						if 'table' == type(list) then
+							list = table.getTable(self, unpack(list))
+						else
+							list = table.getTable(self, list)
+						end
+						table.removeFromList(list, id)
+					end
+				end
 			end
 		end)
 		local success, err
@@ -353,6 +379,19 @@ function _M:learnTalent(t_id, force, nb)
 				end
 			end
 		end
+
+		if t.learn_lists then
+			local lists = t.learn_lists
+			if 'table' ~= type(lists) then lists = {lists} end
+			for _, list in ipairs(lists) do
+				if 'table' == type(list) then
+					list = table.getTable(self, unpack(list))
+				else
+					list = table.getTable(self, list)
+				end
+				table.insert(list, t.id)
+			end
+		end
 	end
 
 	for i = 1, (nb or 1) do
@@ -443,6 +482,19 @@ function _M:unlearnTalent(t_id, nb)
 		end
 	end
 
+	if t.learn_lists and not self:knowTalent(t_id) then
+		local lists = t.learn_lists
+		if 'table' ~= type(lists) then lists = {lists} end
+		for _, list in ipairs(lists) do
+			if 'table' == type(list) then
+				list = table.getTable(self, unpack(list))
+			else
+				list = table.getTable(self, list)
+			end
+			table.removeFromList(list, t.id)
+		end
+	end
+
 	if self.talents[t_id] == nil then self.talents_auto[t_id] = nil end
 
 	self.changed = true
diff --git a/game/engines/default/engine/interface/ActorTemporaryEffects.lua b/game/engines/default/engine/interface/ActorTemporaryEffects.lua
index 581a62c35b..1dcbbaf2c8 100644
--- a/game/engines/default/engine/interface/ActorTemporaryEffects.lua
+++ b/game/engines/default/engine/interface/ActorTemporaryEffects.lua
@@ -141,6 +141,20 @@ function _M:setEffect(eff_id, dur, p, silent)
 		end
 	end
 	if ed.activate then ed.activate(self, p, ed) end
+
+	if ed.lists then
+		local lists = ed.lists
+		if 'table' ~= type(lists) then lists = {lists} end
+		for _, list in ipairs(lists) do
+			if 'table' == type(list) then
+				list = table.getTable(self, unpack(list))
+			else
+				list = table.getTable(self, list)
+			end
+		end
+		table.insert(list, eff_id)
+	end
+
 	self.changed = true
 	self:check("on_temporary_effect_added", eff_id, ed, p)
 end
@@ -177,6 +191,19 @@ function _M:removeEffect(eff, silent, force)
 		end
 	end
 	if _M.tempeffect_def[eff].deactivate then _M.tempeffect_def[eff].deactivate(self, p, _M.tempeffect_def[eff]) end
+	if ed.lists then
+		local lists = ed.lists
+		if 'table' ~= type(lists) then lists = {lists} end
+		for _, list in ipairs(lists) do
+			if 'table' == type(list) then
+				list = table.getTable(self, unpack(list))
+			else
+				list = table.getTable(self, list)
+			end
+		end
+		table.removeFromList(list, eff_id)
+	end
+
 	self:check("on_temporary_effect_removed", eff, _M.tempeffect_def[eff], p)
 end
 
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index f4a6dcfaef..15b04b91e6 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -394,6 +394,26 @@ function table.set(table, ...)
 	table[args[#args - 1]] = args[#args]
 end
 
+--[=[
+	Decends recursively through a table by the given list of keys,
+	returning the table at the end. Missing keys will have tables
+	created for them. If a non-table value is encountered, return nil.
+]=]
+function table.getTable(table, ...)
+	if 'table' ~= type(table) then return end
+	local args = {...}
+	for i = 1, #args do
+		local key = args[i]
+		local subtable = table[key]
+		if not subtable then
+			subtable = {}
+			table[key] = subtable
+		end
+		if 'table' ~= type(subtable) then return end
+		table = subtable
+	end
+	return table
+end
 
 -- Taken from http://lua-users.org/wiki/SortedIteration and modified
 local function cmp_multitype(op1, op2)
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 1c1fb34032..45f72560cb 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -2184,7 +2184,7 @@ function _M:onTakeHit(value, src, death_note)
 			self:removeEffect(self.EFF_PSI_DAMAGE_SHIELD)
 		end
 	end
-	
+
 	if value > 0 and self:attr("shadow_empathy") then
 		-- Absorb damage into a random shadow
 		local shadow = self:callTalent(self.T_SHADOW_EMPATHY, "getRandomShadow")
@@ -5082,7 +5082,7 @@ function _M:postUseTalent(ab, ret, silent)
 	end
 
 	-- Cancel stealth!
-	if ab.id ~= self.T_STEALTH and ab.id ~= self.T_HIDE_IN_PLAIN_SIGHT and not util.getval(ab.no_break_stealth, self, ab) then self:breakStealth() end
+	if not util.getval(ab.no_break_stealth, self, ab) then self:breakStealth() end
 	if ab.id ~= self.T_LIGHTNING_SPEED then self:breakLightningSpeed() end
 	if ab.id ~= self.T_GATHER_THE_THREADS and ab.is_spell then self:breakChronoSpells() end
 	if not ab.no_reload_break then self:breakReloading() end
@@ -5166,6 +5166,22 @@ function _M:forceUseTalent(t, def)
 	return unpack(ret)
 end
 
+-- Remove an effect or sustain.
+function _M:removeModifier(id)
+	if 'T_' == id:sub(1, 2) then
+		self:forceUseTalent(id, {ignore_energy=true})
+	elseif 'EFF_' == id:sub(1, 4) then
+		self:removeEffect(id)
+	end
+end
+
+-- Remove a list of effects or sustains.
+function _M:removeModifierList(list)
+	for _, id in ipairs(list) do
+		self:removeModifier(id)
+	end
+end
+
 function _M:breakReloading()
 	if self:hasEffect(self.EFF_RELOADING) then
 		self:removeEffect(self.EFF_RELOADING)
@@ -5174,7 +5190,8 @@ end
 
 --- Breaks stealth if active
 function _M:breakStealth()
-	if self:isTalentActive(self.T_STEALTH) then
+	local breaks = self.break_with_stealth
+	if breaks and #breaks > 0 then
 		local chance = 0
 		if self:knowTalent(self.T_UNSEEN_ACTIONS) then
 			chance = self:callTalent(self.T_UNSEEN_ACTIONS,"getChance") + (self:getLck() - 50) * 0.2
@@ -5182,28 +5199,15 @@ function _M:breakStealth()
 		-- Do not break stealth
 		if rng.percent(chance) then return end
 
-		self:forceUseTalent(self.T_STEALTH, {ignore_energy=true})
+		self:removeModifierList(breaks)
 		self.changed = true
 	end
 end
 
 --- Breaks step up if active
 function _M:breakStepUp()
-	if self:hasEffect(self.EFF_STEP_UP) then
-		self:removeEffect(self.EFF_STEP_UP)
-	end
-	if self:hasEffect(self.EFF_WILD_SPEED) then
-		self:removeEffect(self.EFF_WILD_SPEED)
-	end
-	if self:hasEffect(self.EFF_HUNTER_SPEED) then
-		self:removeEffect(self.EFF_HUNTER_SPEED)
-	end
-	if self:hasEffect(self.EFF_REFLEXIVE_DODGING) then
-		self:removeEffect(self.EFF_REFLEXIVE_DODGING)
-	end
-	if self:hasEffect(self.EFF_SKIRMISHER_DIRECTED_SPEED) then
-		self:removeEffect(self.EFF_SKIRMISHER_DIRECTED_SPEED)
-	end
+	local breaks = self.break_with_step_up
+	if breaks and #breaks > 0 then self:removeModifierList(breaks) end
 end
 
 --- Breaks lightning speed if active
diff --git a/game/modules/tome/data/talents/cunning/stealth.lua b/game/modules/tome/data/talents/cunning/stealth.lua
index b605d36201..ffb001310f 100644
--- a/game/modules/tome/data/talents/cunning/stealth.lua
+++ b/game/modules/tome/data/talents/cunning/stealth.lua
@@ -45,6 +45,7 @@ newTalent{
 	allow_autocast = true,
 	no_energy = true,
 	tactical = { BUFF = 3 },
+	no_break_stealth = true,
 	getStealthPower = function(self, t) return 10 + self:combatScale(math.max(1,self:getCun(10, true) * self:getTalentLevel(t)), 5, 1, 54, 50) end, --TL 5, cun 100 = 54
 	getRadius = function(self, t) return math.ceil(self:combatTalentLimit(t, 0, 8.9, 4.6)) end, -- Limit to range >= 1
 	on_pre_use = function(self, t, silent)
@@ -58,13 +59,14 @@ newTalent{
 		-- Check nearby actors detection ability
 		if not self.x or not self.y or not game.level then return end
 		if not rng.percent(self.hide_chance or 0) then
-			if stealthDetection(self, t.getRadius(self, t)) > 0 then 
+			if stealthDetection(self, t.getRadius(self, t)) > 0 then
 				if not silent then game.logPlayer(self, "You are being observed too closely to enter Stealth!") end
 				return nil
 			end
 		end
 		return true
 	end,
+	sustain_lists = "break_with_stealth",
 	activate = function(self, t)
 		local res = {
 			stealth = self:addTemporaryValue("stealth", t.getStealthPower(self, t)),
@@ -123,6 +125,7 @@ newTalent{
 	-- 90% (~= 47% chance against 1 opponent (range 1) at talent level 1, 270% (~= 75% chance against 1 opponent (range 1) and 3 opponents (range 6) at talent level 5
 	-- vs flat 47% at 1, 75% @ 5 previous
 	stealthMult = function(self, t) return self:combatTalentScale(t, 0.9, 2.7) end,
+	no_break_stealth = true,
 	getChance = function(self, t, fake)
 		local netstealth = t.stealthMult(self, t) * (self:callTalent(self.T_STEALTH, "getStealthPower") + (self:attr("inc_stealth") or 0))
 		if fake then return netstealth end
diff --git a/game/modules/tome/data/timed_effects/physical.lua b/game/modules/tome/data/timed_effects/physical.lua
index 21efc23075..d9299aa3fc 100644
--- a/game/modules/tome/data/timed_effects/physical.lua
+++ b/game/modules/tome/data/timed_effects/physical.lua
@@ -1096,6 +1096,7 @@ newEffect{
 		local d = game.turn - eff.start_turn
 		return util.bound(360 - d / eff.possible_end_turns * 360, 0, 360)
 	end,
+	lists = 'break_with_step_up',
 	activate = function(self, eff)
 		eff.start_turn = game.turn
 		eff.possible_end_turns = 10 * (eff.dur+1)
@@ -1124,6 +1125,7 @@ newEffect{
 		local d = game.turn - eff.start_turn
 		return util.bound(360 - d / eff.possible_end_turns * 360, 0, 360)
 	end,
+	lists = 'break_with_step_up',
 	activate = function(self, eff)
 		eff.start_turn = game.turn
 		eff.possible_end_turns = 10 * (eff.dur+1)
@@ -1152,6 +1154,7 @@ newEffect{
 		local d = game.turn - eff.start_turn
 		return util.bound(360 - d / eff.possible_end_turns * 360, 0, 360)
 	end,
+	lists = 'break_with_step_up',
 	activate = function(self, eff)
 		eff.start_turn = game.turn
 		eff.possible_end_turns = 10 * (eff.dur+1)
@@ -1281,14 +1284,14 @@ newEffect{
 	callbackOnHit = function(self, eff, cb, src)
 		if not src then return cb.value end
 		local share = cb.value * eff.sharePct
-		
+
 		-- deal the redirected damage as physical because I don't know how to preserve the damage type in a callback
 		if not self.__grapling_feedback_damage then
 			self.__grapling_feedback_damage = true
 			DamageType:get(DamageType.PHYSICAL).projector(self or eff.src, eff.trgt.x, eff.trgt.y, DamageType.PHYSICAL, share)
 			self.__grapling_feedback_damage = nil
 		end
-		
+
 		return cb.value - share
 	end,
 }
@@ -1502,6 +1505,7 @@ newEffect{
 	parameters = { power=0.1 },
 	on_gain = function(self, err) return "#Target# speeds up.", "+Reflexive Dodging" end,
 	on_lose = function(self, err) return "#Target# slows down.", "-Reflexive Dodging" end,
+	lists = 'break_with_step_up',
 	activate = function(self, eff)
 		eff.tmpid = self:addTemporaryValue("global_speed_add", eff.power)
 	end,
@@ -2653,6 +2657,7 @@ newEffect {
 	},
 	status = "beneficial",
 	on_lose = function(self, eff) return "#Target# loses speed.", "-Directed Speed" end,
+	lists = 'break_with_step_up',
 	callbackOnMove = function(self, eff, moved, force, ox, oy)
 		local angle_start = normalize_direction(math.atan2(self.y - eff.start_y, self.x - eff.start_x))
 		local angle_last = normalize_direction(math.atan2(self.y - eff.last_y, self.x - eff.last_x))
-- 
GitLab