From 1ef77503f97aba5df13e6c721205a4d834bd5479 Mon Sep 17 00:00:00 2001 From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54> Date: Sat, 31 Jul 2010 00:52:07 +0000 Subject: [PATCH] New engine interface PlayerMouse, this handles the default mouse actions, so that modules can easily implent them. You are encouraged to switch to use it. New engine interface GameTargeting, this handles all the complex targeting code for you. You are encouraged to switch to use it. Updated both ToME and example module to the new interfaces git-svn-id: http://svn.net-core.org/repos/t-engine4@958 51575b47-30f0-44d4-a5cc-537603b46e54 --- game/engine/interface/GameTargeting.lua | 221 ++++++++++++++++++++++++ game/engine/interface/PlayerMouse.lua | 127 ++++++++++++++ game/modules/example/class/Game.lua | 179 ++----------------- game/modules/example/class/Player.lua | 61 +------ game/modules/tome/class/Game.lua | 206 ++-------------------- game/modules/tome/class/Player.lua | 68 ++------ 6 files changed, 391 insertions(+), 471 deletions(-) create mode 100644 game/engine/interface/GameTargeting.lua create mode 100644 game/engine/interface/PlayerMouse.lua diff --git a/game/engine/interface/GameTargeting.lua b/game/engine/interface/GameTargeting.lua new file mode 100644 index 0000000000..3b626849a0 --- /dev/null +++ b/game/engine/interface/GameTargeting.lua @@ -0,0 +1,221 @@ +-- TE4 - T-Engine 4 +-- Copyright (C) 2009, 2010 Nicolas Casalini +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see <http://www.gnu.org/licenses/>. +-- +-- Nicolas Casalini "DarkGod" +-- darkgod@te4.org + +require "engine.class" +require "engine.KeyBind" +local Map = require "engine.Map" +local Target = require "engine.Target" + +--- Handles default targeting interface & display +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" +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() + -- 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 self.tooltip:displayAtMap(self.level.map:getMouseTile(self.tooltip_x , self.tooltip_y)) end + + -- Move target around + if self.old_tmx ~= tmx or self.old_tmy ~= tmy then + self.target.target.x, self.target.target.y = tmx, tmy + end + self.old_tmx, self.old_tmy = tmx, tmy + end +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 and self.player.faction or nil) + if msg then self.log(type(msg) == "string" and msg or "Tactical display disabled. Press shift+'t' to enable.") end + self.level.map.changed = true + self.target:setActive(false) + + if tostring(old) == "exclusive" then + local fct = function(ok) + if not ok 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 + 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("Target yourself?", "Are you sure you want to target yourself?", fct) + else + fct(true) + end + end + else + Map:setViewerFaction(self.player.faction) + if msg then self.log(type(msg) == "string" and msg or "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 + + -- 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() + + if 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 + else + local filter = nil + 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 + self.target:scan(5, nil, self.player.x, self.player.y, filter) + 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 + +--- This setups the default keybindings for targeting +function _M:targetSetupKey() + self.targetmode_key = engine.KeyBind.new() + self.targetmode_key:addCommands{ _SPACE=function() self:targetMode(false, false) self.tooltip_x, self.tooltip_y = nil, nil end, } + self.targetmode_key:addBinds + { + TACTICAL_DISPLAY = function() + self:targetMode(false, false) + self.tooltip_x, self.tooltip_y = nil, nil + end, + ACCEPT = function() + self:targetMode(false, false) + self.tooltip_x, self.tooltip_y = nil, nil + end, + 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, + + MOVE_LEFT = function() if self.target_style == "lock" then self.target:scan(4) 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) 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) 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) 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) 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) 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) 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) 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, + } +end + +--- Handle mouse event for targeting +-- @return true if the event was handled +function _M:targetMouse(button, mx, my, xrel, yrel) + -- Move tooltip + self.tooltip_x, self.tooltip_y = mx, my + local tmx, tmy = self.level.map:getMouseTile(mx, my) + + if self.key == self.targetmode_key then + -- Target with mouse + if button == "none" and xrel and yrel then + self.target:setSpot(tmx, tmy) + -- Cancel target + elseif button ~= "left" and not xrel and not yrel then + self:targetMode(false, false) + self.tooltip_x, self.tooltip_y = nil, nil + -- Accept target + elseif not xrel and not yrel 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 + if coroutine.running() then + local msg + 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 end + msg = typ.msg + end + self:targetMode("exclusive", msg, coroutine.running(), typ) + if typ.nolock then self.target_style = "free" end + if typ.nowarning then self.target_warning = false 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 = target.x + self.target.target.y = target.y +end diff --git a/game/engine/interface/PlayerMouse.lua b/game/engine/interface/PlayerMouse.lua new file mode 100644 index 0000000000..11b9bda6ae --- /dev/null +++ b/game/engine/interface/PlayerMouse.lua @@ -0,0 +1,127 @@ +-- TE4 - T-Engine 4 +-- Copyright (C) 2009, 2010 Nicolas Casalini +-- +-- This program is free software: you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation, either version 3 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program. If not, see <http://www.gnu.org/licenses/>. +-- +-- Nicolas Casalini "DarkGod" +-- darkgod@te4.org + +require "engine.class" +local Astar = require"engine.Astar" +local DirectPath = require"engine.DirectPath" + +--- Handles player default mouse actions +-- Defines some methods to help use the mouse in an uniform way in all modules +module(..., package.seeall, class.make) + +--- Runs to the clicked mouse spot +-- if no monsters in sight it will try to make an A* path, if it fails it will do a direct path<br/> +-- if there are monsters in sight it will move one stop in the direct path direction<br/> +-- this method requires to use PlayerRun interface +-- @param tmx the coords clicked +-- @param tmy the coords clicked +-- @param spotHostiles a function taking only the player as a parameter that must return true if hostiles are in sight +function _M:mouseMove(tmx, tmy, spotHostiles) + if config.settings.tome.cheat and core.key.modState("ctrl") then + game.log("[CHEAT] teleport to %dx%d", tmx, tmy) + self:move(tmx, tmy, true) + else + -- If hostiles, attack! + if (spotHostiles and spotHostiles(self)) or math.floor(core.fov.distance(self.x, self.y, tmx, tmy)) == 1 then + local l = line.new(self.x, self.y, tmx, tmy) + local nx, ny = l() + self:move(nx or self.x, ny or self.y) + return + end + + local a = Astar.new(game.level.map, self) + local path = a:calc(self.x, self.y, tmx, tmy, true) + -- No Astar path ? jsut be dumb and try direct line + if not path then + local d = DirectPath.new(game.level.map, self) + path = d:calc(self.x, self.y, tmx, tmy, true) + end + + if path then + -- Should we just try to move in the direction, aka: attack! + if path[1] and game.level.map:checkAllEntities(path[1].x, path[1].y, "block_move", self) then self:move(path[1].x, path[1].y) return end + + -- Insert the player coords, running needs to find the player + table.insert(path, 1, {x=self.x, y=self.y}) + + -- Move along the projected A* path + self:runFollow(path) + end + end +end + +local moving_around = false +local derivx, derivy = 0, 0 + +--- Handles mouse scrolling the map +-- @param map the Map to scroll +-- @param xrel the x movement velocity, gotten from a mouse event +-- @param yrel the y movement velocity, gotten from a mouse event +function _M:mouseScrollMap(map, xrel, yrel) + derivx = derivx + xrel + derivy = derivy + yrel + map.changed = true + if derivx >= map.tile_w then + map.mx = map.mx - 1 + derivx = derivx - map.tile_w + elseif derivx <= -map.tile_w then + map.mx = map.mx + 1 + derivx = derivx + map.tile_w + end + if derivy >= map.tile_h then + map.my = map.my - 1 + derivy = derivy - map.tile_h + elseif derivy <= -map.tile_h then + map.my = map.my + 1 + derivy = derivy + map.tile_h + end + map._map:setScroll(map.mx, map.my) +end + +--- Handles global mouse event +-- This will handle events like this:<ul> +-- <li>Left click: player mouse movement</li> +-- <li>Shift + left click: map scroll</li> +-- <li>Any other click: pass on the click as a key event, to allow actiosnto be bound to the mouse</li> +-- </ul> +-- @param key the Key object to which to pass the event if not treated, this should be your game default key handler probably +-- @param allow_move true if this will allow player movement (you should use it to check that you are not in targetting mode) +function _M:mouseHandleDefault(key, allow_move, button, mx, my, xrel, yrel) + local tmx, tmy = game.level.map:getMouseTile(mx, my) + + -- Move + if button == "left" and not core.key.modState("shift") and not moving_around and not xrel and not yrel then + if allow_move then self:mouseMove(tmx, tmy) end + + -- Move map around + elseif button == "left" and xrel and yrel and core.key.modState("shift") then + self:mouseScrollMap(game.level.map, xrel, yrel) + moving_around = true + -- Zoom map +-- elseif button == "wheelup" then +-- game.level.map:setZoom(0.1, tmx, tmy) +-- elseif button == "wheeldown" then +-- game.level.map:setZoom(-0.1, tmx, tmy) + -- Pass any other buttons to the keybinder + elseif button ~= "none" and not xrel and not yrel then + key:receiveKey(button, core.key.modState("ctrl") and true or false, core.key.modState("shift") and true or false, core.key.modState("alt") and true or false, core.key.modState("meta") and true or false, nil, false, true) + end + + if not xrel and not yrel then moving_around = false end +end diff --git a/game/modules/example/class/Game.lua b/game/modules/example/class/Game.lua index 3f8bfadd94..6e23d5e037 100644 --- a/game/modules/example/class/Game.lua +++ b/game/modules/example/class/Game.lua @@ -19,12 +19,12 @@ require "engine.class" require "engine.GameTurnBased" +require "engine.interface.GameTargeting" require "engine.KeyBind" local Savefile = require "engine.Savefile" local DamageType = require "engine.DamageType" local Zone = require "engine.Zone" local Map = require "engine.Map" -local Target = require "engine.Target" local Level = require "engine.Level" local Birther = require "engine.Birther" @@ -41,7 +41,7 @@ local Tooltip = require "engine.Tooltip" local QuitDialog = require "mod.dialogs.Quit" -module(..., package.seeall, class.inherit(engine.GameTurnBased, engine.interface.GameMusic, engine.interface.GameSound)) +module(..., package.seeall, class.inherit(engine.GameTurnBased, engine.interface.GameTargeting)) function _M:init() engine.GameTurnBased.init(self, engine.KeyBind.new(), 1000, 100) @@ -74,10 +74,7 @@ function _M:run() if not self.player then self:newGame() end -- Setup the targetting system - 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" + engine.interface.GameTargeting.init(self) -- Ok everything is good to go, activate the game in the engine! self:setCurrent() @@ -180,7 +177,7 @@ end function _M:tick() if self.level then - if self.target.target.entity and not self.level:hasEntity(self.target.target.entity) then self.target.target.entity = false end + self:targetOnTick() engine.GameTurnBased.tick(self) -- Fun stuff: this can make the game realtime, although callit it in display() will make it work better @@ -219,112 +216,21 @@ function _M:display() -- Display the targetting system if active self.target:display() - -- Display a tooltip if available - if self.tooltip_x then - local mx, my = self.tooltip_x , self.tooltip_y - local tmx, tmy = self.level.map:getMouseTile(mx, my) - self.tooltip:displayAtMap(tmx, tmy, mx, my) - end - - -- Move target around - if self.old_tmx ~= tmx or self.old_tmy ~= tmy then - self.target.target.x, self.target.target.y = tmx, tmy - end - self.old_tmx, self.old_tmy = tmx, tmy - -- And the minimap self.level.map:minimapDisplay(self.w - 200, 20, util.bound(self.player.x - 25, 0, self.level.map.w - 50), util.bound(self.player.y - 25, 0, self.level.map.h - 50), 50, 50, 0.6) end - engine.GameTurnBased.display(self) -end + -- Tooltip is displayed over all else + self:targetDisplayTooltip() ---- Targeting mode --- Now before this is an hard piece of code. You probably wont need to change it much.<br/> --- This uses a coroutine to allow a talent to request a target without interruption, yet while preserving the realtime-ness of the engine -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 and "players" or nil) - if msg then self.log(type(msg) == "string" and msg or "Tactical display disabled. Press shift+'t' or right mouse click to enable.") end - self.level.map.changed = true - self.target:setActive(false) - - if tostring(old) == "exclusive" then - self.key = self.normal_key - self.key:setCurrent() - if self.target_co then - local co = self.target_co - self.target_co = nil - 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 - else - Map:setViewerFaction("players") - if msg then self.log(type(msg) == "string" and msg or "Tactical display enabled. Press shift+'t' to disable.") end - self.level.map.changed = true - self.target:setActive(true, typ) - self.target_style = "lock" - - -- 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() - - if 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 - else - self.target:scan(5, nil, self.player.x, self.player.y) - end - end - self.tooltip_x, self.tooltip_y = self.level.map:getTileToScreen(self.target.target.x, self.target.target.y) - end + engine.GameTurnBased.display(self) end --- Setup the keybinds function _M:setupCommands() - -- One key handler for targeting - self.targetmode_key = engine.KeyBind.new() - self.targetmode_key:addCommands{ _SPACE=function() self:targetMode(false, false) end, } - self.targetmode_key:addBinds - { - TACTICAL_DISPLAY = function() self:targetMode(false, false) end, - ACCEPT = function() - self:targetMode(false, false) - self.tooltip_x, self.tooltip_y = nil, nil - end, - 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, - - MOVE_LEFT = function() if self.target_style == "lock" then self.target:scan(4) 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) 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) 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) 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) 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) 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) 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) 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, - } - + -- Make targeting work self.normal_key = self.key + self:targetSetupKey() -- One key handled for normal function self.key:addBinds @@ -453,72 +359,13 @@ function _M:setupCommands() end function _M:setupMouse(reset) - -- Those 2 locals will be "absorbed" into the mosue event handler function, this is a closure - local derivx, derivy = 0, 0 - local moving_around = false - if reset then self.mouse:reset() end self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel) - -- Move tooltip - self.tooltip_x, self.tooltip_y = mx, my - local tmx, tmy = self.level.map:getMouseTile(mx, my) - - if self.key == self.targetmode_key then - -- Target with mouse - if button == "none" and xrel and yrel then - self.target:setSpot(tmx, tmy) - -- Cancel target - elseif button ~= "left" and not xrel and not yrel then - self:targetMode(false, false) - self.tooltip_x, self.tooltip_y = nil, nil - -- Accept target - elseif not xrel and not yrel 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 - end - - -- Move - if button == "left" and not core.key.modState("shift") and not moving_around and not xrel and not yrel then - if self.key == self.normal_key then self.player:mouseMove(tmx, tmy) end - - -- Move map around - elseif button == "left" and xrel and yrel and core.key.modState("shift") then - derivx = derivx + xrel - derivy = derivy + yrel - game.level.map.changed = true - if derivx >= game.level.map.tile_w then - game.level.map.mx = game.level.map.mx - 1 - derivx = derivx - game.level.map.tile_w - elseif derivx <= -game.level.map.tile_w then - game.level.map.mx = game.level.map.mx + 1 - derivx = derivx + game.level.map.tile_w - end - if derivy >= game.level.map.tile_h then - game.level.map.my = game.level.map.my - 1 - derivy = derivy - game.level.map.tile_h - elseif derivy <= -game.level.map.tile_h then - game.level.map.my = game.level.map.my + 1 - derivy = derivy + game.level.map.tile_h - end - game.level.map._map:setScroll(game.level.map.mx, game.level.map.my) - moving_around = true - elseif button ~= "none" and not xrel and not yrel then - self.key:receiveKey( - button, - core.key.modState("ctrl") and true or false, - core.key.modState("shift") and true or false, - core.key.modState("alt") and true or false, - core.key.modState("meta") and true or false, - nil, false, true - ) - end + -- Handle targeting + if self:targetMouse(button, mx, my, xrel, yrel) then return end - if not xrel and not yrel then moving_around = false end + -- Handle the mouse movement/scrolling + self.player:mouseHandleDefault(self.key, self.key == self.normal_key, button, mx, my, xrel, yrel) end) -- Scroll message log self.mouse:registerZone(self.logdisplay.display_x, self.logdisplay.display_y, self.w, self.h, function(button) diff --git a/game/modules/example/class/Player.lua b/game/modules/example/class/Player.lua index d55f466941..4182b135a9 100644 --- a/game/modules/example/class/Player.lua +++ b/game/modules/example/class/Player.lua @@ -21,6 +21,7 @@ require "engine.class" require "mod.class.Actor" require "engine.interface.PlayerRest" require "engine.interface.PlayerRun" +require "engine.interface.PlayerMouse" require "engine.interface.PlayerHotkeys" local Map = require "engine.Map" local Dialog = require "engine.Dialog" @@ -36,6 +37,7 @@ module(..., package.seeall, class.inherit( mod.class.Actor, engine.interface.PlayerRest, engine.interface.PlayerRun, + engine.interface.PlayerMouse, engine.interface.PlayerHotkeys )) @@ -134,30 +136,13 @@ function _M:levelup() end --- Tries to get a target from the user --- *WARNING* If used inside a coroutine it will yield and resume it later when a target is found. --- This is usualy just what you want so dont think too much about it :) function _M:getTarget(typ) - if coroutine.running() then - local msg - if type(typ) == "string" then msg, typ = typ, nil - elseif type(typ) == "table" then - if typ.default_target then game.target.target.entity = typ.default_target end - msg = typ.msg - end - game:targetMode("exclusive", msg, coroutine.running(), typ) - if typ.nolock then - game.target_style = "free" - end - return coroutine.yield() - end - return game.target.target.x, game.target.target.y, game.target.target.entity + return game:targetGetForPlayer(typ) end --- Sets the current target function _M:setTarget(target) - game.target.target.entity = target - game.target.target.x = target.x - game.target.target.y = target.y + return game:targetSetForPlayer(target) end local function spotHostiles(self) @@ -201,40 +186,8 @@ function _M:runCheck() return engine.interface.PlayerRun.runCheck(self) end - ---- Runs to the clicked mouse spot --- if no monsters in sight it will try to make an A* path, if it fails it will do a direct path --- if there are monsters in sight it will move one stop in the direct path direction +--- Move with the mouse +-- We just feed our spotHostile to the interface mouseMove function _M:mouseMove(tmx, tmy) - if config.settings.tome.cheat and game.key.status[game.key._LSHIFT] and game.key.status[game.key._LCTRL] then - game.log("[CHEAT] teleport to %dx%d", tmx, tmy) - self:move(tmx, tmy, true) - else - -- If hostiles, attack! - if spotHostiles(self) or math.floor(core.fov.distance(self.x, self.y, tmx, tmy)) == 1 then - local l = line.new(self.x, self.y, tmx, tmy) - local nx, ny = l() - self:move(nx or self.x, ny or self.y) - return - end - - local a = Astar.new(game.level.map, self) - local path = a:calc(self.x, self.y, tmx, tmy, true) - -- No Astar path ? jsut be dumb and try direct line - if not path then - local d = DirectPath.new(game.level.map, self) - path = d:calc(self.x, self.y, tmx, tmy, true) - end - - if path then - -- Should we just try to move in the direction, aka: attack! - if path[1] and game.level.map:checkAllEntities(path[1].x, path[1].y, "block_move", self) then self:move(path[1].x, path[1].y) return end - - -- Insert the player coords, running needs to find the player - table.insert(path, 1, {x=self.x, y=self.y}) - - -- Move along the projected A* path - self:runFollow(path) - end - end + return engine.interface.PlayerMouse.mouseMove(self, tmx, tmy, spotHostiles) end diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua index fa669b5962..18ea885e04 100644 --- a/game/modules/tome/class/Game.lua +++ b/game/modules/tome/class/Game.lua @@ -21,12 +21,12 @@ require "engine.class" require "engine.GameTurnBased" require "engine.interface.GameMusic" require "engine.interface.GameSound" +require "engine.interface.GameTargeting" local KeyBind = require "engine.KeyBind" local Savefile = require "engine.Savefile" local DamageType = require "engine.DamageType" local Zone = require "engine.Zone" local Map = require "engine.Map" -local Target = require "engine.Target" local Level = require "engine.Level" local Birther = require "engine.Birther" local Astar = require "engine.Astar" @@ -58,7 +58,7 @@ local Calendar = require "engine.Calendar" local Dialog = require "engine.Dialog" local QuitDialog = require "mod.dialogs.Quit" -module(..., package.seeall, class.inherit(engine.GameTurnBased, engine.interface.GameMusic, engine.interface.GameSound)) +module(..., package.seeall, class.inherit(engine.GameTurnBased, engine.interface.GameMusic, engine.interface.GameSound, engine.interface.GameTargeting)) -- Difficulty settings DIFFICULTY_EASY = 1 @@ -107,10 +107,7 @@ function _M:run() self.hotkeys_display.actor = self.player self.npcs_display.actor = self.player - 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" + engine.interface.GameTargeting.init(self) -- Ok everything is good to go, activate the game in the engine! self:setCurrent() @@ -220,8 +217,7 @@ function _M:setupDisplayMode() end if self.level then self.level.map:recreate() - self.target = Target.new(Map, self.player) - self.target.target.entity = self.player + engine.interface.GameTargeting.init(self) self.level.map:moveViewSurround(self.player.x, self.player.y, 8, 8) end self:setupMiniMap() @@ -368,7 +364,7 @@ end function _M:tick() if self.level then - if self.target.target.entity and not self.level:hasEntity(self.target.target.entity) then self.target.target.entity = false end + self:targetOnTick() engine.GameTurnBased.tick(self) -- Fun stuff: this can make the game realtime, although callit it in display() will make it work better @@ -448,126 +444,16 @@ function _M:display() if self.player then self.player.changed = false end -- 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 self.tooltip:displayAtMap(self.level.map:getMouseTile(self.tooltip_x , self.tooltip_y)) end - - -- Move target around - if self.old_tmx ~= tmx or self.old_tmy ~= tmy then - self.target.target.x, self.target.target.y = tmx, tmy - end - self.old_tmx, self.old_tmy = tmx, tmy - end + self:targetDisplayTooltip() engine.GameTurnBased.display(self) end -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 and self.player.faction or nil) - if msg then self.log(type(msg) == "string" and msg or "Tactical display disabled. Press shift+'t' or right mouse click to enable.") end - self.level.map.changed = true - self.target:setActive(false) - - if tostring(old) == "exclusive" then - local fct = function(ok) - if not ok 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 - 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 == game.player.x and self.target.target.y == game.player.y then - Dialog:yesnoPopup("Target yourself?", "Are you sure you want to target yourself?", fct) - else - fct(true) - end - end - else - Map:setViewerFaction(self.player.faction) - if msg then self.log(type(msg) == "string" and msg or "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 - - -- 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() - - if 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 - else - local filter = nil - 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 - self.target:scan(5, nil, self.player.x, self.player.y, filter) - 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:setupCommands() - self.targetmode_key = engine.KeyBind.new() - self.targetmode_key:addCommands{ _SPACE=function() self:targetMode(false, false) self.tooltip_x, self.tooltip_y = nil, nil end, } - self.targetmode_key:addBinds - { - TACTICAL_DISPLAY = function() - self:targetMode(false, false) - self.tooltip_x, self.tooltip_y = nil, nil - end, - ACCEPT = function() - self:targetMode(false, false) - self.tooltip_x, self.tooltip_y = nil, nil - end, - 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, - - MOVE_LEFT = function() if self.target_style == "lock" then self.target:scan(4) 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) 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) 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) 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) 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) 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) 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) 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, - } - + -- Make targeting work self.normal_key = self.key + self:targetSetupKey() + -- Activate profiler keybinds self.key:setupProfiler() @@ -793,79 +679,13 @@ function _M:setupCommands() end function _M:setupMouse(reset) - -- Those 2 locals will be "absorbed" into the mouse event handler function, this is a closure - local derivx, derivy = 0, 0 - local zoom = 1 - local moving_around = false - if reset then self.mouse:reset() end self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel) - -- Move tooltip - self.tooltip_x, self.tooltip_y = mx, my - local tmx, tmy = self.level.map:getMouseTile(mx, my) - - if self.key == self.targetmode_key then - -- Target with mouse - if button == "none" and xrel and yrel then - self.target:setSpot(tmx, tmy) - -- Cancel target - elseif button ~= "left" and not xrel and not yrel then - self:targetMode(false, false) - self.tooltip_x, self.tooltip_y = nil, nil - -- Accept target - elseif not xrel and not yrel 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 - end - - -- Move - if button == "left" and not core.key.modState("shift") and not moving_around and not xrel and not yrel then - if self.key == self.normal_key then self.player:mouseMove(tmx, tmy) end - - -- Move map around - elseif button == "left" and xrel and yrel and core.key.modState("shift") then - derivx = derivx + xrel - derivy = derivy + yrel - game.level.map.changed = true - if derivx >= game.level.map.tile_w then - game.level.map.mx = game.level.map.mx - 1 - derivx = derivx - game.level.map.tile_w - elseif derivx <= -game.level.map.tile_w then - game.level.map.mx = game.level.map.mx + 1 - derivx = derivx + game.level.map.tile_w - end - if derivy >= game.level.map.tile_h then - game.level.map.my = game.level.map.my - 1 - derivy = derivy - game.level.map.tile_h - elseif derivy <= -game.level.map.tile_h then - game.level.map.my = game.level.map.my + 1 - derivy = derivy + game.level.map.tile_h - end - game.level.map._map:setScroll(game.level.map.mx, game.level.map.my) - moving_around = true - -- Zoom map --- elseif button == "wheelup" then --- game.level.map:setZoom(0.1, tmx, tmy) --- elseif button == "wheeldown" then --- game.level.map:setZoom(-0.1, tmx, tmy) - -- Pass any other buttons to the keybinder - elseif button ~= "none" and not xrel and not yrel then - self.key:receiveKey( - button, - core.key.modState("ctrl") and true or false, - core.key.modState("shift") and true or false, - core.key.modState("alt") and true or false, - core.key.modState("meta") and true or false, - nil, false, true - ) - end + -- Handle targeting + if self:targetMouse(button, mx, my, xrel, yrel) then return end - if not xrel and not yrel then moving_around = false end + -- Handle the mouse movement/scrolling + self.player:mouseHandleDefault(self.key, self.key == self.normal_key, button, mx, my, xrel, yrel) end) -- Scroll message log self.mouse:registerZone(self.logdisplay.display_x, self.logdisplay.display_y, self.w, self.h, function(button) diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua index 7fb1129e81..3f1552033c 100644 --- a/game/modules/tome/class/Player.lua +++ b/game/modules/tome/class/Player.lua @@ -23,14 +23,13 @@ require "engine.interface.PlayerRest" require "engine.interface.PlayerRun" require "engine.interface.PlayerHotkeys" require "engine.interface.PlayerSlide" +require "engine.interface.PlayerMouse" local Map = require "engine.Map" local Dialog = require "engine.Dialog" local ActorTalents = require "engine.interface.ActorTalents" local LevelupStatsDialog = require "mod.dialogs.LevelupStatsDialog" local LevelupTalentsDialog = require "mod.dialogs.LevelupTalentsDialog" local DeathDialog = require "mod.dialogs.DeathDialog" -local Astar = require"engine.Astar" -local DirectPath = require"engine.DirectPath" --- Defines the player for ToME -- It is a normal actor, with some redefined methods to handle user interaction.<br/> @@ -40,6 +39,7 @@ module(..., package.seeall, class.inherit( engine.interface.PlayerRest, engine.interface.PlayerRun, engine.interface.PlayerHotkeys, + engine.interface.PlayerMouse, engine.interface.PlayerSlide )) @@ -293,30 +293,13 @@ function _M:levelup() end --- Tries to get a target from the user --- *WARNING* If used inside a coroutine it will yield and resume it later when a target is found. --- This is usualy just what you want so dont think too much about it :) function _M:getTarget(typ) - if game.target.forced then return unpack(game.target.forced) end - if coroutine.running() then - local msg - if type(typ) == "string" then msg, typ = typ, nil - elseif type(typ) == "table" then - if typ.default_target then game.target.target.entity = typ.default_target end - msg = typ.msg - end - game:targetMode("exclusive", msg, coroutine.running(), typ) - if typ.nolock then game.target_style = "free" end - if typ.nowarning then game.target_warning = false end - return coroutine.yield() - end - return game.target.target.x, game.target.target.y, game.target.target.entity + return game:targetGetForPlayer(typ) end --- Sets the current target function _M:setTarget(target) - game.target.target.entity = target - game.target.target.x = target.x - game.target.target.y = target.y + return game:targetSetForPlayer(target) end local function spotHostiles(self) @@ -384,6 +367,12 @@ function _M:runCheck() 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) + return engine.interface.PlayerMouse.mouseMove(self, tmx, tmy, spotHostiles) +end + --- Called after running a step function _M:runMoved() self:playerFOV() @@ -541,43 +530,6 @@ function _M:playerLevelup(on_finish) end end ---- Runs to the clicked mouse spot --- if no monsters in sight it will try to make an A* path, if it fails it will do a direct path --- if there are monsters in sight it will move one stop in the direct path direction -function _M:mouseMove(tmx, tmy) - if config.settings.tome.cheat and core.key.modState("ctrl") then - game.log("[CHEAT] teleport to %dx%d", tmx, tmy) - self:move(tmx, tmy, true) - else - -- If hostiles, attack! - if spotHostiles(self) or math.floor(core.fov.distance(self.x, self.y, tmx, tmy)) == 1 then - local l = line.new(self.x, self.y, tmx, tmy) - local nx, ny = l() - self:move(nx or self.x, ny or self.y) - return - end - - local a = Astar.new(game.level.map, self) - local path = a:calc(self.x, self.y, tmx, tmy, true) - -- No Astar path ? jsut be dumb and try direct line - if not path then - local d = DirectPath.new(game.level.map, self) - path = d:calc(self.x, self.y, tmx, tmy, true) - end - - if path then - -- Should we just try to move in the direction, aka: attack! - if path[1] and game.level.map:checkAllEntities(path[1].x, path[1].y, "block_move", self) then self:move(path[1].x, path[1].y) return end - - -- Insert the player coords, running needs to find the player - table.insert(path, 1, {x=self.x, y=self.y}) - - -- Move along the projected A* path - self:runFollow(path) - end - end -end - --- Use a portal with the orb of many ways function _M:useOrbPortal(portal) if portal.change_wilderness then -- GitLab