Skip to content
Snippets Groups Projects
Commit 9bd0ac25 authored by Hachem_Muche's avatar Hachem_Muche
Browse files

Added attrPowers function.

develop resolvers.calc.talented_ai_tactic
parent b4b1a6dc
No related branches found
No related tags found
No related merge requests found
......@@ -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
......
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment