Skip to content
Snippets Groups Projects
Player.lua 35.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • dg's avatar
    dg committed
    -- ToME - Tales of Maj'Eyal
    
    dg's avatar
    dg committed
    -- Copyright (C) 2009, 2010, 2011 Nicolas Casalini
    
    dg's avatar
    dg committed
    --
    -- 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
    
    
    dg's avatar
    dg committed
    require "engine.class"
    
    dg's avatar
    dg committed
    require "mod.class.Actor"
    
    dg's avatar
    dg committed
    require "engine.interface.PlayerRest"
    
    dg's avatar
    dg committed
    require "engine.interface.PlayerRun"
    
    dg's avatar
    dg committed
    require "engine.interface.PlayerHotkeys"
    
    dg's avatar
    dg committed
    require "engine.interface.PlayerSlide"
    
    require "engine.interface.PlayerMouse"
    
    require "mod.class.interface.PlayerStats"
    
    require "mod.class.interface.PlayerLore"
    
    dg's avatar
    dg committed
    require "mod.class.interface.PlayerDumpJSON"
    
    require "mod.class.interface.PartyDeath"
    
    dg's avatar
    dg committed
    local Map = require "engine.Map"
    
    dg's avatar
    dg committed
    local Dialog = require "engine.ui.Dialog"
    
    local ActorTalents = require "engine.interface.ActorTalents"
    
    local LevelupDialog = require "mod.dialogs.LevelupDialog"
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    --- Defines the player for ToME
    -- It is a normal actor, with some redefined methods to handle user interaction.<br/>
    
    dg's avatar
    dg committed
    -- It is also able to run and rest and use hotkeys
    
    dg's avatar
    dg committed
    module(..., package.seeall, class.inherit(
    	mod.class.Actor,
    	engine.interface.PlayerRest,
    
    dg's avatar
    dg committed
    	engine.interface.PlayerRun,
    
    dg's avatar
    dg committed
    	engine.interface.PlayerHotkeys,
    
    	engine.interface.PlayerSlide,
    
    	mod.class.interface.PlayerStats,
    
    dg's avatar
    dg committed
    	mod.class.interface.PlayerLore,
    
    	mod.class.interface.PlayerDumpJSON,
    	mod.class.interface.PartyDeath
    
    dg's avatar
    dg committed
    ))
    
    dg's avatar
    dg committed
    
    
    -- Allow character registration even after birth
    allow_late_uuid = true
    
    
    dg's avatar
    dg committed
    function _M:init(t, no_default)
    
    dg's avatar
    dg committed
    	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"
    
    dg's avatar
    dg committed
    	t.player = true
    
    	if type(t.open_door) == "nil" then t.open_door = true end
    
    dg's avatar
    dg committed
    	t.type = t.type or "humanoid"
    	t.subtype = t.subtype or "player"
    	t.faction = t.faction or "players"
    
    dg's avatar
    dg committed
    
    
    	t.ai_state = t.ai_state or {talent_in=1, ai_move="move_astar"}
    
    dg's avatar
    dg committed
    	if t.fixed_rating == nil then t.fixed_rating = true end
    
    dg's avatar
    dg committed
    
    
    	-- Dont give free resists & higher stat max to players
    
    	t.resists_cap = t.resists_cap or {}
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	t.lite = t.lite or 0
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	t.rank = t.rank or 3
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	mod.class.Actor.init(self, t, no_default)
    	engine.interface.PlayerHotkeys.init(self, t)
    
    	mod.class.interface.PlayerLore.init(self, t)
    
    dg's avatar
    dg committed
    	self.descriptor = self.descriptor or {}
    	self.died_times = self.died_times or {}
    
    	self.last_learnt_talents = { class={}, generic={} }
    
    dg's avatar
    dg committed
    end
    
    
    function _M:onBirth(birther)
    	-- Make a list of random escort levels
    	local race_def = birther.birth_descriptor_def.race[self.descriptor.race]
    
    dg's avatar
    dg committed
    	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
    
    dg's avatar
    dg committed
    		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)
    
    dg's avatar
    dg committed
    	-- Save where we entered
    
    	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)
    
    dg's avatar
    dg committed
    
    
    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)
    
    dg's avatar
    dg committed
    	if target.on_encounter then
    
    		if x and y and game.level.map(x, y, Map.ACTOR) == target then
    			game.level.map:remove(x, y, Map.ACTOR)
    		end
    
    dg's avatar
    dg committed
    		game.state:handleWorldEncounter(target)
    	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's avatar
    dg committed
    function _M:move(x, y, force)
    
    	local ox, oy = self.x, self.y
    
    dg's avatar
    dg committed
    	local moved = mod.class.Actor.move(self, x, y, force)
    
    dg's avatar
    dg committed
    	if moved then
    
    		game.level.map:moveViewSurround(self.x, self.y, config.settings.tome.scroll_dist, config.settings.tome.scroll_dist)
    
    dg's avatar
    dg committed
    		game.level.map.attrs(self.x, self.y, "walked", true)
    
    dg's avatar
    dg committed
    
    
    		if self.describeFloor then self:describeFloor(self.x, self.y) end
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    --	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
    
    
    dg's avatar
    dg committed
    	-- Update wilderness coords
    
    	if game.zone.wilderness and not force then
    
    dg's avatar
    dg committed
    		-- Cheat with time
    		game.turn = game.turn + 1000
    
    dg's avatar
    dg committed
    		self.wild_x, self.wild_y = self.x, self.y
    
    		if self.x ~= ox or self.y ~= oy then
    			game.state:worldDirectorAI()
    		end
    
    dg's avatar
    dg committed
    	end
    
    
    dg's avatar
    dg committed
    	-- Update zone name
    	if game.zone.variable_zone_name then game:updateZoneName() end
    
    
    dg's avatar
    dg committed
    	return moved
    end
    
    function _M:act()
    
    dg's avatar
    dg committed
    	if not mod.class.Actor.act(self) then return end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	-- 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
    
    
    dg's avatar
    dg committed
    	-- Clean log flasher
    
    dg's avatar
    dg committed
    
    
    	-- 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
    
    
    dg's avatar
    dg committed
    	-- Resting ? Running ? Otherwise pause
    
    dg's avatar
    dg committed
    	if not self:restStep() and not self:runStep() and self.player then
    
    dg's avatar
    dg committed
    		game.paused = true
    
    	elseif not self.player then
    		self:useEnergy()
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    
    --- 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)
    
    dg's avatar
    dg committed
    		elseif self:attr("lightning_speed") then game.fbo_shader:setUniform("motionblur", 2)
    
    		else game.fbo_shader:setUniform("motionblur", 0) -- Disable
    		end
    	end
    end
    
    
    dg's avatar
    dg committed
    -- Precompute FOV form, for speed
    
    local fovdist = {}
    for i = 0, 30 * 30 do
    
    dg's avatar
    dg committed
    	fovdist[i] = math.max((20 - math.sqrt(i)) / 17, 0.6)
    
    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
    
    dg's avatar
    dg committed
    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
    
    
    dg's avatar
    dg committed
    	-- Compute ESP FOV, using cache
    
    dg's avatar
    dg committed
    	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
    
    dg's avatar
    dg committed
    
    	-- 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)
    
    dg's avatar
    dg committed
    				game.level.map:updateMap(x, y)
    
    dg's avatar
    dg committed
    				ok = true
    			end
    
    			if ok then
    
    dg's avatar
    dg committed
    				if self.detect_function then self.detect_function(self, x, y) end
    
    				game.level.map.seens(x, y, 0.6)
    
    dg's avatar
    dg committed
    			end
    		end, true, true, true)
    	end
    
    	-- 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)
    
    dg's avatar
    dg committed
    				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
    
    dg's avatar
    dg committed
    
    	-- 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
    
    				game.level.map.seens(x, y, 0.6)
    
    dg's avatar
    dg committed
    			end
    		end, true, true, true)
    
    		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
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    
    
    	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
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    function _M:doFOV()
    	self:playerFOV()
    end
    
    
    --- 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
    
    
    dg's avatar
    dg committed
    --- 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")
    
    dg's avatar
    dg committed
    	local ret = mod.class.Actor.onTakeHit(self, value, src)
    
    	if self.life < self.max_life * 0.3 then
    
    dg's avatar
    dg committed
    		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
    
    
    dg's avatar
    dg committed
    	return ret
    
    dg's avatar
    dg committed
    end
    
    
    function _M:heal(value, src)
    	-- Difficulty settings
    	if game.difficulty == game.DIFFICULTY_EASY then
    
    	end
    
    	mod.class.Actor.heal(self, value, src)
    end
    
    
    dg's avatar
    dg committed
    function _M:die(src, death_note)
    
    dg's avatar
    dg committed
    	self:runStop("died")
    	self:restStop("died")
    
    
    dg's avatar
    dg committed
    	return self:onPartyDeath(src, death_note)
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    --- Suffocate a bit, lose air
    
    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
    
    dg's avatar
    dg committed
    		self:runStop("suffocating")
    		self:restStop("suffocating")
    	end
    	return dead, affected
    end
    
    
    function _M:onChat()
    	self:runStop("chat started")
    	self:restStop("chat started")
    end
    
    
    dg's avatar
    dg committed
    function _M:setName(name)
    	self.name = name
    
    dg's avatar
    dg committed
    	game.save_name = name
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    --- Notify the player of available cooldowns
    function _M:onTalentCooledDown(tid)
    
    dg's avatar
    dg committed
    	if not self:knowTalent(tid) then return end
    
    dg's avatar
    dg committed
    	local t = self:getTalentFromId(tid)
    
    	local x, y = game.level.map:getTileToScreen(self.x, self.y)
    
    dg's avatar
    dg committed
    	game.flyers:add(x, y, 30, -0.3, -3.5, ("%s available"):format(t.name:capitalize()), {0,255,00})
    
    dg's avatar
    dg committed
    	game.log("#00ff00#%sTalent %s is ready to use.", (t.display_entity and t.display_entity:getDisplayString() or ""), t.name)
    
    dg's avatar
    dg committed
    --- 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
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- Sets the current target
    function _M:setTarget(target)
    
    	return game:targetSetForPlayer(target)
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    local function spotHostiles(self)
    
    dg's avatar
    dg committed
    	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's avatar
    dg committed
    		local actor = game.level.map(x, y, game.level.map.ACTOR)
    
    		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
    
    dg's avatar
    dg committed
    	end, nil)
    
    dg's avatar
    dg committed
    	return seen
    end
    
    --- Can we continue resting ?
    
    -- 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's avatar
    dg committed
    function _M:restCheck()
    
    	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
    
    dg's avatar
    dg committed
    
    
    	for act, def in pairs(game.party.members) do if game.level:hasEntity(act) and not act.dead then
    
    dg's avatar
    dg committed
    		local perc = math.min(self.resting.cnt / 10, 8)
    
    		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)
    
    dg's avatar
    dg committed
    	-- 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
    
    dg's avatar
    dg committed
    
    
    	-- 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
    
    				if cd > 0 then return true end
    			end
    
    dg's avatar
    dg committed
    	return false, "all resources and life at maximum"
    
    dg's avatar
    dg committed
    end
    
    dg's avatar
    dg committed
    
    --- Can we continue running?
    
    dg's avatar
    dg committed
    -- 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)
    
    	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
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	if self.air_regen < 0 then return false, "losing breath!" end
    
    dg's avatar
    dg committed
    	-- Notice any noticeable terrain
    
    dg's avatar
    dg committed
    	local noticed = false
    
    dg's avatar
    dg committed
    	self:runScan(function(x, y, what)
    
    dg's avatar
    dg committed
    		-- Objects are always interesting, only on curent spot
    
    dg's avatar
    dg committed
    		if what == "self" and not game.level.map.attrs(x, y, "obj_seen") then
    
    dg's avatar
    dg committed
    			local obj = game.level.map:getObject(x, y, 1)
    
    dg's avatar
    dg committed
    			if obj then
    				noticed = "object seen"
    
    dg's avatar
    dg committed
    				if not ignore_memory then game.level.map.attrs(x, y, "obj_seen", true) end
    				return
    
    dg's avatar
    dg committed
    			end
    
    dg's avatar
    dg committed
    		end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    		-- 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
    
    dg's avatar
    dg committed
    	end)
    	if noticed then return false, noticed end
    
    	return engine.interface.PlayerRun.runCheck(self)
    end
    
    dg's avatar
    dg committed
    
    
    --- Move with the mouse
    -- We just feed our spotHostile to the interface mouseMove
    
    function _M:mouseMove(tmx, tmy, force_move)
    
    dg's avatar
    dg committed
    	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
    
    dg's avatar
    dg committed
    
    		-- 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
    
    dg's avatar
    dg committed
    		return true
    	end
    
    	return engine.interface.PlayerMouse.mouseMove(self, tmx, tmy, spotHostiles, {recheck=true, astar_check=astar_check}, force_move)
    
    dg's avatar
    dg committed
    --- Called after running a step
    function _M:runMoved()
    	self:playerFOV()
    end
    
    
    --- Called after stopping running
    function _M:runStopped()
    	self:playerFOV()
    end
    
    
    dg's avatar
    dg committed
    --- Activates a hotkey with a type "inventory"
    function _M:hotkeyInventory(name)
    	local find = function(name)
    		local os = {}
    
    		-- Sort invens, use worn first
    		local invens = {}
    
    dg's avatar
    dg committed
    		for inven_id, inven in pairs(self.inven) do
    
    			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])
    
    dg's avatar
    dg committed
    			local o, item = self:findInInventory(inven, name, {no_count=true, force_id=true, no_add_name=true})
    
    dg's avatar
    dg committed
    			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
    
    dg's avatar
    dg committed
    		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()
    
    			end
    		end, "Cancel", "Destroy")
    		return
    	end
    
    dg's avatar
    dg committed
    	self:dropFloor(inven, item, true, true)
    
    	self:sortInven(inven)
    
    dg's avatar
    dg committed
    	self:useEnergy()
    
    dg's avatar
    dg committed
    	self.changed = true
    
    dg's avatar
    dg committed
    end
    
    function _M:doWear(inven, item, o)
    
    dg's avatar
    dg committed
    	self:removeObject(inven, item, true)
    
    dg's avatar
    dg committed
    	local ro = self:wearObject(o, true, true)
    	if ro then
    
    dg's avatar
    dg committed
    		if type(ro) == "table" then self:addObject(inven, ro) end
    
    	elseif not ro then
    
    dg's avatar
    dg committed
    		self:addObject(inven, o)
    
    dg's avatar
    dg committed
    	end
    	self:sortInven()
    	self:useEnergy()
    
    	self:playerCheckSustains()
    
    dg's avatar
    dg committed
    	self.changed = true
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    function _M:doTakeoff(inven, item, o, simple)
    
    dg's avatar
    dg committed
    	if self:takeoffObject(inven, item) then
    		self:addObject(self.INVEN_INVEN, o)
    	end
    
    dg's avatar
    dg committed
    	if not simple then
    		self:sortInven()
    		self:useEnergy()
    	end
    
    	self:playerCheckSustains()
    
    dg's avatar
    dg committed
    	self.changed = true
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    function _M:getEncumberTitleUpdator(title)
    	return function()
    		local enc, max = self:getEncumbrance(), self:getMaxEncumbrance()
    
    		local color = "#00ff00#"
    
    		if enc > max then color = "#ff0000#"
    		elseif enc > max * 0.9 then color = "#ff8a00#"
    		elseif enc > max * 0.75 then color = "#fcff00#"
    		end
    
    dg's avatar
    dg committed
    		return ("%s - %sEncumberance %d/%d"):format(title, color, enc, max)
    
    dg's avatar
    dg committed
    	end
    end
    
    
    dg's avatar
    dg committed
    function _M:playerPickup()
    
    dg's avatar
    dg committed
    	-- If 2 or more objects, display a pickup dialog, otherwise just picks up
    
    dg's avatar
    dg committed
    	if game.level.map:getObject(self.x, self.y, 2) then
    
    dg's avatar
    dg committed
    		local titleupdator = self:getEncumberTitleUpdator("Pickup")
    		local d d = self:showPickupFloor(titleupdator(), nil, function(o, item)
    
    dg's avatar
    dg committed
    			if o and type(o) == "table" then o.__new_pickup = true end
    
    dg's avatar
    dg committed
    			self.changed = true
    
    			d:updateTitle(titleupdator())
    
    dg's avatar
    dg committed
    			d:used()
    
    dg's avatar
    dg committed
    		end)
    	else
    
    dg's avatar
    dg committed
    		self:sortInven()
    
    		if o and type(o) == "table" then
    			self:useEnergy()
    			o.__new_pickup = true
    		end
    		self.changed = true
    
    dg's avatar
    dg committed
    	end
    end
    
    function _M:playerDrop()
    	local inven = self:getInven(self.INVEN_INVEN)
    
    dg's avatar
    dg committed
    	local titleupdator = self:getEncumberTitleUpdator("Drop object")
    
    	local d d = self:showInventory(titleupdator(), inven, nil, function(o, item)
    
    dg's avatar
    dg committed
    		self:doDrop(inven, item)
    
    		d:updateTitle(titleupdator())
    
    dg's avatar
    dg committed
    		return true
    
    dg's avatar
    dg committed
    	end)
    end
    
    function _M:playerWear()
    	local inven = self:getInven(self.INVEN_INVEN)
    
    dg's avatar
    dg committed
    	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
    
    dg's avatar
    dg committed
    	end, function(o, item)
    
    dg's avatar
    dg committed
    		self:doWear(inven, item, o)
    
    		d:updateTitle(titleupdator())
    
    dg's avatar
    dg committed
    		return true
    
    dg's avatar
    dg committed
    	end)
    end
    
    function _M:playerTakeoff()
    
    dg's avatar
    dg committed
    	local titleupdator = self:getEncumberTitleUpdator("Take off object")
    
    	local d d = self:showEquipment(titleupdator(), nil, function(o, inven, item)
    
    dg's avatar
    dg committed
    		self:doTakeoff(inven, item, o)
    
    		d:updateTitle(titleupdator())
    
    dg's avatar
    dg committed
    		return true
    
    dg's avatar
    dg committed
    	end)
    end
    
    
    dg's avatar
    dg committed
    function _M:playerUseItem(object, item, inven)
    
    dg's avatar
    dg committed
    	if game.zone.wilderness then game.logPlayer(self, "You cannot use items on the world map.") return end
    
    dg's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    	local use_fct = function(o, inven, item)
    
    dg's avatar
    dg committed
    		local co = coroutine.create(function()
    			self.changed = true
    
    dg's avatar
    dg committed
    			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
    
    dg's avatar
    dg committed
    				o:identify(true)
    			end
    
    				if o.multicharge and o.multicharge > 1 then
    					o.multicharge = o.multicharge - 1
    
    					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's avatar
    dg committed
    					else
    
    						game.log("You have %s.", o:getName{do_color=true})
    
    dg's avatar
    dg committed
    					end
    
    				self:breakStepUp()
    
    				self:breakStealth()
    				self:breakLightningSpeed()
    
    dg's avatar
    dg committed
    				self:breakGatherTheThreads()
    
    dg's avatar
    dg committed
    				return true
    
    dg's avatar
    dg committed
    			end
    
    dg's avatar
    dg committed
    
    
    			self:breakStepUp()
    
    dg's avatar
    dg committed
    			self:breakStealth()
    
    dg's avatar
    dg committed
    			self:breakLightningSpeed()
    
    dg's avatar
    dg committed
    			self:breakGatherTheThreads()
    
    dg's avatar
    dg committed
    			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
    
    dg's avatar
    dg committed
    	if object and item then return use_fct(object, inven, item) end
    
    dg's avatar
    dg committed
    	local titleupdator = self:getEncumberTitleUpdator("Use object")
    
    dg's avatar
    dg committed
    	self:showEquipInven(titleupdator(),
    
    dg's avatar
    dg committed
    		function(o)
    			return o:canUseObject()
    		end,
    
    dg's avatar
    dg committed
    		use_fct
    
    dg's avatar
    dg committed
    	)
    end
    
    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
    
    dg's avatar
    dg committed
    	local names = ""
    
    	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
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    
    
    	self:playerCheckSustains()
    
    
    dg's avatar
    dg committed
    	game.logPlayer(self, "You switch your weapons to: %s.", names)
    
    -- 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
    
    dg's avatar
    dg committed
    			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's avatar
    dg committed
    
    
    dg's avatar
    dg committed
    --- Use a portal with the orb of many ways
    function _M:useOrbPortal(portal)
    
    dg's avatar
    dg committed
    	if portal.special then portal:special(self) return end
    
    
    dg's avatar
    dg committed
    	if spotHostiles(self) then game.logPlayer(self, "You can not use the Orb with foes in sight.") return end
    
    dg's avatar
    dg committed
    
    
    	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
    
    			if portal.change_wilderness.spot then
    
    dg's avatar
    dg committed
    				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
    
    dg's avatar
    dg committed
    	end
    
    dg's avatar
    dg committed
    	if portal.message then game.logPlayer(self, portal.message) end
    
    dg's avatar
    dg committed
    	if portal.on_use then portal:on_use(self) end
    
    dg's avatar
    dg committed
    	self.energy.value = self.energy.value + game.energy_to_act
    
    dg's avatar
    dg committed
    end
    
    
    dg's avatar
    dg committed
    --- 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's avatar
    dg committed
    	game.logPlayer(self, "You use the %s on the pedestal. There is a distant 'clonk' sound.", o:getName{do_colour=true})
    
    dg's avatar
    dg committed
    	self:grantQuest("orb-command")
    	self:setQuestStatus("orb-command", engine.Quest.COMPLETED, o.define_as)