Newer
Older
-- Copyright (C) 2009, 2010 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.interface.ActorProject"
require "mod.class.interface.Archery"
local DamageType = require "engine.DamageType"
module(..., package.seeall, class.inherit(
-- a ToME actor is a complex beast it uses may inetrfaces
engine.Actor,
mod.class.interface.Combat,
mod.class.interface.Archery
-- Dont save the can_see_cache
_M._no_save_fields.can_see_cache = true
-- Use distance maps
_M.__do_distance_map = true
-- Define some basic combat stats
self.combat_def = 0
self.combat_armor = 0
self.combat_atk = 0
self.combat_apr = 0
self.combat_dam = 0
self.combat_physcrit = 0
self.combat_physspeed = 0
self.combat_spellspeed = 0
self.combat_spellcrit = 0
self.combat_spellpower = 0
self.unused_stats = self.unused_stats or 3
self.unused_talents = self.unused_talents or 2
self.unused_generics = self.unused_generics or 1
t.mana_rating = t.mana_rating or 4
t.stamina_rating = t.stamina_rating or 3
t.positive_negative_rating = t.positive_negative_rating or 3
t.can_pass = t.can_pass or {}
t.move_project = t.move_project or {}
t.stamina_regen = t.stamina_regen or 0.3 -- Stamina regens slower than mana
t.life_regen = t.life_regen or 0.25 -- Life regen real slow
t.positive_regen = t.positive_regen or -0.2 -- Positive energy slowly decays
t.negative_regen = t.negative_regen or -0.2 -- Positive energy slowly decays
t.max_positive = t.max_positive or 50
t.max_negative = t.max_negative or 50
t.positive = t.positive or 0
t.negative = t.negative or 0
t.hate_rating = t.hate_rating or 0.2
t.hate_regen = t.hate_regen or -0.035
t.max_hate = t.max_hate or 10
t.absolute_max_hate = t.absolute_max_hate or 15
t.hate = t.hate or 10
t.hate_per_kill = t.hate_per_kill or 0.8
-- Equilibrium has a default very high max, as bad effects happen even before reaching it
t.max_equilibrium = t.max_equilibrium or 100000
-- Default melee barehanded damage
self.combat = { dam=1, atk=1, apr=0, dammod={str=1} }
engine.interface.ActorProject.init(self, t)
self.changed = true
-- If ressources are too low, disable sustains
if self.mana < 1 or self.stamina < 1 then
for tid, _ in pairs(self.sustain_talents) do
local t = self:getTalentFromId(tid)
if (t.sustain_mana and self.mana < 1) or (t.sustain_stamina and self.stamina < 1) then
self:forceUseTalent(tid, {ignore_energy=true})
end
end
end
if self:knowTalent(self.T_UNNATURAL_BODY) then
local t = self:getTalentFromId(self.T_UNNATURAL_BODY)
t.do_regenLife(self, t)
end
-- Handle thunderstorm, even if the actor is stunned or incampacited it still works
if self:isTalentActive(self.T_THUNDERSTORM) then
local t = self:getTalentFromId(self.T_THUNDERSTORM)
t.do_storm(self, t)
end
if self:isTalentActive(self.T_BODY_OF_FIRE) then
local t = self:getTalentFromId(self.T_BODY_OF_FIRE)
t.do_fire(self, t)
end
if self:isTalentActive(self.T_HYMN_OF_MOONLIGHT) then
local t = self:getTalentFromId(self.T_HYMN_OF_MOONLIGHT)
t.do_beams(self, t)
end
if self:isTalentActive(self.T_BLOOD_FRENZY) then
local t = self:getTalentFromId(self.T_BLOOD_FRENZY)
t.do_turn(self, t)
end
-- this handles cursed gloom turn based effects
if self:isTalentActive(self.T_GLOOM) then
local t = self:getTalentFromId(self.T_GLOOM)
t.do_gloom(self, t)
end
if self:attr("stunned") then
self.stunned_counter = (self.stunned_counter or 0) + (self:attr("stun_immune") or 0) * 100
if self.stunned_counter < 100 then
self.energy.value = 0
else
-- We are saved for this turn
self.stunned_counter = self.stunned_counter - 100
game.logSeen(self, "%s temporarily fights the stun.", self.name:capitalize())
end
end
if self:attr("encased_in_ice") then self.energy.value = 0 end
if self:attr("stoned") then self.energy.value = 0 end
if self:attr("dazed") then self.energy.value = 0 end
local air_level, air_condition = game.level.map:checkEntity(self.x, self.y, Map.TERRAIN, "air_level"), game.level.map:checkEntity(self.x, self.y, Map.TERRAIN, "air_condition")
if air_level then
if not air_condition or not self.can_breath[air_condition] then self:suffocate(-air_level, self) end
end
-- Regain natural balance?
local equilibrium_level = game.level.map:checkEntity(self.x, self.y, Map.TERRAIN, "equilibrium_level")
if equilibrium_level then self:incEquilibrium(equilibrium_level) end
-- Still enough energy to act ?
if self.energy.value < game.energy_to_act then return false end
-- Ok reset the seen cache
self:resetCanSeeCache()
if self.on_act then self:on_act() end
x, y = self.x + rng.range(-1, 1), self.y + rng.range(-1, 1)
end
end
-- Should we prob travel through walls ?
if not force and self:attr("prob_travel") and game.level.map:checkEntity(x, y, Map.TERRAIN, "block_move", self) then
moved = self:probabilityTravel(x, y, self:attr("prob_travel"))
-- A bit weird, but this simple asks the collision code to detect an attack
if not game.level.map:checkAllEntities(x, y, "block_move", self, true) then
game.logPlayer(self, "You are unable to move!")
end
if not force and moved and not self.did_energy then self:useEnergy(game.energy_to_act * self:combatMovementSpeed()) end
-- Try to detect traps
if self:knowTalent(self.T_TRAP_DETECTION) then
local power = self:getTalentLevel(self.T_TRAP_DETECTION) * self:getCun(25)
local grids = core.fov.circle_grids(self.x, self.y, 1, true)
for x, yy in pairs(grids) do for y, _ in pairs(yy) do
local trap = game.level.map(x, y, Map.TRAP)
if trap and not trap:knownBy(self) and self:checkHit(power, trap.detect_power) then
trap:setKnown(self, true)
game.level.map:updateMap(x, y)
game.logPlayer(self, "You have found a trap (%s)!", trap:getName())
end
end end
end
if moved and self:isTalentActive(self.T_BODY_OF_STONE) then
self:forceUseTalent(self.T_BODY_OF_STONE, {ignore_energy=true})
end
--- Get the "path string" for this actor
-- See Map:addPathString() for more info
function _M:getPathString()
local ps = self.open_door and "return {open_door=true,can_pass={" or "return {can_pass={"
for what, check in pairs(self.can_pass) do
ps = ps .. what.."="..check..","
end
-- print("[PATH STRING] for", self.name, " :=: ", ps)
--- Drop no-teleport items
function _M:dropNoTeleportObjects()
for inven_id, inven in pairs(self.inven) do
for item = #inven, 1, -1 do
local o = inven[item]
if o.no_teleport then
self:dropFloor(inven, item, false, true)
game.logPlayer(self, "#LIGHT_RED#Your %s is immunte to the teleportation and drops to the floor!", o:getName{do_color=true})
end
end
end
end
if game.zone.wilderness then return true end
while game.level.map:isBound(tx, ty) and game.level.map:checkAllEntities(tx, ty, "block_move", self) and dist > 0 do
if game.level.map.attrs(tx, ty, "no_teleport") then break end
if game.level.map:isBound(tx, ty) and not game.level.map:checkAllEntities(tx, ty, "block_move", self) and not game.level.map.attrs(tx, ty, "no_teleport") then
--- Teleports randomly to a passable grid
-- This simply calls the default actor teleportRandom but first checks for space-time stability
-- @param x the coord of the teleporatation
-- @param y the coord of the teleporatation
-- @param dist the radius of the random effect, if set to 0 it is a precise teleport
-- @param min_dist the minimun radius of of the effect, will never teleport closer. Defaults to 0 if not set
-- @return true if the teleport worked
function _M:teleportRandom(x, y, dist, min_dist)
if game.level.data.no_teleport_south and y + dist > self.y then
y = self.y - dist
end
local ox, oy = self.x, self.y
local ret = engine.Actor.teleportRandom(self, x, y, dist, min_dist)
if self.x ~= ox or self.y ~= oy then
self.x, self.y, ox, oy = ox, oy, self.x, self.y
self:dropNoTeleportObjects()
self.x, self.y, ox, oy = ox, oy, self.x, self.y
end
return ret
local locs = {}
local ms = {}
self:project(tg, x, y, function(tx, ty)
if not game.level.map.attrs(tx, ty, "no_teleport") then
locs[#locs+1] = {x=tx,y=ty}
ms[#ms+1] = {map=game.level.map.map[tx + ty * w], attrs=game.level.map.attrs[tx + ty * w]}
end
end)
while #locs > 0 do
local l = rng.tableRemove(locs)
local m = rng.tableRemove(ms)
game.level.map.map[l.x + l.y * w] = m.map
game.level.map.attrs[l.x + l.y * w] = m.attrs
for z, e in pairs(m.map) do
if e.move then
e.x = nil e.y = nil e:move(l.x, l.y, true)
end
end
game.level.map:cleanFOV()
game.level.map.changed = true
function _M:magicMap(radius, x, y)
x = x or self.x
y = y or self.y
self:computeFOV(radius, "block_sense", function(x, y)
game.level.map.remembers(x, y, true)
game.level.map.has_seens(x, y, true)
function _M:incMoney(v)
self.money = self.money + v
if self.money < 0 then self.money = 0 end
self.changed = true
if self.player then
world:gainAchievement("TREASURE_HUNTER", self)
world:gainAchievement("TREASURE_HOARDER", self)
world:gainAchievement("DRAGON_GREED", self)
end
end
function _M:getRankStatAdjust()
if self.rank == 1 then return -1
elseif self.rank == 2 then return -0.5
elseif self.rank == 3 then return 0
elseif self.rank == 3.5 then return 1
elseif self.rank == 4 then return 1
elseif self.rank >= 5 then return 1
else return 0
end
end
function _M:getRankLevelAdjust()
if self.rank == 1 then return -1
elseif self.rank == 2 then return 0
elseif self.rank == 3 then return 1
elseif self.rank == 3.5 then return 2
elseif self.rank == 4 then return 3
elseif self.rank >= 5 then return 4
else return 0
end
end
function _M:getRankLifeAdjust(value)
local level_adjust = 1 + self.level / 40
if self.rank == 1 then return value * (level_adjust - 0.2)
elseif self.rank == 2 then return value * (level_adjust - 0.1)
elseif self.rank == 3 then return value * (level_adjust + 0.1)
elseif self.rank == 3.5 then return value * (level_adjust + 0.3)
elseif self.rank == 4 then return value * (level_adjust + 0.3)
elseif self.rank >= 5 then return value * (level_adjust + 0.5)
function _M:getRankResistAdjust()
if self.rank == 1 then return 0.4, 0.9
elseif self.rank == 2 then return 0.5, 1.5
elseif self.rank == 3 then return 0.8, 1.5
elseif self.rank == 3.5 then return 0.9, 1.5
elseif self.rank == 4 then return 0.9, 1.5
elseif self.rank >= 5 then return 0.9, 1.5
else return 0
end
end
function _M:TextRank()
local rank, color = "normal", "#ANTIQUE_WHITE#"
if self.rank == 1 then rank, color = "critter", "#C0C0C0#"
elseif self.rank == 2 then rank, color = "normal", "#ANTIQUE_WHITE#"
elseif self.rank == 3 then rank, color = "elite", "#YELLOW#"
elseif self.rank == 3.5 then rank, color = "unique", "#SANDY_BROWN#"
elseif self.rank == 4 then rank, color = "boss", "#ORANGE#"
elseif self.rank >= 5 then rank, color = "elite boss", "#GOLD#"
end
return rank, color
end
function _M:TextSizeCategory()
local sizecat = "medium"
if self.size_category <= 1 then sizecat = "tiny"
elseif self.size_category == 2 then sizecat = "small"
elseif self.size_category == 3 then sizecat = "medium"
elseif self.size_category == 4 then sizecat = "big"
elseif self.size_category == 5 then sizecat = "huge"
elseif self.size_category >= 6 then sizecat = "gargantuan"
function _M:tooltip(x, y, seen_by)
if seen_by and not seen_by:canSee(self) then return end
local factcolor, factstate, factlevel = "#ANTIQUE_WHITE#", "neutral", self:reactionToward(game.player)
if factlevel < 0 then factcolor, factstate = "#LIGHT_RED#", "hostile"
elseif factlevel > 0 then factcolor, factstate = "#LIGHT_GREEN#", "friendly"
local effs = {}
for tid, act in pairs(self.sustain_talents) do
if act then effs[#effs+1] = ("- #LIGHT_GREEN#%s"):format(self:getTalentFromId(tid).name) end
end
for eff_id, p in pairs(self.tmp) do
local e = self.tempeffect_def[eff_id]
if e.status == "detrimental" then
effs[#effs+1] = ("- #LIGHT_RED#%s"):format(e.desc)
else
effs[#effs+1] = ("- #LIGHT_GREEN#%s"):format(e.desc)
end
end
local resists = {}
for t, v in pairs(self.resists) do
resists[#resists+1] = string.format("%d%% %s", v, t == "all" and "all" or DamageType:get(t).name)
return ([[%s%s%s
Faction: %s%s (%s, %d)
self:getDisplayString(), rank_color, self.name,
self.type:capitalize(), self.subtype:capitalize(),
self.level,
self.exp,
self:getExpChart(self.level+1) or "---",
self:getStr(),
self:getDex(),
self:getMag(),
self:getWil(),
self:getCun(),
self:getCon(),
factcolor, Faction.factions[self.faction].name, factstate, factlevel,
--- Called before healing
function _M:onHeal(value, src)
if self:hasEffect(self.EFF_UNSTOPPABLE) then
return 0
end
--- Called before taking a hit, it's the chance to check for shields
function _M:onTakeHit(value, src)
-- Un-daze
if self:hasEffect(self.EFF_DAZED) then
self:removeEffect(self.EFF_DAZED)
end
-- remove stalking if there is an interaction
if self.stalker and src and self.stalker == src then
self.stalker:removeEffect(self.EFF_STALKER)
self:removeEffect(self.EFF_STALKED)
end
local mana_val = value * self:attr("disruption_shield")
-- We have enough to absord the full hit
if mana_val <= mana then
self:incMana(-mana_val)
self.disruption_shield_absorb = self.disruption_shield_absorb + value
-- Or the shield collapses in a deadly arcane explosion
else
local dam = self.disruption_shield_absorb
-- Deactivate without loosing energy
self:forceUseTalent(self.T_DISRUPTION_SHIELD, {ignore_energy=true})
game.logSeen(self, "%s disruption shield collapses and then explodes in a powerful manastorm!", self.name:capitalize())
self:project(tg, self.x, self.y, DamageType.ARCANE, dam, {type="manathrust"})
end
end
if self:attr("time_shield") then
-- Absorb damage into the time shield
if value <= self.time_shield_absorb then
self.time_shield_absorb = self.time_shield_absorb - value
value = 0
else
self.time_shield_absorb = 0
value = value - self.time_shield_absorb
end
-- If we are at the end of the capacity, release the time shield damage
if self.time_shield_absorb <= 0 then
game.logPlayer(self, "Your time shield crumbles under the damage!")
self:removeEffect(self.EFF_TIME_SHIELD)
if self:attr("damage_shield") then
-- Absorb damage into the shield
if value <= self.damage_shield_absorb then
self.damage_shield_absorb = self.damage_shield_absorb - value
value = 0
else
self.damage_shield_absorb = 0
value = value - self.damage_shield_absorb
end
-- If we are at the end of the capacity, release the time shield damage
if self.damage_shield_absorb <= 0 then
game.logPlayer(self, "Your shield crumbles under the damage!")
self:removeEffect(self.EFF_DAMAGE_SHIELD)
end
end
if self:attr("displacement_shield") then
-- Absorb damage into the displacement shield
if value <= self.displacement_shield and rng.percent(self.displacement_shield_chance) then
game.logSeen(self, "The displacement shield teleports the damage to %s!", self.displacement_shield_target.name)
self.displacement_shield = self.displacement_shield - value
self.displacement_shield_target:takeHit(value, src)
if self:isTalentActive(self.T_BONE_SHIELD) then
local t = self:getTalentFromId(self.T_BONE_SHIELD)
t.absorb(self, t, self:isTalentActive(self.T_BONE_SHIELD))
value = 0
end
-- Mount takes some damage ?
local mount = self:hasMount()
if mount and mount.mount.share_damage then
mount.mount.actor:takeHit(value * mount.mount.share_damage / 100, src)
value = value * (100 - mount.mount.share_damage) / 100
-- Remove the dead mount
if mount.mount.actor.dead and mount.mount.effect then
self:removeEffect(mount.mount.effect)
end
end
if src and src.resolveSource and src:resolveSource().player and value >= 600 then
world:gainAchievement("SIZE_MATTERS", src:resolveSource())
end
-- Stoned ? SHATTER !
if self:attr("stoned") and value >= self.max_life * 0.3 then
-- Make the damage high enough to kill it
value = self.max_life + 1
game.logSeen(self, "%s shatters into pieces!", self.name:capitalize())
end
-- Adds hate
if self:knowTalent(self.T_HATE_POOL) then
local hateGain = 0
local hateMessage
if value / self.max_life >= 0.15 then
-- you take a big hit..adds 0.2 + 0.2 for each 5% over 15%
hateGain = hateGain + 0.2 + (((value / self.max_life) - 0.15) * 10 * 0.5)
hateMessage = "#F53CBE#You fight through the pain!"
end
if value / self.max_life >= 0.05 and (self.life - value) / self.max_life < 0.25 then
-- you take a hit with low health
hateGain = hateGain + 0.4
hateMessage = "#F53CBE#Your rage grows even as your life fades!"
end
if hateGain >= 0.1 then
self.hate = math.min(self.max_hate, self.hate + hateGain)
if hateMessage then
game.logPlayer(self, hateMessage.." (+%0.1f hate)", hateGain)
end
end
end
if src and src.knowTalent and src:knowTalent(src.T_HATE_POOL) then
local hateGain = 0
local hateMessage
if value / src.max_life > 0.33 then
-- you deliver a big hit
hateGain = hateGain + 0.4
hateMessage = "#F53CBE#Your powerful attack feeds your madness!"
end
if hateGain >= 0.1 then
src.hate = math.min(src.max_hate, src.hate + hateGain)
if hateMessage then
game.logPlayer(src, hateMessage.." (+%0.1f hate)", hateGain)
-- Bloodlust!
if src and src.knowTalent and src:knowTalent(src.T_BLOODLUST) then
src:setEffect(src.EFF_BLOODLUST, 1, {})
end
if self:knowTalent(self.T_RAMPAGE) then
local t = self:getTalentFromId(self.T_RAMPAGE)
t:onTakeHit(self, value / self.max_life)
end
if self:hasEffect(self.EFF_UNSTOPPABLE) then
if value > self.life then value = self.life - 1 end
end
-- Split ?
if self.clone_on_hit and value >= self.clone_on_hit.min_dam_pct * self.max_life / 100 and rng.percent(self.clone_on_hit.chance) then
-- Find space
local x, y = util.findFreeGrid(self.x, self.y, 1, true, {[Map.ACTOR]=true})
if x then
-- Find a place around to clone
local a = self:clone()
a.life = math.max(1, a.life - value / 2)
a.clone_on_hit.chance = math.ceil(a.clone_on_hit.chance / 2)
a.energy.val = 0
a.exp_worth = 0.1
a.inven = {}
a.x, a.y = nil, nil
game.zone:addEntity(game.level, a, "actor", x, y)
game.logSeen(self, "%s is split in two!", self.name:capitalize())
value = value / 2
end
end
if self.on_takehit then value = self:check("on_takehit", value, src) end
function _M:resolveSource()
if self.summoner_gain_exp and self.summoner then
return self.summoner:resolveSource()
else
return self
end
end
dg
committed
local killer = src:resolveSource()
killer:gainExp(self:worthExp(killer))
-- Hack: even if the boss dies from something else, give the player exp
if not killer.player and self.rank > 3 then
game.logPlayer(game.player, "You feel a surge of power as a powerful creature falls nearby.")
killer = game.player:resolveSource()
killer:gainExp(self:worthExp(killer))
end
-- Do we get a blooooooody death ?
if rng.percent(33) then self:bloodyDeath() end
if not self.keep_inven_on_death then
if not self.no_drops then
for inven_id, inven in pairs(self.inven) do
for i, o in ipairs(inven) do
-- Handle boss wielding artifacts
if o.__special_boss_drop and rng.percent(o.__special_boss_drop.chance) then
print("Refusing to drop "..self.name.." artifact "..o.name.." with chance "..o.__special_boss_drop.chance)
-- Do not drop
o.no_drop = true
-- Drop a random artifact instead
local ro = game.zone:makeEntity(game.level, "object", {unique=true, not_properties={"lore"}}, nil, true)
if ro then game.zone:addEntity(game.level, ro, "object", self.x, self.y) end
end
o.droppedBy = self.name
else
o:removed()
if src and src.knowTalent and src:knowTalent(src.T_UNENDING_FRENZY) then
src:incStamina(src:getTalentLevel(src.T_UNENDING_FRENZY) * 2)
end
-- Increases blood frenzy
if src and src.knowTalent and src:knowTalent(src.T_BLOOD_FRENZY) and src:isTalentActive(src.T_BLOOD_FRENZY) then
src.blood_frenzy = src.blood_frenzy + src:getTalentLevel(src.T_BLOOD_FRENZY) * 2
end
if self.level - 2 > src.level then
-- level bonus
hateGain = hateGain + (self.level - 2 - src.level) * 0.2
hateMessage = "#F53CBE#You have taken the life of an experienced foe!"
end
-- boss bonus
hateGain = hateGain * 4
hateMessage = "#F53CBE#Your hate has conquered a great adversary!"
elseif self.rank >= 3 then
-- elite bonus
hateGain = hateGain * 2
hateMessage = "#F53CBE#An elite foe has fallen to your hate!"
src.hate = math.min(src.max_hate, src.hate + hateGain)
if hateMessage then
game.logPlayer(src, hateMessage.." (+%0.1f hate)", hateGain - src.hate_per_kill)
if src and src.knowTalent and src:knowTalent(src.T_UNNATURAL_BODY) then
local t = src:getTalentFromId(src.T_UNNATURAL_BODY)
t.on_kill(src, t, self)
end
if src and src.knowTalent and src:knowTalent(src.T_CRUEL_VIGOR) then
local t = src:getTalentFromId(src.T_CRUEL_VIGOR)
t.on_kill(src, t)
end
if src and src.knowTalent and src:knowTalent(src.T_BLOODRAGE) then
local t = src:getTalentFromId(src.T_BLOODRAGE)
t.on_kill(src, t)
end
if src and src.isTalentActive and src:isTalentActive(src.T_FORAGE) then
local t = src:getTalentFromId(src.T_FORAGE)
t.on_kill(src, t, self)
end
if src and src.hasEffect and src:hasEffect(self.EFF_UNSTOPPABLE) then
local p = src:hasEffect(self.EFF_UNSTOPPABLE)
p.kills = p.kills + 1
end
if self:hasEffect(self.EFF_CORROSIVE_WORM) then
local p = self:hasEffect(self.EFF_CORROSIVE_WORM)
p.src:project({type="ball", radius=4, x=self.x, y=self.y}, self.x, self.y, DamageType.ACID, p.explosion, {type="acid"})
end
if src and src.attr and src:attr("vim_on_death") and not self:attr("undead") then src:incVim(src:attr("vim_on_death")) end
if math.floor(p.life) <= 1 and not p.dead then world:gainAchievement("THAT_WAS_CLOSE", p) end
world:gainAchievement("EXTERMINATOR", p, self)
world:gainAchievement("PEST_CONTROL", p, self)
if self.unique then
p:registerUniqueKilled(self)
end
p.all_kills = p.all_kills or {}
p.all_kills[self.name] = p.all_kills[self.name] or 0
p.all_kills[self.name] = p.all_kills[self.name] + 1
end
function _M:learnStats(statorder)
self.auto_stat_cnt = self.auto_stat_cnt or 1
local max = 60
-- Allow to go over a natural 60, up to 80 at level 50
if not self.no_auto_high_stats then max = 60 + (self.level * 20 / 50) end
if self:getStat(statorder[self.auto_stat_cnt]) < max then
self:incStat(statorder[self.auto_stat_cnt], 1)
self.unused_stats = self.unused_stats - 1
end
self.auto_stat_cnt = util.boundWrap(self.auto_stat_cnt + 1, 1, #statorder)
function _M:resetToFull()
self.life = self.max_life
self.mana = self.max_mana
self.air = self.max_air
self.unused_stats = self.unused_stats + 3 + self:getRankStatAdjust()
self.unused_talents = self.unused_talents + 1
self.unused_generics = self.unused_generics + 1
if self.level % 5 == 0 then self.unused_talents = self.unused_talents + 1 end
if self.level % 5 == 0 then self.unused_generics = self.unused_generics - 1 end
-- At levels 10, 20 and 30 we gain a new talent type
if self.level == 10 or self.level == 20 or self.level == 30 then
self.unused_talents_types = self.unused_talents_types + 1
end
-- Gain some basic resistances
if not self.no_auto_resists then
-- Make up a random list of resists the first time
if not self.auto_resists_list then
local list = {
DamageType.PHYSICAL,
DamageType.FIRE, DamageType.COLD, DamageType.ACID, DamageType.LIGHTNING,
DamageType.LIGHT, DamageType.DARKNESS,
DamageType.NATURE, DamageType.BLIGHT,
}
self.auto_resists_list = {}
for i = 1, rng.range(1, self.auto_resists_nb or 2) do
local t = rng.tableRemove(list)
-- Double the chance so that resist is more likely to happen
if rng.percent(30) then self.auto_resists_list[#self.auto_resists_list+1] = t end
self.auto_resists_list[#self.auto_resists_list+1] = t
end
end
-- Provide one of our resists
local t = rng.table(self.auto_resists_list)
if (self.resists[t] or 0) < 50 then
self.resists[t] = (self.resists[t] or 0) + rng.float(self:getRankResistAdjust())
end
-- Bosses have a right to get a general damage reduction
if self.rank >= 4 then
self.resists.all = (self.resists.all or 0) + rng.float(self:getRankResistAdjust()) / (self.rank == 4 and 3 or 2.5)
end
local rating = self.life_rating
if not self.fixed_rating then
rating = rng.range(math.floor(self.life_rating * 0.5), math.floor(self.life_rating * 1.5))
end
self.max_life = self.max_life + math.max(self:getRankLifeAdjust(rating), 1)
self:incMaxPositive(self.positive_negative_rating)
self:incMaxNegative(self.positive_negative_rating)
if self.max_hate < self.absolute_max_hate then
local amount = math.min(self.hate_rating, self.absolute_max_hate - self.max_hate)
self:incMaxHate(amount)
end
-- Force levelup of the golem