From c421493090e386a41aaaf99bb9f3d04ba6ccd942 Mon Sep 17 00:00:00 2001 From: grayswandir <grayswandir76@gmail.com> Date: Fri, 28 Nov 2014 21:21:20 -0500 Subject: [PATCH] Added hooks and such for extending the targeting system. --- game/engines/default/engine/Target.lua | 398 ++++++++++-------- .../default/engine/interface/ActorProject.lua | 21 +- 2 files changed, 242 insertions(+), 177 deletions(-) diff --git a/game/engines/default/engine/Target.lua b/game/engines/default/engine/Target.lua index 78bd6aab8e..2cdb1cbeb8 100644 --- a/game/engines/default/engine/Target.lua +++ b/game/engines/default/engine/Target.lua @@ -24,6 +24,8 @@ local Shader = require "engine.Shader" --- handles targetting module(..., package.seeall, class.make) +_M.defaults = {} + function _M:init(map, source_actor) self.display_x, self.display_y = map.display_x, map.display_y self.w, self.h = map.viewport.width, map.viewport.height @@ -69,7 +71,7 @@ function _M:createTextures() end function _M:enableFBORenderer(texture, shader) - if not shader or not core.display.fboSupportsTransparency then + if not shader or not core.display.fboSupportsTransparency then self.fbo = nil self:createTextures() return @@ -112,7 +114,7 @@ function _M:display(dispx, dispy, prevfbo, rotate_keyframes) sx = sx + game.level.map.display_x sy = sy + game.level.map.display_y self.display_x, self.display_y = dispx or sx or self.display_x, dispy or sy or self.display_y - + if self.active then if not self.fbo then self:realDisplay(self.display_x, self.display_y) @@ -149,11 +151,95 @@ function _M:display(dispx, dispy, prevfbo, rotate_keyframes) self.display_x, self.display_y = ox, oy end +-- Being completely blocked by the corner of an adjacent tile is annoying, so let's make it a special case and hit it instead. +_M.defaults.display_blocked_by_adjacent = function(self, d) + if d.blocked_corner_x then + d.block = true + d.hit = true + d.hit_radius = false + stopped = true + if self.target_type.min_range and core.fov.distance(self.target_type.start_x, self.target_type.start_y, d.lx, d.ly) < self.target_type.min_range then + d.s = self.sr + end + if game.level.map:isBound(d.blocked_corner_x, d.blocked_corner_y) then + d.display_highlight(d.s, d.blocked_corner_x, d.blocked_corner_y) + end + d.s = self.sr + end +end + +_M.defaults.display_check_block_path = function(self, d) + d.block, d.hit, d.hit_radius = false, true, true + d.block, d.hit, d.hit_radius = self.target_type:block_path(d.lx, d.ly, true) +end + +-- Update coordinates and set color +_M.defaults.display_update_hit = function(self, d) + if d.hit then + d.stop_x, d.stop_y = d.lx, d.ly + if not d.block and d.hit == "unknown" then d.s = self.sy end + else + d.s = self.sr + end +end + +_M.defaults.display_update_radius = function(self, d) + if d.hit_radius then + d.stop_radius_x, d.stop_radius_y = d.lx, d.ly + end +end + +_M.defaults.display_update_min_range = function(self, d) + if self.target_type.min_range then + -- Check if we should be "red" + if core.fov.distance(self.target_type.start_x, self.target_type.start_y, d.lx, d.ly) < self.target_type.min_range then + d.s = self.sr + -- Check if we were only "red" because of minimum distance + elseif d.s == self.sr then + d.s = self.sb + end + end +end + +_M.defaults.display_line_step = function(self, d) + d.display_highlight(d.s, d.lx, d.ly) +end + +_M.defaults.display_on_block = function(self, d) + d.s = self.sr + d.stopped = true +end + +_M.defaults.display_on_block_corner = function(self, d) + d.block = true + d.stopped = true + d.hit_radius = false + d.s = self.sr + -- double the fun :-P + if game.level.map:isBound(d.blocked_corner_x, d.blocked_corner_y) then + if self.target_type.display_corner_block then + self.target_type.display_corner_block(self, d) + else + d.display_highlight(d.s, d.blocked_corner_x, d.blocked_corner_y, 2) + end + end +end + +_M.defaults.display_default_target = function(self, d) + -- Entity tracking, if possible and if visible + if self.target.entity and self.target.entity.x and self.target.entity.y and game.level.map.seens(self.target.entity.x, self.target.entity.y) then + self.target.x, self.target.y = self.target.entity.x, self.target.entity.y + end + self.target.x = self.target.x or self.source_actor.x + self.target.y = self.target.y or self.source_actor.y +end + function _M:realDisplay(dispx, dispy, display_highlight) if not display_highlight then if util.isHex() then display_highlight = function(texture, tx, ty, count) count = count or 1 + if self.target_type.filter and not self.target_type.no_filter_highlight and self.target_type.filter(tx, ty) then count = count + 1 end for i = 1, count do texture:toScreenHighlightHex( dispx + (tx - game.level.map.mx) * self.tile_w * Map.zoom, @@ -165,6 +251,7 @@ function _M:realDisplay(dispx, dispy, display_highlight) else display_highlight = function(texture, tx, ty, count) count = count or 1 + if self.target_type.filter and not self.target_type.no_filter_highlight and self.target_type.filter(tx, ty) then count = count + 1 end for i = 1, count do texture:toScreen( dispx + (tx - game.level.map.mx) * self.tile_w * Map.zoom, @@ -206,113 +293,70 @@ function _M:realDisplay(dispx, dispy, display_highlight) return end + local d = {} + d.display_highlight = display_highlight + -- Make sure we have a source if not self.target_type.source_actor then self.target_type.source_actor = self.source_actor end - -- Entity tracking, if possible and if visible - if self.target.entity and self.target.entity.x and self.target.entity.y and game.level.map.seens(self.target.entity.x, self.target.entity.y) then - self.target.x, self.target.y = self.target.entity.x, self.target.entity.y - end - self.target.x = self.target.x or self.source_actor.x - self.target.y = self.target.y or self.source_actor.y + + -- Pick default target + self.target_type.display_default_target(self, d) self.target_type.start_x = self.target_type.start_x or self.target_type.x or self.target_type.source_actor and self.target_type.source_actor.x or self.x self.target_type.start_y = self.target_type.start_y or self.target_type.y or self.target_type.source_actor and self.target_type.source_actor.y or self.y - -- self.cursor:toScreen(dispx + (self.target.x - game.level.map.mx) * self.tile_w * Map.zoom, dispy + (self.target.y - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom) -- Do not display if not requested if not self.active then return end - local s = self.sb - local l + d.s = self.sb if self.target_type.source_actor.lineFOV then - l = self.target_type.source_actor:lineFOV(self.target.x, self.target.y, nil, nil, self.target_type.start_x, self.target_type.start_y) + d.l = self.target_type.source_actor:lineFOV(self.target.x, self.target.y, nil, nil, self.target_type.start_x, self.target_type.start_y) else - l = core.fov.line(self.target_type.start_x, self.target_type.start_y, self.target.x, self.target.y) + d.l = core.fov.line(self.target_type.start_x, self.target_type.start_y, self.target.x, self.target.y) end local block_corner = self.target_type.block_path and function(_, bx, by) local b, h, hr = self.target_type:block_path(bx, by, true) ; return b and h and not hr end or function(_, bx, by) return false end - l:set_corner_block(block_corner) - local lx, ly, blocked_corner_x, blocked_corner_y = l:step() + d.l:set_corner_block(block_corner) + d.lx, d.ly, d.blocked_corner_x, d.blocked_corner_y = d.l:step() - local stop_x, stop_y = self.target_type.start_x, self.target_type.start_y - local stop_radius_x, stop_radius_y = self.target_type.start_x, self.target_type.start_y - local stopped = false - local block, hit, hit_radius + d.stop_x, d.stop_y = self.target_type.start_x, self.target_type.start_y + d.stop_radius_x, d.stop_radius_y = self.target_type.start_x, self.target_type.start_y + d.stopped = false - local firstx, firsty = lx, ly + d.firstx, d.firsty = d.lx, d.ly -- Being completely blocked by the corner of an adjacent tile is annoying, so let's make it a special case and hit it instead - if blocked_corner_x then - block = true - hit = true - hit_radius = false - stopped = true - if self.target_type.min_range and core.fov.distance(self.target_type.start_x, self.target_type.start_y, lx, ly) < self.target_type.min_range then - s = self.sr - end - if game.level.map:isBound(blocked_corner_x, blocked_corner_y) then - display_highlight(s, blocked_corner_x, blocked_corner_y) - end - s = self.sr - end - - while lx and ly do - if not stopped then - block, hit, hit_radius = false, true, true - if self.target_type.block_path then - block, hit, hit_radius = self.target_type:block_path(lx, ly, true) - end + self.target_type.display_blocked_by_adjacent(self, d) + while d.lx and d.ly do + if not d.stopped then + self.target_type.display_check_block_path(self, d) -- Update coordinates and set color - if hit then - stop_x, stop_y = lx, ly - if not block and hit == "unknown" then s = self.sy end - else - s = self.sr - end - if hit_radius then - stop_radius_x, stop_radius_y = lx, ly - end - if self.target_type.min_range then - -- Check if we should be "red" - if core.fov.distance(self.target_type.start_x, self.target_type.start_y, lx, ly) < self.target_type.min_range then - s = self.sr - -- Check if we were only "red" because of minimum distance - elseif s == self.sr then - s = self.sb - end - end - end - display_highlight(s, lx, ly) - if block then - s = self.sr - stopped = true + self.target_type.display_update_hit(self, d) + self.target_type.display_update_radius(self, d) + self.target_type.display_update_min_range(self, d) end - lx, ly, blocked_corner_x, blocked_corner_y = l:step() + self.target_type.display_line_step(self, d) - if blocked_corner_x and not stopped then - block = true - stopped = true - hit_radius = false - s = self.sr - -- double the fun :-P - if game.level.map:isBound(blocked_corner_x, blocked_corner_y) then - display_highlight(s, blocked_corner_x, blocked_corner_y, 2) - end - end + if d.block then self.target_type.display_on_block(self, d) end + + d.lx, d.ly, d.blocked_corner_x, d.blocked_corner_y = d.l:step() + if d.blocked_corner_x and not d.stopped then + self.target_type.display_on_block_corner(self, d) + end end if self.target_type.ball and self.target_type.ball > 0 then core.fov.calc_circle( - stop_radius_x, - stop_radius_y, + d.stop_radius_x, + d.stop_radius_y, game.level.map.w, game.level.map.h, self.target_type.ball, @@ -321,17 +365,19 @@ function _M:realDisplay(dispx, dispy, display_highlight) end, function(_, px, py) if not self.target_type.no_restrict and not game.level.map.remembers(px, py) and not game.level.map.seens(px, py) then - display_highlight(self.syg, px, py) + d.display_highlight(self.syg, px, py) else - display_highlight(self.sg, px, py) + d.display_highlight(self.sg, px, py) end end, - nil) - elseif self.target_type.cone and self.target_type.cone > 0 then + nil) + end + + if self.target_type.cone and self.target_type.cone > 0 then --local dir_angle = math.deg(math.atan2(self.target.y - self.source_actor.y, self.target.x - self.source_actor.x)) core.fov.calc_beam_any_angle( - stop_radius_x, - stop_radius_y, + d.stop_radius_x, + d.stop_radius_y, game.level.map.w, game.level.map.h, self.target_type.cone, @@ -345,16 +391,18 @@ function _M:realDisplay(dispx, dispy, display_highlight) end, function(_, px, py) if not self.target_type.no_restrict and not game.level.map.remembers(px, py) and not game.level.map.seens(px, py) then - display_highlight(self.syg, px, py) + d.display_highlight(self.syg, px, py) else - display_highlight(self.sg, px, py) + d.display_highlight(self.sg, px, py) end end, nil) - elseif self.target_type.wall and self.target_type.wall > 0 then + end + + if self.target_type.wall and self.target_type.wall > 0 then core.fov.calc_wall( - stop_radius_x, - stop_radius_y, + d.stop_radius_x, + d.stop_radius_y, game.level.map.w, game.level.map.h, self.target_type.wall, @@ -368,15 +416,100 @@ function _M:realDisplay(dispx, dispy, display_highlight) end, function(_, px, py) if not self.target_type.no_restrict and not game.level.map.remembers(px, py) and not game.level.map.seens(px, py) then - display_highlight(self.syg, px, py) + d.display_highlight(self.syg, px, py) else - display_highlight(self.sg, px, py) + d.display_highlight(self.sg, px, py) end end, nil) end + + d[1] = "Target:realDisplay" + self:triggerHook(d) end +_M.defaults.block_path = function(typ, lx, ly, for_highlights) + if not game.level.map:isBound(lx, ly) then + return true, false, false + elseif not typ.no_restrict then + if typ.range and typ.start_x then + local dist = core.fov.distance(typ.start_x, typ.start_y, lx, ly) + if dist > typ.range then return true, false, false end + elseif typ.range and typ.source_actor and typ.source_actor.x then + local dist = core.fov.distance(typ.source_actor.x, typ.source_actor.y, lx, ly) + if dist > typ.range then return true, false, false end + end + local is_known = game.level.map.remembers(lx, ly) or game.level.map.seens(lx, ly) + if typ.requires_knowledge and not is_known then + return true, false, false + end + if not typ.pass_terrain and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") then + if for_highlights and not is_known then + return false, "unknown", true + else + return true, true, false + end + -- If we explode due to something other than terrain, then we should explode ON the tile, not before it + elseif typ.stop_block then + local nb = game.level.map:checkAllEntitiesCount(lx, ly, "block_move") + -- Reduce for pass_projectile or pass_terrain, which was handled above + if game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and (typ.pass_terrain or game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile")) then + nb = nb - 1 + end + -- Reduce the nb blocking for friendlies + if not typ.friendlyblock and typ.source_actor and typ.source_actor.reactionToward then + local a = game.level.map(lx, ly, engine.Map.ACTOR) + if a and typ.source_actor:reactionToward(a) > 0 then + nb = nb - 1 + end + end + if nb > 0 then + if for_highlights then + -- Targeting highlight should be yellow if we don't know what we're firing through + if not is_known then + return false, "unknown", true + -- Don't show the path as blocked if it's blocked by an actor we can't see + elseif nb == 1 and typ.source_actor and typ.source_actor.canSee and not typ.source_actor:canSee(game.level.map(lx, ly, engine.Map.ACTOR)) then + return false, true, true + end + end + return true, true, true + end + end + if for_highlights and not is_known then + return false, "unknown", true + end + end + -- If we don't block the path, then the explode point should be here + return false, true, true +end + +_M.defaults.block_radius = function(typ, lx, ly, for_highlights) + return not typ.no_restrict and + game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and + not game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") and + not (for_highlights and not (game.level.map.remembers(lx, ly) or game.level.map.seens(lx, ly))) +end + +--- targeting type strings -> modification function. +_M.types_def = { + ball = function(dest, src) dest.ball = src.radius end, + cone = function(dest, src) + dest.cone = src.radius + dest.cone_angle = src.cone_angle or 55 + dest.selffire = false + end, + wall = function(dest, src) + if util.isHex() then + --with a hex grid, a wall should only be defined by the number of spots + src.halfmax_spots = src.halflength + src.halflength = 2 * src.halflength + end + dest.wall = src.halflength + end, + bolt = function(dest, src) dest.stop_block = true end, + beam = function(dest, scr) dest.line = true end,} + -- @return t The target table used by ActorProject, Projectile, GameTargeting, etc. -- @param t Target table used to generate the -- @param t.type The engine-defined type, populates other more complex variables (see below) @@ -416,92 +549,16 @@ function _M:getType(t) selffire = true, friendlyfire = true, friendlyblock = true, - --- Determines how a path is blocked for a target type - --@param typ The target type table - block_path = function(typ, lx, ly, for_highlights) - if not game.level.map:isBound(lx, ly) then - return true, false, false - elseif not typ.no_restrict then - if typ.range and typ.start_x then - local dist = core.fov.distance(typ.start_x, typ.start_y, lx, ly) - if dist > typ.range then return true, false, false end - elseif typ.range and typ.source_actor and typ.source_actor.x then - local dist = core.fov.distance(typ.source_actor.x, typ.source_actor.y, lx, ly) - if dist > typ.range then return true, false, false end - end - local is_known = game.level.map.remembers(lx, ly) or game.level.map.seens(lx, ly) - if typ.requires_knowledge and not is_known then - return true, false, false - end - if not typ.pass_terrain and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") then - if for_highlights and not is_known then - return false, "unknown", true - else - return true, true, false - end - -- If we explode due to something other than terrain, then we should explode ON the tile, not before it - elseif typ.stop_block then - local nb = game.level.map:checkAllEntitiesCount(lx, ly, "block_move") - -- Reduce for pass_projectile or pass_terrain, which was handled above - if game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and (typ.pass_terrain or game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile")) then - nb = nb - 1 - end - -- Reduce the nb blocking for friendlies - if not typ.friendlyblock and typ.source_actor and typ.source_actor.reactionToward then - local a = game.level.map(lx, ly, engine.Map.ACTOR) - if a and typ.source_actor:reactionToward(a) > 0 then - nb = nb - 1 - end - end - if nb > 0 then - if for_highlights then - -- Targeting highlight should be yellow if we don't know what we're firing through - if not is_known then - return false, "unknown", true - -- Don't show the path as blocked if it's blocked by an actor we can't see - elseif nb == 1 and typ.source_actor and typ.source_actor.canSee and not typ.source_actor:canSee(game.level.map(lx, ly, engine.Map.ACTOR)) then - return false, true, true - end - end - return true, true, true - end - end - if for_highlights and not is_known then - return false, "unknown", true - end - end - -- If we don't block the path, then the explode point should be here - return false, true, true - end, - block_radius = function(typ, lx, ly, for_highlights) - return not typ.no_restrict and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") and not game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "pass_projectile") and not (for_highlights and not (game.level.map.remembers(lx, ly) or game.level.map.seens(lx, ly))) - end } + for k, v in pairs(self.defaults) do target_type[k] = v end -- And now modify for the default types if t.type then - if t.type:find("ball") then - target_type.ball = t.radius - end - if t.type:find("cone") then - target_type.cone = t.radius - target_type.cone_angle = t.cone_angle or 55 - target_type.selffire = false - end - if t.type:find("wall") then - if util.isHex() then - --with a hex grid, a wall should only be defined by the number of spots - t.halfmax_spots = t.halflength - t.halflength = 2*t.halflength - end - target_type.wall = t.halflength - end - if t.type:find("bolt") then - target_type.stop_block = true - elseif t.type:find("beam") then - target_type.line = true + for type_name, fun in pairs(self.types_def) do + if t.type:find(type_name) then fun(target_type, t) end end end + table.update(t, target_type) return t end @@ -551,7 +608,7 @@ function _M:scan(dir, radius, sx, sy, filter, kind) sx = sx or self.target.x sy = sy or self.target.y if not sx or not sy then return end - + kind = kind or engine.Map.ACTOR radius = radius or 20 local actors = {} @@ -602,4 +659,3 @@ function _M:pointAtRange(srcx, srcy, destx, desty, dist) return lx, ly end end - diff --git a/game/engines/default/engine/interface/ActorProject.lua b/game/engines/default/engine/interface/ActorProject.lua index 5f45b8376f..3e82304c65 100644 --- a/game/engines/default/engine/interface/ActorProject.lua +++ b/game/engines/default/engine/interface/ActorProject.lua @@ -56,6 +56,7 @@ function _M:project(t, x, y, damtype, dam, particles) local grids = {} local function addGrid(x, y) + if typ.filter and not typ.filter(x, y) then return end if not grids[x] then grids[x] = {} end grids[x][y] = true end @@ -112,7 +113,9 @@ function _M:project(t, x, y, damtype, dam, particles) end end + local single_target = true if typ.ball and typ.ball > 0 then + single_target = false core.fov.calc_circle( stop_radius_x, stop_radius_y, @@ -128,7 +131,10 @@ function _M:project(t, x, y, damtype, dam, particles) end, nil) addGrid(stop_x, stop_y) - elseif typ.cone and typ.cone > 0 then + end + + if typ.cone and typ.cone > 0 then + single_target = false --local dir_angle = math.deg(math.atan2(y - self.y, x - self.x)) core.fov.calc_beam_any_angle( stop_radius_x, @@ -149,7 +155,10 @@ function _M:project(t, x, y, damtype, dam, particles) end, nil) addGrid(stop_x, stop_y) - elseif typ.wall and typ.wall > 0 then + end + + if typ.wall and typ.wall > 0 then + single_target = false core.fov.calc_wall( stop_radius_x, stop_radius_y, @@ -168,11 +177,11 @@ function _M:project(t, x, y, damtype, dam, particles) addGrid(px, py) end, nil) - else - -- Deal damage: single - addGrid(stop_x, stop_y) end + -- Deal damage: single + if single_target then addGrid(stop_x, stop_y) end + -- Check for minimum range if typ.min_range and core.fov.distance(typ.start_x, typ.start_y, stop_x, stop_y) < typ.min_range then return @@ -188,7 +197,7 @@ function _M:project(t, x, y, damtype, dam, particles) end end end - + self:check("on_project_grids", grids) -- Now project on each grid, one type -- GitLab