Newer
Older
--
-- 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.PlayerMouse"
require "mod.class.interface.PlayerStats"
require "mod.class.interface.PlayerLore"
require "mod.class.interface.PartyDeath"
local ActorTalents = require "engine.interface.ActorTalents"
dg
committed
local LevelupDialog = require "mod.dialogs.LevelupDialog"
--- Defines the player for ToME
-- It is a normal actor, with some redefined methods to handle user interaction.<br/>
module(..., package.seeall, class.inherit(
mod.class.Actor,
engine.interface.PlayerRest,
engine.interface.PlayerMouse,
engine.interface.PlayerSlide,
mod.class.interface.PlayerStats,
mod.class.interface.PlayerDumpJSON,
mod.class.interface.PartyDeath
-- Allow character registration even after birth
allow_late_uuid = true
t.display=t.display or '@'
t.color_r=t.color_r or 230
t.color_g=t.color_g or 230
t.color_b=t.color_b or 230
t.unique = t.unique or "player"
if type(t.open_door) == "nil" then t.open_door = true end
t.type = t.type or "humanoid"
t.subtype = t.subtype or "player"
t.faction = t.faction or "players"
t.ai = t.ai or "tactical"
dg
committed
t.ai_state = t.ai_state or {talent_in=1, ai_move="move_astar"}
-- Dont give free resists & higher stat max to players
t.resists_cap = t.resists_cap or {}
mod.class.Actor.init(self, t, no_default)
engine.interface.PlayerHotkeys.init(self, t)
mod.class.interface.PlayerLore.init(self, t)
self.descriptor = self.descriptor or {}
self.died_times = self.died_times or {}
self.last_learnt_talents = { class={}, generic={} }
function _M:onBirth(birther)
-- Make a list of random escort levels
local race_def = birther.birth_descriptor_def.race[self.descriptor.race]
local subrace_def = birther.birth_descriptor_def.subrace[self.descriptor.subrace]
local def = subrace_def.random_escort_possibilities or race_def.random_escort_possibilities
if def then
local zones = {}
for i, zd in ipairs(def) do for j = zd[2], zd[3] do zones[#zones+1] = {zd[1], j} end end
self.random_escort_levels = {}
for i = 1, 9 do
local z = rng.tableRemove(zones)
print("Random escort on", z[1], z[2])
self.random_escort_levels[z[1]] = self.random_escort_levels[z[1]] or {}
self.random_escort_levels[z[1]][z[2]] = true
end
end
end
function _M:onEnterLevel(zone, level)
self.entered_level = {x=self.x, y=self.y}
-- Fire random escort quest
if self.random_escort_levels and self.random_escort_levels[zone.short_name] and self.random_escort_levels[zone.short_name][level.level] then
self:grantQuest("escort-duty")
end
-- Cancel effects
local effs = {}
for eff_id, p in pairs(self.tmp) do
if self.tempeffect_def[eff_id].cancel_on_level_change then
effs[#effs+1] = eff_id
if type(self.tempeffect_def[eff_id].cancel_on_level_change) == "function" then self.tempeffect_def[eff_id].cancel_on_level_change(self, p) end
end
for i, eff_id in ipairs(effs) do self:removeEffect(eff_id) end
function _M:onEnterLevelEnd(zone, level)
end
function _M:onLeaveLevel(zone, level)
if self:hasEffect(self.EFF_FEED) then
self:removeEffect(self.EFF_FEED, true)
end
-- Fail past escort quests
local eid = "escort-duty-"..zone.short_name.."-"..level.level
if self:hasQuest(eid) and not self:hasQuest(eid):isEnded() then
local q = self:hasQuest(eid)
q.abandoned = true
self:setQuestStatus(eid, q.FAILED)
end
end
-- Wilderness encounter
function _M:onWorldEncounter(target, x, y)
if x and y and game.level.map(x, y, Map.ACTOR) == target then
game.level.map:remove(x, y, Map.ACTOR)
end
function _M:describeFloor(x, y)
-- Autopickup money
if self:getInven(self.INVEN_INVEN) then
local i, nb = 1, 0
local obj = game.level.map:getObject(x, y, i)
while obj do
if not ((obj.auto_pickup and not obj.unique) and self:pickupFloor(i, true)) then
if self:attr("auto_id") and obj:getPowerRank() <= self.auto_id then obj:identify(true) end
nb = nb + 1
i = i + 1
game.logSeen(self, "There is an item here: %s", obj:getName{do_color=true})
end
obj = game.level.map:getObject(x, y, i)
end
end
local g = game.level.map(x, y, game.level.map.TERRAIN)
if g and g.change_level then game.logPlayer(self, "#YELLOW_GREEN#There is "..g.name:a_an().." here (press '<', '>' or right click to use).") end
end
dg
committed
game.level.map:moveViewSurround(self.x, self.y, config.settings.tome.scroll_dist, config.settings.tome.scroll_dist)
if self.describeFloor then self:describeFloor(self.x, self.y) end
-- if not force and ox == self.x and oy == self.y and self.tryPlayerSlide then
-- x, y = self:tryPlayerSlide(x, y, false)
-- self.tryPlayerSlide = false
-- moved = self:move(x, y, false)
-- self.tryPlayerSlide = nil
-- end
if game.zone.wilderness and not force then
if self.x ~= ox or self.y ~= oy then
game.state:worldDirectorAI()
end
-- Update zone name
if game.zone.variable_zone_name then game:updateZoneName() end
-- Run out of time ?
if self.summon_time then
self.summon_time = self.summon_time - 1
if self.summon_time <= 0 then
game.logPlayer(self, "#PINK#Your summoned %s disappears.", self.name)
self:die()
return true
end
end
-- Funky shader things !
self:updateMainShader()
self.old_life = self.life
-- game.flash:empty()
-- update feed/beckoned immediately before the player moves for best visual consistency (this is not perfect but looks much better than updating mid-move)
if self:hasEffect(self.EFF_FEED) then
self.tempeffect_def[self.EFF_FEED].updateFeed(self, self:hasEffect(self.EFF_FEED))
elseif self:hasEffect(self.EFF_FED_UPON) then
local fed_upon_eff = self:hasEffect(self.EFF_FED_UPON)
fed_upon_eff.src.tempeffect_def[fed_upon_eff.src.EFF_FEED].updateFeed(fed_upon_eff.src, fed_upon_eff.src:hasEffect(self.EFF_FEED))
end
if not self:restStep() and not self:runStep() and self.player then
elseif not self.player then
self:useEnergy()
--- Funky shader stuff
function _M:updateMainShader()
if game.fbo_shader then
-- Set shader HP warning
if self.life ~= self.old_life then
if self.life < self.max_life / 2 then game.fbo_shader:setUniform("hp_warning", 1 - (self.life / self.max_life))
else game.fbo_shader:setUniform("hp_warning", 0) end
end
-- Colorize shader
if self:attr("stealth") then game.fbo_shader:setUniform("colorize", {0.9,0.9,0.9,0.6})
elseif self:attr("invisible") then game.fbo_shader:setUniform("colorize", {0.3,0.4,0.9,0.8})
elseif self:attr("unstoppable") then game.fbo_shader:setUniform("colorize", {1,0.2,0,1})
elseif self:attr("lightning_speed") then game.fbo_shader:setUniform("colorize", {0.2,0.3,1,1})
elseif game.level and game.level.data.is_eidolon_plane then game.fbo_shader:setUniform("colorize", {1,1,1,1})
-- elseif game:hasDialogUp() then game.fbo_shader:setUniform("colorize", {0.9,0.9,0.9})
else game.fbo_shader:setUniform("colorize", {0,0,0,0}) -- Disable
-- Blur shader
if self:attr("confused") then game.fbo_shader:setUniform("blur", 2)
-- elseif game:hasDialogUp() then game.fbo_shader:setUniform("blur", 3)
else game.fbo_shader:setUniform("blur", 0) -- Disable
end
-- Moving Blur shader
if self:attr("invisible") then game.fbo_shader:setUniform("motionblur", 3)
elseif self:attr("lightning_speed") then game.fbo_shader:setUniform("motionblur", 2)
else game.fbo_shader:setUniform("motionblur", 0) -- Disable
end
end
end
local fovdist = {}
for i = 0, 30 * 30 do
end
local wild_fovdist = {}
for i = 0, 10 * 10 do
wild_fovdist[i] = math.max((5 - math.sqrt(i)) / 1.4, 0.6)
end
local arcane_eye_true_seeing = function() return true, 100 end
function _M:playerFOV()
-- Clean FOV before computing it
game.level.map:cleanFOV()
-- Do wilderness stuff, nothing else
if game.zone.wilderness then
self:computeFOV(game.zone.wilderness_see_radius, "block_sight", function(x, y, dx, dy, sqdist) game.level.map:applyLite(x, y, wild_fovdist[sqdist]) end, true, true, true)
return
end
if (self.esp_all and self.esp_all > 0) or next(self.esp) then
self:computeFOV(self.esp_range or 10, "block_esp", function(x, y) game.level.map:applyESP(x, y, 0.6) end, true, true, true)
end
-- Handle Sense spell, a simple FOV, using cache. Note that this means some terrain features can be made to block sensing
if self:attr("detect_range") then
self:computeFOV(self:attr("detect_range"), "block_sense", function(x, y)
local ok = false
if self:attr("detect_actor") and game.level.map(x, y, game.level.map.ACTOR) then ok = true end
if self:attr("detect_object") and game.level.map(x, y, game.level.map.OBJECT) then ok = true end
if self:attr("detect_trap") and game.level.map(x, y, game.level.map.TRAP) then
game.level.map(x, y, game.level.map.TRAP):setKnown(self, true)
-- Handle arcane eye
if self:hasEffect(self.EFF_ARCANE_EYE) then
local eff = self:hasEffect(self.EFF_ARCANE_EYE)
local map = game.level.map
core.fov.calc_circle(
eff.x, eff.y, game.level.map.w, game.level.map.h, eff.radius, function(_, x, y) if map:checkAllEntities(x, y, "block_sight", self) then return true end end,
function(_, x, y)
local t = map(x, y, map.ACTOR)
if t and (eff.true_seeing or self:canSee(t)) then
map.seens(x, y, 1)
if self.can_see_cache[t] then self.can_see_cache[t]["nil/nil"] = {true, 100} end
end
end,
cache and map._fovcache["block_sight"]
)
end
-- Handle Preternatural Senses talent, a simple FOV, using cache.
if self:knowTalent(self.T_PRETERNATURAL_SENSES) then
local t = self:getTalentFromId(self.T_PRETERNATURAL_SENSES)
local range = self:getTalentRange(t)
self:computeFOV(range, "block_sense", function(x, y)
if game.level.map(x, y, game.level.map.ACTOR) then
local effStalker = self:hasEffect(self.EFF_STALKER)
if effStalker then
if core.fov.distance(self.x, self.y, effStalker.target.x, effStalker.target.y) <= 10 then
game.level.map.seens(effStalker.target.x, effStalker.target.y, 0.6)
end
end
if not self:attr("blind") then
-- Handle dark vision; same as infravision, but also sees past creeping dark
-- this is treated as a sense, but is filtered by custom LOS code
if self:knowTalent(self.T_DARK_VISION) then
local t = self:getTalentFromId(self.T_DARK_VISION)
local range = self:getTalentRange(t)
self:computeFOV(range, "block_sense", function(x, y)
local actor = game.level.map(x, y, game.level.map.ACTOR)
if actor and self:hasLOS(x, y) then
game.level.map.seens(x, y, 0.6)
end
end, true, true, true)
end
-- Handle infravision/heightened_senses which allow to see outside of lite radius but with LOS
if self:attr("infravision") or self:attr("heightened_senses") then
local rad = (self.heightened_senses or 0) + (self.infravision or 0)
local rad2 = math.max(1, math.floor(rad / 4))
self:computeFOV(rad, "block_sight", function(x, y, dx, dy, sqdist) if game.level.map(x, y, game.level.map.ACTOR) then game.level.map.seens(x, y, fovdist[sqdist]) end end, true, true, true)
self:computeFOV(rad2, "block_sight", function(x, y, dx, dy, sqdist) game.level.map:applyLite(x, y, fovdist[sqdist]) end, true, true, true)
end
-- Compute both the normal and the lite FOV, using cache
-- Do it last so it overrides others
self:computeFOV(self.sight or 10, "block_sight", function(x, y, dx, dy, sqdist)
game.level.map:apply(x, y, fovdist[sqdist])
end, true, false, true)
if self.lite <= 0 then game.level.map:applyLite(self.x, self.y)
else self:computeFOV(self.lite, "block_sight", function(x, y, dx, dy, sqdist) game.level.map:applyLite(x, y) end, true, true, true) end
-- For each entity, generate lite
local uid, e = next(game.level.entities)
while uid do
if e ~= self and e.lite and e.lite > 0 and e.computeFOV then
e:computeFOV(e.lite, "block_sight", function(x, y, dx, dy, sqdist) game.level.map:applyExtraLite(x, y, fovdist[sqdist]) end, true, true)
end
uid, e = next(game.level.entities, uid)
end
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
--- Create a line to target based on field of vision
function _M:lineFOV(tx, ty, extra_block, block, sx, sy)
sx = sx or self.x
sy = sy or self.y
local act = game.level.map(x, y, Map.ACTOR)
local sees_target = game.level.map.seens(tx, ty)
local darkVisionRange
if self:knowTalent(self.T_DARK_VISION) then
local t = self:getTalentFromId(self.T_DARK_VISION)
darkVisionRange = self:getTalentRange(t)
end
local inCreepingDark = false
block = block or function(_, x, y)
if darkVisionRange then
if game.level.map:checkAllEntities(x, y, "creepingDark") then
inCreepingDark = true
end
if inCreepingDark and core.fov.distance(sx, sy, x, y) > darkVisionRange then
return true
end
end
if sees_target then
return game.level.map:checkAllEntities(x, y, "block_sight") or
game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile") or
type(extra_block) == "function" and extra_block(self, x, y)
elseif core.fov.distance(sx, sy, x, y) <= self.sight and (game.level.map.remembers(x, y) or game.level.map.seens(x, y)) then
return game.level.map:checkEntity(x, y, Map.TERRAIN, "block_sight") or
game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(x, y, engine.Map.TERRAIN, "pass_projectile") or
type(extra_block) == "function" and extra_block(self, x, y)
else
return true
end
end
return core.fov.line(sx, sy, tx, ty, block)
end
--- Called before taking a hit, overload mod.class.Actor:onTakeHit() to stop resting and running
function _M:onTakeHit(value, src)
self:runStop("taken damage")
self:restStop("taken damage")
if self.life < self.max_life * 0.3 then
local sx, sy = game.level.map:getTileToScreen(self.x, self.y)
game.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, 2, "LOW HEALTH!", {255,0,0}, true)
end
-- Hit direction warning
if src.x and src.y and (self.x ~= src.x or self.y ~= src.y) then
local range = core.fov.distance(src.x, src.y, self.x, self.y)
if range > 1 then
local angle = math.atan2(src.y - self.y, src.x - self.x)
game.level.map:particleEmitter(self.x, self.y, 1, "hit_warning", {angle=math.deg(angle)})
end
end
function _M:heal(value, src)
-- Difficulty settings
if game.difficulty == game.DIFFICULTY_EASY then
value = value * 1.3
end
mod.class.Actor.heal(self, value, src)
end
dg
committed
function _M:suffocate(value, src, death_msg)
local dead, affected = mod.class.Actor.suffocate(self, value, src, death_msg)
if affected and value > 0 and self.runStop then
self:runStop("suffocating")
self:restStop("suffocating")
end
return dead, affected
end
function _M:onChat()
self:runStop("chat started")
self:restStop("chat started")
end
--- Notify the player of available cooldowns
function _M:onTalentCooledDown(tid)
local t = self:getTalentFromId(tid)
local x, y = game.level.map:getTileToScreen(self.x, self.y)
game.flyers:add(x, y, 30, -0.3, -3.5, ("%s available"):format(t.name:capitalize()), {0,255,00})
game.log("#00ff00#%sTalent %s is ready to use.", (t.display_entity and t.display_entity:getDisplayString() or ""), t.name)
--- Tries to get a target from the user
function _M:getTarget(typ)
if self:attr("encased_in_ice") then
return self.x, self.y, self
else
return game:targetGetForPlayer(typ)
end
--- Sets the current target
function _M:setTarget(target)
return game:targetSetForPlayer(target)
local seen = false
-- Check for visible monsters, only see LOS actors, so telepathy wont prevent resting
core.fov.calc_circle(self.x, self.y, game.level.map.w, game.level.map.h, 20, function(_, x, y) return game.level.map:opaque(x, y) end, function(_, x, y)
dg
committed
if actor and self:reactionToward(actor) < 0 and self:canSee(actor) and game.level.map.seens(x, y) then
seen = {x=x,y=y,actor=actor}
end
-- We can rest if no hostiles are in sight, and if we need life/mana/stamina/psi (and their regen rates allows them to fully regen)
dg
committed
local spotted = spotHostiles(self)
if spotted then return false, ("hostile spotted (%s%s)"):format(spotted.actor.name, game.level.map:isOnScreen(spotted.x, spotted.y) and "" or " - offscreen") end
-- Resting improves regen
for act, def in pairs(game.party.members) do if game.level:hasEntity(act) and not act.dead then
local old_shield = act.arcane_shield
act.arcane_shield = nil
act:heal(act.life_regen * perc)
act.arcane_shield = old_shield
act:incStamina(act.stamina_regen * perc)
act:incMana(act.mana_regen * perc)
act:incPsi(act.psi_regen * perc)
-- Check resources, make sure they CAN go up, otherwise we will never stop
if not self.resting.rest_turns then
if self.air_regen < 0 then return false, "losing breath!" end
if self.life_regen <= 0 then return false, "losing health!" end
if self:getMana() < self:getMaxMana() and self.mana_regen > 0 then return true end
if self:getStamina() < self:getMaxStamina() and self.stamina_regen > 0 then return true end
if self:getPsi() < self:getMaxPsi() and self.psi_regen > 0 then return true end
if self.life < self.max_life and self.life_regen> 0 then return true end
for act, def in pairs(game.party.members) do if game.level:hasEntity(act) and not act.dead then
if act.life < act.max_life and act.life_regen> 0 then return true end
end end
else
return true
end
-- Enter cooldown waiting rest if we are at max already
if self.resting.cnt == 1 then
self.resting.wait_cooldowns = true
end
if self.resting.wait_cooldowns then
for tid, cd in pairs(self.talents_cd) do
if self:isTalentActive(self.T_CONDUIT) and (tid == self.T_KINETIC_AURA or tid == self.T_CHARGED_AURA or tid == self.T_THERMAL_AURA) then
-- nothing
elseif self.talents_auto[tid] then
-- nothing
else
end
end
self.resting.wait_cooldowns = nil
-- We can run if no hostiles are in sight, and if no interesting terrain or characters are next to us.
-- Known traps aren't interesting. We let the engine run around traps, or stop if it can't.
-- 'ignore_memory' is only used when checking for paths around traps. This ensures we don't remember items "obj_seen" that we aren't supposed to
function _M:runCheck(ignore_memory)
dg
committed
local spotted = spotHostiles(self)
if spotted then return false, ("hostile spotted (%s%s)"):format(spotted.actor.name, game.level.map:isOnScreen(spotted.x, spotted.y) and "" or " - offscreen") end
if self.air_regen < 0 then return false, "losing breath!" end
-- Objects are always interesting, only on curent spot
if what == "self" and not game.level.map.attrs(x, y, "obj_seen") then
if not ignore_memory then game.level.map.attrs(x, y, "obj_seen", true) end
return
-- Only notice interesting terrains
local grid = game.level.map(x, y, Map.TERRAIN)
if grid and grid.notice then noticed = "interesting terrain"; return end
if grid and grid.type and grid.type == "store" then noticed = "store entrance spotted"; return end
-- Only notice interesting characters
local actor = game.level.map(x, y, Map.ACTOR)
if actor and actor.can_talk then noticed = "interesting character"; return end
-- We let the engine take care of traps, but we should still notice "trap" stores.
if game.level.map:checkAllEntities(x, y, "store") then noticed = "store entrance spotted"; return end
end)
if noticed then return false, noticed end
return engine.interface.PlayerRun.runCheck(self)
end
--- Move with the mouse
-- We just feed our spotHostile to the interface mouseMove
function _M:mouseMove(tmx, tmy, force_move)
local astar_check = function(x, y)
-- Dont do traps
local trap = game.level.map(x, y, Map.TRAP)
if trap and trap:knownBy(self) and trap:canTrigger(x, y, self, true) then return false end
-- Dont go where you cant breath
if not self:attr("no_breath") then
local air_level, air_condition = game.level.map:checkEntity(x, y, Map.TERRAIN, "air_level"), game.level.map:checkEntity(x, y, Map.TERRAIN, "air_condition")
if air_level then
if not air_condition or not self.can_breath[air_condition] or self.can_breath[air_condition] <= 0 then
return false
end
end
end
return engine.interface.PlayerMouse.mouseMove(self, tmx, tmy, spotHostiles, {recheck=true, astar_check=astar_check}, force_move)
end
--- Called after running a step
function _M:runMoved()
self:playerFOV()
end
--- Called after stopping running
function _M:runStopped()
self:playerFOV()
end
--- Activates a hotkey with a type "inventory"
function _M:hotkeyInventory(name)
local find = function(name)
local os = {}
-- Sort invens, use worn first
local invens = {}
invens[#invens+1] = {inven_id, inven}
end
table.sort(invens, function(a,b) return (a[2].worn and 1 or 0) > (b[2].worn and 1 or 0) end)
for i = 1, #invens do
local inven_id, inven = unpack(invens[i])
local o, item = self:findInInventory(inven, name, {no_count=true, force_id=true, no_add_name=true})
if o and item then os[#os+1] = {o, item, inven_id, inven} end
end
if #os == 0 then return end
table.sort(os, function(a, b) return (a[4].use_speed or 1) < (b[4].use_speed or 1) end)
return os[1][1], os[1][2], os[1][3]
end
local o, item, inven = find(name)
if not o then
Dialog:simplePopup("Item not found", "You do not have any "..name..".")
else
self:playerUseItem(o, item, inven)
end
end
function _M:doDrop(inven, item, on_done)
if game.zone.wilderness then
Dialog:yesnoLongPopup("Warning", "You cannot drop items on the world map.\nIf you drop it, it will be lost forever.", 300, function(ret)
-- The test is reversed because the buttons are reversed, to prevent mistakes
if not ret then
local o = self:removeObject(inven, item, true)
game.logPlayer(self, "You destroy %s.", o:getName{do_colour=true, do_count=true})
self:sortInven()
self:useEnergy()
if on_done then on_done() end
end
end, "Cancel", "Destroy")
return
end
if on_done then on_done() end
local ro = self:wearObject(o, true, true)
if ro then
if self:takeoffObject(inven, item) then
self:addObject(self.INVEN_INVEN, o)
end
function _M:getEncumberTitleUpdator(title)
return function()
local enc, max = self:getEncumbrance(), self:getMaxEncumbrance()
if enc > max then color = "#ff0000#"
elseif enc > max * 0.9 then color = "#ff8a00#"
elseif enc > max * 0.75 then color = "#fcff00#"
end
-- If 2 or more objects, display a pickup dialog, otherwise just picks up
local titleupdator = self:getEncumberTitleUpdator("Pickup")
local d d = self:showPickupFloor(titleupdator(), nil, function(o, item)
dg
committed
local o = self:pickupFloor(item, true)
d:updateTitle(titleupdator())
dg
committed
local o = self:pickupFloor(1, true)
if o and type(o) == "table" then
self:useEnergy()
o.__new_pickup = true
end
self.changed = true
end
end
function _M:playerDrop()
local inven = self:getInven(self.INVEN_INVEN)
local titleupdator = self:getEncumberTitleUpdator("Drop object")
local d d = self:showInventory(titleupdator(), inven, nil, function(o, item)
d:updateTitle(titleupdator())
end)
end
function _M:playerWear()
local inven = self:getInven(self.INVEN_INVEN)
local titleupdator = self:getEncumberTitleUpdator("Wield/wear object")
local d d = self:showInventory(titleupdator(), inven, function(o)
return o:wornInven() and self:getInven(o:wornInven()) and true or false
d:updateTitle(titleupdator())
local titleupdator = self:getEncumberTitleUpdator("Take off object")
local d d = self:showEquipment(titleupdator(), nil, function(o, inven, item)
d:updateTitle(titleupdator())
if game.zone.wilderness then game.logPlayer(self, "You cannot use items on the world map.") return end
if not o then return end
local co = coroutine.create(function()
self.changed = true
-- Count magic devices
if (o.power_source and o.power_source.arcane) and self:attr("forbid_arcane") then
game.logPlayer(self, "Your antimagic disrupts %s.", o:getName{no_count=true, do_color=true})
return true
end
local ret = o:use(self, nil, inven, item) or {}
if not ret.used then return end
if ret.id then
if ret.destroy then
dg
committed
if o.multicharge and o.multicharge > 1 then
o.multicharge = o.multicharge - 1
dg
committed
local _, del = self:removeObject(self:getInven(inven), item)
if del then
game.log("You have no more %s.", o:getName{no_count=true, do_color=true})
dg
committed
game.log("You have %s.", o:getName{do_color=true})
dg
committed
self:sortInven(self:getInven(inven))
self:breakStealth()
self:breakLightningSpeed()
self.changed = true
end)
local ok, ret = coroutine.resume(co)
if not ok and ret then print(debug.traceback(co)) error(ret) end
return true
local titleupdator = self:getEncumberTitleUpdator("Use object")
function _M:quickSwitchWeapons()
local mh1, mh2 = self.inven[self.INVEN_MAINHAND], self.inven[self.INVEN_QS_MAINHAND]
local oh1, oh2 = self.inven[self.INVEN_OFFHAND], self.inven[self.INVEN_QS_OFFHAND]
local pf1, pf2 = self.inven[self.INVEN_PSIONIC_FOCUS], self.inven[self.INVEN_QS_PSIONIC_FOCUS]
if not mh1 or not mh2 or not oh1 or not oh2 then return end
local mhset1, mhset2 = {}, {}
local ohset1, ohset2 = {}, {}
local pfset1, pfset2 = {}, {}
-- Remove them all
for i = #mh1, 1, -1 do mhset1[#mhset1+1] = self:removeObject(mh1, i, true) end
for i = #mh2, 1, -1 do mhset2[#mhset2+1] = self:removeObject(mh2, i, true) end
for i = #oh1, 1, -1 do ohset1[#ohset1+1] = self:removeObject(oh1, i, true) end
for i = #oh2, 1, -1 do ohset2[#ohset2+1] = self:removeObject(oh2, i, true) end
if pf1 and pf2 then
for i = #pf1, 1, -1 do pfset1[#pfset1+1] = self:removeObject(pf1, i, true) end
for i = #pf2, 1, -1 do pfset2[#pfset2+1] = self:removeObject(pf2, i, true) end
end
-- Put them all back
for i = 1, #mhset1 do self:addObject(mh2, mhset1[i]) end
for i = 1, #mhset2 do self:addObject(mh1, mhset2[i]) end
for i = 1, #ohset1 do self:addObject(oh2, ohset1[i]) end
for i = 1, #ohset2 do self:addObject(oh1, ohset2[i]) end
if pf1 and pf2 then
for i = 1, #pfset1 do self:addObject(pf2, pfset1[i]) end
for i = 1, #pfset2 do self:addObject(pf1, pfset2[i]) end
end
if not self:knowTalent(self.T_CELERITY) then self:useEnergy() end
if pf1 and pf2 then
if not pf1[1] then
if mh1[1] and oh1[1] then names = mh1[1]:getName{do_color=true}.." and "..oh1[1]:getName{do_color=true}
elseif mh1[1] and not oh1[1] then names = mh1[1]:getName{do_color=true}
elseif not mh1[1] and oh1[1] then names = oh1[1]:getName{do_color=true}
end
else
if mh1[1] and oh1[1] then names = mh1[1]:getName{do_color=true}.." and "..oh1[1]:getName{do_color=true}.." and "..pf1[1]:getName{do_color=true}
elseif mh1[1] and not oh1[1] then names = mh1[1]:getName{do_color=true}.." and "..pf1[1]:getName{do_color=true}
elseif not mh1[1] and oh1[1] then names = oh1[1]:getName{do_color=true}.." and "..pf1[1]:getName{do_color=true}
end
end
else
if mh1[1] and oh1[1] then names = mh1[1]:getName{do_color=true}.." and "..oh1[1]:getName{do_color=true}
elseif mh1[1] and not oh1[1] then names = mh1[1]:getName{do_color=true}
elseif not mh1[1] and oh1[1] then names = oh1[1]:getName{do_color=true}
end
self.changed = true
-- Go through all sustained talents and turn them off if pre_use fails
function _M:playerCheckSustains()
for tid, _ in pairs(self.talents) do
local t = self:getTalentFromId(tid)
if t.mode == "sustained" and self:isTalentActive(t.id) then
-- handles unarmed
if t.is_unarmed and (self:hasMassiveArmor() or not self:isUnarmed()) then
self:forceUseTalent(tid, {ignore_energy=true})
end
-- handles pre_use checks
if t.on_pre_use and not t.on_pre_use(self, t, silent, fake) then
self:forceUseTalent(tid, {ignore_energy=true})
end
end
end
end
function _M:playerLevelup(on_finish, on_birth)
local ds = LevelupDialog.new(self, on_finish, on_birth)
dg
committed
game:registerDialog(ds)
--- Use a portal with the orb of many ways
function _M:useOrbPortal(portal)
if spotHostiles(self) then game.logPlayer(self, "You can not use the Orb with foes in sight.") return end
if portal.on_preuse then portal:on_preuse(self) end
if portal.nothing then -- nothing
elseif portal.teleport_level then
local x, y = util.findFreeGrid(portal.teleport_level.x, portal.teleport_level.y, 2, true, {[Map.ACTOR]=true})
if x and y then self:move(x, y, true) end
else
if portal.change_wilderness then
game:onLevelLoad(portal.change_wilderness.level_name or (portal.change_zone.."-"..portal.change_level), function(zone, level, spot)
local spot = level:pickSpot(spot)
game.player.wild_x = spot and spot.x or 0
game.player.wild_y = spot and spot.y or 0
end, portal.change_wilderness.spot)
else
self.wild_x = portal.change_wilderness.x or 0
self.wild_y = portal.change_wilderness.y or 0
end
end
game:changeLevel(portal.change_level, portal.change_zone)
if portal.after_zone_teleport then
self:move(portal.after_zone_teleport.x, portal.after_zone_teleport.y, true)
for e, _ in pairs(game.party.members) do if e ~= self then
local x, y = util.findFreeGrid(portal.after_zone_teleport.x, portal.after_zone_teleport.y, 10, true, {[Map.ACTOR]=true})
if x then e:move(x, y, true) end
end end
end
if portal.message then game.logPlayer(self, portal.message) end
self.energy.value = self.energy.value + game.energy_to_act
--- Use the orbs of command
function _M:useCommandOrb(o)
local g = game.level.map(self.x, self.y, Map.TERRAIN)
if not g then return end
if not g.define_as or not o.define_as or o.define_as ~= g.define_as then
game.logPlayer(self, "This does not seem to have any effect.")
return
end
dg
committed
if g.orb_command then
g.orb_command:special(self)
return
end
game.logPlayer(self, "You use the %s on the pedestal. There is a distant 'clonk' sound.", o:getName{do_colour=true})
self:grantQuest("orb-command")
self:setQuestStatus("orb-command", engine.Quest.COMPLETED, o.define_as)