Skip to content
Snippets Groups Projects
Commit f527533a authored by DarkGod's avatar DarkGod
Browse files

Object properties special_on_hit special_on_crit and special_on_kill can now be merged by egos

parent 6101c778
No related branches found
No related tags found
No related merge requests found
......@@ -30,6 +30,18 @@ module(..., package.seeall, class.make)
_no_save_fields = {temp_memory_levels=true, _tmp_data=true}
--- List of rules to run through when applying an ego to an entity.
_M.ego_rules = {}
--- Adds an ego rule.
-- Static method
function _M:addEgoRule(kind, rule)
self.ego_rules[kind] = self.ego_rules[kind] or {}
table.insert(self.ego_rules[kind], rule)
end
--- Setup classes to use for level generation
-- Static method
-- @param t table that contains the name of the classes to use
......@@ -361,7 +373,7 @@ function _M:makeEntity(level, type, filter, force_level, prob_filter)
-- Generate a specific probability list, slower to generate but no need to "try and be lucky"
elseif filter then
local base_list = nil
if filter.base_list then
if filter.base_list then
if _G.type(filter.base_list) == "table" then base_list = filter.base_list
else
local _, _, class, file = filter.base_list:find("(.*):(.*)")
......@@ -433,7 +445,7 @@ local pick_ego = function(self, level, e, eegos, egos_list, type, picked_etype,
if _G.type(etype) == "number" then etype = "" end
local egos = e.egos and level:getEntitiesList(type.."/"..e.egos..":"..etype)
if not egos then egos = self:generateEgoEntities(level, type, etype, eegos, e.__CLASSNAME) end
if self.ego_filter then ego_filter = self.ego_filter(self, level, type, etype, e, ego_filter, egos_list, picked_etype) end
......@@ -475,8 +487,8 @@ function _M:finishEntity(level, type, e, ego_filter)
ego.instant_resolve = nil
-- Void the uid, we dont want to erase the base entity's one
ego.uid = nil
-- Merge additively but with array appending, so that nameless resolvers are not lost
table.mergeAddAppendArray(e, ego, true)
-- Merge according to Object's ego rules.
table.ruleMergeAppendAdd(e, ego, self.ego_rules[type] or {})
e.name = newname
e.egoed = true
end
......@@ -563,8 +575,8 @@ function _M:finishEntity(level, type, e, ego_filter)
ego.instant_resolve = nil
-- Void the uid, we dont want to erase the base entity's one
ego.uid = nil
-- Merge additively but with array appending, so that nameless resolvers are not lost
table.mergeAddAppendArray(e, ego, true)
-- Merge according to Object's ego rules.
table.ruleMergeAppendAdd(e, ego, self.ego_rules[type] or {})
e.name = newname
e.egoed = true
end
......
......@@ -286,6 +286,83 @@ function table.readonly(src)
});
end
-- Make a new table with each k, v = f(k, v) in the original.
function table.map(f, source)
local result = {}
for k, v in pairs(source) do
k2, v2 = f(k, v)
result[k2] = v2
end
return result
end
-- Make a new table with each k, v = k, f(v) in the original.
function table.mapv(f, source)
local result = {}
for k, v in pairs(source) do
result[k] = f(v)
end
return result
end
-- Find the keys that are only in left, only in right, and are common
-- to both.
function table.compareKeys(left, right)
local result = {left = {}, right = {}, both = {}}
for k, _ in pairs(left) do
if right[k] then
result.both[k] = true
else
result.left[k] = true
end
end
for k, _ in pairs(right) do
if not left[k] then
result.right[k] = true
end
end
return result
end
--[=[
Decends recursively through a table by the given list of keys.
1st return: The first non-table value found, or the final value if
we ran out of keys.
2nd return: If the list of keys was exhausted
Meant to replace multiple ands to get a value:
"a and a.b and a.b.c" turns to "rget(a, 'b', 'c')"
]=]
function table.get(table, ...)
if type(table) ~= 'table' then return table, false end
for _, key in ipairs({...}) do
if type(table) ~= 'table' then return table, false end
table = table[key]
end
return table, true
end
--[=[
Set the nested value in a table, creating empty tables as needed.
]=]
function table.set(table, ...)
if type(table) ~= 'table' then return false end
local args = {...}
for i = 1, #args - 2 do
local key = args[i]
local subtable = table[key]
if not subtable then
subtable = {}
table[key] = subtable
end
table = subtable
end
table[args[#args - 1]] = args[#args]
end
-- Taken from http://lua-users.org/wiki/SortedIteration and modified
local function cmp_multitype(op1, op2)
local type1, type2 = type(op1), type(op2)
......@@ -318,6 +395,22 @@ function table.orderedPairs(t)
end
end
-- ordering is a function({k1, v1}, {k2, v2}), it should return true
-- when left is < right.
function table.orderedPairs(t, ordering)
if not next(t) then return function() end end
t = table.listify(t)
if #t > 1 then table.sort(t, ordering) end
local index = 1
return function()
if index <= #t then
value = t[index]
index = index + 1
return value[1], value[2]
end
end
end
--- Shuffles the content of a table (list)
function table.shuffle(t)
local n = #t
......@@ -328,6 +421,89 @@ function table.shuffle(t)
return t
end
-- Common table rules.
table.rules = {}
--[[
Applies a series of rules to a pair of tables. rules should be a list of functions
to be applied, in order, until one returns true.
All keys in the table src are looped through, starting with array
indices, and then the rest of the keys. The rules are given the
following arguments:
The dst table's value for the current key.
The src table's value for the current key.
The current key.
The dst table.
The src table.
The list of rules.
A state table which the rules are free to modify.
--]]
function table.applyRules(dst, src, rules, state)
if not dst or not src then return end
state = state or {}
local used_keys = {}
-- First loop through with ipairs so we get the numbers in order.
for k, v in ipairs(src) do
used_keys[k] = true
for _, rule in ipairs(rules) do
if rule(dst[k], src[k], k, dst, src, rules, state) then
break
end
end
end
-- Then loop through with pairs, skipping the ones we got with ipairs.
for k, v in pairs(src) do
if not used_keys[k] then
for _, rule in ipairs(rules) do
if rule(dst[k], src[k], k, dst, src, rules, state) then
break
end
end
end
end
end
-- Simply overwrites the value.
table.rules.overwrite = function(dvalue, svalue, key, dst)
dst[key] = svalue
return true
end
-- Does the recursion.
table.rules.recurse = function(dvalue, svalue, key, dst, src, rules, state)
if type(dvalue) ~= 'table' or type(svalue) ~= 'table' then return end
state = table.clone(state)
state.path = table.clone(state.path) or {}
table.insert(state.path, key)
table.applyRules(dvalue, svalue, rules, state)
return true
end
-- Appends indices.
table.rules.append = function(dvalue, svalue, key, dst, src, rules, state)
if type(key) ~= 'number' then return end
table.insert(dst, svalue)
return true
end
-- Adds numbers
table.rules.add = function(dvalue, svalue, key, dst)
if type(dvalue) ~= 'number' or type(svalue) ~= 'number' then return end
dst[key] = dvalue + svalue
return true
end
--[[
A convenience method for merging tables, appending numeric indices,
and adding number values in addition to other rules.
--]]
function table.ruleMergeAppendAdd(dst, src, rules)
rules = table.clone(rules)
for _, rule in pairs {'append', 'recurse', 'add', 'overwrite'} do
table.insert(rules, table.rules[rule])
end
table.applyRules(dst, src, rules)
end
function string.ordinal(number)
local suffix = "th"
number = tonumber(number)
......@@ -1921,7 +2097,7 @@ function util.showMainMenu(no_reboot, reboot_engine, reboot_engine_version, rebo
core.steam.cancelGrabSubscribedAddons()
core.steam.sessionTicketCancel()
end
-- Tell the C engine to discard the current lua state and make a new one
print("[MAIN] rebooting lua state: ", reboot_engine, reboot_engine_version, reboot_module, reboot_name, reboot_new)
core.game.reboot("te4core", -1, reboot_engine or "te4", reboot_engine_version or "LATEST", reboot_module or "boot", reboot_name or "player", reboot_new, reboot_einfo or "")
......@@ -2041,4 +2217,3 @@ function require_first(...)
end
return nil
end
......@@ -443,7 +443,7 @@ function _M:getTextualDesc(compare_with, use_actor)
end
end
local compare_table_fields = function(item1, items, infield, field, outformat, text, kfunct, mod, isinversed)
local compare_table_fields = function(item1, items, infield, field, outformat, text, kfunct, mod, isinversed, filter)
mod = mod or 1
isinversed = isinversed or false
local ret = tstring{}
......@@ -467,6 +467,7 @@ function _M:getTextualDesc(compare_with, use_actor)
end
local count1 = 0
for k, v in pairs(tab) do
if filter and not filter(k, v) then goto filtered end
local count = 0
if isinversed then
ret:add(("%s"):format((count1 > 0) and " / " or ""), (v[1] or 0) > 0 and {"color","RED"} or {"color","LIGHT_GREEN"}, outformat:format((v[1] or 0)), {"color","LAST"})
......@@ -501,6 +502,7 @@ function _M:getTextualDesc(compare_with, use_actor)
ret:add(")")
end
ret:add(kfunct(k))
::filtered::
end
if add then
......@@ -556,7 +558,7 @@ function _M:getTextualDesc(compare_with, use_actor)
if combat.wil_attack then
desc:add("Accuracy is based on willpower for this weapon.", true)
end
if combat.is_psionic_focus then
desc:add("This weapon will act as a psionic focus.", true)
end
......@@ -592,7 +594,7 @@ function _M:getTextualDesc(compare_with, use_actor)
for tid, data in pairs(talents) do
desc:add(talents[tid][3] and {"color","WHITE"} or {"color","GREEN"}, ("When this weapon hits: %s (%d%% chance level %d)."):format(self:getTalentFromId(tid).name, talents[tid][1], talents[tid][2]), {"color","LAST"}, true)
end
local talents = {}
if combat.talent_on_crit then
for tid, data in pairs(combat.talent_on_crit) do
......@@ -630,82 +632,125 @@ function _M:getTextualDesc(compare_with, use_actor)
end
--]]
-- Store the DamageTypes with tdesc defined in dt_string
-- Store the DamageTypes without tdesc in combat2 (to be displayed normally)
local found = false
local dt_string = tstring{}
local combat2 = { melee_project = {} }
for i, v in pairs(combat.melee_project or {}) do
local def = DamageType.dam_def[i]
if def and def.tdesc then
local d = def.tdesc(v)
found = true
dt_string:add(d, {"color","LAST"}, true)
else
combat2.melee_project[i] = v
-- get_items takes the combat table and returns a table of items to print.
-- Each of these items one of the following:
-- id -> {priority, string}
-- id -> {priority, message_function(this, compared), value}
-- header is the section header.
local compare_list = function(header, get_items)
local priority_ordering = function(left, right)
return left[2][1] < right[2][1]
end
end
if next(compare_with) then
-- Grab the left and right items.
local left = get_items(combat)
local right = {}
for i, v in ipairs(compare_with) do
for k, item in pairs(get_items(v[field])) do
if not right[k] then
right[k] = item
elseif type(right[k]) == 'number' then
right[k] = right[k] + item
else
right[k] = item
end
end
end
local ranged_string = tstring{}
local ranged_combat = { ranged_project = {} }
for i, v in pairs(combat.ranged_project or {}) do
local def = DamageType.dam_def[i]
if def and def.tdesc then
local d = def.tdesc(v)
found = true
ranged_string:add(d, {"color","LAST"}, true)
-- Exit early if no items.
if not next(left) and not next(right) then return end
desc:add(header, true)
local combined = table.clone(left)
table.merge(combined, right)
for k, _ in table.orderedPairs(combined, priority_ordering) do
l = left[k]
r = right[k]
message = (l and l[2]) or (r and r[2])
if type(message) == 'function' then
desc:add(message(l and l[3], r and r[3] or 0), true)
elseif type(message) == 'string' then
prefix = '* '
color = 'WHITE'
if l and not r then
color = 'GREEN'
prefix = '+ '
end
if not l and r then
color = 'RED'
prefix = '- '
end
desc:add({'color',color}, prefix, message, {'color','LAST'}, true)
end
end
else
ranged_combat.ranged_project[i] = v
local items = get_items(combat)
if next(items) then
desc:add(header, true)
for k, v in table.orderedPairs(items, priority_ordering) do
message = v[2]
if type(message) == 'function' then
desc:add(message(v[3]), true)
elseif type(message) == 'string' then
desc:add({'color','WHITE'}, '* ', message, {'color','LAST'}, true)
end
end
end
end
end
-- Add the on hit section only if theres a tdesc DT or a special_on_hit
if found or special ~= "" then
desc:add({"color","ORANGE"}, "When this weapon hits: ", {"color","LAST"}, true)
local get_special_list = function(combat, key)
local special = combat[key]
end
-- No special
if not special then return {} end
-- Add the special_on_hit desc first always in green
if special ~= "" then
desc:add({"color","GREEN"}, special:capitalize(), {"color","LAST"}, true)
end
-- Add the extended melee_project descriptions after special_on_hit, colors defined by tdesc
if found then
desc:merge(dt_string)
desc:merge(ranged_string)
end
-- Single special
if special.desc then
return {[special.desc] = {10, special.desc}}
end
-- only special_on_hit display is modified, not special_on_crit and so on
special = ""
if combat.special_on_crit then
special = combat.special_on_crit.desc
end
found = false
for i, v in ipairs(compare_with or {}) do
if v[field] and v[field].special_on_crit then
if special ~= v[field].special_on_crit.desc then
desc:add({"color","RED"}, "When this weapon crits: "..v[field].special_on_crit.desc, {"color","LAST"}, true)
else
found = true
-- Multiple specials
local list = {}
for _, special in pairs(special) do
list[special.desc] = {10, special.desc}
end
return list
end
compare_list(
"On weapon hit:",
function(combat)
local list = {}
-- Get complex damage types
for dt, amount in pairs(combat.melee_project or combat.ranged_project or {}) do
local dt_def = DamageType:get(dt)
if dt_def and dt_def.tdesc then
list[dt] = {0, dt_def.tdesc, amount}
end
end
-- Get specials
table.merge(list, get_special_list(combat, 'special_on_hit'))
return list
end
end
if special ~= "" then
desc:add(found and {"color","WHITE"} or {"color","ORANGE"}, "When this weapon crits: "..special, {"color","LAST"}, true)
end
)
local special = ""
if combat.special_on_kill then
special = combat.special_on_kill.desc
end
compare_list(
"On weapon crit:",
function(combat)
return get_special_list(combat, 'special_on_crit')
end
)
if special ~= "" then
desc:add(found and {"color","WHITE"} or {"color","ORANGE"}, "When this weapon kills: "..special, {"color","LAST"}, true)
end
compare_list(
"On weapon kill:",
function(combat)
return get_special_list(combat, 'special_on_kill')
end
)
found = false
for i, v in ipairs(compare_with or {}) do
......@@ -719,7 +764,7 @@ function _M:getTextualDesc(compare_with, use_actor)
elseif found then
desc:add({"color","RED"}, "When used from stealth a simple attack with it will not break stealth.", {"color","LAST"}, true)
end
if combat.crushing_blow then
desc:add({"color", "YELLOW"}, "Crushing Blows: ", {"color", "LAST"}, "Damage dealt by this weapon is increased by half your critical multiplier, if doing so would kill the target.", true)
end
......@@ -727,23 +772,30 @@ function _M:getTextualDesc(compare_with, use_actor)
compare_fields(combat, compare_with, field, "travel_speed", "%+d%%", "Travel speed: ", 100, false, false, add_table)
compare_fields(combat, compare_with, field, "phasing", "%+d%%", "Damage Shield penetration (this weapon only): ", 1, false, false, add_table)
compare_fields(combat, compare_with, field, "lifesteal", "%+d%%", "Lifesteal (this weapon only): ", 1, false, false, add_table)
if combat.tg_type and combat.tg_type == "beam" then
desc:add({"color","YELLOW"}, ("Shots beam through all targets."), {"color","LAST"}, true)
end
-- Use the second combat table for melee_project so we don't repeat the tdesc entries
compare_table_fields(combat2, compare_with, field, "melee_project", "%+d", "Damage (Melee): ", function(item)
compare_table_fields(
combat, compare_with, field, "melee_project", "%+d", "Damage (Melee): ",
function(item)
local col = (DamageType.dam_def[item] and DamageType.dam_def[item].text_color or "#WHITE#"):toTString()
return col[2], (" %s"):format(DamageType.dam_def[item].name),{"color","LAST"}
end)
end,
nil, nil,
function(k, v) return not DamageType.dam_def[k].tdesc end)
compare_table_fields(ranged_combat, compare_with, field, "ranged_project", "%+d", "Damage (Ranged): ", function(item)
compare_table_fields(
combat, compare_with, field, "ranged_project", "%+d", "Damage (Ranged): ",
function(item)
local col = (DamageType.dam_def[item] and DamageType.dam_def[item].text_color or "#WHITE#"):toTString()
return col[2], (" %s"):format(DamageType.dam_def[item].name),{"color","LAST"}
end)
end,
nil, nil,
function(k, v) return not DamageType.dam_def[k].tdesc end)
compare_table_fields(combat, compare_with, field, "burst_on_hit", "%+d", "Burst (radius 1) on hit: ", function(item)
local col = (DamageType.dam_def[item] and DamageType.dam_def[item].text_color or "#WHITE#"):toTString()
......@@ -1181,7 +1233,7 @@ function _M:getTextualDesc(compare_with, use_actor)
compare_fields(w, compare_with, field, "infravision", "%+d", "Infravision radius: ")
compare_fields(w, compare_with, field, "heightened_senses", "%+d", "Heightened senses radius: ")
compare_fields(w, compare_with, field, "sight", "%+d", "Sight radius: ")
compare_fields(w, compare_with, field, "see_stealth", "%+d", "See stealth: ")
compare_fields(w, compare_with, field, "see_invisible", "%+d", "See invisible: ")
......@@ -1218,10 +1270,10 @@ function _M:getTextualDesc(compare_with, use_actor)
compare_fields(w, compare_with, field, "nature_summon_max", "%+d", "Max wilder summons: ")
compare_fields(w, compare_with, field, "nature_summon_regen", "%+.2f", "Life regen bonus (wilder-summons): ")
compare_fields(w, compare_with, field, "shield_dur", "%+d", "Damage Shield Duration: ")
compare_fields(w, compare_with, field, "shield_factor", "%+d%%", "Damage Shield Power: ")
compare_fields(w, compare_with, field, "iceblock_pierce", "%+d%%", "Ice block penetration: ")
compare_fields(w, compare_with, field, "slow_projectiles", "%+d%%", "Slows Projectiles: ")
......@@ -1229,13 +1281,13 @@ function _M:getTextualDesc(compare_with, use_actor)
compare_fields(w, compare_with, field, "paradox_reduce_fails", "%+d", "Reduces paradox failures(equivalent to willpower): ")
compare_fields(w, compare_with, field, "damage_backfire", "%+d%%", "Damage Backlash: ", nil, true)
compare_fields(w, compare_with, field, "resist_unseen", "%-d%%", "Reduce all damage from unseen attackers: ")
if w.undead then
desc:add("The wearer is treated as an undead.", true)
end
if w.demon then
desc:add("The wearer is treated as a demon.", true)
end
......@@ -1243,7 +1295,7 @@ function _M:getTextualDesc(compare_with, use_actor)
if w.blind then
desc:add("The wearer is blinded.", true)
end
if w.sleep then
desc:add("The wearer is asleep.", true)
end
......@@ -1251,7 +1303,7 @@ function _M:getTextualDesc(compare_with, use_actor)
if w.blind_fight then
desc:add({"color", "YELLOW"}, "Blind-Fight: ", {"color", "LAST"}, "This item allows the wearer to attack unseen targets without any penalties.", true)
end
if w.lucid_dreamer then
desc:add({"color", "YELLOW"}, "Lucid Dreamer: ", {"color", "LAST"}, "This item allows the wearer to act while sleeping.", true)
end
......@@ -1259,7 +1311,7 @@ function _M:getTextualDesc(compare_with, use_actor)
if w.no_breath then
desc:add("The wearer no longer has to breathe.", true)
end
if w.quick_weapon_swap then
desc:add({"color", "YELLOW"}, "Quick Weapon Swap:", {"color", "LAST"}, "This item allows the wearer to swap to their secondary weapon without spending a turn.", true)
end
......
......@@ -25,6 +25,24 @@ module(..., package.seeall, class.inherit(Zone))
_M:enableLastPersistZones(3)
-- Merge special_on_crit values.
_M:addEgoRule("object", function(dvalue, svalue, key, dst, src, rules, state)
-- Only work on the special_on_* keys.
if key ~= 'special_on_hit' and key ~= 'special_on_crit' and key ~= 'special_on_kill' then return end
-- If the special isn't a table, make it an empty one.
if type(dvalue) ~= 'table' then dvalue = {} end
if type(svalue) ~= 'table' then svalue = {} end
-- If the special is a single special, wrap it to allow multiple.
if dvalue.fct then dvalue = {dvalue} end
if svalue.fct then svalue = {svalue} end
-- Save changes to the specials.
dst[key] = dvalue
src[key] = svalue
-- Return false so we can let the normal table merge take care of
-- the rest.
return false
end)
--- Called when the zone file is loaded
function _M:onLoadZoneFile(basedir)
-- Load events if they exist
......
......@@ -342,7 +342,7 @@ end
function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
damtype = damtype or (weapon and weapon.damtype) or DamageType.PHYSICAL
mult = mult or 1
--Life Steal
if weapon and weapon.lifesteal then
self:attr("lifesteal", weapon.lifesteal)
......@@ -351,7 +351,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
local mode = "other"
if self:hasShield() then mode = "shield"
elseif self:hasTwoHandedWeapon() then mode = "twohanded"
elseif self:hasTwoHandedWeapon() then mode = "twohanded"
elseif self:hasDualWeapon() then mode = "dualwield"
end
self.turn_procs.weapon_type = {kind=weapon and weapon.talented or "unknown", mode=mode}
......@@ -426,7 +426,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
game:delayedLogDamage(self, target, 0, ("%s(%d parried#LAST#)"):format(DamageType:get(damtype).text_color or "#aaaaaa#", deflect), false)
dam = math.max(dam - deflect,0)
print("[ATTACK] after DUAL_WEAPON_DEFENSE", dam)
end
end
end
if target.knowTalent and target:hasEffect(target.EFF_GESTURE_OF_GUARDING) and not target:attr("encased_in_ice") then
local deflected = math.min(dam, target:callTalent(target.T_GESTURE_OF_GUARDING, "doGuard")) or 0
......@@ -487,10 +487,10 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
if weapon and weapon.phasing then
self:attr("damage_shield_penetrate", weapon.phasing)
end
local oldproj = DamageType:getProjectingFor(self)
if self.__talent_running then DamageType:projectingFor(self, {project_type={talent=self.__talent_running}}) end
if weapon and weapon.crushing_blow then self:attr("crushing_blow", 1) end
-- Damage conversion?
......@@ -515,7 +515,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
if dam > 0 then
DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam))
end
if weapon and weapon.crushing_blow then self:attr("crushing_blow", -1) end
if self.__talent_running then DamageType:projectingFor(self, oldproj) end
......@@ -658,7 +658,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
self:incStamina(-8)
self.shattering_impact_last_turn = game.turn
end
-- Damage Backlash
if dam > 0 and self.attr and self:attr("damage_backfire") then
local hurt = math.min(dam, target.life) * self.damage_backfire / 100
......@@ -772,13 +772,35 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
end
-- Special effect
if hitted and weapon and weapon.special_on_hit and weapon.special_on_hit.fct and (not target.dead or weapon.special_on_hit.on_kill) then
weapon.special_on_hit.fct(weapon, self, target, dam)
if hitted and weapon and weapon.special_on_hit then
local specials = weapon.special_on_hit
if specials.fct then specials = {specials} end
for _, special in ipairs(specials) do
if special.fct and (not target.dead or special.on_kill) then
special.fct(weapon, self, target, dam)
end
end
end
if hitted and crit and weapon and weapon.special_on_crit then
local specials = weapon.special_on_crit
if specials.fct then specials = {specials} end
for _, special in ipairs(specials) do
if special.fct and (not target.dead or special.on_kill) then
special.fct(weapon, self, target, dam)
end
end
end
if hitted and crit and weapon and weapon.special_on_crit and weapon.special_on_crit.fct and (not target.dead or weapon.special_on_crit.on_kill) then
weapon.special_on_crit.fct(weapon, self, target, dam)
end
if hitted and weapon and weapon.special_on_kill and target.dead then
local specials = weapon.special_on_kill
if specials.fct then specials = {specials} end
for _, special in ipairs(specials) do
if special.fct then
special.fct(weapon, self, target, dam)
end
end
end
if hitted and weapon and weapon.special_on_kill and weapon.special_on_kill.fct and target.dead then
weapon.special_on_kill.fct(weapon, self, target, dam)
......@@ -836,9 +858,9 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
if cadam then
game.logSeen(self, "%s counters the attack!", target.name:capitalize())
target:attackTarget(self, nil, cadam, true)
end
end
end
end
-- Gesture of Guarding counterattack
if hitted and not target.dead and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:hasEffect(target.EFF_GESTURE_OF_GUARDING) then
local t = target:getTalentFromId(target.T_GESTURE_OF_GUARDING)
......@@ -868,8 +890,8 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
-- Roll with it
if hitted and target:attr("knockback_on_hit") and not target.turn_procs.roll_with_it and rng.percent(util.bound(dam, 0, 100)) then
local ox, oy = self.x, self.y
game:onTickEnd(function()
target:knockback(ox, oy, 1)
game:onTickEnd(function()
target:knockback(ox, oy, 1)
if not target:hasEffect(target.EFF_WILD_SPEED) then target:setEffect(target.EFF_WILD_SPEED, 1, {power=200}) end
end)
target.turn_procs.roll_with_it = true
......@@ -959,7 +981,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
self.turn_procs.weapon_type = nil
self.__global_accuracy_damage_bonus = nil
--Life Steal
if weapon and weapon.lifesteal then
self:attr("lifesteal", -weapon.lifesteal)
......@@ -1005,7 +1027,7 @@ function _M:combatGetTraining(weapon)
if type(_M.weapon_talents[weapon.talented]) == "table" then
local ktid, max = _M.weapon_talents[weapon.talented][1], self:getTalentLevel(_M.weapon_talents[weapon.talented][1])
for i, tid in ipairs(_M.weapon_talents[weapon.talented]) do
if self:knowTalent(tid) then
if self:knowTalent(tid) then
if self:getTalentLevel(tid) > max then
ktid = tid
max = self:getTalentLevel(tid)
......@@ -1063,7 +1085,7 @@ function _M:combatDefenseBase(fake)
end
local d = math.max(0, self.combat_def + (self:getDex() - 10) * 0.35 + (self:getLck() - 50) * 0.4)
local mult = 1
if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then
mult = mult + self:callTalent(self.T_MOBILE_DEFENCE,"getDef")
end
......@@ -1310,7 +1332,7 @@ function _M:combatTalentScale(t, low, high, power, add, shift, raw)
if power == "log" then -- always >= 0
return math.max(0, m * math.log10(tl + shift) + b + add)
-- return math.max(0, m * math.log10(tl + shift) + b + add), m, b
else
else
return math.max(0, m * (tl + shift)^power + b + add)
-- return math.max(0, m * (tl + shift)^power + b + add), m, b
end
......@@ -1339,7 +1361,7 @@ function _M:combatStatScale(stat, low, high, power, add, shift)
if power == "log" then -- always >= 0
return math.max(0, m * math.log10(stat + shift) + b + add)
-- return math.max(0, m * math.log10(stat + shift) + b + add), m, b
else
else
return math.max(0, m * (stat + shift)^power + b + add)
-- return math.max(0, m * (stat + shift)^power + b + add), m, b
end
......@@ -1901,11 +1923,11 @@ function _M:combatMentalResist(fake)
local d = self.combat_mentalresist + (self:getCun() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add
if self:attr("dazed") then d = d / 2 end
local nm = self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES)
if nm and rng.percent(20) and not fake then
d = d * (1-self.tempeffect_def.EFF_CURSE_OF_NIGHTMARES.getVisionsReduction(nm, nm.level)/100)
end
end
return self:rescaleCombatStats(d)
end
......@@ -1923,8 +1945,8 @@ end
--- Returns the resistance
function _M:combatGetResist(type)
local power = 100
if self.force_use_resist and self.force_use_resist ~= type then
type = self.force_use_resist
if self.force_use_resist and self.force_use_resist ~= type then
type = self.force_use_resist
power = self.force_use_resist_percent or 100
end
......@@ -2321,5 +2343,5 @@ end
function _M:logCombat(target, style, ...)
if not game.uiset or not game.uiset.logdisplay then return end
local visible, srcSeen, tgtSeen = game:logVisible(self, target) -- should a message be displayed?
if visible then game.uiset.logdisplay(game:logMessage(self, srcSeen, target, tgtSeen, style, ...)) end
if visible then game.uiset.logdisplay(game:logMessage(self, srcSeen, target, tgtSeen, style, ...)) end
end
......@@ -47,7 +47,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
src.elemental_mastery = old
return dam
end
if src:attr("twilight_mastery") then
local ndam = dam * src.twilight_mastery
local old = src.twilight_mastery
......@@ -236,7 +236,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
if dam ~= lastdam then
game:delayedLogDamage(src, target, 0, ("%s(%d to psi shield)#LAST#"):format(DamageType:get(type).text_color or "#aaaaaa#", lastdam-dam), false)
end
--target.T_STONE_FORTRESS could be checked/applied here (ReduceDamage function in Dwarven Fortress talent)
-- Damage Smearing
......@@ -263,8 +263,8 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
if src:checkClassification(tostring(k)) then res = math.max(res, v) end
end
res = math.min(res, target.resists_cap_actor_type or 90)
res = math.min(res, target.resists_cap_actor_type or 90)
if res ~= 0 then
print("[PROJECTOR] before entity", src.type, "resists dam", dam)
if res >= 100 then dam = 0
......@@ -310,7 +310,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
dam= dam - demon_block
target:incVim((-demon_block)/20)
end
-- Static reduce damage
if dam > 0 and target.isTalentActive and target:isTalentActive(target.T_ANTIMAGIC_SHIELD) then
local t = target:getTalentFromId(target.T_ANTIMAGIC_SHIELD)
......@@ -359,16 +359,16 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
local def = src.tempeffect_def[src.EFF_CURSE_OF_MISFORTUNE]
dam = def.doUnfortunateEnd(src, eff, target, dam)
end
if src:attr("crushing_blow") and (dam * (1.25 + (src.combat_critical_power or 0)/200)) > target.life then
dam = dam * (1.25 + (src.combat_critical_power or 0)/200)
game.logPlayer(src, "You end your target with a crushing blow!")
end
if target:attr("resist_unseen") and not target:canSee(src) then
dam = dam * (1 - math.min(target.resist_unseen,100)/100)
end
-- Sanctuary: reduces damage if it comes from outside of Gloom
if target.isTalentActive and target:isTalentActive(target.T_GLOOM) and target:knowTalent(target.T_SANCTUARY) then
if tmp and tmp.sanctuaryDamageChange then
......@@ -397,7 +397,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
print("[PROJECTOR] Chant of Fortress (source) dam", dam)
end
end
end
end
-- Psychic Projection
if src.attr and src:attr("is_psychic_projection") and not game.zone.is_dream_scape then
......@@ -408,7 +408,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
end
end
if src.necrotic_minion_be_nice and src.summoner == target then
if src.necrotic_minion_be_nice and src.summoner == target then
dam = dam * (1 - src.necrotic_minion_be_nice)
end
......@@ -649,7 +649,7 @@ newDamageType{
else
local old = src.fire_convert_to
src.fire_convert_to = nil
dam = DamageType:get(old[1]).projector(src, x, y, old[1], dam * old[2] / 100) +
dam = DamageType:get(old[1]).projector(src, x, y, old[1], dam * old[2] / 100) +
DamageType:get(type).projector(src, x, y, type, dam * (100 - old[2]) / 100)
src.fire_convert_to = old
return dam
......@@ -803,7 +803,7 @@ newDamageType{
end
local target = game.level.map(x, y, Map.ACTOR)
if target then
local energyDrain = (game.energy_to_act * 0.2)
local energyDrain = (game.energy_to_act * 0.2)
target.energy.value = target.energy.value - energyDrain
end
end,
......@@ -1731,8 +1731,19 @@ newDamageType{
-- Log entries are pretty limited currently because it can be quite spammy with the default messages already
newDamageType{
name = "item mind gloom", type = "ITEM_MIND_GLOOM",
tdesc = function(dam)
return ("#LIGHT_GREEN#%d%%#LAST# chance to cause #YELLOW#random insanity#LAST#"):format(dam)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* #LIGHT_GREEN#%d%%#LAST# chance to cause #YELLOW#random insanity#LAST#%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -1762,8 +1773,19 @@ newDamageType{
newDamageType{
name = "item darkness numbing", type = "ITEM_DARKNESS_NUMBING",
tdesc = function(dam)
return ("#LIGHT_GREEN#%d%%#LAST# chance to inflict #GREY#damage reduction#LAST#"):format(dam)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* #LIGHT_GREEN#%d%%#LAST# chance to inflict #GREY#damage reduction#LAST#%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -1777,18 +1799,29 @@ newDamageType{
newDamageType{
name = "item temporal energize", type = "ITEM_TEMPORAL_ENERGIZE",
tdesc = function(dam)
return ("#LIGHT_GREEN#%d%%#LAST# chance to gain #LIGHT_STEEL_BLUE#%d%% of a turn#LAST#"):format(dam, 10)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* #LIGHT_GREEN#%d%%#LAST# chance to gain #LIGHT_STEEL_BLUE#10%% of a turn#LAST#%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
if target and src and src.name and rng.percent(dam) then
if src.turn_procs and src.turn_procs.item_temporal_energize and src.turn_procs.item_temporal_energize > 3 then
if src.turn_procs and src.turn_procs.item_temporal_energize and src.turn_procs.item_temporal_energize > 3 then
game.logSeen(src, "#LIGHT_STEEL_BLUE#%s can't gain any more energy this turn! ", src.name:capitalize())
return
end
local energy = (game.energy_to_act * 0.1)
local energy = (game.energy_to_act * 0.1)
src.energy.value = src.energy.value + energy
--game.logSeen(target, "Time seems to bend and quicken energizing %s!", src.name:capitalize())
......@@ -1799,8 +1832,19 @@ newDamageType{
newDamageType{
name = "item acid corrode", type = "ITEM_ACID_CORRODE", text_color = "#GREEN#",
tdesc = function(dam)
return ("#LIGHT_GREEN#%d%%#LAST# chance to #GREEN#corrode armor#LAST#"):format(dam)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* #LIGHT_GREEN#%d%%#LAST# chance to #GREEN#corrode armor#LAST#%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -1814,8 +1858,19 @@ newDamageType{
newDamageType{
name = "item light blind", type = "ITEM_LIGHT_BLIND",
tdesc = function(dam)
return ("#LIGHT_GREEN#%d%%#LAST# chance to #YELLOW#blind#LAST#"):format(dam)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* #LIGHT_GREEN#%d%%#LAST# chance to #YELLOW#blind#LAST#%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -1832,8 +1887,19 @@ newDamageType{
newDamageType{
name = "item lightning daze", type = "ITEM_LIGHTNING_DAZE",
tdesc = function(dam)
return ("#LIGHT_GREEN#%d%%#LAST# chance to #ROYAL_BLUE#daze#LAST#"):format(dam)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* #LIGHT_GREEN#%d%%#LAST# chance to #ROYAL_BLUE#daze#LAST#%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -1848,11 +1914,22 @@ newDamageType{
end
end,
}
newDamageType{
name = "item blight disease", type = "ITEM_BLIGHT_DISEASE", text_color = "#DARK_GREEN#",
tdesc = function(dam)
return ("#LIGHT_GREEN#%d%%#LAST# chance to #DARK_GREEN#disease#LAST#"):format(dam)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* #LIGHT_GREEN#%d%%#LAST# chance to #DARK_GREEN#disease#LAST#%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -1866,8 +1943,19 @@ newDamageType{
newDamageType{
name = "item manaburn arcane", type = "ITEM_ANTIMAGIC_MANABURN", text_color = "#PURPLE#",
tdesc = function(dam)
return ("#DARK_ORCHID#%d arcane resource#LAST# burn"):format(dam)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d#LAST#)"):format(diff)
end
end
return ("* #DARK_ORCHID#%d arcane resource#LAST# burn%s")
:format(dam or 0, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -1897,8 +1985,19 @@ newDamageType{
newDamageType{
name = "item nature slow", type = "ITEM_NATURE_SLOW", text_color = "#LIGHT_GREEN#",
tdesc = function(dam)
return ("Slows global speed by #LIGHT_GREEN#%d%%#LAST#"):format(dam)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* Slows global speed by #LIGHT_GREEN#%d%%#LAST#%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -1911,8 +2010,19 @@ newDamageType{
-- Reduces all offensive powers by 20%
newDamageType{
name = "item antimagic scouring", type = "ITEM_ANTIMAGIC_SCOURING", text_color = "#ORCHID#",
tdesc = function(dam)
return ("#LIGHT_GREEN#%d%%#LAST# chance to #ORCHID#reduce powers#LAST# by %d%%"):format(dam, 20)
tdesc = function(dam, oldDam)
parens = ""
dam = dam or 0
if oldDam then
diff = dam - oldDam
if diff > 0 then
parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
elseif diff < 0 then
parens = (" (#RED#%d%%#LAST#)"):format(diff)
end
end
return ("* #LIGHT_GREEN#%d%%#LAST# chance to #ORCHID#reduce powers#LAST# by %d%%%s")
:format(dam, parens)
end,
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
......@@ -2118,17 +2228,17 @@ newDamageType{
projector = function(src, x, y, type, dam)
local target = game.level.map(x, y, Map.ACTOR)
if target and not target:attr("undead") then
target:setEffect(target.EFF_EMPOWERED_HEALING, 1, {power=(dam/200)})
if dam >= 100 then target:attr("allow_on_heal", 1) end
target:heal(dam, src)
-- If the target is shielded already then add to the shield power, else add a shield
local shield_power = dam * util.bound((target.healing_factor or 1), 0, 2.5)
if not target:hasEffect(target.EFF_DAMAGE_SHIELD) then
if not target:hasEffect(target.EFF_DAMAGE_SHIELD) then
target:setEffect(target.EFF_DAMAGE_SHIELD, 2, {power=shield_power})
else
-- Shields can't usually merge, so change the parameters manually
-- Shields can't usually merge, so change the parameters manually
local shield = target:hasEffect(target.EFF_DAMAGE_SHIELD)
shield.power = shield.power + shield_power
target.damage_shield_absorb = target.damage_shield_absorb + shield_power
......@@ -2162,8 +2272,8 @@ newDamageType{
if dam >= 100 then src:attr("allow_on_heal", 1) end
src:heal(dam / 2, src)
if dam >= 100 then src:attr("allow_on_heal", -1) end
end
end,
}
......@@ -3083,7 +3193,7 @@ newDamageType{
local target = game.level.map(x, y, Map.ACTOR)
if target then
local realdam = DamageType:get(DamageType.SLIME).projector(src, x, y, DamageType.SLIME, {dam=dam.dam, power=0.30})
if dam.nb > 0 then
dam.done = dam.done or {}
dam.done[target.uid] = true
......@@ -3103,7 +3213,7 @@ newDamageType{
end
end
return realdam
end
end
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