diff --git a/game/engines/default/engine/Projectile.lua b/game/engines/default/engine/Projectile.lua index 3ee1f020b867f703737791ea2431680fc0b8e805..a4afb08e6831d3c6a24190d6ccb80f35bd11b9b3 100644 --- a/game/engines/default/engine/Projectile.lua +++ b/game/engines/default/engine/Projectile.lua @@ -195,12 +195,14 @@ function _M:act() if x and y then self:move(x, y) end if act then self.src:projectDoAct(self.project.def.typ, self.project.def.tg, self.project.def.damtype, self.project.def.dam, self.project.def.particles, self.x, self.y, self.tmp_proj) end if stop then - -- Correct the explosion source position if we exploded on terrain - local radius_x, radius_y + local block, hit, hit_radius = false, true, true if self.project.def.typ.block_path then - _, radius_x, radius_y = self.project.def.typ:block_path(self.x, self.y) + block, hit, hit_radius = self.project.def.typ:block_path(self.x, self.y) end - if not radius_x then + local radius_x, radius_y + if hit_radius then + radius_x, radius_y = self.x, self.y + else radius_x, radius_y = self.old_x, self.old_y end game.level:removeEntity(self) diff --git a/game/engines/default/engine/Target.lua b/game/engines/default/engine/Target.lua index 5e2170f7a21949c639b2b8733fba8a085edb26fa..294dd5a3e1038a98b7c5be11a7d2ef40de9219a1 100644 --- a/game/engines/default/engine/Target.lua +++ b/game/engines/default/engine/Target.lua @@ -75,44 +75,46 @@ function _M:display(dispx, dispy) local lx, ly = l() local initial_dir = lx and coord_to_dir[lx - self.source_actor.x][ly - self.source_actor.y] or 5 local stop_x, stop_y = self.source_actor.x, self.source_actor.y - local stop_radius_x, stop_radius_y = stopx, stopy - local blocked = false + local stop_radius_x, stop_radius_y = self.source_actor.x, self.source_actor.y + local stopped = false while lx and ly do - if s == self.sb then - stop_radius_x, stop_radius_y = stop_x, stop_y + local 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) + end + + -- Update coordinates and set color + if hit and not stopped then stop_x, stop_y = lx, ly + else + s = self.sr + end + if hit_radius and not stopped then + stop_radius_x, stop_radius_y = lx, ly end - if self.target_type.min_range then + if self.target_type.min_range and not stopped then -- Check if we should be "red" if core.fov.distance(self.source_actor.x, self.source_actor.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 and not blocked then + elseif s == self.sr then s = self.sb end end s:toScreen(self.display_x + (lx - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (ly - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom) - if self.target_type.block_path and self.target_type:block_path(lx, ly) then + if block then s = self.sr - blocked = true + stopped = true end + lx, ly = l() end self.cursor:toScreen(self.display_x + (self.target.x - game.level.map.mx) * self.tile_w * Map.zoom, self.display_y + (self.target.y - game.level.map.my) * self.tile_h * Map.zoom, self.tile_w * Map.zoom, self.tile_h * Map.zoom) - -- Correct the explosion source position if we exploded on terrain - local radius_x, radius_y - if self.target_type.block_path and self.target_type.radius and self.target_type.radius > 0 then - _, radius_x, radius_y = self.target_type:block_path(stop_x, stop_y) - end - if not radius_x then - radius_x, radius_y = stop_radius_x, stop_radius_y - end - if self.target_type.ball and self.target_type.ball > 0 then core.fov.calc_circle( - radius_x, - radius_y, + stop_radius_x, + stop_radius_y, game.level.map.w, game.level.map.h, self.target_type.ball, @@ -128,8 +130,8 @@ function _M:display(dispx, dispy) nil) elseif self.target_type.cone and self.target_type.cone > 0 then core.fov.calc_beam( - radius_x, - radius_y, + stop_radius_x, + stop_radius_y, game.level.map.w, game.level.map.h, self.target_type.cone, @@ -164,7 +166,7 @@ end -- @param t.stop_block Boolean that stops the target on the first tile that has an entity that blocks move. -- @param t.range The range the target can be from the origin. -- @param t.pass_terrain Boolean that allows the target to pass through terrain to remembered tiles on the other side. --- @param t.block_path(typ, lx, ly) Function called on each tile to determine if the targeting is blocked. Automatically set when using t.typ, but the user can provide their own if they know what they are doing. +-- @param t.block_path(typ, lx, ly) Function called on each tile to determine if the targeting is blocked. Automatically set when using t.typ, but the user can provide their own if they know what they are doing. It should return three arguments: block, hit, hit_radius -- @param t.block_radius(typ, lx, ly) Function called on each tile when projecting the radius to determine if the radius projection is blocked. Automatically set when using t.typ, but the user can provide their own if they know what they are doing. function _M:getType(t) if not t then return {} end @@ -177,14 +179,27 @@ function _M:getType(t) friendlyfire=true, block_path = function(typ, lx, ly) if not typ.no_restrict then - if typ.requires_knowledge and not game.level.map.remembers(lx, ly) and not game.level.map.seens(lx, ly) then return true end - if not typ.pass_terrain and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then return true + if 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) + -- Need to handle range 1 separately to allow diagonal hits + if typ.range == 1 then + if math.floor(dist) > 1 then return true, false, false end + else + if dist > typ.range then return true, false, false end + end + end + if typ.requires_knowledge and not game.level.map.remembers(lx, ly) and not game.level.map.seens(lx, ly) then + return true, false, false + end + if not typ.pass_terrain and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") then + return true, true, false -- If we explode due to something other than terrain, then we should explode ON the tile, not before it - elseif typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then return true, lx, ly end - if typ.range and typ.source_actor and typ.source_actor.x and core.fov.distance(typ.source_actor.x, typ.source_actor.y, lx, ly) > typ.range then return true end + elseif typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then + return true, true, true + end end -- If we don't block the path, then the explode point should be here - return false, lx, ly + return false, true, true end, block_radius=function(typ, lx, ly) return not typ.no_restrict and game.level.map:checkEntity(lx, ly, engine.Map.TERRAIN, "block_move") diff --git a/game/engines/default/engine/interface/ActorProject.lua b/game/engines/default/engine/interface/ActorProject.lua index 22933c49610cb5b7c7720fce4c417f7353c4aa17..42f023e89137a3820296faf8cb3c4aacdab1e414 100644 --- a/game/engines/default/engine/interface/ActorProject.lua +++ b/game/engines/default/engine/interface/ActorProject.lua @@ -60,42 +60,60 @@ function _M:project(t, x, y, damtype, dam, particles) lx, ly = l() local initial_dir = lx and coord_to_dir[lx - srcx][ly - srcy] or 5 while lx and ly do - stop_radius_x, stop_radius_y = stop_x, stop_y - stop_x, stop_y = lx, ly - - -- Deal damage: beam - if typ.line then addGrid(lx, ly) end - - -- Call the on project of the target grid if possible - if not t.bypass and game.level.map:checkAllEntities(lx, ly, "on_project", self, t, lx, ly, damtype, dam, particles) then - return + local block, hit, hit_radius = false, true, true + if typ.block_path then + block, hit, hit_radius = typ:block_path(lx, ly) + end + if hit then + stop_x, stop_y = lx, ly + -- Deal damage: beam + if typ.line then addGrid(lx, ly) end + -- WHAT DOES THIS DO AGAIN? + -- Call the on project of the target grid if possible + if not t.bypass and game.level.map:checkAllEntities(lx, ly, "on_project", self, t, lx, ly, damtype, dam, particles) then + return + end + end + if hit_radius then + stop_radius_x, stop_radius_y = lx, ly end - if typ.block_path and typ:block_path(lx, ly) then break end + if block then break end lx, ly = l() end - -- Correct the explosion source position if we exploded on terrain - local radius_x, radius_y - if typ.block_path then - _, radius_x, radius_y = typ:block_path(stop_x, stop_y) - end - if not radius_x then - radius_x, radius_y = stop_radius_x, stop_radius_y - end if typ.ball and typ.ball > 0 then - core.fov.calc_circle(radius_x, radius_y, game.level.map.w, game.level.map.h, typ.ball, function(_, px, py) - -- Deal damage: ball - addGrid(px, py) - if typ.block_radius and typ:block_radius(px, py) then return true end - end, function()end, nil) + core.fov.calc_circle( + stop_radius_x, + stop_radius_y, + game.level.map.w, + game.level.map.h, + typ.ball, + function(_, px, py) + if typ.block_radius and typ:block_radius(px, py) then return true end + end, + function(_, px, py) + -- Deal damage: ball + addGrid(px, py) + end, + nil) addGrid(stop_x, stop_y) elseif typ.cone and typ.cone > 0 then - core.fov.calc_beam(radius_x, radius_y, game.level.map.w, game.level.map.h, typ.cone, initial_dir, typ.cone_angle, function(_, px, py) - -- Deal damage: cone - addGrid(px, py) - if typ.block_radius and typ:block_radius(px, py) then return true end - end, function()end, nil) + core.fov.calc_beam( + stop_radius_x, + stop_radius_y, + game.level.map.w, + game.level.map.h, + typ.cone, + initial_dir, + typ.cone_angle, + function(_, px, py) + if typ.block_radius and typ:block_radius(px, py) then return true end + end, + function(_, px, py) + addGrid(px, py) + end, + nil) addGrid(stop_x, stop_y) else -- Deal damage: single @@ -154,19 +172,19 @@ function _M:canProject(t, x, y) local l = line.new(self.x, self.y, x, y) lx, ly = l() while lx and ly do - stop_radius_x, stop_radius_y = stop_x, stop_y - stop_x, stop_y = lx, ly - if typ.block_path and typ:block_path(lx, ly) then break end - lx, ly = l() - end + local block, hit, hit_radius = false, true, true + if typ.block_path then + block, hit, hit_radius = typ:block_path(lx, ly) + end + if hit then + stop_x, stop_y = lx, ly + end + if hit_radius then + stop_radius_x, stop_radius_y = lx, ly + end - -- Correct the explosion source position if we exploded on terrain - local radius_x, radius_y - if typ.block_path then - _, radius_x, radius_y = typ:block_path(stop_x, stop_y) - end - if not radius_x then - radius_x, radius_y = stop_radius_x, stop_radius_y + if block then break end + lx, ly = l() end -- Check for minimum range @@ -175,7 +193,7 @@ function _M:canProject(t, x, y) end if stop_x == x and stop_y == y then return true, stop_x, stop_y, stop_x, stop_y end - return false, stop_x, stop_y, radius_x, radius_y + return false, stop_x, stop_y, stop_radius_x, stop_radius_y end --- Project damage to a distance using a moving projectile @@ -196,6 +214,13 @@ function _M:projectile(t, x, y, damtype, dam, particles) game.zone:addEntity(game.level, proj, "projectile", self.x, self.y) end +-- @param typ a target type table +-- @param tgtx the target's x-coordinate +-- @param tgty the target's y-coordinate +-- @param x the projectile's x-coordinate +-- @param y the projectile's x-coordinate +-- @param srcx the sources's x-coordinate +-- @param srcx the source's x-coordinate -- @return lx x-coordinate the projectile travels to next -- @return ly y-coordinate the projectile travels to next -- @return act should we call projectDoAct (usually only for beam) @@ -210,10 +235,24 @@ function _M:projectDoMove(typ, tgtx, tgty, x, y, srcx, srcy) if lx and ly then lx, ly = l() end if lx and ly then - if typ.block_path and typ:block_path(lx, ly) then return lx, ly, false, true end + local block, hit, hit_radius = false, true, true + if typ.block_path then + block, hit, hit_radius = typ:block_path(lx, ly) + end + + if block then + if hit then + return lx, ly, false, true + -- If we don't hit the tile, pass back nils to stop on the current spot + else + return nil, nil, false, true + end + end -- End of the map - if lx < 0 or lx >= game.level.map.w or ly < 0 or ly >= game.level.map.h then return lx, ly, false, true end + if lx < 0 or lx >= game.level.map.w or ly < 0 or ly >= game.level.map.h then + return nil, nil, false, true + end -- Deal damage: beam if typ.line and (lx ~= tgtx or ly ~= tgty) then return lx, ly, true, false end @@ -251,19 +290,39 @@ function _M:projectDoStop(typ, tg, damtype, dam, particles, lx, ly, tmp, rx, ry) end if typ.ball and typ.ball > 0 then - core.fov.calc_circle(rx, ry, game.level.map.w, game.level.map.h, typ.ball, function(_, px, py) - -- Deal damage: ball - addGrid(px, py) - if typ.block_radius and typ:block_radius(px, py) then return true end - end, function()end, nil) + core.fov.calc_circle( + rx, + ry, + game.level.map.w, + game.level.map.h, + typ.ball, + function(_, px, py) + if typ.block_radius and typ:block_radius(px, py) then return true end + end, + function(_, px, py) + -- Deal damage: ball + addGrid(px, py) + end, + nil) addGrid(rx, ry) elseif typ.cone and typ.cone > 0 then local initial_dir = lx and util.getDir(lx, ly, x, y) or 5 - core.fov.calc_beam(rx, ry, game.level.map.w, game.level.map.h, typ.cone, initial_dir, typ.cone_angle, function(_, px, py) - -- Deal damage: cone - addGrid(px, py) - if typ.block_radius and typ:block_radius(px, py) then return true end - end, function()end, nil) + core.fov.calc_beam( + rx, + ry, + game.level.map.w, + game.level.map.h, + typ.cone, + initial_dir, + typ.cone_angle, + function(_, px, py) + if typ.block_radius and typ:block_radius(px, py) then return true end + end, + function(_, px, py) + -- Deal damage: cone + addGrid(px, py) + end, + nil) addGrid(rx, ry) else -- Deal damage: single diff --git a/game/modules/tome/dialogs/MapMenu.lua b/game/modules/tome/dialogs/MapMenu.lua index a72b28ea2221608b232ac94310f5843901fb9a7b..39d928d54ffb9fa931575681ea6264b54cf7815d 100644 --- a/game/modules/tome/dialogs/MapMenu.lua +++ b/game/modules/tome/dialogs/MapMenu.lua @@ -103,17 +103,26 @@ function _M:generateList() -- Talents if game.zone and not game.zone.wilderness then + local canHit = function(tg, x, y) + local hit = false + player:project(tg, x, y, function(px, py) + if px == x and py == y then + hit = true + end + end) + return hit + end local tals = {} for tid, _ in pairs(player.talents) do local t = player:getTalentFromId(tid) local t_avail = false local tg = player:getTalentTarget(t) - local default_tg = {type=util.getval(t.direct_hit, player, t) and "hit" or "bolt"} - local total_dist = (player:getTalentRange(t) + player:getTalentRadius(t)) + local total_range = player:getTalentRange(t) + player:getTalentRadius(t) + local default_tg = {type=util.getval(t.direct_hit, player, t) and "hit" or "bolt", range=total_range} if t.mode == "activated" and not player:isTalentCoolingDown(t) and player:preUseTalent(t, true, true) and (not player:getTalentRequiresTarget(t) or - player:canProject(tg or default_tg, self.tmx, self.tmy)) + canHit(tg or default_tg, self.tmx, self.tmy)) then t_avail = true elseif t.mode == "sustained" and not t.no_npc_use and