diff --git a/game/engine/interface/GameTargeting.lua b/game/engine/interface/GameTargeting.lua new file mode 100644 index 0000000000000000000000000000000000000000..3b626849a0f7c805b1f6d74de421fb2755e8d88b --- /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 0000000000000000000000000000000000000000..11b9bda6ae3f223e4319d119f1a1066a9abd9c3a --- /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 3f8bfadd944ad5435651ce0678bf198091517bf8..6e23d5e0371fdef13688ad1de6128ea72fc6b28d 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 d55f46694174ba03ee5fb73f3fc80b1e4da6f95e..4182b135a95fa1e0c4b7ae48fcfb4cbcb08ca7fe 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 fa669b596296b7f61ffb9f7f824cf3ec1918de1b..18ea885e04029d75df7a1ac5e6b1d0c84d9c05bf 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 7fb1129e8127e5210cc54f039c681a0aadded5c3..3f1552033c5ecf1b5b9ec15bb7ee70ad9ebc6e20 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