From 9bd0ac2573a641d4be8061788168a31be36b5472 Mon Sep 17 00:00:00 2001
From: Hachem_Muche <Hachem_Muche@stanfordalumni.org>
Date: Tue, 22 Jul 2014 09:40:43 -0700
Subject: [PATCH] Added attrPowers function. develop
 resolvers.calc.talented_ai_tactic

---
 game/modules/tome/class/GameState.lua | 57 +++++++++++++++------------
 game/modules/tome/resolvers.lua       | 33 ++++++++++------
 2 files changed, 53 insertions(+), 37 deletions(-)

diff --git a/game/modules/tome/class/GameState.lua b/game/modules/tome/class/GameState.lua
index d65b1e1fe9..84eb8fd5a7 100644
--- a/game/modules/tome/class/GameState.lua
+++ b/game/modules/tome/class/GameState.lua
@@ -258,21 +258,34 @@ _M.power_themes = {
 --- defined power sources, used for equipment generation, defined in class descriptors
 _M.power_sources = table.map(function(k, v) return k, true end, table.keys_to_values({'technique','technique_ranged','nature','arcane','psionic','antimagic'}))
 
+--- map attributes to power restrictions for an entity
+--	returns an updated list of forbidden power types including attributes
+function _M:attrPowers(e, not_ps)
+	not_ps = table.clone(not_ps or e.not_power_source or e.forbid_power_source) or {}
+	if e.attr then
+		if e:attr("has_arcane_knowledge") then not_ps.antimagic = true end
+		if e:attr("undead") then not_ps.nature = true not_ps.antimagic = true end
+		if e:attr("forbid_arcane") then not_ps.arcane = true end
+		if e:attr("forbid_nature") then not_ps.nature = true end
+	end
+	return not_ps
+end
+
 --- Checks power_source compatibility between two entities
 --	returns true if e2 is compatible with e1, false otherwise
 --	by default, only checks .power_source vs. .forbid_power_source between entities
 --	if require_power is true, it will also check that e2.power_source (if present) has a match in e1.power_source
---	use update_power_source to resolve conflicts.
-function _M:check_power_source(e1, e2, require_power)
+--	use updatePowers to resolve conflicts.
+function _M:checkPowers(e1, e2, require_power)
 	if not e1 or not e2 then return true end
 	local ok = true
 --print("Comparing power sources",e1.name, e2.name)
 	-- check for excluded power sources first
-	local not_ps = e2.not_power_source or e2.forbid_power_source or {}
+	local not_ps = self:attrPowers(e2)
 	for ps, _ in pairs(e1.power_source or {}) do
 		if not_ps[ps] then return false end
 	end
-	not_ps = e1.not_power_source or e1.forbid_power_source or {}
+	not_ps = self:attrPowers(e1)
 	for ps, _ in pairs(e2.power_source or {}) do
 		if not_ps[ps] then return false end
 	end
@@ -295,7 +308,7 @@ end
 -- themes included can add to forbid_ps and allow_ps
 -- precedence is: forbid_ps > allow_ps > force_themes
 -- returns forbid_ps, allow_ps, themes (made consistent)
-function _M:update_power_source(forbid_ps, allow_ps, randthemes, force_themes)
+function _M:updatePowers(forbid_ps, allow_ps, randthemes, force_themes)
 	local spec_powers = allow_ps and next(allow_ps)
 	local yes_ps = spec_powers and table.clone(allow_ps) or table.clone(self.power_sources)
 	local not_ps = forbid_ps and table.clone(forbid_ps) or {}
@@ -403,7 +416,7 @@ function _M:generateRandart(data)
 	local o = base:cloneFull()
 
 o.baseobj = base:cloneFull() -- debugging code
-o.gendata = table.clone(data, true)
+o.gendata = table.clone(data, true) -- debugging code
 
 	-- Load possible random powers
 	local powers_list = engine.Object:loadList(o.randart_able, nil, nil,
@@ -430,12 +443,12 @@ o.gendata = table.clone(data, true)
 		psource = table.clone(o.power_source)
 		if data.power_source then table.merge(psource, data.power_source) end
 		-- forbid power sources that conflict with existing power source
-		data.forbid_power_source, psource = self:update_power_source(data.forbid_power_source, psource)
+		data.forbid_power_source, psource = self:updatePowers(data.forbid_power_source, psource)
 		if data.power_source then data.power_source = psource end
 	end
 	-- resolve any power/theme conflicts with input data
 	local themes
-	data.forbid_power_source, psource, themes = self:update_power_source(data.forbid_power_source, data.power_source, nb_themes, data.force_themes)
+	data.forbid_power_source, psource, themes = self:updatePowers(data.forbid_power_source, data.power_source, nb_themes, data.force_themes)
 	if data.power_source then data.power_source = psource end
 	
 	themes = table.map(function(k, v) return k, true end, table.keys_to_values(themes))
@@ -564,7 +577,7 @@ o.gendata = table.clone(data, true)
 				if ignore_filter then return true end
 				if not ef.special or ef.special(e) then
 					if gr_ego and not e.greater_ego then return false end
-					return game.state:check_power_source(ef, e, true) -- check power_source compatibility
+					return game.state:checkPowers(ef, e, true) -- check power_source compatibility
 				end
 			end
 
@@ -746,7 +759,6 @@ o.gendata = table.clone(data, true)
 		end
 		o.combat.damtype = pickDamtype(themes)
 	end
---	if o and o.combat and not (o.subtype and o.subtype == "staff") and not (o.subtype and o.subtype == "mindstar") then o.combat.damtype = pickDamtype(themes) end
 
 	if data.post then
 		data.post(o)
@@ -760,9 +772,8 @@ o.gendata = table.clone(data, true)
 end
 
 --- Adds randart properties (egos and random powers) to an existing object
--- o is the object to be updated
+-- o is the object to be updated (o.egos and o.randart_able should be defined as needed)
 -- data is the table of randart parameters passed to generateRandart
--- o.egos and o.randart_able should be defined as needed
 -- usable powers and set properties are not overwritten if present
 function _M:addRandartProperties(o, data)
 	print(" ** adding randart properties to ", o.name, o.uid)
@@ -1909,12 +1920,11 @@ end
 --	data.no_class_restrictions set true to skip class compatibility checks <nil>
 --	data.add_trees = {["talent tree name 1"]=true, ["talent tree name 2"]=true, ..} additional talent trees to learn
 --	data.check_talents_level set true to enforce talent level restrictions <nil>
---	data.auto_sustain  set true to activate sustained talents at birth <nil>
+--	data.auto_sustain set true to activate sustained talents at birth <nil>
 --	data.forbid_equip set true for no equipment <nil>
 --	data.loot_quality = drop table to use <"boss">
 --	data.drop_equipment set true to force dropping of equipment <nil>
 --	instant set true to force instant learning of talents and generating golem <nil>
---	returns true if the class was successfully added
 function _M:applyRandomClass(b, data, instant)
 	if not data.level then data.level = b.level end
 
@@ -1944,13 +1954,8 @@ function _M:applyRandomClass(b, data, instant)
 		-- b.forbid_power_source --> b.not_power_source used for classes
 		b.power_source = table.merge(b.power_source or {}, class.power_source or {})
 		b.not_power_source = table.merge(b.not_power_source or {}, class.not_power_source or {})
-		if b:attr("has_arcane_knowledge") or b:attr("undead") then b.not_power_source.antimagic = true end
-		if b:attr("undead") then b.not_power_source.nature = true end
-		if b:attr("forbid_arcane") then b.not_power_source.arcane = true end
-		if b:attr("forbid_nature") then b.not_power_source.nature = true end
-
 		-- update power source parameters with the new class
-		b.not_power_source, b.power_source = self:update_power_source(b.not_power_source, b.power_source)
+		b.not_power_source, b.power_source = self:updatePowers(self:attrPowers(b, b.not_power_source), b.power_source)
 print("   power types: not_power_source =", table.concat(table.keys(b.not_power_source),","), "power_source =", table.concat(table.keys(b.power_source),","))
 
 		-- Add stats
@@ -2019,7 +2024,6 @@ print("   power types: not_power_source =", table.concat(table.keys(b.not_power_
 						ok = false
 					end
 				end
-
 				if ok then list[t.id] = true end
 			end
 		end
@@ -2060,7 +2064,7 @@ print("   power types: not_power_source =", table.concat(table.keys(b.not_power_
 	while to_apply > 0 do
 		local c = rng.tableRemove(list)
 		if not c then break end --repeat attempts until list is exhausted
-		if data.no_class_restrictions or self:check_power_source(b, c) then  -- recheck power restricts here to account for any previously picked classes
+		if data.no_class_restrictions or self:checkPowers(b, c) then  -- recheck power restricts here to account for any previously picked classes
 			if apply_class(table.clone(c, true)) then to_apply = to_apply - 1 end
 		else
 			print("  class", c.name, " rejected due to power source")
@@ -2080,7 +2084,8 @@ end
 --	data.life_rating <1.7 * base.life_rating + 4-9>
 --	data.resources_boost = multiplier for maximum resource pool sizes <3>
 --	data.talent_cds_factor = multiplier for all talent cooldowns <1>
---	data.ai <"tactical" if rank>3 or base.ai>
+--	data.ai = ai_type <"tactical" if rank>3 or base.ai>
+--	data.ai_tactic = tactical weights table for the tactical ai <nil>
 --	data.no_loot_randart set true to not drop a randart <nil>
 --	data.on_die set true to run base.rng_boss_on_die and base.rng_boss_on_die_custom on death <nil>
 --	data.name_scheme <randart_name_rules.default>
@@ -2107,6 +2112,7 @@ function _M:createRandomBoss(base, data)
 		b.name = name.." the "..b.name
 	end
 	print("Creating random boss ", b.name, data.level, "level", data.nb_classes, "classes")
+	if data.force_classes then print("  * forcing classes:",table.concat(table.keys(data.force_classes),",")) end
 	b.unique = b.name
 	b.randboss = true
 	local boss_id = "RND_BOSS_"..b.name:upper():gsub("[^A-Z]", "_")
@@ -2151,7 +2157,6 @@ function _M:createRandomBoss(base, data)
 	else b.ai = (b.rank > 3) and "tactical" or b.ai
 	end
 	b.ai_state = { talent_in=1, ai_move=data.ai_move or "move_astar" }
-	b.ai_tactic = resolvers.talented_ai_tactic()
 
 	-- Remove default equipment, if any
 	local todel = {}
@@ -2213,8 +2218,8 @@ function _M:createRandomBoss(base, data)
 		self:resetToFull()
 	end
 
---	resolve ai tactical weights here?
---	b.ai_tactic = resolvers.talented_ai_tactic()
+--	b.ai_tactic = data.ai_tactic or resolvers.talented_ai_tactic() --Update tactics based on talents
+--	b:resolve(nil, true)
 	-- Anything else
 	if data.post then data.post(b, data) end
 
diff --git a/game/modules/tome/resolvers.lua b/game/modules/tome/resolvers.lua
index ae06a65df4..57e16f039f 100644
--- a/game/modules/tome/resolvers.lua
+++ b/game/modules/tome/resolvers.lua
@@ -51,9 +51,10 @@ function resolvers.calc.equip(t, e)
 					filter.random_art_replace.chance = 100
 				end
 			end
-			if o and o.power_source and (o.power_source.antimagic and e:attr("has_arcane_knowledge") or o.power_source.arcane and e:attr("forbid_arcane")) then
+--			if o and o.power_source and (o.power_source.antimagic and e:attr("has_arcane_knowledge") or o.power_source.arcane and e:attr("forbid_arcane")) then
+			if o and not filter.no_power_restrictions and not game.state:checkPowers(e, o) then -- Check power restrictions
 				ok = false
-				print("  Equipment resolver for ", e.name ," -- incompatible equipment ", o.name, "retrying", tries, "forbid ps:", filter.forbid_power_source and table.concat(table.keys(filter.forbid_power_source, ",")))
+				print("  Equipment resolver for ", e.name ," -- incompatible equipment ", o.name, "retrying", tries, "forbid ps:", filter.forbid_power_source and table.concat(table.keys(filter.forbid_power_source, ",")), "vs ps", o.power_source and table.concat(table.keys(o.power_source, ",")))
 			end
 		until ok or tries > 4
 		if o then
@@ -635,19 +636,21 @@ end
 function resolvers.calc.talented_ai_tactic(t, e)
 	local tactic_total = t[2] or t.tactic_total or 10 --want tactic weights to total 10
 	local weight_power = t[3] or t.weight_power or 0.5 --smooth out tactical weights
-	local tacs_offense = {attack=1, attackarea=1, disable=0.5}
+	local tacs_offense = {attack=1, attackarea=1}
 	local tacs_close = {closein=1, go_melee=1}
-	local tacs_defense = {escape=1, defend=1, heal=1, protect=1, disable = 0.5}
+	local tacs_defense = {escape=1, defend=1, heal=1, protect=1, disable = 1}
 	local tac_types = {type="melee",type = "ranged", type="tank", type="survivor"}
 	local tactic, tactical = {}, {total = 0} 
-	local count = {talents = 0, atk_count = 0, atk_value = 0, atk_melee = 0, atk_range = 0, total_range = 0,
-		close_count = 0, def_count = 0, def_value = 0, disable=0}
+	local count = {talents = 0, atk_count = 0, atk_value = 0, total_range = 0,
+		atk_melee = 0, melee_value = 0, range_value = 0, atk_range = 0, 
+		escape = 0, close = 0, def_count = 0, def_value = 0, disable=0}
 	local do_count, val
 	local tac_count = #table.keys(tacs_offense) + #table.keys(tacs_close) + #table.keys(tacs_defense)
 	-- go through all talents, adding up all the tactical weights from the tactical tables weighted by talent level
 	for tid, tl in pairs(e.talents) do
 		local tal = e:getTalentFromId(tid)
-		local range = e:getTalentRange(tal) + e:getTalentRadius(tal)*2/3
+		local range = e:getTalentRange(tal)
+		if range > 0 then range = range + e:getTalentRadius(tal)*2/3 end
 		if tal and tal.tactical then
 			do_count = false
 			for tt, wt in pairs(tal.tactical) do
@@ -655,29 +658,34 @@ function resolvers.calc.talented_ai_tactic(t, e)
 				if type(wt) == "number" then val = wt
 				elseif type(wt) == "table" then
 					for _, n in pairs(wt) do
-						if type(n) == "number" then val = val + n end
+						if type(n) == "number" then val = math.max(val, n) end
 					end
+					if val == 0 then val = 2 end
 				end
 				tactical[tt] = (tactical[tt] or 0) + val -- sum up all the input weights
 				if tacs_offense[tt] then
 					do_count = true
 					count.atk_count = count.atk_count + 1
-					count.atk_value = count.atk_value + tacs_offense[tt] * val
+					val = val * tacs_offense[tt]
+					count.atk_value = count.atk_value + val
 					count.total_range = count.total_range + range
 					if range >= 2 then
 						count.atk_range = count.atk_range + 1
+						count.range_value = count.range_value + val
 					else
 						count.atk_melee = count.atk_melee + 1
+						count.melee_value = count.melee_value + val
 					end
 				end
 				if tacs_defense[tt] then
 					do_count = true
 					count.def_count = count.def_count + 1
 					count.def_value = count.def_value + tacs_defense[tt] * val
+					if tt == "escape" then count.escape = count.escape + 1 end
 				end
 				if tacs_close[tt] then
 					do_count = true
-					count.close_count = count.close_count + 1
+					count.close = count.close + 1
 				end
 				if do_count then
 					count.talents = count.talents + 1
@@ -711,7 +719,8 @@ function resolvers.calc.talented_ai_tactic(t, e)
 	end
 	
 	-- Minimum range?
-	if count.atk_range > count.atk_melee and (count.atk_range - count.close_count)/(count.atk_melee + 1) > 2 then
+--	if count.atk_range > count.atk_melee and (count.atk_range + count.escape - count.close)/(count.atk_melee + 1) > 2 then
+	if count.atk_range + count.escape > count.atk_melee + count.close and count.range_value /(count.melee_value + 1) > 1.5 then
 		tactic.safe_range = math.max(2, math.ceil(count.avg_attack_range/2)) --only for ranged/survivor
 	end
 	
@@ -719,6 +728,8 @@ function resolvers.calc.talented_ai_tactic(t, e)
 	tactic.tactical_sum=tactical tactic.count = count
 	tactic.type = ttype
 	tactic.tac_count = tac_count
+	print("talented_ai_tactic resolver for", e.name, "level=", e.level)
+	for tac, wt in pairs(tactic) do print("  ##", tac, wt) end
 	return tactic
 end
 
-- 
GitLab