Newer
Older
-- TE4 - T-Engine 4
--
-- 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.class"
require "engine.KeyBind"
local Map = require "engine.Map"
local Target = require "engine.Target"
--- Handles default targeting interface & display
-- @classmod engine.generator.interface.GameTargeting
module(..., package.seeall, class.make)
--- Initializes targeting
function _M:init()
self.target = Target.new(Map, self.player)
self.target.target.entity = self.player
self.old_tmx, self.old_tmy = 0, 0
self.target_style = "lock"
-- Allow scrolling when targetting
self.target.on_set_target = function(self, how)
if self.target and self.target_type and self.target.x and self.active and self.target_type.stop_before_target and how == "scan" then
local start_x = self.target_type.start_x or self.target_type.x or self.target_type.source_actor and self.target_type.source_actor.x or self.x
local start_y = self.target_type.start_y or self.target_type.y or self.target_type.source_actor and self.target_type.source_actor.y or self.y
local l = core.fov.line(self.target.x, self.target.y, start_x, start_y)
local lx, ly = l:step()
if lx and ly then
self.target.x = lx
self.target.y = ly
self.target.entity = game.level.map(self.target.x, self.target.y, engine.Map.ACTOR)
end
end
if self.key ~= self.targetmode_key then return end
local dx, dy = game.level.map:moveViewSurround(self.target.x, self.target.y, 1, 1, true)
DarkGod
committed
if how == "mouse" and (dx ~= 0 or dy ~= 0) then
local cx, cy = core.mouse.get()
core.mouse.set(cx - game.level.map.tile_w * dx, cy - game.level.map.tile_h * dy)
end
end
end
--- Maintain the current target each tick
-- Make sure the target still exists
function _M:targetOnTick()
if self.target.target.entity and not self.level:hasEntity(self.target.target.entity) then self.target.target.entity = false end
end
--- Display the tooltip, if any
function _M:targetDisplayTooltip(dx, dy, force, nb_keyframes)
-- Tooltip is displayed over all else
if self.level and self.level.map and self.level.map.finished then
-- Display a tooltip if available
if self.tooltip_x then
if type(self.tooltip_x) == "table" then
self.tooltip:toScreen(self.tooltip.last_display_x, self.tooltip.last_display_y, nb_keyframes)
tmx, tmy = self.level.map:getMouseTile(self.tooltip_x , self.tooltip_y)
self.tooltip:displayAtMap(tmx, tmy, dx, dy, nil, force, nb_keyframes)
end
-- Move target around
if self.old_tmx ~= tmx or self.old_tmy ~= tmy then
DarkGod
committed
if not (self.target_mode and self.target_no_move_tooltip) then
self.target.target.x, self.target.target.y = tmx, tmy
end
end
self.old_tmx, self.old_tmy = tmx, tmy
end
end
--- Forces the tooltip to pop with the given text
function _M:tooltipDisplayAtMap(x, y, text, extra, force, nb_keyframes)
self.tooltip:displayAtMap(nil, nil, x, y, text, force, nb_keyframes)
if extra and type(extra) == "table" then
if extra.up then self.tooltip.last_display_y = self.tooltip.last_display_y - self.tooltip.h end
end
--- Forces to hide the tooltip
function _M:tooltipHide()
self.tooltip_x = nil
end
--- Enter/leave targeting mode
-- This is the "meat" of this interface, do not expect to understand it easily, it mixes some nasty stuff
-- This require the Game to have both a "key" field (this is the default) and a "normal_key" field<br/>
-- It will switch over to a special keyhandler and then restore the "normal_key" one
function _M:targetMode(v, msg, co, typ)
local old = self.target_mode
self.target_mode = v
if not v then
Map:setViewerFaction((self.always_target == true or self.always_target == "old") and self.player.faction or nil)
if msg then self.log(type(msg) == "string" and msg or _t"Tactical display disabled. Press shift+'t' to enable.") end
self.level.map.changed = true
self.targetmode_trigger_hotkey = nil
self.target:setActive(false)
if tostring(old) == "exclusive" then
local x, y, e = self.target.target.x, self.target.target.y, self.target.target.entity
dg
committed
local fct = function(notok)
if notok then
self.target.target.entity = nil
self.target.target.x = nil
self.target.target.y = nil
end
self.key = self.normal_key
self.key:setCurrent()
if self.target_co then
local co = self.target_co
self.target_co = nil
self.target.target.x, self.target.target.y, self.target.target.entity = x, y, e
local ok, err = coroutine.resume(co, self.target.target.x, self.target.target.y, self.target.target.entity)
if not ok and err then print(debug.traceback(co)) error(err) end
end
end
if self.target_warning and self.target.target.x == self.player.x and self.target.target.y == self.player.y then
Dialog:yesnoPopup(type(self.target_warning) == "string" and self.target_warning or _t"Target yourself?", _t"Are you sure you want to target yourself?", fct, _t"No", _t"Yes", nil, true)
dg
committed
fct(false)
end
end
else
Map:setViewerFaction(self.player.faction)
if msg then self.log(type(msg) == "string" and msg or _t"Tactical display enabled. Press shift+'t' to disable.") end
self.level.map.changed = true
self.target:setActive(true, typ)
self.target_style = "lock"
self.target_warning = true
DarkGod
committed
self.target_no_move_tooltip = true
if type(typ) == "table" and typ.no_move_tooltip then
self.target_no_move_tooltip = true
end
if type(typ) == "table" and typ.talent then
self.target_warning = typ.talent.name
elseif type(typ) == "table" and typ.__name then
self.target_warning = typ.__name
end
-- Exclusive mode means we disable the current key handler and use a specific one
-- that only allows targetting and resumes talent coroutine when done
if tostring(v) == "exclusive" then
self.target_co = co
self.key = self.targetmode_key
self.key:setCurrent()
local do_scan = true
DarkGod
committed
if type(typ) == "table" and typ.no_start_scan then
do_scan = false
else
if self.target_no_start_scan
or (
self.target.target.entity and
self.level.map.seens(self.target.target.entity.x, self.target.target.entity.y) and
self.player ~= self.target.target.entity
) then
DarkGod
committed
if type(typ) == "table" and typ.first_target ~= "friend" and self.target.target and self.target.target.entity and self.player:reactionToward(self.target.target.entity) >= 0 then
else
do_scan = false
end
end
end
if do_scan then
local filter = nil
if typ.custom_scan_filter then
filter = typ.custom_scan_filter
elseif not (type(typ) == "table" and typ.no_first_target_filter) then
DarkGod
committed
if type(typ) == "table" and typ.first_target and typ.first_target == "friend" then
filter = function(a) return self.player:reactionToward(a) >= 0 end
else
filter = function(a) return self.player:reactionToward(a) < 0 end
end
DarkGod
committed
self.target:scan(5, nil, self.player.x, self.player.y, filter, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on)
if self.target.target.entity and self.target.target.entity.x and self.target.target.entity.y then self.target.target.x, self.target.target.y = self.target.target.entity.x, self.target.target.entity.y end
end
if self.target.target.x then
self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y)
end
end
end
function _M:targetTriggerHotkey(i)
self.targetmode_trigger_hotkey = i
end
--- This setups the default keybindings for targeting
function _M:targetSetupKey()
local accept = function() self:targetMode(false, false) self.tooltip_x, self.tooltip_y = nil, nil end
self.targetmode_key = engine.KeyBind.new()
self.targetmode_key:addCommands{ _SPACE=accept, [{"_SPACE","ctrl"}]=accept, [{"_RETURN","ctrl"}]=accept, [{"_KP_ENTER","ctrl"}]=accept }
if engine.interface and engine.interface.PlayerHotkeys then
engine.interface.PlayerHotkeys:bindAllHotkeys(self.targetmode_key, function(i)
if self.targetmode_trigger_hotkey == i then accept() end
end)
end
self.targetmode_key:addBinds
{
TACTICAL_DISPLAY = accept,
ACCEPT = accept,
EXIT = function()
self.target.target.entity = nil
self.target.target.x = nil
self.target.target.y = nil
self:targetMode(false, false)
self.tooltip_x, self.tooltip_y = nil, nil
end,
-- Targeting movement
RUN_LEFT = function() self.target:freemove(4) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
RUN_RIGHT = function() self.target:freemove(6) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
RUN_UP = function() self.target:freemove(8) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
RUN_DOWN = function() self.target:freemove(2) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
RUN_LEFT_DOWN = function() self.target:freemove(1) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
RUN_RIGHT_DOWN = function() self.target:freemove(3) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
RUN_LEFT_UP = function() self.target:freemove(7) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
RUN_RIGHT_UP = function() self.target:freemove(9) self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
DarkGod
committed
MOVE_LEFT = function() if self.target_style == "lock" then self.target:scan(4, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(4, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(4) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT = function() if self.target_style == "lock" then self.target:scan(6, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(6, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(6) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_UP = function() if self.target_style == "lock" then self.target:scan(8, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(8, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(8) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_DOWN = function() if self.target_style == "lock" then self.target:scan(2, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(2, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(2) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_LEFT_DOWN = function() if self.target_style == "lock" then self.target:scan(1, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(1, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(1) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT_DOWN = function() if self.target_style == "lock" then self.target:scan(3, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(3, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(3) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_LEFT_UP = function() if self.target_style == "lock" then self.target:scan(7, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(7, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(7) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_RIGHT_UP = function() if self.target_style == "lock" then self.target:scan(9, nil, nil, nil, nil, self.target.target_type and type(self.target.target_type) == "table" and self.target.target_type.scan_on) elseif self.target_style == "immediate" then self.target:setDirFrom(9, self.target.target.entity or self.player) self.targetmode_key:triggerVirtual("ACCEPT") return else self.target:freemove(9) end self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) end,
MOVE_STAY = function()
if self.target_style == "immediate" then
self.target:setDirFrom(5, self.target.target.entity or self.player)
self.targetmode_key:triggerVirtual("ACCEPT")
return
end
self.target:setSpot(self.target.source_actor.x, self.target.source_actor.y, "freemove")
self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y)
end,
SCREENSHOT = function() self.normal_key:triggerVirtual("SCREENSHOT") end,
}
end
--- Handle mouse event for targeting
-- @return true if the event was handled
function _M:targetMouse(button, mx, my, xrel, yrel, event)
if not self.level then return end
-- Move tooltip
self.tooltip_x, self.tooltip_y = mx, my
local tmx, tmy = self.level.map:getMouseTile(mx, my)
self.target:setSpot(tmx, tmy, "mouse")
if self.key == self.targetmode_key then
-- Target with mouse
if button == "none" and xrel and yrel and event == "motion" then
self.target:setSpotInMotion(tmx, tmy, "mouse")
elseif button == "left" and not xrel and not yrel and event == "button" then
self:targetMode(false, false)
self.tooltip_x, self.tooltip_y = nil, nil
elseif not xrel and not yrel and event == "button" then
self.target.target.entity = nil
self.target.target.x = nil
self.target.target.y = nil
self:targetMode(false, false)
self.tooltip_x, self.tooltip_y = nil, nil
end
return true
end
end
--- Player requests a target
-- This method should be called by your Player:getTarget() method, it will handle everything
-- @param typ the targeting parameters
function _M:targetGetForPlayer(typ)
if self.target.forced then return unpack(self.target.forced) end
local msg
DarkGod
committed
self.target_no_start_scan = nil
if type(typ) == "string" then msg, typ = typ, nil
elseif type(typ) == "table" then
if typ.default_target then
self.target.target.entity = typ.default_target
DarkGod
committed
self.target_no_start_scan = true
msg = typ.msg
end
self:targetMode("exclusive", msg, coroutine.running(), typ)
if typ.nowarning then self.target_warning = false end
if self.target.target.x and config.settings.auto_accept_target and not typ.immediate_keys and (not typ.nolock or typ.can_autoaccept) and (not typ.nowarning or typ.can_autoaccept) and (not typ.no_restrict or typ.can_autoaccept) then
self.target_co = nil
self:targetMode(false, false) self.tooltip_x, self.tooltip_y = nil, nil
return self.target.target.x, self.target.target.y, self.target.target.entity
end
if typ.immediate_keys then self.target_style = "immediate" end
if typ.nolock then self.target_style = "free" end
return coroutine.yield()
end
return self.target.target.x, self.target.target.y, self.target.target.entity
end
--- Player wants to set its target
-- This method should be called by your Player:setTarget() method, it will handle everything
function _M:targetSetForPlayer(target)
self.target.target.entity = target
self.target.target.x = (type(target) == "table" and target.x) or nil
self.target.target.y = (type(target) == "table" and target.y) or nil